puppet-5.5.10/0000755005276200011600000000000013417162177013020 5ustar jenkinsjenkinspuppet-5.5.10/COMMITTERS.md0000644005276200011600000002705113417161721015027 0ustar jenkinsjenkinsCommitting changes to Puppet ==== We would like to make it easier for community members to contribute to Puppet using pull requests, even if it makes the task of reviewing and committing these changes a little harder. Pull requests are only ever based on a single branch, however, we maintain more than one active branch. As a result contributors should target their changes at the master branch. This makes the process of contributing a little easier for the contributor since they don't need to concern themselves with the question, "What branch do I base my changes on?" This is already called out in the [CONTRIBUTING.md](https://goo.gl/XRH2J). Therefore, it is the responsibility of the committer to re-base the change set on the appropriate branch which should receive the contribution. It is also the responsibility of the committer to review the change set in an effort to make sure the end users must opt-in to new behavior that is incompatible with previous behavior. We employ the use of [feature flags](https://stackoverflow.com/questions/7707383/what-is-a-feature-flag) as the primary way to achieve this user opt-in behavior. Finally, it is the responsibility of the committer to make sure the `master` and `stable` branches are both clean and working at all times. Clean means that dead code is not allowed, everything needs to be usable in some manner at all points in time. Stable is not an indication of the build status, but rather an expression of our intent that the `stable` branch does not receive new functionality. The rest of this document addresses the concerns of the committer. This document will help guide the committer decide which branch to base, or re-base a contribution on top of. This document also describes our branch management strategy, which is closely related to the decision of what branch to commit changes into. Terminology ==== Many of these terms have more than one meaning. For the purposes of this document, the following terms refer to specific things. **contributor** - A person who makes a change to Puppet and submits a change set in the form of a pull request. **change set** - A set of discrete patches which combined together form a contribution. A change set takes the form of Git commits and is submitted to Puppet in the form of a pull request. **committer** - A person responsible for reviewing a pull request and then making the decision what base branch to merge the change set into. **base branch** - A branch in Git that contains an active history of changes and will eventually be released using semantic version guidelines. The branch named `master` will always exist as a base branch. The other base branches are `stable`, and `security` described below. **master branch** - The branch where new functionality that are not bug fixes is merged. **stable branch** - The branch where bug fixes against the latest release or release candidate are merged. **security** - Where critical security fixes are merged. These change sets will then be merged into release branches independently from one another. (i.e. no merging up). Please do not submit pull requests against the security branch and instead report all security related issues to security@puppetlabs.com as per our security policy published at [https://puppetlabs.com/security/](https://puppetlabs.com/security/). Committer Guide ==== This section provides a guide to follow while committing change sets to Puppet base branches. How to decide what release(s) should be patched --- This section provides a guide to help a committer decide the specific base branch that a change set should be merged into. The latest minor release of a major release is the only base branch that should be patched. These patches will be merged into `master` if they contain new functionality. They will be merged into `stable` and `master` if they fix a critical bug. Older minor releases in a major release do not get patched. Before the switch to [semantic versions](http://semver.org/) committers did not have to think about the difference between minor and major releases. Committing to the latest minor release of a major release is a policy intended to limit the number of active base branches that must be managed. Security patches are handled as a special case. Security patches may be applied to earlier minor releases of a major release, but the patches should first be merged into the `security` branch. Security patches should be merged by Puppet Labs staff members. Pull requests should not be submitted with the security branch as the base branch. Please send all security related information or patches to security@puppetlabs.com as per our [Security Policy](https://puppetlabs.com/security/). The CI systems are configured to run against `master` and `stable`. Over time, these branches will refer to different versions, but their name will remain fixed to avoid having to update CI jobs and tasks as new versions are released. How to commit a change set to multiple base branches --- A change set may apply to multiple branches, for example a bug fix should be applied to the stable release and the development branch. In this situation the change set needs to be committed to multiple base branches. This section provides a guide for how to merge patches into these branches, e.g. `stable` is patched, how should the changes be applied to `master`? First, rebase the change set onto the `stable` branch. Next, merge the change set into the `stable` branch using a merge commit. Once merged into `stable`, merge the same change set into `master` without doing a rebase as to preserve the commit identifiers. This merge strategy follows the [git flow](http://nvie.com/posts/a-successful-git-branching-model/) model. Both of these change set merges should have a merge commit which makes it much easier to track a set of commits as a logical change set through the history of a branch. Merge commits should be created using the `--no-ff --log` git merge options. Any merge conflicts should be resolved using the merge commit in order to preserve the commit identifiers for each individual change. This ensures `git branch --contains` will accurately report all of the base branches which contain a specific patch. Using this strategy, the stable branch need not be reset. Both `master` and `stable` have infinite lifetimes. Patch versions, also known as bug fix releases, will be tagged and released directly from the `stable` branch. Major and minor versions, also known as feature releases, will be tagged and released directly from the `master` branch. Upon release of a new major or minor version all of the changes in the `master` branch will be merged into the `stable` branch. Code review checklist --- This section aims to provide a checklist of things to look for when reviewing a pull request and determining if the change set should be merged into a base branch: * All tests pass * Are there any platform gotchas? (Does a change make an assumption about platform specific behavior that is incompatible with other platforms? e.g. Windows paths vs. POSIX paths.) * Is the change backwards compatible? (It should be) * Are there YARD docs for API changes? * Does the change set also require documentation changes? If so is the documentation being kept up to date? * Does the change set include clean code? (software code that is formatted correctly and in an organized manner so that another coder can easily read or modify it.) HINT: `git diff master --check` * Does the change set conform to the contributing guide? Commit citizen guidelines: --- This section aims to provide guidelines for being a good commit citizen by paying attention to our automated build tools. * Don’t push on a broken build. (A broken build is defined as a failing job in the [Puppet FOSS](https://jenkins.puppetlabs.com/view/Puppet%20FOSS/) page.) * Watch the build until your changes have gone through green * Update the ticket status and target version. The target version field in our issue tracker should be updated to be the next release of Puppet. For example, if the most recent release of Puppet is 3.1.1 and you merge a backwards compatible change set into master, then the target version should be 3.2.0 in the issue tracker.) * Ensure the pull request is closed (Hint: amend your merge commit to contain the string `closes #123` where 123 is the pull request number and github will automatically close the pull request when the branch is pushed.) Example Procedure ==== This section helps a committer rebase a contribution onto an earlier base branch, then merge into the base branch and up through all active base branches. Suppose a contributor submits a pull request based on master. The change set fixes a bug reported against Puppet 3.1.1 which is the most recently released version of Puppet. In this example the committer should rebase the change set onto the `stable` branch since this is a bug rather than new functionality. First, the committer pulls down the branch using the `hub` gem. This tool automates the process of adding the remote repository and creating a local branch to track the remote branch. $ hub checkout https://github.com/puppetlabs/puppet/pull/1234 Branch jeffmccune-fix_foo_error set up to track remote branch fix_foo_error from jeffmccune. Switched to a new branch 'jeffmccune-fix_foo_error' At this point the topic branch is a descendant of master, but we want it to descend from `stable`. The committer rebases the change set onto `stable`. $ git branch bug/stable/fix_foo_error $ git rebase --onto stable master bug/stable/fix_foo_error First, rewinding head to replay your work on top of it... Applying: (#23456) Fix FooError that always bites users in 3.1.1 The `git rebase` command may be interpreted as, "First, check out the branch named `bug/stable/fix_foo_error`, then take the changes that were previously based on `master` and re-base them onto `stable`. Now that we have a topic branch containing the change set based on the `stable` release branch, the committer merges in: $ git checkout stable Switched to branch 'stable' $ git merge --no-ff --log bug/stable/fix_foo_error Merge made by the 'recursive' strategy. foo | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 foo Once merged into the first base branch, the committer merges the `stable` branch into `master`, being careful to preserve the same commit identifiers. $ git checkout master Switched to branch 'master' $ git merge --no-ff --log stable Merge made by the 'recursive' strategy. foo | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 foo Once the change set has been merged into one base branch, the change set should not be modified in order to keep the history clean, avoid "double" commits, and preserve the usefulness of `git branch --contains`. If there are any merge conflicts, they are to be resolved in the merge commit itself and not by re-writing (rebasing) the patches for one base branch, but not another. Once the change set has been merged into `stable` and into `master`, the committer pushes. Please note, the checklist should be complete at this point. It's helpful to make sure your local branches are up to date to avoid one of the branches failing to fast forward while the other succeeds. Both the `stable` and `master` branches are being pushed at the same time. $ git push puppetlabs master:master stable:stable That's it! The committer then updates the pull request, updates the issue in our issue tracker, and keeps an eye on the [build status](http://jenkins.puppetlabs.com). puppet-5.5.10/CONTRIBUTING.md0000644005276200011600000002067013417161721015250 0ustar jenkinsjenkins# How to contribute Third-party patches are essential for keeping Puppet great. We simply can't access the huge number of platforms and myriad configurations for running Puppet. 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. ## Puppet Core vs Modules New functionality is typically directed toward modules to provide a slimmer Puppet Core, reducing its surface area, and to allow greater freedom for module maintainers to ship releases at their own cadence, rather than being held to the cadence of Puppet releases. With Puppet 4's "all in one" packaging, a list of modules at specific versions will be packaged with the core so that popular types and providers will still be available as part of the "out of the box" experience. Generally, new types and new OS-specific providers for existing types should be added in modules. Exceptions would be things like new cross-OS providers and updates to existing core types. If you are unsure of whether your contribution should be implemented as a module or part of Puppet Core, you may visit [#puppet-dev on slack](https://puppetcommunity.slack.com/), or ask on the [puppet-dev mailing list](https://groups.google.com/forum/#!forum/puppet-dev) for advice. ## Getting Started * Make sure you have a [Jira account](https://tickets.puppetlabs.com). * Make sure you have a [GitHub account](https://github.com/signup/free). * Submit a Jira ticket for your issue if 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. * A ticket is not necessary for [trivial changes](https://puppet.com/community/trivial-patch-exemption-policy) * 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, run `git checkout -b fix/master/my_contribution master`. Please avoid working directly on the `master` branch. * Make commits of logical and atomic units. * Check for unnecessary whitespace with `git diff --check` before committing. * Make sure your commit messages are in the proper format. If the commit addresses an issue filed in the [Puppet Jira project](https://tickets.puppetlabs.com/browse/PUP), start the first line of the commit with the issue number in parentheses. ``` (PUP-1234) 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. * For details on how to run tests, please see [the quickstart guide](https://github.com/puppetlabs/puppet/blob/master/docs/quickstart.md) ## Writing Translatable Code We use [gettext tooling](https://github.com/puppetlabs/gettext-setup-gem) to extract user-facing strings and pull in translations based on the user's locale at runtime. In order for this tooling to work, all user-facing strings must be wrapped in the `_()` translation function, so they can be extracted into files for the translators. When adding user-facing strings to your work, follow these guidelines: * Use full sentences. Strings built up out of concatenated bits are hard to translate. * Use string formatting instead of interpolation. Use the hash format and give good names to the placeholder values that can be used by translators to understand the meaning of the formatted values. For example: `_('Creating new user %{name}.') % { name: user.name }` * Use `n_()` for pluralization. (see gettext gem docs linked above for details) It is the responsibility of contributors and code reviewers to ensure that all user-facing strings are marked in new PRs before merging. ## Making Trivial Changes For [changes of a trivial nature](https://puppet.com/community/trivial-patch-exemption-policy), it is not always necessary to create a new ticket in Jira. In this case, it is appropriate to start the first line of a commit with one of `(docs)`, `(maint)`, or `(packaging)` instead of a ticket number. If a Jira ticket exists for the documentation commit, you can include it after the `(docs)` token. ``` (docs)(DOCUMENT-000) Add docs commit example to CONTRIBUTING There is no example for contributing a documentation commit to the Puppet repository. This is a problem because the contributor is left to assume how a commit of this nature may appear. The first line is a real-life imperative statement with '(docs)' in place of what would have been the PUP project ticket number in a non-documentation related commit. The body describes the nature of the new documentation or comments added. ``` For commits that address trivial repository maintenance tasks or packaging issues, start the first line of the commit with `(maint)` or `(packaging)`, respectively. ## Submitting Changes * Sign the [Contributor License Agreement](http://links.puppet.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 Jira ticket to mark that you have submitted code and are ready for it to be reviewed (Status: Ready for Merge). * Include a link to the pull request in the ticket. * The core team looks at Pull Requests on a regular basis in a weekly triage meeting that we hold in a public Google Hangout. The hangout is announced in the weekly status updates that are sent to the puppet-dev list. Notes are posted to the [Puppet Community community-triage repo](https://github.com/puppet-community/community-triage/tree/master/core/notes) and include a link to a YouTube recording of the hangout. * After feedback has been given we expect responses within two weeks. After two weeks we may close the pull request if it isn't showing any activity. ## Revert Policy By running tests in advance and by engaging with peer review for prospective changes, your contributions have a high probability of becoming long lived parts of the the project. After being merged, the code will run through a series of testing pipelines on a large number of operating system environments. These pipelines can reveal incompatibilities that are difficult to detect in advance. If the code change results in a test failure, we will make our best effort to correct the error. If a fix cannot be determined and committed within 24 hours of its discovery, the commit(s) responsible _may_ be reverted, at the discretion of the committer and Puppet maintainers. This action would be taken to help maintain passing states in our testing pipelines. The original contributor will be notified of the revert in the Jira ticket associated with the change. A reference to the test(s) and operating system(s) that failed as a result of the code change will also be added to the Jira ticket. This test(s) should be used to check future submissions of the code to ensure the issue has been resolved. ### Summary * Changes resulting in test pipeline failures will be reverted if they cannot be resolved within one business day. ## Additional Resources * [Puppet community guidelines](https://puppet.com/community/community-guidelines) * [Bug tracker (Jira)](https://tickets.puppetlabs.com) * [Contributor License Agreement](http://links.puppet.com/cla) * [General GitHub documentation](https://help.github.com/) * [GitHub pull request documentation](https://help.github.com/articles/creating-a-pull-request/) * #puppet-dev IRC channel on freenode.org ([Archive](https://botbot.me/freenode/puppet-dev/)) * [puppet-dev mailing list](https://groups.google.com/forum/#!forum/puppet-dev) * [Community PR Triage notes](https://github.com/puppet-community/community-triage/tree/master/core/notes) puppet-5.5.10/MAINTAINERS0000644005276200011600000000220213417161721014503 0ustar jenkinsjenkins{ "version": 1, "file_format": "This MAINTAINERS file format is described at https://github.com/puppetlabs/maintainers", "issues": "https://tickets.puppet.com/browse/PUP", "internal_list": "https://groups.google.com/a/puppet.com/forum/?hl=en#!forum/discuss-puppet-maintainers", "people": [ { "github": "hlindberg", "email": "henrik.lindberg@puppet.com", "name": "Henrik Lindberg" }, { "github": "joshcooper", "email": "josh@puppet.com", "name": "Josh Cooper" }, { "github": "MikaelSmith", "email": "michael.smith@puppet.com", "name": "Michael Smith" }, { "github": "thallgren", "email": "thomas.hallgren@puppet.com", "name": "Thomas Hallgren" }, { "github": "branan", "email": "branan@puppet.com", "name": "Branan Riley" }, { "github": "Iristyle", "name": "Ethan J. Brown" }, { "github": "er0ck", "email": "eric.thompson@puppet.com", "name": "Eric Thompson" }, { "github": "jtappa", "email": "jorie@puppet.com", "name": "Jorie Tappa" } ] } puppet-5.5.10/Gemfile0000644005276200011600000000716413417161721014315 0ustar jenkinsjenkinssource ENV['GEM_SOURCE'] || "https://rubygems.org" gemspec def location_for(place, fake_version = nil) if place =~ /^(git[:@][^#]*)#(.*)/ [fake_version, { :git => $1, :branch => $2, :require => false }].compact elsif place =~ /^file:\/\/(.*)/ ['>= 0', { :path => File.expand_path($1), :require => false }] else [place, { :require => false }] end end group(:packaging) do gem 'packaging', *location_for(ENV['PACKAGING_LOCATION'] || '~> 0.99') end # C Ruby (MRI) or Rubinius, but NOT Windows platforms :ruby do gem 'pry', :group => :development gem 'redcarpet', '~> 2.0', :group => :development gem "racc", "1.4.9", :group => :development # To enable the augeas feature, use this gem. # Note that it is a native gem, so the augeas headers/libs # are neeed. #gem 'ruby-augeas', :group => :development end # override .gemspec deps - may issue warning depending on Bundler version gem "facter", *location_for(ENV['FACTER_LOCATION']) if ENV.has_key?('FACTER_LOCATION') gem "hiera", *location_for(ENV['HIERA_LOCATION']) if ENV.has_key?('HIERA_LOCATION') # PUP-7115 - return to a gem dependency in Puppet 5 # gem "semantic_puppet", *location_for(ENV['SEMANTIC_PUPPET_LOCATION'] || ['>= 0.1.3', '< 2']) group(:development, :test) do # rake is in .gemspec as a development dependency but cannot # be removed here *yet* due to TravisCI / AppVeyor which call: # bundle install --without development # PUP-7433 describes work necessary to restructure this gem "rake", *location_for(ENV['RAKE_LOCATION'] || '~> 12.2.1') if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('2.0.0') gem "rake", *location_for(ENV['RAKE_LOCATION'] || '~> 12.2') if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.0.0') # rubocop:disable Bundler/DuplicatedGem gem "rspec", "~> 3.1", :require => false gem "rspec-its", "~> 1.1", :require => false gem "rspec-collection_matchers", "~> 1.1", :require => false gem "rspec-legacy_formatters", "~> 1.0", :require => false # Mocha is not compatible across minor version changes; because of this only # versions matching ~> 0.10.5 are supported. All other versions are unsupported # and can be expected to fail. gem "mocha", "~> 0.10.5", :require => false gem "yarjuf", "~> 2.0" # json-schema does not support windows, so omit it from the platforms list gem "json-schema", "~> 2.0", :require => false, :platforms => [:ruby, :jruby] if RUBY_VERSION >= '2.0' # pin rubocop as 0.50 requires a higher version of the rainbow gem (see below) gem 'rubocop', '~> 0.49.1', :platforms => [:ruby] gem 'rubocop-i18n', '~> 1.2.0', :platforms => [:ruby] end # pin rainbow gem as 2.2.1 requires rubygems 2.6.9+ and (donotwant) gem "rainbow", "< 2.2.1", :platforms => [:ruby] gem 'rdoc', "~> 4.1", :platforms => [:ruby] gem 'yard' # ronn is used for generating manpages. gem 'ronn', '~> 0.7.3', :platforms => [:ruby] # webmock requires addressable as as of 2.5.0 addressable started # requiring the public_suffix gem which requires Ruby 2 gem 'addressable', '< 2.5.0' gem 'webmock', '~> 1.24' gem 'vcr', '~> 2.9' gem "hiera-eyaml", :require => false gem 'memory_profiler', :platforms => [:mri_21, :mri_22, :mri_23, :mri_24, :mri_25] end group(:development) do if RUBY_PLATFORM != 'java' gem 'ruby-prof', '>= 0.16.0', :require => false end gem 'gettext-setup', '~> 0.28', :require => false end group(:extra) do gem "rack", "~> 1.4", :require => false gem "puppetlabs_spec_helper", :require => false gem "msgpack", :require => false end if File.exists? "#{__FILE__}.local" eval(File.read("#{__FILE__}.local"), binding) end # vim:filetype=ruby puppet-5.5.10/LICENSE0000644005276200011600000000127113417161721014020 0ustar jenkinsjenkins Puppet - Automating Configuration Management. Copyright (C) 2005-2016 Puppet, Inc. Puppet, Inc. can be contacted at: info@puppet.com Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. puppet-5.5.10/README.md0000644005276200011600000000665613417161721014306 0ustar jenkinsjenkinsPuppet ====== [![Travis Status](https://travis-ci.org/puppetlabs/puppet.svg?branch=master)](https://travis-ci.org/puppetlabs/puppet) [![Appveyor Status](https://ci.appveyor.com/api/projects/status/cvhpypd4504sevqq/branch/master?svg=true)](https://ci.appveyor.com/project/puppetlabs/puppet/branch/master) [![Gem Version](https://badge.fury.io/rb/puppet.svg)](https://badge.fury.io/rb/puppet) [![Inline docs](https://inch-ci.org/github/puppetlabs/puppet.svg)](https://inch-ci.org/github/puppetlabs/puppet) Puppet, an automated administrative engine for your Linux, Unix, and Windows systems, performs administrative tasks (such as adding users, installing packages, and updating server configurations) based on a centralized specification. Documentation ------------- Documentation for Puppet and related projects can be found online at the [Puppet Docs site](https://puppet.com/docs). HTTP API -------- [HTTP API Index](https://puppet.com/docs/puppet/5.5/http_api/http_api_index.html) Installation ------------ The best way to run Puppet is with [Puppet Enterprise (PE)](https://puppet.com/puppet/puppet-enterprise), which also includes orchestration features, a web console, and professional support. The PE documentation is [available here.](https://puppet.com/docs/pe/latest) To install an open source release of Puppet, [see the installation guide on the docs site.](https://puppet.com/docs/puppet/5.5/install_pre.html) If you need to run Puppet from source as a tester or developer, see the [Running Puppet from Source](https://docs.puppet.com/puppet/3.8/from_source.html) guide on the docs site. Developing and Contributing ------ We'd love to get contributions from you! For a quick guide to getting your system setup for developing, take a look at our [Quickstart Guide](https://github.com/puppetlabs/puppet/blob/master/docs/quickstart.md). Once you are up and running, take a look at the [Contribution Documents](https://github.com/puppetlabs/puppet/blob/master/CONTRIBUTING.md) to see how to get your changes merged in. For more complete docs on developing with Puppet, take a look at the rest of the [developer documents](https://github.com/puppetlabs/puppet/blob/master/docs/index.md). License ------- See [LICENSE](https://github.com/puppetlabs/puppet/blob/master/LICENSE) file. Support ------- Please log tickets and issues at our [JIRA tracker](https://tickets.puppetlabs.com). A [mailing list](https://groups.google.com/forum/?fromgroups#!forum/puppet-users) is available for asking questions and getting help from others. In addition, there is an active #puppet channel on Freenode. We use semantic version numbers for our releases and recommend that users stay as up-to-date as possible by upgrading to patch releases and minor releases as they become available. Bugfixes and ongoing development will occur in minor releases for the current major version. Security fixes will be backported to a previous major version on a best-effort basis, until the previous major version is no longer maintained. For example: If a security vulnerability is discovered in Puppet 4.1.1, we would fix it in the 4 series, most likely as 4.1.2. Maintainers would then make a best effort to backport that fix onto the latest Puppet 3 release. Long-term support, including security patches and bug fixes, is available for commercial customers. Please see the following page for more details: [Puppet Enterprise Support Lifecycle](https://puppet.com/misc/puppet-enterprise-lifecycle) puppet-5.5.10/Rakefile0000644005276200011600000001101413417161721014454 0ustar jenkinsjenkins# Rakefile for Puppet -*- ruby -*- RAKE_ROOT = File.dirname(__FILE__) # We need access to the Puppet.version method $LOAD_PATH.unshift(File.expand_path("lib")) require 'puppet/version' $LOAD_PATH << File.join(RAKE_ROOT, 'tasks') begin require 'rubygems' require 'rubygems/package_task' rescue LoadError # Users of older versions of Rake (0.8.7 for example) will not necessarily # have rubygems installed, or the newer rubygems package_task for that # matter. require 'rake/packagetask' require 'rake/gempackagetask' end require 'rake' require 'open3' Dir['tasks/**/*.rake'].each { |t| load t } begin require 'packaging' Pkg::Util::RakeUtils.load_packaging_tasks rescue LoadError => e puts "Error loading packaging rake tasks: #{e}" end namespace :package do task :bootstrap do puts 'Bootstrap is no longer needed, using packaging-as-a-gem' end task :implode do puts 'Implode is no longer needed, using packaging-as-a-gem' end end task :default do sh %{rake -T} end task :spec do sh %{rspec #{ENV['TEST'] || ENV['TESTS'] || 'spec'}} end desc 'run static analysis with rubocop' task(:rubocop) do if RUBY_VERSION < '2.0' puts 'rubocop tests require Ruby 2.0 or higher' puts 'skipping rubocop' else require 'rubocop' cli = RuboCop::CLI.new exit_code = cli.run(%w(--display-cop-names --format simple)) raise "RuboCop detected offenses" if exit_code != 0 end end desc "verify that commit messages match CONTRIBUTING.md requirements" task(:commits) do # This rake task looks at the summary from every commit from this branch not # in the branch targeted for a PR. This is accomplished by using the # TRAVIS_COMMIT_RANGE environment variable, which is present in travis CI and # populated with the range of commits the PR contains. If not available, this # falls back to `master..HEAD` as a next best bet as `master` is unlikely to # ever be absent. commit_range = ENV['TRAVIS_COMMIT_RANGE'].nil? ? 'master..HEAD' : ENV['TRAVIS_COMMIT_RANGE'].sub(/\.\.\./, '..') puts "Checking commits #{commit_range}" %x{git log --no-merges --pretty=%s #{commit_range}}.each_line do |commit_summary| # This regex tests for the currently supported commit summary tokens: maint, doc, packaging, or pup-. # The exception tries to explain it in more full. if /^\((maint|doc|docs|packaging|l10n|pup-\d+)\)|revert/i.match(commit_summary).nil? raise "\n\n\n\tThis commit summary didn't match CONTRIBUTING.md guidelines:\n" \ "\n\t\t#{commit_summary}\n" \ "\tThe commit summary (i.e. the first line of the commit message) should start with one of:\n" \ "\t\t(PUP-) # this is most common and should be a ticket at tickets.puppet.com\n" \ "\t\t(docs)\n" \ "\t\t(docs)(DOCUMENT-)\n" \ "\t\t(maint)\n" \ "\t\t(packaging)\n" \ "\t\t(L10n)\n" \ "\n\tThis test for the commit summary is case-insensitive.\n\n\n" else puts "#{commit_summary}" end puts "...passed" end end desc "verify that changed files are clean of Ruby warnings" task(:warnings) do # This rake task looks at all files modified in this branch. This is # accomplished by using the TRAVIS_COMMIT_RANGE environment variable, which # is present in travis CI and populated with the range of commits the PR # contains. If not available, this falls back to `master..HEAD` as a next # best bet as `master` is unlikely to ever be absent. commit_range = ENV['TRAVIS_COMMIT_RANGE'].nil? ? 'master...HEAD' : ENV['TRAVIS_COMMIT_RANGE'] ruby_files_ok = true puts "Checking modified files #{commit_range}" %x{git diff --diff-filter=ACM --name-only #{commit_range}}.each_line do |modified_file| modified_file.chomp! # Skip racc generated file as it can have many warnings that cannot be manually fixed next if modified_file.end_with?("pops/parser/eparser.rb") next if modified_file.start_with?('spec/fixtures/', 'acceptance/fixtures/') || File.extname(modified_file) != '.rb' puts modified_file stdout, stderr, _ = Open3.capture3("ruby -wc \"#{modified_file}\"") unless stderr.empty? ruby_files_ok = false puts stderr end puts stdout end raise "One or more ruby files contain warnings." unless ruby_files_ok end if Rake.application.top_level_tasks.grep(/^gettext:/).any? begin spec = Gem::Specification.find_by_name 'gettext-setup' load "#{spec.gem_dir}/lib/tasks/gettext.rake" GettextSetup.initialize(File.absolute_path('locales', File.dirname(__FILE__))) rescue LoadError end end puppet-5.5.10/Gemfile.lock0000644005276200011600000000715013417162174015242 0ustar jenkinsjenkinsPATH remote: . specs: puppet (5.5.10) CFPropertyList (~> 2.2) facter (>= 2.4.0, < 4) fast_gettext (~> 1.1.2) hiera (>= 3.2.1, < 4) hocon (~> 1.0) locale (~> 2.1) multi_json (~> 1.13) net-ssh (>= 3.0, < 5) GEM remote: https://artifactory.delivery.puppetlabs.net/artifactory/api/gems/rubygems/ specs: CFPropertyList (2.3.6) addressable (2.4.0) artifactory (2.8.2) ast (2.4.0) builder (3.2.3) coderay (1.1.2) crack (0.4.3) safe_yaml (~> 1.0.0) diff-lcs (1.3) facter (2.5.1) fast_gettext (1.1.2) gettext (3.2.9) locale (>= 2.0.5) text (>= 1.3.0) gettext-setup (0.30) fast_gettext (~> 1.1.0) gettext (>= 3.0.2) locale hashdiff (0.3.8) hiera (3.5.0) hiera-eyaml (2.1.0) highline (~> 1.6.19) trollop (~> 2.0) highline (1.6.21) hocon (1.2.5) hpricot (0.8.6) json-schema (2.8.1) addressable (>= 2.4) locale (2.1.2) memory_profiler (0.9.12) metaclass (0.0.4) method_source (0.9.2) mocha (0.10.5) metaclass (~> 0.0.1) msgpack (1.2.6) multi_json (1.13.1) mustache (1.1.0) net-ssh (4.2.0) packaging (0.99.21) artifactory (~> 2) rake (~> 12.3) parallel (1.12.1) parser (2.5.3.0) ast (~> 2.4.0) powerpack (0.1.2) pry (0.12.2) coderay (~> 1.1.0) method_source (~> 0.9.0) puppet-lint (2.3.6) puppet-syntax (2.4.1) rake puppetlabs_spec_helper (1.1.1) mocha puppet-lint puppet-syntax rake rspec-puppet racc (1.4.9) rack (1.6.11) rainbow (2.1.0) rake (12.3.2) rdiscount (2.2.0.1) rdoc (4.3.0) redcarpet (2.3.0) ronn (0.7.3) hpricot (>= 0.8.2) mustache (>= 0.7.0) rdiscount (>= 1.5.8) rspec (3.8.0) rspec-core (~> 3.8.0) rspec-expectations (~> 3.8.0) rspec-mocks (~> 3.8.0) rspec-collection_matchers (1.1.3) rspec-expectations (>= 2.99.0.beta1) rspec-core (3.8.0) rspec-support (~> 3.8.0) rspec-expectations (3.8.2) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.8.0) rspec-its (1.2.0) rspec-core (>= 3.0.0) rspec-expectations (>= 3.0.0) rspec-legacy_formatters (1.0.1) rspec (~> 3.0) rspec-mocks (3.8.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.8.0) rspec-puppet (2.7.2) rspec rspec-support (3.8.0) rubocop (0.49.1) parallel (~> 1.10) parser (>= 2.3.3.1, < 3.0) powerpack (~> 0.1) rainbow (>= 1.99.1, < 3.0) ruby-progressbar (~> 1.7) unicode-display_width (~> 1.0, >= 1.0.1) rubocop-i18n (1.2.0) rubocop (~> 0.49.0) ruby-prof (0.17.0) ruby-progressbar (1.10.0) safe_yaml (1.0.4) text (1.3.1) trollop (2.9.9) unicode-display_width (1.4.1) vcr (2.9.3) webmock (1.24.6) addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff yard (0.9.16) yarjuf (2.0.0) builder rspec (~> 3) PLATFORMS ruby DEPENDENCIES addressable (< 2.5.0) gettext-setup (~> 0.28) hiera-eyaml json-schema (~> 2.0) memory_profiler mocha (~> 0.10.5) msgpack packaging (~> 0.99) pry puppet! puppetlabs_spec_helper racc (= 1.4.9) rack (~> 1.4) rainbow (< 2.2.1) rake (~> 12.2) rdoc (~> 4.1) redcarpet (~> 2.0) ronn (~> 0.7.3) rspec (~> 3.1) rspec-collection_matchers (~> 1.1) rspec-its (~> 1.1) rspec-legacy_formatters (~> 1.0) rubocop (~> 0.49.1) rubocop-i18n (~> 1.2.0) ruby-prof (>= 0.16.0) vcr (~> 2.9) webmock (~> 1.24) yard yarjuf (~> 2.0) BUNDLED WITH 1.16.5 puppet-5.5.10/install.rb0000755005276200011600000004244113417161721015015 0ustar jenkinsjenkins#! /usr/bin/env ruby #-- # Copyright 2004 Austin Ziegler # Install utility. Based on the original installation script for rdoc by the # Pragmatic Programmers. # # This program is free software. It may be redistributed and/or modified under # the terms of the GPL version 2 (or later) or the Ruby licence. # # Usage # ----- # In most cases, if you have a typical project layout, you will need to do # absolutely nothing to make this work for you. This layout is: # # bin/ # executable files -- "commands" # lib/ # the source of the library # # The default behaviour: # 1) Build Rdoc documentation from all files in bin/ (excluding .bat and .cmd), # all .rb files in lib/, ./README, ./ChangeLog, and ./Install. # 2) Build ri documentation from all files in bin/ (excluding .bat and .cmd), # and all .rb files in lib/. This is disabled by default on Microsoft Windows. # 3) Install commands from bin/ into the Ruby bin directory. On Windows, if a # if a corresponding batch file (.bat or .cmd) exists in the bin directory, # it will be copied over as well. Otherwise, a batch file (always .bat) will # be created to run the specified command. # 4) Install all library files ending in .rb from lib/ into Ruby's # site_lib/version directory. # #++ require 'rbconfig' require 'find' require 'fileutils' require 'tempfile' require 'optparse' require 'ostruct' begin require 'rdoc/rdoc' $haverdoc = true rescue LoadError puts "Missing rdoc; skipping documentation" $haverdoc = false end PREREQS = %w{openssl facter cgi hiera} MIN_FACTER_VERSION = 1.5 InstallOptions = OpenStruct.new def glob(list) g = list.map { |i| Dir.glob(i) } g.flatten! g.compact! g end def do_configs(configs, target, strip = 'conf/') Dir.mkdir(target) unless File.directory? target configs.each do |cf| ocf = File.join(InstallOptions.config_dir, cf.gsub(/#{strip}/, '')) FileUtils.install(cf, ocf, {:mode => 0644, :preserve => true, :verbose => true}) end if $operatingsystem == 'windows' src_dll = 'ext/windows/eventlog/puppetres.dll' dst_dll = File.join(InstallOptions.bin_dir, 'puppetres.dll') FileUtils.install(src_dll, dst_dll, {:mode => 0644, :preserve => true, :verbose => true}) require 'win32/registry' include Win32::Registry::Constants begin Win32::Registry::HKEY_LOCAL_MACHINE.create('SYSTEM\CurrentControlSet\services\eventlog\Application\Puppet', KEY_ALL_ACCESS | 0x0100) do |reg| reg.write_s('EventMessageFile', dst_dll.tr('/', '\\')) reg.write_i('TypesSupported', 0x7) end rescue Win32::Registry::Error => e warn "Failed to create puppet eventlog registry key: #{e}" end end end def do_bins(bins, target, strip = 's?bin/') Dir.mkdir(target) unless File.directory? target bins.each do |bf| obf = bf.gsub(/#{strip}/, '') install_binfile(bf, obf, target) end end def do_libs(libs, strip = 'lib/') libs.each do |lf| next if File.directory? lf olf = File.join(InstallOptions.site_dir, lf.sub(/^#{strip}/, '')) op = File.dirname(olf) FileUtils.makedirs(op, {:mode => 0755, :verbose => true}) FileUtils.chmod(0755, op) FileUtils.install(lf, olf, {:mode => 0644, :preserve => true, :verbose => true}) end end def do_man(man, strip = 'man/') man.each do |mf| omf = File.join(InstallOptions.man_dir, mf.gsub(/#{strip}/, '')) om = File.dirname(omf) FileUtils.makedirs(om, {:mode => 0755, :verbose => true}) FileUtils.chmod(0755, om) FileUtils.install(mf, omf, {:mode => 0644, :preserve => true, :verbose => true}) # Solaris does not support gzipped man pages. When called with # --no-check-prereqs/without facter the default gzip behavior still applies unless $operatingsystem == "Solaris" gzip = %x{which gzip} gzip.chomp! %x{#{gzip} -f #{omf}} end end end def do_locales(locale, strip = 'locales/') locale.each do |lf| next if File.directory? lf olf = File.join(InstallOptions.locale_dir, lf.sub(/^#{strip}/, '')) op = File.dirname(olf) FileUtils.makedirs(op, {:mode => 0755, :verbose => true}) FileUtils.chmod(0755, op) FileUtils.install(lf, olf, {:mode => 0644, :preserve => true, :verbose => true}) end end # Verify that all of the prereqs are installed def check_prereqs PREREQS.each { |pre| begin require pre if pre == "facter" # to_f isn't quite exact for strings like "1.5.1" but is good # enough for this purpose. facter_version = Facter.version.to_f if facter_version < MIN_FACTER_VERSION puts "Facter version: #{facter_version}; minimum required: #{MIN_FACTER_VERSION}; cannot install" exit -1 end end rescue LoadError puts "Could not load #{pre}; cannot install" exit -1 end } end ## # Prepare the file installation. # def prepare_installation InstallOptions.configs = true InstallOptions.check_prereqs = true InstallOptions.batch_files = true # Only try to do docs if we're sure they have rdoc if $haverdoc InstallOptions.rdoc = true InstallOptions.ri = true else InstallOptions.rdoc = false InstallOptions.ri = false end ARGV.options do |opts| opts.banner = "Usage: #{File.basename($0)} [options]" opts.separator "" opts.on('--[no-]rdoc', 'Prevents the creation of RDoc output.', 'Default on.') do |onrdoc| InstallOptions.rdoc = onrdoc end opts.on('--[no-]ri', 'Prevents the creation of RI output.', 'Default off on mswin32.') do |onri| InstallOptions.ri = onri end opts.on('--[no-]tests', 'Prevents the execution of unit tests.', 'Default off.') do |ontest| InstallOptions.tests = ontest warn "The tests flag is no longer functional in Puppet and is deprecated as of Dec 19, 2012. It will be removed in a future version of Puppet." end opts.on('--[no-]configs', 'Prevents the installation of config files', 'Default off.') do |ontest| InstallOptions.configs = ontest end opts.on('--destdir[=OPTIONAL]', 'Installation prefix for all targets', 'Default essentially /') do |destdir| InstallOptions.destdir = destdir end opts.on('--configdir[=OPTIONAL]', 'Installation directory for config files', 'Default /etc/puppetlabs/puppet') do |configdir| InstallOptions.configdir = configdir end opts.on('--codedir[=OPTIONAL]', 'Installation directory for code files', 'Default /etc/puppetlabs/code') do |codedir| InstallOptions.codedir = codedir end opts.on('--vardir[=OPTIONAL]', 'Installation directory for var files', 'Default /opt/puppetlabs/puppet/cache') do |vardir| InstallOptions.vardir = vardir end opts.on('--rundir[=OPTIONAL]', 'Installation directory for state files', 'Default /var/run/puppetlabs') do |rundir| InstallOptions.rundir = rundir end opts.on('--logdir[=OPTIONAL]', 'Installation directory for log files', 'Default /var/log/puppetlabs/puppet') do |logdir| InstallOptions.logdir = logdir end opts.on('--bindir[=OPTIONAL]', 'Installation directory for binaries', 'overrides RbConfig::CONFIG["bindir"]') do |bindir| InstallOptions.bindir = bindir end opts.on('--localedir[=OPTIONAL]', 'Installation directory for locale information', 'Default /opt/puppetlabs/puppet/share/locale') do |localedir| InstallOptions.localedir = localedir end opts.on('--ruby[=OPTIONAL]', 'Ruby interpreter to use with installation', 'overrides ruby used to call install.rb') do |ruby| InstallOptions.ruby = ruby end opts.on('--sitelibdir[=OPTIONAL]', 'Installation directory for libraries', 'overrides RbConfig::CONFIG["sitelibdir"]') do |sitelibdir| InstallOptions.sitelibdir = sitelibdir end opts.on('--mandir[=OPTIONAL]', 'Installation directory for man pages', 'overrides RbConfig::CONFIG["mandir"]') do |mandir| InstallOptions.mandir = mandir end opts.on('--[no-]check-prereqs', 'Prevents validation of prerequisite libraries', 'Default on') do |prereq| InstallOptions.check_prereqs = prereq end opts.on('--no-batch-files', 'Prevents installation of batch files for windows', 'Default off') do |batch_files| InstallOptions.batch_files = false end opts.on('--quick', 'Performs a quick installation. Only the', 'installation is done.') do |quick| InstallOptions.rdoc = false InstallOptions.ri = false InstallOptions.configs = true end opts.on('--full', 'Performs a full installation. All', 'optional installation steps are run.') do |full| InstallOptions.rdoc = true InstallOptions.ri = true InstallOptions.configs = true end opts.separator("") opts.on_tail('--help', "Shows this help text.") do $stderr.puts opts exit end opts.parse! end version = [RbConfig::CONFIG["MAJOR"], RbConfig::CONFIG["MINOR"]].join(".") libdir = File.join(RbConfig::CONFIG["libdir"], "ruby", version) # Mac OS X 10.5 and higher declare bindir # /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin # which is not generally where people expect executables to be installed # These settings are appropriate defaults for all OS X versions. if RUBY_PLATFORM =~ /^universal-darwin[\d\.]+$/ RbConfig::CONFIG['bindir'] = "/usr/bin" end # Here we only set $operatingsystem if we have opted to check for prereqs. # Otherwise facter won't be guaranteed to be present. if InstallOptions.check_prereqs check_prereqs $operatingsystem = Facter.value :operatingsystem end if $operatingsystem == "windows" begin # populates constants used to specify default Windows directories require 'win32/dir' rescue LoadError => e puts "Cannot run on Microsoft Windows without the win32-process, win32-dir & win32-service gems: #{e}" exit -1 end end if not InstallOptions.configdir.nil? configdir = InstallOptions.configdir elsif $operatingsystem == "windows" configdir = File.join(Dir::COMMON_APPDATA, "PuppetLabs", "puppet", "etc") else configdir = "/etc/puppetlabs/puppet" end if not InstallOptions.codedir.nil? codedir = InstallOptions.codedir elsif $operatingsystem == "windows" codedir = File.join(Dir::COMMON_APPDATA, "PuppetLabs", "code") else codedir = "/etc/puppetlabs/code" end if not InstallOptions.vardir.nil? vardir = InstallOptions.vardir elsif $operatingsystem == "windows" vardir = File.join(Dir::COMMON_APPDATA, "PuppetLabs", "puppet", "cache") else vardir = "/opt/puppetlabs/puppet/cache" end if not InstallOptions.rundir.nil? rundir = InstallOptions.rundir elsif $operatingsystem == "windows" rundir = File.join(Dir::COMMON_APPDATA, "PuppetLabs", "puppet", "var", "run") else rundir = "/var/run/puppetlabs" end if not InstallOptions.logdir.nil? logdir = InstallOptions.logdir elsif $operatingsystem == "windows" logdir = File.join(Dir::COMMON_APPDATA, "PuppetLabs", "puppet", "var", "log") else logdir = "/var/log/puppetlabs/puppet" end if not InstallOptions.bindir.nil? bindir = InstallOptions.bindir else bindir = RbConfig::CONFIG['bindir'] end if not InstallOptions.localedir.nil? localedir = InstallOptions.localedir else if $operatingsystem == "windows" localedir = File.join(Dir::PROGRAM_FILES, "Puppet Labs", "Puppet", "puppet", "share", "locale") else localedir = "/opt/puppetlabs/puppet/share/locale" end end if not InstallOptions.sitelibdir.nil? sitelibdir = InstallOptions.sitelibdir else sitelibdir = RbConfig::CONFIG["sitelibdir"] if sitelibdir.nil? sitelibdir = $LOAD_PATH.find { |x| x =~ /site_ruby/ } if sitelibdir.nil? sitelibdir = File.join(libdir, "site_ruby") elsif sitelibdir !~ Regexp.quote(version) sitelibdir = File.join(sitelibdir, version) end end end if not InstallOptions.mandir.nil? mandir = InstallOptions.mandir else mandir = RbConfig::CONFIG['mandir'] end # This is the new way forward if not InstallOptions.destdir.nil? destdir = InstallOptions.destdir # To be deprecated once people move over to using --destdir option elsif not ENV['DESTDIR'].nil? destdir = ENV['DESTDIR'] warn "DESTDIR is deprecated. Use --destdir instead." else destdir = '' end configdir = join(destdir, configdir) codedir = join(destdir, codedir) vardir = join(destdir, vardir) rundir = join(destdir, rundir) logdir = join(destdir, logdir) bindir = join(destdir, bindir) localedir = join(destdir, localedir) mandir = join(destdir, mandir) sitelibdir = join(destdir, sitelibdir) FileUtils.makedirs(configdir) if InstallOptions.configs FileUtils.makedirs(codedir) FileUtils.makedirs(bindir) FileUtils.makedirs(mandir) FileUtils.makedirs(sitelibdir) FileUtils.makedirs(vardir) FileUtils.makedirs(rundir) FileUtils.makedirs(logdir) FileUtils.makedirs(localedir) InstallOptions.site_dir = sitelibdir InstallOptions.codedir = codedir InstallOptions.config_dir = configdir InstallOptions.bin_dir = bindir InstallOptions.lib_dir = libdir InstallOptions.man_dir = mandir InstallOptions.var_dir = vardir InstallOptions.run_dir = rundir InstallOptions.log_dir = logdir InstallOptions.locale_dir = localedir end ## # Join two paths. On Windows, dir must be converted to a relative path, # by stripping the drive letter, but only if the basedir is not empty. # def join(basedir, dir) return "#{basedir}#{dir[2..-1]}" if $operatingsystem == "windows" and basedir.length > 0 and dir.length > 2 "#{basedir}#{dir}" end ## # Build the rdoc documentation. Also, try to build the RI documentation. # def build_rdoc(files) return unless $haverdoc begin r = RDoc::RDoc.new r.document(["--main", "README", "--title", "Puppet -- Site Configuration Management", "--line-numbers"] + files) rescue RDoc::RDocError => e $stderr.puts e.message rescue Exception => e $stderr.puts "Couldn't build RDoc documentation\n#{e.message}" end end def build_ri(files) return unless $haverdoc return if $operatingsystem == "windows" begin ri = RDoc::RDoc.new #ri.document(["--ri-site", "--merge"] + files) ri.document(["--ri-site"] + files) rescue RDoc::RDocError => e $stderr.puts e.message rescue Exception => e $stderr.puts "Couldn't build Ri documentation\n#{e.message}" $stderr.puts "Continuing with install..." end end ## # Install file(s) from ./bin to RbConfig::CONFIG['bindir']. Patch it on the way # to insert a #! line; on a Unix install, the command is named as expected # (e.g., bin/rdoc becomes rdoc); the shebang line handles running it. Under # windows, we add an '.rb' extension and let file associations do their stuff. def install_binfile(from, op_file, target) tmp_file = Tempfile.new('puppet-binfile') if not InstallOptions.ruby.nil? ruby = InstallOptions.ruby else ruby = File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name']) end File.open(from) do |ip| File.open(tmp_file.path, "w") do |op| op.puts "#!#{ruby}" unless $operatingsystem == "windows" contents = ip.readlines contents.shift if contents[0] =~ /^#!/ op.write contents.join end end if $operatingsystem == "windows" && InstallOptions.batch_files installed_wrapper = false unless File.extname(from).match(/\.(cmd|bat)/) if File.exists?("#{from}.bat") FileUtils.install("#{from}.bat", File.join(target, "#{op_file}.bat"), :mode => 0755, :preserve => true, :verbose => true) installed_wrapper = true end if File.exists?("#{from}.cmd") FileUtils.install("#{from}.cmd", File.join(target, "#{op_file}.cmd"), :mode => 0755, :preserve => true, :verbose => true) installed_wrapper = true end if not installed_wrapper tmp_file2 = Tempfile.new('puppet-wrapper') cwv = <<-EOS @echo off SETLOCAL if exist "%~dp0environment.bat" ( call "%~dp0environment.bat" %0 %* ) else ( SET "PATH=%~dp0;%PATH%" ) ruby.exe -S -- puppet %* EOS File.open(tmp_file2.path, "w") { |cw| cw.puts cwv } FileUtils.install(tmp_file2.path, File.join(target, "#{op_file}.bat"), :mode => 0755, :preserve => true, :verbose => true) tmp_file2.unlink end end end FileUtils.install(tmp_file.path, File.join(target, op_file), :mode => 0755, :preserve => true, :verbose => true) tmp_file.unlink end # Change directory into the puppet root so we don't get the wrong files for install. FileUtils.cd File.dirname(__FILE__) do # Set these values to what you want installed. configs = glob(%w{conf/auth.conf conf/puppet.conf conf/hiera.yaml}) bins = glob(%w{bin/*}) #rdoc = glob(%w{bin/* lib/**/*.rb README* }).reject { |e| e=~ /\.(bat|cmd)$/ } #ri = glob(%w{bin/*.rb lib/**/*.rb}).reject { |e| e=~ /\.(bat|cmd)$/ } man = glob(%w{man/man[0-9]/*}) libs = glob(%w{lib/**/*}) locales = glob(%w{locales/**/*}) prepare_installation if $operatingsystem == "windows" windows_bins = glob(%w{ext/windows/*bat}) end #build_rdoc(rdoc) if InstallOptions.rdoc #build_ri(ri) if InstallOptions.ri do_configs(configs, InstallOptions.config_dir) if InstallOptions.configs do_bins(bins, InstallOptions.bin_dir) do_bins(windows_bins, InstallOptions.bin_dir, 'ext/windows/') if $operatingsystem == "windows" && InstallOptions.batch_files do_libs(libs) do_locales(locales) do_man(man) unless $operatingsystem == "windows" end puppet-5.5.10/bin/0000755005276200011600000000000013417162176013567 5ustar jenkinsjenkinspuppet-5.5.10/bin/puppet0000755005276200011600000000024513417161721015026 0ustar jenkinsjenkins#!/usr/bin/env ruby begin require 'puppet/util/command_line' Puppet::Util::CommandLine.new.execute rescue LoadError => e $stderr.puts e.message exit(1) end puppet-5.5.10/lib/0000755005276200011600000000000013417162176013565 5ustar jenkinsjenkinspuppet-5.5.10/lib/hiera/0000755005276200011600000000000013417162176014655 5ustar jenkinsjenkinspuppet-5.5.10/lib/hiera/puppet_function.rb0000644005276200011600000000550313417161721020422 0ustar jenkinsjenkinsrequire 'hiera_puppet' # Provides the base class for the puppet functions hiera, hiera_array, hiera_hash, and hiera_include. # The actual function definitions will call init_dispatch and override the merge_type and post_lookup methods. # # @see hiera_array.rb, hiera_include.rb under lib/puppet/functions for sample usage # class Hiera::PuppetFunction < Puppet::Functions::InternalFunction def self.init_dispatch dispatch :hiera_splat do scope_param param 'Tuple[String, Any, Any, 1, 3]', :args end dispatch :hiera_no_default do scope_param param 'String',:key end dispatch :hiera_with_default do scope_param param 'String',:key param 'Any', :default optional_param 'Any', :override end dispatch :hiera_block1 do scope_param param 'String', :key block_param 'Callable[1,1]', :default_block end dispatch :hiera_block2 do scope_param param 'String', :key param 'Any', :override block_param 'Callable[1,1]', :default_block end end def hiera_splat(scope, args) hiera(scope, *args) end def hiera_no_default(scope, key) post_lookup(scope, key, lookup(scope, key, nil, false, nil)) end def hiera_with_default(scope, key, default, override = nil) post_lookup(scope, key, lookup(scope, key, default, true, override)) end def hiera_block1(scope, key, &default_block) post_lookup(scope, key, lookup(scope, key, nil, false, nil, &default_block)) end def hiera_block2(scope, key, override, &default_block) post_lookup(scope, key, lookup(scope, key, nil, false, override, &default_block)) end def lookup(scope, key, default, has_default, override, &default_block) unless Puppet[:strict] == :off #TRANSLATORS 'lookup' is a puppet function and should not be translated message = _("The function '%{class_name}' is deprecated in favor of using 'lookup'.") % { class_name: self.class.name } message += ' '+ _("See https://puppet.com/docs/puppet/%{minor_version}/deprecated_language.html") % { minor_version: Puppet.minor_version } Puppet.warn_once('deprecations', self.class.name, message) end lookup_invocation = Puppet::Pops::Lookup::Invocation.new(scope, {}, {}) adapter = lookup_invocation.lookup_adapter lookup_invocation.set_hiera_xxx_call lookup_invocation.set_global_only unless adapter.global_only? || adapter.has_environment_data_provider?(lookup_invocation) lookup_invocation.set_hiera_v3_location_overrides(override) unless override.nil? || override.is_a?(Array) && override.empty? Puppet::Pops::Lookup.lookup(key, nil, default, has_default, merge_type, lookup_invocation, &default_block) end def merge_type :first end def post_lookup(scope, key, result) result end end puppet-5.5.10/lib/hiera/scope.rb0000644005276200011600000000411013417161721016302 0ustar jenkinsjenkinsclass Hiera class Scope CALLING_CLASS = 'calling_class'.freeze CALLING_CLASS_PATH = 'calling_class_path'.freeze CALLING_MODULE = 'calling_module'.freeze MODULE_NAME = 'module_name'.freeze CALLING_KEYS = [CALLING_CLASS, CALLING_CLASS_PATH, CALLING_MODULE].freeze EMPTY_STRING = ''.freeze attr_reader :real def initialize(real) @real = real end def [](key) if key == CALLING_CLASS ans = find_hostclass(@real) elsif key == CALLING_CLASS_PATH ans = find_hostclass(@real).gsub(/::/, '/') elsif key == CALLING_MODULE ans = safe_lookupvar(MODULE_NAME) else ans = safe_lookupvar(key) end ans == EMPTY_STRING ? nil : ans end # This method is used to handle the throw of :undefined_variable since when # strict variables is not in effect, missing handling of the throw leads to # a more expensive code path. # def safe_lookupvar(key) reason = catch :undefined_variable do return @real.lookupvar(key) end case Puppet[:strict] when :off # do nothing when :warning Puppet.warn_once(Puppet::Parser::Scope::UNDEFINED_VARIABLES_KIND, _("Variable: %{name}") % { name: key }, _("Undefined variable '%{name}'; %{reason}") % { name: key, reason: reason } ) when :error raise ArgumentError, _("Undefined variable '%{name}'; %{reason}") % { name: key, reason: reason } end nil end private :safe_lookupvar def exist?(key) CALLING_KEYS.include?(key) || @real.exist?(key) end def include?(key) CALLING_KEYS.include?(key) || @real.include?(key) end def catalog @real.catalog end def resource @real.resource end def compiler @real.compiler end def find_hostclass(scope) if scope.source and scope.source.type == :hostclass return scope.source.name.downcase elsif scope.parent return find_hostclass(scope.parent) else return nil end end private :find_hostclass end end puppet-5.5.10/lib/hiera_puppet.rb0000644005276200011600000000336313417161721016577 0ustar jenkinsjenkinsrequire 'hiera' require 'hiera/scope' require 'puppet' module HieraPuppet module_function def lookup(key, default, scope, override, resolution_type) scope = Hiera::Scope.new(scope) answer = hiera.lookup(key, default, scope, override, resolution_type) if answer.nil? raise Puppet::ParseError, _("Could not find data item %{key} in any Hiera data file and no default supplied") % { key: key } end answer end def parse_args(args) # Functions called from Puppet manifests like this: # # hiera("foo", "bar") # # Are invoked internally after combining the positional arguments into a # single array: # # func = function_hiera # func(["foo", "bar"]) # # Functions called from templates preserve the positional arguments: # # scope.function_hiera("foo", "bar") # # Deal with Puppet's special calling mechanism here. if args[0].is_a?(Array) args = args[0] end if args.empty? raise Puppet::ParseError, _("Please supply a parameter to perform a Hiera lookup") end key = args[0] default = args[1] override = args[2] return [key, default, override] end private module_function def hiera @hiera ||= Hiera.new(:config => hiera_config) end def hiera_config config = {} if config_file = hiera_config_file config = Hiera::Config.load(config_file) end config[:logger] = 'puppet' config end def hiera_config_file hiera_config = Puppet.settings[:hiera_config] if Puppet::FileSystem.exist?(hiera_config) hiera_config else Puppet.warning _("Config file %{hiera_config} not found, using Hiera defaults") % { hiera_config: hiera_config } nil end end end puppet-5.5.10/lib/puppet/0000755005276200011600000000000013417162176015102 5ustar jenkinsjenkinspuppet-5.5.10/lib/puppet/agent.rb0000644005276200011600000001023113417161721016515 0ustar jenkinsjenkinsrequire 'puppet/application' require 'puppet/error' require 'puppet/util/at_fork' require 'timeout' # A general class for triggering a run of another # class. class Puppet::Agent require 'puppet/agent/locker' include Puppet::Agent::Locker require 'puppet/agent/disabler' include Puppet::Agent::Disabler require 'puppet/util/splayer' include Puppet::Util::Splayer # Special exception class used to signal an agent run has timed out. class RunTimeoutError < Exception end attr_reader :client_class, :client, :should_fork def initialize(client_class, should_fork=true) @should_fork = can_fork? && should_fork @client_class = client_class end def can_fork? Puppet.features.posix? && RUBY_PLATFORM != 'java' end def needing_restart? Puppet::Application.restart_requested? end # Perform a run with our client. def run(client_options = {}) if disabled? Puppet.notice _("Skipping run of %{client_class}; administratively disabled (Reason: '%{disable_message}');\nUse 'puppet agent --enable' to re-enable.") % { client_class: client_class, disable_message: disable_message } return end result = nil block_run = Puppet::Application.controlled_run do splay client_options.fetch :splay, Puppet[:splay] result = run_in_fork(should_fork) do with_client(client_options[:transaction_uuid], client_options[:job_id]) do |client| client_args = client_options.merge(:pluginsync => Puppet::Configurer.should_pluginsync?) begin lock do # NOTE: Timeout is pretty heinous as the location in which it # throws an error is entirely unpredictable, which means that # it can interrupt code blocks that perform cleanup or enforce # sanity. The only thing a Puppet agent should do after this # error is thrown is die with as much dignity as possible. Timeout.timeout(Puppet[:runtimeout], RunTimeoutError) do client.run(client_args) end end rescue Puppet::LockError Puppet.notice _("Run of %{client_class} already in progress; skipping (%{lockfile_path} exists)") % { client_class: client_class, lockfile_path: lockfile_path } return rescue RunTimeoutError => detail Puppet.log_exception(detail, _("Execution of %{client_class} did not complete within %{runtimeout} seconds and was terminated.") % {client_class: client_class, runtimeout: Puppet[:runtimeout]}) return 1 rescue StandardError => detail Puppet.log_exception(detail, _("Could not run %{client_class}: %{detail}") % { client_class: client_class, detail: detail }) 1 end end end true end Puppet.notice _("Shutdown/restart in progress (%{status}); skipping run") % { status: Puppet::Application.run_status.inspect } unless block_run result end def stopping? Puppet::Application.stop_requested? end def run_in_fork(forking = true) return yield unless forking or Puppet.features.windows? atForkHandler = Puppet::Util::AtFork.get_handler atForkHandler.prepare begin child_pid = Kernel.fork do atForkHandler.child $0 = _("puppet agent: applying configuration") begin exit(yield) rescue SystemExit exit(-1) rescue NoMemoryError exit(-2) end end ensure atForkHandler.parent end exit_code = Process.waitpid2(child_pid) case exit_code[1].exitstatus when -1 raise SystemExit when -2 raise NoMemoryError end exit_code[1].exitstatus end private # Create and yield a client instance, keeping a reference # to it during the yield. def with_client(transaction_uuid, job_id = nil) begin @client = client_class.new(transaction_uuid, job_id) rescue StandardError => detail Puppet.log_exception(detail, _("Could not create instance of %{client_class}: %{detail}") % { client_class: client_class, detail: detail }) return end yield @client ensure @client = nil end end puppet-5.5.10/lib/puppet/agent/0000755005276200011600000000000013417162176016200 5ustar jenkinsjenkinspuppet-5.5.10/lib/puppet/agent/disabler.rb0000644005276200011600000000265313417161721020313 0ustar jenkinsjenkinsrequire 'puppet/util/json_lockfile' # This module is responsible for encapsulating the logic for # "disabling" the puppet agent during a run; in other words, # keeping track of enough state to answer the question # "has the puppet agent been administratively disabled?" # # The implementation involves writing a lockfile with JSON # contents, and is considered part of the public Puppet API # because it used by external tools such as mcollective. # # For more information, please see docs on the website. # http://links.puppet.com/agent_lockfiles module Puppet::Agent::Disabler DISABLED_MESSAGE_JSON_KEY = "disabled_message" # Let the daemon run again, freely in the filesystem. def enable Puppet.notice _("Enabling Puppet.") disable_lockfile.unlock end # Stop the daemon from making any catalog runs. def disable(msg=nil) data = {} Puppet.notice _("Disabling Puppet.") if (! msg.nil?) data[DISABLED_MESSAGE_JSON_KEY] = msg end disable_lockfile.lock(data) end def disabled? disable_lockfile.locked? end def disable_message data = disable_lockfile.lock_data return nil if data.nil? if data.has_key?(DISABLED_MESSAGE_JSON_KEY) return data[DISABLED_MESSAGE_JSON_KEY] end nil end def disable_lockfile @disable_lockfile ||= Puppet::Util::JsonLockfile.new(Puppet[:agent_disabled_lockfile]) @disable_lockfile end private :disable_lockfile end puppet-5.5.10/lib/puppet/agent/locker.rb0000644005276200011600000000320013417161721017772 0ustar jenkinsjenkinsrequire 'puppet/util/pidlock' require 'puppet/error' # This module is responsible for encapsulating the logic for "locking" the # puppet agent during a catalog run; in other words, keeping track of enough # state to answer the question "is there a puppet agent currently applying a # catalog?" # # The implementation involves writing a lockfile whose contents are simply the # PID of the running agent process. This is considered part of the public # Puppet API because it used by external tools such as mcollective. # # For more information, please see docs on the website. # http://links.puppet.com/agent_lockfiles module Puppet::Agent::Locker # Yield if we get a lock, else raise Puppet::LockError. Return # value of block yielded. def lock if lockfile.lock begin yield ensure lockfile.unlock end else fail Puppet::LockError, _('Failed to acquire lock') end end # @deprecated def running? #TRANSLATORS 'Puppet::Agent::Locker.running?' is a method name and should not be translated message = _('Puppet::Agent::Locker.running? is deprecated as it is inherently unsafe.') #TRANSLATORS 'LockError' should not be translated message += ' ' + _('The only safe way to know if the lock is locked is to try lock and perform some '\ 'action and then handle the LockError that may result.') Puppet.deprecation_warning(message) lockfile.locked? end def lockfile_path @lockfile_path ||= Puppet[:agent_catalog_run_lockfile] end def lockfile @lockfile ||= Puppet::Util::Pidlock.new(lockfile_path) @lockfile end private :lockfile end puppet-5.5.10/lib/puppet/application/0000755005276200011600000000000013417162176017405 5ustar jenkinsjenkinspuppet-5.5.10/lib/puppet/application/catalog.rb0000644005276200011600000000017513417161721021342 0ustar jenkinsjenkinsrequire 'puppet/application/indirection_base' class Puppet::Application::Catalog < Puppet::Application::IndirectionBase end puppet-5.5.10/lib/puppet/application/config.rb0000644005276200011600000000021713417161721021172 0ustar jenkinsjenkinsrequire 'puppet/application/face_base' class Puppet::Application::Config < Puppet::Application::FaceBase environment_mode :not_required end puppet-5.5.10/lib/puppet/application/describe.rb0000644005276200011600000001234413417161721021511 0ustar jenkinsjenkinsrequire 'puppet/application' class Formatter def initialize(width) @width = width end def wrap(txt, opts) return "" unless txt && !txt.empty? work = (opts[:scrub] ? scrub(txt) : txt) indent = (opts[:indent] ? opts[:indent] : 0) textLen = @width - indent patt = Regexp.new("\\A(.{0,#{textLen}})[ \n]") prefix = " " * indent res = [] while work.length > textLen if work =~ patt res << $1 work.slice!(0, $MATCH.length) else res << work.slice!(0, textLen) end end res << work if work.length.nonzero? prefix + res.join("\n#{prefix}") end def header(txt, sep = "-") "\n#{txt}\n" + sep * txt.size end private def scrub(text) # For text with no carriage returns, there's nothing to do. return text if text !~ /\n/ # If we can match an indentation, then just remove that same level of # indent from every line. if text =~ /^(\s+)/ indent = $1 return text.gsub(/^#{indent}/,'') else return text end end end class TypeDoc def initialize @format = Formatter.new(76) @types = {} Puppet::Type.loadall Puppet::Type.eachtype { |type| next if type.name == :component @types[type.name] = type } end def list_types puts "These are the types known to puppet:\n" @types.keys.sort { |a, b| a.to_s <=> b.to_s }.each do |name| type = @types[name] s = type.doc.gsub(/\s+/, " ") n = s.index(". ") if n.nil? s = ".. no documentation .." elsif n > 45 s = s[0, 45] + " ..." else s = s[0, n] end printf "%-15s - %s\n", name, s end end def format_type(name, opts) name = name.to_sym unless @types.has_key?(name) puts "Unknown type #{name}" return end type = @types[name] puts @format.header(name.to_s, "=") puts @format.wrap(type.doc, :indent => 0, :scrub => true) + "\n\n" puts @format.header("Parameters") if opts[:parameters] format_attrs(type, [:property, :param]) else list_attrs(type, [:property, :param]) end if opts[:meta] puts @format.header("Meta Parameters") if opts[:parameters] format_attrs(type, [:meta]) else list_attrs(type, [:meta]) end end if type.providers.size > 0 puts @format.header("Providers") if opts[:providers] format_providers(type) else list_providers(type) end end end # List details about attributes def format_attrs(type, attrs) docs = {} type.allattrs.each do |name| kind = type.attrtype(name) docs[name] = type.attrclass(name).doc if attrs.include?(kind) && name != :provider end docs.sort { |a,b| a[0].to_s <=> b[0].to_s }.each { |name, doc| print "\n- **#{name}**" if type.key_attributes.include?(name) and name != :name puts " (*namevar*)" else puts "" end puts @format.wrap(doc, :indent => 4, :scrub => true) } end # List the names of attributes def list_attrs(type, attrs) params = [] type.allattrs.each do |name| kind = type.attrtype(name) params << name.to_s if attrs.include?(kind) && name != :provider end puts @format.wrap(params.sort.join(", "), :indent => 4) end def format_providers(type) type.providers.sort { |a,b| a.to_s <=> b.to_s }.each { |prov| puts "\n- **#{prov}**" puts @format.wrap(type.provider(prov).doc, :indent => 4, :scrub => true) } end def list_providers(type) list = type.providers.sort { |a,b| a.to_s <=> b.to_s }.join(", ") puts @format.wrap(list, :indent => 4) end end class Puppet::Application::Describe < Puppet::Application banner "puppet describe [options] [type]" option("--short", "-s", "Only list parameters without detail") do |arg| options[:parameters] = false end option("--providers","-p") option("--list", "-l") option("--meta","-m") def summary _("Display help about resource types") end def help <<-HELP puppet-describe(8) -- #{summary} ======== SYNOPSIS -------- Prints help about Puppet resource types, providers, and metaparameters. USAGE ----- puppet describe [-h|--help] [-s|--short] [-p|--providers] [-l|--list] [-m|--meta] OPTIONS ------- * --help: Print this help text * --providers: Describe providers in detail for each type * --list: List all types * --meta: List all metaparameters * --short: List only parameters without detail EXAMPLE ------- $ puppet describe --list $ puppet describe file --providers $ puppet describe user -s -m AUTHOR ------ David Lutterkort COPYRIGHT --------- Copyright (c) 2011 Puppet Inc., LLC Licensed under the Apache 2.0 License HELP end def preinit options[:parameters] = true end def main doc = TypeDoc.new if options[:list] doc.list_types else options[:types].each { |name| doc.format_type(name, options) } end end def setup options[:types] = command_line.args.dup handle_help(nil) unless options[:list] || options[:types].size > 0 $stderr.puts "Warning: ignoring types when listing all types" if options[:list] && options[:types].size > 0 end end puppet-5.5.10/lib/puppet/application/epp.rb0000644005276200011600000000020113417161721020502 0ustar jenkinsjenkinsrequire 'puppet/application/face_base' require 'puppet/face' class Puppet::Application::Epp < Puppet::Application::FaceBase end puppet-5.5.10/lib/puppet/application/face_base.rb0000644005276200011600000002314313417161721021620 0ustar jenkinsjenkinsrequire 'puppet/application' require 'puppet/face' require 'optparse' require 'pp' class Puppet::Application::FaceBase < Puppet::Application option("--debug", "-d") do |arg| set_log_level(:debug => true) end option("--verbose", "-v") do |_| set_log_level(:verbose => true) end option("--render-as FORMAT") do |format| self.render_as = format.to_sym end option("--help", "-h") do |arg| if action && !@is_default_action # Only invoke help on the action if it was specified, not if # it was the default action. puts Puppet::Face[:help, :current].help(face.name, action.name) else puts Puppet::Face[:help, :current].help(face.name) end exit(0) end attr_accessor :face, :action, :type, :arguments, :render_as def render_as=(format) @render_as = Puppet::Network::FormatHandler.format(format) @render_as or raise ArgumentError, _("I don't know how to render '%{format}'") % { format: format } end def render(result, args_and_options) hook = action.when_rendering(render_as.name) if hook # when defining when_rendering on your action you can optionally # include arguments and options if hook.arity > 1 result = hook.call(result, *args_and_options) else result = hook.call(result) end end render_as.render(result) end def preinit super Signal.trap(:INT) do $stderr.puts _("Cancelling Face") exit(0) end end def parse_options # We need to parse enough of the command line out early, to identify what # the action is, so that we can obtain the full set of options to parse. # REVISIT: These should be configurable versions, through a global # '--version' option, but we don't implement that yet... --daniel 2011-03-29 @type = Puppet::Util::ConstantInflector.constant2file(self.class.name.to_s.sub(/.+:/, '')).to_sym @face = Puppet::Face[@type, :current] # Now, walk the command line and identify the action. We skip over # arguments based on introspecting the action and all, and find the first # non-option word to use as the action. action_name = nil index = -1 until action_name or (index += 1) >= command_line.args.length do item = command_line.args[index] if item =~ /^-/ then option = @face.options.find do |name| item =~ /^-+#{name.to_s.gsub(/[-_]/, '[-_]')}(?:[ =].*)?$/ end if option then option = @face.get_option(option) # If we have an inline argument, just carry on. We don't need to # care about optional vs mandatory in that case because we do a real # parse later, and that will totally take care of raising the error # when we get there. --daniel 2011-04-04 if option.takes_argument? and !item.index('=') then index += 1 unless (option.optional_argument? and command_line.args[index + 1] =~ /^-/) end elsif option = find_global_settings_argument(item) then unless Puppet.settings.boolean? option.name then # As far as I can tell, we treat non-bool options as always having # a mandatory argument. --daniel 2011-04-05 # ... But, the mandatory argument will not be the next item if an = is # employed in the long form of the option. --jeffmccune 2012-09-18 index += 1 unless item =~ /^--#{option.name}=/ end elsif option = find_application_argument(item) then index += 1 if (option[:argument] and not option[:optional]) else raise OptionParser::InvalidOption.new(item.sub(/=.*$/, '')) end else # Stash away the requested action name for later, and try to fetch the # action object it represents; if this is an invalid action name that # will be nil, and handled later. action_name = item.to_sym @action = Puppet::Face.find_action(@face.name, action_name) @face = @action.face if @action end end if @action.nil? if @action = @face.get_default_action() then @is_default_action = true else # First try to handle global command line options # But ignoring invalid options as this is a invalid action, and # we want the error message for that instead. begin super rescue OptionParser::InvalidOption end face = @face.name action = action_name.nil? ? 'default' : "'#{action_name}'" msg = _("'%{face}' has no %{action} action. See `puppet help %{face}`.") % { face: face, action: action } Puppet.err(msg) Puppet::Util::Log.force_flushqueue() exit false end end # Now we can interact with the default option code to build behaviour # around the full set of options we now know we support. @action.options.each do |o| o = @action.get_option(o) # make it the object. self.class.option(*o.optparse) # ...and make the CLI parse it. end # ...and invoke our parent to parse all the command line options. super end def find_global_settings_argument(item) Puppet.settings.each do |name, object| object.optparse_args.each do |arg| next unless arg =~ /^-/ # sadly, we have to emulate some of optparse here... pattern = /^#{arg.sub('[no-]', '').sub(/[ =].*$/, '')}(?:[ =].*)?$/ pattern.match item and return object end end return nil # nothing found. end def find_application_argument(item) self.class.option_parser_commands.each do |options, function| options.each do |option| next unless option =~ /^-/ pattern = /^#{option.sub('[no-]', '').sub(/[ =].*$/, '')}(?:[ =].*)?$/ next unless pattern.match(item) return { :argument => option =~ /[ =]/, :optional => option =~ /[ =]\[/ } end end return nil # not found end def setup Puppet::Util::Log.newdestination :console @arguments = command_line.args # Note: because of our definition of where the action is set, we end up # with it *always* being the first word of the remaining set of command # line arguments. So, strip that off when we construct the arguments to # pass down to the face action. --daniel 2011-04-04 # Of course, now that we have default actions, we should leave the # "action" name on if we didn't actually consume it when we found our # action. @arguments.delete_at(0) unless @is_default_action # We copy all of the app options to the end of the call; This allows each # action to read in the options. This replaces the older model where we # would invoke the action with options set as global state in the # interface object. --daniel 2011-03-28 @arguments << options # If we don't have a rendering format, set one early. self.render_as ||= (@action.render_as || :console) end def main status = false # Call the method associated with the provided action (e.g., 'find'). unless @action puts Puppet::Face[:help, :current].help(@face.name) raise _("%{face} does not respond to action %{arg}") % { face: face, arg: arguments.first } end # We need to do arity checking here because this is generic code # calling generic methods – that have argument defaulting. We need to # make sure we don't accidentally pass the options as the first # argument to a method that takes one argument. eg: # # puppet facts find # => options => {} # @arguments => [{}] # => @face.send :bar, {} # # def face.bar(argument, options = {}) # => bar({}, {}) # oops! we thought the options were the # # positional argument!! # # We could also fix this by making it mandatory to pass the options on # every call, but that would make the Ruby API much more annoying to # work with; having the defaulting is a much nicer convention to have. # # We could also pass the arguments implicitly, by having a magic # 'options' method that was visible in the scope of the action, which # returned the right stuff. # # That sounds attractive, but adds complications to all sorts of # things, especially when you think about how to pass options when you # are writing Ruby code that calls multiple faces. Especially if # faces are involved in that. ;) # # --daniel 2011-04-27 if (arity = @action.positional_arg_count) > 0 unless (count = arguments.length) == arity then raise ArgumentError, n_("puppet %{face} %{action} takes %{arg_count} argument, but you gave %{given_count}", "puppet %{face} %{action} takes %{arg_count} arguments, but you gave %{given_count}", arity - 1) % { face: @face.name, action: @action.name, arg_count: arity-1, given_count: count-1 } end end if @face.deprecated? Puppet.deprecation_warning(_("'puppet %{face}' is deprecated and will be removed in a future release") % { face: @face.name }) end result = @face.send(@action.name, *arguments) puts render(result, arguments) unless result.nil? status = true # We need an easy way for the action to set a specific exit code, so we # rescue SystemExit here; This allows each action to set the desired exit # code by simply calling Kernel::exit. eg: # # exit(2) # # --kelsey 2012-02-14 rescue SystemExit => detail status = detail.status rescue => detail Puppet.log_exception(detail) Puppet.err _("Try 'puppet help %{face} %{action}' for usage") % { face: @face.name, action: @action.name } ensure exit status end end puppet-5.5.10/lib/puppet/application/facts.rb0000644005276200011600000000070413417161721021026 0ustar jenkinsjenkinsrequire 'puppet/application/indirection_base' class Puppet::Application::Facts < Puppet::Application::IndirectionBase # Allows `puppet facts` actions to be run against environments that # don't exist locally, such as using the `--environment` flag to make a REST # request to a specific environment on a master. There is no way to set this # behavior per-action, so it must be set for the face as a whole. environment_mode :not_required end puppet-5.5.10/lib/puppet/application/filebucket.rb0000644005276200011600000002060013417161721022040 0ustar jenkinsjenkinsrequire 'puppet/application' class Puppet::Application::Filebucket < Puppet::Application option("--bucket BUCKET","-b") option("--debug","-d") option("--fromdate FROMDATE","-f") option("--todate TODATE","-t") option("--local","-l") option("--remote","-r") option("--verbose","-v") attr :args def summary _("Store and retrieve files in a filebucket") end def help <<-HELP puppet-filebucket(8) -- #{summary} ======== SYNOPSIS -------- A stand-alone Puppet filebucket client. USAGE ----- puppet filebucket [-h|--help] [-V|--version] [-d|--debug] [-v|--verbose] [-l|--local] [-r|--remote] [-s|--server ] [-f|--fromdate ] [-t|--todate ] [-b|--bucket ] ... Puppet filebucket can operate in three modes, with only one mode per call: backup: Send one or more files to the specified file bucket. Each sent file is printed with its resulting md5 sum. get: Return the text associated with an md5 sum. The text is printed to stdout, and only one file can be retrieved at a time. restore: Given a file path and an md5 sum, store the content associated with the sum into the specified file path. You can specify an entirely new path to this argument; you are not restricted to restoring the content to its original location. diff: Print a diff in unified format between two checksums in the filebucket or between a checksum and its matching file. list: List all files in the current local filebucket. Listing remote filebuckets is not allowed. DESCRIPTION ----------- This is a stand-alone filebucket client for sending files to a local or central filebucket. Note that 'filebucket' defaults to using a network-based filebucket available on the server named 'puppet'. To use this, you'll have to be running as a user with valid Puppet certificates. Alternatively, you can use your local file bucket by specifying '--local', or by specifying '--bucket' with a local path. OPTIONS ------- Note that any setting that's valid in the configuration file is also a valid long argument. For example, 'ssldir' is a valid setting, so you can specify '--ssldir ' as an argument. See the configuration file documentation at https://puppet.com/docs/puppet/latest/configuration.html for the full list of acceptable parameters. A commented list of all configuration options can also be generated by running puppet with '--genconfig'. * --bucket: Specify a local filebucket path. This overrides the default path set in '$clientbucketdir'. * --debug: Enable full debugging. * --fromdate: (list only) Select bucket files from 'fromdate'. * --help: Print this help message. * --local: Use the local filebucket. This uses the default configuration information and the bucket located at the '$clientbucketdir' setting by default. If '--bucket' is set, puppet uses that path instead. * --remote: Use a remote filebucket. This uses the default configuration information and the bucket located at the '$bucketdir' setting by default. * --server: The server to send the file to, instead of locally. * --todate: (list only) Select bucket files until 'todate'. * --verbose: Print extra information. * --version: Print version information. EXAMPLES -------- ## Backup a file to the filebucket, then restore it to a temporary directory $ puppet filebucket backup /etc/passwd /etc/passwd: 429b225650b912a2ee067b0a4cf1e949 $ puppet filebucket restore /tmp/passwd 429b225650b912a2ee067b0a4cf1e949 ## Diff between two files in the filebucket $ puppet filebucket -l diff d43a6ecaa892a1962398ac9170ea9bf2 7ae322f5791217e031dc60188f4521ef 1a2 > again ## Diff between the file in the filebucket and a local file $ puppet filebucket -l diff d43a6ecaa892a1962398ac9170ea9bf2 /tmp/testFile 1a2 > again ## Backup a file to the filebucket and observe that it keeps each backup separate $ puppet filebucket -l list d43a6ecaa892a1962398ac9170ea9bf2 2015-05-11 09:27:56 /tmp/TestFile $ echo again >> /tmp/TestFile $ puppet filebucket -l backup /tmp/TestFile /tmp/TestFile: 7ae322f5791217e031dc60188f4521ef $ puppet filebucket -l list d43a6ecaa892a1962398ac9170ea9bf2 2015-05-11 09:27:56 /tmp/TestFile 7ae322f5791217e031dc60188f4521ef 2015-05-11 09:52:15 /tmp/TestFile ## List files in a filebucket within date ranges $ puppet filebucket -l -f 2015-01-01 -t 2015-01-11 list $ puppet filebucket -l -f 2015-05-10 list d43a6ecaa892a1962398ac9170ea9bf2 2015-05-11 09:27:56 /tmp/TestFile 7ae322f5791217e031dc60188f4521ef 2015-05-11 09:52:15 /tmp/TestFile $ puppet filebucket -l -f "2015-05-11 09:30:00" list 7ae322f5791217e031dc60188f4521ef 2015-05-11 09:52:15 /tmp/TestFile $ puppet filebucket -l -t "2015-05-11 09:30:00" list d43a6ecaa892a1962398ac9170ea9bf2 2015-05-11 09:27:56 /tmp/TestFile ## Manage files in a specific local filebucket $ puppet filebucket -b /tmp/TestBucket backup /tmp/TestFile2 /tmp/TestFile2: d41d8cd98f00b204e9800998ecf8427e $ puppet filebucket -b /tmp/TestBucket list d41d8cd98f00b204e9800998ecf8427e 2015-05-11 09:33:22 /tmp/TestFile2 ## From a Puppet master, list files in the master bucketdir $ puppet filebucket -b $(puppet config print bucketdir --section master) list d43a6ecaa892a1962398ac9170ea9bf2 2015-05-11 09:27:56 /tmp/TestFile 7ae322f5791217e031dc60188f4521ef 2015-05-11 09:52:15 /tmp/TestFile AUTHOR ------ Luke Kanies COPYRIGHT --------- Copyright (c) 2011 Puppet Inc., LLC Licensed under the Apache 2.0 License HELP end def run_command @args = command_line.args command = args.shift return send(command) if %w{get backup restore diff list}.include? command help end def get md5 = args.shift out = @client.getfile(md5) print out end def backup raise _("You must specify a file to back up") unless args.length > 0 args.each do |file| unless Puppet::FileSystem.exist?(file) $stderr.puts _("%{file}: no such file") % { file: file } next end unless FileTest.readable?(file) $stderr.puts _("%{file}: cannot read file") % { file: file } next end md5 = @client.backup(file) puts "#{file}: #{md5}" end end def list fromdate = options[:fromdate] todate = options[:todate] out = @client.list(fromdate, todate) print out end def restore file = args.shift md5 = args.shift @client.restore(file, md5) end def diff raise Puppet::Error, _("Need exactly two arguments: filebucket diff ") unless args.count == 2 left = args.shift right = args.shift if Puppet::FileSystem.exist?(left) # It's a file file_a = left checksum_a = nil else file_a = nil checksum_a = left end if Puppet::FileSystem.exist?(right) # It's a file file_b = right checksum_b = nil else file_b = nil checksum_b = right end if (checksum_a || file_a) && (checksum_b || file_b) Puppet.info(_("Comparing %{checksum_a} %{checksum_b} %{file_a} %{file_b}") % { checksum_a: checksum_a, checksum_b: checksum_b, file_a: file_a, file_b: file_b }) print @client.diff(checksum_a, checksum_b, file_a, file_b) else raise Puppet::Error, _("Need exactly two arguments: filebucket diff ") end end def setup Puppet::Log.newdestination(:console) @client = nil @server = nil Signal.trap(:INT) do $stderr.puts _("Cancelling") exit(1) end if options[:debug] Puppet::Log.level = :debug elsif options[:verbose] Puppet::Log.level = :info end exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? require 'puppet/file_bucket/dipper' begin if options[:local] or options[:bucket] path = options[:bucket] || Puppet[:clientbucketdir] @client = Puppet::FileBucket::Dipper.new(:Path => path) else if Puppet[:server_list] && !Puppet[:server_list].empty? server = Puppet[:server_list].first @client = Puppet::FileBucket::Dipper.new( :Server => server[0], :Port => server[1] ) else @client = Puppet::FileBucket::Dipper.new(:Server => Puppet[:server]) end end rescue => detail Puppet.log_exception(detail) exit(1) end end end puppet-5.5.10/lib/puppet/application/generate.rb0000644005276200011600000000021513417161721021515 0ustar jenkinsjenkinsrequire 'puppet/application/face_base' # The Generate application. class Puppet::Application::Generate < Puppet::Application::FaceBase end puppet-5.5.10/lib/puppet/application/help.rb0000644005276200011600000000021513417161721020653 0ustar jenkinsjenkinsrequire 'puppet/application/face_base' class Puppet::Application::Help < Puppet::Application::FaceBase environment_mode :not_required end puppet-5.5.10/lib/puppet/application/indirection_base.rb0000644005276200011600000000016713417161721023232 0ustar jenkinsjenkinsrequire 'puppet/application/face_base' class Puppet::Application::IndirectionBase < Puppet::Application::FaceBase end puppet-5.5.10/lib/puppet/application/key.rb0000644005276200011600000000017113417161721020514 0ustar jenkinsjenkinsrequire 'puppet/application/indirection_base' class Puppet::Application::Key < Puppet::Application::IndirectionBase end puppet-5.5.10/lib/puppet/application/man.rb0000644005276200011600000000015313417161721020477 0ustar jenkinsjenkinsrequire 'puppet/application/face_base' class Puppet::Application::Man < Puppet::Application::FaceBase end puppet-5.5.10/lib/puppet/application/module.rb0000644005276200011600000000015613417161721021214 0ustar jenkinsjenkinsrequire 'puppet/application/face_base' class Puppet::Application::Module < Puppet::Application::FaceBase end puppet-5.5.10/lib/puppet/application/node.rb0000644005276200011600000000017213417161721020652 0ustar jenkinsjenkinsrequire 'puppet/application/indirection_base' class Puppet::Application::Node < Puppet::Application::IndirectionBase end puppet-5.5.10/lib/puppet/application/parser.rb0000644005276200011600000000020413417161721021215 0ustar jenkinsjenkinsrequire 'puppet/application/face_base' require 'puppet/face' class Puppet::Application::Parser < Puppet::Application::FaceBase end puppet-5.5.10/lib/puppet/application/plugin.rb0000644005276200011600000000015513417161721021224 0ustar jenkinsjenkinsrequire 'puppet/application/face_base' class Puppet::Application::Plugin < Puppet::Application::FaceBase end puppet-5.5.10/lib/puppet/application/report.rb0000644005276200011600000000017413417161721021242 0ustar jenkinsjenkinsrequire 'puppet/application/indirection_base' class Puppet::Application::Report < Puppet::Application::IndirectionBase end puppet-5.5.10/lib/puppet/application/resource.rb0000644005276200011600000001417513417161721021564 0ustar jenkinsjenkinsrequire 'puppet/application' class Puppet::Application::Resource < Puppet::Application attr_accessor :host, :extra_params def preinit @extra_params = [] end option("--debug","-d") option("--verbose","-v") option("--edit","-e") option("--to_yaml","-y") option("--types", "-t") do |arg| types = [] Puppet::Type.loadall Puppet::Type.eachtype do |t| next if t.name == :component types << t.name.to_s end puts types.sort exit(0) end option("--param PARAM", "-p") do |arg| @extra_params << arg.to_sym end def summary _("The resource abstraction layer shell") end def help <<-HELP puppet-resource(8) -- #{summary} ======== SYNOPSIS -------- Uses the Puppet RAL to directly interact with the system. USAGE ----- puppet resource [-h|--help] [-d|--debug] [-v|--verbose] [-e|--edit] [-p|--param ] [-t|--types] [-y|--to_yaml] [] [= ...] DESCRIPTION ----------- This command provides simple facilities for converting current system state into Puppet code, along with some ability to modify the current state using Puppet's RAL. By default, you must at least provide a type to list, in which case puppet resource will tell you everything it knows about all resources of that type. You can optionally specify an instance name, and puppet resource will only describe that single instance. If given a type, a name, and a series of = pairs, puppet resource will modify the state of the specified resource. Alternately, if given a type, a name, and the '--edit' flag, puppet resource will write its output to a file, open that file in an editor, and then apply the saved file as a Puppet transaction. OPTIONS ------- Note that any setting that's valid in the configuration file is also a valid long argument. For example, 'ssldir' is a valid setting, so you can specify '--ssldir ' as an argument. See the configuration file documentation at https://puppet.com/docs/puppet/latest/configuration.html for the full list of acceptable parameters. A commented list of all configuration options can also be generated by running puppet with '--genconfig'. * --debug: Enable full debugging. * --edit: Write the results of the query to a file, open the file in an editor, and read the file back in as an executable Puppet manifest. * --help: Print this help message. * --param: Add more parameters to be outputted from queries. * --types: List all available types. * --verbose: Print extra information. * --to_yaml: Output found resources in yaml format, suitable to use with Hiera and create_resources. EXAMPLE ------- This example uses `puppet resource` to return a Puppet configuration for the user `luke`: $ puppet resource user luke user { 'luke': home => '/home/luke', uid => '100', ensure => 'present', comment => 'Luke Kanies,,,', gid => '1000', shell => '/bin/bash', groups => ['sysadmin','audio','video','puppet'] } AUTHOR ------ Luke Kanies COPYRIGHT --------- Copyright (c) 2011 Puppet Inc., LLC Licensed under the Apache 2.0 License HELP end def main env = Puppet.lookup(:environments).get(Puppet[:environment]) Puppet.override(:current_environment => env, :loaders => Puppet::Pops::Loaders.new(env)) do type, name, params = parse_args(command_line.args) raise _("Editing with Yaml output is not supported") if options[:edit] and options[:to_yaml] resources = find_or_save_resources(type, name, params) if options[:to_yaml] text = resources.map do |resource| resource.prune_parameters(:parameters_to_include => @extra_params).to_hierayaml.force_encoding(Encoding.default_external) end.join("\n") text.prepend("#{type.downcase}:\n") else text = resources.map do |resource| resource.prune_parameters(:parameters_to_include => @extra_params).to_manifest.force_encoding(Encoding.default_external) end.join("\n") end options[:edit] ? handle_editing(text) : (puts text) end end def setup Puppet::Util::Log.newdestination(:console) set_log_level end private def local_key(type, name) [type, name].join('/') end def handle_editing(text) require 'tempfile' # Prefer the current directory, which is more likely to be secure # and, in the case of interactive use, accessible to the user. tmpfile = Tempfile.new('x2puppet', Dir.pwd, :encoding => Encoding::UTF_8) begin # sync write, so nothing buffers before we invoke the editor. tmpfile.sync = true tmpfile.puts text # edit the content system(ENV["EDITOR"] || 'vi', tmpfile.path) # ...and, now, pass that file to puppet to apply. Because # many editors rename or replace the original file we need to # feed the pathname, not the file content itself, to puppet. system('puppet apply -v ' + tmpfile.path) ensure # The temporary file will be safely removed. tmpfile.close(true) end end def parse_args(args) type = args.shift or raise _("You must specify the type to display") Puppet::Type.type(type) or raise _("Could not find type %{type}") % { type: type } name = args.shift params = {} args.each do |setting| if setting =~ /^(\w+)=(.+)$/ params[$1] = $2 else raise _("Invalid parameter setting %{setting}") % { setting: setting } end end [type, name, params] end def find_or_save_resources(type, name, params) key = local_key(type, name) if name if params.empty? [ Puppet::Resource.indirection.find( key ) ] else resource = Puppet::Resource.new( type, name, :parameters => params ) # save returns [resource that was saved, transaction log from applying the resource] save_result = Puppet::Resource.indirection.save(resource, key) [ save_result.first ] end else if type == "file" raise _("Listing all file instances is not supported. Please specify a file or directory, e.g. puppet resource file /etc") end Puppet::Resource.indirection.search( key, {} ) end end end puppet-5.5.10/lib/puppet/application/status.rb0000644005276200011600000000017413417161721021252 0ustar jenkinsjenkinsrequire 'puppet/application/indirection_base' class Puppet::Application::Status < Puppet::Application::IndirectionBase end puppet-5.5.10/lib/puppet/application/agent.rb0000644005276200011600000003517613417161721021037 0ustar jenkinsjenkinsrequire 'puppet/application' require 'puppet/daemon' require 'puppet/util/pidlock' require 'puppet/agent' require 'puppet/configurer' require 'puppet/ssl/oids' class Puppet::Application::Agent < Puppet::Application run_mode :agent def app_defaults super.merge({ :catalog_terminus => :rest, :catalog_cache_terminus => :json, :node_terminus => :rest, :facts_terminus => :facter, }) end def preinit # Do an initial trap, so that cancels don't get a stack trace. Signal.trap(:INT) do $stderr.puts _("Cancelling startup") exit(0) end { :waitforcert => nil, :detailed_exitcodes => false, :verbose => false, :debug => false, :setdest => false, :enable => false, :disable => false, :fqdn => nil, :serve => [], :digest => 'SHA256', :graph => true, :fingerprint => false, :sourceaddress => nil, }.each do |opt,val| options[opt] = val end @argv = ARGV.dup end option("--disable [MESSAGE]") do |message| options[:disable] = true options[:disable_message] = message end option("--enable") option("--debug","-d") option("--fqdn FQDN","-f") option("--test","-t") option("--verbose","-v") option("--fingerprint") option("--digest DIGEST") option("--sourceaddress IP_ADDRESS") option("--detailed-exitcodes") do |arg| options[:detailed_exitcodes] = true end option("--logdest DEST", "-l DEST") do |arg| handle_logdest_arg(arg) end option("--waitforcert WAITFORCERT", "-w") do |arg| options[:waitforcert] = arg.to_i end option("--job-id ID") do |arg| options[:job_id] = arg end def summary _("The puppet agent daemon") end def help <<-HELP puppet-agent(8) -- #{summary} ======== SYNOPSIS -------- Retrieves the client configuration from the puppet master and applies it to the local host. This service may be run as a daemon, run periodically using cron (or something similar), or run interactively for testing purposes. USAGE ----- puppet agent [--certname ] [-D|--daemonize|--no-daemonize] [-d|--debug] [--detailed-exitcodes] [--digest ] [--disable [MESSAGE]] [--enable] [--fingerprint] [-h|--help] [-l|--logdest syslog|eventlog||console] [--masterport ] [--noop] [-o|--onetime] [--sourceaddress ] [-t|--test] [-v|--verbose] [-V|--version] [-w|--waitforcert ] DESCRIPTION ----------- This is the main puppet client. Its job is to retrieve the local machine's configuration from a remote server and apply it. In order to successfully communicate with the remote server, the client must have a certificate signed by a certificate authority that the server trusts; the recommended method for this, at the moment, is to run a certificate authority as part of the puppet server (which is the default). The client will connect and request a signed certificate, and will continue connecting until it receives one. Once the client has a signed certificate, it will retrieve its configuration and apply it. USAGE NOTES ----------- 'puppet agent' does its best to find a compromise between interactive use and daemon use. Run with no arguments and no configuration, it will go into the background, attempt to get a signed certificate, and retrieve and apply its configuration every 30 minutes. Some flags are meant specifically for interactive use -- in particular, 'test', 'tags' and 'fingerprint' are useful. '--test' does a single run in the foreground with verbose logging, then exits. It will also exit if it can't get a valid catalog. The exit code after running with '--test' is 0 if the catalog was successfully applied, and 1 if the run either failed or wasn't attempted (due to another run already in progress). '--tags' allows you to specify what portions of a configuration you want to apply. Puppet elements are tagged with all of the class or definition names that contain them, and you can use the 'tags' flag to specify one of these names, causing only configuration elements contained within that class or definition to be applied. This is very useful when you are testing new configurations -- for instance, if you are just starting to manage 'ntpd', you would put all of the new elements into an 'ntpd' class, and call puppet with '--tags ntpd', which would only apply that small portion of the configuration during your testing, rather than applying the whole thing. '--fingerprint' is a one-time flag. In this mode 'puppet agent' will run once and display on the console (and in the log) the current certificate (or certificate request) fingerprint. Providing the '--digest' option allows to use a different digest algorithm to generate the fingerprint. The main use is to verify that before signing a certificate request on the master, the certificate request the master received is the same as the one the client sent (to prevent against man-in-the-middle attacks when signing certificates). OPTIONS ------- Note that any Puppet setting that's valid in the configuration file is also a valid long argument. For example, 'server' is a valid setting, so you can specify '--server ' as an argument. Boolean settings translate into '--setting' and '--no-setting' pairs. See the configuration file documentation at https://puppet.com/docs/puppet/latest/configuration.html for the full list of acceptable settings. A commented list of all settings can also be generated by running puppet agent with '--genconfig'. * --certname: Set the certname (unique ID) of the client. The master reads this unique identifying string, which is usually set to the node's fully-qualified domain name, to determine which configurations the node will receive. Use this option to debug setup problems or implement unusual node identification schemes. (This is a Puppet setting, and can go in puppet.conf.) * --daemonize: Send the process into the background. This is the default. (This is a Puppet setting, and can go in puppet.conf. Note the special 'no-' prefix for boolean settings on the command line.) * --no-daemonize: Do not send the process into the background. (This is a Puppet setting, and can go in puppet.conf. Note the special 'no-' prefix for boolean settings on the command line.) * --debug: Enable full debugging. * --detailed-exitcodes: Provide extra information about the run via exit codes; only works if '--test' or '--onetime' is also specified. If enabled, 'puppet agent' will use the following exit codes: 0: The run succeeded with no changes or failures; the system was already in the desired state. 1: The run failed, or wasn't attempted due to another run already in progress. 2: The run succeeded, and some resources were changed. 4: The run succeeded, and some resources failed. 6: The run succeeded, and included both changes and failures. * --digest: Change the certificate fingerprinting digest algorithm. The default is SHA256. Valid values depends on the version of OpenSSL installed, but will likely contain MD5, MD2, SHA1 and SHA256. * --disable: Disable working on the local system. This puts a lock file in place, causing 'puppet agent' not to work on the system until the lock file is removed. This is useful if you are testing a configuration and do not want the central configuration to override the local state until everything is tested and committed. Disable can also take an optional message that will be reported by the 'puppet agent' at the next disabled run. 'puppet agent' uses the same lock file while it is running, so no more than one 'puppet agent' process is working at a time. 'puppet agent' exits after executing this. * --enable: Enable working on the local system. This removes any lock file, causing 'puppet agent' to start managing the local system again (although it will continue to use its normal scheduling, so it might not start for another half hour). 'puppet agent' exits after executing this. * --fingerprint: Display the current certificate or certificate signing request fingerprint and then exit. Use the '--digest' option to change the digest algorithm used. * --help: Print this help message * --job-id: Attach the specified job id to the catalog request and the report used for this agent run. This option only works when '--onetime' is used. * --logdest: Where to send log messages. Choose between 'syslog' (the POSIX syslog service), 'eventlog' (the Windows Event Log), 'console', or the path to a log file. If debugging or verbosity is enabled, this defaults to 'console'. Otherwise, it defaults to 'syslog' on POSIX systems and 'eventlog' on Windows. A path ending with '.json' will receive structured output in JSON format. The log file will not have an ending ']' automatically written to it due to the appending nature of logging. It must be appended manually to make the content valid JSON. * --masterport: The port on which to contact the puppet master. (This is a Puppet setting, and can go in puppet.conf.) * --noop: Use 'noop' mode where the daemon runs in a no-op or dry-run mode. This is useful for seeing what changes Puppet will make without actually executing the changes. (This is a Puppet setting, and can go in puppet.conf. Note the special 'no-' prefix for boolean settings on the command line.) * --onetime: Run the configuration once. Runs a single (normally daemonized) Puppet run. Useful for interactively running puppet agent when used in conjunction with the --no-daemonize option. (This is a Puppet setting, and can go in puppet.conf. Note the special 'no-' prefix for boolean settings on the command line.) * --sourceaddress: Set the source IP address for transactions. This defaults to automatically selected. (This is a Puppet setting, and can go in puppet.conf.) * --test: Enable the most common options used for testing. These are 'onetime', 'verbose', 'no-daemonize', 'no-usecacheonfailure', 'detailed-exitcodes', 'no-splay', and 'show_diff'. * --verbose: Turn on verbose reporting. * --version: Print the puppet version number and exit. * --waitforcert: This option only matters for daemons that do not yet have certificates and it is enabled by default, with a value of 120 (seconds). This causes 'puppet agent' to connect to the server every 2 minutes and ask it to sign a certificate request. This is useful for the initial setup of a puppet client. You can turn off waiting for certificates by specifying a time of 0. (This is a Puppet setting, and can go in puppet.conf. Note the special 'no-' prefix for boolean settings on the command line.) EXAMPLE ------- $ puppet agent --server puppet.domain.com DIAGNOSTICS ----------- Puppet agent accepts the following signals: * SIGHUP: Restart the puppet agent daemon. * SIGINT and SIGTERM: Shut down the puppet agent daemon. * SIGUSR1: Immediately retrieve and apply configurations from the puppet master. * SIGUSR2: Close file descriptors for log files and reopen them. Used with logrotate. AUTHOR ------ Luke Kanies COPYRIGHT --------- Copyright (c) 2011 Puppet Inc., LLC Licensed under the Apache 2.0 License HELP end def run_command if options[:fingerprint] fingerprint else # It'd be nice to daemonize later, but we have to daemonize before # waiting for certificates so that we don't block daemon = daemonize_process_when(Puppet[:daemonize]) # Setup signal traps immediately after daemonization so we clean up the daemon daemon.set_signal_traps wait_for_certificates if Puppet[:onetime] onetime(daemon) else main(daemon) end end end def fingerprint host = Puppet::SSL::Host.new unless cert = host.certificate || host.certificate_request $stderr.puts _("Fingerprint asked but no certificate nor certificate request have yet been issued") exit(1) return end unless digest = cert.digest(options[:digest].to_s) raise ArgumentError, _("Could not get fingerprint for digest '%{digest}'") % { digest: options[:digest] } end puts digest.to_s end def onetime(daemon) begin exitstatus = daemon.agent.run(:job_id => options[:job_id]) rescue => detail Puppet.log_exception(detail) end daemon.stop(:exit => false) if not exitstatus exit(1) elsif options[:detailed_exitcodes] then exit(exitstatus) else exit(0) end end def main(daemon) Puppet.notice _("Starting Puppet client version %{version}") % { version: Puppet.version } daemon.start end # Enable all of the most common test options. def setup_test Puppet.settings.handlearg("--no-usecacheonfailure") Puppet.settings.handlearg("--no-splay") Puppet.settings.handlearg("--show_diff") Puppet.settings.handlearg("--no-daemonize") options[:verbose] = true Puppet[:onetime] = true options[:detailed_exitcodes] = true end def setup raise ArgumentError, _("The puppet agent command does not take parameters") unless command_line.args.empty? setup_test if options[:test] setup_logs exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? Puppet::SSL::Oids.register_puppet_oids if options[:fqdn] Puppet[:certname] = options[:fqdn] end Puppet.settings.use :main, :agent, :ssl Puppet::Transaction::Report.indirection.terminus_class = :rest # we want the last report to be persisted locally Puppet::Transaction::Report.indirection.cache_class = :yaml if Puppet[:catalog_cache_terminus] Puppet::Resource::Catalog.indirection.cache_class = Puppet[:catalog_cache_terminus] end if options[:fingerprint] # in fingerprint mode we just need # access to the local files and we don't need a ca Puppet::SSL::Host.ca_location = :none else Puppet::SSL::Host.ca_location = :remote setup_agent end end private def enable_disable_client(agent) if options[:enable] agent.enable elsif options[:disable] agent.disable(options[:disable_message] || 'reason not specified') end exit(0) end def setup_agent agent = Puppet::Agent.new(Puppet::Configurer, (not(Puppet[:onetime]))) enable_disable_client(agent) if options[:enable] or options[:disable] @agent = agent end def daemonize_process_when(should_daemonize) daemon = Puppet::Daemon.new(Puppet::Util::Pidlock.new(Puppet[:pidfile])) daemon.argv = @argv daemon.agent = @agent daemon.daemonize if should_daemonize daemon end def wait_for_certificates host = Puppet::SSL::Host.new waitforcert = options[:waitforcert] || (Puppet[:onetime] ? 0 : Puppet[:waitforcert]) host.wait_for_cert(waitforcert) end end puppet-5.5.10/lib/puppet/application/apply.rb0000644005276200011600000002436413417161721021063 0ustar jenkinsjenkinsrequire 'puppet/application' require 'puppet/configurer' require 'puppet/util/profiler/aggregate' class Puppet::Application::Apply < Puppet::Application require 'puppet/util/splayer' include Puppet::Util::Splayer option("--debug","-d") option("--execute EXECUTE","-e") do |arg| options[:code] = arg end option("--loadclasses","-L") option("--test","-t") option("--verbose","-v") option("--use-nodes") option("--detailed-exitcodes") option("--write-catalog-summary") option("--catalog catalog", "-c catalog") do |arg| options[:catalog] = arg end option("--logdest LOGDEST", "-l") do |arg| handle_logdest_arg(arg) end option("--parseonly") do |args| puts "--parseonly has been removed. Please use 'puppet parser validate '" exit 1 end def summary _("Apply Puppet manifests locally") end def help <<-HELP puppet-apply(8) -- #{summary} ======== SYNOPSIS -------- Applies a standalone Puppet manifest to the local system. USAGE ----- puppet apply [-h|--help] [-V|--version] [-d|--debug] [-v|--verbose] [-e|--execute] [--detailed-exitcodes] [-L|--loadclasses] [-l|--logdest syslog|eventlog||console] [--noop] [--catalog ] [--write-catalog-summary] DESCRIPTION ----------- This is the standalone puppet execution tool; use it to apply individual manifests. When provided with a modulepath, via command line or config file, puppet apply can effectively mimic the catalog that would be served by puppet master with access to the same modules, although there are some subtle differences. When combined with scheduling and an automated system for pushing manifests, this can be used to implement a serverless Puppet site. Most users should use 'puppet agent' and 'puppet master' for site-wide manifests. OPTIONS ------- Note that any setting that's valid in the configuration file is also a valid long argument. For example, 'tags' is a valid setting, so you can specify '--tags ,' as an argument. See the configuration file documentation at https://puppet.com/docs/puppet/latest/configuration.html for the full list of acceptable parameters. A commented list of all configuration options can also be generated by running puppet with '--genconfig'. * --debug: Enable full debugging. * --detailed-exitcodes: Provide extra information about the run via exit codes. If enabled, 'puppet apply' will use the following exit codes: 0: The run succeeded with no changes or failures; the system was already in the desired state. 1: The run failed. 2: The run succeeded, and some resources were changed. 4: The run succeeded, and some resources failed. 6: The run succeeded, and included both changes and failures. * --help: Print this help message * --loadclasses: Load any stored classes. 'puppet agent' caches configured classes (usually at /etc/puppetlabs/puppet/classes.txt), and setting this option causes all of those classes to be set in your puppet manifest. * --logdest: Where to send log messages. Choose between 'syslog' (the POSIX syslog service), 'eventlog' (the Windows Event Log), 'console', or the path to a log file. Defaults to 'console'. A path ending with '.json' will receive structured output in JSON format. The log file will not have an ending ']' automatically written to it due to the appending nature of logging. It must be appended manually to make the content valid JSON. * --noop: Use 'noop' mode where Puppet runs in a no-op or dry-run mode. This is useful for seeing what changes Puppet will make without actually executing the changes. * --execute: Execute a specific piece of Puppet code * --test: Enable the most common options used for testing. These are 'verbose', 'detailed-exitcodes' and 'show_diff'. * --verbose: Print extra information. * --catalog: Apply a JSON catalog (such as one generated with 'puppet master --compile'). You can either specify a JSON file or pipe in JSON from standard input. * --write-catalog-summary After compiling the catalog saves the resource list and classes list to the node in the state directory named classes.txt and resources.txt EXAMPLE ------- $ puppet apply -l /tmp/manifest.log manifest.pp $ puppet apply --modulepath=/root/dev/modules -e "include ntpd::server" $ puppet apply --catalog catalog.json AUTHOR ------ Luke Kanies COPYRIGHT --------- Copyright (c) 2011 Puppet Inc., LLC Licensed under the Apache 2.0 License HELP end def app_defaults super.merge({ :default_file_terminus => :file_server, }) end def run_command if options[:catalog] apply else main end end def apply if options[:catalog] == "-" text = $stdin.read else text = Puppet::FileSystem.read(options[:catalog], :encoding => 'utf-8') end env = Puppet.lookup(:environments).get(Puppet[:environment]) Puppet.override(:current_environment => env, :loaders => Puppet::Pops::Loaders.new(env)) do catalog = read_catalog(text) apply_catalog(catalog) end end def main # Set our code or file to use. if options[:code] or command_line.args.length == 0 Puppet[:code] = options[:code] || STDIN.read else manifest = command_line.args.shift raise _("Could not find file %{manifest}") % { manifest: manifest } unless Puppet::FileSystem.exist?(manifest) Puppet.warning(_("Only one file can be applied per run. Skipping %{files}") % { files: command_line.args.join(', ') }) if command_line.args.size > 0 end # splay if needed splay unless Puppet[:node_name_fact].empty? # Collect our facts. unless facts = Puppet::Node::Facts.indirection.find(Puppet[:node_name_value]) raise _("Could not find facts for %{node}") % { node: Puppet[:node_name_value] } end Puppet[:node_name_value] = facts.values[Puppet[:node_name_fact]] facts.name = Puppet[:node_name_value] end # Find our Node unless node = Puppet::Node.indirection.find(Puppet[:node_name_value]) raise _("Could not find node %{node}") % { node: Puppet[:node_name_value] } end configured_environment = node.environment || Puppet.lookup(:current_environment) apply_environment = manifest ? configured_environment.override_with(:manifest => manifest) : configured_environment # Modify the node descriptor to use the special apply_environment. # It is based on the actual environment from the node, or the locally # configured environment if the node does not specify one. # If a manifest file is passed on the command line, it overrides # the :manifest setting of the apply_environment. node.environment = apply_environment #TRANSLATORS "puppet apply" is a program command and should not be translated Puppet.override({:current_environment => apply_environment}, _("For puppet apply")) do # Merge in the facts. node.merge(facts.values) if facts # Add server facts so $server_facts[environment] exists when doing a puppet apply node.add_server_facts({}) # Allow users to load the classes that puppet agent creates. if options[:loadclasses] file = Puppet[:classfile] if Puppet::FileSystem.exist?(file) unless FileTest.readable?(file) $stderr.puts _("%{file} is not readable") % { file: file } exit(63) end node.classes = Puppet::FileSystem.read(file, :encoding => 'utf-8').split(/[\s]+/) end end begin # Compile the catalog starttime = Time.now # When compiling, the compiler traps and logs certain errors # Those that do not lead to an immediate exit are caught by the general # rule and gets logged. # catalog = begin Puppet::Resource::Catalog.indirection.find(node.name, :use_node => node) rescue Puppet::ParseErrorWithIssue, Puppet::Error # already logged and handled by the compiler for these two cases exit(1) end # Translate it to a RAL catalog catalog = catalog.to_ral catalog.finalize catalog.retrieval_duration = Time.now - starttime if options[:write_catalog_summary] catalog.write_class_file catalog.write_resource_file end exit_status = Puppet.override(:loaders => Puppet::Pops::Loaders.new(apply_environment)) { apply_catalog(catalog) } if not exit_status exit(1) elsif options[:detailed_exitcodes] then exit(exit_status) else exit(0) end rescue => detail Puppet.log_exception(detail) exit(1) end end ensure if @profiler Puppet::Util::Profiler.remove_profiler(@profiler) @profiler.shutdown end end # Enable all of the most common test options. def setup_test Puppet.settings.handlearg("--no-splay") Puppet.settings.handlearg("--show_diff") options[:verbose] = true options[:detailed_exitcodes] = true end def setup setup_test if options[:test] exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? handle_logdest_arg(Puppet[:logdest]) Puppet::Util::Log.newdestination(:console) unless options[:setdest] Signal.trap(:INT) do $stderr.puts _("Exiting") exit(1) end Puppet.settings.use :main, :agent, :ssl if Puppet[:catalog_cache_terminus] Puppet::Resource::Catalog.indirection.cache_class = Puppet[:catalog_cache_terminus] end # we want the last report to be persisted locally Puppet::Transaction::Report.indirection.cache_class = :yaml set_log_level if Puppet[:profile] @profiler = Puppet::Util::Profiler.add_profiler(Puppet::Util::Profiler::Aggregate.new(Puppet.method(:info), "apply")) end end private def read_catalog(text) format = Puppet::Resource::Catalog.default_format begin catalog = Puppet::Resource::Catalog.convert_from(format, text) rescue => detail raise Puppet::Error, _("Could not deserialize catalog from %{format}: %{detail}") % { format: format, detail: detail }, detail.backtrace end catalog.to_ral end def apply_catalog(catalog) configurer = Puppet::Configurer.new configurer.run(:catalog => catalog, :pluginsync => false) end end puppet-5.5.10/lib/puppet/application/ca.rb0000644005276200011600000000033713417161721020313 0ustar jenkinsjenkinsrequire 'puppet/application/face_base' require 'puppet/ssl/oids' class Puppet::Application::Ca < Puppet::Application::FaceBase run_mode :master def setup Puppet::SSL::Oids.register_puppet_oids super end end puppet-5.5.10/lib/puppet/application/cert.rb0000644005276200011600000002252213417161721020665 0ustar jenkinsjenkinsrequire 'puppet/application' require 'puppet/ssl/certificate_authority/interface' class Puppet::Application::Cert < Puppet::Application run_mode :master attr_accessor :all, :ca, :digest, :signed def subcommand @subcommand end def subcommand=(name) # Handle the nasty, legacy mapping of "clean" to "destroy". sub = name.to_sym @subcommand = (sub == :clean ? :destroy : sub) end option("--clean", "-c") do |arg| self.subcommand = "destroy" end option("--all", "-a") do |arg| @all = true end option("--digest DIGEST") do |arg| @digest = arg end option("--signed", "-s") do |arg| @signed = true end option("--debug", "-d") do |arg| options[:debug] = true set_log_level end option("--list", "-l") do |arg| self.subcommand = :list end option("--revoke", "-r") do |arg| self.subcommand = :revoke end option("--generate", "-g") do |arg| self.subcommand = :generate end option("--sign", "-s") do |arg| self.subcommand = :sign end option("--print", "-p") do |arg| self.subcommand = :print end option("--verify", "-v") do |arg| self.subcommand = :verify end option("--fingerprint", "-f") do |arg| self.subcommand = :fingerprint end option("--reinventory") do |arg| self.subcommand = :reinventory end option("--[no-]allow-dns-alt-names") do |value| options[:allow_dns_alt_names] = value end option("--[no-]allow-authorization-extensions") do |value| options[:allow_authorization_extensions] = value end option("--verbose", "-v") do |arg| options[:verbose] = true set_log_level end option("--human-readable", "-H") do |arg| options[:format] = :human end option("--machine-readable", "-m") do |arg| options[:format] = :machine end option("--interactive", "-i") do |arg| options[:interactive] = true end option("--assume-yes", "-y") do |arg| options[:yes] = true end def summary _("Manage certificates and requests (Deprecated)") end def help <<-HELP puppet-cert(8) -- #{summary} ======== SYNOPSIS -------- Standalone certificate authority. Capable of generating certificates, but mostly used for signing certificate requests from puppet clients. USAGE ----- puppet cert [-h|--help] [-V|--version] [-d|--debug] [-v|--verbose] [--digest ] [] DESCRIPTION ----------- Because the puppet master service defaults to not signing client certificate requests, this script is available for signing outstanding requests. It can be used to list outstanding requests and then either sign them individually or sign all of them. ACTIONS ------- Every action except 'list' and 'generate' requires a hostname to act on, unless the '--all' option is set. The most important actions for day-to-day use are 'list' and 'sign'. * clean: Revoke a host's certificate (if applicable) and remove all files related to that host from puppet cert's storage. This is useful when rebuilding hosts, since new certificate signing requests will only be honored if puppet cert does not have a copy of a signed certificate for that host. If '--all' is specified then all host certificates, both signed and unsigned, will be removed. * fingerprint: Print the DIGEST (defaults to the signing algorithm) fingerprint of a host's certificate. * generate: Generate a certificate for a named client. A certificate/keypair will be generated for each client named on the command line. * list: List outstanding certificate requests. If '--all' is specified, signed certificates are also listed, prefixed by '+', and revoked or invalid certificates are prefixed by '-' (the verification outcome is printed in parenthesis). If '--human-readable' or '-H' is specified, certificates are formatted in a way to improve human scan-ability. If '--machine-readable' or '-m' is specified, output is formatted concisely for consumption by a script. * print: Print the full-text version of a host's certificate. * revoke: Revoke the certificate of a client. The certificate can be specified either by its serial number (given as a hexadecimal number prefixed by '0x') or by its hostname. The certificate is revoked by adding it to the Certificate Revocation List given by the 'cacrl' configuration option. Note that the puppet master needs to be restarted after revoking certificates. * sign: Sign an outstanding certificate request. If '--interactive' or '-i' is supplied the user will be prompted to confirm that they are signing the correct certificate (recommended). If '--assume-yes' or '-y' is supplied the interactive prompt will assume the answer of 'yes'. * verify: Verify the named certificate against the local CA certificate. * reinventory: Build an inventory of the issued certificates. This will destroy the current inventory file specified by 'cert_inventory' and recreate it from the certificates found in the 'certdir'. Ensure the puppet master is stopped before running this action. OPTIONS ------- Note that any setting that's valid in the configuration file is also a valid long argument. For example, 'ssldir' is a valid setting, so you can specify '--ssldir ' as an argument. See the configuration file documentation at https://puppet.com/docs/puppet/latest/configuration.html for the full list of acceptable parameters. A commented list of all configuration options can also be generated by running puppet cert with '--genconfig'. * --all: Operate on all items. Currently only makes sense with the 'sign', 'list', and 'fingerprint' actions. * --allow-dns-alt-names: Sign a certificate request even if it contains one or more alternate DNS names. If this option isn't specified, 'puppet cert sign' will ignore any requests that contain alternate names. In general, ONLY certs intended for a Puppet master server should include alternate DNS names, since Puppet agent relies on those names for identifying its rightful server. You can make Puppet agent request a certificate with alternate names by setting 'dns_alt_names' in puppet.conf or specifying '--dns_alt_names' on the command line. The output of 'puppet cert list' shows any requested alt names for pending certificate requests. * --allow-authorization-extensions: Enable the signing of a request with authorization extensions. Such requests are sensitive because they can be used to write access rules in Puppet Server. Currently, this is the only means by which such requests can be signed. * --digest: Set the digest for fingerprinting (defaults to the digest used when signing the cert). Valid values depends on your openssl and openssl ruby extension version. * --debug: Enable full debugging. * --help: Print this help message * --verbose: Enable verbosity. * --version: Print the puppet version number and exit. EXAMPLE ------- $ puppet cert list culain.madstop.com $ puppet cert sign culain.madstop.com AUTHOR ------ Luke Kanies COPYRIGHT --------- Copyright (c) 2011 Puppet Inc., LLC Licensed under the Apache 2.0 License HELP end def main if @all hosts = :all elsif @signed hosts = :signed else hosts = command_line.args.collect { |h| h.downcase } end begin if subcommand == :destroy raise _("Refusing to destroy all certs, provide an explicit list of certs to destroy") if hosts == :all signed_hosts = hosts - @ca.waiting? apply(@ca, :revoke, options.merge(:to => signed_hosts)) unless signed_hosts.empty? end apply(@ca, subcommand, options.merge(:to => hosts, :digest => @digest)) rescue => detail Puppet.log_exception(detail) exit(24) end end def setup deprecate require 'puppet/ssl/certificate_authority' exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? Puppet::SSL::Oids.register_puppet_oids Puppet::SSL::Oids.load_custom_oid_file(Puppet[:trusted_oid_mapping_file]) Puppet::Util::Log.newdestination :console if [:generate, :destroy].include? subcommand Puppet::SSL::Host.ca_location = :local else Puppet::SSL::Host.ca_location = :only end # If we are generating, and the option came from the CLI, it gets added to # the data. This will do the right thing for non-local certificates, in # that the command line but *NOT* the config file option will apply. if subcommand == :generate if Puppet.settings.set_by_cli?(:dns_alt_names) options[:dns_alt_names] = Puppet[:dns_alt_names] end end begin @ca = Puppet::SSL::CertificateAuthority.new rescue => detail Puppet.log_exception(detail) exit(23) end end def parse_options # handle the bareword subcommand pattern. result = super unless self.subcommand then if sub = self.command_line.args.shift then self.subcommand = sub else puts help exit end end result end # Create and run an applicator. I wanted to build an interface where you could do # something like 'ca.apply(:generate).to(:all) but I don't think it's really possible. def apply(ca, method, options) raise ArgumentError, _("You must specify the hosts to apply to; valid values are an array or the symbol :all") unless options[:to] applier = Puppet::SSL::CertificateAuthority::Interface.new(method, options) applier.apply(ca) end end puppet-5.5.10/lib/puppet/application/certificate.rb0000644005276200011600000000120613417161721022206 0ustar jenkinsjenkinsrequire 'puppet/application/indirection_base' require 'puppet/ssl/oids' class Puppet::Application::Certificate < Puppet::Application::IndirectionBase def setup Puppet::SSL::Oids.register_puppet_oids location = Puppet::SSL::Host.ca_location if location == :local && !Puppet::SSL::CertificateAuthority.ca? # I'd prefer if this could be dealt with differently; ideally, run_mode should be set as # part of a class definition, and should not be modifiable beyond that. This is one of # the cases where that isn't currently possible. Puppet.settings.preferred_run_mode = "master" end super end end puppet-5.5.10/lib/puppet/application/certificate_request.rb0000644005276200011600000000057213417161721023763 0ustar jenkinsjenkinsrequire 'puppet/application/indirection_base' # NOTE: this is using an "old" naming convention (underscores instead of camel-case), for backwards # compatibility with 2.7.x. When the old naming convention is officially and publicly deprecated, # this should be changed to camel-case. class Puppet::Application::Certificate_request < Puppet::Application::IndirectionBase end puppet-5.5.10/lib/puppet/application/certificate_revocation_list.rb0000644005276200011600000000060213417161721025471 0ustar jenkinsjenkinsrequire 'puppet/application/indirection_base' # NOTE: this is using an "old" naming convention (underscores instead of camel-case), for backwards # compatibility with 2.7.x. When the old naming convention is officially and publicly deprecated, # this should be changed to camel-case. class Puppet::Application::Certificate_revocation_list < Puppet::Application::IndirectionBase end puppet-5.5.10/lib/puppet/application/device.rb0000644005276200011600000003260113417161721021166 0ustar jenkinsjenkinsrequire 'puppet/application' require 'puppet/configurer' require 'puppet/util/network_device' class Puppet::Application::Device < Puppet::Application run_mode :agent attr_accessor :args, :agent, :host def app_defaults super.merge({ :catalog_terminus => :rest, :catalog_cache_terminus => :json, :node_terminus => :rest, :facts_terminus => :network_device, }) end def preinit # Do an initial trap, so that cancels don't get a stack trace. Signal.trap(:INT) do $stderr.puts _("Cancelling startup") exit(0) end { :apply => nil, :waitforcert => nil, :detailed_exitcodes => false, :verbose => false, :debug => false, :centrallogs => false, :setdest => false, :resource => false, :facts => false, :target => nil, :to_yaml => false, }.each do |opt,val| options[opt] = val end @args = {} end option("--centrallogging") option("--debug","-d") option("--resource","-r") option("--facts","-f") option("--to_yaml","-y") option("--verbose","-v") option("--detailed-exitcodes") do |arg| options[:detailed_exitcodes] = true end option("--libdir LIBDIR") do |arg| options[:libdir] = arg end option("--apply MANIFEST") do |arg| options[:apply] = arg.to_s end option("--logdest DEST", "-l DEST") do |arg| handle_logdest_arg(arg) end option("--waitforcert WAITFORCERT", "-w") do |arg| options[:waitforcert] = arg.to_i end option("--port PORT","-p") do |arg| @args[:Port] = arg end option("--target DEVICE", "-t") do |arg| options[:target] = arg.to_s end def summary _("Manage remote network devices") end def help <<-HELP puppet-device(8) -- #{summary} ======== SYNOPSIS -------- Retrieves catalogs from the Puppet master and applies them to remote devices. This subcommand can be run manually; or periodically using cron, a scheduled task, or a similar tool. USAGE ----- puppet device [-h|--help] [-v|--verbose] [-d|--debug] [-l|--logdest syslog||console] [--detailed-exitcodes] [--deviceconfig ] [-w|--waitforcert ] [--libdir ] [-a|--apply ] [-f|--facts] [-r|--resource [name]] [-t|--target ] [--user=] [-V|--version] DESCRIPTION ----------- Devices require a proxy Puppet agent to request certificates, collect facts, retrieve and apply catalogs, and store reports. USAGE NOTES ----------- Devices managed by the puppet-device subcommand on a Puppet agent are configured in device.conf, which is located at $confdir/device.conf by default, and is configurable with the $deviceconfig setting. The device.conf file is an INI-like file, with one section per device: [] type url debug The section name specifies the certname of the device. The values for the type and url properties are specific to each type of device. The optional debug property specifies transport-level debugging, and is limited to telnet and ssh transports. See https://puppet.com/docs/puppet/latest/config_file_device.html for details. OPTIONS ------- Note that any setting that's valid in the configuration file is also a valid long argument. For example, 'server' is a valid configuration parameter, so you can specify '--server ' as an argument. * --help, -h: Print this help message * --verbose, -v: Turn on verbose reporting. * --debug, -d: Enable full debugging. * --logdest, -l: Where to send log messages. Choose between 'syslog' (the POSIX syslog service), 'console', or the path to a log file. If debugging or verbosity is enabled, this defaults to 'console'. Otherwise, it defaults to 'syslog'. A path ending with '.json' will receive structured output in JSON format. The log file will not have an ending ']' automatically written to it due to the appending nature of logging. It must be appended manually to make the content valid JSON. * --detailed-exitcodes: Provide transaction information via exit codes. If this is enabled, an exit code of '1' means at least one device had a compile failure, an exit code of '2' means at least one device had resource changes, and an exit code of '4' means at least one device had resource failures. Exit codes of '3', '5', '6', or '7' means that a bitwise combination of the preceding exit codes happened. * --deviceconfig: Path to the device config file for puppet device. Default: $confdir/device.conf * --waitforcert, -w: This option only matters for targets that do not yet have certificates and it is enabled by default, with a value of 120 (seconds). This causes +puppet device+ to poll the server every 2 minutes and ask it to sign a certificate request. This is useful for the initial setup of a target. You can turn off waiting for certificates by specifying a time of 0. * --libdir: Override the per-device libdir with a local directory. Specifying a libdir also disables pluginsync. This is useful for testing. * --apply: Apply a manifest against a remote target. Target must be specified. * --facts: Displays the facts of a remote target. Target must be specified. * --resource: Displays a resource state as Puppet code, roughly equivalent to `puppet resource`. Can be filterd by title. Requires --target be specified. * --target: Target a specific device/certificate in the device.conf. Doing so will perform a device run against only that device/certificate. * --to_yaml: Output found resources in yaml format, suitable to use with Hiera and create_resources. * --user: The user to run as. EXAMPLE ------- $ puppet device --target remotehost --verbose AUTHOR ------ Brice Figureau COPYRIGHT --------- Copyright (c) 2011-2018 Puppet Inc., LLC Licensed under the Apache 2.0 License HELP end def main if options[:resource] and !options[:target] raise _("resource command requires target") end if options[:facts] and !options[:target] raise _("facts command requires target") end unless options[:apply].nil? raise _("missing argument: --target is required when using --apply") if options[:target].nil? raise _("%{file} does not exist, cannot apply") % { file: options[:apply] } unless File.file?(options[:apply]) end libdir = Puppet[:libdir] vardir = Puppet[:vardir] confdir = Puppet[:confdir] certname = Puppet[:certname] env = Puppet::Node::Environment.remote(Puppet[:environment]) returns = Puppet.override(:current_environment => env, :loaders => Puppet::Pops::Loaders.new(env)) do # find device list require 'puppet/util/network_device/config' devices = Puppet::Util::NetworkDevice::Config.devices.dup if options[:target] devices.select! { |key, value| key == options[:target] } end if devices.empty? if options[:target] raise _("Target device / certificate '%{target}' not found in %{config}") % { target: options[:target], config: Puppet[:deviceconfig] } else Puppet.err _("No device found in %{config}") % { config: Puppet[:deviceconfig] } exit(1) end end devices.collect do |devicename,device| begin device_url = URI.parse(device.url) # Handle nil scheme & port scheme = "#{device_url.scheme}://" if device_url.scheme port = ":#{device_url.port}" if device_url.port # override local $vardir and $certname Puppet[:confdir] = ::File.join(Puppet[:devicedir], device.name) Puppet[:libdir] = options[:libdir] || ::File.join(Puppet[:devicedir], device.name, 'lib') Puppet[:vardir] = ::File.join(Puppet[:devicedir], device.name) Puppet[:certname] = device.name unless options[:resource] || options[:facts] || options[:apply] || options[:libdir] Puppet::Configurer::PluginHandler.new.download_plugins(env) end # this init the device singleton, so that the facts terminus # and the various network_device provider can use it Puppet::Util::NetworkDevice.init(device) if options[:resource] type, name = parse_args(command_line.args) Puppet.info _("retrieving resource: %{resource} from %{target} at %{scheme}%{url_host}%{port}%{url_path}") % { resource: type, target: device.name, scheme: scheme, url_host: device_url.host, port: port, url_path: device_url.path } resources = find_resources(type, name) if options[:to_yaml] text = resources.map do |resource| resource.prune_parameters(:parameters_to_include => @extra_params).to_hierayaml.force_encoding(Encoding.default_external) end.join("\n") text.prepend("#{type.downcase}:\n") else text = resources.map do |resource| resource.prune_parameters(:parameters_to_include => @extra_params).to_manifest.force_encoding(Encoding.default_external) end.join("\n") end (puts text) 0 elsif options[:facts] Puppet.info _("retrieving facts from %{target} at %{scheme}%{url_host}%{port}%{url_path}") % { resource: type, target: device.name, scheme: scheme, url_host: device_url.host, port: port, url_path: device_url.path } remote_facts = Puppet::Node::Facts.indirection.find(name, :environment => env) # Give a proper name to the facts remote_facts.name = remote_facts.values['clientcert'] renderer = Puppet::Network::FormatHandler.format(:console) puts renderer.render(remote_facts) 0 elsif options[:apply] # ensure we have a cache folder structure exists for the device FileUtils.mkdir_p(Puppet[:statedir]) unless File.directory?(Puppet[:statedir]) # avoid reporting to server Puppet::Transaction::Report.indirection.terminus_class = :yaml Puppet::Resource::Catalog.indirection.cache_class = nil require 'puppet/application/apply' begin Puppet[:node_terminus] = :plain Puppet[:catalog_terminus] = :compiler Puppet[:catalog_cache_terminus] = nil Puppet[:facts_terminus] = :network_device Puppet.override(:network_device => true) do Puppet::Application::Apply.new(Puppet::Util::CommandLine.new('puppet', ["apply", options[:apply]])).run_command end end else Puppet.info _("starting applying configuration to %{target} at %{scheme}%{url_host}%{port}%{url_path}") % { target: device.name, scheme: scheme, url_host: device_url.host, port: port, url_path: device_url.path } # this will reload and recompute default settings and create the devices sub vardir Puppet.settings.use :main, :agent, :ssl # ask for a ssl cert if needed, but at least # setup the ssl system for this device. setup_host require 'puppet/configurer' configurer = Puppet::Configurer.new configurer.run(:network_device => true, :pluginsync => Puppet::Configurer.should_pluginsync? && !options[:libdir]) end rescue => detail Puppet.log_exception(detail) # If we rescued an error, then we return 1 as the exit code 1 ensure Puppet[:libdir] = libdir Puppet[:vardir] = vardir Puppet[:confdir] = confdir Puppet[:certname] = certname Puppet::SSL::Host.reset end end end if ! returns or returns.compact.empty? exit(1) elsif options[:detailed_exitcodes] # Bitwise OR the return codes together, puppet style exit(returns.compact.reduce(:|)) elsif returns.include? 1 exit(1) else exit(0) end end def parse_args(args) type = args.shift or raise _("You must specify the type to display") Puppet::Type.type(type) or raise _("Could not find type %{type}") % { type: type } name = args.shift [type, name] end def find_resources(type, name) key = [type, name].join('/') if name [ Puppet::Resource.indirection.find( key ) ] else Puppet::Resource.indirection.search( key, {} ) end end def setup_host @host = Puppet::SSL::Host.new waitforcert = options[:waitforcert] || (Puppet[:onetime] ? 0 : Puppet[:waitforcert]) @host.wait_for_cert(waitforcert) end def setup setup_logs if options[:apply] || options[:facts] || options[:resource] Puppet::Util::Log.newdestination(:console) Puppet.settings.use :main, :agent, :ssl else args[:Server] = Puppet[:server] if options[:centrallogs] logdest = args[:Server] logdest += ":" + args[:Port] if args.include?(:Port) Puppet::Util::Log.newdestination(logdest) end Puppet.settings.use :main, :agent, :device, :ssl # We need to specify a ca location for all of the SSL-related # indirected classes to work; in fingerprint mode we just need # access to the local files and we don't need a ca. Puppet::SSL::Host.ca_location = :remote Puppet::Transaction::Report.indirection.terminus_class = :rest if Puppet[:catalog_cache_terminus] Puppet::Resource::Catalog.indirection.cache_class = Puppet[:catalog_cache_terminus].intern end end end end puppet-5.5.10/lib/puppet/application/doc.rb0000644005276200011600000001333413417161721020476 0ustar jenkinsjenkinsrequire 'puppet/application' class Puppet::Application::Doc < Puppet::Application run_mode :master attr_accessor :unknown_args, :manifest def preinit {:references => [], :mode => :text, :format => :to_markdown }.each do |name,value| options[name] = value end @unknown_args = [] @manifest = false end option("--all","-a") option("--outputdir OUTPUTDIR","-o") option("--verbose","-v") option("--debug","-d") option("--charset CHARSET") option("--format FORMAT", "-f") do |arg| method = "to_#{arg}" require 'puppet/util/reference' if Puppet::Util::Reference.method_defined?(method) options[:format] = method else raise _("Invalid output format %{arg}") % { arg: arg } end end option("--mode MODE", "-m") do |arg| require 'puppet/util/reference' if Puppet::Util::Reference.modes.include?(arg) or arg.intern==:rdoc options[:mode] = arg.intern else raise _("Invalid output mode %{arg}") % { arg: arg } end end option("--list", "-l") do |arg| require 'puppet/util/reference' puts Puppet::Util::Reference.references.collect { |r| Puppet::Util::Reference.reference(r).doc }.join("\n") exit(0) end option("--reference REFERENCE", "-r") do |arg| options[:references] << arg.intern end def summary _("Generate Puppet references") end def help <<-HELP puppet-doc(8) -- #{summary} ======== SYNOPSIS -------- Generates a reference for all Puppet types. Largely meant for internal Puppet Inc. use. (Deprecated) USAGE ----- puppet doc [-h|--help] [-l|--list] [-r|--reference ] DESCRIPTION ----------- This deprecated command generates a Markdown document to stdout describing all installed Puppet types or all allowable arguments to puppet executables. It is largely meant for internal use and is used to generate the reference document available on the Puppet Inc. web site. For Puppet module documentation (and all other use cases) this command has been superseded by the "puppet-strings" module - see https://github.com/puppetlabs/puppetlabs-strings for more information. This command (puppet-doc) will be removed once the puppetlabs internal documentation processing pipeline is completely based on puppet-strings. OPTIONS ------- * --help: Print this help message * --reference: Build a particular reference. Get a list of references by running 'puppet doc --list'. EXAMPLE ------- $ puppet doc -r type > /tmp/type_reference.markdown AUTHOR ------ Luke Kanies COPYRIGHT --------- Copyright (c) 2011 Puppet Inc., LLC Licensed under the Apache 2.0 License HELP end def handle_unknown( opt, arg ) @unknown_args << {:opt => opt, :arg => arg } true end def run_command return [:rdoc].include?(options[:mode]) ? send(options[:mode]) : other end def rdoc exit_code = 0 files = [] unless @manifest env = Puppet.lookup(:current_environment) files += env.modulepath files << ::File.dirname(env.manifest) if env.manifest != Puppet::Node::Environment::NO_MANIFEST end files += command_line.args Puppet.info _("scanning: %{files}") % { files: files.inspect } Puppet.settings[:document_all] = options[:all] || false begin require 'puppet/util/rdoc' if @manifest Puppet::Util::RDoc.manifestdoc(files) else options[:outputdir] = "doc" unless options[:outputdir] Puppet::Util::RDoc.rdoc(options[:outputdir], files, options[:charset]) end rescue => detail Puppet.log_exception(detail, _("Could not generate documentation: %{detail}") % { detail: detail }) exit_code = 1 end exit exit_code end def other text = "" with_contents = options[:references].length <= 1 exit_code = 0 require 'puppet/util/reference' options[:references].sort { |a,b| a.to_s <=> b.to_s }.each do |name| raise _("Could not find reference %{name}") % { name: name } unless section = Puppet::Util::Reference.reference(name) begin # Add the per-section text, but with no ToC text += section.send(options[:format], with_contents) rescue => detail Puppet.log_exception(detail, _("Could not generate reference %{name}: %{detail}") % { name: name, detail: detail }) exit_code = 1 next end end text += Puppet::Util::Reference.footer unless with_contents # We've only got one reference if options[:mode] == :pdf Puppet::Util::Reference.pdf(text) else puts text end exit exit_code end def setup # sole manifest documentation if command_line.args.size > 0 options[:mode] = :rdoc @manifest = true end if options[:mode] == :rdoc setup_rdoc else setup_reference end setup_logging end def setup_reference if options[:all] # Don't add dynamic references to the "all" list. require 'puppet/util/reference' options[:references] = Puppet::Util::Reference.references.reject do |ref| Puppet::Util::Reference.reference(ref).dynamic? end end options[:references] << :type if options[:references].empty? end def setup_rdoc # consume the unknown options # and feed them as settings if @unknown_args.size > 0 @unknown_args.each do |option| # force absolute path for modulepath when passed on commandline if option[:opt]=="--modulepath" option[:arg] = option[:arg].split(::File::PATH_SEPARATOR).collect { |p| ::File.expand_path(p) }.join(::File::PATH_SEPARATOR) end Puppet.settings.handlearg(option[:opt], option[:arg]) end end end def setup_logging Puppet::Util::Log.level = :warning set_log_level Puppet::Util::Log.newdestination(:console) end end puppet-5.5.10/lib/puppet/application/lookup.rb0000644005276200011600000003046213417161721021243 0ustar jenkinsjenkinsrequire 'puppet/application' require 'puppet/pops' require 'puppet/node' require 'puppet/parser/compiler' class Puppet::Application::Lookup < Puppet::Application RUN_HELP = _("Run 'puppet lookup --help' for more details").freeze DEEP_MERGE_OPTIONS = '--knock-out-prefix, --sort-merged-arrays, and --merge-hash-arrays'.freeze run_mode :master # Options for lookup option('--merge TYPE') do |arg| options[:merge] = arg end option('--debug', '-d') option('--verbose', '-v') option('--render-as FORMAT') do |format| options[:render_as] = format.downcase.to_sym end option('--type TYPE_STRING') do |arg| options[:type] = arg end option('--compile', '-c') option('--knock-out-prefix PREFIX_STRING') do |arg| options[:prefix] = arg end option('--sort-merge-arrays') option('--merge-hash-arrays') option('--explain') option('--explain-options') option('--default VALUE') do |arg| options[:default_value] = arg end # not yet supported option('--trusted') # Options for facts/scope option('--node NODE_NAME') do |arg| options[:node] = arg end option('--facts FACT_FILE') do |arg| if %w{.yaml .yml .json}.include?(arg.match(/\.[^.]*$/)[0]) options[:fact_file] = arg else raise _("The --fact file only accepts yaml and json files.\n%{run_help}") % { run_help: RUN_HELP } end end def app_defaults super.merge({ :facts_terminus => 'yaml' }) end def setup_logs # This sets up logging based on --debug or --verbose if they are set in `options` set_log_level # This uses console for everything that is not a compilation Puppet::Util::Log.newdestination(:console) end def setup_terminuses require 'puppet/file_serving/content' require 'puppet/file_serving/metadata' Puppet::FileServing::Content.indirection.terminus_class = :file_server Puppet::FileServing::Metadata.indirection.terminus_class = :file_server Puppet::FileBucket::File.indirection.terminus_class = :file end def setup setup_logs exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? Puppet.settings.use :main, :master, :ssl, :metrics setup_terminuses end def summary _("Interactive Hiera lookup") end def help <<-HELP puppet-lookup(8) -- #{summary} ======== SYNOPSIS -------- Does Hiera lookups from the command line. Since this command needs access to your Hiera data, make sure to run it on a node that has a copy of that data. This usually means logging into a Puppet Server node and running 'puppet lookup' with sudo. The most common version of this command is: 'puppet lookup --node --environment --explain' USAGE ----- puppet lookup [--help] [--type ] [--merge first|unique|hash|deep] [--knock-out-prefix ] [--sort-merged-arrays] [--merge-hash-arrays] [--explain] [--environment ] [--default ] [--node ] [--facts ] [--compile] [--render-as s|json|yaml|binary|msgpack] DESCRIPTION ----------- The lookup command is a CLI for Puppet's 'lookup()' function. It searches your Hiera data and returns a value for the requested lookup key, so you can test and explore your data. It is a modern replacement for the 'hiera' command. Hiera usually relies on a node's facts to locate the relevant data sources. By default, 'puppet lookup' uses facts from the node you run the command on, but you can get data for any other node with the '--node ' option. If possible, the lookup command will use the requested node's real stored facts from PuppetDB; if PuppetDB isn't configured or you want to provide arbitrary fact values, you can pass alternate facts as a JSON or YAML file with '--facts '. If you're debugging your Hiera data and want to see where values are coming from, use the '--explain' option. If '--explain' isn't specified, lookup exits with 0 if a value was found and 1 otherwise. With '--explain', lookup always exits with 0 unless there is a major error. You can provide multiple lookup keys to this command, but it only returns a value for the first found key, omitting the rest. For more details about how Hiera works, see the Hiera documentation: https://puppet.com/docs/puppet/latest/hiera_intro.html OPTIONS ------- * --help: Print this help message. * --explain Explain the details of how the lookup was performed and where the final value came from (or the reason no value was found). * --node Specify which node to look up data for; defaults to the node where the command is run. Since Hiera's purpose is to provide different values for different nodes (usually based on their facts), you'll usually want to use some specific node's facts to explore your data. If the node where you're running this command is configured to talk to PuppetDB, the command will use the requested node's most recent facts. Otherwise, you can override facts with the '--facts' option. * --facts Specify a .json or .yaml file of key => value mappings to override the facts for this lookup. Any facts not specified in this file maintain their original value. * --environment Like with most Puppet commands, you can specify an environment on the command line. This is important for lookup because different environments can have different Hiera data. * --merge first|unique|hash|deep: Specify the merge behavior, overriding any merge behavior from the data's lookup_options. 'first' returns the first value found. 'unique' appends everything to a merged, deduplicated array. 'hash' performs a simple hash merge by overwriting keys of lower lookup priority. 'deep' performs a deep merge on values of Array and Hash type. There are additional options that can be used with 'deep'. * --knock-out-prefix Can be used with the 'deep' merge strategy. Specifies a prefix to indicate a value should be removed from the final result. * --sort-merged-arrays Can be used with the 'deep' merge strategy. When this flag is used, all merged arrays are sorted. * --merge-hash-arrays Can be used with the 'deep' merge strategy. When this flag is used, hashes WITHIN arrays are deep-merged with their counterparts by position. * --explain-options Explain whether a lookup_options hash affects this lookup, and how that hash was assembled. (lookup_options is how Hiera configures merge behavior in data.) * --default A value to return if Hiera can't find a value in data. For emulating calls to the 'lookup()' function that include a default. * --type : Assert that the value has the specified type. For emulating calls to the 'lookup()' function that include a data type. * --compile Perform a full catalog compilation prior to the lookup. If your hierarchy and data only use the $facts, $trusted, and $server_facts variables, you don't need this option; however, if your Hiera configuration uses arbitrary variables set by a Puppet manifest, you might need this option to get accurate data. No catalog compilation takes place unless this flag is given. * --render-as s|json|yaml|binary|msgpack Specify the output format of the results; "s" means plain text. The default when producing a value is yaml and the default when producing an explanation is s. EXAMPLE ------- To look up 'key_name' using the Puppet Server node's facts: $ puppet lookup key_name To look up 'key_name' with agent.local's facts: $ puppet lookup --node agent.local key_name To get the first value found for 'key_name_one' and 'key_name_two' with agent.local's facts while merging values and knocking out the prefix 'foo' while merging: $ puppet lookup --node agent.local --merge deep --knock-out-prefix foo key_name_one key_name_two To lookup 'key_name' with agent.local's facts, and return a default value of 'bar' if nothing was found: $ puppet lookup --node agent.local --default bar key_name To see an explanation of how the value for 'key_name' would be found, using agent.local's facts: $ puppet lookup --node agent.local --explain key_name COPYRIGHT --------- Copyright (c) 2015 Puppet Inc., LLC Licensed under the Apache 2.0 License HELP end def main keys = command_line.args #unless options[:node] # raise "No node was given via the '--node' flag for the scope of the lookup.\n#{RUN_HELP}" #end if (options[:sort_merged_arrays] || options[:merge_hash_arrays] || options[:prefix]) && options[:merge] != 'deep' raise _("The options %{deep_merge_opts} are only available with '--merge deep'\n%{run_help}") % { deep_merge_opts: DEEP_MERGE_OPTIONS, run_help: RUN_HELP } end use_default_value = !options[:default_value].nil? merge_options = nil merge = options[:merge] unless merge.nil? strategies = Puppet::Pops::MergeStrategy.strategy_keys unless strategies.include?(merge.to_sym) strategies = strategies.map {|k| "'#{k}'"} raise _("The --merge option only accepts %{strategies}, or %{last_strategy}\n%{run_help}") % { strategies: strategies[0...-1].join(', '), last_strategy: strategies.last, run_help: RUN_HELP } end if merge == 'deep' merge_options = {'strategy' => 'deep', 'sort_merged_arrays' => !options[:sort_merged_arrays].nil?, 'merge_hash_arrays' => !options[:merge_hash_arrays].nil?} if options[:prefix] merge_options.merge!({'knockout_prefix' => options[:prefix]}) end else merge_options = {'strategy' => merge} end end explain_data = !!options[:explain] explain_options = !!options[:explain_options] only_explain_options = explain_options && !explain_data if keys.empty? if only_explain_options # Explain lookup_options for lookup of an unqualified value. keys = Puppet::Pops::Lookup::GLOBAL else raise _('No keys were given to lookup.') end end explain = explain_data || explain_options # Format defaults to text (:s) when producing an explanation and :yaml when producing the value format = options[:render_as] || (explain ? :s : :yaml) renderer = Puppet::Network::FormatHandler.format(format) raise _("Unknown rendering format '%{format}'") % { format: format } if renderer.nil? generate_scope do |scope| lookup_invocation = Puppet::Pops::Lookup::Invocation.new(scope, {}, {}, explain ? Puppet::Pops::Lookup::Explainer.new(explain_options, only_explain_options) : nil) begin type = options.include?(:type) ? Puppet::Pops::Types::TypeParser.singleton.parse(options[:type], scope) : nil result = Puppet::Pops::Lookup.lookup(keys, type, options[:default_value], use_default_value, merge_options, lookup_invocation) puts renderer.render(result) unless explain rescue Puppet::DataBinding::LookupError => e lookup_invocation.report_text { e.message } exit(1) unless explain end puts format == :s ? lookup_invocation.explainer.explain : renderer.render(lookup_invocation.explainer.to_hash) if explain end exit(0) end def generate_scope if options[:node] node = options[:node] else node = Puppet[:node_name_value] # If we want to lookup the node we are currently on # we must returning these settings to their default values Puppet.settings[:facts_terminus] = 'facter' end unless node.is_a?(Puppet::Node) # to allow unit tests to pass a node instance ni = Puppet::Node.indirection tc = ni.terminus_class if tc == :plain || options[:compile] node = ni.find(node) else ni.terminus_class = :plain node = ni.find(node) ni.terminus_class = tc end end fact_file = options[:fact_file] if fact_file if fact_file.end_with?("json") given_facts = Puppet::Util::Json.load(Puppet::FileSystem.read(fact_file, :encoding => 'utf-8')) else given_facts = YAML.load(Puppet::FileSystem.read(fact_file, :encoding => 'utf-8')) end unless given_facts.instance_of?(Hash) raise _("Incorrect formatted data in %{fact_file} given via the --facts flag") % { fact_file: fact_file } end node.add_extra_facts(given_facts) end Puppet[:code] = 'undef' unless options[:compile] compiler = Puppet::Parser::Compiler.new(node) compiler.compile { |catalog| yield(compiler.topscope); catalog } end end puppet-5.5.10/lib/puppet/application/master.rb0000644005276200011600000002211313417161721021217 0ustar jenkinsjenkinsrequire 'puppet/application' require 'puppet/daemon' require 'puppet/util/pidlock' class Puppet::Application::Master < Puppet::Application run_mode :master option("--debug", "-d") option("--verbose", "-v") # internal option, only to be used by ext/rack/config.ru option("--rack") option("--compile host", "-c host") do |arg| options[:node] = arg end option("--logdest DEST", "-l DEST") do |arg| handle_logdest_arg(arg) end option("--parseonly") do |args| puts "--parseonly has been removed. Please use 'puppet parser validate '" exit 1 end def summary _("The puppet master daemon") end def help <<-HELP puppet-master(8) -- #{summary} ======== SYNOPSIS -------- The central puppet server. Functions as a certificate authority by default. USAGE ----- puppet master [-D|--daemonize|--no-daemonize] [-d|--debug] [-h|--help] [-l|--logdest syslog||console] [-v|--verbose] [-V|--version] [--compile ] DESCRIPTION ----------- This command starts an instance of puppet master, running as a daemon and using Ruby's built-in Webrick webserver. Puppet master can also be managed by other application servers; when this is the case, this executable is not used. OPTIONS ------- Note that any Puppet setting that's valid in the configuration file is also a valid long argument. For example, 'server' is a valid setting, so you can specify '--server ' as an argument. Boolean settings translate into '--setting' and '--no-setting' pairs. See the configuration file documentation at https://puppet.com/docs/puppet/latest/configuration.html for the full list of acceptable settings. A commented list of all settings can also be generated by running puppet master with '--genconfig'. * --daemonize: Send the process into the background. This is the default. (This is a Puppet setting, and can go in puppet.conf. Note the special 'no-' prefix for boolean settings on the command line.) * --no-daemonize: Do not send the process into the background. (This is a Puppet setting, and can go in puppet.conf. Note the special 'no-' prefix for boolean settings on the command line.) * --debug: Enable full debugging. * --help: Print this help message. * --logdest: Where to send log messages. Choose between 'syslog' (the POSIX syslog service), 'console', or the path to a log file. If debugging or verbosity is enabled, this defaults to 'console'. Otherwise, it defaults to 'syslog'. A path ending with '.json' will receive structured output in JSON format. The log file will not have an ending ']' automatically written to it due to the appending nature of logging. It must be appended manually to make the content valid JSON. * --masterport: The port on which to listen for traffic. The default port is 8140. (This is a Puppet setting, and can go in puppet.conf.) * --verbose: Enable verbosity. * --version: Print the puppet version number and exit. * --compile: Compile a catalogue and output it in JSON from the puppet master. Uses facts contained in the $vardir/yaml/ directory to compile the catalog. EXAMPLE ------- puppet master DIAGNOSTICS ----------- When running as a standalone daemon, puppet master accepts the following signals: * SIGHUP: Restart the puppet master server. * SIGINT and SIGTERM: Shut down the puppet master server. * SIGUSR2: Close file descriptors for log files and reopen them. Used with logrotate. AUTHOR ------ Luke Kanies COPYRIGHT --------- Copyright (c) 2012 Puppet Inc., LLC Licensed under the Apache 2.0 License HELP end def app_defaults super.merge({ :facts_terminus => 'yaml' }) end def preinit Signal.trap(:INT) do $stderr.puts _("Canceling startup") exit(0) end # save ARGV to protect us from it being smashed later by something @argv = ARGV.dup end def run_command if options[:node] compile else main end end def compile begin unless catalog = Puppet::Resource::Catalog.indirection.find(options[:node]) raise _("Could not compile catalog for %{node}") % { node: options[:node] } end puts JSON::pretty_generate(catalog.to_resource, :allow_nan => true, :max_nesting => false) rescue => detail Puppet.log_exception(detail, _("Failed to compile catalog for node %{node}: %{detail}") % { node: options[:node], detail: detail }) exit(30) end exit(0) end def main require 'etc' # Make sure we've got a localhost ssl cert Puppet::SSL::Host.localhost # And now configure our server to *only* hit the CA for data, because that's # all it will have write access to. Puppet::SSL::Host.ca_location = :only if Puppet::SSL::CertificateAuthority.ca? if Puppet.features.root? if Puppet::Type.type(:user).new(:name => Puppet[:user]).exists? begin Puppet::Util.chuser rescue => detail Puppet.log_exception(detail, _("Could not change user to %{user}: %{detail}") % { user: Puppet[:user], detail: detail }) exit(39) end else Puppet.err(_("Could not change user to %{user}. User does not exist and is required to continue.") % { user: Puppet[:user] }) exit(74) end end if options[:rack] Puppet.deprecation_warning(_("The Rack Puppet master server is deprecated and will be removed in a future release. Please use Puppet Server instead. See http://links.puppet.com/deprecate-rack-webrick-servers for more information.")) start_rack_master else Puppet.deprecation_warning(_("The WEBrick Puppet master server is deprecated and will be removed in a future release. Please use Puppet Server instead. See http://links.puppet.com/deprecate-rack-webrick-servers for more information.")) start_webrick_master end end def setup_logs set_log_level handle_logdest_arg(Puppet[:logdest]) unless options[:setdest] if options[:node] # We are compiling a catalog for a single node with '--compile' and a # logging destination has not already been explicitly specified. Puppet::Util::Log.newdestination(:console) elsif !(Puppet[:daemonize] or options[:rack]) # We are running a webrick master which has been explicitly foregrounded # and a logging destination has not already been explicitly specified, # assume users want to see logging and log to the console. Puppet::Util::Log.newdestination(:console) else # No explicit log destination has been given with '--logdest', or via settings, # and we're either a daemonized webrick master or running under rack, log to # syslog. Puppet::Util::Log.newdestination(:syslog) end end end def setup_terminuses require 'puppet/file_serving/content' require 'puppet/file_serving/metadata' Puppet::FileServing::Content.indirection.terminus_class = :file_server Puppet::FileServing::Metadata.indirection.terminus_class = :file_server Puppet::FileBucket::File.indirection.terminus_class = :file end def setup_ssl # Configure all of the SSL stuff. if Puppet::SSL::CertificateAuthority.ca? Puppet::SSL::Host.ca_location = :local Puppet.settings.use :ca Puppet::SSL::CertificateAuthority.instance else Puppet::SSL::Host.ca_location = :none end Puppet::SSL::Oids.register_puppet_oids Puppet::SSL::Oids.load_custom_oid_file(Puppet[:trusted_oid_mapping_file]) end # Honor the :node_cache_terminus setting if users have specified it directly. # We normally want this nil as use-cases for querying nodes should be going to # PuppetDB. # @see PUP-6060 # @return [void] def setup_node_cache Puppet::Node.indirection.cache_class = Puppet[:node_cache_terminus] end def setup raise Puppet::Error.new(_("Puppet master is not supported on Microsoft Windows")) if Puppet.features.microsoft_windows? setup_logs exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? Puppet.settings.use :main, :master, :ssl, :metrics setup_terminuses setup_node_cache setup_ssl end private # Start a master that will be using WeBrick. # # This method will block until the master exits. def start_webrick_master require 'puppet/network/server' daemon = Puppet::Daemon.new(Puppet::Util::Pidlock.new(Puppet[:pidfile])) daemon.argv = @argv daemon.server = Puppet::Network::Server.new(Puppet[:bindaddress], Puppet[:masterport]) daemon.daemonize if Puppet[:daemonize] # Setup signal traps immediately after daemonization so we clean up the daemon daemon.set_signal_traps announce_start_of_master daemon.start end # Start a master that will be used for a Rack container. # # This method immediately returns the Rack handler that must be returned to # the calling Rack container def start_rack_master require 'puppet/network/http/rack' announce_start_of_master return Puppet::Network::HTTP::Rack.new() end def announce_start_of_master Puppet.notice _("Starting Puppet master version %{version}") % { version: Puppet.version } end end puppet-5.5.10/lib/puppet/application/script.rb0000644005276200011600000001744213417161721021241 0ustar jenkinsjenkinsrequire 'puppet/application' require 'puppet/configurer' require 'puppet/util/profiler/aggregate' require 'puppet/parser/script_compiler' class Puppet::Application::Script < Puppet::Application option("--debug","-d") option("--execute EXECUTE","-e") do |arg| options[:code] = arg end option("--test","-t") option("--verbose","-v") option("--logdest LOGDEST", "-l") do |arg| handle_logdest_arg(arg) end def summary _("Run a puppet manifests as a script without compiling a catalog") end def help <<-HELP puppet-script(8) -- #{summary} ======== SYNOPSIS -------- Runs a puppet language script without compiling a catalog. USAGE ----- puppet script [-h|--help] [-V|--version] [-d|--debug] [-v|--verbose] [-e|--execute] [-l|--logdest syslog|eventlog||console] [--noop] DESCRIPTION ----------- This is a standalone puppet script runner tool; use it to run puppet code without compiling a catalog. When provided with a modulepath, via command line or config file, puppet script can load functions, types, tasks and plans from modules. OPTIONS ------- Note that any setting that's valid in the configuration file is also a valid long argument. For example, 'environment' is a valid setting, so you can specify '--environment mytest' as an argument. See the configuration file documentation at https://puppet.com/docs/puppet/latest/configuration.html for the full list of acceptable parameters. A commented list of all configuration options can also be generated by running puppet with '--genconfig'. * --debug: Enable full debugging. * --help: Print this help message * --logdest: Where to send log messages. Choose between 'syslog' (the POSIX syslog service), 'eventlog' (the Windows Event Log), 'console', or the path to a log file. Defaults to 'console'. A path ending with '.json' will receive structured output in JSON format. The log file will not have an ending ']' automatically written to it due to the appending nature of logging. It must be appended manually to make the content valid JSON. * --noop: Use 'noop' mode where Puppet runs in a no-op or dry-run mode. This is useful for seeing what changes Puppet will make without actually executing the changes. Applies to tasks only. * --execute: Execute a specific piece of Puppet code * --verbose: Print extra information. EXAMPLE ------- $ puppet script -l /tmp/manifest.log manifest.pp $ puppet script --modulepath=/root/dev/modules -e 'notice("hello world")' AUTHOR ------ Henrik Lindberg COPYRIGHT --------- Copyright (c) 2017 Puppet Inc., LLC Licensed under the Apache 2.0 License HELP end def app_defaults super.merge({ :default_file_terminus => :file_server, }) end def run_command if Puppet.features.bolt? Puppet.override(:bolt_executor => Bolt::Executor.new) do main end else raise _("Bolt must be installed to use the script application") end end def main # The tasks feature is always on Puppet[:tasks] = true # Set the puppet code or file to use. if options[:code] || command_line.args.length == 0 Puppet[:code] = options[:code] || STDIN.read else manifest = command_line.args.shift raise _("Could not find file %{manifest}") % { manifest: manifest } unless Puppet::FileSystem.exist?(manifest) Puppet.warning(_("Only one file can be used per run. Skipping %{files}") % { files: command_line.args.join(', ') }) if command_line.args.size > 0 end unless Puppet[:node_name_fact].empty? # Collect the facts specified for that node unless facts = Puppet::Node::Facts.indirection.find(Puppet[:node_name_value]) raise _("Could not find facts for %{node}") % { node: Puppet[:node_name_value] } end Puppet[:node_name_value] = facts.values[Puppet[:node_name_fact]] facts.name = Puppet[:node_name_value] end # Find the Node unless node = Puppet::Node.indirection.find(Puppet[:node_name_value]) raise _("Could not find node %{node}") % { node: Puppet[:node_name_value] } end configured_environment = node.environment || Puppet.lookup(:current_environment) apply_environment = manifest ? configured_environment.override_with(:manifest => manifest) : configured_environment # Modify the node descriptor to use the special apply_environment. # It is based on the actual environment from the node, or the locally # configured environment if the node does not specify one. # If a manifest file is passed on the command line, it overrides # the :manifest setting of the apply_environment. node.environment = apply_environment # TRANSLATION, the string "For puppet script" is not user facing Puppet.override({:current_environment => apply_environment}, "For puppet script") do # Merge in the facts. node.merge(facts.values) if facts # Add server facts so $server_facts[environment] exists when doing a puppet script # SCRIPT TODO: May be needed when running scripts under orchestrator. Leave it for now. # node.add_server_facts({}) begin # Compile the catalog # When compiling, the compiler traps and logs certain errors # Those that do not lead to an immediate exit are caught by the general # rule and gets logged. # begin # support the following features when evaluating puppet code # * $facts with facts from host running the script # * $settings with 'settings::*' namespace populated, and '$settings::all_local' hash # * $trusted as setup when using puppet apply # * an environment # # fixup trusted information node.sanitize() compiler = Puppet::Parser::ScriptCompiler.new(node.environment, node.name) topscope = compiler.topscope # When scripting the trusted data are always local, but set them anyway topscope.set_trusted(node.trusted_data) # Server facts are always about the local node's version etc. topscope.set_server_facts(node.server_facts) # Set $facts for the node running the script facts_hash = node.facts.nil? ? {} : node.facts.values topscope.set_facts(facts_hash) # create the $settings:: variables topscope.merge_settings(node.environment.name, false) compiler.compile() rescue Puppet::ParseErrorWithIssue, Puppet::Error # already logged and handled by the compiler for these two cases exit(1) end exit(0) rescue => detail Puppet.log_exception(detail) exit(1) end end ensure if @profiler Puppet::Util::Profiler.remove_profiler(@profiler) @profiler.shutdown end end def setup exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? handle_logdest_arg(Puppet[:logdest]) Puppet::Util::Log.newdestination(:console) unless options[:setdest] Signal.trap(:INT) do $stderr.puts _("Exiting") exit(1) end # TODO: This skips applying the settings catalog for these settings, but # the effect of doing this is unknown. It may be that it only works if there is a puppet # installed where a settings catalog have already been applied... # This saves 1/5th of the startup time # Puppet.settings.use :main, :agent, :ssl # When running a script, the catalog is not relevant, and neither is caching of it Puppet::Resource::Catalog.indirection.cache_class = nil # we do not want the last report to be persisted Puppet::Transaction::Report.indirection.cache_class = nil set_log_level if Puppet[:profile] @profiler = Puppet::Util::Profiler.add_profiler(Puppet::Util::Profiler::Aggregate.new(Puppet.method(:info), "script")) end end end puppet-5.5.10/lib/puppet/coercion.rb0000644005276200011600000000167413417161721017233 0ustar jenkinsjenkins# Various methods used to coerce values into a canonical form. # # @api private module Puppet::Coercion # Try to coerce various input values into boolean true/false # # Only a very limited subset of values are allowed. This method does not try # to provide a generic "truthiness" system. # # @param value [Boolean, Symbol, String] # @return [Boolean] # @raise # @api private def self.boolean(value) # downcase strings if value.respond_to? :downcase value = value.downcase end case value when true, :true, 'true', :yes, 'yes' true when false, :false, 'false', :no, 'no' false else fail('expected a boolean value') end end # Return the list of acceptable boolean values. # # This is limited to lower-case, even though boolean() is case-insensitive. # # @return [Array] # @raise # @api private def self.boolean_values ['true', 'false', 'yes', 'no'] end end puppet-5.5.10/lib/puppet/compilable_resource_type.rb0000644005276200011600000000133413417161721022502 0ustar jenkinsjenkinsrequire 'puppet' # The CompilableResourceType module should be either included in a class or used as a class extension # to mark that the instance used as the 'resource type' of a resource instance # is an object that is compatible with Puppet::Type's API wrt. compiling. # Puppet Resource Types written in Ruby use a meta programmed Ruby Class as the type. Those classes # are subtypes of Puppet::Type. Meta data (Pcore/puppet language) based resource types uses instances of # a class instead. # module Puppet::CompilableResourceType # All 3.x resource types implemented in Ruby using Puppet::Type respond true. # Other kinds of implementations should reimplement and return false. def is_3x_ruby_plugin? true end endpuppet-5.5.10/lib/puppet/configurer/0000755005276200011600000000000013417162176017245 5ustar jenkinsjenkinspuppet-5.5.10/lib/puppet/configurer/downloader.rb0000644005276200011600000000330613417161721021725 0ustar jenkinsjenkinsrequire 'puppet/configurer' require 'puppet/resource/catalog' class Puppet::Configurer::Downloader attr_reader :name, :path, :source, :ignore # Evaluate our download, returning the list of changed values. def evaluate Puppet.info _("Retrieving %{name}") % { name: name } files = [] begin catalog.apply do |trans| trans.changed?.each do |resource| yield resource if block_given? files << resource[:path] end end rescue Puppet::Error => detail Puppet.log_exception(detail, _("Could not retrieve %{name}: %{detail}") % { name: name, detail: detail }) end files end def initialize(name, path, source, ignore = nil, environment = nil, source_permissions = :ignore) @name, @path, @source, @ignore, @environment, @source_permissions = name, path, source, ignore, environment, source_permissions end def catalog catalog = Puppet::Resource::Catalog.new("PluginSync", @environment) catalog.host_config = false catalog.add_resource(file) catalog end def file args = default_arguments.merge(:path => path, :source => source) args[:ignore] = ignore.split if ignore Puppet::Type.type(:file).new(args) end private def default_arguments defargs = { :path => path, :recurse => true, :links => :follow, :source => source, :source_permissions => @source_permissions, :tag => name, :purge => true, :force => true, :backup => false, :noop => false } if !Puppet.features.microsoft_windows? defargs.merge!( { :owner => Process.uid, :group => Process.gid } ) end return defargs end end puppet-5.5.10/lib/puppet/configurer/fact_handler.rb0000644005276200011600000000356613417161721022211 0ustar jenkinsjenkinsrequire 'puppet/indirector/facts/facter' require 'puppet/configurer' require 'puppet/configurer/downloader' # Break out the code related to facts. This module is # just included into the agent, but having it here makes it # easier to test. module Puppet::Configurer::FactHandler def find_facts # This works because puppet agent configures Facts to use 'facter' for # finding facts and the 'rest' terminus for caching them. Thus, we'll # compile them and then "cache" them on the server. begin facts = Puppet::Node::Facts.indirection.find(Puppet[:node_name_value], :environment => Puppet::Node::Environment.remote(@environment)) unless Puppet[:node_name_fact].empty? Puppet[:node_name_value] = facts.values[Puppet[:node_name_fact]] facts.name = Puppet[:node_name_value] end facts rescue SystemExit,NoMemoryError raise rescue Exception => detail message = _("Could not retrieve local facts: %{detail}") % { detail: detail } Puppet.log_exception(detail, message) raise Puppet::Error, message, detail.backtrace end end def facts_for_uploading facts = find_facts # NOTE: :facts specified as parameters are URI encoded here, # then encoded for a second time depending on their length: # # <= 1024 characters sent via query string of a HTTP GET, additionally query string encoded # > 1024 characters sent in POST data, additionally x-www-form-urlencoded # so it's only important that encoding method here return original values # correctly when CGI.unescape called against it (in compiler code) if Puppet[:preferred_serialization_format] == "pson" {:facts_format => :pson, :facts => Puppet::Util.uri_query_encode(facts.render(:pson)) } else {:facts_format => 'application/json', :facts => Puppet::Util.uri_query_encode(facts.render(:json)) } end end end puppet-5.5.10/lib/puppet/configurer/plugin_handler.rb0000644005276200011600000000260413417161721022562 0ustar jenkinsjenkins# Break out the code related to plugins. This module is # just included into the agent, but having it here makes it # easier to test. require 'puppet/configurer' class Puppet::Configurer::PluginHandler SUPPORTED_LOCALES_MOUNT_AGENT_VERSION = Gem::Version.new("5.3.4") def download_plugins(environment) source_permissions = Puppet.features.microsoft_windows? ? :ignore : :use plugin_downloader = Puppet::Configurer::Downloader.new( "plugin", Puppet[:plugindest], Puppet[:pluginsource], Puppet[:pluginsignore], environment ) plugin_fact_downloader = Puppet::Configurer::Downloader.new( "pluginfacts", Puppet[:pluginfactdest], Puppet[:pluginfactsource], Puppet[:pluginsignore], environment, source_permissions ) result = [] result += plugin_fact_downloader.evaluate result += plugin_downloader.evaluate server_agent_version = Puppet.lookup(:server_agent_version) { "0.0" } if Gem::Version.new(server_agent_version) >= SUPPORTED_LOCALES_MOUNT_AGENT_VERSION locales_downloader = Puppet::Configurer::Downloader.new( "locales", Puppet[:localedest], Puppet[:localesource], Puppet[:pluginsignore] + " *.pot config.yaml", environment ) result += locales_downloader.evaluate end Puppet::Util::Autoload.reload_changed result end end puppet-5.5.10/lib/puppet/confine.rb0000644005276200011600000000317013417161721017044 0ustar jenkinsjenkins# The class that handles testing whether our providers # actually work or not. require 'puppet/util' class Puppet::Confine include Puppet::Util @tests = {} class << self attr_accessor :name end def self.inherited(klass) name = klass.to_s.split("::").pop.downcase.to_sym raise "Test #{name} is already defined" if @tests.include?(name) klass.name = name @tests[name] = klass end def self.test(name) unless @tests.include?(name) begin require "puppet/confine/#{name}" rescue LoadError => detail unless detail.to_s =~ /No such file|cannot load such file/i warn "Could not load confine test '#{name}': #{detail}" end # Could not find file if !Puppet[:always_retry_plugins] @tests[name] = nil end end end @tests[name] end attr_reader :values # Mark that this confine is used for testing binary existence. attr_accessor :for_binary def for_binary? for_binary end # Used for logging. attr_accessor :label def initialize(values) values = [values] unless values.is_a?(Array) @values = values end # Provide a hook for the message when there's a failure. def message(value) "" end # Collect the results of all of them. def result values.collect { |value| pass?(value) } end # Test whether our confine matches. def valid? values.each do |value| unless pass?(value) Puppet.debug(label + ": " + message(value)) return false end end return true ensure reset end # Provide a hook for subclasses. def reset end end puppet-5.5.10/lib/puppet/confine/0000755005276200011600000000000013417162176016523 5ustar jenkinsjenkinspuppet-5.5.10/lib/puppet/confine/any.rb0000644005276200011600000000075413417161721017640 0ustar jenkinsjenkinsclass Puppet::Confine::Any < Puppet::Confine def self.summarize(confines) confines.inject(0) { |count, confine| count + confine.summary } end def pass?(value) !! value end def message(value) "0 confines (of #{value.length}) were true" end def summary result.find_all { |v| v == true }.length end def valid? if @values.any? { |value| pass?(value) } true else Puppet.debug("#{label}: #{message(@values)}") false end end end puppet-5.5.10/lib/puppet/confine/exists.rb0000644005276200011600000000072313417161721020364 0ustar jenkinsjenkinsrequire 'puppet/confine' class Puppet::Confine::Exists < Puppet::Confine def self.summarize(confines) confines.inject([]) { |total, confine| total + confine.summary } end def pass?(value) value && (for_binary? ? which(value) : Puppet::FileSystem.exist?(value)) end def message(value) "file #{value} does not exist" end def summary result.zip(values).inject([]) { |array, args| val, f = args; array << f unless val; array } end end puppet-5.5.10/lib/puppet/confine/false.rb0000644005276200011600000000054113417161721020135 0ustar jenkinsjenkinsrequire 'puppet/confine' class Puppet::Confine::False < Puppet::Confine def self.summarize(confines) confines.inject(0) { |count, confine| count + confine.summary } end def pass?(value) ! value end def message(value) "true value when expecting false" end def summary result.find_all { |v| v == false }.length end end puppet-5.5.10/lib/puppet/confine/feature.rb0000644005276200011600000000057713417161721020507 0ustar jenkinsjenkinsrequire 'puppet/confine' class Puppet::Confine::Feature < Puppet::Confine def self.summarize(confines) confines.collect { |c| c.values }.flatten.uniq.find_all { |value| ! confines[0].pass?(value) } end # Is the named feature available? def pass?(value) Puppet.features.send(value.to_s + "?") end def message(value) "feature #{value} is missing" end end puppet-5.5.10/lib/puppet/confine/true.rb0000644005276200011600000000062413417161721020024 0ustar jenkinsjenkinsrequire 'puppet/confine' class Puppet::Confine::True < Puppet::Confine def self.summarize(confines) confines.inject(0) { |count, confine| count + confine.summary } end def pass?(value) # Double negate, so we only get true or false. ! ! value end def message(value) "false value when expecting true" end def summary result.find_all { |v| v == true }.length end end puppet-5.5.10/lib/puppet/confine/variable.rb0000644005276200011600000000315613417161721020635 0ustar jenkinsjenkinsrequire 'puppet/confine' # Require a specific value for a variable, either a Puppet setting # or a Facter value. This class is a bit weird because the name # is set explicitly by the ConfineCollection class -- from this class, # it's not obvious how the name would ever get set. class Puppet::Confine::Variable < Puppet::Confine # Provide a hash summary of failing confines -- the key of the hash # is the name of the confine, and the value is the missing yet required values. # Only returns failed values, not all required values. def self.summarize(confines) result = Hash.new { |hash, key| hash[key] = [] } confines.inject(result) { |total, confine| total[confine.name] += confine.values unless confine.valid?; total } end # This is set by ConfineCollection. attr_accessor :name # Retrieve the value from facter def facter_value @facter_value ||= ::Facter.value(name).to_s.downcase end def initialize(values) super @values = @values.collect { |v| v.to_s.downcase } end def message(value) "facter value '#{test_value}' for '#{self.name}' not in required list '#{values.join(",")}'" end # Compare the passed-in value to the retrieved value. def pass?(value) test_value.downcase.to_s == value.to_s.downcase end def reset # Reset the cache. We want to cache it during a given # run, but not across runs. @facter_value = nil end def valid? @values.include?(test_value.to_s.downcase) ensure reset end private def setting? Puppet.settings.valid?(name) end def test_value setting? ? Puppet.settings[name] : facter_value end end puppet-5.5.10/lib/puppet/confine_collection.rb0000644005276200011600000000234613417161721021263 0ustar jenkinsjenkins# Manage a collection of confines, returning a boolean or # helpful information. require 'puppet/confine' class Puppet::ConfineCollection def confine(hash) if hash.include?(:for_binary) for_binary = true hash.delete(:for_binary) else for_binary = false end hash.each do |test, values| if klass = Puppet::Confine.test(test) @confines << klass.new(values) @confines[-1].for_binary = true if for_binary else confine = Puppet::Confine.test(:variable).new(values) confine.name = test @confines << confine end @confines[-1].label = self.label end end attr_reader :label def initialize(label) @label = label @confines = [] end # Return a hash of the whole confine set, used for the Provider # reference. def summary confines = Hash.new { |hash, key| hash[key] = [] } @confines.each { |confine| confines[confine.class] << confine } result = {} confines.each do |klass, list| value = klass.summarize(list) next if (value.respond_to?(:length) and value.length == 0) or (value == 0) result[klass.name] = value end result end def valid? ! @confines.detect { |c| ! c.valid? } end end puppet-5.5.10/lib/puppet/confiner.rb0000644005276200011600000000332213417161721017225 0ustar jenkinsjenkinsrequire 'puppet/confine_collection' # The Confiner module contains methods for managing a Provider's confinement (suitability under given # conditions). The intent is to include this module in an object where confinement management is wanted. # It lazily adds an instance variable `@confine_collection` to the object where it is included. # module Puppet::Confiner # Confines a provider to be suitable only under the given conditions. # The hash describes a confine using mapping from symbols to values or predicate code. # # * _fact_name_ => value of fact (or array of facts) # * `:exists` => the path to an existing file # * `:true` => a predicate code block returning true # * `:false` => a predicate code block returning false # * `:feature` => name of system feature that must be present # * `:any` => an array of expressions that will be ORed together # # @example # confine :operatingsystem => [:redhat, :fedora] # confine :true { ... } # # @param hash [Hash<{Symbol => Object}>] hash of confines # @return [void] # @api public # def confine(hash) confine_collection.confine(hash) end # @return [Puppet::ConfineCollection] the collection of confines # @api private # def confine_collection @confine_collection ||= Puppet::ConfineCollection.new(self.to_s) end # Checks whether this implementation is suitable for the current platform (or returns a summary # of all confines if short == false). # @return [Boolean. Hash] Returns whether the confines are all valid (if short == true), or a hash of all confines # if short == false. # @api public # def suitable?(short = true) return(short ? confine_collection.valid? : confine_collection.summary) end end puppet-5.5.10/lib/puppet/context.rb0000644005276200011600000000617613417161721017120 0ustar jenkinsjenkins# Puppet::Context is a system for tracking services and contextual information # that puppet needs to be able to run. Values are "bound" in a context when it is created # and cannot be changed; however a child context can be created, using # {#override}, that provides a different value. # # When binding a {Proc}, the proc is called when the value is looked up, and the result # is memoized for subsequent lookups. This provides a lazy mechanism that can be used to # delay expensive production of values until they are needed. # # @api private class Puppet::Context require 'puppet/context/trusted_information' class UndefinedBindingError < Puppet::Error; end class StackUnderflow < Puppet::Error; end class UnknownRollbackMarkError < Puppet::Error; end class DuplicateRollbackMarkError < Puppet::Error; end # @api private def initialize(initial_bindings) @table = initial_bindings @ignores = [] @description = "root" @id = 0 @rollbacks = {} @stack = [[0, nil, nil]] end # @api private def push(overrides, description = "") @id += 1 @stack.push([@id, @table, @description]) @table = @table.merge(overrides || {}) @description = description end # @api private def pop if @stack[-1][0] == 0 raise(StackUnderflow, _("Attempted to pop, but already at root of the context stack.")) else (_, @table, @description) = @stack.pop end end # @api private def lookup(name, &block) if @table.include?(name) && !@ignores.include?(name) value = @table[name] value.is_a?(Proc) ? (@table[name] = value.call) : value elsif block block.call else raise UndefinedBindingError, _("Unable to lookup '%{name}'") % { name: name } end end # @api private def override(bindings, description = "", &block) mark_point = "override over #{@stack[-1][0]}" mark(mark_point) push(bindings, description) yield ensure rollback(mark_point) end # @api private def ignore(name) @ignores << name end # @api private def restore(name) if @ignores.include?(name) @ignores.delete(name) else raise UndefinedBindingError, _("no '%{name}' in ignores %{ignores} at top of %{stack}") % { name: name, ignores: @ignores.inspect, stack: @stack.inspect } end end # Mark a place on the context stack to later return to with {rollback}. # # @param name [Object] The identifier for the mark # # @api private def mark(name) if @rollbacks[name].nil? @rollbacks[name] = @stack[-1][0] else raise DuplicateRollbackMarkError, _("Mark for '%{name}' already exists") % { name: name } end end # Roll back to a mark set by {mark}. # # Rollbacks can only reach a mark accessible via {pop}. If the mark is not on # the current context stack the behavior of rollback is undefined. # # @param name [Object] The identifier for the mark # # @api private def rollback(name) if @rollbacks[name].nil? raise UnknownRollbackMarkError, _("Unknown mark '%{name}'") % { name: name } end while @stack[-1][0] != @rollbacks[name] pop end @rollbacks.delete(name) end end puppet-5.5.10/lib/puppet/context/0000755005276200011600000000000013417162176016566 5ustar jenkinsjenkinspuppet-5.5.10/lib/puppet/context/trusted_information.rb0000644005276200011600000000402313417161721023204 0ustar jenkinsjenkins# @api private class Puppet::Context::TrustedInformation # one of 'remote', 'local', or false, where 'remote' is authenticated via cert, # 'local' is trusted by virtue of running on the same machine (not a remote # request), and false is an unauthenticated remote request. # # @return [String, Boolean] attr_reader :authenticated # The validated certificate name used for the request # # @return [String] attr_reader :certname # Extra information that comes from the trusted certificate's extensions. # # @return [Hash{Object => Object}] attr_reader :extensions # The domain name derived from the validated certificate name # # @return [String] attr_reader :domain # The hostname derived from the validated certificate name # # @return [String] attr_reader :hostname def initialize(authenticated, certname, extensions) @authenticated = authenticated.freeze @certname = certname.freeze @extensions = extensions.freeze if @certname hostname, domain = @certname.split('.', 2) else hostname = nil domain = nil end @hostname = hostname.freeze @domain = domain.freeze end def self.remote(authenticated, node_name, certificate) if authenticated extensions = {} if certificate.nil? Puppet.info(_('TrustedInformation expected a certificate, but none was given.')) else extensions = Hash[certificate.custom_extensions.collect do |ext| [ext['oid'].freeze, ext['value'].freeze] end] end new('remote', node_name, extensions) else new(false, nil, {}) end end def self.local(node) # Always trust local data by picking up the available parameters. client_cert = node ? node.parameters['clientcert'] : nil new('local', client_cert, {}) end def to_h { 'authenticated'.freeze => authenticated, 'certname'.freeze => certname, 'extensions'.freeze => extensions, 'hostname'.freeze => hostname, 'domain'.freeze => domain, }.freeze end end puppet-5.5.10/lib/puppet/data_binding.rb0000644005276200011600000000067413417161721020034 0ustar jenkinsjenkinsrequire 'puppet/indirector' # A class for managing data lookups class Puppet::DataBinding # Set up indirection, so that data can be looked for in the compiler extend Puppet::Indirector indirects(:data_binding, :terminus_setting => :data_binding_terminus, :doc => "Where to find external data bindings.") class LookupError < Puppet::PreformattedError; end class RecursiveLookupError < Puppet::DataBinding::LookupError; end end puppet-5.5.10/lib/puppet/datatypes/0000755005276200011600000000000013417162176017100 5ustar jenkinsjenkinspuppet-5.5.10/lib/puppet/datatypes/error.rb0000644005276200011600000000133413417161721020552 0ustar jenkinsjenkinsPuppet::DataTypes.create_type('Error') do interface <<-PUPPET type_parameters => { kind => Optional[Variant[String,Regexp,Type[Enum],Type[Pattern],Type[NotUndef],Type[Undef]]], issue_code => Optional[Variant[String,Regexp,Type[Enum],Type[Pattern],Type[NotUndef],Type[Undef]]] }, attributes => { msg => String[1], kind => { type => Optional[String[1]], value => undef }, details => { type => Optional[Hash[String[1],Data]], value => undef }, issue_code => { type => Optional[String[1]], value => undef }, }, functions => { message => Callable[[], String[1]] } PUPPET require 'puppet/datatypes/impl/error' implementation_class Puppet::DataTypes::Error end puppet-5.5.10/lib/puppet/datatypes/impl/0000755005276200011600000000000013417162176020041 5ustar jenkinsjenkinspuppet-5.5.10/lib/puppet/datatypes/impl/error.rb0000644005276200011600000000165613417161721021522 0ustar jenkinsjenkinsclass Puppet::DataTypes::Error attr_reader :msg, :kind, :issue_code, :details alias message msg def self.from_asserted_hash(hash) new(hash['msg'], hash['kind'], hash['details'], hash['issue_code']) end def _pcore_init_hash result = { 'msg' => @msg } result['kind'] = @kind unless @kind.nil? result['details'] = @details unless @details.nil? result['issue_code'] = @issue_code unless @issue_code.nil? result end def initialize(msg, kind = nil, details = nil, issue_code = nil) @msg = msg @kind = kind @details = details @issue_code = issue_code end def eql?(o) self.class.equal?(o.class) && @msg == o.msg && @kind == o.kind && @issue_code == o.issue_code && @details == o.details end alias == eql? def hash @msg.hash ^ @kind.hash ^ @issue_code.hash end def to_s Puppet::Pops::Types::StringConverter.singleton.convert(self) end end puppet-5.5.10/lib/puppet/external/0000755005276200011600000000000013417162176016724 5ustar jenkinsjenkinspuppet-5.5.10/lib/puppet/external/pson/0000755005276200011600000000000013417162176017703 5ustar jenkinsjenkinspuppet-5.5.10/lib/puppet/external/pson/common.rb0000644005276200011600000003111113417161721021510 0ustar jenkinsjenkinsrequire 'puppet/external/pson/version' # PSON is a vendored version of pure_json v1.1.9 plus puppet patches. It # is deprecated and should not be used for future work. Use JSON instead. # # @deprecated module PSON class << self # If _object_ is string-like parse the string and return the parsed result # as a Ruby data structure. Otherwise generate a PSON text from the Ruby # data structure object and return it. # # The _opts_ argument is passed through to generate/parse respectively, see # generate and parse for their documentation. def [](object, opts = {}) if object.respond_to? :to_str PSON.parse(object.to_str, opts => {}) else PSON.generate(object, opts => {}) end end # Returns the PSON parser class, that is used by PSON. This might be either # PSON::Ext::Parser or PSON::Pure::Parser. attr_reader :parser # Set the PSON parser class _parser_ to be used by PSON. def parser=(parser) # :nodoc: @parser = parser remove_const :Parser if const_defined? :Parser const_set :Parser, parser end # Return the constant located at _path_. # Anything may be registered as a path by calling register_path, above. # Otherwise, the format of _path_ has to be either ::A::B::C or A::B::C. # In either of these cases A has to be defined in Object (e.g. the path # must be an absolute namespace path. If the constant doesn't exist at # the given path, an ArgumentError is raised. def deep_const_get(path) # :nodoc: path = path.to_s path.split(/::/).inject(Object) do |p, c| case when c.empty? then p when p.const_defined?(c) then p.const_get(c) else raise ArgumentError, _("can't find const for unregistered document type %{path}") % { path: path} end end end # Set the module _generator_ to be used by PSON. def generator=(generator) # :nodoc: @generator = generator generator_methods = generator::GeneratorMethods for const in generator_methods.constants klass = deep_const_get(const) modul = generator_methods.const_get(const) klass.class_eval do instance_methods(false).each do |m| m.to_s == 'to_pson' and remove_method m end include modul end end self.state = generator::State const_set :State, self.state end # Returns the PSON generator modul, that is used by PSON. This might be # either PSON::Ext::Generator or PSON::Pure::Generator. attr_reader :generator # Returns the PSON generator state class, that is used by PSON. This might # be either PSON::Ext::Generator::State or PSON::Pure::Generator::State. attr_accessor :state # This is create identifier, that is used to decide, if the _pson_create_ # hook of a class should be called. It defaults to 'document_type'. attr_accessor :create_id end self.create_id = 'document_type' NaN = (-1.0) ** 0.5 Infinity = 1.0/0 MinusInfinity = -Infinity # The base exception for PSON errors. class PSONError < StandardError; end # This exception is raised, if a parser error occurs. class ParserError < PSONError; end # This exception is raised, if the nesting of parsed datastructures is too # deep. class NestingError < ParserError; end # This exception is raised, if a generator or unparser error occurs. class GeneratorError < PSONError; end # For backwards compatibility UnparserError = GeneratorError # If a circular data structure is encountered while unparsing # this exception is raised. class CircularDatastructure < GeneratorError; end # This exception is raised, if the required unicode support is missing on the # system. Usually this means, that the iconv library is not installed. class MissingUnicodeSupport < PSONError; end module_function # Parse the PSON string _source_ into a Ruby data structure and return it. # # _opts_ can have the following # keys: # * *max_nesting*: The maximum depth of nesting allowed in the parsed data # structures. Disable depth checking with :max_nesting => false, it defaults # to 19. # * *allow_nan*: If set to true, allow NaN, Infinity and -Infinity in # defiance of RFC 4627 to be parsed by the Parser. This option defaults # to false. def parse(source, opts = {}) PSON.parser.new(source, opts).parse end # Parse the PSON string _source_ into a Ruby data structure and return it. # The bang version of the parse method, defaults to the more dangerous values # for the _opts_ hash, so be sure only to parse trusted _source_ strings. # # _opts_ can have the following keys: # * *max_nesting*: The maximum depth of nesting allowed in the parsed data # structures. Enable depth checking with :max_nesting => anInteger. The parse! # methods defaults to not doing max depth checking: This can be dangerous, # if someone wants to fill up your stack. # * *allow_nan*: If set to true, allow NaN, Infinity, and -Infinity in # defiance of RFC 4627 to be parsed by the Parser. This option defaults # to true. def parse!(source, opts = {}) opts = { :max_nesting => false, :allow_nan => true }.update(opts) PSON.parser.new(source, opts).parse end # Unparse the Ruby data structure _obj_ into a single line PSON string and # return it. _state_ is # * a PSON::State object, # * or a Hash like object (responding to to_hash), # * an object convertible into a hash by a to_h method, # that is used as or to configure a State object. # # It defaults to a state object, that creates the shortest possible PSON text # in one line, checks for circular data structures and doesn't allow NaN, # Infinity, and -Infinity. # # A _state_ hash can have the following keys: # * *indent*: a string used to indent levels (default: ''), # * *space*: a string that is put after, a : or , delimiter (default: ''), # * *space_before*: a string that is put before a : pair delimiter (default: ''), # * *object_nl*: a string that is put at the end of a PSON object (default: ''), # * *array_nl*: a string that is put at the end of a PSON array (default: ''), # * *check_circular*: true if checking for circular data structures # should be done (the default), false otherwise. # * *allow_nan*: true if NaN, Infinity, and -Infinity should be # generated, otherwise an exception is thrown, if these values are # encountered. This options defaults to false. # * *max_nesting*: The maximum depth of nesting allowed in the data # structures from which PSON is to be generated. Disable depth checking # with :max_nesting => false, it defaults to 19. # # See also the fast_generate for the fastest creation method with the least # amount of sanity checks, and the pretty_generate method for some # defaults for a pretty output. def generate(obj, state = nil) if state state = State.from_state(state) else state = State.new end obj.to_pson(state) end # :stopdoc: # I want to deprecate these later, so I'll first be silent about them, and # later delete them. alias unparse generate module_function :unparse # :startdoc: # Unparse the Ruby data structure _obj_ into a single line PSON string and # return it. This method disables the checks for circles in Ruby objects, and # also generates NaN, Infinity, and, -Infinity float values. # # *WARNING*: Be careful not to pass any Ruby data structures with circles as # _obj_ argument, because this will cause PSON to go into an infinite loop. def fast_generate(obj) obj.to_pson(nil) end # :stopdoc: # I want to deprecate these later, so I'll first be silent about them, and later delete them. alias fast_unparse fast_generate module_function :fast_unparse # :startdoc: # Unparse the Ruby data structure _obj_ into a PSON string and return it. The # returned string is a prettier form of the string returned by #unparse. # # The _opts_ argument can be used to configure the generator, see the # generate method for a more detailed explanation. def pretty_generate(obj, opts = nil) state = PSON.state.new( :indent => ' ', :space => ' ', :object_nl => "\n", :array_nl => "\n", :check_circular => true ) if opts if opts.respond_to? :to_hash opts = opts.to_hash elsif opts.respond_to? :to_h opts = opts.to_h else raise TypeError, "can't convert #{opts.class} into Hash" end state.configure(opts) end obj.to_pson(state) end # :stopdoc: # I want to deprecate these later, so I'll first be silent about them, and later delete them. alias pretty_unparse pretty_generate module_function :pretty_unparse # :startdoc: # Load a ruby data structure from a PSON _source_ and return it. A source can # either be a string-like object, an IO like object, or an object responding # to the read method. If _proc_ was given, it will be called with any nested # Ruby object as an argument recursively in depth first order. # # This method is part of the implementation of the load/dump interface of # Marshal and YAML. def load(source, proc = nil) if source.respond_to? :to_str source = source.to_str elsif source.respond_to? :to_io source = source.to_io.read else source = source.read end result = parse(source, :max_nesting => false, :allow_nan => true) recurse_proc(result, &proc) if proc result end def recurse_proc(result, &proc) case result when Array result.each { |x| recurse_proc x, &proc } proc.call result when Hash result.each { |x, y| recurse_proc x, &proc; recurse_proc y, &proc } proc.call result else proc.call result end end private :recurse_proc module_function :recurse_proc alias restore load module_function :restore # Dumps _obj_ as a PSON string, i.e. calls generate on the object and returns # the result. # # If anIO (an IO like object or an object that responds to the write method) # was given, the resulting PSON is written to it. # # If the number of nested arrays or objects exceeds _limit_ an ArgumentError # exception is raised. This argument is similar (but not exactly the # same!) to the _limit_ argument in Marshal.dump. # # This method is part of the implementation of the load/dump interface of # Marshal and YAML. def dump(obj, anIO = nil, limit = nil) if anIO and limit.nil? anIO = anIO.to_io if anIO.respond_to?(:to_io) unless anIO.respond_to?(:write) limit = anIO anIO = nil end end limit ||= 0 result = generate(obj, :allow_nan => true, :max_nesting => limit) if anIO anIO.write result anIO else result end rescue PSON::NestingError raise ArgumentError, _("exceed depth limit"), $!.backtrace end # Provide a smarter wrapper for changing string encoding that works with # both Ruby 1.8 (iconv) and 1.9 (String#encode). Thankfully they seem to # have compatible input syntax, at least for the encodings we touch. if String.method_defined?("encode") def encode(to, from, string) string.encode(to, from) end else require 'iconv' def encode(to, from, string) Iconv.conv(to, from, string) end end end module ::Kernel private # Outputs _objs_ to STDOUT as PSON strings in the shortest form, that is in # one line. def j(*objs) objs.each do |obj| puts PSON::generate(obj, :allow_nan => true, :max_nesting => false) end nil end # Outputs _objs_ to STDOUT as PSON strings in a pretty format, with # indentation and over many lines. def jj(*objs) objs.each do |obj| puts PSON::pretty_generate(obj, :allow_nan => true, :max_nesting => false) end nil end # If _object_ is string-like parse the string and return the parsed result as # a Ruby data structure. Otherwise generate a PSON text from the Ruby data # structure object and return it. # # The _opts_ argument is passed through to generate/parse respectively, see # generate and parse for their documentation. def PSON(object, opts = {}) if object.respond_to? :to_str PSON.parse(object.to_str, opts) else PSON.generate(object, opts) end end end class ::Class # Returns true, if this class can be used to create an instance # from a serialised PSON string. The class has to implement a class # method _pson_create_ that expects a hash as first parameter, which includes # the required data. def pson_creatable? respond_to?(:pson_create) end end puppet-5.5.10/lib/puppet/external/pson/pure.rb0000644005276200011600000000061113417161721021174 0ustar jenkinsjenkinsrequire 'puppet/external/pson/common' require 'puppet/external/pson/pure/parser' require 'puppet/external/pson/pure/generator' module PSON # This module holds all the modules/classes that implement PSON's # functionality in pure ruby. module Pure $DEBUG and warn "Using pure library for PSON." PSON.parser = Parser PSON.generator = Generator end PSON_LOADED = true end puppet-5.5.10/lib/puppet/external/pson/pure/0000755005276200011600000000000013417162176020656 5ustar jenkinsjenkinspuppet-5.5.10/lib/puppet/external/pson/pure/generator.rb0000644005276200011600000003202613417161721023167 0ustar jenkinsjenkinsmodule PSON MAP = { "\x0" => '\u0000', "\x1" => '\u0001', "\x2" => '\u0002', "\x3" => '\u0003', "\x4" => '\u0004', "\x5" => '\u0005', "\x6" => '\u0006', "\x7" => '\u0007', "\b" => '\b', "\t" => '\t', "\n" => '\n', "\xb" => '\u000b', "\f" => '\f', "\r" => '\r', "\xe" => '\u000e', "\xf" => '\u000f', "\x10" => '\u0010', "\x11" => '\u0011', "\x12" => '\u0012', "\x13" => '\u0013', "\x14" => '\u0014', "\x15" => '\u0015', "\x16" => '\u0016', "\x17" => '\u0017', "\x18" => '\u0018', "\x19" => '\u0019', "\x1a" => '\u001a', "\x1b" => '\u001b', "\x1c" => '\u001c', "\x1d" => '\u001d', "\x1e" => '\u001e', "\x1f" => '\u001f', '"' => '\"', '\\' => '\\\\', } # :nodoc: # Convert a UTF8 encoded Ruby string _string_ to a PSON string, encoded with # UTF16 big endian characters as \u????, and return it. if String.method_defined?(:force_encoding) def utf8_to_pson(string) # :nodoc: string = string.dup string << '' # XXX workaround: avoid buffer sharing string.force_encoding(Encoding::ASCII_8BIT) string.gsub!(/["\\\x0-\x1f]/) { MAP[$MATCH] } string rescue => e raise GeneratorError, "Caught #{e.class}: #{e}", e.backtrace end else def utf8_to_pson(string) # :nodoc: string.gsub(/["\\\x0-\x1f]/n) { MAP[$MATCH] } end end module_function :utf8_to_pson module Pure module Generator # This class is used to create State instances, that are use to hold data # while generating a PSON text from a Ruby data structure. class State # Creates a State object from _opts_, which ought to be Hash to create # a new State instance configured by _opts_, something else to create # an unconfigured instance. If _opts_ is a State object, it is just # returned. def self.from_state(opts) case opts when self opts when Hash new(opts) else new end end # Instantiates a new State object, configured by _opts_. # # _opts_ can have the following keys: # # * *indent*: a string used to indent levels (default: ''), # * *space*: a string that is put after, a : or , delimiter (default: ''), # * *space_before*: a string that is put before a : pair delimiter (default: ''), # * *object_nl*: a string that is put at the end of a PSON object (default: ''), # * *array_nl*: a string that is put at the end of a PSON array (default: ''), # * *check_circular*: true if checking for circular data structures # should be done (the default), false otherwise. # * *check_circular*: true if checking for circular data structures # should be done, false (the default) otherwise. # * *allow_nan*: true if NaN, Infinity, and -Infinity should be # generated, otherwise an exception is thrown, if these values are # encountered. This options defaults to false. def initialize(opts = {}) @seen = {} @indent = '' @space = '' @space_before = '' @object_nl = '' @array_nl = '' @check_circular = true @allow_nan = false configure opts end # This string is used to indent levels in the PSON text. attr_accessor :indent # This string is used to insert a space between the tokens in a PSON # string. attr_accessor :space # This string is used to insert a space before the ':' in PSON objects. attr_accessor :space_before # This string is put at the end of a line that holds a PSON object (or # Hash). attr_accessor :object_nl # This string is put at the end of a line that holds a PSON array. attr_accessor :array_nl # This integer returns the maximum level of data structure nesting in # the generated PSON, max_nesting = 0 if no maximum is checked. attr_accessor :max_nesting def check_max_nesting(depth) # :nodoc: return if @max_nesting.zero? current_nesting = depth + 1 current_nesting > @max_nesting and raise NestingError, "nesting of #{current_nesting} is too deep" end # Returns true, if circular data structures should be checked, # otherwise returns false. def check_circular? @check_circular end # Returns true if NaN, Infinity, and -Infinity should be considered as # valid PSON and output. def allow_nan? @allow_nan end # Returns _true_, if _object_ was already seen during this generating # run. def seen?(object) @seen.key?(object.__id__) end # Remember _object_, to find out if it was already encountered (if a # cyclic data structure is if a cyclic data structure is rendered). def remember(object) @seen[object.__id__] = true end # Forget _object_ for this generating run. def forget(object) @seen.delete object.__id__ end # Configure this State instance with the Hash _opts_, and return # itself. def configure(opts) @indent = opts[:indent] if opts.key?(:indent) @space = opts[:space] if opts.key?(:space) @space_before = opts[:space_before] if opts.key?(:space_before) @object_nl = opts[:object_nl] if opts.key?(:object_nl) @array_nl = opts[:array_nl] if opts.key?(:array_nl) @check_circular = !!opts[:check_circular] if opts.key?(:check_circular) @allow_nan = !!opts[:allow_nan] if opts.key?(:allow_nan) if !opts.key?(:max_nesting) # defaults to 19 @max_nesting = 19 elsif opts[:max_nesting] @max_nesting = opts[:max_nesting] else @max_nesting = 0 end self end # Returns the configuration instance variables as a hash, that can be # passed to the configure method. def to_h result = {} for iv in %w{indent space space_before object_nl array_nl check_circular allow_nan max_nesting} result[iv.intern] = instance_variable_get("@#{iv}") end result end end module GeneratorMethods module Object # Converts this object to a string (calling #to_s), converts # it to a PSON string, and returns the result. This is a fallback, if no # special method #to_pson was defined for some object. def to_pson(*) to_s.to_pson end end module Hash # Returns a PSON string containing a PSON object, that is unparsed from # this Hash instance. # _state_ is a PSON::State object, that can also be used to configure the # produced PSON string output further. # _depth_ is used to find out nesting depth, to indent accordingly. def to_pson(state = nil, depth = 0, *) if state state = PSON.state.from_state(state) state.check_max_nesting(depth) pson_check_circular(state) { pson_transform(state, depth) } else pson_transform(state, depth) end end private def pson_check_circular(state) if state and state.check_circular? state.seen?(self) and raise PSON::CircularDatastructure, "circular data structures not supported!" state.remember self end yield ensure state and state.forget self end def pson_shift(state, depth) state and not state.object_nl.empty? or return '' state.indent * depth end def pson_transform(state, depth) delim = ',' if state delim << state.object_nl result = '{' result << state.object_nl result << map { |key,value| s = pson_shift(state, depth + 1) s << key.to_s.to_pson(state, depth + 1) s << state.space_before s << ':' s << state.space s << value.to_pson(state, depth + 1) }.join(delim) result << state.object_nl result << pson_shift(state, depth) result << '}' else result = '{' result << map { |key,value| key.to_s.to_pson << ':' << value.to_pson }.join(delim) result << '}' end result end end module Array # Returns a PSON string containing a PSON array, that is unparsed from # this Array instance. # _state_ is a PSON::State object, that can also be used to configure the # produced PSON string output further. # _depth_ is used to find out nesting depth, to indent accordingly. def to_pson(state = nil, depth = 0, *) if state state = PSON.state.from_state(state) state.check_max_nesting(depth) pson_check_circular(state) { pson_transform(state, depth) } else pson_transform(state, depth) end end private def pson_check_circular(state) if state and state.check_circular? state.seen?(self) and raise PSON::CircularDatastructure, "circular data structures not supported!" state.remember self end yield ensure state and state.forget self end def pson_shift(state, depth) state and not state.array_nl.empty? or return '' state.indent * depth end def pson_transform(state, depth) delim = ',' if state delim << state.array_nl result = '[' result << state.array_nl result << map { |value| pson_shift(state, depth + 1) << value.to_pson(state, depth + 1) }.join(delim) result << state.array_nl result << pson_shift(state, depth) result << ']' else '[' << map { |value| value.to_pson }.join(delim) << ']' end end end module Integer # Returns a PSON string representation for this Integer number. def to_pson(*) to_s end end module Float # Returns a PSON string representation for this Float number. def to_pson(state = nil, *) if infinite? || nan? if !state || state.allow_nan? to_s else raise GeneratorError, "#{self} not allowed in PSON" end else to_s end end end module String # This string should be encoded with UTF-8 A call to this method # returns a PSON string encoded with UTF16 big endian characters as # \u????. def to_pson(*) '"' << PSON.utf8_to_pson(self) << '"' end # Module that holds the extending methods if, the String module is # included. module Extend # Raw Strings are PSON Objects (the raw bytes are stored in an array for the # key "raw"). The Ruby String can be created by this module method. def pson_create(o) o['raw'].pack('C*') end end # Extends _modul_ with the String::Extend module. def self.included(modul) modul.extend Extend end # This method creates a raw object hash, that can be nested into # other data structures and will be unparsed as a raw string. This # method should be used, if you want to convert raw strings to PSON # instead of UTF-8 strings, e.g. binary data. def to_pson_raw_object # create_id will be ignored during deserialization { PSON.create_id => self.class.name, 'raw' => self.unpack('C*'), } end # This method creates a PSON text from the result of # a call to to_pson_raw_object of this String. def to_pson_raw(*args) to_pson_raw_object.to_pson(*args) end end module TrueClass # Returns a PSON string for true: 'true'. def to_pson(*) 'true' end end module FalseClass # Returns a PSON string for false: 'false'. def to_pson(*) 'false' end end module NilClass # Returns a PSON string for nil: 'null'. def to_pson(*) 'null' end end end end end end puppet-5.5.10/lib/puppet/external/pson/pure/parser.rb0000644005276200011600000002374113417161721022501 0ustar jenkinsjenkinsrequire 'strscan' module PSON module Pure # This class implements the PSON parser that is used to parse a PSON string # into a Ruby data structure. class Parser < StringScanner STRING = /" ((?:[^\x0-\x1f"\\] | # escaped special characters: \\["\\\/bfnrt] | \\u[0-9a-fA-F]{4} | # match all but escaped special characters: \\[\x20-\x21\x23-\x2e\x30-\x5b\x5d-\x61\x63-\x65\x67-\x6d\x6f-\x71\x73\x75-\xff])*) "/nx INTEGER = /(-?0|-?[1-9]\d*)/ FLOAT = /(-? (?:0|[1-9]\d*) (?: \.\d+(?i:e[+-]?\d+) | \.\d+ | (?i:e[+-]?\d+) ) )/x NAN = /NaN/ INFINITY = /Infinity/ MINUS_INFINITY = /-Infinity/ OBJECT_OPEN = /\{/ OBJECT_CLOSE = /\}/ ARRAY_OPEN = /\[/ ARRAY_CLOSE = /\]/ PAIR_DELIMITER = /:/ COLLECTION_DELIMITER = /,/ TRUE = /true/ FALSE = /false/ NULL = /null/ IGNORE = %r( (?: //[^\n\r]*[\n\r]| # line comments /\* # c-style comments (?: [^*/]| # normal chars /[^*]| # slashes that do not start a nested comment \*[^/]| # asterisks that do not end this comment /(?=\*/) # single slash before this comment's end )* \*/ # the End of this comment |[ \t\r\n]+ # whitespaces: space, horizontal tab, lf, cr )+ )mx UNPARSED = Object.new # Creates a new PSON::Pure::Parser instance for the string _source_. # # It will be configured by the _opts_ hash. _opts_ can have the following # keys: # * *max_nesting*: The maximum depth of nesting allowed in the parsed data # structures. Disable depth checking with :max_nesting => false|nil|0, # it defaults to 19. # * *allow_nan*: If set to true, allow NaN, Infinity and -Infinity in # defiance of RFC 4627 to be parsed by the Parser. This option defaults # to false. # * *object_class*: Defaults to Hash # * *array_class*: Defaults to Array def initialize(source, opts = {}) source = convert_encoding source super source if !opts.key?(:max_nesting) # defaults to 19 @max_nesting = 19 elsif opts[:max_nesting] @max_nesting = opts[:max_nesting] else @max_nesting = 0 end @allow_nan = !!opts[:allow_nan] @object_class = opts[:object_class] || Hash @array_class = opts[:array_class] || Array end alias source string # Parses the current PSON string _source_ and returns the complete data # structure as a result. def parse reset obj = nil until eos? case when scan(OBJECT_OPEN) obj and raise ParserError, "source '#{peek(20)}' not in PSON!" @current_nesting = 1 obj = parse_object when scan(ARRAY_OPEN) obj and raise ParserError, "source '#{peek(20)}' not in PSON!" @current_nesting = 1 obj = parse_array when skip(IGNORE) ; else raise ParserError, "source '#{peek(20)}' not in PSON!" end end obj or raise ParserError, "source did not contain any PSON!" obj end private def convert_encoding(source) if source.respond_to?(:to_str) source = source.to_str else raise TypeError, "#{source.inspect} is not like a string" end if supports_encodings?(source) if source.encoding == ::Encoding::ASCII_8BIT b = source[0, 4].bytes.to_a source = case when b.size >= 4 && b[0] == 0 && b[1] == 0 && b[2] == 0 source.dup.force_encoding(::Encoding::UTF_32BE).encode!(::Encoding::UTF_8) when b.size >= 4 && b[0] == 0 && b[2] == 0 source.dup.force_encoding(::Encoding::UTF_16BE).encode!(::Encoding::UTF_8) when b.size >= 4 && b[1] == 0 && b[2] == 0 && b[3] == 0 source.dup.force_encoding(::Encoding::UTF_32LE).encode!(::Encoding::UTF_8) when b.size >= 4 && b[1] == 0 && b[3] == 0 source.dup.force_encoding(::Encoding::UTF_16LE).encode!(::Encoding::UTF_8) else source.dup end else source = source.encode(::Encoding::UTF_8) end source.force_encoding(::Encoding::ASCII_8BIT) else b = source source = case when b.size >= 4 && b[0] == 0 && b[1] == 0 && b[2] == 0 PSON.encode('utf-8', 'utf-32be', b) when b.size >= 4 && b[0] == 0 && b[2] == 0 PSON.encode('utf-8', 'utf-16be', b) when b.size >= 4 && b[1] == 0 && b[2] == 0 && b[3] == 0 PSON.encode('utf-8', 'utf-32le', b) when b.size >= 4 && b[1] == 0 && b[3] == 0 PSON.encode('utf-8', 'utf-16le', b) else b end end source end def supports_encodings?(string) # Some modules, such as REXML on 1.8.7 (see #22804) can actually create # a top-level Encoding constant when they are misused. Therefore # checking for just that constant is not enough, so we'll be a bit more # robust about if we can actually support encoding transformations. string.respond_to?(:encoding) && defined?(::Encoding) end # Unescape characters in strings. UNESCAPE_MAP = Hash.new { |h, k| h[k] = k.chr } UNESCAPE_MAP.update( { ?" => '"', ?\\ => '\\', ?/ => '/', ?b => "\b", ?f => "\f", ?n => "\n", ?r => "\r", ?t => "\t", ?u => nil, }) def parse_string if scan(STRING) return '' if self[1].empty? string = self[1].gsub(%r{(?:\\[\\bfnrt"/]|(?:\\u(?:[A-Fa-f\d]{4}))+|\\[\x20-\xff])}n) do |c| if u = UNESCAPE_MAP[$MATCH[1]] u else # \uXXXX bytes = '' i = 0 while c[6 * i] == ?\\ && c[6 * i + 1] == ?u bytes << c[6 * i + 2, 2].to_i(16) << c[6 * i + 4, 2].to_i(16) i += 1 end PSON.encode('utf-8', 'utf-16be', bytes) end end string.force_encoding(Encoding::UTF_8) if string.respond_to?(:force_encoding) string else UNPARSED end rescue => e raise GeneratorError, "Caught #{e.class}: #{e}", e.backtrace end def parse_value case when scan(FLOAT) Float(self[1]) when scan(INTEGER) Integer(self[1]) when scan(TRUE) true when scan(FALSE) false when scan(NULL) nil when (string = parse_string) != UNPARSED string when scan(ARRAY_OPEN) @current_nesting += 1 ary = parse_array @current_nesting -= 1 ary when scan(OBJECT_OPEN) @current_nesting += 1 obj = parse_object @current_nesting -= 1 obj when @allow_nan && scan(NAN) NaN when @allow_nan && scan(INFINITY) Infinity when @allow_nan && scan(MINUS_INFINITY) MinusInfinity else UNPARSED end end def parse_array raise NestingError, "nesting of #@current_nesting is too deep" if @max_nesting.nonzero? && @current_nesting > @max_nesting result = @array_class.new delim = false until eos? case when (value = parse_value) != UNPARSED delim = false result << value skip(IGNORE) if scan(COLLECTION_DELIMITER) delim = true elsif match?(ARRAY_CLOSE) ; else raise ParserError, "expected ',' or ']' in array at '#{peek(20)}'!" end when scan(ARRAY_CLOSE) raise ParserError, "expected next element in array at '#{peek(20)}'!" if delim break when skip(IGNORE) ; else raise ParserError, "unexpected token in array at '#{peek(20)}'!" end end result end def parse_object raise NestingError, "nesting of #@current_nesting is too deep" if @max_nesting.nonzero? && @current_nesting > @max_nesting result = @object_class.new delim = false until eos? case when (string = parse_string) != UNPARSED skip(IGNORE) raise ParserError, "expected ':' in object at '#{peek(20)}'!" unless scan(PAIR_DELIMITER) skip(IGNORE) unless (value = parse_value).equal? UNPARSED result[string] = value delim = false skip(IGNORE) if scan(COLLECTION_DELIMITER) delim = true elsif match?(OBJECT_CLOSE) ; else raise ParserError, "expected ',' or '}' in object at '#{peek(20)}'!" end else raise ParserError, "expected value in object at '#{peek(20)}'!" end when scan(OBJECT_CLOSE) raise ParserError, "expected next name, value pair in object at '#{peek(20)}'!" if delim break when skip(IGNORE) ; else raise ParserError, "unexpected token in object at '#{peek(20)}'!" end end result end end end end puppet-5.5.10/lib/puppet/external/pson/version.rb0000644005276200011600000000041713417161721021712 0ustar jenkinsjenkinsmodule PSON # PSON version VERSION = '1.1.9' VERSION_ARRAY = VERSION.split(/\./).map { |x| x.to_i } # :nodoc: VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc: VERSION_MINOR = VERSION_ARRAY[1] # :nodoc: VERSION_BUILD = VERSION_ARRAY[2] # :nodoc: end puppet-5.5.10/lib/puppet/external/dot.rb0000644005276200011600000002217513417161721020041 0ustar jenkinsjenkins# rdot.rb # # # This is a modified version of dot.rb from Dave Thomas's rdoc project. I [Horst Duchene] # renamed it to rdot.rb to avoid collision with an installed rdoc/dot. # # It also supports undirected edges. module DOT # These global vars are used to make nice graph source. $tab = ' ' $tab2 = $tab * 2 # if we don't like 4 spaces, we can change it any time def change_tab (t) $tab = t $tab2 = t * 2 end # options for node declaration NODE_OPTS = [ # attributes due to # http://www.graphviz.org/Documentation/dotguide.pdf # March, 26, 2005 'bottomlabel', # auxiliary label for nodes of shape M* 'color', # default: black; node shape color 'comment', # any string (format-dependent) 'distortion', # default: 0.0; node distortion for shape=polygon 'fillcolor', # default: lightgrey/black; node fill color 'fixedsize', # default: false; label text has no affect on node size 'fontcolor', # default: black; type face color 'fontname', # default: Times-Roman; font family 'fontsize', # default: 14; point size of label 'group', # name of node's group 'height', # default: .5; height in inches 'label', # default: node name; any string 'layer', # default: overlay range; all, id or id:id 'orientation', # default: 0.0; node rotation angle 'peripheries', # shape-dependent number of node boundaries 'regular', # default: false; force polygon to be regular 'shape', # default: ellipse; node shape; see Section 2.1 and Appendix E 'shapefile', # external EPSF or SVG custom shape file 'sides', # default: 4; number of sides for shape=polygon 'skew' , # default: 0.0; skewing of node for shape=polygon 'style', # graphics options, e.g. bold, dotted, filled; cf. Section 2.3 'toplabel', # auxiliary label for nodes of shape M* 'URL', # URL associated with node (format-dependent) 'width', # default: .75; width in inches 'z', # default: 0.0; z coordinate for VRML output # maintained for backward compatibility or rdot internal 'bgcolor', 'rank' ] # options for edge declaration EDGE_OPTS = [ 'arrowhead', # default: normal; style of arrowhead at head end 'arrowsize', # default: 1.0; scaling factor for arrowheads 'arrowtail', # default: normal; style of arrowhead at tail end 'color', # default: black; edge stroke color 'comment', # any string (format-dependent) 'constraint', # default: true use edge to affect node ranking 'decorate', # if set, draws a line connecting labels with their edges 'dir', # default: forward; forward, back, both, or none 'fontcolor', # default: black type face color 'fontname', # default: Times-Roman; font family 'fontsize', # default: 14; point size of label 'headlabel', # label placed near head of edge 'headport', # n,ne,e,se,s,sw,w,nw 'headURL', # URL attached to head label if output format is ismap 'label', # edge label 'labelangle', # default: -25.0; angle in degrees which head or tail label is rotated off edge 'labeldistance', # default: 1.0; scaling factor for distance of head or tail label from node 'labelfloat', # default: false; lessen constraints on edge label placement 'labelfontcolor', # default: black; type face color for head and tail labels 'labelfontname', # default: Times-Roman; font family for head and tail labels 'labelfontsize', # default: 14 point size for head and tail labels 'layer', # default: overlay range; all, id or id:id 'lhead', # name of cluster to use as head of edge 'ltail', # name of cluster to use as tail of edge 'minlen', # default: 1 minimum rank distance between head and tail 'samehead', # tag for head node; edge heads with the same tag are merged onto the same port 'sametail', # tag for tail node; edge tails with the same tag are merged onto the same port 'style', # graphics options, e.g. bold, dotted, filled; cf. Section 2.3 'taillabel', # label placed near tail of edge 'tailport', # n,ne,e,se,s,sw,w,nw 'tailURL', # URL attached to tail label if output format is ismap 'weight', # default: 1; integer cost of stretching an edge # maintained for backward compatibility or rdot internal 'id' ] # options for graph declaration GRAPH_OPTS = [ 'bgcolor', 'center', 'clusterrank', 'color', 'concentrate', 'fontcolor', 'fontname', 'fontsize', 'label', 'layerseq', 'margin', 'mclimit', 'nodesep', 'nslimit', 'ordering', 'orientation', 'page', 'rank', 'rankdir', 'ranksep', 'ratio', 'size' ] # a root class for any element in dot notation class DOTSimpleElement attr_accessor :name def initialize (params = {}) @label = params['name'] ? params['name'] : '' end def to_s @name end end # an element that has options ( node, edge, or graph ) class DOTElement < DOTSimpleElement # attr_reader :parent attr_accessor :name, :options def initialize (params = {}, option_list = []) super(params) @name = params['name'] ? params['name'] : nil @parent = params['parent'] ? params['parent'] : nil @options = {} option_list.each{ |i| @options[i] = params[i] if params[i] } @options['label'] ||= @name if @name != 'node' end def each_option @options.each{ |i| yield i } end def each_option_pair @options.each_pair{ |key, val| yield key, val } end #def parent=( thing ) # @parent.delete( self ) if defined?( @parent ) and @parent # @parent = thing #end end # This is used when we build nodes that have shape=record # ports don't have options :) class DOTPort < DOTSimpleElement attr_accessor :label def initialize (params = {}) super(params) @name = params['label'] ? params['label'] : '' end def to_s ( @name && @name != "" ? "<#{@name}>" : "" ) + "#{@label}" end end # node element class DOTNode < DOTElement def initialize (params = {}, option_list = NODE_OPTS) super(params, option_list) @ports = params['ports'] ? params['ports'] : [] end def each_port @ports.each { |i| yield i } end def << (thing) @ports << thing end def push (thing) @ports.push(thing) end def pop @ports.pop end def to_s (t = '') # This code is totally incomprehensible; it needs to be replaced! label = @options['shape'] != 'record' && @ports.length == 0 ? @options['label'] ? t + $tab + "label = \"#{@options['label']}\"\n" : '' : t + $tab + 'label = "' + " \\\n" + t + $tab2 + "#{@options['label']}| \\\n" + @ports.collect{ |i| t + $tab2 + i.to_s }.join( "| \\\n" ) + " \\\n" + t + $tab + '"' + "\n" t + "#{@name} [\n" + @options.to_a.collect{ |i| i[1] && i[0] != 'label' ? t + $tab + "#{i[0]} = #{i[1]}" : nil }.compact.join( ",\n" ) + ( label != '' ? ",\n" : "\n" ) + label + t + "]\n" end end # A subgraph element is the same to graph, but has another header in dot # notation. class DOTSubgraph < DOTElement def initialize (params = {}, option_list = GRAPH_OPTS) super(params, option_list) @nodes = params['nodes'] ? params['nodes'] : [] @dot_string = 'graph' end def each_node @nodes.each{ |i| yield i } end def << (thing) @nodes << thing end def push (thing) @nodes.push( thing ) end def pop @nodes.pop end def to_s (t = '') hdr = t + "#{@dot_string} #{@name} {\n" options = @options.to_a.collect{ |name, val| val && name != 'label' ? t + $tab + "#{name} = #{val}" : name ? t + $tab + "#{name} = \"#{val}\"" : nil }.compact.join( "\n" ) + "\n" nodes = @nodes.collect{ |i| i.to_s( t + $tab ) }.join( "\n" ) + "\n" hdr + options + nodes + t + "}\n" end end # This is a graph. class DOTDigraph < DOTSubgraph def initialize (params = {}, option_list = GRAPH_OPTS) super(params, option_list) @dot_string = 'digraph' end end # This is an edge. class DOTEdge < DOTElement attr_accessor :from, :to def initialize (params = {}, option_list = EDGE_OPTS) super(params, option_list) @from = params['from'] ? params['from'] : nil @to = params['to'] ? params['to'] : nil end def edge_link '--' end def to_s (t = '') t + "#{@from} #{edge_link} #{to} [\n" + @options.to_a.collect{ |i| i[1] && i[0] != 'label' ? t + $tab + "#{i[0]} = #{i[1]}" : i[1] ? t + $tab + "#{i[0]} = \"#{i[1]}\"" : nil }.compact.join( "\n" ) + "\n#{t}]\n" end end class DOTDirectedEdge < DOTEdge def edge_link '->' end end end puppet-5.5.10/lib/puppet/external/nagios.rb0000644005276200011600000000151013417161721020521 0ustar jenkinsjenkins#-------------------- # A script to retrieve hosts from ldap and create an importable # cfservd file from them require 'digest/md5' #require 'ldap' require 'puppet/external/nagios/parser.rb' require 'puppet/external/nagios/base.rb' module Nagios NAGIOSVERSION = '1.1' # yay colors PINK = "" GREEN = "" YELLOW = "" SLATE = "" ORANGE = "" BLUE = "" NOCOLOR = "" RESET = "" def self.version NAGIOSVERSION end class Config def Config.import(config) text = String.new File.open(config) { |file| file.each { |line| text += line } } parser = Nagios::Parser.new parser.parse(text) end def Config.each Nagios::Object.objects.each { |object| yield object } end end end puppet-5.5.10/lib/puppet/external/nagios/0000755005276200011600000000000013417162176020204 5ustar jenkinsjenkinspuppet-5.5.10/lib/puppet/external/nagios/base.rb0000644005276200011600000003006713417161721021444 0ustar jenkinsjenkins# The base class for all of our Nagios object types. Everything else # is mostly just data. class Nagios::Base class UnknownNagiosType < RuntimeError # When an unknown type is asked for by name. end include Enumerable class << self attr_accessor :parameters, :derivatives, :ocs, :name, :att attr_accessor :ldapbase attr_writer :namevar attr_reader :superior end # Attach one class to another. def self.attach(hash) @attach ||= {} hash.each do |n, v| @attach[n] = v end end # Convert a parameter to camelcase def self.camelcase(param) param.gsub(/_./) do |match| match.sub(/_/,'').capitalize end end # Uncamelcase a parameter. def self.decamelcase(param) param.gsub(/[A-Z]/) do |match| "_#{match.downcase}" end end # Create a new instance of a given class. def self.create(name, args = {}) name = name.intern if name.is_a? String if @types.include?(name) @types[name].new(args) else raise UnknownNagiosType, "Unknown type #{name}" end end # Yield each type in turn. def self.eachtype @types.each do |name, type| yield [name, type] end end # Create a mapping. def self.map(hash) @map ||= {} hash.each do |n, v| @map[n] = v end end # Return a mapping (or nil) for a param def self.mapping(name) name = name.intern if name.is_a? String if defined?(@map) @map[name] else nil end end # Return the namevar for the canonical name. def self.namevar if defined?(@namevar) return @namevar else if parameter?(:name) return :name elsif tmp = (self.name.to_s + "_name").intern and parameter?(tmp) @namevar = tmp return @namevar else raise "Type #{self.name} has no name var" end end end # Create a new type. def self.newtype(name, &block) name = name.intern if name.is_a? String @types ||= {} # Create the class, with the correct name. t = Class.new(self) t.name = name # Everyone gets this. There should probably be a better way, and I # should probably hack the attribute system to look things up based on # this "use" setting, but, eh. t.parameters = [:use] const_set(name.to_s.capitalize,t) # Evaluate the passed block. This should usually define all of the work. t.class_eval(&block) @types[name] = t end # Define both the normal case and camelcase method for a parameter def self.paramattr(name) camel = camelcase(name) param = name [name, camel].each do |method| define_method(method) do @parameters[param] end define_method(method.to_s + "=") do |value| @parameters[param] = value end end end # Is the specified name a valid parameter? def self.parameter?(name) name = name.intern if name.is_a? String @parameters.include?(name) end # Manually set the namevar def self.setnamevar(name) name = name.intern if name.is_a? String @namevar = name end # Set the valid parameters for this class def self.setparameters(*array) @parameters += array end # Set the superior ldap object class. Seems silly to include this # in this class, but, eh. def self.setsuperior(name) @superior = name end # Parameters to suppress in output. def self.suppress(name) @suppress ||= [] @suppress << name end # Whether a given parameter is suppressed. def self.suppress?(name) defined?(@suppress) and @suppress.include?(name) end # Return our name as the string. def self.to_s self.name.to_s end # Return a type by name. def self.type(name) name = name.intern if name.is_a? String @types[name] end # Convenience methods. def [](param) send(param) end # Convenience methods. def []=(param,value) send(param.to_s + "=", value) end # Iterate across all ofour set parameters. def each @parameters.each { |param,value| yield(param,value) } end # Initialize our object, optionally with a list of parameters. def initialize(args = {}) @parameters = {} args.each { |param,value| self[param] = value } if @namevar == :_naginator_name self['_naginator_name'] = self['name'] end end # Handle parameters like attributes. def method_missing(mname, *args) pname = mname.to_s pname.sub!(/=/, '') if self.class.parameter?(pname) if pname =~ /A-Z/ pname = self.class.decamelcase(pname) end self.class.paramattr(pname) # Now access the parameters directly, to make it at least less # likely we'll end up in an infinite recursion. if mname.to_s =~ /=$/ @parameters[pname] = args.first else return @parameters[mname] end else super end end # Retrieve our name, through a bit of redirection. def name send(self.class.namevar) end # This is probably a bad idea. def name=(value) unless self.class.namevar.to_s == "name" send(self.class.namevar.to_s + "=", value) end end def namevar (self.type + "_name").intern end def parammap(param) unless defined?(@map) map = { self.namevar => "cn" } map.update(self.class.map) if self.class.map end if map.include?(param) return map[param] else return "nagios-" + param.id2name.gsub(/_/,'-') end end def parent unless defined?(self.class.attached) puts "Duh, you called parent on an unattached class" return end klass,param = self.class.attached unless @parameters.include?(param) puts "Huh, no attachment param" return end klass[@parameters[param]] end # okay, this sucks # how do i get my list of ocs? def to_ldif str = self.dn + "\n" ocs = Array.new if self.class.ocs # i'm storing an array, so i have to flatten it and stuff kocs = self.class.ocs ocs.push(*kocs) end ocs.push "top" oc = self.class.to_s oc.sub!(/Nagios/,'nagios') oc.sub!(/::/,'') ocs.push oc ocs.each { |objclass| str += "objectclass: #{objclass}\n" } @parameters.each { |name,value| next if self.class.suppress.include?(name) ldapname = self.parammap(name) str += ldapname + ": #{value}\n" } str += "\n" end def to_s str = "define #{self.type} {\n" @parameters.keys.sort.each { |param| value = @parameters[param] str += %{\t%-30s %s\n} % [ param, if value.is_a? Array value.join(",").sub(';', '\;') else value.to_s.sub(';', '\;') end ] } str += "}\n" str end # The type of object we are. def type self.class.name end # object types newtype :host do setparameters :host_name, :alias, :display_name, :address, :parents, :hostgroups, :check_command, :initial_state, :max_check_attempts, :check_interval, :retry_interval, :active_checks_enabled, :passive_checks_enabled, :check_period, :obsess_over_host, :check_freshness, :freshness_threshold, :event_handler, :event_handler_enabled, :low_flap_threshold, :high_flap_threshold, :flap_detection_enabled, :flap_detection_options, :failure_prediction_enabled, :process_perf_data, :retain_status_information, :retain_nonstatus_information, :contacts, :contact_groups, :notification_interval, :first_notification_delay, :notification_period, :notification_options, :notifications_enabled, :stalking_options, :notes, :notes_url, :action_url, :icon_image, :icon_image_alt, :vrml_image, :statusmap_image, "2d_coords".intern, "3d_coords".intern, :register, :use, :realm, :poller_tag, :business_impact setsuperior "person" map :address => "ipHostNumber" end newtype :hostgroup do setparameters :hostgroup_name, :alias, :members, :hostgroup_members, :notes, :notes_url, :action_url, :register, :use, :realm end newtype :service do attach :host => :host_name setparameters :host_name, :hostgroup_name, :service_description, :display_name, :servicegroups, :is_volatile, :check_command, :initial_state, :max_check_attempts, :check_interval, :retry_interval, :normal_check_interval, :retry_check_interval, :active_checks_enabled, :passive_checks_enabled, :parallelize_check, :check_period, :obsess_over_service, :check_freshness, :freshness_threshold, :event_handler, :event_handler_enabled, :low_flap_threshold, :high_flap_threshold, :flap_detection_enabled,:flap_detection_options, :process_perf_data, :failure_prediction_enabled, :retain_status_information, :retain_nonstatus_information, :notification_interval, :first_notification_delay, :notification_period, :notification_options, :notifications_enabled, :contacts, :contact_groups, :stalking_options, :notes, :notes_url, :action_url, :icon_image, :icon_image_alt, :register, :use, :_naginator_name, :poller_tag, :business_impact suppress :host_name setnamevar :_naginator_name end newtype :servicegroup do setparameters :servicegroup_name, :alias, :members, :servicegroup_members, :notes, :notes_url, :action_url, :register, :use end newtype :contact do setparameters :contact_name, :alias, :contactgroups, :host_notifications_enabled, :service_notifications_enabled, :host_notification_period, :service_notification_period, :host_notification_options, :service_notification_options, :host_notification_commands, :service_notification_commands, :email, :pager, :address1, :address2, :address3, :address4, :address5, :address6, :can_submit_commands, :retain_status_information, :retain_nonstatus_information, :register, :use setsuperior "person" end newtype :contactgroup do setparameters :contactgroup_name, :alias, :members, :contactgroup_members, :register, :use end # TODO - We should support generic time periods here eg "day 1 - 15" newtype :timeperiod do setparameters :timeperiod_name, :alias, :sunday, :monday, :tuesday, :wednesday, :thursday, :friday, :saturday, :exclude, :register, :use end newtype :command do setparameters :command_name, :command_line, :poller_tag end newtype :servicedependency do setparameters :dependent_host_name, :dependent_hostgroup_name, :dependent_service_description, :host_name, :hostgroup_name, :service_description, :inherits_parent, :execution_failure_criteria, :notification_failure_criteria, :dependency_period, :register, :use, :_naginator_name setnamevar :_naginator_name end newtype :serviceescalation do setparameters :host_name, :hostgroup_name, :servicegroup_name, :service_description, :contacts, :contact_groups, :first_notification, :last_notification, :notification_interval, :escalation_period, :escalation_options, :register, :use, :_naginator_name setnamevar :_naginator_name end newtype :hostdependency do setparameters :dependent_host_name, :dependent_hostgroup_name, :host_name, :hostgroup_name, :inherits_parent, :execution_failure_criteria, :notification_failure_criteria, :dependency_period, :register, :use, :_naginator_name setnamevar :_naginator_name end newtype :hostescalation do setparameters :host_name, :hostgroup_name, :contacts, :contact_groups, :first_notification, :last_notification, :notification_interval, :escalation_period, :escalation_options, :register, :use, :_naginator_name setnamevar :_naginator_name end newtype :hostextinfo do setparameters :host_name, :notes, :notes_url, :icon_image, :icon_image_alt, :vrml_image, :statusmap_image, "2d_coords".intern, "3d_coords".intern, :register, :use setnamevar :host_name end newtype :serviceextinfo do setparameters :host_name, :service_description, :notes, :notes_url, :action_url, :icon_image, :icon_image_alt, :register, :use, :_naginator_name setnamevar :_naginator_name end end puppet-5.5.10/lib/puppet/external/nagios/grammar.ry0000644005276200011600000001377213417161721022213 0ustar jenkinsjenkins# vim: syntax=ruby class Nagios::Parser token DEFINE NAME PARAM LCURLY RCURLY VALUE RETURN rule decls: decl { return val[0] if val[0] } | decls decl { if val[1].nil? result = val[0] else if val[0].nil? result = val[1] else result = [ val[0], val[1] ].flatten end end } ; decl: object { result = [val[0]] } | RETURN { result = nil } ; object: DEFINE NAME LCURLY returns vars RCURLY { result = Nagios::Base.create(val[1],val[4]) } ; vars: var | vars var { val[1].each {|p,v| val[0][p] = v } result = val[0] } ; var: PARAM VALUE returns { result = {val[0] => val[1]} } | PARAM returns { result = {val[0] => "" } } ; returns: RETURN | RETURN returns ; end ----inner require 'strscan' class ::Nagios::Parser::SyntaxError < RuntimeError; end def parse(src) if (RUBY_VERSION < '2.1.0') && src.respond_to?("force_encoding") then src.force_encoding("ASCII-8BIT") end @ss = StringScanner.new(src) # state variables @in_parameter_value = false @in_object_definition = false @done = false @line = 1 @yydebug = true do_parse end # This tokenizes the outside of object definitions, # and detects when we start defining an object. # We ignore whitespaces, comments and inline comments. # We yield when finding newlines, the "define" keyword, # the object name and the opening curly bracket. def tokenize_outside_definitions case when (chars = @ss.skip(/[ \t]+/)) # ignore whitespace /\s+/ ; when (text = @ss.scan(/\#.*$/)) # ignore comments ; when (text = @ss.scan(/;.*$/)) # ignore inline comments ; when (text = @ss.scan(/\n/)) # newline [:RETURN, text] when (text = @ss.scan(/\b(define)\b/)) # the "define" keyword [:DEFINE, text] when (text = @ss.scan(/[^{ \t\n]+/)) # the name of the object being defined (everything not an opening curly bracket or a separator) [:NAME, text] when (text = @ss.scan(/\{/)) # the opening curly bracket - we enter object definition @in_object_definition = true [:LCURLY, text] else text = @ss.string[@ss.pos .. -1] raise ScanError, "can not match: '#{text}'" end # case end # This tokenizes until we find the parameter name. def tokenize_parameter_name case when (chars = @ss.skip(/[ \t]+/)) # ignore whitespace /\s+/ ; when (text = @ss.scan(/\#.*$/)) # ignore comments ; when (text = @ss.scan(/;.*$/)) # ignore inline comments ; when (text = @ss.scan(/\n/)) # newline [:RETURN, text] when (text = @ss.scan(/\}/)) # closing curly bracket : end of definition @in_object_definition = false [:RCURLY, text] when (not @in_parameter_value and (text = @ss.scan(/\S+/))) # This is the name of the parameter @in_parameter_value = true [:PARAM, text] else text = @ss.string[@ss.pos .. -1] raise ScanError, "can not match: '#{text}'" end # case end # This tokenizes the parameter value. # There is a special handling for lines containing semicolons : # - unescaped semicolons are line comments (and should stop parsing of the line) # - escaped (with backslash \) semicolons should be kept in the parameter value (without the backslash) def tokenize_parameter_value case when (chars = @ss.skip(/[ \t]+/)) # ignore whitespace /\s+/ ; when (text = @ss.scan(/\#.*$/)) # ignore comments ; when (text = @ss.scan(/\n/)) # newline @in_parameter_value = false [:RETURN, text] when (text = @ss.scan(/.+$/)) # Value of parameter @in_parameter_value = false # Special handling of inline comments (;) and escaped semicolons (\;) # We split the string on escaped semicolons (\;), # Then we rebuild it as long as there are no inline comments (;) # We join the rebuilt string with unescaped semicolons (on purpose) array = text.split('\;', 0) text = "" array.each do |elt| # Now we split at inline comments. If we have more than 1 element in the array # it means we have an inline comment, so we are able to stop parsing # However we still want to reconstruct the string with its first part (before the comment) linearray = elt.split(';', 0) # Let's reconstruct the string with a (unescaped) semicolon if text != "" then text += ';' end text += linearray[0] # Now we can stop if linearray.length > 1 then break end end # We strip the text to remove spaces between end of string and beginning of inline comment [:VALUE, text.strip] else text = @ss.string[@ss.pos .. -1] raise ScanError, "can not match: '#{text}'" end # case end # This tokenizes inside an object definition. # Two cases : parameter name and parameter value def tokenize_inside_definitions if @in_parameter_value tokenize_parameter_value else tokenize_parameter_name end end # The lexer. Very simple. def token text = @ss.peek(1) @line += 1 if text == "\n" token = if @in_object_definition tokenize_inside_definitions else tokenize_outside_definitions end token end def next_token return if @ss.eos? # skips empty actions until _next_token = token or @ss.eos?; end _next_token end def yydebug 1 end def yywrap 0 end def on_error(token, value, vstack ) # text = @ss.string[@ss.pos .. -1] text = @ss.peek(20) msg = "" unless value.nil? msg = "line #{@line}: syntax error at value '#{value}' : #{text}" else msg = "line #{@line}: syntax error at token '#{token}' : #{text}" end if @ss.eos? msg = "line #{@line}: Unexpected end of file" end if token == '$end'.intern puts "okay, this is silly" else raise ::Nagios::Parser::SyntaxError, msg end end puppet-5.5.10/lib/puppet/external/nagios/makefile0000644005276200011600000000024713417161721021702 0ustar jenkinsjenkinsall: parser.rb debug: parser.rb setdebug parser.rb: grammar.ry racc -oparser.rb grammar.ry setdebug: perl -pi -e 's{\@yydebug =.*$$}{\@yydebug = true}' parser.rb puppet-5.5.10/lib/puppet/external/nagios/parser.rb0000644005276200011600000002175613417161721022033 0ustar jenkinsjenkins# # DO NOT MODIFY!!!! # This file is automatically generated by Racc 1.4.9 # from Racc grammer file "". # require 'racc/parser.rb' module Nagios class Parser < Racc::Parser module_eval(<<'...end grammar.ry/module_eval...', 'grammar.ry', 50) require 'strscan' class ::Nagios::Parser::SyntaxError < RuntimeError; end def parse(src) if (RUBY_VERSION < '2.1.0') && src.respond_to?("force_encoding") then src.force_encoding("ASCII-8BIT") end @ss = StringScanner.new(src) # state variables @in_parameter_value = false @in_object_definition = false @done = false @line = 1 @yydebug = true do_parse end # This tokenizes the outside of object definitions, # and detects when we start defining an object. # We ignore whitespaces, comments and inline comments. # We yield when finding newlines, the "define" keyword, # the object name and the opening curly bracket. def tokenize_outside_definitions case when (chars = @ss.skip(/[ \t]+/)) # ignore whitespace /\s+/ ; when (text = @ss.scan(/\#.*$/)) # ignore comments ; when (text = @ss.scan(/;.*$/)) # ignore inline comments ; when (text = @ss.scan(/\n/)) # newline [:RETURN, text] when (text = @ss.scan(/\b(define)\b/)) # the "define" keyword [:DEFINE, text] when (text = @ss.scan(/[^{ \t\n]+/)) # the name of the object being defined (everything not an opening curly bracket or a separator) [:NAME, text] when (text = @ss.scan(/\{/)) # the opening curly bracket - we enter object definition @in_object_definition = true [:LCURLY, text] else text = @ss.string[@ss.pos .. -1] raise ScanError, "can not match: '#{text}'" end # case end # This tokenizes until we find the parameter name. def tokenize_parameter_name case when (chars = @ss.skip(/[ \t]+/)) # ignore whitespace /\s+/ ; when (text = @ss.scan(/\#.*$/)) # ignore comments ; when (text = @ss.scan(/;.*$/)) # ignore inline comments ; when (text = @ss.scan(/\n/)) # newline [:RETURN, text] when (text = @ss.scan(/\}/)) # closing curly bracket : end of definition @in_object_definition = false [:RCURLY, text] when (not @in_parameter_value and (text = @ss.scan(/\S+/))) # This is the name of the parameter @in_parameter_value = true [:PARAM, text] else text = @ss.string[@ss.pos .. -1] raise ScanError, "can not match: '#{text}'" end # case end # This tokenizes the parameter value. # There is a special handling for lines containing semicolons : # - unescaped semicolons are line comments (and should stop parsing of the line) # - escaped (with backslash \) semicolons should be kept in the parameter value (without the backslash) def tokenize_parameter_value case when (chars = @ss.skip(/[ \t]+/)) # ignore whitespace /\s+/ ; when (text = @ss.scan(/\#.*$/)) # ignore comments ; when (text = @ss.scan(/\n/)) # newline @in_parameter_value = false [:RETURN, text] when (text = @ss.scan(/.+$/)) # Value of parameter @in_parameter_value = false # Special handling of inline comments (;) and escaped semicolons (\;) # We split the string on escaped semicolons (\;), # Then we rebuild it as long as there are no inline comments (;) # We join the rebuilt string with unescaped semicolons (on purpose) array = text.split('\;', 0) text = "" array.each do |elt| # Now we split at inline comments. If we have more than 1 element in the array # it means we have an inline comment, so we are able to stop parsing # However we still want to reconstruct the string with its first part (before the comment) linearray = elt.split(';', 0) # Let's reconstruct the string with a (unescaped) semicolon if text != "" then text += ';' end text += linearray[0] # Now we can stop if linearray.length > 1 then break end end # We strip the text to remove spaces between end of string and beginning of inline comment [:VALUE, text.strip] else text = @ss.string[@ss.pos .. -1] raise ScanError, "can not match: '#{text}'" end # case end # This tokenizes inside an object definition. # Two cases : parameter name and parameter value def tokenize_inside_definitions if @in_parameter_value tokenize_parameter_value else tokenize_parameter_name end end # The lexer. Very simple. def token text = @ss.peek(1) @line += 1 if text == "\n" token = if @in_object_definition tokenize_inside_definitions else tokenize_outside_definitions end token end def next_token return if @ss.eos? # skips empty actions until _next_token = token or @ss.eos?; end _next_token end def yydebug 1 end def yywrap 0 end def on_error(token, value, vstack ) # text = @ss.string[@ss.pos .. -1] text = @ss.peek(20) msg = "" unless value.nil? msg = "line #{@line}: syntax error at value '#{value}' : #{text}" else msg = "line #{@line}: syntax error at token '#{token}' : #{text}" end if @ss.eos? msg = "line #{@line}: Unexpected end of file" end if token == '$end'.intern puts "okay, this is silly" else raise ::Nagios::Parser::SyntaxError, msg end end ...end grammar.ry/module_eval... ##### State transition tables begin ### racc_action_table = [ 8, 3, 3, 14, 12, 18, 10, 4, 4, 20, 12, 14, 12, 9, 6, 12 ] racc_action_check = [ 5, 0, 5, 13, 9, 13, 8, 0, 5, 14, 14, 11, 12, 6, 3, 20 ] racc_action_pointer = [ -1, nil, nil, 11, nil, 0, 8, nil, 6, -4, nil, 7, 4, -1, 2, nil, nil, nil, nil, nil, 7, nil ] racc_action_default = [ -12, -1, -3, -12, -4, -12, -12, -2, -12, -12, 22, -12, -10, -12, -12, -6, -11, -7, -5, -9, -12, -8 ] racc_goto_table = [ 11, 1, 15, 16, 17, 19, 7, 13, 5, nil, nil, 21 ] racc_goto_check = [ 4, 2, 6, 4, 6, 4, 2, 5, 1, nil, nil, 4 ] racc_goto_pointer = [ nil, 8, 1, nil, -9, -4, -9 ] racc_goto_default = [ nil, nil, nil, 2, nil, nil, nil ] racc_reduce_table = [ 0, 0, :racc_error, 1, 10, :_reduce_1, 2, 10, :_reduce_2, 1, 11, :_reduce_3, 1, 11, :_reduce_4, 6, 12, :_reduce_5, 1, 14, :_reduce_none, 2, 14, :_reduce_7, 3, 15, :_reduce_8, 2, 15, :_reduce_9, 1, 13, :_reduce_none, 2, 13, :_reduce_none ] racc_reduce_n = 12 racc_shift_n = 22 racc_token_table = { false => 0, :error => 1, :DEFINE => 2, :NAME => 3, :PARAM => 4, :LCURLY => 5, :RCURLY => 6, :VALUE => 7, :RETURN => 8 } racc_nt_base = 9 racc_use_result_var = true Racc_arg = [ racc_action_table, racc_action_check, racc_action_default, racc_action_pointer, racc_goto_table, racc_goto_check, racc_goto_default, racc_goto_pointer, racc_nt_base, racc_reduce_table, racc_token_table, racc_shift_n, racc_reduce_n, racc_use_result_var ] Racc_token_to_s_table = [ "$end", "error", "DEFINE", "NAME", "PARAM", "LCURLY", "RCURLY", "VALUE", "RETURN", "$start", "decls", "decl", "object", "returns", "vars", "var" ] Racc_debug_parser = false ##### State transition tables end ##### # reduce 0 omitted module_eval(<<'.,.,', 'grammar.ry', 6) def _reduce_1(val, _values, result) return val[0] if val[0] result end .,., module_eval(<<'.,.,', 'grammar.ry', 8) def _reduce_2(val, _values, result) if val[1].nil? result = val[0] else if val[0].nil? result = val[1] else result = [ val[0], val[1] ].flatten end end result end .,., module_eval(<<'.,.,', 'grammar.ry', 20) def _reduce_3(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'grammar.ry', 21) def _reduce_4(val, _values, result) result = nil result end .,., module_eval(<<'.,.,', 'grammar.ry', 25) def _reduce_5(val, _values, result) result = Nagios::Base.create(val[1],val[4]) result end .,., # reduce 6 omitted module_eval(<<'.,.,', 'grammar.ry', 31) def _reduce_7(val, _values, result) val[1].each {|p,v| val[0][p] = v } result = val[0] result end .,., module_eval(<<'.,.,', 'grammar.ry', 38) def _reduce_8(val, _values, result) result = {val[0] => val[1]} result end .,., module_eval(<<'.,.,', 'grammar.ry', 39) def _reduce_9(val, _values, result) result = {val[0] => "" } result end .,., # reduce 10 omitted # reduce 11 omitted def _reduce_none(val, _values, result) val[0] end end # class Parser end # module Nagios puppet-5.5.10/lib/puppet/face.rb0000644005276200011600000000112413417161721016316 0ustar jenkinsjenkins# The public name of this feature is 'face', but we have hidden all the # plumbing over in the 'interfaces' namespace to make clear the distinction # between the two. # # This file exists to ensure that the public name is usable without revealing # the details of the implementation; you really only need go look at anything # under Interfaces if you are looking to extend the implementation. # # It isn't hidden to gratuitously hide things, just to make it easier to # separate out the interests people will have. --daniel 2011-04-07 require 'puppet/interface' Puppet::Face = Puppet::Interface puppet-5.5.10/lib/puppet/face/0000755005276200011600000000000013417162176016000 5ustar jenkinsjenkinspuppet-5.5.10/lib/puppet/face/catalog.rb0000644005276200011600000001103013417161721017725 0ustar jenkinsjenkinsrequire 'puppet/indirector/face' Puppet::Indirector::Face.define(:catalog, '0.0.1') do copyright "Puppet Inc.", 2011 license "Apache 2 license; see COPYING" summary _("Compile, save, view, and convert catalogs.") description <<-'EOT' This subcommand deals with catalogs, which are compiled per-node artifacts generated from a set of Puppet manifests. By default, it interacts with the compiling subsystem and compiles a catalog using the default manifest and `certname`, but you can change the source of the catalog with the `--terminus` option. You can also choose to print any catalog in 'dot' format (for easy graph viewing with OmniGraffle or Graphviz) with '--render-as dot'. EOT short_description <<-'EOT' This subcommand deals with catalogs, which are compiled per-node artifacts generated from a set of Puppet manifests. By default, it interacts with the compiling subsystem and compiles a catalog using the default manifest and `certname`; use the `--terminus` option to change the source of the catalog. EOT deactivate_action(:destroy) deactivate_action(:search) find = get_action(:find) find.summary "Retrieve the catalog for a node." find.arguments "" find.returns <<-'EOT' A serialized catalog. When used from the Ruby API, returns a Puppet::Resource::Catalog object. EOT action(:apply) do summary "Find and apply a catalog." description <<-'EOT' Finds and applies a catalog. This action takes no arguments, but the source of the catalog can be managed with the `--terminus` option. EOT returns <<-'EOT' Nothing. When used from the Ruby API, returns a Puppet::Transaction::Report object. EOT examples <<-'EOT' Apply the locally cached catalog: $ puppet catalog apply --terminus yaml Retrieve a catalog from the master and apply it, in one step: $ puppet catalog apply --terminus rest API example: # ... Puppet::Face[:catalog, '0.0.1'].download # (Termini are singletons; catalog.download has a side effect of # setting the catalog terminus to yaml) report = Puppet::Face[:catalog, '0.0.1'].apply # ... EOT when_invoked do |options| catalog = Puppet::Face[:catalog, "0.0.1"].find(Puppet[:certname]) or raise "Could not find catalog for #{Puppet[:certname]}" catalog = catalog.to_ral report = Puppet::Transaction::Report.new report.configuration_version = catalog.version report.environment = Puppet[:environment] Puppet::Util::Log.newdestination(report) begin benchmark(:notice, "Finished catalog run in %{seconds} seconds") do catalog.apply(:report => report) end rescue => detail Puppet.log_exception(detail, "Failed to apply catalog: #{detail}") end report.finalize_report report end end action(:download) do summary "Download this node's catalog from the puppet master server." description <<-'EOT' Retrieves a catalog from the puppet master and saves it to the local yaml cache. This action always contacts the puppet master and will ignore alternate termini. The saved catalog can be used in any subsequent catalog action by specifying '--terminus yaml' for that action. EOT returns "Nothing." notes <<-'EOT' When used from the Ruby API, this action has a side effect of leaving Puppet::Resource::Catalog.indirection.terminus_class set to yaml. The terminus must be explicitly re-set for subsequent catalog actions. EOT examples <<-'EOT' Retrieve and store a catalog: $ puppet catalog download API example: Puppet::Face[:plugin, '0.0.1'].download Puppet::Face[:facts, '0.0.1'].upload Puppet::Face[:catalog, '0.0.1'].download # ... EOT when_invoked do |options| Puppet::Resource::Catalog.indirection.terminus_class = :rest Puppet::Resource::Catalog.indirection.cache_class = nil catalog = nil retrieval_duration = thinmark do catalog = Puppet::Face[:catalog, '0.0.1'].find(Puppet[:certname]) end catalog.retrieval_duration = retrieval_duration catalog.write_class_file Puppet::Resource::Catalog.indirection.terminus_class = :yaml Puppet::Face[:catalog, "0.0.1"].save(catalog) Puppet.notice "Saved catalog for #{Puppet[:certname]} to #{Puppet::Resource::Catalog.indirection.terminus.path(Puppet[:certname])}" nil end end end puppet-5.5.10/lib/puppet/face/catalog/0000755005276200011600000000000013417162176017412 5ustar jenkinsjenkinspuppet-5.5.10/lib/puppet/face/catalog/select.rb0000644005276200011600000000334713417161721021220 0ustar jenkinsjenkins# Select and show a list of resources of a given type. Puppet::Face.define(:catalog, '0.0.1') do action :select do summary _("Retrieve a catalog and filter it for resources of a given type.") arguments _(" ") returns _(<<-'EOT') A list of resource references ("Type[title]"). When used from the API, returns an array of Puppet::Resource objects excised from a catalog. EOT description <<-'EOT' Retrieves a catalog for the specified host, then searches it for all resources of the requested type. EOT notes <<-'NOTES' By default, this action will retrieve a catalog from Puppet's compiler subsystem; you must call the action with `--terminus rest` if you wish to retrieve a catalog from the puppet master. FORMATTING ISSUES: This action cannot currently render useful yaml; instead, it returns an entire catalog. Use json instead. NOTES examples <<-'EOT' Ask the puppet master for a list of managed file resources for a node: $ puppet catalog select --terminus rest somenode.magpie.lan file EOT when_invoked do |host, type, options| # REVISIT: Eventually, type should have a default value that triggers # the non-specific behaviour. For now, though, this will do. # --daniel 2011-05-03 catalog = Puppet::Resource::Catalog.indirection.find(host) if type == '*' catalog.resources else type = type.downcase catalog.resources.reject { |res| res.type.downcase != type } end end when_rendering :console do |value| if value.nil? then _("no matching resources found") else value.map {|x| x.to_s }.join("\n") end end end end puppet-5.5.10/lib/puppet/face/facts.rb0000644005276200011600000000536713417161721017433 0ustar jenkinsjenkinsrequire 'puppet/indirector/face' require 'puppet/node/facts' Puppet::Indirector::Face.define(:facts, '0.0.1') do copyright "Puppet Inc.", 2011 license _("Apache 2 license; see COPYING") summary _("Retrieve and store facts.") description <<-'EOT' This subcommand manages facts, which are collections of normalized system information used by Puppet. It can read facts directly from the local system (with the default `facter` terminus). EOT find = get_action(:find) find.summary _("Retrieve a node's facts.") find.arguments _("[]") find.returns <<-'EOT' A hash containing some metadata and (under the "values" key) the set of facts for the requested node. When used from the Ruby API: A Puppet::Node::Facts object. RENDERING ISSUES: Facts cannot currently be rendered as a string; use yaml or json. EOT find.notes <<-'EOT' When using the `facter` terminus, the host argument is ignored. EOT find.examples <<-'EOT' Get facts from the local system: $ puppet facts find EOT find.default = true deactivate_action(:destroy) deactivate_action(:search) action(:upload) do summary _("Upload local facts to the puppet master.") description <<-'EOT' Reads facts from the local system using the `facter` terminus, then saves the returned facts using the rest terminus. EOT returns "Nothing." notes <<-'EOT' This action requires that the puppet master's `auth.conf` file allow `PUT` or `save` access to the `/puppet/v3/facts` API endpoint. For details on configuring Puppet Server's `auth.conf`, see: For legacy Rack-based Puppet Masters, see: EOT examples <<-'EOT' Upload facts: $ puppet facts upload EOT render_as :json when_invoked do |options| # Use `agent` sections settings for certificates, Puppet Server URL, # etc. instead of `user` section settings. Puppet.settings.preferred_run_mode = :agent Puppet::Node::Facts.indirection.terminus_class = :facter facts = Puppet::Node::Facts.indirection.find(Puppet[:node_name_value]) unless Puppet[:node_name_fact].empty? Puppet[:node_name_value] = facts.values[Puppet[:node_name_fact]] facts.name = Puppet[:node_name_value] end Puppet::Node::Facts.indirection.terminus_class = :rest server = Puppet::Node::Facts::Rest.server Puppet.notice(_("Uploading facts for '%{node}' to: '%{server}'") % { node: Puppet[:node_name_value], server: server}) Puppet::Node::Facts.indirection.save(facts) end end end puppet-5.5.10/lib/puppet/face/generate.rb0000644005276200011600000000431313417161721020113 0ustar jenkinsjenkinsrequire 'puppet/face' require 'puppet/generate/type' # Create the Generate face Puppet::Face.define(:generate, '0.1.0') do copyright 'Puppet Inc.', 2016 license _('Apache 2 license; see COPYING') summary _('Generates Puppet code from Ruby definitions.') action(:types) do summary _('Generates Puppet code for custom types') description <<-'EOT' Generates definitions for custom resource types using Puppet code. Types defined in Puppet code can be used to isolate custom type definitions between different environments. EOT examples <<-'EOT' Generate Puppet type definitions for all custom resource types in the current environment: $ puppet generate types Generate Puppet type definitions for all custom resource types in the specified environment: $ puppet generate types --environment development EOT option '--format ' + _('') do summary _('The generation output format to use. Supported formats: pcore.') default_to { 'pcore' } before_action do |_, _, options| raise ArgumentError, _("'%{format}' is not a supported format for type generation.") % { format: options[:format] } unless ['pcore'].include?(options[:format]) end end option '--force' do summary _('Forces the generation of output files (skips up-to-date checks).') default_to { false } end when_invoked do |options| generator = Puppet::Generate::Type inputs = generator.find_inputs(options[:format].to_sym) environment = Puppet.lookup(:current_environment) # get the common output directory (in /.resource_types) - create it if it does not exists # error if it exists and is not a directory # path_to_env = environment.configuration.path_to_env outputdir = File.join(path_to_env, '.resource_types') if Puppet::FileSystem.exist?(outputdir) && !Puppet::FileSystem.directory?(outputdir) raise ArgumentError, _("The output directory '%{outputdir}' exists and is not a directory") % { outputdir: outputdir } end Puppet::FileSystem::mkpath(outputdir) generator.generate(inputs, outputdir, options[:force]) nil end end end puppet-5.5.10/lib/puppet/face/help/0000755005276200011600000000000013417162176016730 5ustar jenkinsjenkinspuppet-5.5.10/lib/puppet/face/help/action.erb0000644005276200011600000000512613417161721020676 0ustar jenkinsjenkins<%# encoding: UTF-8%> <% if action.deprecated? -%> <%= "Warning: 'puppet #{action.face.name} #{action.name}' is deprecated and will be removed in a future release." %> <% end %> <% if action.synopsis -%> USAGE: <%= action.synopsis %> <% end -%> <%= action.short_description || action.summary || face.summary || "undocumented subcommand" %> <% if action.returns -%> RETURNS: <%= action.returns.strip %> <% end -%> OPTIONS: <%# Remove these options once we can introspect them normally. -%> --render-as FORMAT - The rendering format to use. --verbose - Whether to log verbosely. --debug - Whether to log debug information. <% optionroom = 30 summaryroom = 80 - 5 - optionroom disp_glob_opts = action.display_global_options.uniq unless disp_glob_opts.empty? disp_glob_opts.sort.each do |name| option = name desc = Puppet.settings.setting(option).desc type = Puppet.settings.setting(option).default type ||= Puppet.settings.setting(option).type.to_s.upcase -%> <%= "--#{option} #{type}".ljust(optionroom) + ' - ' -%> <% if !(desc) -%> undocumented option <% elsif desc.length <= summaryroom -%> <%= desc %> <% else words = desc.split wrapped = [''] i = 0 words.each do |word| if wrapped[i].length + word.length <= summaryroom wrapped[i] << word + ' ' else i += 1 wrapped[i] = word + ' ' end end -%> <%= wrapped.shift.strip %> <% wrapped.each do |line| -%> <%= (' ' * (optionroom + 5) ) + line.strip %> <% end end end end unless action.options.empty? action.options.sort.each do |name| option = action.get_option name -%> <%= " " + option.optparse.join(" | ")[0,(optionroom - 1)].ljust(optionroom) + ' - ' -%> <% if !(option.summary) -%> undocumented option <% elsif option.summary.length <= summaryroom -%> <%= option.summary %> <% else words = option.summary.split wrapped = [''] i = 0 words.each do |word| if wrapped[i].length + word.length <= summaryroom wrapped[i] << word + ' ' else i += 1 wrapped[i] = word + ' ' end end -%> <%= wrapped.shift.strip %> <% wrapped.each do |line| -%> <%= (' ' * (optionroom + 5) ) + line.strip %> <% end end end -%> <% end -%> <% if face.respond_to? :indirection -%> TERMINI: <%= face.class.terminus_classes(face.indirection.name).join(", ") %> <% end -%> See 'puppet help <%= face.name %>' or 'man puppet-<%= face.name %>' for full help. puppet-5.5.10/lib/puppet/face/help/face.erb0000644005276200011600000000640013417161721020313 0ustar jenkinsjenkins<%# encoding: UTF-8%> <% if face.deprecated? -%> <%= "Warning: 'puppet #{face.name}' is deprecated and will be removed in a future release." %> <% end %> <% if face.synopsis -%> USAGE: <%= face.synopsis %> <% end -%> <%= (face.short_description || face.summary || "undocumented subcommand").strip %> OPTIONS: <%# Remove these options once we can introspect them normally. -%> --render-as FORMAT - The rendering format to use. --verbose - Whether to log verbosely. --debug - Whether to log debug information. <% optionroom = 30 summaryroom = 80 - 5 - optionroom disp_glob_opts = face.display_global_options.uniq unless disp_glob_opts.empty? disp_glob_opts.sort.each do |name| option = name desc = Puppet.settings.setting(option).desc type = Puppet.settings.setting(option).default type ||= Puppet.settings.setting(option).type.to_s.upcase -%> <%= "--#{option} #{type}".ljust(optionroom) + ' - ' -%> <% if !(desc) -%> undocumented option <% elsif desc.length <= summaryroom -%> <%= desc %> <% else words = desc.split wrapped = [''] i = 0 words.each do |word| if wrapped[i].length + word.length <= summaryroom wrapped[i] << word + ' ' else i += 1 wrapped[i] = word + ' ' end end -%> <%= wrapped.shift.strip %> <% wrapped.each do |line| -%> <%= (' ' * (optionroom + 5) ) + line.strip %> <% end end end end unless face.options.empty? face.options.sort.each do |name| option = face.get_option name -%> <%= " " + option.optparse.join(" | ")[0,(optionroom - 1)].ljust(optionroom) + ' - ' -%> <% if !(option.summary) -%> undocumented option <% elsif option.summary.length <= summaryroom -%> <%= option.summary %> <% else words = option.summary.split wrapped = [''] i = 0 words.each do |word| if wrapped[i].length + word.length <= summaryroom wrapped[i] << word + ' ' else i += 1 wrapped[i] = word + ' ' end end -%> <%= wrapped.shift.strip %> <% wrapped.each do |line| -%> <%= (' ' * (optionroom + 5) ) + line.strip %> <% end end end -%> <% end -%> ACTIONS: <% padding = face.actions.map{|x| x.to_s.length}.max + 2 summaryroom = 80 - (padding + 4) face.actions.each do |actionname| action = face.get_action(actionname) -%> <%= action.name.to_s.ljust(padding) + ' ' -%> <% if !(action.summary) -%> undocumented action <% elsif action.summary.length <= summaryroom -%> <%= action.summary %> <% else words = action.summary.split wrapped = [''] i = 0 words.each do |word| if wrapped[i].length + word.length <= summaryroom wrapped[i] << word + ' ' else i += 1 wrapped[i] = word + ' ' end end -%> <%= wrapped.shift.strip %> <% wrapped.each do |line| -%> <%= (' ' * (padding + 4) ) + line.strip %> <% end end end -%> <% if face.respond_to? :indirection -%> TERMINI: <%= face.class.terminus_classes(face.indirection.name).join(", ") %> <% end -%> See 'puppet help <%= face.name %>' or 'man puppet-<%= face.name %>' for full help. puppet-5.5.10/lib/puppet/face/help/man.erb0000644005276200011600000001036113417161721020171 0ustar jenkinsjenkins<%# encoding: UTF-8%> puppet-<%= face.name %>(8) -- <%= face.summary || "Undocumented subcommand." %> <%= '=' * (_erbout.length - 1) %> <% if face.synopsis -%> SYNOPSIS -------- <%= face.synopsis %> <% end if face.description -%> DESCRIPTION ----------- <%= face.description.strip %> <% end -%> OPTIONS ------- Note that any setting that's valid in the configuration file is also a valid long argument, although it may or may not be relevant to the present action. For example, `server` and `run_mode` are valid settings, so you can specify `--server `, or `--run_mode ` as an argument. See the configuration file documentation at for the full list of acceptable parameters. A commented list of all configuration options can also be generated by running puppet with `--genconfig`. * --render-as FORMAT: The format in which to render output. The most common formats are `json`, `s` (string), `yaml`, and `console`, but other options such as `dot` are sometimes available. * --verbose: Whether to log verbosely. * --debug: Whether to log debug information. <% unless face.display_global_options.empty? face.display_global_options.uniq.sort.each do |name| option = name desc = Puppet::Util::Docs.scrub(Puppet.settings.setting(option).desc) type = Puppet.settings.setting(option).default type ||= Puppet.settings.setting(option).type.to_s.upcase -%> <%= "* --#{option} #{type}" %>: <%= (desc || 'Undocumented setting.').gsub(/^/, ' ') %> <% end end -%> <% unless face.options.empty? face.options.sort.each do |name| option = face.get_option name -%> <%= "* " + option.optparse.join(" | " ) %>: <%= (option.description || option.summary || "Undocumented option.").gsub(/^/, ' ') %> <% end end -%> ACTIONS ------- <% face.actions.each do |actionname| action = face.get_action(actionname) -%> * `<%= action.name.to_s %>` - <%= action.summary %>: <% if action.synopsis -%> `SYNOPSIS` <%= action.synopsis %> <% end -%> `DESCRIPTION` <% if action.description -%> <%= action.description.gsub(/^/, ' ') %> <% else -%> <%= action.summary || "Undocumented action." %> <% end -%> <% unique_options = action.options - face.options unique_display_global_options = action.display_global_options - face.display_global_options unless unique_options.empty? and unique_display_global_options.empty? -%> `OPTIONS` <% unique_display_global_options.uniq.sort.each do |name| option = name desc = Puppet::Util::Docs.scrub(Puppet.settings.setting(option).desc) type = Puppet.settings.setting(option).default type ||= Puppet.settings.setting(option).type.to_s.upcase -%> <%= "<--#{option} #{type}>" %> - <%= (desc || "Undocumented setting.").gsub(/^/, ' ') %> <% end -%> <% unique_options.sort.each do |name| option = action.get_option name text = (option.description || option.summary || "Undocumented option.").chomp + "\n" -%> <%= '<' + option.optparse.join("> | <") + '>' %> - <%= text.gsub(/^/, ' ') %> <% end -%> <% end -%> <% if action.returns -%> `RETURNS` <%= action.returns.gsub(/^/, ' ') %> <% end if action.notes -%> `NOTES` <%= action.notes.gsub(/^/, ' ') %> <% end end if face.examples or face.actions.any? {|actionname| face.get_action(actionname).examples} -%> EXAMPLES -------- <% end if face.examples -%> <%= face.examples %> <% end face.actions.each do |actionname| action = face.get_action(actionname) if action.examples -%> `<%= action.name.to_s %>` <%= action.examples.strip %> <% end end -%> <% if face.notes or face.respond_to? :indirection -%> NOTES ----- <% if face.notes -%> <%= face.notes.strip %> <% end # notes if face.respond_to? :indirection -%> This subcommand is an indirector face, which exposes `find`, `search`, `save`, and `destroy` actions for an indirected subsystem of Puppet. Valid termini for this face include: * `<%= face.class.terminus_classes(face.indirection.name).join("`\n* `") %>` <% end # indirection end # notes or indirection unless face.authors.empty? -%> AUTHOR ------ <%= face.authors.join("\n").gsub(/^/, ' * ') %> <% end -%> COPYRIGHT AND LICENSE --------------------- <%= face.copyright %> <%= face.license %> puppet-5.5.10/lib/puppet/face/help/global.erb0000644005276200011600000000150213417161721020653 0ustar jenkinsjenkins<%# encoding: UTF-8%> Usage: puppet [options] [options] Available subcommands: <%# NOTE: this is probably not a good long-term solution for this. We're only iterating over applications to find the list of things we need to show help for... this works for now because faces can't be run without an application stub. However, when #6753 is resolved, all of the application stubs for faces will go away, and this will need to be updated to reflect that. --cprice 2012-04-26 %> <% all_application_summaries.each do |appname, summary| -%> <%= appname.to_s.ljust(16) %> <%= summary %> <% end -%> See 'puppet help ' for help on a specific subcommand action. See 'puppet help ' for help on a specific subcommand. Puppet v<%= Puppet.version %> puppet-5.5.10/lib/puppet/face/man.rb0000644005276200011600000001220213417161721017070 0ustar jenkinsjenkinsrequire 'puppet/face' require 'puppet/util' require 'pathname' require 'erb' Puppet::Face.define(:man, '0.0.1') do copyright "Puppet Inc.", 2011 license _("Apache 2 license; see COPYING") summary _("Display Puppet manual pages.") description <<-EOT Please use the command 'puppet help ' or the system manpage system 'man puppet-' to display information about Puppet subcommands. The deprecated man subcommand displays manual pages for all Puppet subcommands. If the `ronn` gem () is installed on your system, puppet man will display fully-formatted man pages. If `ronn` is not available, puppet man will display the raw (but human-readable) source text in a pager. EOT notes <<-EOT The pager used for display will be the first found of `$MANPAGER`, `$PAGER`, `less`, `most`, or `more`. EOT action(:man) do summary _("Display the manual page for a Puppet subcommand.") arguments _("") #TRANSLATORS '--render-as s' is a command line option and should not be translated returns _(<<-'EOT') The man data, in Markdown format, suitable for consumption by Ronn. RENDERING ISSUES: To skip fancy formatting and output the raw Markdown text (e.g. for use in a pipeline), call this action with '--render-as s'. EOT examples <<-'EOT' View the installed manual page for the subcommand 'config': $ man puppet-config (Deprecated) View the manual page for the subcommand 'config': $ puppet man config EOT default when_invoked do |*args| # 'args' is an array of the subcommand and arguments from the command line and an options hash # [, ..., {options}] _options = args.pop unless valid_command_line?(args) print_man_help #TRANSLATORS 'puppet man' is a specific command line and should not be translated raise ArgumentError, _("The 'puppet man' command takes a single subcommand to review the subcommand's manpage") end manpage = args.first if default_case?(manpage) print_man_help return nil end if legacy_applications.include?(manpage) return Puppet::Application[manpage].help end # set 'face' as it's used in the erb processing. face = Puppet::Face[manpage.to_sym, :current] _face = face # suppress the unused variable warning file = (Pathname(__FILE__).dirname + "help" + 'man.erb') erb = ERB.new(file.read, nil, '-') erb.filename = file.to_s # Run the ERB template in our current binding, including all the local # variables we established just above. --daniel 2011-04-11 return erb.result(binding) end when_rendering :console do |text| # OK, if we have Ronn on the path we can delegate to it and override the # normal output process. Otherwise delegate to a pager on the raw text, # otherwise we finally just delegate to our parent. Oh, well. # These are the same options for less that git normally uses. # -R : Pass through color control codes (allows display of colors) # -X : Don't init/deinit terminal (leave display on screen on exit) # -F : automatically exit if display fits entirely on one screen # -S : don't wrap long lines ENV['LESS'] ||= 'FRSX' ronn = Puppet::Util.which('ronn') pager = [ENV['MANPAGER'], ENV['PAGER'], 'less', 'most', 'more']. detect {|x| x and x.length > 0 and Puppet::Util.which(x) } if ronn # ronn is a stupid about pager selection, we can be smarter. :) ENV['PAGER'] = pager if pager args = "--man --manual='Puppet Manual' --organization='Puppet Inc., LLC'" # manual pages could contain UTF-8 text IO.popen("#{ronn} #{args}", 'w:UTF-8') do |fh| fh.write text end '' # suppress local output, neh? elsif pager # manual pages could contain UTF-8 text IO.popen(pager, 'w:UTF-8') do |fh| fh.write text end '' else text end end end def valid_command_line?(args) # not too many arguments # This allows the command line case of "puppet man man man" to not throw an error because face_based eats # one of the "man"'s, which means this command line ends up looking like this in the code: 'manface.man("man")' # However when we generate manpages, we do the same call. So we have to allow it and generate the real manpage. args.length <= 1 end # by default, if you ask for the man manpage "puppet man man" face_base removes the "man" from the args that we # are passed, so we get nil instead def default_case?(manpage) manpage.nil? end def print_man_help puts Puppet::Face[:help, :current].help(:man) end def legacy_applications # The list of applications, less those that are duplicated as a face. Puppet::Application.available_application_names.reject do |appname| Puppet::Face.face? appname.to_sym, :current or # ...this is a nasty way to exclude non-applications. :( %w{face_base indirection_base}.include? appname end end deprecate end puppet-5.5.10/lib/puppet/face/module.rb0000644005276200011600000000117013417161721017604 0ustar jenkinsjenkinsrequire 'puppet/face' require 'puppet/module_tool' require 'puppet/util/colors' Puppet::Face.define(:module, '1.0.0') do extend Puppet::Util::Colors copyright "Puppet Inc.", 2012 license _("Apache 2 license; see COPYING") summary _("Creates, installs and searches for modules on the Puppet Forge.") description <<-EOT This subcommand can find, install, and manage modules from the Puppet Forge, a repository of user-contributed Puppet code. It can also generate empty modules, and prepare locally developed modules for release on the Forge. EOT display_global_options "environment", "modulepath" end puppet-5.5.10/lib/puppet/face/module/0000755005276200011600000000000013417162176017265 5ustar jenkinsjenkinspuppet-5.5.10/lib/puppet/face/module/changes.rb0000644005276200011600000000256413417161721021224 0ustar jenkinsjenkinsPuppet::Face.define(:module, '1.0.0') do action(:changes) do summary _("Show modified files of an installed module.") description <<-EOT Shows any files in a module that have been modified since it was installed. This action compares the files on disk to the md5 checksums included in the module's checksums.json or, if that is missing, in metadata.json. EOT returns _("Array of strings representing paths of modified files.") examples <<-EOT Show modified files of an installed module: $ puppet module changes /etc/puppetlabs/code/modules/vcsrepo/ warning: 1 files modified lib/puppet/provider/vcsrepo.rb EOT arguments _("") when_invoked do |path, options| Puppet::ModuleTool.set_option_defaults options unless root_path = Puppet::ModuleTool.find_module_root(path) raise ArgumentError, _("Could not find a valid module at %{path}") % { path: path.inspect } end Puppet::ModuleTool::Applications::Checksummer.run(root_path, options) end when_rendering :console do |return_value| if return_value.empty? Puppet.notice _("No modified files") else Puppet.warning _("%{count} files modified") % { count: return_value.size } end return_value.map do |changed_file| "#{changed_file}" end.join("\n") end end end puppet-5.5.10/lib/puppet/face/module/build.rb0000644005276200011600000000577613417161721020723 0ustar jenkinsjenkinsPuppet::Face.define(:module, '1.0.0') do action(:build) do summary _("Build a module release package.") description <<-EOT Prepares a local module for release on the Puppet Forge by building a ready-to-upload archive file. Note: Module build uses MD5 checksums, which are prohibited on FIPS enabled systems. This action uses the metadata.json file in the module directory to set metadata used by the Forge. See for more about writing metadata.json files. After being built, the release archive file can be found in the module's `pkg` directory. EOT returns _("Pathname object representing the path to the release archive.") examples <<-EOT Build a module release: $ puppet module build puppetlabs-apache notice: Building /Users/kelseyhightower/puppetlabs-apache for release Module built: /Users/kelseyhightower/puppetlabs-apache/pkg/puppetlabs-apache-0.0.1.tar.gz Build the module in the current working directory: $ cd /Users/kelseyhightower/puppetlabs-apache $ puppet module build notice: Building /Users/kelseyhightower/puppetlabs-apache for release Module built: /Users/kelseyhightower/puppetlabs-apache/pkg/puppetlabs-apache-0.0.1.tar.gz EOT arguments _("[]") when_invoked do |*args| options = args.pop if options.nil? or args.length > 1 then raise ArgumentError, _("puppet module build only accepts 0 or 1 arguments") end module_path = args.first if module_path.nil? pwd = Dir.pwd module_path = Puppet::ModuleTool.find_module_root(pwd) if module_path.nil? raise _("Unable to find metadata.json in module root %{pwd} or parent directories. See for required file format.") % { pwd: pwd } end else unless Puppet::ModuleTool.is_module_root?(module_path) raise _("Unable to find metadata.json in module root %{module_path} or parent directories. See for required file format.") % { module_path: module_path } end end #TRANSLATORS 'puppet module build' is the name of the puppet command and 'Puppet Development Kit' is the name of the software package replacing this action and should not be translated. Puppet.deprecation_warning _("`puppet module build` is deprecated and will be removed in a future release. This action has been replaced by Puppet Development Kit. For more information visit https://puppet.com/docs/pdk/latest/pdk.html.") Puppet::ModuleTool.set_option_defaults options Puppet::ModuleTool::Applications::Builder.run(module_path, options) end when_rendering :console do |return_value| # Get the string representation of the Pathname object. _("Module built: %{path}") % { path: return_value.expand_path.to_s } end deprecate end end puppet-5.5.10/lib/puppet/face/module/generate.rb0000644005276200011600000002121013417161721021373 0ustar jenkinsjenkinsPuppet::Face.define(:module, '1.0.0') do action(:generate) do summary _("Generate boilerplate for a new module.") description <<-EOT Generates boilerplate for a new module by creating the directory structure and files recommended for the Puppet community's best practices. A module may need additional directories beyond this boilerplate if it provides plugins, files, or templates. EOT returns _("Array of Pathname objects representing paths of generated files.") examples <<-EOT Generate a new module in the current directory: $ puppet module generate puppetlabs-ssh We need to create a metadata.json file for this module. Please answer the following questions; if the question is not applicable to this module, feel free to leave it blank. Puppet uses Semantic Versioning (semver.org) to version modules. What version is this module? [0.1.0] --> Who wrote this module? [puppetlabs] --> What license does this module code fall under? [Apache-2.0] --> How would you describe this module in a single sentence? --> Where is this module's source code repository? --> Where can others go to learn more about this module? --> Where can others go to file issues about this module? --> ---------------------------------------- { "name": "puppetlabs-ssh", "version": "0.1.0", "author": "puppetlabs", "summary": null, "license": "Apache-2.0", "source": "", "project_page": null, "issues_url": null, "dependencies": [ { "name": "puppetlabs-stdlib", "version_requirement": ">= 1.0.0" } ] } ---------------------------------------- About to generate this metadata; continue? [n/Y] --> Notice: Generating module at /Users/username/Projects/puppet/puppetlabs-ssh... Notice: Populating ERB templates... Finished; module generated in puppetlabs-ssh. puppetlabs-ssh/manifests puppetlabs-ssh/manifests/init.pp puppetlabs-ssh/metadata.json puppetlabs-ssh/README.md puppetlabs-ssh/spec puppetlabs-ssh/spec/spec_helper.rb puppetlabs-ssh/tests puppetlabs-ssh/tests/init.pp EOT option "--skip-interview" do summary _("Bypass the interactive metadata interview") description <<-EOT Do not attempt to perform a metadata interview. Primarily useful for automatic execution of `puppet module generate`. EOT end arguments _("") when_invoked do |name, options| # Since we only want to interview if it's being rendered to the console # (i.e. when invoked with `puppet module generate`), we can't do any work # here in the when_invoked block. The result of this block is then # passed to each renderer, which will handle it appropriately; by # returning a simple message like this, every renderer will simply output # the string. # Our `when_rendering :console` handler will ignore this value and # actually generate the module. # # All this is necessary because it is not possible at this point in time # to know what the destination of the output is. _("This format is not supported by this action.") end when_rendering :console do |_, name, options| Puppet::ModuleTool.set_option_defaults options begin # A default dependency for all newly generated modules is being # introduced as a substitute for the comments we used to include in the # previous module data specifications. While introducing a default # dependency is less than perfectly desirable, the cost is low, and the # syntax is obtuse enough to justify its inclusion. metadata = Puppet::ModuleTool::Metadata.new.update( 'name' => name, 'version' => '0.1.0', 'dependencies' => [ { 'name' => 'puppetlabs-stdlib', 'version_requirement' => '>= 1.0.0' } ] ) rescue ArgumentError msg = _("Could not generate directory %{name}, you must specify a dash-separated username and module name.") % { name: name.inspect } raise ArgumentError, msg, $!.backtrace end dest = Puppet::ModuleTool::Generate.destination(metadata) result = Puppet::ModuleTool::Generate.generate(metadata, options[:skip_interview]) path = dest.relative_path_from(Pathname.pwd) puts _("Finished; module generated in %{path}.") % { path: path } result.join("\n") end deprecate end end module Puppet::ModuleTool::Generate module_function def generate(metadata, skip_interview = false) #TRANSLATORS 'puppet module generate' is the name of the puppet command and 'Puppet Development Kit' is the name of the software package replacing this action and should not be translated. Puppet.deprecation_warning _("`puppet module generate` is deprecated and will be removed in a future release. This action has been replaced by Puppet Development Kit. For more information visit https://puppet.com/docs/pdk/latest/pdk.html.") interview(metadata) unless skip_interview destination = duplicate_skeleton(metadata) all_files = destination.basename + '**/*' return Dir[all_files.to_s] end def interview(metadata) puts _("We need to create a metadata.json file for this module. Please answer the following questions; if the question is not applicable to this module, feel free to leave it blank.") begin puts puts _("Puppet uses Semantic Versioning (semver.org) to version modules.") puts _("What version is this module? [%{version}]") % { version: metadata.version } metadata.update 'version' => user_input(metadata.version) rescue Puppet.err _("We're sorry, we could not parse that as a Semantic Version.") retry end puts puts _("Who wrote this module? [%{author}]") % { author: metadata.author } metadata.update 'author' => user_input(metadata.author) puts puts _("What license does this module code fall under? [%{license}]") % { license: metadata.license } metadata.update 'license' => user_input(metadata.license) puts puts _("How would you describe this module in a single sentence?") metadata.update 'summary' => user_input(metadata.summary) puts puts _("Where is this module's source code repository?") metadata.update 'source' => user_input(metadata.source) puts puts _("Where can others go to learn more about this module?%{project_page}") % { project_page: metadata.project_page && " [#{metadata.project_page}]" } metadata.update 'project_page' => user_input(metadata.project_page) puts puts _("Where can others go to file issues about this module?%{issues}") % { issues: metadata.issues_url && " [#{metadata.issues_url}]" } metadata.update 'issues_url' => user_input(metadata.issues_url) puts puts '-' * 40 puts metadata.to_json puts '-' * 40 puts puts _("About to generate this metadata; continue? [n/Y]") if user_input('Y') !~ /^y(es)?$/i puts _("Aborting...") exit 0 end end def user_input(default=nil) print '--> ' input = STDIN.gets.chomp.strip input = default if input == '' return input end def destination(metadata) return @dest if defined? @dest @dest = Pathname.pwd + metadata.name raise ArgumentError, _("%{destination} already exists.") % { destination: @dest } if @dest.exist? return @dest end def duplicate_skeleton(metadata) dest = destination(metadata) puts Puppet.notice _("Generating module at %{dest}...") % { dest: dest } FileUtils.cp_r skeleton_path, dest populate_templates(metadata, dest) return dest end def populate_templates(metadata, destination) Puppet.notice _("Populating templates...") formatters = { :erb => proc { |data, ctx| ERB.new(data).result(ctx) }, :template => proc { |data, _| data }, } formatters.each do |type, block| templates = destination + "**/*.#{type}" Dir.glob(templates.to_s, File::FNM_DOTMATCH).each do |erb| path = Pathname.new(erb) content = block[path.read, binding] target = path.parent + path.basename(".#{type}") target.open('w:UTF-8') { |f| f.write(content) } path.unlink end end end def skeleton_path return @path if defined? @path path = Pathname(Puppet.settings[:module_skeleton_dir]) path = Pathname(__FILE__).dirname + '../../module_tool/skeleton/templates/generator' unless path.directory? @path = path end end puppet-5.5.10/lib/puppet/face/module/install.rb0000644005276200011600000001324013417161721021253 0ustar jenkinsjenkins# encoding: UTF-8 require 'puppet/forge' require 'puppet/module_tool/install_directory' require 'pathname' Puppet::Face.define(:module, '1.0.0') do action(:install) do summary _("Install a module from the Puppet Forge or a release archive.") description <<-EOT Installs a module from the Puppet Forge or from a release archive file. Note: Module install uses MD5 checksums, which are prohibited on FIPS enabled systems. The specified module will be installed into the directory specified with the `--target-dir` option, which defaults to the first directory in the modulepath. EOT returns _("Pathname object representing the path to the installed module.") examples <<-'EOT' Install a module: $ puppet module install puppetlabs-vcsrepo Preparing to install into /etc/puppetlabs/code/modules ... Downloading from https://forgeapi.puppet.com ... Installing -- do not interrupt ... /etc/puppetlabs/code/modules └── puppetlabs-vcsrepo (v0.0.4) Install a module to a specific environment: $ puppet module install puppetlabs-vcsrepo --environment development Preparing to install into /etc/puppetlabs/code/environments/development/modules ... Downloading from https://forgeapi.puppet.com ... Installing -- do not interrupt ... /etc/puppetlabs/code/environments/development/modules └── puppetlabs-vcsrepo (v0.0.4) Install a specific module version: $ puppet module install puppetlabs-vcsrepo -v 0.0.4 Preparing to install into /etc/puppetlabs/modules ... Downloading from https://forgeapi.puppet.com ... Installing -- do not interrupt ... /etc/puppetlabs/code/modules └── puppetlabs-vcsrepo (v0.0.4) Install a module into a specific directory: $ puppet module install puppetlabs-vcsrepo --target-dir=/opt/puppetlabs/puppet/modules Preparing to install into /opt/puppetlabs/puppet/modules ... Downloading from https://forgeapi.puppet.com ... Installing -- do not interrupt ... /opt/puppetlabs/puppet/modules └── puppetlabs-vcsrepo (v0.0.4) Install a module into a specific directory and check for dependencies in other directories: $ puppet module install puppetlabs-vcsrepo --target-dir=/opt/puppetlabs/puppet/modules --modulepath /etc/puppetlabs/code/modules Preparing to install into /opt/puppetlabs/puppet/modules ... Downloading from https://forgeapi.puppet.com ... Installing -- do not interrupt ... /opt/puppetlabs/puppet/modules └── puppetlabs-vcsrepo (v0.0.4) Install a module from a release archive: $ puppet module install puppetlabs-vcsrepo-0.0.4.tar.gz Preparing to install into /etc/puppetlabs/code/modules ... Downloading from https://forgeapi.puppet.com ... Installing -- do not interrupt ... /etc/puppetlabs/code/modules └── puppetlabs-vcsrepo (v0.0.4) Install a module from a release archive and ignore dependencies: $ puppet module install puppetlabs-vcsrepo-0.0.4.tar.gz --ignore-dependencies Preparing to install into /etc/puppetlabs/code/modules ... Installing -- do not interrupt ... /etc/puppetlabs/code/modules └── puppetlabs-vcsrepo (v0.0.4) EOT arguments _("") option "--force", "-f" do summary _("Force overwrite of existing module, if any. (Implies --ignore-dependencies.)") description <<-EOT Force overwrite of existing module, if any. Implies --ignore-dependencies. EOT end option "--target-dir DIR", "-i DIR" do summary _("The directory into which modules are installed.") description <<-EOT The directory into which modules are installed; defaults to the first directory in the modulepath. Specifying this option will change the installation directory, and will use the existing modulepath when checking for dependencies. If you wish to check a different set of directories for dependencies, you must also use the `--environment` or `--modulepath` options. EOT end option "--ignore-dependencies" do summary _("Do not attempt to install dependencies. (Implied by --force.)") description <<-EOT Do not attempt to install dependencies. Implied by --force. EOT end option "--version VER", "-v VER" do summary _("Module version to install.") description <<-EOT Module version to install; can be an exact version or a requirement string, eg '>= 1.0.3'. Defaults to latest version. EOT end option '--strict-semver' do summary _('Whether version ranges should exclude pre-release versions') end when_invoked do |name, options| Puppet::ModuleTool.set_option_defaults options Puppet.notice _("Preparing to install into %{dir} ...") % { dir: options[:target_dir] } install_dir = Puppet::ModuleTool::InstallDirectory.new(Pathname.new(options[:target_dir])) Puppet::ModuleTool::Applications::Installer.run(name, install_dir, options) end when_rendering :console do |return_value, name, options| if return_value[:result] == :noop Puppet.notice _("Module %{name} %{version} is already installed.") % { name: name, version: return_value[:version] } exit 0 elsif return_value[:result] == :failure Puppet.err(return_value[:error][:multiline]) exit 1 else tree = Puppet::ModuleTool.build_tree(return_value[:graph], return_value[:install_dir]) "#{return_value[:install_dir]}\n" + Puppet::ModuleTool.format_tree(tree) end end end end puppet-5.5.10/lib/puppet/face/module/list.rb0000644005276200011600000002253413417161721020566 0ustar jenkinsjenkins# encoding: UTF-8 Puppet::Face.define(:module, '1.0.0') do action(:list) do summary _("List installed modules") description <<-HEREDOC Lists the installed puppet modules. By default, this action scans the modulepath from puppet.conf's `[main]` block; use the --modulepath option to change which directories are scanned. The output of this action includes information from the module's metadata, including version numbers and unmet module dependencies. HEREDOC returns _("hash of paths to module objects") option "--tree" do summary _("Whether to show dependencies as a tree view") end option '--strict-semver' do summary _('Whether version ranges should exclude pre-release versions') end examples <<-'EOT' List installed modules: $ puppet module list /etc/puppetlabs/code/modules ├── bodepd-create_resources (v0.0.1) ├── puppetlabs-bacula (v0.0.2) ├── puppetlabs-mysql (v0.0.1) ├── puppetlabs-sqlite (v0.0.1) └── puppetlabs-stdlib (v2.2.1) /opt/puppetlabs/puppet/modules (no modules installed) List installed modules in a tree view: $ puppet module list --tree /etc/puppetlabs/code/modules └─┬ puppetlabs-bacula (v0.0.2) ├── puppetlabs-stdlib (v2.2.1) ├─┬ puppetlabs-mysql (v0.0.1) │ └── bodepd-create_resources (v0.0.1) └── puppetlabs-sqlite (v0.0.1) /opt/puppetlabs/puppet/modules (no modules installed) List installed modules from a specified environment: $ puppet module list --environment production /etc/puppetlabs/code/modules ├── bodepd-create_resources (v0.0.1) ├── puppetlabs-bacula (v0.0.2) ├── puppetlabs-mysql (v0.0.1) ├── puppetlabs-sqlite (v0.0.1) └── puppetlabs-stdlib (v2.2.1) /opt/puppetlabs/puppet/modules (no modules installed) List installed modules from a specified modulepath: $ puppet module list --modulepath /opt/puppetlabs/puppet/modules /opt/puppetlabs/puppet/modules (no modules installed) EOT when_invoked do |options| Puppet::ModuleTool.set_option_defaults(options) environment = options[:environment_instance] { :environment => environment, :modules_by_path => environment.modules_by_path, } end when_rendering :console do |result, options| environment = result[:environment] modules_by_path = result[:modules_by_path] output = '' environment.modules_strict_semver = !!options[:strict_semver] warn_unmet_dependencies(environment) environment.modulepath.each do |path| modules = modules_by_path[path] no_mods = modules.empty? ? _(' (no modules installed)') : '' output << "#{path}#{no_mods}\n" if options[:tree] # The modules with fewest things depending on them will be the # parent of the tree. Can't assume to start with 0 dependencies # since dependencies may be cyclical. modules_by_num_requires = modules.sort_by {|m| m.required_by.size} @seen = {} tree = list_build_tree(modules_by_num_requires, [], nil, :label_unmet => true, :path => path, :label_invalid => false) else tree = [] modules.sort_by { |mod| mod.forge_name or mod.name }.each do |mod| tree << list_build_node(mod, path, :label_unmet => false, :path => path, :label_invalid => true) end end output << Puppet::ModuleTool.format_tree(tree) end output end end def warn_unmet_dependencies(environment) error_types = [:non_semantic_version, :version_mismatch, :missing] @unmet_deps = {} error_types.each do |type| @unmet_deps[type] = Hash.new do |hash, key| hash[key] = { :errors => [], :parent => nil } end end # Prepare the unmet dependencies for display on the console. environment.modules.sort_by {|mod| mod.name}.each do |mod| unmet_grouped = Hash.new { |h,k| h[k] = [] } unmet_grouped = mod.unmet_dependencies.inject(unmet_grouped) do |acc, dep| acc[dep[:reason]] << dep acc end unmet_grouped.each do |type, deps| unless deps.empty? unmet_grouped[type].sort_by { |dep| dep[:name] }.each do |dep| dep_name = dep[:name].gsub('/', '-') installed_version = dep[:mod_details][:installed_version] version_constraint = dep[:version_constraint] parent_name = dep[:parent][:name].gsub('/', '-') parent_version = dep[:parent][:version] msg = _("'%{parent_name}' (%{parent_version}) requires '%{dependency_name}' (%{dependency_version})") % { parent_name: parent_name, parent_version: parent_version, dependency_name: dep_name, dependency_version: version_constraint } @unmet_deps[type][dep[:name]][:errors] << msg @unmet_deps[type][dep[:name]][:parent] = { :name => dep[:parent][:name], :version => parent_version } @unmet_deps[type][dep[:name]][:version] = installed_version end end end end # Display unmet dependencies by category. error_display_order = [:non_semantic_version, :version_mismatch, :missing] error_display_order.each do |type| unless @unmet_deps[type].empty? @unmet_deps[type].keys.sort.each do |dep| name = dep.gsub('/', '-') errors = @unmet_deps[type][dep][:errors] version = @unmet_deps[type][dep][:version] msg = case type when :version_mismatch _("Module '%{name}' (v%{version}) fails to meet some dependencies:\n") % { name: name, version: version } when :non_semantic_version _("Non semantic version dependency %{name} (v%{version}):\n") % { name: name, version: version } else _("Missing dependency '%{name}':\n") % { name: name } end errors.each { |error_string| msg << " #{error_string}\n" } Puppet.warning msg.chomp end end end end # Prepare a list of module objects and their dependencies for print in a # tree view. # # Returns an Array of Hashes # # Example: # # [ # { # :text => "puppetlabs-bacula (v0.0.2)", # :dependencies=> [ # { :text => "puppetlabs-stdlib (v2.2.1)", :dependencies => [] }, # { # :text => "puppetlabs-mysql (v1.0.0)" # :dependencies => [ # { # :text => "bodepd-create_resources (v0.0.1)", # :dependencies => [] # } # ] # }, # { :text => "puppetlabs-sqlite (v0.0.1)", :dependencies => [] }, # ] # } # ] # # When the above data structure is passed to Puppet::ModuleTool.build_tree # you end up with something like this: # # /etc/puppetlabs/code/modules # └─┬ puppetlabs-bacula (v0.0.2) # ├── puppetlabs-stdlib (v2.2.1) # ├─┬ puppetlabs-mysql (v1.0.0) # │ └── bodepd-create_resources (v0.0.1) # └── puppetlabs-sqlite (v0.0.1) # def list_build_tree(list, ancestors=[], parent=nil, params={}) list.map do |mod| next if @seen[(mod.forge_name or mod.name)] node = list_build_node(mod, parent, params) @seen[(mod.forge_name or mod.name)] = true unless ancestors.include?(mod) node[:dependencies] ||= [] missing_deps = mod.unmet_dependencies.select do |dep| dep[:reason] == :missing end missing_deps.map do |mis_mod| str = "#{colorize(:bg_red, _('UNMET DEPENDENCY'))} #{mis_mod[:name].gsub('/', '-')} " str << "(#{colorize(:cyan, mis_mod[:version_constraint])})" node[:dependencies] << { :text => str } end node[:dependencies] += list_build_tree(mod.dependencies_as_modules, ancestors + [mod], mod, params) end node end.compact end # Prepare a module object for print in a tree view. Each node in the tree # must be a Hash in the following format: # # { :text => "puppetlabs-mysql (v1.0.0)" } # # The value of a module's :text is affected by three (3) factors: the format # of the tree, its dependency status, and the location in the modulepath # relative to its parent. # # Returns a Hash # def list_build_node(mod, parent, params) str = '' str << (mod.forge_name ? mod.forge_name.gsub('/', '-') : mod.name) str << ' (' + colorize(:cyan, mod.version ? "v#{mod.version}" : '???') + ')' unless File.dirname(mod.path) == params[:path] str << " [#{File.dirname(mod.path)}]" end if @unmet_deps[:version_mismatch].include?(mod.forge_name) if params[:label_invalid] str << ' ' + colorize(:red, _('invalid')) elsif parent.respond_to?(:forge_name) unmet_parent = @unmet_deps[:version_mismatch][mod.forge_name][:parent] if (unmet_parent[:name] == parent.forge_name && unmet_parent[:version] == "v#{parent.version}") str << ' ' + colorize(:red, _('invalid')) end end end { :text => str } end end puppet-5.5.10/lib/puppet/face/module/search.rb0000644005276200011600000000752313417161721021061 0ustar jenkinsjenkinsrequire 'puppet/util/terminal' require 'puppet/forge' Puppet::Face.define(:module, '1.0.0') do action(:search) do summary _("Search the Puppet Forge for a module.") description <<-EOT Searches a repository for modules whose names, descriptions, or keywords match the provided search term. EOT returns _("Array of module metadata hashes") examples <<-EOT Search the Puppet Forge for a module: $ puppet module search puppetlabs NAME DESCRIPTION AUTHOR KEYWORDS bacula This is a generic Apache module @puppetlabs backups EOT arguments _("") when_invoked do |term, options| Puppet::ModuleTool.set_option_defaults options Puppet::ModuleTool::Applications::Searcher.new(term, Puppet::Forge.new(options[:module_repository] || Puppet[:module_repository], options[:strict_semver]), options).run end when_rendering :console do |results, term, options| if results[:result] == :failure raise results[:error][:multiline] end return _("No results found for '%{term}'.") % { term: term } if results[:answers].empty? padding = ' ' headers = { 'full_name' => 'NAME', 'desc' => 'DESCRIPTION', 'author' => 'AUTHOR', 'tag_list' => 'KEYWORDS', } min_widths = Hash[ *headers.map { |k,v| [k, v.length] }.flatten ] min_widths['full_name'] = min_widths['author'] = 12 min_width = min_widths.inject(0) { |sum,pair| sum + pair.last } + (padding.length * (headers.length - 1)) terminal_width = [Puppet::Util::Terminal.width, min_width].max columns = results[:answers].inject(min_widths) do |hash, result| deprecated_buffer = result['deprecated_at'].nil? ? 0 : 11 # ' DEPRECATED'.length { 'full_name' => [ hash['full_name'], result['full_name'].length + deprecated_buffer ].max, 'desc' => [ hash['desc'], result['desc'].length ].max, 'author' => [ hash['author'], "@#{result['author']}".length ].max, 'tag_list' => [ hash['tag_list'], result['tag_list'].join(' ').length ].max, } end flex_width = terminal_width - columns['full_name'] - columns['author'] - (padding.length * (headers.length - 1)) tag_lists = results[:answers].map { |r| r['tag_list'] } while (columns['tag_list'] > flex_width / 3) longest_tag_list = tag_lists.sort_by { |tl| tl.join(' ').length }.last break if [ [], [term] ].include? longest_tag_list longest_tag_list.delete(longest_tag_list.sort_by { |t| t == term ? -1 : t.length }.last) columns['tag_list'] = tag_lists.map { |tl| tl.join(' ').length }.max end columns['tag_list'] = [ flex_width / 3, tag_lists.map { |tl| tl.join(' ').length }.max, ].max columns['desc'] = flex_width - columns['tag_list'] format = %w{full_name desc author tag_list}.map do |k| "%-#{ [ columns[k], min_widths[k] ].max }s" end.join(padding) + "\n" highlight = proc do |s| s = s.gsub(term, colorize(:green, term)) s = s.gsub(term.gsub('/', '-'), colorize(:green, term.gsub('/', '-'))) if term =~ /\// s = s.gsub(' DEPRECATED', colorize(:red, ' DEPRECATED')) s end format % [ headers['full_name'], headers['desc'], headers['author'], headers['tag_list'] ] + results[:answers].map do |match| name, desc, author, keywords = %w{full_name desc author tag_list}.map { |k| match[k] } name += ' DEPRECATED' unless match['deprecated_at'].nil? desc = desc[0...(columns['desc'] - 3)] + '...' if desc.length > columns['desc'] highlight[format % [ name.sub('/', '-'), desc, "@#{author}", [keywords].flatten.join(' ') ]] end.join end end end puppet-5.5.10/lib/puppet/face/module/uninstall.rb0000644005276200011600000000660513417161721021625 0ustar jenkinsjenkinsPuppet::Face.define(:module, '1.0.0') do action(:uninstall) do summary _("Uninstall a puppet module.") description <<-EOT Uninstalls a puppet module from the modulepath (or a specific target directory). Note: Module uninstall uses MD5 checksums, which are prohibited on FIPS enabled systems. EOT returns _("Hash of module objects representing uninstalled modules and related errors.") examples <<-'EOT' Uninstall a module: $ puppet module uninstall puppetlabs-ssh Removed /etc/puppetlabs/code/modules/ssh (v1.0.0) Uninstall a module from a specific directory: $ puppet module uninstall puppetlabs-ssh --modulepath /opt/puppetlabs/puppet/modules Removed /opt/puppetlabs/puppet/modules/ssh (v1.0.0) Uninstall a module from a specific environment: $ puppet module uninstall puppetlabs-ssh --environment development Removed /etc/puppetlabs/code/environments/development/modules/ssh (v1.0.0) Uninstall a specific version of a module: $ puppet module uninstall puppetlabs-ssh --version 2.0.0 Removed /etc/puppetlabs/code/modules/ssh (v2.0.0) EOT arguments _("") option "--force", "-f" do summary _("Force uninstall of an installed module.") description <<-EOT Force the uninstall of an installed module even if there are local changes or the possibility of causing broken dependencies. EOT end option "--ignore-changes", "-c" do summary _("Ignore any local changes made. (Implied by --force.)") description <<-EOT Uninstall an installed module even if there are local changes to it. (Implied by --force.) EOT end option "--version=" do summary _("The version of the module to uninstall") description <<-EOT The version of the module to uninstall. When using this option, a module matching the specified version must be installed or else an error is raised. EOT end option '--strict-semver' do summary _('Whether version ranges should exclude pre-release versions') end when_invoked do |name, options| name = name.gsub('/', '-') Puppet::ModuleTool.set_option_defaults options message = if options[:version] module_version = colorize(:cyan, options[:version].sub(/^(?=\d)/, 'v')) _("Preparing to uninstall '%{name}' (%{module_version}) ...") % { name: name, module_version: module_version } else _("Preparing to uninstall '%{name}' ...") % { name: name } end Puppet.notice message Puppet::ModuleTool::Applications::Uninstaller.run(name, options) end when_rendering :console do |return_value| if return_value[:result] == :failure Puppet.err(return_value[:error][:multiline]) exit 1 else mod = return_value[:affected_modules].first message = if mod.version module_version = colorize(:cyan, mod.version.to_s.sub(/^(?=\d)/, 'v')) _("Removed '%{name}' (%{module_version}) from %{path}") % { name: return_value[:module_name], module_version: module_version, path: mod.modulepath } else _("Removed '%{name}' from %{path}") % { name: return_value[:module_name], path: mod.modulepath } end message end end end end puppet-5.5.10/lib/puppet/face/module/upgrade.rb0000644005276200011600000000553413417161721021243 0ustar jenkinsjenkins# encoding: UTF-8 Puppet::Face.define(:module, '1.0.0') do action(:upgrade) do summary _("Upgrade a puppet module.") description <<-EOT Upgrades a puppet module. Note: Module upgrade uses MD5 checksums, which are prohibited on FIPS enabled systems. EOT returns "Hash" examples <<-EOT upgrade an installed module to the latest version $ puppet module upgrade puppetlabs-apache /etc/puppetlabs/puppet/modules └── puppetlabs-apache (v1.0.0 -> v2.4.0) upgrade an installed module to a specific version $ puppet module upgrade puppetlabs-apache --version 2.1.0 /etc/puppetlabs/puppet/modules └── puppetlabs-apache (v1.0.0 -> v2.1.0) upgrade an installed module for a specific environment $ puppet module upgrade puppetlabs-apache --environment test /etc/puppetlabs/code/environments/test/modules └── puppetlabs-apache (v1.0.0 -> v2.4.0) EOT arguments _("") option "--force", "-f" do summary _("Force upgrade of an installed module. (Implies --ignore-dependencies.)") description <<-EOT Force the upgrade of an installed module even if there are local changes or the possibility of causing broken dependencies. Implies --ignore-dependencies. EOT end option "--ignore-dependencies" do summary _("Do not attempt to install dependencies. (Implied by --force.)") description <<-EOT Do not attempt to install dependencies. Implied by --force. EOT end option "--ignore-changes", "-c" do summary _("Ignore and overwrite any local changes made. (Implied by --force.)") description <<-EOT Upgrade an installed module even if there are local changes to it. (Implied by --force.) EOT end option "--version=" do summary _("The version of the module to upgrade to.") description <<-EOT The version of the module to upgrade to. EOT end option '--strict-semver' do summary _('Whether version ranges should exclude pre-release versions') end when_invoked do |name, options| name = name.gsub('/', '-') Puppet.notice _("Preparing to upgrade '%{name}' ...") % { name: name } Puppet::ModuleTool.set_option_defaults options Puppet::ModuleTool::Applications::Upgrader.new(name, options).run end when_rendering :console do |return_value| if return_value[:result] == :noop Puppet.notice return_value[:error][:multiline] exit 0 elsif return_value[:result] == :failure Puppet.err(return_value[:error][:multiline]) exit 1 else tree = Puppet::ModuleTool.build_tree(return_value[:graph], return_value[:base_dir]) "#{return_value[:base_dir]}\n" + Puppet::ModuleTool.format_tree(tree) end end end end puppet-5.5.10/lib/puppet/face/node.rb0000644005276200011600000000325713417161721017254 0ustar jenkinsjenkinsrequire 'puppet/indirector/face' Puppet::Indirector::Face.define(:node, '0.0.1') do copyright "Puppet Inc.", 2011 license _("Apache 2 license; see COPYING") summary _("View and manage node definitions.") description <<-'EOT' This subcommand interacts with node objects, which are used by Puppet to build a catalog. A node object consists of the node's facts, environment, node parameters (exposed in the parser as top-scope variables), and classes. EOT deactivate_action(:destroy) deactivate_action(:search) deactivate_action(:save) find = get_action(:find) find.summary _("Retrieve a node object.") find.arguments _("") #TRANSLATORS the following are specific names and should not be translated `classes`, `environment`, `expiration`, `name`, `parameters`, Puppet::Node find.returns _(<<-'EOT') A hash containing the node's `classes`, `environment`, `expiration`, `name`, `parameters` (its facts, combined with any ENC-set parameters), and `time`. When used from the Ruby API: a Puppet::Node object. RENDERING ISSUES: Rendering as string and json are currently broken; node objects can only be rendered as yaml. EOT find.examples <<-'EOT' Retrieve an "empty" (no classes, no ENC-imposed parameters, and an environment of "production") node: $ puppet node find somenode.puppetlabs.lan --terminus plain --render-as yaml Retrieve a node using the puppet master's configured ENC: $ puppet node find somenode.puppetlabs.lan --terminus exec --run_mode master --render-as yaml Retrieve the same node from the puppet master: $ puppet node find somenode.puppetlabs.lan --terminus rest --render-as yaml EOT end puppet-5.5.10/lib/puppet/face/node/0000755005276200011600000000000013417162176016725 5ustar jenkinsjenkinspuppet-5.5.10/lib/puppet/face/node/clean.rb0000644005276200011600000000607613417161721020340 0ustar jenkinsjenkinsPuppet::Face.define(:node, '0.0.1') do action(:clean) do summary _("Clean up signed certs, cached facts, node objects, and reports for a node stored by the puppetmaster") arguments _(" [ ...]") description <<-'EOT' Cleans up the following information a puppet master knows about a node: - ($vardir/ssl/ca/signed/node.domain.pem) - ($vardir/yaml/facts/node.domain.yaml) - ($vardir/yaml/node/node.domain.yaml) - ($vardir/reports/node.domain) EOT when_invoked do |*args| nodes = args[0..-2] options = args.last raise _("At least one node should be passed") if nodes.empty? || nodes == options # This seems really bad; run_mode should be set as part of a class # definition, and should not be modifiable beyond that. This is one of # the only places left in the code that tries to manipulate it. Other # parts of code that handle certificates behave differently if the # run_mode is master. Those other behaviors are needed for cleaning the # certificates correctly. Puppet.settings.preferred_run_mode = "master" if Puppet::SSL::CertificateAuthority.ca? Puppet::SSL::Host.ca_location = :local else Puppet::SSL::Host.ca_location = :none end Puppet::Node::Facts.indirection.terminus_class = :yaml Puppet::Node::Facts.indirection.cache_class = :yaml Puppet::Node.indirection.terminus_class = :yaml Puppet::Node.indirection.cache_class = :yaml nodes.each { |node| cleanup(node.downcase) } end end def cleanup(node) clean_cert(node) clean_cached_facts(node) clean_cached_node(node) clean_reports(node) end # clean signed cert for +host+ def clean_cert(node) if Puppet::SSL::CertificateAuthority.ca? Puppet::Face[:ca, :current].revoke(node) Puppet::Face[:ca, :current].destroy(node) Puppet.info _("%{node} certificates removed from ca") % { node: node } else Puppet.info _("Not managing %{node} certs as this host is not a CA") % { node: node } end end # clean facts for +host+ def clean_cached_facts(node) Puppet::Node::Facts.indirection.destroy(node) Puppet.info _("%{node}'s facts removed") % { node: node } end # clean cached node +host+ def clean_cached_node(node) Puppet::Node.indirection.destroy(node) Puppet.info _("%{node}'s cached node removed") % { node: node } end # clean node reports for +host+ def clean_reports(node) Puppet::Transaction::Report.indirection.destroy(node) Puppet.info _("%{node}'s reports removed") % { node: node } end def environment @environment ||= Puppet.lookup(:current_environment) end def type_is_ensurable(resource) if (type = Puppet::Type.type(resource.restype)) && type.validattr?(:ensure) return true else type = environment.known_resource_types.find_definition(resource.restype) return true if type && type.arguments.keys.include?('ensure') end return false end end puppet-5.5.10/lib/puppet/face/parser.rb0000644005276200011600000001452713417161721017625 0ustar jenkinsjenkinsrequire 'puppet/face' require 'puppet/parser' Puppet::Face.define(:parser, '0.0.1') do copyright "Puppet Inc.", 2014 license _("Apache 2 license; see COPYING") summary _("Interact directly with the parser.") action :validate do summary _("Validate the syntax of one or more Puppet manifests.") arguments _("[] [ ...]") returns _("Nothing, or the first syntax error encountered.") description <<-'EOT' This action validates Puppet DSL syntax without compiling a catalog or syncing any resources. If no manifest files are provided, it will validate the default site manifest. When validating multiple issues per file are reported up to the settings of max_error, and max_warnings. The processing stops after having reported issues for the first encountered file with errors. EOT examples <<-'EOT' Validate the default site manifest at /etc/puppetlabs/puppet/manifests/site.pp: $ puppet parser validate Validate two arbitrary manifest files: $ puppet parser validate init.pp vhost.pp Validate from STDIN: $ cat init.pp | puppet parser validate EOT when_invoked do |*args| args.pop files = args if files.empty? if not STDIN.tty? Puppet[:code] = STDIN.read validate_manifest else manifest = Puppet.lookup(:current_environment).manifest files << manifest Puppet.notice _("No manifest specified. Validating the default manifest %{manifest}") % { manifest: manifest } end end missing_files = [] files.each do |file| if Puppet::FileSystem.exist?(file) validate_manifest(file) else missing_files << file end end unless missing_files.empty? raise Puppet::Error, _("One or more file(s) specified did not exist:\n%{files}") % { files: missing_files.collect {|f| " " * 3 + f + "\n"} } end nil end end action (:dump) do summary _("Outputs a dump of the internal parse tree for debugging") arguments "[--format ] [--pretty] { -e | [ ...] } " returns _("A dump of the resulting AST model unless there are syntax or validation errors.") description <<-'EOT' This action parses and validates the Puppet DSL syntax without compiling a catalog or syncing any resources. The output format can be controlled using the --format where: * 'old' is the default, but now deprecated format which is not API. * 'pn' is the Puppet Extended S-Expression Notation. * 'json' outputs the same graph as 'pn' but with JSON syntax. The output will be "pretty printed" when the option --pretty is given together with --format 'pn' or 'json'. This option has no effect on the 'old' format. The command accepts one or more manifests (.pp) files, or an -e followed by the puppet source text. If no arguments are given, the stdin is read (unless it is attached to a terminal) The output format of the dumped tree is intended for debugging purposes and is not API, it may change from time to time. EOT option "--e " + _("") do default_to { nil } summary _("dump one source expression given on the command line.") end option("--[no-]validate") do summary _("Whether or not to validate the parsed result, if no-validate only syntax errors are reported") end option('--format ' + _('')) do summary _("Get result in 'old' (deprecated format), 'pn' (new format), or 'json' (new format in JSON).") end option('--pretty') do summary _('Pretty print output. Only applicable together with --format pn or json') end when_invoked do |*args| require 'puppet/pops' options = args.pop if options[:e] dump_parse(options[:e], 'command-line-string', options, false) elsif args.empty? if ! STDIN.tty? dump_parse(STDIN.read, 'stdin', options, false) else raise Puppet::Error, _("No input to parse given on command line or stdin") end else files = args available_files = files.select do |file| Puppet::FileSystem.exist?(file) end missing_files = files - available_files dumps = available_files.collect do |file| dump_parse(Puppet::FileSystem.read(file, :encoding => 'utf-8'), file, options) end.join("") if missing_files.empty? dumps else dumps + _("One or more file(s) specified did not exist:\n") + missing_files.collect { |f| " #{f}" }.join("\n") end end end end def dump_parse(source, filename, options, show_filename = true) output = "" evaluating_parser = Puppet::Pops::Parser::EvaluatingParser.new begin if options[:validate] parse_result = evaluating_parser.parse_string(source, filename) else # side step the assert_and_report step parse_result = evaluating_parser.parser.parse_string(source) end if show_filename output << "--- #{filename}" end fmt = options[:format] if fmt.nil? || fmt == 'old' output << Puppet::Pops::Model::ModelTreeDumper.new.dump(parse_result) << "\n" else require 'puppet/pops/pn' pn = Puppet::Pops::Model::PNTransformer.transform(parse_result) case fmt when 'json' options[:pretty] ? JSON.pretty_unparse(pn.to_data) : JSON.dump(pn.to_data) else pn.format(options[:pretty] ? Puppet::Pops::PN::Indent.new(' ') : nil, output) end end rescue Puppet::ParseError => detail if show_filename Puppet.err("--- #{filename}") end Puppet.err(detail.message) "" end end # @api private def validate_manifest(manifest = nil) env = Puppet.lookup(:current_environment) loaders = Puppet::Pops::Loaders.new(env) Puppet.override( {:loaders => loaders } , _('For puppet parser validate')) do begin validation_environment = manifest ? env.override_with(:manifest => manifest) : env validation_environment.check_for_reparse validation_environment.known_resource_types.clear rescue => detail Puppet.log_exception(detail) exit(1) end end end end puppet-5.5.10/lib/puppet/face/plugin.rb0000644005276200011600000000355413417161721017625 0ustar jenkinsjenkinsrequire 'puppet/face' require 'puppet/configurer/plugin_handler' Puppet::Face.define(:plugin, '0.0.1') do copyright "Puppet Inc.", 2011 license _("Apache 2 license; see COPYING") summary _("Interact with the Puppet plugin system.") description <<-'EOT' This subcommand provides network access to the puppet master's store of plugins. The puppet master serves Ruby code collected from the `lib` directories of its modules. These plugins can be used on agent nodes to extend Facter and implement custom types and providers. Plugins are normally downloaded by puppet agent during the course of a run. EOT action :download do summary _("Download plugins from the puppet master.") description <<-'EOT' Downloads plugins from the configured puppet master. Any plugins downloaded in this way will be used in all subsequent Puppet activity. This action modifies files on disk. EOT returns _(<<-'EOT') A list of the files downloaded, or a confirmation that no files were downloaded. When used from the Ruby API, this action returns an array of the files downloaded, which will be empty if none were retrieved. EOT examples <<-'EOT' Retrieve plugins from the puppet master: $ puppet plugin download Retrieve plugins from the puppet master (API example): $ Puppet::Face[:plugin, '0.0.1'].download EOT when_invoked do |options| remote_environment_for_plugins = Puppet::Node::Environment.remote(Puppet[:environment]) handler = Puppet::Configurer::PluginHandler.new() handler.download_plugins(remote_environment_for_plugins) end when_rendering :console do |value| if value.empty? then _("No plugins downloaded.") else _("Downloaded these plugins: %{plugins}") % { plugins: value.join(', ') } end end end end puppet-5.5.10/lib/puppet/face/report.rb0000644005276200011600000000344613417161721017642 0ustar jenkinsjenkinsrequire 'puppet/indirector/face' Puppet::Indirector::Face.define(:report, '0.0.1') do copyright "Puppet Inc.", 2011 license _("Apache 2 license; see COPYING") summary _("Create, display, and submit reports.") save = get_action(:save) save.summary _("API only: submit a report.") save.arguments _("") save.returns _("Nothing.") save.examples <<-'EOT' From the implementation of `puppet report submit` (API example): begin Puppet::Transaction::Report.indirection.terminus_class = :rest Puppet::Face[:report, "0.0.1"].save(report) Puppet.notice "Uploaded report for #{report.name}" rescue => detail Puppet.log_exception(detail, "Could not send report: #{detail}") end EOT action(:submit) do summary _("API only: submit a report with error handling.") description <<-'EOT' API only: Submits a report to the puppet master. This action is essentially a shortcut and wrapper for the `save` action with the `rest` terminus, and provides additional details in the event of a failure. EOT arguments _("") examples <<-'EOT' API example: # ... report = Puppet::Face[:catalog, '0.0.1'].apply Puppet::Face[:report, '0.0.1'].submit(report) return report EOT when_invoked do |report, options| begin Puppet::Transaction::Report.indirection.terminus_class = :rest Puppet::Face[:report, "0.0.1"].save(report) Puppet.notice _("Uploaded report for %{name}") % { name: report.name } rescue => detail Puppet.log_exception(detail, _("Could not send report: %{detail}") % { detail: detail }) end end end deactivate_action(:find) deactivate_action(:search) deactivate_action(:destroy) end puppet-5.5.10/lib/puppet/face/resource.rb0000644005276200011600000000321613417161721020151 0ustar jenkinsjenkinsrequire 'puppet/indirector/face' Puppet::Indirector::Face.define(:resource, '0.0.1') do copyright "Puppet Inc.", 2011 license _("Apache 2 license; see COPYING") summary _("API only: interact directly with resources via the RAL.") description <<-'EOT' API only: this face provides a Ruby API with functionality similar to the puppet resource subcommand. EOT deactivate_action(:destroy) search = get_action(:search) search.summary _("API only: get all resources of a single type.") search.arguments _("") search.returns _("An array of Puppet::Resource objects.") search.examples <<-'EOT' Get a list of all user resources (API example): all_users = Puppet::Face[:resource, '0.0.1'].search("user") EOT find = get_action(:find) find.summary _("API only: get a single resource.") find.arguments _("/") find.returns _("A Puppet::Resource object.") find.examples <<-'EOT' Print information about a user on this system (API example): puts Puppet::Face[:resource, '0.0.1'].find("user/luke").to_json EOT save = get_action(:save) save.summary _("API only: create a new resource.") save.description <<-EOT API only: creates a new resource. EOT save.arguments _("<resource_object>") save.returns _("The same resource object passed as an argument.") save.examples <<-'EOT' Create a new file resource (API example): my_resource = Puppet::Resource.new( :file, "/tmp/demonstration", :parameters => {:ensure => :present, :content => "some\nthing\n"} ) Puppet::Face[:resource, '0.0.1'].save(my_resource) EOT end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/face/status.rb�������������������������������������������������������������0000644�0052762�0001160�00000003611�13417161721�017644� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/face' Puppet::Indirector::Face.define(:status, '0.0.1') do copyright "Puppet Inc.", 2011 license _("Apache 2 license; see COPYING") summary _("View puppet server status.") deactivate_action(:destroy) deactivate_action(:save) deactivate_action(:search) find = get_action(:find) find.default = true find.summary _("Check status of puppet master server.") #TRANSLATORS the string 'Puppet::Status' is a Puppet language object and should not be translated find.returns _(<<-'EOT') A "true" response or a low-level connection error. When used from the Ruby API: returns a Puppet::Status object. EOT find.description <<-'EOT' Checks whether a Puppet server is properly receiving and processing HTTP requests. This action is only useful when used with '--terminus rest'; when invoked with the `local` terminus, `find` will always return true. Over REST, this action will query the configured puppet master by default. To query other servers, including puppet agent nodes started with the <--listen> option, you can set the global <--server> and <--masterport> options on the command line; note that agent nodes listen on port 8139. EOT find.short_description <<-EOT Checks whether a Puppet server is properly receiving and processing HTTP requests. This action is only useful when used with '--terminus rest', and will always return true when invoked locally. EOT find.notes <<-'EOT' This action requires that the server's `auth.conf` file allow find access to the `status` REST terminus. Puppet agent does not use this facility, and it is turned off by default. See <https://puppet.com/docs/puppet/latest/config_file_auth.html> for more details. EOT find.examples <<-'EOT' Check the status of the configured puppet master: $ puppet status find --terminus rest EOT deprecate end �����������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/face/ca.rb�����������������������������������������������������������������0000644�0052762�0001160�00000021063�13417161721�016705� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/face' Puppet::Face.define(:ca, '0.1.0') do copyright "Puppet Inc.", 2011 license _("Apache 2 license; see COPYING") summary _("Local Puppet Certificate Authority management.") description <<-TEXT This provides local management of the Puppet Certificate Authority. You can use this subcommand to sign outstanding certificate requests, list and manage local certificates, and inspect the state of the CA. TEXT action :list do summary _("List certificates and/or certificate requests.") description <<-TEXT This will list the current certificates and certificate signing requests in the Puppet CA. You will also get the fingerprint, and any certificate verification failure reported. TEXT option "--[no-]all" do summary _("Include all certificates and requests.") end option "--[no-]pending" do summary _("Include pending certificate signing requests.") end option "--[no-]signed" do summary _("Include signed certificates.") end option "--digest " + _("ALGORITHM") do summary _("The hash algorithm to use when displaying the fingerprint") end option "--subject " + _("PATTERN") do summary _("Only list if the subject matches PATTERN.") description <<-TEXT Only include certificates or requests where subject matches PATTERN. PATTERN is interpreted as a regular expression, allowing complex filtering of the content. TEXT end when_invoked do |options| #TRANSLATORS "CA" stands for "certificate authority" raise _("Not a CA") unless Puppet::SSL::CertificateAuthority.ca? unless ca = Puppet::SSL::CertificateAuthority.instance #TRANSLATORS "CA" stands for "certificate authority" raise _("Unable to fetch the CA") end Puppet::SSL::Host.ca_location = :only pattern = options[:subject].nil? ? nil : Regexp.new(options[:subject], Regexp::IGNORECASE) pending = options[:pending].nil? ? options[:all] : options[:pending] signed = options[:signed].nil? ? options[:all] : options[:signed] # By default we list pending, so if nothing at all was requested... unless pending or signed then pending = true end hosts = [] pending and hosts += ca.waiting? signed and hosts += ca.list pattern and hosts = hosts.select {|hostname| pattern.match hostname } hosts.sort.map {|host| Puppet::SSL::Host.new(host) } end when_rendering :console do |hosts, options| unless ca = Puppet::SSL::CertificateAuthority.instance raise _("Unable to fetch the CA") end length = hosts.map{|x| x.name.length }.max.to_i + 1 hosts.map do |host| name = host.name.ljust(length) if host.certificate_request then " #{name} #{host.certificate_request.digest(options[:digest])}" else begin ca.verify(host.name) "+ #{name} #{host.certificate.digest(options[:digest])}" rescue Puppet::SSL::CertificateAuthority::CertificateVerificationError => e "- #{name} #{host.certificate.digest(options[:digest])} (#{e.to_s})" end end end.join("\n") end end action :destroy do summary _("Destroy named certificate or pending certificate request.") when_invoked do |host, options| raise _("Not a CA") unless Puppet::SSL::CertificateAuthority.ca? unless ca = Puppet::SSL::CertificateAuthority.instance raise _("Unable to fetch the CA") end Puppet::SSL::Host.ca_location = :local ca.destroy host end end action :revoke do summary _("Add certificate to certificate revocation list.") when_invoked do |host, options| raise _("Not a CA") unless Puppet::SSL::CertificateAuthority.ca? unless ca = Puppet::SSL::CertificateAuthority.instance raise _("Unable to fetch the CA") end Puppet::SSL::Host.ca_location = :only begin ca.revoke host rescue ArgumentError => e # This is a bit naff, but it makes the behaviour consistent with the # destroy action. The underlying tools could be nicer for that sort # of thing; they have fairly inconsistent reporting of failures. raise unless e.to_s =~ /Could not find a serial number for / _("Nothing was revoked") end end end action :generate do summary _("Generate a certificate for a named client.") option "--dns-alt-names " + _("NAMES") do summary _("Additional DNS names to add to the certificate request") description Puppet.settings.setting(:dns_alt_names).desc end when_invoked do |host, options| raise _("Not a CA") unless Puppet::SSL::CertificateAuthority.ca? unless ca = Puppet::SSL::CertificateAuthority.instance raise _("Unable to fetch the CA") end Puppet::SSL::Host.ca_location = :local begin ca.generate(host, :dns_alt_names => options[:dns_alt_names]) rescue RuntimeError => e if e.to_s =~ /already has a requested certificate/ _("%{host} already has a certificate request; use sign instead") % { host: host } else raise end rescue ArgumentError => e if e.to_s =~ /A Certificate already exists for / _("%{host} already has a certificate") % { host: host } else raise end end end end action :sign do summary _("Sign an outstanding certificate request.") option("--[no-]allow-dns-alt-names") do summary _("Whether or not to accept DNS alt names in the certificate request") end when_invoked do |host, options| raise _("Not a CA") unless Puppet::SSL::CertificateAuthority.ca? unless ca = Puppet::SSL::CertificateAuthority.instance raise _("Unable to fetch the CA") end Puppet::SSL::Host.ca_location = :only begin signing_options = options.select { |k,_| [:allow_dns_alt_names, :allow_authorization_extensions].include?(k) } ca.sign(host, signing_options) rescue ArgumentError => e if e.to_s =~ /Could not find certificate request/ e.to_s else raise end end end end action :print do summary _("Print the full-text version of a host's certificate.") when_invoked do |host, options| raise _("Not a CA") unless Puppet::SSL::CertificateAuthority.ca? unless ca = Puppet::SSL::CertificateAuthority.instance raise _("Unable to fetch the CA") end Puppet::SSL::Host.ca_location = :only ca.print host end end action :fingerprint do #TRANSLATORS "DIGEST" refers to a hash algorithm summary _("Print the DIGEST (defaults to the signing algorithm) fingerprint of a host's certificate.") option "--digest " + _("ALGORITHM") do summary _("The hash algorithm to use when displaying the fingerprint") end when_invoked do |host, options| #TRANSLATORS "CA" stands for "certificate authority" raise _("Not a CA") unless Puppet::SSL::CertificateAuthority.ca? unless Puppet::SSL::CertificateAuthority.instance #TRANSLATORS "CA" stands for "certificate authority" raise _("Unable to fetch the CA") end Puppet::SSL::Host.ca_location = :only if cert = (Puppet::SSL::Certificate.indirection.find(host) || Puppet::SSL::CertificateRequest.indirection.find(host)) cert.digest(options[:digest]).to_s else nil end end end action :verify do summary "Verify the named certificate against the local CA certificate." when_invoked do |host, options| #TRANSLATORS "CA" stands for "certificate authority" raise _("Not a CA") unless Puppet::SSL::CertificateAuthority.ca? unless ca = Puppet::SSL::CertificateAuthority.instance #TRANSLATORS "CA" stands for "certificate authority" raise _("Unable to fetch the CA") end Puppet::SSL::Host.ca_location = :only begin ca.verify host { :host => host, :valid => true } rescue ArgumentError => e raise unless e.to_s =~ /Could not find a certificate for/ { :host => host, :valid => false, :error => e.to_s } rescue Puppet::SSL::CertificateAuthority::CertificateVerificationError => e { :host => host, :valid => false, :error => e.to_s } end end when_rendering :console do |value| if value[:valid] nil else _("Could not verify %{host}: %{error}") % { host: value[:host], error: value[:error] } end end end deprecate end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/face/certificate.rb��������������������������������������������������������0000644�0052762�0001160�00000013543�13417161721�020610� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/face' require 'puppet/ssl/host' Puppet::Indirector::Face.define(:certificate, '0.0.1') do copyright "Puppet Inc.", 2011 license _("Apache 2 license; see COPYING") summary _("Provide access to the CA for certificate management.") description <<-EOT This subcommand interacts with a local or remote Puppet certificate authority. Currently, its behavior is not a full superset of `puppet cert`; specifically, it is unable to mimic puppet cert's "clean" option, and its "generate" action submits a CSR rather than creating a signed certificate. EOT option "--ca-location " + _("LOCATION") do required summary _("Which certificate authority to use (local or remote).") description <<-EOT Whether to act on the local certificate authority or one provided by a remote puppet master. Allowed values are 'local' and 'remote.' This option is required. EOT before_action do |action, args, options| unless [:remote, :local, :only].include? options[:ca_location].to_sym raise ArgumentError, _("Valid values for ca-location are 'remote', 'local', 'only'.") end Puppet::SSL::Host.ca_location = options[:ca_location].to_sym end end action :generate do summary _("Generate a new certificate signing request.") arguments _("<host>") returns "Nothing." description <<-EOT Generates and submits a certificate signing request (CSR) for the specified host. This CSR will then have to be signed by a user with the proper authorization on the certificate authority. Puppet agent usually handles CSR submission automatically. This action is primarily useful for requesting certificates for individual users and external applications. EOT examples <<-EOT Request a certificate for "somenode" from the site's CA: $ puppet certificate generate somenode.puppetlabs.lan --ca-location remote EOT # Duplicate the option here explicitly to distinguish if it was passed arg # us vs. set in the config file. option "--dns-alt-names "+ _("NAMES") do summary _("Additional DNS names to add to the certificate request") description Puppet.settings.setting(:dns_alt_names).desc end when_invoked do |name, options| host = Puppet::SSL::Host.new(name) # We have a weird case where we have --dns_alt_names from Puppet, but # this option is --dns-alt-names. Until we can get rid of --dns-alt-names # or do a global tr('-', '_'), we have to support both. # In supporting both, we'll use Puppet[:dns_alt_names] if specified on # command line. We'll use options[:dns_alt_names] if specified on # command line. If both specified, we'll fail. # jeffweiss 17 april 2012 global_setting_from_cli = Puppet.settings.set_by_cli?(:dns_alt_names) == true raise ArgumentError, _("Can't specify both --dns_alt_names and --dns-alt-names") if options[:dns_alt_names] and global_setting_from_cli options[:dns_alt_names] = Puppet[:dns_alt_names] if global_setting_from_cli # If dns_alt_names are specified via the command line, we will always add # them. Otherwise, they will default to the config file setting iff this # cert is for the host we're running on. unless Puppet::FileSystem.exist?(Puppet[:hostcert]) Puppet.push_context({:ssl_host => host}) end host.generate_certificate_request(:dns_alt_names => options[:dns_alt_names]) end end action :list do summary _("List all certificate signing requests.") returns <<-EOT An array of #inspect output from CSR objects. This output is currently messy, but does contain the names of nodes requesting certificates. This action returns #inspect strings even when used from the Ruby API. EOT when_invoked do |options| Puppet::SSL::Host.indirection.search("*", { :for => :certificate_request, }).map { |h| h.inspect } end end action :sign do summary _("Sign a certificate signing request for HOST.") arguments _("<host>") returns <<-EOT A string that appears to be (but isn't) an x509 certificate. EOT examples <<-EOT Sign somenode.puppetlabs.lan's certificate: $ puppet certificate sign somenode.puppetlabs.lan --ca-location remote EOT option("--[no-]allow-dns-alt-names") do summary _("Whether or not to accept DNS alt names in the certificate request") end when_invoked do |name, options| host = Puppet::SSL::Host.new(name) if Puppet::SSL::Host.ca_location == :remote if options[:allow_dns_alt_names] raise ArgumentError, _("--allow-dns-alt-names may not be specified with a remote CA") end host.desired_state = 'signed' Puppet::SSL::Host.indirection.save(host) else # We have to do this case manually because we need to specify # allow_dns_alt_names. unless ca = Puppet::SSL::CertificateAuthority.instance raise ArgumentError, _("This process is not configured as a certificate authority") end signing_options = {allow_dns_alt_names: options[:allow_dns_alt_names]} ca.sign(name, signing_options) end end end # Indirector action doc overrides find = get_action(:find) find.summary _("Retrieve a certificate.") find.arguments _("<host>") find.render_as = :s find.returns <<-EOT An x509 SSL certificate. Note that this action has a side effect of caching a copy of the certificate in Puppet's `ssldir`. EOT destroy = get_action(:destroy) destroy.summary _("Delete a certificate.") destroy.arguments _("<host>") destroy.returns "Nothing." destroy.description <<-EOT Deletes a certificate. This action currently only works on the local CA. EOT deactivate_action(:search) deactivate_action(:save) deprecate end �������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/face/certificate_request.rb������������������������������������������������0000644�0052762�0001160�00000003345�13417161721�022357� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/face' Puppet::Indirector::Face.define(:certificate_request, '0.0.1') do copyright "Puppet Inc.", 2011 license "Apache 2 license; see COPYING" summary _("Manage certificate requests.") description <<-EOT This subcommand retrieves and submits certificate signing requests (CSRs). EOT deactivate_action(:destroy) find = get_action(:find) find.summary "Retrieve a single CSR." find.arguments "[<host>]" find.render_as = :s find.returns <<-EOT A single certificate request. When used from the Ruby API, returns a Puppet::SSL::CertificateRequest object. Defaults to the current nodes certname. EOT find.examples <<-EOT Retrieve a single CSR from the puppet master's CA: $ puppet certificate_request find somenode.puppetlabs.lan --terminus rest EOT search = get_action(:search) search.summary "Retrieve all outstanding CSRs." search.arguments "<dummy_text>" search.render_as = :s search.returns <<-EOT A list of certificate requests. When used from the Ruby API, returns an array of Puppet::SSL::CertificateRequest objects. EOT search.short_description <<-EOT Retrieves all outstanding certificate signing requests. Due to a known bug, this action requires a dummy search key, the content of which is irrelevant. EOT search.notes <<-EOT Although this action always returns all CSRs, it requires a dummy search key; this is a known bug. EOT search.examples <<-EOT Retrieve all CSRs from the local CA (similar to 'puppet cert list'): $ puppet certificate_request search x --terminus ca EOT get_action(:save).summary "API only: submit a certificate signing request." get_action(:save).arguments "<x509_CSR>" deprecate end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/face/certificate_revocation_list.rb����������������������������������������0000644�0052762�0001160�00000003504�13417161721�024070� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/face' Puppet::Indirector::Face.define(:certificate_revocation_list, '0.0.1') do copyright "Puppet Inc.", 2011 license "Apache 2 license; see COPYING" summary _("Manage the list of revoked certificates.") description <<-EOT This subcommand is primarily for retrieving the certificate revocation list from the CA. EOT find = get_action(:find) find.summary "Retrieve the certificate revocation list." find.render_as = :s find.returns <<-EOT The certificate revocation list. When used from the Ruby API: returns an OpenSSL::X509::CRL object. EOT find.short_description <<-EOT Retrieves the certificate revocation list. EOT find.notes <<-EOT Although this action always returns the CRL from the specified terminus. EOT find.examples <<-EXAMPLES Retrieve a copy of the puppet master's CRL: $ puppet certificate_revocation_list find --terminus rest EXAMPLES destroy = get_action(:destroy) destroy.summary "Delete the certificate revocation list." destroy.arguments "<dummy_text>" destroy.returns "Nothing." destroy.description <<-EOT Deletes the certificate revocation list. This cannot be done over REST, but it is possible to delete the locally cached copy or the local CA's copy of the CRL. EOT destroy.short_description <<-EOT Deletes the certificate revocation list. This cannot be done over REST, but it is possible to delete the locally cached copy or the local CA's copy of the CRL. Due to a known bug, this action requires a dummy argument, the content of which is irrelevant. EOT destroy.notes <<-EOT Although this action always deletes the CRL from the specified terminus, it requires a dummy argument; this is a known bug. EOT deactivate_action(:search) deactivate_action(:save) deprecate end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/face/config.rb�������������������������������������������������������������0000644�0052762�0001160�00000023072�13417161721�017571� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/face' require 'puppet/settings/ini_file' Puppet::Face.define(:config, '0.0.1') do extend Puppet::Util::Colors copyright "Puppet Inc.", 2011 license _("Apache 2 license; see COPYING") summary _("Interact with Puppet's settings.") description "This subcommand can inspect and modify settings from Puppet's 'puppet.conf' configuration file. For documentation about individual settings, see https://puppet.com/docs/puppet/latest/configuration.html." DEFAULT_SECTION_MARKER = Object.new DEFAULT_SECTION = "main" option "--section " + _("SECTION_NAME") do default_to { DEFAULT_SECTION_MARKER } #Sentinel object for default detection during commands summary _("The section of the configuration file to interact with.") description <<-EOT The section of the puppet.conf configuration file to interact with. The three most commonly used sections are 'main', 'master', and 'agent'. 'Main' is the default, and is used by all Puppet applications. Other sections can override 'main' values for specific applications --- the 'master' section affects puppet master and puppet cert, and the 'agent' section affects puppet agent. Less commonly used is the 'user' section, which affects puppet apply. Any other section will be treated as the name of a legacy environment (a deprecated feature), and can only include the 'manifest' and 'modulepath' settings. EOT end action(:print) do summary _("Examine Puppet's current settings.") arguments _("all | <setting> [<setting> ...]") description <<-'EOT' Prints the value of a single setting or a list of settings. This action is a replacement interface to the information available with `puppet <subcommand> --configprint`. EOT notes <<-'EOT' By default, this action reads the general configuration in the 'main' section. Use the '--section' and '--environment' flags to examine other configuration domains. EOT examples <<-'EOT' Get puppet's runfile directory: $ puppet config print rundir Get a list of important directories from the master's config: $ puppet config print all --section master | grep -E "(path|dir)" EOT when_invoked do |*args| options = args.pop @default_section = false if options[:section] == DEFAULT_SECTION_MARKER options[:section] = DEFAULT_SECTION @default_section = true end render_all_settings = args.empty? || args == ['all'] args = Puppet.settings.to_a.collect(&:first) if render_all_settings values_from_the_selected_section = Puppet.settings.values(nil, options[:section].to_sym) loader_settings = { :environmentpath => values_from_the_selected_section.interpolate(:environmentpath), :basemodulepath => values_from_the_selected_section.interpolate(:basemodulepath), } to_be_rendered = nil Puppet.override(Puppet.base_context(loader_settings), _("New environment loaders generated from the requested section.")) do # And now we can lookup values that include those from environments configured from # the requested section values = Puppet.settings.values(Puppet[:environment].to_sym, options[:section].to_sym) if Puppet::Util::Log.sendlevel?(:info) warn_default_section(options[:section]) if @default_section report_section_and_environment(options[:section], Puppet.settings[:environment]) end to_be_rendered = {} args.sort.each do |setting_name| to_be_rendered[setting_name] = values.print(setting_name.to_sym) end end # convert symbols to strings before formatting output if render_all_settings to_be_rendered = stringifyhash(to_be_rendered) end to_be_rendered end when_rendering :console do |to_be_rendered| output = '' if to_be_rendered.keys.length > 1 to_be_rendered.keys.sort.each do |setting| output << "#{setting} = #{to_be_rendered[setting]}\n" end else output << "#{to_be_rendered.to_a[0].last}\n" end output end end def stringifyhash(hash) newhash = {} hash.each do |key, val| key = key.to_s if val.is_a? Hash newhash[key] = stringifyhash(val) elsif val.is_a? Symbol newhash[key] = val.to_s else newhash[key] = val end end newhash end def warn_default_section(section_name) messages = [] messages << _("No section specified; defaulting to '%{section_name}'.") % { section_name: section_name } #TRANSLATORS '--section' is a command line option and should not be translated messages << _("Set the config section by using the `--section` flag.") #TRANSLATORS `puppet config --section user print foo` is a command line example and should not be translated messages << _("For example, `puppet config --section user print foo`.") messages << _("For more information, see https://puppet.com/docs/puppet/latest/configuration.html") Puppet.warning(messages.join("\n")) end def report_section_and_environment(section_name, environment_name) $stderr.puts colorize(:hyellow, _("Resolving settings from section '%{section_name}' in environment '%{environment_name}'") % { section_name: section_name, environment_name: environment_name }) end action(:set) do summary _("Set Puppet's settings.") arguments _("[setting_name] [setting_value]") description <<-'EOT' Updates values in the `puppet.conf` configuration file. EOT notes <<-'EOT' By default, this action manipulates the configuration in the 'main' section. Use the '--section' flag to manipulate other configuration domains. EOT examples <<-'EOT' Set puppet's runfile directory: $ puppet config set rundir /var/run/puppetlabs Set the vardir for only the agent: $ puppet config set vardir /opt/puppetlabs/puppet/cache --section agent EOT when_invoked do |name, value, options| @default_section = false if options[:section] == DEFAULT_SECTION_MARKER options[:section] = DEFAULT_SECTION @default_section = true end if name == 'environment' && options[:section] == 'main' Puppet.warning _(<<-EOM).chomp The environment should be set in either the `[user]`, `[agent]`, or `[master]` section. Variables set in the `[agent]` section are used when running `puppet agent`. Variables set in the `[user]` section are used when running various other puppet subcommands, like `puppet apply` and `puppet module`; these require the defined environment directory to exist locally. Set the config section by using the `--section` flag. For example, `puppet config --section user set environment foo`. For more information, see https://puppet.com/docs/puppet/latest/configuration.html#environment EOM end if Puppet::Util::Log.sendlevel?(:info) report_section_and_environment(options[:section], Puppet.settings[:environment]) end path = Puppet::FileSystem.pathname(Puppet.settings.which_configuration_file) Puppet::FileSystem.touch(path) Puppet::FileSystem.open(path, nil, 'r+:UTF-8') do |file| Puppet::Settings::IniFile.update(file) do |config| config.set(options[:section], name, value) end end nil end end action(:delete) do summary _("Delete a Puppet setting.") arguments _("<setting>") #TRANSLATORS 'main' is a specific section name and should not be translated description "Deletes a setting from the specified section. (The default is the section 'main')." notes <<-'EOT' By default, this action deletes the configuration setting from the 'main' configuration domain. Use the '--section' flags to delete settings from other configuration domains. EOT examples <<-'EOT' Delete the setting 'setting_name' from the 'main' configuration domain: $ puppet config delete setting_name Delete the setting 'setting_name' from the 'master' configuration domain: $ puppet config delete setting_name --section master EOT when_invoked do |name, options| @default_section = false if options[:section] == DEFAULT_SECTION_MARKER options[:section] = DEFAULT_SECTION @default_section = true end path = Puppet::FileSystem.pathname(Puppet.settings.which_configuration_file) if Puppet::FileSystem.exist?(path) Puppet::FileSystem.open(path, nil, 'r+:UTF-8') do |file| Puppet::Settings::IniFile.update(file) do |config| setting_string = config.delete(options[:section], name) if setting_string if Puppet::Util::Log.sendlevel?(:info) report_section_and_environment(options[:section], Puppet.settings[:environment]) end puts(_("Deleted setting from '%{section_name}': '%{setting_string}'") % { section_name: options[:section], name: name, setting_string: setting_string.strip }) else Puppet.warning(_("No setting found in configuration file for section '%{section_name}' setting name '%{name}'") % { section_name: options[:section], name: name }) end end end else #TRANSLATORS the 'puppet.conf' is a specific file and should not be translated Puppet.warning(_("The puppet.conf file does not exist %{puppet_conf}") % { puppet_conf: path }) end nil end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/face/epp.rb����������������������������������������������������������������0000644�0052762�0001160�00000053001�13417161721�017103� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/face' require 'puppet/pops' require 'puppet/parser/files' require 'puppet/file_system' Puppet::Face.define(:epp, '0.0.1') do copyright "Puppet Inc.", 2014 license _("Apache 2 license; see COPYING") summary _("Interact directly with the EPP template parser/renderer.") action(:validate) do summary _("Validate the syntax of one or more EPP templates.") arguments _("[<template>] [<template> ...]") returns _("Nothing, or encountered syntax errors.") description <<-'EOT' This action validates EPP syntax without producing any output. When validating, multiple issues per file are reported up to the settings of max_error, and max_warnings. The processing stops after having reported issues for the first encountered file with errors unless the option --continue_on_error is given. Files can be given using the `modulename/template.epp` style to lookup the template from a module, or be given as a reference to a file. If the reference to a file can be resolved against a template in a module, the module version wins - in this case use an absolute path to reference the template file if the module version is not wanted. Exits with 0 if there were no validation errors. EOT option("--[no-]continue_on_error") do summary _("Whether or not to continue after errors are reported for a template.") end examples <<-'EOT' Validate the template 'template.epp' in module 'mymodule': $ puppet epp validate mymodule/template.epp Validate two arbitrary template files: $ puppet epp validate mymodule/template1.epp yourmodule/something.epp Validate a template somewhere in the file system: $ puppet epp validate /tmp/testing/template1.epp Validate a template against a file relative to the current directory: $ puppet epp validate template1.epp $ puppet epp validate ./template1.epp Validate from STDIN: $ cat template.epp | puppet epp validate Continue on error to see errors for all templates: $ puppet epp validate mymodule/template1.epp mymodule/template2.epp --continue_on_error EOT when_invoked do |*args| options = args.pop # pass a dummy node, as facts are not needed for validation options[:node] = Puppet::Node.new("testnode", :facts => Puppet::Node::Facts.new("facts", {})) compiler = create_compiler(options) status = true # no validation error yet files = args if files.empty? if not STDIN.tty? tmp = validate_template_string(STDIN.read) status &&= tmp else # This is not an error since a validate of all files in an empty # directory should not be treated as a failed validation. Puppet.notice _("No template specified. No action taken") end end missing_files = [] files.each do |file| break if !status && !options[:continue_on_error] template_file = effective_template(file, compiler.environment) if template_file tmp = validate_template(template_file) status &&= tmp else missing_files << file end end if !missing_files.empty? raise Puppet::Error, _("One or more file(s) specified did not exist:\n%{missing_files_list}") % { missing_files_list: missing_files.map { |f| " #{f}" }.join("\n") } else # Exit with 1 if there were errors raise Puppet::Error, _("Errors while validating epp") unless status end end end action (:dump) do summary _("Outputs a dump of the internal template parse tree for debugging") arguments "[--format <old|pn|json>] [--pretty] { -e <source> | [<templates> ...] } " returns _("A dump of the resulting AST model unless there are syntax or validation errors.") description <<-'EOT' The dump action parses and validates the EPP syntax and dumps the resulting AST model in a human readable (but not necessarily an easy to understand) format. The output format can be controlled using the --format <old|pn|json> where: * 'old' is the default, but now deprecated format which is not API. * 'pn' is the Puppet Extended S-Expression Notation. * 'json' outputs the same graph as 'pn' but with JSON syntax. The output will be "pretty printed" when the option --pretty is given together with --format 'pn' or 'json'. This option has no effect on the 'old' format. The command accepts one or more templates (.epp) files, or an -e followed by the template source text. The given templates can be paths to template files, or references to templates in modules when given on the form <modulename>/<template-name>.epp. If no arguments are given, the stdin is read (unless it is attached to a terminal) If multiple templates are given, they are separated with a header indicating the name of the template. This can be suppressed with the option --no-header. The option --[no-]header has no effect when a single template is dumped. When debugging the epp parser itself, it may be useful to suppress the validation step with the `--no-validate` option to observe what the parser produced from the given source. This command ignores the --render-as setting/option. EOT option("--e " + _("<source>")) do default_to { nil } summary _("Dump one epp source expression given on the command line.") end option("--[no-]validate") do summary _("Whether or not to validate the parsed result, if no-validate only syntax errors are reported.") end option('--format ' + _('<old, pn, or json>')) do summary _("Get result in 'old' (deprecated format), 'pn' (new format), or 'json' (new format in JSON).") end option('--pretty') do summary _('Pretty print output. Only applicable together with --format pn or json') end option("--[no-]header") do summary _("Whether or not to show a file name header between files.") end when_invoked do |*args| require 'puppet/pops' options = args.pop # pass a dummy node, as facts are not needed for dump options[:node] = Puppet::Node.new("testnode", :facts => Puppet::Node::Facts.new("facts", {})) options[:header] = options[:header].nil? ? true : options[:header] options[:validate] = options[:validate].nil? ? true : options[:validate] compiler = create_compiler(options) # Print to a buffer since the face needs to return the resulting string # and the face API is "all or nothing" # buffer = StringIO.new if options[:e] buffer.print dump_parse(options[:e], 'command-line-string', options, false) elsif args.empty? if ! STDIN.tty? buffer.print dump_parse(STDIN.read, 'stdin', options, false) else raise Puppet::Error, _("No input to parse given on command line or stdin") end else templates, missing_files = args.reduce([[],[]]) do |memo, file| template_file = effective_template(file, compiler.environment) if template_file.nil? memo[1] << file else memo[0] << template_file end memo end show_filename = templates.count > 1 templates.each do |file| buffer.print dump_parse(Puppet::FileSystem.read(file, :encoding => 'utf-8'), file, options, show_filename) end if !missing_files.empty? raise Puppet::Error, _("One or more file(s) specified did not exist:\n%{missing_files_list}") % { missing_files_list: missing_files.collect { |f| " #{f}" }.join("\n") } end end buffer.string end end action (:render) do summary _("Renders an epp template as text") arguments "-e <source> | [<templates> ...] " returns _("A rendered result of one or more given templates.") description <<-'EOT' This action renders one or more EPP templates. The command accepts one or more templates (.epp files), given the same way as templates are given to the puppet `epp` function (a full path, or a relative reference on the form '<modulename>/<template-name>.epp'), or as a relative path.args In case the given path matches both a modulename/template and a file, the template from the module is used. An inline_epp equivalent can also be performed by giving the template after an -e, or by piping the EPP source text to the command. Values to the template can be defined using the Puppet Language on the command line with `--values` or in a .pp or .yaml file referenced with `--values_file`. If specifying both the result is merged with --values having higher precedence. The --values option allows a Puppet Language sequence of expressions to be defined on the command line the same way as it may be given in a .pp file referenced with `--values_file`. It may set variable values (that become available in the template), and must produce either `undef` or a `Hash` of values (the hash may be empty). Producing `undef` simulates that the template is called without an arguments hash and thus only references variables in its outer scope. When a hash is given, a template is limited to seeing only the global scope. It is thus possible to simulate the different types of calls to the `epp` and `inline_epp` functions, with or without a given hash. Note that if variables are given, they are always available in this simulation - to test that the template only references variables given as arguments, produce a hash in --values or the --values_file, do not specify any variables that are not global, and turn on --strict_variables setting. If multiple templates are given, the same set of values are given to each template. If both --values and --value_file are used, the --values are merged on top of those given in the file. When multiple templates are rendered, a separating header is output between the templates showing the name of the template before the output. The header output can be turned off with `--no-header`. This also concatenates the template results without any added newline separators. Facts from the node where the command is being run are used by default.args Facts can be obtained for other nodes if they have called in, and reported their facts by using the `--node <nodename>` flag. Overriding node facts as well as additional facts can be given in a .yaml or .json file and referencing it with the --facts option. (Values can be obtained in yaml format directly from `facter`, or from puppet for a given node). Note that it is not possible to simulate the reserved variable name `$facts` in any other way. Note that it is not possible to set variables using the Puppet Language that have the same names as facts as this result in an error; "attempt to redefine a variable" since facts are set first. Exits with 0 if there were no validation errors. On errors, no rendered output is produced for that template file. When designing EPP templates, it is strongly recommended to define all template arguments in the template, and to give them in a hash when calling `epp` or `inline_epp` and to use as few global variables as possible, preferably only the $facts hash. This makes templates more free standing and are easier to reuse, and to test. EOT examples <<-'EOT' Render the template in module 'mymodule' called 'mytemplate.epp', and give it two arguments `a` and `b`: $ puppet epp render mymodule/mytemplate.epp --values '{a => 10, b => 20}' Render a template using an absolute path: $ puppet epp render /tmp/testing/mytemplate.epp --values '{a => 10, b => 20}' Render a template with data from a .pp file: $ puppet epp render /tmp/testing/mytemplate.epp --values_file mydata.pp Render a template with data from a .pp file and override one value on the command line: $ puppet epp render /tmp/testing/mytemplate.epp --values_file mydata.pp --values '{a=>10}' Render from STDIN: $ cat template.epp | puppet epp render --values '{a => 10, b => 20}' Set variables in a .pp file and render a template that uses variable references: # data.pp file $greeted = 'a global var' undef $ puppet epp render -e 'hello <%= $greeted %>' --values_file data.pp Render a template that outputs a fact: $ facter --yaml > data.yaml $ puppet epp render -e '<% $facts[osfamily] %>' --facts data.yaml EOT option("--node " + _("<node_name>")) do summary _("The name of the node for which facts are obtained. Defaults to facts for the local node.") end option("--e " + _("<source>")) do default_to { nil } summary _("Render one inline epp template given on the command line.") end option("--values " + _("<values_hash>")) do summary _("A Hash in Puppet DSL form given as arguments to the template being rendered.") end option("--values_file " + _("<pp_or_yaml_file>")) do summary _("A .pp or .yaml file that is processed to produce a hash of values for the template.") end option("--facts " + _("<facts_file>")) do summary _("A .yaml or .json file containing a hash of facts made available in $facts and $trusted") end option("--[no-]header") do summary _("Whether or not to show a file name header between rendered results.") end when_invoked do |*args| options = args.pop options[:header] = options[:header].nil? ? true : options[:header] compiler = create_compiler(options) compiler.with_context_overrides('For rendering epp') do # Print to a buffer since the face needs to return the resulting string # and the face API is "all or nothing" # buffer = StringIO.new status = true if options[:e] buffer.print render_inline(options[:e], compiler, options) elsif args.empty? if ! STDIN.tty? buffer.print render_inline(STDIN.read, compiler, options) else raise Puppet::Error, _("No input to process given on command line or stdin") end else show_filename = args.count > 1 file_nbr = 0 args.each do |file| begin buffer.print render_file(file, compiler, options, show_filename, file_nbr += 1) rescue Puppet::ParseError => detail Puppet.err(detail.message) status = false end end end raise Puppet::Error, _("error while rendering epp") unless status buffer.string end end end def dump_parse(source, filename, options, show_filename = true) output = "" evaluating_parser = Puppet::Pops::Parser::EvaluatingParser::EvaluatingEppParser.new begin if options[:validate] parse_result = evaluating_parser.parse_string(source, filename) else # side step the assert_and_report step parse_result = evaluating_parser.parser.parse_string(source) end if show_filename && options[:header] output << "--- #{filename}\n" end fmt = options[:format] if fmt.nil? || fmt == 'old' output << Puppet::Pops::Model::ModelTreeDumper.new.dump(parse_result) << "\n" else require 'puppet/pops/pn' pn = Puppet::Pops::Model::PNTransformer.transform(parse_result) case fmt when 'json' options[:pretty] ? JSON.pretty_unparse(pn.to_data) : JSON.dump(pn.to_data) else pn.format(options[:pretty] ? Puppet::Pops::PN::Indent.new(' ') : nil, output) end end rescue Puppet::ParseError => detail if show_filename Puppet.err("--- #{filename}") end Puppet.err(detail.message) "" end end def get_values(compiler, options) template_values = nil if values_file = options[:values_file] begin if values_file =~ /\.yaml$/ template_values = YAML.load_file(values_file) elsif values_file =~ /\.pp$/ evaluating_parser = Puppet::Pops::Parser::EvaluatingParser.new template_values = evaluating_parser.evaluate_file(compiler.topscope, values_file) else Puppet.err(_("Only .yaml or .pp can be used as a --values_file")) end rescue => e Puppet.err(_("Could not load --values_file %{error}") % { error: e.message }) end if !(template_values.nil? || template_values.is_a?(Hash)) Puppet.err(_("--values_file option must evaluate to a Hash or undef/nil, got: '%{template_class}'") % { template_class: template_values.class }) end end if values = options[:values] evaluating_parser = Puppet::Pops::Parser::EvaluatingParser.new result = evaluating_parser.evaluate_string(compiler.topscope, values, 'values-hash') case result when nil template_values when Hash template_values.nil? ? result : template_values.merge(result) else Puppet.err(_("--values option must evaluate to a Hash or undef, got: '%{values_class}'") % { values_class: result.class }) end else template_values end end def render_inline(epp_source, compiler, options) template_args = get_values(compiler, options) Puppet::Pops::Evaluator::EppEvaluator.inline_epp(compiler.topscope, epp_source, template_args) end def render_file(epp_template_name, compiler, options, show_filename, file_nbr) template_args = get_values(compiler, options) output = "" begin if show_filename && options[:header] output << "\n" unless file_nbr == 1 output << "--- #{epp_template_name}\n" end # Change to an absolute file only if reference is to a an existing file. Note that an absolute file must be used # or the template must be found on the module path when calling the epp evaluator. template_file = Puppet::Parser::Files.find_template(epp_template_name, compiler.environment) if template_file.nil? && Puppet::FileSystem.exist?(epp_template_name) epp_template_name = File.expand_path(epp_template_name) end output << Puppet::Pops::Evaluator::EppEvaluator.epp(compiler.topscope, epp_template_name, compiler.environment, template_args) rescue Puppet::ParseError => detail Puppet.err("--- #{epp_template_name}") if show_filename raise detail end output end # @api private def validate_template(template) parser = Puppet::Pops::Parser::EvaluatingParser::EvaluatingEppParser.new() parser.parse_file(template) true rescue => detail Puppet.log_exception(detail) false end # @api private def validate_template_string(source) parser = Puppet::Pops::Parser::EvaluatingParser::EvaluatingEppParser.new() parser.parse_string(source, '<stdin>') true rescue => detail Puppet.log_exception(detail) false end # @api private def create_compiler(options) if options[:node] node = options[:node] else node = Puppet[:node_name_value] # If we want to lookup the node we are currently on # we must returning these settings to their default values Puppet.settings[:facts_terminus] = 'facter' Puppet.settings[:node_cache_terminus] = nil end unless node.is_a?(Puppet::Node) node = Puppet::Node.indirection.find(node) # Found node must be given the environment to use in some cases, use the one configured # or given on the command line node.environment = Puppet[:environment] end fact_file = options[:facts] if fact_file if fact_file.is_a?(Hash) # when used via the Face API given_facts = fact_file elsif fact_file.end_with?("json") given_facts = Puppet::Util::Json.load(Puppet::FileSystem.read(fact_file, :encoding => 'utf-8')) else given_facts = YAML.load(Puppet::FileSystem.read(fact_file, :encoding => 'utf-8')) end unless given_facts.instance_of?(Hash) raise _("Incorrect formatted data in %{fact_file} given via the --facts flag") % { fact_file: fact_file } end # It is difficult to add to or modify the set of facts once the node is created # as changes does not show up in parameters. Rather than manually patching up # a node and risking future regressions, a new node is created from scratch node = Puppet::Node.new(node.name, :facts => Puppet::Node::Facts.new("facts", node.facts.values.merge(given_facts))) node.environment = Puppet[:environment] node.merge(node.facts.values) end compiler = Puppet::Parser::Compiler.new(node) # configure compiler with facts and node related data # Set all global variables from facts compiler.send(:set_node_parameters) # pretend that the main class (named '') has been evaluated # since it is otherwise not possible to resolve top scope variables # using '::' when rendering. (There is no harm doing this for the other actions) # compiler.topscope.class_set('', compiler.topscope) compiler end # Produces the effective template file from a module/template or file reference # @api private def effective_template(file, env) template_file = Puppet::Parser::Files.find_template(file, env) if !template_file.nil? template_file elsif Puppet::FileSystem.exist?(file) file else nil end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/face/help.rb���������������������������������������������������������������0000644�0052762�0001160�00000015722�13417161721�017257� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/face' require 'puppet/application/face_base' require 'puppet/util/constant_inflector' require 'pathname' require 'erb' Puppet::Face.define(:help, '0.0.1') do copyright "Puppet Inc.", 2011 license _("Apache 2 license; see COPYING") summary _("Display Puppet help.") action(:help) do summary _("Display help about Puppet subcommands and their actions.") arguments _("[<subcommand>] [<action>]") returns _("Short help text for the specified subcommand or action.") examples _(<<-'EOT') Get help for an action: $ puppet help EOT option "--version " + _("VERSION") do summary _("The version of the subcommand for which to show help.") end default when_invoked do |*args| options = args.pop if default_case?(args) || help_for_help?(args) return erb('global.erb').result(binding) end if args.length > 2 #TRANSLATORS 'puppet help' is a command line and should not be translated raise ArgumentError, _("The 'puppet help' command takes two (optional) arguments: a subcommand and an action") end version = :current if options.has_key? :version if options[:version].to_s !~ /^current$/i version = options[:version] else if args.length == 0 #TRANSLATORS '--version' is a command line option and should not be translated raise ArgumentError, _("Supplying a '--version' only makes sense when a Faces subcommand is given") end end end facename, actionname = args if legacy_applications.include? facename if actionname raise ArgumentError, _("The legacy subcommand '%{sub_command}' does not support supplying an action") % { sub_command: facename } end return render_application_help(facename) else return render_face_help(facename, actionname, version) end end end def default_case?(args) args.empty? end def help_for_help?(args) args.length == 1 && args.first == 'help' end def render_application_help(applicationname) return Puppet::Application[applicationname].help rescue StandardError, LoadError => detail message = [] message << _('Could not load help for the application %{application_name}.') % { application_name: applicationname } message << _('Please check the error logs for more information.') message << '' message << _('Detail: "%{detail}"') % { detail: detail.message } fail ArgumentError, message.join("\n"), detail.backtrace end def render_face_help(facename, actionname, version) face, action = load_face_help(facename, actionname, version) return template_for(face, action).result(binding) rescue StandardError, LoadError => detail message = [] message << _('Could not load help for the face %{face_name}.') % { face_name: facename } message << _('Please check the error logs for more information.') message << '' message << _('Detail: "%{detail}"') % { detail: detail.message } fail ArgumentError, message.join("\n"), detail.backtrace end def load_face_help(facename, actionname, version) face = Puppet::Face[facename.to_sym, version] if actionname action = face.get_action(actionname.to_sym) if ! action fail ArgumentError, _("Unable to load action %{actionname} from %{face}") % { actionname: actionname, face: face } end end [face, action] end def template_for(face, action) if action.nil? erb('face.erb') else erb('action.erb') end end def erb(name) template = (Pathname(__FILE__).dirname + "help" + name) erb = ERB.new(template.read, nil, '-') erb.filename = template.to_s return erb end # Return a list of applications that are not simply just stubs for Faces. def legacy_applications Puppet::Application.available_application_names.reject do |appname| (is_face_app?(appname)) or (exclude_from_docs?(appname)) end.sort end # Return a list of all applications (both legacy and Face applications), along with a summary # of their functionality. # @return [Array] An Array of Arrays. The outer array contains one entry per application; each # element in the outer array is a pair whose first element is a String containing the application # name, and whose second element is a String containing the summary for that application. def all_application_summaries() Puppet::Application.available_application_names.sort.inject([]) do |result, appname| next result if exclude_from_docs?(appname) if (is_face_app?(appname)) begin face = Puppet::Face[appname, :current] # Add deprecation message to summary if the face is deprecated summary = face.deprecated? ? face.summary + ' ' + _("(Deprecated)") : face.summary result << [appname, summary] rescue StandardError, LoadError error_message = _("!%{sub_command}! Subcommand unavailable due to error.") % { sub_command: appname } error_message += ' ' + _("Check error logs.") result << [ error_message ] end else begin summary = Puppet::Application[appname].summary if summary.empty? summary = horribly_extract_summary_from(appname) end result << [appname, summary] rescue StandardError, LoadError error_message = _("!%{sub_command}! Subcommand unavailable due to error.") % { sub_command: appname } error_message += ' ' + _("Check error logs.") result << [ error_message ] end end end end def horribly_extract_summary_from(appname) help = Puppet::Application[appname].help.split("\n") # Now we find the line with our summary, extract it, and return it. This # depends on the implementation coincidence of how our pages are # formatted. If we can't match the pattern we expect we return the empty # string to ensure we don't blow up in the summary. --daniel 2011-04-11 while line = help.shift do if md = /^puppet-#{appname}\([^\)]+\) -- (.*)$/.match(line) return md[1] end end return '' end # This should absolutely be a private method, but for some reason it appears # that you can't use the 'private' keyword inside of a Face definition. # See #14205. #private :horribly_extract_summary_from def exclude_from_docs?(appname) %w{face_base indirection_base}.include? appname end # This should absolutely be a private method, but for some reason it appears # that you can't use the 'private' keyword inside of a Face definition. # See #14205. #private :exclude_from_docs? def is_face_app?(appname) clazz = Puppet::Application.find(appname) clazz.ancestors.include?(Puppet::Application::FaceBase) end # This should probably be a private method, but for some reason it appears # that you can't use the 'private' keyword inside of a Face definition. # See #14205. #private :is_face_app? end ����������������������������������������������puppet-5.5.10/lib/puppet/face/key.rb����������������������������������������������������������������0000644�0052762�0001160�00000001011�13417161721�017101� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/face' Puppet::Indirector::Face.define(:key, '0.0.1') do copyright "Puppet Inc.", 2011 license _("Apache 2 license; see COPYING") summary _("Create, save, and remove certificate keys.") description <<-'EOT' This subcommand manages certificate private keys. Keys are created automatically by puppet agent and when certificate requests are generated with 'puppet certificate generate'; it should not be necessary to use this subcommand directly. EOT deprecate end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/feature/�������������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�016535� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/feature/bolt.rb������������������������������������������������������������0000644�0052762�0001160�00000000115�13417161721�020012� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/feature' Puppet.features.add(:bolt, :libs => ['bolt']) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/feature/cfpropertylist.rb��������������������������������������������������0000644�0052762�0001160�00000000141�13417161721�022142� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/feature' Puppet.features.add(:cfpropertylist, :libs => ['cfpropertylist']) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/feature/hiera_eyaml.rb�����������������������������������������������������0000644�0052762�0001160�00000000161�13417161721�021332� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/feature' Puppet.features.add(:hiera_eyaml, :libs => ['hiera/backend/eyaml/parser/parser']) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/feature/hocon.rb�����������������������������������������������������������0000644�0052762�0001160�00000000117�13417161721�020162� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/feature' Puppet.features.add(:hocon, :libs => ['hocon']) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/feature/libuser.rb���������������������������������������������������������0000644�0052762�0001160�00000000371�13417161721�020523� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/feature' require 'puppet/util/libuser' Puppet.features.add(:libuser) { File.executable?("/usr/sbin/lgroupadd") and File.executable?("/usr/sbin/luseradd") and Puppet::FileSystem.exist?(Puppet::Util::Libuser.getconf) } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/feature/msgpack.rb���������������������������������������������������������0000644�0052762�0001160�00000000123�13417161721�020476� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/feature' Puppet.features.add(:msgpack, :libs => ["msgpack"]) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/feature/pe_license.rb������������������������������������������������������0000644�0052762�0001160�00000000245�13417161721�021164� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/feature' #Is the pe license library installed providing the ability to read licenses. Puppet.features.add(:pe_license, :libs => %{pe_license}) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/feature/selinux.rb���������������������������������������������������������0000644�0052762�0001160�00000000123�13417161721�020540� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/feature' Puppet.features.add(:selinux, :libs => ["selinux"]) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/feature/ssh.rb�������������������������������������������������������������0000644�0052762�0001160�00000000116�13417161721�017650� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/feature' Puppet.features.add(:ssh, :libs => %{net/ssh}) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/feature/telnet.rb����������������������������������������������������������0000644�0052762�0001160�00000000206�13417161721�020346� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/feature' Puppet.features.add :telnet do begin require 'net/telnet' rescue LoadError false end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/feature/zlib.rb������������������������������������������������������������0000644�0052762�0001160�00000000227�13417161721�020016� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/feature' # We want this to load if possible, but it's not automatically # required. Puppet.features.add(:zlib, :libs => %{zlib}) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/feature/base.rb������������������������������������������������������������0000644�0052762�0001160�00000005536�13417161721�020000� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/feature' # Add the simple features, all in one file. # Order is important as some features depend on others # We have a syslog implementation Puppet.features.add(:syslog, :libs => ["syslog"]) # We can use POSIX user functions Puppet.features.add(:posix) do require 'etc' !Etc.getpwuid(0).nil? && Puppet.features.syslog? end # We can use Microsoft Windows functions Puppet.features.add(:microsoft_windows) do begin # ruby require 'Win32API' # case matters in this require! # Note: Setting codepage here globally ensures all strings returned via # WIN32OLE (Ruby's late-bound COM support) are encoded in Encoding::UTF_8 # # Also, this does not modify the value of WIN32OLE.locale - which defaults # to 2048 (at least on US English Windows) and is not listed in the MS # locales table, here: https://msdn.microsoft.com/en-us/library/ms912047(v=winembedded.10).aspx require 'win32ole' ; WIN32OLE.codepage = WIN32OLE::CP_UTF8 # gems require 'win32/process' require 'win32/dir' require 'win32/service' true rescue LoadError => err #TRANSLATORS "win32-process", "win32-dir", and "win32-service" are program names and should not be translated warn _("Cannot run on Microsoft Windows without the win32-process, win32-dir and win32-service gems: %{err}") % { err: err } unless Puppet.features.posix? end end raise Puppet::Error,_("Cannot determine basic system flavour") unless Puppet.features.posix? or Puppet.features.microsoft_windows? # We've got LDAP available. Puppet.features.add(:ldap, :libs => ["ldap"]) # We have the Rdoc::Usage library. Puppet.features.add(:usage, :libs => %w{rdoc/ri/ri_paths rdoc/usage}) # We have libshadow, useful for managing passwords. Puppet.features.add(:libshadow, :libs => ["shadow"]) # We're running as root. Puppet.features.add(:root) { require 'puppet/util/suidmanager'; Puppet::Util::SUIDManager.root? } # We have lcs diff Puppet.features.add :diff, :libs => %w{diff/lcs diff/lcs/hunk} # We have augeas Puppet.features.add(:augeas, :libs => ["augeas"]) # We have OpenSSL Puppet.features.add(:openssl, :libs => ["openssl"]) # We have sqlite Puppet.features.add(:sqlite, :libs => ["sqlite3"]) # We have Hiera Puppet.features.add(:hiera, :libs => ["hiera"]) Puppet.features.add(:minitar, :libs => ["archive/tar/minitar"]) # We can manage symlinks Puppet.features.add(:manages_symlinks) do if ! Puppet::Util::Platform.windows? true else module WindowsSymlink require 'ffi' extend FFI::Library def self.is_implemented begin ffi_lib :kernel32 attach_function :CreateSymbolicLinkW, [:lpwstr, :lpwstr, :dword], :boolean true rescue LoadError Puppet.debug("CreateSymbolicLink is not available") false end end end WindowsSymlink.is_implemented end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/feature/eventlog.rb��������������������������������������������������������0000644�0052762�0001160�00000000152�13417161721�020676� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/feature' if Puppet.features.microsoft_windows? Puppet.features.add(:eventlog) end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/feature/rack.rb������������������������������������������������������������0000644�0052762�0001160�00000000612�13417161721�017774� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/feature' # See if we have rack available, an HTTP Application Stack # Explicitly depend on rack library version >= 1.0.0 Puppet.features.add(:rack) do require 'rack' if ! (defined?(::Rack) and defined?(::Rack.release)) false else major_version = ::Rack.release.split('.')[0].to_i if major_version >= 1 true else false end end end ����������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/file_bucket.rb�������������������������������������������������������������0000644�0052762�0001160�00000000115�13417161721�017673� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# stub module Puppet::FileBucket class BucketError < RuntimeError; end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/file_bucket/���������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�017356� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/file_bucket/dipper.rb������������������������������������������������������0000644�0052762�0001160�00000012622�13417161721�021164� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'pathname' require 'puppet/file_bucket' require 'puppet/file_bucket/file' require 'puppet/indirector/request' require 'puppet/util/diff' require 'tempfile' class Puppet::FileBucket::Dipper include Puppet::Util::Checksums # This is a transitional implementation that uses REST # to access remote filebucket files. attr_accessor :name # Creates a bucket client def initialize(hash = {}) # Emulate the XMLRPC client server = hash[:Server] port = hash[:Port] || Puppet[:masterport] if hash.include?(:Path) @local_path = hash[:Path] @rest_path = nil else @local_path = nil @rest_path = "filebucket://#{server}:#{port}/" end @checksum_type = Puppet[:digest_algorithm].to_sym @digest = method(@checksum_type) end def local? !! @local_path end # Backs up a file to the file bucket def backup(file) file_handle = Puppet::FileSystem.pathname(file) raise(ArgumentError, _("File %{file} does not exist") % { file: file }) unless Puppet::FileSystem.exist?(file_handle) begin file_bucket_file = Puppet::FileBucket::File.new(file_handle, :bucket_path => @local_path) files_original_path = absolutize_path(file) dest_path = "#{@rest_path}#{file_bucket_file.name}/#{files_original_path}" file_bucket_path = "#{@rest_path}#{file_bucket_file.checksum_type}/#{file_bucket_file.checksum_data}/#{files_original_path}" # Make a HEAD request for the file so that we don't waste time # uploading it if it already exists in the bucket. unless Puppet::FileBucket::File.indirection.head(file_bucket_path, :bucket_path => file_bucket_file.bucket_path) Puppet::FileBucket::File.indirection.save(file_bucket_file, dest_path) end return file_bucket_file.checksum_data rescue => detail message = _("Could not back up %{file}: %{detail}") % { file: file, detail: detail } Puppet.log_exception(detail, message) raise Puppet::Error, message, detail.backtrace end end # Diffs two filebucket files identified by their sums def diff(checksum_a, checksum_b, file_a, file_b) raise RuntimeError, _("Diff is not supported on this platform") if Puppet[:diff] == "" if checksum_a source_path = "#{@rest_path}#{@checksum_type}/#{checksum_a}" if checksum_b file_diff = Puppet::FileBucket::File.indirection.find( source_path, :bucket_path => @local_path, :diff_with => checksum_b) elsif file_b tmp_file = ::Tempfile.new('diff') begin restore(tmp_file.path, checksum_a) file_diff = Puppet::Util::Diff.diff(tmp_file.path, file_b) ensure tmp_file.close tmp_file.unlink end else raise Puppet::Error, _("Please provide a file or checksum to diff with") end elsif file_a if checksum_b tmp_file = ::Tempfile.new('diff') begin restore(tmp_file.path, checksum_b) file_diff = Puppet::Util::Diff.diff(file_a, tmp_file.path) ensure tmp_file.close tmp_file.unlink end elsif file_b file_diff = Puppet::Util::Diff.diff(file_a, file_b) end end raise Puppet::Error, _("Failed to diff files") unless file_diff file_diff.to_s end # Retrieves a file by sum. def getfile(sum) get_bucket_file(sum).to_s end # Retrieves a FileBucket::File by sum. def get_bucket_file(sum) source_path = "#{@rest_path}#{@checksum_type}/#{sum}" file_bucket_file = Puppet::FileBucket::File.indirection.find(source_path, :bucket_path => @local_path) raise Puppet::Error, _("File not found") unless file_bucket_file file_bucket_file end # Restores the file def restore(file, sum) restore = true file_handle = Puppet::FileSystem.pathname(file) if Puppet::FileSystem.exist?(file_handle) cursum = Puppet::FileBucket::File.new(file_handle).checksum_data() # if the checksum has changed... # this might be extra effort if cursum == sum restore = false end end if restore if newcontents = get_bucket_file(sum) newsum = newcontents.checksum_data changed = nil if Puppet::FileSystem.exist?(file_handle) and ! Puppet::FileSystem.writable?(file_handle) changed = Puppet::FileSystem.stat(file_handle).mode ::File.chmod(changed | 0200, file) end ::File.open(file, ::File::WRONLY|::File::TRUNC|::File::CREAT) { |of| of.binmode newcontents.stream do |source_stream| FileUtils.copy_stream(source_stream, of) end } ::File.chmod(changed, file) if changed else Puppet.err _("Could not find file with checksum %{sum}") % { sum: sum } return nil end return newsum else return nil end end # List Filebucket content. def list(fromdate, todate) raise Puppet::Error, _("Listing remote file buckets is not allowed") unless local? source_path = "#{@rest_path}#{@checksum_type}/" file_bucket_list = Puppet::FileBucket::File.indirection.find( source_path, :bucket_path => @local_path, :list_all => true, :fromdate => fromdate, :todate => todate) raise Puppet::Error, _("File not found") unless file_bucket_list file_bucket_list.to_s end private def absolutize_path( path ) Pathname.new(path).realpath end end ��������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/file_bucket/file.rb��������������������������������������������������������0000644�0052762�0001160�00000005573�13417161721�020627� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/file_bucket' require 'puppet/indirector' require 'puppet/util/checksums' require 'digest/md5' require 'stringio' class Puppet::FileBucket::File # This class handles the abstract notion of a file in a filebucket. # There are mechanisms to save and load this file locally and remotely in puppet/indirector/filebucketfile/* # There is a compatibility class that emulates pre-indirector filebuckets in Puppet::FileBucket::Dipper extend Puppet::Indirector indirects :file_bucket_file, :terminus_class => :selector attr :bucket_path def self.supported_formats [:binary] end def initialize(contents, options = {}) case contents when String @contents = StringContents.new(contents) when Pathname @contents = FileContents.new(contents) else raise ArgumentError.new(_("contents must be a String or Pathname, got a %{contents_class}") % { contents_class: contents.class }) end @bucket_path = options.delete(:bucket_path) @checksum_type = Puppet[:digest_algorithm].to_sym raise ArgumentError.new(_("Unknown option(s): %{opts}") % { opts: options.keys.join(', ') }) unless options.empty? end # @return [Num] The size of the contents def size @contents.size() end # @return [IO] A stream that reads the contents def stream(&block) @contents.stream(&block) end def checksum_type @checksum_type.to_s end def checksum "{#{checksum_type}}#{checksum_data}" end def checksum_data @checksum_data ||= @contents.checksum_data(@checksum_type) end def to_s to_binary end def to_binary @contents.to_binary end def contents to_binary end def name "#{checksum_type}/#{checksum_data}" end def self.from_binary(contents) self.new(contents) end class StringContents def initialize(content) @contents = content; end def stream(&block) s = StringIO.new(@contents) begin block.call(s) ensure s.close end end def size @contents.size end def checksum_data(base_method) Puppet.info(_("Computing checksum on string")) Puppet::Util::Checksums.method(base_method).call(@contents) end def to_binary # This is not so horrible as for FileContent, but still possible to mutate the content that the # checksum is based on... so semi horrible... return @contents; end end class FileContents def initialize(path) @path = path end def stream(&block) Puppet::FileSystem.open(@path, nil, 'rb', &block) end def size Puppet::FileSystem.size(@path) end def checksum_data(base_method) Puppet.info(_("Computing checksum on file %{path}") % { path: @path }) Puppet::Util::Checksums.method(:"#{base_method}_file").call(@path) end def to_binary Puppet::FileSystem::binread(@path) end end end �������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/file_serving.rb������������������������������������������������������������0000644�0052762�0001160�00000000075�13417161721�020100� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Just a stub class. class Puppet::FileServing # :nodoc: end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/file_serving/��������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�017556� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/file_serving/configuration.rb����������������������������������������������0000644�0052762�0001160�00000006557�13417161721�022762� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet' require 'puppet/file_serving' require 'puppet/file_serving/mount' require 'puppet/file_serving/mount/file' require 'puppet/file_serving/mount/modules' require 'puppet/file_serving/mount/plugins' require 'puppet/file_serving/mount/locales' require 'puppet/file_serving/mount/pluginfacts' require 'puppet/file_serving/mount/tasks' class Puppet::FileServing::Configuration require 'puppet/file_serving/configuration/parser' def self.configuration @configuration ||= new end Mount = Puppet::FileServing::Mount private_class_method :new attr_reader :mounts #private :mounts # Find the right mount. Does some shenanigans to support old-style module # mounts. def find_mount(mount_name, environment) # Reparse the configuration if necessary. readconfig # This can be nil. mounts[mount_name] end def initialize @mounts = {} @config_file = nil # We don't check to see if the file is modified the first time, # because we always want to parse at first. readconfig(false) end # Is a given mount available? def mounted?(name) @mounts.include?(name) end # Split the path into the separate mount point and path. def split_path(request) # Reparse the configuration if necessary. readconfig mount_name, path = request.key.split(File::Separator, 2) raise(ArgumentError, _("Cannot find file: Invalid mount '%{mount_name}'") % { mount_name: mount_name }) unless mount_name =~ %r{^[-\w]+$} raise(ArgumentError, _("Cannot find file: Invalid relative path '%{path}'") % { path: path }) if path and path.split('/').include?('..') return nil unless mount = find_mount(mount_name, request.environment) if mount.name == "modules" and mount_name != "modules" # yay backward-compatibility path = "#{mount_name}/#{path}" end if path == "" path = nil elsif path # Remove any double slashes that might have occurred path = path.gsub(/\/+/, "/") end return mount, path end def umount(name) @mounts.delete(name) if @mounts.include? name end private def mk_default_mounts @mounts["modules"] ||= Mount::Modules.new("modules") @mounts["modules"].allow('*') if @mounts["modules"].empty? @mounts["plugins"] ||= Mount::Plugins.new("plugins") @mounts["plugins"].allow('*') if @mounts["plugins"].empty? @mounts["locales"] ||= Mount::Locales.new("locales") @mounts["locales"].allow('*') if @mounts["locales"].empty? @mounts["pluginfacts"] ||= Mount::PluginFacts.new("pluginfacts") @mounts["pluginfacts"].allow('*') if @mounts["pluginfacts"].empty? @mounts["tasks"] ||= Mount::Tasks.new("tasks") @mounts["tasks"].allow('*') if @mounts["tasks"].empty? end # Read the configuration file. def readconfig(check = true) config = Puppet[:fileserverconfig] return unless Puppet::FileSystem.exist?(config) @parser ||= Puppet::FileServing::Configuration::Parser.new(config) return if check and ! @parser.changed? # Don't assign the mounts hash until we're sure the parsing succeeded. begin newmounts = @parser.parse @mounts = newmounts rescue => detail Puppet.log_exception(detail, _("Error parsing fileserver configuration: %{detail}; using old configuration") % { detail: detail }) end ensure # Make sure we've got our plugins and modules. mk_default_mounts end end �������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/file_serving/configuration/������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�022425� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/file_serving/configuration/parser.rb���������������������������������������0000644�0052762�0001160�00000010631�13417161721�024242� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/file_serving/configuration' require 'puppet/util/watched_file' class Puppet::FileServing::Configuration::Parser Mount = Puppet::FileServing::Mount MODULES = 'modules' # Parse our configuration file. def parse raise(_("File server configuration %{config_file} does not exist") % { config_file: @file }) unless Puppet::FileSystem.exist?(@file) raise(_("Cannot read file server configuration %{config_file}") % { config_file: @file }) unless FileTest.readable?(@file) @mounts = {} @count = 0 File.open(@file) do |f| mount = nil f.each_line do |line| # Have the count increment at the top, in case we throw exceptions. @count += 1 case line when /^\s*#/; next # skip comments when /^\s*$/; next # skip blank lines when /\[([-\w]+)\]/ mount = newmount($1) when /^\s*(\w+)\s+(.+?)(\s*#.*)?$/ var = $1 value = $2 value.strip! raise(ArgumentError, _("Fileserver configuration file does not use '=' as a separator")) if value =~ /^=/ case var when "path" path(mount, value) when "allow" allow(mount, value) when "deny" deny(mount, value) else error_location_str = Puppet::Util::Errors.error_location(@file.filename, @count) raise ArgumentError.new(_("Invalid argument '%{var}' at %{error_location}") % { var: var, error_location: error_location_str }) end else error_location_str = Puppet::Util::Errors.error_location(@file.filename, @count) raise ArgumentError.new(_("Invalid entry at %{error_location}: '%{file_text}'") % { file_text: line.chomp, error_location: error_location_str }) end end end validate @mounts end def initialize(filename) @file = Puppet::Util::WatchedFile.new(filename) end def changed? @file.changed? end private # Allow a given pattern access to a mount. def allow(mount, value) value.split(/\s*,\s*/).each { |val| begin mount.info _("allowing %{val} access") % { val: val } mount.allow(val) rescue Puppet::AuthStoreError => detail error_location_str = Puppet::Util::Errors.error_location(@file, @count) raise ArgumentError.new("%{detail} %{error_location}" % { detail: detail.to_s, error_location: error_location_str }) end } end # Deny a given pattern access to a mount. def deny(mount, value) value.split(/\s*,\s*/).each { |val| begin mount.info _("denying %{val} access") % { val: val } mount.deny(val) rescue Puppet::AuthStoreError => detail error_location_str = Puppet::Util::Errors.error_location(@file, @count) raise ArgumentError.new("%{detail} %{error_location}" % { detail: detail.to_s, error_location: error_location_str }) end } end # Create a new mount. def newmount(name) if @mounts.include?(name) error_location_str = Puppet::Util::Errors.error_location(@file, @count) raise ArgumentError.new(_("%{mount} is already mounted at %{name} at %{error_location}") % { mount: @mounts[name], name: name, error_location: error_location_str }) end case name when "modules" mount = Mount::Modules.new(name) when "plugins" mount = Mount::Plugins.new(name) when "tasks" mount = Mount::Tasks.new(name) when "locales" mount = Mount::Locales.new(name) else mount = Mount::File.new(name) end @mounts[name] = mount mount end # Set the path for a mount. def path(mount, value) if mount.respond_to?(:path=) begin mount.path = value rescue ArgumentError => detail Puppet.log_exception(detail, _("Removing mount \"%{mount}\": %{detail}") % { mount: mount.name, detail: detail }) @mounts.delete(mount.name) end else Puppet.warning _("The '%{mount}' module can not have a path. Ignoring attempt to set it") % { mount: mount.name } end end # Make sure all of our mounts are valid. We have to do this after the fact # because details are added over time as the file is parsed. def validate @mounts.each { |name, mount| mount.validate } end end �������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/file_serving/content.rb����������������������������������������������������0000644�0052762�0001160�00000002136�13417161721�021552� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector' require 'puppet/file_serving' require 'puppet/file_serving/base' # A class that handles retrieving file contents. # It only reads the file when its content is specifically # asked for. class Puppet::FileServing::Content < Puppet::FileServing::Base extend Puppet::Indirector indirects :file_content, :terminus_class => :selector attr_writer :content def self.supported_formats [:binary] end def self.from_binary(content) instance = new("/this/is/a/fake/path") instance.content = content instance end # This is no longer used, but is still called by the file server implementations when interacting # with their model abstraction. def collect(source_permissions = nil) end # Read the content of our file in. def content unless @content # This stat can raise an exception, too. raise(ArgumentError, _("Cannot read the contents of links unless following links")) if stat.ftype == "symlink" @content = Puppet::FileSystem.binread(full_path) end @content end def to_binary File.new(full_path, "rb") end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/file_serving/http_metadata.rb����������������������������������������������0000644�0052762�0001160�00000002657�13417161721�022727� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/file_serving/metadata' # Simplified metadata representation, suitable for the information # that is available from HTTP headers. class Puppet::FileServing::HttpMetadata < Puppet::FileServing::Metadata def initialize(http_response, path = '/dev/null') super(path) # ignore options that do not apply to HTTP metadata @owner = @group = @mode = nil # hash available checksums for eventual collection @checksums = {} # use a default mtime in case there is no usable HTTP header @checksums[:mtime] = "{mtime}#{Time.now}" if checksum = http_response['content-md5'] # convert base64 digest to hex checksum = checksum.unpack("m0").first.unpack("H*").first @checksums[:md5] = "{md5}#{checksum}" end if last_modified = http_response['last-modified'] mtime = DateTime.httpdate(last_modified).to_time @checksums[:mtime] = "{mtime}#{mtime.utc}" end @ftype = 'file' self end # Override of the parent class method. Does not call super! # We can only return metadata that was extracted from the # HTTP headers during #initialize. def collect # Prefer the checksum_type from the indirector request options # but fall back to the alternative otherwise [ @checksum_type, :md5, :sha256, :sha384, :sha512, :sha224, :mtime ].each do |type| @checksum_type = type @checksum = @checksums[type] return if @checksum end end end ���������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/file_serving/mount.rb������������������������������������������������������0000644�0052762�0001160�00000001461�13417161721�021242� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/network/authstore' require 'puppet/util/logging' require 'puppet/file_serving' require 'puppet/file_serving/metadata' require 'puppet/file_serving/content' # Broker access to the filesystem, converting local URIs into metadata # or content objects. class Puppet::FileServing::Mount < Puppet::Network::AuthStore include Puppet::Util::Logging attr_reader :name def find(path, options) raise NotImplementedError end # Create our object. It must have a name. def initialize(name) unless name =~ %r{^[-\w]+$} raise ArgumentError, _("Invalid mount name format '%{name}'") % { name: name } end @name = name super() end def search(path, options) raise NotImplementedError end def to_s "mount[#{@name}]" end # A noop. def validate end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/file_serving/mount/��������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020720� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/file_serving/mount/file.rb�������������������������������������������������0000644�0052762�0001160�00000005677�13417161721�022176� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/file_serving/mount' class Puppet::FileServing::Mount::File < Puppet::FileServing::Mount def self.localmap @localmap ||= { "h" => Facter.value("hostname"), "H" => [ Facter.value("hostname"), Facter.value("domain") ].join("."), "d" => Facter.value("domain") } end def complete_path(relative_path, node) full_path = path(node) raise ArgumentError.new(_("Mounts without paths are not usable")) unless full_path # If there's no relative path name, then we're serving the mount itself. return full_path unless relative_path file = ::File.join(full_path, relative_path) if !(Puppet::FileSystem.exist?(file) or Puppet::FileSystem.symlink?(file)) Puppet.info(_("File does not exist or is not accessible: %{file}") % { file: file }) return nil end file end # Return an instance of the appropriate class. def find(short_file, request) complete_path(short_file, request.node) end # Return the path as appropriate, expanding as necessary. def path(node = nil) if expandable? return expand(@path, node) else return @path end end # Set the path. def path=(path) # FIXME: For now, just don't validate paths with replacement # patterns in them. if path =~ /%./ # Mark that we're expandable. @expandable = true else raise ArgumentError, _("%{path} does not exist or is not a directory") % { path: path } unless FileTest.directory?(path) raise ArgumentError, _("%{path} is not readable") % { path: path } unless FileTest.readable?(path) @expandable = false end @path = path end def search(path, request) return nil unless path = complete_path(path, request.node) [path] end # Verify our configuration is valid. This should really check to # make sure at least someone will be allowed, but, eh. def validate raise ArgumentError.new(_("Mounts without paths are not usable")) if @path.nil? end private # Create a map for a specific node. def clientmap(node) { "h" => node.sub(/\..*$/, ""), "H" => node, "d" => node.sub(/[^.]+\./, "") # domain name } end # Replace % patterns as appropriate. def expand(path, node = nil) # This map should probably be moved into a method. map = nil if node map = clientmap(node) else Puppet.notice _("No client; expanding '%{path}' with local host") % { path: path } # Else, use the local information map = localmap end path.gsub(/%(.)/) do |v| key = $1 if key == "%" "%" else map[key] || v end end end # Do we have any patterns in our path, yo? def expandable? if defined?(@expandable) @expandable else false end end # Cache this manufactured map, since if it's used it's likely # to get used a lot. def localmap self.class.localmap end end �����������������������������������������������������������������puppet-5.5.10/lib/puppet/file_serving/mount/locales.rb����������������������������������������������0000644�0052762�0001160�00000002251�13417161721�022662� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/file_serving/mount' # Find files in the modules' locales directories. # This is a very strange mount because it merges # many directories into one. class Puppet::FileServing::Mount::Locales < Puppet::FileServing::Mount # Return an instance of the appropriate class. def find(relative_path, request) return nil unless mod = request.environment.modules.find { |m| m.locale(relative_path) } path = mod.locale(relative_path) path end def search(relative_path, request) # We currently only support one kind of search on locales - return # them all. Puppet.debug("Warning: calling Locales.search with empty module path.") if request.environment.modules.empty? paths = request.environment.modules.find_all { |mod| mod.locales? }.collect { |mod| mod.locale_directory } if paths.empty? # If the modulepath is valid then we still need to return a valid root # directory for the search, but make sure nothing inside it is # returned. request.options[:recurse] = false request.environment.modulepath.empty? ? nil : request.environment.modulepath else paths end end def valid? true end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/file_serving/mount/modules.rb����������������������������������������������0000644�0052762�0001160�00000001150�13417161721�022705� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/file_serving/mount' # This is the modules-specific mount: it knows how to search through # modules for files. Yay. class Puppet::FileServing::Mount::Modules < Puppet::FileServing::Mount # Return an instance of the appropriate class. def find(path, request) raise _("No module specified") if path.to_s.empty? module_name, relative_path = path.split("/", 2) return nil unless mod = request.environment.module(module_name) mod.file(relative_path) end def search(path, request) if result = find(path, request) [result] end end def valid? true end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/file_serving/mount/pluginfacts.rb������������������������������������������0000644�0052762�0001160�00000002302�13417161721�023554� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/file_serving/mount' # Find files in the modules' pluginfacts directories. # This is a very strange mount because it merges # many directories into one. class Puppet::FileServing::Mount::PluginFacts < Puppet::FileServing::Mount # Return an instance of the appropriate class. def find(relative_path, request) return nil unless mod = request.environment.modules.find { |m| m.pluginfact(relative_path) } path = mod.pluginfact(relative_path) path end def search(relative_path, request) # We currently only support one kind of search on plugins - return # them all. Puppet.debug("Warning: calling Plugins.search with empty module path.") if request.environment.modules.empty? paths = request.environment.modules.find_all { |mod| mod.pluginfacts? }.collect { |mod| mod.plugin_fact_directory } if paths.empty? # If the modulepath is valid then we still need to return a valid root # directory for the search, but make sure nothing inside it is # returned. request.options[:recurse] = false request.environment.modulepath.empty? ? nil : request.environment.modulepath else paths end end def valid? true end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/file_serving/mount/plugins.rb����������������������������������������������0000644�0052762�0001160�00000002251�13417161721�022721� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/file_serving/mount' # Find files in the modules' plugins directories. # This is a very strange mount because it merges # many directories into one. class Puppet::FileServing::Mount::Plugins < Puppet::FileServing::Mount # Return an instance of the appropriate class. def find(relative_path, request) return nil unless mod = request.environment.modules.find { |m| m.plugin(relative_path) } path = mod.plugin(relative_path) path end def search(relative_path, request) # We currently only support one kind of search on plugins - return # them all. Puppet.debug("Warning: calling Plugins.search with empty module path.") if request.environment.modules.empty? paths = request.environment.modules.find_all { |mod| mod.plugins? }.collect { |mod| mod.plugin_directory } if paths.empty? # If the modulepath is valid then we still need to return a valid root # directory for the search, but make sure nothing inside it is # returned. request.options[:recurse] = false request.environment.modulepath.empty? ? nil : request.environment.modulepath else paths end end def valid? true end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/file_serving/mount/tasks.rb������������������������������������������������0000644�0052762�0001160�00000000720�13417161721�022364� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/file_serving/mount' class Puppet::FileServing::Mount::Tasks < Puppet::FileServing::Mount def find(path, request) raise _("No task specified") if path.to_s.empty? module_name, task_path = path.split("/", 2) return nil unless mod = request.environment.module(module_name) mod.task_file(task_path) end def search(path, request) if result = find(path, request) [result] end end def valid? true end end ������������������������������������������������puppet-5.5.10/lib/puppet/file_serving/terminus_helper.rb��������������������������������������������0000644�0052762�0001160�00000002072�13417161721�023304� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/file_serving' require 'puppet/file_serving/fileset' # Define some common methods for FileServing termini. module Puppet::FileServing::TerminusHelper # Create model instance for a file in a file server. def path2instance(request, path, options = {}) result = model.new(path, :relative_path => options[:relative_path]) result.links = request.options[:links] if request.options[:links] result.checksum_type = request.options[:checksum_type] if request.options[:checksum_type] result.source_permissions = request.options[:source_permissions] if request.options[:source_permissions] result.collect result end # Create model instances for all files in a fileset. def path2instances(request, *paths) filesets = paths.collect do |path| # Filesets support indirector requests as an options collection Puppet::FileServing::Fileset.new(path, request) end Puppet::FileServing::Fileset.merge(*filesets).collect do |file, base_path| path2instance(request, base_path, :relative_path => file) end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/file_serving/terminus_selector.rb������������������������������������������0000644�0052762�0001160�00000001561�13417161721�023647� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/file_serving' # This module is used to pick the appropriate terminus # in file-serving indirections. This is necessary because # the terminus varies based on the URI asked for. module Puppet::FileServing::TerminusSelector def select(request) # We rely on the request's parsing of the URI. # Short-circuit to :file if it's a fully-qualified path or specifies a 'file' protocol. if Puppet::Util.absolute_path?(request.key) return :file end case request.protocol when "file" :file when "puppet" if request.server :rest else Puppet[:default_file_terminus] end when "http","https" :http when nil :file_server else raise ArgumentError, _("URI protocol '%{protocol}' is not currently supported for file serving") % { protocol: request.protocol } end end end �����������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/file_serving/base.rb�������������������������������������������������������0000644�0052762�0001160�00000004664�13417161721�021022� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/file_serving' require 'puppet/util' require 'puppet/util/methodhelper' # The base class for Content and Metadata; provides common # functionality like the behaviour around links. class Puppet::FileServing::Base include Puppet::Util::MethodHelper # This is for external consumers to store the source that was used # to retrieve the metadata. attr_accessor :source # Does our file exist? def exist? stat return true rescue return false end # Return the full path to our file. Fails if there's no path set. def full_path if relative_path.nil? or relative_path == "" or relative_path == "." full_path = path else full_path = File.join(path, relative_path) end if Puppet.features.microsoft_windows? # Replace multiple slashes as long as they aren't at the beginning of a filename full_path.gsub(%r{(./)/+}, '\1') else full_path.gsub(%r{//+}, '/') end end def initialize(path, options = {}) self.path = path @links = :manage set_options(options) end # Determine how we deal with links. attr_reader :links def links=(value) value = value.to_sym value = :manage if value == :ignore #TRANSLATORS ':link', ':manage', ':follow' should not be translated raise(ArgumentError, _(":links can only be set to :manage or :follow")) unless [:manage, :follow].include?(value) @links = value end # Set our base path. attr_reader :path def path=(path) raise ArgumentError.new(_("Paths must be fully qualified")) unless Puppet::FileServing::Base.absolute?(path) @path = path end # Set a relative path; this is used for recursion, and sets # the file's path relative to the initial recursion point. attr_reader :relative_path def relative_path=(path) raise ArgumentError.new(_("Relative paths must not be fully qualified")) if Puppet::FileServing::Base.absolute?(path) @relative_path = path end # Stat our file, using the appropriate link-sensitive method. def stat @stat_method ||= self.links == :manage ? :lstat : :stat Puppet::FileSystem.send(@stat_method, full_path) end def to_data_hash { 'path' => @path, 'relative_path' => @relative_path, 'links' => @links.to_s } end def self.absolute?(path) Puppet::Util.absolute_path?(path, :posix) or (Puppet.features.microsoft_windows? and Puppet::Util.absolute_path?(path, :windows)) end end ����������������������������������������������������������������������������puppet-5.5.10/lib/puppet/file_serving/fileset.rb����������������������������������������������������0000644�0052762�0001160�00000012266�13417161721�021540� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'find' require 'puppet/file_serving' require 'puppet/file_serving/metadata' # Operate recursively on a path, returning a set of file paths. class Puppet::FileServing::Fileset attr_reader :path, :ignore, :links attr_accessor :recurse, :recurselimit, :checksum_type # Produce a hash of files, with merged so that earlier files # with the same postfix win. E.g., /dir1/subfile beats /dir2/subfile. # It's a hash because we need to know the relative path of each file, # and the base directory. # This will probably only ever be used for searching for plugins. def self.merge(*filesets) result = {} filesets.each do |fileset| fileset.files.each do |file| result[file] ||= fileset.path end end result end def initialize(path, options = {}) if Puppet.features.microsoft_windows? # REMIND: UNC path path = path.chomp(File::SEPARATOR) unless path =~ /^[A-Za-z]:\/$/ else path = path.chomp(File::SEPARATOR) unless path == File::SEPARATOR end raise ArgumentError.new(_("Fileset paths must be fully qualified: %{path}") % { path: path }) unless Puppet::Util.absolute_path?(path) @path = path # Set our defaults. self.ignore = [] self.links = :manage @recurse = false @recurselimit = :infinite if options.is_a?(Puppet::Indirector::Request) initialize_from_request(options) else initialize_from_hash(options) end raise ArgumentError.new(_("Fileset paths must exist")) unless valid?(path) #TRANSLATORS "recurse" and "recurselimit" are parameter names and should not be translated raise ArgumentError.new(_("Fileset recurse parameter must not be a number anymore, please use recurselimit")) if @recurse.is_a?(Integer) end # Return a list of all files in our fileset. This is different from the # normal definition of find in that we support specific levels # of recursion, which means we need to know when we're going another # level deep, which Find doesn't do. def files files = perform_recursion # Now strip off the leading path, so each file becomes relative, and remove # any slashes that might end up at the beginning of the path. result = files.collect { |file| file.sub(%r{^#{Regexp.escape(@path)}/*}, '') } # And add the path itself. result.unshift(".") result end def ignore=(values) values = [values] unless values.is_a?(Array) @ignore = values.collect(&:to_s) end def links=(links) links = links.to_sym #TRANSLATORS ":links" is a parameter name and should not be translated raise(ArgumentError, _("Invalid :links value '%{links}'") % { links: links }) unless [:manage, :follow].include?(links) @links = links @stat_method = @links == :manage ? :lstat : :stat end private def initialize_from_hash(options) options.each do |option, value| method = option.to_s + "=" begin send(method, value) rescue NoMethodError raise ArgumentError, _("Invalid option '%{option}'") % { option: option }, $!.backtrace end end end def initialize_from_request(request) [:links, :ignore, :recurse, :recurselimit, :checksum_type].each do |param| if request.options.include?(param) # use 'include?' so the values can be false value = request.options[param] elsif request.options.include?(param.to_s) value = request.options[param.to_s] end next if value.nil? value = true if value == "true" value = false if value == "false" value = Integer(value) if value.is_a?(String) and value =~ /^\d+$/ send(param.to_s + "=", value) end end FileSetEntry = Struct.new(:depth, :path, :ignored, :stat_method) do def down_level(to) FileSetEntry.new(depth + 1, File.join(path, to), ignored, stat_method) end def basename File.basename(path) end def children return [] unless directory? Dir.entries(path, encoding: Encoding::UTF_8). reject { |child| ignore?(child) }. collect { |child| down_level(child) } end def ignore?(child) return true if child == "." || child == ".." return false if ignored == [nil] ignored.any? { |pattern| File.fnmatch?(pattern, child) } end def directory? Puppet::FileSystem.send(stat_method, path).directory? rescue Errno::ENOENT, Errno::EACCES false end end # Pull the recursion logic into one place. It's moderately hairy, and this # allows us to keep the hairiness apart from what we do with the files. def perform_recursion current_dirs = [FileSetEntry.new(0, @path, @ignore, @stat_method)] result = [] while entry = current_dirs.shift if continue_recursion_at?(entry.depth + 1) entry.children.each do |child| result << child.path current_dirs << child end end end result end def valid?(path) Puppet::FileSystem.send(@stat_method, path) true rescue Errno::ENOENT, Errno::EACCES false end def continue_recursion_at?(depth) # recurse if told to, and infinite recursion or current depth not at the limit self.recurse && (self.recurselimit == :infinite || depth <= self.recurselimit) end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/file_serving/metadata.rb���������������������������������������������������0000644�0052762�0001160�00000011655�13417161721�021666� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet' require 'puppet/indirector' require 'puppet/file_serving' require 'puppet/file_serving/base' require 'puppet/util/checksums' require 'uri' # A class that handles retrieving file metadata. class Puppet::FileServing::Metadata < Puppet::FileServing::Base include Puppet::Util::Checksums extend Puppet::Indirector indirects :file_metadata, :terminus_class => :selector attr_reader :path, :owner, :group, :mode, :checksum_type, :checksum, :ftype, :destination, :source_permissions, :content_uri PARAM_ORDER = [:mode, :ftype, :owner, :group] def checksum_type=(type) raise(ArgumentError, _("Unsupported checksum type %{type}") % { type: type }) unless Puppet::Util::Checksums.respond_to?("#{type}_file") @checksum_type = type end def source_permissions=(source_permissions) raise(ArgumentError, _("Unsupported source_permission %{source_permissions}") % { source_permissions: source_permissions }) unless [:use, :use_when_creating, :ignore].include?(source_permissions.intern) @source_permissions = source_permissions.intern end def content_uri=(path) begin uri = URI.parse(Puppet::Util.uri_encode(path)) rescue URI::InvalidURIError => detail raise(ArgumentError, _("Could not understand URI %{path}: %{detail}") % { path: path, detail: detail }) end raise(ArgumentError, _("Cannot use opaque URLs '%{path}'") % { path: path }) unless uri.hierarchical? raise(ArgumentError, _("Must use URLs of type puppet as content URI")) if uri.scheme != "puppet" @content_uri = path.encode(Encoding::UTF_8) end class MetaStat extend Forwardable def initialize(stat, source_permissions) @stat = stat @source_permissions_ignore = (!source_permissions || source_permissions == :ignore) end def owner @source_permissions_ignore ? Process.euid : @stat.uid end def group @source_permissions_ignore ? Process.egid : @stat.gid end def mode @source_permissions_ignore ? 0644 : @stat.mode end def_delegators :@stat, :ftype end class WindowsStat < MetaStat if Puppet.features.microsoft_windows? require 'puppet/util/windows/security' end def initialize(stat, path, source_permissions) super(stat, source_permissions) @path = path raise(ArgumentError, _("Unsupported Windows source permissions option %{source_permissions}") % { source_permissions: source_permissions }) unless @source_permissions_ignore end { :owner => 'S-1-5-32-544', :group => 'S-1-0-0', :mode => 0644 }.each do |method, default_value| define_method method do return default_value end end end def collect_stat(path) stat = stat() if Puppet.features.microsoft_windows? WindowsStat.new(stat, path, @source_permissions) else MetaStat.new(stat, @source_permissions) end end # Retrieve the attributes for this file, relative to a base directory. # Note that Puppet::FileSystem.stat(path) raises Errno::ENOENT # if the file is absent and this method does not catch that exception. def collect(source_permissions = nil) real_path = full_path stat = collect_stat(real_path) @owner = stat.owner @group = stat.group @ftype = stat.ftype # We have to mask the mode, yay. @mode = stat.mode & 007777 case stat.ftype when "file" @checksum = ("{#{@checksum_type}}") + send("#{@checksum_type}_file", real_path).to_s when "directory" # Always just timestamp the directory. @checksum_type = "ctime" @checksum = ("{#{@checksum_type}}") + send("#{@checksum_type}_file", path).to_s when "link" @destination = Puppet::FileSystem.readlink(real_path) @checksum = ("{#{@checksum_type}}") + send("#{@checksum_type}_file", real_path).to_s rescue nil else raise ArgumentError, _("Cannot manage files of type %{file_type}") % { file_type: stat.ftype } end end def initialize(path,data={}) @owner = data.delete('owner') @group = data.delete('group') @mode = data.delete('mode') if checksum = data.delete('checksum') @checksum_type = checksum['type'] @checksum = checksum['value'] end @checksum_type ||= Puppet[:digest_algorithm] @ftype = data.delete('type') @destination = data.delete('destination') @source = data.delete('source') @content_uri = data.delete('content_uri') super(path,data) end def to_data_hash super.update( { 'owner' => owner, 'group' => group, 'mode' => mode, 'checksum' => { 'type' => checksum_type, 'value' => checksum }, 'type' => ftype, 'destination' => destination, }.merge(content_uri ? {'content_uri' => content_uri} : {}) .merge(source ? {'source' => source} : {}) ) end def self.from_data_hash(data) new(data.delete('path'), data) end end �����������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/file_system.rb�������������������������������������������������������������0000644�0052762�0001160�00000026771�13417161721�017762� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::FileSystem require 'puppet/util' require 'puppet/file_system/path_pattern' require 'puppet/file_system/file_impl' require 'puppet/file_system/memory_file' require 'puppet/file_system/memory_impl' require 'puppet/file_system/uniquefile' # create instance of the file system implementation to use for the current platform @impl = if Puppet::Util::Platform.windows? require 'puppet/file_system/windows' Puppet::FileSystem::Windows else require 'puppet/file_system/posix' Puppet::FileSystem::Posix end.new() # Allows overriding the filesystem for the duration of the given block. # The filesystem will only contain the given file(s). # # @param files [Puppet::FileSystem::MemoryFile] the files to have available # # @api private # def self.overlay(*files, &block) old_impl = @impl @impl = Puppet::FileSystem::MemoryImpl.new(*files) yield ensure @impl = old_impl end # Opens the given path with given mode, and options and optionally yields it to the given block. # # @param path [String, Pathname] the path to the file to operate on # @param mode [Integer] The mode to apply to the file if it is created # @param options [String] Extra file operation mode information to use # This is the standard mechanism Ruby uses in the IO class, and therefore # encoding may be specified explicitly as fmode : encoding or fmode : "BOM|UTF-*" # for example, a:ASCII or w+:UTF-8 # @yield The file handle, in the mode given by options, else read-write mode # @return [Void] # # @api public # def self.open(path, mode, options, &block) @impl.open(assert_path(path), mode, options, &block) end # @return [Object] The directory of this file as an opaque handle # # @api public # def self.dir(path) @impl.dir(assert_path(path)) end # @return [String] The directory of this file as a String # # @api public # def self.dir_string(path) @impl.path_string(@impl.dir(assert_path(path))) end # @return [Boolean] Does the directory of the given path exist? def self.dir_exist?(path) @impl.exist?(@impl.dir(assert_path(path))) end # Creates all directories down to (inclusive) the dir of the given path def self.dir_mkpath(path) @impl.mkpath(@impl.dir(assert_path(path))) end # @return [Object] the name of the file as a opaque handle # # @api public # def self.basename(path) @impl.basename(assert_path(path)) end # @return [String] the name of the file # # @api public # def self.basename_string(path) @impl.path_string(@impl.basename(assert_path(path))) end # @return [Integer] the size of the file # # @api public # def self.size(path) @impl.size(assert_path(path)) end # Allows exclusive updates to a file to be made by excluding concurrent # access using flock. This means that if the file is on a filesystem that # does not support flock, this method will provide no protection. # # While polling to acquire the lock the process will wait ever increasing # amounts of time in order to prevent multiple processes from wasting # resources. # # @param path [Pathname] the path to the file to operate on # @param mode [Integer] The mode to apply to the file if it is created # @param options [String] Extra file operation mode information to use # (defaults to read-only mode 'r') # This is the standard mechanism Ruby uses in the IO class, and therefore # encoding may be specified explicitly as fmode : encoding or fmode : "BOM|UTF-*" # for example, a:ASCII or w+:UTF-8 # @param timeout [Integer] Number of seconds to wait for the lock (defaults to 300) # @yield The file handle, in the mode given by options, else read-write mode # @return [Void] # @raise [Timeout::Error] If the timeout is exceeded while waiting to acquire the lock # # @api public # def self.exclusive_open(path, mode, options = 'r', timeout = 300, &block) @impl.exclusive_open(assert_path(path), mode, options, timeout, &block) end # Processes each line of the file by yielding it to the given block # # @api public # def self.each_line(path, &block) @impl.each_line(assert_path(path), &block) end # @return [String] The contents of the file # # @api public # def self.read(path, opts = {}) @impl.read(assert_path(path), opts) end # Read a file keeping the original line endings intact. This # attempts to open files using binary mode using some encoding # overrides and falling back to IO.read when none of the # encodings are valid. # # @return [String] The contents of the file # # @api public # def self.read_preserve_line_endings(path) @impl.read_preserve_line_endings(assert_path(path)) end # @return [String] The binary contents of the file # # @api public # def self.binread(path) @impl.binread(assert_path(path)) end # Determines if a file exists by verifying that the file can be stat'd. # Will follow symlinks and verify that the actual target path exists. # # @return [Boolean] true if the named file exists. # # @api public # def self.exist?(path) @impl.exist?(assert_path(path)) end # Determines if a file is a directory. # # @return [Boolean] true if the given file is a directory. # # @api public def self.directory?(path) @impl.directory?(assert_path(path)) end # Determines if a file is a file. # # @return [Boolean] true if the given file is a file. # # @api public def self.file?(path) @impl.file?(assert_path(path)) end # Determines if a file is executable. # # @todo Should this take into account extensions on the windows platform? # # @return [Boolean] true if this file can be executed # # @api public # def self.executable?(path) @impl.executable?(assert_path(path)) end # @return [Boolean] Whether the file is writable by the current process # # @api public # def self.writable?(path) @impl.writable?(assert_path(path)) end # Touches the file. On most systems this updates the mtime of the file. # # @api public # def self.touch(path) @impl.touch(assert_path(path)) end # Creates directories for all parts of the given path. # # @api public # def self.mkpath(path) @impl.mkpath(assert_path(path)) end # @return [Array<Object>] references to all of the children of the given # directory path, excluding `.` and `..`. # @api public def self.children(path) @impl.children(assert_path(path)) end # Creates a symbolic link dest which points to the current file. # If dest already exists: # # * and is a file, will raise Errno::EEXIST # * and is a directory, will return 0 but perform no action # * and is a symlink referencing a file, will raise Errno::EEXIST # * and is a symlink referencing a directory, will return 0 but perform no action # # With the :force option set to true, when dest already exists: # # * and is a file, will replace the existing file with a symlink (DANGEROUS) # * and is a directory, will return 0 but perform no action # * and is a symlink referencing a file, will modify the existing symlink # * and is a symlink referencing a directory, will return 0 but perform no action # # @param dest [String] The path to create the new symlink at # @param [Hash] options the options to create the symlink with # @option options [Boolean] :force overwrite dest # @option options [Boolean] :noop do not perform the operation # @option options [Boolean] :verbose verbose output # # @raise [Errno::EEXIST] dest already exists as a file and, :force is not set # # @return [Integer] 0 # # @api public # def self.symlink(path, dest, options = {}) @impl.symlink(assert_path(path), dest, options) end # @return [Boolean] true if the file is a symbolic link. # # @api public # def self.symlink?(path) @impl.symlink?(assert_path(path)) end # @return [String] the name of the file referenced by the given link. # # @api public # def self.readlink(path) @impl.readlink(assert_path(path)) end # Deletes the given paths, returning the number of names passed as arguments. # See also Dir::rmdir. # # @raise an exception on any error. # # @return [Integer] the number of paths passed as arguments # # @api public # def self.unlink(*paths) @impl.unlink(*(paths.map {|p| assert_path(p) })) end # @return [File::Stat] object for the named file. # # @api public # def self.stat(path) @impl.stat(assert_path(path)) end # @return [Integer] the size of the file # # @api public # def self.size(path) @impl.size(assert_path(path)) end # @return [File::Stat] Same as stat, but does not follow the last symbolic # link. Instead, reports on the link itself. # # @api public # def self.lstat(path) @impl.lstat(assert_path(path)) end # Compares the contents of this file against the contents of a stream. # # @param stream [IO] The stream to compare the contents against # @return [Boolean] Whether the contents were the same # # @api public # def self.compare_stream(path, stream) @impl.compare_stream(assert_path(path), stream) end # Produces an opaque pathname "handle" object representing the given path. # Different implementations of the underlying file system may use different runtime # objects. The produced "handle" should be used in all other operations # that take a "path". No operation should be directly invoked on the returned opaque object # # @param path [String] The string representation of the path # @return [Object] An opaque path handle on which no operations should be directly performed # # @api public # def self.pathname(path) @impl.pathname(path) end # Produces a string representation of the opaque path handle, with expansions # performed on ~. For Windows, this means that C:\Users\Admini~1\AppData will # be expanded to C:\Users\Administrator\AppData. On POSIX filesystems, the # value ~ will be expanded to something like /Users/Foo # # This method exists primarlily to resolve a Ruby deficiency where # File.expand_path doesn't handle ~ in each segment on a Windows path # # @param path [Object] a path handle produced by {#pathname} # @return [String] a string representation of the path # def self.expand_path(path, dir_string = nil) @impl.expand_path(path, dir_string) end # Asserts that the given path is of the expected type produced by #pathname # # @raise [ArgumentError] when path is not of the expected type # # @api public # def self.assert_path(path) @impl.assert_path(path) end # Produces a string representation of the opaque path handle. # # @param path [Object] a path handle produced by {#pathname} # @return [String] a string representation of the path # def self.path_string(path) @impl.path_string(path) end # Create and open a file for write only if it doesn't exist. # # @see Puppet::FileSystem::open # # @raise [Errno::EEXIST] path already exists. # # @api public # def self.exclusive_create(path, mode, &block) @impl.exclusive_create(assert_path(path), mode, &block) end # Changes permission bits on the named path to the bit pattern represented # by mode. # # @param mode [Integer] The mode to apply to the file if it is created # @param path [String] The path to the file, can also accept [PathName] # # @raise [Errno::ENOENT]: path doesn't exist # # @api public # def self.chmod(mode, path) @impl.chmod(mode, path) end end �������puppet-5.5.10/lib/puppet/file_system/���������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�017425� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/file_system/file_impl.rb���������������������������������������������������0000644�0052762�0001160�00000005547�13417161721�021720� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Abstract implementation of the Puppet::FileSystem # class Puppet::FileSystem::FileImpl def pathname(path) path.is_a?(Pathname) ? path : Pathname.new(path) end def assert_path(path) return path if path.is_a?(Pathname) # Some paths are string, or in the case of WatchedFile, it pretends to be # one by implementing to_str. if path.respond_to?(:to_str) Pathname.new(path) else raise ArgumentError, _("FileSystem implementation expected Pathname, got: '%{klass}'") % { klass: path.class } end end def path_string(path) path.to_s end def expand_path(path, dir_string = nil) # ensure `nil` values behave like underlying File.expand_path ::File.expand_path(path.nil? ? nil : path_string(path), dir_string) end def open(path, mode, options, &block) ::File.open(path, options, mode, &block) end def dir(path) path.dirname end def basename(path) path.basename.to_s end def size(path) path.size end def exclusive_create(path, mode, &block) opt = File::CREAT | File::EXCL | File::WRONLY self.open(path, mode, opt, &block) end def exclusive_open(path, mode, options = 'r', timeout = 300, &block) wait = 0.001 + (Kernel.rand / 1000) written = false while !written ::File.open(path, options, mode) do |rf| if rf.flock(::File::LOCK_EX|::File::LOCK_NB) yield rf written = true else sleep wait timeout -= wait wait *= 2 if timeout < 0 raise Timeout::Error, _("Timeout waiting for exclusive lock on %{path}") % { path: @path } end end end end end def each_line(path, &block) ::File.open(path) do |f| f.each_line do |line| yield line end end end def read(path, opts = {}) path.read(opts) end def read_preserve_line_endings(path) read(path) end def binread(path) raise NotImplementedError end def exist?(path) ::File.exist?(path) end def directory?(path) ::File.directory?(path) end def file?(path) ::File.file?(path) end def executable?(path) ::File.executable?(path) end def writable?(path) path.writable? end def touch(path) ::FileUtils.touch(path) end def mkpath(path) path.mkpath end def children(path) path.children end def symlink(path, dest, options = {}) FileUtils.symlink(path, dest, options) end def symlink?(path) File.symlink?(path) end def readlink(path) File.readlink(path) end def unlink(*paths) File.unlink(*paths) end def stat(path) File.stat(path) end def lstat(path) File.lstat(path) end def compare_stream(path, stream) open(path, 0, 'rb') { |this| FileUtils.compare_stream(this, stream) } end def chmod(mode, path) FileUtils.chmod(mode, path) end end ���������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/file_system/memory_file.rb�������������������������������������������������0000644�0052762�0001160�00000002657�13417161721�022266� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# An in-memory file abstraction. Commonly used with Puppet::FileSystem::File#overlay # @api private class Puppet::FileSystem::MemoryFile attr_reader :path, :children def self.a_missing_file(path) new(path, :exist? => false, :executable? => false) end def self.a_regular_file_containing(path, content) new(path, :exist? => true, :executable? => false, :content => content) end def self.an_executable(path) new(path, :exist? => true, :executable? => true) end def self.a_directory(path, children = []) new(path, :exist? => true, :excutable? => true, :directory? => true, :children => children) end def initialize(path, properties) @path = path @properties = properties @children = (properties[:children] || []).collect do |child| child.duplicate_as(File.join(@path, child.path)) end end def directory?; @properties[:directory?]; end def exist?; @properties[:exist?]; end def executable?; @properties[:executable?]; end def each_line(&block) handle.each_line(&block) end def handle raise Errno::ENOENT unless exist? StringIO.new(@properties[:content] || '') end def duplicate_as(other_path) self.class.new(other_path, @properties) end def absolute? Pathname.new(path).absolute? end def to_path path end def to_s to_path end def inspect "<Puppet::FileSystem::MemoryFile:#{to_s}>" end end ���������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/file_system/memory_impl.rb�������������������������������������������������0000644�0052762�0001160�00000002752�13417161721�022304� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::FileSystem::MemoryImpl def initialize(*files) @files = files + all_children_of(files) end def expand_path(path, dir_string = nil) File.expand_path(path, dir_string) end def exist?(path) path.exist? end def directory?(path) path.directory? end def file?(path) path.file? end def executable?(path) path.executable? end def children(path) path.children end def each_line(path, &block) path.each_line(&block) end def pathname(path) find(path) || Puppet::FileSystem::MemoryFile.a_missing_file(path) end def basename(path) path.duplicate_as(File.basename(path_string(path))) end def path_string(object) object.path end def read(path, opts = {}) handle = assert_path(path).handle handle.read end def read_preserve_line_endings(path) read(path) end def open(path, *args, &block) handle = assert_path(path).handle if block_given? yield handle else return handle end end def assert_path(path) if path.is_a?(Puppet::FileSystem::MemoryFile) path else find(path) or raise ArgumentError, _("Unable to find registered object for %{path}") % { path: path.inspect } end end private def find(path) @files.find { |file| file.path == path } end def all_children_of(files) children = files.collect(&:children).flatten if children.empty? [] else children + all_children_of(children) end end end ����������������������puppet-5.5.10/lib/puppet/file_system/path_pattern.rb������������������������������������������������0000644�0052762�0001160�00000004424�13417161721�022442� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'pathname' require 'puppet/error' module Puppet::FileSystem class PathPattern class InvalidPattern < Puppet::Error; end TRAVERSAL = /^\.\.$/ ABSOLUTE_UNIX = /^\// ABSOLUTE_WINDOWS = /^[a-z]:/i #ABSOLUT_VODKA #notappearinginthisclass CURRENT_DRIVE_RELATIVE_WINDOWS = /^\\/ def self.relative(pattern) RelativePathPattern.new(pattern) end def self.absolute(pattern) AbsolutePathPattern.new(pattern) end class << self protected :new end # @param prefix [AbsolutePathPattern] An absolute path pattern instance # @return [AbsolutePathPattern] A new AbsolutePathPattern prepended with # the passed prefix's pattern. def prefix_with(prefix) new_pathname = prefix.pathname + pathname self.class.absolute(new_pathname.to_s) end def glob Dir.glob(pathname.to_s) end def to_s pathname.to_s end protected attr_reader :pathname private def validate @pathname.each_filename do |e| if e =~ TRAVERSAL raise(InvalidPattern, _("PathPatterns cannot be created with directory traversals.")) end end case @pathname.to_s when CURRENT_DRIVE_RELATIVE_WINDOWS raise(InvalidPattern, _("A PathPattern cannot be a Windows current drive relative path.")) end end def initialize(pattern) begin @pathname = Pathname.new(pattern.strip) rescue ArgumentError => error raise InvalidPattern.new(_("PathPatterns cannot be created with a zero byte."), error) end validate end end class RelativePathPattern < PathPattern def absolute? false end def validate super case @pathname.to_s when ABSOLUTE_WINDOWS raise(InvalidPattern, _("A relative PathPattern cannot be prefixed with a drive.")) when ABSOLUTE_UNIX raise(InvalidPattern, _("A relative PathPattern cannot be an absolute path.")) end end end class AbsolutePathPattern < PathPattern def absolute? true end def validate super if @pathname.to_s !~ ABSOLUTE_UNIX and @pathname.to_s !~ ABSOLUTE_WINDOWS raise(InvalidPattern, _("An absolute PathPattern cannot be a relative path.")) end end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/file_system/posix.rb�������������������������������������������������������0000644�0052762�0001160�00000002200�13417161721�021101� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::FileSystem::Posix < Puppet::FileSystem::FileImpl def binread(path) path.binread end # Provide an encoding agnostic version of compare_stream # # The FileUtils implementation in Ruby 2.0+ was modified in a manner where # it cannot properly compare File and StringIO instances. To sidestep that # issue this method reimplements the faster 2.0 version that will correctly # compare binary File and StringIO streams. def compare_stream(path, stream) open(path, 0, 'rb') do |this| bsize = stream_blksize(this, stream) sa = "".force_encoding('ASCII-8BIT') sb = "".force_encoding('ASCII-8BIT') begin this.read(bsize, sa) stream.read(bsize, sb) return true if sa.empty? && sb.empty? end while sa == sb false end end private def stream_blksize(*streams) streams.each do |s| next unless s.respond_to?(:stat) size = blksize(s.stat) return size if size end default_blksize() end def blksize(st) s = st.blksize return nil unless s return nil if s == 0 s end def default_blksize 1024 end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/file_system/uniquefile.rb��������������������������������������������������0000644�0052762�0001160�00000010172�13417161721�022114� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/file_system' require 'delegate' require 'tmpdir' # A class that provides `Tempfile`-like capabilities, but does not attempt to # manage the deletion of the file for you. API is identical to the # normal `Tempfile` class. # # @api public class Puppet::FileSystem::Uniquefile < DelegateClass(File) # Convenience method which ensures that the file is closed and # unlinked before returning # # @param identifier [String] additional part of generated pathname # @yieldparam file [File] the temporary file object # @return result of the passed block # @api private def self.open_tmp(identifier) f = new(identifier) yield f ensure if f f.close! end end def initialize(basename, *rest) create_tmpname(basename, *rest) do |tmpname, n, opts| mode = File::RDWR|File::CREAT|File::EXCL perm = 0600 if opts mode |= opts.delete(:mode) || 0 opts[:perm] = perm perm = nil else opts = perm end self.class.locking(tmpname) do @tmpfile = File.open(tmpname, mode, opts) @tmpname = tmpname end @mode = mode & ~(File::CREAT|File::EXCL) perm or opts.freeze @opts = opts end super(@tmpfile) end # Opens or reopens the file with mode "r+". def open @tmpfile.close if @tmpfile @tmpfile = File.open(@tmpname, @mode, @opts) __setobj__(@tmpfile) end def _close begin @tmpfile.close if @tmpfile ensure @tmpfile = nil end end protected :_close def close(unlink_now=false) if unlink_now close! else _close end end def close! _close unlink end def unlink return unless @tmpname begin File.unlink(@tmpname) rescue Errno::ENOENT rescue Errno::EACCES # may not be able to unlink on Windows; just ignore return end @tmpname = nil end alias delete unlink # Returns the full path name of the temporary file. # This will be nil if #unlink has been called. def path @tmpname end private def make_tmpname(prefix_suffix, n) case prefix_suffix when String prefix = prefix_suffix suffix = "" when Array prefix = prefix_suffix[0] suffix = prefix_suffix[1] else raise ArgumentError, _("unexpected prefix_suffix: %{value}") % { value: prefix_suffix.inspect } end t = Time.now.strftime("%Y%m%d") path = "#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}" path << "-#{n}" if n path << suffix end def create_tmpname(basename, *rest) if opts = try_convert_to_hash(rest[-1]) opts = opts.dup if rest.pop.equal?(opts) max_try = opts.delete(:max_try) opts = [opts] else opts = [] end tmpdir, = *rest if $SAFE > 0 and tmpdir.tainted? tmpdir = '/tmp' else tmpdir ||= tmpdir() end n = nil begin path = File.expand_path(make_tmpname(basename, n), tmpdir) yield(path, n, *opts) rescue Errno::EEXIST n ||= 0 n += 1 retry if !max_try or n < max_try raise _("cannot generate temporary name using `%{basename}' under `%{tmpdir}'") % { basename: basename, tmpdir: tmpdir } end path end def try_convert_to_hash(h) begin h.to_hash rescue NoMethodError nil end end @@systmpdir ||= defined?(Etc.systmpdir) ? Etc.systmpdir : '/tmp' def tmpdir tmp = '.' if $SAFE > 0 @@systmpdir else for dir in [ Puppet::Util.get_env('TMPDIR'), Puppet::Util.get_env('TMP'), Puppet::Util.get_env('TEMP'), @@systmpdir, '/tmp'] if dir and stat = File.stat(dir) and stat.directory? and stat.writable? tmp = dir break end rescue nil end File.expand_path(tmp) end end class << self # yields with locking for +tmpname+ and returns the result of the # block. def locking(tmpname) lock = tmpname + '.lock' mkdir(lock) yield ensure rmdir(lock) if Puppet::FileSystem.exist?(lock) end def mkdir(*args) Dir.mkdir(*args) end def rmdir(*args) Dir.rmdir(*args) end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/file_system/windows.rb�����������������������������������������������������0000644�0052762�0001160�00000010165�13417161721�021442� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/file_system/posix' require 'puppet/util/windows' class Puppet::FileSystem::Windows < Puppet::FileSystem::Posix def open(path, mode, options, &block) # PUP-6959 mode is explicitly ignored until it can be implemented # Ruby on Windows uses mode for setting file attributes like read-only and # archived, not for setting permissions like POSIX raise TypeError.new('mode must be specified as an Integer') if mode && !mode.is_a?(Numeric) ::File.open(path, options, nil, &block) end def expand_path(path, dir_string = nil) # ensure `nil` values behave like underlying File.expand_path string_path = ::File.expand_path(path.nil? ? nil : path_string(path), dir_string) # if no tildes, nothing to expand, no need to call Windows API, return original string return string_path if !string_path.index('~') begin # no need to do existence check up front as GetLongPathName implies that check is performed # and it should be the exception that files aren't actually present string_path = Puppet::Util::Windows::File.get_long_pathname(string_path) rescue Puppet::Util::Windows::Error => e # preserve original File.expand_path behavior for file / path not found by returning string raise if (e.code != Puppet::Util::Windows::File::ERROR_FILE_NOT_FOUND && e.code != Puppet::Util::Windows::File::ERROR_PATH_NOT_FOUND) end string_path end def exist?(path) return Puppet::Util::Windows::File.exist?(path) end def symlink(path, dest, options = {}) raise_if_symlinks_unsupported dest_exists = exist?(dest) # returns false on dangling symlink dest_stat = Puppet::Util::Windows::File.stat(dest) if dest_exists # silent fail to preserve semantics of original FileUtils return 0 if dest_exists && dest_stat.ftype == 'directory' if dest_exists && dest_stat.ftype == 'file' && options[:force] != true raise(Errno::EEXIST, _("%{dest} already exists and the :force option was not specified") % { dest: dest }) end if options[:noop] != true ::File.delete(dest) if dest_exists # can only be file Puppet::Util::Windows::File.symlink(path, dest) end 0 end def symlink?(path) return false if ! Puppet.features.manages_symlinks? Puppet::Util::Windows::File.symlink?(path) end def readlink(path) raise_if_symlinks_unsupported Puppet::Util::Windows::File.readlink(path) end def unlink(*file_names) if ! Puppet.features.manages_symlinks? return ::File.unlink(*file_names) end file_names.each do |file_name| file_name = file_name.to_s # handle PathName stat = Puppet::Util::Windows::File.stat(file_name) rescue nil # sigh, Ruby + Windows :( if !stat ::File.unlink(file_name) rescue Dir.rmdir(file_name) elsif stat.ftype == 'directory' if Puppet::Util::Windows::File.symlink?(file_name) Dir.rmdir(file_name) else raise Errno::EPERM.new(file_name) end else ::File.unlink(file_name) end end file_names.length end def stat(path) Puppet::Util::Windows::File.stat(path) end def lstat(path) if ! Puppet.features.manages_symlinks? return Puppet::Util::Windows::File.stat(path) end Puppet::Util::Windows::File.lstat(path) end def chmod(mode, path) Puppet::Util::Windows::Security.set_mode(mode, path.to_s) end def read_preserve_line_endings(path) contents = path.read( :mode => 'rb', :encoding => Encoding::UTF_8) contents = path.read( :mode => 'rb', :encoding => Encoding::default_external) unless contents.valid_encoding? contents = path.read unless contents.valid_encoding? contents end private def raise_if_symlinks_unsupported if ! Puppet.features.manages_symlinks? msg = _("This version of Windows does not support symlinks. Windows Vista / 2008 or higher is required.") raise Puppet::Util::Windows::Error.new(msg) end if ! Puppet::Util::Windows::Process.process_privilege_symlink? Puppet.warning _("The current user does not have the necessary permission to manage symlinks.") end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/forge/���������������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�016204� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/forge/cache.rb�������������������������������������������������������������0000644�0052762�0001160�00000003217�13417161721�017572� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'uri' require 'puppet/forge' class Puppet::Forge # = Cache # # Provides methods for reading files from local cache, filesystem or network. class Cache # Instantiate new cache for the +repository+ instance. def initialize(repository, options = {}) @repository = repository @options = options end # Return filename retrieved from +uri+ instance. Will download this file and # cache it if needed. # # TODO: Add checksum support. # TODO: Add error checking. def retrieve(url) (path + File.basename(url.to_s)).tap do |cached_file| uri = url.is_a?(::URI) ? url : ::URI.parse(url) unless cached_file.file? if uri.scheme == 'file' # CGI.unescape butchers Uris that are escaped properly FileUtils.cp(URI.unescape(uri.path), cached_file) else # TODO: Handle HTTPS; probably should use repository.contact data = read_retrieve(uri) cached_file.open('wb') { |f| f.write data } end end end end # Return contents of file at the given URI's +uri+. def read_retrieve(uri) return uri.read end # Return Pathname for repository's cache directory, create it if needed. def path (self.class.base_path + @repository.cache_key).tap{ |o| o.mkpath } end # Return the base Pathname for all the caches. def self.base_path (Pathname(Puppet.settings[:module_working_dir]) + 'cache').tap do |o| o.mkpath unless o.exist? end end # Clean out all the caches. def self.clean base_path.rmtree if base_path.exist? end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/forge/errors.rb������������������������������������������������������������0000644�0052762�0001160�00000010201�13417161721�020032� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/json' require 'puppet/error' require 'puppet/forge' # Puppet::Forge specific exceptions module Puppet::Forge::Errors # This exception is the parent for all Forge API errors class ForgeError < Puppet::Error # This is normally set by the child class, but if it is not this will # fall back to displaying the message as a multiline. # # @return [String] the multiline version of the error message def multiline self.message end end # This exception is raised when there is an SSL verification error when # communicating with the forge. class SSLVerifyError < ForgeError # @option options [String] :uri The URI that failed # @option options [String] :original the original exception def initialize(options) @uri = options[:uri] original = options[:original] super(_("Unable to verify the SSL certificate at %{uri}") % { uri: @uri }, original) end # Return a multiline version of the error message # # @return [String] the multiline version of the error message def multiline message = [] message << _('Could not connect via HTTPS to %{uri}') % { uri: @uri } message << _(' Unable to verify the SSL certificate') message << _(' The certificate may not be signed by a valid CA') message << _(' The CA bundle included with OpenSSL may not be valid or up to date') message.join("\n") end end # This exception is raised when there is a communication error when connecting # to the forge class CommunicationError < ForgeError # @option options [String] :uri The URI that failed # @option options [String] :original the original exception def initialize(options) @uri = options[:uri] original = options[:original] @detail = original.message message = _("Unable to connect to the server at %{uri}. Detail: %{detail}.") % { uri: @uri, detail: @detail } super(message, original) end # Return a multiline version of the error message # # @return [String] the multiline version of the error message def multiline message = [] message << _('Could not connect to %{uri}') % { uri: @uri } message << _(' There was a network communications problem') message << _(" The error we caught said '%{detail}'") % { detail: @detail } message << _(' Check your network connection and try again') message.join("\n") end end # This exception is raised when there is a bad HTTP response from the forge # and optionally a message in the response. class ResponseError < ForgeError # @option options [String] :uri The URI that failed # @option options [String] :input The user's input (e.g. module name) # @option options [String] :message Error from the API response (optional) # @option options [Net::HTTPResponse] :response The original HTTP response def initialize(options) @uri = options[:uri] @message = options[:message] response = options[:response] @response = "#{response.code} #{response.message.strip}" begin body = Puppet::Util::Json.load(response.body) if body['message'] @message ||= body['message'].strip end rescue Puppet::Util::Json::ParseError end message = if @message _("Request to Puppet Forge failed.") + ' ' + _("Detail: %{detail}.") % { detail: "#{@message} / #{@response}" } else _("Request to Puppet Forge failed.") + ' ' + _("Detail: %{detail}.") % { detail: @response } end super(message, original) end # Return a multiline version of the error message # # @return [String] the multiline version of the error message def multiline message = [] message << _('Request to Puppet Forge failed.') message << _(' The server being queried was %{uri}') % { uri: @uri } message << _(" The HTTP response we received was '%{response}'") % { response: @response } message << _(" The message we received said '%{message}'") % { message: @message } if @message message.join("\n") end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/forge/repository.rb��������������������������������������������������������0000644�0052762�0001160�00000010633�13417161721�020746� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'net/https' require 'digest/sha1' require 'uri' require 'puppet/util/http_proxy' require 'puppet/forge' require 'puppet/forge/errors' require 'puppet/network/http' class Puppet::Forge # = Repository # # This class is a file for accessing remote repositories with modules. class Repository include Puppet::Forge::Errors attr_reader :uri, :cache # List of Net::HTTP exceptions to catch NET_HTTP_EXCEPTIONS = [ EOFError, Errno::ECONNABORTED, Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EINVAL, Errno::ETIMEDOUT, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError, SocketError, ] if Puppet.features.zlib? NET_HTTP_EXCEPTIONS << Zlib::GzipFile::Error end # Instantiate a new repository instance rooted at the +url+. # The library will report +for_agent+ in the User-Agent to the repository. def initialize(host, for_agent) @host = host @agent = for_agent @cache = Cache.new(self) @uri = URI.parse(host) end # Return a Net::HTTPResponse read for this +path+. def make_http_request(path, io = nil) request = get_request_object(@uri.path.chomp('/')+path) Puppet.debug "HTTP GET #{@host}#{request.path}" return read_response(request, io) end def forge_authorization if Puppet[:forge_authorization] Puppet[:forge_authorization] elsif Puppet.features.pe_license? PELicense.load_license_key.authorization_token end end # responsible for properly encoding a URI def get_request_object(path) headers = { "User-Agent" => user_agent, } if Puppet.features.zlib? headers = headers.merge({ "Accept-Encoding" => Puppet::Network::HTTP::Compression::ACCEPT_ENCODING }) end if forge_authorization headers = headers.merge({"Authorization" => forge_authorization}) end request = Net::HTTP::Get.new(Puppet::Util.uri_encode(path), headers) unless @uri.user.nil? || @uri.password.nil? || forge_authorization request.basic_auth(@uri.user, @uri.password) end return request end # Return a Net::HTTPResponse read from this HTTPRequest +request+. # # @param request [Net::HTTPRequest] request to make # @return [Net::HTTPResponse] response from request # @raise [Puppet::Forge::Errors::CommunicationError] if there is a network # related error # @raise [Puppet::Forge::Errors::SSLVerifyError] if there is a problem # verifying the remote SSL certificate def read_response(request, io = nil) http_object = Puppet::Util::HttpProxy.get_http_object(uri) http_object.start do |http| response = http.request(request) if Puppet.features.zlib? if response && response.key?("content-encoding") case response["content-encoding"] when "gzip" response.body = Zlib::GzipReader.new(StringIO.new(response.read_body), :encoding => "ASCII-8BIT").read response.delete("content-encoding") when "deflate" response.body = Zlib::Inflate.inflate(response.read_body) response.delete("content-encoding") end end end io.write(response.body) if io.respond_to? :write response end rescue *NET_HTTP_EXCEPTIONS => e raise CommunicationError.new(:uri => @uri.to_s, :original => e) rescue OpenSSL::SSL::SSLError => e if e.message =~ /certificate verify failed/ raise SSLVerifyError.new(:uri => @uri.to_s, :original => e) else raise e end end # Return the local file name containing the data downloaded from the # repository at +release+ (e.g. "myuser-mymodule"). def retrieve(release) path = @host.chomp('/') + release return cache.retrieve(path) end # Return the URI string for this repository. def to_s "#<#{self.class} #{@host}>" end # Return the cache key for this repository, this a hashed string based on # the URI. def cache_key return @cache_key ||= [ @host.to_s.gsub(/[^[:alnum:]]+/, '_').sub(/_$/, ''), Digest::SHA1.hexdigest(@host.to_s) ].join('-').freeze end private def user_agent @user_agent ||= [ @agent, Puppet[:http_user_agent] ].join(' ').freeze end end end �����������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/�����������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�017112� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/alert.rb���������������������������������������������������������0000644�0052762�0001160�00000000576�13417161721�020551� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Logs a message on the server at level `alert`. Puppet::Functions.create_function(:alert, Puppet::Functions::InternalFunction) do # @param values The values to log. # @return [Undef] dispatch :alert do scope_param repeated_param 'Any', :values return_type 'Undef' end def alert(scope, *values) Puppet::Util::Log.log_func(scope, :alert, values) end end ����������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/all.rb�����������������������������������������������������������0000644�0052762�0001160�00000006120�13417161721�020201� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Runs a [lambda](https://puppet.com/docs/puppet/latest/lang_lambdas.html) # repeatedly using each value in a data structure until the lambda returns a non "truthy" value which # makes the function return `false`, or if the end of the iteration is reached, `true` is returned. # # This function takes two mandatory arguments, in this order: # # 1. An array, hash, or other iterable object that the function will iterate over. # 2. A lambda, which the function calls for each element in the first argument. It can # request one or two parameters. # # @example Using the `all` function # # `$data.all |$parameter| { <PUPPET CODE BLOCK> }` # # or # # `all($data) |$parameter| { <PUPPET CODE BLOCK> }` # # @example Using the `all` function with an Array and a one-parameter lambda # # ```puppet # # For the array $data, run a lambda that checks that all values are multiples of 10 # $data = [10, 20, 30] # notice $data.all |$item| { $item % 10 == 0 } # ``` # # Would notice `true`. # # When the first argument is a `Hash`, Puppet passes each key and value pair to the lambda # as an array in the form `[key, value]`. # # @example Using the `all` function with a `Hash` and a one-parameter lambda # # ```puppet # # For the hash $data, run a lambda using each item as a key-value array # $data = { 'a_0'=> 10, 'b_1' => 20 } # notice $data.all |$item| { $item[1] % 10 == 0 } # ``` # # Would notice `true` if all values in the hash are multiples of 10. # # When the lambda accepts two arguments, the first argument gets the index in an array # or the key from a hash, and the second argument the value. # # # @example Using the `all` function with a hash and a two-parameter lambda # # ```puppet # # Check that all values are a multiple of 10 and keys start with 'abc' # $data = {abc_123 => 10, abc_42 => 20, abc_blue => 30} # notice $data.all |$key, $value| { $value % 10 == 0 and $key =~ /^abc/ } # ``` # # Would notice true. # # For an general examples that demonstrates iteration, see the Puppet # [iteration](https://puppet.com/docs/puppet/latest/lang_iteration.html) # documentation. # # @since 5.2.0 # Puppet::Functions.create_function(:all) do dispatch :all_Hash_2 do param 'Hash[Any, Any]', :hash block_param 'Callable[2,2]', :block end dispatch :all_Hash_1 do param 'Hash[Any, Any]', :hash block_param 'Callable[1,1]', :block end dispatch :all_Enumerable_2 do param 'Iterable', :enumerable block_param 'Callable[2,2]', :block end dispatch :all_Enumerable_1 do param 'Iterable', :enumerable block_param 'Callable[1,1]', :block end def all_Hash_1(hash) hash.each_pair.all? { |x| yield(x) } end def all_Hash_2(hash) hash.each_pair.all? { |x,y| yield(x,y) } end def all_Enumerable_1(enumerable) Puppet::Pops::Types::Iterable.asserted_iterable(self, enumerable).all? { |e| yield(e) } end def all_Enumerable_2(enumerable) enum = Puppet::Pops::Types::Iterable.asserted_iterable(self, enumerable) if enum.hash_style? enum.all? { |entry| yield(*entry) } else enum.each_with_index { |e, i| return false unless yield(i, e) } true end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/annotate.rb������������������������������������������������������0000644�0052762�0001160�00000007760�13417161721�021255� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Handles annotations on objects. The function can be used in four different ways. # # With two arguments, an `Annotation` type and an object, the function returns the annotation # for the object of the given type, or `undef` if no such annotation exists. # # @example Using `annotate` with two arguments # # ```puppet # $annotation = Mod::NickNameAdapter.annotate(o) # # $annotation = annotate(Mod::NickNameAdapter.annotate, o) # ``` # # With three arguments, an `Annotation` type, an object, and a block, the function returns the # annotation for the object of the given type, or annotates it with a new annotation initialized # from the hash returned by the given block when no such annotation exists. The block will not # be called when an annotation of the given type is already present. # # @example Using `annotate` with two arguments and a block # # ```puppet # $annotation = Mod::NickNameAdapter.annotate(o) || { { 'nick_name' => 'Buddy' } } # # $annotation = annotate(Mod::NickNameAdapter.annotate, o) || { { 'nick_name' => 'Buddy' } } # ``` # # With three arguments, an `Annotation` type, an object, and an `Hash`, the function will annotate # the given object with a new annotation of the given type that is initialized from the given hash. # An existing annotation of the given type is discarded. # # @example Using `annotate` with three arguments where third argument is a Hash # # ```puppet # $annotation = Mod::NickNameAdapter.annotate(o, { 'nick_name' => 'Buddy' }) # # $annotation = annotate(Mod::NickNameAdapter.annotate, o, { 'nick_name' => 'Buddy' }) # ``` # # With three arguments, an `Annotation` type, an object, and an the string `clear`, the function will # clear the annotation of the given type in the given object. The old annotation is returned if # it existed. # # @example Using `annotate` with three arguments where third argument is the string 'clear' # # ```puppet # $annotation = Mod::NickNameAdapter.annotate(o, clear) # # $annotation = annotate(Mod::NickNameAdapter.annotate, o, clear) # ``` # # With three arguments, the type `Pcore`, an object, and a Hash of hashes keyed by `Annotation` types, # the function will annotate the given object with all types used as keys in the given hash. Each annotation # is initialized with the nested hash for the respective type. The annotated object is returned. # # @example Add multiple annotations to a new instance of `Mod::Person` using the `Pcore` type. # # ```puppet # $person = Pcore.annotate(Mod::Person({'name' => 'William'}), { # Mod::NickNameAdapter >= { 'nick_name' => 'Bill' }, # Mod::HobbiesAdapter => { 'hobbies' => ['Ham Radio', 'Philatelist'] } # }) # ``` # # @since 5.0.0 # Puppet::Functions.create_function(:annotate) do dispatch :annotate do param 'Type[Annotation]', :type param 'Any', :value optional_block_param 'Callable[0, 0]', :block end dispatch :annotate_new do param 'Type[Annotation]', :type param 'Any', :value param 'Variant[Enum[clear],Hash[Pcore::MemberName,Any]]', :annotation_hash end dispatch :annotate_multi do param 'Type[Pcore]', :type param 'Any', :value param 'Hash[Type[Annotation], Hash[Pcore::MemberName,Any]]', :annotations end # @param type [Annotation] the annotation type # @param value [Object] the value to annotate # @param block [Proc] optional block to produce the annotation hash # def annotate(type, value, &block) type.implementation_class.annotate(value, &block) end # @param type [Annotation] the annotation type # @param value [Object] the value to annotate # @param annotation_hash [Hash{String => Object}] the annotation hash # def annotate_new(type, value, annotation_hash) type.implementation_class.annotate_new(value, annotation_hash) end # @param type [Type] the Pcore type # @param value [Object] the value to annotate # @param annotations [Hash{Annotation => Hash{String => Object}}] hash of annotation hashes # def annotate_multi(type, value, annotations) type.implementation_class.annotate(value, annotations) end end ����������������puppet-5.5.10/lib/puppet/functions/any.rb�����������������������������������������������������������0000644�0052762�0001160�00000006773�13417161721�020236� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Runs a [lambda](https://puppet.com/docs/puppet/latest/lang_lambdas.html) # repeatedly using each value in a data structure until the lambda returns a "truthy" value which # makes the function return `true`, or if the end of the iteration is reached, false is returned. # # This function takes two mandatory arguments, in this order: # # 1. An array, hash, or other iterable object that the function will iterate over. # 2. A lambda, which the function calls for each element in the first argument. It can # request one or two parameters. # # @example Using the `any` function # # `$data.any |$parameter| { <PUPPET CODE BLOCK> }` # # or # # `any($data) |$parameter| { <PUPPET CODE BLOCK> }` # # @example Using the `any` function with an Array and a one-parameter lambda # # ```puppet # # For the array $data, run a lambda that checks if an unknown hash contains those keys # $data = ["routers", "servers", "workstations"] # $looked_up = lookup('somekey', Hash) # notice $data.any |$item| { $looked_up[$item] } # ``` # # Would notice `true` if the looked up hash had a value that is neither `false` nor `undef` for at least # one of the keys. That is, it is equivalent to the expression # `$looked_up[routers] || $looked_up[servers] || $looked_up[workstations]`. # # When the first argument is a `Hash`, Puppet passes each key and value pair to the lambda # as an array in the form `[key, value]`. # # @example Using the `any` function with a `Hash` and a one-parameter lambda # # ```puppet # # For the hash $data, run a lambda using each item as a key-value array. # $data = {"rtr" => "Router", "svr" => "Server", "wks" => "Workstation"} # $looked_up = lookup('somekey', Hash) # notice $data.any |$item| { $looked_up[$item[0]] } # ``` # # Would notice `true` if the looked up hash had a value for one of the wanted key that is # neither `false` nor `undef`. # # When the lambda accepts two arguments, the first argument gets the index in an array # or the key from a hash, and the second argument the value. # # # @example Using the `any` function with an array and a two-parameter lambda # # ```puppet # # Check if there is an even numbered index that has a non String value # $data = [key1, 1, 2, 2] # notice $data.any |$index, $value| { $index % 2 == 0 and $value !~ String } # ``` # # Would notice true as the index `2` is even and not a `String` # # For an general examples that demonstrates iteration, see the Puppet # [iteration](https://puppet.com/docs/puppet/latest/lang_iteration.html) # documentation. # # @since 5.2.0 # Puppet::Functions.create_function(:any) do dispatch :any_Hash_2 do param 'Hash[Any, Any]', :hash block_param 'Callable[2,2]', :block end dispatch :any_Hash_1 do param 'Hash[Any, Any]', :hash block_param 'Callable[1,1]', :block end dispatch :any_Enumerable_2 do param 'Iterable', :enumerable block_param 'Callable[2,2]', :block end dispatch :any_Enumerable_1 do param 'Iterable', :enumerable block_param 'Callable[1,1]', :block end def any_Hash_1(hash) hash.each_pair.any? { |x| yield(x) } end def any_Hash_2(hash) hash.each_pair.any? { |x,y| yield(x, y) } end def any_Enumerable_1(enumerable) Puppet::Pops::Types::Iterable.asserted_iterable(self, enumerable).any? { |e| yield(e) } end def any_Enumerable_2(enumerable) enum = Puppet::Pops::Types::Iterable.asserted_iterable(self, enumerable) if enum.hash_style? enum.any? { |entry| yield(*entry) } else enum.each_with_index { |e, i| return true if yield(i, e) } false end end end �����puppet-5.5.10/lib/puppet/functions/assert_type.rb���������������������������������������������������0000644�0052762�0001160�00000006476�13417161721�022011� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Returns the given value if it is of the given # [data type](https://puppet.com/docs/puppet/latest/lang_data.html), or # otherwise either raises an error or executes an optional two-parameter # [lambda](https://puppet.com/docs/puppet/latest/lang_lambdas.html). # # The function takes two mandatory arguments, in this order: # # 1. The expected data type. # 2. A value to compare against the expected data type. # # @example Using `assert_type` # # ```puppet # $raw_username = 'Amy Berry' # # # Assert that $raw_username is a non-empty string and assign it to $valid_username. # $valid_username = assert_type(String[1], $raw_username) # # # $valid_username contains "Amy Berry". # # If $raw_username was an empty string or a different data type, the Puppet run would # # fail with an "Expected type does not match actual" error. # ``` # # You can use an optional lambda to provide enhanced feedback. The lambda takes two # mandatory parameters, in this order: # # 1. The expected data type as described in the function's first argument. # 2. The actual data type of the value. # # @example Using `assert_type` with a warning and default value # # ```puppet # $raw_username = 'Amy Berry' # # # Assert that $raw_username is a non-empty string and assign it to $valid_username. # # If it isn't, output a warning describing the problem and use a default value. # $valid_username = assert_type(String[1], $raw_username) |$expected, $actual| { # warning( "The username should be \'${expected}\', not \'${actual}\'. Using 'anonymous'." ) # 'anonymous' # } # # # $valid_username contains "Amy Berry". # # If $raw_username was an empty string, the Puppet run would set $valid_username to # # "anonymous" and output a warning: "The username should be 'String[1, default]', not # # 'String[0, 0]'. Using 'anonymous'." # ``` # # For more information about data types, see the # [documentation](https://puppet.com/docs/puppet/latest/lang_data.html). # # @since 4.0.0 # Puppet::Functions.create_function(:assert_type) do dispatch :assert_type do param 'Type', :type param 'Any', :value optional_block_param 'Callable[Type, Type]', :block end dispatch :assert_type_s do param 'String', :type_string param 'Any', :value optional_block_param 'Callable[Type, Type]', :block end # @param type [Type] the type the value must be an instance of # @param value [Object] the value to assert # def assert_type(type, value) unless Puppet::Pops::Types::TypeCalculator.instance?(type,value) inferred_type = Puppet::Pops::Types::TypeCalculator.infer_set(value) if block_given? # Give the inferred type to allow richer comparison in the given block (if generalized # information is lost). # value = yield(type, inferred_type) else raise Puppet::Pops::Types::TypeAssertionError.new( Puppet::Pops::Types::TypeMismatchDescriber.singleton.describe_mismatch('assert_type():', type, inferred_type), type, inferred_type) end end value end # @param type_string [String] the type the value must be an instance of given in String form # @param value [Object] the value to assert # def assert_type_s(type_string, value, &proc) t = Puppet::Pops::Types::TypeParser.singleton.parse(type_string) block_given? ? assert_type(t, value, &proc) : assert_type(t, value) end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/binary_file.rb���������������������������������������������������0000644�0052762�0001160�00000002443�13417161721�021720� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Loads a binary file from a module or file system and returns its contents as a `Binary`. # The argument to this function should be a `<MODULE NAME>/<FILE>` # reference, which will load `<FILE>` from a module's `files` # directory. (For example, the reference `mysql/mysqltuner.pl` will load the # file `<MODULES DIRECTORY>/mysql/files/mysqltuner.pl`.) # # This function also accepts an absolute file path that allows reading # binary file content from anywhere on disk. # # An error is raised if the given file does not exists. # # To search for the existence of files, use the `find_file()` function. # # - since 4.8.0 # # @since 4.8.0 # Puppet::Functions.create_function(:binary_file, Puppet::Functions::InternalFunction) do dispatch :binary_file do scope_param param 'String', :path end def binary_file(scope, unresolved_path) path = Puppet::Parser::Files.find_file(unresolved_path, scope.compiler.environment) unless path && Puppet::FileSystem.exist?(path) #TRANSLATORS the string "binary_file()" should not be translated raise Puppet::ParseError, _("binary_file(): The given file '%{unresolved_path}' does not exist") % { unresolved_path: unresolved_path } end Puppet::Pops::Types::PBinaryType::Binary.from_binary_string(Puppet::FileSystem.binread(path)) end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/break.rb���������������������������������������������������������0000644�0052762�0001160�00000002344�13417161721�020521� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Breaks an innermost iteration as if it encountered an end of input. # This function does not return to the caller. # # The signal produced to stop the iteration bubbles up through # the call stack until either terminating the innermost iteration or # raising an error if the end of the call stack is reached. # # The break() function does not accept an argument. # # @example Using `break` # # ```puppet # $data = [1,2,3] # notice $data.map |$x| { if $x == 3 { break() } $x*10 } # ``` # # Would notice the value `[10, 20]` # # @example Using a nested `break` # # ```puppet # function break_if_even($x) { # if $x % 2 == 0 { break() } # } # $data = [1,2,3] # notice $data.map |$x| { break_if_even($x); $x*10 } #``` # Would notice the value `[10]` # # * Also see functions `next` and `return` # # @since 4.8.0 # Puppet::Functions.create_function(:break) do dispatch :break_impl do end def break_impl() # get file, line if available, else they are set to nil file, line = Puppet::Pops::PuppetStack.top_of_stack # PuppetStopIteration contains file and line and is a StopIteration exception # so it can break a Ruby Kernel#loop or enumeration # raise Puppet::Pops::Evaluator::PuppetStopIteration.new(file, line) end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/contain.rb�������������������������������������������������������0000644�0052762�0001160�00000004651�13417161721�021073� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Makes one or more classes be contained inside the current class. # If any of these classes are undeclared, they will be declared as if # there were declared with the `include` function. # Accepts a class name, an array of class names, or a comma-separated # list of class names. # # A contained class will not be applied before the containing class is # begun, and will be finished before the containing class is finished. # # You must use the class's full name; # relative names are not allowed. In addition to names in string form, # you may also directly use `Class` and `Resource` `Type`-values that are produced by # evaluating resource and relationship expressions. # # The function returns an array of references to the classes that were contained thus # allowing the function call to `contain` to directly continue. # # - Since 4.0.0 support for `Class` and `Resource` `Type`-values, absolute names # - Since 4.7.0 a value of type `Array[Type[Class[n]]]` is returned with all the contained classes # Puppet::Functions.create_function(:contain, Puppet::Functions::InternalFunction) do dispatch :contain do scope_param # The function supports what the type system sees as Ruby runtime objects, and # they cannot be parameterized to find what is actually valid instances. # The validation is instead done in the function body itself via a call to # `transform_and_assert_classnames` on the calling scope. required_repeated_param 'Any', :names end def contain(scope, *classes) if Puppet[:tasks] raise Puppet::ParseErrorWithIssue.from_issue_and_stack( Puppet::Pops::Issues::CATALOG_OPERATION_NOT_SUPPORTED_WHEN_SCRIPTING, {:operation => 'contain'}) end # Make call patterns uniform and protected against nested arrays, also make # names absolute if so desired. classes = scope.transform_and_assert_classnames(classes.flatten) result = classes.map {|name| Puppet::Pops::Types::TypeFactory.host_class(name) } containing_resource = scope.resource # This is the same as calling the include function but faster and does not rely on the include # function. (scope.compiler.evaluate_classes(classes, scope, false) || []).each do |resource| if ! scope.catalog.edge?(containing_resource, resource) scope.catalog.add_edge(containing_resource, resource) end end # Result is an Array[Class, 1, n] which allows chaining other operations result end end���������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/crit.rb����������������������������������������������������������0000644�0052762�0001160�00000000571�13417161721�020376� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Logs a message on the server at level `crit`. Puppet::Functions.create_function(:crit, Puppet::Functions::InternalFunction) do # @param values The values to log. # @return [Undef] dispatch :crit do scope_param repeated_param 'Any', :values return_type 'Undef' end def crit(scope, *values) Puppet::Util::Log.log_func(scope, :crit, values) end end ���������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/debug.rb���������������������������������������������������������0000644�0052762�0001160�00000000576�13417161721�020530� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Logs a message on the server at level `debug`. Puppet::Functions.create_function(:debug, Puppet::Functions::InternalFunction) do # @param values The values to log. # @return [Undef] dispatch :debug do scope_param repeated_param 'Any', :values return_type 'Undef' end def debug(scope, *values) Puppet::Util::Log.log_func(scope, :debug, values) end end ����������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/defined.rb�������������������������������������������������������0000644�0052762�0001160�00000014022�13417161721�021027� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Determines whether a given class or resource type is defined and returns a Boolean # value. You can also use `defined` to determine whether a specific resource is defined, # or whether a variable has a value (including `undef`, as opposed to the variable never # being declared or assigned). # # This function takes at least one string argument, which can be a class name, type name, # resource reference, or variable reference of the form `'$name'`. (Note that the `$` sign # is included in the string which must be in single quotes to prevent the `$` character # to be interpreted as interpolation. # # The `defined` function checks both native and defined types, including types # provided by modules. Types and classes are matched by their names. The function matches # resource declarations by using resource references. # # @example Different types of `defined` function matches # # ```puppet # # Matching resource types # defined("file") # defined("customtype") # # # Matching defines and classes # defined("foo") # defined("foo::bar") # # # Matching variables (note the single quotes) # defined('$name') # # # Matching declared resources # defined(File['/tmp/file']) # ``` # # Puppet depends on the configuration's evaluation order when checking whether a resource # is declared. # # @example Importance of evaluation order when using `defined` # # ```puppet # # Assign values to $is_defined_before and $is_defined_after using identical `defined` # # functions. # # $is_defined_before = defined(File['/tmp/file']) # # file { "/tmp/file": # ensure => present, # } # # $is_defined_after = defined(File['/tmp/file']) # # # $is_defined_before returns false, but $is_defined_after returns true. # ``` # # This order requirement only refers to evaluation order. The order of resources in the # configuration graph (e.g. with `before` or `require`) does not affect the `defined` # function's behavior. # # > **Warning:** Avoid relying on the result of the `defined` function in modules, as you # > might not be able to guarantee the evaluation order well enough to produce consistent # > results. This can cause other code that relies on the function's result to behave # > inconsistently or fail. # # If you pass more than one argument to `defined`, the function returns `true` if _any_ # of the arguments are defined. You can also match resources by type, allowing you to # match conditions of different levels of specificity, such as whether a specific resource # is of a specific data type. # # @example Matching multiple resources and resources by different types with `defined` # # ```puppet # file { "/tmp/file1": # ensure => file, # } # # $tmp_file = file { "/tmp/file2": # ensure => file, # } # # # Each of these statements return `true` ... # defined(File['/tmp/file1']) # defined(File['/tmp/file1'],File['/tmp/file2']) # defined(File['/tmp/file1'],File['/tmp/file2'],File['/tmp/file3']) # # ... but this returns `false`. # defined(File['/tmp/file3']) # # # Each of these statements returns `true` ... # defined(Type[Resource['file','/tmp/file2']]) # defined(Resource['file','/tmp/file2']) # defined(File['/tmp/file2']) # defined('$tmp_file') # # ... but each of these returns `false`. # defined(Type[Resource['exec','/tmp/file2']]) # defined(Resource['exec','/tmp/file2']) # defined(File['/tmp/file3']) # defined('$tmp_file2') # ``` # # @since 2.7.0 # @since 3.6.0 variable reference and future parser types # @since 3.8.1 type specific requests with future parser # @since 4.0.0 # Puppet::Functions.create_function(:'defined', Puppet::Functions::InternalFunction) do dispatch :is_defined do scope_param required_repeated_param 'Variant[String, Type[CatalogEntry], Type[Type[CatalogEntry]]]', :vals end def is_defined(scope, *vals) vals.any? do |val| case val when String if val =~ /^\$(.+)$/ scope.exist?($1) else case val when '' next nil when 'main' # Find the main class (known as ''), it does not have to be in the catalog Puppet::Pops::Evaluator::Runtime3ResourceSupport.find_main_class(scope) else # Find a resource type, definition or class definition Puppet::Pops::Evaluator::Runtime3ResourceSupport.find_resource_type_or_class(scope, val) end end when Puppet::Resource # Find instance of given resource type and title that is in the catalog scope.compiler.findresource(val.resource_type, val.title) when Puppet::Pops::Types::PResourceType raise ArgumentError, _('The given resource type is a reference to all kind of types') if val.type_name.nil? type = Puppet::Pops::Evaluator::Runtime3ResourceSupport.find_resource_type(scope, val.type_name) val.title.nil? ? type : scope.compiler.findresource(type, val.title) when Puppet::Pops::Types::PClassType raise ArgumentError, _('The given class type is a reference to all classes') if val.class_name.nil? scope.compiler.findresource(:class, val.class_name) when Puppet::Pops::Types::PTypeType case val.type when Puppet::Pops::Types::PResourceType # It is most reasonable to take Type[File] and Type[File[foo]] to mean the same as if not wrapped in a Type # Since the difference between File and File[foo] already captures the distinction of type vs instance. is_defined(scope, val.type) when Puppet::Pops::Types::PClassType # Interpreted as asking if a class (and nothing else) is defined without having to be included in the catalog # (this is the same as asking for just the class' name, but with the added certainty that it cannot be a defined type. # raise ArgumentError, _('The given class type is a reference to all classes') if val.type.class_name.nil? Puppet::Pops::Evaluator::Runtime3ResourceSupport.find_hostclass(scope, val.type.class_name) end else raise ArgumentError, _("Invalid argument of type '%{value_class}' to 'defined'") % { value_class: val.class } end end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/each.rb����������������������������������������������������������0000644�0052762�0001160�00000011650�13417161721�020335� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Runs a [lambda](https://puppet.com/docs/puppet/latest/lang_lambdas.html) # repeatedly using each value in a data structure, then returns the values unchanged. # # This function takes two mandatory arguments, in this order: # # 1. An array, hash, or other iterable object that the function will iterate over. # 2. A lambda, which the function calls for each element in the first argument. It can # request one or two parameters. # # @example Using the `each` function # # `$data.each |$parameter| { <PUPPET CODE BLOCK> }` # # or # # `each($data) |$parameter| { <PUPPET CODE BLOCK> }` # # When the first argument (`$data` in the above example) is an array, Puppet passes each # value in turn to the lambda, then returns the original values. # # @example Using the `each` function with an array and a one-parameter lambda # # ```puppet # # For the array $data, run a lambda that creates a resource for each item. # $data = ["routers", "servers", "workstations"] # $data.each |$item| { # notify { $item: # message => $item # } # } # # Puppet creates one resource for each of the three items in $data. Each resource is # # named after the item's value and uses the item's value in a parameter. # ``` # # When the first argument is a hash, Puppet passes each key and value pair to the lambda # as an array in the form `[key, value]` and returns the original hash. # # @example Using the `each` function with a hash and a one-parameter lambda # # ```puppet # # For the hash $data, run a lambda using each item as a key-value array that creates a # # resource for each item. # $data = {"rtr" => "Router", "svr" => "Server", "wks" => "Workstation"} # $data.each |$items| { # notify { $items[0]: # message => $items[1] # } # } # # Puppet creates one resource for each of the three items in $data, each named after the # # item's key and containing a parameter using the item's value. # ``` # # When the first argument is an array and the lambda has two parameters, Puppet passes the # array's indexes (enumerated from 0) in the first parameter and its values in the second # parameter. # # @example Using the `each` function with an array and a two-parameter lambda # # ```puppet # # For the array $data, run a lambda using each item's index and value that creates a # # resource for each item. # $data = ["routers", "servers", "workstations"] # $data.each |$index, $value| { # notify { $value: # message => $index # } # } # # Puppet creates one resource for each of the three items in $data, each named after the # # item's value and containing a parameter using the item's index. # ``` # # When the first argument is a hash, Puppet passes its keys to the first parameter and its # values to the second parameter. # # @example Using the `each` function with a hash and a two-parameter lambda # # ```puppet # # For the hash $data, run a lambda using each item's key and value to create a resource # # for each item. # $data = {"rtr" => "Router", "svr" => "Server", "wks" => "Workstation"} # $data.each |$key, $value| { # notify { $key: # message => $value # } # } # # Puppet creates one resource for each of the three items in $data, each named after the # # item's key and containing a parameter using the item's value. # ``` # # For an example that demonstrates how to create multiple `file` resources using `each`, # see the Puppet # [iteration](https://puppet.com/docs/puppet/latest/lang_iteration.html) # documentation. # # @since 4.0.0 # Puppet::Functions.create_function(:each) do dispatch :foreach_Hash_2 do param 'Hash[Any, Any]', :hash block_param 'Callable[2,2]', :block end dispatch :foreach_Hash_1 do param 'Hash[Any, Any]', :hash block_param 'Callable[1,1]', :block end dispatch :foreach_Enumerable_2 do param 'Iterable', :enumerable block_param 'Callable[2,2]', :block end dispatch :foreach_Enumerable_1 do param 'Iterable', :enumerable block_param 'Callable[1,1]', :block end def foreach_Hash_1(hash) enumerator = hash.each_pair begin hash.size.times do yield(enumerator.next) end rescue StopIteration end # produces the receiver hash end def foreach_Hash_2(hash) enumerator = hash.each_pair begin hash.size.times do yield(*enumerator.next) end rescue StopIteration end # produces the receiver hash end def foreach_Enumerable_1(enumerable) enum = Puppet::Pops::Types::Iterable.asserted_iterable(self, enumerable) begin loop { yield(enum.next) } rescue StopIteration end # produces the receiver enumerable end def foreach_Enumerable_2(enumerable) enum = Puppet::Pops::Types::Iterable.asserted_iterable(self, enumerable) if enum.hash_style? enum.each { |entry| yield(*entry) } else begin index = 0 loop do yield(index, enum.next) index += 1 end rescue StopIteration end end # produces the receiver enumerable end end ����������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/emerg.rb���������������������������������������������������������0000644�0052762�0001160�00000000576�13417161721�020541� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Logs a message on the server at level `emerg`. Puppet::Functions.create_function(:emerg, Puppet::Functions::InternalFunction) do # @param values The values to log. # @return [Undef] dispatch :emerg do scope_param repeated_param 'Any', :values return_type 'Undef' end def emerg(scope, *values) Puppet::Util::Log.log_func(scope, :emerg, values) end end ����������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/empty.rb���������������������������������������������������������0000644�0052762�0001160�00000003555�13417161721�020600� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Returns `true` if the given argument is an empty collection of values. # # This function can answer if one of the following is empty: # * `Array`, `Hash` - having zero entries # * `String`, `Binary` - having zero length # # For backwards compatibility with the stdlib function with the same name the # following data types are also accepted by the function instead of raising an error. # Using these is deprecated and will raise a warning: # # * `Numeric` - `false` is returned for all `Numeric` values. # * `Undef` - `true` is returned for all `Undef` values. # # @example Using `empty` # # ```puppet # notice([].empty) # notice(empty([])) # # would both notice 'true' # ``` # # @since Puppet 5.5.0 - support for Binary # Puppet::Functions.create_function(:empty) do dispatch :collection_empty do param 'Collection', :coll end dispatch :string_empty do param 'String', :str end dispatch :numeric_empty do param 'Numeric', :num end dispatch :binary_empty do param 'Binary', :bin end dispatch :undef_empty do param 'Undef', :x end def collection_empty(coll) coll.empty? end def string_empty(str) str.empty? end # For compatibility reasons - return false rather than error on floats and integers # (Yes, it is strange) # def numeric_empty(num) deprecation_warning_for('Numeric') false end def binary_empty(bin) bin.length == 0 end # For compatibility reasons - return true rather than error on undef # (Yes, it is strange, but undef was passed as empty string in 3.x API) # def undef_empty(x) true end def deprecation_warning_for(arg_type) file, line = Puppet::Pops::PuppetStack.top_of_stack msg = _("Calling function empty() with %{arg_type} value is deprecated.") % { arg_type: arg_type } Puppet.warn_once('deprecations', "empty-from-#{file}-#{line}", msg, file, line) end end ���������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/epp.rb�����������������������������������������������������������0000644�0052762�0001160�00000003700�13417161721�020216� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Evaluates an Embedded Puppet (EPP) template file and returns the rendered text # result as a String. # # `epp('<MODULE NAME>/<TEMPLATE FILE>', <PARAMETER HASH>)` # # The first argument to this function should be a `<MODULE NAME>/<TEMPLATE FILE>` # reference, which loads `<TEMPLATE FILE>` from `<MODULE NAME>`'s `templates` # directory. In most cases, the last argument is optional; if used, it should be a # [hash](/puppet/latest/reference/lang_data_hash.html) that contains parameters to # pass to the template. # # - See the [template](/puppet/latest/reference/lang_template.html) documentation # for general template usage information. # - See the [EPP syntax](/puppet/latest/reference/lang_template_epp.html) # documentation for examples of EPP. # # For example, to call the apache module's `templates/vhost/_docroot.epp` # template and pass the `docroot` and `virtual_docroot` parameters, call the `epp` # function like this: # # `epp('apache/vhost/_docroot.epp', { 'docroot' => '/var/www/html', # 'virtual_docroot' => '/var/www/example' })` # # This function can also accept an absolute path, which can load a template file # from anywhere on disk. # # Puppet produces a syntax error if you pass more parameters than are declared in # the template's parameter tag. When passing parameters to a template that # contains a parameter tag, use the same names as the tag's declared parameters. # # Parameters are required only if they are declared in the called template's # parameter tag without default values. Puppet produces an error if the `epp` # function fails to pass any required parameter. # # @since 4.0.0 # Puppet::Functions.create_function(:epp, Puppet::Functions::InternalFunction) do dispatch :epp do scope_param param 'String', :path optional_param 'Hash[Pattern[/^\w+$/], Any]', :parameters end def epp(scope, path, parameters = nil) Puppet::Pops::Evaluator::EppEvaluator.epp(scope, path, scope.compiler.environment, parameters) end end ����������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/err.rb�����������������������������������������������������������0000644�0052762�0001160�00000000564�13417161721�020227� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Logs a message on the server at level `err`. Puppet::Functions.create_function(:err, Puppet::Functions::InternalFunction) do # @param values The values to log. # @return [Undef] dispatch :err do scope_param repeated_param 'Any', :values return_type 'Undef' end def err(scope, *values) Puppet::Util::Log.log_func(scope, :err, values) end end ��������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/find_file.rb�����������������������������������������������������0000644�0052762�0001160�00000002642�13417161721�021355� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Finds an existing file from a module and returns its path. # # The argument to this function should be a String as a `<MODULE NAME>/<FILE>` # reference, which will search for `<FILE>` relative to a module's `files` # directory. (For example, the reference `mysql/mysqltuner.pl` will search for the # file `<MODULES DIRECTORY>/mysql/files/mysqltuner.pl`.) # # This function can also accept: # # * An absolute String path, which will check for the existence of a file from anywhere on disk. # * Multiple String arguments, which will return the path of the **first** file # found, skipping non existing files. # * An array of string paths, which will return the path of the **first** file # found from the given paths in the array, skipping non existing files. # # The function returns `undef` if none of the given paths were found # # @since 4.8.0 # Puppet::Functions.create_function(:find_file, Puppet::Functions::InternalFunction) do dispatch :find_file do scope_param repeated_param 'String', :paths end dispatch :find_file_array do scope_param repeated_param 'Array[String]', :paths_array end def find_file_array(scope, array) find_file(scope, *array) end def find_file(scope, *args) args.each do |file| found = Puppet::Parser::Files.find_file(file, scope.compiler.environment) if found && Puppet::FileSystem.exist?(found) return found end end nil end end ����������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/flatten.rb�������������������������������������������������������0000644�0052762�0001160�00000003016�13417161721�021067� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Returns a flat Array produced from its possibly deeply nested given arguments. # # One or more arguments of any data type can be given to this function. # The result is always a flat array representation where any nested arrays are recursively flattened. # # @example Typical use of `flatten()` # # ```puppet # flatten(['a', ['b', ['c']]]) # # Would return: ['a','b','c'] # ``` # # To flatten other kinds of iterables (for example hashes, or intermediate results like from a `reverse_each`) # first convert the result to an array using `Array($x)`, or `$x.convert_to(Array)`. See the `new` function # for details and options when performing a conversion. # # @example Flattening a Hash # # ```puppet # $hsh = { a => 1, b => 2} # # # -- without conversion # $hsh.flatten() # # Would return [{a => 1, b => 2}] # # # -- with conversion # $hsh.convert_to(Array).flatten() # # Would return [a,1,b,2] # # flatten(Array($hsh)) # # Would also return [a,1,b,2] # ``` # # @example Flattening and concatenating at the same time # # ```puppet # $a1 = [1, [2, 3]] # $a2 = [[4,[5,6]] # $x = 7 # flatten($a1, $a2, $x) # # would return [1,2,3,4,5,6,7] # ``` # # @example Transforming to Array if not already an Array # # ```puppet # flatten(42) # # Would return [42] # # flatten([42]) # # Would also return [42] # ``` # # @since 5.5.0 support for flattening and concatenating at the same time # Puppet::Functions.create_function(:flatten) do dispatch :flatten_args do repeated_param 'Any', :args end def flatten_args(*args) args.flatten() end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/hiera.rb���������������������������������������������������������0000644�0052762�0001160�00000006144�13417161721�020527� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'hiera/puppet_function' # Performs a standard priority lookup of the hierarchy and returns the most specific value # for a given key. The returned value can be any type of data. # # This function is deprecated in favor of the `lookup` function. While this function # continues to work, it does **not** support: # * `lookup_options` stored in the data # * lookup across global, environment, and module layers # # The function takes up to three arguments, in this order: # # 1. A string key that Hiera searches for in the hierarchy. **Required**. # 2. An optional default value to return if Hiera doesn't find anything matching the key. # * If this argument isn't provided and this function results in a lookup failure, Puppet # fails with a compilation error. # 3. The optional name of an arbitrary # [hierarchy level](https://puppet.com/docs/hiera/latest/hierarchy.html) to insert at the # top of the hierarchy. This lets you temporarily modify the hierarchy for a single lookup. # * If Hiera doesn't find a matching key in the overriding hierarchy level, it continues # searching the rest of the hierarchy. # # The `hiera` function does **not** find all matches throughout a hierarchy, instead # returning the first specific value starting at the top of the hierarchy. To search # throughout a hierarchy, use the `hiera_array` or `hiera_hash` functions. # # @example Using `hiera` # # ```yaml # # Assuming hiera.yaml # # :hierarchy: # # - web01.example.com # # - common # # # Assuming web01.example.com.yaml: # # users: # # - "Amy Barry" # # - "Carrie Douglas" # # # Assuming common.yaml: # users: # admins: # - "Edith Franklin" # - "Ginny Hamilton" # regular: # - "Iris Jackson" # - "Kelly Lambert" # ``` # # ```puppet # # Assuming we are not web01.example.com: # # $users = hiera('users', undef) # # # $users contains {admins => ["Edith Franklin", "Ginny Hamilton"], # # regular => ["Iris Jackson", "Kelly Lambert"]} # ``` # # You can optionally generate the default value with a # [lambda](https://puppet.com/docs/puppet/latest/lang_lambdas.html) that # takes one parameter. # # @example Using `hiera` with a lambda # # ```puppet # # Assuming the same Hiera data as the previous example: # # $users = hiera('users') | $key | { "Key \'${key}\' not found" } # # # $users contains {admins => ["Edith Franklin", "Ginny Hamilton"], # # regular => ["Iris Jackson", "Kelly Lambert"]} # # If hiera couldn't match its key, it would return the lambda result, # # "Key 'users' not found". # ``` # # The returned value's data type depends on the types of the results. In the example # above, Hiera matches the 'users' key and returns it as a hash. # # See # [the 'Using the lookup function' documentation](https://puppet.com/docs/puppet/latest/hiera_automatic.html) for how to perform lookup of data. # Also see # [the 'Using the deprecated hiera functions' documentation](https://puppet.com/docs/puppet/latest/hiera_automatic.html) # for more information about the Hiera 3 functions. # # @since 4.0.0 # Puppet::Functions.create_function(:hiera, Hiera::PuppetFunction) do init_dispatch end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/hiera_array.rb���������������������������������������������������0000644�0052762�0001160�00000005604�13417161721�021725� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'hiera/puppet_function' # Finds all matches of a key throughout the hierarchy and returns them as a single flattened # array of unique values. If any of the matched values are arrays, they're flattened and # included in the results. This is called an # [array merge lookup](https://puppet.com/docs/hiera/latest/lookup_types.html#array-merge). # # This function is deprecated in favor of the `lookup` function. While this function # continues to work, it does **not** support: # * `lookup_options` stored in the data # * lookup across global, environment, and module layers # # The `hiera_array` function takes up to three arguments, in this order: # # 1. A string key that Hiera searches for in the hierarchy. **Required**. # 2. An optional default value to return if Hiera doesn't find anything matching the key. # * If this argument isn't provided and this function results in a lookup failure, Puppet # fails with a compilation error. # 3. The optional name of an arbitrary # [hierarchy level](https://puppet.com/docs/hiera/latest/hierarchy.html) to insert at the # top of the hierarchy. This lets you temporarily modify the hierarchy for a single lookup. # * If Hiera doesn't find a matching key in the overriding hierarchy level, it continues # searching the rest of the hierarchy. # # @example Using `hiera_array` # # ```yaml # # Assuming hiera.yaml # # :hierarchy: # # - web01.example.com # # - common # # # Assuming common.yaml: # # users: # # - 'cdouglas = regular' # # - 'efranklin = regular' # # # Assuming web01.example.com.yaml: # # users: 'abarry = admin' # ``` # # ```puppet # $allusers = hiera_array('users', undef) # # # $allusers contains ["cdouglas = regular", "efranklin = regular", "abarry = admin"]. # ``` # # You can optionally generate the default value with a # [lambda](https://puppet.com/docs/puppet/latest/lang_lambdas.html) that # takes one parameter. # # @example Using `hiera_array` with a lambda # # ```puppet # # Assuming the same Hiera data as the previous example: # # $allusers = hiera_array('users') | $key | { "Key \'${key}\' not found" } # # # $allusers contains ["cdouglas = regular", "efranklin = regular", "abarry = admin"]. # # If hiera_array couldn't match its key, it would return the lambda result, # # "Key 'users' not found". # ``` # # `hiera_array` expects that all values returned will be strings or arrays. If any matched # value is a hash, Puppet raises a type mismatch error. # # See # [the 'Using the lookup function' documentation](https://puppet.com/docs/puppet/latest/hiera_automatic.html) for how to perform lookup of data. # Also see # [the 'Using the deprecated hiera functions' documentation](https://puppet.com/docs/puppet/latest/hiera_automatic.html) # for more information about the Hiera 3 functions. # # @since 4.0.0 # Puppet::Functions.create_function(:hiera_array, Hiera::PuppetFunction) do init_dispatch def merge_type :unique end end ����������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/hiera_hash.rb����������������������������������������������������0000644�0052762�0001160�00000006313�13417161721�021530� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'hiera/puppet_function' # Finds all matches of a key throughout the hierarchy and returns them in a merged hash. # # This function is deprecated in favor of the `lookup` function. While this function # continues to work, it does **not** support: # * `lookup_options` stored in the data # * lookup across global, environment, and module layers # # If any of the matched hashes share keys, the final hash uses the value from the # highest priority match. This is called a # [hash merge lookup](https://puppet.com/docs/hiera/latest/lookup_types.html#hash-merge). # # The merge strategy is determined by Hiera's # [`:merge_behavior`](https://puppet.com/docs/hiera/latest/configuring.html#mergebehavior) # setting. # # The `hiera_hash` function takes up to three arguments, in this order: # # 1. A string key that Hiera searches for in the hierarchy. **Required**. # 2. An optional default value to return if Hiera doesn't find anything matching the key. # * If this argument isn't provided and this function results in a lookup failure, Puppet # fails with a compilation error. # 3. The optional name of an arbitrary # [hierarchy level](https://puppet.com/docs/hiera/latest/hierarchy.html) to insert at the # top of the hierarchy. This lets you temporarily modify the hierarchy for a single lookup. # * If Hiera doesn't find a matching key in the overriding hierarchy level, it continues # searching the rest of the hierarchy. # # @example Using `hiera_hash` # # ```yaml # # Assuming hiera.yaml # # :hierarchy: # # - web01.example.com # # - common # # # Assuming common.yaml: # # users: # # regular: # # 'cdouglas': 'Carrie Douglas' # # # Assuming web01.example.com.yaml: # # users: # # administrators: # # 'aberry': 'Amy Berry' # ``` # # ```puppet # # Assuming we are not web01.example.com: # # $allusers = hiera_hash('users', undef) # # # $allusers contains {regular => {"cdouglas" => "Carrie Douglas"}, # # administrators => {"aberry" => "Amy Berry"}} # ``` # # You can optionally generate the default value with a # [lambda](https://puppet.com/docs/puppet/latest/lang_lambdas.html) that # takes one parameter. # # @example Using `hiera_hash` with a lambda # # ```puppet # # Assuming the same Hiera data as the previous example: # # $allusers = hiera_hash('users') | $key | { "Key \'${key}\' not found" } # # # $allusers contains {regular => {"cdouglas" => "Carrie Douglas"}, # # administrators => {"aberry" => "Amy Berry"}} # # If hiera_hash couldn't match its key, it would return the lambda result, # # "Key 'users' not found". # ``` # # `hiera_hash` expects that all values returned will be hashes. If any of the values # found in the data sources are strings or arrays, Puppet raises a type mismatch error. # # See # [the 'Using the lookup function' documentation](https://puppet.com/docs/puppet/latest/hiera_automatic.html) for how to perform lookup of data. # Also see # [the 'Using the deprecated hiera functions' documentation](https://puppet.com/docs/puppet/latest/hiera_automatic.html) # for more information about the Hiera 3 functions. # # @since 4.0.0 # Puppet::Functions.create_function(:hiera_hash, Hiera::PuppetFunction) do init_dispatch def merge_type :hash end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/hiera_include.rb�������������������������������������������������0000644�0052762�0001160�00000007534�13417161721�022236� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'hiera/puppet_function' # Assigns classes to a node using an # [array merge lookup](https://puppet.com/docs/hiera/latest/lookup_types.html#array-merge) # that retrieves the value for a user-specified key from Hiera's data. # # This function is deprecated in favor of the `lookup` function in combination with `include`. # While this function continues to work, it does **not** support: # * `lookup_options` stored in the data # * lookup across global, environment, and module layers # # @example Using `lookup` and `include` instead of of the deprecated `hiera_include` # # ```puppet # # In site.pp, outside of any node definitions and below any top-scope variables: # lookup('classes', Array[String], 'unique').include # ``` # # The `hiera_include` function requires: # # - A string key name to use for classes. # - A call to this function (i.e. `hiera_include('classes')`) in your environment's # `sites.pp` manifest, outside of any node definitions and below any top-scope variables # that Hiera uses in lookups. # - `classes` keys in the appropriate Hiera data sources, with an array for each # `classes` key and each value of the array containing the name of a class. # # The function takes up to three arguments, in this order: # # 1. A string key that Hiera searches for in the hierarchy. **Required**. # 2. An optional default value to return if Hiera doesn't find anything matching the key. # * If this argument isn't provided and this function results in a lookup failure, Puppet # fails with a compilation error. # 3. The optional name of an arbitrary # [hierarchy level](https://puppet.com/docs/hiera/latest/hierarchy.html) to insert at the # top of the hierarchy. This lets you temporarily modify the hierarchy for a single lookup. # * If Hiera doesn't find a matching key in the overriding hierarchy level, it continues # searching the rest of the hierarchy. # # The function uses an # [array merge lookup](https://puppet.com/docs/hiera/latest/lookup_types.html#array-merge) # to retrieve the `classes` array, so every node gets every class from the hierarchy. # # @example Using `hiera_include` # # ```yaml # # Assuming hiera.yaml # # :hierarchy: # # - web01.example.com # # - common # # # Assuming web01.example.com.yaml: # # classes: # # - apache::mod::php # # # Assuming common.yaml: # # classes: # # - apache # ``` # # ```puppet # # In site.pp, outside of any node definitions and below any top-scope variables: # hiera_include('classes', undef) # # # Puppet assigns the apache and apache::mod::php classes to the web01.example.com node. # ``` # # You can optionally generate the default value with a # [lambda](https://puppet.com/docs/puppet/latest/lang_lambdas.html) that # takes one parameter. # # @example Using `hiera_include` with a lambda # # ```puppet # # Assuming the same Hiera data as the previous example: # # # In site.pp, outside of any node definitions and below any top-scope variables: # hiera_include('classes') | $key | {"Key \'${key}\' not found" } # # # Puppet assigns the apache and apache::mod::php classes to the web01.example.com node. # # If hiera_include couldn't match its key, it would return the lambda result, # # "Key 'classes' not found". # ``` # # See # [the 'Using the lookup function' documentation](https://puppet.com/docs/puppet/latest/hiera_automatic.html) for how to perform lookup of data. # Also see # [the 'Using the deprecated hiera functions' documentation](https://puppet.com/docs/puppet/latest/hiera_automatic.html) # for more information about the Hiera 3 functions. # # @since 4.0.0 # Puppet::Functions.create_function(:hiera_include, Hiera::PuppetFunction) do init_dispatch def merge_type :unique end def post_lookup(scope, key, value) raise Puppet::ParseError, _("Could not find data item %{key}") % { key: key } if value.nil? call_function_with_scope(scope, 'include', value) unless value.empty? end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/hocon_data.rb����������������������������������������������������0000644�0052762�0001160�00000002551�13417161721�021534� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The `hocon_data` is a hiera 5 `data_hash` data provider function. # See [the configuration guide documentation](https://puppet.com/docs/puppet/latest/hiera_config_yaml_5.html#configuring-a-hierarchy-level-built-in-backends) for # how to use this function. # # Note that this function is not supported without a hocon library being present. # # @since 4.9.0 # Puppet::Functions.create_function(:hocon_data) do unless Puppet.features.hocon? raise Puppet::DataBinding::LookupError, _('Lookup using Hocon data_hash function is not supported without hocon library') end require 'hocon' require 'hocon/config_error' dispatch :hocon_data do param 'Struct[{path=>String[1]}]', :options param 'Puppet::LookupContext', :context end argument_mismatch :missing_path do param 'Hash', :options param 'Puppet::LookupContext', :context end def hocon_data(options, context) path = options['path'] context.cached_file_data(path) do |content| begin Hocon.parse(content) rescue Hocon::ConfigError => ex raise Puppet::DataBinding::LookupError, _("Unable to parse (%{path}): %{message}") % { path: path, message: ex.message } end end end def missing_path(options, context) "one of 'path', 'paths' 'glob', 'globs' or 'mapped_paths' must be declared in hiera.yaml when using this data_hash function" end end �������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/import.rb��������������������������������������������������������0000644�0052762�0001160�00000000414�13417161721�020743� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The import function raises an error when called to inform the user that import is no longer supported. # Puppet::Functions.create_function(:import) do def import(*args) raise Puppet::Pops::SemanticError.new(Puppet::Pops::Issues::DISCONTINUED_IMPORT) end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/include.rb�������������������������������������������������������0000644�0052762�0001160�00000004701�13417161721�021057� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Declares one or more classes, causing the resources in them to be # evaluated and added to the catalog. Accepts a class name, an array of class # names, or a comma-separated list of class names. # # The `include` function can be used multiple times on the same class and will # only declare a given class once. If a class declared with `include` has any # parameters, Puppet will automatically look up values for them in Hiera, using # `<class name>::<parameter name>` as the lookup key. # # Contrast this behavior with resource-like class declarations # (`class {'name': parameter => 'value',}`), which must be used in only one place # per class and can directly set parameters. You should avoid using both `include` # and resource-like declarations with the same class. # # The `include` function does not cause classes to be contained in the class # where they are declared. For that, see the `contain` function. It also # does not create a dependency relationship between the declared class and the # surrounding class; for that, see the `require` function. # # You must use the class's full name; # relative names are not allowed. In addition to names in string form, # you may also directly use `Class` and `Resource` `Type`-values that are produced by # the resource and relationship expressions. # # - Since < 3.0.0 # - Since 4.0.0 support for class and resource type values, absolute names # - Since 4.7.0 returns an `Array[Type[Class]]` of all included classes # Puppet::Functions.create_function(:include, Puppet::Functions::InternalFunction) do dispatch :include do scope_param # The function supports what the type system sees as Ruby runtime objects, and # they cannot be parameterized to find what is actually valid instances. # The validation is instead done in the function body itself via a call to # `transform_and_assert_classnames` on the calling scope. required_repeated_param 'Any', :names end def include(scope, *classes) if Puppet[:tasks] raise Puppet::ParseErrorWithIssue.from_issue_and_stack( Puppet::Pops::Issues::CATALOG_OPERATION_NOT_SUPPORTED_WHEN_SCRIPTING, {:operation => 'include'}) end classes = scope.transform_and_assert_classnames(classes.flatten) result = classes.map {|name| Puppet::Pops::Types::TypeFactory.host_class(name) } scope.compiler.evaluate_classes(classes, scope, false) # Result is an Array[Class, 1, n] which allows chaining other operations result end end ���������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/info.rb����������������������������������������������������������0000644�0052762�0001160�00000000571�13417161721�020370� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Logs a message on the server at level `info`. Puppet::Functions.create_function(:info, Puppet::Functions::InternalFunction) do # @param values The values to log. # @return [Undef] dispatch :info do scope_param repeated_param 'Any', :values return_type 'Undef' end def info(scope, *values) Puppet::Util::Log.log_func(scope, :info, values) end end ���������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/inline_epp.rb����������������������������������������������������0000644�0052762�0001160�00000004515�13417161721�021561� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Evaluates an Embedded Puppet (EPP) template string and returns the rendered # text result as a String. # # `inline_epp('<EPP TEMPLATE STRING>', <PARAMETER HASH>)` # # The first argument to this function should be a string containing an EPP # template. In most cases, the last argument is optional; if used, it should be a # [hash](/puppet/latest/reference/lang_data_hash.html) that contains parameters to # pass to the template. # # - See the [template](/puppet/latest/reference/lang_template.html) documentation # for general template usage information. # - See the [EPP syntax](/puppet/latest/reference/lang_template_epp.html) # documentation for examples of EPP. # # For example, to evaluate an inline EPP template and pass it the `docroot` and # `virtual_docroot` parameters, call the `inline_epp` function like this: # # `inline_epp('docroot: <%= $docroot %> Virtual docroot: <%= $virtual_docroot %>', # { 'docroot' => '/var/www/html', 'virtual_docroot' => '/var/www/example' })` # # Puppet produces a syntax error if you pass more parameters than are declared in # the template's parameter tag. When passing parameters to a template that # contains a parameter tag, use the same names as the tag's declared parameters. # # Parameters are required only if they are declared in the called template's # parameter tag without default values. Puppet produces an error if the # `inline_epp` function fails to pass any required parameter. # # An inline EPP template should be written as a single-quoted string or # [heredoc](/puppet/latest/reference/lang_data_string.html#heredocs). # A double-quoted string is subject to expression interpolation before the string # is parsed as an EPP template. # # For example, to evaluate an inline EPP template using a heredoc, call the # `inline_epp` function like this: # # ```puppet # # Outputs 'Hello given argument planet!' # inline_epp(@(END), { x => 'given argument' }) # <%- | $x, $y = planet | -%> # Hello <%= $x %> <%= $y %>! # END # ``` # # @since 4.0.0 # Puppet::Functions.create_function(:inline_epp, Puppet::Functions::InternalFunction) do dispatch :inline_epp do scope_param() param 'String', :template optional_param 'Hash[Pattern[/^\w+$/], Any]', :parameters end def inline_epp(scope, template, parameters = nil) Puppet::Pops::Evaluator::EppEvaluator.inline_epp(scope, template, parameters) end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/join.rb����������������������������������������������������������0000644�0052762�0001160�00000003476�13417161721�020403� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Joins the values of an Array into a string with elements separated by a delimiter. # # Supports up to two arguments # * **values** - first argument is required and must be an an `Array` # * **delimiter** - second arguments is the delimiter between elements, must be a `String` if given, and defaults to an empty string. # # @example Typical use of `join` # # ```puppet # join(['a','b','c'], ",") # # Would result in: "a,b,c" # ``` # # Note that array is flattened before elements are joined, but flattening does not extend to arrays nested in hashes or other objects. # # @example Arrays nested in hashes are not joined # # ```puppet # $a = [1,2, undef, 'hello', [x,y,z], {a => 2, b => [3, 4]}] # notice join($a, ', ') # # # would result in noticing: # # 1, 2, , hello, x, y, z, {"a"=>2, "b"=>[3, 4]} # ``` # # For joining iterators and other containers of elements a conversion must first be made to # an `Array`. The reason for this is that there are many options how such a conversion should # be made. # # @example Joining the result of a reverse_each converted to an array # # ```puppet # [1,2,3].reverse_each.convert_to(Array).join(', ') # # would result in: "3, 2, 1" # ``` # @example Joining a hash # # ```puppet # {a => 1, b => 2}.convert_to(Array).join(', ') # # would result in "a, 1, b, 2" # ``` # # For more detailed control over the formatting (including indentations and line breaks, delimiters around arrays # and hash entries, between key/values in hash entries, and individual formatting of values in the array) # see the `new` function for `String` and its formatting options for `Array` and `Hash`. # Puppet::Functions.create_function(:join) do dispatch :join do param 'Array', :arg optional_param 'String', :delimiter end def join(arg, delimiter = '', puppet_formatting = false) arg.join(delimiter) end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/json_data.rb�����������������������������������������������������0000644�0052762�0001160�00000002216�13417161721�021375� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The `json_data` is a hiera 5 `data_hash` data provider function. # See [the configuration guide documentation](https://puppet.com/docs/puppet/latest/hiera_config_yaml_5.html#configuring-a-hierarchy-level-built-in-backends) for # how to use this function. # # @since 4.8.0 # Puppet::Functions.create_function(:json_data) do dispatch :json_data do param 'Struct[{path=>String[1]}]', :options param 'Puppet::LookupContext', :context end argument_mismatch :missing_path do param 'Hash', :options param 'Puppet::LookupContext', :context end def json_data(options, context) path = options['path'] context.cached_file_data(path) do |content| begin Puppet::Util::Json.load(content) rescue Puppet::Util::Json::ParseError => ex # Filename not included in message, so we add it here. raise Puppet::DataBinding::LookupError, "Unable to parse (%{path}): %{message}" % { path: path, message: ex.message } end end end def missing_path(options, context) "one of 'path', 'paths' 'glob', 'globs' or 'mapped_paths' must be declared in hiera.yaml when using this data_hash function" end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/keys.rb����������������������������������������������������������0000644�0052762�0001160�00000001254�13417161721�020407� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Returns the keys of a hash as an Array # # @example Using `keys` # # ```puppet # $hsh = {"apples" => 3, "oranges" => 4 } # $hsh.keys() # keys($hsh) # # both results in the array ["apples", "oranges"] # ``` # # * Note that a hash in the puppet language accepts any data value (including `undef`) unless # it is constrained with a `Hash` data type that narrows the allowed data types. # * For an empty hash, an empty array is returned. # * The order of the keys is the same as the order in the hash (typically the order in which they were added). # Puppet::Functions.create_function(:keys) do dispatch :keys do param 'Hash', :hsh end def keys(hsh) hsh.keys end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/length.rb��������������������������������������������������������0000644�0052762�0001160�00000001630�13417161721�020713� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Returns the length of an Array, Hash, String, or Binary value. # # The returned value is a positive integer indicating the number # of elements in the container; counting (possibly multibyte) characters for a `String`, # bytes in a `Binary`, number of elements in an `Array`, and number of # key-value associations in a Hash. # # @example Using `length` # # ```puppet # "roses".length() # 5 # length("violets") # 7 # [10, 20].length # 2 # {a => 1, b => 3}.length # 2 # ``` # # @since 5.5.0 - also supporting Binary # Puppet::Functions.create_function(:length) do dispatch :collection_length do param 'Collection', :arg end dispatch :string_length do param 'String', :arg end dispatch :binary_length do param 'Binary', :arg end def collection_length(col) col.size end def string_length(s) s.length end def binary_length(bin) bin.length end end ��������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/lest.rb����������������������������������������������������������0000644�0052762�0001160�00000002432�13417161721�020402� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Calls a [lambda](https://puppet.com/docs/puppet/latest/lang_lambdas.html) # without arguments if the value given to `lest` is `undef`. # Returns the result of calling the lambda if the argument is `undef`, otherwise the # given argument. # # The `lest` function is useful in a chain of `then` calls, or in general # as a guard against `undef` values. The function can be used to call `fail`, or to # return a default value. # # These two expressions are equivalent: # # ```puppet # if $x == undef { do_things() } # lest($x) || { do_things() } # ``` # # @example Using the `lest` function # # ```puppet # $data = {a => [ b, c ] } # notice $data.dig(a, b, c) # .then |$x| { $x * 2 } # .lest || { fail("no value for $data[a][b][c]" } # ``` # # Would fail the operation because $data[a][b][c] results in `undef` # (there is no `b` key in `a`). # # In contrast - this example: # # ```puppet # $data = {a => { b => { c => 10 } } } # notice $data.dig(a, b, c) # .then |$x| { $x * 2 } # .lest || { fail("no value for $data[a][b][c]" } # ``` # # Would notice the value `20` # # @since 4.5.0 # Puppet::Functions.create_function(:lest) do dispatch :lest do param 'Any', :arg block_param 'Callable[0,0]', :block end def lest(arg) if arg.nil? yield() else arg end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/lookup.rb��������������������������������������������������������0000644�0052762�0001160�00000023412�13417161721�020745� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Uses the Puppet lookup system to retrieve a value for a given key. By default, # this returns the first value found (and fails compilation if no values are # available), but you can configure it to merge multiple values into one, fail # gracefully, and more. # # When looking up a key, Puppet will search up to three tiers of data, in the # following order: # # 1. Hiera. # 2. The current environment's data provider. # 3. The indicated module's data provider, if the key is of the form # `<MODULE NAME>::<SOMETHING>`. # # #### Arguments # # You must provide the name of a key to look up, and can optionally provide other # arguments. You can combine these arguments in the following ways: # # * `lookup( <NAME>, [<VALUE TYPE>], [<MERGE BEHAVIOR>], [<DEFAULT VALUE>] )` # * `lookup( [<NAME>], <OPTIONS HASH> )` # * `lookup( as above ) |$key| { # lambda returns a default value }` # # Arguments in `[square brackets]` are optional. # # The arguments accepted by `lookup` are as follows: # # 1. `<NAME>` (string or array) --- The name of the key to look up. # * This can also be an array of keys. If Puppet doesn't find anything for the # first key, it will try again with the subsequent ones, only resorting to a # default value if none of them succeed. # 2. `<VALUE TYPE>` (data type) --- A # [data type](https://puppet.com/docs/puppet/latest/lang_data_type.html) # that must match the retrieved value; if not, the lookup (and catalog # compilation) will fail. Defaults to `Data` (accepts any normal value). # 3. `<MERGE BEHAVIOR>` (string or hash; see **"Merge Behaviors"** below) --- # Whether (and how) to combine multiple values. If present, this overrides any # merge behavior specified in the data sources. Defaults to no value; Puppet will # use merge behavior from the data sources if present, and will otherwise do a # first-found lookup. # 4. `<DEFAULT VALUE>` (any normal value) --- If present, `lookup` returns this # when it can't find a normal value. Default values are never merged with found # values. Like a normal value, the default must match the value type. Defaults to # no value; if Puppet can't find a normal value, the lookup (and compilation) will # fail. # 5. `<OPTIONS HASH>` (hash) --- Alternate way to set the arguments above, plus # some less-common extra options. If you pass an options hash, you can't combine # it with any regular arguments (except `<NAME>`). An options hash can have the # following keys: # * `'name'` --- Same as `<NAME>` (argument 1). You can pass this as an # argument or in the hash, but not both. # * `'value_type'` --- Same as `<VALUE TYPE>` (argument 2). # * `'merge'` --- Same as `<MERGE BEHAVIOR>` (argument 3). # * `'default_value'` --- Same as `<DEFAULT VALUE>` (argument 4). # * `'default_values_hash'` (hash) --- A hash of lookup keys and default # values. If Puppet can't find a normal value, it will check this hash for the # requested key before giving up. You can combine this with `default_value` or # a lambda, which will be used if the key isn't present in this hash. Defaults # to an empty hash. # * `'override'` (hash) --- A hash of lookup keys and override values. Puppet # will check for the requested key in the overrides hash _first;_ if found, it # returns that value as the _final_ value, ignoring merge behavior. Defaults # to an empty hash. # # Finally, `lookup` can take a lambda, which must accept a single parameter. # This is yet another way to set a default value for the lookup; if no results are # found, Puppet will pass the requested key to the lambda and use its result as # the default value. # # #### Merge Behaviors # # Puppet lookup uses a hierarchy of data sources, and a given key might have # values in multiple sources. By default, Puppet returns the first value it finds, # but it can also continue searching and merge all the values together. # # > **Note:** Data sources can use the special `lookup_options` metadata key to # request a specific merge behavior for a key. The `lookup` function will use that # requested behavior unless you explicitly specify one. # # The valid merge behaviors are: # # * `'first'` --- Returns the first value found, with no merging. Puppet lookup's # default behavior. # * `'unique'` (called "array merge" in classic Hiera) --- Combines any number of # arrays and scalar values to return a merged, flattened array with all duplicate # values removed. The lookup will fail if any hash values are found. # * `'hash'` --- Combines the keys and values of any number of hashes to return a # merged hash. If the same key exists in multiple source hashes, Puppet will use # the value from the highest-priority data source; it won't recursively merge the # values. # * `'deep'` --- Combines the keys and values of any number of hashes to return a # merged hash. If the same key exists in multiple source hashes, Puppet will # recursively merge hash or array values (with duplicate values removed from # arrays). For conflicting scalar values, the highest-priority value will win. # * `{'strategy' => 'first'}`, `{'strategy' => 'unique'}`, # or `{'strategy' => 'hash'}` --- Same as the string versions of these merge behaviors. # * `{'strategy' => 'deep', <DEEP OPTION> => <VALUE>, ...}` --- Same as `'deep'`, # but can adjust the merge with additional options. The available options are: # * `'knockout_prefix'` (string or undef) --- A string prefix to indicate a # value should be _removed_ from the final result. If a value is exactly equal # to the prefix, it will knockout the entire element. Defaults to `undef`, which # disables this feature. # * `'sort_merged_arrays'` (boolean) --- Whether to sort all arrays that are # merged together. Defaults to `false`. # * `'merge_hash_arrays'` (boolean) --- Whether to merge hashes within arrays. # Defaults to `false`. # # @example Look up a key and return the first value found # # lookup('ntp::service_name') # # @example Do a unique merge lookup of class names, then add all of those classes to the catalog (like `hiera_include`) # # lookup('classes', Array[String], 'unique').include # # @example Do a deep hash merge lookup of user data, but let higher priority sources remove values by prefixing them with `--` # # lookup( { 'name' => 'users', # 'merge' => { # 'strategy' => 'deep', # 'knockout_prefix' => '--', # }, # }) # # @since 4.0.0 Puppet::Functions.create_function(:lookup, Puppet::Functions::InternalFunction) do local_types do type 'NameType = Variant[String, Array[String]]' type 'ValueType = Type' type 'DefaultValueType = Any' type 'MergeType = Variant[String[1], Hash[String, Scalar]]' type 'BlockType = Callable[NameType]' type "OptionsWithName = Struct[{\ name => NameType,\ value_type => Optional[ValueType],\ default_value => Optional[DefaultValueType],\ override => Optional[Hash[String,Any]],\ default_values_hash => Optional[Hash[String,Any]],\ merge => Optional[MergeType]\ }]" type "OptionsWithoutName = Struct[{\ value_type => Optional[ValueType],\ default_value => Optional[DefaultValueType],\ override => Optional[Hash[String,Any]],\ default_values_hash => Optional[Hash[String,Any]],\ merge => Optional[MergeType]\ }]" end dispatch :lookup_1 do scope_param param 'NameType', :name optional_param 'ValueType', :value_type optional_param 'MergeType', :merge end dispatch :lookup_2 do scope_param param 'NameType', :name param 'Optional[ValueType]', :value_type param 'Optional[MergeType]', :merge param 'DefaultValueType', :default_value end dispatch :lookup_3 do scope_param param 'NameType', :name optional_param 'ValueType', :value_type optional_param 'MergeType', :merge required_block_param 'BlockType', :block end # Lookup without name. Name then becomes a required entry in the options hash dispatch :lookup_4 do scope_param param 'OptionsWithName', :options_hash optional_block_param 'BlockType', :block end # Lookup using name and options hash. dispatch :lookup_5 do scope_param param 'Variant[String,Array[String]]', :name param 'OptionsWithoutName', :options_hash optional_block_param 'BlockType', :block end def lookup_1(scope, name, value_type=nil, merge=nil) do_lookup(scope, name, value_type, nil, false, {}, {}, merge) end def lookup_2(scope, name, value_type, merge, default_value) do_lookup(scope, name, value_type, default_value, true, {}, {}, merge) end def lookup_3(scope, name, value_type=nil, merge=nil, &block) do_lookup(scope, name, value_type, nil, false, {}, {}, merge, &block) end def lookup_4(scope, options_hash, &block) do_lookup(scope, options_hash['name'], *hash_args(options_hash), &block) end def lookup_5(scope, name, options_hash, &block) do_lookup(scope, name, *hash_args(options_hash), &block) end def do_lookup(scope, name, value_type, default_value, has_default, override, default_values_hash, merge, &block) Puppet::Pops::Lookup.lookup(name, value_type, default_value, has_default, merge, Puppet::Pops::Lookup::Invocation.new(scope, override, default_values_hash), &block) end def hash_args(options_hash) [ options_hash['value_type'], options_hash['default_value'], options_hash.include?('default_value'), options_hash['override'] || {}, options_hash['default_values_hash'] || {}, options_hash['merge'] ] end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/map.rb�����������������������������������������������������������0000644�0052762�0001160�00000007310�13417161721�020210� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Applies a [lambda](https://puppet.com/docs/puppet/latest/lang_lambdas.html) # to every value in a data structure and returns an array containing the results. # # This function takes two mandatory arguments, in this order: # # 1. An array, hash, or other iterable object that the function will iterate over. # 2. A lambda, which the function calls for each element in the first argument. It can # request one or two parameters. # # @example Using the `map` function # # `$transformed_data = $data.map |$parameter| { <PUPPET CODE BLOCK> }` # # or # # `$transformed_data = map($data) |$parameter| { <PUPPET CODE BLOCK> }` # # When the first argument (`$data` in the above example) is an array, Puppet passes each # value in turn to the lambda. # # @example Using the `map` function with an array and a one-parameter lambda # # ```puppet # # For the array $data, return an array containing each value multiplied by 10 # $data = [1,2,3] # $transformed_data = $data.map |$items| { $items * 10 } # # $transformed_data contains [10,20,30] # ``` # # When the first argument is a hash, Puppet passes each key and value pair to the lambda # as an array in the form `[key, value]`. # # @example Using the `map` function with a hash and a one-parameter lambda # # ```puppet # # For the hash $data, return an array containing the keys # $data = {'a'=>1,'b'=>2,'c'=>3} # $transformed_data = $data.map |$items| { $items[0] } # # $transformed_data contains ['a','b','c'] # ``` # # When the first argument is an array and the lambda has two parameters, Puppet passes the # array's indexes (enumerated from 0) in the first parameter and its values in the second # parameter. # # @example Using the `map` function with an array and a two-parameter lambda # # ```puppet # # For the array $data, return an array containing the indexes # $data = [1,2,3] # $transformed_data = $data.map |$index,$value| { $index } # # $transformed_data contains [0,1,2] # ``` # # When the first argument is a hash, Puppet passes its keys to the first parameter and its # values to the second parameter. # # @example Using the `map` function with a hash and a two-parameter lambda # # ```puppet # # For the hash $data, return an array containing each value # $data = {'a'=>1,'b'=>2,'c'=>3} # $transformed_data = $data.map |$key,$value| { $value } # # $transformed_data contains [1,2,3] # ``` # # @since 4.0.0 # Puppet::Functions.create_function(:map) do dispatch :map_Hash_2 do param 'Hash[Any, Any]', :hash block_param 'Callable[2,2]', :block end dispatch :map_Hash_1 do param 'Hash[Any, Any]', :hash block_param 'Callable[1,1]', :block end dispatch :map_Enumerable_2 do param 'Iterable', :enumerable block_param 'Callable[2,2]', :block end dispatch :map_Enumerable_1 do param 'Iterable', :enumerable block_param 'Callable[1,1]', :block end def map_Hash_1(hash) result = [] begin hash.map {|x, y| result << yield([x, y]) } rescue StopIteration end result end def map_Hash_2(hash) result = [] begin hash.map {|x, y| result << yield(x, y) } rescue StopIteration end result end def map_Enumerable_1(enumerable) result = [] enum = Puppet::Pops::Types::Iterable.asserted_iterable(self, enumerable) begin loop { result << yield(enum.next) } rescue StopIteration end result end def map_Enumerable_2(enumerable) enum = Puppet::Pops::Types::Iterable.asserted_iterable(self, enumerable) if enum.hash_style? enum.map { |entry| yield(*entry) } else result = [] begin index = 0 loop do result << yield(index, enum.next) index = index + 1 end rescue StopIteration end result end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/match.rb���������������������������������������������������������0000644�0052762�0001160�00000010405�13417161721�020526� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Matches a regular expression against a string and returns an array containing the match # and any matched capturing groups. # # The first argument is a string or array of strings. The second argument is either a # regular expression, regular expression represented as a string, or Regex or Pattern # data type that the function matches against the first argument. # # The returned array contains the entire match at index 0, and each captured group at # subsequent index values. If the value or expression being matched is an array, the # function returns an array with mapped match results. # # If the function doesn't find a match, it returns 'undef'. # # @example Matching a regular expression in a string # # ```puppet # $matches = "abc123".match(/[a-z]+[1-9]+/) # # $matches contains [abc123] # ``` # # @example Matching a regular expressions with grouping captures in a string # # ```puppet # $matches = "abc123".match(/([a-z]+)([1-9]+)/) # # $matches contains [abc123, abc, 123] # ``` # # @example Matching a regular expression with grouping captures in an array of strings # # ```puppet # $matches = ["abc123","def456"].match(/([a-z]+)([1-9]+)/) # # $matches contains [[abc123, abc, 123], [def456, def, 456]] # ``` # # @since 4.0.0 # Puppet::Functions.create_function(:match) do dispatch :match do param 'String', :string param 'Variant[Any, Type]', :pattern end dispatch :enumerable_match do param 'Array[String]', :string param 'Variant[Any, Type]', :pattern end def initialize(closure_scope, loader) super # Make this visitor shared among all instantiations of this function since it is faster. # This can be used because it is not possible to replace # a puppet runtime (where this function is) without a reboot. If you model a function in a module after # this class, use a regular instance variable instead to enable reloading of the module without reboot # @@match_visitor ||= Puppet::Pops::Visitor.new(self, "match", 1, 1) end # Matches given string against given pattern and returns an Array with matches. # @param string [String] the string to match # @param pattern [String, Regexp, Puppet::Pops::Types::PPatternType, Puppet::Pops::PRegexpType, Array] the pattern # @return [Array<String>] matches where first match is the entire match, and index 1-n are captures from left to right # def match(string, pattern) @@match_visitor.visit_this_1(self, pattern, string) end # Matches given Array[String] against given pattern and returns an Array with mapped match results. # # @param array [Array<String>] the array of strings to match # @param pattern [String, Regexp, Puppet::Pops::Types::PPatternType, Puppet::Pops::PRegexpType, Array] the pattern # @return [Array<Array<String, nil>>] Array with matches (see {#match}), non matching entries produce a nil entry # def enumerable_match(array, pattern) array.map {|s| match(s, pattern) } end protected def match_Object(obj, s) msg = _("match() expects pattern of T, where T is String, Regexp, Regexp[r], Pattern[p], or Array[T]. Got %{klass}") % { klass: obj.class } raise ArgumentError, msg end def match_String(pattern_string, s) do_match(s, Regexp.new(pattern_string)) end def match_Regexp(regexp, s) do_match(s, regexp) end def match_PTypeAliasType(alias_t, s) match(s, alias_t.resolved_type) end def match_PVariantType(var_t, s) # Find first matching type (or error out if one of the variants is not acceptable) result = nil var_t.types.find {|t| result = match(s, t) } result end def match_PRegexpType(regexp_t, s) raise ArgumentError, _("Given Regexp Type has no regular expression") unless regexp_t.pattern do_match(s, regexp_t.regexp) end def match_PPatternType(pattern_t, s) # Since we want the actual match result (not just a boolean), an iteration over # Pattern's regular expressions is needed. (They are of PRegexpType) result = nil pattern_t.patterns.find {|pattern| result = match(s, pattern) } result end # Returns the first matching entry def match_Array(array, s) result = nil array.flatten.find {|entry| result = match(s, entry) } result end private def do_match(s, regexp) if result = regexp.match(s) result.to_a end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/module_directory.rb����������������������������������������������0000644�0052762�0001160�00000002345�13417161721�023007� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Finds an existing module and returns the path to its root directory. # # The argument to this function should be a module name String # For example, the reference `mysql` will search for the # directory `<MODULES DIRECTORY>/mysql` and return the first # found on the modulepath. # # This function can also accept: # # * Multiple String arguments, which will return the path of the **first** module # found, skipping non existing modules. # * An array of module names, which will return the path of the **first** module # found from the given names in the array, skipping non existing modules. # # The function returns `undef` if none of the given modules were found # # @since 5.4.0 # Puppet::Functions.create_function(:module_directory, Puppet::Functions::InternalFunction) do dispatch :module_directory do scope_param repeated_param 'String', :names end dispatch :module_directory_array do scope_param repeated_param 'Array[String]', :names end def module_directory_array(scope, names) module_directory(scope, *names) end def module_directory(scope, *names) names.each do |module_name| found = scope.compiler.environment.module(module_name) return found.path if found end nil end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/next.rb����������������������������������������������������������0000644�0052762�0001160�00000000677�13417161721�020422� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Makes iteration continue with the next value, optionally with a given value for this iteration. # If a value is not given it defaults to `undef` # # @since 4.7.0 # Puppet::Functions.create_function(:next) do dispatch :next_impl do optional_param 'Any', :value end def next_impl(value = nil) file, line = Puppet::Pops::PuppetStack.top_of_stack exc = Puppet::Pops::Evaluator::Next.new(value, file, line) raise exc end end �����������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/notice.rb��������������������������������������������������������0000644�0052762�0001160�00000000603�13417161721�020712� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Logs a message on the server at level `notice`. Puppet::Functions.create_function(:notice, Puppet::Functions::InternalFunction) do # @param values The values to log. # @return [Undef] dispatch :notice do scope_param repeated_param 'Any', :values return_type 'Undef' end def notice(scope, *values) Puppet::Util::Log.log_func(scope, :notice, values) end end �����������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/reduce.rb��������������������������������������������������������0000644�0052762�0001160�00000012637�13417161721�020712� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Applies a [lambda](https://puppet.com/docs/puppet/latest/lang_lambdas.html) # to every value in a data structure from the first argument, carrying over the returned # value of each iteration, and returns the result of the lambda's final iteration. This # lets you create a new value or data structure by combining values from the first # argument's data structure. # # This function takes two mandatory arguments, in this order: # # 1. An array, hash, or other iterable object that the function will iterate over. # 2. A lambda, which the function calls for each element in the first argument. It takes # two mandatory parameters: # 1. A memo value that is overwritten after each iteration with the iteration's result. # 2. A second value that is overwritten after each iteration with the next value in the # function's first argument. # # @example Using the `reduce` function # # `$data.reduce |$memo, $value| { ... }` # # or # # `reduce($data) |$memo, $value| { ... }` # # You can also pass an optional "start memo" value as an argument, such as `start` below: # # `$data.reduce(start) |$memo, $value| { ... }` # # or # # `reduce($data, start) |$memo, $value| { ... }` # # When the first argument (`$data` in the above example) is an array, Puppet passes each # of the data structure's values in turn to the lambda's parameters. When the first # argument is a hash, Puppet converts each of the hash's values to an array in the form # `[key, value]`. # # If you pass a start memo value, Puppet executes the lambda with the provided memo value # and the data structure's first value. Otherwise, Puppet passes the structure's first two # values to the lambda. # # Puppet calls the lambda for each of the data structure's remaining values. For each # call, it passes the result of the previous call as the first parameter ($memo in the # above examples) and the next value from the data structure as the second parameter # ($value). # # If the structure has one value, Puppet returns the value and does not call the lambda. # # @example Using the `reduce` function # # ```puppet # # Reduce the array $data, returning the sum of all values in the array. # $data = [1, 2, 3] # $sum = $data.reduce |$memo, $value| { $memo + $value } # # $sum contains 6 # # # Reduce the array $data, returning the sum of a start memo value and all values in the # # array. # $data = [1, 2, 3] # $sum = $data.reduce(4) |$memo, $value| { $memo + $value } # # $sum contains 10 # # # Reduce the hash $data, returning the sum of all values and concatenated string of all # # keys. # $data = {a => 1, b => 2, c => 3} # $combine = $data.reduce |$memo, $value| { # $string = "${memo[0]}${value[0]}" # $number = $memo[1] + $value[1] # [$string, $number] # } # # $combine contains [abc, 6] # ``` # # @example Using the `reduce` function with a start memo and two-parameter lambda # # ```puppet # # Reduce the array $data, returning the sum of all values in the array and starting # # with $memo set to an arbitrary value instead of $data's first value. # $data = [1, 2, 3] # $sum = $data.reduce(4) |$memo, $value| { $memo + $value } # # At the start of the lambda's first iteration, $memo contains 4 and $value contains 1. # # After all iterations, $sum contains 10. # # # Reduce the hash $data, returning the sum of all values and concatenated string of # # all keys, and starting with $memo set to an arbitrary array instead of $data's first # # key-value pair. # $data = {a => 1, b => 2, c => 3} # $combine = $data.reduce( [d, 4] ) |$memo, $value| { # $string = "${memo[0]}${value[0]}" # $number = $memo[1] + $value[1] # [$string, $number] # } # # At the start of the lambda's first iteration, $memo contains [d, 4] and $value # # contains [a, 1]. # # $combine contains [dabc, 10] # ``` # # @example Using the `reduce` function to reduce a hash of hashes # # ```puppet # # Reduce a hash of hashes $data, merging defaults into the inner hashes. # $data = { # 'connection1' => { # 'username' => 'user1', # 'password' => 'pass1', # }, # 'connection_name2' => { # 'username' => 'user2', # 'password' => 'pass2', # }, # } # # $defaults = { # 'maxActive' => '20', # 'maxWait' => '10000', # 'username' => 'defaultuser', # 'password' => 'defaultpass', # } # # $merged = $data.reduce( {} ) |$memo, $x| { # $memo + { $x[0] => $defaults + $data[$x[0]] } # } # # At the start of the lambda's first iteration, $memo is set to {}, and $x is set to # # the first [key, value] tuple. The key in $data is, therefore, given by $x[0]. In # # subsequent rounds, $memo retains the value returned by the expression, i.e. # # $memo + { $x[0] => $defaults + $data[$x[0]] }. # ``` # # @since 4.0.0 # Puppet::Functions.create_function(:reduce) do dispatch :reduce_without_memo do param 'Iterable', :enumerable block_param 'Callable[2,2]', :block end dispatch :reduce_with_memo do param 'Iterable', :enumerable param 'Any', :memo block_param 'Callable[2,2]', :block end def reduce_without_memo(enumerable) enum = Puppet::Pops::Types::Iterable.asserted_iterable(self, enumerable) enum.reduce do |memo, x| begin yield(memo, x) rescue StopIteration return memo end end end def reduce_with_memo(enumerable, given_memo) enum = Puppet::Pops::Types::Iterable.asserted_iterable(self, enumerable) enum.reduce(given_memo) do |memo, x| begin yield(memo, x) rescue StopIteration return memo end end end end �������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/regsubst.rb������������������������������������������������������0000644�0052762�0001160�00000007352�13417161721�021277� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Performs regexp replacement on a string or array of strings. # # @param target [String, Array[String]] # The string or array of strings to operate on. If an array, the replacement will be # performed on each of the elements in the array, and the return value will be an array. # @param pattern [String, Regexp, Type[Regexp]] # The regular expression matching the target string. If you want it anchored at the start # and or end of the string, you must do that with ^ and $ yourself. # @param replacement [String, Hash[String, String]] # Replacement string. Can contain backreferences to what was matched using \\0 (whole match), # \\1 (first set of parentheses), and so on. # If the second argument is a Hash, and the matched text is one of its keys, the corresponding value is the replacement string. # @param flags [Optional[Pattern[/^[GEIM]*$/]], Pattern[/^G?$/]] # Optional. String of single letter flags for how the regexp is interpreted (E, I, and M cannot be used # if pattern is a precompiled regexp): # - *E* Extended regexps # - *I* Ignore case in regexps # - *M* Multiline regexps # - *G* Global replacement; all occurrences of the regexp in each target string will be replaced. Without this, only the first occurrence will be replaced. # @param encoding [Enum['N','E','S','U']] # Optional. How to handle multibyte characters when compiling the regexp (must not be used when pattern is a # precompiled regexp). A single-character string with the following values: # - *N* None # - *E* EUC # - *S* SJIS # - *U* UTF-8 # @return [Array[String], String] The result of the substitution. Result type is the same as for the target parameter. # # @example Get the third octet from the node's IP address: # # ```puppet # $i3 = regsubst($ipaddress,'^(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)$','\\3') # ``` # # @example Put angle brackets around each octet in the node's IP address: # # ```puppet # $x = regsubst($ipaddress, /([0-9]+)/, '<\\1>', 'G') # ``` # Puppet::Functions.create_function(:regsubst) do dispatch :regsubst_string do param 'Variant[Array[String],String]', :target param 'String', :pattern param 'Variant[String,Hash[String,String]]', :replacement optional_param 'Optional[Pattern[/^[GEIM]*$/]]', :flags optional_param "Enum['N','E','S','U']", :encoding end dispatch :regsubst_regexp do param 'Variant[Array[String],String]', :target param 'Variant[Regexp,Type[Regexp]]', :pattern param 'Variant[String,Hash[String,String]]', :replacement optional_param 'Pattern[/^G?$/]', :flags end def regsubst_string(target, pattern, replacement, flags = nil, encoding = nil) re_flags = 0 operation = :sub if !flags.nil? flags.split(//).each do |f| case f when 'G' then operation = :gsub when 'E' then re_flags |= Regexp::EXTENDED when 'I' then re_flags |= Regexp::IGNORECASE when 'M' then re_flags |= Regexp::MULTILINE end end end inner_regsubst(target, Regexp.compile(pattern, re_flags, encoding), replacement, operation) end def regsubst_regexp(target, pattern, replacement, flags = nil) pattern = (pattern.pattern || '') if pattern.is_a?(Puppet::Pops::Types::PRegexpType) inner_regsubst(target, pattern, replacement, flags == 'G' ? :gsub : :sub) end def inner_regsubst(target, re, replacement, op) target.respond_to?(op) ? target.send(op, re, replacement) : target.collect { |e| e.send(op, re, replacement) } end private :inner_regsubst end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/require.rb�������������������������������������������������������0000644�0052762�0001160�00000006141�13417161721�021110� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Requires the specified classes. # Evaluate one or more classes, adding the required class as a dependency. # # The relationship metaparameters work well for specifying relationships # between individual resources, but they can be clumsy for specifying # relationships between classes. This function is a superset of the # 'include' function, adding a class relationship so that the requiring # class depends on the required class. # # Warning: using require in place of include can lead to unwanted dependency cycles. # # For instance the following manifest, with 'require' instead of 'include' would produce a nasty # dependence cycle, because notify imposes a before between File[/foo] and Service[foo]: # # ```puppet # class myservice { # service { foo: ensure => running } # } # # class otherstuff { # include myservice # file { '/foo': notify => Service[foo] } # } # ``` # # Note that this function only works with clients 0.25 and later, and it will # fail if used with earlier clients. # # You must use the class's full name; # relative names are not allowed. In addition to names in string form, # you may also directly use Class and Resource Type values that are produced when evaluating # resource and relationship expressions. # # - Since 4.0.0 Class and Resource types, absolute names # - Since 4.7.0 Returns an Array[Type[Class]] with references to the required classes # Puppet::Functions.create_function(:require, Puppet::Functions::InternalFunction) do dispatch :require_impl do scope_param # The function supports what the type system sees as Ruby runtime objects, and # they cannot be parameterized to find what is actually valid instances. # The validation is instead done in the function body itself via a call to # `transform_and_assert_classnames` on the calling scope. required_repeated_param 'Any', :names end def require_impl(scope, *classes) if Puppet[:tasks] raise Puppet::ParseErrorWithIssue.from_issue_and_stack( Puppet::Pops::Issues::CATALOG_OPERATION_NOT_SUPPORTED_WHEN_SCRIPTING, {:operation => 'require'}) end # Make call patterns uniform and protected against nested arrays, also make # names absolute if so desired. classes = scope.transform_and_assert_classnames(classes.flatten) result = classes.map {|name| Puppet::Pops::Types::TypeFactory.host_class(name) } # This is the same as calling the include function (but faster) since it again # would otherwise need to perform the optional absolute name transformation # (for no reason since they are already made absolute here). # scope.compiler.evaluate_classes(classes, scope, false) krt = scope.environment.known_resource_types classes.each do |klass| # lookup the class in the scopes klass = (classobj = krt.find_hostclass(klass)) ? classobj.name : nil raise Puppet::ParseError.new(_("Could not find class %{klass}") % { klass: klass }) unless klass ref = Puppet::Resource.new(:class, klass) resource = scope.resource resource.set_parameter(:require, [resource[:require]].flatten.compact << ref) end result end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/return.rb��������������������������������������������������������0000644�0052762�0001160�00000000736�13417161721�020757� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Makes iteration continue with the next value, optionally with a given value for this iteration. # If a value is not given it defaults to `undef` # # @since 4.7.0 # Puppet::Functions.create_function(:return, Puppet::Functions::InternalFunction) do dispatch :return_impl do optional_param 'Any', :value end def return_impl(value = nil) file, line = Puppet::Pops::PuppetStack.top_of_stack raise Puppet::Pops::Evaluator::Return.new(value, file, line) end end ����������������������������������puppet-5.5.10/lib/puppet/functions/reverse_each.rb��������������������������������������������������0000644�0052762�0001160�00000005270�13417161721�022071� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Reverses the order of the elements of something that is iterable and optionally runs a # [lambda](https://puppet.com/docs/puppet/latest/lang_lambdas.html) for each # element. # # This function takes one to two arguments: # # 1. An `Iterable` that the function will iterate over. # 2. An optional lambda, which the function calls for each element in the first argument. It must # request one parameter. # # @example Using the `reverse_each` function # # ```puppet # $data.reverse_each |$parameter| { <PUPPET CODE BLOCK> } # ``` # # or # # ```puppet # $reverse_data = $data.reverse_each # ``` # # or # # ```puppet # reverse_each($data) |$parameter| { <PUPPET CODE BLOCK> } # ``` # # or # # ```puppet # $reverse_data = reverse_each($data) # ``` # # When no second argument is present, Puppet returns an `Iterable` that represents the reverse # order of its first argument. This allows methods on `Iterable` to be chained. # # When a lambda is given as the second argument, Puppet iterates the first argument in reverse # order and passes each value in turn to the lambda, then returns `undef`. # # @example Using the `reverse_each` function with an array and a one-parameter lambda # # ```puppet # # Puppet will log a notice for each of the three items # # in $data in reverse order. # $data = [1,2,3] # $data.reverse_each |$item| { notice($item) } # ``` # # When no second argument is present, Puppet returns a new `Iterable` which allows it to # be directly chained into another function that takes an `Iterable` as an argument. # # @example Using the `reverse_each` function chained with a `map` function. # # ```puppet # # For the array $data, return an array containing each # # value multiplied by 10 in reverse order # $data = [1,2,3] # $transformed_data = $data.reverse_each.map |$item| { $item * 10 } # # $transformed_data is set to [30,20,10] # ``` # # @example Using `reverse_each` function chained with a `map` in alternative syntax # # ```puppet # # For the array $data, return an array containing each # # value multiplied by 10 in reverse order # $data = [1,2,3] # $transformed_data = map(reverse_each($data)) |$item| { $item * 10 } # # $transformed_data is set to [30,20,10] # ``` # # @since 4.4.0 # Puppet::Functions.create_function(:reverse_each) do dispatch :reverse_each do param 'Iterable', :iterable end dispatch :reverse_each_block do param 'Iterable', :iterable block_param 'Callable[1,1]', :block end def reverse_each(iterable) # produces an Iterable Puppet::Pops::Types::Iterable.asserted_iterable(self, iterable).reverse_each end def reverse_each_block(iterable, &block) Puppet::Pops::Types::Iterable.asserted_iterable(self, iterable).reverse_each(&block) nil end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/scanf.rb���������������������������������������������������������0000644�0052762�0001160�00000002412�13417161721�020523� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Scans a string and returns an array of one or more converted values based on the given format string. # See the documentation of Ruby's String#scanf method for details about the supported formats (which # are similar but not identical to the formats used in Puppet's `sprintf` function.) # # This function takes two mandatory arguments: the first is the string to convert, and the second is # the format string. The result of the scan is an array, with each successfully scanned and transformed value. # The scanning stops if a scan is unsuccessful, and the scanned result up to that point is returned. If there # was no successful scan, the result is an empty array. # # "42".scanf("%i") # # You can also optionally pass a lambda to scanf, to do additional validation or processing. # # # "42".scanf("%i") |$x| { # unless $x[0] =~ Integer { # fail "Expected a well formed integer value, got '$x[0]'" # } # $x[0] # } # # # # # # @since 4.0.0 # Puppet::Functions.create_function(:scanf) do require 'scanf' dispatch :scanf do param 'String', :data param 'String', :format optional_block_param end def scanf(data, format) result = data.scanf(format) if block_given? result = yield(result) end result end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/slice.rb���������������������������������������������������������0000644�0052762�0001160�00000007434�13417161721�020541� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Slices an array or hash into pieces of a given size. # # This function takes two mandatory arguments: the first should be an array or hash, and the second specifies # the number of elements to include in each slice. # # When the first argument is a hash, each key value pair is counted as one. For example, a slice size of 2 will produce # an array of two arrays with key, and value. # # @example Slicing a Hash # # ```puppet # $a.slice(2) |$entry| { notice "first ${$entry[0]}, second ${$entry[1]}" } # $a.slice(2) |$first, $second| { notice "first ${first}, second ${second}" } # ``` # The function produces a concatenated result of the slices. # # @example Slicing an Array # # ```puppet # slice([1,2,3,4,5,6], 2) # produces [[1,2], [3,4], [5,6]] # slice(Integer[1,6], 2) # produces [[1,2], [3,4], [5,6]] # slice(4,2) # produces [[0,1], [2,3]] # slice('hello',2) # produces [[h, e], [l, l], [o]] # ``` # # @example Passing a lambda to a slice (optional) # # ```puppet # $a.slice($n) |$x| { ... } # slice($a) |$x| { ... } # ``` # # The lambda should have either one parameter (receiving an array with the slice), or the same number # of parameters as specified by the slice size (each parameter receiving its part of the slice). # If there are fewer remaining elements than the slice size for the last slice, it will contain the remaining # elements. If the lambda has multiple parameters, excess parameters are set to undef for an array, or # to empty arrays for a hash. # # @example Getting individual values of a slice # # ```puppet # $a.slice(2) |$first, $second| { ... } # ``` # # @since 4.0.0 # Puppet::Functions.create_function(:slice) do dispatch :slice_Hash do param 'Hash[Any, Any]', :hash param 'Integer[1, default]', :slice_size optional_block_param end dispatch :slice_Enumerable do param 'Iterable', :enumerable param 'Integer[1, default]', :slice_size optional_block_param end def slice_Hash(hash, slice_size, &pblock) result = slice_Common(hash, slice_size, [], block_given? ? pblock : nil) block_given? ? hash : result end def slice_Enumerable(enumerable, slice_size, &pblock) enum = Puppet::Pops::Types::Iterable.asserted_iterable(self, enumerable) result = slice_Common(enum, slice_size, nil, block_given? ? pblock : nil) block_given? ? enumerable : result end def slice_Common(o, slice_size, filler, pblock) serving_size = asserted_slice_serving_size(pblock, slice_size) enumerator = o.each_slice(slice_size) result = [] if serving_size == 1 begin if pblock loop do pblock.call(enumerator.next) end else loop do result << enumerator.next end end rescue StopIteration end else begin loop do a = enumerator.next if a.size < serving_size a = a.dup.fill(filler, a.length...serving_size) end pblock.call(*a) end rescue StopIteration end end if pblock o else result end end def asserted_slice_serving_size(pblock, slice_size) if pblock arity = pblock.arity serving_size = arity < 0 ? slice_size : arity else serving_size = 1 end if serving_size == 0 raise ArgumentError, _("slice(): block must define at least one parameter. Block has 0.") end unless serving_size == 1 || serving_size == slice_size raise ArgumentError, _("slice(): block must define one parameter, or the same number of parameters as the given size of the slice (%{slice_size}). Block has %{serving_size}; %{parameter_names}") % { slice_size: slice_size, serving_size: serving_size, parameter_names: pblock.parameter_names.join(', ') } end serving_size end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/split.rb���������������������������������������������������������0000644�0052762�0001160�00000002433�13417161721�020567� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Splits a string into an array using a given pattern. # The pattern can be a string, regexp or regexp type. # # @example Splitting a String value # # ```puppet # $string = 'v1.v2:v3.v4' # $array_var1 = split($string, /:/) # $array_var2 = split($string, '[.]') # $array_var3 = split($string, Regexp['[.:]']) # # #`$array_var1` now holds the result `['v1.v2', 'v3.v4']`, # # while `$array_var2` holds `['v1', 'v2:v3', 'v4']`, and # # `$array_var3` holds `['v1', 'v2', 'v3', 'v4']`. # ``` # # Note that in the second example, we split on a literal string that contains # a regexp meta-character (`.`), which must be escaped. A simple # way to do that for a single character is to enclose it in square # brackets; a backslash will also escape a single character. # Puppet::Functions.create_function(:split) do dispatch :split_String do param 'String', :str param 'String', :pattern end dispatch :split_Regexp do param 'String', :str param 'Regexp', :pattern end dispatch :split_RegexpType do param 'String', :str param 'Type[Regexp]', :pattern end def split_String(str, pattern) str.split(Regexp.compile(pattern)) end def split_Regexp(str, pattern) str.split(pattern) end def split_RegexpType(str, pattern) str.split(pattern.regexp) end end�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/step.rb����������������������������������������������������������0000644�0052762�0001160�00000005605�13417161721�020413� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Provides stepping with given interval over elements in an iterable and optionally runs a # [lambda](https://puppet.com/docs/puppet/latest/lang_lambdas.html) for each # element. # # This function takes two to three arguments: # # 1. An 'Iterable' that the function will iterate over. # 2. An `Integer` step factor. This must be a positive integer. # 3. An optional lambda, which the function calls for each element in the interval. It must # request one parameter. # # @example Using the `step` function # # ```puppet # $data.step(<n>) |$parameter| { <PUPPET CODE BLOCK> } # ``` # # or # # ```puppet # $stepped_data = $data.step(<n>) # ``` # # or # # ```puppet # step($data, <n>) |$parameter| { <PUPPET CODE BLOCK> } # ``` # # or # # ```puppet # $stepped_data = step($data, <n>) # ``` # # When no block is given, Puppet returns an `Iterable` that yields the first element and every nth successor # element, from its first argument. This allows functions on iterables to be chained. # When a block is given, Puppet iterates and calls the block with the first element and then with # every nth successor element. It then returns `undef`. # # @example Using the `step` function with an array, a step factor, and a one-parameter block # # ```puppet # # For the array $data, call a block with the first element and then with each 3rd successor element # $data = [1,2,3,4,5,6,7,8] # $data.step(3) |$item| { # notice($item) # } # # Puppet notices the values '1', '4', '7'. # ``` # When no block is given, Puppet returns a new `Iterable` which allows it to be directly chained into # another function that takes an `Iterable` as an argument. # # @example Using the `step` function chained with a `map` function. # # ```puppet # # For the array $data, return an array, set to the first element and each 5th successor element, in reverse # # order multiplied by 10 # $data = Integer[0,20] # $transformed_data = $data.step(5).map |$item| { $item * 10 } # $transformed_data contains [0,50,100,150,200] # ``` # # @example The same example using `step` function chained with a `map` in alternative syntax # # ```puppet # # For the array $data, return an array, set to the first and each 5th # # successor, in reverse order, multiplied by 10 # $data = Integer[0,20] # $transformed_data = map(step($data, 5)) |$item| { $item * 10 } # $transformed_data contains [0,50,100,150,200] # ``` # # @since 4.4.0 # Puppet::Functions.create_function(:step) do dispatch :step do param 'Iterable', :iterable param 'Integer[1]', :step end dispatch :step_block do param 'Iterable', :iterable param 'Integer[1]', :step block_param 'Callable[1,1]', :block end def step(iterable, step) # produces an Iterable Puppet::Pops::Types::Iterable.asserted_iterable(self, iterable).step(step) end def step_block(iterable, step, &block) Puppet::Pops::Types::Iterable.asserted_iterable(self, iterable).step(step, &block) nil end end ���������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/strftime.rb������������������������������������������������������0000644�0052762�0001160�00000016726�13417161721�021303� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Formats timestamp or timespan according to the directives in the given format string. The directives begins with a percent (%) character. # Any text not listed as a directive will be passed through to the output string. # # A third optional timezone argument can be provided. The first argument will then be formatted to represent a local time in that # timezone. The timezone can be any timezone that is recognized when using the '%z' or '%Z' formats, or the word 'current', in which # case the current timezone of the evaluating process will be used. The timezone argument is case insensitive. # # The default timezone, when no argument is provided, or when using the keyword `default`, is 'UTC'. # # The directive consists of a percent (%) character, zero or more flags, optional minimum field width and # a conversion specifier as follows: # # ``` # %[Flags][Width]Conversion # ``` # # ### Flags that controls padding # # | Flag | Meaning # | ---- | --------------- # | - | Don't pad numerical output # | _ | Use spaces for padding # | 0 | Use zeros for padding # # ### `Timestamp` specific flags # # | Flag | Meaning # | ---- | --------------- # | # | Change case # | ^ | Use uppercase # | : | Use colons for %z # # ### Format directives applicable to `Timestamp` (names and padding can be altered using flags): # # **Date (Year, Month, Day):** # # | Format | Meaning | # | ------ | ------- | # | Y | Year with century, zero-padded to at least 4 digits | # | C | year / 100 (rounded down such as 20 in 2009) | # | y | year % 100 (00..99) | # | m | Month of the year, zero-padded (01..12) | # | B | The full month name ("January") | # | b | The abbreviated month name ("Jan") | # | h | Equivalent to %b | # | d | Day of the month, zero-padded (01..31) | # | e | Day of the month, blank-padded ( 1..31) | # | j | Day of the year (001..366) | # # **Time (Hour, Minute, Second, Subsecond):** # # | Format | Meaning | # | ------ | ------- | # | H | Hour of the day, 24-hour clock, zero-padded (00..23) | # | k | Hour of the day, 24-hour clock, blank-padded ( 0..23) | # | I | Hour of the day, 12-hour clock, zero-padded (01..12) | # | l | Hour of the day, 12-hour clock, blank-padded ( 1..12) | # | P | Meridian indicator, lowercase ("am" or "pm") | # | p | Meridian indicator, uppercase ("AM" or "PM") | # | M | Minute of the hour (00..59) | # | S | Second of the minute (00..60) | # | L | Millisecond of the second (000..999). Digits under millisecond are truncated to not produce 1000 | # | N | Fractional seconds digits, default is 9 digits (nanosecond). Digits under a specified width are truncated to avoid carry up | # # **Time (Hour, Minute, Second, Subsecond):** # # | Format | Meaning | # | ------ | ------- | # | z | Time zone as hour and minute offset from UTC (e.g. +0900) | # | :z | hour and minute offset from UTC with a colon (e.g. +09:00) | # | ::z | hour, minute and second offset from UTC (e.g. +09:00:00) | # | Z | Abbreviated time zone name or similar information. (OS dependent) | # # **Weekday:** # # | Format | Meaning | # | ------ | ------- | # | A | The full weekday name ("Sunday") | # | a | The abbreviated name ("Sun") | # | u | Day of the week (Monday is 1, 1..7) | # | w | Day of the week (Sunday is 0, 0..6) | # # **ISO 8601 week-based year and week number:** # # The first week of YYYY starts with a Monday and includes YYYY-01-04. # The days in the year before the first week are in the last week of # the previous year. # # | Format | Meaning | # | ------ | ------- | # | G | The week-based year | # | g | The last 2 digits of the week-based year (00..99) | # | V | Week number of the week-based year (01..53) | # # **Week number:** # # The first week of YYYY that starts with a Sunday or Monday (according to %U # or %W). The days in the year before the first week are in week 0. # # | Format | Meaning | # | ------ | ------- | # | U | Week number of the year. The week starts with Sunday. (00..53) | # | W | Week number of the year. The week starts with Monday. (00..53) | # # **Seconds since the Epoch:** # # | Format | Meaning | # | s | Number of seconds since 1970-01-01 00:00:00 UTC. | # # **Literal string:** # # | Format | Meaning | # | ------ | ------- | # | n | Newline character (\n) | # | t | Tab character (\t) | # | % | Literal "%" character | # # **Combination:** # # | Format | Meaning | # | ------ | ------- | # | c | date and time (%a %b %e %T %Y) | # | D | Date (%m/%d/%y) | # | F | The ISO 8601 date format (%Y-%m-%d) | # | v | VMS date (%e-%^b-%4Y) | # | x | Same as %D | # | X | Same as %T | # | r | 12-hour time (%I:%M:%S %p) | # | R | 24-hour time (%H:%M) | # | T | 24-hour time (%H:%M:%S) | # # @example Using `strftime` with a `Timestamp`: # # ```puppet # $timestamp = Timestamp('2016-08-24T12:13:14') # # # Notice the timestamp using a format that notices the ISO 8601 date format # notice($timestamp.strftime('%F')) # outputs '2016-08-24' # # # Notice the timestamp using a format that notices weekday, month, day, time (as UTC), and year # notice($timestamp.strftime('%c')) # outputs 'Wed Aug 24 12:13:14 2016' # # # Notice the timestamp using a specific timezone # notice($timestamp.strftime('%F %T %z', 'PST')) # outputs '2016-08-24 04:13:14 -0800' # # # Notice the timestamp using timezone that is current for the evaluating process # notice($timestamp.strftime('%F %T', 'current')) # outputs the timestamp using the timezone for the current process # ``` # # ### Format directives applicable to `Timespan`: # # | Format | Meaning | # | ------ | ------- | # | D | Number of Days | # | H | Hour of the day, 24-hour clock | # | M | Minute of the hour (00..59) | # | S | Second of the minute (00..59) | # | L | Millisecond of the second (000..999). Digits under millisecond are truncated to not produce 1000. | # | N | Fractional seconds digits, default is 9 digits (nanosecond). Digits under a specified length are truncated to avoid carry up | # # The format directive that represents the highest magnitude in the format will be allowed to overflow. # I.e. if no "%D" is used but a "%H" is present, then the hours will be more than 23 in case the # timespan reflects more than a day. # # @example Using `strftime` with a Timespan and a format # # ```puppet # $duration = Timespan({ hours => 3, minutes => 20, seconds => 30 }) # # # Notice the duration using a format that outputs <hours>:<minutes>:<seconds> # notice($duration.strftime('%H:%M:%S')) # outputs '03:20:30' # # # Notice the duration using a format that outputs <minutes>:<seconds> # notice($duration.strftime('%M:%S')) # outputs '200:30' # ``` # # - Since 4.8.0 # Puppet::Functions.create_function(:strftime) do dispatch :format_timespan do param 'Timespan', :time_object param 'String', :format end dispatch :format_timestamp do param 'Timestamp', :time_object param 'String', :format optional_param 'String', :timezone end dispatch :legacy_strftime do param 'String', :format optional_param 'String', :timezone end def format_timespan(time_object, format) time_object.format(format) end def format_timestamp(time_object, format, timezone = nil) time_object.format(format, timezone) end def legacy_strftime(format, timezone = nil) file, line = Puppet::Pops::PuppetStack.top_of_stack Puppet.warn_once('deprecations', 'legacy#strftime', _('The argument signature (String format, [String timezone]) is deprecated for #strftime. See #strftime documentation and Timespan type for more info'), file, line) Puppet::Pops::Time::Timestamp.format_time(format, Time.now.utc, timezone) end end ������������������������������������������puppet-5.5.10/lib/puppet/functions/then.rb����������������������������������������������������������0000644�0052762�0001160�00000005074�13417161721�020376� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Calls a [lambda](https://puppet.com/docs/puppet/latest/lang_lambdas.html) # with the given argument unless the argument is `undef`. # Returns `undef` if the argument is `undef`, and otherwise the result of giving the # argument to the lambda. # # This is useful to process a sequence of operations where an intermediate # result may be `undef` (which makes the entire sequence `undef`). # The `then` function is especially useful with the function `dig` which # performs in a similar way "digging out" a value in a complex structure. # # @example Using `dig` and `then` # # ```puppet # $data = {a => { b => [{x => 10, y => 20}, {x => 100, y => 200}]}} # notice $data.dig(a, b, 1, x).then |$x| { $x * 2 } # ``` # # Would notice the value 200 # # Contrast this with: # # ```puppet # $data = {a => { b => [{x => 10, y => 20}, {not_x => 100, why => 200}]}} # notice $data.dig(a, b, 1, x).then |$x| { $x * 2 } # ``` # # Which would notice `undef` since the last lookup of 'x' results in `undef` which # is returned (without calling the lambda given to the `then` function). # # As a result there is no need for conditional logic or a temporary (non local) # variable as the result is now either the wanted value (`x`) multiplied # by 2 or `undef`. # # Calls to `then` can be chained. In the next example, a structure is using an offset based on # using 1 as the index to the first element (instead of 0 which is used in the language). # We are not sure if user input actually contains an index at all, or if it is # outside the range of available names.args. # # @example Chaining calls to the `then` function # # ```puppet # # Names to choose from # $names = ['Ringo', 'Paul', 'George', 'John'] # # # Structure where 'beatle 2' is wanted (but where the number refers # # to 'Paul' because input comes from a source using 1 for the first # # element). # # $data = ['singer', { beatle => 2 }] # $picked = assert_type(String, # # the data we are interested in is the second in the array, # # a hash, where we want the value of the key 'beatle' # $data.dig(1, 'beatle') # # and we want the index in $names before the given index # .then |$x| { $names[$x-1] } # # so we can construct a string with that beatle's name # .then |$x| { "Picked Beatle '${x}'" } # ) # notice $picked # ``` # # Would notice "Picked Beatle 'Paul'", and would raise an error if the result # was not a String. # # * Since 4.5.0 # Puppet::Functions.create_function(:then) do dispatch :then do param 'Any', :arg block_param 'Callable[1,1]', :block end def then(arg) return nil if arg.nil? yield(arg) end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/tree_each.rb�����������������������������������������������������0000644�0052762�0001160�00000016637�13417161721�021366� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Runs a [lambda](https://puppet.com/docs/puppet/latest/lang_lambdas.html) # recursively and repeatedly using values from a data structure, then returns the unchanged data structure, or if # a lambda is not given, returns an `Iterator` for the tree. # # This function takes one mandatory argument, one optional, and an optional block in this order: # # 1. An `Array`, `Hash`, `Iterator`, or `Object` that the function will iterate over. # 2. An optional hash with the options: # * `include_containers` => `Optional[Boolean]` # default true - if containers should be given to the lambda # * `include_values` => `Optional[Boolean]` # default true - if non containers should be given to the lambda # * `include_root` => `Optional[Boolean]` # default true - if the root container should be given to the lambda # * `container_type` => `Optional[Type[Variant[Array, Hash, Object]]]` # a type that determines what a container is - can only # be set to a type that matches the default `Variant[Array, Hash, Object]`. # * `order` => `Enum[depth_first, breadth_first]` # default ´depth_first`, the order in which elements are visited # * `include_refs` => Optional[Boolean] # default `false`, if attributes in objects marked as bing of `reference` kind # should be included. # 3. An optional lambda, which the function calls for each element in the first argument. It must # accept one or two arguments; either `$path`, and `$value`, or just `$value`. # # @example Using the `tree_each` function # # `$data.tree_each |$path, $value| { <PUPPET CODE BLOCK> }` # `$data.tree_each |$value| { <PUPPET CODE BLOCK> }` # # or # # `tree_each($data) |$path, $value| { <PUPPET CODE BLOCK> }` # `tree_each($data) |$value| { <PUPPET CODE BLOCK> }` # # The parameter `$path` is always given as an `Array` containing the path that when applied to # the tree as `$data.dig(*$path) yields the `$value`. # The `$value` is the value at that path. # # For `Array` values, the path will contain `Integer` entries with the array index, # and for `Hash` values, the path will contain the hash key, which may be `Any` value. # For `Object` containers, the entry is the name of the attribute (a `String`). # # The tree is walked in either depth-first order, or in breadth-first order under the control of the # `order` option, yielding each `Array`, `Hash`, `Object`, and each entry/attribute. # The default is `depth_first` which means that children are processed before siblings. # An order of `breadth_first` means that siblings are processed before children. # # @example depth- or breadth-first order # # ```puppet # [1, [2, 3], 4] # ``` # # Results in: # # If containers are skipped: # # * `depth_first` order `1`, `2`, `3`, `4` # * `breadth_first` order `1`, `4`,`2`, `3` # # If containers and root, are included: # # * `depth_first` order `[1, [2, 3], 4]`, `1`, `[2, 3]`, `2`, `3`, `4` # * `breadth_first` order `[1, [2, 3], 4]`, `1`, `[2, 3]`, `4`, `2`, `3` # # Typical use of the `tree_each` function include: # * a more efficient way to iterate over a tree than first using `flatten` on an array # as that requires a new (potentially very large) array to be created # * when a tree needs to be transformed and 'pretty printed' in a template # * avoiding having to write a special recursive function when tree contains hashes (flatten does # not work on hashes) # # @example A flattened iteration over a tree excluding Collections # # ```puppet # $data = [1, 2, [3, [4, 5]]] # $data.tree_each({include_containers => false}) |$v| { notice "$v" } # ``` # # This would call the lambda 5 times with with the following values in sequence: `1`, `2`, `3`, `4`, `5` # # @example A flattened iteration over a tree (including containers by default) # # ```puppet # $data = [1, 2, [3, [4, 5]]] # $data.tree_each |$v| { notice "$v" } # ``` # # This would call the lambda 7 times with the following values in sequence: # `1`, `2`, `[3, [4, 5]]`, `3`, `[4, 5]`, `4`, `5` # # @example A flattened iteration over a tree (including only non root containers) # # ```puppet # $data = [1, 2, [3, [4, 5]]] # $data.tree_each({include_values => false, include_root => false}) |$v| { notice "$v" } # ``` # # This would call the lambda 2 times with the following values in sequence: # `[3, [4, 5]]`, `[4, 5]` # # Any Puppet Type system data type can be used to filter what is # considered to be a container, but it must be a narrower type than one of # the default Array, Hash, Object types - for example it is not possible to make a # `String` be a container type. # # @example Only `Array` as container type # # ```puppet # $data = [1, {a => 'hello', b => [100, 200]}, [3, [4, 5]]] # $data.tree_each({container_type => Array, include_containers => false} |$v| { notice "$v" } # ``` # # Would call the lambda 5 times with `1`, `{a => 'hello', b => [100, 200]}`, `3`, `4`, `5` # # **Chaining** When calling `tree_each` without a lambda the function produces an `Iterator` # that can be chained into another iteration. Thus it is easy to use one of: # # * `reverse_each` - get "leaves before root" # * `filter` - prune the tree # * `map` - transform each element # * `reduce` - produce something else # # Note than when chaining, the value passed on is a `Tuple` with `[path, value]`. # # @example Pruning a tree # # ```puppet # # A tree of some complexity (here very simple for readability) # $tree = [ # { name => 'user1', status => 'inactive', id => '10'}, # { name => 'user2', status => 'active', id => '20'} # ] # notice $tree.tree_each.filter |$v| { # $value = $v[1] # $value =~ Hash and $value[status] == active # } # ``` # # Would notice `[[[1], {name => user2, status => active, id => 20}]]`, which can then be processed # further as each filtered result appears as a `Tuple` with `[path, value]`. # # # For general examples that demonstrates iteration see the Puppet # [iteration](https://puppet.com/docs/puppet/latest/lang_iteration.html) # documentation. # # @since 5.0.0 # Puppet::Functions.create_function(:tree_each) do local_types do type "OptionsType = Struct[{\ container_type => Optional[Type],\ include_root => Optional[Boolean], include_containers => Optional[Boolean],\ include_values => Optional[Boolean],\ order => Optional[Enum[depth_first, breadth_first]],\ include_refs => Optional[Boolean]\ }]" end dispatch :tree_Enumerable2 do param 'Variant[Iterator, Array, Hash, Object]', :tree optional_param 'OptionsType', :options block_param 'Callable[2,2]', :block end dispatch :tree_Enumerable1 do param 'Variant[Iterator, Array, Hash, Object]', :tree optional_param 'OptionsType', :options block_param 'Callable[1,1]', :block end dispatch :tree_Iterable do param 'Variant[Iterator, Array, Hash, Object]', :tree optional_param 'OptionsType', :options end def tree_Enumerable1(enum, options = {}, &block) iterator(enum, options).each {|_, v| yield(v) } enum end def tree_Enumerable2(enum, options = {}, &block) iterator(enum, options).each {|path, v| yield(path, v) } enum end def tree_Iterable(enum, options = {}, &block) Puppet::Pops::Types::Iterable.on(iterator(enum, options)) end def iterator(enum, options) if depth_first?(options) Puppet::Pops::Types::Iterable::DepthFirstTreeIterator.new(enum, options) else Puppet::Pops::Types::Iterable::BreadthFirstTreeIterator.new(enum, options) end end def depth_first?(options) (order = options['order']).nil? ? true : order == 'depth_first' end end �������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/type.rb����������������������������������������������������������0000644�0052762�0001160�00000003663�13417161721�020423� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Returns the data type of a given value with a given degree of generality. # # ```puppet # type InferenceFidelity = Enum[generalized, reduced, detailed] # # function type(Any $value, InferenceFidelity $fidelity = 'detailed') # returns Type # ``` # # @example Using `type` # # ``` puppet # notice type(42) =~ Type[Integer] # ``` # # Would notice `true`. # # By default, the best possible inference is made where all details are retained. # This is good when the type is used for further type calculations but is overwhelmingly # rich in information if it is used in a error message. # # The optional argument `$fidelity` may be given as (from lowest to highest fidelity): # # * `generalized` - reduces to common type and drops size constraints # * `reduced` - reduces to common type in collections # * `detailed` - (default) all details about inferred types is retained # # @example Using `type()` with different inference fidelity: # # ``` puppet # notice type([3.14, 42], 'generalized') # notice type([3.14, 42], 'reduced'') # notice type([3.14, 42], 'detailed') # notice type([3.14, 42]) # ``` # # Would notice the four values: # # 1. 'Array[Numeric]' # 2. 'Array[Numeric, 2, 2]' # 3. 'Tuple[Float[3.14], Integer[42,42]]]' # 4. 'Tuple[Float[3.14], Integer[42,42]]]' # # @since 4.4.0 # Puppet::Functions.create_function(:type) do dispatch :type_detailed do param 'Any', :value optional_param 'Enum[detailed]', :inference_method end dispatch :type_parameterized do param 'Any', :value param 'Enum[reduced]', :inference_method end dispatch :type_generalized do param 'Any', :value param 'Enum[generalized]', :inference_method end def type_detailed(value, _ = nil) Puppet::Pops::Types::TypeCalculator.infer_set(value) end def type_parameterized(value, _) Puppet::Pops::Types::TypeCalculator.infer(value) end def type_generalized(value, _) Puppet::Pops::Types::TypeCalculator.infer(value).generalize end end �����������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/unique.rb��������������������������������������������������������0000644�0052762�0001160�00000011322�13417161721�020737� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Produces a unique set of values from an `Iterable` argument. # # * If the argument is a `String`, the unique set of characters are returned as a new `String`. # * If the argument is a `Hash`, the resulting hash associates a set of keys with a set of unique values. # * For all other types of `Iterable` (`Array`, `Iterator`) the result is an `Array` with # a unique set of entries. # * Comparison of all `String` values are case sensitive. # * An optional code block can be given - if present it is given each candidate value and its return is used instead of the given value. This # enables transformation of the value before comparison. The result of the lambda is only used for comparison. # * The optional code block when used with a hash is given each value (not the keys). # # @example Using unique with a String # # ```puppet # # will produce 'abc' # "abcaabb".unique # ``` # # @example Using unique with an Array # # ```puppet # # will produce ['a', 'b', 'c'] # ['a', 'b', 'c', 'a', 'a', 'b'].unique # ``` # # @example Using unique with a Hash # # ```puppet # # will produce { ['a', 'b'] => [10], ['c'] => [20]} # {'a' => 10, 'b' => 10, 'c' => 20}.unique # # # will produce { 'a' => 10, 'c' => 20 } (use first key with first value) # Hash.new({'a' => 10, 'b' => 10, 'c' => 20}.unique.map |$k, $v| { [ $k[0] , $v[0]] }) # # # will produce { 'b' => 10, 'c' => 20 } (use last key with first value) # Hash.new({'a' => 10, 'b' => 10, 'c' => 20}.unique.map |$k, $v| { [ $k[-1] , $v[0]] }) # ``` # # @example Using unique with an Iterable # # ``` # # will produce [3, 2, 1] # [1,2,2,3,3].reverse_each.unique # ``` # # @example Using unique with a lambda # # ```puppet # # will produce [['sam', 'smith'], ['sue', 'smith']] # [['sam', 'smith'], ['sam', 'brown'], ['sue', 'smith']].unique |$x| { $x[0] } # # # will produce [['sam', 'smith'], ['sam', 'brown']] # [['sam', 'smith'], ['sam', 'brown'], ['sue', 'smith']].unique |$x| { $x[1] } # # # will produce ['aBc', 'bbb'] (using a lambda to make comparison using downcased (%d) strings) # ['aBc', 'AbC', 'bbb'].unique |$x| { String($x,'%d') } # # # will produce {[a] => [10], [b, c, d, e] => [11, 12, 100]} # {a => 10, b => 11, c => 12, d => 100, e => 11}.unique |$v| { if $v > 10 { big } else { $v } } # ``` # # Note that for `Hash` the result is slightly different than for the other data types. For those the result contains the # *first-found* unique value, but for `Hash` it contains associations from a set of keys to the set of values clustered by the # equality lambda (or the default value equality if no lambda was given). This makes the `unique` function more versatile for hashes # in general, while requiring that the simple computation of "hash's unique set of values" is performed as `$hsh.map |$k, $v| { $v }.unique`. # (A unique set of hash keys is in general meaningless (since they are unique by definition) - although if processed with a different # lambda for equality that would be different. First map the hash to an array of its keys if such a unique computation is wanted). # If the more advanced clustering is wanted for one of the other data types, simply transform it into a `Hash` as shown in the # following example. # # @example turning a string or array into a hash with index keys # # ```puppet # # Array ['a', 'b', 'c'] to Hash with index results in # # {0 => 'a', 1 => 'b', 2 => 'c'} # Hash(['a', 'b', 'c'].map |$i, $v| { [$i, $v]}) # # # String "abc" to Hash with index results in # # {0 => 'a', 1 => 'b', 2 => 'c'} # Hash(Array("abc").map |$i,$v| { [$i, $v]}) # "abc".to(Array).map |$i,$v| { [$i, $v]}.to(Hash) # ``` # # @since Puppet 5.0.0 # Puppet::Functions.create_function(:unique) do dispatch :unique_string do param 'String', :string optional_block_param 'Callable[String]', :block end dispatch :unique_hash do param 'Hash', :hash optional_block_param 'Callable[Any]', :block end dispatch :unique_array do param 'Array', :array optional_block_param 'Callable[Any]', :block end dispatch :unique_iterable do param 'Iterable', :iterable optional_block_param 'Callable[Any]', :block end def unique_string(string, &block) string.split('').uniq(&block).join('') end def unique_hash(hash, &block) block = lambda {|v| v } unless block_given? result = Hash.new {|h, k| h[k] = {:keys =>[], :values =>[]} } hash.each_pair do |k,v| rc = result[ block.call(v) ] rc[:keys] << k rc[:values] << v end # reduce the set of possibly duplicated value entries inverted = {} result.each_pair {|k,v| inverted[v[:keys]] = v[:values].uniq } inverted end def unique_array(array,&block) array.uniq(&block) end def unique_iterable(iterable, &block) Puppet::Pops::Types::Iterable.on(iterable).uniq(&block) end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/unwrap.rb��������������������������������������������������������0000644�0052762�0001160�00000002226�13417161721�020750� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Unwraps a Sensitive value and returns the wrapped object. # # @example Usage of unwrap # # ```puppet # $plaintext = 'hunter2' # $pw = Sensitive.new($plaintext) # notice("Wrapped object is $pw") #=> Prints "Wrapped object is Sensitive [value redacted]" # $unwrapped = $pw.unwrap # notice("Unwrapped object is $unwrapped") #=> Prints "Unwrapped object is hunter2" # ``` # # You can optionally pass a block to unwrap in order to limit the scope where the # unwrapped value is visible. # # @example Unwrapping with a block of code # # ```puppet # $pw = Sensitive.new('hunter2') # notice("Wrapped object is $pw") #=> Prints "Wrapped object is Sensitive [value redacted]" # $pw.unwrap |$unwrapped| { # $conf = inline_template("password: ${unwrapped}\n") # Sensitive.new($conf) # } #=> Returns a new Sensitive object containing an interpolated config file # # $unwrapped is now out of scope # ``` # # @since 4.0.0 # Puppet::Functions.create_function(:unwrap) do dispatch :unwrap do param 'Sensitive', :arg optional_block_param end def unwrap(arg) unwrapped = arg.unwrap if block_given? yield(unwrapped) else unwrapped end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/values.rb��������������������������������������������������������0000644�0052762�0001160�00000001257�13417161721�020736� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Returns the values of a hash as an Array # # @example Using `values` # # ```puppet # $hsh = {"apples" => 3, "oranges" => 4 } # $hsh.values() # values($hsh) # # both results in the array [3, 4] # ``` # # * Note that a hash in the puppet language accepts any data value (including `undef`) unless # it is constrained with a `Hash` data type that narrows the allowed data types. # * For an empty hash, an empty array is returned. # * The order of the values is the same as the order in the hash (typically the order in which they were added). # Puppet::Functions.create_function(:values) do dispatch :values do param 'Hash', :hsh end def values(hsh) hsh.values end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/versioncmp.rb����������������������������������������������������0000644�0052762�0001160�00000001354�13417161721�021622� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/package' # Compares two version numbers. # # Prototype: # # \$result = versioncmp(a, b) # # Where a and b are arbitrary version strings. # # This function returns: # # * `1` if version a is greater than version b # * `0` if the versions are equal # * `-1` if version a is less than version b # # @example Using versioncmp # # if versioncmp('2.6-1', '2.4.5') > 0 { # notice('2.6-1 is > than 2.4.5') # } # # This function uses the same version comparison algorithm used by Puppet's # `package` type. # Puppet::Functions.create_function(:versioncmp) do dispatch :versioncmp do param 'String', :a param 'String', :b end def versioncmp(a, b) Puppet::Util::Package.versioncmp(a, b) end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/warning.rb�������������������������������������������������������0000644�0052762�0001160�00000000610�13417161721�021074� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Logs a message on the server at level `warning`. Puppet::Functions.create_function(:warning, Puppet::Functions::InternalFunction) do # @param values The values to log. # @return [Undef] dispatch :warning do scope_param repeated_param 'Any', :values return_type 'Undef' end def warning(scope, *values) Puppet::Util::Log.log_func(scope, :warning, values) end end ������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/with.rb����������������������������������������������������������0000644�0052762�0001160�00000001656�13417161721�020415� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Calls a [lambda](https://puppet.com/docs/puppet/latest/lang_lambdas.html) # with the given arguments and returns the result. # # Since a lambda's scope is # [local](https://puppet.com/docs/puppet/latest/lang_lambdas.html#lambda-scope) # to the lambda, you can use the `with` function to create private blocks of code within a # class using variables whose values cannot be accessed outside of the lambda. # # @example Using `with` # # ```puppet # # Concatenate three strings into a single string formatted as a list. # $fruit = with("apples", "oranges", "bananas") |$x, $y, $z| { # "${x}, ${y}, and ${z}" # } # $check_var = $x # # $fruit contains "apples, oranges, and bananas" # # $check_var is undefined, as the value of $x is local to the lambda. # ``` # # @since 4.0.0 # Puppet::Functions.create_function(:with) do dispatch :with do repeated_param 'Any', :arg block_param end def with(*args) yield(*args) end end ����������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/call.rb����������������������������������������������������������0000644�0052762�0001160�00000002025�13417161721�020344� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Calls an arbitrary Puppet function by name. # # This function takes one mandatory argument and one or more optional arguments: # # 1. A string corresponding to a function name. # 2. Any number of arguments to be passed to the called function. # 3. An optional lambda, if the function being called supports it. # # @example Using the `call` function # # ```puppet # $a = 'notice' # call($a, 'message') # ``` # # @example Using the `call` function with a lambda # # ```puppet # $a = 'each' # $b = [1,2,3] # call($a, $b) |$item| { # notify { $item: } # } # ``` # # The `call` function can be used to call either Ruby functions or Puppet language # functions. # # @since 5.0.0 # Puppet::Functions.create_function(:call, Puppet::Functions::InternalFunction) do dispatch :call_impl_block do scope_param param 'String', :function_name repeated_param 'Any', :arguments optional_block_param end def call_impl_block(scope, function_name, *args, &block) call_function_with_scope(scope, function_name, *args, &block) end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/convert_to.rb����������������������������������������������������0000644�0052762�0001160�00000002056�13417161721�021617� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The `convert_to(value, type)` is a convenience function that does the same as `new(type, value)`. # The difference in the argument ordering allows it to be used in chained style for # improved readability "left to right". # # When the function is given a lambda, it is called with the converted value, and the function # returns what the lambda returns, otherwise the converted value. # # @example 'convert_to' instead of 'new' # # ```puppet # # The harder to read variant: # # Using new operator - that is "calling the type" with operator () # Hash(Array("abc").map |$i,$v| { [$i, $v] }) # # # The easier to read variant: # # using 'convert_to' # "abc".convert_to(Array).map |$i,$v| { [$i, $v] }.convert_to(Hash) # ``` # # @since 5.4.0 # Puppet::Functions.create_function(:convert_to) do dispatch :convert_to do param 'Any', :value param 'Type', :type optional_block_param 'Callable[1,1]', :block end def convert_to(value, type, &block) result = call_function('new', type, value) block_given? ? yield(result) : result end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/dig.rb�����������������������������������������������������������0000644�0052762�0001160�00000003133�13417161721�020175� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Returns a value for a sequence of given keys/indexes into a structure, such as # an array or hash. # # This function is used to "dig into" a complex data structure by # using a sequence of keys / indexes to access a value from which # the next key/index is accessed recursively. # # The first encountered `undef` value or key stops the "dig" and `undef` is returned. # # An error is raised if an attempt is made to "dig" into # something other than an `undef` (which immediately returns `undef`), an `Array` or a `Hash`. # # @example Using `dig` # # ```puppet # $data = {a => { b => [{x => 10, y => 20}, {x => 100, y => 200}]}} # notice $data.dig('a', 'b', 1, 'x') # ``` # # Would notice the value 100. # # This is roughly equivalent to `$data['a']['b'][1]['x']`. However, a standard # index will return an error and cause catalog compilation failure if any parent # of the final key (`'x'`) is `undef`. The `dig` function will return `undef`, # rather than failing catalog compilation. This allows you to check if data # exists in a structure without mandating that it always exists. # # @since 4.5.0 # Puppet::Functions.create_function(:dig) do dispatch :dig do param 'Optional[Collection]', :data repeated_param 'Any', :arg end def dig(data, *args) walked_path = [] args.reduce(data) do | d, k | return nil if d.nil? || k.nil? if !(d.is_a?(Array) || d.is_a?(Hash)) raise ArgumentError, _("The given data does not contain a Collection at %{walked_path}, got '%{klass}'") % { walked_path: walked_path, klass: d.class } end walked_path << k d[k] end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/eyaml_lookup_key.rb����������������������������������������������0000644�0052762�0001160�00000007226�13417161721�023011� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The `eyaml_lookup_key` is a hiera 5 `lookup_key` data provider function. # See [the configuration guide documentation](https://puppet.com/docs/puppet/latest/hiera_config_yaml_5.html#configuring-a-hierarchy-level-hiera-eyaml) for # how to use this function. # # @since 5.0.0 # Puppet::Functions.create_function(:eyaml_lookup_key) do unless Puppet.features.hiera_eyaml? raise Puppet::DataBinding::LookupError, 'Lookup using eyaml lookup_key function is only supported when the hiera_eyaml library is present' end require 'hiera/backend/eyaml/encryptor' require 'hiera/backend/eyaml/utils' require 'hiera/backend/eyaml/options' require 'hiera/backend/eyaml/parser/parser' dispatch :eyaml_lookup_key do param 'String[1]', :key param 'Hash[String[1],Any]', :options param 'Puppet::LookupContext', :context end def eyaml_lookup_key(key, options, context) return context.cached_value(key) if context.cache_has_key(key) # Can't do this with an argument_mismatch dispatcher since there is no way to declare a struct that at least # contains some keys but may contain other arbitrary keys. unless options.include?('path') #TRANSLATORS 'eyaml_lookup_key':, 'path', 'paths' 'glob', 'globs', 'mapped_paths', and lookup_key should not be translated raise ArgumentError, _("'eyaml_lookup_key': one of 'path', 'paths' 'glob', 'globs' or 'mapped_paths' must be declared in hiera.yaml"\ " when using this lookup_key function") end # nil key is used to indicate that the cache contains the raw content of the eyaml file raw_data = context.cached_value(nil) if raw_data.nil? raw_data = load_data_hash(options, context) context.cache(nil, raw_data) end context.not_found unless raw_data.include?(key) context.cache(key, decrypt_value(raw_data[key], context, options)) end def load_data_hash(options, context) path = options['path'] context.cached_file_data(path) do |content| begin data = YAML.load(content, path) if data.is_a?(Hash) Puppet::Pops::Lookup::HieraConfig.symkeys_to_string(data) else msg = _("%{path}: file does not contain a valid yaml hash") % { path: path } raise Puppet::DataBinding::LookupError, msg if Puppet[:strict] == :error && data != false Puppet.warning(msg) {} end rescue YAML::SyntaxError => ex # Psych errors includes the absolute path to the file, so no need to add that # to the message raise Puppet::DataBinding::LookupError, "Unable to parse #{ex.message}" end end end def decrypt_value(value, context, options) case value when String decrypt(value, context, options) when Hash result = {} value.each_pair { |k, v| result[context.interpolate(k)] = decrypt_value(v, context, options) } result when Array value.map { |v| decrypt_value(v, context, options) } else value end end def decrypt(data, context, options) if encrypted?(data) # Options must be set prior to each call to #parse since they end up as static variables in # the Options class. They cannot be set once before #decrypt_value is called, since each #decrypt # might cause a new lookup through interpolation. That lookup in turn, might use a different eyaml # config. # Hiera::Backend::Eyaml::Options.set(options) tokens = Hiera::Backend::Eyaml::Parser::ParserFactory.hiera_backend_parser.parse(data) data = tokens.map(&:to_plain_text).join.chomp end context.interpolate(data) end def encrypted?(data) /.*ENC\[.*?\]/ =~ data ? true : false end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/functions/filter.rb��������������������������������������������������������0000644�0052762�0001160�00000010710�13417161721�020716� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Applies a [lambda](https://puppet.com/docs/puppet/latest/lang_lambdas.html) # to every value in a data structure and returns an array or hash containing any elements # for which the lambda evaluates to `true`. # # This function takes two mandatory arguments, in this order: # # 1. An array, hash, or other iterable object that the function will iterate over. # 2. A lambda, which the function calls for each element in the first argument. It can # request one or two parameters. # # @example Using the `filter` function # # `$filtered_data = $data.filter |$parameter| { <PUPPET CODE BLOCK> }` # # or # # `$filtered_data = filter($data) |$parameter| { <PUPPET CODE BLOCK> }` # # When the first argument (`$data` in the above example) is an array, Puppet passes each # value in turn to the lambda and returns an array containing the results. # # @example Using the `filter` function with an array and a one-parameter lambda # # ```puppet # # For the array $data, return an array containing the values that end with "berry" # $data = ["orange", "blueberry", "raspberry"] # $filtered_data = $data.filter |$items| { $items =~ /berry$/ } # # $filtered_data = [blueberry, raspberry] # ``` # # When the first argument is a hash, Puppet passes each key and value pair to the lambda # as an array in the form `[key, value]` and returns a hash containing the results. # # @example Using the `filter` function with a hash and a one-parameter lambda # # ```puppet # # For the hash $data, return a hash containing all values of keys that end with "berry" # $data = { "orange" => 0, "blueberry" => 1, "raspberry" => 2 } # $filtered_data = $data.filter |$items| { $items[0] =~ /berry$/ } # # $filtered_data = {blueberry => 1, raspberry => 2} # # When the first argument is an array and the lambda has two parameters, Puppet passes the # array's indexes (enumerated from 0) in the first parameter and its values in the second # parameter. # # @example Using the `filter` function with an array and a two-parameter lambda # # ```puppet # # For the array $data, return an array of all keys that both end with "berry" and have # # an even-numbered index # $data = ["orange", "blueberry", "raspberry"] # $filtered_data = $data.filter |$indexes, $values| { $indexes % 2 == 0 and $values =~ /berry$/ } # # $filtered_data = [raspberry] # ``` # # When the first argument is a hash, Puppet passes its keys to the first parameter and its # values to the second parameter. # # @example Using the `filter` function with a hash and a two-parameter lambda # # ```puppet # # For the hash $data, return a hash of all keys that both end with "berry" and have # # values less than or equal to 1 # $data = { "orange" => 0, "blueberry" => 1, "raspberry" => 2 } # $filtered_data = $data.filter |$keys, $values| { $keys =~ /berry$/ and $values <= 1 } # # $filtered_data = {blueberry => 1} # ``` # # @since 4.0.0 # Puppet::Functions.create_function(:filter) do dispatch :filter_Hash_2 do param 'Hash[Any, Any]', :hash block_param 'Callable[2,2]', :block end dispatch :filter_Hash_1 do param 'Hash[Any, Any]', :hash block_param 'Callable[1,1]', :block end dispatch :filter_Enumerable_2 do param 'Iterable', :enumerable block_param 'Callable[2,2]', :block end dispatch :filter_Enumerable_1 do param 'Iterable', :enumerable block_param 'Callable[1,1]', :block end def filter_Hash_1(hash) result = hash.select {|x, y| yield([x, y]) == true } # Ruby 1.8.7 returns Array result = Hash[result] unless result.is_a? Hash result end def filter_Hash_2(hash) result = hash.select {|x, y| yield(x, y) == true } # Ruby 1.8.7 returns Array result = Hash[result] unless result.is_a? Hash result end def filter_Enumerable_1(enumerable) result = [] enum = Puppet::Pops::Types::Iterable.asserted_iterable(self, enumerable) begin loop do it = enum.next if yield(it) == true result << it end end rescue StopIteration end result end def filter_Enumerable_2(enumerable) enum = Puppet::Pops::Types::Iterable.asserted_iterable(self, enumerable) if enum.hash_style? result = {} enum.each { |k, v| result[k] = v if yield(k, v) == true } result else result = [] begin index = 0 loop do it = enum.next if yield(index, it) == true result << it end index += 1 end rescue StopIteration end result end end end ��������������������������������������������������������puppet-5.5.10/lib/puppet/functions/new.rb�����������������������������������������������������������0000644�0052762�0001160�00000107760�13417161721�020236� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Creates a new instance/object of a given data type. # # This function makes it possible to create new instances of # concrete data types. If a block is given it is called with the # just created instance as an argument. # # Calling this function is equivalent to directly # calling the data type: # # @example `new` and calling type directly are equivalent # # ```puppet # $a = Integer.new("42") # $b = Integer("42") # ``` # # These would both convert the string `"42"` to the decimal value `42`. # # @example arguments by position or by name # # ```puppet # $a = Integer.new("42", 8) # $b = Integer({from => "42", radix => 8}) # ``` # # This would convert the octal (radix 8) number `"42"` in string form # to the decimal value `34`. # # The new function supports two ways of giving the arguments: # # * by name (using a hash with property to value mapping) # * by position (as regular arguments) # # Note that it is not possible to create new instances of # some abstract data types (for example `Variant`). The data type `Optional[T]` is an # exception as it will create an instance of `T` or `undef` if the # value to convert is `undef`. # # The arguments that can be given is determined by the data type. # # > An assertion is always made that the produced value complies with the given type constraints. # # @example data type constraints are checked # # ```puppet # Integer[0].new("-100") # ``` # # Would fail with an assertion error (since value is less than 0). # # The following sections show the arguments and conversion rules # per data type built into the Puppet Type System. # # ### Conversion to Optional[T] and NotUndef[T] # # Conversion to these data types is the same as a conversion to the type argument `T`. # In the case of `Optional[T]` it is accepted that the argument to convert may be `undef`. # It is however not acceptable to give other arguments (than `undef`) that cannot be # converted to `T`. # # ### Conversion to Integer # # A new `Integer` can be created from `Integer`, `Float`, `Boolean`, and `String` values. # For conversion from `String` it is possible to specify the radix (base). # # ```puppet # type Radix = Variant[Default, Integer[2,2], Integer[8,8], Integer[10,10], Integer[16,16]] # # function Integer.new( # String $value, # Radix $radix = 10, # Boolean $abs = false # ) # # function Integer.new( # Variant[Numeric, Boolean] $value, # Boolean $abs = false # ) # ``` # # * When converting from `String` the default radix is 10. # * If radix is not specified an attempt is made to detect the radix from the start of the string: # * `0b` or `0B` is taken as radix 2. # * `0x` or `0X` is taken as radix 16. # * `0` as radix 8. # * All others are decimal. # * Conversion from `String` accepts an optional sign in the string. # * For hexadecimal (radix 16) conversion an optional leading "0x", or "0X" is accepted. # * For octal (radix 8) an optional leading "0" is accepted. # * For binary (radix 2) an optional leading "0b" or "0B" is accepted. # * When `radix` is set to `default`, the conversion is based on the leading. # characters in the string. A leading "0" for radix 8, a leading "0x", or "0X" for # radix 16, and leading "0b" or "0B" for binary. # * Conversion from `Boolean` results in 0 for `false` and 1 for `true`. # * Conversion from `Integer`, `Float`, and `Boolean` ignores the radix. # * `Float` value fractions are truncated (no rounding). # * When `abs` is set to `true`, the result will be an absolute integer. # # @example Converting to Integer in multiple ways # # ```puppet # $a_number = Integer("0xFF", 16) # results in 255 # $a_number = Integer("010") # results in 8 # $a_number = Integer("010", 10) # results in 10 # $a_number = Integer(true) # results in 1 # $a_number = Integer(-38, 10, true) # results in 38 # ``` # # ### Conversion to Float # # A new `Float` can be created from `Integer`, `Float`, `Boolean`, and `String` values. # For conversion from `String` both float and integer formats are supported. # # ```puppet # function Float.new( # Variant[Numeric, Boolean, String] $value, # Boolean $abs = true # ) # ``` # # * For an integer, the floating point fraction of `.0` is added to the value. # * A `Boolean` `true` is converted to 1.0, and a `false` to 0.0 # * In `String` format, integer prefixes for hex and binary are understood (but not octal since # floating point in string format may start with a '0'). # * When `abs` is set to `true`, the result will be an absolute floating point value. # # ### Conversion to Numeric # # A new `Integer` or `Float` can be created from `Integer`, `Float`, `Boolean` and # `String` values. # # ```puppet # function Numeric.new( # Variant[Numeric, Boolean, String] $value, # Boolean $abs = true # ) # ``` # # * If the value has a decimal period, or if given in scientific notation # (e/E), the result is a `Float`, otherwise the value is an `Integer`. The # conversion from `String` always uses a radix based on the prefix of the string. # * Conversion from `Boolean` results in 0 for `false` and 1 for `true`. # * When `abs` is set to `true`, the result will be an absolute `Float`or `Integer` value. # # @example Converting to Numeric in different ways # # ```puppet # $a_number = Numeric(true) # results in 1 # $a_number = Numeric("0xFF") # results in 255 # $a_number = Numeric("010") # results in 8 # $a_number = Numeric("3.14") # results in 3.14 (a float) # $a_number = Numeric(-42.3, true) # results in 42.3 # $a_number = Numeric(-42, true) # results in 42 # ``` # # ### Conversion to Timespan # # A new `Timespan` can be created from `Integer`, `Float`, `String`, and `Hash` values. Several variants of the constructor are provided. # # **Timespan from seconds** # # When a Float is used, the decimal part represents fractions of a second. # # ```puppet # function Timespan.new( # Variant[Float, Integer] $value # ) # ``` # # **Timespan from days, hours, minutes, seconds, and fractions of a second** # # The arguments can be passed separately in which case the first four, days, hours, minutes, and seconds are mandatory and the rest are optional. # All values may overflow and/or be negative. The internal 128-bit nano-second integer is calculated as: # # ``` # (((((days * 24 + hours) * 60 + minutes) * 60 + seconds) * 1000 + milliseconds) * 1000 + microseconds) * 1000 + nanoseconds # ``` # # ```puppet # function Timespan.new( # Integer $days, Integer $hours, Integer $minutes, Integer $seconds, # Integer $milliseconds = 0, Integer $microseconds = 0, Integer $nanoseconds = 0 # ) # ``` # # or, all arguments can be passed as a `Hash`, in which case all entries are optional: # # ```puppet # function Timespan.new( # Struct[{ # Optional[negative] => Boolean, # Optional[days] => Integer, # Optional[hours] => Integer, # Optional[minutes] => Integer, # Optional[seconds] => Integer, # Optional[milliseconds] => Integer, # Optional[microseconds] => Integer, # Optional[nanoseconds] => Integer # }] $hash # ) # ``` # # **Timespan from String and format directive patterns** # # The first argument is parsed using the format optionally passed as a string or array of strings. When an array is used, an attempt # will be made to parse the string using the first entry and then with each entry in succession until parsing succeeds. If the second # argument is omitted, an array of default formats will be used. # # An exception is raised when no format was able to parse the given string. # # ```puppet # function Timespan.new( # String $string, Variant[String[2],Array[String[2], 1]] $format = <default format>) # ) # ``` # # the arguments may also be passed as a `Hash`: # # ```puppet # function Timespan.new( # Struct[{ # string => String[1], # Optional[format] => Variant[String[2],Array[String[2], 1]] # }] $hash # ) # ``` # # The directive consists of a percent (%) character, zero or more flags, optional minimum field width and # a conversion specifier as follows: # ``` # %[Flags][Width]Conversion # ``` # # ##### Flags: # # | Flag | Meaning # | ---- | --------------- # | - | Don't pad numerical output # | _ | Use spaces for padding # | 0 | Use zeros for padding # # ##### Format directives: # # | Format | Meaning | # | ------ | ------- | # | D | Number of Days | # | H | Hour of the day, 24-hour clock | # | M | Minute of the hour (00..59) | # | S | Second of the minute (00..59) | # | L | Millisecond of the second (000..999) | # | N | Fractional seconds digits | # # The format directive that represents the highest magnitude in the format will be allowed to # overflow. I.e. if no "%D" is used but a "%H" is present, then the hours may be more than 23. # # The default array contains the following patterns: # # ``` # ['%D-%H:%M:%S', '%D-%H:%M', '%H:%M:%S', '%H:%M'] # ``` # # Examples - Converting to Timespan # # ```puppet # $duration = Timespan(13.5) # 13 seconds and 500 milliseconds # $duration = Timespan({days=>4}) # 4 days # $duration = Timespan(4, 0, 0, 2) # 4 days and 2 seconds # $duration = Timespan('13:20') # 13 hours and 20 minutes (using default pattern) # $duration = Timespan('10:03.5', '%M:%S.%L') # 10 minutes, 3 seconds, and 5 milli-seconds # $duration = Timespan('10:03.5', '%M:%S.%N') # 10 minutes, 3 seconds, and 5 nano-seconds # ``` # # ### Conversion to Timestamp # # A new `Timestamp` can be created from `Integer`, `Float`, `String`, and `Hash` values. Several variants of the constructor are provided. # # **Timestamp from seconds since epoch (1970-01-01 00:00:00 UTC)** # # When a Float is used, the decimal part represents fractions of a second. # # ```puppet # function Timestamp.new( # Variant[Float, Integer] $value # ) # ``` # # **Timestamp from String and patterns consisting of format directives** # # The first argument is parsed using the format optionally passed as a string or array of strings. When an array is used, an attempt # will be made to parse the string using the first entry and then with each entry in succession until parsing succeeds. If the second # argument is omitted, an array of default formats will be used. # # A third optional timezone argument can be provided. The first argument will then be parsed as if it represents a local time in that # timezone. The timezone can be any timezone that is recognized when using the '%z' or '%Z' formats, or the word 'current', in which # case the current timezone of the evaluating process will be used. The timezone argument is case insensitive. # # The default timezone, when no argument is provided, or when using the keyword `default`, is 'UTC'. # # It is illegal to provide a timezone argument other than `default` in combination with a format that contains '%z' or '%Z' since that # would introduce an ambiguity as to which timezone to use. The one extracted from the string, or the one provided as an argument. # # An exception is raised when no format was able to parse the given string. # # ```puppet # function Timestamp.new( # String $string, # Variant[String[2],Array[String[2], 1]] $format = <default format>, # String $timezone = default) # ) # ``` # # the arguments may also be passed as a `Hash`: # # ```puppet # function Timestamp.new( # Struct[{ # string => String[1], # Optional[format] => Variant[String[2],Array[String[2], 1]], # Optional[timezone] => String[1] # }] $hash # ) # ``` # # The directive consists of a percent (%) character, zero or more flags, optional minimum field width and # a conversion specifier as follows: # ``` # %[Flags][Width]Conversion # ``` # # ##### Flags: # # | Flag | Meaning # | ---- | --------------- # | - | Don't pad numerical output # | _ | Use spaces for padding # | 0 | Use zeros for padding # | # | Change names to upper-case or change case of am/pm # | ^ | Use uppercase # | : | Use colons for %z # # ##### Format directives (names and padding can be altered using flags): # # **Date (Year, Month, Day):** # # | Format | Meaning | # | ------ | ------- | # | Y | Year with century, zero-padded to at least 4 digits | # | C | year / 100 (rounded down such as 20 in 2009) | # | y | year % 100 (00..99) | # | m | Month of the year, zero-padded (01..12) | # | B | The full month name ("January") | # | b | The abbreviated month name ("Jan") | # | h | Equivalent to %b | # | d | Day of the month, zero-padded (01..31) | # | e | Day of the month, blank-padded ( 1..31) | # | j | Day of the year (001..366) | # # **Time (Hour, Minute, Second, Subsecond):** # # | Format | Meaning | # | ------ | ------- | # | H | Hour of the day, 24-hour clock, zero-padded (00..23) | # | k | Hour of the day, 24-hour clock, blank-padded ( 0..23) | # | I | Hour of the day, 12-hour clock, zero-padded (01..12) | # | l | Hour of the day, 12-hour clock, blank-padded ( 1..12) | # | P | Meridian indicator, lowercase ("am" or "pm") | # | p | Meridian indicator, uppercase ("AM" or "PM") | # | M | Minute of the hour (00..59) | # | S | Second of the minute (00..60) | # | L | Millisecond of the second (000..999). Digits under millisecond are truncated to not produce 1000 | # | N | Fractional seconds digits, default is 9 digits (nanosecond). Digits under a specified width are truncated to avoid carry up | # # **Time (Hour, Minute, Second, Subsecond):** # # | Format | Meaning | # | ------ | ------- | # | z | Time zone as hour and minute offset from UTC (e.g. +0900) | # | :z | hour and minute offset from UTC with a colon (e.g. +09:00) | # | ::z | hour, minute and second offset from UTC (e.g. +09:00:00) | # | Z | Abbreviated time zone name or similar information. (OS dependent) | # # **Weekday:** # # | Format | Meaning | # | ------ | ------- | # | A | The full weekday name ("Sunday") | # | a | The abbreviated name ("Sun") | # | u | Day of the week (Monday is 1, 1..7) | # | w | Day of the week (Sunday is 0, 0..6) | # # **ISO 8601 week-based year and week number:** # # The first week of YYYY starts with a Monday and includes YYYY-01-04. # The days in the year before the first week are in the last week of # the previous year. # # | Format | Meaning | # | ------ | ------- | # | G | The week-based year | # | g | The last 2 digits of the week-based year (00..99) | # | V | Week number of the week-based year (01..53) | # # **Week number:** # # The first week of YYYY that starts with a Sunday or Monday (according to %U # or %W). The days in the year before the first week are in week 0. # # | Format | Meaning | # | ------ | ------- | # | U | Week number of the year. The week starts with Sunday. (00..53) | # | W | Week number of the year. The week starts with Monday. (00..53) | # # **Seconds since the Epoch:** # # | Format | Meaning | # | s | Number of seconds since 1970-01-01 00:00:00 UTC. | # # **Literal string:** # # | Format | Meaning | # | ------ | ------- | # | n | Newline character (\n) | # | t | Tab character (\t) | # | % | Literal "%" character | # # **Combination:** # # | Format | Meaning | # | ------ | ------- | # | c | date and time (%a %b %e %T %Y) | # | D | Date (%m/%d/%y) | # | F | The ISO 8601 date format (%Y-%m-%d) | # | v | VMS date (%e-%^b-%4Y) | # | x | Same as %D | # | X | Same as %T | # | r | 12-hour time (%I:%M:%S %p) | # | R | 24-hour time (%H:%M) | # | T | 24-hour time (%H:%M:%S) | # # The default array contains the following patterns: # # When a timezone argument (other than `default`) is explicitly provided: # # ``` # ['%FT%T.L', '%FT%T', '%F'] # ``` # # otherwise: # # ``` # ['%FT%T.%L %Z', '%FT%T %Z', '%F %Z', '%FT%T.L', '%FT%T', '%F'] # ``` # # Examples - Converting to Timestamp # # ```puppet # $ts = Timestamp(1473150899) # 2016-09-06 08:34:59 UTC # $ts = Timestamp({string=>'2015', format=>'%Y'}) # 2015-01-01 00:00:00.000 UTC # $ts = Timestamp('Wed Aug 24 12:13:14 2016', '%c') # 2016-08-24 12:13:14 UTC # $ts = Timestamp('Wed Aug 24 12:13:14 2016 PDT', '%c %Z') # 2016-08-24 19:13:14.000 UTC # $ts = Timestamp('2016-08-24 12:13:14', '%F %T', 'PST') # 2016-08-24 20:13:14.000 UTC # $ts = Timestamp('2016-08-24T12:13:14', default, 'PST') # 2016-08-24 20:13:14.000 UTC # # ``` # # ### Conversion to Type # # A new `Type` can be created from its `String` representation. # # @example Creating a type from a string # # ```puppet # $t = Type.new('Integer[10]') # ``` # # ### Conversion to String # # Conversion to `String` is the most comprehensive conversion as there are many # use cases where a string representation is wanted. The defaults for the many options # have been chosen with care to be the most basic "value in textual form" representation. # The more advanced forms of formatting are intended to enable writing special purposes formatting # functions in the Puppet language. # # A new string can be created from all other data types. The process is performed in # several steps - first the data type of the given value is inferred, then the resulting data type # is used to find the most significant format specified for that data type. And finally, # the found format is used to convert the given value. # # The mapping from data type to format is referred to as the *format map*. This map # allows different formatting depending on type. # # @example Positive Integers in Hexadecimal prefixed with '0x', negative in Decimal # # ```puppet # $format_map = { # Integer[default, 0] => "%d", # Integer[1, default] => "%#x" # } # String("-1", $format_map) # produces '-1' # String("10", $format_map) # produces '0xa' # ``` # # A format is specified on the form: # # ``` # %[Flags][Width][.Precision]Format # ``` # # `Width` is the number of characters into which the value should be fitted. This allocated space is # padded if value is shorter. By default it is space padded, and the flag `0` will cause padding with `0` # for numerical formats. # # `Precision` is the number of fractional digits to show for floating point, and the maximum characters # included in a string format. # # Note that all data type supports the formats `s` and `p` with the meaning "default string representation" and # "default programmatic string representation" (which for example means that a String is quoted in 'p' format). # # **Signatures of String conversion** # # ```puppet # type Format = Pattern[/^%([\s\+\-#0\[\{<\(\|]*)([1-9][0-9]*)?(?:\.([0-9]+))?([a-zA-Z])/] # type ContainerFormat = Struct[{ # format => Optional[String], # separator => Optional[String], # separator2 => Optional[String], # string_formats => Hash[Type, Format] # }] # type TypeMap = Hash[Type, Variant[Format, ContainerFormat]] # type Formats = Variant[Default, String[1], TypeMap] # # function String.new( # Any $value, # Formats $string_formats # ) # ``` # # Where: # # * `separator` is the string used to separate entries in an array, or hash (extra space should not be included at # the end), defaults to `","` # * `separator2` is the separator between key and value in a hash entry (space padding should be included as # wanted), defaults to `" => "`. # * `string_formats` is a data type to format map for values contained in arrays and hashes - defaults to `{Any => "%p"}`. Note that # these nested formats are not applicable to data types that are containers; they are always formatted as per the top level # format specification. # # @example Simple Conversion to String (using defaults) # # ```puppet # $str = String(10) # produces '10' # $str = String([10]) # produces '["10"]' # ``` # # @example Simple Conversion to String specifying the format for the given value directly # # ```puppet # $str = String(10, "%#x") # produces '0x10' # $str = String([10], "%(a") # produces '("10")' # ``` # # @example Specifying type for values contained in an array # # ```puppet # $formats = { # Array => { # format => '%(a', # string_formats => { Integer => '%#x' } # } # } # $str = String([1,2,3], $formats) # produces '(0x1, 0x2, 0x3)' # ``` # # The given formats are merged with the default formats, and matching of values to convert against format is based on # the specificity of the mapped type; for example, different formats can be used for short and long arrays. # # **Integer to String** # # | Format | Integer Formats # | ------ | --------------- # | d | Decimal, negative values produces leading '-'. # | x X | Hexadecimal in lower or upper case. Uses ..f/..F for negative values unless + is also used. A `#` adds prefix 0x/0X. # | o | Octal. Uses ..0 for negative values unless `+` is also used. A `#` adds prefix 0. # | b B | Binary with prefix 'b' or 'B'. Uses ..1/..1 for negative values unless `+` is also used. # | c | Numeric value representing a Unicode value, result is a one unicode character string, quoted if alternative flag # is used # | s | Same as d, or d in quotes if alternative flag # is used. # | p | Same as d. # | eEfgGaA | Converts integer to float and formats using the floating point rules. # # Defaults to `d`. # # **Float to String** # # | Format | Float formats # | ------ | ------------- # | f | Floating point in non exponential notation. # | e E | Exponential notation with 'e' or 'E'. # | g G | Conditional exponential with 'e' or 'E' if exponent < -4 or >= the precision. # | a A | Hexadecimal exponential form, using 'x'/'X' as prefix and 'p'/'P' before exponent. # | s | Converted to string using format p, then applying string formatting rule, alternate form # quotes result. # | p | Same as f format with minimum significant number of fractional digits, prec has no effect. # | dxXobBc | Converts float to integer and formats using the integer rules. # # Defaults to `p`. # # **String to String** # # | Format | String # | ------ | ------ # | s | Unquoted string, verbatim output of control chars. # | p | Programmatic representation - strings are quoted, interior quotes and control chars are escaped. # | C | Each `::` name segment capitalized, quoted if alternative flag `#` is used. # | c | Capitalized string, quoted if alternative flag `#` is used. # | d | Downcased string, quoted if alternative flag `#` is used. # | u | Upcased string, quoted if alternative flag `#` is used. # | t | Trims leading and trailing whitespace from the string, quoted if alternative flag `#` is used. # # Defaults to `s` at top level and `p` inside array or hash. # # **Boolean to String** # # | Format | Boolean Formats # | ---- | ------------------- # | t T | String 'true'/'false' or 'True'/'False', first char if alternate form is used (i.e. 't'/'f' or 'T'/'F'). # | y Y | String 'yes'/'no', 'Yes'/'No', 'y'/'n' or 'Y'/'N' if alternative flag `#` is used. # | dxXobB | Numeric value 0/1 in accordance with the given format which must be valid integer format. # | eEfgGaA | Numeric value 0.0/1.0 in accordance with the given float format and flags. # | s | String 'true' / 'false'. # | p | String 'true' / 'false'. # # **Regexp to String** # # | Format | Regexp Formats # | ---- | -------------- # | s | No delimiters, quoted if alternative flag `#` is used. # | p | Delimiters `/ /`. # # **Undef to String** # # | Format | Undef formats # | ------ | ------------- # | s | Empty string, or quoted empty string if alternative flag `#` is used. # | p | String 'undef', or quoted '"undef"' if alternative flag `#` is used. # | n | String 'nil', or 'null' if alternative flag `#` is used. # | dxXobB | String 'NaN'. # | eEfgGaA | String 'NaN'. # | v | String 'n/a'. # | V | String 'N/A'. # | u | String 'undef', or 'undefined' if alternative `#` flag is used. # # **Default value to String** # # | Format | Default formats # | ------ | --------------- # | d D | String 'default' or 'Default', alternative form `#` causes value to be quoted. # | s | Same as d. # | p | Same as d. # # **Binary value to String** # # | Format | Default formats # | ------ | --------------- # | s | binary as unquoted UTF-8 characters (errors if byte sequence is invalid UTF-8). Alternate form escapes non ascii bytes. # | p | 'Binary("<base64strict>")' # | b | '<base64>' - base64 string with newlines inserted # | B | '<base64strict>' - base64 strict string (without newlines inserted) # | u | '<base64urlsafe>' - base64 urlsafe string # | t | 'Binary' - outputs the name of the type only # | T | 'BINARY' - output the name of the type in all caps only # # * The alternate form flag `#` will quote the binary or base64 text output. # * The format `%#s` allows invalid UTF-8 characters and outputs all non ascii bytes # as hex escaped characters on the form `\\xHH` where `H` is a hex digit. # * The width and precision values are applied to the text part only in `%p` format. # # **Array & Tuple to String** # # | Format | Array/Tuple Formats # | ------ | ------------- # | a | Formats with `[ ]` delimiters and `,`, alternate form `#` indents nested arrays/hashes. # | s | Same as a. # | p | Same as a. # # See "Flags" `<[({\|` for formatting of delimiters, and "Additional parameters for containers; Array and Hash" for # more information about options. # # The alternate form flag `#` will cause indentation of nested array or hash containers. If width is also set # it is taken as the maximum allowed length of a sequence of elements (not including delimiters). If this max length # is exceeded, each element will be indented. # # **Hash & Struct to String** # # | Format | Hash/Struct Formats # | ------ | ------------- # | h | Formats with `{ }` delimiters, `,` element separator and ` => ` inner element separator unless overridden by flags. # | s | Same as h. # | p | Same as h. # | a | Converts the hash to an array of [k,v] tuples and formats it using array rule(s). # # See "Flags" `<[({\|` for formatting of delimiters, and "Additional parameters for containers; Array and Hash" for # more information about options. # # The alternate form flag `#` will format each hash key/value entry indented on a separate line. # # **Type to String** # # | Format | Array/Tuple Formats # | ------ | ------------- # | s | The same as `p`, quoted if alternative flag `#` is used. # | p | Outputs the type in string form as specified by the Puppet Language. # # **Flags** # # | Flag | Effect # | ------ | ------ # | (space) | A space instead of `+` for numeric output (`-` is shown), for containers skips delimiters. # | # | Alternate format; prefix 0x/0x, 0 (octal) and 0b/0B for binary, Floats force decimal '.'. For g/G keep trailing 0. # | + | Show sign +/- depending on value's sign, changes x, X, o, b, B format to not use 2's complement form. # | - | Left justify the value in the given width. # | 0 | Pad with 0 instead of space for widths larger than value. # | <[({\| | Defines an enclosing pair <> [] () {} or \| \| when used with a container type. # # ### Conversion to Boolean # # Accepts a single value as argument: # # * Float 0.0 is `false`, all other float values are `true` # * Integer 0 is `false`, all other integer values are `true` # * Strings # * `true` if 'true', 'yes', 'y' (case independent compare) # * `false` if 'false', 'no', 'n' (case independent compare) # * Boolean is already boolean and is simply returned # # ### Conversion to Array and Tuple # # When given a single value as argument: # # * A non empty `Hash` is converted to an array matching `Array[Tuple[Any,Any], 1]`. # * An empty `Hash` becomes an empty array. # * An `Array` is simply returned. # * An `Iterable[T]` is turned into an array of `T` instances. # * A `Binary` is converted to an `Array[Integer[0,255]]` of byte values # # When given a second Boolean argument: # # * if `true`, a value that is not already an array is returned as a one element array. # * if `false`, (the default), converts the first argument as shown above. # # @example Ensuring value is an array # # ```puppet # $arr = Array($value, true) # ``` # # Conversion to a `Tuple` works exactly as conversion to an `Array`, only that the constructed array is # asserted against the given tuple type. # # ### Conversion to Hash and Struct # # Accepts a single value as argument: # # * An empty `Array` becomes an empty `Hash` # * An `Array` matching `Array[Tuple[Any,Any], 1]` is converted to a hash where each tuple describes a key/value entry # * An `Array` with an even number of entries is interpreted as `[key1, val1, key2, val2, ...]` # * An `Iterable` is turned into an `Array` and then converted to hash as per the array rules # * A `Hash` is simply returned # # Alternatively, a tree can be constructed by giving two values; an array of tuples on the form `[path, value]` # (where the `path` is the path from the root of a tree, and `value` the value at that position in the tree), and # either the option `'tree'` (do not convert arrays to hashes except the top level), or # `'hash_tree'` (convert all arrays to hashes). # # The tree/hash_tree forms of Hash creation are suited for transforming the result of an iteration # using `tree_each` and subsequent filtering or mapping. # # @example Mapping a hash tree # # Mapping an arbitrary structure in a way that keeps the structure, but where some values are replaced # can be done by using the `tree_each` function, mapping, and then constructing a new Hash from the result: # # ```puppet # # A hash tree with 'water' at different locations # $h = { a => { b => { x => 'water'}}, b => { y => 'water'} } # # a helper function that turns water into wine # function make_wine($x) { if $x == 'water' { 'wine' } else { $x } } # # create a flattened tree with water turned into wine # $flat_tree = $h.tree_each.map |$entry| { [$entry[0], make_wine($entry[1])] } # # create a new Hash and log it # notice Hash($flat_tree, 'hash_tree') # ``` # # Would notice the hash `{a => {b => {x => wine}}, b => {y => wine}}` # # Conversion to a `Struct` works exactly as conversion to a `Hash`, only that the constructed hash is # asserted against the given struct type. # # ### Conversion to a Regexp # # A `String` can be converted into a `Regexp` # # **Example**: Converting a String into a Regexp # ```puppet # $s = '[a-z]+\.com' # $r = Regexp($s) # if('foo.com' =~ $r) { # ... # } # ``` # # ### Creating a SemVer # # A SemVer object represents a single [Semantic Version](http://semver.org/). # It can be created from a String, individual values for its parts, or a hash specifying the value per part. # See the specification at [semver.org](http://semver.org/) for the meaning of the SemVer's parts. # # The signatures are: # # ```puppet # type PositiveInteger = Integer[0,default] # type SemVerQualifier = Pattern[/\A(?<part>[0-9A-Za-z-]+)(?:\.\g<part>)*\Z/] # type SemVerString = String[1] # type SemVerHash =Struct[{ # major => PositiveInteger, # minor => PositiveInteger, # patch => PositiveInteger, # Optional[prerelease] => SemVerQualifier, # Optional[build] => SemVerQualifier # }] # # function SemVer.new(SemVerString $str) # # function SemVer.new( # PositiveInteger $major # PositiveInteger $minor # PositiveInteger $patch # Optional[SemVerQualifier] $prerelease = undef # Optional[SemVerQualifier] $build = undef # ) # # function SemVer.new(SemVerHash $hash_args) # ``` # # @example SemVer and SemVerRange usage # # ```puppet # # As a type, SemVer can describe disjunct ranges which versions can be # # matched against - here the type is constructed with two # # SemVerRange objects. # # # $t = SemVer[ # SemVerRange('>=1.0.0 <2.0.0'), # SemVerRange('>=3.0.0 <4.0.0') # ] # notice(SemVer('1.2.3') =~ $t) # true # notice(SemVer('2.3.4') =~ $t) # false # notice(SemVer('3.4.5') =~ $t) # true # ``` # # ### Creating a SemVerRange # # A `SemVerRange` object represents a range of `SemVer`. It can be created from # a `String`, or from two `SemVer` instances, where either end can be given as # a literal `default` to indicate infinity. The string format of a `SemVerRange` is specified by # the [Semantic Version Range Grammar](https://github.com/npm/node-semver#ranges). # # > Use of the comparator sets described in the grammar (joining with `||`) is not supported. # # The signatures are: # # ```puppet # type SemVerRangeString = String[1] # type SemVerRangeHash = Struct[{ # min => Variant[Default, SemVer], # Optional[max] => Variant[Default, SemVer], # Optional[exclude_max] => Boolean # }] # # function SemVerRange.new( # SemVerRangeString $semver_range_string # ) # # function SemVerRange.new( # Variant[Default,SemVer] $min # Variant[Default,SemVer] $max # Optional[Boolean] $exclude_max = undef # ) # # function SemVerRange.new( # SemVerRangeHash $semver_range_hash # ) # ``` # # For examples of `SemVerRange` use see "Creating a SemVer" # # ### Creating a Binary # # A `Binary` object represents a sequence of bytes and it can be created from a String in Base64 format, # an Array containing byte values. A Binary can also be created from a Hash containing the value to convert to # a `Binary`. # # The signatures are: # # ```puppet # type ByteInteger = Integer[0,255] # type Base64Format = Enum["%b", "%u", "%B", "%s"] # type StringHash = Struct[{value => String, "format" => Optional[Base64Format]}] # type ArrayHash = Struct[{value => Array[ByteInteger]}] # type BinaryArgsHash = Variant[StringHash, ArrayHash] # # function Binary.new( # String $base64_str, # Optional[Base64Format] $format # ) # # # function Binary.new( # Array[ByteInteger] $byte_array # } # # # Same as for String, or for Array, but where arguments are given in a Hash. # function Binary.new(BinaryArgsHash $hash_args) # ``` # # The formats have the following meaning: # # | format | explanation | # | ---- | ---- | # | B | The data is in base64 strict encoding # | u | The data is in URL safe base64 encoding # | b | The data is in base64 encoding, padding as required by base64 strict, is added by default # | s | The data is a puppet string. The string must be valid UTF-8, or convertible to UTF-8 or an error is raised. # | r | (Ruby Raw) the byte sequence in the given string is used verbatim irrespective of possible encoding errors # # * The default format is `%B`. # * Note that the format `%r` should be used sparingly, or not at all. It exists for backwards compatibility reasons when someone receiving # a string from some function and that string should be treated as Binary. Such code should be changed to return a Binary instead of a String. # # @example Creating a Binary # # ```puppet # # create the binary content "abc" # $a = Binary('YWJj') # # # create the binary content from content in a module's file # $b = binary_file('mymodule/mypicture.jpg') # ``` # # * Since 4.5.0 # * Binary type since 4.8.0 # # ### Creating an instance of a `Type` using the `Init` type # # The type `Init[T]` describes a value that can be used when instantiating a type. When used as the first argument in a call to `new`, it # will dispatch the call to its contained type and optionally augment the parameter list with additional arguments. # # @example Creating an instance of Integer using Init[Integer] # # ```puppet # # The following declaration # $x = Init[Integer].new('128') # # is exactly the same as # $x = Integer.new('128') # ``` # # or, with base 16 and using implicit new # # ```puppet # # The following declaration # $x = Init[Integer,16]('80') # # is exactly the same as # $x = Integer('80', 16) # ``` # # @example Creating an instance of String using a predefined format # # ```puppet # $fmt = Init[String,'%#x'] # notice($fmt(256)) # will notice '0x100' # ``` # # @since 4.5.0 # Puppet::Functions.create_function(:new, Puppet::Functions::InternalFunction) do dispatch :new_instance do scope_param param 'Type', :type repeated_param 'Any', :args optional_block_param end def new_instance(scope, t, *args) return args[0] if args.size == 1 && !t.is_a?(Puppet::Pops::Types::PInitType) && t.instance?(args[0]) result = assert_type(t, new_function_for_type(t, scope).call(scope, *args)) return block_given? ? yield(result) : result end def new_function_for_type(t, scope) @new_function_cache ||= Hash.new() {|hsh, key| hsh[key] = key.new_function.new(scope, loader) } @new_function_cache[t] end def assert_type(type, value) Puppet::Pops::Types::TypeAsserter.assert_instance_of(['Converted value from %s.new()', type], type, value) end end ����������������puppet-5.5.10/lib/puppet/functions/yaml_data.rb�����������������������������������������������������0000644�0052762�0001160�00000002761�13417161721�021373� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The `yaml_data` is a hiera 5 `data_hash` data provider function. # See [the configuration guide documentation](https://puppet.com/docs/puppet/latest/hiera_config_yaml_5.html#configuring-a-hierarchy-level-built-in-backends) for # how to use this function. # # @since 4.8.0 # require 'yaml' Puppet::Functions.create_function(:yaml_data) do dispatch :yaml_data do param 'Struct[{path=>String[1]}]', :options param 'Puppet::LookupContext', :context end argument_mismatch :missing_path do param 'Hash', :options param 'Puppet::LookupContext', :context end def yaml_data(options, context) path = options['path'] context.cached_file_data(path) do |content| begin data = YAML.load(content, path) if data.is_a?(Hash) Puppet::Pops::Lookup::HieraConfig.symkeys_to_string(data) else msg = _("%{path}: file does not contain a valid yaml hash" % { path: path }) raise Puppet::DataBinding::LookupError, msg if Puppet[:strict] == :error && data != false Puppet.warning(msg) {} end rescue YAML::SyntaxError => ex # Psych errors includes the absolute path to the file, so no need to add that # to the message raise Puppet::DataBinding::LookupError, "Unable to parse #{ex.message}" end end end def missing_path(options, context) "one of 'path', 'paths' 'glob', 'globs' or 'mapped_paths' must be declared in hiera.yaml when using this data_hash function" end end ���������������puppet-5.5.10/lib/puppet/generate/������������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�016674� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/generate/models/�����������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020157� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/generate/models/type/������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�021140� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/generate/models/type/property.rb�������������������������������������������0000644�0052762�0001160�00000005447�13417161721�023356� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet module Generate module Models module Type # A model for resource type properties and parameters. class Property # Gets the name of the property as a Puppet string literal. attr_reader :name # Gets the Puppet type of the property. attr_reader :type # Gets the doc string of the property. attr_reader :doc # Initializes a property model. # @param property [Puppet::Property] The Puppet property to model. # @return [void] def initialize(property) @name = Puppet::Pops::Types::StringConverter.convert(property.name.to_s, '%p') @type = self.class.get_puppet_type(property) @doc = property.doc.strip @is_namevar = property.isnamevar? end # Determines if this property is a namevar. # @return [Boolean] Returns true if the property is a namevar or false if not. def is_namevar? @is_namevar end # Gets the Puppet type for a property. # @param property [Puppet::Property] The Puppet property to get the Puppet type for. # @return [String] Returns the string representing the Puppet type. def self.get_puppet_type(property) # HACK: the value collection does not expose the underlying value information at all # thus this horribleness to get the underlying values hash regexes = [] strings = [] values = property.value_collection.instance_variable_get('@values') || {} values.each do |_, value| if value.regex? regexes << Puppet::Pops::Types::StringConverter.convert(value.name, '%p') next end strings << Puppet::Pops::Types::StringConverter.convert(value.name.to_s, '%p') value.aliases.each do |a| strings << Puppet::Pops::Types::StringConverter.convert(a.to_s, '%p') end end # If no string or regexes, default to Any type return 'Any' if strings.empty? && regexes.empty? # Calculate a variant of supported values # Note that boolean strings are mapped to Variant[Boolean, Enum['true', 'false']] # because of tech debt... enum = strings.empty? ? nil : "Enum[#{strings.join(', ')}]" pattern = regexes.empty? ? nil : "Pattern[#{regexes.join(', ')}]" boolean = strings.include?('\'true\'') || strings.include?('\'false\'') ? 'Boolean' : nil variant = [boolean, enum, pattern].reject { |t| t.nil? } return variant[0] if variant.size == 1 "Variant[#{variant.join(', ')}]" end end end end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/generate/models/type/type.rb�����������������������������������������������0000644�0052762�0001160�00000003701�13417161721�022442� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/generate/models/type/property' module Puppet module Generate module Models module Type # A model for Puppet resource types. class Type # Gets the name of the type as a Puppet string literal. attr_reader :name # Gets the doc string of the type. attr_reader :doc # Gets the properties of the type. attr_reader :properties # Gets the parameters of the type. attr_reader :parameters # Gets the title patterns of the type attr_reader :title_patterns # Gets the isomorphic member attribute of the type attr_reader :isomorphic # Gets the capability member attribute of the type attr_reader :capability # Initializes a type model. # @param type [Puppet::Type] The Puppet type to model. # @return [void] def initialize(type) @name = Puppet::Pops::Types::StringConverter.convert(type.name.to_s, '%p') @doc = type.doc.strip @properties = type.properties.map { |p| Property.new(p) } @parameters = type.parameters.map do |name| Property.new(type.paramclass(name)) end sc = Puppet::Pops::Types::StringConverter.singleton @title_patterns = Hash[type.title_patterns.map do |mapping| [ sc.convert(mapping[0], '%p'), sc.convert(mapping[1].map do |names| next if names.empty? raise Puppet::Error, _('title patterns that use procs are not supported.') unless names.size == 1 names[0].to_s end, '%p') ] end] @isomorphic = type.isomorphic? @capability = type.is_capability? end def render(template) template.result(binding) end end end end end end ���������������������������������������������������������������puppet-5.5.10/lib/puppet/generate/templates/��������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020672� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/generate/templates/type/���������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�021653� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/generate/templates/type/pcore.erb������������������������������������������0000644�0052762�0001160�00000002343�13417161721�023452� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file was automatically generated on <%= Time.now %>. # Use the 'puppet generate types' command to regenerate this file. <%- unless doc.empty? -%> <%- doc.each_line do |line| -%> # <%= line -%> <%- end %> <%- end -%> Puppet::Resource::ResourceType3.new( <%= name %>, [ <%- properties.each_with_index do |property, index| -%> <%- unless property.doc.empty? -%> <%- property.doc.each_line do |line| -%> # <%= line -%> <%- end %> <%- end -%> Puppet::Resource::Param(<%= property.type %>, <%= property.name %><% if property.is_namevar? %>, true<% end %>)<% if index + 1 < properties.size %>, <%- end -%> <%- end %> ], [ <%- parameters.each_with_index do |parameter, index| -%> <%- unless parameter.doc.empty? -%> <%- parameter.doc.each_line do |line| -%> # <%= line -%> <%- end %> <%- end -%> Puppet::Resource::Param(<%= parameter.type %>, <%= parameter.name %><% if parameter.is_namevar? %>, true<% end %>)<% if index + 1 < parameters.size %>, <%- end -%> <%- end %> ], { <%- title_patterns.each_with_index do |mapping, index| -%> <%= mapping[0] %> => <%= mapping[1] %><%= "," if index + 1 < title_patterns.size %> <%- end -%> }, <%= isomorphic -%>, <%= capability -%> ) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/generate/type.rb�����������������������������������������������������������0000644�0052762�0001160�00000023010�13417161721�020171� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'erb' require 'fileutils' require 'puppet/util/autoload' require 'puppet/generate/models/type/type' module Puppet module Generate # Responsible for generating type definitions in Puppet class Type # Represents an input to the type generator class Input # Gets the path to the input. attr_reader :path # Gets the format to use for generating the output file. attr_reader :format # Initializes an input. # @param base [String] The base path where the input is located. # @param path [String] The path to the input file. # @param format [Symbol] The format to use for generation. # @return [void] def initialize(base, path, format) @base = base @path = path self.format = format end # Gets the expected resource type name for the input. # @return [Symbol] Returns the expected resource type name for the input. def type_name File.basename(@path, '.rb').to_sym end # Sets the format to use for this input. # @param format [Symbol] The format to use for generation. # @return [Symbol] Returns the new format. def format=(format) format = format.to_sym raise _("unsupported format '%{format}'.") % { format: format } unless self.class.supported_format?(format) @format = format end # Determines if the output file is up-to-date with respect to the input file. # @param [String, nil] The path to output to, or nil if determined by input # @return [Boolean] Returns true if the output is up-to-date or false if not. def up_to_date?(outputdir) f = effective_output_path(outputdir) Puppet::FileSystem::exist?(f) && (Puppet::FileSystem::stat(@path) <=> Puppet::FileSystem::stat(f)) <= 0 end # Gets the filename of the output file. # @return [String] Returns the name to the output file. def output_name @output_name ||= case @format when :pcore "#{File.basename(@path, '.rb')}.pp" else raise _("unsupported format '%{format}'.") % { format: @format } end end # Gets the path to the output file. # @return [String] Returns the path to the output file. def output_path @output_path ||= case @format when :pcore File.join(@base, 'pcore', 'types', output_name) else raise _("unsupported format '%{format}'.") % { format: @format } end end # Sets the path to the output file. # @param path [String] The new path to the output file. # @return [String] Returns the new path to the output file. def output_path=(path) @output_path = path end # Returns the outputpath to use given an outputdir that may be nil # If outputdir is not nil, the returned path is relative to that outpudir # otherwise determined by this input. # @param [String, nil] The outputdirectory to use, or nil if to be determined by this Input def effective_output_path(outputdir) outputdir ? File.join(outputdir, output_name) : output_path end # Gets the path to the template to use for this input. # @return [String] Returns the path to the template. def template_path File.join(File.dirname(__FILE__), 'templates', 'type', "#{@format}.erb") end # Gets the string representation of the input. # @return [String] Returns the string representation of the input. def to_s @path end # Determines if the given format is supported # @param format [Symbol] The format to use for generation. # @return [Boolean] Returns true if the format is supported or false if not. def self.supported_format?(format) [:pcore].include?(format) end end # Finds the inputs for the generator. # @param format [Symbol] The format to use. # @param environment [Puppet::Node::Environment] The environment to search for inputs. Defaults to the current environment. # @return [Array<Input>] Returns the array of inputs. def self.find_inputs(format = :pcore, environment = Puppet.lookup(:current_environment)) Puppet.debug "Searching environment '#{environment.name}' for custom types." inputs = [] environment.modules.each do |mod| directory = File.join(Puppet::Util::Autoload.cleanpath(mod.plugin_directory), 'puppet', 'type') unless Puppet::FileSystem.exist?(directory) Puppet.debug "Skipping '#{mod.name}' module because it contains no custom types." next end Puppet.debug "Searching '#{mod.name}' module for custom types." Dir.glob("#{directory}/*.rb") do |file| next unless Puppet::FileSystem.file?(file) Puppet.debug "Found custom type source file '#{file}'." inputs << Input.new(mod.path, file, format) end end # Sort the inputs by path inputs.sort_by! { |input| input.path } end # Generates files for the given inputs. # If a file is up to date (newer than input) it is kept. # If a file is out of date it is regenerated. # If there is a file for a non existing output in a given output directory it is removed. # If using input specific output removal must be made by hand if input is removed. # # @param inputs [Array<Input>] The inputs to generate files for. # @param outputdir [String, nil] the outputdir where all output should be generated, or nil if next to input # @param force [Boolean] True to force the generation of the output files (skip up-to-date checks) or false if not. # @return [void] def self.generate(inputs, outputdir = nil, force = false) # remove files for non existing inputs unless outputdir.nil? filenames_to_keep = inputs.map {|i| i.output_name } existing_files = Puppet::FileSystem.children(outputdir).map {|f| Puppet::FileSystem.basename(f) } files_to_remove = existing_files - filenames_to_keep files_to_remove.each do |f| Puppet::FileSystem.unlink(File.join(outputdir, f)) end Puppet.notice(_("Removed output '%{files_to_remove}' for non existing inputs") % { files_to_remove: files_to_remove }) unless files_to_remove.empty? end if inputs.empty? Puppet.notice _('No custom types were found.') return nil end templates = {} templates.default_proc = lambda { |hash, key| raise _("template was not found at '%{key}'.") % { key: key } unless Puppet::FileSystem.file?(key) template = ERB.new(File.read(key), nil, '-') template.filename = key template } up_to_date = true Puppet.notice _('Generating Puppet resource types.') inputs.each do |input| if !force && input.up_to_date?(outputdir) Puppet.debug "Skipping '#{input}' because it is up-to-date." next end up_to_date = false type_name = input.type_name Puppet.debug "Loading custom type '#{type_name}' in '#{input}'." begin require input.path rescue SystemExit raise rescue Exception => e # Log the exception and move on to the next input Puppet.log_exception(e, _("Failed to load custom type '%{type_name}' from '%{input}': %{message}") % { type_name: type_name, input: input, message: e.message }) next end # HACK: there's no way to get a type without loading it (sigh); for now, just get the types hash directly types ||= Puppet::Type.instance_variable_get('@types') # Assume the type follows the naming convention unless type = types[type_name] Puppet.err _("Custom type '%{type_name}' was not defined in '%{input}'.") % { type_name: type_name, input: input } next end # Create the model begin model = Models::Type::Type.new(type) rescue Exception => e # Move on to the next input Puppet.log_exception(e, "#{input}: #{e.message}") next end # Render the template begin result = model.render(templates[input.template_path]) rescue Exception => e Puppet.log_exception(e) raise end # Write the output file begin effective_output_path = input.effective_output_path(outputdir) Puppet.notice _("Generating '%{effective_output_path}' using '%{format}' format.") % { effective_output_path: effective_output_path, format: input.format } FileUtils.mkdir_p(File.dirname(effective_output_path)) Puppet::FileSystem.open(effective_output_path, nil, 'w:UTF-8') do |file| file.write(result) end rescue Exception => e Puppet.log_exception(e, _("Failed to generate '%{effective_output_path}': %{message}") % { effective_output_path: effective_output_path, message: e.message }) # Move on to the next input next end end Puppet.notice _('No files were generated because all inputs were up-to-date.') if up_to_date end end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/gettext/�������������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�016566� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/gettext/module_translations.rb���������������������������������������������0000644�0052762�0001160�00000003461�13417161721�023200� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/gettext/config' module Puppet::ModuleTranslations # @api private # Loads translation files for each of the specified modules, # if present. Requires the modules to have `forge_name` specified. # @param [[Module]] modules a list of modules for which to # load translations def self.load_from_modulepath(modules) modules.each do |mod| next unless mod.forge_name && mod.has_translations?(Puppet::GettextConfig.current_locale) module_name = mod.forge_name.gsub('/', '-') if Puppet::GettextConfig.load_translations(module_name, mod.locale_directory, :po) Puppet.debug "Loaded translations for #{module_name}." elsif Puppet::GettextConfig.gettext_loaded? Puppet.debug "Could not find translation files for #{module_name} at #{mod.locale_directory}. Skipping translation initialization." else Puppet.warn_once("gettext_unavailable", "gettext_unavailable", "No gettext library found, skipping translation initialization.") end end end # @api private # Loads translation files that have been pluginsync'd for modules # from the $vardir. # @param [String] vardir the path to Puppet's vardir def self.load_from_vardir(vardir) locale = Puppet::GettextConfig.current_locale Dir.glob("#{vardir}/locales/#{locale}/*.po") do |f| module_name = File.basename(f, ".po") if Puppet::GettextConfig.load_translations(module_name, File.join(vardir, "locales"), :po) Puppet.debug "Loaded translations for #{module_name}." elsif Puppet::GettextConfig.gettext_loaded? Puppet.debug "Could not load translations for #{module_name}." else Puppet.warn_once("gettext_unavailable", "gettext_unavailable", "No gettext library found, skipping translation initialization.") end end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/gettext/stubs.rb�����������������������������������������������������������0000644�0052762�0001160�00000000374�13417161721�020252� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# These stub the translation methods normally brought in # by FastGettext. Used when Gettext could not be properly # initialized. def _(msg) msg end def n_(*args, &block) plural = args[2] == 1 ? args[0] : args[1] block ? block.call : plural end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/gettext/config.rb����������������������������������������������������������0000644�0052762�0001160�00000024017�13417161721�020357� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/platform' require 'puppet/file_system' module Puppet::GettextConfig LOCAL_PATH = File.absolute_path('../../../locales', File.dirname(__FILE__)) POSIX_PATH = File.absolute_path('../../../../../share/locale', File.dirname(__FILE__)) WINDOWS_PATH = File.absolute_path('../../../../../../../puppet/share/locale', File.dirname(__FILE__)) # This is the only domain name that won't be a symbol, making it unique from environments. DEFAULT_TEXT_DOMAIN = 'default-text-domain' # Load gettext helpers and track whether they're available. # Used instead of features because we initialize gettext before features is available. begin require 'fast_gettext' require 'locale' # Make translation methods (e.g. `_()` and `n_()`) available everywhere. class ::Object include FastGettext::Translation end @gettext_loaded = true rescue LoadError # Stub out gettext's `_` and `n_()` methods, which attempt to load translations, # with versions that do nothing require 'puppet/gettext/stubs' @gettext_loaded = false end # @api private # Whether we were able to require fast_gettext and locale # @return [Boolean] true if translation gems were successfully loaded def self.gettext_loaded? @gettext_loaded end # @api private # Returns the currently selected locale from FastGettext, # or 'en' of gettext has not been loaded # @return [String] the active locale def self.current_locale if gettext_loaded? return FastGettext.default_locale else return 'en' end end # @api private # Returns a list of the names of the loaded text domains # @return [[String]] the names of the loaded text domains def self.loaded_text_domains return [] if @gettext_disabled || !gettext_loaded? return FastGettext.translation_repositories.keys end # @api private # Clears the translation repository for the given text domain, # creating it if it doesn't exist, then adds default translations # and switches to using this domain. # @param [String, Symbol] domain_name the name of the domain to create def self.reset_text_domain(domain_name) return if @gettext_disabled || !gettext_loaded? domain_name = domain_name.to_sym Puppet.debug "Reset text domain to #{domain_name.inspect}" FastGettext.add_text_domain(domain_name, type: :chain, chain: [], report_warning: false) copy_default_translations(domain_name) FastGettext.text_domain = domain_name end # @api private # Resets the thread's configured text_domain to the default text domain. # In Puppet Server, thread A may process a compile request that configures # a domain, while thread B may invalidate that environment and delete the # domain. That leaves thread A with an invalid text_domain selected. # To avoid that, clear_text_domain after any processing that needs the # non-default text domain. def self.clear_text_domain return if @gettext_disabled || !gettext_loaded? FastGettext.text_domain = nil end # @api private # Creates a default text domain containing the translations for # Puppet as the start of chain. When semantic_puppet gets initialized, # its translations are added to this chain. This is used as a cache # so that all non-module translations only need to be loaded once as # we create and reset environment-specific text domains. # # @return true if Puppet translations were successfully loaded, false # otherwise def self.create_default_text_domain return if @gettext_disabled || !gettext_loaded? FastGettext.add_text_domain(DEFAULT_TEXT_DOMAIN, type: :chain, chain: [], report_warning: false) FastGettext.default_text_domain = DEFAULT_TEXT_DOMAIN load_translations('puppet', puppet_locale_path, translation_mode(puppet_locale_path), DEFAULT_TEXT_DOMAIN) end # @api private # Switches the active text domain, if the requested domain exists. # @param [String, Symbol] domain_name the name of the domain to switch to def self.use_text_domain(domain_name) return if @gettext_disabled || !gettext_loaded? domain_name = domain_name.to_sym if FastGettext.translation_repositories.include?(domain_name) Puppet.debug "Use text domain #{domain_name.inspect}" FastGettext.text_domain = domain_name else Puppet.debug "Requested unknown text domain #{domain_name.inspect}" end end # @api private # Delete all text domains. def self.delete_all_text_domains FastGettext.translation_repositories.clear FastGettext.default_text_domain = nil FastGettext.text_domain = nil end # @api private # Deletes the text domain with the given name # @param [String, Symbol] domain_name the name of the domain to delete def self.delete_text_domain(domain_name) return if @gettext_disabled || !gettext_loaded? domain_name = domain_name.to_sym deleted = FastGettext.translation_repositories.delete(domain_name) if FastGettext.text_domain == domain_name Puppet.debug "Deleted current text domain #{domain_name.inspect}: #{!deleted.nil?}" FastGettext.text_domain = nil else Puppet.debug "Deleted text domain #{domain_name.inspect}: #{!deleted.nil?}" end end # @api private # Deletes all text domains except the default one def self.delete_environment_text_domains return if @gettext_disabled || !gettext_loaded? FastGettext.translation_repositories.keys.each do |key| # do not clear default translations next if key == DEFAULT_TEXT_DOMAIN FastGettext.translation_repositories.delete(key) end FastGettext.text_domain = nil end # @api private # Adds translations from the default text domain to the specified # text domain. Creates the default text domain if one does not exist # (this will load Puppet's translations). # # Since we are currently (Nov 2017) vendoring semantic_puppet, in normal # flows these translations will be copied along with Puppet's. # # @param [Symbol] domain_name the name of the domain to add translations to def self.copy_default_translations(domain_name) return if @gettext_disabled || !gettext_loaded? if FastGettext.default_text_domain.nil? create_default_text_domain end puppet_translations = FastGettext.translation_repositories[FastGettext.default_text_domain].chain FastGettext.translation_repositories[domain_name].chain.push(*puppet_translations) end # @api private # Search for puppet gettext config files # @return [String] path to the config, or nil if not found def self.puppet_locale_path if Puppet::FileSystem.exist?(LOCAL_PATH) return LOCAL_PATH elsif Puppet::Util::Platform.windows? && Puppet::FileSystem.exist?(WINDOWS_PATH) return WINDOWS_PATH elsif !Puppet::Util::Platform.windows? && Puppet::FileSystem.exist?(POSIX_PATH) return POSIX_PATH else nil end end # @api private # Determine which translation file format to use # @param [String] conf_path the path to the gettext config file # @return [Symbol] :mo if in a package structure, :po otherwise def self.translation_mode(conf_path) if WINDOWS_PATH == conf_path || POSIX_PATH == conf_path return :mo else return :po end end # @api private # Prevent future gettext initializations def self.disable_gettext @gettext_disabled = true end # @api private # Attempt to load translations for the given project. # @param [String] project_name the project whose translations we want to load # @param [String] locale_dir the path to the directory containing translations # @param [Symbol] file_format translation file format to use, either :po or :mo # @return true if initialization succeeded, false otherwise def self.load_translations(project_name, locale_dir, file_format, text_domain = FastGettext.text_domain) if project_name.nil? || project_name.empty? raise Puppet::Error, "A project name must be specified in order to initialize translations." end return false if @gettext_disabled || !@gettext_loaded return false unless locale_dir && Puppet::FileSystem.exist?(locale_dir) unless file_format == :po || file_format == :mo raise Puppet::Error, "Unsupported translation file format #{file_format}; please use :po or :mo" end add_repository_to_domain(project_name, locale_dir, file_format, text_domain) return true end # @api private # Add the translations for this project to the domain's repository chain # chain for the currently selected text domain, if needed. # @param [String] project_name the name of the project for which to load translations # @param [String] locale_dir the path to the directory containing translations # @param [Symbol] file_format the format of the translations files, :po or :mo def self.add_repository_to_domain(project_name, locale_dir, file_format, text_domain = FastGettext.text_domain) return if @gettext_disabled || !gettext_loaded? current_chain = FastGettext.translation_repositories[text_domain].chain repository = FastGettext::TranslationRepository.build(project_name, path: locale_dir, type: file_format, report_warning: false) current_chain << repository end # @api private # Sets FastGettext's locale to the current system locale def self.setup_locale return if @gettext_disabled || !gettext_loaded? set_locale(Locale.current.language) end # @api private # Sets the language in which to display strings. # @param [String] locale the language portion of a locale string (e.g. "ja") def self.set_locale(locale) return if @gettext_disabled || !gettext_loaded? # make sure we're not using the `available_locales` machinery FastGettext.default_available_locales = nil FastGettext.default_locale = locale end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/graph/���������������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�016203� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/graph/key.rb���������������������������������������������������������������0000644�0052762�0001160�00000000715�13417161721�017316� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Sequential, nestable keys for tracking order of insertion in "the graph" # @api private class Puppet::Graph::Key include Comparable attr_reader :value protected :value def initialize(value = [0]) @value = value end def next next_values = @value.clone next_values[-1] += 1 Puppet::Graph::Key.new(next_values) end def down Puppet::Graph::Key.new(@value + [0]) end def <=>(other) @value <=> other.value end end ���������������������������������������������������puppet-5.5.10/lib/puppet/graph/prioritizer.rb�������������������������������������������������������0000644�0052762�0001160�00000001027�13417161721�021105� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Base, template method, class for Prioritizers. This provides the basic # tracking facilities used. # # @api private class Puppet::Graph::Prioritizer def initialize @priority = {} end def forget(key) @priority.delete(key) end def record_priority_for(key, priority) @priority[key] = priority end def generate_priority_for(key) raise NotImplementedError end def generate_priority_contained_in(container, key) raise NotImplementedError end def priority_of(key) @priority[key] end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/graph/rb_tree_map.rb�������������������������������������������������������0000644�0052762�0001160�00000023460�13417161721�021007� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Algorithms and Containers project is Copyright (c) 2009 Kanwei Li # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the 'Software'), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # # A RbTreeMap is a map that is stored in sorted order based on the order of its keys. This ordering is # determined by applying the function <=> to compare the keys. No duplicate values for keys are allowed, # so duplicate values are overwritten. # # A major advantage of RBTreeMap over a Hash is the fact that keys are stored in order and can thus be # iterated over in order. This is useful for many datasets. # # The implementation is adapted from Robert Sedgewick's Left Leaning Red-Black Tree implementation, # which can be found at https://www.cs.princeton.edu/~rs/talks/LLRB/Java/RedBlackBST.java # # Most methods have O(log n) complexity. class Puppet::Graph::RbTreeMap include Enumerable attr_reader :size alias_method :length, :size # Create and initialize a new empty TreeMap. def initialize @root = nil @size = 0 end # Insert an item with an associated key into the TreeMap, and returns the item inserted # # Complexity: O(log n) # # map = Containers::TreeMap.new # map.push("MA", "Massachusetts") #=> "Massachusetts" # map.get("MA") #=> "Massachusetts" def push(key, value) @root = insert(@root, key, value) @root.color = :black value end alias_method :[]=, :push # Return true if key is found in the TreeMap, false otherwise # # Complexity: O(log n) # # map = Containers::TreeMap.new # map.push("MA", "Massachusetts") # map.push("GA", "Georgia") # map.has_key?("GA") #=> true # map.has_key?("DE") #=> false def has_key?(key) !get_recursive(@root, key).nil? end # Return the item associated with the key, or nil if none found. # # Complexity: O(log n) # # map = Containers::TreeMap.new # map.push("MA", "Massachusetts") # map.push("GA", "Georgia") # map.get("GA") #=> "Georgia" def get(key) node = get_recursive(@root, key) node ? node.value : nil node.value if node end alias_method :[], :get # Return the smallest key in the map. # # Complexity: O(log n) # # map = Containers::TreeMap.new # map.push("MA", "Massachusetts") # map.push("GA", "Georgia") # map.min_key #=> "GA" def min_key @root.nil? ? nil : min_recursive(@root).key end # Return the largest key in the map. # # Complexity: O(log n) # # map = Containers::TreeMap.new # map.push("MA", "Massachusetts") # map.push("GA", "Georgia") # map.max_key #=> "MA" def max_key @root.nil? ? nil : max_recursive(@root).key end # Deletes the item and key if it's found, and returns the item. Returns nil # if key is not present. # # Complexity: O(log n) # # map = Containers::TreeMap.new # map.push("MA", "Massachusetts") # map.push("GA", "Georgia") # map.delete("MA") #=> "Massachusetts" def delete(key) result = nil if @root return unless has_key? key @root, result = delete_recursive(@root, key) @root.color = :black if @root @size -= 1 end result end # Returns true if the tree is empty, false otherwise def empty? @root.nil? end # Deletes the item with the smallest key and returns the item. Returns nil # if key is not present. # # Complexity: O(log n) # # map = Containers::TreeMap.new # map.push("MA", "Massachusetts") # map.push("GA", "Georgia") # map.delete_min #=> "Massachusetts" # map.size #=> 1 def delete_min result = nil if @root @root, result = delete_min_recursive(@root) @root.color = :black if @root @size -= 1 end result end # Deletes the item with the largest key and returns the item. Returns nil # if key is not present. # # Complexity: O(log n) # # map = Containers::TreeMap.new # map.push("MA", "Massachusetts") # map.push("GA", "Georgia") # map.delete_max #=> "Georgia" # map.size #=> 1 def delete_max result = nil if @root @root, result = delete_max_recursive(@root) @root.color = :black if @root @size -= 1 end result end # Yields [key, value] pairs in order by key. def each(&blk) recursive_yield(@root, &blk) end def first return nil unless @root node = min_recursive(@root) [node.key, node.value] end def last return nil unless @root node = max_recursive(@root) [node.key, node.value] end def to_hash @root ? @root.to_hash : {} end class Node # :nodoc: all attr_accessor :color, :key, :value, :left, :right def initialize(key, value) @key = key @value = value @color = :red @left = nil @right = nil end def to_hash h = { :node => { :key => @key, :value => @value, :color => @color, } } h.merge!(:left => left.to_hash) if @left h.merge!(:right => right.to_hash) if @right h end def red? @color == :red end def colorflip @color = @color == :red ? :black : :red @left.color = @left.color == :red ? :black : :red @right.color = @right.color == :red ? :black : :red end def rotate_left r = @right r_key, r_value = r.key, r.value b = r.left r.left = @left @left = r @right = r.right r.right = b r.color, r.key, r.value = :red, @key, @value @key, @value = r_key, r_value self end def rotate_right l = @left l_key, l_value = l.key, l.value b = l.right l.right = @right @right = l @left = l.left l.left = b l.color, l.key, l.value = :red, @key, @value @key, @value = l_key, l_value self end def move_red_left colorflip if (@right.left && @right.left.red?) @right.rotate_right rotate_left colorflip end self end def move_red_right colorflip if (@left.left && @left.left.red?) rotate_right colorflip end self end def fixup rotate_left if @right && @right.red? rotate_right if (@left && @left.red?) && (@left.left && @left.left.red?) colorflip if (@left && @left.red?) && (@right && @right.red?) self end end private def recursive_yield(node, &blk) return unless node recursive_yield(node.left, &blk) yield node.key, node.value recursive_yield(node.right, &blk) end def delete_recursive(node, key) if (key <=> node.key) == -1 node.move_red_left if ( !isred(node.left) && !isred(node.left.left) ) node.left, result = delete_recursive(node.left, key) else node.rotate_right if isred(node.left) if ( ( (key <=> node.key) == 0) && node.right.nil? ) return nil, node.value end if ( !isred(node.right) && !isred(node.right.left) ) node.move_red_right end if (key <=> node.key) == 0 result = node.value min_child = min_recursive(node.right) node.value = min_child.value node.key = min_child.key node.right = delete_min_recursive(node.right).first else node.right, result = delete_recursive(node.right, key) end end return node.fixup, result end def delete_min_recursive(node) if node.left.nil? return nil, node.value end if ( !isred(node.left) && !isred(node.left.left) ) node.move_red_left end node.left, result = delete_min_recursive(node.left) return node.fixup, result end def delete_max_recursive(node) if (isred(node.left)) node = node.rotate_right end return nil, node.value if node.right.nil? if ( !isred(node.right) && !isred(node.right.left) ) node.move_red_right end node.right, result = delete_max_recursive(node.right) return node.fixup, result end def get_recursive(node, key) return nil if node.nil? case key <=> node.key when 0 then return node when -1 then return get_recursive(node.left, key) when 1 then return get_recursive(node.right, key) end end def min_recursive(node) return node if node.left.nil? min_recursive(node.left) end def max_recursive(node) return node if node.right.nil? max_recursive(node.right) end def insert(node, key, value) unless node @size += 1 return Node.new(key, value) end case key <=> node.key when 0 then node.value = value when -1 then node.left = insert(node.left, key, value) when 1 then node.right = insert(node.right, key, value) end node.rotate_left if (node.right && node.right.red?) node.rotate_right if (node.left && node.left.red? && node.left.left && node.left.left.red?) node.colorflip if (node.left && node.left.red? && node.right && node.right.red?) node end def isred(node) return false if node.nil? node.color == :red end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/graph/relationship_graph.rb������������������������������������������������0000644�0052762�0001160�00000021132�13417161721�022404� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The relationship graph is the final form of a puppet catalog in # which all dependency edges are explicitly in the graph. This form of the # catalog is used to traverse the graph in the order in which resources are # managed. # # @api private class Puppet::Graph::RelationshipGraph < Puppet::Graph::SimpleGraph attr_reader :blockers def initialize(prioritizer) super() @prioritizer = prioritizer @ready = Puppet::Graph::RbTreeMap.new @generated = {} @done = {} @blockers = {} @providerless_types = [] end def populate_from(catalog) add_all_resources_as_vertices(catalog) build_manual_dependencies build_autorelation_dependencies(catalog) write_graph(:relationships) if catalog.host_config? replace_containers_with_anchors(catalog) write_graph(:expanded_relationships) if catalog.host_config? end def add_vertex(vertex, priority = nil) super(vertex) if priority @prioritizer.record_priority_for(vertex, priority) else @prioritizer.generate_priority_for(vertex) end end def add_relationship(f, t, label=nil) super(f, t, label) @ready.delete(@prioritizer.priority_of(t)) end def remove_vertex!(vertex) super @prioritizer.forget(vertex) end def resource_priority(resource) @prioritizer.priority_of(resource) end # Enqueue the initial set of resources, those with no dependencies. def enqueue_roots vertices.each do |v| @blockers[v] = direct_dependencies_of(v).length enqueue(v) if @blockers[v] == 0 end end # Decrement the blocker count for the resource by 1. If the number of # blockers is unknown, count them and THEN decrement by 1. def unblock(resource) @blockers[resource] ||= direct_dependencies_of(resource).select { |r2| !@done[r2] }.length if @blockers[resource] > 0 @blockers[resource] -= 1 else resource.warning _("appears to have a negative number of dependencies") end @blockers[resource] <= 0 end def clear_blockers @blockers.clear end def enqueue(*resources) resources.each do |resource| @ready[@prioritizer.priority_of(resource)] = resource end end def finish(resource) direct_dependents_of(resource).each do |v| enqueue(v) if unblock(v) end @done[resource] = true end def next_resource @ready.delete_min end def traverse(options = {}, &block) continue_while = options[:while] || lambda { true } pre_process = options[:pre_process] || lambda { |resource| } overly_deferred_resource_handler = options[:overly_deferred_resource_handler] || lambda { |resource| } canceled_resource_handler = options[:canceled_resource_handler] || lambda { |resource| } teardown = options[:teardown] || lambda {} graph_cycle_handler = options[:graph_cycle_handler] || lambda { [] } if cycles = report_cycles_in_graph graph_cycle_handler.call(cycles) end enqueue_roots deferred_resources = [] while continue_while.call() && (resource = next_resource) if resource.suitable? made_progress = true pre_process.call(resource) yield resource finish(resource) else deferred_resources << resource end if @ready.empty? and deferred_resources.any? if made_progress enqueue(*deferred_resources) else deferred_resources.each do |res| overly_deferred_resource_handler.call(res) finish(res) end end made_progress = false deferred_resources = [] end end if !continue_while.call() while (resource = next_resource) canceled_resource_handler.call(resource) finish(resource) end end teardown.call() end private def add_all_resources_as_vertices(catalog) catalog.resources.each do |vertex| add_vertex(vertex) end end def build_manual_dependencies vertices.each do |vertex| vertex.builddepends.each do |edge| add_edge(edge) end end end def build_autorelation_dependencies(catalog) vertices.each do |vertex| [:require,:subscribe].each do |rel_type| vertex.send("auto#{rel_type}".to_sym, catalog).each do |edge| # don't let automatic relationships conflict with manual ones. next if edge?(edge.source, edge.target) if edge?(edge.target, edge.source) vertex.debug "Skipping automatic relationship with #{edge.source}" else vertex.debug "Adding auto#{rel_type} relationship with #{edge.source}" if rel_type == :require edge.event = :NONE else edge.callback = :refresh edge.event = :ALL_EVENTS end add_edge(edge) end end end [:before,:notify].each do |rel_type| vertex.send("auto#{rel_type}".to_sym, catalog).each do |edge| # don't let automatic relationships conflict with manual ones. next if edge?(edge.target, edge.source) if edge?(edge.source, edge.target) vertex.debug "Skipping automatic relationship with #{edge.target}" else vertex.debug "Adding auto#{rel_type} relationship with #{edge.target}" if rel_type == :before edge.event = :NONE else edge.callback = :refresh edge.event = :ALL_EVENTS end add_edge(edge) end end end end end # Impose our container information on another graph by using it # to replace any container vertices X with a pair of vertices # { admissible_X and completed_X } such that # # 0) completed_X depends on admissible_X # 1) contents of X each depend on admissible_X # 2) completed_X depends on each on the contents of X # 3) everything which depended on X depends on completed_X # 4) admissible_X depends on everything X depended on # 5) the containers and their edges must be removed # # Note that this requires attention to the possible case of containers # which contain or depend on other containers, but has the advantage # that the number of new edges created scales linearly with the number # of contained vertices regardless of how containers are related; # alternatives such as replacing container-edges with content-edges # scale as the product of the number of external dependencies, which is # to say geometrically in the case of nested / chained containers. # Default_label = { :callback => :refresh, :event => :ALL_EVENTS } def replace_containers_with_anchors(catalog) stage_class = Puppet::Type.type(:stage) whit_class = Puppet::Type.type(:whit) component_class = Puppet::Type.type(:component) containers = catalog.resources.find_all { |v| (v.is_a?(component_class) or v.is_a?(stage_class)) and vertex?(v) } # # These two hashes comprise the aforementioned attention to the possible # case of containers that contain / depend on other containers; they map # containers to their sentinels but pass other vertices through. Thus we # can "do the right thing" for references to other vertices that may or # may not be containers. # admissible = Hash.new { |h,k| k } completed = Hash.new { |h,k| k } containers.each { |x| admissible[x] = whit_class.new(:name => "admissible_#{x.ref}", :catalog => catalog) completed[x] = whit_class.new(:name => "completed_#{x.ref}", :catalog => catalog) # This copies the original container's tags over to the two anchor whits. # Without this, tags are not propagated to the container's resources. admissible[x].set_tags(x) completed[x].set_tags(x) priority = @prioritizer.priority_of(x) add_vertex(admissible[x], priority) add_vertex(completed[x], priority) } # # Implement the six requirements listed above # containers.each { |x| contents = catalog.adjacent(x, :direction => :out) add_edge(admissible[x],completed[x]) if contents.empty? # (0) contents.each { |v| add_edge(admissible[x],admissible[v],Default_label) # (1) add_edge(completed[v], completed[x], Default_label) # (2) } # (3) & (5) adjacent(x,:direction => :in,:type => :edges).each { |e| add_edge(completed[e.source],admissible[x],e.label) remove_edge! e } # (4) & (5) adjacent(x,:direction => :out,:type => :edges).each { |e| add_edge(completed[x],admissible[e.target],e.label) remove_edge! e } } containers.each { |x| remove_vertex! x } # (5) end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/graph/sequential_prioritizer.rb��������������������������������������������0000644�0052762�0001160�00000001642�13417161721�023342� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This implements a priority in which keys are given values that will keep them # in the same priority in which they priorities are requested. Nested # structures (those in which a key is contained within another key) are # preserved in such a way that child keys are after the parent and before the # key after the parent. # # @api private class Puppet::Graph::SequentialPrioritizer < Puppet::Graph::Prioritizer def initialize super @container = {} @count = Puppet::Graph::Key.new end def generate_priority_for(key) if priority_of(key).nil? @count = @count.next record_priority_for(key, @count) else priority_of(key) end end def generate_priority_contained_in(container, key) @container[container] ||= priority_of(container).down priority = @container[container].next record_priority_for(key, priority) @container[container] = priority priority end end ����������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/graph/random_prioritizer.rb������������������������������������������������0000644�0052762�0001160�00000000567�13417161721�022455� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Assign a random priority to items. # # @api private class Puppet::Graph::RandomPrioritizer < Puppet::Graph::Prioritizer def generate_priority_for(key) if priority_of(key).nil? record_priority_for(key, SecureRandom.uuid) else priority_of(key) end end def generate_priority_contained_in(container, key) generate_priority_for(key) end end �����������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/graph/simple_graph.rb������������������������������������������������������0000644�0052762�0001160�00000042051�13417161721�021177� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/external/dot' require 'puppet/relationship' require 'set' # A hopefully-faster graph class to replace the use of GRATR. class Puppet::Graph::SimpleGraph include Puppet::Util::PsychSupport # # All public methods of this class must maintain (assume ^ ensure) the following invariants, where "=~=" means # equiv. up to order: # # @in_to.keys =~= @out_to.keys =~= all vertices # @in_to.values.collect { |x| x.values }.flatten =~= @out_from.values.collect { |x| x.values }.flatten =~= all edges # @in_to[v1][v2] =~= @out_from[v2][v1] =~= all edges from v1 to v2 # @in_to [v].keys =~= vertices with edges leading to v # @out_from[v].keys =~= vertices with edges leading from v # no operation may shed reference loops (for gc) # recursive operation must scale with the depth of the spanning trees, or better (e.g. no recursion over the set # of all vertices, etc.) # # This class is intended to be used with DAGs. However, if the # graph has a cycle, it will not cause non-termination of any of the # algorithms. # def initialize @in_to = {} @out_from = {} @upstream_from = {} @downstream_from = {} end # Clear our graph. def clear @in_to.clear @out_from.clear @upstream_from.clear @downstream_from.clear end # Which resources depend upon the given resource. def dependencies(resource) vertex?(resource) ? upstream_from_vertex(resource).keys : [] end def dependents(resource) vertex?(resource) ? downstream_from_vertex(resource).keys : [] end # Whether our graph is directed. Always true. Used to produce dot files. def directed? true end # Determine all of the leaf nodes below a given vertex. def leaves(vertex, direction = :out) tree_from_vertex(vertex, direction).keys.find_all { |c| adjacent(c, :direction => direction).empty? } end # Collect all of the edges that the passed events match. Returns # an array of edges. def matching_edges(event, base = nil) source = base || event.resource unless vertex?(source) Puppet.warning _("Got an event from invalid vertex %{source}") % { source: source.ref } return [] end # Get all of the edges that this vertex should forward events # to, which is the same thing as saying all edges directly below # This vertex in the graph. @out_from[source].values.flatten.find_all { |edge| edge.match?(event.name) } end # Return a reversed version of this graph. def reversal result = self.class.new vertices.each { |vertex| result.add_vertex(vertex) } edges.each do |edge| result.add_edge edge.class.new(edge.target, edge.source, edge.label) end result end # Return the size of the graph. def size vertices.size end def to_a vertices end # This is a simple implementation of Tarjan's algorithm to find strongly # connected components in the graph; this is a fairly ugly implementation, # because I can't just decorate the vertices themselves. # # This method has an unhealthy relationship with the find_cycles_in_graph # method below, which contains the knowledge of how the state object is # maintained. def tarjan(root, s) # initialize the recursion stack we use to work around the nasty lack of a # decent Ruby stack. recur = [{ :node => root }] while not recur.empty? do frame = recur.last vertex = frame[:node] case frame[:step] when nil then s[:index][vertex] = s[:number] s[:lowlink][vertex] = s[:number] s[:number] = s[:number] + 1 s[:stack].push(vertex) s[:seen][vertex] = true frame[:children] = adjacent(vertex) frame[:step] = :children when :children then if frame[:children].length > 0 then child = frame[:children].shift if ! s[:index][child] then # Never seen, need to recurse. frame[:step] = :after_recursion frame[:child] = child recur.push({ :node => child }) elsif s[:seen][child] then s[:lowlink][vertex] = [s[:lowlink][vertex], s[:index][child]].min end else if s[:lowlink][vertex] == s[:index][vertex] then this_scc = [] begin top = s[:stack].pop s[:seen][top] = false this_scc << top end until top == vertex s[:scc] << this_scc end recur.pop # done with this node, finally. end when :after_recursion then s[:lowlink][vertex] = [s[:lowlink][vertex], s[:lowlink][frame[:child]]].min frame[:step] = :children else fail "#{frame[:step]} is an unknown step" end end end # Find all cycles in the graph by detecting all the strongly connected # components, then eliminating everything with a size of one as # uninteresting - which it is, because it can't be a cycle. :) # # This has an unhealthy relationship with the 'tarjan' method above, which # it uses to implement the detection of strongly connected components. def find_cycles_in_graph state = { :number => 0, :index => {}, :lowlink => {}, :scc => [], :stack => [], :seen => {} } # we usually have a disconnected graph, must walk all possible roots vertices.each do |vertex| if ! state[:index][vertex] then tarjan vertex, state end end # To provide consistent results to the user, given that a hash is never # assured to return the same order, and given our graph processing is # based on hash tables, we need to sort the cycles internally, as well as # the set of cycles. # # Given we are in a failure state here, any extra cost is more or less # irrelevant compared to the cost of a fix - which is on a human # time-scale. state[:scc].select do |component| multi_vertex_component?(component) || single_vertex_referring_to_self?(component) end.map do |component| component.sort end.sort end # Perform a BFS on the sub graph representing the cycle, with a view to # generating a sufficient set of paths to report the cycle meaningfully, and # ideally usefully, for the end user. # # BFS is preferred because it will generally report the shortest paths # through the graph first, which are more likely to be interesting to the # user. I think; it would be interesting to verify that. --daniel 2011-01-23 def paths_in_cycle(cycle, max_paths = 1) #TRANSLATORS "negative or zero" refers to the count of paths raise ArgumentError, _("negative or zero max_paths") if max_paths < 1 # Calculate our filtered outbound vertex lists... adj = {} cycle.each do |vertex| adj[vertex] = adjacent(vertex).select{|s| cycle.member? s} end found = [] # frame struct is vertex, [path] stack = [[cycle.first, []]] while frame = stack.shift do if frame[1].member?(frame[0]) then found << frame[1] + [frame[0]] break if found.length >= max_paths else adj[frame[0]].each do |to| stack.push [to, frame[1] + [frame[0]]] end end end return found.sort end # @return [Array] array of dependency cycles (arrays) def report_cycles_in_graph cycles = find_cycles_in_graph number_of_cycles = cycles.length return if number_of_cycles == 0 message = n_("Found %{num} dependency cycle:\n", "Found %{num} dependency cycles:\n", number_of_cycles) % { num: number_of_cycles } cycles.each do |cycle| paths = paths_in_cycle(cycle) message += paths.map{ |path| '(' + path.join(' => ') + ')'}.join('\n') + '\n' end if Puppet[:graph] then filename = write_cycles_to_graph(cycles) message += _("Cycle graph written to %{filename}.") % { filename: filename } else #TRANSLATORS '--graph' refers to a command line option and OmniGraffle and GraphViz are program names and should not be translated message += _("Try the '--graph' option and opening the resulting '.dot' file in OmniGraffle or GraphViz") end Puppet.err(message) cycles end def write_cycles_to_graph(cycles) # This does not use the DOT graph library, just writes the content # directly. Given the complexity of this, there didn't seem much point # using a heavy library to generate exactly the same content. --daniel 2011-01-27 graph = ["digraph Resource_Cycles {"] graph << ' label = "Resource Cycles"' cycles.each do |cycle| paths_in_cycle(cycle, 10).each do |path| graph << path.map { |v| '"' + v.to_s.gsub(/"/, '\\"') + '"' }.join(" -> ") end end graph << '}' filename = File.join(Puppet[:graphdir], "cycles.dot") # DOT files are assumed to be UTF-8 by default - http://www.graphviz.org/doc/info/lang.html File.open(filename, "w:UTF-8") { |f| f.puts graph } return filename end # Add a new vertex to the graph. def add_vertex(vertex) @in_to[vertex] ||= {} @out_from[vertex] ||= {} end # Remove a vertex from the graph. def remove_vertex!(v) return unless vertex?(v) @upstream_from.clear @downstream_from.clear (@in_to[v].values+@out_from[v].values).flatten.each { |e| remove_edge!(e) } @in_to.delete(v) @out_from.delete(v) end # Test whether a given vertex is in the graph. def vertex?(v) @in_to.include?(v) end # Return a list of all vertices. def vertices @in_to.keys end # Add a new edge. The graph user has to create the edge instance, # since they have to specify what kind of edge it is. def add_edge(e,*a) return add_relationship(e,*a) unless a.empty? e = Puppet::Relationship.from_data_hash(e) if e.is_a?(Hash) @upstream_from.clear @downstream_from.clear add_vertex(e.source) add_vertex(e.target) # Avoid multiple lookups here. This code is performance critical arr = (@in_to[e.target][e.source] ||= []) arr << e unless arr.include?(e) arr = (@out_from[e.source][e.target] ||= []) arr << e unless arr.include?(e) end def add_relationship(source, target, label = nil) add_edge Puppet::Relationship.new(source, target, label) end # Find all matching edges. def edges_between(source, target) (@out_from[source] || {})[target] || [] end # Is there an edge between the two vertices? def edge?(source, target) vertex?(source) and vertex?(target) and @out_from[source][target] end def edges @in_to.values.collect { |x| x.values }.flatten end def each_edge @in_to.each { |t,ns| ns.each { |s,es| es.each { |e| yield e }}} end # Remove an edge from our graph. def remove_edge!(e) if edge?(e.source,e.target) @upstream_from.clear @downstream_from.clear @in_to [e.target].delete e.source if (@in_to [e.target][e.source] -= [e]).empty? @out_from[e.source].delete e.target if (@out_from[e.source][e.target] -= [e]).empty? end end # Find adjacent edges. def adjacent(v, options = {}) return [] unless ns = (options[:direction] == :in) ? @in_to[v] : @out_from[v] (options[:type] == :edges) ? ns.values.flatten : ns.keys end # Just walk the tree and pass each edge. def walk(source, direction) # Use an iterative, breadth-first traversal of the graph. One could do # this recursively, but Ruby's slow function calls and even slower # recursion make the shorter, recursive algorithm cost-prohibitive. stack = [source] seen = Set.new until stack.empty? node = stack.shift next if seen.member? node connected = adjacent(node, :direction => direction) connected.each do |target| yield node, target end stack.concat(connected) seen << node end end # A different way of walking a tree, and a much faster way than the # one that comes with GRATR. def tree_from_vertex(start, direction = :out) predecessor={} walk(start, direction) do |parent, child| predecessor[child] = parent end predecessor end def downstream_from_vertex(v) return @downstream_from[v] if @downstream_from[v] result = @downstream_from[v] = {} @out_from[v].keys.each do |node| result[node] = 1 result.update(downstream_from_vertex(node)) end result end def direct_dependents_of(v) (@out_from[v] || {}).keys end def upstream_from_vertex(v) return @upstream_from[v] if @upstream_from[v] result = @upstream_from[v] = {} @in_to[v].keys.each do |node| result[node] = 1 result.update(upstream_from_vertex(node)) end result end def direct_dependencies_of(v) (@in_to[v] || {}).keys end # Return an array of the edge-sets between a series of n+1 vertices (f=v0,v1,v2...t=vn) # connecting the two given vertices. The ith edge set is an array containing all the # edges between v(i) and v(i+1); these are (by definition) never empty. # # * if f == t, the list is empty # * if they are adjacent the result is an array consisting of # a single array (the edges from f to t) # * and so on by induction on a vertex m between them # * if there is no path from f to t, the result is nil # # This implementation is not particularly efficient; it's used in testing where clarity # is more important than last-mile efficiency. # def path_between(f,t) if f==t [] elsif direct_dependents_of(f).include?(t) [edges_between(f,t)] elsif dependents(f).include?(t) m = (dependents(f) & direct_dependencies_of(t)).first path_between(f,m) + path_between(m,t) else nil end end # LAK:FIXME This is just a paste of the GRATR code with slight modifications. # Return a DOT::DOTDigraph for directed graphs or a DOT::DOTSubgraph for an # undirected Graph. _params_ can contain any graph property specified in # rdot.rb. If an edge or vertex label is a kind of Hash then the keys # which match +dot+ properties will be used as well. def to_dot_graph (params = {}) params['name'] ||= self.class.name.gsub(/:/,'_') fontsize = params['fontsize'] ? params['fontsize'] : '8' graph = (directed? ? DOT::DOTDigraph : DOT::DOTSubgraph).new(params) edge_klass = directed? ? DOT::DOTDirectedEdge : DOT::DOTEdge vertices.each do |v| name = v.ref params = {'name' => '"'+name+'"', 'fontsize' => fontsize, 'label' => name} v_label = v.ref params.merge!(v_label) if v_label and v_label.kind_of? Hash graph << DOT::DOTNode.new(params) end edges.each do |e| params = {'from' => '"'+ e.source.ref + '"', 'to' => '"'+ e.target.ref + '"', 'fontsize' => fontsize } e_label = e.ref params.merge!(e_label) if e_label and e_label.kind_of? Hash graph << edge_klass.new(params) end graph end # Output the dot format as a string def to_dot (params={}) to_dot_graph(params).to_s; end # Produce the graph files if requested. def write_graph(name) return unless Puppet[:graph] file = File.join(Puppet[:graphdir], "#{name}.dot") # DOT files are assumed to be UTF-8 by default - http://www.graphviz.org/doc/info/lang.html File.open(file, "w:UTF-8") { |f| f.puts to_dot("name" => name.to_s.capitalize) } end # This flag may be set to true to use the new YAML serialization # format (where @vertices is a simple list of vertices rather than a # list of VertexWrapper objects). Deserialization supports both # formats regardless of the setting of this flag. class << self attr_accessor :use_new_yaml_format end self.use_new_yaml_format = false def initialize_from_hash(hash) initialize vertices = hash['vertices'] edges = hash['edges'] if vertices.is_a?(Hash) # Support old (2.6) format vertices = vertices.keys end vertices.each { |v| add_vertex(v) } unless vertices.nil? edges.each { |e| add_edge(e) } unless edges.nil? end def to_data_hash hash = { 'edges' => edges.map(&:to_data_hash) } hash['vertices'] = if self.class.use_new_yaml_format vertices else # Represented in YAML using the old (version 2.6) format. result = {} vertices.each do |vertex| adjacencies = {} [:in, :out].each do |direction| direction_hash = {} adjacencies[direction.to_s] = direction_hash adjacent(vertex, :direction => direction, :type => :edges).each do |edge| other_vertex = direction == :in ? edge.source : edge.target (direction_hash[other_vertex.to_s] ||= []) << edge end direction_hash.each_pair { |key, edges| direction_hash[key] = edges.uniq.map(&:to_data_hash) } end vname = vertex.to_s result[vname] = { 'adjacencies' => adjacencies, 'vertex' => vname } end result end hash end def multi_vertex_component?(component) component.length > 1 end private :multi_vertex_component? def single_vertex_referring_to_self?(component) if component.length == 1 vertex = component[0] adjacent(vertex).include?(vertex) else false end end private :single_vertex_referring_to_self? end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/graph/title_hash_prioritizer.rb��������������������������������������������0000644�0052762�0001160�00000001036�13417161721�023311� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Prioritize keys, which must be Puppet::Resources, based on a static hash of # the key's ref. This prioritizer does not take containment into account. # # @api private require 'digest/sha1' class Puppet::Graph::TitleHashPrioritizer < Puppet::Graph::Prioritizer def generate_priority_for(resource) record_priority_for(resource, Digest::SHA1.hexdigest("NaCl, MgSO4 (salts) and then #{resource.ref}")) end def generate_priority_contained_in(container, resource) generate_priority_for(resource) end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector.rb��������������������������������������������������������������0000644�0052762�0001160�00000005140�13417161721�017564� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Manage indirections to termini. They are organized in terms of indirections - # - e.g., configuration, node, file, certificate -- and each indirection has one # or more terminus types defined. The indirection is configured via the # +indirects+ method, which will be called by the class extending itself # with this module. module Puppet::Indirector # LAK:FIXME We need to figure out how to handle documentation for the # different indirection types. require 'puppet/indirector/indirection' require 'puppet/indirector/terminus' require 'puppet/indirector/code' require 'puppet/indirector/envelope' require 'puppet/network/format_support' def self.configure_routes(application_routes) application_routes.each do |indirection_name, termini| indirection_name = indirection_name.to_sym terminus_name = termini["terminus"] cache_name = termini["cache"] Puppet::Indirector::Terminus.terminus_class(indirection_name, terminus_name || cache_name) indirection = Puppet::Indirector::Indirection.instance(indirection_name) raise _("Indirection %{indirection_name} does not exist") % { indirection_name: indirection_name } unless indirection indirection.terminus_class = terminus_name if terminus_name indirection.cache_class = cache_name if cache_name end end # Declare that the including class indirects its methods to # this terminus. The terminus name must be the name of a Puppet # default, not the value -- if it's the value, then it gets # evaluated at parse time, which is before the user has had a chance # to override it. def indirects(indirection, options = {}) raise(ArgumentError, _("Already handling indirection for %{current}; cannot also handle %{next}") % { current: @indirection.name, next: indirection }) if @indirection # populate this class with the various new methods extend ClassMethods include Puppet::Indirector::Envelope include Puppet::Network::FormatSupport # record the indirected class name for documentation purposes options[:indirected_class] = name # instantiate the actual Terminus for that type and this name (:ldap, w/ args :node) # & hook the instantiated Terminus into this class (Node: @indirection = terminus) @indirection = Puppet::Indirector::Indirection.new(self, indirection, options) end module ClassMethods attr_reader :indirection end # Helper definition for indirections that handle filenames. BadNameRegexp = Regexp.union(/^\.\./, %r{[\\/]}, "\0", /(?i)^[a-z]:/) end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/����������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�017244� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/catalog/��������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020656� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/catalog/compiler.rb���������������������������������������������0000644�0052762�0001160�00000043655�13417161721�023025� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/node' require 'puppet/resource/catalog' require 'puppet/indirector/code' require 'puppet/util/profiler' require 'puppet/util/checksums' require 'yaml' require 'uri' class Puppet::Resource::Catalog::Compiler < Puppet::Indirector::Code desc "Compiles catalogs on demand using Puppet's compiler." include Puppet::Util include Puppet::Util::Checksums attr_accessor :code # @param request [Puppet::Indirector::Request] an indirection request # (possibly) containing facts # @return [Puppet::Node::Facts] facts object corresponding to facts in request def extract_facts_from_request(request) return unless text_facts = request.options[:facts] unless format = request.options[:facts_format] raise ArgumentError, _("Facts but no fact format provided for %{request}") % { request: request.key } end Puppet::Util::Profiler.profile(_("Found facts"), [:compiler, :find_facts]) do facts = text_facts.is_a?(Puppet::Node::Facts) ? text_facts : convert_wire_facts(text_facts, format) unless facts.name == request.key raise Puppet::Error, _("Catalog for %{request} was requested with fact definition for the wrong node (%{fact_name}).") % { request: request.key.inspect, fact_name: facts.name.inspect } end return facts end end def save_facts_from_request(facts, request) Puppet::Node::Facts.indirection.save(facts, nil, :environment => request.environment, :transaction_uuid => request.options[:transaction_uuid]) end # Compile a node's catalog. def find(request) facts = extract_facts_from_request(request) save_facts_from_request(facts, request) if !facts.nil? node = node_from_request(facts, request) node.trusted_data = Puppet.lookup(:trusted_information) { Puppet::Context::TrustedInformation.local(node) }.to_h if node.environment node.environment.with_text_domain do compile(node, request.options) end else compile(node, request.options) end end # filter-out a catalog to remove exported resources def filter(catalog) return catalog.filter { |r| r.virtual? } if catalog.respond_to?(:filter) catalog end def initialize Puppet::Util::Profiler.profile(_("Setup server facts for compiling"), [:compiler, :init_server_facts]) do set_server_facts end end # Is our compiler part of a network, or are we just local? def networked? Puppet.run_mode.master? end private # @param facts [String] facts in a wire format for decoding # @param format [String] a content-type string # @return [Puppet::Node::Facts] facts object deserialized from supplied string # @api private def convert_wire_facts(facts, format) if format == 'pson' # We unescape here because the corresponding code in Puppet::Configurer::FactHandler encodes with Puppet::Util.uri_query_encode # PSON is deprecated, but continue to accept from older agents return Puppet::Node::Facts.convert_from('pson', CGI.unescape(facts)) elsif format == 'application/json' return Puppet::Node::Facts.convert_from('json', CGI.unescape(facts)) else raise ArgumentError, _("Unsupported facts format") end end # Add any extra data necessary to the node. def add_node_data(node) # Merge in our server-side facts, so they can be used during compilation. node.add_server_facts(@server_facts) end # Determine which checksum to use; if agent_checksum_type is not nil, # use the first entry in it that is also in known_checksum_types. # If no match is found, return nil. def common_checksum_type(agent_checksum_type) if agent_checksum_type agent_checksum_types = agent_checksum_type.split('.').map {|type| type.to_sym} checksum_type = agent_checksum_types.drop_while do |type| not known_checksum_types.include? type end.first end checksum_type end def get_content_uri(metadata, source, environment_path) # The static file content server doesn't know how to expand mountpoints, so # we need to do that ourselves from the actual system path of the source file. # This does that, while preserving any user-specified server or port. source_path = Pathname.new(metadata.full_path) path = source_path.relative_path_from(environment_path).to_s source_as_uri = URI.parse(Puppet::Util.uri_encode(source)) server = source_as_uri.host port = ":#{source_as_uri.port}" if source_as_uri.port return "puppet://#{server}#{port}/#{path}" end # Helper method to decide if a file resource's metadata can be inlined. # Also used to profile/log reasons for not inlining. def inlineable?(resource, sources) case when resource[:ensure] == 'absent' #TRANSLATORS Inlining refers to adding additional metadata (in this case we are not inlining) return Puppet::Util::Profiler.profile(_("Not inlining absent resource"), [:compiler, :static_compile_inlining, :skipped_file_metadata, :absent]) { false } when sources.empty? #TRANSLATORS Inlining refers to adding additional metadata (in this case we are not inlining) return Puppet::Util::Profiler.profile(_("Not inlining resource without sources"), [:compiler, :static_compile_inlining, :skipped_file_metadata, :no_sources]) { false } when (not (sources.all? {|source| source =~ /^puppet:/})) #TRANSLATORS Inlining refers to adding additional metadata (in this case we are not inlining) return Puppet::Util::Profiler.profile(_("Not inlining unsupported source scheme"), [:compiler, :static_compile_inlining, :skipped_file_metadata, :unsupported_scheme]) { false } else return true end end # Return true if metadata is inlineable, meaning the request's source is # for the 'modules' mount and the resolved path is of the form: # $codedir/environments/$environment/*/*/files/** def inlineable_metadata?(metadata, source, environment_path) source_as_uri = URI.parse(Puppet::Util.uri_encode(source)) location = Puppet::Module::FILETYPES['files'] !!(source_as_uri.path =~ /^\/modules\// && metadata.full_path =~ /#{environment_path}[^\/]+\/[^\/]+\/#{location}\/.+/) end # Helper method to log file resources that could not be inlined because they # fall outside of an environment. def log_file_outside_environment #TRANSLATORS Inlining refers to adding additional metadata (in this case we are not inlining) Puppet::Util::Profiler.profile(_("Not inlining file outside environment"), [:compiler, :static_compile_inlining, :skipped_file_metadata, :file_outside_environment]) { true } end # Helper method to log file resources that were successfully inlined. def log_metadata_inlining #TRANSLATORS Inlining refers to adding additional metadata Puppet::Util::Profiler.profile(_("Inlining file metadata"), [:compiler, :static_compile_inlining, :inlined_file_metadata]) { true } end # Inline file metadata for static catalogs # Initially restricted to files sourced from codedir via puppet:/// uri. def inline_metadata(catalog, checksum_type) environment_path = Pathname.new File.join(Puppet[:environmentpath], catalog.environment, "") list_of_resources = catalog.resources.find_all { |res| res.type == "File" } # TODO: get property/parameter defaults if entries are nil in the resource # For now they're hard-coded to match the File type. list_of_resources.each do |resource| sources = [resource[:source]].flatten.compact next unless inlineable?(resource, sources) # both need to handle multiple sources if resource[:recurse] == true || resource[:recurse] == 'true' || resource[:recurse] == 'remote' # Construct a hash mapping sources to arrays (list of files found recursively) of metadata options = { :environment => catalog.environment_instance, :links => resource[:links] ? resource[:links].to_sym : :manage, :checksum_type => resource[:checksum] ? resource[:checksum].to_sym : checksum_type.to_sym, :source_permissions => resource[:source_permissions] ? resource[:source_permissions].to_sym : :ignore, :recurse => true, :recurselimit => resource[:recurselimit], :ignore => resource[:ignore], } sources_in_environment = true source_to_metadatas = {} sources.each do |source| source = Puppet::Type.type(:file).attrclass(:source).normalize(source) if list_of_data = Puppet::FileServing::Metadata.indirection.search(source, options) basedir_meta = list_of_data.find {|meta| meta.relative_path == '.'} devfail "FileServing::Metadata search should always return the root search path" if basedir_meta.nil? if ! inlineable_metadata?(basedir_meta, source, environment_path) # If any source is not in the environment path, skip inlining this resource. log_file_outside_environment sources_in_environment = false break end base_content_uri = get_content_uri(basedir_meta, source, environment_path) list_of_data.each do |metadata| if metadata.relative_path == '.' metadata.content_uri = base_content_uri else metadata.content_uri = "#{base_content_uri}/#{metadata.relative_path}" end end source_to_metadatas[source] = list_of_data # Optimize for returning less data if sourceselect is first if resource[:sourceselect] == 'first' || resource[:sourceselect].nil? break end end end if sources_in_environment && !source_to_metadatas.empty? log_metadata_inlining catalog.recursive_metadata[resource.title] = source_to_metadatas end else options = { :environment => catalog.environment_instance, :links => resource[:links] ? resource[:links].to_sym : :manage, :checksum_type => resource[:checksum] ? resource[:checksum].to_sym : checksum_type.to_sym, :source_permissions => resource[:source_permissions] ? resource[:source_permissions].to_sym : :ignore } metadata = nil sources.each do |source| source = Puppet::Type.type(:file).attrclass(:source).normalize(source) if data = Puppet::FileServing::Metadata.indirection.find(source, options) metadata = data metadata.source = source break end end raise _("Could not get metadata for %{resource}") % { resource: resource[:source] } unless metadata if inlineable_metadata?(metadata, metadata.source, environment_path) metadata.content_uri = get_content_uri(metadata, metadata.source, environment_path) log_metadata_inlining # If the file is in the environment directory, we can safely inline catalog.metadata[resource.title] = metadata else # Log a profiler event that we skipped this file because it is not in an environment. log_file_outside_environment end end end end # Compile the actual catalog. def compile(node, options) if node.environment && node.environment.static_catalogs? && options[:static_catalog] && options[:code_id] # Check for errors before compiling the catalog checksum_type = common_checksum_type(options[:checksum_type]) raise Puppet::Error, _("Unable to find a common checksum type between agent '%{agent_type}' and master '%{master_type}'.") % { agent_type: options[:checksum_type], master_type: known_checksum_types } unless checksum_type end escaped_node_name = node.name.gsub(/%/, '%%') if checksum_type if node.environment escaped_node_environment = node.environment.to_s.gsub(/%/, '%%') benchmark_str = _("Compiled static catalog for %{node} in environment %{environment} in %%{seconds} seconds") % { node: escaped_node_name, environment: escaped_node_environment } profile_str = _("Compiled static catalog for %{node} in environment %{environment}") % { node: node.name, environment: node.environment } else benchmark_str = _("Compiled static catalog for %{node} in %%{seconds} seconds") % { node: escaped_node_name } profile_str = _("Compiled static catalog for %{node}") % { node: node.name } end else if node.environment escaped_node_environment = node.environment.to_s.gsub(/%/, '%%') benchmark_str = _("Compiled catalog for %{node} in environment %{environment} in %%{seconds} seconds") % { node: escaped_node_name, environment: escaped_node_environment } profile_str = _("Compiled catalog for %{node} in environment %{environment}") % { node: node.name, environment: node.environment } else benchmark_str = _("Compiled catalog for %{node} in %%{seconds} seconds") % { node: escaped_node_name } profile_str = _("Compiled catalog for %{node}") % { node: node.name } end end config = nil benchmark(:notice, benchmark_str) do compile_type = checksum_type ? :static_compile : :compile Puppet::Util::Profiler.profile(profile_str, [:compiler, compile_type, node.environment, node.name]) do begin config = Puppet::Parser::Compiler.compile(node, options[:code_id]) rescue Puppet::Error => detail Puppet.err(detail.to_s) if networked? raise ensure Puppet::Type.clear_misses unless Puppet[:always_retry_plugins] end if checksum_type && config.is_a?(model) escaped_node_name = node.name.gsub(/%/, '%%') if node.environment escaped_node_environment = node.environment.to_s.gsub(/%/, '%%') #TRANSLATORS Inlined refers to adding additional metadata benchmark_str = _("Inlined resource metadata into static catalog for %{node} in environment %{environment} in %%{seconds} seconds") % { node: escaped_node_name, environment: escaped_node_environment } #TRANSLATORS Inlined refers to adding additional metadata profile_str = _("Inlined resource metadata into static catalog for %{node} in environment %{environment}") % { node: node.name, environment: node.environment } else #TRANSLATORS Inlined refers to adding additional metadata benchmark_str = _("Inlined resource metadata into static catalog for %{node} in %%{seconds} seconds") % { node: escaped_node_name } #TRANSLATORS Inlined refers to adding additional metadata profile_str = _("Inlined resource metadata into static catalog for %{node}") % { node: node.name } end benchmark(:notice, benchmark_str) do Puppet::Util::Profiler.profile(profile_str, [:compiler, :static_compile_postprocessing, node.environment, node.name]) do inline_metadata(config, checksum_type) end end end end end config end # Use indirection to find the node associated with a given request def find_node(name, environment, transaction_uuid, configured_environment, facts) Puppet::Util::Profiler.profile(_("Found node information"), [:compiler, :find_node]) do node = nil begin node = Puppet::Node.indirection.find(name, :environment => environment, :transaction_uuid => transaction_uuid, :configured_environment => configured_environment, :facts => facts) rescue => detail message = _("Failed when searching for node %{name}: %{detail}") % { name: name, detail: detail } Puppet.log_exception(detail, message) raise Puppet::Error, message, detail.backtrace end # Add any external data to the node. if node add_node_data(node) end node end end # Extract the node from the request, or use the request # to find the node. def node_from_request(facts, request) if node = request.options[:use_node] if request.remote? raise Puppet::Error, _("Invalid option use_node for a remote request") else return node end end # We rely on our authorization system to determine whether the connected # node is allowed to compile the catalog's node referenced by key. # By default the REST authorization system makes sure only the connected node # can compile his catalog. # This allows for instance monitoring systems or puppet-load to check several # node's catalog with only one certificate and a modification to auth.conf # If no key is provided we can only compile the currently connected node. name = request.key || request.node if node = find_node(name, request.environment, request.options[:transaction_uuid], request.options[:configured_environment], facts) return node end raise ArgumentError, _("Could not find node '%{name}'; cannot compile") % { name: name } end # Initialize our server fact hash; we add these to each client, and they # won't change while we're running, so it's safe to cache the values. def set_server_facts @server_facts = {} # Add our server version to the fact list @server_facts["serverversion"] = Puppet.version.to_s # And then add the server name and IP {"servername" => "fqdn", "serverip" => "ipaddress" }.each do |var, fact| if value = Facter.value(fact) @server_facts[var] = value else Puppet.warning _("Could not retrieve fact %{fact}") % { fact: fact } end end if @server_facts["servername"].nil? host = Facter.value(:hostname) if domain = Facter.value(:domain) @server_facts["servername"] = [host, domain].join(".") else @server_facts["servername"] = host end end end end �����������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/catalog/msgpack.rb����������������������������������������������0000644�0052762�0001160�00000000327�13417161721�022625� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/resource/catalog' require 'puppet/indirector/msgpack' class Puppet::Resource::Catalog::Msgpack < Puppet::Indirector::Msgpack desc "Store catalogs as flat files, serialized using MessagePack." end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/catalog/rest.rb�������������������������������������������������0000644�0052762�0001160�00000000275�13417161721�022157� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/resource/catalog' require 'puppet/indirector/rest' class Puppet::Resource::Catalog::Rest < Puppet::Indirector::REST desc "Find resource catalogs over HTTP via REST." end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/catalog/store_configs.rb����������������������������������������0000644�0052762�0001160�00000000374�13417161721�024046� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/store_configs' require 'puppet/resource/catalog' class Puppet::Resource::Catalog::StoreConfigs < Puppet::Indirector::StoreConfigs desc %q{Part of the "storeconfigs" feature. Should not be directly set by end users.} end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/catalog/json.rb�������������������������������������������������0000644�0052762�0001160�00000001372�13417161721�022152� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/resource/catalog' require 'puppet/indirector/json' class Puppet::Resource::Catalog::Json < Puppet::Indirector::JSON desc "Store catalogs as flat files, serialized using JSON." def from_json(text) utf8 = text.force_encoding(Encoding::UTF_8) if utf8.valid_encoding? model.convert_from('json', utf8) else Puppet.info(_("Unable to deserialize catalog from json, retrying with pson")) model.convert_from('pson', text.force_encoding(Encoding::BINARY)) end end def to_json(object) object.render('json') rescue Puppet::Network::FormatHandler::FormatError Puppet.info(_("Unable to serialize catalog to json, retrying with pson")) object.render('pson').force_encoding(Encoding::BINARY) end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/catalog/yaml.rb�������������������������������������������������0000644�0052762�0001160�00000001026�13417161721�022137� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/resource/catalog' require 'puppet/indirector/yaml' class Puppet::Resource::Catalog::Yaml < Puppet::Indirector::Yaml desc "Store catalogs as flat files, serialized using YAML." private # Override these, because yaml doesn't want to convert our self-referential # objects. This is hackish, but eh. def from_yaml(text) if config = YAML.load(text) return config end end def to_yaml(config) # We can't yaml-dump classes. #config.edgelist_class = nil YAML.dump(config) end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/certificate/����������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�021526� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/certificate/rest.rb���������������������������������������������0000644�0052762�0001160�00000000627�13417161721�023030� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/ssl/certificate' require 'puppet/indirector/rest' class Puppet::SSL::Certificate::Rest < Puppet::Indirector::REST desc "Find certificates over HTTP via REST." use_server_setting(:ca_server) use_port_setting(:ca_port) use_srv_service(:ca) def find(request) return nil unless result = super result.name = request.key unless result.name == request.key result end end ���������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/certificate/ca.rb�����������������������������������������������0000644�0052762�0001160�00000000377�13417161721�022440� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/ssl_file' require 'puppet/ssl/certificate' class Puppet::SSL::Certificate::Ca < Puppet::Indirector::SslFile desc "Manage the CA collection of signed SSL certificates on disk." store_in :signeddir store_ca_at :cacert end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/certificate/disabled_ca.rb��������������������������������������0000644�0052762�0001160�00000001250�13417161721�024256� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/code' require 'puppet/ssl/certificate' class Puppet::SSL::Certificate::DisabledCa < Puppet::Indirector::Code desc "Manage SSL certificates on disk, but reject any remote access to the SSL data store. Used when a master has an explicitly disabled CA to prevent clients getting confusing 'success' behaviour." def initialize @file = Puppet::SSL::Certificate.indirection.terminus(:file) end [:find, :head, :search, :save, :destroy].each do |name| define_method(name) do |request| if request.remote? raise Puppet::Error, _("this master is not a CA") else @file.send(name, request) end end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/certificate/file.rb���������������������������������������������0000644�0052762�0001160�00000000350�13417161721�022763� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/ssl_file' require 'puppet/ssl/certificate' class Puppet::SSL::Certificate::File < Puppet::Indirector::SslFile desc "Manage SSL certificates on disk." store_in :certdir store_ca_at :localcacert end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/certificate_request/��������������������������������������������0000755�0052762�0001160�00000000000�13417162176�023276� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/certificate_request/file.rb�������������������������������������0000644�0052762�0001160�00000000365�13417161721�024541� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/ssl_file' require 'puppet/ssl/certificate_request' class Puppet::SSL::CertificateRequest::File < Puppet::Indirector::SslFile desc "Manage the collection of certificate requests on disk." store_in :requestdir end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/certificate_request/memory.rb�����������������������������������0000644�0052762�0001160�00000000354�13417161721�025130� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/ssl/certificate_request' require 'puppet/indirector/memory' class Puppet::SSL::CertificateRequest::Memory < Puppet::Indirector::Memory desc "Store certificate requests in memory. This is used for testing puppet." end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/certificate_request/rest.rb�������������������������������������0000644�0052762�0001160�00000000454�13417161721�024576� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/ssl/certificate_request' require 'puppet/indirector/rest' class Puppet::SSL::CertificateRequest::Rest < Puppet::Indirector::REST desc "Find and save certificate requests over HTTP via REST." use_server_setting(:ca_server) use_port_setting(:ca_port) use_srv_service(:ca) end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/certificate_request/ca.rb���������������������������������������0000644�0052762�0001160�00000001461�13417161721�024203� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/ssl_file' require 'puppet/ssl/certificate_request' class Puppet::SSL::CertificateRequest::Ca < Puppet::Indirector::SslFile desc "Manage the CA collection of certificate requests on disk." store_in :csrdir def save(request) if host = Puppet::SSL::Host.indirection.find(request.key) if Puppet[:allow_duplicate_certs] Puppet.notice _("%{request} already has a %{host} certificate; new certificate will overwrite it") % { request: request.key, host: host.state } else raise _("%{request} already has a %{host} certificate; ignoring certificate request") % { request: request.key, host: host.state } end end result = super Puppet.notice _("%{request} has a waiting certificate request") % { request: request.key } result end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/certificate_request/disabled_ca.rb������������������������������0000644�0052762�0001160�00000001305�13417161721�026027� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/code' require 'puppet/ssl/certificate_request' class Puppet::SSL::CertificateRequest::DisabledCa < Puppet::Indirector::Code desc "Manage SSL certificate requests on disk, but reject any remote access to the SSL data store. Used when a master has an explicitly disabled CA to prevent clients getting confusing 'success' behaviour." def initialize @file = Puppet::SSL::CertificateRequest.indirection.terminus(:file) end [:find, :head, :search, :save, :destroy].each do |name| define_method(name) do |request| if request.remote? raise Puppet::Error, _("this master is not a CA") else @file.send(name, request) end end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/code.rb���������������������������������������������������������0000644�0052762�0001160�00000000261�13417161721�020475� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/terminus' # Do nothing, requiring that the back-end terminus do all # of the work. class Puppet::Indirector::Code < Puppet::Indirector::Terminus end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/data_binding/���������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�021647� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/data_binding/hiera.rb�������������������������������������������0000644�0052762�0001160�00000000237�13417161721�023261� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/hiera' require 'hiera/scope' class Puppet::DataBinding::Hiera < Puppet::Indirector::Hiera desc "Retrieve data using Hiera." end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/data_binding/none.rb��������������������������������������������0000644�0052762�0001160�00000000336�13417161721�023130� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/none' class Puppet::DataBinding::None < Puppet::Indirector::None desc "A Dummy terminus that always throws :no_such_key for data lookups." def find(request) throw :no_such_key end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/direct_file_server.rb�������������������������������������������0000644�0052762�0001160�00000000724�13417161721�023426� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/file_serving/terminus_helper' require 'puppet/indirector/terminus' class Puppet::Indirector::DirectFileServer < Puppet::Indirector::Terminus include Puppet::FileServing::TerminusHelper def find(request) return nil unless Puppet::FileSystem.exist?(request.key) path2instance(request, request.key) end def search(request) return nil unless Puppet::FileSystem.exist?(request.key) path2instances(request, request.key) end end ��������������������������������������������puppet-5.5.10/lib/puppet/indirector/envelope.rb�����������������������������������������������������0000644�0052762�0001160�00000000355�13417161721�021404� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector' # Provide any attributes or functionality needed for indirected # instances. module Puppet::Indirector::Envelope attr_accessor :expiration def expired? expiration and expiration < Time.now end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/errors.rb�������������������������������������������������������0000644�0052762�0001160�00000000143�13417161721�021076� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/error' module Puppet::Indirector class ValidationError < Puppet::Error; end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/exec.rb���������������������������������������������������������0000644�0052762�0001160�00000002305�13417161721�020510� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/terminus' require 'puppet/util' class Puppet::Indirector::Exec < Puppet::Indirector::Terminus # Look for external node definitions. def find(request) name = request.key external_command = command # Make sure it's an array raise Puppet::DevError, _("Exec commands must be an array") unless external_command.is_a?(Array) # Make sure it's fully qualified. raise ArgumentError, _("You must set the exec parameter to a fully qualified command") unless Puppet::Util.absolute_path?(external_command[0]) # Add our name to it. external_command << name begin output = execute(external_command, :failonfail => true, :combine => false) rescue Puppet::ExecutionFailure => detail raise Puppet::Error, _("Failed to find %{name} via exec: %{detail}") % { name: name, detail: detail }, detail.backtrace end if output =~ /\A\s*\Z/ # all whitespace Puppet.debug "Empty response for #{name} from #{self.name} terminus" return nil else return output end end private # Proxy the execution, so it's easier to test. def execute(command, arguments) Puppet::Util::Execution.execute(command,arguments) end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/face.rb���������������������������������������������������������0000644�0052762�0001160�00000012322�13417161721�020462� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/face' class Puppet::Indirector::Face < Puppet::Face option "--terminus _" + _("TERMINUS") do summary _("The indirector terminus to use.") description <<-EOT Indirector faces expose indirected subsystems of Puppet. These subsystems are each able to retrieve and alter a specific type of data (with the familiar actions of `find`, `search`, `save`, and `destroy`) from an arbitrary number of pluggable backends. In Puppet parlance, these backends are called terminuses. Almost all indirected subsystems have a `rest` terminus that interacts with the puppet master's data. Most of them have additional terminuses for various local data models, which are in turn used by the indirected subsystem on the puppet master whenever it receives a remote request. The terminus for an action is often determined by context, but occasionally needs to be set explicitly. See the "Notes" section of this face's manpage for more details. EOT before_action do |action, args, options| set_terminus(options[:terminus]) end after_action do |action, args, options| indirection.reset_terminus_class end end def self.indirections Puppet::Indirector::Indirection.instances.collect { |t| t.to_s }.sort end def self.terminus_classes(indirection) Puppet::Indirector::Terminus.terminus_classes(indirection.to_sym).collect { |t| t.to_s }.sort end def call_indirection_method(method, key, options) begin if method == :save # key is really the instance to save result = indirection.__send__(method, key, nil, options) else result = indirection.__send__(method, key, options) end rescue => detail message = _("Could not call '%{method}' on '%{indirection}': %{detail}") % { method: method, indirection: indirection_name, detail: detail } Puppet.log_exception(detail, message) raise RuntimeError, message, detail.backtrace end return result end option "--extra " + _("HASH") do summary _("Extra arguments to pass to the indirection request") description <<-EOT A terminus can take additional arguments to refine the operation, which are passed as an arbitrary hash to the back-end. Anything passed as the extra value is just send direct to the back-end. EOT default_to do Hash.new end end action :destroy do summary _("Delete an object.") arguments _("<key>") when_invoked {|key, options| call_indirection_method :destroy, key, options[:extra] } end action :find do summary _("Retrieve an object by name.") arguments _("[<key>]") when_invoked do |*args| # Default the key to Puppet[:certname] if none is supplied if args.length == 1 key = Puppet[:certname] options = args.last else key, options = *args end call_indirection_method :find, key, options[:extra] end end action :save do summary _("API only: create or overwrite an object.") arguments _("<key>") description <<-EOT API only: create or overwrite an object. As the Faces framework does not currently accept data from STDIN, save actions cannot currently be invoked from the command line. EOT when_invoked {|key, options| call_indirection_method :save, key, options[:extra] } end action :search do summary _("Search for an object or retrieve multiple objects.") arguments _("<query>") when_invoked {|key, options| call_indirection_method :search, key, options[:extra] } end # Print the configuration for the current terminus class action :info do summary _("Print the default terminus class for this face.") description <<-EOT Prints the default terminus class for this subcommand. Note that different run modes may have different default termini; when in doubt, specify the run mode with the '--run_mode' option. EOT when_invoked do |options| if t = indirection.terminus_class _("Run mode '%{mode}': %{terminus}") % { mode: Puppet.run_mode.name, terminus: t } else _("No default terminus class for run mode '%{mode}'") % { mode: Puppet.run_mode.name } end end end attr_accessor :from def indirection_name @indirection_name || name.to_sym end # Here's your opportunity to override the indirection name. By default it # will be the same name as the face. def set_indirection_name(name) @indirection_name = name end # Return an indirection associated with a face, if one exists; # One usually does. def indirection unless @indirection @indirection = Puppet::Indirector::Indirection.instance(indirection_name) @indirection or raise _("Could not find terminus for %{indirection}") % { indirection: indirection_name } end @indirection end def set_terminus(from) begin indirection.terminus_class = from rescue => detail msg = _("Could not set '%{indirection}' terminus to '%{from}' (%{detail}); valid terminus types are %{types}") % { indirection: indirection.name, from: from, detail: detail, types: self.class.terminus_classes(indirection.name).join(", ") } raise detail, msg, detail.backtrace end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/facts/����������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020344� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/facts/facter.rb�������������������������������������������������0000644�0052762�0001160�00000005527�13417161721�022141� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/node/facts' require 'puppet/indirector/code' class Puppet::Node::Facts::Facter < Puppet::Indirector::Code desc "Retrieve facts from Facter. This provides a somewhat abstract interface between Puppet and Facter. It's only `somewhat` abstract because it always returns the local host's facts, regardless of what you attempt to find." def allow_remote_requests? false end def destroy(facts) raise Puppet::DevError, _('You cannot destroy facts in the code store; it is only used for getting facts from Facter') end def save(facts) raise Puppet::DevError, _('You cannot save facts to the code store; it is only used for getting facts from Facter') end # Lookup a host's facts up in Facter. def find(request) Facter.reset # Note: we need to setup puppet's external search paths before adding the puppetversion # fact. This is because in Facter 2.x, the first `Facter.add` causes Facter to create # its directory loaders which cannot be changed, meaning other external facts won't # be resolved. (PUP-4607) self.class.setup_external_search_paths(request) self.class.setup_search_paths(request) # Initialize core Puppet facts, such as puppetversion Puppet.initialize_facts result = Puppet::Node::Facts.new(request.key, Facter.to_hash) result.add_local_facts result.sanitize result end def self.setup_search_paths(request) # Add any per-module fact directories to facter's search path dirs = request.environment.modulepath.collect do |dir| ['lib', 'plugins'].map do |subdirectory| Dir.glob("#{dir}/*/#{subdirectory}/facter") end end.flatten + Puppet[:factpath].split(File::PATH_SEPARATOR) dirs = dirs.select do |dir| next false unless FileTest.directory?(dir) # Even through we no longer directly load facts in the terminus, # print out each .rb in the facts directory as module # developers may find that information useful for debugging purposes if Puppet::Util::Log.sendlevel?(:info) Puppet.info _("Loading facts") Dir.glob("#{dir}/*.rb").each do |file| Puppet.debug "Loading facts from #{file}" end end true end Facter.search(*dirs) end def self.setup_external_search_paths(request) # Add any per-module external fact directories to facter's external search path dirs = [] request.environment.modules.each do |m| if m.has_external_facts? dir = m.plugin_fact_directory Puppet.debug "Loading external facts from #{dir}" dirs << dir end end # Add system external fact directory if it exists if FileTest.directory?(Puppet[:pluginfactdest]) dir = Puppet[:pluginfactdest] Puppet.debug "Loading external facts from #{dir}" dirs << dir end Facter.search_external dirs end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/facts/memory.rb�������������������������������������������������0000644�0052762�0001160�00000000546�13417161721�022201� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/node/facts' require 'puppet/indirector/memory' class Puppet::Node::Facts::Memory < Puppet::Indirector::Memory desc "Keep track of facts in memory but nowhere else. This is used for one-time compiles, such as what the stand-alone `puppet` does. To use this terminus, you must load it with the data you want it to contain." end ����������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/facts/network_device.rb�����������������������������������������0000644�0052762�0001160�00000001411�13417161721�023671� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/node/facts' require 'puppet/indirector/code' class Puppet::Node::Facts::NetworkDevice < Puppet::Indirector::Code desc "Retrieve facts from a network device." def allow_remote_requests? false end # Look a device's facts up through the current device. def find(request) result = Puppet::Node::Facts.new(request.key, Puppet::Util::NetworkDevice.current.facts) result.add_local_facts result.sanitize result end def destroy(facts) raise Puppet::DevError, _("You cannot destroy facts in the code store; it is only used for getting facts from a remote device") end def save(facts) raise Puppet::DevError, _("You cannot save facts to the code store; it is only used for getting facts from a remote device") end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/facts/rest.rb���������������������������������������������������0000644�0052762�0001160�00000001244�13417161721�021642� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/node/facts' require 'puppet/indirector/rest' class Puppet::Node::Facts::Rest < Puppet::Indirector::REST desc "Find and save facts about nodes over HTTP via REST." def save(request) raise ArgumentError, _("PUT does not accept options") unless request.options.empty? response = do_request(request) do |req| http_put(req, IndirectedRoutes.request_to_uri(req), req.instance.render, headers.merge({ "Content-Type" => req.instance.mime })) end if is_http_200?(response) content_type, body = parse_response(response) deserialize_save(content_type, body) else raise convert_to_http_error(response) end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/facts/store_configs.rb������������������������������������������0000644�0052762�0001160�00000000435�13417161721�023532� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/node/facts' require 'puppet/indirector/store_configs' class Puppet::Node::Facts::StoreConfigs < Puppet::Indirector::StoreConfigs desc %q{Part of the "storeconfigs" feature. Should not be directly set by end users.} def allow_remote_requests? false end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/facts/yaml.rb���������������������������������������������������0000644�0052762�0001160�00000003567�13417161721�021641� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/node/facts' require 'puppet/indirector/yaml' class Puppet::Node::Facts::Yaml < Puppet::Indirector::Yaml desc "Store client facts as flat files, serialized using YAML, or return deserialized facts from disk." def search(request) node_names = [] Dir.glob(yaml_dir_path).each do |file| facts = YAML.load_file(file) node_names << facts.name if node_matches?(facts, request.options) end node_names end private # Return the path to a given node's file. def yaml_dir_path base = Puppet.run_mode.master? ? Puppet[:yamldir] : Puppet[:clientyamldir] File.join(base, 'facts', '*.yaml') end def node_matches?(facts, options) options.each do |key, value| type, name, operator = key.to_s.split(".") operator ||= 'eq' return false unless node_matches_option?(type, name, operator, value, facts) end return true end def node_matches_option?(type, name, operator, value, facts) case type when "meta" case name when "timestamp" compare_timestamp(operator, facts.timestamp, Time.parse(value)) end when "facts" compare_facts(operator, facts.values[name], value) end end def compare_facts(operator, value1, value2) return false unless value1 case operator when "eq" value1.to_s == value2.to_s when "le" value1.to_f <= value2.to_f when "ge" value1.to_f >= value2.to_f when "lt" value1.to_f < value2.to_f when "gt" value1.to_f > value2.to_f when "ne" value1.to_s != value2.to_s end end def compare_timestamp(operator, value1, value2) case operator when "eq" value1 == value2 when "le" value1 <= value2 when "ge" value1 >= value2 when "lt" value1 < value2 when "gt" value1 > value2 when "ne" value1 != value2 end end end �����������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/file_bucket_file/�����������������������������������������������0000755�0052762�0001160�00000000000�13417162176�022517� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/file_bucket_file/file.rb����������������������������������������0000644�0052762�0001160�00000026365�13417161721�023772� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/code' require 'puppet/file_bucket/file' require 'puppet/util/checksums' require 'puppet/util/diff' require 'fileutils' module Puppet::FileBucketFile class File < Puppet::Indirector::Code include Puppet::Util::Checksums include Puppet::Util::Diff desc "Store files in a directory set based on their checksums." def find(request) request.options[:bucket_path] ||= Puppet[:bucketdir] # If filebucket mode is 'list' if request.options[:list_all] return nil unless ::File.exists?(request.options[:bucket_path]) return list(request) end checksum, files_original_path = request_to_checksum_and_path(request) contents_file = path_for(request.options[:bucket_path], checksum, 'contents') paths_file = path_for(request.options[:bucket_path], checksum, 'paths') if Puppet::FileSystem.exist?(contents_file) && matches(paths_file, files_original_path) if request.options[:diff_with] other_contents_file = path_for(request.options[:bucket_path], request.options[:diff_with], 'contents') raise _("could not find diff_with %{diff}") % { diff: request.options[:diff_with] } unless Puppet::FileSystem.exist?(other_contents_file) raise _("Unable to diff on this platform") unless Puppet[:diff] != "" return diff(Puppet::FileSystem.path_string(contents_file), Puppet::FileSystem.path_string(other_contents_file)) else #TRANSLATORS "FileBucket" should not be translated Puppet.info _("FileBucket read %{checksum}") % { checksum: checksum } model.new(Puppet::FileSystem.binread(contents_file)) end else nil end end def list(request) if request.remote? raise Puppet::Error, _("Listing remote file buckets is not allowed") end fromdate = request.options[:fromdate] || "0:0:0 1-1-1970" todate = request.options[:todate] || Time.now.strftime("%F %T") begin to = Time.parse(todate) rescue ArgumentError raise Puppet::Error, _("Error while parsing 'todate'") end begin from = Time.parse(fromdate) rescue ArgumentError raise Puppet::Error, _("Error while parsing 'fromdate'") end # Setting hash's default value to [], needed by the following loop bucket = Hash.new {[]} msg = "" # Get all files with mtime between 'from' and 'to' Pathname.new(request.options[:bucket_path]).find { |item| if item.file? and item.basename.to_s == "paths" filenames = item.read.strip.split("\n") filestat = Time.parse(item.stat.mtime.to_s) if from <= filestat and filestat <= to filenames.each do |filename| bucket[filename] += [[ item.stat.mtime , item.parent.basename ]] end end end } # Sort the results bucket.each { |filename, contents| contents.sort_by! do |item| # NOTE: Ruby 2.4 may reshuffle item order even if the keys in sequence are sorted already item[0] end } # Build the output message. Sorted by names then by dates bucket.sort.each { |filename,contents| contents.each { |mtime, chksum| date = mtime.strftime("%F %T") msg += "#{chksum} #{date} #{filename}\n" } } return model.new(msg) end def head(request) checksum, files_original_path = request_to_checksum_and_path(request) contents_file = path_for(request.options[:bucket_path], checksum, 'contents') paths_file = path_for(request.options[:bucket_path], checksum, 'paths') Puppet::FileSystem.exist?(contents_file) && matches(paths_file, files_original_path) end def save(request) instance = request.instance _, files_original_path = request_to_checksum_and_path(request) contents_file = path_for(instance.bucket_path, instance.checksum_data, 'contents') paths_file = path_for(instance.bucket_path, instance.checksum_data, 'paths') save_to_disk(instance, files_original_path, contents_file, paths_file) # don't echo the request content back to the agent model.new('') end def validate_key(request) # There are no ACLs on filebucket files so validating key is not important end private # @param paths_file [Object] Opaque file path # @param files_original_path [String] # def matches(paths_file, files_original_path) # Puppet will have already written the paths_file in the systems encoding # given its possible that request.options[:bucket_path] or Puppet[:bucketdir] # contained characters in an encoding that are not represented the # same way when the bytes are decoded as UTF-8, continue using system encoding Puppet::FileSystem.open(paths_file, 0640, 'a+:external') do |f| path_match(f, files_original_path) end end def path_match(file_handle, files_original_path) return true unless files_original_path # if no path was provided, it's a match file_handle.rewind file_handle.each_line do |line| return true if line.chomp == files_original_path end return false end # @param bucket_file [Puppet::FileBucket::File] IO object representing # content to back up # @param files_original_path [String] Path to original source file on disk # @param contents_file [Pathname] Opaque file path to intended backup # location # @param paths_file [Pathname] Opaque file path to file containing source # file paths on disk # @return [void] # @raise [Puppet::FileBucket::BucketError] on possible sum collision between # existing and new backup # @api private def save_to_disk(bucket_file, files_original_path, contents_file, paths_file) Puppet::Util.withumask(0007) do unless Puppet::FileSystem.dir_exist?(paths_file) Puppet::FileSystem.dir_mkpath(paths_file) end # Puppet will have already written the paths_file in the systems encoding # given its possible that request.options[:bucket_path] or Puppet[:bucketdir] # contained characters in an encoding that are not represented the # same way when the bytes are decoded as UTF-8, continue using system encoding Puppet::FileSystem.exclusive_open(paths_file, 0640, 'a+:external') do |f| if Puppet::FileSystem.exist?(contents_file) if verify_identical_file(contents_file, bucket_file) #TRANSLATORS "FileBucket" should not be translated Puppet.info _("FileBucket got a duplicate file %{file_checksum}") % { file_checksum: bucket_file.checksum } # Don't touch the contents file on Windows, since we can't update the # mtime of read-only files there. if !Puppet::Util::Platform.windows? Puppet::FileSystem.touch(contents_file) end elsif contents_file_matches_checksum?(contents_file, bucket_file.checksum_data, bucket_file.checksum_type) # If the contents or sizes don't match, but the checksum does, # then we've found a conflict (potential hash collision). # Unlikely, but quite bad. Don't remove the file in case it's # needed, but ask the user to validate. # Note: Don't print the full path to the bucket file in the # exception to avoid disclosing file system layout on server. #TRANSLATORS "FileBucket" should not be translated Puppet.err(_("Unable to verify existing FileBucket backup at '%{path}'.") % { path: contents_file.to_path }) raise Puppet::FileBucket::BucketError, _("Existing backup and new file have different content but same checksum, %{value}. Verify existing backup and remove if incorrect.") % { value: bucket_file.checksum } else # PUP-1334 If the contents_file exists but does not match its # checksum, our backup has been corrupted. Warn about overwriting # it, and proceed with new backup. Puppet.warning(_("Existing backup does not match its expected sum, %{sum}. Overwriting corrupted backup.") % { sum: bucket_file.checksum }) copy_bucket_file_to_contents_file(contents_file, bucket_file) end else copy_bucket_file_to_contents_file(contents_file, bucket_file) end unless path_match(f, files_original_path) f.seek(0, IO::SEEK_END) f.puts(files_original_path) end end end end def request_to_checksum_and_path(request) checksum_type, checksum, path = request.key.split(/\//, 3) if path == '' # Treat "md5/<checksum>/" like "md5/<checksum>" path = nil end raise ArgumentError, _("Unsupported checksum type %{checksum_type}") % { checksum_type: checksum_type.inspect } if checksum_type != Puppet[:digest_algorithm] expected = method(checksum_type + "_hex_length").call raise _("Invalid checksum %{checksum}") % { checksum: checksum.inspect } if checksum !~ /^[0-9a-f]{#{expected}}$/ [checksum, path] end # @return [Object] Opaque path as constructed by the Puppet::FileSystem # def path_for(bucket_path, digest, subfile = nil) bucket_path ||= Puppet[:bucketdir] dir = ::File.join(digest[0..7].split("")) basedir = ::File.join(bucket_path, dir, digest) Puppet::FileSystem.pathname(subfile ? ::File.join(basedir, subfile) : basedir) end # @param contents_file [Pathname] Opaque file path to intended backup # location # @param bucket_file [Puppet::FileBucket::File] IO object representing # content to back up # @return [Boolean] whether the data in contents_file is of the same size # and content as that in the bucket_file # @api private def verify_identical_file(contents_file, bucket_file) (bucket_file.to_binary.bytesize == Puppet::FileSystem.size(contents_file)) && (bucket_file.stream() {|s| Puppet::FileSystem.compare_stream(contents_file, s) }) end # @param contents_file [Pathname] Opaque file path to intended backup # location # @param expected_checksum_data [String] expected value of checksum of type # checksum_type # @param checksum_type [String] type of check sum of checksum_data, ie "md5" # @return [Boolean] whether the checksum of the contents_file matches the # supplied checksum # @api private def contents_file_matches_checksum?(contents_file, expected_checksum_data, checksum_type) contents_file_checksum_data = Puppet::Util::Checksums.method(:"#{checksum_type}_file").call(contents_file.to_path) contents_file_checksum_data == expected_checksum_data end # @param contents_file [Pathname] Opaque file path to intended backup # location # @param bucket_file [Puppet::FileBucket::File] IO object representing # content to back up # @return [void] # @api private def copy_bucket_file_to_contents_file(contents_file, bucket_file) Puppet::Util.replace_file(contents_file, 0440) do |of| # PUP-1044 writes all of the contents bucket_file.stream() do |src| FileUtils.copy_stream(src, of) end end end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/file_bucket_file/rest.rb����������������������������������������0000644�0052762�0001160�00000000353�13417161721�024015� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/rest' require 'puppet/file_bucket/file' module Puppet::FileBucketFile class Rest < Puppet::Indirector::REST desc "This is a REST based mechanism to send/retrieve file to/from the filebucket" end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/file_bucket_file/selector.rb������������������������������������0000644�0052762�0001160�00000002002�13417161721�024651� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/code' module Puppet::FileBucketFile class Selector < Puppet::Indirector::Code desc "Select the terminus based on the request" def select(request) if request.protocol == 'https' :rest else :file end end def get_terminus(request) indirection.terminus(select(request)) end def head(request) get_terminus(request).head(request) end def find(request) get_terminus(request).find(request) end def save(request) get_terminus(request).save(request) end def search(request) get_terminus(request).search(request) end def destroy(request) get_terminus(request).destroy(request) end def authorized?(request) terminus = get_terminus(request) if terminus.respond_to?(:authorized?) terminus.authorized?(request) else true end end def validate_key(request) get_terminus(request).validate(request) end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/file_content.rb�������������������������������������������������0000644�0052762�0001160�00000000202�13417161721�022227� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# A stub class, so our constants work. class Puppet::Indirector::FileContent # :nodoc: end require 'puppet/file_serving/content' ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/file_content/���������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�021715� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/file_content/file.rb��������������������������������������������0000644�0052762�0001160�00000000401�13417161721�023147� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/file_serving/content' require 'puppet/indirector/file_content' require 'puppet/indirector/direct_file_server' class Puppet::Indirector::FileContent::File < Puppet::Indirector::DirectFileServer desc "Retrieve file contents from disk." end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/file_content/file_server.rb�������������������������������������0000644�0052762�0001160�00000000412�13417161721�024537� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/file_serving/content' require 'puppet/indirector/file_content' require 'puppet/indirector/file_server' class Puppet::Indirector::FileContent::FileServer < Puppet::Indirector::FileServer desc "Retrieve file contents using Puppet's fileserver." end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/file_content/http.rb��������������������������������������������0000644�0052762�0001160�00000000727�13417161721�023222� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/file_serving/metadata' require 'puppet/indirector/generic_http' require 'puppet/network/http' class Puppet::Indirector::FileContent::Http < Puppet::Indirector::GenericHttp desc "Retrieve file contents from a remote HTTP server." include Puppet::FileServing::TerminusHelper include Puppet::Network::HTTP::Compression.module @http_method = :get def find(request) response = super model.from_binary(uncompress_body(response)) end end �����������������������������������������puppet-5.5.10/lib/puppet/indirector/file_content/rest.rb��������������������������������������������0000644�0052762�0001160�00000000427�13417161721�023215� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/file_serving/content' require 'puppet/indirector/file_content' require 'puppet/indirector/rest' class Puppet::Indirector::FileContent::Rest < Puppet::Indirector::REST desc "Retrieve file contents via a REST HTTP interface." use_srv_service(:fileserver) end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/file_content/selector.rb����������������������������������������0000644�0052762�0001160�00000001332�13417161721�024054� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/file_serving/content' require 'puppet/indirector/file_content' require 'puppet/indirector/code' require 'puppet/file_serving/terminus_selector' class Puppet::Indirector::FileContent::Selector < Puppet::Indirector::Code desc "Select the terminus based on the request" include Puppet::FileServing::TerminusSelector def get_terminus(request) indirection.terminus(select(request)) end def find(request) get_terminus(request).find(request) end def search(request) get_terminus(request).search(request) end def authorized?(request) terminus = get_terminus(request) if terminus.respond_to?(:authorized?) terminus.authorized?(request) else true end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/file_metadata.rb������������������������������������������������0000644�0052762�0001160�00000000204�13417161721�022337� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# A stub class, so our constants work. class Puppet::Indirector::FileMetadata # :nodoc: end require 'puppet/file_serving/metadata' ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/file_metadata/��������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�022023� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/file_metadata/file.rb�������������������������������������������0000644�0052762�0001160�00000000435�13417161721�023264� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/file_serving/metadata' require 'puppet/indirector/file_metadata' require 'puppet/indirector/direct_file_server' class Puppet::Indirector::FileMetadata::File < Puppet::Indirector::DirectFileServer desc "Retrieve file metadata directly from the local filesystem." end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/file_metadata/file_server.rb������������������������������������0000644�0052762�0001160�00000000415�13417161721�024650� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/file_serving/metadata' require 'puppet/indirector/file_metadata' require 'puppet/indirector/file_server' class Puppet::Indirector::FileMetadata::FileServer < Puppet::Indirector::FileServer desc "Retrieve file metadata using Puppet's fileserver." end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/file_metadata/http.rb�������������������������������������������0000644�0052762�0001160�00000001336�13417161721�023325� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/file_serving/http_metadata' require 'puppet/indirector/generic_http' require 'puppet/indirector/file_metadata' require 'net/http' class Puppet::Indirector::FileMetadata::Http < Puppet::Indirector::GenericHttp desc "Retrieve file metadata from a remote HTTP server." include Puppet::FileServing::TerminusHelper @http_method = :head def find(request) head = super if head.is_a?(Net::HTTPSuccess) metadata = Puppet::FileServing::HttpMetadata.new(head) metadata.checksum_type = request.options[:checksum_type] if request.options[:checksum_type] metadata.collect metadata end end def search(request) raise Puppet::Error, _("cannot lookup multiple files") end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/file_metadata/rest.rb�������������������������������������������0000644�0052762�0001160�00000000432�13417161721�023317� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/file_serving/metadata' require 'puppet/indirector/file_metadata' require 'puppet/indirector/rest' class Puppet::Indirector::FileMetadata::Rest < Puppet::Indirector::REST desc "Retrieve file metadata via a REST HTTP interface." use_srv_service(:fileserver) end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/file_metadata/selector.rb���������������������������������������0000644�0052762�0001160�00000001335�13417161721�024165� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/file_serving/metadata' require 'puppet/indirector/file_metadata' require 'puppet/indirector/code' require 'puppet/file_serving/terminus_selector' class Puppet::Indirector::FileMetadata::Selector < Puppet::Indirector::Code desc "Select the terminus based on the request" include Puppet::FileServing::TerminusSelector def get_terminus(request) indirection.terminus(select(request)) end def find(request) get_terminus(request).find(request) end def search(request) get_terminus(request).search(request) end def authorized?(request) terminus = get_terminus(request) if terminus.respond_to?(:authorized?) terminus.authorized?(request) else true end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/file_server.rb��������������������������������������������������0000644�0052762�0001160�00000004017�13417161721�022073� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/file_serving/configuration' require 'puppet/file_serving/fileset' require 'puppet/file_serving/terminus_helper' require 'puppet/indirector/terminus' # Look files up using the file server. class Puppet::Indirector::FileServer < Puppet::Indirector::Terminus include Puppet::FileServing::TerminusHelper # Is the client authorized to perform this action? def authorized?(request) return false unless [:find, :search].include?(request.method) mount, _ = configuration.split_path(request) # If we're not serving this mount, then access is denied. return false unless mount # If there are no auth directives or there is an 'allow *' directive, then # access is allowed. if mount.empty? || mount.globalallow? return true end Puppet.err _("Denying %{method} request for %{desc} on fileserver mount '%{mount_name}'. Use of auth directives for 'fileserver.conf' mount points is no longer supported. Remove these directives and use the 'auth.conf' file instead for access control.") % { method: request.method, desc: request.description, mount_name: mount.name } return false end # Find our key using the fileserver. def find(request) mount, relative_path = configuration.split_path(request) return nil unless mount # The mount checks to see if the file exists, and returns nil # if not. return nil unless path = mount.find(relative_path, request) path2instance(request, path) end # Search for files. This returns an array rather than a single # file. def search(request) mount, relative_path = configuration.split_path(request) unless mount and paths = mount.search(relative_path, request) Puppet.info _("Could not find filesystem info for file '%{request}' in environment %{env}") % { request: request.key, env: request.environment } return nil end path2instances(request, *paths) end private # Our fileserver configuration, if needed. def configuration Puppet::FileServing::Configuration.configuration end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/generic_http.rb�������������������������������������������������0000644�0052762�0001160�00000000631�13417161721�022237� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/file_serving/terminus_helper' require 'puppet/util/http_proxy' class Puppet::Indirector::GenericHttp < Puppet::Indirector::Terminus desc "Retrieve data from a remote HTTP server." class <<self attr_accessor :http_method end def find(request) uri = URI(request.uri) method = self.class.http_method Puppet::Util::HttpProxy.request_with_redirects(uri,method) end end �������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/hiera.rb��������������������������������������������������������0000644�0052762�0001160�00000005603�13417161721�020660� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/terminus' require 'hiera/scope' class Puppet::Indirector::Hiera < Puppet::Indirector::Terminus def initialize(*args) if ! Puppet.features.hiera? #TRANSLATORS "Hiera" is the name of a code library and should not be translated raise _("Hiera terminus not supported without hiera library") end super end if defined?(::Psych::SyntaxError) DataBindingExceptions = [::StandardError, ::Psych::SyntaxError] else DataBindingExceptions = [::StandardError] end def find(request) not_found = Object.new options = request.options Puppet.debug { "Performing a hiera indirector lookup of #{request.key} with options #{options.inspect}" } value = hiera.lookup(request.key, not_found, Hiera::Scope.new(options[:variables]), nil, convert_merge(options[:merge])) throw :no_such_key if value.equal?(not_found) value rescue *DataBindingExceptions => detail error = Puppet::DataBinding::LookupError.new("DataBinding 'hiera': #{detail.message}") error.set_backtrace(detail.backtrace) raise error end private # Converts a lookup 'merge' parameter argument into a Hiera 'resolution_type' argument. # # @param merge [String,Hash,nil] The lookup 'merge' argument # @return [Symbol,Hash,nil] The Hiera 'resolution_type' def convert_merge(merge) case merge when nil when 'first' # Nil is OK. Defaults to Hiera :priority nil when Puppet::Pops::MergeStrategy convert_merge(merge.configuration) when 'unique' # Equivalent to Hiera :array :array when 'hash' # Equivalent to Hiera :hash with default :native merge behavior. A Hash must be passed here # to override possible Hiera deep merge config settings. { :behavior => :native } when 'deep' # Equivalent to Hiera :hash with :deeper merge behavior. { :behavior => :deeper } when Hash strategy = merge['strategy'] if strategy == 'deep' result = { :behavior => :deeper } # Remaining entries must have symbolic keys merge.each_pair { |k,v| result[k.to_sym] = v unless k == 'strategy' } result else convert_merge(strategy) end else #TRANSLATORS "merge" is a parameter name and should not be translated raise Puppet::DataBinding::LookupError, _("Unrecognized value for request 'merge' parameter: '%{merge}'") % { merge: merge } end end def self.hiera_config hiera_config = Puppet.settings[:hiera_config] config = {} if Puppet::FileSystem.exist?(hiera_config) config = Hiera::Config.load(hiera_config) else Puppet.warning _("Config file %{hiera_config} not found, using Hiera defaults") % { hiera_config: hiera_config } end config[:logger] = 'puppet' config end def self.hiera @hiera ||= Hiera.new(:config => hiera_config) end def hiera self.class.hiera end end �����������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/json.rb���������������������������������������������������������0000644�0052762�0001160�00000004751�13417161721�020544� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/terminus' require 'puppet/util' # The base class for JSON indirection terminus implementations. # # This should generally be preferred to the YAML base for any future # implementations, since it is faster and can load untrusted data safely. class Puppet::Indirector::JSON < Puppet::Indirector::Terminus def find(request) load_json_from_file(path(request.key), request.key) end def save(request) filename = path(request.key) FileUtils.mkdir_p(File.dirname(filename)) Puppet::Util.replace_file(filename, 0660) {|f| f.print to_json(request.instance).force_encoding(Encoding::BINARY) } rescue TypeError => detail Puppet.log_exception(detail, _("Could not save %{json} %{request}: %{detail}") % { json: self.name, request: request.key, detail: detail }) end def destroy(request) Puppet::FileSystem.unlink(path(request.key)) rescue => detail unless detail.is_a? Errno::ENOENT raise Puppet::Error, _("Could not destroy %{json} %{request}: %{detail}") % { json: self.name, request: request.key, detail: detail }, detail.backtrace end 1 # emulate success... end def search(request) Dir.glob(path(request.key)).collect do |file| load_json_from_file(file, request.key) end end # Return the path to a given node's file. def path(name, ext = '.json') if name =~ Puppet::Indirector::BadNameRegexp then Puppet.crit(_("directory traversal detected in %{json}: %{name}") % { json: self.class, name: name.inspect }) raise ArgumentError, _("invalid key") end base = Puppet.run_mode.master? ? Puppet[:server_datadir] : Puppet[:client_datadir] File.join(base, self.class.indirection_name.to_s, name.to_s + ext) end private def load_json_from_file(file, key) json = nil begin json = Puppet::FileSystem.read(file, :encoding => Encoding::BINARY) rescue Errno::ENOENT return nil rescue => detail raise Puppet::Error, _("Could not read JSON data for %{name} %{key}: %{detail}") % { name: indirection.name, key: key, detail: detail }, detail.backtrace end begin return from_json(json) rescue => detail raise Puppet::Error, _("Could not parse JSON data for %{name} %{key}: %{detail}") % { name: indirection.name, key: key, detail: detail }, detail.backtrace end end def from_json(text) model.convert_from('json', text.force_encoding(Encoding::UTF_8)) end def to_json(object) object.render('json') end end �����������������������puppet-5.5.10/lib/puppet/indirector/key/������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020034� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/key/memory.rb���������������������������������������������������0000644�0052762�0001160�00000000275�13417161721�021670� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/ssl/key' require 'puppet/indirector/memory' class Puppet::SSL::Key::Memory < Puppet::Indirector::Memory desc "Store keys in memory. This is used for testing puppet." end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/key/ca.rb�������������������������������������������������������0000644�0052762�0001160�00000000572�13417161721�020743� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/ssl_file' require 'puppet/ssl/key' class Puppet::SSL::Key::Ca < Puppet::Indirector::SslFile desc "Manage the CA's private key on disk. This terminus works with the CA key *only*, because that's the only key that the CA ever interacts with." store_in :privatekeydir store_ca_at :cakey def allow_remote_requests? false end end ��������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/key/disabled_ca.rb����������������������������������������������0000644�0052762�0001160�00000001211�13417161721�022561� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/code' require 'puppet/ssl/key' class Puppet::SSL::Key::DisabledCa < Puppet::Indirector::Code desc "Manage the CA private key, but reject any remote access to the SSL data store. Used when a master has an explicitly disabled CA to prevent clients getting confusing 'success' behaviour." def initialize @file = Puppet::SSL::Key.indirection.terminus(:file) end [:find, :head, :search, :save, :destroy].each do |name| define_method(name) do |request| if request.remote? raise Puppet::Error, _("this master is not a CA") else @file.send(name, request) end end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/key/file.rb�����������������������������������������������������0000644�0052762�0001160�00000002627�13417161721�021302� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/ssl_file' require 'puppet/ssl/key' class Puppet::SSL::Key::File < Puppet::Indirector::SslFile desc "Manage SSL private and public keys on disk." store_in :privatekeydir store_ca_at :cakey def allow_remote_requests? false end # Where should we store the public key? def public_key_path(name) if ca?(name) Puppet[:capub] else File.join(Puppet[:publickeydir], name.to_s + ".pem") end end # Remove the public key, in addition to the private key def destroy(request) super key_path = Puppet::FileSystem.pathname(public_key_path(request.key)) return unless Puppet::FileSystem.exist?(key_path) begin Puppet::FileSystem.unlink(key_path) rescue => detail raise Puppet::Error, _("Could not remove %{request} public key: %{detail}") % { request: request.key, detail: detail }, detail.backtrace end end # Save the public key, in addition to the private key. def save(request) super begin # RFC 1421 states PEM is 7-bit ASCII https://tools.ietf.org/html/rfc1421 Puppet.settings.setting(:publickeydir).open_file(public_key_path(request.key), 'w:ASCII') do |f| f.print request.instance.content.public_key.to_pem end rescue => detail raise Puppet::Error, _("Could not write %{request}: %{detail}") % { request: request.key, detail: detail }, detail.backtrace end end end ���������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/memory.rb�������������������������������������������������������0000644�0052762�0001160�00000001374�13417161721�021101� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/terminus' # Manage a memory-cached list of instances. class Puppet::Indirector::Memory < Puppet::Indirector::Terminus def initialize clear end def clear @instances = {} end def destroy(request) raise ArgumentError.new(_("Could not find %{request} to destroy") % { request: request.key }) unless @instances.include?(request.key) @instances.delete(request.key) end def find(request) @instances[request.key] end def search(request) found_keys = @instances.keys.find_all { |key| key.include?(request.key) } found_keys.collect { |key| @instances[key] } end def head(request) not find(request).nil? end def save(request) @instances[request.key] = request.instance end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/msgpack.rb������������������������������������������������������0000644�0052762�0001160�00000005336�13417161721�021220� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/terminus' require 'puppet/util' # The base class for MessagePack indirection terminus implementations. # # This should generally be preferred to the PSON base for any future # implementations, since it is ~ 30 times faster class Puppet::Indirector::Msgpack < Puppet::Indirector::Terminus def initialize(*args) if ! Puppet.features.msgpack? raise _("MessagePack terminus not supported without msgpack library") end super end def find(request) load_msgpack_from_file(path(request.key), request.key) end def save(request) filename = path(request.key) FileUtils.mkdir_p(File.dirname(filename)) Puppet::Util.replace_file(filename, 0660) {|f| f.print to_msgpack(request.instance) } rescue TypeError => detail Puppet.log_exception(detail, _("Could not save %{name} %{request}: %{detail}") % { name: self.name, request: request.key, detail: detail }) end def destroy(request) Puppet::FileSystem.unlink(path(request.key)) rescue => detail unless detail.is_a? Errno::ENOENT raise Puppet::Error, _("Could not destroy %{name} %{request}: %{detail}") % { name: self.name, request: request.key, detail: detail }, detail.backtrace end 1 # emulate success... end def search(request) Dir.glob(path(request.key)).collect do |file| load_msgpack_from_file(file, request.key) end end # Return the path to a given node's file. def path(name, ext = '.msgpack') if name =~ Puppet::Indirector::BadNameRegexp then Puppet.crit(_("directory traversal detected in %{indirection}: %{name}") % { indirection: self.class, name: name.inspect }) raise ArgumentError, _("invalid key") end base = Puppet.run_mode.master? ? Puppet[:server_datadir] : Puppet[:client_datadir] File.join(base, self.class.indirection_name.to_s, name.to_s + ext) end private def load_msgpack_from_file(file, key) msgpack = nil begin msgpack = Puppet::FileSystem.read(file, :encoding => 'utf-8') rescue Errno::ENOENT return nil rescue => detail #TRANSLATORS "MessagePack" is a program name and should not be translated raise Puppet::Error, _("Could not read MessagePack data for %{indirection} %{key}: %{detail}") % { indirection: indirection.name, key: key, detail: detail }, detail.backtrace end begin return from_msgpack(msgpack) rescue => detail raise Puppet::Error, _("Could not parse MessagePack data for %{indirection} %{key}: %{detail}") % { indirection: indirection.name, key: key, detail: detail }, detail.backtrace end end def from_msgpack(text) model.convert_from('msgpack', text) end def to_msgpack(object) object.render('msgpack') end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/node/�����������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020171� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/node/memory.rb��������������������������������������������������0000644�0052762�0001160�00000000663�13417161721�022026� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/node' require 'puppet/indirector/memory' class Puppet::Node::Memory < Puppet::Indirector::Memory desc "Keep track of nodes in memory but nowhere else. This is used for one-time compiles, such as what the stand-alone `puppet` does. To use this terminus, you must load it with the data you want it to contain; it is only useful for developers and should generally not be chosen by a normal user." end �����������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/node/msgpack.rb�������������������������������������������������0000644�0052762�0001160�00000000363�13417161721�022140� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/node' require 'puppet/indirector/msgpack' class Puppet::Node::Msgpack < Puppet::Indirector::Msgpack desc "Store node information as flat files, serialized using MessagePack, or deserialize stored MessagePack nodes." end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/node/plain.rb���������������������������������������������������0000644�0052762�0001160�00000001343�13417161721�021615� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/node' require 'puppet/indirector/plain' class Puppet::Node::Plain < Puppet::Indirector::Plain desc "Always return an empty node object. Assumes you keep track of nodes in flat file manifests. You should use it when you don't have some other, functional source you want to use, as the compiler will not work without a valid node terminus. Note that class is responsible for merging the node's facts into the node instance before it is returned." # Just return an empty node. def find(request) node = super node.environment = request.environment facts = request.options[:facts].is_a?(Puppet::Node::Facts) ? request.options[:facts] : nil node.fact_merge(facts) node end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/node/rest.rb����������������������������������������������������0000644�0052762�0001160�00000000341�13417161721�021464� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/node' require 'puppet/indirector/rest' class Puppet::Node::Rest < Puppet::Indirector::REST desc "Get a node via REST. Puppet agent uses this to allow the puppet master to override its environment." end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/node/store_configs.rb�������������������������������������������0000644�0052762�0001160�00000000343�13417161721�023355� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/store_configs' require 'puppet/node' class Puppet::Node::StoreConfigs < Puppet::Indirector::StoreConfigs desc %q{Part of the "storeconfigs" feature. Should not be directly set by end users.} end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/node/exec.rb����������������������������������������������������0000644�0052762�0001160�00000004111�13417161721�021432� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/node' require 'puppet/indirector/exec' class Puppet::Node::Exec < Puppet::Indirector::Exec desc "Call an external program to get node information. See the [External Nodes](https://puppet.com/docs/puppet/latest/lang_write_functions_in_puppet.html) page for more information." include Puppet::Util def command command = Puppet[:external_nodes] raise ArgumentError, _("You must set the 'external_nodes' parameter to use the external node terminus") unless command != _("none") command.split end # Look for external node definitions. def find(request) output = super or return nil # Translate the output to ruby. result = translate(request.key, output) facts = request.options[:facts].is_a?(Puppet::Node::Facts) ? request.options[:facts] : nil # Set the requested environment if it wasn't overridden # If we don't do this it gets set to the local default result[:environment] ||= request.environment create_node(request.key, result, facts) end private # Proxy the execution, so it's easier to test. def execute(command, arguments) Puppet::Util::Execution.execute(command,arguments) end # Turn our outputted objects into a Puppet::Node instance. def create_node(name, result, facts = nil) node = Puppet::Node.new(name) set = false [:parameters, :classes, :environment].each do |param| if value = result[param] node.send(param.to_s + "=", value) set = true end end node.fact_merge(facts) node end # Translate the yaml string into Ruby objects. def translate(name, output) YAML.load(output).inject({}) do |hash, data| case data[0] when String hash[data[0].intern] = data[1] when Symbol hash[data[0]] = data[1] else raise Puppet::Error, _("key is a %{klass}, not a string or symbol") % { klass: data[0].class } end hash end rescue => detail raise Puppet::Error, _("Could not load external node results for %{name}: %{detail}") % { name: name, detail: detail }, detail.backtrace end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/node/ldap.rb����������������������������������������������������0000644�0052762�0001160�00000020316�13417161721�021433� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/node' require 'puppet/indirector/ldap' class Puppet::Node::Ldap < Puppet::Indirector::Ldap desc "Search in LDAP for node configuration information. See the [LDAP Nodes](https://puppet.com/docs/puppet/latest/nodes_ldap.html) page for more information. This will first search for whatever the certificate name is, then (if that name contains a `.`) for the short name, then `default`. Requires ruby-ldap with MRI ruby or jruby-ldap with puppetserver/jruby" # The attributes that Puppet class information is stored in. def class_attributes Puppet[:ldapclassattrs].split(/\s*,\s*/) end # Separate this out so it's relatively atomic. It's tempting to call # process instead of name2hash() here, but it ends up being # difficult to test because all exceptions get caught by ldapsearch. # LAK:NOTE Unfortunately, the ldap support is too stupid to throw anything # but LDAP::ResultError, even on bad connections, so we are rough-handed # with our error handling. def name2hash(name) info = nil ldapsearch(search_filter(name)) { |entry| info = entry2hash(entry) } info end # Look for our node in ldap. def find(request) names = [request.key] names << request.key.sub(/\..+/, '') if request.key.include?(".") # we assume it's an fqdn names << "default" facts = request.options[:facts].is_a?(Puppet::Node::Facts) ? request.options[:facts] : nil node = nil names.each do |name| next unless info = name2hash(name) merge_parent(info) if info[:parent] info[:environment] ||= request.environment node = info2node(request.key, info, facts) break end node end # Find more than one node. LAK:NOTE This is a bit of a clumsy API, because the 'search' # method currently *requires* a key. It seems appropriate in some cases but not others, # and I don't really know how to get rid of it as a requirement but allow it when desired. def search(request) if classes = request.options[:class] classes = [classes] unless classes.is_a?(Array) filter = "(&(objectclass=puppetClient)(puppetclass=" + classes.join(")(puppetclass=") + "))" else filter = "(objectclass=puppetClient)" end infos = [] ldapsearch(filter) { |entry| infos << entry2hash(entry, request.options[:fqdn]) } return infos.collect do |info| merge_parent(info) if info[:parent] info[:environment] ||= request.environment info2node(info[:name], info) end end # The parent attribute, if we have one. def parent_attribute if pattr = Puppet[:ldapparentattr] and ! pattr.empty? pattr else nil end end # The attributes that Puppet will stack as array over the full # hierarchy. def stacked_attributes Puppet[:ldapstackedattrs].split(/\s*,\s*/) end # Convert the found entry into a simple hash. def entry2hash(entry, fqdn = false) result = {} cn = entry.dn[ /cn\s*=\s*([^,\s]+)/i,1] dcs = entry.dn.scan(/dc\s*=\s*([^,\s]+)/i) result[:name] = fqdn ? ([cn]+dcs).join('.') : cn result[:parent] = get_parent_from_entry(entry) if parent_attribute result[:classes] = get_classes_from_entry(entry) result[:stacked] = get_stacked_values_from_entry(entry) result[:parameters] = get_parameters_from_entry(entry) # delete from parameters to prevent "Already declered variable $environment" error result[:environment] = result[:parameters].delete(:environment) if result[:parameters][:environment] result[:stacked_parameters] = {} if result[:stacked] result[:stacked].each do |value| param = value.split('=', 2) result[:stacked_parameters][param[0]] = param[1] end end if result[:stacked_parameters] result[:stacked_parameters].each do |param, value| result[:parameters][param] = value unless result[:parameters].include?(param) end end result[:parameters] = convert_parameters(result[:parameters]) result end # Default to all attributes. def search_attributes ldapattrs = Puppet[:ldapattrs] # results in everything getting returned return nil if ldapattrs == "all" search_attrs = class_attributes + ldapattrs.split(/\s*,\s*/) if pattr = parent_attribute search_attrs << pattr end search_attrs end # The ldap search filter to use. def search_filter(name) filter = Puppet[:ldapstring] if filter.include? "%s" # Don't replace the string in-line, since that would hard-code our node # info. filter = filter.gsub('%s', name) end filter end private # Add our hash of ldap information to the node instance. def add_to_node(node, information) node.classes = information[:classes].uniq unless information[:classes].nil? or information[:classes].empty? node.parameters = information[:parameters] unless information[:parameters].nil? or information[:parameters].empty? node.environment = information[:environment] if information[:environment] end def convert_parameters(parameters) result = {} parameters.each do |param, value| if value.is_a?(Array) result[param] = value.collect { |v| convert(v) } else result[param] = convert(value) end end result end # Convert any values if necessary. def convert(value) case value when Integer; value when "true"; true when "false"; false else value end end # Find information for our parent and merge it into the current info. def find_and_merge_parent(parent, information) parent_info = name2hash(parent) || raise(Puppet::Error.new(_("Could not find parent node '%{parent}'") % { parent: parent })) information[:classes] += parent_info[:classes] parent_info[:parameters].each do |param, value| # Specifically test for whether it's set, so false values are handled correctly. information[:parameters][param] = value unless information[:parameters].include?(param) end information[:environment] ||= parent_info[:environment] parent_info[:parent] end # Take a name and a hash, and return a node instance. def info2node(name, info, facts = nil) node = Puppet::Node.new(name) add_to_node(node, info) node.fact_merge(facts) node end def merge_parent(info) parent = info[:parent] # Preload the parent array with the node name. parents = [info[:name]] while parent raise ArgumentError, _("Found loop in LDAP node parents; %{parent} appears twice") % { parent: parent } if parents.include?(parent) parents << parent parent = find_and_merge_parent(parent, info) end info end def get_classes_from_entry(entry) result = class_attributes.inject([]) do |array, attr| if values = entry.vals(attr) values.each do |v| array << v end end array end result.uniq end # Workaround jruby-ldap 0.0.2 missing the #to_hash method # # @see https://github.com/jruby/jruby-ldap/pull/5 # # @param entry [LDAP::Entry] The LDAP::Entry object to convert to a hash # @return [Hash] The hash of the provided LDAP::Entry object with keys # downcased (puppet variables need to start with lowercase char) def ldap_entry_to_hash(entry) h = {} entry.get_attributes.each { |a| h[a.downcase.to_sym] = entry[a] } h[:dn] = [entry.dn] h end def get_parameters_from_entry(entry) stacked_params = stacked_attributes ldap_entry_to_hash(entry).inject({}) do |hash, ary| unless stacked_params.include?(ary[0]) # don't add our stacked parameters to the main param list if ary[1].length == 1 hash[ary[0]] = ary[1].shift else hash[ary[0]] = ary[1] end end hash end end def get_parent_from_entry(entry) pattr = parent_attribute return nil unless values = entry.vals(pattr) if values.length > 1 raise Puppet::Error, _("Node entry %{entry} specifies more than one parent: %{parents}") % { entry: entry.dn, parents: values.inspect } end return(values.empty? ? nil : values.shift) end def get_stacked_values_from_entry(entry) stacked_attributes.inject([]) do |result, attr| if values = entry.vals(attr) result += values end result end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/node/write_only_yaml.rb�����������������������������������������0000644�0052762�0001160�00000002600�13417161721�023724� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/node' require 'puppet/indirector/yaml' # This is a WriteOnlyYaml terminus that exists only for the purpose of being able to write # node cache data that later can be read by the YAML terminus. # The use case this supports is to make it possible to search among the "current nodes" # when Puppet DB (recommended) or other central storage of information is not available. # # @see puppet issue 16753 # @see Puppet::Application::Master#setup_node_cache # @api private # class Puppet::Node::WriteOnlyYaml < Puppet::Indirector::Yaml def initialize #TRANSLATORS 'Puppet::Node::WriteOnlyYaml' is a class and should not be translated message = _('Puppet::Node::WriteOnlyYaml is deprecated and will be removed in a future release of Puppet.') Puppet.warn_once('deprecations', 'Puppet::Node::WriteOnlyYaml', message) super end desc "(Deprecated) Store node information as flat files, serialized using YAML, does not deserialize (write only)." # Overridden to always return nil. This is a write only terminus. # @param [Object] request Ignored. # @return [nil] This implementation always return nil' # @api public def find(request) nil end # Overridden to always return nil. This is a write only terminus. # @param [Object] request Ignored. # @return [nil] This implementation always return nil # @api public def search(request) nil end end ��������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/node/yaml.rb����������������������������������������������������0000644�0052762�0001160�00000000415�13417161721�021453� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/node' require 'puppet/indirector/yaml' class Puppet::Node::Yaml < Puppet::Indirector::Yaml desc "Store node information as flat files, serialized using YAML, or deserialize stored YAML nodes." protected def fix(object) object end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/none.rb���������������������������������������������������������0000644�0052762�0001160�00000000305�13417161721�020521� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/terminus' # A none terminus type, meant to always return nil class Puppet::Indirector::None < Puppet::Indirector::Terminus def find(request) return nil end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/plain.rb��������������������������������������������������������0000644�0052762�0001160�00000000401�13417161721�020662� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/terminus' # An empty terminus type, meant to just return empty objects. class Puppet::Indirector::Plain < Puppet::Indirector::Terminus # Just return nothing. def find(request) indirection.model.new(request.key) end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/report/���������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020557� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/report/msgpack.rb�����������������������������������������������0000644�0052762�0001160�00000000504�13417161721�022523� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/transaction/report' require 'puppet/indirector/msgpack' class Puppet::Transaction::Report::Msgpack < Puppet::Indirector::Msgpack desc "Store last report as a flat file, serialized using MessagePack." # Force report to be saved there def path(name,ext='.msgpack') Puppet[:lastrunreport] end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/report/processor.rb���������������������������������������������0000644�0052762�0001160�00000003207�13417161721�023120� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/transaction/report' require 'puppet/indirector/code' require 'puppet/reports' class Puppet::Transaction::Report::Processor < Puppet::Indirector::Code desc "Puppet's report processor. Processes the report with each of the report types listed in the 'reports' setting." def initialize Puppet.settings.use(:main, :reporting, :metrics) end def save(request) process(request.instance) end def destroy(request) processors do |mod| mod.destroy(request.key) if mod.respond_to?(:destroy) end end private # Process the report with each of the configured report types. # LAK:NOTE This isn't necessarily the best design, but it's backward # compatible and that's good enough for now. def process(report) Puppet.debug "Received report to process from #{report.host}" processors do |mod| Puppet.debug "Processing report from #{report.host} with processor #{mod}" # We have to use a dup because we're including a module in the # report. newrep = report.dup begin newrep.extend(mod) newrep.process rescue => detail Puppet.log_exception(detail, _("Report %{report} failed: %{detail}") % { report: name, detail: detail }) end end end # Handle the parsing of the reports attribute. def reports Puppet[:reports].gsub(/(^\s+)|(\s+$)/, '').split(/\s*,\s*/) end def processors(&blk) return [] if Puppet[:reports] == "none" reports.each do |name| if mod = Puppet::Reports.report(name) yield(mod) else Puppet.warning _("No report named '%{name}'") % { name: name } end end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/report/rest.rb��������������������������������������������������0000644�0052762�0001160�00000002061�13417161721�022053� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/rest' require 'semantic_puppet' class Puppet::Transaction::Report::Rest < Puppet::Indirector::REST desc "Get server report over HTTP via REST." use_server_setting(:report_server) use_port_setting(:report_port) use_srv_service(:report) def handle_response(request, response) if !response.is_a?(Net::HTTPSuccess) server_version = response[Puppet::Network::HTTP::HEADER_PUPPET_VERSION] if server_version && SemanticPuppet::Version.parse(server_version).major < Puppet::Indirector::REST::MAJOR_VERSION_JSON_DEFAULT && Puppet[:preferred_serialization_format] != 'pson' format = Puppet[:preferred_serialization_format] raise Puppet::Error.new(_("Server version %{version} does not accept reports in '%{format}', use `preferred_serialization_format=pson`") % {version: server_version, format: format}) end end end private def deserialize_save(content_type, body) format = Puppet::Network::FormatHandler.format_for(content_type) format.intern(Array, body) end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/report/yaml.rb��������������������������������������������������0000644�0052762�0001160�00000000461�13417161721�022042� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/transaction/report' require 'puppet/indirector/yaml' class Puppet::Transaction::Report::Yaml < Puppet::Indirector::Yaml desc "Store last report as a flat file, serialized using YAML." # Force report to be saved there def path(name,ext='.yaml') Puppet[:lastrunreport] end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/resource/�������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�021073� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/resource/ral.rb�������������������������������������������������0000644�0052762�0001160�00000003427�13417161721�022177� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/resource/validator' class Puppet::Resource::Ral < Puppet::Indirector::Code include Puppet::Resource::Validator desc "Manipulate resources with the resource abstraction layer. Only used internally." def allow_remote_requests? false end def find( request ) # find by name res = type(request).instances.find { |o| o.name == resource_name(request) } res ||= type(request).new(:name => resource_name(request), :audit => type(request).properties.collect { |s| s.name }) res.to_resource end def search( request ) conditions = request.options.dup conditions[:name] = resource_name(request) if resource_name(request) type(request).instances.map do |res| res.to_resource end.find_all do |res| conditions.all? {|property, value| res.to_resource[property].to_s == value.to_s} end.sort do |a,b| a.title <=> b.title end end def save( request ) # In RAL-land, to "save" means to actually try to change machine state res = request.instance ral_res = res.to_ral catalog = Puppet::Resource::Catalog.new(nil, request.environment) catalog.add_resource ral_res transaction = catalog.apply [ral_res.to_resource, transaction.report] end private # {type,resource}_name: the resource name may contain slashes: # File["/etc/hosts"]. To handle, assume the type name does # _not_ have any slashes in it, and split only on the first. def type_name( request ) request.key.split('/', 2)[0] end def resource_name( request ) name = request.key.split('/', 2)[1] name unless name == "" end def type( request ) Puppet::Type.type(type_name(request)) or raise Puppet::Error, _("Could not find type %{request_type}") % { request_type: type_name(request) } end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/resource/store_configs.rb���������������������������������������0000644�0052762�0001160�00000000523�13417161721�024257� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/store_configs' require 'puppet/indirector/resource/validator' class Puppet::Resource::StoreConfigs < Puppet::Indirector::StoreConfigs include Puppet::Resource::Validator desc %q{Part of the "storeconfigs" feature. Should not be directly set by end users.} def allow_remote_requests? false end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/resource/validator.rb�������������������������������������������0000644�0052762�0001160�00000000477�13417161721�023410� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Resource::Validator def validate_key(request) type, title = request.key.split('/', 2) unless type.downcase == request.instance.type.downcase and title == request.instance.title raise Puppet::Indirector::ValidationError, _("Resource instance does not match request key") end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/status.rb�������������������������������������������������������0000644�0052762�0001160�00000000114�13417161721�021103� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# A stub class, so our constants work. class Puppet::Indirector::Status end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/status/���������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020567� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/status/local.rb�������������������������������������������������0000644�0052762�0001160�00000000403�13417161721�022176� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/status' class Puppet::Indirector::Status::Local < Puppet::Indirector::Code desc "Get status locally. Only used internally." def find( *anything ) status = model.new status.version= Puppet.version status end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/status/rest.rb��������������������������������������������������0000644�0052762�0001160�00000000415�13417161721�022064� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/status' require 'puppet/indirector/rest' class Puppet::Indirector::Status::Rest < Puppet::Indirector::REST desc "Get puppet master's status via REST. Useful because it tests the health of both the web server and the indirector." end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/store_configs.rb������������������������������������������������0000644�0052762�0001160�00000001117�13417161721�022430� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Indirector::StoreConfigs < Puppet::Indirector::Terminus def initialize super # This will raise if the indirection can't be found, so we can assume it # is always set to a valid instance from here on in. @target = indirection.terminus Puppet[:storeconfigs_backend] end attr_reader :target def head(request) target.head request end def find(request) target.find request end def search(request) target.search request end def save(request) target.save request end def destroy(request) target.save request end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/certificate_revocation_list/������������������������������������0000755�0052762�0001160�00000000000�13417162176�025012� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/certificate_revocation_list/ca.rb�������������������������������0000644�0052762�0001160�00000000400�13417161721�025707� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/ssl_file' require 'puppet/ssl/certificate_revocation_list' class Puppet::SSL::CertificateRevocationList::Ca < Puppet::Indirector::SslFile desc "Manage the CA collection of certificate requests on disk." store_at :cacrl end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/certificate_revocation_list/disabled_ca.rb����������������������0000644�0052762�0001160�00000001333�13417161721�027544� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/code' require 'puppet/ssl/certificate_revocation_list' class Puppet::SSL::CertificateRevocationList::DisabledCa < Puppet::Indirector::Code desc "Manage SSL certificate revocation lists, but reject any remote access to the SSL data store. Used when a master has an explicitly disabled CA to prevent clients getting confusing 'success' behaviour." def initialize @file = Puppet::SSL::CertificateRevocationList.indirection.terminus(:file) end [:find, :head, :search, :save, :destroy].each do |name| define_method(name) do |request| if request.remote? raise Puppet::Error, _("this master is not a CA") else @file.send(name, request) end end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/certificate_revocation_list/file.rb�����������������������������0000644�0052762�0001160�00000000371�13417161721�026252� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/ssl_file' require 'puppet/ssl/certificate_revocation_list' class Puppet::SSL::CertificateRevocationList::File < Puppet::Indirector::SslFile desc "Manage the global certificate revocation list." store_at :hostcrl end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/certificate_revocation_list/rest.rb�����������������������������0000644�0052762�0001160�00000001205�13417161721�026305� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/ssl/certificate_revocation_list' require 'puppet/indirector/rest' class Puppet::SSL::CertificateRevocationList::Rest < Puppet::Indirector::REST desc "Find and save certificate revocation lists over HTTP via REST." use_server_setting(:ca_server) use_port_setting(:ca_port) use_srv_service(:ca) def find(request) if !Puppet::FileSystem.exist?(Puppet[:hostcrl]) msg = "Disable certificate revocation checking when fetching the CRL and no CRL is present" overrides = {certificate_revocation: false} Puppet.override(overrides, msg) do super end else super end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/certificate_status.rb�������������������������������������������0000644�0052762�0001160�00000000115�13417161721�023446� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector' class Puppet::Indirector::CertificateStatus end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/certificate_status/���������������������������������������������0000755�0052762�0001160�00000000000�13417162176�023131� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/certificate_status/file.rb��������������������������������������0000644�0052762�0001160�00000005772�13417161721�024403� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet' require 'puppet/indirector/certificate_status' require 'puppet/ssl/certificate' require 'puppet/ssl/certificate_authority' require 'puppet/ssl/certificate_request' require 'puppet/ssl/host' require 'puppet/ssl/key' class Puppet::Indirector::CertificateStatus::File < Puppet::Indirector::Code desc "Manipulate certificate status on the local filesystem. Only functional on the CA." def ca raise ArgumentError, _("This process is not configured as a certificate authority") unless Puppet::SSL::CertificateAuthority.ca? Puppet::SSL::CertificateAuthority.new end def destroy(request) deleted = [] [ Puppet::SSL::Certificate, Puppet::SSL::CertificateRequest, Puppet::SSL::Key, ].collect do |part| if part.indirection.destroy(request.key) deleted << "#{part}" end end return _("Nothing was deleted") if deleted.empty? _("Deleted for %{request}: %{deleted}") % { request: request.key, deleted: deleted.join(", ") } end def save(request) if request.instance.desired_state == "signed" certificate_request = Puppet::SSL::CertificateRequest.indirection.find(request.key) raise Puppet::Error, _("Cannot sign for host %{request} without a certificate request") % { request: request.key } unless certificate_request ca.sign(request.key) elsif request.instance.desired_state == "revoked" certificate = Puppet::SSL::Certificate.indirection.find(request.key) raise Puppet::Error, _("Cannot revoke host %{request} because has it doesn't have a signed certificate") % { request: request.key } unless certificate ca.revoke(request.key) else raise Puppet::Error, _("State %{state} invalid; Must specify desired state of 'signed' or 'revoked' for host %{request}") % { state: request.instance.desired_state, request: request.key } end end def search(request) # Support historic interface wherein users provide classes to filter # the search. When used via the REST API, the arguments must be # a Symbol or an Array containing Symbol objects. klasses = case request.options[:for] when Class [request.options[:for]] when nil [ Puppet::SSL::Certificate, Puppet::SSL::CertificateRequest, Puppet::SSL::Key, ] else [request.options[:for]].flatten.map do |klassname| indirection.class.model(klassname.to_sym) end end klasses.collect do |klass| klass.indirection.search(request.key, request.options) end.flatten.collect do |result| result.name end.uniq.collect(&Puppet::SSL::Host.method(:new)) end def find(request) ssl_host = Puppet::SSL::Host.new(request.key) public_key = Puppet::SSL::Certificate.indirection.find(request.key) if ssl_host.certificate_request || public_key ssl_host else nil end end def validate_key(request) # We only use desired_state from the instance and use request.key # otherwise, so the name does not need to match end end ������puppet-5.5.10/lib/puppet/indirector/certificate_status/rest.rb��������������������������������������0000644�0052762�0001160�00000000555�13417161721�024433� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/ssl/host' require 'puppet/indirector/rest' require 'puppet/indirector/certificate_status' class Puppet::Indirector::CertificateStatus::Rest < Puppet::Indirector::REST desc "Sign, revoke, search for, or clean certificates & certificate requests over HTTP." use_server_setting(:ca_server) use_port_setting(:ca_port) use_srv_service(:ca) end ���������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/indirection.rb��������������������������������������������������0000644�0052762�0001160�00000025772�13417161721�022110� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/docs' require 'puppet/util/profiler' require 'puppet/util/methodhelper' require 'puppet/indirector/envelope' require 'puppet/indirector/request' # The class that connects functional classes with their different collection # back-ends. Each indirection has a set of associated terminus classes, # each of which is a subclass of Puppet::Indirector::Terminus. class Puppet::Indirector::Indirection include Puppet::Util::MethodHelper include Puppet::Util::Docs attr_accessor :name, :model attr_reader :termini @@indirections = [] # Find an indirection by name. This is provided so that Terminus classes # can specifically hook up with the indirections they are associated with. def self.instance(name) @@indirections.find { |i| i.name == name } end # Return a list of all known indirections. Used to generate the # reference. def self.instances @@indirections.collect { |i| i.name } end # Find an indirected model by name. This is provided so that Terminus classes # can specifically hook up with the indirections they are associated with. def self.model(name) return nil unless match = @@indirections.find { |i| i.name == name } match.model end # Create and return our cache terminus. def cache raise Puppet::DevError, _("Tried to cache when no cache class was set") unless cache_class terminus(cache_class) end # Should we use a cache? def cache? cache_class ? true : false end attr_reader :cache_class # Define a terminus class to be used for caching. def cache_class=(class_name) validate_terminus_class(class_name) if class_name @cache_class = class_name end # This is only used for testing. def delete @@indirections.delete(self) if @@indirections.include?(self) end # Set the time-to-live for instances created through this indirection. def ttl=(value) #TRANSLATORS "TTL" stands for "time to live" and refers to a duration of time raise ArgumentError, _("Indirection TTL must be an integer") unless value.is_a?(Integer) @ttl = value end # Default to the runinterval for the ttl. def ttl @ttl ||= Puppet[:runinterval] end # Calculate the expiration date for a returned instance. def expiration Time.now + ttl end # Generate the full doc string. def doc text = "" text << scrub(@doc) << "\n\n" if @doc text << "* **Indirected Class**: `#{@indirected_class}`\n"; if terminus_setting text << "* **Terminus Setting**: #{terminus_setting}\n" end text end def initialize(model, name, options = {}) @model = model @name = name @termini = {} @cache_class = nil @terminus_class = nil raise(ArgumentError, _("Indirection %{name} is already defined") % { name: @name }) if @@indirections.find { |i| i.name == @name } @@indirections << self @indirected_class = options.delete(:indirected_class) if mod = options[:extend] extend(mod) options.delete(:extend) end # This is currently only used for cache_class and terminus_class. set_options(options) end # Set up our request object. def request(*args) Puppet::Indirector::Request.new(self.name, *args) end # Return the singleton terminus for this indirection. def terminus(terminus_name = nil) # Get the name of the terminus. raise Puppet::DevError, _("No terminus specified for %{name}; cannot redirect") % { name: self.name } unless terminus_name ||= terminus_class termini[terminus_name] ||= make_terminus(terminus_name) end # This can be used to select the terminus class. attr_accessor :terminus_setting # Determine the terminus class. def terminus_class unless @terminus_class if setting = self.terminus_setting self.terminus_class = Puppet.settings[setting] else raise Puppet::DevError, _("No terminus class nor terminus setting was provided for indirection %{name}") % { name: self.name} end end @terminus_class end def reset_terminus_class @terminus_class = nil end # Specify the terminus class to use. def terminus_class=(klass) validate_terminus_class(klass) @terminus_class = klass end # This is used by terminus_class= and cache=. def validate_terminus_class(terminus_class) unless terminus_class and terminus_class.to_s != "" raise ArgumentError, _("Invalid terminus name %{terminus_class}") % { terminus_class: terminus_class.inspect } end unless Puppet::Indirector::Terminus.terminus_class(self.name, terminus_class) raise ArgumentError, _("Could not find terminus %{terminus_class} for indirection %{name}") % { terminus_class: terminus_class, name: self.name } end end # Expire a cached object, if one is cached. Note that we don't actually # remove it, we expire it and write it back out to disk. This way people # can still use the expired object if they want. def expire(key, options={}) request = request(:expire, key, nil, options) return nil unless cache? && !request.ignore_cache_save? return nil unless instance = cache.find(request(:find, key, nil, options)) Puppet.info _("Expiring the %{cache} cache of %{instance}") % { cache: self.name, instance: instance.name } # Set an expiration date in the past instance.expiration = Time.now - 60 cache.save(request(:save, nil, instance, options)) end def allow_remote_requests? terminus.allow_remote_requests? end # Search for an instance in the appropriate terminus, caching the # results if caching is configured.. def find(key, options={}) request = request(:find, key, nil, options) terminus = prepare(request) result = find_in_cache(request) if not result.nil? result elsif request.ignore_terminus? nil else # Otherwise, return the result from the terminus, caching if # appropriate. result = terminus.find(request) if not result.nil? result.expiration ||= self.expiration if result.respond_to?(:expiration) if cache? && !request.ignore_cache_save? Puppet.info _("Caching %{indirection} for %{request}") % { indirection: self.name, request: request.key } begin cache.save request(:save, key, result, options) rescue => detail Puppet.log_exception(detail) raise detail end end filtered = result if terminus.respond_to?(:filter) Puppet::Util::Profiler.profile(_("Filtered result for %{indirection} %{request}") % { indirection: self.name, request: request.key }, [:indirector, :filter, self.name, request.key]) do begin filtered = terminus.filter(result) rescue Puppet::Error => detail Puppet.log_exception(detail) raise detail end end end filtered end end end # Search for an instance in the appropriate terminus, and return a # boolean indicating whether the instance was found. def head(key, options={}) request = request(:head, key, nil, options) terminus = prepare(request) # Look in the cache first, then in the terminus. Force the result # to be a boolean. !!(find_in_cache(request) || terminus.head(request)) end def find_in_cache(request) # See if our instance is in the cache and up to date. return nil unless cache? and ! request.ignore_cache? and cached = cache.find(request) if cached.expired? Puppet.info _("Not using expired %{indirection} for %{request} from cache; expired at %{expiration}") % { indirection: self.name, request: request.key, expiration: cached.expiration } return nil end Puppet.debug "Using cached #{self.name} for #{request.key}" cached rescue => detail Puppet.log_exception(detail, _("Cached %{indirection} for %{request} failed: %{detail}") % { indirection: self.name, request: request.key, detail: detail }) nil end # Remove something via the terminus. def destroy(key, options={}) request = request(:destroy, key, nil, options) terminus = prepare(request) result = terminus.destroy(request) if cache? and cache.find(request(:find, key, nil, options)) # Reuse the existing request, since it's equivalent. cache.destroy(request) end result end # Search for more than one instance. Should always return an array. def search(key, options={}) request = request(:search, key, nil, options) terminus = prepare(request) if result = terminus.search(request) raise Puppet::DevError, _("Search results from terminus %{terminus_name} are not an array") % { terminus_name: terminus.name } unless result.is_a?(Array) result.each do |instance| next unless instance.respond_to? :expiration instance.expiration ||= self.expiration end return result end end # Save the instance in the appropriate terminus. This method is # normally an instance method on the indirected class. def save(instance, key = nil, options={}) request = request(:save, key, instance, options) terminus = prepare(request) result = terminus.save(request) # If caching is enabled, save our document there cache.save(request) if cache? && !request.ignore_cache_save? result end private # Check authorization if there's a hook available; fail if there is one # and it returns false. def check_authorization(request, terminus) # At this point, we're assuming authorization makes no sense without # client information. return unless request.node # This is only to authorize via a terminus-specific authorization hook. return unless terminus.respond_to?(:authorized?) unless terminus.authorized?(request) msg = if request.options.empty? _("Not authorized to call %{method} on %{description}") % { method: request.method, description: request.description } else _("Not authorized to call %{method} on %{description} with %{option}") % { method: request.method, description: request.description, option: request.options.inspect } end raise ArgumentError, msg end end # Pick the appropriate terminus, check the request's authorization, and return it. # @param [Puppet::Indirector::Request] request instance # @return [Puppet::Indirector::Terminus] terminus instance (usually a subclass # of Puppet::Indirector::Terminus) for this request def prepare(request) # Pick our terminus. terminus_name = terminus_class dest_terminus = terminus(terminus_name) check_authorization(request, dest_terminus) dest_terminus.validate(request) dest_terminus end # Create a new terminus instance. def make_terminus(terminus_class) # Load our terminus class. unless klass = Puppet::Indirector::Terminus.terminus_class(self.name, terminus_class) raise ArgumentError, _("Could not find terminus %{terminus_class} for indirection %{indirection}") % { terminus_class: terminus_class, indirection: self.name } end klass.new end end ������puppet-5.5.10/lib/puppet/indirector/ldap.rb���������������������������������������������������������0000644�0052762�0001160�00000005037�13417161721�020511� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/terminus' require 'puppet/util/ldap/connection' class Puppet::Indirector::Ldap < Puppet::Indirector::Terminus def initialize #TRANSLATORS 'Puppet::Indirector::Ldap' is a class and should not be translated Puppet.deprecation_warning(_("Puppet::Indirector::Ldap is deprecated and will be removed in a future release of Puppet.")); super end # Perform our ldap search and process the result. def find(request) ldapsearch(search_filter(request.key)) { |entry| return process(entry) } || nil end # Process the found entry. We assume that we don't just want the # ldap object. def process(entry) raise Puppet::DevError, _("The 'process' method has not been overridden for the LDAP terminus for %{name}") % { name: self.name } end # Default to all attributes. def search_attributes nil end def search_base Puppet[:ldapbase] end # The ldap search filter to use. def search_filter(name) raise Puppet::DevError, _("No search string set for LDAP terminus for %{name}") % { name: self.name } end # Find the ldap node, return the class list and parent node specially, # and everything else in a parameter hash. def ldapsearch(filter) raise ArgumentError.new(_("You must pass a block to ldapsearch")) unless block_given? found = false count = 0 begin connection.search(search_base, 2, filter, search_attributes) do |entry| found = true yield entry end rescue SystemExit,NoMemoryError raise rescue Exception => detail if count == 0 # Try reconnecting to ldap if we get an exception and we haven't yet retried. count += 1 @connection = nil Puppet.warning _("Retrying LDAP connection") retry else error = Puppet::Error.new(_("LDAP Search failed")) error.set_backtrace(detail.backtrace) raise error end end found end # Create an ldap connection. def connection unless @connection #TRANSLATORS "ruby/ldap libraries" are code dependencies raise Puppet::Error, _("Could not set up LDAP Connection: Missing ruby/ldap libraries") unless Puppet.features.ldap? begin conn = Puppet::Util::Ldap::Connection.instance conn.start @connection = conn.connection rescue => detail message = _("Could not connect to LDAP: %{detail}") % { detail: detail } Puppet.log_exception(detail, message) raise Puppet::Error, message, detail.backtrace end end @connection end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/request.rb������������������������������������������������������0000644�0052762�0001160�00000017307�13417161721�021264� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'cgi' require 'uri' require 'puppet/indirector' require 'puppet/network/resolver' require 'puppet/util/psych_support' # This class encapsulates all of the information you need to make an # Indirection call, and as a result also handles REST calls. It's somewhat # analogous to an HTTP Request object, except tuned for our Indirector. class Puppet::Indirector::Request include Puppet::Util::PsychSupport attr_accessor :key, :method, :options, :instance, :node, :ip, :authenticated, :ignore_cache, :ignore_cache_save, :ignore_terminus attr_accessor :server, :port, :uri, :protocol attr_reader :indirection_name # trusted_information is specifically left out because we can't serialize it # and keep it "trusted" OPTION_ATTRIBUTES = [:ip, :node, :authenticated, :ignore_terminus, :ignore_cache, :ignore_cache_save, :instance, :environment] # Is this an authenticated request? def authenticated? # Double negative, so we just get true or false ! ! authenticated end def environment # If environment has not been set directly, we should use the application's # current environment @environment ||= Puppet.lookup(:current_environment) end def environment=(env) @environment = if env.is_a?(Puppet::Node::Environment) env else Puppet.lookup(:environments).get!(env) end end # LAK:NOTE This is a messy interface to the cache, and it's only # used by the Configurer class. I decided it was better to implement # it now and refactor later, when we have a better design, than # to spend another month coming up with a design now that might # not be any better. def ignore_cache? ignore_cache end def ignore_cache_save? ignore_cache_save end def ignore_terminus? ignore_terminus end def initialize(indirection_name, method, key, instance, options = {}) @instance = instance options ||= {} self.indirection_name = indirection_name self.method = method options = options.inject({}) { |hash, ary| hash[ary[0].to_sym] = ary[1]; hash } set_attributes(options) @options = options if key # If the request key is a URI, then we need to treat it specially, # because it rewrites the key. We could otherwise strip server/port/etc # info out in the REST class, but it seemed bad design for the REST # class to rewrite the key. if key.to_s =~ /^\w+:\// and not Puppet::Util.absolute_path?(key.to_s) # it's a URI set_uri_key(key) else @key = key end end @key = @instance.name if ! @key and @instance end # Look up the indirection based on the name provided. def indirection Puppet::Indirector::Indirection.instance(indirection_name) end def indirection_name=(name) @indirection_name = name.to_sym end def model raise ArgumentError, _("Could not find indirection '%{indirection}'") % { indirection: indirection_name } unless i = indirection i.model end # Are we trying to interact with multiple resources, or just one? def plural? method == :search end # Create the query string, if options are present. def query_string return "" if options.nil? || options.empty? encode_params(expand_into_parameters(options.to_a)) end def expand_into_parameters(data) data.inject([]) do |params, key_value| key, value = key_value expanded_value = case value when Array value.collect { |val| [key, val] } else [key_value] end params.concat(expand_primitive_types_into_parameters(expanded_value)) end end def expand_primitive_types_into_parameters(data) data.inject([]) do |params, key_value| key, value = key_value case value when nil params when true, false, String, Symbol, Integer, Float params << [key, value] else raise ArgumentError, _("HTTP REST queries cannot handle values of type '%{klass}'") % { klass: value.class } end end end def encode_params(params) params.collect do |key, value| "#{key}=#{Puppet::Util.uri_query_encode(value.to_s)}" end.join("&") end def initialize_from_hash(hash) @indirection_name = hash['indirection_name'].to_sym @method = hash['method'].to_sym @key = hash['key'] @instance = hash['instance'] @options = hash['options'] end def to_data_hash { 'indirection_name' => @indirection_name.to_s, 'method' => @method.to_s, 'key' => @key, 'instance' => @instance, 'options' => @options } end def to_hash result = options.dup OPTION_ATTRIBUTES.each do |attribute| if value = send(attribute) result[attribute] = value end end result end def description return(uri ? uri : "/#{indirection_name}/#{key}") end def do_request(srv_service=:puppet, default_server=nil, default_port=nil, &block) # We were given a specific server to use, so just use that one. # This happens if someone does something like specifying a file # source using a puppet:// URI with a specific server. return yield(self) if !self.server.nil? if Puppet.settings[:use_srv_records] Puppet::Network::Resolver.each_srv_record(Puppet.settings[:srv_domain], srv_service) do |srv_server, srv_port| begin self.server = srv_server self.port = srv_port return yield(self) rescue SystemCallError => e Puppet.warning _("Error connecting to %{srv_server}:%{srv_port}: %{message}") % { srv_server: srv_server, srv_port: srv_port, message: e.message } end end end # ... Fall back onto the default server. bound_server = Puppet.lookup(:server) do if primary_server = Puppet.settings[:server_list][0] primary_server[0] else Puppet.settings[:server] end end bound_port = Puppet.lookup(:serverport) do if primary_server = Puppet.settings[:server_list][0] primary_server[1] else Puppet.settings[:masterport] end end self.server = default_server || bound_server self.port = default_port || bound_port Puppet.debug "No more servers left, falling back to #{self.server}:#{self.port}" if Puppet.settings[:use_srv_records] return yield(self) end def remote? self.node or self.ip end private def set_attributes(options) OPTION_ATTRIBUTES.each do |attribute| if options.include?(attribute.to_sym) send(attribute.to_s + "=", options[attribute]) options.delete(attribute) end end end # Parse the key as a URI, setting attributes appropriately. def set_uri_key(key) @uri = key begin # calling uri_encode for UTF-8 characters will % escape them and keep them UTF-8 uri = URI.parse(Puppet::Util.uri_encode(key)) rescue => detail raise ArgumentError, _("Could not understand URL %{key}: %{detail}") % { key: key, detail: detail }, detail.backtrace end # Just short-circuit these to full paths if uri.scheme == "file" @key = Puppet::Util.uri_to_path(uri) return end @server = uri.host if uri.host # If the URI class can look up the scheme, it will provide a port, # otherwise it will default to '0'. if uri.port.to_i == 0 and uri.scheme == "puppet" @port = Puppet.settings[:masterport].to_i else @port = uri.port.to_i end # filebucket:// is only used internally to pass request details # from Dipper objects to the indirector. The wire always uses HTTPS. if uri.scheme == 'filebucket' @protocol = 'https' else @protocol = uri.scheme end @key = URI.unescape(uri.path.sub(/^\//, '')) end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/rest.rb���������������������������������������������������������0000644�0052762�0001160�00000026260�13417161721�020547� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'net/http' require 'uri' require 'puppet/util/json' require 'semantic_puppet' require 'puppet/network/http' require 'puppet/network/http_pool' # Access objects via REST class Puppet::Indirector::REST < Puppet::Indirector::Terminus include Puppet::Network::HTTP::Compression.module IndirectedRoutes = Puppet::Network::HTTP::API::IndirectedRoutes EXCLUDED_FORMATS = [:yaml, :b64_zlib_yaml, :dot] # puppet major version where JSON is enabled by default MAJOR_VERSION_JSON_DEFAULT = 5 class << self attr_reader :server_setting, :port_setting end # Specify the setting that we should use to get the server name. def self.use_server_setting(setting) @server_setting = setting end # Specify the setting that we should use to get the port. def self.use_port_setting(setting) @port_setting = setting end # Specify the service to use when doing SRV record lookup def self.use_srv_service(service) @srv_service = service end def self.srv_service @srv_service || :puppet end # The logic for server and port is kind of gross. In summary: # IF an endpoint-specific setting is requested AND that setting has been set by the user # Use that setting. # The defaults for these settings are the "normal" server/masterport settings, so # when they are unset we instead want to "fall back" to the failover-selected # host/port pair. # ELSE IF we have a failover-selected host/port # Use what the failover logic came up with # ELSE IF the server_list setting is in use # Use the first entry - failover hasn't happened yet, but that # setting is still authoritative # ELSE # Go for the legacy server/masterport settings, and hope for the best def self.server setting = server_setting() if setting && setting != :server && Puppet.settings.set_by_config?(setting) Puppet.settings[setting] else server = Puppet.lookup(:server) do if primary_server = Puppet.settings[:server_list][0] Puppet.debug "Dynamically-bound server lookup failed; using first entry" primary_server[0] else setting ||= :server Puppet.debug "Dynamically-bound server lookup failed, falling back to #{setting} setting" Puppet.settings[setting] end end server end end # For port there's a little bit of an extra snag: setting a specific # server setting and relying on the default port for that server is # common, so we also want to check if the assocaited SERVER setting # has been set by the user. If either of those are set we ignore the # failover-selected port. def self.port setting = port_setting() srv_setting = server_setting() if (setting && setting != :masterport && Puppet.settings.set_by_config?(setting)) || (srv_setting && srv_setting != :server && Puppet.settings.set_by_config?(srv_setting)) Puppet.settings[setting].to_i else port = Puppet.lookup(:serverport) do if primary_server = Puppet.settings[:server_list][0] Puppet.debug "Dynamically-bound port lookup failed; using first entry" # Port might not be set, so we want to fallback in that # case. We know we don't need to use `setting` here, since # the default value of every port setting is `masterport` (primary_server[1] || Puppet.settings[:masterport]) else setting ||= :masterport Puppet.debug "Dynamically-bound port lookup failed; falling back to #{setting} setting" Puppet.settings[setting] end end port.to_i end end # Provide appropriate headers. def headers # yaml is not allowed on the network network_formats = model.supported_formats - EXCLUDED_FORMATS mime_types = network_formats.map { |f| model.get_format(f).mime } common_headers = { "Accept" => mime_types.join(', '), Puppet::Network::HTTP::HEADER_PUPPET_VERSION => Puppet.version } add_accept_encoding(common_headers) end def add_profiling_header(headers) if (Puppet[:profile]) headers[Puppet::Network::HTTP::HEADER_ENABLE_PROFILING] = "true" end headers end def network(request) Puppet::Network::HttpPool.http_instance(request.server || self.class.server, request.port || self.class.port) end def http_get(request, path, headers = nil, *args) http_request(:get, request, path, add_profiling_header(headers), *args) end def http_post(request, path, data, headers = nil, *args) http_request(:post, request, path, data, add_profiling_header(headers), *args) end def http_head(request, path, headers = nil, *args) http_request(:head, request, path, add_profiling_header(headers), *args) end def http_delete(request, path, headers = nil, *args) http_request(:delete, request, path, add_profiling_header(headers), *args) end def http_put(request, path, data, headers = nil, *args) http_request(:put, request, path, data, add_profiling_header(headers), *args) end def http_request(method, request, *args) conn = network(request) conn.send(method, *args) end def find(request) uri, body = IndirectedRoutes.request_to_uri_and_body(request) uri_with_query_string = "#{uri}?#{body}" response = do_request(request) do |req| # WEBrick in Ruby 1.9.1 only supports up to 1024 character lines in an HTTP request # http://redmine.ruby-lang.org/issues/show/3991 if "GET #{uri_with_query_string} HTTP/1.1\r\n".length > 1024 uri_with_env = "#{uri}?environment=#{request.environment.name}" http_post(req, uri_with_env, body, headers) else http_get(req, uri_with_query_string, headers) end end if is_http_200?(response) content_type, body = parse_response(response) result = deserialize_find(content_type, body) result.name = request.key if result.respond_to?(:name=) result elsif is_http_404?(response) return nil unless request.options[:fail_on_404] # 404 can get special treatment as the indirector API can not produce a meaningful # reason to why something is not found - it may not be the thing the user is # expecting to find that is missing, but something else (like the environment). # While this way of handling the issue is not perfect, there is at least an error # that makes a user aware of the reason for the failure. # _, body = parse_response(response) msg = _("Find %{uri} resulted in 404 with the message: %{body}") % { uri: elide(uri_with_query_string, 100), body: body } raise Puppet::Error, msg else nil end end def head(request) response = do_request(request) do |req| http_head(req, IndirectedRoutes.request_to_uri(req), headers) end if is_http_200?(response) true else false end end def search(request) response = do_request(request) do |req| http_get(req, IndirectedRoutes.request_to_uri(req), headers) end if is_http_200?(response) content_type, body = parse_response(response) deserialize_search(content_type, body) || [] else [] end end def destroy(request) raise ArgumentError, _("DELETE does not accept options") unless request.options.empty? response = do_request(request) do |req| http_delete(req, IndirectedRoutes.request_to_uri(req), headers) end if is_http_200?(response) content_type, body = parse_response(response) deserialize_destroy(content_type, body) else nil end end def save(request) raise ArgumentError, _("PUT does not accept options") unless request.options.empty? response = do_request(request) do |req| http_put(req, IndirectedRoutes.request_to_uri(req), req.instance.render, headers.merge({ "Content-Type" => req.instance.mime })) end if is_http_200?(response) content_type, body = parse_response(response) deserialize_save(content_type, body) else nil end end # Encapsulate call to request.do_request with the arguments from this class # Then yield to the code block that was called in # We certainly could have retained the full request.do_request(...) { |r| ... } # but this makes the code much cleaner and we only then actually make the call # to request.do_request from here, thus if we change what we pass or how we # get it, we only need to change it here. def do_request(request) response = request.do_request(self.class.srv_service, self.class.server, self.class.port) { |req| yield(req) } handle_response(request, response) if response response end def handle_response(request, response) server_version = response[Puppet::Network::HTTP::HEADER_PUPPET_VERSION] if server_version Puppet.lookup(:server_agent_version) do Puppet.push_context(:server_agent_version => server_version) end if SemanticPuppet::Version.parse(server_version).major < MAJOR_VERSION_JSON_DEFAULT && Puppet[:preferred_serialization_format] != 'pson' #TRANSLATORS "PSON" should not be translated Puppet.warning(_("Downgrading to PSON for future requests")) Puppet[:preferred_serialization_format] = 'pson' end end end def validate_key(request) # Validation happens on the remote end end private def is_http_200?(response) case response.code when "404" false when /^2/ true else # Raise the http error if we didn't get a 'success' of some kind. raise convert_to_http_error(response) end end def is_http_404?(response) response.code == "404" end def convert_to_http_error(response) if response.body.to_s.empty? && response.respond_to?(:message) returned_message = response.message elsif response['content-type'].is_a?(String) content_type, body = parse_response(response) if content_type =~ /[pj]son/ returned_message = Puppet::Util::Json.load(body)["message"] else returned_message = uncompress_body(response) end else returned_message = uncompress_body(response) end message = _("Error %{code} on SERVER: %{returned_message}") % { code: response.code, returned_message: returned_message } Net::HTTPError.new(message, response) end # Returns the content_type, stripping any appended charset, and the # body, decompressed if necessary (content-encoding is checked inside # uncompress_body) def parse_response(response) if response['content-type'] [ response['content-type'].gsub(/\s*;.*$/,''), uncompress_body(response) ] else raise _("No content type in http response; cannot parse") end end def deserialize_find(content_type, body) model.convert_from(content_type, body) end def deserialize_search(content_type, body) model.convert_from_multiple(content_type, body) end def deserialize_destroy(content_type, body) model.convert_from(content_type, body) end def deserialize_save(content_type, body) nil end def elide(string, length) if Puppet::Util::Log.level == :debug || string.length <= length string else string[0, length - 3] + "..." end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/ssl_file.rb�����������������������������������������������������0000644�0052762�0001160�00000016060�13417161721�021367� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/ssl' class Puppet::Indirector::SslFile < Puppet::Indirector::Terminus # Specify the directory in which multiple files are stored. def self.store_in(setting) @directory_setting = setting end # Specify a single file location for storing just one file. # This is used for things like the CRL. def self.store_at(setting) @file_setting = setting end # Specify where a specific ca file should be stored. def self.store_ca_at(setting) @ca_setting = setting end class << self attr_reader :directory_setting, :file_setting, :ca_setting end # The full path to where we should store our files. def self.collection_directory return nil unless directory_setting Puppet.settings[directory_setting] end # The full path to an individual file we would be managing. def self.file_location return nil unless file_setting Puppet.settings[file_setting] end # The full path to a ca file we would be managing. def self.ca_location return nil unless ca_setting Puppet.settings[ca_setting] end # We assume that all files named 'ca' are pointing to individual ca files, # rather than normal host files. It's a bit hackish, but all the other # solutions seemed even more hackish. def ca?(name) name == Puppet::SSL::Host.ca_name end def initialize Puppet.settings.use(:main, :ssl) (collection_directory || file_location) or raise Puppet::DevError, _("No file or directory setting provided; terminus %{class_name} cannot function") % { class_name: self.class.name } end def path(name) if name =~ Puppet::Indirector::BadNameRegexp then Puppet.crit(_("directory traversal detected in %{indirection}: %{name}") % { indirection: self.class, name: name.inspect }) raise ArgumentError, _("invalid key") end if ca?(name) and ca_location ca_location elsif collection_directory File.join(collection_directory, name.to_s + ".pem") else file_location end end # Remove our file. def destroy(request) path = Puppet::FileSystem.pathname(path(request.key)) return false unless Puppet::FileSystem.exist?(path) Puppet.notice _("Removing file %{model} %{request} at '%{path}'") % { model: model, request: request.key, path: path } begin Puppet::FileSystem.unlink(path) rescue => detail raise Puppet::Error, _("Could not remove %{request}: %{detail}") % { request: request.key, detail: detail }, detail.backtrace end end # Find the file on disk, returning an instance of the model. def find(request) filename = rename_files_with_uppercase(path(request.key)) filename ? create_model(request.key, filename) : nil end # Save our file to disk. def save(request) path = path(request.key) dir = File.dirname(path) raise Puppet::Error.new(_("Cannot save %{request}; parent directory %{dir} does not exist") % { request: request.key, dir: dir }) unless FileTest.directory?(dir) raise Puppet::Error.new(_("Cannot save %{request}; parent directory %{dir} is not writable") % { request: request.key, dir: dir }) unless FileTest.writable?(dir) write(request.key, path) { |f| f.print request.instance.to_s } end # Search for more than one file. At this point, it just returns # an instance for every file in the directory. def search(request) dir = collection_directory Dir.entries(dir). select { |file| file =~ /\.pem$/ }. collect { |file| create_model(file.sub(/\.pem$/, ''), File.join(dir, file)) }. compact end private def create_model(name, path) result = model.new(name) # calls Puppet::SSL::Base#read for subclasses of Puppet::SSL::Base # with the exception of any overrides, like Puppet::SSL::Key result.read(path) result end # Demeterish pointers to class info. def collection_directory self.class.collection_directory end def file_location self.class.file_location end def ca_location self.class.ca_location end # A hack method to deal with files that exist with a different case. # Just renames it; doesn't read it in or anything. # LAK:NOTE This is a copy of the method in sslcertificates/support.rb, # which we'll be EOL'ing at some point. This method was added at 20080702 # and should be removed at some point. def rename_files_with_uppercase(file) return file if Puppet::FileSystem.exist?(file) dir, short = File.split(file) return nil unless Puppet::FileSystem.exist?(dir) raise ArgumentError, _("Tried to fix SSL files to a file containing uppercase") unless short.downcase == short real_file = Dir.entries(dir).reject { |f| f =~ /^\./ }.find do |other| other.downcase == short end return nil unless real_file full_file = File.join(dir, real_file) Puppet.deprecation_warning _("Automatic downcasing and renaming of ssl files is deprecated; please request the file using its correct case: %{full_file}") % { full_file: full_file } File.rename(full_file, file) file end # Yield a filehandle set up appropriately, either with our settings doing # the work or opening a filehandle manually. def write(name, path) # All types serialized to disk contain only ASCII content: # * SSL::Key may be a .export(OpenSSL::Cipher::DES.new(:EDE3, :CBC), pass) or .to_pem # * All other classes are translated to strings by calling .to_pem # Serialization of: # Puppet::SSL::Certificate by Puppet::SSL::Certificate::Ca, Puppet::SSL::Certificate::File # -----BEGIN CERTIFICATE----- # Puppet::SSL::Key by Puppet::SSL::Key::Ca, Puppet::SSL::Key::File # -----BEGIN RSA PRIVATE KEY----- if ca?(name) and ca_location Puppet.settings.setting(self.class.ca_setting).open('w:ASCII') { |f| yield f } # Serialization of: # Puppet::SSL::CertificateRevocationList by Puppet::SSL::CertificateRevocationList::Ca, Puppet::SSL::CertificateRevocationList::File # -----BEGIN X509 CRL----- elsif file_location Puppet.settings.setting(self.class.file_setting).open('w:ASCII') { |f| yield f } # Serialization of: # Puppet::SSL::Certificate by Puppet::SSL::Certificate::Ca, Puppet::SSL::Certificate::File # -----BEGIN CERTIFICATE----- # Puppet::SSL::CertificateRequest by Puppet::SSL::CertificateRequest::Ca, Puppet::SSL::CertificateRequest::File # -----BEGIN CERTIFICATE REQUEST----- # Puppet::SSL::Key by Puppet::SSL::Key::Ca, Puppet::SSL::Key::File # -----BEGIN RSA PRIVATE KEY----- elsif setting = self.class.directory_setting begin Puppet.settings.setting(setting).open_file(path, 'w:ASCII') { |f| yield f } rescue => detail raise Puppet::Error, _("Could not write %{path} to %{setting}: %{detail}") % { path: path, setting: setting, detail: detail }, detail.backtrace end else raise Puppet::DevError, _("You must provide a setting to determine where the files are stored") end end end # LAK:NOTE This has to be at the end, because classes like SSL::Key use this # class, and this require statement loads those, which results in a load loop # and lots of failures. require 'puppet/ssl/host' ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/terminus.rb�����������������������������������������������������0000644�0052762�0001160�00000012147�13417161721�021437� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector' require 'puppet/indirector/errors' require 'puppet/indirector/indirection' require 'puppet/util/instance_loader' # A simple class that can function as the base class for indirected types. class Puppet::Indirector::Terminus require 'puppet/util/docs' extend Puppet::Util::Docs class << self include Puppet::Util::InstanceLoader attr_accessor :name, :terminus_type attr_reader :abstract_terminus, :indirection # Are we an abstract terminus type, rather than an instance with an # associated indirection? def abstract_terminus? abstract_terminus end # Convert a constant to a short name. def const2name(const) const.sub(/^[A-Z]/) { |i| i.downcase }.gsub(/[A-Z]/) { |i| "_#{i.downcase}" }.intern end # Look up the indirection if we were only provided a name. def indirection=(name) if name.is_a?(Puppet::Indirector::Indirection) @indirection = name elsif ind = Puppet::Indirector::Indirection.instance(name) @indirection = ind else raise ArgumentError, _("Could not find indirection instance %{name} for %{terminus}") % { name: name, terminus: self.name } end end def indirection_name @indirection.name end # Register our subclass with the appropriate indirection. # This follows the convention that our terminus is named after the # indirection. def inherited(subclass) longname = subclass.to_s if longname =~ /#<Class/ raise Puppet::DevError, _("Terminus subclasses must have associated constants") end names = longname.split("::") # Convert everything to a lower-case symbol, converting camelcase to underscore word separation. name = names.pop.sub(/^[A-Z]/) { |i| i.downcase }.gsub(/[A-Z]/) { |i| "_#{i.downcase}" }.intern subclass.name = name # Short-circuit the abstract types, which are those that directly subclass # the Terminus class. if self == Puppet::Indirector::Terminus subclass.mark_as_abstract_terminus return end # Set the terminus type to be the name of the abstract terminus type. # Yay, class/instance confusion. subclass.terminus_type = self.name # This subclass is specifically associated with an indirection. raise("Invalid name #{longname}") unless names.length > 0 processed_name = names.pop.sub(/^[A-Z]/) { |i| i.downcase }.gsub(/[A-Z]/) { |i| "_#{i.downcase}" } if processed_name.empty? raise Puppet::DevError, _("Could not discern indirection model from class constant") end # This will throw an exception if the indirection instance cannot be found. # Do this last, because it also registers the terminus type with the indirection, # which needs the above information. subclass.indirection = processed_name.intern # And add this instance to the instance hash. Puppet::Indirector::Terminus.register_terminus_class(subclass) end # Mark that this instance is abstract. def mark_as_abstract_terminus @abstract_terminus = true end def model indirection.model end # Convert a short name to a constant. def name2const(name) name.to_s.capitalize.sub(/_(.)/) { |i| $1.upcase } end # Register a class, probably autoloaded. def register_terminus_class(klass) setup_instance_loading klass.indirection_name instance_hash(klass.indirection_name)[klass.name] = klass end # Return a terminus by name, using the autoloader. def terminus_class(indirection_name, terminus_type) setup_instance_loading indirection_name loaded_instance(indirection_name, terminus_type) end # Return all terminus classes for a given indirection. def terminus_classes(indirection_name) setup_instance_loading indirection_name instance_loader(indirection_name).files_to_load.map do |file| File.basename(file).chomp(".rb").intern end end private def setup_instance_loading(type) instance_load type, "puppet/indirector/#{type}" unless instance_loading?(type) end end def indirection self.class.indirection end def initialize raise Puppet::DevError, _("Cannot create instances of abstract terminus types") if self.class.abstract_terminus? end def model self.class.model end def name self.class.name end def allow_remote_requests? true end def terminus_type self.class.terminus_type end def validate(request) if request.instance validate_model(request) validate_key(request) end end def validate_key(request) unless request.key == request.instance.name raise Puppet::Indirector::ValidationError, _("Instance name %{name} does not match requested key %{key}") % { name: request.instance.name.inspect, key: request.key.inspect } end end def validate_model(request) unless model === request.instance raise Puppet::Indirector::ValidationError, _("Invalid instance type %{klass}, expected %{model_type}") % { klass: request.instance.class.inspect, model_type: model.inspect } end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/indirector/yaml.rb���������������������������������������������������������0000644�0052762�0001160�00000004120�13417161721�020523� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/terminus' require 'puppet/util/yaml' # The base class for YAML indirection termini. class Puppet::Indirector::Yaml < Puppet::Indirector::Terminus # Read a given name's file in and convert it from YAML. def find(request) file = path(request.key) return nil unless Puppet::FileSystem.exist?(file) begin return fix(Puppet::Util::Yaml.load_file(file)) rescue Puppet::Util::Yaml::YamlLoadError => detail raise Puppet::Error, _("Could not parse YAML data for %{indirection} %{request}: %{detail}") % { indirection: indirection.name, request: request.key, detail: detail }, detail.backtrace end end # Convert our object to YAML and store it to the disk. def save(request) raise ArgumentError.new(_("You can only save objects that respond to :name")) unless request.instance.respond_to?(:name) file = path(request.key) basedir = File.dirname(file) # This is quite likely a bad idea, since we're not managing ownership or modes. Dir.mkdir(basedir) unless Puppet::FileSystem.exist?(basedir) begin Puppet::Util::Yaml.dump(request.instance, file) rescue TypeError => detail Puppet.err _("Could not save %{indirection} %{request}: %{detail}") % { indirection: self.name, request: request.key, detail: detail } end end # Return the path to a given node's file. def path(name,ext='.yaml') if name =~ Puppet::Indirector::BadNameRegexp then Puppet.crit(_("directory traversal detected in %{indirection}: %{name}") % { indirection: self.class, name: name.inspect }) raise ArgumentError, _("invalid key") end base = Puppet.run_mode.master? ? Puppet[:yamldir] : Puppet[:clientyamldir] File.join(base, self.class.indirection_name.to_s, name.to_s + ext) end def destroy(request) file_path = path(request.key) Puppet::FileSystem.unlink(file_path) if Puppet::FileSystem.exist?(file_path) end def search(request) Dir.glob(path(request.key,'')).collect do |file| fix(Puppet::Util::Yaml.load_file(file)) end end protected def fix(object) object end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/info_service.rb������������������������������������������������������������0000644�0052762�0001160�00000001144�13417161721�020075� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� module Puppet::InfoService require 'puppet/info_service/class_information_service' require 'puppet/info_service/task_information_service' def self.classes_per_environment(env_file_hash) Puppet::InfoService::ClassInformationService.new.classes_per_environment(env_file_hash) end def self.tasks_per_environment(environment_name) Puppet::InfoService::TaskInformationService.tasks_per_environment(environment_name) end def self.task_data(environment_name, module_name, task_name) Puppet::InfoService::TaskInformationService.task_data(environment_name, module_name, task_name) end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/info_service/��������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�017555� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/info_service/class_information_service.rb����������������������������������0000644�0052762�0001160�00000005470�13417161721�025335� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet' require 'puppet/pops' require 'puppet/pops/evaluator/json_strict_literal_evaluator' class Puppet::InfoService::ClassInformationService def initialize @file_to_result = {} @parser = Puppet::Pops::Parser::EvaluatingParser.new() end def classes_per_environment(env_file_hash) # In this version of puppet there is only one way to parse manifests, as feature switches per environment # are added or removed, this logic needs to change to compute the result per environment with the correct # feature flags in effect. unless env_file_hash.is_a?(Hash) raise ArgumentError, _('Given argument must be a Hash') end result = {} # for each environment # for each file # if file already processed, use last result or error # env_file_hash.each do |env, files| env_result = result[env] = {} files.each do |f| env_result[f] = result_of(f) end end result end private def type_parser Puppet::Pops::Types::TypeParser.singleton end def literal_evaluator @@literal_evaluator ||= Puppet::Pops::Evaluator::JsonStrictLiteralEvaluator.new end def result_of(f) entry = @file_to_result[f] if entry.nil? @file_to_result[f] = entry = parse_file(f) end entry end def parse_file(f) return {:error => _("The file %{f} does not exist") % { f: f }} unless Puppet::FileSystem.exist?(f) begin parse_result = @parser.parse_file(f) {:classes => parse_result.definitions.select {|d| d.is_a?(Puppet::Pops::Model::HostClassDefinition)}.map do |d| {:name => d.name, :params => d.parameters.map {|p| extract_param(p) } } end } rescue StandardError => e {:error => e.message } end end def extract_param(p) extract_default(extract_type({:name => p.name}, p), p) end def extract_type(structure, p) return structure if p.type_expr.nil? structure[:type] = typeexpr_to_string(p.type_expr) structure end def extract_default(structure, p) value_expr = p.value return structure if value_expr.nil? default_value = value_as_literal(value_expr) structure[:default_literal] = default_value unless default_value.nil? structure[:default_source] = extract_value_source(value_expr) structure end def typeexpr_to_string(type_expr) begin type_parser.interpret_any(type_expr, nil).to_s rescue Puppet::ParseError # type is to complex - contains expressions that are not literal nil end end def value_as_literal(value_expr) catch(:not_literal) do return literal_evaluator.literal(value_expr) end nil end # Extracts the source for the expression def extract_value_source(value_expr) value_expr.locator.extract_tree_text(value_expr) end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/info_service/task_information_service.rb�����������������������������������0000644�0052762�0001160�00000002417�13417161721�025170� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::InfoService::TaskInformationService require 'puppet/module' def self.tasks_per_environment(environment_name) # get the actual environment object, raise error if the named env doesn't exist env = Puppet.lookup(:environments).get!(environment_name) env.modules.map do |mod| mod.tasks.map do |task| {:module => {:name => task.module.name}, :name => task.name} end end.flatten end def self.task_data(environment_name, module_name, task_name) # raise EnvironmentNotFound if applicable Puppet.lookup(:environments).get!(environment_name) pup_module = Puppet::Module.find(module_name, environment_name) if pup_module.nil? raise Puppet::Module::MissingModule, _("Module %{module_name} not found in environment %{environment_name}.") % {module_name: module_name, environment_name: environment_name} end task = pup_module.tasks.find { |t| t.name == task_name } if task.nil? raise Puppet::Module::Task::TaskNotFound, _("Task %{task_name} not found in module %{module_name}.") % {task_name: task_name, module_name: module_name} end {:metadata_file => task.metadata_file, :files => task.files} end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/interface/�����������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�017042� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/interface/action_builder.rb������������������������������������������������0000644�0052762�0001160�00000013412�13417161721�022346� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This class is used to build {Puppet::Interface::Action actions}. # When an action is defined with # {Puppet::Interface::ActionManager#action} the block is evaluated # within the context of a new instance of this class. # @api public class Puppet::Interface::ActionBuilder # The action under construction # @return [Puppet::Interface::Action] # @api private attr_reader :action # Builds a new action. # @return [Puppet::Interface::Action] # @api private def self.build(face, name, &block) raise "Action #{name.inspect} must specify a block" unless block new(face, name, &block).action end # Deprecates the action # @return [void] # @api private # @dsl Faces def deprecate @action.deprecate end # Ideally the method we're defining here would be added to the action, and a # method on the face would defer to it, but we can't get scope correct, so # we stick with this. --daniel 2011-03-24 # Sets what the action does when it is invoked. This takes a block # which will be called when the action is invoked. The action will # accept arguments based on the arity of the block. It should always # take at least one argument for options. Options will be the last # argument. # # @overload when_invoked({|options| ... }) # An action with no arguments # @overload when_invoked({|arg1, arg2, options| ... }) # An action with two arguments # @return [void] # @api public # @dsl Faces def when_invoked(&block) @action.when_invoked = block end # Sets a block to be run at the rendering stage, for a specific # rendering type (eg JSON, YAML, console), after the block for # when_invoked gets run. This manipulates the value returned by the # action. It makes it possible to work around limitations in the # underlying object returned, and should be avoided in favor of # returning a more capable object. # @api private # @todo this needs more # @dsl Faces def when_rendering(type = nil, &block) if type.nil? then # the default error message sucks --daniel 2011-04-18 #TRANSLATORS 'when_rendering' is a method name and should not be translated raise ArgumentError, _('You must give a rendering format to when_rendering') end if block.nil? then #TRANSLATORS 'when_rendering' is a method name and should not be translated raise ArgumentError, _('You must give a block to when_rendering') end @action.set_rendering_method_for(type, block) end # Declare that this action can take a specific option, and provide the # code to do so. One or more strings are given, in the style of # OptionParser (see example). These strings are parsed to derive a # name for the option. Any `-` characters within the option name (ie # excluding the initial `-` or `--` for an option) will be translated # to `_`.The first long option will be used as the name, and the rest # are retained as aliases. The original form of the option is used # when invoking the face, the translated form is used internally. # # When the action is invoked the value of the option is available in # a hash passed to the {Puppet::Interface::ActionBuilder#when_invoked # when_invoked} block, using the option name in symbol form as the # hash key. # # The block to this method is used to set attributes for the option # (see {Puppet::Interface::OptionBuilder}). # # @param declaration [String] Option declarations, as described above # and in the example. # # @example Say hi # action :say_hi do # option "-u USER", "--user-name USER" do # summary "Who to say hi to" # end # # when_invoked do |options| # "Hi, #{options[:user_name]}" # end # end # @api public # @dsl Faces def option(*declaration, &block) option = Puppet::Interface::OptionBuilder.build(@action, *declaration, &block) @action.add_option(option) end # Set this as the default action for the face. # @api public # @dsl Faces # @return [void] def default(value = true) @action.default = !!value end # @api private def display_global_options(*args) @action.add_display_global_options args end alias :display_global_option :display_global_options # Sets the default rendering format # @api private def render_as(value = nil) if value.nil? #TRANSLATORS 'render_as' is a method name and should not be translated raise ArgumentError, _("You must give a rendering format to render_as") end formats = Puppet::Network::FormatHandler.formats unless formats.include? value raise ArgumentError, _("%{value} is not a valid rendering format: %{formats_list}") % { value: value.inspect, formats_list: formats.sort.join(", ")} end @action.render_as = value end # Metaprogram the simple DSL from the target class. Puppet::Interface::Action.instance_methods.grep(/=$/).each do |setter| next if setter =~ /^=/ property = setter.to_s.chomp('=') unless method_defined? property # Using eval because the argument handling semantics are less awful than # when we use the define_method/block version. The later warns on older # Ruby versions if you pass the wrong number of arguments, but carries # on, which is totally not what we want. --daniel 2011-04-18 eval <<-METHOD def #{property}(value) @action.#{property} = value end METHOD end end private def initialize(face, name, &block) @face = face @action = Puppet::Interface::Action.new(face, name) instance_eval(&block) unless @action.when_invoked #TRANSLATORS 'when_invoked' is a method name and should not be translated and 'block' is a Ruby code block raise ArgumentError, _("actions need to know what to do when_invoked; please add the block") end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/interface/action_manager.rb������������������������������������������������0000644�0052762�0001160�00000006301�13417161721�022331� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This class is not actually public API, but the method # {Puppet::Interface::ActionManager#action action} is public when used # as part of the Faces DSL (i.e. from within a # {Puppet::Interface.define define} block). # @api public module Puppet::Interface::ActionManager # Declare that this app can take a specific action, and provide # the code to do so. # Defines a new action. This takes a block to build the action using # the methods on {Puppet::Interface::ActionBuilder}. # @param name [Symbol] The name that will be used to invoke the # action # @overload action(name, {|| block}) # @return [void] # @api public # @dsl Faces def action(name, &block) @actions ||= {} Puppet.warning _("Redefining action %{name} for %{self}") % { name: name, self: self } if action?(name) action = Puppet::Interface::ActionBuilder.build(self, name, &block) # REVISIT: (#18042) doesn't this mean we can't redefine the default action? -- josh if action.default and current = get_default_action raise "Actions #{current.name} and #{name} cannot both be default" end @actions[action.name] = action end # Returns the list of available actions for this face. # @return [Array<Symbol>] The names of the actions for this face # @api private def actions @actions ||= {} result = @actions.keys if self.is_a?(Class) and superclass.respond_to?(:actions) result += superclass.actions elsif self.class.respond_to?(:actions) result += self.class.actions end # We need to uniq the result, because we duplicate actions when they are # fetched to ensure that they have the correct bindings; they shadow the # parent, and uniq implements that. --daniel 2011-06-01 (result - @deactivated_actions.to_a).uniq.sort end # Retrieves a named action # @param name [Symbol] The name of the action # @return [Puppet::Interface::Action] The action object # @api private def get_action(name) @actions ||= {} result = @actions[name.to_sym] if result.nil? if self.is_a?(Class) and superclass.respond_to?(:get_action) found = superclass.get_action(name) elsif self.class.respond_to?(:get_action) found = self.class.get_action(name) end if found then # This is not the nicest way to make action equivalent to the Ruby # Method object, rather than UnboundMethod, but it will do for now, # and we only have to make this change in *one* place. --daniel 2011-04-12 result = @actions[name.to_sym] = found.__dup_and_rebind_to(self) end end return result end # Retrieves the default action for the face # @return [Puppet::Interface::Action] # @api private def get_default_action default = actions.map {|x| get_action(x) }.select {|x| x.default } if default.length > 1 raise "The actions #{default.map(&:name).join(", ")} cannot all be default" end default.first end # Deactivate a named action # @return [Puppet::Interface::Action] # @api public def deactivate_action(name) @deactivated_actions ||= Set.new @deactivated_actions.add name.to_sym end # @api private def action?(name) actions.include?(name.to_sym) end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/interface/documentation.rb�������������������������������������������������0000644�0052762�0001160�00000027340�13417161721�022241� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Interface # @api private module DocGen require 'puppet/util/docs' # @api private def self.strip_whitespace(text) # I don't want no... Puppet::Util::Docs.scrub(text) end # The documentation attributes all have some common behaviours; previously # we open-coded them across the set of six things, but that seemed # wasteful - especially given that they were literally the same, and had # the same bug hidden in them. # # This feels a bit like overkill, but at least the common code is common # now. --daniel 2011-04-29 # @api private def attr_doc(name, &validate) # Now, which form of the setter do we want, validated or not? get_arg = "value.to_s" if validate define_method(:"_validate_#{name}", validate) get_arg = "_validate_#{name}(#{get_arg})" end # We use module_eval, which I don't like much, because we can't have an # argument to a block with a default value in Ruby 1.8, and I don't like # the side-effects (eg: no argument count validation) of using blocks # without as methods. When we are 1.9 only (hah!) you can totally # replace this with some up-and-up define_method. --daniel 2011-04-29 module_eval(<<-EOT, __FILE__, __LINE__ + 1) def #{name}(value = nil) self.#{name} = value unless value.nil? @#{name} end def #{name}=(value) @#{name} = Puppet::Interface::DocGen.strip_whitespace(#{get_arg}) end EOT end end # This module can be mixed in to provide a minimal set of # documentation attributes. # @api public module TinyDocs extend Puppet::Interface::DocGen # @!method summary(summary) # Sets a summary of this object. # @api public # @dsl Faces attr_doc :summary do |value| value =~ /\n/ and #TRANSLATORS 'Face' refers to a programming API in Puppet, 'summary' and 'description' are specifc attribute names and should not be translated raise ArgumentError, _("Face summary should be a single line; put the long text in 'description' instead.") value end # @!method description(description) # Sets the long description of this object. # @param description [String] The description of this object. # @api public # @dsl Faces attr_doc :description # @api private def build_synopsis(face, action = nil, arguments = nil) PrettyPrint.format do |s| s.text("puppet #{face}") s.text(" #{action}") unless action.nil? s.text(" ") options.each do |option| option = get_option(option) wrap = option.required? ? %w{ < > } : %w{ [ ] } s.group(0, *wrap) do option.optparse.each do |item| unless s.current_group.first? s.breakable s.text '|' s.breakable end s.text item end end s.breakable end display_global_options.sort.each do |option| wrap = %w{ [ ] } s.group(0, *wrap) do type = Puppet.settings.setting(option).default type ||= Puppet.settings.setting(option).type.to_s.upcase s.text "--#{option} #{type}" s.breakable end s.breakable end if arguments then s.text arguments.to_s end end end end # This module can be mixed in to provide a full set of documentation # attributes. It is intended to be used for {Puppet::Interface}. # @api public module FullDocs extend Puppet::Interface::DocGen include TinyDocs # @!method examples # @overload examples(text) # Sets examples. # @param text [String] Example text # @api public # @return [void] # @dsl Faces # @overload examples # Returns documentation of examples # @return [String] The examples # @api private attr_doc :examples # @!method notes(text) # @overload notes(text) # Sets optional notes. # @param text [String] The notes # @api public # @return [void] # @dsl Faces # @overload notes # Returns any optional notes # @return [String] The notes # @api private attr_doc :notes # @!method license(text) # @overload license(text) # Sets the license text # @param text [String] the license text # @api public # @return [void] # @dsl Faces # @overload license # Returns the license # @return [String] The license # @api private attr_doc :license attr_doc :short_description # @overload short_description(value) # Sets a short description for this object. # @param value [String, nil] A short description (about a paragraph) # of this component. If `value` is `nil` the short_description # will be set to the shorter of the first paragraph or the first # five lines of {description}. # @return [void] # @api public # @dsl Faces # @overload short_description # Get the short description for this object # @return [String, nil] The short description of this object. If none is # set it will be derived from {description}. Returns `nil` if # {description} is `nil`. # @api private def short_description(value = nil) self.short_description = value unless value.nil? if @short_description.nil? then return nil if @description.nil? lines = @description.split("\n") first_paragraph_break = lines.index('') || 5 grab = [5, first_paragraph_break].min @short_description = lines[0, grab].join("\n") @short_description += ' [...]' if (grab < lines.length and first_paragraph_break >= 5) end @short_description end # @overload author(value) # Adds an author to the documentation for this object. To set # multiple authors, call this once for each author. # @param value [String] the name of the author # @api public # @dsl Faces # @overload author # Returns a list of authors # @return [String, nil] The names of all authors separated by # newlines, or `nil` if no authors have been set. # @api private def author(value = nil) unless value.nil? then unless value.is_a? String #TRANSLATORS 'author' is an attribute name and should not be translated raise ArgumentError, _('author must be a string; use multiple statements for multiple authors') end if value =~ /\n/ then #TRANSLATORS 'author' is an attribute name and should not be translated raise ArgumentError, _('author should be a single line; use multiple statements for multiple authors') end @authors.push(Puppet::Interface::DocGen.strip_whitespace(value)) end @authors.empty? ? nil : @authors.join("\n") end # Returns a list of authors. See {author}. # @return [String] The list of authors, separated by newlines. # @api private def authors @authors end # @api private def author=(value) # I think it's a bug that this ends up being the exposed # version of `author` on ActionBuilder if Array(value).any? {|x| x =~ /\n/ } then #TRANSLATORS 'author' is an attribute name and should not be translated raise ArgumentError, _('author should be a single line; use multiple statements') end @authors = Array(value).map{|x| Puppet::Interface::DocGen.strip_whitespace(x) } end alias :authors= :author= # Sets the copyright owner and year. This returns the copyright # string, so it can be called with no arguments retrieve that string # without side effects. # @param owner [String, Array<String>] The copyright owner or an # array of owners # @param years [Integer, Range<Integer>, Array<Integer,Range<Integer>>] # The copyright year or years. Years can be specified with integers, # a range of integers, or an array of integers and ranges of # integers. # @return [String] A string describing the copyright on this object. # @api public # @dsl Faces def copyright(owner = nil, years = nil) if years.nil? and not owner.nil? then #TRANSLATORS 'copyright' is an attribute name and should not be translated raise ArgumentError, _('copyright takes the owners names, then the years covered') end self.copyright_owner = owner unless owner.nil? self.copyright_years = years unless years.nil? if self.copyright_years or self.copyright_owner then "Copyright #{self.copyright_years} by #{self.copyright_owner}" else "Unknown copyright owner and years." end end # Sets the copyright owner # @param value [String, Array<String>] The copyright owner or # owners. # @return [String] Comma-separated list of copyright owners # @api private attr_accessor :copyright_owner def copyright_owner=(value) case value when String then @copyright_owner = value when Array then @copyright_owner = value.join(", ") else #TRANSLATORS 'copyright' is an attribute name and should not be translated raise ArgumentError, _("copyright owner must be a string or an array of strings") end @copyright_owner end # Sets the copyright year # @param value [Integer, Range<Integer>, Array<Integer, Range>] The # copyright year or years. # @return [String] # @api private attr_accessor :copyright_years def copyright_years=(value) years = munge_copyright_year value years = (years.is_a?(Array) ? years : [years]). sort_by do |x| x.is_a?(Range) ? x.first : x end @copyright_years = years.map do |year| if year.is_a? Range then "#{year.first}-#{year.last}" else year end end.join(", ") end # @api private def munge_copyright_year(input) case input when Range then input when Integer then if input < 1970 then fault = "before 1970" elsif input > (future = Time.now.year + 2) then fault = "after #{future}" end if fault then #TRANSLATORS 'copyright' is an attribute name and should not be translated raise ArgumentError, _("copyright with a year %{value} is very strange; did you accidentally add or subtract two years?") % { value: fault } end input when String then input.strip.split(/,/).map do |part| part = part.strip if part =~ /^\d+$/ then part.to_i elsif found = part.split(/-/) then unless found.length == 2 and found.all? {|x| x.strip =~ /^\d+$/ } #TRANSLATORS 'copyright' is an attribute name and should not be translated raise ArgumentError, _("%{value} is not a good copyright year or range") % { value: part.inspect } end Range.new(found[0].to_i, found[1].to_i) else #TRANSLATORS 'copyright' is an attribute name and should not be translated raise ArgumentError, _("%{value} is not a good copyright year or range") % { value: part.inspect } end end when Array then result = [] input.each do |item| item = munge_copyright_year item if item.is_a? Array result.concat item else result << item end end result else #TRANSLATORS 'copyright' is an attribute name and should not be translated raise ArgumentError, _("%{value} is not a good copyright year, set, or range") % { value: input.inspect } end end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/interface/option.rb��������������������������������������������������������0000644�0052762�0001160�00000014012�13417161721�020670� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This represents an option on an action or face (to be globally applied # to its actions). Options should be constructed by calling # {Puppet::Interface::OptionManager#option}, which is available on # {Puppet::Interface}, and then calling methods of # {Puppet::Interface::OptionBuilder} in the supplied block. # @api public class Puppet::Interface::Option include Puppet::Interface::TinyDocs # @api private def initialize(parent, *declaration, &block) @parent = parent @optparse = [] @default = nil # Collect and sort the arguments in the declaration. dups = {} declaration.each do |item| if item.is_a? String and item.to_s =~ /^-/ then unless item =~ /^-[a-z]\b/ or item =~ /^--[^-]/ then raise ArgumentError, _("%{option}: long options need two dashes (--)") % { option: item.inspect } end @optparse << item # Duplicate checking... # for our duplicate checking purpose, we don't make a check with the # translated '-' -> '_'. Right now, we do that on purpose because of # a duplicated option made publicly available on certificate and ca # faces for dns alt names. Puppet defines 'dns_alt_names', those # faces include 'dns-alt-names'. We can't get rid of 'dns-alt-names' # yet, so we need to do our duplicate checking on the untranslated # version of the option. # jeffweiss 17 april 2012 name = optparse_to_optionname(item) if Puppet.settings.include? name then raise ArgumentError, _("%{option}: already defined in puppet") % { option: item.inspect } end if dup = dups[name] then raise ArgumentError, _("%{option}: duplicates existing alias %{duplicate} in %{parent}") % { option: item.inspect, duplicate: dup.inspect, parent: @parent } else dups[name] = item end else raise ArgumentError, _("%{option} is not valid for an option argument") % { option: item.inspect } end end if @optparse.empty? then raise ArgumentError, _("No option declarations found while building") end # Now, infer the name from the options; we prefer the first long option as # the name, rather than just the first option. @name = optparse_to_name(@optparse.find do |a| a =~ /^--/ end || @optparse.first) @aliases = @optparse.map { |o| optparse_to_name(o) } # Do we take an argument? If so, are we consistent about it, because # incoherence here makes our life super-difficult, and we can more easily # relax this rule later if we find a valid use case for it. --daniel 2011-03-30 @argument = @optparse.any? { |o| o =~ /[ =]/ } if @argument and not @optparse.all? { |o| o =~ /[ =]/ } then raise ArgumentError, _("Option %{name} is inconsistent about taking an argument") % { name: @name } end # Is our argument optional? The rules about consistency apply here, also, # just like they do to taking arguments at all. --daniel 2011-03-30 @optional_argument = @optparse.any? { |o| o=~/[ =]\[/ } if @optional_argument raise ArgumentError, _("Options with optional arguments are not supported") end if @optional_argument and not @optparse.all? { |o| o=~/[ =]\[/ } then raise ArgumentError, _("Option %{name} is inconsistent about the argument being optional") % { name: @name } end end # to_s and optparse_to_name are roughly mirrored, because they are used to # transform options to name symbols, and vice-versa. This isn't a full # bidirectional transformation though. --daniel 2011-04-07 def to_s @name.to_s.tr('_', '-') end # @api private def optparse_to_optionname(declaration) unless found = declaration.match(/^-+(?:\[no-\])?([^ =]+)/) then raise ArgumentError, _("Can't find a name in the declaration %{declaration}") % { declaration: declaration.inspect } end found.captures.first end # @api private def optparse_to_name(declaration) name = optparse_to_optionname(declaration).tr('-', '_') unless name.to_s =~ /^[a-z]\w*$/ raise _("%{name} is an invalid option name") % { name: name.inspect } end name.to_sym end def takes_argument? !!@argument end def optional_argument? !!@optional_argument end def required? !!@required end def has_default? !!@default end def default=(proc) if required raise ArgumentError, _("%{name} can't be optional and have a default value") % { name: self } end unless proc.is_a? Proc #TRANSLATORS 'proc' is a Ruby block of code raise ArgumentError, _("default value for %{name} is a %{class_name}, not a proc") % { name: self, class_name: proc.class.name.inspect } end @default = proc end def default @default and @default.call end attr_reader :parent, :name, :aliases, :optparse attr_accessor :required def required=(value) if has_default? raise ArgumentError, _("%{name} can't be optional and have a default value") % { name: self } end @required = value end attr_accessor :before_action def before_action=(proc) unless proc.is_a? Proc #TRANSLATORS 'proc' is a Ruby block of code raise ArgumentError, _("before action hook for %{name} is a %{class_name}, not a proc") % { name: self, class_name: proc.class.name.inspect } end @before_action = @parent.__send__(:__add_method, __decoration_name(:before), proc) end attr_accessor :after_action def after_action=(proc) unless proc.is_a? Proc #TRANSLATORS 'proc' is a Ruby block of code raise ArgumentError, _("after action hook for %{name} is a %{class_name}, not a proc") % { name: self, class_name: proc.class.name.inspect } end @after_action = @parent.__send__(:__add_method, __decoration_name(:after), proc) end def __decoration_name(type) if @parent.is_a? Puppet::Interface::Action then :"option #{name} from #{parent.name} #{type} decoration" else :"option #{name} #{type} decoration" end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/interface/option_builder.rb������������������������������������������������0000644�0052762�0001160�00000007352�13417161721�022407� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# @api public class Puppet::Interface::OptionBuilder # The option under construction # @return [Puppet::Interface::Option] # @api private attr_reader :option # Build an option # @return [Puppet::Interface::Option] # @api private def self.build(face, *declaration, &block) new(face, *declaration, &block).option end def initialize(face, *declaration, &block) @face = face @option = Puppet::Interface::Option.new(face, *declaration) instance_eval(&block) if block_given? @option end # Metaprogram the simple DSL from the option class. Puppet::Interface::Option.instance_methods.grep(/=$/).each do |setter| next if setter =~ /^=/ dsl = setter.to_s.chomp('=') unless private_method_defined? dsl define_method(dsl) do |value| @option.send(setter, value) end end end # Override some methods that deal in blocks, not objects. # Sets a block to be executed when an action is invoked before the # main action code. This is most commonly used to validate an option. # @yieldparam action [Puppet::Interface::Action] The action being # invoked # @yieldparam args [Array] The arguments given to the action # @yieldparam options [Hash<Symbol=>Object>] Any options set # @api public # @dsl Faces def before_action(&block) unless block #TRANSLATORS 'before_action' is a method name and should not be translated raise ArgumentError, _("%{option} before_action requires a block") % { option: @option } end if @option.before_action #TRANSLATORS 'before_action' is a method name and should not be translated raise ArgumentError, _("%{option} already has a before_action set") % { option: @option } end unless block.arity == 3 then #TRANSLATORS 'before_action' is a method name and should not be translated raise ArgumentError, _("before_action takes three arguments, action, args, and options") end @option.before_action = block end # Sets a block to be executed after an action is invoked. # !(see before_action) # @api public # @dsl Faces def after_action(&block) unless block #TRANSLATORS 'after_action' is a method name and should not be translated raise ArgumentError, _("%{option} after_action requires a block") % { option: @option } end if @option.after_action #TRANSLATORS 'after_action' is a method name and should not be translated raise ArgumentError, _("%{option} already has an after_action set") % { option: @option } end unless block.arity == 3 then #TRANSLATORS 'after_action' is a method name and should not be translated raise ArgumentError, _("after_action takes three arguments, action, args, and options") end @option.after_action = block end # Sets whether the option is required. If no argument is given it # defaults to setting it as a required option. # @api public # @dsl Faces def required(value = true) @option.required = value end # Sets a block that will be used to compute the default value for this # option. It will be evaluated when the action is invoked. The block # should take no arguments. # @api public # @dsl Faces def default_to(&block) unless block #TRANSLATORS 'default_to' is a method name and should not be translated raise ArgumentError, _("%{option} default_to requires a block") % { option: @option } end if @option.has_default? raise ArgumentError, _("%{option} already has a default value") % { option: @option } end unless block.arity == 0 #TRANSLATORS 'default_to' is a method name and should not be translated raise ArgumentError, _("%{option} default_to block should not take any arguments") % { option: @option } end @option.default = block end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/interface/option_manager.rb������������������������������������������������0000644�0052762�0001160�00000006070�13417161721�022367� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This class is not actually public API, but the method # {Puppet::Interface::OptionManager#option option} is public when used # as part of the Faces DSL (i.e. from within a # {Puppet::Interface.define define} block). # @api public module Puppet::Interface::OptionManager # @api private def display_global_options(*args) @display_global_options ||= [] [args].flatten.each do |refopt| unless Puppet.settings.include?(refopt) #TRANSLATORS 'Puppet.settings' references to the Puppet settings options and should not be translated raise ArgumentError, _("Global option %{option} does not exist in Puppet.settings") % { option: refopt } end @display_global_options << refopt if refopt end @display_global_options.uniq! @display_global_options end alias :display_global_option :display_global_options def all_display_global_options walk_inheritance_tree(@display_global_options, :all_display_global_options) end # @api private def walk_inheritance_tree(start, sym) result = (start || []) if self.is_a?(Class) and superclass.respond_to?(sym) result = superclass.send(sym) + result elsif self.class.respond_to?(sym) result = self.class.send(sym) + result end return result end # Declare that this app can take a specific option, and provide the # code to do so. See {Puppet::Interface::ActionBuilder#option} for # details. # # @api public # @dsl Faces def option(*declaration, &block) add_option Puppet::Interface::OptionBuilder.build(self, *declaration, &block) end # @api private def add_option(option) # @options collects the added options in the order they're declared. # @options_hash collects the options keyed by alias for quick lookups. @options ||= [] @options_hash ||= {} option.aliases.each do |name| if conflict = get_option(name) then raise ArgumentError, _("Option %{option} conflicts with existing option %{conflict}") % { option: option, conflict: conflict } end actions.each do |action| action = get_action(action) if conflict = action.get_option(name) then raise ArgumentError, _("Option %{option} conflicts with existing option %{conflict} on %{action}") % { option: option, conflict: conflict, action: action } end end end @options << option.name option.aliases.each do |name| @options_hash[name] = option end return option end # @api private def options walk_inheritance_tree(@options, :options) end # @api private def get_option(name, with_inherited_options = true) @options_hash ||= {} result = @options_hash[name.to_sym] if result.nil? and with_inherited_options then if self.is_a?(Class) and superclass.respond_to?(:get_option) result = superclass.get_option(name) elsif self.class.respond_to?(:get_option) result = self.class.get_option(name) end end return result end # @api private def option?(name) options.include? name.to_sym end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/interface/action.rb��������������������������������������������������������0000644�0052762�0001160�00000033637�13417161721�020653� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/methodhelper' require 'prettyprint' # This represents an action that is attached to a face. Actions should # be constructed by calling {Puppet::Interface::ActionManager#action}, # which is available on {Puppet::Interface}, and then calling methods of # {Puppet::Interface::ActionBuilder} in the supplied block. # @api private class Puppet::Interface::Action include Puppet::Util::MethodHelper extend Puppet::Interface::DocGen include Puppet::Interface::FullDocs # @api private def initialize(face, name, attrs = {}) raise "#{name.inspect} is an invalid action name" unless name.to_s =~ /^[a-z]\w*$/ @face = face @name = name.to_sym # The few bits of documentation we actually demand. The default license # is a favour to our end users; if you happen to get that in a core face # report it as a bug, please. --daniel 2011-04-26 @authors = [] @license = 'All Rights Reserved' set_options(attrs) # @options collects the added options in the order they're declared. # @options_hash collects the options keyed by alias for quick lookups. @options = [] @display_global_options = [] @options_hash = {} @when_rendering = {} end # This is not nice, but it is the easiest way to make us behave like the # Ruby Method object rather than UnboundMethod. Duplication is vaguely # annoying, but at least we are a shallow clone. --daniel 2011-04-12 # @return [void] # @api private def __dup_and_rebind_to(to) bound_version = self.dup bound_version.instance_variable_set(:@face, to) return bound_version end def to_s() "#{@face}##{@name}" end # The name of this action # @return [Symbol] attr_reader :name # The face this action is attached to # @return [Puppet::Interface] attr_reader :face # Whether this is the default action for the face # @return [Boolean] # @api private attr_accessor :default def default? !!@default end ######################################################################## # Documentation... attr_doc :returns attr_doc :arguments def synopsis build_synopsis(@face.name, default? ? nil : name, arguments) end ######################################################################## # Support for rendering formats and all. # @api private def when_rendering(type) unless type.is_a? Symbol raise ArgumentError, _("The rendering format must be a symbol, not %{class_name}") % { class_name: type.class.name } end # Do we have a rendering hook for this name? return @when_rendering[type].bind(@face) if @when_rendering.has_key? type # How about by another name? alt = type.to_s.sub(/^to_/, '').to_sym return @when_rendering[alt].bind(@face) if @when_rendering.has_key? alt # Guess not, nothing to run. return nil end # @api private def set_rendering_method_for(type, proc) unless proc.is_a? Proc msg = if proc.nil? #TRANSLATORS 'set_rendering_method_for' and 'Proc' should not be translated _("The second argument to set_rendering_method_for must be a Proc") else #TRANSLATORS 'set_rendering_method_for' and 'Proc' should not be translated _("The second argument to set_rendering_method_for must be a Proc, not %{class_name}") % { class_name: proc.class.name } end raise ArgumentError, msg end if proc.arity != 1 and proc.arity != (@positional_arg_count + 1) msg = if proc.arity < 0 then #TRANSLATORS 'when_rendering', 'when_invoked' are method names and should not be translated _("The when_rendering method for the %{face} face %{name} action takes either just one argument,"\ " the result of when_invoked, or the result plus the %{arg_count} arguments passed to the"\ " when_invoked block, not a variable number") % { face: @face.name, name: name, arg_count: @positional_arg_count } else #TRANSLATORS 'when_rendering', 'when_invoked' are method names and should not be translated _("The when_rendering method for the %{face} face %{name} action takes either just one argument,"\ " the result of when_invoked, or the result plus the %{arg_count} arguments passed to the"\ " when_invoked block, not %{string}") % { face: @face.name, name: name, arg_count: @positional_arg_count, string: proc.arity.to_s } end raise ArgumentError, msg end unless type.is_a? Symbol raise ArgumentError, _("The rendering format must be a symbol, not %{class_name}") % { class_name: type.class.name } end if @when_rendering.has_key? type then raise ArgumentError, _("You can't define a rendering method for %{type} twice") % { type: type } end # Now, the ugly bit. We add the method to our interface object, and # retrieve it, to rotate through the dance of getting a suitable method # object out of the whole process. --daniel 2011-04-18 @when_rendering[type] = @face.__send__( :__add_method, __render_method_name_for(type), proc) end # @return [void] # @api private def __render_method_name_for(type) :"#{name}_when_rendering_#{type}" end private :__render_method_name_for # @api private # @return [Symbol] attr_accessor :render_as def render_as=(value) @render_as = value.to_sym end # @api private # @return [void] def deprecate @deprecated = true end # @api private # @return [Boolean] def deprecated? @deprecated end ######################################################################## # Initially, this was defined to allow the @action.invoke pattern, which is # a very natural way to invoke behaviour given our introspection # capabilities. Heck, our initial plan was to have the faces delegate to # the action object for invocation and all. # # It turns out that we have a binding problem to solve: @face was bound to # the parent class, not the subclass instance, and we don't pass the # appropriate context or change the binding enough to make this work. # # We could hack around it, by either mandating that you pass the context in # to invoke, or try to get the binding right, but that has probably got # subtleties that we don't instantly think of – especially around threads. # # So, we are pulling this method for now, and will return it to life when we # have the time to resolve the problem. For now, you should replace... # # @action = @face.get_action(name) # @action.invoke(arg1, arg2, arg3) # # ...with... # # @action = @face.get_action(name) # @face.send(@action.name, arg1, arg2, arg3) # # I understand that is somewhat cumbersome, but it functions as desired. # --daniel 2011-03-31 # # PS: This code is left present, but commented, to support this chunk of # documentation, for the benefit of the reader. # # def invoke(*args, &block) # @face.send(name, *args, &block) # end # We need to build an instance method as a wrapper, using normal code, to be # able to expose argument defaulting between the caller and definer in the # Ruby API. An extra method is, sadly, required for Ruby 1.8 to work since # it doesn't expose bind on a block. # # Hopefully we can improve this when we finally shuffle off the last of Ruby # 1.8 support, but that looks to be a few "enterprise" release eras away, so # we are pretty stuck with this for now. # # Patches to make this work more nicely with Ruby 1.9 using runtime version # checking and all are welcome, provided that they don't change anything # outside this little ol' bit of code and all. # # Incidentally, we though about vendoring evil-ruby and actually adjusting # the internal C structure implementation details under the hood to make # this stuff work, because it would have been cleaner. Which gives you an # idea how motivated we were to make this cleaner. Sorry. # --daniel 2011-03-31 # The arity of the action # @return [Integer] attr_reader :positional_arg_count # The block that is executed when the action is invoked # @return [block] attr_accessor :when_invoked def when_invoked=(block) internal_name = "#{@name} implementation, required on Ruby 1.8".to_sym arity = @positional_arg_count = block.arity if arity == 0 then # This will never fire on 1.8.7, which treats no arguments as "*args", # but will on 1.9.2, which treats it as "no arguments". Which bites, # because this just begs for us to wind up in the horrible situation # where a 1.8 vs 1.9 error bites our end users. --daniel 2011-04-19 #TRANSLATORS 'when_invoked' should not be translated raise ArgumentError, _("when_invoked requires at least one argument (options) for action %{name}") % { name: @name } elsif arity > 0 then range = Range.new(1, arity - 1) decl = range.map { |x| "arg#{x}" } << "options = {}" optn = "" args = "[" + (range.map { |x| "arg#{x}" } << "options").join(", ") + "]" else range = Range.new(1, arity.abs - 1) decl = range.map { |x| "arg#{x}" } << "*rest" optn = "rest << {} unless rest.last.is_a?(Hash)" if arity == -1 then args = "rest" else args = "[" + range.map { |x| "arg#{x}" }.join(", ") + "] + rest" end end file = __FILE__ + "+eval[wrapper]" line = __LINE__ + 2 # <== points to the same line as 'def' in the wrapper. wrapper = <<WRAPPER def #{@name}(#{decl.join(", ")}) #{optn} args = #{args} action = get_action(#{name.inspect}) args << action.validate_and_clean(args.pop) __invoke_decorations(:before, action, args, args.last) rval = self.__send__(#{internal_name.inspect}, *args) __invoke_decorations(:after, action, args, args.last) return rval end WRAPPER if @face.is_a?(Class) @face.class_eval do eval wrapper, nil, file, line end @face.send(:define_method, internal_name, &block) @when_invoked = @face.instance_method(name) else @face.instance_eval do eval wrapper, nil, file, line end @face.meta_def(internal_name, &block) @when_invoked = @face.method(name).unbind end end def add_option(option) option.aliases.each do |name| if conflict = get_option(name) then raise ArgumentError, _("Option %{option} conflicts with existing option %{conflict}") % { option: option, conflict: conflict } elsif conflict = @face.get_option(name) then raise ArgumentError, _("Option %{option} conflicts with existing option %{conflict} on %{face}") % { option: option, conflict: conflict, face: @face } end end @options << option.name option.aliases.each do |name| @options_hash[name] = option end option end def option?(name) @options_hash.include? name.to_sym end def options @face.options + @options end def add_display_global_options(*args) @display_global_options ||= [] [args].flatten.each do |refopt| unless Puppet.settings.include? refopt #TRANSLATORS 'Puppet.settings' should not be translated raise ArgumentError, _("Global option %{option} does not exist in Puppet.settings") % { option: refopt } end @display_global_options << refopt end @display_global_options.uniq! @display_global_options end def display_global_options(*args) args ? add_display_global_options(args) : @display_global_options + @face.display_global_options end alias :display_global_option :display_global_options def get_option(name, with_inherited_options = true) option = @options_hash[name.to_sym] if option.nil? and with_inherited_options option = @face.get_option(name) end option end def validate_and_clean(original) # The final set of arguments; effectively a hand-rolled shallow copy of # the original, which protects the caller from the surprises they might # get if they passed us a hash and we mutated it... result = {} # Check for multiple aliases for the same option, and canonicalize the # name of the argument while we are about it. overlap = Hash.new do |h, k| h[k] = [] end unknown = [] original.keys.each do |name| if option = get_option(name) then canonical = option.name if result.has_key? canonical overlap[canonical] << name else result[canonical] = original[name] end elsif Puppet.settings.include? name result[name] = original[name] else unknown << name end end unless overlap.empty? overlap_list = overlap.map {|k, v| "(#{k}, #{v.sort.join(', ')})" }.join(", ") raise ArgumentError, _("Multiple aliases for the same option passed: %{overlap_list}") % { overlap_list: overlap_list } end unless unknown.empty? unknown_list = unknown.sort.join(", ") raise ArgumentError, _("Unknown options passed: %{unknown_list}") % { unknown_list: unknown_list } end # Inject default arguments and check for missing mandating options. missing = [] options.map {|x| get_option(x) }.each do |option| name = option.name next if result.has_key? name if option.has_default? result[name] = option.default elsif option.required? missing << name end end unless missing.empty? missing_list = missing.sort.join(', ') raise ArgumentError, _("The following options are required: %{missing_list}") % { missing_list: missing_list } end # All done. return result end ######################################################################## # Support code for action decoration; see puppet/interface.rb for the gory # details of why this is hidden away behind private. --daniel 2011-04-15 private # @return [void] # @api private def __add_method(name, proc) @face.__send__ :__add_method, name, proc end end �������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/interface/face_collection.rb�����������������������������������������������0000644�0052762�0001160�00000011741�13417161721�022477� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Interface::FaceCollection @faces = Hash.new { |hash, key| hash[key] = {} } @loader = Puppet::Util::Autoload.new(:application, 'puppet/face') def self.faces unless @loaded @loaded = true names = @loader.files_to_load.map {|fn| ::File.basename(fn, '.rb')}.uniq names.each {|name| self[name, :current]} end @faces.keys.select {|name| @faces[name].length > 0 } end def self.[](name, version) name = underscorize(name) get_face(name, version) or load_face(name, version) end def self.get_action_for_face(name, action_name, version) name = underscorize(name) # If the version they request specifically doesn't exist, don't search # elsewhere. Usually this will start from :current and all... return nil unless face = self[name, version] unless action = face.get_action(action_name) # ...we need to search for it bound to an o{lder,ther} version. Since # we load all actions when the face is first references, this will be in # memory in the known set of versions of the face. (@faces[name].keys - [ :current ]).sort.reverse_each do |vers| break if action = @faces[name][vers].get_action(action_name) end end return action end # get face from memory, without loading. def self.get_face(name, pattern) return nil unless @faces.has_key? name return @faces[name][:current] if pattern == :current versions = @faces[name].keys - [ :current ] range = pattern.is_a?(SemanticPuppet::Version) ? SemanticPuppet::VersionRange.new(pattern, pattern) : SemanticPuppet::VersionRange.parse(pattern) found = find_matching(range, versions) return @faces[name][found] end def self.find_matching(range, versions) versions.select { |v| range === v }.sort.last end # try to load the face, and return it. def self.load_face(name, version) # We always load the current version file; the common case is that we have # the expected version and any compatibility versions in the same file, # the default. Which means that this is almost always the case. # # We use require to avoid executing the code multiple times, like any # other Ruby library that we might want to use. --daniel 2011-04-06 if safely_require name then # If we wanted :current, we need to index to find that; direct version # requests just work as they go. --daniel 2011-04-06 if version == :current then # We need to find current out of this. This is the largest version # number that doesn't have a dedicated on-disk file present; those # represent "experimental" versions of faces, which we don't fully # support yet. # # We walk the versions from highest to lowest and take the first version # that is not defined in an explicitly versioned file on disk as the # current version. # # This constrains us to only ship experimental versions with *one* # version in the file, not multiple, but given you can't reliably load # them except by side-effect when you ignore that rule this seems safe # enough... # # Given those constraints, and that we are not going to ship a versioned # interface that is not :current in this release, we are going to leave # these thoughts in place, and just punt on the actual versioning. # # When we upgrade the core to support multiple versions we can solve the # problems then; as lazy as possible. # # We do support multiple versions in the same file, though, so we sort # versions here and return the last item in that set. # # --daniel 2011-04-06 latest_ver = @faces[name].keys.sort.last @faces[name][:current] = @faces[name][latest_ver] end end unless version == :current or get_face(name, version) # Try an obsolete version of the face, if needed, to see if that helps? safely_require name, version end return get_face(name, version) end def self.safely_require(name, version = nil) path = @loader.expand(version ? ::File.join(version.to_s, name.to_s) : name) require path true rescue LoadError => e raise unless e.message =~ %r{-- #{path}$} # ...guess we didn't find the file; return a much better problem. nil rescue SyntaxError => e raise unless e.message =~ %r{#{path}\.rb:\d+: } Puppet.err _("Failed to load face %{name}:\n%{detail}") % { name: name, detail: e } # ...but we just carry on after complaining. nil end def self.register(face) @faces[underscorize(face.name)][face.version] = face end def self.underscorize(name) unless name.to_s =~ /^[-_a-z][-_a-z0-9]*$/i then #TRANSLATORS 'face' refers to a programming API in Puppet raise ArgumentError, _("%{name} (%{class_name}) is not a valid face name") % { name: name.inspect, class_name: name.class } end name.to_s.downcase.split(/[-_]/).join('_').to_sym end end �������������������������������puppet-5.5.10/lib/puppet/metatype/������������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�016732� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/metatype/manager.rb��������������������������������������������������������0000644�0052762�0001160�00000014645�13417161721�020676� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet' require 'puppet/util/classgen' require 'puppet/node/environment' # This module defines methods dealing with Type management. # This module gets included into the Puppet::Type class, it's just split out here for clarity. # @api public # module Puppet::MetaType module Manager include Puppet::Util::ClassGen # An implementation specific method that removes all type instances during testing. # @note Only use this method for testing purposes. # @api private # def allclear @types.each { |name, type| type.clear } end # Clears any types that were used but absent when types were last loaded. # @note Used after each catalog compile when always_retry_plugins is false # @api private # def clear_misses unless @types.nil? @types.delete_if {|_, v| v.nil? } end end # Iterates over all already loaded Type subclasses. # @yield [t] a block receiving each type # @yieldparam t [Puppet::Type] each defined type # @yieldreturn [Object] the last returned object is also returned from this method # @return [Object] the last returned value from the block. def eachtype @types.each do |name, type| # Only consider types that have names #if ! type.parameters.empty? or ! type.validproperties.empty? yield type #end end end # Loads all types. # @note Should only be used for purposes such as generating documentation as this is potentially a very # expensive operation. # @return [void] # def loadall typeloader.loadall end # Defines a new type or redefines an existing type with the given name. # A convenience method on the form `new<name>` where name is the name of the type is also created. # (If this generated method happens to clash with an existing method, a warning is issued and the original # method is kept). # # @param name [String] the name of the type to create or redefine. # @param options [Hash] options passed on to {Puppet::Util::ClassGen#genclass} as the option `:attributes`. # @option options [Puppet::Type] # Puppet::Type. This option is not passed on as an attribute to genclass. # @yield [ ] a block evaluated in the context of the created class, thus allowing further detailing of # that class. # @return [Class<inherits Puppet::Type>] the created subclass # @see Puppet::Util::ClassGen.genclass # # @dsl type # @api public def newtype(name, options = {}, &block) # Handle backward compatibility unless options.is_a?(Hash) #TRANSLATORS 'Puppet::Type.newtype' should not be translated Puppet.warning(_("Puppet::Type.newtype(%{name}) now expects a hash as the second argument, not %{argument}") % { name: name, argument: options.inspect}) end # First make sure we don't have a method sitting around name = name.intern newmethod = "new#{name}" # Used for method manipulation. selfobj = singleton_class @types ||= {} if @types.include?(name) if self.respond_to?(newmethod) # Remove the old newmethod selfobj.send(:remove_method,newmethod) end end options = symbolize_options(options) # Then create the class. klass = genclass( name, :parent => Puppet::Type, :overwrite => true, :hash => @types, :attributes => options, &block ) # Now define a "new<type>" method for convenience. if self.respond_to? newmethod # Refuse to overwrite existing methods like 'newparam' or 'newtype'. #TRANSLATORS 'new%{method}' will become a method name, do not translate this string Puppet.warning(_("'new%{method}' method already exists; skipping") % { method: name.to_s }) else selfobj.send(:define_method, newmethod) do |*args| klass.new(*args) end end # If they've got all the necessary methods defined and they haven't # already added the property, then do so now. klass.ensurable if klass.ensurable? and ! klass.validproperty?(:ensure) # Now set up autoload any providers that might exist for this type. klass.providerloader = Puppet::Util::Autoload.new(klass, "puppet/provider/#{klass.name.to_s}") # We have to load everything so that we can figure out the default provider. klass.providerloader.loadall Puppet.lookup(:current_environment) klass.providify unless klass.providers.empty? loc = block_given? ? block.source_location : nil uri = loc.nil? ? nil : URI("#{Puppet::Util.path_to_uri(loc[0])}?line=#{loc[1]}") Puppet::Pops::Loaders.register_runtime3_type(name, uri) klass end # Removes an existing type. # @note Only use this for testing. # @api private def rmtype(name) # Then create the class. rmclass(name, :hash => @types) singleton_class.send(:remove_method, "new#{name}") if respond_to?("new#{name}") end # Returns a Type instance by name. # This will load the type if not already defined. # @param [String, Symbol] name of the wanted Type # @return [Puppet::Type, nil] the type or nil if the type was not defined and could not be loaded # def type(name) # Avoid loading if name obviously is not a type name if name.to_s.include?(':') return nil end @types ||= {} # We are overwhelmingly symbols here, which usually match, so it is worth # having this special-case to return quickly. Like, 25K symbols vs. 300 # strings in this method. --daniel 2012-07-17 return @types[name] if @types.include? name # Try mangling the name, if it is a string. if name.is_a? String name = name.downcase.intern return @types[name] if @types.include? name end # Try loading the type. if typeloader.load(name, Puppet.lookup(:current_environment)) #TRANSLATORS 'puppet/type/%{name}' should not be translated Puppet.warning(_("Loaded puppet/type/%{name} but no class was created") % { name: name }) unless @types.include? name elsif !Puppet[:always_retry_plugins] # PUP-5482 - Only look for a type once if plugin retry is disabled @types[name] = nil end # ...and I guess that is that, eh. return @types[name] end # Creates a loader for Puppet types. # Defaults to an instance of {Puppet::Util::Autoload} if no other auto loader has been set. # @return [Puppet::Util::Autoload] the loader to use. # @api private def typeloader unless defined?(@typeloader) @typeloader = Puppet::Util::Autoload.new(self, "puppet/type") end @typeloader end end end �������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module/��������������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�016367� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module/task.rb�������������������������������������������������������������0000644�0052762�0001160�00000005610�13417161721�017653� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/logging' class Puppet::Module class Task class Error < Puppet::Error; end class InvalidName < Error; end class InvalidFile < Error; end class TaskNotFound < Error; end FORBIDDEN_EXTENSIONS = %w{.conf .md} def self.is_task_name?(name) return true if name =~ /^[a-z][a-z0-9_]*$/ return false end # Determine whether a file has a legal name for either a task's executable or metadata file. def self.is_tasks_filename?(path) name_less_extension = File.basename(path, '.*') return false if not is_task_name?(name_less_extension) FORBIDDEN_EXTENSIONS.each do |ext| return false if path.end_with?(ext) end return true end def self.is_tasks_metadata_filename?(name) is_tasks_filename?(name) && name.end_with?('.json') end def self.is_tasks_executable_filename?(name) is_tasks_filename?(name) && !name.end_with?('.json') end def self.tasks_in_module(pup_module) Dir.glob(File.join(pup_module.tasks_directory, '*')) .keep_if { |f| is_tasks_filename?(f) } .group_by { |f| task_name_from_path(f) } .map { |task, files| new_with_files(pup_module, task, files) } end attr_reader :name, :module, :metadata_file, :files def initialize(pup_module, task_name, files, metadata_file = nil) if !Puppet::Module::Task.is_task_name?(task_name) raise InvalidName, _("Task names must start with a lowercase letter and be composed of only lowercase letters, numbers, and underscores") end all_files = metadata_file.nil? ? files : files + [metadata_file] all_files.each do |f| if !f.start_with?(pup_module.tasks_directory) msg = _("The file '%{path}' is not located in the %{module_name} module's tasks directory") % {path: f.to_s, module_name: pup_module.name} # we can include some extra context for the log message: Puppet.err(msg + " (#{pup_module.tasks_directory})") raise InvalidFile, msg end end name = task_name == "init" ? pup_module.name : "#{pup_module.name}::#{task_name}" @module = pup_module @name = name @metadata_file = metadata_file if metadata_file @files = files end def ==(other) self.name == other.name && self.module == other.module end def self.new_with_files(pup_module, name, tasks_files) files = tasks_files.map do |filename| File.join(pup_module.tasks_directory, File.basename(filename)) end metadata_files, exe_files = files.partition { |f| is_tasks_metadata_filename?(f) } Puppet::Module::Task.new(pup_module, name, exe_files, metadata_files.first) end private_class_method :new_with_files def self.task_name_from_path(path) return File.basename(path, '.*') end private_class_method :task_name_from_path end end ������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/���������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�017424� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/applications/��������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�022112� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/applications/checksummer.rb������������������������������������0000644�0052762�0001160�00000003401�13417161721�024736� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/json' require 'puppet/module_tool/checksums' module Puppet::ModuleTool module Applications class Checksummer < Application def initialize(path, options = {}) @path = Pathname.new(path) super(options) end def run changes = [] sums = Puppet::ModuleTool::Checksums.new(@path) checksums.each do |child_path, canonical_checksum| # Avoid checksumming the checksums.json file next if File.basename(child_path) == "checksums.json" path = @path + child_path unless path.exist? && canonical_checksum == sums.checksum(path) changes << child_path end end # Return an Array of strings representing file paths of files that have # been modified since this module was installed. All paths are relative # to the installed module directory. This return value is used by the # module_tool face changes action, and displayed on the console. # # Example return value: # # [ "REVISION", "manifests/init.pp"] # changes end private def checksums if checksums_file.exist? Puppet::Util::Json.load(checksums_file.read) elsif metadata_file.exist? # Check metadata.json too; legacy modules store their checksums there. Puppet::Util::Json.load(metadata_file.read)['checksums'] or raise ArgumentError, _("No file containing checksums found.") else raise ArgumentError, _("No file containing checksums found.") end end def metadata_file @path + 'metadata.json' end def checksums_file @path + 'checksums.json' end end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/applications/searcher.rb���������������������������������������0000644�0052762�0001160�00000001274�13417161721�024232� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::ModuleTool module Applications class Searcher < Application include Puppet::Forge::Errors def initialize(term, forge, options = {}) @term = term @forge = forge super(options) end def run results = {} begin Puppet.notice _("Searching %{host} ...") % { host: @forge.host } results[:answers] = @forge.search(@term) results[:result] = :success rescue ForgeError => e results[:result] = :failure results[:error] = { :oneline => e.message, :multiline => e.multiline, } end results end end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/applications/application.rb������������������������������������0000644�0052762�0001160�00000005520�13417161721�024737� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'net/http' require 'puppet/util/json' require 'puppet/util/colors' module Puppet::ModuleTool module Applications class Application include Puppet::Util::Colors def self.run(*args) new(*args).run end attr_accessor :options def initialize(options = {}) @options = options end def run raise NotImplementedError, "Should be implemented in child classes." end def discuss(response, success, failure) case response when Net::HTTPOK, Net::HTTPCreated Puppet.notice success else errors = Puppet::Util::Json.load(response.body)['error'] rescue "HTTP #{response.code}, #{response.body}" Puppet.warning "#{failure} (#{errors})" end end def metadata(require_metadata = false) return @metadata if @metadata @metadata = Puppet::ModuleTool::Metadata.new unless @path raise ArgumentError, _("Could not determine module path") end if require_metadata && !Puppet::ModuleTool.is_module_root?(@path) raise ArgumentError, _("Unable to find metadata.json in module root at %{path} See https://puppet.com/docs/puppet/latest/modules_publishing.html for required file format.") % { path: @path } end metadata_path = File.join(@path, 'metadata.json') if File.file?(metadata_path) File.open(metadata_path) do |f| begin @metadata.update(Puppet::Util::Json.load(f)) rescue Puppet::Util::Json::ParserError => ex raise ArgumentError, _("Could not parse JSON %{metadata_path}") % { metadata_path: metadata_path }, ex.backtrace end end end if File.file?(File.join(@path, 'Modulefile')) Puppet.warning _("A Modulefile was found in the root directory of the module. This file will be ignored and can safely be removed.") end return @metadata end def load_metadata! @metadata = nil metadata(true) end def parse_filename(filename) if match = /^((.*?)-(.*?))-(\d+\.\d+\.\d+.*?)$/.match(File.basename(filename, '.tar.gz')) module_name, author, shortname, version = match.captures else raise ArgumentError, _("Could not parse filename to obtain the username, module name and version. (%{release_name})") % { release_name: @release_name } end unless SemanticPuppet::Version.valid?(version) raise ArgumentError, _("Invalid version format: %{version} (Semantic Versions are acceptable: http://semver.org)") % { version: version } end return { :module_name => module_name, :author => author, :dir_name => shortname, :version => version } end end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/applications/builder.rb����������������������������������������0000644�0052762�0001160�00000011646�13417161721�024070� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'fileutils' require 'puppet/util/json' require 'puppet/file_system' require 'pathspec' require 'facter' module Puppet::ModuleTool module Applications class Builder < Application def initialize(path, options = {}) @path = File.expand_path(path) @pkg_path = File.join(@path, 'pkg') super(options) end def run # Disallow anything that invokes md5 to avoid un-friendly termination due to FIPS raise _("Module building is prohibited in FIPS mode.") if Facter.value(:fips_enabled) load_metadata! create_directory copy_contents write_json Puppet.notice _("Building %{path} for release") % { path: @path } pack relative = Pathname.new(archive_file).relative_path_from(Pathname.new(File.expand_path(Dir.pwd))) # Return the Pathname object representing the path to the release # archive just created. This return value is used by the module_tool # face build action, and displayed to on the console using the to_s # method. # # Example return value: # # <Pathname:puppetlabs-apache/pkg/puppetlabs-apache-0.0.1.tar.gz> # relative end private def archive_file File.join(@pkg_path, "#{metadata.release_name}.tar.gz") end def pack FileUtils.rm archive_file rescue nil tar = Puppet::ModuleTool::Tar.instance Dir.chdir(@pkg_path) do tar.pack(metadata.release_name, archive_file) end end def create_directory FileUtils.mkdir(@pkg_path) rescue nil if File.directory?(build_path) FileUtils.rm_rf(build_path, :secure => true) end FileUtils.mkdir(build_path) end def ignored_files if @ignored_files return @ignored_files else pmtignore = File.join(@path, '.pmtignore') gitignore = File.join(@path, '.gitignore') if File.file? pmtignore @ignored_files = PathSpec.new Puppet::FileSystem.read(pmtignore, :encoding => 'utf-8') elsif File.file? gitignore @ignored_files = PathSpec.new Puppet::FileSystem.read(gitignore, :encoding => 'utf-8') else @ignored_files = PathSpec.new end end end def copy_contents symlinks = [] Find.find(File.join(@path)) do |path| # because Find.find finds the path itself if path == @path next end # Needed because pathspec looks for a trailing slash in the path to # determine if a path is a directory path = path.to_s + '/' if File.directory? path # if it matches, then prune it with fire unless ignored_files.match_paths([path], @path).empty? Find.prune end # don't copy all the Puppet ARTIFACTS rel = Pathname.new(path).relative_path_from(Pathname.new(@path)) case rel.to_s when *Puppet::ModuleTool::ARTIFACTS Find.prune end # make dir tree, copy files, and add symlinks to the symlinks list dest = "#{build_path}/#{rel.to_s}" if File.directory? path FileUtils.mkdir dest, :mode => File.stat(path).mode elsif Puppet::FileSystem.symlink? path symlinks << path else FileUtils.cp path, dest, :preserve => true end end # send a message about each symlink and raise an error if they exist unless symlinks.empty? symlinks.each do |s| s = Pathname.new s mpath = Pathname.new @path Puppet.warning _("Symlinks in modules are unsupported. Please investigate symlink %{from} -> %{to}.") % { from: s.relative_path_from(mpath), to: s.realpath.relative_path_from(mpath) } end raise Puppet::ModuleTool::Errors::ModuleToolError, _("Found symlinks. Symlinks in modules are not allowed, please remove them.") end end def write_json metadata_path = File.join(build_path, 'metadata.json') if metadata.to_hash.include? 'checksums' Puppet.warning _("A 'checksums' field was found in metadata.json. This field will be ignored and can safely be removed.") end # TODO: This may necessarily change the order in which the metadata.json # file is packaged from what was written by the user. This is a # regretable, but required for now. Puppet::FileSystem.open(metadata_path, nil, 'w:UTF-8') do |f| f.write(metadata.to_json) end Puppet::FileSystem.open(File.join(build_path, 'checksums.json'), nil, 'wb') do |f| f.write(Puppet::Util::Json.dump(Checksums.new(build_path), :pretty => true)) end end def build_path @build_path ||= File.join(@pkg_path, metadata.release_name) end end end end ������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/applications/installer.rb��������������������������������������0000644�0052762�0001160�00000032300�13417161721�024425� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'open-uri' require 'pathname' require 'fileutils' require 'tmpdir' require 'puppet/forge' require 'puppet/module_tool' require 'puppet/module_tool/shared_behaviors' require 'puppet/module_tool/install_directory' require 'puppet/module_tool/local_tarball' require 'puppet/module_tool/installed_modules' module Puppet::ModuleTool module Applications class Installer < Application include Puppet::ModuleTool::Errors include Puppet::Forge::Errors def initialize(name, install_dir, options = {}) super(options) @action = :install @environment = options[:environment_instance] @ignore_dependencies = forced? || options[:ignore_dependencies] @name = name @install_dir = install_dir @strict_semver = !!options[:strict_semver] Puppet::Forge::Cache.clean @local_tarball = Puppet::FileSystem.exist?(name) if @local_tarball release = local_tarball_source.release @name = release.name options[:version] = release.version.to_s SemanticPuppet::Dependency.add_source(local_tarball_source) # If we're operating on a local tarball and ignoring dependencies, we # don't need to search any additional sources. This will cut down on # unnecessary network traffic. unless @ignore_dependencies SemanticPuppet::Dependency.add_source(installed_modules_source) SemanticPuppet::Dependency.add_source(module_repository) end else SemanticPuppet::Dependency.add_source(installed_modules_source) unless forced? SemanticPuppet::Dependency.add_source(module_repository) end end def run # Disallow anything that invokes md5 to avoid un-friendly termination due to FIPS raise _("Module install is prohibited in FIPS mode.") if Facter.value(:fips_enabled) name = @name.tr('/', '-') version = options[:version] || '>= 0.0.0' results = { :action => :install, :module_name => name, :module_version => version } begin if installed_module = installed_modules[name] unless forced? if Puppet::Module.parse_range(version, @strict_semver).include? installed_module.version results[:result] = :noop results[:version] = installed_module.version return results else changes = Checksummer.run(installed_modules[name].mod.path) rescue [] raise AlreadyInstalledError, :module_name => name, :installed_version => installed_modules[name].version, :requested_version => options[:version] || :latest, :local_changes => changes end end end @install_dir.prepare(name, options[:version] || 'latest') results[:install_dir] = @install_dir.target unless @local_tarball && @ignore_dependencies Puppet.notice _("Downloading from %{host} ...") % { host: module_repository.host } end if @ignore_dependencies graph = build_single_module_graph(name, version) else graph = build_dependency_graph(name, version) end unless forced? add_module_name_constraints_to_graph(graph) end installed_modules.each do |mod, release| mod = mod.tr('/', '-') next if mod == name version = release.version unless forced? # Since upgrading already installed modules can be troublesome, # we'll place constraints on the graph for each installed module, # locking it to upgrades within the same major version. installed_range = ">=#{version} #{version.major}.x" graph.add_constraint('installed', mod, installed_range) do |node| Puppet::Module.parse_range(installed_range, @strict_semver).include? node.version end release.mod.dependencies.each do |dep| dep_name = dep['name'].tr('/', '-') range = dep['version_requirement'] graph.add_constraint("#{mod} constraint", dep_name, range) do |node| Puppet::Module.parse_range(range, @strict_semver).include? node.version end end end end # Ensure that there is at least one candidate release available # for the target package. if graph.dependencies[name].empty? raise NoCandidateReleasesError, results.merge(:module_name => name, :source => module_repository.host, :requested_version => options[:version] || :latest) end begin Puppet.info _("Resolving dependencies ...") releases = SemanticPuppet::Dependency.resolve(graph) rescue SemanticPuppet::Dependency::UnsatisfiableGraph raise NoVersionsSatisfyError, results.merge(:requested_name => name) end unless forced? # Check for module name conflicts. releases.each do |rel| if installed_module = installed_modules_source.by_name[rel.name.split('-').last] next if installed_module.has_metadata? && installed_module.forge_name.tr('/', '-') == rel.name if rel.name != name dependency = { :name => rel.name, :version => rel.version } end raise InstallConflictError, :requested_module => name, :requested_version => options[:version] || 'latest', :dependency => dependency, :directory => installed_module.path, :metadata => installed_module.metadata end end end Puppet.info _("Preparing to install ...") releases.each { |release| release.prepare } Puppet.notice _('Installing -- do not interrupt ...') releases.each do |release| installed = installed_modules[release.name] if forced? || installed.nil? release.install(Pathname.new(results[:install_dir])) else release.install(Pathname.new(installed.mod.modulepath)) end end results[:result] = :success results[:installed_modules] = releases results[:graph] = [ build_install_graph(releases.first, releases) ] rescue ModuleToolError, ForgeError => err results[:error] = { :oneline => err.message, :multiline => err.multiline, } ensure results[:result] ||= :failure end results end private def module_repository @repo ||= Puppet::Forge.new(Puppet[:module_repository], @strict_semver) end def local_tarball_source @tarball_source ||= begin Puppet::ModuleTool::LocalTarball.new(@name, @strict_semver) rescue Puppet::Module::Error => e raise InvalidModuleError.new(@name, :action => @action, :error => e) end end def installed_modules_source @installed ||= Puppet::ModuleTool::InstalledModules.new(@environment) end def installed_modules installed_modules_source.modules end def build_single_module_graph(name, version) range = Puppet::Module.parse_range(version, @strict_semver) graph = SemanticPuppet::Dependency::Graph.new(name => range) releases = SemanticPuppet::Dependency.fetch_releases(name) releases.each { |release| release.dependencies.clear } graph << releases end def build_dependency_graph(name, version) SemanticPuppet::Dependency.query(name => version) end def build_install_graph(release, installed, graphed = []) graphed << release dependencies = release.dependencies.values.map do |deps| dep = (deps & installed).first unless dep.nil? || graphed.include?(dep) build_install_graph(dep, installed, graphed) end end previous = installed_modules[release.name] previous = previous.version if previous return { :release => release, :name => release.name, :path => release.install_dir.to_s, :dependencies => dependencies.compact, :version => release.version, :previous_version => previous, :action => (previous.nil? || previous == release.version || forced? ? :install : :upgrade), } end include Puppet::ModuleTool::Shared # Return a Pathname object representing the path to the module # release package in the `Puppet.settings[:module_working_dir]`. def get_release_packages get_local_constraints if !forced? && @installed.include?(@module_name) raise AlreadyInstalledError, :module_name => @module_name, :installed_version => @installed[@module_name].first.version, :requested_version => @version || (@conditions[@module_name].empty? ? :latest : :best), :local_changes => Puppet::ModuleTool::Applications::Checksummer.run(@installed[@module_name].first.path) end if @ignore_dependencies && @source == :filesystem @urls = {} @remote = { "#{@module_name}@#{@version}" => { } } @versions = { @module_name => [ { :vstring => @version, :semver => SemanticPuppet::Version.parse(@version) } ] } else get_remote_constraints(@forge) end @graph = resolve_constraints({ @module_name => @version }) @graph.first[:tarball] = @filename if @source == :filesystem resolve_install_conflicts(@graph) unless forced? # This clean call means we never "cache" the module we're installing, but this # is desired since module authors can easily rerelease modules different content but the same # version number, meaning someone with the old content cached will be very confused as to why # they can't get new content. # Long term we should just get rid of this caching behavior and cleanup downloaded modules after they install # but for now this is a quick fix to disable caching Puppet::Forge::Cache.clean download_tarballs(@graph, @graph.last[:path], @forge) end # # Resolve installation conflicts by checking if the requested module # or one of its dependencies conflicts with an installed module. # # Conflicts occur under the following conditions: # # When installing 'puppetlabs-foo' and an existing directory in the # target install path contains a 'foo' directory and we cannot determine # the "full name" of the installed module. # # When installing 'puppetlabs-foo' and 'pete-foo' is already installed. # This is considered a conflict because 'puppetlabs-foo' and 'pete-foo' # install into the same directory 'foo'. # def resolve_install_conflicts(graph, is_dependency = false) Puppet.debug("Resolving conflicts for #{graph.map {|n| n[:module]}.join(',')}") graph.each do |release| @environment.modules_by_path[options[:target_dir]].each do |mod| if mod.has_metadata? metadata = { :name => mod.forge_name.gsub('/', '-'), :version => mod.version } next if release[:module] == metadata[:name] else metadata = nil end if release[:module] =~ /-#{mod.name}$/ dependency_info = { :name => release[:module], :version => release[:version][:vstring] } dependency = is_dependency ? dependency_info : nil all_versions = @versions["#{@module_name}"].sort_by { |h| h[:semver] } versions = all_versions.select { |x| x[:semver].special == '' } versions = all_versions if versions.empty? latest_version = versions.last[:vstring] raise InstallConflictError, :requested_module => @module_name, :requested_version => @version || "latest: v#{latest_version}", :dependency => dependency, :directory => mod.path, :metadata => metadata end end deps = release[:dependencies] if deps && !deps.empty? resolve_install_conflicts(deps, true) end end end # # Check if a file is a vaild module package. # --- # FIXME: Checking for a valid module package should be more robust and # use the actual metadata contained in the package. 03132012 - Hightower # +++ # def is_module_package?(name) filename = File.expand_path(name) filename =~ /.tar.gz$/ end end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/applications/uninstaller.rb������������������������������������0000644�0052762�0001160�00000007627�13417161721�025006� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::ModuleTool module Applications class Uninstaller < Application include Puppet::ModuleTool::Errors def initialize(name, options) @name = name @options = options @errors = Hash.new {|h, k| h[k] = {}} @unfiltered = [] @installed = [] @suggestions = [] @environment = options[:environment_instance] @ignore_changes = options[:force] || options[:ignore_changes] @strict_semver = !!options[:strict_semver] end def run # Disallow anything that invokes md5 to avoid un-friendly termination due to FIPS raise _("Module uninstall is prohibited in FIPS mode.") if Facter.value(:fips_enabled) results = { :module_name => @name, :requested_version => @version, } begin find_installed_module validate_module FileUtils.rm_rf(@installed.first.path, :secure => true) results[:affected_modules] = @installed results[:result] = :success rescue ModuleToolError => err results[:error] = { :oneline => err.message, :multiline => err.multiline, } rescue => e results[:error] = { :oneline => e.message, :multiline => e.respond_to?(:multiline) ? e.multiline : [e.to_s, e.backtrace].join("\n") } ensure results[:result] ||= :failure end results end private def find_installed_module @environment.modules_by_path.values.flatten.each do |mod| mod_name = (mod.forge_name || mod.name).gsub('/', '-') if mod_name == @name @unfiltered << { :name => mod_name, :version => mod.version, :path => mod.modulepath, } if @options[:version] && mod.version next unless Puppet::Module.parse_range(@options[:version], @strict_semver).include?(SemanticPuppet::Version.parse(mod.version)) end @installed << mod elsif mod_name =~ /#{@name}/ @suggestions << mod_name end end if @installed.length > 1 raise MultipleInstalledError, :action => :uninstall, :module_name => @name, :installed_modules => @installed.sort_by { |mod| @environment.modulepath.index(mod.modulepath) } elsif @installed.empty? if @unfiltered.empty? raise NotInstalledError, :action => :uninstall, :suggestions => @suggestions, :module_name => @name else raise NoVersionMatchesError, :installed_modules => @unfiltered.sort_by { |mod| @environment.modulepath.index(mod[:path]) }, :version_range => @options[:version], :module_name => @name end end end def validate_module mod = @installed.first unless @ignore_changes changes = begin Puppet::ModuleTool::Applications::Checksummer.run(mod.path) rescue ArgumentError [] end if mod.has_metadata? && !changes.empty? raise LocalChangesError, :action => :uninstall, :module_name => (mod.forge_name || mod.name).gsub('/', '-'), :requested_version => @options[:version], :installed_version => mod.version end end if !@options[:force] && !mod.required_by.empty? raise ModuleIsRequiredError, :module_name => (mod.forge_name || mod.name).gsub('/', '-'), :required_by => mod.required_by, :requested_version => @options[:version], :installed_version => mod.version end end end end end ���������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/applications/unpacker.rb���������������������������������������0000644�0052762�0001160�00000006010�13417161721�024237� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'pathname' require 'tmpdir' require 'puppet/util/json' require 'puppet/file_system' module Puppet::ModuleTool module Applications class Unpacker < Application def self.unpack(filename, target) app = self.new(filename, :target_dir => target) app.unpack app.sanity_check app.move_into(target) end def self.harmonize_ownership(source, target) unless Puppet.features.microsoft_windows? source = Pathname.new(source) unless source.respond_to?(:stat) target = Pathname.new(target) unless target.respond_to?(:stat) FileUtils.chown_R(source.stat.uid, source.stat.gid, target) end end def initialize(filename, options = {}) @filename = Pathname.new(filename) super(options) @module_path = Pathname(options[:target_dir]) end def run unpack sanity_check module_dir = @module_path + module_name move_into(module_dir) # Return the Pathname object representing the directory where the # module release archive was unpacked the to. return module_dir end # @api private # Error on symlinks and other junk def sanity_check symlinks = Dir.glob("#{tmpdir}/**/*", File::FNM_DOTMATCH).map { |f| Pathname.new(f) }.select {|p| Puppet::FileSystem.symlink? p} tmpdirpath = Pathname.new tmpdir symlinks.each do |s| Puppet.warning _("Symlinks in modules are unsupported. Please investigate symlink %{from}->%{to}.") % { from: s.relative_path_from(tmpdirpath), to: Puppet::FileSystem.readlink(s) } end end # @api private def unpack begin Puppet::ModuleTool::Tar.instance.unpack(@filename.to_s, tmpdir, [@module_path.stat.uid, @module_path.stat.gid].join(':')) rescue Puppet::ExecutionFailure => e raise RuntimeError, _("Could not extract contents of module archive: %{message}") % { message: e.message } end end # @api private def root_dir return @root_dir if @root_dir # Grab the first directory containing a metadata.json file metadata_file = Dir["#{tmpdir}/**/metadata.json"].sort_by(&:length)[0] if metadata_file @root_dir = Pathname.new(metadata_file).dirname else raise _("No valid metadata.json found!") end end # @api private def module_name metadata = Puppet::Util::Json.load((root_dir + 'metadata.json').read) metadata['name'][/-(.*)/, 1] end # @api private def move_into(dir) dir = Pathname.new(dir) dir.rmtree if dir.exist? FileUtils.mv(root_dir, dir) ensure FileUtils.rmtree(tmpdir) end # Obtain a suitable temporary path for unpacking tarballs # # @api private # @return [String] path to temporary unpacking location def tmpdir @dir ||= Dir.mktmpdir('tmp', Puppet::Forge::Cache.base_path) end end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/applications/upgrader.rb���������������������������������������0000644�0052762�0001160�00000025212�13417161721�024245� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'pathname' require 'puppet/forge' require 'puppet/module_tool' require 'puppet/module_tool/shared_behaviors' require 'puppet/module_tool/install_directory' require 'puppet/module_tool/installed_modules' module Puppet::ModuleTool module Applications class Upgrader < Application include Puppet::ModuleTool::Errors def initialize(name, options) super(options) @action = :upgrade @environment = options[:environment_instance] @name = name @ignore_changes = forced? || options[:ignore_changes] @ignore_dependencies = forced? || options[:ignore_dependencies] @strict_semver = !!options[:strict_semver] SemanticPuppet::Dependency.add_source(installed_modules_source) SemanticPuppet::Dependency.add_source(module_repository) end def run # Disallow anything that invokes md5 to avoid un-friendly termination due to FIPS raise _("Module upgrade is prohibited in FIPS mode.") if Facter.value(:fips_enabled) name = @name.tr('/', '-') version = options[:version] || '>= 0.0.0' results = { :action => :upgrade, :requested_version => options[:version] || :latest, } begin all_modules = @environment.modules_by_path.values.flatten matching_modules = all_modules.select do |x| x.forge_name && x.forge_name.tr('/', '-') == name end if matching_modules.empty? raise NotInstalledError, results.merge(:module_name => name) elsif matching_modules.length > 1 raise MultipleInstalledError, results.merge(:module_name => name, :installed_modules => matching_modules) end installed_release = installed_modules[name] # `priority` is an attribute of a `SemanticPuppet::Dependency::Source`, # which is delegated through `ModuleRelease` instances for the sake of # comparison (sorting). By default, the `InstalledModules` source has # a priority of 10 (making it the most preferable source, so that # already installed versions of modules are selected in preference to # modules from e.g. the Forge). Since we are specifically looking to # upgrade this module, we don't want the installed version of this # module to be chosen in preference to those with higher versions. # # This implementation is suboptimal, and since we can expect this sort # of behavior to be reasonably common in Semantic, we should probably # see about implementing a `ModuleRelease#override_priority` method # (or something similar). def installed_release.priority 0 end mod = installed_release.mod results[:installed_version] = SemanticPuppet::Version.parse(mod.version) dir = Pathname.new(mod.modulepath) vstring = mod.version ? "v#{mod.version}" : '???' Puppet.notice _("Found '%{name}' (%{version}) in %{dir} ...") % { name: name, version: colorize(:cyan, vstring), dir: dir } unless @ignore_changes changes = Checksummer.run(mod.path) rescue [] if mod.has_metadata? && !changes.empty? raise LocalChangesError, :action => :upgrade, :module_name => name, :requested_version => results[:requested_version], :installed_version => mod.version end end Puppet::Forge::Cache.clean # Ensure that there is at least one candidate release available # for the target package. available_versions = module_repository.fetch(name) if available_versions.empty? raise NoCandidateReleasesError, results.merge(:module_name => name, :source => module_repository.host) elsif results[:requested_version] != :latest requested = Puppet::Module.parse_range(results[:requested_version], @strict_semver) unless available_versions.any? {|m| requested.include? m.version} raise NoCandidateReleasesError, results.merge(:module_name => name, :source => module_repository.host) end end Puppet.notice _("Downloading from %{host} ...") % { host: module_repository.host } if @ignore_dependencies graph = build_single_module_graph(name, version) else graph = build_dependency_graph(name, version) end unless forced? add_module_name_constraints_to_graph(graph) end installed_modules.each do |installed_module, release| installed_module = installed_module.tr('/', '-') next if installed_module == name version = release.version unless forced? # Since upgrading already installed modules can be troublesome, # we'll place constraints on the graph for each installed # module, locking it to upgrades within the same major version. installed_range = ">=#{version} #{version.major}.x" graph.add_constraint('installed', installed_module, installed_range) do |node| Puppet::Module.parse_range(installed_range, @strict_semver).include? node.version end release.mod.dependencies.each do |dep| dep_name = dep['name'].tr('/', '-') range = dep['version_requirement'] graph.add_constraint("#{installed_module} constraint", dep_name, range) do |node| Puppet::Module.parse_range(range, @strict_semver).include? node.version end end end end begin Puppet.info _("Resolving dependencies ...") releases = SemanticPuppet::Dependency.resolve(graph) rescue SemanticPuppet::Dependency::UnsatisfiableGraph raise NoVersionsSatisfyError, results.merge(:requested_name => name) end releases.each do |rel| if mod = installed_modules_source.by_name[rel.name.split('-').last] next if mod.has_metadata? && mod.forge_name.tr('/', '-') == rel.name if rel.name != name dependency = { :name => rel.name, :version => rel.version } end raise InstallConflictError, :requested_module => name, :requested_version => options[:version] || 'latest', :dependency => dependency, :directory => mod.path, :metadata => mod.metadata end end child = releases.find { |x| x.name == name } unless forced? if child.version == results[:installed_version] versions = graph.dependencies[name].map { |r| r.version } newer_versions = versions.select { |v| v > results[:installed_version] } raise VersionAlreadyInstalledError, :module_name => name, :requested_version => results[:requested_version], :installed_version => results[:installed_version], :newer_versions => newer_versions, :possible_culprits => installed_modules_source.fetched.reject { |x| x == name } elsif child.version < results[:installed_version] raise DowngradingUnsupportedError, :module_name => name, :requested_version => results[:requested_version], :installed_version => results[:installed_version] end end Puppet.info _("Preparing to upgrade ...") releases.each { |release| release.prepare } Puppet.notice _('Upgrading -- do not interrupt ...') releases.each do |release| if installed = installed_modules[release.name] release.install(Pathname.new(installed.mod.modulepath)) else release.install(dir) end end results[:result] = :success results[:base_dir] = releases.first.install_dir results[:affected_modules] = releases results[:graph] = [ build_install_graph(releases.first, releases) ] rescue VersionAlreadyInstalledError => e results[:result] = (e.newer_versions.empty? ? :noop : :failure) results[:error] = { :oneline => e.message, :multiline => e.multiline } rescue => e results[:error] = { :oneline => e.message, :multiline => e.respond_to?(:multiline) ? e.multiline : [e.to_s, e.backtrace].join("\n") } ensure results[:result] ||= :failure end results end private def module_repository @repo ||= Puppet::Forge.new(Puppet[:module_repository], @strict_semver) end def installed_modules_source @installed ||= Puppet::ModuleTool::InstalledModules.new(@environment) end def installed_modules installed_modules_source.modules end def build_single_module_graph(name, version) range = Puppet::Module.parse_range(version, @strict_semver) graph = SemanticPuppet::Dependency::Graph.new(name => range) releases = SemanticPuppet::Dependency.fetch_releases(name) releases.each { |release| release.dependencies.clear } graph << releases end def build_dependency_graph(name, version) SemanticPuppet::Dependency.query(name => version) end def build_install_graph(release, installed, graphed = []) previous = installed_modules[release.name] previous = previous.version if previous action = :upgrade unless previous && previous != release.version action = :install end graphed << release dependencies = release.dependencies.values.map do |deps| dep = (deps & installed).first if dep == installed_modules[dep.name] next end if dep && !graphed.include?(dep) build_install_graph(dep, installed, graphed) end end.compact return { :release => release, :name => release.name, :path => release.install_dir, :dependencies => dependencies.compact, :version => release.version, :previous_version => previous, :action => action, } end include Puppet::ModuleTool::Shared end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/checksums.rb���������������������������������������������������0000644�0052762�0001160�00000002304�13417161721�021730� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'digest/md5' require 'puppet/network/format_support' module Puppet::ModuleTool # = Checksums # # This class provides methods for generating checksums for data and adding # them to +Metadata+. class Checksums include Puppet::Network::FormatSupport include Enumerable # Instantiate object with string +path+ to create checksums from. def initialize(path) @path = Pathname.new(path) end # Return checksum for the +Pathname+. def checksum(pathname) return Digest::MD5.hexdigest(Puppet::FileSystem.binread(pathname)) end # Return checksums for object's +Pathname+, generate if it's needed. # Result is a hash of path strings to checksum strings. def data unless @data @data = {} @path.find do |descendant| if Puppet::ModuleTool.artifact?(descendant) Find.prune elsif descendant.file? path = descendant.relative_path_from(@path) @data[path.to_s] = checksum(descendant) end end end return @data end alias :to_data_hash :data alias :to_hash :data # TODO: Why? def each(&block) data.each(&block) end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/contents_description.rb����������������������������������������0000644�0052762�0001160�00000005407�13417161721�024212� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/module_tool' module Puppet::ModuleTool # = ContentsDescription # # This class populates +Metadata+'s Puppet type information. class ContentsDescription # Instantiate object for string +module_path+. def initialize(module_path) @module_path = module_path end # Update +Metadata+'s Puppet type information. def annotate(metadata) metadata.types.replace data.clone end # Return types for this module. Result is an array of hashes, each of which # describes a Puppet type. The type description hash structure is: # * :name => Name of this Puppet type. # * :doc => Documentation for this type. # * :properties => Array of hashes representing the type's properties, each # containing :name and :doc. # * :parameters => Array of hashes representing the type's parameters, each # containing :name and :doc. # * :providers => Array of hashes representing the types providers, each # containing :name and :doc. # TODO Write a TypeDescription to encapsulate these structures and logic? def data unless @data @data = [] type_names = [] for module_filename in Dir[File.join(@module_path, "lib/puppet/type/*.rb")] require module_filename type_name = File.basename(module_filename, ".rb") type_names << type_name for provider_filename in Dir[File.join(@module_path, "lib/puppet/provider/#{type_name}/*.rb")] require provider_filename end end type_names.each do |name| if type = Puppet::Type.type(name.to_sym) type_hash = {:name => name, :doc => type.doc} type_hash[:properties] = attr_doc(type, :property) type_hash[:parameters] = attr_doc(type, :param) if type.providers.size > 0 type_hash[:providers] = provider_doc(type) end @data << type_hash else Puppet.warning _("Could not find/load type: %{name}") % { name: name } end end end @data end # Return an array of hashes representing this +type+'s attrs of +kind+ # (e.g. :param or :property), each containing :name and :doc. def attr_doc(type, kind) attrs = [] type.allattrs.each do |name| if type.attrtype(name) == kind && name != :provider attrs.push(:name => name, :doc => type.attrclass(name).doc) end end attrs end # Return an array of hashes representing this +type+'s providers, each # containing :name and :doc. def provider_doc(type) providers = [] type.providers.sort.each do |prov| providers.push(:name => prov, :doc => type.provider(prov).doc) end providers end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/dependency.rb��������������������������������������������������0000644�0052762�0001160�00000002663�13417161721�022071� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/module_tool' require 'puppet/network/format_support' module Puppet::ModuleTool class Dependency include Puppet::Network::FormatSupport attr_reader :full_module_name, :username, :name, :version_requirement, :repository # Instantiates a new module dependency with a +full_module_name+ (e.g. # "myuser-mymodule"), and optional +version_requirement+ (e.g. "0.0.1") and # optional repository (a URL string). def initialize(full_module_name, version_requirement = nil, repository = nil) @full_module_name = full_module_name # TODO: add error checking, the next line raises ArgumentError when +full_module_name+ is invalid @username, @name = Puppet::ModuleTool.username_and_modname_from(full_module_name) @version_requirement = version_requirement @repository = repository ? Puppet::Forge::Repository.new(repository, nil) : nil end # We override Object's ==, eql, and hash so we can more easily find identical # dependencies. def ==(o) self.hash == o.hash end alias :eql? :== def hash [@full_module_name, @version_requirement, @repository].hash end def to_data_hash result = { :name => @full_module_name } result[:version_requirement] = @version_requirement if @version_requirement && ! @version_requirement.nil? result[:repository] = @repository.to_s if @repository && ! @repository.nil? result end end end �����������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/errors.rb������������������������������������������������������0000644�0052762�0001160�00000000505�13417161721�021260� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/module_tool' module Puppet::ModuleTool module Errors require 'puppet/module_tool/errors/base' require 'puppet/module_tool/errors/installer' require 'puppet/module_tool/errors/uninstaller' require 'puppet/module_tool/errors/upgrader' require 'puppet/module_tool/errors/shared' end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/errors/��������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020740� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/errors/base.rb�������������������������������������������������0000644�0052762�0001160�00000000544�13417161721�022175� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::ModuleTool::Errors class ModuleToolError < Puppet::Error def v(version) (version || '???').to_s.sub(/^(?=\d)/, 'v') end def vstring if @action == :upgrade "#{v(@installed_version)} -> #{v(@requested_version)}" else "#{v(@installed_version || @requested_version)}" end end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/errors/installer.rb��������������������������������������������0000644�0052762�0001160�00000011140�13417161721�023252� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::ModuleTool::Errors class InstallError < ModuleToolError; end class AlreadyInstalledError < InstallError def initialize(options) @module_name = options[:module_name] @installed_version = v(options[:installed_version]) @requested_version = v(options[:requested_version]) @local_changes = options[:local_changes] super _("'%{module_name}' (%{version}) requested; '%{module_name}' (%{installed_version}) already installed") % { module_name: @module_name, version: @requested_version, installed_version: @installed_version } end def multiline message = [] message << _("Could not install module '%{module_name}' (%{version})") % { module_name: @module_name, version: @requested_version } message << _(" Module '%{module_name}' (%{installed_version}) is already installed") % { module_name: @module_name, installed_version: @installed_version } message << _(" Installed module has had changes made locally") unless @local_changes.empty? #TRANSLATORS `puppet module upgrade` is a command line and should not be translated message << _(" Use `puppet module upgrade` to install a different version") #TRANSLATORS `puppet module install --force` is a command line and should not be translated message << _(" Use `puppet module install --force` to re-install only this module") message.join("\n") end end class MissingPackageError < InstallError def initialize(options) @requested_package = options[:requested_package] @source = options[:source] super _("Could not install '%{requested_package}'; no releases are available from %{source}") % { requested_package: @requested_package, source: @source } end def multiline message = [] message << _("Could not install '%{requested_package}'") % { requested_package: @requested_package } message << _(" No releases are available from %{source}") % { source: @source } message << _(" Does '%{requested_package}' have at least one published release?") % { requested_package: @requested_package } message.join("\n") end end class InstallPathExistsNotDirectoryError < InstallError def initialize(original, options) @requested_module = options[:requested_module] @requested_version = options[:requested_version] @directory = options[:directory] super(_("'%{module_name}' (%{version}) requested; Path %{dir} is not a directory.") % { module_name: @requested_module, version: @requested_version, dir: @directory }, original) end def multiline message = [] message << _("Could not install module '%{module_name}' (%{version})") % { module_name: @requested_module, version: @requested_version } message << _(" Path '%{directory}' exists but is not a directory.") % { directory: @directory } #TRANSLATORS "mkdir -p '%{directory}'" is a command line example and should not be translated message << _(" A potential solution is to rename the path and then \"mkdir -p '%{directory}'\"") % { directory: @directory } message.join("\n") end end class PermissionDeniedCreateInstallDirectoryError < InstallError def initialize(original, options) @requested_module = options[:requested_module] @requested_version = options[:requested_version] @directory = options[:directory] super(_("'%{module_name}' (%{version}) requested; Permission is denied to create %{dir}.") % { module_name: @requested_module, version: @requested_version, dir: @directory }, original) end def multiline message = [] message << _("Could not install module '%{module_name}' (%{version})") % { module_name: @requested_module, version: @requested_version } message << _(" Permission is denied when trying to create directory '%{directory}'.") % { directory: @directory } message << _(' A potential solution is to check the ownership and permissions of parent directories.') message.join("\n") end end class InvalidPathInPackageError < InstallError def initialize(options) @entry_path = options[:entry_path] @directory = options[:directory] super _("Attempt to install file with an invalid path into %{path} under %{dir}") % { path: @entry_path.inspect, dir: @directory.inspect } end def multiline message = [] message << _('Could not install package with an invalid path.') message << _(' Package attempted to install file into %{path} under %{directory}.') % { path: @entry_path.inspect, directory: @directory.inspect } message.join("\n") end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/errors/shared.rb�����������������������������������������������0000644�0052762�0001160�00000023107�13417161721�022531� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::ModuleTool::Errors class NoVersionsSatisfyError < ModuleToolError def initialize(options) @requested_name = options[:requested_name] @requested_version = options[:requested_version] @installed_version = options[:installed_version] @conditions = options[:conditions] @action = options[:action] super _("Could not %{action} '%{module_name}' (%{version}); no version satisfies all dependencies") % { action: @action, module_name: @requested_name, version: vstring } end def multiline message = [] message << _("Could not %{action} module '%{module_name}' (%{version})") % { action: @action, module_name: @requested_name, version: vstring } message << _(" No version of '%{module_name}' can satisfy all dependencies") % { module_name: @requested_name } #TRANSLATORS `puppet module %{action} --ignore-dependencies` is a command line and should not be translated message << _(" Use `puppet module %{action} --ignore-dependencies` to %{action} only this module") % { action: @action } message.join("\n") end end class NoCandidateReleasesError < ModuleToolError def initialize(options) @module_name = options[:module_name] @requested_version = options[:requested_version] @installed_version = options[:installed_version] @source = options[:source] @action = options[:action] if @requested_version == :latest super _("Could not %{action} '%{module_name}'; no releases are available from %{source}") % { action: @action, module_name: @module_name, source: @source } else super _("Could not %{action} '%{module_name}'; no releases matching '%{version}' are available from %{source}") % { action: @action, module_name: @module_name, version: @requested_version, source: @source } end end def multiline message = [] message << _("Could not %{action} '%{module_name}' (%{version})") % { action: @action, module_name: @module_name, version: vstring } if @requested_version == :latest message << _(" No releases are available from %{source}") % { source: @source } message << _(" Does '%{module_name}' have at least one published release?") % { module_name: @module_name } else message << _(" No releases matching '%{requested_version}' are available from %{source}") % { requested_version: @requested_version, source: @source } end message.join("\n") end end class InstallConflictError < ModuleToolError def initialize(options) @requested_module = options[:requested_module] @requested_version = v(options[:requested_version]) @dependency = options[:dependency] @directory = options[:directory] @metadata = options[:metadata] super _("'%{module_name}' (%{version}) requested; installation conflict") % { module_name: @requested_module, version: @requested_version } end def multiline message = [] message << _("Could not install module '%{module_name}' (%{version})") % { module_name: @requested_module, version: @requested_version } if @dependency message << _(" Dependency '%{name}' (%{version}) would overwrite %{directory}") % { name: @dependency[:name], version: v(@dependency[:version]), directory: @directory } else message << _(" Installation would overwrite %{directory}") % { directory: @directory } end if @metadata message << _(" Currently, '%{current_name}' (%{current_version}) is installed to that directory") % { current_name: @metadata["name"], current_version: v(@metadata["version"]) } end if @dependency #TRANSLATORS `puppet module install --ignore-dependencies` is a command line and should not be translated message << _(" Use `puppet module install --ignore-dependencies` to install only this module") else #TRANSLATORS `puppet module install --force` is a command line and should not be translated message << _(" Use `puppet module install --force` to install this module anyway") end message.join("\n") end end class InvalidDependencyCycleError < ModuleToolError def initialize(options) @module_name = options[:module_name] @requested_module = options[:requested_module] @requested_version = options[:requested_version] @conditions = options[:conditions] @source = options[:source][1..-1] super _("'%{module_name}' (%{version}) requested; Invalid dependency cycle") % { module_name: @requested_module, version: v(@requested_version) } end def multiline dependency_list = [] dependency_list << _("You specified '%{name}' (%{version})") % { name: @source.first[:name], version: v(@requested_version) } dependency_list += @source[1..-1].map do |m| #TRANSLATORS This message repeats as separate lines as a list under the heading "You specified '%{name}' (%{version})\n" _("This depends on '%{name}' (%{version})") % { name: m[:name], version: v(m[:version]) } end message = [] message << _("Could not install module '%{module_name}' (%{version})") % { module_name: @requested_module, version: v(@requested_version) } message << _(" No version of '%{module_name}' will satisfy dependencies") % { module_name: @module_name } message << dependency_list.map {|s| " #{s}".join(",\n")} #TRANSLATORS `puppet module install --force` is a command line and should not be translated message << _(" Use `puppet module install --force` to install this module anyway") message.join("\n") end end class NotInstalledError < ModuleToolError def initialize(options) @module_name = options[:module_name] @suggestions = options[:suggestions] || [] @action = options[:action] super _("Could not %{action} '%{module_name}'; module is not installed") % { action: @action, module_name: @module_name } end def multiline message = [] message << _("Could not %{action} module '%{module_name}'") % { action: @action, module_name: @module_name } message << _(" Module '%{module_name}' is not installed") % { module_name: @module_name } message += @suggestions.map do |suggestion| #TRANSLATORS `puppet module %{action} %{suggestion}` is a command line and should not be translated _(" You may have meant `puppet module %{action} %{suggestion}`") % { action: @action, suggestion: suggestion } end #TRANSLATORS `puppet module install` is a command line and should not be translated message << _(" Use `puppet module install` to install this module") if @action == :upgrade message.join("\n") end end class MultipleInstalledError < ModuleToolError def initialize(options) @module_name = options[:module_name] @modules = options[:installed_modules] @action = options[:action] #TRANSLATORS "module path" refers to a set of directories where modules may be installed super _("Could not %{action} '%{module_name}'; module appears in multiple places in the module path") % { action: @action, module_name: @module_name } end def multiline message = [] message << _("Could not %{action} module '%{module_name}'") % { action: @action, module_name: @module_name } message << _(" Module '%{module_name}' appears multiple places in the module path") % { module_name: @module_name } message += @modules.map do |mod| #TRANSLATORS This is repeats as separate lines as a list under "Module '%{module_name}' appears multiple places in the module path" _(" '%{module_name}' (%{version}) was found in %{path}") % { module_name: @module_name, version: v(mod.version), path: mod.modulepath } end #TRANSLATORS `--modulepath` is command line option and should not be translated message << _(" Use the `--modulepath` option to limit the search to specific directories") message.join("\n") end end class LocalChangesError < ModuleToolError def initialize(options) @module_name = options[:module_name] @requested_version = options[:requested_version] @installed_version = options[:installed_version] @action = options[:action] super _("Could not %{action} '%{module_name}'; module has had changes made locally") % { action: @action, module_name: @module_name } end def multiline message = [] message << _("Could not %{action} module '%{module_name}' (%{version})") % { action: @action, module_name: @module_name, version: vstring } message << _(" Installed module has had changes made locally") #TRANSLATORS `puppet module %{action} --ignore-changes` is a command line and should not be translated message << _(" Use `puppet module %{action} --ignore-changes` to %{action} this module anyway") % { action: @action } message.join("\n") end end class InvalidModuleError < ModuleToolError def initialize(name, options) @name = name @action = options[:action] @error = options[:error] super _("Could not %{action} '%{module_name}'; %{error}") % { action: @action, module_name: @name, error: @error.message } end def multiline message = [] message << _("Could not %{action} module '%{module_name}'") % { action: @action, module_name: @name } message << _(" Failure trying to parse metadata") message << _(" Original message was: %{message}") % { message: @error.message } message.join("\n") end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/errors/uninstaller.rb������������������������������������������0000644�0052762�0001160�00000004555�13417161721�023631� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::ModuleTool::Errors class UninstallError < ModuleToolError; end class NoVersionMatchesError < UninstallError def initialize(options) @module_name = options[:module_name] @modules = options[:installed_modules] @version = options[:version_range] super _("Could not uninstall '%{module_name}'; no installed version matches") % { module_name: @module_name } end def multiline message = [] message << _("Could not uninstall module '%{module_name}' (%{version})") % { module_name: @module_name, version: v(@version) } message << _(" No installed version of '%{module_name}' matches (%{version})") % { module_name: @module_name, version: v(@version) } message += @modules.map do |mod| _(" '%{module_name}' (%{version}) is installed in %{path}") % { module_name: mod[:name], version: v(mod[:version]), path: mod[:path] } end message.join("\n") end end class ModuleIsRequiredError < UninstallError def initialize(options) @module_name = options[:module_name] @required_by = options[:required_by] @requested_version = options[:requested_version] @installed_version = options[:installed_version] super _("Could not uninstall '%{module_name}'; installed modules still depend upon it") % { module_name: @module_name } end def multiline message = [] if @requested_version message << _("Could not uninstall module '%{module_name}' (v%{requested_version})") % { module_name: @module_name, requested_version: @requested_version } else message << _("Could not uninstall module '%{module_name}'") % { module_name: @module_name } end message << _(" Other installed modules have dependencies on '%{module_name}' (%{version})") % { module_name: @module_name, version: v(@installed_version) } message += @required_by.map do |mod| _(" '%{module_name}' (%{version}) requires '%{module_dep}' (%{dep_version})") % { module_name: mod['name'], version: v(mod['version']), module_dep: @module_name, dep_version: v(mod['version_requirement']) } end #TRANSLATORS `puppet module uninstall --force` is a command line option that should not be translated message << _(" Use `puppet module uninstall --force` to uninstall this module anyway") message.join("\n") end end end ���������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/errors/upgrader.rb���������������������������������������������0000644�0052762�0001160�00000005055�13417161721�023076� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::ModuleTool::Errors class UpgradeError < ModuleToolError def initialize(msg) @action = :upgrade super end end class VersionAlreadyInstalledError < UpgradeError attr_reader :newer_versions def initialize(options) @module_name = options[:module_name] @requested_version = options[:requested_version] @installed_version = options[:installed_version] @dependency_name = options[:dependency_name] @newer_versions = options[:newer_versions] @possible_culprits = options[:possible_culprits] super _("Could not upgrade '%{module_name}'; more recent versions not found") % { module_name: @module_name } end def multiline message = [] message << _("Could not upgrade module '%{module_name}' (%{version})") % { module_name: @module_name, version: vstring } if @newer_versions.empty? message << _(" The installed version is already the latest version matching %{version}") % { version: vstring } else message << _(" There are %{count} newer versions") % { count: @newer_versions.length } message << _(" No combination of dependency upgrades would satisfy all dependencies") unless @possible_culprits.empty? message << _(" Dependencies will not be automatically upgraded across major versions") message << _(" Upgrading one or more of these modules may permit the upgrade to succeed:") @possible_culprits.each do |name| message << " - #{name}" end end end #TRANSLATORS `puppet module upgrade --force` is a command line option that should not be translated message << _(" Use `puppet module upgrade --force` to upgrade only this module") message.join("\n") end end class DowngradingUnsupportedError < UpgradeError def initialize(options) @module_name = options[:module_name] @requested_version = options[:requested_version] @installed_version = options[:installed_version] @conditions = options[:conditions] @action = options[:action] super _("Could not %{action} '%{module_name}' (%{version}); downgrades are not allowed") % { action: @action, module_name: @module_name, version: vstring } end def multiline message = [] message << _("Could not %{action} module '%{module_name}' (%{version})") % { action: @action, module_name: @module_name, version: vstring } message << _(" Downgrading is not allowed.") message.join("\n") end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/install_directory.rb�������������������������������������������0000644�0052762�0001160�00000002410�13417161721�023473� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/module_tool' require 'puppet/module_tool/errors' module Puppet module ModuleTool # Control the install location for modules. class InstallDirectory include Puppet::ModuleTool::Errors attr_reader :target def initialize(target) @target = target end # prepare the module install location. This will create the location if # needed. def prepare(module_name, version) return if @target.directory? begin @target.mkpath Puppet.notice _("Created target directory %{dir}") % { dir: @target } rescue SystemCallError => orig_error raise converted_to_friendly_error(module_name, version, orig_error) end end private ERROR_MAPPINGS = { Errno::EACCES => PermissionDeniedCreateInstallDirectoryError, Errno::EEXIST => InstallPathExistsNotDirectoryError, } def converted_to_friendly_error(module_name, version, orig_error) return orig_error if not ERROR_MAPPINGS.include?(orig_error.class) ERROR_MAPPINGS[orig_error.class].new(orig_error, :requested_module => module_name, :requested_version => version, :directory => @target.to_s) end end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/tar.rb���������������������������������������������������������0000644�0052762�0001160�00000000775�13417161721�020543� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/module_tool' require 'puppet/util' module Puppet::ModuleTool::Tar require 'puppet/module_tool/tar/gnu' require 'puppet/module_tool/tar/mini' def self.instance if Puppet.features.minitar? && Puppet.features.zlib? Mini.new elsif Puppet::Util.which('tar') && ! Puppet::Util::Platform.windows? Gnu.new else #TRANSLATORS "tar" is a program name and should not be translated raise RuntimeError, _('No suitable tar implementation found') end end end ���puppet-5.5.10/lib/puppet/module_tool/tar/�����������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020212� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/tar/gnu.rb�����������������������������������������������������0000644�0052762�0001160�00000001265�13417161721�021327� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'shellwords' class Puppet::ModuleTool::Tar::Gnu def unpack(sourcefile, destdir, owner) sourcefile = File.expand_path(sourcefile) destdir = File.expand_path(destdir) Dir.chdir(destdir) do Puppet::Util::Execution.execute("gzip -dc #{Shellwords.shellescape(sourcefile)} | tar xof -") Puppet::Util::Execution.execute("find . -type d -exec chmod 755 {} +") Puppet::Util::Execution.execute("find . -type f -exec chmod u+rw,g+r,a-st {} +") Puppet::Util::Execution.execute("chown -R #{owner} .") end end def pack(sourcedir, destfile) Puppet::Util::Execution.execute("tar cf - #{sourcedir} | gzip -c > #{File.basename(destfile)}") end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/tar/mini.rb����������������������������������������������������0000644�0052762�0001160�00000007056�13417161721�021476� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::ModuleTool::Tar::Mini def unpack(sourcefile, destdir, _) Zlib::GzipReader.open(sourcefile) do |reader| Archive::Tar::Minitar.unpack(reader, destdir, find_valid_files(reader)) do |action, name, stats| case action when :dir validate_entry(destdir, name) set_dir_mode!(stats) Puppet.debug("Extracting: #{destdir}/#{name}") when :file_start # Octal string of the old file mode. validate_entry(destdir, name) set_file_mode!(stats) Puppet.debug("Extracting: #{destdir}/#{name}") end set_default_user_and_group!(stats) stats end end end def pack(sourcedir, destfile) Zlib::GzipWriter.open(destfile) do |writer| Archive::Tar::Minitar.pack(sourcedir, writer) do |step, name, stats| # TODO smcclellan 2017-10-31 Set permissions here when this yield block # executes before the header is written. As it stands, the `stats` # argument isn't mutable in a way that will effect the desired mode for # the file. end end end private EXECUTABLE = 0755 NOT_EXECUTABLE = 0644 USER_EXECUTE = 0100 def set_dir_mode!(stats) if stats.key?(:mode) # This is only the case for `pack`, so this code will not run. stats[:mode] = EXECUTABLE elsif stats.key?(:entry) old_mode = stats[:entry].instance_variable_get(:@mode) if old_mode.is_a?(Integer) stats[:entry].instance_variable_set(:@mode, EXECUTABLE) end end end # Sets a file mode to 0755 if the file is executable by the user. # Sets a file mode to 0644 if the file mode is set (non-Windows). def sanitized_mode(old_mode) old_mode & USER_EXECUTE != 0 ? EXECUTABLE : NOT_EXECUTABLE end def set_file_mode!(stats) if stats.key?(:mode) # This is only the case for `pack`, so this code will not run. stats[:mode] = sanitized_mode(stats[:mode]) elsif stats.key?(:entry) old_mode = stats[:entry].instance_variable_get(:@mode) # If the user can execute the file, set 0755, otherwise 0644. if old_mode.is_a?(Integer) new_mode = sanitized_mode(old_mode) stats[:entry].instance_variable_set(:@mode, new_mode) end end end # Sets UID and GID to 0 for standardization. def set_default_user_and_group!(stats) stats[:uid] = 0 stats[:gid] = 0 end # Find all the valid files in tarfile. # # This check was mainly added to ignore 'x' and 'g' flags from the PAX # standard but will also ignore any other non-standard tar flags. # tar format info: https://pic.dhe.ibm.com/infocenter/zos/v1r13/index.jsp?topic=%2Fcom.ibm.zos.r13.bpxa500%2Ftaf.htm # pax format info: https://pic.dhe.ibm.com/infocenter/zos/v1r13/index.jsp?topic=%2Fcom.ibm.zos.r13.bpxa500%2Fpxarchfm.htm def find_valid_files(tarfile) Archive::Tar::Minitar.open(tarfile).collect do |entry| flag = entry.typeflag if flag.nil? || flag =~ /[[:digit:]]/ && (0..7).include?(flag.to_i) entry.full_name else Puppet.debug "Invalid tar flag '#{flag}' will not be extracted: #{entry.name}" next end end end def validate_entry(destdir, path) if Pathname.new(path).absolute? raise Puppet::ModuleTool::Errors::InvalidPathInPackageError, :entry_path => path, :directory => destdir end path = File.expand_path File.join(destdir, path) if path !~ /\A#{Regexp.escape destdir}/ raise Puppet::ModuleTool::Errors::InvalidPathInPackageError, :entry_path => path, :directory => destdir end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/applications.rb������������������������������������������������0000644�0052762�0001160�00000001031�13417161721�022425� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/module_tool' module Puppet::ModuleTool module Applications require 'puppet/module_tool/applications/application' require 'puppet/module_tool/applications/builder' require 'puppet/module_tool/applications/checksummer' require 'puppet/module_tool/applications/installer' require 'puppet/module_tool/applications/searcher' require 'puppet/module_tool/applications/unpacker' require 'puppet/module_tool/applications/uninstaller' require 'puppet/module_tool/applications/upgrader' end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/installed_modules.rb�������������������������������������������0000644�0052762�0001160�00000005266�13417161721�023464� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'pathname' require 'puppet/forge' require 'puppet/module_tool' module Puppet::ModuleTool class InstalledModules < SemanticPuppet::Dependency::Source attr_reader :modules, :by_name def priority 10 end def initialize(env) @env = env modules = env.modules_by_path @fetched = [] @modules = {} @by_name = {} env.modulepath.each do |path| modules[path].each do |mod| @by_name[mod.name] = mod next unless mod.has_metadata? release = ModuleRelease.new(self, mod) @modules[release.name] ||= release end end @modules.freeze end # Fetches {ModuleRelease} entries for each release of the named module. # # @param name [String] the module name to look up # @return [Array<SemanticPuppet::Dependency::ModuleRelease>] a list of releases for # the given name # @see SemanticPuppet::Dependency::Source#fetch def fetch(name) name = name.tr('/', '-') if @modules.key? name @fetched << name [ @modules[name] ] else [ ] end end def fetched @fetched end class ModuleRelease < SemanticPuppet::Dependency::ModuleRelease attr_reader :mod, :metadata def initialize(source, mod, strict_semver = true) @mod = mod @metadata = mod.metadata name = mod.forge_name.tr('/', '-') begin version = SemanticPuppet::Version.parse(mod.version) rescue SemanticPuppet::Version::ValidationFailure Puppet.warning _("%{module_name} (%{path}) has an invalid version number (%{version}). The version has been set to 0.0.0. If you are the maintainer for this module, please update the metadata.json with a valid Semantic Version (http://semver.org).") % { module_name: mod.name, path: mod.path, version: mod.version } version = SemanticPuppet::Version.parse("0.0.0") end release = "#{name}@#{version}" super(source, name, version, {}) if mod.dependencies mod.dependencies.each do |dependency| results = Puppet::ModuleTool.parse_module_dependency(release, dependency, strict_semver) dep_name, parsed_range, range = results add_constraint('initialize', dep_name, range.to_s) do |node| parsed_range === node.version end end end end def install_dir Pathname.new(@mod.path).dirname end def install(dir) # If we're already installed, there's no need for us to faff about. end def prepare # We're already installed; what preparation remains? end end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/local_tarball.rb�����������������������������������������������0000644�0052762�0001160�00000004556�13417161721�022551� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'pathname' require 'tmpdir' require 'puppet/forge' require 'puppet/module_tool' module Puppet::ModuleTool class LocalTarball < SemanticPuppet::Dependency::Source attr_accessor :release def initialize(filename, strict_semver = true) unpack(filename, tmpdir) Puppet.debug "Unpacked local tarball to #{tmpdir}" mod = Puppet::Module.new('tarball', tmpdir, nil, strict_semver) @release = ModuleRelease.new(self, mod) end def fetch(name) if @release.name == name [ @release ] else [ ] end end def prepare(release) release.mod.path end def install(release, dir) staging_dir = release.prepare module_dir = dir + release.name[/-(.*)/, 1] module_dir.rmtree if module_dir.exist? # Make sure unpacked module has the same ownership as the folder we are moving it into. Puppet::ModuleTool::Applications::Unpacker.harmonize_ownership(dir, staging_dir) FileUtils.mv(staging_dir, module_dir) end class ModuleRelease < SemanticPuppet::Dependency::ModuleRelease attr_reader :mod, :install_dir, :metadata def initialize(source, mod) @mod = mod @metadata = mod.metadata name = mod.forge_name.tr('/', '-') version = SemanticPuppet::Version.parse(mod.version) release = "#{name}@#{version}" if mod.dependencies dependencies = mod.dependencies.map do |dep| Puppet::ModuleTool.parse_module_dependency(release, dep, mod.strict_semver?)[0..1] end dependencies = Hash[dependencies] end super(source, name, version, dependencies || {}) end def install(dir) @source.install(self, dir) @install_dir = dir end def prepare @source.prepare(self) end end private # Obtain a suitable temporary path for unpacking tarballs # # @return [String] path to temporary unpacking location def tmpdir @dir ||= Dir.mktmpdir('local-tarball', Puppet::Forge::Cache.base_path) end def unpack(file, destination) begin Puppet::ModuleTool::Applications::Unpacker.unpack(file, destination) rescue Puppet::ExecutionFailure => e raise RuntimeError, _("Could not extract contents of module archive: %{message}") % { message: e.message } end end end end ��������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/metadata.rb����������������������������������������������������0000644�0052762�0001160�00000016556�13417161721�021541� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/methodhelper' require 'puppet/module_tool' require 'puppet/network/format_support' require 'uri' require 'puppet/util/json' require 'set' module Puppet::ModuleTool # This class provides a data structure representing a module's metadata. # @api private class Metadata include Puppet::Network::FormatSupport attr_accessor :module_name DEFAULTS = { 'name' => nil, 'version' => nil, 'author' => nil, 'summary' => nil, 'license' => 'Apache-2.0', 'source' => '', 'project_page' => nil, 'issues_url' => nil, 'dependencies' => Set.new.freeze, 'data_provider' => nil, } def initialize @data = DEFAULTS.dup @data['dependencies'] = @data['dependencies'].dup end # Returns a filesystem-friendly version of this module name. def dashed_name @data['name'].tr('/', '-') if @data['name'] end # Returns a string that uniquely represents this version of this module. def release_name return nil unless @data['name'] && @data['version'] [ dashed_name, @data['version'] ].join('-') end alias :name :module_name alias :full_module_name :dashed_name # Merges the current set of metadata with another metadata hash. This # method also handles the validation of module names and versions, in an # effort to be proactive about module publishing constraints. def update(data) process_name(data) if data['name'] process_version(data) if data['version'] process_source(data) if data['source'] process_data_provider(data) if data['data_provider'] merge_dependencies(data) if data['dependencies'] @data.merge!(data) return self end # Validates the name and version_requirement for a dependency, then creates # the Dependency and adds it. # Returns the Dependency that was added. def add_dependency(name, version_requirement=nil, repository=nil) validate_name(name) validate_version_range(version_requirement) if version_requirement if dup = @data['dependencies'].find { |d| d.full_module_name == name && d.version_requirement != version_requirement } raise ArgumentError, _("Dependency conflict for %{module_name}: Dependency %{name} was given conflicting version requirements %{version_requirement} and %{dup_version}. Verify that there are no duplicates in the metadata.json.") % { module_name: full_module_name, name: name, version_requirement: version_requirement, dup_version: dup.version_requirement } end dep = Dependency.new(name, version_requirement, repository) @data['dependencies'].add(dep) dep end # Provides an accessor for the now defunct 'description' property. This # addresses a regression in Puppet 3.6.x where previously valid templates # referring to the 'description' property were broken. # @deprecated def description @data['description'] end def dependencies @data['dependencies'].to_a end # Returns a hash of the module's metadata. Used by Puppet's automated # serialization routines. # # @see Puppet::Network::FormatSupport#to_data_hash def to_hash @data end alias :to_data_hash :to_hash def to_json data = @data.dup.merge('dependencies' => dependencies) contents = data.keys.map do |k| value = (Puppet::Util::Json.dump(data[k], :pretty => true) rescue data[k].to_json) %Q("#{k.to_s}": #{value}) end "{\n" + contents.join(",\n").gsub(/^/, ' ') + "\n}\n" end # Expose any metadata keys as callable reader methods. def method_missing(name, *args) return @data[name.to_s] if @data.key? name.to_s super end private # Do basic validation and parsing of the name parameter. def process_name(data) validate_name(data['name']) author, @module_name = data['name'].split(/[-\/]/, 2) data['author'] ||= author if @data['author'] == DEFAULTS['author'] end # Do basic validation on the version parameter. def process_version(data) validate_version(data['version']) end def process_data_provider(data) validate_data_provider(data['data_provider']) end # Do basic parsing of the source parameter. If the source is hosted on # GitHub, we can predict sensible defaults for both project_page and # issues_url. def process_source(data) if data['source'] =~ %r[://] source_uri = URI.parse(data['source']) else source_uri = URI.parse("http://#{data['source']}") end if source_uri.host =~ /^(www\.)?github\.com$/ source_uri.scheme = 'https' source_uri.path.sub!(/\.git$/, '') data['project_page'] ||= @data['project_page'] || source_uri.to_s data['issues_url'] ||= @data['issues_url'] || source_uri.to_s.sub(/\/*$/, '') + '/issues' end rescue URI::Error return end # Validates and parses the dependencies. def merge_dependencies(data) data['dependencies'].each do |dep| add_dependency(dep['name'], dep['version_requirement'], dep['repository']) end # Clear dependencies so @data dependencies are not overwritten data.delete 'dependencies' end # Validates that the given module name is both namespaced and well-formed. def validate_name(name) return if name =~ /\A[a-z0-9]+[-\/][a-z][a-z0-9_]*\Z/i namespace, modname = name.split(/[-\/]/, 2) modname = :namespace_missing if namespace == '' err = case modname when nil, '', :namespace_missing _("the field must be a namespaced module name") when /[^a-z0-9_]/i _("the module name contains non-alphanumeric (or underscore) characters") when /^[^a-z]/i _("the module name must begin with a letter") else _("the namespace contains non-alphanumeric characters") end raise ArgumentError, _("Invalid 'name' field in metadata.json: %{err}") % { err: err } end # Validates that the version string can be parsed as per SemVer. def validate_version(version) return if SemanticPuppet::Version.valid?(version) err = _("version string cannot be parsed as a valid Semantic Version") raise ArgumentError, _("Invalid 'version' field in metadata.json: %{err}") % { err: err } end # Validates that the given _value_ is a symbolic name that starts with a letter # and then contains only letters, digits, or underscore. Will raise an ArgumentError # if that's not the case. # # @param value [Object] The value to be tested def validate_data_provider(value) if value.is_a?(String) unless value =~ /^[a-zA-Z][a-zA-Z0-9_]*$/ if value =~ /^[a-zA-Z]/ raise ArgumentError, _("field 'data_provider' contains non-alphanumeric characters") else raise ArgumentError, _("field 'data_provider' must begin with a letter") end end else raise ArgumentError, _("field 'data_provider' must be a string") end end # Validates that the version range can be parsed by Semantic. def validate_version_range(version_range) SemanticPuppet::VersionRange.parse(version_range) rescue ArgumentError => e raise ArgumentError, _("Invalid 'version_range' field in metadata.json: %{err}") % { err: e } end end end ��������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/shared_behaviors.rb��������������������������������������������0000644�0052762�0001160�00000015100�13417161721�023251� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::ModuleTool::Shared include Puppet::ModuleTool::Errors def get_local_constraints @local = Hash.new { |h,k| h[k] = { } } @conditions = Hash.new { |h,k| h[k] = [] } @installed = Hash.new { |h,k| h[k] = [] } @environment.modules_by_path.values.flatten.each do |mod| mod_name = (mod.forge_name || mod.name).gsub('/', '-') @installed[mod_name] << mod d = @local["#{mod_name}@#{mod.version}"] (mod.dependencies || []).each do |hash| name, conditions = hash['name'], hash['version_requirement'] name = name.gsub('/', '-') d[name] = conditions @conditions[name] << { :module => mod_name, :version => mod.version, :dependency => conditions } end end end def get_remote_constraints(forge) @remote = Hash.new { |h,k| h[k] = { } } @urls = {} @versions = Hash.new { |h,k| h[k] = [] } Puppet.notice _("Downloading from %{uri} ...") % { uri: forge.uri } author, modname = Puppet::ModuleTool.username_and_modname_from(@module_name) info = forge.remote_dependency_info(author, modname, @options[:version]) info.each do |pair| mod_name, releases = pair mod_name = mod_name.gsub('/', '-') releases.each do |rel| semver = SemanticPuppet::Version.parse(rel['version']) rescue SemanticPuppet::Version::MIN @versions[mod_name] << { :vstring => rel['version'], :semver => semver } @versions[mod_name].sort! { |a, b| a[:semver] <=> b[:semver] } @urls["#{mod_name}@#{rel['version']}"] = rel['file'] d = @remote["#{mod_name}@#{rel['version']}"] (rel['dependencies'] || []).each do |name, conditions| d[name.gsub('/', '-')] = conditions end end end end def implicit_version(mod) return :latest if @conditions[mod].empty? if @conditions[mod].all? { |c| c[:queued] || c[:module] == :you } return :latest end return :best end def annotated_version(mod, versions) if versions.empty? return implicit_version(mod) else return "#{implicit_version(mod)}: #{versions.last}" end end def resolve_constraints(dependencies, source = [{:name => :you}], seen = {}, action = @action) dependencies = dependencies.map do |mod, range| source.last[:dependency] = range @conditions[mod] << { :module => source.last[:name], :version => source.last[:version], :dependency => range, :queued => true } if forced? range = Puppet::Module.parse_range(@version, @strict_semver) rescue Puppet::Module.parse_range('>= 0.0.0', @strict_semver) else range = (@conditions[mod]).map do |r| Puppet::Module.parse_range(r[:dependency], @strict_semver) rescue Puppet::Module.parse_range('>= 0.0.0', @strict_semver) end.inject(&:&) end if @action == :install && seen.include?(mod) next if range === seen[mod][:semver] req_module = @module_name req_versions = @versions["#{@module_name}"].map { |v| v[:semver] } raise InvalidDependencyCycleError, :module_name => mod, :source => (source + [{ :name => mod, :version => source.last[:dependency] }]), :requested_module => req_module, :requested_version => @version || annotated_version(req_module, req_versions), :conditions => @conditions end if !(forced? || @installed[mod].empty? || source.last[:name] == :you) next if range === SemanticPuppet::Version.parse(@installed[mod].first.version) action = :upgrade elsif @installed[mod].empty? action = :install end if action == :upgrade @conditions.each { |_, conds| conds.delete_if { |c| c[:module] == mod } } end versions = @versions["#{mod}"].select { |h| range === h[:semver] } valid_versions = versions.select { |x| x[:semver].special == '' } valid_versions = versions if valid_versions.empty? unless version = valid_versions.last req_module = @module_name req_versions = @versions["#{@module_name}"].map { |v| v[:semver] } raise NoVersionsSatisfyError, :requested_name => req_module, :requested_version => @version || annotated_version(req_module, req_versions), :installed_version => @installed[@module_name].empty? ? nil : @installed[@module_name].first.version, :dependency_name => mod, :conditions => @conditions[mod], :action => @action end seen[mod] = version { :module => mod, :version => version, :action => action, :previous_version => @installed[mod].empty? ? nil : @installed[mod].first.version, :file => @urls["#{mod}@#{version[:vstring]}"], :path => action == :install ? @options[:target_dir] : (@installed[mod].empty? ? @options[:target_dir] : @installed[mod].first.modulepath), :dependencies => [] } end.compact dependencies.each do |mod| deps = @remote["#{mod[:module]}@#{mod[:version][:vstring]}"].sort_by(&:first) mod[:dependencies] = resolve_constraints(deps, source + [{ :name => mod[:module], :version => mod[:version][:vstring] }], seen, :install) end unless @ignore_dependencies return dependencies end def download_tarballs(graph, default_path, forge) graph.map do |release| begin if release[:tarball] cache_path = Pathname(release[:tarball]) else cache_path = forge.retrieve(release[:file]) end rescue OpenURI::HTTPError => e raise RuntimeError, _("Could not download module: %{message}") % { message: e.message }, e.backtrace end [ { (release[:path] ||= default_path) => cache_path}, *download_tarballs(release[:dependencies], default_path, forge) ] end.flatten end def forced? options[:force] end def add_module_name_constraints_to_graph(graph) # Puppet modules are installed by "module name", but resolved by # "full name" (including namespace). So that we don't run into # problems at install time, we should reject any solution that # depends on multiple nodes with the same "module name". graph.add_graph_constraint('PMT') do |nodes| names = nodes.map { |x| x.dependency_names + [ x.name ] }.flatten names = names.map { |x| x.tr('/', '-') }.uniq names = names.map { |x| x[/-(.*)/, 1] } names.length == names.uniq.length end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/skeleton/������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�021250� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/skeleton/templates/��������������������������������������������0000755�0052762�0001160�00000000000�13417162176�023246� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/skeleton/templates/generator/����������������������������������0000755�0052762�0001160�00000000000�13417162176�025234� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/skeleton/templates/generator/Gemfile���������������������������0000644�0052762�0001160�00000000754�13417161721�026530� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������source ENV['GEM_SOURCE'] || 'https://rubygems.org' puppetversion = ENV.key?('PUPPET_VERSION') ? ENV['PUPPET_VERSION'] : ['>= 3.3'] gem 'metadata-json-lint' gem 'puppet', puppetversion gem 'puppetlabs_spec_helper', '>= 1.2.0' gem 'puppet-lint', '>= 1.0.0' gem 'facter', '>= 1.7.0' gem 'rspec-puppet' # rspec must be v2 for ruby 1.8.7 if RUBY_VERSION >= '1.8.7' && RUBY_VERSION < '1.9' gem 'rspec', '~> 2.0' gem 'rake', '~> 10.0' else # rubocop requires ruby >= 1.9 gem 'rubocop' end ��������������������puppet-5.5.10/lib/puppet/module_tool/skeleton/templates/generator/README.md.erb���������������������0000644�0052762�0001160�00000007342�13417161721�027263� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# <%= metadata.name %> #### Table of Contents 1. [Description](#description) 1. [Setup - The basics of getting started with <%= metadata.name %>](#setup) * [What <%= metadata.name %> affects](#what-<%= metadata.name %>-affects) * [Setup requirements](#setup-requirements) * [Beginning with <%= metadata.name %>](#beginning-with-<%= metadata.name %>) 1. [Usage - Configuration options and additional functionality](#usage) 1. [Reference - An under-the-hood peek at what the module is doing and how](#reference) 1. [Limitations - OS compatibility, etc.](#limitations) 1. [Development - Guide for contributing to the module](#development) ## Description Start with a one- or two-sentence summary of what the module does and/or what problem it solves. This is your 30-second elevator pitch for your module. Consider including OS/Puppet version it works with. You can give more descriptive information in a second paragraph. This paragraph should answer the questions: "What does this module *do*?" and "Why would I use it?" If your module has a range of functionality (installation, configuration, management, etc.), this is the time to mention it. ## Setup ### What <%= metadata.name %> affects **OPTIONAL** If it's obvious what your module touches, you can skip this section. For example, folks can probably figure out that your mysql_instance module affects their MySQL instances. If there's more that they should know about, though, this is the place to mention: * A list of files, packages, services, or operations that the module will alter, impact, or execute. * Dependencies that your module automatically installs. * Warnings or other important notices. ### Setup Requirements **OPTIONAL** If your module requires anything extra before setting up (pluginsync enabled, etc.), mention it here. If your most recent release breaks compatibility or requires particular steps for upgrading, you might want to include an additional "Upgrading" section here. ### Beginning with <%= metadata.name %> The very basic steps needed for a user to get the module up and running. This can include setup steps, if necessary, or it can be an example of the most basic use of the module. ## Usage This section is where you describe how to customize, configure, and do the fancy stuff with your module here. It's especially helpful if you include usage examples and code samples for doing things with your module. ## Reference Users need a complete list of your module's classes, types, defined types providers, facts, and functions, along with the parameters for each. You can provide this list either via Puppet Strings code comments or as a complete list in this Reference section. * If you are using Puppet Strings code comments, this Reference section should include Strings information so that your users know how to access your documentation. * If you are not using Puppet Strings, include a list of all of your classes, defined types, and so on, along with their parameters. Each element in this listing should include: * The data type, if applicable. * A description of what the element does. * Valid values, if the data type doesn't make it obvious. * Default value, if any. ## Limitations This is where you list OS compatibility, version compatibility, etc. If there are Known Issues, you might want to include them under their own heading here. ## Development Since your module is awesome, other users will want to play with it. Let them know what the ground rules for contributing are. ## Release Notes/Contributors/Etc. **Optional** If you aren't using changelog, put your release notes here (though you should consider using changelog). You can also add any additional sections you feel are necessary or important to include here. Please use the `## ` header. ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/skeleton/templates/generator/Rakefile��������������������������0000644�0052762�0001160�00000001630�13417161721�026674� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppetlabs_spec_helper/rake_tasks' require 'puppet-lint/tasks/puppet-lint' require 'metadata-json-lint/rake_task' if RUBY_VERSION >= '1.9' require 'rubocop/rake_task' RuboCop::RakeTask.new end PuppetLint.configuration.send('disable_80chars') PuppetLint.configuration.relative = true PuppetLint.configuration.ignore_paths = ['spec/**/*.pp', 'pkg/**/*.pp'] desc 'Validate manifests, templates, and ruby files' task :validate do Dir['manifests/**/*.pp'].each do |manifest| sh "puppet parser validate --noop #{manifest}" end Dir['spec/**/*.rb', 'lib/**/*.rb'].each do |ruby_file| sh "ruby -c #{ruby_file}" unless ruby_file =~ %r{spec/fixtures} end Dir['templates/**/*.erb'].each do |template| sh "erb -P -x -T '-' #{template} | ruby -c" end end desc 'Run lint, validate, and spec tests.' task :test do [:lint, :validate, :spec].each do |test| Rake::Task[test].invoke end end ��������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/skeleton/templates/generator/examples/�������������������������0000755�0052762�0001160�00000000000�13417162176�027052� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/skeleton/templates/generator/examples/init.pp.erb��������������0000644�0052762�0001160�00000001035�13417161721�031117� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The baseline for module testing used by Puppet Inc. is that each manifest # should have a corresponding test manifest that declares that class or defined # type. # # Tests are then run by using puppet apply --noop (to check for compilation # errors and view a log of events) or by fully applying the test in a virtual # environment (to compare the resulting system state to the desired state). # # Learn more about module testing here: # https://puppet.com/docs/puppet/latest/bgtm.html#testing-your-module # include ::<%= metadata.name %> ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/skeleton/templates/generator/manifests/������������������������0000755�0052762�0001160�00000000000�13417162176�027225� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/skeleton/templates/generator/manifests/init.pp.erb�������������0000644�0052762�0001160�00000002140�13417161721�031270� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Class: <%= metadata.name %> # =========================== # # Full description of class <%= metadata.name %> here. # # Parameters # ---------- # # Document parameters here. # # * `sample parameter` # Explanation of what this parameter affects and what it defaults to. # e.g. "Specify one or more upstream ntp servers as an array." # # Variables # ---------- # # Here you should define a list of variables that this module would require. # # * `sample variable` # Explanation of how this variable affects the function of this class and if # it has a default. e.g. "The parameter enc_ntp_servers must be set by the # External Node Classifier as a comma separated list of hostnames." (Note, # global variables should be avoided in favor of class parameters as # of Puppet 2.6.) # # Examples # -------- # # @example # class { '<%= metadata.name %>': # servers => [ 'pool.ntp.org', 'ntp.local.company.com' ], # } # # Authors # ------- # # Author Name <author@domain.com> # # Copyright # --------- # # Copyright <%= Time.now.year %> Your name here, unless otherwise noted. # class <%= metadata.name %> { } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/skeleton/templates/generator/metadata.json.erb�����������������0000644�0052762�0001160�00000000030�13417161721�030442� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������<%= metadata.to_json %> ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/skeleton/templates/generator/spec/�����������������������������0000755�0052762�0001160�00000000000�13417162176�026166� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/skeleton/templates/generator/spec/classes/���������������������0000755�0052762�0001160�00000000000�13417162176�027623� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/skeleton/templates/generator/spec/classes/init_spec.rb.erb�����0000644�0052762�0001160�00000000261�13417161721�032666� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'spec_helper' describe '<%= metadata.name %>' do context 'with default values for all parameters' do it { should contain_class('<%= metadata.name %>') } end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/module_tool/skeleton/templates/generator/spec/spec_helper.rb���������������0000644�0052762�0001160�00000000064�13417161721�030777� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppetlabs_spec_helper/module_spec_helper' ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network.rb�����������������������������������������������������������������0000644�0052762�0001160�00000000135�13417161721�017112� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Just a stub, so we can correctly scope other classes. module Puppet::Network # :nodoc: end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/�������������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�016573� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/auth_config_parser.rb����������������������������������������������0000644�0052762�0001160�00000006176�13417161721�022767� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/network/rights' module Puppet::Network class AuthConfigParser def self.new_from_file(file) self.new(Puppet::FileSystem.read(file, :encoding => 'utf-8')) end def initialize(string) @string = string end def parse Puppet::Network::AuthConfig.new(parse_rights) end def parse_rights rights = Puppet::Network::Rights.new right = nil count = 1 @string.each_line { |line| case line.chomp when /^\s*#/, /^\s*$/ # skip comments and blank lines when /^path\s+((?:~\s+)?[^ ]+)\s*$/ # "path /path" or "path ~ regex" name = $1.chomp right = rights.newright(name, count, @file) when /^\s*(allow(?:_ip)?|deny(?:_ip)?|method|environment|auth(?:enticated)?)\s+(.+?)(\s*#.*)?$/ if right.nil? #TRANSLATORS "path" is a configuration file entry and should not be translated raise Puppet::ConfigurationError, _("Missing or invalid 'path' before right directive at %{error_location}") % { error_location: Puppet::Util::Errors.error_location(@file, count) } end parse_right_directive(right, $1, $2, count) else error_location_str = Puppet::Util::Errors.error_location(nil, count) raise Puppet::ConfigurationError, _("Invalid entry at %{error_location}: %{file_text}") % { error_location: error_location_str, file_text: line } end count += 1 } # Verify each of the rights are valid. # We let the check raise an error, so that it can raise an error # pointing to the specific problem. rights.each { |name, r| r.valid? } rights end def parse_right_directive(right, var, value, count) value.strip! case var when "allow" modify_right(right, :allow, value, _("allowing %{value} access"), count) when "deny" modify_right(right, :deny, value, _("denying %{value} access"), count) when "allow_ip" modify_right(right, :allow_ip, value, _("allowing IP %{value} access"), count) when "deny_ip" modify_right(right, :deny_ip, value, _("denying IP %{value} access"), count) when "method" modify_right(right, :restrict_method, value, _("allowing 'method' %{value}"), count) when "environment" modify_right(right, :restrict_environment, value, _("adding environment %{value}"), count) when /auth(?:enticated)?/ modify_right(right, :restrict_authenticated, value, _("adding authentication %{value}"), count) else error_location_str = Puppet::Util::Errors.error_location(nil, count) raise Puppet::ConfigurationError, _("Invalid argument '%{var}' at %{error_location}") % { var: var, error_location: error_location_str } end end def modify_right(right, method, value, msg, count) value.split(/\s*,\s*/).each do |val| begin val.strip! right.info msg % { value: val } right.send(method, val) rescue Puppet::AuthStoreError => detail error_location_str = Puppet::Util::Errors.error_location(@file, count) raise Puppet::ConfigurationError, "#{detail} #{error_location_str}", detail.backtrace end end end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/authorization.rb���������������������������������������������������0000644�0052762�0001160�00000002225�13417161721�022014� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/network/client_request' require 'puppet/network/authconfig' require 'puppet/network/auth_config_parser' module Puppet::Network class AuthConfigLoader # Create our config object if necessary. If there's no configuration file # we install our defaults def self.authconfig @auth_config_file ||= Puppet::Util::WatchedFile.new(Puppet[:rest_authconfig]) if (not @auth_config) or @auth_config_file.changed? begin @auth_config = Puppet::Network::AuthConfigParser.new_from_file(Puppet[:rest_authconfig]).parse rescue Errno::ENOENT, Errno::ENOTDIR @auth_config = Puppet::Network::AuthConfig.new end end @auth_config end end module Authorization @@authconfigloader_class = nil def self.authconfigloader_class=(klass) @@authconfigloader_class = klass end def authconfig authconfigloader = @@authconfigloader_class || AuthConfigLoader authconfigloader.authconfig end # Verify that our client has access. def check_authorization(method, path, params) authconfig.check_authorization(method, path, params) end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/authstore.rb�������������������������������������������������������0000644�0052762�0001160�00000021434�13417161721�021135� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# standard module for determining whether a given hostname or IP has access to # the requested resource require 'ipaddr' require 'puppet/util/logging' module Puppet class AuthStoreError < Puppet::Error; end class AuthorizationError < Puppet::Error; end class Network::AuthStore include Puppet::Util::Logging # Is a given combination of name and ip address allowed? If either input # is non-nil, then both inputs must be provided. If neither input # is provided, then the authstore is considered local and defaults to "true". def allowed?(name, ip) if name or ip # This is probably unnecessary, and can cause some weirdness in # cases where we're operating over localhost but don't have a real # IP defined. raise Puppet::DevError, _("Name and IP must be passed to 'allowed?'") unless name and ip # else, we're networked and such else # we're local return true end # yay insecure overrides return true if globalallow? if decl = declarations.find { |d| d.match?(name, ip) } return decl.result end info _("defaulting to no access for %{name}") % { name: name } false end # Mark a given pattern as allowed. def allow(pattern) # a simple way to allow anyone at all to connect if pattern == "*" @globalallow = true else store(:allow, pattern) end nil end def allow_ip(pattern) store(:allow_ip, pattern) end # Deny a given pattern. def deny(pattern) store(:deny, pattern) end def deny_ip(pattern) store(:deny_ip, pattern) end # Is global allow enabled? def globalallow? @globalallow end # does this auth store has any rules? def empty? @globalallow.nil? && @declarations.size == 0 end def initialize @globalallow = nil @declarations = [] end def to_s "authstore" end def interpolate(match) @modified_declarations = @declarations.collect { |ace| ace.interpolate(match) }.sort end def reset_interpolation @modified_declarations = nil end private # Returns our ACEs list, but if we have a modification of it, let's return # it. This is used if we want to override the this purely immutable list # by a modified version. def declarations @modified_declarations || @declarations end # Store the results of a pattern into our hash. Basically just # converts the pattern and sticks it into the hash. def store(type, pattern) @declarations << Declaration.new(type, pattern) @declarations.sort! nil end # A single declaration. Stores the info for a given declaration, # provides the methods for determining whether a declaration matches, # and handles sorting the declarations appropriately. class Declaration include Puppet::Util include Comparable # The type of declaration: either :allow or :deny attr_reader :type VALID_TYPES = [ :allow, :deny, :allow_ip, :deny_ip ] attr_accessor :name # The pattern we're matching against. Can be an IPAddr instance, # or an array of strings, resulting from reversing a hostname # or domain name. attr_reader :pattern # The length. Only used for iprange and domain. attr_accessor :length # Sort the declarations most specific first. def <=>(other) compare(exact?, other.exact?) || compare(ip?, other.ip?) || ((length != other.length) && (other.length <=> length)) || compare(deny?, other.deny?) || ( ip? ? pattern.to_s <=> other.pattern.to_s : pattern <=> other.pattern) end def deny? type == :deny end def exact? @exact == :exact end def initialize(type, pattern) self.type = type self.pattern = pattern end # Are we an IP type? def ip? name == :ip end # Does this declaration match the name/ip combo? def match?(name, ip) if ip? pattern.include?(IPAddr.new(ip)) else matchname?(name) end end # Set the pattern appropriately. Also sets the name and length. def pattern=(pattern) if [:allow_ip, :deny_ip].include?(self.type) parse_ip(pattern) else parse(pattern) end @orig = pattern end # Mapping a type of statement into a return value. def result [:allow, :allow_ip].include?(type) end def to_s "#{type}: #{pattern}" end # Set the declaration type. Either :allow or :deny. def type=(type) type = type.intern raise ArgumentError, _("Invalid declaration type %{type}") % { type: type } unless VALID_TYPES.include?(type) @type = type end # interpolate a pattern to replace any # backreferences by the given match # for instance if our pattern is $1.reductivelabs.com # and we're called with a MatchData whose capture 1 is puppet # we'll return a pattern of puppet.reductivelabs.com def interpolate(match) clone = dup if @name == :dynamic clone.pattern = clone.pattern.reverse.collect do |p| p.gsub(/\$(\d)/) { |m| match[$1.to_i] } end.join(".") end clone end private # Returns nil if both values are true or both are false, returns # -1 if the first is true, and 1 if the second is true. Used # in the <=> operator. def compare(me, them) (me and them) ? nil : me ? -1 : them ? 1 : nil end # Does the name match our pattern? def matchname?(name) case @name when :domain, :dynamic, :opaque name = munge_name(name) (pattern == name) or (not exact? and pattern.zip(name).all? { |p,n| p == n }) when :regex Regexp.new(pattern.slice(1..-2)).match(name) end end # Convert the name to a common pattern. def munge_name(name) # Change to name.downcase.split(".",-1).reverse for FQDN support name.downcase.split(".").reverse end # Parse our input pattern and figure out what kind of allowable # statement it is. The output of this is used for later matching. Octet = '(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])' IPv4 = "#{Octet}\.#{Octet}\.#{Octet}\.#{Octet}" IPv6_full = "_:_:_:_:_:_:_:_|_:_:_:_:_:_::_?|_:_:_:_:_::((_:)?_)?|_:_:_:_::((_:){0,2}_)?|_:_:_::((_:){0,3}_)?|_:_::((_:){0,4}_)?|_::((_:){0,5}_)?|::((_:){0,6}_)?" IPv6_partial = "_:_:_:_:_:_:|_:_:_:_::(_:)?|_:_::(_:){0,2}|_::(_:){0,3}" # It should be: # IP = "#{IPv4}|#{IPv6_full}|(#{IPv6_partial}#{IPv4})".gsub(/_/,'([0-9a-fA-F]{1,4})').gsub(/\(/,'(?:') # but ruby's ipaddr lib doesn't support the hybrid format IP = "#{IPv4}|#{IPv6_full}".gsub(/_/,'([0-9a-fA-F]{1,4})').gsub(/\(/,'(?:') def parse_ip(value) @name = :ip @exact, @length, @pattern = *case value when /^(?:#{IP})\/(\d+)$/ # 12.34.56.78/24, a001:b002::efff/120, c444:1000:2000::9:192.168.0.1/112 [:inexact, $1.to_i, IPAddr.new(value)] when /^(#{IP})$/ # 10.20.30.40, [:exact, nil, IPAddr.new(value)] when /^(#{Octet}\.){1,3}\*$/ # an ip address with a '*' at the end segments = value.split(".")[0..-2] bits = 8*segments.length [:inexact, bits, IPAddr.new((segments+[0,0,0])[0,4].join(".") + "/#{bits}")] else raise AuthStoreError, _("Invalid IP pattern %{value}") % { value: value } end end def parse(value) @name,@exact,@length,@pattern = *case value when /^(\w[-\w]*\.)+[-\w]+$/ # a full hostname # Change to /^(\w[-\w]*\.)+[-\w]+\.?$/ for FQDN support [:domain,:exact,nil,munge_name(value)] when /^\*(\.(\w[-\w]*)){1,}$/ # *.domain.com host_sans_star = munge_name(value)[0..-2] [:domain,:inexact,host_sans_star.length,host_sans_star] when /\$\d+/ # a backreference pattern ala $1.reductivelabs.com or 192.168.0.$1 or $1.$2 [:dynamic,:exact,nil,munge_name(value)] when /^\w[-.@\w]*$/ # ? Just like a host name but allow '@'s and ending '.'s [:opaque,:exact,nil,[value]] when /^\/.*\/$/ # a regular expression [:regex,:inexact,nil,value] else raise AuthStoreError, "Invalid pattern #{value}" end end end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/client_request.rb��������������������������������������������������0000644�0052762�0001160�00000001467�13417161721�022151� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Network # :nodoc: # A struct-like class for passing around a client request. It's mostly # just used for validation and authorization. class ClientRequest attr_accessor :name, :ip, :authenticated, :handler, :method def authenticated? self.authenticated end # A common way of talking about the full call. Individual servers # are responsible for setting the values correctly, but this common # format makes it possible to check rights. def call raise ArgumentError, _("Request is not set up; cannot build call") unless handler and method [handler, method].join(".") end def initialize(name, ip, authenticated) @name, @ip, @authenticated = name, ip, authenticated end def to_s "#{self.name}(#{self.ip})" end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/format.rb����������������������������������������������������������0000644�0052762�0001160�00000006741�13417161721�020413� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/confiner' # A simple class for modeling encoding formats for moving # instances around the network. class Puppet::Network::Format include Puppet::Confiner attr_reader :name, :mime attr_accessor :intern_method, :render_method, :intern_multiple_method, :render_multiple_method, :weight, :required_methods, :extension, :charset def init_attribute(name, default) value = @options.delete(name) value = default if value.nil? self.send(name.to_s + "=", value) end def initialize(name, options = {}, &block) @name = name.to_s.downcase.intern @options = options # This must be done early the values can be used to set required_methods define_method_names method_list = { :intern_method => "from_#{name}", :intern_multiple_method => "from_multiple_#{name}", :render_multiple_method => "to_multiple_#{name}", :render_method => "to_#{name}" } init_attribute(:mime, "text/#{name}") init_attribute(:weight, 5) init_attribute(:required_methods, method_list.keys) init_attribute(:extension, name.to_s) init_attribute(:charset, nil) method_list.each do |method, value| init_attribute(method, value) end raise ArgumentError, _("Unsupported option(s) %{options_list}") % { options_list: @options.keys } unless @options.empty? @options = nil instance_eval(&block) if block_given? end def intern(klass, text) return klass.send(intern_method, text) if klass.respond_to?(intern_method) raise NotImplementedError, "#{klass} does not respond to #{intern_method}; can not intern instances from #{mime}" end def intern_multiple(klass, text) return klass.send(intern_multiple_method, text) if klass.respond_to?(intern_multiple_method) raise NotImplementedError, "#{klass} does not respond to #{intern_multiple_method}; can not intern multiple instances from #{mime}" end def mime=(mime) @mime = mime.to_s.downcase end def render(instance) return instance.send(render_method) if instance.respond_to?(render_method) raise NotImplementedError, "#{instance.class} does not respond to #{render_method}; can not render instances to #{mime}" end def render_multiple(instances) # This method implicitly assumes that all instances are of the same type. return instances[0].class.send(render_multiple_method, instances) if instances[0].class.respond_to?(render_multiple_method) raise NotImplementedError, _("%{klass} does not respond to %{method}; can not render multiple instances to %{mime}") % { klass: instances[0].class, method: render_multiple_method, mime: mime } end def required_methods_present?(klass) [:intern_method, :intern_multiple_method, :render_multiple_method].each do |name| return false unless required_method_present?(name, klass, :class) end return false unless required_method_present?(:render_method, klass, :instance) true end def supported?(klass) suitable? and required_methods_present?(klass) end def to_s "Puppet::Network::Format[#{name}]" end private def define_method_names @intern_method = "from_#{name}" @render_method = "to_#{name}" @intern_multiple_method = "from_multiple_#{name}" @render_multiple_method = "to_multiple_#{name}" end def required_method_present?(name, klass, type) return true unless required_methods.include?(name) method = send(name) return(type == :class ? klass.respond_to?(method) : klass.method_defined?(method)) end end �������������������������������puppet-5.5.10/lib/puppet/network/format_handler.rb��������������������������������������������������0000644�0052762�0001160�00000005440�13417161721�022103� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'yaml' require 'puppet/network' require 'puppet/network/format' module Puppet::Network::FormatHandler class FormatError < Puppet::Error; end ALL_MEDIA_TYPES = '*/*'.freeze @formats = {} def self.create(*args, &block) instance = Puppet::Network::Format.new(*args, &block) @formats[instance.name] = instance instance end def self.create_serialized_formats(name,options = {},&block) ["application/x-#{name}", "application/#{name}", "text/x-#{name}", "text/#{name}"].each { |mime_type| create name, {:mime => mime_type}.update(options), &block } end def self.format(name) @formats[name.to_s.downcase.intern] end def self.format_for(name) name = format_to_canonical_name(name) format(name) end def self.format_by_extension(ext) @formats.each do |name, format| return format if format.extension == ext end nil end # Provide a list of all formats. def self.formats @formats.keys end # Return a format capable of handling the provided mime type. def self.mime(mimetype) mimetype = mimetype.to_s.downcase @formats.values.find { |format| format.mime == mimetype } end # Return a format name given: # * a format name # * a mime-type # * a format instance def self.format_to_canonical_name(format) case format when Puppet::Network::Format out = format when %r{\w+/\w+} out = mime(format) else out = format(format) end if out.nil? raise ArgumentError, _("No format matches the given format name or mime-type (%{format})") % {format: format} end out.name end # Determine which of the accepted formats should be used given what is supported. # # @param accepted [Array<String, Symbol>] the accepted formats in a form a # that generally conforms to an HTTP Accept header. Any quality specifiers # are ignored and instead the formats are simply in strict preference order # (most preferred is first) # @param supported [Array<Symbol>] the names of the supported formats (the # most preferred format is first) # @return [Array<Puppet::Network::Format>] the most suitable formats that # are both accepted and supported # @api private def self.most_suitable_formats_for(accepted, supported) accepted.collect do |format| format.to_s.sub(/;q=.*$/, '') end.collect do |format| if format == ALL_MEDIA_TYPES supported.first else format_to_canonical_name_or_nil(format) end end.compact.find_all do |format| supported.include?(format) end.collect do |format| format_for(format) end end # @api private def self.format_to_canonical_name_or_nil(format) format_to_canonical_name(format) rescue ArgumentError nil end end require 'puppet/network/formats' ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/http/��������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�017552� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/http/api/����������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020323� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/http/api/indirection_type.rb���������������������������������������0000644�0052762�0001160�00000001316�13417161721�024214� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Network::HTTP::API::IndirectionType INDIRECTION_TYPE_MAP = { "certificate" => :ca, "certificate_request" => :ca, "certificate_revocation_list" => :ca, "certificate_status" => :ca } def self.master_url_prefix "#{Puppet::Network::HTTP::MASTER_URL_PREFIX}/v3" end def self.ca_url_prefix "#{Puppet::Network::HTTP::CA_URL_PREFIX}/v1" end def self.type_for(indirection) INDIRECTION_TYPE_MAP[indirection] || :master end def self.url_prefix_for(indirection_name) case type_for(indirection_name) when :ca ca_url_prefix when :master master_url_prefix else raise ArgumentError, _("Not a valid indirection type") end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/http/api/master.rb�������������������������������������������������0000644�0052762�0001160�00000000056�13417161721�022137� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Network::HTTP::API::Master end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/http/api/master/���������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�021616� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/http/api/master/v3.rb����������������������������������������������0000644�0052762�0001160�00000001530�13417161721�022465� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Network::HTTP::API::Master::V3 require 'puppet/network/http/api/master/v3/authorization' require 'puppet/network/http/api/master/v3/environments' require 'puppet/network/http/api/master/v3/environment' require 'puppet/network/http/api/indirected_routes' AUTHZ = Authorization.new INDIRECTED = Puppet::Network::HTTP::Route. path(/.*/). any(Puppet::Network::HTTP::API::IndirectedRoutes.new) ENVIRONMENTS = Puppet::Network::HTTP::Route. path(%r{^/environments$}).get(AUTHZ.wrap do Environments.new(Puppet.lookup(:environments)) end) ENVIRONMENT = Puppet::Network::HTTP::Route. path(%r{^/environment/[^/]+$}).get(AUTHZ.wrap do Environment.new end) def self.routes Puppet::Network::HTTP::Route.path(%r{v3}). any. chain(ENVIRONMENTS, ENVIRONMENT, INDIRECTED) end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/http/api/master/v3/������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�022146� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/http/api/master/v3/authorization.rb��������������������������������0000644�0052762�0001160�00000001037�13417161721�025367� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/network/authorization' class Puppet::Network::HTTP::API::Master::V3::Authorization include Puppet::Network::Authorization def wrap(&block) lambda do |request, response| begin authconfig.check_authorization(:find, request.path, request.params) rescue Puppet::Network::AuthorizationError => e raise Puppet::Network::HTTP::Error::HTTPNotAuthorizedError.new(e.message, Puppet::Network::HTTP::Issues::FAILED_AUTHORIZATION) end block.call.call(request, response) end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/http/api/master/v3/environment.rb����������������������������������0000644�0052762�0001160�00000006741�13417161721�025042� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/json' require 'puppet/parser/environment_compiler' class Puppet::Network::HTTP::API::Master::V3::Environment def call(request, response) env_name = request.routing_path.split('/').last env = Puppet.lookup(:environments).get(env_name) code_id = request.params[:code_id] if env.nil? raise Puppet::Network::HTTP::Error::HTTPNotFoundError.new(_("%{env_name} is not a known environment") % { env_name: env_name }, Puppet::Network::HTTP::Issues::RESOURCE_NOT_FOUND) end catalog = Puppet::Parser::EnvironmentCompiler.compile(env, code_id).to_resource env_graph = build_environment_graph(catalog) response.respond_with(200, "application/json", Puppet::Util::Json.dump(env_graph)) end def build_environment_graph(catalog) # This reads catalog and code_id off the catalog rather than using the one # from the request. There shouldn't really be a case where the two differ, # but if they do, the one from the catalog itself is authoritative. env_graph = {:environment => catalog.environment, :applications => {}, :code_id => catalog.code_id} applications = catalog.resources.select do |res| type = res.resource_type type.is_a?(Puppet::Resource::Type) && type.application? end applications.each do |app| file, line = app.file, app.line nodes = app['nodes'] required_components = catalog.direct_dependents_of(app).map {|comp| comp.ref} mapped_components = nodes.values.flatten.map {|comp| comp.ref} nonexistent_components = mapped_components - required_components if nonexistent_components.any? raise Puppet::ParseError.new( _("Application %{application} assigns nodes to non-existent components: %{component_list}") % { application: app, component_list: nonexistent_components.join(', ') }, file, line) end missing_components = required_components - mapped_components if missing_components.any? raise Puppet::ParseError.new(_("Application %{application} has components without assigned nodes: %{component_list}") % { application: app, component_list: missing_components.join(', ') }, file, line) end # Turn the 'nodes' hash into a map component ref => node name node_mapping = {} nodes.each do |node, comps| comps = [comps] unless comps.is_a?(Array) comps.each do |comp| raise Puppet::ParseError.new(_("Application %{app} assigns multiple nodes to component %{comp}") % { app: app, comp: comp }, file, line) if node_mapping.include?(comp.ref) node_mapping[comp.ref] = node.title end end app_components = {} catalog.direct_dependents_of(app).each do |comp| app_components[comp.ref] = { :produces => comp.export.map(&:ref), :consumes => prerequisites(comp).map(&:ref), :node => node_mapping[comp.ref] } end env_graph[:applications][app.ref] = app_components end env_graph end private # Finds all the prerequisites of component +comp+. They are all the # capability resources that +comp+ depends on; this includes resources # that +comp+ consumes but also resources it merely requires def prerequisites(comp) params = Puppet::Type.relationship_params.select { |p| p.direction == :in }.map(&:name) params.map { |rel| comp[rel] }.flatten.compact.select do |rel| rel.resource_type && rel.resource_type.is_capability? end end end �������������������������������puppet-5.5.10/lib/puppet/network/http/api/master/v3/environments.rb���������������������������������0000644�0052762�0001160�00000001513�13417161721�025215� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/json' class Puppet::Network::HTTP::API::Master::V3::Environments def initialize(env_loader) @env_loader = env_loader end def call(request, response) response.respond_with(200, "application/json", Puppet::Util::Json.dump({ "search_paths" => @env_loader.search_paths, "environments" => Hash[@env_loader.list.collect do |env| [env.name, { "settings" => { "modulepath" => env.full_modulepath, "manifest" => env.manifest, "environment_timeout" => timeout(env), "config_version" => env.config_version || '', } }] end] })) end private def timeout(env) ttl = @env_loader.get_conf(env.name).environment_timeout if ttl == Float::INFINITY "unlimited" else ttl end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/http/api/ca.rb�����������������������������������������������������0000644�0052762�0001160�00000000052�13417161721�021223� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Network::HTTP::API::CA end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/http/api/ca/�������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020706� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/http/api/ca/v1.rb��������������������������������������������������0000644�0052762�0001160�00000000466�13417161721�021562� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/network/http/api/indirected_routes' class Puppet::Network::HTTP::API::CA::V1 INDIRECTED = Puppet::Network::HTTP::Route. path(/.*/). any(Puppet::Network::HTTP::API::IndirectedRoutes.new) def self.routes Puppet::Network::HTTP::Route.path(%r{v1}).any.chain(INDIRECTED) end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/http/api/indirected_routes.rb��������������������������������������0000644�0052762�0001160�00000024726�13417161721�024371� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/network/authorization' require 'puppet/network/http/api/indirection_type' class Puppet::Network::HTTP::API::IndirectedRoutes include Puppet::Network::Authorization # How we map http methods and the indirection name in the URI # to an indirection method. METHOD_MAP = { "GET" => { :plural => :search, :singular => :find }, "POST" => { :singular => :find, }, "PUT" => { :singular => :save }, "DELETE" => { :singular => :destroy }, "HEAD" => { :singular => :head } } IndirectionType = Puppet::Network::HTTP::API::IndirectionType def self.routes Puppet::Network::HTTP::Route.path(/.*/).any(new) end # handle an HTTP request def call(request, response) indirection, method, key, params = uri2indirection(request.method, request.path, request.params) certificate = request.client_cert if !indirection.allow_remote_requests? # TODO: should we tell the user we found an indirection but it doesn't # allow remote requests, or just pretend there's no handler at all? what # are the security implications for the former? raise Puppet::Network::HTTP::Error::HTTPNotFoundError.new(_("No handler for %{indirection}") % { indirection: indirection.name }, :NO_INDIRECTION_REMOTE_REQUESTS) end trusted = Puppet::Context::TrustedInformation.remote(params[:authenticated], params[:node], certificate) Puppet.override(:trusted_information => trusted) do send("do_#{method}", indirection, key, params, request, response) end end def uri2indirection(http_method, uri, params) # the first field is always nil because of the leading slash, indirection_type, version, indirection_name, key = uri.split("/", 5)[1..-1] url_prefix = "/#{indirection_type}/#{version}" environment = params.delete(:environment) if indirection_name !~ /^\w+$/ raise Puppet::Network::HTTP::Error::HTTPBadRequestError.new( _("The indirection name must be purely alphanumeric, not '%{indirection_name}'") % { indirection_name: indirection_name }) end # this also depluralizes the indirection_name if it is a search method = indirection_method(http_method, indirection_name) # check whether this indirection matches the prefix and version in the # request if url_prefix != IndirectionType.url_prefix_for(indirection_name) raise Puppet::Network::HTTP::Error::HTTPBadRequestError.new( _("Indirection '%{indirection_name}' does not match url prefix '%{url_prefix}'") % { indirection_name: indirection_name, url_prefix: url_prefix }) end indirection = Puppet::Indirector::Indirection.instance(indirection_name.to_sym) if !indirection raise Puppet::Network::HTTP::Error::HTTPNotFoundError.new( _("Could not find indirection '%{indirection_name}'") % { indirection_name: indirection_name }, Puppet::Network::HTTP::Issues::HANDLER_NOT_FOUND) end if !environment raise Puppet::Network::HTTP::Error::HTTPBadRequestError.new( _("An environment parameter must be specified")) end if ! Puppet::Node::Environment.valid_name?(environment) raise Puppet::Network::HTTP::Error::HTTPBadRequestError.new( _("The environment must be purely alphanumeric, not '%{environment}'") % { environment: environment }) end configured_environment = Puppet.lookup(:environments).get(environment) unless configured_environment.nil? configured_environment = configured_environment.override_from_commandline(Puppet.settings) params[:environment] = configured_environment end begin check_authorization(method, "#{url_prefix}/#{indirection_name}/#{key}", params) rescue Puppet::Network::AuthorizationError => e raise Puppet::Network::HTTP::Error::HTTPNotAuthorizedError.new(e.message) end if configured_environment.nil? raise Puppet::Network::HTTP::Error::HTTPNotFoundError.new( _("Could not find environment '%{environment}'") % { environment: environment }) end params.delete(:bucket_path) if key == "" or key.nil? raise Puppet::Network::HTTP::Error::HTTPBadRequestError.new( _("No request key specified in %{uri}") % { uri: uri }) end [indirection, method, key, params] end private # Execute our find. def do_find(indirection, key, params, request, response) unless result = indirection.find(key, params) raise Puppet::Network::HTTP::Error::HTTPNotFoundError.new(_("Could not find %{value0} %{key}") % { value0: indirection.name, key: key }, Puppet::Network::HTTP::Issues::RESOURCE_NOT_FOUND) end rendered_result = result rendered_format = first_response_formatter_for(indirection.model, request) do |format| if result.respond_to?(:render) Puppet::Util::Profiler.profile(_("Rendered result in %{format}") % { format: format }, [:http, :v3_render, format]) do rendered_result = result.render(format) end end end Puppet::Util::Profiler.profile(_("Sent response"), [:http, :v3_response]) do response.respond_with(200, rendered_format, rendered_result) end end # Execute our head. def do_head(indirection, key, params, request, response) unless indirection.head(key, params) raise Puppet::Network::HTTP::Error::HTTPNotFoundError.new(_("Could not find %{indirection} %{key}") % { indirection: indirection.name, key: key }, Puppet::Network::HTTP::Issues::RESOURCE_NOT_FOUND) end # No need to set a response because no response is expected from a # HEAD request. All we need to do is not die. end # Execute our search. def do_search(indirection, key, params, request, response) result = indirection.search(key, params) if result.nil? raise Puppet::Network::HTTP::Error::HTTPNotFoundError.new(_("Could not find instances in %{indirection} with '%{key}'") % { indirection: indirection.name, key: key }, Puppet::Network::HTTP::Issues::RESOURCE_NOT_FOUND) end rendered_result = nil rendered_format = first_response_formatter_for(indirection.model, request) do |format| rendered_result = indirection.model.render_multiple(format, result) end response.respond_with(200, rendered_format, rendered_result) end # Execute our destroy. def do_destroy(indirection, key, params, request, response) formatter = accepted_response_formatter_or_json_for(indirection.model, request) result = indirection.destroy(key, params) response.respond_with(200, formatter, formatter.render(result)) end # Execute our save. def do_save(indirection, key, params, request, response) formatter = accepted_response_formatter_or_json_for(indirection.model, request) sent_object = read_body_into_model(indirection.model, request) result = indirection.save(sent_object, key) response.respond_with(200, formatter, formatter.render(result)) end # Return the first response formatter that didn't cause the yielded # block to raise a FormatError. def first_response_formatter_for(model, request, &block) formats = accepted_response_formatters_for(model, request) formatter = formats.find do |format| begin yield format true rescue Puppet::Network::FormatHandler::FormatError false end end return formatter if formatter raise Puppet::Network::HTTP::Error::HTTPNotAcceptableError.new( _("No supported formats are acceptable (Accept: %{accepted_formats})") % { accepted_formats: formats }, Puppet::Network::HTTP::Issues::UNSUPPORTED_FORMAT) end # Return an array of response formatters that the client accepts and # the server supports. def accepted_response_formatters_for(model_class, request) request.response_formatters_for(model_class.supported_formats) end # Return the first response formatter that the client accepts and # the server supports, or default to 'application/json'. def accepted_response_formatter_or_json_for(model_class, request) request.response_formatters_for(model_class.supported_formats, "application/json").first end def read_body_into_model(model_class, request) data = request.body.to_s formatter = request.formatter if formatter.supported?(model_class) begin return model_class.convert_from(formatter.name.to_s, data) rescue => e raise Puppet::Network::HTTP::Error::HTTPBadRequestError.new( _("The request body is invalid: %{message}") % { message: e.message }) end end #TRANSLATORS "mime-type" is a keyword and should not be translated raise Puppet::Network::HTTP::Error::HTTPUnsupportedMediaTypeError.new( _("Client sent a mime-type (%{header}) that doesn't correspond to a format we support") % { header: request.headers['content-type'] }, Puppet::Network::HTTP::Issues::UNSUPPORTED_MEDIA_TYPE) end def indirection_method(http_method, indirection) raise Puppet::Network::HTTP::Error::HTTPMethodNotAllowedError.new( _("No support for http method %{http_method}") % { http_method: http_method }) unless METHOD_MAP[http_method] unless method = METHOD_MAP[http_method][plurality(indirection)] raise Puppet::Network::HTTP::Error::HTTPBadRequestError.new( _("No support for plurality %{indirection} for %{http_method} operations") % { indirection: plurality(indirection), http_method: http_method }) end method end def self.request_to_uri(request) uri, body = request_to_uri_and_body(request) "#{uri}?#{body}" end def self.request_to_uri_and_body(request) url_prefix = IndirectionType.url_prefix_for(request.indirection_name.to_s) indirection = request.method == :search ? pluralize(request.indirection_name.to_s) : request.indirection_name.to_s ["#{url_prefix}/#{indirection}/#{Puppet::Util.uri_encode(request.key)}", "environment=#{request.environment.name}&#{request.query_string}"] end def self.pluralize(indirection) return(indirection == "status" ? "statuses" : indirection + "s") end def plurality(indirection) # NOTE These specific hooks for paths are ridiculous, but it's a *many*-line # fix to not need this, and our goal is to move away from the complication # that leads to the fix being too long. return :singular if indirection == "facts" return :singular if indirection == "status" return :singular if indirection == "certificate_status" result = (indirection =~ /s$|_search$/) ? :plural : :singular indirection.sub!(/s$|_search$/, '') indirection.sub!(/statuse$/, 'status') result end end ������������������������������������������puppet-5.5.10/lib/puppet/network/http/compression.rb������������������������������������������������0000644�0052762�0001160�00000006752�13417161721�022445� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/network/http' module Puppet::Network::HTTP::Compression # from https://github.com/ruby/ruby/blob/v2_1_3/lib/net/http/generic_request.rb#L40 ACCEPT_ENCODING = "gzip;q=1.0,deflate;q=0.6,identity;q=0.3" # this module function allows to use the right underlying # methods depending on zlib presence def module return(Puppet.features.zlib? ? Active : None) end module_function :module module Active require 'zlib' require 'stringio' # return an uncompressed body if the response has been # compressed def uncompress_body(response) case response['content-encoding'] when 'gzip' # ZLib::GzipReader has an associated encoding, by default Encoding.default_external return Zlib::GzipReader.new(StringIO.new(response.body), :encoding => Encoding::BINARY).read when 'deflate' return Zlib::Inflate.new.inflate(response.body) when nil, 'identity' return response.body else raise Net::HTTPError.new(_("Unknown content encoding - %{encoding}") % { encoding: response['content-encoding'] }, response) end end def uncompress(response) raise Net::HTTPError.new("No block passed", response) unless block_given? case response['content-encoding'] when 'gzip','deflate' uncompressor = ZlibAdapter.new when nil, 'identity' uncompressor = IdentityAdapter.new else raise Net::HTTPError.new(_("Unknown content encoding - %{encoding}") % { encoding: response['content-encoding'] }, response) end begin yield uncompressor ensure uncompressor.close end end def add_accept_encoding(headers={}) headers['accept-encoding'] = Puppet::Network::HTTP::Compression::ACCEPT_ENCODING headers end # This adapters knows how to uncompress both 'zlib' stream (the deflate algorithm from Content-Encoding) # and GZip streams. class ZlibAdapter def initialize(uncompressor = Zlib::Inflate.new(15 + 32)) # Create an inflater that knows to parse GZip streams and zlib streams. # This uses a property of the C Zlib library, documented as follow: # windowBits can also be greater than 15 for optional gzip decoding. Add # 32 to windowBits to enable zlib and gzip decoding with automatic header # detection, or add 16 to decode only the gzip format (the zlib format will # return a Z_DATA_ERROR). If a gzip stream is being decoded, strm->adler is # a crc32 instead of an adler32. @uncompressor = uncompressor @first = true end def uncompress(chunk) out = @uncompressor.inflate(chunk) @first = false return out rescue Zlib::DataError # it can happen that we receive a raw deflate stream # which might make our inflate throw a data error. # in this case, we try with a verbatim (no header) # deflater. @uncompressor = Zlib::Inflate.new if @first then @first = false retry end raise end def close @uncompressor.finish ensure @uncompressor.close end end end module None def uncompress_body(response) response.body end def add_accept_encoding(headers) headers end def uncompress(response) yield IdentityAdapter.new end end class IdentityAdapter def uncompress(chunk) chunk end def close end end end ����������������������puppet-5.5.10/lib/puppet/network/http/error.rb������������������������������������������������������0000644�0052762�0001160�00000004074�13417161721�021230� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/json' module Puppet::Network::HTTP::Error Issues = Puppet::Network::HTTP::Issues class HTTPError < Exception attr_reader :status, :issue_kind def initialize(message, status, issue_kind) super(message) @status = status @issue_kind = issue_kind end def to_json Puppet::Util::Json.dump({:message => message, :issue_kind => @issue_kind}) end end class HTTPNotAcceptableError < HTTPError CODE = 406 def initialize(message, issue_kind = Issues::RUNTIME_ERROR) super(_("Not Acceptable: %{message}") % { message: message }, CODE, issue_kind) end end class HTTPNotFoundError < HTTPError CODE = 404 def initialize(message, issue_kind = Issues::RUNTIME_ERROR) super(_("Not Found: %{message}") % { message: message }, CODE, issue_kind) end end class HTTPNotAuthorizedError < HTTPError CODE = 403 def initialize(message, issue_kind = Issues::RUNTIME_ERROR) super(_("Not Authorized: %{message}") % { message: message }, CODE, issue_kind) end end class HTTPBadRequestError < HTTPError CODE = 400 def initialize(message, issue_kind = Issues::RUNTIME_ERROR) super(_("Bad Request: %{message}") % { message: message }, CODE, issue_kind) end end class HTTPMethodNotAllowedError < HTTPError CODE = 405 def initialize(message, issue_kind = Issues::RUNTIME_ERROR) super(_("Method Not Allowed: %{message}") % { message: message }, CODE, issue_kind) end end class HTTPUnsupportedMediaTypeError < HTTPError CODE = 415 def initialize(message, issue_kind = Issues::RUNTIME_ERROR) super(_("Unsupported Media Type: %{message}") % { message: message }, CODE, issue_kind) end end class HTTPServerError < HTTPError CODE = 500 def initialize(original_error, issue_kind = Issues::RUNTIME_ERROR) super(_("Server Error: %{message}") % { message: original_error.message }, CODE, issue_kind) end def to_json Puppet::Util::Json.dump({:message => message, :issue_kind => @issue_kind}) end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/http/issues.rb�����������������������������������������������������0000644�0052762�0001160�00000000771�13417161721�021412� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Network::HTTP::Issues NO_INDIRECTION_REMOTE_REQUESTS = :NO_INDIRECTION_REMOTE_REQUESTS HANDLER_NOT_FOUND = :HANDLER_NOT_FOUND RESOURCE_NOT_FOUND = :RESOURCE_NOT_FOUND ENVIRONMENT_NOT_FOUND = :ENVIRONMENT_NOT_FOUND RUNTIME_ERROR = :RUNTIME_ERROR MISSING_HEADER_FIELD = :MISSING_HEADER_FIELD UNSUPPORTED_FORMAT = :UNSUPPORTED_FORMAT UNSUPPORTED_METHOD = :UNSUPPORTED_METHOD FAILED_AUTHORIZATION = :FAILED_AUTHORIZATION UNSUPPORTED_MEDIA_TYPE = :UNSUPPORTED_MEDIA_TYPE end �������puppet-5.5.10/lib/puppet/network/http/memory_response.rb��������������������������������������������0000644�0052762�0001160�00000000331�13417161721�023315� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Network::HTTP::MemoryResponse attr_reader :code, :type, :body def initialize @body = "" end def respond_with(code, type, body) @code = code @type = type @body += body end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/http/nocache_pool.rb�����������������������������������������������0000644�0052762�0001160�00000000741�13417161721�022525� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# A pool that does not cache HTTP connections. # # @api private class Puppet::Network::HTTP::NoCachePool def initialize(factory = Puppet::Network::HTTP::Factory.new) @factory = factory end # Yields a <tt>Net::HTTP</tt> connection. # # @yieldparam http [Net::HTTP] An HTTP connection def with_connection(site, verify, &block) http = @factory.create_connection(site) verify.setup_connection(http) yield http end def close # do nothing end end �������������������������������puppet-5.5.10/lib/puppet/network/http/pool.rb�������������������������������������������������������0000644�0052762�0001160�00000006260�13417161721�021047� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# A pool for persistent <tt>Net::HTTP</tt> connections. Connections are # stored in the pool indexed by their {Puppet::Network::HTTP::Site Site}. # Connections are borrowed from the pool, yielded to the caller, and # released back into the pool. If a connection is expired, it will be # closed either when a connection to that site is requested, or when # the pool is closed. The pool can store multiple connections to the # same site, and will be reused in MRU order. # # @api private # class Puppet::Network::HTTP::Pool FIFTEEN_SECONDS = 15 attr_reader :factory def initialize(keepalive_timeout = FIFTEEN_SECONDS) @pool = {} @factory = Puppet::Network::HTTP::Factory.new @keepalive_timeout = keepalive_timeout end def with_connection(site, verify, &block) reuse = true http = borrow(site, verify) begin if http.use_ssl? && http.verify_mode != OpenSSL::SSL::VERIFY_PEER reuse = false end yield http rescue => detail reuse = false raise detail ensure if reuse release(site, http) else close_connection(site, http) end end end def close @pool.each_pair do |site, sessions| sessions.each do |session| close_connection(site, session.connection) end end @pool.clear end # @api private def pool @pool end # Safely close a persistent connection. # # @api private def close_connection(site, http) Puppet.debug("Closing connection for #{site}") http.finish rescue => detail Puppet.log_exception(detail, _("Failed to close connection for %{site}: %{detail}") % { site: site, detail: detail }) end # Borrow and take ownership of a persistent connection. If a new # connection is created, it will be started prior to being returned. # # @api private def borrow(site, verify) @pool[site] = active_sessions(site) session = @pool[site].shift if session Puppet.debug("Using cached connection for #{site}") session.connection else http = @factory.create_connection(site) verify.setup_connection(http) Puppet.debug("Starting connection for #{site}") http.start setsockopts(http.instance_variable_get(:@socket)) http end end # Set useful socket option(s) which lack from default settings in Net:HTTP # # @api private def setsockopts(netio) socket = netio.io socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true) end # Release a connection back into the pool. # # @api private def release(site, http) expiration = Time.now + @keepalive_timeout session = Puppet::Network::HTTP::Session.new(http, expiration) Puppet.debug("Caching connection for #{site}") sessions = @pool[site] if sessions sessions.unshift(session) else @pool[site] = [session] end end # Returns an Array of sessions whose connections are not expired. # # @api private def active_sessions(site) now = Time.now sessions = @pool[site] || [] sessions.select do |session| if session.expired?(now) close_connection(site, session.connection) false else true end end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/http/request.rb����������������������������������������������������0000644�0052762�0001160�00000005330�13417161721�021563� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Network::HTTP::Request = Struct.new(:headers, :params, :method, :path, :routing_path, :client_cert, :body) do def self.from_hash(hash) symbol_members = members.collect(&:intern) unknown = hash.keys - symbol_members if unknown.empty? new(hash[:headers] || {}, hash[:params] || {}, hash[:method] || "GET", hash[:path], hash[:routing_path] || hash[:path], hash[:client_cert], hash[:body]) else raise ArgumentError, _("Unknown arguments: %{args}") % { args: unknown.collect(&:inspect).join(', ') } end end def route_into(prefix) self.class.new(headers, params, method, path, routing_path.sub(prefix, ''), client_cert, body) end def formatter if header = headers['content-type'] header.gsub!(/\s*;.*$/,'') # strip any charset format = Puppet::Network::FormatHandler.mime(header) return format if valid_network_format?(format) #TRANSLATORS "mime-type" is a keyword and should not be translated raise Puppet::Network::HTTP::Error::HTTPUnsupportedMediaTypeError.new( _("Client sent a mime-type (%{header}) that doesn't correspond to a format we support") % { header: headers['content-type'] }, Puppet::Network::HTTP::Issues::UNSUPPORTED_MEDIA_TYPE) end raise Puppet::Network::HTTP::Error::HTTPBadRequestError.new( _("No Content-Type header was received, it isn't possible to unserialize the request"), Puppet::Network::HTTP::Issues::MISSING_HEADER_FIELD) end def response_formatters_for(supported_formats, default_accepted_formats = nil) accepted_formats = headers['accept'] || default_accepted_formats if accepted_formats.nil? raise Puppet::Network::HTTP::Error::HTTPBadRequestError.new(_("Missing required Accept header"), Puppet::Network::HTTP::Issues::MISSING_HEADER_FIELD) end formats = Puppet::Network::FormatHandler.most_suitable_formats_for( accepted_formats.split(/\s*,\s*/), supported_formats) formats.find_all do |format| # we are only passed supported_formats that are suitable # and whose klass implements the required_methods valid_network_format?(format) end return formats unless formats.empty? raise Puppet::Network::HTTP::Error::HTTPNotAcceptableError.new( _("No supported formats are acceptable (Accept: %{accepted_formats})") % { accepted_formats: accepted_formats }, Puppet::Network::HTTP::Issues::UNSUPPORTED_FORMAT) end private def valid_network_format?(format) # YAML in network requests is not supported. See http://links.puppet.com/deprecate_yaml_on_network format != nil && format.name != :yaml && format.name != :b64_zlib_yaml end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/http/response.rb���������������������������������������������������0000644�0052762�0001160�00000001056�13417161721�021732� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Network::HTTP::Response def initialize(handler, response) @handler = handler @response = response end def respond_with(code, type, body) format = Puppet::Network::FormatHandler.format_for(type) mime = format.mime charset = format.charset if charset if body.is_a?(String) && body.encoding != charset body.encode!(charset) end mime += "; charset=#{charset.name.downcase}" end @handler.set_content_type(@response, mime) @handler.set_response(@response, body, code) end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/http/route.rb������������������������������������������������������0000644�0052762�0001160�00000004215�13417161721�021232� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Network::HTTP::Route MethodNotAllowedHandler = lambda do |req, res| raise Puppet::Network::HTTP::Error::HTTPMethodNotAllowedError.new("method #{req.method} not allowed for route #{req.path}", Puppet::Network::HTTP::Issues::UNSUPPORTED_METHOD) end NO_HANDLERS = [MethodNotAllowedHandler] attr_reader :path_matcher def self.path(path_matcher) new(path_matcher) end def initialize(path_matcher) @path_matcher = path_matcher @method_handlers = { :GET => NO_HANDLERS, :HEAD => NO_HANDLERS, :OPTIONS => NO_HANDLERS, :POST => NO_HANDLERS, :PUT => NO_HANDLERS, :DELETE => NO_HANDLERS } @chained = [] end def get(*handlers) @method_handlers[:GET] = handlers return self end def head(*handlers) @method_handlers[:HEAD] = handlers return self end def options(*handlers) @method_handlers[:OPTIONS] = handlers return self end def post(*handlers) @method_handlers[:POST] = handlers return self end def put(*handlers) @method_handlers[:PUT] = handlers return self end def delete(*handlers) @method_handlers[:DELETE] = handlers return self end def any(*handlers) @method_handlers.each do |method, registered_handlers| @method_handlers[method] = handlers end return self end def chain(*routes) @chained = routes self end def matches?(request) Puppet.debug("Evaluating match for #{self.inspect}") if match(request.routing_path) return true else Puppet.debug("Did not match path (#{request.routing_path.inspect})") end return false end def process(request, response) handlers = @method_handlers[request.method.upcase.intern] || NO_HANDLERS handlers.each do |handler| handler.call(request, response) end subrequest = request.route_into(match(request.routing_path).to_s) if chained_route = @chained.find { |route| route.matches?(subrequest) } chained_route.process(subrequest, response) end end def inspect "Route #{@path_matcher.inspect}" end private def match(path) @path_matcher.match(path) end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/http/session.rb����������������������������������������������������0000644�0052762�0001160�00000000562�13417161721�021560� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# An HTTP session that references a persistent HTTP connection and # an expiration time for the connection. # # @api private # class Puppet::Network::HTTP::Session attr_reader :connection def initialize(connection, expiration_time) @connection = connection @expiration_time = expiration_time end def expired?(now) @expiration_time <= now end end ����������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/http/site.rb�������������������������������������������������������0000644�0052762�0001160�00000001423�13417161721�021036� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Represents a site to which HTTP connections are made. It is a value # object, and is suitable for use in a hash. If two sites are equal, # then a persistent connection made to the first site, can be re-used # for the second. # # @api private # class Puppet::Network::HTTP::Site attr_reader :scheme, :host, :port def initialize(scheme, host, port) @scheme = scheme @host = host @port = port.to_i end def addr "#{@scheme}://#{@host}:#{@port.to_s}" end alias to_s addr def ==(rhs) (@scheme == rhs.scheme) && (@host == rhs.host) && (@port == rhs.port) end alias eql? == def hash [@scheme, @host, @port].hash end def use_ssl? @scheme == 'https' end def move_to(uri) self.class.new(uri.scheme, uri.host, uri.port) end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/http/api.rb��������������������������������������������������������0000644�0052762�0001160�00000004411�13417161721�020643� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Network::HTTP::API require 'puppet/version' def self.not_found Puppet::Network::HTTP::Route. path(/.*/). any(lambda do |req, res| raise Puppet::Network::HTTP::Error::HTTPNotFoundError.new("No route for #{req.method} #{req.path}", Puppet::Network::HTTP::Issues::HANDLER_NOT_FOUND) end) end def self.not_found_upgrade Puppet::Network::HTTP::Route. path(/.*/). any(lambda do |req, res| raise Puppet::Network::HTTP::Error::HTTPNotFoundError.new("Error: Invalid URL - Puppet expects requests that conform to the " + "/puppet and /puppet-ca APIs.\n\n" + "Note that Puppet 3 agents aren't compatible with this version; if you're " + "running Puppet 3, you must either upgrade your agents to match the server " + "or point them to a server running Puppet 3.\n\n" + "Master Info:\n" + " Puppet version: #{Puppet.version}\n" + " Supported /puppet API versions: #{Puppet::Network::HTTP::MASTER_URL_VERSIONS}\n" + " Supported /puppet-ca API versions: #{Puppet::Network::HTTP::CA_URL_VERSIONS}", Puppet::Network::HTTP::Issues::HANDLER_NOT_FOUND) end) end def self.master_routes master_prefix = Regexp.new("^#{Puppet::Network::HTTP::MASTER_URL_PREFIX}/") Puppet::Network::HTTP::Route.path(master_prefix). any. chain(Puppet::Network::HTTP::API::Master::V3.routes, Puppet::Network::HTTP::API.not_found) end def self.ca_routes ca_prefix = Regexp.new("^#{Puppet::Network::HTTP::CA_URL_PREFIX}/") Puppet::Network::HTTP::Route.path(ca_prefix). any. chain(Puppet::Network::HTTP::API::CA::V1.routes, Puppet::Network::HTTP::API.not_found) end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/http/connection.rb�������������������������������������������������0000644�0052762�0001160�00000030215�13417161721�022232� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'net/https' require 'puppet/ssl/host' require 'puppet/ssl/configuration' require 'puppet/ssl/validator' require 'puppet/network/http' require 'uri' require 'date' require 'time' module Puppet::Network::HTTP # This will be raised if too many redirects happen for a given HTTP request class RedirectionLimitExceededException < Puppet::Error ; end # This class provides simple methods for issuing various types of HTTP # requests. It's interface is intended to mirror Ruby's Net::HTTP # object, but it provides a few important bits of additional # functionality. Notably: # # * Any HTTPS requests made using this class will use Puppet's SSL # certificate configuration for their authentication, and # * Provides some useful error handling for any SSL errors that occur # during a request. # @api public class Connection OPTION_DEFAULTS = { :use_ssl => true, :verify => nil, :redirect_limit => 10, } # Creates a new HTTP client connection to `host`:`port`. # @param host [String] the host to which this client will connect to # @param port [Integer] the port to which this client will connect to # @param options [Hash] options influencing the properties of the created # connection, # @option options [Boolean] :use_ssl true to connect with SSL, false # otherwise, defaults to true # @option options [#setup_connection] :verify An object that will configure # any verification to do on the connection # @option options [Integer] :redirect_limit the number of allowed # redirections, defaults to 10 passing any other option in the options # hash results in a Puppet::Error exception # # @note the HTTP connection itself happens lazily only when {#request}, or # one of the {#get}, {#post}, {#delete}, {#head} or {#put} is called # @note The correct way to obtain a connection is to use one of the factory # methods on {Puppet::Network::HttpPool} # @api private def initialize(host, port, options = {}) @host = host @port = port unknown_options = options.keys - OPTION_DEFAULTS.keys raise Puppet::Error, _("Unrecognized option(s): %{opts}") % { opts: unknown_options.map(&:inspect).sort.join(', ') } unless unknown_options.empty? options = OPTION_DEFAULTS.merge(options) @use_ssl = options[:use_ssl] @verify = options[:verify] @redirect_limit = options[:redirect_limit] @site = Puppet::Network::HTTP::Site.new(@use_ssl ? 'https' : 'http', host, port) @pool = Puppet.lookup(:http_pool) end # @!macro [new] common_options # @param options [Hash] options influencing the request made. Any # options not recognized by this class will be ignored - no error will # be thrown. # @option options [Hash{Symbol => String}] :basic_auth The basic auth # :username and :password to use for the request, :metric_id Ignored # by this class - used by Puppet Server only. The metric id by which # to track metrics on requests. # @param path [String] # @param headers [Hash{String => String}] # @!macro common_options # @api public def get(path, headers = {}, options = {}) do_request(Net::HTTP::Get.new(path, headers), options) end # @param path [String] # @param data [String] # @param headers [Hash{String => String}] # @!macro common_options # @api public def post(path, data, headers = nil, options = {}) request = Net::HTTP::Post.new(path, headers) request.body = data do_request(request, options) end # @param path [String] # @param headers [Hash{String => String}] # @!macro common_options # @api public def head(path, headers = {}, options = {}) do_request(Net::HTTP::Head.new(path, headers), options) end # @param path [String] # @param headers [Hash{String => String}] # @!macro common_options # @api public def delete(path, headers = {'Depth' => 'Infinity'}, options = {}) do_request(Net::HTTP::Delete.new(path, headers), options) end # @param path [String] # @param data [String] # @param headers [Hash{String => String}] # @!macro common_options # @api public def put(path, data, headers = nil, options = {}) request = Net::HTTP::Put.new(path, headers) request.body = data do_request(request, options) end def request(method, *args) self.send(method, *args) end # TODO: These are proxies for the Net::HTTP#request_* methods, which are # almost the same as the "get", "post", etc. methods that we've ported above, # but they are able to accept a code block and will yield to it, which is # necessary to stream responses, e.g. file content. For now # we're not funneling these proxy implementations through our #request # method above, so they will not inherit the same error handling. In the # future we may want to refactor these so that they are funneled through # that method and do inherit the error handling. def request_get(*args, &block) with_connection(@site) do |connection| connection.request_get(*args, &block) end end def request_head(*args, &block) with_connection(@site) do |connection| connection.request_head(*args, &block) end end def request_post(*args, &block) with_connection(@site) do |connection| connection.request_post(*args, &block) end end # end of Net::HTTP#request_* proxies # The address to connect to. def address @site.host end # The port to connect to. def port @site.port end # Whether to use ssl def use_ssl? @site.use_ssl? end private def do_request(request, options) current_request = request current_site = @site response = nil 0.upto(@redirect_limit) do |redirection| return response if response with_connection(current_site) do |connection| apply_options_to(current_request, options) current_response = execute_request(connection, current_request) case current_response.code.to_i when 301, 302, 307 # handle redirection location = URI.parse(current_response['location']) current_site = current_site.move_to(location) # update to the current request path current_request = current_request.class.new(location.path) current_request.body = request.body request.each do |header, value| current_request[header] = value end when 429, 503 response = handle_retry_after(current_response) else response = current_response end end # and try again... end raise RedirectionLimitExceededException, _("Too many HTTP redirections for %{host}:%{port}") % { host: @host, port: @port } end # Handles the Retry-After header of a HTTPResponse # # This method checks the response for a Retry-After header and handles # it by sleeping for the indicated number of seconds. The response is # returned unmodified if no Retry-After header is present. # # @param response [Net::HTTPResponse] A response received from the # HTTP client. # # @return [nil] Sleeps and returns nil if the response contained a # Retry-After header that indicated the request should be retried. # @return [Net::HTTPResponse] Returns the `response` unmodified if # no Retry-After header was present or the Retry-After header could # not be parsed as an integer or RFC 2822 date. def handle_retry_after(response) retry_after = response['Retry-After'] return response if retry_after.nil? retry_sleep = parse_retry_after_header(retry_after) # Recover remote hostname if Net::HTTPResponse was generated by a # method that fills in the uri attribute. # # TODO: Drop the respond_to? check when support for Ruby 1.9.3 is dropped. server_hostname = if response.respond_to?(:uri) && response.uri.is_a?(URI) response.uri.host else # TRANSLATORS: Used in the phrase: # "Received a response from the remote server." _('the remote server') end if retry_sleep.nil? Puppet.err(_('Received a %{status_code} response from %{server_hostname}, but the Retry-After header value of "%{retry_after}" could not be converted to an integer or RFC 2822 date.') % {status_code: response.code, server_hostname: server_hostname, retry_after: retry_after.inspect}) return response end # Cap maximum sleep at the run interval of the Puppet agent. retry_sleep = [retry_sleep, Puppet[:runinterval]].min Puppet.warning(_('Received a %{status_code} response from %{server_hostname}. Sleeping for %{retry_sleep} seconds before retrying the request.') % {status_code: response.code, server_hostname: server_hostname, retry_sleep: retry_sleep}) ::Kernel.sleep(retry_sleep) return nil end # Parse the value of a Retry-After header # # Parses a string containing an Integer or RFC 2822 datestamp and returns # an integer number of seconds before a request can be retried. # # @param header_value [String] The value of the Retry-After header. # # @return [Integer] Number of seconds to wait before retrying the # request. Will be equal to 0 for the case of date that has already # passed. # @return [nil] Returns `nil` when the `header_value` can't be # parsed as an Integer or RFC 2822 date. def parse_retry_after_header(header_value) retry_after = begin Integer(header_value) rescue TypeError, ArgumentError begin DateTime.rfc2822(header_value) rescue ArgumentError return nil end end case retry_after when Integer retry_after when DateTime sleep = (retry_after.to_time - DateTime.now.to_time).to_i (sleep > 0) ? sleep : 0 end end def apply_options_to(request, options) request["User-Agent"] = Puppet[:http_user_agent] if options[:basic_auth] request.basic_auth(options[:basic_auth][:user], options[:basic_auth][:password]) end end def execute_request(connection, request) start = Time.now connection.request(request) rescue EOFError => e elapsed = (Time.now - start).to_f.round(3) uri = @site.addr + request.path.split('?')[0] eof = EOFError.new(_('request %{uri} interrupted after %{elapsed} seconds') % {uri: uri, elapsed: elapsed}) eof.set_backtrace(e.backtrace) unless e.backtrace.empty? raise eof end def with_connection(site, &block) response = nil @pool.with_connection(site, @verify) do |conn| response = yield conn end response rescue OpenSSL::SSL::SSLError => error # can be nil peer_cert = @verify.peer_certs.last if error.message.include? "certificate verify failed" msg = error.message msg << ": [" + @verify.verify_errors.join('; ') + "]" raise Puppet::Error, msg, error.backtrace elsif peer_cert && !OpenSSL::SSL.verify_certificate_identity(peer_cert.content, site.host) valid_certnames = [peer_cert.name, *peer_cert.subject_alt_names].uniq if valid_certnames.size > 1 expected_certnames = _("expected one of %{certnames}") % { certnames: valid_certnames.join(', ') } else expected_certnames = _("expected %{certname}") % { certname: valid_certnames.first } end msg = _("Server hostname '%{host}' did not match server certificate; %{expected_certnames}") % { host: site.host, expected_certnames: expected_certnames } raise Puppet::Error, msg, error.backtrace else raise end end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/http/factory.rb����������������������������������������������������0000644�0052762�0001160�00000003110�13417161721�021534� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'openssl' require 'net/http' require 'puppet/util/http_proxy' # Factory for <tt>Net::HTTP</tt> objects. # # Encapsulates the logic for creating a <tt>Net::HTTP</tt> object based on the # specified {Puppet::Network::HTTP::Site Site} and puppet settings. # # @api private # class Puppet::Network::HTTP::Factory @@openssl_initialized = false KEEP_ALIVE_TIMEOUT = 2**31 - 1 def initialize # PUP-1411, make sure that openssl is initialized before we try to connect if ! @@openssl_initialized OpenSSL::SSL::SSLContext.new @@openssl_initialized = true end end def create_connection(site) Puppet.debug("Creating new connection for #{site}") args = [site.host, site.port] unless Puppet::Util::HttpProxy.no_proxy?(site) if Puppet[:http_proxy_host] == "none" args << nil << nil else args << Puppet[:http_proxy_host] << Puppet[:http_proxy_port] end end http = Net::HTTP.new(*args) http.use_ssl = site.use_ssl? http.read_timeout = Puppet[:http_read_timeout] http.open_timeout = Puppet[:http_connect_timeout] http.keep_alive_timeout = KEEP_ALIVE_TIMEOUT if http.respond_to?(:keep_alive_timeout=) if Puppet[:sourceaddress] if http.respond_to?(:local_host) Puppet.debug("Using source IP #{Puppet[:sourceaddress]}") http.local_host = Puppet[:sourceaddress] else raise ArgumentError, "Setting 'sourceaddress' is unsupported by this version of Net::HTTP." end end if Puppet[:http_debug] http.set_debug_output($stderr) end http end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/http/handler.rb����������������������������������������������������0000644�0052762�0001160�00000012316�13417161721�021512� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Network::HTTP end require 'puppet/network/http' require 'puppet/network/rights' require 'puppet/util/profiler' require 'puppet/util/profiler/aggregate' require 'resolv' module Puppet::Network::HTTP::Handler include Puppet::Network::HTTP::Issues # These shouldn't be allowed to be set by clients # in the query string, for security reasons. DISALLOWED_KEYS = ["node", "ip"] def register(routes) # There's got to be a simpler way to do this, right? dupes = {} routes.each { |r| dupes[r.path_matcher] = (dupes[r.path_matcher] || 0) + 1 } dupes = dupes.collect { |pm, count| pm if count > 1 }.compact if dupes.count > 0 raise ArgumentError, _("Given multiple routes with identical path regexes: %{regexes}") % { regexes: dupes.map{ |rgx| rgx.inspect }.join(', ') } end @routes = routes Puppet.debug("Routes Registered:") @routes.each do |route| Puppet.debug(route.inspect) end end # Retrieve all headers from the http request, as a hash with the header names # (lower-cased) as the keys def headers(request) raise NotImplementedError end # The mime type is always passed to the `set_content_type` method, so # it is no longer necessary to retrieve the Format's mime type. # # @deprecated def format_to_mime(format) format.is_a?(Puppet::Network::Format) ? format.mime : format end # handle an HTTP request def process(request, response) new_response = Puppet::Network::HTTP::Response.new(self, response) request_headers = headers(request) request_params = params(request) request_method = http_method(request) request_path = path(request) new_request = Puppet::Network::HTTP::Request.new(request_headers, request_params, request_method, request_path, request_path, client_cert(request), body(request)) response[Puppet::Network::HTTP::HEADER_PUPPET_VERSION] = Puppet.version profiler = configure_profiler(request_headers, request_params) Puppet::Util::Profiler.profile(_("Processed request %{request_method} %{request_path}") % { request_method: request_method, request_path: request_path }, [:http, request_method, request_path]) do if route = @routes.find { |r| r.matches?(new_request) } route.process(new_request, new_response) else raise Puppet::Network::HTTP::Error::HTTPNotFoundError.new(_("No route for %{request} %{path}") % { request: new_request.method, path: new_request.path }, HANDLER_NOT_FOUND) end end rescue Puppet::Network::HTTP::Error::HTTPError => e Puppet.info(e.message) new_response.respond_with(e.status, "application/json", e.to_json) rescue StandardError => e http_e = Puppet::Network::HTTP::Error::HTTPServerError.new(e) log_msg = [http_e.message, *e.backtrace].join("\n") Puppet.err(log_msg) new_response.respond_with(http_e.status, "application/json", http_e.to_json) ensure if profiler remove_profiler(profiler) end cleanup(request) end # Set the response up, with the body and status. def set_response(response, body, status = 200) raise NotImplementedError end # Set the specified format as the content type of the response. def set_content_type(response, format) raise NotImplementedError end # resolve node name from peer's ip address # this is used when the request is unauthenticated def resolve_node(result) begin return Resolv.getname(result[:ip]) rescue => detail Puppet.err _("Could not resolve %{ip}: %{detail}") % { ip: result[:ip], detail: detail } end result[:ip] end private # methods to be overridden by the including web server class def http_method(request) raise NotImplementedError end def path(request) raise NotImplementedError end def request_key(request) raise NotImplementedError end def body(request) raise NotImplementedError end def params(request) raise NotImplementedError end def client_cert(request) raise NotImplementedError end def cleanup(request) # By default, there is nothing to cleanup. end def decode_params(params) params.select { |key, _| allowed_parameter?(key) }.inject({}) do |result, ary| param, value = ary result[param.to_sym] = parse_parameter_value(param, value) result end end def allowed_parameter?(name) not (name.nil? || name.empty? || DISALLOWED_KEYS.include?(name)) end def parse_parameter_value(param, value) if value.is_a?(Array) value.collect { |v| parse_primitive_parameter_value(v) } else parse_primitive_parameter_value(value) end end def parse_primitive_parameter_value(value) case value when "true" true when "false" false when /^\d+$/ Integer(value) when /^\d+\.\d+$/ value.to_f else value end end def configure_profiler(request_headers, request_params) if (request_headers.has_key?(Puppet::Network::HTTP::HEADER_ENABLE_PROFILING.downcase) or Puppet[:profile]) Puppet::Util::Profiler.add_profiler(Puppet::Util::Profiler::Aggregate.new(Puppet.method(:info), request_params.object_id)) end end def remove_profiler(profiler) profiler.shutdown Puppet::Util::Profiler.remove_profiler(profiler) end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/http/rack.rb�������������������������������������������������������0000644�0052762�0001160�00000002267�13417161721�021021� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'rack' require 'rack/request' require 'rack/response' require 'puppet/network/http' require 'puppet/network/http/rack/rest' # An rack application, for running the Puppet HTTP Server. class Puppet::Network::HTTP::Rack # The real rack application (which needs to respond to call). # The work we need to do, roughly is: # * Read request (from env) and prepare a response # * Route the request to the correct handler # * Return the response (in rack-format) to our caller. def call(env) request = Rack::Request.new(env) response = Rack::Response.new Puppet.debug 'Handling request: %s %s' % [request.request_method, request.fullpath] begin Puppet::Network::HTTP::RackREST.new.process(request, response) rescue => detail # Send a Status 500 Error on unhandled exceptions. response.status = 500 response['Content-Type'] = 'text/plain' response.write _("Internal Server Error: \"%{message}\"") % { message: detail.message } # log what happened Puppet.log_exception(detail, _("Puppet Server (Rack): Internal Server Error: Unhandled Exception: \"%{message}\"") % { message: detail.message }) end response.finish end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/http/rack/���������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020472� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/http/rack/rest.rb��������������������������������������������������0000644�0052762�0001160�00000011364�13417161721�021774� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'openssl' require 'cgi' require 'puppet/network/http/handler' require 'puppet/util/ssl' require 'uri' class Puppet::Network::HTTP::RackREST include Puppet::Network::HTTP::Handler ContentType = 'Content-Type'.freeze CHUNK_SIZE = 8192 class RackFile def initialize(file) @file = file end def each while chunk = @file.read(CHUNK_SIZE) yield chunk end end def close @file.close end end def initialize(args={}) super() register([Puppet::Network::HTTP::API.master_routes, Puppet::Network::HTTP::API.ca_routes, Puppet::Network::HTTP::API.not_found_upgrade]) end def set_content_type(response, format) response[ContentType] = format end # produce the body of the response def set_response(response, result, status = 200) response.status = status unless result.is_a?(File) response.write result else response["Content-Length"] = result.stat.size.to_s response.body = RackFile.new(result) end end # Retrieve all headers from the http request, as a map. def headers(request) headers = request.env.select {|k,v| k.start_with? 'HTTP_'}.inject({}) do |m, (k,v)| m[k.sub(/^HTTP_/, '').gsub('_','-').downcase] = v m end headers['content-type'] = request.content_type headers end # Return which HTTP verb was used in this request. def http_method(request) request.request_method end # Return the query params for this request. def params(request) if request.post? params = request.params else # rack doesn't support multi-valued query parameters, # e.g. ignore, so parse them ourselves params = CGI.parse(request.query_string) convert_singular_arrays_to_value(params) end result = decode_params(params) result.merge(extract_client_info(request)) end # what path was requested? (this is, without any query parameters) def path(request) # The value that Passenger provides for 'path' is escaped # (URL percent-encoded), see # https://github.com/phusion/passenger/blob/release-5.0.26/src/apache2_module/Hooks.cpp#L885 # for the implementation as hooked up to an Apache web server. Code # in the indirector / HTTP layer which consumes this path, however, assumes # that it has already been unescaped, so it is unescaped here. if request.path # don't use CGI.unescape which mangles space handling URI.unescape(request.path.encode(Encoding::UTF_8)) end end # return the request body def body(request) request.body.read end def client_cert(request) # This environment variable is set by mod_ssl, note that it # requires the `+ExportCertData` option in the `SSLOptions` directive cert = request.env['SSL_CLIENT_CERT'] # NOTE: The SSL_CLIENT_CERT environment variable will be the empty string # when Puppet agent nodes have not yet obtained a signed certificate. if cert.nil? || cert.empty? # When running with unicorn, the SSL_CLIENT_CERT variable is not available # in the environment, therefore we have to pass a header: 'X-SSL-Client-Cert' cert = request.env['HTTP_X_SSL_CLIENT_CERT'] if cert.nil? || cert.empty? nil else # in contrast to the environment variable, the client cert is passed in # as single string, therefore restore the certificate to a valid pem # encoded certificate cert.gsub!(/ /, "\n") cert.gsub!(/BEGIN\nCERT/, "BEGIN CERT") cert.gsub!(/END\nCERT/, "END CERT") cert = Puppet::SSL::Certificate.from_instance(OpenSSL::X509::Certificate.new(cert)) cert end else Puppet::SSL::Certificate.from_instance(OpenSSL::X509::Certificate.new(cert)) end end # Passenger freaks out if we finish handling the request without reading any # part of the body, so make sure we have. def cleanup(request) request.body.read(1) nil end def extract_client_info(request) result = {} result[:ip] = request.ip # if we find SSL info in the headers, use them to get a hostname from the CN. # try this with :ssl_client_header, which defaults should work for # Apache with StdEnvVars. subj_str = request.env[Puppet[:ssl_client_header]] subject = Puppet::Util::SSL.subject_from_dn(subj_str || "") if cn = Puppet::Util::SSL.cn_from_subject(subject) result[:node] = cn result[:authenticated] = (request.env[Puppet[:ssl_client_verify_header]] == 'SUCCESS') else result[:node] = resolve_node(result) result[:authenticated] = false end result end def convert_singular_arrays_to_value(hash) hash.each do |key, value| if value.size == 1 hash[key] = value.first end end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/http/webrick.rb����������������������������������������������������0000644�0052762�0001160�00000007201�13417161721�021520� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'webrick' require 'webrick/https' require 'puppet/network/http/webrick/rest' require 'thread' require 'puppet/ssl/certificate' require 'puppet/ssl/certificate_revocation_list' require 'puppet/ssl/configuration' class Puppet::Network::HTTP::WEBrick CIPHERS = "EDH+CAMELLIA:EDH+aRSA:EECDH+aRSA+AESGCM:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH:+CAMELLIA256:+AES256:+CAMELLIA128:+AES128:+SSLv3:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!DSS:!RC4:!SEED:!IDEA:!ECDSA:kEDH:CAMELLIA256-SHA:AES256-SHA:CAMELLIA128-SHA:AES128-SHA" def initialize @listening = false end def listen(address, port) @server = create_server(address, port) @server.mount('/', Puppet::Network::HTTP::WEBrickREST) raise _("WEBrick server is already listening") if @listening @listening = true @thread = Thread.new do @server.start do |sock| timeout = 10.0 if ! IO.select([sock],nil,nil,timeout) raise _("Client did not send data within %{timeout} seconds of connecting") % { timeout: ("%.1f") % timeout } end sock.accept @server.run(sock) end end sleep 0.1 until @server.status == :Running end def unlisten raise _("WEBrick server is not listening") unless @listening @server.shutdown wait_for_shutdown @server = nil @listening = false end def listening? @listening end def wait_for_shutdown @thread.join end # @api private def create_server(address, port) address = nil if address == '*' arguments = {:BindAddress => address, :Port => port, :DoNotReverseLookup => true} arguments.merge!(setup_logger) arguments.merge!(setup_ssl) BasicSocket.do_not_reverse_lookup = true server = WEBrick::HTTPServer.new(arguments) server.ssl_context.ciphers = CIPHERS server end # Configure our http log file. def setup_logger # Make sure the settings are all ready for us. Puppet.settings.use(:main, :ssl, :application) file = Puppet[:masterhttplog] # open the log manually to prevent file descriptor leak # webrick logged strings may contain UTF-8 file_io = ::File.open(file, "a+:UTF-8") file_io.sync = true if defined?(Fcntl::FD_CLOEXEC) file_io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) end args = [file_io] args << WEBrick::Log::DEBUG if Puppet::Util::Log.level == :debug logger = WEBrick::Log.new(*args) return :Logger => logger, :AccessLog => [ [logger, WEBrick::AccessLog::COMMON_LOG_FORMAT ], [logger, WEBrick::AccessLog::REFERER_LOG_FORMAT ] ] end # Add all of the ssl cert information. def setup_ssl results = {} # Get the cached copy. We know it's been generated, too. host = Puppet::SSL::Host.localhost raise Puppet::Error, _("Could not retrieve certificate for %{host} and not running on a valid certificate authority") % { value0: host.name } unless host.certificate results[:SSLPrivateKey] = host.key.content results[:SSLCertificate] = host.certificate.content results[:SSLStartImmediately] = false results[:SSLEnable] = true results[:SSLOptions] = OpenSSL::SSL::OP_NO_SSLv2 | OpenSSL::SSL::OP_NO_SSLv3 raise Puppet::Error, _("Could not find CA certificate") unless Puppet::SSL::Certificate.indirection.find(Puppet::SSL::CA_NAME) results[:SSLCACertificateFile] = ssl_configuration.ca_auth_file results[:SSLVerifyClient] = OpenSSL::SSL::VERIFY_PEER results[:SSLCertificateStore] = host.ssl_store results end private def ssl_configuration @ssl_configuration ||= Puppet::SSL::Configuration.new( Puppet[:localcacert], :ca_auth_file => Puppet[:ssl_server_ca_auth]) end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/http/webrick/������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�021200� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/http/webrick/rest.rb�����������������������������������������������0000644�0052762�0001160�00000005674�13417161721�022511� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/network/http/handler' require 'resolv' require 'webrick' require 'webrick/httputils' require 'puppet/util/ssl' class Puppet::Network::HTTP::WEBrickREST < WEBrick::HTTPServlet::AbstractServlet include Puppet::Network::HTTP::Handler def self.mutex @mutex ||= Mutex.new end def initialize(server) raise ArgumentError, _("server is required") unless server register([Puppet::Network::HTTP::API.master_routes, Puppet::Network::HTTP::API.ca_routes, Puppet::Network::HTTP::API.not_found_upgrade]) super(server) end # Retrieve the request parameters, including authentication information. def params(request) query = request.query || {} params = if request.request_method == "PUT" # webrick doesn't look at the query string for PUT requests, it only # looks at the body, and then only if the body has a content type that # looks like url-encoded form data. We need the query string data as well. WEBrick::HTTPUtils.parse_query(request.query_string).merge(query) else query end params = Hash[params.collect do |key, value| all_values = value.list [key, all_values.length == 1 ? value : all_values] end] params = decode_params(params) params.merge(client_information(request)) end # WEBrick uses a service method to respond to requests. Simply delegate to # the handler response method. def service(request, response) self.class.mutex.synchronize do process(request, response) end end def headers(request) result = {} request.each do |k, v| result[k.downcase] = v end result end def http_method(request) request.request_method end def path(request) request.path end def body(request) request.body end def client_cert(request) if cert = request.client_cert Puppet::SSL::Certificate.from_instance(cert) else nil end end # Set the specified format as the content type of the response. def set_content_type(response, format) response["content-type"] = format end def set_response(response, result, status = 200) response.status = status if status >= 200 and status != 304 response.body = result response["content-length"] = result.stat.size if result.is_a?(File) end end # Retrieve node/cert/ip information from the request object. def client_information(request) result = {} if peer = request.peeraddr and ip = peer[3] result[:ip] = ip end # If they have a certificate (which will almost always be true) # then we get the hostname from the cert, instead of via IP # info result[:authenticated] = false if cert = request.client_cert and cn = Puppet::Util::SSL.cn_from_subject(cert.subject) result[:node] = cn result[:authenticated] = true else result[:node] = resolve_node(result) end result end end ��������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/http_pool.rb�������������������������������������������������������0000644�0052762�0001160�00000004170�13417161721�021125� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/network/http/connection' module Puppet::Network; end # This module contains the factory methods that should be used for getting a # {Puppet::Network::HTTP::Connection} instance. The pool may return a new # connection or a persistent cached connection, depending on the underlying # pool implementation in use. # # @api public # module Puppet::Network::HttpPool @http_client_class = Puppet::Network::HTTP::Connection def self.http_client_class @http_client_class end def self.http_client_class=(klass) @http_client_class = klass end # Retrieve a connection for the given host and port. # # @param host [String] The hostname to connect to # @param port [Integer] The port on the host to connect to # @param use_ssl [Boolean] Whether to use an SSL connection # @param verify_peer [Boolean] Whether to verify the peer credentials, if possible. Verification will not take place if the CA certificate is missing. # @return [Puppet::Network::HTTP::Connection] # # @api public # def self.http_instance(host, port, use_ssl = true, verify_peer = true) verifier = if verify_peer Puppet::SSL::Validator.default_validator() else Puppet::SSL::Validator.no_validator() end http_client_class.new(host, port, :use_ssl => use_ssl, :verify => verifier) end # Get an http connection that will be secured with SSL and have the # connection verified with the given verifier # # @param host [String] the DNS name to connect to # @param port [Integer] the port to connect to # @param verifier [#setup_connection, #peer_certs, #verify_errors] An object that will setup the appropriate # verification on a Net::HTTP instance and report any errors and the certificates used. # @return [Puppet::Network::HTTP::Connection] # # @api public # def self.http_ssl_instance(host, port, verifier = Puppet::SSL::Validator.default_validator()) http_client_class.new(host, port, :use_ssl => true, :verify => verifier) end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/rest_controller.rb�������������������������������������������������0000644�0052762�0001160�00000000064�13417161721�022333� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Network::RESTController # :nodoc: end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/rights.rb����������������������������������������������������������0000644�0052762�0001160�00000013270�13417161721�020416� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/network/authstore' require 'puppet/error' module Puppet::Network # this exception is thrown when a request is not authenticated class AuthorizationError < Puppet::Error; end # Rights class manages a list of ACLs for paths. class Rights # Check that name is allowed or not def allowed?(name, *args) !is_forbidden_and_why?(name, :node => args[0], :ip => args[1]) end def is_request_forbidden_and_why?(method, path, params) methods_to_check = if method == :head # :head is ok if either :find or :save is ok. [:find, :save] else [method] end authorization_failure_exceptions = methods_to_check.map do |m| is_forbidden_and_why?(path, params.merge({:method => m})) end if authorization_failure_exceptions.include? nil # One of the methods we checked is ok, therefore this request is ok. nil else # Just need to return any of the failure exceptions. authorization_failure_exceptions.first end end def is_forbidden_and_why?(name, args = {}) res = :nomatch @rights.find do |acl| found = false # an acl can return :dunno, which means "I'm not qualified to answer your question, # please ask someone else". This is used when for instance an acl matches, but not for the # current rest method, where we might think some other acl might be more specific. if match = acl.match?(name) args[:match] = match if (res = acl.allowed?(args[:node], args[:ip], args)) != :dunno # return early if we're allowed return nil if res # we matched, select this acl found = true end end found end # if we end up here, then that means we either didn't match or failed, in any # case will return an error to the outside world msg = "#{name} [#{args[:method]}]" AuthorizationError.new(_("Forbidden request: %{msg}") % { msg: msg }) end def initialize @rights = [] end def [](name) @rights.find { |acl| acl == name } end def empty? @rights.empty? end def include?(name) @rights.include?(name) end def each @rights.each { |r| yield r.name,r } end # Define a new right to which access can be provided. def newright(name, line=nil, file=nil) add_right( Right.new(name, line, file) ) end private def add_right(right) @rights << right right end # Retrieve a right by name. def right(name) self[name] end # A right. class Right < Puppet::Network::AuthStore attr_accessor :name, :key # Overriding Object#methods sucks for debugging. If we're in here in the # future, it would be nice to rename Right#methods attr_accessor :methods, :environment, :authentication attr_accessor :line, :file ALL = [:save, :destroy, :find, :search] Puppet::Util.logmethods(self, true) def initialize(name, line, file) @methods = [] @environment = [] @authentication = true # defaults to authenticated @name = name @line = line || 0 @file = file @methods = ALL case name when /^\// @key = Regexp.new("^" + Regexp.escape(name)) when /^~/ # this is a regex @name = name.gsub(/^~\s+/,'') @key = Regexp.new(@name) else raise ArgumentError, _("Unknown right type '%{name}'") % { name: name } end super() end def to_s "access[#{@name}]" end # There's no real check to do at this point def valid? true end # does this right is allowed for this triplet? # if this right is too restrictive (ie we don't match this access method) # then return :dunno so that upper layers have a chance to try another right # tailored to the given method def allowed?(name, ip, args = {}) if not @methods.include?(args[:method]) return :dunno elsif @environment.size > 0 and not @environment.include?(args[:environment]) return :dunno elsif (@authentication and not args[:authenticated]) return :dunno end begin # make sure any capture are replaced if needed interpolate(args[:match]) if args[:match] res = super(name,ip) ensure reset_interpolation end res end # restrict this right to some method only def restrict_method(m) m = m.intern if m.is_a?(String) raise ArgumentError, _("'%{m}' is not an allowed value for method directive") % { m: m } unless ALL.include?(m) # if we were allowing all methods, then starts from scratch if @methods === ALL @methods = [] end raise ArgumentError, _("'%{m}' is already in the '%{name}' ACL") % { m: m, name: name } if @methods.include?(m) @methods << m end def restrict_environment(environment) env = Puppet.lookup(:environments).get(environment) raise ArgumentError, _("'%{env}' is already in the '%{name}' ACL") % { env: env, name: name } if @environment.include?(env) @environment << env end def restrict_authenticated(authentication) case authentication when "yes", "on", "true", true authentication = true when "no", "off", "false", false, "all" ,"any", :all, :any authentication = false else raise ArgumentError, _("'%{name}' incorrect authenticated value: %{authentication}") % { name: name, authentication: authentication } end @authentication = authentication end def match?(key) # otherwise match with the regex self.key.match(key) end def ==(name) self.name == name.gsub(/^~\s+/,'') end end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/authconfig.rb������������������������������������������������������0000644�0052762�0001160�00000007601�13417161721�021246� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/network/rights' require 'puppet/network/http' module Puppet class ConfigurationError < Puppet::Error; end class Network::DefaultAuthProvider attr_accessor :rights def self.master_url_prefix Puppet::Network::HTTP::MASTER_URL_PREFIX end def self.ca_url_prefix Puppet::Network::HTTP::CA_URL_PREFIX end def self.default_acl [ # Master API V3 { :acl => "#{master_url_prefix}/v3/environments", :method => :find, :allow => '*', :authenticated => true }, { :acl => "~ ^#{master_url_prefix}\/v3\/catalog\/([^\/]+)$", :method => :find, :allow => '$1', :authenticated => true }, { :acl => "~ ^#{master_url_prefix}\/v3\/node\/([^\/]+)$", :method => :find, :allow => '$1', :authenticated => true }, { :acl => "~ ^#{master_url_prefix}\/v3\/report\/([^\/]+)$", :method => :save, :allow => '$1', :authenticated => true }, # this one will allow all file access, and thus delegate # to fileserver.conf { :acl => "#{master_url_prefix}/v3/file" }, { :acl => "#{master_url_prefix}/v3/status", :method => [:find], :authenticated => true }, # CA API V1 { :acl => "#{ca_url_prefix}/v1/certificate_revocation_list/ca", :method => :find, :authenticated => true }, # These allow `auth any`, because if you can do them anonymously you # should probably also be able to do them when trusted. { :acl => "#{ca_url_prefix}/v1/certificate/ca", :method => :find, :authenticated => :any }, { :acl => "#{ca_url_prefix}/v1/certificate/", :method => :find, :authenticated => :any }, { :acl => "#{ca_url_prefix}/v1/certificate_request", :method => [:find, :save], :authenticated => :any }, ] end # Just proxy the setting methods to our rights stuff [:allow, :deny].each do |method| define_method(method) do |*args| @rights.send(method, *args) end end # force regular ACLs to be present def insert_default_acl self.class.default_acl.each do |acl| unless rights[acl[:acl]] Puppet.info _("Inserting default '%{acl}' (auth %{auth}) ACL") % { acl: acl[:acl], auth: acl[:authenticated] } mk_acl(acl) end end # queue an empty (ie deny all) right for every other path # actually this is not strictly necessary as the rights system # denies not explicitly allowed paths unless rights["/"] rights.newright("/").restrict_authenticated(:any) end end def mk_acl(acl) right = @rights.newright(acl[:acl]) right.allow(acl[:allow] || "*") if method = acl[:method] method = [method] unless method.is_a?(Array) method.each { |m| right.restrict_method(m) } end right.restrict_authenticated(acl[:authenticated]) unless acl[:authenticated].nil? end # check whether this request is allowed in our ACL # raise an Puppet::Network::AuthorizedError if the request # is denied. def check_authorization(method, path, params) if authorization_failure_exception = @rights.is_request_forbidden_and_why?(method, path, params) Puppet.warning(_("Denying access: %{authorization_failure_exception}") % { authorization_failure_exception: authorization_failure_exception }) raise authorization_failure_exception end end def initialize(rights=nil) @rights = rights || Puppet::Network::Rights.new insert_default_acl end end class Network::AuthConfig @@authprovider_class = nil def self.authprovider_class=(klass) @@authprovider_class = klass end def self.authprovider_class @@authprovider_class || Puppet::Network::DefaultAuthProvider end def initialize(rights=nil) @authprovider = self.class.authprovider_class.new(rights) end def check_authorization(method, path, params) @authprovider.check_authorization(method, path, params) end end end �������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/format_support.rb��������������������������������������������������0000644�0052762�0001160�00000010104�13417161721�022173� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/network/format_handler' # Provides network serialization support when included # @api public module Puppet::Network::FormatSupport def self.included(klass) klass.extend(ClassMethods) end module ClassMethods def convert_from(format, data) get_format(format).intern(self, data) rescue => err #TRANSLATORS "intern" is a function name and should not be translated raise Puppet::Network::FormatHandler::FormatError, _("Could not intern from %{format}: %{err}") % { format: format, err: err }, err.backtrace end def convert_from_multiple(format, data) get_format(format).intern_multiple(self, data) rescue => err #TRANSLATORS "intern_multiple" is a function name and should not be translated raise Puppet::Network::FormatHandler::FormatError, _("Could not intern_multiple from %{format}: %{err}") % { format: format, err: err }, err.backtrace end def render_multiple(format, instances) get_format(format).render_multiple(instances) rescue => err #TRANSLATORS "render_multiple" is a function name and should not be translated raise Puppet::Network::FormatHandler::FormatError, _("Could not render_multiple to %{format}: %{err}") % { format: format, err: err }, err.backtrace end def default_format supported_formats[0] end def support_format?(name) Puppet::Network::FormatHandler.format(name).supported?(self) end def supported_formats result = format_handler.formats.collect do |f| format_handler.format(f) end.find_all do |f| f.supported?(self) end.sort do |a, b| # It's an inverse sort -- higher weight formats go first. b.weight <=> a.weight end.collect do |f| f.name end result = put_preferred_format_first(result) Puppet.debug "#{friendly_name} supports formats: #{result.join(' ')}" result end # @api private def get_format(format_name) format_handler.format_for(format_name) end private def format_handler Puppet::Network::FormatHandler end def friendly_name if self.respond_to? :indirection indirection.name else self end end def put_preferred_format_first(list) preferred_format = Puppet.settings[:preferred_serialization_format].to_sym if list.include?(preferred_format) list.delete(preferred_format) list.unshift(preferred_format) else Puppet.debug "Value of 'preferred_serialization_format' (#{preferred_format}) is invalid for #{friendly_name}, using default (#{list.first})" end list end end def to_msgpack(*args) to_data_hash.to_msgpack(*args) end # @deprecated, use to_json def to_pson(*args) to_data_hash.to_pson(*args) end def to_json(*args) Puppet::Util::Json.dump(to_data_hash, *args) end def render(format = nil) format ||= self.class.default_format self.class.get_format(format).render(self) rescue => err #TRANSLATORS "render" is a function name and should not be translated raise Puppet::Network::FormatHandler::FormatError, _("Could not render to %{format}: %{err}") % { format: format, err: err }, err.backtrace end def mime(format = nil) format ||= self.class.default_format self.class.get_format(format).mime rescue => err #TRANSLATORS "mime" is a function name and should not be translated raise Puppet::Network::FormatHandler::FormatError, _("Could not mime to %{format}: %{err}") % { format: format, err: err }, err.backtrace end def support_format?(name) self.class.support_format?(name) end # @comment Document to_data_hash here as it is called as a hook from to_msgpack if it exists # @!method to_data_hash(*args) # @api public # @abstract # This method may be implemented to return a hash object that is used for serializing. # The object returned by this method should contain all the info needed to instantiate it again. # If the method exists it will be called from to_msgpack and other serialization methods. # @return [Hash] end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/formats.rb���������������������������������������������������������0000644�0052762�0001160�00000011712�13417161721�020570� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/network/format_handler' require 'puppet/util/json' Puppet::Network::FormatHandler.create_serialized_formats(:msgpack, :weight => 20, :mime => "application/x-msgpack", :required_methods => [:render_method, :intern_method], :intern_method => :from_data_hash) do confine :feature => :msgpack def intern(klass, text) data = MessagePack.unpack(text) return data if data.is_a?(klass) klass.from_data_hash(data) end def intern_multiple(klass, text) MessagePack.unpack(text).collect do |data| klass.from_data_hash(data) end end def render_multiple(instances) instances.to_msgpack end end Puppet::Network::FormatHandler.create_serialized_formats(:yaml) do def intern(klass, text) data = YAML.load(text) data_to_instance(klass, data) end def intern_multiple(klass, text) data = YAML.load(text) unless data.respond_to?(:collect) raise Puppet::Network::FormatHandler::FormatError, _("Serialized YAML did not contain a collection of instances when calling intern_multiple") end data.collect do |datum| data_to_instance(klass, datum) end end def data_to_instance(klass, data) return data if data.is_a?(klass) unless data.is_a? Hash raise Puppet::Network::FormatHandler::FormatError, _("Serialized YAML did not contain a valid instance of %{klass}") % { klass: klass } end klass.from_data_hash(data) end def render(instance) instance.to_yaml end # Yaml monkey-patches Array, so this works. def render_multiple(instances) instances.to_yaml end def supported?(klass) true end end Puppet::Network::FormatHandler.create(:s, :mime => "text/plain", :charset => Encoding::UTF_8, :extension => "txt") # By default, to_binary is called to render and from_binary called to intern. Note unlike # text-based formats (json, yaml, etc), we don't use to_data_hash for binary. Puppet::Network::FormatHandler.create(:binary, :mime => "application/octet-stream", :weight => 1, :required_methods => [:render_method, :intern_method]) do end # PSON is deprecated Puppet::Network::FormatHandler.create_serialized_formats(:pson, :weight => 10, :required_methods => [:render_method, :intern_method], :intern_method => :from_data_hash) do def intern(klass, text) data_to_instance(klass, PSON.parse(text)) end def intern_multiple(klass, text) PSON.parse(text).collect do |data| data_to_instance(klass, data) end end # PSON monkey-patches Array, so this works. def render_multiple(instances) instances.to_pson end # If they pass class information, we want to ignore it. # This is required for compatibility with Puppet 3.x def data_to_instance(klass, data) if data.is_a?(Hash) and d = data['data'] data = d end return data if data.is_a?(klass) klass.from_data_hash(data) end end Puppet::Network::FormatHandler.create_serialized_formats(:json, :mime => 'application/json', :charset => Encoding::UTF_8, :weight => 15, :required_methods => [:render_method, :intern_method], :intern_method => :from_data_hash) do def intern(klass, text) data_to_instance(klass, Puppet::Util::Json.load(text)) end def intern_multiple(klass, text) Puppet::Util::Json.load(text).collect do |data| data_to_instance(klass, data) end end def render_multiple(instances) Puppet::Util::Json.dump(instances) end # Unlike PSON, we do not need to unwrap the data envelope, because legacy 3.x agents # have never supported JSON def data_to_instance(klass, data) return data if data.is_a?(klass) klass.from_data_hash(data) end end # This is really only ever going to be used for Catalogs. Puppet::Network::FormatHandler.create_serialized_formats(:dot, :required_methods => [:render_method]) Puppet::Network::FormatHandler.create(:console, :mime => 'text/x-console-text', :weight => 0) do def json @json ||= Puppet::Network::FormatHandler.format(:json) end def render(datum) return datum if datum.is_a?(String) || datum.is_a?(Numeric) # Simple hash to table if datum.is_a?(Hash) && datum.keys.all? { |x| x.is_a?(String) || x.is_a?(Numeric) } output = '' column_a = datum.empty? ? 2 : datum.map{ |k,v| k.to_s.length }.max + 2 datum.sort_by { |k,v| k.to_s } .each do |key, value| output << key.to_s.ljust(column_a) output << json.render(value). chomp.gsub(/\n */) { |x| x + (' ' * column_a) } output << "\n" end return output end # Print one item per line for arrays if datum.is_a? Array output = '' datum.each do |item| output << item.to_s output << "\n" end return output end # ...or pretty-print the inspect outcome. Puppet::Util::Json.dump(datum, :pretty => true, :quirks_mode => true) end def render_multiple(data) data.collect(&:render).join("\n") end end ������������������������������������������������������puppet-5.5.10/lib/puppet/network/http.rb������������������������������������������������������������0000644�0052762�0001160�00000001775�13417161721�020104� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Network::HTTP HEADER_ENABLE_PROFILING = "X-Puppet-Profiling" HEADER_PUPPET_VERSION = "X-Puppet-Version" MASTER_URL_PREFIX = "/puppet" MASTER_URL_VERSIONS = "v3" CA_URL_PREFIX = "/puppet-ca" CA_URL_VERSIONS = "v1" require 'puppet/network/authorization' require 'puppet/network/http/issues' require 'puppet/network/http/error' require 'puppet/network/http/route' require 'puppet/network/http/api' require 'puppet/network/http/api/ca' require 'puppet/network/http/api/ca/v1' require 'puppet/network/http/api/master' require 'puppet/network/http/api/master/v3' require 'puppet/network/http/handler' require 'puppet/network/http/response' require 'puppet/network/http/request' require 'puppet/network/http/site' require 'puppet/network/http/session' require 'puppet/network/http/factory' require 'puppet/network/http/nocache_pool' require 'puppet/network/http/pool' require 'puppet/network/http/memory_response' require 'puppet/network/http/compression' end ���puppet-5.5.10/lib/puppet/network/resolver.rb��������������������������������������������������������0000644�0052762�0001160�00000005516�13417161721�020763� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'resolv' module Puppet::Network; end module Puppet::Network::Resolver # Iterate through the list of servers that service this hostname # and yield each server/port since SRV records have ports in them # It will override whatever masterport setting is already set. def self.each_srv_record(domain, service_name = :puppet, &block) if (domain.nil? or domain.empty?) Puppet.debug "Domain not known; skipping SRV lookup" return end Puppet.debug "Searching for SRV records for domain: #{domain}" case service_name when :puppet then service = '_x-puppet' when :ca then service = '_x-puppet-ca' when :report then service = '_x-puppet-report' when :file then service = '_x-puppet-fileserver' else service = "_x-puppet-#{service_name.to_s}" end srv_record = "#{service}._tcp.#{domain}" resolver = Resolv::DNS.new records = resolver.getresources(srv_record, Resolv::DNS::Resource::IN::SRV) Puppet.debug "Found #{records.size} SRV records for: #{srv_record}" if records.size == 0 && service_name != :puppet # Try the generic :puppet service if no SRV records were found # for the specific service. each_srv_record(domain, :puppet, &block) else each_priority(records) do |priority, recs| while next_rr = recs.delete(find_weighted_server(recs)) Puppet.debug "Yielding next server of #{next_rr.target.to_s}:#{next_rr.port}" yield next_rr.target.to_s, next_rr.port end end end end def self.each_priority(records) pri_hash = records.inject({}) do |groups, element| groups[element.priority] ||= [] groups[element.priority] << element groups end pri_hash.keys.sort.each do |key| yield key, pri_hash[key] end end private_class_method :each_priority def self.find_weighted_server(records) return nil if records.nil? || records.empty? return records.first if records.size == 1 # Calculate the sum of all weights in the list of resource records, # This is used to then select hosts until the weight exceeds what # random number we selected. For example, if we have weights of 1 8 and 3: # # |-|---|--------| # ^ # We generate a random number 5, and iterate through the records, adding # the current record's weight to the accumulator until the weight of the # current record plus previous records is greater than the random number. total_weight = records.inject(0) { |sum,record| sum + weight(record) } current_weight = 0 chosen_weight = 1 + Kernel.rand(total_weight) records.each do |record| current_weight += weight(record) return record if current_weight >= chosen_weight end end def self.weight(record) record.weight == 0 ? 1 : record.weight * 10 end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/network/server.rb����������������������������������������������������������0000644�0052762�0001160�00000001465�13417161721�020427� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/network/http' require 'puppet/network/http/webrick' # # @api private class Puppet::Network::Server attr_reader :address, :port def initialize(address, port) @port = port @address = address @http_server = Puppet::Network::HTTP::WEBrick.new @listening = false # Make sure we have all of the directories we need to function. Puppet.settings.use(:main, :ssl, :application) end def listening? @listening end def start raise _("Cannot listen -- already listening.") if listening? @listening = true @http_server.listen(address, port) end def stop raise _("Cannot unlisten -- not currently listening.") unless listening? @http_server.unlisten @listening = false end def wait_for_shutdown @http_server.wait_for_shutdown end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/node/����������������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�016027� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/node/environment.rb��������������������������������������������������������0000644�0052762�0001160�00000046532�13417161721�020725� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util' require 'monitor' require 'puppet/parser/parser_factory' # Just define it, so this class has fewer load dependencies. class Puppet::Node end # Puppet::Node::Environment acts as a container for all configuration # that is expected to vary between environments. # # ## The root environment # # In addition to normal environments that are defined by the user,there is a # special 'root' environment. It is defined as an instance variable on the # Puppet::Node::Environment metaclass. The environment name is `*root*` and can # be accessed by looking up the `:root_environment` using {Puppet.lookup}. # # The primary purpose of the root environment is to contain parser functions # that are not bound to a specific environment. The main case for this is for # logging functions. Logging functions are attached to the 'root' environment # when {Puppet::Parser::Functions.reset} is called. class Puppet::Node::Environment NO_MANIFEST = :no_manifest # The create() factory method should be used instead. # # @api private def self.new(*args) create(*args) end private_class_method :new # Create a new environment with the given name # # @param name [Symbol] the name of the environment # @param modulepath [Array<String>] the list of paths from which to load modules # @param manifest [String] the path to the manifest for the environment or # the constant Puppet::Node::Environment::NO_MANIFEST if there is none. # @param config_version [String] path to a script whose output will be added # to report logs (optional) # @return [Puppet::Node::Environment] # # @api public def self.create(name, modulepath, manifest = NO_MANIFEST, config_version = nil) obj = self.allocate obj.send(:initialize, name.intern, expand_dirs(extralibs() + modulepath), manifest == NO_MANIFEST ? manifest : Puppet::FileSystem.expand_path(manifest), config_version) obj end # A remote subclass to make it easier to trace instances when debugging. # @api private class Remote < Puppet::Node::Environment; end # A "reference" to a remote environment. The created environment instance # isn't expected to exist on the local system, but is instead a reference to # environment information on a remote system. For instance when a catalog is # being applied, this will be used on the agent. # # @note This does not provide access to the information of the remote # environment's modules, manifest, or anything else. It is simply a value # object to pass around and use as an environment. # # @param name [Symbol] The name of the remote environment # def self.remote(name) Remote.create(name, [], NO_MANIFEST) end # Instantiate a new environment # # @note {Puppet::Node::Environment.new} is private for historical reasons, as # previously it had been overridden to return memoized objects and was # replaced with {Puppet::Node::Environment.create}, so this will not be # invoked with the normal Ruby initialization semantics. # # @param name [Symbol] The environment name def initialize(name, modulepath, manifest, config_version) @name = name @modulepath = modulepath @manifest = manifest @config_version = config_version @modules_strict_semver = false end # Creates a new Puppet::Node::Environment instance, overriding any of the passed # parameters. # # @param env_params [Hash<{Symbol => String,Array<String>}>] new environment # parameters (:modulepath, :manifest, :config_version) # @return [Puppet::Node::Environment] def override_with(env_params) return self.class.create(name, env_params[:modulepath] || modulepath, env_params[:manifest] || manifest, env_params[:config_version] || config_version) end # Creates a new Puppet::Node::Environment instance, overriding :manifest, # :modulepath, or :config_version from the passed settings if they were # originally set from the commandline, or returns self if there is nothing to # override. # # @param settings [Puppet::Settings] an initialized puppet settings instance # @return [Puppet::Node::Environment] new overridden environment or self if # there are no commandline changes from settings. def override_from_commandline(settings) overrides = {} if settings.set_by_cli?(:modulepath) overrides[:modulepath] = self.class.split_path(settings.value(:modulepath)) end if settings.set_by_cli?(:config_version) overrides[:config_version] = settings.value(:config_version) end if settings.set_by_cli?(:manifest) overrides[:manifest] = settings.value(:manifest) end overrides.empty? ? self : self.override_with(overrides) end # @param [String] name Environment name to check for valid syntax. # @return [Boolean] true if name is valid # @api public def self.valid_name?(name) !!name.match(/\A\w+\Z/) end # @!attribute [r] name # @api public # @return [Symbol] the human readable environment name that serves as the # environment identifier attr_reader :name # @api public # @return [Array<String>] All directories present on disk in the modulepath def modulepath @modulepath.find_all do |p| Puppet::FileSystem.directory?(p) end end # @api public # @return [Array<String>] All directories in the modulepath (even if they are not present on disk) def full_modulepath @modulepath end # @!attribute [r] manifest # @api public # @return [String] path to the manifest file or directory. attr_reader :manifest # @!attribute [r] config_version # @api public # @return [String] path to a script whose output will be added to report logs # (optional) attr_reader :config_version # Cached loaders - management of value handled by Puppet::Pops::Loaders # @api private attr_accessor :loaders # Checks to make sure that this environment did not have a manifest set in # its original environment.conf if Puppet is configured with # +disable_per_environment_manifest+ set true. If it did, the environment's # modules may not function as intended by the original authors, and we may # seek to halt a puppet compilation for a node in this environment. # # The only exception to this would be if the environment.conf manifest is an exact, # uninterpolated match for the current +default_manifest+ setting. # # @return [Boolean] true if using directory environments, and # Puppet[:disable_per_environment_manifest] is true, and this environment's # original environment.conf had a manifest setting that is not the # Puppet[:default_manifest]. # @api private def conflicting_manifest_settings? return false if !Puppet[:disable_per_environment_manifest] original_manifest = configuration.raw_setting(:manifest) !original_manifest.nil? && !original_manifest.empty? && original_manifest != Puppet[:default_manifest] end # @api private def static_catalogs? if @static_catalogs.nil? environment_conf = Puppet.lookup(:environments).get_conf(name) @static_catalogs = (environment_conf.nil? ? Puppet[:static_catalogs] : environment_conf.static_catalogs) end @static_catalogs end # Return the environment configuration # @return [Puppet::Settings::EnvironmentConf] The configuration # # @api private def configuration Puppet.lookup(:environments).get_conf(name) end # Checks the environment and settings for any conflicts # @return [Array<String>] an array of validation errors # @api public def validation_errors errors = [] if conflicting_manifest_settings? errors << _("The 'disable_per_environment_manifest' setting is true, and the '%{env_name}' environment has an environment.conf manifest that conflicts with the 'default_manifest' setting.") % { env_name: name } end errors end # Checks if this environment permits use of rich data types in the catalog # @return [Boolean] `true` if rich data is permitted. # @api private def rich_data? if @rich_data.nil? environment_conf = Puppet.lookup(:environments).get_conf(name) @rich_data = (environment_conf.nil? ? Puppet[:rich_data] : environment_conf.rich_data) end @rich_data end # Return an environment-specific Puppet setting. # # @api public # # @param param [String, Symbol] The environment setting to look up # @return [Object] The resolved setting value def [](param) Puppet.settings.value(param, self.name) end # A SemanticPuppet::VersionRange version >= 1.0.0 will not include versions with pre-release # identifiers unless that is explicitly declared. This may cause backward compatibility # issues when resolving module dependencies and the flag is therefore set to `false` by default. # # @param flag [Boolean] set to true to resolve module dependencies using strict SemVer semantics # def modules_strict_semver=(flag) @modules_strict_semver = flag end # @return [Boolean] the current value of the modules_strict_semver flag. # @api public def modules_strict_semver? @modules_strict_semver end # @api public # @return [Puppet::Resource::TypeCollection] The current global TypeCollection def known_resource_types if @known_resource_types.nil? @known_resource_types = Puppet::Resource::TypeCollection.new(self) @known_resource_types.import_ast(perform_initial_import(), '') end @known_resource_types end # Yields each modules' plugin directory if the plugin directory (modulename/lib) # is present on the filesystem. # # @yield [String] Yields the plugin directory from each module to the block. # @api public def each_plugin_directory(&block) modules.map(&:plugin_directory).each do |lib| lib = Puppet::Util::Autoload.cleanpath(lib) yield lib if File.directory?(lib) end end # Locate a module instance by the module name alone. # # @api public # # @param name [String] The module name # @return [Puppet::Module, nil] The module if found, else nil def module(name) modules.find {|mod| mod.name == name} end # Locate a module instance by the full forge name (EG authorname/module) # # @api public # # @param forge_name [String] The module name # @return [Puppet::Module, nil] The module if found, else nil def module_by_forge_name(forge_name) _, modname = forge_name.split('/') found_mod = self.module(modname) found_mod and found_mod.forge_name == forge_name ? found_mod : nil end # Return all modules for this environment in the order they appear in the # modulepath. # @note If multiple modules with the same name are present they will # both be added, but methods like {#module} and {#module_by_forge_name} # will return the first matching entry in this list. # @note This value is cached so that the filesystem doesn't have to be # re-enumerated every time this method is invoked, since that # enumeration could be a costly operation and this method is called # frequently. The cache expiry is determined by `Puppet[:filetimeout]`. # @api public # @return [Array<Puppet::Module>] All modules for this environment def modules if @modules.nil? module_references = [] seen_modules = {} modulepath.each do |path| Dir.entries(path).each do |name| next unless Puppet::Module.is_module_directory?(name, path) warn_about_mistaken_path(path, name) if not seen_modules[name] module_references << {:name => name, :path => File.join(path, name)} seen_modules[name] = true end end end @modules = module_references.collect do |reference| begin Puppet::Module.new(reference[:name], reference[:path], self, modules_strict_semver?) rescue Puppet::Module::Error => e Puppet.log_exception(e) nil end end.compact end @modules end # Generate a warning if the given directory in a module path entry is named `lib`. # # @api private # # @param path [String] The module directory containing the given directory # @param name [String] The directory name def warn_about_mistaken_path(path, name) if name == "lib" Puppet.debug("Warning: Found directory named 'lib' in module path ('#{path}/lib'); unless " + "you are expecting to load a module named 'lib', your module path may be set " + "incorrectly.") end end # Modules broken out by directory in the modulepath # # @note This method _changes_ the current working directory while enumerating # the modules. This seems rather dangerous. # # @api public # # @return [Hash<String, Array<Puppet::Module>>] A hash whose keys are file # paths, and whose values is an array of Puppet Modules for that path def modules_by_path modules_by_path = {} modulepath.each do |path| if Puppet::FileSystem.exist?(path) Dir.chdir(path) do module_names = Dir.entries(path).select do |name| Puppet::Module.is_module_directory?(name, path) end modules_by_path[path] = module_names.sort.map do |name| Puppet::Module.new(name, File.join(path, name), self, modules_strict_semver?) end end else modules_by_path[path] = [] end end modules_by_path end # All module requirements for all modules in the environment modulepath # # @api public # # @comment This has nothing to do with an environment. It seems like it was # stuffed into the first convenient class that vaguely involved modules. # # @example # environment.module_requirements # # => { # # 'username/amodule' => [ # # { # # 'name' => 'username/moduledep', # # 'version' => '1.2.3', # # 'version_requirement' => '>= 1.0.0', # # }, # # { # # 'name' => 'username/anotherdep', # # 'version' => '4.5.6', # # 'version_requirement' => '>= 3.0.0', # # } # # ] # # } # # # # @return [Hash<String, Array<Hash<String, String>>>] See the method example # for an explanation of the return value. def module_requirements deps = {} modules.each do |mod| next unless mod.forge_name deps[mod.forge_name] ||= [] mod.dependencies and mod.dependencies.each do |mod_dep| dep_name = mod_dep['name'].tr('-', '/') (deps[dep_name] ||= []) << { 'name' => mod.forge_name, 'version' => mod.version, 'version_requirement' => mod_dep['version_requirement'] } end end deps.each do |mod, mod_deps| deps[mod] = mod_deps.sort_by { |d| d['name'] } end deps end # Loads module translations for the current environment once for # the lifetime of the environment. Execute a block in the context # of that translation domain. def with_text_domain return yield if Puppet[:disable_i18n] if @text_domain.nil? @text_domain = @name Puppet::GettextConfig.reset_text_domain(@text_domain) Puppet::ModuleTranslations.load_from_modulepath(modules) else Puppet::GettextConfig.use_text_domain(@text_domain) end yield ensure # Is a noop if disable_i18n is true Puppet::GettextConfig.clear_text_domain end # Checks if a reparse is required (cache of files is stale). # def check_for_reparse if (Puppet[:code] != @parsed_code || @known_resource_types.parse_failed?) @parsed_code = nil @known_resource_types = nil end end # @return [String] The YAML interpretation of the object # Return the name of the environment as a string interpretation of the object def to_yaml to_s.to_yaml end # @return [String] The stringified value of the `name` instance variable # @api public def to_s name.to_s end # @api public def inspect %Q{<#{self.class}:#{self.object_id} @name="#{name}" @manifest="#{manifest}" @modulepath="#{full_modulepath.join(":")}" >} end # @return [Symbol] The `name` value, cast to a string, then cast to a symbol. # # @api public # # @note the `name` instance variable is a Symbol, but this casts the value # to a String and then converts it back into a Symbol which will needlessly # create an object that needs to be garbage collected def to_sym to_s.to_sym end def self.split_path(path_string) path_string.split(File::PATH_SEPARATOR) end def ==(other) return true if other.kind_of?(Puppet::Node::Environment) && self.name == other.name && self.full_modulepath == other.full_modulepath && self.manifest == other.manifest end alias eql? == def hash [self.class, name, full_modulepath, manifest].hash end private def self.extralibs() if Puppet::Util.get_env('PUPPETLIB') split_path(Puppet::Util.get_env('PUPPETLIB')) else [] end end def self.expand_dirs(dirs) dirs.collect do |dir| Puppet::FileSystem.expand_path(dir) end end # Reparse the manifests for the given environment # # There are two sources that can be used for the initial parse: # # 1. The value of `Puppet[:code]`: Puppet can take a string from # its settings and parse that as a manifest. This is used by various # Puppet applications to read in a manifest and pass it to the # environment as a side effect. This is attempted first. # 2. The contents of this environment's +manifest+ attribute: Puppet will # try to load the environment manifest. # # @return [Puppet::Parser::AST::Hostclass] The AST hostclass object # representing the 'main' hostclass def perform_initial_import parser = Puppet::Parser::ParserFactory.parser @parsed_code = Puppet[:code] if @parsed_code != "" parser.string = @parsed_code parser.parse else file = self.manifest # if the manifest file is a reference to a directory, parse and combine # all .pp files in that directory if file == NO_MANIFEST empty_parse_result elsif File.directory?(file) parse_results = Puppet::FileSystem::PathPattern.absolute(File.join(file, '**/*.pp')).glob.sort.map do | file_to_parse | parser.file = file_to_parse parser.parse end # Use a parser type specific merger to concatenate the results Puppet::Parser::AST::Hostclass.new('', :code => Puppet::Parser::ParserFactory.code_merger.concatenate(parse_results)) else parser.file = file parser.parse end end rescue Puppet::ParseErrorWithIssue => detail @known_resource_types.parse_failed = true detail.environment = self.name raise rescue => detail @known_resource_types.parse_failed = true msg = _("Could not parse for environment %{env}: %{detail}") % { env: self, detail: detail } error = Puppet::Error.new(msg) error.set_backtrace(detail.backtrace) raise error end # Return an empty top-level hostclass to indicate that no file was loaded # # @return [Puppet::Parser::AST::Hostclass] def empty_parse_result return Puppet::Parser::AST::Hostclass.new('') end # A None subclass to make it easier to trace the NONE environment when debugging. # @api private class None < Puppet::Node::Environment; end # A special "null" environment # # This environment should be used when there is no specific environment in # effect. NONE = None.create(:none, []) end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/node/facts.rb��������������������������������������������������������������0000644�0052762�0001160�00000006161�13417161721�017453� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'time' require 'puppet/node' require 'puppet/indirector' require 'puppet/util/psych_support' # Manage a given node's facts. This either accepts facts and stores them, or # returns facts for a given node. class Puppet::Node::Facts include Puppet::Util::PsychSupport # Set up indirection, so that nodes can be looked for in # the node sources. extend Puppet::Indirector # We want to expire any cached nodes if the facts are saved. module NodeExpirer def save(instance, key = nil, options={}) Puppet::Node.indirection.expire(instance.name, options) super end end indirects :facts, :terminus_setting => :facts_terminus, :extend => NodeExpirer attr_accessor :name, :values, :timestamp def add_local_facts values["clientcert"] = Puppet.settings[:certname] values["clientversion"] = Puppet.version.to_s values["clientnoop"] = Puppet.settings[:noop] end def initialize(name, values = {}) @name = name @values = values add_timestamp end def initialize_from_hash(data) @name = data['name'] @values = data['values'] # Timestamp will be here in YAML, e.g. when reading old reports timestamp = @values.delete('_timestamp') # Timestamp will be here in JSON timestamp ||= data['timestamp'] if timestamp.is_a? String @timestamp = Time.parse(timestamp) else @timestamp = timestamp end self.expiration = data['expiration'] if expiration.is_a? String self.expiration = Time.parse(expiration) end end # Add extra values, such as facts given to lookup on the command line. The # extra values will override existing values. # @param extra_values [Hash{String=>Object}] the values to add # @api private def add_extra_values(extra_values) @values.merge!(extra_values) nil end # Sanitize fact values by converting everything not a string, Boolean # numeric, array or hash into strings. def sanitize values.each do |fact, value| values[fact] = sanitize_fact value end end def ==(other) return false unless self.name == other.name values == other.values end def self.from_data_hash(data) new_facts = allocate new_facts.initialize_from_hash(data) new_facts end def to_data_hash result = { 'name' => name, 'values' => values } if @timestamp if @timestamp.is_a? Time result['timestamp'] = @timestamp.iso8601(9) else result['timestamp'] = @timestamp end end if expiration if expiration.is_a? Time result['expiration'] = expiration.iso8601(9) else result['expiration'] = expiration end end result end def add_timestamp @timestamp = Time.now end private def sanitize_fact(fact) if fact.is_a? Hash then ret = {} fact.each_pair { |k,v| ret[sanitize_fact k]=sanitize_fact v } ret elsif fact.is_a? Array then fact.collect { |i| sanitize_fact i } elsif fact.is_a? Numeric \ or fact.is_a? TrueClass \ or fact.is_a? FalseClass \ or fact.is_a? String fact else fact.to_s end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parameter/�����������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�017062� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parameter/boolean.rb�������������������������������������������������������0000644�0052762�0001160�00000000607�13417161721�021024� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/coercion' # This specialized {Puppet::Parameter} handles Boolean options, accepting lots # of strings and symbols for both truth and falsehood. # class Puppet::Parameter::Boolean < Puppet::Parameter def unsafe_munge(value) Puppet::Coercion.boolean(value) end def self.initvars super @value_collection.newvalues(*Puppet::Coercion.boolean_values) end end �������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parameter/package_options.rb�����������������������������������������������0000644�0052762�0001160�00000001465�13417161721�022556� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parameter' # This specialized {Puppet::Parameter} handles munging of package options. # Package options are passed as an array of key value pairs. Special munging is # required as the keys and values needs to be quoted in a safe way. # class Puppet::Parameter::PackageOptions < Puppet::Parameter def unsafe_munge(values) values = [values] unless values.is_a? Array values.collect do |val| case val when Hash safe_hash = {} val.each_pair do |k, v| safe_hash[quote(k)] = quote(v) end safe_hash when String quote(val) else fail(_("Expected either a string or hash of options")) end end end # @api private def quote(value) value.include?(' ') ? %Q["#{value.gsub(/"/, '\"')}"] : value end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parameter/path.rb����������������������������������������������������������0000644�0052762�0001160�00000004234�13417161721�020341� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parameter' # This specialized {Puppet::Parameter} handles validation and munging of paths. # By default, a single path is accepted, and by calling {accept_arrays} it is possible to # allow an array of paths. # class Puppet::Parameter::Path < Puppet::Parameter # Specifies whether multiple paths are accepted or not. # @dsl type # def self.accept_arrays(bool = true) @accept_arrays = !!bool end def self.arrays? @accept_arrays end # Performs validation of the given paths. # If the concrete parameter defines a validation method, it may call this method to perform # path validation. # @raise [Puppet::Error] if this property is configured for single paths and an array is given # @raise [Puppet::Error] if a path is not an absolute path # @return [Array<String>] the given paths # def validate_path(paths) if paths.is_a?(Array) and ! self.class.arrays? then fail _("%{name} only accepts a single path, not an array of paths") % { name: name } end fail(_("%{name} must be a fully qualified path") % { name: name }) unless Array(paths).all? {|path| absolute_path?(path)} paths end # This is the default implementation of the `validate` method. # It will be overridden if the validate option is used when defining the parameter. # @return [void] # def unsafe_validate(paths) validate_path(paths) end # This is the default implementation of `munge`. # If the concrete parameter defines a `munge` method, this default implementation will be overridden. # This default implementation does not perform any munging, it just checks the one/many paths # constraints. A derived implementation can perform this check as: # `paths.is_a?(Array) and ! self.class.arrays?` and raise a {Puppet::Error}. # @param paths [String, Array<String>] one of multiple paths # @return [String, Array<String>] the given paths # @raise [Puppet::Error] if the given paths does not comply with the on/many paths rule. def unsafe_munge(paths) if paths.is_a?(Array) and ! self.class.arrays? then fail _("%{name} only accepts a single path, not an array of paths") % { name: name } end paths end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parameter/value.rb���������������������������������������������������������0000644�0052762�0001160�00000004511�13417161721�020517� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parameter/value_collection' # Describes an acceptable value for a parameter or property. # An acceptable value is either specified as a literal value or a regular expression. # @note this class should be used via the api methods in {Puppet::Parameter} and {Puppet::Property} # @api private # class Puppet::Parameter::Value attr_reader :name, :options, :event attr_accessor :block, :method, :required_features, :invalidate_refreshes # Adds an alias for this value. # Makes the given _name_ be an alias for this acceptable value. # @param name [Symbol] the additional alias this value should be known as # @api private # def alias(name) @aliases << convert(name) end # @return [Array<Symbol>] Returns all aliases (or an empty array). # @api private # def aliases @aliases.dup end # Stores the event that our value generates, if it does so. # @api private # def event=(value) @event = convert(value) end # Initializes the instance with a literal accepted value, or a regular expression. # If anything else is passed, it is turned into a String, and then made into a Symbol. # @param name [Symbol, Regexp, Object] the value to accept, Symbol, a regular expression, or object to convert. # @api private # def initialize(name) if name.is_a?(Regexp) @name = name else # Convert to a string and then a symbol, so things like true/false # still show up as symbols. @name = convert(name) end @aliases = [] end # Checks if the given value matches the acceptance rules (literal value, regular expression, or one # of the aliases. # @api private # def match?(value) if regex? return true if name =~ value.to_s else return(name == convert(value) ? true : @aliases.include?(convert(value))) end end # @return [Boolean] whether the accepted value is a regular expression or not. # @api private # def regex? @name.is_a?(Regexp) end private # A standard way of converting all of our values, so we're always # comparing apples to apples. # @api private # def convert(value) case value when Symbol, '' # can't intern an empty string value when String value.intern when true :true when false :false else value.to_s.intern end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parameter/value_collection.rb����������������������������������������������0000644�0052762�0001160�00000016431�13417161721�022736� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parameter/value' # A collection of values and regular expressions, used for specifying allowed values # in a given parameter. # @note This class is considered part of the internal implementation of {Puppet::Parameter}, and # {Puppet::Property} and the functionality provided by this class should be used via their interfaces. # @comment This class probably have several problems when trying to use it with a combination of # regular expressions and aliases as it finds an acceptable value holder vi "name" which may be # a regular expression... # # @api private # class Puppet::Parameter::ValueCollection # Aliases the given existing _other_ value with the additional given _name_. # @return [void] # @api private # def aliasvalue(name, other) other = other.to_sym unless value = match?(other) raise Puppet::DevError, _("Cannot alias nonexistent value %{value}") % { value: other } end value.alias(name) end # Returns a doc string (enumerating the acceptable values) for all of the values in this parameter/property. # @return [String] a documentation string. # @api private # def doc unless defined?(@doc) @doc = "" unless values.empty? @doc << "Valid values are " @doc << @strings.collect do |value| if aliases = value.aliases and ! aliases.empty? "`#{value.name}` (also called `#{aliases.join(", ")}`)" else "`#{value.name}`" end end.join(", ") << ". " end unless regexes.empty? @doc << "Values can match `#{regexes.join("`, `")}`." end end @doc end # @return [Boolean] Returns whether the set of allowed values is empty or not. # @api private # def empty? @values.empty? end # @api private # def initialize # We often look values up by name, so a hash makes more sense. @values = {} # However, we want to retain the ability to match values in order, # but we always prefer directly equality (i.e., strings) over regex matches. @regexes = [] @strings = [] end # Checks if the given value is acceptable (matches one of the literal values or patterns) and returns # the "matcher" that matched. # Literal string matchers are tested first, if both a literal and a regexp match would match, the literal # match wins. # # @param test_value [Object] the value to test if it complies with the configured rules # @return [Puppet::Parameter::Value, nil] The instance of Puppet::Parameter::Value that matched the given value, or nil if there was no match. # @api private # def match?(test_value) # First look for normal values if value = @strings.find { |v| v.match?(test_value) } return value end # Then look for a regex match @regexes.find { |v| v.match?(test_value) } end # Munges the value if it is valid, else produces the same value. # @param value [Object] the value to munge # @return [Object] the munged value, or the given value # @todo This method does not seem to do any munging. It just returns the value if it matches the # regexp, or the (most likely Symbolic) allowed value if it matches (which is more of a replacement # of one instance with an equal one. Is the intent that this method should be specialized? # @api private # def munge(value) return value if empty? if instance = match?(value) if instance.regex? return value else return instance.name end else return value end end # Defines a new valid value for a {Puppet::Property}. # A valid value is specified as a literal (typically a Symbol), but can also be # specified with a regexp. # # @param name [Symbol, Regexp] a valid literal value, or a regexp that matches a value # @param options [Hash] a hash with options # @option options [Symbol] :event The event that should be emitted when this value is set. # @todo Option :event original comment says "event should be returned...", is "returned" the correct word # to use? # @option options [Symbol] :invalidate_refreshes True if a change on this property should invalidate and # remove any scheduled refreshes (from notify or subscribe) targeted at the same resource. For example, if # a change in this property takes into account any changes that a scheduled refresh would have performed, # then the scheduled refresh would be deleted. # @option options [Object] _any_ Any other option is treated as a call to a setter having the given # option name (e.g. `:required_features` calls `required_features=` with the option's value as an # argument). # @api private # def newvalue(name, options = {}, &block) call_opt = options[:call] unless call_opt.nil? devfail "Cannot use obsolete :call value '#{call_opt}' for property '#{self.class.name}'" unless call_opt == :none || call_opt == :instead #TRANSLATORS ':call' is a property and should not be translated message = _("Property option :call is deprecated and no longer used.") message += ' ' + _("Please remove it.") Puppet.deprecation_warning(message) options = options.reject { |k,v| k == :call } end value = Puppet::Parameter::Value.new(name) @values[value.name] = value if value.regex? @regexes << value else @strings << value end options.each { |opt, arg| value.send(opt.to_s + "=", arg) } if block_given? devfail "Cannot use :call value ':none' in combination with a block for property '#{self.class.name}'" if call_opt == :none value.block = block value.method ||= "set_#{value.name}" if !value.regex? else devfail "Cannot use :call value ':instead' without a block for property '#{self.class.name}'" if call_opt == :instead end value end # Defines one or more valid values (literal or regexp) for a parameter or property. # @return [void] # @dsl type # @api private # def newvalues(*names) names.each { |name| newvalue(name) } end # @return [Array<String>] An array of the regular expressions in string form, configured as matching valid values. # @api private # def regexes @regexes.collect { |r| r.name.inspect } end # Validates the given value against the set of valid literal values and regular expressions. # @raise [ArgumentError] if the value is not accepted # @return [void] # @api private # def validate(value) return if empty? unless @values.detect {|name, v| v.match?(value)} str = _("Invalid value %{value}.") % { value: value.inspect } str += " " + _("Valid values are %{value_list}.") % { value_list: values.join(", ") } unless values.empty? str += " " + _("Valid values match %{pattern}.") % { pattern: regexes.join(", ") } unless regexes.empty? raise ArgumentError, str end end # Returns a valid value matcher (a literal or regular expression) # @todo This looks odd, asking for an instance that matches a symbol, or an instance that has # a regexp. What is the intention here? Marking as api private... # # @return [Puppet::Parameter::Value] a valid value matcher # @api private # def value(name) @values[name] end # @return [Array<Symbol>] Returns a list of valid literal values. # @see regexes # @api private # def values @strings.collect { |s| s.name } end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser.rb������������������������������������������������������������������0000644�0052762�0001160�00000001140�13417161721�016712� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# is only needed to create the name space module Puppet::Parser; end require 'puppet/parser/ast' require 'puppet/parser/abstract_compiler' require 'puppet/parser/compiler' require 'puppet/parser/compiler/catalog_validator' require 'puppet/resource/type_collection' require 'puppet/parser/functions' require 'puppet/parser/files' require 'puppet/parser/relationship' require 'puppet/resource/type' require 'monitor' Dir[File.dirname(__FILE__) + '/parser/compiler/catalog_validator/*.rb'].each { |f| require f } # PUP-3274 This should probably go someplace else class Puppet::LexError < RuntimeError; end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/��������������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�016376� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/abstract_compiler.rb������������������������������������������������0000644�0052762�0001160�00000002231�13417161721�022411� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� module Puppet::Parser::AbstractCompiler # Returns the catalog for a compilation. Must return a Puppet::Resource::Catalog or fail with an # error if the specific compiler does not support catalog operations. # def catalog raise Puppet::DevError("Class '#{self.class}' should have implemented 'catalog'") end # Returns the environment for the compilation # def environment raise Puppet::DevError("Class '#{self.class}' should have implemented 'environment'") end # Produces a new scope # This method is here if there are functions/logic that will call this for some other purpose than to create # a named scope for a class. It may not have to be here. (TODO) # def newscope(scope, options) raise Puppet::DevError("Class '#{self.class}' should have implemented 'newscope'") end # Returns a hash of all externally referenceable qualified variables # def qualified_variables raise Puppet::DevError("Class '#{self.class}' should have implemented 'qualified_variables'") end # Returns the top scope instance def topscope raise Puppet::DevError("Class '#{self.class}' should have implemented 'topscope'") end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/ast/����������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�017165� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/ast/block_expression.rb���������������������������������������������0000644�0052762�0001160�00000000666�13417161721�023066� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Evaluates contained expressions, produce result of the last # class Puppet::Parser::AST::BlockExpression < Puppet::Parser::AST::Branch def evaluate(scope) @children.reduce(nil) { |_, child| child.safeevaluate(scope) } end def sequence_with(other) Puppet::Parser::AST::BlockExpression.new(:children => self.children + other.children) end def to_s "[" + @children.collect { |c| c.to_s }.join(', ') + "]" end end ��������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/ast/hostclass.rb����������������������������������������������������0000644�0052762�0001160�00000001230�13417161721�021504� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parser/ast/top_level_construct' class Puppet::Parser::AST::Hostclass < Puppet::Parser::AST::TopLevelConstruct attr_accessor :name, :context def initialize(name, context = {}) @context = context @name = name end def instantiate(modname) new_class = Puppet::Resource::Type.new(:hostclass, @name, @context.merge(:module_name => modname)) all_types = [new_class] if code code.each do |nested_ast_node| if nested_ast_node.respond_to? :instantiate all_types += nested_ast_node.instantiate(modname) end end end return all_types end def code() @context[:code] end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/ast/node.rb���������������������������������������������������������0000644�0052762�0001160�00000001123�13417161721�020427� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Parser::AST::Node < Puppet::Parser::AST::TopLevelConstruct attr_accessor :names, :context def initialize(names, context = {}) raise ArgumentError, _("names should be an array") unless names.is_a? Array if context[:parent] raise Puppet::DevError, _("Node inheritance is removed in Puppet 4.0.0. See http://links.puppet.com/puppet-node-inheritance-deprecation") end @names = names @context = context end def instantiate(modname) @names.map { |name| Puppet::Resource::Type.new(:node, name, @context.merge(:module_name => modname)) } end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/ast/pops_bridge.rb��������������������������������������������������0000644�0052762�0001160�00000024512�13417161721�022006� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parser/ast/top_level_construct' require 'puppet/pops' # The AST::Bridge contains classes that bridges between the new Pops based model # and the 3.x AST. This is required to be able to reuse the Puppet::Resource::Type which is # fundamental for the rest of the logic. # class Puppet::Parser::AST::PopsBridge # Bridges to one Pops Model Expression # The @value is the expression # This is used to represent the body of a class, definition, or node, and for each parameter's default value # expression. # class Expression < Puppet::Parser::AST::Leaf def initialize args super @@evaluator ||= Puppet::Pops::Parser::EvaluatingParser.new() end def to_s Puppet::Pops::Model::ModelTreeDumper.new.dump(@value) end def source_text source_adapter = Puppet::Pops::Utils.find_closest_positioned(@value) source_adapter ? source_adapter.extract_text() : nil end def evaluate(scope) object = @@evaluator.evaluate(scope, @value) @@evaluator.convert_to_3x(object, scope) end # Adapts to 3x where top level constructs needs to have each to iterate over children. Short circuit this # by yielding self. By adding this there is no need to wrap a pops expression inside an AST::BlockExpression # def each yield self end def sequence_with(other) if value.nil? # This happens when testing and not having a complete setup other else # When does this happen ? Ever ? raise "sequence_with called on Puppet::Parser::AST::PopsBridge::Expression - please report use case" # What should be done if the above happens (We don't want this to happen). # Puppet::Parser::AST::BlockExpression.new(:children => [self] + other.children) end end # The 3x requires code plugged in to an AST to have this in certain positions in the tree. The purpose # is to either print the content, or to look for things that needs to be defined. This implementation # cheats by always returning an empty array. (This allows simple files to not require a "Program" at the top. # def children [] end end class ExpressionSupportingReturn < Expression def initialize args super end def evaluate(scope) return catch(:return) do return catch(:next) do return super(scope) end end end end # Bridges the top level "Program" produced by the pops parser. # Its main purpose is to give one point where all definitions are instantiated (actually defined since the # Puppet 3x terminology is somewhat misleading - the definitions are instantiated, but instances of the created types # are not created, that happens when classes are included / required, nodes are matched and when resources are instantiated # by a resource expression (which is also used to instantiate a host class). # class Program < Puppet::Parser::AST::TopLevelConstruct attr_reader :program_model, :context def initialize(program_model, context = {}) @program_model = program_model @context = context @ast_transformer ||= Puppet::Pops::Model::AstTransformer.new(@context[:file]) @@evaluator ||= Puppet::Pops::Parser::EvaluatingParser.new() end # This is the 3x API, the 3x AST searches through all code to find the instructions that can be instantiated. # This Pops-model based instantiation relies on the parser to build this list while parsing (which is more # efficient as it avoids one full scan of all logic via recursive enumeration/yield) # def instantiate(modname) @program_model.definitions.map do |d| case d when Puppet::Pops::Model::HostClassDefinition instantiate_HostClassDefinition(d, modname) when Puppet::Pops::Model::ResourceTypeDefinition instantiate_ResourceTypeDefinition(d, modname) when Puppet::Pops::Model::CapabilityMapping instantiate_CapabilityMapping(d, modname) when Puppet::Pops::Model::NodeDefinition instantiate_NodeDefinition(d, modname) when Puppet::Pops::Model::SiteDefinition instantiate_SiteDefinition(d, modname) when Puppet::Pops::Model::Application instantiate_ApplicationDefinition(d, modname) else loaders = Puppet::Pops::Loaders.loaders loaders.instantiate_definition(d, loaders.find_loader(modname)) # The 3x logic calling this will not know what to do with the result, it is compacted away at the end nil end end.flatten().compact() # flatten since node definition may have returned an array # Compact since 4x definitions are not understood by compiler end def evaluate(scope) @@evaluator.evaluate(scope, program_model) end # Adapts to 3x where top level constructs needs to have each to iterate over children. Short circuit this # by yielding self. This means that the HostClass container will call this bridge instance with `instantiate`. # def each yield self end # Returns true if this Program only contains definitions def is_definitions_only? is_definition?(program_model) end private def is_definition?(o) case o when Puppet::Pops::Model::Program is_definition?(o.body) when Puppet::Pops::Model::BlockExpression o.statements.all {|s| is_definition?(s) } when Puppet::Pops::Model::Definition true else false end end def instantiate_Parameter(o) # 3x needs parameters as an array of `[name]` or `[name, value_expr]` if o.value [o.name, Expression.new(:value => o.value)] else [o.name] end end def create_type_map(definition) result = {} # No need to do anything if there are no parameters return result unless definition.parameters.size > 0 # No need to do anything if there are no typed parameters typed_parameters = definition.parameters.select {|p| p.type_expr } return result if typed_parameters.empty? # If there are typed parameters, they need to be evaluated to produce the corresponding type # instances. This evaluation requires a scope. A scope is not available when doing deserialization # (there is also no initialized evaluator). When running apply and test however, the environment is # reused and we may reenter without a scope (which is fine). A debug message is then output in case # there is the need to track down the odd corner case. See {#obtain_scope}. # if scope = obtain_scope typed_parameters.each do |p| result[p.name] = @@evaluator.evaluate(scope, p.type_expr) end end result end # Obtains the scope or issues a warning if :global_scope is not bound def obtain_scope scope = Puppet.lookup(:global_scope) do # This occurs when testing and when applying a catalog (there is no scope available then), and # when running tests that run a partial setup. # This is bad if the logic is trying to compile, but a warning can not be issues since it is a normal # use case that there is no scope when requesting the type in order to just get the parameters. Puppet.debug {_("Instantiating Resource with type checked parameters - scope is missing, skipping type checking.")} nil end scope end # Produces a hash with data for Definition and HostClass def args_from_definition(o, modname, expr_class = Expression) args = { :arguments => o.parameters.collect {|p| instantiate_Parameter(p) }, :argument_types => create_type_map(o), :module_name => modname } unless is_nop?(o.body) args[:code] = expr_class.new(:value => o.body) end @ast_transformer.merge_location(args, o) end def instantiate_HostClassDefinition(o, modname) args = args_from_definition(o, modname, ExpressionSupportingReturn) args[:parent] = absolute_reference(o.parent_class) Puppet::Resource::Type.new(:hostclass, o.name, @context.merge(args)) end def instantiate_ResourceTypeDefinition(o, modname) instance = Puppet::Resource::Type.new(:definition, o.name, @context.merge(args_from_definition(o, modname, ExpressionSupportingReturn))) Puppet::Pops::Loaders.register_runtime3_type(instance.name, o.locator.to_uri(o)) instance end def instantiate_CapabilityMapping(o, modname) # Use an intermediate 'capability_mapping' type to pass this info to the compiler where the # actual mapping takes place Puppet::Resource::Type.new(:capability_mapping, "#{o.component} #{o.kind} #{o.capability}", { :arguments => { 'component' => o.component, 'kind' => o.kind, 'blueprint' => { :capability => o.capability, :mappings => o.mappings.reduce({}) do |memo, mapping| memo[mapping.attribute_name] = Expression.new(:value => mapping.value_expr) memo end }}}) end def instantiate_ApplicationDefinition(o, modname) args = args_from_definition(o, modname) Puppet::Resource::Type.new(:application, o.name, @context.merge(args)) end def instantiate_NodeDefinition(o, modname) args = { :module_name => modname } unless is_nop?(o.body) args[:code] = Expression.new(:value => o.body) end unless is_nop?(o.parent) args[:parent] = @ast_transformer.hostname(o.parent) end args = @ast_transformer.merge_location(args, o) host_matches = @ast_transformer.hostname(o.host_matches) host_matches.collect do |name| Puppet::Resource::Type.new(:node, name, @context.merge(args)) end end def instantiate_SiteDefinition(o, modname) args = { :module_name => modname } unless is_nop?(o.body) args[:code] = Expression.new(:value => o.body) end args = @ast_transformer.merge_location(args, o) Puppet::Resource::Type.new(:site, 'site', @context.merge(args)) end def code() Expression.new(:value => @value) end def is_nop?(o) @ast_transformer.is_nop?(o) end def absolute_reference(ref) if ref.nil? || ref.empty? || ref.start_with?('::') ref else "::#{ref}" end end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/ast/resource.rb�����������������������������������������������������0000644�0052762�0001160�00000005117�13417161721�021340� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Instruction for Resource instantiation. # Instantiates resources of both native and user defined types. # class Puppet::Parser::AST::Resource < Puppet::Parser::AST::Branch attr_accessor :type, :instances, :exported, :virtual def initialize(argshash) Puppet.warn_once('deprecations', 'AST::Resource', _('Use of Puppet::Parser::AST::Resource is deprecated and not fully functional')) super(argshash) end # Evaluates resources by adding them to the compiler for lazy evaluation # and returning the produced resource references. # def evaluate(scope) # We want virtual to be true if exported is true. We can't # just set :virtual => self.virtual in the initialization, # because sometimes the :virtual attribute is set *after* # :exported, in which case it clobbers :exported if :exported # is true. Argh, this was a very tough one to track down. virt = self.virtual || self.exported # First level of implicit iteration: build a resource for each # instance. This handles things like: # file { '/foo': owner => blah; '/bar': owner => blah } @instances.map do |instance| # Evaluate all of the specified params. paramobjects = instance.parameters.map { |param| param.safeevaluate(scope) } resource_titles = instance.title.safeevaluate(scope) # it's easier to always use an array, even for only one name resource_titles = [resource_titles] unless resource_titles.is_a?(Array) fully_qualified_type, resource_titles = scope.resolve_type_and_titles(type, resource_titles) # Second level of implicit iteration; build a resource for each # title. This handles things like: # file { ['/foo', '/bar']: owner => blah } resource_titles.flatten.map do |resource_title| exceptwrap :type => Puppet::ParseError do resource = Puppet::Parser::Resource.new( fully_qualified_type, resource_title, :parameters => paramobjects, :file => self.file, :line => self.line, :exported => self.exported, :virtual => virt, :source => scope.source, :scope => scope, :strict => true ) if resource.resource_type.is_a? Puppet::Resource::Type resource.resource_type.instantiate_resource(scope, resource) end scope.compiler.add_resource(scope, resource) scope.compiler.evaluate_classes([resource_title], scope, false) if fully_qualified_type == 'class' resource end end end.flatten.reject { |resource| resource.nil? } end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/ast/resource_instance.rb��������������������������������������������0000644�0052762�0001160�00000000614�13417161721�023221� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# A simple container for a parameter for an object. Consists of a # title and a set of parameters. # class Puppet::Parser::AST::ResourceInstance < Puppet::Parser::AST::Branch attr_accessor :title, :parameters def initialize(argshash) Puppet.warn_once('deprecations', 'AST::ResourceInstance', _('Use of Puppet::Parser::AST::ResourceInstance is deprecated')) super(argshash) end end ��������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/ast/resourceparam.rb������������������������������������������������0000644�0052762�0001160�00000001515�13417161721�022357� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The AST object for the parameters inside resource expressions # class Puppet::Parser::AST::ResourceParam < Puppet::Parser::AST::Branch attr_accessor :value, :param, :add def initialize(argshash) Puppet.warn_once('deprecations', 'AST::ResourceParam', _('Use of Puppet::Parser::AST::ResourceParam is deprecated and not fully functional')) super(argshash) end def each [@param, @value].each { |child| yield child } end # Return the parameter and the value. def evaluate(scope) value = @value.safeevaluate(scope) return Puppet::Parser::Resource::Param.new( :name => @param, :value => value.nil? ? :undef : value, :source => scope.source, :line => self.line, :file => self.file, :add => self.add ) end def to_s "#{@param} => #{@value.to_s}" end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/ast/top_level_construct.rb������������������������������������������0000644�0052762�0001160�00000000254�13417161721�023603� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The base class for AST nodes representing top level things: # hostclasses, definitions, and nodes. class Puppet::Parser::AST::TopLevelConstruct < Puppet::Parser::AST end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/ast/branch.rb�������������������������������������������������������0000644�0052762�0001160�00000001047�13417161721�020744� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The parent class of all AST objects that contain other AST objects. # Everything but the really simple objects descend from this. It is # important to note that Branch objects contain other AST objects only -- # if you want to contain values, use a descendant of the AST::Leaf class. # # @api private class Puppet::Parser::AST::Branch < Puppet::Parser::AST include Enumerable attr_accessor :pin, :children def each @children.each { |child| yield child } end def initialize(arghash) super(arghash) @children ||= [] end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/ast/leaf.rb���������������������������������������������������������0000644�0052762�0001160�00000003634�13417161721�020422� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The base class for all of the leaves of the parse trees. These # basically just have types and values. Both of these parameters # are simple values, not AST objects. # class Puppet::Parser::AST::Leaf < Puppet::Parser::AST attr_accessor :value, :type # Return our value. def evaluate(scope) @value end def match(value) @value == value end def to_s @value.to_s unless @value.nil? end end # Host names, either fully qualified or just the short name, or even a regex # class Puppet::Parser::AST::HostName < Puppet::Parser::AST::Leaf def initialize(hash) super # Note that this is an AST::Regex, not a Regexp unless @value.is_a?(Regex) @value = @value.to_s.downcase if @value =~ /[^-\w.]/ raise Puppet::DevError, _("'%{value}' is not a valid hostname") % { value: @value } end end end # implementing eql? and hash so that when an HostName is stored # in a hash it has the same hashing properties as the underlying value def eql?(value) @value.eql?(value.is_a?(HostName) ? value.value : value) end def hash @value.hash end end class Puppet::Parser::AST::Regex < Puppet::Parser::AST::Leaf def initialize(hash) super # transform value from hash options unless it is already a regular expression @value = Regexp.new(@value) unless @value.is_a?(Regexp) end # we're returning self here to wrap the regexp and to be used in places # where a string would have been used, without modifying any client code. # For instance, in many places we have the following code snippet: # val = @val.safeevaluate(@scope) # if val.match(otherval) # ... # end # this way, we don't have to modify this test specifically for handling # regexes. # def evaluate(scope) self end def match(value) @value.match(value) end def to_s Puppet::Pops::Types::PRegexpType.regexp_to_s_with_delimiters(@value) end end ����������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/compiler/�����������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020210� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/compiler/catalog_validator.rb���������������������������������������0000644�0052762�0001160�00000001703�13417161721�024210� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Abstract class for a catalog validator that can be registered with the compiler to run at # a certain stage. class Puppet::Parser::Compiler class CatalogValidator PRE_FINISH = :pre_finish FINAL = :final # Returns true if the validator should run at the given stage. The default # implementation will only run at stage `FINAL` # # @param stage [Symbol] One of the stage constants defined in this class # @return [Boolean] true if the validator should run at the given stage # def self.validation_stage?(stage) FINAL.equal?(stage) end attr_reader :catalog # @param catalog [Puppet::Resource::Catalog] The catalog to validate def initialize(catalog) @catalog = catalog end # Validate some aspect of the catalog and raise a `CatalogValidationError` on failure def validate end end class CatalogValidationError < Puppet::Error include Puppet::ExternalFileError end end �������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/compiler/catalog_validator/�����������������������������������������0000755�0052762�0001160�00000000000�13417162176�023667� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/compiler/catalog_validator/env_relationship_validator.rb������������0000644�0052762�0001160�00000004505�13417161721�031631� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Parser::Compiler # Validator that asserts that all capability resources that are referenced by 'consume' or 'require' has # been exported by some other resource in the environment class CatalogValidator::EnvironmentRelationshipValidator < CatalogValidator def validate assumed_exports = {} exported = {} catalog.resources.each do |resource| next unless resource.is_a?(Puppet::Parser::Resource) resource.eachparam do |param| pclass = Puppet::Type.metaparamclass(param.name) validate_relationship(resource, param, assumed_exports, exported) if !pclass.nil? && pclass < Puppet::Type::RelationshipMetaparam end end assumed_exports.each_pair do |key, (param, cap)| raise CatalogValidationError.new(_("Capability '%{cap}' referenced by '%{param}' is never exported") % { cap: cap, param: param.name }, param.file, param.line) unless exported.include?(key) end nil end private def validate_relationship(resource, param, assumed_exports, exported) case param.name when :require, :consume add_capability_ref(param, param.value, assumed_exports) when :export add_exported(resource, param, param.value, exported) end end def add_capability_ref(param, value, assumed_exports) case value when Array value.each { |v| add_capability_ref(param, v, assumed_exports) } when Puppet::Resource rt = value.resource_type unless rt.nil? || !rt.is_capability? title_key = catalog.title_key_for_ref(value.ref) assumed_exports[title_key] = [param, value] end nil end end def add_exported(resource, param, value, hash) case value when Array value.each { |v| add_exported(resource, param, v, hash) } when Puppet::Resource rt = value.resource_type unless rt.nil? || !rt.is_capability? title_key = catalog.title_key_for_ref(value.ref) if hash.include?(title_key) raise CatalogValidationError.new(_("'%{value}' is exported by both '%{hash}' and '%{resource}'") % { value: value, hash: hash[title_key], resource: resource }, param.file, param.line) else hash[title_key] = resource end end end end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/compiler/catalog_validator/relationship_validator.rb����������������0000644�0052762�0001160�00000004242�13417161721�030757� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Parser::Compiler # Validator that asserts that only 'require', 'consume', and 'export' is used when declaring relationships # to capability resources. class CatalogValidator::RelationshipValidator < CatalogValidator def validate catalog.resources.each do |resource| next unless resource.is_a?(Puppet::Parser::Resource) next if resource.virtual? resource.eachparam do |param| pclass = Puppet::Type.metaparamclass(param.name) validate_relationship(param) if !pclass.nil? && pclass < Puppet::Type::RelationshipMetaparam end end nil end private # A hash lookup is 6x avg times faster than find among 3 values. CAPABILITY_ACCEPTED_METAPARAMS = {:require => true, :consume => true, :export => true}.freeze def validate_relationship(param) # when relationship is to a capability if has_capability?(param.value) unless CAPABILITY_ACCEPTED_METAPARAMS[param.name] raise CatalogValidationError.new( _("'%{param}' is not a valid relationship to a capability") % { param: param.name }, param.file, param.line) end else # all other relationships requires the referenced resource to exist refs = param.value.is_a?(Array) ? param.value.flatten : [param.value] refs.each do |r| next if r.nil? || r == :undef res = r.to_s begin found = catalog.resource(res) rescue ArgumentError => e # Raise again but with file and line information raise CatalogValidationError.new(e.message, param.file, param.line) end unless found msg = _("Could not find resource '%{res}' in parameter '%{param}'") % { res: res, param: param.name.to_s } raise CatalogValidationError.new(msg, param.file, param.line) end end end end def has_capability?(value) case value when Array value.find { |v| has_capability?(v) } when Puppet::Resource rt = value.resource_type !rt.nil? && rt.is_capability? else false end end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/compiler/catalog_validator/site_validator.rb������������������������0000644�0052762�0001160�00000001311�13417161721�027214� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Parser::Compiler # Validator that asserts that only application components can appear inside a site. class CatalogValidator::SiteValidator < CatalogValidator def self.validation_stage?(stage) PRE_FINISH.equal?(stage) end def validate the_site_resource = catalog.resource('Site', 'site') return unless the_site_resource catalog.downstream_from_vertex(the_site_resource).keys.each do |r| unless r.is_application_component? || r.resource_type.application? raise CatalogValidationError.new(_("Only application components can appear inside a site - %{res} is not allowed") % { res: r }, r.file, r.line) end end end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/e4_parser_adapter.rb������������������������������������������������0000644�0052762�0001160�00000002611�13417161721�022302� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/pops' module Puppet module Parser # Adapts an egrammar/eparser to respond to the public API of the classic parser # and makes use of the new evaluator. # class E4ParserAdapter def initialize @file = '' @string = '' @use = :unspecified end def file=(file) @file = file @use = :file end def parse(string = nil) self.string= string if string parser = Pops::Parser::EvaluatingParser.singleton model = if @use == :string # Parse with a source_file to set in created AST objects (it was either given, or it may be unknown # if caller did not set a file and the present a string. # parser.parse_string(@string, @file || "unknown-source-location") else parser.parse_file(@file) end # the parse_result may be # * empty / nil (no input) # * a Model::Program # * a Model::Expression # args = {} Pops::Model::AstTransformer.new(@file).merge_location(args, model) ast_code = if model.is_a? Pops::Model::Program AST::PopsBridge::Program.new(model, args) else args[:value] = model AST::PopsBridge::Expression.new(args) end # Create the "main" class for the content - this content will get merged with all other "main" content AST::Hostclass.new('', :code => ast_code) end def string=(string) @string = string @use = :string end end end end �����������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/environment_compiler.rb���������������������������������������������0000644�0052762�0001160�00000015620�13417161721�023160� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parser/compiler' class Puppet::Parser::EnvironmentCompiler < Puppet::Parser::Compiler def self.compile(env, code_id=nil) begin env.check_for_reparse node = Puppet::Node.new(env) node.environment = env new(node, :code_id => code_id).compile rescue => detail message = _("%{detail} in environment %{env}") % { detail: detail, env: env.name } Puppet.log_exception(detail, message) raise Puppet::Error, message, detail.backtrace end end def initialize(node, options = {}) super @overridden_functions = {} end def add_function_overrides add_function_override('hiera_include', proc { Puppet.debug "Ignoring hiera_include() during environment catalog compilation" }) end def add_function_override(func_name, override) typed_name = Puppet::Pops::Loader::TypedName.new(:function, func_name) loader = loaders.puppet_system_loader # Remove and preserve existing entry. A `nil` is also preserved to indicate # an override that didn't replace a loaded function. entry = loader.get_entry(typed_name) existing = entry.nil? ? nil : entry.value loader.remove_entry(typed_name) unless existing.nil? @overridden_functions[typed_name] = existing # Add the override to the loader loader.set_entry(typed_name, override) end def remove_function_overrides loader = loaders.puppet_system_loader @overridden_functions.each_pair do |typed_name, overridden| loader.remove_entry(typed_name) loader.set_entry(typed_name, overridden) unless overridden.nil? end end def add_catalog_validators super add_catalog_validator(CatalogValidator::SiteValidator) add_catalog_validator(CatalogValidator::EnvironmentRelationshipValidator) end def compile add_function_overrides begin Puppet.override(@context_overrides, _("For compiling environment catalog %{env}") % { env: environment.name }) do @catalog.environment_instance = environment Puppet::Util::Profiler.profile(_("Env Compile: Created settings scope"), [:compiler, :create_settings_scope]) { create_settings_scope } Puppet::Util::Profiler.profile(_("Env Compile: Evaluated main"), [:compiler, :evaluate_main]) { evaluate_main } Puppet::Util::Profiler.profile(_("Env Compile: Evaluated site"), [:compiler, :evaluate_site]) { evaluate_site } Puppet::Util::Profiler.profile(_("Env Compile: Evaluated application instances"), [:compiler, :evaluate_applications]) { evaluate_applications } Puppet::Util::Profiler.profile(_("Env Compile: Prune"), [:compiler, :prune_catalog]) { prune_catalog } Puppet::Util::Profiler.profile(_("Env Compile: Validate Catalog pre-finish"), [:compiler, :validate_pre_finish]) do validate_catalog(CatalogValidator::PRE_FINISH) end Puppet::Util::Profiler.profile(_("Env Compile: Finished catalog"), [:compiler, :finish_catalog]) { finish } fail_on_unevaluated Puppet::Util::Profiler.profile(_("Env Compile: Validate Catalog final"), [:compiler, :validate_final]) do validate_catalog(CatalogValidator::FINAL) end if block_given? yield @catalog else @catalog end end ensure remove_function_overrides end end # @api private def prune_catalog prune_env_catalog end # Prunes the catalog by dropping all resources that are not contained under the Site (if a site expression is used). # As a consequence all edges to/from dropped resources are also dropped. # Once the pruning is performed, this compiler returns the pruned list when calling the #resources method. # The pruning does not alter the order of resources in the resources list. # def prune_env_catalog # Everything under Class[main], that is not under (inclusive of) Site[site] should be pruned as those resources # are intended for nodes in a node catalog. # the_main_class_resource = @catalog.resource('Class', '') the_site_resource = @catalog.resource('Site', 'site') # Get downstream vertexes returns a hash where the keys are the resources and values nesting level rooted_in_main = @catalog.downstream_from_vertex(the_main_class_resource).keys to_be_removed = if the_site_resource keep_from_site = @catalog.downstream_from_vertex(the_site_resource).keys keep_from_site << the_site_resource rooted_in_main - keep_from_site else rooted_in_main end @catalog.remove_resource(*to_be_removed) # The compiler keeps a list of added resources, this shadows that list with the now pruned result @pruned_resources = @catalog.resources end def add_resource(scope, resource) @resources << resource @catalog.add_resource(resource) if !resource.class? && resource[:stage] raise ArgumentError, _("Only classes can set 'stage'; normal resources like %{resource} cannot change run stage") % { resource: resource } end # Stages should not be inside of classes. They are always a # top-level container, regardless of where they appear in the # manifest. return if resource.stage? # This adds a resource to the class it lexically appears in in the # manifest. unless resource.class? @catalog.add_edge(scope.resource, resource) end resource.mark_unevaluated_consumer if is_capability_consumer?(resource) assert_app_in_site(scope, resource) end def evaluate_ast_node() # Do nothing, the environment catalog is not built for a particular node. end def on_empty_site Puppet.warning(_("Environment Compiler: Could not find a site definition to evaluate")) end def evaluate_applications exceptwrap do resources.select { |resource| type = resource.resource_type; type.is_a?(Puppet::Resource::Type) && type.application? }.each do |resource| Puppet::Util::Profiler.profile(_("Evaluated application %{resource}") % { resource: resource }, [:compiler, :evaluate_resource, resource]) do resource.evaluate end end end end def evaluate_classes(titles, scope, lazy) # Always lazy in an Environment compilation super(titles, scope, true) end # Overrides the regular compiler to be able to return the list of resources after a prune # has taken place in the graph representation. Before a prune, the list is the same as in the regular # compiler # def resources @pruned_resources || super end def is_capability?(value) if value.is_a?(Array) value.find { |ev| is_capability?(ev) } elsif value.is_a?(Puppet::Resource) rstype = value.resource_type rstype.nil? ? false : rstype.is_capability? else false end end private :is_capability? def is_capability_consumer?(resource) resource.eachparam { |param| return true if (param.name == :consume || param.name == :require) && is_capability?(param.value) } false end private :is_capability_consumer? end ����������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/files.rb������������������������������������������������������������0000644�0052762�0001160�00000005762�13417161721�020032� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Parser::Files module_function # Return a list of manifests as absolute filenames matching the given # pattern. # # @param pattern [String] A reference for a file in a module. It is the # format "<modulename>/<file glob>" # @param environment [Puppet::Node::Environment] the environment of modules # # @return [Array(String, Array<String>)] the module name and the list of files found # @api private def find_manifests_in_modules(pattern, environment) module_name, file_pattern = split_file_path(pattern) if mod = environment.module(module_name) [mod.name, mod.match_manifests(file_pattern)] else [nil, []] end end # Find the path to the given file selector. Files can be selected in # one of two ways: # * absolute path: the path is simply returned # * modulename/filename selector: a file is found in the file directory # of the named module. # # In the second case a nil is returned if there isn't a file found. In the # first case (absolute path), there is no existence check done and so the # path will be returned even if there isn't a file available. # # @param template [String] the file selector # @param environment [Puppet::Node::Environment] the environment in which to search # @return [String, nil] the absolute path to the file or nil if there is no file found # # @api private def find_file(file, environment) find_in_module(file, environment) do |mod,module_file| mod.file(module_file) end end # Find the path to the given template selector. Templates can be selected in # a couple of ways: # * absolute path: the path is simply returned # * modulename/filename selector: a file is found in the template directory # of the named module. # # In the last two cases a nil is returned if there isn't a file found. In the # first case (absolute path), there is no existence check done and so the # path will be returned even if there isn't a file available. # # @param template [String] the template selector # @param environment [Puppet::Node::Environment] the environment in which to search # @return [String, nil] the absolute path to the template file or nil if there is no file found # # @api private def find_template(template, environment) find_in_module(template, environment) do |mod,template_file| mod.template(template_file) end end # @api private def find_in_module(reference, environment) if Puppet::Util.absolute_path?(reference) reference else path, file = split_file_path(reference) mod = environment.module(path) if file && mod yield(mod, file) else nil end end end # Split the path into the module and the rest of the path, or return # nil if the path is empty or absolute (starts with a /). # @api private def split_file_path(path) if path == "" || Puppet::Util.absolute_path?(path) nil else path.split(File::SEPARATOR, 2) end end end ��������������puppet-5.5.10/lib/puppet/parser/functions/����������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020406� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/assert_type.rb��������������������������������������������0000644�0052762�0001160�00000004020�13417161721�023264� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction( :assert_type, :type => :rvalue, :arity => -3, :doc => <<DOC Returns the given value if it is of the given [data type](https://puppet.com/docs/puppet/latest/lang_data.html), or otherwise either raises an error or executes an optional two-parameter [lambda](https://puppet.com/docs/puppet/latest/lang_lambdas.html). The function takes two mandatory arguments, in this order: 1. The expected data type. 2. A value to compare against the expected data type. **Example**: Using `assert_type` ~~~ puppet $raw_username = 'Amy Berry' # Assert that $raw_username is a non-empty string and assign it to $valid_username. $valid_username = assert_type(String[1], $raw_username) # $valid_username contains "Amy Berry". # If $raw_username was an empty string or a different data type, the Puppet run would # fail with an "Expected type does not match actual" error. ~~~ You can use an optional lambda to provide enhanced feedback. The lambda takes two mandatory parameters, in this order: 1. The expected data type as described in the function's first argument. 2. The actual data type of the value. **Example**: Using `assert_type` with a warning and default value ~~~ puppet $raw_username = 'Amy Berry' # Assert that $raw_username is a non-empty string and assign it to $valid_username. # If it isn't, output a warning describing the problem and use a default value. $valid_username = assert_type(String[1], $raw_username) |$expected, $actual| { warning( "The username should be \'${expected}\', not \'${actual}\'. Using 'anonymous'." ) 'anonymous' } # $valid_username contains "Amy Berry". # If $raw_username was an empty string, the Puppet run would set $valid_username to # "anonymous" and output a warning: "The username should be 'String[1, default]', not # 'String[0, 0]'. Using 'anonymous'." ~~~ For more information about data types, see the [documentation](https://puppet.com/docs/puppet/latest/lang_data.html). - Since 4.0.0 DOC ) do |args| Puppet::Parser::Functions::Error.is4x('assert_type') end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/binary_file.rb��������������������������������������������0000644�0052762�0001160�00000001412�13417161721�023207� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction( :binary_file, :type => :rvalue, :arity => 1, :doc => <<-DOC Loads a binary file from a module or file system and returns its contents as a Binary. The argument to this function should be a `<MODULE NAME>/<FILE>` reference, which will load `<FILE>` from a module's `files` directory. (For example, the reference `mysql/mysqltuner.pl` will load the file `<MODULES DIRECTORY>/mysql/files/mysqltuner.pl`.) This function also accepts an absolute file path that allows reading binary file content from anywhere on disk. An error is raised if the given file does not exists. To search for the existence of files, use the `find_file()` function. - since 4.8.0 DOC ) do |args| Puppet::Parser::Functions::Error.is4x('binary_file') end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/break.rb��������������������������������������������������0000644�0052762�0001160�00000001625�13417161721�022016� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction( :break, :arity => 0, :doc => <<-DOC Breaks the innermost iteration as if it encountered an end of input. This function does not return to the caller. The signal produced to stop the iteration bubbles up through the call stack until either terminating the innermost iteration or raising an error if the end of the call stack is reached. The break() function does not accept an argument. **Example:** Using `break` ```puppet $data = [1,2,3] notice $data.map |$x| { if $x == 3 { break() } $x*10 } ``` Would notice the value `[10, 20]` **Example:** Using a nested `break` ```puppet function break_if_even($x) { if $x % 2 == 0 { break() } } $data = [1,2,3] notice $data.map |$x| { break_if_even($x); $x*10 } ``` Would notice the value `[10]` * Also see functions `next` and `return` * Since 4.8.0 DOC ) do |args| Puppet::Parser::Functions::Error.is4x('break') end �����������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/contain.rb������������������������������������������������0000644�0052762�0001160�00000002562�13417161721�022366� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Called within a class definition, establishes a containment # relationship with another class Puppet::Parser::Functions::newfunction( :contain, :arity => -2, :doc => "Contain one or more classes inside the current class. If any of these classes are undeclared, they will be declared as if called with the `include` function. Accepts a class name, an array of class names, or a comma-separated list of class names. A contained class will not be applied before the containing class is begun, and will be finished before the containing class is finished. You must use the class's full name; relative names are not allowed. In addition to names in string form, you may also directly use Class and Resource Type values that are produced by evaluating resource and relationship expressions. The function returns an array of references to the classes that were contained thus allowing the function call to `contain` to directly continue. - Since 4.0.0 support for Class and Resource Type values, absolute names - Since 4.7.0 an Array[Type[Class[n]]] is returned with all the contained classes " ) do |classes| # Call the 4.x version of this function in case 3.x ruby code uses this function Puppet.warn_once('deprecations', '3xfunction#contain', _("Calling function_contain via the Scope class is deprecated. Use Scope#call_function instead")) call_function('contain', classes) end ����������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/create_resources.rb���������������������������������������0000644�0052762�0001160�00000010142�13417161721�024261� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction(:create_resources, :arity => -3, :doc => <<-'ENDHEREDOC') do |args| Converts a hash into a set of resources and adds them to the catalog. This function takes two mandatory arguments: a resource type, and a hash describing a set of resources. The hash should be in the form `{title => {parameters} }`: # A hash of user resources: $myusers = { 'nick' => { uid => '1330', gid => allstaff, groups => ['developers', 'operations', 'release'], }, 'dan' => { uid => '1308', gid => allstaff, groups => ['developers', 'prosvc', 'release'], }, } create_resources(user, $myusers) A third, optional parameter may be given, also as a hash: $defaults = { 'ensure' => present, 'provider' => 'ldap', } create_resources(user, $myusers, $defaults) The values given on the third argument are added to the parameters of each resource present in the set given on the second argument. If a parameter is present on both the second and third arguments, the one on the second argument takes precedence. This function can be used to create defined resources and classes, as well as native resources. Virtual and Exported resources may be created by prefixing the type name with @ or @@ respectively. For example, the $myusers hash may be exported in the following manner: create_resources("@@user", $myusers) The $myusers may be declared as virtual resources using: create_resources("@user", $myusers) Note that `create_resources` will filter out parameter values that are `undef` so that normal data binding and puppet default value expressions are considered (in that order) for the final value of a parameter (just as when setting a parameter to `undef` in a puppet language resource declaration). ENDHEREDOC if Puppet[:tasks] raise Puppet::ParseErrorWithIssue.from_issue_and_stack( Puppet::Pops::Issues::CATALOG_OPERATION_NOT_SUPPORTED_WHEN_SCRIPTING, {:operation => 'create_resources'}) end raise ArgumentError, (_("create_resources(): wrong number of arguments (%{count}; must be 2 or 3)") % { count: args.length }) if args.length > 3 raise ArgumentError, (_('create_resources(): second argument must be a hash')) unless args[1].is_a?(Hash) if args.length == 3 raise ArgumentError, (_('create_resources(): third argument, if provided, must be a hash')) unless args[2].is_a?(Hash) end type, instances, defaults = args defaults ||= {} type_name = type.sub(/^@{1,2}/, '').downcase # Get file/line information from the puppet stack (where call comes from in puppet source) # If relayed via other puppet functions in ruby that do not nest their calls, the source position # will be in the original puppet source. # file, line = Puppet::Pops::PuppetStack.top_of_stack if type.start_with? '@@' exported = true virtual = true elsif type.start_with? '@' virtual = true end if type_name == 'class' && (exported || virtual) # cannot find current evaluator, so use another evaluator = Puppet::Pops::Parser::EvaluatingParser.new.evaluator # optionally fails depending on configured severity of issue evaluator.runtime_issue(Puppet::Pops::Issues::CLASS_NOT_VIRTUALIZABLE) end instances.map do |title, params| # Add support for iteration if title is an array resource_titles = title.is_a?(Array) ? title : [title] Puppet::Pops::Evaluator::Runtime3ResourceSupport.create_resources( file, line, self, virtual, exported, type_name, resource_titles, defaults.merge(params).map do |name, value| value = nil if value == :undef Puppet::Parser::Resource::Param.new( :name => name, :value => value, # wide open to various data types, must be correct :source => self.source, # TODO: support :line => line, :file => file, :add => false ) end.compact ) end.flatten.compact end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/defined.rb������������������������������������������������0000644�0052762�0001160�00000006467�13417161721�022341� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction( :defined, :type => :rvalue, :arity => -2, :doc => <<DOC Determines whether a given class or resource type is defined and returns a Boolean value. You can also use `defined` to determine whether a specific resource is defined, or whether a variable has a value (including `undef`, as opposed to the variable never being declared or assigned). This function takes at least one string argument, which can be a class name, type name, resource reference, or variable reference of the form `'$name'`. The `defined` function checks both native and defined types, including types provided by modules. Types and classes are matched by their names. The function matches resource declarations by using resource references. **Examples**: Different types of `defined` function matches ~~~ puppet # Matching resource types defined("file") defined("customtype") # Matching defines and classes defined("foo") defined("foo::bar") # Matching variables defined('$name') # Matching declared resources defined(File['/tmp/file']) ~~~ Puppet depends on the configuration's evaluation order when checking whether a resource is declared. **Example**: Importance of evaluation order when using `defined` ~~~ puppet # Assign values to $is_defined_before and $is_defined_after using identical `defined` # functions. $is_defined_before = defined(File['/tmp/file']) file { "/tmp/file": ensure => present, } $is_defined_after = defined(File['/tmp/file']) # $is_defined_before returns false, but $is_defined_after returns true. ~~~ This order requirement only refers to evaluation order. The order of resources in the configuration graph (e.g. with `before` or `require`) does not affect the `defined` function's behavior. > **Warning:** Avoid relying on the result of the `defined` function in modules, as you > might not be able to guarantee the evaluation order well enough to produce consistent > results. This can cause other code that relies on the function's result to behave > inconsistently or fail. If you pass more than one argument to `defined`, the function returns `true` if _any_ of the arguments are defined. You can also match resources by type, allowing you to match conditions of different levels of specificity, such as whether a specific resource is of a specific data type. **Example**: Matching multiple resources and resources by different types with `defined` ~~~ puppet file { "/tmp/file1": ensure => file, } $tmp_file = file { "/tmp/file2": ensure => file, } # Each of these statements return `true` ... defined(File['/tmp/file1']) defined(File['/tmp/file1'],File['/tmp/file2']) defined(File['/tmp/file1'],File['/tmp/file2'],File['/tmp/file3']) # ... but this returns `false`. defined(File['/tmp/file3']) # Each of these statements returns `true` ... defined(Type[Resource['file','/tmp/file2']]) defined(Resource['file','/tmp/file2']) defined(File['/tmp/file2']) defined('$tmp_file') # ... but each of these returns `false`. defined(Type[Resource['exec','/tmp/file2']]) defined(Resource['exec','/tmp/file2']) defined(File['/tmp/file3']) defined('$tmp_file2') ~~~ - Since 2.7.0 - Since 3.6.0 variable reference and future parser types - Since 3.8.1 type specific requests with future parser - Since 4.0.0 includes all future parser features DOC ) do |vals| Puppet::Parser::Functions::Error.is4x('defined') end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/dig.rb����������������������������������������������������0000644�0052762�0001160�00000002314�13417161721�021471� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction( :dig, :type => :rvalue, :arity => -1, :doc => <<-DOC Returns a value for a sequence of given keys/indexes into a structure, such as an array or hash. This function is used to "dig into" a complex data structure by using a sequence of keys / indexes to access a value from which the next key/index is accessed recursively. The first encountered `undef` value or key stops the "dig" and `undef` is returned. An error is raised if an attempt is made to "dig" into something other than an `undef` (which immediately returns `undef`), an `Array` or a `Hash`. **Example:** Using `dig` ```puppet $data = {a => { b => [{x => 10, y => 20}, {x => 100, y => 200}]}} notice $data.dig('a', 'b', 1, 'x') ``` Would notice the value 100. This is roughly equivalent to `$data['a']['b'][1]['x']`. However, a standard index will return an error and cause catalog compilation failure if any parent of the final key (`'x'`) is `undef`. The `dig` function will return undef, rather than failing catalog compilation. This allows you to check if data exists in a structure without mandating that it always exists. * Since 4.5.0 DOC ) do |args| Puppet::Parser::Functions::Error.is4x('dig') end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/digest.rb�������������������������������������������������0000644�0052762�0001160�00000000522�13417161721�022204� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/checksums' Puppet::Parser::Functions::newfunction(:digest, :type => :rvalue, :arity => 1, :doc => "Returns a hash value from a provided string using the digest_algorithm setting from the Puppet config file.") do |args| algo = Puppet[:digest_algorithm] Puppet::Util::Checksums.method(algo.intern).call args[0] end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/each.rb���������������������������������������������������0000644�0052762�0001160�00000006572�13417161721�021640� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction( :each, :type => :rvalue, :arity => -3, :doc => <<-DOC Runs a [lambda](https://puppet.com/docs/puppet/latest/lang_lambdas.html) repeatedly using each value in a data structure, then returns the values unchanged. This function takes two mandatory arguments, in this order: 1. An array or hash the function will iterate over. 2. A lambda, which the function calls for each element in the first argument. It can request one or two parameters. **Example**: Using the `each` function `$data.each |$parameter| { <PUPPET CODE BLOCK> }` or `each($data) |$parameter| { <PUPPET CODE BLOCK> }` When the first argument (`$data` in the above example) is an array, Puppet passes each value in turn to the lambda, then returns the original values. **Example**: Using the `each` function with an array and a one-parameter lambda ~~~ puppet # For the array $data, run a lambda that creates a resource for each item. $data = ["routers", "servers", "workstations"] $data.each |$item| { notify { $item: message => $item } } # Puppet creates one resource for each of the three items in $data. Each resource is # named after the item's value and uses the item's value in a parameter. ~~~ When the first argument is a hash, Puppet passes each key and value pair to the lambda as an array in the form `[key, value]` and returns the original hash. **Example**: Using the `each` function with a hash and a one-parameter lambda ~~~ puppet # For the hash $data, run a lambda using each item as a key-value array that creates a # resource for each item. $data = {"rtr" => "Router", "svr" => "Server", "wks" => "Workstation"} $data.each |$items| { notify { $items[0]: message => $items[1] } } # Puppet creates one resource for each of the three items in $data, each named after the # item's key and containing a parameter using the item's value. ~~~ When the first argument is an array and the lambda has two parameters, Puppet passes the array's indexes (enumerated from 0) in the first parameter and its values in the second parameter. **Example**: Using the `each` function with an array and a two-parameter lambda ~~~ puppet # For the array $data, run a lambda using each item's index and value that creates a # resource for each item. $data = ["routers", "servers", "workstations"] $data.each |$index, $value| { notify { $value: message => $index } } # Puppet creates one resource for each of the three items in $data, each named after the # item's value and containing a parameter using the item's index. ~~~ When the first argument is a hash, Puppet passes its keys to the first parameter and its values to the second parameter. **Example**: Using the `each` function with a hash and a two-parameter lambda ~~~ puppet # For the hash $data, run a lambda using each item's key and value to create a resource # for each item. $data = {"rtr" => "Router", "svr" => "Server", "wks" => "Workstation"} $data.each |$key, $value| { notify { $key: message => $value } } # Puppet creates one resource for each of the three items in $data, each named after the # item's key and containing a parameter using the item's value. ~~~ For an example that demonstrates how to create multiple `file` resources using `each`, see the Puppet [iteration](https://puppet.com/docs/puppet/latest/lang_iteration.html) documentation. - Since 4.0.0 DOC ) do |args| Puppet::Parser::Functions::Error.is4x('each') end ��������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/epp.rb����������������������������������������������������0000644�0052762�0001160�00000003257�13417161721�021521� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction(:epp, :type => :rvalue, :arity => -2, :doc => "Evaluates an Embedded Puppet (EPP) template file and returns the rendered text result as a String. `epp('<MODULE NAME>/<TEMPLATE FILE>', <PARAMETER HASH>)` The first argument to this function should be a `<MODULE NAME>/<TEMPLATE FILE>` reference, which loads `<TEMPLATE FILE>` from `<MODULE NAME>`'s `templates` directory. In most cases, the last argument is optional; if used, it should be a [hash](/puppet/latest/reference/lang_data_hash.html) that contains parameters to pass to the template. - See the [template](/puppet/latest/reference/lang_template.html) documentation for general template usage information. - See the [EPP syntax](/puppet/latest/reference/lang_template_epp.html) documentation for examples of EPP. For example, to call the apache module's `templates/vhost/_docroot.epp` template and pass the `docroot` and `virtual_docroot` parameters, call the `epp` function like this: `epp('apache/vhost/_docroot.epp', { 'docroot' => '/var/www/html', 'virtual_docroot' => '/var/www/example' })` This function can also accept an absolute path, which can load a template file from anywhere on disk. Puppet produces a syntax error if you pass more parameters than are declared in the template's parameter tag. When passing parameters to a template that contains a parameter tag, use the same names as the tag's declared parameters. Parameters are required only if they are declared in the called template's parameter tag without default values. Puppet produces an error if the `epp` function fails to pass any required parameter. - Since 4.0.0") do |args| Puppet::Parser::Functions::Error.is4x('epp') end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/fail.rb���������������������������������������������������0000644�0052762�0001160�00000000333�13417161721�021640� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction(:fail, :arity => -1, :doc => "Fail with a parse error.") do |vals| vals = vals.collect { |s| s.to_s }.join(" ") if vals.is_a? Array raise Puppet::ParseError, vals.to_s end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/file.rb���������������������������������������������������0000644�0052762�0001160�00000002124�13417161721�021644� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/file_system' Puppet::Parser::Functions::newfunction( :file, :arity => -2, :type => :rvalue, :doc => "Loads a file from a module and returns its contents as a string. The argument to this function should be a `<MODULE NAME>/<FILE>` reference, which will load `<FILE>` from a module's `files` directory. (For example, the reference `mysql/mysqltuner.pl` will load the file `<MODULES DIRECTORY>/mysql/files/mysqltuner.pl`.) This function can also accept: * An absolute path, which can load a file from anywhere on disk. * Multiple arguments, which will return the contents of the **first** file found, skipping any files that don't exist. " ) do |vals| path = nil vals.each do |file| found = Puppet::Parser::Files.find_file(file, compiler.environment) if found && Puppet::FileSystem.exist?(found) path = found break end end if path Puppet::FileSystem.read_preserve_line_endings(path) else raise Puppet::ParseError, _("Could not find any files from %{values}") % { values: vals.join(", ") } end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/find_file.rb����������������������������������������������0000644�0052762�0001160�00000001751�13417161721�022651� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction( :find_file, :type => :rvalue, :arity => -2, :doc => <<-DOC Finds an existing file from a module and returns its path. The argument to this function should be a String as a `<MODULE NAME>/<FILE>` reference, which will search for `<FILE>` relative to a module's `files` directory. (For example, the reference `mysql/mysqltuner.pl` will search for the file `<MODULES DIRECTORY>/mysql/files/mysqltuner.pl`.) This function can also accept: * An absolute String path, which will check for the existence of a file from anywhere on disk. * Multiple String arguments, which will return the path of the **first** file found, skipping non existing files. * An array of string paths, which will return the path of the **first** file found from the given paths in the array, skipping non existing files. The function returns `undef` if none of the given paths were found - since 4.8.0 DOC ) do |args| Puppet::Parser::Functions::Error.is4x('find_file') end �����������������������puppet-5.5.10/lib/puppet/parser/functions/fqdn_rand.rb����������������������������������������������0000644�0052762�0001160�00000003470�13417161721�022666� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'digest/md5' require 'digest/sha2' Puppet::Parser::Functions::newfunction(:fqdn_rand, :arity => -2, :type => :rvalue, :doc => "Usage: `fqdn_rand(MAX, [SEED])`. MAX is required and must be a positive integer; SEED is optional and may be any number or string. Generates a random Integer number greater than or equal to 0 and less than MAX, combining the `$fqdn` fact and the value of SEED for repeatable randomness. (That is, each node will get a different random number from this function, but a given node's result will be the same every time unless its hostname changes.) This function is usually used for spacing out runs of resource-intensive cron tasks that run on many nodes, which could cause a thundering herd or degrade other services if they all fire at once. Adding a SEED can be useful when you have more than one such task and need several unrelated random numbers per node. (For example, `fqdn_rand(30)`, `fqdn_rand(30, 'expensive job 1')`, and `fqdn_rand(30, 'expensive job 2')` will produce totally different numbers.)") do |args| max = args.shift.to_i # Puppet 5.4's fqdn_rand function produces a different value than earlier versions # for the same set of inputs. # This causes problems because the values are often written into service configuration files. # When they change, services get notified and restart. # Restoring previous fqdn_rand behavior of calculating its seed value using MD5 # when running on a non-FIPS enabled platform and only using SHA256 on FIPS enabled # platforms. if Puppet::Util::Platform.fips_enabled? seed = Digest::SHA256.hexdigest([self['::fqdn'],max,args].join(':')).hex else seed = Digest::MD5.hexdigest([self['::fqdn'],max,args].join(':')).hex end Puppet::Util.deterministic_rand_int(seed,max) end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/hiera.rb��������������������������������������������������0000644�0052762�0001160�00000006567�13417161721�022034� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'hiera_puppet' module Puppet::Parser::Functions newfunction( :hiera, :type => :rvalue, :arity => -2, :doc => <<DOC Performs a standard priority lookup of the hierarchy and returns the most specific value for a given key. The returned value can be any type of data. The function takes up to three arguments, in this order: 1. A string key that Hiera searches for in the hierarchy. **Required**. 2. An optional default value to return if Hiera doesn't find anything matching the key. * If this argument isn't provided and this function results in a lookup failure, Puppet fails with a compilation error. 3. The optional name of an arbitrary [hierarchy level](https://puppet.com/docs/hiera/latest/hierarchy.html) to insert at the top of the hierarchy. This lets you temporarily modify the hierarchy for a single lookup. * If Hiera doesn't find a matching key in the overriding hierarchy level, it continues searching the rest of the hierarchy. The `hiera` function does **not** find all matches throughout a hierarchy, instead returining the first specific value starting at the top of the hierarchy. To search throughout a hierarchy, use the `hiera_array` or `hiera_hash` functions. **Example**: Using `hiera` ~~~ yaml # Assuming hiera.yaml # :hierarchy: # - web01.example.com # - common # Assuming web01.example.com.yaml: # users: # - "Amy Barry" # - "Carrie Douglas" # Assuming common.yaml: users: admins: - "Edith Franklin" - "Ginny Hamilton" regular: - "Iris Jackson" - "Kelly Lambert" ~~~ ~~~ puppet # Assuming we are not web01.example.com: $users = hiera('users', undef) # $users contains {admins => ["Edith Franklin", "Ginny Hamilton"], # regular => ["Iris Jackson", "Kelly Lambert"]} ~~~ You can optionally generate the default value with a [lambda](https://puppet.com/docs/puppet/latest/lang_lambdas.html) that takes one parameter. **Example**: Using `hiera` with a lambda ~~~ puppet # Assuming the same Hiera data as the previous example: $users = hiera('users') | $key | { "Key \'${key}\' not found" } # $users contains {admins => ["Edith Franklin", "Ginny Hamilton"], # regular => ["Iris Jackson", "Kelly Lambert"]} # If hiera couldn't match its key, it would return the lambda result, # "Key 'users' not found". ~~~ The returned value's data type depends on the types of the results. In the example above, Hiera matches the 'users' key and returns it as a hash. The `hiera` function is deprecated in favor of using `lookup` and will be removed in 6.0.0. See https://puppet.com/docs/puppet/#{Puppet.minor_version}/deprecated_language.html. Replace the calls as follows: | from | to | | ---- | ---| | hiera($key) | lookup($key) | | hiera($key, $default) | lookup($key, { 'default_value' => $default }) | | hiera($key, $default, $level) | override level not supported | Note that calls using the 'override level' option are not directly supported by 'lookup' and the produced result must be post processed to get exactly the same result, for example using simple hash/array `+` or with calls to stdlib's `deep_merge` function depending on kind of hiera call and setting of merge in hiera.yaml. See [the documentation](https://puppet.com/docs/hiera/latest/puppet.html#hiera-lookup-functions) for more information about Hiera lookup functions. - Since 4.0.0 DOC ) do |*args| Error.is4x('hiera') end end �����������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/hiera_array.rb��������������������������������������������0000644�0052762�0001160�00000006306�13417161721�023221� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'hiera_puppet' module Puppet::Parser::Functions newfunction( :hiera_array, :type => :rvalue, :arity => -2, :doc => <<-DOC Finds all matches of a key throughout the hierarchy and returns them as a single flattened array of unique values. If any of the matched values are arrays, they're flattened and included in the results. This is called an [array merge lookup](https://puppet.com/docs/hiera/latest/lookup_types.html#array-merge). The `hiera_array` function takes up to three arguments, in this order: 1. A string key that Hiera searches for in the hierarchy. **Required**. 2. An optional default value to return if Hiera doesn't find anything matching the key. * If this argument isn't provided and this function results in a lookup failure, Puppet fails with a compilation error. 3. The optional name of an arbitrary [hierarchy level](https://puppet.com/docs/hiera/latest/hierarchy.html) to insert at the top of the hierarchy. This lets you temporarily modify the hierarchy for a single lookup. * If Hiera doesn't find a matching key in the overriding hierarchy level, it continues searching the rest of the hierarchy. **Example**: Using `hiera_array` ~~~ yaml # Assuming hiera.yaml # :hierarchy: # - web01.example.com # - common # Assuming common.yaml: # users: # - 'cdouglas = regular' # - 'efranklin = regular' # Assuming web01.example.com.yaml: # users: 'abarry = admin' ~~~ ~~~ puppet $allusers = hiera_array('users', undef) # $allusers contains ["cdouglas = regular", "efranklin = regular", "abarry = admin"]. ~~~ You can optionally generate the default value with a [lambda](https://puppet.com/docs/puppet/latest/lang_lambdas.html) that takes one parameter. **Example**: Using `hiera_array` with a lambda ~~~ puppet # Assuming the same Hiera data as the previous example: $allusers = hiera_array('users') | $key | { "Key \'${key}\' not found" } # $allusers contains ["cdouglas = regular", "efranklin = regular", "abarry = admin"]. # If hiera_array couldn't match its key, it would return the lambda result, # "Key 'users' not found". ~~~ `hiera_array` expects that all values returned will be strings or arrays. If any matched value is a hash, Puppet raises a type mismatch error. `hiera_array` is deprecated in favor of using `lookup` and will be removed in 6.0.0. See https://puppet.com/docs/puppet/#{Puppet.minor_version}/deprecated_language.html. Replace the calls as follows: | from | to | | ---- | ---| | hiera_array($key) | lookup($key, { 'merge' => 'unique' }) | | hiera_array($key, $default) | lookup($key, { 'default_value' => $default, 'merge' => 'unique' }) | | hiera_array($key, $default, $level) | override level not supported | Note that calls using the 'override level' option are not directly supported by 'lookup' and the produced result must be post processed to get exactly the same result, for example using simple hash/array `+` or with calls to stdlib's `deep_merge` function depending on kind of hiera call and setting of merge in hiera.yaml. See [the documentation](https://puppet.com/docs/hiera/latest/puppet.html#hiera-lookup-functions) for more information about Hiera lookup functions. - Since 4.0.0 DOC ) do |*args| Error.is4x('hiera_array') end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/hiera_hash.rb���������������������������������������������0000644�0052762�0001160�00000006765�13417161721�023037� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'hiera_puppet' module Puppet::Parser::Functions newfunction( :hiera_hash, :type => :rvalue, :arity => -2, :doc => <<-DOC Finds all matches of a key throughout the hierarchy and returns them in a merged hash. If any of the matched hashes share keys, the final hash uses the value from the highest priority match. This is called a [hash merge lookup](https://puppet.com/docs/hiera/latest/lookup_types.html#hash-merge). The merge strategy is determined by Hiera's [`:merge_behavior`](https://puppet.com/docs/hiera/latest/configuring.html#mergebehavior) setting. The `hiera_hash` function takes up to three arguments, in this order: 1. A string key that Hiera searches for in the hierarchy. **Required**. 2. An optional default value to return if Hiera doesn't find anything matching the key. * If this argument isn't provided and this function results in a lookup failure, Puppet fails with a compilation error. 3. The optional name of an arbitrary [hierarchy level](https://puppet.com/docs/hiera/latest/hierarchy.html) to insert at the top of the hierarchy. This lets you temporarily modify the hierarchy for a single lookup. * If Hiera doesn't find a matching key in the overriding hierarchy level, it continues searching the rest of the hierarchy. **Example**: Using `hiera_hash` ~~~ yaml # Assuming hiera.yaml # :hierarchy: # - web01.example.com # - common # Assuming common.yaml: # users: # regular: # 'cdouglas': 'Carrie Douglas' # Assuming web01.example.com.yaml: # users: # administrators: # 'aberry': 'Amy Berry' ~~~ ~~~ puppet # Assuming we are not web01.example.com: $allusers = hiera_hash('users', undef) # $allusers contains {regular => {"cdouglas" => "Carrie Douglas"}, # administrators => {"aberry" => "Amy Berry"}} ~~~ You can optionally generate the default value with a [lambda](https://puppet.com/docs/puppet/latest/lang_lambdas.html) that takes one parameter. **Example**: Using `hiera_hash` with a lambda ~~~ puppet # Assuming the same Hiera data as the previous example: $allusers = hiera_hash('users') | $key | { "Key \'${key}\' not found" } # $allusers contains {regular => {"cdouglas" => "Carrie Douglas"}, # administrators => {"aberry" => "Amy Berry"}} # If hiera_hash couldn't match its key, it would return the lambda result, # "Key 'users' not found". ~~~ `hiera_hash` expects that all values returned will be hashes. If any of the values found in the data sources are strings or arrays, Puppet raises a type mismatch error. `hiera_hash` is deprecated in favor of using `lookup` and will be removed in 6.0.0. See https://puppet.com/docs/puppet/#{Puppet.minor_version}/deprecated_language.html. Replace the calls as follows: | from | to | | ---- | ---| | hiera_hash($key) | lookup($key, { 'merge' => 'hash' }) | | hiera_hash($key, $default) | lookup($key, { 'default_value' => $default, 'merge' => 'hash' }) | | hiera_hash($key, $default, $level) | override level not supported | Note that calls using the 'override level' option are not directly supported by 'lookup' and the produced result must be post processed to get exactly the same result, for example using simple hash/array `+` or with calls to stdlib's `deep_merge` function depending on kind of hiera call and setting of merge in hiera.yaml. See [the documentation](https://puppet.com/docs/hiera/latest/puppet.html#hiera-lookup-functions) for more information about Hiera lookup functions. - Since 4.0.0 DOC ) do |*args| Error.is4x('hiera_hash') end end �����������puppet-5.5.10/lib/puppet/parser/functions/hiera_include.rb������������������������������������������0000644�0052762�0001160�00000007144�13417161721�023527� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'hiera_puppet' module Puppet::Parser::Functions newfunction( :hiera_include, :arity => -2, :doc => <<-DOC Assigns classes to a node using an [array merge lookup](https://puppet.com/docs/hiera/latest/lookup_types.html#array-merge) that retrieves the value for a user-specified key from Hiera's data. The `hiera_include` function requires: - A string key name to use for classes. - A call to this function (i.e. `hiera_include('classes')`) in your environment's `sites.pp` manifest, outside of any node definitions and below any top-scope variables that Hiera uses in lookups. - `classes` keys in the appropriate Hiera data sources, with an array for each `classes` key and each value of the array containing the name of a class. The function takes up to three arguments, in this order: 1. A string key that Hiera searches for in the hierarchy. **Required**. 2. An optional default value to return if Hiera doesn't find anything matching the key. * If this argument isn't provided and this function results in a lookup failure, Puppet fails with a compilation error. 3. The optional name of an arbitrary [hierarchy level](https://puppet.com/docs/hiera/latest/hierarchy.html) to insert at the top of the hierarchy. This lets you temporarily modify the hierarchy for a single lookup. * If Hiera doesn't find a matching key in the overriding hierarchy level, it continues searching the rest of the hierarchy. The function uses an [array merge lookup](https://puppet.com/docs/hiera/latest/lookup_types.html#array-merge) to retrieve the `classes` array, so every node gets every class from the hierarchy. **Example**: Using `hiera_include` ~~~ yaml # Assuming hiera.yaml # :hierarchy: # - web01.example.com # - common # Assuming web01.example.com.yaml: # classes: # - apache::mod::php # Assuming common.yaml: # classes: # - apache ~~~ ~~~ puppet # In site.pp, outside of any node definitions and below any top-scope variables: hiera_include('classes', undef) # Puppet assigns the apache and apache::mod::php classes to the web01.example.com node. ~~~ You can optionally generate the default value with a [lambda](https://puppet.com/docs/puppet/latest/lang_lambdas.html) that takes one parameter. **Example**: Using `hiera_include` with a lambda ~~~ puppet # Assuming the same Hiera data as the previous example: # In site.pp, outside of any node definitions and below any top-scope variables: hiera_include('classes') | $key | {"Key \'${key}\' not found" } # Puppet assigns the apache and apache::mod::php classes to the web01.example.com node. # If hiera_include couldn't match its key, it would return the lambda result, # "Key 'classes' not found". ~~~ `hiera_include` is deprecated in favor of using a combination of `include` and `lookup` and will be removed in Puppet 6.0.0. Replace the calls as follows: | from | to | | ---- | ---| | hiera_include($key) | include(lookup($key, { 'merge' => 'unique' })) | | hiera_include($key, $default) | include(lookup($key, { 'default_value' => $default, 'merge' => 'unique' })) | | hiera_include($key, $default, $level) | override level not supported | See [the Upgrading to Hiera 5 migration guide](https://puppet.com/docs/puppet/5.5/hiera_migrate.html) for more information. Note that calls using the 'override level' option are not directly supported by 'lookup' and the produced result must be post processed to get exactly the same result, for example using simple hash/array `+` or with calls to stdlib's `deep_merge` function depending on kind of hiera call and setting of merge in hiera.yaml. - Since 4.0.0 DOC ) do |*args| Error.is4x('hiera_include') end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/include.rb������������������������������������������������0000644�0052762�0001160�00000003521�13417161721�022352� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Include the specified classes Puppet::Parser::Functions::newfunction(:include, :arity => -2, :doc => "Declares one or more classes, causing the resources in them to be evaluated and added to the catalog. Accepts a class name, an array of class names, or a comma-separated list of class names. The `include` function can be used multiple times on the same class and will only declare a given class once. If a class declared with `include` has any parameters, Puppet will automatically look up values for them in Hiera, using `<class name>::<parameter name>` as the lookup key. Contrast this behavior with resource-like class declarations (`class {'name': parameter => 'value',}`), which must be used in only one place per class and can directly set parameters. You should avoid using both `include` and resource-like declarations with the same class. The `include` function does not cause classes to be contained in the class where they are declared. For that, see the `contain` function. It also does not create a dependency relationship between the declared class and the surrounding class; for that, see the `require` function. You must use the class's full name; relative names are not allowed. In addition to names in string form, you may also directly use Class and Resource Type values that are produced by the future parser's resource and relationship expressions. - Since < 3.0.0 - Since 4.0.0 support for class and resource type values, absolute names - Since 4.7.0 returns an Array[Type[Class]] of all included classes ") do |classes| call_function('include', classes) #TRANSLATORS "function_include", "Scope", and "Scope#call_function" refer to Puppet internals and should not be translated Puppet.warn_once('deprecations', '3xfunction#include', _("Calling function_include via the Scope class is deprecated. Use Scope#call_function instead")) end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/inline_epp.rb���������������������������������������������0000644�0052762�0001160�00000004213�13417161721�023050� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction(:inline_epp, :type => :rvalue, :arity => -2, :doc => "Evaluates an Embedded Puppet (EPP) template string and returns the rendered text result as a String. `inline_epp('<EPP TEMPLATE STRING>', <PARAMETER HASH>)` The first argument to this function should be a string containing an EPP template. In most cases, the last argument is optional; if used, it should be a [hash](/puppet/latest/reference/lang_data_hash.html) that contains parameters to pass to the template. - See the [template](/puppet/latest/reference/lang_template.html) documentation for general template usage information. - See the [EPP syntax](/puppet/latest/reference/lang_template_epp.html) documentation for examples of EPP. For example, to evaluate an inline EPP template and pass it the `docroot` and `virtual_docroot` parameters, call the `inline_epp` function like this: `inline_epp('docroot: <%= $docroot %> Virtual docroot: <%= $virtual_docroot %>', { 'docroot' => '/var/www/html', 'virtual_docroot' => '/var/www/example' })` Puppet produces a syntax error if you pass more parameters than are declared in the template's parameter tag. When passing parameters to a template that contains a parameter tag, use the same names as the tag's declared parameters. Parameters are required only if they are declared in the called template's parameter tag without default values. Puppet produces an error if the `inline_epp` function fails to pass any required parameter. An inline EPP template should be written as a single-quoted string or [heredoc](/puppet/latest/reference/lang_data_string.html#heredocs). A double-quoted string is subject to expression interpolation before the string is parsed as an EPP template. For example, to evaluate an inline EPP template using a heredoc, call the `inline_epp` function like this: ~~~ puppet # Outputs 'Hello given argument planet!' inline_epp(@(END), { x => 'given argument' }) <%- | $x, $y = planet | -%> Hello <%= $x %> <%= $y %>! END ~~~ - Since 3.5 - Requires [future parser](/puppet/3.8/reference/experiments_future.html) in Puppet 3.5 to 3.8") do |arguments| Puppet::Parser::Functions::Error.is4x('inline_epp') end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/inline_template.rb����������������������������������������0000644�0052762�0001160�00000001767�13417161721�024112� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction(:inline_template, :type => :rvalue, :arity => -2, :doc => "Evaluate a template string and return its value. See [the templating docs](https://puppet.com/docs/puppet/latest/lang_template.html) for more information. Note that if multiple template strings are specified, their output is all concatenated and returned as the output of the function.") do |vals| if Puppet[:tasks] raise Puppet::ParseErrorWithIssue.from_issue_and_stack( Puppet::Pops::Issues::FEATURE_NOT_SUPPORTED_WHEN_SCRIPTING, {:feature => 'ERB inline_template'}) end require 'erb' vals.collect do |string| # Use a wrapper, so the template can't get access to the full # Scope object. wrapper = Puppet::Parser::TemplateWrapper.new(self) begin wrapper.result(string) rescue => detail raise Puppet::ParseError, _("Failed to parse inline template: %{detail}") % { detail: detail }, detail.backtrace end end.join("") end ���������puppet-5.5.10/lib/puppet/parser/functions/lest.rb���������������������������������������������������0000644�0052762�0001160�00000002302�13417161721�021672� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction( :lest, :type => :rvalue, :arity => -2, :doc => <<-DOC Call a [lambda](https://puppet.com/docs/puppet/latest/lang_lambdas.html) (which should accept no arguments) if the argument given to the function is `undef`. Returns the result of calling the lambda if the argument is `undef`, otherwise the given argument. The `lest` function is useful in a chain of `then` calls, or in general as a guard against `undef` values. The function can be used to call `fail`, or to return a default value. These two expressions are equivalent: ```puppet if $x == undef { do_things() } lest($x) || { do_things() } ``` **Example:** Using the `lest` function ```puppet $data = {a => [ b, c ] } notice $data.dig(a, b, c) .then |$x| { $x * 2 } .lest || { fail("no value for $data[a][b][c]" } ``` Would fail the operation because $data[a][b][c] results in `undef` (there is no `b` key in `a`). In contrast - this example: ```puppet $data = {a => { b => { c => 10 } } } notice $data.dig(a, b, c) .then |$x| { $x * 2 } .lest || { fail("no value for $data[a][b][c]" } ``` Would notice the value `20` * Since 4.5.0 DOC ) do |args| Puppet::Parser::Functions::Error.is4x('lest') end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/lookup.rb�������������������������������������������������0000644�0052762�0001160�00000014231�13417161721�022240� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Parser::Functions newfunction(:lookup, :type => :rvalue, :arity => -2, :doc => <<-'ENDHEREDOC') do |args| Uses the Puppet lookup system to retrieve a value for a given key. By default, this returns the first value found (and fails compilation if no values are available), but you can configure it to merge multiple values into one, fail gracefully, and more. When looking up a key, Puppet will search up to three tiers of data, in the following order: 1. Hiera. 2. The current environment's data provider. 3. The indicated module's data provider, if the key is of the form `<MODULE NAME>::<SOMETHING>`. #### Arguments You must provide the name of a key to look up, and can optionally provide other arguments. You can combine these arguments in the following ways: * `lookup( <NAME>, [<VALUE TYPE>], [<MERGE BEHAVIOR>], [<DEFAULT VALUE>] )` * `lookup( [<NAME>], <OPTIONS HASH> )` * `lookup( as above ) |$key| { # lambda returns a default value }` Arguments in `[square brackets]` are optional. The arguments accepted by `lookup` are as follows: 1. `<NAME>` (string or array) --- The name of the key to look up. * This can also be an array of keys. If Puppet doesn't find anything for the first key, it will try again with the subsequent ones, only resorting to a default value if none of them succeed. 2. `<VALUE TYPE>` (data type) --- A [data type](https://puppet.com/docs/puppet/latest/lang_data_type.html) that must match the retrieved value; if not, the lookup (and catalog compilation) will fail. Defaults to `Data` (accepts any normal value). 3. `<MERGE BEHAVIOR>` (string or hash; see **"Merge Behaviors"** below) --- Whether (and how) to combine multiple values. If present, this overrides any merge behavior specified in the data sources. Defaults to no value; Puppet will use merge behavior from the data sources if present, and will otherwise do a first-found lookup. 4. `<DEFAULT VALUE>` (any normal value) --- If present, `lookup` returns this when it can't find a normal value. Default values are never merged with found values. Like a normal value, the default must match the value type. Defaults to no value; if Puppet can't find a normal value, the lookup (and compilation) will fail. 5. `<OPTIONS HASH>` (hash) --- Alternate way to set the arguments above, plus some less-common extra options. If you pass an options hash, you can't combine it with any regular arguments (except `<NAME>`). An options hash can have the following keys: * `'name'` --- Same as `<NAME>` (argument 1). You can pass this as an argument or in the hash, but not both. * `'value_type'` --- Same as `<VALUE TYPE>` (argument 2). * `'merge'` --- Same as `<MERGE BEHAVIOR>` (argument 3). * `'default_value'` --- Same as `<DEFAULT VALUE>` (argument 4). * `'default_values_hash'` (hash) --- A hash of lookup keys and default values. If Puppet can't find a normal value, it will check this hash for the requested key before giving up. You can combine this with `default_value` or a lambda, which will be used if the key isn't present in this hash. Defaults to an empty hash. * `'override'` (hash) --- A hash of lookup keys and override values. Puppet will check for the requested key in the overrides hash _first;_ if found, it returns that value as the _final_ value, ignoring merge behavior. Defaults to an empty hash. Finally, `lookup` can take a lambda, which must accept a single parameter. This is yet another way to set a default value for the lookup; if no results are found, Puppet will pass the requested key to the lambda and use its result as the default value. #### Merge Behaviors Puppet lookup uses a hierarchy of data sources, and a given key might have values in multiple sources. By default, Puppet returns the first value it finds, but it can also continue searching and merge all the values together. > **Note:** Data sources can use the special `lookup_options` metadata key to request a specific merge behavior for a key. The `lookup` function will use that requested behavior unless you explicitly specify one. The valid merge behaviors are: * `'first'` --- Returns the first value found, with no merging. Puppet lookup's default behavior. * `'unique'` (called "array merge" in classic Hiera) --- Combines any number of arrays and scalar values to return a merged, flattened array with all duplicate values removed. The lookup will fail if any hash values are found. * `'hash'` --- Combines the keys and values of any number of hashes to return a merged hash. If the same key exists in multiple source hashes, Puppet will use the value from the highest-priority data source; it won't recursively merge the values. * `'deep'` --- Combines the keys and values of any number of hashes to return a merged hash. If the same key exists in multiple source hashes, Puppet will recursively merge hash or array values (with duplicate values removed from arrays). For conflicting scalar values, the highest-priority value will win. * `{'strategy' => 'first|unique|hash'}` --- Same as the string versions of these merge behaviors. * `{'strategy' => 'deep', <DEEP OPTION> => <VALUE>, ...}` --- Same as `'deep'`, but can adjust the merge with additional options. The available options are: * `'knockout_prefix'` (string or undef) --- A string prefix to indicate a value should be _removed_ from the final result. Defaults to `undef`, which disables this feature. * `'sort_merged_arrays'` (boolean) --- Whether to sort all arrays that are merged together. Defaults to `false`. * `'merge_hash_arrays'` (boolean) --- Whether to merge hashes within arrays. Defaults to `false`. #### Examples Look up a key and return the first value found: lookup('ntp::service_name') Do a unique merge lookup of class names, then add all of those classes to the catalog (like `hiera_include`): lookup('classes', Array[String], 'unique').include Do a deep hash merge lookup of user data, but let higher priority sources remove values by prefixing them with `--`: lookup( { 'name' => 'users', 'merge' => { 'strategy' => 'deep', 'knockout_prefix' => '--', }, }) ENDHEREDOC Error.is4x('lookup') end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/map.rb����������������������������������������������������0000644�0052762�0001160�00000004562�13417161721�021512� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction( :map, :type => :rvalue, :arity => -3, :doc => <<-DOC Applies a [lambda](https://puppet.com/docs/puppet/latest/lang_lambdas.html) to every value in a data structure and returns an array containing the results. This function takes two mandatory arguments, in this order: 1. An array or hash the function will iterate over. 2. A lambda, which the function calls for each element in the first argument. It can request one or two parameters. **Example**: Using the `map` function `$transformed_data = $data.map |$parameter| { <PUPPET CODE BLOCK> }` or `$transformed_data = map($data) |$parameter| { <PUPPET CODE BLOCK> }` When the first argument (`$data` in the above example) is an array, Puppet passes each value in turn to the lambda. **Example**: Using the `map` function with an array and a one-parameter lambda ~~~ puppet # For the array $data, return an array containing each value multiplied by 10 $data = [1,2,3] $transformed_data = $data.map |$items| { $items * 10 } # $transformed_data contains [10,20,30] ~~~ When the first argument is a hash, Puppet passes each key and value pair to the lambda as an array in the form `[key, value]`. **Example**: Using the `map` function with a hash and a one-parameter lambda ~~~ puppet # For the hash $data, return an array containing the keys $data = {'a'=>1,'b'=>2,'c'=>3} $transformed_data = $data.map |$items| { $items[0] } # $transformed_data contains ['a','b','c'] ~~~ When the first argument is an array and the lambda has two parameters, Puppet passes the array's indexes (enumerated from 0) in the first parameter and its values in the second parameter. **Example**: Using the `map` function with an array and a two-parameter lambda ~~~ puppet # For the array $data, return an array containing the indexes $data = [1,2,3] $transformed_data = $data.map |$index,$value| { $index } # $transformed_data contains [0,1,2] ~~~ When the first argument is a hash, Puppet passes its keys to the first parameter and its values to the second parameter. **Example**: Using the `map` function with a hash and a two-parameter lambda ~~~ puppet # For the hash $data, return an array containing each value $data = {'a'=>1,'b'=>2,'c'=>3} $transformed_data = $data.map |$key,$value| { $value } # $transformed_data contains [1,2,3] ~~~ - Since 4.0.0 DOC ) do |args| Puppet::Parser::Functions::Error.is4x('map') end ����������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/match.rb��������������������������������������������������0000644�0052762�0001160�00000002477�13417161721�022034� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction( :match, :arity => 2, :doc => <<-DOC Matches a regular expression against a string and returns an array containing the match and any matched capturing groups. The first argument is a string or array of strings. The second argument is either a regular expression, regular expression represented as a string, or Regex or Pattern data type that the function matches against the first argument. The returned array contains the entire match at index 0, and each captured group at subsequent index values. If the value or expression being matched is an array, the function returns an array with mapped match results. If the function doesn't find a match, it returns 'undef'. **Example**: Matching a regular expression in a string ~~~ ruby $matches = "abc123".match(/[a-z]+[1-9]+/) # $matches contains [abc123] ~~~ **Example**: Matching a regular expressions with grouping captures in a string ~~~ ruby $matches = "abc123".match(/([a-z]+)([1-9]+)/) # $matches contains [abc123, abc, 123] ~~~ **Example**: Matching a regular expression with grouping captures in an array of strings ~~~ ruby $matches = ["abc123","def456"].match(/([a-z]+)([1-9]+)/) # $matches contains [[abc123, abc, 123], [def456, def, 456]] ~~~ - Since 4.0.0 DOC ) do |args| Puppet::Parser::Functions::Error.is4x('match') end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/md5.rb����������������������������������������������������0000644�0052762�0001160�00000000321�13417161721�021407� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'digest/md5' Puppet::Parser::Functions::newfunction(:md5, :type => :rvalue, :arity => 1, :doc => "Returns a MD5 hash value from a provided string.") do |args| Digest::MD5.hexdigest(args[0]) end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/new.rb����������������������������������������������������0000644�0052762�0001160�00000103307�13417161721�021523� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction( :new, :type => :rvalue, :arity => -1, :doc => <<-DOC Creates a new instance/object of a given data type. This function makes it possible to create new instances of concrete data types. If a block is given it is called with the just created instance as an argument. Calling this function is equivalent to directly calling the data type: **Example:** `new` and calling type directly are equivalent ```puppet $a = Integer.new("42") $b = Integer("42") ``` These would both convert the string `"42"` to the decimal value `42`. **Example:** arguments by position or by name ```puppet $a = Integer.new("42", 8) $b = Integer({from => "42", radix => 8}) ``` This would convert the octal (radix 8) number `"42"` in string form to the decimal value `34`. The new function supports two ways of giving the arguments: * by name (using a hash with property to value mapping) * by position (as regular arguments) Note that it is not possible to create new instances of some abstract data types (for example `Variant`). The data type `Optional[T]` is an exception as it will create an instance of `T` or `undef` if the value to convert is `undef`. The arguments that can be given is determined by the data type. > An assertion is always made that the produced value complies with the given type constraints. **Example:** data type constraints are checked ```puppet Integer[0].new("-100") ``` Would fail with an assertion error (since value is less than 0). The following sections show the arguments and conversion rules per data type built into the Puppet Type System. ### Conversion to Optional[T] and NotUndef[T] Conversion to these data types is the same as a conversion to the type argument `T`. In the case of `Optional[T]` it is accepted that the argument to convert may be `undef`. It is however not acceptable to give other arguments (than `undef`) that cannot be converted to `T`. ### Conversion to Integer A new `Integer` can be created from `Integer`, `Float`, `Boolean`, and `String` values. For conversion from `String` it is possible to specify the radix (base). ```puppet type Radix = Variant[Default, Integer[2,2], Integer[8,8], Integer[10,10], Integer[16,16]] function Integer.new( String $value, Radix $radix = 10, Boolean $abs = false ) function Integer.new( Variant[Numeric, Boolean] $value, Boolean $abs = false ) ``` * When converting from `String` the default radix is 10. * If radix is not specified an attempt is made to detect the radix from the start of the string: * `0b` or `0B` is taken as radix 2. * `0x` or `0X` is taken as radix 16. * `0` as radix 8. * All others are decimal. * Conversion from `String` accepts an optional sign in the string. * For hexadecimal (radix 16) conversion an optional leading "0x", or "0X" is accepted. * For octal (radix 8) an optional leading "0" is accepted. * For binary (radix 2) an optional leading "0b" or "0B" is accepted. * When `radix` is set to `default`, the conversion is based on the leading. characters in the string. A leading "0" for radix 8, a leading "0x", or "0X" for radix 16, and leading "0b" or "0B" for binary. * Conversion from `Boolean` results in 0 for `false` and 1 for `true`. * Conversion from `Integer`, `Float`, and `Boolean` ignores the radix. * `Float` value fractions are truncated (no rounding). * When `abs` is set to `true`, the result will be an absolute integer. Examples - Converting to Integer: ```puppet $a_number = Integer("0xFF", 16) # results in 255 $a_number = Integer("010") # results in 8 $a_number = Integer("010", 10) # results in 10 $a_number = Integer(true) # results in 1 $a_number = Integer(-38, 10, true) # results in 38 ``` ### Conversion to Float A new `Float` can be created from `Integer`, `Float`, `Boolean`, and `String` values. For conversion from `String` both float and integer formats are supported. ```puppet function Float.new( Variant[Numeric, Boolean, String] $value, Boolean $abs = true ) ``` * For an integer, the floating point fraction of `.0` is added to the value. * A `Boolean` `true` is converted to 1.0, and a `false` to 0.0 * In `String` format, integer prefixes for hex and binary are understood (but not octal since floating point in string format may start with a '0'). * When `abs` is set to `true`, the result will be an absolute floating point value. ### Conversion to Numeric A new `Integer` or `Float` can be created from `Integer`, `Float`, `Boolean` and `String` values. ```puppet function Numeric.new( Variant[Numeric, Boolean, String] $value, Boolean $abs = true ) ``` * If the value has a decimal period, or if given in scientific notation (e/E), the result is a `Float`, otherwise the value is an `Integer`. The conversion from `String` always uses a radix based on the prefix of the string. * Conversion from `Boolean` results in 0 for `false` and 1 for `true`. * When `abs` is set to `true`, the result will be an absolute `Float`or `Integer` value. Examples - Converting to Numeric ```puppet $a_number = Numeric(true) # results in 1 $a_number = Numeric("0xFF") # results in 255 $a_number = Numeric("010") # results in 8 $a_number = Numeric("3.14") # results in 3.14 (a float) $a_number = Numeric(-42.3, true) # results in 42.3 $a_number = Numeric(-42, true) # results in 42 ``` ### Conversion to Timespan A new `Timespan` can be created from `Integer`, `Float`, `String`, and `Hash` values. Several variants of the constructor are provided. #### Timespan from seconds When a Float is used, the decimal part represents fractions of a second. ```puppet function Timespan.new( Variant[Float, Integer] $value ) ``` #### Timespan from days, hours, minutes, seconds, and fractions of a second The arguments can be passed separately in which case the first four, days, hours, minutes, and seconds are mandatory and the rest are optional. All values may overflow and/or be negative. The internal 128-bit nano-second integer is calculated as: ``` (((((days * 24 + hours) * 60 + minutes) * 60 + seconds) * 1000 + milliseconds) * 1000 + microseconds) * 1000 + nanoseconds ``` ```puppet function Timespan.new( Integer $days, Integer $hours, Integer $minutes, Integer $seconds, Integer $milliseconds = 0, Integer $microseconds = 0, Integer $nanoseconds = 0 ) ``` or, all arguments can be passed as a `Hash`, in which case all entries are optional: ```puppet function Timespan.new( Struct[{ Optional[negative] => Boolean, Optional[days] => Integer, Optional[hours] => Integer, Optional[minutes] => Integer, Optional[seconds] => Integer, Optional[milliseconds] => Integer, Optional[microseconds] => Integer, Optional[nanoseconds] => Integer }] $hash ) ``` #### Timespan from String and format directive patterns The first argument is parsed using the format optionally passed as a string or array of strings. When an array is used, an attempt will be made to parse the string using the first entry and then with each entry in succession until parsing succeeds. If the second argument is omitted, an array of default formats will be used. An exception is raised when no format was able to parse the given string. ```puppet function Timespan.new( String $string, Variant[String[2],Array[String[2], 1]] $format = <default format>) ) ``` the arguments may also be passed as a `Hash`: ```puppet function Timespan.new( Struct[{ string => String[1], Optional[format] => Variant[String[2],Array[String[2], 1]] }] $hash ) ``` The directive consists of a percent (%) character, zero or more flags, optional minimum field width and a conversion specifier as follows: ``` %[Flags][Width]Conversion ``` ##### Flags: | Flag | Meaning | ---- | --------------- | - | Don't pad numerical output | _ | Use spaces for padding | 0 | Use zeros for padding ##### Format directives: | Format | Meaning | | ------ | ------- | | D | Number of Days | | H | Hour of the day, 24-hour clock | | M | Minute of the hour (00..59) | | S | Second of the minute (00..59) | | L | Millisecond of the second (000..999) | | N | Fractional seconds digits | The format directive that represents the highest magnitude in the format will be allowed to overflow. I.e. if no "%D" is used but a "%H" is present, then the hours may be more than 23. The default array contains the following patterns: ``` ['%D-%H:%M:%S', '%D-%H:%M', '%H:%M:%S', '%H:%M'] ``` Examples - Converting to Timespan ```puppet $duration = Timespan(13.5) # 13 seconds and 500 milliseconds $duration = Timespan({days=>4}) # 4 days $duration = Timespan(4, 0, 0, 2) # 4 days and 2 seconds $duration = Timespan('13:20') # 13 hours and 20 minutes (using default pattern) $duration = Timespan('10:03.5', '%M:%S.%L') # 10 minutes, 3 seconds, and 5 milli-seconds $duration = Timespan('10:03.5', '%M:%S.%N') # 10 minutes, 3 seconds, and 5 nano-seconds ``` ### Conversion to Timestamp A new `Timestamp` can be created from `Integer`, `Float`, `String`, and `Hash` values. Several variants of the constructor are provided. #### Timestamp from seconds since epoch (1970-01-01 00:00:00 UTC) When a Float is used, the decimal part represents fractions of a second. ```puppet function Timestamp.new( Variant[Float, Integer] $value ) ``` #### Timestamp from String and patterns consisting of format directives The first argument is parsed using the format optionally passed as a string or array of strings. When an array is used, an attempt will be made to parse the string using the first entry and then with each entry in succession until parsing succeeds. If the second argument is omitted, an array of default formats will be used. A third optional timezone argument can be provided. The first argument will then be parsed as if it represents a local time in that timezone. The timezone can be any timezone that is recognized when using the '%z' or '%Z' formats, or the word 'current', in which case the current timezone of the evaluating process will be used. The timezone argument is case insensitive. The default timezone, when no argument is provided, or when using the keyword `default`, is 'UTC'. It is illegal to provide a timezone argument other than `default` in combination with a format that contains '%z' or '%Z' since that would introduce an ambiguity as to which timezone to use. The one extracted from the string, or the one provided as an argument. An exception is raised when no format was able to parse the given string. ```puppet function Timestamp.new( String $string, Variant[String[2],Array[String[2], 1]] $format = <default format>, String $timezone = default) ) ``` the arguments may also be passed as a `Hash`: ```puppet function Timestamp.new( Struct[{ string => String[1], Optional[format] => Variant[String[2],Array[String[2], 1]], Optional[timezone] => String[1] }] $hash ) ``` The directive consists of a percent (%) character, zero or more flags, optional minimum field width and a conversion specifier as follows: ``` %[Flags][Width]Conversion ``` ##### Flags: | Flag | Meaning | ---- | --------------- | - | Don't pad numerical output | _ | Use spaces for padding | 0 | Use zeros for padding | # | Change names to upper-case or change case of am/pm | ^ | Use uppercase | : | Use colons for %z ##### Format directives (names and padding can be altered using flags): **Date (Year, Month, Day):** | Format | Meaning | | ------ | ------- | | Y | Year with century, zero-padded to at least 4 digits | | C | year / 100 (rounded down such as 20 in 2009) | | y | year % 100 (00..99) | | m | Month of the year, zero-padded (01..12) | | B | The full month name ("January") | | b | The abbreviated month name ("Jan") | | h | Equivalent to %b | | d | Day of the month, zero-padded (01..31) | | e | Day of the month, blank-padded ( 1..31) | | j | Day of the year (001..366) | **Time (Hour, Minute, Second, Subsecond):** | Format | Meaning | | ------ | ------- | | H | Hour of the day, 24-hour clock, zero-padded (00..23) | | k | Hour of the day, 24-hour clock, blank-padded ( 0..23) | | I | Hour of the day, 12-hour clock, zero-padded (01..12) | | l | Hour of the day, 12-hour clock, blank-padded ( 1..12) | | P | Meridian indicator, lowercase ("am" or "pm") | | p | Meridian indicator, uppercase ("AM" or "PM") | | M | Minute of the hour (00..59) | | S | Second of the minute (00..60) | | L | Millisecond of the second (000..999). Digits under millisecond are truncated to not produce 1000 | | N | Fractional seconds digits, default is 9 digits (nanosecond). Digits under a specified width are truncated to avoid carry up | **Time (Hour, Minute, Second, Subsecond):** | Format | Meaning | | ------ | ------- | | z | Time zone as hour and minute offset from UTC (e.g. +0900) | | :z | hour and minute offset from UTC with a colon (e.g. +09:00) | | ::z | hour, minute and second offset from UTC (e.g. +09:00:00) | | Z | Abbreviated time zone name or similar information. (OS dependent) | **Weekday:** | Format | Meaning | | ------ | ------- | | A | The full weekday name ("Sunday") | | a | The abbreviated name ("Sun") | | u | Day of the week (Monday is 1, 1..7) | | w | Day of the week (Sunday is 0, 0..6) | **ISO 8601 week-based year and week number:** The first week of YYYY starts with a Monday and includes YYYY-01-04. The days in the year before the first week are in the last week of the previous year. | Format | Meaning | | ------ | ------- | | G | The week-based year | | g | The last 2 digits of the week-based year (00..99) | | V | Week number of the week-based year (01..53) | **Week number:** The first week of YYYY that starts with a Sunday or Monday (according to %U or %W). The days in the year before the first week are in week 0. | Format | Meaning | | ------ | ------- | | U | Week number of the year. The week starts with Sunday. (00..53) | | W | Week number of the year. The week starts with Monday. (00..53) | **Seconds since the Epoch:** | Format | Meaning | | s | Number of seconds since 1970-01-01 00:00:00 UTC. | **Literal string:** | Format | Meaning | | ------ | ------- | | n | Newline character (\n) | | t | Tab character (\t) | | % | Literal "%" character | **Combination:** | Format | Meaning | | ------ | ------- | | c | date and time (%a %b %e %T %Y) | | D | Date (%m/%d/%y) | | F | The ISO 8601 date format (%Y-%m-%d) | | v | VMS date (%e-%^b-%4Y) | | x | Same as %D | | X | Same as %T | | r | 12-hour time (%I:%M:%S %p) | | R | 24-hour time (%H:%M) | | T | 24-hour time (%H:%M:%S) | The default array contains the following patterns: When a timezone argument (other than `default`) is explicitly provided: ``` ['%FT%T.L', '%FT%T', '%F'] ``` otherwise: ``` ['%FT%T.%L %Z', '%FT%T %Z', '%F %Z', '%FT%T.L', '%FT%T', '%F'] ``` Examples - Converting to Timestamp ```puppet $ts = Timestamp(1473150899) # 2016-09-06 08:34:59 UTC $ts = Timestamp({string=>'2015', format=>'%Y'}) # 2015-01-01 00:00:00.000 UTC $ts = Timestamp('Wed Aug 24 12:13:14 2016', '%c') # 2016-08-24 12:13:14 UTC $ts = Timestamp('Wed Aug 24 12:13:14 2016 PDT', '%c %Z') # 2016-08-24 19:13:14.000 UTC $ts = Timestamp('2016-08-24 12:13:14', '%F %T', 'PST') # 2016-08-24 20:13:14.000 UTC $ts = Timestamp('2016-08-24T12:13:14', default, 'PST') # 2016-08-24 20:13:14.000 UTC ``` ### Conversion to Type A new `Type` can be create from its `String` representation. **Example:** Creating a type from a string ```puppet $t = Type.new('Integer[10]') ``` ### Conversion to String Conversion to `String` is the most comprehensive conversion as there are many use cases where a string representation is wanted. The defaults for the many options have been chosen with care to be the most basic "value in textual form" representation. The more advanced forms of formatting are intended to enable writing special purposes formatting functions in the Puppet language. A new string can be created from all other data types. The process is performed in several steps - first the data type of the given value is inferred, then the resulting data type is used to find the most significant format specified for that data type. And finally, the found format is used to convert the given value. The mapping from data type to format is referred to as the *format map*. This map allows different formatting depending on type. **Example:** Positive Integers in Hexadecimal prefixed with '0x', negative in Decimal ```puppet $format_map = { Integer[default, 0] => "%d", Integer[1, default] => "%#x" } String("-1", $format_map) # produces '-1' String("10", $format_map) # produces '0xa' ``` A format is specified on the form: ``` %[Flags][Width][.Precision]Format ``` `Width` is the number of characters into which the value should be fitted. This allocated space is padded if value is shorter. By default it is space padded, and the flag `0` will cause padding with `0` for numerical formats. `Precision` is the number of fractional digits to show for floating point, and the maximum characters included in a string format. Note that all data type supports the formats `s` and `p` with the meaning "default string representation" and "default programmatic string representation" (which for example means that a String is quoted in 'p' format). #### Signatures of String conversion ```puppet type Format = Pattern[/^%([\s\+\-#0\[\{<\(\|]*)([1-9][0-9]*)?(?:\.([0-9]+))?([a-zA-Z])/] type ContainerFormat = Struct[{ format => Optional[String], separator => Optional[String], separator2 => Optional[String], string_formats => Hash[Type, Format] }] type TypeMap = Hash[Type, Variant[Format, ContainerFormat]] type Formats = Variant[Default, String[1], TypeMap] function String.new( Any $value, Formats $string_formats ) ``` Where: * `separator` is the string used to separate entries in an array, or hash (extra space should not be included at the end), defaults to `","` * `separator2` is the separator between key and value in a hash entry (space padding should be included as wanted), defaults to `" => "`. * `string_formats` is a data type to format map for values contained in arrays and hashes - defaults to `{Any => "%p"}`. Note that these nested formats are not applicable to data types that are containers; they are always formatted as per the top level format specification. **Example:** Simple Conversion to String (using defaults) ```puppet $str = String(10) # produces '10' $str = String([10]) # produces '["10"]' ``` **Example:** Simple Conversion to String specifying the format for the given value directly ```puppet $str = String(10, "%#x") # produces '0x10' $str = String([10], "%(a") # produces '("10")' ``` **Example:** Specifying type for values contained in an array ```puppet $formats = { Array => { format => '%(a', string_formats => { Integer => '%#x' } } } $str = String([1,2,3], $formats) # produces '(0x1, 0x2, 0x3)' ``` The given formats are merged with the default formats, and matching of values to convert against format is based on the specificity of the mapped type; for example, different formats can be used for short and long arrays. #### Integer to String | Format | Integer Formats | ------ | --------------- | d | Decimal, negative values produces leading '-'. | x X | Hexadecimal in lower or upper case. Uses ..f/..F for negative values unless + is also used. A `#` adds prefix 0x/0X. | o | Octal. Uses ..0 for negative values unless `+` is also used. A `#` adds prefix 0. | b B | Binary with prefix 'b' or 'B'. Uses ..1/..1 for negative values unless `+` is also used. | c | Numeric value representing a Unicode value, result is a one unicode character string, quoted if alternative flag # is used | s | Same as d, or d in quotes if alternative flag # is used. | p | Same as d. | eEfgGaA | Converts integer to float and formats using the floating point rules. Defaults to `d`. #### Float to String | Format | Float formats | ------ | ------------- | f | Floating point in non exponential notation. | e E | Exponential notation with 'e' or 'E'. | g G | Conditional exponential with 'e' or 'E' if exponent < -4 or >= the precision. | a A | Hexadecimal exponential form, using 'x'/'X' as prefix and 'p'/'P' before exponent. | s | Converted to string using format p, then applying string formatting rule, alternate form # quotes result. | p | Same as f format with minimum significant number of fractional digits, prec has no effect. | dxXobBc | Converts float to integer and formats using the integer rules. Defaults to `p`. #### String to String | Format | String | ------ | ------ | s | Unquoted string, verbatim output of control chars. | p | Programmatic representation - strings are quoted, interior quotes and control chars are escaped. | C | Each `::` name segment capitalized, quoted if alternative flag `#` is used. | c | Capitalized string, quoted if alternative flag `#` is used. | d | Downcased string, quoted if alternative flag `#` is used. | u | Upcased string, quoted if alternative flag `#` is used. | t | Trims leading and trailing whitespace from the string, quoted if alternative flag `#` is used. Defaults to `s` at top level and `p` inside array or hash. #### Boolean to String | Format | Boolean Formats | ---- | ------------------- | t T | String 'true'/'false' or 'True'/'False', first char if alternate form is used (i.e. 't'/'f' or 'T'/'F'). | y Y | String 'yes'/'no', 'Yes'/'No', 'y'/'n' or 'Y'/'N' if alternative flag `#` is used. | dxXobB | Numeric value 0/1 in accordance with the given format which must be valid integer format. | eEfgGaA | Numeric value 0.0/1.0 in accordance with the given float format and flags. | s | String 'true' / 'false'. | p | String 'true' / 'false'. #### Regexp to String | Format | Regexp Formats | ---- | -------------- | s | No delimiters, quoted if alternative flag `#` is used. | p | Delimiters `/ /`. #### Undef to String | Format | Undef formats | ------ | ------------- | s | Empty string, or quoted empty string if alternative flag `#` is used. | p | String 'undef', or quoted '"undef"' if alternative flag `#` is used. | n | String 'nil', or 'null' if alternative flag `#` is used. | dxXobB | String 'NaN'. | eEfgGaA | String 'NaN'. | v | String 'n/a'. | V | String 'N/A'. | u | String 'undef', or 'undefined' if alternative `#` flag is used. #### Default value to String | Format | Default formats | ------ | --------------- | d D | String 'default' or 'Default', alternative form `#` causes value to be quoted. | s | Same as d. | p | Same as d. #### Binary value to String | Format | Default formats | ------ | --------------- | s | binary as unquoted UTF-8 characters (errors if byte sequence is invalid UTF-8). Alternate form escapes non ascii bytes. | p | 'Binary("<base64strict>")' | b | '<base64>' - base64 string with newlines inserted | B | '<base64strict>' - base64 strict string (without newlines inserted) | u | '<base64urlsafe>' - base64 urlsafe string | t | 'Binary' - outputs the name of the type only | T | 'BINARY' - output the name of the type in all caps only * The alternate form flag `#` will quote the binary or base64 text output. * The format `%#s` allows invalid UTF-8 characters and outputs all non ascii bytes as hex escaped characters on the form `\\xHH` where `H` is a hex digit. * The width and precision values are applied to the text part only in `%p` format. #### Array & Tuple to String | Format | Array/Tuple Formats | ------ | ------------- | a | Formats with `[ ]` delimiters and `,`, alternate form `#` indents nested arrays/hashes. | s | Same as a. | p | Same as a. See "Flags" `<[({\|` for formatting of delimiters, and "Additional parameters for containers; Array and Hash" for more information about options. The alternate form flag `#` will cause indentation of nested array or hash containers. If width is also set it is taken as the maximum allowed length of a sequence of elements (not including delimiters). If this max length is exceeded, each element will be indented. #### Hash & Struct to String | Format | Hash/Struct Formats | ------ | ------------- | h | Formats with `{ }` delimiters, `,` element separator and ` => ` inner element separator unless overridden by flags. | s | Same as h. | p | Same as h. | a | Converts the hash to an array of [k,v] tuples and formats it using array rule(s). See "Flags" `<[({\|` for formatting of delimiters, and "Additional parameters for containers; Array and Hash" for more information about options. The alternate form flag `#` will format each hash key/value entry indented on a separate line. #### Type to String | Format | Array/Tuple Formats | ------ | ------------- | s | The same as `p`, quoted if alternative flag `#` is used. | p | Outputs the type in string form as specified by the Puppet Language. #### Flags | Flag | Effect | ------ | ------ | (space) | A space instead of `+` for numeric output (`-` is shown), for containers skips delimiters. | # | Alternate format; prefix 0x/0x, 0 (octal) and 0b/0B for binary, Floats force decimal '.'. For g/G keep trailing 0. | + | Show sign +/- depending on value's sign, changes x, X, o, b, B format to not use 2's complement form. | - | Left justify the value in the given width. | 0 | Pad with 0 instead of space for widths larger than value. | <[({\| | Defines an enclosing pair <> [] () {} or \| \| when used with a container type. ### Conversion to Boolean Accepts a single value as argument: * Float 0.0 is `false`, all other float values are `true` * Integer 0 is `false`, all other integer values are `true` * Strings * `true` if 'true', 'yes', 'y' (case independent compare) * `false` if 'false', 'no', 'n' (case independent compare) * Boolean is already boolean and is simply returned ### Conversion to Array and Tuple When given a single value as argument: * A non empty `Hash` is converted to an array matching `Array[Tuple[Any,Any], 1]`. * An empty `Hash` becomes an empty array. * An `Array` is simply returned. * An `Iterable[T]` is turned into an array of `T` instances. * A `Binary` is converted to an `Array[Integer[0,255]]` of byte values When given a second Boolean argument: * if `true`, a value that is not already an array is returned as a one element array. * if `false`, (the default), converts the first argument as shown above. **Example:** Ensuring value is an array ```puppet $arr = Array($value, true) ``` Conversion to a `Tuple` works exactly as conversion to an `Array`, only that the constructed array is asserted against the given tuple type. ### Conversion to Hash and Struct Accepts a single value as argument: * An empty `Array` becomes an empty `Hash` * An `Array` matching `Array[Tuple[Any,Any], 1]` is converted to a hash where each tuple describes a key/value entry * An `Array` with an even number of entries is interpreted as `[key1, val1, key2, val2, ...]` * An `Iterable` is turned into an `Array` and then converted to hash as per the array rules * A `Hash` is simply returned Alternatively, a tree can be constructed by giving two values; an array of tuples on the form `[path, value]` (where the `path` is the path from the root of a tree, and `value` the value at that position in the tree), and either the option `'tree'` (do not convert arrays to hashes except the top level), or `'hash_tree'` (convert all arrays to hashes). The tree/hash_tree forms of Hash creation are suited for transforming the result of an iteration using `tree_each` and subsequent filtering or mapping. **Example:** Mapping a hash tree Mapping an arbitrary structure in a way that keeps the structure, but where some values are replaced can be done by using the `tree_each` function, mapping, and then constructing a new Hash from the result: ```puppet # A hash tree with 'water' at different locations $h = { a => { b => { x => 'water'}}, b => { y => 'water'} } # a helper function that turns water into wine function make_wine($x) { if $x == 'water' { 'wine' } else { $x } } # create a flattened tree with water turned into wine $flat_tree = $h.tree_each.map |$entry| { [$entry[0], make_wine($entry[1])] } # create a new Hash and log it notice Hash($flat_tree, 'hash_tree') ``` Would notice the hash `{a => {b => {x => wine}}, b => {y => wine}}` Conversion to a `Struct` works exactly as conversion to a `Hash`, only that the constructed hash is asserted against the given struct type. ### Conversion to a Regexp A `String` can be converted into a `Regexp` **Example**: Converting a String into a Regexp ```puppet $s = '[a-z]+\.com' $r = Regexp($s) if('foo.com' =~ $r) { ... } ``` ### Creating a SemVer A SemVer object represents a single [Semantic Version](http://semver.org/). It can be created from a String, individual values for its parts, or a hash specifying the value per part. See the specification at [semver.org](http://semver.org/) for the meaning of the SemVer's parts. The signatures are: ```puppet type PositiveInteger = Integer[0,default] type SemVerQualifier = Pattern[/\A(?<part>[0-9A-Za-z-]+)(?:\.\g<part>)*\Z/] type SemVerString = String[1] type SemVerHash =Struct[{ major => PositiveInteger, minor => PositiveInteger, patch => PositiveInteger, Optional[prerelease] => SemVerQualifier, Optional[build] => SemVerQualifier }] function SemVer.new(SemVerString $str) function SemVer.new( PositiveInteger $major PositiveInteger $minor PositiveInteger $patch Optional[SemVerQualifier] $prerelease = undef Optional[SemVerQualifier] $build = undef ) function SemVer.new(SemVerHash $hash_args) ``` **Examples:** SemVer and SemVerRange usage ```puppet # As a type, SemVer can describe disjunct ranges which versions can be # matched against - here the type is constructed with two # SemVerRange objects. # $t = SemVer[ SemVerRange('>=1.0.0 <2.0.0'), SemVerRange('>=3.0.0 <4.0.0') ] notice(SemVer('1.2.3') =~ $t) # true notice(SemVer('2.3.4') =~ $t) # false notice(SemVer('3.4.5') =~ $t) # true ``` ### Creating a SemVerRange A `SemVerRange` object represents a range of `SemVer`. It can be created from a `String`, or from two `SemVer` instances, where either end can be given as a literal `default` to indicate infinity. The string format of a `SemVerRange` is specified by the [Semantic Version Range Grammar](https://github.com/npm/node-semver#ranges). > Use of the comparator sets described in the grammar (joining with `||`) is not supported. The signatures are: ```puppet type SemVerRangeString = String[1] type SemVerRangeHash = Struct[{ min => Variant[Default, SemVer], Optional[max] => Variant[Default, SemVer], Optional[exclude_max] => Boolean }] function SemVerRange.new( SemVerRangeString $semver_range_string ) function SemVerRange.new( Variant[Default,SemVer] $min Variant[Default,SemVer] $max Optional[Boolean] $exclude_max = undef ) function SemVerRange.new( SemVerRangeHash $semver_range_hash ) ``` For examples of `SemVerRange` use, see "Creating a SemVer". ### Creating a Binary A `Binary` object represents a sequence of bytes and it can be created from a String in Base64 format, an Array containing byte values. A Binary can also be created from a Hash containing the value to convert to a `Binary`. The signatures are: ```puppet type ByteInteger = Integer[0,255] type Base64Format = Enum["%b", "%u", "%B", "%s"] type StringHash = Struct[{value => String, "format" => Optional[Base64Format]}] type ArrayHash = Struct[{value => Array[ByteInteger]}] type BinaryArgsHash = Variant[StringHash, ArrayHash] function Binary.new( String $base64_str, Optional[Base64Format] $format ) function Binary.new( Array[ByteInteger] $byte_array } # Same as for String, or for Array, but where arguments are given in a Hash. function Binary.new(BinaryArgsHash $hash_args) ``` The formats have the following meaning: | format | explanation | | ---- | ---- | | B | The data is in base64 strict encoding | u | The data is in URL safe base64 encoding | b | The data is in base64 encoding, padding as required by base64 strict, is added by default | s | The data is a puppet string. The string must be valid UTF-8, or convertible to UTF-8 or an error is raised. | r | (Ruby Raw) the byte sequence in the given string is used verbatim irrespective of possible encoding errors * The default format is `%B`. * Note that the format `%r` should be used sparingly, or not at all. It exists for backwards compatibility reasons when someone receiving a string from some function and that string should be treated as Binary. Such code should be changed to return a Binary instead of a String. **Examples:** Creating a Binary ```puppet # create the binary content "abc" $a = Binary('YWJj') # create the binary content from content in a module's file $b = binary_file('mymodule/mypicture.jpg') ``` * Since 4.5.0 * Binary type since 4.8.0 Creating an instance of a `Type` using the `Init` type. ------- The type `Init[T]` describes a value that can be used when instantiating a type. When used as the first argument in a call to `new`, it will dispatch the call to its contained type and optionally augment the parameter list with additional arguments. **Example:** Creating an instance of Integer using Init[Integer] ```puppet # The following declaration $x = Init[Integer].new('128') # is exactly the same as $x = Integer.new('128') ``` or, with base 16 and using implicit new ```puppet # The following declaration $x = Init[Integer,16]('80') # is exactly the same as $x = Integer('80', 16) ``` **Example:** Creating an instance of String using a predefined format ```puppet $fmt = Init[String,'%#x'] notice($fmt(256)) # will notice '0x100' ``` DOC ) do |args| Puppet::Parser::Functions::Error.is4x('new') end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/next.rb���������������������������������������������������0000644�0052762�0001160�00000002465�13417161721�021713� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction( :next, :arity => -2, :doc => <<-DOC Immediately returns the given optional value from a block (lambda), function, class body or user defined type body. If a value is not given, an `undef` value is returned. This function does not return to the immediate caller. The signal produced to return a value bubbles up through the call stack until reaching a code block (lambda), function, class definition or definition of a user defined type at which point the value given to the function will be produced as the result of that body of code. An error is raised if the signal to return a value reaches the end of the call stack. **Example:** Using `next` in `each` ```puppet $data = [1,2,3] $data.each |$x| { if $x == 2 { next() } notice $x } ``` Would notice the values `1` and `3` **Example:** Using `next` to produce a value If logic consists of deeply nested conditionals it may be complicated to get out of the innermost conditional. A call to `next` can then simplify the logic. This example however, only shows the principle. ```puppet $data = [1,2,3] notice $data.map |$x| { if $x == 2 { next($x*100) }; $x*10 } ``` Would notice the value `[10, 200, 30]` * Also see functions `return` and `break` * Since 4.8.0 DOC ) do |args| Puppet::Parser::Functions::Error.is4x('next') end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/realize.rb������������������������������������������������0000644�0052762�0001160�00000001554�13417161721�022366� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This is just syntactic sugar for a collection, although it will generally # be a good bit faster. Puppet::Parser::Functions::newfunction(:realize, :arity => -2, :doc => "Make a virtual object real. This is useful when you want to know the name of the virtual object and don't want to bother with a full collection. It is slightly faster than a collection, and, of course, is a bit shorter. You must pass the object using a reference; e.g.: `realize User[luke]`." ) do |vals| if Puppet[:tasks] raise Puppet::ParseErrorWithIssue.from_issue_and_stack( Puppet::Pops::Issues::CATALOG_OPERATION_NOT_SUPPORTED_WHEN_SCRIPTING, {:operation => 'realize'}) end vals = [vals] unless vals.is_a?(Array) coll = Puppet::Pops::Evaluator::Collectors::FixedSetCollector.new(self, vals.flatten) compiler.add_collection(coll) end ����������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/reduce.rb�������������������������������������������������0000644�0052762�0001160�00000011043�13417161721�022174� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction( :reduce, :type => :rvalue, :arity => -3, :doc => <<-DOC Applies a [lambda](https://puppet.com/docs/puppet/latest/lang_lambdas.html) to every value in a data structure from the first argument, carrying over the returned value of each iteration, and returns the result of the lambda's final iteration. This lets you create a new value or data structure by combining values from the first argument's data structure. This function takes two mandatory arguments, in this order: 1. An array or hash the function will iterate over. 2. A lambda, which the function calls for each element in the first argument. It takes two mandatory parameters: 1. A memo value that is overwritten after each iteration with the iteration's result. 2. A second value that is overwritten after each iteration with the next value in the function's first argument. **Example**: Using the `reduce` function `$data.reduce |$memo, $value| { ... }` or `reduce($data) |$memo, $value| { ... }` You can also pass an optional "start memo" value as an argument, such as `start` below: `$data.reduce(start) |$memo, $value| { ... }` or `reduce($data, start) |$memo, $value| { ... }` When the first argument (`$data` in the above example) is an array, Puppet passes each of the data structure's values in turn to the lambda's parameters. When the first argument is a hash, Puppet converts each of the hash's values to an array in the form `[key, value]`. If you pass a start memo value, Puppet executes the lambda with the provided memo value and the data structure's first value. Otherwise, Puppet passes the structure's first two values to the lambda. Puppet calls the lambda for each of the data structure's remaining values. For each call, it passes the result of the previous call as the first parameter ($memo in the above examples) and the next value from the data structure as the second parameter ($value). If the structure has one value, Puppet returns the value and does not call the lambda. **Example**: Using the `reduce` function ~~~ puppet # Reduce the array $data, returning the sum of all values in the array. $data = [1, 2, 3] $sum = $data.reduce |$memo, $value| { $memo + $value } # $sum contains 6 # Reduce the array $data, returning the sum of a start memo value and all values in the # array. $data = [1, 2, 3] $sum = $data.reduce(4) |$memo, $value| { $memo + $value } # $sum contains 10 # Reduce the hash $data, returning the sum of all values and concatenated string of all # keys. $data = {a => 1, b => 2, c => 3} $combine = $data.reduce |$memo, $value| { $string = "${memo[0]}${value[0]}" $number = $memo[1] + $value[1] [$string, $number] } # $combine contains [abc, 6] ~~~ **Example**: Using the `reduce` function with a start memo and two-parameter lambda ~~~ puppet # Reduce the array $data, returning the sum of all values in the array and starting # with $memo set to an arbitrary value instead of $data's first value. $data = [1, 2, 3] $sum = $data.reduce(4) |$memo, $value| { $memo + $value } # At the start of the lambda's first iteration, $memo contains 4 and $value contains 1. # After all iterations, $sum contains 10. # Reduce the hash $data, returning the sum of all values and concatenated string of # all keys, and starting with $memo set to an arbitrary array instead of $data's first # key-value pair. $data = {a => 1, b => 2, c => 3} $combine = $data.reduce( [d, 4] ) |$memo, $value| { $string = "${memo[0]}${value[0]}" $number = $memo[1] + $value[1] [$string, $number] } # At the start of the lambda's first iteration, $memo contains [d, 4] and $value # contains [a, 1]. # $combine contains [dabc, 10] ~~~ **Example**: Using the `reduce` function to reduce a hash of hashes ~~~ puppet # Reduce a hash of hashes $data, merging defaults into the inner hashes. $data = { 'connection1' => { 'username' => 'user1', 'password' => 'pass1', }, 'connection_name2' => { 'username' => 'user2', 'password' => 'pass2', }, } $defaults = { 'maxActive' => '20', 'maxWait' => '10000', 'username' => 'defaultuser', 'password' => 'defaultpass', } $merged = $data.reduce( {} ) |$memo, $x| { $memo + { $x[0] => $defaults + $data[$x[0]] } } # At the start of the lambda's first iteration, $memo is set to {}, and $x is set to # the first [key, value] tuple. The key in $data is, therefore, given by $x[0]. In # subsequent rounds, $memo retains the value returned by the expression, i.e. # $memo + { $x[0] => $defaults + $data[$x[0]] }. ~~~ - Since 4.0.0 DOC ) do |args| Puppet::Parser::Functions::Error.is4x('reduce') end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/regsubst.rb�����������������������������������������������0000644�0052762�0001160�00000005600�13417161721�022565� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (C) 2009 Thomas Bellman # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THOMAS BELLMAN BE LIABLE FOR ANY CLAIM, DAMAGES OR # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. # # Except as contained in this notice, the name of Thomas Bellman shall # not be used in advertising or otherwise to promote the sale, use or # other dealings in this Software without prior written authorization # from Thomas Bellman. module Puppet::Parser::Functions newfunction( :regsubst, :type => :rvalue, :arity => -4, :doc => " Perform regexp replacement on a string or array of strings. * *Parameters* (in order): * _target_ The string or array of strings to operate on. If an array, the replacement will be performed on each of the elements in the array, and the return value will be an array. * _regexp_ The regular expression matching the target string. If you want it anchored at the start and or end of the string, you must do that with ^ and $ yourself. * _replacement_ Replacement string. Can contain backreferences to what was matched using \\0 (whole match), \\1 (first set of parentheses), and so on. * _flags_ Optional. String of single letter flags for how the regexp is interpreted: - *E* Extended regexps - *I* Ignore case in regexps - *M* Multiline regexps - *G* Global replacement; all occurrences of the regexp in each target string will be replaced. Without this, only the first occurrence will be replaced. * _encoding_ Optional. How to handle multibyte characters. A single-character string with the following values: - *N* None - *E* EUC - *S* SJIS - *U* UTF-8 * *Examples* Get the third octet from the node's IP address: $i3 = regsubst($ipaddress,'^(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+)$','\\3') Put angle brackets around each octet in the node's IP address: $x = regsubst($ipaddress, '([0-9]+)', '<\\1>', 'G') ") do |args| Error.is4x('regsubst') end end ��������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/require.rb������������������������������������������������0000644�0052762�0001160�00000003154�13417161721�022405� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Requires the specified classes Puppet::Parser::Functions::newfunction( :require, :arity => -2, :doc =>"Evaluate one or more classes, adding the required class as a dependency. The relationship metaparameters work well for specifying relationships between individual resources, but they can be clumsy for specifying relationships between classes. This function is a superset of the 'include' function, adding a class relationship so that the requiring class depends on the required class. Warning: using require in place of include can lead to unwanted dependency cycles. For instance the following manifest, with 'require' instead of 'include' would produce a nasty dependence cycle, because notify imposes a before between File[/foo] and Service[foo]: class myservice { service { foo: ensure => running } } class otherstuff { include myservice file { '/foo': notify => Service[foo] } } Note that this function only works with clients 0.25 and later, and it will fail if used with earlier clients. You must use the class's full name; relative names are not allowed. In addition to names in string form, you may also directly use Class and Resource Type values that are produced when evaluating resource and relationship expressions. - Since 4.0.0 Class and Resource types, absolute names - Since 4.7.0 Returns an Array[Type[Class]] with references to the required classes ") do |classes| call_function('require', classes) Puppet.warn_once('deprecations', '3xfunction#require', _("Calling function_require via the Scope class is deprecated. Use Scope#call_function instead")) end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/return.rb�������������������������������������������������0000644�0052762�0001160�00000005067�13417161721�022255� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction( :return, :arity => -2, :doc => <<-DOC Immediately returns the given optional value from a function, class body or user defined type body. If a value is not given, an `undef` value is returned. This function does not return to the immediate caller. If this function is called from within a lambda, the return action is from the scope of the function containing the lambda (top scope), not the function accepting the lambda (local scope). The signal produced to return a value bubbles up through the call stack until reaching a function, class definition or definition of a user defined type at which point the value given to the function will be produced as the result of that body of code. An error is raised if the signal to return a value reaches the end of the call stack. **Example:** Using `return` ```puppet function example($x) { # handle trivial cases first for better readability of # what follows if $x == undef or $x == [] or $x == '' { return false } # complex logic to determine if value is true true } notice example([]) # would notice false notice example(42) # would notice true ``` **Example:** Using `return` in a class ```puppet class example($x) { # handle trivial cases first for better readability of # what follows if $x == undef or $x == [] or $x == '' { # Do some default configuration of this class notice 'foo' return() } # complex logic configuring the class if something more interesting # was given in $x notice 'bar' } ``` When used like this: ```puppet class { example: x => [] } ``` The code would notice `'foo'`, but not `'bar'`. When used like this: ```puppet class { example: x => [some_value] } ``` The code would notice `'bar'` but not `'foo'` Note that the returned value is ignored if used in a class or user defined type. **Example:** Using `return` in a lambda ```puppet # Concatenate three strings into a single string formatted as a list. function getFruit() { with("apples", "oranges", "bananas") |$x, $y, $z| { return("${x}, ${y}, and ${z}") } notice "not reached" } $fruit = getFruit() notice $fruit # The output contains "apples, oranges, and bananas". # "not reached" is not output because the function returns its value within the # calling function's scope, which stops processing the calling function before # the `notice "not reached"` statement. # Using `return()` outside of a calling function results in an error. ``` * Also see functions `return` and `break` * Since 4.8.0 DOC ) do |args| Puppet::Parser::Functions::Error.is4x('return') end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/reverse_each.rb�������������������������������������������0000644�0052762�0001160�00000004367�13417161721�023373� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction( :reverse_each, :type => :rvalue, :arity => -1, :doc => <<-DOC Reverses the order of the elements of something that is iterable and optionally runs a [lambda](https://puppet.com/docs/puppet/latest/lang_lambdas.html) for each element. This function takes one to two arguments: 1. An `Iterable` that the function will iterate over. 2. An optional lambda, which the function calls for each element in the first argument. It must request one parameter. **Example:** Using the `reverse_each` function ```puppet $data.reverse_each |$parameter| { <PUPPET CODE BLOCK> } ``` or ```puppet $reverse_data = $data.reverse_each ``` or ```puppet reverse_each($data) |$parameter| { <PUPPET CODE BLOCK> } ``` or ```puppet $reverse_data = reverse_each($data) ``` When no second argument is present, Puppet returns an `Iterable` that represents the reverse order of its first argument. This allows methods on `Iterable` to be chained. When a lambda is given as the second argument, Puppet iterates the first argument in reverse order and passes each value in turn to the lambda, then returns `undef`. **Example:** Using the `reverse_each` function with an array and a one-parameter lambda ``` puppet # Puppet will log a notice for each of the three items # in $data in reverse order. $data = [1,2,3] $data.reverse_each |$item| { notice($item) } ``` When no second argument is present, Puppet returns a new `Iterable` which allows it to be directly chained into another function that takes an `Iterable` as an argument. **Example:** Using the `reverse_each` function chained with a `map` function. ```puppet # For the array $data, return an array containing each # value multiplied by 10 in reverse order $data = [1,2,3] $transformed_data = $data.reverse_each.map |$item| { $item * 10 } # $transformed_data is set to [30,20,10] ``` **Example:** Using `reverse_each` function chained with a `map` in alternative syntax ```puppet # For the array $data, return an array containing each # value multiplied by 10 in reverse order $data = [1,2,3] $transformed_data = map(reverse_each($data)) |$item| { $item * 10 } # $transformed_data is set to [30,20,10] ``` * Since 4.4.0 DOC ) do |args| Puppet::Parser::Functions::Error.is4x('reverse_each') end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/scanf.rb��������������������������������������������������0000644�0052762�0001160�00000002154�13417161721�022022� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'scanf' Puppet::Parser::Functions::newfunction( :scanf, :type => :rvalue, :arity => 2, :doc => <<-DOC Scans a string and returns an array of one or more converted values based on the given format string. See the documentation of Ruby's String#scanf method for details about the supported formats (which are similar but not identical to the formats used in Puppet's `sprintf` function.) This function takes two mandatory arguments: the first is the string to convert, and the second is the format string. The result of the scan is an array, with each successfully scanned and transformed value. The scanning stops if a scan is unsuccessful, and the scanned result up to that point is returned. If there was no successful scan, the result is an empty array. ```puppet "42".scanf("%i") ``` You can also optionally pass a lambda to scanf, to do additional validation or processing. ```puppet "42".scanf("%i") |$x| { unless $x[0] =~ Integer { fail "Expected a well formed integer value, got '$x[0]'" } $x[0] } ``` - Since 4.0.0 DOC ) do |args| data = args[0] format = args[1] data.scanf(format) end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/sha1.rb���������������������������������������������������0000644�0052762�0001160�00000000325�13417161721�021562� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'digest/sha1' Puppet::Parser::Functions::newfunction(:sha1, :type => :rvalue, :arity => 1, :doc => "Returns a SHA1 hash value from a provided string.") do |args| Digest::SHA1.hexdigest(args[0]) end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/sha256.rb�������������������������������������������������0000644�0052762�0001160�00000000327�13417161721�021740� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'digest/sha2' Puppet::Parser::Functions::newfunction(:sha256, :type => :rvalue, :arity => 1, :doc => "Returns a SHA256 hash value from a provided string.") do |args| Digest::SHA256.hexdigest(args[0]) end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/shellquote.rb���������������������������������������������0000644�0052762�0001160�00000004605�13417161721�023120� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (C) 2009 Thomas Bellman # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THOMAS BELLMAN BE LIABLE FOR ANY CLAIM, DAMAGES OR # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. # # Except as contained in this notice, the name of Thomas Bellman shall # not be used in advertising or otherwise to promote the sale, use or # other dealings in this Software without prior written authorization # from Thomas Bellman. Puppet::Parser::Functions.newfunction(:shellquote, :type => :rvalue, :arity => -1, :doc => "\ Quote and concatenate arguments for use in Bourne shell. Each argument is quoted separately, and then all are concatenated with spaces. If an argument is an array, the elements of that array is interpolated within the rest of the arguments; this makes it possible to have an array of arguments and pass that array to shellquote instead of having to specify each argument individually in the call. ") \ do |args| safe = 'a-zA-Z0-9@%_+=:,./-' # Safe unquoted dangerous = '!"`$\\' # Unsafe inside double quotes result = [] args.flatten.each do |word| if word.length != 0 and word.count(safe) == word.length result << word elsif word.count(dangerous) == 0 result << ('"' + word + '"') elsif word.count("'") == 0 result << ("'" + word + "'") else r = '"' word.each_byte do |c| r += "\\" if dangerous.include?(c.chr) r += c.chr end r += '"' result << r end end return result.join(" ") end ���������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/slice.rb��������������������������������������������������0000644�0052762�0001160�00000003024�13417161721�022024� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction( :slice, :type => :rvalue, :arity => -3, :doc => <<-DOC This function takes two mandatory arguments: the first should be an array or hash, and the second specifies the number of elements to include in each slice. When the first argument is a hash, each key value pair is counted as one. For example, a slice size of 2 will produce an array of two arrays with key, and value. $a.slice(2) |$entry| { notice "first ${$entry[0]}, second ${$entry[1]}" } $a.slice(2) |$first, $second| { notice "first ${first}, second ${second}" } The function produces a concatenated result of the slices. slice([1,2,3,4,5,6], 2) # produces [[1,2], [3,4], [5,6]] slice(Integer[1,6], 2) # produces [[1,2], [3,4], [5,6]] slice(4,2) # produces [[0,1], [2,3]] slice('hello',2) # produces [[h, e], [l, l], [o]] You can also optionally pass a lambda to slice. $a.slice($n) |$x| { ... } slice($a) |$x| { ... } The lambda should have either one parameter (receiving an array with the slice), or the same number of parameters as specified by the slice size (each parameter receiving its part of the slice). If there are fewer remaining elements than the slice size for the last slice, it will contain the remaining elements. If the lambda has multiple parameters, excess parameters are set to undef for an array, or to empty arrays for a hash. $a.slice(2) |$first, $second| { ... } - Since 4.0.0 DOC ) do |args| Puppet::Parser::Functions::Error.is4x('slice') end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/split.rb��������������������������������������������������0000644�0052762�0001160�00000001467�13417161721�022071� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Parser::Functions newfunction( :split, :type => :rvalue, :arity => 2, :doc => "\ Split a string variable into an array using the specified split regexp. *Example:* $string = 'v1.v2:v3.v4' $array_var1 = split($string, ':') $array_var2 = split($string, '[.]') $array_var3 = split($string, Regexp['[.:]']) `$array_var1` now holds the result `['v1.v2', 'v3.v4']`, while `$array_var2` holds `['v1', 'v2:v3', 'v4']`, and `$array_var3` holds `['v1', 'v2', 'v3', 'v4']`. Note that in the second example, we split on a literal string that contains a regexp meta-character (.), which must be escaped. A simple way to do that for a single character is to enclose it in square brackets; a backslash will also escape a single character.") do |args| Error.is4x('split') end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/sprintf.rb������������������������������������������������0000644�0052762�0001160�00000004702�13417161721�022416� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (C) 2009 Thomas Bellman # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THOMAS BELLMAN BE LIABLE FOR ANY CLAIM, DAMAGES OR # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. # # Except as contained in this notice, the name of Thomas Bellman shall # not be used in advertising or otherwise to promote the sale, use or # other dealings in this Software without prior written authorization # from Thomas Bellman. Puppet::Parser::Functions::newfunction( :sprintf, :type => :rvalue, :arity => -2, :doc => "Perform printf-style formatting of text. The first parameter is format string describing how the rest of the parameters should be formatted. See the documentation for the [`Kernel::sprintf` function](https://ruby-doc.org/core/Kernel.html) in Ruby for details. To use [named format](https://idiosyncratic-ruby.com/49-what-the-format.html) arguments, provide a hash containing the target string values as the argument to be formatted. For example: ```puppet notice sprintf(\"%<x>s : %<y>d\", { 'x' => 'value is', 'y' => 42 }) ``` This statement produces a notice of `value is : 42`." ) do |args| fmt = args[0] args = args[1..-1] begin return sprintf(fmt, *args) rescue KeyError => e if args.size == 1 && args[0].is_a?(Hash) # map the single hash argument such that all top level string keys are symbols # as that allows named arguments to be used in the format string. # result = {} args[0].each_pair { |k,v| result[k.is_a?(String) ? k.to_sym : k] = v } return sprintf(fmt, result) end raise e end end ��������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/step.rb���������������������������������������������������0000644�0052762�0001160�00000004622�13417161721�021705� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction( :step, :type => :rvalue, :arity => -1, :doc => <<-DOC Provides stepping with given interval over elements in an iterable and optionally runs a [lambda](https://puppet.com/docs/puppet/latest/lang_lambdas.html) for each element. This function takes two to three arguments: 1. An 'Iterable' that the function will iterate over. 2. An `Integer` step factor. This must be a positive integer. 3. An optional lambda, which the function calls for each element in the interval. It must request one parameter. **Example:** Using the `step` function ```puppet $data.step(<n>) |$parameter| { <PUPPET CODE BLOCK> } ``` or ```puppet $stepped_data = $data.step(<n>) ``` or ```puppet step($data, <n>) |$parameter| { <PUPPET CODE BLOCK> } ``` or ```puppet $stepped_data = step($data, <n>) ``` When no block is given, Puppet returns an `Iterable` that yields the first element and every nth successor element, from its first argument. This allows functions on iterables to be chained. When a block is given, Puppet iterates and calls the block with the first element and then with every nth successor element. It then returns `undef`. **Example:** Using the `step` function with an array, a step factor, and a one-parameter block ```puppet # For the array $data, call a block with the first element and then with each 3rd successor element $data = [1,2,3,4,5,6,7,8] $data.step(3) |$item| { notice($item) } # Puppet notices the values '1', '4', '7'. ``` When no block is given, Puppet returns a new `Iterable` which allows it to be directly chained into another function that takes an `Iterable` as an argument. **Example:** Using the `step` function chained with a `map` function. ```puppet # For the array $data, return an array, set to the first element and each 5th successor element, in reverse # order multiplied by 10 $data = Integer[0,20] $transformed_data = $data.step(5).map |$item| { $item * 10 } $transformed_data contains [0,50,100,150,200] ``` **Example:** The same example using `step` function chained with a `map` in alternative syntax ```puppet # For the array $data, return an array, set to the first and each 5th # successor, in reverse order, multiplied by 10 $data = Integer[0,20] $transformed_data = map(step($data, 5)) |$item| { $item * 10 } $transformed_data contains [0,50,100,150,200] ``` * Since 4.4.0 DOC ) do |args| Puppet::Parser::Functions::Error.is4x('step') end ��������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/strftime.rb�����������������������������������������������0000644�0052762�0001160�00000014546�13417161721�022575� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction( :strftime, :type => :rvalue, :arity => -3, :doc => <<DOC Formats timestamp or timespan according to the directives in the given format string. The directives begins with a percent (%) character. Any text not listed as a directive will be passed through to the output string. A third optional timezone argument can be provided. The first argument will then be formatted to represent a local time in that timezone. The timezone can be any timezone that is recognized when using the '%z' or '%Z' formats, or the word 'current', in which case the current timezone of the evaluating process will be used. The timezone argument is case insensitive. The default timezone, when no argument is provided, or when using the keyword `default`, is 'UTC'. The directive consists of a percent (%) character, zero or more flags, optional minimum field width and a conversion specifier as follows: ``` %[Flags][Width]Conversion ``` ### Flags that controls padding | Flag | Meaning | ---- | --------------- | - | Don't pad numerical output | _ | Use spaces for padding | 0 | Use zeros for padding ### `Timestamp` specific flags | Flag | Meaning | ---- | --------------- | # | Change case | ^ | Use uppercase | : | Use colons for %z ### Format directives applicable to `Timestamp` (names and padding can be altered using flags): **Date (Year, Month, Day):** | Format | Meaning | | ------ | ------- | | Y | Year with century, zero-padded to at least 4 digits | | C | year / 100 (rounded down such as 20 in 2009) | | y | year % 100 (00..99) | | m | Month of the year, zero-padded (01..12) | | B | The full month name ("January") | | b | The abbreviated month name ("Jan") | | h | Equivalent to %b | | d | Day of the month, zero-padded (01..31) | | e | Day of the month, blank-padded ( 1..31) | | j | Day of the year (001..366) | **Time (Hour, Minute, Second, Subsecond):** | Format | Meaning | | ------ | ------- | | H | Hour of the day, 24-hour clock, zero-padded (00..23) | | k | Hour of the day, 24-hour clock, blank-padded ( 0..23) | | I | Hour of the day, 12-hour clock, zero-padded (01..12) | | l | Hour of the day, 12-hour clock, blank-padded ( 1..12) | | P | Meridian indicator, lowercase ("am" or "pm") | | p | Meridian indicator, uppercase ("AM" or "PM") | | M | Minute of the hour (00..59) | | S | Second of the minute (00..60) | | L | Millisecond of the second (000..999). Digits under millisecond are truncated to not produce 1000 | | N | Fractional seconds digits, default is 9 digits (nanosecond). Digits under a specified width are truncated to avoid carry up | **Time (Hour, Minute, Second, Subsecond):** | Format | Meaning | | ------ | ------- | | z | Time zone as hour and minute offset from UTC (e.g. +0900) | | :z | hour and minute offset from UTC with a colon (e.g. +09:00) | | ::z | hour, minute and second offset from UTC (e.g. +09:00:00) | | Z | Abbreviated time zone name or similar information. (OS dependent) | **Weekday:** | Format | Meaning | | ------ | ------- | | A | The full weekday name ("Sunday") | | a | The abbreviated name ("Sun") | | u | Day of the week (Monday is 1, 1..7) | | w | Day of the week (Sunday is 0, 0..6) | **ISO 8601 week-based year and week number:** The first week of YYYY starts with a Monday and includes YYYY-01-04. The days in the year before the first week are in the last week of the previous year. | Format | Meaning | | ------ | ------- | | G | The week-based year | | g | The last 2 digits of the week-based year (00..99) | | V | Week number of the week-based year (01..53) | **Week number:** The first week of YYYY that starts with a Sunday or Monday (according to %U or %W). The days in the year before the first week are in week 0. | Format | Meaning | | ------ | ------- | | U | Week number of the year. The week starts with Sunday. (00..53) | | W | Week number of the year. The week starts with Monday. (00..53) | **Seconds since the Epoch:** | Format | Meaning | | s | Number of seconds since 1970-01-01 00:00:00 UTC. | **Literal string:** | Format | Meaning | | ------ | ------- | | n | Newline character (\n) | | t | Tab character (\t) | | % | Literal "%" character | **Combination:** | Format | Meaning | | ------ | ------- | | c | date and time (%a %b %e %T %Y) | | D | Date (%m/%d/%y) | | F | The ISO 8601 date format (%Y-%m-%d) | | v | VMS date (%e-%^b-%4Y) | | x | Same as %D | | X | Same as %T | | r | 12-hour time (%I:%M:%S %p) | | R | 24-hour time (%H:%M) | | T | 24-hour time (%H:%M:%S) | **Example**: Using `strftime` with a `Timestamp`: ~~~ puppet $timestamp = Timestamp('2016-08-24T12:13:14') # Notice the timestamp using a format that notices the ISO 8601 date format notice($timestamp.strftime('%F')) # outputs '2016-08-24' # Notice the timestamp using a format that notices weekday, month, day, time (as UTC), and year notice($timestamp.strftime('%c')) # outputs 'Wed Aug 24 12:13:14 2016' # Notice the timestamp using a specific timezone notice($timestamp.strftime('%F %T %z', 'PST')) # outputs '2016-08-24 04:13:14 -0800' # Notice the timestamp using timezone that is current for the evaluating process notice($timestamp.strftime('%F %T', 'current')) # outputs the timestamp using the timezone for the current process ~~~ ### Format directives applicable to `Timespan`: | Format | Meaning | | ------ | ------- | | D | Number of Days | | H | Hour of the day, 24-hour clock | | M | Minute of the hour (00..59) | | S | Second of the minute (00..59) | | L | Millisecond of the second (000..999). Digits under millisecond are truncated to not produce 1000. | | N | Fractional seconds digits, default is 9 digits (nanosecond). Digits under a specified length are truncated to avoid carry up | The format directive that represents the highest magnitude in the format will be allowed to overflow. I.e. if no "%D" is used but a "%H" is present, then the hours will be more than 23 in case the timespan reflects more than a day. **Example**: Using `strftime` with a Timespan and a format ~~~ puppet $duration = Timespan({ hours => 3, minutes => 20, seconds => 30 }) # Notice the duration using a format that outputs <hours>:<minutes>:<seconds> notice($duration.strftime('%H:%M:%S')) # outputs '03:20:30' # Notice the duration using a format that outputs <minutes>:<seconds> notice($duration.strftime('%M:%S')) # outputs '200:30' ~~~ - Since 4.8.0 DOC ) do |args| Puppet::Parser::Functions::Error.is4x('strftime') end ����������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/tag.rb����������������������������������������������������0000644�0052762�0001160�00000000745�13417161721�021507� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Tag the current scope with each passed name Puppet::Parser::Functions::newfunction(:tag, :arity => -2, :doc => "Add the specified tags to the containing class or definition. All contained objects will then acquire that tag, also. ") do |vals| if Puppet[:tasks] raise Puppet::ParseErrorWithIssue.from_issue_and_stack( Puppet::Pops::Issues::CATALOG_OPERATION_NOT_SUPPORTED_WHEN_SCRIPTING, {:operation => 'tag'}) end self.resource.tag(*vals) end ���������������������������puppet-5.5.10/lib/puppet/parser/functions/tagged.rb�������������������������������������������������0000644�0052762�0001160�00000001471�13417161721�022164� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Test whether a given tag is set. This functions as a big OR -- if any of the specified tags are unset, we return false. Puppet::Parser::Functions::newfunction(:tagged, :type => :rvalue, :arity => -2, :doc => "A boolean function that tells you whether the current container is tagged with the specified tags. The tags are ANDed, so that all of the specified tags must be included for the function to return true.") do |vals| if Puppet[:tasks] raise Puppet::ParseErrorWithIssue.from_issue_and_stack( Puppet::Pops::Issues::CATALOG_OPERATION_NOT_SUPPORTED_WHEN_SCRIPTING, {:operation => 'tagged'}) end retval = true vals.each do |val| unless compiler.catalog.tagged?(val) or resource.tagged?(val) retval = false break end end return retval end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/template.rb�����������������������������������������������0000644�0052762�0001160�00000003220�13417161721�022536� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction(:template, :type => :rvalue, :arity => -2, :doc => "Loads an ERB template from a module, evaluates it, and returns the resulting value as a string. The argument to this function should be a `<MODULE NAME>/<TEMPLATE FILE>` reference, which will load `<TEMPLATE FILE>` from a module's `templates` directory. (For example, the reference `apache/vhost.conf.erb` will load the file `<MODULES DIRECTORY>/apache/templates/vhost.conf.erb`.) This function can also accept: * An absolute path, which can load a template file from anywhere on disk. * Multiple arguments, which will evaluate all of the specified templates and return their outputs concatenated into a single string.") do |vals| if Puppet[:tasks] raise Puppet::ParseErrorWithIssue.from_issue_and_stack( Puppet::Pops::Issues::FEATURE_NOT_SUPPORTED_WHEN_SCRIPTING, {:feature => 'ERB template'}) end vals.collect do |file| # Use a wrapper, so the template can't get access to the full # Scope object. debug "Retrieving template #{file}" wrapper = Puppet::Parser::TemplateWrapper.new(self) wrapper.file = file begin wrapper.result rescue => detail info = detail.backtrace.first.split(':') message = [] message << _("Failed to parse template %{file}:") % { file: file } message << _(" Filepath: %{file_path}") % { file_path: info[0] } message << _(" Line: %{line}") % { line: info[1] } message << _(" Detail: %{detail}") % { detail: detail } raise Puppet::ParseError, message.join("\n") + "\n" end end.join("") end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/then.rb���������������������������������������������������0000644�0052762�0001160�00000004627�13417161721�021675� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction( :then, :type => :rvalue, :arity => -2, :doc => <<-DOC Call a [lambda](https://puppet.com/docs/puppet/latest/lang_lambdas.html) with the given argument unless the argument is undef. Return `undef` if argument is `undef`, and otherwise the result of giving the argument to the lambda. This is useful to process a sequence of operations where an intermediate result may be `undef` (which makes the entire sequence `undef`). The `then` function is especially useful with the function `dig` which performs in a similar way "digging out" a value in a complex structure. **Example:** Using `dig` and `then` ```puppet $data = {a => { b => [{x => 10, y => 20}, {x => 100, y => 200}]}} notice $data.dig(a, b, 1, x).then |$x| { $x * 2 } ``` Would notice the value 200 Contrast this with: ```puppet $data = {a => { b => [{x => 10, y => 20}, {ex => 100, why => 200}]}} notice $data.dig(a, b, 1, x).then |$x| { $x * 2 } ``` Which would notice `undef` since the last lookup of 'x' results in `undef` which is returned (without calling the lambda given to the `then` function). As a result there is no need for conditional logic or a temporary (non local) variable as the result is now either the wanted value (`x`) multiplied by 2 or `undef`. Calls to `then` can be chained. In the next example, a structure is using an offset based on using 1 as the index to the first element (instead of 0 which is used in the language). We are not sure if user input actually contains an index at all, or if it is outside the range of available names.args. **Example:** Chaining calls to the `then` function ```puppet # Names to choose from $names = ['Ringo', 'Paul', 'George', 'John'] # Structure where 'beatle 2' is wanted (but where the number refers # to 'Paul' because input comes from a source using 1 for the first # element). $data = ['singer', { beatle => 2 }] $picked = assert_type(String, # the data we are interested in is the second in the array, # a hash, where we want the value of the key 'beatle' $data.dig(1, 'beatle') # and we want the index in $names before the given index .then |$x| { $names[$x-1] } # so we can construct a string with that beatle's name .then |$x| { "Picked Beatle '${x}'" } ) ``` Would notice "Picked Beatle 'Paul'", and would raise an error if the result was not a String. * Since 4.5.0 DOC ) do |args| Puppet::Parser::Functions::Error.is4x('then') end ���������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/type.rb���������������������������������������������������0000644�0052762�0001160�00000002612�13417161721�021710� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction( :type, :type => :rvalue, :arity => -1, :doc => <<-DOC Returns the data type of a given value with a given degree of generality. ```puppet type InferenceFidelity = Enum[generalized, reduced, detailed] function type(Any $value, InferenceFidelity $fidelity = 'detailed') # returns Type ``` **Example:** Using `type` ``` puppet notice type(42) =~ Type[Integer] ``` Would notice `true`. By default, the best possible inference is made where all details are retained. This is good when the type is used for further type calculations but is overwhelmingly rich in information if it is used in a error message. The optional argument `$fidelity` may be given as (from lowest to highest fidelity): * `generalized` - reduces to common type and drops size constraints * `reduced` - reduces to common type in collections * `detailed` - (default) all details about inferred types is retained **Example:** Using `type()` with different inference fidelity: ``` puppet notice type([3.14, 42], 'generalized') notice type([3.14, 42], 'reduced'') notice type([3.14, 42], 'detailed') notice type([3.14, 42]) ``` Would notice the four values: 1. 'Array[Numeric]' 2. 'Array[Numeric, 2, 2]' 3. 'Tuple[Float[3.14], Integer[42,42]]]' 4. 'Tuple[Float[3.14], Integer[42,42]]]' * Since 4.4.0 DOC ) do |args| Puppet::Parser::Functions::Error.is4x('type') end ����������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/versioncmp.rb���������������������������������������������0000644�0052762�0001160�00000001223�13417161721�023111� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/package' Puppet::Parser::Functions::newfunction( :versioncmp, :type => :rvalue, :arity => 2, :doc => "Compares two version numbers. Prototype: \$result = versioncmp(a, b) Where a and b are arbitrary version strings. This function returns: * `1` if version a is greater than version b * `0` if the versions are equal * `-1` if version a is less than version b Example: if versioncmp('2.6-1', '2.4.5') > 0 { notice('2.6-1 is > than 2.4.5') } This function uses the same version comparison algorithm used by Puppet's `package` type. ") do |args| return Puppet::Util::Package.versioncmp(args[0], args[1]) end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/with.rb���������������������������������������������������0000644�0052762�0001160�00000001510�13417161721�021676� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction( :with, :type => :rvalue, :arity => -1, :doc => <<-DOC Call a [lambda](https://puppet.com/docs/puppet/latest/lang_lambdas.html) with the given arguments and return the result. Since a lambda's scope is local to the lambda, you can use the `with` function to create private blocks of code within a class using variables whose values cannot be accessed outside of the lambda. **Example**: Using `with` ~~~ puppet # Concatenate three strings into a single string formatted as a list. $fruit = with("apples", "oranges", "bananas") |$x, $y, $z| { "${x}, ${y}, and ${z}" } $check_var = $x # $fruit contains "apples, oranges, and bananas" # $check_var is undefined, as the value of $x is local to the lambda. ~~~ - Since 4.0.0 DOC ) do |args| Puppet::Parser::Functions::Error.is4x('with') end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/filter.rb�������������������������������������������������0000644�0052762�0001160�00000005556�13417161721�022226� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Parser::Functions::newfunction( :filter, :type => :rvalue, :arity => -3, :doc => <<-DOC Applies a [lambda](https://puppet.com/docs/puppet/latest/lang_lambdas.html) to every value in a data structure and returns an array or hash containing any elements for which the lambda evaluates to `true`. This function takes two mandatory arguments, in this order: 1. An array or hash the function will iterate over. 2. A lambda, which the function calls for each element in the first argument. It can request one or two parameters. **Example**: Using the `filter` function `$filtered_data = $data.filter |$parameter| { <PUPPET CODE BLOCK> }` or `$filtered_data = filter($data) |$parameter| { <PUPPET CODE BLOCK> }` When the first argument (`$data` in the above example) is an array, Puppet passes each value in turn to the lambda and returns an array containing the results. **Example**: Using the `filter` function with an array and a one-parameter lambda ~~~ puppet # For the array $data, return an array containing the values that end with "berry" $data = ["orange", "blueberry", "raspberry"] $filtered_data = $data.filter |$items| { $items =~ /berry$/ } # $filtered_data = [blueberry, raspberry] ~~~ When the first argument is a hash, Puppet passes each key and value pair to the lambda as an array in the form `[key, value]` and returns a hash containing the results. **Example**: Using the `filter` function with a hash and a one-parameter lambda ~~~ puppet # For the hash $data, return a hash containing all values of keys that end with "berry" $data = { "orange" => 0, "blueberry" => 1, "raspberry" => 2 } $filtered_data = $data.filter |$items| { $items[0] =~ /berry$/ } # $filtered_data = {blueberry => 1, raspberry => 2} When the first argument is an array and the lambda has two parameters, Puppet passes the array's indexes (enumerated from 0) in the first parameter and its values in the second parameter. **Example**: Using the `filter` function with an array and a two-parameter lambda ~~~ puppet # For the array $data, return an array of all keys that both end with "berry" and have # an even-numbered index $data = ["orange", "blueberry", "raspberry"] $filtered_data = $data.filter |$indexes, $values| { $indexes % 2 == 0 and $values =~ /berry$/ } # $filtered_data = [raspberry] ~~~ When the first argument is a hash, Puppet passes its keys to the first parameter and its values to the second parameter. **Example**: Using the `filter` function with a hash and a two-parameter lambda ~~~ puppet # For the hash $data, return a hash of all keys that both end with "berry" and have # values less than or equal to 1 $data = { "orange" => 0, "blueberry" => 1, "raspberry" => 2 } $filtered_data = $data.filter |$keys, $values| { $keys =~ /berry$/ and $values <= 1 } # $filtered_data = {blueberry => 1} ~~~ - Since 4.0.0 DOC ) do |args| Puppet::Parser::Functions::Error.is4x('filter') end ��������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions/generate.rb�����������������������������������������������0000644�0052762�0001160�00000003326�13417161721�022524� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Runs an external command and returns the results Puppet::Parser::Functions::newfunction(:generate, :arity => -2, :type => :rvalue, :doc => "Calls an external command on the Puppet master and returns the results of the command. Any arguments are passed to the external command as arguments. If the generator does not exit with return code of 0, the generator is considered to have failed and a parse error is thrown. Generators can only have file separators, alphanumerics, dashes, and periods in them. This function will attempt to protect you from malicious generator calls (e.g., those with '..' in them), but it can never be entirely safe. No subshell is used to execute generators, so all shell metacharacters are passed directly to the generator.") do |args| #TRANSLATORS "fully qualified" refers to a fully qualified file system path raise Puppet::ParseError, _("Generators must be fully qualified") unless Puppet::Util.absolute_path?(args[0]) if Puppet.features.microsoft_windows? valid = args[0] =~ /^[a-z]:(?:[\/\\][-.~\w]+)+$/i else valid = args[0] =~ /^[-\/\w.+]+$/ end unless valid raise Puppet::ParseError, _("Generators can only contain alphanumerics, file separators, and dashes") end if args[0] =~ /\.\./ raise Puppet::ParseError, _("Can not use generators with '..' in them.") end begin Dir.chdir(File.dirname(args[0])) { Puppet::Util::Execution.execute(args).to_str } rescue Puppet::ExecutionFailure => detail raise Puppet::ParseError, _("Failed to execute generator %{generator}: %{detail}") % { generator: args[0], detail: detail }, detail.backtrace end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/parser_factory.rb���������������������������������������������������0000644�0052762�0001160�00000001472�13417161721�021745� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet; end module Puppet::Parser # The ParserFactory makes selection of parser possible. # Currently, it is possible to switch between two different parsers: # * classic_parser, the parser in 3.1 # * eparser, the Expression Based Parser # class ParserFactory # Produces a parser instance for the given environment def self.parser evaluating_parser end # Creates an instance of an E4ParserAdapter that adapts an # EvaluatingParser to the 3x way of parsing. # def self.evaluating_parser unless defined?(Puppet::Parser::E4ParserAdapter) require 'puppet/parser/e4_parser_adapter' require 'puppet/pops/parser/code_merger' end E4ParserAdapter.new end def self.code_merger Puppet::Pops::Parser::CodeMerger.new end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/relationship.rb�����������������������������������������������������0000644�0052762�0001160�00000005710�13417161721�021422� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Parser::Relationship attr_accessor :source, :target, :type PARAM_MAP = {:relationship => :before, :subscription => :notify} def arrayify(resources, left) case resources when Puppet::Pops::Evaluator::Collectors::AbstractCollector # on the LHS, go as far left as possible, else whatever the collected result is left ? leftmost_alternative(resources) : resources.collected.values when Array resources else [resources] end end def evaluate(catalog) arrayify(source, true).each do |s| arrayify(target, false).each do |t| mk_relationship(s, t, catalog) end end end def initialize(source, target, type) @source, @target, @type = source, target, type end def param_name PARAM_MAP[type] || raise(ArgumentError, _("Invalid relationship type %{relationship_type}") % { relationship_type: type }) end def mk_relationship(source, target, catalog) source_ref = canonical_ref(source) target_ref = canonical_ref(target) rel_param = param_name unless source_resource = catalog.resource(*source_ref) raise ArgumentError, _("Could not find resource '%{source}' for relationship on '%{target}'") % { source: source.to_s, target: target.to_s } end unless catalog.resource(*target_ref) raise ArgumentError, _("Could not find resource '%{target}' for relationship from '%{source}'") % { target: target.to_s, source: source.to_s } end Puppet.debug {"Adding relationship from #{source} to #{target} with '#{param_name}'"} if source_resource[rel_param].class != Array source_resource[rel_param] = [source_resource[rel_param]].compact end source_resource[rel_param] << (target_ref[1].nil? ? target_ref[0] : "#{target_ref[0]}[#{target_ref[1]}]") end private # Finds the leftmost alternative for a collector (if it is empty, try its empty alternative recursively until there is # either nothing left, or a non empty set is found. # def leftmost_alternative(x) if x.is_a?(Puppet::Pops::Evaluator::Collectors::AbstractCollector) collected = x.collected return collected.values unless collected.empty? adapter = Puppet::Pops::Adapters::EmptyAlternativeAdapter.get(x) adapter.nil? ? [] : leftmost_alternative(adapter.empty_alternative) elsif x.is_a?(Array) && x.size == 1 && x[0].is_a?(Puppet::Pops::Evaluator::Collectors::AbstractCollector) leftmost_alternative(x[0]) else x end end # Turns a PResourceType or PClassType into an array [type, title] and all other references to [ref, nil] # This is needed since it is not possible to find resources in the catalog based on the type system types :-( # (note, the catalog is also used on the agent side) def canonical_ref(ref) case ref when Puppet::Pops::Types::PResourceType [ref.type_name, ref.title] when Puppet::Pops::Types::PClassType ['class', ref.class_name] else [ref.to_s, nil] end end end ��������������������������������������������������������puppet-5.5.10/lib/puppet/parser/resource/�����������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020225� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/resource/param.rb���������������������������������������������������0000644�0052762�0001160�00000000672�13417161721�021652� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The parameters we stick in Resources. class Puppet::Parser::Resource::Param include Puppet::Util include Puppet::Util::Errors include Puppet::Util::MethodHelper attr_accessor :name, :value, :source, :add, :file, :line def initialize(hash) set_options(hash) requiredopts(:name) @name = @name.intern end def line_to_i line ? Integer(line) : nil end def to_s "#{self.name} => #{self.value}" end end ����������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/templatewrapper.rb��������������������������������������������������0000644�0052762�0001160�00000005310�13417161721�022131� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parser/files' require 'erb' require 'puppet/file_system' # A simple wrapper for templates, so they don't have full access to # the scope objects. # # @api private class Puppet::Parser::TemplateWrapper include Puppet::Util Puppet::Util.logmethods(self) def initialize(scope) @__scope__ = scope end # @return [String] The full path name of the template that is being executed # @api public def file @__file__ end # @return [Puppet::Parser::Scope] The scope in which the template is evaluated # @api public def scope @__scope__ end # Find which line in the template (if any) we were called from. # @return [String] the line number # @api private def script_line identifier = Regexp.escape(@__file__ || "(erb)") (caller.find { |l| l =~ /#{identifier}:/ }||"")[/:(\d+):/,1] end private :script_line # Should return true if a variable is defined, false if it is not # @api public def has_variable?(name) scope.include?(name.to_s) end # @return [Array<String>] The list of defined classes # @api public def classes scope.catalog.classes end # @return [Array<String>] The tags defined in the current scope # @api public def tags scope.tags end # @return [Array<String>] All the defined tags # @api public def all_tags scope.catalog.tags end # @api private def file=(filename) unless @__file__ = Puppet::Parser::Files.find_template(filename, scope.compiler.environment) raise Puppet::ParseError, _("Could not find template '%{filename}'") % { filename: filename } end end # @api private def result(string = nil) if string template_source = "inline template" else string = Puppet::FileSystem.read_preserve_line_endings(@__file__) template_source = @__file__ end # Expose all the variables in our scope as instance variables of the # current object, making it possible to access them without conflict # to the regular methods. escaped_template_source = template_source.gsub(/%/, '%%') benchmark(:debug, _("Bound template variables for %{template_source} in %%{seconds} seconds") % { template_source: escaped_template_source }) do scope.to_hash.each do |name, value| realname = name.gsub(/[^\w]/, "_") instance_variable_set("@#{realname}", value) end end result = nil benchmark(:debug, _("Interpolated template %{template_source} in %%{seconds} seconds") % { template_source: escaped_template_source }) do template = ERB.new(string, 0, "-") template.filename = @__file__ result = template.result(binding) end result end def to_s "template[#{(@__file__ ? @__file__ : "inline")}]" end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/type_loader.rb������������������������������������������������������0000644�0052762�0001160�00000010340�13417161721�021223� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'find' require 'forwardable' require 'puppet/parser/parser_factory' class Puppet::Parser::TypeLoader extend Forwardable class TypeLoaderError < StandardError; end # Import manifest files that match a given file glob pattern. # # @param pattern [String] the file glob to apply when determining which files # to load # @param dir [String] base directory to use when the file is not # found in a module # @api private def import(pattern, dir) modname, files = Puppet::Parser::Files.find_manifests_in_modules(pattern, environment) if files.empty? abspat = File.expand_path(pattern, dir) file_pattern = abspat + (File.extname(abspat).empty? ? '.pp' : '' ) files = Dir.glob(file_pattern).uniq.reject { |f| FileTest.directory?(f) } modname = nil if files.empty? raise_no_files_found(pattern) end end load_files(modname, files) end # Load all of the manifest files in all known modules. # @api private def import_all # And then load all files from each module, but (relying on system # behavior) only load files from the first module of a given name. E.g., # given first/foo and second/foo, only files from first/foo will be loaded. environment.modules.each do |mod| load_files(mod.name, mod.all_manifests) end end def_delegator :environment, :known_resource_types def initialize(env) self.environment = env end def environment @environment end def environment=(env) if env.is_a?(String) or env.is_a?(Symbol) @environment = Puppet.lookup(:environments).get!(env) else @environment = env end end # Try to load the object with the given fully qualified name. def try_load_fqname(type, fqname) return nil if fqname == "" # special-case main. files_to_try_for(fqname).each do |filename| begin imported_types = import_from_modules(filename) if result = imported_types.find { |t| t.type == type and t.name == fqname } Puppet.debug {"Automatically imported #{fqname} from #{filename} into #{environment}"} return result end rescue TypeLoaderError # I'm not convinced we should just drop these errors, but this # preserves existing behaviours. end end # Nothing found. return nil end def parse_file(file) Puppet.debug("importing '#{file}' in environment #{environment}") parser = Puppet::Parser::ParserFactory.parser parser.file = file return parser.parse end private def import_from_modules(pattern) modname, files = Puppet::Parser::Files.find_manifests_in_modules(pattern, environment) if files.empty? raise_no_files_found(pattern) end load_files(modname, files) end def raise_no_files_found(pattern) raise TypeLoaderError, _("No file(s) found for import of '%{pattern}'") % { pattern: pattern } end def load_files(modname, files) @loaded ||= {} loaded_asts = [] files.reject { |file| @loaded[file] }.each do |file| # The squelch_parse_errors use case is for parsing for the purpose of searching # for information and it should not abort. # There is currently one user in indirector/resourcetype/parser # if Puppet.lookup(:squelch_parse_errors) {|| false } begin loaded_asts << parse_file(file) rescue => e # Resume from errors so that all parseable files may # still be parsed. Mark this file as loaded so that # it would not be parsed next time (handle it as if # it was successfully parsed). Puppet.debug("Unable to parse '#{file}': #{e.message}") end else loaded_asts << parse_file(file) end @loaded[file] = true end loaded_asts.collect do |ast| known_resource_types.import_ast(ast, modname) end.flatten end # Return a list of all file basenames that should be tried in order # to load the object with the given fully qualified name. def files_to_try_for(qualified_name) qualified_name.split('::').inject([]) do |paths, name| add_path_for_name(paths, name) end end def add_path_for_name(paths, name) if paths.empty? [name] else paths.unshift(File.join(paths.first, name)) end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/ast.rb��������������������������������������������������������������0000644�0052762�0001160�00000004422�13417161721�017507� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The base class for the 3x "parse tree", now only used by the top level # constructs and the compiler. # Handles things like file name, line #, and also does the initialization # for all of the parameters of all of the child objects. # class Puppet::Parser::AST AST = Puppet::Parser::AST include Puppet::Util::Errors include Puppet::Util::MethodHelper attr_accessor :parent, :scope, :file, :line, :pos def inspect "( #{self.class} #{self.to_s} #{@children.inspect} )" end # Evaluate the current object. Just a stub method, since the subclass # should override this method. def evaluate(scope) end # The version of the evaluate method that should be called, because it # correctly handles errors. It is critical to use this method because # it can enable you to catch the error where it happens, rather than # much higher up the stack. def safeevaluate(scope) # We duplicate code here, rather than using exceptwrap, because this # is called so many times during parsing. begin return self.evaluate(scope) rescue Puppet::Pops::Evaluator::PuppetStopIteration => detail raise detail # # Only deals with StopIteration from the break() function as a general # # StopIteration is a general runtime problem # raise Puppet::ParseError.new(detail.message, detail.file, detail.line, detail) rescue Puppet::Error => detail raise adderrorcontext(detail) rescue => detail error = Puppet::ParseError.new(detail.to_s, nil, nil, detail) # We can't use self.fail here because it always expects strings, # not exceptions. raise adderrorcontext(error, detail) end end # Initialize the object. Requires a hash as the argument, and # takes each of the parameters of the hash and calls the setter # method for them. This is probably pretty inefficient and should # likely be changed at some point. def initialize(args) set_options(args) end end # And include all of the AST subclasses. require 'puppet/parser/ast/branch' require 'puppet/parser/ast/leaf' require 'puppet/parser/ast/block_expression' require 'puppet/parser/ast/hostclass' require 'puppet/parser/ast/node' require 'puppet/parser/ast/resource' require 'puppet/parser/ast/resource_instance' require 'puppet/parser/ast/resourceparam' ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/compiler.rb���������������������������������������������������������0000644�0052762�0001160�00000070024�13417161721�020533� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'forwardable' require 'puppet/node' require 'puppet/resource/catalog' require 'puppet/util/errors' require 'puppet/loaders' require 'puppet/pops' # Maintain a graph of scopes, along with a bunch of data # about the individual catalog we're compiling. class Puppet::Parser::Compiler include Puppet::Parser::AbstractCompiler extend Forwardable include Puppet::Util include Puppet::Util::Errors include Puppet::Util::MethodHelper include Puppet::Pops::Evaluator::Runtime3Support def self.compile(node, code_id = nil) node.environment.check_for_reparse errors = node.environment.validation_errors if !errors.empty? errors.each { |e| Puppet.err(e) } if errors.size > 1 errmsg = [ _("Compilation has been halted because: %{error}") % { error: errors.first }, _("For more information, see https://puppet.com/docs/puppet/latest/environments_about.html"), ] raise(Puppet::Error, errmsg.join(' ')) end new(node, :code_id => code_id).compile {|resulting_catalog| resulting_catalog.to_resource } rescue Puppet::ParseErrorWithIssue => detail detail.node = node.name Puppet.log_exception(detail) raise rescue => detail message = _("%{message} on node %{node}") % { message: detail, node: node.name } Puppet.log_exception(detail, message) raise Puppet::Error, message, detail.backtrace end attr_reader :node, :facts, :collections, :catalog, :resources, :relationships, :topscope attr_reader :qualified_variables # Access to the configured loaders for 4x # @return [Puppet::Pops::Loader::Loaders] the configured loaders # @api private attr_reader :loaders # The id of code input to the compiler. # @api private attr_accessor :code_id # Add a collection to the global list. def_delegator :@collections, :<<, :add_collection def_delegator :@relationships, :<<, :add_relationship # Store a resource override. def add_override(override) # If possible, merge the override in immediately. if resource = @catalog.resource(override.ref) resource.merge(override) else # Otherwise, store the override for later; these # get evaluated in Resource#finish. @resource_overrides[override.ref] << override end end def add_resource(scope, resource) type = resource.resource_type if type.is_a?(Puppet::Resource::Type) && type.application? @applications << resource assert_app_in_site(scope, resource) return end if @current_app # We are in the process of pulling application components out that # apply to this node Puppet.notice "Check #{resource}" return unless @current_components.any? do |comp| comp.type == resource.type && comp.title == resource.title end end @resources << resource # Note that this will fail if the resource is not unique. @catalog.add_resource(resource) if not resource.class? and resource[:stage] #TRANSLATORS "stage" is a keyword in Puppet and should not be translated raise ArgumentError, _("Only classes can set 'stage'; normal resources like %{resource} cannot change run stage") % { resource: resource } end # Stages should not be inside of classes. They are always a # top-level container, regardless of where they appear in the # manifest. return if resource.stage? # This adds a resource to the class it lexically appears in in the # manifest. unless resource.class? @catalog.add_edge(scope.resource, resource) end end def assert_app_in_site(scope, resource) if resource.type == 'App' if scope.resource # directly contained in a Site return if scope.resource.type == 'Site' # contained in something that may be contained in Site upstream = @catalog.upstream_from_vertex(scope.resource) if upstream return if upstream.keys.map(&:type).include?('Site') end end #TRANSLATORS "Site" is a puppet keyword and should not be translated raise ArgumentError, _("Application instances like '%{resource}' can only be contained within a Site") % { resource: resource } end end # Store the fact that we've evaluated a class def add_class(name) @catalog.add_class(name) unless name == "" end # Add a catalog validator that will run at some stage to this compiler # @param catalog_validators [Class<CatalogValidator>] The catalog validator class to add def add_catalog_validator(catalog_validators) @catalog_validators << catalog_validators nil end def add_catalog_validators add_catalog_validator(CatalogValidator::RelationshipValidator) end # Return a list of all of the defined classes. def_delegator :@catalog, :classes, :classlist def with_context_overrides(description = '', &block) Puppet.override( @context_overrides , description, &block) end # Compiler our catalog. This mostly revolves around finding and evaluating classes. # This is the main entry into our catalog. def compile Puppet.override( @context_overrides , _("For compiling %{node}") % { node: node.name }) do @catalog.environment_instance = environment # Set the client's parameters into the top scope. Puppet::Util::Profiler.profile(_("Compile: Set node parameters"), [:compiler, :set_node_params]) { set_node_parameters } Puppet::Util::Profiler.profile(_("Compile: Created settings scope"), [:compiler, :create_settings_scope]) { create_settings_scope } Puppet::Util::Profiler.profile(_("Compile: Evaluated capability mappings"), [:compiler, :evaluate_capability_mappings]) { evaluate_capability_mappings } #TRANSLATORS "main" is a function name and should not be translated Puppet::Util::Profiler.profile(_("Compile: Evaluated main"), [:compiler, :evaluate_main]) { evaluate_main } Puppet::Util::Profiler.profile(_("Compile: Evaluated site"), [:compiler, :evaluate_site]) { evaluate_site } Puppet::Util::Profiler.profile(_("Compile: Evaluated AST node"), [:compiler, :evaluate_ast_node]) { evaluate_ast_node } Puppet::Util::Profiler.profile(_("Compile: Evaluated node classes"), [:compiler, :evaluate_node_classes]) { evaluate_node_classes } Puppet::Util::Profiler.profile(_("Compile: Evaluated application instances"), [:compiler, :evaluate_applications]) { evaluate_applications } # New capability mappings may have been defined when the site was evaluated Puppet::Util::Profiler.profile(_("Compile: Evaluated site capability mappings"), [:compiler, :evaluate_capability_mappings]) { evaluate_capability_mappings } Puppet::Util::Profiler.profile(_("Compile: Evaluated generators"), [:compiler, :evaluate_generators]) { evaluate_generators } Puppet::Util::Profiler.profile(_("Compile: Validate Catalog pre-finish"), [:compiler, :validate_pre_finish]) do validate_catalog(CatalogValidator::PRE_FINISH) end Puppet::Util::Profiler.profile(_("Compile: Finished catalog"), [:compiler, :finish_catalog]) { finish } Puppet::Util::Profiler.profile(_("Compile: Prune"), [:compiler, :prune_catalog]) { prune_catalog } fail_on_unevaluated Puppet::Util::Profiler.profile(_("Compile: Validate Catalog final"), [:compiler, :validate_final]) do validate_catalog(CatalogValidator::FINAL) end if block_given? yield @catalog else @catalog end end end def validate_catalog(validation_stage) @catalog_validators.select { |vclass| vclass.validation_stage?(validation_stage) }.each { |vclass| vclass.new(@catalog).validate } end # Constructs the overrides for the context def context_overrides() { :current_environment => environment, :global_scope => @topscope, # 4x placeholder for new global scope :loaders => @loaders, # 4x loaders } end def_delegator :@collections, :delete, :delete_collection # Return the node's environment. def environment node.environment end # Evaluate all of the classes specified by the node. # Classes with parameters are evaluated as if they were declared. # Classes without parameters or with an empty set of parameters are evaluated # as if they were included. This means classes with an empty set of # parameters won't conflict even if the class has already been included. def evaluate_node_classes if @node.classes.is_a? Hash classes_with_params, classes_without_params = @node.classes.partition {|name,params| params and !params.empty?} # The results from Hash#partition are arrays of pairs rather than hashes, # so we have to convert to the forms evaluate_classes expects (Hash, and # Array of class names) classes_with_params = Hash[classes_with_params] classes_without_params.map!(&:first) else classes_with_params = {} classes_without_params = @node.classes end evaluate_classes(classes_with_params, @node_scope || topscope) evaluate_classes(classes_without_params, @node_scope || topscope) end # Evaluates the site - the top container for an environment catalog # The site contain behaves analogous to a node - for the environment catalog, node expressions are ignored # as the result is cross node. The site expression serves as a container for everything that is across # all nodes. # # @api private # def evaluate_site # Has a site been defined? If not, do nothing but issue a warning. # site = environment.known_resource_types.find_site() unless site on_empty_site() return end # Create a resource to model this site and add it to catalog resource = site.ensure_in_catalog(topscope) # The site sets node scope to be able to shadow what is in top scope @node_scope = topscope.class_scope(site) # Evaluates the logic contain in the site expression resource.evaluate end # @api private def on_empty_site # do nothing end # Prunes the catalog by dropping all resources are contained under the Site (if a site expression is used). # As a consequence all edges to/from dropped resources are also dropped. # Once the pruning is performed, this compiler returns the pruned list when calling the #resources method. # The pruning does not alter the order of resources in the resources list. # # @api private def prune_catalog prune_node_catalog end def prune_node_catalog # Everything under Site[site] should be pruned as that is for the environment catalog, not a node # the_site_resource = @catalog.resource('Site', 'site') if the_site_resource # Get downstream vertexes returns a hash where the keys are the resources and values nesting level to_be_removed = @catalog.downstream_from_vertex(the_site_resource).keys # Drop the Site[site] resource if it has no content if to_be_removed.empty? to_be_removed << the_site_resource end else to_be_removed = [] end # keep_from_site is populated with any App resources. application_resources = @resources.select {|r| r.type == 'App' } # keep all applications plus what is directly referenced from applications keep_from_site = application_resources keep_from_site += application_resources.map {|app| @catalog.direct_dependents_of(app) }.flatten to_be_removed -= keep_from_site @catalog.remove_resource(*to_be_removed) # set the pruned result @resources = @catalog.resources end # @api private def evaluate_applications @applications.each do |app| components = [] mapping = app.parameters[:nodes] ? app.parameters[:nodes].value : {} raise Puppet::Error, _("Invalid node mapping in %{app}: Mapping must be a hash") % { app: app.ref } unless mapping.is_a?(Hash) all_mapped = Set.new mapping.each do |k,v| raise Puppet::Error, _("Invalid node mapping in %{app}: Key %{k} is not a Node") % { app: app.ref, k: k } unless k.is_a?(Puppet::Resource) && k.type == 'Node' v = [v] unless v.is_a?(Array) v.each do |res| raise Puppet::Error, _("Invalid node mapping in %{app}: Value %{res} is not a resource") % { app: app.ref, res: res } unless res.is_a?(Puppet::Resource) raise Puppet::Error, _("Application %{app} maps component %{res} to multiple nodes") % { app: app.ref, res: res } if all_mapped.add?(res.ref).nil? components << res if k.title == node.name end end begin @current_app = app @current_components = components unless @current_components.empty? Puppet.notice "EVAL APP #{app} #{components.inspect}" # Add the app itself since components mapped to the current node # will have a containment edge for it # @todo lutter 2015-01-28: the node mapping winds up in the # catalog, but probably shouldn't @catalog.add_resource(@current_app) @current_app.evaluate end ensure @current_app = nil @current_components = nil end end end # Evaluates each specified class in turn. If there are any classes that # can't be found, an error is raised. This method really just creates resource objects # that point back to the classes, and then the resources are themselves # evaluated later in the process. # def evaluate_classes(classes, scope, lazy_evaluate = true) raise Puppet::DevError, _("No source for scope passed to evaluate_classes") unless scope.source class_parameters = nil # if we are a param class, save the classes hash # and transform classes to be the keys if classes.class == Hash class_parameters = classes classes = classes.keys end unless @current_components.nil? classes = classes.select do |title| @current_components.any? { |comp| comp.class? && comp.title == title } end end hostclasses = classes.collect do |name| environment.known_resource_types.find_hostclass(name) or raise Puppet::Error, _("Could not find class %{name} for %{node}") % { name: name, node: node.name } end if class_parameters resources = ensure_classes_with_parameters(scope, hostclasses, class_parameters) if !lazy_evaluate resources.each(&:evaluate) end resources else already_included, newly_included = ensure_classes_without_parameters(scope, hostclasses) if !lazy_evaluate newly_included.each(&:evaluate) end already_included + newly_included end end def evaluate_relationships @relationships.each { |rel| rel.evaluate(catalog) } end # Return a resource by either its ref or its type and title. def_delegator :@catalog, :resource, :findresource def initialize(node, options = {}) @node = sanitize_node(node) # Array of resources representing all application instances we've found @applications = [] # We use @current_app and @current_components to signal to the # evaluator that we are in the middle of evaluating an # application. They are set in evaluate_applications to the application # instance, resp. to an array of the components of that application # that is mapped to the current node. They are only non-nil when we are # in the middle of executing evaluate_applications @current_app = nil @current_components = nil set_options(options) initvars add_catalog_validators # Resolutions of fully qualified variable names @qualified_variables = {} end # Create a new scope, with either a specified parent scope or # using the top scope. def newscope(parent, options = {}) parent ||= topscope scope = Puppet::Parser::Scope.new(self, options) scope.parent = parent scope end # Return any overrides for the given resource. def resource_overrides(resource) @resource_overrides[resource.ref] end private def ensure_classes_with_parameters(scope, hostclasses, parameters) hostclasses.collect do |klass| klass.ensure_in_catalog(scope, parameters[klass.name] || {}) end end def ensure_classes_without_parameters(scope, hostclasses) already_included = [] newly_included = [] hostclasses.each do |klass| class_scope = scope.class_scope(klass) if class_scope already_included << class_scope.resource else newly_included << klass.ensure_in_catalog(scope) end end [already_included, newly_included] end def evaluate_capability_mappings krt = environment.known_resource_types krt.capability_mappings.each_value do |capability_mapping| args = capability_mapping.arguments component_ref = args['component'] kind = args['kind'] # That component_ref is either a QREF or a Class['literal'|QREF] is asserted during validation so no # need to check that here if component_ref.is_a?(Puppet::Pops::Model::QualifiedReference) component_name = component_ref.cased_value component_type = 'type' component = krt.find_definition(component_name) else component_name = component_ref.keys[0].value component_type = 'class' component = krt.find_hostclass(component_name) end if component.nil? raise Puppet::ParseError, _("Capability mapping error: %{kind} clause references nonexistent %{component_type} %{component_name}") % { kind: kind, component_type: component_type, component_name: component_name } end blueprint = args['blueprint'] if kind == 'produces' component.add_produces(blueprint) else component.add_consumes(blueprint) end end krt.capability_mappings.clear # No longer needed end # If ast nodes are enabled, then see if we can find and evaluate one. def evaluate_ast_node krt = environment.known_resource_types return unless krt.nodes? #ast_nodes? # Now see if we can find the node. astnode = nil @node.names.each do |name| break if astnode = krt.node(name.to_s.downcase) end unless (astnode ||= krt.node("default")) raise Puppet::ParseError, _("Could not find node statement with name 'default' or '%{names}'") % { names: node.names.join(", ") } end # Create a resource to model this node, and then add it to the list # of resources. resource = astnode.ensure_in_catalog(topscope) resource.evaluate @node_scope = topscope.class_scope(astnode) end # Evaluate our collections and return true if anything returned an object. # The 'true' is used to continue a loop, so it's important. def evaluate_collections return false if @collections.empty? exceptwrap do # We have to iterate over a dup of the array because # collections can delete themselves from the list, which # changes its length and causes some collections to get missed. Puppet::Util::Profiler.profile(_("Evaluated collections"), [:compiler, :evaluate_collections]) do found_something = false @collections.dup.each do |collection| found_something = true if collection.evaluate end found_something end end end # Make sure all of our resources have been evaluated into native resources. # We return true if any resources have, so that we know to continue the # evaluate_generators loop. def evaluate_definitions exceptwrap do Puppet::Util::Profiler.profile(_("Evaluated definitions"), [:compiler, :evaluate_definitions]) do urs = unevaluated_resources.each do |resource| begin resource.evaluate rescue Puppet::Pops::Evaluator::PuppetStopIteration => detail # needs to be handled specifically as the error has the file/line/position where this # occurred rather than the resource fail(Puppet::Pops::Issues::RUNTIME_ERROR, detail, {:detail => detail.message}, detail) rescue Puppet::Error => e # PuppetError has the ability to wrap an exception, if so, use the wrapped exception's # call stack instead fail(Puppet::Pops::Issues::RUNTIME_ERROR, resource, {:detail => e.message}, e.original || e) end end !urs.empty? end end end # Iterate over collections and resources until we're sure that the whole # compile is evaluated. This is necessary because both collections # and defined resources can generate new resources, which themselves could # be defined resources. def evaluate_generators count = 0 loop do done = true Puppet::Util::Profiler.profile(_("Iterated (%{count}) on generators") % { count: count + 1 }, [:compiler, :iterate_on_generators]) do # Call collections first, then definitions. done = false if evaluate_collections done = false if evaluate_definitions end break if done count += 1 if count > 1000 raise Puppet::ParseError, _("Somehow looped more than 1000 times while evaluating host catalog") end end end # Find and evaluate our main object, if possible. def evaluate_main krt = environment.known_resource_types @main = krt.find_hostclass('') || krt.add(Puppet::Resource::Type.new(:hostclass, '')) @topscope.source = @main @main_resource = Puppet::Parser::Resource.new('class', :main, :scope => @topscope, :source => @main) @topscope.resource = @main_resource add_resource(@topscope, @main_resource) @main_resource.evaluate end # Make sure the entire catalog is evaluated. def fail_on_unevaluated fail_on_unevaluated_overrides fail_on_unevaluated_resource_collections end # If there are any resource overrides remaining, then we could # not find the resource they were supposed to override, so we # want to throw an exception. def fail_on_unevaluated_overrides remaining = @resource_overrides.values.flatten.collect(&:ref) if !remaining.empty? raise Puppet::ParseError, _("Could not find resource(s) %{resources} for overriding") % { resources: remaining.join(', ') } end end # Make sure there are no remaining collections that are waiting for # resources that have not yet been instantiated. If this occurs it # is an error (missing resource - it could not be realized). # def fail_on_unevaluated_resource_collections remaining = @collections.collect(&:unresolved_resources).flatten.compact if !remaining.empty? raise Puppet::ParseError, _("Failed to realize virtual resources %{resources}") % { resources: remaining.join(', ') } end end # Make sure all of our resources and such have done any last work # necessary. def finish evaluate_relationships resources.each do |resource| # Add in any resource overrides. if overrides = resource_overrides(resource) overrides.each do |over| resource.merge(over) end # Remove the overrides, so that the configuration knows there # are none left. overrides.clear end resource.finish if resource.respond_to?(:finish) end add_resource_metaparams end def add_resource_metaparams unless main = catalog.resource(:class, :main) #TRANSLATORS "main" is a function name and should not be translated raise _("Couldn't find main") end names = Puppet::Type.metaparams.select do |name| !Puppet::Parser::Resource.relationship_parameter?(name) end data = {} catalog.walk(main, :out) do |source, target| if source_data = data[source] || metaparams_as_data(source, names) # only store anything in the data hash if we've actually got # data data[source] ||= source_data source_data.each do |param, value| target[param] = value if target[param].nil? end data[target] = source_data.merge(metaparams_as_data(target, names)) end target.merge_tags_from(source) end end def metaparams_as_data(resource, params) data = nil params.each do |param| unless resource[param].nil? # Because we could be creating a hash for every resource, # and we actually probably don't often have any data here at all, # we're optimizing a bit by only creating a hash if there's # any data to put in it. data ||= {} data[param] = resource[param] end end data end # Set up all of our internal variables. def initvars # The list of overrides. This is used to cache overrides on objects # that don't exist yet. We store an array of each override. @resource_overrides = Hash.new do |overs, ref| overs[ref] = [] end # The list of collections that have been created. This is a global list, # but they each refer back to the scope that created them. @collections = [] # The list of relationships to evaluate. @relationships = [] # For maintaining the relationship between scopes and their resources. @catalog = Puppet::Resource::Catalog.new(@node.name, @node.environment, @code_id) # MOVED HERE - SCOPE IS NEEDED (MOVE-SCOPE) # Create the initial scope, it is needed early @topscope = Puppet::Parser::Scope.new(self) # Initialize loaders and Pcore @loaders = Puppet::Pops::Loaders.new(environment) # Need to compute overrides here, and remember them, because we are about to # enter the magic zone of known_resource_types and initial import. # Expensive entries in the context are bound lazily. @context_overrides = context_overrides() # This construct ensures that initial import (triggered by instantiating # the structure 'known_resource_types') has a configured context # It cannot survive the initvars method, and is later reinstated # as part of compiling... # Puppet.override( @context_overrides , _("For initializing compiler")) do # THE MAGIC STARTS HERE ! This triggers parsing, loading etc. @catalog.version = environment.known_resource_types.version @loaders.pre_load end @catalog.add_resource(Puppet::Parser::Resource.new("stage", :main, :scope => @topscope)) # local resource array to maintain resource ordering @resources = [] # Make sure any external node classes are in our class list if @node.classes.class == Hash @catalog.add_class(*@node.classes.keys) else @catalog.add_class(*@node.classes) end @catalog_validators = [] end def sanitize_node(node) node.sanitize node end # Set the node's parameters into the top-scope as variables. def set_node_parameters node.parameters.each do |param, value| # We don't want to set @topscope['environment'] from the parameters, # instead we want to get that from the node's environment itself in # case a custom node terminus has done any mucking about with # node.parameters. next if param.to_s == 'environment' # Ensure node does not leak Symbol instances in general @topscope[param.to_s] = value.is_a?(Symbol) ? value.to_s : value end @topscope['environment'] = node.environment.name.to_s # These might be nil. catalog.client_version = node.parameters["clientversion"] catalog.server_version = node.parameters["serverversion"] @topscope.set_trusted(node.trusted_data) @topscope.set_server_facts(node.server_facts) facts_hash = node.facts.nil? ? {} : node.facts.values @topscope.set_facts(facts_hash) end SETTINGS = 'settings'.freeze def create_settings_scope resource_types = environment.known_resource_types settings_type = resource_types.hostclass(SETTINGS) if settings_type.nil? settings_type = Puppet::Resource::Type.new(:hostclass, SETTINGS) resource_types.add(settings_type) end settings_resource = Puppet::Parser::Resource.new('class', SETTINGS, :scope => @topscope) @catalog.add_resource(settings_resource) settings_type.evaluate_code(settings_resource) scope = @topscope.class_scope(settings_type) scope.merge_settings(environment.name) end # Return an array of all of the unevaluated resources. These will be definitions, # which need to get evaluated into native resources. def unevaluated_resources # The order of these is significant for speed due to short-circuiting resources.reject { |resource| resource.evaluated? or resource.virtual? or resource.builtin_type? } end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/functions.rb��������������������������������������������������������0000644�0052762�0001160�00000023522�13417161721�020732� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/autoload' require 'puppet/parser/scope' require 'puppet/pops/adaptable' # A module for managing parser functions. Each specified function # is added to a central module that then gets included into the Scope # class. # # @api public module Puppet::Parser::Functions Environment = Puppet::Node::Environment class << self include Puppet::Util end # Reset the list of loaded functions. # # @api private def self.reset # Runs a newfunction to create a function for each of the log levels root_env = Puppet.lookup(:root_environment) AnonymousModuleAdapter.clear(root_env) Puppet::Util::Log.levels.each do |level| newfunction(level, :environment => root_env, :doc => "Log a message on the server at level #{level.to_s}.") do |vals| send(level, vals.join(" ")) end end end # Accessor for singleton autoloader # # @api private def self.autoloader @autoloader ||= Puppet::Util::Autoload.new(self, "puppet/parser/functions") end # An adapter that ties the anonymous module that acts as the container for all 3x functions to the environment # where the functions are created. This adapter ensures that the life-cycle of those functions doesn't exceed # the life-cycle of the environment. # # @api private class AnonymousModuleAdapter < Puppet::Pops::Adaptable::Adapter attr_accessor :module end # Get the module that functions are mixed into corresponding to an # environment # # @api private def self.environment_module(env) AnonymousModuleAdapter.adapt(env) do |a| a.module ||= Module.new do @metadata = {} def self.all_function_info @metadata end def self.get_function_info(name) @metadata[name] end def self.add_function_info(name, info) @metadata[name] = info end end end.module end # Create a new Puppet DSL function. # # **The {newfunction} method provides a public API.** # # This method is used both internally inside of Puppet to define parser # functions. For example, template() is defined in # {file:lib/puppet/parser/functions/template.rb template.rb} using the # {newfunction} method. Third party Puppet modules such as # [stdlib](https://forge.puppetlabs.com/puppetlabs/stdlib) use this method to # extend the behavior and functionality of Puppet. # # See also [Docs: Custom # Functions](https://puppet.com/docs/puppet/5.5/lang_write_functions_in_puppet.html) # # @example Define a new Puppet DSL Function # >> Puppet::Parser::Functions.newfunction(:double, :arity => 1, # :doc => "Doubles an object, typically a number or string.", # :type => :rvalue) {|i| i[0]*2 } # => {:arity=>1, :type=>:rvalue, # :name=>"function_double", # :doc=>"Doubles an object, typically a number or string."} # # @example Invoke the double function from irb as is done in RSpec examples: # >> require 'puppet_spec/scope' # >> scope = PuppetSpec::Scope.create_test_scope_for_node('example') # => Scope() # >> scope.function_double([2]) # => 4 # >> scope.function_double([4]) # => 8 # >> scope.function_double([]) # ArgumentError: double(): Wrong number of arguments given (0 for 1) # >> scope.function_double([4,8]) # ArgumentError: double(): Wrong number of arguments given (2 for 1) # >> scope.function_double(["hello"]) # => "hellohello" # # @param [Symbol] name the name of the function represented as a ruby Symbol. # The {newfunction} method will define a Ruby method based on this name on # the parser scope instance. # # @param [Proc] block the block provided to the {newfunction} method will be # executed when the Puppet DSL function is evaluated during catalog # compilation. The arguments to the function will be passed as an array to # the first argument of the block. The return value of the block will be # the return value of the Puppet DSL function for `:rvalue` functions. # # @option options [:rvalue, :statement] :type (:statement) the type of function. # Either `:rvalue` for functions that return a value, or `:statement` for # functions that do not return a value. # # @option options [String] :doc ('') the documentation for the function. # This string will be extracted by documentation generation tools. # # @option options [Integer] :arity (-1) the # [arity](https://en.wikipedia.org/wiki/Arity) of the function. When # specified as a positive integer the function is expected to receive # _exactly_ the specified number of arguments. When specified as a # negative number, the function is expected to receive _at least_ the # absolute value of the specified number of arguments incremented by one. # For example, a function with an arity of `-4` is expected to receive at # minimum 3 arguments. A function with the default arity of `-1` accepts # zero or more arguments. A function with an arity of 2 must be provided # with exactly two arguments, no more and no less. Added in Puppet 3.1.0. # # @option options [Puppet::Node::Environment] :environment (nil) can # explicitly pass the environment we wanted the function added to. Only used # to set logging functions in root environment # # @return [Hash] describing the function. # # @api public def self.newfunction(name, options = {}, &block) name = name.intern environment = options[:environment] || Puppet.lookup(:current_environment) Puppet.warning _("Overwriting previous definition for function %{name}") % { name: name } if get_function(name, environment) arity = options[:arity] || -1 ftype = options[:type] || :statement unless ftype == :statement or ftype == :rvalue raise Puppet::DevError, _("Invalid statement type %{type}") % { type: ftype.inspect } end # the block must be installed as a method because it may use "return", # which is not allowed from procs. real_fname = "real_function_#{name}" environment_module(environment).send(:define_method, real_fname, &block) fname = "function_#{name}" env_module = environment_module(environment) env_module.send(:define_method, fname) do |*args| Puppet::Util::Profiler.profile(_("Called %{name}") % { name: name }, [:functions, name]) do if args[0].is_a? Array if arity >= 0 and args[0].size != arity raise ArgumentError, _("%{name}(): Wrong number of arguments given (%{arg_count} for %{arity})") % { name: name, arg_count: args[0].size, arity: arity } elsif arity < 0 and args[0].size < (arity+1).abs raise ArgumentError, _("%{name}(): Wrong number of arguments given (%{arg_count} for minimum %{min_arg_count})") % { name: name, arg_count: args[0].size, min_arg_count: (arity+1).abs } end self.send(real_fname, args[0]) else raise ArgumentError, _("custom functions must be called with a single array that contains the arguments. For example, function_example([1]) instead of function_example(1)") end end end func = {:arity => arity, :type => ftype, :name => fname} func[:doc] = options[:doc] if options[:doc] env_module.add_function_info(name, func) func end # Determine if a function is defined # # @param [Symbol] name the function # @param [Puppet::Node::Environment] environment the environment to find the function in # # @return [Symbol, false] The name of the function if it's defined, # otherwise false. # # @api public def self.function(name, environment = Puppet.lookup(:current_environment)) name = name.intern unless func = get_function(name, environment) autoloader.load(name, environment) func = get_function(name, environment) end if func func[:name] else false end end def self.functiondocs(environment = Puppet.lookup(:current_environment)) autoloader.loadall ret = "" merged_functions(environment).sort { |a,b| a[0].to_s <=> b[0].to_s }.each do |name, hash| ret << "#{name}\n#{"-" * name.to_s.length}\n" if hash[:doc] ret << Puppet::Util::Docs.scrub(hash[:doc]) else ret << "Undocumented.\n" end ret << "\n\n- *Type*: #{hash[:type]}\n\n" end ret end # Determine whether a given function returns a value. # # @param [Symbol] name the function # @param [Puppet::Node::Environment] environment The environment to find the function in # @return [Boolean] whether it is an rvalue function # # @api public def self.rvalue?(name, environment = Puppet.lookup(:current_environment)) func = get_function(name, environment) func ? func[:type] == :rvalue : false end # Return the number of arguments a function expects. # # @param [Symbol] name the function # @param [Puppet::Node::Environment] environment The environment to find the function in # @return [Integer] The arity of the function. See {newfunction} for # the meaning of negative values. # # @api public def self.arity(name, environment = Puppet.lookup(:current_environment)) func = get_function(name, environment) func ? func[:arity] : -1 end class << self private def merged_functions(environment) root = environment_module(Puppet.lookup(:root_environment)) env = environment_module(environment) root.all_function_info.merge(env.all_function_info) end def get_function(name, environment) environment_module(environment).get_function_info(name.intern) || environment_module(Puppet.lookup(:root_environment)).get_function_info(name.intern) end end class Error def self.is4x(name) raise Puppet::ParseError, _("%{name}() can only be called using the 4.x function API. See Scope#call_function") % { name: name } end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/resource.rb���������������������������������������������������������0000644�0052762�0001160�00000033602�13417161721�020551� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/resource' # The primary difference between this class and its # parent is that this class has rules on who can set # parameters class Puppet::Parser::Resource < Puppet::Resource require 'puppet/parser/resource/param' require 'puppet/util/tagging' include Puppet::Util include Puppet::Util::MethodHelper include Puppet::Util::Errors include Puppet::Util::Logging attr_accessor :source, :scope, :collector_id attr_accessor :virtual, :override, :translated, :catalog, :evaluated attr_accessor :file, :line attr_reader :exported, :parameters # Determine whether the provided parameter name is a relationship parameter. def self.relationship_parameter?(name) @relationship_names ||= Puppet::Type.relationship_params.collect { |p| p.name } @relationship_names.include?(name) end # Set up some boolean test methods def translated?; !!@translated; end def override?; !!@override; end def evaluated?; !!@evaluated; end def [](param) param = param.intern if param == :title return self.title end if @parameters.has_key?(param) @parameters[param].value else nil end end def eachparam @parameters.each do |name, param| yield param end end def environment scope.environment end # Process the stage metaparameter for a class. A containment edge # is drawn from the class to the stage. The stage for containment # defaults to main, if none is specified. def add_edge_to_stage return unless self.class? unless stage = catalog.resource(:stage, self[:stage] || (scope && scope.resource && scope.resource[:stage]) || :main) raise ArgumentError, _("Could not find stage %{stage} specified by %{resource}") % { stage: self[:stage] || :main, resource: self } end self[:stage] ||= stage.title unless stage.title == :main catalog.add_edge(stage, self) end # Retrieve the associated definition and evaluate it. def evaluate return if evaluated? Puppet::Util::Profiler.profile(_("Evaluated resource %{res}") % { res: self }, [:compiler, :evaluate_resource, self]) do @evaluated = true if builtin_type? devfail "Cannot evaluate a builtin type (#{type})" elsif resource_type.nil? self.fail "Cannot find definition #{type}" else finish_evaluation() # do not finish completely (as that destroys Sensitive data) resource_type.evaluate_code(self) end end end # Mark this resource as both exported and virtual, # or remove the exported mark. def exported=(value) if value @virtual = true @exported = value else @exported = value end end # Finish the evaluation by assigning defaults and scope tags # @api private # def finish_evaluation return if @evaluation_finished add_scope_tags @evaluation_finished = true end # Do any finishing work on this object, called before # storage/translation. The method does nothing the second time # it is called on the same resource. # # @param do_validate [Boolean] true if validation should be performed # # @api private def finish(do_validate = true) return if finished? @finished = true finish_evaluation replace_sensitive_data validate if do_validate end # Has this resource already been finished? def finished? @finished end def initialize(type, title, attributes, with_defaults = true) raise ArgumentError, _('Resources require a hash as last argument') unless attributes.is_a? Hash raise ArgumentError, _('Resources require a scope') unless attributes[:scope] super(type, title, attributes) @source ||= scope.source if with_defaults scope.lookupdefaults(self.type).each_pair do |name, param| unless @parameters.include?(name) self.debug "Adding default for #{name}" param = param.dup @parameters[name] = param tag(*param.value) if param.name == :tag end end end end # Is this resource modeling an isomorphic resource type? def isomorphic? if builtin_type? return resource_type.isomorphic? else return true end end def is_unevaluated_consumer? # We don't declare a new variable here just to test. Saves memory instance_variable_defined?(:@unevaluated_consumer) end def mark_unevaluated_consumer @unevaluated_consumer = true end # Merge an override resource in. This will throw exceptions if # any overrides aren't allowed. def merge(resource) # Test the resource scope, to make sure the resource is even allowed # to override. unless self.source.object_id == resource.source.object_id || resource.source.child_of?(self.source) raise Puppet::ParseError.new(_("Only subclasses can override parameters"), resource.file, resource.line) end if evaluated? error_location_str = Puppet::Util::Errors.error_location(file, line) msg = if error_location_str.empty? _('Attempt to override an already evaluated resource with new values') else _('Attempt to override an already evaluated resource, defined at %{error_location}, with new values') % { error_location: error_location_str } end strict = Puppet[:strict] unless strict == :off if strict == :error raise Puppet::ParseError.new(msg, resource.file, resource.line) else msg += Puppet::Util::Errors.error_location_with_space(resource.file, resource.line) Puppet.warning(msg) end end end # Some of these might fail, but they'll fail in the way we want. resource.parameters.each do |name, param| override_parameter(param) end end def name self[:name] || self.title end # A temporary occasion, until I get paths in the scopes figured out. alias path to_s # Define a parameter in our resource. # if we ever receive a parameter named 'tag', set # the resource tags with its value. def set_parameter(param, value = nil) if ! param.is_a?(Puppet::Parser::Resource::Param) param = param.name if param.is_a?(Puppet::Pops::Resource::Param) param = Puppet::Parser::Resource::Param.new( :name => param, :value => value, :source => self.source ) end tag(*param.value) if param.name == :tag # And store it in our parameter hash. @parameters[param.name] = param end alias []= set_parameter def to_hash parse_title.merge(@parameters.reduce({}) do |result, (_, param)| value = param.value value = (:undef == value) ? nil : value unless value.nil? case param.name when :before, :subscribe, :notify, :require if value.is_a?(Array) value = value.flatten.reject {|v| v.nil? || :undef == v } end result[param.name] = value else result[param.name] = value end end result end) end # Convert this resource to a RAL resource. def to_ral copy_as_resource.to_ral end # Answers if this resource is tagged with at least one of the tags given in downcased string form. # # The method is a faster variant of the tagged? method that does no conversion of its # arguments. # # The match takes into account the tags that a resource will inherit from its container # but have not been set yet. # It does *not* take tags set via resource defaults as these will *never* be set on # the resource itself since all resources always have tags that are automatically # assigned. # # @param tag_array [Array[String]] list tags to look for # @return [Boolean] true if this instance is tagged with at least one of the provided tags # def raw_tagged?(tag_array) super || ((scope_resource = scope.resource) && !scope_resource.equal?(self) && scope_resource.raw_tagged?(tag_array)) end # Fills resource params from a capability # # This backs 'consumes => Sql[one]' # @api private def add_parameters_from_consume return if self[:consume].nil? map = {} [ self[:consume] ].flatten.map do |ref| # Assert that the ref really is a resource reference raise Puppet::Error, _("Invalid consume in %{value0}: %{ref} is not a resource") % { value0: self.ref, ref: ref } unless ref.is_a?(Puppet::Resource) # Resolve references t = ref.type t = Puppet::Pops::Evaluator::Runtime3ResourceSupport.find_resource_type(scope, t) unless t == 'class' || t == 'node' cap = catalog.resource(t, ref.title) if cap.nil? raise Puppet::Error, _("Resource %{ref} could not be found; it might not have been produced yet") % { ref: ref } end # Ensure that the found resource is a capability resource raise Puppet::Error, _("Invalid consume in %{ref}: %{cap} is not a capability resource") % { ref: ref, cap: cap } unless cap.resource_type.is_capability? cap end.each do |cns| # Establish mappings blueprint = resource_type.consumes.find do |bp| bp[:capability] == cns.type end # @todo lutter 2015-08-03: catch this earlier, can we do this during # static analysis ? raise _("Resource %{res} tries to consume %{cns} but no 'consumes' mapping exists for %{resource_type} and %{cns_type}") % { res: self, cns: cns, resource_type: self.resource_type, cns_type: cns.type } unless blueprint # setup scope that has, for each attr of cns, a binding to cns[attr] scope.with_global_scope do |global_scope| cns_scope = global_scope.newscope(:source => self, :resource => self) cns.to_hash.each { |name, value| cns_scope[name.to_s] = value } # evaluate mappings in that scope resource_type.arguments.keys.each do |name| if expr = blueprint[:mappings][name] # Explicit mapping value = expr.safeevaluate(cns_scope) else value = cns[name] end unless value.nil? # @todo lutter 2015-07-01: this should be caught by the checker # much earlier. We consume several capres, at least two of which # want to map to the same parameter (PUP-5080) raise _("Attempt to reassign attribute '%{name}' in '%{resource}' caused by multiple consumed mappings to the same attribute") % { name: name, resource: self } if map[name] map[name] = value end end end end map.each { |name, value| self[name] = value if self[name].nil? } end def offset nil end def pos nil end private def add_scope_tags scope_resource = scope.resource unless scope_resource.nil? || scope_resource.equal?(self) merge_tags_from(scope_resource) end end def replace_sensitive_data parameters.each_pair do |name, param| if param.value.is_a?(Puppet::Pops::Types::PSensitiveType::Sensitive) @sensitive_parameters << name param.value = param.value.unwrap end end end # Accept a parameter from an override. def override_parameter(param) # This can happen if the override is defining a new parameter, rather # than replacing an existing one. (set_parameter(param) and return) unless current = @parameters[param.name] # Parameter is already set - if overriding with a default - simply ignore the setting of the default value return if scope.is_default?(type, param.name, param.value) # The parameter is already set. Fail if they're not allowed to override it. unless param.source.child_of?(current.source) || param.source.equal?(current.source) && scope.is_default?(type, param.name, current.value) error_location_str = Puppet::Util::Errors.error_location(current.file, current.line) msg = if current.source.to_s == '' if error_location_str.empty? _("Parameter '%{name}' is already set on %{resource}; cannot redefine") % { name: param.name, resource: ref } else _("Parameter '%{name}' is already set on %{resource} at %{error_location}; cannot redefine") % { name: param.name, resource: ref, error_location: error_location_str } end else if error_location_str.empty? _("Parameter '%{name}' is already set on %{resource} by %{source}; cannot redefine") % { name: param.name, resource: ref, source: current.source.to_s } else _("Parameter '%{name}' is already set on %{resource} by %{source} at %{error_location}; cannot redefine") % { name: param.name, resource: ref, source: current.source.to_s, error_location: error_location_str } end end raise Puppet::ParseError.new(msg, param.file, param.line) end # If we've gotten this far, we're allowed to override. # Merge with previous value, if the parameter was generated with the +> # syntax. It's important that we use a copy of the new param instance # here, not the old one, and not the original new one, so that the source # is registered correctly for later overrides but the values aren't # implicitly shared when multiple resources are overridden at once (see # ticket #3556). if param.add param = param.dup param.value = [current.value, param.value].flatten end set_parameter(param) end # Make sure the resource's parameters are all valid for the type. def validate if builtin_type? begin @parameters.each { |name, value| validate_parameter(name) } rescue => detail self.fail Puppet::ParseError, detail.to_s + " on #{self}", detail end else resource_type.validate_resource(self) end end def extract_parameters(params) params.each do |param| # Don't set the same parameter twice self.fail Puppet::ParseError, _("Duplicate parameter '%{param}' for on %{resource}") % { param: param.name, resource: self } if @parameters[param.name] set_parameter(param) end end end ������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/scope.rb������������������������������������������������������������0000644�0052762�0001160�00000106526�13417161721�020041� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� # The scope class, which handles storing and retrieving variables and types and # such. require 'forwardable' require 'puppet/parser' require 'puppet/parser/templatewrapper' require 'puppet/parser/resource' require 'puppet/util/methodhelper' # This class is part of the internal parser/evaluator/compiler functionality of Puppet. # It is passed between the various classes that participate in evaluation. # None of its methods are API except those that are clearly marked as such. # # @api public class Puppet::Parser::Scope extend Forwardable include Puppet::Util::MethodHelper # Variables that always exist with nil value even if not set BUILT_IN_VARS = ['module_name'.freeze, 'caller_module_name'.freeze].freeze EMPTY_HASH = {}.freeze Puppet::Util.logmethods(self) include Puppet::Util::Errors attr_accessor :source, :resource attr_reader :compiler attr_accessor :parent # Hash of hashes of default values per type name attr_reader :defaults # Alias for `compiler.environment` def environment @compiler.environment end # Alias for `compiler.catalog` def catalog @compiler.catalog end # Abstract base class for LocalScope and MatchScope # class Ephemeral attr_reader :parent def initialize(parent = nil) @parent = parent end def is_local_scope? false end def [](name) if @parent @parent[name] end end def include?(name) (@parent and @parent.include?(name)) end def bound?(name) false end def add_entries_to(target = {}) @parent.add_entries_to(target) unless @parent.nil? # do not include match data ($0-$n) target end end class LocalScope < Ephemeral def initialize(parent=nil) super parent @symbols = {} end def [](name) val = @symbols[name] val.nil? && !@symbols.include?(name) ? super : val end def is_local_scope? true end def []=(name, value) @symbols[name] = value end def include?(name) bound?(name) || super end def delete(name) @symbols.delete(name) end def bound?(name) @symbols.include?(name) end def add_entries_to(target = {}) super @symbols.each do |k, v| if v == :undef || v.nil? target.delete(k) else target[ k ] = v end end target end end class MatchScope < Ephemeral attr_accessor :match_data def initialize(parent = nil, match_data = nil) super parent @match_data = match_data end def is_local_scope? false end def [](name) if bound?(name) @match_data[name.to_i] else super end end def include?(name) bound?(name) or super end def bound?(name) # A "match variables" scope reports all numeric variables to be bound if the scope has # match_data. Without match data the scope is transparent. # @match_data && name =~ /^\d+$/ end def []=(name, value) # TODO: Bad choice of exception raise Puppet::ParseError, _("Numerical variables cannot be changed. Attempt to set $%{name}") % { name: name } end def delete(name) # TODO: Bad choice of exception raise Puppet::ParseError, _("Numerical variables cannot be deleted: Attempt to delete: $%{name}") % { name: name } end def add_entries_to(target = {}) # do not include match data ($0-$n) super end end # @api private class ParameterScope < Ephemeral class Access attr_accessor :value def assigned? instance_variable_defined?(:@value) end end # A parameter default must be evaluated using a special scope. The scope that is given to this method must # have a `ParameterScope` as its last ephemeral scope. This method will then push a `MatchScope` while the # given `expression` is evaluated. The method will catch any throw of `:unevaluated_parameter` and produce # an error saying that the evaluated parameter X tries to access the unevaluated parameter Y. # # @param name [String] the name of the currently evaluated parameter # @param expression [Puppet::Parser::AST] the expression to evaluate # @param scope [Puppet::Parser::Scope] a scope where a `ParameterScope` has been pushed # @return [Object] the result of the evaluation # # @api private def evaluate3x(name, expression, scope) scope.with_guarded_scope do bad = catch(:unevaluated_parameter) do scope.new_match_scope(nil) return as_read_only { expression.safeevaluate(scope) } end parameter_reference_failure(name, bad) end end def evaluate(name, expression, scope, evaluator) scope.with_guarded_scope do bad = catch(:unevaluated_parameter) do scope.new_match_scope(nil) return as_read_only { evaluator.evaluate(expression, scope) } end parameter_reference_failure(name, bad) end end def parameter_reference_failure(from, to) # Parameters are evaluated in the order they have in the @params hash. keys = @params.keys raise Puppet::Error, _("%{callee}: expects a value for parameter $%{to}") % { callee: @callee_name, to: to } if keys.index(to) < keys.index(from) raise Puppet::Error, _("%{callee}: default expression for $%{from} tries to illegally access not yet evaluated $%{to}") % { callee: @callee_name, from: from, to: to } end private :parameter_reference_failure def initialize(parent, callee_name, param_names) super(parent) @callee_name = callee_name @params = {} param_names.each { |name| @params[name] = Access.new } end def [](name) access = @params[name] return super if access.nil? throw(:unevaluated_parameter, name) unless access.assigned? access.value end def []=(name, value) raise Puppet::Error, _("Attempt to assign variable %{name} when evaluating parameters") % { name: name } if @read_only @params[name] ||= Access.new @params[name].value = value end def bound?(name) @params.include?(name) end def include?(name) @params.include?(name) || super end def is_local_scope? true end def as_read_only read_only = @read_only @read_only = true begin yield ensure @read_only = read_only end end def to_hash Hash[@params.select {|_, access| access.assigned? }.map { |name, access| [name, access.value] }] end end # Returns true if the variable of the given name has a non nil value. # TODO: This has vague semantics - does the variable exist or not? # use ['name'] to get nil or value, and if nil check with exist?('name') # this include? is only useful because of checking against the boolean value false. # def include?(name) catch(:undefined_variable) { return ! self[name].nil? } false end # Returns true if the variable of the given name is set to any value (including nil) # # @return [Boolean] if variable exists or not # def exist?(name) # Note !! ensure the answer is boolean !! if name =~ /^(.*)::(.+)$/ class_name = $1 variable_name = $2 return true if class_name == '' && BUILT_IN_VARS.include?(variable_name) # lookup class, but do not care if it is not evaluated since that will result # in it not existing anyway. (Tests may run with just scopes and no evaluated classes which # will result in class_scope for "" not returning topscope). klass = find_hostclass(class_name) other_scope = klass.nil? ? nil : class_scope(klass) if other_scope.nil? class_name == '' ? compiler.topscope.exist?(variable_name) : false else other_scope.exist?(variable_name) end else next_scope = inherited_scope || enclosing_scope effective_symtable(true).include?(name) || next_scope && next_scope.exist?(name) || BUILT_IN_VARS.include?(name) end end # Returns true if the given name is bound in the current (most nested) scope for assignments. # def bound?(name) # Do not look in ephemeral (match scope), the semantics is to answer if an assignable variable is bound effective_symtable(false).bound?(name) end # Is the value true? This allows us to control the definition of truth # in one place. def self.true?(value) case value when '' false when :undef false else !!value end end # Coerce value to a number, or return `nil` if it isn't one. def self.number?(value) case value when Numeric value when /^-?\d+(:?\.\d+|(:?\.\d+)?e\d+)$/ value.to_f when /^0x[0-9a-f]+$/i value.to_i(16) when /^0[0-7]+$/ value.to_i(8) when /^-?\d+$/ value.to_i else nil end end def find_hostclass(name) environment.known_resource_types.find_hostclass(name) end def find_definition(name) environment.known_resource_types.find_definition(name) end def find_global_scope() # walk upwards until first found node_scope or top_scope if is_nodescope? || is_topscope? self else next_scope = inherited_scope || enclosing_scope if next_scope.nil? # this happens when testing, and there is only a single test scope and no link to any # other scopes self else next_scope.find_global_scope() end end end def findresource(type, title = nil) @compiler.catalog.resource(type, title) end # Initialize our new scope. Defaults to having no parent. def initialize(compiler, options = EMPTY_HASH) if compiler.is_a? Puppet::Parser::AbstractCompiler @compiler = compiler else raise Puppet::DevError, _("you must pass a compiler instance to a new scope object") end set_options(options) extend_with_functions_module # The symbol table for this scope. This is where we store variables. # @symtable = Ephemeral.new(nil, true) @symtable = LocalScope.new(nil) @ephemeral = [ MatchScope.new(@symtable, nil) ] # All of the defaults set for types. It's a hash of hashes, # with the first key being the type, then the second key being # the parameter. @defaults = Hash.new { |dhash,type| dhash[type] = {} } # The table for storing class singletons. This will only actually # be used by top scopes and node scopes. @class_scopes = {} end # Store the fact that we've evaluated a class, and store a reference to # the scope in which it was evaluated, so that we can look it up later. def class_set(name, scope) if parent parent.class_set(name, scope) else @class_scopes[name] = scope end end # Return the scope associated with a class. This is just here so # that subclasses can set their parent scopes to be the scope of # their parent class, and it's also used when looking up qualified # variables. def class_scope(klass) # They might pass in either the class or class name k = klass.respond_to?(:name) ? klass.name : klass @class_scopes[k] || (parent && parent.class_scope(k)) end # Collect all of the defaults set at any higher scopes. # This is a different type of lookup because it's # additive -- it collects all of the defaults, with defaults # in closer scopes overriding those in later scopes. # # The lookupdefaults searches in the the order: # # * inherited # * contained (recursive) # * self # def lookupdefaults(type) values = {} # first collect the values from the parents if parent parent.lookupdefaults(type).each { |var,value| values[var] = value } end # then override them with any current values # this should probably be done differently if @defaults.include?(type) @defaults[type].each { |var,value| values[var] = value } end values end # Check if the given value is a known default for the given type # def is_default?(type, key, value) defaults_for_type = @defaults[type] unless defaults_for_type.nil? default_param = defaults_for_type[key] return true if !default_param.nil? && value == default_param.value end !parent.nil? && parent.is_default?(type, key, value) end # Look up a defined type. def lookuptype(name) # This happens a lot, avoid making a call to make a call krt = environment.known_resource_types krt.find_definition(name) || krt.find_hostclass(name) end def undef_as(x,v) if v.nil? or v == :undef x else v end end # Lookup a variable within this scope using the Puppet language's # scoping rules. Variables can be qualified using just as in a # manifest. # # @param [String] name the variable name to lookup # @param [Hash] hash of options, only internal code should give this # @param [Boolean] if resolution is of the leaf of a qualified name - only internal code should give this # @return Object the value of the variable, or if not found; nil if `strict_variables` is false, and thrown :undefined_variable otherwise # # @api public def lookupvar(name, options = EMPTY_HASH) unless name.is_a? String raise Puppet::ParseError, _("Scope variable name %{name} is a %{klass}, not a string") % { name: name.inspect, klass: name.class } end # If name has '::' in it, it is resolved as a qualified variable unless (idx = name.index('::')).nil? # Always drop leading '::' if present as that is how the values are keyed. return lookup_qualified_variable(idx == 0 ? name[2..-1] : name, options) end # At this point, search is for a non qualified (simple) name table = @ephemeral.last val = table[name] return val unless val.nil? && !table.include?(name) next_scope = inherited_scope || enclosing_scope if next_scope next_scope.lookupvar(name, options) else variable_not_found(name) end end UNDEFINED_VARIABLES_KIND = 'undefined_variables'.freeze # The exception raised when a throw is uncaught is different in different versions # of ruby. In >=2.2.0 it is UncaughtThrowError (which did not exist prior to this) # UNCAUGHT_THROW_EXCEPTION = defined?(UncaughtThrowError) ? UncaughtThrowError : ArgumentError def variable_not_found(name, reason=nil) # Built in variables and numeric variables always exist if BUILT_IN_VARS.include?(name) || name =~ Puppet::Pops::Patterns::NUMERIC_VAR_NAME return nil end begin throw(:undefined_variable, reason) rescue UNCAUGHT_THROW_EXCEPTION case Puppet[:strict] when :off # do nothing when :warning Puppet.warn_once(UNDEFINED_VARIABLES_KIND, _("Variable: %{name}") % { name: name }, _("Undefined variable '%{name}'; %{reason}") % { name: name, reason: reason } ) when :error raise ArgumentError, _("Undefined variable '%{name}'; %{reason}") % { name: name, reason: reason } end end nil end # Retrieves the variable value assigned to the name given as an argument. The name must be a String, # and namespace can be qualified with '::'. The value is looked up in this scope, its parent scopes, # or in a specific visible named scope. # # @param varname [String] the name of the variable (may be a qualified name using `(ns'::')*varname` # @param options [Hash] Additional options, not part of api. # @return [Object] the value assigned to the given varname # @see #[]= # @api public # def [](varname, options = EMPTY_HASH) lookupvar(varname, options) end # The class scope of the inherited thing of this scope's resource. # # @return [Puppet::Parser::Scope] The scope or nil if there is not an inherited scope def inherited_scope if resource && resource.type == TYPENAME_CLASS && !resource.resource_type.parent.nil? qualified_scope(resource.resource_type.parent) else nil end end # The enclosing scope (topscope or nodescope) of this scope. # The enclosing scopes are produced when a class or define is included at # some point. The parent scope of the included class or define becomes the # scope in which it was included. The chain of parent scopes is followed # until a node scope or the topscope is found # # @return [Puppet::Parser::Scope] The scope or nil if there is no enclosing scope def enclosing_scope if has_enclosing_scope? if parent.is_topscope? || parent.is_nodescope? parent else parent.enclosing_scope end end end def is_classscope? resource && resource.type == TYPENAME_CLASS end def is_nodescope? resource && resource.type == TYPENAME_NODE end def is_topscope? equal?(@compiler.topscope) end # @api private def lookup_qualified_variable(fqn, options) table = @compiler.qualified_variables val = table[fqn] return val if !val.nil? || table.include?(fqn) # not found - search inherited scope for class leaf_index = fqn.rindex('::') unless leaf_index.nil? leaf_name = fqn[ (leaf_index+2)..-1 ] class_name = fqn[ 0, leaf_index ] begin qs = qualified_scope(class_name) unless qs.nil? return qs.get_local_variable(leaf_name) if qs.has_local_variable?(leaf_name) iscope = qs.inherited_scope return lookup_qualified_variable("#{iscope.source.name}::#{leaf_name}", options) unless iscope.nil? end rescue RuntimeError => e # because a failure to find the class, or inherited should be reported against given name return handle_not_found(class_name, leaf_name, options, e.message) end end # report with leading '::' by using empty class_name return handle_not_found('', fqn, options) end # @api private def has_local_variable?(name) @ephemeral.last.include?(name) end # @api private def get_local_variable(name) @ephemeral.last[name] end def handle_not_found(class_name, variable_name, position, reason = nil) unless Puppet[:strict_variables] # Do not issue warning if strict variables are on, as an error will be raised by variable_not_found location = if position[:lineproc] Puppet::Util::Errors.error_location_with_space(nil, position[:lineproc].call) else Puppet::Util::Errors.error_location_with_space(position[:file], position[:line]) end variable_not_found("#{class_name}::#{variable_name}", "#{reason}#{location}") return nil end variable_not_found("#{class_name}::#{variable_name}", reason) end def has_enclosing_scope? ! parent.nil? end private :has_enclosing_scope? def qualified_scope(classname) raise _("class %{classname} could not be found") % { classname: classname } unless klass = find_hostclass(classname) raise _("class %{classname} has not been evaluated") % { classname: classname } unless kscope = class_scope(klass) kscope end private :qualified_scope # Returns a Hash containing all variables and their values, optionally (and # by default) including the values defined in parent. Local values # shadow parent values. Ephemeral scopes for match results ($0 - $n) are not included. # def to_hash(recursive = true) if recursive and has_enclosing_scope? target = enclosing_scope.to_hash(recursive) if !(inherited = inherited_scope).nil? target.merge!(inherited.to_hash(recursive)) end else target = Hash.new end # add all local scopes @ephemeral.last.add_entries_to(target) target end # Create a new scope and set these options. def newscope(options = {}) compiler.newscope(self, options) end def parent_module_name return nil unless @parent && @parent.source @parent.source.module_name end # Set defaults for a type. The typename should already be downcased, # so that the syntax is isolated. We don't do any kind of type-checking # here; instead we let the resource do it when the defaults are used. def define_settings(type, params) table = @defaults[type] # if we got a single param, it'll be in its own array params = [params] unless params.is_a?(Array) params.each { |param| if table.include?(param.name) raise Puppet::ParseError.new(_("Default already defined for %{type} { %{param} }; cannot redefine") % { type: type, param: param.name }, param.file, param.line) end table[param.name] = param } end # Merge all settings for the given _env_name_ into this scope # @param env_name [Symbol] the name of the environment # @param set_in_this_scope [Boolean] if the settings variables should also be set in this instance of scope def merge_settings(env_name, set_in_this_scope=true) settings = Puppet.settings table = effective_symtable(false) global_table = compiler.qualified_variables all_local = {} settings.each_key do |name| next if :name == name key = name.to_s value = transform_setting(settings.value_sym(name, env_name)) if set_in_this_scope table[key] = value end all_local[key] = value # also write the fqn into global table for direct lookup global_table["settings::#{key}"] = value end # set the 'all_local' - a hash of all settings global_table["settings::all_local"] = all_local nil end def transform_setting(val) if val.is_a?(String) || val.is_a?(Numeric) || true == val || false == val || nil == val val elsif val.is_a?(Array) val.map {|entry| transform_setting(entry) } elsif val.is_a?(Hash) result = {} val.each {|k,v| result[transform_setting(k)] = transform_setting(v) } result else # not ideal, but required as there are settings values that are special :undef == val ? nil : val.to_s end end private :transform_setting VARNAME_TRUSTED = 'trusted'.freeze VARNAME_FACTS = 'facts'.freeze VARNAME_SERVER_FACTS = 'server_facts'.freeze RESERVED_VARIABLE_NAMES = [VARNAME_TRUSTED, VARNAME_FACTS].freeze TYPENAME_CLASS = 'Class'.freeze TYPENAME_NODE = 'Node'.freeze # Set a variable in the current scope. This will override settings # in scopes above, but will not allow variables in the current scope # to be reassigned. # It's preferred that you use self[]= instead of this; only use this # when you need to set options. def setvar(name, value, options = EMPTY_HASH) if name =~ /^[0-9]+$/ raise Puppet::ParseError.new(_("Cannot assign to a numeric match result variable '$%{name}'") % { name: name }) # unless options[:ephemeral] end unless name.is_a? String raise Puppet::ParseError, _("Scope variable name %{name} is a %{class_type}, not a string") % { name: name.inspect, class_type: name.class } end # Check for reserved variable names if (name == VARNAME_TRUSTED || name == VARNAME_FACTS) && !options[:privileged] raise Puppet::ParseError, _("Attempt to assign to a reserved variable name: '%{name}'") % { name: name } end # Check for server_facts reserved variable name if the trusted_sever_facts setting is true if name == VARNAME_SERVER_FACTS && !options[:privileged] && Puppet[:trusted_server_facts] raise Puppet::ParseError, _("Attempt to assign to a reserved variable name: '%{name}'") % { name: name } end table = effective_symtable(options[:ephemeral]) if table.bound?(name) error = Puppet::ParseError.new(_("Cannot reassign variable '$%{name}'") % { name: name }) error.file = options[:file] if options[:file] error.line = options[:line] if options[:line] raise error end table[name] = value # Assign the qualified name in the environment # Note that Settings scope has a source set to Boolean true. # # Only meaningful to set a fqn globally if table to assign to is the top of the scope's ephemeral stack if @symtable.equal?(table) if is_topscope? # the scope name is '::' compiler.qualified_variables[name] = value elsif source.is_a?(Puppet::Resource::Type) && source.type == :hostclass # the name is the name of the class sourcename = source.name compiler.qualified_variables["#{sourcename}::#{name}"] = value end end value end def set_trusted(hash) setvar('trusted', deep_freeze(hash), :privileged => true) end def set_facts(hash) setvar('facts', deep_freeze(hash), :privileged => true) end def set_server_facts(hash) setvar('server_facts', deep_freeze(hash), :privileged => true) end # Deeply freezes the given object. The object and its content must be of the types: # Array, Hash, Numeric, Boolean, Symbol, Regexp, NilClass, or String. All other types raises an Error. # (i.e. if they are assignable to Puppet::Pops::Types::Data type). # def deep_freeze(object) case object when Array object.each {|v| deep_freeze(v) } object.freeze when Hash object.each {|k, v| deep_freeze(k); deep_freeze(v) } object.freeze when NilClass, Numeric, TrueClass, FalseClass # do nothing when String object.freeze else raise Puppet::Error, _("Unsupported data type: '%{klass}'") % { klass: object.class } end object end private :deep_freeze # Return the effective "table" for setting variables. # This method returns the first ephemeral "table" that acts as a local scope, or this # scope's symtable. If the parameter `use_ephemeral` is true, the "top most" ephemeral "table" # will be returned (irrespective of it being a match scope or a local scope). # # @param use_ephemeral [Boolean] whether the top most ephemeral (of any kind) should be used or not def effective_symtable(use_ephemeral) s = @ephemeral[-1] return s || @symtable if use_ephemeral while s && !s.is_local_scope?() s = s.parent end s || @symtable end # Sets the variable value of the name given as an argument to the given value. The value is # set in the current scope and may shadow a variable with the same name in a visible outer scope. # It is illegal to re-assign a variable in the same scope. It is illegal to set a variable in some other # scope/namespace than the scope passed to a method. # # @param varname [String] The variable name to which the value is assigned. Must not contain `::` # @param value [String] The value to assign to the given variable name. # @param options [Hash] Additional options, not part of api and no longer used. # # @api public # def []=(varname, value, _ = nil) setvar(varname, value) end # Used mainly for logging def to_s # As this is used for logging, this should really not be done in this class at all... return "Scope(#{@resource})" unless @resource.nil? # For logging of function-scope - it is now showing the file and line. detail = Puppet::Pops::PuppetStack.top_of_stack return "Scope()" if detail.empty? # shorten the path if possible path = detail[0] env_path = nil env_path = environment.configuration.path_to_env unless (environment.nil? || environment.configuration.nil?) # check module paths first since they may be in the environment (i.e. they are longer) if module_path = environment.full_modulepath.detect {|m_path| path.start_with?(m_path) } path = "<module>" + path[module_path.length..-1] elsif env_path && path && path.start_with?(env_path) path = "<env>" + path[env_path.length..-1] end # Make the output appear as "Scope(path, line)" "Scope(#{[path, detail[1]].join(', ')})" end alias_method :inspect, :to_s # Pop ephemeral scopes up to level and return them # # @param level [Integer] a positive integer # @return [Array] the removed ephemeral scopes # @api private def pop_ephemerals(level) @ephemeral.pop(@ephemeral.size - level) end # Push ephemeral scopes onto the ephemeral scope stack # @param ephemeral_scopes [Array] # @api private def push_ephemerals(ephemeral_scopes) ephemeral_scopes.each { |ephemeral_scope| @ephemeral.push(ephemeral_scope) } unless ephemeral_scopes.nil? end def ephemeral_level @ephemeral.size end # TODO: Who calls this? def new_ephemeral(local_scope = false) if local_scope @ephemeral.push(LocalScope.new(@ephemeral.last)) else @ephemeral.push(MatchScope.new(@ephemeral.last, nil)) end end # Execute given block in global scope with no ephemerals present # # @yieldparam [Scope] global_scope the global and ephemeral less scope # @return [Object] the return of the block # # @api private def with_global_scope(&block) find_global_scope.without_ephemeral_scopes(&block) end # Execute given block with a ephemeral scope containing the given variables # @api private def with_local_scope(scope_variables) local = LocalScope.new(@ephemeral.last) scope_variables.each_pair { |k, v| local[k] = v } @ephemeral.push(local) begin yield(self) ensure @ephemeral.pop end end # Execute given block with all ephemeral popped from the ephemeral stack # # @api private def without_ephemeral_scopes save_ephemeral = @ephemeral begin @ephemeral = [ @symtable ] yield(self) ensure @ephemeral = save_ephemeral end end # Nests a parameter scope # @param [String] callee_name the name of the function, template, or resource that defines the parameters # @param [Array<String>] param_names list of parameter names # @yieldparam [ParameterScope] param_scope the nested scope # @api private def with_parameter_scope(callee_name, param_names) param_scope = ParameterScope.new(@ephemeral.last, callee_name, param_names) with_guarded_scope do @ephemeral.push(param_scope) yield(param_scope) end end # Execute given block and ensure that ephemeral level is restored # # @return [Object] the return of the block # # @api private def with_guarded_scope elevel = ephemeral_level begin yield ensure pop_ephemerals(elevel) end end # Sets match data in the most nested scope (which always is a MatchScope), it clobbers match data already set there # def set_match_data(match_data) @ephemeral.last.match_data = match_data end # Nests a match data scope def new_match_scope(match_data) @ephemeral.push(MatchScope.new(@ephemeral.last, match_data)) end def ephemeral_from(match, file = nil, line = nil) case match when Hash # Create local scope ephemeral and set all values from hash new_ephemeral(true) match.each {|k,v| setvar(k, v, :file => file, :line => line, :ephemeral => true) } # Must always have an inner match data scope (that starts out as transparent) # In 3x slightly wasteful, since a new nested scope is created for a match # (TODO: Fix that problem) new_ephemeral(false) else raise(ArgumentError,_("Invalid regex match data. Got a %{klass}") % { klass: match.class }) unless match.is_a?(MatchData) # Create a match ephemeral and set values from match data new_match_scope(match) end end # @api private def find_resource_type(type) raise Puppet::DevError, _("Scope#find_resource_type() is no longer supported, use Puppet::Pops::Evaluator::Runtime3ResourceSupport instead") end # @api private def find_builtin_resource_type(type) raise Puppet::DevError, _("Scope#find_builtin_resource_type() is no longer supported, use Puppet::Pops::Evaluator::Runtime3ResourceSupport instead") end # @api private def find_defined_resource_type(type) raise Puppet::DevError, _("Scope#find_defined_resource_type() is no longer supported, use Puppet::Pops::Evaluator::Runtime3ResourceSupport instead") end def method_missing(method, *args, &block) method.to_s =~ /^function_(.*)$/ name = $1 super unless name super unless Puppet::Parser::Functions.function(name) # In odd circumstances, this might not end up defined by the previous # method, so we might as well be certain. if respond_to? method send(method, *args) else raise Puppet::DevError, _("Function %{name} not defined despite being loaded!") % { name: name } end end # To be removed when enough time has passed after puppet 5.0.0 # @api private def resolve_type_and_titles(type, titles) raise Puppet::DevError, _("Scope#resolve_type_and_title() is no longer supported, use Puppet::Pops::Evaluator::Runtime3ResourceSupport instead") end # Transforms references to classes to the form suitable for # lookup in the compiler. # # Makes names passed in the names array absolute if they are relative. # # Transforms Class[] and Resource[] type references to class name # or raises an error if a Class[] is unspecific, if a Resource is not # a 'class' resource, or if unspecific (no title). # # # @param names [Array<String>] names to (optionally) make absolute # @return [Array<String>] names after transformation # def transform_and_assert_classnames(names) names.map do |name| case name when NilClass raise ArgumentError, _("Cannot use undef as a class name") when String raise ArgumentError, _("Cannot use empty string as a class name") if name.empty? name.sub(/^([^:]{1,2})/, '::\1') when Puppet::Resource assert_class_and_title(name.type, name.title) name.title.sub(/^([^:]{1,2})/, '::\1') when Puppet::Pops::Types::PClassType #TRANSLATORS "Class" and "Type" are Puppet keywords and should not be translated raise ArgumentError, _("Cannot use an unspecific Class[] Type") unless name.class_name name.class_name.sub(/^([^:]{1,2})/, '::\1') when Puppet::Pops::Types::PResourceType assert_class_and_title(name.type_name, name.title) name.title.sub(/^([^:]{1,2})/, '::\1') end.downcase end end # Calls a 3.x or 4.x function by name with arguments given in an array using the 4.x calling convention # and returns the result. # Note that it is the caller's responsibility to rescue the given ArgumentError and provide location information # to aid the user find the problem. The problem is otherwise reported against the source location that # invoked the function that ultimately called this method. # # @return [Object] the result of the called function # @raise ArgumentError if the function does not exist def call_function(func_name, args, &block) Puppet::Pops::Parser::EvaluatingParser.new.evaluator.external_call_function(func_name, args, self, &block) end private def assert_class_and_title(type_name, title) if type_name.nil? || type_name == '' #TRANSLATORS "Resource" is a class name and should not be translated raise ArgumentError, _("Cannot use an unspecific Resource[] where a Resource['class', name] is expected") end unless type_name =~ /^[Cc]lass$/ #TRANSLATORS "Resource" is a class name and should not be translated raise ArgumentError, _("Cannot use a Resource[%{type_name}] where a Resource['class', name] is expected") % { type_name: type_name } end if title.nil? #TRANSLATORS "Resource" is a class name and should not be translated raise ArgumentError, _("Cannot use an unspecific Resource['class'] where a Resource['class', name] is expected") end end def extend_with_functions_module root = Puppet.lookup(:root_environment) extend Puppet::Parser::Functions.environment_module(root) extend Puppet::Parser::Functions.environment_module(environment) if environment != root end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/parser/script_compiler.rb��������������������������������������������������0000644�0052762�0001160�00000007574�13417161721�022131� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/loaders' require 'puppet/pops' # A Script "compiler" that does not support catalog operations # # The Script compiler is "one shot" - it does not support rechecking if underlying source has changed or # deal with possible errors in a cached environment. # class Puppet::Parser::ScriptCompiler # Allows the ScriptCompiler to use the 3.x Scope class without being an actual "Compiler" # include Puppet::Parser::AbstractCompiler # @api private attr_reader :topscope # @api private attr_reader :qualified_variables # Access to the configured loaders for 4x # @return [Puppet::Pops::Loader::Loaders] the configured loaders # @api private attr_reader :loaders # @api private attr_reader :environment # @api private attr_reader :node_name def with_context_overrides(description = '', &block) Puppet.override( @context_overrides , description, &block) end # Evaluates the configured setup for a script + code in an environment with modules # def compile Puppet[:strict_variables] = true Puppet[:strict] = :error # TRANSLATORS, "For running script" is not user facing Puppet.override( @context_overrides , "For running script") do #TRANSLATORS "main" is a function name and should not be translated result = Puppet::Util::Profiler.profile(_("Script: Evaluated main"), [:script, :evaluate_main]) { evaluate_main } if block_given? yield self else result end end rescue Puppet::ParseErrorWithIssue => detail detail.node = node_name Puppet.log_exception(detail) raise rescue => detail message = "#{detail} on node #{node_name}" Puppet.log_exception(detail, message) raise Puppet::Error, message, detail.backtrace end # Constructs the overrides for the context def context_overrides() { :current_environment => environment, :global_scope => @topscope, # 4x placeholder for new global scope :loaders => @loaders, # 4x loaders } end # Create a script compiler for the given environment where errors are logged as coming # from the given node_name # def initialize(environment, node_name) @environment = environment @node_name = node_name # Create the initial scope, it is needed early @topscope = Puppet::Parser::Scope.new(self) # Initialize loaders and Pcore @loaders = Puppet::Pops::Loaders.new(environment) # Need to compute overrides here, and remember them, because we are about to # Expensive entries in the context are bound lazily. @context_overrides = context_overrides() # Resolutions of fully qualified variable names @qualified_variables = {} end # Having multiple named scopes hanging from top scope is not supported when scripting # in the regular compiler this is used to create one named scope per class. # When scripting, the "main class" is just a container of the top level code to evaluate # and it is not evaluated as a class added to a catalog. Since classes are not supported # there is no need to support the concept of "named scopes" as all variables are local # or in the top scope itself (notably, the $settings:: namespace is initialized # as just a set of variables in that namespace - there is no named scope for 'settings' # when scripting. # # Keeping this method here to get specific error as being unsure if there are functions/logic # that will call this. The AbstractCompiler defines this method, but maybe it does not # have to (TODO). # def newscope(parent, options = {}) raise _('having multiple named scopes is not supported when scripting') end private # Find and evaluate the top level code. def evaluate_main @loaders.pre_load program = @loaders.load_main_manifest return program.nil? ? nil : Puppet::Pops::Parser::EvaluatingParser.singleton.evaluator.evaluate(program, @topscope) end end ������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/plugins.rb�����������������������������������������������������������������0000644�0052762�0001160�00000000373�13417161721�017106� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The Puppet Plugins module defines extension points where plugins can be configured # to add or modify puppet's behavior. See the respective classes in this module for more # details. # # @api public # @since Puppet 4.0.0 # module Puppet::Plugins end���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/plugins/�������������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�016563� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/plugins/syntax_checkers.rb�������������������������������������������������0000644�0052762�0001160�00000013235�13417161721�022304� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Plugins module SyntaxCheckers # The lookup **key** for the multibind containing syntax checkers used to syntax check embedded string in non # puppet DSL syntax. # @api public SYNTAX_CHECKERS_KEY = :'puppet::syntaxcheckers' # SyntaxChecker is a Puppet Extension Point for the purpose of extending Puppet with syntax checkers. # The intended use is to create a class derived from this class and then register it with the Puppet context # # Creating the Extension Class # ---------------------------- # As an example, a class for checking custom xml (aware of some custom schemes) may be authored in # say a puppet module called 'exampleorg/xmldata'. The name of the class should start with `Puppetx::<user>::<module>`, # e.g. 'Puppetx::Exampleorg::XmlData::XmlChecker" and # be located in `lib/puppetx/exampleorg/xml_data/xml_checker.rb`. The Puppet Binder will auto-load this file when it # has a binding to the class `Puppetx::Exampleorg::XmlData::XmlChecker' # The Ruby Module `Puppetx` is created by Puppet, the remaining modules should be created by the loaded logic - e.g.: # # @example Defining an XmlChecker # module Puppetx::Exampleorg # module XmlData # class XmlChecker < Puppetx::Puppetlabs::SyntaxCheckers::SyntaxChecker # def check(text, syntax_identifier, acceptor, location_hash) # # do the checking # end # end # end # end # # Implementing the check method # ----------------------------- # The implementation of the {#check} method should naturally perform syntax checking of the given text/string and # produce found issues on the given `acceptor`. These can be warnings or errors. The method should return `false` if # any warnings or errors were produced (it is up to the caller to check for error/warning conditions and report them # to the user). # # Issues are reported by calling the given `acceptor`, which takes a severity (e.g. `:error`, # or `:warning), an {Puppet::Pops::Issues::Issue} instance, and a {Puppet::Pops::Adapters::SourcePosAdapter} # (which describes details about linenumber, position, and length of the problem area). Note that the # `location_info` given to the check method holds information about the location of the string in its *container* # (e.g. the source position of a Heredoc); this information can be used if more detailed information is not # available, or combined if there are more details (relative to the start of the checked string). # # @example Reporting an issue # # create an issue with a symbolic name (that can serve as a reference to more details about the problem), # # make the name unique # issue = Puppet::Pops::Issues::issue(:EXAMPLEORG_XMLDATA_ILLEGAL_XML) { "syntax error found in xml text" } # source_pos = Puppet::Pops::Adapters::SourcePosAdapter.new() # source_pos.line = info[:line] # use this if there is no detail from the used parser # source_pos.pos = info[:pos] # use this pos if there is no detail from used parser # # # report it # acceptor.accept(Puppet::Pops::Validation::Diagnostic.new(:error, issue, info[:file], source_pos, {})) # # There is usually a cap on the number of errors/warnings that are presented to the user, this is handled by the # reporting logic, but care should be taken to not generate too many as the issues are kept in memory until # the checker returns. The acceptor may set a limit and simply ignore issues past a certain (high) number of reported # issues (this number is typically higher than the cap on issues reported to the user). # # The `syntax_identifier` # ----------------------- # The extension makes use of a syntax identifier written in mime-style. This identifier can be something simple # as 'xml', or 'json', but can also consist of several segments joined with '+' where the most specific syntax variant # is placed first. When searching for a syntax checker; say for JSON having some special traits, say 'userdata', the # author of the text may indicate this as the text having the syntax "userdata+json" - when a checker is looked up it is # first checked if there is a checker for "userdata+json", if none is found, a lookup is made for "json" (since the text # must at least be valid json). The given identifier is passed to the checker (to allow the same checker to check for # several dialects/specializations). # # Use in Puppet DSL # ----------------- # The Puppet DSL Heredoc support makes use of the syntax checker extension. A user of a # heredoc can specify the syntax in the heredoc tag, e.g.`@(END:userdata+json)`. # # # @abstract # class SyntaxChecker # Checks the text for syntax issues and reports them to the given acceptor. # This implementation is abstract, it raises {NotImplementedError} since a subclass should have implemented the # method. # # @param text [String] The text to check # @param syntax_identifier [String] The syntax identifier in mime style (e.g. 'json', 'json-patch+json', 'xml', 'myapp+xml' # @option location_info [String] :file The filename where the string originates # @option location_info [Integer] :line The line number identifying the location where the string is being used/checked # @option location_info [Integer] :position The position on the line identifying the location where the string is being used/checked # @return [Boolean] Whether the checked string had issues (warnings and/or errors) or not. # @api public # def check(text, syntax_identifier, acceptor, location_info) raise NotImplementedError, "The class #{self.class.name} should have implemented the method check()" end end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/plugins/configuration.rb���������������������������������������������������0000644�0052762�0001160�00000001204�13417161721�021747� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Configures the Puppet Plugins, by registering extension points # and default implementations. # # See the respective configured services for more information. # # @api private # module Puppet::Plugins module Configuration require 'puppet/plugins/syntax_checkers' require 'puppet/syntax_checkers/base64' require 'puppet/syntax_checkers/json' def self.load_plugins # Register extensions # ------------------- { SyntaxCheckers::SYNTAX_CHECKERS_KEY => { 'json' => Puppet::SyntaxCheckers::Json.new, 'base64' => Puppet::SyntaxCheckers::Base64.new } } end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/����������������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�016063� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/adaptable.rb����������������������������������������������������������0000644�0052762�0001160�00000017630�13417161721�020327� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Adaptable is a mix-in module that adds adaptability to a class. # This means that an adapter can # associate itself with an instance of the class and store additional data/have behavior. # # This mechanism should be used when there is a desire to keep implementation concerns separate. # In Ruby it is always possible to open and modify a class or instance to teach it new tricks, but it # is however not possible to do this for two different versions of some service at the same time. # The Adaptable pattern is also good when only a few of the objects of some class needs to have extra # information (again possible in Ruby by adding instance variables dynamically). In fact, the implementation # of Adaptable does just that; it adds an instance variable named after the adapter class and keeps an # instance of this class in this slot. # # @note the implementation details; the fact that an instance variable is used to keep the adapter # instance data should not # be exploited as the implementation of _being adaptable_ may change in the future. # @api private # module Puppet::Pops module Adaptable # Base class for an Adapter. # # A typical adapter just defines some accessors. # # A more advanced adapter may need to setup the adapter based on the object it is adapting. # @example Making Duck adaptable # class Duck # include Puppet::Pops::Adaptable # end # @example Giving a Duck a nick name # class NickNameAdapter < Puppet::Pops::Adaptable::Adapter # attr_accessor :nick_name # end # d = Duck.new # NickNameAdapter.adapt(d).nick_name = "Daffy" # NickNameAdapter.get(d).nick_name # => "Daffy" # # @example Giving a Duck a more elaborate nick name # class NickNameAdapter < Puppet::Pops::Adaptable::Adapter # attr_accessor :nick_name, :object # def initialize o # @object = o # @nick_name = "Yo" # end # def nick_name # "#{@nick_name}, the #{o.class.name}" # end # def NickNameAdapter.create_adapter(o) # x = new o # x # end # end # d = Duck.new # n = NickNameAdapter.adapt(d) # n.nick_name # => "Yo, the Duck" # n.nick_name = "Daffy" # n.nick_name # => "Daffy, the Duck" # @example Using a block to set values # NickNameAdapter.adapt(o) { |a| a.nick_name = "Buddy!" } # NickNameAdapter.adapt(o) { |a, o| a.nick_name = "You're the best #{o.class.name} I met."} # class Adapter # Returns an existing adapter for the given object, or nil, if the object is not # adapted. # # @param o [Adaptable] object to get adapter from # @return [Adapter<self>] an adapter of the same class as the receiver of #get # @return [nil] if the given object o has not been adapted by the receiving adapter # @raise [ArgumentError] if the object is not adaptable # def self.get(o) attr_name = self_attr_name if o.instance_variable_defined?(attr_name) o.instance_variable_get(attr_name) else nil end end # Returns an existing adapter for the given object, or creates a new adapter if the # object has not been adapted, or the adapter has been cleared. # # @example Using a block to set values # NickNameAdapter.adapt(o) { |a| a.nick_name = "Buddy!" } # NickNameAdapter.adapt(o) { |a, o| a.nick_name = "Your the best #{o.class.name} I met."} # @overload adapt(o) # @overload adapt(o, {|adapter| block}) # @overload adapt(o, {|adapter, o| block}) # @param o [Adaptable] object to add adapter to # @yieldparam adapter [Adapter<self>] the created adapter # @yieldparam o [Adaptable] optional, the given adaptable # @param block [Proc] optional, evaluated in the context of the adapter (existing or new) # @return [Adapter<self>] an adapter of the same class as the receiver of the call # @raise [ArgumentError] if the given object o is not adaptable # def self.adapt(o, &block) attr_name = self_attr_name adapter = if o.instance_variable_defined?(attr_name) && value = o.instance_variable_get(attr_name) value else associate_adapter(create_adapter(o), o) end if block_given? case block.arity when 1 block.call(adapter) else block.call(adapter, o) end end adapter end # Creates a new adapter, associates it with the given object and returns the adapter. # # @example Using a block to set values # NickNameAdapter.adapt_new(o) { |a| a.nick_name = "Buddy!" } # NickNameAdapter.adapt_new(o) { |a, o| a.nick_name = "Your the best #{o.class.name} I met."} # This is used when a fresh adapter is wanted instead of possible returning an # existing adapter as in the case of {Adapter.adapt}. # @overload adapt_new(o) # @overload adapt_new(o, {|adapter| block}) # @overload adapt_new(o, {|adapter, o| block}) # @yieldparam adapter [Adapter<self>] the created adapter # @yieldparam o [Adaptable] optional, the given adaptable # @param o [Adaptable] object to add adapter to # @param block [Proc] optional, evaluated in the context of the new adapter # @return [Adapter<self>] an adapter of the same class as the receiver of the call # @raise [ArgumentError] if the given object o is not adaptable # def self.adapt_new(o, &block) adapter = associate_adapter(create_adapter(o), o) if block_given? case block.arity when 1 block.call(adapter) else block.call(adapter, o) end end adapter end # Clears the adapter set in the given object o. Returns any set adapter or nil. # @param o [Adaptable] the object where the adapter should be cleared # @return [Adapter] if an adapter was set # @return [nil] if the adapter has not been set # def self.clear(o) attr_name = self_attr_name if o.instance_variable_defined?(attr_name) o.send(:remove_instance_variable, attr_name) else nil end end # This base version creates an instance of the class (i.e. an instance of the concrete subclass # of Adapter). A Specialization may want to create an adapter instance specialized for the given target # object. # @param o [Adaptable] The object to adapt. This implementation ignores this variable, but a # specialization may want to initialize itself differently depending on the object it is adapting. # @return [Adapter<self>] instance of the subclass of Adapter receiving the call # def self.create_adapter(o) new end # Associates the given adapter with the given target object # @param adapter [Adapter] the adapter to associate with the given object _o_ # @param o [Adaptable] the object to adapt # @return [adapter] the given adapter # def self.associate_adapter(adapter, o) o.instance_variable_set(self_attr_name, adapter) adapter end # Returns a suitable instance variable name given a class name. # The returned string is the fully qualified name of a class with '::' replaced by '_' since # '::' is not allowed in an instance variable name. # @param name [String] the fully qualified name of a class # @return [String] the name with all '::' replaced by '_' # @api private # def self.instance_var_name(name) name.split(DOUBLE_COLON).join(USCORE) end # Returns the name of the class, or the name of the type if the class represents an Object type # @return [String] the name of the class or type def self.type_name self.name end # Returns a suitable instance variable name for the _name_ of this instance. The name is created by calling # Adapter#instance_var_name and then cached. # @return [String] the instance variable name for _name_ # @api private # def self.self_attr_name @attr_name_sym ||= :"@#{instance_var_name(type_name)}" end end end end ��������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/evaluator/������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020065� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/evaluator/access_operator.rb������������������������������������������0000644�0052762�0001160�00000062327�13417161721�023573� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Evaluator # AccessOperator handles operator [] # This operator is part of evaluation. # class AccessOperator # Provides access to the Puppet 3.x runtime (scope, etc.) # This separation has been made to make it easier to later migrate the evaluator to an improved runtime. # include Runtime3Support attr_reader :semantic # Initialize with AccessExpression to enable reporting issues # @param access_expression [Model::AccessExpression] the semantic object being evaluated # @return [void] # def initialize(access_expression) @@access_visitor ||= Visitor.new(self, "access", 2, nil) @semantic = access_expression end def access (o, scope, *keys) @@access_visitor.visit_this_2(self, o, scope, keys) end protected def access_Object(o, scope, keys) type = Puppet::Pops::Types::TypeCalculator.infer(o) if type.is_a?(Puppet::Pops::Types::TypeWithMembers) access_func = type['[]'] return access_func.invoke(o, scope, keys) unless access_func.nil? end fail(Issues::OPERATOR_NOT_APPLICABLE, @semantic.left_expr, :operator=>'[]', :left_value => o) end def access_Binary(o, scope, keys) Puppet::Pops::Types::PBinaryType::Binary.from_binary_string(access_String(o.binary_buffer, scope, keys)) end def access_String(o, scope, keys) keys.flatten! result = case keys.size when 0 fail(Issues::BAD_STRING_SLICE_ARITY, @semantic.left_expr, {:actual => keys.size}) when 1 # Note that Ruby 1.8.7 requires a length of 1 to produce a String k1 = Utils.to_n(keys[0]) bad_string_access_key_type(o, 0, k1.nil? ? keys[0] : k1) unless k1.is_a?(Integer) k2 = 1 k1 = k1 < 0 ? o.length + k1 : k1 # abs pos # if k1 is outside, a length of 1 always produces an empty string if k1 < 0 EMPTY_STRING else o[ k1, k2 ] end when 2 k1 = Utils.to_n(keys[0]) k2 = Utils.to_n(keys[1]) [k1, k2].each_with_index { |k,i| bad_string_access_key_type(o, i, k.nil? ? keys[i] : k) unless k.is_a?(Integer) } k1 = k1 < 0 ? o.length + k1 : k1 # abs pos (negative is count from end) k2 = k2 < 0 ? o.length - k1 + k2 + 1 : k2 # abs length (negative k2 is length from pos to end count) # if k1 is outside, adjust to first position, and adjust length if k1 < 0 k2 = k2 + k1 k1 = 0 end o[ k1, k2 ] else fail(Issues::BAD_STRING_SLICE_ARITY, @semantic.left_expr, {:actual => keys.size}) end # Specified as: an index outside of range, or empty result == empty string (result.nil? || result.empty?) ? EMPTY_STRING : result end # Parameterizes a PRegexp Type with a pattern string or r ruby egexp # def access_PRegexpType(o, scope, keys) keys.flatten! unless keys.size == 1 blamed = keys.size == 0 ? @semantic : @semantic.keys[1] fail(Issues::BAD_TYPE_SLICE_ARITY, blamed, :base_type => o, :min=>1, :actual => keys.size) end assert_keys(keys, o, 1, 1, String, Regexp) Types::TypeFactory.regexp(*keys) end # Evaluates <ary>[] with 1 or 2 arguments. One argument is an index lookup, two arguments is a slice from/to. # def access_Array(o, scope, keys) keys.flatten! case keys.size when 0 fail(Issues::BAD_ARRAY_SLICE_ARITY, @semantic.left_expr, {:actual => keys.size}) when 1 key = coerce_numeric(keys[0], @semantic.keys[0], scope) unless key.is_a?(Integer) bad_access_key_type(o, 0, key, Integer) end o[key] when 2 # A slice [from, to] with support for -1 to mean start, or end respectively. k1 = coerce_numeric(keys[0], @semantic.keys[0], scope) k2 = coerce_numeric(keys[1], @semantic.keys[1], scope) [k1, k2].each_with_index { |k,i| bad_access_key_type(o, i, k, Integer) unless k.is_a?(Integer) } # Help confused Ruby do the right thing (it truncates to the right, but negative index + length can never overlap # the available range. k1 = k1 < 0 ? o.length + k1 : k1 # abs pos (negative is count from end) k2 = k2 < 0 ? o.length - k1 + k2 + 1 : k2 # abs length (negative k2 is length from pos to end count) # if k1 is outside, adjust to first position, and adjust length if k1 < 0 k2 = k2 + k1 k1 = 0 end # Help ruby always return empty array when asking for a sub array result = o[ k1, k2 ] result.nil? ? [] : result else fail(Issues::BAD_ARRAY_SLICE_ARITY, @semantic.left_expr, {:actual => keys.size}) end end # Evaluates <hsh>[] with support for one or more arguments. If more than one argument is used, the result # is an array with each lookup. # @note # Does not flatten its keys to enable looking up with a structure # def access_Hash(o, scope, keys) # Look up key in hash, if key is nil, try alternate form (:undef) before giving up. # This is done because the hash may have been produced by 3x logic and may thus contain :undef. result = keys.collect do |k| o.fetch(k) { |key| key.nil? ? o[:undef] : nil } end case result.size when 0 fail(Issues::BAD_HASH_SLICE_ARITY, @semantic.left_expr, {:actual => keys.size}) when 1 result.pop else # remove nil elements and return result.compact! result end end def access_PBooleanType(o, scope, keys) keys.flatten! assert_keys(keys, o, 1, 1, TrueClass, FalseClass) Types::TypeFactory.boolean(keys[0]) end def access_PEnumType(o, scope, keys) keys.flatten! last = keys.last case_insensitive = false if last == true || last == false keys = keys[0...-1] case_insensitive = last end assert_keys(keys, o, 1, Float::INFINITY, String) Types::PEnumType.new(keys, case_insensitive) end def access_PVariantType(o, scope, keys) keys.flatten! assert_keys(keys, o, 1, Float::INFINITY, Types::PAnyType) Types::TypeFactory.variant(*keys) end def access_PSemVerType(o, scope, keys) keys.flatten! assert_keys(keys, o, 1, Float::INFINITY, String, SemanticPuppet::VersionRange) Types::TypeFactory.sem_ver(*keys) end def access_PTimestampType(o, scope, keys) keys.flatten! fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, :base_type => o, :min=>0, :max => 2, :actual => keys.size) if keys.size > 2 Types::TypeFactory.timestamp(*keys) end def access_PTimespanType(o, scope, keys) keys.flatten! fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, :base_type => o, :min=>0, :max => 2, :actual => keys.size) if keys.size > 2 Types::TypeFactory.timespan(*keys) end def access_PTupleType(o, scope, keys) keys.flatten! if Types::TypeFactory.is_range_parameter?(keys[-2]) && Types::TypeFactory.is_range_parameter?(keys[-1]) size_type = Types::TypeFactory.range(keys[-2], keys[-1]) keys = keys[0, keys.size - 2] elsif Types::TypeFactory.is_range_parameter?(keys[-1]) size_type = Types::TypeFactory.range(keys[-1], :default) keys = keys[0, keys.size - 1] end assert_keys(keys, o, 1, Float::INFINITY, Types::PAnyType) Types::TypeFactory.tuple(keys, size_type) end def access_PCallableType(o, scope, keys) if keys.size > 0 && keys[0].is_a?(Array) unless keys.size == 2 fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, :base_type => o, :min=>2, :max => 2, :actual => keys.size) end unless keys[1].is_a?(Types::PAnyType) bad_type_specialization_key_type(o, 1, k, Types::PAnyType) end end Types::TypeFactory.callable(*keys) end def access_PStructType(o, scope, keys) assert_keys(keys, o, 1, 1, Hash) Types::TypeFactory.struct(keys[0]) end def access_PStringType(o, scope, keys) keys.flatten! case keys.size when 1 size_t = collection_size_t(0, keys[0]) when 2 size_t = collection_size_t(0, keys[0], keys[1]) else fail(Issues::BAD_STRING_SLICE_ARITY, @semantic, {:actual => keys.size}) end Types::TypeFactory.string(size_t) end # Asserts type of each key and calls fail with BAD_TYPE_SPECIFICATION # @param keys [Array<Object>] the evaluated keys # @param o [Object] evaluated LHS reported as :base_type # @param min [Integer] the minimum number of keys (typically 1) # @param max [Numeric] the maximum number of keys (use same as min, specific number, or Float::INFINITY) # @param allowed_classes [Class] a variable number of classes that each key must be an instance of (any) # @api private # def assert_keys(keys, o, min, max, *allowed_classes) size = keys.size unless size.between?(min, max || Float::INFINITY) fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, :base_type => o, :min=>1, :max => max, :actual => keys.size) end keys.each_with_index do |k, i| unless allowed_classes.any? {|clazz| k.is_a?(clazz) } bad_type_specialization_key_type(o, i, k, *allowed_classes) end end end def bad_access_key_type(lhs, key_index, actual, *expected_classes) fail(Issues::BAD_SLICE_KEY_TYPE, @semantic.keys[key_index], { :left_value => lhs, :actual => bad_key_type_name(actual), :expected_classes => expected_classes }) end def bad_string_access_key_type(lhs, key_index, actual) fail(Issues::BAD_STRING_SLICE_KEY_TYPE, @semantic.keys[key_index], { :left_value => lhs, :actual_type => bad_key_type_name(actual), }) end def bad_key_type_name(actual) case actual when nil 'Undef' when :default 'Default' else Types::TypeCalculator.generalize(Types::TypeCalculator.infer(actual)).to_s end end def bad_type_specialization_key_type(type, key_index, actual, *expected_classes) label_provider = Model::ModelLabelProvider.new() expected = expected_classes.map {|c| label_provider.label(c) }.join(' or ') fail(Issues::BAD_TYPE_SPECIALIZATION, @semantic.keys[key_index], { :type => type, :message => _("Cannot use %{key} where %{expected} is expected") % { key: bad_key_type_name(actual), expected: expected } }) end def access_PPatternType(o, scope, keys) keys.flatten! assert_keys(keys, o, 1, Float::INFINITY, String, Regexp, Types::PPatternType, Types::PRegexpType) Types::TypeFactory.pattern(*keys) end def access_PURIType(o, scope, keys) keys.flatten! if keys.size == 1 param = keys[0] unless Types::PURIType::TYPE_URI_PARAM_TYPE.instance?(param) fail(Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'URI-Type', :actual => param.class}) end Types::PURIType.new(param) else fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'URI-Type', :min => 1, :actual => keys.size}) end end def access_POptionalType(o, scope, keys) keys.flatten! if keys.size == 1 type = keys[0] unless type.is_a?(Types::PAnyType) if type.is_a?(String) type = Types::TypeFactory.string(type) else fail(Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'Optional-Type', :actual => type.class}) end end Types::POptionalType.new(type) else fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'Optional-Type', :min => 1, :actual => keys.size}) end end def access_PSensitiveType(o, scope, keys) keys.flatten! if keys.size == 1 type = keys[0] unless type.is_a?(Types::PAnyType) fail(Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'Sensitive-Type', :actual => type.class}) end Types::PSensitiveType.new(type) else fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'Sensitive-Type', :min => 1, :actual => keys.size}) end end def access_PObjectType(o, scope, keys) keys.flatten! if o.resolved? && !o.name.nil? Types::PObjectTypeExtension.create(o, keys) else if keys.size == 1 Types::TypeFactory.object(keys[0]) else fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'Object-Type', :min => 1, :actual => keys.size}) end end end def access_PTypeSetType(o, scope, keys) keys.flatten! if keys.size == 1 Types::TypeFactory.type_set(keys[0]) else fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'TypeSet-Type', :min => 1, :actual => keys.size}) end end def access_PNotUndefType(o, scope, keys) keys.flatten! case keys.size when 0 Types::TypeFactory.not_undef when 1 type = keys[0] case type when String type = Types::TypeFactory.string(type) when Types::PAnyType type = nil if type.class == Types::PAnyType else fail(Issues::BAD_NOT_UNDEF_SLICE_TYPE, @semantic.keys[0], {:base_type => 'NotUndef-Type', :actual => type.class}) end Types::TypeFactory.not_undef(type) else fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'NotUndef-Type', :min => 0, :max => 1, :actual => keys.size}) end end def access_PTypeType(o, scope, keys) keys.flatten! if keys.size == 1 unless keys[0].is_a?(Types::PAnyType) fail(Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'Type-Type', :actual => keys[0].class}) end Types::PTypeType.new(keys[0]) else fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'Type-Type', :min => 1, :actual => keys.size}) end end def access_PInitType(o, scope, keys) unless keys[0].is_a?(Types::PAnyType) fail(Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'Init-Type', :actual => keys[0].class}) end Types::TypeFactory.init(*keys) end def access_PIterableType(o, scope, keys) keys.flatten! if keys.size == 1 unless keys[0].is_a?(Types::PAnyType) fail(Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'Iterable-Type', :actual => keys[0].class}) end Types::PIterableType.new(keys[0]) else fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'Iterable-Type', :min => 1, :actual => keys.size}) end end def access_PIteratorType(o, scope, keys) keys.flatten! if keys.size == 1 unless keys[0].is_a?(Types::PAnyType) fail(Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'Iterator-Type', :actual => keys[0].class}) end Types::PIteratorType.new(keys[0]) else fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'Iterator-Type', :min => 1, :actual => keys.size}) end end def access_PRuntimeType(o, scope, keys) keys.flatten! assert_keys(keys, o, 2, 2, String, String) # create runtime type based on runtime and name of class, (not inference of key's type) Types::TypeFactory.runtime(*keys) end def access_PIntegerType(o, scope, keys) keys.flatten! unless keys.size.between?(1, 2) fail(Issues::BAD_INTEGER_SLICE_ARITY, @semantic, {:actual => keys.size}) end keys.each_with_index do |x, index| fail(Issues::BAD_INTEGER_SLICE_TYPE, @semantic.keys[index], {:actual => x.class}) unless (x.is_a?(Integer) || x == :default) end Types::PIntegerType.new(*keys) end def access_PFloatType(o, scope, keys) keys.flatten! unless keys.size.between?(1, 2) fail(Issues::BAD_FLOAT_SLICE_ARITY, @semantic, {:actual => keys.size}) end keys.each_with_index do |x, index| fail(Issues::BAD_FLOAT_SLICE_TYPE, @semantic.keys[index], {:actual => x.class}) unless (x.is_a?(Float) || x.is_a?(Integer) || x == :default) end from, to = keys from = from == :default || from.nil? ? nil : Float(from) to = to == :default || to.nil? ? nil : Float(to) Types::PFloatType.new(from, to) end # A Hash can create a new Hash type, one arg sets value type, two args sets key and value type in new type. # With 3 or 4 arguments, these are used to create a size constraint. # It is not possible to create a collection of Hash types directly. # def access_PHashType(o, scope, keys) keys.flatten! if keys.size == 2 && keys[0].is_a?(Integer) && keys[1].is_a?(Integer) return Types::PHashType.new(nil, nil, Types::PIntegerType.new(*keys)) end keys[0,2].each_with_index do |k, index| unless k.is_a?(Types::PAnyType) fail(Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[index], {:base_type => 'Hash-Type', :actual => k.class}) end end case keys.size when 2 size_t = nil when 3 size_t = keys[2] size_t = Types::PIntegerType.new(size_t) unless size_t.is_a?(Types::PIntegerType) when 4 size_t = collection_size_t(2, keys[2], keys[3]) else fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, { :base_type => 'Hash-Type', :min => 2, :max => 4, :actual => keys.size }) end Types::PHashType.new(keys[0], keys[1], size_t) end # CollectionType is parameterized with a range def access_PCollectionType(o, scope, keys) keys.flatten! case keys.size when 1 size_t = collection_size_t(0, keys[0]) when 2 size_t = collection_size_t(0, keys[0], keys[1]) else fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'Collection-Type', :min => 1, :max => 2, :actual => keys.size}) end Types::PCollectionType.new(size_t) end # An Array can create a new Array type. It is not possible to create a collection of Array types. # def access_PArrayType(o, scope, keys) keys.flatten! case keys.size when 1 unless keys[0].is_a?(Types::PAnyType) fail(Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'Array-Type', :actual => keys[0].class}) end type = keys[0] size_t = nil when 2 if keys[0].is_a?(Types::PAnyType) size_t = collection_size_t(1, keys[1]) type = keys[0] else size_t = collection_size_t(0, keys[0], keys[1]) type = nil end when 3 if keys[0].is_a?(Types::PAnyType) size_t = collection_size_t(1, keys[1], keys[2]) type = keys[0] else fail(Issues::BAD_TYPE_SLICE_TYPE, @semantic.keys[0], {:base_type => 'Array-Type', :actual => keys[0].class}) end else fail(Issues::BAD_TYPE_SLICE_ARITY, @semantic, {:base_type => 'Array-Type', :min => 1, :max => 3, :actual => keys.size}) end Types::PArrayType.new(type, size_t) end # Produces an PIntegerType (range) given one or two keys. def collection_size_t(start_index, *keys) if keys.size == 1 && keys[0].is_a?(Types::PIntegerType) keys[0] else keys.each_with_index do |x, index| fail(Issues::BAD_COLLECTION_SLICE_TYPE, @semantic.keys[start_index + index], {:actual => x.class}) unless (x.is_a?(Integer) || x == :default) end Types::PIntegerType.new(*keys) end end # A Puppet::Resource represents either just a type (no title), or is a fully qualified type/title. # def access_Resource(o, scope, keys) # To access a Puppet::Resource as if it was a PResourceType, simply infer it, and take the type of # the parameterized meta type (i.e. Type[Resource[the_resource_type, the_resource_title]]) t = Types::TypeCalculator.infer(o).type # must map "undefined title" from resource to nil t.title = nil if t.title == EMPTY_STRING access(t, scope, *keys) end # If a type reference is encountered here, it's an error def access_PTypeReferenceType(o, scope, keys) fail(Issues::UNKNOWN_RESOURCE_TYPE, @semantic, {:type_name => o.type_string }) end # A Resource can create a new more specific Resource type, and/or an array of resource types # If the given type has title set, it can not be specified further. # @example # Resource[File] # => File # Resource[File, 'foo'] # => File[foo] # Resource[File. 'foo', 'bar'] # => [File[foo], File[bar]] # File['foo', 'bar'] # => [File[foo], File[bar]] # File['foo']['bar'] # => Value of the 'bar' parameter in the File['foo'] resource # Resource[File]['foo', 'bar'] # => [File[Foo], File[bar]] # Resource[File, 'foo', 'bar'] # => [File[foo], File[bar]] # Resource[File, 'foo']['bar'] # => Value of the 'bar' parameter in the File['foo'] resource # def access_PResourceType(o, scope, keys) blamed = keys.size == 0 ? @semantic : @semantic.keys[0] if keys.size == 0 fail(Issues::BAD_TYPE_SLICE_ARITY, blamed, :base_type => o.to_s, :min => 1, :max => -1, :actual => 0) end # Must know which concrete resource type to operate on in all cases. # It is not allowed to specify the type in an array arg - e.g. Resource[[File, 'foo']] # type_name is LHS type_name if set, else the first given arg type_name = o.type_name || Types::TypeFormatter.singleton.capitalize_segments(keys.shift) type_name = case type_name when Types::PResourceType type_name.type_name when String type_name else # blame given left expression if it defined the type, else the first given key expression blame = o.type_name.nil? ? @semantic.keys[0] : @semantic.left_expr fail(Issues::ILLEGAL_RESOURCE_SPECIALIZATION, blame, {:actual => bad_key_type_name(type_name)}) end # type name must conform if type_name !~ Patterns::CLASSREF_EXT fail(Issues::ILLEGAL_CLASSREF, blamed, {:name=>type_name}) end # The result is an array if multiple titles are given, or if titles are specified with an array # (possibly multiple arrays, and nested arrays). result_type_array = keys.size > 1 || keys[0].is_a?(Array) keys_orig_size = keys.size keys.flatten! keys.compact! # If given keys that were just a mix of empty/nil with empty array as a result. # As opposed to calling the function the wrong way (without any arguments), (configurable issue), # Return an empty array # if keys.empty? && keys_orig_size > 0 optionally_fail(Issues::EMPTY_RESOURCE_SPECIALIZATION, blamed) return result_type_array ? [] : nil end if !o.title.nil? # lookup resource and return one or more parameter values resource = find_resource(scope, o.type_name, o.title) unless resource fail(Issues::UNKNOWN_RESOURCE, @semantic, {:type_name => o.type_name, :title => o.title}) end result = keys.map do |k| unless is_parameter_of_resource?(scope, resource, k) fail(Issues::UNKNOWN_RESOURCE_PARAMETER, @semantic, {:type_name => o.type_name, :title => o.title, :param_name=>k}) end get_resource_parameter_value(scope, resource, k) end return result_type_array ? result : result.pop end keys = [:no_title] if keys.size < 1 # if there was only a type_name and it was consumed result = keys.each_with_index.map do |t, i| unless t.is_a?(String) || t == :no_title index = keys_orig_size != keys.size ? i+1 : i fail(Issues::BAD_TYPE_SPECIALIZATION, @semantic.keys[index], { :type => o, :message => "Cannot use #{bad_key_type_name(t)} where a resource title String is expected" }) end Types::PResourceType.new(type_name, t == :no_title ? nil : t) end # returns single type if request was for a single entity, else an array of types (possibly empty) return result_type_array ? result : result.pop end NS = '::'.freeze def access_PClassType(o, scope, keys) blamed = keys.size == 0 ? @semantic : @semantic.keys[0] keys_orig_size = keys.size if keys_orig_size == 0 fail(Issues::BAD_TYPE_SLICE_ARITY, blamed, :base_type => o.to_s, :min => 1, :max => -1, :actual => 0) end # The result is an array if multiple classnames are given, or if classnames are specified with an array # (possibly multiple arrays, and nested arrays). result_type_array = keys.size > 1 || keys[0].is_a?(Array) keys.flatten! keys.compact! # If given keys that were just a mix of empty/nil with empty array as a result. # As opposed to calling the function the wrong way (without any arguments), (configurable issue), # Return an empty array # if keys.empty? && keys_orig_size > 0 optionally_fail(Issues::EMPTY_RESOURCE_SPECIALIZATION, blamed) return result_type_array ? [] : nil end if o.class_name.nil? result = keys.each_with_index.map do |c, i| fail(Issues::ILLEGAL_HOSTCLASS_NAME, @semantic.keys[i], {:name => c}) unless c.is_a?(String) name = c.downcase # Remove leading '::' since all references are global, and 3x runtime does the wrong thing name = name[2..-1] if name[0,2] == NS fail(Issues::ILLEGAL_NAME, @semantic.keys[i], {:name=>c}) unless name =~ Patterns::NAME Types::PClassType.new(name) end else # lookup class resource and return one or more parameter values resource = find_resource(scope, 'class', o.class_name) if resource result = keys.map do |k| if is_parameter_of_resource?(scope, resource, k) get_resource_parameter_value(scope, resource, k) else fail(Issues::UNKNOWN_RESOURCE_PARAMETER, @semantic, {:type_name => 'Class', :title => o.class_name, :param_name=>k}) end end else fail(Issues::UNKNOWN_RESOURCE, @semantic, {:type_name => 'Class', :title => o.class_name}) end end # returns single type as type, else an array of types return result_type_array ? result : result.pop end end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/evaluator/callable_signature.rb���������������������������������������0000644�0052762�0001160�00000006440�13417161721�024231� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# CallableSignature # === # A CallableSignature describes how something callable expects to be called. # Different implementation of this class are used for different types of callables. # # @api public # class Puppet::Pops::Evaluator::CallableSignature # Returns the names of the parameters as an array of strings. This does not include the name # of an optional block parameter. # # All implementations are not required to supply names for parameters. They may be used if present, # to provide user feedback in errors etc. but they are not authoritative about the number of # required arguments, optional arguments, etc. # # A derived class must implement this method. # # @return [Array<String>] - an array of names (that may be empty if names are unavailable) # # @api public # def parameter_names raise NotImplementedError.new end # Returns a PCallableType with the type information, required and optional count, and type information about # an optional block. # # A derived class must implement this method. # # @return [Puppet::Pops::Types::PCallableType] # @api public # def type raise NotImplementedError.new end # Returns the expected type for an optional block. The type may be nil, which means that the callable does # not accept a block. If a type is returned it is one of Callable, Optional[Callable], Variant[Callable,...], # or Optional[Variant[Callable, ...]]. The Variant type is used when multiple signatures are acceptable. # The Optional type is used when the block is optional. # # @return [Puppet::Pops::Types::PAnyType, nil] the expected type of a block given as the last parameter in a call. # # @api public # def block_type type.block_type end # Returns the name of the block parameter if the callable accepts a block. # @return [String] the name of the block parameter # A derived class must implement this method. # @api public # def block_name raise NotImplementedError.new end # Returns a range indicating the optionality of a block. One of [0,0] (does not accept block), [0,1] (optional # block), and [1,1] (block required) # # @return [Array(Integer, Integer)] the range of the block parameter # def block_range type.block_range end # Returns the range of required/optional argument values as an array of [min, max], where an infinite # end is given as Float::INFINITY. To test against infinity, use the infinity? method. # # @return [Array[Integer, Numeric]] - an Array with [min, max] # # @api public # def args_range type.size_range end # Returns true if the last parameter captures the rest of the arguments, with a possible cap, as indicated # by the `args_range` method. # A derived class must implement this method. # # @return [Boolean] true if last parameter captures the rest of the given arguments (up to a possible cap) # @api public # def last_captures_rest? raise NotImplementedError.new end # Returns true if the given x is infinity # @return [Boolean] true, if given value represents infinity # # @api public # def infinity?(x) x == Float::INFINITY end # @return [Boolean] true if this signature represents an argument mismatch, false otherwise # # @api private def argument_mismatch_handler? false end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/evaluator/closure.rb��������������������������������������������������0000644�0052762�0001160�00000026674�13417161721�022100� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Evaluator class Jumper < Exception attr_reader :value attr_reader :file attr_reader :line def initialize(value, file, line) @value = value @file = file @line = line end end class Next < Jumper def initialize(value, file, line) super end end class Return < Jumper def initialize(value, file, line) super end end class PuppetStopIteration < StopIteration attr_reader :file attr_reader :line attr_reader :pos def initialize(file, line, pos = nil) @file = file @line = line @pos = pos end def message "break() from context where this is illegal" end end # A Closure represents logic bound to a particular scope. # As long as the runtime (basically the scope implementation) has the behavior of Puppet 3x it is not # safe to return and later use this closure. # # The 3x scope is essentially a named scope with an additional internal local/ephemeral nested scope state. # In 3x there is no way to directly refer to the nested scopes, instead, the named scope must be in a particular # state. Specifically, closures that require a local/ephemeral scope to exist at a later point will fail. # It is safe to call a closure (even with 3x scope) from the very same place it was defined, but not # returning it and expecting the closure to reference the scope's state at the point it was created. # # Note that this class is a CallableSignature, and the methods defined there should be used # as the API for obtaining information in a callable-implementation agnostic way. # class Closure < CallableSignature attr_reader :evaluator attr_reader :model attr_reader :enclosing_scope def initialize(evaluator, model) @evaluator = evaluator @model = model end # Evaluates a closure in its enclosing scope after having matched given arguments with parameters (from left to right) # @api public def call(*args) call_with_scope(enclosing_scope, args) end # This method makes a Closure compatible with a Dispatch. This is used when the closure is wrapped in a Function # and the function is called. (Saves an extra Dispatch that just delegates to a Closure and avoids having two # checks of the argument type/arity validity). # @api private def invoke(instance, calling_scope, args, &block) enclosing_scope.with_global_scope do |global_scope| call_with_scope(global_scope, args, &block) end end def call_by_name_with_scope(scope, args_hash, enforce_parameters) call_by_name_internal(scope, args_hash, enforce_parameters) end def call_by_name(args_hash, enforce_parameters) call_by_name_internal(enclosing_scope, args_hash, enforce_parameters) end # Call closure with argument assignment by name def call_by_name_internal(closure_scope, args_hash, enforce_parameters) if enforce_parameters # Push a temporary parameter scope used while resolving the parameter defaults closure_scope.with_parameter_scope(closure_name, parameter_names) do |param_scope| # Assign all non-nil values, even those that represent non-existent parameters. args_hash.each { |k, v| param_scope[k] = v unless v.nil? } parameters.each do |p| name = p.name arg = args_hash[name] if arg.nil? # Arg either wasn't given, or it was undef if p.value.nil? # No default. Assign nil if the args_hash included it param_scope[name] = nil if args_hash.include?(name) else param_scope[name] = param_scope.evaluate(name, p.value, closure_scope, @evaluator) end end end args_hash = param_scope.to_hash end Types::TypeMismatchDescriber.validate_parameters(closure_name, params_struct, args_hash) result = catch(:next) do @evaluator.evaluate_block_with_bindings(closure_scope, args_hash, @model.body) end Types::TypeAsserter.assert_instance_of(nil, return_type, result) do "value returned from #{closure_name}" end else @evaluator.evaluate_block_with_bindings(closure_scope, args_hash, @model.body) end end private :call_by_name_internal def parameters @model.parameters end # Returns the number of parameters (required and optional) # @return [Integer] the total number of accepted parameters def parameter_count # yes, this is duplication of code, but it saves a method call @model.parameters.size end # @api public def parameter_names @model.parameters.collect(&:name) end def return_type @return_type ||= create_return_type end # @api public def type @callable ||= create_callable_type end # @api public def params_struct @params_struct ||= create_params_struct end # @api public def last_captures_rest? last = @model.parameters[-1] last && last.captures_rest end # @api public def block_name # TODO: Lambda's does not support blocks yet. This is a placeholder 'unsupported_block' end CLOSURE_NAME = 'lambda'.freeze # @api public def closure_name() CLOSURE_NAME end class Dynamic < Closure def initialize(evaluator, model, scope) @enclosing_scope = scope super(evaluator, model) end def enclosing_scope @enclosing_scope end def call(*args) # A return from an unnamed closure is treated as a return from the context evaluating # calling this closure - that is, as if it was the return call itself. # jumper = catch(:return) do return call_with_scope(enclosing_scope, args) end raise jumper end end class Named < Closure def initialize(name, evaluator, model) @name = name super(evaluator, model) end def closure_name @name end # The assigned enclosing scope, or global scope if enclosing scope was initialized to nil # def enclosing_scope # Named closures are typically used for puppet functions and they cannot be defined # in an enclosing scope as they are cashed and reused. They need to bind to the # global scope at time of use rather at time of definition. # Unnamed closures are always a runtime construct, they are never bound by a loader # and are thus garbage collected at end of a compilation. # Puppet.lookup(:global_scope) { {} } end end private def call_with_scope(scope, args) variable_bindings = combine_values_with_parameters(scope, args) tc = Types::TypeCalculator.singleton final_args = tc.infer_set(parameters.reduce([]) do |tmp_args, param| if param.captures_rest tmp_args.concat(variable_bindings[param.name]) else tmp_args << variable_bindings[param.name] end end) if type.callable?(final_args) result = catch(:next) do @evaluator.evaluate_block_with_bindings(scope, variable_bindings, @model.body) end Types::TypeAsserter.assert_instance_of(nil, return_type, result) do "value returned from #{closure_name}" end else raise ArgumentError, Types::TypeMismatchDescriber.describe_signatures(closure_name, [self], final_args) end end def combine_values_with_parameters(scope, args) scope.with_parameter_scope(closure_name, parameter_names) do |param_scope| parameters.each_with_index do |parameter, index| param_captures = parameter.captures_rest default_expression = parameter.value if index >= args.size if default_expression # not given, has default value = param_scope.evaluate(parameter.name, default_expression, scope, @evaluator) if param_captures && !value.is_a?(Array) # correct non array default value value = [value] end else # not given, does not have default if param_captures # default for captures rest is an empty array value = [] else @evaluator.fail(Issues::MISSING_REQUIRED_PARAMETER, parameter, { :param_name => parameter.name }) end end else given_argument = args[index] if param_captures # get excess arguments value = args[(parameter_count-1)..-1] # If the input was a single nil, or undef, and there is a default, use the default # This supports :undef in case it was used in a 3x data structure and it is passed as an arg # if value.size == 1 && (given_argument.nil? || given_argument == :undef) && default_expression value = param_scope.evaluate(parameter.name, default_expression, scope, @evaluator) # and ensure it is an array value = [value] unless value.is_a?(Array) end else value = given_argument end end param_scope[parameter.name] = value end param_scope.to_hash end end def create_callable_type() types = [] from = 0 to = 0 in_optional_parameters = false closure_scope = enclosing_scope parameters.each do |param| type, param_range = create_param_type(param, closure_scope) types << type if param_range[0] == 0 in_optional_parameters = true elsif param_range[0] != 0 && in_optional_parameters @evaluator.fail(Issues::REQUIRED_PARAMETER_AFTER_OPTIONAL, param, { :param_name => param.name }) end from += param_range[0] to += param_range[1] end param_types = Types::PTupleType.new(types, Types::PIntegerType.new(from, to)) Types::PCallableType.new(param_types, nil, return_type) end def create_params_struct type_factory = Types::TypeFactory members = {} closure_scope = enclosing_scope parameters.each do |param| arg_type, _ = create_param_type(param, closure_scope) key_type = type_factory.string(param.name.to_s) key_type = type_factory.optional(key_type) unless param.value.nil? members[key_type] = arg_type end type_factory.struct(members) end def create_return_type if @model.return_type @evaluator.evaluate(@model.return_type, @enclosing_scope) else Types::PAnyType::DEFAULT end end def create_param_type(param, closure_scope) type = if param.type_expr @evaluator.evaluate(param.type_expr, closure_scope) else Types::PAnyType::DEFAULT end if param.captures_rest && type.is_a?(Types::PArrayType) # An array on a slurp parameter is how a size range is defined for a # slurp (Array[Integer, 1, 3] *$param). However, the callable that is # created can't have the array in that position or else type checking # will require the parameters to be arrays, which isn't what is # intended. The array type contains the intended information and needs # to be unpacked. param_range = type.size_range type = type.element_type elsif param.captures_rest && !type.is_a?(Types::PArrayType) param_range = ANY_NUMBER_RANGE elsif param.value param_range = OPTIONAL_SINGLE_RANGE else param_range = REQUIRED_SINGLE_RANGE end [type, param_range] end # Produces information about parameters compatible with a 4x Function (which can have multiple signatures) def signatures [ self ] end ANY_NUMBER_RANGE = [0, Float::INFINITY] OPTIONAL_SINGLE_RANGE = [0, 1] REQUIRED_SINGLE_RANGE = [1, 1] end end end ��������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/evaluator/collector_transformer.rb������������������������������������0000644�0052762�0001160�00000013626�13417161721�025025� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Evaluator class CollectorTransformer def initialize @@query_visitor ||= Visitor.new(nil, "query", 1, 1) @@match_visitor ||= Visitor.new(nil, "match", 1, 1) @@evaluator ||= EvaluatorImpl.new @@compare_operator ||= CompareOperator.new() end def transform(o, scope) #TRANSLATORS 'CollectExpression' is a class name and should not be translated raise ArgumentError, _("Expected CollectExpression") unless o.is_a? Model::CollectExpression raise "LHS is not a type" unless o.type_expr.is_a? Model::QualifiedReference type = o.type_expr.value().downcase() if type == 'class' fail "Classes cannot be collected" end resource_type = Runtime3ResourceSupport.find_resource_type(scope, type) fail "Resource type #{type} doesn't exist" unless resource_type if !o.operations.empty? overrides = { :parameters => o.operations.map{ |x| @@evaluator.evaluate(x, scope)}.flatten, :file => o.file, :line => o.line, :source => scope.source, :scope => scope } end code = query_unless_nop(o.query, scope) case o.query when Model::VirtualQuery newcoll = Collectors::CatalogCollector.new(scope, resource_type, code, overrides) when Model::ExportedQuery match = match_unless_nop(o.query, scope) newcoll = Collectors::ExportedCollector.new(scope, resource_type, match, code, overrides) end scope.compiler.add_collection(newcoll) newcoll end protected def query(o, scope) @@query_visitor.visit_this_1(self, o, scope) end def match(o, scope) @@match_visitor.visit_this_1(self, o, scope) end def query_unless_nop(query, scope) unless query.expr.nil? || query.expr.is_a?(Model::Nop) query(query.expr, scope) end end def match_unless_nop(query, scope) unless query.expr.nil? || query.expr.is_a?(Model::Nop) match(query.expr, scope) end end def query_AndExpression(o, scope) left_code = query(o.left_expr, scope) right_code = query(o.right_expr, scope) proc do |resource| left_code.call(resource) && right_code.call(resource) end end def query_OrExpression(o, scope) left_code = query(o.left_expr, scope) right_code = query(o.right_expr, scope) proc do |resource| left_code.call(resource) || right_code.call(resource) end end def query_ComparisonExpression(o, scope) left_code = query(o.left_expr, scope) right_code = query(o.right_expr, scope) case o.operator when '==' if left_code == "tag" # Ensure that to_s and downcase is done once, i.e. outside the proc block and # then use raw_tagged? instead of tagged? if right_code.is_a?(Array) tags = right_code else tags = [ right_code ] end tags = tags.collect do |t| raise ArgumentError, _('Cannot transform a number to a tag') if t.is_a?(Numeric) t.to_s.downcase end proc do |resource| resource.raw_tagged?(tags) end else proc do |resource| if (tmp = resource[left_code]).is_a?(Array) @@compare_operator.include?(tmp, right_code, scope) else @@compare_operator.equals(tmp, right_code) end end end when '!=' proc do |resource| !@@compare_operator.equals(resource[left_code], right_code) end end end def query_AccessExpression(o, scope) pops_object = @@evaluator.evaluate(o, scope) # Convert to Puppet 3 style objects since that is how they are represented # in the catalog. @@evaluator.convert(pops_object, scope, nil) end def query_VariableExpression(o, scope) @@evaluator.evaluate(o, scope) end def query_LiteralBoolean(o, scope) @@evaluator.evaluate(o, scope) end def query_LiteralString(o, scope) @@evaluator.evaluate(o, scope) end def query_ConcatenatedString(o, scope) @@evaluator.evaluate(o, scope) end def query_LiteralNumber(o, scope) @@evaluator.evaluate(o, scope) end def query_LiteralUndef(o, scope) nil end def query_QualifiedName(o, scope) @@evaluator.evaluate(o, scope) end def query_ParenthesizedExpression(o, scope) query(o.expr, scope) end def query_Object(o, scope) raise ArgumentError, _("Cannot transform object of class %{klass}") % { klass: o.class } end def match_AccessExpression(o, scope) pops_object = @@evaluator.evaluate(o, scope) # Convert to Puppet 3 style objects since that is how they are represented # in the catalog. @@evaluator.convert(pops_object, scope, nil) end def match_AndExpression(o, scope) left_match = match(o.left_expr, scope) right_match = match(o.right_expr, scope) return [left_match, 'and', right_match] end def match_OrExpression(o, scope) left_match = match(o.left_expr, scope) right_match = match(o.right_expr, scope) return [left_match, 'or', right_match] end def match_ComparisonExpression(o, scope) left_match = match(o.left_expr, scope) right_match = match(o.right_expr, scope) return [left_match, o.operator.to_s, right_match] end def match_VariableExpression(o, scope) @@evaluator.evaluate(o, scope) end def match_LiteralBoolean(o, scope) @@evaluator.evaluate(o, scope) end def match_LiteralString(o, scope) @@evaluator.evaluate(o, scope) end def match_LiteralUndef(o, scope) nil end def match_ConcatenatedString(o, scope) @@evaluator.evaluate(o, scope) end def match_LiteralNumber(o, scope) @@evaluator.evaluate(o, scope) end def match_QualifiedName(o, scope) @@evaluator.evaluate(o, scope) end def match_ParenthesizedExpression(o, scope) match(o.expr, scope) end def match_Object(o, scope) raise ArgumentError, _("Cannot transform object of class %{klass}") % { klass: o.class } end end end end ����������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/evaluator/collectors/�������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�022236� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/evaluator/collectors/abstract_collector.rb����������������������������0000644�0052762�0001160�00000005022�13417161721�026426� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Pops::Evaluator::Collectors::AbstractCollector attr_reader :scope # The collector's hash of overrides {:parameters => params} attr_reader :overrides # The set of collected resources attr_reader :collected # An empty array which will be returned by the unresolved_resources # method unless we have a FixSetCollector EMPTY_RESOURCES = [].freeze # Initialized the instance variables needed by the base # collector class to perform evaluation # # @param [Puppet::Parser::Scope] scope # # @param [Hash] overrides a hash of optional overrides # @options opts [Array] :parameters # @options opts [String] :file # @options opts [Array] :line # @options opts [Puppet::Resource::Type] :source # @options opts [Puppet::Parser::Scope] :scope def initialize(scope, overrides = nil) @collected = {} @scope = scope if !(overrides.nil? || overrides[:parameters]) raise ArgumentError, _("Exported resource try to override without parameters") end @overrides = overrides end # Collects resources and marks collected objects as non-virtual. Also # handles overrides. # # @return [Array] the resources we have collected def evaluate objects = collect.each do |obj| obj.virtual = false end return false if objects.empty? if @overrides and !objects.empty? overrides[:source].meta_def(:child_of?) do |klass| true end objects.each do |res| unless @collected.include?(res.ref) t = res.type t = Puppet::Pops::Evaluator::Runtime3ResourceSupport.find_resource_type(scope, t) newres = Puppet::Parser::Resource.new(t, res.title, @overrides) scope.compiler.add_override(newres) end end end objects.reject! { |o| @collected.include?(o.ref) } return false if objects.empty? objects.reduce(@collected) { |c,o| c[o.ref]=o; c } objects end # This should only return an empty array unless we have # an FixedSetCollector, in which case it will return the # resources that have not yet been realized # # @return [Array] the resources that have not been resolved def unresolved_resources EMPTY_RESOURCES end # Collect the specified resources. The way this is done depends on which type # of collector we are dealing with. This method is implemented differently in # each of the three child classes # # @return [Array] the collected resources def collect raise NotImplementedError, "This method must be implemented by the child class" end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/evaluator/collectors/catalog_collector.rb�����������������������������0000644�0052762�0001160�00000001521�13417161721�026235� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Pops::Evaluator::Collectors::CatalogCollector < Puppet::Pops::Evaluator::Collectors::AbstractCollector # Creates a CatalogCollector using the AbstractCollector's # constructor to set the scope and overrides # # param [Puppet::CompilableResourceType] type the resource type to be collected # param [Proc] query the query which defines which resources to match def initialize(scope, type, query, overrides = nil) super(scope, overrides) @query = query @type = Puppet::Resource.new(type, 'whatever').type end # Collects virtual resources based off a collection in a manifest def collect t = @type q = @query scope.compiler.resources.find_all do |resource| resource.type == t && (q ? q.call(resource) : true) end end def to_s "Catalog-Collector[#{@type.to_s}]" end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/evaluator/collectors/exported_collector.rb����������������������������0000644�0052762�0001160�00000004242�13417161721�026460� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Pops::Evaluator::Collectors::ExportedCollector < Puppet::Pops::Evaluator::Collectors::AbstractCollector # Creates an ExportedCollector using the AbstractCollector's # constructor to set the scope and overrides # # param [Puppet::CompilableResourceType] type the resource type to be collected # param [Array] equery an array representation of the query (exported query) # param [Proc] cquery a proc representation of the query (catalog query) def initialize(scope, type, equery, cquery, overrides = nil) super(scope, overrides) @equery = equery @cquery = cquery @type = Puppet::Resource.new(type, 'whatever').type end # Ensures that storeconfigs is present before calling AbstractCollector's # evaluate method def evaluate if Puppet[:storeconfigs] != true return false end super end # Collect exported resources as defined by an exported # collection. Used with PuppetDB def collect resources = [] time = Puppet::Util.thinmark do t = @type q = @cquery resources = scope.compiler.resources.find_all do |resource| resource.type == t && resource.exported? && (q.nil? || q.call(resource)) end found = Puppet::Resource.indirection. search(@type, :host => @scope.compiler.node.name, :filter => @equery, :scope => @scope) found_resources = found.map {|x| x.is_a?(Puppet::Parser::Resource) ? x : x.to_resource(@scope)} found_resources.each do |item| if existing = @scope.findresource(item.resource_type, item.title) unless existing.collector_id == item.collector_id raise Puppet::ParseError, _("A duplicate resource was found while collecting exported resources, with the type and title %{title}") % { title: item.ref } end else item.exported = false @scope.compiler.add_resource(@scope, item) resources << item end end end scope.debug("Collected %s %s resource%s in %.2f seconds" % [resources.length, @type, resources.length == 1 ? "" : "s", time]) resources end def to_s "Exported-Collector[#{@type.to_s}]" end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/evaluator/collectors/fixed_set_collector.rb���������������������������0000644�0052762�0001160�00000002043�13417161721�026575� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Pops::Evaluator::Collectors::FixedSetCollector < Puppet::Pops::Evaluator::Collectors::AbstractCollector # Creates a FixedSetCollector using the AbstractCollector constructor # to set the scope. It is not possible for a collection to have # overrides in this case, since we have a fixed set of resources that # can be different types. # # @param [Array] resources the fixed set of resources we want to realize def initialize(scope, resources) super(scope) @resources = resources.is_a?(Array)? resources.dup : [resources] end # Collects a fixed set of resources and realizes them. Used # by the realize function def collect resolved = [] result = @resources.reduce([]) do |memo, ref| if res = @scope.findresource(ref.to_s) res.virtual = false memo << res resolved << ref end memo end @resources = @resources - resolved @scope.compiler.delete_collection(self) if @resources.empty? result end def unresolved_resources @resources end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/evaluator/epp_evaluator.rb��������������������������������������������0000644�0052762�0001160�00000011650�13417161721�023256� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Handler of Epp call/evaluation from the epp and inline_epp functions # class Puppet::Pops::Evaluator::EppEvaluator def self.inline_epp(scope, epp_source, template_args = nil) unless epp_source.is_a?(String) #TRANSLATORS 'inline_epp()' is a method name and 'epp' refers to 'Embedded Puppet (EPP) template' and should not be translated raise ArgumentError, _("inline_epp(): the first argument must be a String with the epp source text, got a %{class_name}") % { class_name: epp_source.class } end # Parse and validate the source parser = Puppet::Pops::Parser::EvaluatingParser::EvaluatingEppParser.new begin result = parser.parse_string(epp_source, 'inlined-epp-text') rescue Puppet::ParseError => e #TRANSLATORS 'inline_epp()' is a method name and 'EPP' refers to 'Embedded Puppet (EPP) template' and should not be translated raise ArgumentError, _("inline_epp(): Invalid EPP: %{detail}") % { detail: e.message } end # Evaluate (and check template_args) evaluate(parser, 'inline_epp', scope, false, result, template_args) end def self.epp(scope, file, env_name, template_args = nil) unless file.is_a?(String) #TRANSLATORS 'epp()' is a method name and should not be translated raise ArgumentError, _("epp(): the first argument must be a String with the filename, got a %{class_name}") % { class_name: file.class } end unless Puppet::FileSystem.exist?(file) unless file =~ /\.epp$/ file = file + ".epp" end end scope.debug "Retrieving epp template #{file}" template_file = Puppet::Parser::Files.find_template(file, env_name) if template_file.nil? || !Puppet::FileSystem.exist?(template_file) raise Puppet::ParseError, _("Could not find template '%{file}'") % { file: file } end # Parse and validate the source parser = Puppet::Pops::Parser::EvaluatingParser::EvaluatingEppParser.new begin result = parser.parse_file(template_file) rescue Puppet::ParseError => e #TRANSLATORS 'epp()' is a method name and 'EPP' refers to 'Embedded Puppet (EPP) template' and should not be translated raise ArgumentError, _("epp(): Invalid EPP: %{detail}") % { detail: e.message } end # Evaluate (and check template_args) evaluate(parser, 'epp', scope, true, result, template_args) end def self.evaluate(parser, func_name, scope, use_global_scope_only, parse_result, template_args) template_args, template_args_set = handle_template_args(func_name, template_args) body = parse_result.body unless body.is_a?(Puppet::Pops::Model::LambdaExpression) #TRANSLATORS 'LambdaExpression' is a class name and should not be translated raise ArgumentError, _("%{function_name}(): the parser did not produce a LambdaExpression, got '%{class_name}'") % { function_name: func_name, class_name: body.class } end unless body.body.is_a?(Puppet::Pops::Model::EppExpression) #TRANSLATORS 'EppExpression' is a class name and should not be translated raise ArgumentError, _("%{function_name}(): the parser did not produce an EppExpression, got '%{class_name}'") % { function_name: func_name, class_name: body.body.class } end unless parse_result.definitions.empty? #TRANSLATORS 'EPP' refers to 'Embedded Puppet (EPP) template' raise ArgumentError, _("%{function_name}(): The EPP template contains illegal expressions (definitions)") % { function_name: func_name } end parameters_specified = body.body.parameters_specified if parameters_specified || template_args_set enforce_parameters = parameters_specified else enforce_parameters = true end # filter out all qualified names and set them in qualified_variables # only pass unqualified (filtered) variable names to the the template filtered_args = {} template_args.each_pair do |k, v| if k =~ /::/ k = k[2..-1] if k.start_with?('::') scope[k] = v else filtered_args[k] = v end end template_args = filtered_args # inline_epp() logic sees all local variables, epp() all global if use_global_scope_only scope.with_global_scope do |global_scope| parser.closure(body, global_scope).call_by_name(template_args, enforce_parameters) end else parser.closure(body, scope).call_by_name(template_args, enforce_parameters) end end private_class_method :evaluate def self.handle_template_args(func_name, template_args) if template_args.nil? [{}, false] else unless template_args.is_a?(Hash) #TRANSLATORS 'template_args' is a variable name and should not be translated raise ArgumentError, _("%{function_name}(): the template_args must be a Hash, got a %{class_name}") % { function_name: func_name, class_name: template_args.class } end [template_args, true] end end private_class_method :handle_template_args end ����������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/evaluator/json_strict_literal_evaluator.rb����������������������������0000644�0052762�0001160�00000003160�13417161721�026544� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Literal values for # # * String # * Numbers # * Booleans # * Undef (produces nil) # * Array # * Hash where keys must be Strings # * QualifiedName # # Not considered literal: # # * QualifiedReference # i.e. File, FooBar # * Default is not accepted as being literal # * Regular Expression is not accepted as being literal # * Hash with non String keys # * String with interpolation # class Puppet::Pops::Evaluator::JsonStrictLiteralEvaluator #include Puppet::Pops::Utils COMMA_SEPARATOR = ', '.freeze def initialize @@literal_visitor ||= Puppet::Pops::Visitor.new(self, "literal", 0, 0) end def literal(ast) @@literal_visitor.visit_this_0(self, ast) end def literal_Object(o) throw :not_literal end def literal_Factory(o) literal(o.model) end def literal_Program(o) literal(o.body) end def literal_LiteralString(o) o.value end def literal_QualifiedName(o) o.value end def literal_LiteralNumber(o) o.value end def literal_LiteralBoolean(o) o.value end def literal_LiteralUndef(o) nil end def literal_ConcatenatedString(o) # use double quoted string value if there is no interpolation throw :not_literal unless o.segments.size == 1 && o.segments[0].is_a?(Puppet::Pops::Model::LiteralString) o.segments[0].value end def literal_LiteralList(o) o.values.map {|v| literal(v) } end def literal_LiteralHash(o) o.entries.reduce({}) do |result, entry| key = literal(entry.key) throw :not_literal unless key.is_a?(String) result[key] = literal(entry.value) result end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/evaluator/literal_evaluator.rb����������������������������������������0000644�0052762�0001160�00000002772�13417161721�024133� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Evaluator # Literal values for # String (not containing interpolation) # Numbers # Booleans # Undef (produces nil) # Array # Hash # QualifiedName # Default (produced :default) # Regular Expression (produces ruby regular expression) # # Not considered literal # QualifiedReference # i.e. File, FooBar # class LiteralEvaluator COMMA_SEPARATOR = ', '.freeze def initialize @@literal_visitor ||= Visitor.new(self, "literal", 0, 0) end def literal(ast) @@literal_visitor.visit_this_0(self, ast) end def literal_Object(o) throw :not_literal end def literal_Factory(o) literal(o.model) end def literal_Program(o) literal(o.body) end def literal_LiteralString(o) o.value end def literal_QualifiedName(o) o.value end def literal_LiteralNumber(o) o.value end def literal_LiteralBoolean(o) o.value end def literal_LiteralUndef(o) nil end def literal_LiteralDefault(o) :default end def literal_LiteralRegularExpression(o) o.value end def literal_ConcatenatedString(o) # use double quoted string value if there is no interpolation throw :not_literal unless o.segments.size == 1 && o.segments[0].is_a?(Model::LiteralString) o.segments[0].value end def literal_LiteralList(o) o.values.map {|v| literal(v) } end def literal_LiteralHash(o) o.entries.reduce({}) do |result, entry| result[literal(entry.key)] = literal(entry.value) result end end end end end ������puppet-5.5.10/lib/puppet/pops/evaluator/puppet_proc.rb����������������������������������������������0000644�0052762�0001160�00000004217�13417161721�022751� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Complies with Proc API by mapping a Puppet::Pops::Evaluator::Closure to a ruby Proc. # Creating and passing an instance of this class instead of just a plain block makes # it possible to inherit the parameter info and arity from the closure. Advanced users # may also access the closure itself. The Puppet::Pops::Functions::Dispatcher uses this # when it needs to get the Callable type of the closure. # # The class is part of the Puppet Function API for Ruby and thus public API but a user # should never create an instance of this class. # # @api public class Puppet::Pops::Evaluator::PuppetProc < Proc # Creates a new instance from a closure and a block that will dispatch # all parameters to the closure. The block must be similar to: # # { |*args| closure.call(*args) } # # @param closure [Puppet::Pops::Evaluator::Closure] The closure to map # @param &block [Block] The varargs block that invokes the closure.call method # # @api private def self.new(closure, &block) proc = super(&block) proc.instance_variable_set(:@closure, closure) proc end # @return [Puppet::Pops::Evaluator::Closure] the mapped closure # @api public attr_reader :closure # @overrides Block.lambda? # @return [Boolean] always false since this proc doesn't do the Ruby lambda magic # @api public def lambda? false end # Maps the closure parameters to standard Block parameter info where each # parameter is represented as a two element Array where the first # element is :req, :opt, or :rest and the second element is the name # of the parameter. # # @return [Array<Array<Symbol>>] array of parameter info pairs # @overrides Block.parameters # @api public def parameters @closure.parameters.map do |param| sym = param.name.to_sym if param.captures_rest [ :rest, sym ] elsif param.value [ :opt, sym ] else [ :req, sym ] end end end # @return [Integer] the arity of the block # @overrides Block.arity # @api public def arity parameters.reduce(0) do |memo, param| count = memo + 1 break -count unless param[0] == :req count end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/evaluator/relationship_operator.rb������������������������������������0000644�0052762�0001160�00000016535�13417161721�025033� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Evaluator # The RelationshipOperator implements the semantics of the -> <- ~> <~ operators creating relationships or notification # relationships between the left and right hand side's references to resources. # # This is separate class since a second level of evaluation is required that transforms string in left or right hand # to type references. The task of "making a relationship" is delegated to the "runtime support" class that is included. # This is done to separate the concerns of the new evaluator from the 3x runtime; messy logic goes into the runtime support # module. Later when more is cleaned up this can be simplified further. # class RelationshipOperator # Provides access to the Puppet 3.x runtime (scope, etc.) # This separation has been made to make it easier to later migrate the evaluator to an improved runtime. # include Runtime3Support class IllegalRelationshipOperandError < RuntimeError attr_reader :operand def initialize operand @operand = operand end end class NotCatalogTypeError < RuntimeError attr_reader :type def initialize type @type = type end end def initialize @type_transformer_visitor = Visitor.new(self, "transform", 1, 1) @type_calculator = Types::TypeCalculator.new() tf = Types::TypeFactory @catalog_type = tf.variant(tf.catalog_entry, tf.type_type(tf.catalog_entry)) end def transform(o, scope) @type_transformer_visitor.visit_this_1(self, o, scope) end # Catch all non transformable objects # @api private def transform_Object(o, scope) raise IllegalRelationshipOperandError.new(o) end # A Resource is by definition a Catalog type, but of 3.x type # @api private def transform_Resource(o, scope) Types::TypeFactory.resource(o.type, o.title) end # A string must be a type reference in string format # @api private def transform_String(o, scope) assert_catalog_type(Types::TypeParser.singleton.parse(o), scope) end # A qualified name is short hand for a class with this name # @api private def transform_QualifiedName(o, scope) Types::TypeFactory.host_class(o.value) end # Types are what they are, just check the type # @api private def transform_PAnyType(o, scope) assert_catalog_type(o, scope) end # This transforms a 3x Collector (the result of evaluating a 3x AST::Collection). # It is passed through verbatim since it is evaluated late by the compiler. At the point # where the relationship is evaluated, it is simply recorded with the compiler for later evaluation. # If one of the sides of the relationship is a Collector it is evaluated before the actual # relationship is formed. (All of this happens at a later point in time. # def transform_Collector(o, scope) o end def transform_AbstractCollector(o, scope) o end # Array content needs to be transformed def transform_Array(o, scope) o.map{|x| transform(x, scope) } end # Asserts (and returns) the type if it is a PCatalogEntryType # (A PCatalogEntryType is the base class of PClassType, and PResourceType). # def assert_catalog_type(o, scope) unless @type_calculator.assignable?(@catalog_type, o) raise NotCatalogTypeError.new(o) end # TODO must check if this is an abstract PResourceType (i.e. without a type_name) - which should fail ? # e.g. File -> File (and other similar constructs) - maybe the catalog protects against this since references # may be to future objects... o end RELATIONSHIP_OPERATORS = ['->', '~>', '<-', '<~'].freeze REVERSE_OPERATORS = ['<-', '<~'].freeze RELATION_TYPE = { '->' => :relationship, '<-' => :relationship, '~>' => :subscription, '<~' => :subscription }.freeze # Evaluate a relationship. # TODO: The error reporting is not fine grained since evaluation has already taken place # There is no references to the original source expressions at this point, only the overall # relationship expression. (e.g.. the expression may be ['string', func_call(), etc.] -> func_call()) # To implement this, the general evaluator needs to be able to track each evaluation result and associate # it with a corresponding expression. This structure should then be passed to the relationship operator. # def evaluate (left_right_evaluated, relationship_expression, scope) # assert operator (should have been validated, but this logic makes assumptions which would # screw things up royally). Better safe than sorry. unless RELATIONSHIP_OPERATORS.include?(relationship_expression.operator) fail(Issues::UNSUPPORTED_OPERATOR, relationship_expression, {:operator => relationship_expression.operator}) end begin # Turn each side into an array of types (this also asserts their type) # (note wrap in array first if value is not already an array) # # TODO: Later when objects are Puppet Runtime Objects and know their type, it will be more efficient to check/infer # the type first since a chained operation then does not have to visit each element again. This is not meaningful now # since inference needs to visit each object each time, and this is what the transformation does anyway). # # real is [left, right], and both the left and right may be a single value or an array. In each case all content # should be flattened, and then transformed to a type. left or right may also be a value that is transformed # into an array, and thus the resulting left and right must be flattened individually # Once flattened, the operands should be sets (to remove duplicate entries) # real = left_right_evaluated.collect {|x| [x].flatten.collect {|y| transform(y, scope) }} real[0].flatten! real[1].flatten! real[0].uniq! real[1].uniq! # reverse order if operator is Right to Left source, target = reverse_operator?(relationship_expression) ? real.reverse : real # Add the relationships to the catalog source.each {|s| target.each {|t| add_relationship(s, t, RELATION_TYPE[relationship_expression.operator], scope) }} # The result is the transformed source RHS unless it is empty, in which case the transformed LHS is returned. # This closes the gap created by an empty set of references in a chain of relationship # such that X -> [ ] -> Y results in X -> Y. #result = real[1].empty? ? real[0] : real[1] if real[1].empty? # right side empty, simply use the left (whatever it may be) result = real[0] else right = real[1] if right.size == 1 && right[0].is_a?(Puppet::Pops::Evaluator::Collectors::AbstractCollector) # the collector when evaluated later may result in an empty set, if so, the # lazy relationship forming logic needs to have access to the left value. adapter = Puppet::Pops::Adapters::EmptyAlternativeAdapter.adapt(right[0]) adapter.empty_alternative = real[0] end result = right end result rescue NotCatalogTypeError => e fail(Issues::NOT_CATALOG_TYPE, relationship_expression, {:type => @type_calculator.string(e.type)}) rescue IllegalRelationshipOperandError => e fail(Issues::ILLEGAL_RELATIONSHIP_OPERAND_TYPE, relationship_expression, {:operand => e.operand}) end end def reverse_operator?(o) REVERSE_OPERATORS.include?(o.operator) end end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/evaluator/runtime3_resource_support.rb��������������������������������0000644�0052762�0001160�00000010302�13417161721�025652� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Evaluator # @api private module Runtime3ResourceSupport CLASS_STRING = 'class'.freeze def self.create_resources(file, line, scope, virtual, exported, type_name, resource_titles, evaluated_parameters) env = scope.environment # loader = Adapters::LoaderAdapter.loader_for_model_object(o, scope) if type_name.is_a?(String) && type_name.casecmp(CLASS_STRING) == 0 # Resolve a 'class' and its titles resource_titles = resource_titles.collect do |a_title| hostclass = env.known_resource_types.find_hostclass(a_title) hostclass ? hostclass.name : a_title end # resolved type is just the string CLASS resolved_type = CLASS_STRING else # resolve a resource type - pcore based, ruby impl, user defined, or application resolved_type = find_resource_type(scope, type_name) end # TODO: Unknown resource causes creation of Resource to fail with ArgumentError, should give # a proper Issue. Now the result is "Error while evaluating a Resource Statement" with the message # from the raised exception. (It may be good enough). unless resolved_type # TODO: do this the right way raise ArgumentError, _("Unknown resource type: '%{type}'") % { type: type_name } end # Build a resource for each title - use the resolved *type* as opposed to a reference # as this makes the created resource retain the type instance. # resource_titles.map do |resource_title| resource = Puppet::Parser::Resource.new( resolved_type, resource_title, :parameters => evaluated_parameters, :file => file, :line => line, :exported => exported, :virtual => virtual, # WTF is this? Which source is this? The file? The name of the context ? :source => scope.source, :scope => scope, :strict => true ) # If this resource type supports inheritance (e.g. 'class') the parent chain must be walked # This impl delegates to the resource type to figure out what is needed. # if resource.resource_type.is_a? Puppet::Resource::Type resource.resource_type.instantiate_resource(scope, resource) end scope.compiler.add_resource(scope, resource) # Classes are evaluated immediately scope.compiler.evaluate_classes([resource_title], scope, false) if resolved_type == CLASS_STRING # Turn the resource into a PTypeType (a reference to a resource type) # weed out nil's resource_to_ptype(resource) end end def self.find_resource_type(scope, type_name) find_builtin_resource_type(scope, type_name) || find_defined_resource_type(scope, type_name) end def self.find_resource_type_or_class(scope, name) find_builtin_resource_type(scope, name) || find_defined_resource_type(scope, name) || find_hostclass(scope, name) end def self.resource_to_ptype(resource) nil if resource.nil? # inference returns the meta type since the 3x Resource is an alternate way to describe a type Puppet::Pops::Types::TypeCalculator.singleton().infer(resource).type end def self.find_main_class(scope) # Find the main class (known as ''), it does not have to be in the catalog scope.environment.known_resource_types.find_hostclass('') end def self.find_hostclass(scope, class_name) scope.environment.known_resource_types.find_hostclass(class_name) end def self.find_builtin_resource_type(scope, type_name) if type_name.include?(':') # Skip the search for built in types as they are always in global namespace # (At least for now). return nil end loader = scope.compiler.loaders.private_environment_loader if loaded = loader.load(:resource_type_pp, type_name) return loaded end # horrible - should be loaded by a "last loader" in 4.x loaders instead. Puppet::Type.type(type_name) end private_class_method :find_builtin_resource_type def self.find_defined_resource_type(scope, type_name) krt = scope.environment.known_resource_types krt.find_definition(type_name) || krt.application(type_name) end private_class_method :find_defined_resource_type end end end������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/evaluator/compare_operator.rb�����������������������������������������0000644�0052762�0001160�00000015332�13417161721�023752� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Evaluator # Compares the puppet DSL way # # ==Equality # All string vs. numeric equalities check for numeric equality first, then string equality # Arrays are equal to arrays if they have the same length, and each element #equals # Hashes are equal to hashes if they have the same size and keys and values #equals. # All other objects are equal if they are ruby #== equal # class CompareOperator include Utils # Provides access to the Puppet 3.x runtime (scope, etc.) # This separation has been made to make it easier to later migrate the evaluator to an improved runtime. # include Runtime3Support def initialize @@equals_visitor ||= Visitor.new(self, "equals", 1, 1) @@compare_visitor ||= Visitor.new(self, "cmp", 1, 1) @@match_visitor ||= Visitor.new(self, "match", 2, 2) @@include_visitor ||= Visitor.new(self, "include", 2, 2) end def equals (a, b) @@equals_visitor.visit_this_1(self, a, b) end # Performs a comparison of a and b, and return > 0 if a is bigger, 0 if equal, and < 0 if b is bigger. # Comparison of String vs. Numeric always compares using numeric. def compare(a, b) @@compare_visitor.visit_this_1(self, a, b) end # Performs a match of a and b, and returns true if b matches a def match(a, b, scope = nil) @@match_visitor.visit_this_2(self, b, a, scope) end # Answers is b included in a def include?(a, b, scope) @@include_visitor.visit_this_2(self, a, b, scope) end protected def cmp_String(a, b) return a.casecmp(b) if b.is_a?(String) raise ArgumentError.new(_("A String is not comparable to a non String")) end # Equality is case independent. def equals_String(a, b) return false unless b.is_a?(String) a.casecmp(b) == 0 end def cmp_Numeric(a, b) if b.is_a?(Numeric) a <=> b else raise ArgumentError.new(_("A Numeric is not comparable to non Numeric")) end end def equals_Numeric(a, b) if b.is_a?(Numeric) a == b else false end end def equals_Array(a, b) return false unless b.is_a?(Array) && a.size == b.size a.each_index {|i| return false unless equals(a.slice(i), b.slice(i)) } true end def equals_Hash(a, b) return false unless b.is_a?(Hash) && a.size == b.size a.each {|ak, av| return false unless equals(b[ak], av)} true end def cmp_Symbol(a, b) if b.is_a?(Symbol) a <=> b else raise ArgumentError.new(_("Symbol not comparable to non Symbol")) end end def cmp_Timespan(a, b) raise ArgumentError.new(_('Timespans are only comparable to Timespans, Integers, and Floats')) unless b.is_a?(Time::Timespan) || b.is_a?(Integer) || b.is_a?(Float) a <=> b end def cmp_Timestamp(a, b) raise ArgumentError.new(_('Timestamps are only comparable to Timestamps, Integers, and Floats')) unless b.is_a?(Time::Timestamp) || b.is_a?(Integer) || b.is_a?(Float) a <=> b end def cmp_Version(a, b) raise ArgumentError.new(_('Versions not comparable to non Versions')) unless b.is_a?(SemanticPuppet::Version) a <=> b end def cmp_Object(a, b) raise ArgumentError.new(_('Only Strings, Numbers, Timespans, Timestamps, and Versions are comparable')) end def equals_Object(a, b) a == b end def equals_NilClass(a, b) # :undef supported in case it is passed from a 3x data structure b.nil? || b == :undef end def equals_Symbol(a, b) # :undef supported in case it is passed from a 3x data structure a == b || a == :undef && b.nil? end def include_Object(a, b, scope) false end def include_String(a, b, scope) case b when String # substring search downcased a.downcase.include?(b.downcase) when Regexp matched = a.match(b) # nil, or MatchData set_match_data(matched, scope) # creates ephemeral !!matched # match (convert to boolean) when Numeric # convert string to number, true if == equals(a, b) else false end end def include_Binary(a, b, scope) case b when Puppet::Pops::Types::PBinaryType::Binary a.binary_buffer.include?(b.binary_buffer) when String a.binary_buffer.include?(b) when Numeric a.binary_buffer.bytes.include?(b) else false end end def include_Array(a, b, scope) case b when Regexp matched = nil a.each do |element| next unless element.is_a? String matched = element.match(b) # nil, or MatchData break if matched end # Always set match data, a "not found" should not keep old match data visible set_match_data(matched, scope) # creates ephemeral return !!matched when String, SemanticPuppet::Version a.any? { |element| match(b, element, scope) } when Types::PAnyType a.each {|element| return true if b.instance?(element) } return false else a.each {|element| return true if equals(element, b) } return false end end def include_Hash(a, b, scope) include?(a.keys, b, scope) end def include_VersionRange(a, b, scope) Types::PSemVerRangeType.include?(a, b) end # Matches in general by using == operator def match_Object(pattern, a, scope) equals(a, pattern) end # Matches only against strings def match_Regexp(regexp, left, scope) return false unless left.is_a? String matched = regexp.match(left) set_match_data(matched, scope) unless scope.nil? # creates or clears ephemeral !!matched # convert to boolean end # Matches against semvers and strings def match_Version(version, left, scope) if left.is_a?(SemanticPuppet::Version) version == left elsif left.is_a? String begin version == SemanticPuppet::Version.parse(left) rescue ArgumentError false end else false end end # Matches against semvers and strings def match_VersionRange(range, left, scope) Types::PSemVerRangeType.include?(range, left) end def match_PAnyType(any_type, left, scope) # right is a type and left is not - check if left is an instance of the given type # (The reverse is not terribly meaningful - computing which of the case options that first produces # an instance of a given type). # any_type.instance?(left) end def match_Array(array, left, scope) return false unless left.is_a?(Array) return false unless left.length == array.length array.each_with_index.all? { | pattern, index| match(left[index], pattern, scope) } end def match_Hash(hash, left, scope) return false unless left.is_a?(Hash) hash.all? {|x,y| match(left[x], y, scope) } end def match_Symbol(symbol, left, scope) return true if symbol == :default equals(left, default) end end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/evaluator/evaluator_impl.rb�������������������������������������������0000644�0052762�0001160�00000127113�13417161721�023435� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parser/scope' require 'puppet/pops/evaluator/compare_operator' require 'puppet/pops/evaluator/relationship_operator' require 'puppet/pops/evaluator/access_operator' require 'puppet/pops/evaluator/closure' require 'puppet/pops/evaluator/external_syntax_support' require 'puppet/pops/types/iterable' module Puppet::Pops module Evaluator # This implementation of {Evaluator} performs evaluation using the puppet 3.x runtime system # in a manner largely compatible with Puppet 3.x, but adds new features and introduces constraints. # # The evaluation uses _polymorphic dispatch_ which works by dispatching to the first found method named after # the class or one of its super-classes. The EvaluatorImpl itself mainly deals with evaluation (it currently # also handles assignment), and it uses a delegation pattern to more specialized handlers of some operators # that in turn use polymorphic dispatch; this to not clutter EvaluatorImpl with too much responsibility). # # Since a pattern is used, only the main entry points are fully documented. The parameters _o_ and _scope_ are # the same in all the polymorphic methods, (the type of the parameter _o_ is reflected in the method's name; # either the actual class, or one of its super classes). The _scope_ parameter is always the scope in which # the evaluation takes place. If nothing else is mentioned, the return is always the result of evaluation. # # See {Visitable} and {Visitor} for more information about # polymorphic calling. # class EvaluatorImpl include Utils # Provides access to the Puppet 3.x runtime (scope, etc.) # This separation has been made to make it easier to later migrate the evaluator to an improved runtime. # include Runtime3Support include ExternalSyntaxSupport COMMA_SEPARATOR = ', '.freeze # Reference to Issues name space makes it easier to refer to issues # (Issues are shared with the validator). # Issues = Issues def initialize @@initialized ||= static_initialize # Use null migration checker unless given in context @migration_checker = Puppet.lookup(:migration_checker) { Migration::MigrationChecker.singleton } end # @api private def static_initialize @@eval_visitor ||= Visitor.new(self, "eval", 1, 1) @@lvalue_visitor ||= Visitor.new(self, "lvalue", 1, 1) @@assign_visitor ||= Visitor.new(self, "assign", 3, 3) @@string_visitor ||= Visitor.new(self, "string", 1, 1) @@type_calculator ||= Types::TypeCalculator.singleton @@compare_operator ||= CompareOperator.new @@relationship_operator ||= RelationshipOperator.new true end private :static_initialize # @api private def type_calculator @@type_calculator end # Evaluates the given _target_ object in the given scope. # # @overload evaluate(target, scope) # @param target [Object] evaluation target - see methods on the pattern assign_TYPE for actual supported types. # @param scope [Object] the runtime specific scope class where evaluation should take place # @return [Object] the result of the evaluation # # @api public # def evaluate(target, scope) begin @@eval_visitor.visit_this_1(self, target, scope) rescue SemanticError => e # A raised issue may not know the semantic target, use errors call stack, but fill in the # rest from a supplied semantic object, or the target instruction if there is not semantic # object. # fail(e.issue, e.semantic || target, e.options, e) rescue Puppet::PreformattedError => e # Already formatted with location information, and with the wanted call stack. # Note this is currently a specialized ParseError, so rescue-order is important # raise e rescue Puppet::ParseError => e # ParseError may be raised in ruby code without knowing the location # in puppet code. # Accept a ParseError that has file or line information available # as an error that should be used verbatim. (Tests typically run without # setting a file name). # ParseError can supply an original - it is impossible to determine which # call stack that should be propagated, using the ParseError's backtrace. # if e.file || e.line raise e else # Since it had no location information, treat it as user intended a general purpose # error. Pass on its call stack. fail(Issues::RUNTIME_ERROR, target, {:detail => e.message}, e) end rescue Puppet::Error => e # PuppetError has the ability to wrap an exception, if so, use the wrapped exception's # call stack instead fail(Issues::RUNTIME_ERROR, target, {:detail => e.message}, e.original || e) rescue StopIteration => e # Ensure these are not rescued as StandardError raise e rescue StandardError => e # All other errors, use its message and call stack fail(Issues::RUNTIME_ERROR, target, {:detail => e.message}, e) end end # Assigns the given _value_ to the given _target_. The additional argument _o_ is the instruction that # produced the target/value tuple and it is used to set the origin of the result. # # @param target [Object] assignment target - see methods on the pattern assign_TYPE for actual supported types. # @param value [Object] the value to assign to `target` # @param o [Model::PopsObject] originating instruction # @param scope [Object] the runtime specific scope where evaluation should take place # # @api private # def assign(target, value, o, scope) @@assign_visitor.visit_this_3(self, target, value, o, scope) end # Computes a value that can be used as the LHS in an assignment. # @param o [Object] the expression to evaluate as a left (assignable) entity # @param scope [Object] the runtime specific scope where evaluation should take place # # @api private # def lvalue(o, scope) @@lvalue_visitor.visit_this_1(self, o, scope) end # Produces a String representation of the given object _o_ as used in interpolation. # @param o [Object] the expression of which a string representation is wanted # @param scope [Object] the runtime specific scope where evaluation should take place # # @api public # def string(o, scope) @@string_visitor.visit_this_1(self, o, scope) end # Evaluate a BlockExpression in a new scope with variables bound to the # given values. # # @param scope [Puppet::Parser::Scope] the parent scope # @param variable_bindings [Hash{String => Object}] the variable names and values to bind (names are keys, bound values are values) # @param block [Model::BlockExpression] the sequence of expressions to evaluate in the new scope # # @api private # def evaluate_block_with_bindings(scope, variable_bindings, block_expr) scope.with_guarded_scope do # change to create local scope_from - cannot give it file and line - # that is the place of the call, not "here" create_local_scope_from(variable_bindings, scope) evaluate(block_expr, scope) end end # Implementation of case option matching. # # This is the type of matching performed in a case option, using == for every type # of value except regular expression where a match is performed. # def match?(left, right) @@compare_operator.match(left, right, nil) end protected def lvalue_VariableExpression(o, scope) # evaluate the name evaluate(o.expr, scope) end # Catches all illegal lvalues # def lvalue_Object(o, scope) fail(Issues::ILLEGAL_ASSIGNMENT, o) end # An array is assignable if all entries are lvalues def lvalue_LiteralList(o, scope) o.values.map {|x| lvalue(x, scope) } end # Assign value to named variable. # The '$' sign is never part of the name. # @example In Puppet DSL # $name = value # @param name [String] name of variable without $ # @param value [Object] value to assign to the variable # @param o [Model::PopsObject] originating instruction # @param scope [Object] the runtime specific scope where evaluation should take place # @return [value<Object>] # def assign_String(name, value, o, scope) if name =~ /::/ fail(Issues::CROSS_SCOPE_ASSIGNMENT, o.left_expr, {:name => name}) end set_variable(name, value, o, scope) value end def assign_Numeric(n, value, o, scope) fail(Issues::ILLEGAL_NUMERIC_ASSIGNMENT, o.left_expr, {:varname => n.to_s}) end # Catches all illegal assignment (e.g. 1 = 2, {'a'=>1} = 2, etc) # def assign_Object(name, value, o, scope) fail(Issues::ILLEGAL_ASSIGNMENT, o) end def assign_Array(lvalues, values, o, scope) if values.is_a?(Hash) lvalues.map do |lval| assign(lval, values.fetch(lval) {|k| fail(Issues::MISSING_MULTI_ASSIGNMENT_KEY, o, :key =>k)}, o, scope) end elsif values.is_a?(Puppet::Pops::Types::PClassType) if Puppet[:tasks] fail(Issues::CATALOG_OPERATION_NOT_SUPPORTED_WHEN_SCRIPTING, o, {:operation => _('multi var assignment from class')}) end # assign variables from class variables # lookup class resource and return one or more parameter values # TODO: behavior when class_name is nil resource = find_resource(scope, 'class', values.class_name) if resource base_name = "#{values.class_name.downcase}::" idx = -1 result = lvalues.map do |lval| idx += 1 varname = "#{base_name}#{lval}" if variable_exists?(varname, scope) result = get_variable_value(varname, o, scope) assign(lval, result, o, scope) else fail(Puppet::Pops::Issues::MISSING_MULTI_ASSIGNMENT_VARIABLE, o.left_expr.values[idx], {:name => varname}) end end else fail(Issues::UNKNOWN_RESOURCE, o.right_expr, {:type_name => 'Class', :title => values.class_name}) end else values = [values] unless values.is_a?(Array) if values.size != lvalues.size fail(Issues::ILLEGAL_MULTI_ASSIGNMENT_SIZE, o, :expected =>lvalues.size, :actual => values.size) end lvalues.zip(values).map { |lval, val| assign(lval, val, o, scope) } end end def eval_Factory(o, scope) evaluate(o.model, scope) end # Evaluates any object not evaluated to something else to itself. def eval_Object o, scope o end # Allows nil to be used as a Nop, Evaluates to nil def eval_NilClass(o, scope) nil end # Evaluates Nop to nil. def eval_Nop(o, scope) nil end # Captures all LiteralValues not handled elsewhere. # def eval_LiteralValue(o, scope) o.value end # Reserved Words fail to evaluate # def eval_ReservedWord(o, scope) if !o.future fail(Issues::RESERVED_WORD, o, {:word => o.word}) else o.word end end def eval_LiteralDefault(o, scope) :default end def eval_LiteralUndef(o, scope) nil end # A QualifiedReference (i.e. a capitalized qualified name such as Foo, or Foo::Bar) evaluates to a PTypeType # def eval_QualifiedReference(o, scope) type = Types::TypeParser.singleton.interpret(o) fail(Issues::UNKNOWN_RESOURCE_TYPE, o, {:type_name => type.type_string }) if type.is_a?(Types::PTypeReferenceType) type end def eval_NotExpression(o, scope) ! is_true?(evaluate(o.expr, scope), o.expr) end def eval_UnaryMinusExpression(o, scope) - coerce_numeric(evaluate(o.expr, scope), o, scope) end def eval_UnfoldExpression(o, scope) candidate = evaluate(o.expr, scope) case candidate when nil [] when Array candidate when Hash candidate.to_a when Puppet::Pops::Types::Iterable candidate.to_a else # turns anything else into an array (so result can be unfolded) [candidate] end end # Abstract evaluation, returns array [left, right] with the evaluated result of left_expr and # right_expr # @return <Array<Object, Object>> array with result of evaluating left and right expressions # def eval_BinaryExpression o, scope [ evaluate(o.left_expr, scope), evaluate(o.right_expr, scope) ] end # Evaluates assignment with operators =, +=, -= and # # @example Puppet DSL # $a = 1 # $a += 1 # $a -= 1 # def eval_AssignmentExpression(o, scope) name = lvalue(o.left_expr, scope) value = evaluate(o.right_expr, scope) if o.operator == '=' assign(name, value, o, scope) else fail(Issues::UNSUPPORTED_OPERATOR, o, {:operator => o.operator}) end value end ARITHMETIC_OPERATORS = ['+', '-', '*', '/', '%', '<<', '>>'].freeze COLLECTION_OPERATORS = ['+', '-', '<<'].freeze # Handles binary expression where lhs and rhs are array/hash or numeric and operator is +, - , *, % / << >> # def eval_ArithmeticExpression(o, scope) left = evaluate(o.left_expr, scope) right = evaluate(o.right_expr, scope) begin result = calculate(left, right, o, scope) rescue ArgumentError => e fail(Issues::RUNTIME_ERROR, o, {:detail => e.message}, e) end result end # Handles binary expression where lhs and rhs are array/hash or numeric and operator is +, - , *, % / << >> # def calculate(left, right, bin_expr, scope) operator = bin_expr.operator unless ARITHMETIC_OPERATORS.include?(operator) fail(Issues::UNSUPPORTED_OPERATOR, bin_expr, {:operator => operator}) end left_o = bin_expr.left_expr if left.is_a?(URI) && operator == '+' concatenate(left, right) elsif (left.is_a?(Array) || left.is_a?(Hash)) && COLLECTION_OPERATORS.include?(operator) # Handle operation on collections case operator when '+' concatenate(left, right) when '-' delete(left, right) when '<<' unless left.is_a?(Array) fail(Issues::OPERATOR_NOT_APPLICABLE, left_o, {:operator => operator, :left_value => left}) end left + [right] end else # Handle operation on numeric left = coerce_numeric(left, left_o, scope) right = coerce_numeric(right, bin_expr.right_expr, scope) begin if operator == '%' && (left.is_a?(Float) || right.is_a?(Float)) # Deny users the fun of seeing severe rounding errors and confusing results fail(Issues::OPERATOR_NOT_APPLICABLE, left_o, {:operator => operator, :left_value => left}) if left.is_a?(Float) fail(Issues::OPERATOR_NOT_APPLICABLE_WHEN, left_o, {:operator => operator, :left_value => left, :right_value => right}) end if right.is_a?(Time::TimeData) && !left.is_a?(Time::TimeData) if operator == '+' || operator == '*' && right.is_a?(Time::Timespan) # Switch places. Let the TimeData do the arithmetic x = left left = right right = x elsif operator == '-' && right.is_a?(Time::Timespan) left = Time::Timespan.new((left * Time::NSECS_PER_SEC).to_i) else fail(Issues::OPERATOR_NOT_APPLICABLE_WHEN, left_o, {:operator => operator, :left_value => left, :right_value => right}) end end result = left.send(operator, right) rescue NoMethodError fail(Issues::OPERATOR_NOT_APPLICABLE, left_o, {:operator => operator, :left_value => left}) rescue ZeroDivisionError fail(Issues::DIV_BY_ZERO, bin_expr.right_expr) end case result when Float if result == Float::INFINITY || result == -Float::INFINITY fail(Issues::RESULT_IS_INFINITY, left_o, {:operator => operator}) end when Integer if result < MIN_INTEGER || result > MAX_INTEGER fail(Issues::NUMERIC_OVERFLOW, bin_expr, {:value => result}) end end result end end def eval_EppExpression(o, scope) scope["@epp"] = [] evaluate(o.body, scope) result = scope["@epp"].join result end def eval_RenderStringExpression(o, scope) scope["@epp"] << o.value.dup nil end def eval_RenderExpression(o, scope) scope["@epp"] << string(evaluate(o.expr, scope), scope) nil end # Evaluates Puppet DSL ->, ~>, <-, and <~ def eval_RelationshipExpression(o, scope) # First level evaluation, reduction to basic data types or puppet types, the relationship operator then translates this # to the final set of references (turning strings into references, which can not naturally be done by the main evaluator since # all strings should not be turned into references. # real = eval_BinaryExpression(o, scope) @@relationship_operator.evaluate(real, o, scope) end # Evaluates x[key, key, ...] # def eval_AccessExpression(o, scope) left = evaluate(o.left_expr, scope) keys = o.keys || [] if left.is_a?(Types::PClassType) # Evaluate qualified references without errors no undefined types keys = keys.map {|key| key.is_a?(Model::QualifiedReference) ? Types::TypeParser.singleton.interpret(key) : evaluate(key, scope) } else keys = keys.map {|key| evaluate(key, scope) } # Resource[File] becomes File return keys[0] if Types::PResourceType::DEFAULT == left && keys.size == 1 && keys[0].is_a?(Types::PResourceType) end AccessOperator.new(o).access(left, scope, *keys) end # Evaluates <, <=, >, >=, and == # def eval_ComparisonExpression o, scope left = evaluate(o.left_expr, scope) right = evaluate(o.right_expr, scope) begin # Left is a type if left.is_a?(Types::PAnyType) case o.operator when '==' @@type_calculator.equals(left,right) when '!=' !@@type_calculator.equals(left,right) when '<' # left can be assigned to right, but they are not equal @@type_calculator.assignable?(right, left) && ! @@type_calculator.equals(left,right) when '<=' # left can be assigned to right @@type_calculator.assignable?(right, left) when '>' # right can be assigned to left, but they are not equal @@type_calculator.assignable?(left,right) && ! @@type_calculator.equals(left,right) when '>=' # right can be assigned to left @@type_calculator.assignable?(left, right) else fail(Issues::UNSUPPORTED_OPERATOR, o, {:operator => o.operator}) end else case o.operator when '==' @@compare_operator.equals(left,right) when '!=' ! @@compare_operator.equals(left,right) when '<' @@compare_operator.compare(left,right) < 0 when '<=' @@compare_operator.compare(left,right) <= 0 when '>' @@compare_operator.compare(left,right) > 0 when '>=' @@compare_operator.compare(left,right) >= 0 else fail(Issues::UNSUPPORTED_OPERATOR, o, {:operator => o.operator}) end end rescue ArgumentError => e fail(Issues::COMPARISON_NOT_POSSIBLE, o, { :operator => o.operator, :left_value => left, :right_value => right, :detail => e.message}, e) end end # Evaluates matching expressions with type, string or regexp rhs expression. # If RHS is a type, the =~ matches compatible (instance? of) type. # # @example # x =~ /abc.*/ # @example # x =~ "abc.*/" # @example # y = "abc" # x =~ "${y}.*" # @example # [1,2,3] =~ Array[Integer[1,10]] # # Note that a string is not instance? of Regexp, only Regular expressions are. # The Pattern type should instead be used as it is specified as subtype of String. # # @return [Boolean] if a match was made or not. Also sets $0..$n to matchdata in current scope. # def eval_MatchExpression o, scope left = evaluate(o.left_expr, scope) pattern = evaluate(o.right_expr, scope) # matches RHS types as instance of for all types except a parameterized Regexp[R] if pattern.is_a?(Types::PAnyType) # evaluate as instance? of type check matched = pattern.instance?(left) # convert match result to Boolean true, or false return o.operator == '=~' ? !!matched : !matched end if pattern.is_a?(SemanticPuppet::VersionRange) # evaluate if range includes version matched = Types::PSemVerRangeType.include?(pattern, left) return o.operator == '=~' ? matched : !matched end begin pattern = Regexp.new(pattern) unless pattern.is_a?(Regexp) rescue StandardError => e fail(Issues::MATCH_NOT_REGEXP, o.right_expr, {:detail => e.message}, e) end unless left.is_a?(String) fail(Issues::MATCH_NOT_STRING, o.left_expr, {:left_value => left}) end matched = pattern.match(left) # nil, or MatchData set_match_data(matched,scope) # creates ephemeral # convert match result to Boolean true, or false o.operator == '=~' ? !!matched : !matched end # Evaluates Puppet DSL `in` expression # def eval_InExpression o, scope left = evaluate(o.left_expr, scope) right = evaluate(o.right_expr, scope) @@compare_operator.include?(right, left, scope) end # @example # $a and $b # b is only evaluated if a is true # def eval_AndExpression o, scope is_true?(evaluate(o.left_expr, scope), o.left_expr) ? is_true?(evaluate(o.right_expr, scope), o.right_expr) : false end # @example # a or b # b is only evaluated if a is false # def eval_OrExpression o, scope is_true?(evaluate(o.left_expr, scope), o.left_expr) ? true : is_true?(evaluate(o.right_expr, scope), o.right_expr) end # Evaluates each entry of the literal list and creates a new Array # Supports unfolding of entries # @return [Array] with the evaluated content # def eval_LiteralList o, scope unfold([], o.values, scope) end # Evaluates each entry of the literal hash and creates a new Hash. # @return [Hash] with the evaluated content # def eval_LiteralHash o, scope # optimized o.entries.reduce({}) {|h,entry| h[evaluate(entry.key, scope)] = evaluate(entry.value, scope); h } end # Evaluates all statements and produces the last evaluated value # def eval_BlockExpression o, scope o.statements.reduce(nil) {|memo, s| evaluate(s, scope)} end # Performs optimized search over case option values, lazily evaluating each # until there is a match. If no match is found, the case expression's default expression # is evaluated (it may be nil or Nop if there is no default, thus producing nil). # If an option matches, the result of evaluating that option is returned. # @return [Object, nil] what a matched option returns, or nil if nothing matched. # def eval_CaseExpression(o, scope) # memo scope level before evaluating test - don't want a match in the case test to leak $n match vars # to expressions after the case expression. # scope.with_guarded_scope do test = evaluate(o.test, scope) result = nil the_default = nil if o.options.find do |co| # the first case option that matches if co.values.find do |c| c = unwind_parentheses(c) case c when Model::LiteralDefault the_default = co.then_expr next false when Model::UnfoldExpression # not ideal for error reporting, since it is not known which unfolded result # that caused an error - the entire unfold expression is blamed (i.e. the var c, passed to is_match?) evaluate(c, scope).any? {|v| is_match?(test, v, c, co, scope) } else is_match?(test, evaluate(c, scope), c, co, scope) end end result = evaluate(co.then_expr, scope) true # the option was picked end end result # an option was picked, and produced a result else evaluate(the_default, scope) # evaluate the default (should be a nop/nil) if there is no default). end end end # Evaluates a CollectExpression by creating a collector transformer. The transformer # will evaluate the collection, create the appropriate collector, and hand it off # to the compiler to collect the resources specified by the query. # def eval_CollectExpression o, scope if o.query.is_a?(Model::ExportedQuery) optionally_fail(Issues::RT_NO_STORECONFIGS, o); end CollectorTransformer.new().transform(o,scope) end def eval_ParenthesizedExpression(o, scope) evaluate(o.expr, scope) end # This evaluates classes, nodes and resource type definitions to nil, since 3x: # instantiates them, and evaluates their parameters and body. This is achieved by # providing bridge AST classes in Puppet::Parser::AST::PopsBridge that bridges a # Pops Program and a Pops Expression. # # Since all Definitions are handled "out of band", they are treated as a no-op when # evaluated. # def eval_Definition(o, scope) nil end def eval_Program(o, scope) begin file = o.locator.file line = 0 # Add stack frame for "top scope" logic. See Puppet::Pops::PuppetStack return Puppet::Pops::PuppetStack.stack(file, line, self, 'evaluate', [o.body, scope]) #evaluate(o.body, scope) rescue Puppet::Pops::Evaluator::PuppetStopIteration => ex # breaking out of a file level program is not allowed #TRANSLATOR break() is a method that should not be translated raise Puppet::ParseError.new(_("break() from context where this is illegal"), ex.file, ex.line) end end # Produces Array[PAnyType], an array of resource references # def eval_ResourceExpression(o, scope) exported = o.exported virtual = o.virtual # Get the type name type_name = if (tmp_name = o.type_name).is_a?(Model::QualifiedName) tmp_name.value # already validated as a name else type_name_acceptable = case o.type_name when Model::QualifiedReference true when Model::AccessExpression o.type_name.left_expr.is_a?(Model::QualifiedReference) end evaluated_name = evaluate(tmp_name, scope) unless type_name_acceptable actual = type_calculator.generalize(type_calculator.infer(evaluated_name)).to_s fail(Issues::ILLEGAL_RESOURCE_TYPE, o.type_name, {:actual => actual}) end # must be a CatalogEntry subtype case evaluated_name when Types::PClassType unless evaluated_name.class_name.nil? fail(Issues::ILLEGAL_RESOURCE_TYPE, o.type_name, {:actual=> evaluated_name.to_s}) end 'class' when Types::PResourceType unless evaluated_name.title().nil? fail(Issues::ILLEGAL_RESOURCE_TYPE, o.type_name, {:actual=> evaluated_name.to_s}) end evaluated_name.type_name # assume validated when Types::PTypeReferenceType fail(Issues::UNKNOWN_RESOURCE_TYPE, o.type_string, {:type_name => evaluated_name.to_s}) else actual = type_calculator.generalize(type_calculator.infer(evaluated_name)).to_s fail(Issues::ILLEGAL_RESOURCE_TYPE, o.type_name, {:actual=>actual}) end end # This is a runtime check - the model is valid, but will have runtime issues when evaluated # and storeconfigs is not set. if(o.exported) optionally_fail(Issues::RT_NO_STORECONFIGS_EXPORT, o); end titles_to_body = {} body_to_titles = {} body_to_params = {} # titles are evaluated before attribute operations o.bodies.map do | body | titles = evaluate(body.title, scope) # Title may not be nil # Titles may be given as an array, it is ok if it is empty, but not if it contains nil entries # Titles may not be an empty String # Titles must be unique in the same resource expression # There may be a :default entry, its entries apply with lower precedence # if titles.nil? fail(Issues::MISSING_TITLE, body.title) end titles = [titles].flatten # Check types of evaluated titles and duplicate entries titles.each_with_index do |title, index| if title.nil? fail(Issues::MISSING_TITLE_AT, body.title, {:index => index}) elsif !title.is_a?(String) && title != :default actual = type_calculator.generalize(type_calculator.infer(title)).to_s fail(Issues::ILLEGAL_TITLE_TYPE_AT, body.title, {:index => index, :actual => actual}) elsif title == EMPTY_STRING fail(Issues::EMPTY_STRING_TITLE_AT, body.title, {:index => index}) elsif titles_to_body[title] fail(Issues::DUPLICATE_TITLE, o, {:title => title}) end titles_to_body[title] = body end # Do not create a real instance from the :default case titles.delete(:default) body_to_titles[body] = titles # Store evaluated parameters in a hash associated with the body, but do not yet create resource # since the entry containing :defaults may appear later body_to_params[body] = body.operations.reduce({}) do |param_memo, op| params = evaluate(op, scope) params = [params] unless params.is_a?(Array) params.each do |p| if param_memo.include? p.name fail(Issues::DUPLICATE_ATTRIBUTE, o, {:attribute => p.name}) end param_memo[p.name] = p end param_memo end end # Titles and Operations have now been evaluated and resources can be created # Each production is a PResource, and an array of all is produced as the result of # evaluating the ResourceExpression. # defaults_hash = body_to_params[titles_to_body[:default]] || {} o.bodies.map do | body | titles = body_to_titles[body] params = defaults_hash.merge(body_to_params[body] || {}) create_resources(o, scope, virtual, exported, type_name, titles, params.values) end.flatten.compact end def eval_ResourceOverrideExpression(o, scope) evaluated_resources = evaluate(o.resources, scope) evaluated_parameters = o.operations.map { |op| evaluate(op, scope) } create_resource_overrides(o, scope, [evaluated_resources].flatten, evaluated_parameters) evaluated_resources end # Produces 3x parameter def eval_AttributeOperation(o, scope) create_resource_parameter(o, scope, o.attribute_name, evaluate(o.value_expr, scope), o.operator) end def eval_AttributesOperation(o, scope) hashed_params = evaluate(o.expr, scope) unless hashed_params.is_a?(Hash) actual = type_calculator.generalize(type_calculator.infer(hashed_params)).to_s fail(Issues::TYPE_MISMATCH, o.expr, {:expected => 'Hash', :actual => actual}) end hashed_params.map { |k,v| create_resource_parameter(o, scope, k, v, '=>') } end # Sets default parameter values for a type, produces the type # def eval_ResourceDefaultsExpression(o, scope) type = evaluate(o.type_ref, scope) type_name = if type.is_a?(Types::PResourceType) && !type.type_name.nil? && type.title.nil? type.type_name # assume it is a valid name else actual = type_calculator.generalize(type_calculator.infer(type)) fail(Issues::ILLEGAL_RESOURCE_TYPE, o.type_ref, {:actual => actual}) end evaluated_parameters = o.operations.map {|op| evaluate(op, scope) } create_resource_defaults(o, scope, type_name, evaluated_parameters) # Produce the type type end # Evaluates function call by name. # def eval_CallNamedFunctionExpression(o, scope) # If LHS is a type (i.e. Integer, or Integer[...] # the call is taken as an instantiation of the given type # functor = o.functor_expr if functor.is_a?(Model::QualifiedReference) || functor.is_a?(Model::AccessExpression) && functor.left_expr.is_a?(Model::QualifiedReference) # instantiation type = evaluate(functor, scope) return call_function_with_block('new', unfold([type], o.arguments || [], scope), o, scope) end # The functor expression is not evaluated, it is not possible to select the function to call # via an expression like $a() case functor when Model::QualifiedName # ok when Model::RenderStringExpression # helpful to point out this easy to make Epp error fail(Issues::ILLEGAL_EPP_PARAMETERS, o) else fail(Issues::ILLEGAL_EXPRESSION, o.functor_expr, {:feature=>'function name', :container => o}) end name = o.functor_expr.value call_function_with_block(name, unfold([], o.arguments, scope), o, scope) end # Evaluation of CallMethodExpression handles a NamedAccessExpression functor (receiver.function_name) # def eval_CallMethodExpression(o, scope) unless o.functor_expr.is_a? Model::NamedAccessExpression fail(Issues::ILLEGAL_EXPRESSION, o.functor_expr, {:feature=>'function accessor', :container => o}) end receiver = unfold([], [o.functor_expr.left_expr], scope) name = o.functor_expr.right_expr unless name.is_a? Model::QualifiedName fail(Issues::ILLEGAL_EXPRESSION, o.functor_expr, {:feature=>'function name', :container => o}) end name = name.value # the string function name obj = receiver[0] receiver_type = Types::TypeCalculator.infer(obj) if receiver_type.is_a?(Types::TypeWithMembers) member = receiver_type[name] unless member.nil? args = unfold([], o.arguments || [], scope) return o.lambda.nil? ? member.invoke(obj, scope, args) : member.invoke(obj, scope, args, &proc_from_lambda(o.lambda, scope)) end end call_function_with_block(name, unfold(receiver, o.arguments || [], scope), o, scope) end def call_function_with_block(name, evaluated_arguments, o, scope) if o.lambda.nil? call_function(name, evaluated_arguments, o, scope) else call_function(name, evaluated_arguments, o, scope, &proc_from_lambda(o.lambda, scope)) end end private :call_function_with_block def proc_from_lambda(lambda, scope) closure = Closure::Dynamic.new(self, lambda, scope) PuppetProc.new(closure) { |*args| closure.call(*args) } end private :proc_from_lambda # @example # $x ? { 10 => true, 20 => false, default => 0 } # def eval_SelectorExpression o, scope # memo scope level before evaluating test - don't want a match in the case test to leak $n match vars # to expressions after the selector expression. # scope.with_guarded_scope do test = evaluate(o.left_expr, scope) the_default = nil selected = o.selectors.find do |s| me = unwind_parentheses(s.matching_expr) case me when Model::LiteralDefault the_default = s.value_expr false when Model::UnfoldExpression # not ideal for error reporting, since it is not known which unfolded result # that caused an error - the entire unfold expression is blamed (i.e. the var c, passed to is_match?) evaluate(me, scope).any? {|v| is_match?(test, v, me, s, scope) } else is_match?(test, evaluate(me, scope), me, s, scope) end end if selected evaluate(selected.value_expr, scope) elsif the_default evaluate(the_default, scope) else fail(Issues::UNMATCHED_SELECTOR, o.left_expr, :param_value => test) end end end # SubLocatable is simply an expression that holds location information def eval_SubLocatedExpression o, scope evaluate(o.expr, scope) end # Evaluates Puppet DSL Heredoc def eval_HeredocExpression o, scope result = evaluate(o.text_expr, scope) assert_external_syntax(scope, result, o.syntax, o.text_expr) result end # Evaluates Puppet DSL `if` def eval_IfExpression o, scope scope.with_guarded_scope do if is_true?(evaluate(o.test, scope), o.test) evaluate(o.then_expr, scope) else evaluate(o.else_expr, scope) end end end # Evaluates Puppet DSL `unless` def eval_UnlessExpression o, scope scope.with_guarded_scope do unless is_true?(evaluate(o.test, scope), o.test) evaluate(o.then_expr, scope) else evaluate(o.else_expr, scope) end end end # Evaluates a variable (getting its value) # The evaluator is lenient; any expression producing a String is used as a name # of a variable. # def eval_VariableExpression o, scope # Evaluator is not too fussy about what constitutes a name as long as the result # is a String and a valid variable name # name = evaluate(o.expr, scope) # Should be caught by validation, but make this explicit here as well, or mysterious evaluation issues # may occur for some evaluation use cases. case name when String when Numeric else fail(Issues::ILLEGAL_VARIABLE_EXPRESSION, o.expr) end get_variable_value(name, o, scope) end # Evaluates double quoted strings that may contain interpolation # def eval_ConcatenatedString o, scope o.segments.collect {|expr| string(evaluate(expr, scope), scope)}.join end # If the wrapped expression is a QualifiedName, it is taken as the name of a variable in scope. # Note that this is different from the 3.x implementation, where an initial qualified name # is accepted. (e.g. `"---${var + 1}---"` is legal. This implementation requires such concrete # syntax to be expressed in a model as `(TextExpression (+ (Variable var) 1)` - i.e. moving the decision to # the parser. # # Semantics; the result of an expression is turned into a string, nil is silently transformed to empty # string. # @return [String] the interpolated result # def eval_TextExpression o, scope if o.expr.is_a?(Model::QualifiedName) string(get_variable_value(o.expr.value, o, scope), scope) else string(evaluate(o.expr, scope), scope) end end def string_Object(o, scope) o.to_s end def string_Symbol(o, scope) if :undef == o # optimized comparison 1.44 vs 1.95 EMPTY_STRING else o.to_s end end def string_Array(o, scope) "[#{o.map {|e| string(e, scope)}.join(COMMA_SEPARATOR)}]" end def string_Hash(o, scope) "{#{o.map {|k,v| "#{string(k, scope)} => #{string(v, scope)}"}.join(COMMA_SEPARATOR)}}" end def string_Regexp(o, scope) Types::PRegexpType.regexp_to_s_with_delimiters(o) end def string_PAnyType(o, scope) o.to_s end # Produces concatenation / merge of x and y. # # When x is an Array, y of type produces: # # * Array => concatenation `[1,2], [3,4] => [1,2,3,4]` # * Hash => concatenation of hash as array `[key, value, key, value, ...]` # * any other => concatenation of single value # # When x is a Hash, y of type produces: # # * Array => merge of array interpreted as `[key, value, key, value,...]` # * Hash => a merge, where entries in `y` overrides # * any other => error # # When x is a URI, y of type produces: # # * String => merge of URI interpreted x + URI(y) using URI merge semantics # * URI => merge of URI interpreted x + y using URI merge semantics # * any other => error # # When x is nil, an empty array is used instead. # # @note to concatenate an Array, nest the array - i.e. `[1,2], [[2,3]]` # # @overload concatenate(obj_x, obj_y) # @param obj_x [Object] object to wrap in an array and concatenate to; see other overloaded methods for return type # @param ary_y [Object] array to concatenate at end of `ary_x` # @return [Object] wraps obj_x in array before using other overloaded option based on type of obj_y # @overload concatenate(ary_x, ary_y) # @param ary_x [Array] array to concatenate to # @param ary_y [Array] array to concatenate at end of `ary_x` # @return [Array] new array with `ary_x` + `ary_y` # @overload concatenate(ary_x, hsh_y) # @param ary_x [Array] array to concatenate to # @param hsh_y [Hash] converted to array form, and concatenated to array # @return [Array] new array with `ary_x` + `hsh_y` converted to array # @overload concatenate (ary_x, obj_y) # @param ary_x [Array] array to concatenate to # @param obj_y [Object] non array or hash object to add to array # @return [Array] new array with `ary_x` + `obj_y` added as last entry # @overload concatenate(hsh_x, ary_y) # @param hsh_x [Hash] the hash to merge with # @param ary_y [Array] array interpreted as even numbered sequence of key, value merged with `hsh_x` # @return [Hash] new hash with `hsh_x` merged with `ary_y` interpreted as hash in array form # @overload concatenate(hsh_x, hsh_y) # @param hsh_x [Hash] the hash to merge to # @param hsh_y [Hash] hash merged with `hsh_x` # @return [Hash] new hash with `hsh_x` merged with `hsh_y` # @overload concatenate(uri_x, uri_y) # @param uri_x [URI] the uri to merge to # @param uri_y [URI] uri to merged with `uri_x` # @return [URI] new uri with `uri_x` merged with `uri_y` # @overload concatenate(uri_x, string_y) # @param uri_x [URI] the uri to merge to # @param string_y [String] string to merge with `uri_x` # @return [URI] new uri with `uri_x` merged with `string_y` # @raise [ArgumentError] when `xxx_x` is neither an Array nor a Hash # @raise [ArgumentError] when `xxx_x` is a Hash, and `xxx_y` is neither Array nor Hash. # def concatenate(x, y) case x when Array y = case y when Array then y when Hash then y.to_a else [y] end x + y # new array with concatenation when Hash y = case y when Hash then y when Array # Hash[[a, 1, b, 2]] => {} # Hash[a,1,b,2] => {a => 1, b => 2} # Hash[[a,1], [b,2]] => {[a,1] => [b,2]} # Hash[[[a,1], [b,2]]] => {a => 1, b => 2} # Use type calculator to determine if array is Array[Array[?]], and if so use second form # of call t = @@type_calculator.infer(y) if t.element_type.is_a? Types::PArrayType Hash[y] else Hash[*y] end else raise ArgumentError.new(_('Can only append Array or Hash to a Hash')) end x.merge y # new hash with overwrite when URI raise ArgumentError.new(_('An URI can only be merged with an URI or String')) unless y.is_a?(String) || y.is_a?(URI) x + y else concatenate([x], y) end end # Produces the result x \ y (set difference) # When `x` is an Array, `y` is transformed to an array and then all matching elements removed from x. # When `x` is a Hash, all contained keys are removed from x as listed in `y` if it is an Array, or all its keys if it is a Hash. # The difference is returned. The given `x` and `y` are not modified by this operation. # @raise [ArgumentError] when `x` is neither an Array nor a Hash # def delete(x, y) result = x.dup case x when Array y = case y when Array then y when Hash then y.to_a else [y] end y.each {|e| result.delete(e) } when Hash y = case y when Array then y when Hash then y.keys else [y] end y.each {|e| result.delete(e) } else raise ArgumentError.new(_("Can only delete from an Array or Hash.")) end result end # Implementation of case option matching. # # This is the type of matching performed in a case option, using == for every type # of value except regular expression where a match is performed. # def is_match?(left, right, o, option_expr, scope) @@compare_operator.match(left, right, scope) end # Maps the expression in the given array to their product except for UnfoldExpressions which are first unfolded. # The result is added to the given result Array. # @param result [Array] Where to add the result (may contain information to add to) # @param array [Array[Model::Expression] the expressions to map # @param scope [Puppet::Parser::Scope] the scope to evaluate in # @return [Array] the given result array with content added from the operation # def unfold(result, array, scope) array.each do |x| x = unwind_parentheses(x) if x.is_a?(Model::UnfoldExpression) result.concat(evaluate(x, scope)) else result << evaluate(x, scope) end end result end private :unfold def unwind_parentheses(o) return o unless o.is_a?(Model::ParenthesizedExpression) unwind_parentheses(o.expr) end private :unwind_parentheses end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/evaluator/external_syntax_support.rb����������������������������������0000644�0052762�0001160�00000003326�13417161721�025435� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This module is an integral part of the evaluator. It deals with the concern of validating # external syntax in text produced by heredoc and templates. # require 'puppet/plugins/syntax_checkers' module Puppet::Pops::Evaluator::ExternalSyntaxSupport def assert_external_syntax(scope, result, syntax, reference_expr) # ignore 'unspecified syntax' return if syntax.nil? || syntax == '' checker = checker_for_syntax(scope, syntax) # ignore syntax with no matching checker return unless checker # Call checker and give it the location information from the expression # (as opposed to where the heredoc tag is (somewhere on the line above)). acceptor = Puppet::Pops::Validation::Acceptor.new() checker.check(result, syntax, acceptor, reference_expr) if acceptor.error_count > 0 checker_message = "Invalid produced text having syntax: '#{syntax}'." Puppet::Pops::IssueReporter.assert_and_report(acceptor, :message => checker_message) raise ArgumentError, _("Internal Error: Configuration of runtime error handling wrong: should have raised exception") end end # Finds the most significant checker for the given syntax (most significant is to the right). # Returns nil if there is no registered checker. # def checker_for_syntax(scope, syntax) checkers_hash = Puppet.lookup(:plugins)[Puppet::Plugins::SyntaxCheckers::SYNTAX_CHECKERS_KEY] checkers_hash[lookup_keys_for_syntax(syntax).find {|x| checkers_hash[x] }] end # Returns an array of possible syntax names def lookup_keys_for_syntax(syntax) segments = syntax.split(/\+/) result = [] begin result << segments.join("+") segments.shift end until segments.empty? result end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/evaluator/runtime3_converter.rb���������������������������������������0000644�0052762�0001160�00000016113�13417161721�024244� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops::Evaluator # Converts nested 4x supported values to 3x values. This is required because # resources and other objects do not know about the new type system, and does not support # regular expressions. Unfortunately this has to be done for array and hash as well. # A complication is that catalog types needs to be resolved against the scope. # # Users should not create instances of this class. Instead the class methods {Runtime3Converter.convert}, # {Runtime3Converter.map_args}, or {Runtime3Converter.instance} should be used class Runtime3Converter MAX_INTEGER = Puppet::Pops::MAX_INTEGER MIN_INTEGER = Puppet::Pops::MIN_INTEGER # Converts 4x supported values to a 3x values. Same as calling Runtime3Converter.instance.map_args(...) # # @param args [Array] Array of values to convert # @param scope [Puppet::Parser::Scope] The scope to use when converting # @param undef_value [Object] The value that nil is converted to # @return [Array] The converted values # def self.map_args(args, scope, undef_value) @instance.map_args(args, scope, undef_value) end # Converts 4x supported values to a 3x values. Same as calling Runtime3Converter.instance.convert(...) # # @param o [Object]The value to convert # @param scope [Puppet::Parser::Scope] The scope to use when converting # @param undef_value [Object] The value that nil is converted to # @return [Object] The converted value # def self.convert(o, scope, undef_value) @instance.convert(o, scope, undef_value) end # Returns the singleton instance of this class. # @return [Runtime3Converter] The singleton instance def self.instance @instance end # Converts 4x supported values to a 3x values. # # @param args [Array] Array of values to convert # @param scope [Puppet::Parser::Scope] The scope to use when converting # @param undef_value [Object] The value that nil is converted to # @return [Array] The converted values # def map_args(args, scope, undef_value) args.map {|a| convert(a, scope, undef_value) } end # Converts a 4x supported value to a 3x value. # # @param o [Object]The value to convert # @param scope [Puppet::Parser::Scope] The scope to use when converting # @param undef_value [Object] The value that nil is converted to # @return [Object] The converted value # def convert(o, scope, undef_value) @convert_visitor.visit_this_2(self, o, scope, undef_value) end def convert_NilClass(o, scope, undef_value) @inner ? :undef : undef_value end def convert_Integer(o, scope, undef_value) return o unless o < MIN_INTEGER || o > MAX_INTEGER range_end = o > MAX_INTEGER ? 'max' : 'min' raise Puppet::Error, "Use of a Ruby Integer outside of Puppet Integer #{range_end} range, got '#{"0x%x" % o}'" end def convert_BigDecimal(o, scope, undef_value) # transform to same value float value if possible without any rounding error f = o.to_f return f unless f != o raise Puppet::Error, "Use of a Ruby BigDecimal value outside Puppet Float range, got '#{o}'" end def convert_String(o, scope, undef_value) # although wasteful, needed because user code may mutate these strings in Resources o.frozen? ? o.dup : o end def convert_Object(o, scope, undef_value) o end def convert_Array(o, scope, undef_value) ic = @inner_converter o.map {|x| ic.convert(x, scope, undef_value) } end def convert_Hash(o, scope, undef_value) result = {} ic = @inner_converter o.each {|k,v| result[ic.convert(k, scope, undef_value)] = ic.convert(v, scope, undef_value) } result end def convert_Iterator(o, scope, undef_value) raise Puppet::Error, _('Use of an Iterator is not supported here') end def convert_Symbol(o, scope, undef_value) o == :undef && !@inner ? undef_value : o end def convert_PAnyType(o, scope, undef_value) o end def convert_PCatalogEntryType(o, scope, undef_value) # Since 4x does not support dynamic scoping, all names are absolute and can be # used as is (with some check/transformation/mangling between absolute/relative form # due to Puppet::Resource's idiosyncratic behavior where some references must be # absolute and others cannot be. # Thus there is no need to call scope.resolve_type_and_titles to do dynamic lookup. t, title = catalog_type_to_split_type_title(o) t = Runtime3ResourceSupport.find_resource_type(scope, t) unless t == 'class' || t == 'node' Puppet::Resource.new(t, title) end # Produces an array with [type, title] from a PCatalogEntryType # This method is used to produce the arguments for creation of reference resource instances # (used when 3x is operating on a resource). # Ensures that resources are *not* absolute. # def catalog_type_to_split_type_title(catalog_type) split_type = catalog_type.is_a?(Puppet::Pops::Types::PTypeType) ? catalog_type.type : catalog_type case split_type when Puppet::Pops::Types::PClassType class_name = split_type.class_name ['class', class_name.nil? ? nil : class_name.sub(/^::/, '')] when Puppet::Pops::Types::PResourceType type_name = split_type.type_name title = split_type.title if type_name =~ /^(::)?[Cc]lass$/ ['class', title.nil? ? nil : title.sub(/^::/, '')] else # Ensure that title is '' if nil # Resources with absolute name always results in error because tagging does not support leading :: [type_name.nil? ? nil : type_name.sub(/^::/, '').downcase, title.nil? ? '' : title] end else #TRANSLATORS 'PClassType' and 'PResourceType' are Puppet types and should not be translated raise ArgumentError, _("Cannot split the type %{class_name}, it represents neither a PClassType, nor a PResourceType.") % { class_name: catalog_type.class } end end protected def initialize(inner = false) @inner = inner @inner_converter = inner ? self : self.class.new(true) @convert_visitor = Puppet::Pops::Visitor.new(self, 'convert', 2, 2) end @instance = self.new end # A Ruby function written for the 3.x API cannot be expected to handle extended data types. This # converter ensures that they are converted to String format # @api private class Runtime3FunctionArgumentConverter < Runtime3Converter def convert_Regexp(o, scope, undef_value) # Puppet 3x cannot handle parameter values that are regular expressions. Turn into regexp string in # source form o.inspect end def convert_Version(o, scope, undef_value) # Puppet 3x cannot handle SemVers. Use the string form o.to_s end def convert_VersionRange(o, scope, undef_value) # Puppet 3x cannot handle SemVerRanges. Use the string form o.to_s end def convert_Binary(o, scope, undef_value) # Puppet 3x cannot handle Binary. Use the string form o.to_s end def convert_Timespan(o, scope, undef_value) # Puppet 3x cannot handle Timespans. Use the string form o.to_s end def convert_Timestamp(o, scope, undef_value) # Puppet 3x cannot handle Timestamps. Use the string form o.to_s end @instance = self.new end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/evaluator/runtime3_support.rb�����������������������������������������0000644�0052762�0001160�00000064037�13417161721�023761� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Evaluator # A module with bindings between the new evaluator and the 3x runtime. # The intention is to separate all calls into scope, compiler, resource, etc. in this module # to make it easier to later refactor the evaluator for better implementations of the 3x classes. # # @api private module Runtime3Support NAME_SPACE_SEPARATOR = '::'.freeze # Fails the evaluation of _semantic_ with a given issue. # # @param issue [Issue] the issue to report # @param semantic [ModelPopsObject] the object for which evaluation failed in some way. Used to determine origin. # @param options [Hash] hash of optional named data elements for the given issue # @return [!] this method does not return # @raise [Puppet::ParseError] an evaluation error initialized from the arguments (TODO: Change to EvaluationError?) # def fail(issue, semantic, options={}, except=nil) optionally_fail(issue, semantic, options, except) # an error should have been raised since fail always fails raise ArgumentError, _("Internal Error: Configuration of runtime error handling wrong: should have raised exception") end # Optionally (based on severity) Fails the evaluation of _semantic_ with a given issue # If the given issue is configured to be of severity < :error it is only reported, and the function returns. # # @param issue [Issue] the issue to report # @param semantic [ModelPopsObject] the object for which evaluation failed in some way. Used to determine origin. # @param options [Hash] hash of optional named data elements for the given issue # @return [!] this method does not return # @raise [Puppet::ParseError] an evaluation error initialized from the arguments (TODO: Change to EvaluationError?) # def optionally_fail(issue, semantic, options={}, except=nil) if except.nil? # Want a stacktrace, and it must be passed as an exception begin raise EvaluationError.new() rescue EvaluationError => e except = e end end diagnostic_producer.accept(issue, semantic, options, except) end # Optionally (based on severity) Fails the evaluation with a given issue # If the given issue is configured to be of severity < :error it is only reported, and the function returns. # The location the issue is reported against is found is based on the top file/line in the puppet call stack # # @param issue [Issue] the issue to report # @param options [Hash] hash of optional named data elements for the given issue # @return [!] this method may not return, nil if it does # @raise [Puppet::ParseError] an evaluation error initialized from the arguments # def runtime_issue(issue, options={}) # Get position from puppet runtime stack file, line = Puppet::Pops::PuppetStack.top_of_stack # Use a SemanticError as the sourcepos semantic = Puppet::Pops::SemanticError.new(issue, nil, options.merge({:file => file, :line => line})) optionally_fail(issue, semantic) nil end # Binds the given variable name to the given value in the given scope. # The reference object `o` is intended to be used for origin information - the 3x scope implementation # only makes use of location when there is an error. This is now handled by other mechanisms; first a check # is made if a variable exists and an error is raised if attempting to change an immutable value. Errors # in name, numeric variable assignment etc. have also been validated prior to this call. In the event the # scope.setvar still raises an error, the general exception handling for evaluation of the assignment # expression knows about its location. Because of this, there is no need to extract the location for each # setting (extraction is somewhat expensive since 3x requires line instead of offset). # def set_variable(name, value, o, scope) # Scope also checks this but requires that location information are passed as options. # Those are expensive to calculate and a test is instead made here to enable failing with better information. # The error is not specific enough to allow catching it - need to check the actual message text. # TODO: Improve the messy implementation in Scope. # if name == "server_facts" fail(Issues::ILLEGAL_RESERVED_ASSIGNMENT, o, {:name => name} ) end if scope.bound?(name) if Puppet::Parser::Scope::RESERVED_VARIABLE_NAMES.include?(name) fail(Issues::ILLEGAL_RESERVED_ASSIGNMENT, o, {:name => name} ) else fail(Issues::ILLEGAL_REASSIGNMENT, o, {:name => name} ) end end scope.setvar(name, value) end # Returns the value of the variable (nil is returned if variable has no value, or if variable does not exist) # def get_variable_value(name, o, scope) # Puppet 3x stores all variables as strings (then converts them back to numeric with a regexp... to see if it is a match variable) # Not ideal, scope should support numeric lookup directly instead. # TODO: consider fixing scope catch(:undefined_variable) { x = scope.lookupvar(name.to_s) # Must convert :undef back to nil - this can happen when an undefined variable is used in a # parameter's default value expression - there nil must be :undef to work with the rest of 3x. # Now that the value comes back to 4x it is changed to nil. return :undef.equal?(x) ? nil : x } # It is always ok to reference numeric variables even if they are not assigned. They are always undef # if not set by a match expression. # unless name =~ Puppet::Pops::Patterns::NUMERIC_VAR_NAME optionally_fail(Puppet::Pops::Issues::UNKNOWN_VARIABLE, o, {:name => name}) end nil # in case unknown variable is configured as a warning or ignore end # Returns true if the variable of the given name is set in the given most nested scope. True is returned even if # variable is bound to nil. # def variable_bound?(name, scope) scope.bound?(name.to_s) end # Returns true if the variable is bound to a value or nil, in the scope or it's parent scopes. # def variable_exists?(name, scope) scope.exist?(name.to_s) end def set_match_data(match_data, scope) # See set_variable for rationale for not passing file and line to ephemeral_from. # NOTE: The 3x scope adds one ephemeral(match) to its internal stack per match that succeeds ! It never # clears anything. Thus a context that performs many matches will get very deep (there simply is no way to # clear the match variables without rolling back the ephemeral stack.) # This implementation does not attempt to fix this, it behaves the same bad way. unless match_data.nil? scope.ephemeral_from(match_data) end end # Creates a local scope with vairalbes set from a hash of variable name to value # def create_local_scope_from(hash, scope) # two dummy values are needed since the scope tries to give an error message (can not happen in this # case - it is just wrong, the error should be reported by the caller who knows in more detail where it # is in the source. # raise ArgumentError, _("Internal error - attempt to create a local scope without a hash") unless hash.is_a?(Hash) scope.ephemeral_from(hash) end # Creates a nested match scope def create_match_scope_from(scope) # Create a transparent match scope (for future matches) scope.new_match_scope(nil) end def get_scope_nesting_level(scope) scope.ephemeral_level end def set_scope_nesting_level(scope, level) # 3x uses this method to reset the level, scope.pop_ephemerals(level) end # Adds a relationship between the given `source` and `target` of the given `relationship_type` # @param source [Puppet:Pops::Types::PCatalogEntryType] the source end of the relationship (from) # @param target [Puppet:Pops::Types::PCatalogEntryType] the target end of the relationship (to) # @param relationship_type [:relationship, :subscription] the type of the relationship # def add_relationship(source, target, relationship_type, scope) # The 3x way is to record a Puppet::Parser::Relationship that is evaluated at the end of the compilation. # This means it is not possible to detect any duplicates at this point (and signal where an attempt is made to # add a duplicate. There is also no location information to signal the original place in the logic. The user will have # to go fish. # The 3.x implementation is based on Strings :-o, so the source and target must be transformed. The resolution is # done by Catalog#resource(type, title). To do that, it creates a Puppet::Resource since it is responsible for # translating the name/type/title and create index-keys used by the catalog. The Puppet::Resource has bizarre parsing of # the type and title (scan for [] that is interpreted as type/title (but it gets it wrong). # Moreover if the type is "" or "component", the type is Class, and if the type is :main, it is :main, all other cases # undergo capitalization of name-segments (foo::bar becomes Foo::Bar). (This was earlier done in the reverse by the parser). # Further, the title undergoes the same munging !!! # # That bug infested nest of messy logic needs serious Exorcism! # # Unfortunately it is not easy to simply call more intelligent methods at a lower level as the compiler evaluates the recorded # Relationship object at a much later point, and it is responsible for invoking all the messy logic. # # TODO: Revisit the below logic when there is a sane implementation of the catalog, compiler and resource. For now # concentrate on transforming the type references to what is expected by the wacky logic. # # HOWEVER, the Compiler only records the Relationships, and the only method it calls is @relationships.each{|x| x.evaluate(catalog) } # Which means a smarter Relationship class could do this right. Instead of obtaining the resource from the catalog using # the borked resource(type, title) which creates a resource for the purpose of looking it up, it needs to instead # scan the catalog's resources # # GAAAH, it is even worse! # It starts in the parser, which parses "File['foo']" into an AST::ResourceReference with type = File, and title = foo # This AST is evaluated by looking up the type/title in the scope - causing it to be loaded if it exists, and if not, the given # type name/title is used. It does not search for resource instances, only classes and types. It returns symbolic information # [type, [title, title]]. From this, instances of Puppet::Resource are created and returned. These only have type/title information # filled out. One or an array of resources are returned. # This set of evaluated (empty reference) Resource instances are then passed to the relationship operator. It creates a # Puppet::Parser::Relationship giving it a source and a target that are (empty reference) Resource instances. These are then remembered # until the relationship is evaluated by the compiler (at the end). When evaluation takes place, the (empty reference) Resource instances # are converted to String (!?! WTF) on the simple format "#{type}[#{title}]", and the catalog is told to find a resource, by giving # it this string. If it cannot find the resource it fails, else the before/notify parameter is appended with the target. # The search for the resource begin with (you guessed it) again creating an (empty reference) resource from type and title (WTF?!?!). # The catalog now uses the reference resource to compute a key [r.type, r.title.to_s] and also gets a uniqueness key from the # resource (This is only a reference type created from title and type). If it cannot find it with the first key, it uses the # uniqueness key to lookup. # # This is probably done to allow a resource type to munge/translate the title in some way (but it is quite unclear from the long # and convoluted path of evaluation. # In order to do this in a way that is similar to 3.x two resources are created to be used as keys. # # And if that is not enough, a source/target may be a Collector (a baked query that will be evaluated by the # compiler - it is simply passed through here for processing by the compiler at the right time). # if source.is_a?(Collectors::AbstractCollector) # use verbatim - behavior defined by 3x source_resource = source else # transform into the wonderful String representation in 3x type, title = Runtime3Converter.instance.catalog_type_to_split_type_title(source) type = Runtime3ResourceSupport.find_resource_type(scope, type) unless type == 'class' || type == 'node' source_resource = Puppet::Resource.new(type, title) end if target.is_a?(Collectors::AbstractCollector) # use verbatim - behavior defined by 3x target_resource = target else # transform into the wonderful String representation in 3x type, title = Runtime3Converter.instance.catalog_type_to_split_type_title(target) type = Runtime3ResourceSupport.find_resource_type(scope, type) unless type == 'class' || type == 'node' target_resource = Puppet::Resource.new(type, title) end # Add the relationship to the compiler for later evaluation. scope.compiler.add_relationship(Puppet::Parser::Relationship.new(source_resource, target_resource, relationship_type)) end # Coerce value `v` to numeric or fails. # The given value `v` is coerced to Numeric, and if that fails the operation # calls {#fail}. # @param v [Object] the value to convert # @param o [Object] originating instruction # @param scope [Object] the (runtime specific) scope where evaluation of o takes place # @return [Numeric] value `v` converted to Numeric. # def coerce_numeric(v, o, scope) if v.is_a?(Numeric) return v end unless n = Utils.to_n(v) fail(Issues::NOT_NUMERIC, o, {:value => v}) end # this point is reached if there was a conversion optionally_fail(Issues::NUMERIC_COERCION, o, {:before => v, :after => n}) n end # Provides the ability to call a 3.x or 4.x function from the perspective of a 3.x function or ERB template. # The arguments to the function must be an Array containing values compliant with the 4.x calling convention. # If the targeted function is a 3.x function, the values will be transformed. # @param name [String] the name of the function (without the 'function_' prefix used by scope) # @param args [Array] arguments, may be empty # @param scope [Object] the (runtime specific) scope where evaluation takes place # @raise [ArgumentError] 'unknown function' if the function does not exist # def external_call_function(name, args, scope, &block) # Call via 4x API if the function exists there loaders = scope.compiler.loaders # Since this is a call from non puppet code, it is not possible to relate it to a module loader # It is known where the call originates by using the scope associated module - but this is the calling scope # and it does not defined the visibility of functions from a ruby function's perspective. Instead, # this is done from the perspective of the environment. loader = loaders.private_environment_loader if loader && func = loader.load(:function, name) Puppet::Util::Profiler.profile(name, [:functions, name]) do return func.call(scope, *args, &block) end end # Call via 3x API if function exists there raise ArgumentError, _("Unknown function '%{name}'") % { name: name } unless Puppet::Parser::Functions.function(name) # Arguments must be mapped since functions are unaware of the new and magical creatures in 4x. # NOTE: Passing an empty string last converts nil/:undef to empty string mapped_args = Runtime3FunctionArgumentConverter.map_args(args, scope, '') result = scope.send("function_#{name}", mapped_args, &block) # Prevent non r-value functions from leaking their result (they are not written to care about this) Puppet::Parser::Functions.rvalue?(name) ? result : nil end def call_function(name, args, o, scope, &block) file, line = extract_file_line(o) loader = Adapters::LoaderAdapter.loader_for_model_object(o, file) # 'ruby -wc' thinks that _func is unused, because the only reference to it # is inside of the Kernel.eval string below. By prefixing it with the # underscore, we let Ruby know to not worry about whether it's unused or not. if loader && _func = loader.load(:function, name) Puppet::Util::Profiler.profile(name, [:functions, name]) do # Add stack frame when calling. See Puppet::Pops::PuppetStack return Kernel.eval('_func.call(scope, *args, &block)'.freeze, Kernel.binding, file || '', line) end end # Call via 3x API if function exists there fail(Issues::UNKNOWN_FUNCTION, o, {:name => name}) unless Puppet::Parser::Functions.function(name) # Arguments must be mapped since functions are unaware of the new and magical creatures in 4x. # NOTE: Passing an empty string last converts nil/:undef to empty string mapped_args = Runtime3FunctionArgumentConverter.map_args(args, scope, '') result = Puppet::Pops::PuppetStack.stack(file, line, scope, "function_#{name}", [mapped_args], &block) # Prevent non r-value functions from leaking their result (they are not written to care about this) Puppet::Parser::Functions.rvalue?(name) ? result : nil end # The o is used for source reference def create_resource_parameter(o, scope, name, value, operator) file, line = extract_file_line(o) Puppet::Parser::Resource::Param.new( :name => name, :value => convert(value, scope, nil), # converted to 3x since 4x supports additional objects / types :source => scope.source, :line => line, :file => file, :add => operator == '+>' ) end def convert(value, scope, undef_value) Runtime3Converter.instance.convert(value, scope, undef_value) end def create_resources(o, scope, virtual, exported, type_name, resource_titles, evaluated_parameters) # Not 100% accurate as this is the resource expression location and each title is processed separately # The titles are however the result of evaluation and they have no location at this point (an array # of positions for the source expressions are required for this to work). # TODO: Revisit and possible improve the accuracy. # file, line = extract_file_line(o) Runtime3ResourceSupport.create_resources(file, line, scope, virtual, exported, type_name, resource_titles, evaluated_parameters) end # Defines default parameters for a type with the given name. # def create_resource_defaults(o, scope, type_name, evaluated_parameters) # Note that name must be capitalized in this 3x call # The 3x impl creates a Resource instance with a bogus title and then asks the created resource # for the type of the name. # Note, locations are available per parameter. # scope.define_settings(capitalize_qualified_name(type_name), evaluated_parameters.flatten) end # Capitalizes each segment of a qualified name # def capitalize_qualified_name(name) name.split(/::/).map(&:capitalize).join(NAME_SPACE_SEPARATOR) end # Creates resource overrides for all resource type objects in evaluated_resources. The same set of # evaluated parameters are applied to all. # def create_resource_overrides(o, scope, evaluated_resources, evaluated_parameters) # Not 100% accurate as this is the resource expression location and each title is processed separately # The titles are however the result of evaluation and they have no location at this point (an array # of positions for the source expressions are required for this to work. # TODO: Revisit and possible improve the accuracy. # file, line = extract_file_line(o) # A *=> results in an array of arrays evaluated_parameters = evaluated_parameters.flatten evaluated_resources.each do |r| unless r.is_a?(Types::PResourceType) && r.type_name != 'class' fail(Issues::ILLEGAL_OVERRIDDEN_TYPE, o, {:actual => r} ) end t = Runtime3ResourceSupport.find_resource_type(scope, r.type_name) resource = Puppet::Parser::Resource.new( t, r.title, { :parameters => evaluated_parameters, :file => file, :line => line, # WTF is this? Which source is this? The file? The name of the context ? :source => scope.source, :scope => scope }, false # defaults should not override ) scope.compiler.add_override(resource) end end # Finds a resource given a type and a title. # def find_resource(scope, type_name, title) scope.compiler.findresource(type_name, title) end # Returns the value of a resource's parameter by first looking up the parameter in the resource # and then in the defaults for the resource. Since the resource exists (it must in order to look up its # parameters, any overrides have already been applied). Defaults are not applied to a resource until it # has been finished (which typically has not taken place when this is evaluated; hence the dual lookup). # def get_resource_parameter_value(scope, resource, parameter_name) # This gets the parameter value, or nil (for both valid parameters and parameters that do not exist). val = resource[parameter_name] # Sometimes the resource is a Puppet::Parser::Resource and sometimes it is # a Puppet::Resource. The Puppet::Resource case occurs when puppet language # is evaluated against an already completed catalog (where all instances of # Puppet::Parser::Resource are converted to Puppet::Resource instances). # Evaluating against an already completed catalog is really only found in # the language specification tests, where the puppet language is used to # test itself. if resource.is_a?(Puppet::Parser::Resource) # The defaults must be looked up in the scope where the resource was created (not in the given # scope where the lookup takes place. resource_scope = resource.scope if val.nil? && resource_scope && defaults = resource_scope.lookupdefaults(resource.type) # NOTE: 3x resource keeps defaults as hash using symbol for name as key to Parameter which (again) holds # name and value. # NOTE: meta parameters that are unset ends up here, and there are no defaults for those encoded # in the defaults, they may receive hardcoded defaults later (e.g. 'tag'). param = defaults[parameter_name.to_sym] # Some parameters (meta parameters like 'tag') does not return a param from which the value can be obtained # at all times. Instead, they return a nil param until a value has been set. val = param.nil? ? nil : param.value end end val end # Returns true, if the given name is the name of a resource parameter. # def is_parameter_of_resource?(scope, resource, name) return false unless name.is_a?(String) resource.valid_parameter?(name) end def resource_to_ptype(resource) nil if resource.nil? # inference returns the meta type since the 3x Resource is an alternate way to describe a type type_calculator.infer(resource).type end # This is the same type of "truth" as used in the current Puppet DSL. # def is_true?(value, o) # Is the value true? This allows us to control the definition of truth # in one place. case value # Support :undef since it may come from a 3x structure when :undef false when String true else !!value end end # Utility method for TrueClass || FalseClass # @param x [Object] the object to test if it is instance of TrueClass or FalseClass def is_boolean? x x.is_a?(TrueClass) || x.is_a?(FalseClass) end def extract_file_line(o) o.is_a?(Model::Positioned) ? [o.file, o.line] : [nil, -1] end # Creates a diagnostic producer def diagnostic_producer Validation::DiagnosticProducer.new( ExceptionRaisingAcceptor.new(), # Raises exception on all issues SeverityProducer.new(), # All issues are errors Model::ModelLabelProvider.new()) end # Configure the severity of failures class SeverityProducer < Validation::SeverityProducer Issues = Issues def initialize super p = self # Issues triggering warning only if --debug is on if Puppet[:debug] p[Issues::EMPTY_RESOURCE_SPECIALIZATION] = :warning else p[Issues::EMPTY_RESOURCE_SPECIALIZATION] = :ignore end # if strict variables are on, an error is raised # if strict variables are off, the Puppet[strict] defines what is done # if Puppet[:strict_variables] p[Issues::UNKNOWN_VARIABLE] = :error elsif Puppet[:strict] == :off p[Issues::UNKNOWN_VARIABLE] = :ignore else p[Issues::UNKNOWN_VARIABLE] = Puppet[:strict] end # Store config issues, ignore or warning p[Issues::RT_NO_STORECONFIGS_EXPORT] = Puppet[:storeconfigs] ? :ignore : :warning p[Issues::RT_NO_STORECONFIGS] = Puppet[:storeconfigs] ? :ignore : :warning p[Issues::CLASS_NOT_VIRTUALIZABLE] = Puppet[:strict] == :off ? :warning : Puppet[:strict] p[Issues::NUMERIC_COERCION] = Puppet[:strict] == :off ? :ignore : Puppet[:strict] p[Issues::SERIALIZATION_DEFAULT_CONVERTED_TO_STRING] = Puppet[:strict] == :off ? :warning : Puppet[:strict] p[Issues::SERIALIZATION_UNKNOWN_CONVERTED_TO_STRING] = Puppet[:strict] == :off ? :warning : Puppet[:strict] p[Issues::SERIALIZATION_UNKNOWN_KEY_CONVERTED_TO_STRING] = Puppet[:strict] == :off ? :warning : Puppet[:strict] end end # An acceptor of diagnostics that immediately raises an exception. class ExceptionRaisingAcceptor < Validation::Acceptor def accept(diagnostic) super IssueReporter.assert_and_report(self, { :message => "Evaluation Error:", :emit_warnings => true, # log warnings :exception_class => Puppet::PreformattedError }) if errors? raise ArgumentError, _("Internal Error: Configuration of runtime error handling wrong: should have raised exception") end end end class EvaluationError < StandardError end end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/functions/������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020073� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/functions/dispatcher.rb�����������������������������������������������0000644�0052762�0001160�00000004466�13417161721�022553� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Evaluate the dispatches defined as {Puppet::Pops::Functions::Dispatch} # instances to call the appropriate method on the # {Puppet::Pops::Functions::Function} instance. # # @api private class Puppet::Pops::Functions::Dispatcher attr_reader :dispatchers # @api private def initialize() @dispatchers = [ ] end # Answers if dispatching has been defined # @return [Boolean] true if dispatching has been defined # # @api private def empty? @dispatchers.empty? end # Dispatches the call to the first found signature (entry with matching type). # # @param instance [Puppet::Functions::Function] - the function to call # @param calling_scope [T.B.D::Scope] - the scope of the caller # @param args [Array<Object>] - the given arguments in the form of an Array # @return [Object] - what the called function produced # # @api private def dispatch(instance, calling_scope, args, &block) found = @dispatchers.find { |d| d.type.callable_with?(args, block) } unless found args_type = Puppet::Pops::Types::TypeCalculator.singleton.infer_set(block_given? ? args + [block] : args) raise ArgumentError, Puppet::Pops::Types::TypeMismatchDescriber.describe_signatures(instance.class.name, signatures, args_type) end if found.argument_mismatch_handler? msg = found.invoke(instance, calling_scope, args) raise ArgumentError, "'#{instance.class.name}' #{msg}" end catch(:next) do found.invoke(instance, calling_scope, args, &block) end end # Adds a dispatch directly to the set of dispatchers. # @api private def add(a_dispatch) @dispatchers << a_dispatch end # Produces a CallableType for a single signature, and a Variant[<callables>] otherwise # # @api private def to_type() # make a copy to make sure it can be contained by someone else (even if it is not contained here, it # should be treated as immutable). # callables = dispatchers.map { | dispatch | dispatch.type } # multiple signatures, produce a Variant type of Callable1-n (must copy them) # single signature, produce single Callable callables.size > 1 ? Puppet::Pops::Types::TypeFactory.variant(*callables) : callables.pop end # @api private def signatures @dispatchers.reject { |dispatcher| dispatcher.argument_mismatch_handler? } end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/functions/function.rb�������������������������������������������������0000644�0052762�0001160�00000012762�13417161721�022250� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# @note WARNING: This new function API is still under development and may change at # any time # # A function in the puppet evaluator. # # Functions are normally defined by another system, which produces subclasses # of this class as well as constructing delegations to call the appropriate methods. # # This class should rarely be used directly. Instead functions should be # constructed using {Puppet::Functions.create_function}. # # @api public class Puppet::Pops::Functions::Function # The scope where the function was defined attr_reader :closure_scope # The loader that loaded this function. # Should be used if function wants to load other things. # attr_reader :loader def initialize(closure_scope, loader) @closure_scope = closure_scope @loader = loader end # Invokes the function via the dispatching logic that performs type check and weaving. # A specialized function may override this method to do its own dispatching and checking of # the raw arguments. A specialized implementation can rearrange arguments, add or remove # arguments and then delegate to the dispatching logic by calling: # # @example Delegating to the dispatcher # def call(scope, *args) # manipulated_args = args + ['easter_egg'] # self.class.dispatcher.dispatch(self, scope, manipulated_args) # end # # System functions that must have access to the calling scope can use this technique. Functions # in general should not need the calling scope. (The closure scope; what is visible where the function # is defined) is available via the method `closure_scope`). # # @api public def call(scope, *args, &block) begin result = catch(:return) do return self.class.dispatcher.dispatch(self, scope, args, &block) end return result.value rescue Puppet::Pops::Evaluator::Next => jumper begin throw :next, jumper.value rescue Puppet::Parser::Scope::UNCAUGHT_THROW_EXCEPTION raise Puppet::ParseError.new("next() from context where this is illegal", jumper.file, jumper.line) end rescue Puppet::Pops::Evaluator::Return => jumper begin throw :return, jumper rescue Puppet::Parser::Scope::UNCAUGHT_THROW_EXCEPTION raise Puppet::ParseError.new("return() from context where this is illegal", jumper.file, jumper.line) end end end # Allows the implementation of a function to call other functions by name. The callable functions # are those visible to the same loader that loaded this function (the calling function). The # referenced function is called with the calling functions closure scope as the caller's scope. # # @param function_name [String] The name of the function # @param *args [Object] splat of arguments # @return [Object] The result returned by the called function # # @api public def call_function(function_name, *args, &block) internal_call_function(closure_scope, function_name, args, &block) end def closure_scope # If closure scope is explicitly set to not nil, if there is a global scope, otherwise an empty hash @closure_scope || Puppet.lookup(:global_scope) { {} } end # The dispatcher for the function # # @api private def self.dispatcher @dispatcher ||= Puppet::Pops::Functions::Dispatcher.new end # Produces information about parameters in a way that is compatible with Closure # # @api private def self.signatures @dispatcher.signatures end protected # Allows the implementation of a function to call other functions by name and pass the caller # scope. The callable functions are those visible to the same loader that loaded this function # (the calling function). # # @param scope [Puppet::Parser::Scope] The caller scope # @param function_name [String] The name of the function # @param args [Array] array of arguments # @return [Object] The result returned by the called function # # @api public def internal_call_function(scope, function_name, args, &block) the_loader = loader unless the_loader raise ArgumentError, _("Function %{class_name}(): cannot call function '%{function_name}' - no loader specified") % { class_name: self.class.name, function_name: function_name } end func = the_loader.load(:function, function_name) if func Puppet::Util::Profiler.profile(function_name, [:functions, function_name]) do return func.call(scope, *args, &block) end end # Check if a 3x function is present. Raise a generic error if it's not to allow upper layers to fill in the details # about where in a puppet manifest this error originates. (Such information is not available here). loader_scope = closure_scope func_3x = Puppet::Parser::Functions.function(function_name, loader_scope.environment) if loader_scope.is_a?(Puppet::Parser::Scope) unless func_3x raise ArgumentError, _("Function %{class_name}(): Unknown function: '%{function_name}'") % { class_name: self.class.name, function_name: function_name } end # Call via 3x API # Arguments must be mapped since functions are unaware of the new and magical creatures in 4x. # NOTE: Passing an empty string last converts nil/:undef to empty string result = scope.send(func_3x, Puppet::Pops::Evaluator::Runtime3FunctionArgumentConverter.map_args(args, loader_scope, ''), &block) # Prevent non r-value functions from leaking their result (they are not written to care about this) Puppet::Parser::Functions.rvalue?(function_name) ? result : nil end end ��������������puppet-5.5.10/lib/puppet/pops/functions/dispatch.rb�������������������������������������������������0000644�0052762�0001160�00000006522�13417161721�022217� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Functions # Defines a connection between a implementation method and the signature that # the method will handle. # # This interface should not be used directly. Instead dispatches should be # constructed using the DSL defined in {Puppet::Functions}. # # @api private class Dispatch < Evaluator::CallableSignature # @api public attr_reader :type # TODO: refactor to parameter_names since that makes it API attr_reader :param_names attr_reader :injections # Describes how arguments are woven if there are injections, a regular argument is a given arg index, an array # an injection description. # attr_reader :weaving # @api public attr_reader :block_name # @param type [Puppet::Pops::Types::PArrayType, Puppet::Pops::Types::PTupleType] - type describing signature # @param method_name [String] the name of the method that will be called when type matches given arguments # @param param_names [Array<String>] names matching the number of parameters specified by type (or empty array) # @param block_name [String,nil] name of block parameter, no nil # @param injections [Array<Array>] injection data for weaved parameters # @param weaving [Array<Integer,Array>] weaving knits # @param last_captures [Boolean] true if last parameter is captures rest # @param argument_mismatch_handler [Boolean] true if this is a dispatch for an argument mismatch # @api private def initialize(type, method_name, param_names, last_captures = false, block_name = nil, injections = EMPTY_ARRAY, weaving = EMPTY_ARRAY, argument_mismatch_handler = false) @type = type @method_name = method_name @param_names = param_names @last_captures = last_captures @block_name = block_name @injections = injections @weaving = weaving @argument_mismatch_handler = argument_mismatch_handler end # @api private def parameter_names @param_names end # @api private def last_captures_rest? @last_captures end def argument_mismatch_handler? @argument_mismatch_handler end # @api private def invoke(instance, calling_scope, args, &block) result = instance.send(@method_name, *weave(calling_scope, args), &block) return_type = @type.return_type Types::TypeAsserter.assert_instance_of(nil, return_type, result) { "value returned from function '#{@method_name}'" } unless return_type.nil? result end # @api private def weave(scope, args) # no need to weave if there are no injections if @injections.empty? args else new_args = [] @weaving.each do |knit| if knit.is_a?(Array) injection_name = @injections[knit[0]] new_args << case injection_name when :scope scope when :pal_script_compiler Puppet.lookup(:pal_script_compiler) else raise ArgumentError, _("Unknown injection %{injection_name}") % { injection_name: injection_name } end else # Careful so no new nil arguments are added since they would override default # parameter values in the received if knit < 0 idx = -knit - 1 new_args += args[idx..-1] if idx < args.size else new_args << args[knit] if knit < args.size end end end new_args end end end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/issue_reporter.rb�����������������������������������������������������0000644�0052762�0001160�00000012277�13417161721�021466� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops class IssueReporter # @param acceptor [Validation::Acceptor] the acceptor containing reported issues # @option options [String] :message (nil) A message text to use as prefix in # a single Error message # @option options [Boolean] :emit_warnings (false) whether warnings should be emitted # @option options [Boolean] :emit_errors (true) whether errors should be # emitted or only the given message # @option options [Exception] :exception_class (Puppet::ParseError) The exception to raise # def self.assert_and_report(acceptor, options) return unless acceptor max_errors = options[:max_errors] || Puppet[:max_errors] max_warnings = options[:max_warnings] || Puppet[:max_warnings] max_deprecations = options[:max_deprecations] || (Puppet[:disable_warnings].include?('deprecations') ? 0 : Puppet[:max_deprecations]) emit_warnings = options[:emit_warnings] || false emit_errors = options[:emit_errors].nil? ? true : !!options[:emit_errors] emit_message = options[:message] emit_exception = options[:exception_class] || Puppet::ParseErrorWithIssue # If there are warnings output them warnings = acceptor.warnings if emit_warnings && warnings.size > 0 formatter = Validation::DiagnosticFormatterPuppetStyle.new emitted_w = 0 emitted_dw = 0 acceptor.warnings.each do |w| if w.severity == :deprecation # Do *not* call Puppet.deprecation_warning it is for internal deprecation, not # deprecation of constructs in manifests! (It is not designed for that purpose even if # used throughout the code base). # log_message(:warning, formatter, w) if emitted_dw < max_deprecations emitted_dw += 1 else log_message(:warning, formatter, w) if emitted_w < max_warnings emitted_w += 1 end break if emitted_w >= max_warnings && emitted_dw >= max_deprecations # but only then end end # If there were errors, report the first found. Use a puppet style formatter. errors = acceptor.errors if errors.size > 0 unless emit_errors raise emit_exception.new(emit_message) end formatter = Validation::DiagnosticFormatterPuppetStyle.new if errors.size == 1 || max_errors <= 1 # raise immediately exception = create_exception(emit_exception, emit_message, formatter, errors[0]) # if an exception was given as cause, use it's backtrace instead of the one indicating "here" if errors[0].exception exception.set_backtrace(errors[0].exception.backtrace) end raise exception end emitted = 0 if emit_message Puppet.err(emit_message) end errors.each do |e| log_message(:err, formatter, e) emitted += 1 break if emitted >= max_errors end giving_up_message = if (emit_warnings && warnings.size > 0) _("Language validation logged %{error_count} errors, and %{warning_count} warnings. Giving up") % { error_count: errors.size, warning_count: warnings.size } else _("Language validation logged %{error_count} errors. Giving up") % { error_count: errors.size } end exception = emit_exception.new(giving_up_message) exception.file = errors[0].file raise exception end end def self.format_with_prefix(prefix, message) return message unless prefix [prefix, message].join(' ') end def self.warning(semantic, issue, args) Puppet::Util::Log.create({ :level => :warning, :message => issue.format(args), :arguments => args, :issue_code => issue.issue_code, :file => semantic.file, :line => semantic.line, :pos => semantic.pos, }) end def self.error(exception_class, semantic, issue, args) raise exception_class.new(issue.format(args), semantic.file, semantic.line, semantic.pos, nil, issue.issue_code, args) end def self.create_exception(exception_class, emit_message, formatter, diagnostic) file = diagnostic.file file = (file.is_a?(String) && file.empty?) ? nil : file line = pos = nil if diagnostic.source_pos line = diagnostic.source_pos.line pos = diagnostic.source_pos.pos end exception_class.new(format_with_prefix(emit_message, formatter.format_message(diagnostic)), file, line, pos, nil, diagnostic.issue.issue_code, diagnostic.arguments) end private_class_method :create_exception def self.log_message(severity, formatter, diagnostic) file = diagnostic.file file = (file.is_a?(String) && file.empty?) ? nil : file line = pos = nil if diagnostic.source_pos line = diagnostic.source_pos.line pos = diagnostic.source_pos.pos end Puppet::Util::Log.create({ :level => severity, :message => formatter.format_message(diagnostic), :arguments => diagnostic.arguments, :issue_code => diagnostic.issue.issue_code, :file => file, :line => line, :pos => pos, }) end private_class_method :log_message end end���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/label_provider.rb�����������������������������������������������������0000644�0052762�0001160�00000004357�13417161721�021405� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Provides a label for an object. # This simple implementation calls #to_s on the given object, and handles articles 'a/an/the'. # module Puppet::Pops::LabelProvider VOWELS = %w{a e i o u y} SKIPPED_CHARACTERS = %w{" '} A = "a" AN = "an" # Provides a label for the given object by calling `to_s` on the object. # The intent is for this method to be overridden in concrete label providers. def label o o.to_s end # Produces a label for the given text with indefinite article (a/an) def a_an o text = label(o) "#{article(text)} #{text}" end # Produces a label for the given text with indefinite article (A/An) def a_an_uc o text = label(o) "#{article(text).capitalize} #{text}" end # Produces a label for the given text with *definite article* (the). def the o "the #{label(o)}" end # Produces a label for the given text with *definite article* (The). def the_uc o "The #{label(o)}" end # Appends 's' to (optional) text if count != 1 else an empty string def plural_s(count, text = '') count == 1 ? text : "#{text}s" end # Combines several strings using commas and a final conjunction def combine_strings(strings, conjunction = 'or') case strings.size when 0 '' when 1 strings[0] when 2 "#{strings[0]} #{conjunction} #{strings[1]}" else "#{strings[0...-1].join(', ')}, #{conjunction} #{strings.last}" end end # Produces an *indefinite article* (a/an) for the given text ('a' if # it starts with a vowel) This is obviously flawed in the general # sense as may labels have punctuation at the start and this method # does not translate punctuation to English words. Also, if a vowel is # pronounced as a consonant, the article should not be "an". # def article s article_for_letter(first_letter_of(s)) end private def first_letter_of(string) char = string[0,1] if SKIPPED_CHARACTERS.include? char char = string[1,1] end if char == "" raise Puppet::DevError, _("<%{string}> does not appear to contain a word") % { string: string } end char end def article_for_letter(letter) downcased = letter.downcase if VOWELS.include? downcased AN else A end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/loader/���������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�017331� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/loader/base_loader.rb�������������������������������������������������0000644�0052762�0001160�00000011316�13417161721�022113� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Loader # BaseLoader # === # An abstract implementation of Loader # # A derived class should implement `find(typed_name)` and set entries, and possible handle "miss caching". # # @api private # class BaseLoader < Loader # The parent loader attr_reader :parent def initialize(parent_loader, loader_name) super(loader_name) @parent = parent_loader # the higher priority loader to consult @named_values = {} # hash name => NamedEntry @last_result = nil # the value of the last name (optimization) end def discover(type, error_collector = nil, name_authority = Pcore::RUNTIME_NAME_AUTHORITY, &block) result = [] @named_values.each_pair do |key, entry| result << key unless entry.nil? || entry.value.nil? || key.type != type || (block_given? && !yield(key)) end result.concat(parent.discover(type, error_collector, name_authority, &block)) result.uniq! result end # @api public # def load_typed(typed_name) # The check for "last queried name" is an optimization when a module searches. First it checks up its parent # chain, then itself, and then delegates to modules it depends on. # These modules are typically parented by the same # loader as the one initiating the search. It is inefficient to again try to search the same loader for # the same name. if @last_result.nil? || typed_name != @last_result.typed_name @last_result = internal_load(typed_name) else @last_result end end # @api public # def loaded_entry(typed_name, check_dependencies = false) if @named_values.has_key?(typed_name) @named_values[typed_name] elsif parent parent.loaded_entry(typed_name, check_dependencies) else nil end end # This method is final (subclasses should not override it) # # @api private # def get_entry(typed_name) @named_values[typed_name] end # @api private # def set_entry(typed_name, value, origin = nil) # It is never ok to redefine in the very same loader unless redefining a 'not found' if entry = @named_values[typed_name] fail_redefine(entry) unless entry.value.nil? end # Check if new entry shadows existing entry and fail # (unless special loader allows shadowing) if typed_name.type == :type && !allow_shadowing? entry = loaded_entry(typed_name) if entry fail_redefine(entry) unless entry.value.nil? #|| entry.value == value end end @last_result = Loader::NamedEntry.new(typed_name, value, origin) @named_values[typed_name] = @last_result end # @api private # def add_entry(type, name, value, origin) set_entry(TypedName.new(type, name), value, origin) end # @api private # def remove_entry(typed_name) unless @named_values.delete(typed_name).nil? @last_result = nil unless @last_result.nil? || typed_name != @last_result.typed_name end end # Promotes an already created entry (typically from another loader) to this loader # # @api private # def promote_entry(named_entry) typed_name = named_entry.typed_name if entry = @named_values[typed_name] then fail_redefine(entry); end @named_values[typed_name] = named_entry end protected def allow_shadowing? false end private def fail_redefine(entry) origin_info = entry.origin ? _("Originally set %{original}.") % { original: origin_label(entry.origin) } : _("Set at unknown location") raise ArgumentError, _("Attempt to redefine entity '%{name}'. %{origin_info}") % { name: entry.typed_name, origin_info: origin_info } end # TODO: Should not really be here?? - TODO: A Label provider ? semantics for the URI? # def origin_label(origin) if origin && origin.is_a?(URI) format_uri(origin) elsif origin.respond_to?(:uri) format_uri(origin.uri) else origin end end def format_uri(uri) (uri.scheme == 'puppet' ? 'by ' : 'at ') + uri.to_s.sub(/^puppet:/,'') end # loads in priority order: # 1. already loaded here # 2. load from parent # 3. find it here # 4. give up # def internal_load(typed_name) # avoid calling get_entry by looking it up te = @named_values[typed_name] return te unless te.nil? || te.value.nil? te = parent.load_typed(typed_name) return te unless te.nil? || te.value.nil? # Under some circumstances, the call to the parent loader will have resulted in files being # parsed that in turn contained references to the requested entity and hence, caused a # recursive call into this loader. This means that the entry might be present now, so a new # check must be made. te = @named_values[typed_name] te.nil? || te.value.nil? ? find(typed_name) : te end end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/loader/dependency_loader.rb�������������������������������������������0000644�0052762�0001160�00000006470�13417161721�023324� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# =DependencyLoader # This loader provides visibility into a set of other loaders. It is used as a child of a ModuleLoader (or other # loader) to make its direct dependencies visible for loading from contexts that have access to this dependency loader. # Access is typically given to logic that resides inside of the module, but not to those that just depend on the module. # # It is instantiated with a name, and with a set of dependency_loaders. # # @api private # class Puppet::Pops::Loader::DependencyLoader < Puppet::Pops::Loader::BaseLoader # An index of module_name to module loader used to speed up lookup of qualified names attr_reader :index # Creates a DependencyLoader for one parent loader # # @param parent_loader [Puppet::Pops::Loader] typically a module loader for the root # @param name [String] the name of the dependency-loader (used for debugging and tracing only) # @param dependency_loaders [Array<Puppet::Pops::Loader>] array of loaders for modules this module depends on # def initialize(parent_loader, name, dependency_loaders) super parent_loader, name @dependency_loaders = dependency_loaders end def discover(type, error_collector = nil, name_authority = Puppet::Pops::Pcore::RUNTIME_NAME_AUTHORITY, &block) result = [] @dependency_loaders.each { |loader| result.concat(loader.discover(type, error_collector, name_authority, &block)) } result.concat(super) result end # Finds name in a loader this loader depends on / can see # def find(typed_name) if typed_name.qualified? if l = index()[typed_name.name_parts[0]] l.load_typed(typed_name) else # no module entered as dependency with name matching first segment of wanted name nil end else # a non name-spaced name, have to search since it can be anywhere. # (Note: superclass caches the result in this loader as it would have to repeat this search for every # lookup otherwise). loaded = @dependency_loaders.reduce(nil) do |previous, loader| break previous if !previous.nil? loader.load_typed(typed_name) end if loaded promote_entry(loaded) end loaded end end # @api public # def loaded_entry(typed_name, check_dependencies = false) super || (check_dependencies ? loaded_entry_in_dependency(typed_name, check_dependencies) : nil) end def to_s "(DependencyLoader '#{@loader_name}' [" + @dependency_loaders.map {|loader| loader.to_s }.join(' ,') + "])" end private def loaded_entry_in_dependency(typed_name, check_dependencies) if typed_name.qualified? if l = index[typed_name.name_parts[0]] l.loaded_entry(typed_name) else # no module entered as dependency with name matching first segment of wanted name nil end else # a non name-spaced name, have to search since it can be anywhere. # (Note: superclass caches the result in this loader as it would have to repeat this search for every # lookup otherwise). @dependency_loaders.reduce(nil) do |previous, loader| break previous if !previous.nil? loader.loaded_entry(typed_name, check_dependencies) end end end def index @index ||= @dependency_loaders.reduce({}) { |index, loader| index[loader.module_name] = loader; index } end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/loader/gem_support.rb�������������������������������������������������0000644�0052762�0001160�00000004006�13417161721�022215� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# GemSupport offers methods to find a gem's location by name or gem://gemname URI. # # TODO: The Puppet 3x, uses Puppet::Util::RubyGems to do this, and obtain paths, and avoids using ::Gems # when ::Bundler is in effect. A quick check what happens on Ruby 1.8.7 and Ruby 1.9.3 with current # version of bundler seems to work just fine without jumping through any hoops. Hopefully the Puppet::Utils::RubyGems is # just dealing with arcane things prior to RubyGems 1.8 that are not needed any more. To verify there is # the need to set up a scenario where additional bundles than what Bundler allows for a given configuration are available # and then trying to access those. # module Puppet::Pops::Loader::GemSupport # Produces the root directory of a gem given as an URI (gem://gemname/optional/path), or just the # gemname as a string. # def gem_dir(uri_or_string) case uri_or_string when URI gem_dir_from_uri(uri_or_string) when String gem_dir_from_name(uri_or_string) end end # Produces the root directory of a gem given as an uri, where hostname is the gemname, and an optional # path is appended to the root of the gem (i.e. if the reference is given to a sub-location within a gem. # TODO: FIND by name raises exception Gem::LoadError with list of all gems on the path # def gem_dir_from_uri(uri) unless spec = Gem::Specification.find_by_name(uri.hostname) raise ArgumentError, _("Gem not found %{uri}") % { uri: uri } end # if path given append that, else append given subdir if uri.path.empty? spec.gem_dir else File.join(spec.full_gem_path, uri.path) end end # Produces the root directory of a gem given as a string with the gem's name. # TODO: FIND by name raises exception Gem::LoadError with list of all gems on the path # def gem_dir_from_name(gem_name) unless spec = Gem::Specification.find_by_name(gem_name) raise ArgumentError, _("Gem not found '%{gem_name}'") % { gem_name: gem_name } end spec.full_gem_path end end��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/loader/null_loader.rb�������������������������������������������������0000644�0052762�0001160�00000002201�13417161721�022144� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The null loader is empty and delegates everything to its parent if it has one. # class Puppet::Pops::Loader::NullLoader < Puppet::Pops::Loader::Loader attr_reader :loader_name # Construct a NullLoader, optionally with a parent loader # def initialize(parent_loader=nil, loader_name = "null-loader") super(loader_name) @parent = parent_loader end # Has parent if one was set when constructed def parent @parent end def find(typed_name) if @parent.nil? nil else @parent.find(typed_name) end end def load_typed(typed_name) if @parent.nil? nil else @parent.load_typed(typed_name) end end def loaded_entry(typed_name, check_dependencies = false) if @parent.nil? nil else @parent.loaded_entry(typed_name, check_dependencies) end end # Has no entries on its own - always nil def get_entry(typed_name) nil end # Finds nothing, there are no entries def find(name) nil end # Cannot store anything def set_entry(typed_name, value, origin = nil) nil end def to_s() "(NullLoader '#{loader_name}')" end end�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/loader/predefined_loader.rb�������������������������������������������0000644�0052762�0001160�00000001216�13417161721�023304� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops::Loader # A PredefinedLoader is a loader that is manually populated with loaded elements # before being used. It never loads anything on its own. # class PredefinedLoader < BaseLoader def find(typed_name) nil end def to_s() "(PredefinedLoader '#{loader_name}')" end # Allows shadowing since this loader is used internally for things like function local types # And they should win as there is otherwise a risk that the local types clash with built in types # that were added after the function was written, or by resource types loaded by the 3x auto loader. # def allow_shadowing? true end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/loader/puppet_function_instantiator.rb��������������������������������0000644�0052762�0001160�00000010421�13417161721�025670� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Loader # The PuppetFunctionInstantiator instantiates a Puppet::Functions::PuppetFunction given a Puppet Programming language # source that when called evaluates the Puppet logic it contains. # class PuppetFunctionInstantiator # Produces an instance of the Function class with the given typed_name, or fails with an error if the # given puppet source does not produce this instance when evaluated. # # @param loader [Loader] The loader the function is associated with # @param typed_name [TypedName] the type / name of the function to load # @param source_ref [URI, String] a reference to the source / origin of the puppet code to evaluate # @param pp_code_string [String] puppet code in a string # # @return [Functions::Function] - an instantiated function with global scope closure associated with the given loader # def self.create(loader, typed_name, source_ref, pp_code_string) parser = Parser::EvaluatingParser.new() # parse and validate result = parser.parse_string(pp_code_string, source_ref) # Only one function is allowed (and no other definitions) case result.definitions.size when 0 raise ArgumentError, _("The code loaded from %{source_ref} does not define the function '%{func_name}' - it is empty.") % { source_ref: source_ref, func_name: typed_name.name } when 1 # ok else raise ArgumentError, _("The code loaded from %{source_ref} must contain only the function '%{type_name}' - it has additional definitions.") % { source_ref: source_ref, type_name: typed_name.name } end the_function_definition = result.definitions[0] unless the_function_definition.is_a?(Model::FunctionDefinition) raise ArgumentError, _("The code loaded from %{source_ref} does not define the function '%{type_name}' - no function found.") % { source_ref: source_ref, type_name: typed_name.name } end unless the_function_definition.name == typed_name.name expected = typed_name.name actual = the_function_definition.name raise ArgumentError, _("The code loaded from %{source_ref} produced function with the wrong name, expected %{expected}, actual %{actual}") % { source_ref: source_ref, expected: expected, actual: actual } end unless result.body == the_function_definition raise ArgumentError, _("The code loaded from %{source} contains additional logic - can only contain the function %{name}") % { source: source_ref, name: typed_name.name } end # Adapt the function definition with loader - this is used from logic contained in it body to find the # loader to use when making calls to the new function API. Such logic have a hard time finding the closure (where # the loader is known - hence this mechanism private_loader = loader.private_loader Adapters::LoaderAdapter.adapt(the_function_definition).loader_name = private_loader.loader_name # Cannot bind loaded functions to global scope, that must be done without binding that scope as # loaders survive a compilation. closure_scope = nil # Puppet.lookup(:global_scope) { {} } created = create_function_class(the_function_definition) # create the function instance - it needs closure (scope), and loader (i.e. where it should start searching for things # when calling functions etc. # It should be bound to global scope created.new(closure_scope, private_loader) end # Creates Function class and instantiates it based on a FunctionDefinition model # @return [Array<TypedName, Functions.Function>] - array of # typed name, and an instantiated function with global scope closure associated with the given loader # def self.create_from_model(function_definition, loader) created = create_function_class(function_definition) typed_name = TypedName.new(:function, function_definition.name) [typed_name, created.new(nil, loader)] end def self.create_function_class(function_definition) # Create a 4x function wrapper around a named closure Puppet::Functions.create_function(function_definition.name, Puppet::Functions::PuppetFunction) do # TODO: should not create a new evaluator per function init_dispatch(Evaluator::Closure::Named.new( function_definition.name, Evaluator::EvaluatorImpl.new(), function_definition)) end end end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/loader/puppet_plan_instantiator.rb������������������������������������0000644�0052762�0001160�00000010225�13417161721�024777� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Loader # The PuppetPlanInstantiator instantiates a Puppet::Functions::PuppetFunction given a Puppet Programming language # source that when called evaluates the Puppet logic it contains. # class PuppetPlanInstantiator # Produces an instance of the Function class with the given typed_name, or fails with an error if the # given puppet source does not produce this instance when evaluated. # # @param loader [Loader] The loader the function is associated with # @param typed_name [TypedName] the type / name of the function to load # @param source_ref [URI, String] a reference to the source / origin of the puppet code to evaluate # @param pp_code_string [String] puppet code in a string # # @return [Functions::Function] - an instantiated function with global scope closure associated with the given loader # def self.create(loader, typed_name, source_ref, pp_code_string) parser = Parser::EvaluatingParser.new() # parse and validate result = parser.parse_string(pp_code_string, source_ref) # Only one function is allowed (and no other definitions) case result.definitions.size when 0 raise ArgumentError, _("The code loaded from %{source_ref} does not define the plan '%{plan_name}' - it is empty.") % { source_ref: source_ref, plan_name: typed_name.name } when 1 # ok else raise ArgumentError, _("The code loaded from %{source_ref} must contain only the plan '%{plan_name}' - it has additional definitions.") % { source_ref: source_ref, plan_name: typed_name.name } end the_plan_definition = result.definitions[0] unless the_plan_definition.is_a?(Model::PlanDefinition) raise ArgumentError, _("The code loaded from %{source_ref} does not define the plan '%{plan_name}' - no plan found.") % { source_ref: source_ref, plan_name: typed_name.name } end unless the_plan_definition.name == typed_name.name expected = typed_name.name actual = the_plan_definition.name raise ArgumentError, _("The code loaded from %{source_ref} produced plan with the wrong name, expected %{expected}, actual %{actual}") % { source_ref: source_ref, expected: expected, actual: actual } end unless result.body == the_plan_definition raise ArgumentError, _("The code loaded from %{source} contains additional logic - can only contain the plan %{plan_name}") % { source: source_ref, plan_name: typed_name.name } end # Adapt the function definition with loader - this is used from logic contained in it body to find the # loader to use when making calls to the new function API. Such logic have a hard time finding the closure (where # the loader is known - hence this mechanism private_loader = loader.private_loader Adapters::LoaderAdapter.adapt(the_plan_definition).loader_name = private_loader.loader_name # Cannot bind loaded functions to global scope, that must be done without binding that scope as # loaders survive a compilation. closure_scope = nil created = create_function_class(the_plan_definition) # create the function instance - it needs closure (scope), and loader (i.e. where it should start searching for things # when calling functions etc. # It should be bound to global scope created.new(closure_scope, private_loader) end # Creates Function class and instantiates it based on a FunctionDefinition model # @return [Array<TypedName, Functions.Function>] - array of # typed name, and an instantiated function with global scope closure associated with the given loader # def self.create_from_model(plan_definition, loader) created = create_function_class(plan_definition) typed_name = TypedName.new(:plan, plan_definition.name) [typed_name, created.new(nil, loader)] end def self.create_function_class(plan_definition) # Create a 4x function wrapper around a named closure Puppet::Functions.create_function(plan_definition.name, Puppet::Functions::PuppetFunction) do # TODO: should not create a new evaluator per function init_dispatch(Evaluator::Closure::Named.new( plan_definition.name, Evaluator::EvaluatorImpl.new(), plan_definition)) end end end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/loader/puppet_resource_type_impl_instantiator.rb����������������������0000644�0052762�0001160�00000007373�13417161721�027770� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Loader # The PuppetResourceTypeImplInstantiator instantiates a Puppet::Pops::ResourceTypeImpl object. # given a Puppet Programming language source that when called evaluates the Puppet logic it contains. # class PuppetResourceTypeImplInstantiator # Produces an instance of Puppet::Pops::ResourceTypeImpl, or fails with an error if the # given puppet source does not produce such an instance when evaluated. # # @param loader [Loader] The loader the function is associated with # @param typed_name [TypedName] the type / name of the resource type impl to load # @param source_ref [URI, String] a reference to the source / origin of the puppet code to evaluate # @param pp_code_string [String] puppet code in a string # # @return [Puppet::Pops::ResourceTypeImpl] - an instantiated ResourceTypeImpl # def self.create(loader, typed_name, source_ref, pp_code_string) parser = Parser::EvaluatingParser.new() # parse and validate model = parser.parse_string(pp_code_string, source_ref) statements = if model.is_a?(Model::Program) if model.body.is_a?(Model::BlockExpression) model.body.statements else [model.body] end else EMPTY_ARRAY end statements = statements.reject { |s| s.is_a?(Model::Nop) } if statements.empty? raise ArgumentError, _("The code loaded from %{source_ref} does not create the resource type '%{type_name}' - it is empty") % { source_ref: source_ref, type_name: typed_name.name } end rname = Resource::ResourceTypeImpl._pcore_type.name unless statements.find do |s| if s.is_a?(Model::CallMethodExpression) functor_expr = s.functor_expr functor_expr.is_a?(Model::NamedAccessExpression) && functor_expr.left_expr.is_a?(Model::QualifiedReference) && functor_expr.left_expr.cased_value == rname && functor_expr.right_expr.is_a?(Model::QualifiedName) && functor_expr.right_expr.value == 'new' else false end end raise ArgumentError, _("The code loaded from %{source_ref} does not create the resource type '%{type_name}' - no call to %{rname}.new found.") % { source_ref: source_ref, type_name: typed_name.name, rname: rname } end unless statements.size == 1 raise ArgumentError, _("The code loaded from %{source_ref} must contain only the creation of resource type '%{type_name}' - it has additional logic.") % { source_ref: source_ref, type_name: typed_name.name } end closure_scope = Puppet.lookup(:global_scope) { {} } resource_type_impl = parser.evaluate(closure_scope, model) unless resource_type_impl.is_a?(Puppet::Pops::Resource::ResourceTypeImpl) got = resource_type.class raise ArgumentError, _("The code loaded from %{source_ref} does not define the resource type '%{type_name}' - got '%{got}'.") % { source_ref: source_ref, type_name: typed_name.name, got: got } end unless resource_type_impl.name == typed_name.name expected = typed_name.name actual = resource_type_impl.name raise ArgumentError, _("The code loaded from %{source_ref} produced resource type with the wrong name, expected '%{expected}', actual '%{actual}'") % { source_ref: source_ref, expected: expected, actual: actual } end # Adapt the resource type definition with loader - this is used from logic contained in it body to find the # loader to use when making calls to the new function API. Such logic have a hard time finding the closure (where # the loader is known - hence this mechanism Adapters::LoaderAdapter.adapt(resource_type_impl).loader_name = loader.loader_name resource_type_impl end end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/loader/ruby_data_type_instantiator.rb���������������������������������0000644�0052762�0001160�00000004316�13417161721�025467� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The RubyTypeInstantiator instantiates a data type from the ruby source # that calls Puppet::DataTypes.create_type. # class Puppet::Pops::Loader::RubyDataTypeInstantiator # Produces an instance of class derived from PAnyType class with the given typed_name, or fails with an error if the # given ruby source does not produce this instance when evaluated. # # @param loader [Puppet::Pops::Loader::Loader] The loader the type is associated with # @param typed_name [Puppet::Pops::Loader::TypedName] the type / name of the type to load # @param source_ref [URI, String] a reference to the source / origin of the ruby code to evaluate # @param ruby_code_string [String] ruby code in a string # # @return [Puppet::Pops::Types::PAnyType] - an instantiated data type associated with the given loader # def self.create(loader, typed_name, source_ref, ruby_code_string) unless ruby_code_string.is_a?(String) && ruby_code_string =~ /Puppet\:\:DataTypes\.create_type/ raise ArgumentError, _("The code loaded from %{source_ref} does not seem to be a Puppet 5x API data type - no create_type call.") % { source_ref: source_ref } end # make the private loader available in a binding to allow it to be passed on loader_for_type = loader.private_loader here = get_binding(loader_for_type) created = eval(ruby_code_string, here, source_ref, 1) unless created.is_a?(Puppet::Pops::Types::PAnyType) raise ArgumentError, _("The code loaded from %{source_ref} did not produce a data type when evaluated. Got '%{klass}'") % { source_ref: source_ref, klass: created.class } end unless created.name.casecmp(typed_name.name) == 0 raise ArgumentError, _("The code loaded from %{source_ref} produced mis-matched name, expected '%{type_name}', got %{created_name}") % { source_ref: source_ref, type_name: typed_name.name, created_name: created.name } end created end # Produces a binding where the given loader is bound as a local variable (loader_injected_arg). This variable can be used in loaded # ruby code - e.g. to call Puppet::Function.create_loaded_function(:name, loader,...) # def self.get_binding(loader_injected_arg) binding end private_class_method :get_binding end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/loader/ruby_function_instantiator.rb����������������������������������0000644�0052762�0001160�00000005247�13417161721�025346� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The RubyFunctionInstantiator instantiates a Puppet::Functions::Function given the ruby source # that calls Puppet::Functions.create_function. # class Puppet::Pops::Loader::RubyFunctionInstantiator # Produces an instance of the Function class with the given typed_name, or fails with an error if the # given ruby source does not produce this instance when evaluated. # # @param loader [Puppet::Pops::Loader::Loader] The loader the function is associated with # @param typed_name [Puppet::Pops::Loader::TypedName] the type / name of the function to load # @param source_ref [URI, String] a reference to the source / origin of the ruby code to evaluate # @param ruby_code_string [String] ruby code in a string # # @return [Puppet::Pops::Functions.Function] - an instantiated function with global scope closure associated with the given loader # def self.create(loader, typed_name, source_ref, ruby_code_string) unless ruby_code_string.is_a?(String) && ruby_code_string =~ /Puppet\:\:Functions\.create_function/ raise ArgumentError, _("The code loaded from %{source_ref} does not seem to be a Puppet 4x API function - no create_function call.") % { source_ref: source_ref } end # make the private loader available in a binding to allow it to be passed on loader_for_function = loader.private_loader here = get_binding(loader_for_function) created = eval(ruby_code_string, here, source_ref, 1) unless created.is_a?(Class) raise ArgumentError, _("The code loaded from %{source_ref} did not produce a Function class when evaluated. Got '%{klass}'") % { source_ref: source_ref, klass: created.class } end unless created.name.to_s == typed_name.name() raise ArgumentError, _("The code loaded from %{source_ref} produced mis-matched name, expected '%{type_name}', got %{created_name}") % { source_ref: source_ref, type_name: typed_name.name, created_name: created.name } end # create the function instance - it needs closure (scope), and loader (i.e. where it should start searching for things # when calling functions etc. # It should be bound to global scope # Sets closure scope to nil, to let it be picked up at runtime from Puppet.lookup(:global_scope) # If function definition used the loader from the binding to create a new loader, that loader wins created.new(nil, loader_for_function) end # Produces a binding where the given loader is bound as a local variable (loader_injected_arg). This variable can be used in loaded # ruby code - e.g. to call Puppet::Function.create_loaded_function(:name, loader,...) # def self.get_binding(loader_injected_arg) binding end private_class_method :get_binding end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/loader/runtime3_type_loader.rb����������������������������������������0000644�0052762�0001160�00000006676�13417161721�024025� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Loader # Runtime3TypeLoader # === # Loads a resource type using the 3.x type loader # # @api private class Runtime3TypeLoader < BaseLoader def initialize(parent_loader, loaders, environment, env_path) super(parent_loader, environment.name) @environment = environment @resource_3x_loader = env_path.nil? ? nil : ModuleLoaders.pcore_resource_type_loader_from(parent_loader, loaders, env_path) end def discover(type, error_collector = nil, name_authority = Pcore::RUNTIME_NAME_AUTHORITY, &block) # TODO: Use generated index of all known types (requires separate utility). parent.discover(type, error_collector, name_authority, &block) end def to_s() "(Runtime3TypeLoader '#{loader_name()}')" end # Finds typed/named entity in this module # @param typed_name [TypedName] the type/name to find # @return [Loader::NamedEntry, nil found/created entry, or nil if not found # def find(typed_name) return nil unless typed_name.name_authority == Pcore::RUNTIME_NAME_AUTHORITY case typed_name.type when :type value = nil name = typed_name.name if @resource_3x_loader.nil? value = Puppet::Type.type(name) unless typed_name.qualified? if value.nil? # Look for a user defined type value = @environment.known_resource_types.find_definition(name) end else impl_te = find_impl(TypedName.new(:resource_type_pp, name, typed_name.name_authority)) value = impl_te.value unless impl_te.nil? end if value.nil? # Cache the fact that it wasn't found set_entry(typed_name, nil) return nil end # Loaded types doesn't have the same life cycle as this loader, so we must start by # checking if the type was created. If it was, an entry will already be stored in # this loader. If not, then it was created before this loader was instantiated and # we must therefore add it. te = get_entry(typed_name) te = set_entry(typed_name, Types::TypeFactory.resource(value.name.to_s)) if te.nil? || te.value.nil? te when :resource_type_pp @resource_3x_loader.nil? ? nil : find_impl(typed_name) else nil end end # Find the implementation for the resource type by first consulting the internal loader for pp defined 'Puppet::Resource::ResourceType3' # instances, then check for a Puppet::Type and lastly check for a defined type. # def find_impl(typed_name) name = typed_name.name te = StaticLoader::BUILTIN_TYPE_NAMES_LC.include?(name) ? nil : @resource_3x_loader.load_typed(typed_name) if te.nil? || te.value.nil? # Look for Puppet::Type value = Puppet::Type.type(name) unless typed_name.qualified? if value.nil? # Look for a user defined type value = @environment.known_resource_types.find_definition(name) if value.nil? # Cache the fact that it wasn't found @resource_3x_loader.set_entry(typed_name, nil) return nil end end te = @resource_3x_loader.get_entry(typed_name) te = @resource_3x_loader.set_entry(typed_name, value) if te.nil? || te.value.nil? end te end private :find_impl # Allows shadowing since this loader is populated with all loaded resource types at time # of loading. This loading will, for built in types override the aliases configured in the static # loader. # def allow_shadowing? true end end end end ������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/loader/simple_environment_loader.rb�����������������������������������0000644�0052762�0001160�00000001121�13417161721�025107� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SimpleEnvironmentLoader # === # This loader does not load anything and it is populated by the bootstrapping logic that loads # the site.pp or equivalent for an environment. It does not restrict the names of what it may contain, # and what is loaded here overrides any child loaders (modules). # class Puppet::Pops::Loader::SimpleEnvironmentLoader < Puppet::Pops::Loader::BaseLoader attr_accessor :private_loader # Never finds anything, everything "loaded" is set externally def find(typed_name) nil end def to_s() "(SimpleEnvironmentLoader '#{loader_name}')" end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/loader/type_definition_instantiator.rb��������������������������������0000644�0052762�0001160�00000007656�13417161721�025657� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The TypeDefinitionInstantiator instantiates a type alias or a type definition # module Puppet::Pops module Loader class TypeDefinitionInstantiator def self.create(loader, typed_name, source_ref, pp_code_string) # parse and validate parser = Parser::EvaluatingParser.new() model = parser.parse_string(pp_code_string, source_ref) # Only one type is allowed (and no other definitions) name = typed_name.name case model.definitions.size when 0 raise ArgumentError, _("The code loaded from %{source_ref} does not define the type '%{name}' - it is empty.") % { source_ref: source_ref, name: name } when 1 # ok else raise ArgumentError, _("The code loaded from %{source_ref} must contain only the type '%{name}' - it has additional definitions.") % { source_ref: source_ref, name: name } end type_definition = model.definitions[0] unless type_definition.is_a?(Model::TypeAlias) || type_definition.is_a?(Model::TypeDefinition) raise ArgumentError, _("The code loaded from %{source_ref} does not define the type '%{name}' - no type alias or type definition found.") % { source_ref: source_ref, name: name } end actual_name = type_definition.name unless name == actual_name.downcase raise ArgumentError, _("The code loaded from %{source_ref} produced type with the wrong name, expected '%{name}', actual '%{actual_name}'") % { source_ref: source_ref, name: name, actual_name: actual_name } end unless model.body == type_definition raise ArgumentError, _("The code loaded from %{source_ref} contains additional logic - can only contain the type '%{name}'") % { source_ref: source_ref, name: name } end # Adapt the type definition with loader - this is used from logic contained in its body to find the # loader to use when resolving contained aliases API. Such logic have a hard time finding the closure (where # the loader is known - hence this mechanism private_loader = loader.private_loader Adapters::LoaderAdapter.adapt(type_definition).loader_name = private_loader.loader_name create_runtime_type(type_definition) end def self.create_from_model(type_definition, loader) typed_name = TypedName.new(:type, type_definition.name) type = create_runtime_type(type_definition) loader.set_entry( typed_name, type, type_definition.locator.to_uri(type_definition)) type end # @api private def self.create_runtime_type(type_definition) # Using the RUNTIME_NAME_AUTHORITY as the name_authority is motivated by the fact that the type # alias name (managed by the runtime) becomes the name of the created type # create_type(type_definition.name, type_definition.type_expr, Pcore::RUNTIME_NAME_AUTHORITY) end # @api private def self.create_type(name, type_expr, name_authority) create_named_type(name, named_definition(type_expr), type_expr, name_authority) end # @api private def self.create_named_type(name, type_name, type_expr, name_authority) case type_name when 'Object' # No need for an alias. The Object type itself will receive the name instead unless type_expr.is_a?(Model::LiteralHash) type_expr = type_expr.keys.empty? ? nil : type_expr.keys[0] unless type_expr.is_a?(Hash) end Types::PObjectType.new(name, type_expr) when 'TypeSet' # No need for an alias. The Object type itself will receive the name instead type_expr = type_expr.keys.empty? ? nil : type_expr.keys[0] unless type_expr.is_a?(Hash) Types::PTypeSetType.new(name, type_expr, name_authority) else Types::PTypeAliasType.new(name, type_expr) end end # @api private def self.named_definition(te) return 'Object' if te.is_a?(Model::LiteralHash) te.is_a?(Model::AccessExpression) && (left = te.left_expr).is_a?(Model::QualifiedReference) ? left.cased_value : nil end def several_paths? false end end end end ����������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/loader/typed_name.rb��������������������������������������������������0000644�0052762�0001160�00000002500�13417161721�021773� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Loader # A namespace/name/type combination that can be used as a compound hash key # # @api public class TypedName attr_reader :hash attr_reader :type attr_reader :name_authority attr_reader :name attr_reader :name_parts attr_reader :compound_name def initialize(type, name, name_authority = Pcore::RUNTIME_NAME_AUTHORITY) name = name.downcase @type = type @name_authority = name_authority # relativize the name (get rid of leading ::), and make the split string available parts = name.to_s.split(DOUBLE_COLON) if parts[0].empty? parts.shift @name = name[2..-1] else @name = name end @name_parts = parts.freeze # Use a frozen compound key for the hash and comparison. Most varying part first @compound_name = "#{@name}/#{@type}/#{@name_authority}".freeze @hash = @compound_name.hash freeze end def ==(o) o.class == self.class && o.compound_name == @compound_name end alias eql? == # @return the parent of this instance, or nil if this instance is not qualified def parent @name_parts.size > 1 ? self.class.new(@type, @name_parts[0...-1].join(DOUBLE_COLON), @name_authority) : nil end def qualified? @name_parts.size > 1 end def to_s "#{@name_authority}/#{@type}/#{@name}" end end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/loader/uri_helper.rb��������������������������������������������������0000644�0052762�0001160�00000001341�13417161721�022006� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops::Loader::UriHelper # Raises an exception if specified gem can not be located # def path_for_uri(uri, subdir='lib') case uri.scheme when "gem" begin spec = Gem::Specification.find_by_name(uri.hostname) # if path given append that, else append given subdir File.join(spec.gem_dir, uri.path.empty?() ? subdir : uri.path) rescue StandardError => e raise "TODO TYPE: Failed to located gem #{uri}. #{e.message}" end when "file" File.join(uri.path, subdir) when nil File.join(uri.path, subdir) else raise "Not a valid scheme for a loader: #{uri.scheme}. Use a 'file:' (or just a path), or 'gem://gemname[/path]" end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/loader/loader.rb������������������������������������������������������0000644�0052762�0001160�00000017564�13417161721�021134� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Loader # === # A Loader is responsible for loading "entities" ("instantiable and executable objects in the puppet language" which # are type, hostclass, definition, function, and bindings. # # The main method for users of a Loader is the `load` or `load_typed methods`, which returns a previously loaded entity # of a given type/name, and searches and loads the entity if not already loaded. # # private entities # --- # TODO: handle loading of entities that are private. Suggest that all calls pass an origin_loader (the loader # where request originated (or symbol :public). A module loader has one (or possibly a list) of what is # considered to represent private loader - i.e. the dependency loader for a module. If an entity is private # it should be stored with this status, and an error should be raised if the origin_loader is not on the list # of accepted "private" loaders. # The private loaders can not be given at creation time (they are parented by the loader in question). Another # alternative is to check if the origin_loader is a child loader, but this requires bidirectional links # between loaders or a search if loader with private entity is a parent of the origin_loader). # # @api public # module Puppet::Pops module Loader ENVIRONMENT = 'environment'.freeze ENVIRONMENT_PRIVATE = 'environment private'.freeze class Loader attr_reader :loader_name # Describes the kinds of things that loaders can load LOADABLE_KINDS = [:func_4x, :func_4xpp, :datatype, :type_pp, :resource_type_pp, :plan, :task].freeze # @param [String] name the name of the loader. Must be unique among all loaders maintained by a {Loader} instance def initialize(loader_name) @loader_name = loader_name.freeze end # Search all places where this loader would find values of a given type and return a list the # found values for which the given block returns true. All found entries will be returned if no # block is given. # # Errors that occur function discovery will either be logged as warnings or collected by the optional # `error_collector` array. When provided, it will receive {Puppet::DataTypes::Error} instances describing # each error in detail and no warnings will be logged. # # @param type [Symbol] the type of values to search for # @param error_collector [Array<Puppet::DataTypes::Error>] an optional array that will receive errors during load # @param name_authority [String] the name authority, defaults to the pcore runtime # @yield [typed_name] optional block to filter the results # @yieldparam [TypedName] typed_name the typed name of a found entry # @yieldreturn [Boolean] `true` to keep the entry, `false` to discard it. # @return [Array<TypedName>] the list of names of discovered values def discover(type, error_collector = nil, name_authority = Pcore::RUNTIME_NAME_AUTHORITY, &block) return EMPTY_ARRAY end # Produces the value associated with the given name if already loaded, or available for loading # by this loader, one of its parents, or other loaders visible to this loader. # This is the method an external party should use to "get" the named element. # # An implementor of this method should first check if the given name is already loaded by self, or a parent # loader, and if so return that result. If not, it should call `find` to perform the loading. # # @param type [:Symbol] the type to load # @param name [String, Symbol] the name of the entity to load # @return [Object, nil] the value or nil if not found # # @api public # def load(type, name) if result = load_typed(TypedName.new(type, name.to_s)) result.value end end # Loads the given typed name, and returns a NamedEntry if found, else returns nil. # This the same a `load`, but returns a NamedEntry with origin/value information. # # @param typed_name [TypedName] - the type, name combination to lookup # @return [NamedEntry, nil] the entry containing the loaded value, or nil if not found # # @api public # def load_typed(typed_name) raise NotImplementedError, "Class #{self.class.name} must implement method #load_typed" end # Returns an already loaded entry if one exists, or nil. This does not trigger loading # of the given type/name. # # @param typed_name [TypedName] - the type, name combination to lookup # @param check_dependencies [Boolean] - if dependencies should be checked in addition to here and parent # @return [NamedEntry, nil] the entry containing the loaded value, or nil if not found # @api public # def loaded_entry(typed_name, check_dependencies = false) raise NotImplementedError, "Class #{self.class.name} must implement method #loaded_entry" end # Produces the value associated with the given name if defined **in this loader**, or nil if not defined. # This lookup does not trigger any loading, or search of the given name. # An implementor of this method may not search or look up in any other loader, and it may not # define the name. # # @param typed_name [TypedName] - the type, name combination to lookup # # @api private # def [](typed_name) if found = get_entry(typed_name) found.value else nil end end # Searches for the given name in this loader's context (parents should already have searched their context(s) without # producing a result when this method is called). # An implementation of find typically caches the result. # # @param typed_name [TypedName] the type, name combination to lookup # @return [NamedEntry, nil] the entry for the loaded entry, or nil if not found # # @api private # def find(typed_name) raise NotImplementedError, "Class #{self.class.name} must implement method #find" end # Returns the parent of the loader, or nil, if this is the top most loader. This implementation returns nil. def parent nil end # Produces the private loader for loaders that have a one (the visibility given to loaded entities). # For loaders that does not provide a private loader, self is returned. # # @api private def private_loader self end # Binds a value to a name. The name should not start with '::', but may contain multiple segments. # # @param type [:Symbol] the type of the entity being set # @param name [String, Symbol] the name of the entity being set # @param origin [URI, #uri, String] the origin of the set entity, a URI, or provider of URI, or URI in string form # @return [NamedEntry, nil] the created entry # # @api private # def set_entry(type, name, value, origin = nil) raise NotImplementedError.new end # Produces a NamedEntry if a value is bound to the given name, or nil if nothing is bound. # # @param typed_name [TypedName] the type, name combination to lookup # @return [NamedEntry, nil] the value bound in an entry # # @api private # def get_entry(typed_name) raise NotImplementedError.new end # A loader is by default a loader for all kinds of loadables. An implementation may override # if it cannot load all kinds. # # @api private def loadables LOADABLE_KINDS end # A loader may want to implement its own version with more detailed information. def to_s loader_name end # Loaders may contain references to the environment they load items within. # Consequently, calling Kernel#inspect may return strings that are large # enough to cause OutOfMemoryErrors on some platforms. # # We do not call alias_method here as that would copy the content of to_s # at this point to inspect (ie children would print out `loader_name` # rather than their version of to_s if they chose to implement it). def inspect self.to_s end # An entry for one entity loaded by the loader. # class NamedEntry attr_reader :typed_name attr_reader :value attr_reader :origin def initialize(typed_name, value, origin) @typed_name = typed_name @value = value @origin = origin freeze() end end end end end ��������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/loader/loader_paths.rb������������������������������������������������0000644�0052762�0001160�00000024445�13417161721�022327� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� # LoaderPaths # === # The central loader knowledge about paths, what they represent and how to instantiate from them. # Contains helpers (*smart paths*) to deal with lazy resolution of paths. # # TODO: Currently only supports loading of functions (2 kinds) # module Puppet::Pops module Loader module LoaderPaths # Returns an array of SmartPath, each instantiated with a reference to the given loader (for root path resolution # and existence checks). The smart paths in the array appear in precedence order. The returned array may be # mutated. # def self.relative_paths_for_type(type, loader) result = [] case type when :function # Only include support for the loadable items the loader states it can contain if loader.loadables.include?(:func_4x) result << FunctionPath4x.new(loader) end if loader.loadables.include?(:func_4xpp) result << FunctionPathPP.new(loader) end # When wanted also add FunctionPath3x to load 3x functions when :plan result << PlanPathPP.new(loader) when :task result << TaskPath.new(loader) if Puppet[:tasks] && loader.loadables.include?(:task) when :type result << DataTypePath.new(loader) if loader.loadables.include?(:datatype) result << TypePathPP.new(loader) if loader.loadables.include?(:type_pp) when :resource_type_pp result << ResourceTypeImplPP.new(loader) if loader.loadables.include?(:resource_type_pp) else # unknown types, simply produce an empty result; no paths to check, nothing to find... move along... [] end result end # # DO NOT REMOVE YET. needed later? when there is the need to decamel a classname # def de_camel(fq_name) # fq_name.to_s.gsub(/::/, '/'). # gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). # gsub(/([a-z\d])([A-Z])/,'\1_\2'). # tr("-", "_"). # downcase # end class SmartPath # Generic path, in the sense of "if there are any entities of this kind to load, where are they?" attr_reader :generic_path # Creates SmartPath for the given loader (loader knows how to check for existence etc.) def initialize(loader) @loader = loader end def generic_path return @generic_path unless @generic_path.nil? the_root_path = root_path # @loader.path @generic_path = (the_root_path.nil? ? relative_path : File.join(the_root_path, relative_path)) end def match_many? false end def root_path @loader.path end def lib_root? @loader.lib_root? end # Effective path is the generic path + the name part(s) + extension. # def effective_path(typed_name, start_index_in_name) "#{File.join(generic_path, typed_name.name_parts)}#{extension}" end def typed_name(type, name_authority, relative_path, module_name) # Module name is assumed to be included in the path and therefore not added here n = '' unless extension.empty? # Remove extension relative_path = relative_path[0..-(extension.length+1)] end relative_path.split('/').each do |segment| n << '::' if n.size > 0 n << segment end TypedName.new(type, n, name_authority) end def valid_path?(path) path.end_with?(extension) && path.start_with?(generic_path) end def valid_name?(typed_name) true end def relative_path raise NotImplementedError.new end def instantiator raise NotImplementedError.new end end class RubySmartPath < SmartPath EXTENSION = '.rb'.freeze def extension EXTENSION end # Duplication of extension information, but avoids one call def effective_path(typed_name, start_index_in_name) "#{File.join(generic_path, typed_name.name_parts)}.rb" end end # A PuppetSmartPath is rooted at the loader's directory one level up from what the loader specifies as it # path (which is a reference to its 'lib' directory. # class PuppetSmartPath < SmartPath EXTENSION = '.pp'.freeze def extension EXTENSION end # Duplication of extension information, but avoids one call def effective_path(typed_name, start_index_in_name) # Puppet name to path always skips the name-space as that is part of the generic path # i.e. <module>/mymodule/functions/foo.pp is the function mymodule::foo parts = typed_name.name_parts if start_index_in_name > 0 return nil if start_index_in_name >= parts.size parts = parts[start_index_in_name..-1] end "#{File.join(generic_path, parts)}#{extension}" end def typed_name(type, name_authority, relative_path, module_name) n = '' n << module_name unless module_name.nil? unless extension.empty? # Remove extension relative_path = relative_path[0..-(extension.length+1)] end relative_path.split('/').each do |segment| n << '::' if n.size > 0 n << segment end TypedName.new(type, n, name_authority) end end class FunctionPath4x < RubySmartPath SYSTEM_FUNCTION_PATH_4X = File.join('puppet', 'functions').freeze FUNCTION_PATH_4X = File.join('lib', SYSTEM_FUNCTION_PATH_4X).freeze def relative_path lib_root? ? SYSTEM_FUNCTION_PATH_4X : FUNCTION_PATH_4X end def instantiator RubyFunctionInstantiator end end class FunctionPath3x < RubySmartPath SYSTEM_FUNCTION_PATH_3X = File.join('puppet', 'parser', 'functions').freeze FUNCTION_PATH_3X = File.join('lib', SYSTEM_FUNCTION_PATH_3X).freeze def relative_path lib_root? ? SYSTEM_FUNCTION_PATH_3X : FUNCTION_PATH_3X end def instantiator RubyLegacyFunctionInstantiator end end class FunctionPathPP < PuppetSmartPath FUNCTION_PATH_PP = 'functions'.freeze def relative_path FUNCTION_PATH_PP end def instantiator PuppetFunctionInstantiator end end class DataTypePath < RubySmartPath SYSTEM_TYPE_PATH = File.join('puppet', 'datatypes').freeze TYPE_PATH = File.join('lib', SYSTEM_TYPE_PATH).freeze def relative_path lib_root? ? SYSTEM_TYPE_PATH : TYPE_PATH end def instantiator RubyDataTypeInstantiator end end class TypePathPP < PuppetSmartPath TYPE_PATH_PP = 'types'.freeze def relative_path TYPE_PATH_PP end def instantiator TypeDefinitionInstantiator end end # TaskPath is like PuppetSmartPath but it does not use an extension and may # match more than one path with one name class TaskPath < PuppetSmartPath TASKS_PATH = 'tasks'.freeze FORBIDDEN_EXTENSIONS = %w{.conf .md}.freeze def extension EMPTY_STRING end def match_many? true end def relative_path TASKS_PATH end def typed_name(type, name_authority, relative_path, module_name) n = '' n << module_name unless module_name.nil? # Remove the file extension, defined as everything after the *last* dot. relative_path = relative_path.sub(%r{\.[^/.]*\z}, '') if relative_path == 'init' && !(module_name.nil? || module_name.empty?) TypedName.new(type, module_name, name_authority) else relative_path.split('/').each do |segment| n << '::' if n.size > 0 n << segment end TypedName.new(type, n, name_authority) end end def instantiator require_relative 'task_instantiator' TaskInstantiator end def valid_name?(typed_name) # TODO: Remove when PE has proper namespace handling typed_name.name_parts.size <= 2 end def valid_path?(path) path.start_with?(generic_path) && is_task_name?(File.basename(path, '.*')) && !FORBIDDEN_EXTENSIONS.any? { |ext| path.end_with?(ext) } end def is_task_name?(name) !!(name =~ /^[a-z][a-z0-9_]*$/) end end class ResourceTypeImplPP < PuppetSmartPath RESOURCE_TYPES_PATH_PP = '.resource_types'.freeze def relative_path RESOURCE_TYPES_PATH_PP end def root_path @loader.path end def instantiator PuppetResourceTypeImplInstantiator end # The effect paths for resource type impl is the full name # since resource types are not name spaced. # This overrides the default PuppetSmartPath. # def effective_path(typed_name, start_index_in_name) # Resource type to name does not skip the name-space # i.e. <module>/mymodule/resource_types/foo.pp is the resource type foo "#{File.join(generic_path, typed_name.name_parts)}.pp" end end class PlanPathPP < PuppetSmartPath PLAN_PATH_PP = File.join('plans') def relative_path PLAN_PATH_PP end def instantiator() Puppet::Pops::Loader::PuppetPlanInstantiator end def typed_name(type, name_authority, relative_path, module_name) if relative_path == 'init.pp' && !(module_name.nil? || module_name.empty?) TypedName.new(type, module_name, name_authority) else n = '' n << module_name unless module_name.nil? unless extension.empty? # Remove extension relative_path = relative_path[0..-(extension.length+1)] end relative_path.split('/').each do |segment| n << '::' if n.size > 0 n << segment end TypedName.new(type, n, name_authority) end end end # SmartPaths # === # Holds effective SmartPath instances per type # class SmartPaths def initialize(path_based_loader) @loader = path_based_loader @smart_paths = {} end # Ensures that the paths for the type have been probed and pruned to what is existing relative to # the given root. # # @param type [Symbol] the entity type to load # @return [Array<SmartPath>] array of effective paths for type (may be empty) # def effective_paths(type) smart_paths = @smart_paths loader = @loader unless effective_paths = smart_paths[type] # type not yet processed, does the various directories for the type exist ? # Get the relative dirs for the type paths_for_type = LoaderPaths.relative_paths_for_type(type, loader) # Check which directories exist in the loader's content/index effective_paths = smart_paths[type] = paths_for_type.select { |sp| loader.meaningful_to_search?(sp) } end effective_paths end end end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/loader/module_loaders.rb����������������������������������������������0000644�0052762�0001160�00000052122�13417161721�022651� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Loader # =ModuleLoaders # A ModuleLoader loads items from a single module. # The ModuleLoaders (ruby) module contains various such loaders. There is currently one concrete # implementation, ModuleLoaders::FileBased that loads content from the file system. # Other implementations can be created - if they are based on name to path mapping where the path # is relative to a root path, they can derive the base behavior from the ModuleLoaders::AbstractPathBasedModuleLoader class. # # Examples of such extensions could be a zip/jar/compressed file base loader. # # Notably, a ModuleLoader does not configure itself - it is given the information it needs (the root, its name etc.) # Logic higher up in the loader hierarchy of things makes decisions based on the "shape of modules", and "available # modules" to determine which module loader to use for each individual module. (There could be differences in # internal layout etc.) # # A module loader is also not aware of the mapping of name to relative paths. # # @api private # module ModuleLoaders def self.system_loader_from(parent_loader, loaders) # Puppet system may be installed in a fixed location via RPM, installed as a Gem, via source etc. # The only way to find this across the different ways puppet can be installed is # to search up the path from this source file's __FILE__ location until it finds the base of # puppet. # puppet_lib = File.realpath(File.join(File.dirname(__FILE__), '../../..')) LibRootedFileBased.new(parent_loader, loaders, nil, puppet_lib, # may or may not have a 'lib' above 'puppet' 'puppet_system', [:func_4x, :datatype] # only load ruby functions and types from "puppet" ) end def self.environment_loader_from(parent_loader, loaders, env_path) if env_path.nil? || env_path.empty? EmptyLoader.new(parent_loader, ENVIRONMENT) else FileBased.new(parent_loader, loaders, ENVIRONMENT, env_path, ENVIRONMENT ) end end def self.module_loader_from(parent_loader, loaders, module_name, module_path) ModuleLoaders::FileBased.new(parent_loader, loaders, module_name, module_path, module_name ) end def self.pcore_resource_type_loader_from(parent_loader, loaders, environment_path) ModuleLoaders::FileBased.new(parent_loader, loaders, nil, environment_path, 'pcore_resource_types' ) end class EmptyLoader < BaseLoader def find(typed_name) return nil end def private_loader @private_loader ||= self end def private_loader=(loader) @private_loader = loader end end class AbstractPathBasedModuleLoader < BaseLoader # The name of the module, or nil, if this is a global "component" attr_reader :module_name # The path to the location of the module/component - semantics determined by subclass attr_reader :path # A map of type to smart-paths that help with minimizing the number of paths to scan attr_reader :smart_paths # A Module Loader has a private loader, it is lazily obtained on request to provide the visibility # for entities contained in the module. Since a ModuleLoader also represents an environment and it is # created a different way, this loader can be set explicitly by the loaders bootstrap logic. # # @api private attr_accessor :private_loader # Initialize a kind of ModuleLoader for one module # @param parent_loader [Loader] loader with higher priority # @param loaders [Loaders] the container for this loader # @param module_name [String] the name of the module (non qualified name), may be nil for a global "component" # @param path [String] the path to the root of the module (semantics defined by subclass) # @param loader_name [String] a name that is used for human identification (useful when module_name is nil) # def initialize(parent_loader, loaders, module_name, path, loader_name, loadables) super parent_loader, loader_name raise ArgumentError, 'path based loader cannot be instantiated without a path' if path.nil? || path.empty? @module_name = module_name @path = path @smart_paths = LoaderPaths::SmartPaths.new(self) @loaders = loaders @loadables = loadables unless (loadables - LOADABLE_KINDS).empty? #TRANSLATORS 'loadables' is a variable containing loadable modules and should not be translated raise ArgumentError, _('given loadables are not of supported loadable kind') end loaders.add_loader_by_name(self) end def loadables @loadables end def discover(type, error_collector = nil, name_authority = Pcore::RUNTIME_NAME_AUTHORITY, &block) global = global? if name_authority == Pcore::RUNTIME_NAME_AUTHORITY smart_paths.effective_paths(type).each do |sp| relative_paths(sp).each do |rp| tp = sp.typed_name(type, name_authority, rp, global ? nil : @module_name) next unless sp.valid_name?(tp) begin load_typed(tp) unless block_given? && !block.yield(tp) rescue StandardError => e if error_collector.nil? Puppet.warn_once(:unloadable_entity, tp.to_s, e.message) else err = Puppet::DataTypes::Error.new( Issues::LOADER_FAILURE.format(:type => type), 'PUPPET_LOADER_FAILURE', { 'original_error' => e.message }, Issues::LOADER_FAILURE.issue_code) error_collector << err unless error_collector.include?(err) end end end end end super end # Finds typed/named entity in this module # @param typed_name [TypedName] the type/name to find # @return [Loader::NamedEntry, nil found/created entry, or nil if not found # def find(typed_name) # This loader is tailored to only find entries in the current runtime return nil unless typed_name.name_authority == Pcore::RUNTIME_NAME_AUTHORITY # Assume it is a global name, and that all parts of the name should be used when looking up name_parts = typed_name.name_parts # Certain types and names can be disqualified up front if name_parts.size > 1 # The name is in a name space. # Then entity cannot possible be in this module unless the name starts with the module name. # Note: If "module" represents a "global component", the module_name is nil and cannot match which is # ok since such a "module" cannot have namespaced content). # return nil unless name_parts[0] == module_name else # The name is in the global name space. case typed_name.type when :function, :resource_type, :resource_type_pp # Can be defined in module using a global name. No action required when :plan if !global? # Global name must be the name of the module return nil unless name_parts[0] == module_name # Look for the special 'init' plan. origin, smart_path = find_existing_path(init_plan_name) return smart_path.nil? ? nil : instantiate(smart_path, typed_name, origin) end when :task if !global? # Global name must be the name of the module return nil unless name_parts[0] == module_name # Look for the special 'init' Task origin, smart_path = find_existing_path(init_task_name) return smart_path.nil? ? nil : instantiate(smart_path, typed_name, origin) end when :type if !global? # Global name must be the name of the module unless name_parts[0] == module_name # Check for ruby defined data type in global namespace before giving up origin, smart_path = find_existing_path(typed_name) return smart_path.is_a?(LoaderPaths::DataTypePath) ? instantiate(smart_path, typed_name, origin) : nil end # Look for the special 'init_typeset' TypeSet origin, smart_path = find_existing_path(init_typeset_name) return nil if smart_path.nil? value = smart_path.instantiator.create(self, typed_name, origin, get_contents(origin)) if value.is_a?(Types::PTypeSetType) # cache the entry and return it return set_entry(typed_name, value, origin) end #TRANSLATORS 'TypeSet' should not be translated raise ArgumentError, _("The code loaded from %{origin} does not define the TypeSet '%{module_name}'") % { origin: origin, module_name: module_name.capitalize } end else # anything else cannot possibly be in this module # TODO: should not be allowed anyway... may have to revisit this decision return nil end end # Get the paths that actually exist in this module (they are lazily processed once and cached). # The result is an array (that may be empty). # Find the file to instantiate, and instantiate the entity if file is found origin, smart_path = find_existing_path(typed_name) return instantiate(smart_path, typed_name, origin) unless smart_path.nil? return nil unless typed_name.type == :type && typed_name.qualified? # Search for TypeSet using parent name ts_name = typed_name.parent while ts_name # Do not traverse parents here. This search must be confined to this loader tse = get_entry(ts_name) tse = find(ts_name) if tse.nil? || tse.value.nil? if tse && (ts = tse.value).is_a?(Types::PTypeSetType) # The TypeSet might be unresolved at this point. If so, it must be resolved using # this loader. That in turn, adds all contained types to this loader. ts.resolve(self) te = get_entry(typed_name) return te unless te.nil? end ts_name = ts_name.parent end nil end def instantiate(smart_path, typed_name, origin) if origin.is_a?(Array) value = smart_path.instantiator.create(self, typed_name, origin) else value = smart_path.instantiator.create(self, typed_name, origin, get_contents(origin)) end # cache the entry and return it set_entry(typed_name, value, origin) end # Abstract method that subclasses override that checks if it is meaningful to search using a generic smart path. # This optimization is performed to not be tricked into searching an empty directory over and over again. # The implementation may perform a deep search for file content other than directories and cache this in # and index. It is guaranteed that a call to meaningful_to_search? takes place before checking any other # path with relative_path_exists?. # # This optimization exists because many modules have been created from a template and they have # empty directories for functions, types, etc. (It is also the place to create a cached index of the content). # # @param smart_path [String] a path relative to the module's root # @return [Boolean] true if there is content in the directory appointed by the relative path # def meaningful_to_search?(smart_path) raise NotImplementedError.new end # Abstract method that subclasses override to answer if the given relative path exists, and if so returns that path # # @param resolved_path [String] a path resolved by a smart path against the loader's root (if it has one) # @return [String, nil] the found path or nil if no such path was found # def existing_path(resolved_path) raise NotImplementedError.new end # Abstract method that subclasses override to return an array of paths that match the resolved path regardless of # path extension. # # @param resolved_path [String] a path, without extension, resolved by a smart path against the loader's root (if it has one) # @return [Array<String>] # def existing_paths(resolved_path) raise NotImplementedError.new end # Abstract method that subclasses override to produce the content of the effective path. # It should either succeed and return a String or fail with an exception. # # @param effective_path [String] a path as resolved by a smart path # @return [String] the content of the file # def get_contents(effective_path) raise NotImplementedError.new end # Abstract method that subclasses override to produce a source reference String used to identify the # system resource (resource in the URI sense). # # @param relative_path [String] a path relative to the module's root # @return [String] a reference to the source file (in file system, zip file, or elsewhere). # def get_source_ref(relative_path) raise NotImplementedError.new end # Answers the question if this loader represents a global component (true for resource type loader and environment loader) # # @return [Boolean] `true` if this loader represents a global component # def global? module_name.nil? || module_name == ENVIRONMENT end # Answers `true` if the loader used by this instance is rooted beneath 'lib'. This is # typically true for the the system_loader. It will have a path relative to the parent # of 'puppet' instead of the parent of 'lib/puppet' since the 'lib' directory of puppet # is renamed during install. This is significant for loaders that load ruby code. # # @return [Boolean] a boolean answering if the loader is rooted beneath 'lib'. def lib_root? false end # Produces the private loader for the module. If this module is not already resolved, this will trigger resolution # def private_loader # The system loader has a nil module_name and it does not have a private_loader as there are no functions # that can only by called by puppet runtime - if so, it acts as the private loader directly. @private_loader ||= (global? ? self : @loaders.private_loader_for_module(module_name)) end # Return all paths that matches the given smart path. The returned paths are # relative to the `#generic_path` of the given smart path. # # @param smart_path [SmartPath] the path to find relative paths for # @return [Array<String>] found paths def relative_paths(smart_path) raise NotImplementedError.new end private # @return [TypedName] the fake typed name that maps to the init_typeset path for this module def init_typeset_name @init_typeset_name ||= TypedName.new(:type, "#{module_name}::init_typeset") end # @return [TypedName] the fake typed name that maps to the path of an init[arbitrary extension] # file that represents a task named after the module def init_task_name @init_task_name ||= TypedName.new(:task, "#{module_name}::init") end # @return [TypedName] the fake typed name that maps to the path of an init.pp file that represents # a plan named after the module def init_plan_name @init_plan_name ||= TypedName.new(:plan, "#{module_name}::init") end # Find an existing path or paths for the given `typed_name`. Return `nil` if no path is found # @param typed_name [TypedName] the `typed_name` to find a path for # @return [Array,nil] `nil`or a two element array where the first element is an effective path or array of paths # (depending on the `SmartPath`) and the second element is the `SmartPath` that produced the effective path or # paths. A path is a String def find_existing_path(typed_name) is_global = global? smart_paths.effective_paths(typed_name.type).each do |sp| next unless sp.valid_name?(typed_name) origin = sp.effective_path(typed_name, is_global ? 0 : 1) unless origin.nil? if sp.match_many? # Find all paths that starts with origin origins = existing_paths(origin) return [origins, sp] unless origins.empty? else existing = existing_path(origin) return [origin, sp] unless existing.nil? end end end nil end end # @api private # class FileBased < AbstractPathBasedModuleLoader attr_reader :smart_paths attr_reader :path_index # Create a kind of ModuleLoader for one module (Puppet Module, or module like) # # @param parent_loader [Loader] typically the loader for the environment or root # @param module_name [String] the name of the module (non qualified name), may be nil for "modules" only containing globals # @param path [String] the path to the root of the module (semantics defined by subclass) # @param loader_name [String] a name that identifies the loader # def initialize(parent_loader, loaders, module_name, path, loader_name, loadables = LOADABLE_KINDS) super @path_index = Set.new end def existing_path(effective_path) # Optimized, checks index instead of visiting file system @path_index.include?(effective_path) ? effective_path : nil end def existing_paths(effective_path) dirname = File.dirname(effective_path) basename = File.basename(effective_path) # Select all paths matching `effective_path` up until an optional file extension @path_index.select do |path| File.basename(path, '.*') == basename && File.dirname(path) == dirname end end def meaningful_to_search?(smart_path) ! add_to_index(smart_path).empty? end def to_s() "(ModuleLoader::FileBased '#{loader_name}' '#{module_name}')" end def add_to_index(smart_path) found = Dir.glob(File.join(smart_path.generic_path, '**', "*#{smart_path.extension}")) # The reason for not always rejecting directories here is performance (avoid extra stat calls). The # false positives (directories with a matching extension) is an error in any case and will be caught # later. found = found.reject { |file_name| File.directory?(file_name) } if smart_path.extension.empty? @path_index.merge(found) found end def get_contents(effective_path) Puppet::FileSystem.read(effective_path, :encoding => 'utf-8') end # Return all paths that matches the given smart path. The returned paths are # relative to the `#generic_path` of the given smart path. # # This method relies on the cache and does not perform any file system access # # @param smart_path [SmartPath] the path to find relative paths for # @return [Array<String>] found paths def relative_paths(smart_path) root = smart_path.generic_path found = [] @path_index.each do |path| found << Pathname(path).relative_path_from(Pathname(root)).to_s if smart_path.valid_path?(path) end found end end # Specialization used by the system_loader which is limited to see what's beneath 'lib' and hence # cannot be rooted in its parent. The 'lib' directory is renamed during install so any attempt # to traverse into it from above would fail. # # @api private # class LibRootedFileBased < FileBased def lib_root? true end end # Loads from a gem specified as a URI, gem://gemname/optional/path/in/gem, or just a String gemname. # The source reference (shown in errors etc.) is the expanded path of the gem as this is believed to be more # helpful - given the location it should be quite obvious which gem it is, without the location, the user would # need to go on a hunt for where the file actually is located. # # TODO: How does this get instantiated? Does the gemname refelect the name of the module (the namespace) # or is that specified a different way? Can a gem be the container of multiple modules? # # @api private # class GemBased < FileBased include GemSupport attr_reader :gem_ref # Create a kind of ModuleLoader for one module # The parameters are: # * parent_loader - typically the loader for the root # * module_name - the name of the module (non qualified name) # * gem_ref - [URI, String] gem reference to the root of the module (URI, gem://gemname/optional/path/in/gem), or # just the gem's name as a String. # def initialize(parent_loader, loaders, module_name, gem_ref, loader_name, loadables = LOADABLE_KINDS) @gem_ref = gem_ref super parent_loader, loaders, module_name, gem_dir(gem_ref), loader_name, loadables end def to_s() "(ModuleLoader::GemBased '#{loader_name}' '#{@gem_ref}' [#{module_name}])" end end end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/loader/static_loader.rb�����������������������������������������������0000644�0052762�0001160�00000007615�13417161721�022477� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� # Static Loader contains constants, basic data types and other types required for the system # to boot. # module Puppet::Pops module Loader class StaticLoader < Loader BUILTIN_TYPE_NAMES = %w{ Augeas Component Computer Cron Exec File Filebucket Group Host Interface K5login Macauthorization Mailalias Maillist Mcx Mount Nagios_command Nagios_contact Nagios_contactgroup Nagios_host Nagios_hostdependency Nagios_hostescalation Nagios_hostgroup Nagios_hostextinfo Nagios_service Nagios_servicedependency Nagios_serviceescalation Nagios_serviceextinfo Nagios_servicegroup Nagios_timeperiod Node Notify Package Resources Router Schedule Scheduled_task Selboolean Selmodule Service Ssh_authorized_key Sshkey Stage Tidy User Vlan Whit Yumrepo Zfs Zone Zpool }.freeze BUILTIN_TYPE_NAMES_LC = Set.new(BUILTIN_TYPE_NAMES.map { |n| n.downcase }).freeze BUILTIN_ALIASES = { 'Data' => 'Variant[ScalarData,Undef,Hash[String,Data],Array[Data]]', 'RichDataKey' => 'Variant[String,Numeric]', 'RichData' => 'Variant[Scalar,SemVerRange,Binary,Sensitive,Type,TypeSet,URI,Object,Undef,Default,Hash[RichDataKey,RichData],Array[RichData]]', # Backward compatible aliases. 'Puppet::LookupKey' => 'RichDataKey', 'Puppet::LookupValue' => 'RichData' }.freeze attr_reader :loaded def initialize @loaded = {} @runtime_3_initialized = false create_built_in_types end def discover(type, error_collector = nil, name_authority = Pcore::RUNTIME_NAME_AUTHORITY) # Static loader only contains runtime types return EMPTY_ARRAY unless type == :type && name_authority == name_authority = Pcore::RUNTIME_NAME_AUTHORITY typed_names = type == :type && name_authority == Pcore::RUNTIME_NAME_AUTHORITY ? @loaded.keys : EMPTY_ARRAY block_given? ? typed_names.select { |tn| yield(tn) } : typed_names end def load_typed(typed_name) load_constant(typed_name) end def get_entry(typed_name) load_constant(typed_name) end def set_entry(typed_name, value, origin = nil) @loaded[typed_name] = Loader::NamedEntry.new(typed_name, value, origin) end def find(name) # There is nothing to search for, everything this loader knows about is already available nil end def parent nil # at top of the hierarchy end def to_s() "(StaticLoader)" end def loaded_entry(typed_name, check_dependencies = false) @loaded[typed_name] end def runtime_3_init unless @runtime_3_initialized @runtime_3_initialized = true create_resource_type_references end nil end def register_aliases aliases = BUILTIN_ALIASES.map { |name, string| add_type(name, Types::PTypeAliasType.new(name, Types::TypeFactory.type_reference(string), nil)) } aliases.each { |type| type.resolve(self) } end private def load_constant(typed_name) @loaded[typed_name] end def create_built_in_types origin_uri = URI("puppet:Puppet-Type-System/Static-Loader") type_map = Puppet::Pops::Types::TypeParser.type_map type_map.each do |name, type| set_entry(TypedName.new(:type, name), type, origin_uri) end end def create_resource_type_references() # These needs to be done quickly and we do not want to scan the file system for these # We are also not interested in their definition only that they exist. # These types are in all environments. # BUILTIN_TYPE_NAMES.each { |name| create_resource_type_reference(name) } end def add_type(name, type) set_entry(TypedName.new(:type, name), type) type end def create_resource_type_reference(name) add_type(name, Types::TypeFactory.resource(name)) end end end end �������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/loader/task_instantiator.rb�������������������������������������������0000644�0052762�0001160�00000004450�13417161721�023415� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The TypeDefinitionInstantiator instantiates a type alias or a type definition # module Puppet::Pops module Loader class TaskInstantiator def self.create(loader, typed_name, source_refs) name = typed_name.name metadata = nil task_source = nil source_refs.each do |source_ref| if source_ref.end_with?('.json') metadata = source_ref elsif task_source.nil? task_source = source_ref else raise ArgumentError, _('Only one file can exists besides the .json file for task %{name} in directory %{directory}') % { name: name, directory: File.dirname(source_refs[0]) } end end if task_source.nil? raise ArgumentError, _('No source besides task metadata was found in directory %{directory} for task %{name}') % { name: name, directory: File.dirname(source_refs[0]) } end create_task(loader, name, task_source, metadata) end def self.create_task(loader, name, task_source, metadata) if metadata.nil? create_task_from_hash(name, task_source, EMPTY_HASH) else json_text = loader.get_contents(metadata) begin create_task_from_hash(name, task_source, Puppet::Util::Json.load(json_text) || EMPTY_HASH) rescue Puppet::Util::Json::ParseError => ex raise Puppet::ParseError.new(ex.message, metadata) rescue Types::TypeAssertionError => ex # Not strictly a parser error but from the users perspective, the file content didn't parse properly. The # ParserError also conveys file info (even though line is unknown) msg = _('Failed to load metadata for task %{name}: %{reason}') % { :name => name, :reason => ex.message } raise Puppet::ParseError.new(msg, metadata) end end end def self.create_task_from_hash(name, task_source, hash) arguments = { 'name' => name, 'executable' => task_source } hash.each_pair do |key, value| if 'parameters' == key || 'output' == key ps = {} value.each_pair do |k, v| pd = v.dup t = v['type'] pd['type'] = t.nil? ? Types::TypeFactory.data : Types::TypeParser.singleton.parse(t) ps[k] = pd end value = ps end arguments[key] = value end Types::TypeFactory.task.from_hash(arguments) end end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/lookup.rb�������������������������������������������������������������0000644�0052762�0001160�00000010000�13417161721�017703� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This class is the backing implementation of the Puppet function 'lookup'. # See puppet/functions/lookup.rb for documentation. # module Puppet::Pops module Lookup LOOKUP_OPTIONS = 'lookup_options'.freeze GLOBAL = '__global__'.freeze # Performs a lookup in the configured scopes and optionally merges the default. # # This is a backing function and all parameters are assumed to have been type checked. # See puppet/functions/lookup.rb for full documentation and all parameter combinations. # # @param name [String|Array<String>] The name or names to lookup # @param type [Types::PAnyType|nil] The expected type of the found value # @param default_value [Object] The value to use as default when no value is found # @param has_default [Boolean] Set to _true_ if _default_value_ is included (_nil_ is a valid _default_value_) # @param merge [MergeStrategy,String,Hash<String,Object>,nil] Merge strategy or hash with strategy and options # @param lookup_invocation [Invocation] Invocation data containing scope, overrides, and defaults # @return [Object] The found value # def self.lookup(name, value_type, default_value, has_default, merge, lookup_invocation) names = name.is_a?(Array) ? name : [name] # find first name that yields a non-nil result and wrap it in a two element array # with name and value. not_found = MergeStrategy::NOT_FOUND override_values = lookup_invocation.override_values result_with_name = names.reduce([nil, not_found]) do |memo, key| value = override_values.include?(key) ? assert_type(["Value found for key '%s' in override hash", key], value_type, override_values[key]) : not_found catch(:no_such_key) { value = search_and_merge(key, lookup_invocation, merge, false) } if value.equal?(not_found) break [key, assert_type('Found value', value_type, value)] unless value.equal?(not_found) memo end # Use the 'default_values' hash as a last resort if nothing is found if result_with_name[1].equal?(not_found) default_values = lookup_invocation.default_values unless default_values.empty? result_with_name = names.reduce(result_with_name) do |memo, key| value = default_values.include?(key) ? assert_type(["Value found for key '%s' in default values hash", key], value_type, default_values[key]) : not_found memo = [key, value] break memo unless value.equal?(not_found) memo end end end answer = result_with_name[1] if answer.equal?(not_found) if block_given? answer = assert_type('Value returned from default block', value_type, yield(name)) elsif has_default answer = assert_type('Default value', value_type, default_value) else lookup_invocation.emit_debug_info(debug_preamble(names)) if Puppet[:debug] fail_lookup(names) end end lookup_invocation.emit_debug_info(debug_preamble(names)) if Puppet[:debug] answer end # @api private def self.debug_preamble(names) if names.size == 1 names = "'#{names[0]}'" else names = names.map { |n| "'#{n}'" }.join(', ') end "Lookup of #{names}" end # @api private def self.search_and_merge(name, lookup_invocation, merge, apl = true) answer = lookup_invocation.lookup_adapter.lookup(name, lookup_invocation, merge) lookup_invocation.emit_debug_info("Automatic Parameter Lookup of '#{name}'") if apl && Puppet[:debug] answer end def self.assert_type(subject, type, value) type ? Types::TypeAsserter.assert_instance_of(subject, type, value) : value end private_class_method :assert_type def self.fail_lookup(names) raise Puppet::DataBinding::LookupError, n_("Function lookup() did not find a value for the name '%{name}'", "Function lookup() did not find a value for any of the names [%{name_list}]", names.size ) % { name: names[0], name_list: names.map { |n| "'#{n}'" }.join(', ') } end private_class_method :fail_lookup end end require_relative 'lookup/lookup_adapter' puppet-5.5.10/lib/puppet/pops/lookup/���������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�017374� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/lookup/configured_data_provider.rb������������������������������������0000644�0052762�0001160�00000005640�13417161721�024751� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require_relative 'hiera_config' require_relative 'data_provider' module Puppet::Pops module Lookup # @api private class ConfiguredDataProvider include DataProvider # @param config [HieraConfig,nil] the configuration def initialize(config = nil) @config = config.nil? ? nil : assert_config_version(config) end def config(lookup_invocation) @config ||= assert_config_version(HieraConfig.create(lookup_invocation, configuration_path(lookup_invocation), self)) end # Needed to assign generated version 4 config # @deprecated def config=(config) @config = config end # @return [Pathname] the path to the configuration def config_path @config.nil? ? nil : @config.config_path end # @return [String] the name of this provider def name n = "#{place} " n << '"' << module_name << '" ' unless module_name.nil? n << 'Data Provider' n << " (#{@config.name})" unless @config.nil? n end # Performs a lookup by searching all configured locations for the given _key_. A merge will be performed if # the value is found in more than one location. # # @param key [String] The key to lookup # @param lookup_invocation [Invocation] The current lookup invocation # @param merge [MergeStrategy,String,Hash{String => Object},nil] Merge strategy, merge strategy name, strategy and options hash, or nil (implies "first found") # @return [Object] the found object # @throw :no_such_key when the object is not found def unchecked_key_lookup(key, lookup_invocation, merge) lookup_invocation.with(:data_provider, self) do merge_strategy = MergeStrategy.strategy(merge) dps = data_providers(lookup_invocation) if dps.empty? lookup_invocation.report_not_found(key) throw :no_such_key end merge_strategy.lookup(dps, lookup_invocation) do |data_provider| data_provider.unchecked_key_lookup(key, lookup_invocation, merge_strategy) end end end protected # Assert that the given config version is accepted by this data provider. # # @param config [HieraConfig] the configuration to check # @return [HieraConfig] the argument # @raise [Puppet::DataBinding::LookupError] if the configuration version is unacceptable def assert_config_version(config) config end # Return the root of the configured entity # # @param lookup_invocation [Invocation] The current lookup invocation # @return [Pathname] Path to root of the module # @raise [Puppet::DataBinding::LookupError] if the given module is can not be found # def provider_root(lookup_invocation) raise NotImplementedError, "#{self.class.name} must implement method '#provider_root'" end def configuration_path(lookup_invocation) provider_root(lookup_invocation) + HieraConfig::CONFIG_FILE_NAME end private def data_providers(lookup_invocation) config(lookup_invocation).configured_data_providers(lookup_invocation, self) end end end end ������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/lookup/context.rb�����������������������������������������������������0000644�0052762�0001160�00000014053�13417161721�021403� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require_relative 'interpolation' module Puppet::Pops module Lookup # The EnvironmentContext is adapted to the current environment # class EnvironmentContext < Adaptable::Adapter class FileData attr_reader :data def initialize(path, inode, mtime, size, data) @path = path @inode = inode @mtime = mtime @size = size @data = data end def valid?(stat) stat.ino == @inode && stat.mtime == @mtime && stat.size == @size end end attr_reader :environment_name def self.create_adapter(environment) new(environment) end def initialize(environment) @environment_name = environment.name @file_data_cache = {} end # Loads the contents of the file given by _path_. The content is then yielded to the provided block in # case a block is given, and the returned value from that block is cached and returned by this method. # If no block is given, the content is stored instead. # # The cache is retained as long as the inode, mtime, and size of the file remains unchanged. # # @param path [String] path to the file to be read # @yieldparam content [String] the content that was read from the file # @yieldreturn [Object] some result based on the content # @return [Object] the content, or if a block was given, the return value of the block # def cached_file_data(path) file_data = @file_data_cache[path] stat = Puppet::FileSystem.stat(path) unless file_data && file_data.valid?(stat) Puppet.debug("File at '#{path}' was changed, reloading") if file_data content = Puppet::FileSystem.read(path, :encoding => 'utf-8') file_data = FileData.new(path, stat.ino, stat.mtime, stat.size, block_given? ? yield(content) : content) @file_data_cache[path] = file_data end file_data.data end end # A FunctionContext is created for each unique hierarchy entry and adapted to the Compiler (and hence shares # the compiler's life-cycle). # @api private class FunctionContext include Interpolation attr_reader :module_name, :function attr_accessor :data_hash def initialize(environment_context, module_name, function) @data_hash = nil @cache = {} @environment_context = environment_context @module_name = module_name @function = function end def cache(key, value) @cache[key] = value end def cache_all(hash) @cache.merge!(hash) nil end def cache_has_key(key) @cache.include?(key) end def cached_value(key) @cache[key] end def cached_entries(&block) if block_given? enumerator = @cache.each_pair @cache.size.times do if block.arity == 2 yield(*enumerator.next) else yield(enumerator.next) end end nil else Types::Iterable.on(@cache) end end def cached_file_data(path, &block) @environment_context.cached_file_data(path, &block) end def environment_name @environment_context.environment_name end end # The Context is created once for each call to a function. It provides a combination of the {Invocation} object needed # to provide explanation support and the {FunctionContext} object needed to provide the private cache. # The {Context} is part of the public API. It will be passed to a _data_hash_, _data_dig_, or _lookup_key_ function and its # attributes and methods can be used in a Puppet function as well as in a Ruby function. # The {Context} is maps to the Pcore type 'Puppet::LookupContext' # # @api public class Context include Types::PuppetObject extend Forwardable def self._pcore_type @type end def self.register_ptype(loader, ir) tf = Types::TypeFactory key_type = tf.optional(tf.scalar) @type = Pcore::create_object_type(loader, ir, self, 'Puppet::LookupContext', 'Any', { 'environment_name' => { Types::KEY_TYPE => Types::PStringType::NON_EMPTY, Types::KEY_KIND => Types::PObjectType::ATTRIBUTE_KIND_DERIVED }, 'module_name' => { Types::KEY_TYPE => tf.variant(Types::PStringType::NON_EMPTY, Types::PUndefType::DEFAULT) } }, { 'not_found' => tf.callable([0, 0], tf.undef), 'explain' => tf.callable([0, 0, tf.callable(0,0)], tf.undef), 'interpolate' => tf.callable(1, 1), 'cache' => tf.callable([key_type, tf.any], tf.any), 'cache_all' => tf.callable([tf.hash_kv(key_type, tf.any)], tf.undef), 'cache_has_key' => tf.callable([key_type], tf.boolean), 'cached_value' => tf.callable([key_type], tf.any), 'cached_entries' => tf.variant( tf.callable([0, 0, tf.callable(1,1)], tf.undef), tf.callable([0, 0, tf.callable(2,2)], tf.undef), tf.callable([0, 0], tf.iterable(tf.tuple([key_type, tf.any])))), 'cached_file_data' => tf.callable(tf.string, tf.optional(tf.callable([1, 1]))) } ).resolve(loader) end # Mainly for test purposes. Makes it possible to create a {Context} in Puppet code provided that a current {Invocation} exists. def self.from_asserted_args(module_name) new(FunctionContext.new(EnvironmentContext.adapt(Puppet.lookup(:environments).get(Puppet[:environment])), module_name, nil), Invocation.current) end # Public methods delegated to the {FunctionContext} def_delegators :@function_context, :cache, :cache_all, :cache_has_key, :cached_value, :cached_entries, :environment_name, :module_name, :cached_file_data def initialize(function_context, lookup_invocation) @lookup_invocation = lookup_invocation @function_context = function_context end # Will call the given block to obtain a textual explanation if explanation support is active. # def explain(&block) @lookup_invocation.report_text(&block) nil end # Resolve interpolation expressions in the given value # @param [Object] value # @return [Object] the value with all interpolation expressions resolved def interpolate(value) @function_context.interpolate(value, @lookup_invocation, true) end def not_found throw :no_such_key end # @api private def invocation @lookup_invocation end end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/lookup/data_adapter.rb������������������������������������������������0000644�0052762�0001160�00000000543�13417161721�022327� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Lookup # A class that adapts a Hash # @api private class DataAdapter < Adaptable::Adapter def self.create_adapter(o) new end def initialize @data = {} end def [](name) @data[name] end def include?(name) @data.include? name end def []=(name, value) @data[name] = value end end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/lookup/data_dig_function_provider.rb����������������������������������0000644�0052762�0001160�00000012144�13417161721�025271� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require_relative 'function_provider' module Puppet::Pops module Lookup # @api private class DataDigFunctionProvider < FunctionProvider TAG = 'data_dig'.freeze # Performs a lookup with the assumption that a recursive check has been made. # # @param key [LookupKey] The key to lookup # @param lookup_invocation [Invocation] The current lookup invocation # @param merge [MergeStrategy,String,Hash{String => Object},nil] Merge strategy, merge strategy name, strategy and options hash, or nil (implies "first found") # @return [Object] the found object # @throw :no_such_key when the object is not found def unchecked_key_lookup(key, lookup_invocation, merge) lookup_invocation.with(:data_provider, self) do MergeStrategy.strategy(merge).lookup(locations, lookup_invocation) do |location| invoke_with_location(lookup_invocation, location, key, merge) end end end def invoke_with_location(lookup_invocation, location, key, merge) if location.nil? key.undig(lookup_invocation.report_found(key, validated_data_dig(key, lookup_invocation, nil, merge))) else lookup_invocation.with(:location, location) do key.undig(lookup_invocation.report_found(key, validated_data_dig(key, lookup_invocation, location, merge))) end end end def label 'Data Dig' end def validated_data_dig(key, lookup_invocation, location, merge) validate_data_value(data_dig(key, lookup_invocation, location, merge)) do msg = "Value for key '#{key}', returned from #{full_name}" location.nil? ? msg : "#{msg}, when using location '#{location}'," end end private def data_dig(key, lookup_invocation, location, merge) unless location.nil? || location.exist? lookup_invocation.report_location_not_found throw :no_such_key end ctx = function_context(lookup_invocation, location) ctx.data_hash ||= {} catch(:no_such_key) do hash = ctx.data_hash hash[key] = ctx.function.call(lookup_invocation.scope, key.to_a, options(location), Context.new(ctx, lookup_invocation)) unless hash.include?(key) return hash[key] end lookup_invocation.report_not_found(key) throw :no_such_key end end # @api private class V3BackendFunctionProvider < DataDigFunctionProvider TAG = 'hiera3_backend'.freeze def data_dig(key, lookup_invocation, location, merge) @backend ||= instantiate_backend(lookup_invocation) # A merge_behavior retrieved from hiera.yaml must not be converted here. Instead, passing the symbol :hash # tells the V3 backend to pick it up from the config. resolution_type = lookup_invocation.hiera_v3_merge_behavior? ? :hash : convert_merge(merge) @backend.lookup(key.to_s, lookup_invocation.scope, lookup_invocation.hiera_v3_location_overrides, resolution_type, {:recurse_guard => nil}) end def full_name "hiera version 3 backend '#{options[HieraConfig::KEY_BACKEND]}'" end def value_is_validated? false end private def instantiate_backend(lookup_invocation) backend_name = options[HieraConfig::KEY_BACKEND] begin require 'hiera/backend' require "hiera/backend/#{backend_name.downcase}_backend" backend = Hiera::Backend.const_get("#{backend_name.capitalize}_backend").new return backend.method(:lookup).arity == 4 ? Hiera::Backend::Backend1xWrapper.new(backend) : backend rescue LoadError => e lookup_invocation.report_text { "Unable to load backend '#{backend_name}': #{e.message}" } throw :no_such_key rescue NameError => e lookup_invocation.report_text { "Unable to instantiate backend '#{backend_name}': #{e.message}" } throw :no_such_key end end # Converts a lookup 'merge' parameter argument into a Hiera 'resolution_type' argument. # # @param merge [String,Hash,nil] The lookup 'merge' argument # @return [Symbol,Hash,nil] The Hiera 'resolution_type' def convert_merge(merge) case merge when nil when 'first', 'default' # Nil is OK. Defaults to Hiera :priority nil when Puppet::Pops::MergeStrategy convert_merge(merge.configuration) when 'unique' # Equivalent to Hiera :array :array when 'hash' # Equivalent to Hiera :hash with default :native merge behavior. A Hash must be passed here # to override possible Hiera deep merge config settings. { :behavior => :native } when 'deep', 'unconstrained_deep' # Equivalent to Hiera :hash with :deeper merge behavior. { :behavior => :deeper } when 'reverse_deep' # Equivalent to Hiera :hash with :deep merge behavior. { :behavior => :deep } when Hash strategy = merge['strategy'] case strategy when 'deep', 'unconstrained_deep', 'reverse_deep' result = { :behavior => strategy == 'reverse_deep' ? :deep : :deeper } # Remaining entries must have symbolic keys merge.each_pair { |k,v| result[k.to_sym] = v unless k == 'strategy' } result else convert_merge(strategy) end else raise Puppet::DataBinding::LookupError, "Unrecognized value for request 'merge' parameter: '#{merge}'" end end end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/lookup/data_hash_function_provider.rb���������������������������������0000644�0052762�0001160�00000010154�13417161721�025450� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require_relative 'function_provider' require_relative 'interpolation' module Puppet::Pops module Lookup # @api private class DataHashFunctionProvider < FunctionProvider include SubLookup include Interpolation TAG = 'data_hash'.freeze def self.trusted_return_type @trusted_return_type ||= Types::PHashType.new(DataProvider.key_type, DataProvider.value_type) end # Performs a lookup with the assumption that a recursive check has been made. # # @param key [LookupKey] The key to lookup # @param lookup_invocation [Invocation] The current lookup invocation # @param merge [MergeStrategy,String,Hash{String => Object},nil] Merge strategy, merge strategy name, strategy and options hash, or nil (implies "first found") # @return [Object] the found object # @throw :no_such_key when the object is not found def unchecked_key_lookup(key, lookup_invocation, merge) root_key = key.root_key lookup_invocation.with(:data_provider, self) do MergeStrategy.strategy(merge).lookup(locations, lookup_invocation) do |location| invoke_with_location(lookup_invocation, location, root_key) end end end private def invoke_with_location(lookup_invocation, location, root_key) if location.nil? lookup_key(lookup_invocation, nil, root_key) else lookup_invocation.with(:location, location) do if location.exist? lookup_key(lookup_invocation, location, root_key) else lookup_invocation.report_location_not_found throw :no_such_key end end end end def lookup_key(lookup_invocation, location, root_key) lookup_invocation.report_found(root_key, data_value(lookup_invocation, location, root_key)) end def data_value(lookup_invocation, location, root_key) hash = data_hash(lookup_invocation, location) value = hash[root_key] if value.nil? && !hash.include?(root_key) lookup_invocation.report_not_found(root_key) throw :no_such_key end value = validate_data_value(value) do msg = "Value for key '#{root_key}', in hash returned from #{full_name}" location.nil? ? msg : "#{msg}, when using location '#{location}'," end interpolate(value, lookup_invocation, true) end def data_hash(lookup_invocation, location) ctx = function_context(lookup_invocation, location) ctx.data_hash ||= parent_data_provider.validate_data_hash(call_data_hash_function(ctx, lookup_invocation, location)) do msg = "Value returned from #{full_name}" location.nil? ? msg : "#{msg}, when using location '#{location}'," end end def call_data_hash_function(ctx, lookup_invocation, location) ctx.function.call(lookup_invocation.scope, options(location), Context.new(ctx, lookup_invocation)) end end # @api private class V3DataHashFunctionProvider < DataHashFunctionProvider TAG = 'v3_data_hash'.freeze def initialize(name, parent_data_provider, function_name, options, locations) @datadir = options.delete(HieraConfig::KEY_DATADIR) super end def unchecked_key_lookup(key, lookup_invocation, merge) extra_paths = lookup_invocation.hiera_v3_location_overrides if extra_paths.nil? || extra_paths.empty? super else # Extra paths provided. Must be resolved and placed in front of known paths paths = parent_data_provider.config(lookup_invocation).resolve_paths(@datadir, extra_paths, lookup_invocation, false, ".#{@name}") all_locations = paths + locations root_key = key.root_key lookup_invocation.with(:data_provider, self) do MergeStrategy.strategy(merge).lookup(all_locations, lookup_invocation) do |location| invoke_with_location(lookup_invocation, location, root_key) end end end end end # TODO: API 5.0, remove this class # @api private class V4DataHashFunctionProvider < DataHashFunctionProvider TAG = 'v4_data_hash'.freeze def name "Deprecated API function \"#{function_name}\"" end def full_name "deprecated API function '#{function_name}'" end def call_data_hash_function(ctx, lookup_invocation, location) ctx.function.call(lookup_invocation.scope) end end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/lookup/data_provider.rb�����������������������������������������������0000644�0052762�0001160�00000007025�13417161721�022543� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Lookup # @api private module DataProvider def self.key_type @key_type end def self.value_type @value_type end def self.register_types(loader) tp = Types::TypeParser.singleton @key_type = tp.parse('RichDataKey', loader) @value_type = tp.parse('RichData', loader) end # Performs a lookup with an endless recursion check. # # @param key [LookupKey] The key to lookup # @param lookup_invocation [Invocation] The current lookup invocation # @param merge [MergeStrategy,String,Hash{String=>Object},nil] Merge strategy or hash with strategy and options # def key_lookup(key, lookup_invocation, merge) lookup_invocation.check(key.to_s) { unchecked_key_lookup(key, lookup_invocation, merge) } end # Performs a lookup using a module default hierarchy with an endless recursion check. All providers except # the `ModuleDataProvider` will throw `:no_such_key` if this method is called. # # @param key [LookupKey] The key to lookup # @param lookup_invocation [Invocation] The current lookup invocation # @param merge [MergeStrategy,String,Hash{String=>Object},nil] Merge strategy or hash with strategy and options # def key_lookup_in_default(key, lookup_invocation, merge) throw :no_such_key end def lookup(key, lookup_invocation, merge) lookup_invocation.check(key.to_s) { unchecked_key_lookup(key, lookup_invocation, merge) } end # Performs a lookup with the assumption that a recursive check has been made. # # @param key [LookupKey] The key to lookup # @param lookup_invocation [Invocation] The current lookup invocation # @param merge [MergeStrategy,String,Hash{String => Object},nil] Merge strategy, merge strategy name, strategy and options hash, or nil (implies "first found") # @return [Object] the found object # @throw :no_such_key when the object is not found def unchecked_key_lookup(key, lookup_invocation, merge) raise NotImplementedError, "Subclass of #{DataProvider.name} must implement 'unchecked_lookup' method" end # @return [String,nil] the name of the module that this provider belongs to nor `nil` if it doesn't belong to a module def module_name nil end # @return [String] the name of the this data provider def name raise NotImplementedError, "Subclass of #{DataProvider.name} must implement 'name' method" end # @returns `true` if the value provided by this instance can always be trusted, `false` otherwise def value_is_validated? false end # Asserts that _data_hash_ is a hash. Will yield to obtain origin of value in case an error is produced # # @param data_hash [Hash{String=>Object}] The data hash # @return [Hash{String=>Object}] The data hash def validate_data_hash(data_hash, &block) Types::TypeAsserter.assert_instance_of(nil, Types::PHashType::DEFAULT, data_hash, &block) end # Asserts that _data_value_ is of valid type. Will yield to obtain origin of value in case an error is produced # # @param data_provider [DataProvider] The data provider that produced the hash # @return [Object] The data value def validate_data_value(value, &block) # The DataProvider.value_type is self recursive so further recursive check of collections is needed here unless value_is_validated? || DataProvider.value_type.instance?(value) actual_type = Types::TypeCalculator.singleton.infer(value) raise Types::TypeAssertionError.new("#{yield} has wrong type, expects Puppet::LookupValue, got #{actual_type}", DataProvider.value_type, actual_type) end value end end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/lookup/environment_data_provider.rb�����������������������������������0000644�0052762�0001160�00000001615�13417161721�025166� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require_relative 'configured_data_provider' module Puppet::Pops module Lookup # @api private class EnvironmentDataProvider < ConfiguredDataProvider def place 'Environment' end protected def assert_config_version(config) if config.version > 3 config else if Puppet[:strict] == :error config.fail(Issues::HIERA_VERSION_3_NOT_GLOBAL, :where => 'environment') else Puppet.warn_once(:hiera_v3_at_env_root, config.config_path, _('hiera.yaml version 3 found at the environment root was ignored'), config.config_path) end nil end end # Return the root of the environment # # @param lookup_invocation [Invocation] The current lookup invocation # @return [Pathname] Path to root of the environment def provider_root(lookup_invocation) Pathname.new(lookup_invocation.scope.environment.configuration.path_to_env) end end end end �������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/lookup/explainer.rb���������������������������������������������������0000644�0052762�0001160�00000034204�13417161721�021706� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Lookup # The ExplainNode contains information of a specific node in a tree traversed during # lookup. The tree can be traversed using the `parent` and `branches` attributes of # each node. # # Each leaf node contains information about what happened when the leaf of the branch # was traversed. class ExplainNode def branches @branches ||= [] end def to_hash hash = {} hash[:branches] = @branches.map {|b| b.to_hash} unless @branches.nil? || @branches.empty? hash end def explain io = '' dump_on(io, '', '') io end def inspect to_s end def to_s s = self.class.name s = "#{s} with #{@branches.size} branches" unless @branches.nil? s end def text(text) @texts ||= [] @texts << text end def dump_on(io, indent, first_indent) dump_texts(io, indent) end def dump_texts(io, indent) @texts.each { |text| io << indent << text << "\n" } if instance_variable_defined?(:@texts) end end class ExplainTreeNode < ExplainNode attr_reader :parent, :event, :value attr_accessor :key def initialize(parent) @parent = parent @event = nil end def found_in_overrides(key, value) @key = key.to_s @value = value @event = :found_in_overrides end def found_in_defaults(key, value) @key = key.to_s @value = value @event = :found_in_defaults end def found(key, value) @key = key.to_s @value = value @event = :found end def result(value) @value = value @event = :result end def not_found(key) @key = key.to_s @event = :not_found end def location_not_found @event = :location_not_found end def increase_indent(indent) indent + ' ' end def to_hash hash = super hash[:key] = @key unless @key.nil? hash[:value] = @value if [:found, :found_in_defaults, :found_in_overrides, :result].include?(@event) hash[:event] = @event unless @event.nil? hash[:texts] = @texts unless @texts.nil? hash[:type] = type hash end def type :root end def dump_outcome(io, indent) case @event when :not_found io << indent << 'No such key: "' << @key << "\"\n" when :found, :found_in_overrides, :found_in_defaults io << indent << 'Found key: "' << @key << '" value: ' dump_value(io, indent, @value) io << ' in overrides' if @event == :found_in_overrides io << ' in defaults' if @event == :found_in_defaults io << "\n" end dump_texts(io, indent) end def dump_value(io, indent, value) case value when Hash io << '{' unless value.empty? inner_indent = increase_indent(indent) value.reduce("\n") do |sep, (k, v)| io << sep << inner_indent dump_value(io, inner_indent, k) io << ' => ' dump_value(io, inner_indent, v) ",\n" end io << "\n" << indent end io << '}' when Array io << '[' unless value.empty? inner_indent = increase_indent(indent) value.reduce("\n") do |sep, v| io << sep << inner_indent dump_value(io, inner_indent, v) ",\n" end io << "\n" << indent end io << ']' else io << value.inspect end end def to_s "#{self.class.name}: #{@key}, #{@event}" end end class ExplainTop < ExplainTreeNode def initialize(parent, type, key) super(parent) @type = type self.key = key.to_s end def dump_on(io, indent, first_indent) io << first_indent << 'Searching for "' << key << "\"\n" indent = increase_indent(indent) branches.each {|b| b.dump_on(io, indent, indent)} end end class ExplainInvalidKey < ExplainTreeNode def initialize(parent, key) super(parent) @key = key.to_s end def dump_on(io, indent, first_indent) io << first_indent << "Invalid key \"" << @key << "\"\n" end def type :invalid_key end end class ExplainMergeSource < ExplainNode attr_reader :merge_source def initialize(merge_source) @merge_source = merge_source end def dump_on(io, indent, first_indent) io << first_indent << 'Using merge options from "' << merge_source << "\" hash\n" end def to_hash { :type => type, :merge_source => merge_source } end def type :merge_source end end class ExplainModule < ExplainTreeNode def initialize(parent, module_name) super(parent) @module_name = module_name end def dump_on(io, indent, first_indent) case @event when :module_not_found io << indent << 'Module "' << @module_name << "\" not found\n" when :module_provider_not_found io << indent << 'Module data provider for module "' << @module_name << "\" not found\n" end end def module_not_found @event = :module_not_found end def module_provider_not_found @event = :module_provider_not_found end def type :module end end class ExplainInterpolate < ExplainTreeNode def initialize(parent, expression) super(parent) @expression = expression end def dump_on(io, indent, first_indent) io << first_indent << 'Interpolation on "' << @expression << "\"\n" indent = increase_indent(indent) branches.each {|b| b.dump_on(io, indent, indent)} end def to_hash hash = super hash[:expression] = @expression hash end def type :interpolate end end class ExplainMerge < ExplainTreeNode def initialize(parent, merge) super(parent) @merge = merge end def dump_on(io, indent, first_indent) return if branches.size == 0 # It's pointless to report a merge where there's only one branch return branches[0].dump_on(io, indent, first_indent) if branches.size == 1 io << first_indent << 'Merge strategy ' << @merge.class.key.to_s << "\n" indent = increase_indent(indent) options = options_wo_strategy unless options.nil? io << indent << 'Options: ' dump_value(io, indent, options) io << "\n" end branches.each {|b| b.dump_on(io, indent, indent)} if @event == :result io << indent << 'Merged result: ' dump_value(io, indent, @value) io << "\n" end end def to_hash return branches[0].to_hash if branches.size == 1 hash = super hash[:merge] = @merge.class.key options = options_wo_strategy hash[:options] = options unless options.nil? hash end def type :merge end def options_wo_strategy options = @merge.options if !options.nil? && options.include?('strategy') options = options.dup options.delete('strategy') end options.empty? ? nil : options end end class ExplainGlobal < ExplainTreeNode def initialize(parent, binding_terminus) super(parent) @binding_terminus = binding_terminus end def dump_on(io, indent, first_indent) io << first_indent << 'Data Binding "' << @binding_terminus.to_s << "\"\n" indent = increase_indent(indent) branches.each {|b| b.dump_on(io, indent, indent)} dump_outcome(io, indent) end def to_hash hash = super hash[:name] = @binding_terminus hash end def type :global end end class ExplainDataProvider < ExplainTreeNode def initialize(parent, provider) super(parent) @provider = provider end def dump_on(io, indent, first_indent) io << first_indent << @provider.name << "\n" indent = increase_indent(indent) if @provider.respond_to?(:config_path) path = @provider.config_path io << indent << 'Using configuration "' << path.to_s << "\"\n" unless path.nil? end branches.each {|b| b.dump_on(io, indent, indent)} dump_outcome(io, indent) end def to_hash hash = super hash[:name] = @provider.name if @provider.respond_to?(:config_path) path = @provider.config_path hash[:configuration_path] = path.to_s unless path.nil? end hash[:module] = @provider.module_name if @provider.is_a?(ModuleDataProvider) hash end def type :data_provider end end class ExplainLocation < ExplainTreeNode def initialize(parent, location) super(parent) @location = location end def dump_on(io, indent, first_indent) location = @location.location type_name = type == :path ? 'Path' : 'URI' io << indent << type_name << ' "' << location.to_s << "\"\n" indent = increase_indent(indent) io << indent << 'Original ' << type_name.downcase << ': "' << @location.original_location << "\"\n" branches.each {|b| b.dump_on(io, indent, indent)} io << indent << type_name << " not found\n" if @event == :location_not_found dump_outcome(io, indent) end def to_hash hash = super location = @location.location if type == :path hash[:original_path] = @location.original_location hash[:path] = location.to_s else hash[:original_uri] = @location.original_location hash[:uri] = location.to_s end hash end def type @location.location.is_a?(Pathname) ? :path : :uri end end class ExplainSubLookup < ExplainTreeNode def initialize(parent, sub_key) super(parent) @sub_key = sub_key end def dump_on(io, indent, first_indent) io << indent << 'Sub key: "' << @sub_key.join('.') << "\"\n" indent = increase_indent(indent) branches.each {|b| b.dump_on(io, indent, indent)} dump_outcome(io, indent) end def type :sub_key end end class ExplainKeySegment < ExplainTreeNode def initialize(parent, segment) super(parent) @segment = segment end def dump_on(io, indent, first_indent) dump_outcome(io, indent) end def type :segment end end class ExplainScope < ExplainTreeNode def initialize(parent, name) super(parent) @name = name end def dump_on(io, indent, first_indent) io << indent << @name << "\n" indent = increase_indent(indent) branches.each {|b| b.dump_on(io, indent, indent)} dump_outcome(io, indent) end def to_hash hash = super hash[:name] = @name hash end def type :scope end end class Explainer < ExplainNode def initialize(explain_options = false, only_explain_options = false) @current = self @explain_options = explain_options @only_explain_options = only_explain_options end def push(qualifier_type, qualifier) node = case (qualifier_type) when :global ExplainGlobal.new(@current, qualifier) when :location ExplainLocation.new(@current, qualifier) when :interpolate ExplainInterpolate.new(@current, qualifier) when :data_provider ExplainDataProvider.new(@current, qualifier) when :merge ExplainMerge.new(@current, qualifier) when :module ExplainModule.new(@current, qualifier) when :scope ExplainScope.new(@current, qualifier) when :sub_lookup ExplainSubLookup.new(@current, qualifier) when :segment ExplainKeySegment.new(@current, qualifier) when :meta, :data ExplainTop.new(@current, qualifier_type, qualifier) when :invalid_key ExplainInvalidKey.new(@current, qualifier) else #TRANSLATORS 'Explain' is referring to the 'Explainer' class and should not be translated raise ArgumentError, _("Unknown Explain type %{qualifier_type}") % { qualifier_type: qualifier_type } end @current.branches << node @current = node end def only_explain_options? @only_explain_options end def explain_options? @explain_options end def pop @current = @current.parent unless @current.parent.nil? end def accept_found_in_overrides(key, value) @current.found_in_overrides(key, value) end def accept_found_in_defaults(key, value) @current.found_in_defaults(key, value) end def accept_found(key, value) @current.found(key, value) end def accept_merge_source(merge_source) @current.branches << ExplainMergeSource.new(merge_source) end def accept_not_found(key) @current.not_found(key) end def accept_location_not_found @current.location_not_found end def accept_module_not_found(module_name) push(:module, module_name) @current.module_not_found pop end def accept_module_provider_not_found(module_name) push(:module, module_name) @current.module_provider_not_found pop end def accept_result(result) @current.result(result) end def accept_text(text) @current.text(text) end def dump_on(io, indent, first_indent) branches.each { |b| b.dump_on(io, indent, first_indent) } dump_texts(io, indent) end def to_hash branches.size == 1 ? branches[0].to_hash : super end end class DebugExplainer < Explainer attr_reader :wrapped_explainer def initialize(wrapped_explainer) @wrapped_explainer = wrapped_explainer if wrapped_explainer.nil? @current = self @explain_options = false @only_explain_options = false else @current = wrapped_explainer @explain_options = wrapped_explainer.explain_options? @only_explain_options = wrapped_explainer.only_explain_options? end end def dump_on(io, indent, first_indent) @current.equal?(self) ? super : @current.dump_on(io, indent, first_indent) end def emit_debug_info(preamble) io = '' io << preamble << "\n" dump_on(io, ' ', ' ') Puppet.debug(io.chomp!) end end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/lookup/function_provider.rb�������������������������������������������0000644�0052762�0001160�00000006300�13417161721�023452� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require_relative 'data_adapter' require_relative 'context' require_relative 'data_provider' module Puppet::Pops module Lookup # @api private class FunctionProvider include DataProvider attr_reader :parent_data_provider, :function_name, :locations # Returns the type that all the return type of all functions must be assignable to. # For `lookup_key` and `data_dig`, that will be the `Puppet::LookupValue` type. For # `data_hash` it will be a Hash[Puppet::LookupKey,Puppet::LookupValue]` # # @return [Type] the trusted return type def self.trusted_return_type DataProvider.value_type end def initialize(name, parent_data_provider, function_name, options, locations) @name = name @parent_data_provider = parent_data_provider @function_name = function_name @options = options @locations = locations || [nil] @contexts = {} end # @return [FunctionContext] the function context associated with this provider def function_context(lookup_invocation, location) @contexts[location] ||= create_function_context(lookup_invocation) end def create_function_context(lookup_invocation) FunctionContext.new(EnvironmentContext.adapt(lookup_invocation.scope.compiler.environment), module_name, function(lookup_invocation)) end def module_name @parent_data_provider.module_name end def name "Hierarchy entry \"#{@name}\"" end def full_name "#{self.class::TAG} function '#{@function_name}'" end def to_s name end # Obtains the options to send to the function, optionally merged with a 'path' or 'uri' option # # @param [Pathname,URI] location The location to add to the options # @return [Hash{String => Object}] The options hash def options(location = nil) location = location.location unless location.nil? case location when Pathname @options.merge(HieraConfig::KEY_PATH => location.to_s) when URI @options.merge(HieraConfig::KEY_URI => location.to_s) else @options end end def value_is_validated? @value_is_validated end private def function(lookup_invocation) @function ||= load_function(lookup_invocation) end def load_function(lookup_invocation) loaders = lookup_invocation.scope.compiler.loaders typed_name = Loader::TypedName.new(:function, @function_name) loader = if typed_name.qualified? qualifier = typed_name.name_parts[0] qualifier == 'environment' ? loaders.private_environment_loader : loaders.private_loader_for_module(qualifier) else loaders.private_environment_loader end te = loader.load_typed(typed_name) if te.nil? || te.value.nil? @parent_data_provider.config(lookup_invocation).fail(Issues::HIERA_DATA_PROVIDER_FUNCTION_NOT_FOUND, :function_type => self.class::TAG, :function_name => @function_name) end func = te.value @value_is_validated = func.class.dispatcher.dispatchers.all? do |dispatcher| rt = dispatcher.type.return_type if rt.nil? false else Types::TypeAsserter.assert_assignable(nil, self.class.trusted_return_type, rt) { "Return type of '#{self.class::TAG}' function named '#{function_name}'" } true end end func end end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/lookup/global_data_provider.rb����������������������������������������0000644�0052762�0001160�00000005337�13417161721�024067� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'hiera/scope' require_relative 'configured_data_provider' module Puppet::Pops module Lookup # @api private class GlobalDataProvider < ConfiguredDataProvider def place 'Global' end def unchecked_key_lookup(key, lookup_invocation, merge) config = config(lookup_invocation) if(config.version == 3) # Hiera version 3 needs access to special scope variables scope = lookup_invocation.scope unless scope.is_a?(Hiera::Scope) return lookup_invocation.with_scope(Hiera::Scope.new(scope)) do |hiera_invocation| # Confine to global scope unless an environment data provider has been defined (same as for hiera_xxx functions) adapter = lookup_invocation.lookup_adapter hiera_invocation.set_global_only unless adapter.global_only? || adapter.has_environment_data_provider?(lookup_invocation) hiera_invocation.lookup(key, lookup_invocation.module_name) { unchecked_key_lookup(key , hiera_invocation, merge) } end end merge = MergeStrategy.strategy(merge) unless config.merge_strategy.is_a?(DefaultMergeStrategy) if lookup_invocation.hiera_xxx_call? && merge.is_a?(HashMergeStrategy) # Merge strategy defined in the hiera config only applies when the call stems from a hiera_hash call. merge = config.merge_strategy lookup_invocation.set_hiera_v3_merge_behavior end end value = super(key, lookup_invocation, merge) if lookup_invocation.hiera_xxx_call? if merge.is_a?(HashMergeStrategy) || merge.is_a?(DeepMergeStrategy) # hiera_hash calls should error when found values are not hashes Types::TypeAsserter.assert_instance_of('value', Types::PHashType::DEFAULT, value) end if !key.segments.nil? && (merge.is_a?(HashMergeStrategy) || merge.is_a?(UniqueMergeStrategy)) strategy = merge.is_a?(HashMergeStrategy) ? 'hash' : 'array' # Fail with old familiar message from Hiera 3 raise Puppet::DataBinding::LookupError, "Resolution type :#{strategy} is illegal when accessing values using dotted keys. Offending key was '#{key}'" end end value else super end end protected def assert_config_version(config) config.fail(Issues::HIERA_UNSUPPORTED_VERSION_IN_GLOBAL) if config.version == 4 config end # Return the root of the environment # # @param lookup_invocation [Invocation] The current lookup invocation # @return [Pathname] Path to the parent of the hiera configuration file def provider_root(lookup_invocation) configuration_path(lookup_invocation).parent end def configuration_path(lookup_invocation) lookup_invocation.global_hiera_config_path end end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/lookup/interpolation.rb�����������������������������������������������0000644�0052762�0001160�00000013022�13417161721�022601� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'hiera/scope' require_relative 'sub_lookup' module Puppet::Pops module Lookup # Adds support for interpolation expressions. The expressions may contain keys that uses dot-notation # to further navigate into hashes and arrays # # @api public module Interpolation include SubLookup # @param value [Object] The value to interpolate # @param context [Context] The current lookup context # @param allow_methods [Boolean] `true` if interpolation expression that contains lookup methods are allowed # @return [Object] the result of resolving all interpolations in the given value # @api public def interpolate(value, context, allow_methods) case value when String value.index('%{').nil? ? value : interpolate_string(value, context, allow_methods) when Array value.map { |element| interpolate(element, context, allow_methods) } when Hash result = {} value.each_pair { |k, v| result[interpolate(k, context, allow_methods)] = interpolate(v, context, allow_methods) } result else value end end private EMPTY_INTERPOLATIONS = { '' => true, '::' => true, '""' => true, "''" => true, '"::"' => true, "'::'" => true }.freeze # Matches a key that is quoted using a matching pair of either single or double quotes. QUOTED_KEY = /^(?:"([^"]+)"|'([^']+)')$/ def interpolate_string(subject, context, allow_methods) lookup_invocation = context.is_a?(Invocation) ? context : context.invocation lookup_invocation.with(:interpolate, subject) do subject.gsub(/%\{([^\}]*)\}/) do |match| expr = $1 # Leading and trailing spaces inside an interpolation expression are insignificant expr.strip! value = nil unless EMPTY_INTERPOLATIONS[expr] method_key, key = get_method_and_data(expr, allow_methods) is_alias = method_key == :alias # Alias is only permitted if the entire string is equal to the interpolate expression fail(Issues::HIERA_INTERPOLATION_ALIAS_NOT_ENTIRE_STRING) if is_alias && subject != match value = interpolate_method(method_key).call(key, lookup_invocation, subject) # break gsub and return value immediately if this was an alias substitution. The value might be something other than a String return value if is_alias value = lookup_invocation.check(method_key == :scope ? "scope:#{key}" : key) { interpolate(value, lookup_invocation, allow_methods) } end value.nil? ? '' : value end end end def interpolate_method(method_key) @@interpolate_methods ||= begin global_lookup = lambda do |key, lookup_invocation, _| scope = lookup_invocation.scope if scope.is_a?(Hiera::Scope) && !lookup_invocation.global_only? # "unwrap" the Hiera::Scope scope = scope.real end lookup_invocation.with_scope(scope) do |sub_invocation| sub_invocation.lookup(key) { Lookup.lookup(key, nil, '', true, nil, sub_invocation) } end end scope_lookup = lambda do |key, lookup_invocation, subject| segments = split_key(key) { |problem| Puppet::DataBinding::LookupError.new("#{problem} in string: #{subject}") } root_key = segments.shift value = lookup_invocation.with(:scope, 'Global Scope') do ovr = lookup_invocation.override_values if ovr.include?(root_key) lookup_invocation.report_found_in_overrides(root_key, ovr[root_key]) else scope = lookup_invocation.scope val = scope[root_key] if val.nil? && !nil_in_scope?(scope, root_key) defaults = lookup_invocation.default_values if defaults.include?(root_key) lookup_invocation.report_found_in_defaults(root_key, defaults[root_key]) else nil end else lookup_invocation.report_found(root_key, val) end end end unless value.nil? || segments.empty? found = nil; catch(:no_such_key) { found = sub_lookup(key, lookup_invocation, segments, value) } value = found; end lookup_invocation.remember_scope_lookup(key, root_key, segments, value) value end { :lookup => global_lookup, :hiera => global_lookup, # this is just an alias for 'lookup' :alias => global_lookup, # same as 'lookup' but expression must be entire string and result is not subject to string substitution :scope => scope_lookup, :literal => lambda { |key, _, _| key } }.freeze end interpolate_method = @@interpolate_methods[method_key] fail(Issues::HIERA_INTERPOLATION_UNKNOWN_INTERPOLATION_METHOD, :name => method_key) unless interpolate_method interpolate_method end # Because the semantics of Puppet::Parser::Scope#include? differs from Hash#include? def nil_in_scope?(scope, key) if scope.is_a?(Hash) scope.include?(key) else scope.exist?(key) end end def get_method_and_data(data, allow_methods) if match = data.match(/^(\w+)\((?:["]([^"]+)["]|[']([^']+)['])\)$/) fail(Issues::HIERA_INTERPOLATION_METHOD_SYNTAX_NOT_ALLOWED) unless allow_methods key = match[1].to_sym data = match[2] || match[3] # double or single qouted else key = :scope end [key, data] end def fail(issue, args = EMPTY_HASH) raise Puppet::DataBinding::LookupError.new( issue.format(args), nil, nil, nil, nil, issue.issue_code) end end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/lookup/invocation.rb��������������������������������������������������0000644�0052762�0001160�00000020153�13417161721�022066� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require_relative 'explainer' module Puppet::Pops module Lookup # @api private class Invocation attr_reader :scope, :override_values, :default_values, :explainer, :module_name, :top_key, :adapter_class def self.current @current end # Creates a new instance with same settings as this instance but with a new given scope # and yields with that scope. # # @param scope [Puppet::Parser::Scope] The new scope # @return [Invocation] the new instance def with_scope(scope) yield(Invocation.new(scope, override_values, default_values, explainer)) end # Creates a context object for a lookup invocation. The object contains the current scope, overrides, and default # values and may optionally contain an {ExplanationAcceptor} instance that will receive book-keeping information # about the progress of the lookup. # # If the _explain_ argument is a boolean, then _false_ means that no explanation is needed and _true_ means that # the default explanation acceptor should be used. The _explain_ argument may also be an instance of the # `ExplanationAcceptor` class. # # @param scope [Puppet::Parser::Scope] The scope to use for the lookup # @param override_values [Hash<String,Object>|nil] A map to use as override. Values found here are returned immediately (no merge) # @param default_values [Hash<String,Object>] A map to use as the last resort (but before default) # @param explainer [boolean,Explanainer] An boolean true to use the default explanation acceptor or an explainer instance that will receive information about the lookup def initialize(scope, override_values = EMPTY_HASH, default_values = EMPTY_HASH, explainer = nil, adapter_class = nil) @scope = scope @override_values = override_values @default_values = default_values parent_invocation = self.class.current if parent_invocation && (adapter_class.nil? || adapter_class == parent_invocation.adapter_class) # Inherit from parent invocation (track recursion) @name_stack = parent_invocation.name_stack @adapter_class = parent_invocation.adapter_class # Inherit Hiera 3 legacy properties set_hiera_xxx_call if parent_invocation.hiera_xxx_call? set_hiera_v3_merge_behavior if parent_invocation.hiera_v3_merge_behavior? set_global_only if parent_invocation.global_only? povr = parent_invocation.hiera_v3_location_overrides set_hiera_v3_location_overrides(povr) unless povr.empty? # Inherit explainer unless a new explainer is given or disabled using false explainer = explainer == false ? nil : parent_invocation.explainer else @name_stack = [] @adapter_class = adapter_class.nil? ? LookupAdapter : adapter_class unless explainer.is_a?(Explainer) explainer = explainer == true ? Explainer.new : nil end explainer = DebugExplainer.new(explainer) if Puppet[:debug] && !explainer.is_a?(DebugExplainer) end @explainer = explainer end def lookup(key, module_name = nil) key = LookupKey.new(key) unless key.is_a?(LookupKey) @top_key = key @module_name = module_name.nil? ? key.module_name : module_name save_current = self.class.current if save_current.equal?(self) yield else begin self.class.instance_variable_set(:@current, self) yield ensure self.class.instance_variable_set(:@current, save_current) end end end def check(name) if @name_stack.include?(name) raise Puppet::DataBinding::RecursiveLookupError, _("Recursive lookup detected in [%{name_stack}]") % { name_stack: @name_stack.join(', ') } end return unless block_given? @name_stack.push(name) begin yield rescue Puppet::DataBinding::LookupError raise rescue Puppet::Error => detail raise Puppet::DataBinding::LookupError.new(detail.message, nil, nil, nil, detail) ensure @name_stack.pop end end def emit_debug_info(preamble) @explainer.emit_debug_info(preamble) if @explainer.is_a?(DebugExplainer) end def lookup_adapter @adapter ||= @adapter_class.adapt(scope.compiler) end # This method is overridden by the special Invocation used while resolving interpolations in a # Hiera configuration file (hiera.yaml) where it's used for collecting and remembering the current # values that the configuration was based on # # @api private def remember_scope_lookup(*lookup_result) # Does nothing by default end # The qualifier_type can be one of: # :global - qualifier is the data binding terminus name # :data_provider - qualifier a DataProvider instance # :path - qualifier is a ResolvedPath instance # :merge - qualifier is a MergeStrategy instance # :interpolation - qualifier is the unresolved interpolation expression # :meta - qualifier is the module name # :data - qualifier is the key # # @param qualifier [Object] A branch, a provider, or a path def with(qualifier_type, qualifier) if explainer.nil? yield else @explainer.push(qualifier_type, qualifier) begin yield ensure @explainer.pop end end end def without_explain if explainer.nil? yield else save_explainer = @explainer begin @explainer = nil yield ensure @explainer = save_explainer end end end def only_explain_options? @explainer.nil? ? false : @explainer.only_explain_options? end def explain_options? @explainer.nil? ? false : @explainer.explain_options? end def report_found_in_overrides(key, value) @explainer.accept_found_in_overrides(key, value) unless @explainer.nil? value end def report_found_in_defaults(key, value) @explainer.accept_found_in_defaults(key, value) unless @explainer.nil? value end def report_found(key, value) @explainer.accept_found(key, value) unless @explainer.nil? value end def report_merge_source(merge_source) @explainer.accept_merge_source(merge_source) unless @explainer.nil? end # Report the result of a merge or fully resolved interpolated string # @param value [Object] The result to report # @return [Object] the given value def report_result(value) @explainer.accept_result(value) unless @explainer.nil? value end def report_not_found(key) @explainer.accept_not_found(key) unless @explainer.nil? end def report_location_not_found @explainer.accept_location_not_found unless @explainer.nil? end def report_module_not_found(module_name) @explainer.accept_module_not_found(module_name) unless @explainer.nil? end def report_module_provider_not_found(module_name) @explainer.accept_module_provider_not_found(module_name) unless @explainer.nil? end def report_text(&block) unless @explainer.nil? @explainer.accept_text(block.call) end end def global_only? lookup_adapter.global_only? || (instance_variable_defined?(:@global_only) ? @global_only : false) end # Instructs the lookup framework to only perform lookups in the global layer # @return [Invocation] self def set_global_only @global_only = true self end # @return [Pathname] the full path of the hiera.yaml config file def global_hiera_config_path lookup_adapter.global_hiera_config_path end # @return [Boolean] `true` if the invocation stems from the hiera_xxx function family def hiera_xxx_call? instance_variable_defined?(:@hiera_xxx_call) end def set_hiera_xxx_call @hiera_xxx_call = true end # @return [Boolean] `true` if the invocation stems from the hiera_xxx function family def hiera_v3_merge_behavior? instance_variable_defined?(:@hiera_v3_merge_behavior) end def set_hiera_v3_merge_behavior @hiera_v3_merge_behavior = true end # Overrides passed from hiera_xxx functions down to V3DataHashFunctionProvider def set_hiera_v3_location_overrides(overrides) @hiera_v3_location_overrides = [overrides].flatten unless overrides.nil? end def hiera_v3_location_overrides instance_variable_defined?(:@hiera_v3_location_overrides) ? @hiera_v3_location_overrides : EMPTY_ARRAY end protected def name_stack @name_stack.clone end end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/lookup/location_resolver.rb�������������������������������������������0000644�0052762�0001160�00000010234�13417161721�023445� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'pathname' require_relative 'interpolation' module Puppet::Pops module Lookup # Class that keeps track of the original location (as it appears in the declaration, before interpolation), # and the fully resolved location, and whether or not the resolved location exists. # # @api private class ResolvedLocation attr_reader :original_location, :location # @param original_location [String] location as found in declaration. May contain interpolation expressions # @param location [Pathname,URI] the expanded location # @param exist [Boolean] `true` if the location is assumed to exist # @api public def initialize(original_location, location, exist) @original_location = original_location @location = location @exist = exist end # @return [Boolean] `true` if the location is assumed to exist # @api public def exist? @exist end # @return the resolved location as a string def to_s @location.to_s end end # Helper methods to resolve interpolated locations # # @api private module LocationResolver include Interpolation def expand_globs(datadir, declared_globs, lookup_invocation) declared_globs.map do |declared_glob| glob = datadir + interpolate(declared_glob, lookup_invocation, false) Pathname.glob(glob).reject { |path| path.directory? }.map { |path| ResolvedLocation.new(glob.to_s, path, true) } end.flatten end # @param datadir [Pathname] The base when creating absolute paths # @param declared_paths [Array<String>] paths as found in declaration. May contain interpolation expressions # @param lookup_invocation [Puppet::Pops::Lookup::Invocation] The current lookup invocation # @param is_default_config [Boolean] `true` if this is the default config and non-existent paths should be excluded # @param extension [String] Required extension such as '.yaml' or '.json'. Use only if paths without extension can be expected # @return [Array<ResolvedLocation>] Array of resolved paths def resolve_paths(datadir, declared_paths, lookup_invocation, is_default_config, extension = nil) result = [] declared_paths.each do |declared_path| path = interpolate(declared_path, lookup_invocation, false) path += extension unless extension.nil? || path.end_with?(extension) path = datadir + path path_exists = path.exist? result << ResolvedLocation.new(declared_path, path, path_exists) unless is_default_config && !path_exists end result end # @param declared_uris [Array<String>] paths as found in declaration. May contain interpolation expressions # @param lookup_invocation [Puppet::Pops::Lookup::Invocation] The current lookup invocation # @return [Array<ResolvedLocation>] Array of resolved paths def expand_uris(declared_uris, lookup_invocation) declared_uris.map do |declared_uri| uri = URI(interpolate(declared_uri, lookup_invocation, false)) ResolvedLocation.new(declared_uri, uri, true) end end def expand_mapped_paths(datadir, mapped_path_triplet, lookup_invocation) # The scope interpolation method is used directly to avoid unnecessary parsing of the string that otherwise # would need to be generated mapped_vars = interpolate_method(:scope).call(mapped_path_triplet[0], lookup_invocation, 'mapped_path[0]') # No paths here unless the scope lookup returned something return EMPTY_ARRAY if mapped_vars.nil? || mapped_vars.empty? mapped_vars = [mapped_vars] if mapped_vars.is_a?(String) var_key = mapped_path_triplet[1] template = mapped_path_triplet[2] scope = lookup_invocation.scope lookup_invocation.with_local_memory_eluding(var_key) do mapped_vars.map do |var| # Need to use parent lookup invocation to avoid adding 'var' to the set of variables to track for changes. The # variable that 'var' stems from is added above. path = scope.with_local_scope(var_key => var) { datadir + interpolate(template, lookup_invocation, false) } ResolvedLocation.new(template, path, path.exist?) end end end end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/lookup/lookup_adapter.rb����������������������������������������������0000644�0052762�0001160�00000052010�13417161721�022723� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require_relative 'data_adapter' require_relative 'lookup_key' module Puppet::Pops module Lookup # A LookupAdapter is a specialized DataAdapter that uses its hash to store data providers. It also remembers the compiler # that it is attached to and maintains a cache of _lookup options_ retrieved from the data providers associated with the # compiler's environment. # # @api private class LookupAdapter < DataAdapter LOOKUP_OPTIONS_PREFIX = LOOKUP_OPTIONS + '.' LOOKUP_OPTIONS_PREFIX.freeze LOOKUP_OPTIONS_PATTERN_START = '^'.freeze HASH = 'hash'.freeze MERGE = 'merge'.freeze CONVERT_TO = 'convert_to'.freeze NEW = 'new'.freeze def self.create_adapter(compiler) new(compiler) end def initialize(compiler) super() @compiler = compiler @lookup_options = {} end # Performs a lookup using global, environment, and module data providers. Merge the result using the given # _merge_ strategy. If the merge strategy is nil, then an attempt is made to find merge options in the # `lookup_options` hash for an entry associated with the key. If no options are found, the no merge is performed # and the first found entry is returned. # # @param key [String] The key to lookup # @param lookup_invocation [Invocation] the lookup invocation # @param merge [MergeStrategy,String,Hash{String => Object},nil] Merge strategy, merge strategy name, strategy and options hash, or nil (implies "first found") # @return [Object] the found object # @throw :no_such_key when the object is not found # def lookup(key, lookup_invocation, merge) # The 'lookup_options' key is reserved and not found as normal data if key == LOOKUP_OPTIONS || key.start_with?(LOOKUP_OPTIONS_PREFIX) lookup_invocation.with(:invalid_key, LOOKUP_OPTIONS) do throw :no_such_key end end key = LookupKey.new(key) lookup_invocation.lookup(key, key.module_name) do if lookup_invocation.only_explain_options? catch(:no_such_key) { do_lookup(LookupKey::LOOKUP_OPTIONS, lookup_invocation, HASH) } nil else lookup_options = lookup_lookup_options(key, lookup_invocation) || {} if merge.nil? # Used cached lookup_options # merge = lookup_merge_options(key, lookup_invocation) merge = lookup_options[MERGE] lookup_invocation.report_merge_source(LOOKUP_OPTIONS) unless merge.nil? end convert_result(key.to_s, lookup_options, lookup_invocation, lambda do lookup_invocation.with(:data, key.to_s) do catch(:no_such_key) { return do_lookup(key, lookup_invocation, merge) } throw :no_such_key if lookup_invocation.global_only? key.dig(lookup_invocation, lookup_default_in_module(key, lookup_invocation)) end end) end end end # Performs a possible conversion of the result of calling `the_lookup` lambda # The conversion takes place if there is a 'convert_to' key in the lookup_options # If there is no conversion, the result of calling `the_lookup` is returned # otherwise the successfully converted value. # Errors are raised if the convert_to is faulty (bad type string, or if a call to # new(T, <args>) fails. # # @param key [String] The key to lookup # @param lookup_options [Hash] a hash of options # @param lookup_invocation [Invocation] the lookup invocation # @param the_lookup [Lambda] zero arg lambda that performs the lookup of a value # @return [Object] the looked up value, or converted value if there was conversion # @throw :no_such_key when the object is not found (if thrown by `the_lookup`) # def convert_result(key, lookup_options, lookup_invocation, the_lookup) result = the_lookup.call convert_to = lookup_options[CONVERT_TO] return result if convert_to.nil? convert_to = convert_to.is_a?(Array) ? convert_to : [convert_to] if convert_to[0].is_a?(String) begin convert_to[0] = Puppet::Pops::Types::TypeParser.singleton.parse(convert_to[0]) rescue StandardError => e raise Puppet::DataBinding::LookupError, _("Invalid data type in lookup_options for key '%{key}' could not parse '%{source}', error: '%{msg}") % { key: key, source: convert_to[0], msg: e.message} end end begin result = lookup_invocation.scope.call_function(NEW, [convert_to[0], result, *convert_to[1..-1]]) # TRANSLATORS 'lookup_options', 'convert_to' and args_string variable should not be translated, args_string = Puppet::Pops::Types::StringConverter.singleton.convert(convert_to) lookup_invocation.report_text { _("Applying convert_to lookup_option with arguments %{args}") % { args: args_string } } rescue StandardError => e raise Puppet::DataBinding::LookupError, _("The convert_to lookup_option for key '%{key}' raised error: %{msg}") % { key: key, msg: e.message} end result end def lookup_global(key, lookup_invocation, merge_strategy) # hiera_xxx will always use global_provider regardless of data_binding_terminus setting terminus = lookup_invocation.hiera_xxx_call? ? :hiera : Puppet[:data_binding_terminus] case terminus when :hiera, 'hiera' provider = global_provider(lookup_invocation) throw :no_such_key if provider.nil? provider.key_lookup(key, lookup_invocation, merge_strategy) when :none, 'none', '', nil # If global lookup is disabled, immediately report as not found lookup_invocation.report_not_found(key) throw :no_such_key else lookup_invocation.with(:global, terminus) do catch(:no_such_key) do return lookup_invocation.report_found(key, Puppet::DataBinding.indirection.find(key.root_key, {:environment => environment, :variables => lookup_invocation.scope, :merge => merge_strategy})) end lookup_invocation.report_not_found(key) throw :no_such_key end end rescue Puppet::DataBinding::LookupError => detail raise detail unless detail.issue_code.nil? error = Puppet::Error.new(_("Lookup of key '%{key}' failed: %{detail}") % { key: lookup_invocation.top_key, detail: detail.message }) error.set_backtrace(detail.backtrace) raise error end def lookup_in_environment(key, lookup_invocation, merge_strategy) provider = env_provider(lookup_invocation) throw :no_such_key if provider.nil? provider.key_lookup(key, lookup_invocation, merge_strategy) end def lookup_in_module(key, lookup_invocation, merge_strategy) module_name = lookup_invocation.module_name # Do not attempt to do a lookup in a module unless the name is qualified. throw :no_such_key if module_name.nil? provider = module_provider(lookup_invocation, module_name) if provider.nil? if environment.module(module_name).nil? lookup_invocation.report_module_not_found(module_name) else lookup_invocation.report_module_provider_not_found(module_name) end throw :no_such_key end provider.key_lookup(key, lookup_invocation, merge_strategy) end def lookup_default_in_module(key, lookup_invocation) module_name = lookup_invocation.module_name # Do not attempt to do a lookup in a module unless the name is qualified. throw :no_such_key if module_name.nil? provider = module_provider(lookup_invocation, module_name) throw :no_such_key if provider.nil? || !provider.config(lookup_invocation).has_default_hierarchy? lookup_invocation.with(:scope, "Searching default_hierarchy of module \"#{module_name}\"") do merge_strategy = nil if merge_strategy.nil? @module_default_lookup_options ||= {} options = @module_default_lookup_options.fetch(module_name) do |k| meta_invocation = Invocation.new(lookup_invocation.scope) meta_invocation.lookup(LookupKey::LOOKUP_OPTIONS, k) do opts = nil lookup_invocation.with(:scope, "Searching for \"#{LookupKey::LOOKUP_OPTIONS}\"") do catch(:no_such_key) do opts = compile_patterns( validate_lookup_options( provider.key_lookup_in_default(LookupKey::LOOKUP_OPTIONS, meta_invocation, MergeStrategy.strategy(HASH)), k)) end end @module_default_lookup_options[k] = opts end end lookup_options = extract_lookup_options_for_key(key, options) merge_strategy = lookup_options[MERGE] unless lookup_options.nil? end lookup_invocation.with(:scope, "Searching for \"#{key}\"") do provider.key_lookup_in_default(key, lookup_invocation, merge_strategy) end end end # Retrieve the merge options that match the given `name`. # # @param key [LookupKey] The key for which we want merge options # @param lookup_invocation [Invocation] the lookup invocation # @return [String,Hash,nil] The found merge options or nil # def lookup_merge_options(key, lookup_invocation) lookup_options = lookup_lookup_options(key, lookup_invocation) lookup_options.nil? ? nil : lookup_options[MERGE] end # Retrieve the lookup options that match the given `name`. # # @param key [LookupKey] The key for which we want lookup options # @param lookup_invocation [Puppet::Pops::Lookup::Invocation] the lookup invocation # @return [String,Hash,nil] The found lookup options or nil # def lookup_lookup_options(key, lookup_invocation) module_name = key.module_name # Retrieve the options for the module. We use nil as a key in case we have no module if !@lookup_options.include?(module_name) options = retrieve_lookup_options(module_name, lookup_invocation, MergeStrategy.strategy(HASH)) @lookup_options[module_name] = options else options = @lookup_options[module_name] end extract_lookup_options_for_key(key, options) end def extract_lookup_options_for_key(key, options) return nil if options.nil? rk = key.root_key key_opts = options[0] unless key_opts.nil? key_opt = key_opts[rk] return key_opt unless key_opt.nil? end patterns = options[1] patterns.each_pair { |pattern, value| return value if pattern =~ rk } unless patterns.nil? nil end # @param lookup_invocation [Puppet::Pops::Lookup::Invocation] the lookup invocation # @return [Boolean] `true` if an environment data provider version 5 is configured def has_environment_data_provider?(lookup_invocation) ep = env_provider(lookup_invocation) ep.nil? ? false : ep.config(lookup_invocation).version >= 5 end # @return [Pathname] the full path of the hiera.yaml config file def global_hiera_config_path @global_hiera_config_path ||= Pathname.new(Puppet.settings[:hiera_config]) end # @param path [String] the absolute path name of the global hiera.yaml file. # @return [LookupAdapter] self def set_global_hiera_config_path(path) @global_hiera_config_path = Pathname.new(path) self end def global_only? instance_variable_defined?(:@global_only) ? @global_only : false end # Instructs the lookup framework to only perform lookups in the global layer # @return [LookupAdapter] self def set_global_only @global_only = true self end private PROVIDER_STACK = [:lookup_global, :lookup_in_environment, :lookup_in_module].freeze def validate_lookup_options(options, module_name) raise Puppet::DataBinding::LookupError.new(_("value of %{opts} must be a hash") % { opts: LOOKUP_OPTIONS }) unless options.is_a?(Hash) unless options.nil? return options if module_name.nil? pfx = "#{module_name}::" options.each_pair do |key, value| if key.start_with?(LOOKUP_OPTIONS_PATTERN_START) unless key[1..pfx.length] == pfx raise Puppet::DataBinding::LookupError.new(_("all %{opts} patterns must match a key starting with module name '%{module_name}'") % { opts: LOOKUP_OPTIONS, module_name: module_name }) end else unless key.start_with?(pfx) raise Puppet::DataBinding::LookupError.new(_("all %{opts} keys must start with module name '%{module_name}'") % { opts: LOOKUP_OPTIONS, module_name: module_name }) end end end end def compile_patterns(options) return nil if options.nil? key_options = {} pattern_options = {} options.each_pair do |key, value| if key.start_with?(LOOKUP_OPTIONS_PATTERN_START) pattern_options[Regexp.compile(key)] = value else key_options[key] = value end end [key_options.empty? ? nil : key_options, pattern_options.empty? ? nil : pattern_options] end def do_lookup(key, lookup_invocation, merge) if lookup_invocation.global_only? key.dig(lookup_invocation, lookup_global(key, lookup_invocation, merge)) else merge_strategy = Puppet::Pops::MergeStrategy.strategy(merge) key.dig(lookup_invocation, merge_strategy.lookup(PROVIDER_STACK, lookup_invocation) { |m| send(m, key, lookup_invocation, merge_strategy) }) end end GLOBAL_ENV_MERGE = 'Global and Environment'.freeze # Retrieve lookup options that applies when using a specific module (i.e. a merge of the pre-cached # `env_lookup_options` and the module specific data) def retrieve_lookup_options(module_name, lookup_invocation, merge_strategy) meta_invocation = Invocation.new(lookup_invocation.scope) meta_invocation.lookup(LookupKey::LOOKUP_OPTIONS, lookup_invocation.module_name) do meta_invocation.with(:meta, LOOKUP_OPTIONS) do if meta_invocation.global_only? compile_patterns(global_lookup_options(meta_invocation, merge_strategy)) else opts = env_lookup_options(meta_invocation, merge_strategy) unless module_name.nil? # Store environment options at key nil. This removes the need for an additional lookup for keys that are not prefixed. @lookup_options[nil] = compile_patterns(opts) unless @lookup_options.include?(nil) catch(:no_such_key) do module_opts = validate_lookup_options(lookup_in_module(LookupKey::LOOKUP_OPTIONS, meta_invocation, merge_strategy), module_name) opts = if opts.nil? module_opts else merge_strategy.lookup([GLOBAL_ENV_MERGE, "Module #{lookup_invocation.module_name}"], meta_invocation) do |n| meta_invocation.with(:scope, n) { meta_invocation.report_found(LOOKUP_OPTIONS, n == GLOBAL_ENV_MERGE ? opts : module_opts) } end end end end compile_patterns(opts) end end end end # Retrieve and cache the global lookup options def global_lookup_options(lookup_invocation, merge_strategy) if !instance_variable_defined?(:@global_lookup_options) @global_lookup_options = nil catch(:no_such_key) { @global_lookup_options = validate_lookup_options(lookup_global(LookupKey::LOOKUP_OPTIONS, lookup_invocation, merge_strategy), nil) } end @global_lookup_options end # Retrieve and cache lookup options specific to the environment of the compiler that this adapter is attached to (i.e. a merge # of global and environment lookup options). def env_lookup_options(lookup_invocation, merge_strategy) if !instance_variable_defined?(:@env_lookup_options) global_options = global_lookup_options(lookup_invocation, merge_strategy) @env_only_lookup_options = nil catch(:no_such_key) { @env_only_lookup_options = validate_lookup_options(lookup_in_environment(LookupKey::LOOKUP_OPTIONS, lookup_invocation, merge_strategy), nil) } if global_options.nil? @env_lookup_options = @env_only_lookup_options elsif @env_only_lookup_options.nil? @env_lookup_options = global_options else @env_lookup_options = merge_strategy.merge(global_options, @env_only_lookup_options) end end @env_lookup_options end def global_provider(lookup_invocation) @global_provider = GlobalDataProvider.new unless instance_variable_defined?(:@global_provider) @global_provider end def env_provider(lookup_invocation) @env_provider = initialize_env_provider(lookup_invocation) unless instance_variable_defined?(:@env_provider) @env_provider end def module_provider(lookup_invocation, module_name) # Test if the key is present for the given module_name. It might be there even if the # value is nil (which indicates that no module provider is configured for the given name) unless self.include?(module_name) self[module_name] = initialize_module_provider(lookup_invocation, module_name) end self[module_name] end def initialize_module_provider(lookup_invocation, module_name) mod = environment.module(module_name) return nil if mod.nil? metadata = mod.metadata provider_name = metadata.nil? ? nil : metadata['data_provider'] mp = nil if mod.has_hiera_conf? mp = ModuleDataProvider.new(module_name) # A version 5 hiera.yaml trumps a data provider setting in the module mp_config = mp.config(lookup_invocation) if mp_config.nil? mp = nil elsif mp_config.version >= 5 unless provider_name.nil? || Puppet[:strict] == :off Puppet.warn_once('deprecations', "metadata.json#data_provider-#{module_name}", _("Defining \"data_provider\": \"%{name}\" in metadata.json is deprecated. It is ignored since a '%{config}' with version >= 5 is present") % { name: provider_name, config: HieraConfig::CONFIG_FILE_NAME }, mod.metadata_file) end provider_name = nil end end if provider_name.nil? mp else unless Puppet[:strict] == :off msg = _("Defining \"data_provider\": \"%{name}\" in metadata.json is deprecated.") % { name: provider_name } msg += " " + _("A '%{hiera_config}' file should be used instead") % { hiera_config: HieraConfig::CONFIG_FILE_NAME } if mp.nil? Puppet.warn_once('deprecations', "metadata.json#data_provider-#{module_name}", msg, mod.metadata_file) end case provider_name when 'none' nil when 'hiera' mp || ModuleDataProvider.new(module_name) when 'function' mp = ModuleDataProvider.new(module_name) mp.config = HieraConfig.v4_function_config(Pathname(mod.path), "#{module_name}::data", mp) mp else raise Puppet::Error.new(_("Environment '%{env}', cannot find module_data_provider '%{provider}'")) % { env: environment.name, provider: provider_name } end end end def initialize_env_provider(lookup_invocation) env_conf = environment.configuration return nil if env_conf.nil? || env_conf.path_to_env.nil? # Get the name of the data provider from the environment's configuration provider_name = env_conf.environment_data_provider env_path = Pathname(env_conf.path_to_env) config_path = env_path + HieraConfig::CONFIG_FILE_NAME ep = nil if config_path.exist? ep = EnvironmentDataProvider.new # A version 5 hiera.yaml trumps any data provider setting in the environment.conf ep_config = ep.config(lookup_invocation) if ep_config.nil? ep = nil elsif ep_config.version >= 5 unless provider_name.nil? || Puppet[:strict] == :off Puppet.warn_once('deprecations', 'environment.conf#data_provider', _("Defining environment_data_provider='%{provider_name}' in environment.conf is deprecated") % { provider_name: provider_name }, env_path + 'environment.conf') unless provider_name == 'hiera' Puppet.warn_once('deprecations', 'environment.conf#data_provider_overridden', _("The environment_data_provider='%{provider_name}' setting is ignored since '%{config_path}' version >= 5") % { provider_name: provider_name, config_path: config_path }, env_path + 'environment.conf') end end provider_name = nil end end if provider_name.nil? ep else unless Puppet[:strict] == :off msg = _("Defining environment_data_provider='%{provider_name}' in environment.conf is deprecated.") % { provider_name: provider_name } msg += " " + _("A '%{hiera_config}' file should be used instead") % { hiera_config: HieraConfig::CONFIG_FILE_NAME } if ep.nil? Puppet.warn_once('deprecations', 'environment.conf#data_provider', msg, env_path + 'environment.conf') end case provider_name when 'none' nil when 'hiera' # Use hiera.yaml or default settings if it is missing ep || EnvironmentDataProvider.new when 'function' ep = EnvironmentDataProvider.new ep.config = HieraConfigV5.v4_function_config(env_path, 'environment::data', ep) ep else raise Puppet::Error.new(_("Environment '%{env}', cannot find environment_data_provider '%{provider}'") % { env: environment.name, provider: provider_name }) end end end # @return [Puppet::Node::Environment] the environment of the compiler that this adapter is associated with def environment @compiler.environment end end end end require_relative 'invocation' require_relative 'global_data_provider' require_relative 'environment_data_provider' require_relative 'module_data_provider' ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/lookup/lookup_key.rb��������������������������������������������������0000644�0052762�0001160�00000005172�13417161721�022102� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require_relative 'sub_lookup' module Puppet::Pops module Lookup # @api private class LookupKey include SubLookup attr_reader :module_name, :root_key, :segments def initialize(key) segments = split_key(key) { |problem| Puppet::DataBinding::LookupError.new(_("%{problem} in key: '%{key}'") % { problem: problem, key: key }) } root_key = segments.shift.freeze qual_index = root_key.index(DOUBLE_COLON) @key = key @module_name = qual_index.nil? ? nil : root_key[0..qual_index-1].freeze @root_key = root_key @segments = segments.empty? ? nil : segments.freeze end def dig(lookup_invocation, value) @segments.nil? ? value : sub_lookup(@key, lookup_invocation, @segments, value) end # Prunes a found root value with respect to subkeys in this key. The given _value_ is returned untouched # if this key has no subkeys. Otherwise an attempt is made to create a Hash or Array that contains only the # path to the appointed value and that value. # # If subkeys exists and no value is found, then this method will return `nil`, an empty `Array` or an empty `Hash` # to enable further merges to be applied. The returned type depends on the given _value_. # # @param value [Object] the value to prune # @return the possibly pruned value def prune(value) if @segments.nil? value else pruned = @segments.reduce(value) do |memo, segment| memo.is_a?(Hash) || memo.is_a?(Array) && segment.is_a?(Integer) ? memo[segment] : nil end if pruned.nil? case value when Hash EMPTY_HASH when Array EMPTY_ARRAY else nil end else undig(pruned) end end end # Create a structure that can be dug into using the subkeys of this key in order to find the # given _value_. If this key has no subkeys, the _value_ is returned. # # @param value [Object] the value to wrap in a structure in case this value has subkeys # @return [Object] the possibly wrapped value def undig(value) @segments.nil? ? value : segments.reverse.reduce(value) do |memo, segment| if segment.is_a?(Integer) x = [] x[segment] = memo else x = { segment => memo } end x end end def to_a unless instance_variable_defined?(:@all_segments) a = [@root_key] a += @segments unless @segments.nil? @all_segments = a.freeze end @all_segments end def eql?(v) v.is_a?(LookupKey) && @key == v.to_s end alias == eql? def hash @key.hash end def to_s @key end LOOKUP_OPTIONS = LookupKey.new('lookup_options') end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/lookup/lookup_key_function_provider.rb��������������������������������0000644�0052762�0001160�00000006214�13417161721�025717� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require_relative 'function_provider' module Puppet::Pops module Lookup # @api private class LookupKeyFunctionProvider < FunctionProvider TAG = 'lookup_key'.freeze # Performs a lookup with the assumption that a recursive check has been made. # # @param key [LookupKey] The key to lookup # @param lookup_invocation [Invocation] The current lookup invocation # @param merge [MergeStrategy,String,Hash{String => Object},nil] Merge strategy, merge strategy name, strategy and options hash, or nil (implies "first found") # @return [Object] the found object # @throw :no_such_key when the object is not found def unchecked_key_lookup(key, lookup_invocation, merge) root_key = key.root_key lookup_invocation.with(:data_provider, self) do MergeStrategy.strategy(merge).lookup(locations, lookup_invocation) do |location| invoke_with_location(lookup_invocation, location, root_key, merge) end end end def invoke_with_location(lookup_invocation, location, root_key, merge) if location.nil? value = lookup_key(root_key, lookup_invocation, nil, merge) lookup_invocation.report_found(root_key, value) else lookup_invocation.with(:location, location) do value = lookup_key(root_key, lookup_invocation, location, merge) lookup_invocation.report_found(root_key, value) end end end def label 'Lookup Key' end private def lookup_key(key, lookup_invocation, location, merge) unless location.nil? || location.exist? lookup_invocation.report_location_not_found throw :no_such_key end ctx = function_context(lookup_invocation, location) ctx.data_hash ||= {} catch(:no_such_key) do hash = ctx.data_hash unless hash.include?(key) hash[key] = validate_data_value(ctx.function.call(lookup_invocation.scope, key, options(location), Context.new(ctx, lookup_invocation))) do msg = "Value for key '#{key}', returned from #{full_name}" location.nil? ? msg : "#{msg}, when using location '#{location}'," end end return hash[key] end lookup_invocation.report_not_found(key) throw :no_such_key end end # @api private class V3LookupKeyFunctionProvider < LookupKeyFunctionProvider TAG = 'v3_lookup_key'.freeze def initialize(name, parent_data_provider, function_name, options, locations) @datadir = options.delete(HieraConfig::KEY_DATADIR) super end def unchecked_key_lookup(key, lookup_invocation, merge) extra_paths = lookup_invocation.hiera_v3_location_overrides if extra_paths.nil? || extra_paths.empty? super else # Extra paths provided. Must be resolved and placed in front of known paths paths = parent_data_provider.config(lookup_invocation).resolve_paths(@datadir, extra_paths, lookup_invocation, false, ".#{@name}") all_locations = paths + locations root_key = key.root_key lookup_invocation.with(:data_provider, self) do MergeStrategy.strategy(merge).lookup(all_locations, lookup_invocation) do |location| invoke_with_location(lookup_invocation, location, root_key, merge) end end end end end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/lookup/module_data_provider.rb����������������������������������������0000644�0052762�0001160�00000005623�13417161721�024112� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require_relative 'configured_data_provider' module Puppet::Pops module Lookup # @api private class ModuleDataProvider < ConfiguredDataProvider attr_reader :module_name def initialize(module_name, config = nil) super(config) @module_name = module_name end def place 'Module' end # Performs a lookup using a module default hierarchy with an endless recursion check. # # @param key [LookupKey] The key to lookup # @param lookup_invocation [Invocation] The current lookup invocation # @param merge [MergeStrategy,String,Hash{String=>Object},nil] Merge strategy or hash with strategy and options # def key_lookup_in_default(key, lookup_invocation, merge) dps = config(lookup_invocation).configured_data_providers(lookup_invocation, self, true) if dps.empty? lookup_invocation.report_not_found(key) throw :no_such_key end merge_strategy = MergeStrategy.strategy(merge) lookup_invocation.check(key.to_s) do lookup_invocation.with(:data_provider, self) do merge_strategy.lookup(dps, lookup_invocation) do |data_provider| data_provider.unchecked_key_lookup(key, lookup_invocation, merge_strategy) end end end end # Asserts that all keys in the given _data_hash_ are prefixed with the configured _module_name_. Removes entries # that does not follow the convention and logs a warning. # # @param data_hash [Hash] The data hash # @return [Hash] The possibly pruned hash def validate_data_hash(data_hash) super module_prefix = "#{module_name}::" data_hash.each_key.reduce(data_hash) do |memo, k| next memo if k == LOOKUP_OPTIONS || k.start_with?(module_prefix) msg = "#{yield} must use keys qualified with the name of the module" memo = memo.clone if memo.equal?(data_hash) memo.delete(k) Puppet.warning("Module '#{module_name}': #{msg}") memo end data_hash end protected def assert_config_version(config) if config.version > 3 config else if Puppet[:strict] == :error config.fail(Issues::HIERA_VERSION_3_NOT_GLOBAL, :where => 'module') else Puppet.warn_once(:hiera_v3_at_module_root, config.config_path, _('hiera.yaml version 3 found at module root was ignored'), config.config_path) end nil end end # Return the root of the module with the name equal to the configured module name # # @param lookup_invocation [Invocation] The current lookup invocation # @return [Pathname] Path to root of the module # @raise [Puppet::DataBinding::LookupError] if the module can not be found # def provider_root(lookup_invocation) env = lookup_invocation.scope.environment mod = env.module(module_name) raise Puppet::DataBinding::LookupError, _("Environment '%{env}', cannot find module '%{module_name}'") % { env: env.name, module_name: module_name } unless mod Pathname.new(mod.path) end end end end �������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/lookup/sub_lookup.rb��������������������������������������������������0000644�0052762�0001160�00000007056�13417161721�022106� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Lookup module SubLookup SPECIAL = /['"\.]/ # Split key into segments. A segment may be a quoted string (both single and double quotes can # be used) and the segment separator is the '.' character. Whitespace will be trimmed off on # both sides of each segment. Whitespace within quotes are not trimmed. # # If the key cannot be parsed, this method will yield a string describing the problem to a one # parameter block. The block must return an exception instance. # # @param key [String] the string to split # @return [Array<String>] the array of segments # @yieldparam problem [String] the problem, i.e. 'Syntax error' # @yieldreturn [Exception] the exception to raise # # @api public def split_key(key) return [key] if key.match(SPECIAL).nil? segments = key.split(/(\s*"[^"]+"\s*|\s*'[^']+'\s*|[^'".]+)/) if segments.empty? # Only happens if the original key was an empty string raise yield('Syntax error') elsif segments.shift == '' count = segments.size raise yield('Syntax error') unless count > 0 segments.keep_if { |seg| seg != '.' } raise yield('Syntax error') unless segments.size * 2 == count + 1 segments.map! do |segment| segment.strip! if segment.start_with?('"') || segment.start_with?("'") segment[1..-2] elsif segment =~ /^(:?[+-]?[0-9]+)$/ segment.to_i else segment end end else raise yield('Syntax error') end end # Perform a sub-lookup using the given _segments_ to access the given _value_. Each segment must be a string. A string # consisting entirely of digits will be treated as an indexed lookup which means that the value that it is applied to # must be an array. Other types of segments will expect that the given value is something other than a String that # implements the '#[]' method. # # @param key [String] the original key (only used for error messages) # @param context [Context] The current lookup context # @param segments [Array<String>] the segments to use for lookup # @param value [Object] the value to access using the segments # @return [Object] the value obtained when accessing the value # # @api public def sub_lookup(key, context, segments, value) lookup_invocation = context.is_a?(Invocation) ? context : context.invocation lookup_invocation.with(:sub_lookup, segments) do segments.each do |segment| lookup_invocation.with(:segment, segment) do if value.nil? lookup_invocation.report_not_found(segment) throw :no_such_key end if segment.is_a?(Integer) && value.instance_of?(Array) unless segment >= 0 && segment < value.size lookup_invocation.report_not_found(segment) throw :no_such_key end else unless value.respond_to?(:'[]') && !(value.is_a?(Array) || value.instance_of?(String)) raise Puppet::DataBinding::LookupError, _("Data Provider type mismatch: Got %{klass} when a hash-like object was expected to access value using '%{segment}' from key '%{key}'") % { klass: value.class.name, segment: segment, key: key } end unless value.include?(segment) lookup_invocation.report_not_found(segment) throw :no_such_key end end value = value[segment] lookup_invocation.report_found(segment, value) end end value end end end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/lookup/hiera_config.rb������������������������������������������������0000644�0052762�0001160�00000070742�13417161721�022343� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require_relative 'data_dig_function_provider' require_relative 'data_hash_function_provider' require_relative 'lookup_key_function_provider' require_relative 'location_resolver' module Puppet::Pops module Lookup # @api private class ScopeLookupCollectingInvocation < Invocation def initialize(scope) super(scope) @scope_interpolations = [] end def remember_scope_lookup(key, root_key, segments, value) @scope_interpolations << [key, root_key, segments, value] unless !value.nil? && key.start_with?('::') end def scope_interpolations # Save extra checks by keeping the array unique with respect to the key (first entry) @scope_interpolations.uniq! { |si| si[0] } @scope_interpolations end # Yield invocation that remembers all but the given name def with_local_memory_eluding(name) save_si = @scope_interpolations @scope_interpolations = [] result = yield save_si.concat(@scope_interpolations.reject { |entry| entry[1] == name }) @scope_interpolations = save_si result end end # @api private class HieraConfig include LocationResolver include LabelProvider CONFIG_FILE_NAME = 'hiera.yaml' KEY_NAME = 'name'.freeze KEY_VERSION = 'version'.freeze KEY_DATADIR = 'datadir'.freeze KEY_DEFAULT_HIERARCHY = 'default_hierarchy'.freeze KEY_HIERARCHY = 'hierarchy'.freeze KEY_LOGGER = 'logger'.freeze KEY_OPTIONS = 'options'.freeze KEY_PATH = 'path'.freeze KEY_PATHS = 'paths'.freeze KEY_MAPPED_PATHS = 'mapped_paths'.freeze KEY_GLOB = 'glob'.freeze KEY_GLOBS = 'globs'.freeze KEY_URI = 'uri'.freeze KEY_URIS = 'uris'.freeze KEY_DEFAULTS = 'defaults'.freeze KEY_DATA_HASH = DataHashFunctionProvider::TAG KEY_LOOKUP_KEY = LookupKeyFunctionProvider::TAG KEY_DATA_DIG = DataDigFunctionProvider::TAG KEY_V3_DATA_HASH = V3DataHashFunctionProvider::TAG KEY_V3_LOOKUP_KEY = V3LookupKeyFunctionProvider::TAG KEY_V3_BACKEND = V3BackendFunctionProvider::TAG KEY_V4_DATA_HASH = V4DataHashFunctionProvider::TAG KEY_BACKEND = 'backend'.freeze KEY_EXTENSION = 'extension'.freeze FUNCTION_KEYS = [KEY_DATA_HASH, KEY_LOOKUP_KEY, KEY_DATA_DIG, KEY_V3_BACKEND] ALL_FUNCTION_KEYS = FUNCTION_KEYS + [KEY_V4_DATA_HASH] LOCATION_KEYS = [KEY_PATH, KEY_PATHS, KEY_GLOB, KEY_GLOBS, KEY_URI, KEY_URIS, KEY_MAPPED_PATHS] FUNCTION_PROVIDERS = { KEY_DATA_HASH => DataHashFunctionProvider, KEY_DATA_DIG => DataDigFunctionProvider, KEY_LOOKUP_KEY => LookupKeyFunctionProvider, KEY_V3_DATA_HASH => V3DataHashFunctionProvider, KEY_V3_BACKEND => V3BackendFunctionProvider, KEY_V3_LOOKUP_KEY => V3LookupKeyFunctionProvider, KEY_V4_DATA_HASH => V4DataHashFunctionProvider } def self.v4_function_config(config_root, function_name, owner) unless Puppet[:strict] == :off Puppet.warn_once('deprecations', 'legacy_provider_function', _("Using of legacy data provider function '%{function_name}'. Please convert to a 'data_hash' function") % { function_name: function_name }) end HieraConfigV5.new(config_root, nil, { KEY_VERSION => 5, KEY_HIERARCHY => [ { KEY_NAME => "Legacy function '#{function_name}'", KEY_V4_DATA_HASH => function_name } ] }.freeze, owner ) end def self.config_exist?(config_root) config_path = config_root + CONFIG_FILE_NAME config_path.exist? end def self.symkeys_to_string(struct) case(struct) when Hash map = {} struct.each_pair {|k,v| map[ k.is_a?(Symbol) ? k.to_s : k] = symkeys_to_string(v) } map when Array struct.map { |v| symkeys_to_string(v) } else struct end end # Creates a new HieraConfig from the given _config_root_. This is where the 'hiera.yaml' is expected to be found # and is also the base location used when resolving relative paths. # # @param lookup_invocation [Invocation] Invocation data containing scope, overrides, and defaults # @param config_path [Pathname] Absolute path to the configuration file # @param owner [ConfiguredDataProvider] The data provider that will own the created configuration # @return [LookupConfiguration] the configuration def self.create(lookup_invocation, config_path, owner) if config_path.is_a?(Hash) config_path = nil loaded_config = config_path else config_root = config_path.parent if config_path.exist? env_context = EnvironmentContext.adapt(lookup_invocation.scope.compiler.environment) loaded_config = env_context.cached_file_data(config_path) do |content| parsed = YAML.load(content, config_path) # For backward compatibility, we must treat an empty file, or a yaml that doesn't # produce a Hash as Hiera version 3 default. if parsed.is_a?(Hash) parsed else Puppet.warning(_("%{config_path}: File exists but does not contain a valid YAML hash. Falling back to Hiera version 3 default config") % { config_path: config_path }) HieraConfigV3::DEFAULT_CONFIG_HASH end end else config_path = nil loaded_config = HieraConfigV5::DEFAULT_CONFIG_HASH end end version = loaded_config[KEY_VERSION] || loaded_config[:version] version = version.nil? ? 3 : version.to_i case version when 5 HieraConfigV5.new(config_root, config_path, loaded_config, owner) when 4 HieraConfigV4.new(config_root, config_path, loaded_config, owner) when 3 HieraConfigV3.new(config_root, config_path, loaded_config, owner) else issue = Issues::HIERA_UNSUPPORTED_VERSION raise Puppet::DataBinding::LookupError.new( issue.format(:version => version), config_path, nil, nil, nil, issue.issue_code) end end attr_reader :config_path, :version # Creates a new HieraConfig from the given _config_root_. This is where the 'lookup.yaml' is expected to be found # and is also the base location used when resolving relative paths. # # @param config_path [Pathname] Absolute path to the configuration # @param loaded_config [Hash] the loaded configuration def initialize(config_root, config_path, loaded_config, owner) @config_root = config_root @config_path = config_path @loaded_config = loaded_config @config = validate_config(self.class.symkeys_to_string(@loaded_config), owner) @data_providers = nil end def fail(issue, args = EMPTY_HASH, line = nil) raise Puppet::DataBinding::LookupError.new( issue.format(args.merge(:label => self)), @config_path, line, nil, nil, issue.issue_code) end def has_default_hierarchy? false end # Returns the data providers for this config # # @param lookup_invocation [Invocation] Invocation data containing scope, overrides, and defaults # @param parent_data_provider [DataProvider] The data provider that loaded this configuration # @return [Array<DataProvider>] the data providers def configured_data_providers(lookup_invocation, parent_data_provider, use_default_hierarchy = false) unless @data_providers && scope_interpolations_stable?(lookup_invocation) if @data_providers lookup_invocation.report_text { _('Hiera configuration recreated due to change of scope variables used in interpolation expressions') } end slc_invocation = ScopeLookupCollectingInvocation.new(lookup_invocation.scope) begin @data_providers = create_configured_data_providers(slc_invocation, parent_data_provider, false) if has_default_hierarchy? @default_data_providers = create_configured_data_providers(slc_invocation, parent_data_provider, true) end rescue StandardError => e # Raise a LookupError with a RUNTIME_ERROR issue to prevent this being translated to an evaluation error triggered in the pp file # where the lookup started if e.message =~ /^Undefined variable '([^']+)'/ var = $1 fail(Issues::HIERA_UNDEFINED_VARIABLE, { :name => var }, find_line_matching(/%\{['"]?#{var}['"]?}/)) end raise e end @scope_interpolations = slc_invocation.scope_interpolations end use_default_hierarchy ? @default_data_providers : @data_providers end # Find first line in configuration that matches regexp after given line. Comments are stripped def find_line_matching(regexp, start_line = 1) line_number = 0 File.foreach(@config_path) do |line| line_number += 1 next if line_number < start_line quote = nil stripped = '' line.each_codepoint do |cp| if cp == 0x22 || cp == 0x27 # double or single quote if quote == cp quote = nil elsif quote.nil? quote = cp end elsif cp == 0x23 # unquoted hash mark break end stripped << cp end return line_number if stripped =~ regexp end nil end def scope_interpolations_stable?(lookup_invocation) if @scope_interpolations.empty? true else scope = lookup_invocation.scope lookup_invocation.without_explain do @scope_interpolations.all? do |key, root_key, segments, old_value| value = scope[root_key] unless value.nil? || segments.empty? found = nil; catch(:no_such_key) { found = sub_lookup(key, lookup_invocation, segments, value) } value = found; end old_value.eql?(value) end end end end # @api private def create_configured_data_providers(lookup_invocation, parent_data_provider, use_default_hierarchy) self.class.not_implemented(self, 'create_configured_data_providers') end def validate_config(config, owner) self.class.not_implemented(self, 'validate_config') end def version self.class.not_implemented(self, 'version') end def name "hiera configuration version #{version}" end def create_hiera3_backend_provider(name, backend, parent_data_provider, datadir, paths, hiera3_config) # Custom backend. Hiera v3 must be installed, it's logger configured, and it must be made aware of the loaded config require 'hiera' if Hiera::Config.instance_variable_defined?(:@config) && (current_config = Hiera::Config.instance_variable_get(:@config)).is_a?(Hash) current_config.each_pair do |key, val| case key when :hierarchy, :backends hiera3_config[key] = ([val] + [hiera3_config[key]]).flatten.uniq else hiera3_config[key] = val end end else if hiera3_config.include?(KEY_LOGGER) Hiera.logger = hiera3_config[KEY_LOGGER].to_s else Hiera.logger = 'puppet' end end unless Hiera::Interpolate.const_defined?(:PATCHED_BY_HIERA_5) # Replace the class methods 'hiera_interpolate' and 'alias_interpolate' with a method that wires back and performs global # lookups using the lookup framework. This is necessary since the classic Hiera is made aware only of custom backends. class << Hiera::Interpolate hiera_interpolate = Proc.new do |data, key, scope, extra_data, context| override = context[:order_override] invocation = Puppet::Pops::Lookup::Invocation.current unless override.nil? && invocation.global_only? invocation = Puppet::Pops::Lookup::Invocation.new(scope) invocation.set_global_only invocation.set_hiera_v3_location_overrides(override) unless override.nil? end Puppet::Pops::Lookup::LookupAdapter.adapt(scope.compiler).lookup(key, invocation, nil) end send(:remove_method, :hiera_interpolate) send(:remove_method, :alias_interpolate) send(:define_method, :hiera_interpolate, hiera_interpolate) send(:define_method, :alias_interpolate, hiera_interpolate) end Hiera::Interpolate.send(:const_set, :PATCHED_BY_HIERA_5, true) end Hiera::Config.instance_variable_set(:@config, hiera3_config) # Use a special lookup_key that delegates to the backend paths = nil if !paths.nil? && paths.empty? create_data_provider(name, parent_data_provider, KEY_V3_BACKEND, 'hiera_v3_data', { KEY_DATADIR => datadir, KEY_BACKEND => backend }, paths) end private def create_data_provider(name, parent_data_provider, function_kind, function_name, options, locations) FUNCTION_PROVIDERS[function_kind].new(name, parent_data_provider, function_name, options, locations) end def self.not_implemented(impl, method_name) raise NotImplementedError, "The class #{impl.class.name} should have implemented the method #{method_name}()" end end # @api private class HieraConfigV3 < HieraConfig KEY_BACKENDS = 'backends'.freeze KEY_MERGE_BEHAVIOR = 'merge_behavior'.freeze KEY_DEEP_MERGE_OPTIONS = 'deep_merge_options'.freeze def self.config_type return @@CONFIG_TYPE if class_variable_defined?(:@@CONFIG_TYPE) tf = Types::TypeFactory nes_t = Types::PStringType::NON_EMPTY # This is a hash, not a type. Contained backends are added prior to validation @@CONFIG_TYPE = { tf.optional(KEY_VERSION) => tf.range(3,3), tf.optional(KEY_BACKENDS) => tf.variant(nes_t, tf.array_of(nes_t)), tf.optional(KEY_LOGGER) => nes_t, tf.optional(KEY_MERGE_BEHAVIOR) => tf.enum('deep', 'deeper', 'native'), tf.optional(KEY_DEEP_MERGE_OPTIONS) => tf.hash_kv(nes_t, tf.variant(tf.string, tf.boolean)), tf.optional(KEY_HIERARCHY) => tf.variant(nes_t, tf.array_of(nes_t)) } end def create_configured_data_providers(lookup_invocation, parent_data_provider, _) scope = lookup_invocation.scope unless scope.is_a?(Hiera::Scope) lookup_invocation = Invocation.new( Hiera::Scope.new(scope), lookup_invocation.override_values, lookup_invocation.default_values, lookup_invocation.explainer) end default_datadir = File.join(Puppet.settings[:codedir], 'environments', '%{::environment}', 'hieradata') data_providers = {} [@config[KEY_BACKENDS]].flatten.each do |backend| if data_providers.include?(backend) first_line = find_line_matching(/[^\w]#{backend}(?:[^\w]|$)/) line = find_line_matching(/[^\w]#{backend}(?:[^\w]|$)/, first_line + 1) if first_line unless line line = first_line first_line = nil end fail(Issues::HIERA_BACKEND_MULTIPLY_DEFINED, { :name => backend, :first_line => first_line }, line) end original_paths = [@config[KEY_HIERARCHY]].flatten backend_config = @config[backend] if backend_config.nil? backend_config = EMPTY_HASH else backend_config = interpolate(backend_config, lookup_invocation, false) end datadir = Pathname(backend_config[KEY_DATADIR] || interpolate(default_datadir, lookup_invocation, false)) ext = backend_config[KEY_EXTENSION] if ext.nil? ext = backend == 'hocon' ? '.conf' : ".#{backend}" else ext = ".#{ext}" end paths = resolve_paths(datadir, original_paths, lookup_invocation, @config_path.nil?, ext) data_providers[backend] = case when backend == 'json', backend == 'yaml' create_data_provider(backend, parent_data_provider, KEY_V3_DATA_HASH, "#{backend}_data", { KEY_DATADIR => datadir }, paths) when backend == 'hocon' && Puppet.features.hocon? create_data_provider(backend, parent_data_provider, KEY_V3_DATA_HASH, 'hocon_data', { KEY_DATADIR => datadir }, paths) when backend == 'eyaml' && Puppet.features.hiera_eyaml? create_data_provider(backend, parent_data_provider, KEY_V3_LOOKUP_KEY, 'eyaml_lookup_key', backend_config.merge(KEY_DATADIR => datadir), paths) else create_hiera3_backend_provider(backend, backend, parent_data_provider, datadir, paths, @loaded_config) end end data_providers.values end DEFAULT_CONFIG_HASH = { KEY_BACKENDS => %w(yaml), KEY_HIERARCHY => %w(nodes/%{::trusted.certname} common), KEY_MERGE_BEHAVIOR => 'native' } def validate_config(config, owner) unless Puppet[:strict] == :off Puppet.warn_once('deprecations', 'hiera.yaml', _("%{config_path}: Use of 'hiera.yaml' version 3 is deprecated. It should be converted to version 5") % { config_path: @config_path }, config_path.to_s) end config[KEY_VERSION] ||= 3 config[KEY_BACKENDS] ||= DEFAULT_CONFIG_HASH[KEY_BACKENDS] config[KEY_HIERARCHY] ||= DEFAULT_CONFIG_HASH[KEY_HIERARCHY] config[KEY_MERGE_BEHAVIOR] ||= DEFAULT_CONFIG_HASH[KEY_MERGE_BEHAVIOR] config[KEY_DEEP_MERGE_OPTIONS] ||= {} backends = [ config[KEY_BACKENDS] ].flatten # Create the final struct used for validation (backends are included as keys to arbitrary configs in the form of a hash) tf = Types::TypeFactory backend_elements = {} backends.each { |backend| backend_elements[tf.optional(backend)] = tf.hash_kv(Types::PStringType::NON_EMPTY, tf.any) } v3_struct = tf.struct(self.class.config_type.merge(backend_elements)) Types::TypeAsserter.assert_instance_of(["The Lookup Configuration at '%s'", @config_path], v3_struct, config) end def merge_strategy @merge_strategy ||= create_merge_strategy end def version 3 end private def create_merge_strategy key = @config[KEY_MERGE_BEHAVIOR] case key when nil, 'native' MergeStrategy.strategy(nil) when 'array' MergeStrategy.strategy(:unique) when 'deep', 'deeper' merge = { 'strategy' => key == 'deep' ? 'reverse_deep' : 'unconstrained_deep' } dm_options = @config[KEY_DEEP_MERGE_OPTIONS] merge.merge!(dm_options) if dm_options MergeStrategy.strategy(merge) end end end # @api private class HieraConfigV4 < HieraConfig def self.config_type return @@CONFIG_TYPE if class_variable_defined?(:@@CONFIG_TYPE) tf = Types::TypeFactory nes_t = Types::PStringType::NON_EMPTY @@CONFIG_TYPE = tf.struct({ KEY_VERSION => tf.range(4, 4), tf.optional(KEY_DATADIR) => nes_t, tf.optional(KEY_HIERARCHY) => tf.array_of(tf.struct( KEY_BACKEND => nes_t, KEY_NAME => nes_t, tf.optional(KEY_DATADIR) => nes_t, tf.optional(KEY_PATH) => nes_t, tf.optional(KEY_PATHS) => tf.array_of(nes_t) )) }) end def create_configured_data_providers(lookup_invocation, parent_data_provider, _) default_datadir = @config[KEY_DATADIR] data_providers = {} @config[KEY_HIERARCHY].each do |he| name = he[KEY_NAME] if data_providers.include?(name) first_line = find_line_matching(/\s+name:\s+['"]?#{name}(?:[^\w]|$)/) line = find_line_matching(/\s+name:\s+['"]?#{name}(?:[^\w]|$)/, first_line + 1) if first_line unless line line = first_line first_line = nil end fail(Issues::HIERA_HIERARCHY_NAME_MULTIPLY_DEFINED, { :name => name, :first_line => first_line }, line) end original_paths = he[KEY_PATHS] || [he[KEY_PATH] || name] datadir = @config_root + (he[KEY_DATADIR] || default_datadir) provider_name = he[KEY_BACKEND] data_providers[name] = case when provider_name == 'json', provider_name == 'yaml' create_data_provider(name, parent_data_provider, KEY_DATA_HASH, "#{provider_name}_data", {}, resolve_paths(datadir, original_paths, lookup_invocation, @config_path.nil?, ".#{provider_name}")) when provider_name == 'hocon' && Puppet.features.hocon? create_data_provider(name, parent_data_provider, KEY_DATA_HASH, 'hocon_data', {}, resolve_paths(datadir, original_paths, lookup_invocation, @config_path.nil?, '.conf')) else fail(Issues::HIERA_NO_PROVIDER_FOR_BACKEND, { :name => provider_name }, find_line_matching(/[^\w]#{provider_name}(?:[^\w]|$)/)) end end data_providers.values end def validate_config(config, owner) unless Puppet[:strict] == :off Puppet.warn_once('deprecations', 'hiera.yaml', _("%{config_path}: Use of 'hiera.yaml' version 4 is deprecated. It should be converted to version 5") % { config_path: @config_path }, config_path.to_s) end config[KEY_DATADIR] ||= 'data' config[KEY_HIERARCHY] ||= [{ KEY_NAME => 'common', KEY_BACKEND => 'yaml' }] Types::TypeAsserter.assert_instance_of(["The Lookup Configuration at '%s'", @config_path], self.class.config_type, config) end def version 4 end end # @api private class HieraConfigV5 < HieraConfig def self.config_type return @@CONFIG_TYPE if class_variable_defined?(:@@CONFIG_TYPE_V5) tf = Types::TypeFactory nes_t = Types::PStringType::NON_EMPTY # Validated using Ruby URI implementation uri_t = Types::PStringType::NON_EMPTY # The option name must start with a letter and end with a letter or digit. May contain underscore and dash. option_name_t = tf.pattern(/\A[A-Za-z](:?[0-9A-Za-z_-]*[0-9A-Za-z])?\z/) hierarchy_t = tf.array_of(tf.struct( { KEY_NAME => nes_t, tf.optional(KEY_OPTIONS) => tf.hash_kv(option_name_t, tf.data), tf.optional(KEY_DATA_HASH) => nes_t, tf.optional(KEY_LOOKUP_KEY) => nes_t, tf.optional(KEY_V3_BACKEND) => nes_t, tf.optional(KEY_V4_DATA_HASH) => nes_t, tf.optional(KEY_DATA_DIG) => nes_t, tf.optional(KEY_PATH) => nes_t, tf.optional(KEY_PATHS) => tf.array_of(nes_t, tf.range(1, :default)), tf.optional(KEY_GLOB) => nes_t, tf.optional(KEY_GLOBS) => tf.array_of(nes_t, tf.range(1, :default)), tf.optional(KEY_URI) => uri_t, tf.optional(KEY_URIS) => tf.array_of(uri_t, tf.range(1, :default)), tf.optional(KEY_MAPPED_PATHS) => tf.array_of(nes_t, tf.range(3, 3)), tf.optional(KEY_DATADIR) => nes_t })) @@CONFIG_TYPE = tf.struct({ KEY_VERSION => tf.range(5, 5), tf.optional(KEY_DEFAULTS) => tf.struct( { tf.optional(KEY_DATA_HASH) => nes_t, tf.optional(KEY_LOOKUP_KEY) => nes_t, tf.optional(KEY_DATA_DIG) => nes_t, tf.optional(KEY_DATADIR) => nes_t, tf.optional(KEY_OPTIONS) => tf.hash_kv(option_name_t, tf.data), }), tf.optional(KEY_HIERARCHY) => hierarchy_t, tf.optional(KEY_DEFAULT_HIERARCHY) => hierarchy_t }) end def create_configured_data_providers(lookup_invocation, parent_data_provider, use_default_hierarchy) defaults = @config[KEY_DEFAULTS] || EMPTY_HASH datadir = defaults[KEY_DATADIR] || 'data' # Hashes enumerate their values in the order that the corresponding keys were inserted so it's safe to use # a hash for the data_providers. data_providers = {} if @config.include?(KEY_DEFAULT_HIERARCHY) unless parent_data_provider.is_a?(ModuleDataProvider) fail(Issues::HIERA_DEFAULT_HIERARCHY_NOT_IN_MODULE, EMPTY_HASH, find_line_matching(/\s+default_hierarchy:/)) end elsif use_default_hierarchy return data_providers end @config[use_default_hierarchy ? KEY_DEFAULT_HIERARCHY : KEY_HIERARCHY].each do |he| name = he[KEY_NAME] if data_providers.include?(name) first_line = find_line_matching(/\s+name:\s+['"]?#{name}(?:[^\w]|$)/) line = find_line_matching(/\s+name:\s+['"]?#{name}(?:[^\w]|$)/, first_line + 1) if first_line unless line line = first_line first_line = nil end fail(Issues::HIERA_HIERARCHY_NAME_MULTIPLY_DEFINED, { :name => name, :first_line => first_line }, line) end function_kind = ALL_FUNCTION_KEYS.find { |key| he.include?(key) } if function_kind.nil? function_kind = FUNCTION_KEYS.find { |key| defaults.include?(key) } function_name = defaults[function_kind] else function_name = he[function_kind] end entry_datadir = @config_root + (he[KEY_DATADIR] || datadir) entry_datadir = Pathname(interpolate(entry_datadir.to_s, lookup_invocation, false)) location_key = LOCATION_KEYS.find { |key| he.include?(key) } locations = case location_key when KEY_PATHS resolve_paths(entry_datadir, he[location_key], lookup_invocation, @config_path.nil?) when KEY_PATH resolve_paths(entry_datadir, [he[location_key]], lookup_invocation, @config_path.nil?) when KEY_GLOBS expand_globs(entry_datadir, he[location_key], lookup_invocation) when KEY_GLOB expand_globs(entry_datadir, [he[location_key]], lookup_invocation) when KEY_URIS expand_uris(he[location_key], lookup_invocation) when KEY_URI expand_uris([he[location_key]], lookup_invocation) when KEY_MAPPED_PATHS expand_mapped_paths(entry_datadir, he[location_key], lookup_invocation) else nil end next if @config_path.nil? && !locations.nil? && locations.empty? # Default config and no existing paths found options = he[KEY_OPTIONS] || defaults[KEY_OPTIONS] options = options.nil? ? EMPTY_HASH : interpolate(options, lookup_invocation, false) if(function_kind == KEY_V3_BACKEND) v3options = { :datadir => entry_datadir.to_s } options.each_pair { |k, v| v3options[k.to_sym] = v } data_providers[name] = create_hiera3_backend_provider(name, function_name, parent_data_provider, entry_datadir, locations, { :hierarchy => locations.nil? ? [] : locations.map do |loc| path = loc.original_location path.end_with?(".#{function_name}") ? path[0..-(function_name.length + 2)] : path end, function_name.to_sym => v3options, :backends => [ function_name ], :logger => 'puppet' }) else data_providers[name] = create_data_provider(name, parent_data_provider, function_kind, function_name, options, locations) end end data_providers.values end def has_default_hierarchy? @config.include?(KEY_DEFAULT_HIERARCHY) end RESERVED_OPTION_KEYS = ['path', 'uri'].freeze DEFAULT_CONFIG_HASH = { KEY_VERSION => 5, KEY_DEFAULTS => { KEY_DATADIR => 'data', KEY_DATA_HASH => 'yaml_data' }, KEY_HIERARCHY => [ { KEY_NAME => 'Common', KEY_PATH => 'common.yaml', } ] }.freeze def validate_config(config, owner) config[KEY_DEFAULTS] ||= DEFAULT_CONFIG_HASH[KEY_DEFAULTS] config[KEY_HIERARCHY] ||= DEFAULT_CONFIG_HASH[KEY_HIERARCHY] Types::TypeAsserter.assert_instance_of(["The Lookup Configuration at '%s'", @config_path], self.class.config_type, config) defaults = config[KEY_DEFAULTS] validate_defaults(defaults) unless defaults.nil? config[KEY_HIERARCHY].each { |he| validate_hierarchy(he, defaults, owner) } if config.include?(KEY_DEFAULT_HIERARCHY) unless owner.is_a?(ModuleDataProvider) fail(Issues::HIERA_DEFAULT_HIERARCHY_NOT_IN_MODULE, EMPTY_HASH, find_line_matching(/(?:^|\s+)#{KEY_DEFAULT_HIERARCHY}:/)) end config[KEY_DEFAULT_HIERARCHY].each { |he| validate_hierarchy(he, defaults, owner) } end config end def validate_hierarchy(he, defaults, owner) name = he[KEY_NAME] case ALL_FUNCTION_KEYS.count { |key| he.include?(key) } when 0 if defaults.nil? || FUNCTION_KEYS.count { |key| defaults.include?(key) } == 0 fail(Issues::HIERA_MISSING_DATA_PROVIDER_FUNCTION, :name => name) end when 1 # OK else fail(Issues::HIERA_MULTIPLE_DATA_PROVIDER_FUNCTIONS, :name => name) end v3_backend = he[KEY_V3_BACKEND] unless v3_backend.nil? unless owner.is_a?(GlobalDataProvider) fail(Issues::HIERA_V3_BACKEND_NOT_GLOBAL, EMPTY_HASH, find_line_matching(/\s+#{KEY_V3_BACKEND}:/)) end if v3_backend == 'json' || v3_backend == 'yaml' || v3_backend == 'hocon' && Puppet.features.hocon? # Disallow use of backends that have corresponding "data_hash" functions in version 5 fail(Issues::HIERA_V3_BACKEND_REPLACED_BY_DATA_HASH, { :function_name => v3_backend }, find_line_matching(/\s+#{KEY_V3_BACKEND}:\s*['"]?#{v3_backend}(?:[^\w]|$)/)) end end if LOCATION_KEYS.count { |key| he.include?(key) } > 1 fail(Issues::HIERA_MULTIPLE_LOCATION_SPECS, :name => name) end options = he[KEY_OPTIONS] unless options.nil? RESERVED_OPTION_KEYS.each do |key| fail(Issues::HIERA_OPTION_RESERVED_BY_PUPPET, :key => key, :name => name) if options.include?(key) end end end def validate_defaults(defaults) case FUNCTION_KEYS.count { |key| defaults.include?(key) } when 0, 1 # OK else fail(Issues::HIERA_MULTIPLE_DATA_PROVIDER_FUNCTIONS_IN_DEFAULT) end options = defaults[KEY_OPTIONS] unless options.nil? RESERVED_OPTION_KEYS.each do |key| fail(Issues::HIERA_DEFAULT_OPTION_RESERVED_BY_PUPPET, :key => key) if options.include?(key) end end end def version 5 end end end end ������������������������������puppet-5.5.10/lib/puppet/pops/merge_strategy.rb�����������������������������������������������������0000644�0052762�0001160�00000035271�13417161721�021434� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'deep_merge/core' module Puppet::Pops # Merges to objects into one based on an implemented strategy. # class MergeStrategy NOT_FOUND = Object.new.freeze def self.strategies @@strategies ||= {} end private_class_method :strategies # Finds the merge strategy for the given _merge_, creates an instance of it and returns that instance. # # @param merge [MergeStrategy,String,Hash<String,Object>,nil] The merge strategy. Can be a string or symbol denoting the key # identifier or a hash with options where the key 'strategy' denotes the key # @return [MergeStrategy] The matching merge strategy # def self.strategy(merge) return DefaultMergeStrategy::INSTANCE unless merge return merge if merge.is_a?(MergeStrategy) if merge.is_a?(Hash) merge_strategy = merge['strategy'] if merge_strategy.nil? #TRANSLATORS 'merge' is a variable name and 'strategy' is a key and should not be translated raise ArgumentError, _("The hash given as 'merge' must contain the name of a strategy in string form for the key 'strategy'") end merge_options = merge.size == 1 ? EMPTY_HASH : merge else merge_strategy = merge merge_options = EMPTY_HASH end merge_strategy = merge_strategy.to_sym if merge_strategy.is_a?(String) strategy_class = strategies[merge_strategy] raise ArgumentError, _("Unknown merge strategy: '%{strategy}'") % { strategy: merge_strategy } if strategy_class.nil? merge_options == EMPTY_HASH ? strategy_class::INSTANCE : strategy_class.new(merge_options) end # Returns the list of merge strategy keys known to this class # # @return [Array<Symbol>] List of strategy keys # def self.strategy_keys strategies.keys - [:default, :unconstrained_deep, :reverse_deep] end # Adds a new merge strategy to the map of strategies known to this class # # @param strategy_class [Class<MergeStrategy>] The class of the added strategy # def self.add_strategy(strategy_class) unless MergeStrategy > strategy_class #TRANSLATORS 'MergeStrategies.add_strategy' is a method, 'stratgey_class' is a variable and 'MergeStrategy' is a class name and should not be translated raise ArgumentError, _("MergeStrategies.add_strategy 'strategy_class' must be a 'MergeStrategy' class. Got %{strategy_class}") % { strategy_class: strategy_class } end strategies[strategy_class.key] = strategy_class nil end # Finds a merge strategy that corresponds to the given _merge_ argument and delegates the task of merging the elements of _e1_ and _e2_ to it. # # @param e1 [Object] The first element # @param e2 [Object] The second element # @return [Object] The result of the merge # def self.merge(e1, e2, merge) strategy(merge).merge(e1, e2) end def self.key raise NotImplementedError, "Subclass must implement 'key'" end # Create a new instance of this strategy configured with the given _options_ # @param merge_options [Hash<String,Object>] Merge options def initialize(options) assert_type('The merge options', self.class.options_t, options) unless options.empty? @options = options end # Merges the elements of _e1_ and _e2_ according to the rules of this strategy and options given when this # instance was created # # @param e1 [Object] The first element # @param e2 [Object] The second element # @return [Object] The result of the merge # def merge(e1, e2) checked_merge( assert_type('The first element of the merge', value_t, e1), assert_type('The second element of the merge', value_t, e2)) end # TODO: API 5.0 Remove this method # @deprecated def merge_lookup(lookup_variants) lookup(lookup_variants, Lookup::Invocation.current) end # Merges the result of yielding the given _lookup_variants_ to a given block. # # @param lookup_variants [Array] The variants to pass as second argument to the given block # @return [Object] the merged value. # @yield [} ] # @yieldparam variant [Object] each variant given in the _lookup_variants_ array. # @yieldreturn [Object] the value to merge with other values # @throws :no_such_key if the lookup was unsuccessful # # Merges the result of yielding the given _lookup_variants_ to a given block. # # @param lookup_variants [Array] The variants to pass as second argument to the given block # @return [Object] the merged value. # @yield [} ] # @yieldparam variant [Object] each variant given in the _lookup_variants_ array. # @yieldreturn [Object] the value to merge with other values # @throws :no_such_key if the lookup was unsuccessful # def lookup(lookup_variants, lookup_invocation) case lookup_variants.size when 0 throw :no_such_key when 1 merge_single(yield(lookup_variants[0])) else lookup_invocation.with(:merge, self) do result = lookup_variants.reduce(NOT_FOUND) do |memo, lookup_variant| not_found = true value = catch(:no_such_key) do v = yield(lookup_variant) not_found = false v end if not_found memo else memo.equal?(NOT_FOUND) ? convert_value(value) : merge(memo, value) end end throw :no_such_key if result == NOT_FOUND lookup_invocation.report_result(result) end end end # Converts a single value to the type expected when merging two elements # @param value [Object] the value to convert # @return [Object] the converted value def convert_value(value) value end # Applies the merge strategy on a single element. Only applicable for `unique` # @param value [Object] the value to merge with nothing # @return [Object] the merged value def merge_single(value) value end def options @options end def configuration if @options.nil? || @options.empty? self.class.key.to_s else @options.include?('strategy') ? @options : { 'strategy' => self.class.key.to_s }.merge(@options) end end protected # Returns the type used to validate the options hash # # @return [Types::PStructType] the puppet type # def self.options_t @options_t ||=Types::TypeParser.singleton.parse("Struct[{strategy=>Optional[Pattern[/#{key}/]]}]") end # Returns the type used to validate the options hash # # @return [Types::PAnyType] the puppet type # def value_t raise NotImplementedError, "Subclass must implement 'value_t'" end def checked_merge(e1, e2) raise NotImplementedError, "Subclass must implement 'checked_merge(e1,e2)'" end def assert_type(param, type, value) Types::TypeAsserter.assert_instance_of(param, type, value) end end # Simple strategy that returns the first value found. It never merges any values. # class FirstFoundStrategy < MergeStrategy INSTANCE = self.new(EMPTY_HASH) def self.key :first end # Returns the first value found # # @param lookup_variants [Array] The variants to pass as second argument to the given block # @return [Object] the merged value # @throws :no_such_key unless the lookup was successful # def lookup(lookup_variants, _) # First found does not continue when a root key was found and a subkey wasn't since that would # simulate a hash merge lookup_variants.each { |lookup_variant| catch(:no_such_key) { return yield(lookup_variant) } } throw :no_such_key end protected def value_t @value_t ||= Types::PAnyType::DEFAULT end MergeStrategy.add_strategy(self) end # Same as {FirstFoundStrategy} but used when no strategy has been explicitly given class DefaultMergeStrategy < FirstFoundStrategy INSTANCE = self.new(EMPTY_HASH) def self.key :default end MergeStrategy.add_strategy(self) end # Produces a new hash by merging hash e1 with hash e2 in such a way that the values of duplicate keys # will be those of e1 # class HashMergeStrategy < MergeStrategy INSTANCE = self.new(EMPTY_HASH) def self.key :hash end # @param e1 [Hash<String,Object>] The hash that will act as the source of the merge # @param e2 [Hash<String,Object>] The hash that will act as the receiver for the merge # @return [Hash<String,Object]] The merged hash # @see Hash#merge def checked_merge(e1, e2) e2.merge(e1) end protected def value_t @value_t ||= Types::TypeParser.singleton.parse('Hash[String,Data]') end MergeStrategy.add_strategy(self) end # Merges two values that must be either scalar or arrays into a unique set of values. # # Scalar values will be converted into a one element arrays and array values will be flattened # prior to forming the unique set. The order of the elements is preserved with e1 being the # first contributor of elements and e2 the second. # class UniqueMergeStrategy < MergeStrategy INSTANCE = self.new(EMPTY_HASH) def self.key :unique end # @param e1 [Array<Object>] The first array # @param e2 [Array<Object>] The second array # @return [Array<Object>] The unique set of elements # def checked_merge(e1, e2) convert_value(e1) | convert_value(e2) end def convert_value(e) e.is_a?(Array) ? e.flatten : [e] end # If _value_ is an array, then return the result of calling `uniq` on that array. Otherwise, # the argument is returned. # @param value [Object] the value to merge with nothing # @return [Object] the merged value def merge_single(value) value.is_a?(Array) ? value.uniq : value end protected def value_t @value_t ||= Types::TypeParser.singleton.parse('Variant[Scalar,Array[Data]]') end MergeStrategy.add_strategy(self) end # Documentation copied from https://github.com/danielsdeleo/deep_merge/blob/master/lib/deep_merge/core.rb # altered with respect to _preserve_unmergeables_ since this implementation always disables that option. # # The destination is dup'ed before the deep_merge is called to allow frozen objects as values. # # deep_merge method permits merging of arbitrary child elements. The two top level # elements must be hashes. These hashes can contain unlimited (to stack limit) levels # of child elements. These child elements to not have to be of the same types. # Where child elements are of the same type, deep_merge will attempt to merge them together. # Where child elements are not of the same type, deep_merge will skip or optionally overwrite # the destination element with the contents of the source element at that level. # So if you have two hashes like this: # source = {:x => [1,2,3], :y => 2} # dest = {:x => [4,5,'6'], :y => [7,8,9]} # dest.deep_merge!(source) # Results: {:x => [1,2,3,4,5,'6'], :y => 2} # # "deep_merge" will unconditionally overwrite any unmergeables and merge everything else. # # Options: # Options are specified in the last parameter passed, which should be in hash format: # hash.deep_merge!({:x => [1,2]}, {:knockout_prefix => '--'}) # - 'knockout_prefix' Set to string value to signify prefix which deletes elements from existing element. Defaults is _undef_ # - 'sort_merged_arrays' Set to _true_ to sort all arrays that are merged together. Default is _false_ # - 'merge_hash_arrays' Set to _true_ to merge hashes within arrays. Default is _false_ # # Selected Options Details: # :knockout_prefix => The purpose of this is to provide a way to remove elements # from existing Hash by specifying them in a special way in incoming hash # source = {:x => ['--1', '2']} # dest = {:x => ['1', '3']} # dest.ko_deep_merge!(source) # Results: {:x => ['2','3']} # Additionally, if the knockout_prefix is passed alone as a string, it will cause # the entire element to be removed: # source = {:x => '--'} # dest = {:x => [1,2,3]} # dest.ko_deep_merge!(source) # Results: {:x => ""} # # :merge_hash_arrays => merge hashes within arrays # source = {:x => [{:y => 1}]} # dest = {:x => [{:z => 2}]} # dest.deep_merge!(source, {:merge_hash_arrays => true}) # Results: {:x => [{:y => 1, :z => 2}]} # class DeepMergeStrategy < MergeStrategy INSTANCE = self.new(EMPTY_HASH) def self.key :deep end def checked_merge(e1, e2) dm_options = { :preserve_unmergeables => false } options.each_pair { |k,v| dm_options[k.to_sym] = v unless k == 'strategy' } # e2 (the destination) is deep cloned to avoid that the passed in object mutates DeepMerge.deep_merge!(e1, deep_clone(e2), dm_options) end def deep_clone(value) if value.is_a?(Hash) result = value.clone value.each{ |k, v| result[k] = deep_clone(v) } result elsif value.is_a?(Array) value.map{ |v| deep_clone(v) } else value end end protected # Returns a type that allows all deep_merge options except 'preserve_unmergeables' since we force # the setting of that option to false # # @return [Types::PAnyType] the puppet type used when validating the options hash def self.options_t @options_t ||= Types::TypeParser.singleton.parse('Struct[{'\ "strategy=>Optional[Pattern[#{key}]],"\ 'knockout_prefix=>Optional[String],'\ 'merge_debug=>Optional[Boolean],'\ 'merge_hash_arrays=>Optional[Boolean],'\ 'sort_merged_arrays=>Optional[Boolean],'\ '}]') end def value_t @value_t ||= Types::PAnyType::DEFAULT end MergeStrategy.add_strategy(self) end # Same as {DeepMergeStrategy} but without constraint on valid merge options # (needed for backward compatibility with Hiera v3) class UnconstrainedDeepMergeStrategy < DeepMergeStrategy def self.key :unconstrained_deep end # @return [Types::PAnyType] the puppet type used when validating the options hash def self.options_t @options_t ||= Types::TypeParser.singleton.parse('Hash[String[1],Any]') end MergeStrategy.add_strategy(self) end # Same as {UnconstrainedDeepMergeStrategy} but with reverse priority of merged elements. # (needed for backward compatibility with Hiera v3) class ReverseDeepMergeStrategy < UnconstrainedDeepMergeStrategy INSTANCE = self.new(EMPTY_HASH) def self.key :reverse_deep end def checked_merge(e1, e2) super(e2, e1) end MergeStrategy.add_strategy(self) end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/migration/������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020054� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/migration/migration_checker.rb����������������������������������������0000644�0052762�0001160�00000002757�13417161721�024064� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This class defines the private API of the MigrationChecker support. # @api private # class Puppet::Pops::Migration::MigrationChecker def initialize() end def self.singleton @null_checker ||= self.new end # Produces a hash of available migrations; a map from a symbolic name in string form to a brief description. # This version has no such supported migrations. def available_migrations() { } end # For 3.8/4.0 def report_ambiguous_integer(o) raise Puppet::DevError, _("Unsupported migration method called") end # For 3.8/4.0 def report_ambiguous_float(o) raise Puppet::DevError, _("Unsupported migration method called") end # For 3.8/4.0 def report_empty_string_true(value, o) raise Puppet::DevError, _("Unsupported migration method called") end # For 3.8/4.0 def report_uc_bareword_type(value, o) raise Puppet::DevError, _("Unsupported migration method called") end # For 3.8/4.0 def report_equality_type_mismatch(left, right, o) raise Puppet::DevError, _("Unsupported migration method called") end # For 3.8/4.0 def report_option_type_mismatch(test_value, option_value, option_expr, matching_expr) raise Puppet::DevError, _("Unsupported migration method called") end # For 3.8/4.0 def report_in_expression(o) raise Puppet::DevError, _("Unsupported migration method called") end # For 3.8/4.0 def report_array_last_in_block(o) raise Puppet::DevError, _("Unsupported migration method called") end end �����������������puppet-5.5.10/lib/puppet/pops/model/����������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�017163� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/model/ast_transformer.rb����������������������������������������������0000644�0052762�0001160�00000010230�13417161721�022710� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parser/ast' # The receiver of `import(file)` calls; once per imported file, or nil if imports are ignored # # Transforms a Pops::Model to classic Puppet AST. # TODO: Documentation is currently skipped completely (it is only used for Rdoc) # class Puppet::Pops::Model::AstTransformer AST = Puppet::Parser::AST Model = Puppet::Pops::Model attr_reader :importer def initialize(source_file = "unknown-file", importer=nil) @@transform_visitor ||= Puppet::Pops::Visitor.new(nil,"transform",0,0) @@query_transform_visitor ||= Puppet::Pops::Visitor.new(nil,"query",0,0) @@hostname_transform_visitor ||= Puppet::Pops::Visitor.new(nil,"hostname",0,0) @importer = importer @source_file = source_file end # Initialize klass from o (location) and hash (options to created instance). # The object o is used to compute a source location. It may be nil. Source position is merged into # the given options (non surgically). If o is non-nil, the first found source position going up # the containment hierarchy is set. I.e. callers should pass nil if a source position is not wanted # or known to be unobtainable for the object. # # @param o [Object, nil] object from which source position / location is obtained, may be nil # @param klass [Class<Puppet::Parser::AST>] the ast class to create an instance of # @param hash [Hash] hash with options for the class to create # def ast(o, klass, hash={}) # create and pass hash with file and line information # PUP-3274 - still needed since hostname transformation requires AST::HostName, and AST::Regexp klass.new(merge_location(hash, o)) end # THIS IS AN EXPENSIVE OPERATION # The 3x AST requires line, pos etc. to be recorded directly in the AST nodes and this information # must be computed. # (Newer implementation only computes the information that is actually needed; typically when raising an # exception). # def merge_location(hash, o) if o pos = {} locator = o.locator offset = o.is_a?(Model::Program) ? 0 : o.offset pos[:line] = locator.line_for_offset(offset) pos[:pos] = locator.pos_on_line(offset) pos[:file] = locator.file if nil_or_empty?(pos[:file]) && !nil_or_empty?(@source_file) pos[:file] = @source_file end hash = hash.merge(pos) end hash end # Transforms pops expressions into AST 3.1 statements/expressions def transform(o) begin @@transform_visitor.visit_this_0(self,o) rescue StandardError => e loc_data = {} merge_location(loc_data, o) raise Puppet::ParseError.new(_("Error while transforming to Puppet 3 AST: %{message}") % { message: e.message }, loc_data[:file], loc_data[:line], loc_data[:pos], e) end end # Transforms pops expressions into AST 3.1 query expressions def query(o) @@query_transform_visitor.visit_this_0(self, o) end # Transforms pops expressions into AST 3.1 hostnames def hostname(o) @@hostname_transform_visitor.visit_this_0(self, o) end # Ensures transformation fails if a 3.1 non supported object is encountered in a query expression # def query_Object(o) raise _("Not a valid expression in a collection query: %{class_name}") % { class_name: o.class.name } end # Transforms Array of host matching expressions into a (Ruby) array of AST::HostName def hostname_Array(o) o.collect {|x| ast x, AST::HostName, :value => hostname(x) } end def hostname_LiteralValue(o) return o.value end def hostname_QualifiedName(o) return o.value end def hostname_LiteralNumber(o) transform(o) # Number to string with correct radix end def hostname_LiteralDefault(o) return 'default' end def hostname_LiteralRegularExpression(o) ast o, AST::Regex, :value => o.value end def hostname_Object(o) raise _("Illegal expression - unacceptable as a node name") end def transform_Object(o) raise _("Unacceptable transform - found an Object without a rule: %{klass}") % { klass: o.class } end # Nil, nop # Bee bopp a luh-lah, a bop bop boom. # def is_nop?(o) o.nil? || o.is_a?(Model::Nop) end def nil_or_empty?(x) x.nil? || x == '' end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/model/model_tree_dumper.rb��������������������������������������������0000644�0052762�0001160�00000025360�13417161721�023204� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Dumps a Pops::Model in reverse polish notation; i.e. LISP style # The intention is to use this for debugging output # TODO: BAD NAME - A DUMP is a Ruby Serialization # class Puppet::Pops::Model::ModelTreeDumper < Puppet::Pops::Model::TreeDumper def dump_Array o o.collect {|e| do_dump(e) } end def dump_LiteralFloat o o.value.to_s end def dump_LiteralInteger o case o.radix when 10 o.value.to_s when 8 "0%o" % o.value when 16 "0x%X" % o.value else "bad radix:" + o.value.to_s end end def dump_LiteralValue o o.value.to_s end def dump_QualifiedReference o o.cased_value.to_s end def dump_Factory o o['locator'] ||= Puppet::Pops::Parser::Locator.locator("<not from source>", nil) do_dump(o.model) end def dump_Application o ["application", o.name, do_dump(o.parameters), do_dump(o.body)] end def dump_ArithmeticExpression o [o.operator.to_s, do_dump(o.left_expr), do_dump(o.right_expr)] end # x[y] prints as (slice x y) def dump_AccessExpression o if o.keys.size <= 1 ["slice", do_dump(o.left_expr), do_dump(o.keys[0])] else ["slice", do_dump(o.left_expr), do_dump(o.keys)] end end def dump_MatchesExpression o [o.operator.to_s, do_dump(o.left_expr), do_dump(o.right_expr)] end def dump_CollectExpression o result = ["collect", do_dump(o.type_expr), :indent, :break, do_dump(o.query), :indent] o.operations do |ao| result << :break << do_dump(ao) end result += [:dedent, :dedent ] result end def dump_EppExpression o result = ["epp"] # result << ["parameters"] + o.parameters.collect {|p| do_dump(p) } if o.parameters.size() > 0 if o.body result << do_dump(o.body) else result << [] end result end def dump_ExportedQuery o result = ["<<| |>>"] result += dump_QueryExpression(o) unless is_nop?(o.expr) result end def dump_VirtualQuery o result = ["<| |>"] result += dump_QueryExpression(o) unless is_nop?(o.expr) result end def dump_QueryExpression o [do_dump(o.expr)] end def dump_ComparisonExpression o [o.operator.to_s, do_dump(o.left_expr), do_dump(o.right_expr)] end def dump_AndExpression o ["&&", do_dump(o.left_expr), do_dump(o.right_expr)] end def dump_OrExpression o ["||", do_dump(o.left_expr), do_dump(o.right_expr)] end def dump_InExpression o ["in", do_dump(o.left_expr), do_dump(o.right_expr)] end def dump_AssignmentExpression o [o.operator.to_s, do_dump(o.left_expr), do_dump(o.right_expr)] end # Produces (name => expr) or (name +> expr) def dump_AttributeOperation o [o.attribute_name, o.operator, do_dump(o.value_expr)] end def dump_AttributesOperation o ['* =>', do_dump(o.expr)] end def dump_LiteralList o ["[]"] + o.values.collect {|x| do_dump(x)} end def dump_LiteralHash o ["{}"] + o.entries.collect {|x| do_dump(x)} end def dump_KeyedEntry o [do_dump(o.key), do_dump(o.value)] end def dump_MatchExpression o [o.operator.to_s, do_dump(o.left_expr), do_dump(o.right_expr)] end def dump_LiteralString o "'#{o.value}'" end def dump_LambdaExpression o result = ["lambda"] result << ["parameters"] + o.parameters.collect {|p| do_dump(p) } if o.parameters.size() > 0 result << ['return_type', do_dump(o.return_type)] unless o.return_type.nil? if o.body result << do_dump(o.body) else result << [] end result end def dump_LiteralDefault o ":default" end def dump_LiteralUndef o ":undef" end def dump_LiteralRegularExpression o Puppet::Pops::Types::StringConverter.convert(o.value, '%p') end def dump_Nop o ":nop" end def dump_NamedAccessExpression o [".", do_dump(o.left_expr), do_dump(o.right_expr)] end def dump_NilClass o "()" end def dump_NotExpression o ['!', dump(o.expr)] end def dump_VariableExpression o "$#{dump(o.expr)}" end # Interpolation (to string) shown as (str expr) def dump_TextExpression o ["str", do_dump(o.expr)] end def dump_UnaryMinusExpression o ['-', do_dump(o.expr)] end def dump_UnfoldExpression o ['unfold', do_dump(o.expr)] end def dump_BlockExpression o result = ["block", :indent] o.statements.each {|x| result << :break; result << do_dump(x) } result << :dedent << :break result end # Interpolated strings are shown as (cat seg0 seg1 ... segN) def dump_ConcatenatedString o ["cat"] + o.segments.collect {|x| do_dump(x)} end def dump_HeredocExpression(o) ["@(#{o.syntax})", :indent, :break, do_dump(o.text_expr), :dedent, :break] end def dump_HostClassDefinition o result = ["class", o.name] result << ["inherits", o.parent_class] if o.parent_class result << ["parameters"] + o.parameters.collect {|p| do_dump(p) } if o.parameters.size() > 0 if o.body result << do_dump(o.body) else result << [] end result end def dump_PlanDefinition o result = ["plan", o.name] result << ["parameters"] + o.parameters.collect {|p| do_dump(p) } if o.parameters.size() > 0 if o.body result << do_dump(o.body) else result << [] end result end def dump_NodeDefinition o result = ["node"] result << ["matches"] + o.host_matches.collect {|m| do_dump(m) } result << ["parent", do_dump(o.parent)] if o.parent if o.body result << do_dump(o.body) else result << [] end result end def dump_SiteDefinition o result = ["site"] if o.body result << do_dump(o.body) else result << [] end result end def dump_NamedDefinition o # the nil must be replaced with a string result = [nil, o.name] result << ["parameters"] + o.parameters.collect {|p| do_dump(p) } if o.parameters.size() > 0 if o.body result << do_dump(o.body) else result << [] end result end def dump_FunctionDefinition o result = ['function', o.name] result << ['parameters'] + o.parameters.collect {|p| do_dump(p) } if o.parameters.size() > 0 result << ['return_type', do_dump(o.return_type)] unless o.return_type.nil? if o.body result << do_dump(o.body) else result << [] end result end def dump_ResourceTypeDefinition o result = dump_NamedDefinition(o) result[0] = 'define' result end def dump_CapabilityMapping o [o.kind, do_dump(o.component), o.capability, do_dump(o.mappings)] end def dump_ResourceOverrideExpression o form = o.form == 'regular' ? '' : o.form + '-' result = [form+'override', do_dump(o.resources), :indent] o.operations.each do |p| result << :break << do_dump(p) end result << :dedent result end def dump_ReservedWord o [ 'reserved', o.word ] end # Produces parameters as name, or (= name value) def dump_Parameter o name_prefix = o.captures_rest ? '*' : '' name_part = "#{name_prefix}#{o.name}" if o.value && o.type_expr ["=t", do_dump(o.type_expr), name_part, do_dump(o.value)] elsif o.value ["=", name_part, do_dump(o.value)] elsif o.type_expr ["t", do_dump(o.type_expr), name_part] else name_part end end def dump_ParenthesizedExpression o do_dump(o.expr) end # Hides that Program exists in the output (only its body is shown), the definitions are just # references to contained classes, resource types, and nodes def dump_Program(o) dump(o.body) end def dump_IfExpression o result = ["if", do_dump(o.test), :indent, :break, ["then", :indent, do_dump(o.then_expr), :dedent]] result += [:break, ["else", :indent, do_dump(o.else_expr), :dedent], :dedent] unless is_nop? o.else_expr result end def dump_UnlessExpression o result = ["unless", do_dump(o.test), :indent, :break, ["then", :indent, do_dump(o.then_expr), :dedent]] result += [:break, ["else", :indent, do_dump(o.else_expr), :dedent], :dedent] unless is_nop? o.else_expr result end # Produces (invoke name args...) when not required to produce an rvalue, and # (call name args ... ) otherwise. # def dump_CallNamedFunctionExpression o result = [o.rval_required ? "call" : "invoke", do_dump(o.functor_expr)] o.arguments.collect {|a| result << do_dump(a) } result << do_dump(o.lambda) if o.lambda result end # def dump_CallNamedFunctionExpression o # result = [o.rval_required ? "call" : "invoke", do_dump(o.functor_expr)] # o.arguments.collect {|a| result << do_dump(a) } # result # end def dump_CallMethodExpression o result = [o.rval_required ? "call-method" : "invoke-method", do_dump(o.functor_expr)] o.arguments.collect {|a| result << do_dump(a) } result << do_dump(o.lambda) if o.lambda result end def dump_CaseExpression o result = ["case", do_dump(o.test), :indent] o.options.each do |s| result << :break << do_dump(s) end result << :dedent end def dump_CaseOption o result = ["when"] result << o.values.collect {|x| do_dump(x) } result << ["then", do_dump(o.then_expr) ] result end def dump_RelationshipExpression o [o.operator.to_s, do_dump(o.left_expr), do_dump(o.right_expr)] end def dump_RenderStringExpression o ["render-s", " '#{o.value}'"] end def dump_RenderExpression o ["render", do_dump(o.expr)] end def dump_ResourceBody o result = [do_dump(o.title), :indent] o.operations.each do |p| result << :break << do_dump(p) end result << :dedent result end def dump_ResourceDefaultsExpression o form = o.form == 'regular' ? '' : o.form + '-' result = [form+'resource-defaults', do_dump(o.type_ref), :indent] o.operations.each do |p| result << :break << do_dump(p) end result << :dedent result end def dump_ResourceExpression o form = o.form == 'regular' ? '' : o.form + '-' result = [form+'resource', do_dump(o.type_name), :indent] o.bodies.each do |b| result << :break << do_dump(b) end result << :dedent result end def dump_SelectorExpression o ["?", do_dump(o.left_expr)] + o.selectors.collect {|x| do_dump(x) } end def dump_SelectorEntry o [do_dump(o.matching_expr), "=>", do_dump(o.value_expr)] end def dump_SubLocatedExpression o ["sublocated", do_dump(o.expr)] end def dump_TypeAlias(o) ['type-alias', o.name, do_dump(o.type_expr)] end def dump_TypeMapping(o) ['type-mapping', do_dump(o.type_expr), do_dump(o.mapping_expr)] end def dump_TypeDefinition(o) ['type-definition', o.name, o.parent, do_dump(o.body)] end def dump_Object o [o.class.to_s, o.to_s] end def is_nop? o o.nil? || o.is_a?(Puppet::Pops::Model::Nop) end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/model/tree_dumper.rb��������������������������������������������������0000644�0052762�0001160�00000002360�13417161721�022017� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Base class for formatted textual dump of a "model" # class Puppet::Pops::Model::TreeDumper attr_accessor :indent_count def initialize initial_indentation = 0 @@dump_visitor ||= Puppet::Pops::Visitor.new(nil,"dump",0,0) @indent_count = initial_indentation end def dump(o) format(do_dump(o)) end def do_dump(o) @@dump_visitor.visit_this_0(self, o) end def indent " " * indent_count end def format(x) result = "" parts = format_r(x) parts.each_index do |i| if i > 0 # separate with space unless previous ends with whitespace or ( result << ' ' if parts[i] != ")" && parts[i-1] !~ /.*(?:\s+|\()$/ && parts[i] !~ /^\s+/ end result << parts[i].to_s end result end def format_r(x) result = [] case x when :break result << "\n" + indent when :indent @indent_count += 1 when :dedent @indent_count -= 1 when Array result << '(' result += x.collect {|a| format_r(a) }.flatten result << ')' when Symbol result << x.to_s # Allows Symbols in arrays e.g. ["text", =>, "text"] else result << x end result end def is_nop? o o.nil? || o.is_a?(Puppet::Pops::Model::Nop) end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/model/ast.pp����������������������������������������������������������0000644�0052762�0001160�00000037767�13417161721�020332� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������type Puppet::AST = TypeSet[{ pcore_version => '1.0.0', types => { Locator => Object[{ attributes => { 'string' => String, 'file' => String, 'line_index' => { type => Optional[Array[Integer]], value => undef } } }], PopsObject => Object[{ }], Positioned => Object[{ parent => PopsObject, attributes => { 'locator' => { type => Locator, kind => reference }, 'offset' => Integer, 'length' => Integer, 'file' => { type => String, kind => derived, annotations => { RubyMethod => { 'body' => '@locator.file' } } }, 'line' => { type => Integer, kind => derived, annotations => { RubyMethod => { 'body' => '@locator.line_for_offset(@offset)' } } }, 'pos' => { type => Integer, kind => derived, annotations => { RubyMethod => { 'body' => '@locator.pos_on_line(@offset)' } } } }, equality => [] }], Expression => Object[{ parent => Positioned }], Nop => Object[{ parent => Expression }], BinaryExpression => Object[{ parent => Expression, attributes => { 'left_expr' => Expression, 'right_expr' => Expression } }], UnaryExpression => Object[{ parent => Expression, attributes => { 'expr' => Expression } }], ParenthesizedExpression => Object[{ parent => UnaryExpression }], NotExpression => Object[{ parent => UnaryExpression }], UnaryMinusExpression => Object[{ parent => UnaryExpression }], UnfoldExpression => Object[{ parent => UnaryExpression }], AssignmentExpression => Object[{ parent => BinaryExpression, attributes => { 'operator' => Enum['+=', '-=', '='] } }], ArithmeticExpression => Object[{ parent => BinaryExpression, attributes => { 'operator' => Enum['%', '*', '+', '-', '/', '<<', '>>'] } }], RelationshipExpression => Object[{ parent => BinaryExpression, attributes => { 'operator' => Enum['->', '<-', '<~', '~>'] } }], AccessExpression => Object[{ parent => Expression, attributes => { 'left_expr' => Expression, 'keys' => { type => Array[Expression], value => [] } } }], ComparisonExpression => Object[{ parent => BinaryExpression, attributes => { 'operator' => Enum['!=', '<', '<=', '==', '>', '>='] } }], MatchExpression => Object[{ parent => BinaryExpression, attributes => { 'operator' => Enum['!~', '=~'] } }], InExpression => Object[{ parent => BinaryExpression }], BooleanExpression => Object[{ parent => BinaryExpression }], AndExpression => Object[{ parent => BooleanExpression }], OrExpression => Object[{ parent => BooleanExpression }], LiteralList => Object[{ parent => Expression, attributes => { 'values' => { type => Array[Expression], value => [] } } }], KeyedEntry => Object[{ parent => Positioned, attributes => { 'key' => Expression, 'value' => Expression } }], LiteralHash => Object[{ parent => Expression, attributes => { 'entries' => { type => Array[KeyedEntry], value => [] } } }], BlockExpression => Object[{ parent => Expression, attributes => { 'statements' => { type => Array[Expression], value => [] } } }], CaseOption => Object[{ parent => Expression, attributes => { 'values' => Array[Expression, 1, default], 'then_expr' => { type => Optional[Expression], value => undef } } }], CaseExpression => Object[{ parent => Expression, attributes => { 'test' => Expression, 'options' => { type => Array[CaseOption], value => [] } } }], QueryExpression => Object[{ parent => Expression, attributes => { 'expr' => { type => Optional[Expression], value => undef } } }], ExportedQuery => Object[{ parent => QueryExpression }], VirtualQuery => Object[{ parent => QueryExpression }], AbstractAttributeOperation => Object[{ parent => Positioned }], AttributeOperation => Object[{ parent => AbstractAttributeOperation, attributes => { 'attribute_name' => String, 'operator' => Enum['+>', '=>'], 'value_expr' => Expression } }], AttributesOperation => Object[{ parent => AbstractAttributeOperation, attributes => { 'expr' => Expression } }], CollectExpression => Object[{ parent => Expression, attributes => { 'type_expr' => Expression, 'query' => QueryExpression, 'operations' => { type => Array[AbstractAttributeOperation], value => [] } } }], Parameter => Object[{ parent => Positioned, attributes => { 'name' => String, 'value' => { type => Optional[Expression], value => undef }, 'type_expr' => { type => Optional[Expression], value => undef }, 'captures_rest' => { type => Optional[Boolean], value => undef } } }], Definition => Object[{ parent => Expression }], NamedDefinition => Object[{ parent => Definition, attributes => { 'name' => String, 'parameters' => { type => Array[Parameter], value => [] }, 'body' => { type => Optional[Expression], value => undef } } }], FunctionDefinition => Object[{ parent => NamedDefinition, attributes => { 'return_type' => { type => Optional[Expression], value => undef } } }], ResourceTypeDefinition => Object[{ parent => NamedDefinition }], Application => Object[{ parent => NamedDefinition }], QRefDefinition => Object[{ parent => Definition, attributes => { 'name' => String } }], TypeAlias => Object[{ parent => QRefDefinition, attributes => { 'type_expr' => { type => Optional[Expression], value => undef } } }], TypeMapping => Object[{ parent => Definition, attributes => { 'type_expr' => { type => Optional[Expression], value => undef }, 'mapping_expr' => { type => Optional[Expression], value => undef } } }], TypeDefinition => Object[{ parent => QRefDefinition, attributes => { 'parent' => { type => Optional[String], value => undef }, 'body' => { type => Optional[Expression], value => undef } } }], NodeDefinition => Object[{ parent => Definition, attributes => { 'parent' => { type => Optional[Expression], value => undef }, 'host_matches' => Array[Expression, 1, default], 'body' => { type => Optional[Expression], value => undef } } }], SiteDefinition => Object[{ parent => Definition, attributes => { 'body' => { type => Optional[Expression], value => undef } } }], SubLocatedExpression => Object[{ parent => Expression, attributes => { 'expr' => Expression, 'line_offsets' => { type => Array[Integer], value => [] }, 'leading_line_count' => { type => Optional[Integer], value => undef }, 'leading_line_offset' => { type => Optional[Integer], value => undef } } }], HeredocExpression => Object[{ parent => Expression, attributes => { 'syntax' => { type => Optional[String], value => undef }, 'text_expr' => Expression } }], HostClassDefinition => Object[{ parent => NamedDefinition, attributes => { 'parent_class' => { type => Optional[String], value => undef } } }], PlanDefinition => Object[{ parent => FunctionDefinition, }], LambdaExpression => Object[{ parent => Expression, attributes => { 'parameters' => { type => Array[Parameter], value => [] }, 'body' => { type => Optional[Expression], value => undef }, 'return_type' => { type => Optional[Expression], value => undef } } }], IfExpression => Object[{ parent => Expression, attributes => { 'test' => Expression, 'then_expr' => { type => Optional[Expression], value => undef }, 'else_expr' => { type => Optional[Expression], value => undef } } }], UnlessExpression => Object[{ parent => IfExpression }], CallExpression => Object[{ parent => Expression, attributes => { 'rval_required' => { type => Boolean, value => false }, 'functor_expr' => Expression, 'arguments' => { type => Array[Expression], value => [] }, 'lambda' => { type => Optional[Expression], value => undef } } }], CallFunctionExpression => Object[{ parent => CallExpression }], CallNamedFunctionExpression => Object[{ parent => CallExpression }], CallMethodExpression => Object[{ parent => CallExpression }], Literal => Object[{ parent => Expression }], LiteralValue => Object[{ parent => Literal }], LiteralRegularExpression => Object[{ parent => LiteralValue, attributes => { 'value' => Any, 'pattern' => String } }], LiteralString => Object[{ parent => LiteralValue, attributes => { 'value' => String } }], LiteralNumber => Object[{ parent => LiteralValue }], LiteralInteger => Object[{ parent => LiteralNumber, attributes => { 'radix' => { type => Integer, value => 10 }, 'value' => Integer } }], LiteralFloat => Object[{ parent => LiteralNumber, attributes => { 'value' => Float } }], LiteralUndef => Object[{ parent => Literal }], LiteralDefault => Object[{ parent => Literal }], LiteralBoolean => Object[{ parent => LiteralValue, attributes => { 'value' => Boolean } }], TextExpression => Object[{ parent => UnaryExpression }], ConcatenatedString => Object[{ parent => Expression, attributes => { 'segments' => { type => Array[Expression], value => [] } } }], QualifiedName => Object[{ parent => LiteralValue, attributes => { 'value' => String } }], ReservedWord => Object[{ parent => LiteralValue, attributes => { 'word' => String, 'future' => { type => Optional[Boolean], value => undef } } }], QualifiedReference => Object[{ parent => LiteralValue, attributes => { 'cased_value' => String, 'value' => { type => String, kind => derived, annotations => { RubyMethod => { 'body' => '@cased_value.downcase' } } } } }], VariableExpression => Object[{ parent => UnaryExpression }], EppExpression => Object[{ parent => Expression, attributes => { 'parameters_specified' => { type => Optional[Boolean], value => undef }, 'body' => { type => Optional[Expression], value => undef } } }], RenderStringExpression => Object[{ parent => LiteralString }], RenderExpression => Object[{ parent => UnaryExpression }], ResourceBody => Object[{ parent => Positioned, attributes => { 'title' => { type => Optional[Expression], value => undef }, 'operations' => { type => Array[AbstractAttributeOperation], value => [] } } }], AbstractResource => Object[{ parent => Expression, attributes => { 'form' => { type => Enum['exported', 'regular', 'virtual'], value => 'regular' }, 'virtual' => { type => Boolean, kind => derived, annotations => { RubyMethod => { 'body' => "@form == 'virtual' || @form == 'exported'" } } }, 'exported' => { type => Boolean, kind => derived, annotations => { RubyMethod => { 'body' => "@form == 'exported'" } } } } }], ResourceExpression => Object[{ parent => AbstractResource, attributes => { 'type_name' => Expression, 'bodies' => { type => Array[ResourceBody], value => [] } } }], CapabilityMapping => Object[{ parent => Definition, attributes => { 'kind' => String, 'capability' => String, 'component' => Expression, 'mappings' => { type => Array[AbstractAttributeOperation], value => [] } } }], ResourceDefaultsExpression => Object[{ parent => AbstractResource, attributes => { 'type_ref' => { type => Optional[Expression], value => undef }, 'operations' => { type => Array[AbstractAttributeOperation], value => [] } } }], ResourceOverrideExpression => Object[{ parent => AbstractResource, attributes => { 'resources' => Expression, 'operations' => { type => Array[AbstractAttributeOperation], value => [] } } }], SelectorEntry => Object[{ parent => Positioned, attributes => { 'matching_expr' => Expression, 'value_expr' => Expression } }], SelectorExpression => Object[{ parent => Expression, attributes => { 'left_expr' => Expression, 'selectors' => { type => Array[SelectorEntry], value => [] } } }], NamedAccessExpression => Object[{ parent => BinaryExpression }], Program => Object[{ parent => PopsObject, attributes => { 'body' => { type => Optional[Expression], value => undef }, 'definitions' => { type => Array[Definition], kind => reference, value => [] }, 'source_text' => { type => String, kind => derived, annotations => { RubyMethod => { 'body' => '@locator.string' } } }, 'source_ref' => { type => String, kind => derived, annotations => { RubyMethod => { 'body' => '@locator.file' } } }, 'line_offsets' => { type => Array[Integer], kind => derived, annotations => { RubyMethod => { 'body' => '@locator.line_index' } } }, 'locator' => Locator } }] } }] ���������puppet-5.5.10/lib/puppet/pops/model/ast.rb����������������������������������������������������������0000644�0052762�0001160�00000433201�13417161721�020275� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# # Generated by Puppet::Pops::Types::RubyGenerator from TypeSet Puppet::AST on -4712-01-01 module Puppet module Pops module Model class PopsObject def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::PopsObject', { }) end include Types::PuppetObject def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::PopsObject initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new end def self.create new end attr_reader :hash def initialize @hash = 2270595461303489901 end def _pcore_init_hash {} end def _pcore_contents end def _pcore_all_contents(path) end def to_s Types::TypeFormatter.string(self) end def eql?(o) o.instance_of?(self.class) end alias == eql? end class Positioned < PopsObject def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::Positioned', { 'parent' => PopsObject._pcore_type, 'attributes' => { 'locator' => { 'type' => Parser::Locator::Locator19._pcore_type, 'kind' => 'reference' }, 'offset' => Types::PIntegerType::DEFAULT, 'length' => Types::PIntegerType::DEFAULT, 'file' => { 'type' => Types::PStringType::DEFAULT, 'kind' => 'derived' }, 'line' => { 'type' => Types::PIntegerType::DEFAULT, 'kind' => 'derived' }, 'pos' => { 'type' => Types::PIntegerType::DEFAULT, 'kind' => 'derived' } }, 'equality' => [] }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::Positioned initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length']) end def self.create(locator, offset, length) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) new(locator, offset, length) end attr_reader :locator attr_reader :offset attr_reader :length def file @locator.file end def line @locator.line_for_offset(@offset) end def pos @locator.pos_on_line(@offset) end def initialize(locator, offset, length) super() @locator = locator @offset = offset @length = length end def _pcore_init_hash result = super result['locator'] = @locator result['offset'] = @offset result['length'] = @length result end end class Expression < Positioned def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::Expression', { 'parent' => Positioned._pcore_type }) end end class Nop < Expression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::Nop', { 'parent' => Expression._pcore_type }) end end class BinaryExpression < Expression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::BinaryExpression', { 'parent' => Expression._pcore_type, 'attributes' => { 'left_expr' => Expression._pcore_type, 'right_expr' => Expression._pcore_type } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::BinaryExpression initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['left_expr'], init_hash['right_expr']) end def self.create(locator, offset, length, left_expr, right_expr) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::BinaryExpression[left_expr]', attrs['left_expr'].type, left_expr) ta.assert_instance_of('Puppet::AST::BinaryExpression[right_expr]', attrs['right_expr'].type, right_expr) new(locator, offset, length, left_expr, right_expr) end attr_reader :left_expr attr_reader :right_expr def initialize(locator, offset, length, left_expr, right_expr) super(locator, offset, length) @hash = @hash ^ left_expr.hash ^ right_expr.hash @left_expr = left_expr @right_expr = right_expr end def _pcore_init_hash result = super result['left_expr'] = @left_expr result['right_expr'] = @right_expr result end def _pcore_contents yield(@left_expr) unless @left_expr.nil? yield(@right_expr) unless @right_expr.nil? end def _pcore_all_contents(path, &block) path << self unless @left_expr.nil? block.call(@left_expr, path) @left_expr._pcore_all_contents(path, &block) end unless @right_expr.nil? block.call(@right_expr, path) @right_expr._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @left_expr.eql?(o.left_expr) && @right_expr.eql?(o.right_expr) end alias == eql? end class UnaryExpression < Expression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::UnaryExpression', { 'parent' => Expression._pcore_type, 'attributes' => { 'expr' => Expression._pcore_type } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::UnaryExpression initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['expr']) end def self.create(locator, offset, length, expr) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::UnaryExpression[expr]', attrs['expr'].type, expr) new(locator, offset, length, expr) end attr_reader :expr def initialize(locator, offset, length, expr) super(locator, offset, length) @hash = @hash ^ expr.hash @expr = expr end def _pcore_init_hash result = super result['expr'] = @expr result end def _pcore_contents yield(@expr) unless @expr.nil? end def _pcore_all_contents(path, &block) path << self unless @expr.nil? block.call(@expr, path) @expr._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @expr.eql?(o.expr) end alias == eql? end class ParenthesizedExpression < UnaryExpression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::ParenthesizedExpression', { 'parent' => UnaryExpression._pcore_type }) end def _pcore_contents yield(@expr) unless @expr.nil? end def _pcore_all_contents(path, &block) path << self unless @expr.nil? block.call(@expr, path) @expr._pcore_all_contents(path, &block) end path.pop end end class NotExpression < UnaryExpression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::NotExpression', { 'parent' => UnaryExpression._pcore_type }) end def _pcore_contents yield(@expr) unless @expr.nil? end def _pcore_all_contents(path, &block) path << self unless @expr.nil? block.call(@expr, path) @expr._pcore_all_contents(path, &block) end path.pop end end class UnaryMinusExpression < UnaryExpression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::UnaryMinusExpression', { 'parent' => UnaryExpression._pcore_type }) end def _pcore_contents yield(@expr) unless @expr.nil? end def _pcore_all_contents(path, &block) path << self unless @expr.nil? block.call(@expr, path) @expr._pcore_all_contents(path, &block) end path.pop end end class UnfoldExpression < UnaryExpression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::UnfoldExpression', { 'parent' => UnaryExpression._pcore_type }) end def _pcore_contents yield(@expr) unless @expr.nil? end def _pcore_all_contents(path, &block) path << self unless @expr.nil? block.call(@expr, path) @expr._pcore_all_contents(path, &block) end path.pop end end class AssignmentExpression < BinaryExpression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::AssignmentExpression', { 'parent' => BinaryExpression._pcore_type, 'attributes' => { 'operator' => Types::PEnumType.new(['+=', '-=', '=']) } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::AssignmentExpression initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['left_expr'], init_hash['right_expr'], init_hash['operator']) end def self.create(locator, offset, length, left_expr, right_expr, operator) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::BinaryExpression[left_expr]', attrs['left_expr'].type, left_expr) ta.assert_instance_of('Puppet::AST::BinaryExpression[right_expr]', attrs['right_expr'].type, right_expr) ta.assert_instance_of('Puppet::AST::AssignmentExpression[operator]', attrs['operator'].type, operator) new(locator, offset, length, left_expr, right_expr, operator) end attr_reader :operator def initialize(locator, offset, length, left_expr, right_expr, operator) super(locator, offset, length, left_expr, right_expr) @hash = @hash ^ operator.hash @operator = operator end def _pcore_init_hash result = super result['operator'] = @operator result end def _pcore_contents yield(@left_expr) unless @left_expr.nil? yield(@right_expr) unless @right_expr.nil? end def _pcore_all_contents(path, &block) path << self unless @left_expr.nil? block.call(@left_expr, path) @left_expr._pcore_all_contents(path, &block) end unless @right_expr.nil? block.call(@right_expr, path) @right_expr._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @operator.eql?(o.operator) end alias == eql? end class ArithmeticExpression < BinaryExpression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::ArithmeticExpression', { 'parent' => BinaryExpression._pcore_type, 'attributes' => { 'operator' => Types::PEnumType.new(['%', '*', '+', '-', '/', '<<', '>>']) } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::ArithmeticExpression initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['left_expr'], init_hash['right_expr'], init_hash['operator']) end def self.create(locator, offset, length, left_expr, right_expr, operator) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::BinaryExpression[left_expr]', attrs['left_expr'].type, left_expr) ta.assert_instance_of('Puppet::AST::BinaryExpression[right_expr]', attrs['right_expr'].type, right_expr) ta.assert_instance_of('Puppet::AST::ArithmeticExpression[operator]', attrs['operator'].type, operator) new(locator, offset, length, left_expr, right_expr, operator) end attr_reader :operator def initialize(locator, offset, length, left_expr, right_expr, operator) super(locator, offset, length, left_expr, right_expr) @hash = @hash ^ operator.hash @operator = operator end def _pcore_init_hash result = super result['operator'] = @operator result end def _pcore_contents yield(@left_expr) unless @left_expr.nil? yield(@right_expr) unless @right_expr.nil? end def _pcore_all_contents(path, &block) path << self unless @left_expr.nil? block.call(@left_expr, path) @left_expr._pcore_all_contents(path, &block) end unless @right_expr.nil? block.call(@right_expr, path) @right_expr._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @operator.eql?(o.operator) end alias == eql? end class RelationshipExpression < BinaryExpression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::RelationshipExpression', { 'parent' => BinaryExpression._pcore_type, 'attributes' => { 'operator' => Types::PEnumType.new(['->', '<-', '<~', '~>']) } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::RelationshipExpression initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['left_expr'], init_hash['right_expr'], init_hash['operator']) end def self.create(locator, offset, length, left_expr, right_expr, operator) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::BinaryExpression[left_expr]', attrs['left_expr'].type, left_expr) ta.assert_instance_of('Puppet::AST::BinaryExpression[right_expr]', attrs['right_expr'].type, right_expr) ta.assert_instance_of('Puppet::AST::RelationshipExpression[operator]', attrs['operator'].type, operator) new(locator, offset, length, left_expr, right_expr, operator) end attr_reader :operator def initialize(locator, offset, length, left_expr, right_expr, operator) super(locator, offset, length, left_expr, right_expr) @hash = @hash ^ operator.hash @operator = operator end def _pcore_init_hash result = super result['operator'] = @operator result end def _pcore_contents yield(@left_expr) unless @left_expr.nil? yield(@right_expr) unless @right_expr.nil? end def _pcore_all_contents(path, &block) path << self unless @left_expr.nil? block.call(@left_expr, path) @left_expr._pcore_all_contents(path, &block) end unless @right_expr.nil? block.call(@right_expr, path) @right_expr._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @operator.eql?(o.operator) end alias == eql? end class AccessExpression < Expression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::AccessExpression', { 'parent' => Expression._pcore_type, 'attributes' => { 'left_expr' => Expression._pcore_type, 'keys' => { 'type' => Types::PArrayType.new(Expression._pcore_type), 'value' => [] } } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::AccessExpression initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['left_expr'], init_hash.fetch('keys') { _pcore_type['keys'].value }) end def self.create(locator, offset, length, left_expr, keys = _pcore_type['keys'].value) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::AccessExpression[left_expr]', attrs['left_expr'].type, left_expr) ta.assert_instance_of('Puppet::AST::AccessExpression[keys]', attrs['keys'].type, keys) new(locator, offset, length, left_expr, keys) end attr_reader :left_expr attr_reader :keys def initialize(locator, offset, length, left_expr, keys = _pcore_type['keys'].value) super(locator, offset, length) @hash = @hash ^ left_expr.hash ^ keys.hash @left_expr = left_expr @keys = keys end def _pcore_init_hash result = super result['left_expr'] = @left_expr result['keys'] = @keys unless _pcore_type['keys'].default_value?(@keys) result end def _pcore_contents yield(@left_expr) unless @left_expr.nil? @keys.each { |value| yield(value) } end def _pcore_all_contents(path, &block) path << self unless @left_expr.nil? block.call(@left_expr, path) @left_expr._pcore_all_contents(path, &block) end @keys.each do |value| block.call(value, path) value._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @left_expr.eql?(o.left_expr) && @keys.eql?(o.keys) end alias == eql? end class ComparisonExpression < BinaryExpression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::ComparisonExpression', { 'parent' => BinaryExpression._pcore_type, 'attributes' => { 'operator' => Types::PEnumType.new(['!=', '<', '<=', '==', '>', '>=']) } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::ComparisonExpression initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['left_expr'], init_hash['right_expr'], init_hash['operator']) end def self.create(locator, offset, length, left_expr, right_expr, operator) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::BinaryExpression[left_expr]', attrs['left_expr'].type, left_expr) ta.assert_instance_of('Puppet::AST::BinaryExpression[right_expr]', attrs['right_expr'].type, right_expr) ta.assert_instance_of('Puppet::AST::ComparisonExpression[operator]', attrs['operator'].type, operator) new(locator, offset, length, left_expr, right_expr, operator) end attr_reader :operator def initialize(locator, offset, length, left_expr, right_expr, operator) super(locator, offset, length, left_expr, right_expr) @hash = @hash ^ operator.hash @operator = operator end def _pcore_init_hash result = super result['operator'] = @operator result end def _pcore_contents yield(@left_expr) unless @left_expr.nil? yield(@right_expr) unless @right_expr.nil? end def _pcore_all_contents(path, &block) path << self unless @left_expr.nil? block.call(@left_expr, path) @left_expr._pcore_all_contents(path, &block) end unless @right_expr.nil? block.call(@right_expr, path) @right_expr._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @operator.eql?(o.operator) end alias == eql? end class MatchExpression < BinaryExpression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::MatchExpression', { 'parent' => BinaryExpression._pcore_type, 'attributes' => { 'operator' => Types::PEnumType.new(['!~', '=~']) } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::MatchExpression initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['left_expr'], init_hash['right_expr'], init_hash['operator']) end def self.create(locator, offset, length, left_expr, right_expr, operator) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::BinaryExpression[left_expr]', attrs['left_expr'].type, left_expr) ta.assert_instance_of('Puppet::AST::BinaryExpression[right_expr]', attrs['right_expr'].type, right_expr) ta.assert_instance_of('Puppet::AST::MatchExpression[operator]', attrs['operator'].type, operator) new(locator, offset, length, left_expr, right_expr, operator) end attr_reader :operator def initialize(locator, offset, length, left_expr, right_expr, operator) super(locator, offset, length, left_expr, right_expr) @hash = @hash ^ operator.hash @operator = operator end def _pcore_init_hash result = super result['operator'] = @operator result end def _pcore_contents yield(@left_expr) unless @left_expr.nil? yield(@right_expr) unless @right_expr.nil? end def _pcore_all_contents(path, &block) path << self unless @left_expr.nil? block.call(@left_expr, path) @left_expr._pcore_all_contents(path, &block) end unless @right_expr.nil? block.call(@right_expr, path) @right_expr._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @operator.eql?(o.operator) end alias == eql? end class InExpression < BinaryExpression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::InExpression', { 'parent' => BinaryExpression._pcore_type }) end def _pcore_contents yield(@left_expr) unless @left_expr.nil? yield(@right_expr) unless @right_expr.nil? end def _pcore_all_contents(path, &block) path << self unless @left_expr.nil? block.call(@left_expr, path) @left_expr._pcore_all_contents(path, &block) end unless @right_expr.nil? block.call(@right_expr, path) @right_expr._pcore_all_contents(path, &block) end path.pop end end class BooleanExpression < BinaryExpression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::BooleanExpression', { 'parent' => BinaryExpression._pcore_type }) end def _pcore_contents yield(@left_expr) unless @left_expr.nil? yield(@right_expr) unless @right_expr.nil? end def _pcore_all_contents(path, &block) path << self unless @left_expr.nil? block.call(@left_expr, path) @left_expr._pcore_all_contents(path, &block) end unless @right_expr.nil? block.call(@right_expr, path) @right_expr._pcore_all_contents(path, &block) end path.pop end end class AndExpression < BooleanExpression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::AndExpression', { 'parent' => BooleanExpression._pcore_type }) end def _pcore_contents yield(@left_expr) unless @left_expr.nil? yield(@right_expr) unless @right_expr.nil? end def _pcore_all_contents(path, &block) path << self unless @left_expr.nil? block.call(@left_expr, path) @left_expr._pcore_all_contents(path, &block) end unless @right_expr.nil? block.call(@right_expr, path) @right_expr._pcore_all_contents(path, &block) end path.pop end end class OrExpression < BooleanExpression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::OrExpression', { 'parent' => BooleanExpression._pcore_type }) end def _pcore_contents yield(@left_expr) unless @left_expr.nil? yield(@right_expr) unless @right_expr.nil? end def _pcore_all_contents(path, &block) path << self unless @left_expr.nil? block.call(@left_expr, path) @left_expr._pcore_all_contents(path, &block) end unless @right_expr.nil? block.call(@right_expr, path) @right_expr._pcore_all_contents(path, &block) end path.pop end end class LiteralList < Expression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::LiteralList', { 'parent' => Expression._pcore_type, 'attributes' => { 'values' => { 'type' => Types::PArrayType.new(Expression._pcore_type), 'value' => [] } } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::LiteralList initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash.fetch('values') { _pcore_type['values'].value }) end def self.create(locator, offset, length, values = _pcore_type['values'].value) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::LiteralList[values]', attrs['values'].type, values) new(locator, offset, length, values) end attr_reader :values def initialize(locator, offset, length, values = _pcore_type['values'].value) super(locator, offset, length) @hash = @hash ^ values.hash @values = values end def _pcore_init_hash result = super result['values'] = @values unless _pcore_type['values'].default_value?(@values) result end def _pcore_contents @values.each { |value| yield(value) } end def _pcore_all_contents(path, &block) path << self @values.each do |value| block.call(value, path) value._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @values.eql?(o.values) end alias == eql? end class KeyedEntry < Positioned def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::KeyedEntry', { 'parent' => Positioned._pcore_type, 'attributes' => { 'key' => Expression._pcore_type, 'value' => Expression._pcore_type } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::KeyedEntry initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['key'], init_hash['value']) end def self.create(locator, offset, length, key, value) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::KeyedEntry[key]', attrs['key'].type, key) ta.assert_instance_of('Puppet::AST::KeyedEntry[value]', attrs['value'].type, value) new(locator, offset, length, key, value) end attr_reader :key attr_reader :value def initialize(locator, offset, length, key, value) super(locator, offset, length) @hash = @hash ^ key.hash ^ value.hash @key = key @value = value end def _pcore_init_hash result = super result['key'] = @key result['value'] = @value result end def _pcore_contents yield(@key) unless @key.nil? yield(@value) unless @value.nil? end def _pcore_all_contents(path, &block) path << self unless @key.nil? block.call(@key, path) @key._pcore_all_contents(path, &block) end unless @value.nil? block.call(@value, path) @value._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @key.eql?(o.key) && @value.eql?(o.value) end alias == eql? end class LiteralHash < Expression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::LiteralHash', { 'parent' => Expression._pcore_type, 'attributes' => { 'entries' => { 'type' => Types::PArrayType.new(KeyedEntry._pcore_type), 'value' => [] } } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::LiteralHash initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash.fetch('entries') { _pcore_type['entries'].value }) end def self.create(locator, offset, length, entries = _pcore_type['entries'].value) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::LiteralHash[entries]', attrs['entries'].type, entries) new(locator, offset, length, entries) end attr_reader :entries def initialize(locator, offset, length, entries = _pcore_type['entries'].value) super(locator, offset, length) @hash = @hash ^ entries.hash @entries = entries end def _pcore_init_hash result = super result['entries'] = @entries unless _pcore_type['entries'].default_value?(@entries) result end def _pcore_contents @entries.each { |value| yield(value) } end def _pcore_all_contents(path, &block) path << self @entries.each do |value| block.call(value, path) value._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @entries.eql?(o.entries) end alias == eql? end class BlockExpression < Expression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::BlockExpression', { 'parent' => Expression._pcore_type, 'attributes' => { 'statements' => { 'type' => Types::PArrayType.new(Expression._pcore_type), 'value' => [] } } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::BlockExpression initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash.fetch('statements') { _pcore_type['statements'].value }) end def self.create(locator, offset, length, statements = _pcore_type['statements'].value) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::BlockExpression[statements]', attrs['statements'].type, statements) new(locator, offset, length, statements) end attr_reader :statements def initialize(locator, offset, length, statements = _pcore_type['statements'].value) super(locator, offset, length) @hash = @hash ^ statements.hash @statements = statements end def _pcore_init_hash result = super result['statements'] = @statements unless _pcore_type['statements'].default_value?(@statements) result end def _pcore_contents @statements.each { |value| yield(value) } end def _pcore_all_contents(path, &block) path << self @statements.each do |value| block.call(value, path) value._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @statements.eql?(o.statements) end alias == eql? end class CaseOption < Expression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::CaseOption', { 'parent' => Expression._pcore_type, 'attributes' => { 'values' => Types::PArrayType.new(Expression._pcore_type, Types::PCollectionType::NOT_EMPTY_SIZE), 'then_expr' => { 'type' => Types::POptionalType.new(Expression._pcore_type), 'value' => nil } } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::CaseOption initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['values'], init_hash['then_expr']) end def self.create(locator, offset, length, values, then_expr = nil) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::CaseOption[values]', attrs['values'].type, values) ta.assert_instance_of('Puppet::AST::CaseOption[then_expr]', attrs['then_expr'].type, then_expr) new(locator, offset, length, values, then_expr) end attr_reader :values attr_reader :then_expr def initialize(locator, offset, length, values, then_expr = nil) super(locator, offset, length) @hash = @hash ^ values.hash ^ then_expr.hash @values = values @then_expr = then_expr end def _pcore_init_hash result = super result['values'] = @values result['then_expr'] = @then_expr unless @then_expr == nil result end def _pcore_contents @values.each { |value| yield(value) } yield(@then_expr) unless @then_expr.nil? end def _pcore_all_contents(path, &block) path << self @values.each do |value| block.call(value, path) value._pcore_all_contents(path, &block) end unless @then_expr.nil? block.call(@then_expr, path) @then_expr._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @values.eql?(o.values) && @then_expr.eql?(o.then_expr) end alias == eql? end class CaseExpression < Expression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::CaseExpression', { 'parent' => Expression._pcore_type, 'attributes' => { 'test' => Expression._pcore_type, 'options' => { 'type' => Types::PArrayType.new(CaseOption._pcore_type), 'value' => [] } } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::CaseExpression initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['test'], init_hash.fetch('options') { _pcore_type['options'].value }) end def self.create(locator, offset, length, test, options = _pcore_type['options'].value) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::CaseExpression[test]', attrs['test'].type, test) ta.assert_instance_of('Puppet::AST::CaseExpression[options]', attrs['options'].type, options) new(locator, offset, length, test, options) end attr_reader :test attr_reader :options def initialize(locator, offset, length, test, options = _pcore_type['options'].value) super(locator, offset, length) @hash = @hash ^ test.hash ^ options.hash @test = test @options = options end def _pcore_init_hash result = super result['test'] = @test result['options'] = @options unless _pcore_type['options'].default_value?(@options) result end def _pcore_contents yield(@test) unless @test.nil? @options.each { |value| yield(value) } end def _pcore_all_contents(path, &block) path << self unless @test.nil? block.call(@test, path) @test._pcore_all_contents(path, &block) end @options.each do |value| block.call(value, path) value._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @test.eql?(o.test) && @options.eql?(o.options) end alias == eql? end class QueryExpression < Expression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::QueryExpression', { 'parent' => Expression._pcore_type, 'attributes' => { 'expr' => { 'type' => Types::POptionalType.new(Expression._pcore_type), 'value' => nil } } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::QueryExpression initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['expr']) end def self.create(locator, offset, length, expr = nil) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::QueryExpression[expr]', attrs['expr'].type, expr) new(locator, offset, length, expr) end attr_reader :expr def initialize(locator, offset, length, expr = nil) super(locator, offset, length) @hash = @hash ^ expr.hash @expr = expr end def _pcore_init_hash result = super result['expr'] = @expr unless @expr == nil result end def _pcore_contents yield(@expr) unless @expr.nil? end def _pcore_all_contents(path, &block) path << self unless @expr.nil? block.call(@expr, path) @expr._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @expr.eql?(o.expr) end alias == eql? end class ExportedQuery < QueryExpression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::ExportedQuery', { 'parent' => QueryExpression._pcore_type }) end def _pcore_contents yield(@expr) unless @expr.nil? end def _pcore_all_contents(path, &block) path << self unless @expr.nil? block.call(@expr, path) @expr._pcore_all_contents(path, &block) end path.pop end end class VirtualQuery < QueryExpression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::VirtualQuery', { 'parent' => QueryExpression._pcore_type }) end def _pcore_contents yield(@expr) unless @expr.nil? end def _pcore_all_contents(path, &block) path << self unless @expr.nil? block.call(@expr, path) @expr._pcore_all_contents(path, &block) end path.pop end end class AbstractAttributeOperation < Positioned def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::AbstractAttributeOperation', { 'parent' => Positioned._pcore_type }) end end class AttributeOperation < AbstractAttributeOperation def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::AttributeOperation', { 'parent' => AbstractAttributeOperation._pcore_type, 'attributes' => { 'attribute_name' => Types::PStringType::DEFAULT, 'operator' => Types::PEnumType.new(['+>', '=>']), 'value_expr' => Expression._pcore_type } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::AttributeOperation initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['attribute_name'], init_hash['operator'], init_hash['value_expr']) end def self.create(locator, offset, length, attribute_name, operator, value_expr) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::AttributeOperation[attribute_name]', attrs['attribute_name'].type, attribute_name) ta.assert_instance_of('Puppet::AST::AttributeOperation[operator]', attrs['operator'].type, operator) ta.assert_instance_of('Puppet::AST::AttributeOperation[value_expr]', attrs['value_expr'].type, value_expr) new(locator, offset, length, attribute_name, operator, value_expr) end attr_reader :attribute_name attr_reader :operator attr_reader :value_expr def initialize(locator, offset, length, attribute_name, operator, value_expr) super(locator, offset, length) @hash = @hash ^ attribute_name.hash ^ operator.hash ^ value_expr.hash @attribute_name = attribute_name @operator = operator @value_expr = value_expr end def _pcore_init_hash result = super result['attribute_name'] = @attribute_name result['operator'] = @operator result['value_expr'] = @value_expr result end def _pcore_contents yield(@value_expr) unless @value_expr.nil? end def _pcore_all_contents(path, &block) path << self unless @value_expr.nil? block.call(@value_expr, path) @value_expr._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @attribute_name.eql?(o.attribute_name) && @operator.eql?(o.operator) && @value_expr.eql?(o.value_expr) end alias == eql? end class AttributesOperation < AbstractAttributeOperation def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::AttributesOperation', { 'parent' => AbstractAttributeOperation._pcore_type, 'attributes' => { 'expr' => Expression._pcore_type } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::AttributesOperation initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['expr']) end def self.create(locator, offset, length, expr) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::AttributesOperation[expr]', attrs['expr'].type, expr) new(locator, offset, length, expr) end attr_reader :expr def initialize(locator, offset, length, expr) super(locator, offset, length) @hash = @hash ^ expr.hash @expr = expr end def _pcore_init_hash result = super result['expr'] = @expr result end def _pcore_contents yield(@expr) unless @expr.nil? end def _pcore_all_contents(path, &block) path << self unless @expr.nil? block.call(@expr, path) @expr._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @expr.eql?(o.expr) end alias == eql? end class CollectExpression < Expression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::CollectExpression', { 'parent' => Expression._pcore_type, 'attributes' => { 'type_expr' => Expression._pcore_type, 'query' => QueryExpression._pcore_type, 'operations' => { 'type' => Types::PArrayType.new(AbstractAttributeOperation._pcore_type), 'value' => [] } } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::CollectExpression initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['type_expr'], init_hash['query'], init_hash.fetch('operations') { _pcore_type['operations'].value }) end def self.create(locator, offset, length, type_expr, query, operations = _pcore_type['operations'].value) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::CollectExpression[type_expr]', attrs['type_expr'].type, type_expr) ta.assert_instance_of('Puppet::AST::CollectExpression[query]', attrs['query'].type, query) ta.assert_instance_of('Puppet::AST::CollectExpression[operations]', attrs['operations'].type, operations) new(locator, offset, length, type_expr, query, operations) end attr_reader :type_expr attr_reader :query attr_reader :operations def initialize(locator, offset, length, type_expr, query, operations = _pcore_type['operations'].value) super(locator, offset, length) @hash = @hash ^ type_expr.hash ^ query.hash ^ operations.hash @type_expr = type_expr @query = query @operations = operations end def _pcore_init_hash result = super result['type_expr'] = @type_expr result['query'] = @query result['operations'] = @operations unless _pcore_type['operations'].default_value?(@operations) result end def _pcore_contents yield(@type_expr) unless @type_expr.nil? yield(@query) unless @query.nil? @operations.each { |value| yield(value) } end def _pcore_all_contents(path, &block) path << self unless @type_expr.nil? block.call(@type_expr, path) @type_expr._pcore_all_contents(path, &block) end unless @query.nil? block.call(@query, path) @query._pcore_all_contents(path, &block) end @operations.each do |value| block.call(value, path) value._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @type_expr.eql?(o.type_expr) && @query.eql?(o.query) && @operations.eql?(o.operations) end alias == eql? end class Parameter < Positioned def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::Parameter', { 'parent' => Positioned._pcore_type, 'attributes' => { 'name' => Types::PStringType::DEFAULT, 'value' => { 'type' => Types::POptionalType.new(Expression._pcore_type), 'value' => nil }, 'type_expr' => { 'type' => Types::POptionalType.new(Expression._pcore_type), 'value' => nil }, 'captures_rest' => { 'type' => Types::POptionalType.new(Types::PBooleanType::DEFAULT), 'value' => nil } } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::Parameter initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['name'], init_hash['value'], init_hash['type_expr'], init_hash['captures_rest']) end def self.create(locator, offset, length, name, value = nil, type_expr = nil, captures_rest = nil) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::Parameter[name]', attrs['name'].type, name) ta.assert_instance_of('Puppet::AST::Parameter[value]', attrs['value'].type, value) ta.assert_instance_of('Puppet::AST::Parameter[type_expr]', attrs['type_expr'].type, type_expr) ta.assert_instance_of('Puppet::AST::Parameter[captures_rest]', attrs['captures_rest'].type, captures_rest) new(locator, offset, length, name, value, type_expr, captures_rest) end attr_reader :name attr_reader :value attr_reader :type_expr attr_reader :captures_rest def initialize(locator, offset, length, name, value = nil, type_expr = nil, captures_rest = nil) super(locator, offset, length) @hash = @hash ^ name.hash ^ value.hash ^ type_expr.hash ^ captures_rest.hash @name = name @value = value @type_expr = type_expr @captures_rest = captures_rest end def _pcore_init_hash result = super result['name'] = @name result['value'] = @value unless @value == nil result['type_expr'] = @type_expr unless @type_expr == nil result['captures_rest'] = @captures_rest unless @captures_rest == nil result end def _pcore_contents yield(@value) unless @value.nil? yield(@type_expr) unless @type_expr.nil? end def _pcore_all_contents(path, &block) path << self unless @value.nil? block.call(@value, path) @value._pcore_all_contents(path, &block) end unless @type_expr.nil? block.call(@type_expr, path) @type_expr._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @name.eql?(o.name) && @value.eql?(o.value) && @type_expr.eql?(o.type_expr) && @captures_rest.eql?(o.captures_rest) end alias == eql? end class Definition < Expression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::Definition', { 'parent' => Expression._pcore_type }) end end class NamedDefinition < Definition def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::NamedDefinition', { 'parent' => Definition._pcore_type, 'attributes' => { 'name' => Types::PStringType::DEFAULT, 'parameters' => { 'type' => Types::PArrayType.new(Parameter._pcore_type), 'value' => [] }, 'body' => { 'type' => Types::POptionalType.new(Expression._pcore_type), 'value' => nil } } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::NamedDefinition initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['name'], init_hash.fetch('parameters') { _pcore_type['parameters'].value }, init_hash['body']) end def self.create(locator, offset, length, name, parameters = _pcore_type['parameters'].value, body = nil) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::NamedDefinition[name]', attrs['name'].type, name) ta.assert_instance_of('Puppet::AST::NamedDefinition[parameters]', attrs['parameters'].type, parameters) ta.assert_instance_of('Puppet::AST::NamedDefinition[body]', attrs['body'].type, body) new(locator, offset, length, name, parameters, body) end attr_reader :name attr_reader :parameters attr_reader :body def initialize(locator, offset, length, name, parameters = _pcore_type['parameters'].value, body = nil) super(locator, offset, length) @hash = @hash ^ name.hash ^ parameters.hash ^ body.hash @name = name @parameters = parameters @body = body end def _pcore_init_hash result = super result['name'] = @name result['parameters'] = @parameters unless _pcore_type['parameters'].default_value?(@parameters) result['body'] = @body unless @body == nil result end def _pcore_contents @parameters.each { |value| yield(value) } yield(@body) unless @body.nil? end def _pcore_all_contents(path, &block) path << self @parameters.each do |value| block.call(value, path) value._pcore_all_contents(path, &block) end unless @body.nil? block.call(@body, path) @body._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @name.eql?(o.name) && @parameters.eql?(o.parameters) && @body.eql?(o.body) end alias == eql? end class FunctionDefinition < NamedDefinition def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::FunctionDefinition', { 'parent' => NamedDefinition._pcore_type, 'attributes' => { 'return_type' => { 'type' => Types::POptionalType.new(Expression._pcore_type), 'value' => nil } } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::FunctionDefinition initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['name'], init_hash.fetch('parameters') { _pcore_type['parameters'].value }, init_hash['body'], init_hash['return_type']) end def self.create(locator, offset, length, name, parameters = _pcore_type['parameters'].value, body = nil, return_type = nil) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::NamedDefinition[name]', attrs['name'].type, name) ta.assert_instance_of('Puppet::AST::NamedDefinition[parameters]', attrs['parameters'].type, parameters) ta.assert_instance_of('Puppet::AST::NamedDefinition[body]', attrs['body'].type, body) ta.assert_instance_of('Puppet::AST::FunctionDefinition[return_type]', attrs['return_type'].type, return_type) new(locator, offset, length, name, parameters, body, return_type) end attr_reader :return_type def initialize(locator, offset, length, name, parameters = _pcore_type['parameters'].value, body = nil, return_type = nil) super(locator, offset, length, name, parameters, body) @hash = @hash ^ return_type.hash @return_type = return_type end def _pcore_init_hash result = super result['return_type'] = @return_type unless @return_type == nil result end def _pcore_contents @parameters.each { |value| yield(value) } yield(@body) unless @body.nil? yield(@return_type) unless @return_type.nil? end def _pcore_all_contents(path, &block) path << self @parameters.each do |value| block.call(value, path) value._pcore_all_contents(path, &block) end unless @body.nil? block.call(@body, path) @body._pcore_all_contents(path, &block) end unless @return_type.nil? block.call(@return_type, path) @return_type._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @return_type.eql?(o.return_type) end alias == eql? end class ResourceTypeDefinition < NamedDefinition def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::ResourceTypeDefinition', { 'parent' => NamedDefinition._pcore_type }) end def _pcore_contents @parameters.each { |value| yield(value) } yield(@body) unless @body.nil? end def _pcore_all_contents(path, &block) path << self @parameters.each do |value| block.call(value, path) value._pcore_all_contents(path, &block) end unless @body.nil? block.call(@body, path) @body._pcore_all_contents(path, &block) end path.pop end end class Application < NamedDefinition def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::Application', { 'parent' => NamedDefinition._pcore_type }) end def _pcore_contents @parameters.each { |value| yield(value) } yield(@body) unless @body.nil? end def _pcore_all_contents(path, &block) path << self @parameters.each do |value| block.call(value, path) value._pcore_all_contents(path, &block) end unless @body.nil? block.call(@body, path) @body._pcore_all_contents(path, &block) end path.pop end end class QRefDefinition < Definition def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::QRefDefinition', { 'parent' => Definition._pcore_type, 'attributes' => { 'name' => Types::PStringType::DEFAULT } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::QRefDefinition initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['name']) end def self.create(locator, offset, length, name) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::QRefDefinition[name]', attrs['name'].type, name) new(locator, offset, length, name) end attr_reader :name def initialize(locator, offset, length, name) super(locator, offset, length) @hash = @hash ^ name.hash @name = name end def _pcore_init_hash result = super result['name'] = @name result end def eql?(o) super && @name.eql?(o.name) end alias == eql? end class TypeAlias < QRefDefinition def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::TypeAlias', { 'parent' => QRefDefinition._pcore_type, 'attributes' => { 'type_expr' => { 'type' => Types::POptionalType.new(Expression._pcore_type), 'value' => nil } } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::TypeAlias initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['name'], init_hash['type_expr']) end def self.create(locator, offset, length, name, type_expr = nil) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::QRefDefinition[name]', attrs['name'].type, name) ta.assert_instance_of('Puppet::AST::TypeAlias[type_expr]', attrs['type_expr'].type, type_expr) new(locator, offset, length, name, type_expr) end attr_reader :type_expr def initialize(locator, offset, length, name, type_expr = nil) super(locator, offset, length, name) @hash = @hash ^ type_expr.hash @type_expr = type_expr end def _pcore_init_hash result = super result['type_expr'] = @type_expr unless @type_expr == nil result end def _pcore_contents yield(@type_expr) unless @type_expr.nil? end def _pcore_all_contents(path, &block) path << self unless @type_expr.nil? block.call(@type_expr, path) @type_expr._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @type_expr.eql?(o.type_expr) end alias == eql? end class TypeMapping < Definition def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::TypeMapping', { 'parent' => Definition._pcore_type, 'attributes' => { 'type_expr' => { 'type' => Types::POptionalType.new(Expression._pcore_type), 'value' => nil }, 'mapping_expr' => { 'type' => Types::POptionalType.new(Expression._pcore_type), 'value' => nil } } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::TypeMapping initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['type_expr'], init_hash['mapping_expr']) end def self.create(locator, offset, length, type_expr = nil, mapping_expr = nil) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::TypeMapping[type_expr]', attrs['type_expr'].type, type_expr) ta.assert_instance_of('Puppet::AST::TypeMapping[mapping_expr]', attrs['mapping_expr'].type, mapping_expr) new(locator, offset, length, type_expr, mapping_expr) end attr_reader :type_expr attr_reader :mapping_expr def initialize(locator, offset, length, type_expr = nil, mapping_expr = nil) super(locator, offset, length) @hash = @hash ^ type_expr.hash ^ mapping_expr.hash @type_expr = type_expr @mapping_expr = mapping_expr end def _pcore_init_hash result = super result['type_expr'] = @type_expr unless @type_expr == nil result['mapping_expr'] = @mapping_expr unless @mapping_expr == nil result end def _pcore_contents yield(@type_expr) unless @type_expr.nil? yield(@mapping_expr) unless @mapping_expr.nil? end def _pcore_all_contents(path, &block) path << self unless @type_expr.nil? block.call(@type_expr, path) @type_expr._pcore_all_contents(path, &block) end unless @mapping_expr.nil? block.call(@mapping_expr, path) @mapping_expr._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @type_expr.eql?(o.type_expr) && @mapping_expr.eql?(o.mapping_expr) end alias == eql? end class TypeDefinition < QRefDefinition def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::TypeDefinition', { 'parent' => QRefDefinition._pcore_type, 'attributes' => { 'parent' => { 'type' => Types::POptionalType.new(Types::PStringType::DEFAULT), 'value' => nil }, 'body' => { 'type' => Types::POptionalType.new(Expression._pcore_type), 'value' => nil } } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::TypeDefinition initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['name'], init_hash['parent'], init_hash['body']) end def self.create(locator, offset, length, name, parent = nil, body = nil) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::QRefDefinition[name]', attrs['name'].type, name) ta.assert_instance_of('Puppet::AST::TypeDefinition[parent]', attrs['parent'].type, parent) ta.assert_instance_of('Puppet::AST::TypeDefinition[body]', attrs['body'].type, body) new(locator, offset, length, name, parent, body) end attr_reader :parent attr_reader :body def initialize(locator, offset, length, name, parent = nil, body = nil) super(locator, offset, length, name) @hash = @hash ^ parent.hash ^ body.hash @parent = parent @body = body end def _pcore_init_hash result = super result['parent'] = @parent unless @parent == nil result['body'] = @body unless @body == nil result end def _pcore_contents yield(@body) unless @body.nil? end def _pcore_all_contents(path, &block) path << self unless @body.nil? block.call(@body, path) @body._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @parent.eql?(o.parent) && @body.eql?(o.body) end alias == eql? end class NodeDefinition < Definition def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::NodeDefinition', { 'parent' => Definition._pcore_type, 'attributes' => { 'parent' => { 'type' => Types::POptionalType.new(Expression._pcore_type), 'value' => nil }, 'host_matches' => Types::PArrayType.new(Expression._pcore_type, Types::PCollectionType::NOT_EMPTY_SIZE), 'body' => { 'type' => Types::POptionalType.new(Expression._pcore_type), 'value' => nil } } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::NodeDefinition initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['host_matches'], init_hash['parent'], init_hash['body']) end def self.create(locator, offset, length, host_matches, parent = nil, body = nil) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::NodeDefinition[parent]', attrs['parent'].type, parent) ta.assert_instance_of('Puppet::AST::NodeDefinition[host_matches]', attrs['host_matches'].type, host_matches) ta.assert_instance_of('Puppet::AST::NodeDefinition[body]', attrs['body'].type, body) new(locator, offset, length, host_matches, parent, body) end attr_reader :parent attr_reader :host_matches attr_reader :body def initialize(locator, offset, length, host_matches, parent = nil, body = nil) super(locator, offset, length) @hash = @hash ^ parent.hash ^ host_matches.hash ^ body.hash @parent = parent @host_matches = host_matches @body = body end def _pcore_init_hash result = super result['parent'] = @parent unless @parent == nil result['host_matches'] = @host_matches result['body'] = @body unless @body == nil result end def _pcore_contents yield(@parent) unless @parent.nil? @host_matches.each { |value| yield(value) } yield(@body) unless @body.nil? end def _pcore_all_contents(path, &block) path << self unless @parent.nil? block.call(@parent, path) @parent._pcore_all_contents(path, &block) end @host_matches.each do |value| block.call(value, path) value._pcore_all_contents(path, &block) end unless @body.nil? block.call(@body, path) @body._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @parent.eql?(o.parent) && @host_matches.eql?(o.host_matches) && @body.eql?(o.body) end alias == eql? end class SiteDefinition < Definition def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::SiteDefinition', { 'parent' => Definition._pcore_type, 'attributes' => { 'body' => { 'type' => Types::POptionalType.new(Expression._pcore_type), 'value' => nil } } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::SiteDefinition initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['body']) end def self.create(locator, offset, length, body = nil) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::SiteDefinition[body]', attrs['body'].type, body) new(locator, offset, length, body) end attr_reader :body def initialize(locator, offset, length, body = nil) super(locator, offset, length) @hash = @hash ^ body.hash @body = body end def _pcore_init_hash result = super result['body'] = @body unless @body == nil result end def _pcore_contents yield(@body) unless @body.nil? end def _pcore_all_contents(path, &block) path << self unless @body.nil? block.call(@body, path) @body._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @body.eql?(o.body) end alias == eql? end class SubLocatedExpression < Expression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::SubLocatedExpression', { 'parent' => Expression._pcore_type, 'attributes' => { 'expr' => Expression._pcore_type, 'line_offsets' => { 'type' => Types::PArrayType.new(Types::PIntegerType::DEFAULT), 'value' => [] }, 'leading_line_count' => { 'type' => Types::POptionalType.new(Types::PIntegerType::DEFAULT), 'value' => nil }, 'leading_line_offset' => { 'type' => Types::POptionalType.new(Types::PIntegerType::DEFAULT), 'value' => nil } } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::SubLocatedExpression initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['expr'], init_hash.fetch('line_offsets') { _pcore_type['line_offsets'].value }, init_hash['leading_line_count'], init_hash['leading_line_offset']) end def self.create(locator, offset, length, expr, line_offsets = _pcore_type['line_offsets'].value, leading_line_count = nil, leading_line_offset = nil) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::SubLocatedExpression[expr]', attrs['expr'].type, expr) ta.assert_instance_of('Puppet::AST::SubLocatedExpression[line_offsets]', attrs['line_offsets'].type, line_offsets) ta.assert_instance_of('Puppet::AST::SubLocatedExpression[leading_line_count]', attrs['leading_line_count'].type, leading_line_count) ta.assert_instance_of('Puppet::AST::SubLocatedExpression[leading_line_offset]', attrs['leading_line_offset'].type, leading_line_offset) new(locator, offset, length, expr, line_offsets, leading_line_count, leading_line_offset) end attr_reader :expr attr_reader :line_offsets attr_reader :leading_line_count attr_reader :leading_line_offset def initialize(locator, offset, length, expr, line_offsets = _pcore_type['line_offsets'].value, leading_line_count = nil, leading_line_offset = nil) super(locator, offset, length) @hash = @hash ^ expr.hash ^ line_offsets.hash ^ leading_line_count.hash ^ leading_line_offset.hash @expr = expr @line_offsets = line_offsets @leading_line_count = leading_line_count @leading_line_offset = leading_line_offset end def _pcore_init_hash result = super result['expr'] = @expr result['line_offsets'] = @line_offsets unless _pcore_type['line_offsets'].default_value?(@line_offsets) result['leading_line_count'] = @leading_line_count unless @leading_line_count == nil result['leading_line_offset'] = @leading_line_offset unless @leading_line_offset == nil result end def _pcore_contents yield(@expr) unless @expr.nil? end def _pcore_all_contents(path, &block) path << self unless @expr.nil? block.call(@expr, path) @expr._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @expr.eql?(o.expr) && @line_offsets.eql?(o.line_offsets) && @leading_line_count.eql?(o.leading_line_count) && @leading_line_offset.eql?(o.leading_line_offset) end alias == eql? end class HeredocExpression < Expression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::HeredocExpression', { 'parent' => Expression._pcore_type, 'attributes' => { 'syntax' => { 'type' => Types::POptionalType.new(Types::PStringType::DEFAULT), 'value' => nil }, 'text_expr' => Expression._pcore_type } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::HeredocExpression initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['text_expr'], init_hash['syntax']) end def self.create(locator, offset, length, text_expr, syntax = nil) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::HeredocExpression[syntax]', attrs['syntax'].type, syntax) ta.assert_instance_of('Puppet::AST::HeredocExpression[text_expr]', attrs['text_expr'].type, text_expr) new(locator, offset, length, text_expr, syntax) end attr_reader :syntax attr_reader :text_expr def initialize(locator, offset, length, text_expr, syntax = nil) super(locator, offset, length) @hash = @hash ^ syntax.hash ^ text_expr.hash @syntax = syntax @text_expr = text_expr end def _pcore_init_hash result = super result['syntax'] = @syntax unless @syntax == nil result['text_expr'] = @text_expr result end def _pcore_contents yield(@text_expr) unless @text_expr.nil? end def _pcore_all_contents(path, &block) path << self unless @text_expr.nil? block.call(@text_expr, path) @text_expr._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @syntax.eql?(o.syntax) && @text_expr.eql?(o.text_expr) end alias == eql? end class HostClassDefinition < NamedDefinition def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::HostClassDefinition', { 'parent' => NamedDefinition._pcore_type, 'attributes' => { 'parent_class' => { 'type' => Types::POptionalType.new(Types::PStringType::DEFAULT), 'value' => nil } } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::HostClassDefinition initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['name'], init_hash.fetch('parameters') { _pcore_type['parameters'].value }, init_hash['body'], init_hash['parent_class']) end def self.create(locator, offset, length, name, parameters = _pcore_type['parameters'].value, body = nil, parent_class = nil) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::NamedDefinition[name]', attrs['name'].type, name) ta.assert_instance_of('Puppet::AST::NamedDefinition[parameters]', attrs['parameters'].type, parameters) ta.assert_instance_of('Puppet::AST::NamedDefinition[body]', attrs['body'].type, body) ta.assert_instance_of('Puppet::AST::HostClassDefinition[parent_class]', attrs['parent_class'].type, parent_class) new(locator, offset, length, name, parameters, body, parent_class) end attr_reader :parent_class def initialize(locator, offset, length, name, parameters = _pcore_type['parameters'].value, body = nil, parent_class = nil) super(locator, offset, length, name, parameters, body) @hash = @hash ^ parent_class.hash @parent_class = parent_class end def _pcore_init_hash result = super result['parent_class'] = @parent_class unless @parent_class == nil result end def _pcore_contents @parameters.each { |value| yield(value) } yield(@body) unless @body.nil? end def _pcore_all_contents(path, &block) path << self @parameters.each do |value| block.call(value, path) value._pcore_all_contents(path, &block) end unless @body.nil? block.call(@body, path) @body._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @parent_class.eql?(o.parent_class) end alias == eql? end class PlanDefinition < FunctionDefinition def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::PlanDefinition', { 'parent' => FunctionDefinition._pcore_type }) end def _pcore_contents @parameters.each { |value| yield(value) } yield(@body) unless @body.nil? yield(@return_type) unless @return_type.nil? end def _pcore_all_contents(path, &block) path << self @parameters.each do |value| block.call(value, path) value._pcore_all_contents(path, &block) end unless @body.nil? block.call(@body, path) @body._pcore_all_contents(path, &block) end unless @return_type.nil? block.call(@return_type, path) @return_type._pcore_all_contents(path, &block) end path.pop end end class LambdaExpression < Expression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::LambdaExpression', { 'parent' => Expression._pcore_type, 'attributes' => { 'parameters' => { 'type' => Types::PArrayType.new(Parameter._pcore_type), 'value' => [] }, 'body' => { 'type' => Types::POptionalType.new(Expression._pcore_type), 'value' => nil }, 'return_type' => { 'type' => Types::POptionalType.new(Expression._pcore_type), 'value' => nil } } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::LambdaExpression initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash.fetch('parameters') { _pcore_type['parameters'].value }, init_hash['body'], init_hash['return_type']) end def self.create(locator, offset, length, parameters = _pcore_type['parameters'].value, body = nil, return_type = nil) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::LambdaExpression[parameters]', attrs['parameters'].type, parameters) ta.assert_instance_of('Puppet::AST::LambdaExpression[body]', attrs['body'].type, body) ta.assert_instance_of('Puppet::AST::LambdaExpression[return_type]', attrs['return_type'].type, return_type) new(locator, offset, length, parameters, body, return_type) end attr_reader :parameters attr_reader :body attr_reader :return_type def initialize(locator, offset, length, parameters = _pcore_type['parameters'].value, body = nil, return_type = nil) super(locator, offset, length) @hash = @hash ^ parameters.hash ^ body.hash ^ return_type.hash @parameters = parameters @body = body @return_type = return_type end def _pcore_init_hash result = super result['parameters'] = @parameters unless _pcore_type['parameters'].default_value?(@parameters) result['body'] = @body unless @body == nil result['return_type'] = @return_type unless @return_type == nil result end def _pcore_contents @parameters.each { |value| yield(value) } yield(@body) unless @body.nil? yield(@return_type) unless @return_type.nil? end def _pcore_all_contents(path, &block) path << self @parameters.each do |value| block.call(value, path) value._pcore_all_contents(path, &block) end unless @body.nil? block.call(@body, path) @body._pcore_all_contents(path, &block) end unless @return_type.nil? block.call(@return_type, path) @return_type._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @parameters.eql?(o.parameters) && @body.eql?(o.body) && @return_type.eql?(o.return_type) end alias == eql? end class IfExpression < Expression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::IfExpression', { 'parent' => Expression._pcore_type, 'attributes' => { 'test' => Expression._pcore_type, 'then_expr' => { 'type' => Types::POptionalType.new(Expression._pcore_type), 'value' => nil }, 'else_expr' => { 'type' => Types::POptionalType.new(Expression._pcore_type), 'value' => nil } } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::IfExpression initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['test'], init_hash['then_expr'], init_hash['else_expr']) end def self.create(locator, offset, length, test, then_expr = nil, else_expr = nil) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::IfExpression[test]', attrs['test'].type, test) ta.assert_instance_of('Puppet::AST::IfExpression[then_expr]', attrs['then_expr'].type, then_expr) ta.assert_instance_of('Puppet::AST::IfExpression[else_expr]', attrs['else_expr'].type, else_expr) new(locator, offset, length, test, then_expr, else_expr) end attr_reader :test attr_reader :then_expr attr_reader :else_expr def initialize(locator, offset, length, test, then_expr = nil, else_expr = nil) super(locator, offset, length) @hash = @hash ^ test.hash ^ then_expr.hash ^ else_expr.hash @test = test @then_expr = then_expr @else_expr = else_expr end def _pcore_init_hash result = super result['test'] = @test result['then_expr'] = @then_expr unless @then_expr == nil result['else_expr'] = @else_expr unless @else_expr == nil result end def _pcore_contents yield(@test) unless @test.nil? yield(@then_expr) unless @then_expr.nil? yield(@else_expr) unless @else_expr.nil? end def _pcore_all_contents(path, &block) path << self unless @test.nil? block.call(@test, path) @test._pcore_all_contents(path, &block) end unless @then_expr.nil? block.call(@then_expr, path) @then_expr._pcore_all_contents(path, &block) end unless @else_expr.nil? block.call(@else_expr, path) @else_expr._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @test.eql?(o.test) && @then_expr.eql?(o.then_expr) && @else_expr.eql?(o.else_expr) end alias == eql? end class UnlessExpression < IfExpression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::UnlessExpression', { 'parent' => IfExpression._pcore_type }) end def _pcore_contents yield(@test) unless @test.nil? yield(@then_expr) unless @then_expr.nil? yield(@else_expr) unless @else_expr.nil? end def _pcore_all_contents(path, &block) path << self unless @test.nil? block.call(@test, path) @test._pcore_all_contents(path, &block) end unless @then_expr.nil? block.call(@then_expr, path) @then_expr._pcore_all_contents(path, &block) end unless @else_expr.nil? block.call(@else_expr, path) @else_expr._pcore_all_contents(path, &block) end path.pop end end class CallExpression < Expression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::CallExpression', { 'parent' => Expression._pcore_type, 'attributes' => { 'rval_required' => { 'type' => Types::PBooleanType::DEFAULT, 'value' => false }, 'functor_expr' => Expression._pcore_type, 'arguments' => { 'type' => Types::PArrayType.new(Expression._pcore_type), 'value' => [] }, 'lambda' => { 'type' => Types::POptionalType.new(Expression._pcore_type), 'value' => nil } } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::CallExpression initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['functor_expr'], init_hash.fetch('rval_required') { false }, init_hash.fetch('arguments') { _pcore_type['arguments'].value }, init_hash['lambda']) end def self.create(locator, offset, length, functor_expr, rval_required = false, arguments = _pcore_type['arguments'].value, lambda = nil) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::CallExpression[rval_required]', attrs['rval_required'].type, rval_required) ta.assert_instance_of('Puppet::AST::CallExpression[functor_expr]', attrs['functor_expr'].type, functor_expr) ta.assert_instance_of('Puppet::AST::CallExpression[arguments]', attrs['arguments'].type, arguments) ta.assert_instance_of('Puppet::AST::CallExpression[lambda]', attrs['lambda'].type, lambda) new(locator, offset, length, functor_expr, rval_required, arguments, lambda) end attr_reader :rval_required attr_reader :functor_expr attr_reader :arguments attr_reader :lambda def initialize(locator, offset, length, functor_expr, rval_required = false, arguments = _pcore_type['arguments'].value, lambda = nil) super(locator, offset, length) @hash = @hash ^ rval_required.hash ^ functor_expr.hash ^ arguments.hash ^ lambda.hash @rval_required = rval_required @functor_expr = functor_expr @arguments = arguments @lambda = lambda end def _pcore_init_hash result = super result['rval_required'] = @rval_required unless @rval_required == false result['functor_expr'] = @functor_expr result['arguments'] = @arguments unless _pcore_type['arguments'].default_value?(@arguments) result['lambda'] = @lambda unless @lambda == nil result end def _pcore_contents yield(@functor_expr) unless @functor_expr.nil? @arguments.each { |value| yield(value) } yield(@lambda) unless @lambda.nil? end def _pcore_all_contents(path, &block) path << self unless @functor_expr.nil? block.call(@functor_expr, path) @functor_expr._pcore_all_contents(path, &block) end @arguments.each do |value| block.call(value, path) value._pcore_all_contents(path, &block) end unless @lambda.nil? block.call(@lambda, path) @lambda._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @rval_required.eql?(o.rval_required) && @functor_expr.eql?(o.functor_expr) && @arguments.eql?(o.arguments) && @lambda.eql?(o.lambda) end alias == eql? end class CallFunctionExpression < CallExpression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::CallFunctionExpression', { 'parent' => CallExpression._pcore_type }) end def _pcore_contents yield(@functor_expr) unless @functor_expr.nil? @arguments.each { |value| yield(value) } yield(@lambda) unless @lambda.nil? end def _pcore_all_contents(path, &block) path << self unless @functor_expr.nil? block.call(@functor_expr, path) @functor_expr._pcore_all_contents(path, &block) end @arguments.each do |value| block.call(value, path) value._pcore_all_contents(path, &block) end unless @lambda.nil? block.call(@lambda, path) @lambda._pcore_all_contents(path, &block) end path.pop end end class CallNamedFunctionExpression < CallExpression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::CallNamedFunctionExpression', { 'parent' => CallExpression._pcore_type }) end def _pcore_contents yield(@functor_expr) unless @functor_expr.nil? @arguments.each { |value| yield(value) } yield(@lambda) unless @lambda.nil? end def _pcore_all_contents(path, &block) path << self unless @functor_expr.nil? block.call(@functor_expr, path) @functor_expr._pcore_all_contents(path, &block) end @arguments.each do |value| block.call(value, path) value._pcore_all_contents(path, &block) end unless @lambda.nil? block.call(@lambda, path) @lambda._pcore_all_contents(path, &block) end path.pop end end class CallMethodExpression < CallExpression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::CallMethodExpression', { 'parent' => CallExpression._pcore_type }) end def _pcore_contents yield(@functor_expr) unless @functor_expr.nil? @arguments.each { |value| yield(value) } yield(@lambda) unless @lambda.nil? end def _pcore_all_contents(path, &block) path << self unless @functor_expr.nil? block.call(@functor_expr, path) @functor_expr._pcore_all_contents(path, &block) end @arguments.each do |value| block.call(value, path) value._pcore_all_contents(path, &block) end unless @lambda.nil? block.call(@lambda, path) @lambda._pcore_all_contents(path, &block) end path.pop end end class Literal < Expression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::Literal', { 'parent' => Expression._pcore_type }) end end class LiteralValue < Literal def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::LiteralValue', { 'parent' => Literal._pcore_type }) end end class LiteralRegularExpression < LiteralValue def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::LiteralRegularExpression', { 'parent' => LiteralValue._pcore_type, 'attributes' => { 'value' => Types::PAnyType::DEFAULT, 'pattern' => Types::PStringType::DEFAULT } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::LiteralRegularExpression initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['value'], init_hash['pattern']) end def self.create(locator, offset, length, value, pattern) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::LiteralRegularExpression[value]', attrs['value'].type, value) ta.assert_instance_of('Puppet::AST::LiteralRegularExpression[pattern]', attrs['pattern'].type, pattern) new(locator, offset, length, value, pattern) end attr_reader :value attr_reader :pattern def initialize(locator, offset, length, value, pattern) super(locator, offset, length) @hash = @hash ^ value.hash ^ pattern.hash @value = value @pattern = pattern end def _pcore_init_hash result = super result['value'] = @value result['pattern'] = @pattern result end def eql?(o) super && @value.eql?(o.value) && @pattern.eql?(o.pattern) end alias == eql? end class LiteralString < LiteralValue def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::LiteralString', { 'parent' => LiteralValue._pcore_type, 'attributes' => { 'value' => Types::PStringType::DEFAULT } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::LiteralString initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['value']) end def self.create(locator, offset, length, value) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::LiteralString[value]', attrs['value'].type, value) new(locator, offset, length, value) end attr_reader :value def initialize(locator, offset, length, value) super(locator, offset, length) @hash = @hash ^ value.hash @value = value end def _pcore_init_hash result = super result['value'] = @value result end def eql?(o) super && @value.eql?(o.value) end alias == eql? end class LiteralNumber < LiteralValue def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::LiteralNumber', { 'parent' => LiteralValue._pcore_type }) end end class LiteralInteger < LiteralNumber def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::LiteralInteger', { 'parent' => LiteralNumber._pcore_type, 'attributes' => { 'radix' => { 'type' => Types::PIntegerType::DEFAULT, 'value' => 10 }, 'value' => Types::PIntegerType::DEFAULT } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::LiteralInteger initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['value'], init_hash.fetch('radix') { 10 }) end def self.create(locator, offset, length, value, radix = 10) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::LiteralInteger[radix]', attrs['radix'].type, radix) ta.assert_instance_of('Puppet::AST::LiteralInteger[value]', attrs['value'].type, value) new(locator, offset, length, value, radix) end attr_reader :radix attr_reader :value def initialize(locator, offset, length, value, radix = 10) super(locator, offset, length) @hash = @hash ^ radix.hash ^ value.hash @radix = radix @value = value end def _pcore_init_hash result = super result['radix'] = @radix unless @radix == 10 result['value'] = @value result end def eql?(o) super && @radix.eql?(o.radix) && @value.eql?(o.value) end alias == eql? end class LiteralFloat < LiteralNumber def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::LiteralFloat', { 'parent' => LiteralNumber._pcore_type, 'attributes' => { 'value' => Types::PFloatType::DEFAULT } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::LiteralFloat initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['value']) end def self.create(locator, offset, length, value) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::LiteralFloat[value]', attrs['value'].type, value) new(locator, offset, length, value) end attr_reader :value def initialize(locator, offset, length, value) super(locator, offset, length) @hash = @hash ^ value.hash @value = value end def _pcore_init_hash result = super result['value'] = @value result end def eql?(o) super && @value.eql?(o.value) end alias == eql? end class LiteralUndef < Literal def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::LiteralUndef', { 'parent' => Literal._pcore_type }) end end class LiteralDefault < Literal def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::LiteralDefault', { 'parent' => Literal._pcore_type }) end end class LiteralBoolean < LiteralValue def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::LiteralBoolean', { 'parent' => LiteralValue._pcore_type, 'attributes' => { 'value' => Types::PBooleanType::DEFAULT } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::LiteralBoolean initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['value']) end def self.create(locator, offset, length, value) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::LiteralBoolean[value]', attrs['value'].type, value) new(locator, offset, length, value) end attr_reader :value def initialize(locator, offset, length, value) super(locator, offset, length) @hash = @hash ^ value.hash @value = value end def _pcore_init_hash result = super result['value'] = @value result end def eql?(o) super && @value.eql?(o.value) end alias == eql? end class TextExpression < UnaryExpression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::TextExpression', { 'parent' => UnaryExpression._pcore_type }) end def _pcore_contents yield(@expr) unless @expr.nil? end def _pcore_all_contents(path, &block) path << self unless @expr.nil? block.call(@expr, path) @expr._pcore_all_contents(path, &block) end path.pop end end class ConcatenatedString < Expression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::ConcatenatedString', { 'parent' => Expression._pcore_type, 'attributes' => { 'segments' => { 'type' => Types::PArrayType.new(Expression._pcore_type), 'value' => [] } } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::ConcatenatedString initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash.fetch('segments') { _pcore_type['segments'].value }) end def self.create(locator, offset, length, segments = _pcore_type['segments'].value) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::ConcatenatedString[segments]', attrs['segments'].type, segments) new(locator, offset, length, segments) end attr_reader :segments def initialize(locator, offset, length, segments = _pcore_type['segments'].value) super(locator, offset, length) @hash = @hash ^ segments.hash @segments = segments end def _pcore_init_hash result = super result['segments'] = @segments unless _pcore_type['segments'].default_value?(@segments) result end def _pcore_contents @segments.each { |value| yield(value) } end def _pcore_all_contents(path, &block) path << self @segments.each do |value| block.call(value, path) value._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @segments.eql?(o.segments) end alias == eql? end class QualifiedName < LiteralValue def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::QualifiedName', { 'parent' => LiteralValue._pcore_type, 'attributes' => { 'value' => Types::PStringType::DEFAULT } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::QualifiedName initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['value']) end def self.create(locator, offset, length, value) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::QualifiedName[value]', attrs['value'].type, value) new(locator, offset, length, value) end attr_reader :value def initialize(locator, offset, length, value) super(locator, offset, length) @hash = @hash ^ value.hash @value = value end def _pcore_init_hash result = super result['value'] = @value result end def eql?(o) super && @value.eql?(o.value) end alias == eql? end class ReservedWord < LiteralValue def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::ReservedWord', { 'parent' => LiteralValue._pcore_type, 'attributes' => { 'word' => Types::PStringType::DEFAULT, 'future' => { 'type' => Types::POptionalType.new(Types::PBooleanType::DEFAULT), 'value' => nil } } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::ReservedWord initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['word'], init_hash['future']) end def self.create(locator, offset, length, word, future = nil) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::ReservedWord[word]', attrs['word'].type, word) ta.assert_instance_of('Puppet::AST::ReservedWord[future]', attrs['future'].type, future) new(locator, offset, length, word, future) end attr_reader :word attr_reader :future def initialize(locator, offset, length, word, future = nil) super(locator, offset, length) @hash = @hash ^ word.hash ^ future.hash @word = word @future = future end def _pcore_init_hash result = super result['word'] = @word result['future'] = @future unless @future == nil result end def eql?(o) super && @word.eql?(o.word) && @future.eql?(o.future) end alias == eql? end class QualifiedReference < LiteralValue def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::QualifiedReference', { 'parent' => LiteralValue._pcore_type, 'attributes' => { 'cased_value' => Types::PStringType::DEFAULT, 'value' => { 'type' => Types::PStringType::DEFAULT, 'kind' => 'derived' } } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::QualifiedReference initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['cased_value']) end def self.create(locator, offset, length, cased_value) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::QualifiedReference[cased_value]', attrs['cased_value'].type, cased_value) new(locator, offset, length, cased_value) end attr_reader :cased_value def value @cased_value.downcase end def initialize(locator, offset, length, cased_value) super(locator, offset, length) @hash = @hash ^ cased_value.hash @cased_value = cased_value end def _pcore_init_hash result = super result['cased_value'] = @cased_value result end def eql?(o) super && @cased_value.eql?(o.cased_value) end alias == eql? end class VariableExpression < UnaryExpression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::VariableExpression', { 'parent' => UnaryExpression._pcore_type }) end def _pcore_contents yield(@expr) unless @expr.nil? end def _pcore_all_contents(path, &block) path << self unless @expr.nil? block.call(@expr, path) @expr._pcore_all_contents(path, &block) end path.pop end end class EppExpression < Expression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::EppExpression', { 'parent' => Expression._pcore_type, 'attributes' => { 'parameters_specified' => { 'type' => Types::POptionalType.new(Types::PBooleanType::DEFAULT), 'value' => nil }, 'body' => { 'type' => Types::POptionalType.new(Expression._pcore_type), 'value' => nil } } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::EppExpression initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['parameters_specified'], init_hash['body']) end def self.create(locator, offset, length, parameters_specified = nil, body = nil) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::EppExpression[parameters_specified]', attrs['parameters_specified'].type, parameters_specified) ta.assert_instance_of('Puppet::AST::EppExpression[body]', attrs['body'].type, body) new(locator, offset, length, parameters_specified, body) end attr_reader :parameters_specified attr_reader :body def initialize(locator, offset, length, parameters_specified = nil, body = nil) super(locator, offset, length) @hash = @hash ^ parameters_specified.hash ^ body.hash @parameters_specified = parameters_specified @body = body end def _pcore_init_hash result = super result['parameters_specified'] = @parameters_specified unless @parameters_specified == nil result['body'] = @body unless @body == nil result end def _pcore_contents yield(@body) unless @body.nil? end def _pcore_all_contents(path, &block) path << self unless @body.nil? block.call(@body, path) @body._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @parameters_specified.eql?(o.parameters_specified) && @body.eql?(o.body) end alias == eql? end class RenderStringExpression < LiteralString def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::RenderStringExpression', { 'parent' => LiteralString._pcore_type }) end end class RenderExpression < UnaryExpression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::RenderExpression', { 'parent' => UnaryExpression._pcore_type }) end def _pcore_contents yield(@expr) unless @expr.nil? end def _pcore_all_contents(path, &block) path << self unless @expr.nil? block.call(@expr, path) @expr._pcore_all_contents(path, &block) end path.pop end end class ResourceBody < Positioned def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::ResourceBody', { 'parent' => Positioned._pcore_type, 'attributes' => { 'title' => { 'type' => Types::POptionalType.new(Expression._pcore_type), 'value' => nil }, 'operations' => { 'type' => Types::PArrayType.new(AbstractAttributeOperation._pcore_type), 'value' => [] } } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::ResourceBody initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['title'], init_hash.fetch('operations') { _pcore_type['operations'].value }) end def self.create(locator, offset, length, title = nil, operations = _pcore_type['operations'].value) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::ResourceBody[title]', attrs['title'].type, title) ta.assert_instance_of('Puppet::AST::ResourceBody[operations]', attrs['operations'].type, operations) new(locator, offset, length, title, operations) end attr_reader :title attr_reader :operations def initialize(locator, offset, length, title = nil, operations = _pcore_type['operations'].value) super(locator, offset, length) @hash = @hash ^ title.hash ^ operations.hash @title = title @operations = operations end def _pcore_init_hash result = super result['title'] = @title unless @title == nil result['operations'] = @operations unless _pcore_type['operations'].default_value?(@operations) result end def _pcore_contents yield(@title) unless @title.nil? @operations.each { |value| yield(value) } end def _pcore_all_contents(path, &block) path << self unless @title.nil? block.call(@title, path) @title._pcore_all_contents(path, &block) end @operations.each do |value| block.call(value, path) value._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @title.eql?(o.title) && @operations.eql?(o.operations) end alias == eql? end class AbstractResource < Expression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::AbstractResource', { 'parent' => Expression._pcore_type, 'attributes' => { 'form' => { 'type' => Types::PEnumType.new(['exported', 'regular', 'virtual']), 'value' => 'regular' }, 'virtual' => { 'type' => Types::PBooleanType::DEFAULT, 'kind' => 'derived' }, 'exported' => { 'type' => Types::PBooleanType::DEFAULT, 'kind' => 'derived' } } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::AbstractResource initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash.fetch('form') { "regular" }) end def self.create(locator, offset, length, form = "regular") ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::AbstractResource[form]', attrs['form'].type, form) new(locator, offset, length, form) end attr_reader :form def virtual @form == 'virtual' || @form == 'exported' end def exported @form == 'exported' end def initialize(locator, offset, length, form = "regular") super(locator, offset, length) @hash = @hash ^ form.hash @form = form end def _pcore_init_hash result = super result['form'] = @form unless @form == "regular" result end def eql?(o) super && @form.eql?(o.form) end alias == eql? end class ResourceExpression < AbstractResource def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::ResourceExpression', { 'parent' => AbstractResource._pcore_type, 'attributes' => { 'type_name' => Expression._pcore_type, 'bodies' => { 'type' => Types::PArrayType.new(ResourceBody._pcore_type), 'value' => [] } } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::ResourceExpression initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['type_name'], init_hash.fetch('form') { "regular" }, init_hash.fetch('bodies') { _pcore_type['bodies'].value }) end def self.create(locator, offset, length, type_name, form = "regular", bodies = _pcore_type['bodies'].value) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::AbstractResource[form]', attrs['form'].type, form) ta.assert_instance_of('Puppet::AST::ResourceExpression[type_name]', attrs['type_name'].type, type_name) ta.assert_instance_of('Puppet::AST::ResourceExpression[bodies]', attrs['bodies'].type, bodies) new(locator, offset, length, type_name, form, bodies) end attr_reader :type_name attr_reader :bodies def initialize(locator, offset, length, type_name, form = "regular", bodies = _pcore_type['bodies'].value) super(locator, offset, length, form) @hash = @hash ^ type_name.hash ^ bodies.hash @type_name = type_name @bodies = bodies end def _pcore_init_hash result = super result['type_name'] = @type_name result['bodies'] = @bodies unless _pcore_type['bodies'].default_value?(@bodies) result end def _pcore_contents yield(@type_name) unless @type_name.nil? @bodies.each { |value| yield(value) } end def _pcore_all_contents(path, &block) path << self unless @type_name.nil? block.call(@type_name, path) @type_name._pcore_all_contents(path, &block) end @bodies.each do |value| block.call(value, path) value._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @type_name.eql?(o.type_name) && @bodies.eql?(o.bodies) end alias == eql? end class CapabilityMapping < Definition def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::CapabilityMapping', { 'parent' => Definition._pcore_type, 'attributes' => { 'kind' => Types::PStringType::DEFAULT, 'capability' => Types::PStringType::DEFAULT, 'component' => Expression._pcore_type, 'mappings' => { 'type' => Types::PArrayType.new(AbstractAttributeOperation._pcore_type), 'value' => [] } } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::CapabilityMapping initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['kind'], init_hash['capability'], init_hash['component'], init_hash.fetch('mappings') { _pcore_type['mappings'].value }) end def self.create(locator, offset, length, kind, capability, component, mappings = _pcore_type['mappings'].value) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::CapabilityMapping[kind]', attrs['kind'].type, kind) ta.assert_instance_of('Puppet::AST::CapabilityMapping[capability]', attrs['capability'].type, capability) ta.assert_instance_of('Puppet::AST::CapabilityMapping[component]', attrs['component'].type, component) ta.assert_instance_of('Puppet::AST::CapabilityMapping[mappings]', attrs['mappings'].type, mappings) new(locator, offset, length, kind, capability, component, mappings) end attr_reader :kind attr_reader :capability attr_reader :component attr_reader :mappings def initialize(locator, offset, length, kind, capability, component, mappings = _pcore_type['mappings'].value) super(locator, offset, length) @hash = @hash ^ kind.hash ^ capability.hash ^ component.hash ^ mappings.hash @kind = kind @capability = capability @component = component @mappings = mappings end def _pcore_init_hash result = super result['kind'] = @kind result['capability'] = @capability result['component'] = @component result['mappings'] = @mappings unless _pcore_type['mappings'].default_value?(@mappings) result end def _pcore_contents yield(@component) unless @component.nil? @mappings.each { |value| yield(value) } end def _pcore_all_contents(path, &block) path << self unless @component.nil? block.call(@component, path) @component._pcore_all_contents(path, &block) end @mappings.each do |value| block.call(value, path) value._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @kind.eql?(o.kind) && @capability.eql?(o.capability) && @component.eql?(o.component) && @mappings.eql?(o.mappings) end alias == eql? end class ResourceDefaultsExpression < AbstractResource def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::ResourceDefaultsExpression', { 'parent' => AbstractResource._pcore_type, 'attributes' => { 'type_ref' => { 'type' => Types::POptionalType.new(Expression._pcore_type), 'value' => nil }, 'operations' => { 'type' => Types::PArrayType.new(AbstractAttributeOperation._pcore_type), 'value' => [] } } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::ResourceDefaultsExpression initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash.fetch('form') { "regular" }, init_hash['type_ref'], init_hash.fetch('operations') { _pcore_type['operations'].value }) end def self.create(locator, offset, length, form = "regular", type_ref = nil, operations = _pcore_type['operations'].value) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::AbstractResource[form]', attrs['form'].type, form) ta.assert_instance_of('Puppet::AST::ResourceDefaultsExpression[type_ref]', attrs['type_ref'].type, type_ref) ta.assert_instance_of('Puppet::AST::ResourceDefaultsExpression[operations]', attrs['operations'].type, operations) new(locator, offset, length, form, type_ref, operations) end attr_reader :type_ref attr_reader :operations def initialize(locator, offset, length, form = "regular", type_ref = nil, operations = _pcore_type['operations'].value) super(locator, offset, length, form) @hash = @hash ^ type_ref.hash ^ operations.hash @type_ref = type_ref @operations = operations end def _pcore_init_hash result = super result['type_ref'] = @type_ref unless @type_ref == nil result['operations'] = @operations unless _pcore_type['operations'].default_value?(@operations) result end def _pcore_contents yield(@type_ref) unless @type_ref.nil? @operations.each { |value| yield(value) } end def _pcore_all_contents(path, &block) path << self unless @type_ref.nil? block.call(@type_ref, path) @type_ref._pcore_all_contents(path, &block) end @operations.each do |value| block.call(value, path) value._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @type_ref.eql?(o.type_ref) && @operations.eql?(o.operations) end alias == eql? end class ResourceOverrideExpression < AbstractResource def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::ResourceOverrideExpression', { 'parent' => AbstractResource._pcore_type, 'attributes' => { 'resources' => Expression._pcore_type, 'operations' => { 'type' => Types::PArrayType.new(AbstractAttributeOperation._pcore_type), 'value' => [] } } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::ResourceOverrideExpression initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['resources'], init_hash.fetch('form') { "regular" }, init_hash.fetch('operations') { _pcore_type['operations'].value }) end def self.create(locator, offset, length, resources, form = "regular", operations = _pcore_type['operations'].value) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::AbstractResource[form]', attrs['form'].type, form) ta.assert_instance_of('Puppet::AST::ResourceOverrideExpression[resources]', attrs['resources'].type, resources) ta.assert_instance_of('Puppet::AST::ResourceOverrideExpression[operations]', attrs['operations'].type, operations) new(locator, offset, length, resources, form, operations) end attr_reader :resources attr_reader :operations def initialize(locator, offset, length, resources, form = "regular", operations = _pcore_type['operations'].value) super(locator, offset, length, form) @hash = @hash ^ resources.hash ^ operations.hash @resources = resources @operations = operations end def _pcore_init_hash result = super result['resources'] = @resources result['operations'] = @operations unless _pcore_type['operations'].default_value?(@operations) result end def _pcore_contents yield(@resources) unless @resources.nil? @operations.each { |value| yield(value) } end def _pcore_all_contents(path, &block) path << self unless @resources.nil? block.call(@resources, path) @resources._pcore_all_contents(path, &block) end @operations.each do |value| block.call(value, path) value._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @resources.eql?(o.resources) && @operations.eql?(o.operations) end alias == eql? end class SelectorEntry < Positioned def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::SelectorEntry', { 'parent' => Positioned._pcore_type, 'attributes' => { 'matching_expr' => Expression._pcore_type, 'value_expr' => Expression._pcore_type } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::SelectorEntry initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['matching_expr'], init_hash['value_expr']) end def self.create(locator, offset, length, matching_expr, value_expr) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::SelectorEntry[matching_expr]', attrs['matching_expr'].type, matching_expr) ta.assert_instance_of('Puppet::AST::SelectorEntry[value_expr]', attrs['value_expr'].type, value_expr) new(locator, offset, length, matching_expr, value_expr) end attr_reader :matching_expr attr_reader :value_expr def initialize(locator, offset, length, matching_expr, value_expr) super(locator, offset, length) @hash = @hash ^ matching_expr.hash ^ value_expr.hash @matching_expr = matching_expr @value_expr = value_expr end def _pcore_init_hash result = super result['matching_expr'] = @matching_expr result['value_expr'] = @value_expr result end def _pcore_contents yield(@matching_expr) unless @matching_expr.nil? yield(@value_expr) unless @value_expr.nil? end def _pcore_all_contents(path, &block) path << self unless @matching_expr.nil? block.call(@matching_expr, path) @matching_expr._pcore_all_contents(path, &block) end unless @value_expr.nil? block.call(@value_expr, path) @value_expr._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @matching_expr.eql?(o.matching_expr) && @value_expr.eql?(o.value_expr) end alias == eql? end class SelectorExpression < Expression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::SelectorExpression', { 'parent' => Expression._pcore_type, 'attributes' => { 'left_expr' => Expression._pcore_type, 'selectors' => { 'type' => Types::PArrayType.new(SelectorEntry._pcore_type), 'value' => [] } } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::SelectorExpression initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['offset'], init_hash['length'], init_hash['left_expr'], init_hash.fetch('selectors') { _pcore_type['selectors'].value }) end def self.create(locator, offset, length, left_expr, selectors = _pcore_type['selectors'].value) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Positioned[locator]', attrs['locator'].type, locator) ta.assert_instance_of('Puppet::AST::Positioned[offset]', attrs['offset'].type, offset) ta.assert_instance_of('Puppet::AST::Positioned[length]', attrs['length'].type, length) ta.assert_instance_of('Puppet::AST::SelectorExpression[left_expr]', attrs['left_expr'].type, left_expr) ta.assert_instance_of('Puppet::AST::SelectorExpression[selectors]', attrs['selectors'].type, selectors) new(locator, offset, length, left_expr, selectors) end attr_reader :left_expr attr_reader :selectors def initialize(locator, offset, length, left_expr, selectors = _pcore_type['selectors'].value) super(locator, offset, length) @hash = @hash ^ left_expr.hash ^ selectors.hash @left_expr = left_expr @selectors = selectors end def _pcore_init_hash result = super result['left_expr'] = @left_expr result['selectors'] = @selectors unless _pcore_type['selectors'].default_value?(@selectors) result end def _pcore_contents yield(@left_expr) unless @left_expr.nil? @selectors.each { |value| yield(value) } end def _pcore_all_contents(path, &block) path << self unless @left_expr.nil? block.call(@left_expr, path) @left_expr._pcore_all_contents(path, &block) end @selectors.each do |value| block.call(value, path) value._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @left_expr.eql?(o.left_expr) && @selectors.eql?(o.selectors) end alias == eql? end class NamedAccessExpression < BinaryExpression def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::NamedAccessExpression', { 'parent' => BinaryExpression._pcore_type }) end def _pcore_contents yield(@left_expr) unless @left_expr.nil? yield(@right_expr) unless @right_expr.nil? end def _pcore_all_contents(path, &block) path << self unless @left_expr.nil? block.call(@left_expr, path) @left_expr._pcore_all_contents(path, &block) end unless @right_expr.nil? block.call(@right_expr, path) @right_expr._pcore_all_contents(path, &block) end path.pop end end class Program < PopsObject def self._pcore_type @_pcore_type ||= Types::PObjectType.new('Puppet::AST::Program', { 'parent' => PopsObject._pcore_type, 'attributes' => { 'body' => { 'type' => Types::POptionalType.new(Expression._pcore_type), 'value' => nil }, 'definitions' => { 'type' => Types::PArrayType.new(Definition._pcore_type), 'kind' => 'reference', 'value' => [] }, 'source_text' => { 'type' => Types::PStringType::DEFAULT, 'kind' => 'derived' }, 'source_ref' => { 'type' => Types::PStringType::DEFAULT, 'kind' => 'derived' }, 'line_offsets' => { 'type' => Types::PArrayType.new(Types::PIntegerType::DEFAULT), 'kind' => 'derived' }, 'locator' => Parser::Locator::Locator19._pcore_type } }) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('Puppet::AST::Program initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new( init_hash['locator'], init_hash['body'], init_hash.fetch('definitions') { _pcore_type['definitions'].value }) end def self.create(locator, body = nil, definitions = _pcore_type['definitions'].value) ta = Types::TypeAsserter attrs = _pcore_type.attributes(true) ta.assert_instance_of('Puppet::AST::Program[body]', attrs['body'].type, body) ta.assert_instance_of('Puppet::AST::Program[definitions]', attrs['definitions'].type, definitions) ta.assert_instance_of('Puppet::AST::Program[locator]', attrs['locator'].type, locator) new(locator, body, definitions) end attr_reader :body attr_reader :definitions attr_reader :locator def current self end def source_text @locator.string end def source_ref @locator.file end def line_offsets @locator.line_index end def initialize(locator, body = nil, definitions = _pcore_type['definitions'].value) super() @hash = @hash ^ body.hash ^ definitions.hash ^ locator.hash @body = body @definitions = definitions @locator = locator end def _pcore_init_hash result = super result['body'] = @body unless @body == nil result['definitions'] = @definitions unless _pcore_type['definitions'].default_value?(@definitions) result['locator'] = @locator result end def _pcore_contents yield(@body) unless @body.nil? yield(@locator) unless @locator.nil? end def _pcore_all_contents(path, &block) path << self unless @body.nil? block.call(@body, path) @body._pcore_all_contents(path, &block) end unless @locator.nil? block.call(@locator, path) @locator._pcore_all_contents(path, &block) end path.pop end def eql?(o) super && @body.eql?(o.body) && @definitions.eql?(o.definitions) && @locator.eql?(o.locator) end alias == eql? end end module Model @@pcore_ast_initialized = false def self.register_pcore_types return if @@pcore_ast_initialized @@pcore_ast_initialized = true all_types = [ Parser::Locator::Locator19, Model::PopsObject, Model::Positioned, Model::Expression, Model::Nop, Model::BinaryExpression, Model::UnaryExpression, Model::ParenthesizedExpression, Model::NotExpression, Model::UnaryMinusExpression, Model::UnfoldExpression, Model::AssignmentExpression, Model::ArithmeticExpression, Model::RelationshipExpression, Model::AccessExpression, Model::ComparisonExpression, Model::MatchExpression, Model::InExpression, Model::BooleanExpression, Model::AndExpression, Model::OrExpression, Model::LiteralList, Model::KeyedEntry, Model::LiteralHash, Model::BlockExpression, Model::CaseOption, Model::CaseExpression, Model::QueryExpression, Model::ExportedQuery, Model::VirtualQuery, Model::AbstractAttributeOperation, Model::AttributeOperation, Model::AttributesOperation, Model::CollectExpression, Model::Parameter, Model::Definition, Model::NamedDefinition, Model::FunctionDefinition, Model::ResourceTypeDefinition, Model::Application, Model::QRefDefinition, Model::TypeAlias, Model::TypeMapping, Model::TypeDefinition, Model::NodeDefinition, Model::SiteDefinition, Model::SubLocatedExpression, Model::HeredocExpression, Model::HostClassDefinition, Model::PlanDefinition, Model::LambdaExpression, Model::IfExpression, Model::UnlessExpression, Model::CallExpression, Model::CallFunctionExpression, Model::CallNamedFunctionExpression, Model::CallMethodExpression, Model::Literal, Model::LiteralValue, Model::LiteralRegularExpression, Model::LiteralString, Model::LiteralNumber, Model::LiteralInteger, Model::LiteralFloat, Model::LiteralUndef, Model::LiteralDefault, Model::LiteralBoolean, Model::TextExpression, Model::ConcatenatedString, Model::QualifiedName, Model::ReservedWord, Model::QualifiedReference, Model::VariableExpression, Model::EppExpression, Model::RenderStringExpression, Model::RenderExpression, Model::ResourceBody, Model::AbstractResource, Model::ResourceExpression, Model::CapabilityMapping, Model::ResourceDefaultsExpression, Model::ResourceOverrideExpression, Model::SelectorEntry, Model::SelectorExpression, Model::NamedAccessExpression, Model::Program] # Create and register a TypeSet that corresponds to all types in the AST model types_map = {} all_types.each do |type| types_map[type._pcore_type.simple_name] = type._pcore_type end type_set = Types::PTypeSetType.new({ 'name' => 'Puppet::AST', 'pcore_version' => '1.0.0', 'types' => types_map }) loc = Puppet::Util.path_to_uri("#{__FILE__}") Loaders.static_loader.set_entry(Loader::TypedName.new(:type, 'puppet::ast', Pcore::RUNTIME_NAME_AUTHORITY), type_set, URI("#{loc}?line=1")) Loaders.register_static_implementations(all_types) end end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/model/factory.rb������������������������������������������������������0000644�0052762�0001160�00000102346�13417161721�021160� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Factory is a helper class that makes construction of a Pops Model # much more convenient. It can be viewed as a small internal DSL for model # constructions. # For usage see tests using the factory. # # @todo All those uppercase methods ... they look bad in one way, but stand out nicely in the grammar... # decide if they should change into lower case names (some of the are lower case)... # module Puppet::Pops module Model class Factory # Shared build_visitor, since there are many instances of Factory being used KEY_LENGTH = 'length'.freeze KEY_OFFSET = 'offset'.freeze KEY_LOCATOR = 'locator'.freeze KEY_OPERATOR = 'operator'.freeze KEY_VALUE = 'value'.freeze KEY_KEYS = 'keys'.freeze KEY_NAME = 'name'.freeze KEY_BODY = 'body'.freeze KEY_EXPR = 'expr'.freeze KEY_LEFT_EXPR = 'left_expr'.freeze KEY_RIGHT_EXPR = 'right_expr'.freeze KEY_PARAMETERS = 'parameters'.freeze BUILD_VISITOR = Visitor.new(self, 'build') INFER_VISITOR = Visitor.new(self, 'infer') INTERPOLATION_VISITOR = Visitor.new(self, 'interpolate') def self.infer(o) if o.instance_of?(Factory) o else new(o) end end attr_reader :model_class, :unfolded def [](key) @init_hash[key] end def []=(key, value) @init_hash[key] = value end def all_factories(&block) block.call(self) @init_hash.each_value { |value| value.all_factories(&block) if value.instance_of?(Factory) } end def model if @current.nil? # Assign a default Locator if it's missing. Should only happen when the factory is used by other # means than from a parser (e.g. unit tests) unless @init_hash.include?(KEY_LOCATOR) @init_hash[KEY_LOCATOR] = Parser::Locator.locator('<no source>', 'no file') unless @model_class <= Program @init_hash[KEY_OFFSET] = 0 @init_hash[KEY_LENGTH] = 0 end end @current = create_model end @current end # Backward API compatibility alias current model def create_model @init_hash.each_pair { |key, elem| @init_hash[key] = factory_to_model(elem) } model_class.from_asserted_hash(@init_hash) end # Initialize a factory with a single object, or a class with arguments applied to build of # created instance # def initialize(o, *args) @init_hash = {} if o.instance_of?(Class) @model_class = o BUILD_VISITOR.visit_this_class(self, o, args) else INFER_VISITOR.visit_this(self, o, EMPTY_ARRAY) end end # Polymorphic interpolate def interpolate() INTERPOLATION_VISITOR.visit_this_class(self, @model_class, EMPTY_ARRAY) end # Building of Model classes def build_Application(o, n, ps, body) @init_hash[KEY_NAME] = n @init_hash[KEY_PARAMETERS] = ps b = f_build_body(body) @init_hash[KEY_BODY] = b unless b.nil? end def build_ArithmeticExpression(o, op, a, b) @init_hash[KEY_OPERATOR] = op build_BinaryExpression(o, a, b) end def build_AssignmentExpression(o, op, a, b) @init_hash[KEY_OPERATOR] = op build_BinaryExpression(o, a, b) end def build_AttributeOperation(o, name, op, value) @init_hash[KEY_OPERATOR] = op @init_hash['attribute_name'] = name.to_s # BOOLEAN is allowed in the grammar @init_hash['value_expr'] = value end def build_AttributesOperation(o, value) @init_hash[KEY_EXPR] = value end def build_AccessExpression(o, left, keys) @init_hash[KEY_LEFT_EXPR] = left @init_hash[KEY_KEYS] = keys end def build_BinaryExpression(o, left, right) @init_hash[KEY_LEFT_EXPR] = left @init_hash[KEY_RIGHT_EXPR] = right end def build_BlockExpression(o, args) @init_hash['statements'] = args end def build_EppExpression(o, parameters_specified, body) @init_hash['parameters_specified'] = parameters_specified b = f_build_body(body) @init_hash[KEY_BODY] = b unless b.nil? end # @param rval_required [Boolean] if the call must produce a value def build_CallExpression(o, functor, rval_required, args) @init_hash['functor_expr'] = functor @init_hash['rval_required'] = rval_required @init_hash['arguments'] = args end def build_CallMethodExpression(o, functor, rval_required, lambda, args) build_CallExpression(o, functor, rval_required, args) @init_hash['lambda'] = lambda end def build_CaseExpression(o, test, args) @init_hash['test'] = test @init_hash['options'] = args end def build_CaseOption(o, value_list, then_expr) value_list = [value_list] unless value_list.is_a?(Array) @init_hash['values'] = value_list b = f_build_body(then_expr) @init_hash['then_expr'] = b unless b.nil? end def build_CollectExpression(o, type_expr, query_expr, attribute_operations) @init_hash['type_expr'] = type_expr @init_hash['query'] = query_expr @init_hash['operations'] = attribute_operations end def build_ComparisonExpression(o, op, a, b) @init_hash[KEY_OPERATOR] = op build_BinaryExpression(o, a, b) end def build_ConcatenatedString(o, args) # Strip empty segments @init_hash['segments'] = args.reject { |arg| arg.model_class == LiteralString && arg['value'].empty? } end def build_HeredocExpression(o, name, expr) @init_hash['syntax'] = name @init_hash['text_expr'] = expr end # @param name [String] a valid classname # @param parameters [Array<Parameter>] may be empty # @param parent_class_name [String, nil] a valid classname referencing a parent class, optional. # @param body [Array<Expression>, Expression, nil] expression that constitute the body # @return [HostClassDefinition] configured from the parameters # def build_HostClassDefinition(o, name, parameters, parent_class_name, body) build_NamedDefinition(o, name, parameters, body) @init_hash['parent_class'] = parent_class_name unless parent_class_name.nil? end def build_ResourceOverrideExpression(o, resources, attribute_operations) @init_hash['resources'] = resources @init_hash['operations'] = attribute_operations end def build_ReservedWord(o, name, future) @init_hash['word'] = name @init_hash['future'] = future end def build_KeyedEntry(o, k, v) @init_hash['key'] = k @init_hash[KEY_VALUE] = v end def build_LiteralHash(o, keyed_entries, unfolded) @init_hash['entries'] = keyed_entries @unfolded = unfolded end def build_LiteralList(o, values) @init_hash['values'] = values end def build_LiteralFloat(o, val) @init_hash[KEY_VALUE] = val end def build_LiteralInteger(o, val, radix) @init_hash[KEY_VALUE] = val @init_hash['radix'] = radix end def build_LiteralString(o, value) @init_hash[KEY_VALUE] = val end def build_IfExpression(o, t, ift, els) @init_hash['test'] = t @init_hash['then_expr'] = ift @init_hash['else_expr'] = els end def build_MatchExpression(o, op, a, b) @init_hash[KEY_OPERATOR] = op build_BinaryExpression(o, a, b) end # Building model equivalences of Ruby objects # Allows passing regular ruby objects to the factory to produce instructions # that when evaluated produce the same thing. def infer_String(o) @model_class = LiteralString @init_hash[KEY_VALUE] = o end def infer_NilClass(o) @model_class = Nop end def infer_TrueClass(o) @model_class = LiteralBoolean @init_hash[KEY_VALUE] = o end def infer_FalseClass(o) @model_class = LiteralBoolean @init_hash[KEY_VALUE] = o end def infer_Integer(o) @model_class = LiteralInteger @init_hash[KEY_VALUE] = o end def infer_Float(o) @model_class = LiteralFloat @init_hash[KEY_VALUE] = o end def infer_Regexp(o) @model_class = LiteralRegularExpression @init_hash['pattern'] = o.inspect @init_hash[KEY_VALUE] = o end # Creates a String literal, unless the symbol is one of the special :undef, or :default # which instead creates a LiterlUndef, or a LiteralDefault. # Supports :undef because nil creates a no-op instruction. def infer_Symbol(o) case o when :undef @model_class = LiteralUndef when :default @model_class = LiteralDefault else infer_String(o.to_s) end end # Creates a LiteralList instruction from an Array, where the entries are built. def infer_Array(o) @model_class = LiteralList @init_hash['values'] = o.map { |e| Factory.infer(e) } end # Create a LiteralHash instruction from a hash, where keys and values are built # The hash entries are added in sorted order based on key.to_s # def infer_Hash(o) @model_class = LiteralHash @init_hash['entries'] = o.sort_by { |k,_| k.to_s }.map { |k, v| Factory.new(KeyedEntry, Factory.infer(k), Factory.infer(v)) } @unfolded = false end def f_build_body(body) case body when NilClass nil when Array Factory.new(BlockExpression, body) when Factory body else Factory.infer(body) end end def build_LambdaExpression(o, parameters, body, return_type) @init_hash[KEY_PARAMETERS] = parameters b = f_build_body(body) @init_hash[KEY_BODY] = b unless b.nil? @init_hash['return_type'] = return_type unless return_type.nil? end def build_NamedDefinition(o, name, parameters, body) @init_hash[KEY_PARAMETERS] = parameters b = f_build_body(body) @init_hash[KEY_BODY] = b unless b.nil? @init_hash[KEY_NAME] = name end def build_FunctionDefinition(o, name, parameters, body, return_type) @init_hash[KEY_PARAMETERS] = parameters b = f_build_body(body) @init_hash[KEY_BODY] = b unless b.nil? @init_hash[KEY_NAME] = name @init_hash['return_type'] = return_type unless return_type.nil? end def build_PlanDefinition(o, name, parameters, body, return_type=nil) @init_hash[KEY_PARAMETERS] = parameters b = f_build_body(body) @init_hash[KEY_BODY] = b unless b.nil? @init_hash[KEY_NAME] = name @init_hash['return_type'] = return_type unless return_type.nil? end def build_CapabilityMapping(o, kind, component, capability, mappings) @init_hash['kind'] = kind @init_hash['component'] = component @init_hash['capability'] = capability @init_hash['mappings'] = mappings end def build_NodeDefinition(o, hosts, parent, body) @init_hash['host_matches'] = hosts @init_hash['parent'] = parent unless parent.nil? # no nop here b = f_build_body(body) @init_hash[KEY_BODY] = b unless b.nil? end def build_SiteDefinition(o, body) b = f_build_body(body) @init_hash[KEY_BODY] = b unless b.nil? end def build_Parameter(o, name, expr) @init_hash[KEY_NAME] = name @init_hash[KEY_VALUE] = expr end def build_QualifiedReference(o, name) @init_hash['cased_value'] = name.to_s end def build_RelationshipExpression(o, op, a, b) @init_hash[KEY_OPERATOR] = op build_BinaryExpression(o, a, b) end def build_ResourceExpression(o, type_name, bodies) @init_hash['type_name'] = type_name @init_hash['bodies'] = bodies end def build_RenderStringExpression(o, string) @init_hash[KEY_VALUE] = string; end def build_ResourceBody(o, title_expression, attribute_operations) @init_hash['title'] = title_expression @init_hash['operations'] = attribute_operations end def build_ResourceDefaultsExpression(o, type_ref, attribute_operations) @init_hash['type_ref'] = type_ref @init_hash['operations'] = attribute_operations end def build_SelectorExpression(o, left, *selectors) @init_hash[KEY_LEFT_EXPR] = left @init_hash['selectors'] = selectors end # Builds a SubLocatedExpression - this wraps the expression in a sublocation configured # from the given token # A SubLocated holds its own locator that is used for subexpressions holding positions relative # to what it describes. # def build_SubLocatedExpression(o, token, expression) @init_hash[KEY_EXPR] = expression @init_hash[KEY_OFFSET] = token.offset @init_hash[KEY_LENGTH] = token.length locator = token.locator @init_hash[KEY_LOCATOR] = locator @init_hash['leading_line_count'] = locator.leading_line_count @init_hash['leading_line_offset'] = locator.leading_line_offset # Index is held in sublocator's parent locator - needed to be able to reconstruct @init_hash['line_offsets'] = locator.locator.line_index end def build_SelectorEntry(o, matching, value) @init_hash['matching_expr'] = matching @init_hash['value_expr'] = value end def build_QueryExpression(o, expr) @init_hash[KEY_EXPR] = expr unless Factory.nop?(expr) end def build_TypeAlias(o, name, type_expr) if type_expr.model_class <= KeyedEntry # KeyedEntry is used for the form: # # type Foo = Bar { ... } # # The entry contains Bar => { ... } and must be transformed into: # # Object[{parent => Bar, ... }] # parent = type_expr['key'] hash = type_expr['value'] pn = parent['cased_value'] unless pn == 'Object' || pn == 'TypeSet' hash['entries'] << Factory.KEY_ENTRY(Factory.QNAME('parent'), parent) parent = Factory.QREF('Object') end type_expr = parent.access([hash]) elsif type_expr.model_class <= LiteralHash # LiteralHash is used for the form: # # type Foo = { ... } # # The hash must be transformed into: # # Object[{ ... }] # type_expr = Factory.QREF('Object').access([type_expr]) end @init_hash['type_expr'] = type_expr @init_hash[KEY_NAME] = name end def build_TypeMapping(o, lhs, rhs) @init_hash['type_expr'] = lhs @init_hash['mapping_expr'] = rhs end def build_TypeDefinition(o, name, parent, body) b = f_build_body(body) @init_hash[KEY_BODY] = b unless b.nil? @init_hash['parent'] = parent @init_hash[KEY_NAME] = name end def build_UnaryExpression(o, expr) @init_hash[KEY_EXPR] = expr unless Factory.nop?(expr) end def build_Program(o, body, definitions, locator) @init_hash[KEY_BODY] = body # non containment @init_hash['definitions'] = definitions @init_hash[KEY_LOCATOR] = locator end def build_QualifiedName(o, name) @init_hash[KEY_VALUE] = name end def build_TokenValue(o) raise "Factory can not deal with a Lexer Token. Got token: #{o}. Probably caused by wrong index in grammar val[n]." end # Factory helpers def f_build_unary(klazz, expr) Factory.new(klazz, expr) end def f_build_binary_op(klazz, op, left, right) Factory.new(klazz, op, left, right) end def f_build_binary(klazz, left, right) Factory.new(klazz, left, right) end def f_arithmetic(op, r) f_build_binary_op(ArithmeticExpression, op, self, r) end def f_comparison(op, r) f_build_binary_op(ComparisonExpression, op, self, r) end def f_match(op, r) f_build_binary_op(MatchExpression, op, self, r) end # Operator helpers def in(r) f_build_binary(InExpression, self, r); end def or(r) f_build_binary(OrExpression, self, r); end def and(r) f_build_binary(AndExpression, self, r); end def not(); f_build_unary(NotExpression, self); end def minus(); f_build_unary(UnaryMinusExpression, self); end def unfold(); f_build_unary(UnfoldExpression, self); end def text(); f_build_unary(TextExpression, self); end def var(); f_build_unary(VariableExpression, self); end def access(r); f_build_binary(AccessExpression, self, r); end def dot r; f_build_binary(NamedAccessExpression, self, r); end def + r; f_arithmetic('+', r); end def - r; f_arithmetic('-', r); end def / r; f_arithmetic('/', r); end def * r; f_arithmetic('*', r); end def % r; f_arithmetic('%', r); end def << r; f_arithmetic('<<', r); end def >> r; f_arithmetic('>>', r); end def < r; f_comparison('<', r); end def <= r; f_comparison('<=', r); end def > r; f_comparison('>', r); end def >= r; f_comparison('>=', r); end def eq r; f_comparison('==', r); end def ne r; f_comparison('!=', r); end def =~ r; f_match('=~', r); end def mne r; f_match('!~', r); end def paren; f_build_unary(ParenthesizedExpression, self); end def relop(op, r) f_build_binary_op(RelationshipExpression, op, self, r) end def select(*args) Factory.new(SelectorExpression, self, *args) end # Same as access, but with varargs and arguments that must be inferred. For testing purposes def access_at(*r) f_build_binary(AccessExpression, self, r.map { |arg| Factory.infer(arg) }) end # For CaseExpression, setting the default for an already build CaseExpression def default(r) @init_hash['options'] << Factory.WHEN(Factory.infer(:default), r) self end def lambda=(lambda) @init_hash['lambda'] = lambda self end # Assignment = def set(r) f_build_binary_op(AssignmentExpression, '=', self, r) end # Assignment += def plus_set(r) f_build_binary_op(AssignmentExpression, '+=', self, r) end # Assignment -= def minus_set(r) f_build_binary_op(AssignmentExpression, '-=', self, r) end def attributes(*args) @init_hash['attributes'] = args self end def offset @init_hash[KEY_OFFSET] end def length @init_hash[KEY_LENGTH] end # Records the position (start -> end) and computes the resulting length. # def record_position(locator, start_locatable, end_locatable) # record information directly in the Positioned object start_offset = start_locatable.offset @init_hash[KEY_LOCATOR] = locator @init_hash[KEY_OFFSET] = start_offset @init_hash[KEY_LENGTH] = end_locatable.nil? ? start_locatable.length : end_locatable.offset + end_locatable.length - start_offset self end # Sets the form of the resource expression (:regular (the default), :virtual, or :exported). # Produces true if the expression was a resource expression, false otherwise. # def self.set_resource_form(expr, form) # Note: Validation handles illegal combinations return false unless expr.instance_of?(self) && expr.model_class <= AbstractResource expr['form'] = form return true end # Returns symbolic information about an expected shape of a resource expression given the LHS of a resource expr. # # * `name { }` => `:resource`, create a resource of the given type # * `Name { }` => ':defaults`, set defaults for the referenced type # * `Name[] { }` => `:override`, overrides instances referenced by LHS # * _any other_ => ':error', all other are considered illegal # def self.resource_shape(expr) if expr == 'class' :class elsif expr.instance_of?(self) mc = expr.model_class if mc <= QualifiedName :resource elsif mc <= QualifiedReference :defaults elsif mc <= AccessExpression # if Resource[e], then it is not resource specific lhs = expr[KEY_LEFT_EXPR] if lhs.model_class <= QualifiedReference && lhs[KEY_VALUE] == 'resource' && expr[KEY_KEYS].size == 1 :defaults else :override end else :error end else :error end end # Factory starting points def self.literal(o); infer(o); end def self.minus(o); infer(o).minus; end def self.unfold(o); infer(o).unfold; end def self.var(o); infer(o).var; end def self.block(*args); new(BlockExpression, args.map { |arg| infer(arg) }); end def self.string(*args); new(ConcatenatedString, args.map { |arg| infer(arg) }); end def self.text(o); infer(o).text; end def self.IF(test_e,then_e,else_e); new(IfExpression, test_e, then_e, else_e); end def self.UNLESS(test_e,then_e,else_e); new(UnlessExpression, test_e, then_e, else_e); end def self.CASE(test_e,*options); new(CaseExpression, test_e, options); end def self.WHEN(values_list, block); new(CaseOption, values_list, block); end def self.MAP(match, value); new(SelectorEntry, match, value); end def self.KEY_ENTRY(key, val); new(KeyedEntry, key, val); end def self.HASH(entries); new(LiteralHash, entries, false); end def self.HASH_UNFOLDED(entries); new(LiteralHash, entries, true); end def self.HEREDOC(name, expr); new(HeredocExpression, name, expr); end def self.STRING(*args); new(ConcatenatedString, args); end def self.SUBLOCATE(token, expr) new(SubLocatedExpression, token, expr); end def self.LIST(entries); new(LiteralList, entries); end def self.PARAM(name, expr=nil); new(Parameter, name, expr); end def self.NODE(hosts, parent, body); new(NodeDefinition, hosts, parent, body); end def self.SITE(body); new(SiteDefinition, body); end # Parameters # Mark parameter as capturing the rest of arguments def captures_rest @init_hash['captures_rest'] = true end # Set Expression that should evaluate to the parameter's type def type_expr(o) @init_hash['type_expr'] = o end # Creates a QualifiedName representation of o, unless o already represents a QualifiedName in which # case it is returned. # def self.fqn(o) o.instance_of?(Factory) && o.model_class <= QualifiedName ? self : new(QualifiedName, o) end # Creates a QualifiedName representation of o, unless o already represents a QualifiedName in which # case it is returned. # def self.fqr(o) o.instance_of?(Factory) && o.model_class <= QualifiedReference ? self : new(QualifiedReference, o) end def self.TEXT(expr) new(TextExpression, infer(expr).interpolate) end # TODO_EPP def self.RENDER_STRING(o) new(RenderStringExpression, o) end def self.RENDER_EXPR(expr) new(RenderExpression, expr) end def self.EPP(parameters, body) if parameters.nil? params = [] parameters_specified = false else params = parameters parameters_specified = true end LAMBDA(params, new(EppExpression, parameters_specified, body), nil) end def self.RESERVED(name, future=false) new(ReservedWord, name, future) end # TODO: This is the same a fqn factory method, don't know if callers to fqn and QNAME can live with the # same result or not yet - refactor into one method when decided. # def self.QNAME(name) new(QualifiedName, name) end def self.NUMBER(name_or_numeric) if n_radix = Utils.to_n_with_radix(name_or_numeric) val, radix = n_radix if val.is_a?(Float) new(LiteralFloat, val) else new(LiteralInteger, val, radix) end else # Bad number should already have been caught by lexer - this should never happen #TRANSLATORS 'NUMBER' refers to a method name and the 'name_or_numeric' was the passed in value and should not be translated raise ArgumentError, _("Internal Error, NUMBER token does not contain a valid number, %{name_or_numeric}") % { name_or_numeric: name_or_numeric } end end # Convert input string to either a qualified name, a LiteralInteger with radix, or a LiteralFloat # def self.QNAME_OR_NUMBER(name) if n_radix = Utils.to_n_with_radix(name) val, radix = n_radix if val.is_a?(Float) new(LiteralFloat, val) else new(LiteralInteger, val, radix) end else new(QualifiedName, name) end end def self.QREF(name) new(QualifiedReference, name) end def self.VIRTUAL_QUERY(query_expr) new(VirtualQuery, query_expr) end def self.EXPORTED_QUERY(query_expr) new(ExportedQuery, query_expr) end def self.ARGUMENTS(args, arg) if !args.empty? && arg.model_class <= LiteralHash && arg.unfolded last = args[args.size() - 1] if last.model_class <= LiteralHash && last.unfolded last['entries'].concat(arg['entries']) return args end end args.push(arg) end def self.ATTRIBUTE_OP(name, op, expr) new(AttributeOperation, name, op, expr) end def self.ATTRIBUTES_OP(expr) new(AttributesOperation, expr) end # Same as CALL_NAMED but with inference and varargs (for testing purposes) def self.call_named(name, rval_required, *argument_list) new(CallNamedFunctionExpression, fqn(name), rval_required, argument_list.map { |arg| infer(arg) }) end def self.CALL_NAMED(name, rval_required, argument_list) new(CallNamedFunctionExpression, name, rval_required, argument_list) end def self.CALL_METHOD(functor, argument_list) new(CallMethodExpression, functor, true, nil, argument_list) end def self.COLLECT(type_expr, query_expr, attribute_operations) new(CollectExpression, type_expr, query_expr, attribute_operations) end def self.NAMED_ACCESS(type_name, bodies) new(NamedAccessExpression, type_name, bodies) end def self.RESOURCE(type_name, bodies) new(ResourceExpression, type_name, bodies) end def self.RESOURCE_DEFAULTS(type_name, attribute_operations) new(ResourceDefaultsExpression, type_name, attribute_operations) end def self.RESOURCE_OVERRIDE(resource_ref, attribute_operations) new(ResourceOverrideExpression, resource_ref, attribute_operations) end def self.RESOURCE_BODY(resource_title, attribute_operations) new(ResourceBody, resource_title, attribute_operations) end def self.PROGRAM(body, definitions, locator) new(Program, body, definitions, locator) end # Builds a BlockExpression if args size > 1, else the single expression/value in args def self.block_or_expression(args, left_brace = nil, right_brace = nil) if args.size > 1 block_expr = new(BlockExpression, args) # If given a left and right brace position, use those # otherwise use the first and last element of the block if !left_brace.nil? && !right_brace.nil? block_expr.record_position(args.first[KEY_LOCATOR], left_brace, right_brace) else block_expr.record_position(args.first[KEY_LOCATOR], args.first, args.last) end block_expr else args[0] end end def self.HOSTCLASS(name, parameters, parent, body) new(HostClassDefinition, name, parameters, parent, body) end def self.DEFINITION(name, parameters, body) new(ResourceTypeDefinition, name, parameters, body) end def self.CAPABILITY_MAPPING(kind, component, cap_name, mappings) new(CapabilityMapping, kind, component, cap_name, mappings) end def self.APPLICATION(name, parameters, body) new(Application, name, parameters, body) end def self.PLAN(name, parameters, body) new(PlanDefinition, name, parameters, body, nil) end def self.FUNCTION(name, parameters, body, return_type) new(FunctionDefinition, name, parameters, body, return_type) end def self.LAMBDA(parameters, body, return_type) new(LambdaExpression, parameters, body, return_type) end def self.TYPE_ASSIGNMENT(lhs, rhs) if lhs.model_class <= AccessExpression new(TypeMapping, lhs, rhs) else new(TypeAlias, lhs['cased_value'], rhs) end end def self.TYPE_DEFINITION(name, parent, body) new(TypeDefinition, name, parent, body) end def self.nop? o o.nil? || o.instance_of?(Factory) && o.model_class <= Nop end STATEMENT_CALLS = { 'require' => true, 'realize' => true, 'include' => true, 'contain' => true, 'tag' => true, 'debug' => true, 'info' => true, 'notice' => true, 'warning' => true, 'err' => true, 'fail' => true, 'import' => true, # discontinued, but transform it to make it call error reporting function 'break' => true, 'next' => true, 'return' => true }.freeze # Returns true if the given name is a "statement keyword" (require, include, contain, # error, notice, info, debug # def self.name_is_statement?(name) STATEMENT_CALLS.include?(name) end class ArgsToNonCallError < RuntimeError attr_reader :args, :name_expr def initialize(args, name_expr) @args = args @name_expr = name_expr end end # Transforms an array of expressions containing literal name expressions to calls if followed by an # expression, or expression list. # def self.transform_calls(expressions) expressions.reduce([]) do |memo, expr| name = memo[-1] if name.instance_of?(Factory) && name.model_class <= QualifiedName && name_is_statement?(name[KEY_VALUE]) if expr.is_a?(Array) expr = expr.reject { |e| e.is_a?(Parser::LexerSupport::TokenValue) } else expr = [expr] end the_call = self.CALL_NAMED(name, false, expr) # last positioned is last arg if there are several the_call.record_position(name[KEY_LOCATOR], name, expr[-1]) memo[-1] = the_call if expr.is_a?(CallNamedFunctionExpression) # Patch statement function call to expression style # This is needed because it is first parsed as a "statement" and the requirement changes as it becomes # an argument to the name to call transform above. expr.rval_required = true end elsif expr.is_a?(Array) raise ArgsToNonCallError.new(expr, name) else memo << expr if expr.model_class <= CallNamedFunctionExpression # Patch rvalue expression function call to statement style. # This is not really required but done to be AST model compliant expr['rval_required'] = false end end memo end end # Transforms a left expression followed by an untitled resource (in the form of attribute_operations) # @param left [Factory, Expression] the lhs followed what may be a hash def self.transform_resource_wo_title(left, attribute_ops, lbrace_token, rbrace_token) # Returning nil means accepting the given as a potential resource expression return nil unless attribute_ops.is_a? Array return nil unless left.model_class <= QualifiedName keyed_entries = attribute_ops.map do |ao| return nil if ao[KEY_OPERATOR] == '+>' KEY_ENTRY(infer(ao['attribute_name']), ao['value_expr']) end a_hash = HASH(keyed_entries) a_hash.record_position(left[KEY_LOCATOR], lbrace_token, rbrace_token) result = block_or_expression(transform_calls([left, a_hash])) result end def interpolate_Factory(c) self end def interpolate_LiteralInteger(c) # convert number to a variable self.var end def interpolate_Object(c) self end def interpolate_QualifiedName(c) self.var end # rewrite left expression to variable if it is name, number, and recurse if it is an access expression # this is for interpolation support in new lexer (${NAME}, ${NAME[}}, ${NUMBER}, ${NUMBER[]} - all # other expressions requires variables to be preceded with $ # def interpolate_AccessExpression(c) lhs = @init_hash[KEY_LEFT_EXPR] if is_interop_rewriteable?(lhs) @init_hash[KEY_LEFT_EXPR] = lhs.interpolate end self end def interpolate_NamedAccessExpression(c) lhs = @init_hash[KEY_LEFT_EXPR] if is_interop_rewriteable?(lhs) @init_hash[KEY_LEFT_EXPR] = lhs.interpolate end self end # Rewrite method calls on the form ${x.each ...} to ${$x.each} def interpolate_CallMethodExpression(c) functor_expr = @init_hash['functor_expr'] if is_interop_rewriteable?(functor_expr) @init_hash['functor_expr'] = functor_expr.interpolate end self end def is_interop_rewriteable?(o) mc = o.model_class if mc <= AccessExpression || mc <= QualifiedName || mc <= NamedAccessExpression || mc <= CallMethodExpression true elsif mc <= LiteralInteger # Only decimal integers can represent variables, else it is a number o['radix'] == 10 else false end end def self.concat(*args) result = '' args.each do |e| if e.instance_of?(Factory) && e.model_class <= LiteralString result << e[KEY_VALUE] elsif e.is_a?(String) result << e else raise ArgumentError, _("can only concatenate strings, got %{class_name}") % { class_name: e.class } end end infer(result) end def to_s "Factory for #{@model_class}" end def factory_to_model(value) if value.instance_of?(Factory) value.contained_current(self) elsif value.instance_of?(Array) value.each_with_index { |el, idx| value[idx] = el.contained_current(self) if el.instance_of?(Factory) } else value end end def contained_current(container) if @current.nil? unless @init_hash.include?(KEY_LOCATOR) @init_hash[KEY_LOCATOR] = container[KEY_LOCATOR] @init_hash[KEY_OFFSET] = container[KEY_OFFSET] || 0 @init_hash[KEY_LENGTH] = 0 end @current = create_model end @current end end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/model/model_label_provider.rb�����������������������������������������0000644�0052762�0001160�00000020031�13417161721�023650� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Model # A provider of labels for model object, producing a human name for the model object. # As an example, if object is an ArithmeticExpression with operator +, `#a_an(o)` produces "a '+' Expression", # #the(o) produces "the + Expression", and #label produces "+ Expression". # class ModelLabelProvider include LabelProvider def initialize @@label_visitor ||= Visitor.new(self,"label",0,0) end # Produces a label for the given objects type/operator without article. # If a Class is given, its name is used as label # def label o @@label_visitor.visit_this_0(self, o) end def label_Factory o ; label(o.model) end def label_Array o ; "Array" end def label_LiteralInteger o ; "Literal Integer" end def label_LiteralFloat o ; "Literal Float" end def label_ArithmeticExpression o ; "'#{o.operator}' expression" end def label_AccessExpression o ; "'[]' expression" end def label_MatchExpression o ; "'#{o.operator}' expression" end def label_CollectExpression o ; label(o.query) end def label_EppExpression o ; "Epp Template" end def label_ExportedQuery o ; "Exported Query" end def label_VirtualQuery o ; "Virtual Query" end def label_QueryExpression o ; "Collect Query" end def label_ComparisonExpression o ; "'#{o.operator}' expression" end def label_AndExpression o ; "'and' expression" end def label_OrExpression o ; "'or' expression" end def label_InExpression o ; "'in' expression" end def label_AssignmentExpression o ; "'#{o.operator}' expression" end def label_AttributeOperation o ; "'#{o.operator}' expression" end def label_LiteralList o ; "Array Expression" end def label_LiteralHash o ; "Hash Expression" end def label_KeyedEntry o ; "Hash Entry" end def label_LiteralBoolean o ; "Boolean" end def label_TrueClass o ; "Boolean" end def label_FalseClass o ; "Boolean" end def label_LiteralString o ; "String" end def label_LambdaExpression o ; "Lambda" end def label_LiteralDefault o ; "'default' expression" end def label_LiteralUndef o ; "'undef' expression" end def label_LiteralRegularExpression o ; "Regular Expression" end def label_Nop o ; "Nop Expression" end def label_NamedAccessExpression o ; "'.' expression" end def label_NilClass o ; "Undef Value" end def label_NotExpression o ; "'not' expression" end def label_VariableExpression o ; "Variable" end def label_TextExpression o ; "Expression in Interpolated String" end def label_UnaryMinusExpression o ; "Unary Minus" end def label_UnfoldExpression o ; "Unfold" end def label_BlockExpression o ; "Block Expression" end def label_ConcatenatedString o ; "Double Quoted String" end def label_HeredocExpression o ; "'@(#{o.syntax})' expression" end def label_HostClassDefinition o ; "Host Class Definition" end def label_FunctionDefinition o ; "Function Definition" end def label_PlanDefinition o ; "Plan Definition" end def label_NodeDefinition o ; "Node Definition" end def label_SiteDefinition o ; "Site Definition" end def label_ResourceTypeDefinition o ; "'define' expression" end def label_ResourceOverrideExpression o ; "Resource Override" end def label_Parameter o ; "Parameter Definition" end def label_ParenthesizedExpression o ; "Parenthesized Expression" end def label_IfExpression o ; "'if' statement" end def label_UnlessExpression o ; "'unless' Statement" end def label_CallNamedFunctionExpression o ; "Function Call" end def label_CallMethodExpression o ; "Method call" end def label_CapabilityMapping o ; "Capability Mapping" end def label_CaseExpression o ; "'case' statement" end def label_CaseOption o ; "Case Option" end def label_RenderStringExpression o ; "Epp Text" end def label_RenderExpression o ; "Epp Interpolated Expression" end def label_RelationshipExpression o ; "'#{o.operator}' expression" end def label_ResourceBody o ; "Resource Instance Definition" end def label_ResourceDefaultsExpression o ; "Resource Defaults Expression" end def label_ResourceExpression o ; "Resource Statement" end def label_SelectorExpression o ; "Selector Expression" end def label_SelectorEntry o ; "Selector Option" end def label_Integer o ; "Integer" end def label_Fixnum o ; "Integer" end def label_Bignum o ; "Integer" end def label_Float o ; "Float" end def label_String o ; "String" end def label_Regexp o ; "Regexp" end def label_Object o ; "Object" end def label_Hash o ; "Hash" end def label_QualifiedName o ; "Name" end def label_QualifiedReference o ; "Type-Name" end def label_PAnyType o ; "#{o}-Type" end def label_ReservedWord o ; "Reserved Word '#{o.word}'" end def label_CatalogCollector o ; "Catalog-Collector" end def label_ExportedCollector o ; "Exported-Collector" end def label_TypeAlias o ; "Type Alias" end def label_TypeMapping o ; "Type Mapping" end def label_TypeDefinition o ; "Type Definition" end def label_Binary o ; "Binary" end def label_Application o ; "Application" end def label_Sensitive o ; "Sensitive" end def label_Timestamp o ; "Timestamp" end def label_Timespan o ; "Timespan" end def label_PResourceType o if o.title "#{o} Resource-Reference" else "#{o}-Type" end end def label_Resource o 'Resource Statement' end def label_Class o if o <= Types::PAnyType simple_name = o.name.split('::').last simple_name[1..-5] + "-Type" else n = o.name n.nil? ? 'Anonymous Class' : n end end end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/model/pn_transformer.rb�����������������������������������������������0000644�0052762�0001160�00000024761�13417161721�022554� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Model class PNTransformer def self.visitor @visitor ||= Visitor.new(nil, 'transform', 0, 0) end def self.singleton @singleton ||= new(visitor) end def self.transform(ast) singleton.transform(ast) end def initialize(visitor) @visitor = visitor end def transform(ast) @visitor.visit_this_0(self, ast) end def transform_AccessExpression(e) PN::List.new([transform(e.left_expr)] + pn_array(e.keys)).as_call('access') end def transform_AndExpression(e) binary_op(e, 'and') end def transform_Application(e) definition_to_pn(e, 'application') end def transform_ArithmeticExpression(e) binary_op(e, e.operator) end def transform_Array(a) PN::List.new(pn_array(a)) end def transform_AssignmentExpression(e) binary_op(e, e.operator) end def transform_AttributeOperation(e) PN::Call.new(e.operator, PN::Literal.new(e.attribute_name), transform(e.value_expr)) end def transform_AttributesOperation(e) PN::Call.new('splat-hash', transform(e.expr)) end def transform_BlockExpression(e) transform(e.statements).as_call('block') end def transform_CallFunctionExpression(e) call_to_pn(e, 'call-lambda', 'invoke-lambda') end def transform_CallMethodExpression(e) call_to_pn(e, 'call-method', 'invoke-method') end def transform_CallNamedFunctionExpression(e) call_to_pn(e, 'call', 'invoke') end def transform_CapabilityMapping(e) PN::Call.new(e.kind, transform(e.component), PN::List.new([PN::Literal.new(e.capability)] + pn_array(e.mappings))) end def transform_CaseExpression(e) PN::Call.new('case', transform(e.test), transform(e.options)) end def transform_CaseOption(e) PN::Map.new([transform(e.values).with_name('when'), block_as_entry('then', e.then_expr)]) end def transform_CollectExpression(e) entries = [transform(e.type_expr).with_name('type'), transform(e.query).with_name('query')] entries << transform(e.operations).with_name('ops') unless e.operations.empty? PN::Map.new(entries).as_call('collect') end def transform_ComparisonExpression(e) binary_op(e, e.operator) end def transform_ConcatenatedString(e) transform(e.segments).as_call('concat') end def transform_EppExpression(e) e.body.nil? ? PN::Call.new('epp') : transform(e.body).as_call('epp') end def transform_ExportedQuery(e) is_nop?(e.expr) ? PN::Call.new('exported-query') : PN::Call.new('exported-query', transform(e.expr)) end def transform_Factory(e) transform(e.model) end def transform_FunctionDefinition(e) definition_to_pn(e, 'function', nil, e.return_type) end def transform_HeredocExpression(e) entries = [] entries << PN::Literal.new(e.syntax).with_name('syntax') unless e.syntax == '' entries << transform(e.text_expr).with_name('text') PN::Map.new(entries).as_call('heredoc') end def transform_HostClassDefinition(e) definition_to_pn(e, 'class', e.parent_class) end def transform_IfExpression(e) if_to_pn(e, 'if') end def transform_InExpression(e) binary_op(e, 'in') end def transform_KeyedEntry(e) PN::Call.new('=>', transform(e.key), transform(e.value)) end def transform_LambdaExpression(e) entries = [] entries << parameters_entry(e.parameters) unless e.parameters.empty? entries << transform(e.return_type).with_name('returns') unless e.return_type.nil? entries << block_as_entry('body', e.body) unless e.body.nil? PN::Map.new(entries).as_call('lambda') end def transform_LiteralBoolean(e) PN::Literal.new(e.value) end def transform_LiteralDefault(_) PN::Call.new('default') end def transform_LiteralFloat(e) PN::Literal.new(e.value) end def transform_LiteralHash(e) transform(e.entries).as_call('hash') end def transform_LiteralInteger(e) vl = PN::Literal.new(e.value) e.radix == 10 ? vl : PN::Map.new([PN::Literal.new(e.radix).with_name('radix'), vl.with_name('value')]).as_call('int') end def transform_LiteralList(e) transform(e.values).as_call('array') end def transform_LiteralRegularExpression(e) PN::Literal.new(Types::PRegexpType.regexp_to_s(e.value)).as_call('regexp') end def transform_LiteralString(e) PN::Literal.new(e.value) end def transform_LiteralUndef(_) PN::Literal.new(nil) end def transform_MatchExpression(e) binary_op(e, e.operator) end def transform_NamedAccessExpression(e) binary_op(e, '.') end def transform_NodeDefinition(e) entries = [transform(e.host_matches).with_name('matches')] entries << transform(e.parent).with_name('parent') unless e.parent.nil? entries << block_as_entry('body', e.body) unless e.body.nil? PN::Map.new(entries).as_call('node') end def transform_Nop(_) PN::Call.new('nop') end def transform_NotExpression(e) PN::Call.new('!', transform(e.expr)) end def transform_OrExpression(e) binary_op(e, 'or') end def transform_Parameter(e) entries = [PN::Literal.new(e.name).with_name('name')] entries << transform(e.type_expr).with_name('type') unless e.type_expr.nil? entries << PN::Literal.new(true).with_name('splat') if e.captures_rest entries << transform(e.value).with_name('value') unless e.value.nil? PN::Map.new(entries).with_name('param') end def transform_ParenthesizedExpression(e) PN::Call.new('paren', transform(e.expr)) end def transform_PlanDefinition(e) definition_to_pn(e, 'plan', nil, e.return_type) end def transform_Program(e) transform(e.body) end def transform_QualifiedName(e) PN::Call.new('qn', PN::Literal.new(e.value)) end def transform_QualifiedReference(e) PN::Call.new('qr', PN::Literal.new(e.cased_value)) end def transform_RelationshipExpression(e) binary_op(e, e.operator) end def transform_RenderExpression(e) PN::Call.new('render', transform(e.expr)) end def transform_RenderStringExpression(e) PN::Literal.new(e.value).as_call('render-s') end def transform_ReservedWord(e) PN::Literal.new(e.word).as_call('reserved') end def transform_ResourceBody(e) PN::Map.new([ transform(e.title).with_name('title'), transform(e.operations).with_name('ops') ]).as_call('resource_body') end def transform_ResourceDefaultsExpression(e) entries = [transform(e.type_ref).with_name('type'), transform(e.operations).with_name('ops')] entries << PN::Literal.new(e.form).with_name('form') unless e.form == 'regular' PN::Map.new(entries).as_call('resource-defaults') end def transform_ResourceExpression(e) entries = [ transform(e.type_name).with_name('type'), PN::List.new(pn_array(e.bodies).map { |body| body[0] }).with_name('bodies') ] entries << PN::Literal.new(e.form).with_name('form') unless e.form == 'regular' PN::Map.new(entries).as_call('resource') end def transform_ResourceOverrideExpression(e) entries = [transform(e.resources).with_name('resources'), transform(e.operations).with_name('ops')] entries << PN::Literal.new(e.form).with_name('form') unless e.form == 'regular' PN::Map.new(entries).as_call('resource-override') end def transform_ResourceTypeDefinition(e) definition_to_pn(e, 'define') end def transform_SelectorEntry(e) PN::Call.new('=>', transform(e.matching_expr), transform(e.value_expr)) end def transform_SelectorExpression(e) PN::Call.new('?', transform(e.left_expr), transform(e.selectors)) end def transform_SiteDefinition(e) transform(e.body).as_call('site') end def transform_SubLocatedExpression(e) transform(e.expr) end def transform_TextExpression(e) PN::Call.new('str', transform(e.expr)) end def transform_TypeAlias(e) PN::Call.new('type-alias', PN::Literal.new(e.name), transform(e.type_expr)) end def transform_TypeDefinition(e) PN::Call.new('type-definition', PN::Literal.new(e.name), PN::Literal.new(e.parent), transform(e.body)) end def transform_TypeMapping(e) PN::Call.new('type-mapping', transform(e.type_expr), transform(e.mapping_expr)) end def transform_UnaryMinusExpression(e) if e.expr.is_a?(LiteralValue) v = e.expr.value if v.is_a?(Numeric) return PN::Literal.new(-v) end end PN::Call.new('-', transform(e.expr)) end def transform_UnfoldExpression(e) PN::Call.new('unfold', transform(e.expr)) end def transform_UnlessExpression(e) if_to_pn(e, 'unless') end def transform_VariableExpression(e) ne = e.expr PN::Call.new('var', ne.is_a?(Model::QualifiedName) ? PN::Literal.new(ne.value) : transform(ne)) end def transform_VirtualQuery(e) is_nop?(e.expr) ? PN::Call.new('virtual-query') : PN::Call.new('virtual-query', transform(e.expr)) end def is_nop?(e) e.nil? || e.is_a?(Nop) end def binary_op(e, op) PN::Call.new(op, transform(e.left_expr), transform(e.right_expr)) end def definition_to_pn(e, type_name, parent = nil, return_type = nil) entries = [PN::Literal.new(e.name).with_name('name')] entries << PN::Literal.new(parent).with_name('parent') unless parent.nil? entries << parameters_entry(e.parameters) unless e.parameters.empty? entries << block_as_entry('body', e.body) unless e.body.nil? entries << transform(return_type).with_name('returns') unless return_type.nil? PN::Map.new(entries).as_call(type_name) end def parameters_entry(parameters) PN::Map.new(parameters.map do |p| entries = [] entries << transform(p.type_expr).with_name('type') unless p.type_expr.nil? entries << PN::Literal(true).with_name('splat') if p.captures_rest entries << transform(p.value).with_name('value') unless p.value.nil? PN::Map.new(entries).with_name(p.name) end).with_name('params') end def block_as_entry(name, expr) if expr.is_a?(BlockExpression) transform(expr.statements).with_name(name) else transform([expr]).with_name(name) end end def pn_array(a) a.map { |e| transform(e) } end def call_to_pn(e, r, nr) entries = [transform(e.functor_expr).with_name('functor'), transform(e.arguments).with_name('args')] entries << transform(e.lambda).with_name('block') unless e.lambda.nil? PN::Map.new(entries).as_call(e.rval_required ? r : nr) end def if_to_pn(e, name) entries = [transform(e.test).with_name('test')] entries << block_as_entry('then', e.then_expr) unless is_nop?(e.then_expr) entries << block_as_entry('else', e.else_expr) unless is_nop?(e.else_expr) PN::Map.new(entries).as_call(name) end end end end ���������������puppet-5.5.10/lib/puppet/pops/parser/���������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�017357� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/parser/code_merger.rb�������������������������������������������������0000644�0052762�0001160�00000002514�13417161721�022154� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� class Puppet::Pops::Parser::CodeMerger # Concatenates the logic in the array of parse results into one parse result. # @return Puppet::Parser::AST::BlockExpression # def concatenate(parse_results) # this is a bit brute force as the result is already 3x ast with wrapped 4x content # this could be combined in a more elegant way, but it is only used to process a handful of files # at the beginning of a puppet run. TODO: Revisit for Puppet 4x when there is no 3x ast at the top. # PUP-5299, some sites have thousands of entries, and run out of stack when evaluating - the logic # below maps the logic as flatly as possible. # children = parse_results.select {|x| !x.nil? && x.code}.reduce([]) do |memo, parsed_class| case parsed_class.code when Puppet::Parser::AST::BlockExpression # the BlockExpression wraps a single 4x instruction that is most likely wrapped in a Factory memo + parsed_class.code.children.map {|c| c.is_a?(Puppet::Pops::Model::Factory) ? c.model : c } when Puppet::Pops::Model::Factory # If it is a 4x instruction wrapped in a Factory memo + parsed_class.code.model else # It is the instruction directly memo << parsed_class.code end end Puppet::Parser::AST::BlockExpression.new(:children => children) end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/parser/epp_parser.rb��������������������������������������������������0000644�0052762�0001160�00000003146�13417161721�022043� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The EppParser is a specialized Puppet Parser that starts parsing in Epp Text mode class Puppet::Pops::Parser::EppParser < Puppet::Pops::Parser::Parser # Initializes the epp parser support by creating a new instance of {Puppet::Pops::Parser::Lexer} # configured to start in Epp Lexing mode. # @return [void] # def initvars self.lexer = Puppet::Pops::Parser::Lexer2.new() end # Parses a file expected to contain epp text/DSL logic. def parse_file(file) unless FileTest.exist?(file) unless file =~ /\.epp$/ file = file + ".epp" end end @lexer.file = file _parse() end # Performs the parsing and returns the resulting model. # The lexer holds state, and this is setup with {#parse_string}, or {#parse_file}. # # TODO: deal with options containing origin (i.e. parsing a string from externally known location). # TODO: should return the model, not a Hostclass # # @api private # def _parse() begin @yydebug = false main = yyparse(@lexer,:scan_epp) # #Commented out now because this hides problems in the racc grammar while developing # # TODO include this when test coverage is good enough. # rescue Puppet::ParseError => except # except.line ||= @lexer.line # except.file ||= @lexer.file # except.pos ||= @lexer.pos # raise except # rescue => except # raise Puppet::ParseError.new(except.message, @lexer.file, @lexer.line, @lexer.pos, except) end return main ensure @lexer.clear @namestack = [] @definitions = [] end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/parser/evaluating_parser.rb�������������������������������������������0000644�0052762�0001160�00000010771�13417161721�023420� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Parser # Does not support "import" and parsing ruby files # class EvaluatingParser attr_reader :parser def initialize() @parser = Parser.new() end def self.singleton @instance ||= new end def parse_string(s, file_source = nil) @file_source = file_source clear() # Handling of syntax error can be much improved (in general), now it bails out of the parser # and does not have as rich information (when parsing a string), need to update it with the file source # (ideally, a syntax error should be entered as an issue, and not just thrown - but that is a general problem # and an improvement that can be made in the eparser (rather than here). # Also a possible improvement (if the YAML parser returns positions) is to provide correct output of position. # begin assert_and_report(parser.parse_string(s, file_source)).model rescue Puppet::ParseErrorWithIssue => e raise e rescue Puppet::ParseError => e # TODO: This is not quite right, why does not the exception have the correct file? e.file = @file_source unless e.file.is_a?(String) && !e.file.empty? raise e end end def parse_file(file) @file_source = file clear() assert_and_report(parser.parse_file(file)).model end def evaluate_string(scope, s, file_source = nil) evaluate(scope, parse_string(s, file_source)) end def evaluate_file(scope, file) evaluate(scope, parse_file(file)) end def clear() @acceptor = nil end # Create a closure that can be called in the given scope def closure(model, scope) Evaluator::Closure::Dynamic.new(evaluator, model, scope) end def evaluate(scope, model) return nil unless model evaluator.evaluate(model, scope) end # Evaluates the given expression in a local scope with the given variable bindings # set in this local scope, returns what the expression returns. # def evaluate_expression_with_bindings(scope, variable_bindings, expression) evaluator.evaluate_block_with_bindings(scope, variable_bindings, expression) end def evaluator # Do not use the cached evaluator if this is a migration run if (Puppet.lookup(:migration_checker) { nil }) return Evaluator::EvaluatorImpl.new() end @@evaluator ||= Evaluator::EvaluatorImpl.new() @@evaluator end def convert_to_3x(object, scope) evaluator.convert(object, scope, nil) end def validate(parse_result) resulting_acceptor = acceptor() validator(resulting_acceptor).validate(parse_result) resulting_acceptor end def acceptor() Validation::Acceptor.new end def validator(acceptor) Validation::ValidatorFactory_4_0.new().validator(acceptor) end def assert_and_report(parse_result) return nil unless parse_result if parse_result['source_ref'].nil? || parse_result['source_ref'] == '' parse_result['source_ref'] = @file_source end validation_result = validate(parse_result.model) IssueReporter.assert_and_report(validation_result, :emit_warnings => true) parse_result end def quote(x) self.class.quote(x) end # Translates an already parsed string that contains control characters, quotes # and backslashes into a quoted string where all such constructs have been escaped. # Parsing the return value of this method using the puppet parser should yield # exactly the same string as the argument passed to this method # # The method makes an exception for the two character sequences \$ and \s. They # will not be escaped since they have a special meaning in puppet syntax. # # TODO: Handle \uXXXX characters ?? # # @param x [String] The string to quote and "unparse" # @return [String] The quoted string # def self.quote(x) escaped = '"' p = nil x.each_char do |c| case p when nil # do nothing when "\t" escaped << '\\t' when "\n" escaped << '\\n' when "\f" escaped << '\\f' # TODO: \cx is a range of characters - skip for now # when "\c" # escaped << '\\c' when '"' escaped << '\\"' when '\\' escaped << if c == '$' || c == 's'; p; else '\\\\'; end # don't escape \ when followed by s or $ else escaped << p end p = c end escaped << p unless p.nil? escaped << '"' end class EvaluatingEppParser < EvaluatingParser def initialize() @parser = EppParser.new() end end end end end �������puppet-5.5.10/lib/puppet/pops/parser/heredoc_support.rb���������������������������������������������0000644�0052762�0001160�00000012220�13417161721�023101� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Parser module HeredocSupport include LexerSupport # Pattern for heredoc `@(endtag[:syntax][/escapes]) # Produces groups for endtag (group 1), syntax (group 2), and escapes (group 3) # PATTERN_HEREDOC = %r{@\(([^:/\r\n\)]+)(?::[[:blank:]]*([a-z][a-zA-Z0-9_+]+)[[:blank:]]*)?(?:/((?:\w|[$])*)[[:blank:]]*)?\)} def heredoc scn = @scanner ctx = @lexing_context locator = @locator before = scn.pos # scanner is at position before @( # find end of the heredoc spec str = scn.scan_until(/\)/) || lex_error(Issues::HEREDOC_UNCLOSED_PARENTHESIS, :followed_by => followed_by) pos_after_heredoc = scn.pos # Note: allows '+' as separator in syntax, but this needs validation as empty segments are not allowed md = str.match(PATTERN_HEREDOC) lex_error(Issues::HEREDOC_INVALID_SYNTAX) unless md endtag = md[1] syntax = md[2] || '' escapes = md[3] endtag.strip! # Is this a dq string style heredoc? (endtag enclosed in "") if endtag =~ /^"(.*)"$/ dqstring_style = true endtag = $1.strip end lex_error(Issues::HEREDOC_EMPTY_ENDTAG) unless endtag.length >= 1 resulting_escapes = [] if escapes escapes = "trnsuL$" if escapes.length < 1 escapes = escapes.split('') unless escapes.length == escapes.uniq.length lex_error(Issues::HEREDOC_MULTIPLE_AT_ESCAPES, :escapes => escapes) end resulting_escapes = ["\\"] escapes.each do |e| case e when "t", "r", "n", "s", "u", "$" resulting_escapes << e when "L" resulting_escapes += ["\n", "\r\n"] else lex_error(Issues::HEREDOC_INVALID_ESCAPE, :actual => e) end end end # Produce a heredoc token to make the syntax available to the grammar enqueue_completed([:HEREDOC, syntax, pos_after_heredoc - before], before) # If this is the second or subsequent heredoc on the line, the lexing context's :newline_jump contains # the position after the \n where the next heredoc text should scan. If not set, this is the first # and it should start scanning after the first found \n (or if not found == error). if ctx[:newline_jump] scn.pos = ctx[:newline_jump] else scn.scan_until(/\n/) || lex_error(Issues::HEREDOC_WITHOUT_TEXT) end # offset 0 for the heredoc, and its line number heredoc_offset = scn.pos heredoc_line = locator.line_for_offset(heredoc_offset)-1 # Compute message to emit if there is no end (to make it refer to the opening heredoc position). eof_error = create_lex_error(Issues::HEREDOC_WITHOUT_END_TAGGED_LINE) # Text from this position (+ lexing contexts offset for any preceding heredoc) is heredoc until a line # that terminates the heredoc is found. # (Endline in EBNF form): WS* ('|' WS*)? ('-' WS*)? endtag WS* \r? (\n|$) endline_pattern = /([[:blank:]]*)(?:([|])[[:blank:]]*)?(?:(\-)[[:blank:]]*)?#{Regexp.escape(endtag)}[[:blank:]]*\r?(?:\n|\z)/ lines = [] while !scn.eos? do one_line = scn.scan_until(/(?:\n|\z)/) raise eof_error unless one_line if md = one_line.match(endline_pattern) leading = md[1] has_margin = md[2] == '|' remove_break = md[3] == '-' # Record position where next heredoc (from same line as current @()) should start scanning for content ctx[:newline_jump] = scn.pos # Process captured lines - remove leading, and trailing newline str = heredoc_text(lines, leading, has_margin, remove_break) # Use a new lexer instance configured with a sub-locator to enable correct positioning sublexer = self.class.new() locator = Locator::SubLocator.new(locator, heredoc_line, heredoc_offset, leading.length()) # Emit a token that provides the grammar with location information about the lines on which the heredoc # content is based. enqueue([:SUBLOCATE, LexerSupport::TokenValue.new([:SUBLOCATE, lines, lines.reduce(0) {|size, s| size + s.length} ], heredoc_offset, locator)]) sublexer.lex_unquoted_string(str, locator, resulting_escapes, dqstring_style) sublexer.interpolate_uq_to(self) # Continue scan after @(...) scn.pos = pos_after_heredoc return else lines << one_line end end raise eof_error end # Produces the heredoc text string given the individual (unprocessed) lines as an array. # @param lines [Array<String>] unprocessed lines of text in the heredoc w/o terminating line # @param leading [String] the leading text up (up to pipe or other terminating char) # @param has_margin [Boolean] if the left margin should be adjusted as indicated by `leading` # @param remove_break [Boolean] if the line break (\r?\n) at the end of the last line should be removed or not # def heredoc_text(lines, leading, has_margin, remove_break) if has_margin leading_pattern = /^#{Regexp.escape(leading)}/ lines = lines.collect {|s| s.gsub(leading_pattern, '') } end result = lines.join('') result.gsub!(/\r?\n\z/m, '') if remove_break result end end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/parser/interpolation_support.rb���������������������������������������0000644�0052762�0001160�00000020153�13417161721�024363� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This module is an integral part of the Lexer. # It defines interpolation support # PERFORMANCE NOTE: There are 4 very similar methods in this module that are designed to be as # performant as possible. While it is possible to parameterize them into one common method, the overhead # of passing parameters and evaluating conditional logic has a negative impact on performance. # module Puppet::Pops::Parser::InterpolationSupport PATTERN_VARIABLE = %r{(::)?(\w+::)*\w+} # This is the starting point for a double quoted string with possible interpolation # The structure mimics that of the grammar. # The logic is explicit (where the former implementation used parameters/structures) given to a # generic handler. # (This is both easier to understand and faster). # def interpolate_dq scn = @scanner ctx = @lexing_context before = scn.pos # skip the leading " by doing a scan since the slurp_dqstring uses last matched when there is an error scn.scan(/"/) value,terminator = slurp_dqstring() text = value after = scn.pos while true case terminator when '"' # simple case, there was no interpolation, return directly return emit_completed([:STRING, text, scn.pos-before], before) when '${' count = ctx[:brace_count] ctx[:brace_count] += 1 # The ${ terminator is counted towards the string part enqueue_completed([:DQPRE, text, scn.pos-before], before) # Lex expression tokens until a closing (balanced) brace count is reached enqueue_until count break when '$' if varname = scn.scan(PATTERN_VARIABLE) # The $ is counted towards the variable enqueue_completed([:DQPRE, text, after-before-1], before) enqueue_completed([:VARIABLE, varname, scn.pos - after + 1], after - 1) break else # false $ variable start text += terminator value,terminator = slurp_dqstring() text += value after = scn.pos end end end interpolate_tail_dq # return the first enqueued token and shift the queue @token_queue.shift end def interpolate_tail_dq scn = @scanner ctx = @lexing_context before = scn.pos value,terminator = slurp_dqstring text = value after = scn.pos while true case terminator when '"' # simple case, there was no further interpolation, return directly enqueue_completed([:DQPOST, text, scn.pos-before], before) return when '${' count = ctx[:brace_count] ctx[:brace_count] += 1 # The ${ terminator is counted towards the string part enqueue_completed([:DQMID, text, scn.pos-before], before) # Lex expression tokens until a closing (balanced) brace count is reached enqueue_until count break when '$' if varname = scn.scan(PATTERN_VARIABLE) # The $ is counted towards the variable enqueue_completed([:DQMID, text, after-before-1], before) enqueue_completed([:VARIABLE, varname, scn.pos - after + 1], after - 1) break else # false $ variable start text += terminator value,terminator = slurp_dqstring text += value after = scn.pos end end end interpolate_tail_dq end # This is the starting point for a un-quoted string with possible interpolation # The logic is explicit (where the former implementation used parameters/strucures) given to a # generic handler. # (This is both easier to understand and faster). # def interpolate_uq scn = @scanner ctx = @lexing_context before = scn.pos value,terminator = slurp_uqstring() text = value after = scn.pos while true case terminator when '' # simple case, there was no interpolation, return directly enqueue_completed([:STRING, text, scn.pos-before], before) return when '${' count = ctx[:brace_count] ctx[:brace_count] += 1 # The ${ terminator is counted towards the string part enqueue_completed([:DQPRE, text, scn.pos-before], before) # Lex expression tokens until a closing (balanced) brace count is reached enqueue_until count break when '$' if varname = scn.scan(PATTERN_VARIABLE) # The $ is counted towards the variable enqueue_completed([:DQPRE, text, after-before-1], before) enqueue_completed([:VARIABLE, varname, scn.pos - after + 1], after - 1) break else # false $ variable start text += terminator value,terminator = slurp_uqstring() text += value after = scn.pos end end end interpolate_tail_uq nil end def interpolate_tail_uq scn = @scanner ctx = @lexing_context before = scn.pos value,terminator = slurp_uqstring text = value after = scn.pos while true case terminator when '' # simple case, there was no further interpolation, return directly enqueue_completed([:DQPOST, text, scn.pos-before], before) return when '${' count = ctx[:brace_count] ctx[:brace_count] += 1 # The ${ terminator is counted towards the string part enqueue_completed([:DQMID, text, scn.pos-before], before) # Lex expression tokens until a closing (balanced) brace count is reached enqueue_until count break when '$' if varname = scn.scan(PATTERN_VARIABLE) # The $ is counted towards the variable enqueue_completed([:DQMID, text, after-before-1], before) enqueue_completed([:VARIABLE, varname, scn.pos - after + 1], after - 1) break else # false $ variable start text += terminator value,terminator = slurp_uqstring text += value after = scn.pos end end end interpolate_tail_uq end # Enqueues lexed tokens until either end of input, or the given brace_count is reached # def enqueue_until brace_count scn = @scanner ctx = @lexing_context queue = @token_queue queue_base = @token_queue[0] scn.skip(self.class::PATTERN_WS) queue_size = queue.size until scn.eos? do if token = lex_token if token.equal?(queue_base) # A nested #interpolate_dq call shifted the queue_base token from the @token_queue. It must # be put back since it is intended for the top level #interpolate_dq call only. queue.insert(0, token) next # all relevant tokens are already on the queue end token_name = token[0] ctx[:after] = token_name if token_name == :RBRACE && ctx[:brace_count] == brace_count qlength = queue.size - queue_size if qlength == 1 # Single token is subject to replacement queue[-1] = transform_to_variable(queue[-1]) elsif qlength > 1 && [:DOT, :LBRACK].include?(queue[queue_size + 1][0]) # A first word, number of name token followed by '[' or '.' is subject to replacement # But not for other operators such as ?, +, - etc. where user must use a $ before the name # to get a variable queue[queue_size] = transform_to_variable(queue[queue_size]) end return end queue << token else scn.skip(self.class::PATTERN_WS) end end end def transform_to_variable(token) token_name = token[0] if [:NUMBER, :NAME, :WORD].include?(token_name) || self.class::KEYWORD_NAMES[token_name] || @taskm_keywords[token_name] t = token[1] ta = t.token_array [:VARIABLE, self.class::TokenValue.new([:VARIABLE, ta[1], ta[2]], t.offset, t.locator)] else token end end # Interpolates unquoted string and transfers the result to the given lexer # (This is used when a second lexer instance is used to lex a substring) # def interpolate_uq_to(lexer) interpolate_uq queue = @token_queue until queue.empty? do lexer.enqueue(queue.shift) end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/parser/lexer_support.rb�����������������������������������������������0000644�0052762�0001160�00000013512�13417161721�022614� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� module Puppet::Pops module Parser require 'puppet/util/multi_match' # This is an integral part of the Lexer. It is broken out into a separate module # for maintainability of the code, and making the various parts of the lexer focused. # module LexerSupport # Returns "<eof>" if at end of input, else the following 5 characters with \n \r \t escaped def followed_by return "<eof>" if @scanner.eos? result = @scanner.rest[0,5] + "..." result.gsub!("\t", '\t') result.gsub!("\n", '\n') result.gsub!("\r", '\r') result end # Returns a quoted string using " or ' depending on the given a strings's content def format_quote(q) if q == "'" '"\'"' else "'#{q}'" end end # Raises a Puppet::LexError with the given message def lex_error_without_pos(issue, args = {}) raise Puppet::ParseErrorWithIssue.new(issue.format(args), nil, nil, nil, nil, issue.issue_code, args) end # Raises a Puppet::ParserErrorWithIssue with the given issue and arguments def lex_error(issue, args = {}, pos=nil) raise create_lex_error(issue, args, pos) end def filename file = @locator.file file.is_a?(String) && !file.empty? ? file : nil end def line(pos) @locator.line_for_offset(pos || @scanner.pos) end def position(pos) @locator.pos_on_line(pos || @scanner.pos) end def lex_warning(issue, args = {}, pos=nil) Puppet::Util::Log.create({ :level => :warning, :message => issue.format(args), :issue_code => issue.issue_code, :file => filename, :line => line(pos), :pos => position(pos), }) end # @param issue [Issues::Issue] the issue # @param args [Hash<Symbol,String>] Issue arguments # @param pos [Integer] # @return [Puppet::ParseErrorWithIssue] the created error def create_lex_error(issue, args = {}, pos = nil) Puppet::ParseErrorWithIssue.new( issue.format(args), filename, line(pos), position(pos), nil, issue.issue_code, args) end # Asserts that the given string value is a float, or an integer in decimal, octal or hex form. # An error is raised if the given value does not comply. # def assert_numeric(value, pos) if value =~ /^0[xX]/ lex_error(Issues::INVALID_HEX_NUMBER, {:value => value}, pos) unless value =~ /^0[xX][0-9A-Fa-f]+$/ elsif value =~ /^0[^.]/ lex_error(Issues::INVALID_OCTAL_NUMBER, {:value => value}, pos) unless value =~ /^0[0-7]+$/ elsif value =~ /^\d+[eE.]/ lex_error(Issues::INVALID_DECIMAL_NUMBER, {:value => value}, pos) unless value =~ /^\d+(?:\.\d+)?(?:[eE]-?\d+)?$/ else lex_error(Issues::ILLEGAL_NUMBER, {:value => value}, pos) unless value =~ /^\d+$/ end end # A TokenValue keeps track of the token symbol, the lexed text for the token, its length # and its position in its source container. There is a cost associated with computing the # line and position on line information. # class TokenValue < Locatable attr_reader :token_array attr_reader :offset attr_reader :locator def initialize(token_array, offset, locator) @token_array = token_array @offset = offset @locator = locator end def length @token_array[2] end def [](key) case key when :value @token_array[1] when :file @locator.file when :line @locator.line_for_offset(@offset) when :pos @locator.pos_on_line(@offset) when :length @token_array[2] when :locator @locator when :offset @offset else nil end end def to_s # This format is very compact and is intended for debugging output from racc parser in # debug mode. If this is made more elaborate the output from a debug run becomes very hard to read. # "'#{self[:value]} #{@token_array[0]}'" end # TODO: Make this comparable for testing # vs symbolic, vs array with symbol and non hash, array with symbol and hash) # end MM = Puppet::Util::MultiMatch MM_ANY = MM::NOT_NIL BOM_UTF_8 = MM.new(0xEF, 0xBB, 0xBF, MM_ANY) BOM_UTF_16_1 = MM.new(0xFE, 0xFF, MM_ANY, MM_ANY) BOM_UTF_16_2 = MM.new(0xFF, 0xFE, MM_ANY, MM_ANY) BOM_UTF_32_1 = MM.new(0x00, 0x00, 0xFE, 0xFF ) BOM_UTF_32_2 = MM.new(0xFF, 0xFE, 0x00, 0x00 ) BOM_UTF_1 = MM.new(0xF7, 0x64, 0x4C, MM_ANY) BOM_UTF_EBCDIC = MM.new(0xDD, 0x73, 0x66, 0x73 ) BOM_SCSU = MM.new(0x0E, 0xFE, 0xFF, MM_ANY) BOM_BOCU = MM.new(0xFB, 0xEE, 0x28, MM_ANY) BOM_GB_18030 = MM.new(0x84, 0x31, 0x95, 0x33 ) LONGEST_BOM = 4 def assert_not_bom(content) name, size = case bom = get_bom(content) when BOM_UTF_32_1, BOM_UTF_32_2 ['UTF-32', 4] when BOM_GB_18030 ['GB-18030', 4] when BOM_UTF_EBCDIC ['UTF-EBCDIC', 4] when BOM_SCSU ['SCSU', 3] when BOM_UTF_8 ['UTF-8', 3] when BOM_UTF_1 ['UTF-1', 3] when BOM_BOCU ['BOCU', 3] when BOM_UTF_16_1, BOM_UTF_16_2 ['UTF-16', 2] else return end lex_error_without_pos( Puppet::Pops::Issues::ILLEGAL_BOM, { :format_name => name, :bytes => "[#{bom.values[0,size].map {|b| "%X" % b}.join(" ")}]" }) end def get_bom(content) # get 5 bytes as efficiently as possible (none of the string methods works since a bom consists of # illegal characters on most platforms, and there is no get_bytes(n). Explicit calls are faster than # looping with a lambda. The get_byte returns nil if there are too few characters, and they # are changed to spaces MM.new( (content.getbyte(0) || ' '), (content.getbyte(1) || ' '), (content.getbyte(2) || ' '), (content.getbyte(3) || ' ') ) end end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/parser/locatable.rb���������������������������������������������������0000644�0052762�0001160�00000000736�13417161721�021633� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Interface for something that is "locatable" (holds offset and length). class Puppet::Pops::Parser::Locatable # The offset in the locator's content def offset end # The length in the locator from the given offset def length end # This class is useful for testing class Fixed < Puppet::Pops::Parser::Locatable attr_reader :offset attr_reader :length def initialize(offset, length) @offset = offset @length = length end end end ����������������������������������puppet-5.5.10/lib/puppet/pops/parser/locator.rb�����������������������������������������������������0000644�0052762�0001160�00000024005�13417161721�021343� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Parser # Helper class that keeps track of where line breaks are located and can answer questions about positions. # class Locator # Creates, or recreates a Locator. A Locator is created if index is not given (a scan is then # performed of the given source string. # def self.locator(string, file, index = nil, char_offsets = false) if char_offsets LocatorForChars.new(string, file, index) else Locator19.new(string, file, index) end end # Returns the file name associated with the string content def file end # Returns the string content def string end def to_s "Locator for file #{file}" end # Returns the position on line (first position on a line is 1) def pos_on_line(offset) end # Returns the line number (first line is 1) for the given offset def line_for_offset(offset) end # Returns the offset on line (first offset on a line is 0). # def offset_on_line(offset) end # Returns the character offset for a given reported offset def char_offset(byte_offset) end # Returns the length measured in number of characters from the given start and end byte offset def char_length(offset, end_offset) end # Extracts the text from offset with given length (measured in what the locator uses for offset) # @returns String - the extracted text def extract_text(offset, length) end def extract_tree_text(ast) first = ast.offset last = first + ast.length ast._pcore_all_contents([]) do |m| next unless m.is_a?(Model::Positioned) m_offset = m.offset m_last = m_offset + m.length first = m_offset if m_offset < first last = m_last if m_last > last end extract_text(first, last - first) end # Returns the line index - an array of line offsets for the start position of each line, starting at 0 for # the first line. # def line_index() end # Produces an URI with path?line=n&pos=n. If origin is unknown the URI is string:?line=n&pos=n def to_uri(ast) f = file if f.nil? || f.empty? f = 'string:' else f = Puppet::Util.path_to_uri(f).to_s end offset = ast.offset URI("#{f}?line=#{line_for_offset(offset).to_s}&pos=#{pos_on_line(offset).to_s}") end # A Sublocator locates a concrete locator (subspace) in a virtual space. # The `leading_line_count` is the (virtual) number of lines preceding the first line in the concrete locator. # The `leading_offset` is the (virtual) byte offset of the first byte in the concrete locator. # The `leading_line_offset` is the (virtual) offset / margin in characters for each line. # # This illustrates characters in the sublocator (`.`) inside the subspace (`X`): # # 1:XXXXXXXX # 2:XXXX.... .. ... .. # 3:XXXX. . .... .. # 4:XXXX............ # # This sublocator would be configured with leading_line_count = 1, # leading_offset=8, and leading_line_offset=4 # # Note that leading_offset must be the same for all lines and measured in characters. # class SubLocator < Locator attr_reader :locator attr_reader :leading_line_count attr_reader :leading_offset attr_reader :leading_line_offset def self.sub_locator(string, file, leading_line_count, leading_offset, leading_line_offset) self.new(Locator.locator(string, file), leading_line_count, leading_offset, leading_line_offset) end def initialize(locator, leading_line_count, leading_offset, leading_line_offset) @locator = locator @leading_line_count = leading_line_count @leading_offset = leading_offset @leading_line_offset = leading_line_offset end def file @locator.file end def string @locator.string end # Given offset is offset in the subspace def line_for_offset(offset) @locator.line_for_offset(offset) + @leading_line_count end # Given offset is offset in the subspace def offset_on_line(offset) @locator.offset_on_line(offset) + @leading_line_offset end # Given offset is offset in the subspace def char_offset(offset) effective_line = @locator.line_for_offset(offset) locator.char_offset(offset) + (effective_line * @leading_line_offset) + @leading_offset end # Given offsets are offsets in the subspace def char_length(offset, end_offset) effective_line = @locator.line_for_offset(end_offset) - @locator.line_for_offset(offset) locator.char_length(offset, end_offset) + (effective_line * @leading_line_offset) end def pos_on_line(offset) offset_on_line(offset) +1 end end class AbstractLocator < Locator attr_accessor :line_index attr_accessor :string attr_reader :string attr_reader :file # Create a locator based on a content string, and a boolean indicating if ruby version support multi-byte strings # or not. # def initialize(string, file, line_index = nil) @string = string.freeze @file = file.freeze @prev_offset = nil @prev_line = nil @line_index = line_index compute_line_index if line_index.nil? end # Returns the position on line (first position on a line is 1) def pos_on_line(offset) offset_on_line(offset) +1 end def to_location_hash(reported_offset, end_offset) pos = pos_on_line(reported_offset) offset = char_offset(reported_offset) length = char_length(reported_offset, end_offset) start_line = line_for_offset(reported_offset) { :line => start_line, :pos => pos, :offset => offset, :length => length} end # Returns the index of the smallest item for which the item > the given value # This is a min binary search. Although written in Ruby it is only slightly slower than # the corresponding method in C in Ruby 2.0.0 - the main benefit to use this method over # the Ruby C version is that it returns the index (not the value) which means there is not need # to have an additional structure to get the index (or record the index in the structure). This # saves both memory and CPU. It also does not require passing a block that is called since this # method is specialized to search the line index. # def ary_bsearch_i(ary, value) low = 0 high = ary.length mid = nil smaller = false satisfied = false v = nil while low < high do mid = low + ((high - low) / 2) v = (ary[mid] > value) if v == true satisfied = true smaller = true elsif !v smaller = false else raise TypeError, "wrong argument, must be boolean or nil, got '#{v.class}'" end if smaller high = mid else low = mid + 1; end end return nil if low == ary.length return nil if !satisfied return low end def hash [string, file, line_index].hash end # Equal method needed by serializer to perform tabulation def eql?(o) self.class == o.class && string == o.string && file == o.file && line_index == o.line_index end # Common impl for 18 and 19 since scanner is byte based def compute_line_index scanner = StringScanner.new(string) result = [0] # first line starts at 0 while scanner.scan_until(/\n/) result << scanner.pos end self.line_index = result.freeze end # Returns the line number (first line is 1) for the given offset def line_for_offset(offset) if @prev_offset == offset # use cache return @prev_line end if line_nbr = ary_bsearch_i(line_index, offset) # cache @prev_offset = offset @prev_line = line_nbr return line_nbr end # If not found it is after last # clear cache @prev_offset = @prev_line = nil return line_index.size end end class LocatorForChars < AbstractLocator def offset_on_line(offset) line_offset = line_index[ line_for_offset(offset)-1 ] offset - line_offset end def char_offset(char_offset) char_offset end def char_length(offset, end_offset) end_offset - offset end # Extracts the text from char offset with given byte length # @returns String - the extracted text def extract_text(offset, length) string.slice(offset, length) end end # This implementation is for Ruby19 and Ruby20. It uses byteslice to get strings from byte based offsets. # For Ruby20 this is faster than using the Stringscanner.charpos method (byteslice outperforms it, when # strings are frozen). # class Locator19 < AbstractLocator include Types::PuppetObject def self._pcore_type @type ||= Types::PObjectType.new('Puppet::AST::Locator', { 'attributes' => { 'string' => Types::PStringType::DEFAULT, 'file' => Types::PStringType::DEFAULT, 'line_index' => { Types::KEY_TYPE => Types::POptionalType.new(Types::PArrayType.new(Types::PIntegerType::DEFAULT)), Types::KEY_VALUE => nil } } }) end # Returns the offset on line (first offset on a line is 0). # Ruby 19 is multibyte but has no character position methods, must use byteslice def offset_on_line(offset) line_offset = line_index[ line_for_offset(offset)-1 ] string.byteslice(line_offset, offset-line_offset).length end # Returns the character offset for a given byte offset # Ruby 19 is multibyte but has no character position methods, must use byteslice def char_offset(byte_offset) string.byteslice(0, byte_offset).length end # Returns the length measured in number of characters from the given start and end byte offset def char_length(offset, end_offset) string.byteslice(offset, end_offset - offset).length end # Extracts the text from byte offset with given byte length # @returns String - the extracted text def extract_text(offset, length) string.byteslice(offset, length) end end end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/parser/parser_support.rb����������������������������������������������0000644�0052762�0001160�00000017666�13417161721�023007� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parser/functions' require 'puppet/parser/files' require 'puppet/resource/type_collection' require 'puppet/resource/type' require 'monitor' module Puppet::Pops module Parser # Supporting logic for the parser. # This supporting logic has slightly different responsibilities compared to the original Puppet::Parser::Parser. # It is only concerned with parsing. # class Parser # Note that the name of the contained class and the file name (currently parser_support.rb) # needs to be different as the class is generated by Racc, and this file (parser_support.rb) is included as a mix in # # Simplify access to the Model factory # Note that the parser/parser support does not have direct knowledge about the Model. # All model construction/manipulation is made by the Factory. # Factory = Model::Factory attr_accessor :lexer attr_reader :definitions # Returns the token text of the given lexer token, or nil, if token is nil def token_text t return t if t.nil? if t.is_a?(Factory) && t.model_class <= Model::QualifiedName t['value'] elsif t.is_a?(Model::QualifiedName) t.value else # else it is a lexer token t[:value] end end # Produces the fully qualified name, with the full (current) namespace for a given name. # # This is needed because class bodies are lazily evaluated and an inner class' container(s) may not # have been evaluated before some external reference is made to the inner class; its must therefore know its complete name # before evaluation-time. # def classname(name) [namespace, name].join('::').sub(/^::/, '') end # Raises a Parse error with location information. Information about file is always obtained from the # lexer. Line and position is produced if the given semantic is a Positioned object and have been given an offset. # def error(semantic, message) except = Puppet::ParseError.new(message) if semantic.is_a?(LexerSupport::TokenValue) except.file = semantic[:file]; except.line = semantic[:line]; except.pos = semantic[:pos]; else locator = @lexer.locator except.file = locator.file if semantic.is_a?(Factory) offset = semantic['offset'] unless offset.nil? except.line = locator.line_for_offset(offset) except.pos = locator.pos_on_line(offset) end end end raise except end # Parses a file expected to contain pp DSL logic. def parse_file(file) unless Puppet::FileSystem.exist?(file) unless file =~ /\.pp$/ file = file + ".pp" end end @lexer.file = file _parse end def initialize() @lexer = Lexer2.new @namestack = [] @definitions = [] end # This is a callback from the generated parser (when an error occurs while parsing) # def on_error(token,value,stack) if token == 0 # denotes end of file value_at = 'end of input' else value_at = "'#{value[:value]}'" end error = Issues::SYNTAX_ERROR.format(:where => value_at) error = "#{error}, token: #{token}" if @yydebug # Note, old parser had processing of "expected token here" - do not try to reinstate: # The 'expected' is only of value at end of input, otherwise any parse error involving a # start of a pair will be reported as expecting the close of the pair - e.g. "$x.each |$x {|", would # report that "seeing the '{', the '}' is expected. That would be wrong. # Real "expected" tokens are very difficult to compute (would require parsing of racc output data). Output of the stack # could help, but can require extensive backtracking and produce many options. # # The lexer should handle the "expected instead of end of file for strings, and interpolation", other expectancies # must be handled by the grammar. The lexer may have enqueued tokens far ahead - the lexer's opinion about this # is not trustworthy. # file = nil line = nil pos = nil if token != 0 file = value[:file] line = value[:line] pos = value[:pos] else # At end of input, use what the lexer thinks is the source file file = lexer.file end file = nil unless file.is_a?(String) && !file.empty? raise Puppet::ParseErrorWithIssue.new(error, file, line, pos, nil, Issues::SYNTAX_ERROR.issue_code) end # Parses a String of pp DSL code. # def parse_string(code, path = nil) @lexer.lex_string(code, path) _parse() end # Mark the factory wrapped model object with location information # @return [Factory] the given factory # @api private # def loc(factory, start_locatable, end_locatable = nil) factory.record_position(@lexer.locator, start_locatable, end_locatable) end # Mark the factory wrapped heredoc model object with location information # @return [Factory] the given factory # @api private # def heredoc_loc(factory, start_locatable, end_locatable = nil) factory.record_heredoc_position(start_locatable, end_locatable) end def aryfy(o) o = [o] unless o.is_a?(Array) o end def namespace @namestack.join('::') end def namestack(name) @namestack << name end def namepop() @namestack.pop end def add_definition(definition) @definitions << definition.model definition end def add_mapping(produces) # The actual handling of mappings happens in PopsBridge add_definition(produces) end # Transforms an array of expressions containing literal name expressions to calls if followed by an # expression, or expression list # def transform_calls(expressions) # Factory transform raises an error if a non qualified name is followed by an argument list # since there is no way that that can be transformed back to sanity. This occurs in situations like this: # # $a = 10, notice hello # # where the "10, notice" forms an argument list. The parser builds an Array with the expressions and includes # the comma tokens to enable the error to be reported against the first comma. # begin Factory.transform_calls(expressions) rescue Factory::ArgsToNonCallError => e # e.args[1] is the first comma token in the list # e.name_expr is the function name expression if e.name_expr.is_a?(Factory) && e.name_expr.model_class <= Model::QualifiedName error(e.args[1], _("attempt to pass argument list to the function '%{name}' which cannot be called without parentheses") % { name: e.name_expr['value'] }) else error(e.args[1], _("illegal comma separated argument list")) end end end # Transforms a LEFT followed by the result of attribute_operations, this may be a call or an invalid sequence def transform_resource_wo_title(left, resource, lbrace_token, rbrace_token) Factory.transform_resource_wo_title(left, resource, lbrace_token, rbrace_token) end # Creates a program with the given body. # def create_program(body) locator = @lexer.locator Factory.PROGRAM(body, definitions, locator) end # Creates an empty program with a single No-op at the input's EOF offset with 0 length. # def create_empty_program() locator = @lexer.locator no_op = Factory.literal(nil) # Create a synthetic NOOP token at EOF offset with 0 size. The lexer does not produce an EOF token that is # visible to the grammar rules. Creating this token is mainly to reuse the positioning logic as it # expects a token decorated with location information. _, token = @lexer.emit_completed([:NOOP,'',0], locator.string.bytesize) loc(no_op, token) # Program with a Noop program = Factory.PROGRAM(no_op, [], locator) program end # Performs the parsing and returns the resulting model. # The lexer holds state, and this is setup with {#parse_string}, or {#parse_file}. # # @api private # def _parse() begin @yydebug = false main = yyparse(@lexer,:scan) end return main ensure @lexer.clear @namestack = [] @definitions = [] end end end end ��������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/parser/pn_parser.rb���������������������������������������������������0000644�0052762�0001160�00000015116�13417161721�021674� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Parser class PNParser LIT_TRUE = 'true'.freeze LIT_FALSE = 'false'.freeze LIT_NIL = 'nil'.freeze TOKEN_END = 0 TOKEN_BOOL = 1 TOKEN_NIL = 2 TOKEN_INT = 3 TOKEN_FLOAT = 4 TOKEN_IDENTIFIER = 5 TOKEN_WS = 0x20 TOKEN_STRING = 0x22 TOKEN_KEY = 0x3a TOKEN_LP = 0x28 TOKEN_RP = 0x29 TOKEN_LB = 0x5b TOKEN_RB = 0x5d TOKEN_LC = 0x7b TOKEN_RC = 0x7d TYPE_END = 0 TYPE_WS = 1 TYPE_DELIM = 2 TYPE_KEY_START = 3 TYPE_STRING_START = 4 TYPE_IDENTIFIER = 5 TYPE_MINUS = 6 TYPE_DIGIT = 7 TYPE_ALPHA = 8 def initialize @char_types = self.class.char_types end def parse(text, locator = nil, offset = nil) @locator = locator @offset = offset @text = text @codepoints = text.codepoints.to_a.freeze @pos = 0 @token = TOKEN_END @token_value = nil next_token parse_next end private def parse_next case @token when TOKEN_LB parse_array when TOKEN_LC parse_map when TOKEN_LP parse_call when TOKEN_BOOL, TOKEN_INT, TOKEN_FLOAT, TOKEN_STRING, TOKEN_NIL parse_literal when TOKEN_END parse_error(_('unexpected end of input')) else parse_error(_('unexpected %{value}' % { value: @token_value })) end end def parse_error(message) file = '' line = 1 pos = 1 if @locator file = @locator.file line = @locator.line_for_offset(@offset) pos = @locator.pos_on_line(@offset) end @codepoints[0, @pos].each do |c| if c == 0x09 line += 1 pos = 1 end end raise Puppet::ParseError.new(message, file, line, pos) end def parse_literal pn = PN::Literal.new(@token_value) next_token pn end def parse_array next_token PN::List.new(parse_elements(TOKEN_RB)) end def parse_map next_token entries = [] while @token != TOKEN_RC && @token != TOKEN_END parse_error(_('map key expected')) unless @token == TOKEN_KEY key = @token_value next_token entries << parse_next.with_name(key) end next_token PN::Map.new(entries) end def parse_call next_token parse_error(_("expected identifier to follow '('")) unless @token == TOKEN_IDENTIFIER name = @token_value next_token PN::Call.new(name, *parse_elements(TOKEN_RP)) end def parse_elements(end_token) elements = [] while @token != end_token && @token != TOKEN_END elements << parse_next end parse_error(_("missing '%{token}' to end list") % { token: end_token.chr(Encoding::UTF_8) } ) unless @token == end_token next_token elements end # All methods below belong to the PN lexer def self.char_types unless instance_variable_defined?(:@char_types) @char_types = Array.new(0x80, TYPE_IDENTIFIER) @char_types[0] = TYPE_END [0x09, 0x0d, 0x0a, 0x20].each { |n| @char_types[n] = TYPE_WS } [TOKEN_LP, TOKEN_RP, TOKEN_LB, TOKEN_RB, TOKEN_LC, TOKEN_RC].each { |n| @char_types[n] = TYPE_DELIM } @char_types[0x2d] = TYPE_MINUS (0x30..0x39).each { |n| @char_types[n] = TYPE_DIGIT } (0x41..0x5a).each { |n| @char_types[n] = TYPE_ALPHA } (0x61..0x7a).each { |n| @char_types[n] = TYPE_ALPHA } @char_types[TOKEN_KEY] = TYPE_KEY_START @char_types[TOKEN_STRING] = TYPE_STRING_START @char_types.freeze end @char_types end def next_token skip_white s = @pos c = next_cp case @char_types[c] when TYPE_END @token_value = nil @token = TOKEN_END when TYPE_MINUS if @char_types[peek_cp] == TYPE_DIGIT next_token # consume float or integer @token_value = -@token_value else consume_identifier(s) end when TYPE_DIGIT skip_decimal_digits c = peek_cp if c == 0x2e # '.' @pos += 1 consume_float(s, c) else @token_value = @text[s..@pos].to_i @token = TOKEN_INT end when TYPE_DELIM @token_value = @text[s] @token = c when TYPE_KEY_START if @char_types[peek_cp] == TYPE_ALPHA next_token @token = TOKEN_KEY else parse_error(_("expected identifier after ':'")) end when TYPE_STRING_START consume_string else consume_identifier(s) end end def consume_identifier(s) while @char_types[peek_cp] >= TYPE_IDENTIFIER do @pos += 1 end id = @text[s...@pos] case id when LIT_TRUE @token = TOKEN_BOOL @token_value = true when LIT_FALSE @token = TOKEN_BOOL @token_value = false when LIT_NIL @token = TOKEN_NIL @token_value = nil else @token = TOKEN_IDENTIFIER @token_value = id end end def consume_string s = @pos b = '' loop do c = next_cp case c when TOKEN_END @pos = s - 1 parse_error(_('unterminated quote')) when TOKEN_STRING @token_value = b @token = TOKEN_STRING break when 0x5c # '\' c = next_cp case c when 0x74 # 't' b << "\t" when 0x72 # 'r' b << "\r" when 0x6e # 'n' b << "\n" when TOKEN_STRING b << '"' when 0x5c # '\' b << "\\" when 0x6f # 'o' c = 0 3.times do n = next_cp if 0x30 <= n && n <= 0x37c c *= 8 c += n - 0x30 else parse_error(_('malformed octal quote')) end end b << c else b << "\\" b << c end else b << c end end end def consume_float(s, d) parse_error(_('digit expected')) if skip_decimal_digits == 0 c = peek_cp if d == 0x2e # '.' if c == 0x45 || c == 0x65 # 'E' or 'e' @pos += 1 parse_error(_('digit expected')) if skip_decimal_digits == 0 c = peek_cp end end parse_error(_('digit expected')) if @char_types[c] == TYPE_ALPHA @token_value = @text[s...@pos].to_f @token = TOKEN_FLOAT end def skip_decimal_digits count = 0 c = peek_cp if c == 0x2d || c == 0x2b # '-' or '+' @pos += 1 c = peek_cp end while @char_types[c] == TYPE_DIGIT do @pos += 1 c = peek_cp count += 1 end count end def skip_white while @char_types[peek_cp] == TYPE_WS do @pos += 1 end end def next_cp c = 0 if @pos < @codepoints.size c = @codepoints[@pos] @pos += 1 end c end def peek_cp @pos < @codepoints.size ? @codepoints[@pos] : 0 end end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/parser/slurp_support.rb�����������������������������������������������0000644�0052762�0001160�00000010645�13417161721�022646� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Parser # This module is an integral part of the Lexer. # It defines the string slurping behavior - finding the string and non string parts in interpolated # strings, translating escape sequences in strings to their single character equivalence. # # PERFORMANCE NOTE: The various kinds of slurping could be made even more generic, but requires # additional parameter passing and evaluation of conditional logic. # TODO: More detailed performance analysis of excessive character escaping and interpolation. # module SlurpSupport include LexerSupport SLURP_SQ_PATTERN = /(?:[^\\]|^)(?:[\\]{2})*[']/ SLURP_DQ_PATTERN = /(?:[^\\]|^)(?:[\\]{2})*(["]|[$]\{?)/ SLURP_UQ_PATTERN = /(?:[^\\]|^)(?:[\\]{2})*([$]\{?|\z)/ # unquoted, no escapes SLURP_UQNE_PATTERN = /(\$\{?|\z)/m SLURP_ALL_PATTERN = /.*(\z)/m SQ_ESCAPES = %w{ \\ ' } DQ_ESCAPES = %w{ \\ $ ' " r n t s u}+["\r\n", "\n"] UQ_ESCAPES = %w{ \\ $ r n t s u}+["\r\n", "\n"] def slurp_sqstring # skip the leading ' @scanner.pos += 1 str = slurp(@scanner, SLURP_SQ_PATTERN, SQ_ESCAPES, :ignore_invalid_escapes) lex_error(Issues::UNCLOSED_QUOTE, :after => "\"'\"", :followed_by => followed_by) unless str str[0..-2] # strip closing "'" from result end def slurp_dqstring scn = @scanner last = scn.matched str = slurp(scn, SLURP_DQ_PATTERN, DQ_ESCAPES, false) unless str lex_error(Issues::UNCLOSED_QUOTE, :after => format_quote(last), :followed_by => followed_by) end # Terminator may be a single char '"', '$', or two characters '${' group match 1 (scn[1]) from the last slurp holds this terminator = scn[1] [str[0..(-1 - terminator.length)], terminator] end # Copy from old lexer - can do much better def slurp_uqstring scn = @scanner str = slurp(scn, @lexing_context[:uq_slurp_pattern], @lexing_context[:escapes], :ignore_invalid_escapes) # Terminator may be a single char '$', two characters '${', or empty string '' at the end of intput. # Group match 1 holds this. # The exceptional case is found by looking at the subgroup 1 of the most recent match made by the scanner (i.e. @scanner[1]). # This is the last match made by the slurp method (having called scan_until on the scanner). # If there is a terminating character is must be stripped and returned separately. # terminator = scn[1] [str[0..(-1 - terminator.length)], terminator] end # Slurps a string from the given scanner until the given pattern and then replaces any escaped # characters given by escapes into their control-character equivalent or in case of line breaks, replaces the # pattern \r?\n with an empty string. # The returned string contains the terminating character. Returns nil if the scanner can not scan until the given # pattern. # def slurp(scanner, pattern, escapes, ignore_invalid_escapes) str = scanner.scan_until(pattern) || return return str unless str.include?('\\') return str.gsub!(/\\(\\|')/m, '\1') || str if escapes.equal?(SQ_ESCAPES) # Process unicode escapes first as they require getting 4 hex digits # If later a \u is found it is warned not to be a unicode escape if escapes.include?('u') # gsub must be repeated to cater for adjacent escapes while(str.gsub!(/((?:[^\\]|^)(?:[\\]{2})*)\\u(?:([\da-fA-F]{4})|\{([\da-fA-F]{1,6})\})/m) { $1 + [($2 || $3).hex].pack("U") }) # empty block. Everything happens in the gsub block end end begin str.gsub!(/\\([^\r\n]|(?:\r?\n))/m) { ch = $1 if escapes.include? ch case ch when 'r' ; "\r" when 'n' ; "\n" when 't' ; "\t" when 's' ; ' ' when 'u' lex_warning(Issues::ILLEGAL_UNICODE_ESCAPE) "\\u" when "\n" ; '' when "\r\n"; '' else ch end else lex_warning(Issues::UNRECOGNIZED_ESCAPE, :ch => ch) unless ignore_invalid_escapes "\\#{ch}" end } rescue ArgumentError => e # A invalid byte sequence may be the result of faulty input as well, but that could not possibly # have reached this far... Unfortunately there is no more specific error and a match on message is # required to differentiate from other internal problems. if e.message =~ /invalid byte sequence/ lex_error(Issues::ILLEGAL_UNICODE_ESCAPE) else raise e end end str end end end end �������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/parser/egrammar.ra����������������������������������������������������0000644�0052762�0001160�00000077576�13417161721�021517� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# vim: syntax=ruby # Parser using the Pops model, expression based class Puppet::Pops::Parser::Parser token STRING DQPRE DQMID DQPOST token WORD token LBRACK RBRACK LBRACE RBRACE SYMBOL FARROW COMMA TRUE token FALSE EQUALS APPENDS DELETES LESSEQUAL NOTEQUAL DOT COLON LLCOLLECT RRCOLLECT token QMARK WSLPAREN LPAREN RPAREN ISEQUAL GREATEREQUAL GREATERTHAN LESSTHAN token IF ELSE token DEFINE ELSIF VARIABLE CLASS INHERITS NODE BOOLEAN token NAME SEMIC CASE DEFAULT AT ATAT LCOLLECT RCOLLECT CLASSREF token NOT OR AND UNDEF PARROW PLUS MINUS TIMES DIV LSHIFT RSHIFT UMINUS token MATCH NOMATCH REGEX IN_EDGE OUT_EDGE IN_EDGE_SUB OUT_EDGE_SUB token IN UNLESS PIPE token LAMBDA SELBRACE token NUMBER token HEREDOC SUBLOCATE token RENDER_STRING RENDER_EXPR EPP_START EPP_END EPP_END_TRIM token FUNCTION token TYPE token PRIVATE ATTR token APPLICATION PRODUCES CONSUMES SITE token PLAN token LOW prechigh left HIGH left SEMIC left PIPE left LPAREN WSLPAREN left RPAREN left DOT nonassoc EPP_START left LBRACK LISTSTART left RBRACK left LCOLLECT LLCOLLECT right NOT nonassoc SPLAT nonassoc UMINUS left IN left MATCH NOMATCH left TIMES DIV MODULO left MINUS PLUS left LSHIFT RSHIFT left NOTEQUAL ISEQUAL left GREATEREQUAL GREATERTHAN LESSTHAN LESSEQUAL left QMARK left AND left OR left LBRACE left SELBRACE left RBRACE right AT ATAT right APPENDS DELETES EQUALS left IN_EDGE OUT_EDGE IN_EDGE_SUB OUT_EDGE_SUB left FARROW left COMMA nonassoc RENDER_EXPR nonassoc RENDER_STRING left LOW preclow rule # Produces [Model::Program] with a body containing what was parsed program : statements { result = create_program(Factory.block_or_expression(val[0])) } | epp_expression { result = create_program(val[0]) } | { result = create_empty_program } # Produces a semantic model (non validated, but semantically adjusted). statements : syntactic_statements { result = transform_calls(val[0]) } # Collects sequence of elements into a list that the statements rule can transform # (Needed because language supports function calls without parentheses around arguments). # Produces Array<Model::Expression> # syntactic_statements : syntactic_statement { result = [val[0]]} | syntactic_statements SEMIC syntactic_statement { result = val[0].push val[2] } | syntactic_statements syntactic_statement { result = val[0].push val[1] } # Produce a single expression or Array of expression # This exists to handle multiple arguments to non parenthesized function call. If e is expression, # the a program can consists of e [e,e,e] where the first may be a name of a function to call. # syntactic_statement : assignment =LOW { result = val[0] } | syntactic_statement COMMA assignment =LOW { result = aryfy(val[0]).push(val[1]).push(val[2]) } # Assignment (is right recursive since assignment is right associative) assignment : relationship =LOW | relationship EQUALS assignment { result = val[0].set(val[2]) ; loc result, val[1] } | relationship APPENDS assignment { result = val[0].plus_set(val[2]) ; loc result, val[1] } | relationship DELETES assignment { result = val[0].minus_set(val[2]); loc result, val[1] } # Argument is like assignment but permits KEY_ENTRY which it converts it to a HASH_UNFOLDED argument : assignment | hashpair { result = Factory.HASH_UNFOLDED([val[0]]); loc result, val[0] } # Repeated adjacent HASH_UNFOLDED entries merged into one HASH_UNFOLDED arguments : argument { result = [val[0]] } | arguments COMMA argument { result = Factory.ARGUMENTS(val[0], val[2]) } relationship : resource =LOW | relationship IN_EDGE resource { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] } | relationship IN_EDGE_SUB resource { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] } | relationship OUT_EDGE resource { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] } | relationship OUT_EDGE_SUB resource { result = val[0].relop(val[1][:value], val[2]); loc result, val[1] } #-- RESOURCE # resource : expression = LOW #---VIRTUAL | AT resource { result = val[1] unless Factory.set_resource_form(result, 'virtual') # This is equivalent to a syntax error - additional semantic restrictions apply error val[0], "Virtual (@) can only be applied to a Resource Expression" end # relocate the result loc result, val[0], val[1] } #---EXPORTED | ATAT resource { result = val[1] unless Factory.set_resource_form(result, 'exported') # This is equivalent to a syntax error - additional semantic restrictions apply error val[0], "Exported (@@) can only be applied to a Resource Expression" end # relocate the result loc result, val[0], val[1] } #---RESOURCE TITLED 3x and 4x | resource LBRACE expression COLON attribute_operations additional_resource_bodies RBRACE { bodies = [Factory.RESOURCE_BODY(val[2], val[4])] + val[5] result = Factory.RESOURCE(val[0], bodies) loc result, val[0], val[6] } #---CLASS RESOURCE | CLASS LBRACE resource_bodies endsemi RBRACE { result = Factory.RESOURCE(Factory.fqn(token_text(val[0])), val[2]) loc result, val[0], val[4] } # --RESOURCE 3X Expression # Handles both 3x overrides and defaults (i.e. single resource_body without title colon) # Slated for possible deprecation since it requires transformation and mix static/evaluation check # | resource LBRACE attribute_operations endcomma RBRACE { result = case Factory.resource_shape(val[0]) when :resource, :class # This catches deprecated syntax. # If the attribute operations does not include +>, then the found expression # is actually a LEFT followed by LITERAL_HASH # unless tmp = transform_resource_wo_title(val[0], val[2], val[1], val[4]) error val[1], "Syntax error resource body without title or hash with +>" end tmp when :defaults Factory.RESOURCE_DEFAULTS(val[0], val[2]) when :override # This was only done for override in original - TODO should it be here at all Factory.RESOURCE_OVERRIDE(val[0], val[2]) else error val[0], "Expression is not valid as a resource, resource-default, or resource-override" end loc result, val[0], val[4] } resource_body : expression COLON attribute_operations endcomma { result = Factory.RESOURCE_BODY(val[0], val[2]) } resource_bodies : resource_body =HIGH { result = [val[0]] } | resource_bodies SEMIC resource_body =HIGH { result = val[0].push val[2] } # This is a rule for the intermediate state where RACC has seen enough tokens to understand that # what is expressed is a Resource Expression, it now has to get to the finishing line # additional_resource_bodies : endcomma { result = [] } | endcomma SEMIC { result = [] } | endcomma SEMIC resource_bodies endsemi { result = val[2] } #-- EXPRESSION # expression : primary_expression | capability_mapping | call_function_expression | bracketed_expression | expression IN expression { result = val[0].in val[2] ; loc result, val[1] } | expression MATCH expression { result = val[0] =~ val[2] ; loc result, val[1] } | expression NOMATCH expression { result = val[0].mne val[2] ; loc result, val[1] } | expression PLUS expression { result = val[0] + val[2] ; loc result, val[1] } | expression MINUS expression { result = val[0] - val[2] ; loc result, val[1] } | expression DIV expression { result = val[0] / val[2] ; loc result, val[1] } | expression TIMES expression { result = val[0] * val[2] ; loc result, val[1] } | expression MODULO expression { result = val[0] % val[2] ; loc result, val[1] } | expression LSHIFT expression { result = val[0] << val[2] ; loc result, val[1] } | expression RSHIFT expression { result = val[0] >> val[2] ; loc result, val[1] } | MINUS expression =UMINUS { result = val[1].minus ; loc result, val[0] } | TIMES expression =SPLAT { result = val[1].unfold ; loc result, val[0] } | expression NOTEQUAL expression { result = val[0].ne val[2] ; loc result, val[1] } | expression ISEQUAL expression { result = val[0].eq val[2] ; loc result, val[1] } | expression GREATERTHAN expression { result = val[0] > val[2] ; loc result, val[1] } | expression GREATEREQUAL expression { result = val[0] >= val[2] ; loc result, val[1] } | expression LESSTHAN expression { result = val[0] < val[2] ; loc result, val[1] } | expression LESSEQUAL expression { result = val[0] <= val[2] ; loc result, val[1] } | NOT expression { result = val[1].not ; loc result, val[0] } | expression AND expression { result = val[0].and val[2] ; loc result, val[1] } | expression OR expression { result = val[0].or val[2] ; loc result, val[1] } | expression QMARK selector_entries { result = val[0].select(*val[2]) ; loc result, val[0] } | LPAREN assignment RPAREN { result = val[1].paren ; loc result, val[0] } | WSLPAREN assignment RPAREN { result = val[1].paren ; loc result, val[0] } bracketed_expression : expression LBRACK access_args endcomma RBRACK =LBRACK { result = val[0].access(val[2]); loc result, val[0], val[4] } access_args : access_arg { result = [val[0]] } | access_args COMMA access_arg { result = Factory.ARGUMENTS(val[0], val[2]) } access_arg : expression | hashpair { result = Factory.HASH_UNFOLDED([val[0]]); loc result, val[0] } #---EXPRESSIONS # (i.e. "argument list") # # This expression list can not contain function calls without parentheses around arguments # Produces Array<Model::Expression> # expressions : expression { result = [val[0]] } | expressions COMMA expression { result = val[0].push(val[2]) } primary_expression : variable | call_method_with_lambda_expression | collection_expression | case_expression | if_expression | unless_expression | definition_expression | application_expression | hostclass_expression | plan_expression | node_definition_expression | site_definition_expression | epp_render_expression | function_definition | type_alias | type_definition | reserved_word | array | hash | regex | quotedtext | type | NUMBER { result = Factory.NUMBER(val[0][:value]) ; loc result, val[0] } | BOOLEAN { result = Factory.literal(val[0][:value]) ; loc result, val[0] } | DEFAULT { result = Factory.literal(:default) ; loc result, val[0] } | UNDEF { result = Factory.literal(:undef) ; loc result, val[0] } | NAME { result = Factory.QNAME_OR_NUMBER(val[0][:value]) ; loc result, val[0] } #---CALL FUNCTION # # Produces Model::CallNamedFunction call_function_expression : call_function_start arguments endcomma RPAREN { result = Factory.CALL_NAMED(val[0], true, val[1]) loc result, val[0], val[3] } | call_function_start RPAREN { result = Factory.CALL_NAMED(val[0], true, []) loc result, val[0], val[1] } | call_function_start arguments endcomma RPAREN lambda { result = Factory.CALL_NAMED(val[0], true, val[1]) loc result, val[0], val[4] result.lambda = val[4] } | call_function_start RPAREN lambda { result = Factory.CALL_NAMED(val[0], true, []) loc result, val[0], val[2] result.lambda = val[2] } call_function_start : expression LPAREN { result = val[0] } | TYPE LPAREN { result = Factory.QNAME(val[0][:value]); loc result, val[0] } #---CALL METHOD # call_method_with_lambda_expression : call_method_expression =LOW { result = val[0] } | call_method_expression lambda { result = val[0]; val[0].lambda = val[1] } call_method_expression : named_access LPAREN arguments RPAREN { result = Factory.CALL_METHOD(val[0], val[2]); loc result, val[1], val[3] } | named_access LPAREN RPAREN { result = Factory.CALL_METHOD(val[0], []); loc result, val[1], val[3] } | named_access =LOW { result = Factory.CALL_METHOD(val[0], []); loc result, val[0] } named_access : expression DOT NAME { result = val[0].dot(Factory.fqn(val[2][:value])) loc result, val[1], val[2] } | expression DOT TYPE { result = val[0].dot(Factory.fqn(val[2][:value])) loc result, val[1], val[2] } #---LAMBDA # lambda : lambda_parameter_list opt_return_type lambda_rest { result = Factory.LAMBDA(val[0][:value], val[2][:value], val[1]) loc result, val[0][:start], val[2][:end] } lambda_rest : LBRACE statements RBRACE { result = {:end => val[2], :value =>val[1] } } | LBRACE RBRACE { result = {:end => val[1], :value => nil } } lambda_parameter_list : PIPE PIPE { result = {:start => val[0], :value => [] } } | PIPE parameters endcomma PIPE { result = {:start => val[0], :value => val[1] } } #---CONDITIONALS #--IF # if_expression : IF if_part { result = val[1] loc(result, val[0], val[1]) } # Produces Model::IfExpression if_part : expression LBRACE statements RBRACE else { result = Factory.IF(val[0], Factory.block_or_expression(val[2], val[1], val[3]), val[4]) loc(result, val[0], (val[4] ? val[4] : val[3])) } | expression LBRACE RBRACE else { result = Factory.IF(val[0], nil, val[3]) loc(result, val[0], (val[3] ? val[3] : val[2])) } # Produces [Model::Expression, nil] - nil if there is no else or elsif part else : # nothing | ELSIF if_part { result = val[1] loc(result, val[0], val[1]) } | ELSE LBRACE statements RBRACE { result = Factory.block_or_expression(val[2], val[1], val[3]) } | ELSE LBRACE RBRACE { result = nil # don't think a nop is needed here either } #--UNLESS # unless_expression : UNLESS expression LBRACE statements RBRACE unless_else { result = Factory.UNLESS(val[1], Factory.block_or_expression(val[3], val[2], val[4]), val[5]) loc result, val[0], val[4] } | UNLESS expression LBRACE RBRACE unless_else { result = Factory.UNLESS(val[1], nil, val[4]) loc result, val[0], val[4] } # Different from else part of if, since "elsif" is not supported, but 'else' is # # Produces [Model::Expression, nil] - nil if there is no else or elsif part unless_else : # nothing | ELSE LBRACE statements RBRACE { result = Factory.block_or_expression(val[2], val[1], val[3]) } | ELSE LBRACE RBRACE { result = nil # don't think a nop is needed here either } #--- CASE EXPRESSION # case_expression : CASE expression LBRACE case_options RBRACE { result = Factory.CASE(val[1], *val[3]) loc result, val[0], val[4] } # Produces Array<Model::CaseOption> case_options : case_option { result = [val[0]] } | case_options case_option { result = val[0].push val[1] } # Produced Model::CaseOption (aka When) case_option : expressions COLON LBRACE options_statements RBRACE { result = Factory.WHEN(val[0], val[3]); loc result, val[1], val[4] } options_statements : nil | statements # This special construct is required or racc will produce the wrong result when the selector entry # LHS is generalized to any expression (LBRACE looks like a hash). Thus it is not possible to write # a selector with a single entry where the entry LHS is a hash. # The SELBRACE token is a LBRACE that follows a QMARK, and this is produced by the lexer with a lookback # Produces Array<Model::SelectorEntry> # selector_entries : selector_entry | SELBRACE selector_entry_list endcomma RBRACE { result = val[1] } # Produces Array<Model::SelectorEntry> selector_entry_list : selector_entry { result = [val[0]] } | selector_entry_list COMMA selector_entry { result = val[0].push val[2] } # Produces a Model::SelectorEntry # This FARROW wins over FARROW in Hash selector_entry : expression FARROW expression { result = Factory.MAP(val[0], val[2]) ; loc result, val[1] } #---COLLECTION # # A Collection is a predicate applied to a set of objects with an implied context (used variables are # attributes of the object. # i.e. this is equivalent to source.select(QUERY).apply(ATTRIBUTE_OPERATIONS) # collection_expression : expression collect_query LBRACE attribute_operations endcomma RBRACE { result = Factory.COLLECT(val[0], val[1], val[3]) loc result, val[0], val[5] } | expression collect_query =LOW { result = Factory.COLLECT(val[0], val[1], []) loc result, val[0], val[1] } collect_query : LCOLLECT optional_query RCOLLECT { result = Factory.VIRTUAL_QUERY(val[1]) ; loc result, val[0], val[2] } | LLCOLLECT optional_query RRCOLLECT { result = Factory.EXPORTED_QUERY(val[1]) ; loc result, val[0], val[2] } optional_query : nil | expression #---ATTRIBUTE OPERATIONS (Not an expression) # attribute_operations : { result = [] } | attribute_operation { result = [val[0]] } | attribute_operations COMMA attribute_operation { result = val[0].push(val[2]) } # Produces String # QUESTION: Why is BOOLEAN valid as an attribute name? # attribute_name : NAME | keyword # In this version, illegal combinations are validated instead of producing syntax errors # (Can give nicer error message "+> is not applicable to...") # Produces Model::AttributeOperation # attribute_operation : attribute_name FARROW expression { result = Factory.ATTRIBUTE_OP(val[0][:value], '=>', val[2]) loc result, val[0], val[2] } | attribute_name PARROW expression { result = Factory.ATTRIBUTE_OP(val[0][:value], '+>', val[2]) loc result, val[0], val[2] } | TIMES FARROW expression { result = Factory.ATTRIBUTES_OP(val[2]) ; loc result, val[0], val[2] } #---DEFINE # # Produces Model::Definition # definition_expression : DEFINE classname parameter_list LBRACE opt_statements RBRACE { definition = Factory.DEFINITION(classname(val[1][:value]), val[2], val[4]) loc(definition, val[0], val[5]) result = add_definition(definition) # New lexer does not keep track of this, this is done in validation if @lexer.respond_to?(:'indefine=') @lexer.indefine = false end } #---APPLICATION application_expression : APPLICATION classname parameter_list LBRACE opt_statements RBRACE { definition = Factory.APPLICATION(classname(val[1][:value]), val[2], val[4]) loc(definition, val[0], val[5]) result = add_definition(definition) } capability_mapping : classname capability_kw classname LBRACE attribute_operations endcomma RBRACE { result = Factory.CAPABILITY_MAPPING(val[1][:value], Factory.QREF(classname(val[0][:value])), classname(val[2][:value]), val[4]) loc result, val[0], val[6] add_mapping(result) } | bracketed_expression capability_kw classname LBRACE attribute_operations endcomma RBRACE { result = Factory.CAPABILITY_MAPPING(val[1][:value], val[0], classname(val[2][:value]), val[4]) loc result, val[0], val[6] add_mapping(result) } capability_kw : PRODUCES | CONSUMES #--PLAN plan_expression : PLAN stacked_classname parameter_list LBRACE opt_statements RBRACE { # Remove this plan's name from the namestack as all nested plans have been parsed namepop definition = Factory.PLAN(classname(val[1][:value]), val[2], val[4]) loc(definition, val[0], val[5]) result = add_definition(definition) } #---HOSTCLASS # # Produces Model::HostClassDefinition # hostclass_expression : CLASS stacked_classname parameter_list classparent LBRACE opt_statements RBRACE { # Remove this class' name from the namestack as all nested classes have been parsed namepop definition = Factory.HOSTCLASS(classname(val[1][:value]), val[2], token_text(val[3]), val[5]) loc(definition, val[0], val[6]) result = add_definition(definition) } # Record the classname so nested classes gets a fully qualified name at parse-time # This is a separate rule since racc does not support intermediate actions. # stacked_classname : classname { namestack(val[0][:value]) ; result = val[0] } opt_statements : statements | nil # Produces String, name or nil result classparent : nil | INHERITS classnameordefault { result = val[1] } # Produces String (this construct allows a class to be named "default" and to be referenced as # the parent class. # TODO: Investigate the validity # Produces a String (classname), or a token (DEFAULT). # classnameordefault : classname | DEFAULT #---SITE # # Produces Model::SiteDefinition # site_definition_expression : SITE LBRACE statements RBRACE { definition = Factory.SITE(val[2]) loc(definition, val[0], val[3]) result = add_definition(definition) } | SITE LBRACE RBRACE { definition = Factory.SITE(nil) loc(definition, val[0], val[2]) result = add_definition(definition) } #---NODE # # Produces Model::NodeDefinition # node_definition_expression : NODE hostnames endcomma nodeparent LBRACE statements RBRACE { definition = Factory.NODE(val[1], val[3], val[5]) loc(definition, val[0], val[6]) result = add_definition(definition) } | NODE hostnames endcomma nodeparent LBRACE RBRACE { definition = Factory.NODE(val[1], val[3], nil) loc(definition, val[0], val[5]) result = add_definition(definition) } # Hostnames is not a list of names, it is a list of name matchers (including a Regexp). # (The old implementation had a special "Hostname" object with some minimal validation) # # Produces Array<Model::LiteralExpression> # hostnames : hostname { result = [result] } | hostnames COMMA hostname { result = val[0].push(val[2]) } # Produces a LiteralExpression (string, :default, or regexp) # String with interpolation is validated for better error message hostname : dotted_name | quotedtext | DEFAULT { result = Factory.literal(:default); loc result, val[0] } | regex dotted_name : name_or_number { result = Factory.literal(val[0][:value]); loc result, val[0] } | dotted_name DOT name_or_number { result = Factory.concat(val[0], '.', val[2][:value]); loc result, val[0], val[2] } name_or_number : NAME | NUMBER # Produces Expression, since hostname is an Expression nodeparent : nil | INHERITS hostname { result = val[1] } #---FUNCTION DEFINITION # function_definition : FUNCTION classname parameter_list opt_return_type LBRACE opt_statements RBRACE { definition = Factory.FUNCTION(val[1][:value], val[2], val[5], val[3]) loc(definition, val[0], val[6]) result = add_definition(definition) } opt_return_type : RSHIFT type { result = val[1] } | RSHIFT type LBRACK access_args RBRACK { result = val[1].access(val[3]) ; loc result, val[1], val[4] } | nil #---NAMES AND PARAMETERS COMMON TO SEVERAL RULES # Produces String # TODO: The error that "class" is not a valid classname is bad - classname rule is also used for other things classname : NAME | WORD | CLASSREF | CLASS { error val[0], "'class' keyword not allowed at this location" } | STRING { error val[0], "A quoted string is not valid as a name here" } # Produces Array<Model::Parameter> parameter_list : nil { result = [] } | LPAREN RPAREN { result = [] } | WSLPAREN RPAREN { result = [] } | LPAREN parameters endcomma RPAREN { result = val[1] } | WSLPAREN parameters endcomma RPAREN { result = val[1] } # Produces Array<Model::Parameter> parameters : parameter { result = [val[0]] } | parameters COMMA parameter { result = val[0].push(val[2]) } # Produces Model::Parameter parameter : untyped_parameter | typed_parameter untyped_parameter : regular_parameter | splat_parameter regular_parameter : VARIABLE EQUALS expression { result = Factory.PARAM(val[0][:value], val[2]) ; loc result, val[0] } | VARIABLE { result = Factory.PARAM(val[0][:value]); loc result, val[0] } splat_parameter : TIMES regular_parameter { result = val[1]; val[1].captures_rest } typed_parameter : parameter_type untyped_parameter { val[1].type_expr(val[0]) ; result = val[1] } parameter_type : type { result = val[0] } | type LBRACK access_args RBRACK { result = val[0].access(val[2]) ; loc result, val[0], val[3] } #--TYPE ALIAS type_alias : type_alias_lhs EQUALS type hash { definition = Factory.TYPE_ASSIGNMENT(val[0], Factory.KEY_ENTRY(val[2], val[3])) loc(definition, val[0], val[3]) result = add_definition(definition) } | type_alias_lhs EQUALS type LBRACK access_args endcomma RBRACK { definition = Factory.TYPE_ASSIGNMENT(val[0], val[2].access(val[4])) loc(definition, val[0], val[5]) result = add_definition(definition) } | type_alias_lhs EQUALS type { definition = Factory.TYPE_ASSIGNMENT(val[0], val[2]) loc(definition, val[0], val[2]) result = add_definition(definition) } | type_alias_lhs EQUALS hash { definition = Factory.TYPE_ASSIGNMENT(val[0], val[2]) loc(definition, val[0], val[2]) result = add_definition(definition) } | type_alias_lhs EQUALS array { definition = Factory.TYPE_ASSIGNMENT(val[0], val[2]) loc(definition, val[0], val[4]) result = add_definition(definition) } type_alias_lhs : TYPE parameter_type { result = val[1] } #--TYPE definition # TODO: Uses the optional_statements rule temporarily since the actual body awaits final spec on methods and attributes. type_definition : TYPE CLASSREF LBRACE optional_statements RBRACE { definition = Factory.TYPE_DEFINITION(val[1][:value], nil, val[3]) loc(definition, val[0], val[4]) result = add_definition(definition) } | TYPE CLASSREF INHERITS CLASSREF LBRACE optional_statements RBRACE { definition = Factory.TYPE_DEFINITION(val[1][:value], val[3][:value], val[5]) loc(definition, val[0], val[6]) result = add_definition(definition) } #--VARIABLE # variable : VARIABLE { fqn = Factory.fqn(val[0][:value]) loc(fqn, val[0]) fqn['offset'] += 1 fqn['length'] -= 1 result = fqn.var loc(result, val[0]) } #---RESERVED WORDS # reserved_word : PRIVATE { result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] } | ATTR { result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] } #---LITERALS (dynamic and static) # array : LISTSTART collection_entries endcomma RBRACK { result = Factory.LIST(val[1]); loc result, val[0], val[3] } | LISTSTART RBRACK { result = Factory.literal([]) ; loc result, val[0], val[1] } | LBRACK collection_entries endcomma RBRACK { result = Factory.LIST(val[1]); loc result, val[0], val[3] } | LBRACK RBRACK { result = Factory.literal([]) ; loc result, val[0], val[1] } hash : LBRACE hashpairs RBRACE { result = Factory.HASH(val[1]); loc result, val[0], val[2] } | LBRACE hashpairs COMMA RBRACE { result = Factory.HASH(val[1]); loc result, val[0], val[3] } | LBRACE RBRACE { result = Factory.literal({}) ; loc result, val[0], val[1] } hashpairs : hashpair { result = [val[0]] } | hashpairs COMMA hashpair { result = val[0].push val[2] } hashpair : hash_entry FARROW hash_entry { result = Factory.KEY_ENTRY(val[0], val[2]); loc result, val[1] } collection_entry: : argument | collection_entry_keyword { result = Factory.literal(val[0][:value]) ; loc result, val[0] } # Like collection_entry but will not allow nested hashpair hash_entry: : assignment | collection_entry_keyword { result = Factory.literal(val[0][:value]) ; loc result, val[0] } collection_entries : collection_entry { result = [val[0]] } | collection_entries COMMA collection_entry { result = Factory.ARGUMENTS(val[0], val[2]) } # Keywords valid as a collection value collection_entry_keyword : TYPE | FUNCTION | APPLICATION | CONSUMES | PRODUCES | SITE quotedtext : string | dq_string | heredoc string : STRING { result = Factory.literal(val[0][:value]) ; loc result, val[0] } | WORD { result = Factory.literal(val[0][:value]) ; loc result, val[0] } dq_string : dqpre dqrval { result = Factory.STRING(val[0], *val[1]) ; loc result, val[0], val[1][-1] } dqpre : DQPRE { result = Factory.literal(val[0][:value]); loc result, val[0] } dqpost : DQPOST { result = Factory.literal(val[0][:value]); loc result, val[0] } dqmid : DQMID { result = Factory.literal(val[0][:value]); loc result, val[0] } dqrval : text_expression dqtail { result = [val[0]] + val[1] } text_expression : assignment { result = Factory.TEXT(val[0]) } dqtail : dqpost { result = [val[0]] } | dqmid dqrval { result = [val[0]] + val[1] } heredoc : HEREDOC sublocated_text { result = Factory.HEREDOC(val[0][:value], val[1]); loc result, val[0] } sublocated_text : SUBLOCATE string { result = Factory.SUBLOCATE(val[0], val[1]); loc result, val[0] } | SUBLOCATE dq_string { result = Factory.SUBLOCATE(val[0], val[1]); loc result, val[0] } epp_expression : EPP_START epp_parameters_list optional_statements { result = Factory.EPP(val[1], val[2]); loc result, val[0] } optional_statements : | statements epp_parameters_list : =LOW{ result = nil } | PIPE PIPE { result = [] } | PIPE parameters endcomma PIPE { result = val[1] } epp_render_expression : RENDER_STRING { result = Factory.RENDER_STRING(val[0][:value]); loc result, val[0] } | RENDER_EXPR expression epp_end { result = Factory.RENDER_EXPR(val[1]); loc result, val[0], val[2] } | RENDER_EXPR LBRACE statements RBRACE epp_end { result = Factory.RENDER_EXPR(Factory.block_or_expression(val[2], val[1], val[3])); loc result, val[0], val[4] } epp_end : EPP_END | EPP_END_TRIM type : CLASSREF { result = Factory.QREF(val[0][:value]) ; loc result, val[0] } regex : REGEX { result = Factory.literal(val[0][:value]); loc result, val[0] } #---MARKERS, SPECIAL TOKENS, SYNTACTIC SUGAR, etc. endcomma : # | COMMA { result = nil } endsemi : # | SEMIC keyword : AND | CASE | CLASS | DEFAULT | DEFINE | ELSE | ELSIF | IF | IN | INHERITS | NODE | OR | UNDEF | UNLESS | TYPE | ATTR | FUNCTION | PRIVATE | APPLICATION | CONSUMES | PRODUCES | SITE nil : { result = nil} end ---- header ---- require 'puppet' require 'puppet/pops' module Puppet class ParseError < Puppet::Error; end class ImportError < Racc::ParseError; end class AlreadyImportedError < ImportError; end end ---- inner ---- # Make emacs happy # Local Variables: # mode: ruby # End: ����������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/parser/eparser.rb�����������������������������������������������������0000644�0052762�0001160�00000340316�13417161721�021347� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# # DO NOT MODIFY!!!! # This file is automatically generated by Racc 1.4.9 # from Racc grammer file "". # require 'racc/parser.rb' require 'puppet' require 'puppet/pops' module Puppet class ParseError < Puppet::Error; end class ImportError < Racc::ParseError; end class AlreadyImportedError < ImportError; end end module Puppet module Pops module Parser class Parser < Racc::Parser module_eval(<<'...end egrammar.ra/module_eval...', 'egrammar.ra', 928) # Make emacs happy # Local Variables: # mode: ruby # End: ...end egrammar.ra/module_eval... ##### State transition tables begin ### clist = [ '65,77,301,-147,63,71,399,72,71,301,72,-278,114,83,87,88,89,-287,348', '-292,289,400,304,302,21,20,116,-290,119,267,302,53,115,56,152,67,12', '86,61,45,48,94,55,46,10,11,-147,158,64,19,290,304,47,118,-278,17,18', '155,131,132,-287,349,-292,82,90,92,91,93,129,54,-290,268,126,44,78,95', '80,81,79,-187,-187,62,50,68,69,57,65,77,60,59,63,71,70,72,454,70,129', '129,114,453,126,126,326,122,128,327,470,-277,125,159,21,20,116,176,119', '471,127,53,115,56,152,67,12,168,61,45,48,84,55,46,10,11,128,128,64,19', '125,125,47,118,181,17,18,155,127,127,177,129,197,82,-277,126,129,129', '129,54,126,126,126,44,78,95,80,81,421,199,72,62,50,68,69,57,65,77,60', '59,63,71,70,72,519,202,129,128,86,453,126,125,128,128,128,269,125,125', '125,127,21,20,-187,-187,127,127,127,53,380,56,94,67,12,114,61,45,48', '301,55,46,10,11,128,94,64,19,125,116,47,119,304,17,18,115,127,301,129', '129,302,82,126,126,129,129,282,54,126,126,304,44,78,152,80,81,118,283', '302,62,50,68,69,57,65,77,60,59,63,71,70,72,294,-232,128,128,155,284', '125,125,128,128,131,132,125,125,127,127,21,20,-188,-188,127,127,129', '53,382,56,126,67,12,158,61,45,48,301,55,46,10,11,178,77,64,19,179,466', '47,465,304,17,18,178,77,301,168,179,302,82,-189,-189,128,129,287,54', '125,126,304,44,78,288,80,81,127,292,302,62,50,68,69,57,65,77,60,59,63', '71,70,72,338,-191,-191,176,114,466,173,465,317,128,280,279,318,125,280', '279,21,20,116,324,119,127,-233,53,115,56,82,67,134,324,61,45,48,86,55', '46,177,78,280,279,64,19,280,279,47,118,94,17,18,178,77,280,279,179,94', '82,329,328,129,340,341,54,126,94,94,44,78,346,80,81,168,354,372,62,50', '68,69,57,65,77,60,59,63,71,70,72,373,375,379,176,384,386,173,390,128', '392,304,395,125,396,301,448,21,20,409,410,127,411,412,53,413,56,82,67', '134,416,61,45,48,292,55,46,177,78,422,424,64,19,395,-232,47,429,431', '17,18,178,77,438,439,179,346,82,347,442,445,395,395,54,158,455,456,44', '78,459,80,81,460,463,467,62,50,68,69,57,65,77,60,59,63,71,70,72,469', '480,482,176,114,484,173,346,488,490,346,493,494,346,497,501,21,20,116', '469,119,503,505,53,115,56,82,67,134,506,61,45,48,507,55,46,177,78,346', '509,64,19,354,514,47,118,515,17,18,516,517,518,527,528,529,82,530,532', '533,534,375,54,,,,44,78,,80,81,,,,62,50,68,69,57,65,77,60,59,63,71,70', '72,,,,,114,,,,,,,,,,,,21,20,116,,119,,,53,115,56,,67,12,,61,45,48,,55', '46,10,11,,,64,19,,,47,118,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,', ',62,50,68,69,57,65,77,60,59,63,71,70,72,,,,,,,,,,,,,,,,,21,20,,,,,,53', ',56,,67,12,,61,45,48,,55,46,10,11,,,64,19,,,47,,,17,18,,,,,,,82,,,,', ',54,,,,44,78,,80,81,,,,62,50,68,69,57,65,77,60,59,63,71,70,72,,,,,,', ',,,,,,,,,,21,20,143,,,,,53,,56,,67,12,,61,45,48,,55,46,10,11,,,64,19', ',,47,,,17,18,,,,,178,77,82,,179,,,,54,,,,44,78,,80,81,,,,147,144,68', '69,145,151,150,146,59,65,77,70,,63,71,,72,,,,176,,,173,,,,,,,,,,21,20', ',,,,,53,,56,82,67,134,,61,45,48,,55,46,177,78,,,64,19,,,47,,,17,18,', ',,,,,82,,,,,,54,,,,44,78,,80,81,,,,62,50,68,69,57,65,77,60,59,63,71', '70,72,,,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,134,,61,45,48,,55,46,,,', ',64,19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,,,62,50,68,69', '57,65,77,60,59,63,71,70,72,,,,,,,,,,,,,,,,,21,20,,,,,114,53,,56,,67', '134,,61,45,48,,55,46,116,,119,,64,19,115,,47,,,17,18,,,,,,,82,,,,,,54', ',118,,44,78,,80,81,,,,62,50,68,69,57,96,97,60,59,65,77,70,95,63,71,183', '72,,,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,12,,61,45,48,,55,46,10,11,', ',64,19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,,,147,144,68,69', '145,151,150,146,59,65,77,70,,63,71,188,72,,,,,,,,,,,,,,,,,21,20,,,,', ',53,,56,,67,12,,61,45,48,,55,46,10,11,,,64,19,,,47,,,17,18,,,,,,,82', ',,,,,54,,,,44,78,,80,81,,,,147,144,68,69,145,151,150,146,59,65,77,70', ',63,71,,72,190,,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,12,,61,45,48,,55', '46,10,11,,,64,19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,,,147', '144,68,69,145,151,150,146,59,65,77,70,,63,71,,72,,,,,,,,,,,,,,,,,21', '20,,,,,,53,,56,,67,12,,61,45,48,,55,46,10,11,,,64,19,,,47,,,17,18,,', ',,,,82,,,,,,54,,,,44,78,,80,81,,,,62,50,68,69,57,65,77,60,59,63,71,70', '201,,,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,134,,61,45,48,,55,46,,,,,64', '19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,,,62,50,68,69,57,65', '77,60,59,63,71,70,72,,,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,12,,61,45', '48,,55,46,10,11,,,64,19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81', ',,,62,50,68,69,57,65,77,60,59,63,71,70,72,,,,,,,,,,,,,,,,,21,20,,,,', ',53,,56,,67,12,,61,45,48,,55,46,10,11,,,64,19,,,47,,,17,18,,,,,,,82', ',,,,,54,,,,44,78,,80,81,,,,62,50,68,69,57,65,77,60,59,63,71,70,72,,', ',,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,12,,61,45,48,,55,46,10,11,,,64,19', ',,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,,,62,50,68,69,57,65,77', '60,59,63,71,70,72,,,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,12,,61,45,48', ',55,46,10,11,,,64,19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,', ',,62,50,68,69,57,65,77,60,59,63,71,70,72,,,,,,,,,,,,,,,,,21,20,,,,,', '53,,56,,67,12,,61,45,48,,55,46,10,11,,,64,19,,,47,,,17,18,,,,,,,82,', ',,,,54,,,,44,78,,80,81,,,,62,50,68,69,57,65,77,60,59,63,71,70,72,,,', ',,,,,,,,,,,,,21,20,,,,,,53,,56,,67,12,,61,45,48,,55,46,10,11,,,64,19', ',,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,,,62,50,68,69,57,65,77', '60,59,63,71,70,72,,,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,12,,61,45,48', ',55,46,10,11,,,64,19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,', ',,62,50,68,69,57,65,77,60,59,63,71,70,72,,,,,,,,,,,,,,,,,21,20,,,,,', '53,,56,,67,12,,61,45,48,,55,46,10,11,,,64,19,,,47,,,17,18,,,,,,,82,', ',,,,54,,,,44,78,,80,81,,,,62,50,68,69,57,65,77,60,59,63,71,70,72,,,', ',,,,,,,,,,,,,21,20,,,,,,53,,56,,67,12,,61,45,48,,55,46,10,11,,,64,19', ',,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,,,62,50,68,69,57,65,77', '60,59,63,71,70,72,,,,,,,,,,,,,,,,,21,20,,,,,,219,234,225,235,67,227', '237,229,45,217,,221,215,,,,,64,19,238,233,216,,,17,214,,,,,,,82,,,,', '236,220,,,,44,78,,80,81,,,,230,218,231,232,226,240,239,228,59,65,77', '70,114,63,71,,72,,,,,,,,,,116,,119,,,,115,21,20,,,,,,53,,56,,67,134', ',61,45,48,,55,46,118,,,,64,19,,,47,,,17,18,,,96,97,,,82,,,95,,,54,,', ',44,78,,80,81,,,,62,50,68,69,57,65,77,60,59,63,71,70,72,,,,,,,,,,,,', ',,,,21,20,,,,,,53,,56,,67,134,,61,45,48,,55,46,,,,,64,19,,,47,,,17,18', ',,,,,,82,,,,,,54,,,,44,78,,80,81,,,,62,50,68,69,57,65,77,60,59,63,71', '70,72,,,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,134,,61,45,48,,55,46,,,', ',64,19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,,,62,50,68,69', '57,65,77,60,59,63,71,70,72,,,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,134', ',61,45,48,,55,46,,,,,64,19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80', '81,,,,62,50,68,69,57,65,77,60,59,63,71,70,72,,,,,,,,,,,,,,,,,21,20,', ',,,,53,,56,,67,134,,61,45,48,,55,46,,,,,64,19,,,47,,,17,18,,,,,,,82', ',,,,,54,,,,44,78,,80,81,,,,62,50,68,69,57,65,77,60,59,63,71,70,72,,', ',,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,134,,61,45,48,,55,46,,,,,64,19,', ',47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,,,62,50,68,69,57,65,77', '60,59,63,71,70,72,,,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,134,,61,45,48', ',55,46,,,,,64,19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,,,62', '50,68,69,57,65,77,60,59,63,71,70,72,,,,,,,,,,,,,,,,,21,20,,,,,,53,,56', ',67,134,,61,45,48,,55,46,,,,,64,19,,,47,,,17,18,,,,,,,82,,,,,,54,,,', '44,78,,80,81,,,,62,50,68,69,57,65,77,60,59,63,71,70,72,,,,,,,,,,,,,', ',,,21,20,,,,,,53,,56,,67,134,,61,45,48,,55,46,,,,,64,19,,,47,,,17,18', ',,,,,,82,,,,,,54,,,,44,78,,80,81,,,,62,50,68,69,57,65,77,60,59,63,71', '70,72,,,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,134,,61,45,48,,55,46,,,', ',64,19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,,,62,50,68,69', '57,65,77,60,59,63,71,70,72,,,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,134', ',61,45,48,,55,46,,,,,64,19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80', '81,,,,62,50,68,69,57,65,77,60,59,63,71,70,72,,,,,,,,,,,,,,,,,21,20,', ',,,,53,,56,,67,134,,61,45,48,,55,46,,,,,64,19,,,47,,,17,18,,,,,,,82', ',,,,,54,,,,44,78,,80,81,,,,62,50,68,69,57,65,77,60,59,63,71,70,72,,', ',,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,134,,61,45,48,,55,46,,,,,64,19,', ',47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,,,62,50,68,69,57,65,77', '60,59,63,71,70,72,,,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,134,,61,45,48', ',55,46,,,,,64,19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,,,62', '50,68,69,57,65,77,60,59,63,71,70,72,,,,,,,,,,,,,,,,,21,20,,,,,,53,,56', ',67,134,,61,45,48,,55,46,,,,,64,19,,,47,,,17,18,,,,,,,82,,,,,,54,,,', '44,78,,80,81,,,,62,50,68,69,57,65,77,60,59,63,71,70,72,,,,,,,,,,,,,', ',,,21,20,,,,,,53,,56,,67,134,,61,45,48,,55,46,,,,,64,19,,,47,,,17,18', ',,,,,,82,,,,,,54,,,,44,78,,80,81,,,,62,50,68,69,57,65,77,60,59,63,71', '70,72,,,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,134,,61,45,48,,55,46,,,', ',64,19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,,,62,50,68,69', '57,65,77,60,59,63,71,70,72,,,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,134', ',61,45,48,,55,46,,,,,64,19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80', '81,,,,62,50,68,69,57,65,77,60,59,63,71,70,72,,,,,,,,,,,,,,,,,21,20,', ',,,,53,,56,,67,134,,61,45,48,,55,46,,,,,64,19,,,47,,,17,18,,,,,,,82', ',,,,,54,,,262,44,78,,80,81,,,,62,50,68,69,57,65,77,60,59,63,71,70,72', ',,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,12,,61,45,48,,55,46,10,11,,,64', '19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,,,147,144,68,69,145', '151,150,146,59,65,77,70,114,63,71,,72,,,,,,,,,,116,,119,,,,115,21,20', ',,,,,53,,56,,67,134,,61,45,48,,55,46,118,,,,64,19,,,47,,,17,18,,,96', '97,,,82,,,95,,,54,,,,44,78,,80,81,,,,62,50,68,69,57,65,77,60,59,63,71', '70,72,,,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,134,,61,45,48,,55,46,,,', ',64,19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,,,62,50,68,69', '57,65,77,60,59,63,71,70,72,,,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,134', ',61,45,48,,55,46,,,,,64,19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80', '81,,,,62,50,68,69,57,65,77,60,59,63,71,70,72,,,,,,,,,,,,,,,,,21,20,306', ',,,,53,,56,,67,12,,61,45,48,,55,46,10,11,,,64,19,,,47,,,17,18,,,,,,', '82,,,,,,54,,,,44,78,,80,81,,,,147,144,68,69,145,151,150,146,59,65,77', '70,,63,71,,72,315,,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,12,,61,45,48', ',55,46,10,11,,,64,19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,', ',,62,50,68,69,57,65,77,60,59,63,71,70,72,,,,,,,,,,,,,,,,,21,20,,,,,', '53,,56,,67,12,,61,45,48,,55,46,10,11,,,64,19,,,47,,,17,18,,,,,,,82,', ',,,,54,,,,44,78,,80,81,,,,62,50,68,69,57,65,77,60,59,63,71,70,72,190', ',,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,12,,61,45,48,,55,46,10,11,,,64', '19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,,,147,144,68,69,145', '151,150,146,59,65,77,70,,63,71,,72,,,347,,,,,,,,,,,,,,21,20,,,,,,53', ',56,,67,134,,61,45,48,,55,46,,,,,64,19,,,47,,,17,18,,,,,,,82,,,,,,54', ',,,44,78,,80,81,,,,62,50,68,69,57,65,77,60,59,63,71,70,72,,,,,,,,,,', ',,,,,,21,20,,,,,,53,,56,,67,134,,61,45,48,,55,46,,,,,64,19,,,47,,,17', '18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,,,62,50,68,69,57,65,77,60,59,63', '71,70,72,,,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,134,,61,45,48,,55,46', ',,,,64,19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,,,62,50,68', '69,57,65,77,60,59,63,71,70,72,,,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67', '134,,61,45,48,,55,46,,,,,64,19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78', ',80,81,,,,62,50,68,69,57,65,77,60,59,63,71,70,72,,,,,,,,,,,,,,,,,21', '20,,,,,,53,,56,,67,134,,61,45,48,,55,46,,,,,64,19,,,47,,,17,18,,,,,', ',82,,,,,,54,,,,44,78,,80,81,,,,62,50,68,69,57,65,77,60,59,63,71,70,72', ',,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,12,,61,45,48,,55,46,10,11,,,64', '19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,,,147,144,68,69,145', '151,150,146,59,65,77,70,,63,71,,72,,,,,,,,,,,,,,,,,21,20,,,,,,53,,56', ',67,12,,61,45,48,,55,46,10,11,,,64,19,,,47,,,17,18,,,,,,,82,,,,,,54', ',,,44,78,,80,81,,,,147,144,68,69,145,151,150,146,59,65,77,70,,63,71', ',72,,,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,12,,61,45,48,,55,46,10,11', ',,64,19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,,,147,144,68', '69,145,151,150,146,59,65,77,70,,63,71,,72,,,,,,,,,,,,,,,,,21,20,,,,', ',53,,56,,67,12,,61,45,48,,55,46,10,11,,,64,19,,,47,,,17,18,,,,,,,82', ',,,,,54,,,,44,78,,80,81,,,,62,50,68,69,57,65,77,60,59,63,71,70,72,402', ',,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,12,,61,45,48,,55,46,10,11,,,64', '19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,,,62,50,68,69,57,65', '77,60,59,63,71,70,72,404,,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,12,,61', '45,48,,55,46,10,11,,,64,19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80', '81,,,,62,50,68,69,57,65,77,60,59,63,71,70,72,,,,,,,,,,,,,,,,,21,20,', ',,,,53,,56,,67,134,,61,45,48,,55,46,,,,,64,19,,,47,,,17,18,,,,,,,82', ',,,,,54,,,,44,78,,80,81,,,,62,50,68,69,57,65,77,60,59,63,71,70,72,,', ',,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,12,,61,45,48,,55,46,10,11,,,64,19', ',,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,,,147,144,68,69,145,151', '150,146,59,65,77,70,,63,71,,72,425,,,,,,,,,,,,,,,,21,20,,,,,,53,,56', ',67,12,,61,45,48,,55,46,10,11,,,64,19,,,47,,,17,18,,,,,,,82,,,,,,54', ',,,44,78,,80,81,,,,147,144,68,69,145,151,150,146,59,65,77,70,,63,71', ',72,,,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,12,,61,45,48,,55,46,10,11', ',,64,19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,,,62,50,68,69', '57,65,77,60,59,63,71,70,72,,,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,134', ',61,45,48,,55,46,,,,,64,19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80', '81,,,,62,50,68,69,57,65,77,60,59,63,71,70,72,,,,,,,,,,,,,,,,,21,20,', ',,,,53,,56,,67,134,,61,45,48,,55,46,,,,,64,19,,,47,,,17,18,,,,,,,82', ',,,,,54,,,,44,78,,80,81,,,,62,50,68,69,57,65,77,60,59,63,71,70,72,,', ',,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,134,,61,45,48,,55,46,,,,,64,19,', ',47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,,,62,50,68,69,57,65,77', '60,59,63,71,70,72,,,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,134,,61,45,48', ',55,46,,,,,64,19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,,,62', '50,68,69,57,65,77,60,59,63,71,70,72,,,,,,,,,,,,,,,,,21,20,,,,,,53,,56', ',67,12,,61,45,48,,55,46,10,11,,,64,19,,,47,,,17,18,,,,,,,82,,,,,,54', ',,,44,78,,80,81,,,,147,144,68,69,145,151,150,146,59,65,77,70,,63,71', ',72,,,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,134,,61,45,48,,55,46,,,,,64', '19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,,,62,50,68,69,57,65', '77,60,59,63,71,70,72,458,,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,12,,61', '45,48,,55,46,10,11,,,64,19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80', '81,,,,62,50,68,69,57,65,77,60,59,63,71,70,72,,,,,,,,,,,,,,,,,21,20,', ',,,,53,,56,,67,134,,61,45,48,,55,46,,,,,64,19,,,47,,,17,18,,,,,,,82', ',,,,,54,,,,44,78,,80,81,,,,62,50,68,69,57,65,77,60,59,63,71,70,72,,', ',,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,12,,61,45,48,,55,46,10,11,,,64,19', ',,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,,,147,144,68,69,145,151', '150,146,59,65,77,70,,63,71,,72,472,,,,,,,,,,,,,,,,21,20,,,,,,53,,56', ',67,134,,61,45,48,,55,46,,,,,64,19,,,47,,,17,18,,,,,,,82,,,,,,54,,,', '44,78,,80,81,,,,62,50,68,69,57,65,77,60,59,63,71,70,72,,,,,,,,,,,,,', ',,,21,20,,,,,,53,,56,,67,12,,61,45,48,,55,46,10,11,,,64,19,,,47,,,17', '18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,,,62,50,68,69,57,65,77,60,59,63', '71,70,72,,,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,12,,61,45,48,,55,46,10', '11,,,64,19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,,,62,50,68', '69,57,65,77,60,59,63,71,70,72,,,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67', '12,,61,45,48,,55,46,10,11,,,64,19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44', '78,,80,81,,,,62,50,68,69,57,65,77,60,59,63,71,70,72,,,,,,,,,,,,,,,,', '21,20,,,,,,53,,56,,67,12,,61,45,48,,55,46,10,11,,,64,19,,,47,,,17,18', ',,,,,,82,,,,,,54,,,,44,78,,80,81,,,,147,144,68,69,145,151,150,146,59', '65,77,70,,63,71,,72,,,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,134,,61,45', '48,,55,46,,,,,64,19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,', ',62,50,68,69,57,65,77,60,59,63,71,70,72,,,,,,,,,,,,,,,,,21,20,,,,,,53', ',56,,67,12,,61,45,48,,55,46,10,11,,,64,19,,,47,,,17,18,,,,,,,82,,,,', ',54,,,,44,78,,80,81,,,,62,50,68,69,57,65,77,60,59,63,71,70,72,,,,,,', ',,,,,,,,,,21,20,,,,,,53,,56,,67,12,,61,45,48,,55,46,10,11,,,64,19,,', '47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,,,147,144,68,69,145,151', '150,146,59,65,77,70,,63,71,,72,,,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67', '12,,61,45,48,,55,46,10,11,,,64,19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44', '78,,80,81,,,,62,50,68,69,57,65,77,60,59,63,71,70,72,,,,,,,,,,,,,,,,', '21,20,,,,,,53,,56,,67,12,,61,45,48,,55,46,10,11,,,64,19,,,47,,,17,18', ',,,,,,82,,,,,,54,,,,44,78,,80,81,,,,147,144,68,69,145,151,150,146,59', '65,77,70,,63,71,,72,,,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,134,,61,45', '48,,55,46,,,,,64,19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,', ',62,50,68,69,57,65,77,60,59,63,71,70,72,,,,,,,,,,,,,,,,,21,20,,,,,,53', ',56,,67,134,,61,45,48,,55,46,,,,,64,19,,,47,,,17,18,,,,,,,82,,,,,,54', ',,,44,78,,80,81,,,,62,50,68,69,57,65,77,60,59,63,71,70,72,511,,,,,,', ',,,,,,,,,21,20,,,,,,53,,56,,67,12,,61,45,48,,55,46,10,11,,,64,19,,,47', ',,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,,,62,50,68,69,57,65,77,60', '59,63,71,70,72,,,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,12,,61,45,48,,55', '46,10,11,,,64,19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,,,62', '50,68,69,57,65,77,60,59,63,71,70,72,521,,,,,,,,,,,,,,,,21,20,,,,,,53', ',56,,67,12,,61,45,48,,55,46,10,11,,,64,19,,,47,,,17,18,,,,,,,82,,,,', ',54,,,,44,78,,80,81,,,,62,50,68,69,57,65,77,60,59,63,71,70,72,523,,', ',,,,,,,,,,,,,21,20,,,,,,53,,56,,67,12,,61,45,48,,55,46,10,11,,,64,19', ',,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,,,,62,50,68,69,57,65,77', '60,59,63,71,70,72,,,,,,,,,,,,,,,,,21,20,,,,,,53,,56,,67,12,,61,45,48', ',55,46,10,11,,,64,19,,,47,,,17,18,,,,,,,82,,,,,,54,,,,44,78,,80,81,', ',,62,50,68,69,57,65,77,60,59,63,71,70,72,,,,,,,,,,,,,,,,,21,20,,,,,', '53,,56,,67,134,,61,45,48,,55,46,,,,,64,19,,,47,,,17,18,,,,,,,82,,,,', ',54,,,,44,78,,80,81,,,,62,50,68,69,57,114,,60,59,,,70,,,,,,110,105,116', ',119,,113,,115,,106,108,107,109,,,,,,,,,,,,,,,,118,,,,112,111,,,98,99', '101,100,103,104,,96,97,114,,307,,,95,,,,,,,110,105,116,,119,,113,,115', ',106,108,107,109,,,,,,102,,,,,,,,,,118,,,,112,111,,,98,99,101,100,103', '104,,96,97,114,,308,,,95,,,,,,,110,105,116,,119,,113,,115,,106,108,107', '109,,,,,,102,,,,,,,,,,118,,,,112,111,,,98,99,101,100,103,104,,96,97', '114,,309,,,95,,,,,,,110,105,116,,119,,113,,115,,106,108,107,109,,,,', ',102,,,,,,,,,,118,,,,112,111,,,98,99,101,100,103,104,114,96,97,,,,,', '95,,,,110,105,116,,119,,113,,115,,106,108,107,109,,,,,,,,,102,,,,,,', '118,,,114,112,111,,,98,99,101,100,103,104,,96,97,116,,119,,,95,115,', ',,114,,,,,,340,341,,,,,110,105,116,344,119,118,113,,115,102,106,108', '107,109,,101,100,,,,96,97,,,,,,95,,118,,,114,112,111,,,98,99,101,100', '103,104,,96,97,116,,119,114,,95,115,102,,,,,,,,,,116,,119,114,,,115', ',,,118,,,,102,,,116,,119,101,100,,115,,96,97,118,,,,114,95,,,98,99,101', '100,,,,96,97,118,116,,119,114,95,,115,98,99,101,100,,,102,96,97,,116', ',119,,95,,115,,,,118,,,102,,,,,98,99,101,100,103,104,,96,97,118,,,102', '114,95,,,98,99,101,100,103,104,,96,97,105,116,,119,114,95,,115,,106', ',,,,102,,,105,116,,119,,,,115,,106,,118,,,102,,,,,98,99,101,100,103', '104,,96,97,118,,,,114,95,,,98,99,101,100,103,104,,96,97,105,116,,119', '114,95,,115,,106,,,,,102,,,105,116,,119,,,,115,,106,,118,,,102,,,,,98', '99,101,100,103,104,,96,97,118,,,,,95,,,98,99,101,100,103,104,114,96', '97,,,,,,95,,,,110,105,116,,119,102,113,,115,,106,108,107,109,,,,,,,', ',102,,,,,,,118,,,,,114,,,98,99,101,100,103,104,,96,97,110,105,116,,119', '95,113,,115,,106,108,107,109,,,,,,,,,,,,,,,,118,,102,,,111,,,98,99,101', '100,103,104,114,96,97,,,350,,,95,,,,110,105,116,,119,,113,,115,,106', '108,107,109,,,,,,,,,102,,,,,,,118,,,,112,111,,,98,99,101,100,103,104', ',96,97,114,-66,,,,95,-66,,,,,,110,105,116,,119,,113,,115,,106,108,107', '109,,,,,,102,,,,,,,,,,118,,,,112,111,,,98,99,101,100,103,104,114,96', '97,,,,,,95,,,,110,105,116,,119,,113,,115,,106,108,107,109,,,,,,,,,102', ',,,,,,118,,,,112,111,,,98,99,101,100,103,104,114,96,97,,,,,,95,,,,110', '105,116,376,119,,113,,115,,106,108,107,109,,,,,,,,,102,,,,,,,118,,,', '112,111,,,98,99,101,100,103,104,114,96,97,,,,,,95,,,,110,105,116,,119', ',113,,115,,106,108,107,109,,,,,,,,,102,,,,,,,118,,,,112,111,,,98,99', '101,100,103,104,114,96,97,,,,,,95,,,,110,105,116,,119,,113,,115,,106', '108,107,109,,,,,,,,,102,,,,,,,118,,,,112,111,,,98,99,101,100,103,104', '114,96,97,,,,,,95,,,,110,105,116,,119,,113,,115,,106,108,107,109,,,', ',,,,,102,,,,,,,118,,,,112,111,,,98,99,101,100,103,104,114,96,97,,,,', ',95,,,,110,105,116,,119,,113,,115,,106,108,107,109,,,,,,,,,102,,,,,', ',118,,,,112,111,,,98,99,101,100,103,104,114,96,97,,,,,,95,,,,110,105', '116,,119,,113,,115,,106,108,107,109,,,,,,,,,102,,,,,,,118,,,,112,111', ',,98,99,101,100,103,104,114,96,97,,,,,,95,,,,110,105,116,,119,,113,', '115,,106,108,107,109,,,,,,,,,102,,,,,,,118,,,,112,111,,,98,99,101,100', '103,104,114,96,97,,,,,,95,,,,110,105,116,,119,,113,,115,,106,108,107', '109,,,,,,,,,102,,,,,,,118,,,,112,111,,,98,99,101,100,103,104,,96,97', ',362,234,361,235,95,359,237,363,,356,,358,360,,,,,,,238,233,364,,,,357', ',,,,102,,,,,,,236,365,,,,,,,,,,,,368,366,369,367,370,240,239,371,362', '234,361,235,,359,237,363,,356,,358,360,,,,,,,238,233,364,,,,357,,,,', ',,,,,,,236,365,,,,,,,,,,,,368,366,369,367,370,240,239,371,362,234,361', '235,,359,237,363,,356,,358,360,,,,,,,238,233,364,,,,357,,,,,,,,,,,,236', '365,,,,,,,,,,,,368,366,369,367,370,240,239,371,362,234,361,235,,359', '237,363,,356,,358,360,,,,,,,238,233,364,,,,357,,,,,,,,,,,,236,365,,', ',,,,,,,,,368,366,369,367,370,240,239,371,362,234,361,235,,359,237,363', ',356,,358,360,,,,,,,238,233,364,,,,357,,,,,,,,,,,,236,365,,,,,,,,,,', ',368,366,369,367,370,240,239,371,362,234,361,235,,359,237,363,,356,', '358,360,,,,,,,238,233,364,,,,357,,,,,,,,,,,,236,365,,,,,,,,,,,,368,366', '369,367,370,240,239,371' ] racc_action_table = arr = ::Array.new(10078, nil) idx = 0 clist.each do |str| str.split(',', -1).each do |i| arr[idx] = i.to_i unless i.empty? idx += 1 end end clist = [ '0,0,303,217,0,0,305,0,181,395,181,215,242,1,7,7,7,216,224,231,155,305', '395,303,0,0,242,232,242,116,395,0,242,0,50,0,0,5,0,0,0,8,0,0,0,0,217', '51,0,0,155,181,0,242,215,0,0,50,16,16,216,224,231,0,7,7,7,7,56,0,232', '116,56,0,0,242,0,0,0,217,217,0,0,0,0,0,4,4,0,0,4,4,0,4,388,181,12,227', '243,388,12,227,189,12,56,189,406,227,56,52,4,4,243,318,243,406,56,4', '243,4,144,4,4,60,4,4,4,4,4,4,4,4,12,227,4,4,12,227,4,243,66,4,4,144', '12,227,318,57,78,4,227,57,59,62,130,4,59,62,130,4,4,243,4,4,320,79,320', '4,4,4,4,4,10,10,4,4,10,10,4,10,498,83,134,57,85,498,134,57,59,62,130', '117,59,62,130,57,10,10,48,48,59,62,130,10,279,10,120,10,10,133,10,10', '10,279,10,10,10,10,134,121,10,10,134,133,10,133,279,10,10,133,134,158', '145,147,279,10,145,147,166,225,137,10,166,225,158,10,10,218,10,10,133', '138,158,10,10,10,10,10,11,11,10,10,11,11,10,11,158,139,145,147,218,142', '145,147,166,225,58,58,166,225,145,147,11,11,63,63,166,225,226,11,280', '11,226,11,11,143,11,11,11,280,11,11,11,11,197,197,11,11,197,402,11,402', '280,11,11,61,61,199,146,61,280,11,64,64,226,230,148,11,226,230,199,11', '11,153,11,11,226,157,199,11,11,11,11,11,17,17,11,11,17,17,11,17,199', '65,65,61,135,463,61,463,169,230,123,123,171,230,164,164,17,17,135,182', '135,230,185,17,135,17,61,17,17,187,17,17,17,203,17,17,61,61,165,165', '17,17,167,167,17,135,208,17,17,229,229,180,180,229,209,17,194,194,379', '429,429,17,379,210,211,17,17,213,17,17,228,264,271,17,17,17,17,17,18', '18,17,17,18,18,17,18,273,274,277,229,281,285,229,290,379,291,292,295', '379,301,302,379,18,18,310,311,379,312,313,18,314,18,229,18,18,316,18', '18,18,319,18,18,229,229,323,325,18,18,337,342,18,343,345,18,18,317,317', '351,353,317,355,18,357,374,377,381,383,18,386,389,390,18,18,393,18,18', '394,401,403,18,18,18,18,18,19,19,18,18,19,19,18,19,404,414,419,317,136', '428,317,430,437,441,444,449,450,451,457,466,19,19,136,467,136,469,471', '19,136,19,317,19,19,474,19,19,19,477,19,19,317,317,478,479,19,19,483', '486,19,136,487,19,19,492,495,496,508,510,512,19,513,520,522,524,531', '19,,,,19,19,,19,19,,,,19,19,19,19,19,20,20,19,19,20,20,19,20,,,,,241', ',,,,,,,,,,,20,20,241,,241,,,20,241,20,,20,20,,20,20,20,,20,20,20,20', ',,20,20,,,20,241,,20,20,,,,,,,20,,,,,,20,,,,20,20,,20,20,,,,20,20,20', '20,20,21,21,20,20,21,21,20,21,,,,,,,,,,,,,,,,,21,21,,,,,,21,,21,,21', '21,,21,21,21,,21,21,21,21,,,21,21,,,21,,,21,21,,,,,,,21,,,,,,21,,,,21', '21,,21,21,,,,21,21,21,21,21,49,49,21,21,49,49,21,49,,,,,,,,,,,,,,,,', '49,49,49,,,,,49,,49,,49,49,,49,49,49,,49,49,49,49,,,49,49,,,49,,,49', '49,,,,,416,416,49,,416,,,,49,,,,49,49,,49,49,,,,49,49,49,49,49,49,49', '49,49,53,53,49,,53,53,,53,,,,416,,,416,,,,,,,,,,53,53,,,,,,53,,53,416', '53,53,,53,53,53,,53,53,416,416,,,53,53,,,53,,,53,53,,,,,,,53,,,,,,53', ',,,53,53,,53,53,,,,53,53,53,53,53,54,54,53,53,54,54,53,54,,,,,,,,,,', ',,,,,,54,54,,,,,,54,,54,,54,54,,54,54,54,,54,54,,,,,54,54,,,54,,,54', '54,,,,,,,54,,,,,,54,,,,54,54,,54,54,,,,54,54,54,54,54,55,55,54,54,55', '55,54,55,,,,,,,,,,,,,,,,,55,55,,,,,246,55,,55,,55,55,,55,55,55,,55,55', '246,,246,,55,55,246,,55,,,55,55,,,,,,,55,,,,,,55,,246,,55,55,,55,55', ',,,55,55,55,55,55,246,246,55,55,70,70,55,246,70,70,70,70,,,,,,,,,,,', ',,,,,70,70,,,,,,70,,70,,70,70,,70,70,70,,70,70,70,70,,,70,70,,,70,,', '70,70,,,,,,,70,,,,,,70,,,,70,70,,70,70,,,,70,70,70,70,70,70,70,70,70', '71,71,70,,71,71,71,71,,,,,,,,,,,,,,,,,71,71,,,,,,71,,71,,71,71,,71,71', '71,,71,71,71,71,,,71,71,,,71,,,71,71,,,,,,,71,,,,,,71,,,,71,71,,71,71', ',,,71,71,71,71,71,71,71,71,71,72,72,71,,72,72,,72,72,,,,,,,,,,,,,,,', '72,72,,,,,,72,,72,,72,72,,72,72,72,,72,72,72,72,,,72,72,,,72,,,72,72', ',,,,,,72,,,,,,72,,,,72,72,,72,72,,,,72,72,72,72,72,72,72,72,72,76,76', '72,,76,76,,76,,,,,,,,,,,,,,,,,76,76,,,,,,76,,76,,76,76,,76,76,76,,76', '76,76,76,,,76,76,,,76,,,76,76,,,,,,,76,,,,,,76,,,,76,76,,76,76,,,,76', '76,76,76,76,81,81,76,76,81,81,76,81,,,,,,,,,,,,,,,,,81,81,,,,,,81,,81', ',81,81,,81,81,81,,81,81,,,,,81,81,,,81,,,81,81,,,,,,,81,,,,,,81,,,,81', '81,,81,81,,,,81,81,81,81,81,84,84,81,81,84,84,81,84,,,,,,,,,,,,,,,,', '84,84,,,,,,84,,84,,84,84,,84,84,84,,84,84,84,84,,,84,84,,,84,,,84,84', ',,,,,,84,,,,,,84,,,,84,84,,84,84,,,,84,84,84,84,84,86,86,84,84,86,86', '84,86,,,,,,,,,,,,,,,,,86,86,,,,,,86,,86,,86,86,,86,86,86,,86,86,86,86', ',,86,86,,,86,,,86,86,,,,,,,86,,,,,,86,,,,86,86,,86,86,,,,86,86,86,86', '86,87,87,86,86,87,87,86,87,,,,,,,,,,,,,,,,,87,87,,,,,,87,,87,,87,87', ',87,87,87,,87,87,87,87,,,87,87,,,87,,,87,87,,,,,,,87,,,,,,87,,,,87,87', ',87,87,,,,87,87,87,87,87,88,88,87,87,88,88,87,88,,,,,,,,,,,,,,,,,88', '88,,,,,,88,,88,,88,88,,88,88,88,,88,88,88,88,,,88,88,,,88,,,88,88,,', ',,,,88,,,,,,88,,,,88,88,,88,88,,,,88,88,88,88,88,89,89,88,88,89,89,88', '89,,,,,,,,,,,,,,,,,89,89,,,,,,89,,89,,89,89,,89,89,89,,89,89,89,89,', ',89,89,,,89,,,89,89,,,,,,,89,,,,,,89,,,,89,89,,89,89,,,,89,89,89,89', '89,90,90,89,89,90,90,89,90,,,,,,,,,,,,,,,,,90,90,,,,,,90,,90,,90,90', ',90,90,90,,90,90,90,90,,,90,90,,,90,,,90,90,,,,,,,90,,,,,,90,,,,90,90', ',90,90,,,,90,90,90,90,90,91,91,90,90,91,91,90,91,,,,,,,,,,,,,,,,,91', '91,,,,,,91,,91,,91,91,,91,91,91,,91,91,91,91,,,91,91,,,91,,,91,91,,', ',,,,91,,,,,,91,,,,91,91,,91,91,,,,91,91,91,91,91,92,92,91,91,92,92,91', '92,,,,,,,,,,,,,,,,,92,92,,,,,,92,,92,,92,92,,92,92,92,,92,92,92,92,', ',92,92,,,92,,,92,92,,,,,,,92,,,,,,92,,,,92,92,,92,92,,,,92,92,92,92', '92,93,93,92,92,93,93,92,93,,,,,,,,,,,,,,,,,93,93,,,,,,93,,93,,93,93', ',93,93,93,,93,93,93,93,,,93,93,,,93,,,93,93,,,,,,,93,,,,,,93,,,,93,93', ',93,93,,,,93,93,93,93,93,94,94,93,93,94,94,93,94,,,,,,,,,,,,,,,,,94', '94,,,,,,94,94,94,94,94,94,94,94,94,94,,94,94,,,,,94,94,94,94,94,,,94', '94,,,,,,,94,,,,,94,94,,,,94,94,,94,94,,,,94,94,94,94,94,94,94,94,94', '95,95,94,247,95,95,,95,,,,,,,,,,247,,247,,,,247,95,95,,,,,,95,,95,,95', '95,,95,95,95,,95,95,247,,,,95,95,,,95,,,95,95,,,247,247,,,95,,,247,', ',95,,,,95,95,,95,95,,,,95,95,95,95,95,96,96,95,95,96,96,95,96,,,,,,', ',,,,,,,,,,96,96,,,,,,96,,96,,96,96,,96,96,96,,96,96,,,,,96,96,,,96,', ',96,96,,,,,,,96,,,,,,96,,,,96,96,,96,96,,,,96,96,96,96,96,97,97,96,96', '97,97,96,97,,,,,,,,,,,,,,,,,97,97,,,,,,97,,97,,97,97,,97,97,97,,97,97', ',,,,97,97,,,97,,,97,97,,,,,,,97,,,,,,97,,,,97,97,,97,97,,,,97,97,97', '97,97,98,98,97,97,98,98,97,98,,,,,,,,,,,,,,,,,98,98,,,,,,98,,98,,98', '98,,98,98,98,,98,98,,,,,98,98,,,98,,,98,98,,,,,,,98,,,,,,98,,,,98,98', ',98,98,,,,98,98,98,98,98,99,99,98,98,99,99,98,99,,,,,,,,,,,,,,,,,99', '99,,,,,,99,,99,,99,99,,99,99,99,,99,99,,,,,99,99,,,99,,,99,99,,,,,,', '99,,,,,,99,,,,99,99,,99,99,,,,99,99,99,99,99,100,100,99,99,100,100,99', '100,,,,,,,,,,,,,,,,,100,100,,,,,,100,,100,,100,100,,100,100,100,,100', '100,,,,,100,100,,,100,,,100,100,,,,,,,100,,,,,,100,,,,100,100,,100,100', ',,,100,100,100,100,100,101,101,100,100,101,101,100,101,,,,,,,,,,,,,', ',,,101,101,,,,,,101,,101,,101,101,,101,101,101,,101,101,,,,,101,101', ',,101,,,101,101,,,,,,,101,,,,,,101,,,,101,101,,101,101,,,,101,101,101', '101,101,102,102,101,101,102,102,101,102,,,,,,,,,,,,,,,,,102,102,,,,', ',102,,102,,102,102,,102,102,102,,102,102,,,,,102,102,,,102,,,102,102', ',,,,,,102,,,,,,102,,,,102,102,,102,102,,,,102,102,102,102,102,103,103', '102,102,103,103,102,103,,,,,,,,,,,,,,,,,103,103,,,,,,103,,103,,103,103', ',103,103,103,,103,103,,,,,103,103,,,103,,,103,103,,,,,,,103,,,,,,103', ',,,103,103,,103,103,,,,103,103,103,103,103,104,104,103,103,104,104,103', '104,,,,,,,,,,,,,,,,,104,104,,,,,,104,,104,,104,104,,104,104,104,,104', '104,,,,,104,104,,,104,,,104,104,,,,,,,104,,,,,,104,,,,104,104,,104,104', ',,,104,104,104,104,104,105,105,104,104,105,105,104,105,,,,,,,,,,,,,', ',,,105,105,,,,,,105,,105,,105,105,,105,105,105,,105,105,,,,,105,105', ',,105,,,105,105,,,,,,,105,,,,,,105,,,,105,105,,105,105,,,,105,105,105', '105,105,106,106,105,105,106,106,105,106,,,,,,,,,,,,,,,,,106,106,,,,', ',106,,106,,106,106,,106,106,106,,106,106,,,,,106,106,,,106,,,106,106', ',,,,,,106,,,,,,106,,,,106,106,,106,106,,,,106,106,106,106,106,107,107', '106,106,107,107,106,107,,,,,,,,,,,,,,,,,107,107,,,,,,107,,107,,107,107', ',107,107,107,,107,107,,,,,107,107,,,107,,,107,107,,,,,,,107,,,,,,107', ',,,107,107,,107,107,,,,107,107,107,107,107,108,108,107,107,108,108,107', '108,,,,,,,,,,,,,,,,,108,108,,,,,,108,,108,,108,108,,108,108,108,,108', '108,,,,,108,108,,,108,,,108,108,,,,,,,108,,,,,,108,,,,108,108,,108,108', ',,,108,108,108,108,108,109,109,108,108,109,109,108,109,,,,,,,,,,,,,', ',,,109,109,,,,,,109,,109,,109,109,,109,109,109,,109,109,,,,,109,109', ',,109,,,109,109,,,,,,,109,,,,,,109,,,,109,109,,109,109,,,,109,109,109', '109,109,110,110,109,109,110,110,109,110,,,,,,,,,,,,,,,,,110,110,,,,', ',110,,110,,110,110,,110,110,110,,110,110,,,,,110,110,,,110,,,110,110', ',,,,,,110,,,,,,110,,,,110,110,,110,110,,,,110,110,110,110,110,111,111', '110,110,111,111,110,111,,,,,,,,,,,,,,,,,111,111,,,,,,111,,111,,111,111', ',111,111,111,,111,111,,,,,111,111,,,111,,,111,111,,,,,,,111,,,,,,111', ',,,111,111,,111,111,,,,111,111,111,111,111,112,112,111,111,112,112,111', '112,,,,,,,,,,,,,,,,,112,112,,,,,,112,,112,,112,112,,112,112,112,,112', '112,,,,,112,112,,,112,,,112,112,,,,,,,112,,,,,,112,,,,112,112,,112,112', ',,,112,112,112,112,112,113,113,112,112,113,113,112,113,,,,,,,,,,,,,', ',,,113,113,,,,,,113,,113,,113,113,,113,113,113,,113,113,,,,,113,113', ',,113,,,113,113,,,,,,,113,,,,,,113,,,113,113,113,,113,113,,,,113,113', '113,113,113,114,114,113,113,114,114,113,114,,,,,,,,,,,,,,,,,114,114', ',,,,,114,,114,,114,114,,114,114,114,,114,114,114,114,,,114,114,,,114', ',,114,114,,,,,,,114,,,,,,114,,,,114,114,,114,114,,,,114,114,114,114', '114,114,114,114,114,118,118,114,248,118,118,,118,,,,,,,,,,248,,248,', ',,248,118,118,,,,,,118,,118,,118,118,,118,118,118,,118,118,248,,,,118', '118,,,118,,,118,118,,,248,248,,,118,,,248,,,118,,,,118,118,,118,118', ',,,118,118,118,118,118,119,119,118,118,119,119,118,119,,,,,,,,,,,,,', ',,,119,119,,,,,,119,,119,,119,119,,119,119,119,,119,119,,,,,119,119', ',,119,,,119,119,,,,,,,119,,,,,,119,,,,119,119,,119,119,,,,119,119,119', '119,119,122,122,119,119,122,122,119,122,,,,,,,,,,,,,,,,,122,122,,,,', ',122,,122,,122,122,,122,122,122,,122,122,,,,,122,122,,,122,,,122,122', ',,,,,,122,,,,,,122,,,,122,122,,122,122,,,,122,122,122,122,122,159,159', '122,122,159,159,122,159,,,,,,,,,,,,,,,,,159,159,159,,,,,159,,159,,159', '159,,159,159,159,,159,159,159,159,,,159,159,,,159,,,159,159,,,,,,,159', ',,,,,159,,,,159,159,,159,159,,,,159,159,159,159,159,159,159,159,159', '168,168,159,,168,168,,168,168,,,,,,,,,,,,,,,,168,168,,,,,,168,,168,', '168,168,,168,168,168,,168,168,168,168,,,168,168,,,168,,,168,168,,,,', ',,168,,,,,,168,,,,168,168,,168,168,,,,168,168,168,168,168,198,198,168', '168,198,198,168,198,,,,,,,,,,,,,,,,,198,198,,,,,,198,,198,,198,198,', '198,198,198,,198,198,198,198,,,198,198,,,198,,,198,198,,,,,,,198,,,', ',,198,,,,198,198,,198,198,,,,198,198,198,198,198,201,201,198,198,201', '201,198,201,201,,,,,,,,,,,,,,,,201,201,,,,,,201,,201,,201,201,,201,201', '201,,201,201,201,201,,,201,201,,,201,,,201,201,,,,,,,201,,,,,,201,,', ',201,201,,201,201,,,,201,201,201,201,201,201,201,201,201,214,214,201', ',214,214,,214,,,214,,,,,,,,,,,,,,214,214,,,,,,214,,214,,214,214,,214', '214,214,,214,214,,,,,214,214,,,214,,,214,214,,,,,,,214,,,,,,214,,,,214', '214,,214,214,,,,214,214,214,214,214,219,219,214,214,219,219,214,219', ',,,,,,,,,,,,,,,,219,219,,,,,,219,,219,,219,219,,219,219,219,,219,219', ',,,,219,219,,,219,,,219,219,,,,,,,219,,,,,,219,,,,219,219,,219,219,', ',,219,219,219,219,219,220,220,219,219,220,220,219,220,,,,,,,,,,,,,,', ',,220,220,,,,,,220,,220,,220,220,,220,220,220,,220,220,,,,,220,220,', ',220,,,220,220,,,,,,,220,,,,,,220,,,,220,220,,220,220,,,,220,220,220', '220,220,221,221,220,220,221,221,220,221,,,,,,,,,,,,,,,,,221,221,,,,', ',221,,221,,221,221,,221,221,221,,221,221,,,,,221,221,,,221,,,221,221', ',,,,,,221,,,,,,221,,,,221,221,,221,221,,,,221,221,221,221,221,262,262', '221,221,262,262,221,262,,,,,,,,,,,,,,,,,262,262,,,,,,262,,262,,262,262', ',262,262,262,,262,262,,,,,262,262,,,262,,,262,262,,,,,,,262,,,,,,262', ',,,262,262,,262,262,,,,262,262,262,262,262,284,284,262,262,284,284,262', '284,,,,,,,,,,,,,,,,,284,284,,,,,,284,,284,,284,284,,284,284,284,,284', '284,284,284,,,284,284,,,284,,,284,284,,,,,,,284,,,,,,284,,,,284,284', ',284,284,,,,284,284,284,284,284,284,284,284,284,287,287,284,,287,287', ',287,,,,,,,,,,,,,,,,,287,287,,,,,,287,,287,,287,287,,287,287,287,,287', '287,287,287,,,287,287,,,287,,,287,287,,,,,,,287,,,,,,287,,,,287,287', ',287,287,,,,287,287,287,287,287,287,287,287,287,288,288,287,,288,288', ',288,,,,,,,,,,,,,,,,,288,288,,,,,,288,,288,,288,288,,288,288,288,,288', '288,288,288,,,288,288,,,288,,,288,288,,,,,,,288,,,,,,288,,,,288,288', ',288,288,,,,288,288,288,288,288,288,288,288,288,289,289,288,,289,289', ',289,,,,,,,,,,,,,,,,,289,289,,,,,,289,,289,,289,289,,289,289,289,,289', '289,289,289,,,289,289,,,289,,,289,289,,,,,,,289,,,,,,289,,,,289,289', ',289,289,,,,289,289,289,289,289,307,307,289,289,307,307,289,307,307', ',,,,,,,,,,,,,,,307,307,,,,,,307,,307,,307,307,,307,307,307,,307,307', '307,307,,,307,307,,,307,,,307,307,,,,,,,307,,,,,,307,,,,307,307,,307', '307,,,,307,307,307,307,307,308,308,307,307,308,308,307,308,308,,,,,', ',,,,,,,,,,308,308,,,,,,308,,308,,308,308,,308,308,308,,308,308,308,308', ',,308,308,,,308,,,308,308,,,,,,,308,,,,,,308,,,,308,308,,308,308,,,', '308,308,308,308,308,309,309,308,308,309,309,308,309,,,,,,,,,,,,,,,,', '309,309,,,,,,309,,309,,309,309,,309,309,309,,309,309,,,,,309,309,,,309', ',,309,309,,,,,,,309,,,,,,309,,,,309,309,,309,309,,,,309,309,309,309', '309,324,324,309,309,324,324,309,324,,,,,,,,,,,,,,,,,324,324,,,,,,324', ',324,,324,324,,324,324,324,,324,324,324,324,,,324,324,,,324,,,324,324', ',,,,,,324,,,,,,324,,,,324,324,,324,324,,,,324,324,324,324,324,324,324', '324,324,327,327,324,,327,327,,327,327,,,,,,,,,,,,,,,,327,327,,,,,,327', ',327,,327,327,,327,327,327,,327,327,327,327,,,327,327,,,327,,,327,327', ',,,,,,327,,,,,,327,,,,327,327,,327,327,,,,327,327,327,327,327,327,327', '327,327,332,332,327,,332,332,,332,,,,,,,,,,,,,,,,,332,332,,,,,,332,', '332,,332,332,,332,332,332,,332,332,332,332,,,332,332,,,332,,,332,332', ',,,,,,332,,,,,,332,,,,332,332,,332,332,,,,332,332,332,332,332,347,347', '332,332,347,347,332,347,,,,,,,,,,,,,,,,,347,347,,,,,,347,,347,,347,347', ',347,347,347,,347,347,,,,,347,347,,,347,,,347,347,,,,,,,347,,,,,,347', ',,,347,347,,347,347,,,,347,347,347,347,347,348,348,347,347,348,348,347', '348,,,,,,,,,,,,,,,,,348,348,,,,,,348,,348,,348,348,,348,348,348,,348', '348,,,,,348,348,,,348,,,348,348,,,,,,,348,,,,,,348,,,,348,348,,348,348', ',,,348,348,348,348,348,349,349,348,348,349,349,348,349,,,,,,,,,,,,,', ',,,349,349,,,,,,349,,349,,349,349,,349,349,349,,349,349,,,,,349,349', ',,349,,,349,349,,,,,,,349,,,,,,349,,,,349,349,,349,349,,,,349,349,349', '349,349,350,350,349,349,350,350,349,350,,,,,,,,,,,,,,,,,350,350,,,,', ',350,,350,,350,350,,350,350,350,,350,350,,,,,350,350,,,350,,,350,350', ',,,,,,350,,,,,,350,,,,350,350,,350,350,,,,350,350,350,350,350,354,354', '350,350,354,354,350,354,,,,,,,,,,,,,,,,,354,354,,,,,,354,,354,,354,354', ',354,354,354,,354,354,354,354,,,354,354,,,354,,,354,354,,,,,,,354,,', ',,,354,,,,354,354,,354,354,,,,354,354,354,354,354,354,354,354,354,375', '375,354,,375,375,,375,,,,,,,,,,,,,,,,,375,375,,,,,,375,,375,,375,375', ',375,375,375,,375,375,,,,,375,375,,,375,,,375,375,,,,,,,375,,,,,,375', ',,,375,375,,375,375,,,,375,375,375,375,375,392,392,375,375,392,392,375', '392,392,,,,,,,,,,,,,,,,392,392,,,,,,392,,392,,392,392,,392,392,392,', '392,392,392,392,,,392,392,,,392,,,392,392,,,,,,,392,,,,,,392,,,,392', '392,,392,392,,,,392,392,392,392,392,396,396,392,392,396,396,392,396', ',,,,,,,,,,,,,,,,396,396,,,,,,396,,396,,396,396,,396,396,396,,396,396', ',,,,396,396,,,396,,,396,396,,,,,,,396,,,,,,396,,,,396,396,,396,396,', ',,396,396,396,396,396,399,399,396,396,399,399,396,399,,,,,,,,,,,,,,', ',,399,399,,,,,,399,,399,,399,399,,399,399,399,,399,399,399,399,,,399', '399,,,399,,,399,399,,,,,,,399,,,,,,399,,,,399,399,,399,399,,,,399,399', '399,399,399,399,399,399,399,407,407,399,,407,407,,407,407,,,,,,,,,,', ',,,,,407,407,,,,,,407,,407,,407,407,,407,407,407,,407,407,,,,,407,407', ',,407,,,407,407,,,,,,,407,,,,,,407,,,,407,407,,407,407,,,,407,407,407', '407,407,409,409,407,407,409,409,407,409,,,,,,,,,,,,,,,,,409,409,,,,', ',409,,409,,409,409,,409,409,409,,409,409,409,409,,,409,409,,,409,,,409', '409,,,,,,,409,,,,,,409,,,,409,409,,409,409,,,,409,409,409,409,409,410', '410,409,409,410,410,409,410,,,,,,,,,,,,,,,,,410,410,,,,,,410,,410,,410', '410,,410,410,410,,410,410,410,410,,,410,410,,,410,,,410,410,,,,,,,410', ',,,,,410,,,,410,410,,410,410,,,,410,410,410,410,410,412,412,410,410', '412,412,410,412,,,,,,,,,,,,,,,,,412,412,,,,,,412,,412,,412,412,,412', '412,412,,412,412,412,412,,,412,412,,,412,,,412,412,,,,,,,412,,,,,,412', ',,,412,412,,412,412,,,,412,412,412,412,412,421,421,412,412,421,421,412', '421,,,,,,,,,,,,,,,,,421,421,,,,,,421,,421,,421,421,,421,421,421,,421', '421,421,421,,,421,421,,,421,,,421,421,,,,,,,421,,,,,,421,,,,421,421', ',421,421,,,,421,421,421,421,421,421,421,421,421,438,438,421,,438,438', ',438,,,,,,,,,,,,,,,,,438,438,,,,,,438,,438,,438,438,,438,438,438,,438', '438,,,,,438,438,,,438,,,438,438,,,,,,,438,,,,,,438,,,,438,438,,438,438', ',,,438,438,438,438,438,445,445,438,438,445,445,438,445,,,,,,,,,,,,,', ',,,445,445,,,,,,445,,445,,445,445,,445,445,445,,445,445,445,445,,,445', '445,,,445,,,445,445,,,,,,,445,,,,,,445,,,,445,445,,445,445,,,,445,445', '445,445,445,453,453,445,445,453,453,445,453,,,,,,,,,,,,,,,,,453,453', ',,,,,453,,453,,453,453,,453,453,453,,453,453,453,453,,,453,453,,,453', ',,453,453,,,,,,,453,,,,,,453,,,,453,453,,453,453,,,,453,453,453,453', '453,453,453,453,453,456,456,453,,456,456,,456,,,,,,,,,,,,,,,,,456,456', ',,,,,456,,456,,456,456,,456,456,456,,456,456,456,456,,,456,456,,,456', ',,456,456,,,,,,,456,,,,,,456,,,,456,456,,456,456,,,,456,456,456,456', '456,459,459,456,456,459,459,456,459,,,,,,,,,,,,,,,,,459,459,,,,,,459', ',459,,459,459,,459,459,459,,459,459,459,459,,,459,459,,,459,,,459,459', ',,,,,,459,,,,,,459,,,,459,459,,459,459,,,,459,459,459,459,459,459,459', '459,459,465,465,459,,465,465,,465,,,,,,,,,,,,,,,,,465,465,,,,,,465,', '465,,465,465,,465,465,465,,465,465,,,,,465,465,,,465,,,465,465,,,,,', ',465,,,,,,465,,,,465,465,,465,465,,,,465,465,465,465,465,470,470,465', '465,470,470,465,470,,,,,,,,,,,,,,,,,470,470,,,,,,470,,470,,470,470,', '470,470,470,,470,470,,,,,470,470,,,470,,,470,470,,,,,,,470,,,,,,470', ',,,470,470,,470,470,,,,470,470,470,470,470,480,480,470,470,480,480,470', '480,480,,,,,,,,,,,,,,,,480,480,,,,,,480,,480,,480,480,,480,480,480,', '480,480,480,480,,,480,480,,,480,,,480,480,,,,,,,480,,,,,,480,,,,480', '480,,480,480,,,,480,480,480,480,480,482,482,480,480,482,482,480,482', ',,,,,,,,,,,,,,,,482,482,,,,,,482,,482,,482,482,,482,482,482,,482,482', '482,482,,,482,482,,,482,,,482,482,,,,,,,482,,,,,,482,,,,482,482,,482', '482,,,,482,482,482,482,482,501,501,482,482,501,501,482,501,501,,,,,', ',,,,,,,,,,501,501,,,,,,501,,501,,501,501,,501,501,501,,501,501,501,501', ',,501,501,,,501,,,501,501,,,,,,,501,,,,,,501,,,,501,501,,501,501,,,', '501,501,501,501,501,503,503,501,501,503,503,501,503,503,,,,,,,,,,,,', ',,,503,503,,,,,,503,,503,,503,503,,503,503,503,,503,503,503,503,,,503', '503,,,503,,,503,503,,,,,,,503,,,,,,503,,,,503,503,,503,503,,,,503,503', '503,503,503,505,505,503,503,505,505,503,505,,,,,,,,,,,,,,,,,505,505', ',,,,,505,,505,,505,505,,505,505,505,,505,505,505,505,,,505,505,,,505', ',,505,505,,,,,,,505,,,,,,505,,,,505,505,,505,505,,,,505,505,505,505', '505,515,515,505,505,515,515,505,515,,,,,,,,,,,,,,,,,515,515,,,,,,515', ',515,,515,515,,515,515,515,,515,515,,,,,515,515,,,515,,,515,515,,,,', ',,515,,,,,,515,,,,515,515,,515,515,,,,515,515,515,515,515,9,,515,515', ',,515,,,,,,9,9,9,,9,,9,,9,,9,9,9,9,,,,,,,,,,,,,,,,9,,,,9,9,,,9,9,9,9', '9,9,,9,9,160,,160,,,9,,,,,,,160,160,160,,160,,160,,160,,160,160,160', '160,,,,,,9,,,,,,,,,,160,,,,160,160,,,160,160,160,160,160,160,,160,160', '162,,162,,,160,,,,,,,162,162,162,,162,,162,,162,,162,162,162,162,,,', ',,160,,,,,,,,,,162,,,,162,162,,,162,162,162,162,162,162,,162,162,163', ',163,,,162,,,,,,,163,163,163,,163,,163,,163,,163,163,163,163,,,,,,162', ',,,,,,,,,163,,,,163,163,,,163,163,163,163,163,163,200,163,163,,,,,,163', ',,,200,200,200,,200,,200,,200,,200,200,200,200,,,,,,,,,163,,,,,,,200', ',,244,200,200,,,200,200,200,200,200,200,,200,200,244,,244,,,200,244', ',,,212,,,,,,200,200,,,,,212,212,212,212,212,244,212,,212,200,212,212', '212,212,,244,244,,,,244,244,,,,,,244,,212,,,245,212,212,,,212,212,212', '212,212,212,,212,212,245,,245,249,,212,245,244,,,,,,,,,,249,,249,250', ',,249,,,,245,,,,212,,,250,,250,245,245,,250,,245,245,249,,,,251,245', ',,249,249,249,249,,,,249,249,250,251,,251,252,249,,251,250,250,250,250', ',,245,250,250,,252,,252,,250,,252,,,,251,,,249,,,,,251,251,251,251,251', '251,,251,251,252,,,250,253,251,,,252,252,252,252,252,252,,252,252,253', '253,,253,254,252,,253,,253,,,,,251,,,254,254,,254,,,,254,,254,,253,', ',252,,,,,253,253,253,253,253,253,,253,253,254,,,,255,253,,,254,254,254', '254,254,254,,254,254,255,255,,255,256,254,,255,,255,,,,,253,,,256,256', ',256,,,,256,,256,,255,,,254,,,,,255,255,255,255,255,255,,255,255,256', ',,,,255,,,256,256,256,256,256,256,257,256,256,,,,,,256,,,,257,257,257', ',257,255,257,,257,,257,257,257,257,,,,,,,,,256,,,,,,,257,,,,,258,,,257', '257,257,257,257,257,,257,257,258,258,258,,258,257,258,,258,,258,258', '258,258,,,,,,,,,,,,,,,,258,,257,,,258,,,258,258,258,258,258,258,259', '258,258,,,259,,,258,,,,259,259,259,,259,,259,,259,,259,259,259,259,', ',,,,,,,258,,,,,,,259,,,,259,259,,,259,259,259,259,259,259,,259,259,263', '263,,,,259,263,,,,,,263,263,263,,263,,263,,263,,263,263,263,263,,,,', ',259,,,,,,,,,,263,,,,263,263,,,263,263,263,263,263,263,270,263,263,', ',,,,263,,,,270,270,270,,270,,270,,270,,270,270,270,270,,,,,,,,,263,', ',,,,,270,,,,270,270,,,270,270,270,270,270,270,275,270,270,,,,,,270,', ',,275,275,275,275,275,,275,,275,,275,275,275,275,,,,,,,,,270,,,,,,,275', ',,,275,275,,,275,275,275,275,275,275,405,275,275,,,,,,275,,,,405,405', '405,,405,,405,,405,,405,405,405,405,,,,,,,,,275,,,,,,,405,,,,405,405', ',,405,405,405,405,405,405,433,405,405,,,,,,405,,,,433,433,433,,433,', '433,,433,,433,433,433,433,,,,,,,,,405,,,,,,,433,,,,433,433,,,433,433', '433,433,433,433,434,433,433,,,,,,433,,,,434,434,434,,434,,434,,434,', '434,434,434,434,,,,,,,,,433,,,,,,,434,,,,434,434,,,434,434,434,434,434', '434,435,434,434,,,,,,434,,,,435,435,435,,435,,435,,435,,435,435,435', '435,,,,,,,,,434,,,,,,,435,,,,435,435,,,435,435,435,435,435,435,436,435', '435,,,,,,435,,,,436,436,436,,436,,436,,436,,436,436,436,436,,,,,,,,', '435,,,,,,,436,,,,436,436,,,436,436,436,436,436,436,462,436,436,,,,,', '436,,,,462,462,462,,462,,462,,462,,462,462,462,462,,,,,,,,,436,,,,,', ',462,,,,462,462,,,462,462,462,462,462,462,504,462,462,,,,,,462,,,,504', '504,504,,504,,504,,504,,504,504,504,504,,,,,,,,,462,,,,,,,504,,,,504', '504,,,504,504,504,504,504,504,,504,504,,269,269,269,269,504,269,269', '269,,269,,269,269,,,,,,,269,269,269,,,,269,,,,,504,,,,,,,269,269,,,', ',,,,,,,,269,269,269,269,269,269,269,269,344,344,344,344,,344,344,344', ',344,,344,344,,,,,,,344,344,344,,,,344,,,,,,,,,,,,344,344,,,,,,,,,,', ',344,344,344,344,344,344,344,344,346,346,346,346,,346,346,346,,346,', '346,346,,,,,,,346,346,346,,,,346,,,,,,,,,,,,346,346,,,,,,,,,,,,346,346', '346,346,346,346,346,346,376,376,376,376,,376,376,376,,376,,376,376,', ',,,,,376,376,376,,,,376,,,,,,,,,,,,376,376,,,,,,,,,,,,376,376,376,376', '376,376,376,376,384,384,384,384,,384,384,384,,384,,384,384,,,,,,,384', '384,384,,,,384,,,,,,,,,,,,384,384,,,,,,,,,,,,384,384,384,384,384,384', '384,384,411,411,411,411,,411,411,411,,411,,411,411,,,,,,,411,411,411', ',,,411,,,,,,,,,,,,411,411,,,,,,,,,,,,411,411,411,411,411,411,411,411' ] racc_action_check = arr = ::Array.new(10078, nil) idx = 0 clist.each do |str| str.split(',', -1).each do |i| arr[idx] = i.to_i unless i.empty? idx += 1 end end racc_action_pointer = [ -2, 13, nil, nil, 84, 24, nil, -2, 32, 8411, 170, 256, 94, nil, nil, nil, -30, 342, 428, 514, 600, 686, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 110, 772, 7, -25, 82, 862, 948, 1034, 66, 145, 188, 150, 114, 313, 151, 196, 234, 265, 124, nil, nil, nil, 1124, 1214, 1304, nil, nil, nil, 1394, nil, 71, 93, nil, 1480, nil, 181, 1566, 171, 1652, 1738, 1824, 1910, 1996, 2082, 2168, 2254, 2340, 2430, 2516, 2602, 2688, 2774, 2860, 2946, 3032, 3118, 3204, 3290, 3376, 3462, 3548, 3634, 3720, 3806, 3892, 3978, 4064, nil, -13, 182, 4154, 4240, 197, 210, 4326, 336, nil, nil, nil, nil, nil, nil, 152, nil, nil, 202, 180, 349, 521, 212, 223, 255, nil, nil, 258, 223, 93, 230, 309, 231, 314, nil, nil, nil, nil, 326, nil, 11, nil, 276, 194, 4412, 8469, nil, 8527, 8585, 340, 364, 236, 368, 4502, 347, nil, 343, nil, nil, nil, nil, nil, nil, nil, nil, 377, 1, 358, nil, nil, 362, nil, 368, nil, 92, nil, nil, nil, nil, 404, nil, nil, 302, 4588, 280, 8640, 4674, nil, 372, nil, nil, nil, nil, 389, 397, 406, 407, 8708, 406, 4764, -1, 5, -9, 220, 4850, 4936, 5022, nil, nil, 6, 237, 286, 95, 413, 399, 323, 7, 15, nil, nil, nil, nil, nil, nil, nil, nil, 607, 5, 91, 8684, 8752, 1059, 2428, 4152, 8769, 8786, 8814, 8831, 8876, 8893, 8938, 8955, 9010, 9056, 9111, nil, nil, 5108, 9169, 410, nil, nil, nil, nil, 9697, 9224, 375, nil, 414, 396, 9279, nil, 401, nil, 176, 262, 433, nil, nil, 5194, 415, nil, 5284, 5374, 5464, 395, 438, 398, nil, nil, 436, nil, nil, nil, nil, nil, 435, 415, -35, nil, -7, nil, 5550, 5636, 5722, 447, 448, 450, 451, 452, nil, 428, 485, 71, 410, 157, nil, nil, 468, 5808, 469, nil, 5898, nil, nil, nil, nil, 5988, nil, nil, nil, nil, 467, nil, nil, nil, nil, 469, 473, 9755, 474, 9813, 6074, 6160, 6246, 6332, 476, nil, 482, 6418, 479, nil, 482, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 485, 6508, 9871, 487, nil, 408, nil, 484, nil, 485, 9929, nil, 428, nil, 86, 491, 493, nil, 6594, 498, 436, -28, 6680, nil, nil, 6766, nil, 499, 275, 500, 490, 9334, 93, 6856, nil, 6942, 7028, 9987, 7114, nil, 516, nil, 833, nil, nil, 517, nil, 7200, nil, nil, nil, nil, nil, nil, 457, 330, 518, nil, nil, 9389, 9444, 9499, 9554, 522, 7290, nil, nil, 523, nil, nil, 521, 7376, nil, nil, nil, 507, 508, 524, nil, 7462, nil, nil, 7552, 528, nil, 7638, nil, nil, 9609, 323, nil, 7728, 530, 509, nil, 536, 7814, 537, nil, nil, 543, nil, nil, 547, 549, 553, 7900, nil, 7986, 553, nil, nil, 557, 527, nil, nil, nil, nil, 563, nil, nil, 564, 565, nil, 172, nil, nil, 8072, nil, 8158, 9664, 8244, nil, nil, 566, nil, 567, nil, 568, 572, nil, 8330, nil, nil, nil, nil, 571, nil, 572, nil, 573, nil, nil, nil, nil, nil, nil, 541, nil, nil, nil, nil ] racc_action_default = [ -3, -298, -1, -2, -4, -5, -8, -10, -18, -23, -298, -298, -190, -35, -36, -37, -38, -298, -298, -298, -298, -298, -70, -71, -72, -73, -74, -75, -76, -77, -78, -79, -80, -81, -82, -83, -84, -85, -86, -87, -88, -89, -90, -91, -92, -93, -94, -95, -96, -298, -298, -103, -107, -298, -298, -298, -298, -298, -298, -298, -298, -298, -298, -246, -269, -245, -298, -217, -218, -219, -298, -298, -298, -242, -243, -244, -298, -248, -298, -261, -264, -298, -270, -298, -298, -7, -298, -298, -298, -298, -298, -298, -298, -298, -144, -298, -298, -298, -298, -298, -298, -298, -298, -298, -298, -298, -298, -298, -298, -298, -298, -298, -298, -298, -298, -101, -298, -139, -297, -297, -24, -25, -298, -297, -160, -187, -188, -189, -190, -191, -298, -156, -157, -49, -190, -50, -57, -298, -298, -14, -15, -16, -271, -98, -236, -238, -241, -237, -298, -233, -239, -240, -102, -207, -214, -269, -104, -297, -298, -298, -298, -115, -298, -298, -297, -297, -298, -297, -298, -271, -171, -173, -174, -175, -176, -177, -179, -180, -245, -246, -297, -298, -271, -221, -230, -231, -234, -271, -223, -298, -226, -227, -232, -247, -298, -252, -255, -298, -259, -298, -298, -298, 536, -6, -9, -11, -12, -13, -19, -20, -21, -22, -298, -271, -298, -94, -95, -96, -289, -282, -288, -276, -145, -148, -298, -279, -293, -190, -296, -285, -291, -218, -219, -275, -280, -281, -283, -284, -286, -294, -295, -39, -40, -41, -42, -43, -44, -45, -46, -47, -48, -51, -52, -53, -54, -55, -56, -58, -59, -298, -60, -133, -298, -23, -271, -64, -67, -108, -109, -144, -143, -298, -142, -298, -273, -298, -30, -297, -192, -298, -298, -298, -61, -62, -272, -298, -100, -298, -298, -259, -298, -298, -298, -186, -113, -271, -197, -199, -200, -201, -202, -204, -298, -298, -269, -298, -106, -298, -298, -298, -298, -298, -298, -298, -298, -168, -297, -272, -298, -297, -211, -212, -213, -298, -272, -298, -224, -298, -249, -250, -251, -253, -298, -256, -257, -258, -260, -271, -262, -265, -267, -268, -8, -298, -144, -298, -272, -298, -298, -298, -298, -271, -135, -298, -272, -271, -147, -298, -276, -277, -278, -279, -282, -285, -287, -288, -289, -290, -291, -292, -293, -296, -140, -141, -298, -274, -144, -298, -163, -298, -193, -271, -194, -271, -144, -17, -97, -229, -298, -298, -298, -110, -298, -184, -298, -272, -298, -205, -206, -298, -105, -298, -118, -298, -124, -68, -298, -298, -128, -297, -297, -144, -297, -167, -298, -181, -298, -172, -178, -298, -209, -298, -220, -235, -222, -225, -228, -254, -298, -298, -271, -28, -146, -151, -149, -150, -137, -298, -272, -63, -65, -298, -27, -31, -271, -297, -164, -165, -166, -298, -298, -271, -99, -298, -208, -215, -259, -298, -112, -298, -114, -198, -203, -118, -117, -298, -298, -124, -123, -298, -298, -298, -127, -129, -298, -161, -162, -298, -271, -298, -298, -182, -297, -271, -263, -266, -298, -32, -134, -136, -138, -29, -298, -195, -196, -298, -298, -111, -298, -116, -119, -298, -122, -298, -69, -297, -152, -153, -298, -158, -298, -170, -298, -298, -26, -33, -159, -155, -216, -185, -298, -121, -298, -126, -298, -131, -132, -154, -169, -183, -210, -273, -120, -125, -130, -34 ] racc_goto_table = [ 2, 274, 174, 172, 141, 264, 133, 135, 136, 124, 191, 137, 138, 213, 156, 153, 261, 374, 339, 272, 272, 154, 335, 291, 142, 193, 440, 321, 185, 185, 295, 417, 85, 130, 408, 1, 120, 121, 271, 273, 139, 468, 160, 162, 163, 182, 187, 3, 486, 464, 443, 260, 266, 164, 165, 322, 124, 391, 293, 180, 500, 139, 139, 192, 407, 524, 351, 195, 432, 167, 200, 337, 377, 446, 414, 166, 418, 204, 205, 206, 207, 461, 398, 212, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 263, 502, 192, 286, 270, 270, 153, 499, 275, 203, 389, 141, 154, 208, 209, 210, 211, 397, 474, 477, 153, 479, 440, 387, 281, 423, 333, 481, 124, 473, 277, 305, 334, 331, 332, 330, 191, 196, 198, 165, nil, 180, nil, 320, nil, nil, nil, 139, 381, 383, nil, nil, nil, nil, 492, nil, nil, nil, nil, nil, 312, 153, 352, 420, nil, 314, nil, 174, 172, nil, nil, 310, 311, nil, 313, 378, 388, nil, nil, nil, 153, nil, 419, nil, nil, 355, 154, 319, nil, 342, nil, 512, nil, nil, nil, 336, nil, nil, 343, nil, 135, nil, nil, nil, nil, 160, 162, 163, nil, nil, nil, nil, nil, nil, 415, nil, nil, 293, nil, 164, 165, 124, nil, 266, 180, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 385, nil, nil, nil, nil, 153, 153, nil, 485, nil, nil, nil, 259, nil, nil, nil, nil, 285, 393, 174, 172, nil, nil, nil, 430, nil, 426, nil, nil, nil, nil, nil, nil, nil, nil, 535, 139, nil, 263, 192, 192, 496, 427, 185, 316, nil, nil, nil, nil, nil, 336, nil, nil, 266, nil, nil, 444, 323, nil, 405, nil, nil, 325, nil, 451, nil, nil, nil, 401, 403, nil, 476, 476, 483, 476, nil, 139, nil, nil, 192, nil, nil, nil, nil, 195, nil, nil, nil, 345, nil, nil, 478, nil, nil, nil, nil, nil, 433, 434, 435, 436, nil, 489, nil, 263, nil, 192, 476, nil, nil, 452, 498, nil, nil, nil, 385, nil, nil, 174, 172, 266, 153, nil, nil, nil, 275, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 447, nil, 353, nil, nil, nil, nil, 476, nil, 462, nil, nil, nil, nil, 139, 266, 457, nil, 531, nil, 405, 266, nil, nil, nil, nil, nil, nil, nil, nil, 525, nil, nil, 394, 263, nil, 192, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 259, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 263, nil, 192, nil, nil, nil, 263, nil, 192, 428, nil, nil, 160, nil, 336, nil, nil, 504, nil, nil, nil, nil, nil, 437, nil, nil, nil, 441, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 510, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 449, nil, 450, nil, nil, nil, 520, nil, 522, 275, 526, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 487, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 491, nil, nil, nil, nil, nil, nil, 495, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 508, nil, nil, nil, nil, 513 ] racc_goto_check = [ 2, 15, 46, 47, 8, 24, 12, 12, 12, 71, 9, 6, 6, 13, 50, 48, 64, 16, 107, 63, 63, 88, 90, 54, 10, 100, 25, 45, 95, 95, 56, 80, 5, 74, 61, 1, 11, 11, 67, 67, 6, 59, 12, 12, 12, 91, 91, 3, 14, 58, 18, 23, 9, 71, 71, 44, 71, 55, 63, 71, 57, 6, 6, 6, 60, 62, 65, 6, 68, 75, 12, 56, 76, 77, 79, 74, 82, 6, 6, 6, 6, 83, 84, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 59, 6, 50, 12, 12, 48, 58, 12, 5, 90, 8, 88, 11, 11, 11, 11, 86, 73, 73, 48, 73, 25, 93, 71, 94, 96, 80, 71, 61, 72, 10, 97, 101, 102, 104, 9, 105, 106, 71, nil, 71, nil, 48, nil, nil, nil, 6, 56, 56, nil, nil, nil, nil, 73, nil, nil, nil, nil, nil, 71, 48, 64, 45, nil, 2, nil, 46, 47, nil, nil, 72, 72, nil, 72, 63, 24, nil, nil, nil, 48, nil, 54, nil, nil, 13, 88, 72, nil, 6, nil, 73, nil, nil, nil, 2, nil, nil, 2, nil, 12, nil, nil, nil, nil, 12, 12, 12, nil, nil, nil, nil, nil, nil, 63, nil, nil, 63, nil, 71, 71, 71, nil, 9, 71, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 8, nil, nil, nil, nil, 48, 48, nil, 107, nil, nil, nil, 12, nil, nil, nil, nil, 17, 48, 46, 47, nil, nil, nil, 13, nil, 9, nil, nil, nil, nil, nil, nil, nil, nil, 16, 6, nil, 12, 6, 6, 90, 100, 95, 17, nil, nil, nil, nil, nil, 2, nil, nil, 9, nil, nil, 13, 17, nil, 12, nil, nil, 17, nil, 13, nil, nil, nil, 2, 2, nil, 63, 63, 24, 63, nil, 6, nil, nil, 6, nil, nil, nil, nil, 6, nil, nil, nil, 17, nil, nil, 13, nil, nil, nil, nil, nil, 12, 12, 12, 12, nil, 64, nil, 12, nil, 6, 63, nil, nil, 50, 24, nil, nil, nil, 8, nil, nil, 46, 47, 9, 48, nil, nil, nil, 12, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 71, nil, 17, nil, nil, nil, nil, 63, nil, 12, nil, nil, nil, nil, 6, 9, 2, nil, 15, nil, 12, 9, nil, nil, nil, nil, nil, nil, nil, nil, 63, nil, nil, 17, 12, nil, 6, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 12, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 12, nil, 6, nil, nil, nil, 12, nil, 6, 17, nil, nil, 12, nil, 2, nil, nil, 12, nil, nil, nil, nil, nil, 17, nil, nil, nil, 17, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 2, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 17, nil, 17, nil, nil, nil, 2, nil, 2, 12, 2, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 17, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 17, nil, nil, nil, nil, nil, nil, 17, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 17, nil, nil, nil, nil, 17 ] racc_goto_pointer = [ nil, 35, 0, 47, nil, 28, -9, nil, -45, -62, -25, 26, -11, -81, -382, -121, -257, 114, -325, nil, nil, nil, nil, -62, -109, -328, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, -126, -154, -59, -58, -35, nil, -37, nil, nil, nil, -134, -234, -128, -405, -353, -363, -245, -275, -440, -99, -97, -196, nil, -80, -278, nil, nil, -3, 10, -288, 17, 10, -205, -306, nil, -242, -286, nil, -242, -314, -221, nil, -182, nil, -29, nil, -176, -25, nil, -161, -196, -42, -68, -62, nil, nil, -51, -58, -57, nil, -56, 62, 62, -182 ] racc_goto_default = [ nil, nil, 475, nil, 4, 5, 6, 7, 184, 140, nil, 8, 9, nil, nil, nil, nil, nil, 276, 13, 14, 15, 16, nil, nil, 265, 406, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 49, nil, 51, 52, 157, nil, nil, nil, 161, nil, nil, nil, nil, nil, 278, nil, nil, 117, nil, 222, 224, 223, 58, nil, nil, nil, 123, nil, nil, 169, nil, 170, 171, 175, 296, 297, 298, 299, 300, 303, 66, nil, nil, 189, 148, 186, 149, 73, 74, 75, 76, nil, nil, nil, 194, nil, nil, nil, nil ] racc_reduce_table = [ 0, 0, :racc_error, 1, 98, :_reduce_1, 1, 98, :_reduce_2, 0, 98, :_reduce_3, 1, 99, :_reduce_4, 1, 101, :_reduce_5, 3, 101, :_reduce_6, 2, 101, :_reduce_7, 1, 102, :_reduce_8, 3, 102, :_reduce_9, 1, 103, :_reduce_none, 3, 103, :_reduce_11, 3, 103, :_reduce_12, 3, 103, :_reduce_13, 1, 105, :_reduce_none, 1, 105, :_reduce_15, 1, 107, :_reduce_16, 3, 107, :_reduce_17, 1, 104, :_reduce_none, 3, 104, :_reduce_19, 3, 104, :_reduce_20, 3, 104, :_reduce_21, 3, 104, :_reduce_22, 1, 108, :_reduce_none, 2, 108, :_reduce_24, 2, 108, :_reduce_25, 7, 108, :_reduce_26, 5, 108, :_reduce_27, 5, 108, :_reduce_28, 4, 115, :_reduce_29, 1, 112, :_reduce_30, 3, 112, :_reduce_31, 1, 111, :_reduce_32, 2, 111, :_reduce_33, 4, 111, :_reduce_34, 1, 109, :_reduce_none, 1, 109, :_reduce_none, 1, 109, :_reduce_none, 1, 109, :_reduce_none, 3, 109, :_reduce_39, 3, 109, :_reduce_40, 3, 109, :_reduce_41, 3, 109, :_reduce_42, 3, 109, :_reduce_43, 3, 109, :_reduce_44, 3, 109, :_reduce_45, 3, 109, :_reduce_46, 3, 109, :_reduce_47, 3, 109, :_reduce_48, 2, 109, :_reduce_49, 2, 109, :_reduce_50, 3, 109, :_reduce_51, 3, 109, :_reduce_52, 3, 109, :_reduce_53, 3, 109, :_reduce_54, 3, 109, :_reduce_55, 3, 109, :_reduce_56, 2, 109, :_reduce_57, 3, 109, :_reduce_58, 3, 109, :_reduce_59, 3, 109, :_reduce_60, 3, 109, :_reduce_61, 3, 109, :_reduce_62, 5, 119, :_reduce_63, 1, 121, :_reduce_64, 3, 121, :_reduce_65, 1, 122, :_reduce_none, 1, 122, :_reduce_67, 1, 123, :_reduce_68, 3, 123, :_reduce_69, 1, 116, :_reduce_none, 1, 116, :_reduce_none, 1, 116, :_reduce_none, 1, 116, :_reduce_none, 1, 116, :_reduce_none, 1, 116, :_reduce_none, 1, 116, :_reduce_none, 1, 116, :_reduce_none, 1, 116, :_reduce_none, 1, 116, :_reduce_none, 1, 116, :_reduce_none, 1, 116, :_reduce_none, 1, 116, :_reduce_none, 1, 116, :_reduce_none, 1, 116, :_reduce_none, 1, 116, :_reduce_none, 1, 116, :_reduce_none, 1, 116, :_reduce_none, 1, 116, :_reduce_none, 1, 116, :_reduce_none, 1, 116, :_reduce_none, 1, 116, :_reduce_none, 1, 116, :_reduce_92, 1, 116, :_reduce_93, 1, 116, :_reduce_94, 1, 116, :_reduce_95, 1, 116, :_reduce_96, 4, 118, :_reduce_97, 2, 118, :_reduce_98, 5, 118, :_reduce_99, 3, 118, :_reduce_100, 2, 146, :_reduce_101, 2, 146, :_reduce_102, 1, 125, :_reduce_103, 2, 125, :_reduce_104, 4, 148, :_reduce_105, 3, 148, :_reduce_106, 1, 148, :_reduce_107, 3, 149, :_reduce_108, 3, 149, :_reduce_109, 3, 147, :_reduce_110, 3, 152, :_reduce_111, 2, 152, :_reduce_112, 2, 150, :_reduce_113, 4, 150, :_reduce_114, 2, 128, :_reduce_115, 5, 154, :_reduce_116, 4, 154, :_reduce_117, 0, 155, :_reduce_none, 2, 155, :_reduce_119, 4, 155, :_reduce_120, 3, 155, :_reduce_121, 6, 129, :_reduce_122, 5, 129, :_reduce_123, 0, 156, :_reduce_none, 4, 156, :_reduce_125, 3, 156, :_reduce_126, 5, 127, :_reduce_127, 1, 157, :_reduce_128, 2, 157, :_reduce_129, 5, 158, :_reduce_130, 1, 159, :_reduce_none, 1, 159, :_reduce_none, 1, 120, :_reduce_none, 4, 120, :_reduce_134, 1, 162, :_reduce_135, 3, 162, :_reduce_136, 3, 161, :_reduce_137, 6, 126, :_reduce_138, 2, 126, :_reduce_139, 3, 163, :_reduce_140, 3, 163, :_reduce_141, 1, 164, :_reduce_none, 1, 164, :_reduce_none, 0, 110, :_reduce_144, 1, 110, :_reduce_145, 3, 110, :_reduce_146, 1, 166, :_reduce_none, 1, 166, :_reduce_none, 3, 165, :_reduce_149, 3, 165, :_reduce_150, 3, 165, :_reduce_151, 6, 130, :_reduce_152, 6, 131, :_reduce_153, 7, 117, :_reduce_154, 7, 117, :_reduce_155, 1, 171, :_reduce_none, 1, 171, :_reduce_none, 6, 133, :_reduce_158, 7, 132, :_reduce_159, 1, 172, :_reduce_160, 1, 170, :_reduce_none, 1, 170, :_reduce_none, 1, 173, :_reduce_none, 2, 173, :_reduce_164, 1, 174, :_reduce_none, 1, 174, :_reduce_none, 4, 135, :_reduce_167, 3, 135, :_reduce_168, 7, 134, :_reduce_169, 6, 134, :_reduce_170, 1, 175, :_reduce_171, 3, 175, :_reduce_172, 1, 177, :_reduce_none, 1, 177, :_reduce_none, 1, 177, :_reduce_175, 1, 177, :_reduce_none, 1, 178, :_reduce_177, 3, 178, :_reduce_178, 1, 179, :_reduce_none, 1, 179, :_reduce_none, 1, 176, :_reduce_none, 2, 176, :_reduce_182, 7, 137, :_reduce_183, 2, 151, :_reduce_184, 5, 151, :_reduce_185, 1, 151, :_reduce_none, 1, 168, :_reduce_none, 1, 168, :_reduce_none, 1, 168, :_reduce_none, 1, 168, :_reduce_190, 1, 168, :_reduce_191, 1, 169, :_reduce_192, 2, 169, :_reduce_193, 2, 169, :_reduce_194, 4, 169, :_reduce_195, 4, 169, :_reduce_196, 1, 153, :_reduce_197, 3, 153, :_reduce_198, 1, 180, :_reduce_none, 1, 180, :_reduce_none, 1, 181, :_reduce_none, 1, 181, :_reduce_none, 3, 183, :_reduce_203, 1, 183, :_reduce_204, 2, 184, :_reduce_205, 2, 182, :_reduce_206, 1, 185, :_reduce_207, 4, 185, :_reduce_208, 4, 138, :_reduce_209, 7, 138, :_reduce_210, 3, 138, :_reduce_211, 3, 138, :_reduce_212, 3, 138, :_reduce_213, 2, 186, :_reduce_214, 5, 139, :_reduce_215, 7, 139, :_reduce_216, 1, 124, :_reduce_217, 1, 140, :_reduce_218, 1, 140, :_reduce_219, 4, 141, :_reduce_220, 2, 141, :_reduce_221, 4, 141, :_reduce_222, 2, 141, :_reduce_223, 3, 142, :_reduce_224, 4, 142, :_reduce_225, 2, 142, :_reduce_226, 1, 189, :_reduce_227, 3, 189, :_reduce_228, 3, 106, :_reduce_229, 1, 191, :_reduce_none, 1, 191, :_reduce_231, 1, 190, :_reduce_none, 1, 190, :_reduce_233, 1, 188, :_reduce_234, 3, 188, :_reduce_235, 1, 192, :_reduce_none, 1, 192, :_reduce_none, 1, 192, :_reduce_none, 1, 192, :_reduce_none, 1, 192, :_reduce_none, 1, 192, :_reduce_none, 1, 144, :_reduce_none, 1, 144, :_reduce_none, 1, 144, :_reduce_none, 1, 193, :_reduce_245, 1, 193, :_reduce_246, 2, 194, :_reduce_247, 1, 196, :_reduce_248, 1, 198, :_reduce_249, 1, 199, :_reduce_250, 2, 197, :_reduce_251, 1, 200, :_reduce_252, 1, 201, :_reduce_253, 2, 201, :_reduce_254, 2, 195, :_reduce_255, 2, 202, :_reduce_256, 2, 202, :_reduce_257, 3, 100, :_reduce_258, 0, 187, :_reduce_none, 1, 187, :_reduce_none, 0, 203, :_reduce_261, 2, 203, :_reduce_262, 4, 203, :_reduce_263, 1, 136, :_reduce_264, 3, 136, :_reduce_265, 5, 136, :_reduce_266, 1, 204, :_reduce_none, 1, 204, :_reduce_none, 1, 145, :_reduce_269, 1, 143, :_reduce_270, 0, 114, :_reduce_none, 1, 114, :_reduce_272, 0, 113, :_reduce_none, 1, 113, :_reduce_none, 1, 167, :_reduce_none, 1, 167, :_reduce_none, 1, 167, :_reduce_none, 1, 167, :_reduce_none, 1, 167, :_reduce_none, 1, 167, :_reduce_none, 1, 167, :_reduce_none, 1, 167, :_reduce_none, 1, 167, :_reduce_none, 1, 167, :_reduce_none, 1, 167, :_reduce_none, 1, 167, :_reduce_none, 1, 167, :_reduce_none, 1, 167, :_reduce_none, 1, 167, :_reduce_none, 1, 167, :_reduce_none, 1, 167, :_reduce_none, 1, 167, :_reduce_none, 1, 167, :_reduce_none, 1, 167, :_reduce_none, 1, 167, :_reduce_none, 1, 167, :_reduce_none, 0, 160, :_reduce_297 ] racc_reduce_n = 298 racc_shift_n = 536 racc_token_table = { false => 0, :error => 1, :STRING => 2, :DQPRE => 3, :DQMID => 4, :DQPOST => 5, :WORD => 6, :LBRACK => 7, :RBRACK => 8, :LBRACE => 9, :RBRACE => 10, :SYMBOL => 11, :FARROW => 12, :COMMA => 13, :TRUE => 14, :FALSE => 15, :EQUALS => 16, :APPENDS => 17, :DELETES => 18, :LESSEQUAL => 19, :NOTEQUAL => 20, :DOT => 21, :COLON => 22, :LLCOLLECT => 23, :RRCOLLECT => 24, :QMARK => 25, :WSLPAREN => 26, :LPAREN => 27, :RPAREN => 28, :ISEQUAL => 29, :GREATEREQUAL => 30, :GREATERTHAN => 31, :LESSTHAN => 32, :IF => 33, :ELSE => 34, :DEFINE => 35, :ELSIF => 36, :VARIABLE => 37, :CLASS => 38, :INHERITS => 39, :NODE => 40, :BOOLEAN => 41, :NAME => 42, :SEMIC => 43, :CASE => 44, :DEFAULT => 45, :AT => 46, :ATAT => 47, :LCOLLECT => 48, :RCOLLECT => 49, :CLASSREF => 50, :NOT => 51, :OR => 52, :AND => 53, :UNDEF => 54, :PARROW => 55, :PLUS => 56, :MINUS => 57, :TIMES => 58, :DIV => 59, :LSHIFT => 60, :RSHIFT => 61, :UMINUS => 62, :MATCH => 63, :NOMATCH => 64, :REGEX => 65, :IN_EDGE => 66, :OUT_EDGE => 67, :IN_EDGE_SUB => 68, :OUT_EDGE_SUB => 69, :IN => 70, :UNLESS => 71, :PIPE => 72, :LAMBDA => 73, :SELBRACE => 74, :NUMBER => 75, :HEREDOC => 76, :SUBLOCATE => 77, :RENDER_STRING => 78, :RENDER_EXPR => 79, :EPP_START => 80, :EPP_END => 81, :EPP_END_TRIM => 82, :FUNCTION => 83, :TYPE => 84, :PRIVATE => 85, :ATTR => 86, :APPLICATION => 87, :PRODUCES => 88, :CONSUMES => 89, :SITE => 90, :PLAN => 91, :LOW => 92, :HIGH => 93, :LISTSTART => 94, :SPLAT => 95, :MODULO => 96 } racc_nt_base = 97 racc_use_result_var = true Racc_arg = [ racc_action_table, racc_action_check, racc_action_default, racc_action_pointer, racc_goto_table, racc_goto_check, racc_goto_default, racc_goto_pointer, racc_nt_base, racc_reduce_table, racc_token_table, racc_shift_n, racc_reduce_n, racc_use_result_var ] Racc_token_to_s_table = [ "$end", "error", "STRING", "DQPRE", "DQMID", "DQPOST", "WORD", "LBRACK", "RBRACK", "LBRACE", "RBRACE", "SYMBOL", "FARROW", "COMMA", "TRUE", "FALSE", "EQUALS", "APPENDS", "DELETES", "LESSEQUAL", "NOTEQUAL", "DOT", "COLON", "LLCOLLECT", "RRCOLLECT", "QMARK", "WSLPAREN", "LPAREN", "RPAREN", "ISEQUAL", "GREATEREQUAL", "GREATERTHAN", "LESSTHAN", "IF", "ELSE", "DEFINE", "ELSIF", "VARIABLE", "CLASS", "INHERITS", "NODE", "BOOLEAN", "NAME", "SEMIC", "CASE", "DEFAULT", "AT", "ATAT", "LCOLLECT", "RCOLLECT", "CLASSREF", "NOT", "OR", "AND", "UNDEF", "PARROW", "PLUS", "MINUS", "TIMES", "DIV", "LSHIFT", "RSHIFT", "UMINUS", "MATCH", "NOMATCH", "REGEX", "IN_EDGE", "OUT_EDGE", "IN_EDGE_SUB", "OUT_EDGE_SUB", "IN", "UNLESS", "PIPE", "LAMBDA", "SELBRACE", "NUMBER", "HEREDOC", "SUBLOCATE", "RENDER_STRING", "RENDER_EXPR", "EPP_START", "EPP_END", "EPP_END_TRIM", "FUNCTION", "TYPE", "PRIVATE", "ATTR", "APPLICATION", "PRODUCES", "CONSUMES", "SITE", "PLAN", "LOW", "HIGH", "LISTSTART", "SPLAT", "MODULO", "$start", "program", "statements", "epp_expression", "syntactic_statements", "syntactic_statement", "assignment", "relationship", "argument", "hashpair", "arguments", "resource", "expression", "attribute_operations", "additional_resource_bodies", "resource_bodies", "endsemi", "endcomma", "resource_body", "primary_expression", "capability_mapping", "call_function_expression", "bracketed_expression", "selector_entries", "access_args", "access_arg", "expressions", "variable", "call_method_with_lambda_expression", "collection_expression", "case_expression", "if_expression", "unless_expression", "definition_expression", "application_expression", "hostclass_expression", "plan_expression", "node_definition_expression", "site_definition_expression", "epp_render_expression", "function_definition", "type_alias", "type_definition", "reserved_word", "array", "hash", "regex", "quotedtext", "type", "call_function_start", "lambda", "call_method_expression", "named_access", "lambda_parameter_list", "opt_return_type", "lambda_rest", "parameters", "if_part", "else", "unless_else", "case_options", "case_option", "options_statements", "nil", "selector_entry", "selector_entry_list", "collect_query", "optional_query", "attribute_operation", "attribute_name", "keyword", "classname", "parameter_list", "opt_statements", "capability_kw", "stacked_classname", "classparent", "classnameordefault", "hostnames", "nodeparent", "hostname", "dotted_name", "name_or_number", "parameter", "untyped_parameter", "typed_parameter", "regular_parameter", "splat_parameter", "parameter_type", "type_alias_lhs", "optional_statements", "collection_entries", "hashpairs", "hash_entry", "collection_entry", "collection_entry_keyword", "string", "dq_string", "heredoc", "dqpre", "dqrval", "dqpost", "dqmid", "text_expression", "dqtail", "sublocated_text", "epp_parameters_list", "epp_end" ] Racc_debug_parser = false ##### State transition tables end ##### # reduce 0 omitted module_eval(<<'.,.,', 'egrammar.ra', 68) def _reduce_1(val, _values, result) result = create_program(Factory.block_or_expression(val[0])) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 69) def _reduce_2(val, _values, result) result = create_program(val[0]) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 70) def _reduce_3(val, _values, result) result = create_empty_program result end .,., module_eval(<<'.,.,', 'egrammar.ra', 74) def _reduce_4(val, _values, result) result = transform_calls(val[0]) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 81) def _reduce_5(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 82) def _reduce_6(val, _values, result) result = val[0].push val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 83) def _reduce_7(val, _values, result) result = val[0].push val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 90) def _reduce_8(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 91) def _reduce_9(val, _values, result) result = aryfy(val[0]).push(val[1]).push(val[2]) result end .,., # reduce 10 omitted module_eval(<<'.,.,', 'egrammar.ra', 96) def _reduce_11(val, _values, result) result = val[0].set(val[2]) ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 97) def _reduce_12(val, _values, result) result = val[0].plus_set(val[2]) ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 98) def _reduce_13(val, _values, result) result = val[0].minus_set(val[2]); loc result, val[1] result end .,., # reduce 14 omitted module_eval(<<'.,.,', 'egrammar.ra', 103) def _reduce_15(val, _values, result) result = Factory.HASH_UNFOLDED([val[0]]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 107) def _reduce_16(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 108) def _reduce_17(val, _values, result) result = Factory.ARGUMENTS(val[0], val[2]) result end .,., # reduce 18 omitted module_eval(<<'.,.,', 'egrammar.ra', 112) def _reduce_19(val, _values, result) result = val[0].relop(val[1][:value], val[2]); loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 113) def _reduce_20(val, _values, result) result = val[0].relop(val[1][:value], val[2]); loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 114) def _reduce_21(val, _values, result) result = val[0].relop(val[1][:value], val[2]); loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 115) def _reduce_22(val, _values, result) result = val[0].relop(val[1][:value], val[2]); loc result, val[1] result end .,., # reduce 23 omitted module_eval(<<'.,.,', 'egrammar.ra', 124) def _reduce_24(val, _values, result) result = val[1] unless Factory.set_resource_form(result, 'virtual') # This is equivalent to a syntax error - additional semantic restrictions apply error val[0], "Virtual (@) can only be applied to a Resource Expression" end # relocate the result loc result, val[0], val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 135) def _reduce_25(val, _values, result) result = val[1] unless Factory.set_resource_form(result, 'exported') # This is equivalent to a syntax error - additional semantic restrictions apply error val[0], "Exported (@@) can only be applied to a Resource Expression" end # relocate the result loc result, val[0], val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 146) def _reduce_26(val, _values, result) bodies = [Factory.RESOURCE_BODY(val[2], val[4])] + val[5] result = Factory.RESOURCE(val[0], bodies) loc result, val[0], val[6] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 153) def _reduce_27(val, _values, result) result = Factory.RESOURCE(Factory.fqn(token_text(val[0])), val[2]) loc result, val[0], val[4] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 162) def _reduce_28(val, _values, result) result = case Factory.resource_shape(val[0]) when :resource, :class # This catches deprecated syntax. # If the attribute operations does not include +>, then the found expression # is actually a LEFT followed by LITERAL_HASH # unless tmp = transform_resource_wo_title(val[0], val[2], val[1], val[4]) error val[1], "Syntax error resource body without title or hash with +>" end tmp when :defaults Factory.RESOURCE_DEFAULTS(val[0], val[2]) when :override # This was only done for override in original - TODO should it be here at all Factory.RESOURCE_OVERRIDE(val[0], val[2]) else error val[0], "Expression is not valid as a resource, resource-default, or resource-override" end loc result, val[0], val[4] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 184) def _reduce_29(val, _values, result) result = Factory.RESOURCE_BODY(val[0], val[2]) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 187) def _reduce_30(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 188) def _reduce_31(val, _values, result) result = val[0].push val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 194) def _reduce_32(val, _values, result) result = [] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 195) def _reduce_33(val, _values, result) result = [] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 196) def _reduce_34(val, _values, result) result = val[2] result end .,., # reduce 35 omitted # reduce 36 omitted # reduce 37 omitted # reduce 38 omitted module_eval(<<'.,.,', 'egrammar.ra', 205) def _reduce_39(val, _values, result) result = val[0].in val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 206) def _reduce_40(val, _values, result) result = val[0] =~ val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 207) def _reduce_41(val, _values, result) result = val[0].mne val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 208) def _reduce_42(val, _values, result) result = val[0] + val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 209) def _reduce_43(val, _values, result) result = val[0] - val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 210) def _reduce_44(val, _values, result) result = val[0] / val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 211) def _reduce_45(val, _values, result) result = val[0] * val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 212) def _reduce_46(val, _values, result) result = val[0] % val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 213) def _reduce_47(val, _values, result) result = val[0] << val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 214) def _reduce_48(val, _values, result) result = val[0] >> val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 215) def _reduce_49(val, _values, result) result = val[1].minus ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 216) def _reduce_50(val, _values, result) result = val[1].unfold ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 217) def _reduce_51(val, _values, result) result = val[0].ne val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 218) def _reduce_52(val, _values, result) result = val[0].eq val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 219) def _reduce_53(val, _values, result) result = val[0] > val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 220) def _reduce_54(val, _values, result) result = val[0] >= val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 221) def _reduce_55(val, _values, result) result = val[0] < val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 222) def _reduce_56(val, _values, result) result = val[0] <= val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 223) def _reduce_57(val, _values, result) result = val[1].not ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 224) def _reduce_58(val, _values, result) result = val[0].and val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 225) def _reduce_59(val, _values, result) result = val[0].or val[2] ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 226) def _reduce_60(val, _values, result) result = val[0].select(*val[2]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 227) def _reduce_61(val, _values, result) result = val[1].paren ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 228) def _reduce_62(val, _values, result) result = val[1].paren ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 231) def _reduce_63(val, _values, result) result = val[0].access(val[2]); loc result, val[0], val[4] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 234) def _reduce_64(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 235) def _reduce_65(val, _values, result) result = Factory.ARGUMENTS(val[0], val[2]) result end .,., # reduce 66 omitted module_eval(<<'.,.,', 'egrammar.ra', 239) def _reduce_67(val, _values, result) result = Factory.HASH_UNFOLDED([val[0]]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 248) def _reduce_68(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 249) def _reduce_69(val, _values, result) result = val[0].push(val[2]) result end .,., # reduce 70 omitted # reduce 71 omitted # reduce 72 omitted # reduce 73 omitted # reduce 74 omitted # reduce 75 omitted # reduce 76 omitted # reduce 77 omitted # reduce 78 omitted # reduce 79 omitted # reduce 80 omitted # reduce 81 omitted # reduce 82 omitted # reduce 83 omitted # reduce 84 omitted # reduce 85 omitted # reduce 86 omitted # reduce 87 omitted # reduce 88 omitted # reduce 89 omitted # reduce 90 omitted # reduce 91 omitted module_eval(<<'.,.,', 'egrammar.ra', 274) def _reduce_92(val, _values, result) result = Factory.NUMBER(val[0][:value]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 275) def _reduce_93(val, _values, result) result = Factory.literal(val[0][:value]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 276) def _reduce_94(val, _values, result) result = Factory.literal(:default) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 277) def _reduce_95(val, _values, result) result = Factory.literal(:undef) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 278) def _reduce_96(val, _values, result) result = Factory.QNAME_OR_NUMBER(val[0][:value]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 287) def _reduce_97(val, _values, result) result = Factory.CALL_NAMED(val[0], true, val[1]) loc result, val[0], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 291) def _reduce_98(val, _values, result) result = Factory.CALL_NAMED(val[0], true, []) loc result, val[0], val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 295) def _reduce_99(val, _values, result) result = Factory.CALL_NAMED(val[0], true, val[1]) loc result, val[0], val[4] result.lambda = val[4] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 300) def _reduce_100(val, _values, result) result = Factory.CALL_NAMED(val[0], true, []) loc result, val[0], val[2] result.lambda = val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 306) def _reduce_101(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 307) def _reduce_102(val, _values, result) result = Factory.QNAME(val[0][:value]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 312) def _reduce_103(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 313) def _reduce_104(val, _values, result) result = val[0]; val[0].lambda = val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 316) def _reduce_105(val, _values, result) result = Factory.CALL_METHOD(val[0], val[2]); loc result, val[1], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 317) def _reduce_106(val, _values, result) result = Factory.CALL_METHOD(val[0], []); loc result, val[1], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 318) def _reduce_107(val, _values, result) result = Factory.CALL_METHOD(val[0], []); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 322) def _reduce_108(val, _values, result) result = val[0].dot(Factory.fqn(val[2][:value])) loc result, val[1], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 326) def _reduce_109(val, _values, result) result = val[0].dot(Factory.fqn(val[2][:value])) loc result, val[1], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 334) def _reduce_110(val, _values, result) result = Factory.LAMBDA(val[0][:value], val[2][:value], val[1]) loc result, val[0][:start], val[2][:end] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 339) def _reduce_111(val, _values, result) result = {:end => val[2], :value =>val[1] } result end .,., module_eval(<<'.,.,', 'egrammar.ra', 340) def _reduce_112(val, _values, result) result = {:end => val[1], :value => nil } result end .,., module_eval(<<'.,.,', 'egrammar.ra', 344) def _reduce_113(val, _values, result) result = {:start => val[0], :value => [] } result end .,., module_eval(<<'.,.,', 'egrammar.ra', 345) def _reduce_114(val, _values, result) result = {:start => val[0], :value => val[1] } result end .,., module_eval(<<'.,.,', 'egrammar.ra', 353) def _reduce_115(val, _values, result) result = val[1] loc(result, val[0], val[1]) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 360) def _reduce_116(val, _values, result) result = Factory.IF(val[0], Factory.block_or_expression(val[2], val[1], val[3]), val[4]) loc(result, val[0], (val[4] ? val[4] : val[3])) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 364) def _reduce_117(val, _values, result) result = Factory.IF(val[0], nil, val[3]) loc(result, val[0], (val[3] ? val[3] : val[2])) result end .,., # reduce 118 omitted module_eval(<<'.,.,', 'egrammar.ra', 372) def _reduce_119(val, _values, result) result = val[1] loc(result, val[0], val[1]) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 376) def _reduce_120(val, _values, result) result = Factory.block_or_expression(val[2], val[1], val[3]) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 379) def _reduce_121(val, _values, result) result = nil # don't think a nop is needed here either result end .,., module_eval(<<'.,.,', 'egrammar.ra', 386) def _reduce_122(val, _values, result) result = Factory.UNLESS(val[1], Factory.block_or_expression(val[3], val[2], val[4]), val[5]) loc result, val[0], val[4] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 390) def _reduce_123(val, _values, result) result = Factory.UNLESS(val[1], nil, val[4]) loc result, val[0], val[4] result end .,., # reduce 124 omitted module_eval(<<'.,.,', 'egrammar.ra', 400) def _reduce_125(val, _values, result) result = Factory.block_or_expression(val[2], val[1], val[3]) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 403) def _reduce_126(val, _values, result) result = nil # don't think a nop is needed here either result end .,., module_eval(<<'.,.,', 'egrammar.ra', 410) def _reduce_127(val, _values, result) result = Factory.CASE(val[1], *val[3]) loc result, val[0], val[4] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 416) def _reduce_128(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 417) def _reduce_129(val, _values, result) result = val[0].push val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 422) def _reduce_130(val, _values, result) result = Factory.WHEN(val[0], val[3]); loc result, val[1], val[4] result end .,., # reduce 131 omitted # reduce 132 omitted # reduce 133 omitted module_eval(<<'.,.,', 'egrammar.ra', 438) def _reduce_134(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 443) def _reduce_135(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 444) def _reduce_136(val, _values, result) result = val[0].push val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 449) def _reduce_137(val, _values, result) result = Factory.MAP(val[0], val[2]) ; loc result, val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 459) def _reduce_138(val, _values, result) result = Factory.COLLECT(val[0], val[1], val[3]) loc result, val[0], val[5] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 463) def _reduce_139(val, _values, result) result = Factory.COLLECT(val[0], val[1], []) loc result, val[0], val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 468) def _reduce_140(val, _values, result) result = Factory.VIRTUAL_QUERY(val[1]) ; loc result, val[0], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 469) def _reduce_141(val, _values, result) result = Factory.EXPORTED_QUERY(val[1]) ; loc result, val[0], val[2] result end .,., # reduce 142 omitted # reduce 143 omitted module_eval(<<'.,.,', 'egrammar.ra', 478) def _reduce_144(val, _values, result) result = [] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 479) def _reduce_145(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 480) def _reduce_146(val, _values, result) result = val[0].push(val[2]) result end .,., # reduce 147 omitted # reduce 148 omitted module_eval(<<'.,.,', 'egrammar.ra', 495) def _reduce_149(val, _values, result) result = Factory.ATTRIBUTE_OP(val[0][:value], '=>', val[2]) loc result, val[0], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 499) def _reduce_150(val, _values, result) result = Factory.ATTRIBUTE_OP(val[0][:value], '+>', val[2]) loc result, val[0], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 503) def _reduce_151(val, _values, result) result = Factory.ATTRIBUTES_OP(val[2]) ; loc result, val[0], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 512) def _reduce_152(val, _values, result) definition = Factory.DEFINITION(classname(val[1][:value]), val[2], val[4]) loc(definition, val[0], val[5]) result = add_definition(definition) # New lexer does not keep track of this, this is done in validation if @lexer.respond_to?(:'indefine=') @lexer.indefine = false end result end .,., module_eval(<<'.,.,', 'egrammar.ra', 524) def _reduce_153(val, _values, result) definition = Factory.APPLICATION(classname(val[1][:value]), val[2], val[4]) loc(definition, val[0], val[5]) result = add_definition(definition) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 531) def _reduce_154(val, _values, result) result = Factory.CAPABILITY_MAPPING(val[1][:value], Factory.QREF(classname(val[0][:value])), classname(val[2][:value]), val[4]) loc result, val[0], val[6] add_mapping(result) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 538) def _reduce_155(val, _values, result) result = Factory.CAPABILITY_MAPPING(val[1][:value], val[0], classname(val[2][:value]), val[4]) loc result, val[0], val[6] add_mapping(result) result end .,., # reduce 156 omitted # reduce 157 omitted module_eval(<<'.,.,', 'egrammar.ra', 552) def _reduce_158(val, _values, result) # Remove this plan's name from the namestack as all nested plans have been parsed namepop definition = Factory.PLAN(classname(val[1][:value]), val[2], val[4]) loc(definition, val[0], val[5]) result = add_definition(definition) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 565) def _reduce_159(val, _values, result) # Remove this class' name from the namestack as all nested classes have been parsed namepop definition = Factory.HOSTCLASS(classname(val[1][:value]), val[2], token_text(val[3]), val[5]) loc(definition, val[0], val[6]) result = add_definition(definition) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 576) def _reduce_160(val, _values, result) namestack(val[0][:value]) ; result = val[0] result end .,., # reduce 161 omitted # reduce 162 omitted # reduce 163 omitted module_eval(<<'.,.,', 'egrammar.ra', 585) def _reduce_164(val, _values, result) result = val[1] result end .,., # reduce 165 omitted # reduce 166 omitted module_eval(<<'.,.,', 'egrammar.ra', 602) def _reduce_167(val, _values, result) definition = Factory.SITE(val[2]) loc(definition, val[0], val[3]) result = add_definition(definition) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 607) def _reduce_168(val, _values, result) definition = Factory.SITE(nil) loc(definition, val[0], val[2]) result = add_definition(definition) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 618) def _reduce_169(val, _values, result) definition = Factory.NODE(val[1], val[3], val[5]) loc(definition, val[0], val[6]) result = add_definition(definition) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 623) def _reduce_170(val, _values, result) definition = Factory.NODE(val[1], val[3], nil) loc(definition, val[0], val[5]) result = add_definition(definition) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 634) def _reduce_171(val, _values, result) result = [result] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 635) def _reduce_172(val, _values, result) result = val[0].push(val[2]) result end .,., # reduce 173 omitted # reduce 174 omitted module_eval(<<'.,.,', 'egrammar.ra', 642) def _reduce_175(val, _values, result) result = Factory.literal(:default); loc result, val[0] result end .,., # reduce 176 omitted module_eval(<<'.,.,', 'egrammar.ra', 646) def _reduce_177(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 647) def _reduce_178(val, _values, result) result = Factory.concat(val[0], '.', val[2][:value]); loc result, val[0], val[2] result end .,., # reduce 179 omitted # reduce 180 omitted # reduce 181 omitted module_eval(<<'.,.,', 'egrammar.ra', 656) def _reduce_182(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 662) def _reduce_183(val, _values, result) definition = Factory.FUNCTION(val[1][:value], val[2], val[5], val[3]) loc(definition, val[0], val[6]) result = add_definition(definition) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 668) def _reduce_184(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 669) def _reduce_185(val, _values, result) result = val[1].access(val[3]) ; loc result, val[1], val[4] result end .,., # reduce 186 omitted # reduce 187 omitted # reduce 188 omitted # reduce 189 omitted module_eval(<<'.,.,', 'egrammar.ra', 679) def _reduce_190(val, _values, result) error val[0], "'class' keyword not allowed at this location" result end .,., module_eval(<<'.,.,', 'egrammar.ra', 680) def _reduce_191(val, _values, result) error val[0], "A quoted string is not valid as a name here" result end .,., module_eval(<<'.,.,', 'egrammar.ra', 684) def _reduce_192(val, _values, result) result = [] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 685) def _reduce_193(val, _values, result) result = [] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 686) def _reduce_194(val, _values, result) result = [] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 687) def _reduce_195(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 688) def _reduce_196(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 692) def _reduce_197(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 693) def _reduce_198(val, _values, result) result = val[0].push(val[2]) result end .,., # reduce 199 omitted # reduce 200 omitted # reduce 201 omitted # reduce 202 omitted module_eval(<<'.,.,', 'egrammar.ra', 705) def _reduce_203(val, _values, result) result = Factory.PARAM(val[0][:value], val[2]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 706) def _reduce_204(val, _values, result) result = Factory.PARAM(val[0][:value]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 709) def _reduce_205(val, _values, result) result = val[1]; val[1].captures_rest result end .,., module_eval(<<'.,.,', 'egrammar.ra', 712) def _reduce_206(val, _values, result) val[1].type_expr(val[0]) ; result = val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 715) def _reduce_207(val, _values, result) result = val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 716) def _reduce_208(val, _values, result) result = val[0].access(val[2]) ; loc result, val[0], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 721) def _reduce_209(val, _values, result) definition = Factory.TYPE_ASSIGNMENT(val[0], Factory.KEY_ENTRY(val[2], val[3])) loc(definition, val[0], val[3]) result = add_definition(definition) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 726) def _reduce_210(val, _values, result) definition = Factory.TYPE_ASSIGNMENT(val[0], val[2].access(val[4])) loc(definition, val[0], val[5]) result = add_definition(definition) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 731) def _reduce_211(val, _values, result) definition = Factory.TYPE_ASSIGNMENT(val[0], val[2]) loc(definition, val[0], val[2]) result = add_definition(definition) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 736) def _reduce_212(val, _values, result) definition = Factory.TYPE_ASSIGNMENT(val[0], val[2]) loc(definition, val[0], val[2]) result = add_definition(definition) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 741) def _reduce_213(val, _values, result) definition = Factory.TYPE_ASSIGNMENT(val[0], val[2]) loc(definition, val[0], val[4]) result = add_definition(definition) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 747) def _reduce_214(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 753) def _reduce_215(val, _values, result) definition = Factory.TYPE_DEFINITION(val[1][:value], nil, val[3]) loc(definition, val[0], val[4]) result = add_definition(definition) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 758) def _reduce_216(val, _values, result) definition = Factory.TYPE_DEFINITION(val[1][:value], val[3][:value], val[5]) loc(definition, val[0], val[6]) result = add_definition(definition) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 767) def _reduce_217(val, _values, result) fqn = Factory.fqn(val[0][:value]) loc(fqn, val[0]) fqn['offset'] += 1 fqn['length'] -= 1 result = fqn.var loc(result, val[0]) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 778) def _reduce_218(val, _values, result) result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 779) def _reduce_219(val, _values, result) result = Factory.RESERVED(val[0][:value]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 785) def _reduce_220(val, _values, result) result = Factory.LIST(val[1]); loc result, val[0], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 786) def _reduce_221(val, _values, result) result = Factory.literal([]) ; loc result, val[0], val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 787) def _reduce_222(val, _values, result) result = Factory.LIST(val[1]); loc result, val[0], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 788) def _reduce_223(val, _values, result) result = Factory.literal([]) ; loc result, val[0], val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 791) def _reduce_224(val, _values, result) result = Factory.HASH(val[1]); loc result, val[0], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 792) def _reduce_225(val, _values, result) result = Factory.HASH(val[1]); loc result, val[0], val[3] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 793) def _reduce_226(val, _values, result) result = Factory.literal({}) ; loc result, val[0], val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 796) def _reduce_227(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 797) def _reduce_228(val, _values, result) result = val[0].push val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 800) def _reduce_229(val, _values, result) result = Factory.KEY_ENTRY(val[0], val[2]); loc result, val[1] result end .,., # reduce 230 omitted module_eval(<<'.,.,', 'egrammar.ra', 804) def _reduce_231(val, _values, result) result = Factory.literal(val[0][:value]) ; loc result, val[0] result end .,., # reduce 232 omitted module_eval(<<'.,.,', 'egrammar.ra', 809) def _reduce_233(val, _values, result) result = Factory.literal(val[0][:value]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 812) def _reduce_234(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 813) def _reduce_235(val, _values, result) result = Factory.ARGUMENTS(val[0], val[2]) result end .,., # reduce 236 omitted # reduce 237 omitted # reduce 238 omitted # reduce 239 omitted # reduce 240 omitted # reduce 241 omitted # reduce 242 omitted # reduce 243 omitted # reduce 244 omitted module_eval(<<'.,.,', 'egrammar.ra', 830) def _reduce_245(val, _values, result) result = Factory.literal(val[0][:value]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 831) def _reduce_246(val, _values, result) result = Factory.literal(val[0][:value]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 833) def _reduce_247(val, _values, result) result = Factory.STRING(val[0], *val[1]) ; loc result, val[0], val[1][-1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 834) def _reduce_248(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 835) def _reduce_249(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 836) def _reduce_250(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 837) def _reduce_251(val, _values, result) result = [val[0]] + val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 838) def _reduce_252(val, _values, result) result = Factory.TEXT(val[0]) result end .,., module_eval(<<'.,.,', 'egrammar.ra', 841) def _reduce_253(val, _values, result) result = [val[0]] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 842) def _reduce_254(val, _values, result) result = [val[0]] + val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 845) def _reduce_255(val, _values, result) result = Factory.HEREDOC(val[0][:value], val[1]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 848) def _reduce_256(val, _values, result) result = Factory.SUBLOCATE(val[0], val[1]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 849) def _reduce_257(val, _values, result) result = Factory.SUBLOCATE(val[0], val[1]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 852) def _reduce_258(val, _values, result) result = Factory.EPP(val[1], val[2]); loc result, val[0] result end .,., # reduce 259 omitted # reduce 260 omitted module_eval(<<'.,.,', 'egrammar.ra', 859) def _reduce_261(val, _values, result) result = nil result end .,., module_eval(<<'.,.,', 'egrammar.ra', 860) def _reduce_262(val, _values, result) result = [] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 861) def _reduce_263(val, _values, result) result = val[1] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 864) def _reduce_264(val, _values, result) result = Factory.RENDER_STRING(val[0][:value]); loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 865) def _reduce_265(val, _values, result) result = Factory.RENDER_EXPR(val[1]); loc result, val[0], val[2] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 866) def _reduce_266(val, _values, result) result = Factory.RENDER_EXPR(Factory.block_or_expression(val[2], val[1], val[3])); loc result, val[0], val[4] result end .,., # reduce 267 omitted # reduce 268 omitted module_eval(<<'.,.,', 'egrammar.ra', 872) def _reduce_269(val, _values, result) result = Factory.QREF(val[0][:value]) ; loc result, val[0] result end .,., module_eval(<<'.,.,', 'egrammar.ra', 875) def _reduce_270(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., # reduce 271 omitted module_eval(<<'.,.,', 'egrammar.ra', 881) def _reduce_272(val, _values, result) result = nil result end .,., # reduce 273 omitted # reduce 274 omitted # reduce 275 omitted # reduce 276 omitted # reduce 277 omitted # reduce 278 omitted # reduce 279 omitted # reduce 280 omitted # reduce 281 omitted # reduce 282 omitted # reduce 283 omitted # reduce 284 omitted # reduce 285 omitted # reduce 286 omitted # reduce 287 omitted # reduce 288 omitted # reduce 289 omitted # reduce 290 omitted # reduce 291 omitted # reduce 292 omitted # reduce 293 omitted # reduce 294 omitted # reduce 295 omitted # reduce 296 omitted module_eval(<<'.,.,', 'egrammar.ra', 912) def _reduce_297(val, _values, result) result = nil result end .,., def _reduce_none(val, _values, result) val[0] end end # class Parser end # module Parser end # module Pops end # module Puppet ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/parser/epp_support.rb�������������������������������������������������0000644�0052762�0001160�00000023113�13417161721�022257� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Parser # This module is an integral part of the Lexer. # It handles scanning of EPP (Embedded Puppet), a form of string/expression interpolation similar to ERB. # require 'strscan' module EppSupport TOKEN_RENDER_STRING = [:RENDER_STRING, nil, 0] TOKEN_RENDER_EXPR = [:RENDER_EXPR, nil, 0] # Scans all of the content and returns it in an array # Note that the terminating [false, false] token is included in the result. # def fullscan_epp result = [] scan_epp {|token, value| result.push([token, value]) } result end # A block must be passed to scan. It will be called with two arguments, a symbol for the token, # and an instance of LexerSupport::TokenValue # PERFORMANCE NOTE: The TokenValue is designed to reduce the amount of garbage / temporary data # and to only convert the lexer's internal tokens on demand. It is slightly more costly to create an # instance of a class defined in Ruby than an Array or Hash, but the gain is much bigger since transformation # logic is avoided for many of its members (most are never used (e.g. line/pos information which is only of # value in general for error messages, and for some expressions (which the lexer does not know about). # def scan_epp # PERFORMANCE note: it is faster to access local variables than instance variables. # This makes a small but notable difference since instance member access is avoided for # every token in the lexed content. # scn = @scanner ctx = @lexing_context queue = @token_queue lex_error(Issues::EPP_INTERNAL_ERROR, :error => 'No string or file given to lexer to process.') unless scn ctx[:epp_mode] = :text enqueue_completed([:EPP_START, nil, 0], 0) interpolate_epp # This is the lexer's main loop until queue.empty? && scn.eos? do if token = queue.shift || lex_token yield [ ctx[:after] = token[0], token[1] ] end end if ctx[:epp_open_position] lex_error(Issues::EPP_UNBALANCED_TAG, {}, ctx[:epp_position]) end # Signals end of input yield [false, false] end def interpolate_epp(skip_leading=false) scn = @scanner ctx = @lexing_context eppscanner = EppScanner.new(scn) before = scn.pos s = eppscanner.scan(skip_leading) case eppscanner.mode when :text # Should be at end of scan, or something is terribly wrong unless @scanner.eos? lex_error(Issues::EPP_INTERNAL_ERROR, :error => 'template scanner returns text mode and is not and end of input') end if s # s may be nil if scanned text ends with an epp tag (i.e. no trailing text). enqueue_completed([:RENDER_STRING, s, scn.pos - before], before) end ctx[:epp_open_position] = nil # do nothing else, scanner is at the end when :error lex_error(eppscanner.issue) when :epp # It is meaningless to render empty string segments, and it is harmful to do this at # the start of the scan as it prevents specification of parameters with <%- ($x, $y) -%> # if s && s.length > 0 enqueue_completed([:RENDER_STRING, s, scn.pos - before], before) end # switch epp_mode to general (embedded) pp logic (non rendered result) ctx[:epp_mode] = :epp ctx[:epp_open_position] = scn.pos when :expr # It is meaningless to render an empty string segment if s && s.length > 0 enqueue_completed([:RENDER_STRING, s, scn.pos - before], before) end enqueue_completed(TOKEN_RENDER_EXPR, before) # switch mode to "epp expr interpolation" ctx[:epp_mode] = :expr ctx[:epp_open_position] = scn.pos else lex_error(Issues::EPP_INTERNAL_ERROR, :error => "Unknown mode #{eppscanner.mode} returned by template scanner") end nil end # A scanner specialized in processing text with embedded EPP (Embedded Puppet) tags. # The scanner is initialized with a StringScanner which it mutates as scanning takes place. # The intent is to use one instance of EppScanner per wanted scan, and this instance represents # the state after the scan. # # @example Sample usage # a = "some text <% pp code %> some more text" # scan = StringScanner.new(a) # eppscan = EppScanner.new(scan) # str = eppscan.scan # eppscan.mode # => :epp # eppscan.lines # => 0 # eppscan # # The scanner supports # * scanning text until <%, <%-, <%= # * while scanning text: # * tokens <%% and %%> are translated to <% and %>, respectively, and is returned as text. # * tokens <%# and %> (or ending with -%>) and the enclosed text is a comment and is not included in the returned text # * text following a comment that ends with -%> gets trailing whitespace (up to and including a line break) trimmed # and this whitespace is not included in the returned text. # * The continuation {#mode} is set to one of: # * `:epp` - for a <% token # * `:expr` - for a <%= token # * `:text` - when there was no continuation mode (e.g. when input ends with text) # * ':error` - if the tokens are unbalanced (reaching the end without a closing matching token). An error message # is then also available via the method {#message}. # # Note that the intent is to use this specialized scanner to scan the text parts, when continuation mode is `:epp` or `:expr` # the pp lexer should advance scanning (using the string scanner) until it reaches and consumes a `-%>` or '%>´ token. If it # finds a `-%> token it should pass this on as a `skip_leading` parameter when it performs the next {#scan}. # class EppScanner # The original scanner used by the lexer/container using EppScanner attr_reader :scanner # The resulting mode after the scan. # The mode is one of `:text` (the initial mode), `:epp` embedded code (no output), `:expr` (embedded # expression), or `:error` # attr_reader :mode # An error issue if `mode == :error`, `nil` otherwise. attr_reader :issue # If the first scan should skip leading whitespace (typically detected by the pp lexer when the # pp mode end-token is found (i.e. `-%>`) and then passed on to the scanner. # attr_reader :skip_leading # Creates an EppScanner based on a StringScanner that represents the state where EppScanner should start scanning. # The given scanner will be mutated (i.e. position moved) to reflect the EppScanner's end state after a scan. # def initialize(scanner) @scanner = scanner end # Here for backwards compatibility. # @deprecated Use issue instead # @return [String] the issue message def message @issue.nil? ? nil : @issue.format end # Scans from the current position in the configured scanner, advances this scanner's position until the end # of the input, or to the first position after a mode switching token (`<%`, `<%-` or `<%=`). Number of processed # lines and continuation mode can be obtained via {#lines}, and {#mode}. # # @return [String, nil] the scanned and processed text, or nil if at the end of the input. # def scan(skip_leading=false) @mode = :text @skip_leading = skip_leading return nil if scanner.eos? s = "" until scanner.eos? part = @scanner.scan_until(/(<%)|\z/) if @skip_leading part.sub!(/^[ \t]*\r?(?:\n|\z)?/,'') @skip_leading = false end # The spec for %%> is to transform it into a literal %>. This is done here, as %%> otherwise would go # undetected in text mode. (i.e. it is not really necessary to escape %> with %%> in text mode unless # adding checks stating that a literal %> is illegal in text (unbalanced). # part.gsub!(/%%>/, '%>') s += part case @scanner.peek(1) when "" # at the end # if s ends with <% then this is an error (unbalanced <% %>) if s.end_with? "<%" @mode = :error @issue = Issues::EPP_UNBALANCED_EXPRESSION end return s when "-" # trim trailing whitespace on same line from accumulated s # return text and signal switch to pp mode @scanner.getch # drop the - s.sub!(/[ \t]*<%\z/, '') @mode = :epp return s when "%" # verbatim text # keep the scanned <%, and continue scanning after skipping one % # (i.e. do nothing here) @scanner.getch # drop the % to get a literal <% in the output when "=" # expression # return text and signal switch to expression mode # drop the scanned <%, and skip past -%>, or %>, but also skip %%> @scanner.getch # drop the = s.slice!(-2..-1) @mode = :expr return s when "#" # template comment # drop the scanned <%, and skip past -%>, or %>, but also skip %%> s.slice!(-2..-1) # unless there is an immediate termination i.e. <%#%> scan for the next %> that is not # preceded by a % (i.e. skip %%>) part = scanner.scan_until(/[^%]%>/) unless part @issue = Issues::EPP_UNBALANCED_COMMENT @mode = :error return s end # Always trim leading whitespace on the same line when there is a comment s.sub!(/[ \t]*\z/, '') @skip_leading = true if part.end_with?("-%>") # Continue scanning for more text else # Switch to pp after having removed the <% s.slice!(-2..-1) @mode = :epp return s end end end end end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/parser/lexer2.rb������������������������������������������������������0000644�0052762�0001160�00000065717�13417161721�021120� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The Lexer is responsible for turning source text into tokens. # This version is a performance enhanced lexer (in comparison to the 3.x and earlier "future parser" lexer. # # Old returns tokens [:KEY, value, { locator = } # Could return [[token], locator] # or Token.new([token], locator) with the same API x[0] = token_symbol, x[1] = self, x[:key] = (:value, :file, :line, :pos) etc require 'strscan' require 'puppet/pops/parser/lexer_support' require 'puppet/pops/parser/heredoc_support' require 'puppet/pops/parser/interpolation_support' require 'puppet/pops/parser/epp_support' require 'puppet/pops/parser/slurp_support' module Puppet::Pops module Parser class Lexer2 include LexerSupport include HeredocSupport include InterpolationSupport include SlurpSupport include EppSupport # ALl tokens have three slots, the token name (a Symbol), the token text (String), and a token text length. # All operator and punctuation tokens reuse singleton arrays Tokens that require unique values create # a unique array per token. # # PEFORMANCE NOTES: # This construct reduces the amount of object that needs to be created for operators and punctuation. # The length is pre-calculated for all singleton tokens. The length is used both to signal the length of # the token, and to advance the scanner position (without having to advance it with a scan(regexp)). # TOKEN_LBRACK = [:LBRACK, '['.freeze, 1].freeze TOKEN_LISTSTART = [:LISTSTART, '['.freeze, 1].freeze TOKEN_RBRACK = [:RBRACK, ']'.freeze, 1].freeze TOKEN_LBRACE = [:LBRACE, '{'.freeze, 1].freeze TOKEN_RBRACE = [:RBRACE, '}'.freeze, 1].freeze TOKEN_SELBRACE = [:SELBRACE, '{'.freeze, 1].freeze TOKEN_LPAREN = [:LPAREN, '('.freeze, 1].freeze TOKEN_WSLPAREN = [:WSLPAREN, '('.freeze, 1].freeze TOKEN_RPAREN = [:RPAREN, ')'.freeze, 1].freeze TOKEN_EQUALS = [:EQUALS, '='.freeze, 1].freeze TOKEN_APPENDS = [:APPENDS, '+='.freeze, 2].freeze TOKEN_DELETES = [:DELETES, '-='.freeze, 2].freeze TOKEN_ISEQUAL = [:ISEQUAL, '=='.freeze, 2].freeze TOKEN_NOTEQUAL = [:NOTEQUAL, '!='.freeze, 2].freeze TOKEN_MATCH = [:MATCH, '=~'.freeze, 2].freeze TOKEN_NOMATCH = [:NOMATCH, '!~'.freeze, 2].freeze TOKEN_GREATEREQUAL = [:GREATEREQUAL, '>='.freeze, 2].freeze TOKEN_GREATERTHAN = [:GREATERTHAN, '>'.freeze, 1].freeze TOKEN_LESSEQUAL = [:LESSEQUAL, '<='.freeze, 2].freeze TOKEN_LESSTHAN = [:LESSTHAN, '<'.freeze, 1].freeze TOKEN_FARROW = [:FARROW, '=>'.freeze, 2].freeze TOKEN_PARROW = [:PARROW, '+>'.freeze, 2].freeze TOKEN_LSHIFT = [:LSHIFT, '<<'.freeze, 2].freeze TOKEN_LLCOLLECT = [:LLCOLLECT, '<<|'.freeze, 3].freeze TOKEN_LCOLLECT = [:LCOLLECT, '<|'.freeze, 2].freeze TOKEN_RSHIFT = [:RSHIFT, '>>'.freeze, 2].freeze TOKEN_RRCOLLECT = [:RRCOLLECT, '|>>'.freeze, 3].freeze TOKEN_RCOLLECT = [:RCOLLECT, '|>'.freeze, 2].freeze TOKEN_PLUS = [:PLUS, '+'.freeze, 1].freeze TOKEN_MINUS = [:MINUS, '-'.freeze, 1].freeze TOKEN_DIV = [:DIV, '/'.freeze, 1].freeze TOKEN_TIMES = [:TIMES, '*'.freeze, 1].freeze TOKEN_MODULO = [:MODULO, '%'.freeze, 1].freeze TOKEN_NOT = [:NOT, '!'.freeze, 1].freeze TOKEN_DOT = [:DOT, '.'.freeze, 1].freeze TOKEN_PIPE = [:PIPE, '|'.freeze, 1].freeze TOKEN_AT = [:AT , '@'.freeze, 1].freeze TOKEN_ATAT = [:ATAT , '@@'.freeze, 2].freeze TOKEN_COLON = [:COLON, ':'.freeze, 1].freeze TOKEN_COMMA = [:COMMA, ','.freeze, 1].freeze TOKEN_SEMIC = [:SEMIC, ';'.freeze, 1].freeze TOKEN_QMARK = [:QMARK, '?'.freeze, 1].freeze TOKEN_TILDE = [:TILDE, '~'.freeze, 1].freeze # lexed but not an operator in Puppet TOKEN_REGEXP = [:REGEXP, nil, 0].freeze TOKEN_IN_EDGE = [:IN_EDGE, '->'.freeze, 2].freeze TOKEN_IN_EDGE_SUB = [:IN_EDGE_SUB, '~>'.freeze, 2].freeze TOKEN_OUT_EDGE = [:OUT_EDGE, '<-'.freeze, 2].freeze TOKEN_OUT_EDGE_SUB = [:OUT_EDGE_SUB, '<~'.freeze, 2].freeze # Tokens that are always unique to what has been lexed TOKEN_STRING = [:STRING, nil, 0].freeze TOKEN_WORD = [:WORD, nil, 0].freeze TOKEN_DQPRE = [:DQPRE, nil, 0].freeze TOKEN_DQMID = [:DQPRE, nil, 0].freeze TOKEN_DQPOS = [:DQPRE, nil, 0].freeze TOKEN_NUMBER = [:NUMBER, nil, 0].freeze TOKEN_VARIABLE = [:VARIABLE, nil, 1].freeze TOKEN_VARIABLE_EMPTY = [:VARIABLE, ''.freeze, 1].freeze # HEREDOC has syntax as an argument. TOKEN_HEREDOC = [:HEREDOC, nil, 0].freeze # EPP_START is currently a marker token, may later get syntax TOKEN_EPPSTART = [:EPP_START, nil, 0].freeze TOKEN_EPPEND = [:EPP_END, '%>', 2].freeze TOKEN_EPPEND_TRIM = [:EPP_END_TRIM, '-%>', 3].freeze # This is used for unrecognized tokens, will always be a single character. This particular instance # is not used, but is kept here for documentation purposes. TOKEN_OTHER = [:OTHER, nil, 0] # Keywords are all singleton tokens with pre calculated lengths. # Booleans are pre-calculated (rather than evaluating the strings "false" "true" repeatedly. # KEYWORDS = { 'case' => [:CASE, 'case', 4], 'class' => [:CLASS, 'class', 5], 'default' => [:DEFAULT, 'default', 7], 'define' => [:DEFINE, 'define', 6], 'if' => [:IF, 'if', 2], 'elsif' => [:ELSIF, 'elsif', 5], 'else' => [:ELSE, 'else', 4], 'inherits' => [:INHERITS, 'inherits', 8], 'node' => [:NODE, 'node', 4], 'and' => [:AND, 'and', 3], 'or' => [:OR, 'or', 2], 'undef' => [:UNDEF, 'undef', 5], 'false' => [:BOOLEAN, false, 5], 'true' => [:BOOLEAN, true, 4], 'in' => [:IN, 'in', 2], 'unless' => [:UNLESS, 'unless', 6], 'function' => [:FUNCTION, 'function', 8], 'type' => [:TYPE, 'type', 4], 'attr' => [:ATTR, 'attr', 4], 'private' => [:PRIVATE, 'private', 7], 'application' => [:APPLICATION, 'application', 11], 'consumes' => [:CONSUMES, 'consumes', 8], 'produces' => [:PRODUCES, 'produces', 8], 'site' => [:SITE, 'site', 4], } KEYWORDS.each {|k,v| v[1].freeze; v.freeze } KEYWORDS.freeze # Reverse lookup of keyword name to string KEYWORD_NAMES = {} KEYWORDS.each {|k, v| KEYWORD_NAMES[v[0]] = k } KEYWORD_NAMES.freeze PATTERN_WS = %r{[[:blank:]\r]+} PATTERN_NON_WS = %r{\w+\b?} # The single line comment includes the line ending. PATTERN_COMMENT = %r{#.*\r?} PATTERN_MLCOMMENT = %r{/\*(.*?)\*/}m PATTERN_REGEX = %r{/[^/]*/} PATTERN_REGEX_END = %r{/} PATTERN_REGEX_A = %r{\A/} # for replacement to "" PATTERN_REGEX_Z = %r{/\Z} # for replacement to "" PATTERN_REGEX_ESC = %r{\\/} # for replacement to "/" # The 3x patterns: # PATTERN_CLASSREF = %r{((::){0,1}[A-Z][-\w]*)+} # PATTERN_NAME = %r{((::)?[a-z0-9][-\w]*)(::[a-z0-9][-\w]*)*} # The NAME and CLASSREF in 4x are strict. Each segment must start with # a letter a-z and may not contain dashes (\w includes letters, digits and _). # PATTERN_CLASSREF = %r{((::){0,1}[A-Z][\w]*)+} PATTERN_NAME = %r{^((::)?[a-z][\w]*)(::[a-z][\w]*)*$} PATTERN_BARE_WORD = %r{((?:::){0,1}(?:[a-z_](?:[\w-]*[\w])?))+} PATTERN_DOLLAR_VAR = %r{\$(::)?(\w+::)*\w+} PATTERN_NUMBER = %r{\b(?:0[xX][0-9A-Fa-f]+|0?\d+(?:\.\d+)?(?:[eE]-?\d+)?)\b} # PERFORMANCE NOTE: # Comparison against a frozen string is faster (than unfrozen). # STRING_BSLASH_SLASH = '\/'.freeze attr_reader :locator def initialize() @selector = { '.' => lambda { emit(TOKEN_DOT, @scanner.pos) }, ',' => lambda { emit(TOKEN_COMMA, @scanner.pos) }, '[' => lambda do before = @scanner.pos if (before == 0 || locator.string[locator.char_offset(before)-1,1] =~ /[[:blank:]\r\n]+/) emit(TOKEN_LISTSTART, before) else emit(TOKEN_LBRACK, before) end end, ']' => lambda { emit(TOKEN_RBRACK, @scanner.pos) }, '(' => lambda do before = @scanner.pos # If first on a line, or only whitespace between start of line and '(' # then the token is special to avoid being taken as start of a call. line_start = @lexing_context[:line_lexical_start] if before == line_start || @scanner.string.byteslice(line_start, before - line_start) =~ /\A[[:blank:]\r]+\Z/ emit(TOKEN_WSLPAREN, before) else emit(TOKEN_LPAREN, before) end end, ')' => lambda { emit(TOKEN_RPAREN, @scanner.pos) }, ';' => lambda { emit(TOKEN_SEMIC, @scanner.pos) }, '?' => lambda { emit(TOKEN_QMARK, @scanner.pos) }, '*' => lambda { emit(TOKEN_TIMES, @scanner.pos) }, '%' => lambda do scn = @scanner before = scn.pos la = scn.peek(2) if la[1] == '>' && @lexing_context[:epp_mode] scn.pos += 2 if @lexing_context[:epp_mode] == :expr enqueue_completed(TOKEN_EPPEND, before) end @lexing_context[:epp_mode] = :text interpolate_epp else emit(TOKEN_MODULO, before) end end, '{' => lambda do # The lexer needs to help the parser since the technology used cannot deal with # lookahead of same token with different precedence. This is solved by making left brace # after ? into a separate token. # @lexing_context[:brace_count] += 1 emit(if @lexing_context[:after] == :QMARK TOKEN_SELBRACE else TOKEN_LBRACE end, @scanner.pos) end, '}' => lambda do @lexing_context[:brace_count] -= 1 emit(TOKEN_RBRACE, @scanner.pos) end, # TOKENS @, @@, @( '@' => lambda do scn = @scanner la = scn.peek(2) if la[1] == '@' emit(TOKEN_ATAT, scn.pos) # TODO; Check if this is good for the grammar elsif la[1] == '(' heredoc else emit(TOKEN_AT, scn.pos) end end, # TOKENS |, |>, |>> '|' => lambda do scn = @scanner la = scn.peek(3) emit(la[1] == '>' ? (la[2] == '>' ? TOKEN_RRCOLLECT : TOKEN_RCOLLECT) : TOKEN_PIPE, scn.pos) end, # TOKENS =, =>, ==, =~ '=' => lambda do scn = @scanner la = scn.peek(2) emit(case la[1] when '=' TOKEN_ISEQUAL when '>' TOKEN_FARROW when '~' TOKEN_MATCH else TOKEN_EQUALS end, scn.pos) end, # TOKENS '+', '+=', and '+>' '+' => lambda do scn = @scanner la = scn.peek(2) emit(case la[1] when '=' TOKEN_APPENDS when '>' TOKEN_PARROW else TOKEN_PLUS end, scn.pos) end, # TOKENS '-', '->', and epp '-%>' (end of interpolation with trim) '-' => lambda do scn = @scanner la = scn.peek(3) before = scn.pos if @lexing_context[:epp_mode] && la[1] == '%' && la[2] == '>' scn.pos += 3 if @lexing_context[:epp_mode] == :expr enqueue_completed(TOKEN_EPPEND_TRIM, before) end interpolate_epp(:with_trim) else emit(case la[1] when '>' TOKEN_IN_EDGE when '=' TOKEN_DELETES else TOKEN_MINUS end, before) end end, # TOKENS !, !=, !~ '!' => lambda do scn = @scanner la = scn.peek(2) emit(case la[1] when '=' TOKEN_NOTEQUAL when '~' TOKEN_NOMATCH else TOKEN_NOT end, scn.pos) end, # TOKENS ~>, ~ '~' => lambda do scn = @scanner la = scn.peek(2) emit(la[1] == '>' ? TOKEN_IN_EDGE_SUB : TOKEN_TILDE, scn.pos) end, '#' => lambda { @scanner.skip(PATTERN_COMMENT); nil }, # TOKENS '/', '/*' and '/ regexp /' '/' => lambda do scn = @scanner la = scn.peek(2) if la[1] == '*' lex_error(Issues::UNCLOSED_MLCOMMENT) if scn.skip(PATTERN_MLCOMMENT).nil? nil else before = scn.pos # regexp position is a regexp, else a div if regexp_acceptable? && value = scn.scan(PATTERN_REGEX) # Ensure an escaped / was not matched while escaped_end(value) more = scn.scan_until(PATTERN_REGEX_END) return emit(TOKEN_DIV, before) unless more value << more end regex = value.sub(PATTERN_REGEX_A, '').sub(PATTERN_REGEX_Z, '').gsub(PATTERN_REGEX_ESC, '/') emit_completed([:REGEX, Regexp.new(regex), scn.pos-before], before) else emit(TOKEN_DIV, before) end end end, # TOKENS <, <=, <|, <<|, <<, <-, <~ '<' => lambda do scn = @scanner la = scn.peek(3) emit(case la[1] when '<' if la[2] == '|' TOKEN_LLCOLLECT else TOKEN_LSHIFT end when '=' TOKEN_LESSEQUAL when '|' TOKEN_LCOLLECT when '-' TOKEN_OUT_EDGE when '~' TOKEN_OUT_EDGE_SUB else TOKEN_LESSTHAN end, scn.pos) end, # TOKENS >, >=, >> '>' => lambda do scn = @scanner la = scn.peek(2) emit(case la[1] when '>' TOKEN_RSHIFT when '=' TOKEN_GREATEREQUAL else TOKEN_GREATERTHAN end, scn.pos) end, # TOKENS :, ::CLASSREF, ::NAME ':' => lambda do scn = @scanner la = scn.peek(3) before = scn.pos if la[1] == ':' # PERFORMANCE NOTE: This could potentially be speeded up by using a case/when listing all # upper case letters. Alternatively, the 'A', and 'Z' comparisons may be faster if they are # frozen. # la2 = la[2] if la2 >= 'A' && la2 <= 'Z' # CLASSREF or error value = scn.scan(PATTERN_CLASSREF) if value && scn.peek(2) != '::' after = scn.pos emit_completed([:CLASSREF, value.freeze, after-before], before) else # move to faulty position ('::<uc-letter>' was ok) scn.pos = scn.pos + 3 lex_error(Issues::ILLEGAL_FULLY_QUALIFIED_CLASS_REFERENCE) end else value = scn.scan(PATTERN_BARE_WORD) if value if value =~ PATTERN_NAME emit_completed([:NAME, value.freeze, scn.pos - before], before) else emit_completed([:WORD, value.freeze, scn.pos - before], before) end else # move to faulty position ('::' was ok) scn.pos = scn.pos + 2 lex_error(Issues::ILLEGAL_FULLY_QUALIFIED_NAME) end end else emit(TOKEN_COLON, before) end end, '$' => lambda do scn = @scanner before = scn.pos if value = scn.scan(PATTERN_DOLLAR_VAR) emit_completed([:VARIABLE, value[1..-1].freeze, scn.pos - before], before) else # consume the $ and let higher layer complain about the error instead of getting a syntax error emit(TOKEN_VARIABLE_EMPTY, before) end end, '"' => lambda do # Recursive string interpolation, 'interpolate' either returns a STRING token, or # a DQPRE with the rest of the string's tokens placed in the @token_queue interpolate_dq end, "'" => lambda do scn = @scanner before = scn.pos emit_completed([:STRING, slurp_sqstring.freeze, scn.pos - before], before) end, "\n" => lambda do # If heredoc_cont is in effect there are heredoc text lines to skip over # otherwise just skip the newline. # ctx = @lexing_context if ctx[:newline_jump] @scanner.pos = ctx[:newline_jump] ctx[:newline_jump] = nil else @scanner.pos += 1 end ctx[:line_lexical_start] = @scanner.pos nil end, '' => lambda { nil } # when the peek(1) returns empty } [ ' ', "\t", "\r" ].each { |c| @selector[c] = lambda { @scanner.skip(PATTERN_WS); nil } } [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'].each do |c| @selector[c] = lambda do scn = @scanner before = scn.pos value = scn.scan(PATTERN_NUMBER) if value length = scn.pos - before assert_numeric(value, before) emit_completed([:NUMBER, value.freeze, length], before) else invalid_number = scn.scan_until(PATTERN_NON_WS) if before > 1 after = scn.pos scn.pos = before - 1 if scn.peek(1) == '.' # preceded by a dot. Is this a bad decimal number then? scn.pos = before - 2 while scn.peek(1) =~ /^\d$/ invalid_number = nil before = scn.pos break if before == 0 scn.pos = scn.pos - 1 end end scn.pos = before invalid_number = scn.peek(after - before) unless invalid_number end assert_numeric(invalid_number, before) scn.pos = before + 1 lex_error(Issues::ILLEGAL_NUMBER, {:value => invalid_number}) end end end ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '_'].each do |c| @selector[c] = lambda do scn = @scanner before = scn.pos value = scn.scan(PATTERN_BARE_WORD) if value && value =~ PATTERN_NAME emit_completed(KEYWORDS[value] || @taskm_keywords[value] || [:NAME, value.freeze, scn.pos - before], before) elsif value emit_completed([:WORD, value.freeze, scn.pos - before], before) else # move to faulty position ([a-z_] was ok) scn.pos = scn.pos + 1 fully_qualified = scn.match?(/::/) if fully_qualified lex_error(Issues::ILLEGAL_FULLY_QUALIFIED_NAME) else lex_error(Issues::ILLEGAL_NAME_OR_BARE_WORD) end end end end ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'].each do |c| @selector[c] = lambda do scn = @scanner before = scn.pos value = @scanner.scan(PATTERN_CLASSREF) if value && @scanner.peek(2) != '::' emit_completed([:CLASSREF, value.freeze, scn.pos - before], before) else # move to faulty position ([A-Z] was ok) scn.pos = scn.pos + 1 lex_error(Issues::ILLEGAL_CLASS_REFERENCE) end end end @selector.default = lambda do # In case of unicode spaces of various kinds that are captured by a regexp, but not by the # simpler case expression above (not worth handling those special cases with better performance). scn = @scanner if scn.skip(PATTERN_WS) nil else # "unrecognized char" emit([:OTHER, scn.peek(0), 1], scn.pos) end end @selector.each { |k,v| k.freeze } @selector.freeze end # Determine if last char of value is escaped by a backslash def escaped_end(value) escaped = false if value.end_with?(STRING_BSLASH_SLASH) value[1...-1].each_codepoint do |cp| if cp == 0x5c # backslash escaped = !escaped else escaped = false end end end escaped end # Clears the lexer state (it is not required to call this as it will be garbage collected # and the next lex call (lex_string, lex_file) will reset the internal state. # def clear() # not really needed, but if someone wants to ensure garbage is collected as early as possible @scanner = nil @locator = nil @lexing_context = nil end # Convenience method, and for compatibility with older lexer. Use the lex_string instead which allows # passing the path to use without first having to call file= (which reads the file if it exists). # (Bad form to use overloading of assignment operator for something that is not really an assignment. Also, # overloading of = does not allow passing more than one argument). # def string=(string) lex_string(string, nil) end def lex_string(string, path=nil) initvars assert_not_bom(string) @scanner = StringScanner.new(string) @locator = Locator.locator(string, path) end # Lexes an unquoted string. # @param string [String] the string to lex # @param locator [Locator] the locator to use (a default is used if nil is given) # @param escapes [Array<String>] array of character strings representing the escape sequences to transform # @param interpolate [Boolean] whether interpolation of expressions should be made or not. # def lex_unquoted_string(string, locator, escapes, interpolate) initvars assert_not_bom(string) @scanner = StringScanner.new(string) @locator = locator || Locator.locator(string, '') @lexing_context[:escapes] = escapes || UQ_ESCAPES @lexing_context[:uq_slurp_pattern] = interpolate ? (escapes.include?('$') ? SLURP_UQ_PATTERN : SLURP_UQNE_PATTERN) : SLURP_ALL_PATTERN end # Convenience method, and for compatibility with older lexer. Use the lex_file instead. # (Bad form to use overloading of assignment operator for something that is not really an assignment). # def file=(file) lex_file(file) end # TODO: This method should not be used, callers should get the locator since it is most likely required to # compute line, position etc given offsets. # def file @locator ? @locator.file : nil end # Initializes lexing of the content of the given file. An empty string is used if the file does not exist. # def lex_file(file) initvars contents = Puppet::FileSystem.exist?(file) ? Puppet::FileSystem.read(file, :mode => 'rb', :encoding => 'utf-8') : '' assert_not_bom(contents) @scanner = StringScanner.new(contents.freeze) @locator = Locator.locator(contents, file) end def initvars @token_queue = [] # NOTE: additional keys are used; :escapes, :uq_slurp_pattern, :newline_jump, :epp_* @lexing_context = { :brace_count => 0, :after => nil, :line_lexical_start => 0 } # Use of --tasks introduces the new keyword 'plan' @taskm_keywords = Puppet[:tasks] ? { 'plan' => [:PLAN, 'plan', 4] }.freeze : EMPTY_HASH end # Scans all of the content and returns it in an array # Note that the terminating [false, false] token is included in the result. # def fullscan result = [] scan {|token| result.push(token) } result end # A block must be passed to scan. It will be called with two arguments, a symbol for the token, # and an instance of LexerSupport::TokenValue # PERFORMANCE NOTE: The TokenValue is designed to reduce the amount of garbage / temporary data # and to only convert the lexer's internal tokens on demand. It is slightly more costly to create an # instance of a class defined in Ruby than an Array or Hash, but the gain is much bigger since transformation # logic is avoided for many of its members (most are never used (e.g. line/pos information which is only of # value in general for error messages, and for some expressions (which the lexer does not know about). # def scan # PERFORMANCE note: it is faster to access local variables than instance variables. # This makes a small but notable difference since instance member access is avoided for # every token in the lexed content. # scn = @scanner lex_error_without_pos(Issues::NO_INPUT_TO_LEXER) unless scn ctx = @lexing_context queue = @token_queue selector = @selector scn.skip(PATTERN_WS) # This is the lexer's main loop until queue.empty? && scn.eos? do if token = queue.shift || selector[scn.peek(1)].call ctx[:after] = token[0] yield token end end # Signals end of input yield [false, false] end # This lexes one token at the current position of the scanner. # PERFORMANCE NOTE: Any change to this logic should be performance measured. # def lex_token @selector[@scanner.peek(1)].call end # Emits (produces) a token [:tokensymbol, TokenValue] and moves the scanner's position past the token # def emit(token, byte_offset) @scanner.pos = byte_offset + token[2] [token[0], TokenValue.new(token, byte_offset, @locator)] end # Emits the completed token on the form [:tokensymbol, TokenValue. This method does not alter # the scanner's position. # def emit_completed(token, byte_offset) [token[0], TokenValue.new(token, byte_offset, @locator)] end # Enqueues a completed token at the given offset def enqueue_completed(token, byte_offset) @token_queue << emit_completed(token, byte_offset) end # Allows subprocessors for heredoc etc to enqueue tokens that are tokenized by a different lexer instance # def enqueue(emitted_token) @token_queue << emitted_token end # Answers after which tokens it is acceptable to lex a regular expression. # PERFORMANCE NOTE: # It may be beneficial to turn this into a hash with default value of true for missing entries. # A case expression with literal values will however create a hash internally. Since a reference is # always needed to the hash, this access is almost as costly as a method call. # def regexp_acceptable? case @lexing_context[:after] # Ends of (potential) R-value generating expressions when :RPAREN, :RBRACK, :RRCOLLECT, :RCOLLECT false # End of (potential) R-value - but must be allowed because of case expressions # Called out here to not be mistaken for a bug. when :RBRACE true # Operands (that can be followed by DIV (even if illegal in grammar) when :NAME, :CLASSREF, :NUMBER, :STRING, :BOOLEAN, :DQPRE, :DQMID, :DQPOST, :HEREDOC, :REGEX, :VARIABLE, :WORD false else true end end end end end �������������������������������������������������puppet-5.5.10/lib/puppet/pops/patterns.rb�����������������������������������������������������������0000644�0052762�0001160�00000005033�13417161721�020244� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The Patterns module contains common regular expression patters for the Puppet DSL language module Puppet::Pops::Patterns # NUMERIC matches hex, octal, decimal, and floating point and captures several parts # 0 = entire matched number, leading and trailing whitespace and sign included # 1 = sign, +, - or nothing # 2 = entire numeric part # 3 = hexadecimal number # 4 = non hex integer portion, possibly with leading 0 (octal) # 5 = floating point part, starts with ".", decimals and optional exponent # # Thus, a hex number has group 3 value, an octal value has group 4 (if it starts with 0), and no group 3 # and a floating point value has group 4 and group 5. # NUMERIC = %r{\A[[:blank:]]*([-+]?)[[:blank:]]*((0[xX][0-9A-Fa-f]+)|(0?\d+)((?:\.\d+)?(?:[eE]-?\d+)?))[[:blank:]]*\z} # Special expression that tests if there is whitespace between sign and number. The expression is used # to strip such whitespace when normal Float or Integer conversion fails. WS_BETWEEN_SIGN_AND_NUMBER = %r{\A([+-])[[:blank:]]+(.*)\z} # ILLEGAL_P3_1_HOSTNAME matches if a hostname contains illegal characters. # This check does not prevent pathological names like 'a....b', '.....', "---". etc. ILLEGAL_HOSTNAME_CHARS = %r{[^-\w.]} # NAME matches a name the same way as the lexer. NAME = %r{\A((::)?[a-z]\w*)(::[a-z]\w*)*\z} # CLASSREF_EXT matches a class reference the same way as the lexer - i.e. the external source form # where each part must start with a capital letter A-Z. # CLASSREF_EXT = %r{\A((::){0,1}[A-Z][\w]*)+\z} # Same as CLASSREF_EXT but cannot start with '::' # CLASSREF_EXT_DECL = %r{\A[A-Z][\w]*(?:::[A-Z][\w]*)*\z} # CLASSREF matches a class reference the way it is represented internally in the # model (i.e. in lower case). # CLASSREF = %r{\A((::){0,1}[a-z][\w]*)+\z} # Same as CLASSREF but cannot start with '::' # CLASSREF_DECL = %r{\A[a-z][\w]*(?:::[a-z][\w]*)*\z} # DOLLAR_VAR matches a variable name including the initial $ character DOLLAR_VAR = %r{\$(::)?(\w+::)*\w+} # VAR_NAME matches the name part of a variable (The $ character is not included) # Note, that only the final segment may start with an underscore. # Note, regexp sensitive to backtracking VAR_NAME = %r{\A(?:::)?(?:[a-z]\w*::)*[a-z_]\w*\z} # PARAM_NAME matches the name part of a parameter (The $ character is not included) PARAM_NAME = %r{\A[a-z_]\w*\z} # A Numeric var name must be the decimal number 0, or a decimal number not starting with 0 NUMERIC_VAR_NAME = %r{\A(?:0|(?:[1-9][0-9]*))\z} end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/pn.rb�����������������������������������������������������������������0000644�0052762�0001160�00000010102�13417161721�017012� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module PN KEY_PATTERN = /^[A-Za-z_-][0-9A-Za-z_-]*$/ def pnError(message) raise ArgumentError, message end def as_call(name) Call.new(name, self) end def as_parameters [self] end def ==(o) eql?(o) end def to_s s = '' format(nil, s) s end def with_name(name) Entry.new(name, self) end def double_quote(str, bld) bld << '"' str.each_codepoint do |codepoint| case codepoint when 0x09 bld << '\\t' when 0x0a bld << '\\n' when 0x0d bld << '\\r' when 0x22 bld << '\\"' when 0x5c bld << '\\\\' else if codepoint < 0x20 bld << sprintf('\\o%3.3o', codepoint) elsif codepoint <= 0x7f bld << codepoint else bld << [codepoint].pack('U') end end end bld << '"' end def format_elements(elements, indent, b) elements.each_with_index do |e, i| if indent b << "\n" << indent.current elsif i > 0 b << ' ' end e.format(indent, b) end end class Indent attr_reader :current def initialize(indent = ' ', current = '') @indent = indent @current = current end def increase Indent.new(@indent, @current + @indent) end end class Call include PN attr_reader :name, :elements def initialize(name, *elements) @name = name @elements = elements end def [](idx) @elements[idx] end def as_call(name) Call.new(name, *@elements) end def as_parameters List.new(@elements) end def eql?(o) o.is_a?(Call) && @name == o.name && @elements == o.elements end def format(indent, b) b << '(' << @name if @elements.size > 0 b << ' ' unless indent format_elements(@elements, indent ? indent.increase : nil, b) end b << ')' end def to_data { '^' => [@name] + @elements.map { |e| e.to_data } } end end class Entry attr_reader :key, :value def initialize(key, value) @key = key @value = value end def eql?(o) o.is_a?(Entry) && @key == o.key && @value == o.value end alias == eql? end class List include PN attr_reader :elements def initialize(elements) @elements = elements end def [](idx) @elements[idx] end def as_call(name) Call.new(name, *@elements) end def as_parameters @elements end def eql?(o) o.is_a?(List) && @elements == o.elements end def format(indent, b) b << '[' format_elements(@elements, indent ? indent.increase : nil, b) unless @elements.empty? b << ']' end def to_data @elements.map { |e| e.to_data } end end class Literal include PN attr_reader :value def initialize(value) @value = value end def format(indent, b) if @value.nil? b << 'nil' elsif value.is_a?(String) double_quote(value, b) else b << value.to_s end end def eql?(o) o.is_a?(Literal) && @value == o.value end def to_data @value end end class Map include PN attr_reader :entries def initialize(entries) entries.each { |e| pnError("key #{e.key} does not conform to pattern /#{KEY_PATTERN.source}/)") unless e.key =~ KEY_PATTERN } @entries = entries end def eql?(o) o.is_a?(Map) && @entries == o.entries end def format(indent, b) local_indent = indent ? indent.increase : nil b << '{' @entries.each_with_index do |e,i| if indent b << "\n" << local_indent.current elsif i > 0 b << ' ' end b << ':' << e.key b << ' ' e.value.format(local_indent, b) end b << '}' end def to_data r = [] @entries.each { |e| r << e.key << e.value.to_data } { '#' => r } end end end end require_relative 'model/pn_transformer' require_relative 'parser/pn_parser' ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/resource/�������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�017712� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/resource/param.rb�����������������������������������������������������0000644�0052762�0001160�00000002507�13417161721�021336� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# An implementation of the interface Puppet::Resource # that adapts the 3.x compiler and catalog expectations on # a resource instance. This instance is backed by a # pcore representation of the resource type an instance of this # ruby class. # # This class must inherit from Puppet::Resource because of the # class expectations in existing logic. # # This implementation does not support # * setting 'strict' - strictness (must refer to an existing type) is always true # * does not support the indirector # # module Puppet::Pops module Resource class Param # This make this class instantiable from Puppet include Puppet::Pops::Types::PuppetObject def self.register_ptype(loader, ir) @ptype = Pcore::create_object_type(loader, ir, self, 'Puppet::Resource::Param', nil, { Types::KEY_TYPE => Types::PTypeType::DEFAULT, Types::KEY_NAME => Types::PStringType::NON_EMPTY, 'name_var' => { Types::KEY_TYPE => Types::PBooleanType::DEFAULT, Types::KEY_VALUE => false } }, EMPTY_HASH, [Types::KEY_NAME] ) end attr_reader :name attr_reader :type attr_reader :name_var def initialize(type, name, name_var = false) @type = type @name = name @name_var = name_var end def to_s name end def self._pcore_type @ptype end end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/resource/resource_type_impl.rb����������������������������������������0000644�0052762�0001160�00000024333�13417161721�024150� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require_relative 'param' module Puppet::Pops module Resource def self.register_ptypes(loader, ir) types = [Param, ResourceTypeImpl].map do |c| c.register_ptype(loader, ir) end types.each {|t| t.resolve(loader) } end class ResourceTypeImpl # Make instances of this class directly creatable from the Puppet Language # as object. # include Puppet::Pops::Types::PuppetObject # Instances of ResourceTypeImpl can be used as the type of a Puppet::Parser::Resource/Puppet::Resource when compiling # include Puppet::CompilableResourceType # Returns the Puppet Type for this instance. def self._pcore_type @ptype end def self.register_ptype(loader, ir) param_ref = Types::PTypeReferenceType.new('Puppet::Resource::Param') @ptype = Pcore::create_object_type(loader, ir, self, 'Puppet::Resource::ResourceType3', nil, { Types::KEY_NAME => Types::PStringType::NON_EMPTY, 'properties' => { Types::KEY_TYPE => Types::PArrayType.new(param_ref), Types::KEY_VALUE => EMPTY_ARRAY }, 'parameters' => { Types::KEY_TYPE => Types::PArrayType.new(param_ref), Types::KEY_VALUE => EMPTY_ARRAY }, 'title_patterns_hash' => { Types::KEY_TYPE => Types::POptionalType.new( Types::PHashType.new(Types::PRegexpType::DEFAULT, Types::PArrayType.new(Types::PStringType::NON_EMPTY))), Types::KEY_VALUE => nil }, 'isomorphic' => { Types::KEY_TYPE => Types::PBooleanType::DEFAULT, Types::KEY_VALUE => true }, 'capability' => { Types::KEY_TYPE => Types::PBooleanType::DEFAULT, Types::KEY_VALUE => false }, }, EMPTY_HASH, [Types::KEY_NAME] ) end def eql?(other) other.is_a?(Puppet::CompilableResourceType) && @name == other.name end alias == eql? # Compares this type against the given _other_ (type) and returns -1, 0, or +1 depending on the order. # @param other [Object] the object to compare against (produces nil, if not kind of Type} # @return [-1, 0, +1, nil] produces -1 if this type is before the given _other_ type, 0 if equals, and 1 if after. # Returns nil, if the given _other_ is not a kind of Type. # @see Comparable # def <=>(other) # Order is only maintained against other types, not arbitrary objects. # The natural order is based on the reference name used when comparing return nil unless other.is_a?(Puppet::CompilableResourceType) # against other type instances. self.ref <=> other.ref end METAPARAMS = [ :noop, :schedule, :audit, :loglevel, :alias, :tag, :require, :subscribe, :before, :notify, :stage, :export, :consume ].freeze # Speed up lookup METAPARAMSET = Set.new(METAPARAMS).freeze attr_reader :name attr_reader :properties attr_reader :parameters attr_reader :title_patterns_hash attr_reader :title_patterns def initialize(name, properties = EMPTY_ARRAY, parameters = EMPTY_ARRAY, title_patterns_hash = nil, isomorphic = true, capability = false) @name = name @properties = properties @parameters = parameters @title_patterns_hash = title_patterns_hash @isomorphic = isomorphic @capability = capability # Compute attributes hash # Compute key_names (possibly compound key if there are multiple name vars). @attributes = {} @key_attributes = [] # Name to kind of attribute @attr_types = {} # Add all meta params METAPARAMS.each {|p| @attr_types[p] = :meta } @property_set = Set.new(properties.map do |p| symname = p.name.to_sym @attributes[symname] = p @key_attributes << symname if p.name_var @attr_types[symname] = :property symname end).freeze @param_set = Set.new(parameters.map do |p| symname = p.name.to_sym @attributes[symname] = p @key_attributes << symname if p.name_var @attr_types[symname] = :param symname end).freeze # API for title patterns is [ [regexp, [ [ [sym, <lambda>], [sym, <lambda>] ] ] ] ] # Where lambdas are optional. This resource type impl does not support lambdas # Note that the pcore file has a simpler hashmap that is post processed here # since the structure must have Symbol instances for names which the .pp representation # does not deliver. # @title_patterns = case @key_attributes.length when 0 # TechDebt: The case of specifying title patterns when having no name vars is unspecified behavior in puppet # Here it is silently ignored. nil when 1 if @title_patterns_hash.nil? [ [ /(.*)/m, [ [@key_attributes.first] ] ] ] else # TechDebt: The case of having one namevar and an empty title patterns is unspecified behavior in puppet. # Here, it may lead to an empty map which may or may not trigger the wanted/expected behavior. # @title_patterns_hash.map { |k,v| [ k, v.map { |n| [ n.to_sym ] } ] } end else if @title_patterns_hash.nil? || @title_patterns_hash.empty? # TechDebt: While title patterns must be specified when more than one is used, they do not have # to match/set them all since some namevars can be omitted (to support the use case in # the 'package' type where 'provider' attribute is handled as part of the key without being # set from the title. # raise Puppet::DevError, _("you must specify title patterns when there are two or more key attributes") end @title_patterns_hash.nil? ? [] : @title_patterns_hash.map { |k,v| [ k, v.map { |n| [ n.to_sym] } ] } end end # Override CompilableResource inclusion def is_3x_ruby_plugin? false end def is_capability? @capability end # Answers if the parameter name is a parameter/attribute of this type # This is part of the Puppet::Type API # Check if used when compiling (it is triggered in an apply) # def valid_parameter?(name) @attributes.include?(name) || METAPARAMSET.include?(name) end # The type implementation of finish does a lot of things # * iterates over all parameters and calls post_compile on them if the parameter impl responds to post_compile # * validates the relationship parameters # # This implementation does nothing - it is assumed that the catalog is already validated # via the relationship validator (done late in the game). def finish() # Do nothing. end # This is called on a resource type # it performs tagging if it is a Class or Node. # It also ensure the parent type is in the catalog, but that is weird since # resource types cannot really inherit def instantiate_resource(scope, resource) # Do nothing because nothing is needed when compiling. # This is what the Puppet::Type implementation does # None of this should be needed # # Make sure our parent class has been evaluated, if we have one. # if parent && !scope.catalog.resource(resource.type, parent) # parent_type(scope).ensure_in_catalog(scope) # end # This will never happen # if ['Class', 'Node'].include? resource.type # scope.catalog.tag(*resource.tags) # end end # Being isomorphic in puppet means that the resource is managing a state # (as opposed to a resource like Exec that is a function, possibly with side effect. # In a Ruby implementation of a resource type, @isomorphic = false is used to turn # off isomorphism, it is true by default. # This is part of the Puppet::Type API. # def isomorphic? @isomorphic end # Produces the names of the attributes that make out the unique id of a resource # def key_attributes @key_attributes end # Gives a type a chance to issue deprecations for parameters. # @param title [String] the title of the resource of this type # @param attributes [Array<Param>] the set parameters in the resource instance def deprecate_params(title, attributes) # TODO: Current API somewhat unclear, if done at type level, or per # Param. end ####################### # UNSUPPORTED STUFF ####################### # Applications are not supported def application? false end ############################ # DON'T KNOW YET ############################ ################################################## # NEVER CALLED COMPILE SIDE FOR A COMPILATION ################################################## # Answers :property, :param or :meta depending on the type of the attribute # According to original version, this is called millions of times # and a cache is required. # @param name [Symbol] def attrtype(name) raise NotImplementedError, "attrtype() - returns the kind (:meta, :param, or :property) of the parameter" # @attr_types[name] end # Returns the implementation of a param/property/attribute - i.e. a Param class def attrclass(name) raise NotImplementedError, "attrclass() - returns the (param) class of the parameter" end # PROBABLY NOT USED WHEN COMPILING # Returns the names of all attributes in a defined order: # * all key attributes (the composite id) # * :provider if it is specified # * all properties # * all parameters # * meta parameters # def allattrs raise NotImplementedError, "allattrs() - return all attribute names in order - probably not used master side" # key_attributes | (parameters & [:provider]) | properties.collect { |property| property.name } | parameters | metaparams end # Sets "applies to host" def apply_to raise NotImplementedError, "apply_to() - probably used when selecting a provider (device/host support)" end def apply_to_host raise NotImplementedError, "apply_to_host() - probably used when selecting a provider (device/host support)" end def apply_to_device raise NotImplementedError, "apply_to_device() - probably used when selecting a provider (device/host support)" end def apply_to_all raise NotImplementedError, "apply_to_all() - probably used when selecting a provider (device/host support)" end def can_apply_to_target(target) raise NotImplementedError, "can_apply_to_target() - probably used when selecting a provider (device/host support)" end end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/resource/resource_type_set.pcore��������������������������������������0000644�0052762�0001160�00000001241�13417161721�024500� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������type Puppet::Resource::Param = Object[{ attributes => { type => Type, name => String[1], name_var => { type => Boolean, value => false } }, equality => [name], }] type Puppet::Resource::ResourceType3 = Object[{ attributes => { name => String[1], properties => { type => Array[Puppet::Resource::Param], value = []}, parameters => { type => Array[Puppet::Resource::Param], value = []}, title_patterns => { type => Optional[Hash[Regexp, Array[String[1]]], value => nil }, isomorphic => { type => Boolean, value => true }, capability => { type => Boolean, value => false }, }, equality => [name], }] ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/semantic_error.rb�����������������������������������������������������0000644�0052762�0001160�00000001362�13417161721�021421� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Error that is used to raise an Issue. See {Puppet::Pops::Issues}. # class Puppet::Pops::SemanticError < RuntimeError attr_accessor :issue attr_accessor :semantic attr_accessor :options # @param issue [Puppet::Pops::Issues::Issue] the issue describing the severity and message # @param semantic [Puppet::Pops::Model::Locatable, nil] the expression causing the failure, or nil if unknown # @param options [Hash] an options hash with Symbol to value mapping - these are the arguments to the issue # def initialize(issue, semantic=nil, options = {}) @issue = issue @semantic = semantic @options = options end def file @options[:file] end def line @options[:line] end def pos @options[:pos] end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/serialization/��������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020740� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/serialization/abstract_reader.rb��������������������������������������0000644�0052762�0001160�00000011407�13417161721�024410� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require_relative 'extension' require_relative 'time_factory' module Puppet::Pops module Serialization # Abstract class for protocol specific readers such as MsgPack or JSON # The abstract reader is capable of reading the primitive scalars: # - Boolean # - Integer # - Float # - String # and, by using extensions, also # - Array start # - Map start # - Object start # - Regexp # - Version # - VersionRange # - Timespan # - Timestamp # - Default # # @api public class AbstractReader # @param [MessagePack::Unpacker,JSON::Unpacker] unpacker The low lever unpacker that delivers the primitive objects # @param [MessagePack::Unpacker,JSON::Unpacker] extension_unpacker Optional unpacker for extensions. Defaults to the unpacker # @api public def initialize(unpacker, extension_unpacker = nil) @read = [] @unpacker = unpacker @extension_unpacker = extension_unpacker.nil? ? unpacker : extension_unpacker register_types end # Read an object from the underlying unpacker # @return [Object] the object that was read # @api public def read obj = @unpacker.read case obj when Extension::InnerTabulation @read[obj.index] when Numeric, Symbol, Extension::NotTabulated, true, false, nil # not tabulated obj else @read << obj obj end end # @return [Integer] The total count of unique primitive values that has been read # @api private def primitive_count @read.size end # @api private def read_payload(data) raise SerializationError, "Internal error: Class #{self.class} does not implement method 'read_payload'" end # @api private def read_tpl_qname(ep) Array.new(ep.read) { read_tpl(ep) }.join('::') end # @api private def read_tpl(ep) obj = ep.read case obj when Integer @read[obj] else @read << obj obj end end # @api private def extension_unpacker @extension_unpacker end # @api private def register_type(extension_number, &block) @unpacker.register_type(extension_number, &block) end # @api private def register_types register_type(Extension::INNER_TABULATION) do |data| read_payload(data) { |ep| Extension::InnerTabulation.new(ep.read) } end register_type(Extension::TABULATION) do |data| read_payload(data) { |ep| Extension::Tabulation.new(ep.read) } end register_type(Extension::ARRAY_START) do |data| read_payload(data) { |ep| Extension::ArrayStart.new(ep.read) } end register_type(Extension::MAP_START) do |data| read_payload(data) { |ep| Extension::MapStart.new(ep.read) } end register_type(Extension::PCORE_OBJECT_START) do |data| read_payload(data) { |ep| type_name = read_tpl_qname(ep); Extension::PcoreObjectStart.new(type_name, ep.read) } end register_type(Extension::OBJECT_START) do |data| read_payload(data) { |ep| Extension::ObjectStart.new(ep.read) } end register_type(Extension::DEFAULT) do |data| read_payload(data) { |ep| Extension::Default::INSTANCE } end register_type(Extension::COMMENT) do |data| read_payload(data) { |ep| Extension::Comment.new(ep.read) } end register_type(Extension::SENSITIVE_START) do |data| read_payload(data) { |ep| Extension::SensitiveStart::INSTANCE } end register_type(Extension::REGEXP) do |data| read_payload(data) { |ep| Regexp.new(ep.read) } end register_type(Extension::TYPE_REFERENCE) do |data| read_payload(data) { |ep| Types::PTypeReferenceType.new(ep.read) } end register_type(Extension::SYMBOL) do |data| read_payload(data) { |ep| ep.read.to_sym } end register_type(Extension::TIME) do |data| read_payload(data) do |ep| sec = ep.read nsec = ep.read Time::Timestamp.new(sec * 1000000000 + nsec) end end register_type(Extension::TIMESPAN) do |data| read_payload(data) do |ep| sec = ep.read nsec = ep.read Time::Timespan.new(sec * 1000000000 + nsec) end end register_type(Extension::VERSION) do |data| read_payload(data) { |ep| SemanticPuppet::Version.parse(ep.read) } end register_type(Extension::VERSION_RANGE) do |data| read_payload(data) { |ep| SemanticPuppet::VersionRange.parse(ep.read) } end register_type(Extension::BASE64) do |data| read_payload(data) { |ep| Types::PBinaryType::Binary.from_base64_strict(ep.read) } end register_type(Extension::BINARY) do |data| # The Ruby MessagePack implementation have special treatment for "ASCII-8BIT" strings. They # are written as binary data. read_payload(data) { |ep| Types::PBinaryType::Binary.new(ep.read) } end register_type(Extension::URI) do |data| read_payload(data) { |ep| URI(ep.read) } end end end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/serialization/abstract_writer.rb��������������������������������������0000644�0052762�0001160�00000014140�13417161721�024457� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require_relative 'extension' module Puppet::Pops module Serialization MAX_INTEGER = 0x7fffffffffffffff MIN_INTEGER = -0x8000000000000000 # Abstract class for protocol specific writers such as MsgPack or JSON # The abstract write is capable of writing the primitive scalars: # - Boolean # - Integer # - Float # - String # and, by using extensions, also # - Array start # - Map start # - Object start # - Regexp # - Version # - VersionRange # - Timespan # - Timestamp # - Default # # @api public class AbstractWriter # @param [MessagePack::Packer,JSON::Packer] packer the underlying packer stream # @param [Hash] options # @option options [Boolean] :tabulate `true` if tabulation is enabled (which is the default). # @param [DebugPacker,nil] extension_packer Optional specific extension packer. Only used for debug output # @api public def initialize(packer, options, extension_packer = nil) @tabulate = options[:tabulate] @tabulate = true if @tabulate.nil? @written = {} @packer = packer @extension_packer = extension_packer.nil? ? packer : extension_packer register_types end # Tell the underlying packer to flush. # @api public def finish @packer.flush end def supports_binary? false end # Write a value on the underlying stream # @api public def write(value) written = false case value when Integer # not tabulated, but integers larger than 64-bit cannot be allowed. raise SerializationError, _('Integer out of bounds') if value > MAX_INTEGER || value < MIN_INTEGER when Numeric, Symbol, Extension::NotTabulated, true, false, nil # not tabulated else if @tabulate index = @written[value] if index.nil? @packer.write(value) written = true @written[value] = @written.size else value = Extension::InnerTabulation.new(index) end end end @packer.write(value) unless written end # Called from extension callbacks only # # @api private def build_payload raise SerializationError, "Internal error: Class #{self.class} does not implement method 'build_payload'" end # @api private def extension_packer @extension_packer end # Called from extension callbacks only # # @api private def write_tpl_qname(ep, qname) names = qname.split('::') ep.write(names.size) names.each {|n| write_tpl(ep, n)} end # Called from extension callbacks only # # @api private def write_tpl(ep, value) #TRANSLATORS 'Integers' is a Ruby class for numbers and should not be translated raise ArgumentError, _('Internal error. Integers cannot be tabulated in extension payload') if value.is_a?(Integer) if @tabulate index = @written[value] if index.nil? @written[value] = @written.size else value = index end end ep.write(value) end # @api private def register_type(extension_number, payload_class, &block) @packer.register_type(extension_number, payload_class, &block) end # @api private def register_types # 0x00 - 0x0F are reserved for low-level serialization / tabulation extensions register_type(Extension::INNER_TABULATION, Extension::InnerTabulation) do |o| build_payload { |ep| ep.write(o.index) } end register_type(Extension::TABULATION, Extension::Tabulation) do |o| build_payload { |ep| ep.write(o.index) } end # 0x10 - 0x1F are reserved for structural extensions register_type(Extension::ARRAY_START, Extension::ArrayStart) do |o| build_payload { |ep| ep.write(o.size) } end register_type(Extension::MAP_START, Extension::MapStart) do |o| build_payload { |ep| ep.write(o.size) } end register_type(Extension::PCORE_OBJECT_START, Extension::PcoreObjectStart) do |o| build_payload { |ep| write_tpl_qname(ep, o.type_name); ep.write(o.attribute_count) } end register_type(Extension::OBJECT_START, Extension::ObjectStart) do |o| build_payload { |ep| ep.write(o.attribute_count) } end # 0x20 - 0x2f reserved for special extension objects register_type(Extension::DEFAULT, Extension::Default) do |o| build_payload { |ep| } end register_type(Extension::COMMENT, Extension::Comment) do |o| build_payload { |ep| ep.write(o.comment) } end register_type(Extension::SENSITIVE_START, Extension::SensitiveStart) do |o| build_payload { |ep| } end # 0x30 - 0x7f reserved for mapping of specific runtime classes register_type(Extension::REGEXP, Regexp) do |o| build_payload { |ep| ep.write(o.source) } end register_type(Extension::TYPE_REFERENCE, Types::PTypeReferenceType) do |o| build_payload { |ep| ep.write(o.type_string) } end register_type(Extension::SYMBOL, Symbol) do |o| build_payload { |ep| ep.write(o.to_s) } end register_type(Extension::TIME, Time::Timestamp) do |o| build_payload { |ep| nsecs = o.nsecs; ep.write(nsecs / 1000000000); ep.write(nsecs % 1000000000) } end register_type(Extension::TIMESPAN, Time::Timespan) do |o| build_payload { |ep| nsecs = o.nsecs; ep.write(nsecs / 1000000000); ep.write(nsecs % 1000000000) } end register_type(Extension::VERSION, SemanticPuppet::Version) do |o| build_payload { |ep| ep.write(o.to_s) } end register_type(Extension::VERSION_RANGE, SemanticPuppet::VersionRange) do |o| build_payload { |ep| ep.write(o.to_s) } end if supports_binary? register_type(Extension::BINARY, Types::PBinaryType::Binary) do |o| # The Ruby MessagePack implementation has special treatment for "ASCII-8BIT" strings. They # are written as binary data. build_payload { |ep| ep.write(o.binary_buffer) } end else register_type(Extension::BASE64, Types::PBinaryType::Binary) do |o| build_payload { |ep| ep.write(o.to_s) } end end URI.scheme_list.values.each do |uri_class| register_type(Extension::URI, uri_class) do |o| build_payload { |ep| ep.write(o.to_s) } end end end def to_s "#{self.class.name}" end def inspect to_s end end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/serialization/deserializer.rb�����������������������������������������0000644�0052762�0001160�00000005277�13417161721�023755� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require_relative 'extension' module Puppet::Pops module Serialization # The deserializer is capable of reading, arrays, maps, and complex objects using an underlying protocol reader. It takes care of # resolving tabulations and assembling complex objects. The type of the complex objects are resolved using a loader. # @api public class Deserializer # Provides access to the reader. # @api private attr_reader :reader, :loader # @param [AbstractReader] reader the reader used when reading primitive objects from a stream # @param [Loader::Loader] loader the loader used when resolving names of types # @api public def initialize(reader, loader) @read = [] @reader = reader @loader = loader end # Read the next value from the reader. # # @return [Object] the value that was read # @api public def read val = @reader.read case val when Extension::Tabulation @read[val.index] when Extension::Default :default when Extension::ArrayStart result = remember([]) val.size.times { result << read } result when Extension::MapStart result = remember({}) val.size.times { key = read; result[key] = read } result when Extension::SensitiveStart Types::PSensitiveType::Sensitive.new(read) when Extension::PcoreObjectStart type_name = val.type_name type = Types::TypeParser.singleton.parse(type_name, @loader) raise SerializationError, _("No implementation mapping found for Puppet Type %{type_name}") % { type_name: type_name } if type.is_a?(Types::PTypeReferenceType) result = type.read(val.attribute_count, self) if result.is_a?(Types::PObjectType) existing_type = loader.load(:type, result.name) if result.eql?(existing_type) result = existing_type else # Add result to the loader unless it is equal to the existing_type. The add # will only succeed when the existing_type is nil. loader.add_entry(:type, result.name, result, nil) end end result when Extension::ObjectStart type = read type.read(val.attribute_count - 1, self) when Numeric, String, true, false, nil val else remember(val) end end # Remember that a value has been read. This means that the value is given an index # and that subsequent reads of a tabulation with that index should return the value. # @param [Object] value The value to remember # @return [Object] the argument # @api private def remember(value) @read << value value end end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/serialization/extension.rb��������������������������������������������0000644�0052762�0001160�00000007324�13417161721�023302� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Serialization module Extension # 0x00 - 0x0F are reserved for low-level serialization / tabulation extensions # Tabulation internal to the low level protocol reader/writer INNER_TABULATION = 0x00 # Tabulation managed by the serializer / deserializer TABULATION = 0x01 # 0x10 - 0x1F are reserved for structural extensions ARRAY_START = 0x10 MAP_START = 0x11 PCORE_OBJECT_START = 0x12 OBJECT_START = 0x13 SENSITIVE_START = 0x14 # 0x20 - 0x2f reserved for special extension objects DEFAULT = 0x20 COMMENT = 0x21 # 0x30 - 0x7f reserved for mapping of specific runtime classes REGEXP = 0x30 TYPE_REFERENCE = 0x31 SYMBOL = 0x32 TIME = 0x33 TIMESPAN = 0x34 VERSION = 0x35 VERSION_RANGE = 0x36 BINARY = 0x37 BASE64 = 0x38 URI = 0x39 # Marker module indicating whether or not an instance is tabulated or not module NotTabulated; end # Marker module for objects that starts a sequence, i.e. ArrayStart, MapStart, and PcoreObjectStart module SequenceStart; end # The class that triggers the use of the DEFAULT extension. It doesn't have any payload class Default include NotTabulated INSTANCE = Default.new end # The class that triggers the use of the TABULATION extension. The payload is the tabulation index class Tabulation include NotTabulated attr_reader :index def initialize(index) @index = index end end # Tabulation internal to the protocol reader/writer class InnerTabulation < Tabulation end # The class that triggers the use of the MAP_START extension. The payload is the map size (number of entries) class MapStart include NotTabulated include SequenceStart attr_reader :size def initialize(size) @size = size end # Sequence size is twice the map size since each entry is written as key and value def sequence_size @size * 2 end end # The class that triggers the use of the ARRAY_START extension. The payload is the array size class ArrayStart include NotTabulated include SequenceStart attr_reader :size def initialize(size) @size = size end def sequence_size @size end end # The class that triggers the use of the SENSITIVE_START extension. It has no payload class SensitiveStart include NotTabulated INSTANCE = SensitiveStart.new end # The class that triggers the use of the PCORE_OBJECT_START extension. The payload is the name of the object type # and the number of attributes in the instance. class PcoreObjectStart include SequenceStart attr_reader :type_name, :attribute_count def initialize(type_name, attribute_count) @type_name = type_name @attribute_count = attribute_count end def hash @type_name.hash * 29 + attribute_count.hash end def eql?(o) o.is_a?(PcoreObjectStart) && o.type_name == @type_name && o.attribute_count == @attribute_count end alias == eql? def sequence_size @attribute_count end end class ObjectStart include SequenceStart attr_reader :attribute_count def initialize(attribute_count) @attribute_count = attribute_count end def hash attribute_count.hash end def eql?(o) o.is_a?(ObjectStart) && o.attribute_count == @attribute_count end alias == eql? def sequence_size @attribute_count end end # The class that triggers the use of the COMMENT extension. The payload is comment text class Comment attr_reader :comment def initialize(comment) @comment = comment end def hash @comment.hash end def eql?(o) o.is_a?(Comment) && o.comment == @comment end alias == eql? end end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/serialization/instance_reader.rb��������������������������������������0000644�0052762�0001160�00000001720�13417161721�024406� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Serialization # An InstanceReader is responsible for reading an instance of a complex object using a deserializer. The read involves creating the # instance, register it with the deserializer (so that self references can be resolved) and then read the instance data (which normally # amounts to all attribute values). # Instance readers are registered with of {Types::PObjectType}s to aid the type when reading instances. # # @api private module InstanceReader # @param [Class] impl_class the class of the instance to be created and initialized # @param [Integer] value_count the expected number of objects that forms the initialization data # @param [Deserializer] deserializer the deserializer to read from, and to register the instance with # @return [Object] the instance that has been read def read(impl_class, value_count, deserializer) Serialization.not_implemented(self, 'read') end end end end ������������������������������������������������puppet-5.5.10/lib/puppet/pops/serialization/instance_writer.rb��������������������������������������0000644�0052762�0001160�00000000742�13417161721�024463� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Serialization # An instance writer is responsible for writing complex objects using a {Serializer} # @api private module InstanceWriter # @param [Types::PObjectType] type the type of instance to write # @param [Object] value the instance # @param [Serializer] serializer the serializer that will receive the written instance def write(type, value, serializer) Serialization.not_implemented(self, 'write') end end end end ������������������������������puppet-5.5.10/lib/puppet/pops/serialization/json.rb�������������������������������������������������0000644�0052762�0001160�00000014236�13417161721�022237� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/json' module Puppet::Pops module Serialization require_relative 'time_factory' require_relative 'abstract_reader' require_relative 'abstract_writer' module JSON # A Writer that writes output in JSON format # @api private class Writer < AbstractWriter def initialize(io, options = {}) super(Packer.new(io, options), options) end # Clear the underlying io stream but does not clear tabulation cache # Specifically designed to enable tabulation to span more than one # separately deserialized object. def clear_io @packer.clear_io end def extension_packer @packer end def packer @packer end def build_payload yield(@packer) end def to_a @packer.to_a end def to_json @packer.to_json end end # A Reader that reads JSON formatted input # @api private class Reader < AbstractReader def initialize(io) super(Unpacker.new(io)) end def re_initialize(io) @unpacker.re_initialize(io) end def read_payload(data) yield(@unpacker) end end # The JSON Packer. Modeled after the MessagePack::Packer # @api private class Packer def initialize(io, options = {}) @io = io @io << '[' @type_registry = {} @nested = [] @verbose = options[:verbose] @verbose = false if @verbose.nil? @indent = options[:indent] || 0 end def register_type(type, klass, &block) @type_registry[klass] = [type, klass, block] end def clear_io # Truncate everything except leading '[' if @io.is_a?(String) @io.slice!(1..-1) else @io.truncate(1) end end def empty? @io.is_a?(String) ? io.length == 1 : @io.pos == 1 end def flush # Drop last comma unless just start marker present if @io.is_a?(String) @io.chop! unless @io.length == 1 @io << ']' else pos = @io.pos @io.pos = pos - 1 unless pos == 1 @io << ']' @io.flush end end def write(obj) case obj when Array write_array_header(obj.size) obj.each { |x| write(x) } when Hash write_map_header(obj.size) obj.each_pair {|k, v| write(k); write(v) } when nil write_nil else write_scalar(obj) end end alias pack write def write_array_header(n) if n < 1 @io << '[]' else @io << '[' @nested << [false, n] end end def write_map_header(n) if n < 1 @io << '{}' else @io << '{' @nested << [true, n * 2] end end def write_nil @io << 'null' write_delim end def to_s to_json end def to_a ::Puppet::Util::Json.load(io_string) end def to_json if @indent > 0 ::Puppet::Util::Json.dump(to_a, { :pretty => true, :indent => ' ' * @indent }) else io_string end end # Write a payload object. Not subject to extensions def write_pl(obj) @io << Puppet::Util::Json.dump(obj) << ',' end def io_string if @io.is_a?(String) @io else @io.pos = 0 @io.read end end private :io_string def write_delim nesting = @nested.last cnt = nesting.nil? ? nil : nesting[1] case cnt when 1 @io << (nesting[0] ? '}' : ']') @nested.pop write_delim when Integer if (cnt % 2) == 0 || !nesting[0] @io << ',' else @io << ':' end nesting[1] = cnt - 1 else @io << ',' end end private :write_delim def write_scalar(obj) ext = @type_registry[obj.class] if ext.nil? case obj when Numeric, String, true, false, nil @io << Puppet::Util::Json.dump(obj) write_delim else raise SerializationError, _("Unable to serialize a %{obj}") % { obj: obj.class.name } end else write_extension(ext, obj) end end private :write_scalar def write_extension(ext, obj) @io << '[' << extension_indicator(ext).to_json << ',' @nested << nil write_extension_payload(ext, obj) @nested.pop if obj.is_a?(Extension::SequenceStart) && obj.sequence_size > 0 @nested << [false, obj.sequence_size] else if @io.is_a?(String) @io.chop! else @io.pos -= 1 end @io << ']' write_delim end end private :write_extension def write_extension_payload(ext, obj) ext[2].call(obj) end private :write_extension_payload def extension_indicator(ext) @verbose ? ext[1].name.sub(/^Puppet::Pops::Serialization::\w+::(.+)$/, '\1') : ext[0] end private :extension_indicator end class Unpacker def initialize(io) re_initialize(io) @type_registry = {} @nested = [] end def re_initialize(io) parsed = parse_io(io) raise SerializationError, _("JSON stream is not an array. It is a %{klass}") % { klass: io.class.name } unless parsed.is_a?(Array) @etor_stack = [parsed.each] end def read obj = nil loop do raise SerializationError, _('Unexpected end of input') if @etor_stack.empty? etor = @etor_stack.last begin obj = etor.next break rescue StopIteration @etor_stack.pop end end if obj.is_a?(Array) ext_etor = obj.each @etor_stack << ext_etor ext_no = ext_etor.next ext_block = @type_registry[ext_no] raise SerializationError, _("Invalid input. %{ext_no} is not a valid extension number") % { ext_no: ext_no } if ext_block.nil? obj = ext_block.call(nil) end obj end def register_type(extension_number, &block) @type_registry[extension_number] = block end private def parse_io(io) case io when IO, StringIO ::Puppet::Util::Json.load(io.read) when String ::Puppet::Util::Json.load(io) else io end end end end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/serialization/json_path.rb��������������������������������������������0000644�0052762�0001160�00000006714�13417161721�023255� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Serialization module JsonPath # Creates a _json_path_ reference from the given `path` argument # # @path path [Array<Integer,String>] An array of integers and strings # @return [String] the created json_path # # @api private def self.to_json_path(path) p = '$' path.each do |seg| if seg.nil? p << '[null]' elsif Types::PScalarDataType::DEFAULT.instance?(seg) p << '[' << Types::StringConverter.singleton.convert(seg, '%p') << ']' else # Unable to construct json path from complex segments return nil end end p end # Resolver for JSON path that uses the Puppet parser to create the AST. The path must start # with '$' which denotes the value that is passed into the parser. This parser can easily # be extended with more elaborate resolution mechanisms involving document sets. # # The parser is limited to constructs generated by the {JsonPath#to_json_path} # method. # # @api private class Resolver def self.singleton @singleton ||= self.new end def initialize @parser = Parser::Parser.new @visitor = Visitor.new(nil, 'resolve', 2, 2) end # Resolve the given _path_ in the given _context_. # @param context [Object] the context used for resolution # @param path [String] the json path # @return [Object] the resolved value # def resolve(context, path) factory = @parser.parse_string(path) v = resolve_any(factory.model.body, context, path) v.is_a?(Builder) ? v.resolve : v end def resolve_any(ast, context, path) @visitor.visit_this_2(self, ast, context, path) end def resolve_AccessExpression(ast, context, path) bad_json_path(path) unless ast.keys.size == 1 receiver = resolve_any(ast.left_expr, context, path) key = resolve_any(ast.keys[0], context, path) if receiver.is_a?(Types::PuppetObject) PCORE_TYPE_KEY == key ? receiver._pcore_type : receiver.send(key) else receiver[key] end end def resolve_NamedAccessExpression(ast, context, path) receiver = resolve_any(ast.left_expr, context, path) key = resolve_any(ast.right_expr, context, path) if receiver.is_a?(Types::PuppetObject) PCORE_TYPE_KEY == key ? receiver._pcore_type : receiver.send(key) else receiver[key] end end def resolve_QualifiedName(ast, _, _) v = ast.value 'null' == v ? nil : v end def resolve_QualifiedReference(ast, _, _) v = ast.cased_value 'null'.casecmp(v) == 0 ? nil : v end def resolve_ReservedWord(ast, _, _) ast.word end def resolve_LiteralUndef(_, _, _) 'undef' end def resolve_LiteralDefault(_, _, _) 'default' end def resolve_VariableExpression(ast, context, path) # A single '$' means root, i.e. the context. bad_json_path(path) unless EMPTY_STRING == resolve_any(ast.expr, context, path) context end def resolve_CallMethodExpression(ast, context, path) bad_json_path(path) unless ast.arguments.empty? resolve_any(ast.functor_expr, context, path) end def resolve_LiteralValue(ast, _, _) ast.value end def resolve_Object(ast, _, path) bad_json_path(path) end def bad_json_path(path) raise SerializationError, _('Unable to parse jsonpath "%{path}"') % { :path => path } end private :bad_json_path end end end end ����������������������������������������������������puppet-5.5.10/lib/puppet/pops/serialization/object.rb�����������������������������������������������0000644�0052762�0001160�00000004443�13417161721�022533� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require_relative 'instance_reader' require_relative 'instance_writer' module Puppet::Pops module Serialization # Instance reader for objects that implement {Types::PuppetObject} # @api private class ObjectReader include InstanceReader def read(type, impl_class, value_count, deserializer) (names, types, required_count) = type.parameter_info(impl_class) max = names.size unless value_count >= required_count && value_count <= max raise Serialization::SerializationError, _("Feature count mismatch for %{value0}. Expected %{required_count} - %{max}, actual %{value_count}") % { value0: impl_class.name, required_count: required_count, max: max, value_count: value_count } end # Deserializer must know about this instance before we read its attributes val = deserializer.remember(impl_class.allocate) args = Array.new(value_count) { deserializer.read } types.each_with_index do |ptype, index| if index < args.size arg = args[index] Types::TypeAsserter.assert_instance_of(nil, ptype, arg) { "#{type.name}[#{names[index]}]" } unless arg == :default else attr = type[names[index]] raise Serialization::SerializationError, _("Missing default value for %{type_name}[%{name}]") % { type_name: type.name, name: names[index] } unless attr && attr.value? args << attr.value end end val.send(:initialize, *args) val end INSTANCE = ObjectReader.new end # Instance writer for objects that implement {Types::PuppetObject} # @api private class ObjectWriter include InstanceWriter def write(type, value, serializer) impl_class = value.class (names, _, required_count) = type.parameter_info(impl_class) args = names.map { |name| value.send(name) } # Pop optional arguments that are default while args.size > required_count break unless type[names[args.size-1]].default_value?(args.last) args.pop end if type.name.start_with?('Pcore::') || serializer.type_by_reference? serializer.push_written(value) serializer.start_pcore_object(type.name, args.size) else serializer.start_object(args.size + 1) serializer.write(type) serializer.push_written(value) end args.each { |arg| serializer.write(arg) } end INSTANCE = ObjectWriter.new end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/serialization/serializer.rb�������������������������������������������0000644�0052762�0001160�00000010513�13417161721�023431� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require_relative 'extension' module Puppet::Pops module Serialization # The serializer is capable of writing, arrays, maps, and complex objects using an underlying protocol writer. It takes care of # tabulating and disassembling complex objects. # @api public class Serializer # Provides access to the writer. # @api private attr_reader :writer # @param writer [AbstractWriter] the writer that is used for writing primitive values # @param options [{String, Object}] serialization options # @option options [Boolean] :type_by_reference `true` if Object types are serialized by name only. # @api public def initialize(writer, options = EMPTY_HASH) @written = {} @writer = writer @options = options end # Tell the underlying writer to finish # @api public def finish @writer.finish end # Write an object # @param [Object] value the object to write # @api public def write(value) case value when Integer, Float, String, true, false, nil @writer.write(value) when :default @writer.write(Extension::Default::INSTANCE) else index = @written[value.object_id] if index.nil? write_tabulated_first_time(value) else @writer.write(Extension::Tabulation.new(index)) end end end # Write the start of an array. # @param [Integer] size the size of the array # @api private def start_array(size) @writer.write(Extension::ArrayStart.new(size)) end # Write the start of a map (hash). # @param [Integer] size the number of entries in the map # @api private def start_map(size) @writer.write(Extension::MapStart.new(size)) end # Write the start of a complex pcore object # @param [String] type_ref the name of the type # @param [Integer] attr_count the number of attributes in the object # @api private def start_pcore_object(type_ref, attr_count) @writer.write(Extension::PcoreObjectStart.new(type_ref, attr_count)) end # Write the start of a complex object # @param [Integer] attr_count the number of attributes in the object # @api private def start_object(attr_count) @writer.write(Extension::ObjectStart.new(attr_count)) end def push_written(value) @written[value.object_id] = @written.size end # Write the start of a sensitive object # @api private def start_sensitive @writer.write(Extension::SensitiveStart::INSTANCE) end def type_by_reference? @options[:type_by_reference] == true end def to_s "#{self.class.name} with #{@writer}" end def inspect to_s end # First time write of a tabulated object. This means that the object is written and then remembered. Subsequent writes # of the same object will yield a write of a tabulation index instead. # @param [Object] value the value to write # @api private def write_tabulated_first_time(value) case when value.instance_of?(Symbol), value.instance_of?(Regexp), value.instance_of?(SemanticPuppet::Version), value.instance_of?(SemanticPuppet::VersionRange), value.instance_of?(Time::Timestamp), value.instance_of?(Time::Timespan), value.instance_of?(Types::PBinaryType::Binary), value.is_a?(URI) push_written(value) @writer.write(value) when value.instance_of?(Array) push_written(value) start_array(value.size) value.each { |elem| write(elem) } when value.instance_of?(Hash) push_written(value) start_map(value.size) value.each_pair { |key, val| write(key); write(val) } when value.instance_of?(Types::PSensitiveType::Sensitive) start_sensitive write(value.unwrap) when value.instance_of?(Types::PTypeReferenceType) push_written(value) @writer.write(value) when value.is_a?(Types::PuppetObject) value._pcore_type.write(value, self) else impl_class = value.class type = Loaders.implementation_registry.type_for_module(impl_class) raise SerializationError, _("No Puppet Type found for %{klass}") % { klass: impl_class.name } unless type.is_a?(Types::PObjectType) type.write(value, self) end end end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/serialization/time_factory.rb�����������������������������������������0000644�0052762�0001160�00000003414�13417161721�023747� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Serialization # Implements all the constructors found in the Time class and ensures that # the created Time object can be serialized and deserialized using its # seconds and nanoseconds without loss of precision. # # @deprecated No longer in use. Functionality replaced by Timestamp # @api private class TimeFactory NANO_DENOMINATOR = 10**9 def self.at(*args) sec_nsec_safe(Time.at(*args)) end def self.gm(*args) sec_nsec_safe(Time.gm(*args)) end def self.local(*args) sec_nsec_safe(Time.local(*args)) end def self.mktime(*args) sec_nsec_safe(Time.mktime(*args)) end def self.new(*args) sec_nsec_safe(Time.new(*args)) end def self.now sec_nsec_safe(Time.now) end def self.utc(*args) sec_nsec_safe(Time.utc(*args)) end # Creates a Time object from a Rational defined as: # # (_sec_ * #NANO_DENOMINATOR + _nsec_) / #NANO_DENOMINATOR # # This ensures that a Time object can be reliably serialized and using its # its #tv_sec and #tv_nsec values and then recreated again (using this method) # without loss of precision. # # @param sec [Integer] seconds since Epoch # @param nsec [Integer] nano seconds # @return [Time] the created object # def self.from_sec_nsec(sec, nsec) Time.at(Rational(sec * NANO_DENOMINATOR + nsec, NANO_DENOMINATOR)) end # Returns a new Time object that is adjusted to ensure that precision is not # lost when it is serialized and deserialized using its seconds and nanoseconds # @param t [Time] the object to adjust # @return [Time] the adjusted object # def self.sec_nsec_safe(t) from_sec_nsec(t.tv_sec, t.tv_nsec) end end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/serialization/from_data_converter.rb����������������������������������0000644�0052762�0001160�00000015174�13417161721�025313� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Serialization class Builder def initialize(values) @values = values @resolved = true end def [](key) @values[key] end def []=(key, value) @values[key] = value @resolved = false if value.is_a?(Builder) end def resolve unless @resolved @resolved = true if @values.is_a?(Array) @values.each_with_index { |v, idx| @values[idx] = v.resolve if v.is_a?(Builder) } elsif @values.is_a?(Hash) @values.each_pair { |k, v| @values[k] = v.resolve if v.is_a?(Builder) } end end @values end end class ObjectHashBuilder < Builder def initialize(instance) super({}) @instance = instance end def resolve @instance._pcore_init_from_hash(super) @instance end end class ObjectArrayBuilder < Builder def initialize(instance) super({}) @instance = instance end def resolve @instance.send(:initialize, *super.values) @instance end end # Class that can process the `Data` produced by the {ToDataConverter} class and reassemble # the objects that were converted. # # @api public class FromDataConverter # Converts the given `Data` _value_ according to the given _options_ and returns the resulting `RichData`. # # @param value [Data] the value to convert # @param options {Symbol => <Boolean,String>} options hash # @option options [Loaders::Loader] :loader the loader to use. Can be `nil` in which case the default is # determined by the {Types::TypeParser}. # @option options [Boolean] :allow_unresolved `true` to allow that rich_data hashes are kept "as is" if the # designated '__pcore_type__' cannot be resolved. Defaults to `false`. # @return [RichData] the processed result. # # @api public def self.convert(value, options = EMPTY_HASH) new(options).convert(value) end # Creates a new instance of the processor # # @param options {Symbol => Object} options hash # @option options [Loaders::Loader] :loader the loader to use. Can be `nil` in which case the default is # determined by the {Types::TypeParser}. # @option options [Boolean] :allow_unresolved `true` to allow that rich_data hashes are kept "as is" if the # designated '__pcore_type__' cannot be resolved. Defaults to `false`. # # @api public def initialize(options = EMPTY_HASH) @allow_unresolved = options[:allow_unresolved] @allow_unresolved = false if @allow_unresolved.nil? @loader = options[:loader] @pcore_type_procs = { PCORE_TYPE_HASH => proc do |hash, _| value = hash[PCORE_VALUE_KEY] build({}) do top = value.size idx = 0 while idx < top key = without_value { convert(value[idx]) } idx += 1 with(key) { convert(value[idx]) } idx += 1 end end end, PCORE_TYPE_SENSITIVE => proc do |hash, _| build(Types::PSensitiveType::Sensitive.new(convert(hash[PCORE_VALUE_KEY]))) end, PCORE_TYPE_DEFAULT => proc do |_, _| build(:default) end, PCORE_TYPE_SYMBOL => proc do |hash, _| build(:"#{hash[PCORE_VALUE_KEY]}") end, PCORE_LOCAL_REF_SYMBOL => proc do |hash, _| build(JsonPath::Resolver.singleton.resolve(@root, hash[PCORE_VALUE_KEY])) end } @pcore_type_procs.default = proc do |hash, type_value| value = hash.include?(PCORE_VALUE_KEY) ? hash[PCORE_VALUE_KEY] : hash.reject { |key, _| PCORE_TYPE_KEY == key } if type_value.is_a?(Hash) type = without_value { convert(type_value) } if type.is_a?(Hash) raise SerializationError, _('Unable to deserialize type from %{type}') % { type: type } unless @allow_unresolved hash else pcore_type_hash_to_value(type, value) end else type = Types::TypeParser.singleton.parse(type_value, @loader) if type.is_a?(Types::PTypeReferenceType) unless @allow_unresolved raise SerializationError, _('No implementation mapping found for Puppet Type %{type_name}') % { type_name: type_value } end hash else pcore_type_hash_to_value(type, value) end end end end # Converts the given `Data` _value_ and returns the resulting `RichData` # # @param value [Data] the value to convert # @return [RichData] the processed result # # @api public def convert(value) if value.is_a?(Hash) pcore_type = value[PCORE_TYPE_KEY] if pcore_type @pcore_type_procs[pcore_type].call(value, pcore_type) else build({}) { value.each_pair { |key, elem| with(key) { convert(elem) }}} end elsif value.is_a?(Array) build([]) { value.each_with_index { |elem, idx| with(idx) { convert(elem)}}} else build(value) end end private def with(key) parent_key = @key @key = key yield @key = parent_key end def with_value(value) @root = value unless instance_variable_defined?(:@root) parent = @current @current = value yield @current = parent value end def without_value parent = @current @current = nil value = yield @current = parent value end def build(value, &block) vx = Builder.new(value) @current[@key] = vx unless @current.nil? with_value(vx, &block) if block_given? vx.resolve end def build_object(builder, &block) @current[@key] = builder unless @current.nil? with_value(builder, &block) if block_given? builder.resolve end def pcore_type_hash_to_value(pcore_type, value) if value.is_a?(Hash) # Complex object if value.empty? build(pcore_type.create) elsif pcore_type.implementation_class.respond_to?(:_pcore_init_from_hash) build_object(ObjectHashBuilder.new(pcore_type.allocate)) { value.each_pair { |key, elem| with(key) { convert(elem) } } } else build_object(ObjectArrayBuilder.new(pcore_type.allocate)) { value.each_pair { |key, elem| with(key) { convert(elem) } } } end elsif value.is_a?(String) build(pcore_type.create(value)) else raise SerializationError, _('Cannot create a %{type_name} from a %{arg_class}') % { :type_name => pcore_type.name, :arg_class => value.class.name } end end end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/serialization/to_data_converter.rb������������������������������������0000644�0052762�0001160�00000025303�13417161721�024765� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Serialization # Class that can process an arbitrary object into a value that is assignable to `Data`. # # @api public class ToDataConverter include Evaluator::Runtime3Support # Convert the given _value_ according to the given _options_ and return the result of the conversion # # @param value [Object] the value to convert # @param options {Symbol => <Boolean,String>} options hash # @option options [Boolean] :rich_data `true` if rich data is enabled # @option options [Boolean] :local_references use local references instead of duplicating complex entries # @option options [Boolean] :type_by_reference `true` if Object types are converted to references rather than embedded. # @option options [Boolean] :symbol_as_string `true` if Symbols should be converted to strings (with type loss) # @option options [String] :path_prefix String to prepend to path in warnings and errors # @return [Data] the processed result. An object assignable to `Data`. # # @api public def self.convert(value, options = EMPTY_HASH) new(options).convert(value) end # Create a new instance of the processor # # @param options {Symbol => Object} options hash # @option options [Boolean] :rich_data `true` if rich data is enabled # @option options [Boolean] :local_references use local references instead of duplicating complex entries # @option options [Boolean] :type_by_reference `true` if Object types are converted to references rather than embedded. # @option options [Boolean] :symbol_as_string `true` if Symbols should be converted to strings (with type loss) # @option options [String] :message_prefix String to prepend to path in warnings and errors # @option semantic [Object] :semantic object to pass to the issue reporter def initialize(options = EMPTY_HASH) @type_by_reference = options[:type_by_reference] @type_by_reference = true if @type_by_reference.nil? @local_reference = options[:local_reference] @local_reference = true if @local_reference.nil? @symbol_as_string = options[:symbol_as_string] @symbol_as_string = false if @symbol_as_string.nil? @rich_data = options[:rich_data] @rich_data = false if @rich_data.nil? @message_prefix = options[:message_prefix] @semantic = options[:semantic] end # Convert the given _value_ # # @param value [Object] the value to convert # @return [Data] the processed result. An object assignable to `Data`. # # @api public def convert(value) @path = [] @values = {} to_data(value) end private def path_to_s s = @message_prefix || '' s << JsonPath.to_json_path(@path)[1..-1] s end def to_data(value) if value.nil? || Types::PScalarDataType::DEFAULT.instance?(value) value elsif :default == value if @rich_data { PCORE_TYPE_KEY => PCORE_TYPE_DEFAULT } else serialization_issue(Issues::SERIALIZATION_DEFAULT_CONVERTED_TO_STRING, :path => path_to_s) 'default' end elsif value.is_a?(Symbol) if @symbol_as_string value.to_s elsif @rich_data { PCORE_TYPE_KEY => PCORE_TYPE_SYMBOL, PCORE_VALUE_KEY => value.to_s } else unknown_to_string_with_warning(value) end elsif value.instance_of?(Array) process(value) do result = [] value.each_with_index do |elem, index| with(index) { result << to_data(elem) } end result end elsif value.instance_of?(Hash) process(value) do if value.keys.all? { |key| key.is_a?(String) } result = {} value.each_pair { |key, elem| with(key) { result[key] = to_data(elem) } } result else non_string_keyed_hash_to_data(value) end end elsif value.instance_of?(Types::PSensitiveType::Sensitive) process(value) do { PCORE_TYPE_KEY => PCORE_TYPE_SENSITIVE, PCORE_VALUE_KEY => to_data(value.unwrap) } end else unknown_to_data(value) end end # If `:local_references` is enabled, then the `object_id` will be associated with the current _path_ of # the context the first time this method is called. The method then returns the result of yielding to # the given block. Subsequent calls with a value that has the same `object_id` will instead return a # reference based on the given path. # # If `:local_references` is disabled, then this method performs a check for endless recursion before # it yields to the given block. The result of yielding is returned. # # @param value [Object] the value # @yield The block that will produce the data for the value # @return [Data] the result of yielding to the given block, or a hash denoting a reference # # @api private def process(value, &block) if @local_reference id = value.object_id ref = @values[id] if ref.nil? @values[id] = @path.dup yield elsif ref.instance_of?(Hash) ref else json_ref = JsonPath.to_json_path(ref) if json_ref.nil? # Complex key and hence no way to reference the prior value. The value must therefore be # duplicated which in turn introduces a risk for endless recursion in case of self # referencing structures with_recursive_guard(value, &block) else @values[id] = { PCORE_TYPE_KEY => PCORE_LOCAL_REF_SYMBOL, PCORE_VALUE_KEY => json_ref } end end else with_recursive_guard(value, &block) end end # Pushes `key` to the end of the path and yields to the given block. The # `key` is popped when the yield returns. # @param key [Object] the key to push on the current path # @yield The block that will produce the returned value # @return [Object] the result of yielding to the given block # # @api private def with(key) @path.push(key) value = yield @path.pop value end # @param value [Object] the value to use when checking endless recursion # @yield The block that will produce the data # @return [Data] the result of yielding to the given block def with_recursive_guard(value) id = value.object_id if @recursive_lock if @recursive_lock.include?(id) serialization_issue(Issues::SERIALIZATION_ENDLESS_RECURSION, :type_name => value.class.name) end @recursive_lock[id] = true else @recursive_lock = { id => true } end v = yield @recursive_lock.delete(id) v end def unknown_to_data(value) @rich_data ? value_to_data_hash(value) : unknown_to_string_with_warning(value) end def unknown_key_to_string_with_warning(value) str = unknown_to_string(value) serialization_issue(Issues::SERIALIZATION_UNKNOWN_KEY_CONVERTED_TO_STRING, :path => path_to_s, :klass => value.class, :value => str) str end def unknown_to_string_with_warning(value) str = unknown_to_string(value) serialization_issue(Issues::SERIALIZATION_UNKNOWN_CONVERTED_TO_STRING, :path => path_to_s, :klass => value.class, :value => str) str end def unknown_to_string(value) value.is_a?(Regexp) ? Puppet::Pops::Types::PRegexpType.regexp_to_s_with_delimiters(value) : value.to_s end def non_string_keyed_hash_to_data(hash) if @rich_data to_key_extended_hash(hash) else result = {} hash.each_pair do |key, value| if key.is_a?(Symbol) && @symbol_as_string key = key.to_s elsif !key.is_a?(String) key = unknown_key_to_string_with_warning(key) end with(key) { result[key] = to_data(value) } end result end end # A Key extended hash is a hash whose keys are not entirely strings. Such a hash # cannot be safely represented as JSON or YAML # # @param hash {Object => Object} the hash to process # @return [String => Data] the representation of the extended hash def to_key_extended_hash(hash) key_value_pairs = [] hash.each_pair do |key, value| key = to_data(key) key_value_pairs << key key_value_pairs << with(key) { to_data(value) } end { PCORE_TYPE_KEY => PCORE_TYPE_HASH, PCORE_VALUE_KEY => key_value_pairs } end def value_to_data_hash(value) pcore_type = value.is_a?(Types::PuppetObject) ? value._pcore_type : Types::TypeCalculator.singleton.infer(value) if pcore_type.is_a?(Puppet::Pops::Types::PRuntimeType) unknown_to_string_with_warning(value) else pcore_tv = pcore_type_to_data(pcore_type) if pcore_type.roundtrip_with_string? { PCORE_TYPE_KEY => pcore_tv, # Scalar values are stored using their default string representation PCORE_VALUE_KEY => Types::StringConverter.singleton.convert(value) } elsif pcore_type.implementation_class.respond_to?(:_pcore_init_from_hash) process(value) do { PCORE_TYPE_KEY => pcore_tv, }.merge(to_data(value._pcore_init_hash)) end else process(value) do (names, _, required_count) = pcore_type.parameter_info(value.class) args = names.map { |name| value.send(name) } # Pop optional arguments that are default while args.size > required_count break unless pcore_type[names[args.size-1]].default_value?(args.last) args.pop end result = { PCORE_TYPE_KEY => pcore_tv } args.each_with_index do |val, idx| key = names[idx] with(key) { result[key] = to_data(val) } end result end end end end def pcore_type_to_data(pcore_type) type_name = pcore_type.name if @type_by_reference || type_name.start_with?('Pcore::') type_name else with(PCORE_TYPE_KEY) { to_data(pcore_type) } end end private :pcore_type_to_data def serialization_issue(issue, options = EMPTY_HASH) semantic = @semantic if semantic.nil? tos = Puppet::Pops::PuppetStack.top_of_stack if tos.empty? semantic = Puppet::Pops::SemanticError.new(issue, nil, EMPTY_HASH) else file, line = stacktrace semantic = Puppet::Pops::SemanticError.new(issue, nil, {:file => file, :line => line}) end end optionally_fail(issue, semantic, options) end end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/time/�����������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�017021� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/time/timespan.rb������������������������������������������������������0000644�0052762�0001160�00000047074�13417161721�021175� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Time NSECS_PER_USEC = 1000 NSECS_PER_MSEC = NSECS_PER_USEC * 1000 NSECS_PER_SEC = NSECS_PER_MSEC * 1000 NSECS_PER_MIN = NSECS_PER_SEC * 60 NSECS_PER_HOUR = NSECS_PER_MIN * 60 NSECS_PER_DAY = NSECS_PER_HOUR * 24 KEY_STRING = 'string'.freeze KEY_FORMAT = 'format'.freeze KEY_NEGATIVE = 'negative'.freeze KEY_DAYS = 'days'.freeze KEY_HOURS = 'hours'.freeze KEY_MINUTES = 'minutes'.freeze KEY_SECONDS = 'seconds'.freeze KEY_MILLISECONDS = 'milliseconds'.freeze KEY_MICROSECONDS = 'microseconds'.freeze KEY_NANOSECONDS = 'nanoseconds'.freeze # TimeData is a Numeric that stores its value internally as nano-seconds but will be considered to be seconds and fractions of # seconds when used in arithmetic or comparison with other Numeric types. # class TimeData < Numeric include LabelProvider attr_reader :nsecs def initialize(nanoseconds) @nsecs = nanoseconds end def <=>(o) case o when self.class @nsecs <=> o.nsecs when Integer to_int <=> o when Float to_f <=> o else nil end end def label(o) Utils.name_to_segments(o.class.name).last end # @return [Float] the number of seconds def to_f @nsecs.fdiv(NSECS_PER_SEC) end # @return [Integer] the number of seconds with fraction part truncated def to_int @nsecs / NSECS_PER_SEC end def to_i to_int end # @return [Complex] short for `#to_f.to_c` def to_c to_f.to_c end # @return [Rational] initial numerator is nano-seconds and denominator is nano-seconds per second def to_r Rational(@nsecs, NSECS_PER_SEC) end undef_method :phase, :polar, :rect, :rectangular end class Timespan < TimeData def self.from_fields(negative, days, hours, minutes, seconds, milliseconds = 0, microseconds = 0, nanoseconds = 0) ns = (((((days * 24 + hours) * 60 + minutes) * 60 + seconds) * 1000 + milliseconds) * 1000 + microseconds) * 1000 + nanoseconds new(negative ? -ns : ns) end def self.from_hash(hash) hash.include?('string') ? from_string_hash(hash) : from_fields_hash(hash) end def self.from_string_hash(hash) parse(hash[KEY_STRING], hash[KEY_FORMAT] || Format::DEFAULTS) end def self.from_fields_hash(hash) from_fields( hash[KEY_NEGATIVE] || false, hash[KEY_DAYS] || 0, hash[KEY_HOURS] || 0, hash[KEY_MINUTES] || 0, hash[KEY_SECONDS] || 0, hash[KEY_MILLISECONDS] || 0, hash[KEY_MICROSECONDS] || 0, hash[KEY_NANOSECONDS] || 0) end def self.parse(str, format = Format::DEFAULTS) if format.is_a?(::Array) format.each do |fmt| fmt = FormatParser.singleton.parse_format(fmt) unless fmt.is_a?(Format) begin return fmt.parse(str) rescue ArgumentError end end raise ArgumentError, _("Unable to parse '%{str}' using any of the formats %{formats}") % { str: str, formats: format.join(', ') } end format = FormatParser.singleton.parse_format(format) unless format.is_a?(Format) format.parse(str) end # @return [true] if the stored value is negative def negative? @nsecs < 0 end def +(o) case o when Timestamp Timestamp.new(@nsecs + o.nsecs) when Timespan Timespan.new(@nsecs + o.nsecs) when Integer, Float # Add seconds Timespan.new(@nsecs + (o * NSECS_PER_SEC).to_i) else raise ArgumentError, _("%{klass} cannot be added to a Timespan") % { klass: a_an_uc(o) } unless o.is_a?(Timespan) end end def -(o) case o when Timespan Timespan.new(@nsecs - o.nsecs) when Integer, Float # Subtract seconds Timespan.new(@nsecs - (o * NSECS_PER_SEC).to_i) else raise ArgumentError, _("%{klass} cannot be subtracted from a Timespan") % { klass: a_an_uc(o) } end end def -@ Timespan.new(-@nsecs) end def *(o) case o when Integer, Float Timespan.new((@nsecs * o).to_i) else raise ArgumentError, _("A Timestamp cannot be multiplied by %{klass}") % { klass: a_an(o) } end end def divmod(o) case o when Integer to_i.divmod(o) when Float to_f.divmod(o) else raise ArgumentError, _("Can not do modulus on a Timespan using a %{klass}") % { klass: a_an(o) } end end def modulo(o) divmod(o)[1] end def %(o) modulo(o) end def div(o) case o when Timespan # Timespan/Timespan yields a Float @nsecs.fdiv(o.nsecs) when Integer, Float Timespan.new(@nsecs.div(o)) else raise ArgumentError, _("A Timespan cannot be divided by %{klass}") % { klass: a_an(o) } end end def /(o) div(o) end # @return [Integer] a positive integer denoting the number of days def days total_days end # @return [Integer] a positive integer, 0 - 23 denoting hours of day def hours total_hours % 24 end # @return [Integer] a positive integer, 0 - 59 denoting minutes of hour def minutes total_minutes % 60 end # @return [Integer] a positive integer, 0 - 59 denoting seconds of minute def seconds total_seconds % 60 end # @return [Integer] a positive integer, 0 - 999 denoting milliseconds of second def milliseconds total_milliseconds % 1000 end # @return [Integer] a positive integer, 0 - 999.999.999 denoting nanoseconds of second def nanoseconds total_nanoseconds % NSECS_PER_SEC end # Formats this timestamp into a string according to the given `format` # # @param [String,Format] format The format to use when producing the string # @return [String] the string representing the formatted timestamp # @raise [ArgumentError] if the format is a string with illegal format characters # @api public def format(format) format = FormatParser.singleton.parse_format(format) unless format.is_a?(Format) format.format(self) end # Formats this timestamp into a string according to {Format::DEFAULTS[0]} # # @return [String] the string representing the formatted timestamp # @api public def to_s format(Format::DEFAULTS[0]) end def to_hash(compact = false) result = {} n = nanoseconds if compact s = total_seconds result[KEY_SECONDS] = negative? ? -s : s result[KEY_NANOSECONDS] = negative? ? -n : n unless n == 0 else add_unless_zero(result, KEY_DAYS, days) add_unless_zero(result, KEY_HOURS, hours) add_unless_zero(result, KEY_MINUTES, minutes) add_unless_zero(result, KEY_SECONDS, seconds) unless n == 0 add_unless_zero(result, KEY_NANOSECONDS, n % 1000) n /= 1000 add_unless_zero(result, KEY_MICROSECONDS, n % 1000) add_unless_zero(result, KEY_MILLISECONDS, n / 1000) end result[KEY_NEGATIVE] = true if negative? end result end def add_unless_zero(result, key, value) result[key] = value unless value == 0 end private :add_unless_zero # @api private def total_days total_nanoseconds / NSECS_PER_DAY end # @api private def total_hours total_nanoseconds / NSECS_PER_HOUR end # @api private def total_minutes total_nanoseconds / NSECS_PER_MIN end # @api private def total_seconds total_nanoseconds / NSECS_PER_SEC end # @api private def total_milliseconds total_nanoseconds / NSECS_PER_MSEC end # @api private def total_microseconds total_nanoseconds / NSECS_PER_USEC end # @api private def total_nanoseconds @nsecs.abs end # Represents a compiled Timestamp format. The format is used both when parsing a timestamp # in string format and when producing a string from a timestamp instance. # class Format # A segment is either a string that will be represented literally in the formatted timestamp # or a value that corresponds to one of the possible format characters. class Segment def append_to(bld, ts) raise NotImplementedError, "'#{self.class.name}' should implement #append_to" end def append_regexp(bld, ts) raise NotImplementedError, "'#{self.class.name}' should implement #append_regexp" end def multiplier raise NotImplementedError, "'#{self.class.name}' should implement #multiplier" end end class LiteralSegment < Segment def append_regexp(bld) bld << "(#{Regexp.escape(@literal)})" end def initialize(literal) @literal = literal end def append_to(bld, ts) bld << @literal end def concat(codepoint) @literal.concat(codepoint) end def nanoseconds 0 end end class ValueSegment < Segment def initialize(padchar, width, default_width) @use_total = false @padchar = padchar @width = width @default_width = default_width @format = create_format end def create_format case @padchar when nil '%d' when ' ' "%#{@width || @default_width}d" else "%#{@padchar}#{@width || @default_width}d" end end def append_regexp(bld) if @width.nil? case @padchar when nil bld << (use_total? ? '([0-9]+)' : "([0-9]{1,#{@default_width}})") when '0' bld << (use_total? ? '([0-9]+)' : "([0-9]{1,#{@default_width}})") else bld << (use_total? ? '\s*([0-9]+)' : "([0-9\\s]{1,#{@default_width}})") end else case @padchar when nil bld << "([0-9]{1,#{@width}})" when '0' bld << "([0-9]{#{@width}})" else bld << "([0-9\\s]{#{@width}})" end end end def nanoseconds(group) group.to_i * multiplier end def multiplier 0 end def set_use_total @use_total = true end def use_total? @use_total end def append_value(bld, n) bld << sprintf(@format, n) end end class DaySegment < ValueSegment def initialize(padchar, width) super(padchar, width, 1) end def multiplier NSECS_PER_DAY end def append_to(bld, ts) append_value(bld, ts.days) end end class HourSegment < ValueSegment def initialize(padchar, width) super(padchar, width, 2) end def multiplier NSECS_PER_HOUR end def append_to(bld, ts) append_value(bld, use_total? ? ts.total_hours : ts.hours) end end class MinuteSegment < ValueSegment def initialize(padchar, width) super(padchar, width, 2) end def multiplier NSECS_PER_MIN end def append_to(bld, ts) append_value(bld, use_total? ? ts.total_minutes : ts.minutes) end end class SecondSegment < ValueSegment def initialize(padchar, width) super(padchar, width, 2) end def multiplier NSECS_PER_SEC end def append_to(bld, ts) append_value(bld, use_total? ? ts.total_seconds : ts.seconds) end end # Class that assumes that leading zeroes are significant and that trailing zeroes are not and left justifies when formatting. # Applicable after a decimal point, and hence to the %L and %N formats. class FragmentSegment < ValueSegment def nanoseconds(group) # Using %L or %N to parse a string only makes sense when they are considered to be fractions. Using them # as a total quantity would introduce ambiguities. raise ArgumentError, _('Format specifiers %L and %N denotes fractions and must be used together with a specifier of higher magnitude') if use_total? n = group.to_i p = 9 - group.length p <= 0 ? n : n * 10 ** p end def create_format if @padchar.nil? '%d' else "%-#{@width || @default_width}d" end end def append_value(bld, n) # Strip trailing zeroes when default format is used n = n.to_s.sub(/\A([0-9]+?)0*\z/, '\1').to_i unless use_total? || @padchar == '0' super(bld, n) end end class MilliSecondSegment < FragmentSegment def initialize(padchar, width) super(padchar, width, 3) end def multiplier NSECS_PER_MSEC end def append_to(bld, ts) append_value(bld, use_total? ? ts.total_milliseconds : ts.milliseconds) end end class NanoSecondSegment < FragmentSegment def initialize(padchar, width) super(padchar, width, 9) end def multiplier width = @width || @default_width if width < 9 10 ** (9 - width) else 1 end end def append_to(bld, ts) ns = ts.total_nanoseconds width = @width || @default_width if width < 9 # Truncate digits to the right, i.e. let %6N reflect microseconds ns /= 10 ** (9 - width) ns %= 10 ** width unless use_total? else ns %= NSECS_PER_SEC unless use_total? end append_value(bld, ns) end end def initialize(format, segments) @format = format.freeze @segments = segments.freeze end def format(timespan) bld = timespan.negative? ? '-' : '' @segments.each { |segment| segment.append_to(bld, timespan) } bld end def parse(timespan) md = regexp.match(timespan) raise ArgumentError, _("Unable to parse '%{timespan}' using format '%{format}'") % { timespan: timespan, format: @format } if md.nil? nanoseconds = 0 md.captures.each_with_index do |group, index| segment = @segments[index] next if segment.is_a?(LiteralSegment) group.lstrip! raise ArgumentError, _("Unable to parse '%{timespan}' using format '%{format}'") % { timespan: timespan, format: @format } unless group =~ /\A[0-9]+\z/ nanoseconds += segment.nanoseconds(group) end Timespan.new(timespan.start_with?('-') ? -nanoseconds : nanoseconds) end def to_s @format end private def regexp @regexp ||= build_regexp end def build_regexp bld = '\A-?' @segments.each { |segment| segment.append_regexp(bld) } bld << '\z' Regexp.new(bld) end end # Parses a string into a Timestamp::Format instance class FormatParser def self.singleton @singleton end def initialize @formats = Hash.new { |hash, str| hash[str] = internal_parse(str) } end def parse_format(format) @formats[format] end private NSEC_MAX = 0 MSEC_MAX = 1 SEC_MAX = 2 MIN_MAX = 3 HOUR_MAX = 4 DAY_MAX = 5 SEGMENT_CLASS_BY_ORDINAL = [ Format::NanoSecondSegment, Format::MilliSecondSegment, Format::SecondSegment, Format::MinuteSegment, Format::HourSegment, Format::DaySegment ] def bad_format_specifier(format, start, position) _("Bad format specifier '%{expression}' in '%{format}', at position %{position}") % { expression: format[start,position-start], format: format, position: position } end def append_literal(bld, codepoint) if bld.empty? || !bld.last.is_a?(Format::LiteralSegment) bld << Format::LiteralSegment.new(''.concat(codepoint)) else bld.last.concat(codepoint) end end # States used by the #internal_parser function STATE_LITERAL = 0 # expects literal or '%' STATE_PAD = 1 # expects pad, width, or format character STATE_WIDTH = 2 # expects width, or format character def internal_parse(str) bld = [] raise ArgumentError, _('Format must be a String') unless str.is_a?(String) highest = -1 state = STATE_LITERAL padchar = '0' width = nil position = -1 fstart = 0 str.codepoints do |codepoint| position += 1 if state == STATE_LITERAL if codepoint == 0x25 # '%' state = STATE_PAD fstart = position padchar = '0' width = nil else append_literal(bld, codepoint) end next end case codepoint when 0x25 # '%' append_literal(bld, codepoint) state = STATE_LITERAL when 0x2D # '-' raise ArgumentError, bad_format_specifier(str, fstart, position) unless state == STATE_PAD padchar = nil state = STATE_WIDTH when 0x5F # '_' raise ArgumentError, bad_format_specifier(str, fstart, position) unless state == STATE_PAD padchar = ' ' state = STATE_WIDTH when 0x44 # 'D' highest = DAY_MAX bld << Format::DaySegment.new(padchar, width) state = STATE_LITERAL when 0x48 # 'H' highest = HOUR_MAX unless highest > HOUR_MAX bld << Format::HourSegment.new(padchar, width) state = STATE_LITERAL when 0x4D # 'M' highest = MIN_MAX unless highest > MIN_MAX bld << Format::MinuteSegment.new(padchar, width) state = STATE_LITERAL when 0x53 # 'S' highest = SEC_MAX unless highest > SEC_MAX bld << Format::SecondSegment.new(padchar, width) state = STATE_LITERAL when 0x4C # 'L' highest = MSEC_MAX unless highest > MSEC_MAX bld << Format::MilliSecondSegment.new(padchar, width) state = STATE_LITERAL when 0x4E # 'N' highest = NSEC_MAX unless highest > NSEC_MAX bld << Format::NanoSecondSegment.new(padchar, width) state = STATE_LITERAL else # only digits allowed at this point raise ArgumentError, bad_format_specifier(str, fstart, position) unless codepoint >= 0x30 && codepoint <= 0x39 if state == STATE_PAD && codepoint == 0x30 padchar = '0' else n = codepoint - 0x30 if width.nil? width = n else width = width * 10 + n end end state = STATE_WIDTH end end raise ArgumentError, bad_format_specifier(str, fstart, position) unless state == STATE_LITERAL unless highest == -1 hc = SEGMENT_CLASS_BY_ORDINAL[highest] bld.find { |s| s.instance_of?(hc) }.set_use_total end Format.new(str, bld) end @singleton = FormatParser.new end class Format DEFAULTS = ['%D-%H:%M:%S.%-N', '%H:%M:%S.%-N', '%M:%S.%-N', '%S.%-N', '%D-%H:%M:%S', '%H:%M:%S', '%D-%H:%M', '%S'].map { |str| FormatParser.singleton.parse_format(str) } end end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/time/timestamp.rb�����������������������������������������������������0000644�0052762�0001160�00000011224�13417161721�021344� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Time class Timestamp < TimeData DEFAULT_FORMATS_WO_TZ = ['%FT%T.%N', '%FT%T', '%F %T.%N', '%F %T', '%F'] DEFAULT_FORMATS = ['%FT%T.%N %Z', '%FT%T %Z', '%F %T.%N %Z', '%F %T %Z', '%F %Z'] + DEFAULT_FORMATS_WO_TZ CURRENT_TIMEZONE = 'current'.freeze KEY_TIMEZONE = 'timezone'.freeze # Converts a timezone that strptime can parse using '%z' into '-HH:MM' or '+HH:MM' # @param [String] tz the timezone to convert # @return [String] the converted timezone # # @api private def self.convert_timezone(tz) if tz =~ /\A[+-]\d\d:\d\d\z/ tz else offset = utc_offset(tz) / 60 if offset < 0 offset = offset.abs sprintf('-%2.2d:%2.2d', offset / 60, offset % 60) else sprintf('+%2.2d:%2.2d', offset / 60, offset % 60) end end end # Returns the zone offset from utc for the given `timezone` # @param [String] timezone the timezone to get the offset for # @return [Integer] the timezone offset, in seconds # # @api private def self.utc_offset(timezone) if CURRENT_TIMEZONE.casecmp(timezone) == 0 ::Time.now.utc_offset else hash = DateTime._strptime(timezone, '%z') offset = hash.nil? ? nil : hash[:offset] raise ArgumentError, _("Illegal timezone '%{timezone}'") % { timezone: timezone } if offset.nil? offset end end # Formats a ruby Time object using the given timezone def self.format_time(format, time, timezone) unless timezone.nil? || timezone.empty? time = time.localtime(convert_timezone(timezone)) end time.strftime(format) end def self.now from_time(::Time.now) end def self.from_time(t) new(t.tv_sec * NSECS_PER_SEC + t.tv_nsec) end def self.from_hash(args_hash) parse(args_hash[KEY_STRING], args_hash[KEY_FORMAT], args_hash[KEY_TIMEZONE]) end def self.parse(str, format = :default, timezone = nil) has_timezone = !(timezone.nil? || timezone.empty? || timezone == :default) if format.nil? || format == :default format = has_timezone ? DEFAULT_FORMATS_WO_TZ : DEFAULT_FORMATS end parsed = nil if format.is_a?(Array) format.each do |fmt| parsed = DateTime._strptime(str, fmt) next if parsed.nil? if parsed.include?(:leftover) || (has_timezone && parsed.include?(:zone)) parsed = nil next end break end if parsed.nil? raise ArgumentError, _( "Unable to parse '%{str}' using any of the formats %{formats}") % { str: str, formats: format.join(', ') } end else parsed = DateTime._strptime(str, format) if parsed.nil? || parsed.include?(:leftover) raise ArgumentError, _("Unable to parse '%{str}' using format '%{format}'") % { str: str, format: format } end if has_timezone && parsed.include?(:zone) raise ArgumentError, _( 'Using a Timezone designator in format specification is mutually exclusive to providing an explicit timezone argument') end end unless has_timezone timezone = parsed[:zone] has_timezone = !timezone.nil? end fraction = parsed[:sec_fraction] # Convert msec rational found in _strptime hash to usec fraction = fraction * 1000000 unless fraction.nil? # Create the Time instance and adjust for timezone parsed_time = ::Time.utc(parsed[:year], parsed[:mon], parsed[:mday], parsed[:hour], parsed[:min], parsed[:sec], fraction) parsed_time -= utc_offset(timezone) if has_timezone # Convert to Timestamp from_time(parsed_time) end undef_method :-@, :+@, :div, :fdiv, :abs, :abs2, :magnitude # does not make sense on a Timestamp if method_defined?(:negative?) undef_method :negative?, :positive? end if method_defined?(:%) undef_method :%, :modulo, :divmod end def +(o) case o when Timespan Timestamp.new(@nsecs + o.nsecs) when Integer, Float Timestamp.new(@nsecs + (o * NSECS_PER_SEC).to_i) else raise ArgumentError, _("%{klass} cannot be added to a Timestamp") % { klass: a_an_uc(o) } end end def -(o) case o when Timestamp # Diff between two timestamps is a timespan Timespan.new(@nsecs - o.nsecs) when Timespan Timestamp.new(@nsecs - o.nsecs) when Integer, Float # Subtract seconds Timestamp.new(@nsecs - (o * NSECS_PER_SEC).to_i) else raise ArgumentError, _("%{klass} cannot be subtracted from a Timestamp") % { klass: a_an_uc(o) } end end def format(format, timezone = nil) self.class.format_time(format, to_time, timezone) end def to_s format(DEFAULT_FORMATS[0]) end def to_time ::Time.at(to_r).utc end end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/����������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�017227� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/annotatable.rb��������������������������������������������������0000644�0052762�0001160�00000001512�13417161721�022036� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Types KEY_ANNOTATIONS = 'annotations'.freeze # Behaviour common to all Pcore annotatable classes # # @api public module Annotatable TYPE_ANNOTATIONS = PHashType.new(PTypeType.new(PTypeReferenceType.new('Annotation')), PHashType::DEFAULT) # @return [{PTypeType => PStructType}] the map of annotations # @api public def annotations @annotations.nil? ? EMPTY_HASH : @annotations end # @api private def init_annotatable(init_hash) @annotations = init_hash[KEY_ANNOTATIONS].freeze end # @api private def annotatable_accept(visitor, guard) @annotations.each_key { |key| key.accept(visitor, guard) } unless @annotations.nil? end # @api private def _pcore_init_hash result = {} result[KEY_ANNOTATIONS] = @annotations unless @annotations.nil? result end end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/annotation.rb���������������������������������������������������0000644�0052762�0001160�00000005237�13417161721�021730� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Types # Pcore variant of the Adaptable::Adapter. Uses a Pcore Object type instead of a Class class Annotation < Adaptable::Adapter include Types::PuppetObject CLEAR = 'clear'.freeze # Register the Annotation type. This is the type that all custom Annotations will inherit from. def self.register_ptype(loader, ir) @type = Pcore::create_object_type(loader, ir, self, 'Annotation', nil, EMPTY_HASH) end def self._pcore_type @type end # Finds an existing annotation for the given object and returns it. # If no annotation was found, and a block is given, a new annotation is created from the # initializer hash that must be returned from the block. # If no annotation was found and no block is given, this method returns `nil` # # @param o [Object] object to annotate # @param block [Proc] optional, evaluated when a new annotation must be created. Should return the init hash # @return [Annotation<self>] an annotation of the same class as the receiver of the call # def self.annotate(o) adapter = get(o) if adapter.nil? if o.is_a?(Annotatable) init_hash = o.annotations[_pcore_type] init_hash = yield if init_hash.nil? && block_given? else init_hash = yield if block_given? end adapter = associate_adapter(_pcore_type.from_hash(init_hash), o) unless init_hash.nil? end adapter end # Forces the creation or removal of an annotation of this type. # If `init_hash` is a hash, a new annotation is created and returned # If `init_hash` is `nil`, then the annotation is cleared and the previous annotation is returned. # # @param o [Object] object to annotate # @param init_hash [Hash{String,Object},nil] the initializer for the annotation or `nil` to clear the annotation # @return [Annotation<self>] an annotation of the same class as the receiver of the call # def self.annotate_new(o, init_hash) if o.is_a?(Annotatable) && o.annotations.include?(_pcore_type) # Prevent clear or redefine of annotations declared on type action = init_hash == CLEAR ? 'clear' : 'redefine' raise ArgumentError, "attempt to #{action} #{type_name} annotation declared on #{o.label}" end if init_hash == CLEAR clear(o) else associate_adapter(_pcore_type.from_hash(init_hash), o) end end # Uses name of type instead of name of the class (the class is likely dynamically generated and as such, # has no name) # @return [String] the name of the type def self.type_name _pcore_type.name end end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/class_loader.rb�������������������������������������������������0000644�0052762�0001160�00000010613�13417161721�022203� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Types # The ClassLoader provides a Class instance given a class name or a meta-type. # If the class is not already loaded, it is loaded using the Puppet Autoloader. # This means it can load a class from a gem, or from puppet modules. # class ClassLoader @autoloader = Puppet::Util::Autoload.new("ClassLoader", "") # Returns a Class given a fully qualified class name. # Lookup of class is never relative to the calling namespace. # @param name [String, Array<String>, Array<Symbol>, PAnyType] A fully qualified # class name String (e.g. '::Foo::Bar', 'Foo::Bar'), a PAnyType, or a fully qualified name in Array form where each part # is either a String or a Symbol, e.g. `%w{Puppetx Puppetlabs SomeExtension}`. # @return [Class, nil] the looked up class or nil if no such class is loaded # @raise ArgumentError If the given argument has the wrong type # @api public # def self.provide(name) case name when String provide_from_string(name) when Array provide_from_name_path(name.join('::'), name) when PAnyType, PTypeType provide_from_type(name) else raise ArgumentError, "Cannot provide a class from a '#{name.class.name}'" end end def self.provide_from_type(type) case type when PRuntimeType raise ArgumentError.new("Only Runtime type 'ruby' is supported, got #{type.runtime}") unless type.runtime == :ruby provide_from_string(type.runtime_type_name) when PBooleanType # There is no other thing to load except this Enum meta type RGen::MetamodelBuilder::MMBase::Boolean when PTypeType # TODO: PTypeType should have a type argument (a PAnyType) so the Class' class could be returned # (but this only matters in special circumstances when meta programming has been used). Class when POptionalType # cannot make a distinction between optional and its type provide_from_type(type.optional_type) # Although not expected to be the first choice for getting a concrete class for these # types, these are of value if the calling logic just has a reference to type. # when PArrayType ; Array when PTupleType ; Array when PHashType ; Hash when PStructType ; Hash when PRegexpType ; Regexp when PIntegerType ; Integer when PStringType ; String when PPatternType ; String when PEnumType ; String when PFloatType ; Float when PUndefType ; NilClass when PCallableType ; Proc else nil end end private_class_method :provide_from_type def self.provide_from_string(name) name_path = name.split(TypeFormatter::NAME_SEGMENT_SEPARATOR) # always from the root, so remove an empty first segment name_path.shift if name_path[0].empty? provide_from_name_path(name, name_path) end def self.provide_from_name_path(name, name_path) # If class is already loaded, try this first result = find_class(name_path) unless result.is_a?(Module) # Attempt to load it using the auto loader loaded_path = nil if paths_for_name(name_path).find {|path| loaded_path = path; @autoloader.load(path, Puppet.lookup(:current_environment)) } result = find_class(name_path) unless result.is_a?(Module) raise RuntimeError, "Loading of #{name} using relative path: '#{loaded_path}' did not create expected class" end end end return nil unless result.is_a?(Module) result end private_class_method :provide_from_string def self.find_class(name_path) name_path.reduce(Object) do |ns, name| begin ns.const_get(name, false) # don't search ancestors rescue NameError return nil end end end private_class_method :find_class def self.paths_for_name(fq_named_parts) # search two entries, one where all parts are decamelized, and one with names just downcased # TODO:this is not perfect - it will not produce the correct mix if a mix of styles are used # The alternative is to test many additional paths. # [fq_named_parts.map {|part| de_camel(part)}.join('/'), fq_named_parts.join('/').downcase ] end private_class_method :paths_for_name def self.de_camel(fq_name) fq_name.to_s.gsub(/::/, '/'). gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). gsub(/([a-z\d])([A-Z])/,'\1_\2'). tr("-", "_"). downcase end private_class_method :de_camel end end end ���������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/enumeration.rb��������������������������������������������������0000644�0052762�0001160�00000001026�13417161721�022074� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The Enumeration class provides default Enumerable::Enumerator creation for Puppet Programming Language # runtime objects that supports the concept of enumeration. # module Puppet::Pops::Types class Enumeration def self.enumerator(o) Puppet.deprecation_warning(_('Enumeration.enumerator is deprecated. Use Iterable.on instead')) Iterable.on(o) end def enumerator(o) Puppet.deprecation_warning(_('Enumeration.enumerator is deprecated. Use Iterable.on instead')) Iterable.on(o) end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/implementation_registry.rb��������������������������������������0000644�0052762�0001160�00000013611�13417161721�024526� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Types # The {ImplementationRegistry} maps names types in the Puppet Type System to names of corresponding implementation # modules/classes. Each mapping is unique and bidirectional so that for any given type name there is only one # implementation and vice versa. # # @api private class ImplementationRegistry TYPE_REGEXP_SUBST = TypeFactory.tuple([PRegexpType::DEFAULT, PStringType::NON_EMPTY]) # Create a new instance. This method is normally only called once # # @param parent [ImplementationRegistry, nil] the parent of this registry def initialize(parent = nil) @parent = parent @type_names_per_implementation = {} @implementations_per_type_name = {} @type_name_substitutions = [] @impl_name_substitutions = [] end # Register a bidirectional type mapping. # # @overload register_type_mapping(runtime_type, puppet_type) # @param runtime_type [PRuntimeType] type that represents the runtime module or class to map to a puppet type # @param puppet_type [PAnyType] type that will be mapped to the runtime module or class # @overload register_type_mapping(runtime_type, pattern_replacement) # @param runtime_type [PRuntimeType] type containing the pattern and replacement to map the runtime type to a puppet type # @param puppet_type [Array(Regexp,String)] the pattern and replacement to map a puppet type to a runtime type def register_type_mapping(runtime_type, puppet_type_or_pattern, _ = nil) TypeAsserter.assert_assignable('First argument of type mapping', PRuntimeType::RUBY, runtime_type) expr = runtime_type.name_or_pattern if expr.is_a?(Array) TypeAsserter.assert_instance_of('Second argument of type mapping', TYPE_REGEXP_SUBST, puppet_type_or_pattern) register_implementation_regexp(puppet_type_or_pattern, expr) else TypeAsserter.assert_instance_of('Second argument of type mapping', PTypeType::DEFAULT, puppet_type_or_pattern) register_implementation(puppet_type_or_pattern, expr) end end # Register a bidirectional namespace mapping # # @param type_namespace [String] the namespace for the puppet types # @param impl_namespace [String] the namespace for the implementations def register_implementation_namespace(type_namespace, impl_namespace, _ = nil) ns = TypeFormatter::NAME_SEGMENT_SEPARATOR register_implementation_regexp( [/\A#{type_namespace}#{ns}(\w+)\z/, "#{impl_namespace}#{ns}\\1"], [/\A#{impl_namespace}#{ns}(\w+)\z/, "#{type_namespace}#{ns}\\1"]) end # Register a bidirectional regexp mapping # # @param type_name_subst [Array(Regexp,String)] regexp and replacement mapping type names to runtime names # @param impl_name_subst [Array(Regexp,String)] regexp and replacement mapping runtime names to type names def register_implementation_regexp(type_name_subst, impl_name_subst, _ = nil) @type_name_substitutions << type_name_subst @impl_name_substitutions << impl_name_subst nil end # Register a bidirectional mapping between a type and an implementation # # @param type [PAnyType,String] the type or type name # @param impl_module[Module,String] the module or module name def register_implementation(type, impl_module, _ = nil) type = type.name if type.is_a?(PAnyType) impl_module = impl_module.name if impl_module.is_a?(Module) @type_names_per_implementation[impl_module] = type @implementations_per_type_name[type] = impl_module nil end # Find the name for the module that corresponds to the given type or type name # # @param type [PAnyType,String] the name of the type # @return [String,nil] the name of the implementation module, or `nil` if no mapping was found def module_name_for_type(type) type = type.name if type.is_a?(PAnyType) name = @parent.module_name_for_type(type) unless @parent.nil? name.nil? ? find_mapping(type, @implementations_per_type_name, @type_name_substitutions) : name end # Find the module that corresponds to the given type or type name # # @param type [PAnyType,String] the name of the type # @return [Module,nil] the name of the implementation module, or `nil` if no mapping was found def module_for_type(type) name = module_name_for_type(type) # TODO Shouldn't ClassLoader be module specific? name.nil? ? nil : ClassLoader.provide(name) end # Find the type name and loader that corresponds to the given runtime module or module name # # @param impl_module [Module,String] the implementation class or class name # @return [String,nil] the name of the type, or `nil` if no mapping was found def type_name_for_module(impl_module) impl_module = impl_module.name if impl_module.is_a?(Module) name = @parent.type_name_for_module(impl_module) unless @parent.nil? name.nil? ? find_mapping(impl_module, @type_names_per_implementation, @impl_name_substitutions) : name end # Find the name for, and then load, the type that corresponds to the given runtime module or module name # The method will return `nil` if no mapping is found, a TypeReference if a mapping was found but the # loader didn't find the type, or the loaded type. # # @param impl_module [Module,String] the implementation class or class name # @return [PAnyType,nil] the type, or `nil` if no mapping was found def type_for_module(impl_module) name = type_name_for_module(impl_module) if name.nil? nil else TypeParser.singleton.parse(name) end end private def find_mapping(name, names, substitutions) found = names[name] if found.nil? substitutions.each do |subst| substituted = name.sub(*subst) return substituted unless substituted == name end end found end end end end �����������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/iterable.rb�����������������������������������������������������0000644�0052762�0001160�00000024431�13417161721�021342� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops::Types # Implemented by classes that can produce an iterator to iterate over their contents module IteratorProducer def iterator raise ArgumentError, 'iterator() is not implemented' end end # The runtime Iterable type for an Iterable module Iterable # Produces an `Iterable` for one of the following types with the following characterstics: # # `String` - yields each character in the string # `Array` - yields each element in the array # `Hash` - yields each key/value pair as a two element array # `Integer` - when positive, yields each value from zero to the given number # `PIntegerType` - yields each element from min to max (inclusive) provided min < max and neither is unbounded. # `PEnumtype` - yields each possible value of the enum. # `Range` - yields an iterator for all elements in the range provided that the range start and end # are both integers or both strings and start is less than end using natural ordering. # `Dir` - yields each name in the directory # # An `ArgumentError` is raised for all other objects. # # @param o [Object] The object to produce an `Iterable` for # @return [Iterable,nil] The produced `Iterable` # @raise [ArgumentError] In case an `Iterable` cannot be produced # @api public def self.asserted_iterable(caller, obj) iter = self.on(obj) raise ArgumentError, "#{caller.class}(): wrong argument type (#{obj.class}; is not Iterable." if iter.nil? iter end # Produces an `Iterable` for one of the following types with the following characteristics: # # `String` - yields each character in the string # `Array` - yields each element in the array # `Hash` - yields each key/value pair as a two element array # `Integer` - when positive, yields each value from zero to the given number # `PIntegerType` - yields each element from min to max (inclusive) provided min < max and neither is unbounded. # `PEnumtype` - yields each possible value of the enum. # `Range` - yields an iterator for all elements in the range provided that the range start and end # are both integers or both strings and start is less than end using natural ordering. # `Dir` - yields each name in the directory # # The value `nil` is returned for all other objects. # # @param o [Object] The object to produce an `Iterable` for # @param element_type [PAnyType] the element type for the iterator. Optional (inferred if not provided) # @return [Iterable,nil] The produced `Iterable` or `nil` if it couldn't be produced # # @api public def self.on(o, element_type = nil) case o when IteratorProducer o.iterator when Iterable o when String Iterator.new(PStringType.new(PIntegerType.new(1, 1)), o.each_char) when Array if o.empty? Iterator.new(PUnitType::DEFAULT, o.each) else if element_type.nil? tc = TypeCalculator.singleton element_type = PVariantType.maybe_create(o.map {|e| tc.infer_set(e) }) end Iterator.new(element_type, o.each) end when Hash # Each element is a two element [key, value] tuple. if o.empty? HashIterator.new(PHashType::DEFAULT_KEY_PAIR_TUPLE, o.each) else if element_type.nil? tc = TypeCalculator.singleton element_type = PTupleType.new([ PVariantType.maybe_create(o.keys.map {|e| tc.infer_set(e) }), PVariantType.maybe_create(o.values.map {|e| tc.infer_set(e) })], PHashType::KEY_PAIR_TUPLE_SIZE) end HashIterator.new(element_type, o.each_pair) end when Integer if o == 0 Iterator.new(PUnitType::DEFAULT, o.times) elsif o > 0 IntegerRangeIterator.new(PIntegerType.new(0, o - 1)) else nil end when PIntegerType # a finite range will always produce at least one element since it's inclusive o.finite_range? ? IntegerRangeIterator.new(o) : nil when PEnumType Iterator.new(o, o.values.each) when PTypeAliasType on(o.resolved_type) when Range min = o.min max = o.max if min.is_a?(Integer) && max.is_a?(Integer) && max >= min IntegerRangeIterator.new(PIntegerType.new(min, max)) elsif min.is_a?(String) && max.is_a?(String) && max >= min # A generalized element type where only the size is inferred is used here since inferring the full # range might waste a lot of memory. if min.length < max.length shortest = min longest = max else shortest = max longest = min end Iterator.new(PStringType.new(PIntegerType.new(shortest.length, longest.length)), o.each) else # Unsupported range. It's either descending or nonsensical for other reasons (float, mixed types, etc.) nil end else # Not supported. We cannot determine the element type nil end end # Answers the question if there is an end to the iteration. Puppet does not currently provide any unbounded # iterables. # # @return [Boolean] `true` if the iteration is unbounded def self.unbounded?(object) case object when Iterable object.unbounded? when String,Integer,Array,Hash,Enumerator,PIntegerType,PEnumType,Dir false else TypeAsserter.assert_instance_of('', PIterableType::DEFAULT, object, false) !object.respond_to?(:size) end end def each(&block) step(1, &block) end def element_type PAnyType::DEFAULT end def reverse_each(&block) # Default implementation cannot propagate reverse_each to a new enumerator so chained # calls must put reverse_each last. raise ArgumentError, 'reverse_each() is not implemented' end def step(step, &block) # Default implementation cannot propagate step to a new enumerator so chained # calls must put stepping last. raise ArgumentError, 'step() is not implemented' end def to_a raise Puppet::Error, 'Attempt to create an Array from an unbounded Iterable' if unbounded? super end def hash_style? false end def unbounded? true end end # @api private class Iterator # Note! We do not include Enumerable module here since that would make this class respond # in a bad way to all enumerable methods. We want to delegate all those calls directly to # the contained @enumeration include Iterable def initialize(element_type, enumeration) @element_type = element_type @enumeration = enumeration end def element_type @element_type end def size @enumeration.size end def respond_to_missing?(name, include_private) @enumeration.respond_to?(name, include_private) end def method_missing(name, *arguments, &block) @enumeration.send(name, *arguments, &block) end def step(step, &block) raise ArgumentError if step <= 0 r = self r = r.step_iterator(step) if step > 1 if block_given? begin if block.arity == 1 loop { yield(r.next) } else loop { yield(*r.next) } end rescue StopIteration end self else r end end def reverse_each(&block) r = Iterator.new(@element_type, @enumeration.reverse_each) block_given? ? r.each(&block) : r end def step_iterator(step) StepIterator.new(@element_type, self, step) end def to_s et = element_type et.nil? ? 'Iterator-Value' : "Iterator[#{et.generalize}]-Value" end def unbounded? Iterable.unbounded?(@enumeration) end end # Special iterator used when iterating over hashes. Returns `true` for `#hash_style?` so that # it is possible to differentiate between two element arrays and key => value associations class HashIterator < Iterator def hash_style? true end end # @api private class StepIterator < Iterator include Enumerable def initialize(element_type, enumeration, step_size) super(element_type, enumeration) raise ArgumentError if step_size <= 0 @step_size = step_size end def next result = @enumeration.next skip = @step_size - 1 if skip > 0 begin skip.times { @enumeration.next } rescue StopIteration end end result end def reverse_each(&block) r = Iterator.new(@element_type, to_a.reverse_each) block_given? ? r.each(&block) : r end def size super / @step_size end end # @api private class IntegerRangeIterator < Iterator include Enumerable def initialize(range, step = 1) raise ArgumentError if step == 0 @range = range @step_size = step @current = (step < 0 ? range.to : range.from) - step end def element_type @range end def next value = @current + @step_size if @step_size < 0 raise StopIteration if value < @range.from else raise StopIteration if value > @range.to end @current = value end def reverse_each(&block) r = IntegerRangeIterator.new(@range, -@step_size) block_given? ? r.each(&block) : r end def size (@range.to - @range.from) / @step_size.abs end def step_iterator(step) # The step iterator must use a range that has its logical end truncated at an even step boundary. This will # fulfil two objectives: # 1. The element_type method should not report excessive integers as possible numbers # 2. A reversed iterator must start at the correct number # range = @range step = @step_size * step mod = (range.to - range.from) % step if mod < 0 range = PIntegerType.new(range.from - mod, range.to) elsif mod > 0 range = PIntegerType.new(range.from, range.to - mod) end IntegerRangeIterator.new(range, step) end def unbounded? false end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/p_init_type.rb��������������������������������������������������0000644�0052762�0001160�00000016011�13417161721�022071� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Types # @api public class PInitType < PTypeWithContainedType def self.register_ptype(loader, ir) create_ptype(loader, ir, 'AnyType', 'type' => { KEY_TYPE => POptionalType.new(PTypeType::DEFAULT), KEY_VALUE => nil }, 'init_args' => { KEY_TYPE => PArrayType::DEFAULT, KEY_VALUE => EMPTY_ARRAY } ) end attr_reader :init_args def initialize(type, init_args) super(type) @init_args = init_args.nil? ? EMPTY_ARRAY : init_args if type.nil? raise ArgumentError, _('Init cannot be parameterized with an undefined type and additional arguments') unless @init_args.empty? @initialized = true else @initialized = false end end def instance?(o, guard = nil) really_instance?(o, guard) == 1 end # @api private def really_instance?(o, guard = nil) if @type.nil? TypeFactory.rich_data.really_instance?(o) else assert_initialized guarded_recursion(guard, 0) do |g| v = @type.really_instance?(o, g) if v < 1 if @single_type s = @single_type.really_instance?(o, g) v = s if s > v end end if v < 1 if @other_type s = @other_type.really_instance?(o, g) s = @other_type.really_instance?([o], g) if s < 0 && @has_optional_single v = s if s > v end end v end end end def eql?(o) super && @init_args == o.init_args end def hash super ^ @init_args.hash end def new_function return super if type.nil? assert_initialized target_type = type single_type = @single_type if @init_args.empty? @new_function ||= Puppet::Functions.create_function(:new_Init, Puppet::Functions::InternalFunction) do @target_type = target_type @single_type = single_type dispatch :from_array do scope_param param 'Array', :value end dispatch :create do scope_param param 'Any', :value end def self.create(scope, value, func) func.call(scope, @target_type, value) end def self.from_array(scope, value, func) # If there is a single argument that matches the array, then that gets priority over # expanding the array into all arguments if @single_type.instance?(value) || (@other_type && !@other_type.instance?(value) && @has_optional_single && @other_type.instance?([value])) func.call(scope, @target_type, value) else func.call(scope, @target_type, *value) end end def from_array(scope, value) self.class.from_array(scope, value, loader.load(:function, 'new')) end def create(scope, value) self.class.create(scope, value, loader.load(:function, 'new')) end end else init_args = @init_args @new_function ||= Puppet::Functions.create_function(:new_Init, Puppet::Functions::InternalFunction) do @target_type = target_type @init_args = init_args dispatch :create do scope_param param 'Any', :value end def self.create(scope, value, func) func.call(scope, @target_type, value, *@init_args) end def create(scope, value) self.class.create(scope, value, loader.load(:function, 'new')) end end end end DEFAULT = PInitType.new(nil, EMPTY_ARRAY) EXACTLY_ONE = [1, 1].freeze def assert_initialized return self if @initialized @initialized = true @self_recursion = true begin # Filter out types that will provide a new_function but are unsuitable to be contained in Init # # Calling Init#new would cause endless recursion # The Optional is the same as Variant[T,Undef]. # The NotUndef is not meaningful to create instances of if @type.instance_of?(PInitType) || @type.instance_of?(POptionalType) || @type.instance_of?(PNotUndefType) raise ArgumentError.new end new_func = @type.new_function rescue ArgumentError raise ArgumentError, _("Creation of new instance of type '%{type_name}' is not supported") % { type_name: @type.to_s } end param_tuples = new_func.dispatcher.signatures.map { |closure| closure.type.param_types } # An instance of the contained type is always a match to this type. single_types = [@type] if @init_args.empty? # A value that is assignable to the type of a single parameter is also a match single_tuples, other_tuples = param_tuples.partition { |tuple| EXACTLY_ONE == tuple.size_range } single_types.concat(single_tuples.map { |tuple| tuple.types[0] }) else tc = TypeCalculator.singleton init_arg_types = @init_args.map { |arg| tc.infer_set(arg) } arg_count = 1 + init_arg_types.size # disqualify all parameter tuples that doesn't allow one value (type unknown at ths stage) + init args. param_tuples = param_tuples.select do |tuple| min, max = tuple.size_range if arg_count >= min && arg_count <= max # Aside from the first parameter, does the other parameters match? tuple.assignable?(PTupleType.new(tuple.types[0..0].concat(init_arg_types))) else false end end if param_tuples.empty? raise ArgumentError, _("The type '%{type}' does not represent a valid set of parameters for %{subject}.new()") % { type: to_s, subject: @type.generalize.name } end single_types.concat(param_tuples.map { |tuple| tuple.types[0] }) other_tuples = EMPTY_ARRAY end @single_type = PVariantType.maybe_create(single_types) unless other_tuples.empty? @other_type = PVariantType.maybe_create(other_tuples) @has_optional_single = other_tuples.any? { |tuple| tuple.size_range.min == 1 } end guard = RecursionGuard.new accept(NoopTypeAcceptor::INSTANCE, guard) @self_recursion = guard.recursive_this?(self) end def accept(visitor, guard) guarded_recursion(guard, nil) do |g| super(visitor, g) @single_type.accept(visitor, guard) if @single_type @other_type.accept(visitor, guard) if @other_type end end protected def _assignable?(o, guard) guarded_recursion(guard, false) do |g| assert_initialized if o.is_a?(PInitType) @type.nil? || @type.assignable?(o.type, g) elsif @type.nil? TypeFactory.rich_data.assignable?(o, g) else @type.assignable?(o, g) || @single_type && @single_type.assignable?(o, g) || @other_type && (@other_type.assignable?(o, g) || @has_optional_single && @other_type.assignable?(PTupleType.new([o]))) end end end private def guarded_recursion(guard, dflt) if @self_recursion guard ||= RecursionGuard.new guard.with_this(self) { |state| (state & RecursionGuard::SELF_RECURSION_IN_THIS) == 0 ? yield(guard) : dflt } else yield(guard) end end end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/p_meta_type.rb��������������������������������������������������0000644�0052762�0001160�00000005251�13417161721�022060� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# @abstract base class for PObjectType and other types that implements lazy evaluation of content # @api private module Puppet::Pops module Types KEY_NAME = 'name'.freeze KEY_TYPE = 'type'.freeze KEY_VALUE = 'value'.freeze class PMetaType < PAnyType include Annotatable attr_reader :loader def self.register_ptype(loader, ir) # Abstract type. It doesn't register anything end def accept(visitor, guard) annotatable_accept(visitor, guard) super end def instance?(o, guard = nil) assignable?(TypeCalculator.infer(o), guard) end # Called from the TypeParser once it has found a type using the Loader. The TypeParser will # interpret the contained expression and the resolved type is remembered. This method also # checks and remembers if the resolve type contains self recursion. # # @param type_parser [TypeParser] type parser that will interpret the type expression # @param loader [Loader::Loader] loader to use when loading type aliases # @return [PTypeAliasType] the receiver of the call, i.e. `self` # @api private def resolve(loader) unless @init_hash_expression.nil? @loader = loader @self_recursion = true # assumed while it being found out below init_hash_expression = @init_hash_expression @init_hash_expression = nil if init_hash_expression.is_a?(Model::LiteralHash) init_hash = resolve_literal_hash(loader, init_hash_expression) else init_hash = resolve_hash(loader, init_hash_expression) end _pcore_init_from_hash(init_hash) # Find out if this type is recursive. A recursive type has performance implications # on several methods and this knowledge is used to avoid that for non-recursive # types. guard = RecursionGuard.new accept(NoopTypeAcceptor::INSTANCE, guard) @self_recursion = guard.recursive_this?(self) end self end def resolve_literal_hash(loader, init_hash_expression) TypeParser.singleton.interpret_LiteralHash(init_hash_expression, loader) end def resolve_hash(loader, init_hash) resolve_type_refs(loader, init_hash) end def resolve_type_refs(loader, o) case o when Hash Hash[o.map { |k, v| [resolve_type_refs(loader, k), resolve_type_refs(loader, v)] }] when Array o.map { |e| resolve_type_refs(loader, e) } when PAnyType o.resolve(loader) else o end end def resolved? @init_hash_expression.nil? end # Returns the expanded string the form of the alias, e.g. <alias name> = <resolved type> # # @return [String] the expanded form of this alias # @api public def to_s TypeFormatter.singleton.alias_expanded_string(self) end end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/p_object_type_extension.rb��������������������������������������0000644�0052762�0001160�00000014744�13417161721�024503� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Types # Base class for Parameterized Object implementations. The wrapper impersonates the base # object and extends it with methods to filter assignable types and instances based on parameter # values. # # @api public class PObjectTypeExtension < PAnyType include TypeWithMembers def self.register_ptype(loader, ir) create_ptype(loader, ir, 'AnyType', 'base_type' => { KEY_TYPE => PTypeType::DEFAULT }, 'init_parameters' => { KEY_TYPE => PArrayType::DEFAULT } ) end attr_reader :base_type, :parameters # @api private def self.create(base_type, init_parameters) impl_class = Loaders.implementation_registry.module_for_type("#{base_type.name}TypeExtension") || self impl_class.new(base_type, init_parameters) end # Creates an array of type parameters from the attributes of the given instance that matches the # type parameters by name. Type parameters for which there is no matching attribute # will have `nil` in their corresponding position on the array. The array is then passed # as the `init_parameters` argument in a call to `create` # # @return [PObjectTypeExtension] the created extension # @api private def self.create_from_instance(base_type, instance) type_parameters = base_type.type_parameters(true) attrs = base_type.attributes(true) params = type_parameters.keys.map do |pn| attr = attrs[pn] attr.nil? ? nil : instance.send(pn) end create(base_type, params) end def [](name) @base_type[name] end # @api private def initialize(base_type, init_parameters) pts = base_type.type_parameters(true) raise Puppet::ParseError, _('The %{label}-Type cannot be parameterized using []') % { label: base_type.label } if pts.empty? @base_type = base_type named_args = init_parameters.size == 1 && init_parameters[0].is_a?(Hash) if named_args # Catch case when first parameter is an assignable Hash named_args = pts.size >= 1 && !pts.values[0].type.instance?(init_parameters[0]) end by_name = {} if named_args hash = init_parameters[0] hash.each_pair do |pn, pv| tp = pts[pn] if tp.nil? raise Puppet::ParseError, _("'%{pn}' is not a known type parameter for %{label}-Type") % { pn: pn, label: base_type.label } end by_name[pn] = check_param(tp, pv) unless pv == :default end else pts.values.each_with_index do |tp, idx| if idx < init_parameters.size pv = init_parameters[idx] by_name[tp.name] = check_param(tp, pv) unless pv == :default end end end if by_name.empty? raise Puppet::ParseError, _('The %{label}-Type cannot be parameterized using an empty parameter list') % { label: base_type.label } end @parameters = by_name end def check_param(type_param, v) TypeAsserter.assert_instance_of(nil, type_param.type, v) { type_param.label } end # Return the parameter values as positional arguments with unset values as :default. The # array is stripped from trailing :default values # @return [Array] the parameter values # @api private def init_parameters pts = @base_type.type_parameters(true) if pts.size > 2 @parameters else result = pts.values.map do |tp| pn = tp.name @parameters.include?(pn) ? @parameters[pn] : :default end # Remove trailing defaults result.pop while result.last == :default result end end # @api private def eql?(o) super(o) && @base_type.eql?(o.base_type) && @parameters.eql?(o.parameters) end # @api private def generalize @base_type end # @api private def hash @base_type.hash ^ @parameters.hash end # @api private def loader @base_type.loader end # @api private def check_self_recursion(originator) @base_type.check_self_recursion(originator) end # @api private def create(*args) @base_type.create(*args) end # @api private def instance?(o, guard = nil) @base_type.instance?(o, guard) && test_instance?(o, guard) end # @api private def new_function @base_type.new_function end # @api private def simple_name @base_type.simple_name end protected # Checks that the given `param_values` hash contains all keys present in the `parameters` of # this instance and that each keyed value is a match for the given parameter. The match is done # using case expression semantics. # # This method is only called when a given type is found to be assignable to the base type of # this extension. # # @param param_values[Hash] the parameter values of the assignable type # @param guard[RecursionGuard] guard against endless recursion # @return [Boolean] true or false to indicate assignability # @api public def test_assignable?(param_values, guard) # Default implementation performs case expression style matching of all parameter values # provided that the value exist (this should always be the case, since all defaults have # been assigned at this point) eval = Parser::EvaluatingParser.singleton.evaluator @parameters.keys.all? do |pn| if param_values.include?(pn) a = param_values[pn] b = @parameters[pn] eval.match?(a, b) || a.is_a?(PAnyType) && b.is_a?(PAnyType) && b.assignable?(a) else false end end end # Checks that the given instance `o` has one attribute for each key present in the `parameters` of # this instance and that each attribute value is a match for the given parameter. The match is done # using case expression semantics. # # This method is only called when the given value is found to be an instance of the base type of # this extension. # # @param o [Object] the instance to test # @param guard[RecursionGuard] guard against endless recursion # @return [Boolean] true or false to indicate if the value is an instance or not # @api public def test_instance?(o, guard) eval = Parser::EvaluatingParser.singleton.evaluator @parameters.keys.all? do |pn| begin m = o.public_method(pn) m.arity == 0 ? eval.match?(m.call, @parameters[pn]) : false rescue NameError false end end end # @api private def _assignable?(o, guard = nil) if o.is_a?(PObjectTypeExtension) @base_type.assignable?(o.base_type, guard) && test_assignable?(o.parameters, guard) else @base_type.assignable?(o, guard) && test_assignable?(EMPTY_HASH, guard) end end end end end ����������������������������puppet-5.5.10/lib/puppet/pops/types/p_runtime_type.rb�����������������������������������������������0000644�0052762�0001160�00000006611�13417161721�022616� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Types # @api public class PRuntimeType < PAnyType TYPE_NAME_OR_PATTERN = PVariantType.new([PStringType::NON_EMPTY, PTupleType.new([PRegexpType::DEFAULT, PStringType::NON_EMPTY])]) def self.register_ptype(loader, ir) create_ptype(loader, ir, 'AnyType', 'runtime' => { KEY_TYPE => POptionalType.new(PStringType::NON_EMPTY), KEY_VALUE => nil }, 'name_or_pattern' => { KEY_TYPE => POptionalType.new(TYPE_NAME_OR_PATTERN), KEY_VALUE => nil } ) end attr_reader :runtime, :name_or_pattern # Creates a new instance of a Runtime type # # @param runtime [String] the name of the runtime, e.g. 'ruby' # @param name_or_pattern [String,Array(Regexp,String)] name of runtime or two patterns, mapping Puppet name => runtime name # @api public def initialize(runtime, name_or_pattern) unless runtime.nil? || runtime.is_a?(Symbol) runtime = TypeAsserter.assert_instance_of("Runtime 'runtime'", PStringType::NON_EMPTY, runtime).to_sym end @runtime = runtime @name_or_pattern = TypeAsserter.assert_instance_of("Runtime 'name_or_pattern'", TYPE_NAME_OR_PATTERN, name_or_pattern, true) end def hash @runtime.hash ^ @name_or_pattern.hash end def eql?(o) self.class == o.class && @runtime == o.runtime && @name_or_pattern == o.name_or_pattern end def instance?(o, guard = nil) assignable?(TypeCalculator.infer(o), guard) end def iterable?(guard = nil) if @runtime == :ruby && !runtime_type_name.nil? begin c = ClassLoader.provide(self) return c < Iterable unless c.nil? rescue ArgumentError end end false end def iterable_type(guard = nil) iterable?(guard) ? PIterableType.new(self) : nil end # @api private def runtime_type_name @name_or_pattern.is_a?(String) ? @name_or_pattern : nil end # @api private def class_or_module raise "Only ruby classes or modules can be produced by this runtime, got '#{runtime}" unless runtime == :ruby raise 'A pattern based Runtime type cannot produce a class or module' if @name_or_pattern.is_a?(Array) com = ClassLoader.provide(self) raise "The name #{@name_or_pattern} does not represent a ruby class or module" if com.nil? com end # @api private def from_puppet_name(puppet_name) if @name_or_pattern.is_a?(Array) substituted = puppet_name.sub(*@name_or_pattern) substituted == puppet_name ? nil : PRuntimeType.new(@runtime, substituted) else nil end end DEFAULT = PRuntimeType.new(nil, nil) RUBY = PRuntimeType.new(:ruby, nil) protected # Assignable if o's has the same runtime and the runtime name resolves to # a class that is the same or subclass of t1's resolved runtime type name # @api private def _assignable?(o, guard) return false unless o.is_a?(PRuntimeType) return false unless @runtime.nil? || @runtime == o.runtime return true if @name_or_pattern.nil? # t1 is wider onp = o.name_or_pattern return true if @name_or_pattern == onp return false unless @name_or_pattern.is_a?(String) && onp.is_a?(String) # NOTE: This only supports Ruby, must change when/if the set of runtimes is expanded begin c1 = ClassLoader.provide(self) c2 = ClassLoader.provide(o) c1.is_a?(Module) && c2.is_a?(Module) && !!(c2 <= c1) rescue ArgumentError false end end end end end �����������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/p_sem_ver_range_type.rb�����������������������������������������0000644�0052762�0001160�00000014312�13417161721�023744� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Types # An unparameterized type that represents all VersionRange instances # # @api public class PSemVerRangeType < PAnyType def self.register_ptype(loader, ir) create_ptype(loader, ir, 'AnyType') end # Check if a version is included in a version range. The version can be a string or # a `SemanticPuppet::SemVer` # # @param range [SemanticPuppet::VersionRange] the range to match against # @param version [SemanticPuppet::Version,String] the version to match # @return [Boolean] `true` if the range includes the given version # # @api public def self.include?(range, version) case version when SemanticPuppet::Version range.include?(version) when String begin range.include?(SemanticPuppet::Version.parse(version)) rescue SemanticPuppet::Version::ValidationFailure false end else false end end # Creates a {SemanticPuppet::VersionRange} from the given _version_range_ argument. If the argument is `nil` or # a {SemanticPuppet::VersionRange}, it is returned. If it is a {String}, it will be parsed into a # {SemanticPuppet::VersionRange}. Any other class will raise an {ArgumentError}. # # @param version_range [SemanticPuppet::VersionRange,String,nil] the version range to convert # @return [SemanticPuppet::VersionRange] the converted version range # @raise [ArgumentError] when the argument cannot be converted into a version range # def self.convert(version_range) case version_range when nil, SemanticPuppet::VersionRange version_range when String SemanticPuppet::VersionRange.parse(version_range) else raise ArgumentError, "Unable to convert a #{version_range.class.name} to a SemVerRange" end end # Checks if range _a_ is a sub-range of (i.e. completely covered by) range _b_ # @param a [SemanticPuppet::VersionRange] the first range # @param b [SemanticPuppet::VersionRange] the second range # # @return [Boolean] `true` if _a_ is completely covered by _b_ def self.covered_by?(a, b) b.begin <= a.begin && (b.end > a.end || b.end == a.end && (!b.exclude_end? || a.exclude_end?)) end # Merge two ranges so that the result matches all versions matched by both. A merge # is only possible when the ranges are either adjacent or have an overlap. # # @param a [SemanticPuppet::VersionRange] the first range # @param b [SemanticPuppet::VersionRange] the second range # @return [SemanticPuppet::VersionRange,nil] the result of the merge # # @api public def self.merge(a, b) if a.include?(b.begin) || b.include?(a.begin) max = [a.end, b.end].max exclude_end = false if a.exclude_end? exclude_end = max == a.end && (max > b.end || b.exclude_end?) elsif b.exclude_end? exclude_end = max == b.end && (max > a.end || a.exclude_end?) end SemanticPuppet::VersionRange.new([a.begin, b.begin].min, max, exclude_end) elsif a.exclude_end? && a.end == b.begin # Adjacent, a before b SemanticPuppet::VersionRange.new(a.begin, b.end, b.exclude_end?) elsif b.exclude_end? && b.end == a.begin # Adjacent, b before a SemanticPuppet::VersionRange.new(b.begin, a.end, a.exclude_end?) else # No overlap nil end end def roundtrip_with_string? true end def instance?(o, guard = nil) o.is_a?(SemanticPuppet::VersionRange) end def eql?(o) self.class == o.class end def hash? super ^ @version_range.hash end def self.new_function(type) @new_function ||= Puppet::Functions.create_loaded_function(:new_VersionRange, type.loader) do local_types do type 'SemVerRangeString = String[1]' type 'SemVerRangeHash = Struct[{min=>Variant[Default,SemVer],Optional[max]=>Variant[Default,SemVer],Optional[exclude_max]=>Boolean}]' end # Constructs a VersionRange from a String with a format specified by # # https://github.com/npm/node-semver#range-grammar # # The logical or || operator is not implemented since it effectively builds # an array of ranges that may be disparate. The {{SemanticPuppet::VersionRange}} inherits # from the standard ruby range. It must be possible to describe that range in terms # of min, max, and exclude max. # # The Puppet Version type is parameterized and accepts multiple ranges so creating such # constraints is still possible. It will just require several parameters rather than one # parameter containing the '||' operator. # dispatch :from_string do param 'SemVerRangeString', :str end # Constructs a VersionRange from a min, and a max version. The Boolean argument denotes # whether or not the max version is excluded or included in the range. It is included by # default. # dispatch :from_versions do param 'Variant[Default,SemVer]', :min param 'Variant[Default,SemVer]', :max optional_param 'Boolean', :exclude_max end # Same as #from_versions but each argument is instead given in a Hash # dispatch :from_hash do param 'SemVerRangeHash', :hash_args end def from_string(str) SemanticPuppet::VersionRange.parse(str) end def from_versions(min, max = :default, exclude_max = false) min = SemanticPuppet::Version::MIN if min == :default max = SemanticPuppet::Version::MAX if max == :default SemanticPuppet::VersionRange.new(min, max, exclude_max) end def from_hash(hash) from_versions(hash['min'], hash.fetch('max') { :default }, hash.fetch('exclude_max') { false }) end end end DEFAULT = PSemVerRangeType.new protected def _assignable?(o, guard) self == o end def self.range_pattern part = '(?<part>[0-9A-Za-z-]+)' parts = "(?<parts>#{part}(?:\\.\\g<part>)*)" qualifier = "(?:-#{parts})?(?:\\+\\g<parts>)?" xr = '(?<xr>[xX*]|0|[1-9][0-9]*)' partial = "(?<partial>#{xr}(?:\\.\\g<xr>(?:\\.\\g<xr>#{qualifier})?)?)" hyphen = "(?:#{partial}\\s+-\\s+\\g<partial>)" simple = "(?<simple>(?:<|>|>=|<=|~|\\^)?\\g<partial>)" "#{hyphen}|#{simple}(?:\\s+\\g<simple>)*" end private_class_method :range_pattern end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/p_sem_ver_type.rb�����������������������������������������������0000644�0052762�0001160�00000010513�13417161721�022567� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Types # A Puppet Language Type that exposes the {{SemanticPuppet::Version}} and {{SemanticPuppet::VersionRange}}. # The version type is parameterized with version ranges. # # @api public class PSemVerType < PScalarType def self.register_ptype(loader, ir) create_ptype(loader, ir, 'ScalarType', 'ranges' => { KEY_TYPE => PArrayType.new(PVariantType.new([PSemVerRangeType::DEFAULT,PStringType::NON_EMPTY])), KEY_VALUE => [] } ) end attr_reader :ranges def initialize(ranges) ranges = ranges.map { |range| range.is_a?(SemanticPuppet::VersionRange) ? range : SemanticPuppet::VersionRange.parse(range) } ranges = merge_ranges(ranges) if ranges.size > 1 @ranges = ranges end def instance?(o, guard = nil) o.is_a?(SemanticPuppet::Version) && (@ranges.empty? || @ranges.any? {|range| range.include?(o) }) end def eql?(o) self.class == o.class && @ranges == o.ranges end def hash? super ^ @ranges.hash end # Creates a SemVer version from the given _version_ argument. If the argument is `nil` or # a {SemanticPuppet::Version}, it is returned. If it is a {String}, it will be parsed into a # {SemanticPuppet::Version}. Any other class will raise an {ArgumentError}. # # @param version [SemanticPuppet::Version,String,nil] the version to convert # @return [SemanticPuppet::Version] the converted version # @raise [ArgumentError] when the argument cannot be converted into a version # def self.convert(version) case version when nil, SemanticPuppet::Version version when String SemanticPuppet::Version.parse(version) else raise ArgumentError, "Unable to convert a #{version.class.name} to a SemVer" end end # @api private def self.new_function(type) @new_function ||= Puppet::Functions.create_loaded_function(:new_Version, type.loader) do local_types do type 'PositiveInteger = Integer[0,default]' type 'SemVerQualifier = Pattern[/\A(?<part>[0-9A-Za-z-]+)(?:\.\g<part>)*\Z/]' type "SemVerPattern = Pattern[/\\A#{SemanticPuppet::Version::REGEX_FULL}\\Z/]" type 'SemVerHash = Struct[{major=>PositiveInteger,minor=>PositiveInteger,patch=>PositiveInteger,Optional[prerelease]=>SemVerQualifier,Optional[build]=>SemVerQualifier}]' end # Creates a SemVer from a string as specified by http://semver.org/ # dispatch :from_string do param 'SemVerPattern', :str end # Creates a SemVer from integers, prerelease, and build arguments # dispatch :from_args do param 'PositiveInteger', :major param 'PositiveInteger', :minor param 'PositiveInteger', :patch optional_param 'SemVerQualifier', :prerelease optional_param 'SemVerQualifier', :build end # Same as #from_args but each argument is instead given in a Hash # dispatch :from_hash do param 'SemVerHash', :hash_args end argument_mismatch :on_error do param 'String', :str end def from_string(str) SemanticPuppet::Version.parse(str) end def from_args(major, minor, patch, prerelease = nil, build = nil) SemanticPuppet::Version.new(major, minor, patch, prerelease, build) end def from_hash(hash) SemanticPuppet::Version.new(hash['major'], hash['minor'], hash['patch'], hash['prerelease'], hash['build']) end def on_error(str) _("The string '%{str}' cannot be converted to a SemVer") % { str: str } end end end DEFAULT = PSemVerType.new(EMPTY_ARRAY) protected def _assignable?(o, guard) return false unless o.class == self.class return true if @ranges.empty? return false if o.ranges.empty? # All ranges in o must be covered by at least one range in self o.ranges.all? do |o_range| @ranges.any? do |range| PSemVerRangeType.covered_by?(o_range, range) end end end # @api private def merge_ranges(ranges) result = [] until ranges.empty? unmerged = [] x = ranges.pop result << ranges.inject(x) do |memo, y| merged = PSemVerRangeType.merge(memo, y) if merged.nil? unmerged << y else memo = merged end memo end ranges = unmerged end result.reverse! end end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/p_sensitive_type.rb���������������������������������������������0000644�0052762�0001160�00000002551�13417161721�023143� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Types # A Puppet Language type that wraps sensitive information. The sensitive type is parameterized by # the wrapped value type. # # # @api public class PSensitiveType < PTypeWithContainedType class Sensitive def initialize(value) @value = value end def unwrap @value end def to_s "Sensitive [value redacted]" end def inspect "#<#{to_s}>" end end def self.register_ptype(loader, ir) create_ptype(loader, ir, 'AnyType') end def initialize(type = nil) @type = type.nil? ? PAnyType.new : type.generalize end def instance?(o, guard = nil) o.is_a?(Sensitive) && @type.instance?(o.unwrap, guard) end def self.new_function(type) @new_function ||= Puppet::Functions.create_loaded_function(:new_Sensitive, type.loader) do dispatch :from_sensitive do param 'Sensitive', :value end dispatch :from_any do param 'Any', :value end def from_any(value) Sensitive.new(value) end # Since the Sensitive value is immutable we can reuse the existing instance instead of making a copy. def from_sensitive(value) value end end end private def _assignable?(o, guard) self.class == o.class && @type.assignable?(o.type, guard) end DEFAULT = PSensitiveType.new end end end �������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/p_timespan_type.rb����������������������������������������������0000644�0052762�0001160�00000013504�13417161721�022752� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Types class PAbstractTimeDataType < PScalarType # @param from [AbstractTime] lower bound for this type. Nil or :default means unbounded # @param to [AbstractTime] upper bound for this type. Nil or :default means unbounded def initialize(from, to = nil) @from = convert_arg(from, true) @to = convert_arg(to, false) raise ArgumentError, "'from' must be less or equal to 'to'. Got (#{@from}, #{@to}" unless @from <= @to end # Checks if this numeric range intersects with another # # @param o [PNumericType] the range to compare with # @return [Boolean] `true` if this range intersects with the other range # @api public def intersect?(o) self.class == o.class && !(@to < o.numeric_from || o.numeric_to < @from) end # Returns the lower bound of the numeric range or `nil` if no lower bound is set. # @return [Float,Integer] def from @from == -Float::INFINITY ? nil : @from end # Returns the upper bound of the numeric range or `nil` if no upper bound is set. # @return [Float,Integer] def to @to == Float::INFINITY ? nil : @to end # Same as #from but will return `-Float::Infinity` instead of `nil` if no lower bound is set. # @return [Float,Integer] def numeric_from @from end # Same as #to but will return `Float::Infinity` instead of `nil` if no lower bound is set. # @return [Float,Integer] def numeric_to @to end def hash @from.hash ^ @to.hash end def eql?(o) self.class == o.class && @from == o.numeric_from && @to == o.numeric_to end def unbounded? @from == -Float::INFINITY && @to == Float::INFINITY end def convert_arg(arg, min) case arg when impl_class arg when Hash impl_class.from_hash(arg) when nil, :default min ? -Float::INFINITY : Float::INFINITY when String impl_class.parse(arg) when Integer impl_class.new(arg * Time::NSECS_PER_SEC) when Float impl_class.new(arg * Time::NSECS_PER_SEC) else raise ArgumentError, "Unable to create a #{impl_class.name} from a #{arg.class.name}" unless arg.nil? || arg == :default nil end end # Concatenates this range with another range provided that the ranges intersect or # are adjacent. When that's not the case, this method will return `nil` # # @param o [PAbstractTimeDataType] the range to concatenate with this range # @return [PAbstractTimeDataType,nil] the concatenated range or `nil` when the ranges were apart # @api public def merge(o) if intersect?(o) || adjacent?(o) new_min = numeric_from <= o.numeric_from ? numeric_from : o.numeric_from new_max = numeric_to >= o.numeric_to ? numeric_to : o.numeric_to self.class.new(new_min, new_max) else nil end end def _assignable?(o, guard) self.class == o.class && numeric_from <= o.numeric_from && numeric_to >= o.numeric_to end end class PTimespanType < PAbstractTimeDataType def self.register_ptype(loader, ir) create_ptype(loader, ir, 'ScalarType', 'from' => { KEY_TYPE => POptionalType.new(PTimespanType::DEFAULT), KEY_VALUE => nil }, 'to' => { KEY_TYPE => POptionalType.new(PTimespanType::DEFAULT), KEY_VALUE => nil } ) end def self.new_function(type) @new_function ||= Puppet::Functions.create_loaded_function(:new_timespan, type.loader) do local_types do type 'Formats = Variant[String[2],Array[String[2], 1]]' end dispatch :from_seconds do param 'Variant[Integer,Float]', :seconds end dispatch :from_string do param 'String[1]', :string optional_param 'Formats', :format end dispatch :from_fields do param 'Integer', :days param 'Integer', :hours param 'Integer', :minutes param 'Integer', :seconds optional_param 'Integer', :milliseconds optional_param 'Integer', :microseconds optional_param 'Integer', :nanoseconds end dispatch :from_string_hash do param <<-TYPE, :hash_arg Struct[{ string => String[1], Optional[format] => Formats }] TYPE end dispatch :from_fields_hash do param <<-TYPE, :hash_arg Struct[{ Optional[negative] => Boolean, Optional[days] => Integer, Optional[hours] => Integer, Optional[minutes] => Integer, Optional[seconds] => Integer, Optional[milliseconds] => Integer, Optional[microseconds] => Integer, Optional[nanoseconds] => Integer }] TYPE end def from_seconds(seconds) Time::Timespan.new((seconds * Time::NSECS_PER_SEC).to_i) end def from_string(string, format = Time::Timespan::Format::DEFAULTS) Time::Timespan.parse(string, format) end def from_fields(days, hours, minutes, seconds, milliseconds = 0, microseconds = 0, nanoseconds = 0) Time::Timespan.from_fields(false, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds) end def from_string_hash(args_hash) Time::Timespan.from_string_hash(args_hash) end def from_fields_hash(args_hash) Time::Timespan.from_fields_hash(args_hash) end end end def generalize DEFAULT end def impl_class Time::Timespan end def instance?(o, guard = nil) o.is_a?(Time::Timespan) && o >= @from && o <= @to end DEFAULT = PTimespanType.new(nil, nil) end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/p_timestamp_type.rb���������������������������������������������0000644�0052762�0001160�00000003513�13417161721�023134� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Types class PTimestampType < PAbstractTimeDataType def self.register_ptype(loader, ir) create_ptype(loader, ir, 'ScalarType', 'from' => { KEY_TYPE => POptionalType.new(PTimestampType::DEFAULT), KEY_VALUE => nil }, 'to' => { KEY_TYPE => POptionalType.new(PTimestampType::DEFAULT), KEY_VALUE => nil } ) end def self.new_function(type) @new_function ||= Puppet::Functions.create_loaded_function(:new_timestamp, type.loader) do local_types do type 'Formats = Variant[String[2],Array[String[2], 1]]' end dispatch :now do end dispatch :from_seconds do param 'Variant[Integer,Float]', :seconds end dispatch :from_string do param 'String[1]', :string optional_param 'Formats', :format optional_param 'String[1]', :timezone end dispatch :from_string_hash do param <<-TYPE, :hash_arg Struct[{ string => String[1], Optional[format] => Formats, Optional[timezone] => String[1] }] TYPE end def now Time::Timestamp.now end def from_string(string, format = :default, timezone = nil) Time::Timestamp.parse(string, format, timezone) end def from_string_hash(args_hash) Time::Timestamp.from_hash(args_hash) end def from_seconds(seconds) Time::Timestamp.new((seconds * Time::NSECS_PER_SEC).to_i) end end end def generalize DEFAULT end def impl_class Time::Timestamp end def instance?(o, guard = nil) o.is_a?(Time::Timestamp) && o >= @from && o <= @to end DEFAULT = PTimestampType.new(nil, nil) end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/p_type_set_type.rb����������������������������������������������0000644�0052762�0001160�00000033553�13417161721�022774� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Types KEY_NAME_AUTHORITY = 'name_authority'.freeze KEY_TYPES = 'types'.freeze KEY_ALIAS = 'alias'.freeze KEY_VERSION = 'version'.freeze KEY_VERSION_RANGE = 'version_range'.freeze KEY_REFERENCES = 'references'.freeze class PTypeSetType < PMetaType # A Loader that makes the types known to the TypeSet visible # # @api private class TypeSetLoader < Loader::BaseLoader def initialize(type_set, parent) super(parent, "(TypeSetFirstLoader '#{type_set.name}')") @type_set = type_set end def name_authority @type_set.name_authority end def model_loader @type_set.loader end def find(typed_name) if typed_name.type == :type && typed_name.name_authority == @type_set.name_authority type = @type_set[typed_name.name] return set_entry(typed_name, type) unless type.nil? end nil end end TYPE_STRING_OR_VERSION = TypeFactory.variant(PStringType::NON_EMPTY, TypeFactory.sem_ver) TYPE_STRING_OR_RANGE = TypeFactory.variant(PStringType::NON_EMPTY, TypeFactory.sem_ver_range) TYPE_TYPE_REFERENCE_I12N = TypeFactory.struct({ KEY_NAME => Pcore::TYPE_QUALIFIED_REFERENCE, KEY_VERSION_RANGE => TYPE_STRING_OR_RANGE, TypeFactory.optional(KEY_NAME_AUTHORITY) => Pcore::TYPE_URI, TypeFactory.optional(KEY_ANNOTATIONS) => TYPE_ANNOTATIONS }) TYPE_TYPESET_I12N = TypeFactory.struct({ TypeFactory.optional(Pcore::KEY_PCORE_URI) => Pcore::TYPE_URI, Pcore::KEY_PCORE_VERSION => TYPE_STRING_OR_VERSION, TypeFactory.optional(KEY_NAME_AUTHORITY) => Pcore::TYPE_URI, TypeFactory.optional(KEY_NAME) => Pcore::TYPE_QUALIFIED_REFERENCE, TypeFactory.optional(KEY_VERSION) => TYPE_STRING_OR_VERSION, TypeFactory.optional(KEY_TYPES) => TypeFactory.hash_kv(Pcore::TYPE_SIMPLE_TYPE_NAME, PVariantType.new([PTypeType::DEFAULT, PObjectType::TYPE_OBJECT_I12N]), PCollectionType::NOT_EMPTY_SIZE), TypeFactory.optional(KEY_REFERENCES) => TypeFactory.hash_kv(Pcore::TYPE_SIMPLE_TYPE_NAME, TYPE_TYPE_REFERENCE_I12N, PCollectionType::NOT_EMPTY_SIZE), TypeFactory.optional(KEY_ANNOTATIONS) => TYPE_ANNOTATIONS, }) def self.register_ptype(loader, ir) create_ptype(loader, ir, 'AnyType', '_pcore_init_hash' => TYPE_TYPESET_I12N.resolve(loader)) end attr_reader :pcore_uri attr_reader :pcore_version attr_reader :name_authority attr_reader :name attr_reader :version attr_reader :types attr_reader :references attr_reader :annotations # Initialize a TypeSet Type instance. The initialization will use either a name and an initialization # hash expression, or a fully resolved initialization hash. # # @overload initialize(name, init_hash_expression) # Used when the TypeSet type is loaded using a type alias expression. When that happens, it is important that # the actual resolution of the expression is deferred until all definitions have been made known to the current # loader. The package will then be resolved when it is loaded by the {TypeParser}. "resolved" here, means that # the hash expression is fully resolved, and then passed to the {#_pcore_init_from_hash} method. # @param name [String] The name of the type set # @param init_hash_expression [Model::LiteralHash] The hash describing the TypeSet features # @param name_authority [String] The default name authority for the type set # # @overload initialize(init_hash) # Used when the package is created by the {TypeFactory}. The init_hash must be fully resolved. # @param init_hash [Hash{String=>Object}] The hash describing the TypeSet features # # @api private def initialize(name_or_init_hash, init_hash_expression = nil, name_authority = nil) @types = EMPTY_HASH @references = EMPTY_HASH if name_or_init_hash.is_a?(Hash) _pcore_init_from_hash(name_or_init_hash) else # Creation using "type XXX = TypeSet[{}]". This means that the name is given @name = TypeAsserter.assert_instance_of('TypeSet name', Pcore::TYPE_QUALIFIED_REFERENCE, name_or_init_hash) @name_authority = TypeAsserter.assert_instance_of('TypeSet name_authority', Pcore::TYPE_URI, name_authority, true) @init_hash_expression = init_hash_expression end end # @api private def _pcore_init_from_hash(init_hash) TypeAsserter.assert_instance_of('TypeSet initializer', TYPE_TYPESET_I12N, init_hash) # Name given to the loader have higher precedence than a name declared in the type @name ||= init_hash[KEY_NAME].freeze @name_authority ||= init_hash[KEY_NAME_AUTHORITY].freeze @pcore_version = PSemVerType.convert(init_hash[Pcore::KEY_PCORE_VERSION]).freeze unless Pcore::PARSABLE_PCORE_VERSIONS.include?(@pcore_version) raise ArgumentError, "The pcore version for TypeSet '#{@name}' is not understood by this runtime. Expected range #{Pcore::PARSABLE_PCORE_VERSIONS}, got #{@pcore_version}" end @pcore_uri = init_hash[Pcore::KEY_PCORE_URI].freeze @version = PSemVerType.convert(init_hash[KEY_VERSION]) @types = init_hash[KEY_TYPES] || EMPTY_HASH @types.freeze # Map downcase names to their camel-cased equivalent @dc_to_cc_map = {} @types.keys.each { |key| @dc_to_cc_map[key.downcase] = key } refs = init_hash[KEY_REFERENCES] if refs.nil? @references = EMPTY_HASH else ref_map = {} root_map = Hash.new { |h, k| h[k] = {} } refs.each do |ref_alias, ref| ref = TypeSetReference.new(self, ref) # Protect against importing the exact same name_authority/name combination twice if the version ranges intersect ref_name = ref.name ref_na = ref.name_authority || @name_authority na_roots = root_map[ref_na] ranges = na_roots[ref_name] if ranges.nil? na_roots[ref_name] = [ref.version_range] else unless ranges.all? { |range| (range & ref.version_range).nil? } raise ArgumentError, "TypeSet '#{@name}' references TypeSet '#{ref_na}/#{ref_name}' more than once using overlapping version ranges" end ranges << ref.version_range end if ref_map.has_key?(ref_alias) raise ArgumentError, "TypeSet '#{@name}' references a TypeSet using alias '#{ref_alias}' more than once" end if @types.has_key?(ref_alias) raise ArgumentError, "TypeSet '#{@name}' references a TypeSet using alias '#{ref_alias}'. The alias collides with the name of a declared type" end ref_map[ref_alias] = ref @dc_to_cc_map[ref_alias.downcase] = ref_alias ref_map[ref_alias] = ref end @references = ref_map.freeze end @dc_to_cc_map.freeze init_annotatable(init_hash) end # Produce a hash suitable for the initializer # @return [Hash{String => Object}] the initialization hash # # @api private def _pcore_init_hash result = super() result[Pcore::KEY_PCORE_URI] = @pcore_uri unless @pcore_uri.nil? result[Pcore::KEY_PCORE_VERSION] = @pcore_version.to_s result[KEY_NAME_AUTHORITY] = @name_authority unless @name_authority.nil? result[KEY_NAME] = @name result[KEY_VERSION] = @version.to_s unless @version.nil? result[KEY_TYPES] = @types unless @types.empty? result[KEY_REFERENCES] = Hash[@references.map { |ref_alias, ref| [ref_alias, ref._pcore_init_hash] }] unless @references.empty? result end # Resolve a type in this type set using a qualified name. The resolved type may either be a type defined in this type set # or a type defined in a type set that is referenced by this type set (nesting may occur to any level). # The name resolution is case insensitive. # # @param qname [String,Loader::TypedName] the qualified name of the type to resolve # @return [PAnyType,nil] the resolved type, or `nil` in case no type could be found # # @api public def [](qname) if qname.is_a?(Loader::TypedName) return nil unless qname.type == :type && qname.name_authority == @name_authority qname = qname.name end type = @types[qname] || @types[@dc_to_cc_map[qname.downcase]] if type.nil? && !@references.empty? segments = qname.split(TypeFormatter::NAME_SEGMENT_SEPARATOR) first = segments[0] type_set_ref = @references[first] || @references[@dc_to_cc_map[first.downcase]] if type_set_ref.nil? nil else type_set = type_set_ref.type_set case segments.size when 1 type_set when 2 type_set[segments[1]] else segments.shift type_set[segments.join(TypeFormatter::NAME_SEGMENT_SEPARATOR)] end end else type end end def defines_type?(t) !@types.key(t).nil? end # Returns the name by which the given type is referenced from within this type set # @param t [PAnyType] # @return [String] the name by which the type is referenced within this type set # # @api private def name_for(t, default_name) key = @types.key(t) if key.nil? if @references.empty? default_name else @references.each_pair do |ref_key, ref| ref_name = ref.type_set.name_for(t, nil) return "#{ref_key}::#{ref_name}" unless ref_name.nil? end default_name end else key end end def accept(visitor, guard) super @types.each_value { |type| type.accept(visitor, guard) } @references.each_value { |ref| ref.accept(visitor, guard) } end # @api private def label "TypeSet '#{@name}'" end # @api private def resolve(loader) super @references.each_value { |ref| ref.resolve(loader) } tsa_loader = TypeSetLoader.new(self, loader) @types.values.each { |type| type.resolve(tsa_loader) } self end # @api private def resolve_literal_hash(loader, init_hash_expression) result = {} type_parser = TypeParser.singleton init_hash_expression.entries.each do |entry| key = type_parser.interpret_any(entry.key, loader) if (key == KEY_TYPES || key == KEY_REFERENCES) && entry.value.is_a?(Model::LiteralHash) # Skip type parser interpretation and convert qualified references directly to String keys. hash = {} entry.value.entries.each do |he| kex = he.key name = kex.is_a?(Model::QualifiedReference) ? kex.cased_value : type_parser.interpret_any(kex, loader) hash[name] = key == KEY_TYPES ? he.value : type_parser.interpret_any(he.value, loader) end result[key] = hash else result[key] = type_parser.interpret_any(entry.value, loader) end end name_auth = resolve_name_authority(result, loader) types = result[KEY_TYPES] if types.is_a?(Hash) types.each do |type_name, value| full_name = "#{@name}::#{type_name}".freeze typed_name = Loader::TypedName.new(:type, full_name, name_auth) if value.is_a?(Model::ResourceDefaultsExpression) # This is actually a <Parent> { <key-value entries> } notation. Convert to a literal hash that contains the parent n = value.type_ref name = n.cased_value entries = [] unless name == 'Object' or name == 'TypeSet' if value.operations.any? { |op| op.attribute_name == KEY_PARENT } case Puppet[:strict] when :warning IssueReporter.warning(value, Issues::DUPLICATE_KEY, :key => KEY_PARENT) when :error IssueReporter.error(Puppet::ParseErrorWithIssue, value, Issues::DUPLICATE_KEY, :key => KEY_PARENT) end end entries << Model::KeyedEntry.new(n.locator, n.offset, n.length, KEY_PARENT, n) end value.operations.each { |op| entries << Model::KeyedEntry.new(op.locator, op.offset, op.length, op.attribute_name, op.value_expr) } value = Model::LiteralHash.new(value.locator, value.offset, value.length, entries) end type = Loader::TypeDefinitionInstantiator.create_type(full_name, value, name_auth) loader.set_entry(typed_name, type, value.locator.to_uri(value)) types[type_name] = type end end result end # @api private def resolve_hash(loader, init_hash) result = Hash[init_hash.map do |key, value| key = resolve_type_refs(loader, key) value = resolve_type_refs(loader, value) unless key == KEY_TYPES && value.is_a?(Hash) [key, value] end] name_auth = resolve_name_authority(result, loader) types = result[KEY_TYPES] if types.is_a?(Hash) types.each do |type_name, value| full_name = "#{@name}::#{type_name}".freeze typed_name = Loader::TypedName.new(:type, full_name, name_auth) meta_name = value.is_a?(Hash) ? 'Object' : 'TypeAlias' type = Loader::TypeDefinitionInstantiator.create_named_type(full_name, meta_name, value, name_auth) loader.set_entry(typed_name, type) types[type_name] = type end end result end def hash @name_authority.hash ^ @name.hash ^ @version.hash end def eql?(o) self.class == o.class && @name_authority == o.name_authority && @name == o.name && @version == o.version end DEFAULT = self.new({ KEY_NAME => 'DefaultTypeSet', KEY_NAME_AUTHORITY => Pcore::RUNTIME_NAME_AUTHORITY, Pcore::KEY_PCORE_URI => Pcore::PCORE_URI, Pcore::KEY_PCORE_VERSION => Pcore::PCORE_VERSION, KEY_VERSION => SemanticPuppet::Version.new(0,0,0) }) protected # @api_private def _assignable?(o, guard) self.class == o.class && (self == DEFAULT || eql?(o)) end private def resolve_name_authority(init_hash, loader) name_auth = @name_authority if name_auth.nil? name_auth = init_hash[KEY_NAME_AUTHORITY] name_auth = loader.name_authority if name_auth.nil? && loader.is_a?(TypeSetLoader) if name_auth.nil? name = @name || init_hash[KEY_NAME] raise ArgumentError, "No 'name_authority' is declared in TypeSet '#{name}' and it cannot be inferred" end end name_auth end end end end �����������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/p_uri_type.rb���������������������������������������������������0000644�0052762�0001160�00000012706�13417161721�021734� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Types class PURIType < PAnyType # Tell evaluator that an members of instances of this type can be invoked using dot notation include TypeWithMembers SCHEME = 'scheme'.freeze USERINFO = 'userinfo'.freeze HOST = 'host'.freeze PORT = 'port'.freeze PATH = 'path'.freeze QUERY = 'query'.freeze FRAGMENT = 'fragment'.freeze OPAQUE = 'opaque'.freeze URI_MEMBERS = { SCHEME => AttrReader.new(SCHEME), USERINFO => AttrReader.new(USERINFO), HOST => AttrReader.new(HOST), PORT => AttrReader.new(PORT), PATH => AttrReader.new(PATH), QUERY => AttrReader.new(QUERY), FRAGMENT => AttrReader.new(FRAGMENT), OPAQUE => AttrReader.new(OPAQUE), } TYPE_URI_INIT_HASH = TypeFactory.struct( TypeFactory.optional(SCHEME) => PStringType::NON_EMPTY, TypeFactory.optional(USERINFO) => PStringType::NON_EMPTY, TypeFactory.optional(HOST) => PStringType::NON_EMPTY, TypeFactory.optional(PORT) => PIntegerType.new(0), TypeFactory.optional(PATH) => PStringType::NON_EMPTY, TypeFactory.optional(QUERY) => PStringType::NON_EMPTY, TypeFactory.optional(FRAGMENT) => PStringType::NON_EMPTY, TypeFactory.optional(OPAQUE) => PStringType::NON_EMPTY, ) TYPE_STRING_PARAM = TypeFactory.optional(PVariantType.new([ PStringType::NON_EMPTY, PRegexpType::DEFAULT, TypeFactory.type_type(PPatternType::DEFAULT), TypeFactory.type_type(PEnumType::DEFAULT), TypeFactory.type_type(PNotUndefType::DEFAULT), TypeFactory.type_type(PUndefType::DEFAULT), ])) TYPE_INTEGER_PARAM = TypeFactory.optional(PVariantType.new([ PIntegerType.new(0), TypeFactory.type_type(PNotUndefType::DEFAULT), TypeFactory.type_type(PUndefType::DEFAULT), ])) TYPE_URI_PARAM_HASH_TYPE = TypeFactory.struct( TypeFactory.optional(SCHEME) => TYPE_STRING_PARAM, TypeFactory.optional(USERINFO) => TYPE_STRING_PARAM, TypeFactory.optional(HOST) => TYPE_STRING_PARAM, TypeFactory.optional(PORT) => TYPE_INTEGER_PARAM, TypeFactory.optional(PATH) => TYPE_STRING_PARAM, TypeFactory.optional(QUERY) => TYPE_STRING_PARAM, TypeFactory.optional(FRAGMENT) => TYPE_STRING_PARAM, TypeFactory.optional(OPAQUE) => TYPE_STRING_PARAM, ) TYPE_URI_PARAM_TYPE = PVariantType.new([PStringType::NON_EMPTY, TYPE_URI_PARAM_HASH_TYPE]) def self.register_ptype(loader, ir) create_ptype(loader, ir, 'AnyType', { 'parameters' => { KEY_TYPE => TypeFactory.optional(TYPE_URI_PARAM_TYPE), KEY_VALUE => nil } } ) end def self.new_function(type) @new_function ||= Puppet::Functions.create_loaded_function(:new_error, type.loader) do dispatch :create do param 'String[1]', :uri end dispatch :from_hash do param TYPE_URI_INIT_HASH, :hash end def create(uri) URI.parse(uri) end def from_hash(init_hash) sym_hash = {} init_hash.each_pair { |k, v| sym_hash[k.to_sym] = v } scheme = sym_hash[:scheme] scheme_class = scheme.nil? ? URI::Generic : (URI.scheme_list[scheme.upcase] || URI::Generic) scheme_class.build(sym_hash) end end end attr_reader :parameters def initialize(parameters = nil) if parameters.is_a?(String) parameters = TypeAsserter.assert_instance_of('URI-Type parameter', Pcore::TYPE_URI, parameters, true) @parameters = uri_to_hash(URI.parse(parameters)) elsif parameters.is_a?(URI) @parameters = uri_to_hash(parameters) elsif parameters.is_a?(Hash) params = TypeAsserter.assert_instance_of('URI-Type parameter', TYPE_URI_PARAM_TYPE, parameters, true) @parameters = params.empty? ? nil : params end end def eql?(o) self.class == o.class && @parameters == o.parameters end def ==(o) eql?(o) end def [](key) URI_MEMBERS[key] end def generalize DEFAULT end def hash self.class.hash ^ @parameters.hash end def instance?(o, guard = nil) return false unless o.is_a?(URI) return true if @parameters.nil? eval = Parser::EvaluatingParser.singleton.evaluator @parameters.keys.all? { |pn| eval.match?(o.send(pn), @parameters[pn]) } end def roundtrip_with_string? true end def _pcore_init_hash @parameters == nil? ? EMPTY_HASH : { 'parameters' => @parameters } end protected def _assignable?(o, guard = nil) return false unless o.class == self.class return true if @parameters.nil? o_params = o.parameters || EMPTY_HASH eval = Parser::EvaluatingParser.singleton.evaluator @parameters.keys.all? do |pn| if o_params.include?(pn) a = o_params[pn] b = @parameters[pn] eval.match?(a, b) || a.is_a?(PAnyType) && b.is_a?(PAnyType) && b.assignable?(a) else false end end end private def uri_to_hash(uri) result = {} scheme = uri.scheme unless scheme.nil? scheme = scheme.downcase result[SCHEME] = scheme end result[USERINFO] = uri.userinfo unless uri.userinfo.nil? result[HOST] = uri.host.downcase unless uri.host.nil? result[PORT] = uri.port.to_s unless uri.port.nil? || uri.port == 80 && 'http' == scheme || uri.port == 443 && 'https' == scheme result[PATH] = uri.path unless uri.path.nil? || uri.path.empty? result[QUERY] = uri.query unless uri.query.nil? result[FRAGMENT] = uri.fragment unless uri.fragment.nil? result[OPAQUE] = uri.opaque unless uri.opaque.nil? result.empty? ? nil : result end DEFAULT = PURIType.new(nil) end end end ����������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/puppet_object.rb������������������������������������������������0000644�0052762�0001160�00000001646�13417161721�022421� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Types # Marker module for implementations that are mapped to Object types # @api public module PuppetObject # Returns the Puppet Type for this instance. The implementing class must # add the {#_pcore_type} as a class method. # # @return [PObjectType] the type def _pcore_type t = self.class._pcore_type if t.parameterized? unless instance_variable_defined?(:@_cached_ptype) # Create a parameterized type based on the values of this instance that # contains a parameter value for each type parameter that matches an # attribute by name and type of value @_cached_ptype = PObjectTypeExtension.create_from_instance(t, self) end t = @_cached_ptype end t end def _pcore_all_contents(path, &block) end def _pcore_contents end def _pcore_init_hash {} end def to_s TypeFormatter.string(self) end end end end ������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/recursion_guard.rb����������������������������������������������0000644�0052762�0001160�00000007660�13417161721�022753� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Types # Keeps track of self recursion of conceptual 'this' and 'that' instances using two separate maps and # a state. The class is used when tracking self recursion in two objects ('this' and 'that') simultaneously. # A typical example of when this is needed is when testing if 'that' Puppet Type is assignable to 'this' # Puppet Type since both types may contain self references. # # All comparisons are made using the `object_id` of the instance rather than the instance itself. # # @api private class RecursionGuard attr_reader :state NO_SELF_RECURSION = 0 SELF_RECURSION_IN_THIS = 1 SELF_RECURSION_IN_THAT = 2 SELF_RECURSION_IN_BOTH = 3 def initialize @state = NO_SELF_RECURSION end # Checks if recursion was detected for the given argument in the 'this' context # @param instance [Object] the instance to check # @return [Integer] the resulting state def recursive_this?(instance) instance_variable_defined?(:@recursive_this_map) && @recursive_this_map.has_key?(instance.object_id) end # Checks if recursion was detected for the given argument in the 'that' context # @param instance [Object] the instance to check # @return [Integer] the resulting state def recursive_that?(instance) instance_variable_defined?(:@recursive_that_map) && @recursive_that_map.has_key?(instance.object_id) end # Add the given argument as 'this' invoke the given block with the resulting state # @param instance [Object] the instance to add # @return [Object] the result of yielding def with_this(instance) if (@state & SELF_RECURSION_IN_THIS) == 0 tc = this_count @state = @state | SELF_RECURSION_IN_THIS if this_put(instance) if tc < this_count # recursive state detected result = yield(@state) # pop state @state &= ~SELF_RECURSION_IN_THIS @this_map.delete(instance.object_id) return result end end yield(@state) end # Add the given argument as 'that' invoke the given block with the resulting state # @param instance [Object] the instance to add # @return [Object] the result of yielding def with_that(instance) if (@state & SELF_RECURSION_IN_THAT) == 0 tc = that_count @state = @state | SELF_RECURSION_IN_THAT if that_put(instance) if tc < that_count # recursive state detected result = yield(@state) # pop state @state &= ~SELF_RECURSION_IN_THAT @that_map.delete(instance.object_id) return result end end yield(@state) end # Add the given argument as 'this' and return the resulting state # @param instance [Object] the instance to add # @return [Integer] the resulting state def add_this(instance) if (@state & SELF_RECURSION_IN_THIS) == 0 @state = @state | SELF_RECURSION_IN_THIS if this_put(instance) end @state end # Add the given argument as 'that' and return the resulting state # @param instance [Object] the instance to add # @return [Integer] the resulting state def add_that(instance) if (@state & SELF_RECURSION_IN_THAT) == 0 @state = @state | SELF_RECURSION_IN_THAT if that_put(instance) end @state end # @return the number of objects added to the `this` map def this_count instance_variable_defined?(:@this_map) ? @this_map.size : 0 end # @return the number of objects added to the `that` map def that_count instance_variable_defined?(:@that_map) ? @that_map.size : 0 end private def this_put(o) id = o.object_id @this_map ||= {} if @this_map.has_key?(id) @recursive_this_map ||= {} @recursive_this_map[id] = true true else @this_map[id] = true false end end def that_put(o) id = o.object_id @that_map ||= {} if @that_map.has_key?(id) @recursive_that_map ||= {} @recursive_that_map[id] = true true else @that_map[id] = true false end end end end end ��������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/ruby_generator.rb�����������������������������������������������0000644�0052762�0001160�00000037534�13417161721�022612� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Types # @api private class RubyGenerator < TypeFormatter RUBY_RESERVED_WORDS = { 'alias' => '_alias', 'begin' => '_begin', 'break' => '_break', 'def' => '_def', 'do' => '_do', 'end' => '_end', 'ensure' => '_ensure', 'for' => '_for', 'module' => '_module', 'next' => '_next', 'nil' => '_nil', 'not' => '_not', 'redo' => '_redo', 'rescue' => '_rescue', 'retry' => '_retry', 'return' => '_return', 'self' => '_self', 'super' => '_super', 'then' => '_then', 'until' => '_until', 'when' => '_when', 'while' => '_while', 'yield' => '_yield', } RUBY_RESERVED_WORDS_REVERSED = Hash[RUBY_RESERVED_WORDS.map { |k, v| [v, k] }] def self.protect_reserved_name(name) RUBY_RESERVED_WORDS[name] || name end def self.unprotect_reserved_name(name) RUBY_RESERVED_WORDS_REVERSED[name] || name end def remove_common_namespace(namespace_segments, name) segments = name.split(TypeFormatter::NAME_SEGMENT_SEPARATOR) namespace_segments.size.times do |idx| break if segments.empty? || namespace_segments[idx] != segments[0] segments.shift end segments end def namespace_relative(namespace_segments, name) remove_common_namespace(namespace_segments, name).join(TypeFormatter::NAME_SEGMENT_SEPARATOR) end def create_class(obj) @dynamic_classes ||= Hash.new do |hash, key| cls = key.implementation_class(false) if cls.nil? rp = key.resolved_parent parent_class = rp.is_a?(PObjectType) ? rp.implementation_class : Object class_def = '' class_body(key, EMPTY_ARRAY, class_def) cls = Class.new(parent_class) cls.class_eval(class_def) cls.define_singleton_method(:_pcore_type) { return key } key.implementation_class = cls end hash[key] = cls end raise ArgumentError, "Expected a Puppet Type, got '#{obj.class.name}'" unless obj.is_a?(PAnyType) @dynamic_classes[obj] end def module_definition_from_typeset(typeset, *impl_subst) module_definition( typeset.types.values, "# Generated by #{self.class.name} from TypeSet #{typeset.name} on #{Date.new}\n", *impl_subst) end def module_definition(types, comment, *impl_subst) object_types, aliased_types = types.partition { |type| type.is_a?(PObjectType) } if impl_subst.empty? impl_names = implementation_names(object_types) else impl_names = object_types.map { |type| type.name.gsub(*impl_subst) } end # extract common implementation module prefix names_by_prefix = Hash.new { |hash, key| hash[key] = [] } index = 0 min_prefix_length = impl_names.reduce(Float::INFINITY) do |len, impl_name| segments = impl_name.split(TypeFormatter::NAME_SEGMENT_SEPARATOR) leaf_name = segments.pop names_by_prefix[segments.freeze] << [index, leaf_name, impl_name] index += 1 len > segments.size ? segments.size : len end min_prefix_length = 0 if min_prefix_length == Float::INFINITY common_prefix = [] segments_array = names_by_prefix.keys min_prefix_length.times do |idx| segment = segments_array[0][idx] break unless segments_array.all? { |sn| sn[idx] == segment } common_prefix << segment end # Create class definition of all contained types bld = '' start_module(common_prefix, comment, bld) class_names = [] names_by_prefix.each_pair do |seg_array, index_and_name_array| added_to_common_prefix = seg_array[common_prefix.length..-1] added_to_common_prefix.each { |name| bld << 'module ' << name << "\n" } index_and_name_array.each do |idx, name, full_name| scoped_class_definition(object_types[idx], name, bld, full_name, *impl_subst) class_names << (added_to_common_prefix + [name]).join(TypeFormatter::NAME_SEGMENT_SEPARATOR) bld << "\n" end added_to_common_prefix.size.times { bld << "end\n" } end aliases = Hash[aliased_types.map { |type| [type.name, type.resolved_type] }] end_module(common_prefix, aliases, class_names, bld) bld end def start_module(common_prefix, comment, bld) bld << '# ' << comment << "\n" common_prefix.each { |cp| bld << 'module ' << cp << "\n" } end def end_module(common_prefix, aliases, class_names, bld) # Emit registration of contained type aliases unless aliases.empty? bld << "Puppet::Pops::Pcore.register_aliases({\n" aliases.each { |name, type| bld << " '" << name << "' => " << TypeFormatter.string(type.to_s) << "\n" } bld.chomp!(",\n") bld << "})\n\n" end # Emit registration of contained types unless class_names.empty? bld << "Puppet::Pops::Pcore.register_implementations([\n" class_names.each { |class_name| bld << ' ' << class_name << ",\n" } bld.chomp!(",\n") bld << "])\n\n" end bld.chomp!("\n") common_prefix.size.times { bld << "end\n" } end def implementation_names(object_types) object_types.map do |type| ir = Loaders.implementation_registry impl_name = ir.module_name_for_type(type) raise Puppet::Error, "Unable to create an instance of #{type.name}. No mapping exists to runtime object" if impl_name.nil? impl_name end end def class_definition(obj, namespace_segments, bld, class_name, *impl_subst) module_segments = remove_common_namespace(namespace_segments, class_name) leaf_name = module_segments.pop module_segments.each { |segment| bld << 'module ' << segment << "\n" } scoped_class_definition(obj,leaf_name, bld, class_name, *impl_subst) module_segments.size.times { bld << "end\n" } module_segments << leaf_name module_segments.join(TypeFormatter::NAME_SEGMENT_SEPARATOR) end def scoped_class_definition(obj, leaf_name, bld, class_name, *impl_subst) bld << 'class ' << leaf_name segments = class_name.split(TypeFormatter::NAME_SEGMENT_SEPARATOR) unless obj.parent.nil? if impl_subst.empty? ir = Loaders.implementation_registry parent_name = ir.module_name_for_type(obj.parent) raise Puppet::Error, "Unable to create an instance of #{obj.parent.name}. No mapping exists to runtime object" if parent_name.nil? else parent_name = obj.parent.name.gsub(*impl_subst) end bld << ' < ' << namespace_relative(segments, parent_name) end bld << "\n" bld << " def self._pcore_type\n" bld << ' @_pcore_type ||= ' << namespace_relative(segments, obj.class.name) << ".new('" << obj.name << "', " bld << TypeFormatter.singleton.ruby('ref').indented(2).string(obj._pcore_init_hash(false)) << ")\n" bld << " end\n" class_body(obj, segments, bld) bld << "end\n" end def class_body(obj, segments, bld) unless obj.parent.is_a?(PObjectType) bld << "\n include " << namespace_relative(segments, Puppet::Pops::Types::PuppetObject.name) << "\n\n" # marker interface bld << " def self.ref(type_string)\n" bld << ' ' << namespace_relative(segments, Puppet::Pops::Types::PTypeReferenceType.name) << ".new(type_string)\n" bld << " end\n" end # Output constants constants, others = obj.attributes(true).values.partition { |a| a.kind == PObjectType::ATTRIBUTE_KIND_CONSTANT } constants = constants.select { |ca| ca.container.equal?(obj) } unless constants.empty? constants.each { |ca| bld << "\n def self." << rname(ca.name) << "\n _pcore_type['" << ca.name << "'].value\n end\n" } constants.each { |ca| bld << "\n def " << rname(ca.name) << "\n self.class." << ca.name << "\n end\n" } end init_params = others.reject { |a| a.kind == PObjectType::ATTRIBUTE_KIND_DERIVED } opt, non_opt = init_params.partition { |ip| ip.value? } derived_attrs, obj_attrs = others.select { |a| a.container.equal?(obj) }.partition { |ip| ip.kind == PObjectType::ATTRIBUTE_KIND_DERIVED } include_type = obj.equality_include_type? && !(obj.parent.is_a?(PObjectType) && obj.parent.equality_include_type?) if obj.equality.nil? eq_names = obj_attrs.reject { |a| a.kind == PObjectType::ATTRIBUTE_KIND_CONSTANT }.map(&:name) else eq_names = obj.equality end # Output type safe hash constructor bld << "\n def self.from_hash(init_hash)\n" bld << ' from_asserted_hash(' << namespace_relative(segments, TypeAsserter.name) << '.assert_instance_of(' bld << "'" << obj.label << " initializer', _pcore_type.init_hash_type, init_hash))\n end\n\n def self.from_asserted_hash(init_hash)\n new" unless non_opt.empty? && opt.empty? bld << "(\n" non_opt.each { |ip| bld << " init_hash['" << ip.name << "'],\n" } opt.each do |ip| if ip.value.nil? bld << " init_hash['" << ip.name << "'],\n" else bld << " init_hash.fetch('" << ip.name << "') { " default_string(bld, ip) bld << " },\n" end end bld.chomp!(",\n") bld << ')' end bld << "\n end\n" # Output type safe constructor bld << "\n def self.create" if init_params.empty? bld << "\n new" else bld << '(' non_opt.each { |ip| bld << rname(ip.name) << ', ' } opt.each do |ip| bld << rname(ip.name) << ' = ' default_string(bld, ip) bld << ', ' end bld.chomp!(', ') bld << ")\n" bld << ' ta = ' << namespace_relative(segments, TypeAsserter.name) << "\n" bld << " attrs = _pcore_type.attributes(true)\n" init_params.each do |a| bld << " ta.assert_instance_of('" << a.container.name << '[' << a.name << ']' bld << "', attrs['" << a.name << "'].type, " << rname(a.name) << ")\n" end bld << ' new(' non_opt.each { |a| bld << rname(a.name) << ', ' } opt.each { |a| bld << rname(a.name) << ', ' } bld.chomp!(', ') bld << ')' end bld << "\n end\n" unless obj.parent.is_a?(PObjectType) && obj_attrs.empty? # Output attr_readers unless obj_attrs.empty? bld << "\n" obj_attrs.each { |a| bld << ' attr_reader :' << rname(a.name) << "\n" } end bld << " attr_reader :hash\n" if obj.parent.nil? derived_attrs.each do |a| bld << "\n def " << rname(a.name) << "\n" code_annotation = RubyMethod.annotate(a) ruby_body = code_annotation.nil? ? nil: code_annotation.body if ruby_body.nil? bld << " raise Puppet::Error, \"no method is implemented for derived #{a.label}\"\n" else bld << ' ' << ruby_body << "\n" end bld << " end\n" end if init_params.empty? bld << "\n def initialize\n @hash = " << obj.hash.to_s << "\n end" if obj.parent.nil? else # Output initializer bld << "\n def initialize" bld << '(' non_opt.each { |ip| bld << rname(ip.name) << ', ' } opt.each do |ip| bld << rname(ip.name) << ' = ' default_string(bld, ip) bld << ', ' end bld.chomp!(', ') bld << ')' hash_participants = init_params.select { |ip| eq_names.include?(ip.name) } if obj.parent.nil? bld << "\n @hash = " bld << obj.hash.to_s << "\n" if hash_participants.empty? else bld << "\n super(" super_args = (non_opt + opt).select { |ip| !ip.container.equal?(obj) } unless super_args.empty? super_args.each { |ip| bld << rname(ip.name) << ', ' } bld.chomp!(', ') end bld << ")\n" bld << ' @hash = @hash ^ ' unless hash_participants.empty? end unless hash_participants.empty? hash_participants.each { |a| bld << rname(a.name) << '.hash ^ ' if a.container.equal?(obj) } bld.chomp!(' ^ ') bld << "\n" end init_params.each { |a| bld << ' @' << rname(a.name) << ' = ' << rname(a.name) << "\n" if a.container.equal?(obj) } bld << " end\n" end end unless obj_attrs.empty? && obj.parent.nil? bld << "\n def _pcore_init_hash\n" bld << ' result = ' bld << (obj.parent.nil? ? '{}' : 'super') bld << "\n" obj_attrs.each do |a| bld << " result['" << a.name << "'] = @" << rname(a.name) if a.value? bld << ' unless ' equals_default_string(bld, a) end bld << "\n" end bld << " result\n end\n" end content_participants = init_params.select { |a| content_participant?(a) } if content_participants.empty? unless obj.parent.is_a?(PObjectType) bld << "\n def _pcore_contents\n end\n" bld << "\n def _pcore_all_contents(path)\n end\n" end else bld << "\n def _pcore_contents\n" content_participants.each do |cp| if array_type?(cp.type) bld << ' @' << rname(cp.name) << ".each { |value| yield(value) }\n" else bld << ' yield(@' << rname(cp.name) << ') unless @' << rname(cp.name) << ".nil?\n" end end bld << " end\n\n def _pcore_all_contents(path, &block)\n path << self\n" content_participants.each do |cp| if array_type?(cp.type) bld << ' @' << rname(cp.name) << ".each do |value|\n" bld << " block.call(value, path)\n" bld << " value._pcore_all_contents(path, &block)\n" else bld << ' unless @' << rname(cp.name) << ".nil?\n" bld << ' block.call(@' << rname(cp.name) << ", path)\n" bld << ' @' << rname(cp.name) << "._pcore_all_contents(path, &block)\n" end bld << " end\n" end bld << " path.pop\n end\n" end # Output function placeholders obj.functions(false).each_value do |func| code_annotation = RubyMethod.annotate(func) if code_annotation body = code_annotation.body params = code_annotation.parameters bld << "\n def " << rname(func.name) unless params.nil? || params.empty? bld << '(' << params << ')' end bld << "\n " << body << "\n" else bld << "\n def " << rname(func.name) << "(*args)\n" bld << " # Placeholder for #{func.type}\n" bld << " raise Puppet::Error, \"no method is implemented for #{func.label}\"\n" end bld << " end\n" end unless eq_names.empty? && !include_type bld << "\n def eql?(o)\n" bld << " super &&\n" unless obj.parent.nil? bld << " o.instance_of?(self.class) &&\n" if include_type eq_names.each { |eqn| bld << ' @' << rname(eqn) << '.eql?(o.' << rname(eqn) << ") &&\n" } bld.chomp!(" &&\n") bld << "\n end\n alias == eql?\n" end end def content_participant?(a) a.kind != PObjectType::ATTRIBUTE_KIND_REFERENCE && obj_type?(a.type) end def obj_type?(t) case t when PObjectType true when POptionalType obj_type?(t.optional_type) when PNotUndefType obj_type?(t.type) when PArrayType obj_type?(t.element_type) when PVariantType t.types.all? { |v| obj_type?(v) } else false end end def array_type?(t) case t when PArrayType true when POptionalType array_type?(t.optional_type) when PNotUndefType array_type?(t.type) when PVariantType t.types.all? { |v| array_type?(v) } else false end end def default_string(bld, a) case a.value when nil, true, false, Numeric, String bld << a.value.inspect else bld << "_pcore_type['" << a.name << "'].value" end end def equals_default_string(bld, a) case a.value when nil, true, false, Numeric, String bld << '@' << a.name << ' == ' << a.value.inspect else bld << "_pcore_type['" << a.name << "'].default_value?(@" << a.name << ')' end end def rname(name) RUBY_RESERVED_WORDS[name] || name end end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/ruby_method.rb��������������������������������������������������0000644�0052762�0001160�00000001612�13417161721�022070� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Types class RubyMethod < Annotation # Register the Annotation type. This is the type that all custom Annotations will inherit from. def self.register_ptype(loader, ir) @type = Pcore::create_object_type(loader, ir, self, 'RubyMethod', 'Annotation', 'body' => PStringType::DEFAULT, 'parameters' => { KEY_TYPE => POptionalType.new(PStringType::NON_EMPTY), KEY_VALUE => nil } ) end def self.from_hash(init_hash) from_asserted_hash(Types::TypeAsserter.assert_instance_of('RubyMethod initializer', _pcore_type.init_hash_type, init_hash)) end def self.from_asserted_hash(init_hash) new(init_hash['body'], init_hash['parameters']) end attr_reader :body, :parameters def initialize(body, parameters = nil) @body = body @parameters = parameters end end end end ����������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/tree_iterators.rb�����������������������������������������������0000644�0052762�0001160�00000016136�13417161721�022611� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops::Types module Iterable class TreeIterator include Iterable DEFAULT_CONTAINERS = TypeFactory.variant( PArrayType::DEFAULT, PHashType::DEFAULT, PObjectType::DEFAULT ) # Creates a TreeIterator that by default treats all Array, Hash and Object instances as # containers - the 'containers' option can be set to a type that denotes which types of values # should be treated as containers - a `Variant[Array, Hash]` would for instance not treat # Object values as containers, whereas just `Object` would only treat objects as containers. # # Unrecognized options are silently ignored # # @param [Hash] options the options # @option options [PTypeType] :container_type ('Variant[Hash, Array, Object]') The type(s) that should be treated as containers. The # given type(s) must be assignable to the default container_type. # @option options [Boolean] :include_root ('true') If the root container itself should be included in the iteration (requires # `include_containers` to also be `true` to take effect). # @option options [Boolean] :include_containers ('true') If containers should be included in the iteration # @option options [Boolean] :include_values ('true') If non containers (values) should be included in the iteration # @option options [Boolean] :include_refs ('false') If (non containment) referenced values in Objects should be included # def initialize(enum, options=EMPTY_HASH) @root = enum @element_t = nil @value_stack = [enum] @indexer_stack = [] @current_path = [] @recursed = false @containers_t = options['container_type'] || DEFAULT_CONTAINERS unless DEFAULT_CONTAINERS.assignable?(@containers_t) raise ArgumentError, _("Only Array, Hash, and Object types can be used as container types. Got %{type}") % {type: @containers_t} end @with_root = extract_option(options, 'include_root', true) @with_containers = extract_option(options, 'include_containers', true) @with_values = extract_option(options, 'include_values', true) @with_root = @with_containers && extract_option(options, 'include_root', true) unless @with_containers || @with_values raise ArgumentError, _("Options 'include_containers' and 'include_values' cannot both be false") end @include_refs = !!options['include_refs'] end # Yields each `path, value` if the block arity is 2, and only `value` if arity is 1 # def each(&block) loop do if block.arity == 1 yield(self.next) else yield(*self.next) end end end def size raise "Not yet implemented - computes size lazily" end def unbounded? false end def to_a result = [] loop do result << self.next end result end def to_array to_a end def reverse_each(&block) r = Iterator.new(PAnyType::DEFAULT, to_array.reverse_each) block_given? ? r.each(&block) : r end def step(step, &block) r = StepIterator.new(PAnyType::DEFAULT, self, step) block_given? ? r.each(&block) : r end def indexer_on(val) return nil unless @containers_t.instance?(val) if val.is_a?(Array) val.size.times elsif val.is_a?(Hash) val.each_key else if @include_refs val._pcore_type.attributes.each_key else val._pcore_type.attributes.reject {|k,v| v.kind == PObjectType::ATTRIBUTE_KIND_REFERENCE }.each_key end end end private :indexer_on def has_next?(iterator) begin iterator.peek true rescue StopIteration false end end private :has_next? def extract_option(options, key, default) v = options[key] v.nil? ? default : !!v end private :extract_option end class DepthFirstTreeIterator < TreeIterator # Creates a DepthFirstTreeIterator that by default treats all Array, Hash and Object instances as # containers - the 'containers' option can be set to a type that denotes which types of values # should be treated as containers - a `Variant[Array, Hash]` would for instance not treat # Object values as containers, whereas just `Object` would only treat objects as containers. # # @param [Hash] options the options # @option options [PTypeType] :containers ('Variant[Hash, Array, Object]') The type(s) that should be treated as containers # @option options [Boolean] :with_root ('true') If the root container itself should be included in the iteration # def initialize(enum, options = EMPTY_HASH) super end def next loop do break if @value_stack.empty? # first call if @indexer_stack.empty? @indexer_stack << indexer_on(@root) @recursed = true return [[], @root] if @with_root end begin if @recursed @current_path << nil @recursed = false end idx = @indexer_stack[-1].next @current_path[-1] = idx v = @value_stack[-1] value = v.is_a?(PuppetObject) ? v.send(idx) : v[idx] indexer = indexer_on(value) if indexer # recurse @recursed = true @value_stack << value @indexer_stack << indexer redo unless @with_containers else redo unless @with_values end return [@current_path.dup, value] rescue StopIteration # end of current value's range of content # pop all until out of next values at_the_very_end = false loop do pop_level at_the_very_end = @indexer_stack.empty? break if at_the_very_end || has_next?(@indexer_stack[-1]) end end end raise StopIteration end def pop_level @value_stack.pop @indexer_stack.pop @current_path.pop end private :pop_level end class BreadthFirstTreeIterator < TreeIterator def initialize(enum, options = EMPTY_HASH) @path_stack = [] super end def next loop do break if @value_stack.empty? # first call if @indexer_stack.empty? @indexer_stack << indexer_on(@root) @recursed = true return [[], @root] if @with_root end begin if @recursed @current_path << nil @recursed = false end idx = @indexer_stack[0].next @current_path[-1] = idx v = @value_stack[0] value = v.is_a?(PuppetObject) ? v.send(idx) : v[idx] indexer = indexer_on(value) if indexer @value_stack << value @indexer_stack << indexer @path_stack << @current_path.dup next unless @with_containers end return [@current_path.dup, value] rescue StopIteration # end of current value's range of content # shift all until out of next values at_the_very_end = false loop do shift_level at_the_very_end = @indexer_stack.empty? break if at_the_very_end || has_next?(@indexer_stack[0]) end end end raise StopIteration end def shift_level @value_stack.shift @indexer_stack.shift @current_path = @path_stack.shift @recursed = true end private :shift_level end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/type_acceptor.rb������������������������������������������������0000644�0052762�0001160�00000001344�13417161721�022412� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Types # Implements a standard visitor patter for the Puppet Type system. # # An instance of this module is passed as an argument to the {PAnyType#accept} # method of a Type instance. That type will then use the {TypeAcceptor#visit} callback # on the acceptor and then pass the acceptor to the `accept` method of all contained # type instances so that the it gets a visit from each one recursively. # module TypeAcceptor # @param type [PAnyType] the type that we accept a visit from # @param guard [RecursionGuard] the guard against self recursion def visit(type, guard) end end # An acceptor that does nothing class NoopTypeAcceptor include TypeAcceptor INSTANCE = NoopTypeAcceptor.new end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/type_asserter.rb������������������������������������������������0000644�0052762�0001160�00000004117�13417161721�022443� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Utility module for type assertion # module Puppet::Pops::Types module TypeAsserter # Asserts that a type_to_check is assignable to required_type and raises # a {Puppet::ParseError} if that's not the case # # @param subject [String,Array] String to be prepended to the exception message or Array where the first element is # a format string and the rest are arguments to that format string # @param expected_type [PAnyType] Expected type # @param type_to_check [PAnyType] Type to check against the required type # @return The type_to_check argument # # @api public def self.assert_assignable(subject, expected_type, type_to_check, &block) report_type_mismatch(subject, expected_type, type_to_check, 'is incorrect', &block) unless expected_type.assignable?(type_to_check) type_to_check end # Asserts that a value is an instance of a given type and raises # a {Puppet::ParseError} if that's not the case # # @param subject [String,Array] String to be prepended to the exception message or Array where the first element is # a format string and the rest are arguments to that format string # @param expected_type [PAnyType] Expected type for the value # @param value [Object] Value to check # @param nil_ok [Boolean] Can be true to allow nil value. Optional and defaults to false # @return The value argument # # @api public def self.assert_instance_of(subject, expected_type, value, nil_ok = false, &block) unless value.nil? && nil_ok report_type_mismatch(subject, expected_type, TypeCalculator.singleton.infer_set(value), &block) unless expected_type.instance?(value) end value end def self.report_type_mismatch(subject, expected_type, actual_type, what = 'has wrong type') subject = yield(subject) if block_given? subject = subject[0] % subject[1..-1] if subject.is_a?(Array) raise TypeAssertionError.new( TypeMismatchDescriber.singleton.describe_mismatch("#{subject} #{what},", expected_type, actual_type), expected_type, actual_type) end private_class_method :report_type_mismatch end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/type_assertion_error.rb�����������������������������������������0000644�0052762�0001160�00000001400�13417161721�024023� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� module Puppet::Pops::Types # Raised when an assertion of actual type against an expected type fails. # class TypeAssertionError < Puppet::Error # Returns the expected type # @return [PAnyType] expected type attr_reader :expected_type # Returns the actual type # @return [PAnyType] actual type attr_reader :actual_type # Creates a new instance with a default message, expected, and actual types, # # @param message [String] The default message # @param expected_type [PAnyType] The expected type # @param actual_type [PAnyType] The actual type # def initialize(message, expected_type, actual_type) super(message) @expected_type = expected_type @actual_type = actual_type end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/type_calculator.rb����������������������������������������������0000644�0052762�0001160�00000057464�13417161721�022761� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Types # The TypeCalculator can answer questions about puppet types. # # The Puppet type system is primarily based on sub-classing. When asking the type calculator to infer types from Ruby in general, it # may not provide the wanted answer; it does not for instance take module inclusions and extensions into account. In general the type # system should be unsurprising for anyone being exposed to the notion of type. The type `Data` may require a bit more explanation; this # is an abstract type that includes all scalar types, as well as Array with an element type compatible with Data, and Hash with key # compatible with scalar and elements compatible with Data. Expressed differently; Data is what you typically express using JSON (with # the exception that the Puppet type system also includes Pattern (regular expression) as a scalar. # # Inference # --------- # The `infer(o)` method infers a Puppet type for scalar Ruby objects, and for Arrays and Hashes. # The inference result is instance specific for single typed collections # and allows answering questions about its embedded type. It does not however preserve multiple types in # a collection, and can thus not answer questions like `[1,a].infer() =~ Array[Integer, String]` since the inference # computes the common type Scalar when combining Integer and String. # # The `infer_generic(o)` method infers a generic Puppet type for scalar Ruby object, Arrays and Hashes. # This inference result does not contain instance specific information; e.g. Array[Integer] where the integer # range is the generic default. Just `infer` it also combines types into a common type. # # The `infer_set(o)` method works like `infer` but preserves all type information. It does not do any # reduction into common types or ranges. This method of inference is best suited for answering questions # about an object being an instance of a type. It correctly answers: `[1,a].infer_set() =~ Array[Integer, String]` # # The `generalize!(t)` method modifies an instance specific inference result to a generic. The method mutates # the given argument. Basically, this removes string instances from String, and range from Integer and Float. # # Assignability # ------------- # The `assignable?(t1, t2)` method answers if t2 conforms to t1. The type t2 may be an instance, in which case # its type is inferred, or a type. # # Instance? # --------- # The `instance?(t, o)` method answers if the given object (instance) is an instance that is assignable to the given type. # # String # ------ # Creates a string representation of a type. # # Creation of Type instances # -------------------------- # Instance of the classes in the {Types type model} are used to denote a specific type. It is most convenient # to use the {TypeFactory} when creating instances. # # @note # In general, new instances of the wanted type should be created as they are assigned to models using containment, and a # contained object can only be in one container at a time. Also, the type system may include more details in each type # instance, such as if it may be nil, be empty, contain a certain count etc. Or put differently, the puppet types are not # singletons. # # All types support `copy` which should be used when assigning a type where it is unknown if it is bound or not # to a parent type. A check can be made with `t.eContainer().nil?` # # Equality and Hash # ----------------- # Type instances are equal in terms of Ruby eql? and `==` if they describe the same type, but they are not `equal?` if they are not # the same type instance. Two types that describe the same type have identical hash - this makes them usable as hash keys. # # Types and Subclasses # -------------------- # In general, the type calculator should be used to answer questions if a type is a subtype of another (using {#assignable?}, or # {#instance?} if the question is if a given object is an instance of a given type (or is a subtype thereof). # Many of the types also have a Ruby subtype relationship; e.g. PHashType and PArrayType are both subtypes of PCollectionType, and # PIntegerType, PFloatType, PStringType,... are subtypes of PScalarType. Even if it is possible to answer certain questions about # type by looking at the Ruby class of the types this is considered an implementation detail, and such checks should in general # be performed by the type_calculator which implements the type system semantics. # # The PRuntimeType # ------------- # The PRuntimeType corresponds to a type in the runtime system (currently only supported runtime is 'ruby'). The # type has a runtime_type_name that corresponds to a Ruby Class name. # A Runtime[ruby] type can be used to describe any ruby class except for the puppet types that are specialized # (i.e. PRuntimeType should not be used for Integer, String, etc. since there are specialized types for those). # When the type calculator deals with PRuntimeTypes and checks for assignability, it determines the # "common ancestor class" of two classes. # This check is made based on the superclasses of the two classes being compared. In order to perform this, the # classes must be present (i.e. they are resolved from the string form in the PRuntimeType to a # loaded, instantiated Ruby Class). In general this is not a problem, since the question to produce the common # super type for two objects means that the classes must be present or there would have been # no instances present in the first place. If however the classes are not present, the type # calculator will fall back and state that the two types at least have Any in common. # # @see TypeFactory for how to create instances of types # @see TypeParser how to construct a type instance from a String # @see Types for details about the type model # # Using the Type Calculator # ----- # The type calculator can be directly used via its class methods. If doing time critical work and doing many # calls to the type calculator, it is more performant to create an instance and invoke the corresponding # instance methods. Note that inference is an expensive operation, rather than inferring the same thing # several times, it is in general better to infer once and then copy the result if mutation to a more generic form is # required. # # @api public # class TypeCalculator # @api public def self.assignable?(t1, t2) singleton.assignable?(t1,t2) end # Answers, does the given callable accept the arguments given in args (an array or a tuple) # @param callable [PCallableType] - the callable # @param args [PArrayType, PTupleType] args optionally including a lambda callable at the end # @return [Boolean] true if the callable accepts the arguments # # @api public def self.callable?(callable, args) singleton.callable?(callable, args) end # @api public def self.infer(o) singleton.infer(o) end # @api public def self.generalize(o) singleton.generalize(o) end # @api public def self.infer_set(o) singleton.infer_set(o) end # @api public def self.iterable(t) singleton.iterable(t) end # @return [TypeCalculator] the singleton instance # # @api private def self.singleton @tc_instance ||= new end # @api public # def initialize @@infer_visitor ||= Visitor.new(nil, 'infer',0,0) @@extract_visitor ||= Visitor.new(nil, 'extract',0,0) end # Answers 'can an instance of type t2 be assigned to a variable of type t'. # Does not accept nil/undef unless the type accepts it. # # @api public # def assignable?(t, t2) if t.is_a?(Module) t = type(t) end t.is_a?(PAnyType) ? t.assignable?(t2) : false end # Returns an iterable if the t represents something that can be iterated def enumerable(t) #TRANSLATOR 'TypeCalculator.enumerable' and 'iterable' are methods and should not be translated Puppet.deprecation_warning(_('TypeCalculator.enumerable is deprecated. Use iterable')) iterable(t) end # Returns an iterable if the t represents something that can be iterated def iterable(t) # Create an iterable on the type if possible Iterable.on(t) end # Answers, does the given callable accept the arguments given in args (an array or a tuple) # def callable?(callable, args) callable.is_a?(PAnyType) && callable.callable?(args) end # Answers if the two given types describe the same type def equals(left, right) return false unless left.is_a?(PAnyType) && right.is_a?(PAnyType) # Types compare per class only - an extra test must be made if the are mutually assignable # to find all types that represent the same type of instance # left == right || (assignable?(right, left) && assignable?(left, right)) end # Answers 'what is the Puppet Type corresponding to the given Ruby class' # @param c [Module] the class for which a puppet type is wanted # @api public # def type(c) raise ArgumentError, 'Argument must be a Module' unless c.is_a? Module # Can't use a visitor here since we don't have an instance of the class case when c <= Integer type = PIntegerType::DEFAULT when c == Float type = PFloatType::DEFAULT when c == Numeric type = PNumericType::DEFAULT when c == String type = PStringType::DEFAULT when c == Regexp type = PRegexpType::DEFAULT when c == NilClass type = PUndefType::DEFAULT when c == FalseClass, c == TrueClass type = PBooleanType::DEFAULT when c == Class type = PTypeType::DEFAULT when c == Array # Assume array of any type = PArrayType::DEFAULT when c == Hash # Assume hash of any type = PHashType::DEFAULT else type = PRuntimeType.new(:ruby, c.name) end type end # Generalizes value specific types. The generalized type is returned. # @api public def generalize(o) o.is_a?(PAnyType) ? o.generalize : o end # Answers 'what is the single common Puppet Type describing o', or if o is an Array or Hash, what is the # single common type of the elements (or keys and elements for a Hash). # @api public # def infer(o) # Optimize the most common cases into direct calls. # Explicit if/elsif/else is faster than case if o.is_a?(String) infer_String(o) elsif o.is_a?(Integer) # need subclasses for Ruby < 2.4 infer_Integer(o) elsif o.is_a?(Array) infer_Array(o) elsif o.is_a?(Hash) infer_Hash(o) elsif o.is_a?(Evaluator::PuppetProc) infer_PuppetProc(o) else @@infer_visitor.visit_this_0(self, o) end end def infer_generic(o) generalize(infer(o)) end # Answers 'what is the set of Puppet Types of o' # @api public # def infer_set(o) if o.instance_of?(Array) infer_set_Array(o) elsif o.instance_of?(Hash) infer_set_Hash(o) elsif o.instance_of?(SemanticPuppet::Version) infer_set_Version(o) else infer(o) end end # Answers 'is o an instance of type t' # @api public # def self.instance?(t, o) singleton.instance?(t,o) end # Answers 'is o an instance of type t' # @api public # def instance?(t, o) if t.is_a?(Module) t = type(t) end t.is_a?(PAnyType) ? t.instance?(o) : false end # Answers if t is a puppet type # @api public # def is_ptype?(t) t.is_a?(PAnyType) end # Answers if t represents the puppet type PUndefType # @api public # def is_pnil?(t) t.nil? || t.is_a?(PUndefType) end # Answers, 'What is the common type of t1 and t2?' # # TODO: The current implementation should be optimized for performance # # @api public # def common_type(t1, t2) raise ArgumentError, 'two types expected' unless (is_ptype?(t1) || is_pnil?(t1)) && (is_ptype?(t2) || is_pnil?(t2)) # TODO: This is not right since Scalar U Undef is Any # if either is nil, the common type is the other if is_pnil?(t1) return t2 elsif is_pnil?(t2) return t1 end # If either side is Unit, it is the other type if t1.is_a?(PUnitType) return t2 elsif t2.is_a?(PUnitType) return t1 end # Simple case, one is assignable to the other if assignable?(t1, t2) return t1 elsif assignable?(t2, t1) return t2 end # when both are arrays, return an array with common element type if t1.is_a?(PArrayType) && t2.is_a?(PArrayType) return PArrayType.new(common_type(t1.element_type, t2.element_type)) end # when both are hashes, return a hash with common key- and element type if t1.is_a?(PHashType) && t2.is_a?(PHashType) key_type = common_type(t1.key_type, t2.key_type) value_type = common_type(t1.value_type, t2.value_type) return PHashType.new(key_type, value_type) end # when both are host-classes, reduce to PHostClass[] (since one was not assignable to the other) if t1.is_a?(PClassType) && t2.is_a?(PClassType) return PClassType::DEFAULT end # when both are resources, reduce to Resource[T] or Resource[] (since one was not assignable to the other) if t1.is_a?(PResourceType) && t2.is_a?(PResourceType) # only Resource[] unless the type name is the same return t1.type_name == t2.type_name ? PResourceType.new(t1.type_name, nil) : PResourceType::DEFAULT end # Integers have range, expand the range to the common range if t1.is_a?(PIntegerType) && t2.is_a?(PIntegerType) return PIntegerType.new([t1.numeric_from, t2.numeric_from].min, [t1.numeric_to, t2.numeric_to].max) end # Floats have range, expand the range to the common range if t1.is_a?(PFloatType) && t2.is_a?(PFloatType) return PFloatType.new([t1.numeric_from, t2.numeric_from].min, [t1.numeric_to, t2.numeric_to].max) end if t1.is_a?(PStringType) && (t2.is_a?(PStringType) || t2.is_a?(PEnumType)) if(t2.is_a?(PEnumType)) return t1.value.nil? ? PEnumType::DEFAULT : PEnumType.new(t2.values | [t1.value]) end if t1.size_type.nil? || t2.size_type.nil? return t1.value.nil? || t2.value.nil? ? PStringType::DEFAULT : PEnumType.new([t1.value, t2.value]) end return PStringType.new(common_type(t1.size_type, t2.size_type)) end if t1.is_a?(PPatternType) && t2.is_a?(PPatternType) return PPatternType.new(t1.patterns | t2.patterns) end if t1.is_a?(PEnumType) && (t2.is_a?(PStringType) || t2.is_a?(PEnumType)) # The common type is one that complies with either set if t2.is_a?(PEnumType) return PEnumType.new(t1.values | t2.values) end return t2.value.nil? ? PEnumType::DEFAULT : PEnumType.new(t1.values | [t2.value]) end if t1.is_a?(PVariantType) && t2.is_a?(PVariantType) # The common type is one that complies with either set return PVariantType.maybe_create(t1.types | t2.types) end if t1.is_a?(PRegexpType) && t2.is_a?(PRegexpType) # if they were identical, the general rule would return a parameterized regexp # since they were not, the result is a generic regexp type return PRegexpType::DEFAULT end if t1.is_a?(PCallableType) && t2.is_a?(PCallableType) # They do not have the same signature, and one is not assignable to the other, # what remains is the most general form of Callable return PCallableType::DEFAULT end # Common abstract types, from most specific to most general if common_numeric?(t1, t2) return PNumericType::DEFAULT end if common_scalar_data?(t1, t2) return PScalarDataType::DEFAULT end if common_scalar?(t1, t2) return PScalarType::DEFAULT end if common_data?(t1,t2) return TypeFactory.data end # Meta types Type[Integer] + Type[String] => Type[Data] if t1.is_a?(PTypeType) && t2.is_a?(PTypeType) return PTypeType.new(common_type(t1.type, t2.type)) end if common_rich_data?(t1,t2) return TypeFactory.rich_data end # If both are Runtime types if t1.is_a?(PRuntimeType) && t2.is_a?(PRuntimeType) if t1.runtime == t2.runtime && t1.runtime_type_name == t2.runtime_type_name return t1 end # finding the common super class requires that names are resolved to class # NOTE: This only supports runtime type of :ruby c1 = ClassLoader.provide_from_type(t1) c2 = ClassLoader.provide_from_type(t2) if c1 && c2 c2_superclasses = superclasses(c2) superclasses(c1).each do|c1_super| c2_superclasses.each do |c2_super| if c1_super == c2_super return PRuntimeType.new(:ruby, c1_super.name) end end end end end # They better both be Any type, or the wrong thing was asked and nil is returned t1.is_a?(PAnyType) && t2.is_a?(PAnyType) ? PAnyType::DEFAULT : nil end # Produces the superclasses of the given class, including the class def superclasses(c) result = [c] while s = c.superclass result << s c = s end result end # Reduces an enumerable of types to a single common type. # @api public # def reduce_type(enumerable) enumerable.reduce(nil) {|memo, t| common_type(memo, t) } end # Reduce an enumerable of objects to a single common type # @api public # def infer_and_reduce_type(enumerable) reduce_type(enumerable.map {|o| infer(o) }) end # The type of all modules is PTypeType # @api private # def infer_Module(o) PTypeType::new(PRuntimeType.new(:ruby, o.name)) end # @api private def infer_Closure(o) o.type end # @api private def infer_Iterator(o) PIteratorType.new(o.element_type) end # @api private def infer_Function(o) o.class.dispatcher.to_type end # @api private def infer_Object(o) if o.is_a?(PuppetObject) o._pcore_type else name = o.class.name return PRuntimeType.new(:ruby, nil) if name.nil? # anonymous class that doesn't implement PuppetObject is impossible to infer ir = Loaders.implementation_registry type = ir.nil? ? nil : ir.type_for_module(name) return PRuntimeType.new(:ruby, name) if type.nil? if type.is_a?(PObjectType) && type.parameterized? type = PObjectTypeExtension.create_from_instance(type, o) end type end end # The type of all types is PTypeType # @api private # def infer_PAnyType(o) PTypeType.new(o) end # The type of all types is PTypeType # This is the metatype short circuit. # @api private # def infer_PTypeType(o) PTypeType.new(o) end # @api private def infer_String(o) PStringType.new(o) end # @api private def infer_Float(o) PFloatType.new(o, o) end # @api private def infer_Integer(o) PIntegerType.new(o, o) end # @api private def infer_Regexp(o) PRegexpType.new(o) end # @api private def infer_NilClass(o) PUndefType::DEFAULT end # @api private # @param o [Proc] def infer_Proc(o) min = 0 max = 0 mapped_types = o.parameters.map do |p| case p[0] when :rest max = :default break PAnyType::DEFAULT when :req min += 1 end max += 1 PAnyType::DEFAULT end param_types = Types::PTupleType.new(mapped_types, Types::PIntegerType.new(min, max)) Types::PCallableType.new(param_types) end # @api private def infer_PuppetProc(o) infer_Closure(o.closure) end # Inference of :default as PDefaultType, and all other are Ruby[Symbol] # @api private def infer_Symbol(o) case o when :default PDefaultType::DEFAULT when :undef PUndefType::DEFAULT else infer_Object(o) end end # @api private def infer_Sensitive(o) PSensitiveType.new(infer(o.unwrap)) end # @api private def infer_Timespan(o) PTimespanType.new(o, o) end # @api private def infer_Timestamp(o) PTimestampType.new(o, o) end # @api private def infer_TrueClass(o) PBooleanType::TRUE end # @api private def infer_FalseClass(o) PBooleanType::FALSE end # @api private def infer_URI(o) PURIType.new(o) end # @api private # A Puppet::Parser::Resource, or Puppet::Resource # def infer_Resource(o) # Only Puppet::Resource can have a title that is a symbol :undef, a PResource cannot. # A mapping must be made to empty string. A nil value will result in an error later title = o.title title = '' if :undef == title PTypeType.new(PResourceType.new(o.type.to_s, title)) end # @api private def infer_Array(o) if o.instance_of?(Array) if o.empty? PArrayType::EMPTY else PArrayType.new(infer_and_reduce_type(o), size_as_type(o)) end else infer_Object(o) end end # @api private def infer_Binary(o) PBinaryType::DEFAULT end # @api private def infer_Version(o) PSemVerType::DEFAULT end # @api private def infer_VersionRange(o) PSemVerRangeType::DEFAULT end # @api private def infer_Hash(o) if o.instance_of?(Hash) if o.empty? PHashType::EMPTY else ktype = infer_and_reduce_type(o.keys) etype = infer_and_reduce_type(o.values) PHashType.new(ktype, etype, size_as_type(o)) end else infer_Object(o) end end def size_as_type(collection) size = collection.size PIntegerType.new(size, size) end # Common case for everything that intrinsically only has a single type def infer_set_Object(o) infer(o) end def infer_set_Array(o) if o.empty? PArrayType::EMPTY else PTupleType.new(o.map {|x| infer_set(x) }) end end def infer_set_Hash(o) if o.empty? PHashType::EMPTY elsif o.keys.all? {|k| PStringType::NON_EMPTY.instance?(k) } PStructType.new(o.each_pair.map { |k,v| PStructElement.new(PStringType.new(k), infer_set(v)) }) else ktype = PVariantType.maybe_create(o.keys.map {|k| infer_set(k) }) etype = PVariantType.maybe_create(o.values.map {|e| infer_set(e) }) PHashType.new(unwrap_single_variant(ktype), unwrap_single_variant(etype), size_as_type(o)) end end # @api private def infer_set_Version(o) PSemVerType.new([SemanticPuppet::VersionRange.new(o, o)]) end def unwrap_single_variant(possible_variant) if possible_variant.is_a?(PVariantType) && possible_variant.types.size == 1 possible_variant.types[0] else possible_variant end end # Transform int range to a size constraint # if range == nil the constraint is 1,1 # if range.from == nil min size = 1 # if range.to == nil max size == Infinity # def size_range(range) return [1,1] if range.nil? from = range.from to = range.to x = from.nil? ? 1 : from y = to.nil? ? TheInfinity : to [x, y] end # @api private def self.is_kind_of_callable?(t, optional = true) t.is_a?(PAnyType) && t.kind_of_callable?(optional) end def max(a,b) a >=b ? a : b end def min(a,b) a <= b ? a : b end # Produces the tuple entry at the given index given a tuple type, its from/to constraints on the last # type, and an index. # Produces nil if the index is out of bounds # from must be less than to, and from may not be less than 0 # # @api private # def tuple_entry_at(tuple_t, from, to, index) regular = (tuple_t.types.size - 1) if index < regular tuple_t.types[index] elsif index < regular + to # in the varargs part tuple_t.types[-1] else nil end end # Debugging to_s to reduce the amount of output def to_s '[a TypeCalculator]' end private def common_rich_data?(t1, t2) d = TypeFactory.rich_data d.assignable?(t1) && d.assignable?(t2) end def common_data?(t1, t2) d = TypeFactory.data d.assignable?(t1) && d.assignable?(t2) end def common_scalar_data?(t1, t2) PScalarDataType::DEFAULT.assignable?(t1) && PScalarDataType::DEFAULT.assignable?(t2) end def common_scalar?(t1, t2) PScalarType::DEFAULT.assignable?(t1) && PScalarType::DEFAULT.assignable?(t2) end def common_numeric?(t1, t2) PNumericType::DEFAULT.assignable?(t1) && PNumericType::DEFAULT.assignable?(t2) end end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/type_conversion_error.rb����������������������������������������0000644�0052762�0001160�00000000525�13417161721�024210� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� module Puppet::Pops::Types # Raised when a conversion of a value to another type failed. # class TypeConversionError < Puppet::Error # Creates a new instance with a given message # # @param message [String] The error message describing what failed # def initialize(message) super(message) end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/type_formatter.rb�����������������������������������������������0000644�0052762�0001160�00000045573�13417161721�022631� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Types # String # ------ # Creates a string representation of a type. # # @api public # class TypeFormatter # Produces a String representation of the given type. # @param t [PAnyType] the type to produce a string form # @return [String] the type in string form # # @api public # def self.string(t) @singleton.string(t) end # @return [TypeCalculator] the singleton instance # # @api private def self.singleton @singleton end def expanded tf = clone tf.instance_variable_set(:@expanded, true) tf end def indented(indent = 0, indent_width = 2) tf = clone tf.instance_variable_set(:@indent, indent) tf.instance_variable_set(:@indent_width, indent_width) tf end def ruby(ref_ctor) tf = clone tf.instance_variable_set(:@ruby, true) tf.instance_variable_set(:@ref_ctor, ref_ctor) tf end # Produces a string representing the type # @api public # def string(t) @bld = '' append_string(t) @bld end # Produces an string containing newline characters and indentation that represents the given # type or literal _t_. # # @param t [Object] the type or literal to produce a string for # @param indent [Integer] the current indentation level # @param indent_width [Integer] the number of spaces to use for one indentation # # @api public def indented_string(t, indent = 0, indent_width = 2) @bld = '' append_indented_string(t, indent, indent_width) @bld end # @api private def append_indented_string(t, indent = 0, indent_width = 2, skip_initial_indent = false) save_indent = @indent save_indent_width = @indent_width @indent = indent @indent_width = indent_width begin (@indent * @indent_width).times { @bld << ' ' } unless skip_initial_indent append_string(t) @bld << "\n" ensure @indent = save_indent @indent_width = save_indent_width end end # @api private def ruby_string(ref_ctor, indent, t) @ruby = true @ref_ctor = ref_ctor begin indented_string(t, indent) ensure @ruby = nil @ref_ctor = nil end end def append_default @bld << 'default' end def append_string(t) if @ruby && t.is_a?(PAnyType) @ruby = false begin @bld << @ref_ctor << '(' @@string_visitor.visit_this_0(self, TypeFormatter.new.string(t)) @bld << ')' ensure @ruby = true end else @@string_visitor.visit_this_0(self, t) end end # Produces a string representing the type where type aliases have been expanded # @api public # def alias_expanded_string(t) @expanded = true begin string(t) ensure @expanded = false end end # Produces a debug string representing the type (possibly with more information that the regular string format) # @api public # def debug_string(t) @debug = true begin string(t) ensure @debug = false end end # @api private def string_PAnyType(_) ; @bld << 'Any' ; end # @api private def string_PUndefType(_) ; @bld << 'Undef' ; end # @api private def string_PDefaultType(_) ; @bld << 'Default' ; end # @api private def string_PBooleanType(t) append_array('Boolean', t.value.nil?) { append_string(t.value) } end # @api private def string_PScalarType(_) ; @bld << 'Scalar' ; end # @api private def string_PScalarDataType(_) ; @bld << 'ScalarData' ; end # @api private def string_PNumericType(_) ; @bld << 'Numeric' ; end # @api private def string_PBinaryType(_) ; @bld << 'Binary' ; end # @api private def string_PIntegerType(t) append_array('Integer', t.unbounded?) { append_elements(range_array_part(t)) } end # @api private def string_PTypeType(t) append_array('Type', t.type.nil?) { append_string(t.type) } end # @api private def string_PInitType(t) append_array('Init', t.type.nil?) { append_strings([t.type, *t.init_args]) } end # @api private def string_PIterableType(t) append_array('Iterable', t.element_type.nil?) { append_string(t.element_type) } end # @api private def string_PIteratorType(t) append_array('Iterator', t.element_type.nil?) { append_string(t.element_type) } end # @api private def string_PFloatType(t) append_array('Float', t.unbounded? ) { append_elements(range_array_part(t)) } end # @api private def string_PRegexpType(t) append_array('Regexp', t.pattern.nil?) { append_string(t.regexp) } end # @api private def string_PStringType(t) range = range_array_part(t.size_type) append_array('String', range.empty? && !(@debug && !t.value.nil?)) do if @debug append_elements(range, !t.value.nil?) append_string(t.value) unless t.value.nil? else append_elements(range) end end end # @api private def string_PEnumType(t) append_array('Enum', t.values.empty?) do append_strings(t.values) if t.case_insensitive? @bld << COMMA_SEP append_string(true) end end end # @api private def string_PVariantType(t) append_array('Variant', t.types.empty?) { append_strings(t.types) } end # @api private def string_PSemVerType(t) append_array('SemVer', t.ranges.empty?) { append_strings(t.ranges) } end # @api private def string_PSemVerRangeType(t) @bld << 'SemVerRange' end # @api private def string_PTimestampType(t) min = t.from max = t.to append_array('Timestamp', min.nil? && max.nil?) do min.nil? ? append_default : append_string(min) unless max.nil? || max == min @bld << COMMA_SEP append_string(max) end end end # @api private def string_PTimespanType(t) min = t.from max = t.to append_array('Timespan', min.nil? && max.nil?) do min.nil? ? append_default : append_string(min) unless max.nil? || max == min @bld << COMMA_SEP append_string(max) end end end # @api private def string_PTupleType(t) append_array('Tuple', t.types.empty?) do append_strings(t.types, true) append_elements(range_array_part(t.size_type), true) chomp_list end end # @api private def string_PCallableType(t) if t.return_type.nil? append_array('Callable', t.param_types.nil?) { append_callable_params(t) } else if t.param_types.nil? append_array('Callable', false) { append_strings([[], t.return_type], false) } else append_array('Callable', false) do append_array('', false) { append_callable_params(t) } @bld << COMMA_SEP append_string(t.return_type) end end end end def append_callable_params(t) # translate to string, and skip Unit types append_strings(t.param_types.types.reject {|t2| t2.class == PUnitType }, true) if t.param_types.types.empty? append_strings([0, 0], true) else append_elements(range_array_part(t.param_types.size_type), true) end # Add block T last (after min, max) if present) # append_strings([t.block_type], true) unless t.block_type.nil? chomp_list end # @api private def string_PStructType(t) append_array('Struct', t.elements.empty?) { append_hash(Hash[t.elements.map {|e| struct_element_pair(e) }]) } end # @api private def struct_element_pair(t) k = t.key_type value_optional = t.value_type.assignable?(PUndefType::DEFAULT) if k.is_a?(POptionalType) # Output as literal String k = t.name if value_optional else k = value_optional ? PNotUndefType.new(k) : t.name end [k, t.value_type] end # @api private def string_PPatternType(t) append_array('Pattern', t.patterns.empty?) { append_strings(t.patterns.map(&:regexp)) } end # @api private def string_PCollectionType(t) range = range_array_part(t.size_type) append_array('Collection', range.empty? ) { append_elements(range) } end def string_Object(t) type = TypeCalculator.infer(t) if type.is_a?(PObjectTypeExtension) type = type.base_type end if type.is_a?(PObjectType) init_hash = type.extract_init_hash(t) @bld << type.name << '(' if @indent append_indented_string(init_hash, @indent, @indent_width, true) @bld.chomp! else append_string(init_hash) end @bld << ')' else @bld << 'Instance of ' append_string(type) end end def string_PuppetObject(t) @bld << t._pcore_type.name << '(' if @indent append_indented_string(t._pcore_init_hash, @indent, @indent_width, true) @bld.chomp! else append_string(t._pcore_init_hash) end @bld << ')' end # @api private def string_PURIType(t) append_array('URI', t.parameters.nil?) { append_string(t._pcore_init_hash['parameters']) } end def string_URI(t) @bld << 'URI(' if @indent append_indented_string(t.to_s, @indent, @indent_width, true) @bld.chomp! else append_string(t.to_s) end @bld << ')' end # @api private def string_PUnitType(_) @bld << 'Unit' end # @api private def string_PRuntimeType(t) append_array('Runtime', t.runtime.nil? && t.name_or_pattern.nil?) { append_strings([t.runtime, t.name_or_pattern]) } end # @api private def string_PArrayType(t) if t.has_empty_range? append_array('Array') { append_strings([0, 0]) } else append_array('Array', t == PArrayType::DEFAULT) do append_strings([t.element_type], true) append_elements(range_array_part(t.size_type), true) chomp_list end end end # @api private def string_PHashType(t) if t.has_empty_range? append_array('Hash') { append_strings([0, 0]) } else append_array('Hash', t == PHashType::DEFAULT) do append_strings([t.key_type, t.value_type], true) append_elements(range_array_part(t.size_type), true) chomp_list end end end # @api private def string_PCatalogEntryType(_) @bld << 'CatalogEntry' end # @api private def string_PClassType(t) append_array('Class', t.class_name.nil?) { append_elements([t.class_name]) } end # @api private def string_PResourceType(t) if t.type_name append_array(capitalize_segments(t.type_name), t.title.nil?) { append_string(t.title) } else @bld << 'Resource' end end # @api private def string_PNotUndefType(t) contained_type = t.type append_array('NotUndef', contained_type.nil? || contained_type.class == PAnyType) do if contained_type.is_a?(PStringType) && !contained_type.value.nil? append_string(contained_type.value) else append_string(contained_type) end end end # @api private def string_PAnnotatedMember(m) hash = m._pcore_init_hash if hash.size == 1 string(m.type) else string(hash) end end # Used when printing names of well known keys in an Object type. Placed in a separate # method to allow override. # @api private def symbolic_key(key) @ruby ? "'#{key}'" : key end # @api private def string_PTypeSetType(t) append_array('TypeSet') do append_hash(t._pcore_init_hash.each, proc { |k| @bld << symbolic_key(k) }) do |k,v| case k when KEY_TYPES old_ts = @type_set @type_set = t begin append_hash(v, proc { |tk| @bld << symbolic_key(tk) }) do |tk, tv| if tv.is_a?(Hash) append_object_hash(tv) else append_string(tv) end end rescue @type_set = old_ts end when KEY_REFERENCES append_hash(v, proc { |tk| @bld << symbolic_key(tk) }) else append_string(v) end end end end # @api private def string_PObjectType(t) if @expanded append_object_hash(t._pcore_init_hash(@type_set.nil? || !@type_set.defines_type?(t))) else @bld << (@type_set ? @type_set.name_for(t, t.label) : t.label) end end def string_PObjectTypeExtension(t) append_array(@type_set ? @type_set.name_for(t, t.name) : t.name, false) do ips = t.init_parameters if ips.is_a?(Array) append_strings(ips) else append_string(ips) end end end # @api private def string_PSensitiveType(t) append_array('Sensitive', PAnyType::DEFAULT == t.type) { append_string(t.type) } end # @api private def string_POptionalType(t) optional_type = t.optional_type append_array('Optional', optional_type.nil?) do if optional_type.is_a?(PStringType) && !optional_type.value.nil? append_string(optional_type.value) else append_string(optional_type) end end end # @api private def string_PTypeAliasType(t) expand = @expanded if expand && t.self_recursion? @guard ||= RecursionGuard.new @guard.with_this(t) { |state| format_type_alias_type(t, (state & RecursionGuard::SELF_RECURSION_IN_THIS) == 0) } else format_type_alias_type(t, expand) end end # @api private def format_type_alias_type(t, expand) if @type_set.nil? @bld << t.name if expand && !Loader::StaticLoader::BUILTIN_ALIASES.include?(t.name) @bld << ' = ' append_string(t.resolved_type) end else if expand && @type_set.defines_type?(t) append_string(t.resolved_type) else @bld << @type_set.name_for(t, t.name) end end end # @api private def string_PTypeReferenceType(t) append_array('TypeReference') { append_string(t.type_string) } end # @api private def string_Array(t) append_array('') do if @indent && !is_short_array?(t) @indent += 1 t.each { |elem| newline; append_string(elem); @bld << COMMA_SEP } chomp_list @indent -= 1 newline else append_strings(t) end end end # @api private def string_FalseClass(t) ; @bld << 'false' ; end # @api private def string_Hash(t) append_hash(t) end # @api private def string_Module(t) append_string(TypeCalculator.singleton.type(t)) end # @api private def string_NilClass(t) ; @bld << (@ruby ? 'nil' : 'undef') ; end # @api private def string_Numeric(t) ; @bld << t.to_s ; end # @api private def string_Regexp(t) ; @bld << PRegexpType.regexp_to_s_with_delimiters(t); end # @api private def string_String(t) # Use single qoute on strings that does not contain single quotes, control characters, or backslashes. @bld << StringConverter.singleton.puppet_quote(t) end # @api private def string_Symbol(t) ; @bld << t.to_s ; end # @api private def string_TrueClass(t) ; @bld << 'true' ; end # @api private def string_Version(t) ; @bld << "'#{t}'" ; end # @api private def string_VersionRange(t) ; @bld << "'#{t}'" ; end # @api private def string_Timespan(t) ; @bld << "'#{t}'" ; end # @api private def string_Timestamp(t) ; @bld << "'#{t}'" ; end # Debugging to_s to reduce the amount of output def to_s '[a TypeFormatter]' end NAME_SEGMENT_SEPARATOR = '::'.freeze STARTS_WITH_ASCII_CAPITAL = /^[A-Z]/ # Capitalizes each segment in a name separated with the {NAME_SEPARATOR} conditionally. The name # will not be subject to capitalization if it already starts with a capital letter. This to avoid # that existing camel casing is lost. # # @param qualified_name [String] the name to capitalize # @return [String] the capitalized name # # @api private def capitalize_segments(qualified_name) if !qualified_name.is_a?(String) || qualified_name =~ STARTS_WITH_ASCII_CAPITAL qualified_name else segments = qualified_name.split(NAME_SEGMENT_SEPARATOR) if segments.size == 1 qualified_name.capitalize else segments.each(&:capitalize!) segments.join(NAME_SEGMENT_SEPARATOR) end end end private COMMA_SEP = ', '.freeze HASH_ENTRY_OP = ' => '.freeze def is_short_array?(t) t.empty? || 100 - @indent * @indent_width > t.inject(0) do |sum, elem| case elem when true, false, nil, Numeric, Symbol sum + elem.inspect.length() when String sum + 2 + elem.length when Hash, Array sum + (elem.empty? ? 2 : 1000) else sum + 1000 end end end def range_array_part(t) if t.nil? || t.unbounded? EMPTY_ARRAY else result = [t.from.nil? ? 'default' : t.from.to_s] result << t.to.to_s unless t.to.nil? result end end def append_object_hash(hash) begin @expanded = false append_array('Object') do append_hash(hash, proc { |k| @bld << symbolic_key(k) }) do |k,v| case k when KEY_ATTRIBUTES, KEY_FUNCTIONS # Types might need to be output as type references append_hash(v) do |_, fv| if fv.is_a?(Hash) append_hash(fv, proc { |fak| @bld << symbolic_key(fak) }) do |fak,fav| case fak when KEY_KIND @bld << fav else append_string(fav) end end else append_string(fv) end end when KEY_EQUALITY append_array('') { append_strings(v) } if v.is_a?(Array) else append_string(v) end end end ensure @expanded = true end end def append_elements(array, to_be_continued = false) case array.size when 0 when 1 @bld << array[0] @bld << COMMA_SEP if to_be_continued else array.each { |elem| @bld << elem << COMMA_SEP } chomp_list unless to_be_continued end end def append_strings(array, to_be_continued = false) case array.size when 0 when 1 append_string(array[0]) @bld << COMMA_SEP if to_be_continued else array.each do |elem| append_string(elem) @bld << COMMA_SEP end chomp_list unless to_be_continued end end def append_array(start, empty = false) @bld << start unless empty @bld << '[' yield @bld << ']' end end def append_hash(hash, key_proc = nil) @bld << '{' @indent += 1 if @indent hash.each do |k, v| newline if @indent if key_proc.nil? append_string(k) else key_proc.call(k) end @bld << HASH_ENTRY_OP if block_given? yield(k, v) else append_string(v) end @bld << COMMA_SEP end chomp_list if @indent @indent -= 1 newline end @bld << '}' end def newline @bld.rstrip! @bld << "\n" (@indent * @indent_width).times { @bld << ' ' } end def chomp_list @bld.chomp!(COMMA_SEP) end @singleton = new @@string_visitor = Visitor.new(nil, 'string',0,0) end end end �������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/type_mismatch_describer.rb��������������������������������������0000644�0052762�0001160�00000106215�13417161721�024444� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Types # @api private class TypePathElement attr_reader :key def initialize(key) @key = key end def hash key.hash end def ==(o) self.class == o.class && key == o.key end def eql?(o) self == o end end # @api private class SubjectPathElement < TypePathElement def to_s key end end # @api private class EntryValuePathElement < TypePathElement def to_s "entry '#{key}'" end end # @api private class EntryKeyPathElement < TypePathElement def to_s "key of entry '#{key}'" end end # @api private class ParameterPathElement < TypePathElement def to_s "parameter '#{key}'" end end # @api private class ReturnTypeElement < TypePathElement def initialize(name = 'return') super(name) end def to_s key end end # @api private class BlockPathElement < ParameterPathElement def initialize(name = 'block') super(name) end def to_s key end end # @api private class ArrayPathElement < TypePathElement def to_s "index #{key}" end end # @api private class VariantPathElement < TypePathElement def to_s "variant #{key}" end end # @api private class SignaturePathElement < VariantPathElement def to_s "#{key+1}." end end # @api private class Mismatch attr_reader :path def initialize(path) @path = path || EMPTY_ARRAY end def canonical_path @canonical_path ||= @path.reject { |e| e.is_a?(VariantPathElement) } end def message(variant, position) "#{variant}unknown mismatch#{position}" end def merge(path, o) self.class.new(path) end def ==(o) self.class == o.class && canonical_path == o.canonical_path end def eql?(o) self == o end def hash canonical_path.hash end def chop_path(element_index) return self if element_index >= @path.size chopped_path = @path.clone chopped_path.delete_at(element_index) copy = self.clone copy.instance_variable_set(:@path, chopped_path) copy end def path_string @path.join(' ') end def to_s format end def format p = @path variant = '' position = '' unless p.empty? f = p.first if f.is_a?(SignaturePathElement) variant = " #{f}" p = p.drop(1) end position = " #{p.join(' ')}" unless p.empty? end message(variant, position) end end # @abstract # @api private class KeyMismatch < Mismatch attr_reader :key def initialize(path, key) super(path) @key = key end def ==(o) super.==(o) && key == o.key end def hash super.hash ^ key.hash end end # @api private class MissingKey < KeyMismatch def message(variant, position) "#{variant}#{position} expects a value for key '#{key}'" end end # @api private class MissingParameter < KeyMismatch def message(variant, position) "#{variant}#{position} expects a value for parameter '#{key}'" end end # @api private class ExtraneousKey < KeyMismatch def message(variant, position) "#{variant}#{position} unrecognized key '#{@key}'" end end # @api private class InvalidParameter < ExtraneousKey def message(variant, position) "#{variant}#{position} has no parameter named '#{@key}'" end end # @api private class UnexpectedBlock < Mismatch def message(variant, position) "#{variant}#{position} does not expect a block" end end # @api private class MissingRequiredBlock < Mismatch def message(variant, position) "#{variant}#{position} expects a block" end end # @api private class UnresolvedTypeReference < Mismatch attr_reader :unresolved def initialize(path, unresolved) super(path) @unresolved = unresolved end def ==(o) super.==(o) && @unresolved == o.unresolved end def hash @unresolved.hash end def message(variant, position) "#{variant}#{position} references an unresolved type '#{@unresolved}'" end end # @api private class ExpectedActualMismatch < Mismatch attr_reader :expected, :actual def initialize(path, expected, actual) super(path) @expected = (expected.is_a?(Array) ? PVariantType.maybe_create(expected) : expected).normalize @actual = actual.normalize end def ==(o) super.==(o) && expected == o.expected && actual == o.actual end def hash [canonical_path, expected, actual].hash end end # @api private class TypeMismatch < ExpectedActualMismatch include LabelProvider # @return A new instance with the least restrictive respective boundaries def merge(path, o) self.class.new(path, [expected, o.expected].flatten.uniq, actual) end def message(variant, position) e = expected a = actual multi = false if e.is_a?(POptionalType) e = e.optional_type optional = true end if e.is_a?(PVariantType) e = e.types end if e.is_a?(Array) if report_detailed?(e, a) a = detailed_actual_to_s(e, a) e = e.map { |t| t.to_alias_expanded_s } else e = e.map { |t| short_name(t) }.uniq a = short_name(a) end e.insert(0, 'Undef') if optional case e.size when 1 e = e[0] when 2 e = "#{e[0]} or #{e[1]}" multi = true else e = "#{e[0..e.size-2].join(', ')}, or #{e[e.size-1]}" multi = true end else if report_detailed?(e, a) a = detailed_actual_to_s(e, a) e = e.to_alias_expanded_s else e = short_name(e) a = short_name(a) end if optional e = "Undef or #{e}" multi = true end end if multi "#{variant}#{position} expects a value of type #{e}, got #{label(a)}" else "#{variant}#{position} expects #{a_an(e)} value, got #{label(a)}" end end def label(o) o.to_s end private def short_name(t) # Ensure that Optional, NotUndef, Sensitive, and Type are reported with included # type parameter. if t.is_a?(PTypeWithContainedType) && !(t.type.nil? || t.type.class == PAnyType) "#{t.name}[#{t.type.name}]" else t.name.nil? ? t.simple_name : t.name end end # Answers the question if `e` is a specialized type of `a` # @param e [PAnyType] the expected type # @param a [PAnyType] the actual type # @return [Boolean] `true` when the _e_ is a specialization of _a_ # def specialization(e, a) case e when PStructType a.is_a?(PHashType) when PTupleType a.is_a?(PArrayType) else false end end # Decides whether or not the report must be fully detailed, or if generalization can be permitted # in the mismatch report. All comparisons are made using resolved aliases rather than the alias # itself. # # @param e [PAnyType,Array[PAnyType]] the expected type or array of expected types # @param a [PAnyType] the actual type # @return [Boolean] `true` when the class of _a_ equals the class _e_ or, # in case _e_ is an `Array`, the class of at least one element of _e_ def always_fully_detailed?(e, a) if e.is_a?(Array) e.any? { |t| always_fully_detailed?(t, a) } else e.class == a.class || e.is_a?(PTypeAliasType) || a.is_a?(PTypeAliasType) || specialization(e, a) end end # @param e [PAnyType,Array[PAnyType]] the expected type or array of expected types # @param a [PAnyType] the actual type # @return [Boolean] `true` when _a_ is assignable to _e_ or, in case _e_ is an `Array`, # to at least one element of _e_ def any_assignable?(e, a) e.is_a?(Array) ? e.any? { |t| t.assignable?(a) } : e.assignable?(a) end # @param e [PAnyType,Array[PAnyType]] the expected type or array of expected types # @param a [PAnyType] the actual type # @return [Boolean] `true` when _a_ is assignable to the default generalization of _e_ or, # in case _e_ is an `Array`, to the default generalization of at least one element of _e_ def assignable_to_default?(e, a) if e.is_a?(Array) e.any? { |t| assignable_to_default?(t, a) } else e = e.resolved_type if e.is_a?(PTypeAliasType) e.class::DEFAULT.assignable?(a) end end # @param e [PAnyType,Array[PAnyType]] the expected type or array of expected types # @param a [PAnyType] the actual type # @return [Boolean] `true` when either #always_fully_detailed or #assignable_to_default returns `true` def report_detailed?(e, a) always_fully_detailed?(e, a) || assignable_to_default?(e, a) end # Returns its argument with all type aliases resolved # @param e [PAnyType,Array[PAnyType]] the expected type or array of expected types # @return [PAnyType,Array[PAnyType]] the resolved result def all_resolved(e) if e.is_a?(Array) e.map { |t| all_resolved(t) } else e.is_a?(PTypeAliasType) ? all_resolved(e.resolved_type) : e end end # Returns a string that either represents the generalized type _a_ or the type _a_ verbatim. The latter # form is used when at least one of the following conditions are met: # # - #always_fully_detailed returns `true` for the resolved type of _e_ and _a_ # - #any_assignable? returns `true` for the resolved type of _e_ and the generalized type of _a_. # # @param e [PAnyType,Array[PAnyType]] the expected type or array of expected types # @param a [PAnyType] the actual type # @return [String] The string representation of the type _a_ or generalized type _a_ def detailed_actual_to_s(e, a) e = all_resolved(e) if always_fully_detailed?(e, a) a.to_alias_expanded_s else any_assignable?(e, a.generalize) ? a.to_alias_expanded_s : a.simple_name end end end # @api private class PatternMismatch < TypeMismatch def message(variant, position) e = expected value_pfx = '' if e.is_a?(POptionalType) e = e.optional_type value_pfx = 'an undef value or ' end "#{variant}#{position} expects #{value_pfx}a match for #{e.to_alias_expanded_s}, got #{actual_string}" end def actual_string a = actual a.is_a?(PStringType) && !a.value.nil? ? "'#{a.value}'" : short_name(a) end end # @api private class SizeMismatch < ExpectedActualMismatch def from @expected.from || 0 end def to @expected.to || Float::INFINITY end # @return A new instance with the least restrictive respective boundaries def merge(path, o) range = PIntegerType.new(from < o.from ? from : o.from, to > o.to ? to : o.to) self.class.new(path, range, @actual) end def message(variant, position) "#{variant}#{position} expects size to be #{range_to_s(expected, '0')}, got #{range_to_s(actual, '0')}" end def range_to_s(range, zero_string) min = range.from || 0 max = range.to || Float::INFINITY if min == max min == 0 ? zero_string : min.to_s elsif min == 0 max == Float::INFINITY ? 'unlimited' : "at most #{max}" elsif max == Float::INFINITY "at least #{min}" else "between #{min} and #{max}" end end end # @api private class CountMismatch < SizeMismatch def initialize(path, expected, actual) super(path, expected, actual) end def message(variant, position) min = expected.from || 0 max = expected.to || Float::INFINITY suffix = min == 1 && (max == 1 || max == Float::INFINITY) || min == 0 && max == 1 ? '' : 's' "#{variant}#{position} expects #{range_to_s(expected, 'no')} argument#{suffix}, got #{range_to_s(actual, 'none')}" end end # @api private class TypeMismatchDescriber def self.validate_parameters(subject, params_struct, given_hash, missing_ok = false) singleton.validate_parameters(subject, params_struct, given_hash, missing_ok) end def self.validate_default_parameter(subject, param_name, param_type, value) singleton.validate_default_parameter(subject, param_name, param_type, value) end def self.describe_signatures(closure, signatures, args_tuple) singleton.describe_signatures(closure, signatures, args_tuple) end def self.singleton @singleton ||= new end def tense_deprecated #TRANSLATORS TypeMismatchDescriber is a class name and 'tense' is a method name and should not be translated message = _("Passing a 'tense' argument to the TypeMismatchDescriber is deprecated and ignored.") message += ' ' + _("Everything is now reported using present tense") Puppet.warn_once('deprecations', 'typemismatch#tense', message) end # Validates that all entries in the give_hash exists in the given param_struct, that their type conforms # with the corresponding param_struct element and that all required values are provided. # # @param subject [String] string to be prepended to the exception message # @param params_struct [PStructType] Struct to use for validation # @param given_hash [Hash<String,Object>] the parameters to validate # @param missing_ok [Boolean] Do not generate errors on missing parameters # @param tense [Symbol] deprecated and ignored # def validate_parameters(subject, params_struct, given_hash, missing_ok = false, tense = :ignored) tense_deprecated unless tense == :ignored errors = describe_struct_signature(params_struct, given_hash, missing_ok).flatten case errors.size when 0 when 1 raise Puppet::ParseError.new("#{subject}:#{errors[0].format}") else errors_str = errors.map { |error| error.format }.join("\n ") raise Puppet::ParseError.new("#{subject}:\n #{errors_str}") end end # Describe a confirmed mismatch using present tense # # @param name [String] name of mismatch # @param expected [PAnyType] expected type # @param actual [PAnyType] actual type # @param tense [Symbol] deprecated and ignored # def describe_mismatch(name, expected, actual, tense = :ignored) tense_deprecated unless tense == :ignored errors = describe(expected, actual, [SubjectPathElement.new(name)]) case errors.size when 0 '' when 1 errors[0].format.strip else errors.map { |error| error.format }.join("\n ") end end # @param subject [String] string to be prepended to the exception message # @param param_name [String] parameter name # @param param_type [PAnyType] parameter type # @param value [Object] value to be validated against the given type # @param tense [Symbol] deprecated and ignored # def validate_default_parameter(subject, param_name, param_type, value, tense = :ignored) tense_deprecated unless tense == :ignored unless param_type.instance?(value) errors = describe(param_type, TypeCalculator.singleton.infer_set(value).generalize, [ParameterPathElement.new(param_name)]) case errors.size when 0 when 1 raise Puppet::ParseError.new("#{subject}:#{errors[0].format}") else errors_str = errors.map { |error| error.format }.join("\n ") raise Puppet::ParseError.new("#{subject}:\n #{errors_str}") end end end # Validates that all entries in the _param_hash_ exists in the given param_struct, that their type conforms # with the corresponding param_struct element and that all required values are provided. # An error message is created for each problem found. # # @param params_struct [PStructType] Struct to use for validation # @param param_hash [Hash<String,Object>] The parameters to validate # @param missing_ok [Boolean] Do not generate errors on missing parameters # @return [Array<Mismatch>] An array of found errors. An empty array indicates no errors. def describe_struct_signature(params_struct, param_hash, missing_ok = false) param_type_hash = params_struct.hashed_elements result = param_hash.each_key.reject { |name| param_type_hash.include?(name) }.map { |name| InvalidParameter.new(nil, name) } params_struct.elements.each do |elem| name = elem.name value = param_hash[name] value_type = elem.value_type if param_hash.include?(name) result << describe(value_type, TypeCalculator.singleton.infer_set(value), [ParameterPathElement.new(name)]) unless value_type.instance?(value) else result << MissingParameter.new(nil, name) unless missing_ok || elem.key_type.is_a?(POptionalType) end end result end def describe_signatures(closure, signatures, args_tuple, tense = :ignored) tense_deprecated unless tense == :ignored error_arrays = [] signatures.each_with_index do |signature, index| error_arrays << describe_signature_arguments(signature, args_tuple, [SignaturePathElement.new(index)]) end # Skip block checks if all signatures have argument errors unless error_arrays.all? { |a| !a.empty? } block_arrays = [] signatures.each_with_index do |signature, index| block_arrays << describe_signature_block(signature, args_tuple, [SignaturePathElement.new(index)]) end bc_count = block_arrays.count { |a| !a.empty? } if bc_count == block_arrays.size # Skip argument errors when all alternatives have block errors error_arrays = block_arrays elsif bc_count > 0 # Merge errors giving argument errors precedence over block errors error_arrays.each_with_index { |a, index| error_arrays[index] = block_arrays[index] if a.empty? } end end return nil if error_arrays.empty? label = closure == 'lambda' ? 'block' : "'#{closure}'" errors = merge_descriptions(0, CountMismatch, error_arrays) if errors.size == 1 "#{label}#{errors[0].format}" else if signatures.size == 1 sig = signatures[0] result = ["#{label} expects (#{signature_string(sig)})"] result.concat(error_arrays[0].map { |e| " rejected:#{e.chop_path(0).format}" }) else result = ["#{label} expects one of:"] signatures.each_with_index do |sg, index| result << " (#{signature_string(sg)})" result.concat(error_arrays[index].map { |e| " rejected:#{e.chop_path(0).format}" }) end end result.join("\n") end end def describe_signature_arguments(signature, args_tuple, path) params_tuple = signature.type.param_types params_size_t = params_tuple.size_type || TypeFactory.range(*params_tuple.size_range) if args_tuple.is_a?(PTupleType) arg_types = args_tuple.types elsif args_tuple.is_a?(PArrayType) arg_types = Array.new(params_tuple.types.size, args_tuple.element_type || PUndefType::DEFAULT) else return [TypeMismatch.new(path, params_tuple, args_tuple)] end if arg_types.last.kind_of_callable? # Check other arguments arg_count = arg_types.size - 1 describe_no_block_arguments(signature, arg_types, path, params_size_t, TypeFactory.range(arg_count, arg_count), arg_count) else args_size_t = TypeFactory.range(*args_tuple.size_range) describe_no_block_arguments(signature, arg_types, path, params_size_t, args_size_t, arg_types.size) end end def describe_signature_block(signature, args_tuple, path) param_block_t = signature.block_type arg_block_t = args_tuple.is_a?(PTupleType) ? args_tuple.types.last : nil if TypeCalculator.is_kind_of_callable?(arg_block_t) # Can't pass a block to a callable that doesn't accept one if param_block_t.nil? [UnexpectedBlock.new(path)] else # Check that the block is of the right type describe(param_block_t, arg_block_t, path + [BlockPathElement.new]) end else # Check that the block is optional if param_block_t.nil? || param_block_t.assignable?(PUndefType::DEFAULT) EMPTY_ARRAY else [MissingRequiredBlock.new(path)] end end end def describe_no_block_arguments(signature, atypes, path, expected_size, actual_size, arg_count) # not assignable if the number of types in actual is outside number of types in expected if expected_size.assignable?(actual_size) etypes = signature.type.param_types.types enames = signature.parameter_names arg_count.times do |index| adx = index >= etypes.size ? etypes.size - 1 : index etype = etypes[adx] unless etype.assignable?(atypes[index]) descriptions = describe(etype, atypes[index], path + [ParameterPathElement.new(enames[adx])]) return descriptions unless descriptions.empty? end end EMPTY_ARRAY else [CountMismatch.new(path, expected_size, actual_size)] end end def describe_PVariantType(expected, original, actual, path) variant_descriptions = [] types = expected.types types = [PUndefType::DEFAULT] + types if original.is_a?(POptionalType) types.each_with_index do |vt, index| d = describe(vt, actual, path + [VariantPathElement.new(index)]) return EMPTY_ARRAY if d.empty? variant_descriptions << d end descriptions = merge_descriptions(path.length, SizeMismatch, variant_descriptions) if original.is_a?(PTypeAliasType) && descriptions.size == 1 # All variants failed in this alias so we report it as a mismatch on the alias # rather than reporting individual failures of the variants [TypeMismatch.new(path, original, actual)] else descriptions end end def merge_descriptions(varying_path_position, size_mismatch_class, variant_descriptions) descriptions = variant_descriptions.flatten [size_mismatch_class, MissingRequiredBlock, UnexpectedBlock, TypeMismatch].each do |mismatch_class| mismatches = descriptions.select { |desc| desc.is_a?(mismatch_class) } if mismatches.size == variant_descriptions.size # If they all have the same canonical path, then we can compact this into one generic_mismatch = mismatches.inject do |prev, curr| break nil unless prev.canonical_path == curr.canonical_path prev.merge(prev.path, curr) end unless generic_mismatch.nil? # Report the generic mismatch and skip the rest descriptions = [generic_mismatch] break end end end descriptions = descriptions.uniq descriptions.size == 1 ? [descriptions[0].chop_path(varying_path_position)] : descriptions end def describe_POptionalType(expected, original, actual, path) return EMPTY_ARRAY if actual.is_a?(PUndefType) internal_describe(expected.optional_type, original.is_a?(PTypeAliasType) ? original : expected, actual, path) end def describe_PEnumType(expected, original, actual, path) [PatternMismatch.new(path, original, actual)] end def describe_PPatternType(expected, original, actual, path) [PatternMismatch.new(path, original, actual)] end def describe_PTypeAliasType(expected, original, actual, path) internal_describe(expected.resolved_type.normalize, expected, actual, path) end def describe_PArrayType(expected, original, actual, path) descriptions = [] element_type = expected.element_type || PAnyType::DEFAULT if actual.is_a?(PTupleType) types = actual.types expected_size = expected.size_type || PCollectionType::DEFAULT_SIZE actual_size = actual.size_type || PIntegerType.new(types.size, types.size) if expected_size.assignable?(actual_size) types.each_with_index do |type, idx| descriptions.concat(describe(element_type, type, path + [ArrayPathElement.new(idx)])) unless element_type.assignable?(type) end else descriptions << SizeMismatch.new(path, expected_size, actual_size) end elsif actual.is_a?(PArrayType) expected_size = expected.size_type actual_size = actual.size_type || PCollectionType::DEFAULT_SIZE if expected_size.nil? || expected_size.assignable?(actual_size) descriptions << TypeMismatch.new(path, original, PArrayType.new(actual.element_type)) else descriptions << SizeMismatch.new(path, expected_size, actual_size) end else descriptions << TypeMismatch.new(path, original, actual) end descriptions end def describe_PHashType(expected, original, actual, path) descriptions = [] key_type = expected.key_type || PAnyType::DEFAULT value_type = expected.value_type || PAnyType::DEFAULT if actual.is_a?(PStructType) elements = actual.elements expected_size = expected.size_type || PCollectionType::DEFAULT_SIZE actual_size = PIntegerType.new(elements.count { |a| !a.key_type.assignable?(PUndefType::DEFAULT) }, elements.size) if expected_size.assignable?(actual_size) elements.each do |a| descriptions.concat(describe(key_type, a.key_type, path + [EntryKeyPathElement.new(a.name)])) unless key_type.assignable?(a.key_type) descriptions.concat(describe(value_type, a.value_type, path + [EntryValuePathElement.new(a.name)])) unless value_type.assignable?(a.value_type) end else descriptions << SizeMismatch.new(path, expected_size, actual_size) end elsif actual.is_a?(PHashType) expected_size = expected.size_type actual_size = actual.size_type || PCollectionType::DEFAULT_SIZE if expected_size.nil? || expected_size.assignable?(actual_size) descriptions << TypeMismatch.new(path, original, PHashType.new(actual.key_type, actual.value_type)) else descriptions << SizeMismatch.new(path, expected_size, actual_size) end else descriptions << TypeMismatch.new(path, original, actual) end descriptions end def describe_PStructType(expected, original, actual, path) elements = expected.elements descriptions = [] if actual.is_a?(PStructType) h2 = actual.hashed_elements.clone elements.each do |e1| key = e1.name e2 = h2.delete(key) if e2.nil? descriptions << MissingKey.new(path, key) unless e1.key_type.assignable?(PUndefType::DEFAULT) else descriptions.concat(describe(e1.key_type, e2.key_type, path + [EntryKeyPathElement.new(key)])) unless e1.key_type.assignable?(e2.key_type) descriptions.concat(describe(e1.value_type, e2.value_type, path + [EntryValuePathElement.new(key)])) unless e1.value_type.assignable?(e2.value_type) end end h2.each_key { |key| descriptions << ExtraneousKey.new(path, key) } elsif actual.is_a?(PHashType) actual_size = actual.size_type || PCollectionType::DEFAULT_SIZE expected_size = PIntegerType.new(elements.count { |e| !e.key_type.assignable?(PUndefType::DEFAULT) }, elements.size) if expected_size.assignable?(actual_size) descriptions << TypeMismatch.new(path, original, PHashType.new(actual.key_type, actual.value_type)) else descriptions << SizeMismatch.new(path, expected_size, actual_size) end else descriptions << TypeMismatch.new(path, original, actual) end descriptions end def describe_PTupleType(expected, original, actual, path) describe_tuple(expected, original, actual, path, SizeMismatch) end def describe_argument_tuple(expected, actual, path) describe_tuple(expected, expected, actual, path, CountMismatch) end def describe_tuple(expected, original, actual, path, size_mismatch_class) return EMPTY_ARRAY if expected == actual || expected.types.empty? && (actual.is_a?(PArrayType)) expected_size = expected.size_type || TypeFactory.range(*expected.size_range) if actual.is_a?(PTupleType) actual_size = actual.size_type || TypeFactory.range(*actual.size_range) # not assignable if the number of types in actual is outside number of types in expected if expected_size.assignable?(actual_size) etypes = expected.types descriptions = [] unless etypes.empty? actual.types.each_with_index do |atype, index| adx = index >= etypes.size ? etypes.size - 1 : index descriptions.concat(describe(etypes[adx], atype, path + [ArrayPathElement.new(adx)])) end end descriptions else [size_mismatch_class.new(path, expected_size, actual_size)] end elsif actual.is_a?(PArrayType) t2_entry = actual.element_type if t2_entry.nil? # Array of anything can not be assigned (unless tuple is tuple of anything) - this case # was handled at the top of this method. # [TypeMismatch.new(path, original, actual)] else expected_size = expected.size_type || TypeFactory.range(*expected.size_range) actual_size = actual.size_type || PCollectionType::DEFAULT_SIZE if expected_size.assignable?(actual_size) descriptions = [] expected.types.each_with_index do |etype, index| descriptions.concat(describe(etype, actual.element_type, path + [ArrayPathElement.new(index)])) end descriptions else [size_mismatch_class.new(path, expected_size, actual_size)] end end else [TypeMismatch.new(path, original, actual)] end end def describe_PCallableType(expected, original, actual, path) if actual.is_a?(PCallableType) # nil param_types means, any other Callable is assignable if expected.param_types.nil? && expected.return_type.nil? EMPTY_ARRAY else # NOTE: these tests are made in reverse as it is calling the callable that is constrained # (it's lower bound), not its upper bound param_errors = describe_argument_tuple(expected.param_types, actual.param_types, path) if param_errors.empty? this_return_t = expected.return_type || PAnyType::DEFAULT that_return_t = actual.return_type || PAnyType::DEFAULT unless this_return_t.assignable?(that_return_t) [TypeMismatch.new(path + [ReturnTypeElement.new], this_return_t, that_return_t)] else # names are ignored, they are just information # Blocks must be compatible this_block_t = expected.block_type || PUndefType::DEFAULT that_block_t = actual.block_type || PUndefType::DEFAULT if that_block_t.assignable?(this_block_t) EMPTY_ARRAY else [TypeMismatch.new(path + [BlockPathElement.new], this_block_t, that_block_t)] end end else param_errors end end else [TypeMismatch.new(path, original, actual)] end end def describe_PAnyType(expected, original, actual, path) expected.assignable?(actual) ? EMPTY_ARRAY : [TypeMismatch.new(path, original, actual)] end class UnresolvedTypeFinder include TypeAcceptor attr_reader :unresolved def initialize @unresolved = nil end def visit(type, guard) if @unresolved.nil? && type.is_a?(PTypeReferenceType) @unresolved = type.type_string end end end def describe(expected, actual, path) ures_finder = UnresolvedTypeFinder.new expected.accept(ures_finder, nil) unresolved = ures_finder.unresolved if unresolved [UnresolvedTypeReference.new(path, unresolved)] else internal_describe(expected.normalize, expected, actual, path) end end def internal_describe(expected, original, actual, path) case expected when PVariantType describe_PVariantType(expected, original, actual, path) when PStructType describe_PStructType(expected, original, actual, path) when PHashType describe_PHashType(expected, original, actual, path) when PTupleType describe_PTupleType(expected, original, actual, path) when PArrayType describe_PArrayType(expected, original, actual, path) when PCallableType describe_PCallableType(expected, original, actual, path) when POptionalType describe_POptionalType(expected, original, actual, path) when PPatternType describe_PPatternType(expected, original, actual, path) when PEnumType describe_PEnumType(expected, original, actual, path) when PTypeAliasType describe_PTypeAliasType(expected, original, actual, path) else describe_PAnyType(expected, original, actual, path) end end private :internal_describe # Produces a string for the signature(s) # # @api private def signature_string(signature) param_types = signature.type.param_types param_names = signature.parameter_names from, to = param_types.size_range if from == 0 && to == 0 # No parameters function return '' end required_count = from types = case param_types when PTupleType param_types.types when PArrayType [param_types.element_type] end # join type with names (types are always present, names are optional) # separate entries with comma # param_names = Array.new(types.size, '') if param_names.empty? limit = param_names.size result = param_names.each_with_index.map do |name, index| type = types[index] || types[-1] indicator = '' if to == Float::INFINITY && index == limit - 1 # Last is a repeated_param. indicator = from == param_names.size ? '+' : '*' elsif optional(index, required_count) indicator = '?' type = type.optional_type if type.is_a?(POptionalType) end "#{type} #{name}#{indicator}" end.join(', ') # If there is a block, include it case signature.type.block_type when POptionalType result << ', ' unless result == '' result << "#{signature.type.block_type.optional_type} #{signature.block_name}?" when PCallableType result << ', ' unless result == '' result << "#{signature.type.block_type} #{signature.block_name}" when NilClass # nothing end result end # Why oh why Ruby do you not have a standard Math.max ? # @api private def max(a, b) a >= b ? a : b end # @api private def optional(index, required_count) count = index + 1 count > required_count end end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/type_parser.rb��������������������������������������������������0000644�0052762�0001160�00000055310�13417161721�022110� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This class provides parsing of Type Specification from a string into the Type # Model that is produced by the TypeFactory. # # The Type Specifications that are parsed are the same as the stringified forms # of types produced by the {TypeCalculator TypeCalculator}. # # @api public module Puppet::Pops module Types class TypeParser def self.singleton @singleton ||= TypeParser.new end # @api public def initialize @parser = Parser::Parser.new @type_transformer = Visitor.new(nil, 'interpret', 1, 1) end # Produces a *puppet type* based on the given string. # # @example # parser.parse('Integer') # parser.parse('Array[String]') # parser.parse('Hash[Integer, Array[String]]') # # @param string [String] a string with the type expressed in stringified form as produced by the # types {"#to_s} method. # @param context [Loader::Loader] optional loader used as no adapted loader is found # @return [PAnyType] a specialization of the PAnyType representing the type. # # @api public # def parse(string, context = nil) # quick "peephole" optimization of common data types if t = self.class.opt_type_map[string] return t end model = @parser.parse_string(string) interpret(model.model.body, context) end # @api private def parse_literal(string, context = nil) factory = @parser.parse_string(string) interpret_any(factory.model.body, context) end # @param ast [Puppet::Pops::Model::PopsObject] the ast to interpret # @param context [Loader::Loader] optional loader used when no adapted loader is found # @return [PAnyType] a specialization of the PAnyType representing the type. # # @api public def interpret(ast, context = nil) result = @type_transformer.visit_this_1(self, ast, context) raise_invalid_type_specification_error(ast) unless result.is_a?(PAnyType) result end # @api private def interpret_any(ast, context) @type_transformer.visit_this_1(self, ast, context) end # @api private def interpret_Object(o, context) raise_invalid_type_specification_error(o) end # @api private def interpret_Program(o, context) interpret_any(o.body, context) end # @api private def interpret_TypeAlias(o, context) Loader::TypeDefinitionInstantiator.create_type(o.name, o.type_expr, Pcore::RUNTIME_NAME_AUTHORITY).resolve(loader_from_context(o, context)) end # @api private def interpret_TypeDefinition(o, context) Loader::TypeDefinitionInstantiator.create_runtime_type(o) end # @api private def interpret_LambdaExpression(o, context) o end # @api private def interpret_HeredocExpression(o, context) interpret_any(o.text_expr, context) end def interpret_SubLocatedExpression(o, context) interpret_any(o.expr, context) end # @api private def interpret_QualifiedName(o, context) o.value end # @api private def interpret_LiteralBoolean(o, context) o.value end # @api private def interpret_LiteralDefault(o, context) :default end # @api private def interpret_LiteralFloat(o, context) o.value end # @api private def interpret_LiteralHash(o, context) result = {} o.entries.each do |entry| result[@type_transformer.visit_this_1(self, entry.key, context)] = @type_transformer.visit_this_1(self, entry.value, context) end result end # @api private def interpret_LiteralInteger(o, context) o.value end # @api private def interpret_LiteralList(o, context) o.values.map { |value| @type_transformer.visit_this_1(self, value, context) } end # @api private def interpret_LiteralRegularExpression(o, context) o.value end # @api private def interpret_LiteralString(o, context) o.value end # @api private def interpret_LiteralUndef(o, context) nil end # @api private def interpret_String(o, context) o end # @api private def interpret_UnaryMinusExpression(o, context) -@type_transformer.visit_this_1(self, o.expr, context) end # @api private def self.type_map @type_map ||= { 'integer' => TypeFactory.integer, 'float' => TypeFactory.float, 'numeric' => TypeFactory.numeric, 'init' => TypeFactory.init, 'iterable' => TypeFactory.iterable, 'iterator' => TypeFactory.iterator, 'string' => TypeFactory.string, 'binary' => TypeFactory.binary, 'sensitive' => TypeFactory.sensitive, 'enum' => TypeFactory.enum, 'boolean' => TypeFactory.boolean, 'pattern' => TypeFactory.pattern, 'regexp' => TypeFactory.regexp, 'array' => TypeFactory.array_of_any, 'hash' => TypeFactory.hash_of_any, 'class' => TypeFactory.host_class, 'resource' => TypeFactory.resource, 'collection' => TypeFactory.collection, 'scalar' => TypeFactory.scalar, 'scalardata' => TypeFactory.scalar_data, 'catalogentry' => TypeFactory.catalog_entry, 'undef' => TypeFactory.undef, 'notundef' => TypeFactory.not_undef, 'default' => TypeFactory.default, 'any' => TypeFactory.any, 'variant' => TypeFactory.variant, 'optional' => TypeFactory.optional, 'runtime' => TypeFactory.runtime, 'type' => TypeFactory.type_type, 'tuple' => TypeFactory.tuple, 'struct' => TypeFactory.struct, 'object' => TypeFactory.object, 'typealias' => TypeFactory.type_alias, 'typereference' => TypeFactory.type_reference, 'typeset' => TypeFactory.type_set, # A generic callable as opposed to one that does not accept arguments 'callable' => TypeFactory.all_callables, 'semver' => TypeFactory.sem_ver, 'semverrange' => TypeFactory.sem_ver_range, 'timestamp' => TypeFactory.timestamp, 'timespan' => TypeFactory.timespan, 'uri' => TypeFactory.uri, }.freeze end # @api private def self.opt_type_map # Map of common (and simple to optimize) data types in string form # (Note that some types are the result of evaluation even if they appear to be simple # - for example 'Data' and they cannot be optimized this way since the factory calls # back to the parser for evaluation). # @opt_type_map ||= { 'Integer' => TypeFactory.integer, 'Float' => TypeFactory.float, 'Numeric' => TypeFactory.numeric, 'String' => TypeFactory.string, 'String[1]' => TypeFactory.string(TypeFactory.range(1, :default)), 'Binary' => TypeFactory.binary, 'Boolean' => TypeFactory.boolean, 'Boolean[true]' => TypeFactory.boolean(true), 'Boolean[false]' => TypeFactory.boolean(false), 'Array' => TypeFactory.array_of_any, 'Array[1]' => TypeFactory.array_of(TypeFactory.any, TypeFactory.range(1, :default)), 'Hash' => TypeFactory.hash_of_any, 'Collection' => TypeFactory.collection, 'Scalar' => TypeFactory.scalar, 'Scalardata' => TypeFactory.scalar_data, 'ScalarData' => TypeFactory.scalar_data, 'Catalogentry' => TypeFactory.catalog_entry, 'CatalogEntry' => TypeFactory.catalog_entry, 'Undef' => TypeFactory.undef, 'Default' => TypeFactory.default, 'Any' => TypeFactory.any, 'Type' => TypeFactory.type_type, 'Callable' => TypeFactory.all_callables, 'Semver' => TypeFactory.sem_ver, 'SemVer' => TypeFactory.sem_ver, 'Semverrange' => TypeFactory.sem_ver_range, 'SemVerRange' => TypeFactory.sem_ver_range, 'Timestamp' => TypeFactory.timestamp, 'TimeStamp' => TypeFactory.timestamp, 'Timespan' => TypeFactory.timespan, 'TimeSpan' => TypeFactory.timespan, 'Uri' => TypeFactory.uri, 'URI' => TypeFactory.uri, 'Optional[Integer]' => TypeFactory.optional(TypeFactory.integer), 'Optional[String]' => TypeFactory.optional(TypeFactory.string), 'Optional[String[1]]' => TypeFactory.optional(TypeFactory.string(TypeFactory.range(1, :default))), 'Optional[Array]' => TypeFactory.optional(TypeFactory.array_of_any), 'Optional[Hash]' => TypeFactory.optional(TypeFactory.hash_of_any), }.freeze end # @api private def interpret_QualifiedReference(name_ast, context) name = name_ast.value if found = self.class.type_map[name] found else loader = loader_from_context(name_ast, context) unless loader.nil? type = loader.load(:type, name) type = type.resolve(loader) unless type.nil? end type || TypeFactory.type_reference(name_ast.cased_value) end end # @api private def loader_from_context(ast, context) model_loader = Adapters::LoaderAdapter.loader_for_model_object(ast, nil, context) if context.is_a?(PTypeSetType::TypeSetLoader) # Only swap a given TypeSetLoader for another loader when the other loader is different # from the one associated with the TypeSet expression context.model_loader.equal?(model_loader.parent) ? context : model_loader else model_loader end end # @api private def interpret_AccessExpression(ast, context) parameters = ast.keys.collect { |param| interpret_any(param, context) } qref = ast.left_expr raise_invalid_type_specification_error(ast) unless qref.is_a?(Model::QualifiedReference) type_name = qref.value case type_name when 'array' case parameters.size when 1 type = assert_type(ast, parameters[0]) when 2 if parameters[0].is_a?(PAnyType) type = parameters[0] size_type = if parameters[1].is_a?(PIntegerType) size_type = parameters[1] else assert_range_parameter(ast, parameters[1]) TypeFactory.range(parameters[1], :default) end else type = :default assert_range_parameter(ast, parameters[0]) assert_range_parameter(ast, parameters[1]) size_type = TypeFactory.range(parameters[0], parameters[1]) end when 3 type = assert_type(ast, parameters[0]) assert_range_parameter(ast, parameters[1]) assert_range_parameter(ast, parameters[2]) size_type = TypeFactory.range(parameters[1], parameters[2]) else raise_invalid_parameters_error('Array', '1 to 3', parameters.size) end TypeFactory.array_of(type, size_type) when 'hash' case parameters.size when 2 if parameters[0].is_a?(PAnyType) && parameters[1].is_a?(PAnyType) TypeFactory.hash_of(parameters[1], parameters[0]) else assert_range_parameter(ast, parameters[0]) assert_range_parameter(ast, parameters[1]) TypeFactory.hash_of(:default, :default, TypeFactory.range(parameters[0], parameters[1])) end when 3 size_type = if parameters[2].is_a?(PIntegerType) parameters[2] else assert_range_parameter(ast, parameters[2]) TypeFactory.range(parameters[2], :default) end assert_type(ast, parameters[0]) assert_type(ast, parameters[1]) TypeFactory.hash_of(parameters[1], parameters[0], size_type) when 4 assert_range_parameter(ast, parameters[2]) assert_range_parameter(ast, parameters[3]) assert_type(ast, parameters[0]) assert_type(ast, parameters[1]) TypeFactory.hash_of(parameters[1], parameters[0], TypeFactory.range(parameters[2], parameters[3])) else raise_invalid_parameters_error('Hash', '2 to 4', parameters.size) end when 'collection' size_type = case parameters.size when 1 if parameters[0].is_a?(PIntegerType) parameters[0] else assert_range_parameter(ast, parameters[0]) TypeFactory.range(parameters[0], :default) end when 2 assert_range_parameter(ast, parameters[0]) assert_range_parameter(ast, parameters[1]) TypeFactory.range(parameters[0], parameters[1]) else raise_invalid_parameters_error('Collection', '1 to 2', parameters.size) end TypeFactory.collection(size_type) when 'class' if parameters.size != 1 raise_invalid_parameters_error('Class', 1, parameters.size) end TypeFactory.host_class(parameters[0]) when 'resource' type = parameters[0] if type.is_a?(PTypeReferenceType) type_str = type.type_string param_start = type_str.index('[') if param_start.nil? type = type_str else tps = interpret_any(@parser.parse_string(type_str[param_start..-1]).model, context) raise_invalid_parameters_error(type.to_s, '1', tps.size) unless tps.size == 1 type = type_str[0..param_start-1] parameters = [type] + tps end end create_resource(type, parameters) when 'regexp' # 1 parameter being a string, or regular expression raise_invalid_parameters_error('Regexp', '1', parameters.size) unless parameters.size == 1 TypeFactory.regexp(parameters[0]) when 'enum' # 1..m parameters being string last = parameters.last case_insensitive = false if last == true || last == false parameters = parameters[0...-1] case_insensitive = last end raise_invalid_parameters_error('Enum', '1 or more', parameters.size) unless parameters.size >= 1 parameters.each { |p| raise Puppet::ParseError, _('Enum parameters must be identifiers or strings') unless p.is_a?(String) } PEnumType.new(parameters, case_insensitive) when 'pattern' # 1..m parameters being strings or regular expressions raise_invalid_parameters_error('Pattern', '1 or more', parameters.size) unless parameters.size >= 1 TypeFactory.pattern(*parameters) when 'uri' # 1 parameter which is a string or a URI raise_invalid_parameters_error('URI', '1', parameters.size) unless parameters.size == 1 TypeFactory.uri(parameters[0]) when 'variant' # 1..m parameters being strings or regular expressions raise_invalid_parameters_error('Variant', '1 or more', parameters.size) unless parameters.size >= 1 parameters.each { |p| assert_type(ast, p) } TypeFactory.variant(*parameters) when 'tuple' # 1..m parameters being types (last two optionally integer or literal default raise_invalid_parameters_error('Tuple', '1 or more', parameters.size) unless parameters.size >= 1 length = parameters.size size_type = nil if TypeFactory.is_range_parameter?(parameters[-2]) # min, max specification min = parameters[-2] min = (min == :default || min == 'default') ? 0 : min assert_range_parameter(ast, parameters[-1]) max = parameters[-1] max = max == :default ? nil : max parameters = parameters[0, length-2] size_type = TypeFactory.range(min, max) elsif TypeFactory.is_range_parameter?(parameters[-1]) min = parameters[-1] min = (min == :default || min == 'default') ? 0 : min max = nil parameters = parameters[0, length-1] size_type = TypeFactory.range(min, max) end TypeFactory.tuple(parameters, size_type) when 'callable' # 1..m parameters being types (last three optionally integer or literal default, and a callable) if parameters.size > 1 && parameters[0].is_a?(Array) raise_invalid_parameters_error('callable', '2 when first parameter is an array', parameters.size) unless parameters.size == 2 end TypeFactory.callable(*parameters) when 'struct' # 1..m parameters being types (last two optionally integer or literal default raise_invalid_parameters_error('Struct', '1', parameters.size) unless parameters.size == 1 h = parameters[0] raise_invalid_type_specification_error(ast) unless h.is_a?(Hash) TypeFactory.struct(h) when 'boolean' raise_invalid_parameters_error('Boolean', '1', parameters.size) unless parameters.size == 1 p = parameters[0] raise Puppet::ParseError, 'Boolean parameter must be true or false' unless p == true || p == false TypeFactory.boolean(p) when 'integer' if parameters.size == 1 case parameters[0] when Integer TypeFactory.range(parameters[0], :default) when :default TypeFactory.integer # unbound end elsif parameters.size != 2 raise_invalid_parameters_error('Integer', '1 or 2', parameters.size) else TypeFactory.range(parameters[0] == :default ? nil : parameters[0], parameters[1] == :default ? nil : parameters[1]) end when 'object' raise_invalid_parameters_error('Object', 1, parameters.size) unless parameters.size == 1 TypeFactory.object(parameters[0]) when 'typeset' raise_invalid_parameters_error('Object', 1, parameters.size) unless parameters.size == 1 TypeFactory.type_set(parameters[0]) when 'init' assert_type(ast, parameters[0]) TypeFactory.init(*parameters) when 'iterable' if parameters.size != 1 raise_invalid_parameters_error('Iterable', 1, parameters.size) end assert_type(ast, parameters[0]) TypeFactory.iterable(parameters[0]) when 'iterator' if parameters.size != 1 raise_invalid_parameters_error('Iterator', 1, parameters.size) end assert_type(ast, parameters[0]) TypeFactory.iterator(parameters[0]) when 'float' if parameters.size == 1 case parameters[0] when Integer, Float TypeFactory.float_range(parameters[0], :default) when :default TypeFactory.float # unbound end elsif parameters.size != 2 raise_invalid_parameters_error('Float', '1 or 2', parameters.size) else TypeFactory.float_range(parameters[0] == :default ? nil : parameters[0], parameters[1] == :default ? nil : parameters[1]) end when 'string' size_type = case parameters.size when 1 if parameters[0].is_a?(PIntegerType) parameters[0] else assert_range_parameter(ast, parameters[0]) TypeFactory.range(parameters[0], :default) end when 2 assert_range_parameter(ast, parameters[0]) assert_range_parameter(ast, parameters[1]) TypeFactory.range(parameters[0], parameters[1]) else raise_invalid_parameters_error('String', '1 to 2', parameters.size) end TypeFactory.string(size_type) when 'sensitive' if parameters.size == 0 TypeFactory.sensitive elsif parameters.size == 1 param = parameters[0] assert_type(ast, param) TypeFactory.sensitive(param) else raise_invalid_parameters_error('Sensitive', '0 to 1', parameters.size) end when 'optional' if parameters.size != 1 raise_invalid_parameters_error('Optional', 1, parameters.size) end param = parameters[0] assert_type(ast, param) unless param.is_a?(String) TypeFactory.optional(param) when 'any', 'data', 'catalogentry', 'scalar', 'undef', 'numeric', 'default', 'semverrange' raise_unparameterized_type_error(qref) when 'notundef' case parameters.size when 0 TypeFactory.not_undef when 1 param = parameters[0] assert_type(ast, param) unless param.is_a?(String) TypeFactory.not_undef(param) else raise_invalid_parameters_error("NotUndef", "0 to 1", parameters.size) end when 'type' if parameters.size != 1 raise_invalid_parameters_error('Type', 1, parameters.size) end assert_type(ast, parameters[0]) TypeFactory.type_type(parameters[0]) when 'runtime' raise_invalid_parameters_error('Runtime', '2', parameters.size) unless parameters.size == 2 TypeFactory.runtime(*parameters) when 'timespan' raise_invalid_parameters_error('Timespan', '0 to 2', parameters.size) unless parameters.size <= 2 TypeFactory.timespan(*parameters) when 'timestamp' raise_invalid_parameters_error('Timestamp', '0 to 2', parameters.size) unless parameters.size <= 2 TypeFactory.timestamp(*parameters) when 'semver' raise_invalid_parameters_error('SemVer', '1 or more', parameters.size) unless parameters.size >= 1 TypeFactory.sem_ver(*parameters) else loader = loader_from_context(qref, context) type = nil unless loader.nil? type = loader.load(:type, type_name) type = type.resolve(loader) unless type.nil? end if type.nil? TypeFactory.type_reference(original_text_of(ast)) elsif type.is_a?(PResourceType) raise_invalid_parameters_error(qref.cased_value, 1, parameters.size) unless parameters.size == 1 TypeFactory.resource(type.type_name, parameters[0]) elsif type.is_a?(PObjectType) PObjectTypeExtension.create(type, parameters) else # Must be a type alias. They can't use parameters (yet) raise_unparameterized_type_error(qref) end end end private def create_resource(name, parameters) if parameters.size == 1 TypeFactory.resource(name) elsif parameters.size == 2 TypeFactory.resource(name, parameters[1]) else raise_invalid_parameters_error('Resource', '1 or 2', parameters.size) end end def assert_type(ast, t) raise_invalid_type_specification_error(ast) unless t.is_a?(PAnyType) t end def assert_range_parameter(ast, t) raise_invalid_type_specification_error(ast) unless TypeFactory.is_range_parameter?(t) end def raise_invalid_type_specification_error(ast) raise Puppet::ParseError, _("The expression <%{expression}> is not a valid type specification.") % { expression: original_text_of(ast) } end def raise_invalid_parameters_error(type, required, given) raise Puppet::ParseError, _("Invalid number of type parameters specified: %{type} requires %{required}, %{given} provided") % { type: type, required: required, given: given } end def raise_unparameterized_type_error(ast) raise Puppet::ParseError, _("Not a parameterized type <%{type}>") % { type: original_text_of(ast) } end def raise_unknown_type_error(ast) raise Puppet::ParseError, _("Unknown type <%{type}>") % { type: original_text_of(ast) } end def original_text_of(ast) ast.locator.extract_tree_text(ast) end end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/type_set_reference.rb�������������������������������������������0000644�0052762�0001160�00000003265�13417161721�023427� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Types class TypeSetReference include Annotatable attr_reader :name_authority attr_reader :name attr_reader :version_range attr_reader :type_set def initialize(owner, init_hash) @owner = owner @name_authority = (init_hash[KEY_NAME_AUTHORITY] || owner.name_authority).freeze @name = init_hash[KEY_NAME].freeze @version_range = PSemVerRangeType.convert(init_hash[KEY_VERSION_RANGE]) init_annotatable(init_hash) end def accept(visitor, guard) annotatable_accept(visitor, guard) end def eql?(o) self.class == o.class && @name_authority.eql?(o.name_authority) && @name.eql?(o.name) && @version_range.eql?(o.version_range) end def hash [@name_authority, @name, @version_range].hash end def _pcore_init_hash result = super result[KEY_NAME_AUTHORITY] = @name_authority unless @name_authority == @owner.name_authority result[KEY_NAME] = @name result[KEY_VERSION_RANGE] = @version_range.to_s result end def resolve(loader) typed_name = Loader::TypedName.new(:type, @name, @name_authority) loaded_entry = loader.load_typed(typed_name) type_set = loaded_entry.nil? ? nil : loaded_entry.value raise ArgumentError, "#{self} cannot be resolved" if type_set.nil? raise ArgumentError, "#{self} resolves to a #{type_set.name}" unless type_set.is_a?(PTypeSetType) @type_set = type_set.resolve(loader) unless @version_range.include?(@type_set.version) raise ArgumentError, "#{self} resolves to an incompatible version. Expected #{@version_range}, got #{type_set.version}" end nil end def to_s "#{@owner.label} reference to TypeSet named '#{@name}'" end end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/type_with_members.rb��������������������������������������������0000644�0052762�0001160�00000002327�13417161721�023301� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Types # Interface implemented by a type that has InvocableMembers module TypeWithMembers # @return [InvocableMember,nil] An invocable member if it exists, or `nil` def [](member_name) raise NotImplementedError, "'#{self.class.name}' should implement #[]" end end # Interface implemented by attribute and function members module InvocableMember # Performs type checking of arguments and invokes the method that corresponds to this # method. The result of the invocation is returned # # @param receiver [Object] The receiver of the call # @param scope [Puppet::Parser::Scope] The caller scope # @param args [Array] Array of arguments. # @return [Object] The result returned by the member function or attribute # # @api private def invoke(receiver, scope, args, &block) raise NotImplementedError, "'#{self.class.name}' should implement #invoke" end end # Plays the same role as an PAttribute in the PObjectType. Provides # access to known attr_readers and plain reader methods. class AttrReader include InvocableMember def initialize(message) @message = message.to_sym end def invoke(receiver, scope, args, &block) receiver.send(@message) end end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/p_binary_type.rb������������������������������������������������0000644�0052762�0001160�00000017104�13417161721�022416� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'base64' module Puppet::Pops module Types # A Puppet Language Type that represents binary data content (a sequence of 8-bit bytes). # Instances of this data type can be created from `String` and `Array[Integer[0,255]]` # values. Also see the `binary_file` function for reading binary content from a file. # # A `Binary` can be converted to `String` and `Array` form - see function `new` for # the respective target data type for more information. # # Instances of this data type serialize as base 64 encoded strings when the serialization # format is textual, and as binary content when a serialization format supports this. # # @api public class PBinaryType < PAnyType # Represents a binary buffer # @api public class Binary attr_reader :binary_buffer # Constructs an instance of Binary from a base64 urlsafe encoded string (RFC 2045). # @param [String] A string with RFC 2045 compliant encoded binary # def self.from_base64(str) new(Base64.decode64(str)) end # Constructs an instance of Binary from a base64 encoded string (RFC4648 with "URL and Filename # Safe Alphabet" (That is with '-' instead of '+', and '_' instead of '/'). # def self.from_base64_urlsafe(str) new(Base64.urlsafe_decode64(str)) end # Constructs an instance of Binary from a base64 strict encoded string (RFC 4648) # Where correct padding must be used and line breaks causes an error to be raised. # # @param [String] A string with RFC 4648 compliant encoded binary # def self.from_base64_strict(str) new(Base64.strict_decode64(str)) end # Creates a new Binary from a String containing binary data. If the string's encoding # is not already ASCII-8BIT, a copy of the string is force encoded as ASCII-8BIT (that is Ruby's "binary" format). # This means that this binary will have the exact same content, but the string will considered # to hold a sequence of bytes in the range 0 to 255. # # The given string will be frozen as a side effect if it is in ASCII-8BIT encoding. If this is not # wanted, a copy should be given to this method. # # @param [String] A string with binary data # @api public # def self.from_binary_string(bin) new(bin) end # Creates a new Binary from a String containing text/binary in its given encoding. If the string's encoding # is not already UTF-8, the string is first transcoded to UTF-8. # This means that this binary will have the UTF-8 byte representation of the original string. # For this to be valid, the encoding used in the given string must be valid. # The validity of the given string is therefore asserted. # # The given string will be frozen as a side effect if it is in ASCII-8BIT encoding. If this is not # wanted, a copy should be given to this method. # # @param [String] A string with valid content in its given encoding # @return [Puppet::Pops::Types::PBinaryType::Binary] with the UTF-8 representation of the UTF-8 transcoded string # @api public # def self.from_string(encoded_string) enc = encoded_string.encoding.name unless encoded_string.valid_encoding? raise ArgumentError, _("The given string in encoding '%{enc}' is invalid. Cannot create a Binary UTF-8 representation") % { enc: enc } end # Convert to UTF-8 (if not already UTF-8), and then to binary encoded_string = (enc == "UTF-8") ? encoded_string.dup : encoded_string.encode('UTF-8') encoded_string.force_encoding("ASCII-8BIT") new(encoded_string) end # Creates a new Binary from a String containing raw binary data of unknown encoding. If the string's encoding # is not already ASCII-8BIT, a copy of the string is forced to ASCII-8BIT (that is Ruby's "binary" format). # This means that this binary will have the exact same content, but the string will considered # to hold a sequence of bytes in the range 0 to 255. # # @param [String] A string with binary data # @api private # def initialize(bin) # TODO: When Ruby 1.9.3 support is dropped change this to `bin.b` for binary encoding instead of force_encoding @binary_buffer = (bin.encoding.name == "ASCII-8BIT" ? bin : bin.dup.force_encoding("ASCII-8BIT")).freeze end # Presents the binary content as a string base64 encoded string (without line breaks). # def to_s Base64.strict_encode64(@binary_buffer) end # Returns the binary content as a "relaxed" base64 (standard) encoding where # the string is broken up with new lines. def relaxed_to_s Base64.encode64(@binary_buffer) end # Returns the binary content as a url safe base64 string (where + and / are replaced by - and _) # def urlsafe_to_s Base64.urlsafe_encode64(@binary_buffer) end def hash @binary_buffer.hash end def eql?(o) self.class == o.class && @binary_buffer == o.binary_buffer end def ==(o) self.eql?(o) end def length() @binary_buffer.length end end def self.register_ptype(loader, ir) create_ptype(loader, ir, 'AnyType') end # Only instances of Binary are instances of the PBinaryType # def instance?(o, guard = nil) o.is_a?(Binary) end def eql?(o) self.class == o.class end # Binary uses the strict base64 format as its string representation # @return [TrueClass] true def roundtrip_with_string? true end # @api private def self.new_function(type) @new_function ||= Puppet::Functions.create_loaded_function(:new_Binary, type.loader) do local_types do type 'ByteInteger = Integer[0,255]' type 'Base64Format = Enum["%b", "%u", "%B", "%s", "%r"]' type 'StringHash = Struct[{value => String, "format" => Optional[Base64Format]}]' type 'ArrayHash = Struct[{value => Array[ByteInteger]}]' type 'BinaryArgsHash = Variant[StringHash, ArrayHash]' end # Creates a binary from a base64 encoded string in one of the formats %b, %u, %B, %s, or %r dispatch :from_string do param 'String', :str optional_param 'Base64Format', :format end dispatch :from_array do param 'Array[ByteInteger]', :byte_array end # Same as from_string, or from_array, but value and (for string) optional format are given in the form # of a hash. # dispatch :from_hash do param 'BinaryArgsHash', :hash_args end def from_string(str, format = nil) format ||= '%B' case format when "%b" # padding must be added for older rubies to avoid truncation padding = '=' * (str.length % 3) Binary.new(Base64.decode64(str + padding)) when "%u" Binary.new(Base64.urlsafe_decode64(str)) when "%B" Binary.new(Base64.strict_decode64(str)) when "%s" Binary.from_string(str) when "%r" Binary.from_binary_string(str) else raise ArgumentError, "Unsupported Base64 format '#{format}'" end end def from_array(array) # The array is already known to have bytes in the range 0-255, or it is in error # Without this pack C would produce weird results Binary.from_binary_string(array.pack("C*")) end def from_hash(hash) case hash['value'] when Array from_array(hash['value']) when String from_string(hash['value'], hash['format']) end end end end DEFAULT = PBinaryType.new protected def _assignable?(o, guard) o.class == self.class end end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/p_object_type.rb������������������������������������������������0000644�0052762�0001160�00000117040�13417161721�022400� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require_relative 'ruby_generator' require_relative 'type_with_members' module Puppet::Pops module Types KEY_ATTRIBUTES = 'attributes'.freeze KEY_CHECKS = 'checks'.freeze KEY_CONSTANTS = 'constants'.freeze KEY_EQUALITY = 'equality'.freeze KEY_EQUALITY_INCLUDE_TYPE = 'equality_include_type'.freeze KEY_FINAL = 'final'.freeze KEY_FUNCTIONS = 'functions'.freeze KEY_KIND = 'kind'.freeze KEY_OVERRIDE = 'override'.freeze KEY_PARENT = 'parent'.freeze KEY_TYPE_PARAMETERS = 'type_parameters'.freeze # @api public class PObjectType < PMetaType include TypeWithMembers ATTRIBUTE_KIND_CONSTANT = 'constant'.freeze ATTRIBUTE_KIND_DERIVED = 'derived'.freeze ATTRIBUTE_KIND_GIVEN_OR_DERIVED = 'given_or_derived'.freeze ATTRIBUTE_KIND_REFERENCE = 'reference'.freeze TYPE_ATTRIBUTE_KIND = TypeFactory.enum(ATTRIBUTE_KIND_CONSTANT, ATTRIBUTE_KIND_DERIVED, ATTRIBUTE_KIND_GIVEN_OR_DERIVED, ATTRIBUTE_KIND_REFERENCE) TYPE_OBJECT_NAME = Pcore::TYPE_QUALIFIED_REFERENCE TYPE_ATTRIBUTE = TypeFactory.struct({ KEY_TYPE => PTypeType::DEFAULT, TypeFactory.optional(KEY_FINAL) => PBooleanType::DEFAULT, TypeFactory.optional(KEY_OVERRIDE) => PBooleanType::DEFAULT, TypeFactory.optional(KEY_KIND) => TYPE_ATTRIBUTE_KIND, KEY_VALUE => PAnyType::DEFAULT, TypeFactory.optional(KEY_ANNOTATIONS) => TYPE_ANNOTATIONS }) TYPE_PARAMETER = TypeFactory.struct({ KEY_TYPE => PTypeType::DEFAULT, TypeFactory.optional(KEY_ANNOTATIONS) => TYPE_ANNOTATIONS }) TYPE_CONSTANTS = TypeFactory.hash_kv(Pcore::TYPE_MEMBER_NAME, PAnyType::DEFAULT) TYPE_ATTRIBUTES = TypeFactory.hash_kv(Pcore::TYPE_MEMBER_NAME, TypeFactory.not_undef) TYPE_PARAMETERS = TypeFactory.hash_kv(Pcore::TYPE_MEMBER_NAME, TypeFactory.not_undef) TYPE_ATTRIBUTE_CALLABLE = TypeFactory.callable(0,0) TYPE_FUNCTION_TYPE = PTypeType.new(PCallableType::DEFAULT) TYPE_FUNCTION = TypeFactory.struct({ KEY_TYPE => TYPE_FUNCTION_TYPE, TypeFactory.optional(KEY_FINAL) => PBooleanType::DEFAULT, TypeFactory.optional(KEY_OVERRIDE) => PBooleanType::DEFAULT, TypeFactory.optional(KEY_ANNOTATIONS) => TYPE_ANNOTATIONS }) TYPE_FUNCTIONS = TypeFactory.hash_kv(PVariantType.new([Pcore::TYPE_MEMBER_NAME, PStringType.new('[]')]), TypeFactory.not_undef) TYPE_EQUALITY = TypeFactory.variant(Pcore::TYPE_MEMBER_NAME, TypeFactory.array_of(Pcore::TYPE_MEMBER_NAME)) TYPE_CHECKS = PAnyType::DEFAULT # TBD TYPE_OBJECT_I12N = TypeFactory.struct({ TypeFactory.optional(KEY_NAME) => TYPE_OBJECT_NAME, TypeFactory.optional(KEY_PARENT) => PTypeType::DEFAULT, TypeFactory.optional(KEY_TYPE_PARAMETERS) => TYPE_PARAMETERS, TypeFactory.optional(KEY_ATTRIBUTES) => TYPE_ATTRIBUTES, TypeFactory.optional(KEY_CONSTANTS) => TYPE_CONSTANTS, TypeFactory.optional(KEY_FUNCTIONS) => TYPE_FUNCTIONS, TypeFactory.optional(KEY_EQUALITY) => TYPE_EQUALITY, TypeFactory.optional(KEY_EQUALITY_INCLUDE_TYPE) => PBooleanType::DEFAULT, TypeFactory.optional(KEY_CHECKS) => TYPE_CHECKS, TypeFactory.optional(KEY_ANNOTATIONS) => TYPE_ANNOTATIONS }) def self.register_ptype(loader, ir) type = create_ptype(loader, ir, 'AnyType', '_pcore_init_hash' => TYPE_OBJECT_I12N) # Now, when the Object type exists, add annotations with keys derived from Annotation and freeze the types. annotations = TypeFactory.optional(PHashType.new(PTypeType.new(Annotation._pcore_type), TypeFactory.hash_kv(Pcore::TYPE_MEMBER_NAME, PAnyType::DEFAULT))) TYPE_ATTRIBUTE.hashed_elements[KEY_ANNOTATIONS].replace_value_type(annotations) TYPE_FUNCTION.hashed_elements[KEY_ANNOTATIONS].replace_value_type(annotations) TYPE_OBJECT_I12N.hashed_elements[KEY_ANNOTATIONS].replace_value_type(annotations) PTypeSetType::TYPE_TYPESET_I12N.hashed_elements[KEY_ANNOTATIONS].replace_value_type(annotations) PTypeSetType::TYPE_TYPE_REFERENCE_I12N.hashed_elements[KEY_ANNOTATIONS].replace_value_type(annotations) type end # @abstract Encapsulates behavior common to {PAttribute} and {PFunction} # @api public class PAnnotatedMember include Annotatable include InvocableMember # @return [PObjectType] the object type containing this member # @api public attr_reader :container # @return [String] the name of this member # @api public attr_reader :name # @return [PAnyType] the type of this member # @api public attr_reader :type # @param name [String] The name of the member # @param container [PObjectType] The containing object type # @param init_hash [Hash{String=>Object}] Hash containing feature options # @option init_hash [PAnyType] 'type' The member type (required) # @option init_hash [Boolean] 'override' `true` if this feature must override an inherited feature. Default is `false`. # @option init_hash [Boolean] 'final' `true` if this feature cannot be overridden. Default is `false`. # @option init_hash [Hash{PTypeType => Hash}] 'annotations' Annotations hash. Default is `nil`. # @api public def initialize(name, container, init_hash) @name = name @container = container @type = init_hash[KEY_TYPE] @override = init_hash[KEY_OVERRIDE] @override = false if @override.nil? @final = init_hash[KEY_FINAL] @final = false if @final.nil? init_annotatable(init_hash) end # Delegates to the contained type # @param visitor [TypeAcceptor] the visitor # @param guard [RecursionGuard] guard against recursion. Only used by internal calls # @api public def accept(visitor, guard) annotatable_accept(visitor, guard) @type.accept(visitor, guard) end # Checks if the this _member_ overrides an inherited member, and if so, that this member is declared with override = true and that # the inherited member accepts to be overridden by this member. # # @param parent_members [Hash{String=>PAnnotatedMember}] the hash of inherited members # @return [PAnnotatedMember] this instance # @raises [Puppet::ParseError] if the assertion fails # @api private def assert_override(parent_members) parent_member = parent_members[@name] if parent_member.nil? if @override raise Puppet::ParseError, _("expected %{label} to override an inherited %{feature_type}, but no such %{feature_type} was found") % { label: label, feature_type: feature_type } end self else parent_member.assert_can_be_overridden(self) end end # Checks if the given _member_ can override this member. # # @param member [PAnnotatedMember] the overriding member # @return [PAnnotatedMember] its argument # @raises [Puppet::ParseError] if the assertion fails # @api private def assert_can_be_overridden(member) unless self.class == member.class raise Puppet::ParseError, _("%{member} attempts to override %{label}") % { member: member.label, label: label } end if @final && !(constant? && member.constant?) raise Puppet::ParseError, _("%{member} attempts to override final %{label}") % { member: member.label, label: label } end unless member.override? #TRANSLATOR 'override => true' is a puppet syntax and should not be translated raise Puppet::ParseError, _("%{member} attempts to override %{label} without having override => true") % { member: member.label, label: label } end unless @type.assignable?(member.type) raise Puppet::ParseError, _("%{member} attempts to override %{label} with a type that does not match") % { member: member.label, label: label } end member end def constant? false end # @return [Boolean] `true` if this feature cannot be overridden # @api public def final? @final end # @return [Boolean] `true` if this feature must override an inherited feature # @api public def override? @override end # @api public def hash @name.hash ^ @type.hash end # @api public def eql?(o) self.class == o.class && @name == o.name && @type == o.type && @override == o.override? && @final == o.final? end # @api public def ==(o) eql?(o) end # Returns the member as a hash suitable as an argument for constructor. Name is excluded # @return [Hash{String=>Object}] the initialization hash # @api private def _pcore_init_hash hash = { KEY_TYPE => @type } hash[KEY_FINAL] = true if @final hash[KEY_OVERRIDE] = true if @override hash[KEY_ANNOTATIONS] = @annotations unless @annotations.nil? hash end # @api private def feature_type self.class.feature_type end # @api private def label self.class.label(@container, @name) end # Performs type checking of arguments and invokes the method that corresponds to this # method. The result of the invocation is returned # # @param receiver [Object] The receiver of the call # @param scope [Puppet::Parser::Scope] The caller scope # @param args [Array] Array of arguments. # @return [Object] The result returned by the member function or attribute # # @api private def invoke(receiver, scope, args, &block) @dispatch ||= create_dispatch(receiver) args_type = TypeCalculator.infer_set(block_given? ? args + [block] : args) found = @dispatch.find { |d| d.type.callable?(args_type) } raise ArgumentError, TypeMismatchDescriber.describe_signatures(label, @dispatch, args_type) if found.nil? found.invoke(receiver, scope, args, &block) end # @api private def create_dispatch(instance) # TODO: Assumes Ruby implementation for now if(callable_type.is_a?(PVariantType)) callable_type.types.map do |ct| Functions::Dispatch.new(ct, RubyGenerator.protect_reserved_name(name), [], false, ct.block_type.nil? ? nil : 'block') end else [Functions::Dispatch.new(callable_type, RubyGenerator.protect_reserved_name(name), [], false, callable_type.block_type.nil? ? nil : 'block')] end end # @api private def self.feature_type raise NotImplementedError, "'#{self.class.name}' should implement #feature_type" end def self.label(container, name) "#{feature_type} #{container.label}[#{name}]" end end # Describes a named Attribute in an Object type # @api public class PAttribute < PAnnotatedMember # @return [String,nil] The attribute kind as defined by #TYPE_ATTRIBUTE_KIND, or `nil` attr_reader :kind # @param name [String] The name of the attribute # @param container [PObjectType] The containing object type # @param init_hash [Hash{String=>Object}] Hash containing attribute options # @option init_hash [PAnyType] 'type' The attribute type (required) # @option init_hash [Object] 'value' The default value, must be an instanceof the given `type` (optional) # @option init_hash [String] 'kind' The attribute kind, matching #TYPE_ATTRIBUTE_KIND # @api public def initialize(name, container, init_hash) super(name, container, TypeAsserter.assert_instance_of(nil, TYPE_ATTRIBUTE, init_hash) { "initializer for #{self.class.label(container, name)}" }) @kind = init_hash[KEY_KIND] if @kind == ATTRIBUTE_KIND_CONSTANT # final is implied if init_hash.include?(KEY_FINAL) && !@final #TRANSLATOR 'final => false' is puppet syntax and should not be translated raise Puppet::ParseError, _("%{label} of kind 'constant' cannot be combined with final => false") % { label: label } end @final = true end if init_hash.include?(KEY_VALUE) if @kind == ATTRIBUTE_KIND_DERIVED || @kind == ATTRIBUTE_KIND_GIVEN_OR_DERIVED raise Puppet::ParseError, _("%{label} of kind '%{kind}' cannot be combined with an attribute value") % { label: label, kind: @kind } end v = init_hash[KEY_VALUE] @value = v == :default ? v : TypeAsserter.assert_instance_of(nil, type, v) {"#{label} #{KEY_VALUE}" } else raise Puppet::ParseError, _("%{label} of kind 'constant' requires a value") % { label: label } if @kind == ATTRIBUTE_KIND_CONSTANT @value = :undef # Not to be confused with nil or :default end end def callable_type TYPE_ATTRIBUTE_CALLABLE end # @api public def eql?(o) super && @kind == o.kind && @value == (o.value? ? o.value : :undef) end # Returns the member as a hash suitable as an argument for constructor. Name is excluded # @return [Hash{String=>Object}] the hash # @api private def _pcore_init_hash hash = super unless @kind.nil? hash[KEY_KIND] = @kind hash.delete(KEY_FINAL) if @kind == ATTRIBUTE_KIND_CONSTANT # final is implied end hash[KEY_VALUE] = @value unless @value == :undef hash end def constant? @kind == ATTRIBUTE_KIND_CONSTANT end # @return [Booelan] true if the given value equals the default value for this attribute def default_value?(value) @value == value end # @return [Boolean] `true` if a value has been defined for this attribute. def value? @value != :undef end # Returns the value of this attribute, or raises an error if no value has been defined. Raising an error # is necessary since a defined value may be `nil`. # # @return [Object] the value that has been defined for this attribute. # @raise [Puppet::Error] if no value has been defined # @api public def value # An error must be raised here since `nil` is a valid value and it would be bad to leak the :undef symbol raise Puppet::Error, "#{label} has no value" if @value == :undef @value end # @api private def self.feature_type 'attribute' end end class PTypeParameter < PAttribute # @return [Hash{String=>Object}] the hash # @api private def _pcore_init_hash hash = super hash[KEY_TYPE] = hash[KEY_TYPE].type hash.delete(KEY_VALUE) if hash.include?(KEY_VALUE) && hash[KEY_VALUE].nil? hash end # @api private def self.feature_type 'type_parameter' end end # Describes a named Function in an Object type # @api public class PFunction < PAnnotatedMember # @param name [String] The name of the attribute # @param container [PObjectType] The containing object type # @param init_hash [Hash{String=>Object}] Hash containing function options # @api public def initialize(name, container, init_hash) super(name, container, TypeAsserter.assert_instance_of(["initializer for function '%s'", name], TYPE_FUNCTION, init_hash)) end def callable_type type end # @api private def self.feature_type 'function' end end attr_reader :name attr_reader :parent attr_reader :equality attr_reader :checks attr_reader :annotations # Initialize an Object Type instance. The initialization will use either a name and an initialization # hash expression, or a fully resolved initialization hash. # # @overload initialize(name, init_hash_expression) # Used when the Object type is loaded using a type alias expression. When that happens, it is important that # the actual resolution of the expression is deferred until all definitions have been made known to the current # loader. The object will then be resolved when it is loaded by the {TypeParser}. "resolved" here, means that # the hash expression is fully resolved, and then passed to the {#_pcore_init_from_hash} method. # @param name [String] The name of the object # @param init_hash_expression [Model::LiteralHash] The hash describing the Object features # # @overload initialize(init_hash) # Used when the object is created by the {TypeFactory}. The init_hash must be fully resolved. # @param _pcore_init_hash [Hash{String=>Object}] The hash describing the Object features # @param loader [Loaders::Loader,nil] the loader that loaded the type # # @api private def initialize(_pcore_init_hash, init_hash_expression = nil) if _pcore_init_hash.is_a?(Hash) _pcore_init_from_hash(_pcore_init_hash) @loader = init_hash_expression unless init_hash_expression.nil? else @type_parameters = EMPTY_HASH @attributes = EMPTY_HASH @functions = EMPTY_HASH @name = TypeAsserter.assert_instance_of('object name', TYPE_OBJECT_NAME, _pcore_init_hash) @init_hash_expression = init_hash_expression end end def instance?(o, guard = nil) if o.is_a?(PuppetObject) assignable?(o._pcore_type, guard) else name = o.class.name return false if name.nil? # anonymous class that doesn't implement PuppetObject is not an instance ir = Loaders.implementation_registry type = ir.nil? ? nil : ir.type_for_module(name) !type.nil? && assignable?(type, guard) end end # @api private def new_function @new_function ||= create_new_function end # Assign a new instance reader to this type # @param [Serialization::InstanceReader] reader the reader to assign # @api private def reader=(reader) @reader = reader end # Assign a new instance write to this type # @param [Serialization::InstanceWriter] the writer to assign # @api private def writer=(writer) @writer = writer end # Read an instance of this type from a deserializer # @param [Integer] value_count the number attributes needed to create the instance # @param [Serialization::Deserializer] deserializer the deserializer to read from # @return [Object] the created instance # @api private def read(value_count, deserializer) reader.read(self, implementation_class, value_count, deserializer) end # Write an instance of this type using a serializer # @param [Object] value the instance to write # @param [Serialization::Serializer] the serializer to write to # @api private def write(value, serializer) writer.write(self, value, serializer) end # @api private def create_new_function impl_class = implementation_class return impl_class.create_new_function(self) if impl_class.respond_to?(:create_new_function) (param_names, param_types, required_param_count) = parameter_info(impl_class) # Create the callable with a size that reflects the required and optional parameters create_type = TypeFactory.callable(*param_types, required_param_count, param_names.size) from_hash_type = TypeFactory.callable(init_hash_type, 1, 1) # Create and return a #new_XXX function where the dispatchers are added programmatically. Puppet::Functions.create_loaded_function(:"new_#{name}", loader) do # The class that creates new instances must be available to the constructor methods # and is therefore declared as a variable and accessor on the class that represents # this added function. @impl_class = impl_class def self.impl_class @impl_class end # It's recommended that an implementor of an Object type provides the method #from_asserted_hash. # This method should accept a hash and assume that type assertion has been made already (it is made # by the dispatch added here). if impl_class.respond_to?(:from_asserted_hash) dispatcher.add(Functions::Dispatch.new(from_hash_type, :from_hash, ['hash'])) def from_hash(hash) self.class.impl_class.from_asserted_hash(hash) end end # Add the dispatch that uses the standard #from_asserted_args or #new method on the class. It's assumed that the # method performs no assertions. dispatcher.add(Functions::Dispatch.new(create_type, :create, param_names)) if impl_class.respond_to?(:from_asserted_args) def create(*args) self.class.impl_class.from_asserted_args(*args) end else def create(*args) self.class.impl_class.new(*args) end end end end # @api private def implementation_class(create = true) if @implementation_class.nil? && create ir = Loaders.implementation_registry class_name = ir.nil? ? nil : ir.module_name_for_type(self) if class_name.nil? # Use generator to create a default implementation @implementation_class = RubyGenerator.new.create_class(self) @implementation_class.class_eval(&@implementation_override) if instance_variable_defined?(:@implementation_override) else # Can the mapping be loaded? @implementation_class = ClassLoader.provide(class_name) raise Puppet::Error, "Unable to load class #{class_name}" if @implementation_class.nil? unless @implementation_class < PuppetObject || @implementation_class.respond_to?(:ecore) raise Puppet::Error, "Unable to create an instance of #{name}. #{class_name} does not include module #{PuppetObject.name}" end end end @implementation_class end # @api private def implementation_class=(cls) raise ArgumentError, "attempt to redefine implementation class for #{label}" unless @implementation_class.nil? @implementation_class = cls end # The block passed to this method will be passed in a call to `#class_eval` on the dynamically generated # class for this data type. It's indended use is to complement or redefine the generated methods and # attribute readers. # # The method is normally called with the block passed to `#implementation` when a data type is defined using # {Puppet::DataTypes::create_type}. # # @api private def implementation_override=(block) if !@implementation_class.nil? || instance_variable_defined?(:@implementation_override) raise ArgumentError, "attempt to redefine implementation override for #{label}" end @implementation_override = block end def extract_init_hash(o) return o._pcore_init_hash if o.respond_to?(:_pcore_init_hash) result = {} pic = parameter_info(o.class) attrs = attributes(true) pic[0].each do |name| v = o.send(name) result[name] = v unless attrs[name].default_value?(v) end result end # @api private # @return [(Array<String>, Array<PAnyType>, Integer)] array of parameter names, array of parameter types, and a count reflecting the required number of parameters def parameter_info(impl_class) # Create a types and a names array where optional entries ends up last @parameter_info ||= {} pic = @parameter_info[impl_class] return pic if pic opt_types = [] opt_names = [] non_opt_types = [] non_opt_names = [] init_hash_type.elements.each do |se| if se.key_type.is_a?(POptionalType) opt_names << se.name opt_types << se.value_type else non_opt_names << se.name non_opt_types << se.value_type end end param_names = non_opt_names + opt_names param_types = non_opt_types + opt_types param_count = param_names.size init = impl_class.respond_to?(:from_asserted_args) ? impl_class.method(:from_asserted_args) : impl_class.instance_method(:initialize) init_non_opt_count = 0 init_param_names = init.parameters.map do |p| init_non_opt_count += 1 if :req == p[0] n = p[1].to_s r = RubyGenerator.unprotect_reserved_name(n) unless r.equal?(n) # assert that the protected name wasn't a real name (names can start with underscore) n = r unless param_names.index(r).nil? end n end if init_param_names != param_names if init_param_names.size < param_count || init_non_opt_count > param_count raise Serialization::SerializationError, "Initializer for class #{impl_class.name} does not match the attributes of #{name}" end init_param_names = init_param_names[0, param_count] if init_param_names.size > param_count unless init_param_names == param_names # Reorder needed to match initialize method arguments new_param_types = [] init_param_names.each do |ip| index = param_names.index(ip) if index.nil? raise Serialization::SerializationError, "Initializer for class #{impl_class.name} parameter '#{ip}' does not match any of the the attributes of type #{name}" end new_param_types << param_types[index] end param_names = init_param_names param_types = new_param_types end end pic = [param_names.freeze, param_types.freeze, non_opt_types.size].freeze @parameter_info[impl_class] = pic pic end # @api private def attr_reader_name(se) if se.value_type.is_a?(PBooleanType) || se.value_type.is_a?(POptionalType) && se.value_type.type.is_a?(PBooleanType) "#{se.name}?" else se.name end end def self.from_hash(hash) new(hash, nil) end # @api private def _pcore_init_from_hash(init_hash) TypeAsserter.assert_instance_of('object initializer', TYPE_OBJECT_I12N, init_hash) @type_parameters = EMPTY_HASH @attributes = EMPTY_HASH @functions = EMPTY_HASH # Name given to the loader have higher precedence than a name declared in the type @name ||= init_hash[KEY_NAME] @name.freeze unless @name.nil? @parent = init_hash[KEY_PARENT] parent_members = EMPTY_HASH parent_type_params = EMPTY_HASH parent_object_type = nil unless @parent.nil? check_self_recursion(self) rp = resolved_parent raise Puppet::ParseError, _("reference to unresolved type '%{name}'") % { :name => rp.type_string } if rp.is_a?(PTypeReferenceType) if rp.is_a?(PObjectType) parent_object_type = rp parent_members = rp.members(true) parent_type_params = rp.type_parameters(true) end end type_parameters = init_hash[KEY_TYPE_PARAMETERS] unless type_parameters.nil? || type_parameters.empty? @type_parameters = {} type_parameters.each do |key, param_spec| param_value = :undef if param_spec.is_a?(Hash) param_type = param_spec[KEY_TYPE] param_value = param_spec[KEY_VALUE] if param_spec.include?(KEY_VALUE) else param_type = TypeAsserter.assert_instance_of(nil, PTypeType::DEFAULT, param_spec) { "type_parameter #{label}[#{key}]" } end param_type = POptionalType.new(param_type) unless param_type.is_a?(POptionalType) type_param = PTypeParameter.new(key, self, KEY_TYPE => param_type, KEY_VALUE => param_value).assert_override(parent_type_params) @type_parameters[key] = type_param end end constants = init_hash[KEY_CONSTANTS] attr_specs = init_hash[KEY_ATTRIBUTES] if attr_specs.nil? attr_specs = {} else # attr_specs might be frozen attr_specs = Hash[attr_specs] end unless constants.nil? || constants.empty? constants.each do |key, value| if attr_specs.include?(key) raise Puppet::ParseError, _("attribute %{label}[%{key}] is defined as both a constant and an attribute") % { label: label, key: key } end attr_spec = { # Type must be generic here, or overrides would become impossible KEY_TYPE => TypeCalculator.infer(value).generalize, KEY_VALUE => value, KEY_KIND => ATTRIBUTE_KIND_CONSTANT } # Indicate override if parent member exists. Type check etc. will take place later on. attr_spec[KEY_OVERRIDE] = parent_members.include?(key) attr_specs[key] = attr_spec end end unless attr_specs.empty? @attributes = Hash[attr_specs.map do |key, attr_spec| unless attr_spec.is_a?(Hash) attr_type = TypeAsserter.assert_instance_of(nil, PTypeType::DEFAULT, attr_spec) { "attribute #{label}[#{key}]" } attr_spec = { KEY_TYPE => attr_type } attr_spec[KEY_VALUE] = nil if attr_type.is_a?(POptionalType) end attr = PAttribute.new(key, self, attr_spec) [attr.name, attr.assert_override(parent_members)] end].freeze end func_specs = init_hash[KEY_FUNCTIONS] unless func_specs.nil? || func_specs.empty? @functions = Hash[func_specs.map do |key, func_spec| func_spec = { KEY_TYPE => TypeAsserter.assert_instance_of(nil, TYPE_FUNCTION_TYPE, func_spec) { "function #{label}[#{key}]" } } unless func_spec.is_a?(Hash) func = PFunction.new(key, self, func_spec) name = func.name raise Puppet::ParseError, _("%{label} conflicts with attribute with the same name") % { label: func.label } if @attributes.include?(name) [name, func.assert_override(parent_members)] end].freeze end @equality_include_type = init_hash[KEY_EQUALITY_INCLUDE_TYPE] @equality_include_type = true if @equality_include_type.nil? equality = init_hash[KEY_EQUALITY] equality = [equality] if equality.is_a?(String) if equality.is_a?(Array) unless equality.empty? #TRANSLATORS equality_include_type = false should not be translated raise Puppet::ParseError, _('equality_include_type = false cannot be combined with non empty equality specification') unless @equality_include_type parent_eq_attrs = nil equality.each do |attr_name| attr = parent_members[attr_name] if attr.nil? attr = @attributes[attr_name] || @functions[attr_name] elsif attr.is_a?(PAttribute) # Assert that attribute is not already include by parent equality parent_eq_attrs ||= parent_object_type.equality_attributes if parent_eq_attrs.include?(attr_name) including_parent = find_equality_definer_of(attr) raise Puppet::ParseError, _("%{label} equality is referencing %{attribute} which is included in equality of %{including_parent}") % { label: label, attribute: attr.label, including_parent: including_parent.label } end end unless attr.is_a?(PAttribute) if attr.nil? raise Puppet::ParseError, _("%{label} equality is referencing non existent attribute '%{attribute}'") % { label: label, attribute: attr_name } end raise Puppet::ParseError, _("%{label} equality is referencing %{attribute}. Only attribute references are allowed") % { label: label, attribute: attr.label } end if attr.kind == ATTRIBUTE_KIND_CONSTANT raise Puppet::ParseError, _("%{label} equality is referencing constant %{attribute}.") % { label: label, attribute: attr.label } + ' ' + _("Reference to constant is not allowed in equality") end end end equality.freeze end @equality = equality @checks = init_hash[KEY_CHECKS] init_annotatable(init_hash) end def [](name) member = @attributes[name] || @functions[name] if member.nil? rp = resolved_parent member = rp[name] if rp.is_a?(PObjectType) end member end def accept(visitor, guard) guarded_recursion(guard, nil) do |g| super(visitor, g) @parent.accept(visitor, g) unless parent.nil? @type_parameters.values.each { |p| p.accept(visitor, g) } @attributes.values.each { |a| a.accept(visitor, g) } @functions.values.each { |f| f.accept(visitor, g) } end end def callable_args?(callable, guard) @parent.nil? ? false : @parent.callable_args?(callable, guard) end # Returns the type that a initialization hash used for creating instances of this type must conform to. # # @return [PStructType] the initialization hash type # @api public def init_hash_type @init_hash_type ||= create_init_hash_type end def allocate implementation_class.allocate end def create(*args) implementation_class.create(*args) end def from_hash(hash) implementation_class.from_hash(hash) end # Creates the type that a initialization hash used for creating instances of this type must conform to. # # @return [PStructType] the initialization hash type # @api private def create_init_hash_type struct_elems = {} attributes(true).values.each do |attr| unless attr.kind == ATTRIBUTE_KIND_CONSTANT || attr.kind == ATTRIBUTE_KIND_DERIVED if attr.value? struct_elems[TypeFactory.optional(attr.name)] = attr.type else struct_elems[attr.name] = attr.type end end end TypeFactory.struct(struct_elems) end # The init_hash is primarily intended for serialization and string representation purposes. It creates a hash # suitable for passing to {PObjectType#new(init_hash)} # # @return [Hash{String=>Object}] the features hash # @api public def _pcore_init_hash(include_name = true) result = super() result[KEY_NAME] = @name if include_name && !@name.nil? result[KEY_PARENT] = @parent unless @parent.nil? result[KEY_TYPE_PARAMETERS] = compressed_members_hash(@type_parameters) unless @type_parameters.empty? unless @attributes.empty? # Divide attributes into constants and others tc = TypeCalculator.singleton constants, others = @attributes.partition do |_, a| a.kind == ATTRIBUTE_KIND_CONSTANT && a.type == tc.infer(a.value).generalize end.map { |ha| Hash[ha] } result[KEY_ATTRIBUTES] = compressed_members_hash(others) unless others.empty? unless constants.empty? # { kind => 'constant', type => <type of value>, value => <value> } becomes just <value> constants.each_pair { |key, a| constants[key] = a.value } result[KEY_CONSTANTS] = constants end end result[KEY_FUNCTIONS] = compressed_members_hash(@functions) unless @functions.empty? result[KEY_EQUALITY] = @equality unless @equality.nil? result[KEY_CHECKS] = @checks unless @checks.nil? result end def eql?(o) self.class == o.class && @name == o.name && @parent == o.parent && @type_parameters == o.type_parameters && @attributes == o.attributes && @functions == o.functions && @equality == o.equality && @checks == o.checks end def hash @name.nil? ? [@parent, @type_parameters, @attributes, @functions].hash : @name.hash end def kind_of_callable?(optional=true, guard = nil) @parent.nil? ? false : @parent.kind_of_callable?(optional, guard) end def iterable?(guard = nil) @parent.nil? ? false : @parent.iterable?(guard) end def iterable_type(guard = nil) @parent.nil? ? false : @parent.iterable_type(guard) end def parameterized? if @type_parameters.empty? @parent.is_a?(PObjectType) ? @parent.parameterized? : false else true end end # Returns the members (attributes and functions) of this `Object` type. If _include_parent_ is `true`, then all # inherited members will be included in the returned `Hash`. # # @param include_parent [Boolean] `true` if inherited members should be included # @return [Hash{String=>PAnnotatedMember}] a hash with the members # @api public def members(include_parent = false) get_members(include_parent, :both) end # Returns the attributes of this `Object` type. If _include_parent_ is `true`, then all # inherited attributes will be included in the returned `Hash`. # # @param include_parent [Boolean] `true` if inherited attributes should be included # @return [Hash{String=>PAttribute}] a hash with the attributes # @api public def attributes(include_parent = false) get_members(include_parent, :attributes) end # Returns the attributes that participate in equality comparison. Inherited equality attributes # are included. # @return [Hash{String=>PAttribute}] a hash of attributes # @api public def equality_attributes all = {} collect_equality_attributes(all) all end # @return [Boolean] `true` if this type is included when comparing instances # @api public def equality_include_type? @equality_include_type end # Returns the functions of this `Object` type. If _include_parent_ is `true`, then all # inherited functions will be included in the returned `Hash`. # # @param include_parent [Boolean] `true` if inherited functions should be included # @return [Hash{String=>PFunction}] a hash with the functions # @api public def functions(include_parent = false) get_members(include_parent, :functions) end DEFAULT = PObjectType.new(EMPTY_HASH) # Assert that this type does not inherit from itself # @api private def check_self_recursion(originator) unless @parent.nil? raise Puppet::Error, "The Object type '#{originator.label}' inherits from itself" if @parent.equal?(originator) @parent.check_self_recursion(originator) end end # @api private def label @name || 'Object' end # @api private def resolved_parent parent = @parent while parent.is_a?(PTypeAliasType) parent = parent.resolved_type end parent end def simple_name label.split(DOUBLE_COLON).last end # Returns the type_parameters of this `Object` type. If _include_parent_ is `true`, then all # inherited type_parameters will be included in the returned `Hash`. # # @param include_parent [Boolean] `true` if inherited type_parameters should be included # @return [Hash{String=>PTypeParameter}] a hash with the type_parameters # @api public def type_parameters(include_parent = false) all = {} collect_type_parameters(all, include_parent) all end protected # An Object type is only assignable from another Object type. The other type # or one of its parents must be equal to this type. def _assignable?(o, guard) if o.is_a?(PObjectType) if DEFAULT == self || self == o true else op = o.parent op.nil? ? false : assignable?(op, guard) end elsif o.is_a?(PObjectTypeExtension) assignable?(o.base_type, guard) else false end end def get_members(include_parent, member_type) all = {} collect_members(all, include_parent, member_type) all end def collect_members(collector, include_parent, member_type) if include_parent parent = resolved_parent parent.collect_members(collector, include_parent, member_type) if parent.is_a?(PObjectType) end collector.merge!(@attributes) unless member_type == :functions collector.merge!(@functions) unless member_type == :attributes nil end def collect_equality_attributes(collector) parent = resolved_parent parent.collect_equality_attributes(collector) if parent.is_a?(PObjectType) if @equality.nil? # All attributes except constants participate collector.merge!(@attributes.reject { |_, attr| attr.kind == ATTRIBUTE_KIND_CONSTANT }) else collector.merge!(Hash[@equality.map { |attr_name| [attr_name, @attributes[attr_name]] }]) end nil end def collect_type_parameters(collector, include_parent) if include_parent parent = resolved_parent parent.collect_type_parameters(collector, include_parent) if parent.is_a?(PObjectType) end collector.merge!(@type_parameters) nil end private def compressed_members_hash(features) Hash[features.values.map do |feature| fh = feature._pcore_init_hash if fh.size == 1 type = fh[KEY_TYPE] fh = type unless type.nil? end [feature.name, fh] end] end # @return [PObjectType] the topmost parent who's #equality_attributes include the given _attr_ def find_equality_definer_of(attr) type = self while !type.nil? do p = type.resolved_parent return type unless p.is_a?(PObjectType) return type unless p.equality_attributes.include?(attr.name) type = p end nil end def guarded_recursion(guard, dflt) if @self_recursion guard ||= RecursionGuard.new guard.with_this(self) { |state| (state & RecursionGuard::SELF_RECURSION_IN_THIS) == 0 ? yield(guard) : dflt } else yield(guard) end end def reader @reader ||= Serialization::ObjectReader::INSTANCE end def writer @writer ||= Serialization::ObjectWriter::INSTANCE end end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/string_converter.rb���������������������������������������������0000644�0052762�0001160�00000112753�13417161721�023155� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Types # Converts Puppet runtime objects to String under the control of a Format. # Use from Puppet Language is via the function `new`. # # @api private # class StringConverter # @api private class FormatError < ArgumentError def initialize(type_string, actual, expected) super "Illegal format '#{actual}' specified for value of #{type_string} type - expected one of the characters '#{expected}'" end end class Indentation attr_reader :level attr_reader :first attr_reader :is_indenting alias :first? :first alias :is_indenting? :is_indenting def initialize(level, first, is_indenting) @level = level @first = first @is_indenting = is_indenting end def subsequent first? ? self.class.new(level, false, @is_indenting) : self end def indenting(indenting_flag) self.class.new(level, first?, indenting_flag) end def increase(indenting_flag = false) self.class.new(level + 1, true, indenting_flag) end def breaks? is_indenting? && level > 0 && ! first? end def padding return ' ' * 2 * level end end # Format represents one format specification that is textually represented by %<flags><width>.<precision><format> # Format parses and makes the individual parts available when an instance is created. # # @api private # class Format # Boolean, alternate form (varies in meaning) attr_reader :alt alias :alt? :alt # Nil or Integer with width of field > 0 attr_reader :width # Nil or Integer precisions attr_reader :prec # One char symbol denoting the format attr_reader :format # Symbol, :space, :plus, :ignore attr_reader :plus # Boolean, left adjust in given width or not attr_reader :left # Boolean left_pad with zero instead of space attr_reader :zero_pad # Delimiters for containers, a "left" char representing the pair <[{( attr_reader :delimiters # Map of type to format for elements contained in an object this format applies to attr_accessor :container_string_formats # Separator string inserted between elements in a container attr_accessor :separator # Separator string inserted between sub elements in a container attr_accessor :separator2 attr_reader :orig_fmt FMT_PATTERN_STR = '^%([\s\[+#0{<(|-]*)([1-9][0-9]*)?(?:\.([0-9]+))?([a-zA-Z])$' FMT_PATTERN = Regexp.compile(FMT_PATTERN_STR) DELIMITERS = [ '[', '{', '(', '<', '|',] DELIMITER_MAP = { '[' => ['[', ']'], '{' => ['{', '}'], '(' => ['(', ')'], '<' => ['<', '>'], '|' => ['|', '|'], :space => ['', ''] }.freeze def initialize(fmt) @orig_fmt = fmt match = FMT_PATTERN.match(fmt) unless match raise ArgumentError, "The format '#{fmt}' is not a valid format on the form '%<flags><width>.<prec><format>'" end @format = match[4] unless @format.is_a?(String) && @format.length == 1 raise ArgumentError, "The format must be a one letter format specifier, got '#{@format}'" end @format = @format.to_sym flags = match[1].split('') || [] unless flags.uniq.size == flags.size raise ArgumentError, "The same flag can only be used once, got '#{fmt}'" end @left = flags.include?('-') @alt = flags.include?('#') @plus = (flags.include?(' ') ? :space : (flags.include?('+') ? :plus : :ignore)) @zero_pad = flags.include?('0') @delimiters = nil DELIMITERS.each do |d| next unless flags.include?(d) if !@delimiters.nil? raise ArgumentError, "Only one of the delimiters [ { ( < | can be given in the format flags, got '#{fmt}'" end @delimiters = d end @width = match[2] ? match[2].to_i : nil @prec = match[3] ? match[3].to_i : nil end # Merges one format into this and returns a new `Format`. The `other` format overrides this. # @param [Format] other # @returns [Format] a merged format # def merge(other) result = Format.new(other.orig_fmt) result.separator = other.separator || separator result.separator2 = other.separator2 || separator2 result.container_string_formats = Format.merge_string_formats(container_string_formats, other.container_string_formats) result end # Merges two formats where the `higher` format overrides the `lower`. Produces a new `Format` # @param [Format] lower # @param [Format] higher # @returns [Format] the merged result # def self.merge(lower, higher) unless lower && higher return lower || higher end lower.merge(higher) end # Merges a type => format association and returns a new merged and sorted association. # @param [Format] lower # @param [Format] higher # @returns [Hash] the merged type => format result # def self.merge_string_formats(lower, higher) unless lower && higher return lower || higher end # drop all formats in lower than is more generic in higher. Lower must never # override higher lower = lower.reject { |lk, _| higher.keys.any? { |hk| hk != lk && hk.assignable?(lk) }} merged = (lower.keys + higher.keys).uniq.map do |k| [k, merge(lower[k], higher[k])] end sort_formats(merged) end # Sorts format based on generality of types - most specific types before general # def self.sort_formats(format_map) format_map = format_map.sort do |(a,_),(b,_)| ab = b.assignable?(a) ba = a.assignable?(b) if a == b 0 elsif ab && !ba -1 elsif !ab && ba 1 else # arbitrary order if disjunct (based on name of type) rank_a = type_rank(a) rank_b = type_rank(b) if rank_a == 0 || rank_b == 0 a.to_s <=> b.to_s else rank_a <=> rank_b end end end Hash[format_map] end # Ranks type on specificity where it matters # lower number means more specific def self.type_rank(t) case t when PStructType 1 when PHashType 2 when PTupleType 3 when PArrayType 4 when PPatternType 10 when PEnumType 11 when PStringType 12 else 0 end end # Returns an array with a delimiter pair derived from the format. # If format does not contain a delimiter specification the given default is returned # # @param [Array<String>] the default delimiters # @returns [Array<String>] a tuple with left, right delimiters # def delimiter_pair(default = StringConverter::DEFAULT_ARRAY_DELIMITERS) DELIMITER_MAP[ @delimiters || @plus ] || default end def to_s "%#{@flags}#{@width}.#{@prec}#{@format}" end end # @api public def self.convert(value, string_formats = :default) singleton.convert(value, string_formats) end # @return [TypeConverter] the singleton instance # # @api public def self.singleton @tconv_instance ||= new end # @api private # def initialize @@string_visitor ||= Visitor.new(self, "string", 3, 3) end DEFAULT_INDENTATION = Indentation.new(0, true, false).freeze # format used by default for values in a container # (basically strings are quoted since they may contain a ',')) # DEFAULT_CONTAINER_FORMATS = { PAnyType::DEFAULT => Format.new('%p').freeze, # quoted string (Ruby inspect) }.freeze DEFAULT_ARRAY_FORMAT = Format.new('%a') DEFAULT_ARRAY_FORMAT.separator = ', '.freeze DEFAULT_ARRAY_FORMAT.separator2 = ', '.freeze DEFAULT_ARRAY_FORMAT.container_string_formats = DEFAULT_CONTAINER_FORMATS DEFAULT_ARRAY_FORMAT.freeze DEFAULT_HASH_FORMAT = Format.new('%h') DEFAULT_HASH_FORMAT.separator = ', '.freeze DEFAULT_HASH_FORMAT.separator2 = ' => '.freeze DEFAULT_HASH_FORMAT.container_string_formats = DEFAULT_CONTAINER_FORMATS DEFAULT_HASH_FORMAT.freeze DEFAULT_HASH_DELIMITERS = ['{', '}'].freeze DEFAULT_ARRAY_DELIMITERS = ['[', ']'].freeze DEFAULT_STRING_FORMATS = { PObjectType::DEFAULT => Format.new('%p').freeze, # call with initialization hash PFloatType::DEFAULT => Format.new('%f').freeze, # float PNumericType::DEFAULT => Format.new('%d').freeze, # decimal number PArrayType::DEFAULT => DEFAULT_ARRAY_FORMAT.freeze, PHashType::DEFAULT => DEFAULT_HASH_FORMAT.freeze, PBinaryType::DEFAULT => Format.new('%B').freeze, # strict base64 string unquoted PAnyType::DEFAULT => Format.new('%s').freeze, # unquoted string }.freeze DEFAULT_PARAMETER_FORMAT = { PCollectionType::DEFAULT => '%#p', PObjectType::DEFAULT => '%#p', PBinaryType::DEFAULT => '%p', PStringType::DEFAULT => '%p', PRuntimeType::DEFAULT => '%p' }.freeze # Converts the given value to a String, under the direction of formatting rules per type. # # When converting to string it is possible to use a set of built in conversion rules. # # A format is specified on the form: # # ´´´ # %[Flags][Width][.Precision]Format # ´´´ # # `Width` is the number of characters into which the value should be fitted. This allocated space is # padded if value is shorter. By default it is space padded, and the flag 0 will cause padding with 0 # for numerical formats. # # `Precision` is the number of fractional digits to show for floating point, and the maximum characters # included in a string format. # # Note that all data type supports the formats `s` and `p` with the meaning "default to-string" and # "default-programmatic to-string". # # ### Integer # # | Format | Integer Formats # | ------ | --------------- # | d | Decimal, negative values produces leading '-' # | x X | Hexadecimal in lower or upper case. Uses ..f/..F for negative values unless # is also used # | o | Octal. Uses ..0 for negative values unless # is also used # | b B | Binary with prefix 'b' or 'B'. Uses ..1/..1 for negative values unless # is also used # | c | numeric value representing a Unicode value, result is a one unicode character string, quoted if alternative flag # is used # | s | same as d, or d in quotes if alternative flag # is used # | p | same as d # | eEfgGaA | converts integer to float and formats using the floating point rules # # Defaults to `d` # # ### Float # # | Format | Float formats # | ------ | ------------- # | f | floating point in non exponential notation # | e E | exponential notation with 'e' or 'E' # | g G | conditional exponential with 'e' or 'E' if exponent < -4 or >= the precision # | a A | hexadecimal exponential form, using 'x'/'X' as prefix and 'p'/'P' before exponent # | s | converted to string using format p, then applying string formatting rule, alternate form # quotes result # | p | f format with minimum significant number of fractional digits, prec has no effect # | dxXobBc | converts float to integer and formats using the integer rules # # Defaults to `p` # # ### String # # | Format | String # | ------ | ------ # | s | unquoted string, verbatim output of control chars # | p | programmatic representation - strings are quoted, interior quotes and control chars are escaped # | C | each :: name segment capitalized, quoted if alternative flag # is used # | c | capitalized string, quoted if alternative flag # is used # | d | downcased string, quoted if alternative flag # is used # | u | upcased string, quoted if alternative flag # is used # | t | trims leading and trailing whitespace from the string, quoted if alternative flag # is used # # Defaults to `s` at top level and `p` inside array or hash. # # ### Boolean # # | Format | Boolean Formats # | ---- | ------------------- # | t T | 'true'/'false' or 'True'/'False' , first char if alternate form is used (i.e. 't'/'f' or 'T'/'F'). # | y Y | 'yes'/'no', 'Yes'/'No', 'y'/'n' or 'Y'/'N' if alternative flag # is used # | dxXobB | numeric value 0/1 in accordance with the given format which must be valid integer format # | eEfgGaA | numeric value 0.0/1.0 in accordance with the given float format and flags # | s | 'true' / 'false' # | p | 'true' / 'false' # # ### Regexp # # | Format | Regexp Formats (%/) # | ---- | ------------------ # | s | / / delimiters, alternate flag replaces / delimiters with quotes # | p | / / delimiters # # ### Undef # # | Format | Undef formats # | ------ | ------------- # | s | empty string, or quoted empty string if alternative flag # is used # | p | 'undef', or quoted '"undef"' if alternative flag # is used # | n | 'nil', or 'null' if alternative flag # is used # | dxXobB | 'NaN' # | eEfgGaA | 'NaN' # | v | 'n/a' # | V | 'N/A' # | u | 'undef', or 'undefined' if alternative # flag is used # # ### Default (value) # # | Format | Default formats # | ------ | --------------- # | d D | 'default' or 'Default', alternative form # causes value to be quoted # | s | same as d # | p | same as d # # ### Binary (value) # # | Format | Default formats # | ------ | --------------- # | s | binary as unquoted characters # | p | 'Binary("<base64strict>")' # | b | '<base64>' - base64 string with newlines inserted # | B | '<base64strict>' - base64 strict string (without newlines inserted) # | u | '<base64urlsafe>' - base64 urlsafe string # | t | 'Binary' - outputs the name of the type only # | T | 'BINARY' - output the name of the type in all caps only # # The alternate form flag `#` will quote the binary or base64 text output # The width and precision values are applied to the text part only in `%p` format. # # ### Array & Tuple # # | Format | Array/Tuple Formats # | ------ | ------------- # | a | formats with `[ ]` delimiters and `,`, alternate form `#` indents nested arrays/hashes # | s | same as a # | p | same as a # # See "Flags" `<[({\|` for formatting of delimiters, and "Additional parameters for containers; Array and Hash" for # more information about options. # # The alternate form flag `#` will cause indentation of nested array or hash containers. If width is also set # it is taken as the maximum allowed length of a sequence of elements (not including delimiters). If this max length # is exceeded, each element will be indented. # # ### Hash & Struct # # | Format | Hash/Struct Formats # | ------ | ------------- # | h | formats with `{ }` delimiters, `,` element separator and ` => ` inner element separator unless overridden by flags # | s | same as h # | p | same as h # | a | converts the hash to an array of [k,v] tuples and formats it using array rule(s) # # See "Flags" `<[({\|` for formatting of delimiters, and "Additional parameters for containers; Array and Hash" for # more information about options. # # The alternate form flag `#` will format each hash key/value entry indented on a separate line. # # ### Type # # | Format | Array/Tuple Formats # | ------ | ------------- # | s | The same as p, quoted if alternative flag # is used # | p | Outputs the type in string form as specified by the Puppet Language # # ### Flags # # | Flag | Effect # | ------ | ------ # | (space) | space instead of + for numeric output (- is shown), for containers skips delimiters # | # | alternate format; prefix 0x/0x, 0 (octal) and 0b/0B for binary, Floats force decimal '.'. For g/G keep trailing 0. # | + | show sign +/- depending on value's sign, changes x,X, o,b, B format to not use 2's complement form # | - | left justify the value in the given width # | 0 | pad with 0 instead of space for widths larger than value # | <[({\| | defines an enclosing pair <> [] () {} or \| \| when used with a container type # # # ### Additional parameters for containers; Array and Hash # # For containers (Array and Hash), the format is specified by a hash where the following keys can be set: # * `'format'` - the format specifier for the container itself # * `'separator'` - the separator string to use between elements, should not contain padding space at the end # * `'separator2'` - the separator string to use between association of hash entries key/value # * `'string_formats'´ - a map of type to format for elements contained in the container # # Note that the top level format applies to Array and Hash objects contained/nested in an Array or a Hash. # # Given format mappings are merged with (default) formats and a format specified for a narrower type # wins over a broader. # # @param mode [String, Symbol] :strict or :extended (or :default which is the same as :strict) # @param string_formats [String, Hash] format tring, or a hash mapping type to a format string, and for Array and Hash types map to hash of details # def convert(value, string_formats = :default) options = DEFAULT_STRING_FORMATS value_type = TypeCalculator.infer_set(value) if string_formats.is_a?(String) # For Array and Hash, the format is given as a Hash where 'format' key is the format for the collection itself if Puppet::Pops::Types::PArrayType::DEFAULT.assignable?(value_type) # add the format given for the exact type string_formats = { Puppet::Pops::Types::PArrayType::DEFAULT => {'format' => string_formats }} elsif Puppet::Pops::Types::PHashType::DEFAULT.assignable?(value_type) # add the format given for the exact type string_formats = { Puppet::Pops::Types::PHashType::DEFAULT => {'format' => string_formats }} else # add the format given for the exact type string_formats = { value_type => string_formats } end end case string_formats when :default # do nothing, use default formats when Hash # Convert and validate user input string_formats = validate_input(string_formats) # Merge user given with defaults such that user options wins, merge is deep and format specific options = Format.merge_string_formats(DEFAULT_STRING_FORMATS, string_formats) else raise ArgumentError, "string conversion expects a Default value or a Hash of type to format mappings, got a '#{string_formats.class}'" end _convert(value_type, value, options, DEFAULT_INDENTATION) end # # A method only used for manual debugging as the default output of the formatting rules is # # very hard to read otherwise. # # # # @api private # def dump_string_formats(f, indent = 1) # return f.to_s unless f.is_a?(Hash) # "{#{f.map {|k,v| "#{k.to_s} => #{dump_string_formats(v,indent+1)}"}.join(",\n#{' '*indent} ")}}" # end def _convert(val_type, value, format_map, indentation) @@string_visitor.visit_this_3(self, val_type, value, format_map, indentation) end private :_convert def validate_input(fmt) return nil if fmt.nil? unless fmt.is_a?(Hash) raise ArgumentError, "expected a hash with type to format mappings, got instance of '#{fmt.class}'" end fmt.reduce({}) do | result, entry| key, value = entry unless key.is_a?(Types::PAnyType) raise ArgumentError, "top level keys in the format hash must be data types, got instance of '#{key.class}'" end if value.is_a?(Hash) result[key] = validate_container_input(value) else result[key] = Format.new(value) end result end end private :validate_input FMT_KEYS = %w{separator separator2 format string_formats}.freeze def validate_container_input(fmt) if (fmt.keys - FMT_KEYS).size > 0 raise ArgumentError, "only #{FMT_KEYS.map {|k| "'#{k}'"}.join(', ')} are allowed in a container format, got #{fmt}" end result = Format.new(fmt['format']) result.separator = fmt['separator'] result.separator2 = fmt['separator2'] result.container_string_formats = validate_input(fmt['string_formats']) result end private :validate_container_input def string_PObjectType(val_type, val, format_map, indentation) f = get_format(val_type, format_map) case f.format when :p fmt = TypeFormatter.singleton indentation = indentation.indenting(f.alt? || indentation.is_indenting?) fmt = fmt.indented(indentation.level, 2) if indentation.is_indenting? fmt.string(val) when :s val.to_s when :q val.inspect else raise FormatError.new('Object', f.format, 'spq') end end def string_PObjectTypeExtension(val_type, val, format_map, indentation) string_PObjectType(val_type.base_type, val, format_map, indentation) end def string_PRuntimeType(val_type, val, format_map, indent) # Before giving up on this, and use a string representation of the unknown # object, a check is made to see if the object can present itself as # a hash or an array. If it can, then that representation is used instead. if val.is_a?(Hash) hash = val.to_hash # Ensure that the returned value isn't derived from Hash return string_PHashType(val_type, hash, format_map, indent) if hash.instance_of?(Hash) elsif val.is_a?(Array) array = val.to_a # Ensure that the returned value isn't derived from Array return string_PArrayType(val_type, array, format_map, indent) if array.instance_of?(Array) end f = get_format(val_type, format_map) case f.format when :s val.to_s when :p puppet_quote(val.to_s) when :q val.inspect else raise FormatError.new('Runtime', f.format, 'spq') end end # Basically string_PAnyType converts the value to a String and then formats it according # to the resulting type # # @api private def string_PAnyType(val_type, val, format_map, _) f = get_format(val_type, format_map) Kernel.format(f.orig_fmt, val) end def string_PDefaultType(val_type, val, format_map, _) f = get_format(val_type, format_map) apply_string_flags(f, case f.format when :d, :s, :p f.alt? ? '"default"' : 'default' when :D f.alt? ? '"Default"' : 'Default' else raise FormatError.new('Default', f.format, 'dDsp') end) end # @api private def string_PUndefType(val_type, val, format_map, _) f = get_format(val_type, format_map) apply_string_flags(f, case f.format when :n f.alt? ? 'null' : 'nil' when :u f.alt? ? 'undefined' : 'undef' when :d, :x, :X, :o, :b, :B, :e, :E, :f, :g, :G, :a, :A 'NaN' when :v 'n/a' when :V 'N/A' when :s f.alt? ? '""' : '' when :p f.alt? ? '"undef"' : 'undef' else raise FormatError.new('Undef', f.format, 'nudxXobBeEfgGaAvVsp') end) end # @api private def string_PBooleanType(val_type, val, format_map, indentation) f = get_format(val_type, format_map) case f.format when :t # 'true'/'false' or 't'/'f' if in alt mode str_bool = val.to_s apply_string_flags(f, f.alt? ? str_bool[0] : str_bool) when :T # 'True'/'False' or 'T'/'F' if in alt mode str_bool = val.to_s.capitalize apply_string_flags(f, f.alt? ? str_bool[0] : str_bool) when :y # 'yes'/'no' or 'y'/'n' if in alt mode str_bool = val ? 'yes' : 'no' apply_string_flags(f, f.alt? ? str_bool[0] : str_bool) when :Y # 'Yes'/'No' or 'Y'/'N' if in alt mode str_bool = val ? 'Yes' : 'No' apply_string_flags(f, f.alt? ? str_bool[0] : str_bool) when :d, :x, :X, :o, :b, :B # Boolean in numeric form, formated by integer rule numeric_bool = val ? 1 : 0 string_formats = { Puppet::Pops::Types::PIntegerType::DEFAULT => f} _convert(TypeCalculator.infer_set(numeric_bool), numeric_bool, string_formats, indentation) when :e, :E, :f, :g, :G, :a, :A # Boolean in numeric form, formated by float rule numeric_bool = val ? 1.0 : 0.0 string_formats = { Puppet::Pops::Types::PFloatType::DEFAULT => f} _convert(TypeCalculator.infer_set(numeric_bool), numeric_bool, string_formats, indentation) when :s apply_string_flags(f, val.to_s) when :p apply_string_flags(f, val.inspect) else raise FormatError.new('Boolean', f.format, 'tTyYdxXobBeEfgGaAsp') end end # Performs post-processing of literals to apply width and precision flags def apply_string_flags(f, literal_str) if f.left || f.width || f.prec fmt = '%' fmt << '-' if f.left fmt << f.width.to_s if f.width fmt << '.' << f.prec.to_s if f.prec fmt << 's' Kernel.format(fmt, literal_str) else literal_str end end private :apply_string_flags # @api private def string_PIntegerType(val_type, val, format_map, _) f = get_format(val_type, format_map) case f.format when :d, :x, :X, :o, :b, :B, :p Kernel.format(f.orig_fmt, val) when :e, :E, :f, :g, :G, :a, :A Kernel.format(f.orig_fmt, val.to_f) when :c char = [val].pack("U") char = f.alt? ? "\"#{char}\"" : char Kernel.format(f.orig_fmt.gsub('c','s'), char) when :s fmt = f.alt? ? 'p' : 's' int_str = Kernel.format('%d', val) Kernel.format(f.orig_fmt.gsub('s', fmt), int_str) else raise FormatError.new('Integer', f.format, 'dxXobBeEfgGaAspc') end end # @api private def string_PFloatType(val_type, val, format_map, _) f = get_format(val_type, format_map) case f.format when :d, :x, :X, :o, :b, :B Kernel.format(f.orig_fmt, val.to_i) when :e, :E, :f, :g, :G, :a, :A, :p Kernel.format(f.orig_fmt, val) when :s float_str = f.alt? ? "\"#{Kernel.format('%p', val)}\"" : Kernel.format('%p', val) Kernel.format(f.orig_fmt, float_str) else raise FormatError.new('Float', f.format, 'dxXobBeEfgGaAsp') end end # @api private def string_PBinaryType(val_type, val, format_map, _) f = get_format(val_type, format_map) substitute = f.alt? ? 'p' : 's' case f.format when :s val_to_convert = val.binary_buffer if !f.alt? # Assume it is valid UTF-8 val_to_convert = val_to_convert.dup.force_encoding('UTF-8') # If it isn't unless val_to_convert.valid_encoding? # try to convert and fail with details about what is wrong val_to_convert = val.binary_buffer.encode('UTF-8') end else val_to_convert = val.binary_buffer end Kernel.format(f.orig_fmt.gsub('s', substitute), val_to_convert) when :p # width & precision applied to string, not the the name of the type "Binary(\"#{Kernel.format(f.orig_fmt.gsub('p', 's'), val.to_s)}\")" when :b Kernel.format(f.orig_fmt.gsub('b', substitute), val.relaxed_to_s) when :B Kernel.format(f.orig_fmt.gsub('B', substitute), val.to_s) when :u Kernel.format(f.orig_fmt.gsub('u', substitute), val.urlsafe_to_s) when :t # Output as the type without any data Kernel.format(f.orig_fmt.gsub('t', substitute), 'Binary') when :T # Output as the type without any data in all caps Kernel.format(f.orig_fmt.gsub('T', substitute), 'BINARY') else raise FormatError.new('Binary', f.format, 'bButTsp') end end # @api private def string_PStringType(val_type, val, format_map, _) f = get_format(val_type, format_map) case f.format when :s Kernel.format(f.orig_fmt, val) when :p apply_string_flags(f, puppet_quote(val)) when :c c_val = val.capitalize f.alt? ? apply_string_flags(f, puppet_quote(c_val)) : Kernel.format(f.orig_fmt.gsub('c', 's'), c_val) when :C c_val = val.split('::').map {|s| s.capitalize }.join('::') f.alt? ? apply_string_flags(f, puppet_quote(c_val)) : Kernel.format(f.orig_fmt.gsub('C', 's'), c_val) when :u c_val = val.upcase f.alt? ? apply_string_flags(f, puppet_quote(c_val)) : Kernel.format(f.orig_fmt.gsub('u', 's'), c_val) when :d c_val = val.downcase f.alt? ? apply_string_flags(f, puppet_quote(c_val)) : Kernel.format(f.orig_fmt.gsub('d', 's'), c_val) when :t # trim c_val = val.strip f.alt? ? apply_string_flags(f, puppet_quote(c_val)) : Kernel.format(f.orig_fmt.gsub('t', 's'), c_val) else raise FormatError.new('String', f.format, 'cCudspt') end end # Performs a '%p' formatting of the given _str_ such that the output conforms to Puppet syntax. An ascii string # without control characters, dollar, single-qoute, or backslash, will be quoted using single quotes. All other # strings will be quoted using double quotes. # # @param [String] str the string that should be formatted # @return [String] the formatted string # # @api public def puppet_quote(str) # Assume that the string can be single quoted bld = '\'' bld.force_encoding(str.encoding) escaped = false str.each_codepoint do |codepoint| # Control characters and non-ascii characters cannot be present in a single quoted string return puppet_double_quote(str) if codepoint < 0x20 if escaped bld << 0x5c << codepoint escaped = false else if codepoint == 0x27 bld << 0x5c << codepoint elsif codepoint == 0x5c escaped = true elsif codepoint <= 0x7f bld << codepoint else bld << [codepoint].pack('U') end end end # If string ended with a backslash, then that backslash must be escaped bld << 0x5c if escaped bld << '\'' bld end def puppet_double_quote(str) bld = '"' str.each_codepoint do |codepoint| case codepoint when 0x09 bld << '\\t' when 0x0a bld << '\\n' when 0x0d bld << '\\r' when 0x22 bld << '\\"' when 0x24 bld << '\\$' when 0x5c bld << '\\\\' else if codepoint < 0x20 bld << sprintf('\\u{%X}', codepoint) elsif codepoint <= 0x7f bld << codepoint else bld << [codepoint].pack('U') end end end bld << '"' bld end # @api private def string_PRegexpType(val_type, val, format_map, _) f = get_format(val_type, format_map) case f.format when :p str_regexp = PRegexpType.regexp_to_s_with_delimiters(val) f.orig_fmt == '%p' ? str_regexp : Kernel.format(f.orig_fmt.gsub('p', 's'), str_regexp) when :s str_regexp = PRegexpType.regexp_to_s(val) str_regexp = puppet_quote(str_regexp) if f.alt? f.orig_fmt == '%s' ? str_regexp : Kernel.format(f.orig_fmt, str_regexp) else raise FormatError.new('Regexp', f.format, 'sp') end end def string_PArrayType(val_type, val, format_map, indentation) format = get_format(val_type, format_map) sep = format.separator || DEFAULT_ARRAY_FORMAT.separator string_formats = format.container_string_formats || DEFAULT_CONTAINER_FORMATS delims = format.delimiter_pair(DEFAULT_ARRAY_DELIMITERS) # Make indentation active, if array is in alternative format, or if nested in indenting indentation = indentation.indenting(format.alt? || indentation.is_indenting?) case format.format when :a, :s, :p buf = '' if indentation.breaks? buf << "\n" buf << indentation.padding end buf << delims[0] # Make a first pass to format each element children_indentation = indentation.increase(format.alt?) # tell children they are expected to indent mapped = val.map do |v| if children_indentation.first? children_indentation = children_indentation.subsequent end val_t = TypeCalculator.infer_set(v) _convert(val_t, v, is_container?(val_t) ? format_map : string_formats, children_indentation) end # compute widest run in the array, skip nested arrays and hashes # then if size > width, set flag if a break on each element should be performed if format.alt? && format.width widest = val.each_with_index.reduce([0]) do | memo, v_i | # array or hash breaks if is_a_or_h?(v_i[0]) memo << 0 else memo[-1] += mapped[v_i[1]].length end memo end widest = widest.max sz_break = widest > (format.width || Float::INFINITY) else sz_break = false end # output each element with breaks and padding children_indentation = indentation.increase(format.alt?) val.each_with_index do |v, i| str_val = mapped[i] if children_indentation.first? children_indentation = children_indentation.subsequent # if breaking, indent first element by one if sz_break && !is_a_or_h?(v) buf << ' ' end else buf << sep # if break on each (and breaking will not occur because next is an array or hash) # or, if indenting, and previous was an array or hash, then break and continue on next line # indented. if (sz_break && !is_a_or_h?(v)) || (format.alt? && i > 0 && is_a_or_h?(val[i-1]) && !is_a_or_h?(v)) buf.rstrip! unless buf[-1] == "\n" buf << "\n" buf << children_indentation.padding end end # remove trailing space added by separator if followed by break buf.rstrip! if buf[-1] == ' ' && str_val[0] == "\n" buf << str_val end buf << delims[1] buf else raise FormatError.new('Array', format.format, 'asp') end end def is_a_or_h?(x) x.is_a?(Array) || x.is_a?(Hash) end def is_container?(t) case t when PArrayType, PHashType, PStructType, PTupleType, PObjectType true else false end end # @api private def string_PTupleType(val_type, val, format_map, indentation) string_PArrayType(val_type, val, format_map, indentation) end # @api private def string_PIteratorType(val_type, val, format_map, indentation) v = val.to_a _convert(TypeCalculator.infer_set(v), v, format_map, indentation) end # @api private def string_PHashType(val_type, val, format_map, indentation) format = get_format(val_type, format_map) sep = format.separator || DEFAULT_HASH_FORMAT.separator assoc = format.separator2 || DEFAULT_HASH_FORMAT.separator2 string_formats = format.container_string_formats || DEFAULT_CONTAINER_FORMATS delims = format.delimiter_pair(DEFAULT_HASH_DELIMITERS) if format.alt? sep = sep.rstrip unless sep[-1] == "\n" sep = "#{sep}\n" end cond_break = '' padding = '' case format.format when :a # Convert to array and use array rules array_hash = val.to_a _convert(TypeCalculator.infer_set(array_hash), array_hash, format_map, indentation) when :h, :s, :p indentation = indentation.indenting(format.alt? || indentation.is_indenting?) buf = '' if indentation.breaks? buf << "\n" buf << indentation.padding end children_indentation = indentation.increase if format.alt? cond_break = "\n" padding = children_indentation.padding end buf << delims[0] buf << cond_break # break after opening delimiter if pretty printing buf << val.map do |k,v| key_type = TypeCalculator.infer_set(k) val_type = TypeCalculator.infer_set(v) key = _convert(key_type, k, is_container?(key_type) ? format_map : string_formats, children_indentation) val = _convert(val_type, v, is_container?(val_type) ? format_map : string_formats, children_indentation) "#{padding}#{key}#{assoc}#{val}" end.join(sep) if format.alt? buf << cond_break buf << indentation.padding end buf << delims[1] buf else raise FormatError.new('Hash', format.format, 'hasp') end end # @api private def string_PStructType(val_type, val, format_map, indentation) string_PHashType(val_type, val, format_map, indentation) end # @api private def string_PTypeType(val_type, val, format_map, _) f = get_format(val_type, format_map) case f.format when :s str_val = f.alt? ? "\"#{val.to_s}\"" : val.to_s Kernel.format(f.orig_fmt, str_val) when :p Kernel.format(f.orig_fmt.gsub('p', 's'), val.to_s) else raise FormatError.new('Type', f.format, 'sp') end end # @api private def string_PURIType(val_type, val, format_map, indentation) f = get_format(val_type, format_map) case f.format when :p fmt = TypeFormatter.singleton indentation = indentation.indenting(f.alt? || indentation.is_indenting?) fmt = fmt.indented(indentation.level, 2) if indentation.is_indenting? fmt.string(val) when :s str_val = val.to_s Kernel.format(f.orig_fmt, f.alt? ? puppet_quote(str_val) : str_val) else raise FormatError.new('URI', f.format, 'sp') end end # Maps the inferred type of o to a formatting rule def get_format(val_t, format_options) fmt = format_options.find {|k,_| k.assignable?(val_t) } return fmt[1] unless fmt.nil? return Format.new("%s") end private :get_format end end end ���������������������puppet-5.5.10/lib/puppet/pops/types/type_factory.rb�������������������������������������������������0000644�0052762�0001160�00000042567�13417161721�022275� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Types # Helper module that makes creation of type objects simpler. # @api public # module TypeFactory @type_calculator = TypeCalculator.singleton # Produces the Integer type # @api public # def self.integer PIntegerType::DEFAULT end # Produces an Integer range type # @api public # def self.range(from, to) # optimize eq with symbol (faster when it is left) from = :default == from if from == 'default' to = :default if to == 'default' PIntegerType.new(from, to) end # Produces a Float range type # @api public # def self.float_range(from, to) # optimize eq with symbol (faster when it is left) from = Float(from) unless :default == from || from.nil? to = Float(to) unless :default == to || to.nil? PFloatType.new(from, to) end # Produces the Float type # @api public # def self.float PFloatType::DEFAULT end # Produces the Sensitive type # @api public # def self.sensitive(type = nil) PSensitiveType.new(type) end # Produces the Numeric type # @api public # def self.numeric PNumericType::DEFAULT end # Produces the Init type # @api public def self.init(*args) case args.size when 0 PInitType::DEFAULT when 1 type = args[0] type.nil? ? PInitType::DEFAULT : PInitType.new(type, EMPTY_ARRAY) else type = args.shift PInitType.new(type, args) end end # Produces the Iterable type # @api public # def self.iterable(elem_type = nil) elem_type.nil? ? PIterableType::DEFAULT : PIterableType.new(elem_type) end # Produces the Iterator type # @api public # def self.iterator(elem_type = nil) elem_type.nil? ? PIteratorType::DEFAULT : PIteratorType.new(elem_type) end # Produces a string representation of the type # @api public # def self.label(t) @type_calculator.string(t) end # Produces the String type based on nothing, a string value that becomes an exact match constraint, or a parameterized # Integer type that constraints the size. # # @api public # def self.string(size_type_or_value = nil, *deprecated_second_argument) if deprecated_second_argument.empty? size_type_or_value.nil? ? PStringType::DEFAULT : PStringType.new(size_type_or_value) else if Puppet[:strict] != :off #TRANSLATORS 'TypeFactory#string' is a class and method name and should not be translated message = _("Passing more than one argument to TypeFactory#string is deprecated") Puppet.warn_once('deprecations', "TypeFactory#string_multi_args", message) end deprecated_second_argument.size == 1 ? PStringType.new(deprecated_second_argument[0]) : PEnumType.new(*deprecated_second_argument) end end # Produces the Optional type, i.e. a short hand for Variant[T, Undef] # If the given 'optional_type' argument is a String, then it will be # converted into a String type that represents that string. # # @param optional_type [String,PAnyType,nil] the optional type # @return [POptionalType] the created type # # @api public # def self.optional(optional_type = nil) if optional_type.nil? POptionalType::DEFAULT else POptionalType.new(type_of(optional_type.is_a?(String) ? string(optional_type) : type_of(optional_type))) end end # Produces the Enum type, optionally with specific string values # @api public # def self.enum(*values) last = values.last case_insensitive = false if last == true || last == false case_insensitive = last values = values[0...-1] end PEnumType.new(values, case_insensitive) end # Produces the Variant type, optionally with the "one of" types # @api public # def self.variant(*types) PVariantType.maybe_create(types.map {|v| type_of(v) }) end # Produces the Struct type, either a non parameterized instance representing # all structs (i.e. all hashes) or a hash with entries where the key is # either a literal String, an Enum with one entry, or a String representing exactly one value. # The key type may also be wrapped in a NotUndef or an Optional. # # The value can be a ruby class, a String (interpreted as the name of a ruby class) or # a Type. # # @param hash [{String,PAnyType=>PAnyType}] key => value hash # @return [PStructType] the created Struct type # def self.struct(hash = {}) tc = @type_calculator elements = hash.map do |key_type, value_type| value_type = type_of(value_type) raise ArgumentError, 'Struct element value_type must be a Type' unless value_type.is_a?(PAnyType) # TODO: Should have stricter name rule if key_type.is_a?(String) raise ArgumentError, 'Struct element key cannot be an empty String' if key_type.empty? key_type = string(key_type) # Must make key optional if the value can be Undef key_type = optional(key_type) if tc.assignable?(value_type, PUndefType::DEFAULT) else # assert that the key type is one of String[1], NotUndef[String[1]] and Optional[String[1]] case key_type when PNotUndefType # We can loose the NotUndef wrapper here since String[1] isn't optional anyway key_type = key_type.type s = key_type when POptionalType s = key_type.optional_type when PStringType s = key_type when PEnumType s = key_type.values.size == 1 ? PStringType.new(key_type.values[0]) : nil else raise ArgumentError, "Illegal Struct member key type. Expected NotUndef, Optional, String, or Enum. Got: #{key_type.class.name}" end unless s.is_a?(PStringType) && !s.value.nil? raise ArgumentError, "Unable to extract a non-empty literal string from Struct member key type #{tc.string(key_type)}" end end PStructElement.new(key_type, value_type) end PStructType.new(elements) end # Produces an `Object` type from the given _hash_ that represents the features of the object # # @param hash [{String=>Object}] the hash of feature groups # @return [PObjectType] the created type # def self.object(hash = nil, loader = nil) hash.nil? || hash.empty? ? PObjectType::DEFAULT : PObjectType.new(hash, loader) end def self.type_set(hash = nil) hash.nil? || hash.empty? ? PTypeSetType::DEFAULT : PTypeSetType.new(hash) end def self.timestamp(*args) case args.size when 0 PTimestampType::DEFAULT else PTimestampType.new(*args) end end def self.timespan(*args) case args.size when 0 PTimespanType::DEFAULT else PTimespanType.new(*args) end end def self.tuple(types = [], size_type = nil) PTupleType.new(types.map {|elem| type_of(elem) }, size_type) end # Produces the Boolean type # @api public # def self.boolean(value = nil) value.nil? ? PBooleanType::DEFAULT : (value ? PBooleanType::TRUE : PBooleanType::FALSE) end # Produces the Any type # @api public # def self.any PAnyType::DEFAULT end # Produces the Regexp type # @param pattern [Regexp, String, nil] (nil) The regular expression object or # a regexp source string, or nil for bare type # @api public # def self.regexp(pattern = nil) pattern ? PRegexpType.new(pattern) : PRegexpType::DEFAULT end def self.pattern(*regular_expressions) patterns = regular_expressions.map do |re| case re when String re_t = PRegexpType.new(re) re_t.regexp # compile it to catch errors re_t when Regexp PRegexpType.new(re) when PRegexpType re when PPatternType re.patterns else raise ArgumentError, "Only String, Regexp, Pattern-Type, and Regexp-Type are allowed: got '#{re.class}" end end.flatten.uniq PPatternType.new(patterns) end # Produces the Scalar type # @api public # def self.scalar PScalarType::DEFAULT end # Produces the ScalarData type # @api public # def self.scalar_data PScalarDataType::DEFAULT end # Produces a CallableType matching all callables # @api public # def self.all_callables return PCallableType::DEFAULT end # Produces a Callable type with one signature without support for a block # Use #with_block, or #with_optional_block to add a block to the callable # If no parameters are given, the Callable will describe a signature # that does not accept parameters. To create a Callable that matches all callables # use {#all_callables}. # # The params is a list of types, where the three last entries may be # optionally followed by min, max count, and a Callable which is taken as the # block_type. # If neither min or max are specified the parameters must match exactly. # A min < params.size means that the difference are optional. # If max > params.size means that the last type repeats. # if max is :default, the max value is unbound (infinity). # # Params are given as a sequence of arguments to {#type_of}. # def self.callable(*params) if params.size == 2 && params[0].is_a?(Array) return_t = type_of(params[1]) params = params[0] else return_t = nil end last_callable = TypeCalculator.is_kind_of_callable?(params.last) block_t = last_callable ? params.pop : nil # compute a size_type for the signature based on the two last parameters if is_range_parameter?(params[-2]) && is_range_parameter?(params[-1]) size_type = range(params[-2], params[-1]) params = params[0, params.size - 2] elsif is_range_parameter?(params[-1]) size_type = range(params[-1], :default) params = params[0, params.size - 1] else size_type = nil end types = params.map {|p| type_of(p) } # If the specification requires types, and none were given, a Unit type is used if types.empty? && !size_type.nil? && size_type.range[1] > 0 types << PUnitType::DEFAULT end # create a signature tuple_t = tuple(types, size_type) PCallableType.new(tuple_t, block_t, return_t) end # Produces the abstract type Collection # @api public # def self.collection(size_type = nil) size_type.nil? ? PCollectionType::DEFAULT : PCollectionType.new(size_type) end # Produces the Data type # @api public # def self.data @data_t ||= TypeParser.singleton.parse('Data', Loaders.static_loader) end # Produces the RichData type # @api public # def self.rich_data @rich_data_t ||= TypeParser.singleton.parse('RichData', Loaders.static_loader) end # Produces the RichData type # @api public # def self.rich_data_key @rich_data_key_t ||= TypeParser.singleton.parse('RichDataKey', Loaders.static_loader) end # Creates an instance of the Undef type # @api public def self.undef PUndefType::DEFAULT end # Creates an instance of the Default type # @api public def self.default PDefaultType::DEFAULT end # Creates an instance of the Binary type # @api public def self.binary PBinaryType::DEFAULT end # Produces an instance of the abstract type PCatalogEntryType def self.catalog_entry PCatalogEntryType::DEFAULT end # Produces an instance of the SemVerRange type def self.sem_ver_range PSemVerRangeType::DEFAULT end # Produces an instance of the SemVer type def self.sem_ver(*ranges) ranges.empty? ? PSemVerType::DEFAULT : PSemVerType::new(ranges) end # Produces a PResourceType with a String type_name A PResourceType with a nil # or empty name is compatible with any other PResourceType. A PResourceType # with a given name is only compatible with a PResourceType with the same # name. (There is no resource-type subtyping in Puppet (yet)). # def self.resource(type_name = nil, title = nil) case type_name when PResourceType PResourceType.new(type_name.type_name, title) when String type_name = TypeFormatter.singleton.capitalize_segments(type_name) raise ArgumentError, "Illegal type name '#{type_name}'" unless type_name =~ Patterns::CLASSREF_EXT PResourceType.new(type_name, title) when nil raise ArgumentError, 'The type name cannot be nil, if title is given' unless title.nil? PResourceType::DEFAULT else raise ArgumentError, "The type name cannot be a #{type_name.class.name}" end end # Produces PClassType with a string class_name. A PClassType with # nil or empty name is compatible with any other PClassType. A # PClassType with a given name is only compatible with a PClassType # with the same name. # def self.host_class(class_name = nil) if class_name.nil? PClassType::DEFAULT else PClassType.new(class_name.sub(/^::/, '')) end end # Produces a type for Array[o] where o is either a type, or an instance for # which a type is inferred. # @api public # def self.array_of(o, size_type = nil) PArrayType.new(type_of(o), size_type) end # Produces a type for Hash[Scalar, o] where o is either a type, or an # instance for which a type is inferred. # @api public # def self.hash_of(value, key = scalar, size_type = nil) PHashType.new(type_of(key), type_of(value), size_type) end # Produces a type for Hash[key,value,size] # @param key_type [PAnyType] the key type # @param value_type [PAnyType] the value type # @param size_type [PIntegerType] # @return [PHashType] the created hash type # @api public # def self.hash_kv(key_type, value_type, size_type = nil) PHashType.new(key_type, value_type, size_type) end # Produces a type for Array[Any] # @api public # def self.array_of_any PArrayType::DEFAULT end # Produces a type for Array[Data] # @api public # def self.array_of_data @array_of_data_t = PArrayType.new(data) end # Produces a type for Hash[Any,Any] # @api public # def self.hash_of_any PHashType::DEFAULT end # Produces a type for Hash[String,Data] # @api public # def self.hash_of_data @hash_of_data_t = PHashType.new(string, data) end # Produces a type for NotUndef[T] # The given 'inst_type' can be a string in which case it will be converted into # the type String[inst_type]. # # @param inst_type [Type,String] the type to qualify # @return [PNotUndefType] the NotUndef type # # @api public # def self.not_undef(inst_type = nil) inst_type = string(inst_type) if inst_type.is_a?(String) PNotUndefType.new(inst_type) end # Produces a type for Type[T] # @api public # def self.type_type(inst_type = nil) inst_type.nil? ? PTypeType::DEFAULT : PTypeType.new(inst_type) end # Produces a type for Error # @api public # def self.error @error_t ||= TypeParser.singleton.parse('Error', Loaders.loaders.puppet_system_loader) end def self.task @task_t ||= TypeParser.singleton.parse('Task') end # Produces a type for URI[String or Hash] # @api public # def self.uri(string_uri_or_hash = nil) string_uri_or_hash.nil? ? PURIType::DEFAULT : PURIType.new(string_uri_or_hash) end # Produce a type corresponding to the class of given unless given is a # String, Class or a PAnyType. When a String is given this is taken as # a classname. # def self.type_of(o) if o.is_a?(Class) @type_calculator.type(o) elsif o.is_a?(PAnyType) o elsif o.is_a?(String) PRuntimeType.new(:ruby, o) else @type_calculator.infer_generic(o) end end # Produces a type for a class or infers a type for something that is not a # class # @note # To get the type for the class' class use `TypeCalculator.infer(c)` # # @overload ruby(o) # @param o [Class] produces the type corresponding to the class (e.g. # Integer becomes PIntegerType) # @overload ruby(o) # @param o [Object] produces the type corresponding to the instance class # (e.g. 3 becomes PIntegerType) # # @api public # def self.ruby(o) if o.is_a?(Class) @type_calculator.type(o) else PRuntimeType.new(:ruby, o.class.name) end end # Generic creator of a RuntimeType["ruby"] - allows creating the Ruby type # with nil name, or String name. Also see ruby(o) which performs inference, # or mapps a Ruby Class to its name. # def self.ruby_type(class_name = nil) PRuntimeType.new(:ruby, class_name) end # Generic creator of a RuntimeType - allows creating the type with nil or # String runtime_type_name. Also see ruby_type(o) and ruby(o). # def self.runtime(runtime=nil, runtime_type_name = nil) runtime = runtime.to_sym if runtime.is_a?(String) PRuntimeType.new(runtime, runtime_type_name) end # Returns the type alias for the given expression # @param name [String] the name of the unresolved type # @param expression [Model::Expression] an expression that will evaluate to a type # @return [PTypeAliasType] the type alias def self.type_alias(name = nil, expression = nil) name.nil? ? PTypeAliasType::DEFAULT : PTypeAliasType.new(name, expression) end # Returns the type that represents a type reference with a given name and optional # parameters. # @param type_string [String] the string form of the type # @return [PTypeReferenceType] the type reference def self.type_reference(type_string = nil) type_string == nil ? PTypeReferenceType::DEFAULT : PTypeReferenceType.new(type_string) end # Returns true if the given type t is of valid range parameter type (integer # or literal default). def self.is_range_parameter?(t) t.is_a?(Integer) || t == 'default' || :default == t end end end end �����������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/types/types.rb��������������������������������������������������������0000644�0052762�0001160�00000312542�13417161721�020722� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require_relative 'iterable' require_relative 'enumeration' require_relative 'recursion_guard' require_relative 'type_acceptor' require_relative 'type_asserter' require_relative 'type_assertion_error' require_relative 'type_conversion_error' require_relative 'type_formatter' require_relative 'type_calculator' require_relative 'type_factory' require_relative 'type_parser' require_relative 'class_loader' require_relative 'type_mismatch_describer' require_relative 'puppet_object' module Puppet::Pops module Types # The EMPTY_xxx declarations is for backward compatibility. They should not be explicitly referenced # @api private # @deprecated EMPTY_HASH = Puppet::Pops::EMPTY_HASH # @api private # @deprecated EMPTY_ARRAY = Puppet::Pops::EMPTY_ARRAY # @api private # @deprecated EMPTY_STRING = Puppet::Pops::EMPTY_STRING # The Types model is a model of Puppet Language types. # # The {TypeCalculator} should be used to answer questions about types. The {TypeFactory} or {TypeParser} should be used # to create an instance of a type whenever one is needed. # # The implementation of the Types model contains methods that are required for the type objects to behave as # expected when comparing them and using them as keys in hashes. (No other logic is, or should be included directly in # the model's classes). # # @api public # class TypedModelObject < Object include PuppetObject include Visitable include Adaptable def self._pcore_type @type end def self.create_ptype(loader, ir, parent_name, attributes_hash = EMPTY_HASH) @type = Pcore::create_object_type(loader, ir, self, "Pcore::#{simple_name}Type", "Pcore::#{parent_name}", attributes_hash) end def self.register_ptypes(loader, ir) types = [ Annotation.register_ptype(loader, ir), RubyMethod.register_ptype(loader, ir), ] Types.constants.each do |c| next if c == :PType || c == :PHostClassType cls = Types.const_get(c) next unless cls.is_a?(Class) && cls < self type = cls.register_ptype(loader, ir) types << type unless type.nil? end types.each { |type| type.resolve(loader) } end end # Base type for all types # @api public # class PAnyType < TypedModelObject def self.register_ptype(loader, ir) @type = Pcore::create_object_type(loader, ir, self, 'Pcore::AnyType', 'Any', EMPTY_HASH) end def self.create(*args) # NOTE! Important to use self::DEFAULT and not just DEFAULT since the latter yields PAnyType::DEFAULT args.empty? ? self::DEFAULT : new(*args) end # Accept a visitor that will be sent the message `visit`, once with `self` as the # argument. The visitor will then visit all types that this type contains. # def accept(visitor, guard) visitor.visit(self, guard) end # Checks if _o_ is a type that is assignable to this type. # If _o_ is a `Class` then it is first converted to a type. # If _o_ is a Variant, then it is considered assignable when all its types are assignable # # The check for assignable must be guarded against self recursion since `self`, the given type _o_, # or both, might be a `TypeAlias`. The initial caller of this method will typically never care # about this and hence pass only the first argument, but as soon as a check of a contained type # encounters a `TypeAlias`, then a `RecursionGuard` instance is created and passed on in all # subsequent calls. The recursion is allowed to continue until self recursion has been detected in # both `self` and in the given type. At that point the given type is considered to be assignable # to `self` since all checks up to that point were positive. # # @param o [Class,PAnyType] the class or type to test # @param guard [RecursionGuard] guard against recursion. Only used by internal calls # @return [Boolean] `true` when _o_ is assignable to this type # @api public def assignable?(o, guard = nil) case o when Class # Safe to call _assignable directly since a Class never is a Unit or Variant _assignable?(TypeCalculator.singleton.type(o), guard) when PUnitType true when PTypeAliasType # An alias may contain self recursive constructs. if o.self_recursion? guard ||= RecursionGuard.new if guard.add_that(o) == RecursionGuard::SELF_RECURSION_IN_BOTH # Recursion detected both in self and other. This means that other is assignable # to self. This point would not have been reached otherwise true else assignable?(o.resolved_type, guard) end else assignable?(o.resolved_type, guard) end when PVariantType # Assignable if all contained types are assignable, or if this is exactly Any return true if self.class == PAnyType # An empty variant may be assignable to NotUndef[T] if T is assignable to empty variant return _assignable?(o, guard) if is_a?(PNotUndefType) && o.types.empty? !o.types.empty? && o.types.all? { |vt| assignable?(vt, guard) } when POptionalType # Assignable if undef and contained type is assignable assignable?(PUndefType::DEFAULT) && (o.type.nil? || assignable?(o.type)) when PNotUndefType if !(o.type.nil? || o.type.assignable?(PUndefType::DEFAULT)) assignable?(o.type, guard) else _assignable?(o, guard) end else _assignable?(o, guard) end end # Returns `true` if this instance is a callable that accepts the given _args_type_ type # # @param args_type [PAnyType] the arguments to test # @param guard [RecursionGuard] guard against recursion. Only used by internal calls # @return [Boolean] `true` if this instance is a callable that accepts the given _args_ def callable?(args_type, guard = nil) args_type.is_a?(PAnyType) && kind_of_callable? && args_type.callable_args?(self, guard) end # Returns `true` if this instance is a callable that accepts the given _args_ # # @param args [Array] the arguments to test # @param block [Proc] block, or nil if not called with a block # @return [Boolean] `true` if this instance is a callable that accepts the given _args_ def callable_with?(args, block = nil) false end # Returns `true` if this instance is considered valid as arguments to the given `callable` # @param callable [PAnyType] the callable # @param guard [RecursionGuard] guard against recursion. Only used by internal calls # @return [Boolean] `true` if this instance is considered valid as arguments to the given `callable` # @api private def callable_args?(callable, guard) false end # Called from the `PTypeAliasType` when it detects self recursion. The default is to do nothing # but some self recursive constructs are illegal such as when a `PObjectType` somehow inherits itself # @param originator [PTypeAliasType] the starting point for the check # @raise Puppet::Error if an illegal self recursion is detected # @api private def check_self_recursion(originator) end # Generalizes value specific types. Types that are not value specific will return `self` otherwise # the generalized type is returned. # # @return [PAnyType] The generalized type # @api public def generalize # Applicable to all types that have no variables self end # Returns the loader that loaded this type. # @return [Loaders::Loader] the loader def loader Loaders.static_loader end # Normalizes the type. This does not change the characteristics of the type but it will remove duplicates # and constructs like NotUndef[T] where T is not assignable from Undef and change Variant[*T] where all # T are enums into an Enum. # # @param guard [RecursionGuard] guard against recursion. Only used by internal calls # @return [PAnyType] The iterable type that this type is assignable to or `nil` # @api public def normalize(guard = nil) self end # Called from the TypeParser once it has found a type using the Loader to enable that this type can # resolve internal type expressions using a loader. Presently, this method is a no-op for all types # except the {{PTypeAliasType}}. # # @param loader [Loader::Loader] loader to use # @return [PTypeAliasType] the receiver of the call, i.e. `self` # @api private def resolve(loader) self end # Responds `true` for all callables, variants of callables and unless _optional_ is # false, all optional callables. # @param optional [Boolean] # @param guard [RecursionGuard] guard against recursion. Only used by internal calls # @return [Boolean] `true`if this type is considered callable # @api private def kind_of_callable?(optional = true, guard = nil) false end # Returns `true` if an instance of this type is iterable, `false` otherwise # The method #iterable_type must produce a `PIterableType` instance when this # method returns `true` # # @param guard [RecursionGuard] guard against recursion. Only used by internal calls # @return [Boolean] flag to indicate if instances of this type is iterable. def iterable?(guard = nil) false end # Returns the `PIterableType` that this type should be assignable to, or `nil` if no such type exists. # A type that returns a `PIterableType` must respond `true` to `#iterable?`. # # @example # Any Collection[T] is assignable to an Iterable[T] # A String is assignable to an Iterable[String] iterating over the strings characters # An Integer is assignable to an Iterable[Integer] iterating over the 'times' enumerator # A Type[T] is assignable to an Iterable[Type[T]] if T is an Integer or Enum # # @param guard [RecursionGuard] guard against recursion. Only used by internal calls # @return [PIterableType,nil] The iterable type that this type is assignable to or `nil` # @api private def iterable_type(guard = nil) nil end def hash self.class.hash end # Returns true if the given argument _o_ is an instance of this type # @param guard [RecursionGuard] guard against recursion. Only used by internal calls # @return [Boolean] # @api public def instance?(o, guard = nil) true end # An object is considered to really be an instance of a type when something other than a # TypeAlias or a Variant responds true to a call to {#instance?}. # # @return [Integer] -1 = is not instance, 0 = recursion detected, 1 = is instance # @api private def really_instance?(o, guard = nil) instance?(o, guard) ? 1 : -1 end def eql?(o) self.class == o.class end def ==(o) eql?(o) end def simple_name self.class.simple_name end # Strips the class name from all module prefixes, the leading 'P' and the ending 'Type'. I.e. # an instance of PVariantType will return 'Variant' # @return [String] the simple name of this type def self.simple_name @simple_name ||= ( n = name n[n.rindex(DOUBLE_COLON)+3..n.size-5].freeze ) end def to_alias_expanded_s TypeFormatter.new.alias_expanded_string(self) end def to_s TypeFormatter.string(self) end # Returns the name of the type, without parameters # @return [String] the name of the type # @api public def name simple_name end def create(*args) Loaders.find_loader(nil).load(:function, 'new').call({}, self, *args) end # Create an instance of this type. # The default implementation will just dispatch the call to the class method with the # same name and pass `self` as the first argument. # # @return [Function] the created function # @raises ArgumentError # def new_function self.class.new_function(self) end # This default implementation of of a new_function raises an Argument Error. # Types for which creating a new instance is supported, should create and return # a Puppet Function class by using Puppet:Loaders.create_loaded_function(:new, loader) # and return that result. # # @param type [PAnyType] the type to create a new function for # @return [Function] the created function # @raises ArgumentError # def self.new_function(type) raise ArgumentError.new("Creation of new instance of type '#{type.to_s}' is not supported") end # Answers the question if instances of this type can represent themselves as a string that # can then be passed to the create method # # @return [Boolean] whether or not the instance has a canonical string representation def roundtrip_with_string? false end # The default instance of this type. Each type in the type system has this constant # declared. # DEFAULT = PAnyType.new protected # @api private def _assignable?(o, guard) o.is_a?(PAnyType) end # Produces the tuple entry at the given index given a tuple type, its from/to constraints on the last # type, and an index. # Produces nil if the index is out of bounds # from must be less than to, and from may not be less than 0 # # @api private # def tuple_entry_at(tuple_t, to, index) regular = (tuple_t.types.size - 1) if index < regular tuple_t.types[index] elsif index < regular + to # in the varargs part tuple_t.types[-1] else nil end end # Applies a transformation by sending the given _method_ and _method_args_ to each of the types of the given array # and collecting the results in a new array. If all transformation calls returned the type instance itself (i.e. no # transformation took place), then this method will return `self`. If a transformation did occur, then this method # will either return the transformed array or in case a block was given, the result of calling a given block with # the transformed array. # # @param types [Array<PAnyType>] the array of types to transform # @param method [Symbol] The method to call on each type # @param method_args [Object] The arguments to pass to the method, if any # @return [Object] self, the transformed array, or the result of calling a given block with the transformed array # @yieldparam altered_types [Array<PAnyType>] the altered type array # @api private def alter_type_array(types, method, *method_args) modified = false modified_types = types.map do |t| t_mod = t.send(method, *method_args) modified = !t.equal?(t_mod) unless modified t_mod end if modified block_given? ? yield(modified_types) : modified_types else self end end end # @abstract Encapsulates common behavior for a type that contains one type # @api public class PTypeWithContainedType < PAnyType def self.register_ptype(loader, ir) # Abstract type. It doesn't register anything end attr_reader :type def initialize(type) @type = type end def accept(visitor, guard) super @type.accept(visitor, guard) unless @type.nil? end def generalize if @type.nil? self.class::DEFAULT else ge_type = @type.generalize @type.equal?(ge_type) ? self : self.class.new(ge_type) end end def normalize(guard = nil) if @type.nil? self.class::DEFAULT else ne_type = @type.normalize(guard) @type.equal?(ne_type) ? self : self.class.new(ne_type) end end def hash self.class.hash ^ @type.hash end def eql?(o) self.class == o.class && @type == o.type end def resolve(loader) rtype = @type rtype = rtype.resolve(loader) unless rtype.nil? rtype.equal?(@type) ? self : self.class.new(rtype) end end # The type of types. # @api public # class PTypeType < PTypeWithContainedType def self.register_ptype(loader, ir) create_ptype(loader, ir, 'AnyType', 'type' => { KEY_TYPE => POptionalType.new(PTypeType::DEFAULT), KEY_VALUE => nil } ) end # Returns a new function that produces a Type instance # def self.new_function(type) @new_function ||= Puppet::Functions.create_loaded_function(:new_type, type.loader) do dispatch :from_string do param 'String[1]', :type_string end def from_string(type_string) TypeParser.singleton.parse(type_string, loader) end end end def instance?(o, guard = nil) if o.is_a?(PAnyType) type.nil? || type.assignable?(o, guard) elsif o.is_a?(Module) || o.is_a?(Puppet::Resource) || o.is_a?(Puppet::Parser::Resource) @type.nil? ? true : assignable?(TypeCalculator.infer(o)) else false end end def iterable?(guard = nil) case @type when PEnumType true when PIntegerType @type.finite_range? else false end end def iterable_type(guard = nil) # The types PIntegerType and PEnumType are Iterable case @type when PEnumType # @type describes the element type perfectly since the iteration is made over the # contained choices. PIterableType.new(@type) when PIntegerType # @type describes the element type perfectly since the iteration is made over the # specified range. @type.finite_range? ? PIterableType.new(@type) : nil else nil end end def eql?(o) self.class == o.class && @type == o.type end DEFAULT = PTypeType.new(nil) protected # @api private def _assignable?(o, guard) return false unless o.is_a?(PTypeType) return true if @type.nil? # wide enough to handle all types return false if o.type.nil? # wider than t @type.assignable?(o.type, guard) end end # For backward compatibility PType = PTypeType class PNotUndefType < PTypeWithContainedType def self.register_ptype(loader, ir) create_ptype(loader, ir, 'AnyType', 'type' => { KEY_TYPE => POptionalType.new(PTypeType::DEFAULT), KEY_VALUE => nil } ) end def initialize(type = nil) super(type.class == PAnyType ? nil : type) end def instance?(o, guard = nil) !(o.nil? || o == :undef) && (@type.nil? || @type.instance?(o, guard)) end def normalize(guard = nil) n = super if n.type.nil? n else if n.type.is_a?(POptionalType) # No point in having an optional in a NotUndef PNotUndefType.new(n.type.type).normalize elsif !n.type.assignable?(PUndefType::DEFAULT) # THe type is NotUndef anyway, so it can be stripped of n.type else n end end end def new_function # If only NotUndef, then use Unit's null converter if type.nil? PUnitType.new_function(self) else type.new_function end end DEFAULT = PNotUndefType.new protected # @api private def _assignable?(o, guard) o.is_a?(PAnyType) && !o.assignable?(PUndefType::DEFAULT, guard) && (@type.nil? || @type.assignable?(o, guard)) end end # @api public # class PUndefType < PAnyType def self.register_ptype(loader, ir) create_ptype(loader, ir, 'AnyType') end def instance?(o, guard = nil) o.nil? || :undef == o end # @api private def callable_args?(callable_t, guard) # if callable_t is Optional (or indeed PUndefType), this means that 'missing callable' is accepted callable_t.assignable?(DEFAULT, guard) end DEFAULT = PUndefType.new protected # @api private def _assignable?(o, guard) o.is_a?(PUndefType) end end # A type private to the type system that describes "ignored type" - i.e. "I am what you are" # @api private # class PUnitType < PAnyType def self.register_ptype(loader, ir) create_ptype(loader, ir, 'AnyType') end def instance?(o, guard = nil) true end # A "null" implementation - that simply returns the given argument def self.new_function(type) @new_function ||= Puppet::Functions.create_loaded_function(:new_unit, type.loader) do dispatch :from_args do param 'Any', :from end def from_args(from) from end end end DEFAULT = PUnitType.new def assignable?(o, guard=nil) true end protected # @api private def _assignable?(o, guard) true end end # @api public # class PDefaultType < PAnyType def self.register_ptype(loader, ir) create_ptype(loader, ir, 'AnyType') end def instance?(o, guard = nil) # Ensure that Symbol.== is called here instead of something unknown # that is implemented on o :default == o end DEFAULT = PDefaultType.new protected # @api private def _assignable?(o, guard) o.is_a?(PDefaultType) end end # Type that is a Scalar # @api public # class PScalarType < PAnyType def self.register_ptype(loader, ir) create_ptype(loader, ir, 'AnyType') end def instance?(o, guard = nil) if o.is_a?(String) || o.is_a?(Numeric) || o.is_a?(TrueClass) || o.is_a?(FalseClass) || o.is_a?(Regexp) true elsif o.instance_of?(Array) || o.instance_of?(Hash) || o.is_a?(PAnyType) || o.is_a?(NilClass) false else assignable?(TypeCalculator.infer(o)) end end def roundtrip_with_string? true end DEFAULT = PScalarType.new protected # @api private def _assignable?(o, guard) o.is_a?(PScalarType) || PStringType::DEFAULT.assignable?(o, guard) || PIntegerType::DEFAULT.assignable?(o, guard) || PFloatType::DEFAULT.assignable?(o, guard) || PBooleanType::DEFAULT.assignable?(o, guard) || PRegexpType::DEFAULT.assignable?(o, guard) || PSemVerType::DEFAULT.assignable?(o, guard) || PSemVerRangeType::DEFAULT.assignable?(o, guard) || PTimespanType::DEFAULT.assignable?(o, guard) || PTimestampType::DEFAULT.assignable?(o, guard) end end # Like Scalar but limited to Json Data. # @api public # class PScalarDataType < PScalarType def self.register_ptype(loader, ir) create_ptype(loader, ir, 'ScalarType') end def instance?(o, guard = nil) return o.is_a?(String) || o.is_a?(Integer) || o.is_a?(Float) || o.is_a?(TrueClass) || o.is_a?(FalseClass) end DEFAULT = PScalarDataType.new protected # @api private def _assignable?(o, guard) o.is_a?(PScalarDataType) || PStringType::DEFAULT.assignable?(o, guard) || PIntegerType::DEFAULT.assignable?(o, guard) || PFloatType::DEFAULT.assignable?(o, guard) || PBooleanType::DEFAULT.assignable?(o, guard) end end # A string type describing the set of strings having one of the given values # @api public # class PEnumType < PScalarDataType def self.register_ptype(loader, ir) create_ptype(loader, ir, 'ScalarDataType', 'values' => PArrayType.new(PStringType::NON_EMPTY), 'case_insensitive' => { 'type' => PBooleanType::DEFAULT, 'value' => false }) end attr_reader :values, :case_insensitive def initialize(values, case_insensitive = false) @values = values.uniq.sort.freeze @case_insensitive = case_insensitive end def case_insensitive? @case_insensitive end # Returns Enumerator if no block is given, otherwise, calls the given # block with each of the strings for this enum def each(&block) r = Iterable.on(self) block_given? ? r.each(&block) : r end def generalize # General form of an Enum is a String if @values.empty? PStringType::DEFAULT else range = @values.map(&:size).minmax PStringType.new(PIntegerType.new(range.min, range.max)) end end def iterable?(guard = nil) true end def iterable_type(guard = nil) # An instance of an Enum is a String PStringType::ITERABLE_TYPE end def hash @values.hash ^ @case_insensitive.hash end def eql?(o) self.class == o.class && @values == o.values && @case_insensitive == o.case_insensitive? end def instance?(o, guard = nil) if o.is_a?(String) @case_insensitive ? @values.any? { |p| p.casecmp(o) == 0 } : @values.any? { |p| p == o } else false end end DEFAULT = PEnumType.new(EMPTY_ARRAY) protected # @api private def _assignable?(o, guard) return true if self == o svalues = values if svalues.empty? return true if o.is_a?(PStringType) || o.is_a?(PEnumType) || o.is_a?(PPatternType) end case o when PStringType # if the contained string is found in the set of enums instance?(o.value, guard) when PEnumType !o.values.empty? && (case_insensitive? || !o.case_insensitive?) && o.values.all? { |s| instance?(s, guard) } else false end end end INTEGER_HEX = '(?:0[xX][0-9A-Fa-f]+)' INTEGER_OCT = '(?:0[0-7]+)' INTEGER_BIN = '(?:0[bB][01]+)' INTEGER_DEC = '(?:0|[1-9]\d*)' SIGN_PREFIX = '[+-]?\s*' OPTIONAL_FRACTION = '(?:\.\d+)?' OPTIONAL_EXPONENT = '(?:[eE]-?\d+)?' FLOAT_DEC = '(?:' + INTEGER_DEC + OPTIONAL_FRACTION + OPTIONAL_EXPONENT + ')' INTEGER_PATTERN = '\A' + SIGN_PREFIX + '(?:' + INTEGER_DEC + '|' + INTEGER_HEX + '|' + INTEGER_OCT + '|' + INTEGER_BIN + ')\z' FLOAT_PATTERN = '\A' + SIGN_PREFIX + '(?:' + FLOAT_DEC + '|' + INTEGER_HEX + '|' + INTEGER_OCT + '|' + INTEGER_BIN + ')\z' # @api public # class PNumericType < PScalarDataType def self.register_ptype(loader, ir) create_ptype(loader, ir, 'ScalarDataType', 'from' => { KEY_TYPE => POptionalType.new(PNumericType::DEFAULT), KEY_VALUE => nil }, 'to' => { KEY_TYPE => POptionalType.new(PNumericType::DEFAULT), KEY_VALUE => nil } ) end def self.new_function(type) @new_function ||= Puppet::Functions.create_loaded_function(:new_numeric, type.loader) do local_types do type "Convertible = Variant[Integer, Float, Boolean, Pattern[/#{FLOAT_PATTERN}/], Timespan, Timestamp]" type 'NamedArgs = Struct[{from => Convertible, Optional[abs] => Boolean}]' end dispatch :from_args do param 'Convertible', :from optional_param 'Boolean', :abs end dispatch :from_hash do param 'NamedArgs', :hash_args end argument_mismatch :on_error do param 'Any', :from optional_param 'Boolean', :abs end def from_args(from, abs = false) result = from_convertible(from) abs ? result.abs : result end def from_hash(args_hash) from_args(args_hash['from'], args_hash['abs'] || false) end def from_convertible(from) case from when Float from when Integer from when Time::TimeData from.to_f when TrueClass 1 when FalseClass 0 else begin if from[0] == '0' second_char = (from[1] || '').downcase if second_char == 'b' || second_char == 'x' # use built in conversion return Integer(from) end end Puppet::Pops::Utils.to_n(from) rescue TypeError => e raise TypeConversionError.new(e.message) rescue ArgumentError => e raise TypeConversionError.new(e.message) end end end def on_error(from, abs = false) if from.is_a?(String) _("The string '%{str}' cannot be converted to Numeric") % { str: from } else t = TypeCalculator.singleton.infer(from).generalize _("Value of type %{type} cannot be converted to Numeric") % { type: t } end end end end def initialize(from, to = Float::INFINITY) from = -Float::INFINITY if from.nil? || from == :default to = Float::INFINITY if to.nil? || to == :default raise ArgumentError, "'from' must be less or equal to 'to'. Got (#{from}, #{to}" if from > to @from = from @to = to end # Checks if this numeric range intersects with another # # @param o [PNumericType] the range to compare with # @return [Boolean] `true` if this range intersects with the other range # @api public def intersect?(o) self.class == o.class && !(@to < o.numeric_from || o.numeric_to < @from) end # Returns the lower bound of the numeric range or `nil` if no lower bound is set. # @return [Float,Integer] def from @from == -Float::INFINITY ? nil : @from end # Returns the upper bound of the numeric range or `nil` if no upper bound is set. # @return [Float,Integer] def to @to == Float::INFINITY ? nil : @to end # Same as #from but will return `-Float::Infinity` instead of `nil` if no lower bound is set. # @return [Float,Integer] def numeric_from @from end # Same as #to but will return `Float::Infinity` instead of `nil` if no lower bound is set. # @return [Float,Integer] def numeric_to @to end def hash @from.hash ^ @to.hash end def eql?(o) self.class == o.class && @from == o.numeric_from && @to == o.numeric_to end def instance?(o, guard = nil) (o.is_a?(Float) || o.is_a?(Integer)) && o >= @from && o <= @to end def unbounded? @from == -Float::INFINITY && @to == Float::INFINITY end protected # @api_private def _assignable?(o, guard) return false unless o.is_a?(self.class) # If o min and max are within the range of t @from <= o.numeric_from && @to >= o.numeric_to end DEFAULT = PNumericType.new(-Float::INFINITY) end # @api public # class PIntegerType < PNumericType def self.register_ptype(loader, ir) create_ptype(loader, ir, 'NumericType') end # Will respond `true` for any range that is bounded at both ends. # # @return [Boolean] `true` if the type describes a finite range. def finite_range? @from != -Float::INFINITY && @to != Float::INFINITY end def generalize DEFAULT end def instance?(o, guard = nil) o.is_a?(Integer) && o >= numeric_from && o <= numeric_to end # Checks if this range is adjacent to the given range # # @param o [PIntegerType] the range to compare with # @return [Boolean] `true` if this range is adjacent to the other range # @api public def adjacent?(o) o.is_a?(PIntegerType) && (@to + 1 == o.from || o.to + 1 == @from) end # Concatenates this range with another range provided that the ranges intersect or # are adjacent. When that's not the case, this method will return `nil` # # @param o [PIntegerType] the range to concatenate with this range # @return [PIntegerType,nil] the concatenated range or `nil` when the ranges were apart # @api public def merge(o) if intersect?(o) || adjacent?(o) min = @from <= o.numeric_from ? @from : o.numeric_from max = @to >= o.numeric_to ? @to : o.numeric_to PIntegerType.new(min, max) else nil end end def iterable?(guard = nil) true end def iterable_type(guard = nil) # It's unknown if the iterable will be a range (min, max) or a "times" (0, max) PIterableType.new(PIntegerType::DEFAULT) end # Returns Float.Infinity if one end of the range is unbound def size return Float::INFINITY if @from == -Float::INFINITY || @to == Float::INFINITY 1+(to-from).abs end # Returns the range as an array ordered so the smaller number is always first. # The number may be Infinity or -Infinity. def range [@from, @to] end # Returns Enumerator if no block is given # Returns nil if size is infinity (does not yield) def each(&block) r = Iterable.on(self) block_given? ? r.each(&block) : r end # Returns a range where both to and from are positive numbers. Negative # numbers are converted to zero # @return [PIntegerType] a positive range def to_size @from >= 0 ? self : PIntegerType.new(0, @to < 0 ? 0 : @to) end def new_function @@new_function ||= Puppet::Functions.create_loaded_function(:new, loader) do local_types do type 'Radix = Variant[Default, Integer[2,2], Integer[8,8], Integer[10,10], Integer[16,16]]' type "Convertible = Variant[Numeric, Boolean, Pattern[/#{INTEGER_PATTERN}/], Timespan, Timestamp]" type 'NamedArgs = Struct[{from => Convertible, Optional[radix] => Radix, Optional[abs] => Boolean}]' end dispatch :from_args do param 'Convertible', :from optional_param 'Radix', :radix optional_param 'Boolean', :abs end dispatch :from_hash do param 'NamedArgs', :hash_args end argument_mismatch :on_error_hash do param 'Hash', :hash_args end argument_mismatch :on_error do param 'Any', :from optional_param 'Integer', :radix optional_param 'Boolean', :abs end def from_args(from, radix = :default, abs = false) result = from_convertible(from, radix) abs ? result.abs : result end def from_hash(args_hash) from_args(args_hash['from'], args_hash['radix'] || :default, args_hash['abs'] || false) end def from_convertible(from, radix) case from when Float, Time::TimeData from.to_i when Integer from when TrueClass 1 when FalseClass 0 else begin radix == :default ? Integer(from) : Integer(from, radix) rescue TypeError => e raise TypeConversionError.new(e.message) rescue ArgumentError => e # Test for special case where there is whitespace between sign and number match = Patterns::WS_BETWEEN_SIGN_AND_NUMBER.match(from) if match begin # Try again, this time with whitespace removed return from_args(match[1] + match[2], radix) rescue TypeConversionError # Ignored to retain original error end end raise TypeConversionError.new(e.message) end end end def on_error_hash(args_hash) if args_hash.include?('from') from = args_hash['from'] return on_error(from) unless loader.load(:type, 'convertible').instance?(from) end radix = args_hash['radix'] assert_radix(radix) unless radix.nil? || radix == :default TypeAsserter.assert_instance_of('Integer.new', loader.load(:type, 'namedargs'), args_hash) end def on_error(from, radix = :default, abs = nil) assert_radix(radix) unless radix == :default if from.is_a?(String) _("The string '%{str}' cannot be converted to Integer") % { str: from } else t = TypeCalculator.singleton.infer(from).generalize _("Value of type %{type} cannot be converted to Integer") % { type: t } end end def assert_radix(radix) case radix when 2, 8, 10, 16 else raise ArgumentError.new(_("Illegal radix: %{radix}, expected 2, 8, 10, 16, or default") % { radix: radix }) end radix end end end DEFAULT = PIntegerType.new(-Float::INFINITY) end # @api public # class PFloatType < PNumericType def self.register_ptype(loader, ir) create_ptype(loader, ir, 'NumericType') end def generalize DEFAULT end def instance?(o, guard = nil) o.is_a?(Float) && o >= numeric_from && o <= numeric_to end # Concatenates this range with another range provided that the ranges intersect. When that's not the case, this # method will return `nil` # # @param o [PFloatType] the range to concatenate with this range # @return [PFloatType,nil] the concatenated range or `nil` when the ranges were apart # @api public def merge(o) if intersect?(o) min = @from <= o.from ? @from : o.from max = @to >= o.to ? @to : o.to PFloatType.new(min, max) else nil end end # Returns a new function that produces a Float value # def self.new_function(type) @new_function ||= Puppet::Functions.create_loaded_function(:new_float, type.loader) do local_types do type "Convertible = Variant[Numeric, Boolean, Pattern[/#{FLOAT_PATTERN}/], Timespan, Timestamp]" type 'NamedArgs = Struct[{from => Convertible, Optional[abs] => Boolean}]' end dispatch :from_args do param 'Convertible', :from optional_param 'Boolean', :abs end dispatch :from_hash do param 'NamedArgs', :hash_args end argument_mismatch :on_error do param 'Any', :from optional_param 'Boolean', :abs end def from_args(from, abs = false) result = from_convertible(from) abs ? result.abs : result end def from_hash(args_hash) from_args(args_hash['from'], args_hash['abs'] || false) end def from_convertible(from) case from when Float from when Integer Float(from) when Time::TimeData from.to_f when TrueClass 1.0 when FalseClass 0.0 else begin # support a binary as float if from[0] == '0' && from[1].downcase == 'b' from = Integer(from) end Float(from) rescue TypeError => e raise TypeConversionError.new(e.message) rescue ArgumentError => e # Test for special case where there is whitespace between sign and number match = Patterns::WS_BETWEEN_SIGN_AND_NUMBER.match(from) if match begin # Try again, this time with whitespace removed return from_args(match[1] + match[2]) rescue TypeConversionError # Ignored to retain original error end end raise TypeConversionError.new(e.message) end end end def on_error(from, _ = false) if from.is_a?(String) _("The string '%{str}' cannot be converted to Float") % { str: from } else t = TypeCalculator.singleton.infer(from).generalize _("Value of type %{type} cannot be converted to Float") % { type: t } end end end end DEFAULT = PFloatType.new(-Float::INFINITY) end # @api public # class PCollectionType < PAnyType def self.register_ptype(loader, ir) create_ptype(loader, ir, 'AnyType', 'size_type' => { KEY_TYPE => POptionalType.new(PTypeType.new(PIntegerType::DEFAULT)), KEY_VALUE => nil } ) end attr_reader :size_type def initialize(size_type) @size_type = size_type.nil? ? nil : size_type.to_size end def accept(visitor, guard) super @size_type.accept(visitor, guard) unless @size_type.nil? end def generalize DEFAULT end def normalize(guard = nil) DEFAULT end def instance?(o, guard = nil) # The inferred type of a class derived from Array or Hash is either Runtime or Object. It's not assignable to the Collection type. if o.instance_of?(Array) || o.instance_of?(Hash) @size_type.nil? || @size_type.instance?(o.size) else false end end # Returns an array with from (min) size to (max) size def size_range (@size_type || DEFAULT_SIZE).range end def has_empty_range? from, to = size_range from == 0 && to == 0 end def hash @size_type.hash end def iterable?(guard = nil) true end def eql?(o) self.class == o.class && @size_type == o.size_type end DEFAULT_SIZE = PIntegerType.new(0) ZERO_SIZE = PIntegerType.new(0, 0) NOT_EMPTY_SIZE = PIntegerType.new(1) DEFAULT = PCollectionType.new(nil) protected # @api private # def _assignable?(o, guard) case o when PCollectionType (@size_type || DEFAULT_SIZE).assignable?(o.size_type || DEFAULT_SIZE, guard) when PTupleType # compute the tuple's min/max size, and check if that size matches size_s = size_type || DEFAULT_SIZE size_o = o.size_type if size_o.nil? type_count = o.types.size size_o = PIntegerType.new(type_count, type_count) end size_s.assignable?(size_o) when PStructType from = to = o.elements.size (@size_type || DEFAULT_SIZE).assignable?(PIntegerType.new(from, to), guard) else false end end end class PIterableType < PTypeWithContainedType def self.register_ptype(loader, ir) create_ptype(loader, ir, 'AnyType', 'type' => { KEY_TYPE => POptionalType.new(PTypeType::DEFAULT), KEY_VALUE => nil } ) end def element_type @type end def instance?(o, guard = nil) if @type.nil? || @type.assignable?(PAnyType::DEFAULT, guard) # Any element_type will do case o when Iterable, String, Hash, Array, Range, PEnumType true when Integer o >= 0 when PIntegerType o.finite_range? when PTypeAliasType instance?(o.resolved_type, guard) else false end else assignable?(TypeCalculator.infer(o), guard) end end def iterable?(guard = nil) true end def iterable_type(guard = nil) self end DEFAULT = PIterableType.new(nil) protected # @api private def _assignable?(o, guard) if @type.nil? || @type.assignable?(PAnyType::DEFAULT, guard) # Don't request the iterable_type. Since this Iterable accepts Any element, it is enough that o is iterable. o.iterable? else o = o.iterable_type o.nil? || o.element_type.nil? ? false : @type.assignable?(o.element_type, guard) end end end # @api public # class PIteratorType < PTypeWithContainedType def self.register_ptype(loader, ir) create_ptype(loader, ir, 'AnyType', 'type' => { KEY_TYPE => POptionalType.new(PTypeType::DEFAULT), KEY_VALUE => nil } ) end def element_type @type end def instance?(o, guard = nil) o.is_a?(Iterable) && (@type.nil? || @type.assignable?(o.element_type, guard)) end def iterable?(guard = nil) true end def iterable_type(guard = nil) @type.nil? ? PIterableType::DEFAULT : PIterableType.new(@type) end DEFAULT = PIteratorType.new(nil) protected # @api private def _assignable?(o, guard) o.is_a?(PIteratorType) && (@type.nil? || @type.assignable?(o.element_type, guard)) end end # @api public # class PStringType < PScalarDataType def self.register_ptype(loader, ir) create_ptype(loader, ir, 'ScalarDataType', 'size_type_or_value' => { KEY_TYPE => POptionalType.new(PVariantType.new([PStringType::DEFAULT, PTypeType.new(PIntegerType::DEFAULT)])), KEY_VALUE => nil }) end attr_reader :size_type_or_value def initialize(size_type_or_value, deprecated_multi_args = EMPTY_ARRAY) unless deprecated_multi_args.empty? if Puppet[:strict] != :off #TRANSLATORS 'PStringType#initialize' is a class and method name and should not be translated Puppet.warn_once('deprecations', "PStringType#initialize_multi_args", _("Passing more than one argument to PStringType#initialize is deprecated")) end size_type_or_value = deprecated_multi_args[0] end @size_type_or_value = size_type_or_value.is_a?(PIntegerType) ? size_type_or_value.to_size : size_type_or_value end def accept(visitor, guard) super @size_type_or_value.accept(visitor, guard) if @size_type_or_value.is_a?(PIntegerType) end def generalize DEFAULT end def hash @size_type_or_value.hash end def iterable?(guard = nil) true end def iterable_type(guard = nil) ITERABLE_TYPE end def eql?(o) self.class == o.class && @size_type_or_value == o.size_type_or_value end def instance?(o, guard = nil) # true if size compliant if o.is_a?(String) if @size_type_or_value.is_a?(PIntegerType) @size_type_or_value.instance?(o.size, guard) else @size_type_or_value.nil? ? true : o == value end else false end end def value @size_type_or_value.is_a?(PIntegerType) ? nil : @size_type_or_value end # @deprecated # @api private def values if Puppet[:strict] != :off #TRANSLATORS 'PStringType#values' and '#value' are classes and method names and should not be translated Puppet.warn_once('deprecations', "PStringType#values", _("Method PStringType#values is deprecated. Use #value instead")) end @value.is_a?(String) ? [@value] : EMPTY_ARRAY end def size_type @size_type_or_value.is_a?(PIntegerType) ? @size_type_or_value : nil end def derived_size_type if @size_type_or_value.is_a?(PIntegerType) @size_type_or_value elsif @size_type_or_value.is_a?(String) sz = @size_type_or_value.size PIntegerType.new(sz, sz) else PCollectionType::DEFAULT_SIZE end end def self.new_function(type) @new_function ||= Puppet::Functions.create_loaded_function(:new_string, type.loader) do local_types do type "Format = Pattern[/#{StringConverter::Format::FMT_PATTERN_STR}/]" type 'ContainerFormat = Struct[{ Optional[format] => Format, Optional[separator] => String, Optional[separator2] => String, Optional[string_formats] => Hash[Type, Format] }]' type 'TypeMap = Hash[Type, Variant[Format, ContainerFormat]]' type 'Convertible = Any' type 'Formats = Variant[Default, String[1], TypeMap]' end dispatch :from_args do param 'Convertible', :from optional_param 'Formats', :string_formats end def from_args(from, formats = :default) StringConverter.singleton.convert(from, formats) end end end DEFAULT = PStringType.new(nil) NON_EMPTY = PStringType.new(PCollectionType::NOT_EMPTY_SIZE) # Iterates over each character of the string ITERABLE_TYPE = PIterableType.new(PStringType.new(PIntegerType.new(1,1))) protected # @api private def _assignable?(o, guard) if @size_type_or_value.is_a?(PIntegerType) # A general string is assignable by any other string or pattern restricted string # if the string has a size constraint it does not match since there is no reasonable way # to compute the min/max length a pattern will match. For enum, it is possible to test that # each enumerator value is within range case o when PStringType @size_type_or_value.assignable?(o.derived_size_type, guard) when PEnumType if o.values.empty? # enum represents all enums, and thus all strings, a sized constrained string can thus not # be assigned any enum (unless it is max size). @size_type_or_value.assignable?(PCollectionType::DEFAULT_SIZE, guard) else # true if all enum values are within range orange = o.values.map(&:size).minmax srange = @size_type_or_value.range # If o min and max are within the range of t srange[0] <= orange[0] && srange[1] >= orange[1] end when PPatternType # true if size constraint is at least 0 to +Infinity (which is the same as the default) @size_type_or_value.assignable?(PCollectionType::DEFAULT_SIZE, guard) else # no other type matches string false end else case o when PStringType # Must match exactly when value is a string @size_type_or_value.nil? || @size_type_or_value == o.size_type_or_value when PEnumType @size_type_or_value.nil? ? true : o.values.size == 1 && !o.case_insensitive? && o.values[0] when PPatternType @size_type_or_value.nil? else # All others are false, since no other type describes the same set of specific strings false end end end end # @api public # class PRegexpType < PScalarType def self.register_ptype(loader, ir) create_ptype(loader, ir, 'ScalarType', 'pattern' => { KEY_TYPE => PVariantType.new([PUndefType::DEFAULT, PStringType::DEFAULT, PRegexpType::DEFAULT]), KEY_VALUE => nil }) end # Returns a new function that produces a Regexp instance # def self.new_function(type) @new_function ||= Puppet::Functions.create_loaded_function(:new_float, type.loader) do dispatch :from_string do param 'String', :pattern end def from_string(pattern) Regexp.new(pattern) end end end attr_reader :pattern # @param regexp [Regexp] the regular expression # @return [String] the Regexp as a slash delimited string with slashes escaped def self.regexp_to_s_with_delimiters(regexp) regexp.options == 0 ? regexp.inspect : "/#{regexp.to_s}/" end # @param regexp [Regexp] the regular expression # @return [String] the Regexp as a string without escaped slash def self.regexp_to_s(regexp) # Rubies < 2.0.0 retains escaped delimiters in the source string. @source_retains_escaped_slash ||= Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('2.0.0') source = regexp.source if @source_retains_escaped_slash && source.include?('\\') # Restore corrupt string in rubies <2.0.0, i.e. turn '\/' into '/' but # don't touch valid escapes such as '\s', '\{' etc. escaped = false bld = '' source.each_codepoint do |codepoint| if escaped bld << 0x5c unless codepoint == 0x2f # '/' bld << codepoint escaped = false elsif codepoint == 0x5c # '\' escaped = true elsif codepoint <= 0x7f bld << codepoint else bld << [codepoint].pack('U') end end source = bld end append_flags_group(source, regexp.options) end def self.append_flags_group(rx_string, options) if options == 0 rx_string else bld = '(?' bld << 'i' if (options & Regexp::IGNORECASE) != 0 bld << 'm' if (options & Regexp::MULTILINE) != 0 bld << 'x' if (options & Regexp::EXTENDED) != 0 unless options == (Regexp::IGNORECASE | Regexp::MULTILINE | Regexp::EXTENDED) bld << '-' bld << 'i' if (options & Regexp::IGNORECASE) == 0 bld << 'm' if (options & Regexp::MULTILINE) == 0 bld << 'x' if (options & Regexp::EXTENDED) == 0 end bld << ':' << rx_string << ')' bld.freeze end end def initialize(pattern) if pattern.is_a?(Regexp) @regexp = pattern @pattern = PRegexpType.regexp_to_s(pattern) else @pattern = pattern end end def regexp @regexp ||= Regexp.new(@pattern || '') end def hash @pattern.hash end def eql?(o) self.class == o.class && @pattern == o.pattern end def instance?(o, guard=nil) o.is_a?(Regexp) && @pattern.nil? || regexp == o end DEFAULT = PRegexpType.new(nil) protected # @api private # def _assignable?(o, guard) o.is_a?(PRegexpType) && (@pattern.nil? || @pattern == o.pattern) end end # Represents a subtype of String that narrows the string to those matching the patterns # If specified without a pattern it is basically the same as the String type. # # @api public # class PPatternType < PScalarDataType def self.register_ptype(loader, ir) create_ptype(loader, ir, 'ScalarDataType', 'patterns' => PArrayType.new(PRegexpType::DEFAULT)) end attr_reader :patterns def initialize(patterns) @patterns = patterns.freeze end def accept(visitor, guard) super @patterns.each { |p| p.accept(visitor, guard) } end def hash @patterns.hash end def eql?(o) self.class == o.class && @patterns.size == o.patterns.size && (@patterns - o.patterns).empty? end def instance?(o, guard = nil) o.is_a?(String) && (@patterns.empty? || @patterns.any? { |p| p.regexp.match(o) }) end DEFAULT = PPatternType.new(EMPTY_ARRAY) protected # @api private # def _assignable?(o, guard) return true if self == o case o when PStringType v = o.value if v.nil? # Strings cannot all match a pattern, but if there is no pattern it is ok # (There should really always be a pattern, but better safe than sorry). @patterns.empty? else # the string in String type must match one of the patterns in Pattern type, # or Pattern represents all Patterns == all Strings regexps = @patterns.map { |p| p.regexp } regexps.empty? || regexps.any? { |re| re.match(v) } end when PEnumType if o.values.empty? # Enums (unknown which ones) cannot all match a pattern, but if there is no pattern it is ok # (There should really always be a pattern, but better safe than sorry). @patterns.empty? else # all strings in String/Enum type must match one of the patterns in Pattern type, # or Pattern represents all Patterns == all Strings regexps = @patterns.map { |p| p.regexp } regexps.empty? || o.values.all? { |s| regexps.any? {|re| re.match(s) } } end when PPatternType @patterns.empty? else false end end end # @api public # class PBooleanType < PScalarDataType def self.register_ptype(loader, ir) create_ptype(loader, ir, 'ScalarDataType') end attr_reader :value def initialize(value = nil) @value = value end def eql?(o) o.is_a?(PBooleanType) && @value == o.value end def generalize PBooleanType::DEFAULT end def hash 31 ^ @value.hash end def instance?(o, guard = nil) (o == true || o == false) && (@value.nil? || value == o) end def self.new_function(type) @new_function ||= Puppet::Functions.create_loaded_function(:new_boolean, type.loader) do dispatch :from_args do param "Variant[Integer, Float, Boolean, Enum['false','true','yes','no','y','n',true]]", :from end argument_mismatch :on_error do param 'Any', :from end def from_args(from) from = from.downcase if from.is_a?(String) case from when Float from != 0.0 when Integer from != 0 when false, 'false', 'no', 'n' false else true end end def on_error(from) if from.is_a?(String) _("The string '%{str}' cannot be converted to Boolean") % { str: from } else t = TypeCalculator.singleton.infer(from).generalize _("Value of type %{type} cannot be converted to Boolean") % { type: t } end end end end DEFAULT = PBooleanType.new TRUE = PBooleanType.new(true) FALSE = PBooleanType.new(false) protected # @api private # def _assignable?(o, guard) o.is_a?(PBooleanType) && (@value.nil? || @value == o.value) end end # @api public # # @api public # class PStructElement < TypedModelObject def self.register_ptype(loader, ir) @type = Pcore::create_object_type(loader, ir, self, 'Pcore::StructElement'.freeze, nil, 'key_type' => PTypeType::DEFAULT, 'value_type' => PTypeType::DEFAULT) end attr_accessor :key_type, :value_type def accept(visitor, guard) @key_type.accept(visitor, guard) @value_type.accept(visitor, guard) end def hash value_type.hash ^ key_type.hash end def name k = key_type k = k.optional_type if k.is_a?(POptionalType) k.value end def initialize(key_type, value_type) @key_type = key_type @value_type = value_type end def generalize gv_type = @value_type.generalize @value_type.equal?(gv_type) ? self : PStructElement.new(@key_type, gv_type) end def normalize(guard = nil) nv_type = @value_type.normalize(guard) @value_type.equal?(nv_type) ? self : PStructElement.new(@key_type, nv_type) end def resolve(loader) rkey_type = @key_type.resolve(loader) rvalue_type = @value_type.resolve(loader) rkey_type.equal?(@key_type) && rvalue_type.equal?(@value_type) ? self : self.class.new(rkey_type, rvalue_type) end def <=>(o) self.name <=> o.name end def eql?(o) self == o end def ==(o) self.class == o.class && value_type == o.value_type && key_type == o.key_type end # Special boostrap method to overcome the hen and egg problem with the Object initializer that contains # types that are derived from Object (such as Annotation) # # @api private def replace_value_type(new_type) @value_type = new_type end end # @api public # class PStructType < PAnyType include Enumerable def self.register_ptype(loader, ir) create_ptype(loader, ir, 'AnyType', 'elements' => PArrayType.new(PTypeReferenceType.new('Pcore::StructElement'))) end def initialize(elements) @elements = elements.freeze end def accept(visitor, guard) super @elements.each { |elem| elem.accept(visitor, guard) } end def each if block_given? elements.each { |elem| yield elem } else elements.to_enum end end def generalize if @elements.empty? DEFAULT else alter_type_array(@elements, :generalize) { |altered| PStructType.new(altered) } end end def normalize(guard = nil) if @elements.empty? DEFAULT else alter_type_array(@elements, :normalize, guard) { |altered| PStructType.new(altered) } end end def hashed_elements @hashed ||= @elements.reduce({}) {|memo, e| memo[e.name] = e; memo } end def hash @elements.hash end def iterable?(guard = nil) true end def iterable_type(guard = nil) if self == DEFAULT PIterableType.new(PHashType::DEFAULT_KEY_PAIR_TUPLE) else PIterableType.new( PTupleType.new([ PVariantType.maybe_create(@elements.map {|se| se.key_type }), PVariantType.maybe_create(@elements.map {|se| se.value_type })], PHashType::KEY_PAIR_TUPLE_SIZE)) end end def resolve(loader) changed = false relements = @elements.map do |elem| relem = elem.resolve(loader) changed ||= !relem.equal?(elem) relem end changed ? self.class.new(relements) : self end def eql?(o) self.class == o.class && @elements == o.elements end def elements @elements end def instance?(o, guard = nil) # The inferred type of a class derived from Hash is either Runtime or Object. It's not assignable to the Struct type. return false unless o.instance_of?(Hash) matched = 0 @elements.all? do |e| key = e.name v = o[key] if v.nil? && !o.include?(key) # Entry is missing. Only OK when key is optional e.key_type.assignable?(PUndefType::DEFAULT, guard) else matched += 1 e.value_type.instance?(v, guard) end end && matched == o.size end def new_function # Simply delegate to Hash type and let the higher level assertion deal with # compliance with the Struct type regarding the produced result. PHashType.new_function(self) end DEFAULT = PStructType.new(EMPTY_ARRAY) protected # @api private def _assignable?(o, guard) if o.is_a?(Types::PStructType) h2 = o.hashed_elements matched = 0 elements.all? do |e1| e2 = h2[e1.name] if e2.nil? e1.key_type.assignable?(PUndefType::DEFAULT, guard) else matched += 1 e1.key_type.assignable?(e2.key_type, guard) && e1.value_type.assignable?(e2.value_type, guard) end end && matched == h2.size elsif o.is_a?(Types::PHashType) required = 0 required_elements_assignable = elements.all? do |e| key_type = e.key_type if key_type.assignable?(PUndefType::DEFAULT) # Element is optional so Hash does not need to provide it true else required += 1 if e.value_type.assignable?(o.value_type, guard) # Hash must have something that is assignable. We don't care about the name or size of the key though # because we have no instance of a hash to compare against. key_type.generalize.assignable?(o.key_type) else false end end end if required_elements_assignable size_o = o.size_type || PCollectionType::DEFAULT_SIZE PIntegerType.new(required, elements.size).assignable?(size_o, guard) else false end else false end end end # @api public # class PTupleType < PAnyType include Enumerable def self.register_ptype(loader, ir) create_ptype(loader, ir, 'AnyType', 'types' => PArrayType.new(PTypeType::DEFAULT), 'size_type' => { KEY_TYPE => POptionalType.new(PTypeType.new(PIntegerType::DEFAULT)), KEY_VALUE => nil } ) end # If set, describes min and max required of the given types - if max > size of # types, the last type entry repeats # attr_reader :size_type attr_reader :types def accept(visitor, guard) super @size_type.accept(visitor, guard) unless @size_type.nil? @types.each { |elem| elem.accept(visitor, guard) } end # @api private def callable_args?(callable_t, guard) unless size_type.nil? raise ArgumentError, 'Callable tuple may not have a size constraint when used as args' end params_tuple = callable_t.param_types param_block_t = callable_t.block_type arg_types = @types arg_block_t = arg_types.last if arg_block_t.kind_of_callable?(true, guard) # Can't pass a block to a callable that doesn't accept one return false if param_block_t.nil? # Check that the block is of the right tyṕe return false unless param_block_t.assignable?(arg_block_t, guard) # Check other arguments arg_count = arg_types.size - 1 params_size_t = params_tuple.size_type || PIntegerType.new(*params_tuple.size_range) return false unless params_size_t.assignable?(PIntegerType.new(arg_count, arg_count), guard) ctypes = params_tuple.types arg_count.times do |index| return false unless (ctypes[index] || ctypes[-1]).assignable?(arg_types[index], guard) end return true end # Check that tuple is assignable and that the block (if declared) is optional params_tuple.assignable?(self, guard) && (param_block_t.nil? || param_block_t.assignable?(PUndefType::DEFAULT, guard)) end def initialize(types, size_type = nil) @types = types @size_type = size_type.nil? ? nil : size_type.to_size end # Returns Enumerator for the types if no block is given, otherwise, calls the given # block with each of the types in this tuple def each if block_given? types.each { |x| yield x } else types.to_enum end end def generalize if self == DEFAULT DEFAULT else alter_type_array(@types, :generalize) { |altered_types| PTupleType.new(altered_types, @size_type) } end end def normalize(guard = nil) if self == DEFAULT DEFAULT else alter_type_array(@types, :normalize, guard) { |altered_types| PTupleType.new(altered_types, @size_type) } end end def resolve(loader) changed = false rtypes = @types.map do |type| rtype = type.resolve(loader) changed ||= !rtype.equal?(type) rtype end changed ? self.class.new(rtypes, @size_type) : self end def instance?(o, guard = nil) # The inferred type of a class derived from Array is either Runtime or Object. It's not assignable to the Tuple type. return false unless o.instance_of?(Array) if @size_type return false unless @size_type.instance?(o.size, guard) else return false unless @types.empty? || @types.size == o.size end index = -1 @types.empty? || o.all? do |element| @types.fetch(index += 1) { @types.last }.instance?(element, guard) end end def iterable?(guard = nil) true end def iterable_type(guard = nil) PIterableType.new(PVariantType.maybe_create(types)) end # Returns the number of elements accepted [min, max] in the tuple def size_range if @size_type.nil? types_size = @types.size types_size == 0 ? [0, Float::INFINITY] : [types_size, types_size] else @size_type.range end end # Returns the number of accepted occurrences [min, max] of the last type in the tuple # The defaults is [1,1] # def repeat_last_range if @size_type.nil? return [1, 1] end types_size = @types.size from, to = @size_type.range min = from - (types_size-1) min = min <= 0 ? 0 : min max = to - (types_size-1) [min, max] end def hash @size_type.hash ^ @types.hash end def eql?(o) self.class == o.class && @types == o.types && @size_type == o.size_type end def new_function # Simply delegate to Array type and let the higher level assertion deal with # compliance with the Tuple type regarding the produced result. PArrayType.new_function(self) end DEFAULT = PTupleType.new(EMPTY_ARRAY) protected # @api private def _assignable?(o, guard) return true if self == o return false unless o.is_a?(PTupleType) || o.is_a?(PArrayType) s_types = types size_s = size_type || PIntegerType.new(*size_range) if o.is_a?(PTupleType) size_o = o.size_type || PIntegerType.new(*o.size_range) return false unless size_s.assignable?(size_o, guard) unless s_types.empty? o_types = o.types return size_s.numeric_from == 0 if o_types.empty? o_types.size.times do |index| return false unless (s_types[index] || s_types[-1]).assignable?(o_types[index], guard) end end else size_o = o.size_type || PCollectionType::DEFAULT_SIZE return false unless size_s.assignable?(size_o, guard) unless s_types.empty? o_entry = o.element_type # Array of anything can not be assigned (unless tuple is tuple of anything) - this case # was handled at the top of this method. # return false if o_entry.nil? [s_types.size, size_o.range[1]].min.times { |index| return false unless (s_types[index] || s_types[-1]).assignable?(o_entry, guard) } end end true end end # @api public # class PCallableType < PAnyType def self.register_ptype(loader, ir) create_ptype(loader, ir, 'AnyType', 'param_types' => { KEY_TYPE => POptionalType.new(PTypeType.new(PTupleType::DEFAULT)), KEY_VALUE => nil }, 'block_type' => { KEY_TYPE => POptionalType.new(PTypeType.new(PCallableType::DEFAULT)), KEY_VALUE => nil }, 'return_type' => { KEY_TYPE => POptionalType.new(PTypeType::DEFAULT), KEY_VALUE => PAnyType::DEFAULT } ) end # @return [PAnyType] The type for the values returned by this callable. Returns `nil` if return value is unconstrained attr_reader :return_type # Types of parameters as a Tuple with required/optional count, or an Integer with min (required), max count # @return [PTupleType] the tuple representing the parameter types attr_reader :param_types # Although being an abstract type reference, only Callable, or all Callables wrapped in # Optional or Variant are supported # If not set, the meaning is that block is not supported. # @return [PAnyType|nil] the block type attr_reader :block_type # @param param_types [PTupleType] # @param block_type [PAnyType] # @param return_type [PAnyType] def initialize(param_types, block_type = nil, return_type = nil) @param_types = param_types @block_type = block_type @return_type = return_type == PAnyType::DEFAULT ? nil : return_type end def accept(visitor, guard) super @param_types.accept(visitor, guard) unless @param_types.nil? @block_type.accept(visitor, guard) unless @block_type.nil? @return_type.accept(visitor, guard) unless @return_type.nil? end def generalize if self == DEFAULT DEFAULT else params_t = @param_types.nil? ? nil : @param_types.generalize block_t = @block_type.nil? ? nil : @block_type.generalize return_t = @return_type.nil? ? nil : @return_type.generalize @param_types.equal?(params_t) && @block_type.equal?(block_t) && @return_type.equal?(return_t) ? self : PCallableType.new(params_t, block_t, return_t) end end def normalize(guard = nil) if self == DEFAULT DEFAULT else params_t = @param_types.nil? ? nil : @param_types.normalize(guard) block_t = @block_type.nil? ? nil : @block_type.normalize(guard) return_t = @return_type.nil? ? nil : @return_type.normalize(guard) @param_types.equal?(params_t) && @block_type.equal?(block_t) && @return_type.equal?(return_t) ? self : PCallableType.new(params_t, block_t, return_t) end end def instance?(o, guard = nil) (o.is_a?(Proc) || o.is_a?(Evaluator::Closure) || o.is_a?(Functions::Function)) && assignable?(TypeCalculator.infer(o), guard) end # Returns `true` if this instance is a callable that accepts the given _args_ # # @param args [Array] the arguments to test # @return [Boolean] `true` if this instance is a callable that accepts the given _args_ def callable_with?(args, block = nil) # nil param_types and compatible return type means other Callable is assignable return true if @param_types.nil? return false unless @param_types.instance?(args) if @block_type.nil? block == nil else @block_type.instance?(block) end end # @api private def callable_args?(required_callable_t, guard) # If the required callable is equal or more specific than self, self is acceptable arguments required_callable_t.assignable?(self, guard) end def kind_of_callable?(optional=true, guard = nil) true end # Returns the number of accepted arguments [min, max] def size_range @param_types.nil? ? nil : @param_types.size_range end # Returns the number of accepted arguments for the last parameter type [min, max] # def last_range @param_types.nil? ? nil : @param_types.repeat_last_range end # Range [0,0], [0,1], or [1,1] for the block # def block_range case block_type when POptionalType [0,1] when PVariantType, PCallableType [1,1] else [0,0] end end def hash [@param_types, @block_type, @return_type].hash end def eql?(o) self.class == o.class && @param_types == o.param_types && @block_type == o.block_type && @return_type == o.return_type end def resolve(loader) params_t = @param_types.nil? ? nil : @param_types.resolve(loader) block_t = @block_type.nil? ? nil : @block_type.resolve(loader) return_t = @return_type.nil? ? nil : @return_type.resolve(loader) @param_types.equal?(params_t) && @block_type.equal?(block_t) && @return_type.equal?(return_t) ? self : self.class.new(params_t, block_t, return_t) end DEFAULT = PCallableType.new(nil, nil, nil) protected # @api private def _assignable?(o, guard) return false unless o.is_a?(PCallableType) return false unless @return_type.nil? || @return_type.assignable?(o.return_type || PAnyType::DEFAULT, guard) # nil param_types and compatible return type means other Callable is assignable return true if @param_types.nil? # NOTE: these tests are made in reverse as it is calling the callable that is constrained # (it's lower bound), not its upper bound other_param_types = o.param_types return false if other_param_types.nil? || !other_param_types.assignable?(@param_types, guard) # names are ignored, they are just information # Blocks must be compatible this_block_t = @block_type || PUndefType::DEFAULT that_block_t = o.block_type || PUndefType::DEFAULT that_block_t.assignable?(this_block_t, guard) end end # @api public # class PArrayType < PCollectionType def self.register_ptype(loader, ir) create_ptype(loader, ir, 'CollectionType', 'element_type' => { KEY_TYPE => POptionalType.new(PTypeType::DEFAULT), KEY_VALUE => PAnyType::DEFAULT } ) end attr_reader :element_type def initialize(element_type, size_type = nil) super(size_type) if !size_type.nil? && size_type.from == 0 && size_type.to == 0 @element_type = PUnitType::DEFAULT else @element_type = element_type.nil? ? PAnyType::DEFAULT : element_type end end def accept(visitor, guard) super @element_type.accept(visitor, guard) end # @api private def callable_args?(callable, guard = nil) param_t = callable.param_types block_t = callable.block_type # does not support calling with a block, but have to check that callable is ok with missing block (param_t.nil? || param_t.assignable?(self, guard)) && (block_t.nil? || block_t.assignable?(PUndefType::DEFAULT, guard)) end def generalize if PAnyType::DEFAULT.eql?(@element_type) DEFAULT else ge_type = @element_type.generalize @size_type.nil? && @element_type.equal?(ge_type) ? self : self.class.new(ge_type, nil) end end def eql?(o) super && @element_type == o.element_type end def hash super ^ @element_type.hash end def normalize(guard = nil) if PAnyType::DEFAULT.eql?(@element_type) DEFAULT else ne_type = @element_type.normalize(guard) @element_type.equal?(ne_type) ? self : self.class.new(ne_type, @size_type) end end def resolve(loader) relement_type = @element_type.resolve(loader) relement_type.equal?(@element_type) ? self : self.class.new(relement_type, @size_type) end def instance?(o, guard = nil) # The inferred type of a class derived from Array is either Runtime or Object. It's not assignable to the Array type. return false unless o.instance_of?(Array) return false unless o.all? {|element| @element_type.instance?(element, guard) } size_t = size_type size_t.nil? || size_t.instance?(o.size, guard) end def iterable_type(guard = nil) PAnyType::DEFAULT.eql?(@element_type) ? PIterableType::DEFAULT : PIterableType.new(@element_type) end # Returns a new function that produces an Array # def self.new_function(type) @new_function ||= Puppet::Functions.create_loaded_function(:new_array, type.loader) do dispatch :to_array do param 'Variant[Array,Hash,Binary,Iterable]', :from optional_param 'Boolean[false]', :wrap end dispatch :wrapped do param 'Any', :from param 'Boolean[true]', :wrap end argument_mismatch :on_error do param 'Any', :from optional_param 'Boolean', :wrap end def wrapped(from, _) from.is_a?(Array) ? from : [from] end def to_array(from, _ = false) case from when Array from when Hash from.to_a when PBinaryType::Binary # For older rubies, the #bytes method returns an Enumerator that must be rolled out from.binary_buffer.bytes.to_a else Iterable.on(from).to_a end end def on_error(from, _ = false) t = TypeCalculator.singleton.infer(from).generalize _("Value of type %{type} cannot be converted to Array") % { type: t } end end end DEFAULT = PArrayType.new(nil) EMPTY = PArrayType.new(PUnitType::DEFAULT, ZERO_SIZE) protected # Array is assignable if o is an Array and o's element type is assignable, or if o is a Tuple # @api private def _assignable?(o, guard) if o.is_a?(PTupleType) o_types = o.types size_s = size_type || DEFAULT_SIZE size_o = o.size_type if size_o.nil? type_count = o_types.size size_o = PIntegerType.new(type_count, type_count) end size_s.assignable?(size_o) && o_types.all? { |ot| @element_type.assignable?(ot, guard) } elsif o.is_a?(PArrayType) super && @element_type.assignable?(o.element_type, guard) else false end end end # @api public # class PHashType < PCollectionType def self.register_ptype(loader, ir) create_ptype(loader, ir, 'CollectionType', 'key_type' => { KEY_TYPE => POptionalType.new(PTypeType::DEFAULT), KEY_VALUE => PAnyType::DEFAULT }, 'value_type' => { KEY_TYPE => POptionalType.new(PTypeType::DEFAULT), KEY_VALUE => PAnyType::DEFAULT } ) end attr_accessor :key_type, :value_type def initialize(key_type, value_type, size_type = nil) super(size_type) if !size_type.nil? && size_type.from == 0 && size_type.to == 0 @key_type = PUnitType::DEFAULT @value_type = PUnitType::DEFAULT else @key_type = key_type.nil? ? PAnyType::DEFAULT : key_type @value_type = value_type.nil? ? PAnyType::DEFAULT : value_type end end def accept(visitor, guard) super @key_type.accept(visitor, guard) @value_type.accept(visitor, guard) end def element_type if Puppet[:strict] != :off #TRANSLATOR 'Puppet::Pops::Types::PHashType#element_type' and '#value_type' are class and method names and should not be translated Puppet.warn_once('deprecations', 'Puppet::Pops::Types::PHashType#element_type', _('Puppet::Pops::Types::PHashType#element_type is deprecated, use #value_type instead')) end @value_type end def generalize if self == DEFAULT || self == EMPTY self else key_t = @key_type key_t = key_t.generalize value_t = @value_type value_t = value_t.generalize @size_type.nil? && @key_type.equal?(key_t) && @value_type.equal?(value_t) ? self : PHashType.new(key_t, value_t, nil) end end def normalize(guard = nil) if self == DEFAULT || self == EMPTY self else key_t = @key_type.normalize(guard) value_t = @value_type.normalize(guard) @size_type.nil? && @key_type.equal?(key_t) && @value_type.equal?(value_t) ? self : PHashType.new(key_t, value_t, @size_type) end end def hash super ^ @key_type.hash ^ @value_type.hash end def instance?(o, guard = nil) # The inferred type of a class derived from Hash is either Runtime or Object. It's not assignable to the Hash type. return false unless o.instance_of?(Hash) if o.keys.all? {|key| @key_type.instance?(key, guard) } && o.values.all? {|value| @value_type.instance?(value, guard) } size_t = size_type size_t.nil? || size_t.instance?(o.size, guard) else false end end def iterable?(guard = nil) true end def iterable_type(guard = nil) if self == DEFAULT || self == EMPTY PIterableType.new(DEFAULT_KEY_PAIR_TUPLE) else PIterableType.new(PTupleType.new([@key_type, @value_type], KEY_PAIR_TUPLE_SIZE)) end end def eql?(o) super && @key_type == o.key_type && @value_type == o.value_type end def is_the_empty_hash? self == EMPTY end def resolve(loader) rkey_type = @key_type.resolve(loader) rvalue_type = @value_type.resolve(loader) rkey_type.equal?(@key_type) && rvalue_type.equal?(@value_type) ? self : self.class.new(rkey_type, rvalue_type, @size_type) end def self.array_as_hash(value) return value unless value.is_a?(Array) result = {} value.each_with_index {|v, idx| result[idx] = array_as_hash(v) } result end # Returns a new function that produces a Hash # def self.new_function(type) @new_function ||= Puppet::Functions.create_loaded_function(:new_hash, type.loader) do local_types do type 'KeyValueArray = Array[Tuple[Any,Any],1]' type 'TreeArray = Array[Tuple[Array,Any],1]' type 'NewHashOption = Enum[tree, hash_tree]' end dispatch :from_tree do param 'TreeArray', :from optional_param 'NewHashOption', :build_option end dispatch :from_tuples do param 'KeyValueArray', :from end dispatch :from_array do param 'Any', :from end def from_tuples(tuple_array) Hash[tuple_array] end def from_tree(tuple_array, build_option = nil) if build_option.nil? return from_tuples(tuple_array) end # only remaining possible options is 'tree' or 'hash_tree' all_hashes = build_option == 'hash_tree' result = {} tuple_array.each do |entry| path = entry[0] value = entry[1] if path.empty? # root node (index [] was included - values merge into the result) # An array must be changed to a hash first as this is the root # (Cannot return an array from a Hash.new) if value.is_a?(Array) value.each_with_index {|v, idx| result[idx] = v } else result.merge!(value) end else r = path[0..-2].reduce(result) {|memo, idx| (memo.is_a?(Array) || memo.has_key?(idx)) ? memo[idx] : memo[idx] = {}} r[path[-1]]= (all_hashes ? PHashType.array_as_hash(value) : value) end end result end def from_array(from) case from when Array if from.size == 0 {} else unless from.size % 2 == 0 raise TypeConversionError.new(_('odd number of arguments for Hash')) end Hash[*from] end when Hash from else if PIterableType::DEFAULT.instance?(from) Hash[*Iterable.on(from).to_a] else t = TypeCalculator.singleton.infer(from).generalize raise TypeConversionError.new(_("Value of type %{type} cannot be converted to Hash") % { type: t }) end end end end end DEFAULT = PHashType.new(nil, nil) KEY_PAIR_TUPLE_SIZE = PIntegerType.new(2,2) DEFAULT_KEY_PAIR_TUPLE = PTupleType.new([PUnitType::DEFAULT, PUnitType::DEFAULT], KEY_PAIR_TUPLE_SIZE) EMPTY = PHashType.new(PUnitType::DEFAULT, PUnitType::DEFAULT, PIntegerType.new(0, 0)) protected # Hash is assignable if o is a Hash and o's key and element types are assignable # @api private def _assignable?(o, guard) case o when PHashType size_s = size_type return true if (size_s.nil? || size_s.from == 0) && o.is_the_empty_hash? return false unless @key_type.assignable?(o.key_type, guard) && @value_type.assignable?(o.value_type, guard) super when PStructType # hash must accept String as key type # hash must accept all value types # hash must accept the size of the struct o_elements = o.elements (size_type || DEFAULT_SIZE).instance?(o_elements.size, guard) && o_elements.all? {|e| @key_type.instance?(e.name, guard) && @value_type.assignable?(e.value_type, guard) } else false end end end # A flexible type describing an any? of other types # @api public # class PVariantType < PAnyType include Enumerable def self.register_ptype(loader, ir) create_ptype(loader, ir, 'AnyType', 'types' => PArrayType.new(PTypeType::DEFAULT)) end attr_reader :types # Checks if the number of unique types in the given array is greater than one, and if so # creates a Variant with those types and returns it. If only one unique type is found, # that type is instead returned. # # @param types [Array<PAnyType>] the variants # @return [PAnyType] the resulting type # @api public def self.maybe_create(types) types = flatten_variants(types).uniq types.size == 1 ? types[0] : new(types) end # @param types [Array[PAnyType]] the variants def initialize(types) @types = types.freeze end def accept(visitor, guard) super @types.each { |t| t.accept(visitor, guard) } end def each if block_given? types.each { |t| yield t } else types.to_enum end end def generalize if self == DEFAULT self else alter_type_array(@types, :generalize) { |altered| PVariantType.maybe_create(altered) } end end def normalize(guard = nil) if self == DEFAULT || @types.empty? self else # Normalize all contained types modified = false types = alter_type_array(@types, :normalize, guard) if types == self types = @types else modified = true end if types.size == 1 types[0] elsif types.any? { |t| t.is_a?(PUndefType) || t.is_a?(POptionalType) } # Undef entry present. Use an OptionalType with a normalized Variant without Undefs and Optional wrappers POptionalType.new(PVariantType.maybe_create(types.reject { |t| t.is_a?(PUndefType) }.map { |t| t.is_a?(POptionalType) ? t.type : t })).normalize else # Merge all variants into this one types = PVariantType.flatten_variants(types) size_before_merge = types.size types = swap_not_undefs(types) types = merge_enums(types) types = merge_patterns(types) types = merge_version_ranges(types) types = merge_numbers(PIntegerType, types) types = merge_numbers(PFloatType, types) types = merge_numbers(PTimespanType, types) types = merge_numbers(PTimestampType, types) if types.size == 1 types[0] else modified || types.size != size_before_merge ? PVariantType.maybe_create(types) : self end end end end def self.flatten_variants(types) modified = false types = types.map do |t| if t.is_a?(PVariantType) modified = true t.types else t end end types.flatten! if modified types end def hash @types.hash end def instance?(o, guard = nil) # instance of variant if o is instance? of any of variant's types @types.any? { |type| type.instance?(o, guard) } end def really_instance?(o, guard = nil) @types.reduce(-1) do |memo, type| ri = type.really_instance?(o, guard) break ri if ri > 0 ri > memo ? ri : memo end end def kind_of_callable?(optional = true, guard = nil) @types.all? { |type| type.kind_of_callable?(optional, guard) } end def eql?(o) self.class == o.class && @types.size == o.types.size && (@types - o.types).empty? end DEFAULT = PVariantType.new(EMPTY_ARRAY) def assignable?(o, guard = nil) # an empty Variant does not match Undef (it is void - not even undef) if o.is_a?(PUndefType) && types.empty? return false end return super unless o.is_a?(PVariantType) # If empty, all Variant types match irrespective of the types they hold (including being empty) return true if types.empty? # Since this variant is not empty, an empty Variant cannot match, because it matches nothing # otherwise all types in o must be assignable to this !o.types.empty? && o.types.all? { |vt| super(vt, guard) } end protected # @api private def _assignable?(o, guard) # A variant is assignable if o is assignable to any of its types types.any? { |option_t| option_t.assignable?(o, guard) } end # @api private def swap_not_undefs(array) if array.size > 1 parts = array.partition {|t| t.is_a?(PNotUndefType) } not_undefs = parts[0] if not_undefs.size > 1 others = parts[1] others << PNotUndefType.new(PVariantType.maybe_create(not_undefs.map { |not_undef| not_undef.type }).normalize) array = others end end array end # @api private def merge_enums(array) # Merge case sensitive enums and strings if array.size > 1 parts = array.partition {|t| t.is_a?(PEnumType) && !t.values.empty? && !t.case_insensitive? || t.is_a?(PStringType) && !t.value.nil? } enums = parts[0] if enums.size > 1 others = parts[1] others << PEnumType.new(enums.map { |enum| enum.is_a?(PStringType) ? enum.value : enum.values }.flatten.uniq) array = others end end # Merge case insensitive enums if array.size > 1 parts = array.partition {|t| t.is_a?(PEnumType) && !t.values.empty? && t.case_insensitive? } enums = parts[0] if enums.size > 1 others = parts[1] values = [] enums.each { |enum| enum.values.each { |value| values << value.downcase }} values.uniq! others << PEnumType.new(values, true) array = others end end array end # @api private def merge_patterns(array) if array.size > 1 parts = array.partition {|t| t.is_a?(PPatternType) } patterns = parts[0] if patterns.size > 1 others = parts[1] others << PPatternType.new(patterns.map { |pattern| pattern.patterns }.flatten.uniq) array = others end end array end # @api private def merge_numbers(clazz, array) if array.size > 1 parts = array.partition {|t| t.is_a?(clazz) } ranges = parts[0] array = merge_ranges(ranges) + parts[1] if ranges.size > 1 end array end def merge_version_ranges(array) if array.size > 1 parts = array.partition {|t| t.is_a?(PSemVerType) } ranges = parts[0] array = [PSemVerType.new(ranges.map(&:ranges).flatten)] + parts[1] if ranges.size > 1 end array end # @api private def merge_ranges(ranges) result = [] until ranges.empty? unmerged = [] x = ranges.pop result << ranges.inject(x) do |memo, y| merged = memo.merge(y) if merged.nil? unmerged << y else memo = merged end memo end ranges = unmerged end result end end # Abstract representation of a type that can be placed in a Catalog. # @api public # class PCatalogEntryType < PAnyType def self.register_ptype(loader, ir) create_ptype(loader, ir, 'AnyType') end DEFAULT = PCatalogEntryType.new def instance?(o, guard = nil) assignable?(TypeCalculator.infer(o), guard) end protected # @api private def _assignable?(o, guard) o.is_a?(PCatalogEntryType) end end # Represents a (host-) class in the Puppet Language. # @api public # class PClassType < PCatalogEntryType attr_reader :class_name def self.register_ptype(loader, ir) create_ptype(loader, ir, 'CatalogEntryType', 'class_name' => { KEY_TYPE => POptionalType.new(PStringType::NON_EMPTY), KEY_VALUE => nil } ) end def initialize(class_name) @class_name = class_name end def hash 11 ^ @class_name.hash end def eql?(o) self.class == o.class && @class_name == o.class_name end DEFAULT = PClassType.new(nil) protected # @api private def _assignable?(o, guard) return false unless o.is_a?(PClassType) # Class = Class[name}, Class[name] != Class return true if @class_name.nil? # Class[name] = Class[name] @class_name == o.class_name end end # For backward compatibility PHostClassType = PClassType # Represents a Resource Type in the Puppet Language # @api public # class PResourceType < PCatalogEntryType def self.register_ptype(loader, ir) create_ptype(loader, ir, 'CatalogEntryType', 'type_name' => { KEY_TYPE => POptionalType.new(PStringType::NON_EMPTY), KEY_VALUE => nil }, 'title' => { KEY_TYPE => POptionalType.new(PStringType::NON_EMPTY), KEY_VALUE => nil } ) end attr_reader :type_name, :title, :downcased_name def initialize(type_name, title = nil) @type_name = type_name.freeze @title = title.freeze @downcased_name = type_name.nil? ? nil : @type_name.downcase.freeze end def eql?(o) self.class == o.class && @downcased_name == o.downcased_name && @title == o.title end def hash @downcased_name.hash ^ @title.hash end DEFAULT = PResourceType.new(nil) protected # @api private def _assignable?(o, guard) o.is_a?(PResourceType) && (@downcased_name.nil? || @downcased_name == o.downcased_name && (@title.nil? || @title == o.title)) end end # Represents a type that accept PUndefType instead of the type parameter # required_type - is a short hand for Variant[T, Undef] # @api public # class POptionalType < PTypeWithContainedType def self.register_ptype(loader, ir) create_ptype(loader, ir, 'AnyType', 'type' => { KEY_TYPE => POptionalType.new(PTypeType::DEFAULT), KEY_VALUE => nil } ) end def optional_type @type end def kind_of_callable?(optional=true, guard = nil) optional && !@type.nil? && @type.kind_of_callable?(optional, guard) end def instance?(o, guard = nil) PUndefType::DEFAULT.instance?(o, guard) || (!@type.nil? && @type.instance?(o, guard)) end def normalize(guard = nil) n = super if n.type.nil? n else if n.type.is_a?(PNotUndefType) # No point in having an NotUndef in an Optional POptionalType.new(n.type.type).normalize elsif n.type.assignable?(PUndefType::DEFAULT) # THe type is Optional anyway, so it can be stripped of n.type else n end end end def new_function optional_type.new_function end DEFAULT = POptionalType.new(nil) protected # @api private def _assignable?(o, guard) return true if o.is_a?(PUndefType) return true if @type.nil? if o.is_a?(POptionalType) @type.assignable?(o.optional_type, guard) else @type.assignable?(o, guard) end end end class PTypeReferenceType < PAnyType def self.register_ptype(loader, ir) create_ptype(loader, ir, 'AnyType', 'type_string' => PStringType::NON_EMPTY) end attr_reader :type_string def initialize(type_string) @type_string = type_string end def callable?(args) false end def instance?(o, guard = nil) false end def hash @type_string.hash end def eql?(o) super && o.type_string == @type_string end def resolve(loader) TypeParser.singleton.parse(@type_string, loader) end protected def _assignable?(o, guard) # A type must be assignable to itself or a lot of unit tests will break o == self end DEFAULT = PTypeReferenceType.new('UnresolvedReference') end # Describes a named alias for another Type. # The alias is created with a name and an unresolved type expression. The type expression may # in turn contain other aliases (including the alias that contains it) which means that an alias # might contain self recursion. Whether or not that is the case is computed and remembered when the alias # is resolved since guarding against self recursive constructs is relatively expensive. # # @api public class PTypeAliasType < PAnyType def self.register_ptype(loader, ir) create_ptype(loader, ir, 'AnyType', 'name' => PStringType::NON_EMPTY, 'type_expr' => PAnyType::DEFAULT, 'resolved_type' => { KEY_TYPE => POptionalType.new(PTypeType::DEFAULT), KEY_VALUE => nil } ) end attr_reader :loader, :name # @param name [String] The name of the type # @param type_expr [Model::PopsObject] The expression that describes the aliased type # @param resolved_type [PAnyType] the resolve type (only used for the DEFAULT initialization) def initialize(name, type_expr, resolved_type = nil) @name = name @type_expr = type_expr @resolved_type = resolved_type @self_recursion = false end def assignable?(o, guard = nil) if @self_recursion guard ||= RecursionGuard.new guard.with_this(self) { |state| state == RecursionGuard::SELF_RECURSION_IN_BOTH ? true : super(o, guard) } else super(o, guard) end end # Returns the resolved type. The type must have been resolved by a call prior to calls to this # method or an error will be raised. # # @return [PAnyType] The resolved type of this alias. # @raise [Puppet::Error] unless the type has been resolved prior to calling this method def resolved_type raise Puppet::Error, "Reference to unresolved type #{@name}" unless @resolved_type @resolved_type end def callable_args?(callable, guard) guarded_recursion(guard, false) { |g| resolved_type.callable_args?(callable, g) } end def check_self_recursion(originator) resolved_type.check_self_recursion(originator) unless originator.equal?(self) end def kind_of_callable?(optional=true, guard = nil) guarded_recursion(guard, false) { |g| resolved_type.kind_of_callable?(optional, g) } end def instance?(o, guard = nil) really_instance?(o, guard) == 1 end def iterable?(guard = nil) guarded_recursion(guard, false) { |g| resolved_type.iterable?(g) } end def iterable_type(guard = nil) guarded_recursion(guard, nil) { |g| resolved_type.iterable_type(g) } end def hash @name.hash end # Acceptor used when checking for self recursion and that a type contains # something other than aliases or type references # # @api private class AssertOtherTypeAcceptor def initialize @other_type_detected = false end def visit(type, _) unless type.is_a?(PTypeAliasType) || type.is_a?(PVariantType) @other_type_detected = true end end def other_type_detected? @other_type_detected end end # Acceptor used when re-checking for self recursion after a self recursion has been detected # # @api private class AssertSelfRecursionStatusAcceptor def visit(type, _) type.set_self_recursion_status if type.is_a?(PTypeAliasType) end end def set_self_recursion_status return if @self_recursion || @resolved_type.is_a?(PTypeReferenceType) @self_recursion = true guard = RecursionGuard.new accept(NoopTypeAcceptor::INSTANCE, guard) @self_recursion = guard.recursive_this?(self) when_self_recursion_detected if @self_recursion # no difference end # Called from the TypeParser once it has found a type using the Loader. The TypeParser will # interpret the contained expression and the resolved type is remembered. This method also # checks and remembers if the resolve type contains self recursion. # # @param type_parser [TypeParser] type parser that will interpret the type expression # @param loader [Loader::Loader] loader to use when loading type aliases # @return [PTypeAliasType] the receiver of the call, i.e. `self` # @api private def resolve(loader) @loader = loader if @resolved_type.nil? # resolved to PTypeReferenceType::DEFAULT during resolve to avoid endless recursion @resolved_type = PTypeReferenceType::DEFAULT @self_recursion = true # assumed while it being found out below begin if @type_expr.is_a?(PTypeReferenceType) @resolved_type = @type_expr.resolve(loader) else @resolved_type = TypeParser.singleton.interpret(@type_expr, loader).normalize end # Find out if this type is recursive. A recursive type has performance implications # on several methods and this knowledge is used to avoid that for non-recursive # types. guard = RecursionGuard.new real_type_asserter = AssertOtherTypeAcceptor.new accept(real_type_asserter, guard) unless real_type_asserter.other_type_detected? raise ArgumentError, "Type alias '#{name}' cannot be resolved to a real type" end @self_recursion = guard.recursive_this?(self) # All aliases involved must re-check status since this alias is now resolved if @self_recursion accept(AssertSelfRecursionStatusAcceptor.new, RecursionGuard.new) when_self_recursion_detected end rescue @resolved_type = nil raise end else # An alias may appoint an Object type that isn't resolved yet. The default type # reference is used to prevent endless recursion and should not be resolved here. @resolved_type.resolve(loader) unless @resolved_type.equal?(PTypeReferenceType::DEFAULT) end self end def eql?(o) super && o.name == @name end def accept(visitor, guard) guarded_recursion(guard, nil) do |g| super(visitor, g) @resolved_type.accept(visitor, g) unless @resolved_type.nil? end end def self_recursion? @self_recursion end # Returns the expanded string the form of the alias, e.g. <alias name> = <resolved type> # # @return [String] the expanded form of this alias # @api public def to_s TypeFormatter.singleton.alias_expanded_string(self) end # Delegates to resolved type def respond_to_missing?(name, include_private) resolved_type.respond_to?(name, include_private) end # Delegates to resolved type def method_missing(name, *arguments, &block) super if @resolved_type.equal?(PTypeReferenceType::DEFAULT) resolved_type.send(name, *arguments, &block) end # @api private def really_instance?(o, guard = nil) if @self_recursion guard ||= RecursionGuard.new guard.with_that(o) do guard.with_this(self) { |state| state == RecursionGuard::SELF_RECURSION_IN_BOTH ? 0 : resolved_type.really_instance?(o, guard) } end else resolved_type.really_instance?(o, guard) end end # @return `nil` to prevent serialization of the type_expr used when first initializing this instance # @api private def type_expr nil end protected def _assignable?(o, guard) resolved_type.assignable?(o, guard) end def new_function resolved_type.new_function end private def guarded_recursion(guard, dflt) if @self_recursion guard ||= RecursionGuard.new guard.with_this(self) { |state| (state & RecursionGuard::SELF_RECURSION_IN_THIS) == 0 ? yield(guard) : dflt } else yield(guard) end end def when_self_recursion_detected if @resolved_type.is_a?(PVariantType) # Drop variants that are not real types resolved_types = @resolved_type.types real_types = resolved_types.select do |type| next false if type == self real_type_asserter = AssertOtherTypeAcceptor.new type.accept(real_type_asserter, RecursionGuard.new) real_type_asserter.other_type_detected? end if real_types.size != resolved_types.size if real_types.size == 1 @resolved_type = real_types[0] else @resolved_type = PVariantType.maybe_create(real_types) end # Drop self recursion status in case it's not self recursive anymore guard = RecursionGuard.new accept(NoopTypeAcceptor::INSTANCE, guard) @self_recursion = guard.recursive_this?(self) end end @resolved_type.check_self_recursion(self) if @self_recursion end DEFAULT = PTypeAliasType.new('UnresolvedAlias', nil, PTypeReferenceType::DEFAULT) end end end require 'puppet/pops/pcore' require_relative 'annotatable' require_relative 'p_meta_type' require_relative 'p_object_type' require_relative 'annotation' require_relative 'ruby_method' require_relative 'p_runtime_type' require_relative 'p_sem_ver_type' require_relative 'p_sem_ver_range_type' require_relative 'p_sensitive_type' require_relative 'p_type_set_type' require_relative 'p_timespan_type' require_relative 'p_timestamp_type' require_relative 'p_binary_type' require_relative 'p_init_type' require_relative 'p_object_type_extension' require_relative 'p_uri_type' require_relative 'type_set_reference' require_relative 'implementation_registry' require_relative 'tree_iterators' ��������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/utils.rb��������������������������������������������������������������0000644�0052762�0001160�00000006655�13417161721�017557� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Provides utility methods module Puppet::Pops module Utils # Can the given o be converted to numeric? (or is numeric already) # Accepts a leading '::' # Returns a boolean if the value is numeric # If testing if value can be converted it is more efficient to call {#to_n} or {#to_n_with_radix} directly # and check if value is nil. def self.is_numeric?(o) case o when Numeric true else !!Patterns::NUMERIC.match(relativize_name(o.to_s)) end end # Convert a match from Patterns::NUMERIC to floating point value if # possible def self.match_to_fp(match) if match[5].to_s.length > 0 # Use default radix (default is decimal == 10) for floats # Do not convert a value that is 0 raised to 10^somevalue to float - the value is always 0 # i.e. 0000.0e1, 0e1, 0.0000e1 if Integer(match[4]) == 0 && match[5] =~ /\A\.?0*[eE].*\z/ nil else fp_value = Float(match[2]) if fp_value != Float::INFINITY match[1] == '-' ? -fp_value : fp_value else nil end end end end # To Numeric with radix, or nil if not a number. # If the value is already Numeric it is returned verbatim with a radix of 10. # @param o [String, Number] a string containing a number in octal, hex, integer (decimal) or floating point form # with optional sign +/- # @return [Array<Number, Integer>, nil] array with converted number and radix, or nil if not possible to convert # @api public # def self.to_n_with_radix o begin case o when String match = Patterns::NUMERIC.match(relativize_name(o)) if !match nil elsif match[5].to_s.length > 0 fp_value = match_to_fp(match) fp_value.nil? ? nil : [fp_value, 10] else # Set radix (default is decimal == 10) radix = 10 if match[3].to_s.length > 0 radix = 16 elsif match[4].to_s.length > 1 && match[4][0,1] == '0' radix = 8 end # Ruby 1.8.7 does not have a second argument to Kernel method that creates an # integer from a string, it relies on the prefix 0x, 0X, 0 (and unsupported in puppet binary 'b') # We have the correct string here, match[2] is safe to parse without passing on radix match[1] == '-' ? [-Integer(match[2]), radix] : [Integer(match[2]), radix] end when Numeric # Impossible to calculate radix, assume decimal [o, 10] else nil end rescue ArgumentError nil end end # To Numeric (or already numeric) # Returns nil if value is not numeric, else an Integer or Float. A String may have an optional sign. # # A leading '::' is accepted (and ignored) # def self.to_n o begin case o when String match = Patterns::NUMERIC.match(relativize_name(o)) if !match nil elsif match[5].to_s.length > 0 match_to_fp(match) else match[1] == '-' ? -Integer(match[2]) : Integer(match[2]) end when Numeric o else nil end rescue ArgumentError nil end end # is the name absolute (i.e. starts with ::) def self.is_absolute? name name.start_with? "::".freeze end def self.name_to_segments name name.split("::".freeze) end def self.relativize_name name is_absolute?(name) ? name[2..-1] : name end end end �����������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/validation.rb���������������������������������������������������������0000644�0052762�0001160�00000037442�13417161721�020547� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops # A module with base functionality for validation of a model. # # * **Factory** - an abstract factory implementation that makes it easier to create a new validation factory. # * **SeverityProducer** - produces a severity (:error, :warning, :ignore) for a given Issue # * **DiagnosticProducer** - produces a Diagnostic which binds an Issue to an occurrence of that issue # * **Acceptor** - the receiver/sink/collector of computed diagnostics # * **DiagnosticFormatter** - produces human readable output for a Diagnostic # module Validation # This class is an abstract base implementation of a _model validation factory_ that creates a validator instance # and associates it with a fully configured DiagnosticProducer. # # A _validator_ is responsible for validating a model. There may be different versions of validation available # for one and the same model; e.g. different semantics for different puppet versions, or different types of # validation configuration depending on the context/type of validation that should be performed (static, vs. runtime, etc.). # # This class is abstract and must be subclassed. The subclass must implement the methods # {#label_provider} and {#checker}. It is also expected that the subclass will override # the severity_producer and configure the issues that should be reported as errors (i.e. if they should be ignored, produce # a warning, or a deprecation warning). # # @abstract Subclass must implement {#checker}, and {#label_provider} # @api public # class Factory # Produces a validator with the given acceptor as the recipient of produced diagnostics. # The acceptor is where detected issues are received (and typically collected). # # @param acceptor [Acceptor] the acceptor is the receiver of all detected issues # @return [#validate] a validator responding to `validate(model)` # # @api public # def validator(acceptor) checker(diagnostic_producer(acceptor)) end # Produces the diagnostics producer to use given an acceptor of issues. # # @param acceptor [Acceptor] the acceptor is the receiver of all detected issues # @return [DiagnosticProducer] a detector of issues # # @api public # def diagnostic_producer(acceptor) DiagnosticProducer.new(acceptor, severity_producer(), label_provider()) end # Produces the SeverityProducer to use # Subclasses should implement and add specific overrides # # @return [SeverityProducer] a severity producer producing error, warning or ignore per issue # # @api public # def severity_producer SeverityProducer.new end # Produces the checker to use. # # @abstract # # @api public # def checker(diagnostic_producer) raise NoMethodError, "checker" end # Produces the label provider to use. # # @abstract # # @api public # def label_provider raise NoMethodError, "label_provider" end end # Decides on the severity of a given issue. # The produced severity is one of `:error`, `:warning`, or `:ignore`. # By default, a severity of `:error` is produced for all issues. To configure the severity # of an issue call `#severity=(issue, level)`. # # @return [Symbol] a symbol representing the severity `:error`, `:warning`, or `:ignore` # # @api public # class SeverityProducer @@severity_hash = {:ignore => true, :warning => true, :error => true, :deprecation => true } # Creates a new instance where all issues are diagnosed as :error unless overridden. # @param [Symbol] specifies default severity if :error is not wanted as the default # @api public # def initialize(default_severity = :error) # If diagnose is not set, the default is returned by the block @severities = Hash.new default_severity end # Returns the severity of the given issue. # @return [Symbol] severity level :error, :warning, or :ignore # @api public # def severity(issue) assert_issue(issue) @severities[issue] end # @see {#severity} # @api public # def [] issue severity issue end # Override a default severity with the given severity level. # # @param issue [Issues::Issue] the issue for which to set severity # @param level [Symbol] the severity level (:error, :warning, or :ignore). # @api public # def []=(issue, level) unless issue.is_a? Issues::Issue raise Puppet::DevError.new(_("Attempt to set validation severity for something that is not an Issue. (Got %{issue})") % { issue: issue.class }) end unless @@severity_hash[level] raise Puppet::DevError.new(_("Illegal severity level: %{level} for '%{issue_code}'") % { issue_code: issue.issue_code, level: level }) end unless issue.demotable? || level == :error raise Puppet::DevError.new(_("Attempt to demote the hard issue '%{issue_code}' to %{level}") % { issue_code: issue.issue_code, level: level }) end @severities[issue] = level end # Returns `true` if the issue should be reported or not. # @return [Boolean] this implementation returns true for errors and warnings # # @api public # def should_report? issue diagnose = @severities[issue] diagnose == :error || diagnose == :warning || diagnose == :deprecation end # Checks if the given issue is valid. # @api private # def assert_issue issue unless issue.is_a? Issues::Issue raise Puppet::DevError.new(_("Attempt to get validation severity for something that is not an Issue. (Got %{issue})") % { issue: issue.class }) end end end # A producer of diagnostics. # An producer of diagnostics is given each issue occurrence as they are found by a diagnostician/validator. It then produces # a Diagnostic, which it passes on to a configured Acceptor. # # This class exists to aid a diagnostician/validator which will typically first check if a particular issue # will be accepted at all (before checking for an occurrence of the issue; i.e. to perform check avoidance for expensive checks). # A validator passes an instance of Issue, the semantic object (the "culprit"), a hash with arguments, and an optional # exception. The semantic object is used to determine the location of the occurrence of the issue (file/line), and it # sets keys in the given argument hash that may be used in the formatting of the issue message. # class DiagnosticProducer # A producer of severity for a given issue # @return [SeverityProducer] # attr_reader :severity_producer # A producer of labels for objects involved in the issue # @return [LabelProvider] # attr_reader :label_provider # Initializes this producer. # # @param acceptor [Acceptor] a sink/collector of diagnostic results # @param severity_producer [SeverityProducer] the severity producer to use to determine severity of a given issue # @param label_provider [LabelProvider] a provider of model element type to human readable label # def initialize(acceptor, severity_producer, label_provider) @acceptor = acceptor @severity_producer = severity_producer @label_provider = label_provider end def accept(issue, semantic, arguments={}, except=nil) return unless will_accept? issue # Set label provider unless caller provided a special label provider arguments[:label] ||= @label_provider arguments[:semantic] ||= semantic # A detail message is always provided, but is blank by default. # TODO: this support is questionable, it requires knowledge that :detail is special arguments[:detail] ||= '' # Accept an Error as semantic if it supports methods #file(), #line(), and #pos() if semantic.is_a?(StandardError) unless semantic.respond_to?(:file) && semantic.respond_to?(:line) && semantic.respond_to?(:pos) raise Puppet::DevError, _("Issue %{issue_code}: Cannot pass a %{class_name} as a semantic object when it does not support #pos(), #file() and #line()") % { issue_code: issue.issue_code, class_name: semantic.class } end end source_pos = semantic file = semantic.file unless semantic.nil? severity = @severity_producer.severity(issue) @acceptor.accept(Diagnostic.new(severity, issue, file, source_pos, arguments, except)) end def will_accept? issue @severity_producer.should_report? issue end end class Diagnostic attr_reader :severity attr_reader :issue attr_reader :arguments attr_reader :exception attr_reader :file attr_reader :source_pos def initialize severity, issue, file, source_pos, arguments={}, exception=nil @severity = severity @issue = issue @file = file @source_pos = source_pos @arguments = arguments # TODO: Currently unused, the intention is to provide more information (stack backtrace, etc.) when # debugging or similar - this to catch internal problems reported as higher level issues. @exception = exception end # Two diagnostics are considered equal if the have the same issue, location and severity # (arguments and exception are ignored) # def ==(o) self.class == o.class && same_position?(o) && issue.issue_code == o.issue.issue_code && file == o.file && severity == o.severity end alias eql? == # Position is equal if the diagnostic is not located or if referring to the same offset def same_position?(o) source_pos.nil? && o.source_pos.nil? || source_pos.offset == o.source_pos.offset end private :same_position? def hash @hash ||= [file, source_pos.offset, issue.issue_code, severity].hash end end # Formats a diagnostic for output. # Produces a diagnostic output typical for a compiler (suitable for interpretation by tools) # The format is: # `file:line:pos: Message`, where pos, line and file are included if available. # class DiagnosticFormatter def format diagnostic "#{format_location(diagnostic)} #{format_severity(diagnostic)}#{format_message(diagnostic)}" end def format_message diagnostic diagnostic.issue.format(diagnostic.arguments) end # This produces "Deprecation notice: " prefix if the diagnostic has :deprecation severity, otherwise "". # The idea is that all other diagnostics are emitted with the methods Puppet.err (or an exception), and # Puppet.warning. # @note Note that it is not a good idea to use Puppet.deprecation_warning as it is for internal deprecation. # def format_severity diagnostic diagnostic.severity == :deprecation ? "Deprecation notice: " : "" end def format_location diagnostic file = diagnostic.file file = (file.is_a?(String) && file.empty?) ? nil : file line = pos = nil if diagnostic.source_pos line = diagnostic.source_pos.line pos = diagnostic.source_pos.pos end if file && line && pos "#{file}:#{line}:#{pos}:" elsif file && line "#{file}:#{line}:" elsif file "#{file}:" else "" end end end # Produces a diagnostic output in the "puppet style", where the location is appended with an "at ..." if the # location is known. # class DiagnosticFormatterPuppetStyle < DiagnosticFormatter def format diagnostic if (location = format_location diagnostic) != "" "#{format_severity(diagnostic)}#{format_message(diagnostic)}#{location}" else format_message(diagnostic) end end # The somewhat (machine) unusable format in current use by puppet. # have to be used here for backwards compatibility. def format_location diagnostic file = diagnostic.file file = (file.is_a?(String) && file.empty?) ? nil : file line = pos = nil if diagnostic.source_pos line = diagnostic.source_pos.line pos = diagnostic.source_pos.pos end if file && line && pos " at #{file}:#{line}:#{pos}" elsif file && line " at #{file}:#{line}" elsif line && pos " at line #{line}:#{pos}" elsif line " at line #{line}" elsif file " in #{file}" else "" end end end # An acceptor of diagnostics. # An acceptor of diagnostics is given each issue as they are found by a diagnostician/validator. An # acceptor can collect all found issues, or decide to collect a few and then report, or give up as the first issue # if found. # This default implementation collects all diagnostics in the order they are produced, and can then # answer questions about what was diagnosed. # class Acceptor # All diagnostic in the order they were issued attr_reader :diagnostics # The number of :warning severity issues + number of :deprecation severity issues attr_reader :warning_count # The number of :error severity issues attr_reader :error_count # Initializes this diagnostics acceptor. # By default, the acceptor is configured with a default severity producer. # @param severity_producer [SeverityProducer] the severity producer to use to determine severity of an issue # # TODO add semantic_label_provider # def initialize() @diagnostics = [] @error_count = 0 @warning_count = 0 end # Returns true when errors have been diagnosed. def errors? @error_count > 0 end # Returns true when warnings have been diagnosed. def warnings? @warning_count > 0 end # Returns true when errors and/or warnings have been diagnosed. def errors_or_warnings? errors? || warnings? end # Returns the diagnosed errors in the order they were reported. def errors @diagnostics.select {|d| d.severity == :error } end # Returns the diagnosed warnings in the order they were reported. # (This includes :warning and :deprecation severity) def warnings @diagnostics.select {|d| d.severity == :warning || d.severity == :deprecation } end def errors_and_warnings @diagnostics.select {|d| d.severity != :ignore } end # Returns the ignored diagnostics in the order they were reported (if reported at all) def ignored @diagnostics.select {|d| d.severity == :ignore } end # Add a diagnostic, or all diagnostics from another acceptor to the set of diagnostics # @param diagnostic [Diagnostic, Acceptor] diagnostic(s) that should be accepted def accept(diagnostic) if diagnostic.is_a?(Acceptor) diagnostic.diagnostics.each {|d| self.send(d.severity, d)} else self.send(diagnostic.severity, diagnostic) end end # Prunes the contain diagnostics by removing those for which the given block returns true. # The internal statistics is updated as a consequence of removing. # @return [Array<Diagnostic, nil] the removed set of diagnostics or nil if nothing was removed # def prune(&block) removed = [] @diagnostics.delete_if do |d| if should_remove = yield(d) removed << d end should_remove end removed.each do |d| case d.severity when :error @error_count -= 1 when :warning @warning_count -= 1 # there is not ignore_count end end removed.empty? ? nil : removed end private def ignore diagnostic @diagnostics << diagnostic end def error diagnostic @diagnostics << diagnostic @error_count += 1 end def warning diagnostic @diagnostics << diagnostic @warning_count += 1 end def deprecation diagnostic warning diagnostic end end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/validation/�����������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020215� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/validation/checker4_0.rb����������������������������������������������0000644�0052762�0001160�00000103006�13417161721�022444� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Validation # A Validator validates a model. # # Validation is performed on each model element in isolation. Each method should validate the model element's state # but not validate its referenced/contained elements except to check their validity in their respective role. # The intent is to drive the validation with a tree iterator that visits all elements in a model. # # # TODO: Add validation of multiplicities - this is a general validation that can be checked for all # Model objects via their metamodel. (I.e an extra call to multiplicity check in polymorph check). # This is however mostly valuable when validating model to model transformations, and is therefore T.B.D # class Checker4_0 < Evaluator::LiteralEvaluator attr_reader :acceptor attr_reader :migration_checker def self.check_visitor # Class instance variable rather than Class variable because methods visited # may be overridden in subclass @check_visitor ||= Visitor.new(nil, 'check', 0, 0) end # Initializes the validator with a diagnostics producer. This object must respond to # `:will_accept?` and `:accept`. # def initialize(diagnostics_producer) super() @@rvalue_visitor ||= Visitor.new(nil, "rvalue", 0, 0) @@hostname_visitor ||= Visitor.new(nil, "hostname", 1, 2) @@assignment_visitor ||= Visitor.new(nil, "assign", 0, 1) @@query_visitor ||= Visitor.new(nil, "query", 0, 0) @@relation_visitor ||= Visitor.new(nil, "relation", 0, 0) @@idem_visitor ||= Visitor.new(nil, "idem", 0, 0) @check_visitor = self.class.check_visitor @acceptor = diagnostics_producer # Use null migration checker unless given in context @migration_checker = (Puppet.lookup(:migration_checker) { Migration::MigrationChecker.new() }) end # Validates the entire model by visiting each model element and calling `check`. # The result is collected (or acted on immediately) by the configured diagnostic provider/acceptor # given when creating this Checker. # def validate(model) # tree iterate the model, and call check for each element @path = [] check(model) internal_check_top_construct_in_module(model) model._pcore_all_contents(@path) { |element| check(element) } end def container(index = -1) @path[index] end # Performs regular validity check def check(o) @check_visitor.visit_this_0(self, o) end # Performs check if this is a vaid hostname expression # @param single_feature_name [String, nil] the name of a single valued hostname feature of the value's container. e.g. 'parent' def hostname(o, semantic) @@hostname_visitor.visit_this_1(self, o, semantic) end # Performs check if this is valid as a query def query(o) @@query_visitor.visit_this_0(self, o) end # Performs check if this is valid as a relationship side def relation(o) @@relation_visitor.visit_this_0(self, o) end # Performs check if this is valid as a rvalue def rvalue(o) @@rvalue_visitor.visit_this_0(self, o) end #---TOP CHECK # Performs check if this is valid as a container of a definition (class, define, node) def top(definition, idx = -1) o = container(idx) idx -= 1 case o when NilClass, Model::HostClassDefinition, Model::Program # ok, stop scanning parents when Model::BlockExpression c = container(idx) if !c.is_a?(Model::Program) && (definition.is_a?(Model::FunctionDefinition) || definition.is_a?(Model::TypeAlias) || definition.is_a?(Model::TypeDefinition)) # not ok. These can never be nested in a block acceptor.accept(Issues::NOT_ABSOLUTE_TOP_LEVEL, definition) else # ok, if this is a block representing the body of a class, or is top level top(definition, idx) end when Model::LambdaExpression # A LambdaExpression is a BlockExpression, and this check is needed to prevent the polymorph method for BlockExpression # to accept a lambda. # A lambda can not iteratively create classes, nodes or defines as the lambda does not have a closure. acceptor.accept(Issues::NOT_TOP_LEVEL, definition) else # fail, reached a container that is not top level acceptor.accept(Issues::NOT_TOP_LEVEL, definition) end end # Checks the LHS of an assignment (is it assignable?). # If args[0] is true, assignment via index is checked. # def assign(o, via_index = false) @@assignment_visitor.visit_this_1(self, o, via_index) end # Checks if the expression has side effect ('idem' is latin for 'the same', here meaning that the evaluation state # is known to be unchanged after the expression has been evaluated). The result is not 100% authoritative for # negative answers since analysis of function behavior is not possible. # @return [Boolean] true if expression is known to have no effect on evaluation state # def idem(o) @@idem_visitor.visit_this_0(self, o) end # Returns the last expression in a block, or the expression, if that expression is idem def ends_with_idem(o) if o.is_a?(Model::BlockExpression) last = o.statements[-1] idem(last) ? last : nil else idem(o) ? o : nil end end #---ASSIGNMENT CHECKS def assign_VariableExpression(o, via_index) varname_string = varname_to_s(o.expr) if varname_string =~ Patterns::NUMERIC_VAR_NAME acceptor.accept(Issues::ILLEGAL_NUMERIC_ASSIGNMENT, o, :varname => varname_string) end # Can not assign to something in another namespace (i.e. a '::' in the name is not legal) if acceptor.will_accept? Issues::CROSS_SCOPE_ASSIGNMENT if varname_string =~ /::/ acceptor.accept(Issues::CROSS_SCOPE_ASSIGNMENT, o, :name => varname_string) end end # TODO: Could scan for reassignment of the same variable if done earlier in the same container # Or if assigning to a parameter (more work). end def assign_AccessExpression(o, via_index) # Are indexed assignments allowed at all ? $x[x] = '...' if acceptor.will_accept? Issues::ILLEGAL_INDEXED_ASSIGNMENT acceptor.accept(Issues::ILLEGAL_INDEXED_ASSIGNMENT, o) else # Then the left expression must be assignable-via-index assign(o.left_expr, true) end end def assign_LiteralList(o, via_index) o.values.each {|x| assign(x) } end def assign_Object(o, via_index) # Can not assign to anything else (differentiate if this is via index or not) # i.e. 10 = 'hello' vs. 10['x'] = 'hello' (the root is reported as being in error in both cases) # acceptor.accept(via_index ? Issues::ILLEGAL_ASSIGNMENT_VIA_INDEX : Issues::ILLEGAL_ASSIGNMENT, o) end #---CHECKS def check_Object(o) end def check_Factory(o) check(o.model) end def check_AccessExpression(o) # Only min range is checked, all other checks are RT checks as they depend on the resulting type # of the LHS. if o.keys.size < 1 acceptor.accept(Issues::MISSING_INDEX, o) end end def check_AssignmentExpression(o) case o.operator when '=' assign(o.left_expr) rvalue(o.right_expr) when '+=', '-=' acceptor.accept(Issues::APPENDS_DELETES_NO_LONGER_SUPPORTED, o, {:operator => o.operator}) else acceptor.accept(Issues::UNSUPPORTED_OPERATOR, o, {:operator => o.operator}) end end # Checks that operation with :+> is contained in a ResourceOverride or Collector. # # Parent of an AttributeOperation can be one of: # * CollectExpression # * ResourceOverride # * ResourceBody (ILLEGAL this is a regular resource expression) # * ResourceDefaults (ILLEGAL) # def check_AttributeOperation(o) if o.operator == '+>' # Append operator use is constrained p = container unless p.is_a?(Model::CollectExpression) || p.is_a?(Model::ResourceOverrideExpression) acceptor.accept(Issues::ILLEGAL_ATTRIBUTE_APPEND, o, {:name=>o.attribute_name, :parent=>p}) end end rvalue(o.value_expr) end def check_AttributesOperation(o) # Append operator use is constrained p = container case p when Model::AbstractResource when Model::CollectExpression when Model::CapabilityMapping acceptor.accept(Issues::UNSUPPORTED_OPERATOR_IN_CONTEXT, p, :operator=>'* =>') else # protect against just testing a snippet that has no parent, error message will be a bit strange # but it is not for a real program. parent2 = p.nil? ? o : container(-2) unless parent2.is_a?(Model::AbstractResource) acceptor.accept(Issues::UNSUPPORTED_OPERATOR_IN_CONTEXT, parent2, :operator=>'* =>') end end rvalue(o.expr) end def check_BinaryExpression(o) rvalue(o.left_expr) rvalue(o.right_expr) end def resource_without_title?(o) if o.instance_of?(Model::BlockExpression) statements = o.statements statements.length == 2 && statements[0].instance_of?(Model::QualifiedName) && statements[1].instance_of?(Model::LiteralHash) else false end end def check_BlockExpression(o) if resource_without_title?(o) acceptor.accept(Issues::RESOURCE_WITHOUT_TITLE, o, :name => o.statements[0].value) else o.statements[0..-2].each do |statement| if idem(statement) acceptor.accept(Issues::IDEM_EXPRESSION_NOT_LAST, statement) break # only flag the first end end end end def check_CallNamedFunctionExpression(o) functor = o.functor_expr if functor.is_a?(Model::QualifiedReference) || functor.is_a?(Model::AccessExpression) && functor.left_expr.is_a?(Model::QualifiedReference) # ok (a call to a type) return nil end case functor when Model::QualifiedName # ok nil when Model::RenderStringExpression # helpful to point out this easy to make Epp error acceptor.accept(Issues::ILLEGAL_EPP_PARAMETERS, o) else acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.functor_expr, {:feature=>'function name', :container => o}) end end def check_CapabilityMapping(o) ok = case o.component when Model::QualifiedReference name = o.component.cased_value acceptor.accept(Issues::ILLEGAL_CLASSREF, o.component, {:name=>name}) unless name =~ Patterns::CLASSREF_EXT true when Model::AccessExpression keys = o.component.keys expr = o.component.left_expr if expr.is_a?(Model::QualifiedReference) && keys.size == 1 key = keys[0] key.is_a?(Model::LiteralString) || key.is_a?(Model::QualifiedName) || key.is_a?(Model::QualifiedReference) else false end else false end acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.component, :feature=>'capability mapping', :container => o) unless ok if o.capability !~ Patterns::CLASSREF_EXT acceptor.accept(Issues::ILLEGAL_CLASSREF, o, {:name=>o.capability}) end end def check_EppExpression(o) p = container if p.is_a?(Model::LambdaExpression) internal_check_no_capture(p, o) internal_check_parameter_name_uniqueness(p) end end def check_MethodCallExpression(o) unless o.functor_expr.is_a? Model::QualifiedName acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.functor_expr, :feature => 'function name', :container => o) end end def check_CaseExpression(o) rvalue(o.test) # There can only be one LiteralDefault case option value found_default = false o.options.each do |option| option.values.each do |value| if value.is_a?(Model::LiteralDefault) # Flag the second default as 'unreachable' acceptor.accept(Issues::DUPLICATE_DEFAULT, value, :container => o) if found_default found_default = true end end end end def check_CaseOption(o) o.values.each { |v| rvalue(v) } end def check_CollectExpression(o) unless o.type_expr.is_a? Model::QualifiedReference acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.type_expr, :feature=> 'type name', :container => o) end end # Only used for function names, grammar should not be able to produce something faulty, but # check anyway if model is created programmatically (it will fail in transformation to AST for sure). def check_NamedAccessExpression(o) name = o.right_expr unless name.is_a? Model::QualifiedName acceptor.accept(Issues::ILLEGAL_EXPRESSION, name, :feature=> 'function name', :container => container) end end RESERVED_TYPE_NAMES = { 'type' => true, 'any' => true, 'unit' => true, 'scalar' => true, 'boolean' => true, 'numeric' => true, 'integer' => true, 'float' => true, 'collection' => true, 'array' => true, 'hash' => true, 'tuple' => true, 'struct' => true, 'variant' => true, 'optional' => true, 'enum' => true, 'regexp' => true, 'pattern' => true, 'runtime' => true, } FUTURE_RESERVED_WORDS = { 'plan' => true } # for 'class', 'define', and function def check_NamedDefinition(o) top(o) if o.name !~ Patterns::CLASSREF_DECL acceptor.accept(Issues::ILLEGAL_DEFINITION_NAME, o, {:name=>o.name}) end internal_check_file_namespace(o) internal_check_reserved_type_name(o, o.name) internal_check_future_reserved_word(o, o.name) end def check_TypeAlias(o) top(o) if o.name !~ Patterns::CLASSREF_EXT_DECL acceptor.accept(Issues::ILLEGAL_DEFINITION_NAME, o, {:name=>o.name}) end internal_check_reserved_type_name(o, o.name) internal_check_type_ref(o, o.type_expr) end def check_TypeMapping(o) top(o) lhs = o.type_expr lhs_type = 0 # Not Runtime if lhs.is_a?(Model::AccessExpression) left = lhs.left_expr if left.is_a?(Model::QualifiedReference) && left.cased_value == 'Runtime' lhs_type = 1 # Runtime keys = lhs.keys # Must be a literal string or pattern replacement lhs_type = 2 if keys.size == 2 && pattern_with_replacement?(keys[1]) end end if lhs_type == 0 # This is not a TypeMapping. Something other than Runtime[] on LHS acceptor.accept(Issues::UNSUPPORTED_EXPRESSION, o) else rhs = o.mapping_expr if pattern_with_replacement?(rhs) acceptor.accept(Issues::ILLEGAL_SINGLE_TYPE_MAPPING, o) if lhs_type == 1 elsif type_ref?(rhs) acceptor.accept(Issues::ILLEGAL_REGEXP_TYPE_MAPPING, o) if lhs_type == 2 else acceptor.accept(lhs_type == 1 ? Issues::ILLEGAL_SINGLE_TYPE_MAPPING : Issues::ILLEGAL_REGEXP_TYPE_MAPPING, o) end end end def pattern_with_replacement?(o) if o.is_a?(Model::LiteralList) v = o.values v.size == 2 && v[0].is_a?(Model::LiteralRegularExpression) && v[1].is_a?(Model::LiteralString) else false end end def type_ref?(o) o = o.left_expr if o.is_a?(Model::AccessExpression) o.is_a?(Model::QualifiedReference) end def check_TypeDefinition(o) top(o) internal_check_reserved_type_name(o, o.name) # TODO: Check TypeDefinition body. For now, just error out acceptor.accept(Issues::UNSUPPORTED_EXPRESSION, o) end def check_FunctionDefinition(o) check_NamedDefinition(o) internal_check_return_type(o) internal_check_parameter_name_uniqueness(o) end def check_HostClassDefinition(o) check_NamedDefinition(o) internal_check_no_capture(o) internal_check_parameter_name_uniqueness(o) internal_check_reserved_params(o) internal_check_no_idem_last(o) end def check_ResourceTypeDefinition(o) check_NamedDefinition(o) internal_check_no_capture(o) internal_check_parameter_name_uniqueness(o) internal_check_reserved_params(o) internal_check_no_idem_last(o) end def internal_check_return_type(o) r = o.return_type internal_check_type_ref(o, r) unless r.nil? end def internal_check_type_ref(o, r) n = r.is_a?(Model::AccessExpression) ? r.left_expr : r if n.is_a? Model::QualifiedReference internal_check_future_reserved_word(r, n.value) else acceptor.accept(Issues::ILLEGAL_EXPRESSION, r, :feature => 'a type reference', :container => o) end end def internal_check_no_idem_last(o) if violator = ends_with_idem(o.body) acceptor.accept(Issues::IDEM_NOT_ALLOWED_LAST, violator, {:container => o}) unless resource_without_title?(violator) end end def internal_check_capture_last(o) accepted_index = o.parameters.size() -1 o.parameters.each_with_index do |p, index| if p.captures_rest && index != accepted_index acceptor.accept(Issues::CAPTURES_REST_NOT_LAST, p, {:param_name => p.name}) end end end def internal_check_no_capture(o, container = o) o.parameters.each do |p| if p.captures_rest acceptor.accept(Issues::CAPTURES_REST_NOT_SUPPORTED, p, {:container => container, :param_name => p.name}) end end end def internal_check_reserved_type_name(o, name) if RESERVED_TYPE_NAMES[name] acceptor.accept(Issues::RESERVED_TYPE_NAME, o, {:name => name}) end end def internal_check_future_reserved_word(o, name) if FUTURE_RESERVED_WORDS[name] acceptor.accept(Issues::FUTURE_RESERVED_WORD, o, {:word => name}) end end NO_NAMESPACE = :no_namespace NO_PATH = :no_path BAD_MODULE_FILE = :bad_module_file def internal_check_file_namespace(o) file = o.locator.file return if file.nil? || file == '' #e.g. puppet apply -e '...' file_namespace = namespace_for_file(file) return if file_namespace == NO_NAMESPACE # Downcasing here because check is case-insensitive if file_namespace == BAD_MODULE_FILE || !o.name.downcase.start_with?(file_namespace) acceptor.accept(Issues::ILLEGAL_DEFINITION_LOCATION, o, {:name => o.name, :file => file}) end end def internal_check_top_construct_in_module(prog) return unless prog.is_a?(Model::Program) && !prog.body.nil? #Check that this is a module autoloaded file file = prog.locator.file return if file.nil? return if namespace_for_file(file) == NO_NAMESPACE body = prog.body return if prog.body.is_a?(Model::Nop) #Ignore empty or comment-only files if(body.is_a?(Model::BlockExpression)) body.statements.each { |s| acceptor.accept(Issues::ILLEGAL_TOP_CONSTRUCT_LOCATION, s) unless valid_top_construct?(s) } else acceptor.accept(Issues::ILLEGAL_TOP_CONSTRUCT_LOCATION, body) unless valid_top_construct?(body) end end def valid_top_construct?(o) o.is_a?(Model::Definition) && !o.is_a?(Model::NodeDefinition) end # @api private class Puppet::Util::FileNamespaceAdapter < Puppet::Pops::Adaptable::Adapter attr_accessor :file_to_namespace end def namespace_for_file(file) env = Puppet.lookup(:current_environment) return NO_NAMESPACE if env.nil? Puppet::Util::FileNamespaceAdapter.adapt(env) do |adapter| adapter.file_to_namespace ||= {} file_namespace = adapter.file_to_namespace[file] return file_namespace unless file_namespace.nil? # No cache entry, so we do the calculation path = Pathname.new(file) return adapter.file_to_namespace[file] = NO_NAMESPACE if path.extname != ".pp" path = path.expand_path return adapter.file_to_namespace[file] = NO_NAMESPACE if initial_manifest?(path, env.manifest) #All auto-loaded files from modules come from a module search path dir relative_path = get_module_relative_path(path, env.full_modulepath) return adapter.file_to_namespace[file] = NO_NAMESPACE if relative_path == NO_PATH #If a file comes from a module, but isn't in the right place, always error names = dir_to_names(relative_path) return adapter.file_to_namespace[file] = (names == BAD_MODULE_FILE ? BAD_MODULE_FILE : names.join("::").freeze) end end def initial_manifest?(path, manifest_setting) return false if manifest_setting.nil? || manifest_setting == :no_manifest string_path = path.to_s string_path == manifest_setting || string_path.start_with?(manifest_setting) end def get_module_relative_path(file_path, modulepath_directories) clean_file = file_path.cleanpath parent_path = modulepath_directories.find { |path_dir| is_parent_dir_of(path_dir, clean_file) } return NO_PATH if parent_path.nil? file_path.relative_path_from(Pathname.new(parent_path)) end def is_parent_dir_of(parent_dir, child_dir) parent_dir_path = Pathname.new(parent_dir) clean_parent = parent_dir_path.cleanpath.to_s + File::SEPARATOR return child_dir.to_s.start_with?(clean_parent) end def dir_to_names(relative_path) # Downcasing here because check is case-insensitive path_components = relative_path.to_s.downcase.split(File::SEPARATOR) # Example definition dir: manifests in this path: # <module name>/manifests/<module subdir>/<classfile>.pp dir = path_components[1] # How can we get this result? # If it is not an initial manifest, it must come from a module, # and from the manifests dir there. This may never get used... return BAD_MODULE_FILE unless dir == 'manifests' || dir == 'functions' || dir == 'types' || dir == 'plans' names = path_components[2 .. -2] # Directories inside module names.unshift(path_components[0]) # Name of the module itself # Do not include name of module init file at top level of module # e.g. <module name>/manifests/init.pp filename = path_components[-1] if !(path_components.length == 3 && filename == 'init.pp') names.push(filename[0 .. -4]) # Remove .pp from filename end names end RESERVED_PARAMETERS = { 'name' => true, 'title' => true, } def internal_check_reserved_params(o) o.parameters.each do |p| if RESERVED_PARAMETERS[p.name] acceptor.accept(Issues::RESERVED_PARAMETER, p, {:container => o, :param_name => p.name}) end end end def internal_check_parameter_name_uniqueness(o) unique = Set.new o.parameters.each do |p| acceptor.accept(Issues::DUPLICATE_PARAMETER, p, {:param_name => p.name}) unless unique.add?(p.name) end end def check_IfExpression(o) rvalue(o.test) end def check_KeyedEntry(o) rvalue(o.key) rvalue(o.value) # In case there are additional things to forbid than non-rvalues # acceptor.accept(Issues::ILLEGAL_EXPRESSION, o.key, :feature => 'hash key', :container => container) end def check_LambdaExpression(o) internal_check_capture_last(o) internal_check_return_type(o) end def check_LiteralList(o) o.values.each {|v| rvalue(v) } end def check_LiteralInteger(o) v = o.value if v < MIN_INTEGER || v > MAX_INTEGER acceptor.accept(Issues::NUMERIC_OVERFLOW, o, {:value => v}) end end def check_LiteralHash(o) # the keys of a literal hash may be non-literal expressions. They cannot be checked. unique = Set.new o.entries.each do |entry| catch(:not_literal) do literal_key = literal(entry.key) acceptor.accept(Issues::DUPLICATE_KEY, entry, {:key => literal_key}) if unique.add?(literal_key).nil? end end end def check_NodeDefinition(o) # Check that hostnames are valid hostnames (or regular expressions) hostname(o.host_matches, o) top(o) if violator = ends_with_idem(o.body) acceptor.accept(Issues::IDEM_NOT_ALLOWED_LAST, violator, {:container => o}) unless resource_without_title?(violator) end unless o.parent.nil? acceptor.accept(Issues::ILLEGAL_NODE_INHERITANCE, o.parent) end end # No checking takes place - all expressions using a QualifiedName need to check. This because the # rules are slightly different depending on the container (A variable allows a numeric start, but not # other names). This means that (if the lexer/parser so chooses) a QualifiedName # can be anything when it represents a Bare Word and evaluates to a String. # def check_QualifiedName(o) end # Checks that the value is a valid UpperCaseWord (a CLASSREF), and optionally if it contains a hypen. # DOH: QualifiedReferences are created with LOWER CASE NAMES at parse time def check_QualifiedReference(o) # Is this a valid qualified name? if o.cased_value !~ Patterns::CLASSREF_EXT acceptor.accept(Issues::ILLEGAL_CLASSREF, o, {:name=>o.cased_value}) end end def check_QueryExpression(o) query(o.expr) if o.expr # is optional end def relation_Object(o) rvalue(o) end def relation_CollectExpression(o); end def relation_RelationshipExpression(o); end def check_Parameter(o) if o.name =~ /^(?:0x)?[0-9]+$/ acceptor.accept(Issues::ILLEGAL_NUMERIC_PARAMETER, o, :name => o.name) end unless o.name =~ Patterns::PARAM_NAME acceptor.accept(Issues::ILLEGAL_PARAM_NAME, o, :name => o.name) end return unless o.value internal_check_illegal_assignment(o.value) end def internal_check_illegal_assignment(o) if o.is_a?(Model::AssignmentExpression) acceptor.accept(Issues::ILLEGAL_ASSIGNMENT_CONTEXT, o) else # recursively check all contents unless it's a lambda expression. A lambda may contain # local assignments o._pcore_contents {|model| internal_check_illegal_assignment(model) } unless o.is_a?(Model::LambdaExpression) end end #relationship_side: resource # | resourceref # | collection # | variable # | quotedtext # | selector # | casestatement # | hasharrayaccesses def check_RelationshipExpression(o) relation(o.left_expr) relation(o.right_expr) end def check_ResourceExpression(o) # The expression for type name cannot be statically checked - this is instead done at runtime # to enable better error message of the result of the expression rather than the static instruction. # (This can be revised as there are static constructs that are illegal, but require updating many # tests that expect the detailed reporting). type_name_expr = o.type_name if o.form && o.form != 'regular' && type_name_expr.is_a?(Model::QualifiedName) && type_name_expr.value == 'class' acceptor.accept(Issues::CLASS_NOT_VIRTUALIZABLE, o) end end def check_ResourceBody(o) seenUnfolding = false o.operations.each do |ao| if ao.is_a?(Model::AttributesOperation) if seenUnfolding acceptor.accept(Issues::MULTIPLE_ATTRIBUTES_UNFOLD, ao) else seenUnfolding = true end end end end def check_ResourceDefaultsExpression(o) if o.form != 'regular' acceptor.accept(Issues::NOT_VIRTUALIZEABLE, o) end end def check_ResourceOverrideExpression(o) if o.form != 'regular' acceptor.accept(Issues::NOT_VIRTUALIZEABLE, o) end end def check_ReservedWord(o) if o.future acceptor.accept(Issues::FUTURE_RESERVED_WORD, o, :word => o.word) else acceptor.accept(Issues::RESERVED_WORD, o, :word => o.word) end end def check_SelectorExpression(o) rvalue(o.left_expr) # There can only be one LiteralDefault case option value defaults = o.selectors.select {|v| v.matching_expr.is_a?(Model::LiteralDefault) } unless defaults.size <= 1 # Flag the second default as 'unreachable' acceptor.accept(Issues::DUPLICATE_DEFAULT, defaults[1].matching_expr, :container => o) end end def check_SelectorEntry(o) rvalue(o.matching_expr) end def check_UnaryExpression(o) rvalue(o.expr) end def check_UnlessExpression(o) rvalue(o.test) # TODO: Unless may not have an else part that is an IfExpression (grammar denies this though) end # Checks that variable is either strictly 0, or a non 0 starting decimal number, or a valid VAR_NAME def check_VariableExpression(o) # The expression must be a qualified name or an integer name_expr = o.expr return if name_expr.is_a?(Model::LiteralInteger) if !name_expr.is_a?(Model::QualifiedName) acceptor.accept(Issues::ILLEGAL_EXPRESSION, o, :feature => 'name', :container => o) else # name must be either a decimal string value, or a valid NAME name = o.expr.value if name[0,1] =~ /[0-9]/ unless name =~ Patterns::NUMERIC_VAR_NAME acceptor.accept(Issues::ILLEGAL_NUMERIC_VAR_NAME, o, :name => name) end else unless name =~ Patterns::VAR_NAME acceptor.accept(Issues::ILLEGAL_VAR_NAME, o, :name => name) end end end end #--- HOSTNAME CHECKS # Transforms Array of host matching expressions into a (Ruby) array of AST::HostName def hostname_Array(o, semantic) o.each {|x| hostname(x, semantic) } end def hostname_String(o, semantic) # The 3.x checker only checks for illegal characters - if matching /[^-\w.]/ the name is invalid, # but this allows pathological names like "a..b......c", "----" # TODO: Investigate if more illegal hostnames should be flagged. # if o =~ Patterns::ILLEGAL_HOSTNAME_CHARS acceptor.accept(Issues::ILLEGAL_HOSTNAME_CHARS, semantic, :hostname => o) end end def hostname_LiteralValue(o, semantic) hostname_String(o.value.to_s, o) end def hostname_ConcatenatedString(o, semantic) # Puppet 3.1. only accepts a concatenated string without interpolated expressions if the_expr = o.segments.index {|s| s.is_a?(Model::TextExpression) } acceptor.accept(Issues::ILLEGAL_HOSTNAME_INTERPOLATION, o.segments[the_expr].expr) elsif o.segments.size() != 1 # corner case, bad model, concatenation of several plain strings acceptor.accept(Issues::ILLEGAL_HOSTNAME_INTERPOLATION, o) else # corner case, may be ok, but lexer may have replaced with plain string, this is # here if it does not hostname_String(o.segments[0], o.segments[0]) end end def hostname_QualifiedName(o, semantic) hostname_String(o.value.to_s, o) end def hostname_QualifiedReference(o, semantic) hostname_String(o.value.to_s, o) end def hostname_LiteralNumber(o, semantic) # always ok end def hostname_LiteralDefault(o, semantic) # always ok end def hostname_LiteralRegularExpression(o, semantic) # always ok end def hostname_Object(o, semantic) acceptor.accept(Issues::ILLEGAL_EXPRESSION, o, {:feature => 'hostname', :container => semantic}) end #---QUERY CHECKS # Anything not explicitly allowed is flagged as error. def query_Object(o) acceptor.accept(Issues::ILLEGAL_QUERY_EXPRESSION, o) end # Puppet AST only allows == and != # def query_ComparisonExpression(o) acceptor.accept(Issues::ILLEGAL_QUERY_EXPRESSION, o) unless ['==', '!='].include? o.operator end # Allows AND, OR, and checks if left/right are allowed in query. def query_BooleanExpression(o) query o.left_expr query o.right_expr end def query_ParenthesizedExpression(o) query(o.expr) end def query_VariableExpression(o); end def query_QualifiedName(o); end def query_LiteralNumber(o); end def query_LiteralString(o); end def query_LiteralBoolean(o); end #---RVALUE CHECKS # By default, all expressions are reported as being rvalues # Implement specific rvalue checks for those that are not. # def rvalue_Expression(o); end def rvalue_CollectExpression(o) acceptor.accept(Issues::NOT_RVALUE, o) end def rvalue_Definition(o) acceptor.accept(Issues::NOT_RVALUE, o) end def rvalue_NodeDefinition(o) acceptor.accept(Issues::NOT_RVALUE, o) end def rvalue_UnaryExpression(o) rvalue o.expr end #--IDEM CHECK def idem_Object(o) false end def idem_Nop(o) true end def idem_NilClass(o) true end def idem_Literal(o) true end def idem_LiteralList(o) true end def idem_LiteralHash(o) true end def idem_Factory(o) idem(o.model) end def idem_AccessExpression(o) true end def idem_BinaryExpression(o) true end def idem_MatchExpression(o) false # can have side effect of setting $n match variables end def idem_RelationshipExpression(o) # Always side effect false end def idem_AssignmentExpression(o) # Always side effect false end # Handles UnaryMinusExpression, NotExpression, VariableExpression def idem_UnaryExpression(o) true end # Allow (no-effect parentheses) to be used around a productive expression def idem_ParenthesizedExpression(o) idem(o.expr) end def idem_RenderExpression(o) false end def idem_RenderStringExpression(o) false end def idem_BlockExpression(o) # productive if there is at least one productive expression ! o.statements.any? {|expr| !idem(expr) } end # Returns true even though there may be interpolated expressions that have side effect. # Report as idem anyway, as it is very bad design to evaluate an interpolated string for its # side effect only. def idem_ConcatenatedString(o) true end # Heredoc is just a string, but may contain interpolated string (which may have side effects). # This is still bad design and should be reported as idem. def idem_HeredocExpression(o) true end # May technically have side effects inside the Selector, but this is bad design - treat as idem def idem_SelectorExpression(o) true end def idem_IfExpression(o) [o.test, o.then_expr, o.else_expr].all? {|e| idem(e) } end # Case expression is idem, if test, and all options are idem def idem_CaseExpression(o) return false if !idem(o.test) ! o.options.any? {|opt| !idem(opt) } end # An option is idem if values and the then_expression are idem def idem_CaseOption(o) return false if o.values.any? { |value| !idem(value) } idem(o.then_expr) end #--- NON POLYMORPH, NON CHECKING CODE # Produces string part of something named, or nil if not a QualifiedName or QualifiedReference # def varname_to_s(o) case o when Model::QualifiedName o.value when Model::QualifiedReference o.value else nil end end end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/validation/tasks_checker.rb�������������������������������������������0000644�0052762�0001160�00000002177�13417161721�023355� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Validation # Validator that limits the set of allowed expressions to not include catalog related operations # @api private class TasksChecker < Checker4_0 def check_Application(o) illegalTasksExpression(o) end def check_CapabilityMapping(o) illegalTasksExpression(o) end def check_CollectExpression(o) illegalTasksExpression(o) end def check_HostClassDefinition(o) illegalTasksExpression(o) end def check_NodeDefinition(o) illegalTasksExpression(o) end def check_RelationshipExpression(o) illegalTasksExpression(o) end def check_ResourceDefaultsExpression(o) illegalTasksExpression(o) end def check_ResourceExpression(o) illegalTasksExpression(o) end def check_ResourceOverrideExpression(o) illegalTasksExpression(o) end def check_ResourceTypeDefinition(o) illegalTasksExpression(o) end def check_SiteDefinition(o) illegalTasksExpression(o) end def illegalTasksExpression(o) acceptor.accept(Issues::CATALOG_OPERATION_NOT_SUPPORTED_WHEN_SCRIPTING, o) end def resource_without_title?(o) false end end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/validation/validator_factory_4_0.rb�����������������������������������0000644�0052762�0001160�00000002540�13417161721�024714� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Validation # Configures validation suitable for 4.0 # class ValidatorFactory_4_0 < Factory Issues = Issues # Produces the checker to use def checker diagnostic_producer if Puppet[:tasks] require_relative 'tasks_checker' TasksChecker.new(diagnostic_producer) else Checker4_0.new(diagnostic_producer) end end # Produces the label provider to use def label_provider Model::ModelLabelProvider.new() end # Produces the severity producer to use def severity_producer p = super # Configure each issue that should **not** be an error # # Validate as per the current runtime configuration p[Issues::RT_NO_STORECONFIGS_EXPORT] = Puppet[:storeconfigs] ? :ignore : :warning p[Issues::RT_NO_STORECONFIGS] = Puppet[:storeconfigs] ? :ignore : :warning p[Issues::FUTURE_RESERVED_WORD] = :deprecation p[Issues::DUPLICATE_KEY] = Puppet[:strict] == :off ? :ignore : Puppet[:strict] p[Issues::NAME_WITH_HYPHEN] = :error p[Issues::EMPTY_RESOURCE_SPECIALIZATION] = :ignore p[Issues::CLASS_NOT_VIRTUALIZABLE] = Puppet[:strict] == :off ? :warning : Puppet[:strict] p[Issues::ILLEGAL_DEFINITION_LOCATION] = :deprecation p[Issues::ILLEGAL_TOP_CONSTRUCT_LOCATION] = :deprecation p end end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/visitable.rb����������������������������������������������������������0000644�0052762�0001160�00000000270�13417161721�020364� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Visitable is a mix-in module that makes a class visitable by a Visitor module Puppet::Pops::Visitable def accept(visitor, *arguments) visitor.visit(self, *arguments) end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/visitor.rb������������������������������������������������������������0000644�0052762�0001160�00000010122�13417161721�020076� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops # A Visitor performs delegation to a given receiver based on the configuration of the Visitor. # A new visitor is created with a given receiver, a method prefix, min, and max argument counts. # e.g. # visitor = Visitor.new(self, "visit_from", 1, 1) # will make the visitor call "self.visit_from_CLASS(x)" where CLASS is resolved to the given # objects class, or one of is ancestors, the first class for which there is an implementation of # a method will be selected. # # Raises RuntimeError if there are too few or too many arguments, or if the receiver is not # configured to handle a given visiting object. # class Visitor attr_reader :receiver, :message, :min_args, :max_args, :cache def initialize(receiver, message, min_args=0, max_args=nil) raise ArgumentError.new("min_args must be >= 0") if min_args < 0 raise ArgumentError.new("max_args must be >= min_args or nil") if max_args && max_args < min_args @receiver = receiver @message = message @min_args = min_args @max_args = max_args @cache = Hash.new end # Visit the configured receiver def visit(thing, *args) visit_this(@receiver, thing, args) end NO_ARGS = EMPTY_ARRAY # Visit an explicit receiver def visit_this(receiver, thing, args) raise "Visitor Error: Too few arguments passed. min = #{@min_args}" unless args.length >= @min_args if @max_args raise "Visitor Error: Too many arguments passed. max = #{@max_args}" unless args.length <= @max_args end if method_name = @cache[thing.class] return receiver.send(method_name, thing, *args) else thing.class.ancestors().each do |ancestor| name = ancestor.name next if name.nil? method_name = :"#{@message}_#{name.split(DOUBLE_COLON).last}" next unless receiver.respond_to?(method_name, true) @cache[thing.class] = method_name return receiver.send(method_name, thing, *args) end end raise "Visitor Error: the configured receiver (#{receiver.class}) can't handle instance of: #{thing.class}" end # Visit an explicit receiver def visit_this_class(receiver, clazz, args) raise "Visitor Error: Too few arguments passed. min = #{@min_args}" unless args.length >= @min_args if @max_args raise "Visitor Error: Too many arguments passed. max = #{@max_args}" unless args.length <= @max_args end if method_name = @cache[clazz] return receiver.send(method_name, clazz, *args) else clazz.ancestors().each do |ancestor| name = ancestor.name next if name.nil? method_name = :"#{@message}_#{name.split(DOUBLE_COLON).last}" next unless receiver.respond_to?(method_name, true) @cache[clazz] = method_name return receiver.send(method_name, clazz, *args) end end raise "Visitor Error: the configured receiver (#{receiver.class}) can't handle instance of: #{clazz}" end # Visit an explicit receiver with 0 args # (This is ~30% faster than calling the general method) # def visit_this_0(receiver, thing) if method_name = @cache[thing.class] return receiver.send(method_name, thing) end visit_this(receiver, thing, NO_ARGS) end # Visit an explicit receiver with 1 args # (This is ~30% faster than calling the general method) # def visit_this_1(receiver, thing, arg) if method_name = @cache[thing.class] return receiver.send(method_name, thing, arg) end visit_this(receiver, thing, [arg]) end # Visit an explicit receiver with 2 args # (This is ~30% faster than calling the general method) # def visit_this_2(receiver, thing, arg1, arg2) if method_name = @cache[thing.class] return receiver.send(method_name, thing, arg1, arg2) end visit_this(receiver, thing, [arg1, arg2]) end # Visit an explicit receiver with 3 args # (This is ~30% faster than calling the general method) # def visit_this_3(receiver, thing, arg1, arg2, arg3) if method_name = @cache[thing.class] return receiver.send(method_name, thing, arg1, arg2, arg3) end visit_this(receiver, thing, [arg1, arg2, arg3]) end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/adapters.rb�����������������������������������������������������������0000644�0052762�0001160�00000011667�13417161721�020221� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The Adapters module contains adapters for Documentation, Origin, SourcePosition, and Loader. # module Puppet::Pops module Adapters # A documentation adapter adapts an object with a documentation string. # (The intended use is for a source text parser to extract documentation and store this # in DocumentationAdapter instances). # class DocumentationAdapter < Adaptable::Adapter # @return [String] The documentation associated with an object attr_accessor :documentation end # An empty alternative adapter is used when there is the need to # attach a value to be used if the original is empty. This is used # when a lazy evaluation takes place, and the decision how to handle an # empty case must be delayed. # class EmptyAlternativeAdapter < Adaptable::Adapter # @return [Object] The alternative value associated with an object attr_accessor :empty_alternative end # This class is for backward compatibility only. It's not really an adapter but it is # needed for the puppetlabs-strings gem # @deprecated class SourcePosAdapter def self.adapt(object) new(object) end def initialize(object) @object = object end def file @object.file end def line @object.line end def pos @object.pos end def extract_text @object.locator.extract_text(@object.offset, @object.length) end end # A LoaderAdapter adapts an object with a {Loader}. This is used to make further loading from the # perspective of the adapted object take place in the perspective of this Loader. # # It is typically enough to adapt the root of a model as a search is made towards the root of the model # until a loader is found, but there is no harm in duplicating this information provided a contained # object is adapted with the correct loader. # # @see Utils#find_adapter # @api private class LoaderAdapter < Adaptable::Adapter attr_accessor :loader_name # Finds the loader to use when loading originates from the source position of the given argument. # # @param instance [Model::PopsObject] The model object # @param file [String] the file from where the model was parsed # @param default_loader [Loader] the loader to return if no loader is found for the model # @return [Loader] the found loader or default_loader if it could not be found # def self.loader_for_model_object(model, file = nil, default_loader = nil) loaders = Puppet.lookup(:loaders) { nil } if loaders.nil? default_loader || Loaders.static_loader else loader_name = loader_name_by_source(loaders.environment, model, file) if loader_name.nil? default_loader || loaders[Loader::ENVIRONMENT_PRIVATE] else loaders[loader_name] end end end class PathsAndNameCacheAdapter < Puppet::Pops::Adaptable::Adapter attr_accessor :cache, :paths end # Attempts to find the module that `instance` originates from by looking at it's {SourcePosAdapter} and # compare the `locator.file` found there with the module paths given in the environment found in the # given `scope`. If the file is found to be relative to a path, then the first segment of the relative # path is interpreted as the name of a module. The object that the {SourcePosAdapter} is adapted to # will then be adapted to the private loader for that module and that adapter is returned. # # The method returns `nil` when no module could be found. # # @param environment [Puppet::Node::Environment] the current environment # @param instance [Model::PopsObject] the AST for the code # @param file [String] the path to the file for the code or `nil` # @return [String] the name of the loader associated with the source # @api private def self.loader_name_by_source(environment, instance, file) file = instance.file if file.nil? return nil if file.nil? || EMPTY_STRING == file pn_adapter = PathsAndNameCacheAdapter.adapt(environment) do |a| a.paths ||= environment.modulepath.map { |p| Pathname.new(p) } a.cache ||= {} end dir = File.dirname(file) pn_adapter.cache.fetch(dir) do |key| mod = find_module_for_dir(environment, pn_adapter.paths, dir) loader_name = mod.nil? ? nil : "#{mod.name} private" pn_adapter.cache[key] = loader_name end end # @api private def self.find_module_for_dir(environment, paths, dir) return nil if dir.nil? file_path = Pathname.new(dir) paths.each do |path| begin relative_path = file_path.relative_path_from(path).to_s.split(File::SEPARATOR) rescue ArgumentError # file_path was not relative to the module_path. That's OK. next end if relative_path.length > 1 mod = environment.module(relative_path[0]) return mod unless mod.nil? end end nil end end end end �������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/issues.rb�������������������������������������������������������������0000644�0052762�0001160�00000121573�13417161721�017727� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Defines classes to deal with issues, and message formatting and defines constants with Issues. # @api public # module Puppet::Pops module Issues # Describes an issue, and can produce a message for an occurrence of the issue. # class Issue # The issue code # @return [Symbol] attr_reader :issue_code # A block producing the message # @return [Proc] attr_reader :message_block # Names that must be bound in an occurrence of the issue to be able to produce a message. # These are the names in addition to requirements stipulated by the Issue formatter contract; i.e. :label`, # and `:semantic`. # attr_reader :arg_names # If this issue can have its severity lowered to :warning, :deprecation, or :ignored attr_writer :demotable # Configures the Issue with required arguments (bound by occurrence), and a block producing a message. def initialize issue_code, *args, &block @issue_code = issue_code @message_block = block @arg_names = args @demotable = true end # Returns true if it is allowed to demote this issue def demotable? @demotable end # Formats a message for an occurrence of the issue with argument bindings passed in a hash. # The hash must contain a LabelProvider bound to the key `label` and the semantic model element # bound to the key `semantic`. All required arguments as specified by `arg_names` must be bound # in the given `hash`. # @api public # def format(hash ={}) # Create a Message Data where all hash keys become methods for convenient interpolation # in issue text. msgdata = MessageData.new(*arg_names) begin # Evaluate the message block in the msg data's binding msgdata.format(hash, &message_block) rescue StandardError => e raise RuntimeError, _("Error while reporting issue: %{code}. %{message}") % { code: issue_code, message: e.message }, caller end end end # Provides a binding of arguments passed to Issue.format to method names available # in the issue's message producing block. # @api private # class MessageData def initialize *argnames singleton = class << self; self end argnames.each do |name| singleton.send(:define_method, name) do @data[name] end end end def format(hash, &block) @data = hash instance_eval(&block) end # Obtains the label provider given as a key `:label` in the hash passed to #format. The label provider is # return if no arguments are given. If given an argument, returns the result of calling #label on the label # provider. # # @param args [Object] one object to obtain a label for or zero arguments to obtain the label provider # @return [LabelProvider,String] the label provider or label depending on if an argument is given or not # @raise [Puppet::Error] if no label provider is found def label(*args) args.empty? ? label_provider : label_provider.label(args[0]) end # Returns the label provider given as key `:label` in the hash passed to #format. # @return [LabelProvider] the label provider # @raise [Puppet::Error] if no label provider is found def label_provider label_provider = @data[:label] #TRANSLATORS ":label" is a keyword and should not be translated raise Puppet::Error, _('Label provider key :label must be set to produce the text of the message!') unless label_provider label_provider end # Returns the label provider given as a key in the hash passed to #format. # def semantic #TRANSLATORS ":semantic" is a keyword and should not be translated raise Puppet::Error, _('Label provider key :semantic must be set to produce the text of the message!') unless @data[:semantic] @data[:semantic] end end # Defines an issue with the given `issue_code`, additional required parameters, and a block producing a message. # The block is evaluated in the context of a MessageData which provides convenient access to all required arguments # via accessor methods. In addition to accessors for specified arguments, these are also available: # * `label` - a `LabelProvider` that provides human understandable names for model elements and production of article (a/an/the). # * `semantic` - the model element for which the issue is reported # # @param issue_code [Symbol] the issue code for the issue used as an identifier, should be the same as the constant # the issue is bound to. # @param args [Symbol] required arguments that must be passed when formatting the message, may be empty # @param block [Proc] a block producing the message string, evaluated in a MessageData scope. The produced string # should not end with a period as additional information may be appended. # # @see MessageData # @api public # def self.issue(issue_code, *args, &block) Issue.new(issue_code, *args, &block) end # Creates a non demotable issue. # @see Issue.issue # def self.hard_issue(issue_code, *args, &block) result = Issue.new(issue_code, *args, &block) result.demotable = false result end # @comment Here follows definitions of issues. The intent is to provide a list from which yardoc can be generated # containing more detailed information / explanation of the issue. # These issues are set as constants, but it is unfortunately not possible for the created object to easily know which # name it is bound to. Instead the constant has to be repeated. (Alternatively, it could be done by instead calling # #const_set on the module, but the extra work required to get yardoc output vs. the extra effort to repeat the name # twice makes it not worth it (if doable at all, since there is no tag to artificially construct a constant, and # the parse tag does not produce any result for a constant assignment). # This is allowed (3.1) and has not yet been deprecated. # @todo configuration # NAME_WITH_HYPHEN = issue :NAME_WITH_HYPHEN, :name do _("%{issue} may not have a name containing a hyphen. The name '%{name}' is not legal") % { issue: label.a_an_uc(semantic), name: name } end # When a variable name contains a hyphen and these are illegal. # It is possible to control if a hyphen is legal in a name or not using the setting TODO # @todo describe the setting # @api public # @todo configuration if this is error or warning # VAR_WITH_HYPHEN = issue :VAR_WITH_HYPHEN, :name do _("A variable name may not contain a hyphen. The name '%{name}' is not legal") % { name: name } end # A class, definition, or node may only appear at top level or inside other classes # @todo Is this really true for nodes? Can they be inside classes? Isn't that too late? # @api public # NOT_TOP_LEVEL = hard_issue :NOT_TOP_LEVEL do _("Classes, definitions, and nodes may only appear at toplevel or inside other classes") end NOT_ABSOLUTE_TOP_LEVEL = hard_issue :NOT_ABSOLUTE_TOP_LEVEL do _("%{value} may only appear at toplevel") % { value: label.a_an_uc(semantic) } end CROSS_SCOPE_ASSIGNMENT = hard_issue :CROSS_SCOPE_ASSIGNMENT, :name do _("Illegal attempt to assign to '%{name}'. Cannot assign to variables in other namespaces") % { name: name } end # Assignment can only be made to certain types of left hand expressions such as variables. ILLEGAL_ASSIGNMENT = hard_issue :ILLEGAL_ASSIGNMENT do _("Illegal attempt to assign to '%{value}'. Not an assignable reference") % { value: label.a_an(semantic) } end # Variables are immutable, cannot reassign in the same assignment scope ILLEGAL_REASSIGNMENT = hard_issue :ILLEGAL_REASSIGNMENT, :name do if Validation::Checker4_0::RESERVED_PARAMETERS[name] _("Cannot reassign built in (or already assigned) variable '$%{var}'") % { var: name } else _("Cannot reassign variable '$%{var}'") % { var: name } end end # Variables facts and trusted ILLEGAL_RESERVED_ASSIGNMENT = hard_issue :ILLEGAL_RESERVED_ASSIGNMENT, :name do _("Attempt to assign to a reserved variable name: '$%{var}'") % { var: name } end # Assignment cannot be made to numeric match result variables ILLEGAL_NUMERIC_ASSIGNMENT = issue :ILLEGAL_NUMERIC_ASSIGNMENT, :varname do _("Illegal attempt to assign to the numeric match result variable '$%{var}'. Numeric variables are not assignable") % { var: varname } end # Assignment can only be made to certain types of left hand expressions such as variables. ILLEGAL_ASSIGNMENT_CONTEXT = hard_issue :ILLEGAL_ASSIGNMENT_CONTEXT do _("Assignment not allowed here") end # parameters cannot have numeric names, clashes with match result variables ILLEGAL_NUMERIC_PARAMETER = issue :ILLEGAL_NUMERIC_PARAMETER, :name do _("The numeric parameter name '$%{name}' cannot be used (clashes with numeric match result variables)") % { name: name } end # In certain versions of Puppet it may be allowed to assign to a not already assigned key # in an array or a hash. This is an optional validation that may be turned on to prevent accidental # mutation. # ILLEGAL_INDEXED_ASSIGNMENT = issue :ILLEGAL_INDEXED_ASSIGNMENT do _("Illegal attempt to assign via [index/key]. Not an assignable reference") end # When indexed assignment ($x[]=) is allowed, the leftmost expression must be # a variable expression. # ILLEGAL_ASSIGNMENT_VIA_INDEX = hard_issue :ILLEGAL_ASSIGNMENT_VIA_INDEX do _("Illegal attempt to assign to %{value} via [index/key]. Not an assignable reference") % { value: label.a_an(semantic) } end ILLEGAL_MULTI_ASSIGNMENT_SIZE = hard_issue :ILLEGAL_MULTI_ASSIGNMENT_SIZE, :expected, :actual do _("Mismatched number of assignable entries and values, expected %{expected}, got %{actual}") % { expected: expected, actual: actual } end MISSING_MULTI_ASSIGNMENT_KEY = hard_issue :MISSING_MULTI_ASSIGNMENT_KEY, :key do _("No value for required key '%{key}' in assignment to variables from hash") % { key: key } end MISSING_MULTI_ASSIGNMENT_VARIABLE = hard_issue :MISSING_MULTI_ASSIGNMENT_VARIABLE, :name do _("No value for required variable '$%{name}' in assignment to variables from class reference") % { name: name } end APPENDS_DELETES_NO_LONGER_SUPPORTED = hard_issue :APPENDS_DELETES_NO_LONGER_SUPPORTED, :operator do _("The operator '%{operator}' is no longer supported. See http://links.puppet.com/remove-plus-equals") % { operator: operator } end # For unsupported operators (e.g. += and -= in puppet 4). # UNSUPPORTED_OPERATOR = hard_issue :UNSUPPORTED_OPERATOR, :operator do _("The operator '%{operator}' is not supported.") % { operator: operator } end # For operators that are not supported in specific contexts (e.g. '* =>' in # resource defaults) # UNSUPPORTED_OPERATOR_IN_CONTEXT = hard_issue :UNSUPPORTED_OPERATOR_IN_CONTEXT, :operator do _("The operator '%{operator}' in %{value} is not supported.") % { operator: operator, value: label.a_an(semantic) } end # For non applicable operators (e.g. << on Hash). # OPERATOR_NOT_APPLICABLE = hard_issue :OPERATOR_NOT_APPLICABLE, :operator, :left_value do _("Operator '%{operator}' is not applicable to %{left}.") % { operator: operator, left: label.a_an(left_value) } end OPERATOR_NOT_APPLICABLE_WHEN = hard_issue :OPERATOR_NOT_APPLICABLE_WHEN, :operator, :left_value, :right_value do _("Operator '%{operator}' is not applicable to %{left} when right side is %{right}.") % { operator: operator, left: label.a_an(left_value), right: label.a_an(right_value) } end COMPARISON_NOT_POSSIBLE = hard_issue :COMPARISON_NOT_POSSIBLE, :operator, :left_value, :right_value, :detail do _("Comparison of: %{left} %{operator} %{right}, is not possible. Caused by '%{detail}'.") % { left: label(left_value), operator: operator, right: label(right_value), detail: detail } end MATCH_NOT_REGEXP = hard_issue :MATCH_NOT_REGEXP, :detail do _("Can not convert right match operand to a regular expression. Caused by '%{detail}'.") % { detail: detail } end MATCH_NOT_STRING = hard_issue :MATCH_NOT_STRING, :left_value do _("Left match operand must result in a String value. Got %{left}.") % { left: label.a_an(left_value) } end # Some expressions/statements may not produce a value (known as right-value, or rvalue). # This may vary between puppet versions. # NOT_RVALUE = issue :NOT_RVALUE do _("Invalid use of expression. %{value} does not produce a value") % { value: label.a_an_uc(semantic) } end # Appending to attributes is only allowed in certain types of resource expressions. # ILLEGAL_ATTRIBUTE_APPEND = hard_issue :ILLEGAL_ATTRIBUTE_APPEND, :name, :parent do _("Illegal +> operation on attribute %{attr}. This operator can not be used in %{expression}") % { attr: name, expression: label.a_an(parent) } end ILLEGAL_NAME = hard_issue :ILLEGAL_NAME, :name do _("Illegal name. The given name '%{name}' does not conform to the naming rule /^((::)?[a-z_]\w*)(::[a-z]\\w*)*$/") % { name: name } end ILLEGAL_SINGLE_TYPE_MAPPING = hard_issue :ILLEGAL_TYPE_MAPPING, :expression do _("Illegal type mapping. Expected a Type on the left side, got %{expression}") % { expression: label.a_an_uc(semantic) } end ILLEGAL_REGEXP_TYPE_MAPPING = hard_issue :ILLEGAL_TYPE_MAPPING, :expression do _("Illegal type mapping. Expected a Tuple[Regexp,String] on the left side, got %{expression}") % { expression: label.a_an_uc(semantic) } end ILLEGAL_PARAM_NAME = hard_issue :ILLEGAL_PARAM_NAME, :name do _("Illegal parameter name. The given name '%{name}' does not conform to the naming rule /^[a-z_]\\w*$/") % { name: name } end ILLEGAL_VAR_NAME = hard_issue :ILLEGAL_VAR_NAME, :name do _("Illegal variable name, The given name '%{name}' does not conform to the naming rule /^((::)?[a-z]\\w*)*((::)?[a-z_]\\w*)$/") % { name: name } end ILLEGAL_NUMERIC_VAR_NAME = hard_issue :ILLEGAL_NUMERIC_VAR_NAME, :name do _("Illegal numeric variable name, The given name '%{name}' must be a decimal value if it starts with a digit 0-9") % { name: name } end # In case a model is constructed programmatically, it must create valid type references. # ILLEGAL_CLASSREF = hard_issue :ILLEGAL_CLASSREF, :name do _("Illegal type reference. The given name '%{name}' does not conform to the naming rule") % { name: name } end # This is a runtime issue - storeconfigs must be on in order to collect exported. This issue should be # set to :ignore when just checking syntax. # @todo should be a :warning by default # RT_NO_STORECONFIGS = issue :RT_NO_STORECONFIGS do _("You cannot collect exported resources without storeconfigs being set; the collection will be ignored") end # This is a runtime issue - storeconfigs must be on in order to export a resource. This issue should be # set to :ignore when just checking syntax. # @todo should be a :warning by default # RT_NO_STORECONFIGS_EXPORT = issue :RT_NO_STORECONFIGS_EXPORT do _("You cannot collect exported resources without storeconfigs being set; the export is ignored") end # A hostname may only contain letters, digits, '_', '-', and '.'. # ILLEGAL_HOSTNAME_CHARS = hard_issue :ILLEGAL_HOSTNAME_CHARS, :hostname do _("The hostname '%{hostname}' contains illegal characters (only letters, digits, '_', '-', and '.' are allowed)") % { hostname: hostname } end # A hostname may only contain letters, digits, '_', '-', and '.'. # ILLEGAL_HOSTNAME_INTERPOLATION = hard_issue :ILLEGAL_HOSTNAME_INTERPOLATION do _("An interpolated expression is not allowed in a hostname of a node") end # Issues when an expression is used where it is not legal. # E.g. an arithmetic expression where a hostname is expected. # ILLEGAL_EXPRESSION = hard_issue :ILLEGAL_EXPRESSION, :feature, :container do _("Illegal expression. %{expression} is unacceptable as %{feature} in %{container}") % { expression: label.a_an_uc(semantic), feature: feature, container: label.a_an(container) } end # Issues when a variable is not a NAME # ILLEGAL_VARIABLE_EXPRESSION = hard_issue :ILLEGAL_VARIABLE_EXPRESSION do _("Illegal variable expression. %{expression} did not produce a variable name (String or Numeric).") % { expression: label.a_an_uc(semantic) } end # Issues when an expression is used illegally in a query. # query only supports == and !=, and not <, > etc. # ILLEGAL_QUERY_EXPRESSION = hard_issue :ILLEGAL_QUERY_EXPRESSION do _("Illegal query expression. %{expression} cannot be used in a query") % { expression: label.a_an_uc(semantic) } end # If an attempt is made to make a resource default virtual or exported. # NOT_VIRTUALIZEABLE = hard_issue :NOT_VIRTUALIZEABLE do _("Resource Defaults are not virtualizable") end CLASS_NOT_VIRTUALIZABLE = issue :CLASS_NOT_VIRTUALIZABLE do _("Classes are not virtualizable") end # When an attempt is made to use multiple keys (to produce a range in Ruby - e.g. $arr[2,-1]). # This is not supported in 3x, but it allowed in 4x. # UNSUPPORTED_RANGE = issue :UNSUPPORTED_RANGE, :count do _("Attempt to use unsupported range in %{expression}, %{count} values given for max 1") % { expression: label.a_an(semantic), count: count } end # Issues when expressions that are not implemented or activated in the current version are used. # UNSUPPORTED_EXPRESSION = issue :UNSUPPORTED_EXPRESSION do _("Expressions of type %{expression} are not supported in this version of Puppet") % { expression: label.a_an(semantic) } end ILLEGAL_RELATIONSHIP_OPERAND_TYPE = issue :ILLEGAL_RELATIONSHIP_OPERAND_TYPE, :operand do _("Illegal relationship operand, can not form a relationship with %{expression}. A Catalog type is required.") % { expression: label.a_an(operand) } end NOT_CATALOG_TYPE = issue :NOT_CATALOG_TYPE, :type do _("Illegal relationship operand, can not form a relationship with something of type %{expression_type}. A Catalog type is required.") % { expression_type: type } end BAD_STRING_SLICE_ARITY = issue :BAD_STRING_SLICE_ARITY, :actual do _("String supports [] with one or two arguments. Got %{actual}") % { actual: actual } end BAD_STRING_SLICE_TYPE = issue :BAD_STRING_SLICE_TYPE, :actual do _("String-Type [] requires all arguments to be integers (or default). Got %{actual}") % { actual: actual } end BAD_ARRAY_SLICE_ARITY = issue :BAD_ARRAY_SLICE_ARITY, :actual do _("Array supports [] with one or two arguments. Got %{actual}") % { actual: actual } end BAD_HASH_SLICE_ARITY = issue :BAD_HASH_SLICE_ARITY, :actual do _("Hash supports [] with one or more arguments. Got %{actual}") % { actual: actual } end BAD_INTEGER_SLICE_ARITY = issue :BAD_INTEGER_SLICE_ARITY, :actual do _("Integer-Type supports [] with one or two arguments (from, to). Got %{actual}") % { actual: actual } end BAD_INTEGER_SLICE_TYPE = issue :BAD_INTEGER_SLICE_TYPE, :actual do _("Integer-Type [] requires all arguments to be integers (or default). Got %{actual}") % { actual: actual } end BAD_COLLECTION_SLICE_TYPE = issue :BAD_COLLECTION_SLICE_TYPE, :actual do _("A Type's size constraint arguments must be a single Integer type, or 1-2 integers (or default). Got %{actual}") % { actual: label.a_an(actual) } end BAD_FLOAT_SLICE_ARITY = issue :BAD_INTEGER_SLICE_ARITY, :actual do _("Float-Type supports [] with one or two arguments (from, to). Got %{actual}") % { actual: actual } end BAD_FLOAT_SLICE_TYPE = issue :BAD_INTEGER_SLICE_TYPE, :actual do _("Float-Type [] requires all arguments to be floats, or integers (or default). Got %{actual}") % { actual: actual } end BAD_SLICE_KEY_TYPE = issue :BAD_SLICE_KEY_TYPE, :left_value, :expected_classes, :actual do expected_text = if expected_classes.size > 1 _("one of %{expected} are") % { expected: expected_classes.join(', ') } else _("%{expected} is") % { expected: expected_classes[0] } end _("%{expression}[] cannot use %{actual} where %{expected_text} expected") % { expression: label.a_an_uc(left_value), actual: actual, expected_text: expected_text } end BAD_STRING_SLICE_KEY_TYPE = issue :BAD_STRING_SLICE_KEY_TYPE, :left_value, :actual_type do _("A substring operation does not accept %{label_article} %{actual_type} as a character index. Expected an Integer") % { label_article: label.article(actual_type), actual_type: actual_type } end BAD_NOT_UNDEF_SLICE_TYPE = issue :BAD_NOT_UNDEF_SLICE_TYPE, :base_type, :actual do _("%{expression}[] argument must be a Type or a String. Got %{actual}") % { expression: base_type, actual: actual } end BAD_TYPE_SLICE_TYPE = issue :BAD_TYPE_SLICE_TYPE, :base_type, :actual do _("%{base_type}[] arguments must be types. Got %{actual}") % { base_type: base_type, actual: actual } end BAD_TYPE_SLICE_ARITY = issue :BAD_TYPE_SLICE_ARITY, :base_type, :min, :max, :actual do base_type_label = base_type.is_a?(String) ? base_type : label.a_an_uc(base_type) if max == -1 || max == Float::INFINITY _("%{base_type_label}[] accepts %{min} or more arguments. Got %{actual}") % { base_type_label: base_type_label, min: min, actual: actual } elsif max && max != min _("%{base_type_label}[] accepts %{min} to %{max} arguments. Got %{actual}") % { base_type_label: base_type_label, min: min, max: max, actual: actual } else _("%{base_type_label}[] accepts %{min} %{label}. Got %{actual}") % { base_type_label: base_type_label, min: min, label: label.plural_s(min, _('argument')), actual: actual } end end BAD_TYPE_SPECIALIZATION = hard_issue :BAD_TYPE_SPECIALIZATION, :type, :message do _("Error creating type specialization of %{base_type}, %{message}") % { base_type: label.a_an(type), message: message } end ILLEGAL_TYPE_SPECIALIZATION = issue :ILLEGAL_TYPE_SPECIALIZATION, :kind do _("Cannot specialize an already specialized %{kind} type") % { kind: kind } end ILLEGAL_RESOURCE_SPECIALIZATION = issue :ILLEGAL_RESOURCE_SPECIALIZATION, :actual do _("First argument to Resource[] must be a resource type or a String. Got %{actual}.") % { actual: actual } end EMPTY_RESOURCE_SPECIALIZATION = issue :EMPTY_RESOURCE_SPECIALIZATION do _("Arguments to Resource[] are all empty/undefined") end ILLEGAL_HOSTCLASS_NAME = hard_issue :ILLEGAL_HOSTCLASS_NAME, :name do _("Illegal Class name in class reference. %{expression} cannot be used where a String is expected") % { expression: label.a_an_uc(name) } end ILLEGAL_DEFINITION_NAME = hard_issue :ILLEGAL_DEFINITION_NAME, :name do _("Unacceptable name. The name '%{name}' is unacceptable as the name of %{value}") % { name: name, value: label.a_an(semantic) } end ILLEGAL_DEFINITION_LOCATION = issue :ILLEGAL_DEFINITION_LOCATION, :name, :file do _("Unacceptable location. The name '%{name}' is unacceptable in file '%{file}'") % { name: name, file: file } end ILLEGAL_TOP_CONSTRUCT_LOCATION = issue :ILLEGAL_TOP_CONSTRUCT_LOCATION do if semantic.is_a?(Puppet::Pops::Model::NamedDefinition) _("The %{value} '%{name}' is unacceptable as a top level construct in this location") % { name: semantic.name, value: label(semantic) } else _("This %{value} is unacceptable as a top level construct in this location") % { value: label(semantic) } end end CAPTURES_REST_NOT_LAST = hard_issue :CAPTURES_REST_NOT_LAST, :param_name do _("Parameter $%{param} is not last, and has 'captures rest'") % { param: param_name } end CAPTURES_REST_NOT_SUPPORTED = hard_issue :CAPTURES_REST_NOT_SUPPORTED, :container, :param_name do _("Parameter $%{param} has 'captures rest' - not supported in %{container}") % { param: param_name, container: label.a_an(container) } end REQUIRED_PARAMETER_AFTER_OPTIONAL = hard_issue :REQUIRED_PARAMETER_AFTER_OPTIONAL, :param_name do _("Parameter $%{param} is required but appears after optional parameters") % { param: param_name } end MISSING_REQUIRED_PARAMETER = hard_issue :MISSING_REQUIRED_PARAMETER, :param_name do _("Parameter $%{param} is required but no value was given") % { param: param_name } end NOT_NUMERIC = issue :NOT_NUMERIC, :value do _("The value '%{value}' cannot be converted to Numeric.") % { value: value } end NUMERIC_COERCION = issue :NUMERIC_COERCION, :before, :after do _("The string '%{before}' was automatically coerced to the numerical value %{after}") % { before: before, after: after } end UNKNOWN_FUNCTION = issue :UNKNOWN_FUNCTION, :name do _("Unknown function: '%{name}'.") % { name: name } end UNKNOWN_VARIABLE = issue :UNKNOWN_VARIABLE, :name do _("Unknown variable: '%{name}'.") % { name: name } end RUNTIME_ERROR = issue :RUNTIME_ERROR, :detail do _("Error while evaluating %{expression}, %{detail}") % { expression: label.a_an(semantic), detail: detail } end UNKNOWN_RESOURCE_TYPE = issue :UNKNOWN_RESOURCE_TYPE, :type_name do _("Resource type not found: %{res_type}") % { res_type: type_name } end ILLEGAL_RESOURCE_TYPE = hard_issue :ILLEGAL_RESOURCE_TYPE, :actual do _("Illegal Resource Type expression, expected result to be a type name, or untitled Resource, got %{actual}") % { actual: actual } end DUPLICATE_TITLE = issue :DUPLICATE_TITLE, :title do _("The title '%{title}' has already been used in this resource expression") % { title: title } end DUPLICATE_ATTRIBUTE = issue :DUPLICATE_ATTRIBUE, :attribute do _("The attribute '%{attribute}' has already been set") % { attribute: attribute } end MISSING_TITLE = hard_issue :MISSING_TITLE do _("Missing title. The title expression resulted in undef") end MISSING_TITLE_AT = hard_issue :MISSING_TITLE_AT, :index do _("Missing title at index %{index}. The title expression resulted in an undef title") % { index: index } end ILLEGAL_TITLE_TYPE_AT = hard_issue :ILLEGAL_TITLE_TYPE_AT, :index, :actual do _("Illegal title type at index %{index}. Expected String, got %{actual}") % { index: index, actual: actual } end EMPTY_STRING_TITLE_AT = hard_issue :EMPTY_STRING_TITLE_AT, :index do _("Empty string title at %{index}. Title strings must have a length greater than zero.") % { index: index } end UNKNOWN_RESOURCE = issue :UNKNOWN_RESOURCE, :type_name, :title do _("Resource not found: %{type_name}['%{title}']") % { type_name: type_name, title: title } end UNKNOWN_RESOURCE_PARAMETER = issue :UNKNOWN_RESOURCE_PARAMETER, :type_name, :title, :param_name do _("The resource %{type_name}['%{title}'] does not have a parameter called '%{param}'") % { type_name: type_name.capitalize, title: title, param: param_name } end DIV_BY_ZERO = hard_issue :DIV_BY_ZERO do _("Division by 0") end RESULT_IS_INFINITY = hard_issue :RESULT_IS_INFINITY, :operator do _("The result of the %{operator} expression is Infinity") % { operator: operator } end # TODO_HEREDOC EMPTY_HEREDOC_SYNTAX_SEGMENT = issue :EMPTY_HEREDOC_SYNTAX_SEGMENT, :syntax do _("Heredoc syntax specification has empty segment between '+' : '%{syntax}'") % { syntax: syntax } end ILLEGAL_EPP_PARAMETERS = issue :ILLEGAL_EPP_PARAMETERS do _("Ambiguous EPP parameter expression. Probably missing '<%-' before parameters to remove leading whitespace") end DISCONTINUED_IMPORT = hard_issue :DISCONTINUED_IMPORT do #TRANSLATORS "import" is a function name and should not be translated _("Use of 'import' has been discontinued in favor of a manifest directory. See http://links.puppet.com/puppet-import-deprecation") end IDEM_EXPRESSION_NOT_LAST = issue :IDEM_EXPRESSION_NOT_LAST do _("This %{expression} has no effect. A value was produced and then forgotten (one or more preceding expressions may have the wrong form)") % { expression: label.label(semantic) } end RESOURCE_WITHOUT_TITLE = issue :RESOURCE_WITHOUT_TITLE, :name do _("This expression is invalid. Did you try declaring a '%{name}' resource without a title?") % { name: name } end IDEM_NOT_ALLOWED_LAST = hard_issue :IDEM_NOT_ALLOWED_LAST, :container do _("This %{expression} has no effect. %{container} can not end with a value-producing expression without other effect") % { expression: label.label(semantic), container: label.a_an_uc(container) } end RESERVED_WORD = hard_issue :RESERVED_WORD, :word do _("Use of reserved word: %{word}, must be quoted if intended to be a String value") % { word: word } end FUTURE_RESERVED_WORD = issue :FUTURE_RESERVED_WORD, :word do _("Use of future reserved word: '%{word}'") % { word: word } end RESERVED_TYPE_NAME = hard_issue :RESERVED_TYPE_NAME, :name do _("The name: '%{name}' is already defined by Puppet and can not be used as the name of %{expression}.") % { name: name, expression: label.a_an(semantic) } end UNMATCHED_SELECTOR = hard_issue :UNMATCHED_SELECTOR, :param_value do _("No matching entry for selector parameter with value '%{param}'") % { param: param_value } end ILLEGAL_NODE_INHERITANCE = issue :ILLEGAL_NODE_INHERITANCE do _("Node inheritance is not supported in Puppet >= 4.0.0. See http://links.puppet.com/puppet-node-inheritance-deprecation") end ILLEGAL_OVERRIDDEN_TYPE = issue :ILLEGAL_OVERRIDDEN_TYPE, :actual do _("Resource Override can only operate on resources, got: %{actual}") % { actual: label.label(actual) } end DUPLICATE_PARAMETER = hard_issue :DUPLICATE_PARAMETER, :param_name do _("The parameter '%{param}' is declared more than once in the parameter list") % { param: param_name } end DUPLICATE_KEY = issue :DUPLICATE_KEY, :key do _("The key '%{key}' is declared more than once") % { key: key } end DUPLICATE_DEFAULT = hard_issue :DUPLICATE_DEFAULT, :container do _("This %{container} already has a 'default' entry - this is a duplicate") % { container: label.label(container) } end RESERVED_PARAMETER = hard_issue :RESERVED_PARAMETER, :container, :param_name do _("The parameter $%{param} redefines a built in parameter in %{container}") % { param: param_name, container: label.the(container) } end TYPE_MISMATCH = hard_issue :TYPE_MISMATCH, :expected, :actual do _("Expected value of type %{expected}, got %{actual}") % { expected: expected, actual: actual } end MULTIPLE_ATTRIBUTES_UNFOLD = hard_issue :MULTIPLE_ATTRIBUTES_UNFOLD do _("Unfolding of attributes from Hash can only be used once per resource body") end ILLEGAL_CATALOG_RELATED_EXPRESSION = hard_issue :ILLEGAL_CATALOG_RELATED_EXPRESSION do _("This %{expression} appears in a context where catalog related expressions are not allowed") % { expression: label.label(semantic) } end SYNTAX_ERROR = hard_issue :SYNTAX_ERROR, :where do _("Syntax error at %{location}") % { location: where } end ILLEGAL_CLASS_REFERENCE = hard_issue :ILLEGAL_CLASS_REFERENCE do _('Illegal class reference') end ILLEGAL_FULLY_QUALIFIED_CLASS_REFERENCE = hard_issue :ILLEGAL_FULLY_QUALIFIED_CLASS_REFERENCE do _('Illegal fully qualified class reference') end ILLEGAL_FULLY_QUALIFIED_NAME = hard_issue :ILLEGAL_FULLY_QUALIFIED_NAME do _('Illegal fully qualified name') end ILLEGAL_NAME_OR_BARE_WORD = hard_issue :ILLEGAL_NAME_OR_BARE_WORD do _('Illegal name or bare word') end ILLEGAL_NUMBER = hard_issue :ILLEGAL_NUMBER, :value do _("Illegal number '%{value}'") % { value: value } end ILLEGAL_UNICODE_ESCAPE = issue :ILLEGAL_UNICODE_ESCAPE do _("Unicode escape '\\u' was not followed by 4 hex digits or 1-6 hex digits in {} or was > 10ffff") end INVALID_HEX_NUMBER = hard_issue :INVALID_HEX_NUMBER, :value do _("Not a valid hex number %{value}") % { value: value } end INVALID_OCTAL_NUMBER = hard_issue :INVALID_OCTAL_NUMBER, :value do _("Not a valid octal number %{value}") % { value: value } end INVALID_DECIMAL_NUMBER = hard_issue :INVALID_DECIMAL_NUMBER, :value do _("Not a valid decimal number %{value}") % { value: value } end NO_INPUT_TO_LEXER = hard_issue :NO_INPUT_TO_LEXER do _("Internal Error: No string or file given to lexer to process.") end UNRECOGNIZED_ESCAPE = issue :UNRECOGNIZED_ESCAPE, :ch do _("Unrecognized escape sequence '\\%{ch}'") % { ch: ch } end UNCLOSED_QUOTE = hard_issue :UNCLOSED_QUOTE, :after, :followed_by do _("Unclosed quote after %{after} followed by '%{followed_by}'") % { after: after, followed_by: followed_by } end UNCLOSED_MLCOMMENT = hard_issue :UNCLOSED_MLCOMMENT do _('Unclosed multiline comment') end EPP_INTERNAL_ERROR = hard_issue :EPP_INTERNAL_ERROR, :error do _("Internal error: %{error}") % { error: error } end EPP_UNBALANCED_TAG = hard_issue :EPP_UNBALANCED_TAG do _('Unbalanced epp tag, reached <eof> without closing tag.') end EPP_UNBALANCED_COMMENT = hard_issue :EPP_UNBALANCED_COMMENT do _('Reaching end after opening <%# without seeing %>') end EPP_UNBALANCED_EXPRESSION = hard_issue :EPP_UNBALANCED_EXPRESSION do _('Unbalanced embedded expression - opening <% and reaching end of input') end HEREDOC_UNCLOSED_PARENTHESIS = hard_issue :HEREDOC_UNCLOSED_PARENTHESIS, :followed_by do _("Unclosed parenthesis after '@(' followed by '%{followed_by}'") % { followed_by: followed_by } end HEREDOC_WITHOUT_END_TAGGED_LINE = hard_issue :HEREDOC_WITHOUT_END_TAGGED_LINE do _('Heredoc without end-tagged line') end HEREDOC_INVALID_ESCAPE = hard_issue :HEREDOC_INVALID_ESCAPE, :actual do _("Invalid heredoc escape char. Only t, r, n, s, u, L, $ allowed. Got '%{actual}'") % { actual: actual } end HEREDOC_INVALID_SYNTAX = hard_issue :HEREDOC_INVALID_SYNTAX do _('Invalid syntax in heredoc expected @(endtag[:syntax][/escapes])') end HEREDOC_WITHOUT_TEXT = hard_issue :HEREDOC_WITHOUT_TEXT do _('Heredoc without any following lines of text') end HEREDOC_EMPTY_ENDTAG = hard_issue :HEREDOC_EMPTY_ENDTAG do _('Heredoc with an empty endtag') end HEREDOC_MULTIPLE_AT_ESCAPES = hard_issue :HEREDOC_MULTIPLE_AT_ESCAPES, :escapes do _("An escape char for @() may only appear once. Got '%{escapes}'") % { escapes: escapes.join(', ') } end ILLEGAL_BOM = hard_issue :ILLEGAL_BOM, :format_name, :bytes do _("Illegal %{format} Byte Order mark at beginning of input: %{bom} - remove these from the puppet source") % { format: format_name, bom: bytes } end NO_SUCH_FILE_OR_DIRECTORY = hard_issue :NO_SUCH_FILE_OR_DIRECTORY, :file do _('No such file or directory: %{file}') % { file: file } end NOT_A_FILE = hard_issue :NOT_A_FILE, :file do _('%{file} is not a file') % { file: file } end NUMERIC_OVERFLOW = hard_issue :NUMERIC_OVERFLOW, :value do if value > 0 _("%{expression} resulted in a value outside of Puppet Integer max range, got '%{value}'") % { expression: label.a_an_uc(semantic), value: ("%#+x" % value) } else _("%{expression} resulted in a value outside of Puppet Integer min range, got '%{value}'") % { expression: label.a_an_uc(semantic), value: ("%#+x" % value) } end end HIERA_UNSUPPORTED_VERSION = hard_issue :HIERA_UNSUPPORTED_VERSION, :version do _("This runtime does not support hiera.yaml version %{version}") % { version: version } end HIERA_VERSION_3_NOT_GLOBAL = hard_issue :HIERA_VERSION_3_NOT_GLOBAL, :where do _("hiera.yaml version 3 cannot be used in %{location}") % { location: label.a_an(where) } end HIERA_UNSUPPORTED_VERSION_IN_GLOBAL = hard_issue :HIERA_UNSUPPORTED_VERSION_IN_GLOBAL do _('hiera.yaml version 4 cannot be used in the global layer') end HIERA_UNDEFINED_VARIABLE = hard_issue :HIERA_UNDEFINED_VARIABLE, :name do _("Undefined variable '%{name}'") % { name: name } end HIERA_BACKEND_MULTIPLY_DEFINED = hard_issue :HIERA_BACKEND_MULTIPLY_DEFINED, :name, :first_line do msg = _("Backend '%{name}' is defined more than once.") % { name: name } fl = first_line if fl msg += ' ' + _("First defined at %{error_location}") % { error_location: Puppet::Util::Errors.error_location(nil, fl) } end msg end HIERA_NO_PROVIDER_FOR_BACKEND = hard_issue :HIERA_NO_PROVIDER_FOR_BACKEND, :name do _("No data provider is registered for backend '%{name}'") % { name: name } end HIERA_HIERARCHY_NAME_MULTIPLY_DEFINED = hard_issue :HIERA_HIERARCHY_NAME_MULTIPLY_DEFINED, :name, :first_line do msg = _("Hierarchy name '%{name}' defined more than once.") % { name: name } fl = first_line if fl msg += ' ' + _("First defined at %{error_location}") % { error_location: Puppet::Util::Errors.error_location(nil, fl) } end msg end HIERA_V3_BACKEND_NOT_GLOBAL = hard_issue :HIERA_V3_BACKEND_NOT_GLOBAL do _("'hiera3_backend' is only allowed in the global layer") end HIERA_DEFAULT_HIERARCHY_NOT_IN_MODULE = hard_issue :HIERA_DEFAULT_HIERARCHY_NOT_IN_MODULE do _("'default_hierarchy' is only allowed in the module layer") end HIERA_V3_BACKEND_REPLACED_BY_DATA_HASH = hard_issue :HIERA_V3_BACKEND_REPLACED_BY_DATA_HASH, :function_name do _("Use \"data_hash: %{function_name}_data\" instead of \"hiera3_backend: %{function_name}\"") % { function_name: function_name } end HIERA_MISSING_DATA_PROVIDER_FUNCTION = hard_issue :HIERA_MISSING_DATA_PROVIDER_FUNCTION, :name do _("One of %{keys} must be defined in hierarchy '%{name}'") % { keys: label.combine_strings(Lookup::HieraConfig::FUNCTION_KEYS), name: name } end HIERA_MULTIPLE_DATA_PROVIDER_FUNCTIONS = hard_issue :HIERA_MULTIPLE_DATA_PROVIDER_FUNCTIONS, :name do _("Only one of %{keys} can be defined in hierarchy '%{name}'") % { keys: label.combine_strings(Lookup::HieraConfig::FUNCTION_KEYS), name: name } end HIERA_MULTIPLE_DATA_PROVIDER_FUNCTIONS_IN_DEFAULT = hard_issue :HIERA_MULTIPLE_DATA_PROVIDER_FUNCTIONS_IN_DEFAULT do _("Only one of %{keys} can be defined in defaults") % { keys: label.combine_strings(Lookup::HieraConfig::FUNCTION_KEYS) } end HIERA_MULTIPLE_LOCATION_SPECS = hard_issue :HIERA_MULTIPLE_LOCATION_SPECS, :name do _("Only one of %{keys} can be defined in hierarchy '%{name}'") % { keys: label.combine_strings(Lookup::HieraConfig::LOCATION_KEYS), name: name } end HIERA_OPTION_RESERVED_BY_PUPPET = hard_issue :HIERA_OPTION_RESERVED_BY_PUPPET, :key, :name do _("Option key '%{key}' used in hierarchy '%{name}' is reserved by Puppet") % { key: key, name: name } end HIERA_DEFAULT_OPTION_RESERVED_BY_PUPPET = hard_issue :HIERA_DEFAULT_OPTION_RESERVED_BY_PUPPET, :key do _("Option key '%{key}' used in defaults is reserved by Puppet") % { key: key } end HIERA_DATA_PROVIDER_FUNCTION_NOT_FOUND = hard_issue :HIERA_DATA_PROVIDER_FUNCTION_NOT_FOUND, :function_type, :function_name do _("Unable to find '%{function_type}' function named '%{function_name}'") % { function_type: function_type, function_name: function_name } end HIERA_INTERPOLATION_ALIAS_NOT_ENTIRE_STRING = hard_issue :HIERA_INTERPOLATION_ALIAS_NOT_ENTIRE_STRING do _("'alias' interpolation is only permitted if the expression is equal to the entire string") end HIERA_INTERPOLATION_UNKNOWN_INTERPOLATION_METHOD = hard_issue :HIERA_INTERPOLATION_UNKNOWN_INTERPOLATION_METHOD, :name do _("Unknown interpolation method '%{name}'") % { name: name } end HIERA_INTERPOLATION_METHOD_SYNTAX_NOT_ALLOWED = hard_issue :HIERA_INTERPOLATION_METHOD_SYNTAX_NOT_ALLOWED do _('Interpolation using method syntax is not allowed in this context') end SERIALIZATION_ENDLESS_RECURSION = hard_issue :SERIALIZATION_ENDLESS_RECURSION, :type_name do _('Endless recursion detected when attempting to serialize value of class %{type_name}') % { :type_name => type_name } end SERIALIZATION_DEFAULT_CONVERTED_TO_STRING = issue :SERIALIZATION_DEFAULT_CONVERTED_TO_STRING, :path, :klass, :value do _("%{path} contains the special value default. It will be converted to the String 'default'") % { path: path } end SERIALIZATION_UNKNOWN_CONVERTED_TO_STRING = issue :SERIALIZATION_UNKNOWN_CONVERTED_TO_STRING, :path, :klass, :value do _("%{path} contains %{klass} value. It will be converted to the String '%{value}'") % { path: path, klass: label.a_an(klass), value: value } end SERIALIZATION_UNKNOWN_KEY_CONVERTED_TO_STRING = issue :SERIALIZATION_UNKNOWN_KEY_CONVERTED_TO_STRING, :path, :klass, :value do _("%{path} contains a hash with %{klass} key. It will be converted to the String '%{value}'") % { path: path, klass: label.a_an(klass), value: value } end FEATURE_NOT_SUPPORTED_WHEN_SCRIPTING = issue :NOT_SUPPORTED_WHEN_SCRIPTING, :feature do _("The feature '%{feature}' is only available when compiling a catalog") % { feature: feature } end CATALOG_OPERATION_NOT_SUPPORTED_WHEN_SCRIPTING = issue :CATALOG_OPERATION_NOT_SUPPORTED_WHEN_SCRIPTING, :operation do _("The catalog operation '%{operation}' is only available when compiling a catalog") % { operation: operation } end TASK_OPERATION_NOT_SUPPORTED_WHEN_COMPILING = issue :TASK_OPERATION_NOT_SUPPORTED_WHEN_COMPILING, :operation do _("The task operation '%{operation}' is not available when compiling a catalog") % { operation: operation } end TASK_MISSING_BOLT = issue :TASK_MISSING_BOLT, :action do _("The 'bolt' library is required to %{action}") % { action: action } end UNKNOWN_TASK = issue :UNKNOWN_TASK, :type_name do _('Task not found: %{type_name}') % { type_name: type_name } end LOADER_FAILURE = issue :LOADER_FAILURE, :type do _('Failed to load: %{type_name}') % { type: type } end end end �������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/loaders.rb������������������������������������������������������������0000644�0052762�0001160�00000051670�13417161721�020045� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops # This is the container for all Loader instances. Each Loader instance has a `loader_name` by which it can be uniquely # identified within this container. # A Loader can be private or public. In general, code will have access to the private loader associated with the # location of the code. It will be parented by a loader that in turn have access to other public loaders that # can load only such entries that have been publicly available. The split between public and private is not # yet enforced in Puppet. # # The name of a private loader should always end with ' private' # class Loaders class LoaderError < Puppet::Error; end attr_reader :static_loader attr_reader :puppet_system_loader attr_reader :public_environment_loader attr_reader :private_environment_loader attr_reader :environment def self.new(environment, for_agent = false) obj = environment.loaders if obj.nil? obj = self.allocate obj.send(:initialize, environment, for_agent) end obj end def initialize(environment, for_agent) # Protect against environment havoc raise ArgumentError.new(_("Attempt to redefine already initialized loaders for environment")) unless environment.loaders.nil? environment.loaders = self @environment = environment @loaders_by_name = {} add_loader_by_name(self.class.static_loader) # Create the set of loaders # 1. Puppet, loads from the "running" puppet - i.e. bundled functions, types, extension points and extensions # These cannot be cached since a loaded instance will be bound to its closure scope which holds on to # a compiler and all loaded types. Subsequent request would find remains of the environment that loaded # the content. PUP-4461. # @puppet_system_loader = create_puppet_system_loader() # 2. Environment loader - i.e. what is bound across the environment, may change for each setup # TODO: loaders need to work when also running in an agent doing catalog application. There is no # concept of environment the same way as when running as a master (except when doing apply). # The creation mechanisms should probably differ between the two. # @private_environment_loader = if for_agent add_loader_by_name(Loader::SimpleEnvironmentLoader.new(@puppet_system_loader, 'agent environment')) else create_environment_loader(environment) end Pcore.init_env(@private_environment_loader) # 3. module loaders are set up from the create_environment_loader, they register themselves end # Called after loader has been added to Puppet Context as :loaders so that dynamic types can # be pre-loaded with a fully configured loader system def pre_load @puppet_system_loader.load(:type, 'error') # Will move to Bolt @puppet_system_loader.load(:type, 'executionresult') end # Clears the cached static and puppet_system loaders (to enable testing) # def self.clear @@static_loader = nil Model.class_variable_set(:@@pcore_ast_initialized, false) Model.register_pcore_types end # Calls {#loaders} to obtain the {{Loaders}} instance and then uses it to find the appropriate loader # for the given `module_name`, or for the environment in case `module_name` is `nil` or empty. # # @param module_name [String,nil] the name of the module # @return [Loader::Loader] the found loader # @raise [Puppet::ParseError] if no loader can be found # @api private def self.find_loader(module_name) loaders.find_loader(module_name) end def self.static_implementation_registry if !class_variable_defined?(:@@static_implementation_registry) || @@static_implementation_registry.nil? ir = Types::ImplementationRegistry.new Types::TypeParser.type_map.values.each { |t| ir.register_implementation(t.simple_name, t.class.name) } @@static_implementation_registry = ir end @@static_implementation_registry end def self.static_loader # The static loader can only be changed after a reboot if !class_variable_defined?(:@@static_loader) || @@static_loader.nil? @@static_loader = Loader::StaticLoader.new() @@static_loader.register_aliases Pcore.init(@@static_loader, static_implementation_registry) end @@static_loader end def self.implementation_registry loaders = Puppet.lookup(:loaders) { nil } loaders.nil? ? nil : loaders.implementation_registry end def register_implementations(obj_classes, name_authority) self.class.register_implementations_with_loader(obj_classes, name_authority, @private_environment_loader) end # Register implementations using the global static loader def self.register_static_implementations(obj_classes) register_implementations_with_loader(obj_classes, Pcore::RUNTIME_NAME_AUTHORITY, static_loader) end def self.register_implementations_with_loader(obj_classes, name_authority, loader) types = obj_classes.map do |obj_class| type = obj_class._pcore_type typed_name = Loader::TypedName.new(:type, type.name, name_authority) entry = loader.loaded_entry(typed_name) loader.set_entry(typed_name, type) if entry.nil? || entry.value.nil? type end # Resolve lazy so that all types can cross reference each other types.each { |type| type.resolve(loader) } end # Register the given type with the Runtime3TypeLoader. The registration will not happen unless # the type system has been initialized. # # @param name [String,Symbol] the name of the entity being set # @param origin [URI] the origin or the source where the type is defined # @api private def self.register_runtime3_type(name, origin) loaders = Puppet.lookup(:loaders) { nil } return nil if loaders.nil? rt3_loader = loaders.runtime3_type_loader return nil if rt3_loader.nil? name = name.to_s caps_name = Types::TypeFormatter.singleton.capitalize_segments(name) typed_name = Loader::TypedName.new(:type, name) rt3_loader.set_entry(typed_name, Types::PResourceType.new(caps_name), origin) nil end # Finds a loader to use when deserializing a catalog and then subsequenlty use user # defined types found in that catalog. # def self.catalog_loader loaders = Puppet.lookup(:loaders) { nil } if loaders.nil? loaders = Loaders.new(Puppet.lookup(:current_environment), true) Puppet.push_context(:loaders => loaders) end loaders.find_loader(nil) end # Finds the `Loaders` instance by looking up the :loaders in the global Puppet context # # @return [Loaders] the loaders instance # @raise [Puppet::ParseError] if loader has been bound to the global context # @api private def self.loaders loaders = Puppet.lookup(:loaders) { nil } raise Puppet::ParseError, _("Internal Error: Puppet Context ':loaders' missing") if loaders.nil? loaders end # Lookup a loader by its unique name. # # @param [String] loader_name the name of the loader to lookup # @return [Loader] the found loader # @raise [Puppet::ParserError] if no loader is found def [](loader_name) loader = @loaders_by_name[loader_name] if loader.nil? # Unable to find the module private loader. Try resolving the module loader = private_loader_for_module(loader_name[0..-9]) if loader_name.end_with?(' private') raise Puppet::ParseError, _("Unable to find loader named '%{loader_name}'") % { loader_name: loader_name } if loader.nil? end loader end # Finds the appropriate loader for the given `module_name`, or for the environment in case `module_name` # is `nil` or empty. # # @param module_name [String,nil] the name of the module # @return [Loader::Loader] the found loader # @raise [Puppet::ParseError] if no loader can be found # @api private def find_loader(module_name) if module_name.nil? || EMPTY_STRING == module_name # Use the public environment loader public_environment_loader else # TODO : Later check if definition is private, and then add it to private_loader_for_module # loader = public_loader_for_module(module_name) if loader.nil? raise Puppet::ParseError, _("Internal Error: did not find public loader for module: '%{module_name}'") % { module_name: module_name } end loader end end def implementation_registry # Environment specific implementation registry @implementation_registry ||= Types::ImplementationRegistry.new(self.class.static_implementation_registry) end def static_loader self.class.static_loader end def puppet_system_loader @puppet_system_loader end def runtime3_type_loader @runtime3_type_loader end def public_loader_for_module(module_name) md = @module_resolver[module_name] || (return nil) # Note, this loader is not resolved until there is interest in the visibility of entities from the # perspective of something contained in the module. (Many request may pass through a module loader # without it loading anything. # See {#private_loader_for_module}, and not in {#configure_loaders_for_modules} md.public_loader end def private_loader_for_module(module_name) md = @module_resolver[module_name] || (return nil) # Since there is interest in the visibility from the perspective of entities contained in the # module, it must be resolved (to provide this visibility). # See {#configure_loaders_for_modules} unless md.resolved? @module_resolver.resolve(md) end md.private_loader end def add_loader_by_name(loader) name = loader.loader_name if @loaders_by_name.include?(name) raise Puppet::ParseError, _("Internal Error: Attempt to redefine loader named '%{name}'") % { name: name } end @loaders_by_name[name] = loader end # Load the main manifest for the given environment # # There are two sources that can be used for the initial parse: # # 1. The value of `Puppet[:code]`: Puppet can take a string from # its settings and parse that as a manifest. This is used by various # Puppet applications to read in a manifest and pass it to the # environment as a side effect. This is attempted first. # 2. The contents of the environment's +manifest+ attribute: Puppet will # try to load the environment manifest. The manifest must be a file. # # @return [Model::Program] The manifest parsed into a model object def load_main_manifest parser = Parser::EvaluatingParser.singleton parsed_code = Puppet[:code] program = if parsed_code != "" parser.parse_string(parsed_code, 'unknown-source-location') else file = @environment.manifest # if the manifest file is a reference to a directory, parse and combine # all .pp files in that directory if file == Puppet::Node::Environment::NO_MANIFEST nil elsif File.directory?(file) raise Puppet::Error, "manifest of environment '#{@environment.name}' appoints directory '#{file}'. It must be a file" elsif File.exists?(file) parser.parse_file(file) else raise Puppet::Error, "manifest of environment '#{@environment.name}' appoints '#{file}'. It does not exist" end end instantiate_definitions(program, public_environment_loader) unless program.nil? program rescue Puppet::ParseErrorWithIssue => detail detail.environment = @environment.name raise rescue => detail msg = _('Could not parse for environment %{env}: %{detail}') % { env: @environment, detail: detail } error = Puppet::Error.new(msg) error.set_backtrace(detail.backtrace) raise error end # Add 4.x definitions found in the given program to the given loader. def instantiate_definitions(program, loader) program.definitions.each { |d| instantiate_definition(d, loader) } nil end # Add given 4.x definition to the given loader. def instantiate_definition(definition, loader) case definition when Model::PlanDefinition instantiate_PlanDefinition(definition, loader) when Model::FunctionDefinition instantiate_FunctionDefinition(definition, loader) when Model::TypeAlias instantiate_TypeAlias(definition, loader) when Model::TypeMapping instantiate_TypeMapping(definition, loader) else raise Puppet::ParseError, "Internal Error: Unknown type of definition - got '#{definition.class}'" end end private def instantiate_PlanDefinition(plan_definition, loader) typed_name, f = Loader::PuppetPlanInstantiator.create_from_model(plan_definition, loader) loader.set_entry(typed_name, f, plan_definition.locator.to_uri(plan_definition)) nil end def instantiate_FunctionDefinition(function_definition, loader) # Instantiate Function, and store it in the loader typed_name, f = Loader::PuppetFunctionInstantiator.create_from_model(function_definition, loader) loader.set_entry(typed_name, f, function_definition.locator.to_uri(function_definition)) nil end def instantiate_TypeAlias(type_alias, loader) # Bind the type alias to the loader using the alias Puppet::Pops::Loader::TypeDefinitionInstantiator.create_from_model(type_alias, loader) nil end def instantiate_TypeMapping(type_mapping, loader) tf = Types::TypeParser.singleton lhs = tf.interpret(type_mapping.type_expr, loader) rhs = tf.interpret_any(type_mapping.mapping_expr, loader) implementation_registry.register_type_mapping(lhs, rhs) nil end def create_puppet_system_loader() Loader::ModuleLoaders.system_loader_from(static_loader, self) end def create_environment_loader(environment) # This defines where to start parsing/evaluating - the "initial import" (to use 3x terminology) # Is either a reference to a single .pp file, or a directory of manifests. If the environment becomes # a module and can hold functions, types etc. then these are available across all other modules without # them declaring this dependency - it is however valuable to be able to treat it the same way # bindings and other such system related configuration. # This is further complicated by the many options available: # - The environment may not have a directory, the code comes from one appointed 'manifest' (site.pp) # - The environment may have a directory and also point to a 'manifest' # - The code to run may be set in settings (code) # Further complication is that there is nothing specifying what the visibility is into # available modules. (3x is everyone sees everything). # Puppet binder currently reads confdir/bindings - that is bad, it should be using the new environment support. # env_conf is setup from the environment_dir value passed into Puppet::Environments::Directories.new env_conf = Puppet.lookup(:environments).get_conf(environment.name) env_path = env_conf.nil? || !env_conf.is_a?(Puppet::Settings::EnvironmentConf) ? nil : env_conf.path_to_env if Puppet[:tasks] loader = Loader::ModuleLoaders.environment_loader_from(puppet_system_loader, self, env_path) else # Create the 3.x resource type loader static_loader.runtime_3_init @runtime3_type_loader = add_loader_by_name(Loader::Runtime3TypeLoader.new(puppet_system_loader, self, environment, env_conf.nil? ? nil : env_path)) if env_path.nil? # Not a real directory environment, cannot work as a module TODO: Drop when legacy env are dropped? loader = add_loader_by_name(Loader::SimpleEnvironmentLoader.new(@runtime3_type_loader, Loader::ENVIRONMENT)) else # View the environment as a module to allow loading from it - this module is always called 'environment' loader = Loader::ModuleLoaders.environment_loader_from(@runtime3_type_loader, self, env_path) end end # An environment has a module path even if it has a null loader configure_loaders_for_modules(loader, environment) # modules should see this loader @public_environment_loader = loader # Code in the environment gets to see all modules (since there is no metadata for the environment) # but since this is not given to the module loaders, they can not load global code (since they can not # have prior knowledge about this loader = add_loader_by_name(Loader::DependencyLoader.new(loader, Loader::ENVIRONMENT_PRIVATE, @module_resolver.all_module_loaders())) # The module loader gets the private loader via a lazy operation to look up the module's private loader. # This does not work for an environment since it is not resolved the same way. # TODO: The EnvironmentLoader could be a specialized loader instead of using a ModuleLoader to do the work. # This is subject to future design - an Environment may move more in the direction of a Module. @public_environment_loader.private_loader = loader loader end def configure_loaders_for_modules(parent_loader, environment) @module_resolver = mr = ModuleResolver.new(self) environment.modules.each do |puppet_module| # Create data about this module md = LoaderModuleData.new(puppet_module) mr[puppet_module.name] = md md.public_loader = Loader::ModuleLoaders.module_loader_from(parent_loader, self, md.name, md.path) end # NOTE: Do not resolve all modules here - this is wasteful if only a subset of modules / functions are used # The resolution is triggered by asking for a module's private loader, since this means there is interest # in the visibility from that perspective. # If later, it is wanted that all resolutions should be made up-front (to capture errors eagerly, this # can be introduced (better for production), but may be irritating in development mode. end # =LoaderModuleData # Information about a Module and its loaders. # TODO: should have reference to real model element containing all module data; this is faking it # TODO: Should use Puppet::Module to get the metadata (as a hash) - a somewhat blunt instrument, but that is # what is available with a reasonable API. # class LoaderModuleData attr_accessor :public_loader attr_accessor :private_loader attr_accessor :resolutions # The Puppet::Module this LoaderModuleData represents in the loader configuration attr_reader :puppet_module # @param puppet_module [Puppet::Module] the module instance for the module being represented # def initialize(puppet_module) @puppet_module = puppet_module @resolutions = [] @public_loader = nil @private_loader = nil end def name @puppet_module.name end def version @puppet_module.version end def path @puppet_module.path end def resolved? !@private_loader.nil? end def restrict_to_dependencies? @puppet_module.has_metadata? end def unmet_dependencies? @puppet_module.unmet_dependencies.any? end def dependency_names @puppet_module.dependencies_as_modules.collect(&:name) end end # Resolves module loaders - resolution of model dependencies is done by Puppet::Module # class ModuleResolver def initialize(loaders) @loaders = loaders @index = {} @all_module_loaders = nil end def [](name) @index[name] end def []=(name, module_data) @index[name] = module_data end def all_module_loaders @all_module_loaders ||= @index.values.map {|md| md.public_loader } end def resolve(module_data) if module_data.resolved? nil else module_data.private_loader = if module_data.restrict_to_dependencies? && !Puppet[:tasks] create_loader_with_only_dependencies_visible(module_data) else create_loader_with_all_modules_visible(module_data) end end end private def create_loader_with_all_modules_visible(from_module_data) Puppet.debug{"ModuleLoader: module '#{from_module_data.name}' has unknown dependencies - it will have all other modules visible"} @loaders.add_loader_by_name(Loader::DependencyLoader.new(from_module_data.public_loader, "#{from_module_data.name} private", all_module_loaders())) end def create_loader_with_only_dependencies_visible(from_module_data) if from_module_data.unmet_dependencies? if Puppet[:strict] != :off msg = "ModuleLoader: module '#{from_module_data.name}' has unresolved dependencies" \ " - it will only see those that are resolved." \ " Use 'puppet module list --tree' to see information about modules" case Puppet[:strict] when :error raise LoaderError.new(msg) when :warning Puppet.warn_once(:unresolved_module_dependencies, "unresolved_dependencies_for_module_#{from_module_data.name}", msg) end end end dependency_loaders = from_module_data.dependency_names.collect { |name| @index[name].public_loader } @loaders.add_loader_by_name(Loader::DependencyLoader.new(from_module_data.public_loader, "#{from_module_data.name} private", dependency_loaders)) end end end end ������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/pcore.rb��������������������������������������������������������������0000644�0052762�0001160�00000013736�13417161721�017525� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'uri' module Puppet::Pops module Pcore include Types::PuppetObject TYPE_URI_RX = Types::TypeFactory.regexp(URI.regexp) TYPE_URI = Types::TypeFactory.pattern(TYPE_URI_RX) TYPE_URI_ALIAS = Types::PTypeAliasType.new('Pcore::URI', nil, TYPE_URI) TYPE_SIMPLE_TYPE_NAME = Types::TypeFactory.pattern(/\A[A-Z]\w*\z/) TYPE_QUALIFIED_REFERENCE = Types::TypeFactory.pattern(/\A[A-Z][\w]*(?:::[A-Z][\w]*)*\z/) TYPE_MEMBER_NAME = Types::PPatternType.new([Types::PRegexpType.new(Patterns::PARAM_NAME)]) KEY_PCORE_URI = 'pcore_uri'.freeze KEY_PCORE_VERSION = 'pcore_version'.freeze PCORE_URI = 'http://puppet.com/2016.1/pcore' PCORE_VERSION = SemanticPuppet::Version.new(1,0,0) PARSABLE_PCORE_VERSIONS = SemanticPuppet::VersionRange.parse('1.x') RUNTIME_NAME_AUTHORITY = 'http://puppet.com/2016.1/runtime' def self._pcore_type @type end def self.annotate(instance, annotations_hash) annotations_hash.each_pair do |type, init_hash| type.implementation_class.annotate(instance) { init_hash } end instance end def self.init_env(loader) if Puppet[:tasks] add_object_type('Task', <<-PUPPET, loader) { attributes => { # Fully qualified name of the task name => { type => Pattern[/\\A[a-z][a-z0-9_]*(?:::[a-z][a-z0-9_]*)*\\z/] }, # Full path to executable executable => { type => String }, # Task description description => { type => Optional[String], value => undef }, # Puppet Task version puppet_task_version => { type => Integer, value => 1 }, # Type, description, and sensitive property of each parameter parameters => { type => Optional[Hash[ Pattern[/\\A[a-z][a-z0-9_]*\\z/], Struct[ Optional[description] => String, Optional[sensitive] => Boolean, type => Type]]], value => undef }, # Type, description, and sensitive property of each output output => { type => Optional[Hash[ Pattern[/\\A[a-z][a-z0-9_]*\\z/], Struct[ Optional[description] => String, Optional[sensitive] => Boolean, type => Type]]], value => undef }, supports_noop => { type => Boolean, value => false }, input_method => { type => String, value => 'both' }, } } PUPPET end end def self.init(loader, ir) add_alias('Pcore::URI_RX', TYPE_URI_RX, loader) add_type(TYPE_URI_ALIAS, loader) add_alias('Pcore::SimpleTypeName', TYPE_SIMPLE_TYPE_NAME, loader) add_alias('Pcore::MemberName', TYPE_MEMBER_NAME, loader) add_alias('Pcore::TypeName', TYPE_QUALIFIED_REFERENCE, loader) add_alias('Pcore::QRef', TYPE_QUALIFIED_REFERENCE, loader) Types::TypedModelObject.register_ptypes(loader, ir) @type = create_object_type(loader, ir, Pcore, 'Pcore', nil) ir.register_implementation_namespace('Pcore', 'Puppet::Pops::Pcore') ir.register_implementation_namespace('Puppet::AST', 'Puppet::Pops::Model') ir.register_implementation('Puppet::AST::Locator', 'Puppet::Pops::Parser::Locator::Locator19') Resource.register_ptypes(loader, ir) Lookup::Context.register_ptype(loader, ir); Lookup::DataProvider.register_types(loader) end # Create and register a new `Object` type in the Puppet Type System and map it to an implementation class # # @param loader [Loader::Loader] The loader where the new type will be registered # @param ir [ImplementationRegistry] The implementation registry that maps this class to the new type # @param impl_class [Class] The class that is the implementation of the type # @param type_name [String] The fully qualified name of the new type # @param parent_name [String,nil] The fully qualified name of the parent type # @param attributes_hash [Hash{String => Object}] A hash of attribute definitions for the new type # @param functions_hash [Hash{String => Object}] A hash of function definitions for the new type # @param equality [Array<String>] An array with names of attributes that participate in equality comparison # @return [PObjectType] the created type. Not yet resolved # # @api private def self.create_object_type(loader, ir, impl_class, type_name, parent_name, attributes_hash = EMPTY_HASH, functions_hash = EMPTY_HASH, equality = nil) init_hash = {} init_hash[Types::KEY_PARENT] = Types::PTypeReferenceType.new(parent_name) unless parent_name.nil? init_hash[Types::KEY_ATTRIBUTES] = attributes_hash unless attributes_hash.empty? init_hash[Types::KEY_FUNCTIONS] = functions_hash unless functions_hash.empty? init_hash[Types::KEY_EQUALITY] = equality unless equality.nil? ir.register_implementation(type_name, impl_class) add_type(Types::PObjectType.new(type_name, init_hash), loader) end def self.add_object_type(name, body, loader) add_type(Types::PObjectType.new(name, Parser::EvaluatingParser.new.parse_string(body).body), loader) end def self.add_alias(name, type, loader, name_authority = RUNTIME_NAME_AUTHORITY) add_type(Types::PTypeAliasType.new(name, nil, type), loader, name_authority) end def self.add_type(type, loader, name_authority = RUNTIME_NAME_AUTHORITY) loader.set_entry(Loader::TypedName.new(:type, type.name, name_authority), type) type end def self.register_implementations(impls, name_authority = RUNTIME_NAME_AUTHORITY) Loaders.loaders.register_implementations(impls, name_authority) end def self.register_aliases(aliases, name_authority = RUNTIME_NAME_AUTHORITY, loader = Loaders.loaders.private_environment_loader) aliases.each do |name, type_string| add_type(Types::PTypeAliasType.new(name, Types::TypeFactory.type_reference(type_string), nil), loader, name_authority) end aliases.each_key.map { |name| loader.load(:type, name).resolve(loader) } end end end ����������������������������������puppet-5.5.10/lib/puppet/pops/puppet_stack.rb�������������������������������������������������������0000644�0052762�0001160�00000003302�13417161721�021103� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops # Module for making a call such that there is an identifiable entry on # the ruby call stack enabling getting a puppet call stack # To use this make a call with: # ``` # Puppet::Pops::PuppetStack.stack(file, line, receiver, message, args) # ``` # To get the stack call: # ``` # Puppet::Pops::PuppetStack.stacktrace # # When getting a backtrace in Ruby, the puppet stack frames are # identified as coming from "in 'stack'" and having a ".pp" file # name. # To support testing, a given file that is an empty string, or nil # as well as a nil line number are supported. Such stack frames # will be represented with the text `unknown` and `0´ respectively. # module PuppetStack # Pattern matching an entry in the ruby stack that is a puppet entry PP_ENTRY_PATTERN = /^(.*\.pp)?:([0-9]+):in (`stack'|`block in call_function')/ # Sends a message to an obj such that it appears to come from # file, line when calling stacktrace. # def self.stack(file, line, obj, message, args, &block) file = '' if file.nil? line = 0 if line.nil? if block_given? Kernel.eval("obj.send(message, *args, &block)", Kernel.binding(), file, line) else Kernel.eval("obj.send(message, *args)", Kernel.binding(), file, line) end end def self.stacktrace caller().reduce([]) do |memo, loc| if loc =~ PP_ENTRY_PATTERN memo << [$1.nil? ? 'unknown' : $1, $2.to_i] end memo end end # Returns an Array with the top of the puppet stack, or an empty Array if there was no such entry. # def self.top_of_stack caller.each do |loc| if loc =~ PP_ENTRY_PATTERN return [$1.nil? ? 'unknown' : $1, $2.to_i] end end [] end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/pops/serialization.rb������������������������������������������������������0000644�0052762�0001160�00000002510�13417161721�021256� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Pops module Serialization def self.not_implemented(impl, method_name) raise NotImplementedError, "The class #{impl.class.name} should have implemented the method #{method_name}()" end class SerializationError < Puppet::Error end PCORE_TYPE_KEY = '__pcore_type__'.freeze # Key used when the value can be represented as, and recreated from, a single string that can # be passed to a `from_string` method or an array of values that can be passed to the default # initializer method. PCORE_VALUE_KEY = '__pcore_value__'.freeze # Type key used for hashes that contain keys that are not of type String PCORE_TYPE_HASH = 'Hash'.freeze # Type key used for symbols PCORE_TYPE_SENSITIVE = 'Sensitive'.freeze # Type key used for symbols PCORE_TYPE_SYMBOL = 'Symbol'.freeze # Type key used for Default PCORE_TYPE_DEFAULT = 'Default'.freeze # Type key used for document local references PCORE_LOCAL_REF_SYMBOL = 'LocalRef'.freeze end end require_relative 'serialization/json_path' require_relative 'serialization/from_data_converter' require_relative 'serialization/to_data_converter' require_relative 'serialization/serializer' require_relative 'serialization/deserializer' require_relative 'serialization/json' require_relative 'serialization/time_factory' require_relative 'serialization/object' ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/property.rb����������������������������������������������������������������0000644�0052762�0001160�00000064450�13417161721�017317� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The virtual base class for properties, which are the self-contained building # blocks for actually doing work on the system. require 'puppet' require 'puppet/parameter' # The Property class is the implementation of a resource's attributes of _property_ kind. # A Property is a specialized Resource Type Parameter that has both an 'is' (current) state, and # a 'should' (wanted state). However, even if this is conceptually true, the current _is_ value is # obtained by asking the associated provider for the value, and hence it is not actually part of a # property's state, and only available when a provider has been selected and can obtain the value (i.e. when # running on an agent). # # A Property (also in contrast to a parameter) is intended to describe a managed attribute of # some system entity, such as the name or mode of a file. # # The current value _(is)_ is read and written with the methods {#retrieve} and {#set}, and the wanted # value _(should)_ is read and written with the methods {#value} and {#value=} which delegate to # {#should} and {#should=}, i.e. when a property is used like any other parameter, it is the _should_ value # that is operated on. # # All resource type properties in the puppet system are derived from this class. # # The intention is that new parameters are created by using the DSL method {Puppet::Type.newproperty}. # # @abstract # @note Properties of Types are expressed using subclasses of this class. Such a class describes one # named property of a particular Type (as opposed to describing a type of property in general). This # limits the use of one (concrete) property class instance to occur only once for a given type's inheritance # chain. An instance of a Property class is the value holder of one instance of the resource type (e.g. the # mode of a file resource instance). # A Property class may server as the superclass _(parent)_ of another; e.g. a Size property that describes # handling of measurements such as kb, mb, gb. If a type requires two different size measurements it requires # one concrete class per such measure; e.g. MinSize (:parent => Size), and MaxSize (:parent => Size). # # @see Puppet::Type # @see Puppet::Parameter # # @api public # class Puppet::Property < Puppet::Parameter require 'puppet/property/ensure' # Returns the original wanted value(s) _(should)_ unprocessed by munging/unmunging. # The original values are set by {#value=} or {#should=}. # @return (see #should) # attr_reader :shouldorig # The noop mode for this property. # By setting a property's noop mode to `true`, any management of this property is inhibited. Calculation # and reporting still takes place, but if a change of the underlying managed entity's state # should take place it will not be carried out. This noop # setting overrides the overall `Puppet[:noop]` mode as well as the noop mode in the _associated resource_ # attr_writer :noop class << self # @todo Figure out what this is used for. Can not find any logic in the puppet code base that # reads or writes this attribute. # ??? Probably Unused attr_accessor :unmanaged # @return [Symbol] The name of the property as given when the property was created. # attr_reader :name # @!attribute [rw] array_matching # @comment note that $#46; is a period - char code require to not terminate sentence. # The `is` vs. `should` array matching mode; `:first`, or `:all`. # # @comment there are two blank chars after the symbols to cause a break - do not remove these. # * `:first` # This is primarily used for single value properties. When matched against an array of values # a match is true if the `is` value matches any of the values in the `should` array. When the `is` value # is also an array, the matching is performed against the entire array as the `is` value. # * `:all` # : This is primarily used for multi-valued properties. When matched against an array of # `should` values, the size of `is` and `should` must be the same, and all values in `is` must match # a value in `should`. # # @note The semantics of these modes are implemented by the method {#insync?}. That method is the default # implementation and it has a backwards compatible behavior that imposes additional constraints # on what constitutes a positive match. A derived property may override that method. # @return [Symbol] (:first) the mode in which matching is performed # @see #insync? # @dsl type # @api public # def array_matching @array_matching ||= :first end # @comment This is documented as an attribute - see the {array_matching} method. # def array_matching=(value) value = value.intern if value.is_a?(String) #TRANSLATORS 'Property#array_matching', 'first', and 'all' should not be translated raise ArgumentError, _("Supported values for Property#array_matching are 'first' and 'all'") unless [:first, :all].include?(value) @array_matching = value end # Used to mark a type property as having or lacking idempotency (on purpose # generally). This is used to avoid marking the property as a # corrective_change when there is known idempotency issues with the property # rendering a corrective_change flag as useless. # @return [Boolean] true if the property is marked as idempotent def idempotent @idempotent.nil? ? @idempotent = true : @idempotent end # Attribute setter for the idempotent attribute. # @param [bool] value boolean indicating if the property is idempotent. # @see idempotent def idempotent=(value) @idempotent = value end end # Looks up a value's name among valid values, to enable option lookup with result as a key. # @param name [Object] the parameter value to match against valid values (names). # @return {Symbol, Regexp} a value matching predicate # @api private # def self.value_name(name) if value = value_collection.match?(name) value.name end end # Returns the value of the given option (set when a valid value with the given "name" was defined). # @param name [Symbol, Regexp] the valid value predicate as returned by {value_name} # @param option [Symbol] the name of the wanted option # @return [Object] value of the option # @raise [NoMethodError] if the option is not supported # @todo Guessing on result of passing a non supported option (it performs send(option)). # @api private # def self.value_option(name, option) if value = value_collection.value(name) value.send(option) end end # Defines a new valid value for this property. # A valid value is specified as a literal (typically a Symbol), but can also be # specified with a Regexp. # # @param name [Symbol, Regexp] a valid literal value, or a regexp that matches a value # @param options [Hash] a hash with options # @option options [Symbol] :event The event that should be emitted when this value is set. # @todo Option :event original comment says "event should be returned...", is "returned" the correct word # to use? # @option options [Symbol] :invalidate_refreshes Indicates a change on this property should invalidate and # remove any scheduled refreshes (from notify or subscribe) targeted at the same resource. For example, if # a change in this property takes into account any changes that a scheduled refresh would have performed, # then the scheduled refresh would be deleted. # @option options [Object] any Any other option is treated as a call to a setter having the given # option name (e.g. `:required_features` calls `required_features=` with the option's value as an # argument). # # @dsl type # @api public def self.newvalue(name, options = {}, &block) value = value_collection.newvalue(name, options, &block) unless value.method.nil? method = value.method.to_sym if value.block if instance_methods(false).include?(method) raise ArgumentError, _("Attempt to redefine method %{method} with block") % { method: method } end define_method(method, &value.block) else # Let the method be an alias for calling the providers setter unless we already have this method alias_method(method, :call_provider) unless method_defined?(method) end end value end # Calls the provider setter method for this property with the given value as argument. # @return [Object] what the provider returns when calling a setter for this property's name # @raise [Puppet::Error] when the provider can not handle this property. # @see #set # @api private # def call_provider(value) # We have no idea how to handle this unless our parent have a provider self.fail "#{self.class.name} cannot handle values of type #{value.inspect}" unless @resource.provider method = self.class.name.to_s + "=" unless provider.respond_to? method self.fail "The #{provider.class.name} provider can not handle attribute #{self.class.name}" end provider.send(method, value) end # Formats a message for a property change from the given `current_value` to the given `newvalue`. # @return [String] a message describing the property change. # @note If called with equal values, this is reported as a change. # @raise [Puppet::DevError] if there were issues formatting the message # def change_to_s(current_value, newvalue) begin if current_value == :absent return "defined '#{name}' as #{should_to_s(newvalue)}" elsif newvalue == :absent or newvalue == [:absent] return "undefined '#{name}' from #{is_to_s(current_value)}" else return "#{name} changed #{is_to_s(current_value)} to #{should_to_s(newvalue)}" end rescue Puppet::Error, Puppet::DevError raise rescue => detail message = _("Could not convert change '%{name}' to string: %{detail}") % { name: name, detail: detail } Puppet.log_exception(detail, message) raise Puppet::DevError, message, detail.backtrace end end # Produces the name of the event to use to describe a change of this property's value. # The produced event name is either the event name configured for this property, or a generic # event based on the name of the property with suffix `_changed`, or if the property is # `:ensure`, the name of the resource type and one of the suffixes `_created`, `_removed`, or `_changed`. # @return [String] the name of the event that describes the change # def event_name value = self.should event_name = self.class.value_option(value, :event) and return event_name name == :ensure or return (name.to_s + "_changed").to_sym return (resource.type.to_s + case value when :present; "_created" when :absent; "_removed" else "_changed" end).to_sym end # Produces an event describing a change of this property. # In addition to the event attributes set by the resource type, this method adds: # # * `:name` - the event_name # * `:desired_value` - a.k.a _should_ or _wanted value_ # * `:property` - reference to this property # * `:source_description` - The containment path of this property, indicating what resource this # property is associated with and in what stage and class that resource # was declared, e.g. "/Stage[main]/Myclass/File[/tmp/example]/ensure" # * `:invalidate_refreshes` - if scheduled refreshes should be invalidated # * `:redacted` - if the event will be redacted (due to this property being sensitive) # # @return [Puppet::Transaction::Event] the created event # @see Puppet::Type#event def event(options = {}) attrs = { :name => event_name, :desired_value => should, :property => self, :source_description => path }.merge(options) if should and value = self.class.value_collection.match?(should) attrs[:invalidate_refreshes] = true if value.invalidate_refreshes end attrs[:redacted] = @sensitive resource.event attrs end # Determines whether the property is in-sync or not in a way that is protected against missing value. # @note If the wanted value _(should)_ is not defined or is set to a non-true value then this is # a state that can not be fixed and the property is reported to be in sync. # @return [Boolean] the protected result of `true` or the result of calling {#insync?}. # # @api private # @note Do not override this method. # def safe_insync?(is) # If there is no @should value, consider the property to be in sync. return true unless @should # Otherwise delegate to the (possibly derived) insync? method. insync?(is) end # Protects against override of the {#safe_insync?} method. # @raise [RuntimeError] if the added method is `:safe_insync?` # @api private # def self.method_added(sym) raise "Puppet::Property#safe_insync? shouldn't be overridden; please override insync? instead" if sym == :safe_insync? end # Checks if the current _(is)_ value is in sync with the wanted _(should)_ value. # The check if the two values are in sync is controlled by the result of {#match_all?} which # specifies a match of `:first` or `:all`). The matching of the _is_ value against the entire _should_ value # or each of the _should_ values (as controlled by {#match_all?} is performed by {#property_matches?}. # # A derived property typically only needs to override the {#property_matches?} method, but may also # override this method if there is a need to have more control over the array matching logic. # # @note The array matching logic in this method contains backwards compatible logic that performs the # comparison in `:all` mode by checking equality and equality of _is_ against _should_ converted to array of String, # and that the lengths are equal, and in `:first` mode by checking if one of the _should_ values # is included in the _is_ values. This means that the _is_ value needs to be carefully arranged to # match the _should_. # @todo The implementation should really do return is.zip(@should).all? {|a, b| property_matches?(a, b) } # instead of using equality check and then check against an array with converted strings. # @param is [Object] The current _(is)_ value to check if it is in sync with the wanted _(should)_ value(s) # @return [Boolean] whether the values are in sync or not. # @raise [Puppet::DevError] if wanted value _(should)_ is not an array. # @api public # def insync?(is) self.devfail "#{self.class.name}'s should is not array" unless @should.is_a?(Array) # an empty array is analogous to no should values return true if @should.empty? # Look for a matching value, either for all the @should values, or any of # them, depending on the configuration of this property. if match_all? then # Emulate Array#== using our own comparison function. # A non-array was not equal to an array, which @should always is. return false unless is.is_a? Array # If they were different lengths, they are not equal. return false unless is.length == @should.length # Finally, are all the elements equal? In order to preserve the # behaviour of previous 2.7.x releases, we need to impose some fun rules # on "equality" here. # # Specifically, we need to implement *this* comparison: the two arrays # are identical if the is values are == the should values, or if the is # values are == the should values, stringified. # # This does mean that property equality is not commutative, and will not # work unless the `is` value is carefully arranged to match the should. return (is == @should or is == @should.map(&:to_s)) # When we stop being idiots about this, and actually have meaningful # semantics, this version is the thing we actually want to do. # # return is.zip(@should).all? {|a, b| property_matches?(a, b) } else return @should.any? {|want| property_matches?(is, want) } end end # This method tests if two values are insync? outside of the properties current # should value. This works around the requirement for corrective_change analysis # that requires two older values to be compared with the properties potentially # custom insync? code. # # @param [Object] should the value it should be # @param [Object] is the value it is # @return [Boolean] whether or not the values are in sync or not # @api private def insync_values?(should, is) # Here be dragons. We're setting the should value of a property purely just to # call its insync? method, as it lacks a way to pass in a should. # Unfortunately there isn't an API compatible way of avoiding this, as both should # an insync? behaviours are part of the public API. Future API work should factor # this kind of arbitrary comparisons into the API to remove this complexity. -ken # Backup old should, set it to the new value, then call insync? on the property. old_should = @should begin @should = should insync?(is) rescue # Certain operations may fail, but we don't want to fail the transaction if we can # avoid it #TRANSLATORS 'insync_values?' should not be translated msg = _("Unknown failure using insync_values? on type: %{type} / property: %{name} to compare values %{should} and %{is}") % { type: self.resource.ref, name: self.name, should: should, is: is } Puppet.info(msg) # Return nil, ie. unknown nil ensure # Always restore old should @should = old_should end end # Checks if the given current and desired values are equal. # This default implementation performs this check in a backwards compatible way where # the equality of the two values is checked, and then the equality of current with desired # converted to a string. # # A derived implementation may override this method to perform a property specific equality check. # # The intent of this method is to provide an equality check suitable for checking if the property # value is in sync or not. It is typically called from {#insync?}. # def property_matches?(current, desired) # This preserves the older Puppet behaviour of doing raw and string # equality comparisons for all equality. I am not clear this is globally # desirable, but at least it is not a breaking change. --daniel 2011-11-11 current == desired or current == desired.to_s end # Produces a pretty printing string for the given value. # This default implementation calls {#format_value_for_display} on the class. A derived # implementation may perform property specific pretty printing when the _is_ values # are not already in suitable form. # @param value [Object] the value to format as a string # @return [String] a pretty printing string def is_to_s(value) self.class.format_value_for_display(value) end # Emits a log message at the log level specified for the associated resource. # The log entry is associated with this property. # @param msg [String] the message to log # @return [void] # def log(msg) Puppet::Util::Log.create( :level => resource[:loglevel], :message => msg, :source => self ) end # @return [Boolean] whether the {array_matching} mode is set to `:all` or not def match_all? self.class.array_matching == :all end # @return [Boolean] whether the property is marked as idempotent for the purposes # of calculating corrective change. def idempotent? self.class.idempotent end # @return [Symbol] the name of the property as stated when the property was created. # @note A property class (just like a parameter class) describes one specific property and # can only be used once within one type's inheritance chain. def name self.class.name end # @return [Boolean] whether this property is in noop mode or not. # Returns whether this property is in noop mode or not; if a difference between the # _is_ and _should_ values should be acted on or not. # The noop mode is a transitive setting. The mode is checked in this property, then in # the _associated resource_ and finally in Puppet[:noop]. # @todo This logic is different than Parameter#noop in that the resource noop mode overrides # the property's mode - in parameter it is the other way around. Bug or feature? # def noop # This is only here to make testing easier. if @resource.respond_to?(:noop?) @resource.noop? else if defined?(@noop) @noop else Puppet[:noop] end end end # Retrieves the current value _(is)_ of this property from the provider. # This implementation performs this operation by calling a provider method with the # same name as this property (i.e. if the property name is 'gid', a call to the # 'provider.gid' is expected to return the current value. # @return [Object] what the provider returns as the current value of the property # def retrieve provider.send(self.class.name) end # Sets the current _(is)_ value of this property. # The _name_ associated with the value is first obtained by calling {value_name}. A dynamically created setter # method associated with this _name_ is called if it exists, otherwise the value is set using using the provider's # setter method for this property by calling ({#call_provider}). # # @param value [Object] the value to set # @return [Object] returns the result of calling the setter method or {#call_provider} # @raise [Puppet::Error] if there were problems setting the value using the setter method or when the provider # setter should be used but there is no provider in the associated resource_ # @raise [Puppet::ResourceError] if there was a problem setting the value and it was not raised # as a Puppet::Error. The original exception is wrapped and logged. # @api public # def set(value) # Set a name for looking up associated options like the event. name = self.class.value_name(value) if method = self.class.value_option(name, :method) and self.respond_to?(method) begin self.send(method) rescue Puppet::Error raise rescue => detail error = Puppet::ResourceError.new(_("Could not set '%{value}' on %{class_name}: %{detail}") % { value: value, class_name: self.class.name, detail: detail }, @resource.file, @resource.line, detail) error.set_backtrace detail.backtrace Puppet.log_exception(detail, error.message) raise error end elsif block = self.class.value_option(name, :block) # FIXME It'd be better here to define a method, so that # the blocks could return values. self.instance_eval(&block) else call_provider(value) end end # Returns the wanted _(should)_ value of this property. # If the _array matching mode_ {#match_all?} is true, an array of the wanted values in unmunged format # is returned, else the first value in the array of wanted values in unmunged format is returned. # @return [Array<Object>, Object, nil] Array of values if {#match_all?} else a single value, or nil if there are no # wanted values. # @raise [Puppet::DevError] if the wanted value is non nil and not an array # # @note This method will potentially return different values than the original values as they are # converted via munging/unmunging. If the original values are wanted, call {#shouldorig}. # # @see #shouldorig # @api public # def should return nil unless defined?(@should) self.devfail "should for #{self.class.name} on #{resource.name} is not an array" unless @should.is_a?(Array) if match_all? return @should.collect { |val| self.unmunge(val) } else return self.unmunge(@should[0]) end end # Sets the wanted _(should)_ value of this property. # If the given value is not already an Array, it will be wrapped in one before being set. # This method also sets the cached original _should_ values returned by {#shouldorig}. # # @param values [Array<Object>, Object] the value(s) to set as the wanted value(s) # @raise [StandardError] when validation of a value fails (see {#validate}). # @api public # def should=(values) values = [values] unless values.is_a?(Array) @shouldorig = values values.each { |val| validate(val) } @should = values.collect { |val| self.munge(val) } end # Produces a pretty printing string for the given value. # This default implementation calls {#format_value_for_display} on the class. A derived # implementation may perform property specific pretty printing when the _should_ values # are not already in suitable form. # @param value [Object] the value to format as a string # @return [String] a pretty printing string def should_to_s(value) self.class.format_value_for_display(value) end # Synchronizes the current value _(is)_ and the wanted value _(should)_ by calling {#set}. # @raise [Puppet::DevError] if {#should} is nil # @todo The implementation of this method is somewhat inefficient as it computes the should # array twice. def sync devfail "Got a nil value for should" unless should set(should) end # Asserts that the given value is valid. # If the developer uses a 'validate' hook, this method will get overridden. # @raise [Exception] if the value is invalid, or value can not be handled. # @return [void] # @api private # def unsafe_validate(value) super validate_features_per_value(value) end # Asserts that all required provider features are present for the given property value. # @raise [ArgumentError] if a required feature is not present # @return [void] # @api private # def validate_features_per_value(value) if features = self.class.value_option(self.class.value_name(value), :required_features) features = Array(features) needed_features = features.collect { |f| f.to_s }.join(", ") unless provider.satisfies?(features) #TRANSLATORS 'Provider' refers to a Puppet provider class raise ArgumentError, _("Provider %{provider} must have features '%{needed_features}' to set '%{property}' to '%{value}'") % { provider: provider.class.name, needed_features: needed_features, property: self.class.name, value: value } end end end # @return [Object, nil] Returns the wanted _(should)_ value of this property. def value self.should end # (see #should=) def value=(values) self.should = values end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/property/������������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�016766� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/property/boolean.rb��������������������������������������������������������0000644�0052762�0001160�00000000226�13417161721�020725� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/coercion' class Puppet::Property::Boolean < Puppet::Property def unsafe_munge(value) Puppet::Coercion.boolean(value) end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/property/ensure.rb���������������������������������������������������������0000644�0052762�0001160�00000006165�13417161721�020617� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/property' # This property is automatically added to any {Puppet::Type} that responds # to the methods 'exists?', 'create', and 'destroy'. # # Ensure defaults to having the wanted _(should)_ value `:present`. # # @api public # class Puppet::Property::Ensure < Puppet::Property @name = :ensure def self.defaultvalues newvalue(:present) do if @resource.provider and @resource.provider.respond_to?(:create) @resource.provider.create else @resource.create end nil # return nil so the event is autogenerated end newvalue(:absent) do if @resource.provider and @resource.provider.respond_to?(:destroy) @resource.provider.destroy else @resource.destroy end nil # return nil so the event is autogenerated end defaultto do if @resource.managed? :present else nil end end # This doc will probably get overridden @doc ||= "The basic property that the resource should be in." end def self.inherited(sub) # Add in the two properties that everyone will have. sub.class_eval do end end def change_to_s(currentvalue, newvalue) begin if currentvalue == :absent || currentvalue.nil? return _("created") elsif newvalue == :absent return _("removed") else return _('%{name} changed %{is} to %{should}') % { name: name, is: is_to_s(currentvalue), should: should_to_s(newvalue) } end rescue Puppet::Error, Puppet::DevError raise rescue => detail raise Puppet::DevError, _("Could not convert change %{name} to string: %{detail}") % { name: self.name, detail: detail }, detail.backtrace end end # Retrieves the _is_ value for the ensure property. # The existence of the resource is checked by first consulting the provider (if it responds to # `:exists`), and secondly the resource. A a value of `:present` or `:absent` is returned # depending on if the managed entity exists or not. # # @return [Symbol] a value of `:present` or `:absent` depending on if it exists or not # @raise [Puppet::DevError] if neither the provider nor the resource responds to `:exists` # def retrieve # XXX This is a problem -- whether the object exists or not often # depends on the results of other properties, yet we're the first property # to get checked, which means that those other properties do not have # @is values set. This seems to be the source of quite a few bugs, # although they're mostly logging bugs, not functional ones. if prov = @resource.provider and prov.respond_to?(:exists?) result = prov.exists? elsif @resource.respond_to?(:exists?) result = @resource.exists? else raise Puppet::DevError, _("No ability to determine if %{name} exists") % { name: @resource.class.name } end if result return :present else return :absent end end # If they're talking about the thing at all, they generally want to # say it should exist. #defaultto :present defaultto do if @resource.managed? :present else nil end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/property/keyvalue.rb�������������������������������������������������������0000644�0052762�0001160�00000012324�13417161721�021135� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/property' module Puppet class Property # This subclass of {Puppet::Property} manages string key value pairs. # In order to use this property: # # * the _should_ value must be an array of key-value pairs separated by the 'separator' # * the retrieve method should return a hash with the keys as symbols # @note **IMPORTANT**: In order for this property to work there must also be a 'membership' parameter # The class that inherits from property should override that method with the symbol for the membership # @todo The node with an important message is not very clear. # class KeyValue < Property class << self # This is a class-level variable that child properties can override # if they wish. attr_accessor :log_only_changed_or_new_keys end self.log_only_changed_or_new_keys = false def hash_to_key_value_s(hash) if self.class.log_only_changed_or_new_keys hash = hash.select { |k, _| @changed_or_new_keys.include?(k) } end hash.map { |*pair| pair.join(separator) }.join(delimiter) end def should_to_s(should_value) hash_to_key_value_s(should_value) end def is_to_s(current_value) hash_to_key_value_s(current_value) end def membership :key_value_membership end def inclusive? @resource[membership] == :inclusive end def hashify_should # Puppet casts all should values to arrays. Thus, if the user # passed in a hash for our property's should value, the should_value # parameter will be a single element array so we just extract our value # directly. if ! @should.empty? && @should.first.is_a?(Hash) return @should.first end # Here, should is an array of key/value pairs. @should.inject({}) do |hash, key_value| tmp = key_value.split(separator) hash[tmp[0].strip.intern] = tmp[1] hash end end def process_current_hash(current) return {} if current == :absent #inclusive means we are managing everything so if it isn't in should, its gone current.each_key { |key| current[key] = nil } if inclusive? current end def should return nil unless @should members = hashify_should current = process_current_hash(retrieve) #shared keys will get overwritten by members should_value = current.merge(members) # Figure out the keys that will actually change in our Puppet run. # This lets us reduce the verbosity of Puppet's logging for instances # of this class when we want to. # # NOTE: We use ||= here because we only need to compute the # changed_or_new_keys once (since this property will only be synced once). # @changed_or_new_keys ||= should_value.keys.select do |key| ! current.key?(key) || current[key] != should_value[key] end should_value end # @return [String] Returns a default separator of "=" def separator "=" end # @return [String] Returns a default delimiter of ";" def delimiter ";" end # Retrieves the key-hash from the provider by invoking its method named the same as this property. # @return [Hash] the hash from the provider, or `:absent` # def retrieve #ok, some 'convention' if the keyvalue property is named properties, provider should implement a properties method if key_hash = provider.send(name) and key_hash != :absent return key_hash else return :absent end end # Returns true if there is no _is_ value, else returns if _is_ is equal to _should_ using == as comparison. # @return [Boolean] whether the property is in sync or not. def insync?(is) return true unless is (is == self.should) end # We only accept an array of key/value pairs (strings), a single # key/value pair (string) or a Hash as valid values for our property. # Note that for an array property value, the 'value' passed into the # block corresponds to the array element. validate do |value| unless value.is_a?(String) || value.is_a?(Hash) raise ArgumentError, _("The %{name} property must be specified as a hash or an array of key/value pairs (strings)!") % { name: name } end next if value.is_a?(Hash) unless value.include?("#{separator}") raise ArgumentError, _("Key/value pairs must be separated by '%{separator}'") % {separator: separator} end end # The validate step ensures that our passed-in value is # either a String or a Hash. If our value's a string, # then nothing else needs to be done. Otherwise, we need # to stringify the hash's keys and values to match our # internal representation of the property's value. munge do |value| next value if value.is_a?(String) munged_value = value.to_a.map! do |hash_key, hash_value| [hash_key.to_s.strip.to_sym, hash_value.to_s] end Hash[munged_value] end end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/property/list.rb�����������������������������������������������������������0000644�0052762�0001160�00000003222�13417161721�020260� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/property' module Puppet class Property # This subclass of {Puppet::Property} manages an unordered list of values. # For an ordered list see {Puppet::Property::OrderedList}. # class List < Property def is_to_s(currentvalue) currentvalue == :absent ? super(currentvalue) : currentvalue.join(delimiter) end def membership :membership end def add_should_with_current(should, current) should += current if current.is_a?(Array) should.uniq end def inclusive? @resource[membership] == :inclusive end #dearrayify was motivated because to simplify the implementation of the OrderedList property def dearrayify(array) array.sort.join(delimiter) end def should return nil unless @should members = @should #inclusive means we are managing everything so if it isn't in should, its gone members = add_should_with_current(members, retrieve) if ! inclusive? dearrayify(members) end def delimiter "," end def retrieve #ok, some 'convention' if the list property is named groups, provider should implement a groups method if provider and tmp = provider.send(name) and tmp != :absent return tmp.split(delimiter) else return :absent end end def prepare_is_for_comparison(is) if is == :absent is = [] end dearrayify(is) end def insync?(is) return true unless is (prepare_is_for_comparison(is) == self.should) end end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/property/ordered_list.rb���������������������������������������������������0000644�0052762�0001160�00000001474�13417161721�021773� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/property/list' module Puppet class Property # This subclass of {Puppet::Property} manages an ordered list of values. # The maintained order is the order defined by the 'current' set of values (i.e. the # original order is not disrupted). Any additions are added after the current values # in their given order). # # For an unordered list see {Puppet::Property::List}. # class OrderedList < List def add_should_with_current(should, current) if current.is_a?(Array) #tricky trick #Preserve all the current items in the list #but move them to the back of the line should = should + (current - should) end should end def dearrayify(array) array.join(delimiter) end end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/������������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�016734� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/aix_object.rb�����������������������������������������������������0000644�0052762�0001160�00000042364�13417161721�021374� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Common code for AIX user/group providers. class Puppet::Provider::AixObject < Puppet::Provider desc "Generic AIX resource provider" # Class representing a MappedObject, which can either be an # AIX attribute or a Puppet property. This class lets us # write something like: # # attribute = mappings[:aix_attribute][:uid] # attribute.name # attribute.convert_property_value(uid) # # property = mappings[:puppet_property][:id] # property.name # property.convert_attribute_value(id) # # NOTE: This is an internal class specific to AixObject. It is # not meant to be used anywhere else. That's why we do not have # any validation code in here. # # NOTE: See the comments in the class-level mappings method to # understand what we mean by pure and impure conversion functions. # # NOTE: The 'mapping' code, including this class, could possibly # be moved to a separate module so that it can be re-used in some # of our other providers. See PUP-9082. class MappedObject attr_reader :name def initialize(name, conversion_fn, conversion_fn_code) @name = name @conversion_fn = conversion_fn @conversion_fn_code = conversion_fn_code return unless pure_conversion_fn? # Our conversion function is pure, so we can go ahead # and define it. This way, we can use this MappedObject # at the class-level as well as at the instance-level. define_singleton_method(@conversion_fn) do |value| @conversion_fn_code.call(value) end end def pure_conversion_fn? @conversion_fn_code.arity == 1 end # Sets our MappedObject's provider. This only makes sense # if it has an impure conversion function. We will call this # in the instance-level mappings method after the provider # instance has been created to define our conversion function. # Note that a MappedObject with an impure conversion function # cannot be used at the class level. def set_provider(provider) define_singleton_method(@conversion_fn) do |value| @conversion_fn_code.call(provider, value) end end end class << self #------------- # Mappings # ------------ def mappings return @mappings if @mappings @mappings = {} @mappings[:aix_attribute] = {} @mappings[:puppet_property] = {} @mappings end # Add a mapping from a Puppet property to an AIX attribute. The info must include: # # * :puppet_property -- The puppet property corresponding to this attribute # * :aix_attribute -- The AIX attribute corresponding to this attribute. Defaults # to puppet_property if this is not provided. # * :property_to_attribute -- A lambda that converts a Puppet Property to an AIX attribute # value. Defaults to the identity function if not provided. # * :attribute_to_property -- A lambda that converts an AIX attribute to a Puppet property. # Defaults to the identity function if not provided. # # NOTE: The lambdas for :property_to_attribute or :attribute_to_property can be 'pure' # or 'impure'. A 'pure' lambda is one that needs only the value to do the conversion, # while an 'impure' lambda is one that requires the provider instance along with the # value. 'Pure' lambdas have the interface 'do |value| ...' while 'impure' lambdas have # the interface 'do |provider, value| ...'. # # NOTE: 'Impure' lambdas are useful in case we need to generate more specific error # messages or pass-in instance-specific command-line arguments. def mapping(info = {}) identity_fn = lambda { |x| x } info[:aix_attribute] ||= info[:puppet_property] info[:property_to_attribute] ||= identity_fn info[:attribute_to_property] ||= identity_fn mappings[:aix_attribute][info[:puppet_property]] = MappedObject.new( info[:aix_attribute], :convert_property_value, info[:property_to_attribute] ) mappings[:puppet_property][info[:aix_attribute]] = MappedObject.new( info[:puppet_property], :convert_attribute_value, info[:attribute_to_property] ) end # Creates a mapping from a purely numeric Puppet property to # an attribute def numeric_mapping(info = {}) property = info[:puppet_property] # We have this validation here b/c not all numeric properties # handle this at the property level (e.g. like the UID). Given # that, we might as well go ahead and do this validation for all # of our numeric properties. Doesn't hurt. info[:property_to_attribute] = lambda do |value| unless value.is_a?(Integer) raise ArgumentError, _("Invalid value %{value}: %{property} must be an Integer!") % { value: value, property: property } end value.to_s end # AIX will do the right validation to ensure numeric attributes # can't be set to non-numeric values, so no need for the extra clutter. info[:attribute_to_property] = lambda do |value| value.to_i end mapping(info) end #------------- # Useful Class Methods # ------------ # Defines the getter and setter methods for each Puppet property that's mapped # to an AIX attribute. We define only a getter for the :attributes property. # # Provider subclasses should call this method after they've defined all of # their <puppet_property> => <aix_attribute> mappings. def mk_resource_methods # Define the Getter methods for each of our properties + the attributes # property properties = [:attributes] properties += mappings[:aix_attribute].keys properties.each do |property| # Define the getter define_method(property) do get(property) end # We have a custom setter for the :attributes property, # so no need to define it. next if property == :attributes # Define the setter define_method("#{property}=".to_sym) do |value| set(property, value) end end end # This helper splits a list separated by sep into its corresponding # items. Note that a key precondition here is that none of the items # in the list contain sep. # # Let A be the return value. Then one of our postconditions is: # A.join(sep) == list # # NOTE: This function is only used by the parse_colon_separated_list # function below. It is meant to be an inner lambda. The reason it isn't # here is so we avoid having to create a proc. object for the split_list # lambda each time parse_colon_separated_list is invoked. This will happen # quite often since it is used at the class level and at the instance level. # Since this function is meant to be an inner lambda and thus not exposed # anywhere else, we do not have any unit tests for it. These test cases are # instead covered by the unit tests for parse_colon_separated_list def split_list(list, sep) return [""] if list.empty? list.split(sep, -1) end # Parses a colon-separated list. Example includes something like: # <item1>:<item2>:<item3>:<item4> # # Returns an array of the parsed items, e.g. # [ <item1>, <item2>, <item3>, <item4> ] # # Note that colons inside items are escaped by #! def parse_colon_separated_list(colon_list) # ALGORITHM: # Treat the colon_list as a list separated by '#!:' We will get # something like: # [ <chunk1>, <chunk2>, ... <chunkn> ] # # Each chunk is now a list separated by ':' and none of the items # in each chunk contains an escaped ':'. Now, split each chunk on # ':' to get: # [ [<piece11>, ..., <piece1n>], [<piece21>, ..., <piece2n], ... ] # # Now note that <item1> = <piece11>, <item2> = <piece12> in our original # list, and that <itemn> = <piece1n>#!:<piece21>. This is the main idea # behind what our inject method is trying to do at the end, except that # we replace '#!:' with ':' since the colons are no longer escaped. chunks = split_list(colon_list, '#!:') chunks.map! { |chunk| split_list(chunk, ':') } chunks.inject do |accum, chunk| left = accum.pop right = chunk.shift accum.push("#{left}:#{right}") accum += chunk accum end end # Parses the AIX objects from the command output, returning an array of # hashes with each hash having the following schema: # { # :name => <object_name> # :attributes => <object_attributes> # } # # Output should be of the form # #name:<attr1>:<attr2> ... # <name>:<value1>:<value2> ... # #name:<attr1>:<attr2> ... # <name>:<value1>:<value2> ... # # NOTE: We need to parse the colon-formatted output in case we have # space-separated attributes (e.g. 'gecos'). ":" characters are escaped # with a "#!". def parse_aix_objects(output) # Object names cannot begin with '#', so we are safe to # split individual users this way. We do not have to worry # about an empty list either since there is guaranteed to be # at least one instance of an AIX object (e.g. at least one # user or one group on the system). _, *objects = output.chomp.split(/^#/) objects.map! do |object| attributes_line, values_line = object.chomp.split("\n") attributes = parse_colon_separated_list(attributes_line.chomp) attributes.map!(&:to_sym) values = parse_colon_separated_list(values_line.chomp) attributes_hash = Hash[attributes.zip(values)] object_name = attributes_hash.delete(:name) Hash[[[:name, object_name.to_s], [:attributes, attributes_hash]]] end objects end # Lists all instances of the given object, taking in an optional set # of ia_module arguments. Returns an array of hashes, each hash # having the schema # { # :name => <object_name> # :id => <object_id> # } def list_all(ia_module_args = []) cmd = [command(:list), '-c', *ia_module_args, '-a', 'id', 'ALL'] parse_aix_objects(execute(cmd)).to_a.map do |object| name = object[:name] id = object[:attributes].delete(:id) Hash[[[:name, name,],[:id, id]]] end end #------------- # Provider API # ------------ def instances list_all.to_a.map! do |object| new({ :name => object[:name] }) end end end # Instantiate our mappings. These need to be at the instance-level # since some of our mapped objects may have impure conversion functions # that need our provider instance. def mappings return @mappings if @mappings @mappings = {} self.class.mappings.each do |type, mapped_objects| @mappings[type] = {} mapped_objects.each do |input, mapped_object| if mapped_object.pure_conversion_fn? # Our mapped_object has a pure conversion function so we # can go ahead and use it as-is. @mappings[type][input] = mapped_object next end # Otherwise, we need to dup it and set its provider to our # provider instance. The dup is necessary so that we do not # touch the class-level mapped object. @mappings[type][input] = mapped_object.dup @mappings[type][input].set_provider(self) end end @mappings end # Converts the given attributes hash to CLI args. def attributes_to_args(attributes) attributes.map do |attribute, value| "#{attribute}=#{value}" end end def ia_module_args return [] unless @resource[:ia_load_module] ["-R", @resource[:ia_load_module].to_s] end def lscmd [self.class.command(:list), '-c'] + ia_module_args + [@resource[:name]] end def addcmd(attributes) attribute_args = attributes_to_args(attributes) [self.class.command(:add)] + ia_module_args + attribute_args + [@resource[:name]] end def deletecmd [self.class.command(:delete)] + ia_module_args + [@resource[:name]] end def modifycmd(new_attributes) attribute_args = attributes_to_args(new_attributes) [self.class.command(:modify)] + ia_module_args + attribute_args + [@resource[:name]] end # Modifies the AIX object by setting its new attributes. def modify_object(new_attributes) execute(modifycmd(new_attributes)) object_info(true) end # Gets a Puppet property's value from object_info def get(property) return :absent unless exists? object_info[property] || :absent end # Sets a mapped Puppet property's value. def set(property, value) aix_attribute = mappings[:aix_attribute][property] modify_object( { aix_attribute.name => aix_attribute.convert_property_value(value) } ) rescue Puppet::ExecutionFailure => detail raise Puppet::Error, _("Could not set %{property} on %{resource}[%{name}]: %{detail}") % { property: property, resource: @resource.class.name, name: @resource.name, detail: detail }, detail.backtrace end # This routine validates our new attributes property value to ensure # that it does not contain any Puppet properties. def validate_new_attributes(new_attributes) # Gather all of the <puppet property>, <aix attribute> conflicts to print # them all out when we create our error message. This makes it easy for the # user to update their manifest based on our error message. conflicts = {} mappings[:aix_attribute].each do |property, aix_attribute| next unless new_attributes.key?(aix_attribute.name) conflicts[:properties] ||= [] conflicts[:properties].push(property) conflicts[:attributes] ||= [] conflicts[:attributes].push(aix_attribute.name) end return if conflicts.empty? properties, attributes = conflicts.keys.map do |key| conflicts[key].map! { |name| "'#{name}'" }.join(', ') end detail = _("attributes is setting the %{properties} properties via. the %{attributes} attributes, respectively! Please specify these property values in the resource declaration instead.") % { properties: properties, attributes: attributes } raise Puppet::Error, _("Could not set attributes on %{resource}[%{name}]: %{detail}") % { resource: @resource.class.name, name: @resource.name, detail: detail } end # Modifies the attribute property. Note we raise an error if the user specified # an AIX attribute corresponding to a Puppet property. def attributes=(new_attributes) validate_new_attributes(new_attributes) modify_object(new_attributes) rescue Puppet::ExecutionFailure => detail raise Puppet::Error, _("Could not set attributes on %{resource}[%{name}]: %{detail}") % { resource: @resource.class.name, name: @resource.name, detail: detail }, detail.backtrace end # Collects the current property values of all mapped properties + # the attributes property. def object_info(refresh = false) return @object_info if @object_info && ! refresh @object_info = nil begin output = execute(lscmd) rescue Puppet::ExecutionFailure Puppet.debug(_("aix.object_info(): Could not find %{resource}[%{name}]") % { resource: @resource.class.name, name: @resource.name }) return @object_info end # If lscmd succeeds, then output will contain our object's information. # Thus, .parse_aix_objects will always return a single element array. aix_attributes = self.class.parse_aix_objects(output).first[:attributes] aix_attributes.each do |attribute, value| @object_info ||= {} # If our attribute has a Puppet property, then we store that. Else, we store it as part # of our :attributes property hash if (property = mappings[:puppet_property][attribute]) @object_info[property.name] = property.convert_attribute_value(value) else @object_info[:attributes] ||= {} @object_info[:attributes][attribute] = value end end @object_info end #------------- # Methods that manage the ensure property # ------------ # Check that the AIX object exists def exists? ! object_info.nil? end # Creates a new instance of the resource def create attributes = @resource.should(:attributes) || {} validate_new_attributes(attributes) mappings[:aix_attribute].each do |property, aix_attribute| property_should = @resource.should(property) next if property_should.nil? attributes[aix_attribute.name] = aix_attribute.convert_property_value(property_should) end execute(addcmd(attributes)) rescue Puppet::ExecutionFailure => detail raise Puppet::Error, _("Could not create %{resource} %{name}: %{detail}") % { resource: @resource.class.name, name: @resource.name, detail: detail }, detail.backtrace end # Deletes this instance resource def delete execute(deletecmd) # Recollect the object info so that our current properties reflect # the actual state of the system. Otherwise, puppet resource reports # the wrong info. at the end. Note that this should return nil. object_info(true) rescue Puppet::ExecutionFailure => detail raise Puppet::Error, _("Could not delete %{resource} %{name}: %{detail}") % { resource: @resource.class.name, name: @resource.name, detail: detail }, detail.backtrace end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/command.rb��������������������������������������������������������0000644�0052762�0001160�00000002134�13417161721�020672� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# A command that can be executed on the system class Puppet::Provider::Command attr_reader :executable attr_reader :name # @param [String] name A way of referencing the name # @param [String] executable The path to the executable file # @param resolver An object for resolving the executable to an absolute path (usually Puppet::Util) # @param executor An object for performing the actual execution of the command (usually Puppet::Util::Execution) # @param [Hash] options Extra options to be used when executing (see Puppet::Util::Execution#execute) def initialize(name, executable, resolver, executor, options = {}) @name = name @executable = executable @resolver = resolver @executor = executor @options = options end # @param args [Array<String>] Any command line arguments to pass to the executable # @return The output from the command def execute(*args) resolved_executable = @resolver.which(@executable) or raise Puppet::MissingCommand, _("Command %{name} is missing") % { name: @name } @executor.execute([resolved_executable] + args, @options) end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/confine.rb��������������������������������������������������������0000644�0052762�0001160�00000000415�13417161721�020675� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Confines have been moved out of the provider as they are also used for other things. # This provides backwards compatibility for people still including this old location. require 'puppet/provider' require 'puppet/confine' Puppet::Provider::Confine = Puppet::Confine ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/exec/�������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�017660� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/exec/posix.rb�����������������������������������������������������0000644�0052762�0001160�00000003065�13417161721�021346� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/exec' Puppet::Type.type(:exec).provide :posix, :parent => Puppet::Provider::Exec do has_feature :umask confine :feature => :posix defaultfor :feature => :posix desc <<-EOT Executes external binaries directly, without passing through a shell or performing any interpolation. This is a safer and more predictable way to execute most commands, but prevents the use of globbing and shell built-ins (including control logic like "for" and "if" statements). EOT # Verify that we have the executable def checkexe(command) exe = extractexe(command) if File.expand_path(exe) == exe if !Puppet::FileSystem.exist?(exe) raise ArgumentError, _("Could not find command '%{exe}'") % { exe: exe } elsif !File.file?(exe) raise ArgumentError, _("'%{exe}' is a %{klass}, not a file") % { exe: exe, klass: File.ftype(exe) } elsif !File.executable?(exe) raise ArgumentError, _("'%{exe}' is not executable") % { exe: exe } end return end if resource[:path] Puppet::Util.withenv :PATH => resource[:path].join(File::PATH_SEPARATOR) do return if which(exe) end end # 'which' will only return the command if it's executable, so we can't # distinguish not found from not executable raise ArgumentError, _("Could not find command '%{exe}'") % { exe: exe } end def run(command, check = false) if resource[:umask] Puppet::Util::withumask(resource[:umask]) { super(command, check) } else super(command, check) end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/exec/shell.rb�����������������������������������������������������0000644�0052762�0001160�00000001374�13417161721�021314� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.type(:exec).provide :shell, :parent => :posix do include Puppet::Util::Execution confine :feature => :posix desc <<-EOT Passes the provided command through `/bin/sh`; only available on POSIX systems. This allows the use of shell globbing and built-ins, and does not require that the path to a command be fully-qualified. Although this can be more convenient than the `posix` provider, it also means that you need to be more careful with escaping; as ever, with great power comes etc. etc. This provider closely resembles the behavior of the `exec` type in Puppet 0.25.x. EOT def run(command, check = false) super(['/bin/sh', '-c', command], check) end def validatecmd(command) true end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/exec/windows.rb���������������������������������������������������0000644�0052762�0001160�00000003527�13417161721�021701� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/exec' Puppet::Type.type(:exec).provide :windows, :parent => Puppet::Provider::Exec do confine :operatingsystem => :windows defaultfor :operatingsystem => :windows desc <<-'EOT' Execute external binaries on Windows systems. As with the `posix` provider, this provider directly calls the command with the arguments given, without passing it through a shell or performing any interpolation. To use shell built-ins --- that is, to emulate the `shell` provider on Windows --- a command must explicitly invoke the shell: exec {'echo foo': command => 'cmd.exe /c echo "foo"', } If no extension is specified for a command, Windows will use the `PATHEXT` environment variable to locate the executable. **Note on PowerShell scripts:** PowerShell's default `restricted` execution policy doesn't allow it to run saved scripts. To run PowerShell scripts, specify the `remotesigned` execution policy as part of the command: exec { 'test': path => 'C:/Windows/System32/WindowsPowerShell/v1.0', command => 'powershell -executionpolicy remotesigned -file C:/test.ps1', } EOT # Verify that we have the executable def checkexe(command) exe = extractexe(command) if absolute_path?(exe) if !Puppet::FileSystem.exist?(exe) raise ArgumentError, _("Could not find command '%{exe}'") % { exe: exe } elsif !File.file?(exe) raise ArgumentError, _("'%{exe}' is a %{klass}, not a file") % { exe: exe, klass: File.ftype(exe) } end return end if resource[:path] Puppet::Util.withenv( {'PATH' => resource[:path].join(File::PATH_SEPARATOR)}, :windows) do return if which(exe) end end raise ArgumentError, _("Could not find command '%{exe}'") % { exe: exe } end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/file/�������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�017653� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/file/posix.rb�����������������������������������������������������0000644�0052762�0001160�00000006654�13417161721�021350� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.type(:file).provide :posix do desc "Uses POSIX functionality to manage file ownership and permissions." confine :feature => :posix has_features :manages_symlinks include Puppet::Util::POSIX include Puppet::Util::Warnings require 'etc' def uid2name(id) return id.to_s if id.is_a?(Symbol) or id.is_a?(String) return nil if id > Puppet[:maximum_uid].to_i begin user = Etc.getpwuid(id) rescue TypeError, ArgumentError return nil end if user.uid == "" return nil else return user.name end end # Determine if the user is valid, and if so, return the UID def name2uid(value) Integer(value) rescue uid(value) || false end def gid2name(id) return id.to_s if id.is_a?(Symbol) or id.is_a?(String) return nil if id > Puppet[:maximum_uid].to_i begin group = Etc.getgrgid(id) rescue TypeError, ArgumentError return nil end if group.gid == "" return nil else return group.name end end def name2gid(value) Integer(value) rescue gid(value) || false end def owner unless stat = resource.stat return :absent end currentvalue = stat.uid # On OS X, files that are owned by -2 get returned as really # large UIDs instead of negative ones. This isn't a Ruby bug, # it's an OS X bug, since it shows up in perl, too. if currentvalue > Puppet[:maximum_uid].to_i self.warning _("Apparently using negative UID (%{currentvalue}) on a platform that does not consistently handle them") % { currentvalue: currentvalue } currentvalue = :silly end currentvalue end def owner=(should) # Set our method appropriately, depending on links. if resource[:links] == :manage method = :lchown else method = :chown end begin File.send(method, should, nil, resource[:path]) rescue => detail raise Puppet::Error, _("Failed to set owner to '%{should}': %{detail}") % { should: should, detail: detail }, detail.backtrace end end def group return :absent unless stat = resource.stat currentvalue = stat.gid # On OS X, files that are owned by -2 get returned as really # large GIDs instead of negative ones. This isn't a Ruby bug, # it's an OS X bug, since it shows up in perl, too. if currentvalue > Puppet[:maximum_uid].to_i self.warning _("Apparently using negative GID (%{currentvalue}) on a platform that does not consistently handle them") % { currentvalue: currentvalue } currentvalue = :silly end currentvalue end def group=(should) # Set our method appropriately, depending on links. if resource[:links] == :manage method = :lchown else method = :chown end begin File.send(method, nil, should, resource[:path]) rescue => detail raise Puppet::Error, _("Failed to set group to '%{should}': %{detail}") % { should: should, detail: detail }, detail.backtrace end end def mode if stat = resource.stat return (stat.mode & 007777).to_s(8).rjust(4, '0') else return :absent end end def mode=(value) begin File.chmod(value.to_i(8), resource[:path]) rescue => detail error = Puppet::Error.new(_("failed to set mode %{mode} on %{path}: %{message}") % { mode: mode, path: resource[:path], message: detail.message }) error.set_backtrace detail.backtrace raise error end end end ������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/file/windows.rb���������������������������������������������������0000644�0052762�0001160�00000005553�13417161721�021675� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.type(:file).provide :windows do desc "Uses Microsoft Windows functionality to manage file ownership and permissions." confine :operatingsystem => :windows has_feature :manages_symlinks if Puppet.features.manages_symlinks? include Puppet::Util::Warnings if Puppet.features.microsoft_windows? require 'puppet/util/windows' include Puppet::Util::Windows::Security end # Determine if the account is valid, and if so, return the UID def name2id(value) Puppet::Util::Windows::SID.name_to_sid(value) end # If it's a valid SID, get the name. Otherwise, it's already a name, # so just return it. def id2name(id) if Puppet::Util::Windows::SID.valid_sid?(id) Puppet::Util::Windows::SID.sid_to_name(id) else id end end # We use users and groups interchangeably, so use the same methods for both # (the type expects different methods, so we have to oblige). alias :uid2name :id2name alias :gid2name :id2name alias :name2gid :name2id alias :name2uid :name2id def owner return :absent unless resource.stat get_owner(resource[:path]) end def owner=(should) begin set_owner(should, resolved_path) rescue => detail raise Puppet::Error, _("Failed to set owner to '%{should}': %{detail}") % { should: should, detail: detail }, detail.backtrace end end def group return :absent unless resource.stat get_group(resource[:path]) end def group=(should) begin set_group(should, resolved_path) rescue => detail raise Puppet::Error, _("Failed to set group to '%{should}': %{detail}") % { should: should, detail: detail }, detail.backtrace end end def mode if resource.stat mode = get_mode(resource[:path]) mode ? mode.to_s(8).rjust(4, '0') : :absent else :absent end end def mode=(value) begin set_mode(value.to_i(8), resource[:path]) rescue => detail error = Puppet::Error.new(_("failed to set mode %{mode} on %{path}: %{message}") % { mode: mode, path: resource[:path], message: detail.message }) error.set_backtrace detail.backtrace raise error end :file_changed end def validate if [:owner, :group, :mode].any?{|p| resource[p]} and !supports_acl?(resource[:path]) resource.fail(_("Can only manage owner, group, and mode on filesystems that support Windows ACLs, such as NTFS")) end end attr_reader :file private def file @file ||= Puppet::FileSystem.pathname(resource[:path]) end def resolved_path path = file() # under POSIX, :manage means use lchown - i.e. operate on the link return path.to_s if resource[:links] == :manage # otherwise, use chown -- that will resolve the link IFF it is a link # otherwise it will operate on the path Puppet::FileSystem.symlink?(path) ? Puppet::FileSystem.readlink(path) : path.to_s end end �����������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/group/������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020070� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/group/aix.rb������������������������������������������������������0000644�0052762�0001160�00000006250�13417161721�021174� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Group Puppet provider for AIX. It uses standard commands to manage groups: # mkgroup, rmgroup, lsgroup, chgroup require 'puppet/provider/aix_object' Puppet::Type.type(:group).provide :aix, :parent => Puppet::Provider::AixObject do desc "Group management for AIX." # This will the default provider for this platform defaultfor :operatingsystem => :aix confine :operatingsystem => :aix # Commands that manage the element commands :list => "/usr/sbin/lsgroup" commands :add => "/usr/bin/mkgroup" commands :delete => "/usr/sbin/rmgroup" commands :modify => "/usr/bin/chgroup" # Provider features has_features :manages_aix_lam has_features :manages_members class << self # Used by the AIX user provider. Returns a hash of: # { # :name => <group_name>, # :gid => <gid> # } # # that matches the group, which can either be the group name or # the gid. Takes an optional set of ia_module_args def find(group, ia_module_args = []) groups = list_all(ia_module_args) id_property = mappings[:puppet_property][:id] if group.is_a?(String) # Find by name group_hash = groups.find { |cur_group| cur_group[:name] == group } else # Find by gid group_hash = groups.find do |cur_group| id_property.convert_attribute_value(cur_group[:id]) == group end end unless group_hash raise ArgumentError, _("No AIX group exists with a group name or gid of %{group}!") % { group: group } end # Convert :id => :gid id = group_hash.delete(:id) group_hash[:gid] = id_property.convert_attribute_value(id) group_hash end # Define some Puppet Property => AIX Attribute (and vice versa) # conversion functions here. This is so we can unit test them. def members_to_users(provider, members) members = members.split(',') if members.is_a?(String) unless provider.resource[:auth_membership] current_members = provider.members current_members = [] if current_members == :absent members = (members + current_members).uniq end members.join(',') end def users_to_members(users) users.split(',') end end mapping puppet_property: :members, aix_attribute: :users, property_to_attribute: method(:members_to_users), attribute_to_property: method(:users_to_members) numeric_mapping puppet_property: :gid, aix_attribute: :id # Now that we have all of our mappings, let's go ahead and make # the resource methods (property getters + setters for our mapped # properties + a getter for the attributes property). mk_resource_methods # We could add this to the top-level members property since the # implementation is not platform-specific; however, it is best # to do it this way so that we do not accidentally break something. # This is ok for now, since we do plan on moving this and the # auth_membership management over to the property class in a future # Puppet release. def members_insync?(current, should) current.sort == @resource.parameter(:members).actual_should(current, should) end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/group/directoryservice.rb�����������������������������������������0000644�0052762�0001160�00000001110�13417161721�023766� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/nameservice/directoryservice' Puppet::Type.type(:group).provide :directoryservice, :parent => Puppet::Provider::NameService::DirectoryService do desc "Group management using DirectoryService on OS X. " commands :dscl => "/usr/bin/dscl" confine :operatingsystem => :darwin defaultfor :operatingsystem => :darwin has_feature :manages_members def members_insync?(current, should) return false unless current if current == :absent return should.empty? else return current.sort.uniq == should.sort.uniq end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/group/groupadd.rb�������������������������������������������������0000644�0052762�0001160�00000005777�13417161721�022235� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/nameservice/objectadd' require 'puppet/util/libuser' Puppet::Type.type(:group).provide :groupadd, :parent => Puppet::Provider::NameService::ObjectAdd do desc "Group management via `groupadd` and its ilk. The default for most platforms. " commands :add => "groupadd", :delete => "groupdel", :modify => "groupmod" has_feature :system_groups unless %w{HP-UX Solaris}.include? Facter.value(:operatingsystem) verify :gid, _("GID must be an integer") do |value| value.is_a? Integer end optional_commands :localadd => "lgroupadd", :localdelete => "lgroupdel", :localmodify => "lgroupmod" has_feature :libuser if Puppet.features.libuser? def exists? return !!localgid if @resource.forcelocal? super end def gid return localgid if @resource.forcelocal? get(:gid) end def findgroup(key, value) group_file = "/etc/group" group_keys = ['group_name', 'password', 'gid', 'user_list'] index = group_keys.index(key) File.open(group_file) do |f| f.each_line do |line| group = line.split(":") if group[index] == value f.close return group end end end false end def localgid group = findgroup('group_name', resource[:name]) return group[2] if group false end def check_allow_dup # We have to manually check for duplicates when using libuser # because by default duplicates are allowed. This check is # to ensure consistent behaviour of the useradd provider when # using both useradd and luseradd if not @resource.allowdupe? and @resource.forcelocal? if @resource.should(:gid) and findgroup('gid', @resource.should(:gid).to_s) raise(Puppet::Error, _("GID %{resource} already exists, use allowdupe to force group creation") % { resource: @resource.should(:gid).to_s }) end elsif @resource.allowdupe? and not @resource.forcelocal? return ["-o"] end [] end def addcmd if @resource.forcelocal? cmd = [command(:localadd)] @custom_environment = Puppet::Util::Libuser.getenv else cmd = [command(:add)] end if gid = @resource.should(:gid) unless gid == :absent cmd << flag(:gid) << gid end end cmd += check_allow_dup cmd << "-r" if @resource.system? and self.class.system_groups? cmd << @resource[:name] cmd end def modifycmd(param, value) if @resource.forcelocal? cmd = [command(:localmodify)] @custom_environment = Puppet::Util::Libuser.getenv else cmd = [command(:modify)] end cmd << flag(param) << value # TODO the group type only really manages gid, so there are currently no # tests for this behavior cmd += check_allow_dup if param == :gid cmd << @resource[:name] cmd end def deletecmd if @resource.forcelocal? @custom_environment = Puppet::Util::Libuser.getenv [command(:localdelete), @resource[:name]] else [command(:delete), @resource[:name]] end end end �puppet-5.5.10/lib/puppet/provider/group/ldap.rb�����������������������������������������������������0000644�0052762�0001160�00000003143�13417161721�021331� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/ldap' Puppet::Type.type(:group).provide :ldap, :parent => Puppet::Provider::Ldap do desc "Group management via LDAP. This provider requires that you have valid values for all of the LDAP-related settings in `puppet.conf`, including `ldapbase`. You will almost definitely need settings for `ldapuser` and `ldappassword` in order for your clients to write to LDAP. Note that this provider will automatically generate a GID for you if you do not specify one, but it is a potentially expensive operation, as it iterates across all existing groups to pick the appropriate next one." confine :feature => :ldap, :false => (Puppet[:ldapuser] == "") # We're mapping 'members' here because we want to make it # easy for the ldap user provider to manage groups. This # way it can just use the 'update' method in the group manager, # whereas otherwise it would need to replicate that code. manages(:posixGroup).at("ou=Groups").and.maps :name => :cn, :gid => :gidNumber, :members => :memberUid # Find the next gid after the current largest gid. provider = self manager.generates(:gidNumber).with do largest = 500 if existing = provider.manager.search existing.each do |hash| next unless value = hash[:gid] num = value[0].to_i largest = num if num > largest end end largest + 1 end # Convert a group name to an id. def self.name2id(group) return nil unless result = manager.search("cn=#{group}") and result.length > 0 # Only use the first result. group = result[0] group[:gid][0] end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/group/pw.rb�������������������������������������������������������0000644�0052762�0001160�00000002210�13417161721�021031� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/nameservice/pw' Puppet::Type.type(:group).provide :pw, :parent => Puppet::Provider::NameService::PW do desc "Group management via `pw` on FreeBSD and DragonFly BSD." commands :pw => "pw" has_features :manages_members defaultfor :operatingsystem => [:freebsd, :dragonfly] confine :operatingsystem => [:freebsd, :dragonfly] options :members, :flag => "-M", :method => :mem verify :gid, _("GID must be an integer") do |value| value.is_a? Integer end def addcmd cmd = [command(:pw), "groupadd", @resource[:name]] if gid = @resource.should(:gid) unless gid == :absent cmd << flag(:gid) << gid end end if members = @resource.should(:members) unless members == :absent if members.is_a?(Array) members = members.join(",") end cmd << "-M" << members end end cmd << "-o" if @resource.allowdupe? cmd end def modifycmd(param, value) # members may be an array, need a comma separated list if param == :members and value.is_a?(Array) value = value.join(",") end super(param, value) end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/group/windows_adsi.rb���������������������������������������������0000644�0052762�0001160�00000005602�13417161721�023105� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/windows' Puppet::Type.type(:group).provide :windows_adsi do desc "Local group management for Windows. Group members can be both users and groups. Additionally, local groups can contain domain users." defaultfor :operatingsystem => :windows confine :operatingsystem => :windows has_features :manages_members def initialize(value={}) super(value) @deleted = false end def members_insync?(current, should) return false unless current # By comparing account SIDs we don't have to worry about case # sensitivity, or canonicalization of account names. # Cannot use munge of the group property to canonicalize @should # since the default array_matching comparison is not commutative # dupes automatically weeded out when hashes built current_members = Puppet::Util::Windows::ADSI::Group.name_sid_hash(current) specified_members = Puppet::Util::Windows::ADSI::Group.name_sid_hash(should) current_sids = current_members.keys.to_a specified_sids = specified_members.keys.to_a if @resource[:auth_membership] current_sids.sort == specified_sids.sort else (specified_sids & current_sids) == specified_sids end end def members_to_s(users) return '' if users.nil? or !users.kind_of?(Array) users = users.map do |user_name| sid = Puppet::Util::Windows::SID.name_to_principal(user_name) if !sid resource.debug("#{user_name} (unresolvable to SID)") next user_name end if sid.account =~ /\\/ account, _ = Puppet::Util::Windows::ADSI::User.parse_name(sid.account) else account = sid.account end resource.debug("#{sid.domain}\\#{account} (#{sid.sid})") "#{sid.domain}\\#{account}" end return users.join(',') end def member_valid?(user_name) ! Puppet::Util::Windows::SID.name_to_principal(user_name).nil? end def group @group ||= Puppet::Util::Windows::ADSI::Group.new(@resource[:name]) end def members @members ||= Puppet::Util::Windows::ADSI::Group.name_sid_hash(group.members) @members.keys end def members=(members) group.set_members(members, @resource[:auth_membership]) end def create @group = Puppet::Util::Windows::ADSI::Group.create(@resource[:name]) @group.commit self.members = @resource[:members] end def exists? Puppet::Util::Windows::ADSI::Group.exists?(@resource[:name]) end def delete Puppet::Util::Windows::ADSI::Group.delete(@resource[:name]) @deleted = true end # Only flush if we created or modified a group, not deleted def flush @group.commit if @group && !@deleted end def gid Puppet::Util::Windows::SID.name_to_sid(@resource[:name]) end def gid=(value) fail "gid is read-only" end def self.instances Puppet::Util::Windows::ADSI::Group.map { |g| new(:ensure => :present, :name => g.name) } end end ������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/ldap.rb�����������������������������������������������������������0000644�0052762�0001160�00000007067�13417161721�020206� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider' # The base class for LDAP providers. class Puppet::Provider::Ldap < Puppet::Provider require 'puppet/util/ldap/manager' class << self attr_reader :manager end # Look up all instances at our location. Yay. def self.instances return [] unless list = manager.search list.collect { |entry| new(entry) } end # Specify the ldap manager for this provider, which is # used to figure out how we actually interact with ldap. def self.manages(*args) @manager = Puppet::Util::Ldap::Manager.new @manager.manages(*args) # Set up our getter/setter methods. mk_resource_methods @manager end # Query all of our resources from ldap. def self.prefetch(resources) resources.each do |name, resource| if result = manager.find(name) result[:ensure] = :present resource.provider = new(result) else resource.provider = new(:ensure => :absent) end end end def manager self.class.manager end def create @property_hash[:ensure] = :present self.class.resource_type.validproperties.each do |property| if val = resource.should(property) if property.to_s == 'gid' self.gid = val else @property_hash[property] = val end end end end def delete @property_hash[:ensure] = :absent end def exists? @property_hash[:ensure] != :absent end # Apply our changes to ldap, yo. def flush # Just call the manager's update() method. @property_hash.delete(:groups) @ldap_properties.delete(:groups) manager.update(name, ldap_properties, properties) @property_hash.clear @ldap_properties.clear end def initialize(*args) raise(Puppet::DevError, _("No LDAP Configuration defined for %{class_name}") % { class_name: self.class }) unless self.class.manager raise(Puppet::DevError, _("Invalid LDAP Configuration defined for %{class_name}") % { class_name: self.class }) unless self.class.manager.valid? super @property_hash = @property_hash.inject({}) do |result, ary| param, values = ary # Skip any attributes we don't manage. next result unless self.class.resource_type.valid_parameter?(param) paramclass = self.class.resource_type.attrclass(param) unless values.is_a?(Array) result[param] = values next result end # Only use the first value if the attribute class doesn't manage # arrays of values. if paramclass.superclass == Puppet::Parameter or paramclass.array_matching == :first result[param] = values[0] else result[param] = values end result end # Make a duplicate, so that we have a copy for comparison # at the end. @ldap_properties = @property_hash.dup end # Return the current state of ldap. def ldap_properties @ldap_properties.dup end # Return (and look up if necessary) the desired state. def properties if @property_hash.empty? @property_hash = query || {:ensure => :absent} @property_hash[:ensure] = :absent if @property_hash.empty? end @property_hash.dup end # Collect the current attributes from ldap. Returns # the results, but also stores the attributes locally, # so we have something to compare against when we update. # LAK:NOTE This is normally not used, because we rely on prefetching. def query # Use the module function. unless attributes = manager.find(name) @ldap_properties = {} return nil end @ldap_properties = attributes @ldap_properties.dup end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/nameservice.rb����������������������������������������������������0000644�0052762�0001160�00000022573�13417161721�021566� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet' # This is the parent class of all NSS classes. They're very different in # their backend, but they're pretty similar on the front-end. This class # provides a way for them all to be as similar as possible. class Puppet::Provider::NameService < Puppet::Provider class << self def autogen_default(param) defined?(@autogen_defaults) ? @autogen_defaults[param.intern] : nil end def autogen_defaults(hash) @autogen_defaults ||= {} hash.each do |param, value| @autogen_defaults[param.intern] = value end end def initvars @checks = {} @options = {} super end def instances objects = [] begin method = Puppet::Etc.method(:"get#{section}ent") while ent = method.call objects << new(:name => ent.name, :canonical_name => ent.canonical_name, :ensure => :present) end ensure Puppet::Etc.send("end#{section}ent") end objects end def option(name, option) name = name.intern if name.is_a? String (defined?(@options) and @options.include? name and @options[name].include? option) ? @options[name][option] : nil end def options(name, hash) unless resource_type.valid_parameter?(name) raise Puppet::DevError, _("%{name} is not a valid attribute for %{resource_type}") % { name: name, resource_type: resource_type.name } end @options ||= {} @options[name] ||= {} # Set options individually, so we can call the options method # multiple times. hash.each do |param, value| @options[name][param] = value end end # List everything out by name. Abstracted a bit so that it works # for both users and groups. def listbyname Puppet.deprecation_warning(_("listbyname is deprecated and will be removed in a future release of Puppet. Please use `self.instances` to obtain a list of users.")) names = [] Puppet::Etc.send("set#{section()}ent") begin while ent = Puppet::Etc.send("get#{section()}ent") names << ent.name yield ent.name if block_given? end ensure Puppet::Etc.send("end#{section()}ent") end names end def resource_type=(resource_type) super @resource_type.validproperties.each do |prop| next if prop == :ensure define_method(prop) { get(prop) || :absent} unless public_method_defined?(prop) define_method(prop.to_s + "=") { |*vals| set(prop, *vals) } unless public_method_defined?(prop.to_s + "=") end end # This is annoying, but there really aren't that many options, # and this *is* built into Ruby. def section unless resource_type raise Puppet::DevError, "Cannot determine Etc section without a resource type" end if @resource_type.name == :group "gr" else "pw" end end def validate(name, value) name = name.intern if name.is_a? String if @checks.include? name block = @checks[name][:block] raise ArgumentError, _("Invalid value %{value}: %{error}") % { value: value, error: @checks[name][:error] } unless block.call(value) end end def verify(name, error, &block) name = name.intern if name.is_a? String @checks[name] = {:error => error, :block => block} end private def op(property) @ops[property.name] || ("-#{property.name}") end end # Autogenerate a value. Mostly used for uid/gid, but also used heavily # with DirectoryServices def autogen(field) field = field.intern id_generators = {:user => :uid, :group => :gid} if id_generators[@resource.class.name] == field return self.class.autogen_id(field, @resource.class.name) else if value = self.class.autogen_default(field) return value elsif respond_to?("autogen_#{field}") return send("autogen_#{field}") else return nil end end end # Autogenerate either a uid or a gid. This is not very flexible: we can # only generate one field type per class, and get kind of confused if asked # for both. def self.autogen_id(field, resource_type) # Figure out what sort of value we want to generate. case resource_type when :user; database = :passwd; method = :uid when :group; database = :group; method = :gid else #TRANSLATORS "autogen_id()" is a method name and should not be translated raise Puppet::DevError, _("autogen_id() does not support auto generation of id for resource type %{resource_type}") % { resource_type: resource_type } end # Initialize from the data set, if needed. unless @prevauto # Sadly, Etc doesn't return an enumerator, it just invokes the block # given, or returns the first record from the database. There is no # other, more convenient enumerator for these, so we fake one with this # loop. Thanks, Ruby, for your awesome abstractions. --daniel 2012-03-23 highest = [] Puppet::Etc.send(database) {|entry| highest << entry.send(method) } highest = highest.reject {|x| x > 65000 }.max @prevauto = highest || 1000 end # ...and finally increment and return the next value. @prevauto += 1 end def create if exists? info _("already exists") # The object already exists return nil end begin execute(self.addcmd, {:failonfail => true, :combine => true, :custom_environment => @custom_environment}) if feature?(:manages_password_age) && (cmd = passcmd) execute(cmd, {:failonfail => true, :combine => true, :custom_environment => @custom_environment}) end rescue Puppet::ExecutionFailure => detail raise Puppet::Error, _("Could not create %{resource} %{name}: %{detail}") % { resource: @resource.class.name, name: @resource.name, detail: detail }, detail.backtrace end end def delete unless exists? info _("already absent") # the object already doesn't exist return nil end begin execute(self.deletecmd, {:failonfail => true, :combine => true, :custom_environment => @custom_environment}) rescue Puppet::ExecutionFailure => detail raise Puppet::Error, _("Could not delete %{resource} %{name}: %{detail}") % { resource: @resource.class.name, name: @resource.name, detail: detail }, detail.backtrace end end def ensure if exists? :present else :absent end end # Does our object exist? def exists? !!getinfo(true) end # Retrieve a specific value by name. def get(param) (hash = getinfo(false)) ? unmunge(param, hash[param]) : nil end def munge(name, value) if block = self.class.option(name, :munge) and block.is_a? Proc block.call(value) else value end end def unmunge(name, value) if block = self.class.option(name, :unmunge) and block.is_a? Proc block.call(value) else value end end # Retrieve what we can about our object def getinfo(refresh) if @objectinfo.nil? or refresh == true @etcmethod ||= ("get" + self.class.section.to_s + "nam").intern begin @objectinfo = Puppet::Etc.send(@etcmethod, @canonical_name) rescue ArgumentError @objectinfo = nil end end # Now convert our Etc struct into a hash. @objectinfo ? info2hash(@objectinfo) : nil end # The list of all groups the user is a member of. Different # user mgmt systems will need to override this method. def groups Puppet::Util::POSIX.groups_of(@resource[:name]).join(',') end # Convert the Etc struct into a hash. def info2hash(info) hash = {} self.class.resource_type.validproperties.each do |param| method = posixmethod(param) hash[param] = info.send(posixmethod(param)) if info.respond_to? method end hash end def initialize(resource) super @custom_environment = {} @objectinfo = nil if resource.is_a?(Hash) && !resource[:canonical_name].nil? @canonical_name = resource[:canonical_name] else @canonical_name = resource[:name] end end def set(param, value) self.class.validate(param, value) cmd = modifycmd(param, munge(param, value)) raise Puppet::DevError, _("Nameservice command must be an array") unless cmd.is_a?(Array) begin execute(cmd, {:failonfail => true, :combine => true, :custom_environment => @custom_environment}) rescue Puppet::ExecutionFailure => detail raise Puppet::Error, _("Could not set %{param} on %{resource}[%{name}]: %{detail}") % { param: param, resource: @resource.class.name, name: @resource.name, detail: detail }, detail.backtrace end end # From overriding Puppet::Property#insync? Ruby Etc::getpwnam < 2.1.0 always # returns a struct with binary encoded string values, and >= 2.1.0 will return # binary encoded strings for values incompatible with current locale charset, # or Encoding.default_external if compatible. Compare a "should" value with # encoding of "current" value, to avoid unnecessary property syncs and # comparison of strings with different encodings. (PUP-6777) # # return basic string comparison after re-encoding (same as # Puppet::Property#property_matches) def comments_insync?(current, should) # we're only doing comparison here so don't mutate the string desired = should[0].to_s.dup current == desired.force_encoding(current.encoding) end end �������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/nameservice/������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�021235� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/nameservice/directoryservice.rb�����������������������������������0000644�0052762�0001160�00000052267�13417161721�025156� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet' require 'puppet/provider/nameservice' require 'puppet/util/plist' if Puppet.features.cfpropertylist? require 'fileutils' class Puppet::Provider::NameService::DirectoryService < Puppet::Provider::NameService # JJM: Dive into the singleton_class class << self # JJM: This allows us to pass information when calling # Puppet::Type.type # e.g. Puppet::Type.type(:user).provide :directoryservice, :ds_path => "Users" # This is referenced in the get_ds_path class method attr_writer :ds_path end initvars commands :dscl => "/usr/bin/dscl" commands :dseditgroup => "/usr/sbin/dseditgroup" commands :sw_vers => "/usr/bin/sw_vers" confine :operatingsystem => :darwin confine :feature => :cfpropertylist defaultfor :operatingsystem => :darwin # There is no generalized mechanism for provider cache management, but we can # use post_resource_eval, which will be run for each suitable provider at the # end of each transaction. Use this to clear @all_present after each run. def self.post_resource_eval @all_present = nil end # JJM 2007-07-25: This map is used to map NameService attributes to their # corresponding DirectoryService attribute names. # See: http://images.apple.com/server/docs.Open_Directory_v10.4.pdf # JJM: Note, this is de-coupled from the Puppet::Type, and must # be actively maintained. There may also be collisions with different # types (Users, Groups, Mounts, Hosts, etc...) def ds_to_ns_attribute_map; self.class.ds_to_ns_attribute_map; end def self.ds_to_ns_attribute_map { 'RecordName' => :name, 'PrimaryGroupID' => :gid, 'NFSHomeDirectory' => :home, 'UserShell' => :shell, 'UniqueID' => :uid, 'RealName' => :comment, 'Password' => :password, 'GeneratedUID' => :guid, 'IPAddress' => :ip_address, 'ENetAddress' => :en_address, 'GroupMembership' => :members, } end # JJM The same table as above, inverted. def ns_to_ds_attribute_map; self.class.ns_to_ds_attribute_map end def self.ns_to_ds_attribute_map @ns_to_ds_attribute_map ||= ds_to_ns_attribute_map.invert end def self.password_hash_dir '/var/db/shadow/hash' end def self.users_plist_dir '/var/db/dslocal/nodes/Default/users' end def self.instances # JJM Class method that provides an array of instance objects of this # type. # JJM: Properties are dependent on the Puppet::Type we're managing. type_property_array = [:name] + @resource_type.validproperties # Create a new instance of this Puppet::Type for each object present # on the system. list_all_present.collect do |name_string| self.new(single_report(name_string, *type_property_array)) end end def self.get_ds_path # JJM: 2007-07-24 This method dynamically returns the DS path we're concerned with. # For example, if we're working with an user type, this will be /Users # with a group type, this will be /Groups. # @ds_path is an attribute of the class itself. return @ds_path if defined?(@ds_path) # JJM: "Users" or "Groups" etc ... (Based on the Puppet::Type) # Remember this is a class method, so self.class is Class # Also, @resource_type seems to be the reference to the # Puppet::Type this class object is providing for. @resource_type.name.to_s.capitalize + "s" end def self.list_all_present @all_present ||= begin # JJM: List all objects of this Puppet::Type already present on the system. begin dscl_output = execute(get_exec_preamble("-list")) rescue Puppet::ExecutionFailure fail(_("Could not get %{resource} list from DirectoryService") % { resource: @resource_type.name }) end dscl_output.split("\n") end end def self.parse_dscl_plist_data(dscl_output) Puppet::Util::Plist.parse_plist(dscl_output) end def self.generate_attribute_hash(input_hash, *type_properties) attribute_hash = {} input_hash.each_key do |key| ds_attribute = key.sub("dsAttrTypeStandard:", "") next unless (ds_to_ns_attribute_map.keys.include?(ds_attribute) and type_properties.include? ds_to_ns_attribute_map[ds_attribute]) ds_value = input_hash[key] case ds_to_ns_attribute_map[ds_attribute] when :members ds_value = ds_value # only members uses arrays so far when :gid, :uid # OS X stores objects like uid/gid as strings. # Try casting to an integer for these cases to be # consistent with the other providers and the group type # validation begin ds_value = Integer(ds_value[0]) rescue ArgumentError ds_value = ds_value[0] end else ds_value = ds_value[0] end attribute_hash[ds_to_ns_attribute_map[ds_attribute]] = ds_value end # NBK: need to read the existing password here as it's not actually # stored in the user record. It is stored at a path that involves the # UUID of the user record for non-Mobile local accounts. # Mobile Accounts are out of scope for this provider for now attribute_hash[:password] = self.get_password(attribute_hash[:guid], attribute_hash[:name]) if @resource_type.validproperties.include?(:password) and Puppet.features.root? attribute_hash end def self.single_report(resource_name, *type_properties) # JJM 2007-07-24: # Given a the name of an object and a list of properties of that # object, return all property values in a hash. # # This class method returns nil if the object doesn't exist # Otherwise, it returns a hash of the object properties. all_present_str_array = list_all_present # NBK: shortcut the process if the resource is missing return nil unless all_present_str_array.include? resource_name dscl_vector = get_exec_preamble("-read", resource_name) begin dscl_output = execute(dscl_vector) rescue Puppet::ExecutionFailure fail(_("Could not get report. command execution failed.")) end dscl_plist = self.parse_dscl_plist_data(dscl_output) self.generate_attribute_hash(dscl_plist, *type_properties) end def self.get_exec_preamble(ds_action, resource_name = nil) # JJM 2007-07-24 # DSCL commands are often repetitive and contain the same positional # arguments over and over. See https://developer.apple.com/documentation/Porting/Conceptual/PortingUnix/additionalfeatures/chapter_10_section_9.html # for an example of what I mean. # This method spits out proper DSCL commands for us. # We EXPECT name to be @resource[:name] when called from an instance object. command_vector = [ command(:dscl), "-plist", "." ] # JJM: The actual action to perform. See "man dscl". # Common actions: -create, -delete, -merge, -append, -passwd command_vector << ds_action # JJM: get_ds_path will spit back "Users" or "Groups", # etc... Depending on the Puppet::Type of our self. if resource_name command_vector << "/#{get_ds_path}/#{resource_name}" else command_vector << "/#{get_ds_path}" end # JJM: This returns most of the preamble of the command. # e.g. 'dscl / -create /Users/mccune' command_vector end def self.set_password(resource_name, guid, password_hash) # 10.7 uses salted SHA512 password hashes which are 128 characters plus # an 8 character salt. Previous versions used a SHA1 hash padded with # zeroes. If someone attempts to use a password hash that worked with # a previous version of OS X, we will fail early and warn them. if password_hash.length != 136 #TRANSLATORS 'OS X 10.7' is an operating system and should not be translated, 'Salted SHA512' is the name of a hashing algorithm fail(_("OS X 10.7 requires a Salted SHA512 hash password of 136 characters.") + ' ' + _("Please check your password and try again.")) end plist_file = "#{users_plist_dir}/#{resource_name}.plist" if Puppet::FileSystem.exist?(plist_file) # If a plist already exists in /var/db/dslocal/nodes/Default/users, then # we will need to extract the binary plist from the 'ShadowHashData' # key, log the new password into the resultant plist's 'SALTED-SHA512' # key, and then save the entire structure back. users_plist = Puppet::Util::Plist.read_plist_file(plist_file) # users_plist['ShadowHashData'][0] is actually a binary plist # that's nested INSIDE the user's plist (which itself is a binary # plist). If we encounter a user plist that DOESN'T have a # ShadowHashData field, create one. if users_plist['ShadowHashData'] password_hash_plist = users_plist['ShadowHashData'][0] converted_hash_plist = convert_binary_to_hash(password_hash_plist) else users_plist['ShadowHashData'] = '' converted_hash_plist = {'SALTED-SHA512' => ''} end # converted_hash_plist['SALTED-SHA512'] expects a Base64 encoded # string. The password_hash provided as a resource attribute is a # hex value. We need to convert the provided hex value to a Base64 # encoded string to nest it in the converted hash plist. converted_hash_plist['SALTED-SHA512'] = \ password_hash.unpack('a2'*(password_hash.size/2)).collect { |i| i.hex.chr }.join # Finally, we can convert the nested plist back to binary, embed it # into the user's plist, and convert the resultant plist back to # a binary plist. changed_plist = convert_hash_to_binary(converted_hash_plist) users_plist['ShadowHashData'][0] = changed_plist Puppet::Util::Plist.write_plist_file(users_plist, plist_file, :binary) end end def self.get_password(guid, username) plist_file = "#{users_plist_dir}/#{username}.plist" if Puppet::FileSystem.exist?(plist_file) # If a plist exists in /var/db/dslocal/nodes/Default/users, we will # extract the binary plist from the 'ShadowHashData' key, decode the # salted-SHA512 password hash, and then return it. users_plist = Puppet::Util::Plist.read_plist_file(plist_file) if users_plist['ShadowHashData'] # users_plist['ShadowHashData'][0] is actually a binary plist # that's nested INSIDE the user's plist (which itself is a binary # plist). password_hash_plist = users_plist['ShadowHashData'][0] converted_hash_plist = convert_binary_to_hash(password_hash_plist) # converted_hash_plist['SALTED-SHA512'] is a Base64 encoded # string. The password_hash provided as a resource attribute is a # hex value. We need to convert the Base64 encoded string to a # hex value and provide it back to Puppet. password_hash = converted_hash_plist['SALTED-SHA512'].unpack("H*")[0] password_hash end end end # This method will accept a hash and convert it to a binary plist (string value). def self.convert_hash_to_binary(plist_data) Puppet.debug('Converting plist hash to binary') Puppet::Util::Plist.dump_plist(plist_data, :binary) end # This method will accept a binary plist (as a string) and convert it to a hash. def self.convert_binary_to_hash(plist_data) Puppet.debug('Converting binary plist to hash') Puppet::Util::Plist.parse_plist(plist_data) end # Unlike most other *nixes, OS X doesn't provide built in functionality # for automatically assigning uids and gids to accounts, so we set up these # methods for consumption by functionality like --mkusers # By default we restrict to a reasonably sane range for system accounts def self.next_system_id(id_type, min_id=20) dscl_args = ['.', '-list'] if id_type == 'uid' dscl_args << '/Users' << 'uid' elsif id_type == 'gid' dscl_args << '/Groups' << 'gid' else fail(_("Invalid id_type %{id_type}. Only 'uid' and 'gid' supported") % { id_type: id_type }) end dscl_out = dscl(dscl_args) # We're ok with throwing away negative uids here. ids = dscl_out.split.compact.collect { |l| l.to_i if l.match(/^\d+$/) } ids.compact!.sort! { |a,b| a.to_f <=> b.to_f } # We're just looking for an unused id in our sorted array. ids.each_index do |i| next_id = ids[i] + 1 return next_id if ids[i+1] != next_id and next_id >= min_id end end def ensure=(ensure_value) super # We need to loop over all valid properties for the type we're # managing and call the method which sets that property value # dscl can't create everything at once unfortunately. if ensure_value == :present @resource.class.validproperties.each do |name| next if name == :ensure # LAK: We use property.sync here rather than directly calling # the settor method because the properties might do some kind # of conversion. In particular, the user gid property might # have a string and need to convert it to a number if @resource.should(name) @resource.property(name).sync elsif value = autogen(name) self.send(name.to_s + "=", value) else next end end end end def password=(passphrase) exec_arg_vector = self.class.get_exec_preamble("-read", @resource.name) exec_arg_vector << ns_to_ds_attribute_map[:guid] begin guid_output = execute(exec_arg_vector) guid_plist = Puppet::Util::Plist.parse_plist(guid_output) # Although GeneratedUID like all DirectoryService values can be multi-valued # according to the schema, in practice user accounts cannot have multiple UUIDs # otherwise Bad Things Happen, so we just deal with the first value. guid = guid_plist["dsAttrTypeStandard:#{ns_to_ds_attribute_map[:guid]}"][0] self.class.set_password(@resource.name, guid, passphrase) rescue Puppet::ExecutionFailure => detail fail(_("Could not set %{param} on %{resource}[%{name}]: %{detail}") % { param: param, resource: @resource.class.name, name: @resource.name, detail: detail }) end end # NBK: we override @parent.set as we need to execute a series of commands # to deal with array values, rather than the single command nameservice.rb # expects to be returned by modifycmd. Thus we don't bother defining modifycmd. def set(param, value) self.class.validate(param, value) current_members = @property_value_cache_hash[:members] if param == :members # If we are meant to be authoritative for the group membership # then remove all existing members who haven't been specified # in the manifest. remove_unwanted_members(current_members, value) if @resource[:auth_membership] and not current_members.nil? # if they're not a member, make them one. add_members(current_members, value) else exec_arg_vector = self.class.get_exec_preamble("-create", @resource[:name]) # JJM: The following line just maps the NS name to the DS name # e.g. { :uid => 'UniqueID' } exec_arg_vector << ns_to_ds_attribute_map[param.intern] # JJM: The following line sends the actual value to set the property to exec_arg_vector << value.to_s begin execute(exec_arg_vector) rescue Puppet::ExecutionFailure => detail fail(_("Could not set %{param} on %{resource}[%{name}]: %{detail}") % { param: param, resource: @resource.class.name, name: @resource.name, detail: detail }) end end end # NBK: we override @parent.create as we need to execute a series of commands # to create objects with dscl, rather than the single command nameservice.rb # expects to be returned by addcmd. Thus we don't bother defining addcmd. def create if exists? info _("already exists") return nil end # NBK: First we create the object with a known guid so we can set the contents # of the password hash if required # Shelling out sucks, but for a single use case it doesn't seem worth # requiring people install a UUID library that doesn't come with the system. # This should be revisited if Puppet starts managing UUIDs for other platform # user records. guid = %x{/usr/bin/uuidgen}.chomp exec_arg_vector = self.class.get_exec_preamble("-create", @resource[:name]) exec_arg_vector << ns_to_ds_attribute_map[:guid] << guid begin execute(exec_arg_vector) rescue Puppet::ExecutionFailure => detail fail(_("Could not set GeneratedUID for %{resource} %{name}: %{detail}") % { resource: @resource.class.name, name: @resource.name, detail: detail }) end if value = @resource.should(:password) and value != "" self.class.set_password(@resource[:name], guid, value) end # Now we create all the standard properties Puppet::Type.type(@resource.class.name).validproperties.each do |property| next if property == :ensure value = @resource.should(property) if property == :gid and value.nil? value = self.class.next_system_id('gid') end if property == :uid and value.nil? value = self.class.next_system_id('uid') end if value != "" and not value.nil? if property == :members add_members(nil, value) else exec_arg_vector = self.class.get_exec_preamble("-create", @resource[:name]) exec_arg_vector << ns_to_ds_attribute_map[property.intern] next if property == :password # skip setting the password here exec_arg_vector << value.to_s begin execute(exec_arg_vector) rescue Puppet::ExecutionFailure => detail fail(_("Could not create %{resource} %{name}: %{detail}") % { resource: @resource.class.name, name: @resource.name, detail: detail }) end end end end end def remove_unwanted_members(current_members, new_members) current_members.each do |member| if not new_members.flatten.include?(member) cmd = [:dseditgroup, "-o", "edit", "-n", ".", "-d", member, @resource[:name]] begin execute(cmd) rescue Puppet::ExecutionFailure # TODO: We're falling back to removing the member using dscl due to rdar://8481241 # This bug causes dseditgroup to fail to remove a member if that member doesn't exist cmd = [:dscl, ".", "-delete", "/Groups/#{@resource.name}", "GroupMembership", member] begin execute(cmd) rescue Puppet::ExecutionFailure => detail fail(_("Could not remove %{member} from group: %{resource}, %{detail}") % { member: member, resource: @resource.name, detail: detail }) end end end end end def add_members(current_members, new_members) new_members.flatten.each do |new_member| if current_members.nil? or not current_members.include?(new_member) cmd = [:dseditgroup, "-o", "edit", "-n", ".", "-a", new_member, @resource[:name]] begin execute(cmd) rescue Puppet::ExecutionFailure => detail fail(_("Could not add %{new_member} to group: %{name}, %{detail}") % { new_member: new_member, name: @resource.name, detail: detail }) end end end end def deletecmd # JJM: Like addcmd, only called when deleting the object itself # Note, this isn't used to delete properties of the object, # at least that's how I understand it... self.class.get_exec_preamble("-delete", @resource[:name]) end def getinfo(refresh = false) # JJM 2007-07-24: # Override the getinfo method, which is also defined in nameservice.rb # This method returns and sets @infohash # I'm not re-factoring the name "getinfo" because this method will be # most likely called by nameservice.rb, which I didn't write. if refresh or (! defined?(@property_value_cache_hash) or ! @property_value_cache_hash) # JJM 2007-07-24: OK, there's a bit of magic that's about to # happen... Let's see how strong my grip has become... =) # # self is a provider instance of some Puppet::Type, like # Puppet::Type::User::ProviderDirectoryservice for the case of the # user type and this provider. # # self.class looks like "user provider directoryservice", if that # helps you ... # # self.class.resource_type is a reference to the Puppet::Type class, # probably Puppet::Type::User or Puppet::Type::Group, etc... # # self.class.resource_type.validproperties is a class method, # returning an Array of the valid properties of that specific # Puppet::Type. # # So... something like [:comment, :home, :password, :shell, :uid, # :groups, :ensure, :gid] # # Ultimately, we add :name to the list, delete :ensure from the # list, then report on the remaining list. Pretty whacky, ehh? type_properties = [:name] + self.class.resource_type.validproperties type_properties.delete(:ensure) if type_properties.include? :ensure type_properties << :guid # append GeneratedUID so we just get the report here @property_value_cache_hash = self.class.single_report(@resource[:name], *type_properties) [:uid, :gid].each do |param| @property_value_cache_hash[param] = @property_value_cache_hash[param].to_i if @property_value_cache_hash and @property_value_cache_hash.include?(param) end end @property_value_cache_hash end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/nameservice/objectadd.rb������������������������������������������0000644�0052762�0001160�00000001036�13417161721�023474� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/nameservice' class Puppet::Provider::NameService class ObjectAdd < Puppet::Provider::NameService def deletecmd [command(:delete), @resource[:name]] end # Determine the flag to pass to our command. def flag(name) name = name.intern if name.is_a? String self.class.option(name, :flag) || "-" + name.to_s[0, 1] end def posixmethod(name) name = name.intern if name.is_a? String method = self.class.option(name, :method) || name method end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/nameservice/pw.rb�������������������������������������������������0000644�0052762�0001160�00000000672�13417161721�022210� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/nameservice/objectadd' class Puppet::Provider::NameService class PW < ObjectAdd def deletecmd [command(:pw), "#{@resource.class.name.to_s}del", @resource[:name]] end def modifycmd(param, value) cmd = [ command(:pw), "#{@resource.class.name.to_s}mod", @resource[:name], flag(param), munge(param, value) ] cmd end end end ����������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/network_device.rb�������������������������������������������������0000644�0052762�0001160�00000003242�13417161721�022265� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� # This is the base class of all prefetched network device provider class Puppet::Provider::NetworkDevice < Puppet::Provider def self.device(url) raise "This provider doesn't implement the necessary device method" end def self.lookup(device, name) raise "This provider doesn't implement the necessary lookup method" end def self.prefetch(resources) resources.each do |name, resource| device = Puppet::Util::NetworkDevice.current || device(resource[:device_url]) if result = lookup(device, name) result[:ensure] = :present resource.provider = new(device, result) else resource.provider = new(device, :ensure => :absent) end end rescue => detail # Preserving behavior introduced in #6907 #TRANSLATORS "prefetch" is a program name and should not be translated Puppet.log_exception(detail, _("Could not perform network device prefetch: %{detail}") % { detail: detail }) end def exists? @property_hash[:ensure] != :absent end attr_accessor :device def initialize(device, *args) super(*args) @device = device # Make a duplicate, so that we have a copy for comparison # at the end. @properties = @property_hash.dup end def create @property_hash[:ensure] = :present self.class.resource_type.validproperties.each do |property| if val = resource.should(property) @property_hash[property] = val end end end def destroy @property_hash[:ensure] = :absent end def flush @property_hash.clear end def self.instances end def former_properties @properties.dup end def properties @property_hash.dup end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package.rb��������������������������������������������������������0000644�0052762�0001160�00000003231�13417161721�020646� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Provider::Package < Puppet::Provider # Prefetch our package list, yo. def self.prefetch(packages) instances.each do |prov| if pkg = packages[prov.name] pkg.provider = prov end end end # Clear out the cached values. def flush @property_hash.clear end # Look up the current status. def properties if @property_hash.empty? # For providers that support purging, default to purged; otherwise default to absent # Purged is the "most uninstalled" a package can be, so a purged package will be in-sync with # either `ensure => absent` or `ensure => purged`; an absent package will be out of sync with `ensure => purged`. default_status = self.class.feature?(:purgeable) ? :purged : :absent @property_hash = query || { :ensure => ( default_status )} @property_hash[:ensure] = default_status if @property_hash.empty? end @property_hash.dup end def validate_source(value) true end # Turns a array of options into flags to be passed to a command. # The options can be passed as a string or hash. Note that passing a hash # should only be used in case --foo=bar must be passed, # which can be accomplished with: # install_options => [ { '--foo' => 'bar' } ] # Regular flags like '--foo' must be passed as a string. # @param options [Array] # @return Concatenated list of options # @api private def join_options(options) return unless options options.collect do |val| case val when Hash val.keys.sort.collect do |k| "#{k}=#{val[k]}" end else val end end.flatten end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/����������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020327� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/aix.rb����������������������������������������������������0000644�0052762�0001160�00000011021�13417161721�021423� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/package' require 'puppet/util/package' Puppet::Type.type(:package).provide :aix, :parent => Puppet::Provider::Package do desc "Installation from an AIX software directory, using the AIX `installp` command. The `source` parameter is required for this provider, and should be set to the absolute path (on the puppet agent machine) of a directory containing one or more BFF package files. The `installp` command will generate a table of contents file (named `.toc`) in this directory, and the `name` parameter (or resource title) that you specify for your `package` resource must match a package name that exists in the `.toc` file. Note that package downgrades are *not* supported; if your resource specifies a specific version number and there is already a newer version of the package installed on the machine, the resource will fail with an error message." # The commands we are using on an AIX box are installed standard # (except nimclient) nimclient needs the bos.sysmgt.nim.client fileset. commands :lslpp => "/usr/bin/lslpp", :installp => "/usr/sbin/installp" # AIX supports versionable packages with and without a NIM server has_feature :versionable confine :operatingsystem => [ :aix ] defaultfor :operatingsystem => :aix attr_accessor :latest_info def self.srclistcmd(source) [ command(:installp), "-L", "-d", source ] end def self.prefetch(packages) raise Puppet::Error, _("The aix provider can only be used by root") if Process.euid != 0 return unless packages.detect { |name, package| package.should(:ensure) == :latest } sources = packages.collect { |name, package| package[:source] }.uniq.compact updates = {} sources.each do |source| execute(self.srclistcmd(source)).each_line do |line| if line =~ /^[^#][^:]*:([^:]*):([^:]*)/ current = {} current[:name] = $1 current[:version] = $2 current[:source] = source if updates.key?(current[:name]) previous = updates[current[:name]] updates[current[:name]] = current unless Puppet::Util::Package.versioncmp(previous[:version], current[:version]) == 1 else updates[current[:name]] = current end end end end packages.each do |name, package| if updates.key?(name) package.provider.latest_info = updates[name] end end end def uninstall # Automatically process dependencies when installing/uninstalling # with the -g option to installp. installp "-gu", @resource[:name] # installp will return an exit code of zero even if it didn't uninstall # anything... so let's make sure it worked. unless query().nil? self.fail _("Failed to uninstall package '%{name}'") % { name: @resource[:name] } end end def install(useversion = true) unless source = @resource[:source] self.fail _("A directory is required which will be used to find packages") end pkg = @resource[:name] pkg += " #{@resource.should(:ensure)}" if (! @resource.should(:ensure).is_a? Symbol) and useversion output = installp "-acgwXY", "-d", source, pkg # If the package is superseded, it means we're trying to downgrade and we # can't do that. if output =~ /^#{Regexp.escape(@resource[:name])}\s+.*\s+Already superseded by.*$/ self.fail _("aix package provider is unable to downgrade packages") end end def self.pkglist(hash = {}) cmd = [command(:lslpp), "-qLc"] if name = hash[:pkgname] cmd << name end begin list = execute(cmd).scan(/^[^#][^:]*:([^:]*):([^:]*)/).collect { |n,e| { :name => n, :ensure => e, :provider => self.name } } rescue Puppet::ExecutionFailure => detail if hash[:pkgname] return nil else raise Puppet::Error, _("Could not list installed Packages: %{detail}") % { detail: detail }, detail.backtrace end end if hash[:pkgname] return list.shift else return list end end def self.instances pkglist.collect do |hash| new(hash) end end def latest upd = latest_info unless upd.nil? return "#{upd[:version]}" else raise Puppet::DevError, _("Tried to get latest on a missing package") if properties[:ensure] == :absent return properties[:ensure] end end def query self.class.pkglist(:pkgname => @resource[:name]) end def update self.install(false) end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/appdmg.rb�������������������������������������������������0000644�0052762�0001160�00000007433�13417161721�022126� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Jeff McCune <mccune.jeff@gmail.com> # Changed to app.dmg by: Udo Waechter <root@zoide.net> # Mac OS X Package Installer which handles application (.app) # bundles inside an Apple Disk Image. # # Motivation: DMG files provide a true HFS file system # and are easier to manage. # # Note: the 'apple' Provider checks for the package name # in /L/Receipts. Since we possibly install multiple apps from # a single source, we treat the source .app.dmg file as the package name. # As a result, we store installed .app.dmg file names # in /var/db/.puppet_appdmg_installed_<name> require 'puppet/provider/package' require 'puppet/util/plist' if Puppet.features.cfpropertylist? Puppet::Type.type(:package).provide(:appdmg, :parent => Puppet::Provider::Package) do desc "Package management which copies application bundles to a target." confine :operatingsystem => :darwin confine :feature => :cfpropertylist commands :hdiutil => "/usr/bin/hdiutil" commands :curl => "/usr/bin/curl" commands :ditto => "/usr/bin/ditto" # JJM We store a cookie for each installed .app.dmg in /var/db def self.instances_by_name Dir.entries("/var/db").find_all { |f| f =~ /^\.puppet_appdmg_installed_/ }.collect do |f| name = f.sub(/^\.puppet_appdmg_installed_/, '') yield name if block_given? name end end def self.instances instances_by_name.collect do |name| new(:name => name, :provider => :appdmg, :ensure => :installed) end end def self.installapp(source, name, orig_source) appname = File.basename(source); ditto "--rsrc", source, "/Applications/#{appname}" Puppet::FileSystem.open("/var/db/.puppet_appdmg_installed_#{name}", nil, "w:UTF-8") do |t| t.print "name: '#{name}'\n" t.print "source: '#{orig_source}'\n" end end def self.installpkgdmg(source, name) require 'open-uri' cached_source = source tmpdir = Dir.mktmpdir begin if %r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} =~ cached_source cached_source = File.join(tmpdir, name) begin curl "-o", cached_source, "-C", "-", "-L", "-s", "--url", source Puppet.debug "Success: curl transferred [#{name}]" rescue Puppet::ExecutionFailure Puppet.debug "curl did not transfer [#{name}]. Falling back to slower open-uri transfer methods." cached_source = source end end open(cached_source) do |dmg| xml_str = hdiutil "mount", "-plist", "-nobrowse", "-readonly", "-mountrandom", "/tmp", dmg.path ptable = Puppet::Util::Plist::parse_plist(xml_str) # JJM Filter out all mount-paths into a single array, discard the rest. mounts = ptable['system-entities'].collect { |entity| entity['mount-point'] }.select { |mountloc|; mountloc } begin found_app = false mounts.each do |fspath| Dir.entries(fspath).select { |f| f =~ /\.app$/i }.each do |pkg| found_app = true installapp("#{fspath}/#{pkg}", name, source) end end Puppet.debug "Unable to find .app in .appdmg. #{name} will not be installed." if !found_app ensure hdiutil "eject", mounts[0] end end ensure FileUtils.remove_entry_secure(tmpdir, true) end end def query Puppet::FileSystem.exist?("/var/db/.puppet_appdmg_installed_#{@resource[:name]}") ? {:name => @resource[:name], :ensure => :present} : nil end def install unless source = @resource[:source] self.fail _("Mac OS X PKG DMGs must specify a package source.") end unless name = @resource[:name] self.fail _("Mac OS X PKG DMGs must specify a package name.") end self.class.installpkgdmg(source,name) end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/apple.rb��������������������������������������������������0000644�0052762�0001160�00000002507�13417161721�021754� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/package' # OS X Packaging sucks. We can install packages, but that's about it. Puppet::Type.type(:package).provide :apple, :parent => Puppet::Provider::Package do desc "Package management based on OS X's built-in packaging system. This is essentially the simplest and least functional package system in existence -- it only supports installation; no deletion or upgrades. The provider will automatically add the `.pkg` extension, so leave that off when specifying the package name." confine :operatingsystem => :darwin commands :installer => "/usr/sbin/installer" def self.instances instance_by_name.collect do |name| self.new( :name => name, :provider => :apple, :ensure => :installed ) end end def self.instance_by_name Dir.entries("/Library/Receipts").find_all { |f| f =~ /\.pkg$/ }.collect { |f| name = f.sub(/\.pkg/, '') yield name if block_given? name } end def query Puppet::FileSystem.exist?("/Library/Receipts/#{@resource[:name]}.pkg") ? {:name => @resource[:name], :ensure => :present} : nil end def install unless source = @resource[:source] self.fail _("Mac OS X packages must specify a package source") end installer "-pkg", source, "-target", "/" end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/apt.rb����������������������������������������������������0000644�0052762�0001160�00000006477�13417161721�021451� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.type(:package).provide :apt, :parent => :dpkg, :source => :dpkg do # Provide sorting functionality include Puppet::Util::Package desc "Package management via `apt-get`. This provider supports the `install_options` attribute, which allows command-line flags to be passed to apt-get. These options should be specified as a string (e.g. '--flag'), a hash (e.g. {'--flag' => 'value'}), or an array where each element is either a string or a hash." has_feature :versionable, :install_options commands :aptget => "/usr/bin/apt-get" commands :aptcache => "/usr/bin/apt-cache" commands :preseed => "/usr/bin/debconf-set-selections" defaultfor :osfamily => :debian ENV['DEBIAN_FRONTEND'] = "noninteractive" # disable common apt helpers to allow non-interactive package installs ENV['APT_LISTBUGS_FRONTEND'] = "none" ENV['APT_LISTCHANGES_FRONTEND'] = "none" # A derivative of DPKG; this is how most people actually manage # Debian boxes, and the only thing that differs is that it can # install packages from remote sites. def checkforcdrom have_cdrom = begin !!(File.read("/etc/apt/sources.list") =~ /^[^#]*cdrom:/) rescue # This is basically pathological... false end if have_cdrom and @resource[:allowcdrom] != :true raise Puppet::Error, _("/etc/apt/sources.list contains a cdrom source; not installing. Use 'allowcdrom' to override this failure.") end end # Install a package using 'apt-get'. This function needs to support # installing a specific version. def install self.run_preseed if @resource[:responsefile] should = @resource[:ensure] checkforcdrom cmd = %w{-q -y} if config = @resource[:configfiles] if config == :keep cmd << "-o" << 'DPkg::Options::=--force-confold' else cmd << "-o" << 'DPkg::Options::=--force-confnew' end end str = @resource[:name] case should when true, false, Symbol # pass else # Add the package version and --force-yes option str += "=#{should}" cmd << "--force-yes" end cmd += install_options if @resource[:install_options] cmd << :install << str aptget(*cmd) end # What's the latest package version available? def latest output = aptcache :policy, @resource[:name] if output =~ /Candidate:\s+(\S+)\s/ return $1 else self.err _("Could not find latest version") return nil end end # # preseeds answers to dpkg-set-selection from the "responsefile" # def run_preseed if response = @resource[:responsefile] and Puppet::FileSystem.exist?(response) self.info(_("Preseeding %{response} to debconf-set-selections") % { response: response }) preseed response else self.info _("No responsefile specified or non existent, not preseeding anything") end end def uninstall self.run_preseed if @resource[:responsefile] aptget "-y", "-q", :remove, @resource[:name] end def purge self.run_preseed if @resource[:responsefile] aptget '-y', '-q', :remove, '--purge', @resource[:name] # workaround a "bug" in apt, that already removed packages are not purged super end def install_options join_options(@resource[:install_options]) end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/aptitude.rb�����������������������������������������������0000644�0052762�0001160�00000001544�13417161721�022472� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.type(:package).provide :aptitude, :parent => :apt, :source => :dpkg do desc "Package management via `aptitude`." has_feature :versionable commands :aptitude => "/usr/bin/aptitude" commands :aptcache => "/usr/bin/apt-cache" ENV['DEBIAN_FRONTEND'] = "noninteractive" def aptget(*args) args.flatten! # Apparently aptitude hasn't always supported a -q flag. args.delete("-q") if args.include?("-q") args.delete("--force-yes") if args.include?("--force-yes") output = aptitude(*args) # Yay, stupid aptitude doesn't throw an error when the package is missing. if args.include?(:install) and output =~ /Couldn't find any package/ raise Puppet::Error.new( _("Could not find package %{name}") % { name: self.name } ) end end def purge aptitude '-y', 'purge', @resource[:name] end end ������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/aptrpm.rb�������������������������������������������������0000644�0052762�0001160�00000003514�13417161721�022155� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.type(:package).provide :aptrpm, :parent => :rpm, :source => :rpm do # Provide sorting functionality include Puppet::Util::Package desc "Package management via `apt-get` ported to `rpm`." has_feature :versionable commands :aptget => "apt-get" commands :aptcache => "apt-cache" commands :rpm => "rpm" if command('rpm') confine :true => begin rpm('-ql', 'rpm') rescue Puppet::ExecutionFailure false else true end end # Install a package using 'apt-get'. This function needs to support # installing a specific version. def install should = @resource.should(:ensure) str = @resource[:name] case should when true, false, Symbol # pass else # Add the package version str += "=#{should}" end cmd = %w{-q -y} cmd << 'install' << str aptget(*cmd) end # What's the latest package version available? def latest output = aptcache :showpkg, @resource[:name] if output =~ /Versions:\s*\n((\n|.)+)^$/ versions = $1 available_versions = versions.split(/\n/).collect { |version| if version =~ /^([^\(]+)\(/ $1 else self.warning _("Could not match version '%{version}'") % { version: version } nil end }.reject { |vers| vers.nil? }.sort { |a,b| versioncmp(a,b) } if available_versions.length == 0 self.debug "No latest version" print output if Puppet[:debug] end # Get the latest and greatest version number return available_versions.pop else self.err _("Could not match string") end end def update self.install end def uninstall aptget "-y", "-q", 'remove', @resource[:name] end def purge aptget '-y', '-q', 'remove', '--purge', @resource[:name] end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/blastwave.rb����������������������������������������������0000644�0052762�0001160�00000005260�13417161721�022642� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Packaging using Blastwave's pkg-get program. Puppet::Type.type(:package).provide :blastwave, :parent => :sun, :source => :sun do desc "Package management using Blastwave.org's `pkg-get` command on Solaris." pkgget = "pkg-get" pkgget = "/opt/csw/bin/pkg-get" if FileTest.executable?("/opt/csw/bin/pkg-get") confine :osfamily => :solaris commands :pkgget => pkgget def pkgget_with_cat(*args) Puppet::Util.withenv(:PAGER => "/usr/bin/cat") { pkgget(*args) } end def self.extended(mod) unless command(:pkgget) != "pkg-get" raise Puppet::Error, _("The pkg-get command is missing; blastwave packaging unavailable") end unless Puppet::FileSystem.exist?("/var/pkg-get/admin") Puppet.notice _("It is highly recommended you create '/var/pkg-get/admin'.") Puppet.notice _("See /var/pkg-get/admin-fullauto") end end def self.instances(hash = {}) blastlist(hash).collect do |bhash| bhash.delete(:avail) new(bhash) end end # Turn our blastwave listing into a bunch of hashes. def self.blastlist(hash) command = ["-c"] command << hash[:justme] if hash[:justme] output = Puppet::Util.withenv(:PAGER => "/usr/bin/cat") { pkgget command } list = output.split("\n").collect do |line| next if line =~ /^#/ next if line =~ /^WARNING/ next if line =~ /localrev\s+remoterev/ blastsplit(line) end.reject { |h| h.nil? } if hash[:justme] return list[0] else list.reject! { |h| h[:ensure] == :absent } return list end end # Split the different lines into hashes. def self.blastsplit(line) if line =~ /\s*(\S+)\s+((\[Not installed\])|(\S+))\s+(\S+)/ hash = {} hash[:name] = $1 hash[:ensure] = if $2 == "[Not installed]" :absent else $2 end hash[:avail] = $5 hash[:avail] = hash[:ensure] if hash[:avail] == "SAME" # Use the name method, so it works with subclasses. hash[:provider] = self.name return hash else Puppet.warning _("Cannot match %{line}") % { line: line } return nil end end def install pkgget_with_cat "-f", :install, @resource[:name] end # Retrieve the version from the current package file. def latest hash = self.class.blastlist(:justme => @resource[:name]) hash[:avail] end def query if hash = self.class.blastlist(:justme => @resource[:name]) hash else {:ensure => :absent} end end # Remove the old package, and install the new one def update pkgget_with_cat "-f", :upgrade, @resource[:name] end def uninstall pkgget_with_cat "-f", :remove, @resource[:name] end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/dpkg.rb���������������������������������������������������0000644�0052762�0001160�00000011023�13417161721�021571� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/package' Puppet::Type.type(:package).provide :dpkg, :parent => Puppet::Provider::Package do desc "Package management via `dpkg`. Because this only uses `dpkg` and not `apt`, you must specify the source of any packages you want to manage." has_feature :holdable commands :dpkg => "/usr/bin/dpkg" commands :dpkg_deb => "/usr/bin/dpkg-deb" commands :dpkgquery => "/usr/bin/dpkg-query" # Performs a dpkgquery call with a pipe so that output can be processed # inline in a passed block. # @param args [Array<String>] any command line arguments to be appended to the command # @param block expected to be passed on to execpipe # @return whatever the block returns # @see Puppet::Util::Execution.execpipe # @api private def self.dpkgquery_piped(*args, &block) cmd = args.unshift(command(:dpkgquery)) Puppet::Util::Execution.execpipe(cmd, &block) end def self.instances packages = [] # list out all of the packages dpkgquery_piped('-W', '--showformat', self::DPKG_QUERY_FORMAT_STRING) do |pipe| # now turn each returned line into a package object pipe.each_line do |line| if hash = parse_line(line) packages << new(hash) end end end packages end private # Note: self:: is required here to keep these constants in the context of what will # eventually become this Puppet::Type::Package::ProviderDpkg class. self::DPKG_QUERY_FORMAT_STRING = %Q{'${Status} ${Package} ${Version}\\n'} self::FIELDS_REGEX = %r{^(\S+) +(\S+) +(\S+) (\S+) (\S*)$} self::FIELDS= [:desired, :error, :status, :name, :ensure] # @param line [String] one line of dpkg-query output # @return [Hash,nil] a hash of FIELDS or nil if we failed to match # @api private def self.parse_line(line) hash = nil if match = self::FIELDS_REGEX.match(line) hash = {} self::FIELDS.zip(match.captures) do |field,value| hash[field] = value end hash[:provider] = self.name if hash[:status] == 'not-installed' hash[:ensure] = :purged elsif ['config-files', 'half-installed', 'unpacked', 'half-configured'].include?(hash[:status]) hash[:ensure] = :absent end hash[:ensure] = :held if hash[:desired] == 'hold' else Puppet.debug("Failed to match dpkg-query line #{line.inspect}") end return hash end public def install unless file = @resource[:source] raise ArgumentError, _("You cannot install dpkg packages without a source") end args = [] # We always unhold when installing to remove any prior hold. self.unhold if @resource[:configfiles] == :keep args << '--force-confold' else args << '--force-confnew' end args << '-i' << file dpkg(*args) end def update self.install end # Return the version from the package. def latest output = dpkg_deb "--show", @resource[:source] matches = /^(\S+)\t(\S+)$/.match(output).captures warning _("source doesn't contain named package, but %{name}") % { name: matches[0] } unless matches[0].match( Regexp.escape(@resource[:name]) ) matches[1] end def query hash = nil # list out our specific package begin output = dpkgquery( "-W", "--showformat", self.class::DPKG_QUERY_FORMAT_STRING, @resource[:name] ) hash = self.class.parse_line(output) rescue Puppet::ExecutionFailure # dpkg-query exits 1 if the package is not found. return {:ensure => :purged, :status => 'missing', :name => @resource[:name], :error => 'ok'} end hash ||= {:ensure => :absent, :status => 'missing', :name => @resource[:name], :error => 'ok'} if hash[:error] != "ok" raise Puppet::Error.new( "Package #{hash[:name]}, version #{hash[:ensure]} is in error state: #{hash[:error]}" ) end hash end def uninstall dpkg "-r", @resource[:name] end def purge dpkg "--purge", @resource[:name] end def hold self.install Tempfile.open('puppet_dpkg_set_selection') do |tmpfile| tmpfile.write("#{@resource[:name]} hold\n") tmpfile.flush execute([:dpkg, "--set-selections"], :failonfail => false, :combine => false, :stdinfile => tmpfile.path.to_s) end end def unhold Tempfile.open('puppet_dpkg_set_selection') do |tmpfile| tmpfile.write("#{@resource[:name]} install\n") tmpfile.flush execute([:dpkg, "--set-selections"], :failonfail => false, :combine => false, :stdinfile => tmpfile.path.to_s) end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/fink.rb���������������������������������������������������0000644�0052762�0001160�00000003555�13417161721�021606� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.type(:package).provide :fink, :parent => :dpkg, :source => :dpkg do # Provide sorting functionality include Puppet::Util::Package desc "Package management via `fink`." commands :fink => "/sw/bin/fink" commands :aptget => "/sw/bin/apt-get" commands :aptcache => "/sw/bin/apt-cache" commands :dpkgquery => "/sw/bin/dpkg-query" has_feature :versionable # A derivative of DPKG; this is how most people actually manage # Debian boxes, and the only thing that differs is that it can # install packages from remote sites. def finkcmd(*args) fink(*args) end # Install a package using 'apt-get'. This function needs to support # installing a specific version. def install self.run_preseed if @resource[:responsefile] should = @resource.should(:ensure) str = @resource[:name] case should when true, false, Symbol # pass else # Add the package version str += "=#{should}" end cmd = %w{-b -q -y} cmd << :install << str finkcmd(cmd) end # What's the latest package version available? def latest output = aptcache :policy, @resource[:name] if output =~ /Candidate:\s+(\S+)\s/ return $1 else self.err _("Could not find latest version") return nil end end # # preseeds answers to dpkg-set-selection from the "responsefile" # def run_preseed if response = @resource[:responsefile] and Puppet::FileSystem.exist?(response) self.info(_("Preseeding %{response} to debconf-set-selections") % { response: response }) preseed response else self.info _("No responsefile specified or non existent, not preseeding anything") end end def update self.install end def uninstall finkcmd "-y", "-q", :remove, @model[:name] end def purge aptget '-y', '-q', 'remove', '--purge', @resource[:name] end end ���������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/freebsd.rb������������������������������������������������0000644�0052762�0001160�00000002566�13417161721�022272� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.type(:package).provide :freebsd, :parent => :openbsd do desc "The specific form of package management on FreeBSD. This is an extremely quirky packaging system, in that it freely mixes between ports and packages. Apparently all of the tools are written in Ruby, so there are plans to rewrite this support to directly use those libraries." commands :pkginfo => "/usr/sbin/pkg_info", :pkgadd => "/usr/sbin/pkg_add", :pkgdelete => "/usr/sbin/pkg_delete" confine :operatingsystem => :freebsd def self.listcmd command(:pkginfo) end def install if @resource[:source] =~ /\/$/ if @resource[:source] =~ /^(ftp|https?):/ Puppet::Util.withenv :PACKAGESITE => @resource[:source] do pkgadd "-r", @resource[:name] end else Puppet::Util.withenv :PKG_PATH => @resource[:source] do pkgadd @resource[:name] end end else Puppet.warning _("source is defined but does not have trailing slash, ignoring %{source}") % { source: @resource[:source] } if @resource[:source] pkgadd "-r", @resource[:name] end end def query self.class.instances.each do |provider| if provider.name == @resource.name return provider.properties end end nil end def uninstall pkgdelete "#{@resource[:name]}-#{@resource.should(:ensure)}" end end ������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/hpux.rb���������������������������������������������������0000644�0052762�0001160�00000001712�13417161721�021634� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# HP-UX packaging. require 'puppet/provider/package' Puppet::Type.type(:package).provide :hpux, :parent => Puppet::Provider::Package do desc "HP-UX's packaging system." commands :swinstall => "/usr/sbin/swinstall", :swlist => "/usr/sbin/swlist", :swremove => "/usr/sbin/swremove" confine :operatingsystem => "hp-ux" defaultfor :operatingsystem => "hp-ux" def self.instances # TODO: This is very hard on HP-UX! [] end # source and name are required def install raise ArgumentError, _("source must be provided to install HP-UX packages") unless resource[:source] args = standard_args + ["-s", resource[:source], resource[:name]] swinstall(*args) end def query swlist resource[:name] {:ensure => :present} rescue {:ensure => :absent} end def uninstall args = standard_args + [resource[:name]] swremove(*args) end def standard_args ["-x", "mount_all_filesystems=false"] end end ������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/macports.rb�����������������������������������������������0000644�0052762�0001160�00000006474�13417161721�022512� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/package' require 'puppet/provider/command' Puppet::Type.type(:package).provide :macports, :parent => Puppet::Provider::Package do desc "Package management using MacPorts on OS X. Supports MacPorts versions and revisions, but not variants. Variant preferences may be specified using [the MacPorts variants.conf file](http://guide.macports.org/chunked/internals.configuration-files.html#internals.configuration-files.variants-conf). When specifying a version in the Puppet DSL, only specify the version, not the revision. Revisions are only used internally for ensuring the latest version/revision of a port. " confine :operatingsystem => :darwin has_command(:port, "/opt/local/bin/port") do environment :HOME => "/opt/local" end has_feature :installable has_feature :uninstallable has_feature :upgradeable has_feature :versionable def self.parse_installed_query_line(line) regex = /(\S+)\s+@(\S+)_(\d+).*\(active\)/ fields = [:name, :ensure, :revision] hash_from_line(line, regex, fields) end def self.parse_info_query_line(line) regex = /(\S+)\s+(\S+)/ fields = [:version, :revision] hash_from_line(line, regex, fields) end def self.hash_from_line(line, regex, fields) hash = {} if match = regex.match(line) fields.zip(match.captures) { |field, value| hash[field] = value } hash[:provider] = self.name return hash end nil end def self.instances packages = [] port("-q", :installed).each_line do |line| if hash = parse_installed_query_line(line) packages << new(hash) end end packages end def install should = @resource.should(:ensure) if [:latest, :installed, :present].include?(should) port("-q", :install, @resource[:name]) else port("-q", :install, @resource[:name], "@#{should}") end # MacPorts now correctly exits non-zero with appropriate errors in # situations where a port cannot be found or installed. end def query result = self.class.parse_installed_query_line(execute([command(:port), "-q", :installed, @resource[:name]], :failonfail => false, :combine => false)) return {} if result.nil? return result end def latest # We need both the version and the revision to be confident # we've got the latest revision of a specific version # Note we're still not doing anything with variants here. info_line = execute([command(:port), "-q", :info, "--line", "--version", "--revision", @resource[:name]], :failonfail => false, :combine => false) return nil if info_line == "" if newest = self.class.parse_info_query_line(info_line) current = query # We're doing some fiddling behind the scenes here to cope with updated revisions. # If we're already at the latest version/revision, then just return the version # so the current and desired values match. Otherwise return version and revision # to trigger an upgrade to the latest revision. if newest[:version] == current[:ensure] and newest[:revision] == current[:revision] return current[:ensure] else return "#{newest[:version]}_#{newest[:revision]}" end end nil end def uninstall port("-q", :uninstall, @resource[:name]) end def update install end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/nim.rb����������������������������������������������������0000644�0052762�0001160�00000027351�13417161721�021442� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/package' require 'puppet/util/package' Puppet::Type.type(:package).provide :nim, :parent => :aix, :source => :aix do desc "Installation from an AIX NIM LPP source. The `source` parameter is required for this provider, and should specify the name of a NIM `lpp_source` resource that is visible to the puppet agent machine. This provider supports the management of both BFF/installp and RPM packages. Note that package downgrades are *not* supported; if your resource specifies a specific version number and there is already a newer version of the package installed on the machine, the resource will fail with an error message." # The commands we are using on an AIX box are installed standard # (except nimclient) nimclient needs the bos.sysmgt.nim.client fileset. commands :nimclient => "/usr/sbin/nimclient", :lslpp => "/usr/bin/lslpp", :rpm => "rpm" # If NIM has not been configured, /etc/niminfo will not be present. # However, we have no way of knowing if the NIM server is not configured # properly. confine :exists => "/etc/niminfo" has_feature :versionable attr_accessor :latest_info def self.srclistcmd(source) [ command(:nimclient), "-o", "showres", "-a", "installp_flags=L", "-a", "resource=#{source}" ] end def uninstall output = lslpp("-qLc", @resource[:name]).split(':') # the 6th index in the colon-delimited output contains a " " for installp/BFF # packages, and an "R" for RPMS. (duh.) pkg_type = output[6] case pkg_type when " " installp "-gu", @resource[:name] when "R" rpm "-e", @resource[:name] else self.fail(_("Unrecognized AIX package type identifier: '%{pkg_type}'") % { pkg_type: pkg_type }) end # installp will return an exit code of zero even if it didn't uninstall # anything... so let's make sure it worked. unless query().nil? self.fail _("Failed to uninstall package '%{name}'") % { name: @resource[:name] } end end def install(useversion = true) unless source = @resource[:source] self.fail _("An LPP source location is required in 'source'") end pkg = @resource[:name] version_specified = (useversion and (! @resource.should(:ensure).is_a? Symbol)) # This is unfortunate for a couple of reasons. First, because of a subtle # difference in the command-line syntax for installing an RPM vs an # installp/BFF package, we need to know ahead of time which type of # package we're trying to install. This means we have to execute an # extra command. # # Second, the command is easiest to deal with and runs fastest if we # pipe it through grep on the shell. Unfortunately, the way that # the provider `make_command_methods` metaprogramming works, we can't # use that code path to execute the command (because it treats the arguments # as an array of args that all apply to `nimclient`, which fails when you # hit the `|grep`.) So here we just call straight through to P::U.execute # with a single string argument for the full command, rather than going # through the metaprogrammed layer. We could get rid of the grep and # switch back to the metaprogrammed stuff, and just parse all of the output # in Ruby... but we'd be doing an awful lot of unnecessary work. showres_command = "/usr/sbin/nimclient -o showres -a resource=#{source} |/usr/bin/grep -p -E " if (version_specified) version = @resource.should(:ensure) showres_command << "'#{Regexp.escape(pkg)}( |-)#{Regexp.escape(version)}'" else version = nil showres_command << "'#{Regexp.escape(pkg)}'" end output = Puppet::Util::Execution.execute(showres_command) if (version_specified) package_type = determine_package_type(output, pkg, version) else package_type, version = determine_latest_version(output, pkg) end if (package_type == nil) errmsg = if version_specified _("Unable to find package '%{package}' with version '%{version}' on lpp_source '%{source}'") % { package: pkg, version: version, source: source } else _("Unable to find package '%{package}' on lpp_source '%{source}'") % { package: pkg, source: source } end self.fail errmsg end # This part is a bit tricky. If there are multiple versions of the # package available, then `version` will be set to a value, and we'll need # to add that value to our installation command. However, if there is only # one version of the package available, `version` will be set to `nil`, and # we don't need to add the version string to the command. if (version) # Now we know if the package type is RPM or not, and we can adjust our # `pkg` string for passing to the install command accordingly. if (package_type == :rpm) # RPMs expect a hyphen between the package name and the version number version_separator = "-" else # installp/BFF packages expect a space between the package name and the # version number. version_separator = " " end pkg += version_separator + version end # NOTE: the installp flags here are ignored (but harmless) for RPMs output = nimclient "-o", "cust", "-a", "installp_flags=acgwXY", "-a", "lpp_source=#{source}", "-a", "filesets=#{pkg}" # If the package is superseded, it means we're trying to downgrade and we # can't do that. case package_type when :installp if output =~ /^#{Regexp.escape(@resource[:name])}\s+.*\s+Already superseded by.*$/ self.fail _("NIM package provider is unable to downgrade packages") end when :rpm if output =~ /^#{Regexp.escape(@resource[:name])}.* is superseded by.*$/ self.fail _("NIM package provider is unable to downgrade packages") end end end private ## UTILITY METHODS FOR PARSING `nimclient -o showres` output # This makes me very sad. These regexes seem pretty fragile, but # I spent a lot of time trying to figure out a solution that didn't # require parsing the `nimclient -o showres` output and was unable to # do so. self::HEADER_LINE_REGEX = /^([^\s]+)\s+[^@]+@@(I|R):(\1)\s+[^\s]+$/ self::PACKAGE_LINE_REGEX = /^.*@@(I|R):(.*)$/ self::RPM_PACKAGE_REGEX = /^(.*)-(.*-\d+) \2$/ self::INSTALLP_PACKAGE_REGEX = /^(.*) (.*)$/ # Here is some sample output that shows what the above regexes will be up # against: # FOR AN INSTALLP PACKAGE: # # mypackage.foo ALL @@I:mypackage.foo _all_filesets # @ 1.2.3.1 MyPackage Runtime Environment @@I:mypackage.foo 1.2.3.1 # + 1.2.3.4 MyPackage Runtime Environment @@I:mypackage.foo 1.2.3.4 # + 1.2.3.8 MyPackage Runtime Environment @@I:mypackage.foo 1.2.3.8 # # FOR AN RPM PACKAGE: # # mypackage.foo ALL @@R:mypackage.foo _all_filesets # @@R:mypackage.foo-1.2.3-1 1.2.3-1 # @@R:mypackage.foo-1.2.3-4 1.2.3-4 # @@R:mypackage.foo-1.2.3-8 1.2.3-8 # Parse the output of a `nimclient -o showres` command. Returns a two-dimensional # hash, where the first-level keys are package names, the second-level keys are # version number strings for all of the available version numbers for a package, # and the values indicate the package type (:rpm / :installp) def parse_showres_output(showres_output) paragraphs = split_into_paragraphs(showres_output) packages = {} paragraphs.each do |para| lines = para.split(/$/) parse_showres_header_line(lines.shift) lines.each do |l| package, version, type = parse_showres_package_line(l) packages[package] ||= {} packages[package][version] = type end end packages end # This method basically just splits the multi-line input string into chunks # based on lines that contain nothing but whitespace. It also strips any # leading or trailing whitespace (including newlines) from the resulting # strings and then returns them as an array. def split_into_paragraphs(showres_output) showres_output.split(/^\s*$/).map { |p| p.strip! } end def parse_showres_header_line(line) # This method doesn't produce any meaningful output; it's basically just # meant to validate that the header line for the package listing output # looks sane, so we know we're dealing with the kind of output that we # are capable of handling. unless line.match(self.class::HEADER_LINE_REGEX) self.fail _("Unable to parse output from nimclient showres: line does not match expected package header format:\n'%{line}'") % { line: line } end end def parse_installp_package_string(package_string) unless match = package_string.match(self.class::INSTALLP_PACKAGE_REGEX) self.fail _("Unable to parse output from nimclient showres: package string does not match expected installp package string format:\n'%{package_string}'") % { package_string: package_string } end package_name = match.captures[0] version = match.captures[1] [package_name, version, :installp] end def parse_rpm_package_string(package_string) unless match = package_string.match(self.class::RPM_PACKAGE_REGEX) self.fail _("Unable to parse output from nimclient showres: package string does not match expected rpm package string format:\n'%{package_string}'") % { package_string: package_string } end package_name = match.captures[0] version = match.captures[1] [package_name, version, :rpm] end def parse_showres_package_line(line) unless match = line.match(self.class::PACKAGE_LINE_REGEX) self.fail _("Unable to parse output from nimclient showres: line does not match expected package line format:\n'%{line}'") % { line: line } end package_type_flag = match.captures[0] package_string = match.captures[1] case package_type_flag when "I" parse_installp_package_string(package_string) when "R" parse_rpm_package_string(package_string) else self.fail _("Unrecognized package type specifier: '%{package_type_flag}' in package line:\n'%{line}'") % { package_type_flag: package_type_flag, line: line } end end # Given a blob of output from `nimclient -o showres` and a package name, # this method checks to see if there are multiple versions of the package # available on the lpp_source. If there are, the method returns # [package_type, latest_version] (where package_type is one of :installp or :rpm). # If there is only one version of the package available, it returns # [package_type, nil], because the caller doesn't need to pass the version # string to the command-line command if there is only one version available. # If the package is not available at all, the method simply returns nil (instead # of a tuple). def determine_latest_version(showres_output, package_name) packages = parse_showres_output(showres_output) unless packages.has_key?(package_name) return nil end if (packages[package_name].count == 1) version = packages[package_name].keys[0] return packages[package_name][version], nil else versions = packages[package_name].keys latest_version = (versions.sort { |a, b| Puppet::Util::Package.versioncmp(b, a) })[0] return packages[package_name][latest_version], latest_version end end def determine_package_type(showres_output, package_name, version) packages = parse_showres_output(showres_output) unless (packages.has_key?(package_name) and packages[package_name].has_key?(version)) return nil end packages[package_name][version] end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/openbsd.rb������������������������������������������������0000644�0052762�0001160�00000015737�13417161721�022316� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/package' # Packaging on OpenBSD. Doesn't work anywhere else that I know of. Puppet::Type.type(:package).provide :openbsd, :parent => Puppet::Provider::Package do desc "OpenBSD's form of `pkg_add` support. This provider supports the `install_options` and `uninstall_options` attributes, which allow command-line flags to be passed to pkg_add and pkg_delete. These options should be specified as a string (e.g. '--flag'), a hash (e.g. {'--flag' => 'value'}), or an array where each element is either a string or a hash." commands :pkginfo => "pkg_info", :pkgadd => "pkg_add", :pkgdelete => "pkg_delete" defaultfor :operatingsystem => :openbsd confine :operatingsystem => :openbsd has_feature :versionable has_feature :install_options has_feature :uninstall_options has_feature :upgradeable def self.instances packages = [] begin execpipe(listcmd) do |process| # our regex for matching pkg_info output regex = /^(.*)-(\d[^-]*)[-]?(\w*)(.*)$/ fields = [:name, :ensure, :flavor ] hash = {} # now turn each returned line into a package object process.each_line { |line| if match = regex.match(line.split[0]) fields.zip(match.captures) { |field,value| hash[field] = value } hash[:provider] = self.name packages << new(hash) hash = {} else unless line =~ /Updating the pkgdb/ # Print a warning on lines we can't match, but move # on, since it should be non-fatal warning(_("Failed to match line %{line}") % { line: line }) end end } end return packages rescue Puppet::ExecutionFailure return nil end end def self.listcmd [command(:pkginfo), "-a"] end def latest parse_pkgconf if @resource[:source][-1,1] == ::File::SEPARATOR e_vars = { 'PKG_PATH' => @resource[:source] } else e_vars = {} end if @resource[:flavor] query = "#{@resource[:name]}--#{@resource[:flavor]}" else query = @resource[:name] end output = Puppet::Util.withenv(e_vars) {pkginfo "-Q", query} version = properties[:ensure] if output.nil? or output.size == 0 or output =~ /Error from / debug "Failed to query for #{resource[:name]}" return version else # Remove all fuzzy matches first. output = output.split.select {|p| p =~ /^#{resource[:name]}-(\d[^-]*)[-]?(\w*)/ }.join debug "pkg_info -Q for #{resource[:name]}: #{output}" end if output =~ /^#{resource[:name]}-(\d[^-]*)[-]?(\w*) \(installed\)$/ debug "Package is already the latest available" return version else match = /^(.*)-(\d[^-]*)[-]?(\w*)$/.match(output) debug "Latest available for #{resource[:name]}: #{match[2]}" if version.to_sym == :absent || version.to_sym == :purged return match[2] end vcmp = version.split('.').map{|s|s.to_i} <=> match[2].split('.').map{|s|s.to_i} if vcmp > 0 # The locally installed package may actually be newer than what a mirror # has. Log it at debug, but ignore it otherwise. debug "Package #{resource[:name]} #{version} newer then available #{match[2]}" return version else return match[2] end end end def update self.install(true) end def parse_pkgconf unless @resource[:source] if Puppet::FileSystem.exist?("/etc/pkg.conf") File.open("/etc/pkg.conf", "rb").readlines.each do |line| if matchdata = line.match(/^installpath\s*=\s*(.+)\s*$/i) @resource[:source] = matchdata[1] elsif matchdata = line.match(/^installpath\s*\+=\s*(.+)\s*$/i) if @resource[:source].nil? @resource[:source] = matchdata[1] else @resource[:source] += ":" + matchdata[1] end end end unless @resource[:source] raise Puppet::Error, _("No valid installpath found in /etc/pkg.conf and no source was set") end else raise Puppet::Error, _("You must specify a package source or configure an installpath in /etc/pkg.conf") end end end def install(latest = false) cmd = [] parse_pkgconf if @resource[:source][-1,1] == ::File::SEPARATOR e_vars = { 'PKG_PATH' => @resource[:source] } full_name = get_full_name(latest) else e_vars = {} full_name = @resource[:source] end cmd << install_options cmd << full_name if latest cmd.unshift('-rz') end Puppet::Util.withenv(e_vars) { pkgadd cmd.flatten.compact } end def get_full_name(latest = false) # In case of a real update (i.e., the package already exists) then # pkg_add(8) can handle the flavors. However, if we're actually # installing with 'latest', we do need to handle the flavors. This is # done so we can feed pkg_add(8) the full package name to install to # prevent ambiguity. if latest && resource[:flavor] "#{resource[:name]}--#{resource[:flavor]}" elsif latest # Don't depend on get_version for updates. @resource[:name] else # If :ensure contains a version, use that instead of looking it up. # This allows for installing packages with the same stem, but multiple # version such as openldap-server. if /(\d[^-]*)$/.match(@resource[:ensure].to_s) use_version = @resource[:ensure] else use_version = get_version end [ @resource[:name], use_version, @resource[:flavor]].join('-').gsub(/-+$/, '') end end def get_version execpipe([command(:pkginfo), "-I", @resource[:name]]) do |process| # our regex for matching pkg_info output regex = /^(.*)-(\d[^-]*)[-]?(\w*)(.*)$/ master_version = 0 version = -1 process.each_line do |line| if match = regex.match(line.split[0]) # now we return the first version, unless ensure is latest version = match.captures[1] return version unless @resource[:ensure] == "latest" master_version = version unless master_version > version end end return master_version unless master_version == 0 return '' if version == -1 raise Puppet::Error, _("%{version} is not available for this package") % { version: version } end rescue Puppet::ExecutionFailure return nil end def query # Search for the version info if pkginfo(@resource[:name]) =~ /Information for (inst:)?#{@resource[:name]}-(\S+)/ return { :ensure => $2 } else return nil end end def install_options join_options(resource[:install_options]) end def uninstall_options join_options(resource[:uninstall_options]) end def uninstall pkgdelete uninstall_options.flatten.compact, @resource[:name] end def purge pkgdelete "-c", "-q", @resource[:name] end end ���������������������������������puppet-5.5.10/lib/puppet/provider/package/opkg.rb���������������������������������������������������0000644�0052762�0001160�00000003646�13417161721�021620� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/package' Puppet::Type.type(:package).provide :opkg, :source => :opkg, :parent => Puppet::Provider::Package do desc "Opkg packaging support. Common on OpenWrt and OpenEmbedded platforms" commands :opkg => "opkg" confine :operatingsystem => :openwrt defaultfor :operatingsystem => :openwrt def self.instances packages = [] execpipe("#{command(:opkg)} list-installed") do |process| regex = %r{^(\S+) - (\S+)} fields = [:name, :ensure] hash = {} process.each_line { |line| if match = regex.match(line) fields.zip(match.captures) { |field,value| hash[field] = value } hash[:provider] = self.name packages << new(hash) hash = {} else warning(_("Failed to match line %{line}") % { line: line }) end } end packages rescue Puppet::ExecutionFailure return nil end def latest output = opkg( "list", @resource[:name]) matches = /^(\S+) - (\S+)/.match(output).captures matches[1] end def install # OpenWrt package lists are ephemeral, make sure we have at least # some entries in the list directory for opkg to use opkg('update') if package_lists.size <= 2 if @resource[:source] opkg( '--force-overwrite', 'install', @resource[:source] ) else opkg( '--force-overwrite', 'install', @resource[:name] ) end end def uninstall opkg( 'remove', @resource[:name] ) end def update self.install end def query # list out our specific package output = opkg( 'list-installed', @resource[:name] ) if output =~ /^(\S+) - (\S+)/ return { :ensure => $2 } end nil rescue Puppet::ExecutionFailure return { :ensure => :purged, :status => 'missing', :name => @resource[:name], :error => 'ok', } end private def package_lists Dir.entries('/var/opkg-lists/') end end ������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/pacman.rb�������������������������������������������������0000644�0052762�0001160�00000020347�13417161721�022114� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/package' require 'set' require 'uri' Puppet::Type.type(:package).provide :pacman, :parent => Puppet::Provider::Package do desc "Support for the Package Manager Utility (pacman) used in Archlinux. This provider supports the `install_options` attribute, which allows command-line flags to be passed to pacman. These options should be specified as a string (e.g. '--flag'), a hash (e.g. {'--flag' => 'value'}), or an array where each element is either a string or a hash." # If yaourt is installed, we can make use of it def self.yaourt? @yaourt ||= Puppet::FileSystem.exist?('/usr/bin/yaourt') end commands :pacman => "/usr/bin/pacman" # Yaourt is a common AUR helper which, if installed, we can use to query the AUR commands :yaourt => "/usr/bin/yaourt" if yaourt? confine :operatingsystem => [:archlinux, :manjarolinux] defaultfor :operatingsystem => [:archlinux, :manjarolinux] has_feature :install_options has_feature :uninstall_options has_feature :upgradeable has_feature :virtual_packages # Checks if a given name is a group def self.group?(name) begin !pacman("-Sg", name).empty? rescue Puppet::ExecutionFailure # pacman returns an expected non-zero exit code when the name is not a group false end end # Install a package using 'pacman', or 'yaourt' if available. # Installs quietly, without confirmation or progress bar, updates package # list from servers defined in pacman.conf. def install if @resource[:source] install_from_file else install_from_repo end unless self.query fail(_("Could not find package '%{name}'") % { name: @resource[:name] }) end end # Fetch the list of packages and package groups that are currently installed on the system. # Only package groups that are fully installed are included. If a group adds packages over time, it will not # be considered as fully installed any more, and we would install the new packages on the next run. # If a group removes packages over time, nothing will happen. This is intended. def self.instances instances = [] # Get the installed packages installed_packages = get_installed_packages installed_packages.sort_by { |k, _| k }.each do |package, version| instances << new(to_resource_hash(package, version)) end # Get the installed groups get_installed_groups(installed_packages).each do |group, version| instances << new(to_resource_hash(group, version)) end instances end # returns a hash package => version of installed packages def self.get_installed_packages begin packages = {} execpipe([command(:pacman), "-Q"]) do |pipe| # pacman -Q output is 'packagename version-rel' regex = %r{^(\S+)\s(\S+)} pipe.each_line do |line| if match = regex.match(line) packages[match.captures[0]] = match.captures[1] else warning(_("Failed to match line '%{line}'") % { line: line }) end end end packages rescue Puppet::ExecutionFailure fail(_("Error getting installed packages")) end end # returns a hash of group => version of installed groups def self.get_installed_groups(installed_packages, filter = nil) groups = {} begin # Build a hash of group name => list of packages command = [command(:pacman), "-Sgg"] command << filter if filter execpipe(command) do |pipe| pipe.each_line do |line| name, package = line.split packages = (groups[name] ||= []) packages << package end end # Remove any group that doesn't have all its packages installed groups.delete_if do |_, packages| !packages.all? { |package| installed_packages[package] } end # Replace the list of packages with a version string consisting of packages that make up the group groups.each do |name, packages| groups[name] = packages.sort.map {|package| "#{package} #{installed_packages[package]}"}.join ', ' end rescue Puppet::ExecutionFailure # pacman returns an expected non-zero exit code when the filter name is not a group raise unless filter end groups end # Because Archlinux is a rolling release based distro, installing a package # should always result in the newest release. def update # Install in pacman can be used for update, too self.install end # We rescue the main check from Pacman with a check on the AUR using yaourt, if installed def latest # Synchronize the database pacman "-Sy" resource_name = @resource[:name] # If target is a group, construct the group version return pacman("-Sp", "--print-format", "%n %v", resource_name).lines.map{ |line| line.chomp }.sort.join(', ') if self.class.group?(resource_name) # Start by querying with pacman first # If that fails, retry using yaourt against the AUR pacman_check = true begin if pacman_check output = pacman "-Sp", "--print-format", "%v", resource_name return output.chomp else output = yaourt "-Qma", resource_name output.split("\n").each do |line| return line.split[1].chomp if line =~ /^aur/ end end rescue Puppet::ExecutionFailure if pacman_check and self.class.yaourt? pacman_check = false # now try the AUR retry else raise end end end # Queries information for a package or package group def query installed_packages = self.class.get_installed_packages resource_name = @resource[:name] # Check for the resource being a group version = self.class.get_installed_groups(installed_packages, resource_name)[resource_name] if version unless @resource.allow_virtual? warning(_("%{resource_name} is a group, but allow_virtual is false.") % { resource_name: resource_name }) return nil end else version = installed_packages[resource_name] end # Return nil if no package or group found return nil unless version self.class.to_resource_hash(resource_name, version) end def self.to_resource_hash(name, version) { :name => name, :ensure => version, :provider => self.name } end # Removes a package from the system. def uninstall resource_name = @resource[:name] is_group = self.class.group?(resource_name) fail(_("Refusing to uninstall package group %{resource_name}, because allow_virtual is false.") % { resource_name: resource_name }) if is_group && !@resource.allow_virtual? cmd = %w{--noconfirm --noprogressbar} cmd += uninstall_options if @resource[:uninstall_options] cmd << "-R" cmd << '-s' if is_group cmd << resource_name if self.class.yaourt? yaourt(*cmd) else pacman(*cmd) end end private def install_options join_options(@resource[:install_options]) end def uninstall_options join_options(@resource[:uninstall_options]) end def install_from_file source = @resource[:source] begin source_uri = URI.parse source rescue => detail self.fail Puppet::Error, _("Invalid source '%{source}': %{detail}") % { source: source, detail: detail }, detail end source = case source_uri.scheme when nil then source when /https?/i then source when /ftp/i then source when /file/i then source_uri.path when /puppet/i fail _("puppet:// URL is not supported by pacman") else fail _("Source %{source} is not supported by pacman") % { source: source } end pacman "--noconfirm", "--noprogressbar", "-Sy" pacman "--noconfirm", "--noprogressbar", "-U", source end def install_from_repo resource_name = @resource[:name] # Refuse to install if not allowing virtual packages and the resource is a group fail(_("Refusing to install package group %{resource_name}, because allow_virtual is false.") % { resource_name: resource_name }) if self.class.group?(resource_name) && !@resource.allow_virtual? cmd = %w{--noconfirm --needed --noprogressbar} cmd += install_options if @resource[:install_options] cmd << "-Sy" << resource_name if self.class.yaourt? yaourt(*cmd) else pacman(*cmd) end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/pip3.rb���������������������������������������������������0000644�0052762�0001160�00000001210�13417161721�021514� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Puppet package provider for Python's `pip3` package management frontend. # <http://pip.pypa.io/> require 'puppet/provider/package/pip' Puppet::Type.type(:package).provide :pip3, :parent => :pip do desc "Python packages via `pip3`. This provider supports the `install_options` attribute, which allows command-line flags to be passed to pip3. These options should be specified as a string (e.g. '--flag'), a hash (e.g. {'--flag' => 'value'}), or an array where each element is either a string or a hash." has_feature :installable, :uninstallable, :upgradeable, :versionable, :install_options def self.cmd ["pip3"] end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/pkgdmg.rb�������������������������������������������������0000644�0052762�0001160�00000013572�13417161721�022130� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# # Motivation: DMG files provide a true HFS file system # and are easier to manage and .pkg bundles. # # Note: the 'apple' Provider checks for the package name # in /L/Receipts. Since we install multiple pkg's from a single # source, we treat the source .pkg.dmg file as the package name. # As a result, we store installed .pkg.dmg file names # in /var/db/.puppet_pkgdmg_installed_<name> require 'puppet/provider/package' require 'puppet/util/plist' require 'puppet/util/http_proxy' Puppet::Type.type(:package).provide :pkgdmg, :parent => Puppet::Provider::Package do desc "Package management based on Apple's Installer.app and DiskUtility.app. This provider works by checking the contents of a DMG image for Apple pkg or mpkg files. Any number of pkg or mpkg files may exist in the root directory of the DMG file system, and Puppet will install all of them. Subdirectories are not checked for packages. This provider can also accept plain .pkg (but not .mpkg) files in addition to .dmg files. Notes: * The `source` attribute is mandatory. It must be either a local disk path or an HTTP, HTTPS, or FTP URL to the package. * The `name` of the resource must be the filename (without path) of the DMG file. * When installing the packages from a DMG, this provider writes a file to disk at `/var/db/.puppet_pkgdmg_installed_NAME`. If that file is present, Puppet assumes all packages from that DMG are already installed. * This provider is not versionable and uses DMG filenames to determine whether a package has been installed. Thus, to install new a version of a package, you must create a new DMG with a different filename." confine :operatingsystem => :darwin confine :feature => :cfpropertylist defaultfor :operatingsystem => :darwin commands :installer => "/usr/sbin/installer" commands :hdiutil => "/usr/bin/hdiutil" commands :curl => "/usr/bin/curl" # JJM We store a cookie for each installed .pkg.dmg in /var/db def self.instance_by_name Dir.entries("/var/db").find_all { |f| f =~ /^\.puppet_pkgdmg_installed_/ }.collect do |f| name = f.sub(/^\.puppet_pkgdmg_installed_/, '') yield name if block_given? name end end def self.instances instance_by_name.collect do |name| new(:name => name, :provider => :pkgdmg, :ensure => :installed) end end def self.installpkg(source, name, orig_source) installer "-pkg", source, "-target", "/" # Non-zero exit status will throw an exception. Puppet::FileSystem.open("/var/db/.puppet_pkgdmg_installed_#{name}", nil, "w:UTF-8") do |t| t.print "name: '#{name}'\n" t.print "source: '#{orig_source}'\n" end end def self.installpkgdmg(source, name) unless Puppet::Util::HttpProxy.no_proxy?(source) http_proxy_host = Puppet::Util::HttpProxy.http_proxy_host http_proxy_port = Puppet::Util::HttpProxy.http_proxy_port end unless source =~ /\.dmg$/i || source =~ /\.pkg$/i raise Puppet::Error.new(_("Mac OS X PKG DMGs must specify a source string ending in .dmg or flat .pkg file")) end require 'open-uri' # Dead code; this is never used. The File.open call 20-ish lines south of here used to be Kernel.open but changed in '09. -NF cached_source = source tmpdir = Dir.mktmpdir ext = /(\.dmg|\.pkg)$/i.match(source)[0] begin if %r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} =~ cached_source cached_source = File.join(tmpdir, "#{name}#{ext}") args = [ "-o", cached_source, "-C", "-", "-L", "-s", "--fail", "--url", source ] if http_proxy_host and http_proxy_port args << "--proxy" << "#{http_proxy_host}:#{http_proxy_port}" elsif http_proxy_host and not http_proxy_port args << "--proxy" << http_proxy_host end begin curl(*args) Puppet.debug "Success: curl transferred [#{name}] (via: curl #{args.join(" ")})" rescue Puppet::ExecutionFailure Puppet.debug "curl #{args.join(" ")} did not transfer [#{name}]. Falling back to local file." # This used to fall back to open-uri. -NF cached_source = source end end if source =~ /\.dmg$/i # If you fix this to use open-uri again, you must update the docs above. -NF File.open(cached_source) do |dmg| xml_str = hdiutil "mount", "-plist", "-nobrowse", "-readonly", "-noidme", "-mountrandom", "/tmp", dmg.path hdiutil_info = Puppet::Util::Plist.parse_plist(xml_str) raise Puppet::Error.new(_("No disk entities returned by mount at %{path}") % { path: dmg.path }) unless hdiutil_info.has_key?("system-entities") mounts = hdiutil_info["system-entities"].collect { |entity| entity["mount-point"] }.compact begin mounts.each do |mountpoint| Dir.entries(mountpoint).select { |f| f =~ /\.m{0,1}pkg$/i }.each do |pkg| installpkg("#{mountpoint}/#{pkg}", name, source) end end ensure mounts.each do |mountpoint| hdiutil "eject", mountpoint end end end else installpkg(cached_source, name, source) end ensure FileUtils.remove_entry_secure(tmpdir, true) end end def query if Puppet::FileSystem.exist?("/var/db/.puppet_pkgdmg_installed_#{@resource[:name]}") Puppet.debug "/var/db/.puppet_pkgdmg_installed_#{@resource[:name]} found" return {:name => @resource[:name], :ensure => :present} else return nil end end def install unless source = @resource[:source] raise Puppet::Error.new(_("Mac OS X PKG DMGs must specify a package source.")) end unless name = @resource[:name] raise Puppet::Error.new(_("Mac OS X PKG DMGs must specify a package name.")) end self.class.installpkgdmg(source,name) end end ��������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/pkgin.rb��������������������������������������������������0000644�0052762�0001160�00000004150�13417161721�021757� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require "puppet/provider/package" Puppet::Type.type(:package).provide :pkgin, :parent => Puppet::Provider::Package do desc "Package management using pkgin, a binary package manager for pkgsrc." commands :pkgin => "pkgin" defaultfor :operatingsystem => [ :smartos, :netbsd ] has_feature :installable, :uninstallable, :upgradeable, :versionable def self.parse_pkgin_line(package) # e.g. # vim-7.2.446;Vim editor (vi clone) without GUI match, name, version, status = *package.match(/([^\s;]+)-([^\s;]+)[;\s](=|>|<)?.+$/) if match { :name => name, :status => status, :ensure => version } end end def self.prefetch(packages) super # Without -f, no fresh pkg_summary files are downloaded pkgin("-yf", :update) end def self.instances pkgin(:list).split("\n").map do |package| new(parse_pkgin_line(package)) end end def query packages = parse_pkgsearch_line if packages.empty? if @resource[:ensure] == :absent notice _("declared as absent but unavailable %{file}:%{line}") % { file: @resource.file, line: resource.line } return false else @resource.fail _("No candidate to be installed") end end packages.first.update( :ensure => :absent ) end def parse_pkgsearch_line packages = pkgin(:search, resource[:name]).split("\n") return [] if packages.length == 1 # Remove the last three lines of help text. packages.slice!(-4, 4) pkglist = packages.map{ |line| self.class.parse_pkgin_line(line) } pkglist.select{ |package| resource[:name] == package[:name] } end def install if String === @resource[:ensure] pkgin("-y", :install, "#{resource[:name]}-#{resource[:ensure]}") else pkgin("-y", :install, resource[:name]) end end def uninstall pkgin("-y", :remove, resource[:name]) end def latest package = parse_pkgsearch_line.detect{ |p| p[:status] == '<' } return properties[:ensure] if not package return package[:ensure] end def update pkgin("-y", :install, resource[:name]) end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/pkgutil.rb������������������������������������������������0000644�0052762�0001160�00000012117�13417161721�022330� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Packaging using Peter Bonivart's pkgutil program. Puppet::Type.type(:package).provide :pkgutil, :parent => :sun, :source => :sun do desc "Package management using Peter Bonivart's ``pkgutil`` command on Solaris." pkgutil_bin = "pkgutil" if FileTest.executable?("/opt/csw/bin/pkgutil") pkgutil_bin = "/opt/csw/bin/pkgutil" end confine :osfamily => :solaris has_command(:pkguti, pkgutil_bin) do environment :HOME => ENV['HOME'] end def self.healthcheck() unless Puppet::FileSystem.exist?("/var/opt/csw/pkgutil/admin") Puppet.notice _("It is highly recommended you create '/var/opt/csw/pkgutil/admin'.") Puppet.notice _("See /var/opt/csw/pkgutil") end correct_wgetopts = false [ "/opt/csw/etc/pkgutil.conf", "/etc/opt/csw/pkgutil.conf" ].each do |confpath| File.open(confpath) do |conf| conf.each_line {|line| correct_wgetopts = true if line =~ /^\s*wgetopts\s*=.*(-nv|-q|--no-verbose|--quiet)/ } end end if ! correct_wgetopts Puppet.notice _("It is highly recommended that you set 'wgetopts=-nv' in your pkgutil.conf.") end end def self.instances(hash = {}) healthcheck # Use the available pkg list (-a) to work out aliases aliases = {} availlist.each do |pkg| aliases[pkg[:name]] = pkg[:alias] end # The -c pkglist lists installed packages pkginsts = [] output = pkguti(["-c"]) parse_pkglist(output).each do |pkg| pkg.delete(:avail) pkginsts << new(pkg) # Create a second instance with the alias if it's different pkgalias = aliases[pkg[:name]] if pkgalias and pkg[:name] != pkgalias apkg = pkg.dup apkg[:name] = pkgalias pkginsts << new(apkg) end end pkginsts end # Turns a pkgutil -a listing into hashes with the common alias, full # package name and available version def self.availlist output = pkguti ["-a"] output.split("\n").collect do |line| next if line =~ /^common\s+package/ # header of package list next if noise?(line) if line =~ /\s*(\S+)\s+(\S+)\s+(.*)/ { :alias => $1, :name => $2, :avail => $3 } else Puppet.warning _("Cannot match %{line}") % { line: line } end end.reject { |h| h.nil? } end # Turn our pkgutil -c listing into a hash for a single package. def pkgsingle(resource) # The --single option speeds up the execution, because it queries # the package management system for one package only. command = ["-c", "--single", resource[:name]] self.class.parse_pkglist(run_pkgutil(resource, command), { :justme => resource[:name] }) end # Turn our pkgutil -c listing into a bunch of hashes. def self.parse_pkglist(output, hash = {}) output = output.split("\n") if output[-1] == "Not in catalog" Puppet.warning _("Package not in pkgutil catalog: %{package}") % { package: hash[:justme] } return nil end list = output.collect do |line| next if line =~ /installed\s+catalog/ # header of package list next if noise?(line) pkgsplit(line) end.reject { |h| h.nil? } if hash[:justme] # Single queries may have been for an alias so return the name requested if list.any? list[-1][:name] = hash[:justme] return list[-1] end else list.reject! { |h| h[:ensure] == :absent } return list end end # Identify common types of pkgutil noise as it downloads catalogs etc def self.noise?(line) true if line =~ /^#/ true if line =~ /^Checking integrity / # use_gpg true if line =~ /^gpg: / # gpg verification true if line =~ /^=+> / # catalog fetch true if line =~ /\d+:\d+:\d+ URL:/ # wget without -q false end # Split the different lines into hashes. def self.pkgsplit(line) if line =~ /\s*(\S+)\s+(\S+)\s+(.*)/ hash = {} hash[:name] = $1 hash[:ensure] = if $2 == "notinst" :absent else $2 end hash[:avail] = $3 if hash[:avail] =~ /^SAME\s*$/ hash[:avail] = hash[:ensure] end # Use the name method, so it works with subclasses. hash[:provider] = self.name return hash else Puppet.warning _("Cannot match %{line}") % { line: line } return nil end end def run_pkgutil(resource, *args) # Allow source to be one or more URLs pointing to a repository that all # get passed to pkgutil via one or more -t options if resource[:source] sources = [resource[:source]].flatten pkguti(*[sources.map{|src| [ "-t", src ]}, *args].flatten) else pkguti(*args.flatten) end end def install run_pkgutil @resource, "-y", "-i", @resource[:name] end # Retrieve the version from the current package file. def latest hash = pkgsingle(@resource) hash[:avail] if hash end def query if hash = pkgsingle(@resource) hash else {:ensure => :absent} end end def update run_pkgutil @resource, "-y", "-u", @resource[:name] end def uninstall run_pkgutil @resource, "-y", "-r", @resource[:name] end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/ports.rb��������������������������������������������������0000644�0052762�0001160�00000004350�13417161721�022020� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.type(:package).provide :ports, :parent => :freebsd, :source => :freebsd do desc "Support for FreeBSD's ports. Note that this, too, mixes packages and ports." commands :portupgrade => "/usr/local/sbin/portupgrade", :portversion => "/usr/local/sbin/portversion", :portuninstall => "/usr/local/sbin/pkg_deinstall", :portinfo => "/usr/sbin/pkg_info" %w{INTERACTIVE UNAME}.each do |var| ENV.delete(var) if ENV.include?(var) end def install # -N: install if the package is missing, otherwise upgrade # -M: yes, we're a batch, so don't ask any questions cmd = %w{-N -M BATCH=yes} << @resource[:name] output = portupgrade(*cmd) if output =~ /\*\* No such / raise Puppet::ExecutionFailure, _("Could not find package %{name}") % { name: @resource[:name] } end end # If there are multiple packages, we only use the last one def latest cmd = ["-v", @resource[:name]] begin output = portversion(*cmd) rescue Puppet::ExecutionFailure raise Puppet::Error.new(output, $!) end line = output.split("\n").pop unless line =~ /^(\S+)\s+(\S)\s+(.+)$/ # There's no "latest" version, so just return a placeholder return :latest end pkgstuff = $1 match = $2 info = $3 unless pkgstuff =~ /^\S+-([^-\s]+)$/ raise Puppet::Error, _("Could not match package info '%{pkgstuff}'") % { pkgstuff: pkgstuff } end version = $1 if match == "=" or match == ">" # we're up to date or more recent return version end # Else, we need to be updated; we need to pull out the new version unless info =~ /\((\w+) has (.+)\)/ raise Puppet::Error, _("Could not match version info '%{info}'") % { info: info } end source, newversion = $1, $2 debug "Newer version in #{source}" newversion end def query # support portorigin_glob such as "mail/postfix" name = self.name if name =~ /\// name = self.name.split(/\//).slice(1) end self.class.instances.each do |instance| if instance.name == name return instance.properties end end nil end def uninstall portuninstall @resource[:name] end def update install end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/portupgrade.rb��������������������������������������������0000644�0052762�0001160�00000017266�13417161721�023217� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Whole new package, so include pack stuff require 'puppet/provider/package' Puppet::Type.type(:package).provide :portupgrade, :parent => Puppet::Provider::Package do include Puppet::Util::Execution desc "Support for FreeBSD's ports using the portupgrade ports management software. Use the port's full origin as the resource name. eg (ports-mgmt/portupgrade) for the portupgrade port." ## has_features is usually autodetected based on defs below. # has_features :installable, :uninstallable, :upgradeable commands :portupgrade => "/usr/local/sbin/portupgrade", :portinstall => "/usr/local/sbin/portinstall", :portversion => "/usr/local/sbin/portversion", :portuninstall => "/usr/local/sbin/pkg_deinstall", :portinfo => "/usr/sbin/pkg_info" ## Activate this only once approved by someone important. # defaultfor :operatingsystem => :freebsd # Remove unwanted environment variables. %w{INTERACTIVE UNAME}.each do |var| if ENV.include?(var) ENV.delete(var) end end ######## instances sub command (builds the installed packages list) def self.instances Puppet.debug "portupgrade.rb Building packages list from installed ports" # regex to match output from pkg_info regex = %r{^(\S+)-([^-\s]+):(\S+)$} # Corresponding field names fields = [:portname, :ensure, :portorigin] # define Temporary hash used, packages array of hashes hash = Hash.new packages = [] # exec command cmdline = ["-aoQ"] begin output = portinfo(*cmdline) rescue Puppet::ExecutionFailure raise Puppet::Error.new(output, $!) end # split output and match it and populate temp hash output.split("\n").each { |data| # reset hash to nil for each line hash.clear if match = regex.match(data) # Output matched regex fields.zip(match.captures) { |field, value| hash[field] = value } # populate the actual :name field from the :portorigin # Set :provider to this object name hash[:name] = hash[:portorigin] hash[:provider] = self.name # Add to the full packages listing packages << new(hash) else # unrecognised output from pkg_info Puppet.debug "portupgrade.Instances() - unable to match output: #{data}" end } # return the packages array of hashes return packages end ######## Installation sub command def install Puppet.debug "portupgrade.install() - Installation call on #{@resource[:name]}" # -M: yes, we're a batch, so don't ask any questions cmdline = ["-M BATCH=yes", @resource[:name]] # FIXME: it's possible that portinstall prompts for data so locks up. begin output = portinstall(*cmdline) rescue Puppet::ExecutionFailure raise Puppet::Error.new(output, $!) end if output =~ /\*\* No such / raise Puppet::ExecutionFailure, _("Could not find package %{name}") % { name: @resource[:name] } end # No return code required, so do nil to be clean return nil end ######## Latest subcommand (returns the latest version available, or current version if installed is latest) def latest Puppet.debug "portupgrade.latest() - Latest check called on #{@resource[:name]}" # search for latest version available, or return current version. # cmdline = "portversion -v <portorigin>", returns "<portname> <code> <stuff>" # or "** No matching package found: <portname>" cmdline = ["-v", @resource[:name]] begin output = portversion(*cmdline) rescue Puppet::ExecutionFailure raise Puppet::Error.new(output, $!) end # Check: output format. if output =~ /^\S+-([^-\s]+)\s+(\S)\s+(.*)/ installedversion = $1 comparison = $2 otherdata = $3 # Only return a new version number when it's clear that there is a new version # all others return the current version so no unexpected 'upgrades' occur. case comparison when "=", ">" Puppet.debug "portupgrade.latest() - Installed package is latest (#{installedversion})" return installedversion when "<" # "portpkg-1.7_5 < needs updating (port has 1.14)" # "portpkg-1.7_5 < needs updating (port has 1.14) (=> 'newport/pkg') if otherdata =~ /\(port has (\S+)\)/ newversion = $1 Puppet.debug "portupgrade.latest() - Installed version needs updating to (#{newversion})" return newversion else Puppet.debug "portupgrade.latest() - Unable to determine new version from (#{otherdata})" return installedversion end when "?", "!", "#" Puppet.debug "portupgrade.latest() - Comparison Error reported from portversion (#{output})" return installedversion else Puppet.debug "portupgrade.latest() - Unknown code from portversion output (#{output})" return installedversion end else # error: output not parsed correctly, error out with nil. # Seriously - this section should never be called in a perfect world. # as verification that the port is installed has already happened in query. if output =~ /^\*\* No matching package / raise Puppet::ExecutionFailure, _("Could not find package %{name}") % { name: @resource[:name] } else # Any other error (dump output to log) raise Puppet::ExecutionFailure, _("Unexpected output from portversion: %{output}") % { output: output } end # Just in case we still are running, return nil return nil end # At this point normal operation has finished and we shouldn't have been called. # Error out and let the admin deal with it. raise Puppet::Error, _("portversion.latest() - fatal error with portversion: %{output}") % { output: output } end ###### Query subcommand - return a hash of details if exists, or nil if it doesn't. # Used to make sure the package is installed def query Puppet.debug "portupgrade.query() - Called on #{@resource[:name]}" cmdline = ["-qO", @resource[:name]] begin output = portinfo(*cmdline) rescue Puppet::ExecutionFailure raise Puppet::Error.new(output, $!) end # Check: if output isn't in the right format, return nil if output =~ /^(\S+)-([^-\s]+)/ # Fill in the details hash = Hash.new hash[:portorigin] = self.name hash[:portname] = $1 hash[:ensure] = $2 # If more details are required, then we can do another pkg_info # query here and parse out that output and add to the hash # return the hash to the caller return hash else Puppet.debug "portupgrade.query() - package (#{@resource[:name]}) not installed" return nil end end ####### Uninstall command def uninstall Puppet.debug "portupgrade.uninstall() - called on #{@resource[:name]}" # Get full package name from port origin to uninstall with cmdline = ["-qO", @resource[:name]] begin output = portinfo(*cmdline) rescue Puppet::ExecutionFailure raise Puppet::Error.new(output, $!) end if output =~ /^(\S+)/ # output matches, so uninstall it portuninstall $1 end end ######## Update/upgrade command def update Puppet.debug "portupgrade.update() - called on (#{@resource[:name]})" cmdline = ["-qO", @resource[:name]] begin output = portinfo(*cmdline) rescue Puppet::ExecutionFailure raise Puppet::Error.new(output, $!) end if output =~ /^(\S+)/ # output matches, so upgrade the software cmdline = ["-M BATCH=yes", $1] begin output = portupgrade(*cmdline) rescue Puppet::ExecutionFailure raise Puppet::Error.new(output, $!) end end end ## EOF end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/rug.rb����������������������������������������������������0000644�0052762�0001160�00000002353�13417161721�021447� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.type(:package).provide :rug, :parent => :rpm do desc "Support for suse `rug` package manager." has_feature :versionable commands :rug => "/usr/bin/rug" commands :rpm => "rpm" confine :operatingsystem => [:suse, :sles] # Install a package using 'rug'. def install should = @resource.should(:ensure) self.debug "Ensuring => #{should}" wanted = @resource[:name] # XXX: We don't actually deal with epochs here. case should when true, false, Symbol # pass else # Add the package version wanted += "-#{should}" end rug "--quiet", :install, "-y", wanted unless self.query raise Puppet::ExecutionFailure.new( _("Could not find package %{name}") % { name: self.name } ) end end # What's the latest package version available? def latest #rug can only get a list of *all* available packages? output = rug "list-updates" if output =~ /#{Regexp.escape @resource[:name]}\s*\|\s*([^\s\|]+)/ return $1 else # rug didn't find updates, pretend the current # version is the latest return @property_hash[:ensure] end end def update # rug install can be used for update, too self.install end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/sun.rb����������������������������������������������������0000644�0052762�0001160�00000007076�13417161721�021466� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Sun packaging. require 'puppet/provider/package' Puppet::Type.type(:package).provide :sun, :parent => Puppet::Provider::Package do desc "Sun's packaging system. Requires that you specify the source for the packages you're managing. This provider supports the `install_options` attribute, which allows command-line flags to be passed to pkgadd. These options should be specified as a string (e.g. '--flag'), a hash (e.g. {'--flag' => 'value'}), or an array where each element is either a string or a hash." commands :pkginfo => "/usr/bin/pkginfo", :pkgadd => "/usr/sbin/pkgadd", :pkgrm => "/usr/sbin/pkgrm" confine :osfamily => :solaris defaultfor :osfamily => :solaris has_feature :install_options self::Namemap = { "PKGINST" => :name, "CATEGORY" => :category, "ARCH" => :platform, "VERSION" => :ensure, "BASEDIR" => :root, "VENDOR" => :vendor, "DESC" => :description, } def self.namemap(hash) self::Namemap.keys.inject({}) do |hsh,k| hsh.merge(self::Namemap[k] => hash[k]) end end def self.parse_pkginfo(out) # collect all the lines with : in them, and separate them out by ^$ pkgs = [] pkg = {} out.each_line do |line| case line.chomp when /^\s*$/ pkgs << pkg unless pkg.empty? pkg = {} when /^\s*([^:]+):\s+(.+)$/ pkg[$1] = $2 end end pkgs << pkg unless pkg.empty? pkgs end def self.instances parse_pkginfo(pkginfo('-l')).collect do |p| hash = namemap(p) hash[:provider] = :sun new(hash) end end # Get info on a package, optionally specifying a device. def info2hash(device = nil) args = ['-l'] args << '-d' << device if device args << @resource[:name] begin pkgs = self.class.parse_pkginfo(pkginfo(*args)) errmsg = case pkgs.size when 0 'No message' when 1 pkgs[0]['ERROR'] end return self.class.namemap(pkgs[0]) if errmsg.nil? # according to commit 41356a7 some errors do not raise an exception # so even though pkginfo passed, we have to check the actual output raise Puppet::Error, _("Unable to get information about package %{name} because of: %{errmsg}") % { name: @resource[:name], errmsg: errmsg } rescue Puppet::ExecutionFailure return {:ensure => :absent} end end # Retrieve the version from the current package file. def latest info2hash(@resource[:source])[:ensure] end def query info2hash end # only looking for -G now def install #TRANSLATORS Sun refers to the company name, do not translate raise Puppet::Error, _("Sun packages must specify a package source") unless @resource[:source] options = { :adminfile => @resource[:adminfile], :responsefile => @resource[:responsefile], :source => @resource[:source], :cmd_options => @resource[:install_options] } pkgadd prepare_cmd(options) end def uninstall pkgrm prepare_cmd(:adminfile => @resource[:adminfile]) end # Remove the old package, and install the new one. This will probably # often fail. def update self.uninstall if (@property_hash[:ensure] || info2hash[:ensure]) != :absent self.install end def prepare_cmd(opt) [if_have_value('-a', opt[:adminfile]), if_have_value('-r', opt[:responsefile]), if_have_value('-d', opt[:source]), opt[:cmd_options] || [], ['-n', @resource[:name]]].flatten end def if_have_value(prefix, value) if value [prefix, value] else [] end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/sunfreeware.rb��������������������������������������������0000644�0052762�0001160�00000000633�13417161721�023177� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# At this point, it's an exact copy of the Blastwave stuff. Puppet::Type.type(:package).provide :sunfreeware, :parent => :blastwave, :source => :sun do desc "Package management using sunfreeware.com's `pkg-get` command on Solaris. At this point, support is exactly the same as `blastwave` support and has not actually been tested." commands :pkgget => "pkg-get" confine :osfamily => :solaris end �����������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/tdnf.rb���������������������������������������������������0000644�0052762�0001160�00000001747�13417161721�021613� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.type(:package).provide :tdnf, :parent => :dnf do desc "Support via `tdnf`. This provider supports the `install_options` attribute, which allows command-line flags to be passed to tdnf. These options should be spcified as a string (e.g. '--flag'), a hash (e.g. {'--flag' => 'value'}), or an array where each element is either a string or a hash." has_feature :install_options, :versionable, :virtual_packages commands :cmd => "tdnf", :rpm => "rpm" # Note: this confine was borrowed from the Yum provider. The # original purpose (from way back in 2007) was to make sure we # never try to use RPM on a machine without it. We think this # has probably become obsolete with the way `commands` work, so # we should investigate removing it at some point. if command('rpm') confine :true => begin rpm('--version') rescue Puppet::ExecutionFailure false else true end end defaultfor :operatingsystem => "PhotonOS" end �������������������������puppet-5.5.10/lib/puppet/provider/package/up2date.rb������������������������������������������������0000644�0052762�0001160�00000002026�13417161721�022213� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.type(:package).provide :up2date, :parent => :rpm, :source => :rpm do desc "Support for Red Hat's proprietary `up2date` package update mechanism." commands :up2date => "/usr/sbin/up2date-nox" defaultfor :osfamily => :redhat, :lsbdistrelease => ["2.1", "3", "4"] confine :osfamily => :redhat # Install a package using 'up2date'. def install up2date "-u", @resource[:name] unless self.query raise Puppet::ExecutionFailure.new( _("Could not find package %{name}") % { name: self.name } ) end end # What's the latest package version available? def latest #up2date can only get a list of *all* available packages? output = up2date "--showall" if output =~ /^#{Regexp.escape @resource[:name]}-(\d+.*)\.\w+/ return $1 else # up2date didn't find updates, pretend the current # version is the latest return @property_hash[:ensure] end end def update # Install in up2date can be used for update, too self.install end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/urpmi.rb��������������������������������������������������0000644�0052762�0001160�00000002731�13417161721�022006� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.type(:package).provide :urpmi, :parent => :rpm, :source => :rpm do desc "Support via `urpmi`." commands :urpmi => "urpmi", :urpmq => "urpmq", :rpm => "rpm", :urpme => "urpme" defaultfor :operatingsystem => [:mandriva, :mandrake] has_feature :versionable def install should = @resource.should(:ensure) self.debug "Ensuring => #{should}" wanted = @resource[:name] # XXX: We don't actually deal with epochs here. case should when true, false, Symbol # pass else # Add the package version wanted += "-#{should}" end urpmi "--auto", wanted unless self.query raise Puppet::Error, _("Package %{name} was not present after trying to install it") % { name: self.name } end end # What's the latest package version available? def latest output = urpmq "-S", @resource[:name] if output =~ /^#{Regexp.escape @resource[:name]}\s+:\s+.*\(\s+(\S+)\s+\)/ return $1 else # urpmi didn't find updates, pretend the current # version is the latest return @resource[:ensure] end end def update # Install in urpmi can be used for update, too self.install end # For normal package removal the urpmi provider will delegate to the RPM # provider. If the package to remove has dependencies then uninstalling via # rpm will fail, but `urpme` can be used to remove a package and its # dependencies. def purge urpme '--auto', @resource[:name] end end ���������������������������������������puppet-5.5.10/lib/puppet/provider/package/windows.rb������������������������������������������������0000644�0052762�0001160�00000010011�13417161721�022332� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/package' require 'puppet/util/windows' require 'puppet/provider/package/windows/package' Puppet::Type.type(:package).provide(:windows, :parent => Puppet::Provider::Package) do desc "Windows package management. This provider supports either MSI or self-extracting executable installers. This provider requires a `source` attribute when installing the package. It accepts paths to local files, mapped drives, or UNC paths. This provider supports the `install_options` and `uninstall_options` attributes, which allow command-line flags to be passed to the installer. These options should be specified as a string (e.g. '--flag'), a hash (e.g. {'--flag' => 'value'}), or an array where each element is either a string or a hash. If the executable requires special arguments to perform a silent install or uninstall, then the appropriate arguments should be specified using the `install_options` or `uninstall_options` attributes, respectively. Puppet will automatically quote any option that contains spaces." confine :operatingsystem => :windows defaultfor :operatingsystem => :windows has_feature :installable has_feature :uninstallable has_feature :install_options has_feature :uninstall_options has_feature :versionable attr_accessor :package # Return an array of provider instances def self.instances Puppet::Provider::Package::Windows::Package.map do |pkg| provider = new(to_hash(pkg)) provider.package = pkg provider end end def self.to_hash(pkg) { :name => pkg.name, :ensure => pkg.version || :installed, :provider => :windows } end # Query for the provider hash for the current resource. The provider we # are querying, may not have existed during prefetch def query Puppet::Provider::Package::Windows::Package.find do |pkg| if pkg.match?(resource) return self.class.to_hash(pkg) end end nil end def install installer = Puppet::Provider::Package::Windows::Package.installer_class(resource) command = [installer.install_command(resource), install_options].flatten.compact.join(' ') output = execute(command, :failonfail => false, :combine => true, :cwd => File.dirname(resource[:source]), :suppress_window => true) check_result(output.exitstatus) end def uninstall command = [package.uninstall_command, uninstall_options].flatten.compact.join(' ') output = execute(command, :failonfail => false, :combine => true, :suppress_window => true) check_result(output.exitstatus) end # https://msdn.microsoft.com/en-us/library/windows/desktop/aa368542(v=vs.85).aspx self::ERROR_SUCCESS = 0 self::ERROR_SUCCESS_REBOOT_INITIATED = 1641 self::ERROR_SUCCESS_REBOOT_REQUIRED = 3010 # (Un)install may "fail" because the package requested a reboot, the system requested a # reboot, or something else entirely. Reboot requests mean the package was installed # successfully, but we warn since we don't have a good reboot strategy. def check_result(hr) operation = resource[:ensure] == :absent ? 'uninstall' : 'install' case hr when self.class::ERROR_SUCCESS # yeah when self.class::ERROR_SUCCESS_REBOOT_INITIATED warning(_("The package %{operation}ed successfully and the system is rebooting now.") % { operation: operation }) when self.class::ERROR_SUCCESS_REBOOT_REQUIRED warning(_("The package %{operation}ed successfully, but the system must be rebooted.") % { operation: operation }) else raise Puppet::Util::Windows::Error.new(_("Failed to %{operation}") % { operation: operation }, hr) end end # This only gets called if there is a value to validate, but not if it's absent def validate_source(value) fail(_("The source parameter cannot be empty when using the Windows provider.")) if value.empty? end def install_options join_options(resource[:install_options]) end def uninstall_options join_options(resource[:uninstall_options]) end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/windows/��������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�022021� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/windows/exe_package.rb������������������������������������0000644�0052762�0001160�00000004347�13417161721�024605� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/package/windows/package' class Puppet::Provider::Package::Windows class ExePackage < Puppet::Provider::Package::Windows::Package attr_reader :uninstall_string # registry values to load under each product entry in # HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall # for this provider REG_VALUE_NAMES = [ 'DisplayVersion', 'UninstallString', 'ParentKeyName', 'Security Update', 'Update Rollup', 'Hotfix', 'WindowsInstaller', ] # Return an instance of the package from the registry, or nil def self.from_registry(name, values) if valid?(name, values) ExePackage.new( get_display_name(values), values['DisplayVersion'], values['UninstallString'] ) end end # Is this a valid executable package we should manage? def self.valid?(name, values) # See http://community.spiceworks.com/how_to/show/2238 displayName = get_display_name(values) !!(displayName && displayName.length > 0 && values['UninstallString'] && values['UninstallString'].length > 0 && values['WindowsInstaller'] != 1 && # DWORD name !~ /^KB[0-9]{6}/ && values['ParentKeyName'] == nil && values['Security Update'] == nil && values['Update Rollup'] == nil && values['Hotfix'] == nil) end def initialize(name, version, uninstall_string) super(name, version) @uninstall_string = uninstall_string end # Does this package match the resource? def match?(resource) resource[:name] == name end def self.install_command(resource) munge(resource[:source]) end def uninstall_command # Only quote bare uninstall strings, e.g. # C:\Program Files (x86)\Notepad++\uninstall.exe # Don't quote uninstall strings that are already quoted, e.g. # "c:\ruby187\unins000.exe" # Don't quote uninstall strings that contain arguments: # "C:\Program Files (x86)\Git\unins000.exe" /SILENT if uninstall_string =~ /\A[^"]*.exe\Z/i command = "\"#{uninstall_string}\"" else command = uninstall_string end command end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/windows/msi_package.rb������������������������������������0000644�0052762�0001160�00000004307�13417161721�024610� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/package/windows/package' class Puppet::Provider::Package::Windows class MsiPackage < Puppet::Provider::Package::Windows::Package attr_reader :productcode, :packagecode # From msi.h INSTALLSTATE_DEFAULT = 5 # product is installed for the current user INSTALLUILEVEL_NONE = 2 # completely silent installation # registry values to load under each product entry in # HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall # for this provider REG_VALUE_NAMES = [ 'DisplayVersion', 'WindowsInstaller' ] # Get the COM installer object, it's in a separate method for testing def self.installer # REMIND: when does the COM release happen? WIN32OLE.new("WindowsInstaller.Installer") end # Return an instance of the package from the registry, or nil def self.from_registry(name, values) if valid?(name, values) inst = installer if inst.ProductState(name) == INSTALLSTATE_DEFAULT MsiPackage.new(get_display_name(values), values['DisplayVersion'], name, # productcode inst.ProductInfo(name, 'PackageCode')) end end end # Is this a valid MSI package we should manage? def self.valid?(name, values) # See http://community.spiceworks.com/how_to/show/2238 displayName = get_display_name(values) !!(displayName && displayName.length > 0 && values['WindowsInstaller'] == 1 && # DWORD name =~ /\A\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}\Z/i) end def initialize(name, version, productcode, packagecode) super(name, version) @productcode = productcode @packagecode = packagecode end # Does this package match the resource? def match?(resource) resource[:name].casecmp(packagecode) == 0 || resource[:name].casecmp(productcode) == 0 || resource[:name] == name end def self.install_command(resource) ['msiexec.exe', '/qn', '/norestart', '/i', munge(resource[:source])] end def uninstall_command ['msiexec.exe', '/qn', '/norestart', '/x', productcode] end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/windows/package.rb����������������������������������������0000644�0052762�0001160�00000006464�13417161721�023746� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/package' require 'puppet/util/windows' class Puppet::Provider::Package::Windows class Package extend Enumerable extend Puppet::Util::Errors include Puppet::Util::Windows::Registry extend Puppet::Util::Windows::Registry attr_reader :name, :version REG_DISPLAY_VALUE_NAMES = [ 'DisplayName', 'QuietDisplayName' ] def self.reg_value_names_to_load REG_DISPLAY_VALUE_NAMES | MsiPackage::REG_VALUE_NAMES | ExePackage::REG_VALUE_NAMES end # Enumerate each package. The appropriate package subclass # will be yielded. def self.each(&block) with_key do |key, values| name = key.name.match(/^.+\\([^\\]+)$/).captures[0] [MsiPackage, ExePackage].find do |klass| if pkg = klass.from_registry(name, values) yield pkg end end end end # Yield each registry key and its values associated with an # installed package. This searches both per-machine and current # user contexts, as well as packages associated with 64 and # 32-bit installers. def self.with_key(&block) %w[HKEY_LOCAL_MACHINE HKEY_CURRENT_USER].each do |hive| [KEY64, KEY32].each do |mode| mode |= KEY_READ begin open(hive, 'Software\Microsoft\Windows\CurrentVersion\Uninstall', mode) do |uninstall| each_key(uninstall) do |name, wtime| open(hive, "#{uninstall.keyname}\\#{name}", mode) do |key| yield key, values_by_name(key, reg_value_names_to_load) end end end rescue Puppet::Util::Windows::Error => e raise e unless e.code == Puppet::Util::Windows::Error::ERROR_FILE_NOT_FOUND end end end end # Get the class that knows how to install this resource def self.installer_class(resource) fail(_("The source parameter is required when using the Windows provider.")) unless resource[:source] case resource[:source] when /\.msi"?\Z/i # REMIND: can we install from URL? # REMIND: what about msp, etc MsiPackage when /\.exe"?\Z/i fail(_("The source does not exist: '%{source}'") % { source: resource[:source] }) unless Puppet::FileSystem.exist?(resource[:source]) ExePackage else fail(_("Don't know how to install '%{source}'") % { source: resource[:source] }) end end def self.munge(value) quote(replace_forward_slashes(value)) end def self.replace_forward_slashes(value) if value.include?('/') value.gsub!('/', "\\") Puppet.debug('Package source parameter contained /s - replaced with \\s') end value end def self.quote(value) value.include?(' ') ? %Q["#{value.gsub(/"/, '\"')}"] : value end def self.get_display_name(values) return if values.nil? return values['DisplayName'] if values['DisplayName'] && values['DisplayName'].length > 0 return values['QuietDisplayName'] if values['QuietDisplayName'] && values['QuietDisplayName'].length > 0 '' end def initialize(name, version) @name = name @version = version end end end require 'puppet/provider/package/windows/msi_package' require 'puppet/provider/package/windows/exe_package' ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/zypper.rb�������������������������������������������������0000644�0052762�0001160�00000007745�13417161721�022215� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.type(:package).provide :zypper, :parent => :rpm, :source => :rpm do desc "Support for SuSE `zypper` package manager. Found in SLES10sp2+ and SLES11. This provider supports the `install_options` attribute, which allows command-line flags to be passed to zypper. These options should be specified as a string (e.g. '--flag'), a hash (e.g. {'--flag' => 'value'}), or an array where each element is either a string or a hash." has_feature :versionable, :install_options, :virtual_packages commands :zypper => "/usr/bin/zypper" defaultfor :operatingsystem => [:suse, :sles, :sled, :opensuse] confine :operatingsystem => [:suse, :sles, :sled, :opensuse] # @api private # Reset the latest version hash to nil # needed for spec tests to clear cached value def self.reset! @latest_versions = nil end def self.latest_package_version(package) if @latest_versions.nil? @latest_versions = list_updates end @latest_versions[package] end def self.list_updates output = zypper 'list-updates' avail_updates = {} # split up columns output.lines.each do |line| pkg_ver = line.split(/\s*\|\s*/) # ignore zypper headers next unless pkg_ver[0] == 'v' avail_updates[pkg_ver[2]] = pkg_ver[4] end avail_updates end #on zypper versions <1.0, the version option returns 1 #some versions of zypper output on stderr def zypper_version cmd = [self.class.command(:zypper),"--version"] execute(cmd, { :failonfail => false, :combine => true}) end # Install a package using 'zypper'. def install should = @resource.should(:ensure) self.debug "Ensuring => #{should}" wanted = @resource[:name] # XXX: We don't actually deal with epochs here. case should when true, false, Symbol should = nil else # Add the package version wanted = "#{wanted}-#{should}" end #This has been tested with following zypper versions #SLE 10.4: 0.6.201 #SLE 11.3: 1.6.307 #SLE 12.0: 1.11.14 #Assume that this will work on newer zypper versions #extract version numbers and convert to integers major, minor, patch = zypper_version.scan(/\d+/).map{ |x| x.to_i } self.debug "Detected zypper version #{major}.#{minor}.#{patch}" #zypper version < 1.0 does not support --quiet flag if major < 1 quiet = '--terse' else quiet = '--quiet' end inst_opts = [] inst_opts = install_options if resource[:install_options] options = [] options << quiet options << '--no-gpg-check' unless inst_opts.delete('--no-gpg-check').nil? options << :install #zypper 0.6.13 (OpenSuSE 10.2) does not support auto agree with licenses options << '--auto-agree-with-licenses' unless major < 1 and minor <= 6 and patch <= 13 options << '--no-confirm' options += inst_opts unless inst_opts.empty? # Zypper 0.6.201 doesn't recognize '--name' # It is unclear where this functionality was introduced, but it # is present as early as 1.0.13 options << '--name' unless major < 1 || @resource.allow_virtual? || should options << wanted zypper(*options) unless self.query raise Puppet::ExecutionFailure.new( _("Could not find package %{name}") % { name: self.name } ) end end # What's the latest package version available? def latest return self.class.latest_package_version(@resource[:name]) || # zypper didn't find updates, pretend the current # version is the latest @property_hash[:ensure] end def update # zypper install can be used for update, too self.install end def uninstall #extract version numbers and convert to integers major, minor, _ = zypper_version.scan(/\d+/).map{ |x| x.to_i } if major < 1 super else options = [:remove, '--no-confirm'] if major == 1 && minor < 6 options << '--force-resolution' end options << @resource[:name] zypper(*options) end end end ���������������������������puppet-5.5.10/lib/puppet/provider/package/dnf.rb����������������������������������������������������0000644�0052762�0001160�00000003161�13417161721�021417� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.type(:package).provide :dnf, :parent => :yum do desc "Support via `dnf`. Using this provider's `uninstallable` feature will not remove dependent packages. To remove dependent packages with this provider use the `purgeable` feature, but note this feature is destructive and should be used with the utmost care. This provider supports the `install_options` attribute, which allows command-line flags to be passed to dnf. These options should be specified as a string (e.g. '--flag'), a hash (e.g. {'--flag' => 'value'}), or an array where each element is either a string or a hash." has_feature :install_options, :versionable, :virtual_packages commands :cmd => "dnf", :rpm => "rpm" # Note: this confine was borrowed from the Yum provider. The # original purpose (from way back in 2007) was to make sure we # never try to use RPM on a machine without it. We think this # has probably become obsolete with the way `commands` work, so # we should investigate removing it at some point. if command('rpm') confine :true => begin rpm('--version') rescue Puppet::ExecutionFailure false else true end end defaultfor :operatingsystem => :fedora, :operatingsystemmajrelease => (22..30).to_a defaultfor :osfamily => :redhat, :operatingsystemmajrelease => ["8"] def self.update_command # In DNF, update is deprecated for upgrade 'upgrade' end # The value to pass to DNF as its error output level. # DNF differs from Yum slightly with regards to error outputting. # # @param None # @return [String] def self.error_level '1' end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/gem.rb����������������������������������������������������0000644�0052762�0001160�00000013171�13417161721�021422� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/package' require 'uri' # Ruby gems support. Puppet::Type.type(:package).provide :gem, :parent => Puppet::Provider::Package do desc "Ruby Gem support. If a URL is passed via `source`, then that URL is appended to the list of remote gem repositories; to ensure that only the specified source is used, also pass `--clear-sources` via `install_options`. If source is present but is not a valid URL, it will be interpreted as the path to a local gem file. If source is not present, the gem will be installed from the default gem repositories. Note that to modify this for Windows, it has to be a valid URL. This provider supports the `install_options` and `uninstall_options` attributes, which allow command-line flags to be passed to the gem command. These options should be specified as a string (e.g. '--flag'), a hash (e.g. {'--flag' => 'value'}), or an array where each element is either a string or a hash." has_feature :versionable, :install_options, :uninstall_options commands :gemcmd => "gem" def self.gemlist(options) gem_list_command = [command(:gemcmd), "list"] if options[:local] gem_list_command << "--local" else gem_list_command << "--remote" end if options[:source] gem_list_command << "--source" << options[:source] end if name = options[:justme] gem_list_command << '\A' + name + '\z' end begin list = execute(gem_list_command, {:failonfail => true, :combine => true, :custom_environment => {"HOME"=>ENV["HOME"]}}).lines. map {|set| gemsplit(set) }. reject {|x| x.nil? } rescue Puppet::ExecutionFailure => detail raise Puppet::Error, _("Could not list gems: %{detail}") % { detail: detail }, detail.backtrace end if options[:justme] return list.shift else return list end end def self.gemsplit(desc) # `gem list` when output console has a line like: # *** LOCAL GEMS *** # but when it's not to the console that line # and all blank lines are stripped # so we don't need to check for them if desc =~ /^(\S+)\s+\((.+)\)/ gem_name = $1 versions = $2.sub('default: ', '').split(/,\s*/) { :name => gem_name, :ensure => versions.map{|v| v.split[0]}, :provider => name } else Puppet.warning _("Could not match %{desc}") % { desc: desc } unless desc.chomp.empty? nil end end def self.instances(justme = false) gemlist(:local => true).collect do |hash| new(hash) end end def insync?(is) return false unless is && is != :absent begin dependency = Gem::Dependency.new('', resource[:ensure]) rescue ArgumentError # Bad requirements will cause an error during gem command invocation, so just return not in sync return false end is = [is] unless is.is_a? Array # Check if any version matches the dependency is.any? { |version| dependency.match?('', version) } end def install(useversion = true) command = [command(:gemcmd), "install"] command += install_options if resource[:install_options] if Puppet.features.microsoft_windows? version = resource[:ensure] command << "-v" << %Q["#{version}"] if (! resource[:ensure].is_a? Symbol) and useversion else command << "-v" << resource[:ensure] if (! resource[:ensure].is_a? Symbol) and useversion end if source = resource[:source] begin uri = URI.parse(source) rescue => detail self.fail Puppet::Error, _("Invalid source '%{uri}': %{detail}") % { uri: uri, detail: detail }, detail end case uri.scheme when nil # no URI scheme => interpret the source as a local file command << source when /file/i command << uri.path when 'puppet' # we don't support puppet:// URLs (yet) raise Puppet::Error.new(_("puppet:// URLs are not supported as gem sources")) else # check whether it's an absolute file path to help Windows out if Puppet::Util.absolute_path?(source) command << source else # interpret it as a gem repository command << "--source" << "#{source}" << resource[:name] end end else command << "--no-rdoc" << "--no-ri" << resource[:name] end output = execute(command, {:failonfail => true, :combine => true, :custom_environment => {"HOME"=>ENV["HOME"]}}) # Apparently some stupid gem versions don't exit non-0 on failure self.fail _("Could not install: %{output}") % { output: output.chomp } if output.include?("ERROR") end def latest # This always gets the latest version available. gemlist_options = {:justme => resource[:name]} gemlist_options.merge!({:source => resource[:source]}) unless resource[:source].nil? hash = self.class.gemlist(gemlist_options) hash[:ensure][0] end def query self.class.gemlist(:justme => resource[:name], :local => true) end def uninstall command = [command(:gemcmd), "uninstall"] command << "--executables" << "--all" << resource[:name] command += uninstall_options if resource[:uninstall_options] output = execute(command, {:failonfail => true, :combine => true, :custom_environment => {"HOME"=>ENV["HOME"]}}) # Apparently some stupid gem versions don't exit non-0 on failure self.fail _("Could not uninstall: %{output}") % { output: output.chomp } if output.include?("ERROR") end def update self.install(false) end def install_options join_options(resource[:install_options]) end def uninstall_options join_options(resource[:uninstall_options]) end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/pip.rb����������������������������������������������������0000644�0052762�0001160�00000014442�13417161721�021444� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Puppet package provider for Python's `pip` package management frontend. # <http://pip.pypa.io/> require 'puppet/provider/package' require 'puppet/util/http_proxy' Puppet::Type.type(:package).provide :pip, :parent => ::Puppet::Provider::Package do desc "Python packages via `pip`. This provider supports the `install_options` attribute, which allows command-line flags to be passed to pip. These options should be specified as a string (e.g. '--flag'), a hash (e.g. {'--flag' => 'value'}), or an array where each element is either a string or a hash." has_feature :installable, :uninstallable, :upgradeable, :versionable, :install_options # Parse lines of output from `pip freeze`, which are structured as # _package_==_version_. def self.parse(line) if line.chomp =~ /^([^=]+)==([^=]+)$/ {:ensure => $2, :name => $1, :provider => name} else nil end end # Return an array of structured information about every installed package # that's managed by `pip` or an empty array if `pip` is not available. def self.instances packages = [] pip_cmd = self.pip_cmd return [] unless pip_cmd command = [pip_cmd, 'freeze'] if Puppet::Util::Package.versioncmp(self.pip_version, '8.1.0') >= 0 # a >= b command << '--all' end execpipe command do |process| process.collect do |line| next unless options = parse(line) packages << new(options) end end # Pip can also upgrade pip, but it's not listed in freeze so need to special case it # Pip list would also show pip installed version, but "pip list" doesn't exist for older versions of pip (E.G v1.0) # Not needed when "pip freeze --all" is available if Puppet::Util::Package.versioncmp(self.pip_version, '8.1.0') == -1 && version = self.pip_version packages << new({:ensure => version, :name => File.basename(pip_cmd), :provider => name}) end packages end def self.cmd if Puppet.features.microsoft_windows? ["pip.exe"] else ["pip", "pip-python"] end end def self.pip_cmd self.cmd.map { |c| which(c) }.find { |c| c != nil } end def self.pip_version pip_cmd = self.pip_cmd return nil unless pip_cmd execpipe [pip_cmd, '--version'] do |process| process.collect do |line| return line.strip.match(/^pip (\d+\.\d+\.?\d*).*$/)[1] end end end # Return structured information about a particular package or `nil` if # it is not installed or `pip` itself is not available. def query self.class.instances.each do |provider_pip| return provider_pip.properties if @resource[:name].downcase == provider_pip.name.downcase end return nil end # Use pip CLI to look up versions from PyPI repositories, honoring local pip config such as custom repositories def latest return nil unless self.class.pip_cmd if Puppet::Util::Package.versioncmp(self.class.pip_version, '1.5.4') == -1 # a < b return latest_with_old_pip end latest_with_new_pip end # Install a package. The ensure parameter may specify installed, # latest, a version number, or, in conjunction with the source # parameter, an SCM revision. In that case, the source parameter # gives the fully-qualified URL to the repository. def install args = %w{install -q} args += install_options if @resource[:install_options] if @resource[:source] if String === @resource[:ensure] args << "#{@resource[:source]}@#{@resource[:ensure]}#egg=#{ @resource[:name]}" else args << "#{@resource[:source]}#egg=#{@resource[:name]}" end else case @resource[:ensure] when String args << "#{@resource[:name]}==#{@resource[:ensure]}" when :latest args << "--upgrade" << @resource[:name] else args << @resource[:name] end end lazy_pip(*args) end # Uninstall a package. Uninstall won't work reliably on Debian/Ubuntu # unless this issue gets fixed. # <http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=562544> def uninstall lazy_pip "uninstall", "-y", "-q", @resource[:name] end def update install end # Execute a `pip` command. If Puppet doesn't yet know how to do so, # try to teach it and if even that fails, raise the error. private def lazy_pip(*args) pip(*args) rescue NoMethodError => e # Ensure pip can upgrade pip, which usually puts pip into a new path /usr/local/bin/pip (compared to /usr/bin/pip) # The path to pip needs to be looked up again in the subsequent request. Using the preferred approach as noted # in provider.rb ensures this (copied below for reference) # # @note From provider.rb; It is preferred if the commands are not entered with absolute paths as this allows puppet # to search for them using the PATH variable. if pathname = self.class.cmd.map { |c| which(c) }.find { |c| c != nil } self.class.commands :pip => File.basename(pathname) pip(*args) else raise e, "Could not locate command #{self.class.cmd.join(' and ')}.", e.backtrace end end def install_options join_options(@resource[:install_options]) end def latest_with_new_pip # Less resource intensive approach for pip version 1.5.4 and above execpipe ["#{self.class.pip_cmd}", "install", "#{@resource[:name]}==versionplease"] do |process| process.collect do |line| # PIP OUTPUT: Could not find a version that satisfies the requirement Django==versionplease (from versions: 1.1.3, 1.8rc1) if line =~ /from versions: / textAfterLastMatch = $'.chomp(")\n") versionList = textAfterLastMatch.split(', ').sort do |x,y| Puppet::Util::Package.versioncmp(x, y) end return versionList.last end end return nil end end def latest_with_old_pip Dir.mktmpdir("puppet_pip") do |dir| execpipe ["#{self.class.pip_cmd}", "install", "#{@resource[:name]}", "-d", "#{dir}", "-v"] do |process| process.collect do |line| # PIP OUTPUT: Using version 0.10.1 (newest of versions: 0.10.1, 0.10, 0.9, 0.8.1, 0.8, 0.7.2, 0.7.1, 0.7, 0.6.1, 0.6, 0.5.2, 0.5.1, 0.5, 0.4, 0.3.1, 0.3, 0.2, 0.1) if line =~ /Using version (.+?) \(newest of versions/ return $1 end end return nil end end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/pkg.rb����������������������������������������������������0000644�0052762�0001160�00000024373�13417161721�021441� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/package' Puppet::Type.type(:package).provide :pkg, :parent => Puppet::Provider::Package do desc "OpenSolaris image packaging system. See pkg(5) for more information." # https://docs.oracle.com/cd/E19963-01/html/820-6572/managepkgs.html # A few notes before we start: # Opensolaris pkg has two slightly different formats (as of now.) # The first one is what is distributed with the Solaris 11 Express 11/10 dvd # The latest one is what you get when you update package. # To make things more interesting, pkg version just returns a sha sum. # dvd: pkg version => 052adf36c3f4 # updated: pkg version => 630e1ffc7a19 # Thankfully, Solaris has not changed the commands to be used. # TODO: We still have to allow packages to specify a preferred publisher. has_feature :versionable has_feature :upgradable has_feature :holdable commands :pkg => "/usr/bin/pkg" confine :osfamily => :solaris defaultfor :osfamily => :solaris, :kernelrelease => ['5.11', '5.12'] def self.instances pkg(:list, '-Hv').split("\n").map{|l| new(parse_line(l))} end # The IFO flag field is just what it names, the first field can have either # i_nstalled or -, and second field f_rozen or -, and last # o_bsolate or r_rename or - # so this checks if the installed field is present, and also verifies that # if not the field is -, else we don't know what we are doing and exit with # out doing more damage. def self.ifo_flag(flags) ( case flags[0..0] when 'i' {:status => 'installed'} when '-' {:status => 'known'} else raise ArgumentError, _('Unknown format %{resource_name}: %{full_flags}[%{bad_flag}]') % { resource_name: self.name, full_flags: flags, bad_flag: flags[0..0] } end ).merge( case flags[1..1] when 'f' {:ensure => 'held'} when '-' {} else raise ArgumentError, _('Unknown format %{resource_name}: %{full_flags}[%{bad_flag}]') % { resource_name: self.name, full_flags: flags, bad_flag: flags[1..1] } end ) end # The UFOXI field is the field present in the older pkg # (solaris 2009.06 - snv151a) # similar to IFO, UFOXI is also an either letter or - # u_pdate indicates that an update for the package is available. # f_rozen(n/i) o_bsolete x_cluded(n/i) i_constrained(n/i) # note that u_pdate flag may not be trustable due to constraints. # so we dont rely on it # Frozen was never implemented in UFOXI so skipping frozen here. def self.ufoxi_flag(flags) {} end # pkg state was present in the older version of pkg (with UFOXI) but is # no longer available with the IFO field version. When it was present, # it was used to indicate that a particular version was present (installed) # and later versions were known. Note that according to the pkg man page, # known never lists older versions of the package. So we can rely on this # field to make sure that if a known is present, then the pkg is upgradable. def self.pkg_state(state) case state when /installed/ {:status => 'installed'} when /known/ {:status => 'known'} else raise ArgumentError, _('Unknown format %{resource_name}: %{state}') % { resource_name: self.name, state: state } end end # Here is (hopefully) the only place we will have to deal with multiple # formats of output for different pkg versions. def self.parse_line(line) (case line.chomp # FMRI IFO # pkg://omnios/SUNWcs@0.5.11,5.11-0.151008:20131204T022241Z --- when %r'^pkg://([^/]+)/([^@]+)@(\S+) +(...)$' {:publisher => $1, :name => $2, :ensure => $3}.merge ifo_flag($4) # FMRI STATE UFOXI # pkg://solaris/SUNWcs@0.5.11,5.11-0.151.0.1:20101105T001108Z installed u---- when %r'^pkg://([^/]+)/([^@]+)@(\S+) +(\S+) +(.....)$' {:publisher => $1, :name => $2, :ensure => $3}.merge pkg_state($4).merge(ufoxi_flag($5)) else raise ArgumentError, _('Unknown line format %{resource_name}: %{parse_line}') % { resource_name: self.name, parse_line: line } end).merge({:provider => self.name}) end def hold pkg(:freeze, @resource[:name]) end def unhold r = exec_cmd(command(:pkg), 'unfreeze', @resource[:name]) raise Puppet::Error, _("Unable to unfreeze %{package}") % { package: r[:out] } unless [0,4].include? r[:exit] end def insync?(is) # this is called after the generic version matching logic (insync? for the # type), so we only get here if should != is, and 'should' is a version # number. 'is' might not be, though. should = @resource[:ensure] # NB: it is apparently possible for repository administrators to publish # packages which do not include build or branch versions, but component # version must always be present, and the timestamp is added by pkgsend # publish. if /^[0-9.]+(,[0-9.]+)?(-[0-9.]+)?:[0-9]+T[0-9]+Z$/ !~ should # We have a less-than-explicit version string, which we must accept for # backward compatibility. We can find the real version this would match # by asking pkg for the all matching versions, and selecting the first # installable one [0]; this can change over time when remote repositories # are updated, but the principle of least astonishment should still hold: # if we allow users to specify less-than-explicit versions, the # functionality should match that of the package manager. # # [0]: we could simply get the newest matching version with 'pkg list # -n', but that isn't always correct, since it might not be installable. # If that were the case we could potentially end up returning false for # insync? here but not actually changing the package version in install # (ie. if the currently installed version is the latest matching version # that is installable, we would falsely conclude here that since the # installed version is not the latest matching version, we're not in # sync). 'pkg list -a' instead of '-n' would solve this, but # unfortunately it doesn't consider downgrades 'available' (eg. with # installed foo@1.0, list -a foo@0.9 would fail). name = @resource[:name] potential_matches = pkg(:list, '-Hvfa', "#{name}@#{should}").split("\n").map{|l|self.class.parse_line(l)} n = potential_matches.length if n > 1 warning(_("Implicit version %{should} has %{n} possible matches") % { should: should, n: n }) end potential_matches.each{ |p| command = is == :absent ? 'install' : 'update' status = exec_cmd(command(:pkg), command, '-n', "#{name}@#{p[:ensure]}")[:exit] case status when 4 # if the first installable match would cause no changes, we're in sync return true when 0 warning(_("Selecting version '%{version}' for implicit '%{should}'") % { version: p[:ensure], should: should }) @resource[:ensure] = p[:ensure] return false end } raise Puppet::DevError, _("No version of %{name} matching %{should} is installable, even though the package is currently installed") % { name: name, should: should } end false end # Return the version of the package. Note that the bug # http://defect.opensolaris.org/bz/show_bug.cgi?id=19159% # notes that we can't use -Ha for the same even though the manual page reads that way. def latest # Refresh package metadata before looking for latest versions pkg(:refresh) lines = pkg(:list, "-Hvn", @resource[:name]).split("\n") # remove certificate expiration warnings from the output, but report them cert_warnings = lines.select { |line| line =~ /^Certificate/ } unless cert_warnings.empty? Puppet.warning(_("pkg warning: %{warnings}") % { warnings: cert_warnings.join(', ') }) end lst = lines.select { |line| line !~ /^Certificate/ }.map { |line| self.class.parse_line(line) } # Now we know there is a newer version. But is that installable? (i.e are there any constraints?) # return the first known we find. The only way that is currently available is to do a dry run of # pkg update and see if could get installed (`pkg update -n res`). known = lst.find {|p| p[:status] == 'known' } return known[:ensure] if known and exec_cmd(command(:pkg), 'update', '-n', @resource[:name])[:exit].zero? # If not, then return the installed, else nil (lst.find {|p| p[:status] == 'installed' } || {})[:ensure] end # install the package and accept all licenses. def install(nofail = false) name = @resource[:name] should = @resource[:ensure] # always unhold if explicitly told to install/update self.unhold is = self.query if is[:ensure].to_sym == :absent command = 'install' else command = 'update' end args = ['--accept'] if Puppet::Util::Package.versioncmp(Facter.value(:operatingsystemrelease), '11.2') >= 0 args.push('--sync-actuators-timeout', '900') end unless should.is_a? Symbol name += "@#{should}" end r = exec_cmd(command(:pkg), command, *args, name) return r if nofail raise Puppet::Error, _("Unable to update %{package}") % { package: r[:out] } if r[:exit] != 0 end # uninstall the package. The complication comes from the -r_ecursive flag which is no longer # present in newer package version. def uninstall cmd = [:uninstall] case (pkg :version).chomp when /052adf36c3f4/ cmd << '-r' end cmd << @resource[:name] pkg cmd end # update the package to the latest version available def update r = install(true) # 4 == /No updates available for this image./ return if [0,4].include? r[:exit] raise Puppet::Error, _("Unable to update %{package}") % { package: r[:out] } end # list a specific package def query r = exec_cmd(command(:pkg), 'list', '-Hv', @resource[:name]) return {:ensure => :absent, :name => @resource[:name]} if r[:exit] != 0 self.class.parse_line(r[:out]) end def exec_cmd(*cmd) output = Puppet::Util::Execution.execute(cmd, :failonfail => false, :combine => true) {:out => output, :exit => $CHILD_STATUS.exitstatus} end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/pkgng.rb��������������������������������������������������0000644�0052762�0001160�00000006767�13417161721�021775� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/package' Puppet::Type.type(:package).provide :pkgng, :parent => Puppet::Provider::Package do desc "A PkgNG provider for FreeBSD and DragonFly." commands :pkg => "/usr/local/sbin/pkg" confine :operatingsystem => [:freebsd, :dragonfly] defaultfor :operatingsystem => [:freebsd, :dragonfly] has_feature :versionable has_feature :upgradeable def self.get_query pkg(['query', '-a', '%n %v %o']) end def self.get_version_list pkg(['version', '-voRL=']) end def self.get_latest_version(origin, version_list) if latest_version = version_list.lines.find { |l| l =~ /^#{origin} / } latest_version = latest_version.split(' ').last.split(')').first return latest_version end nil end def self.instances packages = [] begin info = self.get_query version_list = self.get_version_list unless info return packages end info.lines.each do |line| name, version, origin = line.chomp.split(" ", 3) latest_version = get_latest_version(origin, version_list) || version pkg = { :ensure => version, :name => name, :provider => self.name, :origin => origin, :version => version, :latest => latest_version } packages << new(pkg) end return packages rescue Puppet::ExecutionFailure return [] end end def self.prefetch(resources) packages = instances resources.each_key do |name| if provider = packages.find{|p| p.name == name or p.origin == name } resources[name].provider = provider end end end def repo_tag_from_urn(urn) # extract repo tag from URN: urn:freebsd:repo:<tag> match = /^urn:freebsd:repo:(.+)$/.match(urn) raise ArgumentError urn.inspect unless match match[1] end def install source = resource[:source] source = URI(source) unless source.nil? # Ensure we handle the version case resource[:ensure] when true, false, Symbol installname = resource[:name] else # If resource[:name] is actually an origin (e.g. 'www/curl' instead of # just 'curl'), drop the category prefix. pkgng doesn't support version # pinning with the origin syntax (pkg install curl-1.2.3 is valid, but # pkg install www/curl-1.2.3 is not). if resource[:name] =~ /\// installname = resource[:name].split('/')[1] + '-' + resource[:ensure] else installname = resource[:name] + '-' + resource[:ensure] end end if not source # install using default repo logic args = ['install', '-qy', installname] elsif source.scheme == 'urn' # install from repo named in URN tag = repo_tag_from_urn(source.to_s) args = ['install', '-qy', '-r', tag, installname] else # add package located at URL args = ['add', '-q', source.to_s] end pkg(args) end def uninstall pkg(['remove', '-qy', resource[:name]]) end def query if @property_hash[:ensure] == nil return nil else version = @property_hash[:version] return { :version => version } end end def version @property_hash[:version] end # Upgrade to the latest version def update install end # Return the latest version of the package def latest debug "returning the latest #{@property_hash[:name].inspect} version #{@property_hash[:latest].inspect}" @property_hash[:latest] end def origin @property_hash[:origin] end end ���������puppet-5.5.10/lib/puppet/provider/package/portage.rb������������������������������������������������0000644�0052762�0001160�00000025132�13417161721�022313� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/package' require 'fileutils' Puppet::Type.type(:package).provide :portage, :parent => Puppet::Provider::Package do desc "Provides packaging support for Gentoo's portage system. This provider supports the `install_options` and `uninstall_options` attributes, which allows command-line flags to be passed to emerge. These options should be specified as a string (e.g. '--flag'), a hash (e.g. {'--flag' => 'value'}), or an array where each element is either a string or a hash." has_features :install_options, :purgeable, :reinstallable, :uninstall_options, :versionable, :virtual_packages { :emerge => '/usr/bin/emerge', :eix => '/usr/bin/eix', :qatom_bin => '/usr/bin/qatom', :update_eix => '/usr/bin/eix-update', }.each_pair do |name, path| has_command(name, path) do environment :HOME => '/' end end confine :operatingsystem => :gentoo defaultfor :operatingsystem => :gentoo def self.instances result_format = self.eix_result_format result_fields = self.eix_result_fields limit = self.eix_limit version_format = self.eix_version_format slot_versions_format = self.eix_slot_versions_format installed_versions_format = self.eix_installed_versions_format installable_versions_format = self.eix_install_versions_format begin eix_file = File.directory?('/var/cache/eix') ? '/var/cache/eix/portage.eix' : '/var/cache/eix' update_eix if !FileUtils.uptodate?(eix_file, %w{/usr/bin/eix /usr/portage/metadata/timestamp}) search_output = nil Puppet::Util.withenv :EIX_LIMIT => limit, :LASTVERSION => version_format, :LASTSLOTVERSIONS => slot_versions_format, :INSTALLEDVERSIONS => installed_versions_format, :STABLEVERSIONS => installable_versions_format do search_output = eix(*(self.eix_search_arguments + ['--installed'])) end packages = [] search_output.each_line do |search_result| match = result_format.match(search_result) if match package = {} result_fields.zip(match.captures) do |field, value| package[field] = value unless !value or value.empty? end package[:provider] = :portage packages << new(package) end end return packages rescue Puppet::ExecutionFailure => detail raise Puppet::Error.new(detail) end end def install should = @resource.should(:ensure) cmd = %w{} name = qatom[:category] ? "#{qatom[:category]}/#{qatom[:pn]}" : qatom[:pn] name = qatom[:pfx] + name if qatom[:pfx] name = name + '-' + qatom[:pv] if qatom[:pv] name = name + '-' + qatom[:pr] if qatom[:pr] name = name + qatom[:slot] if qatom[:slot] cmd << '--update' if [:latest].include?(should) cmd += install_options if @resource[:install_options] cmd << name emerge(*cmd) end def uninstall should = @resource.should(:ensure) cmd = %w{--rage-clean} name = qatom[:category] ? "#{qatom[:category]}/#{qatom[:pn]}" : qatom[:pn] name = qatom[:pfx] + name if qatom[:pfx] name = name + '-' + qatom[:pv] if qatom[:pv] name = name + '-' + qatom[:pr] if qatom[:pr] name = name + qatom[:slot] if qatom[:slot] cmd += uninstall_options if @resource[:uninstall_options] cmd << name if [:purged].include?(should) Puppet::Util.withenv :CONFIG_PROTECT => "-*" do emerge(*cmd) end else emerge(*cmd) end end def reinstall self.install end def update self.install end def qatom output_format = self.qatom_output_format result_format = self.qatom_result_format result_fields = self.qatom_result_fields @atom ||= begin package_info = {} # do the search search_output = qatom_bin(*([@resource[:name], '--format', output_format])) # verify if the search found anything match = result_format.match(search_output) if match result_fields.zip(match.captures) do |field, value| # some fields can be empty or (null) (if we are not passed a category in the package name for instance) if value == '(null)' || value == '<unset>' package_info[field] = nil elsif !value or value.empty? package_info[field] = nil else package_info[field] = value end end end @atom = package_info rescue Puppet::ExecutionFailure => detail raise Puppet::Error.new(detail) end end def qatom_output_format '"[%[CATEGORY]] [%[PN]] [%[PV]] [%[PR]] [%[SLOT]] [%[pfx]] [%[sfx]]"' end def qatom_result_format /^\"\[(\S*)\]\s+\[(\S*)\]\s+\[(\S*)\]\s+\[(\S*)\]\s+\[(\S*)\]\s+\[(\S*)\]\s+\[(\S*)\](.*)\"$/ end def qatom_result_fields [:category, :pn, :pv, :pr, :slot, :pfx, :sfx] end def self.get_sets @sets ||= begin @sets = emerge(*(['--list-sets'])) end end def query limit = self.class.eix_limit result_format = self.class.eix_result_format result_fields = self.class.eix_result_fields version_format = self.class.eix_version_format slot_versions_format = self.class.eix_slot_versions_format installed_versions_format = self.class.eix_installed_versions_format installable_versions_format = self.class.eix_install_versions_format search_field = qatom[:category] ? '--category-name' : '--name' search_value = qatom[:category] ? "#{qatom[:category]}/#{qatom[:pn]}" : qatom[:pn] @eix_result ||= begin # package sets package_sets = [] self.class.get_sets.each_line do |package_set| package_sets << package_set.to_s.strip end if @resource[:name].match(/^@/) if package_sets.include?(@resource[:name][1..-1].to_s) return({:name => "#{@resource[:name]}", :ensure => '9999', :version_available => nil, :installed_versions => nil, :installable_versions => "9999,"}) end end eix_file = File.directory?('/var/cache/eix') ? '/var/cache/eix/portage.eix' : '/var/cache/eix' update_eix if !FileUtils.uptodate?(eix_file, %w{/usr/bin/eix /usr/portage/metadata/timestamp}) search_output = nil Puppet::Util.withenv :EIX_LIMIT => limit, :LASTVERSION => version_format, :LASTSLOTVERSIONS => slot_versions_format, :INSTALLEDVERSIONS => installed_versions_format, :STABLEVERSIONS => installable_versions_format do search_output = eix(*(self.class.eix_search_arguments + ['--exact',search_field,search_value])) end packages = [] search_output.each_line do |search_result| match = result_format.match(search_result) if match package = {} result_fields.zip(match.captures) do |field, value| package[field] = value unless !value or value.empty? end # dev-lang python [3.4.5] [3.5.2] [2.7.12:2.7,3.4.5:3.4] [2.7.12:2.7,3.4.5:3.4,3.5.2:3.5] https://www.python.org/ An interpreted, interactive, object-oriented programming language # version_available is what we CAN install / update to # ensure is what is currently installed # This DOES NOT choose to install/upgrade or not, just provides current info # prefer checking versions to slots as versions are finer grained if qatom[:pv] package[:version_available] = eix_get_version_for_versions(package[:installable_versions], qatom[:pv]) package[:ensure] = eix_get_version_for_versions(package[:installed_versions], qatom[:pv]) elsif qatom[:slot] package[:version_available] = eix_get_version_for_slot(package[:slot_versions_available], qatom[:slot]) package[:ensure] = eix_get_version_for_slot(package[:installed_slots], qatom[:slot]) end package[:ensure] = package[:ensure] ? package[:ensure] : :absent packages << package end end case packages.size when 0 raise Puppet::Error.new(_("No package found with the specified name [%{name}]") % { name: @resource[:name] }) when 1 @eix_result = packages[0] else raise Puppet::Error.new(_("More than one package with the specified name [%{search_value}], please use the category parameter to disambiguate") % { search_value: search_value }) end rescue Puppet::ExecutionFailure => detail raise Puppet::Error.new(detail) end end def latest self.query[:version_available] end private def eix_get_version_for_versions(versions, target) # [2.7.10-r1,2.7.12,3.4.3-r1,3.4.5,3.5.2] 3.5.2 return nil if versions.nil? versions = versions.split(',') # [2.7.10-r1 2.7.12 3.4.3-r1 3.4.5 3.5.2] versions.find { |version| version == target } # 3.5.2 end private def eix_get_version_for_slot(versions_and_slots, slot) # [2.7.12:2.7 3.4.5:3.4 3.5.2:3.5] 3.5 return nil if versions_and_slots.nil? versions_and_slots = versions_and_slots.split(',') # [2.7.12:2.7 3.4.5:3.4 3.5.2:3.5] versions_and_slots.map! { |version_and_slot| version_and_slot.split(':') } # [2.7.12: 2.7 # 3.4.5: 3.4 # 3.5.2: 3.5] version_for_slot = versions_and_slots.find { |version_and_slot| version_and_slot.last == slot[1..-1] } # [3.5.2: 3.5] version_for_slot.first if version_for_slot # 3.5.2 end def self.eix_search_format "'<category> <name> [<installedversions:LASTVERSION>] [<bestversion:LASTVERSION>] [<installedversions:LASTSLOTVERSIONS>] [<installedversions:INSTALLEDVERSIONS>] [<availableversions:STABLEVERSIONS>] [<bestslotversions:LASTSLOTVERSIONS>] <homepage> <description>\n'" end def self.eix_result_format /^(\S+)\s+(\S+)\s+\[(\S*)\]\s+\[(\S*)\]\s+\[(\S*)\]\s+\[(\S*)\]\s+\[(\S*)\]\s+\[(\S*)\]\s+(\S+)\s+(.*)$/ end def self.eix_result_fields # ensure:[3.4.5], version_available:[3.5.2], installed_slots:[2.7.12:2.7,3.4.5:3.4], installable_versions:[2.7.10-r1,2.7.12,3.4.3-r1,3.4.5,3.5.2] slot_versions_available:[2.7.12:2.7,3.4.5:3.4,3.5.2:3.5] [:category, :name, :ensure, :version_available, :installed_slots, :installed_versions, :installable_versions, :slot_versions_available, :vendor, :description] end def self.eix_version_format '{last}<version>{}' end def self.eix_slot_versions_format '{!first},{}<version>:<slot>' end def self.eix_installed_versions_format '{!first},{}<version>' end def self.eix_install_versions_format '{!first}{!last},{}{}{isstable}<version>{}' end def self.eix_limit '0' end def self.eix_search_arguments ['--nocolor', '--pure-packages', '--format', self.eix_search_format] end def install_options join_options(@resource[:install_options]) end def uninstall_options join_options(@resource[:uninstall_options]) end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/puppet_gem.rb���������������������������������������������0000644�0052762�0001160�00000001156�13417161721�023017� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/package' Puppet::Type.type(:package).provide :puppet_gem, :parent => :gem do desc "Puppet Ruby Gem support. This provider is useful for managing gems needed by the ruby provided in the puppet-agent package." has_feature :versionable, :install_options, :uninstall_options if Puppet.features.microsoft_windows? # On windows, we put our ruby ahead of anything that already # existed on the system PATH. This means that we do not need to # sort out the absolute path. commands :gemcmd => "gem" else commands :gemcmd => "/opt/puppetlabs/puppet/bin/gem" end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/rpm.rb����������������������������������������������������0000644�0052762�0001160�00000030130�13417161721�021442� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/package' # RPM packaging. Should work anywhere that has rpm installed. Puppet::Type.type(:package).provide :rpm, :source => :rpm, :parent => Puppet::Provider::Package do desc "RPM packaging support; should work anywhere with a working `rpm` binary. This provider supports the `install_options` and `uninstall_options` attributes, which allow command-line flags to be passed to rpm. These options should be specified as a string (e.g. '--flag'), a hash (e.g. {'--flag' => 'value'}), or an array where each element is either a string or a hash." has_feature :versionable has_feature :install_options has_feature :uninstall_options has_feature :virtual_packages # Note: self:: is required here to keep these constants in the context of what will # eventually become this Puppet::Type::Package::ProviderRpm class. # The query format by which we identify installed packages self::NEVRA_FORMAT = %Q{%{NAME} %|EPOCH?{%{EPOCH}}:{0}| %{VERSION} %{RELEASE} %{ARCH}\\n} self::NEVRA_REGEX = %r{^'?(\S+) (\S+) (\S+) (\S+) (\S+)$} self::NEVRA_FIELDS = [:name, :epoch, :version, :release, :arch] ARCH_LIST = [ 'noarch', 'i386', 'i686', 'ppc', 'ppc64', 'armv3l', 'armv4b', 'armv4l', 'armv4tl', 'armv5tel', 'armv5tejl', 'armv6l', 'armv7l', 'm68kmint', 's390', 's390x', 'ia64', 'x86_64', 'sh3', 'sh4', ] ARCH_REGEX = Regexp.new(ARCH_LIST.join('|\.')) commands :rpm => "rpm" if command('rpm') confine :true => begin rpm('--version') rescue Puppet::ExecutionFailure false else true end end def self.current_version return @current_version unless @current_version.nil? output = rpm "--version" @current_version = output.gsub('RPM version ', '').strip end # rpm < 4.1 does not support --nosignature def self.nosignature '--nosignature' unless Puppet::Util::Package.versioncmp(current_version, '4.1') < 0 end # rpm < 4.0.2 does not support --nodigest def self.nodigest '--nodigest' unless Puppet::Util::Package.versioncmp(current_version, '4.0.2') < 0 end def self.instances packages = [] # list out all of the packages begin execpipe("#{command(:rpm)} -qa #{nosignature} #{nodigest} --qf '#{self::NEVRA_FORMAT}'") { |process| # now turn each returned line into a package object process.each_line { |line| hash = nevra_to_hash(line) packages << new(hash) unless hash.empty? } } rescue Puppet::ExecutionFailure raise Puppet::Error, _("Failed to list packages"), $!.backtrace end packages end # Find the fully versioned package name and the version alone. Returns # a hash with entries :instance => fully versioned package name, and # :ensure => version-release def query #NOTE: Prior to a fix for issue 1243, this method potentially returned a cached value #IF YOU CALL THIS METHOD, IT WILL CALL RPM #Use get(:property) to check if cached values are available cmd = ["-q", @resource[:name], "#{self.class.nosignature}", "#{self.class.nodigest}", "--qf", "'#{self.class::NEVRA_FORMAT}'"] begin output = rpm(*cmd) rescue Puppet::ExecutionFailure return nil unless @resource.allow_virtual? # rpm -q exits 1 if package not found # retry the query for virtual packages cmd << '--whatprovides' begin output = rpm(*cmd) rescue Puppet::ExecutionFailure # couldn't find a virtual package either return nil end end # FIXME: We could actually be getting back multiple packages # for multilib and this will only return the first such package @property_hash.update(self.class.nevra_to_hash(output)) @property_hash.dup end # Here we just retrieve the version from the file specified in the source. def latest unless source = @resource[:source] @resource.fail _("RPMs must specify a package source") end cmd = [command(:rpm), "-q", "--qf", "'#{self.class::NEVRA_FORMAT}'", "-p", source] h = self.class.nevra_to_hash(execute(cmd)) h[:ensure] rescue Puppet::ExecutionFailure => e raise Puppet::Error, e.message, e.backtrace end def install unless source = @resource[:source] @resource.fail _("RPMs must specify a package source") end version = @property_hash[:ensure] # RPM gets upset if you try to install an already installed package return if @resource.should(:ensure) == version || (@resource.should(:ensure) == :latest && version == latest) flag = ["-i"] flag = ["-U", "--oldpackage"] if version && (version != :absent && version != :purged) flag += install_options if resource[:install_options] rpm flag, source end def uninstall query if get(:arch) == :absent nvr = "#{get(:name)}-#{get(:version)}-#{get(:release)}" arch = ".#{get(:arch)}" # If they specified an arch in the manifest, erase that Otherwise, # erase the arch we got back from the query. If multiple arches are # installed and only the package name is specified (without the # arch), this will uninstall all of them on successive runs of the # client, one after the other # version of RPM prior to 4.2.1 can't accept the architecture as # part of the package name. unless Puppet::Util::Package.versioncmp(self.class.current_version, '4.2.1') < 0 if @resource[:name][-arch.size, arch.size] == arch nvr += arch else nvr += ".#{get(:arch)}" end end flag = ['-e'] flag += uninstall_options if resource[:uninstall_options] rpm flag, nvr end def update self.install end def install_options join_options(resource[:install_options]) end def uninstall_options join_options(resource[:uninstall_options]) end # This is an attempt at implementing RPM's # lib/rpmvercmp.c rpmvercmp(a, b) in Ruby. # # Some of the things in here look REALLY # UGLY and/or arbitrary. Our goal is to # match how RPM compares versions, quirks # and all. # # I've kept a lot of C-like string processing # in an effort to keep this as identical to RPM # as possible. # # returns 1 if str1 is newer than str2, # 0 if they are identical # -1 if str1 is older than str2 def rpmvercmp(str1, str2) return 0 if str1 == str2 front_strip_re = /^[^A-Za-z0-9~]+/ while str1.length > 0 or str2.length > 0 # trim anything that's in front_strip_re and != '~' off the beginning of each string str1 = str1.gsub(front_strip_re, '') str2 = str2.gsub(front_strip_re, '') # "handle the tilde separator, it sorts before everything else" if /^~/.match(str1) && /^~/.match(str2) # if they both have ~, strip it str1 = str1[1..-1] str2 = str2[1..-1] next elsif /^~/.match(str1) return -1 elsif /^~/.match(str2) return 1 end break if str1.length == 0 or str2.length == 0 # "grab first completely alpha or completely numeric segment" isnum = false # if the first char of str1 is a digit, grab the chunk of continuous digits from each string if /^[0-9]+/.match(str1) if str1 =~ /^[0-9]+/ segment1 = $~.to_s str1 = $~.post_match else segment1 = '' end if str2 =~ /^[0-9]+/ segment2 = $~.to_s str2 = $~.post_match else segment2 = '' end isnum = true # else grab the chunk of continuous alphas from each string (which may be '') else if str1 =~ /^[A-Za-z]+/ segment1 = $~.to_s str1 = $~.post_match else segment1 = '' end if str2 =~ /^[A-Za-z]+/ segment2 = $~.to_s str2 = $~.post_match else segment2 = '' end end # if the segments we just grabbed from the strings are different types (i.e. one numeric one alpha), # where alpha also includes ''; "numeric segments are always newer than alpha segments" if segment2.length == 0 return 1 if isnum return -1 end if isnum # "throw away any leading zeros - it's a number, right?" segment1 = segment1.gsub(/^0+/, '') segment2 = segment2.gsub(/^0+/, '') # "whichever number has more digits wins" return 1 if segment1.length > segment2.length return -1 if segment1.length < segment2.length end # "strcmp will return which one is greater - even if the two segments are alpha # or if they are numeric. don't return if they are equal because there might # be more segments to compare" rc = segment1 <=> segment2 return rc if rc != 0 end #end while loop # if we haven't returned anything yet, "whichever version still has characters left over wins" if str1.length > str2.length return 1 elsif str1.length < str2.length return -1 else return 0 end end def insync?(is) return false if [:purged, :absent].include?(is) should = resource[:ensure] 0 == rpm_compareEVR(rpm_parse_evr(should), rpm_parse_evr(is)) end # parse a rpm "version" specification # this re-implements rpm's # rpmUtils.miscutils.stringToVersion() in ruby def rpm_parse_evr(s) ei = s.index(':') if ei e = s[0,ei] s = s[ei+1,s.length] else e = nil end begin e = String(Integer(e)) rescue # If there are non-digits in the epoch field, default to nil e = nil end ri = s.index('-') if ri v = s[0,ri] r = s[ri+1,s.length] if arch = r.scan(ARCH_REGEX)[0] a = arch.gsub(/\./, '') r.gsub!(ARCH_REGEX, '') end else v = s r = nil end return { :epoch => e, :version => v, :release => r, :arch => a } end # how rpm compares two package versions: # rpmUtils.miscutils.compareEVR(), which massages data types and then calls # rpm.labelCompare(), found in rpm.git/python/header-py.c, which # sets epoch to 0 if null, then compares epoch, then ver, then rel # using compare_values() and returns the first non-0 result, else 0. # This function combines the logic of compareEVR() and labelCompare(). # # "version_should" can be v, v-r, or e:v-r. # "version_is" will always be at least v-r, can be e:v-r def rpm_compareEVR(should_hash, is_hash) # pass on to rpm labelCompare if !should_hash[:epoch].nil? rc = compare_values(should_hash[:epoch], is_hash[:epoch]) return rc unless rc == 0 end rc = compare_values(should_hash[:version], is_hash[:version]) return rc unless rc == 0 # here is our special case, PUP-1244. # if should_hash[:release] is nil (not specified by the user), # and comparisons up to here are equal, return equal. We need to # evaluate to whatever level of detail the user specified, so we # don't end up upgrading or *downgrading* when not intended. # # This should NOT be triggered if we're trying to ensure latest. return 0 if should_hash[:release].nil? rc = compare_values(should_hash[:release], is_hash[:release]) return rc end # this method is a native implementation of the # compare_values function in rpm's python bindings, # found in python/header-py.c, as used by rpm. def compare_values(s1, s2) if s1.nil? && s2.nil? return 0 elsif ( not s1.nil? ) && s2.nil? return 1 elsif s1.nil? && (not s2.nil?) return -1 end return rpmvercmp(s1, s2) end private # @param line [String] one line of rpm package query information # @return [Hash] of NEVRA_FIELDS strings parsed from package info # or an empty hash if we failed to parse # @api private def self.nevra_to_hash(line) line.strip! hash = {} if match = self::NEVRA_REGEX.match(line) self::NEVRA_FIELDS.zip(match.captures) { |f, v| hash[f] = v } hash[:provider] = self.name hash[:ensure] = "#{hash[:version]}-#{hash[:release]}" hash[:ensure].prepend("#{hash[:epoch]}:") if hash[:epoch] != '0' else Puppet.debug("Failed to match rpm line #{line}") end return hash end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/package/yum.rb����������������������������������������������������0000644�0052762�0001160�00000025270�13417161721�021467� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.type(:package).provide :yum, :parent => :rpm, :source => :rpm do desc "Support via `yum`. Using this provider's `uninstallable` feature will not remove dependent packages. To remove dependent packages with this provider use the `purgeable` feature, but note this feature is destructive and should be used with the utmost care. This provider supports the `install_options` attribute, which allows command-line flags to be passed to yum. These options should be specified as a string (e.g. '--flag'), a hash (e.g. {'--flag' => 'value'}), or an array where each element is either a string or a hash." has_feature :install_options, :versionable, :virtual_packages commands :cmd => "yum", :rpm => "rpm" if command('rpm') confine :true => begin rpm('--version') rescue Puppet::ExecutionFailure false else true end end defaultfor :osfamily => :redhat def self.prefetch(packages) raise Puppet::Error, _("The yum provider can only be used as root") if Process.euid != 0 super end # Retrieve the latest package version information for a given package name # and combination of repos to enable and disable. # # @note If multiple package versions are defined (such as in the case where a # package is built for multiple architectures), the first package found # will be used. # # @api private # @param package [String] The name of the package to query # @param disablerepo [Array<String>] A list of repositories to disable for this query # @param enablerepo [Array<String>] A list of repositories to enable for this query # @param disableexcludes [Array<String>] A list of repository excludes to disable for this query # @return [Hash<Symbol, String>] def self.latest_package_version(package, disablerepo, enablerepo, disableexcludes) key = [disablerepo, enablerepo, disableexcludes] @latest_versions ||= {} if @latest_versions[key].nil? @latest_versions[key] = check_updates(disablerepo, enablerepo, disableexcludes) end if @latest_versions[key][package] @latest_versions[key][package].first end end # Search for all installed packages that have newer versions, given a # combination of repositories to enable and disable. # # @api private # @param disablerepo [Array<String>] A list of repositories to disable for this query # @param enablerepo [Array<String>] A list of repositories to enable for this query # @param disableexcludes [Array<String>] A list of repository excludes to disable for this query # @return [Hash<String, Array<Hash<String, String>>>] All packages that were # found with a list of found versions for each package. def self.check_updates(disablerepo, enablerepo, disableexcludes) args = [command(:cmd), 'check-update'] args.concat(disablerepo.map { |repo| ["--disablerepo=#{repo}"] }.flatten) args.concat(enablerepo.map { |repo| ["--enablerepo=#{repo}"] }.flatten) args.concat(disableexcludes.map { |repo| ["--disableexcludes=#{repo}"] }.flatten) output = Puppet::Util::Execution.execute(args, :failonfail => false, :combine => false) updates = {} if output.exitstatus == 100 updates = parse_updates(output) elsif output.exitstatus == 0 self.debug "#{command(:cmd)} check-update exited with 0; no package updates available." else self.warning _("Could not check for updates, '%{cmd} check-update' exited with %{status}") % { cmd: command(:cmd), status: output.exitstatus } end updates end def self.parse_updates(str) # Strip off all content before the first blank line body = str.partition(/^\s*\n/m).last updates = Hash.new { |h, k| h[k] = [] } body.split.each_slice(3) do |tuple| break if tuple[0] =~ /^(Obsoleting|Security:|Update)/ break unless tuple[1].match(/^(?:(\d+):)?(\S+)-(\S+)$/) hash = update_to_hash(*tuple[0..1]) # Create entries for both the package name without a version and a # version since yum considers those as mostly interchangeable. short_name = hash[:name] long_name = "#{hash[:name]}.#{hash[:arch]}" updates[short_name] << hash updates[long_name] << hash end updates end def self.update_to_hash(pkgname, pkgversion) # The pkgname string has two parts: name, and architecture. Architecture # is the portion of the string following the last "." character. All # characters preceding the final dot are the package name. Parse out # these two pieces of component data. name, _, arch = pkgname.rpartition('.') if name.empty? raise _("Failed to parse package name and architecture from '%{pkgname}'") % { pkgname: pkgname } end match = pkgversion.match(/^(?:(\d+):)?(\S+)-(\S+)$/) epoch = match[1] || '0' version = match[2] release = match[3] { :name => name, :epoch => epoch, :version => version, :release => release, :arch => arch, } end def self.clear @latest_versions = nil end def self.error_level '0' end def self.update_command # In yum both `upgrade` and `update` can be used to update packages # `yum upgrade` == `yum --obsoletes update` # Quote the DNF docs: # "Yum does this if its obsoletes config option is enabled but # the behavior is not properly documented and can be harmful." # So we'll stick with the safer option # If a user wants to remove obsoletes, they can use { :install_options => '--obsoletes' } # More detail here: https://bugzilla.redhat.com/show_bug.cgi?id=1096506 'update' end def install wanted = @resource[:name] error_level = self.class.error_level update_command = self.class.update_command # If not allowing virtual packages, do a query to ensure a real package exists unless @resource.allow_virtual? execute([command(:cmd), '-d', '0', '-e', error_level, '-y', install_options, :list, wanted].compact) end should = @resource.should(:ensure) self.debug "Ensuring => #{should}" operation = :install case should when :latest current_package = self.query if current_package && !current_package[:ensure].to_s.empty? operation = update_command self.debug "Ensuring latest, so using #{operation}" else self.debug "Ensuring latest, but package is absent, so using #{:install}" operation = :install end should = nil when true, :present, :installed # if we have been given a source and we were not asked for a specific # version feed it to yum directly if @resource[:source] wanted = @resource[:source] self.debug "Installing directly from #{wanted}" end should = nil when false,:absent # pass should = nil else if @resource[:source] # An explicit source was supplied, which means we're ensuring a specific # version, and also supplying the path to a package that supplies that # version. wanted = @resource[:source] self.debug "Installing directly from #{wanted}" else # No explicit source was specified, so add the package version wanted += "-#{should}" if wanted.scan(ARCH_REGEX) self.debug "Detected Arch argument in package! - Moving arch to end of version string" wanted.gsub!(/(.+)(#{ARCH_REGEX})(.+)/,'\1\3\2') end end current_package = self.query if current_package if rpm_compareEVR(rpm_parse_evr(should), rpm_parse_evr(current_package[:ensure])) < 0 self.debug "Downgrading package #{@resource[:name]} from version #{current_package[:ensure]} to #{should}" operation = :downgrade elsif rpm_compareEVR(rpm_parse_evr(should), rpm_parse_evr(current_package[:ensure])) > 0 self.debug "Upgrading package #{@resource[:name]} from version #{current_package[:ensure]} to #{should}" operation = update_command end end end # Yum on el-4 and el-5 returns exit status 0 when trying to install a package it doesn't recognize; # ensure we capture output to check for errors. no_debug = if Facter.value(:operatingsystemmajrelease).to_i > 5 then ["-d", "0"] else [] end command = [command(:cmd)] + no_debug + ["-e", error_level, "-y", install_options, operation, wanted].compact output = execute(command) if output =~ /^No package #{wanted} available\.$/ raise Puppet::Error, _("Could not find package %{wanted}") % { wanted: wanted } end # If a version was specified, query again to see if it is a matching version if should is = self.query raise Puppet::Error, _("Could not find package %{name}") % { name: self.name } unless is # FIXME: Should we raise an exception even if should == :latest # and yum updated us to a version other than @param_hash[:ensure] ? vercmp_result = rpm_compareEVR(rpm_parse_evr(should), rpm_parse_evr(is[:ensure])) raise Puppet::Error, _("Failed to update to version %{should}, got version %{version} instead") % { should: should, version: is[:ensure] } if vercmp_result != 0 end end # What's the latest package version available? def latest upd = self.class.latest_package_version(@resource[:name], disablerepo, enablerepo, disableexcludes) unless upd.nil? # FIXME: there could be more than one update for a package # because of multiarch return "#{upd[:epoch]}:#{upd[:version]}-#{upd[:release]}" else # Yum didn't find updates, pretend the current version is the latest version = properties[:ensure] raise Puppet::DevError, _("Tried to get latest on a missing package") if version == :absent || version == :purged return version end end def update # Install in yum can be used for update, too self.install end def purge execute([command(:cmd), "-y", :erase, @resource[:name]]) end private def enablerepo scan_options(resource[:install_options], '--enablerepo') end def disablerepo scan_options(resource[:install_options], '--disablerepo') end def disableexcludes scan_options(resource[:install_options], '--disableexcludes') end # Scan a structure that looks like the package type 'install_options' # structure for all hashes that have a specific key. # # @api private # @param options [Array<String | Hash>, nil] The options structure. If the # options are nil an empty array will be returned. # @param key [String] The key to look for in all contained hashes # @return [Array<String>] All hash values with the given key. def scan_options(options, key) return [] if options.nil? options.inject([]) do |repos, opt| if opt.is_a? Hash and opt[key] repos << opt[key] end repos end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/parsedfile.rb�����������������������������������������������������0000644�0052762�0001160�00000035754�13417161721�021410� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet' require 'puppet/util/filetype' require 'puppet/util/fileparsing' # This provider can be used as the parent class for a provider that # parses and generates files. Its content must be loaded via the # 'prefetch' method, and the file will be written when 'flush' is called # on the provider instance. At this point, the file is written once # for every provider instance. # # Once the provider prefetches the data, it's the resource's job to copy # that data over to the @is variables. # # NOTE: The prefetch method swallows FileReadErrors by treating the # corresponding target as an empty file. If you would like to turn this # behavior off, then set the raise_prefetch_errors class variable to # true. Doing so will error all resources associated with the failed # target. class Puppet::Provider::ParsedFile < Puppet::Provider extend Puppet::Util::FileParsing class << self attr_accessor :default_target, :target, :raise_prefetch_errors end attr_accessor :property_hash def self.clean(hash) newhash = hash.dup [:record_type, :on_disk].each do |p| newhash.delete(p) if newhash.include?(p) end newhash end def self.clear @target_objects.clear @records.clear end def self.filetype @filetype ||= Puppet::Util::FileType.filetype(:flat) end def self.filetype=(type) if type.is_a?(Class) @filetype = type elsif klass = Puppet::Util::FileType.filetype(type) @filetype = klass else raise ArgumentError, _("Invalid filetype %{type}") % { type: type } end end # Flush all of the targets for which there are modified records. The only # reason we pass a record here is so that we can add it to the stack if # necessary -- it's passed from the instance calling 'flush'. def self.flush(record) # Make sure this record is on the list to be flushed. unless record[:on_disk] record[:on_disk] = true @records << record # If we've just added the record, then make sure our # target will get flushed. modified(record[:target] || default_target) end return unless defined?(@modified) and ! @modified.empty? flushed = [] begin @modified.sort { |a,b| a.to_s <=> b.to_s }.uniq.each do |target| Puppet.debug "Flushing #{@resource_type.name} provider target #{target}" flushed << target flush_target(target) end ensure @modified.reject! { |t| flushed.include?(t) } end end # Make sure our file is backed up, but only back it up once per transaction. # We cheat and rely on the fact that @records is created on each prefetch. def self.backup_target(target) return nil unless target_object(target).respond_to?(:backup) @backup_stats ||= {} return nil if @backup_stats[target] == @records.object_id target_object(target).backup @backup_stats[target] = @records.object_id end # Flush all of the records relating to a specific target. def self.flush_target(target) if @raise_prefetch_errors && @failed_prefetch_targets.key?(target) raise Puppet::Error, _("Failed to read %{target}'s records when prefetching them. Reason: %{detail}") % { target: target, detail: @failed_prefetch_targets[target] } end backup_target(target) records = target_records(target).reject { |r| r[:ensure] == :absent } target_object(target).write(to_file(records)) end # Return the header placed at the top of each generated file, warning # users that modifying this file manually is probably a bad idea. def self.header %{# HEADER: This file was autogenerated at #{Time.now} # HEADER: by puppet. While it can still be managed manually, it # HEADER: is definitely not recommended.\n} end # An optional regular expression matched by third party headers. # # For example, this can be used to filter the vixie cron headers as # erroneously exported by older cron versions. # # @api private # @abstract Providers based on ParsedFile may implement this to make it # possible to identify a header maintained by a third party tool. # The provider can then allow that header to remain near the top of the # written file, or remove it after composing the file content. # If implemented, the function must return a Regexp object. # The expression must be tailored to match exactly one third party header. # @see drop_native_header # @note When specifying regular expressions in multiline mode, avoid # greedy repetitions such as '.*' (use .*? instead). Otherwise, the # provider may drop file content between sparse headers. def self.native_header_regex nil end # How to handle third party headers. # @api private # @abstract Providers based on ParsedFile that make use of the support for # third party headers may override this method to return +true+. # When this is done, headers that are matched by the native_header_regex # are not written back to disk. # @see native_header_regex def self.drop_native_header false end # Add another type var. def self.initvars @records = [] @target_objects = {} # Hash of <target> => <failure reason>. @failed_prefetch_targets = {} @raise_prefetch_errors = false @target = nil # Default to flat files @filetype ||= Puppet::Util::FileType.filetype(:flat) super end # Return a list of all of the records we can find. def self.instances targets.collect do |target| prefetch_target(target) end.flatten.reject { |r| skip_record?(r) }.collect do |record| new(record) end end # Override the default method with a lot more functionality. def self.mk_resource_methods [resource_type.validproperties, resource_type.parameters].flatten.each do |attr| attr = attr.intern define_method(attr) do # If it's not a valid field for this record type (which can happen # when different platforms support different fields), then just # return the should value, so the resource shuts up. if @property_hash[attr] or self.class.valid_attr?(self.class.name, attr) @property_hash[attr] || :absent else if defined?(@resource) @resource.should(attr) else nil end end end define_method(attr.to_s + "=") do |val| mark_target_modified @property_hash[attr] = val end end end # Always make the resource methods. def self.resource_type=(resource) super mk_resource_methods end # Mark a target as modified so we know to flush it. This only gets # used within the attr= methods. def self.modified(target) @modified ||= [] @modified << target unless @modified.include?(target) end # Retrieve all of the data from disk. There are three ways to know # which files to retrieve: We might have a list of file objects already # set up, there might be instances of our associated resource and they # will have a path parameter set, and we will have a default path # set. We need to turn those three locations into a list of files, # prefetch each one, and make sure they're associated with each appropriate # resource instance. def self.prefetch(resources = nil) # Reset the record list. @records = prefetch_all_targets(resources) match_providers_with_resources(resources) end # Match a list of catalog resources with provider instances # # @api private # # @param [Array<Puppet::Resource>] resources A list of resources using this class as a provider def self.match_providers_with_resources(resources) return unless resources matchers = resources.dup @records.each do |record| # Skip things like comments and blank lines next if skip_record?(record) if (resource = resource_for_record(record, resources)) resource.provider = new(record) elsif respond_to?(:match) if resource = match(record, matchers) matchers.delete(resource.title) record[:name] = resource[:name] resource.provider = new(record) end end end end # Look up a resource based on a parsed file record # # @api private # # @param [Hash<Symbol, Object>] record # @param [Array<Puppet::Resource>] resources # # @return [Puppet::Resource, nil] The resource if found, else nil def self.resource_for_record(record, resources) name = record[:name] if name resources[name] end end def self.prefetch_all_targets(resources) records = [] targets(resources).each do |target| records += prefetch_target(target) end records end # Prefetch an individual target. def self.prefetch_target(target) begin target_records = retrieve(target) rescue Puppet::Util::FileType::FileReadError => detail if @raise_prefetch_errors # We will raise an error later in flush_target. This way, # only the resources linked to our target will fail # evaluation. @failed_prefetch_targets[target] = detail.to_s else puts detail.backtrace if Puppet[:trace] Puppet.err _("Could not prefetch %{resource} provider '%{name}' target '%{target}': %{detail}. Treating as empty") % { resource: self.resource_type.name, name: self.name, target: target, detail: detail } end target_records = [] end target_records.each do |r| r[:on_disk] = true r[:target] = target r[:ensure] = :present end target_records = prefetch_hook(target_records) if respond_to?(:prefetch_hook) raise Puppet::DevError, _("Prefetching %{target} for provider %{name} returned nil") % { target: target, name: self.name } unless target_records target_records end # Is there an existing record with this name? def self.record?(name) return nil unless @records @records.find { |r| r[:name] == name } end # Retrieve the text for the file. Returns nil in the unlikely # event that it doesn't exist. def self.retrieve(path) # XXX We need to be doing something special here in case of failure. text = target_object(path).read if text.nil? or text == "" # there is no file return [] else # Set the target, for logging. old = @target begin @target = path return self.parse(text) rescue Puppet::Error => detail detail.file = @target if detail.respond_to?(:file=) raise detail ensure @target = old end end end # Should we skip the record? Basically, we skip text records. # This is only here so subclasses can override it. def self.skip_record?(record) record_type(record[:record_type]).text? end # The mode for generated files if they are newly created. # No mode will be set on existing files. # # @abstract Providers inheriting parsedfile can override this method # to provide a mode. The value should be suitable for File.chmod def self.default_mode nil end # Initialize the object if necessary. def self.target_object(target) # only send the default mode if the actual provider defined it, # because certain filetypes (e.g. the crontab variants) do not # expect it in their initialize method if default_mode @target_objects[target] ||= filetype.new(target, default_mode) else @target_objects[target] ||= filetype.new(target) end @target_objects[target] end # Find all of the records for a given target def self.target_records(target) @records.find_all { |r| r[:target] == target } end # Find a list of all of the targets that we should be reading. This is # used to figure out what targets we need to prefetch. def self.targets(resources = nil) targets = [] # First get the default target raise Puppet::DevError, _("Parsed Providers must define a default target") unless self.default_target targets << self.default_target # Then get each of the file objects targets += @target_objects.keys # Lastly, check the file from any resource instances if resources resources.each do |name, resource| if value = resource.should(:target) targets << value end end end targets.uniq.compact end # Compose file contents from the set of records. # # If self.native_header_regex is not nil, possible vendor headers are # identified by matching the return value against the expression. # If one (or several consecutive) such headers, are found, they are # either moved in front of the self.header if self.drop_native_header # is false (this is the default), or removed from the return value otherwise. # # @api private def self.to_file(records) text = super if native_header_regex and (match = text.match(native_header_regex)) if drop_native_header # concatenate the text in front of and after the native header text = match.pre_match + match.post_match else native_header = match[0] return native_header + header + match.pre_match + match.post_match end end header + text end def create @resource.class.validproperties.each do |property| if value = @resource.should(property) @property_hash[property] = value end end mark_target_modified (@resource.class.name.to_s + "_created").intern end def destroy # We use the method here so it marks the target as modified. self.ensure = :absent (@resource.class.name.to_s + "_deleted").intern end def exists? !(@property_hash[:ensure] == :absent or @property_hash[:ensure].nil?) end # Write our data to disk. def flush # Make sure we've got a target and name set. # If the target isn't set, then this is our first modification, so # mark it for flushing. unless @property_hash[:target] @property_hash[:target] = @resource.should(:target) || self.class.default_target self.class.modified(@property_hash[:target]) end @resource.class.key_attributes.each do |attr| @property_hash[attr] ||= @resource[attr] end self.class.flush(@property_hash) end def initialize(record) super # The 'record' could be a resource or a record, depending on how the provider # is initialized. If we got an empty property hash (probably because the resource # is just being initialized), then we want to set up some defaults. @property_hash = self.class.record?(resource[:name]) || {:record_type => self.class.name, :ensure => :absent} if @property_hash.empty? end # Retrieve the current state from disk. def prefetch raise Puppet::DevError, _("Somehow got told to prefetch with no resource set") unless @resource self.class.prefetch(@resource[:name] => @resource) end def record_type @property_hash[:record_type] end private # Mark both the resource and provider target as modified. def mark_target_modified if defined?(@resource) and restarget = @resource.should(:target) and restarget != @property_hash[:target] self.class.modified(restarget) end self.class.modified(@property_hash[:target]) if @property_hash[:target] != :absent and @property_hash[:target] end end ��������������������puppet-5.5.10/lib/puppet/provider/service/����������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020374� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/service/bsd.rb����������������������������������������������������0000644�0052762�0001160�00000002556�13417161721�021474� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.type(:service).provide :bsd, :parent => :init do desc <<-EOT Generic BSD form of `init`-style service management with `rc.d`. Uses `rc.conf.d` for service enabling and disabling. EOT confine :operatingsystem => [:freebsd, :dragonfly] def rcconf_dir '/etc/rc.conf.d' end def self.defpath superclass.defpath end # remove service file from rc.conf.d to disable it def disable rcfile = File.join(rcconf_dir, @resource[:name]) File.delete(rcfile) if Puppet::FileSystem.exist?(rcfile) end # if the service file exists in rc.conf.d then it's already enabled def enabled? rcfile = File.join(rcconf_dir, @resource[:name]) return :true if Puppet::FileSystem.exist?(rcfile) :false end # enable service by creating a service file under rc.conf.d with the # proper contents def enable Dir.mkdir(rcconf_dir) if not Puppet::FileSystem.exist?(rcconf_dir) rcfile = File.join(rcconf_dir, @resource[:name]) File.open(rcfile, File::WRONLY | File::APPEND | File::CREAT, 0644) { |f| f << "%s_enable=\"YES\"\n" % @resource[:name] } end # Override stop/start commands to use one<cmd>'s and the avoid race condition # where provider tries to stop/start the service before it is enabled def startcmd [self.initscript, :onestart] end def stopcmd [self.initscript, :onestop] end end ��������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/service/daemontools.rb��������������������������������������������0000644�0052762�0001160�00000012275�13417161721�023247� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Daemontools service management # # author Brice Figureau <brice-puppet@daysofwonder.com> Puppet::Type.type(:service).provide :daemontools, :parent => :base do desc <<-'EOT' Daemontools service management. This provider manages daemons supervised by D.J. Bernstein daemontools. When detecting the service directory it will check, in order of preference: * `/service` * `/etc/service` * `/var/lib/svscan` The daemon directory should be in one of the following locations: * `/var/lib/service` * `/etc` ...or this can be overridden in the resource's attributes: service { 'myservice': provider => 'daemontools', path => '/path/to/daemons', } This provider supports out of the box: * start/stop (mapped to enable/disable) * enable/disable * restart * status If a service has `ensure => "running"`, it will link /path/to/daemon to /path/to/service, which will automatically enable the service. If a service has `ensure => "stopped"`, it will only shut down the service, not remove the `/path/to/service` link. EOT commands :svc => "/usr/bin/svc", :svstat => "/usr/bin/svstat" class << self attr_writer :defpath # Determine the daemon path. def defpath unless @defpath ["/var/lib/service", "/etc"].each do |path| if Puppet::FileSystem.exist?(path) @defpath = path break end end raise "Could not find the daemon directory (tested [/var/lib/service,/etc])" unless @defpath end @defpath end end attr_writer :servicedir # returns all providers for all existing services in @defpath # ie enabled or not def self.instances path = self.defpath unless FileTest.directory?(path) Puppet.notice "Service path #{path} does not exist" return end # reject entries that aren't either a directory # or don't contain a run file Dir.entries(path).reject { |e| fullpath = File.join(path, e) e =~ /^\./ or ! FileTest.directory?(fullpath) or ! Puppet::FileSystem.exist?(File.join(fullpath,"run")) }.collect do |name| new(:name => name, :path => path) end end # returns the daemon dir on this node def self.daemondir self.defpath end # find the service dir on this node def servicedir unless @servicedir ["/service", "/etc/service","/var/lib/svscan"].each do |path| if Puppet::FileSystem.exist?(path) @servicedir = path break end end raise "Could not find service directory" unless @servicedir end @servicedir end # returns the full path of this service when enabled # (ie in the service directory) def service File.join(self.servicedir, resource[:name]) end # returns the full path to the current daemon directory # note that this path can be overridden in the resource # definition def daemon File.join(resource[:path], resource[:name]) end def status begin output = svstat self.service if output =~ /:\s+up \(/ return :running end rescue Puppet::ExecutionFailure => detail raise Puppet::Error.new( "Could not get status for service #{resource.ref}: #{detail}", detail) end :stopped end def setupservice if resource[:manifest] Puppet.notice "Configuring #{resource[:name]}" command = [ resource[:manifest], resource[:name] ] #texecute("setupservice", command) system("#{command}") end rescue Puppet::ExecutionFailure => detail raise Puppet::Error.new( "Cannot config #{self.service} to enable it: #{detail}", detail) end def enabled? case self.status when :running # obviously if the daemon is running then it is enabled return :true else # the service is enabled if it is linked return Puppet::FileSystem.symlink?(self.service) ? :true : :false end end def enable if ! FileTest.directory?(self.daemon) Puppet.notice "No daemon dir, calling setupservice for #{resource[:name]}" self.setupservice end if self.daemon if ! Puppet::FileSystem.symlink?(self.service) Puppet.notice "Enabling #{self.service}: linking #{self.daemon} -> #{self.service}" Puppet::FileSystem.symlink(self.daemon, self.service) end end rescue Puppet::ExecutionFailure raise Puppet::Error.new( "No daemon directory found for #{self.service}", $!) end def disable begin if ! FileTest.directory?(self.daemon) Puppet.notice "No daemon dir, calling setupservice for #{resource[:name]}" self.setupservice end if self.daemon if Puppet::FileSystem.symlink?(self.service) Puppet.notice "Disabling #{self.service}: removing link #{self.daemon} -> #{self.service}" Puppet::FileSystem.unlink(self.service) end end rescue Puppet::ExecutionFailure raise Puppet::Error.new( "No daemon directory found for #{self.service}", $!) end self.stop end def restart svc "-t", self.service end def start enable unless enabled? == :true svc "-u", self.service end def stop svc "-d", self.service end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/service/freebsd.rb������������������������������������������������0000644�0052762�0001160�00000010034�13417161721�022324� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.type(:service).provide :freebsd, :parent => :init do desc "Provider for FreeBSD and DragonFly BSD. Uses the `rcvar` argument of init scripts and parses/edits rc files." confine :operatingsystem => [:freebsd, :dragonfly] defaultfor :operatingsystem => [:freebsd, :dragonfly] def rcconf() '/etc/rc.conf' end def rcconf_local() '/etc/rc.conf.local' end def rcconf_dir() '/etc/rc.conf.d' end def self.defpath superclass.defpath end def error(msg) raise Puppet::Error, msg end # Executing an init script with the 'rcvar' argument returns # the service name, rcvar name and whether it's enabled/disabled def rcvar rcvar = execute([self.initscript, :rcvar], :failonfail => true, :combine => false, :squelch => false) rcvar = rcvar.split("\n") rcvar.delete_if {|str| str =~ /^#\s*$/} rcvar[1] = rcvar[1].gsub(/^\$/, '') rcvar end # Extract value name from service or rcvar def extract_value_name(name, rc_index, regex, regex_index) value_name = self.rcvar[rc_index] self.error("No #{name} name found in rcvar") if value_name.nil? value_name = value_name.gsub!(regex, regex_index) self.error("#{name} name is empty") if value_name.nil? self.debug("#{name} name is #{value_name}") value_name end # Extract service name def service_name extract_value_name('service', 0, /# (\S+).*/, '\1') end # Extract rcvar name def rcvar_name extract_value_name('rcvar', 1, /(.*?)(_enable)?=(.*)/, '\1') end # Extract rcvar value def rcvar_value value = self.rcvar[1] self.error("No rcvar value found in rcvar") if value.nil? value = value.gsub!(/(.*)(_enable)?="?(\w+)"?/, '\3') self.error("rcvar value is empty") if value.nil? self.debug("rcvar value is #{value}") value end # Edit rc files and set the service to yes/no def rc_edit(yesno) service = self.service_name rcvar = self.rcvar_name self.debug("Editing rc files: setting #{rcvar} to #{yesno} for #{service}") self.rc_add(service, rcvar, yesno) if not self.rc_replace(service, rcvar, yesno) end # Try to find an existing setting in the rc files # and replace the value def rc_replace(service, rcvar, yesno) success = false # Replace in all files, not just in the first found with a match [rcconf, rcconf_local, rcconf_dir + "/#{service}"].each do |filename| if Puppet::FileSystem.exist?(filename) s = File.read(filename) if s.gsub!(/^(#{rcvar}(_enable)?)=\"?(YES|NO)\"?/, "\\1=\"#{yesno}\"") File.open(filename, File::WRONLY) { |f| f << s } self.debug("Replaced in #{filename}") success = true end end end success end # Add a new setting to the rc files def rc_add(service, rcvar, yesno) append = "\# Added by Puppet\n#{rcvar}_enable=\"#{yesno}\"\n" # First, try the one-file-per-service style if Puppet::FileSystem.exist?(rcconf_dir) File.open(rcconf_dir + "/#{service}", File::WRONLY | File::APPEND | File::CREAT, 0644) { |f| f << append self.debug("Appended to #{f.path}") } else # Else, check the local rc file first, but don't create it if Puppet::FileSystem.exist?(rcconf_local) File.open(rcconf_local, File::WRONLY | File::APPEND) { |f| f << append self.debug("Appended to #{f.path}") } else # At last use the standard rc.conf file File.open(rcconf, File::WRONLY | File::APPEND | File::CREAT, 0644) { |f| f << append self.debug("Appended to #{f.path}") } end end end def enabled? if /YES$/ =~ self.rcvar_value self.debug("Is enabled") return :true end self.debug("Is disabled") :false end def enable self.debug("Enabling") self.rc_edit("YES") end def disable self.debug("Disabling") self.rc_edit("NO") end def startcmd [self.initscript, :onestart] end def stopcmd [self.initscript, :onestop] end def statuscmd [self.initscript, :onestatus] end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/service/gentoo.rb�������������������������������������������������0000644�0052762�0001160�00000002246�13417161721�022213� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Manage gentoo services. Start/stop is the same as InitSvc, but enable/disable # is special. Puppet::Type.type(:service).provide :gentoo, :parent => :init do desc <<-EOT Gentoo's form of `init`-style service management. Uses `rc-update` for service enabling and disabling. EOT commands :update => "/sbin/rc-update" confine :operatingsystem => :gentoo def disable output = update :del, @resource[:name], :default rescue Puppet::ExecutionFailure raise Puppet::Error, "Could not disable #{self.name}: #{output}", $!.backtrace end def enabled? begin output = update :show rescue Puppet::ExecutionFailure return :false end line = output.split(/\n/).find { |l| l.include?(@resource[:name]) } return :false unless line # If it's enabled then it will print output showing service | runlevel if output =~ /^\s*#{@resource[:name]}\s*\|\s*(boot|default)/ return :true else return :false end end def enable output = update :add, @resource[:name], :default rescue Puppet::ExecutionFailure raise Puppet::Error, "Could not enable #{self.name}: #{output}", $!.backtrace end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/service/init.rb���������������������������������������������������0000644�0052762�0001160�00000015007�13417161721�021662� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The standard init-based service type. Many other service types are # customizations of this module. Puppet::Type.type(:service).provide :init, :parent => :base do desc "Standard `init`-style service management." def self.defpath case Facter.value(:operatingsystem) when "FreeBSD", "DragonFly" ["/etc/rc.d", "/usr/local/etc/rc.d"] when "HP-UX" "/sbin/init.d" when "Archlinux" "/etc/rc.d" when "AIX" "/etc/rc.d/init.d" else "/etc/init.d" end end # Debian and Ubuntu should use the Debian provider. # RedHat systems should use the RedHat provider. confine :true => begin os = Facter.value(:operatingsystem).downcase family = Facter.value(:osfamily).downcase !(os == 'debian' || os == 'ubuntu' || family == 'redhat') end # We can't confine this here, because the init path can be overridden. #confine :exists => defpath # some init scripts are not safe to execute, e.g. we do not want # to suddenly run /etc/init.d/reboot.sh status and reboot our system. The # exclude list could be platform agnostic but I assume an invalid init script # on system A will never be a valid init script on system B def self.excludes excludes = [] # these exclude list was found with grep -L '\/sbin\/runscript' /etc/init.d/* on gentoo excludes += %w{functions.sh reboot.sh shutdown.sh} # this exclude list is all from /sbin/service (5.x), but I did not exclude kudzu excludes += %w{functions halt killall single linuxconf reboot boot} # 'wait-for-state' and 'portmap-wait' are excluded from instances here # because they take parameters that have unclear meaning. It looks like # 'wait-for-state' is a generic waiter mainly used internally for other # upstart services as a 'sleep until something happens' # (http://lists.debian.org/debian-devel/2012/02/msg01139.html), while # 'portmap-wait' is a specific instance of a waiter. There is an open # launchpad bug # (https://bugs.launchpad.net/ubuntu/+source/upstart/+bug/962047) that may # eventually explain how to use the wait-for-state service or perhaps why # it should remain excluded. When that bug is addressed this should be # reexamined. excludes += %w{wait-for-state portmap-wait} # these excludes were found with grep -r -L start /etc/init.d excludes += %w{rcS module-init-tools} # Prevent puppet failing on unsafe scripts from Yocto Linux if Facter.value(:osfamily) == "cisco-wrlinux" excludes += %w{banner.sh bootmisc.sh checkroot.sh devpts.sh dmesg.sh hostname.sh mountall.sh mountnfs.sh populate-volatile.sh rmnologin.sh save-rtc.sh sendsigs sysfs.sh umountfs umountnfs.sh} end # Prevent puppet failing to get status of the new service introduced # by the fix for this (bug https://bugs.launchpad.net/ubuntu/+source/lightdm/+bug/982889) # due to puppet's inability to deal with upstart services with instances. excludes += %w{plymouth-ready} # Prevent puppet failing to get status of these services, which need parameters # passed in (see https://bugs.launchpad.net/ubuntu/+source/puppet/+bug/1276766). excludes += %w{idmapd-mounting startpar-bridge} # Prevent puppet failing to get status of these services, additional upstart # service with instances excludes += %w{cryptdisks-udev} excludes += %w{statd-mounting} excludes += %w{gssd-mounting} excludes end # List all services of this type. def self.instances get_services(self.defpath) end def self.get_services(defpath, exclude = self.excludes) defpath = [defpath] unless defpath.is_a? Array instances = [] defpath.each do |path| unless FileTest.directory?(path) Puppet.debug "Service path #{path} does not exist" next end check = [:ensure] check << :enable if public_method_defined? :enabled? Dir.entries(path).each do |name| fullpath = File.join(path, name) next if name =~ /^\./ next if exclude.include? name next if not FileTest.executable?(fullpath) next if not is_init?(fullpath) instances << new(:name => name, :path => path, :hasstatus => true) end end instances end # Mark that our init script supports 'status' commands. def hasstatus=(value) case value when true, "true"; @parameters[:hasstatus] = true when false, "false"; @parameters[:hasstatus] = false else raise Puppet::Error, "Invalid 'hasstatus' value #{value.inspect}" end end # Where is our init script? def initscript @initscript ||= self.search(@resource[:name]) end def paths @paths ||= @resource[:path].find_all do |path| if File.directory?(path) true else if Puppet::FileSystem.exist?(path) self.debug "Search path #{path} is not a directory" else self.debug "Search path #{path} does not exist" end false end end end def search(name) paths.each do |path| fqname = File.join(path,name) if Puppet::FileSystem.exist? fqname return fqname else self.debug("Could not find #{name} in #{path}") end end paths.each do |path| fqname_sh = File.join(path,"#{name}.sh") if Puppet::FileSystem.exist? fqname_sh return fqname_sh else self.debug("Could not find #{name}.sh in #{path}") end end raise Puppet::Error, "Could not find init script for '#{name}'" end # The start command is just the init script with 'start'. def startcmd [initscript, :start] end # The stop command is just the init script with 'stop'. def stopcmd [initscript, :stop] end def restartcmd (@resource[:hasrestart] == :true) && [initscript, :restart] end def texecute(type, command, fof = true, squelch = false, combine = true) if type == :start && Facter.value(:osfamily) == "Solaris" command = ["/usr/bin/ctrun -l child", command].flatten.join(" ") end super(type, command, fof, squelch, combine) end # If it was specified that the init script has a 'status' command, then # we just return that; otherwise, we return false, which causes it to # fallback to other mechanisms. def statuscmd (@resource[:hasstatus] == :true) && [initscript, :status] end private def self.is_init?(script = initscript) file = Puppet::FileSystem.pathname(script) !Puppet::FileSystem.symlink?(file) || Puppet::FileSystem.readlink(file) != "/lib/init/upstart-job" end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/service/launchd.rb������������������������������������������������0000644�0052762�0001160�00000027325�13417161721�022343� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/plist' Puppet::Type.type(:service).provide :launchd, :parent => :base do desc <<-'EOT' This provider manages jobs with `launchd`, which is the default service framework for Mac OS X (and may be available for use on other platforms). For more information, see the `launchd` man page: * <https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man8/launchd.8.html> This provider reads plists out of the following directories: * `/System/Library/LaunchDaemons` * `/System/Library/LaunchAgents` * `/Library/LaunchDaemons` * `/Library/LaunchAgents` ...and builds up a list of services based upon each plist's "Label" entry. This provider supports: * ensure => running/stopped, * enable => true/false * status * restart Here is how the Puppet states correspond to `launchd` states: * stopped --- job unloaded * started --- job loaded * enabled --- 'Disable' removed from job plist file * disabled --- 'Disable' added to job plist file Note that this allows you to do something `launchctl` can't do, which is to be in a state of "stopped/enabled" or "running/disabled". Note that this provider does not support overriding 'restart' EOT include Puppet::Util::Warnings commands :launchctl => "/bin/launchctl" defaultfor :operatingsystem => :darwin confine :operatingsystem => :darwin confine :feature => :cfpropertylist has_feature :enableable has_feature :refreshable mk_resource_methods # These are the paths in OS X where a launchd service plist could # exist. This is a helper method, versus a constant, for easy testing # and mocking # # @api private def self.launchd_paths [ "/Library/LaunchAgents", "/Library/LaunchDaemons", "/System/Library/LaunchAgents", "/System/Library/LaunchDaemons" ] end # Gets the current Darwin version, example 10.6 returns 9 and 10.10 returns 14 # See https://en.wikipedia.org/wiki/Darwin_(operating_system)#Release_history # for more information. # # @api private def self.get_os_version @os_version ||= Facter.value(:operatingsystemmajrelease).to_i end # Defines the path to the overrides plist file where service enabling # behavior is defined in 10.6 and greater. # # With the rewrite of launchd in 10.10+, this moves and slightly changes format. # # @api private def self.launchd_overrides if self.get_os_version < 14 "/var/db/launchd.db/com.apple.launchd/overrides.plist" else "/var/db/com.apple.xpc.launchd/disabled.plist" end end # Caching is enabled through the following three methods. Self.prefetch will # call self.instances to create an instance for each service. Self.flush will # clear out our cache when we're done. def self.prefetch(resources) instances.each do |prov| if resource = resources[prov.name] resource.provider = prov end end end # Self.instances will return an array with each element being a hash # containing the name, provider, path, and status of each service on the # system. def self.instances jobs = self.jobsearch @job_list ||= self.job_list jobs.keys.collect do |job| job_status = @job_list.has_key?(job) ? :running : :stopped new(:name => job, :provider => :launchd, :path => jobs[job], :status => job_status) end end # This method will return a list of files in the passed directory. This method # does not go recursively down the tree and does not return directories # # @param path [String] The directory to glob # # @api private # # @return [Array] of String instances modeling file paths def self.return_globbed_list_of_file_paths(path) array_of_files = Dir.glob(File.join(path, '*')).collect do |filepath| File.file?(filepath) ? filepath : nil end array_of_files.compact end # Get a hash of all launchd plists, keyed by label. This value is cached, but # the cache will be refreshed if refresh is true. # # @api private def self.make_label_to_path_map(refresh=false) return @label_to_path_map if @label_to_path_map and not refresh @label_to_path_map = {} launchd_paths.each do |path| return_globbed_list_of_file_paths(path).each do |filepath| Puppet.debug("Reading launchd plist #{filepath}") job = read_plist(filepath) next if job.nil? if job.has_key?("Label") @label_to_path_map[job["Label"]] = filepath else #TRANSLATORS 'plist' and label' should not be translated Puppet.debug(_("The %{file} plist does not contain a 'label' key; Puppet is skipping it") % { file: filepath }) next end end end @label_to_path_map end # Sets a class instance variable with a hash of all launchd plist files that # are found on the system. The key of the hash is the job id and the value # is the path to the file. If a label is passed, we return the job id and # path for that specific job. def self.jobsearch(label=nil) by_label = make_label_to_path_map if label if by_label.has_key? label return { label => by_label[label] } else # try refreshing the map, in case a plist has been added in the interim by_label = make_label_to_path_map(true) if by_label.has_key? label return { label => by_label[label] } else raise Puppet::Error, "Unable to find launchd plist for job: #{label}" end end else # caller wants the whole map by_label end end # This status method lists out all currently running services. # This hash is returned at the end of the method. def self.job_list @job_list = Hash.new begin output = launchctl :list raise Puppet::Error.new("launchctl list failed to return any data.") if output.nil? output.split("\n").each do |line| @job_list[line.split(/\s/).last] = :running end rescue Puppet::ExecutionFailure raise Puppet::Error.new("Unable to determine status of #{resource[:name]}", $!) end @job_list end # Read a plist, whether its format is XML or in Apple's "binary1" # format. def self.read_plist(path) Puppet::Util::Plist.read_plist_file(path) end # Read overrides plist, retrying if necessary def self.read_overrides i = 1 overrides = nil loop do Puppet.debug(_("Reading overrides plist, attempt %{i}") % {i: i}) if i > 1 overrides = read_plist(launchd_overrides) break unless overrides.nil? raise Puppet::Error.new(_('Unable to read overrides plist, too many attempts')) if i == 20 Puppet.info(_('Overrides file could not be read, trying again.')) Kernel.sleep(0.1) i += 1 end overrides end # Clean out the @property_hash variable containing the cached list of services def flush @property_hash.clear end def exists? Puppet.debug("Puppet::Provider::Launchd:Ensure for #{@property_hash[:name]}: #{@property_hash[:ensure]}") @property_hash[:ensure] != :absent end # finds the path for a given label and returns the path and parsed plist # as an array of [path, plist]. Note plist is really a Hash here. def plist_from_label(label) job = self.class.jobsearch(label) job_path = job[label] if FileTest.file?(job_path) job_plist = self.class.read_plist(job_path) else raise Puppet::Error.new("Unable to parse launchd plist at path: #{job_path}") end [job_path, job_plist] end # when a service includes hasstatus=>false, we override the launchctl # status mechanism and fall back to the base provider status method. def status if @resource && ((@resource[:hasstatus] == :false) || (@resource[:status])) return super else if @property_hash[:status].nil? :absent else @property_hash[:status] end end end # start the service. To get to a state of running/enabled, we need to # conditionally enable at load, then disable by modifying the plist file # directly. def start return ucommand(:start) if resource[:start] job_path, _ = plist_from_label(resource[:name]) did_enable_job = false cmds = [] cmds << :launchctl << :load # always add -w so it always starts the job, it is a noop if it is not needed, this means we do # not have to rescan all launchd plists. cmds << "-w" if self.enabled? == :false || self.status == :stopped # launchctl won't load disabled jobs did_enable_job = true end cmds << job_path begin execute(cmds) rescue Puppet::ExecutionFailure raise Puppet::Error.new("Unable to start service: #{resource[:name]} at path: #{job_path}", $!) end # As load -w clears the Disabled flag, we need to add it in after self.disable if did_enable_job and resource[:enable] == :false end def stop return ucommand(:stop) if resource[:stop] job_path, _ = plist_from_label(resource[:name]) did_disable_job = false cmds = [] cmds << :launchctl << :unload if self.enabled? == :true # keepalive jobs can't be stopped without disabling cmds << "-w" did_disable_job = true end cmds << job_path begin execute(cmds) rescue Puppet::ExecutionFailure raise Puppet::Error.new("Unable to stop service: #{resource[:name]} at path: #{job_path}", $!) end # As unload -w sets the Disabled flag, we need to add it in after self.enable if did_disable_job and resource[:enable] == :true end def restart Puppet.debug("A restart has been triggered for the #{resource[:name]} service") Puppet.debug("Stopping the #{resource[:name]} service") self.stop Puppet.debug("Starting the #{resource[:name]} service") self.start end # launchd jobs are enabled by default. They are only disabled if the key # "Disabled" is set to true, but it can also be set to false to enable it. # Starting in 10.6, the Disabled key in the job plist is consulted, but only # if there is no entry in the global overrides plist. We need to draw a # distinction between undefined, true and false for both locations where the # Disabled flag can be defined. def enabled? job_plist_disabled = nil overrides_disabled = nil _, job_plist = plist_from_label(resource[:name]) job_plist_disabled = job_plist["Disabled"] if job_plist.has_key?("Disabled") if FileTest.file?(self.class.launchd_overrides) and overrides = self.class.read_overrides if overrides.has_key?(resource[:name]) if self.class.get_os_version < 14 overrides_disabled = overrides[resource[:name]]["Disabled"] if overrides[resource[:name]].has_key?("Disabled") else overrides_disabled = overrides[resource[:name]] end end end if overrides_disabled.nil? if job_plist_disabled.nil? or job_plist_disabled == false return :true end elsif overrides_disabled == false return :true end :false end # enable and disable are a bit hacky. We write out the plist with the appropriate value # rather than dealing with launchctl as it is unable to change the Disabled flag # without actually loading/unloading the job. def enable overrides = self.class.read_overrides if self.class.get_os_version < 14 overrides[resource[:name]] = { "Disabled" => false } else overrides[resource[:name]] = false end Puppet::Util::Plist.write_plist_file(overrides, self.class.launchd_overrides) end def disable overrides = self.class.read_overrides if self.class.get_os_version < 14 overrides[resource[:name]] = { "Disabled" => true } else overrides[resource[:name]] = true end Puppet::Util::Plist.write_plist_file(overrides, self.class.launchd_overrides) end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/service/openbsd.rb������������������������������������������������0000644�0052762�0001160�00000005223�13417161721�022350� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.type(:service).provide :openbsd, :parent => :init do desc "Provider for OpenBSD's rc.d daemon control scripts" commands :rcctl => '/usr/sbin/rcctl' confine :operatingsystem => :openbsd defaultfor :operatingsystem => :openbsd has_feature :flaggable def startcmd [command(:rcctl), '-f', :start, @resource[:name]] end def stopcmd [command(:rcctl), :stop, @resource[:name]] end def restartcmd (@resource[:hasrestart] == :true) && [command(:rcctl), '-f', :restart, @resource[:name]] end def statuscmd [command(:rcctl), :check, @resource[:name]] end # @api private # When storing the name, take into account not everything has # '_flags', like 'multicast_host' and 'pf'. def self.instances instances = [] begin execpipe([command(:rcctl), :getall]) do |process| process.each_line do |line| match = /^(.*?)(?:_flags)?=(.*)$/.match(line) attributes_hash = { :name => match[1], :flags => match[2], :hasstatus => true, :provider => :openbsd, } instances << new(attributes_hash); end end instances rescue Puppet::ExecutionFailure return nil end end def enabled? output = execute([command(:rcctl), "get", @resource[:name], "status"], :failonfail => false, :combine => false, :squelch => false) if output.exitstatus == 1 self.debug("Is disabled") return :false else self.debug("Is enabled") return :true end end def enable self.debug("Enabling") rcctl(:enable, @resource[:name]) if @resource[:flags] rcctl(:set, @resource[:name], :flags, @resource[:flags]) end end def disable self.debug("Disabling") rcctl(:disable, @resource[:name]) end def running? output = execute([command(:rcctl), "check", @resource[:name]], :failonfail => false, :combine => false, :squelch => false).chomp return true if output.match(/\(ok\)/) end # Uses the wrapper to prevent failure when the service is not running; # rcctl(8) return non-zero in that case. def flags output = execute([command(:rcctl), "get", @resource[:name], "flags"], :failonfail => false, :combine => false, :squelch => false).chomp self.debug("Flags are: \"#{output}\"") output end def flags=(value) self.debug("Changing flags from #{flags} to #{value}") rcctl(:set, @resource[:name], :flags, value) # If the service is already running, force a restart as the flags have been changed. rcctl(:restart, @resource[:name]) if running? end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/service/openrc.rb�������������������������������������������������0000644�0052762�0001160�00000003332�13417161721�022203� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Gentoo OpenRC Puppet::Type.type(:service).provide :openrc, :parent => :base do desc <<-EOT Support for Gentoo's OpenRC initskripts Uses rc-update, rc-status and rc-service to manage services. EOT defaultfor :operatingsystem => :gentoo defaultfor :operatingsystem => :funtoo has_command(:rcstatus, '/bin/rc-status') do environment :RC_SVCNAME => nil end commands :rcservice => '/sbin/rc-service' commands :rcupdate => '/sbin/rc-update' self::STATUSLINE = /^\s+(.*?)\s*\[\s*(.*)\s*\]$/ def enable rcupdate('-C', :add, @resource[:name]) end def disable rcupdate('-C', :del, @resource[:name]) end # rc-status -a shows all runlevels and dynamic runlevels which # are not considered as enabled. We have to find out under which # runlevel our service is listed def enabled? enabled = :false rcstatus('-C', '-a').each_line do |line| case line.chomp when /^Runlevel: / enabled = :true when /^\S+/ # caption of a dynamic runlevel enabled = :false when self.class::STATUSLINE return enabled if @resource[:name] == $1 end end :false end def self.instances instances = [] rcservice('-C', '--list').each_line do |line| instances << new(:name => line.chomp) end instances end def restartcmd (@resource[:hasrestart] == :true) && [command(:rcservice), @resource[:name], :restart] end def startcmd [command(:rcservice), @resource[:name], :start ] end def stopcmd [command(:rcservice), @resource[:name], :stop] end def statuscmd ((@resource.provider.get(:hasstatus) == true) || (@resource[:hasstatus] == :true)) && [command(:rcservice), @resource[:name], :status] end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/service/openwrt.rb������������������������������������������������0000644�0052762�0001160�00000001471�13417161721�022415� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.type(:service).provide :openwrt, :parent => :init, :source => :init do desc <<-EOT Support for OpenWrt flavored init scripts. Uses /etc/init.d/service_name enable, disable, and enabled. EOT defaultfor :operatingsystem => :openwrt confine :operatingsystem => :openwrt has_feature :enableable def self.defpath ["/etc/init.d"] end def enable system(self.initscript, 'enable') end def disable system(self.initscript, 'disable') end def enabled? # We can't define the "command" for the init script, so we call system? if system(self.initscript, 'enabled') then return :true else return :false end end # Purposely leave blank so we fail back to ps based status detection # As OpenWrt init script do not have status commands def statuscmd end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/service/rcng.rb���������������������������������������������������0000644�0052762�0001160�00000003140�13417161721�021643� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.type(:service).provide :rcng, :parent => :bsd do desc <<-EOT RCng service management with rc.d EOT defaultfor :operatingsystem => [:netbsd, :cargos] confine :operatingsystem => [:netbsd, :cargos] def self.defpath "/etc/rc.d" end # if the service file exists in rc.conf.d AND matches an expected pattern # then it's already enabled def enabled? rcfile = File.join(rcconf_dir, @resource[:name]) if Puppet::FileSystem.exist?(rcfile) File.open(rcfile).readlines.each do |line| # Now look for something that looks like "service=${service:=YES}" or "service=YES" if line.match(/^\s*#{@resource[:name]}=(?:YES|\${#{@resource[:name]}:=YES})/) return :true end end end :false end # enable service by creating a service file under rc.conf.d with the # proper contents, or by modifying it's contents to to enable the service. def enable Dir.mkdir(rcconf_dir) if not Puppet::FileSystem.exist?(rcconf_dir) rcfile = File.join(rcconf_dir, @resource[:name]) if Puppet::FileSystem.exist?(rcfile) newcontents = [] File.open(rcfile).readlines.each do |line| if line.match(/^\s*#{@resource[:name]}=(NO|\$\{#{@resource[:name]}:NO\})/) line = "#{@resource[:name]}=${#{@resource[:name]}:=YES}" end newcontents.push(line) end Puppet::Util.replace_file(rcfile, 0644) do |f| f.puts newcontents end else Puppet::Util.replace_file(rcfile, 0644) do |f| f.puts "%s=${%s:=YES}\n" % [@resource[:name], @resource[:name]] end end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/service/redhat.rb�������������������������������������������������0000644�0052762�0001160�00000004674�13417161721�022176� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Manage Red Hat services. Start/stop uses /sbin/service and enable/disable uses chkconfig Puppet::Type.type(:service).provide :redhat, :parent => :init, :source => :init do desc "Red Hat's (and probably many others') form of `init`-style service management. Uses `chkconfig` for service enabling and disabling. " commands :chkconfig => "/sbin/chkconfig", :service => "/sbin/service" defaultfor :osfamily => :redhat defaultfor :osfamily => :suse, :operatingsystemmajrelease => ["10", "11"] # Remove the symlinks def disable # The off method operates on run levels 2,3,4 and 5 by default We ensure # all run levels are turned off because the reset method may turn on the # service in run levels 0, 1 and/or 6 # We're not using --del here because we want to disable the service only, # and --del removes the service from chkconfig management chkconfig("--level", "0123456", @resource[:name], :off) rescue Puppet::ExecutionFailure => detail raise Puppet::Error, "Could not disable #{self.name}: #{detail}", detail.backtrace end def enabled? name = @resource[:name] begin output = chkconfig name rescue Puppet::ExecutionFailure return :false end # For Suse OS family, chkconfig returns 0 even if the service is disabled or non-existent # Therefore, check the output for '<name> on' (or '<name> B for boot services) # to see if it is enabled return :false unless Facter.value(:osfamily) != 'Suse' || output =~ /^#{name}\s+(on|B)$/ :true end # Don't support them specifying runlevels; always use the runlevels # in the init scripts. def enable chkconfig("--add", @resource[:name]) chkconfig(@resource[:name], :on) rescue Puppet::ExecutionFailure => detail raise Puppet::Error, "Could not enable #{self.name}: #{detail}", detail.backtrace end def initscript raise Puppet::Error, "Do not directly call the init script for '#{@resource[:name]}'; use 'service' instead" end # use hasstatus=>true when its set for the provider. def statuscmd ((@resource.provider.get(:hasstatus) == true) || (@resource[:hasstatus] == :true)) && [command(:service), @resource[:name], "status"] end def restartcmd (@resource[:hasrestart] == :true) && [command(:service), @resource[:name], "restart"] end def startcmd [command(:service), @resource[:name], "start"] end def stopcmd [command(:service), @resource[:name], "stop"] end end ��������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/service/runit.rb��������������������������������������������������0000644�0052762�0001160�00000005576�13417161721�022072� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Daemontools service management # # author Brice Figureau <brice-puppet@daysofwonder.com> Puppet::Type.type(:service).provide :runit, :parent => :daemontools do desc <<-'EOT' Runit service management. This provider manages daemons running supervised by Runit. When detecting the service directory it will check, in order of preference: * `/service` * `/etc/service` * `/var/service` The daemon directory should be in one of the following locations: * `/etc/sv` * `/var/lib/service` or this can be overridden in the service resource parameters: service { 'myservice': provider => 'runit', path => '/path/to/daemons', } This provider supports out of the box: * start/stop * enable/disable * restart * status EOT commands :sv => "/usr/bin/sv" class << self # this is necessary to autodetect a valid resource # default path, since there is no standard for such directory. def defpath unless @defpath ["/etc/sv", "/var/lib/service"].each do |path| if Puppet::FileSystem.exist?(path) @defpath = path break end end raise "Could not find the daemon directory (tested [/etc/sv,/var/lib/service])" unless @defpath end @defpath end end # find the service dir on this node def servicedir unless @servicedir ["/service", "/etc/service","/var/service"].each do |path| if Puppet::FileSystem.exist?(path) @servicedir = path break end end raise "Could not find service directory" unless @servicedir end @servicedir end def status begin output = sv "status", self.daemon return :running if output =~ /^run: / rescue Puppet::ExecutionFailure => detail unless detail.message =~ /(warning: |runsv not running$)/ raise Puppet::Error.new( "Could not get status for service #{resource.ref}: #{detail}", detail ) end end :stopped end def stop sv "stop", self.service end def start if enabled? != :true enable # Work around issue #4480 # runsvdir takes up to 5 seconds to recognize # the symlink created by this call to enable #TRANSLATORS 'runsvdir' is a linux service name and should not be translated Puppet.info _("Waiting 5 seconds for runsvdir to discover service %{service}") % { service: self.service } sleep 5 end sv "start", self.service end def restart sv "restart", self.service end # disable by removing the symlink so that runit # doesn't restart our service behind our back # note that runit doesn't need to perform a stop # before a disable def disable # unlink the daemon symlink to disable it Puppet::FileSystem.unlink(self.service) if Puppet::FileSystem.symlink?(self.service) end end ����������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/service/service.rb������������������������������������������������0000644�0052762�0001160�00000002052�13417161721�022353� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.type(:service).provide :service do desc "The simplest form of service support." def self.instances [] end # How to restart the process. def restart if @resource[:restart] or restartcmd ucommand(:restart) else self.stop self.start end end # There is no default command, which causes other methods to be used def restartcmd end # A simple wrapper so execution failures are a bit more informative. def texecute(type, command, fof = true, squelch = false, combine = true) begin execute(command, :failonfail => fof, :override_locale => false, :squelch => squelch, :combine => combine) rescue Puppet::ExecutionFailure => detail @resource.fail Puppet::Error, "Could not #{type} #{@resource.ref}: #{detail}", detail end nil end # Use either a specified command or the default for our provider. def ucommand(type, fof = true) if c = @resource[type] cmd = [c] else cmd = [send("#{type}cmd")].flatten end texecute(type, cmd, fof) end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/service/smf.rb����������������������������������������������������0000644�0052762�0001160�00000015463�13417161721�021512� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'timeout' # Solaris 10 SMF-style services. Puppet::Type.type(:service).provide :smf, :parent => :base do desc <<-EOT Support for Sun's new Service Management Framework. Starting a service is effectively equivalent to enabling it, so there is only support for starting and stopping services, which also enables and disables them, respectively. By specifying `manifest => "/path/to/service.xml"`, the SMF manifest will be imported if it does not exist. EOT defaultfor :osfamily => :solaris confine :osfamily => :solaris commands :adm => "/usr/sbin/svcadm", :svcs => "/usr/bin/svcs" commands :svccfg => "/usr/sbin/svccfg" has_feature :refreshable def setupservice if resource[:manifest] begin svcs("-l", @resource[:name]) rescue Puppet::ExecutionFailure Puppet.notice "Importing #{@resource[:manifest]} for #{@resource[:name]}" svccfg :import, resource[:manifest] end end rescue Puppet::ExecutionFailure => detail raise Puppet::Error.new( "Cannot config #{self.name} to enable it: #{detail}", detail ) end def self.instances svcs("-H", "-o", "state,fmri" ).split("\n").select{|l| l !~ /^legacy_run/ }.collect do |line| state,fmri = line.split(/\s+/) status = case state when /online/; :running when /maintenance/; :maintenance when /degraded/; :degraded else :stopped end new({:name => fmri, :ensure => status}) end end # Returns the service's FMRI. We fail if multiple FMRIs correspond to # @resource[:name]. # # If the service does not exist or we fail to get any FMRIs from svcs, # this method will raise a Puppet::Error def service_fmri return @fmri if @fmri # `svcs -l` is better to use because we can detect service instances # that have not yet been activated or enabled (i.e. it lets us detect # services that svcadm has not yet touched). `svcs -H -o fmri` is a bit # more limited. lines = svcs("-l", @resource[:name]).chomp.lines.to_a lines.select! { |line| line =~ /^fmri/ } fmris = lines.map! { |line| line.split(' ')[-1].chomp } unless fmris.length == 1 raise Puppet::Error, _("Failed to get the FMRI of the %{service} service: The pattern '%{service}' matches multiple FMRIs! These are the FMRIs it matches: %{all_fmris}") % { service: @resource[:name], all_fmris: fmris.join(', ') } end @fmri = fmris.first end # Returns true if the provider supports incomplete services. def supports_incomplete_services? Puppet::Util::Package.versioncmp(Facter.value(:operatingsystemrelease), '11.1') >= 0 end # Returns true if the service is complete. A complete service is a service that # has the general/complete property defined. def complete_service? unless supports_incomplete_services? raise Puppet::Error, _("Cannot query if the %{service} service is complete: The concept of complete/incomplete services was introduced in Solaris 11.1. You are on a Solaris %{release} machine.") % { service: @resource[:name], release: Facter.value(:operatingsystemrelease) } end return @complete_service if @complete_service # We need to use the service's FMRI when querying its config. because # general/complete is an instance-specific property. fmri = service_fmri # Check if the general/complete property is defined. If it is undefined, # then svccfg will not print anything to the console. property_defn = svccfg("-s", fmri, "listprop", "general/complete").chomp @complete_service = ! property_defn.empty? end def enable self.start end def enabled? case self.status when :running return :true else return :false end end def disable self.stop end def restartcmd if Puppet::Util::Package.versioncmp(Facter.value(:operatingsystemrelease), '11.2') >= 0 [command(:adm), :restart, "-s", @resource[:name]] else # Synchronous restart only supported in Solaris 11.2 and above [command(:adm), :restart, @resource[:name]] end end def startcmd self.setupservice case self.status when :maintenance, :degraded [command(:adm), :clear, @resource[:name]] else [command(:adm), :enable, "-rs", @resource[:name]] end end # Wait for the service to transition into the specified state before returning. # This is necessary due to the asynchronous nature of SMF services. # desired_state should be online, offline, disabled, or uninitialized. # See PUP-5474 for long-term solution to this issue. def wait(*desired_state) Timeout.timeout(60) do loop do states = self.service_states break if desired_state.include?(states[0]) && states[1] == '-' sleep(1) end end rescue Timeout::Error raise Puppet::Error.new("Timed out waiting for #{@resource[:name]} to transition states") end def start # Wait for the service to actually start before returning. super self.wait('online') end def stop # Wait for the service to actually stop before returning. super self.wait('offline', 'disabled', 'uninitialized') end def restart # Wait for the service to actually start before returning. super self.wait('online') end # Determine the current and next states of a service. def service_states svcs("-H", "-o", "state,nstate", @resource[:name]).chomp.split end def status if @resource[:status] super return end begin if supports_incomplete_services? unless complete_service? debug _("The %{service} service is incomplete so its status will be reported as :stopped. See `svcs -xv %{fmri}` for more details.") % { service: @resource[:name], fmri: service_fmri } return :stopped end end # get the current state and the next state, and if the next # state is set (i.e. not "-") use it for state comparison states = service_states state = states[1] == "-" ? states[0] : states[1] rescue Puppet::ExecutionFailure # TODO (PUP-8957): Should this be set back to INFO ? debug "Could not get status on service #{self.name} #{$!}" return :stopped end case state when "online" #self.warning "matched running #{line.inspect}" return :running when "offline", "disabled", "uninitialized" #self.warning "matched stopped #{line.inspect}" return :stopped when "maintenance" return :maintenance when "degraded" return :degraded when "legacy_run" raise Puppet::Error, "Cannot manage legacy services through SMF" else raise Puppet::Error, "Unmanageable state '#{state}' on service #{self.name}" end end def stopcmd [command(:adm), :disable, "-s", @resource[:name]] end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/service/src.rb����������������������������������������������������0000644�0052762�0001160�00000007772�13417161721�021520� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'timeout' # AIX System Resource controller (SRC) Puppet::Type.type(:service).provide :src, :parent => :base do desc "Support for AIX's System Resource controller. Services are started/stopped based on the `stopsrc` and `startsrc` commands, and some services can be refreshed with `refresh` command. Enabling and disabling services is not supported, as it requires modifications to `/etc/inittab`. Starting and stopping groups of subsystems is not yet supported. " defaultfor :operatingsystem => :aix confine :operatingsystem => :aix optional_commands :stopsrc => "/usr/bin/stopsrc", :startsrc => "/usr/bin/startsrc", :refresh => "/usr/bin/refresh", :lssrc => "/usr/bin/lssrc", :lsitab => "/usr/sbin/lsitab", :mkitab => "/usr/sbin/mkitab", :rmitab => "/usr/sbin/rmitab", :chitab => "/usr/sbin/chitab" has_feature :refreshable def self.instances services = lssrc('-S') services.split("\n").reject { |x| x.strip.start_with? '#' }.collect do |line| data = line.split(':') service_name = data[0] new(:name => service_name) end end def startcmd [command(:startsrc), "-s", @resource[:name]] end def stopcmd [command(:stopsrc), "-s", @resource[:name]] end def default_runlevel "2" end def default_action "once" end def enabled? execute([command(:lsitab), @resource[:name]], {:failonfail => false, :combine => true}) $CHILD_STATUS.exitstatus == 0 ? :true : :false end def enable mkitab("%s:%s:%s:%s" % [@resource[:name], default_runlevel, default_action, startcmd.join(" ")]) end def disable rmitab(@resource[:name]) end # Wait for the service to transition into the specified state before returning. # This is necessary due to the asynchronous nature of AIX services. # desired_state should either be :running or :stopped. def wait(desired_state) Timeout.timeout(60) do loop do status = self.status break if status == desired_state.to_sym sleep(1) end end rescue Timeout::Error raise Puppet::Error.new("Timed out waiting for #{@resource[:name]} to transition states") end def start super self.wait(:running) end def stop super self.wait(:stopped) end def restart execute([command(:lssrc), "-Ss", @resource[:name]]).each_line do |line| args = line.split(":") next unless args[0] == @resource[:name] # Subsystems with the -K flag can get refreshed (HUPed) # While subsystems with -S (signals) must be stopped/started method = args[11] do_refresh = case method when "-K" then :true when "-S" then :false else self.fail("Unknown service communication method #{method}") end begin if do_refresh == :true execute([command(:refresh), "-s", @resource[:name]]) else self.stop self.start end return :true rescue Puppet::ExecutionFailure => detail raise Puppet::Error.new("Unable to restart service #{@resource[:name]}, error was: #{detail}", detail ) end end self.fail("No such service found") rescue Puppet::ExecutionFailure => detail raise Puppet::Error.new("Cannot get status of #{@resource[:name]}, error was: #{detail}", detail ) end def status execute([command(:lssrc), "-s", @resource[:name]]).each_line do |line| args = line.split # This is the header line next unless args[0] == @resource[:name] # PID is the 3rd field, but inoperative subsystems # skip this so split doesn't work right state = case args[-1] when "active" then :running when "inoperative" then :stopped end Puppet.debug("Service #{@resource[:name]} is #{args[-1]}") return state end rescue Puppet::ExecutionFailure => detail self.debug(detail.message) return :stopped end end ������puppet-5.5.10/lib/puppet/provider/service/base.rb���������������������������������������������������0000644�0052762�0001160�00000010140�13417161721�021622� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.type(:service).provide :base, :parent => :service do desc "The simplest form of Unix service support. You have to specify enough about your service for this to work; the minimum you can specify is a binary for starting the process, and this same binary will be searched for in the process table to stop the service. As with `init`-style services, it is preferable to specify start, stop, and status commands. " commands :kill => "kill" # get the proper 'ps' invocation for the platform # ported from the facter 2.x implementation, since facter 3.x # is dropping the fact (for which this was the only use) def getps case Facter.value(:operatingsystem) when 'OpenWrt' 'ps www' when 'FreeBSD', 'NetBSD', 'OpenBSD', 'Darwin', 'DragonFly' 'ps auxwww' else 'ps -ef' end end private :getps # Get the process ID for a running process. Requires the 'pattern' # parameter. def getpid @resource.fail "Either stop/status commands or a pattern must be specified" unless @resource[:pattern] regex = Regexp.new(@resource[:pattern]) ps = getps self.debug "Executing '#{ps}'" table = Puppet::Util::Execution.execute(ps) # The output of the PS command can be a mashup of several different # encodings depending on which processes are running and what # arbitrary data has been used to set their name in the process table. # # First, try a polite conversion to in order to match the UTF-8 encoding # of our regular expression. table = Puppet::Util::CharacterEncoding.convert_to_utf_8(table) # If that fails, force to UTF-8 and then scrub as most uses are scanning # for ACII-compatible program names. table.force_encoding(Encoding::UTF_8) unless table.encoding == Encoding::UTF_8 table = Puppet::Util::CharacterEncoding.scrub(table) unless table.valid_encoding? table.each_line { |line| if regex.match(line) self.debug "Process matched: #{line}" ary = line.sub(/^[[:space:]]+/u, '').split(/[[:space:]]+/u) return ary[1] end } nil end private :getpid # Check if the process is running. Prefer the 'status' parameter, # then 'statuscmd' method, then look in the process table. We give # the object the option to not return a status command, which might # happen if, for instance, it has an init script (and thus responds to # 'statuscmd') but does not have 'hasstatus' enabled. def status if @resource[:status] or statuscmd # Don't fail when the exit status is not 0. ucommand(:status, false) # Explicitly calling exitstatus to facilitate testing if $CHILD_STATUS.exitstatus == 0 return :running else return :stopped end elsif pid = getpid self.debug "PID is #{pid}" return :running else return :stopped end end # There is no default command, which causes other methods to be used def statuscmd end # Run the 'start' parameter command, or the specified 'startcmd'. def start ucommand(:start) end # The command used to start. Generated if the 'binary' argument # is passed. def startcmd if @resource[:binary] return @resource[:binary] else raise Puppet::Error, "Services must specify a start command or a binary" end end # Stop the service. If a 'stop' parameter is specified, it # takes precedence; otherwise checks if the object responds to # a 'stopcmd' method, and if so runs that; otherwise, looks # for the process in the process table. # This method will generally not be overridden by submodules. def stop if @resource[:stop] or stopcmd ucommand(:stop) else pid = getpid unless pid self.info _("%{name} is not running") % { name: self.name } return false end begin output = kill pid rescue Puppet::ExecutionFailure @resource.fail Puppet::Error, "Could not kill #{self.name}, PID #{pid}: #{output}", $! end return true end end # There is no default command, which causes other methods to be used def stopcmd end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/service/debian.rb�������������������������������������������������0000644�0052762�0001160�00000005346�13417161721�022146� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Manage debian services. Start/stop is the same as InitSvc, but enable/disable # is special. Puppet::Type.type(:service).provide :debian, :parent => :init do desc <<-EOT Debian's form of `init`-style management. The only differences from `init` are support for enabling and disabling services via `update-rc.d` and the ability to determine enabled status via `invoke-rc.d`. EOT commands :update_rc => "/usr/sbin/update-rc.d" # note this isn't being used as a command until # https://projects.puppetlabs.com/issues/2538 # is resolved. commands :invoke_rc => "/usr/sbin/invoke-rc.d" commands :service => "/usr/sbin/service" defaultfor :operatingsystem => :cumuluslinux, :operatingsystemmajrelease => ['1','2'] defaultfor :operatingsystem => :debian, :operatingsystemmajrelease => ['5','6','7'] # Remove the symlinks def disable if `dpkg --compare-versions $(dpkg-query -W --showformat '${Version}' sysv-rc) ge 2.88 ; echo $?`.to_i == 0 update_rc @resource[:name], "disable" else update_rc "-f", @resource[:name], "remove" update_rc @resource[:name], "stop", "00", "1", "2", "3", "4", "5", "6", "." end end def enabled? # TODO: Replace system call when Puppet::Util::Execution.execute gives us a way # to determine exit status. https://projects.puppetlabs.com/issues/2538 system("/usr/sbin/invoke-rc.d", "--quiet", "--query", @resource[:name], "start") # 104 is the exit status when you query start an enabled service. # 106 is the exit status when the policy layer supplies a fallback action # See x-man-page://invoke-rc.d if [104, 106].include?($CHILD_STATUS.exitstatus) return :true elsif [101, 105].include?($CHILD_STATUS.exitstatus) # 101 is action not allowed, which means we have to do the check manually. # 105 is unknown, which generally means the initscript does not support query # The debian policy states that the initscript should support methods of query # For those that do not, perform the checks manually # http://www.debian.org/doc/debian-policy/ch-opersys.html if get_start_link_count >= 4 return :true else return :false end else return :false end end def get_start_link_count Dir.glob("/etc/rc*.d/S??#{@resource[:name]}").length end def enable update_rc "-f", @resource[:name], "remove" update_rc @resource[:name], "defaults" end def statuscmd # /usr/sbin/service provides an abstraction layer which is able to query services # independent of the init system used. # See https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=775795 (@resource[:hasstatus] == :true) && [command(:service), @resource[:name], "status"] end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/service/systemd.rb������������������������������������������������0000644�0052762�0001160�00000014123�13417161721�022405� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Manage systemd services using systemctl Puppet::Type.type(:service).provide :systemd, :parent => :base do desc "Manages `systemd` services using `systemctl`. Because `systemd` defaults to assuming the `.service` unit type, the suffix may be omitted. Other unit types (such as `.path`) may be managed by providing the proper suffix." commands :systemctl => "systemctl" if Facter.value(:osfamily).downcase == 'debian' # With multiple init systems on Debian, it is possible to have # pieces of systemd around (e.g. systemctl) but not really be # using systemd. We do not do this on other platforms as it can # cause issues when running in a chroot without /run mounted # (PUP-5577) confine :exists => "/run/systemd/system" end defaultfor :osfamily => [:archlinux] defaultfor :osfamily => :redhat, :operatingsystemmajrelease => "7" defaultfor :osfamily => :redhat, :operatingsystem => :fedora defaultfor :osfamily => :suse defaultfor :osfamily => :coreos defaultfor :operatingsystem => :amazon, :operatingsystemmajrelease => ["2"] defaultfor :operatingsystem => :debian, :operatingsystemmajrelease => ["8", "stretch/sid", "9", "buster/sid"] defaultfor :operatingsystem => :ubuntu, :operatingsystemmajrelease => ["15.04","15.10","16.04","16.10","17.04","17.10","18.04"] defaultfor :operatingsystem => :cumuluslinux, :operatingsystemmajrelease => ["3"] def self.instances i = [] output = systemctl('list-unit-files', '--type', 'service', '--full', '--all', '--no-pager') output.scan(/^(\S+)\s+(disabled|enabled|masked|indirect)\s*$/i).each do |m| i << new(:name => m[0]) end return i rescue Puppet::ExecutionFailure return [] end # This helper ensures that the enable state cache is always reset # after a systemctl enable operation. A particular service state is not guaranteed # after such an operation, so the cache must be emptied to prevent inconsistencies # in the provider's believed state of the service and the actual state. # @param action [String,Symbol] One of 'enable', 'disable', 'mask' or 'unmask' def systemctl_change_enable(action) output = systemctl(action, @resource[:name]) rescue raise Puppet::Error, "Could not #{action} #{self.name}: #{output}", $!.backtrace ensure @cached_enabled = nil end def disable systemctl_change_enable(:disable) end def get_start_link_count # Start links don't include '.service'. Just search for the service name. if @resource[:name].match(/\.service/) link_name = @resource[:name].split('.')[0] else link_name = @resource[:name] end Dir.glob("/etc/rc*.d/S??#{link_name}").length end def cached_enabled? return @cached_enabled if @cached_enabled cmd = [command(:systemctl), 'is-enabled', @resource[:name]] @cached_enabled = execute(cmd, :failonfail => false).strip end def enabled? output = cached_enabled? code = $CHILD_STATUS.exitstatus # The masked state is equivalent to the disabled state in terms of # comparison so we only care to check if it is masked if we want to keep # it masked. # # We only return :mask if we're trying to mask the service. This prevents # flapping when simply trying to disable a masked service. return :mask if (@resource[:enable] == :mask) && (output == 'masked') # The indirect state indicates that the unit is not enabled. return :false if output == 'indirect' return :true if (code == 0) if (output.empty?) && (code > 0) && (Facter.value(:osfamily).downcase == 'debian') ret = debian_enabled? return ret if ret end return :false end # This method is required for Debian systems due to the way the SysVInit-Systemd # compatibility layer works. When we are trying to manage a service which does not # have a Systemd unit file, we need to go through the old init script to determine # whether it is enabled or not. See PUP-5016 for more details. # def debian_enabled? system("/usr/sbin/invoke-rc.d", "--quiet", "--query", @resource[:name], "start") if [104, 106].include?($CHILD_STATUS.exitstatus) return :true elsif [101, 105].include?($CHILD_STATUS.exitstatus) # 101 is action not allowed, which means we have to do the check manually. # 105 is unknown, which generally means the initscript does not support query # The debian policy states that the initscript should support methods of query # For those that do not, perform the checks manually # http://www.debian.org/doc/debian-policy/ch-opersys.html if get_start_link_count >= 4 return :true else return :false end else return :false end end def enable self.unmask systemctl_change_enable(:enable) end def mask self.disable systemctl_change_enable(:mask) end def unmask systemctl_change_enable(:unmask) end def restartcmd [command(:systemctl), "restart", @resource[:name]] end def startcmd self.unmask [command(:systemctl), "start", @resource[:name]] end def stopcmd [command(:systemctl), "stop", @resource[:name]] end def statuscmd [command(:systemctl), "is-active", @resource[:name]] end def restart begin super rescue Puppet::Error => e raise Puppet::Error.new(prepare_error_message(@resource[:name], 'restart', e)) end end def start begin super rescue Puppet::Error => e raise Puppet::Error.new(prepare_error_message(@resource[:name], 'start', e)) end end def stop begin super rescue Puppet::Error => e raise Puppet::Error.new(prepare_error_message(@resource[:name], 'stop', e)) end end def prepare_error_message(name, action, exception) error_return = "Systemd #{action} for #{name} failed!\n" journalctl_command = "journalctl -n 50 --since '5 minutes ago' -u #{name} --no-pager" Puppet.debug("Running journalctl command to get logs for systemd #{action} failure: #{journalctl_command}") journalctl_output = execute(journalctl_command) error_return << "journalctl log for #{name}:\n#{journalctl_output}" end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/service/upstart.rb������������������������������������������������0000644�0052762�0001160�00000023236�13417161721�022424� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.type(:service).provide :upstart, :parent => :debian do START_ON = /^\s*start\s+on/ COMMENTED_START_ON = /^\s*#+\s*start\s+on/ MANUAL = /^\s*manual\s*$/ desc "Ubuntu service management with `upstart`. This provider manages `upstart` jobs on Ubuntu. For `upstart` documentation, see <http://upstart.ubuntu.com/>. " confine :any => [ Facter.value(:operatingsystem) == 'Ubuntu', (Facter.value(:osfamily) == 'RedHat' and Facter.value(:operatingsystemrelease) =~ /^6\./), (Facter.value(:operatingsystem) == 'Amazon' and Facter.value(:operatingsystemmajrelease) =~ /\d{4}/), Facter.value(:operatingsystem) == 'LinuxMint', ] defaultfor :operatingsystem => :ubuntu, :operatingsystemmajrelease => ["10.04", "12.04", "14.04", "14.10"] commands :start => "/sbin/start", :stop => "/sbin/stop", :restart => "/sbin/restart", :status_exec => "/sbin/status", :initctl => "/sbin/initctl" # We only want to use upstart as our provider if the upstart daemon is running. # This can be checked by running `initctl version --quiet` on a machine that has # upstart installed. confine :true => begin initctl('version', '--quiet') true rescue false end # upstart developer haven't implemented initctl enable/disable yet: # http://www.linuxplanet.com/linuxplanet/tutorials/7033/2/ has_feature :enableable def self.instances self.get_services(self.excludes) # Take exclude list from init provider end def self.excludes excludes = super if Facter.value(:osfamily) == 'RedHat' # Puppet cannot deal with services that have instances, so we have to # ignore these services using instances on redhat based systems. excludes += %w[serial tty] end excludes end def self.get_services(exclude=[]) instances = [] execpipe("#{command(:initctl)} list") { |process| process.each_line { |line| # needs special handling of services such as network-interface: # initctl list: # network-interface (lo) start/running # network-interface (eth0) start/running # network-interface-security start/running name = \ if matcher = line.match(/^(network-interface)\s\(([^\)]+)\)/) "#{matcher[1]} INTERFACE=#{matcher[2]}" elsif matcher = line.match(/^(network-interface-security)\s\(([^\)]+)\)/) "#{matcher[1]} JOB=#{matcher[2]}" else line.split.first end instances << new(:name => name) } } instances.reject { |instance| exclude.include?(instance.name) } end def self.defpath ["/etc/init", "/etc/init.d"] end def upstart_version @upstart_version ||= initctl("--version").match(/initctl \(upstart ([^\)]*)\)/)[1] end # Where is our override script? def overscript @overscript ||= initscript.gsub(/\.conf$/,".override") end def search(name) # Search prefers .conf as that is what upstart uses [".conf", "", ".sh"].each do |suffix| paths.each do |path| service_name = name.match(/^(\S+)/)[1] fqname = File.join(path, service_name + suffix) if Puppet::FileSystem.exist?(fqname) return fqname end self.debug("Could not find #{name}#{suffix} in #{path}") end end raise Puppet::Error, "Could not find init script or upstart conf file for '#{name}'" end def enabled? return super if not is_upstart? script_contents = read_script_from(initscript) if version_is_pre_0_6_7 enabled_pre_0_6_7?(script_contents) elsif version_is_pre_0_9_0 enabled_pre_0_9_0?(script_contents) elsif version_is_post_0_9_0 enabled_post_0_9_0?(script_contents, read_override_file) end end def enable return super if not is_upstart? script_text = read_script_from(initscript) if version_is_pre_0_9_0 enable_pre_0_9_0(script_text) else enable_post_0_9_0(script_text, read_override_file) end end def disable return super if not is_upstart? script_text = read_script_from(initscript) if version_is_pre_0_6_7 disable_pre_0_6_7(script_text) elsif version_is_pre_0_9_0 disable_pre_0_9_0(script_text) elsif version_is_post_0_9_0 disable_post_0_9_0(read_override_file) end end def startcmd is_upstart? ? [command(:start), @resource[:name]] : super end def stopcmd is_upstart? ? [command(:stop), @resource[:name]] : super end def restartcmd is_upstart? ? (@resource[:hasrestart] == :true) && [command(:restart), @resource[:name]] : super end def statuscmd is_upstart? ? nil : super #this is because upstart is broken with its return codes end def status if (@resource[:hasstatus] == :false) || @resource[:status] || ! is_upstart? return super end output = status_exec(@resource[:name].split) if output =~ /start\// return :running else return :stopped end end private def is_upstart?(script = initscript) Puppet::FileSystem.exist?(script) && script.match(/\/etc\/init\/\S+\.conf/) end def version_is_pre_0_6_7 Puppet::Util::Package.versioncmp(upstart_version, "0.6.7") == -1 end def version_is_pre_0_9_0 Puppet::Util::Package.versioncmp(upstart_version, "0.9.0") == -1 end def version_is_post_0_9_0 Puppet::Util::Package.versioncmp(upstart_version, "0.9.0") >= 0 end def enabled_pre_0_6_7?(script_text) # Upstart version < 0.6.7 means no manual stanza. if script_text.match(START_ON) return :true else return :false end end def enabled_pre_0_9_0?(script_text) # Upstart version < 0.9.0 means no override files # So we check to see if an uncommented start on or manual stanza is the last one in the file # The last one in the file wins. enabled = :false script_text.each_line do |line| if line.match(START_ON) enabled = :true elsif line.match(MANUAL) enabled = :false end end enabled end def enabled_post_0_9_0?(script_text, over_text) # This version has manual stanzas and override files # So we check to see if an uncommented start on or manual stanza is the last one in the # conf file and any override files. The last one in the file wins. enabled = :false script_text.each_line do |line| if line.match(START_ON) enabled = :true elsif line.match(MANUAL) enabled = :false end end over_text.each_line do |line| if line.match(START_ON) enabled = :true elsif line.match(MANUAL) enabled = :false end end if over_text enabled end def enable_pre_0_9_0(text) # We also need to remove any manual stanzas to ensure that it is enabled text = remove_manual_from(text) if enabled_pre_0_9_0?(text) == :false enabled_script = if text.match(COMMENTED_START_ON) uncomment_start_block_in(text) else add_default_start_to(text) end else enabled_script = text end write_script_to(initscript, enabled_script) end def enable_post_0_9_0(script_text, over_text) over_text = remove_manual_from(over_text) if enabled_post_0_9_0?(script_text, over_text) == :false if script_text.match(START_ON) over_text << extract_start_on_block_from(script_text) else over_text << "\nstart on runlevel [2,3,4,5]" end end write_script_to(overscript, over_text) end def disable_pre_0_6_7(script_text) disabled_script = comment_start_block_in(script_text) write_script_to(initscript, disabled_script) end def disable_pre_0_9_0(script_text) write_script_to(initscript, ensure_disabled_with_manual(script_text)) end def disable_post_0_9_0(over_text) write_script_to(overscript, ensure_disabled_with_manual(over_text)) end def read_override_file if Puppet::FileSystem.exist?(overscript) read_script_from(overscript) else "" end end def uncomment(line) line.gsub(/^(\s*)#+/, '\1') end def remove_trailing_comments_from_commented_line_of(line) line.gsub(/^(\s*#+\s*[^#]*).*/, '\1') end def remove_trailing_comments_from(line) line.gsub(/^(\s*[^#]*).*/, '\1') end def unbalanced_parens_on(line) line.count('(') - line.count(')') end def remove_manual_from(text) text.gsub(MANUAL, "") end def comment_start_block_in(text) parens = 0 text.lines.map do |line| if line.match(START_ON) || parens > 0 # If there are more opening parens than closing parens, we need to comment out a multiline 'start on' stanza parens += unbalanced_parens_on(remove_trailing_comments_from(line)) "#" + line else line end end.join('') end def uncomment_start_block_in(text) parens = 0 text.lines.map do |line| if line.match(COMMENTED_START_ON) || parens > 0 parens += unbalanced_parens_on(remove_trailing_comments_from_commented_line_of(line)) uncomment(line) else line end end.join('') end def extract_start_on_block_from(text) parens = 0 text.lines.map do |line| if line.match(START_ON) || parens > 0 parens += unbalanced_parens_on(remove_trailing_comments_from(line)) line end end.join('') end def add_default_start_to(text) text + "\nstart on runlevel [2,3,4,5]" end def ensure_disabled_with_manual(text) remove_manual_from(text) + "\nmanual" end def read_script_from(filename) File.open(filename) do |file| file.read end end def write_script_to(file, text) Puppet::Util.replace_file(file, 0644) do |f| f.write(text) end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/service/windows.rb������������������������������������������������0000644�0052762�0001160�00000007737�13417161721�022424� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Windows Service Control Manager (SCM) provider Puppet::Type.type(:service).provide :windows, :parent => :service do desc <<-EOT Support for Windows Service Control Manager (SCM). This provider can start, stop, enable, and disable services, and the SCM provides working status methods for all services. Control of service groups (dependencies) is not yet supported, nor is running services as a specific user. EOT defaultfor :operatingsystem => :windows confine :operatingsystem => :windows has_feature :refreshable def enable Puppet::Util::Windows::Service.set_startup_mode( @resource[:name], :SERVICE_AUTO_START ) rescue => detail raise Puppet::Error.new(_("Cannot enable %{resource_name}, error was: %{detail}") % { resource_name: @resource[:name], detail: detail }, detail ) end def disable Puppet::Util::Windows::Service.set_startup_mode( @resource[:name], :SERVICE_DISABLED ) rescue => detail raise Puppet::Error.new(_("Cannot disable %{resource_name}, error was: %{detail}") % { resource_name: @resource[:name], detail: detail }, detail ) end def manual_start Puppet::Util::Windows::Service.set_startup_mode( @resource[:name], :SERVICE_DEMAND_START ) rescue => detail raise Puppet::Error.new(_("Cannot enable %{resource_name} for manual start, error was: %{detail}") % { resource_name: @resource[:name], detail: detail }, detail ) end def enabled? return :false unless Puppet::Util::Windows::Service.exists?(@resource[:name]) start_type = Puppet::Util::Windows::Service.service_start_type(@resource[:name]) debug("Service #{@resource[:name]} start type is #{start_type}") case start_type when :SERVICE_AUTO_START, :SERVICE_BOOT_START, :SERVICE_SYSTEM_START :true when :SERVICE_DEMAND_START :manual when :SERVICE_DISABLED :false else raise Puppet::Error.new(_("Unknown start type: %{start_type}") % { start_type: start_type }) end rescue => detail raise Puppet::Error.new(_("Cannot get start type %{resource_name}, error was: %{detail}") % { resource_name: @resource[:name], detail: detail }, detail ) end def start if status == :paused Puppet::Util::Windows::Service.resume(@resource[:name]) return end # status == :stopped here if enabled? == :false # If disabled and not managing enable, respect disabled and fail. if @resource[:enable].nil? raise Puppet::Error.new(_("Will not start disabled service %{resource_name} without managing enable. Specify 'enable => false' to override.") % { resource_name: @resource[:name] }) # Otherwise start. If enable => false, we will later sync enable and # disable the service again. elsif @resource[:enable] == :true enable else manual_start end end Puppet::Util::Windows::Service.start(@resource[:name]) end def stop Puppet::Util::Windows::Service.stop(@resource[:name]) end def status return :stopped unless Puppet::Util::Windows::Service.exists?(@resource[:name]) current_state = Puppet::Util::Windows::Service.service_state(@resource[:name]) state = case current_state when :SERVICE_STOPPED, :SERVICE_STOP_PENDING :stopped when :SERVICE_PAUSED, :SERVICE_PAUSE_PENDING :paused when :SERVICE_RUNNING, :SERVICE_CONTINUE_PENDING, :SERVICE_START_PENDING :running else raise Puppet::Error.new(_("Unknown service state '%{current_state}' for service '%{resource_name}'") % { current_state: current_state, resource_name: @resource[:name] }) end debug("Service #{@resource[:name]} is #{current_state}") return state end # returns all providers for all existing services and startup state def self.instances services = [] Puppet::Util::Windows::Service.services.each do |service_name, _| services.push(new(:name => service_name)) end services end end ���������������������������������puppet-5.5.10/lib/puppet/provider/user/�������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�017712� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/user/aix.rb�������������������������������������������������������0000644�0052762�0001160�00000027330�13417161721�021020� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# User Puppet provider for AIX. It uses standard commands to manage users: # mkuser, rmuser, lsuser, chuser # # Notes: # - AIX users can have expiry date defined with minute granularity, # but Puppet does not allow it. There is a ticket open for that (#5431) # # - AIX maximum password age is in WEEKs, not days # # See https://puppet.com/docs/puppet/latest/provider_development.html # for more information require 'puppet/provider/aix_object' require 'puppet/util/posix' require 'tempfile' require 'date' Puppet::Type.type(:user).provide :aix, :parent => Puppet::Provider::AixObject do desc "User management for AIX." defaultfor :operatingsystem => :aix confine :operatingsystem => :aix # Commands that manage the element commands :list => "/usr/sbin/lsuser" commands :add => "/usr/bin/mkuser" commands :delete => "/usr/sbin/rmuser" commands :modify => "/usr/bin/chuser" commands :chpasswd => "/bin/chpasswd" # Provider features has_features :manages_aix_lam has_features :manages_homedir, :manages_passwords, :manages_shell has_features :manages_expiry, :manages_password_age class << self def group_provider @group_provider ||= Puppet::Type.type(:group).provider(:aix) end # Define some Puppet Property => AIX Attribute (and vice versa) # conversion functions here. def gid_to_pgrp(provider, gid) group = group_provider.find(gid, provider.ia_module_args) group[:name] end def pgrp_to_gid(provider, pgrp) group = group_provider.find(pgrp, provider.ia_module_args) group[:gid] end def expiry_to_expires(expiry) return '0' if expiry == "0000-00-00" || expiry.to_sym == :absent DateTime.parse(expiry, "%Y-%m-%d %H:%M") .strftime("%m%d%H%M%y") end def expires_to_expiry(provider, expires) return :absent if expires == '0' unless (match_obj = /\A(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)\z/.match(expires)) #TRANSLATORS 'AIX' is the name of an operating system and should not be translated Puppet.warning(_("Could not convert AIX expires date '%{expires}' on %{class_name}[%{resource_name}]") % { expires: expires, class_name: provider.resource.class.name, resource_name: provider.resource.name }) return :absent end month, day, year = match_obj[1], match_obj[2], match_obj[-1] return "20#{year}-#{month}-#{day}" end # We do some validation before-hand to ensure the value's an Array, # a String, etc. in the property. This routine does a final check to # ensure our value doesn't have whitespace before we convert it to # an attribute. def groups_property_to_attribute(groups) if groups =~ /\s/ raise ArgumentError, _("Invalid value %{groups}: Groups must be comma separated!") % { groups: groups } end groups end # We do not directly use the groups attribute value because that will # always include the primary group, even if our user is not one of its # members. Instead, we retrieve our property value by parsing the etc/group file, # which matches what we do on our other POSIX platforms like Linux and Solaris. # # See https://www.ibm.com/support/knowledgecenter/en/ssw_aix_72/com.ibm.aix.files/group_security.htm def groups_attribute_to_property(provider, _groups) Puppet::Util::POSIX.groups_of(provider.resource[:name]).join(',') end end mapping puppet_property: :comment, aix_attribute: :gecos mapping puppet_property: :expiry, aix_attribute: :expires, property_to_attribute: method(:expiry_to_expires), attribute_to_property: method(:expires_to_expiry) mapping puppet_property: :gid, aix_attribute: :pgrp, property_to_attribute: method(:gid_to_pgrp), attribute_to_property: method(:pgrp_to_gid) mapping puppet_property: :groups, property_to_attribute: method(:groups_property_to_attribute), attribute_to_property: method(:groups_attribute_to_property) mapping puppet_property: :home mapping puppet_property: :shell numeric_mapping puppet_property: :uid, aix_attribute: :id numeric_mapping puppet_property: :password_max_age, aix_attribute: :maxage numeric_mapping puppet_property: :password_min_age, aix_attribute: :minage numeric_mapping puppet_property: :password_warn_days, aix_attribute: :pwdwarntime # Now that we have all of our mappings, let's go ahead and make # the resource methods (property getters + setters for our mapped # properties + a getter for the attributes property). mk_resource_methods # Setting the primary group (pgrp attribute) on AIX causes both the # current and new primary groups to be included in our user's groups, # which is undesirable behavior. Thus, this custom setter resets the # 'groups' property back to its previous value after setting the primary # group. def gid=(value) old_pgrp = gid cur_groups = groups set(:gid, value) begin self.groups = cur_groups rescue Puppet::Error => detail raise Puppet::Error, _("Could not reset the groups property back to %{cur_groups} after setting the primary group on %{resource}[%{name}]. This means that the previous primary group of %{old_pgrp} and the new primary group of %{new_pgrp} have been added to %{cur_groups}. You will need to manually reset the groups property if this is undesirable behavior. Detail: %{detail}") % { cur_groups: cur_groups, resource: @resource.class.name, name: @resource.name, old_pgrp: old_pgrp, new_pgrp: value, detail: detail }, detail.backtrace end end # Helper function that parses the password from the given # password filehandle. This is here to make testing easier # for #password since we cannot configure Mocha to mock out # a method and have it return a block's value, meaning we # cannot test #password directly (not in a simple and obvious # way, at least). # @api private def parse_password(f) # From the docs, a user stanza is formatted as (newlines are explicitly # stated here for clarity): # <user>:\n # <attribute1>=<value1>\n # <attribute2>=<value2>\n # # First, find our user stanza stanza = f.each_line.find { |line| line =~ /\A#{@resource[:name]}:/ } return :absent unless stanza # Now find the password line, if it exists. Note our call to each_line here # will pick up right where we left off. match_obj = nil f.each_line.find do |line| # Break if we find another user stanza. This means our user # does not have a password. break if line =~ /^\S+:$/ match_obj = /password = (\S+)/.match(line) end return :absent unless match_obj match_obj[1] end #- **password** # The user's password, in whatever encrypted format the local machine # requires. Be sure to enclose any value that includes a dollar sign ($) # in single quotes ('). Requires features manages_passwords. # # Retrieve the password parsing the /etc/security/passwd file. def password # AIX reference indicates this file is ASCII # https://www.ibm.com/support/knowledgecenter/en/ssw_aix_72/com.ibm.aix.files/passwd_security.htm Puppet::FileSystem.open("/etc/security/passwd", nil, "r:ASCII") do |f| parse_password(f) end end def password=(value) user = @resource[:name] begin # Puppet execute does not support strings as input, only files. # The password is expected to be in an encrypted format given -e is specified: # https://www.ibm.com/support/knowledgecenter/ssw_aix_71/com.ibm.aix.cmds1/chpasswd.htm # /etc/security/passwd is specified as an ASCII file per the AIX documentation tempfile = nil tempfile = Tempfile.new("puppet_#{user}_pw", :encoding => Encoding::ASCII) tempfile << "#{user}:#{value}\n" tempfile.close() # Options '-e', '-c', use encrypted password and clear flags # Must receive "user:enc_password" as input # command, arguments = {:failonfail => true, :combine => true} # Fix for bugs #11200 and #10915 cmd = [self.class.command(:chpasswd), *ia_module_args, '-e', '-c'] execute_options = { :failonfail => false, :combine => true, :stdinfile => tempfile.path } output = execute(cmd, execute_options) # chpasswd can return 1, even on success (at least on AIX 6.1); empty output # indicates success if output != "" raise Puppet::ExecutionFailure, "chpasswd said #{output}" end rescue Puppet::ExecutionFailure => detail raise Puppet::Error, "Could not set password on #{@resource.class.name}[#{@resource.name}]: #{detail}", detail.backtrace ensure if tempfile # Extra close will noop. This is in case the write to our tempfile # fails. tempfile.close() tempfile.delete() end end end def create super # We specify the 'groups' AIX attribute in AixObject's create method # when creating our user. However, this does not always guarantee that # our 'groups' property is set to the right value. For example, the # primary group will always be included in the 'groups' property. This is # bad if we're explicitly managing the 'groups' property under inclusive # membership, and we are not specifying the primary group in the 'groups' # property value. # # Setting the groups property here a second time will ensure that our user is # created and in the right state. Note that this is an idempotent operation, # so if AixObject's create method already set it to the right value, then this # will noop. if (groups = @resource.should(:groups)) self.groups = groups end if (password = @resource.should(:password)) self.password = password end end # UNSUPPORTED #- **profile_membership** # Whether specified roles should be treated as the only roles # of which the user is a member or whether they should merely # be treated as the minimum membership list. Valid values are # `inclusive`, `minimum`. # UNSUPPORTED #- **profiles** # The profiles the user has. Multiple profiles should be # specified as an array. Requires features manages_solaris_rbac. # UNSUPPORTED #- **project** # The name of the project associated with a user Requires features # manages_solaris_rbac. # UNSUPPORTED #- **role_membership** # Whether specified roles should be treated as the only roles # of which the user is a member or whether they should merely # be treated as the minimum membership list. Valid values are # `inclusive`, `minimum`. # UNSUPPORTED #- **roles** # The roles the user has. Multiple roles should be # specified as an array. Requires features manages_solaris_rbac. # UNSUPPORTED #- **key_membership** # Whether specified key value pairs should be treated as the only # attributes # of the user or whether they should merely # be treated as the minimum list. Valid values are `inclusive`, # `minimum`. # UNSUPPORTED #- **keys** # Specify user attributes in an array of keyvalue pairs Requires features # manages_solaris_rbac. # UNSUPPORTED #- **allowdupe** # Whether to allow duplicate UIDs. Valid values are `true`, `false`. # UNSUPPORTED #- **auths** # The auths the user has. Multiple auths should be # specified as an array. Requires features manages_solaris_rbac. # UNSUPPORTED #- **auth_membership** # Whether specified auths should be treated as the only auths # of which the user is a member or whether they should merely # be treated as the minimum membership list. Valid values are # `inclusive`, `minimum`. # UNSUPPORTED end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/user/directoryservice.rb������������������������������������������0000644�0052762�0001160�00000062665�13417161721�023636� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet' require 'puppet/util/plist' if Puppet.features.cfpropertylist? require 'base64' Puppet::Type.type(:user).provide :directoryservice do desc "User management on OS X." ## ## ## Provider Settings ## ## ## # Provider command declarations commands :uuidgen => '/usr/bin/uuidgen' commands :dsimport => '/usr/bin/dsimport' commands :dscl => '/usr/bin/dscl' commands :dscacheutil => '/usr/bin/dscacheutil' # Provider confines and defaults confine :operatingsystem => :darwin confine :feature => :cfpropertylist defaultfor :operatingsystem => :darwin # Need this to create getter/setter methods automagically # This command creates methods that return @property_hash[:value] mk_resource_methods # JJM: OS X can manage passwords. has_feature :manages_passwords # 10.8 Passwords use a PBKDF2 salt value has_features :manages_password_salt #provider can set the user's shell has_feature :manages_shell ## ## ## Class Methods ## ## ## # This method exists to map the dscl values to the correct Puppet # properties. This stays relatively consistent, but who knows what # Apple will do next year... def self.ds_to_ns_attribute_map { 'RecordName' => :name, 'PrimaryGroupID' => :gid, 'NFSHomeDirectory' => :home, 'UserShell' => :shell, 'UniqueID' => :uid, 'RealName' => :comment, 'Password' => :password, 'GeneratedUID' => :guid, 'IPAddress' => :ip_address, 'ENetAddress' => :en_address, 'GroupMembership' => :members, } end def self.ns_to_ds_attribute_map @ns_to_ds_attribute_map ||= ds_to_ns_attribute_map.invert end # Prefetching is necessary to use @property_hash inside any setter methods. # self.prefetch uses self.instances to gather an array of user instances # on the system, and then populates the @property_hash instance variable # with attribute data for the specific instance in question (i.e. it # gathers the 'is' values of the resource into the @property_hash instance # variable so you don't have to read from the system every time you need # to gather the 'is' values for a resource. The downside here is that # populating this instance variable for every resource on the system # takes time and front-loads your Puppet run. def self.prefetch(resources) instances.each do |prov| if resource = resources[prov.name] resource.provider = prov end end end # This method assembles an array of provider instances containing # information about every instance of the user type on the system (i.e. # every user and its attributes). The `puppet resource` command relies # on self.instances to gather an array of user instances in order to # display its output. def self.instances get_all_users.collect do |user| self.new(generate_attribute_hash(user)) end end # Return an array of hashes containing information about every user on # the system. def self.get_all_users Puppet::Util::Plist.parse_plist(dscl '-plist', '.', 'readall', '/Users') end # This method accepts an individual user plist, passed as a hash, and # strips the dsAttrTypeStandard: prefix that dscl adds for each key. # An attribute hash is assembled and returned from the properties # supported by the user type. def self.generate_attribute_hash(input_hash) attribute_hash = {} input_hash.each_key do |key| ds_attribute = key.sub("dsAttrTypeStandard:", "") next unless ds_to_ns_attribute_map.keys.include?(ds_attribute) ds_value = input_hash[key] case ds_to_ns_attribute_map[ds_attribute] when :gid, :uid # OS X stores objects like uid/gid as strings. # Try casting to an integer for these cases to be # consistent with the other providers and the group type # validation begin ds_value = Integer(ds_value[0]) rescue ArgumentError ds_value = ds_value[0] end else ds_value = ds_value[0] end attribute_hash[ds_to_ns_attribute_map[ds_attribute]] = ds_value end attribute_hash[:ensure] = :present attribute_hash[:provider] = :directoryservice attribute_hash[:shadowhashdata] = input_hash['dsAttrTypeNative:ShadowHashData'] ############## # Get Groups # ############## groups_array = [] get_list_of_groups.each do |group| if group["dsAttrTypeStandard:GroupMembership"] and group["dsAttrTypeStandard:GroupMembership"].include?(attribute_hash[:name]) groups_array << group["dsAttrTypeStandard:RecordName"][0] end if group["dsAttrTypeStandard:GroupMembers"] and group["dsAttrTypeStandard:GroupMembers"].include?(attribute_hash[:guid]) groups_array << group["dsAttrTypeStandard:RecordName"][0] end end attribute_hash[:groups] = groups_array.uniq.sort.join(',') ################################ # Get Password/Salt/Iterations # ################################ if attribute_hash[:shadowhashdata].nil? or attribute_hash[:shadowhashdata].empty? attribute_hash[:password] = '*' else embedded_binary_plist = get_embedded_binary_plist(attribute_hash[:shadowhashdata]) if embedded_binary_plist['SALTED-SHA512-PBKDF2'] attribute_hash[:password] = get_salted_sha512_pbkdf2('entropy', embedded_binary_plist) attribute_hash[:salt] = get_salted_sha512_pbkdf2('salt', embedded_binary_plist) attribute_hash[:iterations] = get_salted_sha512_pbkdf2('iterations', embedded_binary_plist) elsif embedded_binary_plist['SALTED-SHA512'] attribute_hash[:password] = get_salted_sha512(embedded_binary_plist) end end attribute_hash end def self.get_os_version @os_version ||= Facter.value(:macosx_productversion_major) end # Use dscl to retrieve an array of hashes containing attributes about all # of the local groups on the machine. def self.get_list_of_groups @groups ||= Puppet::Util::Plist.parse_plist(dscl '-plist', '.', 'readall', '/Groups') end # Perform a dscl lookup at the path specified for the specific keyname # value. The value returned is the first item within the array returned # from dscl def self.get_attribute_from_dscl(path, username, keyname) Puppet::Util::Plist.parse_plist(dscl '-plist', '.', 'read', "/#{path}/#{username}", keyname) end # The plist embedded in the ShadowHashData key is a binary plist. The # plist library doesn't read binary plists, so we need to # extract the binary plist, convert it to XML, and return it. def self.get_embedded_binary_plist(shadow_hash_data) embedded_binary_plist = Array(shadow_hash_data[0].delete(' ')).pack('H*') convert_binary_to_hash(embedded_binary_plist) end # This method will accept a hash and convert it to a binary plist (string value). def self.convert_hash_to_binary(plist_data) Puppet.debug('Converting plist hash to binary') Puppet::Util::Plist.dump_plist(plist_data, :binary) end # This method will accept a binary plist (as a string) and convert it to a hash. def self.convert_binary_to_hash(plist_data) Puppet.debug('Converting binary plist to hash') Puppet::Util::Plist.parse_plist(plist_data) end # The salted-SHA512 password hash in 10.7 is stored in the 'SALTED-SHA512' # key as binary data. That data is extracted and converted to a hex string. def self.get_salted_sha512(embedded_binary_plist) embedded_binary_plist['SALTED-SHA512'].unpack("H*")[0] end # This method reads the passed embedded_binary_plist hash and returns values # according to which field is passed. Arguments passed are the hash # containing the value read from the 'ShadowHashData' key in the User's # plist, and the field to be read (one of 'entropy', 'salt', or 'iterations') def self.get_salted_sha512_pbkdf2(field, embedded_binary_plist) case field when 'salt', 'entropy' embedded_binary_plist['SALTED-SHA512-PBKDF2'][field].unpack('H*').first when 'iterations' Integer(embedded_binary_plist['SALTED-SHA512-PBKDF2'][field]) else raise Puppet::Error, 'Puppet has tried to read an incorrect value from the ' + "SALTED-SHA512-PBKDF2 hash. Acceptable fields are 'salt', " + "'entropy', or 'iterations'." end end # In versions 10.5 and 10.6 of OS X, the password hash is stored in a file # in the /var/db/shadow/hash directory that matches the GUID of the user. def self.get_sha1(guid) password_hash = nil password_hash_file = "#{password_hash_dir}/#{guid}" if Puppet::FileSystem.exist?(password_hash_file) and File.file?(password_hash_file) raise Puppet::Error, "Could not read password hash file at #{password_hash_file}" if not File.readable?(password_hash_file) f = File.new(password_hash_file) password_hash = f.read f.close end password_hash end ## ## ## Ensurable Methods ## ## ## def exists? begin dscl '.', 'read', "/Users/#{@resource.name}" rescue Puppet::ExecutionFailure => e Puppet.debug("User was not found, dscl returned: #{e.inspect}") return false end true end # This method is called if ensure => present is passed and the exists? # method returns false. Dscl will directly set most values, but the # setter methods will be used for any exceptions. def create create_new_user(@resource.name) # Retrieve the user's GUID @guid = self.class.get_attribute_from_dscl('Users', @resource.name, 'GeneratedUID')['dsAttrTypeStandard:GeneratedUID'][0] # Get an array of valid User type properties valid_properties = Puppet::Type.type('User').validproperties # Iterate through valid User type properties valid_properties.each do |attribute| next if attribute == :ensure value = @resource.should(attribute) # Value defaults if value.nil? value = case attribute when :gid '20' when :uid next_system_id when :comment @resource.name when :shell '/bin/bash' when :home "/Users/#{@resource.name}" else nil end end # Ensure group names are converted to integers. value = Puppet::Util.gid(value) if attribute == :gid ## Set values ## # For the :password and :groups properties, call the setter methods # to enforce those values. For everything else, use dscl with the # ns_to_ds_attribute_map to set the appropriate values. if value != "" and not value.nil? case attribute when :password self.password = value when :iterations self.iterations = value when :salt self.salt = value when :groups value.split(',').each do |group| merge_attribute_with_dscl('Groups', group, 'GroupMembership', @resource.name) merge_attribute_with_dscl('Groups', group, 'GroupMembers', @guid) end else merge_attribute_with_dscl('Users', @resource.name, self.class.ns_to_ds_attribute_map[attribute], value) end end end end # This method is called when ensure => absent has been set. # Deleting a user is handled by dscl def delete dscl '.', '-delete', "/Users/#{@resource.name}" end ## ## ## Getter/Setter Methods ## ## ## # In the setter method we're only going to take action on groups for which # the user is not currently a member. def groups=(value) guid = self.class.get_attribute_from_dscl('Users', @resource.name, 'GeneratedUID')['dsAttrTypeStandard:GeneratedUID'][0] groups_to_add = value.split(',') - groups.split(',') groups_to_add.each do |group| merge_attribute_with_dscl('Groups', group, 'GroupMembership', @resource.name) merge_attribute_with_dscl('Groups', group, 'GroupMembers', guid) end end # If you thought GETTING a password was bad, try SETTING it. This method # makes me want to cry. A thousand tears... # # I've been unsuccessful in tracking down a way to set the password for # a user using dscl that DOESN'T require passing it as plaintext. We were # also unable to get dsimport to work like this. Due to these downfalls, # the sanest method requires opening the user's plist, dropping in the # password hash, and serializing it back to disk. The problems with THIS # method revolve around dscl. Any time you directly modify a user's plist, # you need to flush the cache that dscl maintains. def password=(value) if self.class.get_os_version == '10.7' if value.length != 136 raise Puppet::Error, "OS X 10.7 requires a Salted SHA512 hash password of 136 characters. Please check your password and try again." end else if value.length != 256 raise Puppet::Error, "OS X versions > 10.7 require a Salted SHA512 PBKDF2 password hash of 256 characters. Please check your password and try again." end assert_full_pbkdf2_password end # Methods around setting the password on OS X are the ONLY methods that # cannot use dscl (because the only way to set it via dscl is by passing # a plaintext password - which is bad). Because of this, we have to change # the user's plist directly. DSCL has its own caching mechanism, which # means that every time we call dscl in this provider we're not directly # changing values on disk (instead, those calls are cached and written # to disk according to Apple's prioritization algorithms). When Puppet # needs to set the password property on OS X > 10.6, the provider has to # tell dscl to write its cache to disk before modifying the user's # plist. The 'dscacheutil -flushcache' command does this. Another issue # is how fast Puppet makes calls to dscl and how long it takes dscl to # enter those calls into its cache. We have to sleep for 2 seconds before # flushing the dscl cache to allow all dscl calls to get INTO the cache # first. This could be made faster (and avoid a sleep call) by finding # a way to enter calls into the dscl cache faster. A sleep time of 1 # second would intermittently require a second Puppet run to set # properties, so 2 seconds seems to be the minimum working value. sleep 2 flush_dscl_cache write_password_to_users_plist(value) # Since we just modified the user's plist, we need to flush the ds cache # again so dscl can pick up on the changes we made. flush_dscl_cache end # The iterations and salt properties, like the password property, can only # be modified by directly changing the user's plist. Because of this fact, # we have to treat the ds cache just like you would in the password= # method. def iterations=(value) if (Puppet::Util::Package.versioncmp(self.class.get_os_version, '10.7') > 0) assert_full_pbkdf2_password sleep 2 flush_dscl_cache users_plist = get_users_plist(@resource.name) shadow_hash_data = get_shadow_hash_data(users_plist) set_salted_pbkdf2(users_plist, shadow_hash_data, 'iterations', value) flush_dscl_cache end end # The iterations and salt properties, like the password property, can only # be modified by directly changing the user's plist. Because of this fact, # we have to treat the ds cache just like you would in the password= # method. def salt=(value) if (Puppet::Util::Package.versioncmp(self.class.get_os_version, '10.7') > 0) assert_full_pbkdf2_password sleep 2 flush_dscl_cache users_plist = get_users_plist(@resource.name) shadow_hash_data = get_shadow_hash_data(users_plist) set_salted_pbkdf2(users_plist, shadow_hash_data, 'salt', value) flush_dscl_cache end end ##### # Dynamically create setter methods for dscl properties ##### # # Setter methods are only called when a resource currently has a value for # that property and it needs changed (true here since all of these values # have a default that is set in the create method). We don't want to merge # in additional values if an incorrect value is set, we want to CHANGE it. # When using the -change argument in dscl, the old value needs to be passed # first (followed by the new value). Because of this, we get the current # value from the @property_hash variable and then use the value passed as # the new value. Because we're prefetching instances of the provider, it's # possible that the value determined at the start of the run may be stale # (i.e. someone changed the value by hand during a Puppet run) - if that's # the case we rescue the error from dscl and alert the user. # # In the event that the user doesn't HAVE a value for the attribute, the # provider should use the -merge option with dscl to add the attribute value # for the user record ['home', 'uid', 'gid', 'comment', 'shell'].each do |setter_method| define_method("#{setter_method}=") do |value| if @property_hash[setter_method.intern] begin dscl '.', '-change', "/Users/#{resource.name}", self.class.ns_to_ds_attribute_map[setter_method.intern], @property_hash[setter_method.intern], value rescue Puppet::ExecutionFailure => e raise Puppet::Error, "Cannot set the #{setter_method} value of '#{value}' for user " + "#{@resource.name} due to the following error: #{e.inspect}", e.backtrace end else begin dscl '.', '-merge', "/Users/#{resource.name}", self.class.ns_to_ds_attribute_map[setter_method.intern], value rescue Puppet::ExecutionFailure => e raise Puppet::Error, "Cannot set the #{setter_method} value of '#{value}' for user " + "#{@resource.name} due to the following error: #{e.inspect}", e.backtrace end end end end ## ## ## Helper Methods ## ## ## def assert_full_pbkdf2_password missing = [:password, :salt, :iterations].select { |parameter| @resource[parameter].nil? } if !missing.empty? raise Puppet::Error, "OS X versions > 10\.7 use PBKDF2 password hashes, which requires all three of salt, iterations, and password hash. This resource is missing: #{missing.join(', ')}." end end def users_plist_dir '/var/db/dslocal/nodes/Default/users' end def self.password_hash_dir '/var/db/shadow/hash' end # This method will merge in a given value using dscl def merge_attribute_with_dscl(path, username, keyname, value) begin dscl '.', '-merge', "/#{path}/#{username}", keyname, value rescue Puppet::ExecutionFailure => detail raise Puppet::Error, "Could not set the dscl #{keyname} key with value: #{value} - #{detail.inspect}", detail.backtrace end end # Create the new user with dscl def create_new_user(username) dscl '.', '-create', "/Users/#{username}" end # Get the next available uid on the system by getting a list of user ids, # sorting them, grabbing the last one, and adding a 1. Scientific stuff here. def next_system_id(min_id=20) dscl_output = dscl '.', '-list', '/Users', 'uid' # We're ok with throwing away negative uids here. Also, remove nil values. user_ids = dscl_output.split.compact.collect { |l| l.to_i if l.match(/^\d+$/) } ids = user_ids.compact!.sort! { |a,b| a.to_f <=> b.to_f } # We're just looking for an unused id in our sorted array. ids.each_index do |i| next_id = ids[i] + 1 return next_id if ids[i+1] != next_id and next_id >= min_id end end # This method is only called on version 10.7 or greater. On 10.7 machines, # passwords are set using a salted-SHA512 hash, and on 10.8 machines, # passwords are set using PBKDF2. It's possible to have users on 10.8 # who have upgraded from 10.7 and thus have a salted-SHA512 password hash. # If we encounter this, do what 10.8 does - remove that key and give them # a 10.8-style PBKDF2 password. def write_password_to_users_plist(value) users_plist = get_users_plist(@resource.name) shadow_hash_data = get_shadow_hash_data(users_plist) if self.class.get_os_version == '10.7' set_salted_sha512(users_plist, shadow_hash_data, value) else # It's possible that a user could exist on the system and NOT have # a ShadowHashData key (especially if the system was upgraded from 10.6). # In this case, a conditional check is needed to determine if the # shadow_hash_data variable is a Hash (it would be false if the key # didn't exist for this user on the system). If the shadow_hash_data # variable IS a Hash and contains the 'SALTED-SHA512' key (indicating an # older 10.7-style password hash), it will be deleted and a newer # 10.8-style (PBKDF2) password hash will be generated. if (shadow_hash_data.class == Hash) && (shadow_hash_data.has_key?('SALTED-SHA512')) shadow_hash_data.delete('SALTED-SHA512') end set_salted_pbkdf2(users_plist, shadow_hash_data, 'entropy', value) end end def flush_dscl_cache dscacheutil '-flushcache' end def get_users_plist(username) # This method will retrieve the data stored in a user's plist and # return it as a native Ruby hash. path = "#{users_plist_dir}/#{username}.plist" Puppet::Util::Plist.read_plist_file(path) end # This method will return the binary plist that's embedded in the # ShadowHashData key of a user's plist, or false if it doesn't exist. def get_shadow_hash_data(users_plist) if users_plist['ShadowHashData'] password_hash_plist = users_plist['ShadowHashData'][0] self.class.convert_binary_to_hash(password_hash_plist) else false end end # This method will embed the binary plist data comprising the user's # password hash (and Salt/Iterations value if the OS is 10.8 or greater) # into the ShadowHashData key of the user's plist. def set_shadow_hash_data(users_plist, binary_plist) binary_plist = Puppet::Util::Plist.string_to_blob(binary_plist) if users_plist.has_key?('ShadowHashData') users_plist['ShadowHashData'][0] = binary_plist else users_plist['ShadowHashData'] = [binary_plist] end write_users_plist_to_disk(users_plist) end # This method accepts an argument of a hex password hash, and base64 # decodes it into a format that OS X 10.7 and 10.8 will store # in the user's plist. def base64_decode_string(value) Base64.decode64([[value].pack("H*")].pack("m").strip) end # Puppet requires a salted-sha512 password hash for 10.7 users to be passed # in Hex, but the embedded plist stores that value as a Base64 encoded # string. This method converts the string and calls the # set_shadow_hash_data method to serialize and write the plist to disk. def set_salted_sha512(users_plist, shadow_hash_data, value) unless shadow_hash_data shadow_hash_data = Hash.new shadow_hash_data['SALTED-SHA512'] = '' end shadow_hash_data['SALTED-SHA512'] = base64_decode_string(value) binary_plist = self.class.convert_hash_to_binary(shadow_hash_data) set_shadow_hash_data(users_plist, binary_plist) end # This method accepts a passed value and one of three fields: 'salt', # 'entropy', or 'iterations'. These fields correspond with the fields # utilized in a PBKDF2 password hashing system # (see https://en.wikipedia.org/wiki/PBKDF2 ) where 'entropy' is the # password hash, 'salt' is the password hash salt value, and 'iterations' # is an integer recommended to be > 10,000. The remaining arguments are # the user's plist itself, and the shadow_hash_data hash containing the # existing PBKDF2 values. def set_salted_pbkdf2(users_plist, shadow_hash_data, field, value) shadow_hash_data = Hash.new unless shadow_hash_data shadow_hash_data['SALTED-SHA512-PBKDF2'] = Hash.new unless shadow_hash_data['SALTED-SHA512-PBKDF2'] case field when 'salt', 'entropy' shadow_hash_data['SALTED-SHA512-PBKDF2'][field] = Puppet::Util::Plist.string_to_blob(base64_decode_string(value)) when 'iterations' shadow_hash_data['SALTED-SHA512-PBKDF2'][field] = Integer(value) else raise Puppet::Error "Puppet has tried to set an incorrect field for the 'SALTED-SHA512-PBKDF2' hash. Acceptable fields are 'salt', 'entropy', or 'iterations'." end # on 10.8, this field *must* contain 8 stars, or authentication will # fail. users_plist['passwd'] = ('*' * 8) # Convert shadow_hash_data to a binary plist, and call the # set_shadow_hash_data method to serialize and write the data # back to the user's plist. binary_plist = self.class.convert_hash_to_binary(shadow_hash_data) set_shadow_hash_data(users_plist, binary_plist) end # This method will accept a plist in XML format, save it to disk, convert # the plist to a binary format, and flush the dscl cache. def write_users_plist_to_disk(users_plist) Puppet::Util::Plist.write_plist_file(users_plist, "#{users_plist_dir}/#{@resource.name}.plist", :binary) end # This is a simple wrapper method for writing values to a file. def write_to_file(filename, value) Puppet.deprecation_warning("Puppet::Type.type(:user).provider(:directoryservice).write_to_file is deprecated and will be removed in Puppet 5.") begin File.open(filename, 'w') { |f| f.write(value)} rescue Errno::EACCES => detail raise Puppet::Error, "Could not write to file #{filename}: #{detail}", detail.backtrace end end end ���������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/user/hpux.rb������������������������������������������������������0000644�0052762�0001160�00000005370�13417161721�021223� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.type(:user).provide :hpuxuseradd, :parent => :useradd do desc "User management for HP-UX. This provider uses the undocumented `-F` switch to HP-UX's special `usermod` binary to work around the fact that its standard `usermod` cannot make changes while the user is logged in. New functionality provides for changing trusted computing passwords and resetting password expirations under trusted computing." defaultfor :operatingsystem => "hp-ux" confine :operatingsystem => "hp-ux" commands :modify => "/usr/sam/lbin/usermod.sam", :delete => "/usr/sam/lbin/userdel.sam", :add => "/usr/sam/lbin/useradd.sam" options :comment, :method => :gecos options :groups, :flag => "-G" options :home, :flag => "-d", :method => :dir verify :gid, "GID must be an integer" do |value| value.is_a? Integer end verify :groups, "Groups must be comma-separated" do |value| value !~ /\s/ end has_features :manages_homedir, :allows_duplicates, :manages_passwords def deletecmd super.insert(1,"-F") end def modifycmd(param,value) cmd = super(param, value) cmd << "-F" if trusted then # Append an additional command to reset the password age to 0 # until a workaround with expiry module can be found for trusted # computing. cmd << ";" cmd << "/usr/lbin/modprpw" cmd << "-v" cmd << "-l" cmd << "#{resource.name}" end cmd end def password # Password management routine for trusted and non-trusted systems #temp="" while ent = Etc.getpwent() do if ent.name == resource.name temp=ent.name break end end Etc.endpwent() if !temp return nil end ent = Etc.getpwnam(resource.name) if ent.passwd == "*" # Either no password or trusted password, check trusted file_name="/tcb/files/auth/#{resource.name.chars.first}/#{resource.name}" if File.file?(file_name) # Found the tcb user for the specific user, now get passwd File.open(file_name).each do |line| if ( line =~ /u_pwd/ ) temp_passwd=line.split(":")[1].split("=")[1] ent.passwd = temp_passwd return ent.passwd end end else debug "No trusted computing user file #{file_name} found." end else return ent.passwd end end def trusted # Check to see if the HP-UX box is running in trusted compute mode # UID for root should always be 0 trusted_sys = exec_getprpw('root','-m uid') if trusted_sys.chomp == "uid=0" return true else return false end end def exec_getprpw(user,opts) Puppet::Util::Execution.execute("/usr/lbin/getprpw #{opts} #{user}", { :combine => true }) end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/user/ldap.rb������������������������������������������������������0000644�0052762�0001160�00000007105�13417161721�021155� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/ldap' Puppet::Type.type(:user).provide :ldap, :parent => Puppet::Provider::Ldap do desc "User management via LDAP. This provider requires that you have valid values for all of the LDAP-related settings in `puppet.conf`, including `ldapbase`. You will almost definitely need settings for `ldapuser` and `ldappassword` in order for your clients to write to LDAP. Note that this provider will automatically generate a UID for you if you do not specify one, but it is a potentially expensive operation, as it iterates across all existing users to pick the appropriate next one." confine :feature => :ldap, :false => (Puppet[:ldapuser] == "") has_feature :manages_passwords, :manages_shell manages(:posixAccount, :person).at("ou=People").named_by(:uid).and.maps :name => :uid, :password => :userPassword, :comment => :cn, :uid => :uidNumber, :gid => :gidNumber, :home => :homeDirectory, :shell => :loginShell # Use the last field of a space-separated array as # the sn. LDAP requires a surname, for some stupid reason. manager.generates(:sn).from(:cn).with do |cn| cn[0].split(/\s+/)[-1] end # Find the next uid after the current largest uid. provider = self manager.generates(:uidNumber).with do largest = 500 if existing = provider.manager.search existing.each do |hash| next unless value = hash[:uid] num = value[0].to_i largest = num if num > largest end end largest + 1 end # Convert our gid to a group name, if necessary. def gid=(value) value = group2id(value) unless value.is_a?(Integer) @property_hash[:gid] = value end # Find all groups this user is a member of in ldap. def groups # We want to cache the current result, so we know if we # have to remove old values. unless @property_hash[:groups] unless result = group_manager.search("memberUid=#{name}") return @property_hash[:groups] = :absent end return @property_hash[:groups] = result.collect { |r| r[:name] }.sort.join(",") end @property_hash[:groups] end # Manage the list of groups this user is a member of. def groups=(values) should = values.split(",") if groups == :absent is = [] else is = groups.split(",") end modes = {} [is, should].flatten.uniq.each do |group| # Skip it when they're in both next if is.include?(group) and should.include?(group) # We're adding a group. modes[group] = :add and next unless is.include?(group) # We're removing a group. modes[group] = :remove and next unless should.include?(group) end modes.each do |group, form| self.fail "Could not find ldap group #{group}" unless ldap_group = group_manager.find(group) current = ldap_group[:members] if form == :add if current.is_a?(Array) and ! current.empty? new = current + [name] else new = [name] end else new = current - [name] new = :absent if new.empty? end group_manager.update(group, {:ensure => :present, :members => current}, {:ensure => :present, :members => new}) end end # Convert a gropu name to an id. def group2id(group) Puppet::Type.type(:group).provider(:ldap).name2id(group) end private def group_manager Puppet::Type.type(:group).provider(:ldap).manager end def group_properties(values) if values.empty? or values == :absent {:ensure => :present} else {:ensure => :present, :members => values} end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/user/openbsd.rb���������������������������������������������������0000644�0052762�0001160�00000004322�13417161721�021665� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/error' Puppet::Type.type(:user).provide :openbsd, :parent => :useradd do desc "User management via `useradd` and its ilk for OpenBSD. Note that you will need to install Ruby's shadow password library (package known as `ruby-shadow`) if you wish to manage user passwords." commands :add => "useradd", :delete => "userdel", :modify => "usermod", :password => "passwd" defaultfor :operatingsystem => :openbsd confine :operatingsystem => :openbsd options :home, :flag => "-d", :method => :dir options :comment, :method => :gecos options :groups, :flag => "-G" options :password, :method => :sp_pwdp options :loginclass, :flag => '-L', :method => :sp_loginclass options :expiry, :method => :sp_expire, :munge => proc { |value| if value == :absent '' else # OpenBSD uses a format like "january 1 1970" Time.parse(value).strftime('%B %d %Y') end }, :unmunge => proc { |value| if value == -1 :absent else # Expiry is days after 1970-01-01 (Date.new(1970,1,1) + value).strftime('%Y-%m-%d') end } [:expiry, :password, :loginclass].each do |shadow_property| define_method(shadow_property) do if Puppet.features.libshadow? if ent = Shadow::Passwd.getspnam(@resource.name) method = self.class.option(shadow_property, :method) # ruby-shadow may not be new enough (< 2.4.1) and therefore lack the # sp_loginclass field. begin return unmunge(shadow_property, ent.send(method)) rescue #TRANSLATORS 'ruby-shadow' is a Ruby gem library Puppet.warning _("ruby-shadow doesn't support %{method}") % { method: method } end end end :absent end end has_features :manages_homedir, :manages_expiry, :system_users has_features :manages_shell if Puppet.features.libshadow? has_features :manages_passwords, :manages_loginclass end def loginclass=(value) set("loginclass", value) end def modifycmd(param, value) cmd = super if param == :groups idx = cmd.index('-G') cmd[idx] = '-S' end cmd end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/user/pw.rb��������������������������������������������������������0000644�0052762�0001160�00000005464�13417161721�020671� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/nameservice/pw' require 'open3' Puppet::Type.type(:user).provide :pw, :parent => Puppet::Provider::NameService::PW do desc "User management via `pw` on FreeBSD and DragonFly BSD." commands :pw => "pw" has_features :manages_homedir, :allows_duplicates, :manages_passwords, :manages_expiry, :manages_shell defaultfor :operatingsystem => [:freebsd, :dragonfly] confine :operatingsystem => [:freebsd, :dragonfly] options :home, :flag => "-d", :method => :dir options :comment, :method => :gecos options :groups, :flag => "-G" options :expiry, :method => :expire, :munge => proc { |value| value = '0000-00-00' if value == :absent value.split("-").reverse.join("-") } verify :gid, "GID must be an integer" do |value| value.is_a? Integer end verify :groups, "Groups must be comma-separated" do |value| value !~ /\s/ end def addcmd cmd = [command(:pw), "useradd", @resource[:name]] @resource.class.validproperties.each do |property| next if property == :ensure or property == :password if value = @resource.should(property) and value != "" cmd << flag(property) << munge(property,value) end end cmd << "-o" if @resource.allowdupe? cmd << "-m" if @resource.managehome? cmd end def modifycmd(param, value) if param == :expiry # FreeBSD uses DD-MM-YYYY rather than YYYY-MM-DD value = value.split("-").reverse.join("-") end cmd = super(param, value) cmd << "-m" if @resource.managehome? cmd end def deletecmd cmd = super cmd << "-r" if @resource.managehome? cmd end def create super # Set the password after create if given self.password = @resource[:password] if @resource[:password] end # use pw to update password hash def password=(cryptopw) Puppet.debug "change password for user '#{@resource[:name]}' method called with hash '#{cryptopw}'" stdin, _, _ = Open3.popen3("pw user mod #{@resource[:name]} -H 0") stdin.puts(cryptopw) stdin.close Puppet.debug "finished password for user '#{@resource[:name]}' method called with hash '#{cryptopw}'" end # get password from /etc/master.passwd def password Puppet.debug "checking password for user '#{@resource[:name]}' method called" current_passline = `getent passwd #{@resource[:name]}` current_password = current_passline.chomp.split(':')[1] if current_passline Puppet.debug "finished password for user '#{@resource[:name]}' method called : '#{current_password}'" current_password end # Get expiry from system and convert to Puppet-style date def expiry expiry = self.get(:expiry) expiry = :absent if expiry == 0 if expiry != :absent t = Time.at(expiry) expiry = "%4d-%02d-%02d" % [t.year, t.month, t.mday] end expiry end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/user/user_role_add.rb���������������������������������������������0000644�0052762�0001160�00000015127�13417161721�023047� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util' require 'puppet/util/user_attr' require 'date' Puppet::Type.type(:user).provide :user_role_add, :parent => :useradd, :source => :useradd do desc "User and role management on Solaris, via `useradd` and `roleadd`." defaultfor :osfamily => :solaris commands :add => "useradd", :delete => "userdel", :modify => "usermod", :password => "passwd", :role_add => "roleadd", :role_delete => "roledel", :role_modify => "rolemod" options :home, :flag => "-d", :method => :dir options :comment, :method => :gecos options :groups, :flag => "-G" options :shell, :flag => "-s" options :roles, :flag => "-R" options :auths, :flag => "-A" options :profiles, :flag => "-P" options :password_min_age, :flag => "-n" options :password_max_age, :flag => "-x" options :password_warn_days, :flag => "-w" verify :gid, "GID must be an integer" do |value| value.is_a? Integer end verify :groups, "Groups must be comma-separated" do |value| value !~ /\s/ end def shell=(value) check_valid_shell set("shell", value) end has_features :manages_homedir, :allows_duplicates, :manages_solaris_rbac, :manages_passwords, :manages_password_age, :manages_shell def check_valid_shell unless File.exists?(@resource.should(:shell)) raise(Puppet::Error, "Shell #{@resource.should(:shell)} must exist") end unless File.executable?(@resource.should(:shell).to_s) raise(Puppet::Error, "Shell #{@resource.should(:shell)} must be executable") end end #must override this to hand the keyvalue pairs def add_properties cmd = [] Puppet::Type.type(:user).validproperties.each do |property| #skip the password because we can't create it with the solaris useradd next if [:ensure, :password, :password_min_age, :password_max_age, :password_warn_days].include?(property) # 1680 Now you can set the hashed passwords on solaris:lib/puppet/provider/user/user_role_add.rb # the value needs to be quoted, mostly because -c might # have spaces in it if value = @resource.should(property) and value != "" if property == :keys cmd += build_keys_cmd(value) else cmd << flag(property) << value end end end cmd end def user_attributes @user_attributes ||= UserAttr.get_attributes_by_name(@resource[:name]) end def flush @user_attributes = nil end def command(cmd) cmd = ("role_#{cmd}").intern if is_role? or (!exists? and @resource[:ensure] == :role) super(cmd) end def is_role? user_attributes and user_attributes[:type] == "role" end def run(cmd, msg) execute(cmd) rescue Puppet::ExecutionFailure => detail raise Puppet::Error, "Could not #{msg} #{@resource.class.name} #{@resource.name}: #{detail}", detail.backtrace end def transition(type) cmd = [command(:modify)] cmd << "-K" << "type=#{type}" cmd += add_properties cmd << @resource[:name] end def create if is_role? run(transition("normal"), "transition role to") else run(addcmd, "create") if cmd = passcmd run(cmd, "change password policy for") end end # added to handle case when password is specified self.password = @resource[:password] if @resource[:password] end def destroy run(deletecmd, "delete "+ (is_role? ? "role" : "user")) end def create_role if exists? and !is_role? run(transition("role"), "transition user to") else run(addcmd, "create role") end end def roles user_attributes[:roles] if user_attributes end def auths user_attributes[:auths] if user_attributes end def profiles user_attributes[:profiles] if user_attributes end def project user_attributes[:project] if user_attributes end def managed_attributes [:name, :type, :roles, :auths, :profiles, :project] end def remove_managed_attributes managed = managed_attributes user_attributes.select { |k,v| !managed.include?(k) }.inject({}) { |hash, array| hash[array[0]] = array[1]; hash } end def keys if user_attributes #we have to get rid of all the keys we are managing another way remove_managed_attributes end end def build_keys_cmd(keys_hash) cmd = [] keys_hash.each do |k,v| cmd << "-K" << "#{k}=#{v}" end cmd end def keys=(keys_hash) run([command(:modify)] + build_keys_cmd(keys_hash) << @resource[:name], "modify attribute key pairs") end # This helper makes it possible to test this on stub data without having to # do too many crazy things! def target_file_path "/etc/shadow" end private :target_file_path #Read in /etc/shadow, find the line for this user (skipping comments, because who knows) and return it #No abstraction, all esoteric knowledge of file formats, yay def shadow_entry return @shadow_entry if defined? @shadow_entry @shadow_entry = File.readlines(target_file_path). reject { |r| r =~ /^[^\w]/ }. # PUP-229: don't suppress the empty fields collect { |l| l.chomp.split(':', -1) }. find { |user, _| user == @resource[:name] } end def password return :absent unless shadow_entry shadow_entry[1] end def password_min_age return :absent unless shadow_entry shadow_entry[3].empty? ? -1 : shadow_entry[3] end def password_max_age return :absent unless shadow_entry shadow_entry[4].empty? ? -1 : shadow_entry[4] end def password_warn_days return :absent unless shadow_entry shadow_entry[5].empty? ? -1 : shadow_entry[5] end # Read in /etc/shadow, find the line for our used and rewrite it with the # new pw. Smooth like 80 grit sandpaper. # # Now uses the `replace_file` mechanism to minimize the chance that we lose # data, but it is still terrible. We still skip platform locking, so a # concurrent `vipw -s` session will have no idea we risk data loss. def password=(cryptopw) begin shadow = File.read(target_file_path) # Go Mifune loves the race here where we can lose data because # /etc/shadow changed between reading it and writing it. # --daniel 2012-02-05 Puppet::Util.replace_file(target_file_path, 0640) do |fh| shadow.each_line do |line| line_arr = line.split(':') if line_arr[0] == @resource[:name] line_arr[1] = cryptopw line_arr[2] = (Date.today - Date.new(1970,1,1)).to_i.to_s line = line_arr.join(':') end fh.print line end end rescue => detail self.fail Puppet::Error, "Could not write replace #{target_file_path}: #{detail}", detail end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/user/useradd.rb���������������������������������������������������0000644�0052762�0001160�00000016510�13417161721�021664� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/nameservice/objectadd' require 'date' require 'puppet/util/libuser' require 'time' require 'puppet/error' Puppet::Type.type(:user).provide :useradd, :parent => Puppet::Provider::NameService::ObjectAdd do desc "User management via `useradd` and its ilk. Note that you will need to install Ruby's shadow password library (often known as `ruby-libshadow`) if you wish to manage user passwords." commands :add => "useradd", :delete => "userdel", :modify => "usermod", :password => "chage" options :home, :flag => "-d", :method => :dir options :comment, :method => :gecos options :groups, :flag => "-G" options :password_min_age, :flag => "-m", :method => :sp_min options :password_max_age, :flag => "-M", :method => :sp_max options :password_warn_days, :flag => "-W", :method => :sp_warn options :password, :method => :sp_pwdp options :expiry, :method => :sp_expire, :munge => proc { |value| if value == :absent '' else case Facter.value(:operatingsystem) when 'Solaris' # Solaris uses %m/%d/%Y for useradd/usermod expiry_year, expiry_month, expiry_day = value.split('-') [expiry_month, expiry_day, expiry_year].join('/') else value end end }, :unmunge => proc { |value| if value == -1 :absent else # Expiry is days after 1970-01-01 (Date.new(1970,1,1) + value).strftime('%Y-%m-%d') end } optional_commands :localadd => "luseradd", :localdelete => "luserdel", :localmodify => "lusermod", :localpassword => "lchage" has_feature :libuser if Puppet.features.libuser? def exists? return !!localuid if @resource.forcelocal? super end def uid return localuid if @resource.forcelocal? get(:uid) end def finduser(key, value) passwd_file = "/etc/passwd" passwd_keys = ['account', 'password', 'uid', 'gid', 'gecos', 'directory', 'shell'] index = passwd_keys.index(key) File.open(passwd_file) do |f| f.each_line do |line| user = line.split(":") if user[index] == value f.close return user end end end false end def local_username finduser('uid', @resource.uid) end def localuid user = finduser('account', resource[:name]) return user[2] if user false end def shell=(value) check_valid_shell set("shell", value) end verify :gid, "GID must be an integer" do |value| value.is_a? Integer end verify :groups, "Groups must be comma-separated" do |value| value !~ /\s/ end has_features :manages_homedir, :allows_duplicates, :manages_expiry has_features :system_users unless %w{HP-UX Solaris}.include? Facter.value(:operatingsystem) has_features :manages_passwords, :manages_password_age if Puppet.features.libshadow? has_features :manages_shell def check_allow_dup # We have to manually check for duplicates when using libuser # because by default duplicates are allowed. This check is # to ensure consistent behaviour of the useradd provider when # using both useradd and luseradd if not @resource.allowdupe? and @resource.forcelocal? if @resource.should(:uid) and finduser('uid', @resource.should(:uid).to_s) raise(Puppet::Error, "UID #{@resource.should(:uid).to_s} already exists, use allowdupe to force user creation") end elsif @resource.allowdupe? and not @resource.forcelocal? return ["-o"] end [] end def check_valid_shell unless File.exists?(@resource.should(:shell)) raise(Puppet::Error, "Shell #{@resource.should(:shell)} must exist") end unless File.executable?(@resource.should(:shell).to_s) raise(Puppet::Error, "Shell #{@resource.should(:shell)} must be executable") end end def check_manage_home cmd = [] if @resource.managehome? and not @resource.forcelocal? cmd << "-m" elsif not @resource.managehome? and Facter.value(:osfamily) == 'RedHat' cmd << "-M" end cmd end def check_system_users if self.class.system_users? and resource.system? ["-r"] else [] end end def add_properties cmd = [] # validproperties is a list of properties in undefined order # sort them to have a predictable command line in tests Puppet::Type.type(:user).validproperties.sort.each do |property| next if property == :ensure next if property_manages_password_age?(property) next if property == :groups and @resource.forcelocal? next if property == :expiry and @resource.forcelocal? # the value needs to be quoted, mostly because -c might # have spaces in it if value = @resource.should(property) and value != "" cmd << flag(property) << munge(property, value) end end cmd end def addcmd if @resource.forcelocal? cmd = [command(:localadd)] @custom_environment = Puppet::Util::Libuser.getenv else cmd = [command(:add)] end if not @resource.should(:gid) and Puppet::Util.gid(@resource[:name]) cmd += ["-g", @resource[:name]] end cmd += add_properties cmd += check_allow_dup cmd += check_manage_home cmd += check_system_users cmd << @resource[:name] end def modifycmd(param, value) if @resource.forcelocal? case param when :groups, :expiry cmd = [command(:modify)] else cmd = [command(property_manages_password_age?(param) ? :localpassword : :localmodify)] end @custom_environment = Puppet::Util::Libuser.getenv else cmd = [command(property_manages_password_age?(param) ? :password : :modify)] end cmd << flag(param) << value cmd += check_allow_dup if param == :uid cmd << @resource[:name] cmd end def deletecmd if @resource.forcelocal? cmd = [command(:localdelete)] @custom_environment = Puppet::Util::Libuser.getenv else cmd = [command(:delete)] end cmd += @resource.managehome? ? ['-r'] : [] cmd << @resource[:name] end def passcmd if @resource.forcelocal? cmd = command(:localpassword) @custom_environment = Puppet::Util::Libuser.getenv else cmd = command(:password) end age_limits = [:password_min_age, :password_max_age, :password_warn_days].select { |property| @resource.should(property) } if age_limits.empty? nil else [cmd, age_limits.collect { |property| [flag(property), @resource.should(property)]}, @resource[:name]].flatten end end [:expiry, :password_min_age, :password_max_age, :password_warn_days, :password].each do |shadow_property| define_method(shadow_property) do if Puppet.features.libshadow? if ent = Shadow::Passwd.getspnam(@canonical_name) method = self.class.option(shadow_property, :method) return unmunge(shadow_property, ent.send(method)) end end :absent end end def create if @resource[:shell] check_valid_shell end super if @resource.forcelocal? and self.groups? set(:groups, @resource[:groups]) end if @resource.forcelocal? and @resource[:expiry] set(:expiry, @resource[:expiry]) end end def groups? !!@resource[:groups] end def property_manages_password_age?(property) property.to_s =~ /password_.+_age|password_warn_days/ end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/user/windows_adsi.rb����������������������������������������������0000644�0052762�0001160�00000010211�13417161721�022717� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/windows' Puppet::Type.type(:user).provide :windows_adsi do desc "Local user management for Windows." defaultfor :operatingsystem => :windows confine :operatingsystem => :windows has_features :manages_homedir, :manages_passwords def initialize(value={}) super(value) @deleted = false end def user @user ||= Puppet::Util::Windows::ADSI::User.new(@resource[:name]) end def groups @groups ||= Puppet::Util::Windows::ADSI::Group.name_sid_hash(user.groups) @groups.keys end def groups=(groups) user.set_groups(groups, @resource[:membership] == :minimum) end def groups_insync?(current, should) return false unless current # By comparing account SIDs we don't have to worry about case # sensitivity, or canonicalization of account names. # Cannot use munge of the group property to canonicalize @should # since the default array_matching comparison is not commutative # dupes automatically weeded out when hashes built current_groups = Puppet::Util::Windows::ADSI::Group.name_sid_hash(current) specified_groups = Puppet::Util::Windows::ADSI::Group.name_sid_hash(should) current_sids = current_groups.keys.to_a specified_sids = specified_groups.keys.to_a if @resource[:membership] == :inclusive current_sids.sort == specified_sids.sort else (specified_sids & current_sids) == specified_sids end end def groups_to_s(groups) return '' if groups.nil? || !groups.kind_of?(Array) groups = groups.map do |group_name| sid = Puppet::Util::Windows::SID.name_to_principal(group_name) if sid.account =~ /\\/ account, _ = Puppet::Util::Windows::ADSI::Group.parse_name(sid.account) else account = sid.account end resource.debug("#{sid.domain}\\#{account} (#{sid.sid})") "#{sid.domain}\\#{account}" end return groups.join(',') end def create @user = Puppet::Util::Windows::ADSI::User.create(@resource[:name]) @user.password = @resource[:password] @user.commit [:comment, :home, :groups].each do |prop| send("#{prop}=", @resource[prop]) if @resource[prop] end if @resource.managehome? Puppet::Util::Windows::User.load_profile(@resource[:name], @resource[:password]) end end def exists? Puppet::Util::Windows::ADSI::User.exists?(@resource[:name]) end def delete # lookup sid before we delete account sid = uid if @resource.managehome? Puppet::Util::Windows::ADSI::User.delete(@resource[:name]) if sid Puppet::Util::Windows::ADSI::UserProfile.delete(sid) end @deleted = true end # Only flush if we created or modified a user, not deleted def flush @user.commit if @user && !@deleted end def comment user['Description'] end def comment=(value) user['Description'] = value end def home user['HomeDirectory'] end def home=(value) user['HomeDirectory'] = value end def password # avoid a LogonUserW style password check when the resource is not yet # populated with a password (as is the case with `puppet resource user`) return nil if @resource[:password].nil? user.password_is?( @resource[:password] ) ? @resource[:password] : nil end def password=(value) if user.disabled? warning _("The user account '%s' is disabled; puppet will not reset the password" % @resource[:name]) elsif user.locked_out? warning _("The user account '%s' is locked out; puppet will not reset the password" % @resource[:name]) elsif user.expired? warning _("The user account '%s' is expired; puppet will not reset the password" % @resource[:name]) else user.password = value end end def uid Puppet::Util::Windows::SID.name_to_sid(@resource[:name]) end def uid=(value) fail "uid is read-only" end [:gid, :shell].each do |prop| define_method(prop) { nil } define_method("#{prop}=") do |v| fail "No support for managing property #{prop} of user #{@resource[:name]} on Windows" end end def self.instances Puppet::Util::Windows::ADSI::User.map { |u| new(:ensure => :present, :name => u.name) } end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/augeas/�����������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020201� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/augeas/augeas.rb��������������������������������������������������0000644�0052762�0001160�00000061210�13417161721�021766� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# # Copyright 2011 Bryan Kearney <bkearney@redhat.com> # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require 'augeas' if Puppet.features.augeas? require 'strscan' require 'puppet/util' require 'puppet/util/diff' require 'puppet/util/package' Puppet::Type.type(:augeas).provide(:augeas) do include Puppet::Util include Puppet::Util::Diff include Puppet::Util::Package confine :feature => :augeas has_features :parse_commands, :need_to_run?,:execute_changes SAVE_NOOP = "noop" SAVE_OVERWRITE = "overwrite" SAVE_NEWFILE = "newfile" SAVE_BACKUP = "backup" COMMANDS = { "set" => [ :path, :string ], "setm" => [ :path, :string, :string ], "rm" => [ :path ], "clear" => [ :path ], "clearm" => [ :path, :string ], "touch" => [ :path ], "mv" => [ :path, :path ], "rename" => [ :path, :string ], "insert" => [ :string, :string, :path ], "get" => [ :path, :comparator, :string ], "values" => [ :path, :glob ], "defvar" => [ :string, :path ], "defnode" => [ :string, :path, :string ], "match" => [ :path, :glob ], "size" => [:comparator, :int], "include" => [:string], "not_include" => [:string], "==" => [:glob], "!=" => [:glob] } COMMANDS["ins"] = COMMANDS["insert"] COMMANDS["remove"] = COMMANDS["rm"] COMMANDS["move"] = COMMANDS["mv"] attr_accessor :aug # Extracts an 2 dimensional array of commands which are in the # form of command path value. # The input can be # - A string with one command # - A string with many commands per line # - An array of strings. def parse_commands(data) context = resource[:context] # Add a trailing / if it is not there if (context.length > 0) context << "/" if context[-1, 1] != "/" end data = data.split($/) if data.is_a?(String) data = data.flatten args = [] data.each do |line| line.strip! next if line.nil? || line.empty? argline = [] sc = StringScanner.new(line) cmd = sc.scan(/\w+|==|!=/) formals = COMMANDS[cmd] fail(_("Unknown command %{cmd}") % { cmd: cmd }) unless formals argline << cmd narg = 0 formals.each do |f| sc.skip(/\s+/) narg += 1 if f == :path start = sc.pos nbracket = 0 inSingleTick = false inDoubleTick = false begin sc.skip(/([^\]\[\s\\'"]|\\.)+/) ch = sc.getch nbracket += 1 if ch == "[" nbracket -= 1 if ch == "]" inSingleTick = !inSingleTick if ch == "'" inDoubleTick = !inDoubleTick if ch == "\"" fail(_("unmatched [")) if nbracket < 0 end until ((nbracket == 0 && !inSingleTick && !inDoubleTick && (ch =~ /\s/)) || sc.eos?) len = sc.pos - start len -= 1 unless sc.eos? unless p = sc.string[start, len] fail(_("missing path argument %{narg} for %{cmd}") % { narg: narg, cmd: cmd }) end # Rip off any ticks if they are there. p = p[1, (p.size - 2)] if p[0,1] == "'" || p[0,1] == "\"" p.chomp!("/") if p[0,1] != '$' && p[0,1] != "/" argline << context + p else argline << p end elsif f == :string delim = sc.peek(1) if delim == "'" || delim == "\"" sc.getch argline << sc.scan(/([^\\#{delim}]|(\\.))*/) # Unescape the delimiter so it's actually possible to have a # literal delim inside the string. We only unescape the # delimeter and not every backslash-escaped character so that # things like escaped spaces '\ ' get passed through because # Augeas needs to see them. If we unescaped them, too, users # would be forced to double-escape them argline.last.gsub!(/\\(#{delim})/, '\1') sc.getch else argline << sc.scan(/[^\s]+/) end fail(_("missing string argument %{narg} for %{cmd}") % { narg: narg, cmd: cmd }) unless argline[-1] elsif f == :comparator argline << sc.scan(/(==|!=|=~|<=|>=|<|>)/) unless argline[-1] puts sc.rest fail(_("invalid comparator for command %{cmd}") % { cmd: cmd }) end elsif f == :int argline << sc.scan(/\d+/).to_i elsif f== :glob argline << sc.rest end end args << argline end args end def open_augeas unless @aug flags = Augeas::NONE flags = Augeas::TYPE_CHECK if resource[:type_check] == :true if resource[:incl] flags |= Augeas::NO_MODL_AUTOLOAD else flags |= Augeas::NO_LOAD end root = resource[:root] load_path = get_load_path(resource) debug("Opening augeas with root #{root}, lens path #{load_path}, flags #{flags}") @aug = Augeas::open(root, load_path,flags) debug("Augeas version #{get_augeas_version} is installed") if versioncmp(get_augeas_version, "0.3.6") >= 0 # Optimize loading if the context is given and it's a simple path, # requires the glob function from Augeas 0.8.2 or up glob_avail = !aug.match("/augeas/version/pathx/functions/glob").empty? opt_ctx = resource[:context].match("^/files/[^'\"\\[\\]]+$") if resource[:context] if resource[:incl] aug.set("/augeas/load/Xfm/lens", resource[:lens]) aug.set("/augeas/load/Xfm/incl", resource[:incl]) restricted_metadata = "/augeas//error" elsif glob_avail and opt_ctx # Optimize loading if the context is given, requires the glob function # from Augeas 0.8.2 or up ctx_path = resource[:context].sub(/^\/files(.*?)\/?$/, '\1/') load_path = "/augeas/load/*['%s' !~ glob(incl) + regexp('/.*')]" % ctx_path if aug.match(load_path).size < aug.match("/augeas/load/*").size aug.rm(load_path) restricted_metadata = "/augeas/files#{ctx_path}/error" else # This will occur if the context is less specific than any glob debug("Unable to optimize files loaded by context path, no glob matches") end end aug.load print_load_errors(restricted_metadata) end @aug end def close_augeas if @aug @aug.close debug("Closed the augeas connection") @aug = nil end end def is_numeric?(s) case s when Integer true when String s.match(/\A[+-]?\d+?(\.\d+)?\Z/n) == nil ? false : true else false end end # Used by the need_to_run? method to process get filters. Returns # true if there is a match, false if otherwise # Assumes a syntax of get /files/path [COMPARATOR] value def process_get(cmd_array) return_value = false #validate and tear apart the command fail (_("Invalid command: %{cmd}") % { cmd: cmd_array.join(" ") }) if cmd_array.length < 4 _ = cmd_array.shift path = cmd_array.shift comparator = cmd_array.shift arg = cmd_array.join(" ") #check the value in augeas result = @aug.get(path) || '' if ['<', '<=', '>=', '>'].include? comparator and is_numeric?(result) and is_numeric?(arg) resultf = result.to_f argf = arg.to_f return_value = (resultf.send(comparator, argf)) elsif comparator == "!=" return_value = (result != arg) elsif comparator == "=~" regex = Regexp.new(arg) return_value = (result =~ regex) else return_value = (result.send(comparator, arg)) end !!return_value end # Used by the need_to_run? method to process values filters. Returns # true if there is a matched value, false if otherwise def process_values(cmd_array) return_value = false #validate and tear apart the command fail(_("Invalid command: %{cmd}") % { cmd: cmd_array.join(" ") }) if cmd_array.length < 3 _ = cmd_array.shift path = cmd_array.shift # Need to break apart the clause clause_array = parse_commands(cmd_array.shift)[0] verb = clause_array.shift #Get the match paths from augeas result = @aug.match(path) || [] fail(_("Error trying to get path '%{path}'") % { path: path }) if (result == -1) #Get the values of the match paths from augeas values = result.collect{|r| @aug.get(r)} case verb when "include" arg = clause_array.shift return_value = values.include?(arg) when "not_include" arg = clause_array.shift return_value = !values.include?(arg) when "==" begin arg = clause_array.shift new_array = to_array(arg) return_value = (values == new_array) rescue fail(_("Invalid array in command: %{cmd}") % { cmd: cmd_array.join(" ") }) end when "!=" begin arg = clause_array.shift new_array = to_array(arg) return_value = (values != new_array) rescue fail(_("Invalid array in command: %{cmd}") % { cmd: cmd_array.join(" ") }) end end !!return_value end # Used by the need_to_run? method to process match filters. Returns # true if there is a match, false if otherwise def process_match(cmd_array) return_value = false #validate and tear apart the command fail(_("Invalid command: %{cmd}") % { cmd: cmd_array.join(" ") }) if cmd_array.length < 3 _ = cmd_array.shift path = cmd_array.shift # Need to break apart the clause clause_array = parse_commands(cmd_array.shift)[0] verb = clause_array.shift #Get the values from augeas result = @aug.match(path) || [] fail(_("Error trying to match path '%{path}'") % { path: path }) if (result == -1) # Now do the work case verb when "size" fail(_("Invalid command: %{cmd}") % { cmd: cmd_array.join(" ") }) if clause_array.length != 2 comparator = clause_array.shift arg = clause_array.shift case comparator when "!=" return_value = !(result.size.send(:==, arg)) else return_value = (result.size.send(comparator, arg)) end when "include" arg = clause_array.shift return_value = result.include?(arg) when "not_include" arg = clause_array.shift return_value = !result.include?(arg) when "==" begin arg = clause_array.shift new_array = to_array(arg) return_value = (result == new_array) rescue fail(_("Invalid array in command: %{cmd}") % { cmd: cmd_array.join(" ") }) end when "!=" begin arg = clause_array.shift new_array = to_array(arg) return_value = (result != new_array) rescue fail(_("Invalid array in command: %{cmd}") % { cmd: cmd_array.join(" ") }) end end !!return_value end # Generate lens load paths from user given paths and local pluginsync dir def get_load_path(resource) load_path = [] # Permits colon separated strings or arrays if resource[:load_path] load_path = [resource[:load_path]].flatten load_path.map! { |path| path.split(/:/) } load_path.flatten! end if Puppet::FileSystem.exist?("#{Puppet[:libdir]}/augeas/lenses") load_path << "#{Puppet[:libdir]}/augeas/lenses" end load_path.join(":") end def get_augeas_version @aug.get("/augeas/version") || "" end def set_augeas_save_mode(mode) @aug.set("/augeas/save", mode) end def print_load_errors(path) errors = @aug.match("/augeas//error") unless errors.empty? if path && !@aug.match(path).empty? warning(_("Loading failed for one or more files, see debug for /augeas//error output")) else debug("Loading failed for one or more files, output from /augeas//error:") end end print_errors(errors) end def print_put_errors errors = @aug.match("/augeas//error[. = 'put_failed']") debug("Put failed on one or more files, output from /augeas//error:") unless errors.empty? print_errors(errors) end def print_errors(errors) errors.each do |errnode| error = @aug.get(errnode) debug("#{errnode} = #{error}") unless error.nil? @aug.match("#{errnode}/*").each do |subnode| subvalue = @aug.get(subnode) debug("#{subnode} = #{subvalue}") end end end # Determines if augeas actually needs to run. def need_to_run? force = resource[:force] return_value = true begin open_augeas filter = resource[:onlyif] unless filter == "" cmd_array = parse_commands(filter)[0] command = cmd_array[0]; begin case command when "get"; return_value = process_get(cmd_array) when "values"; return_value = process_values(cmd_array) when "match"; return_value = process_match(cmd_array) end rescue StandardError => e fail(_("Error sending command '%{command}' with params %{param}/%{message}") % { command: command, param: cmd_array[1..-1].inspect, message: e.message }) end end unless force # If we have a version of augeas which is at least 0.3.6 then we # can make the changes now and see if changes were made. if return_value and versioncmp(get_augeas_version, "0.3.6") >= 0 debug("Will attempt to save and only run if files changed") # Execute in NEWFILE mode so we can show a diff set_augeas_save_mode(SAVE_NEWFILE) do_execute_changes save_result = @aug.save unless save_result print_put_errors fail(_("Saving failed, see debug")) end saved_files = @aug.match("/augeas/events/saved") if saved_files.size > 0 root = resource[:root].sub(/^\/$/, "") saved_files.map! {|key| @aug.get(key).sub(/^\/files/, root) } saved_files.uniq.each do |saved_file| if Puppet[:show_diff] && @resource[:show_diff] self.send(@resource[:loglevel], "\n" + diff(saved_file, saved_file + ".augnew")) end File.delete(saved_file + ".augnew") end debug("Files changed, should execute") return_value = true else debug("Skipping because no files were changed") return_value = false end end end ensure if not return_value or resource.noop? or not save_result close_augeas end end return_value end def execute_changes # Workaround Augeas bug where changing the save mode doesn't trigger a # reload of the previously saved file(s) when we call Augeas#load @aug.match("/augeas/events/saved").each do |file| @aug.rm("/augeas#{@aug.get(file)}/mtime") end # Reload augeas, and execute the changes for real set_augeas_save_mode(SAVE_OVERWRITE) if versioncmp(get_augeas_version, "0.3.6") >= 0 @aug.load do_execute_changes unless @aug.save print_put_errors fail(_("Save failed, see debug")) end :executed ensure close_augeas end # Actually execute the augeas changes. def do_execute_changes commands = parse_commands(resource[:changes]) commands.each do |cmd_array| fail(_("invalid command %{cmd}") % { value0: cmd_array.join[" "] }) if cmd_array.length < 2 command = cmd_array[0] cmd_array.shift begin case command when "set" debug("sending command '#{command}' with params #{cmd_array.inspect}") rv = aug.set(cmd_array[0], cmd_array[1]) fail(_("Error sending command '%{command}' with params %{params}") % { command: command, params: cmd_array.inspect }) if (!rv) when "setm" if aug.respond_to?(command) debug("sending command '#{command}' with params #{cmd_array.inspect}") rv = aug.setm(cmd_array[0], cmd_array[1], cmd_array[2]) fail(_("Error sending command '%{command}' with params %{params}") % { command: command, params: cmd_array.inspect }) if (rv == -1) else fail(_("command '%{command}' not supported in installed version of ruby-augeas") % { command: command }) end when "rm", "remove" debug("sending command '#{command}' with params #{cmd_array.inspect}") rv = aug.rm(cmd_array[0]) fail(_("Error sending command '%{command}' with params %{params}") % { command: command, params: cmd_array.inspect }) if (rv == -1) when "clear" debug("sending command '#{command}' with params #{cmd_array.inspect}") rv = aug.clear(cmd_array[0]) fail(_("Error sending command '%{command}' with params %{params}") % { command: command, params: cmd_array.inspect }) if (!rv) when "clearm" # Check command exists ... doesn't currently in ruby-augeas 0.4.1 if aug.respond_to?(command) debug("sending command '#{command}' with params #{cmd_array.inspect}") rv = aug.clearm(cmd_array[0], cmd_array[1]) fail(_("Error sending command '%{command}' with params %{params}") % { command: command, params: cmd_array.inspect }) if (!rv) else fail(_("command '%{command}' not supported in installed version of ruby-augeas") % { command: command }) end when "touch" debug("sending command '#{command}' (match, set) with params #{cmd_array.inspect}") if aug.match(cmd_array[0]).empty? rv = aug.clear(cmd_array[0]) fail(_("Error sending command '%{command}' with params %{params}") % { command: command, params: cmd_array.inspect }) if (!rv) end when "insert", "ins" label = cmd_array[0] where = cmd_array[1] path = cmd_array[2] case where when "before"; before = true when "after"; before = false else fail(_("Invalid value '%{where}' for where param") % { where: where }) end debug("sending command '#{command}' with params #{[label, where, path].inspect}") rv = aug.insert(path, label, before) fail(_("Error sending command '%{command}' with params %{params}") % { command: command, params: cmd_array.inspect }) if (rv == -1) when "defvar" debug("sending command '#{command}' with params #{cmd_array.inspect}") rv = aug.defvar(cmd_array[0], cmd_array[1]) fail(_("Error sending command '%{command}' with params %{params}") % { command: command, params: cmd_array.inspect }) if (!rv) when "defnode" debug("sending command '#{command}' with params #{cmd_array.inspect}") rv = aug.defnode(cmd_array[0], cmd_array[1], cmd_array[2]) fail(_("Error sending command '%{command}' with params %{params}") % { command: command, params: cmd_array.inspect }) if (!rv) when "mv", "move" debug("sending command '#{command}' with params #{cmd_array.inspect}") rv = aug.mv(cmd_array[0], cmd_array[1]) fail(_("Error sending command '%{command}' with params %{params}") % { command: command, params: cmd_array.inspect }) if (rv == -1) when "rename" debug("sending command '#{command}' with params #{cmd_array.inspect}") rv = aug.rename(cmd_array[0], cmd_array[1]) fail(_("Error sending command '%{command}' with params %{params}") % { command: command, params: cmd_array.inspect }) if (rv == -1) else fail(_("Command '%{command}' is not supported") % { command: command }) end rescue StandardError => e fail(_("Error sending command '%{command}' with params %{params}/%{message}") % { command: command, params: cmd_array.inspect, message: e.message }) end end end def to_array(string) s = StringScanner.new(string) match = array_open(s) raise "Unable to parse array. Unexpected character at: #{s.rest}" if match.nil? array_content = array_values(s) match = array_close(s) raise "Unable to parse array. Unexpected character at: #{s.rest}" if match.nil? array_content end private :to_array def array_open(scanner) scanner.scan(/\s*\[\s*/) end private :array_open def array_close(scanner) scanner.scan(/\s*\]\s*/) end private :array_close def array_separator(scanner) scanner.scan(/\s*,\s*/) end private :array_separator def single_quote_unescaped_char(scanner) scanner.scan(/[^'\\]/) end private :single_quote_unescaped_char def single_quote_escaped_char(scanner) scanner.scan(/\\(['\\])/) && scanner[1] end private :single_quote_escaped_char def single_quote_char(scanner) single_quote_escaped_char(scanner) || single_quote_unescaped_char(scanner) end private :single_quote_char def double_quote_unescaped_char(scanner) scanner.scan(/[^"\\]/) end private :double_quote_unescaped_char # This handles the possible Ruby escape sequences in double-quoted strings, # except for \M-x, \M-\C-x, \M-\cx, \c\M-x, \c?, and \C-?. The full list of # escape sequences, and their meanings is taken from: # https://github.com/ruby/ruby/blob/90fdfec11a4a42653722e2ce2a672d6e87a57b8e/doc/syntax/literals.rdoc#strings def double_quote_escaped_char(scanner) match = scanner.scan(/\\(["\\abtnvfres0-7xu])/) return nil if match.nil? case scanner[1] when '\\' then return '\\' when '"' then return '"' when 'a' then return "\a" when 'b' then return "\b" when 't' then return "\t" when 'n' then return "\n" when 'v' then return "\v" when 'f' then return "\f" when 'r' then return "\r" when 'e' then return "\e" when 's' then return "\s" when /[0-7]/ octal_character = scanner[1] other_digits = scanner.scan(/[0-7]{1,2}/) octal_character << other_digits unless other_digits.nil? return octal_character.to_i(8).chr when 'x' hex_character = scanner.scan(/[0-9a-fA-F]{1,2}/) if hex_character.nil? return nil else return hex_character.to_i(16).chr end when 'u' return unicode_short_hex_character(scanner) || unicode_long_hex_characters(scanner) else # Not a valid escape sequence as far as we're concerned. return nil end end private :double_quote_escaped_char def unicode_short_hex_character(scanner) unicode_character = scanner.scan(/[0-9a-fA-F]{4}/) if unicode_character.nil? return nil else return [unicode_character.hex].pack 'U' end end private :unicode_short_hex_character def unicode_long_hex_characters(scanner) unicode_string = '' return nil unless scanner.scan(/{/) loop do char = scanner.scan(/[0-9a-fA-F]{1,6}/) break if char.nil? unicode_string << [char.hex].pack('U') separator = scanner.scan(/\s/) break if separator.nil? end return nil unless scanner.scan(/}/) if unicode_string.empty? return nil else return unicode_string end end private :unicode_long_hex_characters def single_quoted_string(scanner) quoted_string = '' match = scanner.scan(/'/) return nil if match.nil? loop do match = single_quote_char(scanner) break if match.nil? quoted_string << match end match = scanner.scan(/'/) if match return quoted_string else return nil end end private :single_quoted_string def double_quote_char(scanner) double_quote_escaped_char(scanner) || double_quote_unescaped_char(scanner) end private :double_quote_char def double_quoted_string(scanner) quoted_string = '' match = scanner.scan(/"/) return nil if match.nil? loop do match = double_quote_char(scanner) break if match.nil? quoted_string << match end match = scanner.scan(/"/) if match return quoted_string else return nil end end private :double_quoted_string def quoted_string(scanner) single_quoted_string(scanner) || double_quoted_string(scanner) end private :quoted_string def array_values(scanner) values = [] loop do match = quoted_string(scanner) break if match.nil? values << match match = array_separator(scanner) break if match.nil? end values end private :array_values end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/cisco.rb����������������������������������������������������������0000644�0052762�0001160�00000000467�13417161721�020363� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/network_device/cisco/device' require 'puppet/provider/network_device' # This is the base class of all prefetched cisco device providers class Puppet::Provider::Cisco < Puppet::Provider::NetworkDevice def self.device(url) Puppet::Util::NetworkDevice::Cisco::Device.new(url) end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/computer/���������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020572� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/computer/computer.rb����������������������������������������������0000644�0052762�0001160�00000001460�13417161721�022751� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/nameservice/directoryservice' Puppet::Type.type(:computer).provide :directoryservice, :parent => Puppet::Provider::NameService::DirectoryService do desc "Computer object management using DirectoryService on OS X. Note that these are distinctly different kinds of objects to 'hosts', as they require a MAC address and can have all sorts of policy attached to them. This provider only manages Computer objects in the local directory service domain, not in remote directories. If you wish to manage /etc/hosts on Mac OS X, then simply use the host type as per other platforms." confine :operatingsystem => :darwin defaultfor :operatingsystem => :darwin # hurray for abstraction. The nameservice directoryservice provider can # handle everything we need. super. end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/cron/�������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�017675� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/cron/crontab.rb���������������������������������������������������0000644�0052762�0001160�00000021632�13417161721�021651� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/parsedfile' Puppet::Type.type(:cron).provide(:crontab, :parent => Puppet::Provider::ParsedFile, :default_target => ENV["USER"] || "root", :raise_prefetch_errors => true) do commands :crontab => "crontab" text_line :comment, :match => %r{^\s*#}, :post_parse => proc { |record| record[:name] = $1 if record[:line] =~ /Puppet Name: (.+)\s*$/ } text_line :blank, :match => %r{^\s*$} text_line :environment, :match => %r{^\s*\w+\s*=} def self.filetype tabname = case Facter.value(:osfamily) when "Solaris" :suntab when "AIX" :aixtab else :crontab end Puppet::Util::FileType.filetype(tabname) end self::TIME_FIELDS = [:minute, :hour, :monthday, :month, :weekday] record_line :crontab, :fields => %w{time command}, :match => %r{^\s*(@\w+|\S+\s+\S+\s+\S+\s+\S+\s+\S+)\s+(.+)$}, :absent => '*', :block_eval => :instance do def post_parse(record) time = record.delete(:time) if match = /@(\S+)/.match(time) # is there another way to access the constant? Puppet::Type::Cron::ProviderCrontab::TIME_FIELDS.each { |f| record[f] = :absent } record[:special] = match.captures[0] elsif match = /(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/.match(time) record[:special] = :absent Puppet::Type::Cron::ProviderCrontab::TIME_FIELDS.zip(match.captures).each do |field,value| if value == self.absent record[field] = :absent else record[field] = value.split(",") end end else raise Puppet::Error, _("Line got parsed as a crontab entry but cannot be handled. Please file a bug with the contents of your crontab") end record end def pre_gen(record) if record[:special] and record[:special] != :absent record[:special] = "@#{record[:special]}" end Puppet::Type::Cron::ProviderCrontab::TIME_FIELDS.each do |field| if vals = record[field] and vals.is_a?(Array) record[field] = vals.join(",") end end record end def to_line(record) str = "" record[:name] = nil if record[:unmanaged] str = "# Puppet Name: #{record[:name]}\n" if record[:name] if record[:environment] and record[:environment] != :absent str += record[:environment].map {|line| "#{line}\n"}.join('') end if record[:special] and record[:special] != :absent fields = [:special, :command] else fields = Puppet::Type::Cron::ProviderCrontab::TIME_FIELDS + [:command] end str += record.values_at(*fields).map do |field| if field.nil? or field == :absent self.absent else field end end.join(self.joiner) str end end def create if resource.should(:command) then super else resource.err _("no command specified, cannot create") end end # Look up a resource with a given name whose user matches a record target # # @api private # # @note This overrides the ParsedFile method for finding resources by name, # so that only records for a given user are matched to resources of the # same user so that orphaned records in other crontabs don't get falsely # matched (#2251) # # @param [Hash<Symbol, Object>] record # @param [Array<Puppet::Resource>] resources # # @return [Puppet::Resource, nil] The resource if found, else nil def self.resource_for_record(record, resources) resource = super if resource target = resource[:target] || resource[:user] if record[:target] == target resource end end end # Return the header placed at the top of each generated file, warning # users that modifying this file manually is probably a bad idea. def self.header %{# HEADER: This file was autogenerated at #{Time.now} by puppet. # HEADER: While it can still be managed manually, it is definitely not recommended. # HEADER: Note particularly that the comments starting with 'Puppet Name' should # HEADER: not be deleted, as doing so could cause duplicate cron jobs.\n} end # Regex for finding one vixie cron header. def self.native_header_regex /# DO NOT EDIT THIS FILE.*?Cron version.*?vixie.*?\n/m end # If a vixie cron header is found, it should be dropped, cron will insert # a new one in any case, so we need to avoid duplicates. def self.drop_native_header true end # See if we can match the record against an existing cron job. def self.match(record, resources) # if the record is named, do not even bother (#19876) # except the resource name was implicitly generated (#3220) return false if record[:name] and !record[:unmanaged] resources.each do |name, resource| # Match the command first, since it's the most important one. next unless record[:target] == resource[:target] next unless record[:command] == resource.value(:command) # Now check the time fields compare_fields = self::TIME_FIELDS + [:special] matched = true compare_fields.each do |field| # If the resource does not manage a property (say monthday) it should # always match. If it is the other way around (e.g. resource defines # a should value for :special but the record does not have it, we do # not match next unless resource[field] unless record.include?(field) matched = false break end if record_value = record[field] and resource_value = resource.value(field) # The record translates '*' into absent in the post_parse hook and # the resource type does exactly the opposite (alias :absent to *) next if resource_value == '*' and record_value == :absent next if resource_value == record_value end matched =false break end return resource if matched end false end @name_index = 0 # Collapse name and env records. def self.prefetch_hook(records) name = nil envs = nil result = records.each { |record| case record[:record_type] when :comment if record[:name] name = record[:name] record[:skip] = true # Start collecting env values envs = [] end when :environment # If we're collecting env values (meaning we're in a named cronjob), # store the line and skip the record. if envs envs << record[:line] record[:skip] = true end when :blank # nothing else if name record[:name] = name name = nil else cmd_string = record[:command].gsub(/\s+/, "_") index = ( @name_index += 1 ) record[:name] = "unmanaged:#{cmd_string}-#{ index.to_s }" record[:unmanaged] = true end if envs.nil? or envs.empty? record[:environment] = :absent else # Collect all of the environment lines, and mark the records to be skipped, # since their data is included in our crontab record. record[:environment] = envs # And turn off env collection again envs = nil end end }.reject { |record| record[:skip] } result end def self.to_file(records) text = super # Apparently Freebsd will "helpfully" add a new TZ line to every # single cron line, but not in all cases (e.g., it doesn't do it # on my machine). This is my attempt to fix it so the TZ lines don't # multiply. if text =~ /(^TZ=.+\n)/ tz = $1 text.sub!(tz, '') text = tz + text end text end def user=(user) # we have to mark the target as modified first, to make sure that if # we move a cronjob from userA to userB, userA's crontab will also # be rewritten mark_target_modified @property_hash[:user] = user @property_hash[:target] = user end def user @property_hash[:user] || @property_hash[:target] end CRONTAB_DIR = case Facter.value("osfamily") when "Debian", "HP-UX" "/var/spool/cron/crontabs" when /BSD/ "/var/cron/tabs" when "Darwin" "/usr/lib/cron/tabs/" else "/var/spool/cron" end # Yield the names of all crontab files stored on the local system. # # @note Ignores files that are not writable for the puppet process. # # @api private def self.enumerate_crontabs Puppet.debug "looking for crontabs in #{CRONTAB_DIR}" return unless File.readable?(CRONTAB_DIR) Dir.foreach(CRONTAB_DIR) do |file| path = "#{CRONTAB_DIR}/#{file}" yield(file) if File.file?(path) and File.writable?(path) end end # Include all plausible crontab files on the system # in the list of targets (#11383 / PUP-1381) def self.targets(resources = nil) targets = super(resources) enumerate_crontabs do |target| targets << target end targets.uniq end end ������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/exec.rb�����������������������������������������������������������0000644�0052762�0001160�00000006051�13417161721�020202� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider' require 'puppet/util/execution' class Puppet::Provider::Exec < Puppet::Provider include Puppet::Util::Execution def environment env = {} if (path = resource[:path]) env[:PATH] = path.join(File::PATH_SEPARATOR) end return env unless (envlist = resource[:environment]) envlist = [envlist] unless envlist.is_a? Array envlist.each do |setting| unless (match = /^(\w+)=((.|\n)+)$/.match(setting)) warning _("Cannot understand environment setting %{setting}") % { setting: setting.inspect } next end var = match[1] value = match[2] if env.include?(var) || env.include?(var.to_sym) warning _("Overriding environment setting '%{var}' with '%{value}'") % { var: var, value: value } end env[var] = value end env end def run(command, check = false) output = nil sensitive = resource.parameters[:command].sensitive checkexe(command) debug "Executing#{check ? " check": ""} '#{sensitive ? '[redacted]' : command}'" # Ruby 2.1 and later interrupt execution in a way that bypasses error # handling by default. Passing Timeout::Error causes an exception to be # raised that can be rescued inside of the block by cleanup routines. # # This is backwards compatible all the way to Ruby 1.8.7. Timeout::timeout(resource[:timeout], Timeout::Error) do cwd = resource[:cwd] cwd ||= Dir.pwd # note that we are passing "false" for the "override_locale" parameter, which ensures that the user's # default/system locale will be respected. Callers may override this behavior by setting locale-related # environment variables (LANG, LC_ALL, etc.) in their 'environment' configuration. output = Puppet::Util::Execution.execute( command, :failonfail => false, :combine => true, :cwd => cwd, :uid => resource[:user], :gid => resource[:group], :override_locale => false, :custom_environment => environment(), :sensitive => sensitive ) end # The shell returns 127 if the command is missing. if output.exitstatus == 127 raise ArgumentError, output end # Return output twice as processstatus was returned before, but only exitstatus was ever called. # Output has the exitstatus on it so it is returned instead. This is here twice as changing this # would result in a change to the underlying API. return output, output end def extractexe(command) if command.is_a? Array command.first elsif match = /^"([^"]+)"|^'([^']+)'/.match(command) # extract whichever of the two sides matched the content. match[1] or match[2] else command.split(/ /)[0] end end def validatecmd(command) exe = extractexe(command) # if we're not fully qualified, require a path self.fail _("'%{command}' is not qualified and no path was specified. Please qualify the command or specify a path.") % { command: command } if !absolute_path?(exe) and resource[:path].nil? end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/host/�������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�017711� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/host/parsed.rb����������������������������������������������������0000644�0052762�0001160�00000003100�13417161721�021501� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/parsedfile' hosts = nil case Facter.value(:osfamily) when "Solaris"; hosts = "/etc/inet/hosts" when "windows" require 'win32/resolv' hosts = Win32::Resolv.get_hosts_path else hosts = "/etc/hosts" end Puppet::Type.type(:host).provide(:parsed,:parent => Puppet::Provider::ParsedFile, :default_target => hosts,:filetype => :flat) do confine :exists => hosts text_line :comment, :match => /^#/ text_line :blank, :match => /^\s*$/ hosts_pattern = '^([0-9a-f:]\S+)\s+([^#\s+]\S+)\s*(.*?)?(?:\s*#\s*(.*))?$' record_line :parsed, :fields => %w{ip name host_aliases comment}, :optional => %w{host_aliases comment}, :match => /#{hosts_pattern}/, :post_parse => proc { |hash| # An absent comment should match "comment => ''" hash[:comment] = '' if hash[:comment].nil? or hash[:comment] == :absent unless hash[:host_aliases].nil? or hash[:host_aliases] == :absent hash[:host_aliases].gsub!(/\s+/,' ') # Change delimiter end }, :to_line => proc { |hash| [:ip, :name].each do |n| raise ArgumentError, _("%{attr} is a required attribute for hosts") % { attr: n } unless hash[n] and hash[n] != :absent end str = "#{hash[:ip]}\t#{hash[:name]}" if hash.include? :host_aliases and !hash[:host_aliases].nil? and hash[:host_aliases] != :absent str += "\t#{hash[:host_aliases]}" end if hash.include? :comment and !hash[:comment].empty? str += "\t# #{hash[:comment]}" end str } text_line :incomplete, :match => /(?! (#{hosts_pattern}))/ end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/interface/��������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020674� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/interface/cisco.rb������������������������������������������������0000644�0052762�0001160�00000001017�13417161721�022313� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/cisco' Puppet::Type.type(:interface).provide :cisco, :parent => Puppet::Provider::Cisco do desc "Cisco switch/router provider for interface." mk_resource_methods def self.lookup(device, name) interface = nil device.command do |dev| interface = dev.interface(name) end interface end def initialize(device, *args) super end def flush device.command do |dev| dev.new_interface(name).update(former_properties, properties) end super end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/macauthorization/�������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�022315� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/macauthorization/macauthorization.rb������������������������������0000644�0052762�0001160�00000021660�13417161721�026223� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'facter' require 'puppet/util/plist' if Puppet.features.cfpropertylist? require 'puppet' require 'tempfile' Puppet::Type.type(:macauthorization).provide :macauthorization, :parent => Puppet::Provider do desc "Manage Mac OS X authorization database rules and rights. " commands :security => "/usr/bin/security" confine :operatingsystem => :darwin confine :feature => :cfpropertylist defaultfor :operatingsystem => :darwin AuthDB = "/etc/authorization" @rights = {} @rules = {} @parsed_auth_db = {} @comment = "" # Not implemented yet. Is there any real need to? # This map exists due to the use of hyphens and reserved words in # the authorization schema. PuppetToNativeAttributeMap = { :allow_root => "allow-root", :authenticate_user => "authenticate-user", :auth_class => "class", :k_of_n => "k-of-n", :session_owner => "session-owner", } class << self attr_accessor :parsed_auth_db attr_accessor :rights attr_accessor :rules attr_accessor :comments # Not implemented yet. def prefetch(resources) self.populate_rules_rights end def instances if self.parsed_auth_db == {} self.prefetch(nil) end self.parsed_auth_db.collect do |k,v| new(:name => k) end end def populate_rules_rights auth_plist = Puppet::Util::Plist.parse_plist(AuthDB) raise Puppet::Error.new(_("Cannot parse: %{auth}") % { auth: AuthDB }) if not auth_plist self.rights = auth_plist["rights"].dup self.rules = auth_plist["rules"].dup self.parsed_auth_db = self.rights.dup self.parsed_auth_db.merge!(self.rules.dup) end end # standard required provider instance methods def initialize(resource) if self.class.parsed_auth_db == {} self.class.prefetch(resource) end super end def create # we just fill the @property_hash in here and let the flush method # deal with it rather than repeating code. new_values = {} validprops = Puppet::Type.type(resource.class.name).validproperties validprops.each do |prop| next if prop == :ensure if value = resource.should(prop) and value != "" new_values[prop] = value end end @property_hash = new_values.dup end def destroy # We explicitly delete here rather than in the flush method. case resource[:auth_type] when :right destroy_right when :rule destroy_rule else raise Puppet::Error.new(_("Must specify auth_type when destroying.")) end end def exists? !!self.class.parsed_auth_db.has_key?(resource[:name]) end def flush # deletion happens in the destroy methods if resource[:ensure] != :absent case resource[:auth_type] when :right flush_right when :rule flush_rule else raise Puppet::Error.new(_("flush requested for unknown type.")) end @property_hash.clear end end # utility methods below def destroy_right security "authorizationdb", :remove, resource[:name] end def destroy_rule authdb = Puppet::Util::Plist.parse_plist(AuthDB) authdb_rules = authdb["rules"].dup if authdb_rules[resource[:name]] begin authdb["rules"].delete(resource[:name]) Puppet::Util::Plist.write_plist_file(authdb, AuthDB) rescue Errno::EACCES => e raise Puppet::Error.new(_("Error saving %{auth}: %{error}") % { auth: AuthDB, error: e }, e) end end end def flush_right # first we re-read the right just to make sure we're in sync for # values that weren't specified in the manifest. As we're supplying # the whole plist when specifying the right it seems safest to be # paranoid given the low cost of querying the db once more. cmds = [] cmds << :security << "authorizationdb" << "read" << resource[:name] output = execute(cmds, :failonfail => false, :combine => false) current_values = Puppet::Util::Plist.parse_plist(output) current_values ||= {} specified_values = convert_plist_to_native_attributes(@property_hash) # take the current values, merge the specified values to obtain a # complete description of the new values. new_values = current_values.merge(specified_values) set_right(resource[:name], new_values) end def flush_rule authdb = Puppet::Util::Plist.parse_plist(AuthDB) authdb_rules = authdb["rules"].dup current_values = {} current_values = authdb_rules[resource[:name]] if authdb_rules[resource[:name]] specified_values = convert_plist_to_native_attributes(@property_hash) new_values = current_values.merge(specified_values) set_rule(resource[:name], new_values) end def set_right(name, values) # Both creates and modifies rights as it simply overwrites them. # The security binary only allows for writes using stdin, so we # dump the values to a tempfile. values = convert_plist_to_native_attributes(values) tmp = Tempfile.new('puppet_macauthorization') begin Puppet::Util::Plist.write_plist_file(values, tmp.path) cmds = [] cmds << :security << "authorizationdb" << "write" << name execute(cmds, :failonfail => false, :combine => false, :stdinfile => tmp.path.to_s) rescue Errno::EACCES => e raise Puppet::Error.new(_("Cannot save right to %{path}: %{error}") % { path: tmp.path, error: e }, e) ensure tmp.close tmp.unlink end end def set_rule(name, values) # Both creates and modifies rules as it overwrites the entry in the # rules dictionary. Unfortunately the security binary doesn't # support modifying rules at all so we have to twiddle the whole # plist... :( See Apple Bug #6386000 values = convert_plist_to_native_attributes(values) authdb = Puppet::Util::Plist.parse_plist(AuthDB) authdb["rules"][name] = values begin Puppet::Util::Plist.write_plist_file(authdb, AuthDB) rescue raise Puppet::Error.new(_("Error writing to: %{auth_db}") % { auth_db: AuthDB }) end end def convert_plist_to_native_attributes(propertylist) # This mainly converts the keys from the puppet attributes to the # 'native' ones, but also enforces that the keys are all Strings # rather than Symbols so that any merges of the resultant Hash are # sane. The exception is booleans, where we coerce to a proper bool # if they come in as a symbol. newplist = {} propertylist.each_pair do |key, value| next if key == :ensure # not part of the auth db schema. next if key == :auth_type # not part of the auth db schema. case value when true, :true value = true when false, :false value = false end new_key = key if PuppetToNativeAttributeMap.has_key?(key) new_key = PuppetToNativeAttributeMap[key].to_s elsif not key.is_a?(String) new_key = key.to_s end newplist[new_key] = value end newplist end def retrieve_value(resource_name, attribute) # We set boolean values to symbols when retrieving values raise Puppet::Error.new(_("Cannot find %{resource_name} in auth db") % { resource_name: resource_name }) if not self.class.parsed_auth_db.has_key?(resource_name) if PuppetToNativeAttributeMap.has_key?(attribute) native_attribute = PuppetToNativeAttributeMap[attribute] else native_attribute = attribute.to_s end if self.class.parsed_auth_db[resource_name].has_key?(native_attribute) value = self.class.parsed_auth_db[resource_name][native_attribute] case value when true, :true value = :true when false, :false value = :false end @property_hash[attribute] = value return value else @property_hash.delete(attribute) return "" # so ralsh doesn't display it. end end # property methods below # # We define them all dynamically apart from auth_type which is a special # case due to not being in the actual authorization db schema. properties = [ :allow_root, :authenticate_user, :auth_class, :comment, :group, :k_of_n, :mechanisms, :rule, :session_owner, :shared, :timeout, :tries ] properties.each do |field| define_method(field.to_s) do retrieve_value(resource[:name], field) end define_method(field.to_s + "=") do |value| @property_hash[field] = value end end def auth_type if resource.should(:auth_type) != nil return resource.should(:auth_type) elsif self.exists? # this is here just for ralsh, so it can work out what type it is. if self.class.rights.has_key?(resource[:name]) return :right elsif self.class.rules.has_key?(resource[:name]) return :rule else raise Puppet::Error.new(_("%{resource} is unknown type.") % { resource: resource[:name] }) end else raise Puppet::Error.new(_("auth_type required for new resources.")) end end def auth_type=(value) @property_hash[:auth_type] = value end end ��������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/mailalias/��������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020670� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/mailalias/aliases.rb����������������������������������������������0000644�0052762�0001160�00000002303�13417161721�022627� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/parsedfile' Puppet::Type.type(:mailalias).provide( :aliases, :parent => Puppet::Provider::ParsedFile, :default_target => "/etc/aliases", :filetype => :flat ) do text_line :comment, :match => /^#/ text_line :blank, :match => /^\s*$/ record_line :aliases, :fields => %w{name recipient}, :separator => /\s*:\s*/, :block_eval => :instance do def post_parse(record) if record[:recipient] record[:recipient] = record[:recipient].split(/\s*,\s*/).collect { |d| d.gsub(/^['"]|['"]$/, '') } end record end def process(line) ret = {} records = line.split(':',4) ret[:name] = records[0].strip if records.length == 4 and records[2].strip == 'include' ret[:file] = records[3].strip else records = line.split(':',2) ret[:recipient] = records[1].strip end ret end def to_line(record) if record[:recipient] dest = record[:recipient].collect do |d| # Quote aliases that have non-alpha chars if d =~ /[^-+\w@.]/ '"%s"' % d else d end end.join(",") "#{record[:name]}: #{dest}" elsif record[:file] "#{record[:name]}: :include: #{record[:file]}" end end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/maillist/���������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020552� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/maillist/mailman.rb�����������������������������������������������0000644�0052762�0001160�00000005454�13417161721�022520� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/parsedfile' Puppet::Type.type(:maillist).provide(:mailman) do if [ "CentOS", "RedHat", "Fedora" ].any? { |os| Facter.value(:operatingsystem) == os } commands :list_lists => "/usr/lib/mailman/bin/list_lists", :rmlist => "/usr/lib/mailman/bin/rmlist", :newlist => "/usr/lib/mailman/bin/newlist" commands :mailman => "/usr/lib/mailman/mail/mailman" else # This probably won't work for non-Debian installs, but this path is sure not to be in the PATH. commands :list_lists => "list_lists", :rmlist => "rmlist", :newlist => "newlist" commands :mailman => "/var/lib/mailman/mail/mailman" end mk_resource_methods # Return a list of existing mailman instances. def self.instances list_lists('--bare'). split("\n"). collect { |line| new(:ensure => :present, :name => line.strip) } end # Prefetch our list list, yo. def self.prefetch(lists) instances.each do |prov| if list = lists[prov.name] || lists[prov.name.downcase] list.provider = prov end end end def aliases mailman = self.class.command(:mailman) name = self.name.downcase aliases = {name => "| #{mailman} post #{name}"} %w{admin bounces confirm join leave owner request subscribe unsubscribe}.each do |address| aliases["#{name}-#{address}"] = "| #{mailman} #{address} #{name}" end aliases end # Create the list. def create args = [] if val = @resource[:mailserver] args << "--emailhost" << val end if val = @resource[:webserver] args << "--urlhost" << val end args << self.name if val = @resource[:admin] args << val else raise ArgumentError, _("Mailman lists require an administrator email address") end if val = @resource[:password] args << val else raise ArgumentError, _("Mailman lists require an administrator password") end newlist(*args) end # Delete the list. def destroy(purge = false) args = [] args << "--archives" if purge args << self.name rmlist(*args) end # Does our list exist already? def exists? properties[:ensure] != :absent end # Clear out the cached values. def flush @property_hash.clear end # Look up the current status. def properties if @property_hash.empty? @property_hash = query || {:ensure => :absent} @property_hash[:ensure] = :absent if @property_hash.empty? end @property_hash.dup end # Remove the list and its archives. def purge destroy(true) end # Pull the current state of the list from the full list. We're # getting some double entendre here.... def query self.class.instances.each do |list| if list.name == self.name or list.name.downcase == self.name return list.properties end end nil end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/mcx/��������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�017523� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/mcx/mcxcontent.rb�������������������������������������������������0000644�0052762�0001160�00000010652�13417161721�022231� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'tempfile' Puppet::Type.type(:mcx).provide :mcxcontent, :parent => Puppet::Provider do desc "MCX Settings management using DirectoryService on OS X. This provider manages the entire MCXSettings attribute available to some directory services nodes. This management is 'all or nothing' in that discrete application domain key value pairs are not managed by this provider. It is recommended to use WorkGroup Manager to configure Users, Groups, Computers, or ComputerLists, then use 'ralsh mcx' to generate a puppet manifest from the resulting configuration. Original Author: Jeff McCune (mccune.jeff@gmail.com) " # This provides a mapping of puppet types to DirectoryService # type strings. TypeMap = { :user => "Users", :group => "Groups", :computer => "Computers", :computerlist => "ComputerLists", } class MCXContentProviderException < Exception end commands :dscl => "/usr/bin/dscl" confine :operatingsystem => :darwin defaultfor :operatingsystem => :darwin def self.instances mcx_list = [] TypeMap.each_key do |ds_type| ds_path = "/Local/Default/#{TypeMap[ds_type]}" output = dscl 'localhost', '-list', ds_path member_list = output.split member_list.each do |ds_name| content = mcxexport(ds_type, ds_name) if content.empty? Puppet.debug "/#{TypeMap[ds_type]}/#{ds_name} has no MCX data." else # This node has MCX data. mcx_list << self.new( :name => "/#{TypeMap[ds_type]}/#{ds_name}", :ds_type => ds_type, :ds_name => ds_name, :content => content ) end end end mcx_list end def self.mcxexport(ds_type, ds_name) ds_t = TypeMap[ds_type] ds_n = ds_name.to_s ds_path = "/Local/Default/#{ds_t}/#{ds_n}" dscl 'localhost', '-mcxexport', ds_path end def create self.content=(resource[:content]) end def destroy ds_parms = get_dsparams ds_t = TypeMap[ds_parms[:ds_type]] ds_n = ds_parms[:ds_name].to_s ds_path = "/Local/Default/#{ds_t}/#{ds_n}" dscl 'localhost', '-mcxdelete', ds_path end def exists? begin has_mcx? rescue Puppet::ExecutionFailure return false end end def content ds_parms = get_dsparams self.class.mcxexport(ds_parms[:ds_type], ds_parms[:ds_name]) end def content=(value) # dscl localhost -mcximport ds_parms = get_dsparams mcximport(ds_parms[:ds_type], ds_parms[:ds_name], resource[:content]) end private def has_mcx? !content.empty? end def mcximport(ds_type, ds_name, val) ds_t = TypeMap[ds_type] ds_path = "/Local/Default/#{ds_t}/#{ds_name}" if has_mcx? Puppet.debug "Removing MCX from #{ds_path}" dscl 'localhost', '-mcxdelete', ds_path end # val being passed in is resource[:content] which should be UTF-8 tmp = Tempfile.new('puppet_mcx', :encoding => Encoding::UTF_8) begin tmp << val tmp.flush Puppet.debug "Importing MCX into #{ds_path}" dscl 'localhost', '-mcximport', ds_path, tmp.path ensure tmp.close tmp.unlink end end # Given the resource name string, parse ds_type out. def parse_type(name) ds_type = name.split('/')[1] unless ds_type raise MCXContentProviderException, _("Could not parse ds_type from resource name '%{name}'. Specify with ds_type parameter.") % { name: name } end # De-pluralize and downcase. ds_type = ds_type.chop.downcase.to_sym unless TypeMap.key? ds_type raise MCXContentProviderException, _("Could not parse ds_type from resource name '%{name}'. Specify with ds_type parameter.") % { name: name } end ds_type end # Given the resource name string, parse ds_name out. def parse_name(name) ds_name = name.split('/')[2] unless ds_name raise MCXContentProviderException, _("Could not parse ds_name from resource name '%{name}'. Specify with ds_name parameter.") % { name: name } end ds_name end # Gather ds_type and ds_name from resource or parse it out of the name. def get_dsparams ds_type = resource[:ds_type] ds_type ||= parse_type(resource[:name]) raise MCXContentProviderException unless TypeMap.keys.include? ds_type.to_sym ds_name = resource[:ds_name] ds_name ||= parse_name(resource[:name]) { :ds_type => ds_type.to_sym, :ds_name => ds_name, } end end ��������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/mount.rb����������������������������������������������������������0000644�0052762�0001160�00000004311�13417161721�020415� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet' # A module just to store the mount/unmount methods. Individual providers # still need to add the mount commands manually. module Puppet::Provider::Mount # This only works when the mount point is synced to the fstab. def mount args = [] # In general we do not have to pass mountoptions because we always # flush /etc/fstab before attempting to mount. But old code suggests # that MacOS always needs the mount options to be explicitly passed to # the mount command if Facter.value(:kernel) == 'Darwin' args << "-o" << self.options if self.options and self.options != :absent end args << resource[:name] mountcmd(*args) case get(:ensure) when :absent; set(:ensure => :ghost) when :unmounted; set(:ensure => :mounted) end end def remount #TRANSLATORS refers to remounting a file system info _("Remounting") os = Facter.value(:operatingsystem) supports_remounts = (resource[:remounts] == :true) if supports_remounts && os == 'AIX' remount_with_option("remount") elsif os.match(/^(FreeBSD|DragonFly|OpenBSD)$/) remount_with_option("update") elsif supports_remounts mountcmd "-o", "remount", resource[:name] else unmount mount end end # Remount by appending the supplied param "option" to any existing explicitly # defined options. If resource has no explicitly defined options, will mount # with only "option". # @param [String] option A remount option to use or append with existing options # def remount_with_option(option) if using_explicit_options? options = self.options + "," + option else options = option end mountcmd "-o", options, resource[:name] end def using_explicit_options? !self.options.nil? && !self.options.empty? end # This only works when the mount point is synced to the fstab. def unmount umount(resource[:name]) # Update property hash for future queries (e.g. refresh is called) case get(:ensure) when :mounted; set(:ensure => :unmounted) when :ghost; set(:ensure => :absent) end end # Is the mount currently mounted? def mounted? [:mounted, :ghost].include?(get(:ensure)) end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/mount/������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020076� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/mount/parsed.rb���������������������������������������������������0000644�0052762�0001160�00000024027�13417161721�021701� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/parsedfile' require 'puppet/provider/mount' fstab = nil case Facter.value(:osfamily) when "Solaris"; fstab = "/etc/vfstab" when "AIX"; fstab = "/etc/filesystems" else fstab = "/etc/fstab" end Puppet::Type.type(:mount).provide( :parsed, :parent => Puppet::Provider::ParsedFile, :default_target => fstab, :filetype => :flat ) do include Puppet::Provider::Mount commands :mountcmd => "mount", :umount => "umount" case Facter.value(:osfamily) when "Solaris" @fields = [:device, :blockdevice, :name, :fstype, :pass, :atboot, :options] else @fields = [:device, :name, :fstype, :options, :dump, :pass] end if Facter.value(:osfamily) == "AIX" # * is the comment character on AIX /etc/filesystems text_line :comment, :match => /^\s*\*/ else text_line :comment, :match => /^\s*#/ end text_line :blank, :match => /^\s*$/ optional_fields = @fields - [:device, :name, :blockdevice] mandatory_fields = @fields - optional_fields # fstab will ignore lines that have fewer than the mandatory number of columns, # so we should, too. field_pattern = '(\s*(?>\S+))' text_line :incomplete, :match => /^(?!#{field_pattern}{#{mandatory_fields.length}})/ case Facter.value(:osfamily) when "AIX" # The only field that is actually ordered is :name. See `man filesystems` on AIX @fields = [:name, :account, :boot, :check, :dev, :free, :mount, :nodename, :options, :quota, :size, :type, :vfs, :vol, :log] self.line_separator = "\n" # Override lines and use scan instead of split, because we DON'T want to # remove the separators def self.lines(text) lines = text.split("\n") filesystem_stanza = false filesystem_index = 0 ret = Array.new lines.each_with_index do |line,i| if line.match(%r{^\S+:}) # Begin new filesystem stanza and save the index ret[filesystem_index] = filesystem_stanza.join("\n") if filesystem_stanza filesystem_stanza = Array(line) filesystem_index = i # Eat the preceding blank line ret[i-1] = nil if i > 0 and ret[i-1] and ret[i-1].match(%r{^\s*$}) nil elsif line.match(%r{^(\s*\*.*|\s*)$}) # Just a comment or blank line; add in place ret[i] = line else # Non-comments or blank lines must be part of a stanza filesystem_stanza << line end end # Add the final stanza to the return ret[filesystem_index] = filesystem_stanza.join("\n") if filesystem_stanza ret = ret.compact.flatten ret.reject { |line| line.match(/^\* HEADER/) } end def self.header super.gsub(/^#/,'*') end record_line self.name, :fields => @fields, :separator => /\n/, :block_eval => :instance do def post_parse(result) property_map = { :dev => :device, :nodename => :nodename, :options => :options, :vfs => :fstype, } # Result is modified in-place instead of being returned; icky! memo = result.dup result.clear # Save the line for later, just in case it is unparsable result[:line] = @fields.collect do |field| memo[field] if memo[field] != :absent end.compact.join("\n") result[:record_type] = memo[:record_type] special_options = Array.new result[:name] = memo[:name].sub(%r{:\s*$},'').strip memo.each do |_,k_v| if k_v and k_v.is_a?(String) and k_v.match("=") attr_name, attr_value = k_v.split("=",2).map(&:strip) if attr_map_name = property_map[attr_name.to_sym] # These are normal "options" options (see `man filesystems`) result[attr_map_name] = attr_value else # These /etc/filesystem attributes have no mount resource simile, # so are added to the "options" property for puppet's sake special_options << "#{attr_name}=#{attr_value}" end if result[:nodename] result[:device] = "#{result[:nodename]}:#{result[:device]}" result.delete(:nodename) end end end result[:options] = [result[:options],special_options.sort].flatten.compact.join(',') if ! result[:device] result[:device] = :absent #TRANSLATORS "prefetch" is a program name and should not be translated Puppet.err _("Prefetch: Mount[%{name}]: Field 'device' is missing") % { name: result[:name] } end if ! result[:fstype] result[:fstype] = :absent #TRANSLATORS "prefetch" is a program name and should not be translated Puppet.err _("Prefetch: Mount[%{name}]: Field 'fstype' is missing") % { name: result[:name] } end end def to_line(result) output = Array.new output << "#{result[:name]}:" if result[:device] and result[:device].match(%r{^/}) output << "\tdev\t\t= #{result[:device]}" elsif result[:device] and result[:device] != :absent if ! result[:device].match(%{^.+:/}) # Just skip this entry; it was malformed to begin with Puppet.err _("Mount[%{name}]: Field 'device' must be in the format of <absolute path> or <host>:<absolute path>") % { name: result[:name] } return result[:line] end nodename, path = result[:device].split(":") output << "\tdev\t\t= #{path}" output << "\tnodename\t= #{nodename}" else # Just skip this entry; it was malformed to begin with Puppet.err _("Mount[%{name}]: Field 'device' is required") % { name: result[:name] } return result[:line] end if result[:fstype] and result[:fstype] != :absent output << "\tvfs\t\t= #{result[:fstype]}" else # Just skip this entry; it was malformed to begin with Puppet.err _("Mount[%{name}]: Field 'device' is required") % { name: result[:name] } return result[:line] end if result[:options] options = result[:options].split(',') special_options = options.select do |x| x.match('=') and ["account", "boot", "check", "free", "mount", "size", "type", "vol", "log", "quota"].include? x.split('=').first end options = options - special_options special_options.sort.each do |x| k, v = x.split("=") output << "\t#{k}\t\t= #{v}" end output << "\toptions\t\t= #{options.join(",")}" unless options.empty? end if result[:line] and result[:line].split("\n").sort == output.sort return "\n#{result[:line]}" else return "\n#{output.join("\n")}" end end end else record_line self.name, :fields => @fields, :separator => /\s+/, :joiner => "\t", :optional => optional_fields, :block_eval => :instance do def pre_gen(record) if !record[:options] || record[:options].empty? if Facter.value(:kernel) == 'Linux' record[:options] = 'defaults' else raise Puppet::Error, _("Mount[%{name}]: Field 'options' is required") % { name: record[:name] } end end if !record[:fstype] || record[:fstype].empty? raise Puppet::Error, _("Mount[%{name}]: Field 'fstype' is required") % { name: record[:name] } end record end end end # Every entry in fstab is :unmounted until we can prove different def self.prefetch_hook(target_records) target_records.collect do |record| record[:ensure] = :unmounted if record[:record_type] == :parsed record end end def self.instances providers = super mounts = mountinstances.dup # Update fstab entries that are mounted providers.each do |prov| if mounts.delete({:name => prov.get(:name), :mounted => :yes}) then prov.set(:ensure => :mounted) end end # Add mounts that are not in fstab but mounted mounts.each do |mount| providers << new(:ensure => :ghost, :name => mount[:name]) end providers end def self.prefetch(resources = nil) # Get providers for all resources the user defined and that match # a record in /etc/fstab. super # We need to do two things now: # - Update ensure from :unmounted to :mounted if the resource is mounted # - Check for mounted devices that are not in fstab and # set ensure to :ghost (if the user wants to add an entry # to fstab we need to know if the device was mounted before) mountinstances.each do |hash| if mount = resources[hash[:name]] case mount.provider.get(:ensure) when :absent # Mount not in fstab mount.provider.set(:ensure => :ghost) when :unmounted # Mount in fstab mount.provider.set(:ensure => :mounted) end end end end def self.mountinstances # XXX: Will not work for mount points that have spaces in path (does fstab support this anyways?) regex = case Facter.value(:osfamily) when "Darwin" / on (?:\/private\/var\/automount)?(\S*)/ when "Solaris", "HP-UX" /^(\S*) on / when "AIX" /^(?:\S*\s+\S+\s+)(\S+)/ else / on (\S*)/ end instances = [] mount_output = mountcmd.split("\n") if mount_output.length >= 2 and mount_output[1] =~ /^[- \t]*$/ # On some OSes (e.g. AIX) mount output begins with a header line # followed by a line consisting of dashes and whitespace. # Discard these two lines. mount_output[0..1] = [] end mount_output.each do |line| if match = regex.match(line) and name = match.captures.first instances << {:name => name, :mounted => :yes} # Only :name is important here else raise Puppet::Error, _("Could not understand line %{line} from mount output") % { line: line } end end instances end def flush needs_mount = @property_hash.delete(:needs_mount) super mount if needs_mount end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/naginator.rb������������������������������������������������������0000644�0052762�0001160�00000004173�13417161721�021243� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet' require 'puppet/provider/parsedfile' require 'puppet/external/nagios' # The base class for all Naginator providers. class Puppet::Provider::Naginator < Puppet::Provider::ParsedFile NAME_STRING = "## --PUPPET_NAME-- (called '_naginator_name' in the manifest)" # Retrieve the associated class from Nagios::Base. def self.nagios_type unless @nagios_type name = resource_type.name.to_s.sub(/^nagios_/, '') unless @nagios_type = Nagios::Base.type(name.to_sym) raise Puppet::DevError, _("Could not find nagios type '%{name}'") % { name: name } end # And add our 'ensure' settings, since they aren't a part of # Naginator by default @nagios_type.send(:attr_accessor, :ensure, :target, :on_disk) end @nagios_type end def self.parse(text) Nagios::Parser.new.parse(text.gsub(NAME_STRING, "_naginator_name")) rescue => detail raise Puppet::Error, _("Could not parse configuration for %{resource}: %{detail}") % { resource: resource_type.name, detail: detail }, detail.backtrace end def self.to_file(records) header + records.collect { |record| # Remap the TYPE_name or _naginator_name params to the # name if the record is a template (register == 0) if record.to_s =~ /register\s+0/ record.to_s.sub("_naginator_name", "name").sub(record.type.to_s + "_name", "name") else record.to_s.sub("_naginator_name", NAME_STRING) end }.join("\n") end def self.skip_record?(record) false end def self.valid_attr?(klass, attr_name) nagios_type.parameters.include?(attr_name) end def initialize(resource = nil) if resource.is_a?(Nagios::Base) # We don't use a duplicate here, because some providers (ParsedFile, at least) # use the hash here for later events. @property_hash = resource elsif resource @resource = resource if resource # LAK 2007-05-09: Keep the model stuff around for backward compatibility @model = resource @property_hash = self.class.nagios_type.new else @property_hash = self.class.nagios_type.new end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/scheduled_task/���������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�021716� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/scheduled_task/win32_taskscheduler.rb�����������������������������0000644�0052762�0001160�00000046317�13417161721�026134� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parameter' if Puppet.features.microsoft_windows? require 'puppet/util/windows/taskscheduler' end Puppet::Type.type(:scheduled_task).provide(:win32_taskscheduler) do desc %q{This provider manages scheduled tasks on Windows.} defaultfor :operatingsystem => :windows confine :operatingsystem => :windows MINUTES_IN_DAY = 1440 def self.instances Win32::TaskScheduler.new.tasks.collect do |job_file| job_title = File.basename(job_file, '.job') new( :provider => :win32_taskscheduler, :name => job_title ) end end def exists? Win32::TaskScheduler.new.exists? resource[:name] end def task return @task if @task @task ||= Win32::TaskScheduler.new @task.activate(resource[:name] + '.job') if exists? @task end def clear_task @task = nil @triggers = nil end def enabled task.flags & Win32::TaskScheduler::DISABLED == 0 ? :true : :false end def command task.application_name end def arguments task.parameters end def working_dir task.working_directory end def user account = task.account_information return 'system' if account == '' account end def trigger return @triggers if @triggers @triggers = [] task.trigger_count.times do |i| trigger = begin task.trigger(i) rescue Win32::TaskScheduler::Error # Win32::TaskScheduler can't handle all of the # trigger types Windows uses, so we need to skip the # unhandled types to prevent "puppet resource" from # blowing up. nil end next unless trigger and scheduler_trigger_types.include?(trigger['trigger_type']) puppet_trigger = {} case trigger['trigger_type'] when Win32::TaskScheduler::TASK_TIME_TRIGGER_DAILY puppet_trigger['schedule'] = 'daily' puppet_trigger['every'] = trigger['type']['days_interval'].to_s when Win32::TaskScheduler::TASK_TIME_TRIGGER_WEEKLY puppet_trigger['schedule'] = 'weekly' puppet_trigger['every'] = trigger['type']['weeks_interval'].to_s puppet_trigger['day_of_week'] = days_of_week_from_bitfield(trigger['type']['days_of_week']) when Win32::TaskScheduler::TASK_TIME_TRIGGER_MONTHLYDATE puppet_trigger['schedule'] = 'monthly' puppet_trigger['months'] = months_from_bitfield(trigger['type']['months']) puppet_trigger['on'] = days_from_bitfield(trigger['type']['days']) when Win32::TaskScheduler::TASK_TIME_TRIGGER_MONTHLYDOW puppet_trigger['schedule'] = 'monthly' puppet_trigger['months'] = months_from_bitfield(trigger['type']['months']) puppet_trigger['which_occurrence'] = occurrence_constant_to_name(trigger['type']['weeks']) puppet_trigger['day_of_week'] = days_of_week_from_bitfield(trigger['type']['days_of_week']) when Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE puppet_trigger['schedule'] = 'once' end puppet_trigger['start_date'] = self.class.normalized_date("#{trigger['start_year']}-#{trigger['start_month']}-#{trigger['start_day']}") puppet_trigger['start_time'] = self.class.normalized_time("#{trigger['start_hour']}:#{trigger['start_minute']}") puppet_trigger['enabled'] = trigger['flags'] & Win32::TaskScheduler::TASK_TRIGGER_FLAG_DISABLED == 0 puppet_trigger['minutes_interval'] = trigger['minutes_interval'] ||= 0 puppet_trigger['minutes_duration'] = trigger['minutes_duration'] ||= 0 puppet_trigger['index'] = i @triggers << puppet_trigger end @triggers end def user_insync?(current, should) return false unless current # Win32::TaskScheduler can return the 'SYSTEM' account as the # empty string. current = 'system' if current == '' # By comparing account SIDs we don't have to worry about case # sensitivity, or canonicalization of the account name. Puppet::Util::Windows::SID.name_to_sid(current) == Puppet::Util::Windows::SID.name_to_sid(should[0]) end def trigger_insync?(current, should) should = [should] unless should.is_a?(Array) current = [current] unless current.is_a?(Array) return false unless current.length == should.length current_in_sync = current.all? do |c| should.any? {|s| triggers_same?(c, s)} end should_in_sync = should.all? do |s| current.any? {|c| triggers_same?(c,s)} end current_in_sync && should_in_sync end def command=(value) task.application_name = value end def arguments=(value) task.parameters = value end def working_dir=(value) task.working_directory = value end def enabled=(value) if value == :true task.flags = task.flags & ~Win32::TaskScheduler::DISABLED else task.flags = task.flags | Win32::TaskScheduler::DISABLED end end def trigger=(value) desired_triggers = value.is_a?(Array) ? value : [value] current_triggers = trigger.is_a?(Array) ? trigger : [trigger] extra_triggers = [] desired_to_search = desired_triggers.dup current_triggers.each do |current| if found = desired_to_search.find {|desired| triggers_same?(current, desired)} desired_to_search.delete(found) else extra_triggers << current['index'] end end needed_triggers = [] current_to_search = current_triggers.dup desired_triggers.each do |desired| if found = current_to_search.find {|current| triggers_same?(current, desired)} current_to_search.delete(found) else needed_triggers << desired end end extra_triggers.reverse_each do |index| task.delete_trigger(index) end needed_triggers.each do |trigger_hash| # Even though this is an assignment, the API for # Win32::TaskScheduler ends up appending this trigger to the # list of triggers for the task, while #add_trigger is only able # to replace existing triggers. *shrug* task.trigger = translate_hash_to_trigger(trigger_hash) end end def user=(value) self.fail("Invalid user: #{value}") unless Puppet::Util::Windows::SID.name_to_sid(value) if value.to_s.downcase != 'system' task.set_account_information(value, resource[:password]) else # Win32::TaskScheduler treats a nil/empty username & password as # requesting the SYSTEM account. task.set_account_information(nil, nil) end end def create clear_task @task = Win32::TaskScheduler.new(resource[:name], dummy_time_trigger) self.command = resource[:command] [:arguments, :working_dir, :enabled, :trigger, :user].each do |prop| send("#{prop}=", resource[prop]) if resource[prop] end end def destroy Win32::TaskScheduler.new.delete(resource[:name] + '.job') end def flush unless resource[:ensure] == :absent self.fail('Parameter command is required.') unless resource[:command] # HACK: even though the user may actually be insync?, for task changes to # fully propagate, it is necessary to explicitly set the user for the task, # even when it is SYSTEM (and has a nil password) # this is a Windows security feature with the v1 COM APIs that prevent # arbitrary reassignment of a task scheduler command to run as SYSTEM # without the authorization to do so self.user = resource[:user] task.save @task = nil end end def triggers_same?(current_trigger, desired_trigger) return false unless current_trigger['schedule'] == desired_trigger['schedule'] return false if current_trigger.has_key?('enabled') && !current_trigger['enabled'] desired = desired_trigger.dup desired['start_date'] ||= current_trigger['start_date'] if current_trigger.has_key?('start_date') desired['every'] ||= current_trigger['every'] if current_trigger.has_key?('every') desired['months'] ||= current_trigger['months'] if current_trigger.has_key?('months') desired['on'] ||= current_trigger['on'] if current_trigger.has_key?('on') desired['day_of_week'] ||= current_trigger['day_of_week'] if current_trigger.has_key?('day_of_week') translate_hash_to_trigger(current_trigger) == translate_hash_to_trigger(desired) end def self.normalized_date(date_string) date = Date.parse("#{date_string}") "#{date.year}-#{date.month}-#{date.day}" end def self.normalized_time(time_string) Time.parse("#{time_string}").strftime('%H:%M') end def dummy_time_trigger now = Time.now { 'flags' => 0, 'random_minutes_interval' => 0, 'end_day' => 0, 'end_year' => 0, 'minutes_interval' => 0, 'end_month' => 0, 'minutes_duration' => 0, 'start_year' => now.year, 'start_month' => now.month, 'start_day' => now.day, 'start_hour' => now.hour, 'start_minute' => now.min, 'trigger_type' => Win32::TaskScheduler::ONCE, } end def translate_hash_to_trigger(puppet_trigger) trigger = dummy_time_trigger if puppet_trigger['enabled'] == false trigger['flags'] |= Win32::TaskScheduler::TASK_TRIGGER_FLAG_DISABLED else trigger['flags'] &= ~Win32::TaskScheduler::TASK_TRIGGER_FLAG_DISABLED end extra_keys = puppet_trigger.keys.sort - ['index', 'enabled', 'schedule', 'start_date', 'start_time', 'every', 'months', 'on', 'which_occurrence', 'day_of_week', 'minutes_interval', 'minutes_duration'] self.fail "Unknown trigger option(s): #{Puppet::Parameter.format_value_for_display(extra_keys)}" unless extra_keys.empty? self.fail "Must specify 'start_time' when defining a trigger" unless puppet_trigger['start_time'] case puppet_trigger['schedule'] when 'daily' trigger['trigger_type'] = Win32::TaskScheduler::DAILY trigger['type'] = { 'days_interval' => Integer(puppet_trigger['every'] || 1) } when 'weekly' trigger['trigger_type'] = Win32::TaskScheduler::WEEKLY trigger['type'] = { 'weeks_interval' => Integer(puppet_trigger['every'] || 1) } trigger['type']['days_of_week'] = if puppet_trigger['day_of_week'] bitfield_from_days_of_week(puppet_trigger['day_of_week']) else scheduler_days_of_week.inject(0) {|day_flags,day| day_flags | day} end when 'monthly' trigger['type'] = { 'months' => bitfield_from_months(puppet_trigger['months'] || (1..12).to_a), } if puppet_trigger.keys.include?('on') if puppet_trigger.has_key?('day_of_week') or puppet_trigger.has_key?('which_occurrence') self.fail "Neither 'day_of_week' nor 'which_occurrence' can be specified when creating a monthly date-based trigger" end trigger['trigger_type'] = Win32::TaskScheduler::MONTHLYDATE trigger['type']['days'] = bitfield_from_days(puppet_trigger['on']) elsif puppet_trigger.keys.include?('which_occurrence') or puppet_trigger.keys.include?('day_of_week') self.fail 'which_occurrence cannot be specified as an array' if puppet_trigger['which_occurrence'].is_a?(Array) %w{day_of_week which_occurrence}.each do |field| self.fail "#{field} must be specified when creating a monthly day-of-week based trigger" unless puppet_trigger.has_key?(field) end trigger['trigger_type'] = Win32::TaskScheduler::MONTHLYDOW trigger['type']['weeks'] = occurrence_name_to_constant(puppet_trigger['which_occurrence']) trigger['type']['days_of_week'] = bitfield_from_days_of_week(puppet_trigger['day_of_week']) else self.fail "Don't know how to create a 'monthly' schedule with the options: #{puppet_trigger.keys.sort.join(', ')}" end when 'once' self.fail "Must specify 'start_date' when defining a one-time trigger" unless puppet_trigger['start_date'] trigger['trigger_type'] = Win32::TaskScheduler::ONCE else self.fail "Unknown schedule type: #{puppet_trigger["schedule"].inspect}" end integer_interval = -1 if puppet_trigger['minutes_interval'] integer_interval = Integer(puppet_trigger['minutes_interval']) self.fail 'minutes_interval must be an integer greater or equal to 0' if integer_interval < 0 trigger['minutes_interval'] = integer_interval end integer_duration = -1 if puppet_trigger['minutes_duration'] integer_duration = Integer(puppet_trigger['minutes_duration']) self.fail 'minutes_duration must be an integer greater than minutes_interval and equal to or greater than 0' if integer_duration <= integer_interval && integer_duration != 0 trigger['minutes_duration'] = integer_duration end if integer_interval > 0 && integer_duration == -1 integer_duration = MINUTES_IN_DAY trigger['minutes_duration'] = MINUTES_IN_DAY end if integer_interval >= integer_duration && integer_interval > 0 self.fail 'minutes_interval cannot be set without minutes_duration also being set to a number greater than 0' end if start_date = puppet_trigger['start_date'] start_date = Date.parse(start_date) self.fail "start_date must be on or after 1753-01-01" unless start_date >= Date.new(1753, 1, 1) trigger['start_year'] = start_date.year trigger['start_month'] = start_date.month trigger['start_day'] = start_date.day end start_time = Time.parse(puppet_trigger['start_time']) trigger['start_hour'] = start_time.hour trigger['start_minute'] = start_time.min trigger end def validate_trigger(value) value = [value] unless value.is_a?(Array) value.each do |t| if t.has_key?('index') self.fail "'index' is read-only on scheduled_task triggers and should be removed ('index' is usually provided in puppet resource scheduled_task)." end if t.has_key?('enabled') self.fail "'enabled' is read-only on scheduled_task triggers and should be removed ('enabled' is usually provided in puppet resource scheduled_task)." end translate_hash_to_trigger(t) end true end private def bitfield_from_months(months) bitfield = 0 months = [months] unless months.is_a?(Array) months.each do |month| integer_month = Integer(month) rescue nil self.fail 'Month must be specified as an integer in the range 1-12' unless integer_month == month.to_f and integer_month.between?(1,12) bitfield |= scheduler_months[integer_month - 1] end bitfield end def bitfield_from_days(days) bitfield = 0 days = [days] unless days.is_a?(Array) days.each do |day| # The special "day" of 'last' is represented by day "number" # 32. 'last' has the special meaning of "the last day of the # month", no matter how many days there are in the month. day = 32 if day == 'last' integer_day = Integer(day) self.fail "Day must be specified as an integer in the range 1-31, or as 'last'" unless integer_day.between?(1,32) bitfield |= 1 << integer_day - 1 end bitfield end def bitfield_from_days_of_week(days_of_week) bitfield = 0 days_of_week = [days_of_week] unless days_of_week.is_a?(Array) days_of_week.each do |day_of_week| bitfield |= day_of_week_name_to_constant(day_of_week) end bitfield end def months_from_bitfield(bitfield) months = [] scheduler_months.each do |month| if bitfield & month != 0 months << month_constant_to_number(month) end end months end def days_from_bitfield(bitfield) days = [] i = 0 while bitfield > 0 if bitfield & 1 > 0 # Day 32 has the special meaning of "the last day of the # month", no matter how many days there are in the month. days << (i == 31 ? 'last' : i + 1) end bitfield = bitfield >> 1 i += 1 end days end def days_of_week_from_bitfield(bitfield) days_of_week = [] scheduler_days_of_week.each do |day_of_week| if bitfield & day_of_week != 0 days_of_week << day_of_week_constant_to_name(day_of_week) end end days_of_week end def scheduler_trigger_types [ Win32::TaskScheduler::TASK_TIME_TRIGGER_DAILY, Win32::TaskScheduler::TASK_TIME_TRIGGER_WEEKLY, Win32::TaskScheduler::TASK_TIME_TRIGGER_MONTHLYDATE, Win32::TaskScheduler::TASK_TIME_TRIGGER_MONTHLYDOW, Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE ] end def scheduler_days_of_week [ Win32::TaskScheduler::SUNDAY, Win32::TaskScheduler::MONDAY, Win32::TaskScheduler::TUESDAY, Win32::TaskScheduler::WEDNESDAY, Win32::TaskScheduler::THURSDAY, Win32::TaskScheduler::FRIDAY, Win32::TaskScheduler::SATURDAY ] end def scheduler_months [ Win32::TaskScheduler::JANUARY, Win32::TaskScheduler::FEBRUARY, Win32::TaskScheduler::MARCH, Win32::TaskScheduler::APRIL, Win32::TaskScheduler::MAY, Win32::TaskScheduler::JUNE, Win32::TaskScheduler::JULY, Win32::TaskScheduler::AUGUST, Win32::TaskScheduler::SEPTEMBER, Win32::TaskScheduler::OCTOBER, Win32::TaskScheduler::NOVEMBER, Win32::TaskScheduler::DECEMBER ] end def scheduler_occurrences [ Win32::TaskScheduler::FIRST_WEEK, Win32::TaskScheduler::SECOND_WEEK, Win32::TaskScheduler::THIRD_WEEK, Win32::TaskScheduler::FOURTH_WEEK, Win32::TaskScheduler::LAST_WEEK ] end def day_of_week_constant_to_name(constant) case constant when Win32::TaskScheduler::SUNDAY; 'sun' when Win32::TaskScheduler::MONDAY; 'mon' when Win32::TaskScheduler::TUESDAY; 'tues' when Win32::TaskScheduler::WEDNESDAY; 'wed' when Win32::TaskScheduler::THURSDAY; 'thurs' when Win32::TaskScheduler::FRIDAY; 'fri' when Win32::TaskScheduler::SATURDAY; 'sat' end end def day_of_week_name_to_constant(name) case name when 'sun'; Win32::TaskScheduler::SUNDAY when 'mon'; Win32::TaskScheduler::MONDAY when 'tues'; Win32::TaskScheduler::TUESDAY when 'wed'; Win32::TaskScheduler::WEDNESDAY when 'thurs'; Win32::TaskScheduler::THURSDAY when 'fri'; Win32::TaskScheduler::FRIDAY when 'sat'; Win32::TaskScheduler::SATURDAY end end def month_constant_to_number(constant) month_num = 1 while constant >> month_num - 1 > 1 month_num += 1 end month_num end def occurrence_constant_to_name(constant) case constant when Win32::TaskScheduler::FIRST_WEEK; 'first' when Win32::TaskScheduler::SECOND_WEEK; 'second' when Win32::TaskScheduler::THIRD_WEEK; 'third' when Win32::TaskScheduler::FOURTH_WEEK; 'fourth' when Win32::TaskScheduler::LAST_WEEK; 'last' end end def occurrence_name_to_constant(name) case name when 'first'; Win32::TaskScheduler::FIRST_WEEK when 'second'; Win32::TaskScheduler::SECOND_WEEK when 'third'; Win32::TaskScheduler::THIRD_WEEK when 'fourth'; Win32::TaskScheduler::FOURTH_WEEK when 'last'; Win32::TaskScheduler::LAST_WEEK end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/selboolean/�������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�021057� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/selboolean/getsetsebool.rb����������������������������������������0000644�0052762�0001160�00000002366�13417161721�024105� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.type(:selboolean).provide(:getsetsebool) do desc "Manage SELinux booleans using the getsebool and setsebool binaries." commands :getsebool => "/usr/sbin/getsebool" commands :setsebool => "/usr/sbin/setsebool" def value self.debug "Retrieving value of selboolean #{@resource[:name]}" status = getsebool(@resource[:name]) if status =~ / off$/ return :off elsif status =~ / on$/ then return :on else status.chomp! raise Puppet::Error, "Invalid response '#{status}' returned from getsebool" end end def value=(new) persist = "" if @resource[:persistent] == :true self.debug "Enabling persistence" persist = "-P" end execoutput("#{command(:setsebool)} #{persist} #{@resource[:name]} #{new}") :file_changed end # Required workaround, since SELinux policy prevents setsebool # from writing to any files, even tmp, preventing the standard # 'setsebool("...")' construct from working. def execoutput (cmd) output = '' begin execpipe(cmd) do |out| output = out.readlines.join('').chomp! end rescue Puppet::ExecutionFailure raise Puppet::ExecutionFailure, output.split("\n")[0], $!.backtrace end output end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/selmodule/��������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020725� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/selmodule/semodule.rb���������������������������������������������0000644�0052762�0001160�00000007117�13417161721�023070� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.type(:selmodule).provide(:semodule) do desc "Manage SELinux policy modules using the semodule binary." commands :semodule => "/usr/sbin/semodule" def create begin execoutput("#{command(:semodule)} --install #{selmod_name_to_filename}") rescue Puppet::ExecutionFailure => detail raise Puppet::Error, "Could not load policy module: #{detail}", detail.backtrace end :true end def destroy execoutput("#{command(:semodule)} --remove #{@resource[:name]}") rescue Puppet::ExecutionFailure => detail raise Puppet::Error, "Could not remove policy module: #{detail}", detail.backtrace end def exists? self.debug "Checking for module #{@resource[:name]}" execpipe("#{command(:semodule)} --list") do |out| out.each_line do |line| if line =~ /^#{@resource[:name]}\b/ return :true end end end nil end def syncversion self.debug "Checking syncversion on #{@resource[:name]}" loadver = selmodversion_loaded if(loadver) then filever = selmodversion_file if (filever == loadver) return :true end end :false end def syncversion= (dosync) execoutput("#{command(:semodule)} --upgrade #{selmod_name_to_filename}") rescue Puppet::ExecutionFailure => detail raise Puppet::Error, "Could not upgrade policy module: #{detail}", detail.backtrace end # Helper functions def execoutput (cmd) output = '' begin execpipe(cmd) do |out| output = out.readlines.join('').chomp! end rescue Puppet::ExecutionFailure raise Puppet::ExecutionFailure, output.split("\n")[0], $!.backtrace end output end def selmod_name_to_filename if @resource[:selmodulepath] return @resource[:selmodulepath] else return "#{@resource[:selmoduledir]}/#{@resource[:name]}.pp" end end def selmod_readnext (handle) len = handle.read(4).unpack('V')[0] handle.read(len) end def selmodversion_file magic = 0xF97CFF8F v = nil filename = selmod_name_to_filename # Open a file handle and parse the bytes until version is found Puppet::FileSystem.open(filename, nil, 'rb') do |mod| (hdr, ver, numsec) = mod.read(12).unpack('VVV') raise Puppet::Error, "Found #{hdr} instead of magic #{magic} in #{filename}" if hdr != magic raise Puppet::Error, "Unknown policy file version #{ver} in #{filename}" if ver != 1 # Read through (and throw away) the file section offsets, and also # the magic header for the first section. mod.read((numsec + 1) * 4) ## Section 1 should be "SE Linux Module" selmod_readnext(mod) selmod_readnext(mod) # Skip past the section headers mod.read(14) # Module name selmod_readnext(mod) # At last! the version v = selmod_readnext(mod) end self.debug "file version #{v}" v end def selmodversion_loaded selmod_output = [] selmodule_cmd = "#{command(:semodule)} --list" begin execpipe(selmodule_cmd) do |output| output.each_line do |line| line.chomp! selmod_output << line bits = line.split if bits[0] == @resource[:name] self.debug "load version #{bits[1]}" return bits[1] end end end rescue Puppet::ExecutionFailure raise Puppet::ExecutionFailure, _("Could not list policy modules: \"%{selmodule_command}\" failed with \"%{selmod_output}\"") % { selmodule_command: selmodule_cmd, selmod_output: selmod_output.join(' ') } end nil end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/ssh_authorized_key/�����������������������������������������������0000755�0052762�0001160�00000000000�13417162176�022637� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/ssh_authorized_key/parsed.rb��������������������������������������0000644�0052762�0001160�00000006550�13417161721�024443� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/parsedfile' Puppet::Type.type(:ssh_authorized_key).provide( :parsed, :parent => Puppet::Provider::ParsedFile, :filetype => :flat, :default_target => '' ) do desc "Parse and generate authorized_keys files for SSH." text_line :comment, :match => /^\s*#/ text_line :blank, :match => /^\s*$/ record_line :parsed, :fields => %w{options type key name}, :optional => %w{options}, :rts => /^\s+/, :match => Puppet::Type.type(:ssh_authorized_key).keyline_regex, :post_parse => proc { |h| h[:name] = "" if h[:name] == :absent h[:options] ||= [:absent] h[:options] = Puppet::Type::Ssh_authorized_key::ProviderParsed.parse_options(h[:options]) if h[:options].is_a? String }, :pre_gen => proc { |h| # if this name was generated, don't write it back to disk h[:name] = "" if h[:unnamed] h[:options] = [] if h[:options].include?(:absent) h[:options] = h[:options].join(',') } record_line :key_v1, :fields => %w{options bits exponent modulus name}, :optional => %w{options}, :rts => /^\s+/, :match => /^(?:(.+) )?(\d+) (\d+) (\d+)(?: (.+))?$/ def dir_perm 0700 end def file_perm 0600 end def user uid = Puppet::FileSystem.stat(target).uid Etc.getpwuid(uid).name end def flush raise Puppet::Error, "Cannot write SSH authorized keys without user" unless @resource.should(:user) raise Puppet::Error, "User '#{@resource.should(:user)}' does not exist" unless Puppet::Util.uid(@resource.should(:user)) # ParsedFile usually calls backup_target much later in the flush process, # but our SUID makes that fail to open filebucket files for writing. # Fortunately, there's already logic to make sure it only ever happens once, # so calling it here suppresses the later attempt by our superclass's flush method. self.class.backup_target(target) Puppet::Util::SUIDManager.asuser(@resource.should(:user)) do unless Puppet::FileSystem.exist?(dir = File.dirname(target)) Puppet.debug "Creating #{dir} as #{@resource.should(:user)}" Dir.mkdir(dir, dir_perm) end super File.chmod(file_perm, target) end end # Parse sshv2 option strings, which is a comma-separated list of # either key="values" elements or bare-word elements def self.parse_options(options) result = [] scanner = StringScanner.new(options) while !scanner.eos? scanner.skip(/[ \t]*/) # scan a long option if out = scanner.scan(/[-a-z0-9A-Z_]+=\".*?[^\\]\"/) or out = scanner.scan(/[-a-z0-9A-Z_]+/) result << out else # found an unscannable token, let's abort break end # eat a comma scanner.skip(/[ \t]*,[ \t]*/) end result end def self.prefetch_hook(records) name_index = 0 records.each do |record| if record[:record_type] == :parsed && record[:name].empty? record[:unnamed] = true # Generate a unique ID for unnamed keys, in case they need purging. # If you change this, you have to keep # Puppet::Type::User#unknown_keys_in_file in sync! (PUP-3357) record[:name] = "#{record[:target]}:unnamed-#{ name_index += 1 }" Puppet.debug("generating name for on-disk ssh_authorized_key #{record[:key]}: #{record[:name]}") end end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/sshkey/�����������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020242� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/sshkey/parsed.rb��������������������������������������������������0000644�0052762�0001160�00000002403�13417161721�022037� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/parsedfile' Puppet::Type.type(:sshkey).provide( :parsed, :parent => Puppet::Provider::ParsedFile, :filetype => :flat ) do desc "Parse and generate host-wide known hosts files for SSH." text_line :comment, :match => /^#/ text_line :blank, :match => /^\s*$/ record_line :parsed, :fields => %w{name type key}, :post_parse => proc { |hash| names = hash[:name].split(",", -1) hash[:name] = names.shift hash[:host_aliases] = names }, :pre_gen => proc { |hash| if hash[:host_aliases] hash[:name] = [hash[:name], hash[:host_aliases]].flatten.join(",") hash.delete(:host_aliases) end } # Make sure to use mode 644 if ssh_known_hosts is newly created def self.default_mode 0644 end def self.default_target case Facter.value(:operatingsystem) when "Darwin" # Versions 10.11 and up use /etc/ssh/ssh_known_hosts version = Facter.value(:macosx_productversion_major) if version if Puppet::Util::Package.versioncmp(version, '10.11') >= 0 "/etc/ssh/ssh_known_hosts" else "/etc/ssh_known_hosts" end else "/etc/ssh_known_hosts" end else "/etc/ssh/ssh_known_hosts" end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/vlan/�������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�017674� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/vlan/cisco.rb�����������������������������������������������������0000644�0052762�0001160�00000001040�13417161721�021307� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/provider/cisco' Puppet::Type.type(:vlan).provide :cisco, :parent => Puppet::Provider::Cisco do desc "Cisco switch/router provider for vlans." mk_resource_methods def self.lookup(device, id) vlans = {} device.command do |dev| vlans = dev.parse_vlans || {} end vlans[id] end def initialize(device, *args) super end # Clear out the cached values. def flush device.command do |dev| dev.update_vlan(resource[:name], former_properties, properties) end super end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/yumrepo/����������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020434� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/yumrepo/inifile.rb������������������������������������������������0000644�0052762�0001160�00000022361�13417161721�022377� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/inifile' Puppet::Type.type(:yumrepo).provide(:inifile) do desc <<-EOD Manage yum repo configurations by parsing yum INI configuration files. ### Fetching instances When fetching repo instances, directory entries in '/etc/yum/repos.d', '/etc/yum.repos.d', and the directory optionally specified by the reposdir key in '/etc/yum.conf' will be checked. If a given directory does not exist it will be ignored. In addition, all sections in '/etc/yum.conf' aside from 'main' will be created as sections. ### Storing instances When creating a new repository, a new section will be added in the first yum repo directory that exists. The custom directory specified by the '/etc/yum.conf' reposdir property is checked first, followed by '/etc/yum/repos.d', and then '/etc/yum.repos.d'. If none of these exist, the section will be created in '/etc/yum.conf'. EOD PROPERTIES = Puppet::Type.type(:yumrepo).validproperties # Retrieve all providers based on existing yum repositories # # @api public # @return [Array<Puppet::Provider>] providers generated from existing yum # repository definitions. def self.instances instances = [] virtual_inifile.each_section do |section| # Ignore the 'main' section in yum.conf since it's not a repository. next if section.name == "main" attributes_hash = {:name => section.name, :ensure => :present, :provider => :yumrepo} section.entries.each do |key, value| key = key.to_sym if valid_property?(key) attributes_hash[key] = value elsif key == :name attributes_hash[:descr] = value end end instances << new(attributes_hash) end instances end # Match catalog type instances to provider instances. # # @api public # @param resources [Array<Puppet::Type::Yumrepo>] Resources to prefetch. # @return [void] def self.prefetch(resources) repos = instances resources.each_key do |name| if provider = repos.find { |repo| repo.name == name } resources[name].provider = provider end end end # Return a list of existing directories that could contain repo files. # # @api private # @param conf [String] Configuration file to look for directories in. # @param dirs [Array<String>] Default locations for yum repos. # @return [Array<String>] All present directories that may contain yum repo configs. def self.reposdir(conf='/etc/yum.conf', dirs=['/etc/yum.repos.d', '/etc/yum/repos.d']) reposdir = find_conf_value('reposdir', conf) # Use directories in reposdir if they are set instead of default if reposdir # Follow the code from the yum/config.py reposdir.gsub!("\n", ' ') reposdir.gsub!(',', ' ') dirs = reposdir.split end dirs.select! { |dir| Puppet::FileSystem.exist?(dir) } if dirs.empty? Puppet.debug('No yum directories were found on the local filesystem') end dirs end # Used for testing only # @api private def self.clear @virtual = nil end # Helper method to look up specific values in ini style files. # # @api private # @param value [String] Value to look for in the configuration file. # @param conf [String] Configuration file to check for value. # @return [String] The value of a looked up key from the configuration file. def self.find_conf_value(value, conf='/etc/yum.conf') if Puppet::FileSystem.exist?(conf) file = Puppet::Util::IniConfig::PhysicalFile.new(conf) file.read if (main = file.get_section('main')) main[value] end end end # Enumerate all files that may contain yum repository configs. # '/etc/yum.conf' is always included. # # @api private # @return [Array<String> def self.repofiles files = ["/etc/yum.conf"] reposdir.each do |dir| Dir.glob("#{dir}/*.repo").each do |file| files << file end end files end # Build a virtual inifile by reading in numerous .repo files into a single # virtual file to ease manipulation. # @api private # @return [Puppet::Util::IniConfig::File] The virtual inifile representing # multiple real files. def self.virtual_inifile unless @virtual @virtual = Puppet::Util::IniConfig::File.new self.repofiles.each do |file| @virtual.read(file) if Puppet::FileSystem.file?(file) end end return @virtual end # Is the given key a valid type property? # # @api private # @param key [String] The property to look up. # @return [Boolean] Returns true if the property is defined in the type. def self.valid_property?(key) PROPERTIES.include?(key) end # Return an existing INI section or create a new section in the default location # # The default location is determined based on what yum repo directories # and files are present. If /etc/yum.conf has a value for 'reposdir' then that # is preferred. If no such INI property is found then the first default yum # repo directory that is present is used. If no default directories exist then # /etc/yum.conf is used. # # @param name [String] Section name to lookup in the virtual inifile. # @return [Puppet::Util::IniConfig] The IniConfig section def self.section(name) result = self.virtual_inifile[name] # Create a new section if not found. unless result path = getRepoPath(name) result = self.virtual_inifile.add_section(name, path) end result end # Save all yum repository files and force the mode to 0644 # @api private # @return [void] def self.store(resource) inifile = self.virtual_inifile inifile.store target_mode = 0644 inifile.each_file do |file| next unless Puppet::FileSystem.exist?(file) current_mode = Puppet::FileSystem.stat(file).mode & 0777 unless current_mode == target_mode resource.info _("changing mode of %{file} from %{current_mode} to %{target_mode}") % { file: file, current_mode: "%03o" % current_mode, target_mode: "%03o" % target_mode } Puppet::FileSystem.chmod(target_mode, file) end end end def self.getRepoPath(name) dirs = reposdir() if dirs.empty? # If no repo directories are present, default to using yum.conf. path = '/etc/yum.conf' else # The ordering of reposdir is [defaults, custom], and we want to use # the custom directory if present. path = File.join(dirs.last, "#{name}.repo") end path end # Create a new section for the given repository and set all the specified # properties in the section. # # @api public # @return [void] def create @property_hash[:ensure] = :present # Check to see if the file that would be created in the # default location for the yumrepo already exists on disk. # If it does, read it in to the virtual inifile path = self.class.getRepoPath(name) self.class.virtual_inifile.read(path) if Puppet::FileSystem.file?(path) # We fetch a list of properties from the type, then iterate # over them, avoiding ensure. We're relying on .should to # check if the property has been set and should be modified, # and if so we set it in the virtual inifile. PROPERTIES.each do |property| next if property == :ensure if value = @resource.should(property) self.send("#{property}=", value) end end end # Does the given repository already exist? # # @api public # @return [Boolean] def exists? @property_hash[:ensure] == :present end # Mark the given repository section for destruction. # # The actual removal of the section will be handled by {#flush} after the # resource has been fully evaluated. # # @api public # @return [void] def destroy # Flag file for deletion on flush. current_section.destroy=(true) @property_hash.clear end # Finalize the application of the given resource. # # @api public # @return [void] def flush self.class.store(self) end # Generate setters and getters for our INI properties. PROPERTIES.each do |property| # The ensure property uses #create, #exists, and #destroy we can't generate # meaningful setters and getters for this next if property == :ensure define_method(property) do get_property(property) end define_method("#{property}=") do |value| set_property(property, value) end end # Map the yumrepo 'descr' type property to the 'name' INI property. def descr if ! @property_hash.has_key?(:descr) @property_hash[:descr] = current_section['name'] end value = @property_hash[:descr] value.nil? ? :absent : value end def descr=(value) value = (value == :absent ? nil : value) current_section['name'] = value @property_hash[:descr] = value end private def get_property(property) if ! @property_hash.has_key?(property) @property_hash[property] = current_section[property.to_s] end value = @property_hash[property] value.nil? ? :absent : value end def set_property(property, value) value = (value == :absent ? nil : value) current_section[property.to_s] = value @property_hash[property] = value end def section(name) self.class.section(name) end def current_section self.class.section(self.name) end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/zfs/��������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�017536� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/zfs/zfs.rb��������������������������������������������������������0000644�0052762�0001160�00000006106�13417161721�020663� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.type(:zfs).provide(:zfs) do desc "Provider for zfs." commands :zfs => 'zfs' def self.instances zfs(:list, '-H').split("\n").collect do |line| name, _used, _avail, _refer, _mountpoint = line.split(/\s+/) new({:name => name, :ensure => :present}) end end def add_properties properties = [] Puppet::Type.type(:zfs).validproperties.each do |property| next if property == :ensure if value = @resource[property] and value != "" if property == :volsize properties << "-V" << "#{value}" else properties << "-o" << "#{property}=#{value}" end end end properties end def create zfs(*([:create] + add_properties + [@resource[:name]])) end def destroy zfs(:destroy, @resource[:name]) end def exists? begin zfs(:list, @resource[:name]) true rescue Puppet::ExecutionFailure false end end # On FreeBSD zoned is called jailed def container_property case Facter.value(:operatingsystem) when "FreeBSD" :jailed else :zoned end end PARAMETER_UNSET_OR_NOT_AVAILABLE = '-' # https://docs.oracle.com/cd/E19963-01/html/821-1448/gbscy.html # shareiscsi (added in build 120) was removed from S11 build 136 # aclmode was removed from S11 in build 139 but it may have been added back # acltype is for ZFS on Linux, and allows disabling or enabling POSIX ACLs # http://webcache.googleusercontent.com/search?q=cache:-p74K0DVsdwJ:developers.slashdot.org/story/11/11/09/2343258/solaris-11-released+&cd=13 [:aclmode, :acltype, :shareiscsi].each do |field| # The zfs commands use the property value '-' to indicate that the # property is not set. We make use of this value to indicate that the # property is not set since it is not available. Conversely, if these # properties are attempted to be unset, and resulted in an error, our # best bet is to catch the exception and continue. define_method(field) do begin zfs(:get, "-H", "-o", "value", field, @resource[:name]).strip rescue PARAMETER_UNSET_OR_NOT_AVAILABLE end end define_method(field.to_s + "=") do |should| begin zfs(:set, "#{field}=#{should}", @resource[:name]) rescue end end end [:aclinherit, :atime, :canmount, :checksum, :compression, :copies, :dedup, :devices, :exec, :logbias, :mountpoint, :nbmand, :primarycache, :quota, :readonly, :recordsize, :refquota, :refreservation, :reservation, :secondarycache, :setuid, :sharenfs, :sharesmb, :snapdir, :version, :volsize, :vscan, :xattr].each do |field| define_method(field) do zfs(:get, "-H", "-o", "value", field, @resource[:name]).strip end define_method(field.to_s + "=") do |should| zfs(:set, "#{field}=#{should}", @resource[:name]) end end define_method(:zoned) do zfs(:get, "-H", "-o", "value", container_property, @resource[:name]).strip end define_method("zoned=") do |should| zfs(:set, "#{container_property}=#{should}", @resource[:name]) end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/zone/�������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�017707� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/zone/solaris.rb���������������������������������������������������0000644�0052762�0001160�00000023047�13417161721�021711� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.type(:zone).provide(:solaris) do desc "Provider for Solaris Zones." commands :adm => "/usr/sbin/zoneadm", :cfg => "/usr/sbin/zonecfg" defaultfor :osfamily => :solaris mk_resource_methods # Convert the output of a list into a hash def self.line2hash(line) fields = [:id, :name, :ensure, :path, :uuid, :brand, :iptype] properties = Hash[fields.zip(line.split(':'))] del_id = [:brand, :uuid] # Configured but not installed zones do not have IDs del_id << :id if properties[:id] == "-" del_id.each { |p| properties.delete(p) } properties[:ensure] = properties[:ensure].intern properties[:iptype] = 'exclusive' if properties[:iptype] == 'excl' properties end def self.instances adm(:list, "-cp").split("\n").collect do |line| new(line2hash(line)) end end def multi_conf(name, should, &action) has = properties[name] has = [] if !has || has == :absent rms = has - should adds = should - has (rms.map{|o| action.call(:rm,o)} + adds.map{|o| action.call(:add,o)}).join("\n") end def self.def_prop(var, str) define_method('%s_conf' % var.to_s) do |v| str % v end define_method('%s=' % var.to_s) do |v| setconfig self.send( ('%s_conf'% var).intern, v) end end def self.def_multiprop(var, &conf) define_method(var.to_s) do |v| o = properties[var] return '' if o.nil? or o == :absent o.join(' ') end define_method('%s=' % var.to_s) do |v| setconfig self.send( ('%s_conf'% var).intern, v) end define_method('%s_conf' % var.to_s) do |v| multi_conf(var, v, &conf) end end def_prop :iptype, "set ip-type=%s" def_prop :autoboot, "set autoboot=%s" def_prop :path, "set zonepath=%s" def_prop :pool, "set pool=%s" def_prop :shares, "add rctl\nset name=zone.cpu-shares\nadd value (priv=privileged,limit=%s,action=none)\nend" def_multiprop :ip do |action, str| interface, ip, defrouter = str.split(':') case action when :add cmd = ["add net"] cmd << "set physical=#{interface}" if interface cmd << "set address=#{ip}" if ip cmd << "set defrouter=#{defrouter}" if defrouter cmd << "end" cmd.join("\n") when :rm if ip "remove net address=#{ip}" elsif interface "remove net physical=#{interface}" else raise ArgumentError, _("can not remove network based on default router") end else self.fail action end end def_multiprop :dataset do |action, str| case action when :add; ['add dataset',"set name=#{str}",'end'].join("\n") when :rm; "remove dataset name=#{str}" else self.fail action end end def_multiprop :inherit do |action, str| case action when :add; ['add inherit-pkg-dir', "set dir=#{str}",'end'].join("\n") when :rm; "remove inherit-pkg-dir dir=#{str}" else self.fail action end end def my_properties [:path, :iptype, :autoboot, :pool, :shares, :ip, :dataset, :inherit] end # Perform all of our configuration steps. def configure self.fail "Path is required" unless @resource[:path] arr = ["create -b #{@resource[:create_args]}"] # Then perform all of our configuration steps. It's annoying # that we need this much internal info on the resource. self.resource.properties.each do |property| next unless my_properties.include? property.name method = (property.name.to_s + '_conf').intern arr << self.send(method ,@resource[property.name]) unless property.safe_insync?(properties[property.name]) end setconfig(arr.join("\n")) end def destroy zonecfg :delete, "-F" end def add_cmd(cmd) @cmds = [] if @cmds.nil? @cmds << cmd end def exists? properties[:ensure] != :absent end # We cannot use the execpipe in util because the pipe is not opened in # read/write mode. def exec_cmd(var) # In bash, the exit value of the last command is the exit value of the # entire pipeline out = execute("echo \"#{var[:input]}\" | #{var[:cmd]}", :failonfail => false, :combine => true) st = $?.exitstatus {:out => out, :exit => st} end # Clear out the cached values. def flush return if @cmds.nil? || @cmds.empty? str = (@cmds << "commit" << "exit").join("\n") @cmds = [] @property_hash.clear command = "#{command(:cfg)} -z #{@resource[:name]} -f -" r = exec_cmd(:cmd => command, :input => str) if r[:exit] != 0 or r[:out] =~ /not allowed/ raise ArgumentError, _("Failed to apply configuration") end end def install if @resource[:clone] # TODO: add support for "-s snapshot" zoneadm :clone, @resource[:clone] elsif @resource[:install_args] zoneadm :install, @resource[:install_args].split(" ") else zoneadm :install end end # Look up the current status. def properties if @property_hash.empty? @property_hash = status || {} if @property_hash.empty? @property_hash[:ensure] = :absent else @resource.class.validproperties.each do |name| @property_hash[name] ||= :absent end end end @property_hash.dup end # We need a way to test whether a zone is in process. Our 'ensure' # property models the static states, but we need to handle the temporary ones. def processing? hash = status return false unless hash ["incomplete", "ready", "shutting_down"].include? hash[:ensure] end # Collect the configuration of the zone. The output looks like: # zonename: z1 # zonepath: /export/z1 # brand: native # autoboot: true # bootargs: # pool: # limitpriv: # scheduling-class: # ip-type: shared # hostid: # net: # address: 192.168.1.1 # physical: eg0001 # defrouter not specified # net: # address: 192.168.1.3 # physical: eg0002 # defrouter not specified # def getconfig output = zonecfg :info name = nil current = nil hash = {} output.split("\n").each do |line| case line when /^(\S+):\s*$/ name = $1 current = nil # reset it when /^(\S+):\s*(\S+)$/ hash[$1.intern] = $2 when /^\s+(\S+):\s*(.+)$/ if name hash[name] ||= [] unless current current = {} hash[name] << current end current[$1.intern] = $2 else err "Ignoring '#{line}'" end else debug "Ignoring zone output '#{line}'" end end hash end # Execute a configuration string. Can't be private because it's called # by the properties. def setconfig(str) add_cmd str end def start # Check the sysidcfg stuff if cfg = @resource[:sysidcfg] self.fail "Path is required" unless @resource[:path] zoneetc = File.join(@resource[:path], "root", "etc") sysidcfg = File.join(zoneetc, "sysidcfg") # if the zone root isn't present "ready" the zone # which makes zoneadmd mount the zone root zoneadm :ready unless File.directory?(zoneetc) unless Puppet::FileSystem.exist?(sysidcfg) begin # For compatibility reasons use System encoding for this OS file # the manifest string is UTF-8 so this could result in conversion errors # which should propagate to users Puppet::FileSystem.open(sysidcfg, 0600, "w:#{Encoding.default_external.name}") do |f| f.puts cfg end rescue => detail puts detail.stacktrace if Puppet[:debug] raise Puppet::Error, "Could not create sysidcfg: #{detail}", detail.backtrace end end end zoneadm :boot end # Return a hash of the current status of this zone. def status begin output = adm "-z", @resource[:name], :list, "-p" rescue Puppet::ExecutionFailure return nil end main = self.class.line2hash(output.chomp) # Now add in the configuration information config_status.each do |name, value| main[name] = value end main end def ready zoneadm :ready end def stop zoneadm :halt end def unconfigure zonecfg :delete, "-F" end def uninstall zoneadm :uninstall, "-F" end private # Turn the results of getconfig into status information. def config_status config = getconfig result = {} result[:autoboot] = config[:autoboot] ? config[:autoboot].intern : :true result[:pool] = config[:pool] result[:shares] = config[:shares] if dir = config["inherit-pkg-dir"] result[:inherit] = dir.collect { |dirs| dirs[:dir] } end if datasets = config["dataset"] result[:dataset] = datasets.collect { |dataset| dataset[:name] } end result[:iptype] = config[:'ip-type'] if config[:'ip-type'] if net = config["net"] result[:ip] = net.collect do |params| if params[:defrouter] "#{params[:physical]}:#{params[:address]}:#{params[:defrouter]}" elsif params[:address] "#{params[:physical]}:#{params[:address]}" else params[:physical] end end end result end def zoneadm(*cmd) adm("-z", @resource[:name], *cmd) rescue Puppet::ExecutionFailure => detail self.fail Puppet::Error, "Could not #{cmd[0]} zone: #{detail}", detail end def zonecfg(*cmd) # You apparently can't get the configuration of the global zone (strictly in solaris11) return "" if self.name == "global" begin cfg("-z", self.name, *cmd) rescue Puppet::ExecutionFailure => detail self.fail Puppet::Error, "Could not #{cmd[0]} zone: #{detail}", detail end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/zpool/������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020077� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/provider/zpool/zpool.rb����������������������������������������������������0000644�0052762�0001160�00000006055�13417161721�021570� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.type(:zpool).provide(:zpool) do desc "Provider for zpool." commands :zpool => 'zpool' #NAME SIZE ALLOC FREE CAP HEALTH ALTROOT def self.instances zpool(:list, '-H').split("\n").collect do |line| name, _size, _alloc, _free, _cap, _health, _altroot = line.split(/\s+/) new({:name => name, :ensure => :present}) end end def process_zpool_data(pool_array) if pool_array == [] return Hash.new(:absent) end #get the name and get rid of it pool = Hash.new pool[:pool] = pool_array[0] pool_array.shift tmp = [] #order matters here :( pool_array.reverse_each do |value| sym = nil case value when "spares"; sym = :spare when "logs"; sym = :log when /^mirror|^raidz1|^raidz2/; sym = value =~ /^mirror/ ? :mirror : :raidz pool[:raid_parity] = "raidz2" if value =~ /^raidz2/ else tmp << value sym = :disk if value == pool_array.first end if sym pool[sym] = pool[sym] ? pool[sym].unshift(tmp.reverse.join(' ')) : [tmp.reverse.join(' ')] tmp.clear end end pool end def get_pool_data # https://docs.oracle.com/cd/E19082-01/817-2271/gbcve/index.html # we could also use zpool iostat -v mypool for a (little bit) cleaner output out = execute("zpool status #{@resource[:pool]}", :failonfail => false, :combine => false) zpool_data = out.lines.select { |line| line.index("\t") == 0 }.collect { |l| l.strip.split("\s")[0] } zpool_data.shift zpool_data end def current_pool @current_pool = process_zpool_data(get_pool_data) unless (defined?(@current_pool) and @current_pool) @current_pool end def flush @current_pool= nil end #Adds log and spare def build_named(name) if prop = @resource[name.intern] [name] + prop.collect { |p| p.split(' ') }.flatten else [] end end #query for parity and set the right string def raidzarity @resource[:raid_parity] ? @resource[:raid_parity] : "raidz1" end #handle mirror or raid def handle_multi_arrays(prefix, array) array.collect{ |a| [prefix] + a.split(' ') }.flatten end #builds up the vdevs for create command def build_vdevs if disk = @resource[:disk] disk.collect { |d| d.split(' ') }.flatten elsif mirror = @resource[:mirror] handle_multi_arrays("mirror", mirror) elsif raidz = @resource[:raidz] handle_multi_arrays(raidzarity, raidz) end end def create zpool(*([:create, @resource[:pool]] + build_vdevs + build_named("spare") + build_named("log"))) end def destroy zpool :destroy, @resource[:pool] end def exists? if current_pool[:pool] == :absent false else true end end [:disk, :mirror, :raidz, :log, :spare].each do |field| define_method(field) do current_pool[field] end define_method(field.to_s + "=") do |should| self.fail "zpool #{field} can't be changed. should be #{should}, currently is #{current_pool[field]}" end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/reference/�����������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�017040� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/reference/configuration.rb�������������������������������������������������0000644�0052762�0001160�00000010065�13417161721�022231� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������config = Puppet::Util::Reference.newreference(:configuration, :depth => 1, :doc => "A reference for all settings") do docs = {} Puppet.settings.each do |name, object| docs[name] = object end str = "" docs.sort { |a, b| a[0].to_s <=> b[0].to_s }.each do |name, object| # Make each name an anchor header = name.to_s str << markdown_header(header, 3) # Print the doc string itself begin str << Puppet::Util::Docs.scrub(object.desc) rescue => detail Puppet.log_exception(detail) end str << "\n\n" # Now print the data about the item. val = object.default if name.to_s == 'vardir' val = 'Unix/Linux: /opt/puppetlabs/puppet/cache -- Windows: C:\ProgramData\PuppetLabs\puppet\cache -- Non-root user: ~/.puppetlabs/opt/puppet/cache' elsif name.to_s == 'confdir' val = 'Unix/Linux: /etc/puppetlabs/puppet -- Windows: C:\ProgramData\PuppetLabs\puppet\etc -- Non-root user: ~/.puppetlabs/etc/puppet' elsif name.to_s == 'codedir' val = 'Unix/Linux: /etc/puppetlabs/code -- Windows: C:\ProgramData\PuppetLabs\code -- Non-root user: ~/.puppetlabs/etc/code' elsif name.to_s == 'rundir' val = 'Unix/Linux: /var/run/puppetlabs -- Windows: C:\ProgramData\PuppetLabs\puppet\var\run -- Non-root user: ~/.puppetlabs/var/run' elsif name.to_s == 'logdir' val = 'Unix/Linux: /var/log/puppetlabs/puppet -- Windows: C:\ProgramData\PuppetLabs\puppet\var\log -- Non-root user: ~/.puppetlabs/var/log' elsif name.to_s == 'hiera_config' val = '$confdir/hiera.yaml. However, if a file exists at $codedir/hiera.yaml, Puppet uses that instead.' elsif name.to_s == 'certname' val = "the Host's fully qualified domain name, as determined by facter" end # Leave out the section information; it was apparently confusing people. #str << "- **Section**: #{object.section}\n" unless val == "" str << "- *Default*: #{val}\n" end str << "\n" end return str end config.header = <<EOT ## Configuration settings * Each of these settings can be specified in `puppet.conf` or on the command line. * Puppet Enterprise (PE) and open source Puppet share the configuration settings that are documented here. However, PE defaults for some settings differ from the open source Puppet defaults. Some examples of settings that have different PE defaults include `disable18n`, `environment_timeout`, `always_retry_plugins`, and the Puppet Server JRuby `max-active-instances` setting. To verify PE configuration defaults, check the `puppet.conf` file after installation. * When using boolean settings on the command line, use `--setting` and `--no-setting` instead of `--setting (true|false)`. (Using `--setting false` results in "Error: Could not parse application options: needless argument".) * Settings can be interpolated as `$variables` in other settings; `$environment` is special, in that puppet master will interpolate each agent node's environment instead of its own. * Multiple values should be specified as comma-separated lists; multiple directories should be separated with the system path separator (usually a colon). * Settings that represent time intervals should be specified in duration format: an integer immediately followed by one of the units 'y' (years of 365 days), 'd' (days), 'h' (hours), 'm' (minutes), or 's' (seconds). The unit cannot be combined with other units, and defaults to seconds when omitted. Examples are '3600' which is equivalent to '1h' (one hour), and '1825d' which is equivalent to '5y' (5 years). * If you use the `splay` setting, note that the period that it waits changes each time the Puppet agent is restarted. * Settings that take a single file or directory can optionally set the owner, group, and mode for their value: `rundir = $vardir/run { owner = puppet, group = puppet, mode = 644 }` * The Puppet executables will ignore any setting that isn't relevant to their function. See the [configuration guide][confguide] for more details. [confguide]: https://puppet.com/docs/puppet/latest/config_about_settings.html * * * EOT ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/reference/function.rb������������������������������������������������������0000644�0052762�0001160�00000001302�13417161721�021201� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������function = Puppet::Util::Reference.newreference :function, :doc => "All functions available in the parser" do Puppet::Parser::Functions.functiondocs end function.header = " There are two types of functions in Puppet: Statements and rvalues. Statements stand on their own and do not return arguments; they are used for performing stand-alone work like importing. Rvalues return values and can only be used in a statement requiring a value, such as an assignment or a case statement. Functions execute on the Puppet master. They do not execute on the Puppet agent. Hence they only have access to the commands and data available on the Puppet master host. Here are the functions available in Puppet: " ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/reference/indirection.rb���������������������������������������������������0000644�0052762�0001160�00000007113�13417161721�021671� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector/indirection' require 'puppet/util/checksums' require 'puppet/file_serving/content' require 'puppet/file_serving/metadata' reference = Puppet::Util::Reference.newreference :indirection, :doc => "Indirection types and their terminus classes" do text = "" Puppet::Indirector::Indirection.instances.sort { |a,b| a.to_s <=> b.to_s }.each do |indirection| ind = Puppet::Indirector::Indirection.instance(indirection) name = indirection.to_s.capitalize text << "## " + indirection.to_s + "\n\n" text << Puppet::Util::Docs.scrub(ind.doc) + "\n\n" Puppet::Indirector::Terminus.terminus_classes(ind.name).sort { |a,b| a.to_s <=> b.to_s }.each do |terminus| terminus_name = terminus.to_s term_class = Puppet::Indirector::Terminus.terminus_class(ind.name, terminus) if term_class terminus_doc = Puppet::Util::Docs.scrub(term_class.doc) text << markdown_header("`#{terminus_name}` terminus", 3) << terminus_doc << "\n\n" else Puppet.warning _("Could not build docs for indirector %{name}, terminus %{terminus}: could not locate terminus.") % { name: name.inspect, terminus: terminus_name.inspect } end end end text end reference.header = <<HEADER ## About Indirection Puppet's indirector support pluggable backends (termini) for a variety of key-value stores (indirections). Each indirection type corresponds to a particular Ruby class (the "Indirected Class" below) and values are instances of that class. Each instance's key is available from its `name` method. The termini can be local (e.g., on-disk files) or remote (e.g., using a REST interface to talk to a puppet master). An indirector has five methods, which are mapped into HTTP verbs for the REST interface: * `find(key)` - get a single value (mapped to GET or POST with a singular endpoint) * `search(key)` - get a list of matching values (mapped to GET with a plural endpoint) * `head(key)` - return true if the key exists (mapped to HEAD) * `destroy(key)` - remove the key and value (mapped to DELETE) * `save(instance)` - write the instance to the store, using the instance's name as the key (mapped to PUT) These methods are available via the `indirection` class method on the indirected classes. For example: foo_cert = Puppet::SSL::Certificate.indirection.find('foo.example.com') At startup, each indirection is configured with a terminus. In most cases, this is the default terminus defined by the indirected class, but it can be overridden by the application or face, or overridden with the `route_file` configuration. The available termini differ for each indirection, and are listed below. Indirections can also have a cache, represented by a second terminus. This is a write-through cache: modifications are written both to the cache and to the primary terminus. Values fetched from the terminus are written to the cache. ### Interaction with REST REST endpoints have the form `/{prefix}/{version}/{indirection}/{key}?environment={environment}`, where the indirection can be singular or plural, following normal English spelling rules. On the server side, REST responses are generated from the locally-configured endpoints. ### Indirections and Termini Below is the list of all indirections, their associated terminus classes, and how you select between them. In general, the appropriate terminus class is selected by the application for you (e.g., `puppet agent` would always use the `rest` terminus for most of its indirected classes), but some classes are tunable via normal settings. These will have `terminus setting` documentation listed with them. HEADER �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/reference/metaparameter.rb�������������������������������������������������0000644�0052762�0001160�00000002001�13417161721�022200� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Util::Reference.newreference :metaparameter, :doc => "All Puppet metaparameters and all their details" do str = %{ Metaparameters are attributes that work with any resource type, including custom types and defined types. In general, they affect _Puppet's_ behavior rather than the desired state of the resource. Metaparameters do things like add metadata to a resource (`alias`, `tag`), set limits on when the resource should be synced (`require`, `schedule`, etc.), prevent Puppet from making changes (`noop`), and change logging verbosity (`loglevel`). ## Available Metaparameters } begin params = [] Puppet::Type.eachmetaparam { |param| params << param } params.sort { |a,b| a.to_s <=> b.to_s }.each { |param| str << markdown_header(param.to_s, 3) str << scrub(Puppet::Type.metaparamdoc(param)) str << "\n\n" } rescue => detail Puppet.log_exception(detail, _("incorrect metaparams: %{detail}") % { detail: detail }) exit(1) end str end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/reference/providers.rb�����������������������������������������������������0000644�0052762�0001160�00000007612�13417161721�021403� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������providers = Puppet::Util::Reference.newreference :providers, :title => "Provider Suitability Report", :depth => 1, :dynamic => true, :doc => "Which providers are valid for this machine" do types = [] Puppet::Type.loadall Puppet::Type.eachtype do |klass| next unless klass && klass.providers.length > 0 types << klass end types.sort! { |a,b| a.name.to_s <=> b.name.to_s } command_line = Puppet::Util::CommandLine.new types.reject! { |type| ! command_line.args.include?(type.name.to_s) } unless command_line.args.empty? ret = "Details about this host:\n\n" # Throw some facts in there, so we know where the report is from. ["Ruby Version", "Puppet Version", "Operating System", "Operating System Release"].each do |label| name = label.gsub(/\s+/, '') value = Facter.value(name) ret << option(label, value) end ret << "\n" count = 1 # Produce output for each type. types.each do |type| features = type.features ret << "\n" # add a trailing newline # Now build up a table of provider suitability. headers = %w{Provider Suitable?} + features.collect { |f| f.to_s }.sort table_data = {} functional = false notes = [] default = type.defaultprovider ? type.defaultprovider.name : 'none' type.providers.sort { |a,b| a.to_s <=> b.to_s }.each do |pname| data = [] table_data[pname] = data provider = type.provider(pname) # Add the suitability note if missing = provider.suitable?(false) and missing.empty? data << "*X*" suit = true functional = true else data << "[#{count}]_" # A pointer to the appropriate footnote suit = false end # Add a footnote with the details about why this provider is unsuitable, if that's the case unless suit details = ".. [#{count}]\n" missing.each do |test, values| case test when :exists details << _(" - Missing files %{files}\n") % { files: values.join(", ") } when :variable values.each do |name, facts| if Puppet.settings.valid?(name) details << _(" - Setting %{name} (currently %{value}) not in list %{facts}\n") % { name: name, value: Puppet.settings.value(name).inspect, facts: facts.join(", ") } else details << _(" - Fact %{name} (currently %{value}) not in list %{facts}\n") % { name: name, value: Facter.value(name).inspect, facts: facts.join(", ") } end end when :true details << _(" - Got %{values} true tests that should have been false\n") % { values: values } when :false details << _(" - Got %{values} false tests that should have been true\n") % { values: values } when :feature details << _(" - Missing features %{values}\n") % { values: values.collect { |f| f.to_s }.join(",") } end end notes << details count += 1 end # Add a note for every feature features.each do |feature| if provider.features.include?(feature) data << "*X*" else data << "" end end end ret << markdown_header(type.name.to_s + "_", 2) ret << "[#{type.name}](https://puppet.com/docs/puppet/latest/type.html##{type.name})\n\n" ret << option("Default provider", default) ret << doctable(headers, table_data) notes.each do |note| ret << note + "\n" end ret << "\n" end ret << "\n" ret end providers.header = " Puppet resource types are usually backed by multiple implementations called `providers`, which handle variance between platforms and tools. Different providers are suitable or unsuitable on different platforms based on things like the presence of a given tool. Here are all of the provider-backed types and their different providers. Any unmentioned types do not use providers yet. " ����������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/reference/report.rb��������������������������������������������������������0000644�0052762�0001160�00000001344�13417161721�020675� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/reports' report = Puppet::Util::Reference.newreference :report, :doc => "All available transaction reports" do Puppet::Reports.reportdocs end report.header = " Puppet can generate a report after applying a catalog. This report includes events, log messages, resource statuses, and metrics and metadata about the run. Puppet agent sends its report to a Puppet master server, and Puppet apply processes its own reports. Puppet master and Puppet apply will handle every report with a set of report processors, configurable with the `reports` setting in puppet.conf. This page documents the built-in report processors. See [About Reporting](https://puppet.com/docs/puppet/latest/reporting_about.html) for more details. " ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/reference/type.rb����������������������������������������������������������0000644�0052762�0001160�00000006515�13417161721�020350� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Util::Reference.newreference :type, :doc => "All Puppet resource types and all their details" do types = {} Puppet::Type.loadall Puppet::Type.eachtype { |type| next if type.name == :component next if type.name == :whit types[type.name] = type } str = %{ ## Resource Types - The *namevar* is the parameter used to uniquely identify a type instance. This is the parameter that gets assigned when a string is provided before the colon in a type declaration. In general, only developers will need to worry about which parameter is the `namevar`. In the following code: file { "/etc/passwd": owner => "root", group => "root", mode => "0644" } `/etc/passwd` is considered the title of the file object (used for things like dependency handling), and because `path` is the namevar for `file`, that string is assigned to the `path` parameter. - *Parameters* determine the specific configuration of the instance. They either directly modify the system (internally, these are called properties) or they affect how the instance behaves (e.g., adding a search path for `exec` instances or determining recursion on `file` instances). - *Providers* provide low-level functionality for a given resource type. This is usually in the form of calling out to external commands. When required binaries are specified for providers, fully qualified paths indicate that the binary must exist at that specific path and unqualified binaries indicate that Puppet will search for the binary using the shell path. - *Features* are abilities that some providers might not support. You can use the list of supported features to determine how a given provider can be used. Resource types define features they can use, and providers can be tested to see which features they provide. } types.sort { |a,b| a.to_s <=> b.to_s }.each { |name,type| str << " ---------------- " str << markdown_header(name, 3) str << scrub(type.doc) + "\n\n" # Handle the feature docs. if featuredocs = type.featuredocs str << markdown_header("Features", 4) str << featuredocs end docs = {} type.validproperties.sort { |a,b| a.to_s <=> b.to_s }.reject { |sname| property = type.propertybyname(sname) property.nodoc }.each { |sname| property = type.propertybyname(sname) raise _("Could not retrieve property %{sname} on type %{type_name}") % { sname: sname, type_name: type.name } unless property unless doc = property.doc $stderr.puts _("No docs for %{type}[%{sname}]") % { type: type, sname: sname } next end doc = doc.dup tmp = doc tmp = scrub(tmp) docs[sname] = tmp } str << markdown_header("Parameters", 4) + "\n" type.parameters.sort { |a,b| a.to_s <=> b.to_s }.each { |type_name, param| docs[type_name] = scrub(type.paramdoc(type_name)) } additional_key_attributes = type.key_attributes - [:name] docs.sort { |a, b| a[0].to_s <=> b[0].to_s }.each { |type_name, doc| if additional_key_attributes.include?(type_name) doc = "(**Namevar:** If omitted, this parameter's value defaults to the resource's title.)\n\n" + doc end str << markdown_definitionlist(type_name, doc) } str << "\n" } str end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/relationship.rb������������������������������������������������������������0000644�0052762�0001160�00000004073�13417161721�020127� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# subscriptions are permanent associations determining how different # objects react to an event # This is Puppet's class for modeling edges in its configuration graph. # It used to be a subclass of GRATR::Edge, but that class has weird hash # overrides that dramatically slow down the graphing. class Puppet::Relationship # FormatSupport for serialization methods include Puppet::Network::FormatSupport include Puppet::Util::PsychSupport attr_accessor :source, :target, :callback attr_reader :event def self.from_data_hash(data) source = data['source'] target = data['target'] args = {} if event = data["event"] args[:event] = :"#{event}" end if callback = data["callback"] args[:callback] = :"#{callback}" end new(source, target, args) end def event=(event) #TRANSLATORS 'NONE' should not be translated raise ArgumentError, _("You must pass a callback for non-NONE events") if event != :NONE and ! callback @event = event end def initialize(source, target, options = {}) @source, @target = source, target options = (options || {}).inject({}) { |h,a| h[a[0].to_sym] = a[1]; h } [:callback, :event].each do |option| if value = options[option] send(option.to_s + "=", value) end end end # Does the passed event match our event? This is where the meaning # of :NONE comes from. def match?(event) if self.event.nil? or event == :NONE or self.event == :NONE return false elsif self.event == :ALL_EVENTS or event == self.event return true else return false end end def label result = {} result[:callback] = callback if callback result[:event] = event if event result end def ref "#{source} => #{target}" end def inspect "{ #{source} => #{target} }" end def to_data_hash data = { 'source' => source.to_s, 'target' => target.to_s } data['event'] = event.to_s unless event.nil? data['callback'] = callback.to_s unless callback.nil? data end def to_s ref end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/reports/�������������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�016600� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/reports/http.rb������������������������������������������������������������0000644�0052762�0001160�00000003001�13417161721�020071� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet' require 'puppet/network/http_pool' require 'uri' Puppet::Reports.register_report(:http) do desc <<-DESC Send reports via HTTP or HTTPS. This report processor submits reports as POST requests to the address in the `reporturl` setting. When a HTTPS URL is used, the remote server must present a certificate issued by the Puppet CA or the connection will fail validation. The body of each POST request is the YAML dump of a Puppet::Transaction::Report object, and the Content-Type is set as `application/x-yaml`. DESC def process url = URI.parse(Puppet[:reporturl]) headers = { "Content-Type" => "application/x-yaml" } # This metric_id option is silently ignored by Puppet's http client # (Puppet::Network::HTTP) but is used by Puppet Server's http client # (Puppet::Server::HttpClient) to track metrics on the request made to the # `reporturl` to store a report. options = { :metric_id => [:puppet, :report, :http] } if url.user && url.password options[:basic_auth] = { :user => url.user, :password => url.password } end use_ssl = url.scheme == 'https' conn = Puppet::Network::HttpPool.http_instance(url.host, url.port, use_ssl) response = conn.post(url.path, self.to_yaml, headers, options) unless response.kind_of?(Net::HTTPSuccess) Puppet.err _("Unable to submit report to %{url} [%{code}] %{message}") % { url: Puppet[:reporturl].to_s, code: response.code, message: response.msg } end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/reports/log.rb�������������������������������������������������������������0000644�0052762�0001160�00000000506�13417161721�017702� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/reports' Puppet::Reports.register_report(:log) do desc "Send all received logs to the local log destinations. Usually the log destination is syslog." def process self.logs.each do |log| log.source = "//#{self.host}/#{log.source}" Puppet::Util::Log.newmessage(log) end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/reports/store.rb�����������������������������������������������������������0000644�0052762�0001160�00000003562�13417161721�020262� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet' require 'fileutils' require 'puppet/util' SEPARATOR = [Regexp.escape(File::SEPARATOR.to_s), Regexp.escape(File::ALT_SEPARATOR.to_s)].join Puppet::Reports.register_report(:store) do desc "Store the yaml report on disk. Each host sends its report as a YAML dump and this just stores the file on disk, in the `reportdir` directory. These files collect quickly -- one every half hour -- so it is a good idea to perform some maintenance on them if you use this report (it's the only default report)." def process validate_host(host) dir = File.join(Puppet[:reportdir], host) if ! Puppet::FileSystem.exist?(dir) FileUtils.mkdir_p(dir) FileUtils.chmod_R(0750, dir) end # Now store the report. now = Time.now.gmtime name = %w{year month day hour min}.collect do |method| # Make sure we're at least two digits everywhere "%02d" % now.send(method).to_s end.join("") + ".yaml" file = File.join(dir, name) begin Puppet::Util.replace_file(file, 0640) do |fh| fh.print to_yaml end rescue => detail Puppet.log_exception(detail, "Could not write report for #{host} at #{file}: #{detail}") end # Only testing cares about the return value file end # removes all reports for a given host? def self.destroy(host) validate_host(host) dir = File.join(Puppet[:reportdir], host) if Puppet::FileSystem.exist?(dir) Dir.entries(dir).each do |file| next if ['.','..'].include?(file) file = File.join(dir, file) Puppet::FileSystem.unlink(file) if File.file?(file) end Dir.rmdir(dir) end end def validate_host(host) if host =~ Regexp.union(/[#{SEPARATOR}]/, /\A\.\.?\Z/) raise ArgumentError, _("Invalid node name %{host}") % { host: host.inspect } end end module_function :validate_host end ����������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/resource/������������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�016731� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/resource/capability_finder.rb����������������������������������������������0000644�0052762�0001160�00000013316�13417161721�022725� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# # A helper module to look a capability up from PuppetDB # # @todo lutter 2015-03-10: determine whether this should be based on # Puppet::Pops::Evaluator::Collectors, or at least use # Puppet::Util::Puppetdb::Http require 'net/http' require 'cgi' require 'puppet/util/json' # @api private module Puppet::Resource::CapabilityFinder # Looks up a capability resource from PuppetDB. Capability resources are # required to be unique per environment and code id. If multiple copies of a # capability resource are found, the one matching the current code id is # used. # # @param environment [String] environment name # @param code_id [String,nil] code_id of the catalog # @param cap [Puppet::Resource] the capability resource type instance # @return [Puppet::Resource,nil] The found capability resource or `nil` if it could not be found def self.find(environment, code_id, cap) unless Puppet::Util.const_defined?('Puppetdb') #TRANSLATOR PuppetDB is a product name and should not be translated raise Puppet::DevError, _('PuppetDB is not available') end resources = search(nil, nil, cap) if resources.size > 1 Puppet.debug "Found multiple resources when looking up capability #{cap}, filtering by environment #{environment}" resources = resources.select { |r| r['tags'].any? { |t| t == "producer:#{environment}" } } end if resources.empty? Puppet.debug "Could not find capability resource #{cap} in PuppetDB" elsif resources.size == 1 resource_hash = resources.first elsif code_id_resource = disambiguate_by_code_id(environment, code_id, cap) resource_hash = code_id_resource else #TRANSLATOR PuppetDB is a product name and should not be translated message = _("Unexpected response from PuppetDB when looking up %{capability}:") % { capability: cap } message += "\n" + _("expected exactly one resource but got %{count};") % { count: resources.size } message += "\n" + _("returned data is:\n%{resources}") % { resources: resources.inspect } raise Puppet::DevError, message end if resource_hash resource_hash['type'] = cap.resource_type instantiate_resource(resource_hash) end end def self.search(environment, code_id, cap) query_terms = [ 'and', ['=', 'type', cap.type.capitalize], ['=', 'title', cap.title.to_s], ] if environment.nil? query_terms << ['~', 'tag', "^producer:"] else query_terms << ['=', 'tag', "producer:#{environment}"] end unless code_id.nil? query_terms << ['in', 'certname', ['extract', 'certname', ['select_catalogs', ['=', 'code_id', code_id]]]] end #TRANSLATOR PuppetDB is a product name and should not be translated Puppet.notice _("Looking up capability %{capability} in PuppetDB: %{query_terms}") % { capability: cap, query_terms: query_terms } query_puppetdb(query_terms) end def self.query_puppetdb(query) begin # If using PuppetDB >= 4, use the API method query_puppetdb() result = if Puppet::Util::Puppetdb.respond_to?(:query_puppetdb) # PuppetDB 4 uses a unified query endpoint, so we have to specify what we're querying Puppet::Util::Puppetdb.query_puppetdb(["from", "resources", query]) # For PuppetDB < 4, use the old internal method action() else url = "/pdb/query/v4/resource?query=#{Puppet::Util.uri_query_encode(query.to_json)}" response = Puppet::Util::Puppetdb::Http.action(url) do |conn, uri| conn.get(uri, { 'Accept' => 'application/json'}) end Puppet::Util::Json.load(response.body) end # The format of the response body is documented at # https://puppet.com/docs/puppetdb/3.0/api/query/v4/resources.html#response-format unless result.is_a?(Array) #TRANSLATOR PuppetDB is a product name and should not be translated raise Puppet::DevError, _("Unexpected response from PuppetDB when looking up %{capability}: expected an Array but got %{result}") % { capability: cap, result: result.inspect } end result rescue Puppet::Util::Json::ParseError => e #TRANSLATOR PuppetDB is a product name and should not be translated raise Puppet::DevError, _("Invalid JSON from PuppetDB when looking up %{capability}\n%{detail}") % { capability: cap, detail: e } end end # Find a distinct copy of the given capability resource by searching for only # resources matching the given code_id. Returns `nil` if no code_id is # supplied or if there isn't exactly one matching resource. # # @param environment [String] environment name # @param code_id [String,nil] code_id of the catalog # @param cap [Puppet::Resource] the capability resource type instance def self.disambiguate_by_code_id(environment, code_id, cap) if code_id Puppet.debug "Found multiple resources when looking up capability #{cap}, filtering by code id #{code_id}" resources = search(environment, code_id, cap) if resources.size > 1 Puppet.debug "Found multiple resources matching code id #{code_id} when looking up #{cap}" nil else resources.first end end end private_class_method :disambiguate_by_code_id def self.instantiate_resource(resource_hash) real_type = resource_hash['type'] resource = Puppet::Resource.new(real_type, resource_hash['title']) real_type.parameters.each do |param| param = param.to_s next if param == 'name' if value = resource_hash['parameters'][param] resource[param] = value else Puppet.debug "No capability value for #{resource}->#{param}" end end return resource end private_class_method :instantiate_resource end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/resource/status.rb���������������������������������������������������������0000644�0052762�0001160�00000017753�13417161721�020611� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'time' require 'puppet/network/format_support' require 'puppet/util/psych_support' module Puppet class Resource # This class represents the result of evaluating a given resource. It # contains file and line information about the source, events generated # while evaluating the resource, timing information, and the status of the # resource evaluation. # # @api private class Status include Puppet::Util::PsychSupport include Puppet::Util::Tagging include Puppet::Network::FormatSupport # @!attribute [rw] file # @return [String] The file where `@real_resource` was defined. attr_accessor :file # @!attribute [rw] line # @return [Integer] The line number in the file where `@real_resource` was defined. attr_accessor :line # @!attribute [rw] evaluation_time # @return [Float] The time elapsed in sections while evaluating `@real_resource`. # measured in seconds. attr_accessor :evaluation_time # Boolean status types set while evaluating `@real_resource`. STATES = [:skipped, :failed, :failed_to_restart, :restarted, :changed, :out_of_sync, :scheduled, :corrective_change] attr_accessor(*STATES) # @!attribute [r] source_description # @return [String] The textual description of the path to `@real_resource` # based on the containing structures. This is in contrast to # `@containment_path` which is a list of containment path components. # @example # status.source_description #=> "/Stage[main]/Myclass/Exec[date]" attr_reader :source_description # @!attribute [r] containment_path # @return [Array<String>] A list of resource references that contain # `@real_resource`. # @example A normal contained type # status.containment_path #=> ["Stage[main]", "Myclass", "Exec[date]"] # @example A whit associated with a class # status.containment_path #=> ["Whit[Admissible_class[Main]]"] attr_reader :containment_path # @!attribute [r] time # @return [Time] The time that this status object was created attr_reader :time # @!attribute [r] resource # @return [String] The resource reference for `@real_resource` attr_reader :resource # @!attribute [r] change_count # @return [Integer] A count of the successful changes made while # evaluating `@real_resource`. attr_reader :change_count # @!attribute [r] out_of_sync_count # @return [Integer] A count of the audited changes made while # evaluating `@real_resource`. attr_reader :out_of_sync_count # @!attribute [r] resource_type # @example # status.resource_type #=> 'Notify' # @return [String] The class name of `@real_resource` attr_reader :resource_type # @!attribute [rw] provider_used # @return [String] The class name of the provider used for the resource attr_accessor :provider_used # @!attribute [r] title # @return [String] The title of `@real_resource` attr_reader :title # @!attribute [r] events # @return [Array<Puppet::Transaction::Event>] A list of events generated # while evaluating `@real_resource`. attr_reader :events # @!attribute [r] corrective_change # @return [Boolean] true if the resource contained a corrective change. attr_reader :corrective_change # @!attribute [rw] failed_dependencies # @return [Array<Puppet::Resource>] A cache of all # dependencies of this resource that failed to apply. attr_accessor :failed_dependencies def dependency_failed? failed_dependencies && !failed_dependencies.empty? end def self.from_data_hash(data) obj = self.allocate obj.initialize_from_hash(data) obj end # Provide a boolean method for each of the states. STATES.each do |attr| define_method("#{attr}?") do !! send(attr) end end def <<(event) add_event(event) self end def add_event(event) @events << event if event.status == 'failure' self.failed = true elsif event.status == 'success' @change_count += 1 @changed = true end if event.status != 'audit' @out_of_sync_count += 1 @out_of_sync = true end if event.corrective_change @corrective_change = true end end def failed_because(detail) @real_resource.log_exception(detail, _("Could not evaluate: %{detail}") % { detail: detail }) # There's a contract (implicit unfortunately) that a status of failed # will always be accompanied by an event with some explanatory power. This # is useful for reporting/diagnostics/etc. So synthesize an event here # with the exception detail as the message. fail_with_event(detail.to_s) end # Both set the status state to failed and generate a corresponding # Puppet::Transaction::Event failure with the given message. # @param message [String] the reason for a status failure def fail_with_event(message) add_event(@real_resource.event(:name => :resource_error, :status => "failure", :message => message)) end def initialize(resource) @real_resource = resource @source_description = resource.path @containment_path = resource.pathbuilder @resource = resource.to_s @change_count = 0 @out_of_sync_count = 0 @changed = false @out_of_sync = false @skipped = false @failed = false @corrective_change = false @file = resource.file @line = resource.line merge_tags_from(resource) @time = Time.now @events = [] @resource_type = resource.type.to_s.capitalize @provider_used = resource.provider.class.name.to_s unless resource.provider.nil? @title = resource.title end def initialize_from_hash(data) @resource_type = data['resource_type'] @provider_used = data['provider_used'] @title = data['title'] @resource = data['resource'] @containment_path = data['containment_path'] @file = data['file'] @line = data['line'] @evaluation_time = data['evaluation_time'] @change_count = data['change_count'] @out_of_sync_count = data['out_of_sync_count'] @tags = Puppet::Util::TagSet.new(data['tags']) @time = data['time'] @time = Time.parse(@time) if @time.is_a? String @out_of_sync = data['out_of_sync'] @changed = data['changed'] @skipped = data['skipped'] @failed = data['failed'] @failed_to_restart = data['failed_to_restart'] @corrective_change = data['corrective_change'] @events = data['events'].map do |event| # Older versions contain tags that causes Psych to create instances directly event.is_a?(Puppet::Transaction::Event) ? event : Puppet::Transaction::Event.from_data_hash(event) end end def to_data_hash { 'title' => @title, 'file' => @file, 'line' => @line, 'resource' => @resource, 'resource_type' => @resource_type, 'provider_used' => @provider_used, 'containment_path' => @containment_path, 'evaluation_time' => @evaluation_time, 'tags' => @tags.to_a, 'time' => @time.iso8601(9), 'failed' => @failed, 'failed_to_restart' => self.failed_to_restart?, 'changed' => @changed, 'out_of_sync' => @out_of_sync, 'skipped' => @skipped, 'change_count' => @change_count, 'out_of_sync_count' => @out_of_sync_count, 'events' => @events.map { |event| event.to_data_hash }, 'corrective_change' => @corrective_change, } end end end end ���������������������puppet-5.5.10/lib/puppet/resource/type.rb�����������������������������������������������������������0000644�0052762�0001160�00000046005�13417161721�020237� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parser' require 'puppet/util/warnings' require 'puppet/util/errors' require 'puppet/parser/ast/leaf' # Puppet::Resource::Type represents nodes, classes and defined types. # # @api public class Puppet::Resource::Type Puppet::ResourceType = self include Puppet::Util::Warnings include Puppet::Util::Errors RESOURCE_KINDS = [:hostclass, :node, :definition, :capability_mapping, :application, :site] # Map the names used in our documentation to the names used internally RESOURCE_KINDS_TO_EXTERNAL_NAMES = { :hostclass => "class", :node => "node", :definition => "defined_type", :application => "application", :site => 'site' } RESOURCE_EXTERNAL_NAMES_TO_KINDS = RESOURCE_KINDS_TO_EXTERNAL_NAMES.invert NAME = 'name'.freeze TITLE = 'title'.freeze MODULE_NAME = 'module_name'.freeze CALLER_MODULE_NAME = 'caller_module_name'.freeze PARAMETERS = 'parameters'.freeze KIND = 'kind'.freeze NODES = 'nodes'.freeze DOUBLE_COLON = '::'.freeze EMPTY_ARRAY = [].freeze attr_accessor :file, :line, :doc, :code, :parent, :resource_type_collection attr_reader :namespace, :arguments, :behaves_like, :module_name # The attributes 'produces' and 'consumes' are arrays of the blueprints # of capabilities this type can produce/consume. The entries in the array # are a fairly direct representation of what goes into produces/consumes # clauses. Each entry is a hash with attributes # :capability - the type name of the capres produced/consumed # :mappings - a hash of attribute_name => Expression # These two attributes are populated in # PopsBridge::instantiate_CapabilityMapping # Map from argument (aka parameter) names to Puppet Type # @return [Hash<Symbol, Puppet::Pops::Types::PAnyType] map from name to type # attr_reader :argument_types # This should probably be renamed to 'kind' eventually, in accordance with the changes # made for serialization and API usability (#14137). At the moment that seems like # it would touch a whole lot of places in the code, though. --cprice 2012-04-23 attr_reader :type RESOURCE_KINDS.each do |t| define_method("#{t}?") { self.type == t } end # Are we a child of the passed class? Do a recursive search up our # parentage tree to figure it out. def child_of?(klass) return false unless parent return(klass == parent_type ? true : parent_type.child_of?(klass)) end # Evaluate the resources produced by the given resource. These resources are # evaluated in a separate but identical scope from the rest of the resource. def evaluate_produces(resource, scope) # Only defined types and classes can produce capabilities return unless definition? || hostclass? resource.export.map do |ex| # Assert that the ref really is a resource reference raise Puppet::Error, _("Invalid export in %{reference}: %{ex} is not a resource") % { reference: resource.ref, ex: ex } unless ex.is_a?(Puppet::Resource) raise Puppet::Error, _("Invalid export in %{reference}: %{ex} is not a capability resource") % { reference: resource.ref, ex: ex } if ex.resource_type.nil? || !ex.resource_type.is_capability? blueprint = produces.find { |pr| pr[:capability] == ex.type } if blueprint.nil? raise Puppet::ParseError, _("Resource type %{res_type} does not produce %{ex_type}") % { res_type: resource.type, ex_type: ex.type } end t = ex.type t = Puppet::Pops::Evaluator::Runtime3ResourceSupport.find_resource_type(scope, t) unless t == 'class' || t == 'node' produced_resource = Puppet::Parser::Resource.new(t, ex.title, :scope => scope, :source => self) produced_resource.resource_type.parameters.each do |name| next if name == :name if expr = blueprint[:mappings][name.to_s] produced_resource[name] = expr.safeevaluate(scope) else produced_resource[name] = scope[name.to_s] end end # Tag the produced resource so we can later distinguish it from # copies of the resource that wind up in the catalogs of nodes that # use this resource. We tag the resource with producer:<environment>, # meaning produced resources need to be unique within their # environment # @todo lutter 2014-11-13: we would really like to use a dedicated # metadata field to indicate the producer of a resource, but that # requires changes to PuppetDB and its API; so for now, we just use # tagging produced_resource.tag("producer:#{scope.catalog.environment}") scope.catalog.add_resource(produced_resource) produced_resource[:require] = resource.ref produced_resource end end # Now evaluate the code associated with this class or definition. def evaluate_code(resource) static_parent = evaluate_parent_type(resource) scope = static_parent || resource.scope scope = scope.newscope(:source => self, :resource => resource) unless resource.title == :main scope.compiler.add_class(name) unless definition? set_resource_parameters(resource, scope) resource.add_edge_to_stage evaluate_produces(resource, scope) if code if @match # Only bother setting up the ephemeral scope if there are match variables to add into it scope.with_guarded_scope do scope.ephemeral_from(@match, file, line) code.safeevaluate(scope) end else code.safeevaluate(scope) end end end def initialize(type, name, options = {}) @type = type.to_s.downcase.to_sym raise ArgumentError, _("Invalid resource supertype '%{type}'") % { type: type } unless RESOURCE_KINDS.include?(@type) name = convert_from_ast(name) if name.is_a?(Puppet::Parser::AST::HostName) set_name_and_namespace(name) [:code, :doc, :line, :file, :parent].each do |param| next unless value = options[param] send(param.to_s + '=', value) end set_arguments(options[:arguments]) set_argument_types(options[:argument_types]) @match = nil @module_name = options[:module_name] end def produces @produces || EMPTY_ARRAY end def consumes @consumes || EMPTY_ARRAY end def add_produces(blueprint) @produces ||= [] @produces << blueprint end def add_consumes(blueprint) @consumes ||= [] @consumes << blueprint end # This is only used for node names, and really only when the node name # is a regexp. def match(string) return string.to_s.downcase == name unless name_is_regex? @match = @name.match(string) end # Add code from a new instance to our code. def merge(other) fail _("%{name} is not a class; cannot add code to it") % { name: name } unless type == :hostclass fail _("%{name} is not a class; cannot add code from it") % { name: other.name } unless other.type == :hostclass if name == "" && Puppet.settings[:freeze_main] # It is ok to merge definitions into main even if freeze is on (definitions are nodes, classes, defines, functions, and types) unless other.code.is_definitions_only? fail _("Cannot have code outside of a class/node/define because 'freeze_main' is enabled") end end if parent and other.parent and parent != other.parent fail _("Cannot merge classes with different parent classes (%{name} => %{parent} vs. %{other_name} => %{other_parent})") % { name: name, parent: parent, other_name: other.name, other_parent: other.parent } end # We know they're either equal or only one is set, so keep whichever parent is specified. self.parent ||= other.parent if other.doc self.doc ||= "" self.doc += other.doc end # This might just be an empty, stub class. return unless other.code unless self.code self.code = other.code return end self.code = Puppet::Parser::ParserFactory.code_merger.concatenate([self, other]) end # Make an instance of the resource type, and place it in the catalog # if it isn't in the catalog already. This is only possible for # classes and nodes. No parameters are be supplied--if this is a # parameterized class, then all parameters take on their default # values. def ensure_in_catalog(scope, parameters=nil) resource_type = case type when :definition raise ArgumentError, _('Cannot create resources for defined resource types') when :hostclass :class when :node :node when :site :site end # Do nothing if the resource already exists; this makes sure we don't # get multiple copies of the class resource, which helps provide the # singleton nature of classes. # we should not do this for classes with parameters # if parameters are passed, we should still try to create the resource # even if it exists so that we can fail # this prevents us from being able to combine param classes with include if parameters.nil? resource = scope.catalog.resource(resource_type, name) return resource unless resource.nil? elsif parameters.is_a?(Hash) parameters = parameters.map {|k, v| Puppet::Parser::Resource::Param.new(:name => k, :value => v, :source => self)} end resource = Puppet::Parser::Resource.new(resource_type, name, :scope => scope, :source => self, :parameters => parameters) instantiate_resource(scope, resource) scope.compiler.add_resource(scope, resource) resource end def instantiate_resource(scope, resource) # Make sure our parent class has been evaluated, if we have one. if parent && !scope.catalog.resource(resource.type, parent) parent_type(scope).ensure_in_catalog(scope) end if ['Class', 'Node'].include? resource.type scope.catalog.merge_tags_from(resource) end end def name if type == :node && name_is_regex? "__node_regexp__#{@name.source.downcase.gsub(/[^-\w:.]/,'').sub(/^\.+/,'')}" else @name end end def name_is_regex? @name.is_a?(Regexp) end # @deprecated Not used by Puppet # @api private def assign_parameter_values(parameters, resource) Puppet.deprecation_warning(_('The method Puppet::Resource::Type.assign_parameter_values is deprecated and will be removed in the next major release of Puppet.')) return unless parameters # It'd be nice to assign default parameter values here, # but we can't because they often rely on local variables # created during set_resource_parameters. parameters.each do |name, value| resource.set_parameter name, value end end def parent_type(scope = nil) return nil unless parent @parent_type ||= scope.environment.known_resource_types.send("find_#{type}", parent) || fail(Puppet::ParseError, _("Could not find parent resource type '%{parent}' of type %{parent_type} in %{env}") % { parent: parent, parent_type: type, env: scope.environment }) end # Validate and set any arguments passed by the resource as variables in the scope. # # This method is known to only be used on the server/compile side. # # @param resource [Puppet::Parser::Resource] the resource # @param scope [Puppet::Parser::Scope] the scope # # @api private def set_resource_parameters(resource, scope) # Inject parameters from using external lookup modname = resource[:module_name] || module_name scope[MODULE_NAME] = modname unless modname.nil? caller_name = resource[:caller_module_name] || scope.parent_module_name scope[CALLER_MODULE_NAME] = caller_name unless caller_name.nil? resource.add_parameters_from_consume inject_external_parameters(resource, scope) if @type == :hostclass scope[TITLE] = resource.title.to_s.downcase scope[NAME] = resource.name.to_s.downcase else scope[TITLE] = resource.title scope[NAME] = resource.name end scope.class_set(self.name,scope) if hostclass? || node? param_hash = scope.with_parameter_scope(resource.to_s, arguments.keys) do |param_scope| # Assign directly to the parameter scope to avoid scope parameter validation at this point. It # will happen anyway when the values are assigned to the scope after the parameter scoped has # been popped. resource.each { |k, v| param_scope[k.to_s] = v.value unless k == :name || k == :title } assign_defaults(resource, param_scope, scope) param_scope.to_hash end validate_resource_hash(resource, param_hash) # Assign parameter values to current scope param_hash.each { |param, value| exceptwrap { scope[param] = value }} end # Lookup and inject parameters from external scope # @param resource [Puppet::Parser::Resource] the resource # @param scope [Puppet::Parser::Scope] the scope def inject_external_parameters(resource, scope) # Only lookup parameters for host classes return unless type == :hostclass parameters = resource.parameters arguments.each do |param_name, default| sym_name = param_name.to_sym param = parameters[sym_name] next unless param.nil? || param.value.nil? catch(:no_such_key) do bound_value = Puppet::Pops::Lookup.search_and_merge("#{name}::#{param_name}", Puppet::Pops::Lookup::Invocation.new(scope), nil) # Assign bound value but don't let an undef trump a default expression resource[sym_name] = bound_value unless bound_value.nil? && !default.nil? end end end private :inject_external_parameters def assign_defaults(resource, param_scope, scope) return unless resource.is_a?(Puppet::Parser::Resource) parameters = resource.parameters arguments.each do |param_name, default| next if default.nil? name = param_name.to_sym param = parameters[name] next unless param.nil? || param.value.nil? value = exceptwrap { param_scope.evaluate3x(param_name, default, scope) } resource[name] = value param_scope[param_name] = value end end private :assign_defaults def validate_resource_hash(resource, resource_hash) Puppet::Pops::Types::TypeMismatchDescriber.validate_parameters(resource.to_s, parameter_struct, resource_hash, resource.is_unevaluated_consumer?) end private :validate_resource_hash # Validate that all parameters given to the resource are correct # @param resource [Puppet::Resource] the resource to validate def validate_resource(resource) # Since Sensitive values have special encoding (in a separate parameter) an unwrapped sensitive value must be # recreated as a Sensitive in order to perform correct type checking. sensitives = Set.new(resource.sensitive_parameters) validate_resource_hash(resource, Hash[resource.parameters.map do |name, value| value_to_validate = sensitives.include?(name) ? Puppet::Pops::Types::PSensitiveType::Sensitive.new(value.value) : value.value [name.to_s, value_to_validate] end ]) end # Check whether a given argument is valid. def valid_parameter?(param) parameter_struct.hashed_elements.include?(param.to_s) end def set_arguments(arguments) @arguments = {} @parameter_struct = nil return if arguments.nil? arguments.each do |arg, default| arg = arg.to_s warn_if_metaparam(arg, default) @arguments[arg] = default end end # Sets the argument name to Puppet Type hash used for type checking. # Names must correspond to available arguments (they must be defined first). # Arguments not mentioned will not be type-checked. # def set_argument_types(name_to_type_hash) @argument_types = {} @parameter_struct = nil return unless name_to_type_hash name_to_type_hash.each do |name, t| # catch internal errors unless @arguments.include?(name) raise Puppet::DevError, _("Parameter '%{name}' is given a type, but is not a valid parameter.") % { name: name } end unless t.is_a? Puppet::Pops::Types::PAnyType raise Puppet::DevError, _("Parameter '%{name}' is given a type that is not a Puppet Type, got %{class_name}") % { name: name, class_name: t.class } end @argument_types[name] = t end end # Returns boolean true if an instance of this type is a capability. This # implementation always returns false. This "duck-typing" interface is # shared among other classes and makes it easier to detect capabilities # when they are intermixed with non capability instances. def is_capability? false end private def convert_from_ast(name) value = name.value if value.is_a?(Puppet::Parser::AST::Regex) value.value else value end end def evaluate_parent_type(resource) return unless klass = parent_type(resource.scope) and parent_resource = resource.scope.compiler.catalog.resource(:class, klass.name) || resource.scope.compiler.catalog.resource(:node, klass.name) parent_resource.evaluate unless parent_resource.evaluated? parent_scope(resource.scope, klass) end # Split an fq name into a namespace and name def namesplit(fullname) ary = fullname.split(DOUBLE_COLON) n = ary.pop || "" ns = ary.join(DOUBLE_COLON) return ns, n end def parent_scope(scope, klass) scope.class_scope(klass) || raise(Puppet::DevError, _("Could not find scope for %{class_name}") % { class_name: klass.name }) end def set_name_and_namespace(name) if name.is_a?(Regexp) @name = name @namespace = "" else @name = name.to_s.downcase # Note we're doing something somewhat weird here -- we're setting # the class's namespace to its fully qualified name. This means # anything inside that class starts looking in that namespace first. @namespace, _ = @type == :hostclass ? [@name, ''] : namesplit(@name) end end def warn_if_metaparam(param, default) return unless Puppet::Type.metaparamclass(param) if default warnonce _("%{param} is a metaparam; this value will inherit to all contained resources in the %{name} definition") % { param: param, name: self.name } else raise Puppet::ParseError, _("%{param} is a metaparameter; please choose another parameter name in the %{name} definition") % { param: param, name: self.name } end end def parameter_struct @parameter_struct ||= create_params_struct end def create_params_struct arg_types = argument_types type_factory = Puppet::Pops::Types::TypeFactory members = { type_factory.optional(type_factory.string(NAME)) => type_factory.any } if application? resource_type = type_factory.type_type(type_factory.resource) members[type_factory.string(NODES)] = type_factory.hash_of(type_factory.variant( resource_type, type_factory.array_of(resource_type)), type_factory.type_type(type_factory.resource('node'))) end Puppet::Type.eachmetaparam do |name| # TODO: Once meta parameters are typed, this should change to reflect that type members[name.to_s] = type_factory.any end arguments.each_pair do |name, default| key_type = type_factory.string(name.to_s) key_type = type_factory.optional(key_type) unless default.nil? arg_type = arg_types[name] arg_type = type_factory.any if arg_type.nil? members[key_type] = arg_type end type_factory.struct(members) end private :create_params_struct end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/resource/type_collection.rb������������������������������������������������0000644�0052762�0001160�00000020061�13417161721�022444� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parser/type_loader' require 'puppet/util/file_watcher' require 'puppet/util/warnings' # @api private class Puppet::Resource::TypeCollection attr_reader :environment attr_accessor :parse_failed include Puppet::Util::Warnings def clear @hostclasses.clear @definitions.clear @applications.clear @nodes.clear @notfound.clear @capability_mappings.clear @sites.clear end def initialize(env) @environment = env @hostclasses = {} @definitions = {} @capability_mappings = {} @applications = {} @nodes = {} @notfound = {} @sites = [] # So we can keep a list and match the first-defined regex @node_list = [] end def import_ast(ast, modname) ast.instantiate(modname).each do |instance| add(instance) end end def inspect "TypeCollection" + { :hostclasses => @hostclasses.keys, :definitions => @definitions.keys, :nodes => @nodes.keys, :capability_mappings => @capability_mappings.keys, :applications => @applications.keys, :site => @sites[0] # todo, could be just a binary, this dumps the entire body (good while developing) }.inspect end # @api private def <<(thing) add(thing) self end def add(instance) # return a merged instance, or the given catch(:merged) { send("add_#{instance.type}", instance) instance.resource_type_collection = self instance } end def add_hostclass(instance) handle_hostclass_merge(instance) dupe_check(instance, @hostclasses) { |dupe| _("Class '%{klass}' is already defined%{error}; cannot redefine") % { klass: instance.name, error: dupe.error_context } } dupe_check(instance, @definitions) { |dupe| _("Definition '%{klass}' is already defined%{error}; cannot be redefined as a class") % { klass: instance.name, error: dupe.error_context } } dupe_check(instance, @applications) { |dupe| _("Application '%{klass}' is already defined%{error}; cannot be redefined as a class") % { klass: instance.name, error: dupe.error_context } } @hostclasses[instance.name] = instance instance end def handle_hostclass_merge(instance) # Only main class (named '') can be merged (for purpose of merging top-scopes). return instance unless instance.name == '' if instance.type == :hostclass && (other = @hostclasses[instance.name]) && other.type == :hostclass other.merge(instance) # throw is used to signal merge - avoids dupe checks and adding it to hostclasses throw :merged, other end end # Replaces the known settings with a new instance (that must be named 'settings'). # This is primarily needed for testing purposes. Also see PUP-5954 as it makes # it illegal to merge classes other than the '' (main) class. Prior to this change # settings where always merged rather than being defined from scratch for many testing scenarios # not having a complete side effect free setup for compilation. # def replace_settings(instance) @hostclasses['settings'] = instance end def hostclass(name) @hostclasses[munge_name(name)] end def add_node(instance) dupe_check(instance, @nodes) { |dupe| _("Node '%{name}' is already defined%{error}; cannot redefine") % { name: instance.name, error: dupe.error_context } } @node_list << instance @nodes[instance.name] = instance instance end def add_site(instance) dupe_check_singleton(instance, @sites) { |dupe| _("Site is already defined%{error}; cannot redefine") % { error: dupe.error_context } } @sites << instance instance end def site(_) @sites[0] end def loader @loader ||= Puppet::Parser::TypeLoader.new(environment) end def node(name) name = munge_name(name) if node = @nodes[name] return node end @node_list.each do |n| next unless n.name_is_regex? return n if n.match(name) end nil end def node_exists?(name) @nodes[munge_name(name)] end def nodes? @nodes.length > 0 end def add_definition(instance) dupe_check(instance, @hostclasses) { |dupe| _("'%{name}' is already defined%{error} as a class; cannot redefine as a definition") % { name: instance.name, error: dupe.error_context } } dupe_check(instance, @definitions) { |dupe| _("Definition '%{name}' is already defined%{error}; cannot be redefined") % { name: instance.name, error: dupe.error_context } } dupe_check(instance, @applications) { |dupe| _("'%{name}' is already defined%{error} as an application; cannot be redefined") % { name: instance.name, error: dupe.error_context } } @definitions[instance.name] = instance end def add_capability_mapping(instance) dupe_check(instance, @capability_mappings) { |dupe| _("'%{name}' is already defined%{error} as a class; cannot redefine as a mapping") % { name: instance.name, error: dupe.error_context } } @capability_mappings[instance.name] = instance end def definition(name) @definitions[munge_name(name)] end def add_application(instance) dupe_check(instance, @hostclasses) { |dupe| _("'%{name}' is already defined%{error} as a class; cannot redefine as an application") % { name: instance.name, error: dupe.error_context } } dupe_check(instance, @definitions) { |dupe| _("'%{name}' is already defined%{error} as a definition; cannot redefine as an application") % { name: instance.name, error: dupe.error_context } } dupe_check(instance, @applications) { |dupe| _("'%{name}' is already defined%{error} as an application; cannot be redefined") % { name: instance.name, error: dupe.error_context } } @applications[instance.name] = instance end def application(name) @applications[munge_name(name)] end def find_node(name) @nodes[munge_name(name)] end def find_site() @sites[0] end def find_hostclass(name) find_or_load(name, :hostclass) end def find_definition(name) find_or_load(name, :definition) end def find_application(name) find_or_load(name, :application) end # TODO: This implementation is wasteful as it creates a copy on each request # [:hostclasses, :nodes, :definitions, :capability_mappings, :applications].each do |m| define_method(m) do instance_variable_get("@#{m}").dup end end def parse_failed? @parse_failed end def version if !defined?(@version) if environment.config_version.nil? || environment.config_version == "" @version = Time.now.to_i else @version = Puppet::Util::Execution.execute([environment.config_version]).to_s.strip end end @version rescue Puppet::ExecutionFailure => e raise Puppet::ParseError, _("Execution of config_version command `%{cmd}` failed: %{message}") % { cmd: environment.config_version, message: e.message }, e.backtrace end private COLON_COLON = "::".freeze # Resolve namespaces and find the given object. Autoload it if # necessary. def find_or_load(name, type) # Name is always absolute, but may start with :: which must be removed fqname = (name[0,2] == COLON_COLON ? name[2..-1] : name) result = send(type, fqname) unless result if @notfound[ fqname ] && Puppet[ :ignoremissingtypes ] # do not try to autoload if we already tried and it wasn't conclusive # as this is a time consuming operation. Warn the user. # Check first if debugging is on since the call to debug_once is expensive if Puppet[:debug] debug_once _("Not attempting to load %{type} %{fqname} as this object was missing during a prior compilation") % { type: type, fqname: fqname } end else fqname = munge_name(fqname) result = loader.try_load_fqname(type, fqname) @notfound[ fqname ] = result.nil? end end result end def munge_name(name) name.to_s.downcase end def dupe_check(instance, hash) return unless dupe = hash[instance.name] message = yield dupe instance.fail Puppet::ParseError, message end def dupe_check_singleton(instance, set) return if set.empty? message = yield set[0] instance.fail Puppet::ParseError, message end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/resource/catalog.rb��������������������������������������������������������0000644�0052762�0001160�00000055106�13417161721�020672� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/node' require 'puppet/indirector' require 'puppet/transaction' require 'puppet/util/tagging' require 'puppet/graph' require 'securerandom' require 'puppet/resource/capability_finder' # This class models a node catalog. It is the thing meant to be passed # from server to client, and it contains all of the information in the # catalog, including the resources and the relationships between them. # # @api public class Puppet::Resource::Catalog < Puppet::Graph::SimpleGraph class DuplicateResourceError < Puppet::Error include Puppet::ExternalFileError end extend Puppet::Indirector indirects :catalog, :terminus_setting => :catalog_terminus include Puppet::Util::Tagging # The host name this is a catalog for. attr_accessor :name # The catalog version. Used for testing whether a catalog # is up to date. attr_accessor :version # The id of the code input to the compiler. attr_accessor :code_id # The UUID of the catalog attr_accessor :catalog_uuid # @return [Integer] catalog format version number. This value is constant # for a given version of Puppet; it is incremented when a new release of # Puppet changes the API for the various objects that make up the catalog. attr_accessor :catalog_format # Inlined file metadata for non-recursive find # A hash of title => metadata attr_accessor :metadata # Inlined file metadata for recursive search # A hash of title => { source => [metadata, ...] } attr_accessor :recursive_metadata # How long this catalog took to retrieve. Used for reporting stats. attr_accessor :retrieval_duration # Whether this is a host catalog, which behaves very differently. # In particular, reports are sent, graphs are made, and state is # stored in the state database. If this is set incorrectly, then you often # end up in infinite loops, because catalogs are used to make things # that the host catalog needs. attr_accessor :host_config # Whether this catalog was retrieved from the cache, which affects # whether it is written back out again. attr_accessor :from_cache # Some metadata to help us compile and generally respond to the current state. attr_accessor :client_version, :server_version # A String representing the environment for this catalog attr_accessor :environment # The actual environment instance that was used during compilation attr_accessor :environment_instance # Add classes to our class list. def add_class(*classes) classes.each do |klass| @classes << klass end # Add the class names as tags, too. tag(*classes) end # Returns [typename, title] when given a String with "Type[title]". # Returns [nil, nil] if '[' ']' not detected. # def title_key_for_ref( ref ) s = ref.index('[') e = ref.rindex(']') if s && e && e > s a = [ref[0, s], ref[s+1, e-s-1]] else a = [nil, nil] end return a end def add_resource_before(other, *resources) resources.each do |resource| other_title_key = title_key_for_ref(other.ref) idx = @resources.index(other_title_key) if idx.nil? raise ArgumentError, _("Cannot add resource %{resource_1} before %{resource_2} because %{resource_2} is not yet in the catalog") % { resource_1: resource.ref, resource_2: other.ref } end add_one_resource(resource, idx) end end # Add `resources` to the catalog after `other`. WARNING: adding # multiple resources will produce the reverse ordering, e.g. calling # `add_resource_after(A, [B,C])` will result in `[A,C,B]`. def add_resource_after(other, *resources) resources.each do |resource| other_title_key = title_key_for_ref(other.ref) idx = @resources.index(other_title_key) if idx.nil? raise ArgumentError, _("Cannot add resource %{resource_1} after %{resource_2} because %{resource_2} is not yet in the catalog") % { resource_1: resource.ref, resource_2: other.ref } end add_one_resource(resource, idx+1) end end def add_resource(*resources) resources.each do |resource| add_one_resource(resource) end end # @param resource [A Resource] a resource in the catalog # @return [A Resource, nil] the resource that contains the given resource # @api public def container_of(resource) adjacent(resource, :direction => :in)[0] end def add_one_resource(resource, idx=-1) title_key = title_key_for_ref(resource.ref) if @resource_table[title_key] fail_on_duplicate_type_and_title(resource, title_key) end add_resource_to_table(resource, title_key, idx) create_resource_aliases(resource) resource.catalog = self if resource.respond_to?(:catalog=) add_resource_to_graph(resource) end private :add_one_resource def add_resource_to_table(resource, title_key, idx) @resource_table[title_key] = resource @resources.insert(idx, title_key) end private :add_resource_to_table def add_resource_to_graph(resource) add_vertex(resource) @relationship_graph.add_vertex(resource) if @relationship_graph end private :add_resource_to_graph def create_resource_aliases(resource) # Explicit aliases must always be processed # The alias setting logic checks, and does not error if the alias is set to an already set alias # for the same resource (i.e. it is ok if alias == title explicit_aliases = [resource[:alias]].flatten.compact explicit_aliases.each {| given_alias | self.alias(resource, given_alias) } # Skip creating uniqueness key alias and checking collisions for non-isomorphic resources. return unless resource.respond_to?(:isomorphic?) and resource.isomorphic? # Add an alias if the uniqueness key is valid and not the title, which has already been checked. ukey = resource.uniqueness_key if ukey.any? and ukey != [resource.title] self.alias(resource, ukey) end end private :create_resource_aliases # Create an alias for a resource. def alias(resource, key) ref = resource.ref ref =~ /^(.+)\[/ class_name = $1 || resource.class.name newref = [class_name, key].flatten if key.is_a? String ref_string = "#{class_name}[#{key}]" return if ref_string == ref end # LAK:NOTE It's important that we directly compare the references, # because sometimes an alias is created before the resource is # added to the catalog, so comparing inside the below if block # isn't sufficient. if existing = @resource_table[newref] return if existing == resource resource_declaration = Puppet::Util::Errors.error_location(resource.file, resource.line) msg = if resource_declaration.empty? #TRANSLATORS 'alias' should not be translated _("Cannot alias %{resource} to %{key}; resource %{newref} already declared") % { resource: ref, key: key.inspect, newref: newref.inspect } else #TRANSLATORS 'alias' should not be translated _("Cannot alias %{resource} to %{key} at %{resource_declaration}; resource %{newref} already declared") % { resource: ref, key: key.inspect, resource_declaration: resource_declaration, newref: newref.inspect } end msg += Puppet::Util::Errors.error_location_with_space(existing.file, existing.line) raise ArgumentError, msg end @resource_table[newref] = resource @aliases[ref] ||= [] @aliases[ref] << newref end # Apply our catalog to the local host. # @param options [Hash{Symbol => Object}] a hash of options # @option options [Puppet::Transaction::Report] :report # The report object to log this transaction to. This is optional, # and the resulting transaction will create a report if not # supplied. # # @return [Puppet::Transaction] the transaction created for this # application # # @api public def apply(options = {}) Puppet::Util::Storage.load if host_config? transaction = create_transaction(options) begin transaction.report.as_logging_destination do transaction_evaluate_time = Puppet::Util.thinmark do transaction.evaluate end transaction.report.add_times(:transaction_evaluation, transaction_evaluate_time) end ensure # Don't try to store state unless we're a host config # too recursive. Puppet::Util::Storage.store if host_config? end yield transaction if block_given? transaction end # The relationship_graph form of the catalog. This contains all of the # dependency edges that are used for determining order. # # @param given_prioritizer [Puppet::Graph::Prioritizer] The prioritization # strategy to use when constructing the relationship graph. Defaults the # being determined by the `ordering` setting. # @return [Puppet::Graph::RelationshipGraph] # @api public def relationship_graph(given_prioritizer = nil) if @relationship_graph.nil? @relationship_graph = Puppet::Graph::RelationshipGraph.new(given_prioritizer || prioritizer) @relationship_graph.populate_from(self) end @relationship_graph end def clear(remove_resources = true) super() # We have to do this so that the resources clean themselves up. @resource_table.values.each { |resource| resource.remove } if remove_resources @resource_table.clear @resources = [] if @relationship_graph @relationship_graph.clear @relationship_graph = nil end end def classes @classes.dup end # Create a new resource and register it in the catalog. def create_resource(type, options) unless klass = Puppet::Type.type(type) raise ArgumentError, _("Unknown resource type %{type}") % { type: type } end return unless resource = klass.new(options) add_resource(resource) resource end # Make sure all of our resources are "finished". def finalize make_default_resources @resource_table.values.each { |resource| resource.finish } write_graph(:resources) end def host_config? host_config end def initialize(name = nil, environment = Puppet::Node::Environment::NONE, code_id = nil) super() @name = name @catalog_uuid = SecureRandom.uuid @catalog_format = 1 @metadata = {} @recursive_metadata = {} @classes = [] @resource_table = {} @resources = [] @relationship_graph = nil @host_config = true @environment_instance = environment @environment = environment.to_s @code_id = code_id @aliases = {} if block_given? yield(self) finalize end end # Make the default objects necessary for function. def make_default_resources # We have to add the resources to the catalog, or else they won't get cleaned up after # the transaction. # First create the default scheduling objects Puppet::Type.type(:schedule).mkdefaultschedules.each { |res| add_resource(res) unless resource(res.ref) } # And filebuckets if bucket = Puppet::Type.type(:filebucket).mkdefaultbucket add_resource(bucket) unless resource(bucket.ref) end end # Remove the resource from our catalog. Notice that we also call # 'remove' on the resource, at least until resource classes no longer maintain # references to the resource instances. def remove_resource(*resources) resources.each do |resource| ref = resource.ref title_key = title_key_for_ref(ref) @resource_table.delete(title_key) if aliases = @aliases[ref] aliases.each { |res_alias| @resource_table.delete(res_alias) } @aliases.delete(ref) end remove_vertex!(resource) if vertex?(resource) @relationship_graph.remove_vertex!(resource) if @relationship_graph and @relationship_graph.vertex?(resource) @resources.delete(title_key) # Only Puppet::Type kind of resources respond to :remove, not Puppet::Resource resource.remove if resource.respond_to?(:remove) end end # Look a resource up by its reference (e.g., File[/etc/passwd]). def resource(type, title = nil) # Retain type if it's a type type_name = type.is_a?(Puppet::CompilableResourceType) || type.is_a?(Puppet::Resource::Type) ? type.name : type type_name, title = Puppet::Resource.type_and_title(type_name, title) type = type_name if type.is_a?(String) title_key = [type_name, title.to_s] result = @resource_table[title_key] if result.nil? # an instance has to be created in order to construct the unique key used when # searching for aliases, or when app_management is active and nothing is found in # which case it is needed by the CapabilityFinder. res = Puppet::Resource.new(type, title, { :environment => @environment_instance }) # Must check with uniqueness key because of aliases or if resource transforms title in title # to attribute mappings. result = @resource_table[[type_name, res.uniqueness_key].flatten] if result.nil? resource_type = res.resource_type if resource_type && resource_type.is_capability? # @todo lutter 2015-03-10: this assumes that it is legal to just # mention a capability resource in code and have it automatically # made available, even if the current component does not require it result = Puppet::Resource::CapabilityFinder.find(environment, code_id, res) add_resource(result) if result end end end result end def resource_refs resource_keys.collect{ |type, name| name.is_a?( String ) ? "#{type}[#{name}]" : nil}.compact end def resource_keys @resource_table.keys end def resources @resources.collect do |key| @resource_table[key] end end def self.from_data_hash(data) result = new(data['name'], Puppet::Node::Environment::NONE) if tags = data['tags'] result.tag(*tags) end if version = data['version'] result.version = version end if code_id = data['code_id'] result.code_id = code_id end if catalog_uuid = data['catalog_uuid'] result.catalog_uuid = catalog_uuid end result.catalog_format = data['catalog_format'] || 0 if environment = data['environment'] result.environment = environment result.environment_instance = Puppet::Node::Environment.remote(environment.to_sym) end if resources = data['resources'] result.add_resource(*resources.collect do |res| Puppet::Resource.from_data_hash(res) end) end if edges = data['edges'] edges.each do |edge_hash| edge = Puppet::Relationship.from_data_hash(edge_hash) unless source = result.resource(edge.source) raise ArgumentError, _("Could not intern from data: Could not find relationship source %{source} for %{target}") % { source: edge.source.inspect, target: edge.target.to_s } end edge.source = source unless target = result.resource(edge.target) raise ArgumentError, _("Could not intern from data: Could not find relationship target %{target} for %{source}") % { target: edge.target.inspect, source: edge.source.to_s } end edge.target = target result.add_edge(edge) end end if classes = data['classes'] result.add_class(*classes) end if metadata = data['metadata'] result.metadata = metadata.inject({}) { |h, (k, v)| h[k] = Puppet::FileServing::Metadata.from_data_hash(v); h } end if recursive_metadata = data['recursive_metadata'] result.recursive_metadata = recursive_metadata.inject({}) do |h, (title, source_to_meta_hash)| h[title] = source_to_meta_hash.inject({}) do |inner_h, (source, metas)| inner_h[source] = metas.map {|meta| Puppet::FileServing::Metadata.from_data_hash(meta)} inner_h end h end end result end def to_data_hash metadata_hash = metadata.inject({}) { |h, (k, v)| h[k] = v.to_data_hash; h } recursive_metadata_hash = recursive_metadata.inject({}) do |h, (title, source_to_meta_hash)| h[title] = source_to_meta_hash.inject({}) do |inner_h, (source, metas)| inner_h[source] = metas.map {|meta| meta.to_data_hash} inner_h end h end { 'tags' => tags.to_a, 'name' => name, 'version' => version, 'code_id' => code_id, 'catalog_uuid' => catalog_uuid, 'catalog_format' => catalog_format, 'environment' => environment.to_s, 'resources' => @resources.map { |v| @resource_table[v].to_data_hash }, 'edges' => edges.map { |e| e.to_data_hash }, 'classes' => classes, }.merge(metadata_hash.empty? ? {} : {'metadata' => metadata_hash}).merge(recursive_metadata_hash.empty? ? {} : {'recursive_metadata' => recursive_metadata_hash}) end # Convert our catalog into a RAL catalog. def to_ral to_catalog :to_ral end # Convert our catalog into a catalog of Puppet::Resource instances. def to_resource to_catalog :to_resource end # filter out the catalog, applying +block+ to each resource. # If the block result is false, the resource will # be kept otherwise it will be skipped def filter(&block) # to_catalog must take place in a context where current_environment is set to the same env as the # environment set in the catalog (if it is set) # See PUP-3755 if environment_instance Puppet.override({:current_environment => environment_instance}) do to_catalog :to_resource, &block end else # If catalog has no environment_instance, hope that the caller has made sure the context has the # correct current_environment to_catalog :to_resource, &block end end # Store the classes in the classfile. def write_class_file # classfile paths may contain UTF-8 # https://puppet.com/docs/puppet/latest/configuration.html#classfile classfile = Puppet.settings.setting(:classfile) Puppet::FileSystem.open(classfile.value, classfile.mode.to_i(8), "w:UTF-8") do |f| f.puts classes.join("\n") end rescue => detail Puppet.err _("Could not create class file %{file}: %{detail}") % { file: Puppet[:classfile], detail: detail } end # Store the list of resources we manage def write_resource_file # resourcefile contains resources that may be UTF-8 names # https://puppet.com/docs/puppet/latest/configuration.html#resourcefile resourcefile = Puppet.settings.setting(:resourcefile) Puppet::FileSystem.open(resourcefile.value, resourcefile.mode.to_i(8), "w:UTF-8") do |f| to_print = resources.map do |resource| next unless resource.managed? "#{resource.ref.downcase}" end.compact f.puts to_print.join("\n") end rescue => detail Puppet.err _("Could not create resource file %{file}: %{detail}") % { file: Puppet[:resourcefile], detail: detail } end # Produce the graph files if requested. def write_graph(name) # We only want to graph the main host catalog. return unless host_config? super end private def prioritizer @prioritizer ||= case Puppet[:ordering] when "title-hash" Puppet::Graph::TitleHashPrioritizer.new when "manifest" Puppet::Graph::SequentialPrioritizer.new when "random" Puppet::Graph::RandomPrioritizer.new else raise Puppet::DevError, _("Unknown ordering type %{ordering}") % { ordering: Puppet[:ordering] } end end def create_transaction(options) transaction = Puppet::Transaction.new(self, options[:report], prioritizer) transaction.tags = options[:tags] if options[:tags] transaction.ignoreschedules = true if options[:ignoreschedules] transaction.for_network_device = Puppet.lookup(:network_device) { nil } || options[:network_device] transaction end # Verify that the given resource isn't declared elsewhere. def fail_on_duplicate_type_and_title(resource, title_key) # Short-circuit the common case, return unless existing_resource = @resource_table[title_key] # If we've gotten this far, it's a real conflict error_location_str = Puppet::Util::Errors.error_location(existing_resource.file, existing_resource.line) msg = if error_location_str.empty? _("Duplicate declaration: %{resource} is already declared; cannot redeclare") % { resource: resource.ref } else _("Duplicate declaration: %{resource} is already declared at %{error_location}; cannot redeclare") % { resource: resource.ref, error_location: error_location_str } end raise DuplicateResourceError.new(msg, resource.file, resource.line) end # An abstracted method for converting one catalog into another type of catalog. # This pretty much just converts all of the resources from one class to another, using # a conversion method. def to_catalog(convert) result = self.class.new(self.name, self.environment_instance) result.version = self.version result.code_id = self.code_id result.catalog_uuid = self.catalog_uuid result.catalog_format = self.catalog_format result.metadata = self.metadata result.recursive_metadata = self.recursive_metadata map = {} resources.each do |resource| next if virtual_not_exported?(resource) next if block_given? and yield resource newres = resource.copy_as_resource newres.catalog = result if convert != :to_resource newres = newres.to_ral end # We can't guarantee that resources don't munge their names # (like files do with trailing slashes), so we have to keep track # of what a resource got converted to. map[resource.ref] = newres result.add_resource newres end message = convert.to_s.gsub "_", " " edges.each do |edge| # Skip edges between virtual resources. next if virtual_not_exported?(edge.source) next if block_given? and yield edge.source next if virtual_not_exported?(edge.target) next if block_given? and yield edge.target unless source = map[edge.source.ref] raise Puppet::DevError, _("Could not find resource %{resource} when converting %{message} resources") % { resource: edge.source.ref, message: message } end unless target = map[edge.target.ref] raise Puppet::DevError, _("Could not find resource %{resource} when converting %{message} resources") % { resource: edge.target.ref, message: message } end result.add_edge(source, target, edge.label) end map.clear result.add_class(*self.classes) result.merge_tags_from(self) result end def virtual_not_exported?(resource) resource.virtual && !resource.exported end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/scheduler.rb���������������������������������������������������������������0000644�0052762�0001160�00000000636�13417161721�017405� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Scheduler require 'puppet/scheduler/job' require 'puppet/scheduler/splay_job' require 'puppet/scheduler/scheduler' require 'puppet/scheduler/timer' module_function def create_job(interval, splay=false, splay_limit=0, &block) if splay Puppet::Scheduler::SplayJob.new(interval, splay_limit, &block) else Puppet::Scheduler::Job.new(interval, &block) end end end ��������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/scheduler/�����������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�017060� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/scheduler/job.rb�����������������������������������������������������������0000644�0052762�0001160�00000001560�13417161721�020154� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Scheduler class Job attr_reader :run_interval attr_accessor :last_run attr_accessor :start_time def initialize(run_interval, &block) self.run_interval = run_interval @last_run = nil @run_proc = block @enabled = true end def run_interval=(interval) @run_interval = [interval, 0].max end def ready?(time) if @last_run @last_run + @run_interval <= time else true end end def enabled? @enabled end def enable @enabled = true end def disable @enabled = false end def interval_to_next_from(time) if ready?(time) 0 else @run_interval - (time - @last_run) end end def run(now) @last_run = now if @run_proc @run_proc.call(self) end end end end ������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/scheduler/scheduler.rb�����������������������������������������������������0000644�0052762�0001160�00000002023�13417161721�021353� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Scheduler class Scheduler def initialize(timer=Puppet::Scheduler::Timer.new) @timer = timer end def run_loop(jobs) mark_start_times(jobs, @timer.now) while not enabled(jobs).empty? @timer.wait_for(min_interval_to_next_run_from(jobs, @timer.now)) run_ready(jobs, @timer.now) end end private def enabled(jobs) jobs.select(&:enabled?) end def mark_start_times(jobs, start_time) jobs.each do |job| job.start_time = start_time end end def min_interval_to_next_run_from(jobs, from_time) enabled(jobs).map do |j| j.interval_to_next_from(from_time) end.min end def run_ready(jobs, at_time) enabled(jobs).each do |j| # This check intentionally happens right before each run, # instead of filtering on ready schedulers, since one may adjust # the readiness of a later one if j.ready?(at_time) j.run(at_time) end end end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/scheduler/splay_job.rb�����������������������������������������������������0000644�0052762�0001160�00000001045�13417161721�021362� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Scheduler class SplayJob < Job attr_reader :splay def initialize(run_interval, splay_limit, &block) @splay = calculate_splay(splay_limit) super(run_interval, &block) end def interval_to_next_from(time) if last_run super else (start_time + splay) - time end end def ready?(time) if last_run super else start_time + splay <= time end end private def calculate_splay(limit) rand(limit + 1) end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/scheduler/timer.rb���������������������������������������������������������0000644�0052762�0001160�00000000255�13417161721�020522� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Scheduler class Timer def wait_for(seconds) if seconds > 0 sleep(seconds) end end def now Time.now end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/settings/������������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�016742� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/settings/array_setting.rb��������������������������������������������������0000644�0052762�0001160�00000000515�13417161721�022136� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Settings::ArraySetting < Puppet::Settings::BaseSetting def type :array end def munge(value) case value when String value.split(/\s*,\s*/) when Array value else raise ArgumentError, _("Expected an Array or String, got a %{klass}") % { klass: value.class } end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/settings/autosign_setting.rb�����������������������������������������������0000644�0052762�0001160�00000001271�13417161721�022651� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/settings/base_setting' # A specialization of the file setting to allow boolean values. # # The autosign value can be either a boolean or a file path, and if the setting # is a file path then it may have a owner/group/mode specified. # # @api private class Puppet::Settings::AutosignSetting < Puppet::Settings::FileSetting def munge(value) if ['true', true].include? value true elsif ['false', false, nil].include? value false elsif Puppet::Util.absolute_path?(value) value else raise Puppet::Settings::ValidationError, _("Invalid autosign value %{value}: must be 'true'/'false' or an absolute path") % { value: value } end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/settings/base_setting.rb���������������������������������������������������0000644�0052762�0001160�00000013704�13417161721�021736� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/settings/errors' # The base setting type class Puppet::Settings::BaseSetting attr_accessor :name, :desc, :section, :default, :call_hook attr_reader :short, :deprecated def self.available_call_hook_values [:on_define_and_write, :on_initialize_and_write, :on_write_only] end def call_hook=(value) if value.nil? #TRANSLATORS ':%{name}', ':call_hook', and ':on_write_only' should not be translated Puppet.warning _("Setting :%{name} :call_hook is nil, defaulting to :on_write_only") % { name: name } value = :on_write_only end unless self.class.available_call_hook_values.include?(value) #TRANSLATORS 'call_hook' is a Puppet option name and should not be translated raise ArgumentError, _("Invalid option %{value} for call_hook") % { value: value } end @call_hook = value end def call_hook_on_define? call_hook == :on_define_and_write end def call_hook_on_initialize? call_hook == :on_initialize_and_write end # get the arguments in getopt format def getopt_args if short [["--#{name}", "-#{short}", GetoptLong::REQUIRED_ARGUMENT]] else [["--#{name}", GetoptLong::REQUIRED_ARGUMENT]] end end # get the arguments in OptionParser format def optparse_args if short ["--#{name}", "-#{short}", desc, :REQUIRED] else ["--#{name}", desc, :REQUIRED] end end def hook=(block) @has_hook = true meta_def :handle, &block end def has_hook? @has_hook end # Create the new element. Pretty much just sets the name. def initialize(args = {}) unless @settings = args.delete(:settings) raise ArgumentError.new("You must refer to a settings object") end # explicitly set name prior to calling other param= methods to provide meaningful feedback during # other warnings @name = args[:name] if args.include? :name #set the default value for call_hook @call_hook = :on_write_only if args[:hook] and not args[:call_hook] @has_hook = false if args[:call_hook] and not args[:hook] #TRANSLATORS ':call_hook' and ':hook' are specific setting names and should not be translated raise ArgumentError, _("Cannot reference :call_hook for :%{name} if no :hook is defined") % { name: @name } end args.each do |param, value| method = param.to_s + "=" unless self.respond_to? method raise ArgumentError, _("%{class_name} (setting '%{setting}') does not accept %{parameter}") % { class_name: self.class, setting: args[:name], parameter: param } end self.send(method, value) end unless self.desc raise ArgumentError, _("You must provide a description for the %{class_name} config option") % { class_name: self.name } end end def iscreated @iscreated = true end def iscreated? @iscreated end # short name for the element def short=(value) raise ArgumentError, _("Short names can only be one character.") if value.to_s.length != 1 @short = value.to_s end def default(check_application_defaults_first = false) if @default.is_a? Proc # Give unit tests a chance to reevaluate the call by removing the instance variable unless instance_variable_defined?(:@evaluated_default) @evaluated_default = @default.call end default_value = @evaluated_default else default_value = @default end return default_value unless check_application_defaults_first return @settings.value(name, :application_defaults, true) || default_value end # Convert the object to a config statement. def to_config require 'puppet/util/docs' # Scrub any funky indentation; comment out description. str = Puppet::Util::Docs.scrub(@desc).gsub(/^/, "# ") + "\n" # Add in a statement about the default. str << "# The default value is '#{default(true)}'.\n" if default(true) # If the value has not been overridden, then print it out commented # and unconverted, so it's clear that that's the default and how it # works. value = @settings.value(self.name) if value != @default line = "#{@name} = #{value}" else line = "# #{@name} = #{@default}" end str << (line + "\n") # Indent str.gsub(/^/, " ") end # @param bypass_interpolation [Boolean] Set this true to skip the # interpolation step, returning the raw setting value. Defaults to false. # @return [String] Retrieves the value, or if it's not set, retrieves the default. # @api public def value(bypass_interpolation = false) @settings.value(self.name, nil, bypass_interpolation) end # Modify the value when it is first evaluated def munge(value) value end # Print the value for the user in a config compatible format def print(value) munge(value) end def set_meta(meta) Puppet.notice("#{name} does not support meta data. Ignoring.") end def deprecated=(deprecation) unless [:completely, :allowed_on_commandline].include?(deprecation) #TRANSLATORS 'deprecated' is a Puppet setting and ':completely' and ':allowed_on_commandline' are possible values and should not be translated raise ArgumentError, _("Unsupported deprecated value '%{deprecation}'.") % { deprecation: deprecation } + ' ' + _("Supported values for deprecated are ':completely' or ':allowed_on_commandline'") end @deprecated = deprecation end def deprecated? !!@deprecated end # True if we should raise a deprecation_warning if the setting is submitted # on the commandline or is set in puppet.conf. def completely_deprecated? @deprecated == :completely end # True if we should raise a deprecation_warning if the setting is found in # puppet.conf, but not if the user sets it on the commandline def allowed_on_commandline? @deprecated == :allowed_on_commandline end def inspect %Q{<#{self.class}:#{self.object_id} @name="#{@name}" @section="#{@section}" @default="#{@default}" @call_hook="#{@call_hook}">} end end ������������������������������������������������������������puppet-5.5.10/lib/puppet/settings/boolean_setting.rb������������������������������������������������0000644�0052762�0001160�00000001506�13417161721�022440� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# A simple boolean. class Puppet::Settings::BooleanSetting < Puppet::Settings::BaseSetting # get the arguments in getopt format def getopt_args if short [["--#{name}", "-#{short}", GetoptLong::NO_ARGUMENT], ["--no-#{name}", GetoptLong::NO_ARGUMENT]] else [["--#{name}", GetoptLong::NO_ARGUMENT], ["--no-#{name}", GetoptLong::NO_ARGUMENT]] end end def optparse_args if short ["--[no-]#{name}", "-#{short}", desc, :NONE ] else ["--[no-]#{name}", desc, :NONE] end end def munge(value) case value when true, "true"; return true when false, "false"; return false else raise Puppet::Settings::ValidationError, _("Invalid value '%{value}' for boolean parameter: %{name}") % { value: value.inspect, name: @name } end end def type :boolean end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/settings/certificate_revocation_setting.rb���������������������������������0000644�0052762�0001160�00000001022�13417161721�025525� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/settings/base_setting' class Puppet::Settings::CertificateRevocationSetting < Puppet::Settings::BaseSetting def type :certificate_revocation end def munge(value) case value when 'chain', 'true', TrueClass :chain when 'leaf' :leaf when 'false', FalseClass, NilClass false else raise Puppet::Settings::ValidationError, _("Invalid certificate revocation value %{value}: must be one of 'true', 'chain', 'leaf', or 'false'") % { value: value } end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/settings/directory_setting.rb����������������������������������������������0000644�0052762�0001160�00000001121�13417161721�023016� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Settings::DirectorySetting < Puppet::Settings::FileSetting def type :directory end # @api private # # @param option [String] Extra file operation mode information to use # (defaults to read-only mode 'r') # This is the standard mechanism Ruby uses in the IO class, and therefore # encoding may be explicitly like fmode : encoding or fmode : "BOM|UTF-*" # for example, a:ASCII or w+:UTF-8 def open_file(filename, option = 'r', &block) controlled_access do |mode| Puppet::FileSystem.open(filename, mode, option, &block) end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/settings/duration_setting.rb�����������������������������������������������0000644�0052762�0001160�00000001766�13417161721�022656� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# A setting that represents a span of time, and evaluates to an integer # number of seconds after being parsed class Puppet::Settings::DurationSetting < Puppet::Settings::BaseSetting # How we convert from various units to seconds. UNITMAP = { # 365 days isn't technically a year, but is sufficient for most purposes "y" => 365 * 24 * 60 * 60, "d" => 24 * 60 * 60, "h" => 60 * 60, "m" => 60, "s" => 1 } # A regex describing valid formats with groups for capturing the value and units FORMAT = /^(\d+)(y|d|h|m|s)?$/ def type :duration end # Convert the value to an integer, parsing numeric string with units if necessary. def munge(value) case when value.is_a?(Integer) || value.nil? value when (value.is_a?(String) and value =~ FORMAT) $1.to_i * UNITMAP[$2 || 's'] else raise Puppet::Settings::ValidationError, _("Invalid duration format '%{value}' for parameter: %{name}") % { value: value.inspect, name: @name } end end end ����������puppet-5.5.10/lib/puppet/settings/enum_setting.rb���������������������������������������������������0000644�0052762�0001160�00000000651�13417161721�021765� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Settings::EnumSetting < Puppet::Settings::BaseSetting attr_accessor :values def type :enum end def munge(value) if values.include?(value) value else raise Puppet::Settings::ValidationError, _("Invalid value '%{value}' for parameter %{name}. Allowed values are '%{allowed_values}'") % { value: value, name: @name, allowed_values: values.join("', '") } end end end ���������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/settings/environment_conf.rb�����������������������������������������������0000644�0052762�0001160�00000017335�13417161721�022644� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Configuration settings for a single directory Environment. # @api private class Puppet::Settings::EnvironmentConf ENVIRONMENT_CONF_ONLY_SETTINGS = [:modulepath, :manifest, :config_version].freeze VALID_SETTINGS = (ENVIRONMENT_CONF_ONLY_SETTINGS + [:environment_timeout, :environment_data_provider, :static_catalogs, :rich_data]).freeze # Given a path to a directory environment, attempts to load and parse an # environment.conf in ini format, and return an EnvironmentConf instance. # # An environment.conf is optional, so if the file itself is missing, or # empty, an EnvironmentConf with default values will be returned. # # @note logs warnings if the environment.conf contains any ini sections, # or has settings other than the three handled for directory environments # (:manifest, :modulepath, :config_version) # # @param path_to_env [String] path to the directory environment # @param global_module_path [Array<String>] the installation's base modulepath # setting, appended to default environment modulepaths # @return [EnvironmentConf] the parsed EnvironmentConf object def self.load_from(path_to_env, global_module_path) path_to_env = File.expand_path(path_to_env) conf_file = File.join(path_to_env, 'environment.conf') begin config = Puppet.settings.parse_file(conf_file) validate(conf_file, config) section = config.sections[:main] rescue Errno::ENOENT # environment.conf is an optional file end new(path_to_env, section, global_module_path) end # Provides a configuration object tied directly to the passed environment. # Configuration values are exactly those returned by the environment object, # without interpolation. This is a special case for the default configured # environment returned by the Puppet::Environments::StaticPrivate loader. def self.static_for(environment, environment_timeout = 0, static_catalogs = false, rich_data = false) Static.new(environment, environment_timeout, static_catalogs, nil, rich_data) end attr_reader :section, :path_to_env, :global_modulepath # Create through EnvironmentConf.load_from() def initialize(path_to_env, section, global_module_path) @path_to_env = path_to_env @section = section @global_module_path = global_module_path end def manifest puppet_conf_manifest = Pathname.new(Puppet.settings.value(:default_manifest)) disable_per_environment_manifest = Puppet.settings.value(:disable_per_environment_manifest) fallback_manifest_directory = if puppet_conf_manifest.absolute? puppet_conf_manifest.to_s else File.join(@path_to_env, puppet_conf_manifest.to_s) end if disable_per_environment_manifest environment_conf_manifest = absolute(raw_setting(:manifest)) if environment_conf_manifest && fallback_manifest_directory != environment_conf_manifest #TRANSLATORS 'disable_per_environment_manifest' is a setting and 'environment.conf' is a file name and should not be translated message = _("The 'disable_per_environment_manifest' setting is true, but the environment located at %{path_to_env} has a manifest setting in its environment.conf of '%{environment_conf}' which does not match the default_manifest setting '%{puppet_conf}'.") % { path_to_env: @path_to_env, environment_conf: environment_conf_manifest, puppet_conf: puppet_conf_manifest } message += ' ' + _("If this environment is expecting to find modules in '%{environment_conf}', they will not be available!") % { environment_conf: environment_conf_manifest } Puppet.err(message) end fallback_manifest_directory.to_s else get_setting(:manifest, fallback_manifest_directory) do |manifest| absolute(manifest) end end end def environment_timeout # gen env specific config or use the default value get_setting(:environment_timeout, Puppet.settings.value(:environment_timeout)) do |ttl| # munges the string form statically without really needed the settings system, only # its ability to munge "4s, 3m, 5d, and 'unlimited' into seconds - if already munged into # numeric form, the TTLSetting handles that. Puppet::Settings::TTLSetting.munge(ttl, 'environment_timeout') end end def environment_data_provider get_setting(:environment_data_provider, Puppet.settings.value(:environment_data_provider)) do |value| value end end def modulepath default_modulepath = [File.join(@path_to_env, "modules")] + @global_module_path get_setting(:modulepath, default_modulepath) do |modulepath| path = modulepath.kind_of?(String) ? modulepath.split(File::PATH_SEPARATOR) : modulepath path.map { |p| expand_glob(absolute(p)) }.flatten.join(File::PATH_SEPARATOR) end end def rich_data get_setting(:rich_data, Puppet.settings.value(:rich_data)) do |value| value end end def static_catalogs get_setting(:static_catalogs, Puppet.settings.value(:static_catalogs)) do |value| value end end def config_version get_setting(:config_version) do |config_version| absolute(config_version) end end def raw_setting(setting_name) setting = section.setting(setting_name) if section setting.value if setting end private def self.validate(path_to_conf_file, config) valid = true section_keys = config.sections.keys main = config.sections[:main] if section_keys.size > 1 # warn once per config file path Puppet.warn_once( :invalid_settings_section, "EnvironmentConf-section:#{path_to_conf_file}", _("Invalid sections in environment.conf at '%{path_to_conf_file}'. Environment conf may not have sections. The following sections are being ignored: '%{sections}'") % { path_to_conf_file: path_to_conf_file, sections: (section_keys - [:main]).join(',') }) valid = false end extraneous_settings = main.settings.map(&:name) - VALID_SETTINGS if !extraneous_settings.empty? # warn once per config file path Puppet.warn_once( :invalid_settings, "EnvironmentConf-settings:#{path_to_conf_file}", _("Invalid settings in environment.conf at '%{path_to_conf_file}'. The following unknown setting(s) are being ignored: %{ignored_settings}") % { path_to_conf_file: path_to_conf_file, ignored_settings: extraneous_settings.join(', ') }) valid = false end return valid end def get_setting(setting_name, default = nil) value = raw_setting(setting_name) value = default if value.nil? yield value end def expand_glob(path) return nil if path.nil? if path =~ /[*?\[\{]/ Dir.glob(path) else path end end def absolute(path) return nil if path.nil? if path =~ /^\$/ # Path begins with $something interpolatable path else Puppet::FileSystem.expand_path(path, @path_to_env) end end # Models configuration for an environment that is not loaded from a directory. # # @api private class Static attr_reader :environment_timeout attr_reader :environment_data_provider attr_reader :rich_data attr_reader :static_catalogs def initialize(environment, environment_timeout, static_catalogs, environment_data_provider = nil, rich_data = false) @environment = environment @environment_timeout = environment_timeout @static_catalogs = static_catalogs @environment_data_provider = environment_data_provider @rich_data = rich_data end def path_to_env nil end def manifest @environment.manifest end def modulepath @environment.modulepath.join(File::PATH_SEPARATOR) end def config_version @environment.config_version end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/settings/errors.rb���������������������������������������������������������0000644�0052762�0001160�00000000462�13417161721�020600� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Exceptions for the settings module require 'puppet/error' class Puppet::Settings class SettingsError < Puppet::Error ; end class ValidationError < SettingsError ; end class InterpolationError < SettingsError ; end class ParseError < SettingsError include Puppet::ExternalFileError end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/settings/file_or_directory_setting.rb��������������������������������������0000644�0052762�0001160�00000002017�13417161721�024522� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Settings::FileOrDirectorySetting < Puppet::Settings::FileSetting def initialize(args) super end def type if Puppet::FileSystem.directory?(self.value) || @path_ends_with_slash :directory else :file end end # Overrides munge to be able to read the un-munged value (the FileSetting.munch removes trailing slash) # def munge(value) if value.is_a?(String) && value =~ /[\\\/]$/ @path_ends_with_slash = true end super end # @api private # # @param option [String] Extra file operation mode information to use # (defaults to read-only mode 'r') # This is the standard mechanism Ruby uses in the IO class, and therefore # encoding may be explicitly like fmode : encoding or fmode : "BOM|UTF-*" # for example, a:ASCII or w+:UTF-8 def open_file(filename, option = 'r', &block) if type == :file super else controlled_access do |mode| Puppet::FileSystem.open(filename, mode, option, &block) end end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/settings/ini_file.rb�������������������������������������������������������0000644�0052762�0001160�00000011731�13417161721�021043� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# @api private class Puppet::Settings::IniFile DEFAULT_SECTION_NAME = "main" def self.update(config_fh, &block) config = parse(config_fh) manipulator = Manipulator.new(config) yield manipulator config.write(config_fh) end def self.parse(config_fh) config = new([DefaultSection.new]) config_fh.each_line do |line| case line.chomp when /^(\s*)\[([[:word:]]+)\](\s*)$/ config.append(SectionLine.new($1, $2, $3)) when /^(\s*)([[:word:]]+)(\s*=\s*)(.*?)(\s*)$/ config.append(SettingLine.new($1, $2, $3, $4, $5)) else config.append(Line.new(line)) end end config end def initialize(lines = []) @lines = lines end def append(line) line.previous = @lines.last @lines << line end def delete(section, name) delete_offset = @lines.index(setting(section, name)) next_offset = delete_offset + 1 if next_offset < @lines.length @lines[next_offset].previous = @lines[delete_offset].previous end @lines.delete_at(delete_offset) end def insert_after(line, new_line) new_line.previous = line insertion_point = @lines.index(line) @lines.insert(insertion_point + 1, new_line) if @lines.length > insertion_point + 2 @lines[insertion_point + 2].previous = new_line end end def section_lines @lines.select { |line| line.is_a?(SectionLine) } end def section_line(name) section_lines.find { |section| section.name == name } end def setting(section, name) settings_in(lines_in(section)).find do |line| line.name == name end end def lines_in(section_name) section_lines = [] current_section_name = DEFAULT_SECTION_NAME @lines.each do |line| if line.is_a?(SectionLine) current_section_name = line.name elsif current_section_name == section_name section_lines << line end end section_lines end def settings_in(lines) lines.select { |line| line.is_a?(SettingLine) } end def settings_exist_in_default_section? lines_in(DEFAULT_SECTION_NAME).any? { |line| line.is_a?(SettingLine) } end def section_exists_with_default_section_name? section_lines.any? do |section| !section.is_a?(DefaultSection) && section.name == DEFAULT_SECTION_NAME end end def set_default_section_write_sectionline(value) if index = @lines.find_index { |line| line.is_a?(DefaultSection) } @lines[index].write_sectionline = true end end def write(fh) # If no real section line for the default section exists, configure the # DefaultSection object to write its section line. (DefaultSection objects # don't write the section line unless explicitly configured to do so) if settings_exist_in_default_section? && !section_exists_with_default_section_name? set_default_section_write_sectionline(true) end fh.truncate(0) fh.rewind @lines.each do |line| line.write(fh) end fh.flush end class Manipulator def initialize(config) @config = config end def set(section, name, value) setting = @config.setting(section, name) if setting setting.value = value else add_setting(section, name, value) end end def delete(section_name, name) setting = @config.setting(section_name, name) if setting @config.delete(section_name, name) setting.to_s.chomp end end private def add_setting(section_name, name, value) section = @config.section_line(section_name) if section.nil? previous_line = SectionLine.new("", section_name, "") @config.append(previous_line) else previous_line = @config.settings_in(@config.lines_in(section_name)).last || section end @config.insert_after(previous_line, SettingLine.new("", name, " = ", value, "")) end end module LineNumber attr_accessor :previous def line_number line = 0 previous_line = previous while previous_line line += 1 previous_line = previous_line.previous end line end end Line = Struct.new(:text) do include LineNumber def to_s text end def write(fh) fh.puts(to_s) end end SettingLine = Struct.new(:prefix, :name, :infix, :value, :suffix) do include LineNumber def to_s "#{prefix}#{name}#{infix}#{value}#{suffix}" end def write(fh) fh.puts(to_s) end def ==(other) super(other) && self.line_number == other.line_number end end SectionLine = Struct.new(:prefix, :name, :suffix) do include LineNumber def to_s "#{prefix}[#{name}]#{suffix}" end def write(fh) fh.puts(to_s) end end class DefaultSection < SectionLine attr_accessor :write_sectionline def initialize @write_sectionline = false super("", DEFAULT_SECTION_NAME, "") end def write(fh) if @write_sectionline super(fh) end end end end ���������������������������������������puppet-5.5.10/lib/puppet/settings/path_setting.rb���������������������������������������������������0000644�0052762�0001160�00000000373�13417161721�021756� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Settings::PathSetting < Puppet::Settings::StringSetting def munge(value) if value.is_a?(String) value = value.split(File::PATH_SEPARATOR).map { |d| File.expand_path(d) }.join(File::PATH_SEPARATOR) end value end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/settings/priority_setting.rb�����������������������������������������������0000644�0052762�0001160�00000002311�13417161721�022675� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/settings/base_setting' # A setting that represents a scheduling priority, and evaluates to an # OS-specific priority level. class Puppet::Settings::PrioritySetting < Puppet::Settings::BaseSetting PRIORITY_MAP = if Puppet::Util::Platform.windows? require 'puppet/util/windows/process' { :high => Puppet::Util::Windows::Process::HIGH_PRIORITY_CLASS, :normal => Puppet::Util::Windows::Process::NORMAL_PRIORITY_CLASS, :low => Puppet::Util::Windows::Process::BELOW_NORMAL_PRIORITY_CLASS, :idle => Puppet::Util::Windows::Process::IDLE_PRIORITY_CLASS } else { :high => -10, :normal => 0, :low => 10, :idle => 19 } end def type :priority end def munge(value) return unless value case when value.is_a?(Integer) value when (value.is_a?(String) and value =~ /\d+/) value.to_i when (value.is_a?(String) and PRIORITY_MAP[value.to_sym]) PRIORITY_MAP[value.to_sym] else raise Puppet::Settings::ValidationError, _("Invalid priority format '%{value}' for parameter: %{name}") % { value: value.inspect, name: @name } end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/settings/server_list_setting.rb��������������������������������������������0000644�0052762�0001160�00000000636�13417161721�023365� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Settings::ServerListSetting < Puppet::Settings::ArraySetting def type :server_list end def munge(value) servers = super servers.map! { |server| case server when String server.split(':') when Array server else raise ArgumentError, _("Expected an Array of String, got a %{klass}") % { klass: value.class } end } end end ��������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/settings/string_setting.rb�������������������������������������������������0000644�0052762�0001160�00000000252�13417161721�022324� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Settings::StringSetting < Puppet::Settings::BaseSetting def type :string end def validate(value) value.nil? or value.is_a?(String) end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/settings/symbolic_enum_setting.rb������������������������������������������0000644�0052762�0001160�00000000715�13417161721�023667� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Settings::SymbolicEnumSetting < Puppet::Settings::BaseSetting attr_accessor :values def type :symbolic_enum end def munge(value) sym = value.to_sym if values.include?(sym) sym else raise Puppet::Settings::ValidationError, _("Invalid value '%{value}' for parameter %{name}. Allowed values are '%{allowed_values}'") % { value: value, name: @name, allowed_values: values.join("', '") } end end end ���������������������������������������������������puppet-5.5.10/lib/puppet/settings/terminus_setting.rb�����������������������������������������������0000644�0052762�0001160�00000000511�13417161721�022662� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Settings::TerminusSetting < Puppet::Settings::BaseSetting def munge(value) case value when '', nil nil when String value.intern when Symbol value else raise Puppet::Settings::ValidationError, _("Invalid terminus setting: %{value}") % { value: value } end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/settings/ttl_setting.rb����������������������������������������������������0000644�0052762�0001160�00000003167�13417161721�021631� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# A setting that represents a span of time to live, and evaluates to Numeric # seconds to live where 0 means shortest possible time to live, a positive numeric value means time # to live in seconds, and the symbolic entry 'unlimited' is an infinite amount of time. # class Puppet::Settings::TTLSetting < Puppet::Settings::BaseSetting # How we convert from various units to seconds. UNITMAP = { # 365 days isn't technically a year, but is sufficient for most purposes "y" => 365 * 24 * 60 * 60, "d" => 24 * 60 * 60, "h" => 60 * 60, "m" => 60, "s" => 1 } # A regex describing valid formats with groups for capturing the value and units FORMAT = /^(\d+)(y|d|h|m|s)?$/ def type :ttl end # Convert the value to Numeric, parsing numeric string with units if necessary. def munge(value) self.class.munge(value, @name) end def print(value) val = munge(value) val == Float::INFINITY ? 'unlimited' : val end # Convert the value to Numeric, parsing numeric string with units if necessary. def self.munge(value, param_name) case when value.is_a?(Numeric) if value < 0 raise Puppet::Settings::ValidationError, _("Invalid negative 'time to live' %{value} - did you mean 'unlimited'?") % { value: value.inspect } end value when value == 'unlimited' Float::INFINITY when (value.is_a?(String) and value =~ FORMAT) $1.to_i * UNITMAP[$2 || 's'] else raise Puppet::Settings::ValidationError, _("Invalid 'time to live' format '%{value}' for parameter: %{param_name}") % { value: value.inspect, param_name: param_name } end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/settings/value_translator.rb�����������������������������������������������0000644�0052762�0001160�00000000620�13417161721�022645� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Convert arguments into booleans, integers, or whatever. class Puppet::Settings::ValueTranslator def [](value) # Handle different data types correctly return case value when /^false$/i; false when /^true$/i; true when /^\d+$/i; Integer(value) when true; true when false; false else value.gsub(/^["']|["']$/,'').sub(/\s+$/, '') end end end ����������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/settings/config_file.rb����������������������������������������������������0000644�0052762�0001160�00000011705�13417161721�021532� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/settings/ini_file' ## # @api private # # Parses puppet configuration files # class Puppet::Settings::ConfigFile ## # @param value_converter [Proc] a function that will convert strings into ruby types def initialize(value_converter) @value_converter = value_converter end # @param file [String, File] pointer to the file whose text we are parsing # @param text [String] the actual text of the inifile to be parsed # @param allowed_section_names [Array] an optional array of accepted section # names; if this list is non-empty, sections outside of it will raise an # error. # @return A Struct with a +sections+ array representing each configuration section def parse_file(file, text, allowed_section_names = []) result = Conf.new if !allowed_section_names.empty? allowed_section_names << 'main' unless allowed_section_names.include?('main') end # in Ruby 1.9.3 strings are not UTF-8 by default, so ensure text is treated properly ini = Puppet::Settings::IniFile.parse(StringIO.new(text).set_encoding(Encoding::UTF_8)) unique_sections_in(ini, file, allowed_section_names).each do |section_name| section = Section.new(section_name.to_sym) result.with_section(section) ini.lines_in(section_name).each do |line| if line.is_a?(Puppet::Settings::IniFile::SettingLine) parse_setting(line, section) elsif line.text !~ /^\s*#|^\s*$/ raise Puppet::Settings::ParseError.new(_("Could not match line %{text}") % { text: line.text }, file, line.line_number) end end end result end Conf = Struct.new(:sections) do def initialize super({}) end def with_section(section) sections[section.name] = section self end end Section = Struct.new(:name, :settings) do def initialize(name) super(name, []) end def with_setting(name, value, meta) settings << Setting.new(name, value, meta) self end def setting(name) settings.find { |setting| setting.name == name } end end Setting = Struct.new(:name, :value, :meta) do def has_metadata? meta != NO_META end end Meta = Struct.new(:owner, :group, :mode) NO_META = Meta.new(nil, nil, nil) private def unique_sections_in(ini, file, allowed_section_names) ini.section_lines.collect do |section| if !allowed_section_names.empty? && !allowed_section_names.include?(section.name) error_location_str = Puppet::Util::Errors.error_location(file, section.line_number) message = _("Illegal section '%{name}' in config file at %{error_location}.") % { name: section.name, error_location: error_location_str } #TRANSLATORS 'puppet.conf' is the name of the puppet configuration file and should not be translated. message += ' ' + _("The only valid puppet.conf sections are: [%{allowed_sections_list}].") % { allowed_sections_list: allowed_section_names.join(", ") } message += ' ' + _("Please use the directory environments feature to specify environments.") message += ' ' + _("(See https://puppet.com/docs/puppet/latest/environments_about.html)") raise(Puppet::Error, message) end section.name end.uniq end def parse_setting(setting, section) var = setting.name.intern # We don't want to munge modes, because they're specified in octal, so we'll # just leave them as a String, since Puppet handles that case correctly. if var == :mode value = setting.value else value = @value_converter[setting.value] end # Check to see if this is a file argument and it has extra options begin if value.is_a?(String) and options = extract_fileinfo(value) section.with_setting(var, options[:value], Meta.new(options[:owner], options[:group], options[:mode])) else section.with_setting(var, value, NO_META) end rescue Puppet::Error => detail raise Puppet::Settings::ParseError.new(detail.message, file, setting.line_number, detail) end end def empty_section { :_meta => {} } end def extract_fileinfo(string) result = {} value = string.sub(/\{\s*([^}]+)\s*\}/) do params = $1 params.split(/\s*,\s*/).each do |str| if str =~ /^\s*(\w+)\s*=\s*([\w]+)\s*$/ param, value = $1.intern, $2 result[param] = value unless [:owner, :mode, :group].include?(param) raise ArgumentError, _("Invalid file option '%{parameter}'") % { parameter: param } end if param == :mode and value !~ /^\d+$/ raise ArgumentError, _("File modes must be numbers") end else raise ArgumentError, _("Could not parse '%{string}'") % { string: string } end end '' end result[:value] = value.sub(/\s*$/, '') result end end �����������������������������������������������������������puppet-5.5.10/lib/puppet/settings/file_setting.rb���������������������������������������������������0000644�0052762�0001160�00000016050�13417161721�021740� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# A file. class Puppet::Settings::FileSetting < Puppet::Settings::StringSetting class SettingError < StandardError; end # An unspecified user or group # # @api private class Unspecified def value nil end end # A "root" user or group # # @api private class Root def value "root" end end # A "service" user or group that picks up values from settings when the # referenced user or group is safe to use (it exists or will be created), and # uses the given fallback value when not safe. # # @api private class Service # @param name [Symbol] the name of the setting to use as the service value # @param fallback [String, nil] the value to use when the service value cannot be used # @param settings [Puppet::Settings] the puppet settings object # @param available_method [Symbol] the name of the method to call on # settings to determine if the value in settings is available on the system # def initialize(name, fallback, settings, available_method) @settings = settings @available_method = available_method @name = name @fallback = fallback end def value if safe_to_use_settings_value? @settings[@name] else @fallback end end private def safe_to_use_settings_value? @settings[:mkusers] or @settings.send(@available_method) end end attr_accessor :mode, :create def initialize(args) @group = Unspecified.new @owner = Unspecified.new super(args) end # Should we create files, rather than just directories? def create_files? create end # @param value [String] the group to use on the created file (can only be "root" or "service") # @api public def group=(value) @group = case value when "root" Root.new when "service" # Group falls back to `nil` because we cannot assume that a "root" group exists. # Some systems have root group, others have wheel, others have something else. Service.new(:group, nil, @settings, :service_group_available?) else unknown_value(':group', value) end end # @param value [String] the owner to use on the created file (can only be "root" or "service") # @api public def owner=(value) @owner = case value when "root" Root.new when "service" Service.new(:user, "root", @settings, :service_user_available?) else unknown_value(':owner', value) end end # @return [String, nil] the name of the group to use for the file or nil if the group should not be managed # @api public def group @group.value end # @return [String, nil] the name of the user to use for the file or nil if the user should not be managed # @api public def owner @owner.value end def set_meta(meta) self.owner = meta.owner if meta.owner self.group = meta.group if meta.group self.mode = meta.mode if meta.mode end def munge(value) if value.is_a?(String) and value != ':memory:' # for sqlite3 in-memory tests value = File.expand_path(value) end value end def type :file end # Turn our setting thing into a Puppet::Resource instance. def to_resource return nil unless type = self.type path = self.value return nil unless path.is_a?(String) # Make sure the paths are fully qualified. path = File.expand_path(path) return nil unless type == :directory or create_files? or Puppet::FileSystem.exist?(path) return nil if path =~ /^\/dev/ or path =~ /^[A-Z]:\/dev/i resource = Puppet::Resource.new(:file, path) if Puppet[:manage_internal_file_permissions] if self.mode # This ends up mimicking the munge method of the mode # parameter to make sure that we're always passing the string # version of the octal number. If we were setting the # 'should' value for mode rather than the 'is', then the munge # method would be called for us automatically. Normally, one # wouldn't need to call the munge method manually, since # 'should' gets set by the provider and it should be able to # provide the data in the appropriate format. mode = self.mode mode = mode.to_i(8) if mode.is_a?(String) mode = mode.to_s(8) resource[:mode] = mode end # REMIND fails on Windows because chown/chgrp functionality not supported yet if Puppet.features.root? and !Puppet.features.microsoft_windows? resource[:owner] = self.owner if self.owner resource[:group] = self.group if self.group end end resource[:ensure] = type resource[:loglevel] = :debug resource[:links] = :follow resource[:backup] = false resource.tag(self.section, self.name, "settings") resource end # Make sure any provided variables look up to something. def validate(value) return true unless value.is_a? String value.scan(/\$(\w+)/) { |name| name = $1 unless @settings.include?(name) raise ArgumentError, _("Settings parameter '%{name}' is undefined") % { name: name } end } end # @api private # @param option [String] Extra file operation mode information to use # (defaults to read-only mode 'r') # This is the standard mechanism Ruby uses in the IO class, and therefore # encoding may be explicitly like fmode : encoding or fmode : "BOM|UTF-*" # for example, a:ASCII or w+:UTF-8 def exclusive_open(option = 'r', &block) controlled_access do |mode| Puppet::FileSystem.exclusive_open(file(), mode, option, &block) end end # @api private # @param option [String] Extra file operation mode information to use # (defaults to read-only mode 'r') # This is the standard mechanism Ruby uses in the IO class, and therefore # encoding may be explicitly like fmode : encoding or fmode : "BOM|UTF-*" # for example, a:ASCII or w+:UTF-8 def open(option = 'r', &block) controlled_access do |mode| Puppet::FileSystem.open(file, mode, option, &block) end end private def file Puppet::FileSystem.pathname(value) end def unknown_value(parameter, value) raise SettingError, _("The %{parameter} parameter for the setting '%{name}' must be either 'root' or 'service', not '%{value}'") % { parameter: parameter, name: name, value: value } end def controlled_access(&block) chown = nil if Puppet.features.root? chown = [owner, group] else chown = [nil, nil] end Puppet::Util::SUIDManager.asuser(*chown) do # Update the umask to make non-executable files Puppet::Util.withumask(File.umask ^ 0111) do yielded_value = case self.mode when String self.mode.to_i(8) when NilClass 0640 else self.mode end yield yielded_value end end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/ssl/�����������������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�015703� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/ssl/certificate_signer.rb��������������������������������������������������0000644�0052762�0001160�00000001642�13417161721�022057� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Take care of signing a certificate in a FIPS 140-2 compliant manner. # # @see https://projects.puppetlabs.com/issues/17295 # # @api private class Puppet::SSL::CertificateSigner # @!attribute [r] digest # @return [OpenSSL::Digest] attr_reader :digest def initialize if OpenSSL::Digest.const_defined?('SHA256') @digest = OpenSSL::Digest::SHA256 elsif OpenSSL::Digest.const_defined?('SHA1') @digest = OpenSSL::Digest::SHA1 elsif OpenSSL::Digest.const_defined?('SHA512') @digest = OpenSSL::Digest::SHA512 elsif OpenSSL::Digest.const_defined?('SHA384') @digest = OpenSSL::Digest::SHA384 elsif OpenSSL::Digest.const_defined?('SHA224') @digest = OpenSSL::Digest::SHA224 else raise Puppet::Error, "No FIPS 140-2 compliant digest algorithm in OpenSSL::Digest" end @digest end def sign(content, key) content.sign(key, @digest.new) end end ����������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/ssl/digest.rb��������������������������������������������������������������0000644�0052762�0001160�00000000515�13417161721�017503� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::SSL::Digest attr_reader :digest def initialize(algorithm, content) algorithm ||= 'SHA256' @digest = OpenSSL::Digest.new(algorithm, content) end def to_s "(#{name}) #{to_hex}" end def to_hex @digest.hexdigest.scan(/../).join(':').upcase end def name @digest.name.upcase end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/ssl/validator.rb�����������������������������������������������������������0000644�0052762�0001160�00000003102�13417161721�020204� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'openssl' # API for certificate verification # # @api public class Puppet::SSL::Validator # Factory method for creating an instance of a null/no validator. # This method does not have to be implemented by concrete implementations of this API. # # @return [Puppet::SSL::Validator] produces a validator that performs no validation # # @api public # def self.no_validator() @@no_validator_cache ||= Puppet::SSL::Validator::NoValidator.new() end # Factory method for creating an instance of the default Puppet validator. # This method does not have to be implemented by concrete implementations of this API. # # @return [Puppet::SSL::Validator] produces a validator that performs no validation # # @api public # def self.default_validator() Puppet::SSL::Validator::DefaultValidator.new() end # Array of peer certificates # @return [Array<Puppet::SSL::Certificate>] peer certificates # # @api public # def peer_certs raise NotImplementedError, "Concrete class should have implemented this method" end # Contains the result of validation # @return [Array<String>, nil] nil, empty Array, or Array with messages # # @api public # def verify_errors raise NotImplementedError, "Concrete class should have implemented this method" end # Registers the connection to validate. # # @param [Net::HTTP] connection The connection to validate # # @return [void] # # @api public # def setup_connection(connection) raise NotImplementedError, "Concrete class should have implemented this method" end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/ssl/validator/�������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�017670� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/ssl/validator/no_validator.rb����������������������������������������������0000644�0052762�0001160�00000000501�13417161721�022665� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'openssl' require 'puppet/ssl' # Performs no SSL verification # @api private # class Puppet::SSL::Validator::NoValidator < Puppet::SSL::Validator def setup_connection(connection) connection.verify_mode = OpenSSL::SSL::VERIFY_NONE end def peer_certs [] end def verify_errors [] end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/ssl/validator/default_validator.rb�����������������������������������������0000644�0052762�0001160�00000013473�13417161721�023711� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'openssl' require 'puppet/ssl' # Perform peer certificate verification against the known CA. # If there is no CA information known, then no verification is performed # # @api private # class Puppet::SSL::Validator::DefaultValidator #< class Puppet::SSL::Validator attr_reader :peer_certs attr_reader :verify_errors attr_reader :ssl_configuration FIVE_MINUTES_AS_SECONDS = 5 * 60 # Creates a new DefaultValidator, optionally with an SSL Configuration and SSL Host. # # @param ssl_configuration [Puppet::SSL::Configuration] (a default configuration) ssl_configuration the SSL configuration to use # @param ssl_host [Puppet::SSL::Host] The SSL host to use # # @api private # def initialize( ssl_configuration = Puppet::SSL::Configuration.new( Puppet[:localcacert], { :ca_auth_file => Puppet[:ssl_client_ca_auth] }), ssl_host = Puppet.lookup(:ssl_host)) reset! @ssl_configuration = ssl_configuration @ssl_host = ssl_host end # Resets this validator to its initial validation state. The ssl configuration is not changed. # # @api private # def reset! @peer_certs = [] @verify_errors = [] end # Performs verification of the SSL connection and collection of the # certificates for use in constructing the error message if the verification # failed. This callback will be executed once for each certificate in a # chain being verified. # # From the [OpenSSL # documentation](https://www.openssl.org/docs/ssl/SSL_CTX_set_verify.html): # The `verify_callback` function is used to control the behaviour when the # SSL_VERIFY_PEER flag is set. It must be supplied by the application and # receives two arguments: preverify_ok indicates, whether the verification of # the certificate in question was passed (preverify_ok=1) or not # (preverify_ok=0). x509_store_ctx is a pointer to the complete context used for # the certificate chain verification. # # See {Puppet::Network::HTTP::Connection} for more information and where this # class is intended to be used. # # @param [Boolean] preverify_ok indicates whether the verification of the # certificate in question was passed (preverify_ok=true) # @param [OpenSSL::X509::StoreContext] store_context holds the X509 store context # for the chain being verified. # # @return [Boolean] false if the peer is invalid, true otherwise. # # @api private # def call(preverify_ok, store_context) current_cert = store_context.current_cert @peer_certs << Puppet::SSL::Certificate.from_instance(current_cert) # We must make a copy since the scope of the store_context will be lost # across invocations of this method. if preverify_ok # If we've copied all of the certs in the chain out of the SSL library if @peer_certs.length == store_context.chain.length # (#20027) The peer cert must be issued by a specific authority preverify_ok = valid_peer? end else error = store_context.error || 0 error_string = store_context.error_string || "OpenSSL error #{error}" case error when OpenSSL::X509::V_ERR_CRL_NOT_YET_VALID # current_crl can be nil # https://github.com/ruby/ruby/blob/ruby_1_9_3/ext/openssl/ossl_x509store.c#L501-L510 crl = store_context.current_crl if crl if crl.last_update && crl.last_update < Time.now + FIVE_MINUTES_AS_SECONDS Puppet.debug("Ignoring CRL not yet valid, current time #{Time.now.utc}, CRL last updated #{crl.last_update.utc}") preverify_ok = true else @verify_errors << "#{error_string} for #{crl.issuer}" end else @verify_errors << error_string end else @verify_errors << "#{error_string} for #{current_cert.subject}" end end preverify_ok rescue => ex @verify_errors << ex.message false end # Registers the instance's call method with the connection. # # @param [Net::HTTP] connection The connection to validate # # @return [void] # # @api private # def setup_connection(connection) if ssl_certificates_are_present? connection.cert_store = @ssl_host.ssl_store connection.ca_file = @ssl_configuration.ca_auth_file connection.cert = @ssl_host.certificate.content connection.key = @ssl_host.key.content connection.verify_mode = OpenSSL::SSL::VERIFY_PEER connection.verify_callback = self else connection.verify_mode = OpenSSL::SSL::VERIFY_NONE end end # Validates the peer certificates against the authorized certificates. # # @api private # def valid_peer? descending_cert_chain = @peer_certs.reverse.map {|c| c.content } authz_ca_certs = ssl_configuration.ca_auth_certificates if not has_authz_peer_cert(descending_cert_chain, authz_ca_certs) msg = "The server presented a SSL certificate chain which does not include a " << "CA listed in the ssl_client_ca_auth file. " msg << "Authorized Issuers: #{authz_ca_certs.collect {|c| c.subject}.join(', ')} " << "Peer Chain: #{descending_cert_chain.collect {|c| c.subject}.join(' => ')}" @verify_errors << msg false else true end end # Checks if the set of peer_certs contains at least one certificate issued # by a certificate listed in authz_certs # # @return [Boolean] # # @api private # def has_authz_peer_cert(peer_certs, authz_certs) peer_certs.any? do |peer_cert| authz_certs.any? do |authz_cert| peer_cert.verify(authz_cert.public_key) end end end # @api private # def ssl_certificates_are_present? Puppet::FileSystem.exist?(Puppet[:hostcert]) && Puppet::FileSystem.exist?(@ssl_configuration.ca_auth_file) end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/ssl/base.rb����������������������������������������������������������������0000644�0052762�0001160�00000011333�13417161721�017136� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'openssl' require 'puppet/ssl' require 'puppet/ssl/digest' require 'puppet/util/ssl' # The base class for wrapping SSL instances. class Puppet::SSL::Base # For now, use the YAML separator. SEPARATOR = "\n---\n" # Only allow printing ascii characters, excluding / VALID_CERTNAME = /\A[ -.0-~]+\Z/ def self.from_multiple_s(text) text.split(SEPARATOR).collect { |inst| from_s(inst) } end def self.to_multiple_s(instances) instances.collect { |inst| inst.to_s }.join(SEPARATOR) end def self.wraps(klass) @wrapped_class = klass end def self.wrapped_class raise(Puppet::DevError, _("%{name} has not declared what class it wraps") % { name: self }) unless defined?(@wrapped_class) @wrapped_class end def self.validate_certname(name) raise _("Certname %{name} must not contain unprintable or non-ASCII characters") % { name: name.inspect } unless name =~ VALID_CERTNAME end attr_accessor :name, :content # Is this file for the CA? def ca? name == Puppet::SSL::Host.ca_name end def generate raise Puppet::DevError, _("%{class_name} did not override 'generate'") % { class_name: self.class } end def initialize(name) @name = name.to_s.downcase self.class.validate_certname(@name) end ## # name_from_subject extracts the common name attribute from the subject of an # x.509 certificate certificate # # @api private # # @param [OpenSSL::X509::Name] subject The full subject (distinguished name) of the x.509 # certificate. # # @return [String] the name (CN) extracted from the subject. def self.name_from_subject(subject) Puppet::Util::SSL.cn_from_subject(subject) end # Create an instance of our Puppet::SSL::* class using a given instance of the wrapped class def self.from_instance(instance, name = nil) unless instance.is_a?(wrapped_class) raise ArgumentError, _("Object must be an instance of %{class_name}, %{actual_class} given") % { class_name: wrapped_class, actual_class: instance.class } end if name.nil? and !instance.respond_to?(:subject) raise ArgumentError, _("Name must be supplied if it cannot be determined from the instance") end name ||= name_from_subject(instance.subject) result = new(name) result.content = instance result end # Convert a string into an instance def self.from_s(string, name = nil) instance = wrapped_class.new(string) from_instance(instance, name) end # Read content from disk appropriately. def read(path) # applies to Puppet::SSL::Certificate, Puppet::SSL::CertificateRequest, Puppet::SSL::CertificateRevocationList # Puppet::SSL::Key uses this, but also provides its own override # nothing derives from Puppet::SSL::Certificate, but it is called by a number of other SSL Indirectors: # Puppet::SSL::Certificate::DisabledCa (:find, :save, :destroy) # Puppet::Indirector::CertificateStatus::File (.indirection.find) # Puppet::Network::HTTP::WEBrick (.indirection.find) # Puppet::Network::HTTP::RackREST (.from_instance) # Puppet::Network::HTTP::WEBrickREST (.from_instance) # Puppet::SSL::CertificateAuthority (.new, .indirection.find, .indirection.save) # Puppet::SSL::Host (.indirection.find) # Puppet::SSL::Inventory (.indirection.search, implements its own add / rebuild / serials with encoding UTF8) # Puppet::SSL::CertificateAuthority::Interface (.indirection.find) # Puppet::SSL::Validator::DefaultValidator (.from_instance) / Puppet::SSL::Validator::NoValidator does nothing @content = wrapped_class.new(Puppet::FileSystem.read(path, :encoding => Encoding::ASCII)) end # Convert our thing to pem. def to_s return "" unless content content.to_pem end def to_data_hash to_s end # Provide the full text of the thing we're dealing with. def to_text return "" unless content content.to_text end def fingerprint(md = :SHA256) mds = md.to_s.upcase digest(mds).to_hex end def digest(algorithm=nil) unless algorithm algorithm = digest_algorithm end Puppet::SSL::Digest.new(algorithm, content.to_der) end def digest_algorithm # The signature_algorithm on the X509 cert is a combination of the digest # algorithm and the encryption algorithm # e.g. md5WithRSAEncryption, sha256WithRSAEncryption # Unfortunately there isn't a consistent pattern # See RFCs 3279, 5758 digest_re = Regexp.union( /ripemd160/i, /md[245]/i, /sha\d*/i ) ln = content.signature_algorithm if match = digest_re.match(ln) match[0].downcase else raise Puppet::Error, _("Unknown signature algorithm '%{ln}'") % { ln: ln } end end private def wrapped_class self.class.wrapped_class end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/ssl/certificate.rb���������������������������������������������������������0000644�0052762�0001160�00000005761�13417161721�020516� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/ssl/base' # Manage certificates themselves. This class has no # 'generate' method because the CA is responsible # for turning CSRs into certificates; we can only # retrieve them from the CA (or not, as is often # the case). class Puppet::SSL::Certificate < Puppet::SSL::Base # This is defined from the base class wraps OpenSSL::X509::Certificate extend Puppet::Indirector indirects :certificate, :terminus_class => :file, :doc => <<DOC This indirection wraps an `OpenSSL::X509::Certificate` object, representing a certificate (signed public key). The indirection key is the certificate CN (generally a hostname). DOC # Because of how the format handler class is included, this # can't be in the base class. def self.supported_formats [:s] end def subject_alt_names alts = content.extensions.find{|ext| ext.oid == "subjectAltName"} return [] unless alts alts.value.split(/\s*,\s*/) end def expiration return nil unless content content.not_after end # This name is what gets extracted from the subject before being passed # to the constructor, so it's not downcased def unmunged_name self.class.name_from_subject(content.subject) end # Any extensions registered with custom OIDs as defined in module # Puppet::SSL::Oids may be looked up here. # # A cert with a 'pp_uuid' extension having the value 'abcd' would return: # # [{ 'oid' => 'pp_uuid', 'value' => 'abcd'}] # # @return [Array<Hash{String => String}>] An array of two element hashes, # with key/value pairs for the extension's oid, and its value. def custom_extensions custom_exts = content.extensions.select do |ext| Puppet::SSL::Oids.subtree_of?('ppRegCertExt', ext.oid) or Puppet::SSL::Oids.subtree_of?('ppPrivCertExt', ext.oid) end custom_exts.map do |ext| {'oid' => ext.oid, 'value' => get_ext_val(ext.oid)} end end private # Extract the extensions sequence from the wrapped certificate's raw ASN.1 form def exts_seq # See RFC-2459 section 4.1 (https://tools.ietf.org/html/rfc2459#section-4.1) # to see where this is defined. Essentially this is saying "in the first # sequence in the certificate, find the item that's tagged with 3. This # is where the extensions are stored." @extensions_tag ||= 3 @exts_seq ||= OpenSSL::ASN1.decode(content.to_der).value[0].value.find do |data| (data.tag == @extensions_tag) && (data.tag_class == :CONTEXT_SPECIFIC) end.value[0] end # Get the DER parsed value of an X.509 extension by it's OID, or short name # if one has been registered with OpenSSL. def get_ext_val(oid) ext_obj = exts_seq.value.find do |ext_seq| ext_seq.value[0].value == oid end raw_val = ext_obj.value.last.value begin OpenSSL::ASN1.decode(raw_val).value rescue OpenSSL::ASN1::ASN1Error # This is required to maintain backward compatibility with the previous # way trusted facts were signed. See PUP-3560 raw_val end end end ���������������puppet-5.5.10/lib/puppet/ssl/certificate_authority.rb�����������������������������������������������0000644�0052762�0001160�00000046710�13417161721�022625� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/ssl/host' require 'puppet/ssl/certificate_request' require 'puppet/ssl/certificate_signer' require 'puppet/util' # The class that knows how to sign certificates. It creates # a 'special' SSL::Host whose name is 'ca', thus indicating # that, well, it's the CA. There's some magic in the # indirector/ssl_file terminus base class that does that # for us. # This class mostly just signs certs for us, but # it can also be seen as a general interface into all of the # SSL stuff. class Puppet::SSL::CertificateAuthority # We will only sign extensions on this whitelist, ever. Any CSR with a # requested extension that we don't recognize is rejected, against the risk # that it will introduce some security issue through our ignorance of it. # # Adding an extension to this whitelist simply means we will consider it # further, not that we will always accept a certificate with an extension # requested on this list. RequestExtensionWhitelist = %w{subjectAltName} require 'puppet/ssl/certificate_factory' require 'puppet/ssl/inventory' require 'puppet/ssl/certificate_revocation_list' require 'puppet/ssl/certificate_authority/interface' require 'puppet/ssl/certificate_authority/autosign_command' require 'puppet/network/authstore' class CertificateVerificationError < RuntimeError attr_accessor :error_code def initialize(code) @error_code = code end end def self.singleton_instance @singleton_instance ||= new end class CertificateSigningError < RuntimeError attr_accessor :host def initialize(host) @host = host end end def self.ca? # running as ca? - ensure boolean answer !!(Puppet[:ca] && Puppet.run_mode.master?) end # If this process can function as a CA, then return a singleton instance. def self.instance ca? ? singleton_instance : nil end attr_reader :name, :host # If autosign is configured, autosign the csr we are passed. # @param csr [Puppet::SSL::CertificateRequest] The csr to sign. # @return [Void] # @api private def autosign(csr) if autosign?(csr) Puppet.info _("Autosigning %{csr}") % { csr: csr.name } sign(csr.name) end end # Determine if a CSR can be autosigned by the autosign store or autosign command # # @param csr [Puppet::SSL::CertificateRequest] The CSR to check # @return [true, false] # @api private def autosign?(csr) auto = Puppet[:autosign] decider = case auto when false AutosignNever.new when true AutosignAlways.new else file = Puppet::FileSystem.pathname(auto) if Puppet::FileSystem.executable?(file) Puppet::SSL::CertificateAuthority::AutosignCommand.new(auto) elsif Puppet::FileSystem.exist?(file) AutosignConfig.new(file) else AutosignNever.new end end decider.allowed?(csr) end # Retrieves (or creates, if necessary) the certificate revocation list. def crl unless defined?(@crl) unless @crl = Puppet::SSL::CertificateRevocationList.indirection.find(Puppet::SSL::CA_NAME) @crl = Puppet::SSL::CertificateRevocationList.new(Puppet::SSL::CA_NAME) @crl.generate(host.certificate.content, host.key.content) Puppet::SSL::CertificateRevocationList.indirection.save(@crl) end end @crl end # Delegates this to our Host class. def destroy(name) Puppet::SSL::Host.destroy(name) end # Generates a new certificate. # @return Puppet::SSL::Certificate def generate(name, options = {}) raise ArgumentError, _("A Certificate already exists for %{name}") % { name: name } if Puppet::SSL::Certificate.indirection.find(name) # Pass on any requested subjectAltName field. san = options[:dns_alt_names] host = Puppet::SSL::Host.new(name) host.generate_certificate_request(:dns_alt_names => san) # CSR may have been implicitly autosigned, generating a certificate # Or sign explicitly host.certificate || sign(name, {allow_dns_alt_names: !!san}) end # Generate our CA certificate. def generate_ca_certificate generate_password unless password? host.generate_key unless host.key # Create a new cert request. We do this specially, because we don't want # to actually save the request anywhere. request = Puppet::SSL::CertificateRequest.new(host.name) # We deliberately do not put any subjectAltName in here: the CA # certificate absolutely does not need them. --daniel 2011-10-13 request.generate(host.key) # Create a self-signed certificate. @certificate = sign(host.name, {allow_dns_alt_names: false, self_signing_csr: request}) # And make sure we initialize our CRL. crl end def initialize Puppet.settings.use :main, :ssl, :ca @name = Puppet[:certname] @host = Puppet::SSL::Host.new(Puppet::SSL::Host.ca_name) setup end # Retrieve (or create, if necessary) our inventory manager. def inventory @inventory ||= Puppet::SSL::Inventory.new end # Generate a new password for the CA. def generate_password pass = "" 20.times { pass += (rand(74) + 48).chr } begin # random password is limited to ASCII characters 48 ('0') through 122 ('z') Puppet.settings.setting(:capass).open('w:ASCII') { |f| f.print pass } rescue Errno::EACCES => detail raise Puppet::Error, _("Could not write CA password: %{detail}") % { detail: detail }, detail.backtrace end @password = pass pass end # Lists the names of all signed certificates. # # @param name [Array<string>] filter to cerificate names # # @return [Array<String>] def list(name='*') Puppet::SSL::Certificate.indirection.search(name).collect { |c| c.name } end # Return all the certificate objects as found by the indirector # API for PE license checking. # # Created to prevent the case of reading all certs from disk, getting # just their names and verifying the cert for each name, which then # causes the cert to again be read from disk. # # @author Jeff Weiss <jeff.weiss@puppetlabs.com> # @api Puppet Enterprise Licensing # # @param name [Array<string>] filter to cerificate names # # @return [Array<Puppet::SSL::Certificate>] # # @deprecated Use Puppet::SSL::CertificateAuthority#list or Puppet Server Certificate status API def list_certificates(name='*') Puppet.deprecation_warning(_("Puppet::SSL::CertificateAuthority#list_certificates is deprecated. Please use Puppet::SSL::CertificateAuthority#list or the certificate status API to query certificate information. See https://puppet.com/docs/puppet/latest/http_api/http_certificate_status.html")) Puppet::SSL::Certificate.indirection.search(name) end # Read the next serial from the serial file, and increment the # file so this one is considered used. def next_serial serial = 1 # the serial is 4 hex digits - limited to ASCII Puppet.settings.setting(:serial).exclusive_open('a+:ASCII') do |f| f.rewind serial = f.read.chomp.hex if serial == 0 serial = 1 end f.truncate(0) f.rewind # We store the next valid serial, not the one we just used. f << "%04X" % (serial + 1) end serial end # Does the password file exist? def password? Puppet::FileSystem.exist?(Puppet[:capass]) end # Print a given host's certificate as text. def print(name) (cert = Puppet::SSL::Certificate.indirection.find(name)) ? cert.to_text : nil end # Revoke a given certificate. def revoke(name) raise ArgumentError, _("Cannot revoke certificates when the CRL is disabled") unless crl cert = Puppet::SSL::Certificate.indirection.find(name) serials = if cert [cert.content.serial] elsif name =~ /^0x[0-9A-Fa-f]+$/ [name.hex] else inventory.serials(name) end if serials.empty? raise ArgumentError, _("Could not find a serial number for %{name}") % { name: name } end serials.each do |s| crl.revoke(s, host.key.content) end end # This initializes our CA so it actually works. This should be a private # method, except that you can't any-instance stub private methods, which is # *awesome*. This method only really exists to provide a stub-point during # testing. def setup generate_ca_certificate unless @host.certificate end # Sign a given certificate request. def sign(hostname, options={}) options[:allow_authorization_extensions] ||= false options[:allow_dns_alt_names] ||= false options[:self_signing_csr] ||= nil self_signing_csr = options.delete(:self_signing_csr) if self_signing_csr # # This is a self-signed certificate, which is for the CA. Since this # # forces the certificate to be self-signed, anyone who manages to trick # # the system into going through this path gets a certificate they could # # generate anyway. There should be no security risk from that. csr = self_signing_csr cert_type = :ca issuer = csr.content else unless csr = Puppet::SSL::CertificateRequest.indirection.find(hostname) raise ArgumentError, _("Could not find certificate request for %{hostname}") % { hostname: hostname } end cert_type = :server issuer = host.certificate.content # Make sure that the CSR conforms to our internal signing policies. # This will raise if the CSR doesn't conform, but just in case... check_internal_signing_policies(hostname, csr, options) or raise CertificateSigningError.new(hostname), _("CSR had an unknown failure checking internal signing policies, will not sign!") end cert = Puppet::SSL::Certificate.new(hostname) cert.content = Puppet::SSL::CertificateFactory. build(cert_type, csr, issuer, next_serial) signer = Puppet::SSL::CertificateSigner.new signer.sign(cert.content, host.key.content) Puppet.notice _("Signed certificate request for %{hostname}") % { hostname: hostname } # Add the cert to the inventory before we save it, since # otherwise we could end up with it being duplicated, if # this is the first time we build the inventory file. inventory.add(cert) # Save the now-signed cert. This should get routed correctly depending # on the certificate type. Puppet::SSL::Certificate.indirection.save(cert) # And remove the CSR if this wasn't self signed. Puppet::SSL::CertificateRequest.indirection.destroy(csr.name) unless self_signing_csr cert end def check_internal_signing_policies(hostname, csr, options = {}) options[:allow_authorization_extensions] ||= false options[:allow_dns_alt_names] ||= false # This allows for masters to bootstrap themselves in certain scenarios options[:allow_dns_alt_names] = true if hostname == Puppet[:certname].downcase # Reject unknown request extensions. unknown_req = csr.request_extensions.reject do |x| RequestExtensionWhitelist.include? x["oid"] or Puppet::SSL::Oids.subtree_of?('ppRegCertExt', x["oid"], true) or Puppet::SSL::Oids.subtree_of?('ppPrivCertExt', x["oid"], true) or Puppet::SSL::Oids.subtree_of?('ppAuthCertExt', x["oid"], true) end if unknown_req and not unknown_req.empty? names = unknown_req.map {|x| x["oid"] }.sort.uniq.join(", ") raise CertificateSigningError.new(hostname), _("CSR has request extensions that are not permitted: %{names}") % { names: names } end # Do not sign misleading CSRs cn = csr.content.subject.to_a.assoc("CN")[1] if hostname != cn raise CertificateSigningError.new(hostname), _("CSR subject common name %{name} does not match expected certname %{expected}") % { name: cn.inspect, expected: hostname.inspect } end if hostname !~ Puppet::SSL::Base::VALID_CERTNAME raise CertificateSigningError.new(hostname), _("CSR %{hostname} subject contains unprintable or non-ASCII characters") % { hostname: hostname.inspect } end # Wildcards: we don't allow 'em at any point. # # The stringification here makes the content visible, and saves us having # to scrobble through the content of the CSR subject field to make sure it # is what we expect where we expect it. if csr.content.subject.to_s.include? '*' raise CertificateSigningError.new(hostname), _("CSR subject contains a wildcard, which is not allowed: %{subject}") % { subject: csr.content.subject.to_s } end unless csr.content.verify(csr.content.public_key) raise CertificateSigningError.new(hostname), _("CSR contains a public key that does not correspond to the signing key") end auth_extensions = csr.request_extensions.select do |extension| Puppet::SSL::Oids.subtree_of?('ppAuthCertExt', extension['oid'], true) end if auth_extensions.any? && !options[:allow_authorization_extensions] ext_names = auth_extensions.map do |extension| extension['oid'] end raise CertificateSigningError.new(hostname), _("CSR '%{csr}' contains authorization extensions (%{extensions}), which are disallowed by default. Use `puppet cert --allow-authorization-extensions sign %{csr}` to sign this request.") % { csr: csr.name, extensions: ext_names.join(', ') } end unless csr.subject_alt_names.empty? # If you alt names are allowed, they are required. Otherwise they are # disallowed. Self-signed certs are implicitly trusted, however. unless options[:allow_dns_alt_names] raise CertificateSigningError.new(hostname), _("CSR '%{csr}' contains subject alternative names (%{alt_names}), which are disallowed. Use `puppet cert --allow-dns-alt-names sign %{csr}` to sign this request.") % { csr: csr.name, alt_names: csr.subject_alt_names.join(', ') } end # If subjectAltNames are present, validate that they are only for DNS # labels, not any other kind. unless csr.subject_alt_names.all? {|x| x =~ /^DNS:/ } raise CertificateSigningError.new(hostname), _("CSR '%{csr}' contains a subjectAltName outside the DNS label space: %{alt_names}. To continue, this CSR needs to be cleaned.") % { csr: csr.name, alt_names: csr.subject_alt_names.join(', ') } end # Check for wildcards in the subjectAltName fields too. if csr.subject_alt_names.any? {|x| x.include? '*' } raise CertificateSigningError.new(hostname), _("CSR '%{csr}' subjectAltName contains a wildcard, which is not allowed: %{alt_names}. To continue, this CSR needs to be cleaned.") % { csr: csr.name, alt_names: csr.subject_alt_names.join(', ') } end end return true # good enough for us! end # Utility method for optionally caching the X509 Store for verifying a # large number of certificates in a short amount of time--exactly the # case we have during PE license checking. # # @example Use the cached X509 store # x509store(:cache => true) # # @example Use a freshly create X509 store # x509store # x509store(:cache => false) # # @param [Hash] options the options used for retrieving the X509 Store # @option options [Boolean] :cache whether or not to use a cached version # of the X509 Store # # @return [OpenSSL::X509::Store] # # @deprecated Strictly speaking, #x509_store is marked API private, so we # don't need to publicly deprecate it. But it marked as deprecated here to # avoid the exceedingly small chance that someone comes in and uses it from # within this class before it is removed. def x509_store(options = {}) if (options[:cache]) return @x509store unless @x509store.nil? @x509store = create_x509_store else create_x509_store end end private :x509_store # Creates a brand new OpenSSL::X509::Store with the appropriate # Certificate Revocation List and flags # # @return [OpenSSL::X509::Store] def create_x509_store store = OpenSSL::X509::Store.new() store.add_file(Puppet[:cacert]) store.add_crl(crl.content) if self.crl store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT if Puppet.lookup(:certificate_revocation) store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL | OpenSSL::X509::V_FLAG_CRL_CHECK end store end private :create_x509_store # Utility method which is API for PE license checking. # This is used rather than `verify` because # 1) We have already read the certificate from disk into memory. # To read the certificate from disk again is just wasteful. # 2) Because we're checking a large number of certificates against # a transient CertificateAuthority, we can relatively safely cache # the X509 Store that actually does the verification. # # Long running instances of CertificateAuthority will certainly # want to use `verify` because it will recreate the X509 Store with # the absolutely latest CRL. # # Additionally, this method explicitly returns a boolean whereas # `verify` will raise an error if the certificate has been revoked. # # @author Jeff Weiss <jeff.weiss@puppetlabs.com> # @api Puppet Enterprise Licensing # # @param cert [Puppet::SSL::Certificate] the certificate to check validity of # # @return [Boolean] true if signed, false if unsigned or revoked # # @deprecated use Puppet::SSL::CertificateAuthority#verify or Puppet Server certificate status API def certificate_is_alive?(cert) Puppet.deprecation_warning(_("Puppet::SSL::CertificateAuthority#certificate_is_alive? is deprecated. Please use Puppet::SSL::CertificateAuthority#verify or the certificate status API to query certificate information. See https://puppet.com/docs/puppet/latest/http_api/http_certificate_status.html")) x509_store(:cache => true).verify(cert.content) end # Verify a given host's certificate. The certname is passed in, and # the indirector will be used to locate the actual contents of the # certificate with that name. # # @param name [String] certificate name to verify # # @raise [ArgumentError] if the certificate name cannot be found # (i.e. doesn't exist or is unsigned) # @raise [CertificateVerficationError] if the certificate has been revoked # # @return [Boolean] true if signed, there are no cases where false is returned def verify(name) unless cert = Puppet::SSL::Certificate.indirection.find(name) raise ArgumentError, _("Could not find a certificate for %{name}") % { name: name } end store = create_x509_store raise CertificateVerificationError.new(store.error), store.error_string unless store.verify(cert.content) end def fingerprint(name, md = :SHA256) unless cert = Puppet::SSL::Certificate.indirection.find(name) || Puppet::SSL::CertificateRequest.indirection.find(name) raise ArgumentError, _("Could not find a certificate or csr for %{name}") % { name: name } end cert.fingerprint(md) end # List the waiting certificate requests. def waiting? Puppet::SSL::CertificateRequest.indirection.search("*").collect { |r| r.name } end # @api private class AutosignAlways def allowed?(csr) true end end # @api private class AutosignNever def allowed?(csr) false end end # @api private class AutosignConfig def initialize(config_file) @config = config_file end def allowed?(csr) autosign_store.allowed?(csr.name, '127.1.1.1') end private def autosign_store auth = Puppet::Network::AuthStore.new Puppet::FileSystem.each_line(@config) do |line| next if line =~ /^\s*#/ next if line =~ /^\s*$/ auth.allow(line.chomp) end auth end end end ��������������������������������������������������������puppet-5.5.10/lib/puppet/ssl/certificate_authority/�������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�022275� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/ssl/certificate_authority/autosign_command.rb������������������������������0000644�0052762�0001160�00000002300�13417161721�026137� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/ssl/certificate_authority' require 'puppet/file_system/uniquefile' # This class wraps a given command and invokes it with a CSR name and body to # determine if the given CSR should be autosigned # # @api private class Puppet::SSL::CertificateAuthority::AutosignCommand class CheckFailure < Puppet::Error; end def initialize(path) @path = path end # Run the autosign command with the given CSR name as an argument and the # CSR body on stdin. # # @param csr [String] The CSR name to check for autosigning # @return [true, false] If the CSR should be autosigned def allowed?(csr) name = csr.name cmd = [@path, name] output = Puppet::FileSystem::Uniquefile.open_tmp('puppet-csr') do |csr_file| csr_file.write(csr.to_s) csr_file.flush execute_options = {:stdinfile => csr_file.path, :combine => true, :failonfail => false} Puppet::Util::Execution.execute(cmd, execute_options) end output.chomp! Puppet.debug "Autosign command '#{@path}' exit status: #{output.exitstatus}" Puppet.debug "Autosign command '#{@path}' output: #{output}" case output.exitstatus when 0 true else false end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/ssl/certificate_authority/interface.rb�������������������������������������0000644�0052762�0001160�00000026123�13417161721�024561� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet module SSL class CertificateAuthority # This class is basically a hidden class that knows how to act on the # CA. Its job is to provide a CLI-like interface to the CA class. class Interface INTERFACE_METHODS = [:destroy, :list, :revoke, :generate, :sign, :print, :verify, :fingerprint, :reinventory] DESTRUCTIVE_METHODS = [:destroy, :revoke] SUBJECTLESS_METHODS = [:list, :reinventory] CERT_STATUS_GLYPHS = {:signed => '+', :request => ' ', :invalid => '-'} VALID_CONFIRMATION_VALUES = %w{y Y yes Yes YES} class InterfaceError < ArgumentError; end attr_reader :method, :subjects, :digest, :options # Actually perform the work. def apply(ca) unless subjects || SUBJECTLESS_METHODS.include?(method) raise ArgumentError, _("You must provide hosts or --all when using %{method}") % { method: method } end destructive_subjects = [:signed, :all].include?(subjects) if DESTRUCTIVE_METHODS.include?(method) && destructive_subjects subject_text = (subjects == :all ? subjects : _("all signed")) raise ArgumentError, _("Refusing to %{method} %{subject_text} certs, provide an explicit list of certs to %{method}") % { method: method, subject_text: subject_text } end # if the interface implements the method, use it instead of the ca's method if respond_to?(method) send(method, ca) else (subjects == :all ? ca.list : subjects).each do |host| ca.send(method, host) end end end def generate(ca) raise InterfaceError, _("It makes no sense to generate all hosts; you must specify a list") if subjects == :all subjects.each do |host| ca.generate(host, options) end end def initialize(method, options) self.method = method self.subjects = options.delete(:to) @digest = options.delete(:digest) @options = options end # List the hosts. def list(ca) signed = ca.list if [:signed, :all].include?(subjects) requests = ca.waiting? case subjects when :all hosts = [signed, requests].flatten when :signed hosts = signed.flatten when nil hosts = requests else hosts = subjects signed = ca.list(hosts) end certs = {:signed => {}, :invalid => {}, :request => {}} return if hosts.empty? hosts.uniq.sort.each do |host| verify_error = nil begin ca.verify(host) unless requests.include?(host) rescue Puppet::SSL::CertificateAuthority::CertificateVerificationError => details verify_error = "(#{details.to_s})" end if verify_error type = :invalid cert = Puppet::SSL::Certificate.indirection.find(host) elsif (signed and signed.include?(host)) type = :signed cert = Puppet::SSL::Certificate.indirection.find(host) else type = :request cert = Puppet::SSL::CertificateRequest.indirection.find(host) end certs[type][host] = { :cert => cert, :type => type, :verify_error => verify_error, } end names = certs.values.map(&:keys).flatten name_width = names.sort_by(&:length).last.length rescue 0 # We quote these names, so account for those characters name_width += 2 output = [:request, :signed, :invalid].map do |type| next if certs[type].empty? certs[type].map do |host, info| format_host(host, info, name_width, options[:format]) end end.flatten.compact.sort.join("\n") puts output end def format_host(host, info, width, format) case format when :machine machine_host_formatting(host, info) when :human human_host_formatting(host, info) else if options[:verbose] machine_host_formatting(host, info) else legacy_host_formatting(host, info, width) end end end def machine_host_formatting(host, info) type = info[:type] verify_error = info[:verify_error] cert = info[:cert] alt_names = cert.subject_alt_names - [host] extensions = format_attrs_and_exts(cert) glyph = CERT_STATUS_GLYPHS[type] name = host.inspect fingerprint = cert.digest(@digest).to_s expiration = cert.expiration.iso8601 if type == :signed if type != :invalid if !alt_names.empty? extensions.unshift("alt names: #{alt_names.map(&:inspect).join(', ')}") end if !extensions.empty? metadata_string = "(#{extensions.join(', ')})" unless extensions.empty? end end [glyph, name, fingerprint, expiration, metadata_string, verify_error].compact.join(' ') end def human_host_formatting(host, info) type = info[:type] verify_error = info[:verify_error] cert = info[:cert] alt_names = cert.subject_alt_names - [host] extensions = format_attrs_and_exts(cert) glyph = CERT_STATUS_GLYPHS[type] fingerprint = cert.digest(@digest).to_s if type == :invalid || (extensions.empty? && alt_names.empty?) extension_string = '' else if !alt_names.empty? extensions.unshift("alt names: #{alt_names.map(&:inspect).join(', ')}") end extension_string = "\n Extensions:\n " extension_string << extensions.join("\n ") end if type == :signed expiration_string = "\n Expiration: #{cert.expiration.iso8601}" else expiration_string = '' end status = case type when :invalid then "Invalid - #{verify_error}" when :request then "Request Pending" when :signed then "Signed" end output = "#{glyph} #{host.inspect}" output << "\n #{fingerprint}" output << "\n Status: #{status}" output << expiration_string output << extension_string output << "\n" output end def legacy_host_formatting(host, info, width) type = info[:type] verify_error = info[:verify_error] cert = info[:cert] alt_names = cert.subject_alt_names - [host] extensions = format_attrs_and_exts(cert) glyph = CERT_STATUS_GLYPHS[type] name = host.inspect.ljust(width) fingerprint = cert.digest(@digest).to_s if type != :invalid if alt_names.empty? alt_name_string = nil else alt_name_string = "(alt names: #{alt_names.map(&:inspect).join(', ')})" end if extensions.empty? extension_string = nil else extension_string = "**" end end [glyph, name, fingerprint, alt_name_string, verify_error, extension_string].compact.join(' ') end def format_attrs_and_exts(cert) exts = [] exts += cert.custom_extensions if cert.respond_to?(:custom_extensions) exts += cert.custom_attributes if cert.respond_to?(:custom_attributes) exts += cert.request_extensions if cert.respond_to?(:request_extensions) exts.map {|e| "#{e['oid']}: #{e['value'].inspect}" }.sort end # Set the method to apply. def method=(method) raise ArgumentError, "Invalid method #{method} to apply" unless INTERFACE_METHODS.include?(method) @method = method end # Print certificate information. def print(ca) (subjects == :all ? ca.list : subjects).each do |host| if value = ca.print(host) puts value else raise ArgumentError, _("Could not find certificate for %{host}") % { host: host } end end end # Print certificate information. def fingerprint(ca) (subjects == :all ? ca.list + ca.waiting?: subjects).each do |host| if cert = (Puppet::SSL::Certificate.indirection.find(host) || Puppet::SSL::CertificateRequest.indirection.find(host)) puts "#{host} #{cert.digest(@digest)}" else raise ArgumentError, _("Could not find certificate for %{host}") % { host: host } end end end # Signs given certificates or all waiting if subjects == :all def sign(ca) list = subjects == :all ? ca.waiting? : subjects raise InterfaceError, _("No waiting certificate requests to sign") if list.empty? signing_options = options.select { |k,_| [:allow_authorization_extensions, :allow_dns_alt_names].include?(k) } list.each do |host| cert = Puppet::SSL::CertificateRequest.indirection.find(host) raise InterfaceError, _("Could not find CSR for: %{host}.") % { host: host.inspect } unless cert # ca.sign will also do this - and it should if it is called # elsewhere - but we want to reject an attempt to sign a # problematic csr as early as possible for usability concerns. ca.check_internal_signing_policies(host, cert, signing_options) name_width = host.inspect.length info = {:type => :request, :cert => cert} host_string = format_host(host, info, name_width, options[:format]) puts _("Signing Certificate Request for:\n%{host_string}") % { host_string: host_string } if options[:interactive] STDOUT.print _("Sign Certificate Request? [y/N] ") if !options[:yes] input = STDIN.gets.chomp raise InterfaceError, _("NOT Signing Certificate Request") unless VALID_CONFIRMATION_VALUES.include?(input) else puts _("Assuming YES from `-y' or `--assume-yes' flag") end end ca.sign(host, signing_options) end end def reinventory(ca) ca.inventory.rebuild end # Set the list of hosts we're operating on. Also supports keywords. def subjects=(value) unless value == :all || value == :signed || value.is_a?(Array) raise ArgumentError, _("Subjects must be an array or :all; not %{value}") % { value: value } end @subjects = (value == []) ? nil : value end end end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/ssl/certificate_factory.rb�������������������������������������������������0000644�0052762�0001160�00000021627�13417161721�022244� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/ssl' # This class encapsulates the logic of creating and adding extensions to X509 # certificates. # # @api private module Puppet::SSL::CertificateFactory # Create a new X509 certificate and add any needed extensions to the cert. # # @param cert_type [Symbol] The certificate type to create, which specifies # what extensions are added to the certificate. # One of (:ca, :terminalsubca, :server, :ocsp, :client) # @param csr [Puppet::SSL::CertificateRequest] The signing request associated with # the certificate being created. # @param issuer [OpenSSL::X509::Certificate, OpenSSL::X509::Request] An X509 CSR # if this is a self signed certificate, or the X509 certificate of the CA if # this is a CA signed certificate. # @param serial [Integer] The serial number for the given certificate, which # MUST be unique for the given CA. # @param ttl [String] The duration of the validity for the given certificate. # defaults to Puppet[:ca_ttl] # # @api public # # @return [OpenSSL::X509::Certificate] def self.build(cert_type, csr, issuer, serial, ttl = nil) # Work out if we can even build the requested type of certificate. build_extensions = "build_#{cert_type.to_s}_extensions" respond_to?(build_extensions) or raise ArgumentError, _("%{cert_type} is an invalid certificate type!") % { cert_type: cert_type.to_s } raise ArgumentError, _("Certificate TTL must be an integer") unless ttl.nil? || ttl.is_a?(Integer) # set up the certificate, and start building the content. cert = OpenSSL::X509::Certificate.new cert.version = 2 # X509v3 cert.subject = csr.content.subject cert.issuer = issuer.subject cert.public_key = csr.content.public_key cert.serial = serial # Make the certificate valid as of yesterday, because so many people's # clocks are out of sync. This gives one more day of validity than people # might expect, but is better than making every person who has a messed up # clock fail, and better than having every cert we generate expire a day # before the user expected it to when they asked for "one year". cert.not_before = Time.now - (60*60*24) cert.not_after = Time.now + (ttl || Puppet[:ca_ttl]) add_extensions_to(cert, csr, issuer, send(build_extensions)) return cert end # Add X509v3 extensions to the given certificate. # # @param cert [OpenSSL::X509::Certificate] The certificate to add the # extensions to. # @param csr [OpenSSL::X509::Request] The CSR associated with the given # certificate, which may specify requested extensions for the given cert. # See https://tools.ietf.org/html/rfc2985 Section 5.4.2 Extension request # @param issuer [OpenSSL::X509::Certificate, OpenSSL::X509::Request] An X509 CSR # if this is a self signed certificate, or the X509 certificate of the CA if # this is a CA signed certificate. # @param extensions [Hash<String, Array<String> | String>] The extensions to # add to the certificate, based on the certificate type being created (CA, # server, client, etc) # # @api private # # @return [void] def self.add_extensions_to(cert, csr, issuer, extensions) ef = OpenSSL::X509::ExtensionFactory.new ef.subject_certificate = cert ef.issuer_certificate = issuer.is_a?(OpenSSL::X509::Request) ? cert : issuer # Extract the requested extensions from the CSR. requested_exts = csr.request_extensions.inject({}) do |hash, re| hash[re["oid"]] = [re["value"], re["critical"]] hash end # Produce our final set of extensions. We deliberately order these to # build the way we want: # 1. "safe" default values, like the comment, that no one cares about. # 2. request extensions, from the CSR # 3. extensions based on the type we are generating # 4. overrides, which we always want to have in their form # # This ordering *is* security-critical, but we want to allow the user # enough rope to shoot themselves in the foot, if they want to ignore our # advice and externally approve a CSR that sets the basicConstraints. # # Swapping the order of 2 and 3 would ensure that you couldn't slip a # certificate through where the CA constraint was true, though, if # something went wrong up there. --daniel 2011-10-11 defaults = { "nsComment" => "Puppet Ruby/OpenSSL Internal Certificate" } # See https://www.openssl.org/docs/apps/x509v3_config.html # for information about the special meanings of 'hash', 'keyid', 'issuer' override = { "subjectKeyIdentifier" => "hash", "authorityKeyIdentifier" => "keyid,issuer" } exts = [defaults, requested_exts, extensions, override]. inject({}) {|ret, val| ret.merge(val) } cert.extensions = exts.map do |oid, val| generate_extension(ef, oid, *val) end end private_class_method :add_extensions_to # Woot! We're a CA. def self.build_ca_extensions { # This was accidentally omitted in the previous version of this code: an # effort was made to add it last, but that actually managed to avoid # adding it to the certificate at all. # # We have some sort of bug, which means that when we add it we get a # complaint that the issuer keyid can't be fetched, which breaks all # sorts of things in our test suite and, e.g., bootstrapping the CA. # # https://tools.ietf.org/html/rfc5280#section-4.2.1.1 says that, to be a # conforming CA we MAY omit the field if we are self-signed, which I # think gives us a pass in the specific case. # # It also notes that we MAY derive the ID from the subject and serial # number of the issuer, or from the key ID, and we definitely have the # former data, should we want to restore this... # # Anyway, preserving this bug means we don't risk breaking anything in # the field, even though it would be nice to have. --daniel 2011-10-11 # # "authorityKeyIdentifier" => "keyid:always,issuer:always", "keyUsage" => [%w{cRLSign keyCertSign}, true], "basicConstraints" => ["CA:TRUE", true], } end # We're a terminal CA, probably not self-signed. def self.build_terminalsubca_extensions { "keyUsage" => [%w{cRLSign keyCertSign}, true], "basicConstraints" => ["CA:TRUE,pathlen:0", true], } end # We're a normal server. def self.build_server_extensions { "keyUsage" => [%w{digitalSignature keyEncipherment}, true], "extendedKeyUsage" => [%w{serverAuth clientAuth}, true], "basicConstraints" => ["CA:FALSE", true], } end # Um, no idea. def self.build_ocsp_extensions { "keyUsage" => [%w{nonRepudiation digitalSignature}, true], "extendedKeyUsage" => [%w{serverAuth OCSPSigning}, true], "basicConstraints" => ["CA:FALSE", true], } end # Normal client. def self.build_client_extensions { "keyUsage" => [%w{nonRepudiation digitalSignature keyEncipherment}, true], # We don't seem to use this, but that seems much more reasonable here... "extendedKeyUsage" => [%w{clientAuth emailProtection}, true], "basicConstraints" => ["CA:FALSE", true], "nsCertType" => "client,email", } end # Generate an extension with the given OID, value, and critical state # # @param oid [String] The numeric value or short name of a given OID. X509v3 # extensions must be passed by short name or long name, while custom # extensions may be passed by short name, long name, oid numeric OID. # @param ef [OpenSSL::X509::ExtensionFactory] The extension factory to use # when generating the extension. # @param val [String, Array<String>] The extension value. # @param crit [true, false] Whether the given extension is critical, defaults # to false. # # @return [OpenSSL::X509::Extension] # # @api private def self.generate_extension(ef, oid, val, crit = false) val = val.join(', ') unless val.is_a? String # Enforce the X509v3 rules about subjectAltName being critical: # specifically, it SHOULD NOT be critical if we have a subject, which we # always do. --daniel 2011-10-18 crit = false if oid == "subjectAltName" if Puppet::SSL::Oids.subtree_of?('id-ce', oid) or Puppet::SSL::Oids.subtree_of?('id-pkix', oid) # Attempt to create a X509v3 certificate extension. Standard certificate # extensions may need access to the associated subject certificate and # issuing certificate, so must be created by the OpenSSL::X509::ExtensionFactory # which provides that context. ef.create_ext(oid, val, crit) else # This is not an X509v3 extension which means that the extension # factory cannot generate it. We need to generate the extension # manually. OpenSSL::X509::Extension.new(oid, OpenSSL::ASN1::UTF8String.new(val).to_der, crit) end end private_class_method :generate_extension end ���������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/ssl/certificate_request.rb�������������������������������������������������0000644�0052762�0001160�00000031265�13417161721�022264� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/ssl/base' require 'puppet/ssl/certificate_signer' # This class creates and manages X509 certificate signing requests. # # ## CSR attributes # # CSRs may contain a set of attributes that includes supplementary information # about the CSR or information for the signed certificate. # # PKCS#9/RFC 2985 section 5.4 formally defines the "Challenge password", # "Extension request", and "Extended-certificate attributes", but this # implementation only handles the "Extension request" attribute. Other # attributes may be defined on a CSR, but the RFC doesn't define behavior for # any other attributes so we treat them as only informational. # # ## CSR Extension request attribute # # CSRs may contain an optional set of extension requests, which allow CSRs to # include additional information that may be included in the signed # certificate. Any additional information that should be copied from the CSR # to the signed certificate MUST be included in this attribute. # # This behavior is dictated by PKCS#9/RFC 2985 section 5.4.2. # # @see https://tools.ietf.org/html/rfc2985 "RFC 2985 Section 5.4.2 Extension request" # class Puppet::SSL::CertificateRequest < Puppet::SSL::Base wraps OpenSSL::X509::Request extend Puppet::Indirector # If auto-signing is on, sign any certificate requests as they are saved. module AutoSigner def save(instance, key = nil) super # Try to autosign the CSR. if ca = Puppet::SSL::CertificateAuthority.instance ca.autosign(instance) end end end indirects :certificate_request, :terminus_class => :file, :extend => AutoSigner, :doc => <<DOC This indirection wraps an `OpenSSL::X509::Request` object, representing a certificate signing request (CSR). The indirection key is the certificate CN (generally a hostname). DOC # Because of how the format handler class is included, this # can't be in the base class. def self.supported_formats [:s] end def extension_factory @ef ||= OpenSSL::X509::ExtensionFactory.new end # Create a certificate request with our system settings. # # @param key [OpenSSL::X509::Key, Puppet::SSL::Key] The key pair associated # with this CSR. # @param options [Hash] # @option options [String] :dns_alt_names A comma separated list of # Subject Alternative Names to include in the CSR extension request. # @option options [Hash<String, String, Array<String>>] :csr_attributes A hash # of OIDs and values that are either a string or array of strings. # @option options [Array<String, String>] :extension_requests A hash of # certificate extensions to add to the CSR extReq attribute, excluding # the Subject Alternative Names extension. # # @raise [Puppet::Error] If the generated CSR signature couldn't be verified # # @return [OpenSSL::X509::Request] The generated CSR def generate(key, options = {}) Puppet.info _("Creating a new SSL certificate request for %{name}") % { name: name } # Support either an actual SSL key, or a Puppet key. key = key.content if key.is_a?(Puppet::SSL::Key) # If we're a CSR for the CA, then use the real ca_name, rather than the # fake 'ca' name. This is mostly for backward compatibility with 0.24.x, # but it's also just a good idea. common_name = name == Puppet::SSL::CA_NAME ? Puppet.settings[:ca_name] : name csr = OpenSSL::X509::Request.new csr.version = 0 csr.subject = OpenSSL::X509::Name.new([["CN", common_name]]) csr.public_key = key.public_key if options[:csr_attributes] add_csr_attributes(csr, options[:csr_attributes]) end if (ext_req_attribute = extension_request_attribute(options)) csr.add_attribute(ext_req_attribute) end signer = Puppet::SSL::CertificateSigner.new signer.sign(csr, key) raise Puppet::Error, _("CSR sign verification failed; you need to clean the certificate request for %{name} on the server") % { name: name } unless csr.verify(key.public_key) @content = csr Puppet.info _("Certificate Request fingerprint (%{digest}): %{hex_digest}") % { digest: digest.name, hex_digest: digest.to_hex } @content end def ext_value_to_ruby_value(asn1_arr) # A list of ASN1 types than can't be directly converted to a Ruby type @non_convertible ||= [OpenSSL::ASN1::EndOfContent, OpenSSL::ASN1::BitString, OpenSSL::ASN1::Null, OpenSSL::ASN1::Enumerated, OpenSSL::ASN1::UTCTime, OpenSSL::ASN1::GeneralizedTime, OpenSSL::ASN1::Sequence, OpenSSL::ASN1::Set] begin # Attempt to decode the extension's DER data located in the original OctetString asn1_val = OpenSSL::ASN1.decode(asn1_arr.last.value) rescue OpenSSL::ASN1::ASN1Error # This is to allow supporting the old-style of not DER encoding trusted facts return asn1_arr.last.value end # If the extension value can not be directly converted to an atomic Ruby # type, use the original ASN1 value. This is needed to work around a bug # in Ruby's OpenSSL library which doesn't convert the value of unknown # extension OIDs properly. See PUP-3560 if @non_convertible.include?(asn1_val.class) then # Allows OpenSSL to take the ASN1 value and turn it into something Ruby understands OpenSSL::X509::Extension.new(asn1_arr.first.value, asn1_val.to_der).value else asn1_val.value end end # Return the set of extensions requested on this CSR, in a form designed to # be useful to Ruby: an array of hashes. Which, not coincidentally, you can pass # successfully to the OpenSSL constructor later, if you want. # # @return [Array<Hash{String => String}>] An array of two or three element # hashes, with key/value pairs for the extension's oid, its value, and # optionally its critical state. def request_extensions raise Puppet::Error, _("CSR needs content to extract fields") unless @content # Prefer the standard extReq, but accept the Microsoft specific version as # a fallback, if the standard version isn't found. attribute = @content.attributes.find {|x| x.oid == "extReq" } attribute ||= @content.attributes.find {|x| x.oid == "msExtReq" } return [] unless attribute extensions = unpack_extension_request(attribute) index = -1 extensions.map do |ext_values| index += 1 value = ext_value_to_ruby_value(ext_values) # OK, turn that into an extension, to unpack the content. Lovely that # we have to swap the order of arguments to the underlying method, or # perhaps that the ASN.1 representation chose to pack them in a # strange order where the optional component comes *earlier* than the # fixed component in the sequence. case ext_values.length when 2 {"oid" => ext_values[0].value, "value" => value} when 3 {"oid" => ext_values[0].value, "value" => value, "critical" => ext_values[1].value} else raise Puppet::Error, _("In %{attr}, expected extension record %{index} to have two or three items, but found %{count}") % { attr: attribute.oid, index: index, count: ext_values.length } end end end def subject_alt_names @subject_alt_names ||= request_extensions. select {|x| x["oid"] == "subjectAltName" }. map {|x| x["value"].split(/\s*,\s*/) }. flatten. sort. uniq end # Return all user specified attributes attached to this CSR as a hash. IF an # OID has a single value it is returned as a string, otherwise all values are # returned as an array. # # The format of CSR attributes is specified in PKCS#10/RFC 2986 # # @see https://tools.ietf.org/html/rfc2986 "RFC 2986 Certification Request Syntax Specification" # # @api public # # @return [Hash<String, String>] def custom_attributes x509_attributes = @content.attributes.reject do |attr| PRIVATE_CSR_ATTRIBUTES.include? attr.oid end x509_attributes.map do |attr| {"oid" => attr.oid, "value" => attr.value.value.first.value} end end private # Exclude OIDs that may conflict with how Puppet creates CSRs. # # We only have nominal support for Microsoft extension requests, but since we # ultimately respect that field when looking for extension requests in a CSR # we need to prevent that field from being written to directly. PRIVATE_CSR_ATTRIBUTES = [ 'extReq', '1.2.840.113549.1.9.14', 'msExtReq', '1.3.6.1.4.1.311.2.1.14', ] def add_csr_attributes(csr, csr_attributes) csr_attributes.each do |oid, value| begin if PRIVATE_CSR_ATTRIBUTES.include? oid raise ArgumentError, _("Cannot specify CSR attribute %{oid}: conflicts with internally used CSR attribute") % { oid: oid } end encoded = OpenSSL::ASN1::PrintableString.new(value.to_s) attr_set = OpenSSL::ASN1::Set.new([encoded]) csr.add_attribute(OpenSSL::X509::Attribute.new(oid, attr_set)) Puppet.debug("Added csr attribute: #{oid} => #{attr_set.inspect}") rescue OpenSSL::X509::AttributeError => e raise Puppet::Error, _("Cannot create CSR with attribute %{oid}: %{message}") % { oid: oid, message: e.message }, e.backtrace end end end PRIVATE_EXTENSIONS = [ 'subjectAltName', '2.5.29.17', ] # @api private def extension_request_attribute(options) extensions = [] if options[:extension_requests] options[:extension_requests].each_pair do |oid, value| begin if PRIVATE_EXTENSIONS.include? oid raise Puppet::Error, _("Cannot specify CSR extension request %{oid}: conflicts with internally used extension request") % { oid: oid } end ext = OpenSSL::X509::Extension.new(oid, OpenSSL::ASN1::UTF8String.new(value.to_s).to_der, false) extensions << ext rescue OpenSSL::X509::ExtensionError => e raise Puppet::Error, _("Cannot create CSR with extension request %{oid}: %{message}") % { oid: oid, message: e.message }, e.backtrace end end end if options[:dns_alt_names] raw_names = options[:dns_alt_names].split(/\s*,\s*/).map(&:strip) + [name] parsed_names = raw_names.map do |name| if !name.start_with?("IP:") && !name.start_with?("DNS:") "DNS:#{name}" else name end end.sort.uniq.join(", ") alt_names_ext = extension_factory.create_extension("subjectAltName", parsed_names, false) extensions << alt_names_ext end unless extensions.empty? seq = OpenSSL::ASN1::Sequence(extensions) ext_req = OpenSSL::ASN1::Set([seq]) OpenSSL::X509::Attribute.new("extReq", ext_req) end end # Unpack the extReq attribute into an array of Extensions. # # The extension request attribute is structured like # `Set[Sequence[Extensions]]` where the outer Set only contains a single # sequence. # # In addition the Ruby implementation of ASN1 requires that all ASN1 values # contain a single value, so Sets and Sequence have to contain an array # that in turn holds the elements. This is why we have to unpack an array # every time we unpack a Set/Seq. # # @see https://tools.ietf.org/html/rfc2985#ref-10 5.4.2 CSR Extension Request structure # @see https://tools.ietf.org/html/rfc5280 4.1 Certificate Extension structure # # @api private # # @param attribute [OpenSSL::X509::Attribute] The X509 extension request # # @return [Array<Array<Object>>] A array of arrays containing the extension # OID the critical state if present, and the extension value. def unpack_extension_request(attribute) unless attribute.value.is_a? OpenSSL::ASN1::Set raise Puppet::Error, _("In %{attr}, expected Set but found %{klass}") % { attr: attribute.oid, klass: attribute.value.class } end unless attribute.value.value.is_a? Array raise Puppet::Error, _("In %{attr}, expected Set[Array] but found %{klass}") % { attr: attribute.oid, klass: attribute.value.value.class } end unless attribute.value.value.size == 1 raise Puppet::Error, _("In %{attr}, expected Set[Array] with one value but found %{count} elements") % { attr: attribute.oid, count: attribute.value.value.size } end unless attribute.value.value.first.is_a? OpenSSL::ASN1::Sequence raise Puppet::Error, _("In %{attr}, expected Set[Array[Sequence[...]]], but found %{klass}") % { attr: attribute.oid, klass: extension.class } end unless attribute.value.value.first.value.is_a? Array raise Puppet::Error, _("In %{attr}, expected Set[Array[Sequence[Array[...]]]], but found %{klass}") % { attr: attribute.oid, klass: extension.value.class } end extensions = attribute.value.value.first.value extensions.map(&:value) end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/ssl/certificate_request_attributes.rb��������������������������������������0000644�0052762�0001160�00000002433�13417161721�024525� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/ssl' require 'puppet/util/yaml' # This class transforms simple key/value pairs into the equivalent ASN1 # structures. Values may be strings or arrays of strings. # # @api private class Puppet::SSL::CertificateRequestAttributes attr_reader :path, :custom_attributes, :extension_requests def initialize(path) @path = path @custom_attributes = {} @extension_requests = {} end # Attempt to load a yaml file at the given @path. # @return true if we are able to load the file, false otherwise # @raise [Puppet::Error] if there are unexpected attribute keys def load Puppet.info(_("csr_attributes file loading from %{path}") % { path: path }) if Puppet::FileSystem.exist?(path) hash = Puppet::Util::Yaml.load_file(path, {}) if ! hash.is_a?(Hash) raise Puppet::Error, _("invalid CSR attributes, expected instance of Hash, received instance of %{klass}") % { klass: hash.class } end @custom_attributes = hash.delete('custom_attributes') || {} @extension_requests = hash.delete('extension_requests') || {} if not hash.keys.empty? raise Puppet::Error, _("unexpected attributes %{keys} in %{path}") % { keys: hash.keys.inspect, path: @path.inspect } end return true end return false end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/ssl/certificate_revocation_list.rb�����������������������������������������0000644�0052762�0001160�00000006731�13417161721�024000� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/ssl/base' require 'puppet/indirector' require 'puppet/ssl/certificate_signer' # Manage the CRL. class Puppet::SSL::CertificateRevocationList < Puppet::SSL::Base FIVE_YEARS = 5 * 365*24*60*60 wraps OpenSSL::X509::CRL extend Puppet::Indirector indirects :certificate_revocation_list, :terminus_class => :file, :doc => <<DOC This indirection wraps an `OpenSSL::X509::CRL` object, representing a certificate revocation list (CRL). The indirection key is the CA name (usually literally `ca`). DOC # Convert a string into an instance. def self.from_s(string) super(string, 'foo') # The name doesn't matter end # Because of how the format handler class is included, this # can't be in the base class. def self.supported_formats [:s] end # Knows how to create a CRL with our system defaults. def generate(cert, cakey) Puppet.info _("Creating a new certificate revocation list") create_crl_issued_by(cert) start_at_initial_crl_number update_valid_time_range_to_start_at(Time.now) sign_with(cakey) @content end # The name doesn't actually matter; there's only one CRL. # We just need the name so our Indirector stuff all works more easily. def initialize(fakename) @name = "crl" end # Revoke the certificate with serial number SERIAL issued by this # CA, then write the CRL back to disk. The REASON must be one of the # OpenSSL::OCSP::REVOKED_* reasons def revoke(serial, cakey, reason = OpenSSL::OCSP::REVOKED_STATUS_KEYCOMPROMISE) Puppet.notice _("Revoked certificate with serial %{serial}") % { serial: serial } time = Time.now add_certificate_revocation_for(serial, reason, time) update_to_next_crl_number update_valid_time_range_to_start_at(time) sign_with(cakey) Puppet::SSL::CertificateRevocationList.indirection.save(self) end private def create_crl_issued_by(cert) ef = OpenSSL::X509::ExtensionFactory.new(cert) @content = wrapped_class.new @content.issuer = cert.subject @content.add_extension(ef.create_ext("authorityKeyIdentifier", "keyid:always")) @content.version = 1 end def start_at_initial_crl_number @content.add_extension(crl_number_of(0)) end def add_certificate_revocation_for(serial, reason, time) revoked = OpenSSL::X509::Revoked.new revoked.serial = serial revoked.time = time enum = OpenSSL::ASN1::Enumerated(reason) ext = OpenSSL::X509::Extension.new("CRLReason", enum) revoked.add_extension(ext) @content.add_revoked(revoked) end def update_valid_time_range_to_start_at(time) # The CRL is not valid if the time of checking == the time of last_update. # So to have it valid right now we need to say that it was updated one second ago. @content.last_update = time - 1 @content.next_update = time + FIVE_YEARS end def update_to_next_crl_number @content.extensions = with_next_crl_number_from(@content.extensions) end def with_next_crl_number_from(existing_extensions) existing_crl_num = existing_extensions.find { |e| e.oid == 'crlNumber' } new_crl_num = existing_crl_num ? existing_crl_num.value.to_i + 1 : 0 extensions_without_crl_num = existing_extensions.reject { |e| e.oid == 'crlNumber' } extensions_without_crl_num + [crl_number_of(new_crl_num)] end def crl_number_of(number) OpenSSL::X509::Extension.new('crlNumber', OpenSSL::ASN1::Integer(number)) end def sign_with(cakey) Puppet::SSL::CertificateSigner.new.sign(@content, cakey) end end ���������������������������������������puppet-5.5.10/lib/puppet/ssl/configuration.rb�������������������������������������������������������0000644�0052762�0001160�00000003537�13417161721�021102� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/ssl' require 'openssl' module Puppet module SSL # Puppet::SSL::Configuration is intended to separate out the following concerns: # * CA certificates that authenticate peers (ca_auth_file) # * Who clients trust as distinct from who servers trust. We should not # assume one single self signed CA cert for everyone. class Configuration def initialize(localcacert, options={}) @localcacert = localcacert @ca_auth_file = options[:ca_auth_file] end # @deprecated Use {#ca_auth_file} instead. def ca_chain_file ca_auth_file end # The ca_auth_file method is intended to return the PEM bundle of CA certs # used to authenticate peer connections. def ca_auth_file @ca_auth_file || @localcacert end ## # ca_auth_certificates returns an Array of OpenSSL::X509::Certificate # instances intended to be used in the connection verify_callback. This # method loads and parses the {#ca_auth_file} from the filesystem. # # @api private # # @return [Array<OpenSSL::X509::Certificate>] def ca_auth_certificates @ca_auth_certificates ||= decode_cert_bundle(read_file(ca_auth_file)) end ## # Decode a string of concatenated certificates # # @return [Array<OpenSSL::X509::Certificate>] def decode_cert_bundle(bundle_str) re = /-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----/m pem_ary = bundle_str.scan(re) pem_ary.map do |pem_str| OpenSSL::X509::Certificate.new(pem_str) end end private :decode_cert_bundle # read_file makes testing easier. def read_file(path) # https://www.ietf.org/rfc/rfc2459.txt defines the x509 V3 certificate format # CA bundles are concatenated X509 certificates, but may also include # comments, which could have UTF-8 characters Puppet::FileSystem.read(path, :encoding => Encoding::UTF_8) end private :read_file end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/ssl/host.rb����������������������������������������������������������������0000644�0052762�0001160�00000032163�13417161721�017205� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector' require 'puppet/ssl' require 'puppet/ssl/key' require 'puppet/ssl/certificate' require 'puppet/ssl/certificate_request' require 'puppet/ssl/certificate_revocation_list' require 'puppet/ssl/certificate_request_attributes' # The class that manages all aspects of our SSL certificates -- # private keys, public keys, requests, etc. class Puppet::SSL::Host # Yay, ruby's strange constant lookups. Key = Puppet::SSL::Key CA_NAME = Puppet::SSL::CA_NAME Certificate = Puppet::SSL::Certificate CertificateRequest = Puppet::SSL::CertificateRequest CertificateRevocationList = Puppet::SSL::CertificateRevocationList extend Puppet::Indirector indirects :certificate_status, :terminus_class => :file, :doc => <<DOC This indirection represents the host that ties a key, certificate, and certificate request together. The indirection key is the certificate CN (generally a hostname). DOC attr_reader :name attr_accessor :ca attr_writer :key, :certificate, :certificate_request # This accessor is used in instances for indirector requests to hold desired state attr_accessor :desired_state def self.localhost return @localhost if @localhost @localhost = new @localhost.generate unless @localhost.certificate @localhost.key @localhost end def self.reset @localhost = nil end # This is the constant that people will use to mark that a given host is # a certificate authority. def self.ca_name CA_NAME end class << self attr_reader :ca_location end # Configure how our various classes interact with their various terminuses. def self.configure_indirection(terminus, cache = nil) Certificate.indirection.terminus_class = terminus CertificateRequest.indirection.terminus_class = terminus CertificateRevocationList.indirection.terminus_class = terminus host_map = {:ca => :file, :disabled_ca => nil, :file => nil, :rest => :rest} if term = host_map[terminus] self.indirection.terminus_class = term else self.indirection.reset_terminus_class end if cache # This is weird; we don't actually cache our keys, we # use what would otherwise be the cache as our normal # terminus. Key.indirection.terminus_class = cache else Key.indirection.terminus_class = terminus end if cache Certificate.indirection.cache_class = cache CertificateRequest.indirection.cache_class = cache CertificateRevocationList.indirection.cache_class = cache else # Make sure we have no cache configured. puppet master # switches the configurations around a bit, so it's important # that we specify the configs for absolutely everything, every # time. Certificate.indirection.cache_class = nil CertificateRequest.indirection.cache_class = nil CertificateRevocationList.indirection.cache_class = nil end end CA_MODES = { # Our ca is local, so we use it as the ultimate source of information # And we cache files locally. :local => [:ca, :file], # We're a remote CA client. :remote => [:rest, :file], # We are the CA, so we don't have read/write access to the normal certificates. :only => [:ca], # We have no CA, so we just look in the local file store. :none => [:disabled_ca] } # Specify how we expect to interact with our certificate authority. def self.ca_location=(mode) modes = CA_MODES.collect { |m, vals| m.to_s }.join(", ") raise ArgumentError, _("CA Mode can only be one of: %{modes}") % { modes: modes } unless CA_MODES.include?(mode) @ca_location = mode configure_indirection(*CA_MODES[@ca_location]) end # Puppet::SSL::Host is actually indirected now so the original implementation # has been moved into the certificate_status indirector. This method is in-use # in `puppet cert -c <certname>`. def self.destroy(name) indirection.destroy(name) end def self.from_data_hash(data) instance = new(data["name"]) if data["desired_state"] instance.desired_state = data["desired_state"] end instance end # Puppet::SSL::Host is actually indirected now so the original implementation # has been moved into the certificate_status indirector. This method does not # appear to be in use in `puppet cert -l`. def self.search(options = {}) indirection.search("*", options) end # Is this a ca host, meaning that all of its files go in the CA location? def ca? ca end def key @key ||= Key.indirection.find(name) end # This is the private key; we can create it from scratch # with no inputs. def generate_key @key = Key.new(name) @key.generate begin Key.indirection.save(@key) rescue @key = nil raise end true end def certificate_request @certificate_request ||= CertificateRequest.indirection.find(name) end # Our certificate request requires the key but that's all. def generate_certificate_request(options = {}) generate_key unless key # If this CSR is for the current machine... if name == Puppet[:certname].downcase # ...add our configured dns_alt_names if Puppet[:dns_alt_names] and Puppet[:dns_alt_names] != '' options[:dns_alt_names] ||= Puppet[:dns_alt_names] elsif Puppet::SSL::CertificateAuthority.ca? and fqdn = Facter.value(:fqdn) and domain = Facter.value(:domain) options[:dns_alt_names] = "puppet, #{fqdn}, puppet.#{domain}" end end csr_attributes = Puppet::SSL::CertificateRequestAttributes.new(Puppet[:csr_attributes]) if csr_attributes.load options[:csr_attributes] = csr_attributes.custom_attributes options[:extension_requests] = csr_attributes.extension_requests end @certificate_request = CertificateRequest.new(name) @certificate_request.generate(key.content, options) begin CertificateRequest.indirection.save(@certificate_request) rescue @certificate_request = nil raise end true end def certificate unless @certificate generate_key unless key # get the CA cert first, since it's required for the normal cert # to be of any use. return nil unless Certificate.indirection.find("ca", :fail_on_404 => true) unless ca? return nil unless @certificate = Certificate.indirection.find(name) validate_certificate_with_key end @certificate end def validate_certificate_with_key raise Puppet::Error, _("No certificate to validate.") unless certificate raise Puppet::Error, _("No private key with which to validate certificate with fingerprint: %{fingerprint}") % { fingerprint: certificate.fingerprint } unless key unless certificate.content.check_private_key(key.content) raise Puppet::Error, _(<<ERROR_STRING) % { fingerprint: certificate.fingerprint, cert_name: Puppet[:certname], ssl_dir: Puppet[:ssldir], cert_dir: Puppet[:certdir].gsub('/', '\\') } The certificate retrieved from the master does not match the agent's private key. Did you forget to run as root? Certificate fingerprint: %{fingerprint} To fix this, remove the certificate from both the master and the agent and then start a puppet run, which will automatically regenerate a certificate. On the master: puppet cert clean %{cert_name} On the agent: 1a. On most platforms: find %{ssl_dir} -name %{cert_name}.pem -delete 1b. On Windows: del "%{cert_dir}\\%{cert_name}.pem" /f 2. puppet agent -t ERROR_STRING end end # Generate all necessary parts of our ssl host. def generate generate_key unless key # ask indirector to find any existing requests and download them existing_request = certificate_request # if CSR downloaded from master, but the local keypair was just generated and # does not match the public key in the CSR, fail hard if !existing_request.nil? && (key.content.public_key.to_s != existing_request.content.public_key.to_s) raise Puppet::Error, _(<<ERROR_STRING) % { fingerprint: existing_request.fingerprint, csr_public_key: existing_request.content.public_key.to_text, agent_public_key: key.content.public_key.to_text, cert_name: Puppet[:certname], ssl_dir: Puppet[:ssldir], cert_dir: Puppet[:certdir].gsub('/', '\\') } The CSR retrieved from the master does not match the agent's public key. CSR fingerprint: %{fingerprint} CSR public key: %{csr_public_key} Agent public key: %{agent_public_key} To fix this, remove the CSR from both the master and the agent and then start a puppet run, which will automatically regenerate a CSR. On the master: puppet cert clean %{cert_name} On the agent: 1a. On most platforms: find %{ssl_dir} -name %{cert_name}.pem -delete 1b. On Windows: del "%{cert_dir}\\%{cert_name}.pem" /f 2. puppet agent -t ERROR_STRING end generate_certificate_request unless existing_request # If we can get a CA instance, then we're a valid CA, and we # should use it to sign our request; else, just try to read # the cert. if ! certificate and ca = Puppet::SSL::CertificateAuthority.instance ca.sign(self.name, {allow_dns_alt_names: true}) end end def initialize(name = nil) @name = (name || Puppet[:certname]).downcase Puppet::SSL::Base.validate_certname(@name) @key = @certificate = @certificate_request = nil @ca = (name == self.class.ca_name) end # Extract the public key from the private key. def public_key key.content.public_key end # Create/return a store that uses our SSL info to validate # connections. def ssl_store(purpose = OpenSSL::X509::PURPOSE_ANY) if @ssl_store.nil? @ssl_store = build_ssl_store(purpose) end @ssl_store end def to_data_hash my_cert = Puppet::SSL::Certificate.indirection.find(name) result = { 'name' => name } my_state = state result['state'] = my_state result['desired_state'] = desired_state if desired_state thing_to_use = (my_state == 'requested') ? certificate_request : my_cert # this is for backwards-compatibility # we should deprecate it and transition people to using # json[:fingerprints][:default] # It appears that we have no internal consumers of this api # --jeffweiss 30 aug 2012 result['fingerprint'] = thing_to_use.fingerprint # The above fingerprint doesn't tell us what message digest algorithm was used # No problem, except that the default is changing between 2.7 and 3.0. Also, as # we move to FIPS 140-2 compliance, MD5 is no longer allowed (and, gasp, will # segfault in rubies older than 1.9.3) # So, when we add the newer fingerprints, we're explicit about the hashing # algorithm used. # --jeffweiss 31 july 2012 result['fingerprints'] = {} result['fingerprints']['default'] = thing_to_use.fingerprint suitable_message_digest_algorithms.each do |md| result['fingerprints'][md.to_s] = thing_to_use.fingerprint md end result['dns_alt_names'] = thing_to_use.subject_alt_names result end # eventually we'll probably want to move this somewhere else or make it # configurable # --jeffweiss 29 aug 2012 def suitable_message_digest_algorithms [:SHA1, :SHA224, :SHA256, :SHA384, :SHA512] end # Attempt to retrieve a cert, if we don't already have one. def wait_for_cert(time) begin return if certificate generate return if certificate rescue StandardError => detail Puppet.log_exception(detail, _("Could not request certificate: %{message}") % { message: detail.message }) if time < 1 puts _("Exiting; failed to retrieve certificate and waitforcert is disabled") exit(1) else sleep(time) end retry end if time < 1 puts _("Exiting; no certificate found and waitforcert is disabled") exit(1) end while true sleep time begin break if certificate Puppet.notice _("Did not receive certificate") rescue StandardError => detail Puppet.log_exception(detail, _("Could not request certificate: %{message}") % { message: detail.message }) end end end def state if certificate_request return 'requested' end begin Puppet::SSL::CertificateAuthority.new.verify(name) return 'signed' rescue Puppet::SSL::CertificateAuthority::CertificateVerificationError return 'revoked' end end private def build_ssl_store(purpose) store = OpenSSL::X509::Store.new store.purpose = purpose # Use the file path here, because we don't want to cause # a lookup in the middle of setting our ssl connection. store.add_file(Puppet[:localcacert]) # If we're doing revocation and there's a CRL, add it to our store. if Puppet.lookup(:certificate_revocation) if crl = Puppet::SSL::CertificateRevocationList.indirection.find(CA_NAME) flags = OpenSSL::X509::V_FLAG_CRL_CHECK if Puppet.lookup(:certificate_revocation) == :chain flags |= OpenSSL::X509::V_FLAG_CRL_CHECK_ALL end store.flags = flags store.add_crl(crl.content) else Puppet.debug _("Certificate revocation checking is enabled but a CRL cannot be found; CRL checking will not be performed.") end end store end end require 'puppet/ssl/certificate_authority' �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/ssl/inventory.rb�����������������������������������������������������������0000644�0052762�0001160�00000003665�13417161721�020272� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/ssl' require 'puppet/ssl/certificate' # Keep track of all of our known certificates. class Puppet::SSL::Inventory attr_reader :path # Add a certificate to our inventory. def add(cert) cert = cert.content if cert.is_a?(Puppet::SSL::Certificate) # RFC 5280 says the cert subject may contain UTF8 - https://www.ietf.org/rfc/rfc5280.txt # Note however that Puppet generated SSL files must only contain ASCII characters # based on the validate_certname method of Puppet::SSL::Base Puppet.settings.setting(:cert_inventory).open('a:UTF-8') do |f| f.print format(cert) end end # Format our certificate for output. def format(cert) iso = '%Y-%m-%dT%H:%M:%S%Z' "0x%04x %s %s %s\n" % [cert.serial, cert.not_before.strftime(iso), cert.not_after.strftime(iso), cert.subject] end def initialize @path = Puppet[:cert_inventory] end # Rebuild the inventory from scratch. This should happen if # the file is entirely missing or if it's somehow corrupted. def rebuild Puppet.notice _("Rebuilding inventory file") # RFC 5280 says the cert subject may contain UTF8 - https://www.ietf.org/rfc/rfc5280.txt Puppet.settings.setting(:cert_inventory).open('w:UTF-8') do |f| Puppet::SSL::Certificate.indirection.search("*").each do |cert| f.print format(cert.content) end end end # Find all serial numbers for a given certificate. If none can be found, returns # an empty array. def serials(name) return [] unless Puppet::FileSystem.exist?(@path) # RFC 5280 says the cert subject may contain UTF8 - https://www.ietf.org/rfc/rfc5280.txt # Note however that Puppet generated SSL files must only contain ASCII characters # based on the validate_certname method of Puppet::SSL::Base File.readlines(@path, :encoding => Encoding::UTF_8).collect do |line| /^(\S+).+\/CN=#{name}$/.match(line) end.compact.map { |m| Integer(m[1]) } end end ���������������������������������������������������������������������������puppet-5.5.10/lib/puppet/ssl/key.rb�����������������������������������������������������������������0000644�0052762�0001160�00000003433�13417161721�017016� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/ssl/base' require 'puppet/indirector' # Manage private and public keys as a pair. class Puppet::SSL::Key < Puppet::SSL::Base wraps OpenSSL::PKey::RSA extend Puppet::Indirector indirects :key, :terminus_class => :file, :doc => <<DOC This indirection wraps an `OpenSSL::PKey::RSA object, representing a private key. The indirection key is the certificate CN (generally a hostname). DOC # Because of how the format handler class is included, this # can't be in the base class. def self.supported_formats [:s] end attr_accessor :password_file # Knows how to create keys with our system defaults. def generate Puppet.info _("Creating a new SSL key for %{name}") % { name: name } @content = OpenSSL::PKey::RSA.new(Puppet[:keylength].to_i) end def initialize(name) super if ca? @password_file = Puppet[:capass] else @password_file = Puppet[:passfile] end end def password return nil unless password_file and Puppet::FileSystem.exist?(password_file) # Puppet generates files at the default Puppet[:capass] using ASCII # User configured :passfile could be in any encoding # Use BINARY given the string is passed to an OpenSSL API accepting bytes # note this is only called internally Puppet::FileSystem.read(password_file, :encoding => Encoding::BINARY) end # Optionally support specifying a password file. def read(path) return super unless password_file # RFC 1421 states PEM is 7-bit ASCII https://tools.ietf.org/html/rfc1421 @content = wrapped_class.new(Puppet::FileSystem.read(path, :encoding => Encoding::ASCII), password) end def to_s if pass = password @content.export(OpenSSL::Cipher::DES.new(:EDE3, :CBC), pass) else return super end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/ssl/oids.rb����������������������������������������������������������������0000644�0052762�0001160�00000020714�13417161721�017165� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/ssl' # This module defines OIDs for use within Puppet. # # == ASN.1 Definition # # The following is the formal definition of OIDs specified in this file. # # puppetCertExtensions OBJECT IDENTIFIER ::= {iso(1) identified-organization(3) # dod(6) internet(1) private(4) enterprise(1) 34380 1} # # -- the tree under registeredExtensions 'belongs' to puppetlabs # -- privateExtensions can be extended by enterprises to suit their own needs # registeredExtensions OBJECT IDENTIFIER ::= { puppetCertExtensions 1 } # privateExtensions OBJECT IDENTIFIER ::= { puppetCertExtensions 2 } # authorizationExtensions OBJECT IDENTIFIER ::= { puppetCertExtensions 3 } # # -- subtree of common registered extensions # -- The short names for these OIDs are intentionally lowercased and formatted # -- since they may be exposed inside the Puppet DSL as variables. # pp_uuid OBJECT IDENTIFIER ::= { registeredExtensions 1 } # pp_instance_id OBJECT IDENTIFIER ::= { registeredExtensions 2 } # pp_image_name OBJECT IDENTIFIER ::= { registeredExtensions 3 } # pp_preshared_key OBJECT IDENTIFIER ::= { registeredExtensions 4 } # # @api private module Puppet::SSL::Oids # Note: When updating the following OIDs make sure to also update the OID # definitions here: # https://github.com/puppetlabs/puppetserver/blob/master/src/clj/puppetlabs/puppetserver/certificate_authority.clj#L122-L159 PUPPET_OIDS = [ ["1.3.6.1.4.1.34380", 'puppetlabs', 'Puppet Labs'], ["1.3.6.1.4.1.34380.1", 'ppCertExt', 'Puppet Certificate Extension'], ["1.3.6.1.4.1.34380.1.1", 'ppRegCertExt', 'Puppet Registered Certificate Extension'], ["1.3.6.1.4.1.34380.1.1.1", 'pp_uuid', 'Puppet Node UUID'], ["1.3.6.1.4.1.34380.1.1.2", 'pp_instance_id', 'Puppet Node Instance ID'], ["1.3.6.1.4.1.34380.1.1.3", 'pp_image_name', 'Puppet Node Image Name'], ["1.3.6.1.4.1.34380.1.1.4", 'pp_preshared_key', 'Puppet Node Preshared Key'], ["1.3.6.1.4.1.34380.1.1.5", 'pp_cost_center', 'Puppet Node Cost Center Name'], ["1.3.6.1.4.1.34380.1.1.6", 'pp_product', 'Puppet Node Product Name'], ["1.3.6.1.4.1.34380.1.1.7", 'pp_project', 'Puppet Node Project Name'], ["1.3.6.1.4.1.34380.1.1.8", 'pp_application', 'Puppet Node Application Name'], ["1.3.6.1.4.1.34380.1.1.9", 'pp_service', 'Puppet Node Service Name'], ["1.3.6.1.4.1.34380.1.1.10", 'pp_employee', 'Puppet Node Employee Name'], ["1.3.6.1.4.1.34380.1.1.11", 'pp_created_by', 'Puppet Node created_by Tag'], ["1.3.6.1.4.1.34380.1.1.12", 'pp_environment', 'Puppet Node Environment Name'], ["1.3.6.1.4.1.34380.1.1.13", 'pp_role', 'Puppet Node Role Name'], ["1.3.6.1.4.1.34380.1.1.14", 'pp_software_version', 'Puppet Node Software Version'], ["1.3.6.1.4.1.34380.1.1.15", 'pp_department', 'Puppet Node Department Name'], ["1.3.6.1.4.1.34380.1.1.16", 'pp_cluster', 'Puppet Node Cluster Name'], ["1.3.6.1.4.1.34380.1.1.17", 'pp_provisioner', 'Puppet Node Provisioner Name'], ["1.3.6.1.4.1.34380.1.1.18", 'pp_region', 'Puppet Node Region Name'], ["1.3.6.1.4.1.34380.1.1.19", 'pp_datacenter', 'Puppet Node Datacenter Name'], ["1.3.6.1.4.1.34380.1.1.20", 'pp_zone', 'Puppet Node Zone Name'], ["1.3.6.1.4.1.34380.1.1.21", 'pp_network', 'Puppet Node Network Name'], ["1.3.6.1.4.1.34380.1.1.22", 'pp_securitypolicy', 'Puppet Node Security Policy Name'], ["1.3.6.1.4.1.34380.1.1.23", 'pp_cloudplatform', 'Puppet Node Cloud Platform Name'], ["1.3.6.1.4.1.34380.1.1.24", 'pp_apptier', 'Puppet Node Application Tier'], ["1.3.6.1.4.1.34380.1.1.25", 'pp_hostname', 'Puppet Node Hostname'], ["1.3.6.1.4.1.34380.1.2", 'ppPrivCertExt', 'Puppet Private Certificate Extension'], ["1.3.6.1.4.1.34380.1.3", 'ppAuthCertExt', 'Puppet Certificate Authorization Extension'], ["1.3.6.1.4.1.34380.1.3.1", 'pp_authorization', 'Certificate Extension Authorization'], ["1.3.6.1.4.1.34380.1.3.13", 'pp_auth_role', 'Puppet Node Role Name for Authorization'], ] @did_register_puppet_oids = false # Register our custom Puppet OIDs with OpenSSL so they can be used as CSR # extensions. Without registering these OIDs, OpenSSL will fail when it # encounters such an extension in a CSR. def self.register_puppet_oids() if !@did_register_puppet_oids PUPPET_OIDS.each do |oid_defn| OpenSSL::ASN1::ObjectId.register(*oid_defn) end @did_register_puppet_oids = true end end # Parse custom OID mapping file that enables custom OIDs to be resolved # into user-friendly names. # # @param custom_oid_file [String] File to obtain custom OIDs mapping from # @param map_key [String] Hash key in which custom OIDs mapping is stored # # @example Custom OID mapping file # --- # oid_mapping: # '1.3.6.1.4.1.34380.1.2.1.1': # shortname : 'myshortname' # longname : 'Long name' # '1.3.6.1.4.1.34380.1.2.1.2': # shortname: 'myothershortname' # longname: 'Other Long name' def self.parse_custom_oid_file(custom_oid_file, map_key='oid_mapping') if File.exists?(custom_oid_file) && File.readable?(custom_oid_file) mapping = nil begin mapping = YAML.load_file(custom_oid_file) rescue => err raise Puppet::Error, _("Error loading ssl custom OIDs mapping file from '%{custom_oid_file}': %{err}") % { custom_oid_file: custom_oid_file, err: err }, err.backtrace end unless mapping.has_key?(map_key) raise Puppet::Error, _("Error loading ssl custom OIDs mapping file from '%{custom_oid_file}': no such index '%{map_key}'") % { custom_oid_file: custom_oid_file, map_key: map_key } end unless mapping[map_key].is_a?(Hash) raise Puppet::Error, _("Error loading ssl custom OIDs mapping file from '%{custom_oid_file}': data under index '%{map_key}' must be a Hash") % { custom_oid_file: custom_oid_file, map_key: map_key } end oid_defns = [] mapping[map_key].keys.each do |oid| shortname, longname = mapping[map_key][oid].values_at("shortname","longname") if shortname.nil? || longname.nil? raise Puppet::Error, _("Error loading ssl custom OIDs mapping file from '%{custom_oid_file}': incomplete definition of oid '%{oid}'") % { custom_oid_file: custom_oid_file, oid: oid } end oid_defns << [oid, shortname, longname] end oid_defns end end # Load custom OID mapping file that enables custom OIDs to be resolved # into user-friendly names. # # @param custom_oid_file [String] File to obtain custom OIDs mapping from # @param map_key [String] Hash key in which custom OIDs mapping is stored # # @example Custom OID mapping file # --- # oid_mapping: # '1.3.6.1.4.1.34380.1.2.1.1': # shortname : 'myshortname' # longname : 'Long name' # '1.3.6.1.4.1.34380.1.2.1.2': # shortname: 'myothershortname' # longname: 'Other Long name' def self.load_custom_oid_file(custom_oid_file, map_key='oid_mapping') oid_defns = parse_custom_oid_file(custom_oid_file, map_key) unless oid_defns.nil? begin oid_defns.each do |oid_defn| OpenSSL::ASN1::ObjectId.register(*oid_defn) end rescue => err raise ArgumentError, _("Error registering ssl custom OIDs mapping from file '%{custom_oid_file}': %{err}") % { custom_oid_file: custom_oid_file, err: err }, err.backtrace end end end # Determine if the first OID contains the second OID # # @param first [String] The containing OID, in dotted form or as the short name # @param second [String] The contained OID, in dotted form or as the short name # @param exclusive [true, false] If an OID should not be considered as a subtree of itself # # @example Comparing two dotted OIDs # Puppet::SSL::Oids.subtree_of?('1.3.6.1', '1.3.6.1.4.1') #=> true # Puppet::SSL::Oids.subtree_of?('1.3.6.1', '1.3.6') #=> false # # @example Comparing an OID short name with a dotted OID # Puppet::SSL::Oids.subtree_of?('IANA', '1.3.6.1.4.1') #=> true # Puppet::SSL::Oids.subtree_of?('1.3.6.1', 'enterprises') #=> true # # @example Comparing an OID against itself # Puppet::SSL::Oids.subtree_of?('IANA', 'IANA') #=> true # Puppet::SSL::Oids.subtree_of?('IANA', 'IANA', true) #=> false # # @return [true, false] def self.subtree_of?(first, second, exclusive = false) first_oid = OpenSSL::ASN1::ObjectId.new(first).oid second_oid = OpenSSL::ASN1::ObjectId.new(second).oid if exclusive and first_oid == second_oid false else second_oid.index(first_oid) == 0 end rescue OpenSSL::ASN1::ASN1Error false end end ����������������������������������������������������puppet-5.5.10/lib/puppet/status.rb������������������������������������������������������������������0000644�0052762�0001160�00000001117�13417161721�016745� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/indirector' class Puppet::Status extend Puppet::Indirector indirects :status, :terminus_class => :local attr_accessor :status def initialize( status = nil ) @status = status || {"is_alive" => true} end def to_data_hash @status end def self.from_data_hash(data) if data.include?('status') self.new(data['status']) else self.new(data) end end def name "status" end def name=(name) # NOOP end def version @status['version'] end def version=(version) @status['version'] = version end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/syntax_checkers.rb���������������������������������������������������������0000644�0052762�0001160�00000000130�13417161721�020611� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# A name space for syntax checkers provided by Puppet. module Puppet::SyntaxCheckers end����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/syntax_checkers/�����������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020277� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/syntax_checkers/base64.rb��������������������������������������������������0000644�0052762�0001160�00000004304�13417161721�021704� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# A syntax checker for Base64. # @api public require 'puppet/syntax_checkers' require 'base64' class Puppet::SyntaxCheckers::Base64 < Puppet::Plugins::SyntaxCheckers::SyntaxChecker # Checks the text for BASE64 syntax issues and reports them to the given acceptor. # This checker allows the most relaxed form of Base64, including newlines and missing padding. # It also accept URLsafe input. # # @param text [String] The text to check # @param syntax [String] The syntax identifier in mime style (e.g. 'base64', 'text/xxx+base64') # @param acceptor [#accept] A Diagnostic acceptor # @param source_pos [Puppet::Pops::Adapters::SourcePosAdapter] A source pos adapter with location information # @api public # def check(text, syntax, acceptor, source_pos) raise ArgumentError.new(_("Base64 syntax checker: the text to check must be a String.")) unless text.is_a?(String) raise ArgumentError.new(_("Base64 syntax checker: the syntax identifier must be a String, e.g. json, data+json")) unless syntax.is_a?(String) raise ArgumentError.new(_("Base64 syntax checker: invalid Acceptor, got: '%{klass}'.") % { klass: acceptor.class.name }) unless acceptor.is_a?(Puppet::Pops::Validation::Acceptor) cleaned_text = text.gsub(/[\r?\n[:blank:]]/, '') begin # Do a strict decode64 on text with all whitespace stripped since the non strict version # simply skips all non base64 characters Base64.strict_decode64(cleaned_text) rescue msg = if (cleaned_text.bytes.to_a.size * 8) % 6 != 0 _("Base64 syntax checker: Cannot parse invalid Base64 string - padding is not correct") else _("Base64 syntax checker: Cannot parse invalid Base64 string - contains letters outside strict base 64 range (or whitespace)") end # TODO: improve the pops API to allow simpler diagnostic creation while still maintaining capabilities # and the issue code. (In this case especially, where there is only a single error message being issued). # issue = Puppet::Pops::Issues::issue(:ILLEGAL_BASE64) { msg } acceptor.accept(Puppet::Pops::Validation::Diagnostic.new(:error, issue, source_pos.file, source_pos, {})) end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/syntax_checkers/json.rb����������������������������������������������������0000644�0052762�0001160�00000003667�13417161721�021604� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# A syntax checker for JSON. # @api public require 'puppet/syntax_checkers' class Puppet::SyntaxCheckers::Json < Puppet::Plugins::SyntaxCheckers::SyntaxChecker # Checks the text for JSON syntax issues and reports them to the given acceptor. # # Error messages from the checker are capped at 100 chars from the source text. # # @param text [String] The text to check # @param syntax [String] The syntax identifier in mime style (e.g. 'json', 'json-patch+json', 'xml', 'myapp+xml' # @param acceptor [#accept] A Diagnostic acceptor # @param source_pos [Puppet::Pops::Adapters::SourcePosAdapter] A source pos adapter with location information # @api public # def check(text, syntax, acceptor, source_pos) raise ArgumentError.new(_("Json syntax checker: the text to check must be a String.")) unless text.is_a?(String) raise ArgumentError.new(_("Json syntax checker: the syntax identifier must be a String, e.g. json, data+json")) unless syntax.is_a?(String) raise ArgumentError.new(_("Json syntax checker: invalid Acceptor, got: '%{klass}'.") % { klass: acceptor.class.name }) unless acceptor.is_a?(Puppet::Pops::Validation::Acceptor) #raise ArgumentError.new("Json syntax checker: location_info must be a Hash") unless location_info.is_a?(Hash) begin Puppet::Util::Json.load(text) rescue => e # Cap the message to 100 chars and replace newlines msg = _("JSON syntax checker: Cannot parse invalid JSON string. \"%{message}\"") % { message: e.message().slice(0,100).gsub(/\r?\n/, "\\n") } # TODO: improve the pops API to allow simpler diagnostic creation while still maintaining capabilities # and the issue code. (In this case especially, where there is only a single error message being issued). # issue = Puppet::Pops::Issues::issue(:ILLEGAL_JSON) { msg } acceptor.accept(Puppet::Pops::Validation::Diagnostic.new(:error, issue, source_pos.file, source_pos, {})) end end end �������������������������������������������������������������������������puppet-5.5.10/lib/puppet/test/����������������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�016061� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/test/test_helper.rb��������������������������������������������������������0000644�0052762�0001160�00000024450�13417161721�020724� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'tmpdir' require 'fileutils' module Puppet::Test # This class is intended to provide an API to be used by external projects # when they are running tests that depend on puppet core. This should # allow us to vary the implementation details of managing puppet's state # for testing, from one version of puppet to the next--without forcing # the external projects to do any of that state management or be aware of # the implementation details. # # For now, this consists of a few very simple signatures. The plan is # that it should be the responsibility of the puppetlabs_spec_helper # to broker between external projects and this API; thus, if any # hacks are required (e.g. to determine whether or not a particular) # version of puppet supports this API, those hacks will be consolidated in # one place and won't need to be duplicated in every external project. # # This should also alleviate the anti-pattern that we've been following, # wherein each external project starts off with a copy of puppet core's # test_helper.rb and is exposed to risk of that code getting out of # sync with core. # # Since this class will be "library code" that ships with puppet, it does # not use API from any existing test framework such as rspec. This should # theoretically allow it to be used with other unit test frameworks in the # future, if desired. # # Note that in the future this API could potentially be expanded to handle # other features such as "around_test", but we didn't see a compelling # reason to deal with that right now. class TestHelper # Call this method once, as early as possible, such as before loading tests # that call Puppet. # @return nil def self.initialize() # This meta class instance variable is used as a guard to ensure that # before_each, and after_each are only called once. This problem occurs # when there are more than one puppet test infrastructure orchestrator in use. # The use of both puppetabs-spec_helper, and rodjek-rspec_puppet will cause # two resets of the puppet environment, and will cause problem rolling back to # a known point as there is no way to differentiate where the calls are coming # from. See more information in #before_each_test, and #after_each_test # Note that the variable is only initialized to 0 if nil. This is important # as more than one orchestrator will call initialize. A second call can not # simply set it to 0 since that would potentially destroy an active guard. # @@reentry_count ||= 0 @environmentpath = Dir.mktmpdir('environments') Dir.mkdir("#{@environmentpath}/production") owner = Process.pid Puppet.push_context(Puppet.base_context({ :environmentpath => @environmentpath, :basemodulepath => "", }), "Initial for specs") Puppet::Parser::Functions.reset ObjectSpace.define_finalizer(Puppet.lookup(:environments), proc { if Process.pid == owner FileUtils.rm_rf(@environmentpath) end }) Puppet::SSL::Oids.register_puppet_oids end # Call this method once, when beginning a test run--prior to running # any individual tests. # @return nil def self.before_all_tests() # Make sure that all of the setup is also done for any before(:all) blocks end # Call this method once, at the end of a test run, when no more tests # will be run. # @return nil def self.after_all_tests() end # The name of the rollback mark used in the Puppet.context. This is what # the test infrastructure returns to for each test. # ROLLBACK_MARK = "initial testing state" # Call this method once per test, prior to execution of each individual test. # @return nil def self.before_each_test() # When using both rspec-puppet and puppet-rspec-helper, there are two packages trying # to be helpful and orchestrate the callback sequence. We let only the first win, the # second callback results in a no-op. # Likewise when entering after_each_test(), a check is made to make tear down happen # only once. # return unless @@reentry_count == 0 @@reentry_count = 1 Puppet.mark_context(ROLLBACK_MARK) # We need to preserve the current state of all our indirection cache and # terminus classes. This is pretty important, because changes to these # are global and lead to order dependencies in our testing. # # We go direct to the implementation because there is no safe, sane public # API to manage restoration of these to their default values. This # should, once the value is proved, be moved to a standard API on the # indirector. # # To make things worse, a number of the tests stub parts of the # indirector. These stubs have very specific expectations that what # little of the public API we could use is, well, likely to explode # randomly in some tests. So, direct access. --daniel 2011-08-30 $saved_indirection_state = {} indirections = Puppet::Indirector::Indirection.send(:class_variable_get, :@@indirections) indirections.each do |indirector| $saved_indirection_state[indirector.name] = { :@terminus_class => indirector.instance_variable_get(:@terminus_class), :@cache_class => indirector.instance_variable_get(:@cache_class) } end # The process environment is a shared, persistent resource. # Can't use Puppet.features.microsoft_windows? as it may be mocked out in a test. This can cause test recurring test failures if (!!File::ALT_SEPARATOR) mode = :windows else mode = :posix end $old_env = Puppet::Util.get_environment(mode) # So is the load_path $old_load_path = $LOAD_PATH.dup initialize_settings_before_each() Puppet.push_context( { :trusted_information => Puppet::Context::TrustedInformation.new('local', 'testing', {}), }, "Context for specs") Puppet::Parser::Functions.reset Puppet::Application.clear! Puppet::Util::Profiler.clear Puppet.clear_deprecation_warnings end # Call this method once per test, after execution of each individual test. # @return nil def self.after_each_test() # Ensure that a matching tear down only happens once per completed setup # (see #before_each_test). return unless @@reentry_count == 1 @@reentry_count = 0 Puppet.settings.send(:clear_everything_for_tests) Puppet::Util::Storage.clear Puppet::Util::ExecutionStub.reset Puppet.clear_deprecation_warnings # uncommenting and manipulating this can be useful when tracking down calls to deprecated code #Puppet.log_deprecations_to_file("deprecations.txt", /^Puppet::Util.exec/) # Restore the indirector configuration. See before hook. indirections = Puppet::Indirector::Indirection.send(:class_variable_get, :@@indirections) indirections.each do |indirector| $saved_indirection_state.fetch(indirector.name, {}).each do |variable, value| indirector.instance_variable_set(variable, value) end end $saved_indirection_state = nil # Can't use Puppet.features.microsoft_windows? as it may be mocked out in a test. This can cause test recurring test failures if (!!File::ALT_SEPARATOR) mode = :windows else mode = :posix end # Restore the global process environment. Can't just assign because this # is a magic variable, sadly, and doesn't do that™. It is sufficiently # faster to use the compare-then-set model to avoid excessive work that it # justifies the complexity. --daniel 2012-03-15 unless Puppet::Util.get_environment(mode) == $old_env Puppet::Util.clear_environment(mode) $old_env.each {|k, v| Puppet::Util.set_env(k, v, mode) } end # Clear all environments Puppet.lookup(:environments).clear_all # Restore the load_path late, to avoid messing with stubs from the test. $LOAD_PATH.clear $old_load_path.each {|x| $LOAD_PATH << x } Puppet.rollback_context(ROLLBACK_MARK) end ######################################################################################### # PRIVATE METHODS (not part of the public TestHelper API--do not call these from outside # of this class!) ######################################################################################### def self.app_defaults_for_tests() { :logdir => "/dev/null", :confdir => "/dev/null", :codedir => "/dev/null", :vardir => "/dev/null", :rundir => "/dev/null", :hiera_config => "/dev/null", } end private_class_method :app_defaults_for_tests def self.initialize_settings_before_each() Puppet.settings.preferred_run_mode = "user" # Initialize "app defaults" settings to a good set of test values Puppet.settings.initialize_app_defaults(app_defaults_for_tests) # We don't want to depend upon the reported domain name of the # machine running the tests, nor upon the DNS setup of that # domain. Puppet.settings[:use_srv_records] = false # Longer keys are secure, but they sure make for some slow testing - both # in terms of generating keys, and in terms of anything the next step down # the line doing validation or whatever. Most tests don't care how long # or secure it is, just that it exists, so these are better and faster # defaults, in testing only. # # I would make these even shorter, but OpenSSL doesn't support anything # below 512 bits. Sad, really, because a 0 bit key would be just fine. Puppet[:keylength] = 512 # Although we setup a testing context during initialization, some tests # will end up creating their own context using the real context objects # and use the setting for the environments. In order to avoid those tests # having to deal with a missing environmentpath we can just set it right # here. Puppet[:environmentpath] = @environmentpath Puppet[:environment_timeout] = 0 end private_class_method :initialize_settings_before_each end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/transaction.rb�������������������������������������������������������������0000644�0052762�0001160�00000037360�13417161721�017760� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet' require 'puppet/util/tagging' require 'puppet/util/skip_tags' require 'puppet/application' require 'digest/sha1' require 'set' # the class that actually walks our resource/property tree, collects the changes, # and performs them # # @api private class Puppet::Transaction require 'puppet/transaction/additional_resource_generator' require 'puppet/transaction/event' require 'puppet/transaction/event_manager' require 'puppet/transaction/resource_harness' require 'puppet/resource/status' require 'puppet/transaction/persistence' attr_accessor :catalog, :ignoreschedules, :for_network_device # The report, once generated. attr_reader :report # Routes and stores any events and subscriptions. attr_reader :event_manager # Handles most of the actual interacting with resources attr_reader :resource_harness attr_reader :prefetched_providers, :prefetch_failed_providers # @!attribute [r] persistence # @return [Puppet::Transaction::Persistence] persistence object for cross # transaction storage. attr_reader :persistence include Puppet::Util include Puppet::Util::Tagging def initialize(catalog, report, prioritizer) @catalog = catalog @persistence = Puppet::Transaction::Persistence.new @report = report || Puppet::Transaction::Report.new(catalog.version, catalog.environment) @prioritizer = prioritizer @report.add_times(:config_retrieval, @catalog.retrieval_duration || 0) @event_manager = Puppet::Transaction::EventManager.new(self) @resource_harness = Puppet::Transaction::ResourceHarness.new(self) @prefetched_providers = Hash.new { |h,k| h[k] = {} } @prefetch_failed_providers = Hash.new { |h,k| h[k] = {} } @failed_dependencies_already_notified = Set.new() end # Invoke the pre_run_check hook in every resource in the catalog. # This should (only) be called by Transaction#evaluate before applying # the catalog. # # @see Puppet::Transaction#evaluate # @see Puppet::Type#pre_run_check # @raise [Puppet::Error] If any pre-run checks failed. # @return [void] def perform_pre_run_checks prerun_errors = {} @catalog.vertices.each do |res| begin res.pre_run_check rescue Puppet::Error => detail prerun_errors[res] = detail end end unless prerun_errors.empty? prerun_errors.each do |res, detail| res.log_exception(detail) end raise Puppet::Error, _("Some pre-run checks failed") end end # This method does all the actual work of running a transaction. It # collects all of the changes, executes them, and responds to any # necessary events. def evaluate(&block) block ||= method(:eval_resource) generator = AdditionalResourceGenerator.new(@catalog, nil, @prioritizer) @catalog.vertices.each { |resource| generator.generate_additional_resources(resource) } perform_pre_run_checks persistence.load if persistence.enabled?(catalog) Puppet.info _("Applying configuration version '%{version}'") % { version: catalog.version } if catalog.version continue_while = lambda { !stop_processing? } post_evalable_providers = Set.new pre_process = lambda do |resource| prov_class = resource.provider.class post_evalable_providers << prov_class if prov_class.respond_to?(:post_resource_eval) prefetch_if_necessary(resource) # If we generated resources, we don't know what they are now # blocking, so we opt to recompute it, rather than try to track every # change that would affect the number. relationship_graph.clear_blockers if generator.eval_generate(resource) end providerless_types = [] overly_deferred_resource_handler = lambda do |resource| # We don't automatically assign unsuitable providers, so if there # is one, it must have been selected by the user. return if missing_tags?(resource) if resource.provider resource.err _("Provider %{name} is not functional on this host") % { name: resource.provider.class.name } else providerless_types << resource.type end resource_status(resource).failed = true end canceled_resource_handler = lambda do |resource| resource_status(resource).skipped = true resource.debug "Transaction canceled, skipping" end teardown = lambda do # Just once per type. No need to punish the user. providerless_types.uniq.each do |type| Puppet.err _("Could not find a suitable provider for %{type}") % { type: type } end post_evalable_providers.each do |provider| begin provider.post_resource_eval rescue => detail Puppet.log_exception(detail, _("post_resource_eval failed for provider %{provider}") % { provider: provider }) end end persistence.save if persistence.enabled?(catalog) end # Graph cycles are returned as an array of arrays # - outer array is an array of cycles # - each inner array is an array of resources involved in a cycle # Short circuit resource evaluation if we detect cycle(s) in the graph. Mark # each corresponding resource as failed in the report before we fail to # ensure accurate reporting. graph_cycle_handler = lambda do |cycles| cycles.flatten.uniq.each do |resource| # We add a failed resource event to the status to ensure accurate # reporting through the event manager. resource_status(resource).fail_with_event(_('resource is part of a dependency cycle')) end raise Puppet::Error, _('One or more resource dependency cycles detected in graph') end # Generate the relationship graph, set up our generator to use it # for eval_generate, then kick off our traversal. generator.relationship_graph = relationship_graph relationship_graph.traverse(:while => continue_while, :pre_process => pre_process, :overly_deferred_resource_handler => overly_deferred_resource_handler, :canceled_resource_handler => canceled_resource_handler, :graph_cycle_handler => graph_cycle_handler, :teardown => teardown) do |resource| if resource.is_a?(Puppet::Type::Component) Puppet.warning _("Somehow left a component in the relationship graph") else resource.info _("Starting to evaluate the resource") if Puppet[:evaltrace] && @catalog.host_config? seconds = thinmark { block.call(resource) } resource.info _("Evaluated in %{seconds} seconds") % { seconds: "%0.2f" % seconds } if Puppet[:evaltrace] && @catalog.host_config? end end # if one or more resources has attempted and failed to generate resources, # report it if generator.resources_failed_to_generate report.resources_failed_to_generate = true end # mark the end of transaction evaluate. report.transaction_completed = true Puppet.debug "Finishing transaction #{object_id}" end # Wraps application run state check to flag need to interrupt processing def stop_processing? Puppet::Application.stop_requested? && catalog.host_config? end # Are there any failed resources in this transaction? def any_failed? report.resource_statuses.values.detect { |status| status.failed? || status.failed_to_restart? } end # Find all of the changed resources. def changed? report.resource_statuses.values.find_all { |status| status.changed }.collect { |status| catalog.resource(status.resource) } end def relationship_graph catalog.relationship_graph(@prioritizer) end def resource_status(resource) report.resource_statuses[resource.to_s] || add_resource_status(Puppet::Resource::Status.new(resource)) end # The tags we should be checking. def tags self.tags = Puppet[:tags] unless defined?(@tags) super end def skip_tags @skip_tags ||= Puppet::Util::SkipTags.new(Puppet[:skip_tags]).tags end def prefetch_if_necessary(resource) provider_class = resource.provider.class if !provider_class.respond_to?(:prefetch) or prefetched_providers[resource.type][provider_class.name] or prefetch_failed_providers[resource.type][provider_class.name] return end resources = resources_by_provider(resource.type, provider_class.name) if provider_class == resource.class.defaultprovider providerless_resources = resources_by_provider(resource.type, nil) providerless_resources.values.each {|res| res.provider = provider_class.name} resources.merge! providerless_resources end prefetch(provider_class, resources) end private # Apply all changes for a resource def apply(resource, ancestor = nil) status = resource_harness.evaluate(resource) add_resource_status(status) ancestor ||= resource if !(status.failed? || status.failed_to_restart?) event_manager.queue_events(ancestor, status.events) end rescue => detail resource.err _("Could not evaluate: %{detail}") % { detail: detail } end # Evaluate a single resource. def eval_resource(resource, ancestor = nil) propagate_failure(resource) if skip?(resource) resource_status(resource).skipped = true resource.debug("Resource is being skipped, unscheduling all events") event_manager.dequeue_all_events_for_resource(resource) persistence.copy_skipped(resource.ref) else resource_status(resource).scheduled = true apply(resource, ancestor) event_manager.process_events(resource) end end # Does this resource have any failed dependencies? def failed_dependencies?(resource) # When we introduced the :whit into the graph, to reduce the combinatorial # explosion of edges, we also ended up reporting failures for containers # like class and stage. This is undesirable; while just skipping the # output isn't perfect, it is RC-safe. --daniel 2011-06-07 suppress_report = (resource.class == Puppet::Type.type(:whit)) s = resource_status(resource) if s && s.dependency_failed? # See above. --daniel 2011-06-06 unless suppress_report then s.failed_dependencies.find_all { |d| !(@failed_dependencies_already_notified.include?(d.ref)) }.each do |dep| resource.notice _("Dependency %{dep} has failures: %{status}") % { dep: dep, status: resource_status(dep).failed } @failed_dependencies_already_notified.add(dep.ref) end end end s && s.dependency_failed? end # We need to know if a resource has any failed dependencies before # we try to process it. We keep track of this by keeping a list on # each resource of the failed dependencies, and incrementally # computing it as the union of the failed dependencies of each # first-order dependency. We have to do this as-we-go instead of # up-front at failure time because the graph may be mutated as we # walk it. def propagate_failure(resource) provider_class = resource.provider.class s = resource_status(resource) if prefetch_failed_providers[resource.type][provider_class.name] && !s.nil? message = _("Prefetch failed for %{type_name} provider '%{name}'") % { type_name: resource.type, name: provider_class.name } s.fail_with_event(message) end failed = Set.new relationship_graph.direct_dependencies_of(resource).each do |dep| s = resource_status(dep) next if s.nil? failed.merge(s.failed_dependencies) if s.dependency_failed? failed.add(dep) if s.failed? || s.failed_to_restart? end resource_status(resource).failed_dependencies = failed.to_a end # Should we ignore tags? def ignore_tags? ! @catalog.host_config? end def resources_by_provider(type_name, provider_name) unless @resources_by_provider @resources_by_provider = Hash.new { |h, k| h[k] = Hash.new { |h1, k1| h1[k1] = {} } } @catalog.vertices.each do |resource| if resource.class.attrclass(:provider) prov = resource.provider && resource.provider.class.name @resources_by_provider[resource.type][prov][resource.name] = resource end end end @resources_by_provider[type_name][provider_name] || {} end # Prefetch any providers that support it, yo. We don't support prefetching # types, just providers. def prefetch(provider_class, resources) type_name = provider_class.resource_type.name return if @prefetched_providers[type_name][provider_class.name] || @prefetch_failed_providers[type_name][provider_class.name] Puppet.debug "Prefetching #{provider_class.name} resources for #{type_name}" begin provider_class.prefetch(resources) rescue Exception => detail if !detail.is_a?(LoadError) && !detail.is_a?(Puppet::MissingCommand) raise unless Puppet.settings[:future_features] @prefetch_failed_providers[type_name][provider_class.name] = true end #TRANSLATORS `prefetch` is a function name and should not be translated message = _("Could not prefetch %{type_name} provider '%{name}': %{detail}") % { type_name: type_name, name: provider_class.name, detail: detail } Puppet.log_exception(detail, message) end @prefetched_providers[type_name][provider_class.name] = true end def add_resource_status(status) report.add_resource_status(status) end # Is the resource currently scheduled? def scheduled?(resource) self.ignoreschedules || resource_harness.scheduled?(resource) end # Should this resource be skipped? def skip?(resource) if skip_tags?(resource) resource.debug "Skipping with skip tags #{skip_tags.join(", ")}" elsif missing_tags?(resource) resource.debug "Not tagged with #{tags.join(", ")}" elsif ! scheduled?(resource) resource.debug "Not scheduled" elsif failed_dependencies?(resource) # When we introduced the :whit into the graph, to reduce the combinatorial # explosion of edges, we also ended up reporting failures for containers # like class and stage. This is undesirable; while just skipping the # output isn't perfect, it is RC-safe. --daniel 2011-06-07 unless resource.class == Puppet::Type.type(:whit) then resource.warning _("Skipping because of failed dependencies") end elsif resource_status(resource).failed? && @prefetch_failed_providers[resource.type][resource.provider.class.name] #Do not try to evaluate a resource with a known failed provider resource.warning _("Skipping because provider prefetch failed") elsif resource.virtual? resource.debug "Skipping because virtual" elsif !host_and_device_resource?(resource) && resource.appliable_to_host? && for_network_device resource.debug "Skipping host resources because running on a device" elsif !host_and_device_resource?(resource) && resource.appliable_to_device? && !for_network_device resource.debug "Skipping device resources because running on a posix host" else return false end true end def host_and_device_resource?(resource) resource.appliable_to_host? && resource.appliable_to_device? end # Is this resource tagged appropriately? def missing_tags?(resource) return false if ignore_tags? return false if tags.empty? not resource.tagged?(*tags) end def skip_tags?(resource) return false if ignore_tags? return false if skip_tags.empty? resource.tagged?(*skip_tags) end def split_qualified_tags? false end # These two methods are only made public to enable the existing spec tests to run # under rspec 3 (apparently rspec 2 didn't enforce access controls?). Please do not # treat these as part of a public API. # Possible future improvement: rewrite to not require access to private methods. public :skip? public :missing_tags? end require 'puppet/transaction/report' ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/transaction/���������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�017427� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/transaction/additional_resource_generator.rb�������������������������������0000644�0052762�0001160�00000020034�13417161721�026033� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Adds additional resources to the catalog and relationship graph that are # generated by existing resources. There are two ways that a resource can # generate additional resources, either through the #generate method or the # #eval_generate method. # # @api private class Puppet::Transaction::AdditionalResourceGenerator attr_writer :relationship_graph # [boolean] true if any resource has attempted and failed to generate resources attr_reader :resources_failed_to_generate def initialize(catalog, relationship_graph, prioritizer) @catalog = catalog @relationship_graph = relationship_graph @prioritizer = prioritizer @resources_failed_to_generate = false end def generate_additional_resources(resource) return unless resource.respond_to?(:generate) begin generated = resource.generate rescue => detail @resources_failed_to_generate = true resource.log_exception(detail, _("Failed to generate additional resources using 'generate': %{detail}") % { detail: detail }) end return unless generated generated = [generated] unless generated.is_a?(Array) generated.collect! do |res| @catalog.resource(res.ref) || res end unless resource.depthfirst? # This is reversed because PUP-1963 changed how generated # resources were added to the catalog. It exists for backwards # compatibility only, and can probably be removed in Puppet 5 # # Previously, resources were given sequential priorities in the # relationship graph. Post-1963, resources are added to the # catalog one by one adjacent to the parent resource. This # causes an implicit reversal of their application order from # the old code. The reverse makes it all work like it did. generated.reverse! end generated.each do |res| add_resource(res, resource) add_generated_directed_dependency(resource, res) generate_additional_resources(res) end end def eval_generate(resource) return false unless resource.respond_to?(:eval_generate) raise Puppet::DevError, _("Depthfirst resources are not supported by eval_generate") if resource.depthfirst? begin generated = replace_duplicates_with_catalog_resources(resource.eval_generate) return false if generated.empty? rescue => detail @resources_failed_to_generate = true #TRANSLATORS eval_generate is a method name and should be left untranslated resource.log_exception(detail, _("Failed to generate additional resources using 'eval_generate': %{detail}") % { detail: detail }) return false end add_resources(generated, resource) made = Hash[generated.map(&:name).zip(generated)] contain_generated_resources_in(resource, made) connect_resources_to_ancestors(resource, made) true end private def replace_duplicates_with_catalog_resources(generated) generated.collect do |generated_resource| @catalog.resource(generated_resource.ref) || generated_resource end end def contain_generated_resources_in(resource, made) sentinel = Puppet::Type.type(:whit).new(:name => "completed_#{resource.title}", :catalog => resource.catalog) # Tag the completed whit with all of the tags of the generating resource # except the type name to allow event propogation to succeed beyond the whit # "boundary" when filtering resources with tags. We include auto-generated # tags such as the type name to support implicit filtering as well as # explicit. Note that resource#tags returns a duplicate of the resource's # tags. sentinel.merge_tags_from(resource) priority = @prioritizer.generate_priority_contained_in(resource, sentinel) @relationship_graph.add_vertex(sentinel, priority) redirect_edges_to_sentinel(resource, sentinel, made) made.values.each do |res| # This resource isn't 'completed' until each child has run add_conditional_directed_dependency(res, sentinel, Puppet::Graph::RelationshipGraph::Default_label) end # This edge allows the resource's events to propagate, though it isn't # strictly necessary for ordering purposes add_conditional_directed_dependency(resource, sentinel, Puppet::Graph::RelationshipGraph::Default_label) end def redirect_edges_to_sentinel(resource, sentinel, made) @relationship_graph.adjacent(resource, :direction => :out, :type => :edges).each do |e| next if made[e.target.name] @relationship_graph.add_relationship(sentinel, e.target, e.label) @relationship_graph.remove_edge! e end end def connect_resources_to_ancestors(resource, made) made.values.each do |res| # Depend on the nearest ancestor we generated, falling back to the # resource if we have none parent_name = res.ancestors.find { |a| made[a] and made[a] != res } parent = made[parent_name] || resource add_conditional_directed_dependency(parent, res) end end def add_resources(generated, resource) generated.each do |res| priority = @prioritizer.generate_priority_contained_in(resource, res) add_resource(res, resource, priority) end end def add_resource(res, parent_resource, priority=nil) if @catalog.resource(res.ref).nil? res.merge_tags_from(parent_resource) if parent_resource.depthfirst? @catalog.add_resource_before(parent_resource, res) else @catalog.add_resource_after(parent_resource, res) end @catalog.add_edge(@catalog.container_of(parent_resource), res) if @relationship_graph && priority # If we have a relationship_graph we should add the resource # to it (this is an eval_generate). If we don't, then the # relationship_graph has not yet been created and we can skip # adding it. @relationship_graph.add_vertex(res, priority) end res.finish end end # add correct edge for depth- or breadth- first traversal of # generated resource. Skip generating the edge if there is already # some sort of edge between the two resources. def add_generated_directed_dependency(parent, child, label=nil) if parent.depthfirst? source = child target = parent else source = parent target = child end # For each potential relationship metaparam, check if parent or # child references the other. If there are none, we should add our # edge. edge_exists = Puppet::Type.relationship_params.any? { |param| param_sym = param.name.to_sym if parent[param_sym] parent_contains = parent[param_sym].any? { |res| res.ref == child.ref } else parent_contains = false end if child[param_sym] child_contains = child[param_sym].any? { |res| res.ref == parent.ref } else child_contains = false end parent_contains || child_contains } if not edge_exists # We *cannot* use target.to_resource here! # # For reasons that are beyond my (and, perhaps, human) # comprehension, to_resource will call retrieve. This is # problematic if a generated resource needs the system to be # changed by a previous resource (think a file on a path # controlled by a mount resource). # # Instead of using to_resource, we just construct a resource as # if the arguments to the Type instance had been passed to a # Resource instead. resource = ::Puppet::Resource.new(target.class, target.title, :parameters => target.original_parameters) source[:before] ||= [] source[:before] << resource end end # Copy an important relationships from the parent to the newly-generated # child resource. def add_conditional_directed_dependency(parent, child, label=nil) @relationship_graph.add_vertex(child) edge = parent.depthfirst? ? [child, parent] : [parent, child] if @relationship_graph.edge?(*edge.reverse) parent.debug "Skipping automatic relationship to #{child}" else @relationship_graph.add_relationship(edge[0],edge[1],label) end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/transaction/event_manager.rb�����������������������������������������������0000644�0052762�0001160�00000015344�13417161721�022571� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/transaction' # This class stores, routes, and responds to events generated while evaluating # a transaction. # # @api private class Puppet::Transaction::EventManager # @!attribute [r] transaction # @return [Puppet::Transaction] The transaction associated with this event manager. attr_reader :transaction # @!attribute [r] events # @todo Determine if this instance variable is used for anything aside from testing. # @return [Array<Puppet::Transaction::Events>] A list of events that can be # handled by the target resource. Events that cannot be handled by the # target resource will be discarded. attr_reader :events def initialize(transaction) @transaction = transaction @event_queues = {} @events = [] end def relationship_graph transaction.relationship_graph end # Respond to any queued events for this resource. def process_events(resource) restarted = false queued_events(resource) do |callback, events| r = process_callback(resource, callback, events) restarted ||= r end if restarted queue_events(resource, [resource.event(:name => :restarted, :status => "success")]) transaction.resource_status(resource).restarted = true end end # Queues events for other resources to respond to. All of these events have # to be from the same resource. # # @param resource [Puppet::Type] The resource generating the given events # @param events [Array<Puppet::Transaction::Event>] All events generated by this resource # @return [void] def queue_events(resource, events) #@events += events # Do some basic normalization so we're not doing so many # graph queries for large sets of events. events.inject({}) do |collection, event| collection[event.name] ||= [] collection[event.name] << event collection end.collect do |name, list| # It doesn't matter which event we use - they all have the same source # and name here. event = list[0] # Collect the targets of any subscriptions to those events. We pass # the parent resource in so it will override the source in the events, # since eval_generated children can't have direct relationships. received = (event.name != :restarted) relationship_graph.matching_edges(event, resource).each do |edge| received ||= true unless edge.target.is_a?(Puppet::Type.type(:whit)) next unless method = edge.callback next unless edge.target.respond_to?(method) queue_events_for_resource(resource, edge.target, method, list) end @events << event if received queue_events_for_resource(resource, resource, :refresh, [event]) if resource.self_refresh? and ! resource.deleting? end dequeue_events_for_resource(resource, :refresh) if events.detect { |e| e.invalidate_refreshes } end def dequeue_all_events_for_resource(target) callbacks = @event_queues[target] if callbacks && !callbacks.empty? target.info _("Unscheduling all events on %{target}") % { target: target } @event_queues[target] = {} end end def dequeue_events_for_resource(target, callback) target.info _("Unscheduling %{callback} on %{target}") % { callback: callback, target: target } @event_queues[target][callback] = [] if @event_queues[target] end def queue_events_for_resource(source, target, callback, events) whit = Puppet::Type.type(:whit) # The message that a resource is refreshing the completed-whit for its own class # is extremely counter-intuitive. Basically everything else is easy to understand, # if you suppress the whit-lookingness of the whit resources refreshing_c_whit = target.is_a?(whit) && target.name =~ /^completed_/ if refreshing_c_whit source.debug "The container #{target} will propagate my #{callback} event" else source.info _("Scheduling %{callback} of %{target}") % { callback: callback, target: target } end @event_queues[target] ||= {} @event_queues[target][callback] ||= [] @event_queues[target][callback].concat(events) end def queued_events(resource) return unless callbacks = @event_queues[resource] callbacks.each do |callback, events| yield callback, events unless events.empty? end end private # Should the callback for this resource be invoked? # @param resource [Puppet::Type] The resource to be refreshed # @param events [Array<Puppet::Transaction::Event>] A list of events # associated with this callback and resource. # @return [true, false] Whether the callback should be run. def process_callback?(resource, events) !(events.all? { |e| e.status == "noop" } || resource.noop?) end # Processes callbacks for a given resource. # # @param resource [Puppet::Type] The resource receiving the callback. # @param callback [Symbol] The name of the callback method that will be invoked. # @param events [Array<Puppet::Transaction::Event>] A list of events # associated with this callback and resource. # @return [true, false] Whether the callback was successfully run. def process_callback(resource, callback, events) if !process_callback?(resource, events) process_noop_events(resource, callback, events) return false end resource.send(callback) if not resource.is_a?(Puppet::Type.type(:whit)) message = n_("Triggered '%{callback}' from %{count} event", "Triggered '%{callback}' from %{count} events", events.length) % { count: events.length, callback: callback } resource.notice message add_callback_status_event(resource, callback, message, "success") end return true rescue => detail resource_error_message = _("Failed to call %{callback}: %{detail}") % { callback: callback, detail: detail } resource.err resource_error_message if not resource.is_a?(Puppet::Type.type(:whit)) add_callback_status_event(resource, callback, resource_error_message, "failure") end transaction.resource_status(resource).failed_to_restart = true transaction.resource_status(resource).fail_with_event(resource_error_message) resource.log_exception(detail) return false end def add_callback_status_event(resource, callback, message, status) options = { message: message, status: status, name: callback.to_s } event = resource.event options transaction.resource_status(resource) << event if event end def process_noop_events(resource, callback, events) resource.notice n_("Would have triggered '%{callback}' from %{count} event", "Would have triggered '%{callback}' from %{count} events", events.length) % { count: events.length, callback: callback } # And then add an event for it. queue_events(resource, [resource.event(:status => "noop", :name => :noop_restart)]) end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/transaction/report.rb������������������������������������������������������0000644�0052762�0001160�00000034227�13417161721�021272� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet' require 'puppet/indirector' # This class is used to report what happens on a client. # There are two types of data in a report; _Logs_ and _Metrics_. # # * **Logs** - are the output that each change produces. # * **Metrics** - are all of the numerical data involved in the transaction. # # Use {Puppet::Reports} class to create a new custom report type. This class is indirectly used # as a source of data to report in such a registered report. # # ##Metrics # There are three types of metrics in each report, and each type of metric has one or more values. # # * Time: Keeps track of how long things took. # * Total: Total time for the configuration run # * File: # * Exec: # * User: # * Group: # * Config Retrieval: How long the configuration took to retrieve # * Service: # * Package: # * Resources: Keeps track of the following stats: # * Total: The total number of resources being managed # * Skipped: How many resources were skipped, because of either tagging or scheduling restrictions # * Scheduled: How many resources met any scheduling restrictions # * Out of Sync: How many resources were out of sync # * Applied: How many resources were attempted to be fixed # * Failed: How many resources were not successfully fixed # * Restarted: How many resources were restarted because their dependencies changed # * Failed Restarts: How many resources could not be restarted # * Changes: The total number of changes in the transaction. # # @api public class Puppet::Transaction::Report include Puppet::Util::PsychSupport extend Puppet::Indirector indirects :report, :terminus_class => :processor # The version of the configuration # @todo Uncertain what this is? # @return [???] the configuration version attr_accessor :configuration_version # An agent generated transaction uuid, useful for connecting catalog and report # @return [String] uuid attr_accessor :transaction_uuid # The id of the code input to the compiler. attr_accessor :code_id # The id of the job responsible for this run. attr_accessor :job_id # A master generated catalog uuid, useful for connecting a single catalog to multiple reports. attr_accessor :catalog_uuid # Whether a cached catalog was used in the run, and if so, the reason that it was used. # @return [String] One of the values: 'not_used', 'explicitly_requested', # or 'on_failure' attr_accessor :cached_catalog_status # Contains the name and port of the master that was successfully contacted # @return [String] a string of the format 'servername:port' attr_accessor :master_used # The host name for which the report is generated # @return [String] the host name attr_accessor :host # The name of the environment the host is in # @return [String] the environment name attr_accessor :environment # Whether there are changes that we decided not to apply because of noop # @return [Boolean] # attr_accessor :noop_pending # A hash with a map from resource to status # @return [Hash{String => Puppet::Resource::Status}] Resource name to status. attr_reader :resource_statuses # A list of log messages. # @return [Array<Puppet::Util::Log>] logged messages attr_reader :logs # A hash of metric name to metric value. # @return [Hash<{String => Object}>] A map of metric name to value. # @todo Uncertain if all values are numbers - now marked as Object. # attr_reader :metrics # The time when the report data was generated. # @return [Time] A time object indicating when the report data was generated # attr_reader :time # The status of the client run is an enumeration: 'failed', 'changed' or 'unchanged' # @return [String] the status of the run - one of the values 'failed', 'changed', or 'unchanged' # attr_reader :status # @return [String] The Puppet version in String form. # @see Puppet::version() # attr_reader :puppet_version # @return [Integer] report format version number. This value is constant for # a given version of Puppet; it is incremented when a new release of Puppet # changes the API for the various objects that make up a report. # attr_reader :report_format # Whether the puppet run was started in noop mode # @return [Boolean] # attr_reader :noop # @!attribute [r] corrective_change # @return [Boolean] true if the report contains any events and resources that had # corrective changes. attr_reader :corrective_change # @return [Boolean] true if one or more resources attempted to generate # resources and failed # attr_accessor :resources_failed_to_generate # @return [Boolean] true if the transaction completed it's evaluate # attr_accessor :transaction_completed TOTAL = "total".freeze def self.from_data_hash(data) obj = self.allocate obj.initialize_from_hash(data) obj end def as_logging_destination(&block) Puppet::Util::Log.with_destination(self, &block) end # @api private def <<(msg) @logs << msg self end # @api private def add_times(name, value, accumulate = true) if @external_times[name] && accumulate @external_times[name] += value else @external_times[name] = value end end # @api private def add_metric(name, hash) metric = Puppet::Util::Metric.new(name) hash.each do |metric_name, value| metric.newvalue(metric_name, value) end @metrics[metric.name] = metric metric end # @api private def add_resource_status(status) @resource_statuses[status.resource] = status end # @api private def compute_status(resource_metrics, change_metric) if resources_failed_to_generate || !transaction_completed || (resource_metrics["failed"] || 0) > 0 || (resource_metrics["failed_to_restart"] || 0) > 0 'failed' elsif change_metric > 0 'changed' else 'unchanged' end end # @api private def has_noop_events?(resource) resource.events.any? { |event| event.status == 'noop' } end # @api private def prune_internal_data resource_statuses.delete_if {|name,res| res.resource_type == 'Whit'} end # @api private def finalize_report prune_internal_data calculate_report_corrective_change resource_metrics = add_metric(:resources, calculate_resource_metrics) add_metric(:time, calculate_time_metrics) change_metric = calculate_change_metric add_metric(:changes, {TOTAL => change_metric}) add_metric(:events, calculate_event_metrics) @status = compute_status(resource_metrics, change_metric) @noop_pending = @resource_statuses.any? { |name,res| has_noop_events?(res) } end # @api private def initialize(configuration_version=nil, environment=nil, transaction_uuid=nil, job_id=nil) @metrics = {} @logs = [] @resource_statuses = {} @external_times ||= {} @host = Puppet[:node_name_value] @time = Time.now @report_format = 10 @puppet_version = Puppet.version @configuration_version = configuration_version @transaction_uuid = transaction_uuid @code_id = nil @job_id = job_id @catalog_uuid = nil @cached_catalog_status = nil @master_used = nil @environment = environment @status = 'failed' # assume failed until the report is finalized @noop = Puppet[:noop] @noop_pending = false @corrective_change = false @transaction_completed = false end # @api private def initialize_from_hash(data) @puppet_version = data['puppet_version'] @report_format = data['report_format'] @configuration_version = data['configuration_version'] @transaction_uuid = data['transaction_uuid'] @environment = data['environment'] @status = data['status'] @transaction_completed = data['transaction_completed'] @noop = data['noop'] @noop_pending = data['noop_pending'] @host = data['host'] @time = data['time'] @corrective_change = data['corrective_change'] if master_used = data['master_used'] @master_used = master_used end if catalog_uuid = data['catalog_uuid'] @catalog_uuid = catalog_uuid end if job_id = data['job_id'] @job_id = job_id end if code_id = data['code_id'] @code_id = code_id end if cached_catalog_status = data['cached_catalog_status'] @cached_catalog_status = cached_catalog_status end if @time.is_a? String @time = Time.parse(@time) end @metrics = {} data['metrics'].each do |name, hash| # Older versions contain tags that causes Psych to create instances directly @metrics[name] = hash.is_a?(Puppet::Util::Metric) ? hash : Puppet::Util::Metric.from_data_hash(hash) end @logs = data['logs'].map do |record| # Older versions contain tags that causes Psych to create instances directly record.is_a?(Puppet::Util::Log) ? record : Puppet::Util::Log.from_data_hash(record) end @resource_statuses = {} data['resource_statuses'].map do |key, rs| @resource_statuses[key] = if rs == Puppet::Resource::EMPTY_HASH nil else # Older versions contain tags that causes Psych to create instances directly rs.is_a?(Puppet::Resource::Status) ? rs : Puppet::Resource::Status.from_data_hash(rs) end end end def to_data_hash hash = { 'host' => @host, 'time' => @time.iso8601(9), 'configuration_version' => @configuration_version, 'transaction_uuid' => @transaction_uuid, 'report_format' => @report_format, 'puppet_version' => @puppet_version, 'status' => @status, 'transaction_completed' => @transaction_completed, 'noop' => @noop, 'noop_pending' => @noop_pending, 'environment' => @environment, 'logs' => @logs.map { |log| log.to_data_hash }, 'metrics' => Hash[@metrics.map { |key, metric| [key, metric.to_data_hash] }], 'resource_statuses' => Hash[@resource_statuses.map { |key, rs| [key, rs.nil? ? nil : rs.to_data_hash] }], 'corrective_change' => @corrective_change, } # The following is include only when set hash['master_used'] = @master_used unless @master_used.nil? hash['catalog_uuid'] = @catalog_uuid unless @catalog_uuid.nil? hash['code_id'] = @code_id unless @code_id.nil? hash['job_id'] = @job_id unless @job_id.nil? hash['cached_catalog_status'] = @cached_catalog_status unless @cached_catalog_status.nil? hash end # @return [String] the host name # @api public # def name host end # Provide a human readable textual summary of this report. # @note This is intended for debugging purposes # @return [String] A string with a textual summary of this report. # @api public # def summary report = raw_summary ret = "" report.keys.sort { |a,b| a.to_s <=> b.to_s }.each do |key| ret += "#{Puppet::Util::Metric.labelize(key)}:\n" report[key].keys.sort { |a,b| # sort by label if a == TOTAL 1 elsif b == TOTAL -1 else report[key][a].to_s <=> report[key][b].to_s end }.each do |label| value = report[key][label] next if value == 0 value = "%0.2f" % value if value.is_a?(Float) ret += " %15s %s\n" % [Puppet::Util::Metric.labelize(label) + ":", value] end end ret end # Provides a raw hash summary of this report. # @return [Hash<{String => Object}>] A hash with metrics key to value map # @api public # def raw_summary report = { "version" => { "config" => configuration_version, "puppet" => Puppet.version } } @metrics.each do |name, metric| key = metric.name.to_s report[key] = {} metric.values.each do |metric_name, label, value| report[key][metric_name.to_s] = value end report[key][TOTAL] = 0 unless key == "time" or report[key].include?(TOTAL) end (report["time"] ||= {})["last_run"] = Time.now.tv_sec report end # Computes a single number that represents the report's status. # The computation is based on the contents of this report's metrics. # The resulting number is a bitmask where # individual bits represent the presence of different metrics. # # * 0x2 set if there are changes # * 0x4 set if there are resource failures or resources that failed to restart # @return [Integer] A bitmask where 0x2 is set if there are changes, and 0x4 is set of there are failures. # @api public # def exit_status status = 0 if @metrics["changes"] && @metrics["changes"][TOTAL] && @metrics["resources"] && @metrics["resources"]["failed"] && @metrics["resources"]["failed_to_restart"] status |= 2 if @metrics["changes"][TOTAL] > 0 status |= 4 if @metrics["resources"]["failed"] > 0 status |= 4 if @metrics["resources"]["failed_to_restart"] > 0 else status = -1 end status end private # Mark the report as corrective, if there are any resource_status marked corrective. def calculate_report_corrective_change @corrective_change = resource_statuses.any? do |name, status| status.corrective_change end end def calculate_change_metric resource_statuses.map { |name, status| status.change_count || 0 }.inject(0) { |a,b| a+b } end def calculate_event_metrics metrics = Hash.new(0) %w{total failure success}.each { |m| metrics[m] = 0 } resource_statuses.each do |name, status| metrics[TOTAL] += status.events.length status.events.each do |event| metrics[event.status] += 1 end end metrics end def calculate_resource_metrics metrics = {} metrics[TOTAL] = resource_statuses.length # force every resource key in the report to be present # even if no resources is in this given state Puppet::Resource::Status::STATES.each do |state| metrics[state.to_s] = 0 end resource_statuses.each do |name, status| Puppet::Resource::Status::STATES.each do |state| metrics[state.to_s] += 1 if status.send(state) end end metrics end def calculate_time_metrics metrics = Hash.new(0) resource_statuses.each do |name, status| metrics[status.resource_type.downcase] += status.evaluation_time if status.evaluation_time end @external_times.each do |name, value| metrics[name.to_s.downcase] = value end metrics end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/transaction/resource_harness.rb��������������������������������������������0000644�0052762�0001160�00000030105�13417161721�023320� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/resource/status' class Puppet::Transaction::ResourceHarness NO_ACTION = Object.new extend Forwardable def_delegators :@transaction, :relationship_graph attr_reader :transaction def initialize(transaction) @transaction = transaction @persistence = transaction.persistence end def evaluate(resource) status = Puppet::Resource::Status.new(resource) begin context = ResourceApplicationContext.from_resource(resource, status) perform_changes(resource, context) if status.changed? && ! resource.noop? cache(resource, :synced, Time.now) resource.flush if resource.respond_to?(:flush) end rescue => detail status.failed_because(detail) ensure status.evaluation_time = Time.now - status.time end status end def scheduled?(resource) return true if Puppet[:ignoreschedules] return true unless schedule = schedule(resource) # We use 'checked' here instead of 'synced' because otherwise we'll # end up checking most resources most times, because they will generally # have been synced a long time ago (e.g., a file only gets updated # once a month on the server and its schedule is daily; the last sync time # will have been a month ago, so we'd end up checking every run). schedule.match?(cached(resource, :checked).to_i) end def schedule(resource) unless resource.catalog resource.warning _("Cannot schedule without a schedule-containing catalog") return nil end return nil unless name = resource[:schedule] resource.catalog.resource(:schedule, name) || resource.fail(_("Could not find schedule %{name}") % { name: name }) end # Used mostly for scheduling and auditing at this point. def cached(resource, name) Puppet::Util::Storage.cache(resource)[name] end # Used mostly for scheduling and auditing at this point. def cache(resource, name, value) Puppet::Util::Storage.cache(resource)[name] = value end private def perform_changes(resource, context) cache(resource, :checked, Time.now) # Record the current state in state.yml. context.audited_params.each do |param| cache(resource, param, context.current_values[param]) end ensure_param = resource.parameter(:ensure) if ensure_param && ensure_param.should ensure_event = sync_if_needed(ensure_param, context) else ensure_event = NO_ACTION end if ensure_event == NO_ACTION if context.resource_present? resource.properties.each do |param| sync_if_needed(param, context) end else resource.debug("Nothing to manage: no ensure and the resource doesn't exist") end end capture_audit_events(resource, context) persist_system_values(resource, context) end # We persist the last known values for the properties of a resource after resource # application. # @param [Puppet::Type] resource resource whose values we are to persist. # @param [ResourceApplicationContent] context the application context to operate on. def persist_system_values(resource, context) param_to_event = {} context.status.events.each do |ev| param_to_event[ev.property] = ev end context.system_value_params.each do |pname, param| @persistence.set_system_value(resource.ref, pname.to_s, new_system_value(param, param_to_event[pname.to_s], @persistence.get_system_value(resource.ref, pname.to_s))) end end def sync_if_needed(param, context) historical_value = context.historical_values[param.name] current_value = context.current_values[param.name] do_audit = context.audited_params.include?(param.name) begin if param.should && !param.safe_insync?(current_value) event = create_change_event(param, current_value, historical_value) if do_audit event = audit_event(event, param, context) end brief_audit_message = audit_message(param, do_audit, historical_value, current_value) if param.noop noop(event, param, current_value, brief_audit_message) else sync(event, param, current_value, brief_audit_message) end event else NO_ACTION end rescue => detail # Execution will continue on StandardErrors, just store the event Puppet.log_exception(detail) event = create_change_event(param, current_value, historical_value) event.status = "failure" event.message = param.format(_("change from %s to %s failed: "), param.is_to_s(current_value), param.should_to_s(param.should)) + detail.to_s event rescue Exception => detail # Execution will halt on Exceptions, they get raised to the application event = create_change_event(param, current_value, historical_value) event.status = "failure" event.message = param.format(_("change from %s to %s failed: "), param.is_to_s(current_value), param.should_to_s(param.should)) + detail.to_s raise ensure if event name = param.name.to_s event.message ||= _("could not create change error message for %{name}") % { name: name } event.calculate_corrective_change(@persistence.get_system_value(context.resource.ref, name)) context.record(event) event.send_log context.synced_params << param.name end end end def create_change_event(property, current_value, historical_value) options = {} should = property.should if property.sensitive options[:previous_value] = current_value.nil? ? nil : '[redacted]' options[:desired_value] = should.nil? ? nil : '[redacted]' options[:historical_value] = historical_value.nil? ? nil : '[redacted]' else options[:previous_value] = current_value options[:desired_value] = should options[:historical_value] = historical_value end property.event(options) end # This method is an ugly hack because, given a Time object with nanosecond # resolution, roundtripped through YAML serialization, the Time object will # be truncated to microseconds. # For audit purposes, this code special cases this comparison, and compares # the two objects by their second and microsecond components. tv_sec is the # number of seconds since the epoch, and tv_usec is only the microsecond # portion of time. def are_audited_values_equal(a, b) a == b || (a.is_a?(Time) && b.is_a?(Time) && a.tv_sec == b.tv_sec && a.tv_usec == b.tv_usec) end private :are_audited_values_equal # Populate an existing event with audit information. # # @param event [Puppet::Transaction::Event] The event to be populated. # @param property [Puppet::Property] The property being audited. # @param context [ResourceApplicationContext] # # @return [Puppet::Transaction::Event] The given event, populated with the audit information. def audit_event(event, property, context) event.audited = true event.status = "audit" # The event we've been provided might have been redacted so we need to use the state stored within # the resource application context to see if an event was actually generated. if !are_audited_values_equal(context.historical_values[property.name], context.current_values[property.name]) event.message = property.format(_("audit change: previously recorded value %s has been changed to %s"), property.is_to_s(event.historical_value), property.is_to_s(event.previous_value)) end event end def audit_message(param, do_audit, historical_value, current_value) if do_audit && historical_value && !are_audited_values_equal(historical_value, current_value) param.format(_(" (previously recorded value was %s)"), param.is_to_s(historical_value)) else "" end end def noop(event, param, current_value, audit_message) event.message = param.format(_("current_value %s, should be %s (noop)"), param.is_to_s(current_value), param.should_to_s(param.should)) + audit_message.to_s event.status = "noop" end def sync(event, param, current_value, audit_message) param.sync if param.sensitive event.message = param.format(_("changed %s to %s"), param.is_to_s(current_value), param.should_to_s(param.should)) + audit_message.to_s else event.message = "#{param.change_to_s(current_value, param.should)}#{audit_message}" end event.status = "success" end def capture_audit_events(resource, context) context.audited_params.each do |param_name| if context.historical_values.include?(param_name) if !are_audited_values_equal(context.historical_values[param_name], context.current_values[param_name]) && !context.synced_params.include?(param_name) parameter = resource.parameter(param_name) event = audit_event(create_change_event(parameter, context.current_values[param_name], context.historical_values[param_name]), parameter, context) event.send_log context.record(event) end else property = resource.property(param_name) property.notice(property.format(_("audit change: newly-recorded value %s"), context.current_values[param_name])) end end end # Given an event and its property, calculate the system_value to persist # for future calculations. # @param [Puppet::Transaction::Event] event event to use for processing # @param [Puppet::Property] property correlating property # @param [Object] old_system_value system_value from last transaction # @return [Object] system_value to be used for next transaction def new_system_value(property, event, old_system_value) if event && event.status != "success" # For non-success events, we persist the old_system_value if it is defined, # or use the event previous_value. # If we're using the event previous_value, we ensure that it's # an array. This is needed because properties assume that their # `should` value is an array, and we will use this value later # on in property insync? logic. event_value = [event.previous_value] unless event.previous_value.is_a?(Array) old_system_value.nil? ? event_value : old_system_value else # For non events, or for success cases, we just want to store # the parameters agent value. # We use instance_variable_get here because we want this process to bypass any # munging/unmunging or validation that the property might try to do, since those # operations may not be correctly implemented for custom types. property.instance_variable_get(:@should) end end # @api private ResourceApplicationContext = Struct.new(:resource, :current_values, :historical_values, :audited_params, :synced_params, :status, :system_value_params) do def self.from_resource(resource, status) ResourceApplicationContext.new(resource, resource.retrieve_resource.to_hash, Puppet::Util::Storage.cache(resource).dup, (resource[:audit] || []).map { |p| p.to_sym }, [], status, resource.parameters.select { |n,p| p.is_a?(Puppet::Property) && !p.sensitive }) end def resource_present? resource.present?(current_values) end def record(event) status << event end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/transaction/event.rb�������������������������������������������������������0000644�0052762�0001160�00000010265�13417161721�021074� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/transaction' require 'puppet/util/tagging' require 'puppet/util/logging' require 'puppet/util/methodhelper' require 'puppet/network/format_support' # A simple struct for storing what happens on the system. class Puppet::Transaction::Event include Puppet::Util::MethodHelper include Puppet::Util::Tagging include Puppet::Util::Logging include Puppet::Network::FormatSupport ATTRIBUTES = [:name, :resource, :property, :previous_value, :desired_value, :historical_value, :status, :message, :file, :line, :source_description, :audited, :invalidate_refreshes, :redacted, :corrective_change] attr_accessor(*ATTRIBUTES) attr_accessor :time attr_reader :default_log_level EVENT_STATUSES = %w{noop success failure audit} def self.from_data_hash(data) obj = self.allocate obj.initialize_from_hash(data) obj end def initialize(options = {}) @audited = false @redacted = false @corrective_change = false set_options(options) @time = Time.now end def eql?(event) self.class == event.class && ATTRIBUTES.all? { |attr| send(attr).eql?(event.send(attr)) } end alias == eql? def initialize_from_hash(data) data = Puppet::Pops::Serialization::FromDataConverter.convert(data, { :allow_unresolved => true, :loader => Puppet::Pops::Loaders.static_loader }) @audited = data['audited'] @property = data['property'] @previous_value = data['previous_value'] @desired_value = data['desired_value'] @historical_value = data['historical_value'] @message = data['message'] @name = data['name'].intern if data['name'] @status = data['status'] @time = data['time'] @time = Time.parse(@time) if @time.is_a? String @redacted = data.fetch('redacted', false) @corrective_change = data['corrective_change'] end def to_data_hash hash = { 'audited' => @audited, 'property' => @property, 'previous_value' => @previous_value, 'desired_value' => @desired_value, 'historical_value' => @historical_value, 'message' => @message, 'name' => @name.nil? ? nil : @name.to_s, 'status' => @status, 'time' => @time.iso8601(9), 'redacted' => @redacted, 'corrective_change' => @corrective_change, } Puppet::Pops::Serialization::ToDataConverter.convert(hash, { :rich_data => true, :symbol_as_string => true, :local_reference => false, :type_by_reference => true, :message_prefix => 'Event' }) end def property=(prop) @property_instance = prop @property = prop.to_s end def resource=(res) if res.respond_to?(:[]) and level = res[:loglevel] @default_log_level = level end @resource = res.to_s end def send_log super(log_level, message) end def status=(value) raise ArgumentError, _("Event status can only be %{statuses}") % { statuses: EVENT_STATUSES.join(', ') } unless EVENT_STATUSES.include?(value) @status = value end def to_s message end def inspect %Q(#<#{self.class.name} @name="#{@name.inspect}" @message="#{@message.inspect}">) end # Calculate and set the corrective_change parameter, based on the old_system_value of the property. # @param [Object] old_system_value system_value from last transaction # @return [bool] true if this is a corrective_change def calculate_corrective_change(old_system_value) # Only idempotent properties, and cases where we have an old system_value # are corrective_changes. if @property_instance.idempotent? && !@property_instance.sensitive && !old_system_value.nil? # If the values aren't insync, we have confirmed a corrective_change insync = @property_instance.insync_values?(old_system_value, previous_value) # Preserve the nil state, but flip true/false @corrective_change = insync.nil? ? nil : !insync else @corrective_change = false end end private # If it's a failure, use 'err', else use either the resource's log level (if available) # or 'notice'. def log_level status == "failure" ? :err : (@default_log_level || :notice) end # Used by the Logging module def log_source source_description || property || resource end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/transaction/persistence.rb�������������������������������������������������0000644�0052762�0001160�00000007052�13417161721�022277� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'yaml' require 'puppet/util/yaml' # A persistence store implementation for storing information between # transaction runs for the purposes of information inference (such # as calculating corrective_change). # @api private class Puppet::Transaction::Persistence def initialize @old_data = {} @new_data = {"resources" => {}} end # Obtain the full raw data from the persistence store. # @return [Hash] hash of data stored in persistence store def data @old_data end # Retrieve the system value using the resource and parameter name # @param [String] resource_name name of resource # @param [String] param_name name of the parameter # @return [Object,nil] the system_value def get_system_value(resource_name, param_name) if !@old_data["resources"].nil? && !@old_data["resources"][resource_name].nil? && !@old_data["resources"][resource_name]["parameters"].nil? && !@old_data["resources"][resource_name]["parameters"][param_name].nil? @old_data["resources"][resource_name]["parameters"][param_name]["system_value"] else nil end end def set_system_value(resource_name, param_name, value) @new_data["resources"] ||= {} @new_data["resources"][resource_name] ||= {} @new_data["resources"][resource_name]["parameters"] ||= {} @new_data["resources"][resource_name]["parameters"][param_name] ||= {} @new_data["resources"][resource_name]["parameters"][param_name]["system_value"] = value end def copy_skipped(resource_name) @old_data["resources"] ||= {} old_value = @old_data["resources"][resource_name] if !old_value.nil? @new_data["resources"][resource_name] = old_value end end # Load data from the persistence store on disk. def load filename = Puppet[:transactionstorefile] unless Puppet::FileSystem.exist?(filename) return end unless File.file?(filename) Puppet.warning(_("Transaction store file %{filename} is not a file, ignoring") % { filename: filename }) return end result = nil Puppet::Util.benchmark(:debug, _("Loaded transaction store file in %{seconds} seconds")) do begin result = Puppet::Util::Yaml.load_file(filename, false, true) rescue Puppet::Util::Yaml::YamlLoadError => detail Puppet.log_exception(detail, _("Transaction store file %{filename} is corrupt (%{detail}); replacing") % { filename: filename, detail: detail }, { :level => :warning }) begin File.rename(filename, filename + ".bad") rescue => detail Puppet.log_exception(detail, _("Unable to rename corrupt transaction store file: %{detail}") % { detail: detail }) raise Puppet::Error, _("Could not rename corrupt transaction store file %{filename}; remove manually") % { filename: filename }, detail.backtrace end result = {} end end unless result.is_a?(Hash) Puppet.err _("Transaction store file %{filename} is valid YAML but not returning a hash. Check the file for corruption, or remove it before continuing.") % { filename: filename } return end @old_data = result end # Save data from internal class to persistence store on disk. def save Puppet::Util::Yaml.dump(@new_data, Puppet[:transactionstorefile]) end # Use the catalog and run_mode to determine if persistence should be enabled or not # @param [Puppet::Resource::Catalog] catalog catalog being processed # @return [boolean] true if persistence is enabled def enabled?(catalog) catalog.host_config? && Puppet.run_mode.name == :agent end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/����������������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�016063� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/component.rb����������������������������������������������������������0000644�0052762�0001160�00000004100�13417161721�020400� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� require 'puppet' require 'puppet/type' require 'puppet/transaction' Puppet::Type.newtype(:component) do include Enumerable newparam(:name) do desc "The name of the component. Generally optional." isnamevar end # Override how parameters are handled so that we support the extra # parameters that are used with defined resource types. def [](param) return super if self.class.valid_parameter?(param) @extra_parameters[param.to_sym] end # Override how parameters are handled so that we support the extra # parameters that are used with defined resource types. def []=(param, value) return super if self.class.valid_parameter?(param) @extra_parameters[param.to_sym] = value end # Initialize a new component def initialize(*args) @extra_parameters = {} super end # Component paths are special because they function as containers. def pathbuilder if reference.type == "Class" myname = reference.title else myname = reference.to_s end if p = self.parent return [p.pathbuilder, myname] else return [myname] end end def ref reference.to_s end # We want our title to just be the whole reference, rather than @title. def title ref end def title=(str) @reference = Puppet::Resource.new(str) end def refresh catalog.adjacent(self).each do |child| if child.respond_to?(:refresh) child.refresh child.log "triggering #{:refresh}" end end end def to_s reference.to_s end # Overrides the default implementation to do nothing. # This type contains data from class/define parameters, but does # not have actual parameters or properties at the Type level. We can # simply ignore anything flagged as sensitive here, since any # contained resources will handle that sensitivity themselves. There # is no risk of this information leaking into reports, since no # Component instances survive the graph transmutation. # def set_sensitive_parameters(sensitive_parameters) end private attr_reader :reference end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/file/�����������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�017002� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/file/checksum.rb������������������������������������������������������0000644�0052762�0001160�00000002525�13417161721�021130� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/checksums' # Specify which checksum algorithm to use when checksumming # files. Puppet::Type.type(:file).newparam(:checksum) do include Puppet::Util::Checksums desc "The checksum type to use when determining whether to replace a file's contents. The default checksum type is md5." newvalues "md5", "md5lite", "sha224", "sha256", "sha256lite", "sha384", "sha512", "mtime", "ctime", "none" defaultto do Puppet[:digest_algorithm].to_sym end validate do |value| if Puppet::Util::Platform.fips_enabled? && (value == :md5 || value == :md5lite) raise ArgumentError, _("MD5 is not supported in FIPS mode") end end def sum(content) content = content.is_a?(Puppet::Pops::Types::PBinaryType::Binary) ? content.binary_buffer : content type = digest_algorithm() "{#{type}}" + send(type, content) end def sum_file(path) type = digest_algorithm() method = type.to_s + "_file" "{#{type}}" + send(method, path).to_s end def sum_stream(&block) type = digest_algorithm() method = type.to_s + "_stream" checksum = send(method, &block) "{#{type}}#{checksum}" end private # Return the appropriate digest algorithm with fallbacks in case puppet defaults have not # been initialized. def digest_algorithm value || Puppet[:digest_algorithm].to_sym end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/file/checksum_value.rb������������������������������������������������0000644�0052762�0001160�00000003477�13417161721�022333� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/checksums' require 'puppet/type/file/data_sync' module Puppet Puppet::Type.type(:file).newproperty(:checksum_value) do include Puppet::Util::Checksums include Puppet::DataSync desc "The checksum of the source contents. Only md5, sha256, sha224, sha384 and sha512 are supported when specifying this parameter. If this parameter is set, source_permissions will be assumed to be false, and ownership and permissions will not be read from source." def insync?(is) # If checksum_value and source are specified, manage the file contents. # Otherwise the content property will manage syncing. if resource.parameter(:source).nil? return true end checksum_insync?(resource.parameter(:source), is, true) {|_is| super(_is)} end def property_matches?(current, desired) return true if super(current, desired) return date_matches?(resource.parameter(:checksum).value, current, desired) end def retrieve # If checksum_value and source are specified, manage the file contents. # Otherwise the content property will manage syncing. Don't compute the checksum twice. if resource.parameter(:source).nil? return nil end result = retrieve_checksum(resource) # If the returned type matches the util/checksums format (prefixed with the type), # strip the checksum type. result = sumdata(result) if checksum?(result) result end def sync if resource.parameter(:source).nil? devfail "checksum_value#sync should not be called without a source parameter" end # insync? only returns false if it expects to manage the file content, # so instruct the resource to write its contents. contents_sync(resource.parameter(:source)) end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/file/ctime.rb���������������������������������������������������������0000644�0052762�0001160�00000000756�13417161721�020433� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet Puppet::Type.type(:file).newproperty(:ctime) do desc %q{A read-only state to check the file ctime. On most modern \*nix-like systems, this is the time of the most recent change to the owner, group, permissions, or content of the file.} def retrieve current_value = :absent if stat = @resource.stat current_value = stat.ctime end current_value end validate do |val| fail "ctime is read-only" end end end ������������������puppet-5.5.10/lib/puppet/type/file/data_sync.rb�����������������������������������������������������0000644�0052762�0001160�00000005326�13417161721�021275� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/checksums' require 'puppet/util/diff' require 'date' require 'tempfile' module Puppet module DataSync include Puppet::Util::Checksums include Puppet::Util::Diff def write_temporarily(param) tempfile = Tempfile.new("puppet-file") tempfile.open param.write(tempfile) tempfile.close yield tempfile.path ensure tempfile.delete if tempfile end def checksum_insync?(param, is, has_contents, &block) resource = param.resource if resource.should_be_file? return false if is == :absent else if resource[:ensure] == :present && has_contents && (s = resource.stat) #TRANSLATORS 'Ensure' is an attribute and ':present' is a value and should not be translated resource.warning _("Ensure set to :present but file type is %{file_type} so no content will be synced") % { file_type: s.ftype} end return true end return true if ! resource.replace? is_insync = yield(is) if show_diff?(!is_insync) if param.sensitive send resource[:loglevel], "[diff redacted]" else write_temporarily(param) do |path| send resource[:loglevel], "\n" + diff(resource[:path], path) end end end is_insync end def show_diff?(has_changes) has_changes && Puppet[:show_diff] && resource.show_diff? end def date_matches?(checksum_type, current, desired) time_types = [:mtime, :ctime] return false if !time_types.include?(checksum_type) return false unless current && desired begin if checksum?(current) || checksum?(desired) raise if !time_types.include?(sumtype(current).to_sym) || !time_types.include?(sumtype(desired).to_sym) current = sumdata(current) desired = sumdata(desired) end DateTime.parse(current) >= DateTime.parse(desired) rescue => detail self.fail Puppet::Error, "Resource with checksum_type #{checksum_type} didn't contain a date in #{current} or #{desired}", detail.backtrace end end def retrieve_checksum(resource) return :absent unless stat = resource.stat ftype = stat.ftype # Don't even try to manage the content on directories or links return nil if ["directory","link"].include?(ftype) begin resource.parameter(:checksum).sum_file(resource[:path]) rescue => detail raise Puppet::Error, "Could not read #{ftype} #{resource.title}: #{detail}", detail.backtrace end end def contents_sync(param) return_event = param.resource.stat ? :file_changed : :file_created resource.write(param) return_event end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/file/ensure.rb��������������������������������������������������������0000644�0052762�0001160�00000014073�13417161721�020630� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� module Puppet Puppet::Type.type(:file).ensurable do require 'etc' require 'puppet/util/symbolic_file_mode' include Puppet::Util::SymbolicFileMode desc <<-EOT Whether the file should exist, and if so what kind of file it should be. Possible values are `present`, `absent`, `file`, `directory`, and `link`. * `present` accepts any form of file existence, and creates a normal file if the file is missing. (The file will have no content unless the `content` or `source` attribute is used.) * `absent` ensures the file doesn't exist, and deletes it if necessary. * `file` ensures it's a normal file, and enables use of the `content` or `source` attribute. * `directory` ensures it's a directory, and enables use of the `source`, `recurse`, `recurselimit`, `ignore`, and `purge` attributes. * `link` ensures the file is a symlink, and **requires** that you also set the `target` attribute. Symlinks are supported on all Posix systems and on Windows Vista / 2008 and higher. On Windows, managing symlinks requires Puppet agent's user account to have the "Create Symbolic Links" privilege; this can be configured in the "User Rights Assignment" section in the Windows policy editor. By default, Puppet agent runs as the Administrator account, which has this privilege. Puppet avoids destroying directories unless the `force` attribute is set to `true`. This means that if a file is currently a directory, setting `ensure` to anything but `directory` or `present` will cause Puppet to skip managing the resource and log either a notice or an error. There is one other non-standard value for `ensure`. If you specify the path to another file as the ensure value, it is equivalent to specifying `link` and using that path as the `target`: # Equivalent resources: file { '/etc/inetd.conf': ensure => '/etc/inet/inetd.conf', } file { '/etc/inetd.conf': ensure => link, target => '/etc/inet/inetd.conf', } However, we recommend using `link` and `target` explicitly, since this behavior can be harder to read and is [deprecated](https://docs.puppet.com/puppet/4.3/deprecated_language.html) as of Puppet 4.3.0. EOT # Most 'ensure' properties have a default, but with files we, um, don't. nodefault newvalue(:absent) do Puppet::FileSystem.unlink(@resource[:path]) end aliasvalue(:false, :absent) newvalue(:file, :event => :file_created) do # Make sure we're not managing the content some other way if property = @resource.property(:content) property.sync elsif property = @resource.property(:checksum_value) property.sync else @resource.write @resource.should(:mode) end end #aliasvalue(:present, :file) newvalue(:present, :event => :file_created) do # Make a file if they want something, but this will match almost # anything. set_file end newvalue(:directory, :event => :directory_created) do mode = @resource.should(:mode) parent = File.dirname(@resource[:path]) unless Puppet::FileSystem.exist? parent raise Puppet::Error, "Cannot create #{@resource[:path]}; parent directory #{parent} does not exist" end if mode Puppet::Util.withumask(000) do Dir.mkdir(@resource[:path], symbolic_mode_to_int(mode, 0755, true)) end else Dir.mkdir(@resource[:path]) end @resource.send(:property_fix) return :directory_created end newvalue(:link, :event => :link_created, :required_features => :manages_symlinks) do fail "Cannot create a symlink without a target" unless property = resource.property(:target) property.retrieve property.mklink end # Symlinks. newvalue(/./) do # This code never gets executed. We need the regex to support # specifying it, but the work is done in the 'symlink' code block. end munge do |value| value = super(value) value,resource[:target] = :link,value unless value.is_a? Symbol resource[:links] = :manage if value == :link and resource[:links] != :follow value end def change_to_s(currentvalue, newvalue) return super unless [:file, :present].include?(newvalue) return super unless property = @resource.property(:content) # We know that content is out of sync if we're here, because # it's essentially equivalent to 'ensure' in the transaction. if source = @resource.parameter(:source) should = source.checksum else should = property.should end if should == :absent is = property.retrieve else is = :absent end property.change_to_s(is, should) end # Check that we can actually create anything def check basedir = File.dirname(@resource[:path]) if ! Puppet::FileSystem.exist?(basedir) raise Puppet::Error, "Can not create #{@resource.title}; parent directory does not exist" elsif ! FileTest.directory?(basedir) raise Puppet::Error, "Can not create #{@resource.title}; #{dirname} is not a directory" end end # We have to treat :present specially, because it works with any # type of file. def insync?(currentvalue) unless currentvalue == :absent or resource.replace? return true end if self.should == :present return !(currentvalue.nil? or currentvalue == :absent) else return super(currentvalue) end end def retrieve if stat = @resource.stat return stat.ftype.intern else if self.should == :false return :false else return :absent end end end def sync @resource.remove_existing(self.should) if self.should == :absent return :file_removed end event = super event end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/file/group.rb���������������������������������������������������������0000644�0052762�0001160�00000002452�13417161721�020461� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/posix' module Puppet # Manage file group ownership. Puppet::Type.type(:file).newproperty(:group) do desc <<-EOT Which group should own the file. Argument can be either a group name or a group ID. On Windows, a user (such as "Administrator") can be set as a file's group and a group (such as "Administrators") can be set as a file's owner; however, a file's owner and group shouldn't be the same. (If the owner is also the group, files with modes like `"0640"` will cause log churn, as they will always appear out of sync.) EOT validate do |group| raise(Puppet::Error, "Invalid group name '#{group.inspect}'") unless group and group != "" end def insync?(current) # We don't want to validate/munge groups until we actually start to # evaluate this property, because they might be added during the catalog # apply. @should.map! do |val| provider.name2gid(val) or raise "Could not find group #{val}" end @should.include?(current) end # We want to print names, not numbers def is_to_s(currentvalue) super(provider.gid2name(currentvalue) || currentvalue) end def should_to_s(newvalue) super(provider.gid2name(newvalue) || newvalue) end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/file/mtime.rb���������������������������������������������������������0000644�0052762�0001160�00000000675�13417161721�020445� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet Puppet::Type.type(:file).newproperty(:mtime) do desc %q{A read-only state to check the file mtime. On \*nix-like systems, this is the time of the most recent change to the content of the file.} def retrieve current_value = :absent if stat = @resource.stat current_value = stat.mtime end current_value end validate do |val| fail "mtime is read-only" end end end �������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/file/owner.rb���������������������������������������������������������0000644�0052762�0001160�00000002454�13417161721�020461� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet Puppet::Type.type(:file).newproperty(:owner) do include Puppet::Util::Warnings desc <<-EOT The user to whom the file should belong. Argument can be a user name or a user ID. On Windows, a group (such as "Administrators") can be set as a file's owner and a user (such as "Administrator") can be set as a file's group; however, a file's owner and group shouldn't be the same. (If the owner is also the group, files with modes like `"0640"` will cause log churn, as they will always appear out of sync.) EOT def insync?(current) # We don't want to validate/munge users until we actually start to # evaluate this property, because they might be added during the catalog # apply. @should.map! do |val| provider.name2uid(val) or raise "Could not find user #{val}" end return true if @should.include?(current) unless Puppet.features.root? warnonce "Cannot manage ownership unless running as root" return true end false end # We want to print names, not numbers def is_to_s(currentvalue) super(provider.uid2name(currentvalue) || currentvalue) end def should_to_s(newvalue) super(provider.uid2name(newvalue) || newvalue) end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/file/selcontext.rb����������������������������������������������������0000644�0052762�0001160�00000011255�13417161721�021516� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Manage SELinux context of files. # # This code actually manages three pieces of data in the context. # # [root@delenn files]# ls -dZ / # drwxr-xr-x root root system_u:object_r:root_t / # # The context of '/' here is 'system_u:object_r:root_t'. This is # three separate fields: # # system_u is the user context # object_r is the role context # root_t is the type context # # All three of these fields are returned in a single string by the # output of the stat command, but set individually with the chcon # command. This allows the user to specify a subset of the three # values while leaving the others alone. # # See https://www.nsa.gov/selinux/ for complete docs on SELinux. module Puppet require 'puppet/util/selinux' class SELFileContext < Puppet::Property include Puppet::Util::SELinux def retrieve return :absent unless @resource.stat context = self.get_selinux_current_context(@resource[:path]) is = parse_selinux_context(name, context) if name == :selrange and selinux_support? self.selinux_category_to_label(is) else is end end def retrieve_default_context(property) if @resource[:selinux_ignore_defaults] == :true return nil end unless context = self.get_selinux_default_context(@resource[:path]) return nil end property_default = self.parse_selinux_context(property, context) self.debug "Found #{property} default '#{property_default}' for #{@resource[:path]}" if not property_default.nil? property_default end def insync?(value) if not selinux_support? debug("SELinux bindings not found. Ignoring parameter.") true elsif not selinux_label_support?(@resource[:path]) debug("SELinux not available for this filesystem. Ignoring parameter.") true else super end end def unsafe_munge(should) if not selinux_support? return should end if name == :selrange self.selinux_category_to_label(should) else should end end def sync self.set_selinux_context(@resource[:path], @should, name) :file_changed end end Puppet::Type.type(:file).newparam(:selinux_ignore_defaults) do desc "If this is set then Puppet will not ask SELinux (via matchpathcon) to supply defaults for the SELinux attributes (seluser, selrole, seltype, and selrange). In general, you should leave this set at its default and only set it to true when you need Puppet to not try to fix SELinux labels automatically." newvalues(:true, :false) defaultto :false end Puppet::Type.type(:file).newproperty(:seluser, :parent => Puppet::SELFileContext) do desc "What the SELinux user component of the context of the file should be. Any valid SELinux user component is accepted. For example `user_u`. If not specified it defaults to the value returned by matchpathcon for the file, if any exists. Only valid on systems with SELinux support enabled." @event = :file_changed defaultto { self.retrieve_default_context(:seluser) } end Puppet::Type.type(:file).newproperty(:selrole, :parent => Puppet::SELFileContext) do desc "What the SELinux role component of the context of the file should be. Any valid SELinux role component is accepted. For example `role_r`. If not specified it defaults to the value returned by matchpathcon for the file, if any exists. Only valid on systems with SELinux support enabled." @event = :file_changed defaultto { self.retrieve_default_context(:selrole) } end Puppet::Type.type(:file).newproperty(:seltype, :parent => Puppet::SELFileContext) do desc "What the SELinux type component of the context of the file should be. Any valid SELinux type component is accepted. For example `tmp_t`. If not specified it defaults to the value returned by matchpathcon for the file, if any exists. Only valid on systems with SELinux support enabled." @event = :file_changed defaultto { self.retrieve_default_context(:seltype) } end Puppet::Type.type(:file).newproperty(:selrange, :parent => Puppet::SELFileContext) do desc "What the SELinux range component of the context of the file should be. Any valid SELinux range component is accepted. For example `s0` or `SystemHigh`. If not specified it defaults to the value returned by matchpathcon for the file, if any exists. Only valid on systems with SELinux support enabled and that have support for MCS (Multi-Category Security)." @event = :file_changed defaultto { self.retrieve_default_context(:selrange) } end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/file/target.rb��������������������������������������������������������0000644�0052762�0001160�00000004764�13417161721�020623� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet Puppet::Type.type(:file).newproperty(:target) do desc "The target for creating a link. Currently, symlinks are the only type supported. This attribute is mutually exclusive with `source` and `content`. Symlink targets can be relative, as well as absolute: # (Useful on Solaris) file { '/etc/inetd.conf': ensure => link, target => 'inet/inetd.conf', } Directories of symlinks can be served recursively by instead using the `source` attribute, setting `ensure` to `directory`, and setting the `links` attribute to `manage`." newvalue(:notlink) do # We do nothing if the value is absent return :nochange end # Anything else, basically newvalue(/./) do @resource[:ensure] = :link if ! @resource.should(:ensure) # Only call mklink if ensure didn't call us in the first place. currentensure = @resource.property(:ensure).retrieve mklink if @resource.property(:ensure).safe_insync?(currentensure) end # Create our link. def mklink raise Puppet::Error, "Cannot symlink on this platform version" if !provider.feature?(:manages_symlinks) target = self.should # Clean up any existing objects. The argument is just for logging, # it doesn't determine what's removed. @resource.remove_existing(target) raise Puppet::Error, "Could not remove existing file" if Puppet::FileSystem.exist?(@resource[:path]) Dir.chdir(File.dirname(@resource[:path])) do Puppet::Util::SUIDManager.asuser(@resource.asuser) do mode = @resource.should(:mode) if mode Puppet::Util.withumask(000) do Puppet::FileSystem.symlink(target, @resource[:path]) end else Puppet::FileSystem.symlink(target, @resource[:path]) end end @resource.send(:property_fix) :link_created end end def insync?(currentvalue) if [:nochange, :notlink].include?(self.should) or @resource.recurse? return true elsif ! @resource.replace? and Puppet::FileSystem.exist?(@resource[:path]) return true else return super(currentvalue) end end def retrieve if stat = @resource.stat if stat.ftype == "link" return Puppet::FileSystem.readlink(@resource[:path]) else return :notlink end else return :absent end end end end ������������puppet-5.5.10/lib/puppet/type/file/type.rb����������������������������������������������������������0000644�0052762�0001160�00000000547�13417161721�020311� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet Puppet::Type.type(:file).newproperty(:type) do require 'etc' desc "A read-only state to check the file type." def retrieve current_value = :absent if stat = @resource.stat current_value = stat.ftype end current_value end validate do |val| fail "type is read-only" end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/file/content.rb�������������������������������������������������������0000644�0052762�0001160�00000014270�13417161722�021001� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'net/http' require 'uri' require 'tempfile' require 'puppet/util/checksums' require 'puppet/type/file/data_sync.rb' module Puppet Puppet::Type.type(:file).newproperty(:content) do include Puppet::Util::Checksums include Puppet::DataSync attr_reader :actual_content desc <<-'EOT' The desired contents of a file, as a string. This attribute is mutually exclusive with `source` and `target`. Newlines and tabs can be specified in double-quoted strings using standard escaped syntax --- \n for a newline, and \t for a tab. With very small files, you can construct content strings directly in the manifest... define resolve($nameserver1, $nameserver2, $domain, $search) { $str = "search ${search} domain ${domain} nameserver ${nameserver1} nameserver ${nameserver2} " file { '/etc/resolv.conf': content => $str, } } ...but for larger files, this attribute is more useful when combined with the [template](https://puppet.com/docs/puppet/latest/function.html#template) or [file](https://puppet.com/docs/puppet/latest/function.html#file) function. EOT # Store a checksum as the value, rather than the actual content. # Simplifies everything. munge do |value| if value == :absent value elsif value.is_a?(String) && checksum?(value) # XXX This is potentially dangerous because it means users can't write a file whose # entire contents are a plain checksum unless it is a Binary content. Puppet.puppet_deprecation_warning([ #TRANSLATORS "content" is an attribute and should not be translated _('Using a checksum in a file\'s "content" property is deprecated.'), #TRANSLATORS "filebucket" is a resource type and should not be translated. The quoted occurrence of "content" is an attribute and should not be translated. _('The ability to use a checksum to retrieve content from the filebucket using the "content" property will be removed in a future release.'), #TRANSLATORS "content" is an attribute and should not be translated. _('The literal value of the "content" property will be written to the file.'), #TRANSLATORS "static catalogs" should not be translated. _('The checksum retrieval functionality is being replaced by the use of static catalogs.'), _('See https://puppet.com/docs/puppet/latest/static_catalogs.html for more information.')].join(" "), :file => @resource.file, :line => @resource.line ) if !@actual_content && !resource.parameter(:source) value else @actual_content = value.is_a?(Puppet::Pops::Types::PBinaryType::Binary) ? value.binary_buffer : value resource.parameter(:checksum).sum(@actual_content) end end # Checksums need to invert how changes are printed. def change_to_s(currentvalue, newvalue) # Our "new" checksum value is provided by the source. if source = resource.parameter(:source) and tmp = source.checksum newvalue = tmp end if currentvalue == :absent return "defined content as '#{newvalue}'" elsif newvalue == :absent return "undefined content from '#{currentvalue}'" else return "content changed '#{currentvalue}' to '#{newvalue}'" end end def length (actual_content and actual_content.length) || 0 end def content self.should end # Override this method to provide diffs if asked for. # Also, fix #872: when content is used, and replace is true, the file # should be insync when it exists def insync?(is) if resource[:source] && resource[:checksum_value] # Asserts that nothing has changed since validate ran. devfail "content property should not exist if source and checksum_value are specified" end contents_prop = resource.parameter(:source) || self checksum_insync?(contents_prop, is, !resource[:content].nil?) {|_is| super(_is)} end def property_matches?(current, desired) # If checksum_value is specified, it overrides comparing the content field. checksum_type = resource.parameter(:checksum).value if checksum_value = resource.parameter(:checksum_value) desired = "{#{checksum_type}}#{checksum_value.value}" end # The inherited equality is always accepted, so use it if valid. return true if super(current, desired) return date_matches?(checksum_type, current, desired) end def retrieve retrieve_checksum(resource) end # Make sure we're also managing the checksum property. def should=(value) # treat the value as a bytestring, in Ruby versions that support it, regardless of the encoding # in which it has been supplied value = value.dup.force_encoding(Encoding::ASCII_8BIT) if value.respond_to?(:force_encoding) @resource.newattr(:checksum) unless @resource.parameter(:checksum) super end # Just write our content out to disk. def sync contents_sync(resource.parameter(:source) || self) end def write(file) resource.parameter(:checksum).sum_stream { |sum| each_chunk_from { |chunk| sum << chunk file.print chunk } } end private # the content is munged so if it's a checksum source_or_content is nil # unless the checksum indirectly comes from source def each_chunk_from if actual_content.is_a?(String) yield actual_content elsif content_is_really_a_checksum? && actual_content.nil? yield read_file_from_filebucket elsif actual_content.nil? yield '' end end def content_is_really_a_checksum? checksum?(should) end def read_file_from_filebucket raise "Could not get filebucket from file" unless dipper = resource.bucket sum = should.sub(/\{\w+\}/, '') dipper.getfile(sum) rescue => detail self.fail Puppet::Error, "Could not retrieve content for #{should} from filebucket: #{detail}", detail end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/file/mode.rb����������������������������������������������������������0000644�0052762�0001160�00000015503�13417161722�020253� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Manage file modes. This state should support different formats # for specification (e.g., u+rwx, or -0011), but for now only supports # specifying the full mode. module Puppet Puppet::Type.type(:file).newproperty(:mode) do require 'puppet/util/symbolic_file_mode' include Puppet::Util::SymbolicFileMode desc <<-'EOT' The desired permissions mode for the file, in symbolic or numeric notation. This value **must** be specified as a string; do not use un-quoted numbers to represent file modes. If the mode is omitted (or explicitly set to `undef`), Puppet does not enforce permissions on existing files and creates new files with permissions of `0644`. The `file` type uses traditional Unix permission schemes and translates them to equivalent permissions for systems which represent permissions differently, including Windows. For detailed ACL controls on Windows, you can leave `mode` unmanaged and use [the puppetlabs/acl module.](https://forge.puppetlabs.com/puppetlabs/acl) Numeric modes should use the standard octal notation of `<SETUID/SETGID/STICKY><OWNER><GROUP><OTHER>` (for example, "0644"). * Each of the "owner," "group," and "other" digits should be a sum of the permissions for that class of users, where read = 4, write = 2, and execute/search = 1. * The setuid/setgid/sticky digit is also a sum, where setuid = 4, setgid = 2, and sticky = 1. * The setuid/setgid/sticky digit is optional. If it is absent, Puppet will clear any existing setuid/setgid/sticky permissions. (So to make your intent clear, you should use at least four digits for numeric modes.) * When specifying numeric permissions for directories, Puppet sets the search permission wherever the read permission is set. Symbolic modes should be represented as a string of comma-separated permission clauses, in the form `<WHO><OP><PERM>`: * "Who" should be u (user), g (group), o (other), and/or a (all) * "Op" should be = (set exact permissions), + (add select permissions), or - (remove select permissions) * "Perm" should be one or more of: * r (read) * w (write) * x (execute/search) * t (sticky) * s (setuid/setgid) * X (execute/search if directory or if any one user can execute) * u (user's current permissions) * g (group's current permissions) * o (other's current permissions) Thus, mode `"0664"` could be represented symbolically as either `a=r,ug+w` or `ug=rw,o=r`. However, symbolic modes are more expressive than numeric modes: a mode only affects the specified bits, so `mode => 'ug+w'` will set the user and group write bits, without affecting any other bits. See the manual page for GNU or BSD `chmod` for more details on numeric and symbolic modes. On Windows, permissions are translated as follows: * Owner and group names are mapped to Windows SIDs * The "other" class of users maps to the "Everyone" SID * The read/write/execute permissions map to the `FILE_GENERIC_READ`, `FILE_GENERIC_WRITE`, and `FILE_GENERIC_EXECUTE` access rights; a file's owner always has the `FULL_CONTROL` right * "Other" users can't have any permissions a file's group lacks, and its group can't have any permissions its owner lacks; that is, "0644" is an acceptable mode, but "0464" is not. EOT validate do |value| if !value.is_a?(String) raise Puppet::Error, "The file mode specification must be a string, not '#{value.class.name}'" end unless value.nil? or valid_symbolic_mode?(value) raise Puppet::Error, "The file mode specification is invalid: #{value.inspect}" end end munge do |value| return nil if value.nil? unless valid_symbolic_mode?(value) raise Puppet::Error, "The file mode specification is invalid: #{value.inspect}" end normalize_symbolic_mode(value) end def desired_mode_from_current(desired, current) current = current.to_i(8) if current.is_a? String is_a_directory = @resource.stat && @resource.stat.directory? symbolic_mode_to_int(desired, current, is_a_directory) end # If we're a directory, we need to be executable for all cases # that are readable. This should probably be selectable, but eh. def dirmask(value) if FileTest.directory?(resource[:path]) and value =~ /^\d+$/ then value = value.to_i(8) value |= 0100 if value & 0400 != 0 value |= 010 if value & 040 != 0 value |= 01 if value & 04 != 0 value = value.to_s(8) end value end # If we're not following links and we're a link, then we just turn # off mode management entirely. def insync?(currentvalue) if stat = @resource.stat and stat.ftype == "link" and @resource[:links] != :follow self.debug "Not managing symlink mode" return true else return super(currentvalue) end end def property_matches?(current, desired) return false unless current current_bits = normalize_symbolic_mode(current) desired_bits = desired_mode_from_current(desired, current).to_s(8) current_bits == desired_bits end # Ideally, dirmask'ing could be done at munge time, but we don't know if 'ensure' # will eventually be a directory or something else. And unfortunately, that logic # depends on the ensure, source, and target properties. So rather than duplicate # that logic, and get it wrong, we do dirmask during retrieve, after 'ensure' has # been synced. def retrieve if @resource.stat @should &&= @should.collect { |s| self.dirmask(s) } end super end # Finally, when we sync the mode out we need to transform it; since we # don't have access to the calculated "desired" value here, or the # "current" value, only the "should" value we need to retrieve again. def sync current = @resource.stat ? @resource.stat.mode : 0644 set(desired_mode_from_current(@should[0], current).to_s(8)) end def change_to_s(old_value, desired) return super if desired =~ /^\d+$/ old_bits = normalize_symbolic_mode(old_value) new_bits = normalize_symbolic_mode(desired_mode_from_current(desired, old_bits)) super(old_bits, new_bits) + " (#{desired})" end def should_to_s(should_value) "'#{should_value.rjust(4, '0')}'" end def is_to_s(currentvalue) if currentvalue == :absent # This can occur during audits---if a file is transitioning from # present to absent the mode will have a value of `:absent`. super else "'#{currentvalue.rjust(4, '0')}'" end end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/file/source.rb��������������������������������������������������������0000644�0052762�0001160�00000032366�13417161722�020635� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/file_serving/content' require 'puppet/file_serving/metadata' require 'puppet/file_serving/terminus_helper' require 'puppet/util/http_proxy' require 'puppet/network/http' require 'puppet/network/http/api/indirected_routes' require 'puppet/network/http/compression' module Puppet # Copy files from a local or remote source. This state *only* does any work # when the remote file is an actual file; in that case, this state copies # the file down. If the remote file is a dir or a link or whatever, then # this state, during retrieval, modifies the appropriate other states # so that things get taken care of appropriately. Puppet::Type.type(:file).newparam(:source) do include Puppet::Network::HTTP::Compression.module BINARY_MIME_TYPES = [ Puppet::Network::FormatHandler.format_for('binary').mime ].join(', ').freeze attr_accessor :source, :local desc <<-'EOT' A source file, which will be copied into place on the local system. This attribute is mutually exclusive with `content` and `target`. Allowed values are: * `puppet:` URIs, which point to files in modules or Puppet file server mount points. * Fully qualified paths to locally available files (including files on NFS shares or Windows mapped drives). * `file:` URIs, which behave the same as local file paths. * `http:` URIs, which point to files served by common web servers. The normal form of a `puppet:` URI is: `puppet:///modules/<MODULE NAME>/<FILE PATH>` This will fetch a file from a module on the Puppet master (or from a local module when using Puppet apply). Given a `modulepath` of `/etc/puppetlabs/code/modules`, the example above would resolve to `/etc/puppetlabs/code/modules/<MODULE NAME>/files/<FILE PATH>`. Unlike `content`, the `source` attribute can be used to recursively copy directories if the `recurse` attribute is set to `true` or `remote`. If a source directory contains symlinks, use the `links` attribute to specify whether to recreate links or follow them. _HTTP_ URIs cannot be used to recursively synchronize whole directory trees. You cannot use `source_permissions` values other than `ignore` because HTTP servers do not transfer any metadata that translates to ownership or permission details. The `http` source uses the server `Content-MD5` header as a checksum to determine if the remote file has changed. If the server response does not include that header, Puppet defaults to using the `Last-Modified` header. Multiple `source` values can be specified as an array, and Puppet will use the first source that exists. This can be used to serve different files to different system types: file { '/etc/nfs.conf': source => [ "puppet:///modules/nfs/conf.${host}", "puppet:///modules/nfs/conf.${operatingsystem}", 'puppet:///modules/nfs/conf' ] } Alternately, when serving directories recursively, multiple sources can be combined by setting the `sourceselect` attribute to `all`. EOT validate do |sources| sources = [sources] unless sources.is_a?(Array) sources.each do |source| next if Puppet::Util.absolute_path?(source) begin uri = URI.parse(Puppet::Util.uri_encode(source)) rescue => detail self.fail Puppet::Error, "Could not understand source #{source}: #{detail}", detail end self.fail "Cannot use relative URLs '#{source}'" unless uri.absolute? self.fail "Cannot use opaque URLs '#{source}'" unless uri.hierarchical? unless %w{file puppet http https}.include?(uri.scheme) self.fail "Cannot use URLs of type '#{uri.scheme}' as source for fileserving" end end end SEPARATOR_REGEX = [Regexp.escape(File::SEPARATOR.to_s), Regexp.escape(File::ALT_SEPARATOR.to_s)].join munge do |sources| sources = [sources] unless sources.is_a?(Array) sources.map do |source| source = self.class.normalize(source) if Puppet::Util.absolute_path?(source) # CGI.unescape will butcher properly escaped URIs uri_string = Puppet::Util.path_to_uri(source).to_s # Ruby 1.9.3 and earlier have a URI bug in URI # to_s returns an ASCII string despite UTF-8 fragments # since its escaped its safe to universally call encode # URI.unescape always returns strings in the original encoding URI.unescape(uri_string.encode(Encoding::UTF_8)) else source end end end def self.normalize(source) source.sub(/[#{SEPARATOR_REGEX}]+$/, '') end def change_to_s(currentvalue, newvalue) # newvalue = "{md5}#{@metadata.checksum}" if resource.property(:ensure).retrieve == :absent return "creating from source #{metadata.source} with contents #{metadata.checksum}" else return "replacing from source #{metadata.source} with contents #{metadata.checksum}" end end def checksum metadata && metadata.checksum end # Look up (if necessary) and return local content. def content return @content if @content raise Puppet::DevError, _("No source for content was stored with the metadata") unless metadata.source unless tmp = Puppet::FileServing::Content.indirection.find(metadata.source, :environment => resource.catalog.environment_instance, :links => resource[:links]) self.fail "Could not find any content at %s" % metadata.source end @content = tmp.content end # Copy the values from the source to the resource. Yay. def copy_source_values devfail "Somehow got asked to copy source values without any metadata" unless metadata # conditionally copy :checksum if metadata.ftype != "directory" && !(metadata.ftype == "link" && metadata.links == :manage) copy_source_value(:checksum) end # Take each of the stats and set them as states on the local file # if a value has not already been provided. [:owner, :mode, :group].each do |metadata_method| next if metadata_method == :owner and !Puppet.features.root? next if metadata_method == :group and !Puppet.features.root? case resource[:source_permissions] when :ignore, nil next when :use_when_creating next if Puppet::FileSystem.exist?(resource[:path]) end copy_source_value(metadata_method) end if resource[:ensure] == :absent # We know all we need to elsif metadata.ftype != "link" resource[:ensure] = metadata.ftype elsif resource[:links] == :follow resource[:ensure] = :present else resource[:ensure] = "link" resource[:target] = metadata.destination end end attr_writer :metadata # Provide, and retrieve if necessary, the metadata for this file. Fail # if we can't find data about this host, and fail if there are any # problems in our query. def metadata return @metadata if @metadata if @metadata = resource.catalog.metadata[resource.title] return @metadata end return nil unless value value.each do |source| begin options = { :environment => resource.catalog.environment_instance, :links => resource[:links], :checksum_type => resource[:checksum], :source_permissions => resource[:source_permissions] } if data = Puppet::FileServing::Metadata.indirection.find(source, options) @metadata = data @metadata.source = source break end rescue => detail self.fail Puppet::Error, "Could not retrieve file metadata for #{source}: #{detail}", detail end end self.fail "Could not retrieve information from environment #{resource.catalog.environment} source(s) #{value.join(", ")}" unless @metadata @metadata end def local? found? and scheme == "file" end def full_path Puppet::Util.uri_to_path(uri) if found? end def server? uri and uri.host end def server (uri and uri.host) or Puppet.settings[:server] end def port (uri and uri.port) or Puppet.settings[:masterport] end def uri @uri ||= URI.parse(Puppet::Util.uri_encode(metadata.source)) end def write(file) resource.parameter(:checksum).sum_stream { |sum| each_chunk_from { |chunk| sum << chunk file.print chunk } } end private def scheme (uri and uri.scheme) end def found? ! (metadata.nil? or metadata.ftype.nil?) end def copy_source_value(metadata_method) param_name = (metadata_method == :checksum) ? :content : metadata_method if resource[param_name].nil? or resource[param_name] == :absent if Puppet.features.microsoft_windows? && [:owner, :group, :mode].include?(metadata_method) devfail "Should not have tried to use source owner/mode/group on Windows" end value = metadata.send(metadata_method) # Force the mode value in file resources to be a string containing octal. value = value.to_s(8) if param_name == :mode && value.is_a?(Numeric) resource[param_name] = value if (metadata_method == :checksum) # If copying checksum, also copy checksum_type resource[:checksum] = metadata.checksum_type end end end def each_chunk_from if Puppet[:default_file_terminus] == :file_server yield content elsif local? chunk_file_from_disk { |chunk| yield chunk } else chunk_file_from_source { |chunk| yield chunk } end end def chunk_file_from_disk File.open(full_path, "rb") do |src| while chunk = src.read(8192) yield chunk end end end def get_from_puppet_source(source_uri, content_uri, &block) options = { :environment => resource.catalog.environment_instance } if content_uri options[:code_id] = resource.catalog.code_id request = Puppet::Indirector::Request.new(:static_file_content, :find, content_uri, nil, options) else request = Puppet::Indirector::Request.new(:file_content, :find, source_uri, nil, options) end request.do_request(:fileserver) do |req| connection = Puppet::Network::HttpPool.http_instance(req.server, req.port) connection.request_get(Puppet::Network::HTTP::API::IndirectedRoutes.request_to_uri(req), add_accept_encoding({"Accept" => BINARY_MIME_TYPES}), &block) end end def get_from_http_source(source_uri, &block) Puppet::Util::HttpProxy.request_with_redirects(URI(source_uri), :get, &block) end def get_from_source(&block) source_uri = metadata.source if source_uri =~ /^https?:/ get_from_http_source(source_uri, &block) else get_from_puppet_source(source_uri, metadata.content_uri, &block) end end def chunk_file_from_source get_from_source do |response| case response.code when /^2/; uncompress(response) { |uncompressor| response.read_body { |chunk| yield uncompressor.uncompress(chunk) } } else # Raise the http error if we didn't get a 'success' of some kind. message = "Error #{response.code} on SERVER: #{(response.body||'').empty? ? response.message : uncompress_body(response)}" raise Net::HTTPError.new(message, response) end end end end Puppet::Type.type(:file).newparam(:source_permissions) do desc <<-'EOT' Whether (and how) Puppet should copy owner, group, and mode permissions from the `source` to `file` resources when the permissions are not explicitly specified. (In all cases, explicit permissions will take precedence.) Valid values are `use`, `use_when_creating`, and `ignore`: * `ignore` (the default) will never apply the owner, group, or mode from the `source` when managing a file. When creating new files without explicit permissions, the permissions they receive will depend on platform-specific behavior. On POSIX, Puppet will use the umask of the user it is running as. On Windows, Puppet will use the default DACL associated with the user it is running as. * `use` will cause Puppet to apply the owner, group, and mode from the `source` to any files it is managing. * `use_when_creating` will only apply the owner, group, and mode from the `source` when creating a file; existing files will not have their permissions overwritten. EOT defaultto :ignore newvalues(:use, :use_when_creating, :ignore) munge do |value| value = value ? value.to_sym : :ignore if @resource.file && @resource.line && value != :ignore #TRANSLATORS "source_permissions" is a parameter name and should not be translated Puppet.puppet_deprecation_warning(_("The `source_permissions` parameter is deprecated. Explicitly set `owner`, `group`, and `mode`."), file: @resource.file, line: @resource.line) end value end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/filebucket.rb���������������������������������������������������������0000644�0052762�0001160�00000007425�13417161721�020530� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet require 'puppet/file_bucket/dipper' Type.newtype(:filebucket) do @doc = <<-EOT A repository for storing and retrieving file content by MD5 checksum. Can be local to each agent node, or centralized on a puppet master server. All puppet masters provide a filebucket service that agent nodes can access via HTTP, but you must declare a filebucket resource before any agents will do so. Filebuckets are used for the following features: - **Content backups.** If the `file` type's `backup` attribute is set to the name of a filebucket, Puppet will back up the _old_ content whenever it rewrites a file; see the documentation for the `file` type for more details. These backups can be used for manual recovery of content, but are more commonly used to display changes and differences in a tool like Puppet Dashboard. To use a central filebucket for backups, you will usually want to declare a filebucket resource and a resource default for the `backup` attribute in site.pp: # /etc/puppetlabs/puppet/manifests/site.pp filebucket { 'main': path => false, # This is required for remote filebuckets. server => 'puppet.example.com', # Optional; defaults to the configured puppet master. } File { backup => main, } Puppet master servers automatically provide the filebucket service, so this will work in a default configuration. If you have a heavily restricted `auth.conf` file, you may need to allow access to the `file_bucket_file` endpoint. EOT newparam(:name) do desc "The name of the filebucket." isnamevar end newparam(:server) do desc "The server providing the remote filebucket service. Defaults to the value of the `server` setting (that is, the currently configured puppet master server). This setting is _only_ consulted if the `path` attribute is set to `false`." defaultto { Puppet[:server] } end newparam(:port) do desc "The port on which the remote server is listening. Defaults to the value of the `masterport` setting, which is usually %s." % Puppet[:masterport] defaultto { Puppet[:masterport] } end newparam(:path) do desc "The path to the _local_ filebucket; defaults to the value of the `clientbucketdir` setting. To use a remote filebucket, you _must_ set this attribute to `false`." defaultto { Puppet[:clientbucketdir] } validate do |value| if value.is_a? Array raise ArgumentError, _("You can only have one filebucket path") end if value.is_a? String and not Puppet::Util.absolute_path?(value) raise ArgumentError, _("Filebucket paths must be absolute") end true end end # Create a default filebucket. def self.mkdefaultbucket new(:name => "puppet", :path => Puppet[:clientbucketdir]) end def bucket mkbucket unless defined?(@bucket) @bucket end private def mkbucket # Default is a local filebucket, if no server is given. # If the default path has been removed, too, then # the puppetmaster is used as default server type = "local" args = {} if self[:path] args[:Path] = self[:path] else args[:Server] = self[:server] args[:Port] = self[:port] end begin @bucket = Puppet::FileBucket::Dipper.new(args) rescue => detail message = _("Could not create %{type} filebucket: %{detail}") % { type: type, detail: detail } self.log_exception(detail, message) self.fail(message) end @bucket.name = self.name end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/group.rb��������������������������������������������������������������0000644�0052762�0001160�00000016457�13417161721�017554� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'etc' require 'facter' require 'puppet/property/keyvalue' require 'puppet/parameter/boolean' module Puppet Type.newtype(:group) do @doc = "Manage groups. On most platforms this can only create groups. Group membership must be managed on individual users. On some platforms such as OS X, group membership is managed as an attribute of the group, not the user record. Providers must have the feature 'manages_members' to manage the 'members' property of a group record." feature :manages_members, "For directories where membership is an attribute of groups not users." feature :manages_aix_lam, "The provider can manage AIX Loadable Authentication Module (LAM) system." feature :system_groups, "The provider allows you to create system groups with lower GIDs." feature :libuser, "Allows local groups to be managed on systems that also use some other remote NSS method of managing accounts." ensurable do desc "Create or remove the group." newvalue(:present) do provider.create end newvalue(:absent) do provider.delete end defaultto :present end newproperty(:gid) do desc "The group ID. Must be specified numerically. If no group ID is specified when creating a new group, then one will be chosen automatically according to local system standards. This will likely result in the same group having different GIDs on different systems, which is not recommended. On Windows, this property is read-only and will return the group's security identifier (SID)." def retrieve provider.gid end def sync if self.should == :absent raise Puppet::DevError, _("GID cannot be deleted") else provider.gid = self.should end end munge do |gid| case gid when String if gid =~ /^[-0-9]+$/ gid = Integer(gid) else self.fail _("Invalid GID %{gid}") % { gid: gid } end when Symbol unless gid == :absent self.devfail "Invalid GID #{gid}" end end return gid end end newproperty(:members, :array_matching => :all, :required_features => :manages_members) do desc "The members of the group. For platforms or directory services where group membership is stored in the group objects, not the users. This parameter's behavior can be configured with `auth_membership`." def change_to_s(currentvalue, newvalue) newvalue = actual_should(currentvalue, newvalue) currentvalue = currentvalue.join(",") if currentvalue != :absent newvalue = newvalue.join(",") super(currentvalue, newvalue) end def insync?(current) if provider.respond_to?(:members_insync?) return provider.members_insync?(current, @should) end super(current) end def is_to_s(currentvalue) if provider.respond_to?(:members_to_s) currentvalue = '' if currentvalue.nil? currentvalue = currentvalue.is_a?(Array) ? currentvalue : currentvalue.split(',') return provider.members_to_s(currentvalue) end super(currentvalue) end def should_to_s(newvalue) is_to_s(actual_should(retrieve, newvalue)) end # Calculates the actual should value given the current and # new values. This is only used in should_to_s and change_to_s # to fix the change notification issue reported in PUP-6542. def actual_should(currentvalue, newvalue) currentvalue = munge_members_value(currentvalue) newvalue = munge_members_value(newvalue) if @resource[:auth_membership] newvalue.uniq.sort else (currentvalue + newvalue).uniq.sort end end # Useful helper to handle the possible property value types that we can # both pass-in and return. It munges the value into an array def munge_members_value(value) return [] if value == :absent return value.split(',') if value.is_a?(String) value end validate do |value| if provider.respond_to?(:member_valid?) return provider.member_valid?(value) end end end newparam(:auth_membership, :boolean => true, :parent => Puppet::Parameter::Boolean) do desc "Configures the behavior of the `members` parameter. * `false` (default) --- The provided list of group members is partial, and Puppet **ignores** any members that aren't listed there. * `true` --- The provided list of of group members is comprehensive, and Puppet **purges** any members that aren't listed there." defaultto false end newparam(:name) do desc "The group name. While naming limitations vary by operating system, it is advisable to restrict names to the lowest common denominator, which is a maximum of 8 characters beginning with a letter. Note that Puppet considers group names to be case-sensitive, regardless of the platform's own rules; be sure to always use the same case when referring to a given group." isnamevar end newparam(:allowdupe, :boolean => true, :parent => Puppet::Parameter::Boolean) do desc "Whether to allow duplicate GIDs." defaultto false end newparam(:ia_load_module, :required_features => :manages_aix_lam) do desc "The name of the I&A module to use to manage this user" end newproperty(:attributes, :parent => Puppet::Property::KeyValue, :required_features => :manages_aix_lam) do desc "Specify group AIX attributes, as an array of `'key=value'` strings. This parameter's behavior can be configured with `attribute_membership`." self.log_only_changed_or_new_keys = true def membership :attribute_membership end def delimiter " " end end newparam(:attribute_membership) do desc "AIX only. Configures the behavior of the `attributes` parameter. * `minimum` (default) --- The provided list of attributes is partial, and Puppet **ignores** any attributes that aren't listed there. * `inclusive` --- The provided list of attributes is comprehensive, and Puppet **purges** any attributes that aren't listed there." newvalues(:inclusive, :minimum) defaultto :minimum end newparam(:system, :boolean => true, :parent => Puppet::Parameter::Boolean) do desc "Whether the group is a system group with lower GID." defaultto false end newparam(:forcelocal, :boolean => true, :required_features => :libuser, :parent => Puppet::Parameter::Boolean) do desc "Forces the management of local accounts when accounts are also being managed by some other NSS" defaultto false end # This method has been exposed for puppet to manage users and groups of # files in its settings and should not be considered available outside of # puppet. # # (see Puppet::Settings#service_group_available?) # # @return [Boolean] if the group exists on the system # @api private def exists? provider.exists? end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/notify.rb�������������������������������������������������������������0000644�0052762�0001160�00000001642�13417161721�017716� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# # Simple module for logging messages on the client-side module Puppet Type.newtype(:notify) do @doc = "Sends an arbitrary message to the agent run-time log." apply_to_all newproperty(:message, :idempotent => false) do desc "The message to be sent to the log." def sync case @resource["withpath"] when :true send(@resource[:loglevel], self.should) else Puppet.send(@resource[:loglevel], self.should) end return end def retrieve :absent end def insync?(is) false end defaultto { @resource[:name] } end newparam(:withpath) do desc "Whether to show the full object path." defaultto :false newvalues(:true, :false) end newparam(:name) do desc "An arbitrary tag for your own reference; the name of the message." isnamevar end end end ����������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/resources.rb����������������������������������������������������������0000644�0052762�0001160�00000013755�13417161721�020430� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet' require 'puppet/parameter/boolean' Puppet::Type.newtype(:resources) do @doc = "This is a metatype that can manage other resource types. Any metaparams specified here will be passed on to any generated resources, so you can purge unmanaged resources but set `noop` to true so the purging is only logged and does not actually happen." newparam(:name) do desc "The name of the type to be managed." validate do |name| raise ArgumentError, _("Could not find resource type '%{name}'") % { name: name } unless Puppet::Type.type(name) end munge { |v| v.to_s } end newparam(:purge, :boolean => true, :parent => Puppet::Parameter::Boolean) do desc "Whether to purge unmanaged resources. When set to `true`, this will delete any resource that is not specified in your configuration and is not autorequired by any managed resources. **Note:** The `ssh_authorized_key` resource type can't be purged this way; instead, see the `purge_ssh_keys` attribute of the `user` type." defaultto :false validate do |value| if munge(value) unless @resource.resource_type.respond_to?(:instances) raise ArgumentError, _("Purging resources of type %{res_type} is not supported, since they cannot be queried from the system") % { res_type: @resource[:name] } end raise ArgumentError, _("Purging is only supported on types that accept 'ensure'") unless @resource.resource_type.validproperty?(:ensure) end end end newparam(:unless_system_user) do desc "This keeps system users from being purged. By default, it does not purge users whose UIDs are less than the minimum UID for the system (typically 500 or 1000), but you can specify a different UID as the inclusive limit." newvalues(:true, :false, /^\d+$/) munge do |value| case value when /^\d+/ Integer(value) when :true, true @resource.class.system_users_max_uid when :false, false false when Integer; value else raise ArgumentError, _("Invalid value %{value}") % { value: value.inspect } end end defaultto { if @resource[:name] == "user" @resource.class.system_users_max_uid else nil end } end newparam(:unless_uid) do desc 'This keeps specific uids or ranges of uids from being purged when purge is true. Accepts integers, integer strings, and arrays of integers or integer strings. To specify a range of uids, consider using the range() function from stdlib.' munge do |value| value = [value] unless value.is_a? Array value.flatten.collect do |v| case v when Integer v when String Integer(v) else raise ArgumentError, _("Invalid value %{value}.") % { value: v.inspect } end end end end WINDOWS_SYSTEM_SID_REGEXES = # Administrator, Guest, Domain Admins, Schema Admins, Enterprise Admins. # https://support.microsoft.com/en-us/help/243330/well-known-security-identifiers-in-windows-operating-systems [/S-1-5-21.+-500/, /S-1-5-21.+-501/, /S-1-5-21.+-512/, /S-1-5-21.+-518/, /S-1-5-21.+-519/] def check(resource) @checkmethod ||= "#{self[:name]}_check" @hascheck ||= respond_to?(@checkmethod) if @hascheck return send(@checkmethod, resource) else return true end end def able_to_ensure_absent?(resource) resource[:ensure] = :absent rescue ArgumentError, Puppet::Error err _("The 'ensure' attribute on %{name} resources does not accept 'absent' as a value") % { name: self[:name] } false end # Generate any new resources we need to manage. This is pretty hackish # right now, because it only supports purging. def generate return [] unless self.purge? resource_type.instances. reject { |r| catalog.resource_refs.include? r.ref }. select { |r| check(r) }. select { |r| r.class.validproperty?(:ensure) }. select { |r| able_to_ensure_absent?(r) }. each { |resource| @parameters.each do |name, param| resource[name] = param.value if param.metaparam? end # Mark that we're purging, so transactions can handle relationships # correctly resource.purging } end def resource_type unless defined?(@resource_type) unless type = Puppet::Type.type(self[:name]) raise Puppet::DevError, _("Could not find resource type") end @resource_type = type end @resource_type end # Make sure we don't purge users with specific uids def user_check(resource) return true unless self[:name] == "user" return true unless self[:unless_system_user] resource[:audit] = :uid current_values = resource.retrieve_resource current_uid = current_values[resource.property(:uid)] unless_uids = self[:unless_uid] return false if system_users.include?(resource[:name]) return false if unless_uids && unless_uids.include?(current_uid) if current_uid.is_a?(String) # Windows user; is a system user if any regex matches. WINDOWS_SYSTEM_SID_REGEXES.none? { |regex| current_uid =~ regex } else current_uid > self[:unless_system_user] end end def system_users %w{root nobody bin noaccess daemon sys} end def self.system_users_max_uid return @system_users_max_uid if @system_users_max_uid # First try to read the minimum user id from login.defs if Puppet::FileSystem.exist?('/etc/login.defs') @system_users_max_uid = Puppet::FileSystem.each_line '/etc/login.defs' do |line| break $1.to_i - 1 if line =~ /^\s*UID_MIN\s+(\d+)(\s*#.*)?$/ end end # Otherwise, use a sensible default based on the OS family @system_users_max_uid ||= case Facter.value(:osfamily) when 'OpenBSD', 'FreeBSD' 999 else 499 end @system_users_max_uid end def self.reset_system_users_max_uid! @system_users_max_uid = nil end end �������������������puppet-5.5.10/lib/puppet/type/stage.rb��������������������������������������������������������������0000644�0052762�0001160�00000001537�13417161721�017514� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.newtype(:stage) do desc "A resource type for creating new run stages. Once a stage is available, classes can be assigned to it by declaring them with the resource-like syntax and using [the `stage` metaparameter](https://puppet.com/docs/puppet/latest/metaparameter.html#stage). Note that new stages are not useful unless you also declare their order in relation to the default `main` stage. A complete run stage example: stage { 'pre': before => Stage['main'], } class { 'apt-updates': stage => 'pre', } Individual resources cannot be assigned to run stages; you can only set stages for classes." newparam :name do desc "The name of the stage. Use this as the value for the `stage` metaparameter when assigning classes to this stage." end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/whit.rb���������������������������������������������������������������0000644�0052762�0001160�00000002430�13417161721�017355� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.newtype(:whit) do desc "Whits are internal artifacts of Puppet's current implementation, and Puppet suppresses their appearance in all logs. We make no guarantee of the whit's continued existence, and it should never be used in an actual manifest. Use the `anchor` type from the puppetlabs-stdlib module if you need arbitrary whit-like no-op resources." newparam :name do desc "The name of the whit, because it must have one." end # Hide the fact that we're a whit from logs. # # I hate you, milkman whit. You are so painful, so often. # # In this case the memoized version means we generate a new string about 1.9 # percent of the time, and we allocate about 1.6MB less memory, and generate # a whole lot less GC churn. # # That number probably goes up at least O(n) with the complexity of your # catalog, and I suspect beyond that, because that is, like, 10,000 calls # for 200 distinct objects. Even with just linear, that is a constant # factor of, like, 50n. --daniel 2012-07-17 def to_s @to_s ||= name.sub(/^completed_|^admissible_/, "") end alias path to_s 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 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/augeas.rb�������������������������������������������������������������0000644�0052762�0001160�00000016475�13417161722�017666� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# # Copyright 2011 Bryan Kearney <bkearney@redhat.com> # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require 'puppet/parameter/boolean' Puppet::Type.newtype(:augeas) do include Puppet::Util feature :parse_commands, "Parse the command string" feature :need_to_run?, "If the command should run" feature :execute_changes, "Actually make the changes" @doc = <<-'EOT' Apply a change or an array of changes to the filesystem using the augeas tool. Requires: - [Augeas](http://www.augeas.net) - The ruby-augeas bindings Sample usage with a string: augeas{"test1" : context => "/files/etc/sysconfig/firstboot", changes => "set RUN_FIRSTBOOT YES", onlyif => "match other_value size > 0", } Sample usage with an array and custom lenses: augeas{"jboss_conf": context => "/files", changes => [ "set etc/jbossas/jbossas.conf/JBOSS_IP $ipaddress", "set etc/jbossas/jbossas.conf/JAVA_HOME /usr", ], load_path => "$/usr/share/jbossas/lenses", } EOT newparam (:name) do desc "The name of this task. Used for uniqueness." isnamevar end newparam (:context) do desc "Optional context path. This value is prepended to the paths of all changes if the path is relative. If the `incl` parameter is set, defaults to `/files + incl`; otherwise, defaults to the empty string." defaultto "" munge do |value| if value.empty? and resource[:incl] "/files" + resource[:incl] else value end end end newparam (:onlyif) do desc "Optional augeas command and comparisons to control the execution of this type. Note: `values` is not an actual augeas API command. It calls `match` to retrieve an array of paths in <MATCH_PATH> and then `get` to retrieve the values from each of the returned paths. Supported onlyif syntax: * `get <AUGEAS_PATH> <COMPARATOR> <STRING>` * `values <MATCH_PATH> include <STRING>` * `values <MATCH_PATH> not_include <STRING>` * `values <MATCH_PATH> == <AN_ARRAY>` * `values <MATCH_PATH> != <AN_ARRAY>` * `match <MATCH_PATH> size <COMPARATOR> <INT>` * `match <MATCH_PATH> include <STRING>` * `match <MATCH_PATH> not_include <STRING>` * `match <MATCH_PATH> == <AN_ARRAY>` * `match <MATCH_PATH> != <AN_ARRAY>` where: * `AUGEAS_PATH` is a valid path scoped by the context * `MATCH_PATH` is a valid match syntax scoped by the context * `COMPARATOR` is one of `>, >=, !=, ==, <=,` or `<` * `STRING` is a string * `INT` is a number * `AN_ARRAY` is in the form `['a string', 'another']`" defaultto "" end newparam(:changes) do desc "The changes which should be applied to the filesystem. This can be a command or an array of commands. The following commands are supported: * `set <PATH> <VALUE>` --- Sets the value `VALUE` at location `PATH` * `setm <PATH> <SUB> <VALUE>` --- Sets multiple nodes (matching `SUB` relative to `PATH`) to `VALUE` * `rm <PATH>` --- Removes the node at location `PATH` * `remove <PATH>` --- Synonym for `rm` * `clear <PATH>` --- Sets the node at `PATH` to `NULL`, creating it if needed * `clearm <PATH> <SUB>` --- Sets multiple nodes (matching `SUB` relative to `PATH`) to `NULL` * `touch <PATH>` --- Creates `PATH` with the value `NULL` if it does not exist * `ins <LABEL> (before|after) <PATH>` --- Inserts an empty node `LABEL` either before or after `PATH`. * `insert <LABEL> <WHERE> <PATH>` --- Synonym for `ins` * `mv <PATH> <OTHER PATH>` --- Moves a node at `PATH` to the new location `OTHER PATH` * `move <PATH> <OTHER PATH>` --- Synonym for `mv` * `rename <PATH> <LABEL>` --- Rename a node at `PATH` to a new `LABEL` * `defvar <NAME> <PATH>` --- Sets Augeas variable `$NAME` to `PATH` * `defnode <NAME> <PATH> <VALUE>` --- Sets Augeas variable `$NAME` to `PATH`, creating it with `VALUE` if needed If the `context` parameter is set, that value is prepended to any relative `PATH`s." end newparam(:root) do desc "A file system path; all files loaded by Augeas are loaded underneath `root`." defaultto "/" end newparam(:load_path) do desc "Optional colon-separated list or array of directories; these directories are searched for schema definitions. The agent's `$libdir/augeas/lenses` path will always be added to support pluginsync." defaultto "" end newparam(:force) do desc "Optional command to force the augeas type to execute even if it thinks changes will not be made. This does not override the `onlyif` parameter." defaultto false end newparam(:type_check) do desc "Whether augeas should perform typechecking." newvalues(:true, :false) defaultto :false end newparam(:lens) do desc "Use a specific lens, such as `Hosts.lns`. When this parameter is set, you must also set the `incl` parameter to indicate which file to load. The Augeas documentation includes [a list of available lenses](http://augeas.net/stock_lenses.html)." end newparam(:incl) do desc "Load only a specific file, such as `/etc/hosts`. This can greatly speed up the execution the resource. When this parameter is set, you must also set the `lens` parameter to indicate which lens to use." end validate do has_lens = !self[:lens].nil? has_incl = !self[:incl].nil? self.fail _("You must specify both the lens and incl parameters, or neither.") if has_lens != has_incl end newparam(:show_diff, :boolean => true, :parent => Puppet::Parameter::Boolean) do desc "Whether to display differences when the file changes, defaulting to true. This parameter is useful for files that may contain passwords or other secret data, which might otherwise be included in Puppet reports or other insecure outputs. If the global `show_diff` setting is false, then no diffs will be shown even if this parameter is true." defaultto :true end # This is the actual meat of the code. It forces # augeas to be run and fails or not based on the augeas return # code. newproperty(:returns) do |property| include Puppet::Util desc "The expected return code from the augeas command. Should not be set." defaultto 0 # Make output a bit prettier def change_to_s(currentvalue, newvalue) _("executed successfully") end # if the onlyif resource is provided, then the value is parsed. # a return value of 0 will stop execution because it matches the # default value. def retrieve if @resource.provider.need_to_run?() :need_to_run else 0 end end # Actually execute the command. def sync @resource.provider.execute_changes end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/computer.rb�����������������������������������������������������������0000644�0052762�0001160�00000003532�13417161722�020245� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.newtype(:computer) do @doc = "Computer object management using DirectoryService on OS X. Note that these are distinctly different kinds of objects to 'hosts', as they require a MAC address and can have all sorts of policy attached to them. This provider only manages Computer objects in the local directory service domain, not in remote directories. If you wish to manage `/etc/hosts` file on Mac OS X, then simply use the host type as per other platforms. This type primarily exists to create localhost Computer objects that MCX policy can then be attached to. **Autorequires:** If Puppet is managing the plist file representing a Computer object (located at `/var/db/dslocal/nodes/Default/computers/{name}.plist`), the Computer resource will autorequire it." # ensurable # We autorequire the computer object in case it is being managed at the # file level by Puppet. autorequire(:file) do if self[:name] "/var/db/dslocal/nodes/Default/computers/#{self[:name]}.plist" else nil end end newproperty(:ensure, :parent => Puppet::Property::Ensure) do desc "Control the existences of this computer record. Set this attribute to `present` to ensure the computer record exists. Set it to `absent` to delete any computer records with this name" newvalue(:present) do provider.create end newvalue(:absent) do provider.delete end end newparam(:name) do desc "The authoritative 'short' name of the computer record." isnamevar end newparam(:realname) do desc "The 'long' name of the computer record." end newproperty(:en_address) do desc "The MAC address of the primary network interface. Must match en0." end newproperty(:ip_address) do desc "The IP Address of the Computer object." end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/cron.rb���������������������������������������������������������������0000644�0052762�0001160�00000034536�13417161722�017360� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'etc' require 'facter' require 'puppet/util/filetype' Puppet::Type.newtype(:cron) do @doc = <<-'EOT' Installs and manages cron jobs. Every cron resource created by Puppet requires a command and at least one periodic attribute (hour, minute, month, monthday, weekday, or special). While the name of the cron job is not part of the actual job, the name is stored in a comment beginning with `# Puppet Name: `. These comments are used to match crontab entries created by Puppet with cron resources. If an existing crontab entry happens to match the scheduling and command of a cron resource that has never been synced, Puppet will defer to the existing crontab entry and will not create a new entry tagged with the `# Puppet Name: ` comment. Example: cron { 'logrotate': command => '/usr/sbin/logrotate', user => 'root', hour => 2, minute => 0, } Note that all periodic attributes can be specified as an array of values: cron { 'logrotate': command => '/usr/sbin/logrotate', user => 'root', hour => [2, 4], } ...or using ranges or the step syntax `*/2` (although there's no guarantee that your `cron` daemon supports these): cron { 'logrotate': command => '/usr/sbin/logrotate', user => 'root', hour => ['2-4'], minute => '*/10', } An important note: _the Cron type will not reset parameters that are removed from a manifest_. For example, removing a `minute => 10` parameter will not reset the minute component of the associated cronjob to `*`. These changes must be expressed by setting the parameter to `minute => absent` because Puppet only manages parameters that are out of sync with manifest entries. **Autorequires:** If Puppet is managing the user account specified by the `user` property of a cron resource, then the cron resource will autorequire that user. EOT ensurable # A base class for all of the Cron parameters, since they all have # similar argument checking going on. class CronParam < Puppet::Property class << self attr_accessor :boundaries, :default end # We have to override the parent method, because we consume the entire # "should" array def insync?(is) self.is_to_s(is) == self.should_to_s end # A method used to do parameter input handling. Converts integers # in string form to actual integers, and returns the value if it's # an integer or false if it's just a normal string. def numfix(num) if num =~ /^\d+$/ return num.to_i elsif num.is_a?(Integer) return num else return false end end # Verify that a number is within the specified limits. Return the # number if it is, or false if it is not. def limitcheck(num, lower, upper) (num >= lower and num <= upper) && num end # Verify that a value falls within the specified array. Does case # insensitive matching, and supports matching either the entire word # or the first three letters of the word. def alphacheck(value, ary) tmp = value.downcase # If they specified a shortened version of the name, then see # if we can lengthen it (e.g., mon => monday). if tmp.length == 3 ary.each_with_index { |name, index| if tmp.upcase == name[0..2].upcase return index end } else return ary.index(tmp) if ary.include?(tmp) end false end def should_to_s(value = @should) if value if value.is_a?(Array) && (name == :command || value[0].is_a?(Symbol)) value = value[0] end super(value) else nil end end def is_to_s(value = @is) if value if value.is_a?(Array) && (name == :command || value[0].is_a?(Symbol)) value = value[0] end super(value) else nil end end def should if @should and @should[0] == :absent :absent else @should end end def should=(ary) super @should.flatten! end # The method that does all of the actual parameter value # checking; called by all of the +param<name>=+ methods. # Requires the value, type, and bounds, and optionally supports # a boolean of whether to do alpha checking, and if so requires # the ary against which to do the checking. munge do |value| # Support 'absent' as a value, so that they can remove # a value if value == "absent" or value == :absent return :absent end # Allow the */2 syntax if value =~ /^\*\/[0-9]+$/ return value end # Allow ranges if value =~ /^[0-9]+-[0-9]+$/ return value end # Allow ranges + */2 if value =~ /^[0-9]+-[0-9]+\/[0-9]+$/ return value end if value == "*" return :absent end return value unless self.class.boundaries lower, upper = self.class.boundaries retval = nil if num = numfix(value) retval = limitcheck(num, lower, upper) elsif respond_to?(:alpha) # If it has an alpha method defined, then we check # to see if our value is in that list and if so we turn # it into a number retval = alphacheck(value, alpha) end if retval return retval.to_s else self.fail _("%{value} is not a valid %{name}") % { value: value, name: self.class.name } end end end # Somewhat uniquely, this property does not actually change anything -- it # just calls +@resource.sync+, which writes out the whole cron tab for # the user in question. There is no real way to change individual cron # jobs without rewriting the entire cron file. # # Note that this means that managing many cron jobs for a given user # could currently result in multiple write sessions for that user. newproperty(:command, :parent => CronParam) do desc "The command to execute in the cron job. The environment provided to the command varies by local system rules, and it is best to always provide a fully qualified command. The user's profile is not sourced when the command is run, so if the user's environment is desired it should be sourced manually. All cron parameters support `absent` as a value; this will remove any existing values for that field." def retrieve return_value = super return_value = return_value[0] if return_value && return_value.is_a?(Array) return_value end def should if @should if @should.is_a? Array @should[0] else devfail "command is not an array" end else nil end end def munge(value) value.strip end end newproperty(:special) do desc "A special value such as 'reboot' or 'annually'. Only available on supported systems such as Vixie Cron. Overrides more specific time of day/week settings. Set to 'absent' to make puppet revert to a plain numeric schedule." def specials %w{reboot yearly annually monthly weekly daily midnight hourly absent} + [ :absent ] end validate do |value| raise ArgumentError, _("Invalid special schedule %{value}") % { value: value.inspect } unless specials.include?(value) end def munge(value) # Support value absent so that a schedule can be # forced to change to numeric. if value == "absent" or value == :absent return :absent end value end end newproperty(:minute, :parent => CronParam) do self.boundaries = [0, 59] desc "The minute at which to run the cron job. Optional; if specified, must be between 0 and 59, inclusive." end newproperty(:hour, :parent => CronParam) do self.boundaries = [0, 23] desc "The hour at which to run the cron job. Optional; if specified, must be between 0 and 23, inclusive." end newproperty(:weekday, :parent => CronParam) do def alpha %w{sunday monday tuesday wednesday thursday friday saturday} end self.boundaries = [0, 7] desc "The weekday on which to run the command. Optional; if specified, must be either: - A number between 0 and 7, inclusive, with 0 or 7 being Sunday - The name of the day, such as 'Tuesday'." end newproperty(:month, :parent => CronParam) do def alpha # The ___placeholder accounts for the fact that month is unique among # "nameable" crontab entries in that it does not use 0-based indexing. # Padding the array with a placeholder introduces the appropriate shift # in indices. %w{___placeholder january february march april may june july august september october november december} end self.boundaries = [1, 12] desc "The month of the year. Optional; if specified, must be either: - A number between 1 and 12, inclusive, with 1 being January - The name of the month, such as 'December'." end newproperty(:monthday, :parent => CronParam) do self.boundaries = [1, 31] desc "The day of the month on which to run the command. Optional; if specified, must be between 1 and 31." end newproperty(:environment) do desc "Any environment settings associated with this cron job. They will be stored between the header and the job in the crontab. There can be no guarantees that other, earlier settings will not also affect a given cron job. Also, Puppet cannot automatically determine whether an existing, unmanaged environment setting is associated with a given cron job. If you already have cron jobs with environment settings, then Puppet will keep those settings in the same place in the file, but will not associate them with a specific job. Settings should be specified exactly as they should appear in the crontab, like `PATH=/bin:/usr/bin:/usr/sbin`." validate do |value| unless value =~ /^\s*(\w+)\s*=\s*(.*)\s*$/ or value == :absent or value == "absent" raise ArgumentError, _("Invalid environment setting %{value}") % { value: value.inspect } end end def insync?(is) if is.is_a? Array return is.sort == @should.sort else return is == @should end end def should @should end def should_to_s(newvalue = @should) if newvalue newvalue.join(",") else nil end end end newparam(:name) do desc "The symbolic name of the cron job. This name is used for human reference only and is generated automatically for cron jobs found on the system. This generally won't matter, as Puppet will do its best to match existing cron jobs against specified jobs (and Puppet adds a comment to cron jobs it adds), but it is at least possible that converting from unmanaged jobs to managed jobs might require manual intervention." isnamevar end newproperty(:user) do desc "The user who owns the cron job. This user must be allowed to run cron jobs, which is not currently checked by Puppet. This property defaults to the user running Puppet or `root`. The default crontab provider executes the system `crontab` using the user account specified by this property." defaultto { if not provider.is_a?(@resource.class.provider(:crontab)) struct = Etc.getpwuid(Process.uid) struct.respond_to?(:name) && struct.name or 'root' end } end # Autorequire the owner of the crontab entry. autorequire(:user) do self[:user] end newproperty(:target) do desc "The name of the crontab file in which the cron job should be stored. This property defaults to the value of the `user` property if set, the user running Puppet or `root`. For the default crontab provider, this property is functionally equivalent to the `user` property and should be avoided. In particular, setting both `user` and `target` to different values will result in undefined behavior." defaultto { if provider.is_a?(@resource.class.provider(:crontab)) if val = @resource.should(:user) val else struct = Etc.getpwuid(Process.uid) struct.respond_to?(:name) && struct.name or 'root' end elsif provider.class.ancestors.include?(Puppet::Provider::ParsedFile) provider.class.default_target else nil end } end validate do return true unless self[:special] return true if self[:special] == :absent # there is a special schedule in @should, so we don't want to see # any numeric should values [ :minute, :hour, :weekday, :monthday, :month ].each do |field| next unless self[field] next if self[field] == :absent raise ArgumentError, _("%{cron} cannot specify both a special schedule and a value for %{field}") % { cron: self.ref, field: field } end end # We have to reorder things so that :provide is before :target attr_accessor :uid # Marks the resource as "being purged". # # @api public # # @note This overrides the Puppet::Type method in order to handle # an edge case that has so far been observed during testing only. # Without forcing the should-value for the user property to be # identical to the original cron file, purging from a fixture # will not work, because the user property defaults to the user # running the test. It is not clear whether this scenario can apply # during normal operation. # # @note Also, when not forcing the should-value for the target # property, unpurged file content (such as comments) can end up # being written to the default target (i.e. the current login name). def purging self[:target] = provider.target self[:user] = provider.target super end def value(name) name = name.intern ret = nil if obj = @parameters[name] ret = obj.should ret ||= obj.retrieve if ret == :absent ret = nil end end unless ret case name when :command when :special # nothing else #ret = (self.class.validproperty?(name).default || "*").to_s ret = "*" end end ret end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/exec.rb���������������������������������������������������������������0000644�0052762�0001160�00000052504�13417161722�017336� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet Type.newtype(:exec) do include Puppet::Util::Execution require 'timeout' @doc = "Executes external commands. Any command in an `exec` resource **must** be able to run multiple times without causing harm --- that is, it must be *idempotent*. There are three main ways for an exec to be idempotent: * The command itself is already idempotent. (For example, `apt-get update`.) * The exec has an `onlyif`, `unless`, or `creates` attribute, which prevents Puppet from running the command unless some condition is met. * The exec has `refreshonly => true`, which only allows Puppet to run the command when some other resource is changed. (See the notes on refreshing below.) A caution: There's a widespread tendency to use collections of execs to manage resources that aren't covered by an existing resource type. This works fine for simple tasks, but once your exec pile gets complex enough that you really have to think to understand what's happening, you should consider developing a custom resource type instead, as it will be much more predictable and maintainable. **Refresh:** `exec` resources can respond to refresh events (via `notify`, `subscribe`, or the `~>` arrow). The refresh behavior of execs is non-standard, and can be affected by the `refresh` and `refreshonly` attributes: * If `refreshonly` is set to true, the exec will _only_ run when it receives an event. This is the most reliable way to use refresh with execs. * If the exec already would have run and receives an event, it will run its command **up to two times.** (If an `onlyif`, `unless`, or `creates` condition is no longer met after the first run, the second run will not occur.) * If the exec already would have run, has a `refresh` command, and receives an event, it will run its normal command, then run its `refresh` command (as long as any `onlyif`, `unless`, or `creates` conditions are still met after the normal command finishes). * If the exec would **not** have run (due to an `onlyif`, `unless`, or `creates` attribute) and receives an event, it still will not run. * If the exec has `noop => true`, would otherwise have run, and receives an event from a non-noop resource, it will run once (or run its `refresh` command instead, if it has one). In short: If there's a possibility of your exec receiving refresh events, it becomes doubly important to make sure the run conditions are restricted. **Autorequires:** If Puppet is managing an exec's cwd or the executable file used in an exec's command, the exec resource will autorequire those files. If Puppet is managing the user that an exec should run as, the exec resource will autorequire that user." # Create a new check mechanism. It's basically just a parameter that # provides one extra 'check' method. def self.newcheck(name, options = {}, &block) @checks ||= {} check = newparam(name, options, &block) @checks[name] = check end def self.checks @checks.keys end newproperty(:returns, :array_matching => :all, :event => :executed_command) do |property| include Puppet::Util::Execution munge do |value| value.to_s end def event_name :executed_command end defaultto "0" attr_reader :output desc "The expected exit code(s). An error will be returned if the executed command has some other exit code. Can be specified as an array of acceptable exit codes or a single value. On POSIX systems, exit codes are always integers between 0 and 255. On Windows, **most** exit codes should be integers between 0 and 2147483647. Larger exit codes on Windows can behave inconsistently across different tools. The Win32 APIs define exit codes as 32-bit unsigned integers, but both the cmd.exe shell and the .NET runtime cast them to signed integers. This means some tools will report negative numbers for exit codes above 2147483647. (For example, cmd.exe reports 4294967295 as -1.) Since Puppet uses the plain Win32 APIs, it will report the very large number instead of the negative number, which might not be what you expect if you got the exit code from a cmd.exe session. Microsoft recommends against using negative/very large exit codes, and you should avoid them when possible. To convert a negative exit code to the positive one Puppet will use, add it to 4294967296." # Make output a bit prettier def change_to_s(currentvalue, newvalue) _("executed successfully") end # First verify that all of our checks pass. def retrieve # We need to return :notrun to trigger evaluation; when that isn't # true, we *LIE* about what happened and return a "success" for the # value, which causes us to be treated as in_sync?, which means we # don't actually execute anything. I think. --daniel 2011-03-10 if @resource.check_all_attributes return :notrun else return self.should end end # Actually execute the command. def sync event = :executed_command tries = self.resource[:tries] try_sleep = self.resource[:try_sleep] begin tries.times do |try| # Only add debug messages for tries > 1 to reduce log spam. debug("Exec try #{try+1}/#{tries}") if tries > 1 @output, @status = provider.run(self.resource[:command]) break if self.should.include?(@status.exitstatus.to_s) if try_sleep > 0 and tries > 1 debug("Sleeping for #{try_sleep} seconds between tries") sleep try_sleep end end rescue Timeout::Error self.fail Puppet::Error, _("Command exceeded timeout"), $! end if log = @resource[:logoutput] case log when :true log = @resource[:loglevel] when :on_failure unless self.should.include?(@status.exitstatus.to_s) log = @resource[:loglevel] else log = :false end end unless log == :false @output.split(/\n/).each { |line| self.send(log, line) } end end unless self.should.include?(@status.exitstatus.to_s) if @resource.parameter(:command).sensitive # Don't print sensitive commands in the clear self.fail(_("[command redacted] returned %{status} instead of one of [%{expected}]") % { status: @status.exitstatus, expected: self.should.join(",") }) else self.fail(_("'%{cmd}' returned %{status} instead of one of [%{expected}]") % { cmd: self.resource[:command], status: @status.exitstatus, expected: self.should.join(",") }) end end event end end newparam(:command) do isnamevar desc "The actual command to execute. Must either be fully qualified or a search path for the command must be provided. If the command succeeds, any output produced will be logged at the instance's normal log level (usually `notice`), but if the command fails (meaning its return code does not match the specified code) then any output is logged at the `err` log level." validate do |command| raise ArgumentError, _("Command must be a String, got value of class %{klass}") % { klass: command.class } unless command.is_a? String end end newparam(:path) do desc "The search path used for command execution. Commands must be fully qualified if no path is specified. Paths can be specified as an array or as a '#{File::PATH_SEPARATOR}' separated list." # Support both arrays and colon-separated fields. def value=(*values) @value = values.flatten.collect { |val| val.split(File::PATH_SEPARATOR) }.flatten end end newparam(:user) do desc "The user to run the command as. > **Note:** Puppet cannot execute commands as other users on Windows. Note that if you use this attribute, any error output is not captured due to a bug within Ruby. If you use Puppet to create this user, the exec automatically requires the user, as long as it is specified by name. The $HOME environment variable is not automatically set when using this attribute." validate do |user| if Puppet.features.microsoft_windows? self.fail _("Unable to execute commands as other users on Windows") elsif !Puppet.features.root? && resource.current_username() != user self.fail _("Only root can execute commands as other users") end end end newparam(:group) do desc "The group to run the command as. This seems to work quite haphazardly on different platforms -- it is a platform issue not a Ruby or Puppet one, since the same variety exists when running commands as different users in the shell." # Validation is handled by the SUIDManager class. end newparam(:cwd, :parent => Puppet::Parameter::Path) do desc "The directory from which to run the command. If this directory does not exist, the command will fail." end newparam(:logoutput) do desc "Whether to log command output in addition to logging the exit code. Defaults to `on_failure`, which only logs the output when the command has an exit code that does not match any value specified by the `returns` attribute. As with any resource type, the log level can be controlled with the `loglevel` metaparameter." defaultto :on_failure newvalues(:true, :false, :on_failure) end newparam(:refresh) do desc "An alternate command to run when the `exec` receives a refresh event from another resource. By default, Puppet runs the main command again. For more details, see the notes about refresh behavior above, in the description for this resource type. Note that this alternate command runs with the same `provider`, `path`, `user`, and `group` as the main command. If the `path` isn't set, you must fully qualify the command's name." validate do |command| provider.validatecmd(command) end end newparam(:environment) do desc "An array of any additional environment variables you want to set for a command, such as `[ 'HOME=/root', 'MAIL=root@example.com']`. Note that if you use this to set PATH, it will override the `path` attribute. Multiple environment variables should be specified as an array." validate do |values| values = [values] unless values.is_a? Array values.each do |value| unless value =~ /\w+=/ raise ArgumentError, _("Invalid environment setting '%{value}'") % { value: value } end end end end newparam(:umask, :required_feature => :umask) do desc "Sets the umask to be used while executing this command" munge do |value| if value =~ /^0?[0-7]{1,4}$/ return value.to_i(8) else raise Puppet::Error, _("The umask specification is invalid: %{value}") % { value: value.inspect } end end end newparam(:timeout) do desc "The maximum time the command should take. If the command takes longer than the timeout, the command is considered to have failed and will be stopped. The timeout is specified in seconds. The default timeout is 300 seconds and you can set it to 0 to disable the timeout." munge do |value| value = value.shift if value.is_a?(Array) begin value = Float(value) rescue ArgumentError raise ArgumentError, _("The timeout must be a number."), $!.backtrace end [value, 0.0].max end defaultto 300 end newparam(:tries) do desc "The number of times execution of the command should be tried. This many attempts will be made to execute the command until an acceptable return code is returned. Note that the timeout parameter applies to each try rather than to the complete set of tries." munge do |value| if value.is_a?(String) unless value =~ /^[\d]+$/ raise ArgumentError, _("Tries must be an integer") end value = Integer(value) end raise ArgumentError, _("Tries must be an integer >= 1") if value < 1 value end defaultto 1 end newparam(:try_sleep) do desc "The time to sleep in seconds between 'tries'." munge do |value| if value.is_a?(String) unless value =~ /^[-\d.]+$/ raise ArgumentError, _("try_sleep must be a number") end value = Float(value) end raise ArgumentError, _("try_sleep cannot be a negative number") if value < 0 value end defaultto 0 end newcheck(:refreshonly) do desc <<-'EOT' The command should only be run as a refresh mechanism for when a dependent object is changed. It only makes sense to use this option when this command depends on some other object; it is useful for triggering an action: # Pull down the main aliases file file { '/etc/aliases': source => 'puppet://server/module/aliases', } # Rebuild the database, but only when the file changes exec { newaliases: path => ['/usr/bin', '/usr/sbin'], subscribe => File['/etc/aliases'], refreshonly => true, } Note that only `subscribe` and `notify` can trigger actions, not `require`, so it only makes sense to use `refreshonly` with `subscribe` or `notify`. EOT newvalues(:true, :false) # We always fail this test, because we're only supposed to run # on refresh. def check(value) # We have to invert the values. if value == :true false else true end end end newcheck(:creates, :parent => Puppet::Parameter::Path) do desc <<-'EOT' A file to look for before running the command. The command will only run if the file **doesn't exist.** This parameter doesn't cause Puppet to create a file; it is only useful if **the command itself** creates a file. exec { 'tar -xf /Volumes/nfs02/important.tar': cwd => '/var/tmp', creates => '/var/tmp/myfile', path => ['/usr/bin', '/usr/sbin',], } In this example, `myfile` is assumed to be a file inside `important.tar`. If it is ever deleted, the exec will bring it back by re-extracting the tarball. If `important.tar` does **not** actually contain `myfile`, the exec will keep running every time Puppet runs. EOT accept_arrays # If the file exists, return false (i.e., don't run the command), # else return true def check(value) ! Puppet::FileSystem.exist?(value) end end newcheck(:unless) do desc <<-'EOT' A test command that checks the state of the target system and restricts when the `exec` can run. If present, Puppet runs this test command first, then runs the main command unless the test has an exit code of 0 (success). For example: exec { '/bin/echo root >> /usr/lib/cron/cron.allow': path => '/usr/bin:/usr/sbin:/bin', unless => 'grep root /usr/lib/cron/cron.allow 2>/dev/null', } This would add `root` to the cron.allow file (on Solaris) unless `grep` determines it's already there. Note that this test command runs with the same `provider`, `path`, `user`, and `group` as the main command. If the `path` isn't set, you must fully qualify the command's name. This parameter can also take an array of commands. For example: unless => ['test -f /tmp/file1', 'test -f /tmp/file2'], This `exec` would only run if every command in the array has a non-zero exit code. EOT validate do |cmds| cmds = [cmds] unless cmds.is_a? Array cmds.each do |command| provider.validatecmd(command) end end # Return true if the command does not return 0. def check(value) begin output, status = provider.run(value, true) rescue Timeout::Error err _("Check %{value} exceeded timeout") % { value: value.inspect } return false end output.split(/\n/).each { |line| self.debug(line) } status.exitstatus != 0 end end newcheck(:onlyif) do desc <<-'EOT' A test command that checks the state of the target system and restricts when the `exec` can run. If present, Puppet runs this test command first, and only runs the main command if the test has an exit code of 0 (success). For example: exec { 'logrotate': path => '/usr/bin:/usr/sbin:/bin', provider => shell, onlyif => 'test `du /var/log/messages | cut -f1` -gt 100000', } This would run `logrotate` only if that test returns true. Note that this test command runs with the same `provider`, `path`, `user`, and `group` as the main command. If the `path` isn't set, you must fully qualify the command's name. This parameter can also take an array of commands. For example: onlyif => ['test -f /tmp/file1', 'test -f /tmp/file2'], This `exec` would only run if every command in the array has an exit code of 0 (success). EOT validate do |cmds| cmds = [cmds] unless cmds.is_a? Array cmds.each do |command| provider.validatecmd(command) end end # Return true if the command returns 0. def check(value) begin output, status = provider.run(value, true) rescue Timeout::Error err _("Check %{value} exceeded timeout") % { value: value.inspect } return false end output.split(/\n/).each { |line| self.debug(line) } status.exitstatus == 0 end end # Exec names are not isomorphic with the objects. @isomorphic = false validate do provider.validatecmd(self[:command]) end # FIXME exec should autorequire any exec that 'creates' our cwd autorequire(:file) do reqs = [] # Stick the cwd in there if we have it reqs << self[:cwd] if self[:cwd] file_regex = Puppet.features.microsoft_windows? ? %r{^([a-zA-Z]:[\\/]\S+)} : %r{^(/\S+)} self[:command].scan(file_regex) { |str| reqs << str } self[:command].scan(/^"([^"]+)"/) { |str| reqs << str } [:onlyif, :unless].each { |param| next unless tmp = self[param] tmp = [tmp] unless tmp.is_a? Array tmp.each do |line| # And search the command line for files, adding any we # find. This will also catch the command itself if it's # fully qualified. It might not be a bad idea to add # unqualified files, but, well, that's a bit more annoying # to do. reqs += line.scan(file_regex) end } # For some reason, the += isn't causing a flattening reqs.flatten! reqs end autorequire(:user) do # Autorequire users if they are specified by name if user = self[:user] and user !~ /^\d+$/ user end end def self.instances [] end # Verify that we pass all of the checks. The argument determines whether # we skip the :refreshonly check, which is necessary because we now check # within refresh def check_all_attributes(refreshing = false) self.class.checks.each { |check| next if refreshing and check == :refreshonly if @parameters.include?(check) val = @parameters[check].value val = [val] unless val.is_a? Array val.each do |value| return false unless @parameters[check].check(value) end end } true end def output if self.property(:returns).nil? return nil else return self.property(:returns).output end end # Run the command, or optionally run a separately-specified command. def refresh if self.check_all_attributes(true) if cmd = self[:refresh] provider.run(cmd) else self.property(:returns).sync end end end def current_username Etc.getpwuid(Process.uid).name end private def set_sensitive_parameters(sensitive_parameters) # Respect sensitive commands if sensitive_parameters.include?(:command) sensitive_parameters.delete(:command) parameter(:command).sensitive = true end super(sensitive_parameters) end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/file.rb���������������������������������������������������������������0000644�0052762�0001160�00000110614�13417161722�017326� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'digest/md5' require 'cgi' require 'etc' require 'uri' require 'fileutils' require 'enumerator' require 'pathname' require 'puppet/parameter/boolean' require 'puppet/util/diff' require 'puppet/util/checksums' require 'puppet/util/backups' require 'puppet/util/symbolic_file_mode' Puppet::Type.newtype(:file) do include Puppet::Util::MethodHelper include Puppet::Util::Checksums include Puppet::Util::Backups include Puppet::Util::SymbolicFileMode @doc = "Manages files, including their content, ownership, and permissions. The `file` type can manage normal files, directories, and symlinks; the type should be specified in the `ensure` attribute. File contents can be managed directly with the `content` attribute, or downloaded from a remote source using the `source` attribute; the latter can also be used to recursively serve directories (when the `recurse` attribute is set to `true` or `local`). On Windows, note that file contents are managed in binary mode; Puppet never automatically translates line endings. **Autorequires:** If Puppet is managing the user or group that owns a file, the file resource will autorequire them. If Puppet is managing any parent directories of a file, the file resource will autorequire them." feature :manages_symlinks, "The provider can manage symbolic links." def self.title_patterns # strip trailing slashes from path but allow the root directory, including # for example "/" or "C:/" [ [ %r{^(/|.+:/|.*[^/])/*\Z}m, [ [ :path ] ] ] ] end newparam(:path) do desc <<-'EOT' The path to the file to manage. Must be fully qualified. On Windows, the path should include the drive letter and should use `/` as the separator character (rather than `\\`). EOT isnamevar validate do |value| unless Puppet::Util.absolute_path?(value) fail Puppet::Error, _("File paths must be fully qualified, not '%{path}'") % { path: value } end end munge do |value| if value.start_with?('//') and ::File.basename(value) == "/" # This is a UNC path pointing to a share, so don't add a trailing slash ::File.expand_path(value) else ::File.join(::File.split(::File.expand_path(value))) end end end newparam(:backup) do desc <<-EOT Whether (and how) file content should be backed up before being replaced. This attribute works best as a resource default in the site manifest (`File { backup => main }`), so it can affect all file resources. * If set to `false`, file content won't be backed up. * If set to a string beginning with `.`, such as `.puppet-bak`, Puppet will use copy the file in the same directory with that value as the extension of the backup. (A value of `true` is a synonym for `.puppet-bak`.) * If set to any other string, Puppet will try to back up to a filebucket with that title. See the `filebucket` resource type for more details. (This is the preferred method for backup, since it can be centralized and queried.) Default value: `puppet`, which backs up to a filebucket of the same name. (Puppet automatically creates a **local** filebucket named `puppet` if one doesn't already exist.) Backing up to a local filebucket isn't particularly useful. If you want to make organized use of backups, you will generally want to use the puppet master server's filebucket service. This requires declaring a filebucket resource and a resource default for the `backup` attribute in site.pp: # /etc/puppetlabs/puppet/manifests/site.pp filebucket { 'main': path => false, # This is required for remote filebuckets. server => 'puppet.example.com', # Optional; defaults to the configured puppet master. } File { backup => main, } If you are using multiple puppet master servers, you will want to centralize the contents of the filebucket. Either configure your load balancer to direct all filebucket traffic to a single master, or use something like an out-of-band rsync task to synchronize the content on all masters. EOT defaultto "puppet" munge do |value| # I don't really know how this is happening. value = value.shift if value.is_a?(Array) case value when false, "false", :false false when true, "true", ".puppet-bak", :true ".puppet-bak" when String value else self.fail _("Invalid backup type %{value}") % { value: value.inspect } end end end newparam(:recurse) do desc "Whether to recursively manage the _contents_ of a directory. This attribute is only used when `ensure => directory` is set. The allowed values are: * `false` --- The default behavior. The contents of the directory will not be automatically managed. * `remote` --- If the `source` attribute is set, Puppet will automatically manage the contents of the source directory (or directories), ensuring that equivalent files and directories exist on the target system and that their contents match. Using `remote` will disable the `purge` attribute, but results in faster catalog application than `recurse => true`. The `source` attribute is mandatory when `recurse => remote`. * `true` --- If the `source` attribute is set, this behaves similarly to `recurse => remote`, automatically managing files from the source directory. This also enables the `purge` attribute, which can delete unmanaged files from a directory. See the description of `purge` for more details. The `source` attribute is not mandatory when using `recurse => true`, so you can enable purging in directories where all files are managed individually. By default, setting recurse to `remote` or `true` will manage _all_ subdirectories. You can use the `recurselimit` attribute to limit the recursion depth. " newvalues(:true, :false, :remote) validate { |arg| } munge do |value| newval = super(value) case newval when :true; true when :false; false when :remote; :remote else self.fail _("Invalid recurse value %{value}") % { value: value.inspect } end end end newparam(:recurselimit) do desc "How far Puppet should descend into subdirectories, when using `ensure => directory` and either `recurse => true` or `recurse => remote`. The recursion limit affects which files will be copied from the `source` directory, as well as which files can be purged when `purge => true`. Setting `recurselimit => 0` is the same as setting `recurse => false` --- Puppet will manage the directory, but all of its contents will be treated as unmanaged. Setting `recurselimit => 1` will manage files and directories that are directly inside the directory, but will not manage the contents of any subdirectories. Setting `recurselimit => 2` will manage the direct contents of the directory, as well as the contents of the _first_ level of subdirectories. This pattern continues for each incremental value of `recurselimit`." newvalues(/^[0-9]+$/) munge do |value| newval = super(value) case newval when Integer; value when /^\d+$/; Integer(value) else self.fail _("Invalid recurselimit value %{value}") % { value: value.inspect } end end end newparam(:replace, :boolean => true, :parent => Puppet::Parameter::Boolean) do desc "Whether to replace a file or symlink that already exists on the local system but whose content doesn't match what the `source` or `content` attribute specifies. Setting this to false allows file resources to initialize files without overwriting future changes. Note that this only affects content; Puppet will still manage ownership and permissions." defaultto :true end newparam(:force, :boolean => true, :parent => Puppet::Parameter::Boolean) do desc "Perform the file operation even if it will destroy one or more directories. You must use `force` in order to: * `purge` subdirectories * Replace directories with files or links * Remove a directory when `ensure => absent`" defaultto false end newparam(:ignore) do desc "A parameter which omits action on files matching specified patterns during recursion. Uses Ruby's builtin globbing engine, so shell metacharacters such as `[a-z]*` are fully supported. Matches that would descend into the directory structure are ignored, such as `*/*`." validate do |value| unless value.is_a?(Array) or value.is_a?(String) or value == false self.devfail "Ignore must be a string or an Array" end end end newparam(:links) do desc "How to handle links during file actions. During file copying, `follow` will copy the target file instead of the link and `manage` will copy the link itself. When not copying, `manage` will manage the link, and `follow` will manage the file to which the link points." newvalues(:follow, :manage) defaultto :manage end newparam(:purge, :boolean => true, :parent => Puppet::Parameter::Boolean) do desc "Whether unmanaged files should be purged. This option only makes sense when `ensure => directory` and `recurse => true`. * When recursively duplicating an entire directory with the `source` attribute, `purge => true` will automatically purge any files that are not in the source directory. * When managing files in a directory as individual resources, setting `purge => true` will purge any files that aren't being specifically managed. If you have a filebucket configured, the purged files will be uploaded, but if you do not, this will destroy data. Unless `force => true` is set, purging will **not** delete directories, although it will delete the files they contain. If `recurselimit` is set and you aren't using `force => true`, purging will obey the recursion limit; files in any subdirectories deeper than the limit will be treated as unmanaged and left alone." defaultto :false end newparam(:sourceselect) do desc "Whether to copy all valid sources, or just the first one. This parameter only affects recursive directory copies; by default, the first valid source is the only one used, but if this parameter is set to `all`, then all valid sources will have all of their contents copied to the local system. If a given file exists in more than one source, the version from the earliest source in the list will be used." defaultto :first newvalues(:first, :all) end newparam(:show_diff, :boolean => true, :parent => Puppet::Parameter::Boolean) do desc "Whether to display differences when the file changes, defaulting to true. This parameter is useful for files that may contain passwords or other secret data, which might otherwise be included in Puppet reports or other insecure outputs. If the global `show_diff` setting is false, then no diffs will be shown even if this parameter is true." defaultto :true end newparam(:validate_cmd) do desc "A command for validating the file's syntax before replacing it. If Puppet would need to rewrite a file due to new `source` or `content`, it will check the new content's validity first. If validation fails, the file resource will fail. This command must have a fully qualified path, and should contain a percent (`%`) token where it would expect an input file. It must exit `0` if the syntax is correct, and non-zero otherwise. The command will be run on the target system while applying the catalog, not on the puppet master. Example: file { '/etc/apache2/apache2.conf': content => 'example', validate_cmd => '/usr/sbin/apache2 -t -f %', } This would replace apache2.conf only if the test returned true. Note that if a validation command requires a `%` as part of its text, you can specify a different placeholder token with the `validate_replacement` attribute." end newparam(:validate_replacement) do desc "The replacement string in a `validate_cmd` that will be replaced with an input file name." defaultto '%' end # Autorequire the nearest ancestor directory found in the catalog. autorequire(:file) do req = [] path = Pathname.new(self[:path]) if !path.root? # Start at our parent, to avoid autorequiring ourself parents = path.parent.enum_for(:ascend) if found = parents.find { |p| catalog.resource(:file, p.to_s) } req << found.to_s end end # if the resource is a link, make sure the target is created first req << self[:target] if self[:target] req end # Autorequire the owner and group of the file. {:user => :owner, :group => :group}.each do |type, property| autorequire(type) do if @parameters.include?(property) # The user/group property automatically converts to IDs next unless should = @parameters[property].shouldorig val = should[0] if val.is_a?(Integer) or val =~ /^\d+$/ nil else val end end end end CREATORS = [:content, :source, :target] SOURCE_ONLY_CHECKSUMS = [:none, :ctime, :mtime] validate do creator_count = 0 CREATORS.each do |param| creator_count += 1 if self.should(param) end creator_count += 1 if @parameters.include?(:source) self.fail _("You cannot specify more than one of %{creators}") % { creators: CREATORS.collect { |p| p.to_s}.join(", ") } if creator_count > 1 self.fail _("You cannot specify a remote recursion without a source") if !self[:source] && self[:recurse] == :remote self.fail _("You cannot specify source when using checksum 'none'") if self[:checksum] == :none && !self[:source].nil? SOURCE_ONLY_CHECKSUMS.each do |checksum_type| self.fail _("You cannot specify content when using checksum '%{checksum_type}'") % { checksum_type: checksum_type } if self[:checksum] == checksum_type && !self[:content].nil? end self.warning _("Possible error: recurselimit is set but not recurse, no recursion will happen") if !self[:recurse] && self[:recurselimit] if @parameters[:content] && @parameters[:content].actual_content # Now that we know the checksum, update content (in case it was created before checksum was known). @parameters[:content].value = @parameters[:checksum].sum(@parameters[:content].actual_content) end if self[:checksum] && self[:checksum_value] && !send("#{self[:checksum]}?", self[:checksum_value]) self.fail _("Checksum value '%{value}' is not a valid checksum type %{checksum}") % { value: self[:checksum_value], checksum: self[:checksum] } end self.warning _("Checksum value is ignored unless content or source are specified") if self[:checksum_value] && !self[:content] && !self[:source] provider.validate if provider.respond_to?(:validate) end def self.[](path) return nil unless path super(path.gsub(/\/+/, '/').sub(/\/$/, '')) end def self.instances return [] end # Determine the user to write files as. def asuser if self.should(:owner) && ! self.should(:owner).is_a?(Symbol) writeable = Puppet::Util::SUIDManager.asuser(self.should(:owner)) { FileTest.writable?(::File.dirname(self[:path])) } # If the parent directory is writeable, then we execute # as the user in question. Otherwise we'll rely on # the 'owner' property to do things. asuser = self.should(:owner) if writeable end asuser end def bucket return @bucket if @bucket backup = self[:backup] return nil unless backup return nil if backup =~ /^\./ unless catalog or backup == "puppet" fail _("Can not find filebucket for backups without a catalog") end unless catalog and filebucket = catalog.resource(:filebucket, backup) or backup == "puppet" fail _("Could not find filebucket %{backup} specified in backup") % { backup: backup } end return default_bucket unless filebucket @bucket = filebucket.bucket @bucket end def default_bucket Puppet::Type.type(:filebucket).mkdefaultbucket.bucket end # Does the file currently exist? Just checks for whether # we have a stat def exist? stat ? true : false end def present?(current_values) super && current_values[:ensure] != :false end # We have to do some extra finishing, to retrieve our bucket if # there is one. def finish # Look up our bucket, if there is one bucket super end # Create any children via recursion or whatever. def eval_generate return [] unless self.recurse? recurse end def ancestors ancestors = Pathname.new(self[:path]).enum_for(:ascend).map(&:to_s) ancestors.delete(self[:path]) ancestors end def flush # We want to make sure we retrieve metadata anew on each transaction. @parameters.each do |name, param| param.flush if param.respond_to?(:flush) end @stat = :needs_stat end def initialize(hash) # Used for caching clients @clients = {} super # If they've specified a source, we get our 'should' values # from it. unless self[:ensure] if self[:target] self[:ensure] = :link elsif self[:content] self[:ensure] = :file end end @stat = :needs_stat end # Configure discovered resources to be purged. def mark_children_for_purging(children) children.each do |name, child| next if child[:source] child[:ensure] = :absent end end # Create a new file or directory object as a child to the current # object. def newchild(path) full_path = ::File.join(self[:path], path) # Add some new values to our original arguments -- these are the ones # set at initialization. We specifically want to exclude any param # values set by the :source property or any default values. # LAK:NOTE This is kind of silly, because the whole point here is that # the values set at initialization should live as long as the resource # but values set by default or by :source should only live for the transaction # or so. Unfortunately, we don't have a straightforward way to manage # the different lifetimes of this data, so we kludge it like this. # The right-side hash wins in the merge. options = @original_parameters.merge(:path => full_path).reject { |param, value| value.nil? } # These should never be passed to our children. [:parent, :ensure, :recurse, :recurselimit, :target, :alias, :source].each do |param| options.delete(param) if options.include?(param) end self.class.new(options) end # Files handle paths specially, because they just lengthen their # path names, rather than including the full parent's title each # time. def pathbuilder # We specifically need to call the method here, so it looks # up our parent in the catalog graph. if parent = parent() # We only need to behave specially when our parent is also # a file if parent.is_a?(self.class) # Remove the parent file name list = parent.pathbuilder list.pop # remove the parent's path info return list << self.ref else return super end else return [self.ref] end end # Recursively generate a list of file resources, which will # be used to copy remote files, manage local files, and/or make links # to map to another directory. def recurse children = (self[:recurse] == :remote) ? {} : recurse_local if self[:target] recurse_link(children) elsif self[:source] recurse_remote(children) end # If we're purging resources, then delete any resource that isn't on the # remote system. mark_children_for_purging(children) if self.purge? # REVISIT: sort_by is more efficient? result = children.values.sort { |a, b| a[:path] <=> b[:path] } remove_less_specific_files(result) end def remove_less_specific_files(files) existing_files = catalog.vertices.select { |r| r.is_a?(self.class) } self.class.remove_less_specific_files(files, self[:path], existing_files) do |file| file[:path] end end # This is to fix bug #2296, where two files recurse over the same # set of files. It's a rare case, and when it does happen you're # not likely to have many actual conflicts, which is good, because # this is a pretty inefficient implementation. def self.remove_less_specific_files(files, parent_path, existing_files, &block) # REVISIT: is this Windows safe? AltSeparator? mypath = parent_path.split(::File::Separator) other_paths = existing_files. select { |r| (yield r) != parent_path}. collect { |r| (yield r).split(::File::Separator) }. select { |p| p[0,mypath.length] == mypath } return files if other_paths.empty? files.reject { |file| path = (yield file).split(::File::Separator) other_paths.any? { |p| path[0,p.length] == p } } end # A simple method for determining whether we should be recursing. def recurse? self[:recurse] == true or self[:recurse] == :remote end # Recurse the target of the link. def recurse_link(children) perform_recursion(self[:target]).each do |meta| if meta.relative_path == "." self[:ensure] = :directory next end children[meta.relative_path] ||= newchild(meta.relative_path) if meta.ftype == "directory" children[meta.relative_path][:ensure] = :directory else children[meta.relative_path][:ensure] = :link children[meta.relative_path][:target] = meta.full_path end end children end # Recurse the file itself, returning a Metadata instance for every found file. def recurse_local result = perform_recursion(self[:path]) return {} unless result result.inject({}) do |hash, meta| next hash if meta.relative_path == "." hash[meta.relative_path] = newchild(meta.relative_path) hash end end # Recurse against our remote file. def recurse_remote(children) recurse_remote_metadata.each do |meta| if meta.relative_path == "." self[:checksum] = meta.checksum_type parameter(:source).metadata = meta next end children[meta.relative_path] ||= newchild(meta.relative_path) children[meta.relative_path][:source] = meta.source children[meta.relative_path][:checksum] = meta.checksum_type children[meta.relative_path].parameter(:source).metadata = meta end children end def recurse_remote_metadata sourceselect = self[:sourceselect] total = self[:source].collect do |source| # For each inlined file resource, the catalog contains a hash mapping # source path to lists of metadata returned by a server-side search. if recursive_metadata = catalog.recursive_metadata[title] result = recursive_metadata[source] else result = perform_recursion(source) end next unless result return [] if top = result.find { |r| r.relative_path == "." } and top.ftype != "directory" result.each do |data| if data.relative_path == '.' data.source = source else # REMIND: appending file paths to URL may not be safe, e.g. foo+bar data.source = "#{source}/#{data.relative_path}" end end break result if result and ! result.empty? and sourceselect == :first result end.flatten.compact # This only happens if we have sourceselect == :all unless sourceselect == :first found = [] total.reject! do |data| result = found.include?(data.relative_path) found << data.relative_path unless result result end end total end def perform_recursion(path) Puppet::FileServing::Metadata.indirection.search( path, :links => self[:links], :recurse => (self[:recurse] == :remote ? true : self[:recurse]), :recurselimit => self[:recurselimit], :source_permissions => self[:source_permissions], :ignore => self[:ignore], :checksum_type => (self[:source] || self[:content]) ? self[:checksum] : :none, :environment => catalog.environment_instance ) end # Back up and remove the file or directory at `self[:path]`. # # @param [Symbol] should The file type replacing the current content. # @return [Boolean] True if the file was removed, else False # @raises [fail???] If the file could not be backed up or could not be removed. def remove_existing(should) wanted_type = should.to_s current_type = read_current_type if current_type.nil? return false end if self[:backup] if can_backup?(current_type) backup_existing else self.warning _("Could not back up file of type %{current_type}") % { current_type: current_type } end end if wanted_type != "link" and current_type == wanted_type return false end case current_type when "directory" return remove_directory(wanted_type) when "link", "file", "fifo", "socket" return remove_file(current_type, wanted_type) else # Including: “blockSpecial”, “characterSpecial”, “unknown” self.fail _("Could not remove files of type %{current_type}") % { current_type: current_type } end end def retrieve # This check is done in retrieve to ensure it happens before we try to use # metadata in `copy_source_values`, but so it only fails the resource and not # catalog validation (because that would be a breaking change from Puppet 4). if Puppet.features.microsoft_windows? && parameter(:source) && [:use, :use_when_creating].include?(self[:source_permissions]) #TRANSLATORS "source_permissions => ignore" should not be translated err_msg = _("Copying owner/mode/group from the source file on Windows is not supported; use source_permissions => ignore.") if self[:owner] == nil || self[:group] == nil || self[:mode] == nil # Fail on Windows if source permissions are being used and the file resource # does not have mode owner, group, and mode all set (which would take precedence). self.fail err_msg else # Warn if use source permissions is specified on Windows self.warning err_msg end end # `checksum_value` implies explicit management of all metadata, so skip metadata # retrieval. Otherwise, if source is set, retrieve metadata for source. if (source = parameter(:source)) && property(:checksum_value).nil? source.copy_source_values end super end # Set the checksum, from another property. There are multiple # properties that modify the contents of a file, and they need the # ability to make sure that the checksum value is in sync. def setchecksum(sum = nil) if @parameters.include? :checksum if sum @parameters[:checksum].checksum = sum else # If they didn't pass in a sum, then tell checksum to # figure it out. currentvalue = @parameters[:checksum].retrieve @parameters[:checksum].checksum = currentvalue end end end # Should this thing be a normal file? This is a relatively complex # way of determining whether we're trying to create a normal file, # and it's here so that the logic isn't visible in the content property. def should_be_file? return true if self[:ensure] == :file # I.e., it's set to something like "directory" return false if e = self[:ensure] and e != :present # The user doesn't really care, apparently if self[:ensure] == :present return true unless s = stat return(s.ftype == "file" ? true : false) end # If we've gotten here, then :ensure isn't set return true if self[:content] return true if stat and stat.ftype == "file" false end # Stat our file. Depending on the value of the 'links' attribute, we # use either 'stat' or 'lstat', and we expect the properties to use the # resulting stat object accordingly (mostly by testing the 'ftype' # value). # # We use the initial value :needs_stat to ensure we only stat the file once, # but can also keep track of a failed stat (@stat == nil). This also allows # us to re-stat on demand by setting @stat = :needs_stat. def stat return @stat unless @stat == :needs_stat method = :stat # Files are the only types that support links if (self.class.name == :file and self[:links] != :follow) or self.class.name == :tidy method = :lstat end @stat = begin Puppet::FileSystem.send(method, self[:path]) rescue Errno::ENOENT nil rescue Errno::ENOTDIR nil rescue Errno::EACCES warning _("Could not stat; permission denied") nil end end def to_resource resource = super resource.delete(:target) if resource[:target] == :notlink resource end # Write out the file. To write content, pass the property as an argument # to delegate writing to; must implement a #write method that takes the file # as an argument. def write(property = nil) remove_existing(:file) mode = self.should(:mode) # might be nil mode_int = mode ? symbolic_mode_to_int(mode, Puppet::Util::DEFAULT_POSIX_MODE) : nil if write_temporary_file? Puppet::Util.replace_file(self[:path], mode_int) do |file| file.binmode devfail 'a property should have been provided if write_temporary_file? returned true' if property.nil? content_checksum = property.write(file) file.flush begin file.fsync rescue NotImplementedError # fsync may not be implemented by Ruby on all platforms, but # there is absolutely no recovery path if we detect that. So, we just # ignore the return code. # # However, don't be fooled: that is accepting that we are running in # an unsafe fashion. If you are porting to a new platform don't stub # that out. end fail_if_checksum_is_wrong(file.path, content_checksum) if validate_checksum? if self[:validate_cmd] output = Puppet::Util::Execution.execute(self[:validate_cmd].gsub(self[:validate_replacement], file.path), :failonfail => true, :combine => true) output.split(/\n/).each { |line| self.debug(line) } end end else umask = mode ? 000 : 022 Puppet::Util.withumask(umask) { ::File.open(self[:path], 'wb', mode_int ) { |f| property.write(f) if property } } end # make sure all of the modes are actually correct property_fix end private # Carry the context of sensitive parameters to the the properties that will actually handle that # sensitive data. # # The file type can accept file content from a number of origins and depending on the current # state of the system different properties will be responsible for synchronizing the file # content. This method handles the necessary mapping of originating parameters to the # responsible parameters. def set_sensitive_parameters(sensitive_parameters) # If we have content that's marked as sensitive but the file doesn't exist then the ensure # property will be responsible for syncing content, so we have to mark ensure as sensitive as well. if sensitive_parameters.include?(:content) # The `ensure` parameter is not guaranteed to be defined either and will be conditionally set when # the `content` property is set, so we need to force the creation of the `ensure` property to # set the sensitive context. newattr(:ensure).sensitive = true end # The source parameter isn't actually a property but works by injecting information into the # content property. In order to preserve the intended sensitive context we need to mark content # as sensitive as well. if sensitive_parameters.include?(:source) sensitive_parameters.delete(:source) parameter(:source).sensitive = true # The `source` parameter will generate the `content` property when the resource state is retrieved # but that's long after we've set the sensitive context. Force the early creation of the `content` # attribute so we can mark it as sensitive. newattr(:content).sensitive = true # As noted above, making the `content` property sensitive requires making the `ensure` property # sensitive as well. newattr(:ensure).sensitive = true end super(sensitive_parameters) end # @return [String] The type of the current file, cast to a string. def read_current_type stat_info = stat if stat_info stat_info.ftype.to_s else nil end end # @return [Boolean] If the current file should be backed up and can be backed up. def can_backup?(type) if type == "directory" and force? # (#18110) Directories cannot be removed without :force, # so it doesn't make sense to back them up unless removing with :force. true elsif type == "file" or type == "link" true else # Including: “blockSpecial”, “characterSpecial”, "fifo", "socket", “unknown” false end end # @return [Boolean] if the directory was removed (which is always true currently) # @api private def remove_directory(wanted_type) if force? debug "Removing existing directory for replacement with #{wanted_type}" FileUtils.rmtree(self[:path]) stat_needed true else notice _("Not removing directory; use 'force' to override") false end end # @return [Boolean] if the file was removed (which is always true currently) # @api private def remove_file(current_type, wanted_type) debug "Removing existing #{current_type} for replacement with #{wanted_type}" Puppet::FileSystem.unlink(self[:path]) stat_needed true end def stat_needed @stat = :needs_stat end # Back up the existing file at a given prior to it being removed # @api private # @raise [Puppet::Error] if the file backup failed # @return [void] def backup_existing unless perform_backup #TRANSLATORS refers to a file which could not be backed up raise Puppet::Error, _("Could not back up; will not remove") end end # Should we validate the checksum of the file we're writing? def validate_checksum? self[:checksum] !~ /time/ end # Make sure the file we wrote out is what we think it is. def fail_if_checksum_is_wrong(path, content_checksum) newsum = parameter(:checksum).sum_file(path) return if [:absent, nil, content_checksum].include?(newsum) self.fail _("File written to disk did not match checksum; discarding changes (%{content_checksum} vs %{newsum})") % { content_checksum: content_checksum, newsum: newsum } end def write_temporary_file? # Unfortunately we don't know the source file size before fetching it so # let's assume the file won't be empty. Why isn't it part of the metadata? (c = property(:content) and c.length) || @parameters[:source] end # There are some cases where all of the work does not get done on # file creation/modification, so we have to do some extra checking. def property_fix properties.each do |thing| next unless [:mode, :owner, :group, :seluser, :selrole, :seltype, :selrange].include?(thing.name) # Make sure we get a new stat object @stat = :needs_stat currentvalue = thing.retrieve thing.sync unless thing.safe_insync?(currentvalue) end end end # We put all of the properties in separate files, because there are so many # of them. The order these are loaded is important, because it determines # the order they are in the property lit. require 'puppet/type/file/checksum' require 'puppet/type/file/content' # can create the file require 'puppet/type/file/source' # can create the file require 'puppet/type/file/checksum_value' # can create the file, in place of content require 'puppet/type/file/target' # creates a different type of file require 'puppet/type/file/ensure' # can create the file require 'puppet/type/file/owner' require 'puppet/type/file/group' require 'puppet/type/file/mode' require 'puppet/type/file/type' require 'puppet/type/file/selcontext' # SELinux file context require 'puppet/type/file/ctime' require 'puppet/type/file/mtime' ��������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/host.rb���������������������������������������������������������������0000644�0052762�0001160�00000007557�13417161722�017377� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/property/ordered_list' module Puppet Type.newtype(:host) do ensurable newproperty(:ip) do desc "The host's IP address, IPv4 or IPv6." def valid_v4?(addr) if /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/ =~ addr return $~.captures.all? {|i| i = i.to_i; i >= 0 and i <= 255 } end return false end def valid_v6?(addr) # http://forums.dartware.com/viewtopic.php?t=452 # ...and, yes, it is this hard. Doing it programmatically is harder. return true if addr =~ /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/ return false end def valid_newline?(addr) return false if (addr =~ /\n/ || addr =~ /\r/) return true end validate do |value| return true if ((valid_v4?(value) || valid_v6?(value)) && (valid_newline?(value))) raise Puppet::Error, _("Invalid IP address %{value}") % { value: value.inspect } end end # for now we use OrderedList to indicate that the order does matter. newproperty(:host_aliases, :parent => Puppet::Property::OrderedList) do desc "Any aliases the host might have. Multiple values must be specified as an array." def delimiter " " end def inclusive? true end validate do |value| # This regex already includes newline check. raise Puppet::Error, _("Host aliases cannot include whitespace") if value =~ /\s/ raise Puppet::Error, _("Host aliases cannot be an empty string. Use an empty array to delete all host_aliases ") if value =~ /^\s*$/ end end newproperty(:comment) do desc "A comment that will be attached to the line with a # character." validate do |value| raise Puppet::Error, _("Comment cannot include newline") if (value =~ /\n/ || value =~ /\r/) end end newproperty(:target) do desc "The file in which to store service information. Only used by those providers that write to disk. On most systems this defaults to `/etc/hosts`." defaultto { if @resource.class.defaultprovider.ancestors.include?(Puppet::Provider::ParsedFile) @resource.class.defaultprovider.default_target else nil end } end newparam(:name) do desc "The host name." isnamevar validate do |value| value.split('.').each do |hostpart| unless hostpart =~ /^([\w]+|[\w][\w\-]+[\w])$/ raise Puppet::Error, _("Invalid host name") end end raise Puppet::Error, _("Hostname cannot include newline") if (value =~ /\n/ || value =~ /\r/) end end @doc = "Installs and manages host entries. For most systems, these entries will just be in `/etc/hosts`, but some systems (notably OS X) will have different solutions." end end �������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/interface.rb����������������������������������������������������������0000644�0052762�0001160�00000006357�13417161722�020357� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# # Manages an interface on a given router or switch # require 'puppet/util/network_device/ipcalc' Puppet::Type.newtype(:interface) do @doc = "This represents a router or switch interface. It is possible to manage interface mode (access or trunking, native vlan and encapsulation) and switchport characteristics (speed, duplex)." apply_to_device ensurable do defaultvalues aliasvalue :shutdown, :absent aliasvalue :no_shutdown, :present defaultto { :no_shutdown } end newparam(:name) do desc "The interface's name." end newparam(:device_url) do desc "The URL at which the router or switch can be reached." end newproperty(:description) do desc "Interface description." defaultto { @resource[:name] } end newproperty(:speed) do desc "Interface speed." newvalues(:auto, /^\d+/) end newproperty(:duplex) do desc "Interface duplex." newvalues(:auto, :full, :half) end newproperty(:access_vlan) do desc "Interface static access vlan." newvalues(/^\d+/) end newproperty(:native_vlan) do desc "Interface native vlan when trunking." newvalues(/^\d+/) end newproperty(:encapsulation) do desc "Interface switchport encapsulation." newvalues(:none, :dot1q, :isl, :negotiate) end newproperty(:mode) do desc "Interface switchport mode." newvalues(:access, :trunk, 'dynamic auto', 'dynamic desirable') end newproperty(:allowed_trunk_vlans) do desc "Allowed list of Vlans that this trunk can forward." newvalues(:all, /./) end newproperty(:etherchannel) do desc "Channel group this interface is part of." newvalues(/^\d+/) end newproperty(:ipaddress, :array_matching => :all) do include Puppet::Util::NetworkDevice::IPCalc desc "IP Address of this interface. Note that it might not be possible to set an interface IP address; it depends on the interface type and device type. Valid format of ip addresses are: * IPV4, like 127.0.0.1 * IPV4/prefixlength like 127.0.1.1/24 * IPV6/prefixlength like FE80::21A:2FFF:FE30:ECF0/128 * an optional suffix for IPV6 addresses from this list: `eui-64`, `link-local` It is also possible to supply an array of values. " validate do |values| values = [values] unless values.is_a?(Array) values.each do |value| self.fail _("Invalid interface ip address") unless parse(value.gsub(/\s*(eui-64|link-local)\s*$/,'')) end end munge do |value| option = value =~ /eui-64|link-local/i ? value.gsub(/^.*?\s*(eui-64|link-local)\s*$/,'\1') : nil [parse(value.gsub(/\s*(eui-64|link-local)\s*$/,'')), option].flatten end def value_to_s(value) value = [value] unless value.is_a?(Array) value.map{ |v| "#{v[1].to_s}/#{v[0]} #{v[2]}"}.join(",") end def change_to_s(currentvalue, newvalue) currentvalue = value_to_s(currentvalue) if currentvalue != :absent newvalue = value_to_s(newvalue) super(currentvalue, newvalue) end end def present?(current_values) super && current_values[:ensure] != :shutdown end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/k5login.rb������������������������������������������������������������0000644�0052762�0001160�00000011630�13417161722�017755� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Plug-in type for handling k5login files require 'puppet/util' require 'puppet/util/selinux' require 'puppet/type/file/selcontext' Puppet::Type.newtype(:k5login) do @doc = "Manage the `.k5login` file for a user. Specify the full path to the `.k5login` file as the name, and an array of principals as the `principals` attribute." ensurable # Principals that should exist in the file newproperty(:principals, :array_matching => :all) do desc "The principals present in the `.k5login` file. This should be specified as an array." end # The path/name of the k5login file newparam(:path) do isnamevar desc "The path to the `.k5login` file to manage. Must be fully qualified." validate do |value| unless absolute_path?(value) raise Puppet::Error, _("File paths must be fully qualified.") end end end # To manage the mode of the file newproperty(:mode) do desc "The desired permissions mode of the `.k5login` file." defaultto { "644" } end # To manage the selinux user of the file newproperty(:seluser, :parent => Puppet::SELFileContext) do desc "What the SELinux user component of the context of the file should be. Any valid SELinux user component is accepted. For example `user_u`. If not specified it defaults to the value returned by matchpathcon for the file, if any exists. Only valid on systems with SELinux support enabled." defaultto { "user_u" } end # To manage the selinux role of the file newproperty(:selrole, :parent => Puppet::SELFileContext) do desc "What the SELinux role component of the context of the file should be. Any valid SELinux role component is accepted. For example `role_r`. If not specified it defaults to the value returned by matchpathcon for the file, if any exists. Only valid on systems with SELinux support enabled." defaultto { "object_r" } end # To manage the selinux type of the file newproperty(:seltype, :parent => Puppet::SELFileContext) do desc "What the SELinux type component of the context of the file should be. Any valid SELinux type component is accepted. For example `tmp_t`. If not specified it defaults to the value returned by matchpathcon for the file, if any exists. Only valid on systems with SELinux support enabled." # to my knowledge, `krb5_home_t` is the only valid type for .k5login defaultto { "krb5_home_t" } end # To manage the selinux range of the file newproperty(:selrange, :parent => Puppet::SELFileContext) do desc "What the SELinux range component of the context of the file should be. Any valid SELinux range component is accepted. For example `s0` or `SystemHigh`. If not specified it defaults to the value returned by matchpathcon for the file, if any exists. Only valid on systems with SELinux support enabled and that have support for MCS (Multi-Category Security)." defaultto { "s0" } end # Stat our file. # # We use the initial value :needs_stat to ensure we only stat the file once, # but can also keep track of a failed stat (@stat == nil). This also allows # us to re-stat on demand by setting @stat = :needs_stat. def stat return @stat unless @stat == :needs_stat @stat = begin Puppet::FileSystem.stat(self[:path]) rescue Errno::ENOENT nil rescue Errno::ENOTDIR nil rescue Errno::EACCES warning _("Could not stat; permission denied") nil end end def initialize(args) @stat = :needs_stat super end # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # provide(:k5login) do desc "The k5login provider is the only provider for the k5login type." include Puppet::Util::SELinux # Does this file exist? def exists? Puppet::FileSystem.exist?(@resource[:name]) end # create the file def create write(@resource.should(:principals)) should_mode = @resource.should(:mode) unless self.mode == should_mode self.mode = should_mode end end # remove the file def destroy Puppet::FileSystem.unlink(@resource[:name]) end # Return the principals def principals if Puppet::FileSystem.exist?(@resource[:name]) File.readlines(@resource[:name]).collect { |line| line.chomp } else :absent end end # Write the principals out to the k5login file def principals=(value) write(value) end # Return the mode as an octal string, not as an integer def mode "%o" % (Puppet::FileSystem.stat(@resource[:name]).mode & 007777) end # Set the file mode, converting from a string to an integer. def mode=(value) File.chmod(Integer("0#{value}"), @resource[:name]) end private def write(value) Puppet::Util.replace_file(@resource[:name], 0644) do |f| f.puts value end end end end ��������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/macauthorization.rb���������������������������������������������������0000644�0052762�0001160�00000011454�13417161722�021772� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.newtype(:macauthorization) do @doc = "Manage the Mac OS X authorization database. See the [Apple developer site](https://developer.apple.com/library/content/documentation/Security/Conceptual/Security_Overview/AuthenticationAndAuthorization/AuthenticationAndAuthorization.html) for more information. Note that authorization store directives with hyphens in their names have been renamed to use underscores, as Puppet does not react well to hyphens in identifiers. **Autorequires:** If Puppet is managing the `/etc/authorization` file, each macauthorization resource will autorequire it." ensurable autorequire(:file) do ["/etc/authorization"] end def munge_boolean(value) case value when true, "true", :true :true when false, "false", :false :false else fail("munge_boolean only takes booleans") end end def munge_integer(value) Integer(value) rescue ArgumentError fail _("munge_integer only takes integers") end newparam(:name) do desc "The name of the right or rule to be managed. Corresponds to `key` in Authorization Services. The key is the name of a rule. A key uses the same naming conventions as a right. The Security Server uses a rule's key to match the rule with a right. Wildcard keys end with a '.'. The generic rule has an empty key value. Any rights that do not match a specific rule use the generic rule." isnamevar end newproperty(:auth_type) do desc "Type --- this can be a `right` or a `rule`. The `comment` type has not yet been implemented." newvalue(:right) newvalue(:rule) # newvalue(:comment) # not yet implemented. end newproperty(:allow_root, :boolean => true) do desc "Corresponds to `allow-root` in the authorization store. Specifies whether a right should be allowed automatically if the requesting process is running with `uid == 0`. AuthorizationServices defaults this attribute to false if not specified." newvalue(:true) newvalue(:false) munge do |value| @resource.munge_boolean(value) end end newproperty(:authenticate_user, :boolean => true) do desc "Corresponds to `authenticate-user` in the authorization store." newvalue(:true) newvalue(:false) munge do |value| @resource.munge_boolean(value) end end newproperty(:auth_class) do desc "Corresponds to `class` in the authorization store; renamed due to 'class' being a reserved word in Puppet." newvalue(:user) newvalue(:'evaluate-mechanisms') newvalue(:allow) newvalue(:deny) newvalue(:rule) end newproperty(:comment) do desc "The `comment` attribute for authorization resources." end newproperty(:group) do desc "A group which the user must authenticate as a member of. This must be a single group." end newproperty(:k_of_n) do desc "How large a subset of rule mechanisms must succeed for successful authentication. If there are 'n' mechanisms, then 'k' (the integer value of this parameter) mechanisms must succeed. The most common setting for this parameter is `1`. If `k-of-n` is not set, then every mechanism --- that is, 'n-of-n' --- must succeed." munge do |value| @resource.munge_integer(value) end end newproperty(:mechanisms, :array_matching => :all) do desc "An array of suitable mechanisms." end newproperty(:rule, :array_matching => :all) do desc "The rule(s) that this right refers to." end newproperty(:session_owner, :boolean => true) do desc "Whether the session owner automatically matches this rule or right. Corresponds to `session-owner` in the authorization store." newvalue(:true) newvalue(:false) munge do |value| @resource.munge_boolean(value) end end newproperty(:shared, :boolean => true) do desc "Whether the Security Server should mark the credentials used to gain this right as shared. The Security Server may use any shared credentials to authorize this right. For maximum security, set sharing to false so credentials stored by the Security Server for one application may not be used by another application." newvalue(:true) newvalue(:false) munge do |value| @resource.munge_boolean(value) end end newproperty(:timeout) do desc "The number of seconds in which the credential used by this rule will expire. For maximum security where the user must authenticate every time, set the timeout to 0. For minimum security, remove the timeout attribute so the user authenticates only once per session." munge do |value| @resource.munge_integer(value) end end newproperty(:tries) do desc "The number of tries allowed." munge do |value| @resource.munge_integer(value) end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/mailalias.rb����������������������������������������������������������0000644�0052762�0001160�00000002376�13417161722�020350� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet Type.newtype(:mailalias) do @doc = "Creates an email alias in the local alias database." ensurable newparam(:name, :namevar => true) do desc "The alias name." end newproperty(:recipient, :array_matching => :all) do desc "Where email should be sent. Multiple values should be specified as an array. The file and the recipient entries are mutually exclusive." end newproperty(:file) do desc "A file containing the alias's contents. The file and the recipient entries are mutually exclusive." validate do |value| unless Puppet::Util.absolute_path?(value) fail Puppet::Error, _("File paths must be fully qualified, not '%{value}'") % { value: value } end end end newproperty(:target) do desc "The file in which to store the aliases. Only used by those providers that write to disk." defaultto { if @resource.class.defaultprovider.ancestors.include?(Puppet::Provider::ParsedFile) @resource.class.defaultprovider.default_target else nil end } end validate do if self[:recipient] && self[:file] self.fail _("You cannot specify both a recipient and a file") end end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/maillist.rb�����������������������������������������������������������0000644�0052762�0001160�00000003041�13417161722�020220� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet Type.newtype(:maillist) do @doc = "Manage email lists. This resource type can only create and remove lists; it cannot currently reconfigure them." ensurable do defaultvalues newvalue(:purged) do provider.purge end def change_to_s(current_value, newvalue) return _("Purged %{resource}") % { resource: resource } if newvalue == :purged super end def insync?(is) return true if is == :absent && should == :purged super end end newparam(:name, :namevar => true) do desc "The name of the email list." end newparam(:description) do desc "The description of the mailing list." end newparam(:password) do desc "The admin password." end newparam(:webserver) do desc "The name of the host providing web archives and the administrative interface." end newparam(:mailserver) do desc "The name of the host handling email for the list." end newparam(:admin) do desc "The email address of the administrator." end def generate if provider.respond_to?(:aliases) should = self.should(:ensure) || :present if should == :purged should = :absent end atype = Puppet::Type.type(:mailalias) provider.aliases. reject { |name,recipient| catalog.resource(:mailalias, name) }. collect { |name,recipient| atype.new(:name => name, :recipient => recipient, :ensure => should) } end end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/mcx.rb����������������������������������������������������������������0000644�0052762�0001160�00000005217�13417161722�017200� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Type.newtype(:mcx) do @doc = "MCX object management using DirectoryService on OS X. The default provider of this type merely manages the XML plist as reported by the `dscl -mcxexport` command. This is similar to the content property of the file type in Puppet. The recommended method of using this type is to use Work Group Manager to manage users and groups on the local computer, record the resulting puppet manifest using the command `puppet resource mcx`, then deploy it to other machines. **Autorequires:** If Puppet is managing the user, group, or computer that these MCX settings refer to, the MCX resource will autorequire that user, group, or computer. " feature :manages_content, \ "The provider can manage MCXSettings as a string.", :methods => [:content, :content=] ensurable do desc "Create or remove the MCX setting." newvalue(:present) do provider.create end newvalue(:absent) do provider.destroy end end newparam(:name) do desc "The name of the resource being managed. The default naming convention follows Directory Service paths: /Computers/localhost /Groups/admin /Users/localadmin The `ds_type` and `ds_name` type parameters are not necessary if the default naming convention is followed." isnamevar end newparam(:ds_type) do desc "The DirectoryService type this MCX setting attaches to." newvalues(:user, :group, :computer, :computerlist) end newparam(:ds_name) do desc "The name to attach the MCX Setting to. (For example, `localhost` when `ds_type => computer`.) This setting is not required, as it can be automatically discovered when the resource name is parseable. (For example, in `/Groups/admin`, `group` will be used as the dstype.)" end newproperty(:content, :required_features => :manages_content) do desc "The XML Plist used as the value of MCXSettings in DirectoryService. This is the standard output from the system command: dscl localhost -mcxexport /Local/Default/<ds_type>/ds_name Note that `ds_type` is capitalized and plural in the dscl command." end # JJM Yes, this is not DRY at all. Because of the code blocks # autorequire must be done this way. I think. def setup_autorequire(type) # value returns a Symbol ds_type = value(:ds_type) ds_name = value(:ds_name) if ds_type == type rval = [ ds_name.to_s ] else rval = [ ] end rval end autorequire(:user) do setup_autorequire(:user) end autorequire(:group) do setup_autorequire(:group) end autorequire(:computer) do setup_autorequire(:computer) end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/mount.rb��������������������������������������������������������������0000644�0052762�0001160�00000024671�13417161722�017560� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/property/boolean' module Puppet # We want the mount to refresh when it changes. Type.newtype(:mount, :self_refresh => true) do @doc = "Manages mounted filesystems, including putting mount information into the mount table. The actual behavior depends on the value of the 'ensure' parameter. **Refresh:** `mount` resources can respond to refresh events (via `notify`, `subscribe`, or the `~>` arrow). If a `mount` receives an event from another resource **and** its `ensure` attribute is set to `mounted`, Puppet will try to unmount then remount that filesystem. **Autorequires:** If Puppet is managing any parents of a mount resource --- that is, other mount points higher up in the filesystem --- the child mount will autorequire them. **Autobefores:** If Puppet is managing any child file paths of a mount point, the mount resource will autobefore them." feature :refreshable, "The provider can remount the filesystem.", :methods => [:remount] # Use the normal parent class, because we actually want to # call code when sync is called. newproperty(:ensure) do desc "Control what to do with this mount. Set this attribute to `unmounted` to make sure the filesystem is in the filesystem table but not mounted (if the filesystem is currently mounted, it will be unmounted). Set it to `absent` to unmount (if necessary) and remove the filesystem from the fstab. Set to `mounted` to add it to the fstab and mount it. Set to `present` to add to fstab but not change mount/unmount status." # IS -> SHOULD In Sync Action # ghost -> present NO create # absent -> present NO create # (mounted -> present YES) # (unmounted -> present YES) newvalue(:defined) do provider.create return :mount_created end aliasvalue :present, :defined # IS -> SHOULD In Sync Action # ghost -> unmounted NO create, unmount # absent -> unmounted NO create # mounted -> unmounted NO unmount newvalue(:unmounted) do case self.retrieve when :ghost # (not in fstab but mounted) provider.create @resource.flush provider.unmount return :mount_unmounted when nil, :absent # (not in fstab and not mounted) provider.create return :mount_created when :mounted # (in fstab and mounted) provider.unmount syncothers # I guess it's more likely that the mount was originally mounted with # the wrong attributes so I sync AFTER the umount return :mount_unmounted else raise Puppet::Error, _("Unexpected change from %{current} to unmounted") % { current: current_value } end end # IS -> SHOULD In Sync Action # ghost -> absent NO unmount # mounted -> absent NO provider.destroy AND unmount # unmounted -> absent NO provider.destroy newvalue(:absent, :event => :mount_deleted) do current_value = self.retrieve provider.unmount if provider.mounted? provider.destroy unless current_value == :ghost end # IS -> SHOULD In Sync Action # ghost -> mounted NO provider.create # absent -> mounted NO provider.create AND mount # unmounted -> mounted NO mount newvalue(:mounted, :event => :mount_mounted) do # Create the mount point if it does not already exist. current_value = self.retrieve currently_mounted = provider.mounted? provider.create if [nil, :absent, :ghost].include?(current_value) syncothers # The fs can be already mounted if it was absent but mounted provider.property_hash[:needs_mount] = true unless currently_mounted end # insync: mounted -> present # unmounted -> present def insync?(is) if should == :defined and [:mounted,:unmounted].include?(is) true else super end end def syncothers # We have to flush any changes to disk. currentvalues = @resource.retrieve_resource # Determine if there are any out-of-sync properties. oos = @resource.send(:properties).find_all do |prop| unless currentvalues.include?(prop) raise Puppet::DevError, _("Parent has property %{name} but it doesn't appear in the current values") % { name: prop.name } end if prop.name == :ensure false else ! prop.safe_insync?(currentvalues[prop]) end end.each { |prop| prop.sync }.length @resource.flush if oos > 0 end end newproperty(:device) do desc "The device providing the mount. This can be whatever device is supporting by the mount, including network devices or devices specified by UUID rather than device path, depending on the operating system." validate do |value| raise Puppet::Error, _("device must not contain whitespace: %{value}") % { value: value } if value =~ /\s/ end end # Solaris specifies two devices, not just one. newproperty(:blockdevice) do desc "The device to fsck. This is property is only valid on Solaris, and in most cases will default to the correct value." # Default to the device but with "dsk" replaced with "rdsk". defaultto do if Facter.value(:osfamily) == "Solaris" if device = resource[:device] and device =~ %r{/dsk/} device.sub(%r{/dsk/}, "/rdsk/") elsif fstype = resource[:fstype] and fstype == 'nfs' '-' else nil end else nil end end validate do |value| raise Puppet::Error, _("blockdevice must not contain whitespace: %{value}") % { value: value } if value =~ /\s/ end end newproperty(:fstype) do desc "The mount type. Valid values depend on the operating system. This is a required option." validate do |value| raise Puppet::Error, _("fstype must not contain whitespace: %{value}") % { value: value } if value =~ /\s/ raise Puppet::Error, _("fstype must not be an empty string") if value.empty? end end newproperty(:options) do desc "A single string containing options for the mount, as they would appear in fstab on Linux. For many platforms this is a comma-delimited string. Consult the fstab(5) man page for system-specific details. AIX options other than dev, nodename, or vfs can be defined here. If specified, AIX options of account, boot, check, free, mount, size, type, vol, log, and quota must be ordered alphabetically at the end of the list." validate do |value| raise Puppet::Error, _("options must not contain whitespace: %{value}") % { value: value } if value =~ /\s/ raise Puppet::Error, _("options must not be an empty string") if value.empty? end end newproperty(:pass) do desc "The pass in which the mount is checked." defaultto { if @resource.managed? if Facter.value(:osfamily) == 'Solaris' '-' else 0 end end } end newproperty(:atboot, :parent => Puppet::Property::Boolean) do desc "Whether to mount the mount at boot. Not all platforms support this." def munge(value) munged = super if munged :yes else :no end end end newproperty(:dump) do desc "Whether to dump the mount. Not all platform support this. Valid values are `1` or `0` (or `2` on FreeBSD). Default is `0`." if Facter.value(:operatingsystem) == "FreeBSD" newvalue(%r{(0|1|2)}) else newvalue(%r{(0|1)}) end defaultto { 0 if @resource.managed? } end newproperty(:target) do desc "The file in which to store the mount table. Only used by those providers that write to disk." defaultto { if @resource.class.defaultprovider.ancestors.include?(Puppet::Provider::ParsedFile) @resource.class.defaultprovider.default_target else nil end } end newparam(:name) do desc "The mount path for the mount." isnamevar validate do |value| raise Puppet::Error, _("name must not contain whitespace: %{value}") % { value: value } if value =~ /\s/ end munge do |value| value.gsub(/^(.+?)\/*$/, '\1') end end newparam(:remounts) do desc "Whether the mount can be remounted `mount -o remount`. If this is false, then the filesystem will be unmounted and remounted manually, which is prone to failure." newvalues(:true, :false) defaultto do case Facter.value(:operatingsystem) when "FreeBSD", "Darwin", "DragonFly", "OpenBSD" false when "AIX" if Facter.value(:kernelmajversion) == "5300" false elsif resource[:device] and resource[:device].match(%r{^[^/]+:/}) false else true end else true end end end def refresh # Only remount if we're supposed to be mounted. provider.remount if self.should(:fstype) != "swap" and provider.mounted? end def value(name) name = name.intern if property = @parameters[name] return property.value end end # Ensure that mounts higher up in the filesystem are mounted first autorequire(:mount) do dependencies = [] Pathname.new(@parameters[:name].value).ascend do |parent| dependencies.unshift parent.to_s end dependencies[0..-2] end # Autobefore the mount point's child file paths autobefore(:file) do dependencies = [] file_resources = catalog.resources.select { |resource| resource.type == :file } children_file_resources = file_resources.select { |resource| File.expand_path(resource[:path]) =~ %r(^#{self[:name]}/.) } children_file_resources.each do |child| dependencies.push Pathname.new(child[:path]) end dependencies end end end �����������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/nagios_command.rb�����������������������������������������������������0000644�0052762�0001160�00000000132�13417161722�021356� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/nagios_maker' Puppet::Util::NagiosMaker.create_nagios_type :command ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/nagios_contact.rb�����������������������������������������������������0000644�0052762�0001160�00000000132�13417161722�021373� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/nagios_maker' Puppet::Util::NagiosMaker.create_nagios_type :contact ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/nagios_contactgroup.rb������������������������������������������������0000644�0052762�0001160�00000000137�13417161722�022455� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/nagios_maker' Puppet::Util::NagiosMaker.create_nagios_type :contactgroup ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/nagios_host.rb��������������������������������������������������������0000644�0052762�0001160�00000000127�13417161722�020721� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/nagios_maker' Puppet::Util::NagiosMaker.create_nagios_type :host �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/nagios_hostdependency.rb����������������������������������������������0000644�0052762�0001160�00000000141�13417161722�022754� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/nagios_maker' Puppet::Util::NagiosMaker.create_nagios_type :hostdependency �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/nagios_hostescalation.rb����������������������������������������������0000644�0052762�0001160�00000000141�13417161722�022760� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/nagios_maker' Puppet::Util::NagiosMaker.create_nagios_type :hostescalation �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/nagios_hostextinfo.rb�������������������������������������������������0000644�0052762�0001160�00000000136�13417161722�022316� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/nagios_maker' Puppet::Util::NagiosMaker.create_nagios_type :hostextinfo ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/nagios_hostgroup.rb���������������������������������������������������0000644�0052762�0001160�00000000134�13417161722�021774� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/nagios_maker' Puppet::Util::NagiosMaker.create_nagios_type :hostgroup ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/nagios_service.rb�����������������������������������������������������0000644�0052762�0001160�00000000132�13417161722�021400� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/nagios_maker' Puppet::Util::NagiosMaker.create_nagios_type :service ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/nagios_servicedependency.rb�������������������������������������������0000644�0052762�0001160�00000000144�13417161722�023442� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/nagios_maker' Puppet::Util::NagiosMaker.create_nagios_type :servicedependency ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/nagios_serviceescalation.rb�������������������������������������������0000644�0052762�0001160�00000000144�13417161722�023446� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/nagios_maker' Puppet::Util::NagiosMaker.create_nagios_type :serviceescalation ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/nagios_serviceextinfo.rb����������������������������������������������0000644�0052762�0001160�00000000141�13417161722�022775� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/nagios_maker' Puppet::Util::NagiosMaker.create_nagios_type :serviceextinfo �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/nagios_servicegroup.rb������������������������������������������������0000644�0052762�0001160�00000000137�13417161722�022462� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/nagios_maker' Puppet::Util::NagiosMaker.create_nagios_type :servicegroup ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/nagios_timeperiod.rb��������������������������������������������������0000644�0052762�0001160�00000000135�13417161722�022104� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/nagios_maker' Puppet::Util::NagiosMaker.create_nagios_type :timeperiod �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/package.rb������������������������������������������������������������0000644�0052762�0001160�00000051027�13417161722�020004� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Define the different packaging systems. Each package system is implemented # in a module, which then gets used to individually extend each package object. # This allows packages to exist on the same machine using different packaging # systems. require 'puppet/parameter/package_options' require 'puppet/parameter/boolean' module Puppet Type.newtype(:package) do @doc = "Manage packages. There is a basic dichotomy in package support right now: Some package types (such as yum and apt) can retrieve their own package files, while others (such as rpm and sun) cannot. For those package formats that cannot retrieve their own files, you can use the `source` parameter to point to the correct file. Puppet will automatically guess the packaging format that you are using based on the platform you are on, but you can override it using the `provider` parameter; each provider defines what it requires in order to function, and you must meet those requirements to use a given provider. You can declare multiple package resources with the same `name`, as long as they specify different providers and have unique titles. Note that you must use the _title_ to make a reference to a package resource; `Package[<NAME>]` is not a synonym for `Package[<TITLE>]` like it is for many other resource types. **Autorequires:** If Puppet is managing the files specified as a package's `adminfile`, `responsefile`, or `source`, the package resource will autorequire those files." feature :reinstallable, "The provider can reinstall packages.", :methods => [:reinstall] feature :installable, "The provider can install packages.", :methods => [:install] feature :uninstallable, "The provider can uninstall packages.", :methods => [:uninstall] feature :upgradeable, "The provider can upgrade to the latest version of a package. This feature is used by specifying `latest` as the desired value for the package.", :methods => [:update, :latest] feature :purgeable, "The provider can purge packages. This generally means that all traces of the package are removed, including existing configuration files. This feature is thus destructive and should be used with the utmost care.", :methods => [:purge] feature :versionable, "The provider is capable of interrogating the package database for installed version(s), and can select which out of a set of available versions of a package to install if asked." feature :holdable, "The provider is capable of placing packages on hold such that they are not automatically upgraded as a result of other package dependencies unless explicit action is taken by a user or another package. Held is considered a superset of installed.", :methods => [:hold] feature :install_options, "The provider accepts options to be passed to the installer command." feature :uninstall_options, "The provider accepts options to be passed to the uninstaller command." feature :package_settings, "The provider accepts package_settings to be ensured for the given package. The meaning and format of these settings is provider-specific.", :methods => [:package_settings_insync?, :package_settings, :package_settings=] feature :virtual_packages, "The provider accepts virtual package names for install and uninstall." ensurable do desc <<-EOT What state the package should be in. On packaging systems that can retrieve new packages on their own, you can choose which package to retrieve by specifying a version number or `latest` as the ensure value. On packaging systems that manage configuration files separately from "normal" system files, you can uninstall config files by specifying `purged` as the ensure value. This defaults to `installed`. Version numbers must match the full version to install, including release if the provider uses a release moniker. Ranges or semver patterns are not accepted except for the `gem` package provider. For example, to install the bash package from the rpm `bash-4.1.2-29.el6.x86_64.rpm`, use the string `'4.1.2-29.el6'`. EOT attr_accessor :latest newvalue(:present, :event => :package_installed) do provider.install end newvalue(:absent, :event => :package_removed) do provider.uninstall end newvalue(:purged, :event => :package_purged, :required_features => :purgeable) do provider.purge end newvalue(:held, :event => :package_held, :required_features => :holdable) do provider.hold end # Alias the 'present' value. aliasvalue(:installed, :present) newvalue(:latest, :required_features => :upgradeable) do # Because yum always exits with a 0 exit code, there's a retrieve # in the "install" method. So, check the current state now, # to compare against later. current = self.retrieve begin provider.update rescue => detail self.fail Puppet::Error, _("Could not update: %{detail}") % { detail: detail }, detail end if current == :absent :package_installed else :package_changed end end newvalue(/./, :required_features => :versionable) do begin provider.install rescue => detail self.fail Puppet::Error, _("Could not update: %{detail}") % { detail: detail }, detail end if self.retrieve == :absent :package_installed else :package_changed end end defaultto :installed # Override the parent method, because we've got all kinds of # funky definitions of 'in sync'. def insync?(is) @lateststamp ||= (Time.now.to_i - 1000) # Iterate across all of the should values, and see how they # turn out. @should.each { |should| case should when :present return true unless [:absent, :purged, :held].include?(is) when :latest # Short-circuit packages that are not present return false if is == :absent || is == :purged # Don't run 'latest' more than about every 5 minutes if @latest and ((Time.now.to_i - @lateststamp) / 60) < 5 #self.debug "Skipping latest check" else begin @latest = provider.latest @lateststamp = Time.now.to_i rescue => detail error = Puppet::Error.new(_("Could not get latest version: %{detail}") % { detail: detail }) error.set_backtrace(detail.backtrace) raise error end end case when is.is_a?(Array) && is.include?(@latest) return true when is == @latest return true when is == :present # This will only happen on packaging systems # that can't query versions. return true else self.debug "#{@resource.name} #{is.inspect} is installed, latest is #{@latest.inspect}" end when :absent return true if is == :absent || is == :purged when :purged return true if is == :purged # this handles version number matches and # supports providers that can have multiple versions installed when *Array(is) return true else # We have version numbers, and no match. If the provider has # additional logic, run it here. return provider.insync?(is) if provider.respond_to?(:insync?) end } false end # This retrieves the current state. LAK: I think this method is unused. def retrieve provider.properties[:ensure] end # Provide a bit more information when logging upgrades. def should_to_s(newvalue = @should) if @latest super(@latest) else super(newvalue) end end def change_to_s(currentvalue, newvalue) # Handle transitioning from any previous state to 'purged' return 'purged' if newvalue == :purged # Check for transitions from nil/purged/absent to 'created' (any state that is not absent and not purged) return 'created' if (currentvalue.nil? || currentvalue == :absent || currentvalue == :purged) && (newvalue != :absent && newvalue != :purged) # The base should handle the normal property transitions super(currentvalue, newvalue) end end newparam(:name) do desc "The package name. This is the name that the packaging system uses internally, which is sometimes (especially on Solaris) a name that is basically useless to humans. If a package goes by several names, you can use a single title and then set the name conditionally: # In the 'openssl' class $ssl = $operatingsystem ? { solaris => SMCossl, default => openssl } package { 'openssl': ensure => installed, name => $ssl, } ... $ssh = $operatingsystem ? { solaris => SMCossh, default => openssh } package { 'openssh': ensure => installed, name => $ssh, require => Package['openssl'], } " isnamevar validate do |value| if !value.is_a?(String) raise ArgumentError, _("Name must be a String not %{klass}") % { klass: value.class } end end end # We call providify here so that we can set provider as a namevar. # Normally this method is called after newtype finishes constructing this # Type class. providify paramclass(:provider).isnamevar # We have more than one namevar, so we need title_patterns. However, we # cheat and set the patterns to map to name only and completely ignore # provider. So far, the logic that determines uniqueness appears to just # "Do The Right Thing™" when the provider is explicitly set by the user. # # The following resources will be seen as unique by puppet: # # # Uniqueness Key: ['mysql', nil] # package{'mysql': } # # # Uniqueness Key: ['mysql', 'gem'] # package{'gem-mysql': # name => 'mysql, # provider => gem # } # # This does not handle the case where providers like 'yum' and 'rpm' should # clash. Also, declarations that implicitly use the default provider will # clash with those that explicitly use the default. def self.title_patterns # This is the default title pattern for all types, except hard-wired to # set only name. [ [ /(.*)/m, [ [:name] ] ] ] end newproperty(:package_settings, :required_features=>:package_settings) do desc "Settings that can change the contents or configuration of a package. The formatting and effects of package_settings are provider-specific; any provider that implements them must explain how to use them in its documentation. (Our general expectation is that if a package is installed but its settings are out of sync, the provider should re-install that package with the desired settings.) An example of how package_settings could be used is FreeBSD's port build options --- a future version of the provider could accept a hash of options, and would reinstall the port if the installed version lacked the correct settings. package { 'www/apache22': package_settings => { 'SUEXEC' => false } } Again, check the documentation of your platform's package provider to see the actual usage." validate do |value| if provider.respond_to?(:package_settings_validate) provider.package_settings_validate(value) else super(value) end end munge do |value| if provider.respond_to?(:package_settings_munge) provider.package_settings_munge(value) else super(value) end end def insync?(is) provider.package_settings_insync?(should, is) end def should_to_s(newvalue) if provider.respond_to?(:package_settings_should_to_s) provider.package_settings_should_to_s(should, newvalue) else super(newvalue) end end def is_to_s(currentvalue) if provider.respond_to?(:package_settings_is_to_s) provider.package_settings_is_to_s(should, currentvalue) else super(currentvalue) end end def change_to_s(currentvalue, newvalue) if provider.respond_to?(:package_settings_change_to_s) provider.package_settings_change_to_s(currentvalue, newvalue) else super(currentvalue,newvalue) end end end newparam(:source) do desc "Where to find the package file. This is only used by providers that don't automatically download packages from a central repository. (For example: the `yum` and `apt` providers ignore this attribute, but the `rpm` and `dpkg` providers require it.) Different providers accept different values for `source`. Most providers accept paths to local files stored on the target system. Some providers may also accept URLs or network drive paths. Puppet will not automatically retrieve source files for you, and usually just passes the value of `source` to the package installation command. You can use a `file` resource if you need to manually copy package files to the target system." validate do |value| provider.validate_source(value) end end newparam(:instance) do desc "A read-only parameter set by the package." end newparam(:status) do desc "A read-only parameter set by the package." end newparam(:adminfile) do desc "A file containing package defaults for installing packages. This attribute is only used on Solaris. Its value should be a path to a local file stored on the target system. Solaris's package tools expect either an absolute file path or a relative path to a file in `/var/sadm/install/admin`. The value of `adminfile` will be passed directly to the `pkgadd` or `pkgrm` command with the `-a <ADMINFILE>` option." end newparam(:responsefile) do desc "A file containing any necessary answers to questions asked by the package. This is currently used on Solaris and Debian. The value will be validated according to system rules, but it should generally be a fully qualified path." end newparam(:configfiles) do desc "Whether to keep or replace modified config files when installing or upgrading a package. This only affects the `apt` and `dpkg` providers." defaultto :keep newvalues(:keep, :replace) end newparam(:category) do desc "A read-only parameter set by the package." end newparam(:platform) do desc "A read-only parameter set by the package." end newparam(:root) do desc "A read-only parameter set by the package." end newparam(:vendor) do desc "A read-only parameter set by the package." end newparam(:description) do desc "A read-only parameter set by the package." end newparam(:allowcdrom) do desc "Tells apt to allow cdrom sources in the sources.list file. Normally apt will bail if you try this." newvalues(:true, :false) end newparam(:flavor) do desc "OpenBSD supports 'flavors', which are further specifications for which type of package you want." end newparam(:install_options, :parent => Puppet::Parameter::PackageOptions, :required_features => :install_options) do desc <<-EOT An array of additional options to pass when installing a package. These options are package-specific, and should be documented by the software vendor. One commonly implemented option is `INSTALLDIR`: package { 'mysql': ensure => installed, source => 'N:/packages/mysql-5.5.16-winx64.msi', install_options => [ '/S', { 'INSTALLDIR' => 'C:\\mysql-5.5' } ], } Each option in the array can either be a string or a hash, where each key and value pair are interpreted in a provider specific way. Each option will automatically be quoted when passed to the install command. With Windows packages, note that file paths in an install option must use backslashes. (Since install options are passed directly to the installation command, forward slashes won't be automatically converted like they are in `file` resources.) Note also that backslashes in double-quoted strings _must_ be escaped and backslashes in single-quoted strings _can_ be escaped. EOT end newparam(:uninstall_options, :parent => Puppet::Parameter::PackageOptions, :required_features => :uninstall_options) do desc <<-EOT An array of additional options to pass when uninstalling a package. These options are package-specific, and should be documented by the software vendor. For example: package { 'VMware Tools': ensure => absent, uninstall_options => [ { 'REMOVE' => 'Sync,VSS' } ], } Each option in the array can either be a string or a hash, where each key and value pair are interpreted in a provider specific way. Each option will automatically be quoted when passed to the uninstall command. On Windows, this is the **only** place in Puppet where backslash separators should be used. Note that backslashes in double-quoted strings _must_ be double-escaped and backslashes in single-quoted strings _may_ be double-escaped. EOT end newparam(:allow_virtual, :boolean => true, :parent => Puppet::Parameter::Boolean, :required_features => :virtual_packages) do desc 'Specifies if virtual package names are allowed for install and uninstall.' defaultto true end autorequire(:file) do autos = [] [:responsefile, :adminfile].each { |param| if val = self[param] autos << val end } if source = self[:source] and absolute_path?(source) autos << source end autos end # This only exists for testing. def clear if obj = @parameters[:ensure] obj.latest = nil end end # The 'query' method returns a hash of info if the package # exists and returns nil if it does not. def exists? @provider.get(:ensure) != :absent end def present?(current_values) super && current_values[:ensure] != :purged end # This parameter exists to ensure backwards compatibility is preserved. # See https://github.com/puppetlabs/puppet/pull/2614 for discussion. # If/when a metaparameter for controlling how arbitrary resources respond # to refreshing is created, that will supersede this, and this will be # deprecated. newparam(:reinstall_on_refresh) do desc "Whether this resource should respond to refresh events (via `subscribe`, `notify`, or the `~>` arrow) by reinstalling the package. Only works for providers that support the `reinstallable` feature. This is useful for source-based distributions, where you may want to recompile a package if the build options change. If you use this, be careful of notifying classes when you want to restart services. If the class also contains a refreshable package, doing so could cause unnecessary re-installs." newvalues(:true, :false) defaultto :false end # When a refresh event is triggered, calls reinstall on providers # that support the reinstall_on_refresh parameter. def refresh if provider.reinstallable? && @parameters[:reinstall_on_refresh].value == :true && @parameters[:ensure].value != :purged && @parameters[:ensure].value != :absent && @parameters[:ensure].value != :held provider.reinstall end end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/router.rb�������������������������������������������������������������0000644�0052762�0001160�00000000534�13417161722�017726� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# # Manage a router abstraction module Puppet Type.newtype(:router) do @doc = "Manages connected router." newparam(:url) do desc <<-EOT An SSH or telnet URL at which to access the router, in the form `ssh://user:pass:enable@host/` or `telnet://user:pass:enable@host/`. EOT isnamevar end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/schedule.rb�����������������������������������������������������������0000644�0052762�0001160�00000040300�13417161722�020175� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet Type.newtype(:schedule) do @doc = <<-'EOT' Define schedules for Puppet. Resources can be limited to a schedule by using the [`schedule`](https://puppet.com/docs/puppet/latest/metaparameter.html#schedule) metaparameter. Currently, **schedules can only be used to stop a resource from being applied;** they cannot cause a resource to be applied when it otherwise wouldn't be, and they cannot accurately specify a time when a resource should run. Every time Puppet applies its configuration, it will apply the set of resources whose schedule does not eliminate them from running right then, but there is currently no system in place to guarantee that a given resource runs at a given time. If you specify a very restrictive schedule and Puppet happens to run at a time within that schedule, then the resources will get applied; otherwise, that work may never get done. Thus, it is advisable to use wider scheduling (for example, over a couple of hours) combined with periods and repetitions. For instance, if you wanted to restrict certain resources to only running once, between the hours of two and 4 AM, then you would use this schedule: schedule { 'maint': range => '2 - 4', period => daily, repeat => 1, } With this schedule, the first time that Puppet runs between 2 and 4 AM, all resources with this schedule will get applied, but they won't get applied again between 2 and 4 because they will have already run once that day, and they won't get applied outside that schedule because they will be outside the scheduled range. Puppet automatically creates a schedule for each of the valid periods with the same name as that period (such as hourly and daily). Additionally, a schedule named `puppet` is created and used as the default, with the following attributes: schedule { 'puppet': period => hourly, repeat => 2, } This will cause resources to be applied every 30 minutes by default. The `statettl` setting on the agent affects the ability of a schedule to determine if a resource has already been checked. If the `statettl` is set lower than the span of the associated schedule resource, then a resource could be checked & applied multiple times in the schedule as the information about when the resource was last checked will have expired from the cache. EOT apply_to_all newparam(:name) do desc <<-EOT The name of the schedule. This name is used when assigning the schedule to a resource with the `schedule` metaparameter: schedule { 'everyday': period => daily, range => '2 - 4', } exec { '/usr/bin/apt-get update': schedule => 'everyday', } EOT isnamevar end newparam(:range) do desc <<-EOT The earliest and latest that a resource can be applied. This is always a hyphen-separated range within a 24 hour period, and hours must be specified in numbers between 0 and 23, inclusive. Minutes and seconds can optionally be provided, using the normal colon as a separator. For instance: schedule { 'maintenance': range => '1:30 - 4:30', } This is mostly useful for restricting certain resources to being applied in maintenance windows or during off-peak hours. Multiple ranges can be applied in array context. As a convenience when specifying ranges, you can cross midnight (for example, `range => "22:00 - 04:00"`). EOT # This is lame; properties all use arrays as values, but parameters don't. # That's going to hurt eventually. validate do |values| values = [values] unless values.is_a?(Array) values.each { |value| unless value.is_a?(String) and value =~ /\d+(:\d+){0,2}\s*-\s*\d+(:\d+){0,2}/ self.fail _("Invalid range value '%{value}'") % { value: value } end } end munge do |values| values = [values] unless values.is_a?(Array) ret = [] values.each { |value| range = [] # Split each range value into a hour, minute, second triad value.split(/\s*-\s*/).each { |val| # Add the values as an array. range << val.split(":").collect { |n| n.to_i } } self.fail _("Invalid range %{value}") % { value: value } if range.length != 2 # Make sure the hours are valid [range[0][0], range[1][0]].each do |n| raise ArgumentError, _("Invalid hour '%{n}'") % { n: n } if n < 0 or n > 23 end [range[0][1], range[1][1]].each do |n| raise ArgumentError, _("Invalid minute '%{n}'") % { n: n } if n and (n < 0 or n > 59) end ret << range } # Now our array of arrays ret end def match?(previous, now) # The lowest-level array is of the hour, minute, second triad # then it's an array of two of those, to present the limits # then it's an array of those ranges @value = [@value] unless @value[0][0].is_a?(Array) @value.each do |value| limits = value.collect do |range| ary = [now.year, now.month, now.day, range[0]] if range[1] ary << range[1] else ary << 0 end if range[2] ary << range[2] else ary << 0 end time = Time.local(*ary) unless time.hour == range[0] self.devfail( _("Incorrectly converted time: %{time}: %{hour} vs %{value}") % { time: time, hour: time.hour, value: range[0] } ) end time end unless limits[0] < limits[1] self.info( _("Assuming upper limit should be that time the next day") ) # Find midnight between the two days. Adding one second # to the end of the day is easier than dealing with dates. ary = limits[0].to_a ary[0] = 59 ary[1] = 59 ary[2] = 23 midnight = Time.local(*ary)+1 # If it is currently between the range start and midnight # we consider that a successful match. if now.between?(limits[0], midnight) # We have to check the weekday match here as it is special-cased # to support day-spanning ranges. if @resource[:weekday] return false unless @resource[:weekday].has_key?(now.wday) end return true end # If we didn't match between the starting time and midnight # we must now move our midnight back 24 hours and try # between the new midnight (24 hours prior) and the # ending time. midnight -= 86400 # Now we compare the current time between midnight and the # end time. if now.between?(midnight, limits[1]) # This case is the reason weekday matching is special cased # in the range parameter. If we match a range that has spanned # past midnight we want to match against the weekday when the range # started, not when it currently is. if @resource[:weekday] return false unless @resource[:weekday].has_key?((now - 86400).wday) end return true end # If neither of the above matched then we don't match the # range schedule. return false end # Check to see if a weekday parameter was specified and, if so, # do we match it or not. If we fail we can stop here. # This is required because spanning ranges forces us to check # weekday within the range parameter. if @resource[:weekday] return false unless @resource[:weekday].has_key?(now.wday) end return true if now.between?(*limits) end # Else, return false, since our current time isn't between # any valid times false end end newparam(:periodmatch) do desc "Whether periods should be matched by a numeric value (for instance, whether two times are in the same hour) or by their chronological distance apart (whether two times are 60 minutes apart)." newvalues(:number, :distance) defaultto :distance end newparam(:period) do desc <<-EOT The period of repetition for resources on this schedule. The default is for resources to get applied every time Puppet runs. Note that the period defines how often a given resource will get applied but not when; if you would like to restrict the hours that a given resource can be applied (for instance, only at night during a maintenance window), then use the `range` attribute. If the provided periods are not sufficient, you can provide a value to the *repeat* attribute, which will cause Puppet to schedule the affected resources evenly in the period the specified number of times. Take this schedule: schedule { 'veryoften': period => hourly, repeat => 6, } This can cause Puppet to apply that resource up to every 10 minutes. At the moment, Puppet cannot guarantee that level of repetition; that is, the resource can applied _up to_ every 10 minutes, but internal factors might prevent it from actually running that often (for instance, if a Puppet run is still in progress when the next run is scheduled to start, that next run will be suppressed). See the `periodmatch` attribute for tuning whether to match times by their distance apart or by their specific value. EOT newvalues(:hourly, :daily, :weekly, :monthly, :never) ScheduleScales = { :hourly => 3600, :daily => 86400, :weekly => 604800, :monthly => 2592000 } ScheduleMethods = { :hourly => :hour, :daily => :day, :monthly => :month, :weekly => proc do |prev, now| # Run the resource if the previous day was after this weekday (e.g., prev is wed, current is tue) # or if it's been more than a week since we ran prev.wday > now.wday or (now - prev) > (24 * 3600 * 7) end } def match?(previous, now) return false if value == :never value = self.value case @resource[:periodmatch] when :number method = ScheduleMethods[value] if method.is_a?(Proc) return method.call(previous, now) else # We negate it, because if they're equal we don't run return now.send(method) != previous.send(method) end when :distance scale = ScheduleScales[value] # If the number of seconds between the two times is greater # than the unit of time, we match. We divide the scale # by the repeat, so that we'll repeat that often within # the scale. return (now.to_i - previous.to_i) >= (scale / @resource[:repeat]) end end end newparam(:repeat) do desc "How often a given resource may be applied in this schedule's `period`. Must be an integer." defaultto 1 validate do |value| unless value.is_a?(Integer) or value =~ /^\d+$/ raise Puppet::Error, _("Repeat must be a number") end # This implicitly assumes that 'periodmatch' is distance -- that # is, if there's no value, we assume it's a valid value. return unless @resource[:periodmatch] if value != 1 and @resource[:periodmatch] != :distance raise Puppet::Error, _("Repeat must be 1 unless periodmatch is 'distance', not '%{period}'") % { period: @resource[:periodmatch] } end end munge do |value| value = Integer(value) unless value.is_a?(Integer) value end def match?(previous, now) true end end newparam(:weekday) do desc <<-EOT The days of the week in which the schedule should be valid. You may specify the full day name 'Tuesday', the three character abbreviation 'Tue', or a number (as a string or as an integer) corresponding to the day of the week where 0 is Sunday, 1 is Monday, and so on. Multiple days can be specified as an array. If not specified, the day of the week will not be considered in the schedule. If you are also using a range match that spans across midnight then this parameter will match the day that it was at the start of the range, not necessarily the day that it is when it matches. For example, consider this schedule: schedule { 'maintenance_window': range => '22:00 - 04:00', weekday => 'Saturday', } This will match at 11 PM on Saturday and 2 AM on Sunday, but not at 2 AM on Saturday. EOT validate do |values| values = [values] unless values.is_a?(Array) values.each { |value| if weekday_integer?(value) || weekday_string?(value) value else raise ArgumentError, _("%{value} is not a valid day of the week") % { value: value } end } end def weekday_integer?(value) value.is_a?(Integer) && (0..6).include?(value) end def weekday_string?(value) value.is_a?(String) && (value =~ /^[0-6]$/ || value =~ /^(Mon|Tues?|Wed(?:nes)?|Thu(?:rs)?|Fri|Sat(?:ur)?|Sun)(day)?$/i) end weekdays = { 'sun' => 0, 'mon' => 1, 'tue' => 2, 'wed' => 3, 'thu' => 4, 'fri' => 5, 'sat' => 6, } munge do |values| values = [values] unless values.is_a?(Array) ret = {} values.each do |value| case value when /^[0-6]$/ index = value.to_i when 0..6 index = value else index = weekdays[value[0,3].downcase] end ret[index] = true end ret end def match?(previous, now) # Special case weekday matching with ranges to a no-op here. # If the ranges span days then we can't simply match the current # weekday, as we want to match the weekday as it was when the range # started. As a result, all of that logic is in range, not here. return true if @resource[:range] return true if value.has_key?(now.wday) false end end def self.instances [] end def self.mkdefaultschedules result = [] unless Puppet[:default_schedules] Puppet.debug "Not creating default schedules: default_schedules is false" return result end Puppet.debug "Creating default schedules" result << self.new( :name => "puppet", :period => :hourly, :repeat => "2" ) # And then one for every period @parameters.find { |p| p.name == :period }.value_collection.values.each { |value| result << self.new( :name => value.to_s, :period => value ) } result end def match?(previous = nil, now = nil) # If we've got a value, then convert it to a Time instance previous &&= Time.at(previous) now ||= Time.now # Pull them in order self.class.allattrs.each { |param| if @parameters.include?(param) and @parameters[param].respond_to?(:match?) return false unless @parameters[param].match?(previous, now) end } # If we haven't returned false, then return true; in other words, # any provided schedules need to all match true end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/scheduled_task.rb�����������������������������������������������������0000644�0052762�0001160�00000016700�13417161722�021372� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util' Puppet::Type.newtype(:scheduled_task) do include Puppet::Util @doc = "Installs and manages Windows Scheduled Tasks. All attributes except `name`, `command`, and `trigger` are optional; see the description of the `trigger` attribute for details on setting schedules." ensurable newproperty(:enabled) do desc "Whether the triggers for this task should be enabled. This attribute affects every trigger for the task; triggers cannot be enabled or disabled individually." newvalue(:true, :event => :task_enabled) newvalue(:false, :event => :task_disabled) defaultto(:true) end newparam(:name) do desc "The name assigned to the scheduled task. This will uniquely identify the task on the system." isnamevar end newproperty(:command) do desc "The full path to the application to run, without any arguments." validate do |value| raise Puppet::Error.new(_('Must be specified using an absolute path.')) unless absolute_path?(value) end munge do |value| # windows converts slashes to backslashes, so the *is* value # has backslashes. Do the same for the *should* value, so that # we are slash-insensitive. See #13009 File.expand_path(value).gsub(/\//, '\\') end end newproperty(:working_dir) do desc "The full path of the directory in which to start the command." validate do |value| raise Puppet::Error.new(_('Must be specified using an absolute path.')) unless absolute_path?(value) end end newproperty(:arguments) do desc "Any arguments or flags that should be passed to the command. Multiple arguments should be specified as a space-separated string." end newproperty(:user) do desc "The user to run the scheduled task as. Please note that not all security configurations will allow running a scheduled task as 'SYSTEM', and saving the scheduled task under these conditions will fail with a reported error of 'The operation completed successfully'. It is recommended that you either choose another user to run the scheduled task, or alter the security policy to allow v1 scheduled tasks to run as the 'SYSTEM' account. Defaults to 'SYSTEM'. Please also note that Puppet must be running as a privileged user in order to manage `scheduled_task` resources. Running as an unprivileged user will result in 'access denied' errors." defaultto :system def insync?(current) provider.user_insync?(current, @should) end end newparam(:password) do desc "The password for the user specified in the 'user' attribute. This is only used if specifying a user other than 'SYSTEM'. Since there is no way to retrieve the password used to set the account information for a task, this parameter will not be used to determine if a scheduled task is in sync or not." end newproperty(:trigger, :array_matching => :all) do desc <<-'EOT' One or more triggers defining when the task should run. A single trigger is represented as a hash, and multiple triggers can be specified with an array of hashes. A trigger can contain the following keys: * For all triggers: * `schedule` **(Required)** --- What kind of trigger this is. Valid values are `daily`, `weekly`, `monthly`, or `once`. Each kind of trigger is configured with a different set of keys; see the sections below. (`once` triggers only need a start time/date.) * `start_time` **(Required)** --- The time of day when the trigger should first become active. Several time formats will work, but we suggest 24-hour time formatted as HH:MM. * `start_date` --- The date when the trigger should first become active. Defaults to the current date. You should format dates as YYYY-MM-DD, although other date formats may work. (Under the hood, this uses `Date.parse`.) * `minutes_interval` --- The repeat interval in minutes. * `minutes_duration` --- The duration in minutes, needs to be greater than the minutes_interval. * For `daily` triggers: * `every` --- How often the task should run, as a number of days. Defaults to 1. ("2" means every other day, "3" means every three days, and so on) * For `weekly` triggers: * `every` --- How often the task should run, as a number of weeks. Defaults to 1. ("2" means every other week, "3" means every three weeks, and so on) * `day_of_week` --- Which days of the week the task should run, as an array. Defaults to all days. Each day must be one of `mon`, `tues`, `wed`, `thurs`, `fri`, `sat`, `sun`, or `all`. * For `monthly` (by date) triggers: * `months` --- Which months the task should run, as an array. Defaults to all months. Each month must be an integer between 1 and 12. * `on` **(Required)** --- Which days of the month the task should run, as an array. Each day must be an integer between 1 and 31. * For `monthly` (by weekday) triggers: * `months` --- Which months the task should run, as an array. Defaults to all months. Each month must be an integer between 1 and 12. * `day_of_week` **(Required)** --- Which day of the week the task should run, as an array with only one element. Each day must be one of `mon`, `tues`, `wed`, `thurs`, `fri`, `sat`, `sun`, or `all`. * `which_occurrence` **(Required)** --- The occurrence of the chosen weekday when the task should run. Must be one of `first`, `second`, `third`, `fourth`, or `fifth`. Examples: # Run at 8am on the 1st and 15th days of the month in January, March, # May, July, September, and November, starting after August 31st, 2011. trigger => { schedule => monthly, start_date => '2011-08-31', # Defaults to current date start_time => '08:00', # Must be specified months => [1,3,5,7,9,11], # Defaults to all on => [1, 15], # Must be specified } # Run at 8am on the first Monday of the month for January, March, and May, # starting after August 31st, 2011. trigger => { schedule => monthly, start_date => '2011-08-31', # Defaults to current date start_time => '08:00', # Must be specified months => [1,3,5], # Defaults to all which_occurrence => first, # Must be specified day_of_week => [mon], # Must be specified } # Run daily repeating every 30 minutes between 9am and 5pm (480 minutes) starting after August 31st, 2011. trigger => { schedule => daily, start_date => '2011-08-31', # Defaults to current date start_time => '8:00', # Must be specified minutes_interval => 30, minutes_duration => 480, } EOT validate do |value| provider.validate_trigger(value) end def insync?(current) provider.trigger_insync?(current, @should) end def should_to_s(new_value=@should) super(new_value) end def is_to_s(current_value=@is) super(current_value) end end end ����������������������������������������������������������������puppet-5.5.10/lib/puppet/type/selboolean.rb���������������������������������������������������������0000644�0052762�0001160�00000001230�13417161722�020523� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet Type.newtype(:selboolean) do @doc = "Manages SELinux booleans on systems with SELinux support. The supported booleans are any of the ones found in `/selinux/booleans/`." newparam(:name) do desc "The name of the SELinux boolean to be managed." isnamevar end newproperty(:value) do desc "Whether the SELinux boolean should be enabled or disabled." newvalue(:on) newvalue(:off) end newparam(:persistent) do desc "If set to true, SELinux booleans will be written to disk and persist across reboots." defaultto :false newvalues(:true, :false) end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/selmodule.rb����������������������������������������������������������0000644�0052762�0001160�00000003333�13417161722�020377� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# # Simple module for managing SELinux policy modules # Puppet::Type.newtype(:selmodule) do @doc = "Manages loading and unloading of SELinux policy modules on the system. Requires SELinux support. See man semodule(8) for more information on SELinux policy modules. **Autorequires:** If Puppet is managing the file containing this SELinux policy module (which is either explicitly specified in the `selmodulepath` attribute or will be found at {`selmoduledir`}/{`name`}.pp), the selmodule resource will autorequire that file." ensurable newparam(:name) do desc "The name of the SELinux policy to be managed. You should not include the customary trailing .pp extension." isnamevar end newparam(:selmoduledir) do desc "The directory to look for the compiled pp module file in. If the `selmodulepath` attribute is not specified, Puppet expects to find the module in `<selmoduledir>/<name>.pp`, where `name` is the value of the `name` parameter." defaultto "/usr/share/selinux/targeted" end newparam(:selmodulepath) do desc "The full path to the compiled .pp policy module. You only need to use this if the module file is not in the `selmoduledir` directory." end newproperty(:syncversion) do desc "If set to `true`, the policy will be reloaded if the version found in the on-disk file differs from the loaded version. If set to `false` (the default) the only check that will be made is if the policy is loaded at all or not." newvalue(:true) newvalue(:false) end autorequire(:file) do if self[:selmodulepath] [self[:selmodulepath]] else ["#{self[:selmoduledir]}/#{self[:name]}.pp"] end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/service.rb������������������������������������������������������������0000644�0052762�0001160�00000022624�13417161722�020052� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This is our main way of managing processes right now. # # a service is distinct from a process in that services # can only be managed through the interface of an init script # which is why they have a search path for initscripts and such module Puppet Type.newtype(:service) do @doc = "Manage running services. Service support unfortunately varies widely by platform --- some platforms have very little if any concept of a running service, and some have a very codified and powerful concept. Puppet's service support is usually capable of doing the right thing, but the more information you can provide, the better behaviour you will get. Puppet 2.7 and newer expect init scripts to have a working status command. If this isn't the case for any of your services' init scripts, you will need to set `hasstatus` to false and possibly specify a custom status command in the `status` attribute. As a last resort, Puppet will attempt to search the process table by calling whatever command is listed in the `ps` fact. The default search pattern is the name of the service, but you can specify it with the `pattern` attribute. **Refresh:** `service` resources can respond to refresh events (via `notify`, `subscribe`, or the `~>` arrow). If a `service` receives an event from another resource, Puppet will restart the service it manages. The actual command used to restart the service depends on the platform and can be configured: * If you set `hasrestart` to true, Puppet will use the init script's restart command. * You can provide an explicit command for restarting with the `restart` attribute. * If you do neither, the service's stop and start commands will be used." feature :refreshable, "The provider can restart the service.", :methods => [:restart] feature :enableable, "The provider can enable and disable the service", :methods => [:disable, :enable, :enabled?] feature :controllable, "The provider uses a control variable." feature :flaggable, "The provider can pass flags to the service." feature :maskable, "The provider can 'mask' the service.", :methods => [:mask] newproperty(:enable, :required_features => :enableable) do desc "Whether a service should be enabled to start at boot. This property behaves quite differently depending on the platform; wherever possible, it relies on local tools to enable or disable a given service." newvalue(:true, :event => :service_enabled) do provider.enable end newvalue(:false, :event => :service_disabled) do provider.disable end newvalue(:manual, :event => :service_manual_start) do provider.manual_start end # This only makes sense on systemd systems. Otherwise, it just defaults # to disable. newvalue(:mask, :event => :service_disabled, :required_features => :maskable) do provider.mask end def retrieve provider.enabled? end # This only makes sense on systemd systems. Static services cannot be enabled # or disabled manually. def insync?(current) if provider.respond_to?(:cached_enabled?) && provider.cached_enabled? == 'static' Puppet.debug("Unable to enable or disable static service #{@resource[:name]}") return true end super(current) end validate do |value| if value == :manual and !Puppet.features.microsoft_windows? raise Puppet::Error.new(_("Setting enable to manual is only supported on Microsoft Windows.")) end end end # Handle whether the service should actually be running right now. newproperty(:ensure) do desc "Whether a service should be running." newvalue(:stopped, :event => :service_stopped) do provider.stop end newvalue(:running, :event => :service_started, :invalidate_refreshes => true) do provider.start end aliasvalue(:false, :stopped) aliasvalue(:true, :running) def retrieve provider.status end def sync event = super() if property = @resource.property(:enable) val = property.retrieve property.sync unless property.safe_insync?(val) end event end end newproperty(:flags, :required_features => :flaggable) do desc "Specify a string of flags to pass to the startup script." end newparam(:binary) do desc "The path to the daemon. This is only used for systems that do not support init scripts. This binary will be used to start the service if no `start` parameter is provided." end newparam(:hasstatus) do desc "Declare whether the service's init script has a functional status command. This attribute's default value changed in Puppet 2.7.0. The init script's status command must return 0 if the service is running and a nonzero value otherwise. Ideally, these exit codes should conform to [the LSB's specification][lsb-exit-codes] for init script status actions, but Puppet only considers the difference between 0 and nonzero to be relevant. If a service's init script does not support any kind of status command, you should set `hasstatus` to false and either provide a specific command using the `status` attribute or expect that Puppet will look for the service name in the process table. Be aware that 'virtual' init scripts (like 'network' under Red Hat systems) will respond poorly to refresh events from other resources if you override the default behavior without providing a status command." newvalues(:true, :false) defaultto :true end newparam(:name) do desc <<-EOT The name of the service to run. This name is used to find the service; on platforms where services have short system names and long display names, this should be the short name. (To take an example from Windows, you would use "wuauserv" rather than "Automatic Updates.") EOT isnamevar end newparam(:path) do desc "The search path for finding init scripts. Multiple values should be separated by colons or provided as an array." munge do |value| value = [value] unless value.is_a?(Array) value.flatten.collect { |p| p.split(File::PATH_SEPARATOR) }.flatten end defaultto { provider.class.defpath if provider.class.respond_to?(:defpath) } end newparam(:pattern) do desc "The pattern to search for in the process table. This is used for stopping services on platforms that do not support init scripts, and is also used for determining service status on those service whose init scripts do not include a status command. Defaults to the name of the service. The pattern can be a simple string or any legal Ruby pattern, including regular expressions (which should be quoted without enclosing slashes)." defaultto { @resource[:binary] || @resource[:name] } end newparam(:restart) do desc "Specify a *restart* command manually. If left unspecified, the service will be stopped and then started." end newparam(:start) do desc "Specify a *start* command manually. Most service subsystems support a `start` command, so this will not need to be specified." end newparam(:status) do desc "Specify a *status* command manually. This command must return 0 if the service is running and a nonzero value otherwise. Ideally, these exit codes should conform to [the LSB's specification][lsb-exit-codes] for init script status actions, but Puppet only considers the difference between 0 and nonzero to be relevant. If left unspecified, the status of the service will be determined automatically, usually by looking for the service in the process table. [lsb-exit-codes]: http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html" end newparam(:stop) do desc "Specify a *stop* command manually." end newparam(:control) do desc "The control variable used to manage services (originally for HP-UX). Defaults to the upcased service name plus `START` replacing dots with underscores, for those providers that support the `controllable` feature." defaultto { resource.name.gsub(".","_").upcase + "_START" if resource.provider.controllable? } end newparam :hasrestart do desc "Specify that an init script has a `restart` command. If this is false and you do not specify a command in the `restart` attribute, the init script's `stop` and `start` commands will be used." newvalues(:true, :false) end newparam(:manifest) do desc "Specify a command to config a service, or a path to a manifest to do so." end # Basically just a synonym for restarting. Used to respond # to events. def refresh # Only restart if we're actually running if (@parameters[:ensure] || newattr(:ensure)).retrieve == :running provider.restart else debug "Skipping restart; service is not running" end end def self.needs_ensure_retrieved false end end end ������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/ssh_authorized_key.rb�������������������������������������������������0000644�0052762�0001160�00000011555�13417161722�022316� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet Type.newtype(:ssh_authorized_key) do @doc = "Manages SSH authorized keys. Currently only type 2 keys are supported. In their native habitat, SSH keys usually appear as a single long line, in the format `<TYPE> <KEY> <NAME/COMMENT>`. This resource type requires you to split that line into several attributes. Thus, a key that appears in your `~/.ssh/id_rsa.pub` file like this... ssh-rsa AAAAB3Nza[...]qXfdaQ== nick@magpie.example.com ...would translate to the following resource: ssh_authorized_key { 'nick@magpie.example.com': ensure => present, user => 'nick', type => 'ssh-rsa', key => 'AAAAB3Nza[...]qXfdaQ==', } To ensure that only the currently approved keys are present, you can purge unmanaged SSH keys on a per-user basis. Do this with the `user` resource type's `purge_ssh_keys` attribute: user { 'nick': ensure => present, purge_ssh_keys => true, } This will remove any keys in `~/.ssh/authorized_keys` that aren't being managed with `ssh_authorized_key` resources. See the documentation of the `user` type for more details. **Autorequires:** If Puppet is managing the user account in which this SSH key should be installed, the `ssh_authorized_key` resource will autorequire that user." ensurable newparam(:name) do desc "The SSH key comment. This can be anything, and doesn't need to match the original comment from the `.pub` file. Due to internal limitations, this must be unique across all user accounts; if you want to specify one key for multiple users, you must use a different comment for each instance." isnamevar end newproperty(:type) do desc "The encryption type used." newvalues :'ssh-dss', :'ssh-rsa', :'ecdsa-sha2-nistp256', :'ecdsa-sha2-nistp384', :'ecdsa-sha2-nistp521', :'ssh-ed25519' aliasvalue(:dsa, :'ssh-dss') aliasvalue(:ed25519, :'ssh-ed25519') aliasvalue(:rsa, :'ssh-rsa') end newproperty(:key) do desc "The public key itself; generally a long string of hex characters. The `key` attribute may not contain whitespace. Make sure to omit the following in this attribute (and specify them in other attributes): * Key headers, such as 'ssh-rsa' --- put these in the `type` attribute. * Key identifiers / comments, such as 'joe@joescomputer.local' --- put these in the `name` attribute/resource title." validate do |value| raise Puppet::Error, _("Key must not contain whitespace: %{value}") % { value: value } if value =~ /\s/ end end newproperty(:user) do desc "The user account in which the SSH key should be installed. The resource will autorequire this user if it is being managed as a `user` resource." end newproperty(:target) do desc "The absolute filename in which to store the SSH key. This property is optional and should be used only in cases where keys are stored in a non-standard location, for instance when not in `~user/.ssh/authorized_keys`." defaultto :absent def should return super if defined?(@should) and @should[0] != :absent return nil unless user = resource[:user] begin return File.expand_path("~#{user}/.ssh/authorized_keys") rescue Puppet.debug "The required user is not yet present on the system" return nil end end def insync?(is) is == should end end newproperty(:options, :array_matching => :all) do desc "Key options; see sshd(8) for possible values. Multiple values should be specified as an array." defaultto do :absent end validate do |value| unless value == :absent or value =~ /^[-a-z0-9A-Z_]+(?:=\".*?\")?$/ raise Puppet::Error, _("Option %{value} is not valid. A single option must either be of the form 'option' or 'option=\"value\". Multiple options must be provided as an array") % { value: value } end end end autorequire(:user) do should(:user) if should(:user) end validate do # Go ahead if target attribute is defined return if @parameters[:target].shouldorig[0] != :absent # Go ahead if user attribute is defined return if @parameters.include?(:user) # If neither target nor user is defined, this is an error raise Puppet::Error, _("Attribute 'user' or 'target' is mandatory") end # regular expression suitable for use by a ParsedFile based provider REGEX = /^(?:(.+)\s+)?(ssh-dss|ssh-ed25519|ssh-rsa|ecdsa-sha2-nistp256|ecdsa-sha2-nistp384|ecdsa-sha2-nistp521)\s+([^ ]+)\s*(.*)$/ def self.keyline_regex REGEX end end end ���������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/sshkey.rb�������������������������������������������������������������0000644�0052762�0001160�00000005323�13417161722�017715� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet Type.newtype(:sshkey) do @doc = "Installs and manages ssh host keys. By default, this type will install keys into `/etc/ssh/ssh_known_hosts`. To manage ssh keys in a different `known_hosts` file, such as a user's personal `known_hosts`, pass its path to the `target` parameter. See the `ssh_authorized_key` type to manage authorized keys." ensurable newproperty(:type) do desc "The encryption type used. Probably ssh-dss or ssh-rsa." newvalues :'ssh-dss', :'ssh-ed25519', :'ssh-rsa', :'ecdsa-sha2-nistp256', :'ecdsa-sha2-nistp384', :'ecdsa-sha2-nistp521' aliasvalue(:dsa, :'ssh-dss') aliasvalue(:ed25519, :'ssh-ed25519') aliasvalue(:rsa, :'ssh-rsa') end newproperty(:key) do desc "The key itself; generally a long string of uuencoded characters. The `key` attribute may not contain whitespace. Make sure to omit the following in this attribute (and specify them in other attributes): * Key headers, such as 'ssh-rsa' --- put these in the `type` attribute. * Key identifiers / comments, such as 'joescomputer.local' --- put these in the `name` attribute/resource title." end # FIXME This should automagically check for aliases to the hosts, just # to see if we can automatically glean any aliases. newproperty(:host_aliases) do desc 'Any aliases the host might have. Multiple values must be specified as an array.' attr_accessor :meta def insync?(is) is == @should end # We actually want to return the whole array here, not just the first # value. def should defined?(@should) ? @should : nil end validate do |value| if value =~ /\s/ raise Puppet::Error, _("Aliases cannot include whitespace") end if value =~ /,/ raise Puppet::Error, _("Aliases must be provided as an array, not a comma-separated list") end end end newparam(:name) do desc "The host name that the key is associated with." isnamevar validate do |value| raise Puppet::Error, _("Resourcename cannot include whitespaces") if value =~ /\s/ raise Puppet::Error, _("No comma in resourcename allowed. If you want to specify aliases use the host_aliases property") if value.include?(',') end end newproperty(:target) do desc "The file in which to store the ssh key. Only used by the `parsed` provider." defaultto { if @resource.class.defaultprovider.ancestors.include?(Puppet::Provider::ParsedFile) @resource.class.defaultprovider.default_target else nil end } end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/tidy.rb���������������������������������������������������������������0000644�0052762�0001160�00000024241�13417161722�017360� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/parameter/boolean' Puppet::Type.newtype(:tidy) do require 'puppet/file_serving/fileset' require 'puppet/file_bucket/dipper' @doc = "Remove unwanted files based on specific criteria. Multiple criteria are OR'd together, so a file that is too large but is not old enough will still get tidied. If you don't specify either `age` or `size`, then all files will be removed. This resource type works by generating a file resource for every file that should be deleted and then letting that resource perform the actual deletion. " # Tidy names are not isomorphic with the objects. @isomorphic = false newparam(:path) do desc "The path to the file or directory to manage. Must be fully qualified." isnamevar munge do |value| File.expand_path(value) end end newparam(:recurse) do desc "If target is a directory, recursively descend into the directory looking for files to tidy." newvalues(:true, :false, :inf, /^[0-9]+$/) # Replace the validation so that we allow numbers in # addition to string representations of them. validate { |arg| } munge do |value| newval = super(value) case newval when :true, :inf; true when :false; false when Integer; value when /^\d+$/; Integer(value) else raise ArgumentError, _("Invalid recurse value %{value}") % { value: value.inspect } end end end newparam(:matches) do desc <<-'EOT' One or more (shell type) file glob patterns, which restrict the list of files to be tidied to those whose basenames match at least one of the patterns specified. Multiple patterns can be specified using an array. Example: tidy { '/tmp': age => '1w', recurse => 1, matches => [ '[0-9]pub*.tmp', '*.temp', 'tmpfile?' ], } This removes files from `/tmp` if they are one week old or older, are not in a subdirectory and match one of the shell globs given. Note that the patterns are matched against the basename of each file -- that is, your glob patterns should not have any '/' characters in them, since you are only specifying against the last bit of the file. Finally, note that you must now specify a non-zero/non-false value for recurse if matches is used, as matches only apply to files found by recursion (there's no reason to use static patterns match against a statically determined path). Requiring explicit recursion clears up a common source of confusion. EOT # Make sure we convert to an array. munge do |value| fail _("Tidy can't use matches with recurse 0, false, or undef") if "#{@resource[:recurse]}" =~ /^(0|false|)$/ [value].flatten end # Does a given path match our glob patterns, if any? Return true # if no patterns have been provided. def tidy?(path, stat) basename = File.basename(path) flags = File::FNM_DOTMATCH | File::FNM_PATHNAME return(value.find {|pattern| File.fnmatch(pattern, basename, flags) } ? true : false) end end newparam(:backup) do desc "Whether tidied files should be backed up. Any values are passed directly to the file resources used for actual file deletion, so consult the `file` type's backup documentation to determine valid values." end newparam(:age) do desc "Tidy files whose age is equal to or greater than the specified time. You can choose seconds, minutes, hours, days, or weeks by specifying the first letter of any of those words (for example, '1w' represents one week). Specifying 0 will remove all files." AgeConvertors = { :s => 1, :m => 60, :h => 60 * 60, :d => 60 * 60 * 24, :w => 60 * 60 * 24 * 7, } def convert(unit, multi) if num = AgeConvertors[unit] return num * multi else self.fail _("Invalid age unit '%{unit}'") % { unit: unit } end end def tidy?(path, stat) # If the file's older than we allow, we should get rid of it. (Time.now.to_i - stat.send(resource[:type]).to_i) > value end munge do |age| unit = multi = nil case age when /^([0-9]+)(\w)\w*$/ multi = Integer($1) unit = $2.downcase.intern when /^([0-9]+)$/ multi = Integer($1) unit = :d else #TRANSLATORS tidy is the name of a program and should not be translated self.fail _("Invalid tidy age %{age}") % { age: age } end convert(unit, multi) end end newparam(:size) do desc "Tidy files whose size is equal to or greater than the specified size. Unqualified values are in kilobytes, but *b*, *k*, *m*, *g*, and *t* can be appended to specify *bytes*, *kilobytes*, *megabytes*, *gigabytes*, and *terabytes*, respectively. Only the first character is significant, so the full word can also be used." def convert(unit, multi) if num = { :b => 0, :k => 1, :m => 2, :g => 3, :t => 4 }[unit] result = multi num.times do result *= 1024 end return result else self.fail _("Invalid size unit '%{unit}'") % { unit: unit } end end def tidy?(path, stat) stat.size >= value end munge do |size| case size when /^([0-9]+)(\w)\w*$/ multi = Integer($1) unit = $2.downcase.intern when /^([0-9]+)$/ multi = Integer($1) unit = :k else #TRANSLATORS tidy is the name of a program and should not be translated self.fail _("Invalid tidy size %{age}") % { age: age } end convert(unit, multi) end end newparam(:type) do desc "Set the mechanism for determining age." newvalues(:atime, :mtime, :ctime) defaultto :atime end newparam(:rmdirs, :boolean => true, :parent => Puppet::Parameter::Boolean) do desc "Tidy directories in addition to files; that is, remove directories whose age is older than the specified criteria. This will only remove empty directories, so all contained files must also be tidied before a directory gets removed." end # Erase PFile's validate method validate do end def self.instances [] end def depthfirst? true end def initialize(hash) super # only allow backing up into filebuckets self[:backup] = false unless self[:backup].is_a? Puppet::FileBucket::Dipper end # Make a file resource to remove a given file. def mkfile(path) # Force deletion, so directories actually get deleted. parameters = { :path => path, :backup => self[:backup], :ensure => :absent, :force => true } parameters[:noop] = self[:noop] unless self[:noop].nil? Puppet::Type.type(:file).new(parameters) end def retrieve # Our ensure property knows how to retrieve everything for us. if obj = @parameters[:ensure] return obj.retrieve else return {} end end # Hack things a bit so we only ever check the ensure property. def properties [] end def generate return [] unless stat(self[:path]) case self[:recurse] when Integer, /^\d+$/ parameter = { :recurse => true, :recurselimit => self[:recurse] } when true, :true, :inf parameter = { :recurse => true } end if parameter files = Puppet::FileServing::Fileset.new(self[:path], parameter).files.collect do |f| f == "." ? self[:path] : ::File.join(self[:path], f) end else files = [self[:path]] end found_files = files.find_all { |path| tidy?(path) }.collect { |path| mkfile(path) } result = found_files.each { |file| debug "Tidying #{file.ref}" }.sort { |a,b| b[:path] <=> a[:path] } if found_files.size > 0 #TRANSLATORS "Tidy" is a program name and should not be translated notice _("Tidying %{count} files") % { count: found_files.size } end # No need to worry about relationships if we don't have rmdirs; there won't be # any directories. return result unless rmdirs? # Now make sure that all directories require the files they contain, if all are available, # so that a directory is emptied before we try to remove it. files_by_name = result.inject({}) { |hash, file| hash[file[:path]] = file; hash } files_by_name.keys.sort { |a,b| b <=> a }.each do |path| dir = ::File.dirname(path) next unless resource = files_by_name[dir] if resource[:require] resource[:require] << Puppet::Resource.new(:file, path) else resource[:require] = [Puppet::Resource.new(:file, path)] end end result end # Does a given path match our glob patterns, if any? Return true # if no patterns have been provided. def matches?(path) return true unless self[:matches] basename = File.basename(path) flags = File::FNM_DOTMATCH | File::FNM_PATHNAME if self[:matches].find {|pattern| File.fnmatch(pattern, basename, flags) } return true else debug "No specified patterns match #{path}, not tidying" return false end end # Should we remove the specified file? def tidy?(path) # ignore files that are already managed, since we can't tidy # those files anyway return false if catalog.resource(:file, path) return false unless stat = self.stat(path) return false if stat.ftype == "directory" and ! rmdirs? # The 'matches' parameter isn't OR'ed with the other tests -- # it's just used to reduce the list of files we can match. return false if param = parameter(:matches) and ! param.tidy?(path, stat) tested = false [:age, :size].each do |name| next unless param = parameter(name) tested = true return true if param.tidy?(path, stat) end # If they don't specify either, then the file should always be removed. return true unless tested false end def stat(path) begin Puppet::FileSystem.lstat(path) rescue Errno::ENOENT info _("File does not exist") return nil rescue Errno::EACCES #TRANSLATORS "stat" is a program name and should not be translated warning _("Could not stat; permission denied") return nil end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/user.rb���������������������������������������������������������������0000644�0052762�0001160�00000066636�13417161722�017403� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'etc' require 'facter' require 'puppet/parameter/boolean' require 'puppet/property/list' require 'puppet/property/ordered_list' require 'puppet/property/keyvalue' module Puppet Type.newtype(:user) do @doc = "Manage users. This type is mostly built to manage system users, so it is lacking some features useful for managing normal users. This resource type uses the prescribed native tools for creating groups and generally uses POSIX APIs for retrieving information about them. It does not directly modify `/etc/passwd` or anything. **Autorequires:** If Puppet is managing the user's primary group (as provided in the `gid` attribute) or any group listed in the `groups` attribute then the user resource will autorequire that group. If Puppet is managing any role accounts corresponding to the user's roles, the user resource will autorequire those role accounts." feature :allows_duplicates, "The provider supports duplicate users with the same UID." feature :manages_homedir, "The provider can create and remove home directories." feature :manages_passwords, "The provider can modify user passwords, by accepting a password hash." feature :manages_password_age, "The provider can set age requirements and restrictions for passwords." feature :manages_password_salt, "The provider can set a password salt. This is for providers that implement PBKDF2 passwords with salt properties." feature :manages_solaris_rbac, "The provider can manage roles and normal users" feature :manages_expiry, "The provider can manage the expiry date for a user." feature :system_users, "The provider allows you to create system users with lower UIDs." feature :manages_aix_lam, "The provider can manage AIX Loadable Authentication Module (LAM) system." feature :libuser, "Allows local users to be managed on systems that also use some other remote NSS method of managing accounts." feature :manages_shell, "The provider allows for setting shell and validates if possible" feature :manages_loginclass, "The provider can manage the login class for a user." newproperty(:ensure, :parent => Puppet::Property::Ensure) do newvalue(:present, :event => :user_created) do provider.create end newvalue(:absent, :event => :user_removed) do provider.delete end newvalue(:role, :event => :role_created, :required_features => :manages_solaris_rbac) do provider.create_role end desc "The basic state that the object should be in." # If they're talking about the thing at all, they generally want to # say it should exist. defaultto do if @resource.managed? :present else nil end end def retrieve if provider.exists? if provider.respond_to?(:is_role?) and provider.is_role? return :role else return :present end else return :absent end end end newproperty(:home) do desc "The home directory of the user. The directory must be created separately and is not currently checked for existence." end newproperty(:uid) do desc "The user ID; must be specified numerically. If no user ID is specified when creating a new user, then one will be chosen automatically. This will likely result in the same user having different UIDs on different systems, which is not recommended. This is especially noteworthy when managing the same user on both Darwin and other platforms, since Puppet does UID generation on Darwin, but the underlying tools do so on other platforms. On Windows, this property is read-only and will return the user's security identifier (SID)." munge do |value| case value when String if value =~ /^[-0-9]+$/ value = Integer(value) end end return value end end newproperty(:gid) do desc "The user's primary group. Can be specified numerically or by name. This attribute is not supported on Windows systems; use the `groups` attribute instead. (On Windows, designating a primary group is only meaningful for domain accounts, which Puppet does not currently manage.)" munge do |value| if value.is_a?(String) and value =~ /^[-0-9]+$/ Integer(value) else value end end def insync?(is) # We know the 'is' is a number, so we need to convert the 'should' to a number, # too. @should.each do |value| return true if number = Puppet::Util.gid(value) and is == number end false end def sync found = false @should.each do |value| if number = Puppet::Util.gid(value) provider.gid = number found = true break end end fail _("Could not find group(s) %{groups}") % { groups: @should.join(",") } unless found # Use the default event. end end newproperty(:comment) do desc "A description of the user. Generally the user's full name." def insync?(is) # nameservice provider requires special attention to encoding # Overrides Puppet::Property#insync? if !@should.empty? && provider.respond_to?(:comments_insync?) return provider.comments_insync?(is, @should) end super(is) end # In the case that our comments have incompatible encodings, set external # encoding to support concatenation for display. # overrides Puppet::Property#change_to_s def change_to_s(currentvalue, newvalue) if newvalue.respond_to?(:force_encoding) && !Encoding.compatible?(currentvalue, newvalue) return super(currentvalue, newvalue.dup.force_encoding(currentvalue.encoding)) end super(currentvalue, newvalue) end end newproperty(:shell, :required_features => :manages_shell) do desc "The user's login shell. The shell must exist and be executable. This attribute cannot be managed on Windows systems." end newproperty(:password, :required_features => :manages_passwords) do desc %q{The user's password, in whatever encrypted format the local system requires. Consult your operating system's documentation for acceptable password encryption formats and requirements. * Mac OS X 10.5 and 10.6, and some older Linux distributions, use salted SHA1 hashes. You can use Puppet's built-in `sha1` function to generate a salted SHA1 hash from a password. * Mac OS X 10.7 (Lion), and many recent Linux distributions, use salted SHA512 hashes. The Puppet Labs [stdlib][] module contains a `str2saltedsha512` function which can generate password hashes for these operating systems. * OS X 10.8 and higher use salted SHA512 PBKDF2 hashes. When managing passwords on these systems, the `salt` and `iterations` attributes need to be specified as well as the password. * Windows passwords can be managed only in cleartext, because there is no Windows API for setting the password hash. [stdlib]: https://github.com/puppetlabs/puppetlabs-stdlib/ Enclose any value that includes a dollar sign ($) in single quotes (') to avoid accidental variable interpolation. To redact passwords from reports to PuppetDB, use the `Sensitive` data type. For example, this resource protects the password: ```puppet user { 'foo': ensure => present, password => Sensitive("my secret password") } ``` This results in the password being redacted from the report, as in the `previous_value`, `desired_value`, and `message` fields below. ```yaml events: - !ruby/object:Puppet::Transaction::Event audited: false property: password previous_value: "[redacted]" desired_value: "[redacted]" historical_value: message: changed [redacted] to [redacted] name: :password_changed status: success time: 2017-05-17 16:06:02.934398293 -07:00 redacted: true corrective_change: false corrective_change: false ``` } validate do |value| raise ArgumentError, _("Passwords cannot include ':'") if value.is_a?(String) and value.include?(":") end def change_to_s(currentvalue, newvalue) if currentvalue == :absent return _("created password") else return _("changed password") end end def is_to_s( currentvalue ) return _('[old password hash redacted]') end def should_to_s( newvalue ) return _('[new password hash redacted]') end end newproperty(:password_min_age, :required_features => :manages_password_age) do desc "The minimum number of days a password must be used before it may be changed." munge do |value| case value when String Integer(value) else value end end validate do |value| if value.to_s !~ /^-?\d+$/ raise ArgumentError, _("Password minimum age must be provided as a number.") end end end newproperty(:password_max_age, :required_features => :manages_password_age) do desc "The maximum number of days a password may be used before it must be changed." munge do |value| case value when String Integer(value) else value end end validate do |value| if value.to_s !~ /^-?\d+$/ raise ArgumentError, _("Password maximum age must be provided as a number.") end end end newproperty(:password_warn_days, :required_features => :manages_password_age) do desc "The number of days before a password is going to expire (see the maximum password age) during which the user should be warned." munge do |value| case value when String Integer(value) else value end end validate do |value| if value.to_s !~ /^-?\d+$/ raise ArgumentError, "Password warning days must be provided as a number." end end end newproperty(:groups, :parent => Puppet::Property::List) do desc "The groups to which the user belongs. The primary group should not be listed, and groups should be identified by name rather than by GID. Multiple groups should be specified as an array." validate do |value| if value =~ /^\d+$/ raise ArgumentError, _("Group names must be provided, not GID numbers.") end raise ArgumentError, _("Group names must be provided as an array, not a comma-separated list.") if value.include?(",") raise ArgumentError, _("Group names must not be empty. If you want to specify \"no groups\" pass an empty array") if value.empty? end def change_to_s(currentvalue, newvalue) newvalue = newvalue.split(",") if newvalue != :absent if provider.respond_to?(:groups_to_s) # for Windows ADSI # de-dupe the "newvalue" when the sync event message is generated, # due to final retrieve called after the resource has been modified newvalue = provider.groups_to_s(newvalue).split(',').uniq end super(currentvalue, newvalue) end # override Puppet::Property::List#retrieve def retrieve if provider.respond_to?(:groups_to_s) # Windows ADSI groups returns SIDs, but retrieve needs names # must return qualified names for SIDs for "is" value and puppet resource return provider.groups_to_s(provider.groups).split(',') end super end def insync?(current) if provider.respond_to?(:groups_insync?) return provider.groups_insync?(current, @should) end super(current) end end newparam(:name) do desc "The user name. While naming limitations vary by operating system, it is advisable to restrict names to the lowest common denominator, which is a maximum of 8 characters beginning with a letter. Note that Puppet considers user names to be case-sensitive, regardless of the platform's own rules; be sure to always use the same case when referring to a given user." isnamevar end newparam(:membership) do desc "If `minimum` is specified, Puppet will ensure that the user is a member of all specified groups, but will not remove any other groups that the user is a part of. If `inclusive` is specified, Puppet will ensure that the user is a member of **only** specified groups." newvalues(:inclusive, :minimum) defaultto :minimum end newparam(:system, :boolean => true, :parent => Puppet::Parameter::Boolean) do desc "Whether the user is a system user, according to the OS's criteria; on most platforms, a UID less than or equal to 500 indicates a system user. This parameter is only used when the resource is created and will not affect the UID when the user is present." defaultto false end newparam(:allowdupe, :boolean => true, :parent => Puppet::Parameter::Boolean) do desc "Whether to allow duplicate UIDs." defaultto false end newparam(:managehome, :boolean => true, :parent => Puppet::Parameter::Boolean) do desc "Whether to manage the home directory when Puppet creates or removes the user. This creates the home directory if Puppet also creates the user account, and deletes the home directory if Puppet also removes the user account. This parameter has no effect unless Puppet is also creating or removing the user in the resource at the same time. For instance, Puppet creates a home directory for a managed user if `ensure => present` and the user does not exist at the time of the Puppet run. If the home directory is then deleted manually, Puppet will not recreate it on the next run. Note that on Windows, this manages creation/deletion of the user profile instead of the home directory. The user profile is stored in the `C:\Users\<username>` directory." defaultto false validate do |val| if munge(val) raise ArgumentError, _("User provider %{name} can not manage home directories") % { name: provider.class.name } if provider and not provider.class.manages_homedir? end end end newproperty(:expiry, :required_features => :manages_expiry) do desc "The expiry date for this user. Provide as either the special value `absent` to ensure that the account never expires, or as a zero-padded YYYY-MM-DD format -- for example, 2010-02-19." newvalues :absent newvalues(/^\d{4}-\d{2}-\d{2}$/) validate do |value| if value.intern != :absent and value !~ /^\d{4}-\d{2}-\d{2}$/ #TRANSLATORS YYYY-MM-DD represents a date with a four-digit year, a two-digit month, and a two-digit day, #TRANSLATORS separated by dashes. raise ArgumentError, _("Expiry dates must be YYYY-MM-DD or the string \"absent\"") end end end # Autorequire the group, if it's around autorequire(:group) do autos = [] if obj = @parameters[:gid] and groups = obj.shouldorig groups = groups.collect { |group| if group =~ /^\d+$/ Integer(group) else group end } groups.each { |group| case group when Integer if resource = catalog.resources.find { |r| r.is_a?(Puppet::Type.type(:group)) and r.should(:gid) == group } autos << resource end else autos << group end } end if obj = @parameters[:groups] and groups = obj.should autos += groups.split(",") end autos end # This method has been exposed for puppet to manage users and groups of # files in its settings and should not be considered available outside of # puppet. # # (see Puppet::Settings#service_user_available?) # # @return [Boolean] if the user exists on the system # @api private def exists? provider.exists? end def retrieve absent = false properties.inject({}) { |prophash, property| current_value = :absent if absent prophash[property] = :absent else current_value = property.retrieve prophash[property] = current_value end if property.name == :ensure and current_value == :absent absent = true end prophash } end newproperty(:roles, :parent => Puppet::Property::List, :required_features => :manages_solaris_rbac) do desc "The roles the user has. Multiple roles should be specified as an array." def membership :role_membership end validate do |value| if value =~ /^\d+$/ raise ArgumentError, _("Role names must be provided, not numbers") end raise ArgumentError, _("Role names must be provided as an array, not a comma-separated list") if value.include?(",") end end #autorequire the roles that the user has autorequire(:user) do reqs = [] if roles_property = @parameters[:roles] and roles = roles_property.should reqs += roles.split(',') end reqs end newparam(:role_membership) do desc "Whether specified roles should be considered the **complete list** (`inclusive`) or the **minimum list** (`minimum`) of roles the user has." newvalues(:inclusive, :minimum) defaultto :minimum end newproperty(:auths, :parent => Puppet::Property::List, :required_features => :manages_solaris_rbac) do desc "The auths the user has. Multiple auths should be specified as an array." def membership :auth_membership end validate do |value| if value =~ /^\d+$/ raise ArgumentError, _("Auth names must be provided, not numbers") end raise ArgumentError, _("Auth names must be provided as an array, not a comma-separated list") if value.include?(",") end end newparam(:auth_membership) do desc "Whether specified auths should be considered the **complete list** (`inclusive`) or the **minimum list** (`minimum`) of auths the user has." newvalues(:inclusive, :minimum) defaultto :minimum end newproperty(:profiles, :parent => Puppet::Property::OrderedList, :required_features => :manages_solaris_rbac) do desc "The profiles the user has. Multiple profiles should be specified as an array." def membership :profile_membership end validate do |value| if value =~ /^\d+$/ raise ArgumentError, _("Profile names must be provided, not numbers") end raise ArgumentError, _("Profile names must be provided as an array, not a comma-separated list") if value.include?(",") end end newparam(:profile_membership) do desc "Whether specified roles should be treated as the **complete list** (`inclusive`) or the **minimum list** (`minimum`) of roles of which the user is a member." newvalues(:inclusive, :minimum) defaultto :minimum end newproperty(:keys, :parent => Puppet::Property::KeyValue, :required_features => :manages_solaris_rbac) do desc "Specify user attributes in an array of key = value pairs." def membership :key_membership end end newparam(:key_membership) do desc "Whether specified key/value pairs should be considered the **complete list** (`inclusive`) or the **minimum list** (`minimum`) of the user's attributes." newvalues(:inclusive, :minimum) defaultto :minimum end newproperty(:project, :required_features => :manages_solaris_rbac) do desc "The name of the project associated with a user." end newparam(:ia_load_module, :required_features => :manages_aix_lam) do desc "The name of the I&A module to use to manage this user." end newproperty(:attributes, :parent => Puppet::Property::KeyValue, :required_features => :manages_aix_lam) do desc "Specify AIX attributes for the user in an array of attribute = value pairs." self.log_only_changed_or_new_keys = true def membership :attribute_membership end def delimiter " " end end newparam(:attribute_membership) do desc "Whether specified attribute value pairs should be treated as the **complete list** (`inclusive`) or the **minimum list** (`minimum`) of attribute/value pairs for the user." newvalues(:inclusive, :minimum) defaultto :minimum end newproperty(:salt, :required_features => :manages_password_salt) do desc "This is the 32-byte salt used to generate the PBKDF2 password used in OS X. This field is required for managing passwords on OS X >= 10.8." end newproperty(:iterations, :required_features => :manages_password_salt) do desc "This is the number of iterations of a chained computation of the [PBKDF2 password hash](https://en.wikipedia.org/wiki/PBKDF2). This parameter is used in OS X, and is required for managing passwords on OS X 10.8 and newer." munge do |value| if value.is_a?(String) and value =~/^[-0-9]+$/ Integer(value) else value end end end newparam(:forcelocal, :boolean => true, :required_features => :libuser, :parent => Puppet::Parameter::Boolean) do desc "Forces the management of local accounts when accounts are also being managed by some other NSS" defaultto false end def generate return [] if self[:purge_ssh_keys].empty? find_unmanaged_keys end newparam(:purge_ssh_keys) do desc "Whether to purge authorized SSH keys for this user if they are not managed with the `ssh_authorized_key` resource type. Allowed values are: * `false` (default) --- don't purge SSH keys for this user. * `true` --- look for keys in the `.ssh/authorized_keys` file in the user's home directory. Purge any keys that aren't managed as `ssh_authorized_key` resources. * An array of file paths --- look for keys in all of the files listed. Purge any keys that aren't managed as `ssh_authorized_key` resources. If any of these paths starts with `~` or `%h`, that token will be replaced with the user's home directory." defaultto :false # Use Symbols instead of booleans until PUP-1967 is resolved. newvalues(:true, :false) validate do |value| if [ :true, :false ].include? value.to_s.intern return end value = [ value ] if value.is_a?(String) if value.is_a?(Array) value.each do |entry| raise ArgumentError, _("Each entry for purge_ssh_keys must be a string, not a %{klass}") % { klass: entry.class } unless entry.is_a?(String) valid_home = Puppet::Util.absolute_path?(entry) || entry =~ %r{^~/|^%h/} raise ArgumentError, _("Paths to keyfiles must be absolute, not %{entry}") % { entry: entry } unless valid_home end return end raise ArgumentError, _("purge_ssh_keys must be true, false, or an array of file names, not %{value}") % { value: value.inspect } end munge do |value| # Resolve string, boolean and symbol forms of true and false to a # single representation. test_sym = value.to_s.intern value = test_sym if [:true, :false].include? test_sym return [] if value == :false home = resource[:home] if value == :true and not home raise ArgumentError, _("purge_ssh_keys can only be true for users with a defined home directory") end return [ "#{home}/.ssh/authorized_keys" ] if value == :true # value is an array - munge each value [ value ].flatten.map do |entry| if entry =~ /^~|^%h/ and not home raise ArgumentError, _("purge_ssh_keys value '%{value}' meta character ~ or %{home_placeholder} only allowed for users with a defined home directory") % { value: value, home_placeholder: '%h' } end entry.gsub!(/^~\//, "#{home}/") entry.gsub!(/^%h\//, "#{home}/") entry end end end newproperty(:loginclass, :required_features => :manages_loginclass) do desc "The name of login class to which the user belongs." validate do |value| if value =~ /^\d+$/ raise ArgumentError, _("Class name must be provided.") end end end # Generate ssh_authorized_keys resources for purging. The key files are # taken from the purge_ssh_keys parameter. The generated resources inherit # all metaparameters from the parent user resource. # # @return [Array<Puppet::Type::Ssh_authorized_key] a list of resources # representing the found keys # @see generate # @api private def find_unmanaged_keys self[:purge_ssh_keys]. select { |f| File.readable?(f) }. map { |f| unknown_keys_in_file(f) }. flatten.each do |res| res[:ensure] = :absent res[:user] = self[:name] @parameters.each do |name, param| res[name] = param.value if param.metaparam? end end end # Parse an ssh authorized keys file superficially, extract the comments # on the keys. These are considered names of possible ssh_authorized_keys # resources. Keys that are managed by the present catalog are ignored. # # @see generate # @api private # @return [Array<Puppet::Type::Ssh_authorized_key] a list of resources # representing the found keys def unknown_keys_in_file(keyfile) names = [] name_index = 0 # RFC 4716 specifies UTF-8 allowed in public key files per https://www.ietf.org/rfc/rfc4716.txt # the authorized_keys file may contain UTF-8 comments Puppet::FileSystem.open(keyfile, nil, 'r:UTF-8').each do |line| next unless line =~ Puppet::Type.type(:ssh_authorized_key).keyline_regex # the name is stored in the 4th capture of the regex name = $4 if name.empty? $3.delete("\n") # If no comment is specified for this key, generate a unique internal # name. This uses the same rules as # provider/ssh_authorized_key/parsed (PUP-3357) name = "#{keyfile}:unnamed-#{name_index += 1}" end names << name Puppet.debug "#{self.ref} parsed for purging Ssh_authorized_key[#{name}]" end names.map { |keyname| Puppet::Type.type(:ssh_authorized_key).new( :name => keyname, :target => keyfile) }.reject { |res| catalog.resource_refs.include? res.ref } end end end ��������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/vlan.rb���������������������������������������������������������������0000644�0052762�0001160�00000000711�13417161722�017343� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# # Manages a Vlan on a given router or switch # Puppet::Type.newtype(:vlan) do @doc = "Manages a VLAN on a router or switch." apply_to_device ensurable newparam(:name) do desc "The numeric VLAN ID." isnamevar newvalues(/^\d+/) end newproperty(:description) do desc "The VLAN's name." end newparam(:device_url) do desc "The URL of the router or switch maintaining this VLAN." end end �������������������������������������������������������puppet-5.5.10/lib/puppet/type/yumrepo.rb������������������������������������������������������������0000644�0052762�0001160�00000027327�13417161722�020117� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'uri' Puppet::Type.newtype(:yumrepo) do @doc = "The client-side description of a yum repository. Repository configurations are found by parsing `/etc/yum.conf` and the files indicated by the `reposdir` option in that file (see `yum.conf(5)` for details). Most parameters are identical to the ones documented in the `yum.conf(5)` man page. Continuation lines that yum supports (for the `baseurl`, for example) are not supported. This type does not attempt to read or verify the existence of files listed in the `include` attribute." # Ensure yumrepos can be removed too. ensurable # Doc string for properties that can be made 'absent' ABSENT_DOC="Set this to `absent` to remove it from the file completely." # False can be false/0/no and True can be true/1/yes in yum. YUM_BOOLEAN=/^(true|false|0|1|no|yes)$/ YUM_BOOLEAN_DOC="Valid values are: false/0/no or true/1/yes." # Common munge logic for YUM_BOOLEAN values. Munges for two requirements: # 1) Because of how regex validation works in Puppet::Parameter::Value, # Boolean false and lowercase false will not be considered invalid. However, # if the user specified false (or true), they meant False (or True). # 2) In order for parameter removal to work correctly, when absent is passed # as a string it needs to be munged back to a symbol. munge_yum_bool = Proc.new do |val| val.to_s == 'absent' ? :absent : val.to_s.capitalize end VALID_SCHEMES = %w[file http https ftp] newparam(:name, :namevar => true) do desc "The name of the repository. This corresponds to the `repositoryid` parameter in `yum.conf(5)`." end newparam(:target) do desc "The target parameter will be enabled in a future release and should not be used." defaultto :absent end newproperty(:descr) do desc "A human-readable description of the repository. This corresponds to the name parameter in `yum.conf(5)`. #{ABSENT_DOC}" newvalues(/.*/, :absent) end newproperty(:mirrorlist) do desc "The URL that holds the list of mirrors for this repository. #{ABSENT_DOC}" newvalues(/.*/, :absent) validate do |value| next if value.to_s == 'absent' parsed = URI.parse(value) unless VALID_SCHEMES.include?(parsed.scheme) raise _("Must be a valid URL") end end end newproperty(:baseurl) do desc "The URL for this repository. #{ABSENT_DOC}" newvalues(/.*/, :absent) validate do |value| next if value.to_s == 'absent' value.split(/\s+/).each do |uri| parsed = URI.parse(uri) unless VALID_SCHEMES.include?(parsed.scheme) raise _("Must be a valid URL") end end end end newproperty(:enabled) do desc "Whether this repository is enabled. #{YUM_BOOLEAN_DOC} #{ABSENT_DOC}" newvalues(YUM_BOOLEAN, :absent) munge(&munge_yum_bool) end newproperty(:gpgcheck) do desc "Whether to check the GPG signature on packages installed from this repository. #{YUM_BOOLEAN_DOC} #{ABSENT_DOC}" newvalues(YUM_BOOLEAN, :absent) munge(&munge_yum_bool) end newproperty(:payload_gpgcheck) do desc "Whether to check the GPG signature of the packages payload. #{YUM_BOOLEAN_DOC} #{ABSENT_DOC}" newvalues(YUM_BOOLEAN, :absent) munge(&munge_yum_bool) end newproperty(:repo_gpgcheck) do desc "Whether to check the GPG signature on repodata. #{YUM_BOOLEAN_DOC} #{ABSENT_DOC}" newvalues(YUM_BOOLEAN, :absent) munge(&munge_yum_bool) end newproperty(:gpgkey) do desc "The URL for the GPG key with which packages from this repository are signed. #{ABSENT_DOC}" newvalues(/.*/, :absent) validate do |value| next if value.to_s == 'absent' value.split(/\s+/).each do |uri| parsed = URI.parse(uri) unless VALID_SCHEMES.include?(parsed.scheme) raise _("Must be a valid URL") end end end end newproperty(:mirrorlist_expire) do desc "Time (in seconds) after which the mirrorlist locally cached will expire.\n#{ABSENT_DOC}" newvalues(/^[0-9]+$/, :absent) end newproperty(:include) do desc "The URL of a remote file containing additional yum configuration settings. Puppet does not check for this file's existence or validity. #{ABSENT_DOC}" newvalues(/.*/, :absent) validate do |value| next if value.to_s == 'absent' parsed = URI.parse(value) unless VALID_SCHEMES.include?(parsed.scheme) raise _("Must be a valid URL") end end end newproperty(:exclude) do desc "The string of package names or shell globs separated by spaces to exclude. Packages that match the package name given or shell globs will never be considered in updates or installs for this repo. #{ABSENT_DOC}" newvalues(/.*/, :absent) end newproperty(:gpgcakey) do desc "The URL for the GPG CA key for this repository. #{ABSENT_DOC}" newvalues(/.*/, :absent) validate do |value| next if value.to_s == 'absent' parsed = URI.parse(value) unless VALID_SCHEMES.include?(parsed.scheme) raise _("Must be a valid URL") end end end newproperty(:includepkgs) do desc "The string of package names or shell globs separated by spaces to include. If this is set, only packages matching one of the package names or shell globs will be considered for update or install from this repository. #{ABSENT_DOC}" newvalues(/.*/, :absent) end newproperty(:enablegroups) do desc "Whether yum will allow the use of package groups for this repository. #{YUM_BOOLEAN_DOC} #{ABSENT_DOC}" newvalues(YUM_BOOLEAN, :absent) munge(&munge_yum_bool) end newproperty(:failovermethod) do desc "The failover method for this repository; should be either `roundrobin` or `priority`. #{ABSENT_DOC}" newvalues(/^roundrobin|priority$/, :absent) end newproperty(:keepalive) do desc "Whether HTTP/1.1 keepalive should be used with this repository. #{YUM_BOOLEAN_DOC} #{ABSENT_DOC}" newvalues(YUM_BOOLEAN, :absent) munge(&munge_yum_bool) end newproperty(:retries) do desc "Set the number of times any attempt to retrieve a file should retry before returning an error. Setting this to `0` makes yum try forever.\n#{ABSENT_DOC}" newvalues(/^[0-9]+$/, :absent) end newproperty(:http_caching) do desc "What to cache from this repository. #{ABSENT_DOC}" newvalues(/^(packages|all|none)$/, :absent) end newproperty(:timeout) do desc "Number of seconds to wait for a connection before timing out. #{ABSENT_DOC}" newvalues(/^\d+$/, :absent) end newproperty(:metadata_expire) do desc "Number of seconds after which the metadata will expire. #{ABSENT_DOC}" newvalues(/^([0-9]+[dhm]?|never)$/, :absent) end newproperty(:protect) do desc "Enable or disable protection for this repository. Requires that the `protectbase` plugin is installed and enabled. #{YUM_BOOLEAN_DOC} #{ABSENT_DOC}" newvalues(YUM_BOOLEAN, :absent) munge(&munge_yum_bool) end newproperty(:priority) do desc "Priority of this repository. Can be any integer value (including negative). Requires that the `priorities` plugin is installed and enabled. #{ABSENT_DOC}" newvalues(/^-?\d+$/, :absent) end newproperty(:throttle) do desc "Enable bandwidth throttling for downloads. This option can be expressed as a absolute data rate in bytes/sec or a percentage `60%`. An SI prefix (k, M or G) may be appended to the data rate values.#{ABSENT_DOC}" newvalues(/^\d+[kMG%]?$/, :absent) end newproperty(:bandwidth) do desc "Use to specify the maximum available network bandwidth in bytes/second. Used with the `throttle` option. If `throttle` is a percentage and `bandwidth` is `0` then bandwidth throttling will be disabled. If `throttle` is expressed as a data rate then this option is ignored.#{ABSENT_DOC}" newvalues(/^\d+[kMG]?$/, :absent) end newproperty(:cost) do desc "Cost of this repository. #{ABSENT_DOC}" newvalues(/^\d+$/, :absent) end newproperty(:proxy) do desc "URL of a proxy server that Yum should use when accessing this repository. This attribute can also be set to `'_none_'`, which will make Yum bypass any global proxy settings when accessing this repository. #{ABSENT_DOC}" newvalues(/.*/, :absent) validate do |value| next if value.to_s =~ /^(absent|_none_)$/ parsed = URI.parse(value) unless VALID_SCHEMES.include?(parsed.scheme) raise _("Must be a valid URL") end end end newproperty(:proxy_username) do desc "Username for this proxy. #{ABSENT_DOC}" newvalues(/.*/, :absent) end newproperty(:proxy_password) do desc "Password for this proxy. #{ABSENT_DOC}" newvalues(/.*/, :absent) end newproperty(:s3_enabled) do desc "Access the repository via S3. #{YUM_BOOLEAN_DOC} #{ABSENT_DOC}" newvalues(YUM_BOOLEAN, :absent) munge(&munge_yum_bool) end newproperty(:sslcacert) do desc "Path to the directory containing the databases of the certificate authorities yum should use to verify SSL certificates. #{ABSENT_DOC}" newvalues(/.*/, :absent) end newproperty(:sslverify) do desc "Should yum verify SSL certificates/hosts at all. #{YUM_BOOLEAN_DOC} #{ABSENT_DOC}" newvalues(YUM_BOOLEAN, :absent) munge(&munge_yum_bool) end newproperty(:sslclientcert) do desc "Path to the SSL client certificate yum should use to connect to repositories/remote sites. #{ABSENT_DOC}" newvalues(/.*/, :absent) end newproperty(:sslclientkey) do desc "Path to the SSL client key yum should use to connect to repositories/remote sites. #{ABSENT_DOC}" newvalues(/.*/, :absent) end newproperty(:metalink) do desc "Metalink for mirrors. #{ABSENT_DOC}" newvalues(/.*/, :absent) validate do |value| next if value.to_s == 'absent' parsed = URI.parse(value) unless VALID_SCHEMES.include?(parsed.scheme) raise _("Must be a valid URL") end end end newproperty(:skip_if_unavailable) do desc "Should yum skip this repository if unable to reach it. #{YUM_BOOLEAN_DOC} #{ABSENT_DOC}" newvalues(YUM_BOOLEAN, :absent) munge(&munge_yum_bool) end newproperty(:assumeyes) do desc "Determines if yum prompts for confirmation of critical actions. #{YUM_BOOLEAN_DOC} #{ABSENT_DOC}" newvalues(YUM_BOOLEAN, :absent) munge(&munge_yum_bool) end newproperty(:deltarpm_percentage) do desc "Percentage value that determines when to use deltas for this repository. When the delta is larger than this percentage value of the package, the delta is not used. #{ABSENT_DOC}" newvalues(/^\d+$/, :absent) end newproperty(:deltarpm_metadata_percentage) do desc "Percentage value that determines when to download deltarpm metadata. When the deltarpm metadata is larger than this percentage value of the package, deltarpm metadata is not downloaded. #{ABSENT_DOC}" newvalues(/^\d+$/, :absent) end newproperty(:username) do desc "Username to use for basic authentication to a repo or really any url. #{ABSENT_DOC}" newvalues(/.*/, :absent) end newproperty(:password) do desc "Password to use with the username for basic authentication. #{ABSENT_DOC}" newvalues(/.*/, :absent) end private def set_sensitive_parameters(sensitive_parameters) parameter(:password).sensitive = true if parameter(:password) super(sensitive_parameters) end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/zfs.rb����������������������������������������������������������������0000644�0052762�0001160�00000010722�13417161722�017210� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet Type.newtype(:zfs) do @doc = "Manage zfs. Create destroy and set properties on zfs instances. **Autorequires:** If Puppet is managing the zpool at the root of this zfs instance, the zfs resource will autorequire it. If Puppet is managing any parent zfs instances, the zfs resource will autorequire them." ensurable newparam(:name) do desc "The full name for this filesystem (including the zpool)." end newproperty(:aclinherit) do desc "The aclinherit property. Valid values are `discard`, `noallow`, `restricted`, `passthrough`, `passthrough-x`." end newproperty(:aclmode) do desc "The aclmode property. Valid values are `discard`, `groupmask`, `passthrough`." end newproperty(:acltype) do desc "The acltype propery. Valid values are 'noacl' and 'posixacl'. Only supported on Linux." end newproperty(:atime) do desc "The atime property. Valid values are `on`, `off`." end newproperty(:canmount) do desc "The canmount property. Valid values are `on`, `off`, `noauto`." end newproperty(:checksum) do desc "The checksum property. Valid values are `on`, `off`, `fletcher2`, `fletcher4`, `sha256`." end newproperty(:compression) do desc "The compression property. Valid values are `on`, `off`, `lzjb`, `gzip`, `gzip-[1-9]`, `zle`." end newproperty(:copies) do desc "The copies property. Valid values are `1`, `2`, `3`." end newproperty(:dedup) do desc "The dedup property. Valid values are `on`, `off`." end newproperty(:devices) do desc "The devices property. Valid values are `on`, `off`." end newproperty(:exec) do desc "The exec property. Valid values are `on`, `off`." end newproperty(:logbias) do desc "The logbias property. Valid values are `latency`, `throughput`." end newproperty(:mountpoint) do desc "The mountpoint property. Valid values are `<path>`, `legacy`, `none`." end newproperty(:nbmand) do desc "The nbmand property. Valid values are `on`, `off`." end newproperty(:primarycache) do desc "The primarycache property. Valid values are `all`, `none`, `metadata`." end newproperty(:quota) do desc "The quota property. Valid values are `<size>`, `none`." end newproperty(:readonly) do desc "The readonly property. Valid values are `on`, `off`." end newproperty(:recordsize) do desc "The recordsize property. Valid values are powers of two between 512 and 128k." end newproperty(:refquota) do desc "The refquota property. Valid values are `<size>`, `none`." end newproperty(:refreservation) do desc "The refreservation property. Valid values are `<size>`, `none`." end newproperty(:reservation) do desc "The reservation property. Valid values are `<size>`, `none`." end newproperty(:secondarycache) do desc "The secondarycache property. Valid values are `all`, `none`, `metadata`." end newproperty(:setuid) do desc "The setuid property. Valid values are `on`, `off`." end newproperty(:shareiscsi) do desc "The shareiscsi property. Valid values are `on`, `off`, `type=<type>`." end newproperty(:sharenfs) do desc "The sharenfs property. Valid values are `on`, `off`, share(1M) options" end newproperty(:sharesmb) do desc "The sharesmb property. Valid values are `on`, `off`, sharemgr(1M) options" end newproperty(:snapdir) do desc "The snapdir property. Valid values are `hidden`, `visible`." end newproperty(:version) do desc "The version property. Valid values are `1`, `2`, `3`, `4`, `current`." end newproperty(:volsize) do desc "The volsize property. Valid values are `<size>`" end newproperty(:vscan) do desc "The vscan property. Valid values are `on`, `off`." end newproperty(:xattr) do desc "The xattr property. Valid values are `on`, `off`." end newproperty(:zoned) do desc "The zoned property. Valid values are `on`, `off`." end autorequire(:zpool) do #strip the zpool off the zfs name and autorequire it [@parameters[:name].value.split('/')[0]] end autorequire(:zfs) do #slice and dice, we want all the zfs before this one names = @parameters[:name].value.split('/') names.slice(1..-2).inject([]) { |a,v| a << "#{a.last}/#{v}" }.collect { |fs| names[0] + fs } end end end ����������������������������������������������puppet-5.5.10/lib/puppet/type/zone.rb���������������������������������������������������������������0000644�0052762�0001160�00000026447�13417161722�017374� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/property/list' Puppet::Type.newtype(:zone) do @doc = "Manages Solaris zones. **Autorequires:** If Puppet is managing the directory specified as the root of the zone's filesystem (with the `path` attribute), the zone resource will autorequire that directory." module Puppet::Zone class StateMachine # A silly little state machine. def initialize @state = {} @sequence = [] @state_aliases = {} @default = nil end # The order of calling insert_state is important def insert_state(name, transitions) @sequence << name @state[name] = transitions end def alias_state(state, salias) @state_aliases[state] = salias end def name(n) @state_aliases[n.to_sym] || n.to_sym end def index(state) @sequence.index(name(state)) end # return all states between fs and ss excluding fs def sequence(fs, ss) fi = index(fs) si= index(ss) (if fi > si then @sequence[si .. fi].map{|i| @state[i]}.reverse else @sequence[fi .. si].map{|i| @state[i]} end)[1..-1] end def cmp?(a,b) index(a) < index(b) end end end ensurable do desc "The running state of the zone. The valid states directly reflect the states that `zoneadm` provides. The states are linear, in that a zone must be `configured`, then `installed`, and only then can be `running`. Note also that `halt` is currently used to stop zones." def self.fsm return @fsm if @fsm @fsm = Puppet::Zone::StateMachine.new end def self.alias_state(values) values.each do |k,v| fsm.alias_state(k,v) end end def self.seqvalue(name, hash) fsm.insert_state(name, hash) self.newvalue name end # This is seq value because the order of declaration is important. # i.e we go linearly from :absent -> :configured -> :installed -> :running seqvalue :absent, :down => :destroy seqvalue :configured, :up => :configure, :down => :uninstall seqvalue :installed, :up => :install, :down => :stop seqvalue :running, :up => :start alias_state :incomplete => :installed, :ready => :installed, :shutting_down => :running defaultto :running def self.state_sequence(first, second) fsm.sequence(first, second) end # Why override it? because property/ensure.rb has a default retrieve method # that knows only about :present and :absent. That method just calls # provider.exists? and returns :present if a result was returned. def retrieve provider.properties[:ensure] end def provider_sync_send(method) warned = false while provider.processing? next if warned info _("Waiting for zone to finish processing") warned = true sleep 1 end provider.send(method) provider.flush() end def sync method = nil direction = up? ? :up : :down # We need to get the state we're currently in and just call # everything between it and us. self.class.state_sequence(self.retrieve, self.should).each do |state| method = state[direction] raise Puppet::DevError, _("Cannot move %{direction} from %{name}") % { direction: direction, name: st[:name] } unless method provider_sync_send(method) end ("zone_#{self.should}").intern end # Are we moving up the property tree? def up? self.class.fsm.cmp?(self.retrieve, self.should) end end newparam(:name) do desc "The name of the zone." isnamevar end newparam(:id) do desc "The numerical ID of the zone. This number is autogenerated and cannot be changed." end newparam(:clone) do desc "Instead of installing the zone, clone it from another zone. If the zone root resides on a zfs file system, a snapshot will be used to create the clone; if it resides on a ufs filesystem, a copy of the zone will be used. The zone from which you clone must not be running." end newproperty(:ip, :parent => Puppet::Property::List) do require 'ipaddr' desc "The IP address of the zone. IP addresses **must** be specified with an interface, and may optionally be specified with a default router (sometimes called a defrouter). The interface, IP address, and default router should be separated by colons to form a complete IP address string. For example: `bge0:192.168.178.200` would be a valid IP address string without a default router, and `bge0:192.168.178.200:192.168.178.1` adds a default router to it. For zones with multiple interfaces, the value of this attribute should be an array of IP address strings (each of which must include an interface and may include a default router)." # The default action of list should is to lst.join(' '). By specifying # @should, we ensure the should remains an array. If we override should, we # should also override insync?() -- property/list.rb def should @should end # overridden so that we match with self.should def insync?(is) is = [] if !is || is == :absent is.sort == self.should.sort end end newproperty(:iptype) do desc "The IP stack type of the zone." defaultto :shared newvalue :shared newvalue :exclusive end newproperty(:autoboot, :boolean => true) do desc "Whether the zone should automatically boot." defaultto true newvalues(:true, :false) end newproperty(:path) do desc "The root of the zone's filesystem. Must be a fully qualified file name. If you include `%s` in the path, then it will be replaced with the zone's name. Currently, you cannot use Puppet to move a zone. Consequently this is a readonly property." validate do |value| raise ArgumentError, _("The zone base must be fully qualified") unless value =~ /^\// end munge do |value| if value =~ /%s/ value % @resource[:name] else value end end end newproperty(:pool) do desc "The resource pool for this zone." end newproperty(:shares) do desc "Number of FSS CPU shares allocated to the zone." end newproperty(:dataset, :parent => Puppet::Property::List ) do desc "The list of datasets delegated to the non-global zone from the global zone. All datasets must be zfs filesystem names which are different from the mountpoint." def should @should end # overridden so that we match with self.should def insync?(is) is = [] if !is || is == :absent is.sort == self.should.sort end validate do |value| unless value !~ /^\// raise ArgumentError, _("Datasets must be the name of a zfs filesystem") end end end newproperty(:inherit, :parent => Puppet::Property::List) do desc "The list of directories that the zone inherits from the global zone. All directories must be fully qualified." def should @should end # overridden so that we match with self.should def insync?(is) is = [] if !is || is == :absent is.sort == self.should.sort end validate do |value| unless value =~ /^\// raise ArgumentError, _("Inherited filesystems must be fully qualified") end end end # Specify the sysidcfg file. This is pretty hackish, because it's # only used to boot the zone the very first time. newparam(:sysidcfg) do desc "The text to go into the `sysidcfg` file when the zone is first booted. The best way is to use a template: # $confdir/modules/site/templates/sysidcfg.erb system_locale=en_US timezone=GMT terminal=xterms security_policy=NONE root_password=<%= password %> timeserver=localhost name_service=DNS {domain_name=<%= domain %> name_server=<%= nameserver %>} network_interface=primary {hostname=<%= realhostname %> ip_address=<%= ip %> netmask=<%= netmask %> protocol_ipv6=no default_route=<%= defaultroute %>} nfs4_domain=dynamic And then call that: zone { 'myzone': ip => 'bge0:192.168.0.23', sysidcfg => template('site/sysidcfg.erb'), path => '/opt/zones/myzone', realhostname => 'fully.qualified.domain.name', } The `sysidcfg` only matters on the first booting of the zone, so Puppet only checks for it at that time." end newparam(:create_args) do desc "Arguments to the `zonecfg` create command. This can be used to create branded zones." end newparam(:install_args) do desc "Arguments to the `zoneadm` install command. This can be used to create branded zones." end newparam(:realhostname) do desc "The actual hostname of the zone." end # If Puppet is also managing the base dir or its parent dir, list them # both as prerequisites. autorequire(:file) do if @parameters.include? :path [@parameters[:path].value, ::File.dirname(@parameters[:path].value)] else nil end end # If Puppet is also managing the zfs filesystem which is the zone dataset # then list it as a prerequisite. Zpool's get autorequired by the zfs # type. We just need to autorequire the dataset zfs itself as the zfs type # will autorequire all of the zfs parents and zpool. autorequire(:zfs) do # Check if we have datasets in our zone configuration and autorequire each dataset self[:dataset] if @parameters.include? :dataset end def validate_ip(ip, name) IPAddr.new(ip) if ip rescue ArgumentError self.fail Puppet::Error, _("'%{ip}' is an invalid %{name}") % { ip: ip, name: name }, $! end def validate_exclusive(interface, address, router) return if !interface.nil? and address.nil? self.fail _("only interface may be specified when using exclusive IP stack: %{interface}:%{address}") % { interface: interface, address: address } end def validate_shared(interface, address, router) self.fail _("ip must contain interface name and ip address separated by a \":\"") if interface.nil? or address.nil? [address, router].each do |ip| validate_ip(address, "IP address") unless ip.nil? end end validate do return unless self[:ip] # self[:ip] reflects the type passed from property:ip.should. If we # override it and pass @should, then we get an array here back. self[:ip].each do |ip| interface, address, router = ip.split(':') if self[:iptype] == :shared validate_shared(interface, address, router) else validate_exclusive(interface, address, router) end end end def retrieve provider.flush hash = provider.properties return setstatus(hash) unless hash.nil? or hash[:ensure] == :absent # Return all properties as absent. return Hash[properties.map{|p| [p, :absent]} ] end # Take the results of a listing and set everything appropriately. def setstatus(hash) prophash = {} hash.each do |param, value| next if param == :name case self.class.attrtype(param) when :property # Only try to provide values for the properties we're managing prop = self.property(param) prophash[prop] = value if prop else self[param] = value end end prophash end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/type/zpool.rb��������������������������������������������������������������0000644�0052762�0001160�00000005364�13417161722�017557� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet class Property class VDev < Property def flatten_and_sort(array) array = [array] unless array.is_a? Array array.collect { |a| a.split(' ') }.flatten.sort end def insync?(is) return @should == [:absent] if is == :absent flatten_and_sort(is) == flatten_and_sort(@should) end end class MultiVDev < VDev def insync?(is) return @should == [:absent] if is == :absent return false unless is.length == @should.length is.each_with_index { |list, i| return false unless flatten_and_sort(list) == flatten_and_sort(@should[i]) } #if we made it this far we are in sync true end end end Type.newtype(:zpool) do @doc = "Manage zpools. Create and delete zpools. The provider WILL NOT SYNC, only report differences. Supports vdevs with mirrors, raidz, logs and spares." ensurable newproperty(:disk, :array_matching => :all, :parent => Puppet::Property::VDev) do desc "The disk(s) for this pool. Can be an array or a space separated string." end newproperty(:mirror, :array_matching => :all, :parent => Puppet::Property::MultiVDev) do desc "List of all the devices to mirror for this pool. Each mirror should be a space separated string: mirror => [\"disk1 disk2\", \"disk3 disk4\"], " validate do |value| raise ArgumentError, _("mirror names must be provided as string separated, not a comma-separated list") if value.include?(",") end end newproperty(:raidz, :array_matching => :all, :parent => Puppet::Property::MultiVDev) do desc "List of all the devices to raid for this pool. Should be an array of space separated strings: raidz => [\"disk1 disk2\", \"disk3 disk4\"], " validate do |value| raise ArgumentError, _("raid names must be provided as string separated, not a comma-separated list") if value.include?(",") end end newproperty(:spare, :array_matching => :all, :parent => Puppet::Property::VDev) do desc "Spare disk(s) for this pool." end newproperty(:log, :array_matching => :all, :parent => Puppet::Property::VDev) do desc "Log disks for this pool. This type does not currently support mirroring of log disks." end newparam(:pool) do desc "The name for this pool." isnamevar end newparam(:raid_parity) do desc "Determines parity when using the `raidz` parameter." end validate do has_should = [:disk, :mirror, :raidz].select { |prop| self.should(prop) } self.fail _("You cannot specify %{multiple_props} on this type (only one)") % { multiple_props: has_should.join(" and ") } if has_should.length > 1 end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/����������������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�016057� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/at_fork.rb������������������������������������������������������������0000644�0052762�0001160�00000003024�13417161721�020023� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet' # A module for building AtFork handlers. These handlers are objects providing # pre/post fork callbacks modeled after those registered by the `pthread_atfork` # function. # Currently there are two AtFork handler implementations: # - a noop implementation used on all platforms except Solaris (and possibly # even there as a fallback) # - a Solaris implementation which ensures the forked process runs in a different # contract than the parent process. This is necessary for agent runs started by # the puppet agent service to be able to restart that service without being # killed in the process as a consequence of running in the same contract as the # service. module Puppet::Util::AtFork @handler_class = loop do if Facter.value(:operatingsystem) == 'Solaris' begin require 'puppet/util/at_fork/solaris' # using break to return a value from the loop block break Puppet::Util::AtFork::Solaris rescue LoadError => detail Puppet.log_exception(detail, _('Failed to load Solaris implementation of the Puppet::Util::AtFork handler. Child process contract management will be unavailable, which means that agent runs executed by the puppet agent service will be killed when they attempt to restart the service.')) # fall through to use the no-op implementation end end require 'puppet/util/at_fork/noop' # using break to return a value from the loop block break Puppet::Util::AtFork::Noop end def self.get_handler @handler_class.new end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/at_fork/��������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�017504� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/at_fork/noop.rb�������������������������������������������������������0000644�0052762�0001160�00000000450�13417161721�020776� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# A noop implementation of the Puppet::Util::AtFork handler class Puppet::Util::AtFork::Noop class << self def new # no need to instantiate every time, return the class object itself self end def prepare end def parent end def child end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/at_fork/solaris.rb����������������������������������������������������0000644�0052762�0001160�00000011534�13417161721�021504� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet' require 'fiddle' # Early versions of Fiddle relied on the deprecated DL module and used # classes defined in the namespace of that module instead of classes defined # in the Fiddle's own namespace e.g. DL::Handle instead of Fiddle::Handle. # We don't support those. raise LoadError, _('The loaded Fiddle version is not supported.') unless defined?(Fiddle::Handle) # Solaris implementation of the Puppet::Util::AtFork handler. # The callbacks defined in this implementation ensure the forked process runs # in a different contract than the parent process. This is necessary in order # for the child process to be able to survive termination of the contract its # parent process runs in. This is needed notably for an agent run executed # by a puppet agent service to be able to restart that service without being # killed in the process as a consequence of running in the same contract as # the service, as all processes in the contract are killed when the contract # is terminated during the service restart. class Puppet::Util::AtFork::Solaris private { 'libcontract.so.1' => [ #function name, return value type, parameter types, ... [:ct_ctl_abandon, Fiddle::TYPE_INT, Fiddle::TYPE_INT], [:ct_tmpl_activate, Fiddle::TYPE_INT, Fiddle::TYPE_INT], [:ct_tmpl_clear, Fiddle::TYPE_INT, Fiddle::TYPE_INT], [:ct_tmpl_set_informative, Fiddle::TYPE_INT, Fiddle::TYPE_INT, Fiddle::TYPE_INT], [:ct_tmpl_set_critical, Fiddle::TYPE_INT, Fiddle::TYPE_INT, Fiddle::TYPE_INT], [:ct_pr_tmpl_set_param, Fiddle::TYPE_INT, Fiddle::TYPE_INT, Fiddle::TYPE_INT], [:ct_pr_tmpl_set_fatal, Fiddle::TYPE_INT, Fiddle::TYPE_INT, Fiddle::TYPE_INT], [:ct_status_read, Fiddle::TYPE_INT, Fiddle::TYPE_INT, Fiddle::TYPE_INT, Fiddle::TYPE_VOIDP], [:ct_status_get_id, Fiddle::TYPE_INT, Fiddle::TYPE_VOIDP], [:ct_status_free, Fiddle::TYPE_VOID, Fiddle::TYPE_VOIDP], ], }.each do |library, functions| libhandle = Fiddle::Handle.new(library) functions.each do |f| define_method f[0], Fiddle::Function.new(libhandle[f[0].to_s], f[2..-1], f[1]).method(:call).to_proc end end CTFS_PR_ROOT = File.join('', %w(system contract process)) CTFS_PR_TEMPLATE = File.join(CTFS_PR_ROOT, %q(template)) CTFS_PR_LATEST = File.join(CTFS_PR_ROOT, %q(latest)) CT_PR_PGRPONLY = 0x4 CT_PR_EV_HWERR = 0x20 CTD_COMMON = 0 def raise_if_error(&block) unless (e = yield) == 0 e = SystemCallError.new(nil, e) raise e, e.message, caller end end def activate_new_contract_template begin tmpl = File.open(CTFS_PR_TEMPLATE, File::RDWR) begin tmpl_fd = tmpl.fileno raise_if_error { ct_pr_tmpl_set_param(tmpl_fd, CT_PR_PGRPONLY) } raise_if_error { ct_pr_tmpl_set_fatal(tmpl_fd, CT_PR_EV_HWERR) } raise_if_error { ct_tmpl_set_critical(tmpl_fd, 0) } raise_if_error { ct_tmpl_set_informative(tmpl_fd, CT_PR_EV_HWERR) } raise_if_error { ct_tmpl_activate(tmpl_fd) } rescue tmpl.close raise end @tmpl = tmpl rescue => detail Puppet.log_exception(detail, _('Failed to activate a new process contract template')) end end def deactivate_contract_template(parent) return if @tmpl.nil? tmpl = @tmpl @tmpl = nil begin raise_if_error { ct_tmpl_clear(tmpl.fileno) } rescue => detail msg = if parent _('Failed to deactivate process contract template in the parent process') else _('Failed to deactivate process contract template in the child process') end Puppet.log_exception(detail, msg) exit(1) ensure tmpl.close end end def get_latest_child_contract_id begin stat = File.open(CTFS_PR_LATEST, File::RDONLY) begin stathdl = Fiddle::Pointer.new(0) raise_if_error { ct_status_read(stat.fileno, CTD_COMMON, stathdl.ref) } ctid = ct_status_get_id(stathdl) ct_status_free(stathdl) ensure stat.close end ctid rescue => detail Puppet.log_exception(detail, _('Failed to get latest child process contract id')) nil end end def abandon_latest_child_contract ctid = get_latest_child_contract_id return if ctid.nil? begin ctl = File.open(File.join(CTFS_PR_ROOT, ctid.to_s, %q(ctl)), File::WRONLY) begin raise_if_error { ct_ctl_abandon(ctl.fileno) } ensure ctl.close end rescue => detail Puppet.log_exception(detail, _('Failed to abandon a child process contract')) end end public def prepare activate_new_contract_template end def parent deactivate_contract_template(true) abandon_latest_child_contract end def child deactivate_contract_template(false) end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/backups.rb������������������������������������������������������������0000644�0052762�0001160�00000004765�13417161721�020043� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'find' require 'fileutils' module Puppet::Util::Backups # Deal with backups. def perform_backup(file = nil) # if they specifically don't want a backup, then just say # we're good return true unless self[:backup] # let the path be specified file ||= self[:path] return true unless Puppet::FileSystem.exist?(file) return(self.bucket ? perform_backup_with_bucket(file) : perform_backup_with_backuplocal(file, self[:backup])) end private def perform_backup_with_bucket(fileobj) file = (fileobj.class == String) ? fileobj : fileobj.name case Puppet::FileSystem.lstat(file).ftype when "directory" # we don't need to backup directories when recurse is on return true if self[:recurse] info _("Recursively backing up to filebucket") Find.find(self[:path]) { |f| backup_file_with_filebucket(f) if File.file?(f) } when "file"; backup_file_with_filebucket(file) when "link"; end true end def perform_backup_with_backuplocal(fileobj, backup) file = (fileobj.class == String) ? fileobj : fileobj.name newfile = file + backup remove_backup(newfile) begin bfile = file + backup # N.B. cp_r works on both files and directories FileUtils.cp_r(file, bfile, :preserve => true) return true rescue => detail # since they said they want a backup, let's error out # if we couldn't make one self.fail Puppet::Error, _("Could not back %{file} up: %{message}") % { file: file, message: detail.message }, detail end end def remove_backup(newfile) if self.class.name == :file and self[:links] != :follow method = :lstat else method = :stat end begin stat = Puppet::FileSystem.send(method, newfile) rescue Errno::ENOENT return end if stat.ftype == "directory" raise Puppet::Error, _("Will not remove directory backup %{newfile}; use a filebucket") % { newfile: newfile } end info _("Removing old backup of type %{file_type}") % { file_type: stat.ftype } begin Puppet::FileSystem.unlink(newfile) rescue => detail message = _("Could not remove old backup: %{detail}") % { detail: detail } self.log_exception(detail, message) self.fail Puppet::Error, message, detail end end def backup_file_with_filebucket(f) sum = self.bucket.backup(f) self.info _("Filebucketed %{f} to %{filebucket} with sum %{sum}") % { f: f, filebucket: self.bucket.name, sum: sum } return sum end end �����������puppet-5.5.10/lib/puppet/util/checksums.rb����������������������������������������������������������0000644�0052762�0001160�00000016005�13417161721�020366� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'digest/md5' require 'digest/sha1' require 'time' # A stand-alone module for calculating checksums # in a generic way. module Puppet::Util::Checksums module_function # It's not a good idea to use some of these in some contexts: for example, I # wouldn't try bucketing a file using the :none checksum type. def known_checksum_types [:sha256, :sha256lite, :md5, :md5lite, :sha1, :sha1lite, :sha512, :sha384, :sha224, :mtime, :ctime, :none] end class FakeChecksum def <<(*args) self end end # Is the provided string a checksum? def checksum?(string) # 'sha256lite'.length == 10 string =~ /^\{(\w{3,10})\}\S+/ end # Strip the checksum type from an existing checksum def sumdata(checksum) checksum =~ /^\{(\w+)\}(.+)/ ? $2 : nil end # Strip the checksum type from an existing checksum def sumtype(checksum) checksum =~ /^\{(\w+)\}/ ? $1 : nil end # Calculate a checksum using Digest::SHA256. def sha256(content) require 'digest/sha2' Digest::SHA256.hexdigest(content) end def sha256?(string) string =~ /^\h{64}$/ end def sha256_file(filename, lite = false) require 'digest/sha2' digest = Digest::SHA256.new checksum_file(digest, filename, lite) end def sha256_stream(lite = false, &block) require 'digest/sha2' digest = Digest::SHA256.new checksum_stream(digest, block, lite) end def sha256_hex_length 64 end def sha256lite(content) sha256(content[0..511]) end def sha256lite?(string) sha256?(string) end def sha256lite_file(filename) sha256_file(filename, true) end def sha256lite_stream(&block) sha256_stream(true, &block) end def sha256lite_hex_length sha256_hex_length end # Calculate a checksum using Digest::SHA384. def sha384(content) require 'digest/sha2' Digest::SHA384.hexdigest(content) end def sha384?(string) string =~ /^\h{96}$/ end def sha384_file(filename, lite = false) require 'digest/sha2' digest = Digest::SHA384.new checksum_file(digest, filename, lite) end def sha384_stream(lite = false, &block) require 'digest/sha2' digest = Digest::SHA384.new checksum_stream(digest, block, lite) end def sha384_hex_length 96 end # Calculate a checksum using Digest::SHA512. def sha512(content) require 'digest/sha2' Digest::SHA512.hexdigest(content) end def sha512?(string) string =~ /^\h{128}$/ end def sha512_file(filename, lite = false) require 'digest/sha2' digest = Digest::SHA512.new checksum_file(digest, filename, lite) end def sha512_stream(lite = false, &block) require 'digest/sha2' digest = Digest::SHA512.new checksum_stream(digest, block, lite) end def sha512_hex_length 128 end # Calculate a checksum using Digest::SHA224. def sha224(content) require 'openssl' OpenSSL::Digest::SHA224.new.hexdigest(content) end def sha224?(string) string =~ /^\h{56}$/ end def sha224_file(filename, lite = false) require 'openssl' digest = OpenSSL::Digest::SHA224.new checksum_file(digest, filename, lite) end def sha224_stream(lite = false, &block) require 'openssl' digest = OpenSSL::Digest::SHA224.new checksum_stream(digest, block, lite) end def sha224_hex_length 56 end # Calculate a checksum using Digest::MD5. def md5(content) Digest::MD5.hexdigest(content) end def md5?(string) string =~ /^\h{32}$/ end # Calculate a checksum of a file's content using Digest::MD5. def md5_file(filename, lite = false) digest = Digest::MD5.new checksum_file(digest, filename, lite) end def md5_stream(lite = false, &block) digest = Digest::MD5.new checksum_stream(digest, block, lite) end def md5_hex_length 32 end # Calculate a checksum of the first 500 chars of the content using Digest::MD5. def md5lite(content) md5(content[0..511]) end def md5lite?(string) md5?(string) end # Calculate a checksum of the first 500 chars of a file's content using Digest::MD5. def md5lite_file(filename) md5_file(filename, true) end def md5lite_stream(&block) md5_stream(true, &block) end def md5lite_hex_length md5_hex_length end def mtime(content) "" end def mtime?(string) return true if string.is_a? Time !!DateTime.parse(string) rescue false end # Return the :mtime timestamp of a file. def mtime_file(filename) Puppet::FileSystem.stat(filename).send(:mtime) end # by definition this doesn't exist # but we still need to execute the block given def mtime_stream(&block) noop_digest = FakeChecksum.new yield noop_digest nil end # Calculate a checksum using Digest::SHA1. def sha1(content) Digest::SHA1.hexdigest(content) end def sha1?(string) string =~ /^\h{40}$/ end # Calculate a checksum of a file's content using Digest::SHA1. def sha1_file(filename, lite = false) digest = Digest::SHA1.new checksum_file(digest, filename, lite) end def sha1_stream(lite = false, &block) digest = Digest::SHA1.new checksum_stream(digest, block, lite) end def sha1_hex_length 40 end # Calculate a checksum of the first 500 chars of the content using Digest::SHA1. def sha1lite(content) sha1(content[0..511]) end def sha1lite?(string) sha1?(string) end # Calculate a checksum of the first 500 chars of a file's content using Digest::SHA1. def sha1lite_file(filename) sha1_file(filename, true) end def sha1lite_stream(&block) sha1_stream(true, &block) end def sha1lite_hex_length sha1_hex_length end def ctime(content) "" end def ctime?(string) return true if string.is_a? Time !!DateTime.parse(string) rescue false end # Return the :ctime of a file. def ctime_file(filename) Puppet::FileSystem.stat(filename).send(:ctime) end def ctime_stream(&block) mtime_stream(&block) end def none(content) "" end def none?(string) string.empty? end # Return a "no checksum" def none_file(filename) "" end def none_stream noop_digest = FakeChecksum.new yield noop_digest "" end class DigestLite def initialize(digest, lite = false) @digest = digest @lite = lite @bytes = 0 end # Provide an interface for digests. If lite, only digest the first 512 bytes def <<(str) if @lite if @bytes < 512 buf = str[0, 512 - @bytes] @digest << buf @bytes += buf.length end else @digest << str end end end # Perform an incremental checksum on a file. def checksum_file(digest, filename, lite = false) buffer = lite ? 512 : 4096 File.open(filename, 'rb') do |file| while content = file.read(buffer) digest << content break if lite end end digest.hexdigest end def checksum_stream(digest, block, lite = false) block.call(DigestLite.new(digest, lite)) digest.hexdigest end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/colors.rb�������������������������������������������������������������0000644�0052762�0001160�00000007566�13417161721�017716� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/platform' module Puppet::Util::Colors BLACK = {:console => "\e[0;30m", :html => "color: #FFA0A0" } RED = {:console => "\e[0;31m", :html => "color: #FFA0A0" } GREEN = {:console => "\e[0;32m", :html => "color: #00CD00" } YELLOW = {:console => "\e[0;33m", :html => "color: #FFFF60" } BLUE = {:console => "\e[0;34m", :html => "color: #80A0FF" } MAGENTA = {:console => "\e[0;35m", :html => "color: #FFA500" } CYAN = {:console => "\e[0;36m", :html => "color: #40FFFF" } WHITE = {:console => "\e[0;37m", :html => "color: #FFFFFF" } HBLACK = {:console => "\e[1;30m", :html => "color: #FFA0A0" } HRED = {:console => "\e[1;31m", :html => "color: #FFA0A0" } HGREEN = {:console => "\e[1;32m", :html => "color: #00CD00" } HYELLOW = {:console => "\e[1;33m", :html => "color: #FFFF60" } HBLUE = {:console => "\e[1;34m", :html => "color: #80A0FF" } HMAGENTA = {:console => "\e[1;35m", :html => "color: #FFA500" } HCYAN = {:console => "\e[1;36m", :html => "color: #40FFFF" } HWHITE = {:console => "\e[1;37m", :html => "color: #FFFFFF" } BG_RED = {:console => "\e[0;41m", :html => "background: #FFA0A0"} BG_GREEN = {:console => "\e[0;42m", :html => "background: #00CD00"} BG_YELLOW = {:console => "\e[0;43m", :html => "background: #FFFF60"} BG_BLUE = {:console => "\e[0;44m", :html => "background: #80A0FF"} BG_MAGENTA = {:console => "\e[0;45m", :html => "background: #FFA500"} BG_CYAN = {:console => "\e[0;46m", :html => "background: #40FFFF"} BG_WHITE = {:console => "\e[0;47m", :html => "background: #FFFFFF"} BG_HRED = {:console => "\e[1;41m", :html => "background: #FFA0A0"} BG_HGREEN = {:console => "\e[1;42m", :html => "background: #00CD00"} BG_HYELLOW = {:console => "\e[1;43m", :html => "background: #FFFF60"} BG_HBLUE = {:console => "\e[1;44m", :html => "background: #80A0FF"} BG_HMAGENTA = {:console => "\e[1;45m", :html => "background: #FFA500"} BG_HCYAN = {:console => "\e[1;46m", :html => "background: #40FFFF"} BG_HWHITE = {:console => "\e[1;47m", :html => "background: #FFFFFF"} RESET = {:console => "\e[0m", :html => "" } Colormap = { :debug => WHITE, :info => GREEN, :notice => CYAN, :warning => YELLOW, :err => HMAGENTA, :alert => RED, :emerg => HRED, :crit => HRED, :black => BLACK, :red => RED, :green => GREEN, :yellow => YELLOW, :blue => BLUE, :magenta => MAGENTA, :cyan => CYAN, :white => WHITE, :hblack => HBLACK, :hred => HRED, :hgreen => HGREEN, :hyellow => HYELLOW, :hblue => HBLUE, :hmagenta => HMAGENTA, :hcyan => HCYAN, :hwhite => HWHITE, :bg_red => BG_RED, :bg_green => BG_GREEN, :bg_yellow => BG_YELLOW, :bg_blue => BG_BLUE, :bg_magenta => BG_MAGENTA, :bg_cyan => BG_CYAN, :bg_white => BG_WHITE, :bg_hred => BG_HRED, :bg_hgreen => BG_HGREEN, :bg_hyellow => BG_HYELLOW, :bg_hblue => BG_HBLUE, :bg_hmagenta => BG_HMAGENTA, :bg_hcyan => BG_HCYAN, :bg_hwhite => BG_HWHITE, :reset => { :console => "\e[m", :html => "" } } def colorize(color, str) case Puppet[:color] when true, :ansi, "ansi", "yes" console_color(color, str) when :html, "html" html_color(color, str) else str end end def console_color(color, str) Colormap[color][:console] + str.gsub(RESET[:console], Colormap[color][:console]) + RESET[:console] end def html_color(color, str) span = '<span style="%s">' % Colormap[color][:html] "#{span}%s</span>" % str.gsub(/<span .*?<\/span>/, "</span>\\0#{span}") end end ������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/command_line.rb�������������������������������������������������������0000644�0052762�0001160�00000014363�13417161721�021033� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Bundler and rubygems maintain a set of directories from which to # load gems. If Bundler is loaded, let it determine what can be # loaded. If it's not loaded, then use rubygems. But do this before # loading any puppet code, so that our gem loading system is sane. if not defined? ::Bundler begin require 'rubygems' rescue LoadError end end require 'puppet' require 'puppet/util' require "puppet/util/rubygems" require "puppet/util/limits" require 'puppet/util/colors' require 'puppet/gettext/module_translations' module Puppet module Util # This is the main entry point for all puppet applications / faces; it # is basically where the bootstrapping process / lifecycle of an app # begins. class CommandLine include Puppet::Util::Limits OPTION_OR_MANIFEST_FILE = /^-|\.pp$/ # @param zero [String] the name of the executable # @param argv [Array<String>] the arguments passed on the command line # @param stdin [IO] (unused) def initialize(zero = $0, argv = ARGV, stdin = STDIN) @command = File.basename(zero, '.rb') @argv = argv end # @return [String] name of the subcommand is being executed # @api public def subcommand_name return @command if @command != 'puppet' if @argv.first =~ OPTION_OR_MANIFEST_FILE nil else @argv.first end end # @return [Array<String>] the command line arguments being passed to the subcommand # @api public def args return @argv if @command != 'puppet' if subcommand_name.nil? @argv else @argv[1..-1] end end # Run the puppet subcommand. If the subcommand is determined to be an # external executable, this method will never return and the current # process will be replaced via {Kernel#exec}. # # @return [void] def execute Puppet::Util.exit_on_fail(_("Could not initialize global default settings")) do Puppet.initialize_settings(args) end setpriority(Puppet[:priority]) find_subcommand.run end # @api private def external_subcommand Puppet::Util.which("puppet-#{subcommand_name}") end private def find_subcommand if subcommand_name.nil? if args.include?("--help") || args.include?("-h") ApplicationSubcommand.new("help", CommandLine.new("puppet", ["help"])) else NilSubcommand.new(self) end elsif Puppet::Application.available_application_names.include?(subcommand_name) ApplicationSubcommand.new(subcommand_name, self) elsif path_to_subcommand = external_subcommand ExternalSubcommand.new(path_to_subcommand, self) else UnknownSubcommand.new(subcommand_name, self) end end # @api private class ApplicationSubcommand def initialize(subcommand_name, command_line) @subcommand_name = subcommand_name @command_line = command_line end def run # For most applications, we want to be able to load code from the modulepath, # such as apply, describe, resource, and faces. # For agent and device in agent mode, we only want to load pluginsync'ed code from libdir. # For master, we shouldn't ever be loading per-environment code into the master's # ruby process, but that requires fixing (#17210, #12173, #8750). So for now # we try to restrict to only code that can be autoloaded from the node's # environment. # PUP-2114 - at this point in the bootstrapping process we do not # have an appropriate application-wide current_environment set. # If we cannot find the configured environment, which may not exist, # we do not attempt to add plugin directories to the load path. unless @subcommand_name == 'master' || @subcommand_name == 'agent' || (@subcommand_name == 'device' && (['--apply', '--facts', '--resource'] - @command_line.args).empty?) if configured_environment = Puppet.lookup(:environments).get(Puppet[:environment]) configured_environment.each_plugin_directory do |dir| $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) end Puppet::ModuleTranslations.load_from_modulepath(configured_environment.modules) Puppet::ModuleTranslations.load_from_vardir(Puppet[:vardir]) # Puppet requires Facter, which initializes its lookup paths. Reset Facter to # pickup the new $LOAD_PATH. Facter.reset end end app = Puppet::Application.find(@subcommand_name).new(@command_line) app.run end end # @api private class ExternalSubcommand def initialize(path_to_subcommand, command_line) @path_to_subcommand = path_to_subcommand @command_line = command_line end def run Kernel.exec(@path_to_subcommand, *@command_line.args) end end # @api private class NilSubcommand include Puppet::Util::Colors def initialize(command_line) @command_line = command_line end def run args = @command_line.args if args.include? "--version" or args.include? "-V" puts Puppet.version elsif @command_line.subcommand_name.nil? && args.count > 0 # If the subcommand is truly nil and there is an arg, it's an option; print out the invalid option message puts colorize(:hred, _("Error: Could not parse application options: invalid option: %{opt}") % { opt: args[0] }) exit 1 else puts _("See 'puppet help' for help on available puppet subcommands") end end end # @api private class UnknownSubcommand < NilSubcommand def initialize(subcommand_name, command_line) @subcommand_name = subcommand_name super(command_line) end def run puts colorize(:hred, _("Error: Unknown Puppet subcommand '%{cmd}'") % { cmd: @subcommand_name }) super exit 1 end end end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/command_line/���������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�020504� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/command_line/puppet_option_parser.rb����������������������������������0000644�0052762�0001160�00000006116�13417161721�025311� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/command_line/trollop' module Puppet module Util class CommandLine class PuppetOptionError < Puppet::Error end class TrollopCommandlineError < Puppet::Util::CommandLine::Trollop::CommandlineError; end # This is a command line option parser. It is intended to have an API that is very similar to # the ruby stdlib 'OptionParser' API, for ease of integration into our existing code... however, # However, we've removed the OptionParser-based implementation and are only maintaining the # it's implemented based on the third-party "trollop" library. This was done because there # are places where the stdlib OptionParser is not flexible enough to meet our needs. class PuppetOptionParser def initialize(usage_msg = nil) require "puppet/util/command_line/trollop" @create_default_short_options = false @parser = Trollop::Parser.new do banner usage_msg end end # This parameter, if set, will tell the underlying option parser not to throw an # exception if we pass it options that weren't explicitly registered. We need this # capability because we need to be able to pass all of the command-line options before # we know which application/face they are going to be running, but the app/face # may specify additional command-line arguments that are valid for that app/face. attr_reader :ignore_invalid_options def ignore_invalid_options=(value) @parser.ignore_invalid_options = value end def on(*args, &block) # The 2nd element is an optional "short" representation. if args.length == 3 long, desc, type = args elsif args.length == 4 long, short, desc, type = args else raise ArgumentError, _("this method only takes 3 or 4 arguments. Given: %{args}") % { args: args.inspect } end options = { :long => long, :short => short, :required => false, :callback => pass_only_last_value_on_to(block), :multi => true, } case type when :REQUIRED options[:type] = :string when :NONE options[:type] = :flag else raise PuppetOptionError.new(_("Unsupported type: '%{type}'") % { type: type }) end @parser.opt long.sub("^--", "").intern, desc, options end def parse(*args) args = args[0] if args.size == 1 and Array === args[0] args_copy = args.dup begin @parser.parse args_copy rescue Puppet::Util::CommandLine::Trollop::CommandlineError => err raise PuppetOptionError.new(_("Error parsing arguments"), err) end end def pass_only_last_value_on_to(block) lambda { |values| block.call(values.is_a?(Array) ? values.last : values) } end private :pass_only_last_value_on_to end end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/command_line/trollop.rb�����������������������������������������������0000644�0052762�0001160�00000072016�13417161721�022525� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������## lib/trollop.rb -- trollop command-line processing library ## Author:: William Morgan (mailto: wmorgan-trollop@masanjin.net) ## Copyright:: Copyright 2007 William Morgan ## License:: the same terms as ruby itself ## ## 2012-03: small changes made by cprice (chris@puppetlabs.com); ## patch submitted for upstream consideration: ## https://gitorious.org/trollop/mainline/merge_requests/9 ## 2012-08: namespace changes made by Jeff McCune (jeff@puppetlabs.com) ## moved Trollop into Puppet::Util::CommandLine to prevent monkey ## patching the upstream trollop library if also loaded. require 'date' module Puppet module Util class CommandLine module Trollop VERSION = "1.16.2" ## Thrown by Parser in the event of a commandline error. Not needed if ## you're using the Trollop::options entry. class CommandlineError < StandardError; end ## Thrown by Parser if the user passes in '-h' or '--help'. Handled ## automatically by Trollop#options. class HelpNeeded < StandardError; end ## Thrown by Parser if the user passes in '-h' or '--version'. Handled ## automatically by Trollop#options. class VersionNeeded < StandardError; end ## Regex for floating point numbers FLOAT_RE = /^-?((\d+(\.\d+)?)|(\.\d+))([eE][-+]?[\d]+)?$/ ## Regex for parameters PARAM_RE = /^-(-|\.$|[^\d\.])/ ## The commandline parser. In typical usage, the methods in this class ## will be handled internally by Trollop::options. In this case, only the ## #opt, #banner and #version, #depends, and #conflicts methods will ## typically be called. ## ## If you want to instantiate this class yourself (for more complicated ## argument-parsing logic), call #parse to actually produce the output hash, ## and consider calling it from within ## Trollop::with_standard_exception_handling. class Parser ## The set of values that indicate a flag option when passed as the ## +:type+ parameter of #opt. FLAG_TYPES = [:flag, :bool, :boolean] ## The set of values that indicate a single-parameter (normal) option when ## passed as the +:type+ parameter of #opt. ## ## A value of +io+ corresponds to a readable IO resource, including ## a filename, URI, or the strings 'stdin' or '-'. SINGLE_ARG_TYPES = [:int, :integer, :string, :double, :float, :io, :date] ## The set of values that indicate a multiple-parameter option (i.e., that ## takes multiple space-separated values on the commandline) when passed as ## the +:type+ parameter of #opt. MULTI_ARG_TYPES = [:ints, :integers, :strings, :doubles, :floats, :ios, :dates] ## The complete set of legal values for the +:type+ parameter of #opt. TYPES = FLAG_TYPES + SINGLE_ARG_TYPES + MULTI_ARG_TYPES INVALID_SHORT_ARG_REGEX = /[\d-]/ #:nodoc: ## The values from the commandline that were not interpreted by #parse. attr_reader :leftovers ## The complete configuration hashes for each option. (Mainly useful ## for testing.) attr_reader :specs ## A flag that determines whether or not to attempt to automatically generate "short" options if they are not ## explicitly specified. attr_accessor :create_default_short_options ## A flag that determines whether or not to raise an error if the parser is passed one or more ## options that were not registered ahead of time. If 'true', then the parser will simply ## ignore options that it does not recognize. attr_accessor :ignore_invalid_options ## A flag indicating whether or not the parser should attempt to handle "--help" and ## "--version" specially. If 'false', it will treat them just like any other option. attr_accessor :handle_help_and_version ## Initializes the parser, and instance-evaluates any block given. def initialize *a, &b @version = nil @leftovers = [] @specs = {} @long = {} @short = {} @order = [] @constraints = [] @stop_words = [] @stop_on_unknown = false #instance_eval(&b) if b # can't take arguments cloaker(&b).bind(self).call(*a) if b end ## Define an option. +name+ is the option name, a unique identifier ## for the option that you will use internally, which should be a ## symbol or a string. +desc+ is a string description which will be ## displayed in help messages. ## ## Takes the following optional arguments: ## ## [+:long+] Specify the long form of the argument, i.e. the form with two dashes. If unspecified, will be automatically derived based on the argument name by turning the +name+ option into a string, and replacing any _'s by -'s. ## [+:short+] Specify the short form of the argument, i.e. the form with one dash. If unspecified, will be automatically derived from +name+. ## [+:type+] Require that the argument take a parameter or parameters of type +type+. For a single parameter, the value can be a member of +SINGLE_ARG_TYPES+, or a corresponding Ruby class (e.g. +Integer+ for +:int+). For multiple-argument parameters, the value can be any member of +MULTI_ARG_TYPES+ constant. If unset, the default argument type is +:flag+, meaning that the argument does not take a parameter. The specification of +:type+ is not necessary if a +:default+ is given. ## [+:default+] Set the default value for an argument. Without a default value, the hash returned by #parse (and thus Trollop::options) will have a +nil+ value for this key unless the argument is given on the commandline. The argument type is derived automatically from the class of the default value given, so specifying a +:type+ is not necessary if a +:default+ is given. (But see below for an important caveat when +:multi+: is specified too.) If the argument is a flag, and the default is set to +true+, then if it is specified on the commandline the value will be +false+. ## [+:required+] If set to +true+, the argument must be provided on the commandline. ## [+:multi+] If set to +true+, allows multiple occurrences of the option on the commandline. Otherwise, only a single instance of the option is allowed. (Note that this is different from taking multiple parameters. See below.) ## ## Note that there are two types of argument multiplicity: an argument ## can take multiple values, e.g. "--arg 1 2 3". An argument can also ## be allowed to occur multiple times, e.g. "--arg 1 --arg 2". ## ## Arguments that take multiple values should have a +:type+ parameter ## drawn from +MULTI_ARG_TYPES+ (e.g. +:strings+), or a +:default:+ ## value of an array of the correct type (e.g. [String]). The ## value of this argument will be an array of the parameters on the ## commandline. ## ## Arguments that can occur multiple times should be marked with ## +:multi+ => +true+. The value of this argument will also be an array. ## In contrast with regular non-multi options, if not specified on ## the commandline, the default value will be [], not nil. ## ## These two attributes can be combined (e.g. +:type+ => +:strings+, ## +:multi+ => +true+), in which case the value of the argument will be ## an array of arrays. ## ## There's one ambiguous case to be aware of: when +:multi+: is true and a ## +:default+ is set to an array (of something), it's ambiguous whether this ## is a multi-value argument as well as a multi-occurrence argument. ## In this case, Trollop assumes that it's not a multi-value argument. ## If you want a multi-value, multi-occurrence argument with a default ## value, you must specify +:type+ as well. def opt name, desc="", opts={} raise ArgumentError, _("you already have an argument named '%{name}'") % { name: name } if @specs.member? name ## fill in :type opts[:type] = # normalize case opts[:type] when :boolean, :bool; :flag when :integer; :int when :integers; :ints when :double; :float when :doubles; :floats when Class case opts[:type].name when 'TrueClass', 'FalseClass'; :flag when 'String'; :string when 'Integer'; :int when 'Float'; :float when 'IO'; :io when 'Date'; :date else raise ArgumentError, _("unsupported argument type '%{type}'") % { type: opts[:type].class.name } end when nil; nil else raise ArgumentError, _("unsupported argument type '%{type}'") % { type: opts[:type] } unless TYPES.include?(opts[:type]) opts[:type] end ## for options with :multi => true, an array default doesn't imply ## a multi-valued argument. for that you have to specify a :type ## as well. (this is how we disambiguate an ambiguous situation; ## see the docs for Parser#opt for details.) disambiguated_default = if opts[:multi] && opts[:default].is_a?(Array) && !opts[:type] opts[:default].first else opts[:default] end type_from_default = case disambiguated_default when Integer; :int when Numeric; :float when TrueClass, FalseClass; :flag when String; :string when IO; :io when Date; :date when Array if opts[:default].empty? raise ArgumentError, _("multiple argument type cannot be deduced from an empty array for '%{value0}'") % { value0: opts[:default][0].class.name } end case opts[:default][0] # the first element determines the types when Integer; :ints when Numeric; :floats when String; :strings when IO; :ios when Date; :dates else raise ArgumentError, _("unsupported multiple argument type '%{value0}'") % { value0: opts[:default][0].class.name } end when nil; nil else raise ArgumentError, _("unsupported argument type '%{value0}'") % { value0: opts[:default].class.name } end raise ArgumentError, _(":type specification and default type don't match (default type is %{type_from_default})") % { type_from_default: type_from_default } if opts[:type] && type_from_default && opts[:type] != type_from_default opts[:type] = opts[:type] || type_from_default || :flag ## fill in :long opts[:long] = opts[:long] ? opts[:long].to_s : name.to_s.gsub("_", "-") opts[:long] = case opts[:long] when /^--([^-].*)$/ $1 when /^[^-]/ opts[:long] else raise ArgumentError, _("invalid long option name %{name}") % { name: opts[:long].inspect } end raise ArgumentError, _("long option name %{value0} is already taken; please specify a (different) :long") % { value0: opts[:long].inspect } if @long[opts[:long]] ## fill in :short opts[:short] = opts[:short].to_s if opts[:short] unless opts[:short] == :none opts[:short] = case opts[:short] when /^-(.)$/; $1 when nil, :none, /^.$/; opts[:short] else raise ArgumentError, _("invalid short option name '%{name}'") % { name: opts[:short].inspect } end if opts[:short] raise ArgumentError, _("short option name %{value0} is already taken; please specify a (different) :short") % { value0: opts[:short].inspect } if @short[opts[:short]] raise ArgumentError, _("a short option name can't be a number or a dash") if opts[:short] =~ INVALID_SHORT_ARG_REGEX end ## fill in :default for flags opts[:default] = false if opts[:type] == :flag && opts[:default].nil? ## autobox :default for :multi (multi-occurrence) arguments opts[:default] = [opts[:default]] if opts[:default] && opts[:multi] && !opts[:default].is_a?(Array) ## fill in :multi opts[:multi] ||= false opts[:desc] ||= desc @long[opts[:long]] = name @short[opts[:short]] = name if opts[:short] && opts[:short] != :none @specs[name] = opts @order << [:opt, name] end ## Sets the version string. If set, the user can request the version ## on the commandline. Should probably be of the form "<program name> ## <version number>". def version s=nil; @version = s if s; @version end ## Adds text to the help display. Can be interspersed with calls to ## #opt to build a multi-section help page. def banner s; @order << [:text, s] end alias :text :banner ## Marks two (or more!) options as requiring each other. Only handles ## undirected (i.e., mutual) dependencies. Directed dependencies are ## better modeled with Trollop::die. def depends *syms syms.each { |sym| raise ArgumentError, _("unknown option '%{sym}'") % { sym: sym } unless @specs[sym] } @constraints << [:depends, syms] end ## Marks two (or more!) options as conflicting. def conflicts *syms syms.each { |sym| raise ArgumentError, _("unknown option '%{sym}'") % { sym: sym } unless @specs[sym] } @constraints << [:conflicts, syms] end ## Defines a set of words which cause parsing to terminate when ## encountered, such that any options to the left of the word are ## parsed as usual, and options to the right of the word are left ## intact. ## ## A typical use case would be for subcommand support, where these ## would be set to the list of subcommands. A subsequent Trollop ## invocation would then be used to parse subcommand options, after ## shifting the subcommand off of ARGV. def stop_on *words @stop_words = [*words].flatten end ## Similar to #stop_on, but stops on any unknown word when encountered ## (unless it is a parameter for an argument). This is useful for ## cases where you don't know the set of subcommands ahead of time, ## i.e., without first parsing the global options. def stop_on_unknown @stop_on_unknown = true end ## Parses the commandline. Typically called by Trollop::options, ## but you can call it directly if you need more control. ## ## throws CommandlineError, HelpNeeded, and VersionNeeded exceptions. def parse cmdline=ARGV vals = {} required = {} if handle_help_and_version opt :version, _("Print version and exit") if @version unless @specs[:version] || @long["version"] opt :help, _("Show this message") unless @specs[:help] || @long["help"] end @specs.each do |sym, opts| required[sym] = true if opts[:required] vals[sym] = opts[:default] vals[sym] = [] if opts[:multi] && !opts[:default] # multi arguments default to [], not nil end resolve_default_short_options if create_default_short_options ## resolve symbols given_args = {} @leftovers = each_arg cmdline do |arg, params| sym = case arg when /^-([^-])$/ @short[$1] when /^--no-([^-]\S*)$/ @long["[no-]#{$1}"] when /^--([^-]\S*)$/ @long[$1] ? @long[$1] : @long["[no-]#{$1}"] else raise CommandlineError, _("invalid argument syntax: '%{arg}'") % { arg: arg } end unless sym next 0 if ignore_invalid_options raise CommandlineError, _("unknown argument '%{arg}'") % { arg: arg } unless sym end if given_args.include?(sym) && !@specs[sym][:multi] raise CommandlineError, _("option '%{arg}' specified multiple times") % { arg: arg } end given_args[sym] ||= {} given_args[sym][:arg] = arg given_args[sym][:params] ||= [] # The block returns the number of parameters taken. num_params_taken = 0 unless params.nil? if SINGLE_ARG_TYPES.include?(@specs[sym][:type]) given_args[sym][:params] << params[0, 1] # take the first parameter num_params_taken = 1 elsif MULTI_ARG_TYPES.include?(@specs[sym][:type]) given_args[sym][:params] << params # take all the parameters num_params_taken = params.size end end num_params_taken end if handle_help_and_version ## check for version and help args raise VersionNeeded if given_args.include? :version raise HelpNeeded if given_args.include? :help end ## check constraint satisfaction @constraints.each do |type, syms| constraint_sym = syms.find { |sym| given_args[sym] } next unless constraint_sym case type when :depends syms.each { |sym| raise CommandlineError, _("--%{value0} requires --%{value1}") % { value0: @specs[constraint_sym][:long], value1: @specs[sym][:long] } unless given_args.include? sym } when :conflicts syms.each { |sym| raise CommandlineError, _("--%{value0} conflicts with --%{value1}") % { value0: @specs[constraint_sym][:long], value1: @specs[sym][:long] } if given_args.include?(sym) && (sym != constraint_sym) } end end required.each do |sym, val| raise CommandlineError, _("option --%{opt} must be specified") % { opt: @specs[sym][:long] } unless given_args.include? sym end ## parse parameters given_args.each do |sym, given_data| arg = given_data[:arg] params = given_data[:params] opts = @specs[sym] raise CommandlineError, _("option '%{arg}' needs a parameter") % { arg: arg } if params.empty? && opts[:type] != :flag vals["#{sym}_given".intern] = true # mark argument as specified on the commandline case opts[:type] when :flag if arg =~ /^--no-/ and sym.to_s =~ /^--\[no-\]/ vals[sym] = opts[:default] else vals[sym] = !opts[:default] end when :int, :ints vals[sym] = params.map { |pg| pg.map { |p| parse_integer_parameter p, arg } } when :float, :floats vals[sym] = params.map { |pg| pg.map { |p| parse_float_parameter p, arg } } when :string, :strings vals[sym] = params.map { |pg| pg.map { |p| p.to_s } } when :io, :ios vals[sym] = params.map { |pg| pg.map { |p| parse_io_parameter p, arg } } when :date, :dates vals[sym] = params.map { |pg| pg.map { |p| parse_date_parameter p, arg } } end if SINGLE_ARG_TYPES.include?(opts[:type]) unless opts[:multi] # single parameter vals[sym] = vals[sym][0][0] else # multiple options, each with a single parameter vals[sym] = vals[sym].map { |p| p[0] } end elsif MULTI_ARG_TYPES.include?(opts[:type]) && !opts[:multi] vals[sym] = vals[sym][0] # single option, with multiple parameters end # else: multiple options, with multiple parameters opts[:callback].call(vals[sym]) if opts.has_key?(:callback) end ## modify input in place with only those ## arguments we didn't process cmdline.clear @leftovers.each { |l| cmdline << l } ## allow openstruct-style accessors class << vals def method_missing(m, *args) self[m] || self[m.to_s] end end vals end def parse_date_parameter param, arg #:nodoc: begin begin time = Chronic.parse(param) rescue NameError # chronic is not available end time ? Date.new(time.year, time.month, time.day) : Date.parse(param) rescue ArgumentError raise CommandlineError, _("option '%{arg}' needs a date") % { arg: arg }, $!.backtrace end end ## Print the help message to +stream+. def educate stream=$stdout width # just calculate it now; otherwise we have to be careful not to # call this unless the cursor's at the beginning of a line. left = {} @specs.each do |name, spec| left[name] = "--#{spec[:long]}" + (spec[:short] && spec[:short] != :none ? ", -#{spec[:short]}" : "") + case spec[:type] when :flag; "" when :int; " <i>" when :ints; " <i+>" when :string; " <s>" when :strings; " <s+>" when :float; " <f>" when :floats; " <f+>" when :io; " <filename/uri>" when :ios; " <filename/uri+>" when :date; " <date>" when :dates; " <date+>" end end leftcol_width = left.values.map { |s| s.length }.max || 0 rightcol_start = leftcol_width + 6 # spaces unless @order.size > 0 && @order.first.first == :text stream.puts "#@version\n" if @version stream.puts _("Options:") end @order.each do |what, opt| if what == :text stream.puts wrap(opt) next end spec = @specs[opt] stream.printf " %#{leftcol_width}s: ", left[opt] desc = spec[:desc] + begin default_s = case spec[:default] when $stdout; "<stdout>" when $stdin; "<stdin>" when $stderr; "<stderr>" when Array spec[:default].join(", ") else spec[:default].to_s end if spec[:default] if spec[:desc] =~ /\.$/ _(" (Default: %{default_s})") % { default_s: default_s } else _(" (default: %{default_s})") % { default_s: default_s } end else "" end end stream.puts wrap(desc, :width => width - rightcol_start - 1, :prefix => rightcol_start) end end def width #:nodoc: @width ||= if $stdout.tty? begin require 'curses' Curses::init_screen x = Curses::cols Curses::close_screen x rescue Exception 80 end else 80 end end def wrap str, opts={} # :nodoc: if str == "" [""] else str.split("\n").map { |s| wrap_line s, opts }.flatten end end ## The per-parser version of Trollop::die (see that for documentation). def die arg, msg if msg $stderr.puts _("Error: argument --%{value0} %{msg}.") % { value0: @specs[arg][:long], msg: msg } else $stderr.puts _("Error: %{arg}.") % { arg: arg } end $stderr.puts _("Try --help for help.") exit(-1) end private ## yield successive arg, parameter pairs def each_arg args remains = [] i = 0 until i >= args.length if @stop_words.member? args[i] remains += args[i .. -1] return remains end case args[i] when /^--$/ # arg terminator remains += args[(i + 1) .. -1] return remains when /^--(\S+?)=(.*)$/ # long argument with equals yield "--#{$1}", [$2] i += 1 when /^--(\S+)$/ # long argument params = collect_argument_parameters(args, i + 1) unless params.empty? num_params_taken = yield args[i], params unless num_params_taken if @stop_on_unknown remains += args[i + 1 .. -1] return remains else remains += params end end i += 1 + num_params_taken else # long argument no parameter yield args[i], nil i += 1 end when /^-(\S+)$/ # one or more short arguments shortargs = $1.split(//) shortargs.each_with_index do |a, j| if j == (shortargs.length - 1) params = collect_argument_parameters(args, i + 1) unless params.empty? num_params_taken = yield "-#{a}", params unless num_params_taken if @stop_on_unknown remains += args[i + 1 .. -1] return remains else remains += params end end i += 1 + num_params_taken else # argument no parameter yield "-#{a}", nil i += 1 end else yield "-#{a}", nil end end else if @stop_on_unknown remains += args[i .. -1] return remains else remains << args[i] i += 1 end end end remains end def parse_integer_parameter param, arg raise CommandlineError, _("option '%{arg}' needs an integer") % { arg: arg } unless param =~ /^\d+$/ param.to_i end def parse_float_parameter param, arg raise CommandlineError, _("option '%{arg}' needs a floating-point number") % { arg: arg } unless param =~ FLOAT_RE param.to_f end def parse_io_parameter param, arg case param when /^(stdin|-)$/i; $stdin else require 'open-uri' begin open param rescue SystemCallError => e raise CommandlineError, _("file or url for option '%{arg}' cannot be opened: %{value0}") % { arg: arg, value0: e.message }, e.backtrace end end end def collect_argument_parameters args, start_at params = [] pos = start_at while args[pos] && args[pos] !~ PARAM_RE && !@stop_words.member?(args[pos]) do params << args[pos] pos += 1 end params end def resolve_default_short_options @order.each do |type, name| next unless type == :opt opts = @specs[name] next if opts[:short] c = opts[:long].split(//).find { |d| d !~ INVALID_SHORT_ARG_REGEX && !@short.member?(d) } if c # found a character to use opts[:short] = c @short[c] = name end end end def wrap_line str, opts={} prefix = opts[:prefix] || 0 width = opts[:width] || (self.width - 1) start = 0 ret = [] until start > str.length nextt = if start + width >= str.length str.length else x = str.rindex(/\s/, start + width) x = str.index(/\s/, start) if x && x < start x || str.length end ret << (ret.empty? ? "" : " " * prefix) + str[start ... nextt] start = nextt + 1 end ret end ## instance_eval but with ability to handle block arguments ## thanks to why: http://redhanded.hobix.com/inspect/aBlockCostume.html def cloaker &b (class << self; self; end).class_eval do define_method :cloaker_, &b meth = instance_method :cloaker_ remove_method :cloaker_ meth end end end ## The easy, syntactic-sugary entry method into Trollop. Creates a Parser, ## passes the block to it, then parses +args+ with it, handling any errors or ## requests for help or version information appropriately (and then exiting). ## Modifies +args+ in place. Returns a hash of option values. ## ## The block passed in should contain zero or more calls to +opt+ ## (Parser#opt), zero or more calls to +text+ (Parser#text), and ## probably a call to +version+ (Parser#version). ## ## The returned block contains a value for every option specified with ## +opt+. The value will be the value given on the commandline, or the ## default value if the option was not specified on the commandline. For ## every option specified on the commandline, a key "<option ## name>_given" will also be set in the hash. ## ## Example: ## ## require 'trollop' ## opts = Trollop::options do ## opt :monkey, "Use monkey mode" # a flag --monkey, defaulting to false ## opt :goat, "Use goat mode", :default => true # a flag --goat, defaulting to true ## opt :num_limbs, "Number of limbs", :default => 4 # an integer --num-limbs <i>, defaulting to 4 ## opt :num_thumbs, "Number of thumbs", :type => :int # an integer --num-thumbs <i>, defaulting to nil ## end ## ## ## if called with no arguments ## p opts # => { :monkey => false, :goat => true, :num_limbs => 4, :num_thumbs => nil } ## ## ## if called with --monkey ## p opts # => {:monkey_given=>true, :monkey=>true, :goat=>true, :num_limbs=>4, :help=>false, :num_thumbs=>nil} ## ## See more examples at http://trollop.rubyforge.org. def options args=ARGV, *a, &b @last_parser = Parser.new(*a, &b) with_standard_exception_handling(@last_parser) { @last_parser.parse args } end ## If Trollop::options doesn't do quite what you want, you can create a Parser ## object and call Parser#parse on it. That method will throw CommandlineError, ## HelpNeeded and VersionNeeded exceptions when necessary; if you want to ## have these handled for you in the standard manner (e.g. show the help ## and then exit upon an HelpNeeded exception), call your code from within ## a block passed to this method. ## ## Note that this method will call System#exit after handling an exception! ## ## Usage example: ## ## require 'trollop' ## p = Trollop::Parser.new do ## opt :monkey, "Use monkey mode" # a flag --monkey, defaulting to false ## opt :goat, "Use goat mode", :default => true # a flag --goat, defaulting to true ## end ## ## opts = Trollop::with_standard_exception_handling p do ## o = p.parse ARGV ## raise Trollop::HelpNeeded if ARGV.empty? # show help screen ## o ## end ## ## Requires passing in the parser object. def with_standard_exception_handling parser begin yield rescue CommandlineError => e $stderr.puts _("Error: %{value0}.") % { value0: e.message } $stderr.puts _("Try --help for help.") exit(-1) rescue HelpNeeded parser.educate exit rescue VersionNeeded puts parser.version exit end end ## Informs the user that their usage of 'arg' was wrong, as detailed by ## 'msg', and dies. Example: ## ## options do ## opt :volume, :default => 0.0 ## end ## ## die :volume, "too loud" if opts[:volume] > 10.0 ## die :volume, "too soft" if opts[:volume] < 0.1 ## ## In the one-argument case, simply print that message, a notice ## about -h, and die. Example: ## ## options do ## opt :whatever # ... ## end ## ## Trollop::die "need at least one filename" if ARGV.empty? def die arg, msg=nil if @last_parser @last_parser.die arg, msg else #TRANSLATORS 'Trollop' is the name of a module and 'die' and 'options' are methods in it and should not be translated. raise ArgumentError, _("Trollop::die can only be called after Trollop::options") end end module_function :options, :die, :with_standard_exception_handling end # module end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/constant_inflector.rb�������������������������������������������������0000644�0052762�0001160�00000001216�13417161721�022275� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Created on 2008-02-12 # Copyright Luke Kanies # NOTE: I think it might be worth considering moving these methods directly into Puppet::Util. # A common module for converting between constants and # file names. module Puppet module Util module ConstantInflector def file2constant(file) file.split("/").collect { |name| name.capitalize }.join("::").gsub(/_+(.)/) { |term| $1.capitalize } end module_function :file2constant def constant2file(constant) constant.to_s.gsub(/([a-z])([A-Z])/) { |term| $1 + "_#{$2}" }.gsub("::", "/").downcase end module_function :constant2file end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/diff.rb���������������������������������������������������������������0000644�0052762�0001160�00000004022�13417161721�017305� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'tempfile' # Provide a diff between two strings. module Puppet::Util::Diff include Puppet::Util::Execution require 'tempfile' def diff(old, new) return '' unless diff_cmd = Puppet[:diff] and diff_cmd != "" command = [diff_cmd] if args = Puppet[:diff_args] and args != "" args.split(' ').each do|arg| command << arg end end command << old << new Puppet::Util::Execution.execute(command, :failonfail => false, :combine => false) end module_function :diff # return diff string of two input strings # format defaults to unified # context defaults to 3 lines def lcs_diff(data_old, data_new, format=:unified, context_lines=3) unless Puppet.features.diff? Puppet.warning _("Cannot provide diff without the diff/lcs Ruby library") return "" end data_old = data_old.split(/\n/).map! { |e| e.chomp } data_new = data_new.split(/\n/).map! { |e| e.chomp } output = "" diffs = ::Diff::LCS.diff(data_old, data_new) return output if diffs.empty? oldhunk = hunk = nil file_length_difference = 0 diffs.each do |piece| begin hunk = ::Diff::LCS::Hunk.new( data_old, data_new, piece, context_lines, file_length_difference) file_length_difference = hunk.file_length_difference next unless oldhunk # Hunks may overlap, which is why we need to be careful when our # diff includes lines of context. Otherwise, we might print # redundant lines. if (context_lines > 0) and hunk.overlaps?(oldhunk) hunk.unshift(oldhunk) else output << oldhunk.diff(format) end ensure oldhunk = hunk output << "\n" end end # Handle the last remaining hunk output << oldhunk.diff(format) << "\n" end def string_file_diff(path, string) tempfile = Tempfile.new("puppet-diffing") tempfile.open tempfile.print string tempfile.close notice "\n" + diff(path, tempfile.path) tempfile.delete end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/docs.rb���������������������������������������������������������������0000644�0052762�0001160�00000007772�13417161721�017344� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Some simple methods for helping manage automatic documentation generation. module Puppet::Util::Docs # Specify the actual doc string. def desc(str) @doc = str end # Add a new autodoc block. We have to define these as class methods, # rather than just sticking them in a hash, because otherwise they're # too difficult to do inheritance with. def dochook(name, &block) method = "dochook_#{name}" meta_def method, &block end attr_writer :doc # Generate the full doc string. def doc extra = methods.find_all { |m| m.to_s =~ /^dochook_.+/ }.sort.collect { |m| self.send(m) }.delete_if {|r| r.nil? }.collect {|r| "* #{r}"}.join("\n") if @doc scrub(@doc) + (extra.empty? ? '' : "\n\n#{extra}") else extra end end # Build a table def doctable(headers, data) str = "\n\n" lengths = [] # Figure out the longest field for all columns data.each do |name, values| [name, values].flatten.each_with_index do |value, i| lengths[i] ||= 0 lengths[i] = value.to_s.length if value.to_s.length > lengths[i] end end # The headers could also be longest headers.each_with_index do |value, i| lengths[i] = value.to_s.length if value.to_s.length > lengths[i] end # Add the header names str += headers.zip(lengths).collect { |value, num| pad(value, num) }.join(" | ") + " |" + "\n" # And the header row str += lengths.collect { |num| "-" * num }.join(" | ") + " |" + "\n" # Now each data row data.sort { |a, b| a[0].to_s <=> b[0].to_s }.each do |name, rows| str += [name, rows].flatten.zip(lengths).collect do |value, length| pad(value, length) end.join(" | ") + " |" + "\n" end str + "\n" end # There is nothing that would ever set this. It gets read in reference/type.rb, but will never have any value but nil. attr_reader :nodoc def nodoc? nodoc end # Pad a field with spaces def pad(value, length) value.to_s + (" " * (length - value.to_s.length)) end HEADER_LEVELS = [nil, "#", "##", "###", "####", "#####"] def markdown_header(name, level) "#{HEADER_LEVELS[level]} #{name}\n\n" end def markdown_definitionlist(term, definition) lines = scrub(definition).split("\n") str = "#{term}\n: #{lines.shift}\n" lines.each do |line| str << " " if line =~ /\S/ str << "#{line}\n" end str << "\n" end # Strip indentation and trailing whitespace from embedded doc fragments. # # Multi-line doc fragments are sometimes indented in order to preserve the # formatting of the code they're embedded in. Since indents are syntactic # elements in Markdown, we need to make sure we remove any indent that was # added solely to preserve surrounding code formatting, but LEAVE any indent # that delineates a Markdown element (code blocks, multi-line bulleted list # items). We can do this by removing the *least common indent* from each line. # # Least common indent is defined as follows: # # * Find the smallest amount of leading space on any line... # * ...excluding the first line (which may have zero indent without affecting # the common indent)... # * ...and excluding lines that consist solely of whitespace. # * The least common indent may be a zero-length string, if the fragment is # not indented to match code. # * If there are hard tabs for some dumb reason, we assume they're at least # consistent within this doc fragment. # # See tests in spec/unit/util/docs_spec.rb for examples. def scrub(text) # One-liners are easy! (One-liners may be buffered with extra newlines.) return text.strip if text.strip !~ /\n/ excluding_first_line = text.partition("\n").last indent = excluding_first_line.scan(/^[ \t]*(?=\S)/).min || '' # prevent nil # Clean hanging indent, if any if indent.length > 0 text = text.gsub(/^#{indent}/, '') end # Clean trailing space text.lines.map{|line|line.rstrip}.join("\n").rstrip end module_function :scrub end ������puppet-5.5.10/lib/puppet/util/errors.rb�������������������������������������������������������������0000644�0052762�0001160�00000014221�13417161721�017713� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Some helper methods for throwing and populating errors. # # @api public module Puppet::Util::Errors # Throw a Puppet::DevError with the specified message. Used for unknown or # internal application failures. # # @param msg [String] message used in raised error # @raise [Puppet::DevError] always raised with the supplied message def devfail(msg) self.fail(Puppet::DevError, msg) end # Add line and file info to the supplied exception if info is available from # this object, is appropriately populated and the supplied exception supports # it. When other is supplied, the backtrace will be copied to the error # object and the 'original' will be dropped from the error. # # @param error [Exception] exception that is populated with info # @param other [Exception] original exception, source of backtrace info # @return [Exception] error parameter def adderrorcontext(error, other = nil) error.line ||= self.line if error.respond_to?(:line=) and self.respond_to?(:line) and self.line error.file ||= self.file if error.respond_to?(:file=) and self.respond_to?(:file) and self.file error.original ||= other if error.respond_to?(:original=) error.set_backtrace(other.backtrace) if other and other.respond_to?(:backtrace) # It is not meaningful to keep the wrapped exception since its backtrace has already # been adopted by the error. (The instance variable is private for good reasons). error.instance_variable_set(:@original, nil) error end # Return a human-readable string of this object's file, line, and pos attributes, # if set. # # @param file [String] the file path for the error (nil or "", for not known) # @param line [String] the line number for the error (nil or "", for not known) # @param column [String] the column number for the error (nil or "", for not known) # @return [String] description of file, line, and column # def self.error_location(file, line=nil, column=nil) file = nil if (file.is_a?(String) && file.empty?) line = nil if (line.is_a?(String) && line.empty?) column = nil if (column.is_a?(String) && column.empty?) if file and line and column _("(file: %{file}, line: %{line}, column: %{column})") % { file: file, line: line, column: column } elsif file and line _("(file: %{file}, line: %{line})") % { file: file, line: line } elsif line and column _("(line: %{line}, column: %{column})") % { line: line, column: column } elsif line _("(line: %{line})") % { line: line } elsif file _("(file: %{file})") % { file: file } else '' end end # Return a human-readable string of this object's file, line, and pos attributes, # with a proceeding space in the output # if set. # # @param file [String] the file path for the error (nil or "", for not known) # @param line [String] the line number for the error (nil or "", for not known) # @param column [String] the column number for the error (nil or "", for not known) # @return [String] description of file, line, and column # def self.error_location_with_space(file, line=nil, column=nil) error_location_str = error_location(file, line, column) if error_location_str.empty? '' else ' ' + error_location_str end end # Return a human-readable string of this object's file and line # where unknown entries are listed as 'unknown' # # @param file [String] the file path for the error (nil or "", for not known) # @param line [String] the line number for the error (nil or "", for not known) # @return [String] description of file, and line def self.error_location_with_unknowns(file, line) file = nil if (file.is_a?(String) && file.empty?) line = nil if (line.is_a?(String) && line.empty?) file = _('unknown') unless file line = _('unknown') unless line error_location(file, line) end # Return a human-readable string of this object's file and line attributes, # if set. # # @return [String] description of file and line with a leading space def error_context Puppet::Util::Errors.error_location_with_space(file, line) end # Wrap a call in such a way that we always throw the right exception and keep # as much context as possible. # # @param options [Hash<Symbol,Object>] options used to create error # @option options [Class] :type error type to raise, defaults to # Puppet::DevError # @option options [String] :message message to use in error, default mentions # the name of this class # @raise [Puppet::Error] re-raised with extra context if the block raises it # @raise [Error] of type options[:type], when the block raises other # exceptions def exceptwrap(options = {}) options[:type] ||= Puppet::DevError begin return yield rescue Puppet::Error => detail raise adderrorcontext(detail) rescue => detail message = options[:message] || _("%{klass} failed with error %{error_type}: %{detail}") % { klass: self.class, error_type: detail.class, detail: detail } error = options[:type].new(message) # We can't use self.fail here because it always expects strings, # not exceptions. raise adderrorcontext(error, detail) end retval end # Throw an error, defaulting to a Puppet::Error. # # @overload fail(message, ..) # Throw a Puppet::Error with a message concatenated from the given # arguments. # @param [String] message error message(s) # @overload fail(error_klass, message, ..) # Throw an exception of type error_klass with a message concatenated from # the given arguments. # @param [Class] type of error # @param [String] message error message(s) # @overload fail(error_klass, message, ..) # Throw an exception of type error_klass with a message concatenated from # the given arguments. # @param [Class] type of error # @param [String] message error message(s) # @param [Exception] original exception, source of backtrace info def fail(*args) if args[0].is_a?(Class) type = args.shift else type = Puppet::Error end other = args.count > 1 ? args.pop : nil error = adderrorcontext(type.new(args.join(" ")), other) raise error end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/execution_stub.rb�����������������������������������������������������0000644�0052762�0001160�00000001367�13417161721�021446� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Util class ExecutionStub class << self # Set a stub block that Puppet::Util::Execution.execute() should invoke instead # of actually executing commands on the target machine. Intended # for spec testing. # # The arguments passed to the block are |command, options|, where # command is an array of strings and options is an options hash. def set(&block) @value = block end # Uninstall any execution stub, so that calls to # Puppet::Util::Execution.execute() behave normally again. def reset @value = nil end # Retrieve the current execution stub, or nil if there is no stub. def current_value @value end end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/file_watcher.rb�������������������������������������������������������0000644�0052762�0001160�00000000656�13417161721�021042� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Util::FileWatcher include Enumerable def each(&blk) @files.keys.each(&blk) end def initialize @files = {} end def changed? @files.values.any?(&:changed?) end def watch(filename) return if watching?(filename) @files[filename] = Puppet::Util::WatchedFile.new(filename) end def watching?(filename) @files.has_key?(filename) end def clear @files.clear end end ����������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/filetype.rb�����������������������������������������������������������0000644�0052762�0001160�00000027266�13417161721�020235� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Basic classes for reading, writing, and emptying files. Not much # to see here. require 'puppet/util/selinux' require 'tempfile' require 'fileutils' class Puppet::Util::FileType attr_accessor :loaded, :path, :synced class FileReadError < Puppet::Error; end include Puppet::Util::SELinux class << self attr_accessor :name include Puppet::Util::ClassGen end # Create a new filetype. def self.newfiletype(name, &block) @filetypes ||= {} klass = genclass( name, :block => block, :prefix => "FileType", :hash => @filetypes ) # Rename the read and write methods, so that we're sure they # maintain the stats. klass.class_eval do # Rename the read method define_method(:real_read, instance_method(:read)) define_method(:read) do begin val = real_read @loaded = Time.now if val return val.gsub(/# HEADER.*\n/,'') else return "" end rescue Puppet::Error raise rescue => detail message = _("%{klass} could not read %{path}: %{detail}") % { klass: self.class, path: @path, detail: detail } Puppet.log_exception(detail, message) raise Puppet::Error, message, detail.backtrace end end # And then the write method define_method(:real_write, instance_method(:write)) define_method(:write) do |text| begin val = real_write(text) @synced = Time.now return val rescue Puppet::Error raise rescue => detail message = _("%{klass} could not write %{path}: %{detail}") % { klass: self.class, path: @path, detail: detail } Puppet.log_exception(detail, message) raise Puppet::Error, message, detail.backtrace end end end end def self.filetype(type) @filetypes[type] end # Pick or create a filebucket to use. def bucket @bucket ||= Puppet::Type.type(:filebucket).mkdefaultbucket.bucket end def initialize(path, default_mode = nil) raise ArgumentError.new(_("Path is nil")) if path.nil? @path = path @default_mode = default_mode end # Arguments that will be passed to the execute method. Will set the uid # to the target user if the target user and the current user are not # the same def cronargs if uid = Puppet::Util.uid(@path) and uid == Puppet::Util::SUIDManager.uid {:failonfail => true, :combine => true} else {:failonfail => true, :combine => true, :uid => @path} end end # Operate on plain files. newfiletype(:flat) do # Back the file up before replacing it. def backup bucket.backup(@path) if Puppet::FileSystem.exist?(@path) end # Read the file. def read if Puppet::FileSystem.exist?(@path) # this code path is used by many callers so the original default is # being explicitly preserved Puppet::FileSystem.read(@path, :encoding => Encoding.default_external) else return nil end end # Remove the file. def remove Puppet::FileSystem.unlink(@path) if Puppet::FileSystem.exist?(@path) end # Overwrite the file. def write(text) # this file is managed by the OS and should be using system encoding tf = Tempfile.new("puppet", :encoding => Encoding.default_external) tf.print text; tf.flush File.chmod(@default_mode, tf.path) if @default_mode FileUtils.cp(tf.path, @path) tf.close # If SELinux is present, we need to ensure the file has its expected context set_selinux_default_context(@path) end end # Operate on plain files. newfiletype(:ram) do @@tabs = {} def self.clear @@tabs.clear end def initialize(path, default_mode = nil) # default_mode is meaningless for this filetype, # supported only for compatibility with :flat super @@tabs[@path] ||= "" end # Read the file. def read Puppet.info _("Reading %{path} from RAM") % { path: @path } @@tabs[@path] end # Remove the file. def remove Puppet.info _("Removing %{path} from RAM") % { path: @path } @@tabs[@path] = "" end # Overwrite the file. def write(text) Puppet.info _("Writing %{path} to RAM") % { path: @path } @@tabs[@path] = text end end # Handle Linux-style cron tabs. # # TODO: We can possibly eliminate the "-u <username>" option in cmdbase # by just running crontab under <username>'s uid (like we do for suntab # and aixtab). It may be worth investigating this alternative # implementation in the future. This way, we can refactor all three of # our cron file types into a common crontab file type. newfiletype(:crontab) do def initialize(user) self.path = user end def path=(user) begin @uid = Puppet::Util.uid(user) rescue Puppet::Error => detail raise FileReadError, _("Could not retrieve user %{user}: %{detail}") % { user: user, detail: detail }, detail.backtrace end # XXX We have to have the user name, not the uid, because some # systems *cough*linux*cough* require it that way @path = user end # Read a specific @path's cron tab. def read unless Puppet::Util.uid(@path) Puppet.debug _("The %{path} user does not exist. Treating their crontab file as empty in case Puppet creates them in the middle of the run.") % { path: @path } return "" end Puppet::Util::Execution.execute("#{cmdbase} -l", failonfail: true, combine: true) rescue => detail case detail.to_s when /no crontab for/ return "" when /are not allowed to/ Puppet.debug _("The %{path} user is not authorized to use cron. Their crontab file is treated as empty in case Puppet authorizes them in the middle of the run (by, for example, modifying the cron.deny or cron.allow files).") % { path: @path } return "" else raise FileReadError, _("Could not read crontab for %{path}: %{detail}") % { path: @path, detail: detail }, detail.backtrace end end # Remove a specific @path's cron tab. def remove cmd = "#{cmdbase} -r" if %w{Darwin FreeBSD DragonFly}.include?(Facter.value("operatingsystem")) cmd = "/bin/echo yes | #{cmd}" end Puppet::Util::Execution.execute(cmd, failonfail: true, combine: true) end # Overwrite a specific @path's cron tab; must be passed the @path name # and the text with which to create the cron tab. # # TODO: We should refactor this at some point to make it identical to the # :aixtab and :suntab's write methods so that, at the very least, the pipe # is not created and the crontab command's errors are not swallowed. def write(text) unless Puppet::Util.uid(@path) raise Puppet::Error, _("Cannot write the %{path} user's crontab: The user does not exist") % { path: @path } end # this file is managed by the OS and should be using system encoding IO.popen("#{cmdbase()} -", "w", :encoding => Encoding.default_external) { |p| p.print text } end private # Only add the -u flag when the @path is different. Fedora apparently # does not think I should be allowed to set the @path to my own user name def cmdbase if @uid == Puppet::Util::SUIDManager.uid || Facter.value(:operatingsystem) == "HP-UX" return "crontab" else return "crontab -u #{@path}" end end end # SunOS has completely different cron commands; this class implements # its versions. newfiletype(:suntab) do # Read a specific @path's cron tab. def read unless Puppet::Util.uid(@path) Puppet.debug _("The %{path} user does not exist. Treating their crontab file as empty in case Puppet creates them in the middle of the run.") % { path: @path } return "" end Puppet::Util::Execution.execute(%w{crontab -l}, cronargs) rescue => detail case detail.to_s when /can't open your crontab/ return "" when /you are not authorized to use cron/ Puppet.debug _("The %{path} user is not authorized to use cron. Their crontab file is treated as empty in case Puppet authorizes them in the middle of the run (by, for example, modifying the cron.deny or cron.allow files).") % { path: @path } return "" else raise FileReadError, _("Could not read crontab for %{path}: %{detail}") % { path: @path, detail: detail }, detail.backtrace end end # Remove a specific @path's cron tab. def remove Puppet::Util::Execution.execute(%w{crontab -r}, cronargs) rescue => detail raise FileReadError, _("Could not remove crontab for %{path}: %{detail}") % { path: @path, detail: detail }, detail.backtrace end # Overwrite a specific @path's cron tab; must be passed the @path name # and the text with which to create the cron tab. def write(text) # this file is managed by the OS and should be using system encoding output_file = Tempfile.new("puppet_suntab", :encoding => Encoding.default_external) begin output_file.print text output_file.close # We have to chown the stupid file to the user. File.chown(Puppet::Util.uid(@path), nil, output_file.path) Puppet::Util::Execution.execute(["crontab", output_file.path], cronargs) rescue => detail raise FileReadError, _("Could not write crontab for %{path}: %{detail}") % { path: @path, detail: detail }, detail.backtrace ensure output_file.close output_file.unlink end end end # Support for AIX crontab with output different than suntab's crontab command. newfiletype(:aixtab) do # Read a specific @path's cron tab. def read unless Puppet::Util.uid(@path) Puppet.debug _("The %{path} user does not exist. Treating their crontab file as empty in case Puppet creates them in the middle of the run.") % { path: @path } return "" end Puppet::Util::Execution.execute(%w{crontab -l}, cronargs) rescue => detail case detail.to_s when /open.*in.*directory/ return "" when /not.*authorized.*cron/ Puppet.debug _("The %{path} user is not authorized to use cron. Their crontab file is treated as empty in case Puppet authorizes them in the middle of the run (by, for example, modifying the cron.deny or cron.allow files).") % { path: @path } return "" else raise FileReadError, _("Could not read crontab for %{path}: %{detail}") % { path: @path, detail: detail }, detail.backtrace end end # Remove a specific @path's cron tab. def remove Puppet::Util::Execution.execute(%w{crontab -r}, cronargs) rescue => detail raise FileReadError, _("Could not remove crontab for %{path}: %{detail}") % { path: @path, detail: detail }, detail.backtrace end # Overwrite a specific @path's cron tab; must be passed the @path name # and the text with which to create the cron tab. def write(text) # this file is managed by the OS and should be using system encoding output_file = Tempfile.new("puppet_aixtab", :encoding => Encoding.default_external) begin output_file.print text output_file.close # We have to chown the stupid file to the user. File.chown(Puppet::Util.uid(@path), nil, output_file.path) Puppet::Util::Execution.execute(["crontab", output_file.path], cronargs) rescue => detail raise FileReadError, _("Could not write crontab for %{path}: %{detail}") % { path: @path, detail: detail }, detail.backtrace ensure output_file.close output_file.unlink end end end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/http_proxy.rb���������������������������������������������������������0000644�0052762�0001160�00000013344�13417161721�020624� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'uri' require 'openssl' require 'puppet/network/http' module Puppet::Util::HttpProxy def self.proxy(uri) if self.no_proxy?(uri) proxy_class = Net::HTTP::Proxy(nil) else proxy_class = Net::HTTP::Proxy(self.http_proxy_host, self.http_proxy_port, self.http_proxy_user, self.http_proxy_password) end return proxy_class.new(uri.host, uri.port) end def self.http_proxy_env # Returns a URI object if proxy is set, or nil proxy_env = ENV["http_proxy"] || ENV["HTTP_PROXY"] begin return URI.parse(proxy_env) if proxy_env rescue URI::InvalidURIError return nil end return nil end # The documentation around the format of the no_proxy variable seems # inconsistent. Some suggests the use of the * as a way of matching any # hosts under a domain, e.g.: # *.example.com # Other documentation suggests that just a leading '.' indicates a domain # level exclusion, e.g.: # .example.com # We'll accommodate both here. def self.no_proxy?(dest) unless no_proxy_env = ENV["no_proxy"] || ENV["NO_PROXY"] return false end unless dest.is_a? URI begin dest = URI.parse(dest) rescue URI::InvalidURIError return false end end no_proxy_env.split(/\s*,\s*/).each do |d| host, port = d.split(':') host = Regexp.escape(host).gsub('\*', '.*') #If the host of this no_proxy value starts with '.', this entry is #a domain level entry. Don't pin the regex to the beginning of the entry. #If it does not start with a '.' then it is a host specific entry and #should be matched to the destination starting at the beginning. unless host =~ /^\\\./ host = "^#{host}" end #If this no_proxy entry specifies a port, we want to match it against #the destination port. Otherwise just match hosts. if port no_proxy_regex = %r(#{host}:#{port}$) dest_string = "#{dest.host}:#{dest.port}" else no_proxy_regex = %r(#{host}$) dest_string = "#{dest.host}" end if no_proxy_regex.match(dest_string) return true end end return false end def self.http_proxy_host env = self.http_proxy_env if env and env.host return env.host end if Puppet.settings[:http_proxy_host] == 'none' return nil end return Puppet.settings[:http_proxy_host] end def self.http_proxy_port env = self.http_proxy_env if env and env.port return env.port end return Puppet.settings[:http_proxy_port] end def self.http_proxy_user env = self.http_proxy_env if env and env.user return env.user end if Puppet.settings[:http_proxy_user] == 'none' return nil end return Puppet.settings[:http_proxy_user] end def self.http_proxy_password env = self.http_proxy_env if env and env.password return env.password end if Puppet.settings[:http_proxy_user] == 'none' or Puppet.settings[:http_proxy_password] == 'none' return nil end return Puppet.settings[:http_proxy_password] end # Return a Net::HTTP::Proxy object. # # This method optionally configures SSL correctly if the URI scheme is # 'https', including setting up the root certificate store so remote server # SSL certificates can be validated. # # @param [URI] uri The URI that is to be accessed. # @return [Net::HTTP::Proxy] object constructed tailored for the passed URI def self.get_http_object(uri) proxy = proxy(uri) if uri.scheme == 'https' cert_store = OpenSSL::X509::Store.new cert_store.set_default_paths proxy.use_ssl = true proxy.verify_mode = OpenSSL::SSL::VERIFY_PEER proxy.cert_store = cert_store end if Puppet[:http_debug] proxy.set_debug_output($stderr) end proxy.open_timeout = Puppet[:http_connect_timeout] proxy.read_timeout = Puppet[:http_read_timeout] proxy end # Retrieve a document through HTTP(s), following redirects if necessary. The # returned response body may be compressed, and it is the caller's # responsibility to decompress it based on the 'content-encoding' header. # # Based on the the client implementation in the HTTP pool. # # @see Puppet::Network::HTTP::Connection#request_with_redirects # # @param [URI] uri The address of the resource to retrieve. # @param [symbol] method The name of the Net::HTTP method to use, typically :get, :head, :post etc. # @param [FixNum] redirect_limit The number of redirections that can be followed. # @return [Net::HTTPResponse] a response object def self.request_with_redirects(uri, method, redirect_limit = 10, &block) current_uri = uri response = nil 0.upto(redirect_limit) do |redirection| proxy = get_http_object(current_uri) headers = { 'Accept' => '*/*', 'User-Agent' => Puppet[:http_user_agent] } if Puppet.features.zlib? headers.merge!({"Accept-Encoding" => Puppet::Network::HTTP::Compression::ACCEPT_ENCODING}) end response = proxy.send(:head, current_uri.path, headers) if [301, 302, 307].include?(response.code.to_i) # handle the redirection current_uri = URI.parse(response['location']) next end if method != :head if block_given? response = proxy.send("request_#{method}".to_sym, current_uri.path, headers, &block) else response = proxy.send(method, current_uri.path, headers) end end Puppet.debug("HTTP #{method.to_s.upcase} request to #{current_uri} returned #{response.code} #{response.message}") return response end raise RedirectionLimitExceededException, _("Too many HTTP redirections for %{uri}") % { uri: uri } end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/inifile.rb������������������������������������������������������������0000644�0052762�0001160�00000020337�13417161721�020023� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Module Puppet::IniConfig # A generic way to parse .ini style files and manipulate them in memory # One 'file' can be made up of several physical files. Changes to sections # on the file are tracked so that only the physical files in which # something has changed are written back to disk # Great care is taken to preserve comments and blank lines from the original # files # # The parsing tries to stay close to python's ConfigParser require 'puppet/util/filetype' require 'puppet/error' module Puppet::Util::IniConfig # A section in a .ini file class Section attr_reader :name, :file, :entries attr_writer :destroy def initialize(name, file) @name = name @file = file @dirty = false @entries = [] @destroy = false end # Does this section need to be updated in/removed from the associated file? # # @note This section is dirty if a key has been modified _or_ if the # section has been modified so the associated file can be rewritten # without this section. def dirty? @dirty or @destroy end def mark_dirty @dirty = true end # Should only be used internally def mark_clean @dirty = false end # Should the file be destroyed? def destroy? @destroy end # Add a line of text (e.g., a comment) Such lines # will be written back out in exactly the same # place they were read in def add_line(line) @entries << line end # Set the entry 'key=value'. If no entry with the # given key exists, one is appended to the end of the section def []=(key, value) entry = find_entry(key) @dirty = true if entry.nil? @entries << [key, value] else entry[1] = value end end # Return the value associated with KEY. If no such entry # exists, return nil def [](key) entry = find_entry(key) return(entry.nil? ? nil : entry[1]) end # Format the section as text in the way it should be # written to file def format if @destroy text = "" else text = "[#{name}]\n" @entries.each do |entry| if entry.is_a?(Array) key, value = entry text << "#{key}=#{value}\n" unless value.nil? else text << entry end end end text end private def find_entry(key) @entries.each do |entry| return entry if entry.is_a?(Array) && entry[0] == key end nil end end class PhysicalFile # @!attribute [r] filetype # @api private # @return [Puppet::Util::FileType::FileTypeFlat] attr_reader :filetype # @!attribute [r] contents # @api private # @return [Array<String, Puppet::Util::IniConfig::Section>] attr_reader :contents # @!attribute [rw] destroy_empty # Whether empty files should be removed if no sections are defined. # Defaults to false attr_accessor :destroy_empty # @!attribute [rw] file_collection # @return [Puppet::Util::IniConfig::FileCollection] attr_accessor :file_collection def initialize(file, options = {}) @file = file @contents = [] @filetype = Puppet::Util::FileType.filetype(:flat).new(file) @destroy_empty = options.fetch(:destroy_empty, false) end # Read and parse the on-disk file associated with this object def read text = @filetype.read if text.nil? raise IniParseError, _("Cannot read nonexistent file %{file}") % { file: @file.inspect } end parse(text) end INI_COMMENT = Regexp.union( /^\s*$/, /^[#;]/, /^\s*rem\s/i ) INI_CONTINUATION = /^[ \t\r\n\f]/ INI_SECTION_NAME = /^\[([^\]]+)\]/ INI_PROPERTY = /^\s*([^\s=]+)\s*\=\s*(.*)$/ # @api private def parse(text) section = nil # The name of the current section optname = nil # The name of the last option in section line_num = 0 text.each_line do |l| line_num += 1 if l.match(INI_COMMENT) # Whitespace or comment if section.nil? @contents << l else section.add_line(l) end elsif l.match(INI_CONTINUATION) && section && optname # continuation line section[optname] += "\n#{l.chomp}" elsif (match = l.match(INI_SECTION_NAME)) # section heading section.mark_clean if section section_name = match[1] section = add_section(section_name) optname = nil elsif (match = l.match(INI_PROPERTY)) # the regex strips leading white space from the value, and here we strip the trailing white space as well key = match[1] val = match[2].rstrip if section.nil? raise IniParseError.new(_("Property with key %{key} outside of a section") % { key: key.inspect }) end section[key] = val optname = key else raise IniParseError.new(_("Can't parse line '%{line}'") % { line: l.chomp }, @file, line_num) end end section.mark_clean unless section.nil? end # @return [Array<Puppet::Util::IniConfig::Section>] All sections defined in # this file. def sections @contents.select { |entry| entry.is_a? Section } end # @return [Puppet::Util::IniConfig::Section, nil] The section with the # given name if it exists, else nil. def get_section(name) @contents.find { |entry| entry.is_a? Section and entry.name == name } end def format text = "" @contents.each do |content| if content.is_a? Section text << content.format else text << content end end text end def store if @destroy_empty and (sections.empty? or sections.all?(&:destroy?)) ::File.unlink(@file) elsif sections.any?(&:dirty?) text = self.format @filetype.write(text) end sections.each(&:mark_clean) end # Create a new section and store it in the file contents # # @api private # @param name [String] The name of the section to create # @return [Puppet::Util::IniConfig::Section] def add_section(name) if section_exists?(name) raise IniParseError.new(_("Section %{name} is already defined, cannot redefine") % { name: name.inspect }, @file) end section = Section.new(name, @file) @contents << section section end private def section_exists?(name) if self.get_section(name) true elsif @file_collection and @file_collection.get_section(name) true else false end end end class FileCollection attr_reader :files def initialize @files = {} end # Read and parse a file and store it in the collection. If the file has # already been read it will be destroyed and re-read. def read(file) new_physical_file(file).read end def store @files.values.each do |file| file.store end end def each_section(&block) @files.values.each do |file| file.sections.each do |section| yield section end end end def each_file(&block) @files.keys.each do |path| yield path end end def get_section(name) sect = nil @files.values.each do |file| if (current = file.get_section(name)) sect = current end end sect end alias [] get_section def include?(name) !! get_section(name) end def add_section(name, file) get_physical_file(file).add_section(name) end private # Return a file if it's already been defined, create a new file if it hasn't # been defined. def get_physical_file(file) if @files[file] @files[file] else new_physical_file(file) end end # Create a new physical file and set required attributes on that file. def new_physical_file(file) @files[file] = PhysicalFile.new(file) @files[file].file_collection = self @files[file] end end File = FileCollection class IniParseError < Puppet::Error include Puppet::ExternalFileError end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/json_lockfile.rb������������������������������������������������������0000644�0052762�0001160�00000003567�13417161721�021233� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/lockfile' # This class provides a simple API for managing a lock file # whose contents are a serialized JSON object. In addition # to querying the basic state (#locked?) of the lock, managing # the lock (#lock, #unlock), the contents can be retrieved at # any time while the lock is held (#lock_data). This can be # used to store structured data (state messages, etc.) about # the lock. # # @see Puppet::Util::Lockfile class Puppet::Util::JsonLockfile < Puppet::Util::Lockfile # Lock the lockfile. You may optionally pass a data object, which will be # retrievable for the duration of time during which the file is locked. # # @param [Hash] lock_data an optional Hash of data to associate with the lock. # This may be used to store pids, descriptive messages, etc. The # data may be retrieved at any time while the lock is held by # calling the #lock_data method. <b>NOTE</b> that the JSON serialization # does NOT support Symbol objects--if you pass them in, they will be # serialized as Strings, so you should plan accordingly. # @return [boolean] true if lock is successfully acquired, false otherwise. def lock(lock_data = nil) return false if locked? super(Puppet::Util::Json.dump(lock_data)) end # Retrieve the (optional) lock data that was specified at the time the file # was locked. # @return [Object] the data object. Remember that the serialization does not # support Symbol objects, so if your data Object originally contained symbols, # they will be converted to Strings. def lock_data return nil unless file_locked? file_contents = super return nil if file_contents.nil? or file_contents.empty? Puppet::Util::Json.load(file_contents) rescue Puppet::Util::Json::ParseError Puppet.warning _("Unable to read lockfile data from %{path}: not in JSON") % { path: @file_path } nil end end �����������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/ldap.rb���������������������������������������������������������������0000644�0052762�0001160�00000000036�13417161721�017316� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Util::Ldap end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/ldap/�����������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�016777� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/ldap/generator.rb�����������������������������������������������������0000644�0052762�0001160�00000001252�13417161721�021305� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/ldap' class Puppet::Util::Ldap::Generator # Declare the attribute we'll use to generate the value. def from(source) @source = source self end # Actually do the generation. def generate(value = nil) if value.nil? @generator.call else @generator.call(value) end end # Initialize our generator with the name of the parameter # being generated. def initialize(name) @name = name end def name @name.to_s end def source if @source @source.to_s else nil end end # Provide the code that does the generation. def with(&block) @generator = block self end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/ldap/manager.rb�������������������������������������������������������0000644�0052762�0001160�00000017414�13417161721�020740� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/ldap' require 'puppet/util/ldap/connection' require 'puppet/util/ldap/generator' # The configuration class for LDAP providers, plus # connection handling for actually interacting with ldap. class Puppet::Util::Ldap::Manager attr_reader :objectclasses, :puppet2ldap, :location, :rdn # A null-op that just returns the config. def and self end # Set the offset from the search base and return the config. def at(location) @location = location self end # The basic search base. def base [location, Puppet[:ldapbase]].join(",") end # Convert the name to a dn, then pass the args along to # our connection. def create(name, attributes) attributes = attributes.dup # Add the objectclasses attributes["objectClass"] = objectclasses.collect { |o| o.to_s } attributes["objectClass"] << "top" unless attributes["objectClass"].include?("top") attributes[rdn.to_s] = [name] # Generate any new values we might need. generate(attributes) # And create our resource. connect { |conn| conn.add dn(name), attributes } end # Open, yield, and close the connection. Cannot be left # open, at this point. def connect #TRANSLATORS '#connect' is a method name and and should not be translated, 'block' refers to a Ruby code block raise ArgumentError, _("You must pass a block to #connect") unless block_given? unless @connection if Puppet[:ldaptls] ssl = :tls elsif Puppet[:ldapssl] ssl = true else ssl = false end options = {:ssl => ssl} if user = Puppet[:ldapuser] and user != "" options[:user] = user end if password = Puppet[:ldappassword] and password != "" options[:password] = password end @connection = Puppet::Util::Ldap::Connection.new(Puppet[:ldapserver], Puppet[:ldapport], options) end @connection.start begin yield @connection.connection ensure @connection.close end nil end # Convert the name to a dn, then pass the args along to # our connection. def delete(name) connect { |connection| connection.delete dn(name) } end # Calculate the dn for a given resource. def dn(name) ["#{rdn}=#{name}", base].join(",") end # Convert an ldap-style entry hash to a provider-style hash. def entry2provider(entry) #TRANSLATOR 'dn' refers to a 'distinguished name' in LDAP (Lightweight Directory Access Protocol) and they should not be translated raise ArgumentError, _("Could not get dn from ldap entry") unless entry["dn"] # DN is always a single-entry array. Strip off the bits before the # first comma, then the bits after the remaining equal sign. This is the # name. name = entry["dn"].dup.pop.split(",").shift.split("=").pop result = {:name => name} @ldap2puppet.each do |ldap, puppet| result[puppet] = entry[ldap.to_s] || :absent end result end # Create our normal search filter. def filter return(objectclasses.length == 1 ? "objectclass=#{objectclasses[0]}" : "(&(objectclass=" + objectclasses.join(")(objectclass=") + "))") end # Find the associated entry for a resource. Returns a hash, minus # 'dn', or nil if the entry cannot be found. def find(name) connect do |conn| begin conn.search2(dn(name), 0, "objectclass=*") do |result| # Convert to puppet-appropriate attributes return entry2provider(result) end rescue return nil end end end # Declare a new attribute generator. def generates(parameter) @generators << Puppet::Util::Ldap::Generator.new(parameter) @generators[-1] end # Generate any extra values we need to make the ldap entry work. def generate(values) return unless @generators.length > 0 @generators.each do |generator| # Don't override any values that might exist. next if values[generator.name] if generator.source unless value = values[generator.source] raise ArgumentError, _("%{source} must be defined to generate %{name}") % { source: generator.source, name: generator.name } end result = generator.generate(value) else result = generator.generate end result = [result] unless result.is_a?(Array) result = result.collect { |r| r.to_s } values[generator.name] = result end end def initialize @rdn = :cn @generators = [] end # Specify what classes this provider models. def manages(*classes) @objectclasses = classes self end # Specify the attribute map. Assumes the keys are the puppet # attributes, and the values are the ldap attributes, and creates a map # for each direction. def maps(attributes) # The map with the puppet attributes as the keys @puppet2ldap = attributes # and the ldap attributes as the keys. @ldap2puppet = attributes.inject({}) { |map, ary| map[ary[1]] = ary[0]; map } self end # Return the ldap name for a puppet attribute. def ldap_name(attribute) @puppet2ldap[attribute].to_s end # Convert the name to a dn, then pass the args along to # our connection. def modify(name, mods) connect { |connection| connection.modify dn(name), mods } end # Specify the rdn that we use to build up our dn. def named_by(attribute) @rdn = attribute self end # Return the puppet name for an ldap attribute. def puppet_name(attribute) @ldap2puppet[attribute] end # Search for all entries at our base. A potentially expensive search. def search(sfilter = nil) sfilter ||= filter result = [] connect do |conn| conn.search2(base, 1, sfilter) do |entry| result << entry2provider(entry) end end return(result.empty? ? nil : result) end # Update the ldap entry with the desired state. def update(name, is, should) if should[:ensure] == :absent Puppet.info _("Removing %{name} from ldap") % { name: dn(name) } delete(name) return end # We're creating a new entry if is.empty? or is[:ensure] == :absent Puppet.info _("Creating %{name} in ldap") % { name: dn(name) } # Remove any :absent params and :ensure, then convert the names to ldap names. attrs = ldap_convert(should) create(name, attrs) return end # We're modifying an existing entry. Yuck. mods = [] # For each attribute we're deleting that is present, create a # modify instance for deletion. [is.keys, should.keys].flatten.uniq.each do |property| # They're equal, so do nothing. next if is[property] == should[property] attributes = ldap_convert(should) prop_name = ldap_name(property).to_s # We're creating it. if is[property] == :absent or is[property].nil? mods << LDAP::Mod.new(LDAP::LDAP_MOD_ADD, prop_name, attributes[prop_name]) next end # We're deleting it if should[property] == :absent or should[property].nil? mods << LDAP::Mod.new(LDAP::LDAP_MOD_DELETE, prop_name, []) next end # We're replacing an existing value mods << LDAP::Mod.new(LDAP::LDAP_MOD_REPLACE, prop_name, attributes[prop_name]) end modify(name, mods) end # Is this a complete ldap configuration? def valid? location and objectclasses and ! objectclasses.empty? and puppet2ldap end private # Convert a hash of attributes to ldap-like forms. This mostly means # getting rid of :ensure and making sure everything's an array of strings. def ldap_convert(attributes) attributes.reject { |param, value| value == :absent or param == :ensure }.inject({}) do |result, ary| value = (ary[1].is_a?(Array) ? ary[1] : [ary[1]]).collect { |v| v.to_s } result[ldap_name(ary[0])] = value result end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/ldap/connection.rb����������������������������������������������������0000644�0052762�0001160�00000003433�13417161722�021462� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/ldap' require 'puppet/util/methodhelper' class Puppet::Util::Ldap::Connection include Puppet::Util::MethodHelper attr_accessor :host, :port, :user, :password, :reset, :ssl attr_reader :connection # Return a default connection, using our default settings. def self.instance ssl = if Puppet[:ldaptls] :tls elsif Puppet[:ldapssl] true else false end options = {} options[:ssl] = ssl if user = Puppet.settings[:ldapuser] and user != "" options[:user] = user if pass = Puppet.settings[:ldappassword] and pass != "" options[:password] = pass end end new(Puppet[:ldapserver], Puppet[:ldapport], options) end def close connection.unbind if connection.bound? end def initialize(host, port, options = {}) raise Puppet::Error, _("Could not set up LDAP Connection: Missing ruby/ldap libraries") unless Puppet.features.ldap? @host, @port = host, port set_options(options) end # Create a per-connection unique name. def name [host, port, user, password, ssl].collect { |p| p.to_s }.join("/") end # Should we reset the connection? def reset? reset end # Start our ldap connection. def start case ssl when :tls @connection = LDAP::SSLConn.new(host, port, true) when true @connection = LDAP::SSLConn.new(host, port) else @connection = LDAP::Conn.new(host, port) end @connection.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3) @connection.set_option(LDAP::LDAP_OPT_REFERRALS, LDAP::LDAP_OPT_ON) @connection.simple_bind(user, password) rescue => detail raise Puppet::Error, _("Could not connect to LDAP: %{detail}") % { detail: detail }, detail.backtrace end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/libuser.conf����������������������������������������������������������0000644�0052762�0001160�00000000366�13417161721�020373� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������[import] login_defs = /etc/login.defs default_useradd = /etc/default/useradd [defaults] crypt_style = md5 modules = files shadow create_modules = files shadow [userdefaults] LU_USERNAME = %n LU_GIDNUMBER = %u [groupdefaults] LU_GROUPNAME = %n ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/libuser.rb������������������������������������������������������������0000644�0052762�0001160�00000000316�13417161721�020044� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Util::Libuser def self.getconf File.expand_path("../libuser.conf", __FILE__) end def self.getenv newenv = {} newenv['LIBUSER_CONF'] = getconf newenv end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/limits.rb�������������������������������������������������������������0000644�0052762�0001160�00000000506�13417161721�017701� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util' module Puppet::Util::Limits # @api private def setpriority(priority) return unless priority Process.setpriority(0, Process.pid, priority) rescue Errno::EACCES, NotImplementedError Puppet.warning(_("Failed to set process priority to '%{priority}'") % { priority: priority }) end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/lockfile.rb�����������������������������������������������������������0000644�0052762�0001160�00000003753�13417161721�020177� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This class provides a simple API for managing a lock file # whose contents are an (optional) String. In addition # to querying the basic state (#locked?) of the lock, managing # the lock (#lock, #unlock), the contents can be retrieved at # any time while the lock is held (#lock_data). This can be # used to store pids, messages, etc. # # @see Puppet::Util::JsonLockfile class Puppet::Util::Lockfile attr_reader :file_path def initialize(file_path) @file_path = file_path end # Lock the lockfile. You may optionally pass a data object, which will be # retrievable for the duration of time during which the file is locked. # # @param [String] lock_data an optional String data object to associate # with the lock. This may be used to store pids, descriptive messages, # etc. The data may be retrieved at any time while the lock is held by # calling the #lock_data method. # @return [boolean] true if lock is successfully acquired, false otherwise. def lock(lock_data = nil) begin Puppet::FileSystem.exclusive_create(@file_path, nil) do |fd| fd.print(lock_data) end true rescue Errno::EEXIST false end end def unlock if locked? Puppet::FileSystem.unlink(@file_path) true else false end end def locked? # delegate logic to a more explicit private method file_locked? end # Retrieve the (optional) lock data that was specified at the time the file # was locked. # @return [String] the data object. def lock_data return File.read(@file_path) if file_locked? end # Private, internal utility method for encapsulating the logic about # whether or not the file is locked. This method can be called # by other methods in this class without as much risk of accidentally # being overridden by child classes. # @return [boolean] true if the file is locked, false if it is not. def file_locked? Puppet::FileSystem.exist? @file_path end private :file_locked? end ���������������������puppet-5.5.10/lib/puppet/util/log.rb����������������������������������������������������������������0000644�0052762�0001160�00000030044�13417161721�017161� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/tagging' require 'puppet/util/classgen' require 'puppet/util/psych_support' require 'puppet/network/format_support' require 'facter' # Pass feedback to the user. Log levels are modeled after syslog's, and it is # expected that that will be the most common log destination. Supports # multiple destinations, one of which is a remote server. class Puppet::Util::Log include Puppet::Util extend Puppet::Util::ClassGen include Puppet::Util::PsychSupport include Puppet::Util::Tagging include Puppet::Network::FormatSupport @levels = [:debug,:info,:notice,:warning,:err,:alert,:emerg,:crit] @loglevel = 2 @desttypes = {} # Create a new destination type. def self.newdesttype(name, options = {}, &block) dest = genclass( name, :parent => Puppet::Util::Log::Destination, :prefix => "Dest", :block => block, :hash => @desttypes, :attributes => options ) dest.match(dest.name) dest end require 'puppet/util/log/destination' require 'puppet/util/log/destinations' @destinations = {} @queued = [] class << self include Puppet::Util include Puppet::Util::ClassGen attr_reader :desttypes end # Reset log to basics. Basically just flushes and closes files and # undefs other objects. def Log.close(destination) if @destinations.include?(destination) @destinations[destination].flush if @destinations[destination].respond_to?(:flush) @destinations[destination].close if @destinations[destination].respond_to?(:close) @destinations.delete(destination) end end def self.close_all destinations.keys.each { |dest| close(dest) } #TRANSLATORS "Log.close_all" is a method name and should not be translated raise Puppet::DevError.new(_("Log.close_all failed to close %{destinations}") % { destinations: @destinations.keys.inspect }) if !@destinations.empty? end # Flush any log destinations that support such operations. def Log.flush @destinations.each { |type, dest| dest.flush if dest.respond_to?(:flush) } end def Log.autoflush=(v) @destinations.each do |type, dest| dest.autoflush = v if dest.respond_to?(:autoflush=) end end # Create a new log message. The primary role of this method is to # avoid creating log messages below the loglevel. def Log.create(hash) raise Puppet::DevError, _("Logs require a level") unless hash.include?(:level) raise Puppet::DevError, _("Invalid log level %{level}") % { level: hash[:level] } unless @levels.index(hash[:level]) @levels.index(hash[:level]) >= @loglevel ? Puppet::Util::Log.new(hash) : nil end def Log.destinations @destinations end # Yield each valid level in turn def Log.eachlevel @levels.each { |level| yield level } end # Return the current log level. def Log.level @levels[@loglevel] end # Set the current log level. def Log.level=(level) level = level.intern unless level.is_a?(Symbol) raise Puppet::DevError, _("Invalid loglevel %{level}") % { level: level } unless @levels.include?(level) @loglevel = @levels.index(level) # Enable or disable Facter debugging Facter.debugging(level == :debug) if Facter.respond_to? :debugging end def Log.levels @levels.dup end # Create a new log destination. def Log.newdestination(dest) # Each destination can only occur once. if @destinations.find { |name, obj| obj.name == dest } return end _, type = @desttypes.find do |name, klass| klass.match?(dest) end if type.respond_to?(:suitable?) and not type.suitable?(dest) return end raise Puppet::DevError, _("Unknown destination type %{dest}") % { dest: dest} unless type begin if type.instance_method(:initialize).arity == 1 @destinations[dest] = type.new(dest) else @destinations[dest] = type.new end flushqueue @destinations[dest] rescue => detail Puppet.log_exception(detail) # If this was our only destination, then add the console back in. newdestination(:console) if @destinations.empty? and (dest != :console and dest != "console") end end def Log.with_destination(destination, &block) if @destinations.include?(destination) yield else newdestination(destination) begin yield ensure close(destination) end end end def Log.coerce_string(str) return Puppet::Util::CharacterEncoding.convert_to_utf_8(str) if str.valid_encoding? # We only select the last 10 callers in the stack to avoid being spammy message = _("Received a Log attribute with invalid encoding:%{log_message}") % { log_message: Puppet::Util::CharacterEncoding.convert_to_utf_8(str.dump)} message += '\n' + _("Backtrace:\n%{backtrace}") % { backtrace: caller[0..10].join("\n") } message end private_class_method :coerce_string # Route the actual message. FIXME There are lots of things this method # should do, like caching and a bit more. It's worth noting that there's # a potential for a loop here, if the machine somehow gets the destination set as # itself. def Log.newmessage(msg) return if @levels.index(msg.level) < @loglevel msg.message = coerce_string(msg.message) msg.source = coerce_string(msg.source) queuemessage(msg) if @destinations.length == 0 @destinations.each do |name, dest| dest.handle(msg) end end def Log.queuemessage(msg) @queued.push(msg) end def Log.flushqueue return unless @destinations.size >= 1 @queued.each do |msg| Log.newmessage(msg) end @queued.clear end # Flush the logging queue. If there are no destinations available, # adds in a console logger before flushing the queue. # This is mainly intended to be used as a last-resort attempt # to ensure that logging messages are not thrown away before # the program is about to exit--most likely in a horrific # error scenario. # @return nil def Log.force_flushqueue() if (@destinations.empty? and !(@queued.empty?)) newdestination(:console) end flushqueue end def Log.sendlevel?(level) @levels.index(level) >= @loglevel end # Reopen all of our logs. def Log.reopen Puppet.notice _("Reopening log files") types = @destinations.keys @destinations.each { |type, dest| dest.close if dest.respond_to?(:close) } @destinations.clear # We need to make sure we always end up with some kind of destination begin types.each { |type| Log.newdestination(type) } rescue => detail if @destinations.empty? Log.setup_default Puppet.err detail.to_s end end end def self.setup_default Log.newdestination( (Puppet.features.syslog? ? :syslog : (Puppet.features.eventlog? ? :eventlog : Puppet[:puppetdlog]))) end # Is the passed level a valid log level? def self.validlevel?(level) @levels.include?(level) end def self.from_data_hash(data) obj = allocate obj.initialize_from_hash(data) obj end # Log output using scope and level # # @param [Puppet::Parser::Scope] scope # @param [Symbol] level log level # @param [Array<Object>] vals the values to log (will be converted to string and joined with space) # def self.log_func(scope, level, vals) # NOTE: 3x, does this: vals.join(" ") # New implementation uses the evaluator to get proper formatting per type vals = vals.map { |v| Puppet::Pops::Evaluator::EvaluatorImpl.new.string(v, scope) } # Bypass Puppet.<level> call since it picks up source from "self" which is not applicable in the 4x # Function API. # TODO: When a function can obtain the file, line, pos of the call merge those in (3x supports # options :file, :line. (These were never output when calling the 3x logging functions since # 3x scope does not know about the calling location at that detailed level, nor do they # appear in a report to stdout/error when included). Now, the output simply uses scope (like 3x) # as this is good enough, but does not reflect the true call-stack, but is a rough estimate # of where the logging call originates from). # Puppet::Util::Log.create({:level => level, :source => scope, :message => vals.join(" ")}) nil end attr_accessor :time, :remote, :file, :line, :pos, :source, :issue_code, :environment, :node, :backtrace attr_reader :level, :message def initialize(args) self.level = args[:level] self.message = args[:message] self.source = args[:source] || "Puppet" @time = Time.now if tags = args[:tags] tags.each { |t| self.tag(t) } end # Don't add these unless defined (preserve 3.x API as much as possible) [:file, :line, :pos, :issue_code, :environment, :node, :backtrace].each do |attr| next unless value = args[attr] send(attr.to_s + '=', value) end Log.newmessage(self) end def initialize_from_hash(data) @level = data['level'].intern @message = data['message'] @source = data['source'] @tags = Puppet::Util::TagSet.new(data['tags']) @time = data['time'] if @time.is_a? String @time = Time.parse(@time) end # Don't add these unless defined (preserve 3.x API as much as possible) %w(file line pos issue_code environment node backtrace).each do |name| next unless value = data[name] send(name + '=', value) end end def to_hash self.to_data_hash end def to_data_hash { 'level' => @level.to_s, 'message' => to_s, 'source' => @source, 'tags' => @tags.to_a, 'time' => @time.iso8601(9), 'file' => @file, 'line' => @line, } end def to_structured_hash hash = { 'level' => @level, 'message' => @message, 'source' => @source, 'tags' => @tags.to_a, 'time' => @time.iso8601(9), } %w(file line pos issue_code environment node backtrace).each do |name| attr_name = "@#{name}" hash[name] = instance_variable_get(attr_name) if instance_variable_defined?(attr_name) end hash end def message=(msg) #TRANSLATORS 'Puppet::Util::Log' refers to a Puppet source code class raise ArgumentError, _("Puppet::Util::Log requires a message") unless msg @message = msg.to_s end def level=(level) #TRANSLATORS 'Puppet::Util::Log' refers to a Puppet source code class raise ArgumentError, _("Puppet::Util::Log requires a log level") unless level #TRANSLATORS 'Puppet::Util::Log' refers to a Puppet source code class raise ArgumentError, _("Puppet::Util::Log requires a symbol or string") unless level.respond_to? "to_sym" @level = level.to_sym raise ArgumentError, _("Invalid log level %{level}") % { level: @level } unless self.class.validlevel?(@level) # Tag myself with my log level tag(level) end # If they pass a source in to us, we make sure it is a string, and # we retrieve any tags we can. def source=(source) if defined?(Puppet::Type) && source.is_a?(Puppet::Type) @source = source.path merge_tags_from(source) self.file = source.file self.line = source.line else @source = source.to_s end end def to_report "#{time} #{source} (#{level}): #{to_s}" end def to_s msg = message # Issue based messages do not have details in the message. It # must be appended here unless issue_code.nil? msg = _("Could not parse for environment %{environment}: %{msg}") % { environment: environment, msg: msg } unless environment.nil? msg += Puppet::Util::Errors.error_location_with_space(file, line, pos) msg = _("%{msg} on node %{node}") % { msg: msg, node: node } unless node.nil? if @backtrace.is_a?(Array) msg += "\n" msg += @backtrace.join("\n") end end msg end end # This is for backward compatibility from when we changed the constant to # Puppet::Util::Log because the reports include the constant name. It was # considered for removal but left in due to risk of breakage (PUP-7502). Puppet::Log = Puppet::Util::Log ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/log/������������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�016640� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/log/destination.rb����������������������������������������������������0000644�0052762�0001160�00000001732�13417161721�021504� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# A type of log destination. class Puppet::Util::Log::Destination class << self attr_accessor :name end def self.initvars @matches = [] end # Mark the things we're supposed to match. def self.match(obj) @matches ||= [] @matches << obj end # See whether we match a given thing. def self.match?(obj) # Convert single-word strings into symbols like :console and :syslog if obj.is_a? String and obj =~ /^\w+$/ obj = obj.downcase.intern end @matches.each do |thing| # Search for direct matches or class matches return true if thing === obj or thing == obj.class.to_s end false end def name if defined?(@name) return @name else return self.class.name end end # Set how to handle a message. def self.sethandler(&block) define_method(:handle, &block) end # Mark how to initialize our object. def self.setinit(&block) define_method(:initialize, &block) end end ��������������������������������������puppet-5.5.10/lib/puppet/util/log/destinations.rb���������������������������������������������������0000644�0052762�0001160�00000015241�13417161722�021670� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Puppet::Util::Log.newdesttype :syslog do def self.suitable?(obj) Puppet.features.syslog? end def close Syslog.close end def initialize Syslog.close if Syslog.opened? name = "puppet-#{Puppet.run_mode.name}" options = Syslog::LOG_PID | Syslog::LOG_NDELAY # XXX This should really be configurable. str = Puppet[:syslogfacility] begin facility = Syslog.const_get("LOG_#{str.upcase}") rescue NameError raise Puppet::Error, _("Invalid syslog facility %{str}") % { str: str }, $!.backtrace end @syslog = Syslog.open(name, options, facility) end def handle(msg) # XXX Syslog currently has a bug that makes it so you # cannot log a message with a '%' in it. So, we get rid # of them. if msg.source == "Puppet" msg.to_s.split("\n").each do |line| @syslog.send(msg.level, line.gsub("%", '%%')) end else msg.to_s.split("\n").each do |line| @syslog.send(msg.level, "(%s) %s" % [msg.source.to_s.gsub("%", ""), line.gsub("%", '%%') ] ) end end end end Puppet::Util::Log.newdesttype :file do require 'fileutils' def self.match?(obj) Puppet::Util.absolute_path?(obj) end def close if defined?(@file) @file.close @file = nil end end def flush @file.flush if defined?(@file) end attr_accessor :autoflush def initialize(path) @name = path @json = path.end_with?('.json') ? 1 : 0 # first make sure the directory exists # We can't just use 'Config.use' here, because they've # specified a "special" destination. unless Puppet::FileSystem.exist?(Puppet::FileSystem.dir(path)) FileUtils.mkdir_p(File.dirname(path), :mode => 0755) Puppet.info _("Creating log directory %{dir}") % { dir: File.dirname(path) } end # create the log file, if it doesn't already exist need_array_start = false file_exists = File.exists?(path) if @json == 1 need_array_start = true if file_exists sz = File.size(path) need_array_start = sz == 0 # Assume that entries have been written and that a comma # is needed before next entry @json = 2 if sz > 2 end end file = File.open(path, File::WRONLY|File::CREAT|File::APPEND) file.puts('[') if need_array_start # Give ownership to the user and group puppet will run as if Puppet.features.root? && !Puppet::Util::Platform.windows? && !file_exists begin FileUtils.chown(Puppet[:user], Puppet[:group], path) rescue ArgumentError, Errno::EPERM Puppet.err _("Unable to set ownership to %{user}:%{group} for log file: %{path}") % { user: Puppet[:user], group: Puppet[:group], path: path } end end @file = file @autoflush = Puppet[:autoflush] end def handle(msg) if @json > 0 @json > 1 ? @file.puts(',') : @json = 2 @file.puts(Puppet::Util::Json.dump(msg.to_structured_hash)) else @file.puts("#{msg.time} #{msg.source} (#{msg.level}): #{msg}") end @file.flush if @autoflush end end Puppet::Util::Log.newdesttype :logstash_event do require 'time' def format(msg) # logstash_event format is documented at # https://logstash.jira.com/browse/LOGSTASH-675 data = msg.to_hash data['version'] = 1 data['@timestamp'] = data['time'] data.delete('time') data end def handle(msg) message = format(msg) $stdout.puts Puppet::Util::Json.dump(message) end end Puppet::Util::Log.newdesttype :console do require 'puppet/util/colors' include Puppet::Util::Colors def initialize # Flush output immediately. $stderr.sync = true $stdout.sync = true end def handle(msg) levels = { :emerg => { :name => 'Emergency', :color => :hred, :stream => $stderr }, :alert => { :name => 'Alert', :color => :hred, :stream => $stderr }, :crit => { :name => 'Critical', :color => :hred, :stream => $stderr }, :err => { :name => 'Error', :color => :hred, :stream => $stderr }, :warning => { :name => 'Warning', :color => :hyellow, :stream => $stderr }, :notice => { :name => 'Notice', :color => :reset, :stream => $stdout }, :info => { :name => 'Info', :color => :green, :stream => $stdout }, :debug => { :name => 'Debug', :color => :cyan, :stream => $stdout }, } str = msg.respond_to?(:multiline) ? msg.multiline : msg.to_s str = msg.source == "Puppet" ? str : "#{msg.source}: #{str}" level = levels[msg.level] level[:stream].puts colorize(level[:color], "#{level[:name]}: #{str}") end end # Log to a transaction report. Puppet::Util::Log.newdesttype :report do attr_reader :report match "Puppet::Transaction::Report" def initialize(report) @report = report end def handle(msg) @report << msg end end # Log to an array, just for testing. module Puppet::Test class LogCollector def initialize(logs) @logs = logs end def <<(value) @logs << value end end end Puppet::Util::Log.newdesttype :array do match "Puppet::Test::LogCollector" def initialize(messages) @messages = messages end def handle(msg) @messages << msg end end Puppet::Util::Log.newdesttype :eventlog do # Leaving these in for backwards compatibility - duplicates the same in # Puppet::Util::Windows::EventLog Puppet::Util::Log::DestEventlog::EVENTLOG_ERROR_TYPE = 0x0001 Puppet::Util::Log::DestEventlog::EVENTLOG_WARNING_TYPE = 0x0002 Puppet::Util::Log::DestEventlog::EVENTLOG_INFORMATION_TYPE = 0x0004 Puppet::Util::Log::DestEventlog::EVENTLOG_CHARACTER_LIMIT = 31838 def self.suitable?(obj) Puppet.features.microsoft_windows? end def initialize @eventlog = Puppet::Util::Windows::EventLog.open("Puppet") end def to_native(level) Puppet::Util::Windows::EventLog.to_native(level) end def handle(msg) native_type, native_id = to_native(msg.level) stringified_msg = msg.message.to_s if stringified_msg.length > self.class::EVENTLOG_CHARACTER_LIMIT warning = "...Message exceeds character length limit, truncating." truncated_message_length = self.class::EVENTLOG_CHARACTER_LIMIT - warning.length stringified_truncated_msg = stringified_msg[0..truncated_message_length] stringified_truncated_msg << warning msg.message = stringified_truncated_msg end @eventlog.report_event( :event_type => native_type, :event_id => native_id, :data => (msg.source && msg.source != 'Puppet' ? "#{msg.source}: " : '') + msg.to_s ) end def close if @eventlog @eventlog.close @eventlog = nil end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/metaid.rb�������������������������������������������������������������0000644�0052762�0001160�00000001023�13417161721�017636� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Object # The hidden singleton lurks behind everyone def singleton_class; class << self; self; end; end def meta_eval(&blk); singleton_class.instance_eval(&blk); end # Adds methods to a singleton_class def meta_def(name, &blk) meta_eval { define_method name, &blk } end # Remove singleton_class methods. def meta_undef(name, &blk) meta_eval { remove_method name } end # Defines an instance method within a class def class_def(name, &blk) class_eval { define_method name, &blk } end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/metric.rb�������������������������������������������������������������0000644�0052762�0001160�00000002562�13417161721�017667� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# included so we can test object types require 'puppet' require 'puppet/network/format_support' # A class for handling metrics. This is currently ridiculously hackish. class Puppet::Util::Metric include Puppet::Util::PsychSupport include Puppet::Network::FormatSupport attr_accessor :type, :name, :value, :label attr_writer :values def self.from_data_hash(data) metric = allocate metric.initialize_from_hash(data) metric end def initialize_from_hash(data) @name = data['name'] @label = data['label'] || self.class.labelize(@name) @values = data['values'] end def to_data_hash { 'name' => @name, 'label' => @label, 'values' => @values } end # Return a specific value def [](name) if value = @values.find { |v| v[0] == name } return value[2] else return 0 end end def initialize(name,label = nil) @name = name.to_s @label = label || self.class.labelize(name) @values = [] end def newvalue(name,value,label = nil) raise ArgumentError.new("metric name #{name.inspect} is not a string") unless name.is_a? String label ||= self.class.labelize(name) @values.push [name,label,value] end def values @values.sort { |a, b| a[1] <=> b[1] } end # Convert a name into a label. def self.labelize(name) name.to_s.capitalize.gsub("_", " ") end end ����������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/monkey_patches.rb�����������������������������������������������������0000644�0052762�0001160�00000005460�13417161721�021415� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Util::MonkeyPatches end begin Process.maxgroups = 1024 rescue NotImplementedError # Actually, I just want to ignore it, since various platforms - JRuby, # Windows, and so forth - don't support it, but only because it isn't a # meaningful or implementable concept there. end module RDoc def self.caller(skip=nil) in_gem_wrapper = false Kernel.caller.reject { |call| in_gem_wrapper ||= call =~ /#{Regexp.escape $0}:\d+:in `load'/ } end end class Object # ActiveSupport 2.3.x mixes in a dangerous method # that can cause rspec to fork bomb # and other strange things like that. def daemonize raise NotImplementedError, "Kernel.daemonize is too dangerous, please don't try to use it." end end # (#19151) Reject all SSLv2 ciphers and handshakes require 'openssl' class OpenSSL::SSL::SSLContext if DEFAULT_PARAMS[:options] DEFAULT_PARAMS[:options] |= OpenSSL::SSL::OP_NO_SSLv2 | OpenSSL::SSL::OP_NO_SSLv3 else DEFAULT_PARAMS[:options] = OpenSSL::SSL::OP_NO_SSLv2 | OpenSSL::SSL::OP_NO_SSLv3 end if DEFAULT_PARAMS[:ciphers] DEFAULT_PARAMS[:ciphers] << ':!SSLv2' end alias __original_initialize initialize private :__original_initialize def initialize(*args) __original_initialize(*args) params = { :options => DEFAULT_PARAMS[:options], :ciphers => DEFAULT_PARAMS[:ciphers], } set_params(params) end end require 'puppet/util/platform' if Puppet::Util::Platform.windows? require 'puppet/util/windows' require 'openssl' class OpenSSL::X509::Store @puppet_certs_loaded = false alias __original_set_default_paths set_default_paths def set_default_paths # This can be removed once openssl integrates with windows # cert store, see https://rt.openssl.org/Ticket/Display.html?id=2158 unless @puppet_certs_loaded @puppet_certs_loaded = true Puppet::Util::Windows::RootCerts.instance.to_a.uniq { |cert| cert.to_der }.each do |x509| begin add_cert(x509) rescue OpenSSL::X509::StoreError warn "Failed to add #{x509.subject.to_s}" end end end __original_set_default_paths end end end # The Enumerable#uniq method was added in Ruby 2.4.0 (https://bugs.ruby-lang.org/issues/11090) # This is a backport to earlier Ruby versions. # unless Enumerable.instance_methods.include?(:uniq) module Enumerable def uniq result = [] uniq_map = {} if block_given? each do |value| key = yield value next if uniq_map.has_key?(key) uniq_map[key] = true result << value end else each do |value| next if uniq_map.has_key?(value) uniq_map[value] = true result << value end end result end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/multi_match.rb��������������������������������������������������������0000644�0052762�0001160�00000002564�13417161721�020714� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# MultiMatch allows multiple values to be tested at once in a case expression. # This class is needed since Array does not implement the === operator to mean # "each v === other.each v". # # This class is useful in situations when the Puppet Type System cannot be used # (e.g. in Logging, since it needs to be able to log very early in the initialization # cycle of puppet) # # Typically used with the constants # NOT_NIL # TUPLE # TRIPLE # # which test against single NOT_NIL value, Array with two NOT_NIL, and Array with three NOT_NIL # module Puppet::Util class MultiMatch attr_reader :values def initialize(*values) @values = values end def ===(other) lv = @values # local var is faster than instance var case other when MultiMatch return false unless other.values.size == values.size other.values.each_with_index {|v, i| return false unless lv[i] === v || v === lv[i]} when Array return false unless other.size == values.size other.each_with_index {|v, i| return false unless lv[i] === v || v === lv[i]} else false end true end # Matches any value that is not nil using the === operator. # class MatchNotNil def ===(v) !v.nil? end end NOT_NIL = MatchNotNil.new().freeze TUPLE = MultiMatch.new(NOT_NIL, NOT_NIL).freeze TRIPLE = MultiMatch.new(NOT_NIL, NOT_NIL, NOT_NIL).freeze end end ��������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/network_device.rb�����������������������������������������������������0000644�0052762�0001160�00000001073�13417161721�021410� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Util::NetworkDevice class << self attr_reader :current end def self.init(device) require "puppet/util/network_device/#{device.provider}/device" @current = Puppet::Util::NetworkDevice.const_get(device.provider.capitalize).const_get(:Device).new(device.url, device.options) rescue => detail raise detail, _("Can't load %{provider} for %{device}: %{detail}") % { provider: device.provider, device: device.name, detail: detail }, detail.backtrace end # Should only be used in tests def self.teardown @current = nil end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/network_device/�������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�021067� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/network_device/config.rb����������������������������������������������0000644�0052762�0001160�00000006114�13417161721�022656� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'ostruct' require 'puppet/util/watched_file' require 'puppet/util/network_device' class Puppet::Util::NetworkDevice::Config def self.main @main ||= self.new end def self.devices main.devices || [] end attr_reader :devices def exists? Puppet::FileSystem.exist?(@file.to_str) end def initialize @file = Puppet::Util::WatchedFile.new(Puppet[:deviceconfig]) @devices = {} read(true) # force reading at start end # Read the configuration file. def read(force = false) return unless exists? parse if force or @file.changed? end private def parse begin devices = {} device = nil File.open(@file) do |f| file_line_count = 1 f.each do |line| case line when /^\s*#/ # skip comments file_line_count += 1 next when /^\s*$/ # skip blank lines file_line_count += 1 next when /^\[([\w.-]+)\]\s*$/ # [device.fqdn] name = $1 name.chomp! if devices.include?(name) file_error_location = Puppet::Util::Errors.error_location(nil, file_line_count) device_error_location = Puppet::Util::Errors.error_location(nil, device.line) raise Puppet::Error, _("Duplicate device found at %{file_error_location}, already found at %{device_error_location}") % { file_error_location: file_error_location, device_error_location: device_error_location } end device = OpenStruct.new device.name = name device.line = file_line_count device.options = { :debug => false } Puppet.debug "found device: #{device.name} at #{device.line}" devices[name] = device when /^\s*(type|url|debug)(\s+(.+)\s*)*$/ parse_directive(device, $1, $3, file_line_count) else error_location_str = Puppet::Util::Errors.error_location(nil, file_line_count) raise Puppet::Error, _("Invalid entry at %{error_location}: %{file_text}") % { error_location: error_location_str, file_text: line } end end end rescue Errno::EACCES Puppet.err _("Configuration error: Cannot read %{file}; cannot serve") % { file: @file } #raise Puppet::Error, "Cannot read #{@config}" rescue Errno::ENOENT Puppet.err _("Configuration error: '%{file}' does not exit; cannot serve") % { file: @file } end @devices = devices end def parse_directive(device, var, value, count) case var when "type" device.provider = value when "url" begin URI.parse(value) rescue URI::InvalidURIError raise Puppet::Error, _("%{value} is an invalid url") % { value: value } end device.url = value when "debug" device.options[:debug] = true else error_location_str = Puppet::Util::Errors.error_location(nil, count) raise Puppet::Error, _("Invalid argument '%{var}' at %{error_location}") % { var: var, error_location: error_location_str } end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/network_device/transport.rb�������������������������������������������0000644�0052762�0001160�00000000137�13417161721�023444� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/network_device' # stub module Puppet::Util::NetworkDevice::Transport end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/network_device/transport/���������������������������������������������0000755�0052762�0001160�00000000000�13417162176�023123� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/network_device/transport/base.rb��������������������������������������0000644�0052762�0001160�00000000740�13417161721�024356� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� require 'puppet/util/network_device' require 'puppet/util/network_device/transport' class Puppet::Util::NetworkDevice::Transport::Base attr_accessor :user, :password, :host, :port attr_accessor :default_prompt, :timeout def initialize @timeout = 10 end def send(cmd) end def expect(prompt) end def command(cmd, options = {}) send(cmd) expect(options[:prompt] || default_prompt) do |output| yield output if block_given? end end end ��������������������������������puppet-5.5.10/lib/puppet/util/network_device/transport/ssh.rb���������������������������������������0000644�0052762�0001160�00000006466�13417161722�024255� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� require 'puppet/util/network_device' require 'puppet/util/network_device/transport' require 'puppet/util/network_device/transport/base' # This is an adaptation/simplification of gem net-ssh-telnet, which aims to have # a sane interface to Net::SSH. Credits goes to net-ssh-telnet authors class Puppet::Util::NetworkDevice::Transport::Ssh < Puppet::Util::NetworkDevice::Transport::Base attr_accessor :buf, :ssh, :channel def initialize(verbose = false) super() @verbose = verbose unless Puppet.features.ssh? raise _('Connecting with ssh to a network device requires the \'net/ssh\' ruby library') end end def handles_login? true end def eof? !! @eof end def connect(&block) @output = [] @channel_data = "" begin Puppet.debug("connecting to #{host} as #{user}") @ssh = Net::SSH.start(host, user, :port => port, :password => password, :timeout => timeout) rescue TimeoutError raise TimeoutError, _("timed out while opening an ssh connection to the host"), $!.backtrace rescue Net::SSH::AuthenticationFailed raise Puppet::Error, _("SSH authentication failure connecting to %{host} as %{user}") % { host: host, user: user }, $!.backtrace rescue Net::SSH::Exception raise Puppet::Error, _("SSH connection failure to %{host}") % { host: host }, $!.backtrace end @buf = "" @eof = false @channel = nil @ssh.open_channel do |channel| channel.request_pty { |ch,success| raise _("failed to open pty") unless success } channel.send_channel_request("shell") do |ch, success| raise _("failed to open ssh shell channel") unless success ch.on_data { |_,data| @buf << data } ch.on_extended_data { |_,type,data| @buf << data if type == 1 } ch.on_close { @eof = true } @channel = ch expect(default_prompt, &block) # this is a little bit unorthodox, we're trying to escape # the ssh loop there while still having the ssh connection up # otherwise we wouldn't be able to return ssh stdout/stderr # for a given call of command. return end end @ssh.loop end def close begin @channel.close if @channel @channel = nil @ssh.close if @ssh rescue IOError Puppet.debug "device terminated ssh session impolitely" end end def expect(prompt) line = '' sock = @ssh.transport.socket while not @eof break if line =~ prompt and @buf == '' break if sock.closed? IO::select([sock], [sock], nil, nil) process_ssh # at this point we have accumulated some data in @buf # or the channel has been closed if @buf != "" line += @buf.gsub(/\r\n/no, "\n") @buf = '' yield line if block_given? elsif @eof # channel has been closed break if line =~ prompt if line == '' line = nil yield nil if block_given? end break end end Puppet.debug("ssh: expected #{line}") if @verbose line end def send(line) Puppet.debug("ssh: send #{line}") if @verbose @channel.send_data(line + "\n") end def process_ssh while @buf == "" and not eof? begin @channel.connection.process(0.1) rescue IOError @eof = true end end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/network_device/transport/telnet.rb������������������������������������0000644�0052762�0001160�00000002211�13417161722�024733� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/network_device' require 'puppet/util/network_device/transport' require 'puppet/util/network_device/transport/base' if Puppet.features.telnet? require 'net/telnet' class Puppet::Util::NetworkDevice::Transport::Telnet < Puppet::Util::NetworkDevice::Transport::Base def initialize(verbose = false) super() @verbose = verbose end def handles_login? false end def connect @telnet = Net::Telnet::new("Host" => host, "Port" => port || 23, "Timeout" => 10, "Prompt" => default_prompt) end def close @telnet.close if @telnet @telnet = nil end def expect(prompt) @telnet.waitfor(prompt) do |out| yield out if block_given? end end def command(cmd, options = {}) send(cmd) expect(options[:prompt] || default_prompt) do |output| yield output if block_given? end end def send(line) Puppet.debug("telnet: send #{line}") if @verbose @telnet.puts(line) end end end ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/network_device/base.rb������������������������������������������������0000644�0052762�0001160�00000001360�13417161722�022322� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/autoload' require 'uri' require 'puppet/util/network_device/transport' require 'puppet/util/network_device/transport/base' class Puppet::Util::NetworkDevice::Base attr_accessor :url, :transport def initialize(url, options = {}) @url = URI.parse(url) @autoloader = Puppet::Util::Autoload.new(self, "puppet/util/network_device/transport") if @autoloader.load(@url.scheme) @transport = Puppet::Util::NetworkDevice::Transport.const_get(@url.scheme.capitalize).new(options[:debug]) @transport.host = @url.host @transport.port = @url.port || case @url.scheme ; when "ssh" ; 22 ; when "telnet" ; 23 ; end @transport.user = @url.user @transport.password = @url.password end end end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/network_device/cisco.rb�����������������������������������������������0000644�0052762�0001160�00000000060�13417161722�022504� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� module Puppet::Util::NetworkDevice::Cisco end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/network_device/cisco/�������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�022167� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/network_device/cisco/device.rb����������������������������������������0000644�0052762�0001160�00000020342�13417161722�023750� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet' require 'puppet/util' require 'puppet/util/network_device/base' require 'puppet/util/network_device/ipcalc' require 'puppet/util/network_device/cisco/interface' require 'puppet/util/network_device/cisco/facts' require 'ipaddr' class Puppet::Util::NetworkDevice::Cisco::Device < Puppet::Util::NetworkDevice::Base include Puppet::Util::NetworkDevice::IPCalc attr_accessor :enable_password def initialize(url, options = {}) super(url, options) @enable_password = options[:enable_password] || parse_enable(@url.query) transport.default_prompt = /[#>]\s?\z/n end def parse_enable(query) if query params = CGI.parse(query) params['enable'].first unless params['enable'].empty? end end def connect transport.connect login transport.command("terminal length 0") do |out| enable if out =~ />\s?\z/n end find_capabilities end def disconnect transport.close end def command(cmd = nil) connect out = execute(cmd) if cmd yield self if block_given? disconnect out end def execute(cmd) transport.command(cmd) do |out| if out =~ /^%/mo or out =~ /^Command rejected:/mo # strip off the command just sent error = out.sub(cmd,'') Puppet.err _("Error while executing '%{cmd}', device returned: %{error}") % { cmd: cmd, error: error } end end end def login return if transport.handles_login? if @url.user != '' transport.command(@url.user, :prompt => /^Password:/) else transport.expect(/^Password:/) end transport.command(@url.password) end def enable raise _("Can't issue \"enable\" to enter privileged, no enable password set") unless enable_password transport.command("enable", :prompt => /^Password:/) transport.command(enable_password) end def support_vlan_brief? !! @support_vlan_brief end def find_capabilities out = execute("sh vlan brief") lines = out.split("\n") lines.shift; lines.pop @support_vlan_brief = ! (lines.first =~ /^%/) end IF = { :FastEthernet => %w{FastEthernet FastEth Fast FE Fa F}, :GigabitEthernet => %w{GigabitEthernet GigEthernet GigEth GE Gi G}, :TenGigabitEthernet => %w{TenGigabitEthernet TE Te}, :Ethernet => %w{Ethernet Eth E}, :Serial => %w{Serial Se S}, :PortChannel => %w{PortChannel Port-Channel Po}, :POS => %w{POS P}, :VLAN => %w{VLAN VL V}, :Loopback => %w{Loopback Loop Lo}, :ATM => %w{ATM AT A}, :Dialer => %w{Dialer Dial Di D}, :VirtualAccess => %w{Virtual-Access Virtual-A Virtual Virt} } def canonicalize_ifname(interface) IF.each do |k,ifnames| if found = ifnames.find { |ifname| interface =~ /^#{ifname}\s*\d/i } found = /^#{found}(.+)\Z/i.match(interface) return "#{k.to_s}#{found[1]}".gsub(/\s+/,'') end end interface end def facts @facts ||= Puppet::Util::NetworkDevice::Cisco::Facts.new(transport) facts = {} command do |ng| facts = @facts.retrieve end facts end def interface(name) ifname = canonicalize_ifname(name) interface = parse_interface(ifname) return { :ensure => :absent } if interface.empty? interface.merge!(parse_trunking(ifname)) interface.merge!(parse_interface_config(ifname)) end def new_interface(name) Puppet::Util::NetworkDevice::Cisco::Interface.new(canonicalize_ifname(name), transport) end def parse_interface(name) resource = {} out = execute("sh interface #{name}") lines = out.split("\n") lines.shift; lines.pop lines.each do |l| if l =~ /#{name} is (.+), line protocol is / resource[:ensure] = ($1 == 'up' ? :present : :absent); end if l =~ /Auto Speed \(.+\),/ or l =~ /Auto Speed ,/ or l =~ /Auto-speed/ resource[:speed] = :auto end if l =~ /, (.+)Mb\/s/ resource[:speed] = $1 end if l =~ /\s+Auto-duplex \((.{4})\),/ resource[:duplex] = :auto end if l =~ /\s+(.+)-duplex/ resource[:duplex] = $1 == "Auto" ? :auto : $1.downcase.to_sym end if l =~ /Description: (.+)/ resource[:description] = $1 end end resource end def parse_interface_config(name) resource = Hash.new { |hash, key| hash[key] = Array.new ; } out = execute("sh running-config interface #{name} | begin interface") lines = out.split("\n") lines.shift; lines.pop lines.each do |l| if l =~ /ip address (#{IP}) (#{IP})\s+secondary\s*$/ resource[:ipaddress] << [prefix_length(IPAddr.new($2)), IPAddr.new($1), 'secondary'] end if l =~ /ip address (#{IP}) (#{IP})\s*$/ resource[:ipaddress] << [prefix_length(IPAddr.new($2)), IPAddr.new($1), nil] end if l =~ /ipv6 address (#{IP})\/(\d+) (eui-64|link-local)/ resource[:ipaddress] << [$2.to_i, IPAddr.new($1), $3] end if l =~ /channel-group\s+(\d+)/ resource[:etherchannel] = $1 end end resource end def parse_vlans vlans = {} out = execute(support_vlan_brief? ? "sh vlan brief" : "sh vlan-switch brief") lines = out.split("\n") lines.shift; lines.shift; lines.shift; lines.pop vlan = nil lines.each do |l| case l # vlan name status when /^(\d+)\s+(\w+)\s+(\w+)\s+([a-zA-Z0-9,\/. ]+)\s*$/ vlan = { :name => $1, :description => $2, :status => $3, :interfaces => [] } if $4.strip.length > 0 vlan[:interfaces] = $4.strip.split(/\s*,\s*/).map{ |ifn| canonicalize_ifname(ifn) } end vlans[vlan[:name]] = vlan when /^\s+([a-zA-Z0-9,\/. ]+)\s*$/ raise _("invalid sh vlan summary output") unless vlan if $1.strip.length > 0 vlan[:interfaces] += $1.strip.split(/\s*,\s*/).map{ |ifn| canonicalize_ifname(ifn) } end else end end vlans end def update_vlan(id, is = {}, should = {}) if should[:ensure] == :absent Puppet.info _("Removing %{id} from device vlan") % { id: id } execute("conf t") execute("no vlan #{id}") execute("exit") return end # Cisco VLANs are supposed to be alphanumeric only if should[:description] =~ /[^\w]/ Puppet.err _("Invalid VLAN name '%{name}' for Cisco device.\nVLAN name must be alphanumeric, no spaces or special characters.") % { name: should[:description] } return end # We're creating or updating an entry execute("conf t") execute("vlan #{id}") [is.keys, should.keys].flatten.uniq.each do |property| Puppet.debug("trying property: #{property}: #{should[property]}") next if property != :description execute("name #{should[property]}") end execute("exit") execute("exit") end def parse_trunking(interface) trunking = {} out = execute("sh interface #{interface} switchport") lines = out.split("\n") lines.shift; lines.pop lines.each do |l| case l when /^Administrative mode:\s+(.*)$/i case $1 when "trunk" trunking[:mode] = :trunk when "static access" trunking[:mode] = :access when "dynamic auto" trunking[:mode] = 'dynamic auto' when "dynamic desirable" trunking[:mode] = 'dynamic desirable' else raise _("Unknown switchport mode: %{mode} for %{interface}") % { mode: $1, interface: interface } end when /^Administrative Trunking Encapsulation:\s+(.*)$/ case $1 when "dot1q","isl" trunking[:encapsulation] = $1.to_sym if trunking[:mode] != :access when "negotiate" trunking[:encapsulation] = :negotiate else raise _("Unknown switchport encapsulation: %{value} for %{interface}") % { value: $1, interface: interface } end when /^Access Mode VLAN:\s+(.*) \((.*)\)$/ trunking[:access_vlan] = $1 if $2 != '(Inactive)' when /^Trunking Native Mode VLAN:\s+(.*) \(.*\)$/ trunking[:native_vlan] = $1 when /^Trunking VLANs Enabled:\s+(.*)$/ next if trunking[:mode] == :access vlans = $1 trunking[:allowed_trunk_vlans] = case vlans when /all/i :all when /none/i :none else vlans end end end trunking end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/network_device/cisco/facts.rb�����������������������������������������0000644�0052762�0001160�00000007367�13417161722�023625� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� require 'puppet/util/network_device/cisco' require 'puppet/util/network_device/ipcalc' # this retrieves facts from a cisco device class Puppet::Util::NetworkDevice::Cisco::Facts attr_reader :transport def initialize(transport) @transport = transport end def retrieve facts = {} facts.merge(parse_show_ver) end def parse_show_ver facts = {} out = @transport.command("sh ver") lines = out.split("\n") lines.shift; lines.pop lines.each do |l| case l # cisco WS-C2924C-XL (PowerPC403GA) processor (revision 0x11) with 8192K/1024K bytes of memory. # Cisco 1841 (revision 5.0) with 355328K/37888K bytes of memory. # Cisco 877 (MPC8272) processor (revision 0x200) with 118784K/12288K bytes of memory. # cisco WS-C2960G-48TC-L (PowerPC405) processor (revision C0) with 61440K/4088K bytes of memory. # cisco WS-C2950T-24 (RC32300) processor (revision R0) with 19959K bytes of memory. when /[cC]isco ([\w-]+) (?:\(([\w-]+)\) processor )?\(revision (.+)\) with (\d+[KMG])(?:\/(\d+[KMG]))? bytes of memory\./ facts[:hardwaremodel] = $1 facts[:processor] = $2 if $2 facts[:hardwarerevision] = $3 facts[:memorysize] = $4 # uptime # Switch uptime is 1 year, 12 weeks, 6 days, 22 hours, 32 minutes # c2950 uptime is 3 weeks, 1 day, 23 hours, 36 minutes # c2960 uptime is 2 years, 27 weeks, 5 days, 21 hours, 30 minutes # router uptime is 5 weeks, 1 day, 3 hours, 30 minutes when /^\s*([\w-]+)\s+uptime is (.*?)$/ facts[:hostname] = $1 facts[:uptime] = $2 facts[:uptime_seconds] = uptime_to_seconds($2) facts[:uptime_days] = facts[:uptime_seconds] / 86400 # "IOS (tm) C2900XL Software (C2900XL-C3H2S-M), Version 12.0(5)WC10, RELEASE SOFTWARE (fc1)"=> { :operatingsystem => "IOS", :operatingsystemrelease => "12.0(5)WC10", :operatingsystemmajrelease => "12.0", :operatingsystemfeature => "C3H2S"}, # "IOS (tm) C2950 Software (C2950-I6K2L2Q4-M), Version 12.1(22)EA8a, RELEASE SOFTWARE (fc1)"=> { :operatingsystem => "IOS", :operatingsystemrelease => "12.1(22)EA8a", :operatingsystemmajrelease => "12.1", :operatingsystemfeature => "I6K2L2Q4"}, # "Cisco IOS Software, C2960 Software (C2960-LANBASEK9-M), Version 12.2(44)SE, RELEASE SOFTWARE (fc1)"=>{ :operatingsystem => "IOS", :operatingsystemrelease => "12.2(44)SE", :operatingsystemmajrelease => "12.2", :operatingsystemfeature => "LANBASEK9"}, # "Cisco IOS Software, C870 Software (C870-ADVIPSERVICESK9-M), Version 12.4(11)XJ4, RELEASE SOFTWARE (fc2)"=>{ :operatingsystem => "IOS", :operatingsystemrelease => "12.4(11)XJ40", :operatingsystemmajrelease => "12.4XJ", :operatingsystemfeature => "ADVIPSERVICESK9"}, # "Cisco IOS Software, 1841 Software (C1841-ADVSECURITYK9-M), Version 12.4(24)T4, RELEASE SOFTWARE (fc2)" =>{ :operatingsystem => "IOS", :operatingsystemrelease => "12.4(24)T4", :operatingsystemmajrelease => "12.4T", :operatingsystemfeature => "ADVSECURITYK9"}, when /(?:Cisco )?(IOS)\s*(?:\(tm\) |Software, )?(?:\w+)\s+Software\s+\(\w+-(\w+)-\w+\), Version ([0-9.()A-Za-z]+),/ facts[:operatingsystem] = $1 facts[:operatingsystemrelease] = $3 facts[:operatingsystemmajrelease] = ios_major_version(facts[:operatingsystemrelease]) facts[:operatingsystemfeature] = $2 end end facts end def ios_major_version(version) version.gsub(/^(\d+)\.(\d+)\(.+\)([A-Z]+)([\da-z]+)?/, '\1.\2\3') end def uptime_to_seconds(uptime) captures = (uptime.match(/^(?:(\d+) years?,)?\s*(?:(\d+) weeks?,)?\s*(?:(\d+) days?,)?\s*(?:(\d+) hours?,)?\s*(\d+) minutes?$/)).captures captures.zip([31536000, 604800, 86400, 3600, 60]).inject(0) do |total, (x,y)| total + (x.nil? ? 0 : x.to_i * y) end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/network_device/cisco/interface.rb�������������������������������������0000644�0052762�0001160�00000006155�13417161722�024457� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/network_device/cisco' require 'puppet/util/network_device/ipcalc' # this manages setting properties to an interface in a cisco switch or router class Puppet::Util::NetworkDevice::Cisco::Interface include Puppet::Util::NetworkDevice::IPCalc extend Puppet::Util::NetworkDevice::IPCalc attr_reader :transport, :name def initialize(name, transport) @name = name @transport = transport end COMMANDS = { # property => order, ios command/block/array :description => [1, "description %s"], :speed => [2, "speed %s"], :duplex => [3, "duplex %s"], :encapsulation => [4, "switchport trunk encapsulation %s"], :mode => [5, "switchport mode %s"], :access_vlan => [6, "switchport access vlan %s"], :native_vlan => [7, "switchport trunk native vlan %s"], :allowed_trunk_vlans => [8, "switchport trunk allowed vlan %s"], :etherchannel => [9, ["channel-group %s", "port group %s"]], :ipaddress => [10, lambda do |prefix,ip,option| ip.ipv6? ? "ipv6 address #{ip.to_s}/#{prefix} #{option}" : "ip address #{ip.to_s} #{netmask(Socket::AF_INET,prefix)}" end], :ensure => [11, lambda { |value| value == :present ? "no shutdown" : "shutdown" } ] } def update(is={}, should={}) Puppet.debug("Updating interface #{name}") command("conf t") command("interface #{name}") # apply changes in a defined order for cisco IOS devices [is.keys, should.keys].flatten.uniq.sort {|a,b| COMMANDS[a][0] <=> COMMANDS[b][0] }.each do |property| # Work around for old documentation which shows :native_vlan used for access vlan if property == :access_vlan and should[:mode] != :trunk and should[:access_vlan].nil? should[:access_vlan] = should[:native_vlan] end Puppet.debug("comparing #{property}: #{is[property]} == #{should[property]}") # They're equal, so do nothing. next if is[property] == should[property] # We're deleting it if should[property] == :absent or should[property].nil? execute(property, is[property], "no ") next end # We're replacing an existing value or creating a new one execute(property, should[property]) end command("exit") command("exit") end def execute(property, value, prefix='') case COMMANDS[property][1] when Array COMMANDS[property][1].each do |command| transport.command(prefix + command % value) do |out| break unless out =~ /^%/ end end when String command(prefix + COMMANDS[property][1] % value) when Proc value = [value] unless value.is_a?(Array) value.each do |v| command(prefix + COMMANDS[property][1].call(*v)) end end end def command(command) transport.command(command) do |out| if out =~ /^%/mo or out =~ /^Command rejected:/mo # strip off the command just sent error = out.sub(command,'') Puppet.err _("Error while executing '%{command}', device returned: %{error}") % { command: command, error: error } end end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/network_device/ipcalc.rb����������������������������������������������0000644�0052762�0001160�00000003316�13417161722�022646� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/network_device' module Puppet::Util::NetworkDevice::IPCalc # This is a rip-off of authstore Octet = '(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])' IPv4 = "#{Octet}\.#{Octet}\.#{Octet}\.#{Octet}" IPv6_full = "_:_:_:_:_:_:_:_|_:_:_:_:_:_::_?|_:_:_:_:_::((_:)?_)?|_:_:_:_::((_:){0,2}_)?|_:_:_::((_:){0,3}_)?|_:_::((_:){0,4}_)?|_::((_:){0,5}_)?|::((_:){0,6}_)?" IPv6_partial = "_:_:_:_:_:_:|_:_:_:_::(_:)?|_:_::(_:){0,2}|_::(_:){0,3}" IP = "#{IPv4}|#{IPv6_full}".gsub(/_/,'([0-9a-fA-F]{1,4})').gsub(/\(/,'(?:') def parse(value) case value when /^(#{IP})\/(\d+)$/ # 12.34.56.78/24, a001:b002::efff/120, c444:1000:2000::9:192.168.0.1/112 [$2.to_i,IPAddr.new($1)] when /^(#{IP})$/ # 10.20.30.40, value = IPAddr.new(value) [bits(value.family),value] end end def bits(family) family == Socket::AF_INET6 ? 128 : 32 end def fullmask(family) (1 << bits(family)) - 1 end def mask(family, length) (1 << (bits(family) - length)) - 1 end # returns ip address netmask from prefix length def netmask(family, length) IPAddr.new(fullmask(family) & ~mask(family, length) , family) end # returns an IOS wildmask def wildmask(family, length) IPAddr.new(mask(family, length) , family) end # returns ip address prefix length from netmask def prefix_length(netmask) mask_addr = netmask.to_i return 0 if mask_addr == 0 length=32 if (netmask.ipv6?) length=128 end mask = mask_addr < 2**length ? length : 128 mask.times do if ((mask_addr & 1) == 1) break end mask_addr = mask_addr >> 1 mask = mask - 1 end mask end def linklocal?(ip) end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/package.rb������������������������������������������������������������0000644�0052762�0001160�00000001570�13417161721�017775� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Puppet::Util::Package def versioncmp(version_a, version_b) vre = /[-.]|\d+|[^-.\d]+/ ax = version_a.scan(vre) bx = version_b.scan(vre) while (ax.length>0 && bx.length>0) a = ax.shift b = bx.shift if( a == b ) then next elsif (a == '-' && b == '-') then next elsif (a == '-') then return -1 elsif (b == '-') then return 1 elsif (a == '.' && b == '.') then next elsif (a == '.' ) then return -1 elsif (b == '.' ) then return 1 elsif (a =~ /^\d+$/ && b =~ /^\d+$/) then if( a =~ /^0/ or b =~ /^0/ ) then return a.to_s.upcase <=> b.to_s.upcase end return a.to_i <=> b.to_i else return a.upcase <=> b.upcase end end version_a <=> version_b; end module_function :versioncmp end ����������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/pidlock.rb������������������������������������������������������������0000644�0052762�0001160�00000002017�13417161721�020024� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'fileutils' require 'puppet/util/lockfile' class Puppet::Util::Pidlock def initialize(lockfile) @lockfile = Puppet::Util::Lockfile.new(lockfile) end def locked? clear_if_stale @lockfile.locked? end def mine? Process.pid == lock_pid end def lock return mine? if locked? @lockfile.lock(Process.pid) end def unlock if mine? return @lockfile.unlock else false end end def lock_pid pid = @lockfile.lock_data begin Integer(pid) rescue ArgumentError, TypeError nil end end def file_path @lockfile.file_path end def clear_if_stale return @lockfile.unlock if lock_pid.nil? errors = [Errno::ESRCH] # Win32::Process now throws SystemCallError. Since this could be # defined anywhere, only add when on Windows. errors << SystemCallError if Puppet::Util::Platform.windows? begin Process.kill(0, lock_pid) rescue *errors @lockfile.unlock end end private :clear_if_stale end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/plist.rb��������������������������������������������������������������0000644�0052762�0001160�00000013372�13417161721�017540� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'cfpropertylist' if Puppet.features.cfpropertylist? require 'puppet/util/execution' module Puppet::Util::Plist class FormatError < RuntimeError; end # So I don't have to prepend every method name with 'self.' Most of the # methods are going to be Provider methods (as opposed to methods of the # INSTANCE of the provider). class << self # Defines the magic number for binary plists # # @api private def binary_plist_magic_number "bplist00" end # Defines a default doctype string that should be at the top of most plist # files. Useful if we need to modify an invalid doctype string in memory. # In version 10.9 and lower of OS X the plist at # /System/Library/LaunchDaemons/org.ntp.ntpd.plist had an invalid doctype # string. This corrects for that. def plist_xml_doctype '<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">' end # Read a plist file, whether its format is XML or in Apple's "binary1" # format, using the CFPropertyList gem. def read_plist_file(file_path) # We can't really read the file until we know the source encoding in # Ruby 1.9.x, so we use the magic number to detect it. # NOTE: We used IO.read originally to be Ruby 1.8.x compatible. if read_file_with_offset(file_path, binary_plist_magic_number.length) == binary_plist_magic_number plist_obj = new_cfpropertylist(:file => file_path) return convert_cfpropertylist_to_native_types(plist_obj) else plist_data = open_file_with_args(file_path, "r:UTF-8") plist = parse_plist(plist_data, file_path) return plist if plist Puppet.debug "Plist #{file_path} ill-formatted, converting with plutil" begin plist = Puppet::Util::Execution.execute(['/usr/bin/plutil', '-convert', 'xml1', '-o', '-', file_path], {:failonfail => true, :combine => true}) return parse_plist(plist) rescue Puppet::ExecutionFailure => detail message = _("Cannot read file %{file_path}; Puppet is skipping it.") % { file_path: file_path } message += '\n' + _("Details: %{detail}") % { detail: detail } Puppet.warning(message) end end return nil end # Read plist text using the CFPropertyList gem. def parse_plist(plist_data, file_path = '') bad_xml_doctype = /^.*<!DOCTYPE plist PUBLIC -\/\/Apple Computer.*$/ begin if plist_data =~ bad_xml_doctype plist_data.gsub!( bad_xml_doctype, plist_xml_doctype ) Puppet.debug("Had to fix plist with incorrect DOCTYPE declaration: #{file_path}") end rescue ArgumentError => e Puppet.debug "Failed with #{e.class} on #{file_path}: #{e.inspect}" return nil end begin plist_obj = new_cfpropertylist(:data => plist_data) # CFPropertyList library will raise NoMethodError for invalid data rescue CFFormatError, NoMethodError => e Puppet.debug "Failed with #{e.class} on #{file_path}: #{e.inspect}" return nil end convert_cfpropertylist_to_native_types(plist_obj) end # Helper method to assist in reading a file. It's its own method for # stubbing purposes # # @api private # # @param args [String] Extra file operation mode information to use # (defaults to read-only mode 'r') # This is the standard mechanism Ruby uses in the IO class, and therefore # encoding may be explicitly like fmode : encoding or fmode : "BOM|UTF-*" # for example, a:ASCII or w+:UTF-8 def open_file_with_args(file, args) File.open(file, args).read end # Helper method to assist in generating a new CFPropertyList Plist. It's # its own method for stubbing purposes # # @api private def new_cfpropertylist(plist_opts) CFPropertyList::List.new(plist_opts) end # Helper method to assist in converting a native CFPropertyList object to a # native Ruby object (hash). It's its own method for stubbing purposes # # @api private def convert_cfpropertylist_to_native_types(plist_obj) CFPropertyList.native_types(plist_obj.value) end # Helper method to convert a string into a CFProperty::Blob, which is # needed to properly handle binary strings # # @api private def string_to_blob(str) CFPropertyList::Blob.new(str) end # Helper method to assist in reading a file with an offset value. It's its # own method for stubbing purposes # # @api private def read_file_with_offset(file_path, offset) IO.read(file_path, offset) end def to_format(format) if format.to_sym == :xml CFPropertyList::List::FORMAT_XML elsif format.to_sym == :binary CFPropertyList::List::FORMAT_BINARY elsif format.to_sym == :plain CFPropertyList::List::FORMAT_PLAIN else raise FormatError.new "Unknown plist format #{format}" end end # This method will write a plist file using a specified format (or XML # by default) def write_plist_file(plist, file_path, format = :xml) begin plist_to_save = CFPropertyList::List.new plist_to_save.value = CFPropertyList.guess(plist) plist_to_save.save(file_path, to_format(format), :formatted => true) rescue IOError => e Puppet.err(_("Unable to write the file %{file_path}. %{error}") % { file_path: file_path, error: e.inspect }) end end def dump_plist(plist_data, format = :xml) plist_to_save = CFPropertyList::List.new plist_to_save.value = CFPropertyList.guess(plist_data) plist_to_save.to_str(to_format(format), :formatted => true) end end end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/posix.rb��������������������������������������������������������������0000644�0052762�0001160�00000010647�13417161721�017551� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Utility methods for interacting with POSIX objects; mostly user and group module Puppet::Util::POSIX # This is a list of environment variables that we will set when we want to override the POSIX locale LOCALE_ENV_VARS = ['LANG', 'LC_ALL', 'LC_MESSAGES', 'LANGUAGE', 'LC_COLLATE', 'LC_CTYPE', 'LC_MONETARY', 'LC_NUMERIC', 'LC_TIME'] # This is a list of user-related environment variables that we will unset when we want to provide a pristine # environment for "exec" runs USER_ENV_VARS = ['HOME', 'USER', 'LOGNAME'] class << self # Returns an array of all the groups that the user's a member of. def groups_of(user) groups = [] Puppet::Etc.group do |group| groups << group.name if group.mem.include?(user) end uniq_groups = groups.uniq if uniq_groups != groups Puppet.debug(_('Removing any duplicate group entries')) end uniq_groups end end # Retrieve a field from a POSIX Etc object. The id can be either an integer # or a name. This only works for users and groups. It's also broken on # some platforms, unfortunately, which is why we fall back to the other # method search_posix_field in the gid and uid methods if a sanity check # fails def get_posix_field(space, field, id) raise Puppet::DevError, _("Did not get id from caller") unless id if id.is_a?(Integer) if id > Puppet[:maximum_uid].to_i Puppet.err _("Tried to get %{field} field for silly id %{id}") % { field: field, id: id } return nil end method = methodbyid(space) else method = methodbyname(space) end begin return Etc.send(method, id).send(field) rescue NoMethodError, ArgumentError # ignore it; we couldn't find the object return nil end end # A degenerate method of retrieving name/id mappings. The job of this method is # to retrieve all objects of a certain type, search for a specific entry # and then return a given field from that entry. def search_posix_field(type, field, id) idmethod = idfield(type) integer = false if id.is_a?(Integer) integer = true if id > Puppet[:maximum_uid].to_i Puppet.err _("Tried to get %{field} field for silly id %{id}") % { field: field, id: id } return nil end end Etc.send(type) do |object| if integer and object.send(idmethod) == id return object.send(field) elsif object.name == id return object.send(field) end end # Apparently the group/passwd methods need to get reset; if we skip # this call, then new users aren't found. case type when :passwd; Etc.send(:endpwent) when :group; Etc.send(:endgrent) end nil end # Determine what the field name is for users and groups. def idfield(space) case space.intern when :gr, :group; return :gid when :pw, :user, :passwd; return :uid else raise ArgumentError.new(_("Can only handle users and groups")) end end # Determine what the method is to get users and groups by id def methodbyid(space) case space.intern when :gr, :group; return :getgrgid when :pw, :user, :passwd; return :getpwuid else raise ArgumentError.new(_("Can only handle users and groups")) end end # Determine what the method is to get users and groups by name def methodbyname(space) case space.intern when :gr, :group; return :getgrnam when :pw, :user, :passwd; return :getpwnam else raise ArgumentError.new(_("Can only handle users and groups")) end end # Get the GID def gid(group) get_posix_value(:group, :gid, group) end # Get the UID def uid(user) get_posix_value(:passwd, :uid, user) end private # Get the specified id_field of a given field (user or group), # whether an ID name is provided def get_posix_value(location, id_field, field) begin field = Integer(field) rescue ArgumentError # pass end if field.is_a?(Integer) return nil unless name = get_posix_field(location, :name, field) id = get_posix_field(location, id_field, name) check_value = id else return nil unless id = get_posix_field(location, id_field, field) name = get_posix_field(location, :name, id) check_value = name end if check_value != field return search_posix_field(location, id_field, field) else return id end end end �����������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/profiler.rb�����������������������������������������������������������0000644�0052762�0001160�00000002754�13417161721�020231� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'benchmark' # A simple profiling callback system. # # @api public module Puppet::Util::Profiler require 'puppet/util/profiler/wall_clock' require 'puppet/util/profiler/object_counts' require 'puppet/util/profiler/around_profiler' @profiler = Puppet::Util::Profiler::AroundProfiler.new # Reset the profiling system to the original state # # @api private def self.clear @profiler.clear end # Retrieve the current list of profilers # # @api private def self.current @profiler.current end # @param profiler [#profile] A profiler for the current thread # @api private def self.add_profiler(profiler) @profiler.add_profiler(profiler) end # @param profiler [#profile] A profiler to remove from the current thread # @api private def self.remove_profiler(profiler) @profiler.remove_profiler(profiler) end # Profile a block of code and log the time it took to execute. # # This outputs logs entries to the Puppet masters logging destination # providing the time it took, a message describing the profiled code # and a leaf location marking where the profile method was called # in the profiled hierarchy. # # @param message [String] A description of the profiled event # @param metric_id [Array] A list of strings making up the ID of a metric to profile # @param block [Block] The segment of code to profile # @api public def self.profile(message, metric_id, &block) @profiler.profile(message, metric_id, &block) end end ��������������������puppet-5.5.10/lib/puppet/util/profiler/�������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�017701� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/profiler/aggregate.rb�������������������������������������������������0000644�0052762�0001160�00000003273�13417161721�022154� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/profiler' require 'puppet/util/profiler/wall_clock' class Puppet::Util::Profiler::Aggregate < Puppet::Util::Profiler::WallClock def initialize(logger, identifier) super(logger, identifier) @metrics_hash = Metric.new end def shutdown() super @logger.call("AGGREGATE PROFILING RESULTS:") @logger.call("----------------------------") print_metrics(@metrics_hash, "") @logger.call("----------------------------") end def do_start(description, metric_id) super(description, metric_id) end def do_finish(context, description, metric_id) result = super(context, description, metric_id) update_metric(@metrics_hash, metric_id, result[:time]) result end def update_metric(metrics_hash, metric_id, time) first, *rest = *metric_id if first m = metrics_hash[first] m.increment m.add_time(time) if rest.count > 0 update_metric(m, rest, time) end end end def values @metrics_hash end def print_metrics(metrics_hash, prefix) metrics_hash.sort_by {|k,v| v.time }.reverse_each do |k,v| @logger.call("#{prefix}#{k}: #{v.time} s (#{v.count} calls)") print_metrics(metrics_hash[k], "#{prefix}#{k} -> ") end end class Metric < Hash def initialize super @count = 0 @time = 0 end attr_reader :count, :time def [](key) if !has_key?(key) self[key] = Metric.new end super(key) end def increment @count += 1 end def add_time(time) @time += time end end class Timer def initialize @start = Time.now end def stop Time.now - @start end end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/profiler/around_profiler.rb�������������������������������������������0000644�0052762�0001160�00000003310�13417161721�023410� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# A Profiler that can be used to wrap around blocks of code. It is configured # with other profilers and controls them to start before the block is executed # and finish after the block is executed. # # @api private class Puppet::Util::Profiler::AroundProfiler def initialize @profilers = [] end # Reset the profiling system to the original state # # @api private def clear @profilers = [] end # Retrieve the current list of profilers # # @api private def current @profilers end # @param profiler [#profile] A profiler for the current thread # @api private def add_profiler(profiler) @profilers << profiler profiler end # @param profiler [#profile] A profiler to remove from the current thread # @api private def remove_profiler(profiler) @profilers.delete(profiler) end # Profile a block of code and log the time it took to execute. # # This outputs logs entries to the Puppet masters logging destination # providing the time it took, a message describing the profiled code # and a leaf location marking where the profile method was called # in the profiled hierarchy. # # @param message [String] A description of the profiled event # @param metric_id [Array] A list of strings making up the ID of a metric to profile # @param block [Block] The segment of code to profile # @api private def profile(message, metric_id) retval = nil contexts = {} @profilers.each do |profiler| contexts[profiler] = profiler.start(message, metric_id) end begin retval = yield ensure @profilers.each do |profiler| profiler.finish(contexts[profiler], message, metric_id) end end retval end end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/profiler/logging.rb���������������������������������������������������0000644�0052762�0001160�00000001552�13417161721�021652� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������class Puppet::Util::Profiler::Logging def initialize(logger, identifier) @logger = logger @identifier = identifier @sequence = Sequence.new end def start(description, metric_id) @sequence.next @sequence.down do_start(description, metric_id) end def finish(context, description, metric_id) profile_explanation = do_finish(context, description, metric_id)[:msg] @sequence.up @logger.call("PROFILE [#{@identifier}] #{@sequence} #{description}: #{profile_explanation}") end def shutdown() # nothing to do end class Sequence INITIAL = 0 SEPARATOR = '.' def initialize @elements = [INITIAL] end def next @elements[-1] += 1 end def down @elements << INITIAL end def up @elements.pop end def to_s @elements.join(SEPARATOR) end end end ������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/profiler/object_counts.rb���������������������������������������������0000644�0052762�0001160�00000000576�13417161721�023072� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/profiler/logging' class Puppet::Util::Profiler::ObjectCounts < Puppet::Util::Profiler::Logging def start ObjectSpace.count_objects end def finish(before) after = ObjectSpace.count_objects diff = before.collect do |type, count| [type, after[type] - count] end diff.sort.collect { |pair| pair.join(': ') }.join(', ') end end ����������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/profiler/wall_clock.rb������������������������������������������������0000644�0052762�0001160�00000001377�13417161721�022343� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'puppet/util/profiler/logging' # A profiler implementation that measures the number of seconds a segment of # code takes to execute and provides a callback with a string representation of # the profiling information. # # @api private class Puppet::Util::Profiler::WallClock < Puppet::Util::Profiler::Logging def do_start(description, metric_id) Timer.new end def do_finish(context, description, metric_id) {:time => context.stop, :msg => _("took %{context} seconds") % { context: context }} end class Timer FOUR_DECIMAL_DIGITS = '%0.4f' def initialize @start = Time.now end def stop @time = Time.now - @start @time end def to_s format(FOUR_DECIMAL_DIGITS, @time) end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/psych_support.rb������������������������������������������������������0000644�0052762�0001160�00000002560�13417161721�021324� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This module should be included when a class can be serialized to yaml and # needs to handle the deserialization from Psych with more control. Psych normally # pokes values directly into an instance using `instance_variable_set` which completely # bypasses encapsulation. # # The class that includes this module must implement an instance method `initialize_from_hash` # that is given a hash with attribute to value mappings. # module Puppet::Util::PsychSupport # This method is called from the Psych Yaml deserializer when it encounters a tag # in the form !ruby/object:<class name>. # def init_with(psych_coder) # The PsychCoder has a hashmap of instance variable name (sans the @ symbol) to values # to set, and can thus directly be fed to initialize_from_hash. # initialize_from_hash(psych_coder.map) end # This method is called from the Psych Yaml serializer # The serializer will call this method to create a hash that will be serialized to YAML. # Instead of using the object itself during the mapping process we use what is # returned by calling `to_data_hash` on the object itself since some of the # objects we manage have asymmetrical serialization and deserialization. # def encode_with(psych_encoder) tag = Psych.dump_tags[self.class] || "!ruby/object:#{self.class.name}" psych_encoder.represent_map(tag, to_data_hash) end end ������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/rdoc/�����������������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�017006� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/rdoc/code_objects.rb��������������������������������������������������0000644�0052762�0001160�00000015113�13417161721�021752� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'rdoc/code_objects' module RDoc # This modules contains various class that are used to hold information # about the various Puppet language structures we found while parsing. # # Those will be mapped to their html counterparts which are defined in # PuppetGenerator. # PuppetTopLevel is a top level (usually a .pp/.rb file) module PuppetTopLevel attr_accessor :module_name, :global end # Add top level comments to a class or module # @api private module AddClassModuleComment def add_comment(comment, location = nil) super end end # PuppetModule holds a Puppet Module # This is mapped to an HTMLPuppetModule # it leverage the RDoc (ruby) module infrastructure class PuppetModule < NormalModule include AddClassModuleComment attr_accessor :facts, :plugins def initialize(name,superclass=nil) @facts = [] @plugins = [] @nodes = {} super(name,superclass) end def add_plugin(plugin) name = plugin.name type = plugin.type meth = AnyMethod.new("*args", name) meth.params = "(*args)" meth.visibility = :public meth.document_self = true meth.singleton = false meth.comment = plugin.comment if type == 'function' @function_container ||= add_module(NormalModule, "__functions__") @function_container.add_method(meth) elsif type == 'type' @type_container ||= add_module(NormalModule, "__types__") @type_container.add_method(meth) end end def add_fact(fact) @fact_container ||= add_module(NormalModule, "__facts__") confine_str = fact.confine.empty? ? '' : fact.confine.to_s const = Constant.new(fact.name, confine_str, fact.comment) @fact_container.add_constant(const) end # Adds a module called __nodes__ and adds nodes to it as classes # def add_node(name,superclass) if cls = @nodes[name] return cls end @node_container ||= add_module(NormalModule, "__nodes__") cls = @node_container.add_class(PuppetNode, name, superclass) @nodes[name] = cls if !@done_documenting cls end def each_fact @facts.each {|c| yield c} end def each_plugin @plugins.each {|c| yield c} end def each_node @nodes.each {|c| yield c} end def nodes @nodes.values end end # PuppetClass holds a puppet class # It is mapped to a HTMLPuppetClass for display # It leverages RDoc (ruby) Class class PuppetClass < ClassModule include AddClassModuleComment attr_accessor :resource_list, :requires, :childs, :realizes def initialize(name, superclass) super(name,superclass) @resource_list = [] @requires = [] @realizes = [] @childs = [] end def aref_prefix 'puppet_class' end def add_resource(resource) add_to(@resource_list, resource) end def is_module? false end def superclass=(superclass) @superclass = superclass end # we're (ab)using the RDoc require system here. # we're adding a required Puppet class, overriding # the RDoc add_require method which sees ruby required files. def add_require(required) add_to(@requires, required) end def add_realize(realized) add_to(@realizes, realized) end def add_child(child) @childs << child end # Look up the given symbol. RDoc only looks for class1::class2.method # or class1::class2#method. Since our definitions are mapped to RDoc methods # but are written class1::class2::define we need to perform the lookup by # ourselves. def find_symbol(symbol, method=nil) result = super(symbol) if not result and symbol =~ /::/ modules = symbol.split(/::/) unless modules.empty? module_name = modules.shift result = find_module_named(module_name) if result last_name = "" previous = nil modules.each do |mod| previous = result last_name = mod result = result.find_module_named(mod) break unless result end unless result result = previous method = last_name end end end if result && method if !result.respond_to?(:find_local_symbol) p result.name p method fail end result = result.find_local_symbol(method) end end result end end # PuppetNode holds a puppet node # It is mapped to a HTMLPuppetNode for display # A node is just a variation of a class class PuppetNode < PuppetClass include AddClassModuleComment def initialize(name, superclass) super(name,superclass) end def is_module? false end end # Plugin holds a native puppet plugin (function,type...) # It is mapped to a HTMLPuppetPlugin for display class Plugin < Context attr_accessor :name, :type def initialize(name, type) super() @name = name @type = type @comment = "" end def <=>(other) @name <=> other.name end def full_name @name end def http_url(prefix) path = full_name.split("::") File.join(prefix, *path) + ".html" end def is_fact? false end def to_s res = self.class.name + ": #{@name} (#{@type})\n" res << @comment.to_s res end end # Fact holds a custom fact # It is mapped to a HTMLPuppetPlugin for display class Fact < Context attr_accessor :name, :confine def initialize(name, confine) super() @name = name @confine = confine @comment = "" end def <=>(other) @name <=> other.name end def is_fact? true end def full_name @name end def to_s res = self.class.name + ": #{@name}\n" res << @comment.to_s res end end # PuppetResource holds a puppet resource # It is mapped to a HTMLPuppetResource for display # A resource is defined by its "normal" form Type[title] class PuppetResource < CodeObject attr_accessor :type, :title, :params def initialize(type, title, comment, params) super() @type = type @title = title @comment = comment @params = params end def <=>(other) full_name <=> other.full_name end def full_name @type + "[#{@title}]" end def name full_name end def to_s res = @type + "[#{@title}]\n" res << @comment.to_s res end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/rdoc/generators/������������������������������������������������������0000755�0052762�0001160�00000000000�13417162176�021157� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/rdoc/generators/puppet_generator.rb�����������������������������������0000644�0052762�0001160�00000057451�13417161721�025076� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������require 'rdoc/generators/html_generator' require 'puppet/util/rdoc/code_objects' require 'digest/md5' module Generators # This module holds all the classes needed to generate the HTML documentation # of a bunch of puppet manifests. # # It works by traversing all the code objects defined by the Puppet RDoc::Parser # and produces HTML counterparts objects that in turns are used by RDoc template engine # to produce the final HTML. # # It is also responsible of creating the whole directory hierarchy, and various index # files. # # It is to be noted that the whole system is built on top of ruby RDoc. As such there # is an implicit mapping of puppet entities to ruby entitites: # # Puppet => Ruby # ------------------------ # Module Module # Class Class # Definition Method # Resource # Node # Plugin # Fact MODULE_DIR = "modules" NODE_DIR = "nodes" PLUGIN_DIR = "plugins" # We're monkey patching RDoc markup to allow # lowercase class1::class2::class3 crossref hyperlinking module MarkUp alias :old_markup :markup def new_markup(str, remove_para=false) first = @markup.nil? res = old_markup(str, remove_para) if first and not @markup.nil? @markup.add_special(/\b([a-z]\w+(::\w+)*)/,:CROSSREF) # we need to call it again, since we added a rule res = old_markup(str, remove_para) end res end alias :markup :new_markup end # This is a specialized HTMLGenerator tailored to Puppet manifests class PuppetGenerator < HTMLGenerator def PuppetGenerator.for(options) AllReferences::reset HtmlMethod::reset if options.all_one_file PuppetGeneratorInOne.new(options) else PuppetGenerator.new(options) end end def initialize(options) #:not-new: @options = options load_html_template end # loads our own html template file def load_html_template require 'puppet/util/rdoc/generators/template/puppet/puppet' extend RDoc::Page rescue LoadError $stderr.puts "Could not find Puppet template '#{template}'" exit 99 end def gen_method_index # we don't generate an all define index # as the presentation is per module/per class end # This is the central method, it generates the whole structures # along with all the indices. def generate_html super gen_into(@nodes) gen_into(@plugins) end ## # Generate: # the list of modules # the list of classes and definitions of a specific module # the list of all classes # the list of nodes # the list of resources def build_indices @allfiles = [] @nodes = [] @plugins = [] # contains all the seen modules @modules = {} @allclasses = {} # remove unknown toplevels # it can happen that RDoc triggers a different parser for some files (ie .c, .cc or .h) # in this case RDoc generates a RDoc::TopLevel which we do not support in this generator # So let's make sure we don't generate html for those. @toplevels = @toplevels.select { |tl| tl.is_a? RDoc::PuppetTopLevel } # build the modules, classes and per modules classes and define list @toplevels.each do |toplevel| next unless toplevel.document_self file = HtmlFile.new(toplevel, @options, FILE_DIR) classes = [] methods = [] modules = [] nodes = [] # find all classes of this toplevel # store modules if we find one toplevel.each_classmodule do |k| generate_class_list(classes, modules, k, toplevel, CLASS_DIR) end # find all defines belonging to this toplevel HtmlMethod.all_methods.each do |m| # find parent module, check this method is not already # defined. if m.context.parent.toplevel === toplevel methods << m end end classes.each do |k| @allclasses[k.index_name] = k if !@allclasses.has_key?(k.index_name) end # generate nodes and plugins found classes.each do |k| if k.context.is_module? k.context.each_node do |name,node| nodes << HTMLPuppetNode.new(node, toplevel, NODE_DIR, @options) @nodes << nodes.last end k.context.each_plugin do |plugin| @plugins << HTMLPuppetPlugin.new(plugin, toplevel, PLUGIN_DIR, @options) end k.context.each_fact do |fact| @plugins << HTMLPuppetPlugin.new(fact, toplevel, PLUGIN_DIR, @options) end end end @files << file @allfiles << { "file" => file, "modules" => modules, "classes" => classes, "methods" => methods, "nodes" => nodes } end # scan all classes to create the child's references @allclasses.values.each do |klass| if superklass = klass.context.superclass if superklass = AllReferences[superklass] and (superklass.is_a?(HTMLPuppetClass) or superklass.is_a?(HTMLPuppetNode)) superklass.context.add_child(klass.context) end end end @classes = @allclasses.values end # produce a class/module list of HTMLPuppetModule/HTMLPuppetClass # based on the code object traversal. def generate_class_list(classes, modules, from, html_file, class_dir) if from.is_module? and !@modules.has_key?(from.name) k = HTMLPuppetModule.new(from, html_file, class_dir, @options) classes << k @modules[from.name] = k modules << @modules[from.name] elsif from.is_module? modules << @modules[from.name] elsif !from.is_module? k = HTMLPuppetClass.new(from, html_file, class_dir, @options) classes << k end from.each_classmodule do |mod| generate_class_list(classes, modules, mod, html_file, class_dir) end end # generate all the subdirectories, modules, classes and files def gen_sub_directories super File.makedirs(MODULE_DIR) File.makedirs(NODE_DIR) File.makedirs(PLUGIN_DIR) rescue $stderr.puts $ERROR_INFO.message exit 1 end # generate the index of modules def gen_file_index gen_top_index(@modules.values, 'All Modules', RDoc::Page::TOP_INDEX, "fr_modules_index.html") end # generate a top index def gen_top_index(collection, title, template, filename) template = TemplatePage.new(RDoc::Page::FR_INDEX_BODY, template) res = [] collection.sort.each do |f| if f.document_self res << { "classlist" => CGI.escapeHTML("#{MODULE_DIR}/fr_#{f.index_name}.html"), "module" => CGI.escapeHTML("#{CLASS_DIR}/#{f.index_name}.html"),"name" => CGI.escapeHTML(f.index_name) } end end values = { "entries" => res, 'list_title' => CGI.escapeHTML(title), 'index_url' => main_url, 'charset' => @options.charset, 'style_url' => style_url('', @options.css), } Puppet::FileSystem.open(filename, nil, "w:UTF-8") do |f| template.write_html_on(f, values) end end # generate the all classes index file and the combo index def gen_class_index gen_an_index(@classes, 'All Classes', RDoc::Page::CLASS_INDEX, "fr_class_index.html") @allfiles.each do |file| unless file['file'].context.file_relative_name =~ /\.rb$/ gen_composite_index( file, RDoc::Page::COMBO_INDEX, "#{MODULE_DIR}/fr_#{file["file"].context.module_name}.html") end end end def gen_composite_index(collection, template, filename) return if Puppet::FileSystem.exist?(filename) template = TemplatePage.new(RDoc::Page::FR_INDEX_BODY, template) res1 = [] collection['classes'].sort.each do |f| if f.document_self res1 << { "href" => "../"+CGI.escapeHTML(f.path), "name" => CGI.escapeHTML(f.index_name) } unless f.context.is_module? end end res2 = [] collection['methods'].sort.each do |f| res2 << { "href" => "../#{f.path}", "name" => f.index_name.sub(/\(.*\)$/,'') } if f.document_self end module_name = [] res3 = [] res4 = [] collection['modules'].sort.each do |f| module_name << { "href" => "../"+CGI.escapeHTML(f.path), "name" => CGI.escapeHTML(f.index_name) } unless f.facts.nil? f.facts.each do |fact| res3 << {"href" => "../"+CGI.escapeHTML(AllReferences["PLUGIN(#{fact.name})"].path), "name" => CGI.escapeHTML(fact.name)} end end unless f.plugins.nil? f.plugins.each do |plugin| res4 << {"href" => "../"+CGI.escapeHTML(AllReferences["PLUGIN(#{plugin.name})"].path), "name" => CGI.escapeHTML(plugin.name)} end end end res5 = [] collection['nodes'].sort.each do |f| res5 << { "href" => "../"+CGI.escapeHTML(f.path), "name" => CGI.escapeHTML(f.name) } if f.document_self end values = { "module" => module_name, "classes" => res1, 'classes_title' => CGI.escapeHTML("Classes"), 'defines_title' => CGI.escapeHTML("Defines"), 'facts_title' => CGI.escapeHTML("Custom Facts"), 'plugins_title' => CGI.escapeHTML("Plugins"), 'nodes_title' => CGI.escapeHTML("Nodes"), 'index_url' => main_url, 'charset' => @options.charset, 'style_url' => style_url('', @options.css), } values["defines"] = res2 if res2.size>0 values["facts"] = res3 if res3.size>0 values["plugins"] = res4 if res4.size>0 values["nodes"] = res5 if res5.size>0 Puppet::FileSystem.open(filename, nil, "w:UTF-8") do |f| template.write_html_on(f, values) end end # returns the initial_page url def main_url main_page = @options.main_page ref = nil if main_page ref = AllReferences[main_page] if ref ref = ref.path else $stderr.puts "Could not find main page #{main_page}" end end unless ref for file in @files if file.document_self and file.context.global ref = CGI.escapeHTML("#{CLASS_DIR}/#{file.context.module_name}.html") break end end end unless ref for file in @files if file.document_self and !file.context.global ref = CGI.escapeHTML("#{CLASS_DIR}/#{file.context.module_name}.html") break end end end unless ref $stderr.puts "Couldn't find anything to document" $stderr.puts "Perhaps you've used :stopdoc: in all classes" exit(1) end ref end end # This module is used to generate a referenced full name list of ContextUser module ReferencedListBuilder def build_referenced_list(list) res = [] list.each do |i| ref = AllReferences[i.name] || @context.find_symbol(i.name) ref = ref.viewer if ref and ref.respond_to?(:viewer) name = i.respond_to?(:full_name) ? i.full_name : i.name h_name = CGI.escapeHTML(name) if ref and ref.document_self path = url(ref.path) res << { "name" => h_name, "aref" => path } else res << { "name" => h_name } end end res end end # This module is used to hold/generate a list of puppet resources # this is used in HTMLPuppetClass and HTMLPuppetNode module ResourceContainer def collect_resources list = @context.resource_list @resources = list.collect {|m| HTMLPuppetResource.new(m, self, @options) } end def build_resource_summary_list(path_prefix='') collect_resources unless @resources resources = @resources.sort res = [] resources.each do |r| res << { "name" => CGI.escapeHTML(r.name), "aref" => Puppet::Util.uri_encode(path_prefix)+"\#"+Puppet::Util.uri_query_encode(r.aref) } end res end def build_resource_detail_list(section) outer = [] resources = @resources.sort resources.each do |r| row = {} if r.section == section and r.document_self row["name"] = CGI.escapeHTML(r.name) desc = r.description.strip row["m_desc"] = desc unless desc.empty? row["aref"] = r.aref row["params"] = r.params outer << row end end outer end end class HTMLPuppetClass < HtmlClass include ResourceContainer, ReferencedListBuilder def value_hash super rl = build_resource_summary_list @values["resources"] = rl unless rl.empty? @context.sections.each do |section| secdata = @values["sections"].select { |s| s["secsequence"] == section.sequence } if secdata.size == 1 secdata = secdata[0] rdl = build_resource_detail_list(section) secdata["resource_list"] = rdl unless rdl.empty? end end rl = build_require_list(@context) @values["requires"] = rl unless rl.empty? rl = build_realize_list(@context) @values["realizes"] = rl unless rl.empty? cl = build_child_list(@context) @values["childs"] = cl unless cl.empty? @values end def build_require_list(context) build_referenced_list(context.requires) end def build_realize_list(context) build_referenced_list(context.realizes) end def build_child_list(context) build_referenced_list(context.childs) end end class HTMLPuppetNode < ContextUser include ResourceContainer, ReferencedListBuilder attr_reader :path def initialize(context, html_file, prefix, options) super(context, options) @html_file = html_file @is_module = context.is_module? @values = {} context.viewer = self if options.all_one_file @path = context.full_name else @path = http_url(context.full_name, prefix) end AllReferences.add("NODE(#{@context.full_name})", self) end def name @context.name end # return the relative file name to store this class in, # which is also its url def http_url(full_name, prefix) path = full_name.dup path.gsub!(/<<\s*(\w*)/) { "from-#$1" } if path['<<'] File.join(prefix, path.split("::").collect { |p| Digest::MD5.hexdigest(p) }) + ".html" end def parent_name @context.parent.full_name end def index_name name end def write_on(f) value_hash template = TemplatePage.new( RDoc::Page::BODYINC, RDoc::Page::NODE_PAGE, RDoc::Page::METHOD_LIST) template.write_html_on(f, @values) end def value_hash class_attribute_values add_table_of_sections @values["charset"] = @options.charset @values["style_url"] = style_url(path, @options.css) d = markup(@context.comment) @values["description"] = d unless d.empty? ml = build_method_summary_list @values["methods"] = ml unless ml.empty? rl = build_resource_summary_list @values["resources"] = rl unless rl.empty? il = build_include_list(@context) @values["includes"] = il unless il.empty? rl = build_require_list(@context) @values["requires"] = rl unless rl.empty? rl = build_realize_list(@context) @values["realizes"] = rl unless rl.empty? cl = build_child_list(@context) @values["childs"] = cl unless cl.empty? @values["sections"] = @context.sections.map do |section| secdata = { "sectitle" => section.title, "secsequence" => section.sequence, "seccomment" => markup(section.comment) } al = build_alias_summary_list(section) secdata["aliases"] = al unless al.empty? co = build_constants_summary_list(section) secdata["constants"] = co unless co.empty? al = build_attribute_list(section) secdata["attributes"] = al unless al.empty? cl = build_class_list(0, @context, section) secdata["classlist"] = cl unless cl.empty? mdl = build_method_detail_list(section) secdata["method_list"] = mdl unless mdl.empty? rdl = build_resource_detail_list(section) secdata["resource_list"] = rdl unless rdl.empty? secdata end @values end def build_attribute_list(section) atts = @context.attributes.sort res = [] atts.each do |att| next unless att.section == section if att.visibility == :public || att.visibility == :protected || @options.show_all entry = { "name" => CGI.escapeHTML(att.name), "rw" => att.rw, "a_desc" => markup(att.comment, true) } unless att.visibility == :public || att.visibility == :protected entry["rw"] << "-" end res << entry end end res end def class_attribute_values h_name = CGI.escapeHTML(name) @values["classmod"] = "Node" @values["title"] = CGI.escapeHTML("#{@values['classmod']}: #{h_name}") c = @context c = c.parent while c and !c.diagram @values["diagram"] = diagram_reference(c.diagram) if c && c.diagram @values["full_name"] = h_name parent_class = @context.superclass if parent_class @values["parent"] = CGI.escapeHTML(parent_class) if parent_name lookup = parent_name + "::#{parent_class}" else lookup = parent_class end lookup = "NODE(#{lookup})" parent_url = AllReferences[lookup] || AllReferences[parent_class] @values["par_url"] = aref_to(parent_url.path) if parent_url and parent_url.document_self end files = [] @context.in_files.each do |f| res = {} full_path = CGI.escapeHTML(f.file_absolute_name) res["full_path"] = full_path res["full_path_url"] = aref_to(f.viewer.path) if f.document_self res["cvsurl"] = cvs_url( @options.webcvs, full_path ) if @options.webcvs files << res end @values['infiles'] = files end def build_require_list(context) build_referenced_list(context.requires) end def build_realize_list(context) build_referenced_list(context.realizes) end def build_child_list(context) build_referenced_list(context.childs) end def <=>(other) self.name <=> other.name end end class HTMLPuppetModule < HtmlClass def initialize(context, html_file, prefix, options) super(context, html_file, prefix, options) end def value_hash @values = super fl = build_facts_summary_list @values["facts"] = fl unless fl.empty? pl = build_plugins_summary_list @values["plugins"] = pl unless pl.empty? nl = build_nodes_list(0, @context) @values["nodelist"] = nl unless nl.empty? @values end def build_nodes_list(level, context) res = "" prefix = "  ::" * level; context.nodes.sort.each do |node| if node.document_self res << prefix << "Node " << href(url(node.viewer.path), "link", node.full_name) << "<br />\n" end end res end def build_facts_summary_list potentially_referenced_list(context.facts) {|fn| ["PLUGIN(#{fn})"] } end def build_plugins_summary_list potentially_referenced_list(context.plugins) {|fn| ["PLUGIN(#{fn})"] } end def facts @context.facts end def plugins @context.plugins end end class HTMLPuppetPlugin < ContextUser attr_reader :path def initialize(context, html_file, prefix, options) super(context, options) @html_file = html_file @is_module = false @values = {} context.viewer = self if options.all_one_file @path = context.full_name else @path = http_url(context.full_name, prefix) end AllReferences.add("PLUGIN(#{@context.full_name})", self) end def name @context.name end # return the relative file name to store this class in, # which is also its url def http_url(full_name, prefix) path = full_name.dup path.gsub!(/<<\s*(\w*)/) { "from-#$1" } if path['<<'] File.join(prefix, path.split("::")) + ".html" end def parent_name @context.parent.full_name end def index_name name end def write_on(f) value_hash template = TemplatePage.new( RDoc::Page::BODYINC, RDoc::Page::PLUGIN_PAGE, RDoc::Page::PLUGIN_LIST) template.write_html_on(f, @values) end def value_hash attribute_values add_table_of_sections @values["charset"] = @options.charset @values["style_url"] = style_url(path, @options.css) d = markup(@context.comment) @values["description"] = d unless d.empty? if context.is_fact? unless context.confine.empty? res = {} res["type"] = context.confine[:type] res["value"] = context.confine[:value] @values["confine"] = [res] end else @values["type"] = context.type end @values["sections"] = @context.sections.map do |section| secdata = { "sectitle" => section.title, "secsequence" => section.sequence, "seccomment" => markup(section.comment) } secdata end @values end def attribute_values h_name = CGI.escapeHTML(name) if @context.is_fact? @values["classmod"] = "Fact" else @values["classmod"] = "Plugin" end @values["title"] = "#{@values['classmod']}: #{h_name}" @values["full_name"] = h_name files = [] @context.in_files.each do |f| res = {} full_path = CGI.escapeHTML(f.file_absolute_name) res["full_path"] = full_path res["full_path_url"] = aref_to(f.viewer.path) if f.document_self res["cvsurl"] = cvs_url( @options.webcvs, full_path ) if @options.webcvs files << res end @values['infiles'] = files end def <=>(other) self.name <=> other.name end end class HTMLPuppetResource include MarkUp attr_reader :context @@seq = "R000000" def initialize(context, html_class, options) @context = context @html_class = html_class @options = options @@seq = @@seq.succ @seq = @@seq context.viewer = self AllReferences.add(name, self) end def as_href(from_path) if @options.all_one_file "##{path}" else HTMLGenerator.gen_url(from_path, path) end end def name @context.name end def section @context.section end def index_name "#{@context.name}" end def params @context.params end def parent_name if @context.parent.parent @context.parent.parent.full_name else nil end end def aref @seq end def path if @options.all_one_file aref else @html_class.path + "##{aref}" end end def description markup(@context.comment) end def <=>(other) @context <=> other.context end def document_self @context.document_self end def find_symbol(symbol, method=nil) res = @context.parent.find_symbol(symbol, method) res && res.viewer end end class PuppetGeneratorInOne < HTMLGeneratorInOne def gen_method_index gen_an_index(HtmlMethod.all_methods, 'Defines') end end end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/rdoc/generators/template/���������������������������������������������0000755�0052762�0001160�00000000000�13417162176�022772� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/rdoc/generators/template/puppet/��������������������������������������0000755�0052762�0001160�00000000000�13417162176�024307� 5����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������puppet-5.5.10/lib/puppet/util/rdoc/generators/template/puppet/puppet.rb�����������������������������0000644�0052762�0001160�00000057323�13417161721�026156� 0����������������������������������������������������������������������������������������������������ustar �jenkins�������������������������jenkins����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# # = CSS2 RDoc HTML template # # This is a template for RDoc that uses XHTML 1.0 Transitional and dictates a # bit more of the appearance of the output to cascading stylesheets than the # default. It was designed for clean inline code display, and uses DHTMl to # toggle the visbility of each method's source with each click on the '[source]' # link. # # == Authors # # * Michael Granger <ged@FaerieMUD.org> # # Copyright (c) 2002, 2003 The FaerieMUD Consortium. Some rights reserved. # # This work is licensed under the Creative Commons Attribution License. To view # a copy of this license, visit http://creativecommons.org/licenses/by/1.0/ or # send a letter to Creative Commons, 559 Nathan Abbott Way, Stanford, California # 94305, USA. # module RDoc module Page FONTS = "Verdana,Arial,Helvetica,sans-serif" STYLE = %{ /* Reset */ html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-weight:inherit;font-style:inherit;font-size:100%;font-family:inherit;vertical-align:baseline;} :focus{outline:0;} body{line-height:1;color:#282828;background:#fff;} ol,ul{list-style:none;} table{border-collapse:separate;border-spacing:0;} caption,th,td{text-align:left;font-weight:normal;} blockquote:before,blockquote:after,q:before,q:after{content:"";} blockquote,q{quotes:"""";} body { font-family: Verdana,Arial,Helvetica,sans-serif; font-size: 0.9em; } pre { background: none repeat scroll 0 0 #F7F7F7; border: 1px dashed #DDDDDD; color: #555555; font-family: courier; margin: 10px 19px; padding: 10px; } h1,h2,h3,h4 { margin: 0; color: #efefef; background: transparent; } h1 { font-size: 1.2em; } h2,h3,h4 { margin-top: 1em; color:#558; } h2,h3 { font-size: 1.1em; } a { color: #037; text-decoration: none; } a:hover { color: #04d; } /* Override the base stylesheet's Anchor inside a table cell */ td > a { background: transparent; color: #039; text-decoration: none; } /* and inside a section title */ .section-title > a { background: transparent; color: #eee; text-decoration: none; } /* === Structural elements =================================== */ div#index { padding: 0; } div#index a { display:inline-block; padding:2px 10px; } div#index .section-bar { background: #ffe; padding:10px; } div#classHeader, div#fileHeader { border-bottom: 1px solid #ddd; padding:10px; font-size:0.9em; } div#classHeader a, div#fileHeader a { background: inherit; color: white; } div#classHeader td, div#fileHeader td { color: white; padding:3px; font-size:0.9em; } div#fileHeader { background: #057; } div#classHeader { background: #048; } div#nodeHeader { background: #7f7f7f; } .class-name-in-header { font-weight: bold; } div#bodyContent { padding: 10px; } div#description { padding: 10px; background: #f5f5f5; border: 1px dotted #ddd; line-height:1.2em; } div#description h1,h2,h3,h4,h5,h6 { color: #125;; background: transparent; } div#validator-badges { text-align: center; } div#validator-badges img { border: 0; } div#copyright { color: #333; background: #efefef; font: 0.75em sans-serif; margin-top: 5em; margin-bottom: 0; padding: 0.5em 2em; } /* === Classes =================================== */ table.header-table { color: white; font-size: small; } .type-note { font-size: small; color: #DEDEDE; } .xxsection-bar { background: #eee; color: #333; padding: 3px; } .section-bar { color: #333; border-bottom: 1px solid #ddd; padding:10px 0; margin:5px 0 10px 0; } div#class-list, div#methods, div#includes, div#resources, div#requires, div#realizes, div#attribute-list { padding:10px; } .section-title { background: #79a; color: #eee; padding: 3px; margin-top: 2em; border: 1px solid #999; } .top-aligned-row { vertical-align: top } .bottom-aligned-row { vertical-align: bottom } /* --- Context section classes ----------------------- */ .context-row { } .context-item-name { font-family: monospace; font-weight: bold; color: black; } .context-item-value { font-size: small; color: #448; } .context-item-desc { color: #333; padding-left: 2em; } /* --- Method classes -------------------------- */ .method-detail { background: #f5f5f5; } .method-heading { color: #333; font-style:italic; background: #ddd; padding:5px 10px; } .method-signature { color: black; background: inherit; } .method-name { font-weight: bold; } .method-args { font-style: italic; } .method-description { padding: 10px 10px 20px 10px; } /* --- Source code sections -------------------- */ a.source-toggle { font-size: 90%; } div.method-source-code { background: #262626; color: #ffdead; margin: 1em; padding: 0.5em; border: 1px dashed #999; overflow: hidden; } div.method-source-code pre { color: #ffdead; overflow: hidden; } /* --- Ruby keyword styles --------------------- */ .standalone-code { background: #221111; color: #ffdead; overflow: hidden; } .ruby-constant { color: #7fffd4; background: transparent; } .ruby-keyword { color: #00ffff; background: transparent; } .ruby-ivar { color: #eedd82; background: transparent; } .ruby-operator { color: #00ffee; background: transparent; } .ruby-identifier { color: #ffdead; background: transparent; } .ruby-node { color: #ffa07a; background: transparent; } .ruby-comment { color: #b22222; font-weight: bold; background: transparent; } .ruby-regexp { color: #ffa07a; background: transparent; } .ruby-value { color: #7fffd4; background: transparent; } } ##################################################################### ### H E A D E R T E M P L A T E ##################################################################### XHTML_PREAMBLE = %{<?xml version="1.0" encoding="%charset%"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> } HEADER = XHTML_PREAMBLE + %{ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <title>%title% } ##################################################################### ### C O N T E X T C O N T E N T T E M P L A T E ##################################################################### CONTEXT_CONTENT = %{ } ##################################################################### ### F O O T E R T E M P L A T E ##################################################################### FOOTER = %{ } ##################################################################### ### F I L E P A G E H E A D E R T E M P L A T E ##################################################################### FILE_PAGE = %{

%short_name%

Path: %full_path% IF:cvsurl  (CVS) ENDIF:cvsurl
Last Update: %dtm_modified%
} ##################################################################### ### C L A S S P A G E H E A D E R T E M P L A T E ##################################################################### CLASS_PAGE = %{
IF:parent ENDIF:parent
%classmod% %full_name%
In: START:infiles IF:full_path_url ENDIF:full_path_url %full_path% IF:full_path_url ENDIF:full_path_url IF:cvsurl  (CVS) ENDIF:cvsurl
END:infiles
Parent: IF:par_url ENDIF:par_url %parent% IF:par_url ENDIF:par_url
} NODE_PAGE = %{
IF:parent ENDIF:parent
%classmod% %full_name%
In: START:infiles IF:full_path_url ENDIF:full_path_url %full_path% IF:full_path_url ENDIF:full_path_url IF:cvsurl  (CVS) ENDIF:cvsurl
END:infiles
Parent: IF:par_url ENDIF:par_url %parent% IF:par_url ENDIF:par_url
} PLUGIN_PAGE = %{
%classmod% %full_name%
In: START:infiles IF:full_path_url ENDIF:full_path_url %full_path% IF:full_path_url ENDIF:full_path_url IF:cvsurl  (CVS) ENDIF:cvsurl
END:infiles
} ##################################################################### ### M E T H O D L I S T T E M P L A T E ##################################################################### PLUGIN_LIST = %{
IF:description
%description%
ENDIF:description IF:toc

Contents

ENDIF:toc
IF:confine START:confine

Confine

%type% %value%
END:confine ENDIF:confine IF:type

Type

%type%
ENDIF:type START:sections
IF:sectitle

%sectitle%

IF:seccomment
%seccomment%
ENDIF:seccomment ENDIF:sectitle END:sections } METHOD_LIST = %{
IF:diagram
%diagram%
ENDIF:diagram IF:description
%description%
ENDIF:description IF:toc

Contents

ENDIF:toc
IF:childs

Inherited by

START:childs HREF:aref:name: END:childs
ENDIF:childs IF:methods

Defines

START:methods HREF:aref:name:   END:methods
ENDIF:methods IF:resources

Resources

START:resources HREF:aref:name:   END:resources
ENDIF:resources
IF:includes

Included Classes

START:includes HREF:aref:name: END:includes
ENDIF:includes IF:requires

Required Classes

START:requires HREF:aref:name: END:requires
ENDIF:requires IF:realizes

Realized Resources

START:realizes HREF:aref:name: END:realizes
ENDIF:realizes START:sections
IF:sectitle

%sectitle%

IF:seccomment
%seccomment%
ENDIF:seccomment ENDIF:sectitle IF:facts

Custom Facts

START:facts HREF:aref:name:   END:facts
ENDIF:facts IF:plugins

Plugins

START:plugins HREF:aref:name:   END:plugins
ENDIF:plugins IF:nodelist

Nodes

%nodelist%
ENDIF:nodelist IF:classlist

Classes and Modules

%classlist%
ENDIF:classlist IF:constants

Global Variables

START:constants IF:desc ENDIF:desc END:constants
%name% = %value%  %desc%
ENDIF:constants IF:aliases

External Aliases

START:aliases IF:desc ENDIF:desc END:aliases
%old_name% -> %new_name%
  %desc%
ENDIF:aliases IF:attributes

Attributes

START:attributes IF:rw ENDIF:rw IFNOT:rw ENDIF:rw END:attributes
%name% [%rw%]   %a_desc%
ENDIF:attributes IF:method_list
START:method_list IF:methods

Defines

START:methods
IF:m_desc %m_desc% ENDIF:m_desc IF:sourcecode

[Source]

%sourcecode%
ENDIF:sourcecode
END:methods ENDIF:methods END:method_list
ENDIF:method_list IF:resource_list

Resources

START:resource_list
%name%
IF:params START:params    %name% => %value%
END:params ENDIF:params
IF:m_desc %m_desc% ENDIF:m_desc
END:resource_list
ENDIF:resource_list END:sections } ##################################################################### ### B O D Y T E M P L A T E ##################################################################### BODY = HEADER + %{ !INCLUDE!
} + METHOD_LIST + %{
} + FOOTER BODYINC = HEADER + %{ !INCLUDE!
!INCLUDE!
} + FOOTER ##################################################################### ### S O U R C E C O D E T E M P L A T E ##################################################################### SRC_PAGE = XHTML_PREAMBLE + %{ %title%
%code%
} ##################################################################### ### I N D E X F I L E T E M P L A T E S ##################################################################### FR_INDEX_BODY = %{ !INCLUDE! } FILE_INDEX = XHTML_PREAMBLE + %{ %list_title%

%list_title%

START:entries %name%
END:entries
} TOP_INDEX = XHTML_PREAMBLE + %{ %list_title%

%list_title%

START:entries %name%
END:entries
} CLASS_INDEX = FILE_INDEX METHOD_INDEX = FILE_INDEX COMBO_INDEX = XHTML_PREAMBLE + %{ %classes_title% & %defines_title%
All Classes

Module

START:module %name%
END:module
IF:nodes

%nodes_title%

START:nodes %name%
END:nodes
ENDIF:nodes IF:classes

%classes_title%

START:classes %name%
END:classes
ENDIF:classes IF:defines

%defines_title%

START:defines %name%
END:defines
ENDIF:defines IF:facts

%facts_title%

START:facts %name%
END:facts
ENDIF:facts IF:plugins

%plugins_title%

START:plugins %name%
END:plugins
ENDIF:plugins
} INDEX = %{ %title% } end # module Page end # class RDoc require 'rdoc/generators/template/html/one_page_html' puppet-5.5.10/lib/puppet/util/rdoc/parser.rb0000644005276200011600000000073213417161721020624 0ustar jenkinsjenkins# Puppet "parser" for the rdoc system # The parser uses puppet parser and traverse the AST to instruct RDoc about # our current structures. It also parses ruby files that could contain # either custom facts or puppet plugins (functions, types...) # rdoc2 includes require "rdoc/code_objects" require "puppet/util/rdoc/code_objects" require "rdoc/token_stream" require "rdoc/markup/pre_process" require "rdoc/parser" require "puppet/util/rdoc/parser/puppet_parser_rdoc2.rb" puppet-5.5.10/lib/puppet/util/rdoc/parser/0000755005276200011600000000000013417162176020302 5ustar jenkinsjenkinspuppet-5.5.10/lib/puppet/util/rdoc/parser/puppet_parser_rdoc2.rb0000644005276200011600000000045113417161721024604 0ustar jenkinsjenkinsrequire 'puppet/util/rdoc/parser/puppet_parser_core.rb' module RDoc PUPPET_RDOC_VERSION = 2 # @api private class PuppetParserRDoc2 < Parser include PuppetParserCore def create_rdoc_preprocess Markup::PreProcess.new(@input_file_name, @options.rdoc_include) end end end puppet-5.5.10/lib/puppet/util/rdoc/parser/puppet_parser_core.rb0000644005276200011600000002050313417161722024524 0ustar jenkinsjenkins# Functionality common to both our RDoc version 1 and 2 parsers. module RDoc::PuppetParserCore SITE = "__site__" def self.included(base) base.class_eval do attr_accessor :input_file_name, :top_level # parser registration into RDoc parse_files_matching(/\.(rb)$/) end end # called with the top level file def initialize(top_level, file_name, body, options, stats) @options = options @stats = stats @input_file_name = file_name @top_level = top_level @top_level.extend(RDoc::PuppetTopLevel) @progress = $stderr unless options.quiet end # main entry point def scan environment = Puppet.lookup(:current_environment) scan_top_level(@top_level, environment) @top_level end # Due to a bug in RDoc, we need to roll our own find_module_named # The issue is that RDoc tries harder by asking the parent for a class/module # of the name. But by doing so, it can mistakenly use a module of same name # but from which we are not descendant. def find_object_named(container, name) return container if container.name == name container.each_classmodule do |m| return m if m.name == name end nil end # walk down the namespace and lookup/create container as needed def get_class_or_module(container, name) # class ::A -> A is in the top level if name =~ /^::/ container = @top_level end names = name.split('::') final_name = names.pop names.each do |n| prev_container = container container = find_object_named(container, n) container ||= prev_container.add_class(RDoc::PuppetClass, n, nil) end [container, final_name] end # split_module tries to find if +path+ belongs to the module path # if it does, it returns the module name, otherwise if we are sure # it is part of the global manifest path, "__site__" is returned. # And finally if this path couldn't be mapped anywhere, nil is returned. def split_module(path, environment) # find a module fullpath = File.expand_path(path) Puppet.debug "rdoc: testing #{fullpath}" if fullpath =~ /(.*)\/([^\/]+)\/(?:manifests|plugins|lib)\/.+\.(rb)$/ modpath = $1 name = $2 Puppet.debug "rdoc: module #{name} into #{modpath} ?" environment.modulepath.each do |mp| if File.identical?(modpath,mp) Puppet.debug "rdoc: found module #{name}" return name end end end if fullpath =~ /\.(rb)$/ # there can be paths we don't want to scan under modules # imagine a ruby or manifest that would be distributed as part as a module # but we don't want those to be hosted under environment.modulepath.each do |mp| # check that fullpath is a descendant of mp dirname = fullpath previous = dirname while (dirname = File.dirname(previous)) != previous previous = dirname return nil if File.identical?(dirname,mp) end end end # we are under a global manifests Puppet.debug "rdoc: global manifests" SITE end # create documentation for the top level +container+ def scan_top_level(container, environment) # use the module README as documentation for the module comment = "" %w{README README.rdoc}.each do |rfile| readme = File.join(File.dirname(File.dirname(@input_file_name)), rfile) # module README should be UTF-8, not default system encoding comment = File.open(readme,"r:UTF-8") { |f| f.read } if FileTest.readable?(readme) end look_for_directives_in(container, comment) unless comment.empty? # infer module name from directory name = split_module(@input_file_name, environment) if name.nil? # skip .pp files that are not in manifests directories as we can't guarantee they're part # of a module or the global configuration. # PUP-3638, keeping this while it should have no effect since no .pp files are now processed container.document_self = false return end Puppet.debug "rdoc: scanning for #{name}" container.module_name = name container.global=true if name == SITE container, name = get_class_or_module(container,name) mod = container.add_module(RDoc::PuppetModule, name) mod.record_location(@top_level) mod.add_comment(comment, @input_file_name) if @input_file_name =~ /\.rb$/ parse_plugins(mod) end end # create documentation for plugins def parse_plugins(container) Puppet.debug "rdoc: scanning plugin or fact" if @input_file_name =~ /\/facter\/[^\/]+\.rb$/ parse_fact(container) else parse_puppet_plugin(container) end end # this is a poor man custom fact parser :-) def parse_fact(container) comments = "" current_fact = nil parsed_facts = [] File.open(@input_file_name) do |of| of.each do |line| # fetch comments if line =~ /^[ \t]*# ?(.*)$/ comments += $1 + "\n" elsif line =~ /^[ \t]*Facter.add\(['"](.*?)['"]\)/ current_fact = RDoc::Fact.new($1,{}) look_for_directives_in(container, comments) unless comments.empty? current_fact.comment = comments parsed_facts << current_fact comments = "" Puppet.debug "rdoc: found custom fact #{current_fact.name}" elsif line =~ /^[ \t]*confine[ \t]*:(.*?)[ \t]*=>[ \t]*(.*)$/ current_fact.confine = { :type => $1, :value => $2 } unless current_fact.nil? else # unknown line type comments ="" end end end parsed_facts.each do |f| container.add_fact(f) f.record_location(@top_level) end end # this is a poor man puppet plugin parser :-) # it doesn't extract doc nor desc :-( def parse_puppet_plugin(container) comments = "" current_plugin = nil File.open(@input_file_name) do |of| of.each do |line| # fetch comments if line =~ /^[ \t]*# ?(.*)$/ comments += $1 + "\n" elsif line =~ /^[ \t]*(?:Puppet::Parser::Functions::)?newfunction[ \t]*\([ \t]*:(.*?)[ \t]*,[ \t]*:type[ \t]*=>[ \t]*(:rvalue|:lvalue)/ current_plugin = RDoc::Plugin.new($1, "function") look_for_directives_in(container, comments) unless comments.empty? current_plugin.comment = comments current_plugin.record_location(@top_level) container.add_plugin(current_plugin) comments = "" Puppet.debug "rdoc: found new function plugins #{current_plugin.name}" elsif line =~ /^[ \t]*Puppet::Type.newtype[ \t]*\([ \t]*:(.*?)\)/ current_plugin = RDoc::Plugin.new($1, "type") look_for_directives_in(container, comments) unless comments.empty? current_plugin.comment = comments current_plugin.record_location(@top_level) container.add_plugin(current_plugin) comments = "" Puppet.debug "rdoc: found new type plugins #{current_plugin.name}" elsif line =~ /module Puppet::Parser::Functions/ # skip else # unknown line type comments ="" end end end end # New instance of the appropriate PreProcess for our RDoc version. def create_rdoc_preprocess raise(NotImplementedError, "This method must be overwritten for whichever version of RDoc this parser is working with") end # look_for_directives_in scans the current +comment+ for RDoc directives def look_for_directives_in(context, comment) preprocess = create_rdoc_preprocess preprocess.handle(comment) do |directive, param| case directive when "stopdoc" context.stop_doc "" when "startdoc" context.start_doc context.force_documentation = true "" when "enddoc" #context.done_documenting = true #"" throw :enddoc when "main" options = Options.instance options.main_page = param "" when "title" options = Options.instance options.title = param "" when "section" context.set_current_section(param, comment) comment.replace("") # 1.8 doesn't support #clear break else warn "Unrecognized directive '#{directive}'" break end end remove_private_comments(comment) end def remove_private_comments(comment) comment.gsub!(/^#--.*?^#\+\+/m, '') comment.sub!(/^#--.*/m, '') end end puppet-5.5.10/lib/puppet/util/resource_template.rb0000644005276200011600000000416213417161721022124 0ustar jenkinsjenkinsrequire 'puppet/util' require 'puppet/util/logging' require 'erb' # A template wrapper that evaluates a template in the # context of a resource, allowing the resource attributes # to be looked up from within the template. # This provides functionality essentially equivalent to # the language's template() function. You pass your file # path and the resource you want to use into the initialization # method, then call result on the instance, and you get back # a chunk of text. # The resource's parameters are available as instance variables # (as opposed to the language, where we use a method_missing trick). # For example, say you have a resource that generates a file. You would # need to implement the following style of `generate` method: # # def generate # template = Puppet::Util::ResourceTemplate.new("/path/to/template", self) # # return Puppet::Type.type(:file).new :path => "/my/file", # :content => template.evaluate # end # # This generated file gets added to the catalog (which is what `generate` does), # and its content is the result of the template. You need to use instance # variables in your template, so if your template just needs to have the name # of the generating resource, it would just have: # # <%= @name %> # # Since the ResourceTemplate class sets as instance variables all of the resource's # parameters. # # Note that this example uses the generating resource as its source of # parameters, which is generally most useful, since it allows you to configure # the generated resource via the generating resource. class Puppet::Util::ResourceTemplate include Puppet::Util::Logging def evaluate set_resource_variables ERB.new(Puppet::FileSystem.read(@file, :encoding => 'utf-8'), 0, "-").result(binding) end def initialize(file, resource) raise ArgumentError, _("Template %{file} does not exist") % { file: file } unless Puppet::FileSystem.exist?(file) @file = file @resource = resource end private def set_resource_variables @resource.to_hash.each do |param, value| var = "@#{param.to_s}" instance_variable_set(var, value) end end end puppet-5.5.10/lib/puppet/util/retry_action.rb0000644005276200011600000000303013417161721021075 0ustar jenkinsjenkinsmodule Puppet::Util::RetryAction class RetryException < Exception; end class RetryException::NoBlockGiven < RetryException; end class RetryException::NoRetriesGiven < RetryException;end class RetryException::RetriesExceeded < RetryException; end # Execute the supplied block retrying with exponential backoff. # # @param [Hash] options the retry options # @option options [FixNum] :retries Maximum number of times to retry. # @option options [Array] :retry_exceptions ([StandardError]) Optional array of exceptions that are allowed to be retried. # @yield The block to be executed. def self.retry_action(options = {}) # Retry actions for a specified amount of time. This method will allow the final # retry to complete even if that extends beyond the timeout period. if !block_given? raise RetryException::NoBlockGiven end retries = options[:retries] if retries.nil? raise RetryException::NoRetriesGiven end retry_exceptions = options[:retry_exceptions] || [StandardError] failures = 0 begin yield rescue *retry_exceptions => e if failures >= retries raise RetryException::RetriesExceeded, _("%{retries} exceeded") % { retries: retries }, e.backtrace end Puppet.info(_("Caught exception %{klass}:%{error} retrying") % { klass: e.class, error: e }) failures += 1 # Increase the amount of time that we sleep after every # failed retry attempt. sleep (((2 ** failures) -1) * 0.1) retry end end end puppet-5.5.10/lib/puppet/util/selinux.rb0000644005276200011600000002144213417161721020071 0ustar jenkinsjenkins# Provides utility functions to help interface Puppet to SELinux. # # This requires the very new SELinux Ruby bindings. These bindings closely # mirror the SELinux C library interface. # # Support for the command line tools is not provided because the performance # was abysmal. At this time (2008-11-02) the only distribution providing # these Ruby SELinux bindings which I am aware of is Fedora (in libselinux-ruby). Puppet.features.selinux? # check, but continue even if it's not require 'pathname' module Puppet::Util::SELinux def selinux_support? return false unless defined?(Selinux) if Selinux.is_selinux_enabled == 1 return true end false end # Retrieve and return the full context of the file. If we don't have # SELinux support or if the SELinux call fails then return nil. def get_selinux_current_context(file) return nil unless selinux_support? retval = Selinux.lgetfilecon(file) if retval == -1 return nil end retval[1] end # Retrieve and return the default context of the file. If we don't have # SELinux support or if the SELinux call fails to file a default then return nil. def get_selinux_default_context(file) return nil unless selinux_support? # If the filesystem has no support for SELinux labels, return a default of nil # instead of what matchpathcon would return return nil unless selinux_label_support?(file) # If the file exists we should pass the mode to matchpathcon for the most specific # matching. If not, we can pass a mode of 0. begin filestat = file_lstat(file) mode = filestat.mode rescue Errno::EACCES, Errno::ENOENT mode = 0 end retval = Selinux.matchpathcon(file, mode) if retval == -1 return nil end retval[1] end # Take the full SELinux context returned from the tools and parse it # out to the three (or four) component parts. Supports :seluser, :selrole, # :seltype, and on systems with range support, :selrange. def parse_selinux_context(component, context) if context.nil? or context == "unlabeled" return nil end components = /^([^\s:]+):([^\s:]+):([^\s:]+)(?::([\sa-zA-Z0-9:,._-]+))?$/.match(context) unless components raise Puppet::Error, _("Invalid context to parse: %{context}") % { context: context } end case component when :seluser components[1] when :selrole components[2] when :seltype components[3] when :selrange components[4] else raise Puppet::Error, _("Invalid SELinux parameter type") end end # This updates the actual SELinux label on the file. You can update # only a single component or update the entire context. # The caveat is that since setting a partial context makes no sense the # file has to already exist. Puppet (via the File resource) will always # just try to set components, even if all values are specified by the manifest. # I believe that the OS should always provide at least a fall-through context # though on any well-running system. def set_selinux_context(file, value, component = false) return nil unless selinux_support? && selinux_label_support?(file) if component # Must first get existing context to replace a single component context = Selinux.lgetfilecon(file)[1] if context == -1 # We can't set partial context components when no context exists # unless/until we can find a way to make Puppet call this method # once for all selinux file label attributes. Puppet.warning _("Can't set SELinux context on file unless the file already has some kind of context") return nil end context = context.split(':') case component when :seluser context[0] = value when :selrole context[1] = value when :seltype context[2] = value when :selrange context[3] = value else raise ArgumentError, _("set_selinux_context component must be one of :seluser, :selrole, :seltype, or :selrange") end context = context.join(':') else context = value end retval = Selinux.lsetfilecon(file, context) if retval == 0 return true else Puppet.warning _("Failed to set SELinux context %{context} on %{file}") % { context: context, file: file } return false end end # Since this call relies on get_selinux_default_context it also needs a # full non-relative path to the file. Fortunately, that seems to be all # Puppet uses. This will set the file's SELinux context to the policy's # default context (if any) if it differs from the context currently on # the file. def set_selinux_default_context(file) new_context = get_selinux_default_context(file) return nil unless new_context cur_context = get_selinux_current_context(file) if new_context != cur_context set_selinux_context(file, new_context) return new_context end nil end ## # selinux_category_to_label is an internal method that converts all # selinux categories to their internal representation, avoiding # potential issues when mcstransd is not functional. # # It is not marked private because it is needed by File's # selcontext.rb, but it is not intended for use outside of Puppet's # code. # # @param category [String] An selinux category, such as "s0" or "SystemLow" # # @return [String] the numeric category name, such as "s0" def selinux_category_to_label(category) # We don't cache this, but there's already a ton of duplicate work # in the selinux handling code. path = Selinux.selinux_translations_path begin File.open(path).each do |line| line.strip! next if line.empty? next if line[0] == "#" # skip comments line.gsub!(/[[:space:]]+/m, '') mapping = line.split("=", 2) if category == mapping[1] return mapping[0] end end rescue SystemCallError => ex log_exception(ex) raise Puppet::Error, _("Could not open SELinux category translation file %{path}.") % { context: context } end category end ######################################################################## # Internal helper methods from here on in, kids. Don't fiddle. private # Check filesystem a path resides on for SELinux support against # whitelist of known-good filesystems. # Returns true if the filesystem can support SELinux labels and # false if not. def selinux_label_support?(file) fstype = find_fs(file) return false if fstype.nil? filesystems = ['ext2', 'ext3', 'ext4', 'gfs', 'gfs2', 'xfs', 'jfs', 'btrfs', 'tmpfs'] filesystems.include?(fstype) end # Internal helper function to read and parse /proc/mounts def read_mounts mounts = "" begin if File.method_defined? "read_nonblock" # If possible we use read_nonblock in a loop rather than read to work- # a linux kernel bug. See ticket #1963 for details. mountfh = File.open("/proc/mounts") mounts += mountfh.read_nonblock(1024) while true else # Otherwise we shell out and let cat do it for us mountfh = IO.popen("/bin/cat /proc/mounts") mounts = mountfh.read end rescue EOFError # that's expected rescue return nil ensure mountfh.close if mountfh end mntpoint = {} # Read all entries in /proc/mounts. The second column is the # mountpoint and the third column is the filesystem type. # We skip rootfs because it is always mounted at / mounts.each_line do |line| params = line.split(' ') next if params[2] == 'rootfs' mntpoint[params[1]] = params[2] end mntpoint end # Internal helper function to return which type of filesystem a given file # path resides on def find_fs(path) return nil unless mounts = read_mounts # cleanpath eliminates useless parts of the path (like '.', or '..', or # multiple slashes), without touching the filesystem, and without # following symbolic links. This gives the right (logical) tree to follow # while we try and figure out what file-system the target lives on. path = Pathname(path).cleanpath unless path.absolute? raise Puppet::DevError, _("got a relative path in SELinux find_fs: %{path}") % { path: path } end # Now, walk up the tree until we find a match for that path in the hash. path.ascend do |segment| return mounts[segment.to_s] if mounts.has_key?(segment.to_s) end # Should never be reached... return mounts['/'] end ## # file_lstat is an internal, private method to allow precise stubbing and # mocking without affecting the rest of the system. # # @return [File::Stat] File.lstat result def file_lstat(path) Puppet::FileSystem.lstat(path) end private :file_lstat end puppet-5.5.10/lib/puppet/util/skip_tags.rb0000644005276200011600000000025413417161721020364 0ustar jenkinsjenkinsrequire 'puppet/util/tagging' class Puppet::Util::SkipTags include Puppet::Util::Tagging def initialize(stags) self.tags = stags unless defined?(@tags) end end puppet-5.5.10/lib/puppet/util/splayer.rb0000644005276200011600000000074213417161721020061 0ustar jenkinsjenkins# Handle splay options (sleeping for a random interval before executing) module Puppet::Util::Splayer # Have we splayed already? def splayed? !!@splayed end # Sleep when splay is enabled; else just return. def splay(do_splay = Puppet[:splay]) return unless do_splay return if splayed? time = rand(Puppet[:splaylimit] + 1) Puppet.info _("Sleeping for %{time} seconds (splay is enabled)") % { time: time } sleep(time) @splayed = true end end puppet-5.5.10/lib/puppet/util/symbolic_file_mode.rb0000644005276200011600000001113613417161721022225 0ustar jenkinsjenkinsrequire 'puppet/util' module Puppet module Util module SymbolicFileMode SetUIDBit = ReadBit = 4 SetGIDBit = WriteBit = 2 StickyBit = ExecBit = 1 SymbolicMode = { 'x' => ExecBit, 'w' => WriteBit, 'r' => ReadBit } SymbolicSpecialToBit = { 't' => { 'u' => StickyBit, 'g' => StickyBit, 'o' => StickyBit }, 's' => { 'u' => SetUIDBit, 'g' => SetGIDBit, 'o' => StickyBit } } def valid_symbolic_mode?(value) value = normalize_symbolic_mode(value) return true if value =~ /^0?[0-7]{1,4}$/ return true if value =~ /^([ugoa]*[-=+][-=+rstwxXugo]*)(,[ugoa]*[-=+][-=+rstwxXugo]*)*$/ return false end def normalize_symbolic_mode(value) return nil if value.nil? # We need to treat integers as octal numbers. if value.is_a? Numeric then return value.to_s(8) elsif value =~ /^0?[0-7]{1,4}$/ then return value.to_i(8).to_s(8) else return value end end def symbolic_mode_to_int(modification, to_mode = 0, is_a_directory = false) if modification.nil? or modification == '' then raise Puppet::Error, _("An empty mode string is illegal") end if modification =~ /^[0-7]+$/ then return modification.to_i(8) end if modification =~ /^\d+$/ then raise Puppet::Error, _("Numeric modes must be in octal, not decimal!") end fail _("non-numeric current mode (%{mode})") % { mode: to_mode.inspect } unless to_mode.is_a?(Numeric) original_mode = { 's' => (to_mode & 07000) >> 9, 'u' => (to_mode & 00700) >> 6, 'g' => (to_mode & 00070) >> 3, 'o' => (to_mode & 00007) >> 0, # Are there any execute bits set in the original mode? 'any x?' => (to_mode & 00111) != 0 } final_mode = { 's' => original_mode['s'], 'u' => original_mode['u'], 'g' => original_mode['g'], 'o' => original_mode['o'], } modification.split(/\s*,\s*/).each do |part| begin _, to, dsl = /^([ugoa]*)([-+=].*)$/.match(part).to_a if dsl.nil? then raise Puppet::Error, _('Missing action') end to = "a" unless to and to.length > 0 # We want a snapshot of the mode before we start messing with it to # make actions like 'a-g' atomic. Various parts of the DSL refer to # the original mode, the final mode, or the current snapshot of the # mode, for added fun. snapshot_mode = {} final_mode.each {|k,v| snapshot_mode[k] = v } to.gsub('a', 'ugo').split('').uniq.each do |who| value = snapshot_mode[who] action = '!' actions = { '!' => lambda {|_,_| raise Puppet::Error, _('Missing operation (-, =, or +)') }, '=' => lambda {|m,v| m | v }, '+' => lambda {|m,v| m | v }, '-' => lambda {|m,v| m & ~v }, } dsl.split('').each do |op| case op when /[-+=]/ then action = op # Clear all bits, if this is assignment value = 0 if op == '=' when /[ugo]/ then value = actions[action].call(value, snapshot_mode[op]) when /[rwx]/ then value = actions[action].call(value, SymbolicMode[op]) when 'X' then # Only meaningful in combination with "set" actions. if action != '+' then raise Puppet::Error, _("X only works with the '+' operator") end # As per the BSD manual page, set if this is a directory, or if # any execute bit is set on the original (unmodified) mode. # Ignored otherwise; it is "add if", not "add or clear". if is_a_directory or original_mode['any x?'] then value = actions[action].call(value, ExecBit) end when /[st]/ then bit = SymbolicSpecialToBit[op][who] or fail _("internal error") final_mode['s'] = actions[action].call(final_mode['s'], bit) else raise Puppet::Error, _('Unknown operation') end end # Now, assign back the value. final_mode[who] = value end rescue Puppet::Error => e if part.inspect != modification.inspect then rest = " at #{part.inspect}" else rest = '' end raise Puppet::Error, _("%{error}%{rest} in symbolic mode %{modification}") % { error: e, rest: rest, modification: modification.inspect }, e.backtrace end end result = final_mode['s'] << 9 | final_mode['u'] << 6 | final_mode['g'] << 3 | final_mode['o'] << 0 return result end end end end puppet-5.5.10/lib/puppet/util/terminal.rb0000644005276200011600000000103613417161721020212 0ustar jenkinsjenkinsmodule Puppet::Util::Terminal # Attempts to determine the width of the terminal. This is currently only # supported on POSIX systems, and relies on the claims of `stty` (or `tput`). # # Inspired by code from Thor; thanks wycats! # @return [Number] The column width of the terminal. Defaults to 80 columns. def self.width if Puppet.features.posix? result = %x{stty size 2>/dev/null}.split[1] || %x{tput cols 2>/dev/null}.split[0] end return (result || '80').to_i rescue return 80 end end puppet-5.5.10/lib/puppet/util/user_attr.rb0000644005276200011600000000072013417161721020406 0ustar jenkinsjenkinsclass UserAttr def self.get_attributes_by_name(name) attributes = nil File.readlines('/etc/user_attr').each do |line| next if line =~ /^#/ token = line.split(':') if token[0] == name attributes = {:name => name} token[4].split(';').each do |attr| key_value = attr.split('=') attributes[key_value[0].intern] = key_value[1].strip end break end end attributes end end puppet-5.5.10/lib/puppet/util/warnings.rb0000644005276200011600000000132013417161721020223 0ustar jenkinsjenkins# Methods to help with handling warnings. module Puppet::Util::Warnings module_function def notice_once(msg) Puppet::Util::Warnings.maybe_log(msg, self.class) { Puppet.notice msg } end def debug_once(msg) return nil unless Puppet[:debug] Puppet::Util::Warnings.maybe_log(msg, self.class) { Puppet.debug msg } end def warnonce(msg) Puppet::Util::Warnings.maybe_log(msg, self.class) { Puppet.warning msg } end def clear_warnings @stampwarnings = {} nil end def self.maybe_log(message, klass) @stampwarnings ||= {} @stampwarnings[klass] ||= [] return nil if @stampwarnings[klass].include? message yield @stampwarnings[klass] << message nil end end puppet-5.5.10/lib/puppet/util/watched_file.rb0000644005276200011600000000235213417161721021017 0ustar jenkinsjenkinsrequire 'puppet/util/watcher' # Monitor a given file for changes on a periodic interval. Changes are detected # by looking for a change in the file ctime. class Puppet::Util::WatchedFile # @!attribute [r] filename # @return [String] The fully qualified path to the file. attr_reader :filename # @param filename [String] The fully qualified path to the file. # @param timer [Puppet::Util::Watcher::Timer] The polling interval for checking for file # changes. Setting the timeout to a negative value will treat the file as # always changed. Defaults to `Puppet[:filetimeout]` def initialize(filename, timer = Puppet::Util::Watcher::Timer.new(Puppet[:filetimeout])) @filename = filename @timer = timer @info = Puppet::Util::Watcher::PeriodicWatcher.new( Puppet::Util::Watcher::Common.file_ctime_change_watcher(@filename), timer) end # @return [true, false] If the file has changed since it was last checked. def changed? @info.changed? end # Allow this to be used as the name of the file being watched in various # other methods (such as Puppet::FileSystem.exist?) def to_str @filename end def to_s "" end end puppet-5.5.10/lib/puppet/util/watcher.rb0000644005276200011600000000071013417161721020032 0ustar jenkinsjenkinsmodule Puppet::Util::Watcher require 'puppet/util/watcher/timer' require 'puppet/util/watcher/change_watcher' require 'puppet/util/watcher/periodic_watcher' module Common def self.file_ctime_change_watcher(filename) Puppet::Util::Watcher::ChangeWatcher.watch(lambda do begin Puppet::FileSystem.stat(filename).ctime rescue Errno::ENOENT, Errno::ENOTDIR :absent end end) end end end puppet-5.5.10/lib/puppet/util/watcher/0000755005276200011600000000000013417162176017514 5ustar jenkinsjenkinspuppet-5.5.10/lib/puppet/util/watcher/change_watcher.rb0000644005276200011600000000141013417161721022772 0ustar jenkinsjenkins# Watches for changes over time. It only re-examines the values when it is requested to update readings. # @api private class Puppet::Util::Watcher::ChangeWatcher def self.watch(reader) Puppet::Util::Watcher::ChangeWatcher.new(nil, nil, reader).next_reading end def initialize(previous, current, value_reader) @previous = previous @current = current @value_reader = value_reader end def changed? if uncertain? false else @previous != @current end end def uncertain? @previous.nil? || @current.nil? end def change_current_reading_to(new_value) Puppet::Util::Watcher::ChangeWatcher.new(@current, new_value, @value_reader) end def next_reading change_current_reading_to(@value_reader.call) end end puppet-5.5.10/lib/puppet/util/watcher/periodic_watcher.rb0000644005276200011600000000164413417161721023354 0ustar jenkinsjenkins# Monitor a given watcher for changes on a periodic interval. class Puppet::Util::Watcher::PeriodicWatcher # @param watcher [Puppet::Util::Watcher::ChangeWatcher] a watcher for the value to watch # @param timer [Puppet::Util::Watcher::Timer] A timer to determine when to # recheck the watcher. If the timeout of the timer is negative, then the # watched value is always considered to be changed def initialize(watcher, timer) @watcher = watcher @timer = timer @timer.start end # @return [true, false] If the file has changed since it was last checked. def changed? return true if always_consider_changed? @watcher = examine_watched_info(@watcher) @watcher.changed? end private def always_consider_changed? @timer.timeout < 0 end def examine_watched_info(known) if @timer.expired? @timer.start known.next_reading else known end end end puppet-5.5.10/lib/puppet/util/watcher/timer.rb0000644005276200011600000000037413417161721021160 0ustar jenkinsjenkinsclass Puppet::Util::Watcher::Timer attr_reader :timeout def initialize(timeout) @timeout = timeout end def start @start_time = now end def expired? (now - @start_time) >= @timeout end def now Time.now.to_i end end puppet-5.5.10/lib/puppet/util/windows/0000755005276200011600000000000013417162176017551 5ustar jenkinsjenkinspuppet-5.5.10/lib/puppet/util/windows/access_control_entry.rb0000644005276200011600000000463713417161721024325 0ustar jenkinsjenkins# Windows Access Control Entry # # Represents an access control entry, which grants or denies a subject, # identified by a SID, rights to a securable object. # # @see https://msdn.microsoft.com/en-us/library/windows/desktop/aa374868(v=vs.85).aspx # @api private class Puppet::Util::Windows::AccessControlEntry require 'puppet/util/windows/security' include Puppet::Util::Windows::SID attr_accessor :sid attr_reader :mask, :flags, :type OBJECT_INHERIT_ACE = 0x1 CONTAINER_INHERIT_ACE = 0x2 NO_PROPAGATE_INHERIT_ACE = 0x4 INHERIT_ONLY_ACE = 0x8 INHERITED_ACE = 0x10 ACCESS_ALLOWED_ACE_TYPE = 0x0 ACCESS_DENIED_ACE_TYPE = 0x1 def initialize(sid, mask, flags = 0, type = ACCESS_ALLOWED_ACE_TYPE) @sid = sid @mask = mask @flags = flags @type = type end # Returns true if this ACE is inherited from a parent. If false, # then the ACE is set directly on the object to which it refers. # # @return [Boolean] true if the ACE is inherited def inherited? (@flags & INHERITED_ACE) == INHERITED_ACE end # Returns true if this ACE only applies to children of the object. # If false, it applies to the object. # # @return [Boolean] true if the ACE only applies to children and # not the object itself. def inherit_only? (@flags & INHERIT_ONLY_ACE) == INHERIT_ONLY_ACE end # Returns true if this ACE applies to child directories. # # @return [Boolean] true if the ACE applies to child directories def container_inherit? (@flags & CONTAINER_INHERIT_ACE) == CONTAINER_INHERIT_ACE end # Returns true if this ACE applies to child files. # # @return [Boolean] true if the ACE applies to child files. def object_inherit? (@flags & OBJECT_INHERIT_ACE) == OBJECT_INHERIT_ACE end def inspect inheritance = "" inheritance << '(I)' if inherited? inheritance << '(OI)' if object_inherit? inheritance << '(CI)' if container_inherit? inheritance << '(IO)' if inherit_only? left = "#{sid_to_name(sid)}:#{inheritance}" left = left.ljust(45) "#{left} 0x#{mask.to_s(16)}" end # Returns true if this ACE is equal to +other+ def ==(other) self.class == other.class && sid == other.sid && mask == other.mask && flags == other.flags && type == other.type end alias eql? == end puppet-5.5.10/lib/puppet/util/windows/access_control_list.rb0000644005276200011600000000673013417161721024133 0ustar jenkinsjenkins# Windows Access Control List # # Represents a list of access control entries (ACEs). # # @see https://msdn.microsoft.com/en-us/library/windows/desktop/aa374872(v=vs.85).aspx # @api private class Puppet::Util::Windows::AccessControlList include Enumerable ACCESS_ALLOWED_ACE_TYPE = 0x0 ACCESS_DENIED_ACE_TYPE = 0x1 # Construct an ACL. # # @param acl [Enumerable] A list of aces to copy from. def initialize(acl = nil) if acl @aces = acl.map(&:dup) else @aces = [] end end # Enumerate each ACE in the list. # # @yieldparam ace [Hash] the ace def each @aces.each {|ace| yield ace} end # Allow the +sid+ to access a resource with the specified access +mask+. # # @param sid [String] The SID that the ACE is granting access to # @param mask [int] The access mask granted to the SID # @param flags [int] The flags assigned to the ACE, e.g. +INHERIT_ONLY_ACE+ def allow(sid, mask, flags = 0) @aces << Puppet::Util::Windows::AccessControlEntry.new(sid, mask, flags, ACCESS_ALLOWED_ACE_TYPE) end # Deny the +sid+ access to a resource with the specified access +mask+. # # @param sid [String] The SID that the ACE is denying access to # @param mask [int] The access mask denied to the SID # @param flags [int] The flags assigned to the ACE, e.g. +INHERIT_ONLY_ACE+ def deny(sid, mask, flags = 0) @aces << Puppet::Util::Windows::AccessControlEntry.new(sid, mask, flags, ACCESS_DENIED_ACE_TYPE) end # Reassign all ACEs currently assigned to +old_sid+ to +new_sid+ instead. # If an ACE is inherited or is not assigned to +old_sid+, then it will # be copied as-is to the new ACL, preserving its order within the ACL. # # @param old_sid [String] The old SID, e.g. 'S-1-5-18' # @param new_sid [String] The new SID # @return [AccessControlList] The copied ACL. def reassign!(old_sid, new_sid) new_aces = [] prepend_needed = false aces_to_prepend = [] @aces.each do |ace| new_ace = ace.dup if ace.sid == old_sid if ace.inherited? # create an explicit ACE granting or denying the # new_sid the rights that the inherited ACE # granted or denied the old_sid. We mask off all # flags except those affecting inheritance of the # ACE we're creating. inherit_mask = Puppet::Util::Windows::AccessControlEntry::CONTAINER_INHERIT_ACE | Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE | Puppet::Util::Windows::AccessControlEntry::INHERIT_ONLY_ACE explicit_ace = Puppet::Util::Windows::AccessControlEntry.new(new_sid, ace.mask, ace.flags & inherit_mask, ace.type) aces_to_prepend << explicit_ace else new_ace.sid = new_sid prepend_needed = old_sid == Puppet::Util::Windows::SID::LocalSystem end end new_aces << new_ace end @aces = [] if prepend_needed mask = Puppet::Util::Windows::File::STANDARD_RIGHTS_ALL | Puppet::Util::Windows::File::SPECIFIC_RIGHTS_ALL ace = Puppet::Util::Windows::AccessControlEntry.new( Puppet::Util::Windows::SID::LocalSystem, mask) @aces << ace end @aces.concat(aces_to_prepend) @aces.concat(new_aces) end def inspect str = "" @aces.each do |ace| str << " #{ace.inspect}\n" end str end def ==(other) self.class == other.class && self.to_a == other.to_a end alias eql? == end puppet-5.5.10/lib/puppet/util/windows/api_types.rb0000644005276200011600000002236213417161721022073 0ustar jenkinsjenkinsrequire 'ffi' require 'puppet/util/windows/string' module Puppet::Util::Windows::APITypes module ::FFI WIN32_FALSE = 0 # standard Win32 error codes ERROR_SUCCESS = 0 end module ::FFI::Library # Wrapper method for attach_function + private def attach_function_private(*args) attach_function(*args) private args[0] end end class ::FFI::Pointer NULL_HANDLE = 0 def self.from_string_to_wide_string(str, &block) str = Puppet::Util::Windows::String.wide_string(str) FFI::MemoryPointer.new(:byte, str.bytesize) do |ptr| # uchar here is synonymous with byte ptr.put_array_of_uchar(0, str.bytes.to_a) yield ptr end # ptr has already had free called, so nothing to return nil end def read_win32_bool # BOOL is always a 32-bit integer in Win32 # some Win32 APIs return 1 for true, while others are non-0 read_int32 != FFI::WIN32_FALSE end alias_method :read_dword, :read_uint32 alias_method :read_win32_ulong, :read_uint32 alias_method :read_qword, :read_uint64 alias_method :read_hresult, :read_int32 def read_handle type_size == 4 ? read_uint32 : read_uint64 end alias_method :read_wchar, :read_uint16 alias_method :read_word, :read_uint16 alias_method :read_array_of_wchar, :read_array_of_uint16 def read_wide_string(char_length, dst_encoding = Encoding::UTF_8, encode_options = {}) # char_length is number of wide chars (typically excluding NULLs), *not* bytes str = get_bytes(0, char_length * 2).force_encoding('UTF-16LE') str.encode(dst_encoding, str.encoding, encode_options) rescue Exception => e Puppet.debug "Unable to convert value #{str.nil? ? 'nil' : str.dump} to encoding #{dst_encoding} due to #{e.inspect}" raise end # @param max_char_length [Integer] Maximum number of wide chars to return (typically excluding NULLs), *not* bytes # @param null_terminator [Symbol] Number of number of null wchar characters, *not* bytes, that determine the end of the string # null_terminator = :single_null, then the terminating sequence is two bytes of zero. This is UNIT16 = 0 # null_terminator = :double_null, then the terminating sequence is four bytes of zero. This is UNIT32 = 0 # @param encode_options [Hash] Accepts the same option hash that may be passed to String#encode in Ruby def read_arbitrary_wide_string_up_to(max_char_length = 512, null_terminator = :single_null, encode_options = {}) if null_terminator != :single_null && null_terminator != :double_null raise _("Unable to read wide strings with %{null_terminator} terminal nulls") % { null_terminator: null_terminator } end terminator_width = null_terminator == :single_null ? 1 : 2 reader_method = null_terminator == :single_null ? :get_uint16 : :get_uint32 # Look for a null terminating characters; if found, read up to that null (exclusive) (0...max_char_length - terminator_width).each do |i| return read_wide_string(i, Encoding::UTF_8, encode_options) if send(reader_method, (i * 2)) == 0 end # String is longer than the max; read just to the max read_wide_string(max_char_length, Encoding::UTF_8, encode_options) end def read_win32_local_pointer(&block) ptr = nil begin ptr = read_pointer yield ptr ensure if ptr && ! ptr.null? if FFI::WIN32::LocalFree(ptr.address) != FFI::Pointer::NULL_HANDLE Puppet.debug "LocalFree memory leak" end end end # ptr has already had LocalFree called, so nothing to return nil end def read_com_memory_pointer(&block) ptr = nil begin ptr = read_pointer yield ptr ensure FFI::WIN32::CoTaskMemFree(ptr) if ptr && ! ptr.null? end # ptr has already had CoTaskMemFree called, so nothing to return nil end alias_method :write_dword, :write_uint32 alias_method :write_word, :write_uint16 end # FFI Types # https://github.com/ffi/ffi/wiki/Types # Windows - Common Data Types # https://msdn.microsoft.com/en-us/library/cc230309.aspx # Windows Data Types # https://msdn.microsoft.com/en-us/library/windows/desktop/aa383751(v=vs.85).aspx FFI.typedef :uint16, :word FFI.typedef :uint32, :dword # uintptr_t is defined in an FFI conf as platform specific, either # ulong_long on x64 or just ulong on x86 FFI.typedef :uintptr_t, :handle FFI.typedef :uintptr_t, :hwnd # buffer_inout is similar to pointer (platform specific), but optimized for buffers FFI.typedef :buffer_inout, :lpwstr # buffer_in is similar to pointer (platform specific), but optimized for CONST read only buffers FFI.typedef :buffer_in, :lpcwstr FFI.typedef :buffer_in, :lpcolestr # string is also similar to pointer, but should be used for const char * # NOTE that this is not wide, useful only for A suffixed functions FFI.typedef :string, :lpcstr # pointer in FFI is platform specific # NOTE: for API calls with reserved lpvoid parameters, pass a FFI::Pointer::NULL FFI.typedef :pointer, :lpcvoid FFI.typedef :pointer, :lpvoid FFI.typedef :pointer, :lpword FFI.typedef :pointer, :lpbyte FFI.typedef :pointer, :lpdword FFI.typedef :pointer, :pdword FFI.typedef :pointer, :phandle FFI.typedef :pointer, :ulong_ptr FFI.typedef :pointer, :pbool FFI.typedef :pointer, :lpunknown # any time LONG / ULONG is in a win32 API definition DO NOT USE platform specific width # which is what FFI uses by default # instead create new aliases for these very special cases # NOTE: not a good idea to redefine FFI :ulong since other typedefs may rely on it FFI.typedef :uint32, :win32_ulong FFI.typedef :int32, :win32_long # FFI bool can be only 1 byte at times, # Win32 BOOL is a signed int, and is always 4 bytes, even on x64 # https://blogs.msdn.com/b/oldnewthing/archive/2011/03/28/10146459.aspx FFI.typedef :int32, :win32_bool # BOOLEAN (unlike BOOL) is a BYTE - typedef unsigned char BYTE; FFI.typedef :uchar, :boolean # Same as a LONG, a 32-bit signed integer FFI.typedef :int32, :hresult # NOTE: FFI already defines (u)short as a 16-bit (un)signed like this: # FFI.typedef :uint16, :ushort # FFI.typedef :int16, :short # 8 bits per byte FFI.typedef :uchar, :byte FFI.typedef :uint16, :wchar module ::FFI::WIN32 extend ::FFI::Library # https://msdn.microsoft.com/en-us/library/windows/desktop/aa373931(v=vs.85).aspx # typedef struct _GUID { # DWORD Data1; # WORD Data2; # WORD Data3; # BYTE Data4[8]; # } GUID; class GUID < FFI::Struct layout :Data1, :dword, :Data2, :word, :Data3, :word, :Data4, [:byte, 8] def self.[](s) raise _('Bad GUID format.') unless s =~ /^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$/i new.tap do |guid| guid[:Data1] = s[0, 8].to_i(16) guid[:Data2] = s[9, 4].to_i(16) guid[:Data3] = s[14, 4].to_i(16) guid[:Data4][0] = s[19, 2].to_i(16) guid[:Data4][1] = s[21, 2].to_i(16) s[24, 12].split('').each_slice(2).with_index do |a, i| guid[:Data4][i + 2] = a.join('').to_i(16) end end end def ==(other) Windows.memcmp(other, self, size) == 0 end end # https://msdn.microsoft.com/en-us/library/windows/desktop/ms724950(v=vs.85).aspx # typedef struct _SYSTEMTIME { # WORD wYear; # WORD wMonth; # WORD wDayOfWeek; # WORD wDay; # WORD wHour; # WORD wMinute; # WORD wSecond; # WORD wMilliseconds; # } SYSTEMTIME, *PSYSTEMTIME; class SYSTEMTIME < FFI::Struct layout :wYear, :word, :wMonth, :word, :wDayOfWeek, :word, :wDay, :word, :wHour, :word, :wMinute, :word, :wSecond, :word, :wMilliseconds, :word def to_local_time Time.local(self[:wYear], self[:wMonth], self[:wDay], self[:wHour], self[:wMinute], self[:wSecond], self[:wMilliseconds] * 1000) end end # https://msdn.microsoft.com/en-us/library/windows/desktop/ms724284(v=vs.85).aspx # Contains a 64-bit value representing the number of 100-nanosecond # intervals since January 1, 1601 (UTC). # typedef struct _FILETIME { # DWORD dwLowDateTime; # DWORD dwHighDateTime; # } FILETIME, *PFILETIME; class FILETIME < FFI::Struct layout :dwLowDateTime, :dword, :dwHighDateTime, :dword end ffi_convention :stdcall # https://msdn.microsoft.com/en-us/library/windows/desktop/aa366730(v=vs.85).aspx # HLOCAL WINAPI LocalFree( # _In_ HLOCAL hMem # ); ffi_lib :kernel32 attach_function :LocalFree, [:handle], :handle # https://msdn.microsoft.com/en-us/library/windows/desktop/ms724211(v=vs.85).aspx # BOOL WINAPI CloseHandle( # _In_ HANDLE hObject # ); ffi_lib :kernel32 attach_function_private :CloseHandle, [:handle], :win32_bool # https://msdn.microsoft.com/en-us/library/windows/desktop/ms680722(v=vs.85).aspx # void CoTaskMemFree( # _In_opt_ LPVOID pv # ); ffi_lib :ole32 attach_function :CoTaskMemFree, [:lpvoid], :void end end puppet-5.5.10/lib/puppet/util/windows/com.rb0000644005276200011600000001363213417161721020654 0ustar jenkinsjenkinsrequire 'ffi' module Puppet::Util::Windows::COM extend FFI::Library ffi_convention :stdcall S_OK = 0 S_FALSE = 1 def SUCCEEDED(hr) hr >= 0 end def FAILED(hr) hr < 0 end module_function :SUCCEEDED, :FAILED def raise_if_hresult_failed(name, *args) failed = FAILED(result = send(name, *args)) and raise _("%{name} failed (hresult %{result}).") % { name: name, result: format('%#08x', result) } result ensure yield failed if block_given? end module_function :raise_if_hresult_failed CLSCTX_INPROC_SERVER = 0x1 CLSCTX_INPROC_HANDLER = 0x2 CLSCTX_LOCAL_SERVER = 0x4 CLSCTX_INPROC_SERVER16 = 0x8 CLSCTX_REMOTE_SERVER = 0x10 CLSCTX_INPROC_HANDLER16 = 0x20 CLSCTX_RESERVED1 = 0x40 CLSCTX_RESERVED2 = 0x80 CLSCTX_RESERVED3 = 0x100 CLSCTX_RESERVED4 = 0x200 CLSCTX_NO_CODE_DOWNLOAD = 0x400 CLSCTX_RESERVED5 = 0x800 CLSCTX_NO_CUSTOM_MARSHAL = 0x1000 CLSCTX_ENABLE_CODE_DOWNLOAD = 0x2000 CLSCTX_NO_FAILURE_LOG = 0x4000 CLSCTX_DISABLE_AAA = 0x8000 CLSCTX_ENABLE_AAA = 0x10000 CLSCTX_FROM_DEFAULT_CONTEXT = 0x20000 CLSCTX_ACTIVATE_32_BIT_SERVER = 0x40000 CLSCTX_ACTIVATE_64_BIT_SERVER = 0x80000 CLSCTX_ENABLE_CLOAKING = 0x100000 CLSCTX_PS_DLL = -0x80000000 CLSCTX_INPROC = CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER CLSCTX_ALL = CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER CLSCTX_SERVER = CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER # https://msdn.microsoft.com/en-us/library/windows/desktop/ms686615(v=vs.85).aspx # HRESULT CoCreateInstance( # _In_ REFCLSID rclsid, # _In_ LPUNKNOWN pUnkOuter, # _In_ DWORD dwClsContext, # _In_ REFIID riid, # _Out_ LPVOID *ppv # ); ffi_lib :ole32 attach_function_private :CoCreateInstance, [:pointer, :lpunknown, :dword, :pointer, :lpvoid], :hresult # code modified from Unknownr project https://github.com/rpeev/Unknownr # licensed under MIT module Interface def self.[](*args) spec, iid, *ifaces = args.reverse spec.each { |name, signature| signature[0].unshift(:pointer) } Class.new(FFI::Struct) do const_set(:IID, iid) vtable = Class.new(FFI::Struct) do vtable_hash = Hash[(ifaces.map { |iface| iface::VTBL::SPEC.to_a } << spec.to_a).flatten(1)] const_set(:SPEC, vtable_hash) layout( *self::SPEC.map { |name, signature| [name, callback(*signature)] }.flatten ) end const_set(:VTBL, vtable) layout \ :lpVtbl, :pointer end end end module Helpers def QueryInstance(klass) instance = nil FFI::MemoryPointer.new(:pointer) do |ppv| QueryInterface(klass::IID, ppv) instance = klass.new(ppv.read_pointer) end begin yield instance return self ensure instance.Release end if block_given? instance end def UseInstance(klass, name, *args) instance = nil FFI::MemoryPointer.new(:pointer) do |ppv| send(name, *args, ppv) yield instance = klass.new(ppv.read_pointer) end self ensure instance.Release if instance && ! instance.null? end end module Instance def self.[](iface) Class.new(iface) do send(:include, Helpers) def initialize(pointer) self.pointer = pointer @vtbl = self.class::VTBL.new(self[:lpVtbl]) end attr_reader :vtbl self::VTBL.members.each do |name| define_method(name) do |*args| if Puppet::Util::Windows::COM.FAILED(result = @vtbl[name].call(self, *args)) raise Puppet::Util::Windows::Error.new(_("Failed to call %{klass}::%{name} with HRESULT: %{result}.") % { klass: self, name: name, result: result }, result) end result end end layout \ :lpVtbl, :pointer end end end module Factory def self.[](iface, clsid) Class.new(iface) do send(:include, Helpers) const_set(:CLSID, clsid) def initialize(opts = {}) @opts = opts @opts[:clsctx] ||= CLSCTX_INPROC_SERVER FFI::MemoryPointer.new(:pointer) do |ppv| hr = Puppet::Util::Windows::COM.CoCreateInstance(self.class::CLSID, FFI::Pointer::NULL, @opts[:clsctx], self.class::IID, ppv) if Puppet::Util::Windows::COM.FAILED(hr) raise _("CoCreateInstance failed (%{klass}).") % { klass: self.class } end self.pointer = ppv.read_pointer end @vtbl = self.class::VTBL.new(self[:lpVtbl]) end attr_reader :vtbl self::VTBL.members.each do |name| define_method(name) do |*args| if Puppet::Util::Windows::COM.FAILED(result = @vtbl[name].call(self, *args)) raise Puppet::Util::Windows::Error.new(_("Failed to call %{klass}::%{name} with HRESULT: %{result}.") % { klass: self, name: name, result: result }, result) end result end end layout \ :lpVtbl, :pointer end end end IUnknown = Interface[ FFI::WIN32::GUID['00000000-0000-0000-C000-000000000046'], QueryInterface: [[:pointer, :pointer], :hresult], AddRef: [[], :win32_ulong], Release: [[], :win32_ulong] ] Unknown = Instance[IUnknown] # https://msdn.microsoft.com/en-us/library/windows/desktop/ms678543(v=vs.85).aspx # HRESULT CoInitialize( # _In_opt_ LPVOID pvReserved # ); ffi_lib :ole32 attach_function_private :CoInitialize, [:lpvoid], :hresult # https://msdn.microsoft.com/en-us/library/windows/desktop/ms688715(v=vs.85).aspx # void CoUninitialize(void); ffi_lib :ole32 attach_function_private :CoUninitialize, [], :void def InitializeCom raise_if_hresult_failed(:CoInitialize, FFI::Pointer::NULL) at_exit { CoUninitialize() } end module_function :InitializeCom end puppet-5.5.10/lib/puppet/util/windows/error.rb0000644005276200011600000000576313417161721021235 0ustar jenkinsjenkinsrequire 'puppet/util/windows' # represents an error resulting from a Win32 error code class Puppet::Util::Windows::Error < Puppet::Error require 'ffi' extend FFI::Library attr_reader :code # NOTE: FFI.errno only works properly when prior Win32 calls have been made # through FFI bindings. Calls made through Win32API do not have their error # codes captured by FFI.errno def initialize(message, code = FFI.errno, original = nil) super(message + ": #{self.class.format_error_code(code)}", original) @code = code end # Helper method that wraps FormatMessage that returns a human readable string. def self.format_error_code(code) # specifying 0 will look for LANGID in the following order # 1.Language neutral # 2.Thread LANGID, based on the thread's locale value # 3.User default LANGID, based on the user's default locale value # 4.System default LANGID, based on the system default locale value # 5.US English dwLanguageId = 0 flags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK error_string = '' # this pointer actually points to a :lpwstr (pointer) since we're letting Windows allocate for us FFI::MemoryPointer.new(:pointer, 1) do |buffer_ptr| length = FormatMessageW(flags, FFI::Pointer::NULL, code, dwLanguageId, buffer_ptr, 0, FFI::Pointer::NULL) if length == FFI::WIN32_FALSE # can't raise same error type here or potentially recurse infinitely raise Puppet::Error.new(_("FormatMessageW could not format code %{code}") % { code: code }) end # returns an FFI::Pointer with autorelease set to false, which is what we want buffer_ptr.read_win32_local_pointer do |wide_string_ptr| if wide_string_ptr.null? raise Puppet::Error.new(_("FormatMessageW failed to allocate buffer for code %{code}") % { code: code }) end error_string = wide_string_ptr.read_wide_string(length) end end error_string end ERROR_FILE_NOT_FOUND = 2 ERROR_ACCESS_DENIED = 5 FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100 FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200 FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000 FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000 FORMAT_MESSAGE_MAX_WIDTH_MASK = 0x000000FF ffi_convention :stdcall # https://msdn.microsoft.com/en-us/library/windows/desktop/ms679351(v=vs.85).aspx # DWORD WINAPI FormatMessage( # _In_ DWORD dwFlags, # _In_opt_ LPCVOID lpSource, # _In_ DWORD dwMessageId, # _In_ DWORD dwLanguageId, # _Out_ LPTSTR lpBuffer, # _In_ DWORD nSize, # _In_opt_ va_list *Arguments # ); # NOTE: since we're not preallocating the buffer, use a :pointer for lpBuffer ffi_lib :kernel32 attach_function_private :FormatMessageW, [:dword, :lpcvoid, :dword, :dword, :pointer, :dword, :pointer], :dword end puppet-5.5.10/lib/puppet/util/windows/eventlog.rb0000644005276200011600000001617013417161721021721 0ustar jenkinsjenkinsrequire 'ffi' # Puppet::Util::Windows::EventLog needs to be requirable without having loaded # any other parts of Puppet so it can be leveraged independently by the code # that runs Puppet as a service on Windows. # # For this reason we: # - Define Puppet::Util::Windows # - Replicate logic that exists elsewhere in puppet/util/windows # - Raise generic RuntimeError instead of Puppet::Util::Windows::Error if its not defined module Puppet; module Util; module Windows ; end ; end ; end class Puppet::Util::Windows::EventLog extend FFI::Library # https://msdn.microsoft.com/en-us/library/windows/desktop/aa363679(v=vs.85).aspx EVENTLOG_ERROR_TYPE = 0x0001 EVENTLOG_WARNING_TYPE = 0x0002 EVENTLOG_INFORMATION_TYPE = 0x0004 # These are duplicate definitions from Puppet::Util::Windows::ApiTypes, # established here so this class can be standalone from Puppet, and public so # we can reference them in tests. NULL_HANDLE = 0 WIN32_FALSE = 0 # Register an event log handle for the application # @param source_name [String] the name of the event source to retrieve a handle for # @return [void] # @api public def initialize(source_name = 'Puppet') @eventlog_handle = RegisterEventSourceW(FFI::Pointer::NULL, wide_string(source_name)) if @eventlog_handle == NULL_HANDLE #TRANSLATORS 'Windows' is the operating system and 'RegisterEventSourceW' is a API call and should not be translated raise EventLogError.new(_("RegisterEventSourceW failed to open Windows eventlog"), FFI.errno) end end # Close this instance's event log handle # @return [void] # @api public def close DeregisterEventSource(@eventlog_handle) ensure @eventlog_handle = nil end # Report an event to this instance's event log handle. Accepts a string to # report (:data => ) and event type (:event_type => FixNum) and id # (:event_id => FixNum) as returned by #to_native. The additional arguments to # ReportEventW seen in this method aren't exposed - though ReportEventW # technically can accept multiple strings as well as raw binary data to log, # we accept a single string from Puppet::Util::Log # # @param args [Hash{Symbol=>Object}] options to the associated log event # @return [void] # @api public def report_event(args = {}) unless args[:data].is_a?(String) raise ArgumentError, _("data must be a string, not %{class_name}") % { class_name: args[:data].class } end from_string_to_wide_string(args[:data]) do |message_ptr| FFI::MemoryPointer.new(:pointer) do |message_array_ptr| message_array_ptr.write_pointer(message_ptr) user_sid = FFI::Pointer::NULL raw_data = FFI::Pointer::NULL raw_data_size = 0 num_strings = 1 eventlog_category = 0 report_result = ReportEventW(@eventlog_handle, args[:event_type], eventlog_category, args[:event_id], user_sid, num_strings, raw_data_size, message_array_ptr, raw_data) if report_result == WIN32_FALSE #TRANSLATORS 'Windows' is the operating system and 'ReportEventW' is a API call and should not be translated raise EventLogError.new(_("ReportEventW failed to report event to Windows eventlog"), FFI.errno) end end end end class << self # Feels more natural to do Puppet::Util::Window::EventLog.open("MyApplication") alias :open :new # Query event identifier info for a given log level # @param level [Symbol] an event log level # @return [Array] Win API Event ID, Puppet Event ID # @api public def to_native(level) case level when :debug,:info,:notice [EVENTLOG_INFORMATION_TYPE, 0x01] when :warning [EVENTLOG_WARNING_TYPE, 0x02] when :err,:alert,:emerg,:crit [EVENTLOG_ERROR_TYPE, 0x03] else raise ArgumentError, _("Invalid log level %{level}") % { level: level } end end end private # For the purposes of allowing this class to be standalone, the following are # duplicate definitions from elsewhere in Puppet: # If we're loaded via Puppet we should keep the previous behavior of raising # Puppet::Util::Windows::Error on errors. If we aren't, at least concatenate # the error code to the exception message to pass this information on to the # user if defined?(Puppet::Util::Windows::Error) EventLogError = Puppet::Util::Windows::Error else class EventLogError < RuntimeError def initialize(msg, code) #TRANSLATORS 'Win32' is the Windows API and should not be translated super(msg + ' ' + _("(Win32 error: %{detail})") % { detail: code}) end end end # Private duplicate of Puppet::Util::Windows::String::wide_string # Not for use outside of EventLog! - use Puppet::Util::Windows instead # @api private def wide_string(str) # if given a nil string, assume caller wants to pass a nil pointer to win32 return nil if str.nil? # ruby (< 2.1) does not respect multibyte terminators, so it is possible # for a string to contain a single trailing null byte, followed by garbage # causing buffer overruns. # # See http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?revision=41920&view=revision newstr = str + "\0".encode(str.encoding) newstr.encode!('UTF-16LE') end # Private duplicate of Puppet::Util::Windows::ApiTypes::from_string_to_wide_string # Not for use outside of EventLog! - Use Puppet::Util::Windows instead # @api private def from_string_to_wide_string(str, &block) str = wide_string(str) FFI::MemoryPointer.new(:uchar, str.bytesize) do |ptr| # uchar here is synonymous with byte ptr.put_array_of_uchar(0, str.bytes.to_a) yield ptr end # ptr has already had free called, so nothing to return nil end ffi_convention :stdcall # The following are typedefs in Puppet::Util::Winodws::ApiTypes, but here we # use their original FFI counterparts: # :uintptr_t for :handle # :int32 for :win32_bool # :uint16 for :word # :uint32 for :dword # :pointer for :lpvoid # :uchar for :byte # https://msdn.microsoft.com/en-us/library/windows/desktop/aa363678(v=vs.85).aspx # HANDLE RegisterEventSource( # _In_ LPCTSTR lpUNCServerName, # _In_ LPCTSTR lpSourceName # ); ffi_lib :advapi32 attach_function :RegisterEventSourceW, [:buffer_in, :buffer_in], :uintptr_t private :RegisterEventSourceW # https://msdn.microsoft.com/en-us/library/windows/desktop/aa363642(v=vs.85).aspx # BOOL DeregisterEventSource( # _Inout_ HANDLE hEventLog # ); ffi_lib :advapi32 attach_function :DeregisterEventSource, [:uintptr_t], :int32 private :DeregisterEventSource # https://msdn.microsoft.com/en-us/library/windows/desktop/aa363679(v=vs.85).aspx # BOOL ReportEvent( # _In_ HANDLE hEventLog, # _In_ WORD wType, # _In_ WORD wCategory, # _In_ DWORD dwEventID, # _In_ PSID lpUserSid, # _In_ WORD wNumStrings, # _In_ DWORD dwDataSize, # _In_ LPCTSTR *lpStrings, # _In_ LPVOID lpRawData # ); ffi_lib :advapi32 attach_function :ReportEventW, [:uintptr_t, :uint16, :uint16, :uint32, :pointer, :uint16, :uint32, :pointer, :pointer], :int32 private :ReportEventW end puppet-5.5.10/lib/puppet/util/windows/file.rb0000644005276200011600000004657113417161721021025 0ustar jenkinsjenkinsrequire 'puppet/util/windows' module Puppet::Util::Windows::File require 'ffi' extend FFI::Library extend Puppet::Util::Windows::String FILE_ATTRIBUTE_READONLY = 0x00000001 # https://msdn.microsoft.com/en-us/library/windows/desktop/aa379607(v=vs.85).aspx # The right to use the object for synchronization. This enables a thread to # wait until the object is in the signaled state. Some object types do not # support this access right. SYNCHRONIZE = 0x100000 # The right to delete the object. DELETE = 0x00010000 # The right to read the information in the object's security descriptor, not including the information in the system access control list (SACL). # READ_CONTROL = 0x00020000 # The right to modify the discretionary access control list (DACL) in the object's security descriptor. WRITE_DAC = 0x00040000 # The right to change the owner in the object's security descriptor. WRITE_OWNER = 0x00080000 # Combines DELETE, READ_CONTROL, WRITE_DAC, and WRITE_OWNER access. STANDARD_RIGHTS_REQUIRED = 0xf0000 # Currently defined to equal READ_CONTROL. STANDARD_RIGHTS_READ = 0x20000 # Currently defined to equal READ_CONTROL. STANDARD_RIGHTS_WRITE = 0x20000 # Currently defined to equal READ_CONTROL. STANDARD_RIGHTS_EXECUTE = 0x20000 # Combines DELETE, READ_CONTROL, WRITE_DAC, WRITE_OWNER, and SYNCHRONIZE access. STANDARD_RIGHTS_ALL = 0x1F0000 SPECIFIC_RIGHTS_ALL = 0xFFFF FILE_READ_DATA = 1 FILE_WRITE_DATA = 2 FILE_APPEND_DATA = 4 FILE_READ_EA = 8 FILE_WRITE_EA = 16 FILE_EXECUTE = 32 FILE_DELETE_CHILD = 64 FILE_READ_ATTRIBUTES = 128 FILE_WRITE_ATTRIBUTES = 256 FILE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1FF FILE_GENERIC_READ = STANDARD_RIGHTS_READ | FILE_READ_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA | SYNCHRONIZE FILE_GENERIC_WRITE = STANDARD_RIGHTS_WRITE | FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA | SYNCHRONIZE FILE_GENERIC_EXECUTE = STANDARD_RIGHTS_EXECUTE | FILE_READ_ATTRIBUTES | FILE_EXECUTE | SYNCHRONIZE REPLACEFILE_WRITE_THROUGH = 0x1 REPLACEFILE_IGNORE_MERGE_ERRORS = 0x2 REPLACEFILE_IGNORE_ACL_ERRORS = 0x3 def replace_file(target, source) target_encoded = wide_string(target.to_s) source_encoded = wide_string(source.to_s) flags = REPLACEFILE_IGNORE_MERGE_ERRORS backup_file = nil result = ReplaceFileW( target_encoded, source_encoded, backup_file, flags, FFI::Pointer::NULL, FFI::Pointer::NULL ) return true if result != FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new("ReplaceFile(#{target}, #{source})") end module_function :replace_file def move_file_ex(source, target, flags = 0) result = MoveFileExW(wide_string(source.to_s), wide_string(target.to_s), flags) return true if result != FFI::WIN32_FALSE raise Puppet::Util::Windows::Error. new("MoveFileEx(#{source}, #{target}, #{flags.to_s(8)})") end module_function :move_file_ex def symlink(target, symlink) flags = File.directory?(target) ? 0x1 : 0x0 result = CreateSymbolicLinkW(wide_string(symlink.to_s), wide_string(target.to_s), flags) return true if result != FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new( "CreateSymbolicLink(#{symlink}, #{target}, #{flags.to_s(8)})") end module_function :symlink def exist?(path) path = path.to_str if path.respond_to?(:to_str) # support WatchedFile path = path.to_s # support String and Pathname seen_paths = [] # follow up to 64 symlinks before giving up 0.upto(64) do |depth| # return false if this path has been seen before. This is protection against circular symlinks return false if seen_paths.include?(path.downcase) result = get_attributes(path,false) # return false for path not found return false if result == INVALID_FILE_ATTRIBUTES # return true if path exists and it's not a symlink # Other file attributes are ignored. https://msdn.microsoft.com/en-us/library/windows/desktop/gg258117(v=vs.85).aspx reparse_point = (result & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT if reparse_point && symlink_reparse_point?(path) # walk the symlink and try again... seen_paths << path.downcase path = readlink(path) else # file was found and its not a symlink return true end end false end module_function :exist? INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF #define INVALID_FILE_ATTRIBUTES (DWORD (-1)) def get_attributes(file_name, raise_on_invalid = true) result = GetFileAttributesW(wide_string(file_name.to_s)) if raise_on_invalid && result == INVALID_FILE_ATTRIBUTES raise Puppet::Util::Windows::Error.new("GetFileAttributes(#{file_name})") end result end module_function :get_attributes def add_attributes(path, flags) oldattrs = get_attributes(path) if (oldattrs | flags) != oldattrs set_attributes(path, oldattrs | flags) end end module_function :add_attributes def remove_attributes(path, flags) oldattrs = get_attributes(path) if (oldattrs & ~flags) != oldattrs set_attributes(path, oldattrs & ~flags) end end module_function :remove_attributes def set_attributes(path, flags) success = SetFileAttributesW(wide_string(path), flags) != FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new(_("Failed to set file attributes")) if !success success end module_function :set_attributes #define INVALID_HANDLE_VALUE ((HANDLE)(LONG_PTR)-1) INVALID_HANDLE_VALUE = FFI::Pointer.new(-1).address def self.create_file(file_name, desired_access, share_mode, security_attributes, creation_disposition, flags_and_attributes, template_file_handle) result = CreateFileW(wide_string(file_name.to_s), desired_access, share_mode, security_attributes, creation_disposition, flags_and_attributes, template_file_handle) return result unless result == INVALID_HANDLE_VALUE raise Puppet::Util::Windows::Error.new( "CreateFile(#{file_name}, #{desired_access.to_s(8)}, #{share_mode.to_s(8)}, " + "#{security_attributes}, #{creation_disposition.to_s(8)}, " + "#{flags_and_attributes.to_s(8)}, #{template_file_handle})") end IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003 IO_REPARSE_TAG_HSM = 0xC0000004 IO_REPARSE_TAG_HSM2 = 0x80000006 IO_REPARSE_TAG_SIS = 0x80000007 IO_REPARSE_TAG_WIM = 0x80000008 IO_REPARSE_TAG_CSV = 0x80000009 IO_REPARSE_TAG_DFS = 0x8000000A IO_REPARSE_TAG_SYMLINK = 0xA000000C IO_REPARSE_TAG_DFSR = 0x80000012 IO_REPARSE_TAG_DEDUP = 0x80000013 IO_REPARSE_TAG_NFS = 0x80000014 def self.get_reparse_point_data(handle, &block) # must be multiple of 1024, min 10240 FFI::MemoryPointer.new(MAXIMUM_REPARSE_DATA_BUFFER_SIZE) do |reparse_data_buffer_ptr| device_io_control(handle, FSCTL_GET_REPARSE_POINT, nil, reparse_data_buffer_ptr) reparse_tag = reparse_data_buffer_ptr.read_win32_ulong buffer_type = case reparse_tag when IO_REPARSE_TAG_SYMLINK SYMLINK_REPARSE_DATA_BUFFER when IO_REPARSE_TAG_MOUNT_POINT MOUNT_POINT_REPARSE_DATA_BUFFER when IO_REPARSE_TAG_NFS raise Puppet::Util::Windows::Error.new("Retrieving NFS reparse point data is unsupported") else raise Puppet::Util::Windows::Error.new("DeviceIoControl(#{handle}, " + "FSCTL_GET_REPARSE_POINT) returned unknown tag 0x#{reparse_tag.to_s(16).upcase}") end yield buffer_type.new(reparse_data_buffer_ptr) end # underlying struct MemoryPointer has been cleaned up by this point, nothing to return nil end def self.get_reparse_point_tag(handle) reparse_tag = nil # must be multiple of 1024, min 10240 FFI::MemoryPointer.new(MAXIMUM_REPARSE_DATA_BUFFER_SIZE) do |reparse_data_buffer_ptr| device_io_control(handle, FSCTL_GET_REPARSE_POINT, nil, reparse_data_buffer_ptr) # DWORD ReparseTag is the first member of the struct reparse_tag = reparse_data_buffer_ptr.read_win32_ulong end reparse_tag end def self.device_io_control(handle, io_control_code, in_buffer = nil, out_buffer = nil) if out_buffer.nil? raise Puppet::Util::Windows::Error.new(_("out_buffer is required")) end FFI::MemoryPointer.new(:dword, 1) do |bytes_returned_ptr| result = DeviceIoControl( handle, io_control_code, in_buffer, in_buffer.nil? ? 0 : in_buffer.size, out_buffer, out_buffer.size, bytes_returned_ptr, nil ) if result == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new( "DeviceIoControl(#{handle}, #{io_control_code}, " + "#{in_buffer}, #{in_buffer ? in_buffer.size : ''}, " + "#{out_buffer}, #{out_buffer ? out_buffer.size : ''}") end end out_buffer end FILE_ATTRIBUTE_REPARSE_POINT = 0x400 def reparse_point?(file_name) attributes = get_attributes(file_name, false) return false if (attributes == INVALID_FILE_ATTRIBUTES) (attributes & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT end module_function :reparse_point? def symlink?(file_name) # Puppet currently only handles mount point and symlink reparse points, ignores others reparse_point?(file_name) && symlink_reparse_point?(file_name) end module_function :symlink? GENERIC_READ = 0x80000000 GENERIC_WRITE = 0x40000000 GENERIC_EXECUTE = 0x20000000 GENERIC_ALL = 0x10000000 FILE_SHARE_READ = 1 FILE_SHARE_WRITE = 2 OPEN_EXISTING = 3 FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000 FILE_FLAG_BACKUP_SEMANTICS = 0x02000000 def self.open_symlink(link_name) begin yield handle = create_file( link_name, GENERIC_READ, FILE_SHARE_READ, nil, # security_attributes OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, 0) # template_file ensure FFI::WIN32.CloseHandle(handle) if handle end # handle has had CloseHandle called against it, so nothing to return nil end def readlink(link_name) link = nil open_symlink(link_name) do |handle| link = resolve_symlink(handle) end link end module_function :readlink ERROR_FILE_NOT_FOUND = 2 ERROR_PATH_NOT_FOUND = 3 def get_long_pathname(path) converted = '' FFI::Pointer.from_string_to_wide_string(path) do |path_ptr| # includes terminating NULL buffer_size = GetLongPathNameW(path_ptr, FFI::Pointer::NULL, 0) FFI::MemoryPointer.new(:wchar, buffer_size) do |converted_ptr| if GetLongPathNameW(path_ptr, converted_ptr, buffer_size) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new(_("Failed to call GetLongPathName")) end converted = converted_ptr.read_wide_string(buffer_size - 1) end end converted end module_function :get_long_pathname def get_short_pathname(path) converted = '' FFI::Pointer.from_string_to_wide_string(path) do |path_ptr| # includes terminating NULL buffer_size = GetShortPathNameW(path_ptr, FFI::Pointer::NULL, 0) FFI::MemoryPointer.new(:wchar, buffer_size) do |converted_ptr| if GetShortPathNameW(path_ptr, converted_ptr, buffer_size) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new("Failed to call GetShortPathName") end converted = converted_ptr.read_wide_string(buffer_size - 1) end end converted end module_function :get_short_pathname def stat(file_name) file_name = file_name.to_s # accommodate PathName or String stat = File.stat(file_name) singleton_class = class << stat; self; end target_path = file_name if symlink?(file_name) target_path = readlink(file_name) link_ftype = File.stat(target_path).ftype # sigh, monkey patch instance method for instance, and close over link_ftype singleton_class.send(:define_method, :ftype) do link_ftype end end singleton_class.send(:define_method, :mode) do Puppet::Util::Windows::Security.get_mode(target_path) end stat end module_function :stat def lstat(file_name) file_name = file_name.to_s # accommodate PathName or String # monkey'ing around! stat = File.lstat(file_name) singleton_class = class << stat; self; end singleton_class.send(:define_method, :mode) do Puppet::Util::Windows::Security.get_mode(file_name) end if symlink?(file_name) def stat.ftype "link" end end stat end module_function :lstat # https://msdn.microsoft.com/en-us/library/windows/desktop/aa364571(v=vs.85).aspx FSCTL_GET_REPARSE_POINT = 0x900a8 def self.resolve_symlink(handle) path = nil get_reparse_point_data(handle) do |reparse_data| offset = reparse_data[:PrintNameOffset] length = reparse_data[:PrintNameLength] ptr = reparse_data.pointer + reparse_data.offset_of(:PathBuffer) + offset path = ptr.read_wide_string(length / 2) # length is bytes, need UTF-16 wchars end path end private_class_method :resolve_symlink # these reparse point types are the only ones Puppet currently understands # so rather than raising an exception in readlink, prefer to not consider # the path a symlink when stat'ing later def self.symlink_reparse_point?(path) symlink = false open_symlink(path) do |handle| symlink = [ IO_REPARSE_TAG_SYMLINK, IO_REPARSE_TAG_MOUNT_POINT ].include?(get_reparse_point_tag(handle)) end symlink end private_class_method :symlink_reparse_point? ffi_convention :stdcall # https://msdn.microsoft.com/en-us/library/windows/desktop/aa365512(v=vs.85).aspx # BOOL WINAPI ReplaceFile( # _In_ LPCTSTR lpReplacedFileName, # _In_ LPCTSTR lpReplacementFileName, # _In_opt_ LPCTSTR lpBackupFileName, # _In_ DWORD dwReplaceFlags - 0x1 REPLACEFILE_WRITE_THROUGH, # 0x2 REPLACEFILE_IGNORE_MERGE_ERRORS, # 0x4 REPLACEFILE_IGNORE_ACL_ERRORS # _Reserved_ LPVOID lpExclude, # _Reserved_ LPVOID lpReserved # ); ffi_lib :kernel32 attach_function_private :ReplaceFileW, [:lpcwstr, :lpcwstr, :lpcwstr, :dword, :lpvoid, :lpvoid], :win32_bool # https://msdn.microsoft.com/en-us/library/windows/desktop/aa365240(v=vs.85).aspx # BOOL WINAPI MoveFileEx( # _In_ LPCTSTR lpExistingFileName, # _In_opt_ LPCTSTR lpNewFileName, # _In_ DWORD dwFlags # ); ffi_lib :kernel32 attach_function_private :MoveFileExW, [:lpcwstr, :lpcwstr, :dword], :win32_bool # BOOLEAN WINAPI CreateSymbolicLink( # _In_ LPTSTR lpSymlinkFileName, - symbolic link to be created # _In_ LPTSTR lpTargetFileName, - name of target for symbolic link # _In_ DWORD dwFlags - 0x0 target is a file, 0x1 target is a directory # ); # rescue on Windows < 6.0 so that code doesn't explode begin ffi_lib :kernel32 attach_function_private :CreateSymbolicLinkW, [:lpwstr, :lpwstr, :dword], :boolean rescue LoadError end # https://msdn.microsoft.com/en-us/library/windows/desktop/aa364944(v=vs.85).aspx # DWORD WINAPI GetFileAttributes( # _In_ LPCTSTR lpFileName # ); ffi_lib :kernel32 attach_function_private :GetFileAttributesW, [:lpcwstr], :dword # https://msdn.microsoft.com/en-us/library/windows/desktop/aa365535(v=vs.85).aspx # BOOL WINAPI SetFileAttributes( # _In_ LPCTSTR lpFileName, # _In_ DWORD dwFileAttributes # ); ffi_lib :kernel32 attach_function_private :SetFileAttributesW, [:lpcwstr, :dword], :win32_bool # HANDLE WINAPI CreateFile( # _In_ LPCTSTR lpFileName, # _In_ DWORD dwDesiredAccess, # _In_ DWORD dwShareMode, # _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, # _In_ DWORD dwCreationDisposition, # _In_ DWORD dwFlagsAndAttributes, # _In_opt_ HANDLE hTemplateFile # ); ffi_lib :kernel32 attach_function_private :CreateFileW, [:lpcwstr, :dword, :dword, :pointer, :dword, :dword, :handle], :handle # https://msdn.microsoft.com/en-us/library/windows/desktop/aa363216(v=vs.85).aspx # BOOL WINAPI DeviceIoControl( # _In_ HANDLE hDevice, # _In_ DWORD dwIoControlCode, # _In_opt_ LPVOID lpInBuffer, # _In_ DWORD nInBufferSize, # _Out_opt_ LPVOID lpOutBuffer, # _In_ DWORD nOutBufferSize, # _Out_opt_ LPDWORD lpBytesReturned, # _Inout_opt_ LPOVERLAPPED lpOverlapped # ); ffi_lib :kernel32 attach_function_private :DeviceIoControl, [:handle, :dword, :lpvoid, :dword, :lpvoid, :dword, :lpdword, :pointer], :win32_bool MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16384 # SYMLINK_REPARSE_DATA_BUFFER # https://msdn.microsoft.com/en-us/library/cc232006.aspx # https://msdn.microsoft.com/en-us/library/windows/hardware/ff552012(v=vs.85).aspx # struct is always MAXIMUM_REPARSE_DATA_BUFFER_SIZE bytes class SYMLINK_REPARSE_DATA_BUFFER < FFI::Struct layout :ReparseTag, :win32_ulong, :ReparseDataLength, :ushort, :Reserved, :ushort, :SubstituteNameOffset, :ushort, :SubstituteNameLength, :ushort, :PrintNameOffset, :ushort, :PrintNameLength, :ushort, :Flags, :win32_ulong, # max less above fields dword / uint 4 bytes, ushort 2 bytes # technically a WCHAR buffer, but we care about size in bytes here :PathBuffer, [:byte, MAXIMUM_REPARSE_DATA_BUFFER_SIZE - 20] end # MOUNT_POINT_REPARSE_DATA_BUFFER # https://msdn.microsoft.com/en-us/library/cc232007.aspx # https://msdn.microsoft.com/en-us/library/windows/hardware/ff552012(v=vs.85).aspx # struct is always MAXIMUM_REPARSE_DATA_BUFFER_SIZE bytes class MOUNT_POINT_REPARSE_DATA_BUFFER < FFI::Struct layout :ReparseTag, :win32_ulong, :ReparseDataLength, :ushort, :Reserved, :ushort, :SubstituteNameOffset, :ushort, :SubstituteNameLength, :ushort, :PrintNameOffset, :ushort, :PrintNameLength, :ushort, # max less above fields dword / uint 4 bytes, ushort 2 bytes # technically a WCHAR buffer, but we care about size in bytes here :PathBuffer, [:byte, MAXIMUM_REPARSE_DATA_BUFFER_SIZE - 16] end # https://msdn.microsoft.com/en-us/library/windows/desktop/aa364980(v=vs.85).aspx # DWORD WINAPI GetLongPathName( # _In_ LPCTSTR lpszShortPath, # _Out_ LPTSTR lpszLongPath, # _In_ DWORD cchBuffer # ); ffi_lib :kernel32 attach_function_private :GetLongPathNameW, [:lpcwstr, :lpwstr, :dword], :dword # https://msdn.microsoft.com/en-us/library/windows/desktop/aa364989(v=vs.85).aspx # DWORD WINAPI GetShortPathName( # _In_ LPCTSTR lpszLongPath, # _Out_ LPTSTR lpszShortPath, # _In_ DWORD cchBuffer # ); ffi_lib :kernel32 attach_function_private :GetShortPathNameW, [:lpcwstr, :lpwstr, :dword], :dword end puppet-5.5.10/lib/puppet/util/windows/principal.rb0000644005276200011600000002026613417161721022060 0ustar jenkinsjenkinsrequire 'puppet/util/windows' module Puppet::Util::Windows::SID class Principal extend FFI::Library attr_reader :account, :sid_bytes, :sid, :domain, :domain_account, :account_type def initialize(account, sid_bytes, sid, domain, account_type) # This is only ever called from lookup_account_sid which has already # removed the potential for passing in an account like host\user @account = account @sid_bytes = sid_bytes @sid = sid @domain = domain @account_type = account_type # When domain is available and it is a Domain principal, use domain only # otherwise if domain is available then combine it with parsed account # otherwise when the domain is not available, use the account value directly # WinNT naming standard https://msdn.microsoft.com/en-us/library/windows/desktop/aa746534(v=vs.85).aspx if (domain && !domain.empty? && @account_type == :SidTypeDomain) @domain_account = @domain elsif (domain && !domain.empty?) @domain_account = "#{domain}\\#{@account}" else @domain_account = account end end # added for backward compatibility def ==(compare) compare.is_a?(Puppet::Util::Windows::SID::Principal) && @sid_bytes == compare.sid_bytes end # returns authority qualified account name # prefer to compare Principal instances with == operator or by #sid def to_s @domain_account end # = 8 + max sub identifiers (15) * 4 MAXIMUM_SID_BYTE_LENGTH = 68 ERROR_INSUFFICIENT_BUFFER = 122 def self.lookup_account_name(system_name = nil, account_name) system_name_ptr = FFI::Pointer::NULL begin if system_name system_name_wide = Puppet::Util::Windows::String.wide_string(system_name) # uchar here is synonymous with byte system_name_ptr = FFI::MemoryPointer.new(:byte, system_name_wide.bytesize) system_name_ptr.put_array_of_uchar(0, system_name_wide.bytes.to_a) end FFI::MemoryPointer.from_string_to_wide_string(account_name) do |account_name_ptr| FFI::MemoryPointer.new(:byte, MAXIMUM_SID_BYTE_LENGTH) do |sid_ptr| FFI::MemoryPointer.new(:dword, 1) do |sid_length_ptr| FFI::MemoryPointer.new(:dword, 1) do |domain_length_ptr| FFI::MemoryPointer.new(:uint32, 1) do |name_use_enum_ptr| sid_length_ptr.write_dword(MAXIMUM_SID_BYTE_LENGTH) success = LookupAccountNameW(system_name_ptr, account_name_ptr, sid_ptr, sid_length_ptr, FFI::Pointer::NULL, domain_length_ptr, name_use_enum_ptr) last_error = FFI.errno if (success == FFI::WIN32_FALSE && last_error != ERROR_INSUFFICIENT_BUFFER) raise Puppet::Util::Windows::Error.new(_('Failed to call LookupAccountNameW with account: %{account_name}') % { account_name: account_name}, last_error) end FFI::MemoryPointer.new(:lpwstr, domain_length_ptr.read_dword) do |domain_ptr| if LookupAccountNameW(system_name_ptr, account_name_ptr, sid_ptr, sid_length_ptr, domain_ptr, domain_length_ptr, name_use_enum_ptr) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new(_('Failed to call LookupAccountNameW with account: %{account_name}') % { account_name: account_name} ) end # with a SID returned, loop back through lookup_account_sid to retrieve official name # necessary when accounts like . or '' are passed in return lookup_account_sid( system_name, sid_ptr.read_bytes(sid_length_ptr.read_dword).unpack('C*')) end end end end end end ensure system_name_ptr.free if system_name_ptr != FFI::Pointer::NULL end end def self.lookup_account_sid(system_name = nil, sid_bytes) system_name_ptr = FFI::Pointer::NULL if (sid_bytes.nil? || (!sid_bytes.is_a? Array) || (sid_bytes.length == 0)) #TRANSLATORS `lookup_account_sid` is a variable name and should not be translated raise Puppet::Util::Windows::Error.new(_('Byte array for lookup_account_sid must not be nil and must be at least 1 byte long')) end begin if system_name system_name_wide = Puppet::Util::Windows::String.wide_string(system_name) # uchar here is synonymous with byte system_name_ptr = FFI::MemoryPointer.new(:byte, system_name_wide.bytesize) system_name_ptr.put_array_of_uchar(0, system_name_wide.bytes.to_a) end FFI::MemoryPointer.new(:byte, sid_bytes.length) do |sid_ptr| FFI::MemoryPointer.new(:dword, 1) do |name_length_ptr| FFI::MemoryPointer.new(:dword, 1) do |domain_length_ptr| FFI::MemoryPointer.new(:uint32, 1) do |name_use_enum_ptr| sid_ptr.write_array_of_uchar(sid_bytes) success = LookupAccountSidW(system_name_ptr, sid_ptr, FFI::Pointer::NULL, name_length_ptr, FFI::Pointer::NULL, domain_length_ptr, name_use_enum_ptr) last_error = FFI.errno if (success == FFI::WIN32_FALSE && last_error != ERROR_INSUFFICIENT_BUFFER) raise Puppet::Util::Windows::Error.new(_('Failed to call LookupAccountSidW with bytes: %{sid_bytes}') % { sid_bytes: sid_bytes}, last_error) end FFI::MemoryPointer.new(:lpwstr, name_length_ptr.read_dword) do |name_ptr| FFI::MemoryPointer.new(:lpwstr, domain_length_ptr.read_dword) do |domain_ptr| if LookupAccountSidW(system_name_ptr, sid_ptr, name_ptr, name_length_ptr, domain_ptr, domain_length_ptr, name_use_enum_ptr) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new(_('Failed to call LookupAccountSidW with bytes: %{sid_bytes}') % { sid_bytes: sid_bytes} ) end return new( name_ptr.read_wide_string(name_length_ptr.read_dword), sid_bytes, Puppet::Util::Windows::SID.sid_ptr_to_string(sid_ptr), domain_ptr.read_wide_string(domain_length_ptr.read_dword), SID_NAME_USE[name_use_enum_ptr.read_uint32]) end end end end end end ensure system_name_ptr.free if system_name_ptr != FFI::Pointer::NULL end end ffi_convention :stdcall # https://msdn.microsoft.com/en-us/library/windows/desktop/aa379601(v=vs.85).aspx SID_NAME_USE = enum( :SidTypeUser, 1, :SidTypeGroup, 2, :SidTypeDomain, 3, :SidTypeAlias, 4, :SidTypeWellKnownGroup, 5, :SidTypeDeletedAccount, 6, :SidTypeInvalid, 7, :SidTypeUnknown, 8, :SidTypeComputer, 9, :SidTypeLabel, 10 ) # https://msdn.microsoft.com/en-us/library/windows/desktop/aa379159(v=vs.85).aspx # BOOL WINAPI LookupAccountName( # _In_opt_ LPCTSTR lpSystemName, # _In_ LPCTSTR lpAccountName, # _Out_opt_ PSID Sid, # _Inout_ LPDWORD cbSid, # _Out_opt_ LPTSTR ReferencedDomainName, # _Inout_ LPDWORD cchReferencedDomainName, # _Out_ PSID_NAME_USE peUse # ); ffi_lib :advapi32 attach_function_private :LookupAccountNameW, [:lpcwstr, :lpcwstr, :pointer, :lpdword, :lpwstr, :lpdword, :pointer], :win32_bool # https://msdn.microsoft.com/en-us/library/windows/desktop/aa379166(v=vs.85).aspx # BOOL WINAPI LookupAccountSid( # _In_opt_ LPCTSTR lpSystemName, # _In_ PSID lpSid, # _Out_opt_ LPTSTR lpName, # _Inout_ LPDWORD cchName, # _Out_opt_ LPTSTR lpReferencedDomainName, # _Inout_ LPDWORD cchReferencedDomainName, # _Out_ PSID_NAME_USE peUse # ); ffi_lib :advapi32 attach_function_private :LookupAccountSidW, [:lpcwstr, :pointer, :lpwstr, :lpdword, :lpwstr, :lpdword, :pointer], :win32_bool end end puppet-5.5.10/lib/puppet/util/windows/process.rb0000644005276200011600000004137113417161721021555 0ustar jenkinsjenkinsrequire 'puppet/util/windows' require 'win32/process' require 'ffi' module Puppet::Util::Windows::Process extend Puppet::Util::Windows::String extend FFI::Library WAIT_TIMEOUT = 0x102 WAIT_INTERVAL = 200 # https://docs.microsoft.com/en-us/windows/desktop/ProcThread/process-creation-flags CREATE_NO_WINDOW = 0x08000000 def execute(command, arguments, stdin, stdout, stderr) create_args = { :command_line => command, :startup_info => { :stdin => stdin, :stdout => stdout, :stderr => stderr }, :close_handles => false, } if arguments[:suppress_window] create_args[:creation_flags] = CREATE_NO_WINDOW end cwd = arguments[:cwd] if cwd Dir.chdir(cwd) { Process.create(create_args) } else Process.create(create_args) end end module_function :execute def wait_process(handle) while WaitForSingleObject(handle, WAIT_INTERVAL) == WAIT_TIMEOUT sleep(0) end exit_status = -1 FFI::MemoryPointer.new(:dword, 1) do |exit_status_ptr| if GetExitCodeProcess(handle, exit_status_ptr) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new(_("Failed to get child process exit code")) end exit_status = exit_status_ptr.read_dword # $CHILD_STATUS is not set when calling win32/process Process.create # and since it's read-only, we can't set it. But we can execute a # a shell that simply returns the desired exit status, which has the # desired effect. %x{#{ENV['COMSPEC']} /c exit #{exit_status}} end exit_status end module_function :wait_process def get_current_process # this pseudo-handle does not require closing per MSDN docs GetCurrentProcess() end module_function :get_current_process def open_process_token(handle, desired_access, &block) token_handle = nil begin FFI::MemoryPointer.new(:handle, 1) do |token_handle_ptr| result = OpenProcessToken(handle, desired_access, token_handle_ptr) if result == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new( "OpenProcessToken(#{handle}, #{desired_access.to_s(8)}, #{token_handle_ptr})") end yield token_handle = token_handle_ptr.read_handle end token_handle ensure FFI::WIN32.CloseHandle(token_handle) if token_handle end # token_handle has had CloseHandle called against it, so nothing to return nil end module_function :open_process_token # Execute a block with the current process token def with_process_token(access, &block) handle = get_current_process open_process_token(handle, access) do |token_handle| yield token_handle end # all handles have been closed, so nothing to safely return nil end module_function :with_process_token def lookup_privilege_value(name, system_name = '', &block) FFI::MemoryPointer.new(LUID.size) do |luid_ptr| result = LookupPrivilegeValueW( wide_string(system_name), wide_string(name.to_s), luid_ptr ) if result == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new( "LookupPrivilegeValue(#{system_name}, #{name}, #{luid_ptr})") end yield LUID.new(luid_ptr) end # the underlying MemoryPointer for LUID is cleaned up by this point nil end module_function :lookup_privilege_value def get_token_information(token_handle, token_information, &block) # to determine buffer size FFI::MemoryPointer.new(:dword, 1) do |return_length_ptr| result = GetTokenInformation(token_handle, token_information, nil, 0, return_length_ptr) return_length = return_length_ptr.read_dword if return_length <= 0 raise Puppet::Util::Windows::Error.new( "GetTokenInformation(#{token_handle}, #{token_information}, nil, 0, #{return_length_ptr})") end # re-call API with properly sized buffer for all results FFI::MemoryPointer.new(return_length) do |token_information_buf| result = GetTokenInformation(token_handle, token_information, token_information_buf, return_length, return_length_ptr) if result == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new( "GetTokenInformation(#{token_handle}, #{token_information}, #{token_information_buf}, " + "#{return_length}, #{return_length_ptr})") end yield token_information_buf end end # GetTokenInformation buffer has been cleaned up by this point, nothing to return nil end module_function :get_token_information def parse_token_information_as_token_privileges(token_information_buf) raw_privileges = TOKEN_PRIVILEGES.new(token_information_buf) privileges = { :count => raw_privileges[:PrivilegeCount], :privileges => [] } offset = token_information_buf + TOKEN_PRIVILEGES.offset_of(:Privileges) privilege_ptr = FFI::Pointer.new(LUID_AND_ATTRIBUTES, offset) # extract each instance of LUID_AND_ATTRIBUTES 0.upto(privileges[:count] - 1) do |i| privileges[:privileges] << LUID_AND_ATTRIBUTES.new(privilege_ptr[i]) end privileges end module_function :parse_token_information_as_token_privileges def parse_token_information_as_token_elevation(token_information_buf) TOKEN_ELEVATION.new(token_information_buf) end module_function :parse_token_information_as_token_elevation TOKEN_ALL_ACCESS = 0xF01FF ERROR_NO_SUCH_PRIVILEGE = 1313 def process_privilege_symlink? privilege_symlink = false handle = get_current_process open_process_token(handle, TOKEN_ALL_ACCESS) do |token_handle| lookup_privilege_value('SeCreateSymbolicLinkPrivilege') do |luid| get_token_information(token_handle, :TokenPrivileges) do |token_info| token_privileges = parse_token_information_as_token_privileges(token_info) privilege_symlink = token_privileges[:privileges].any? { |p| p[:Luid].values == luid.values } end end end privilege_symlink rescue Puppet::Util::Windows::Error => e if e.code == ERROR_NO_SUCH_PRIVILEGE false # pre-Vista else raise e end end module_function :process_privilege_symlink? TOKEN_QUERY = 0x0008 # Returns whether or not the owner of the current process is running # with elevated security privileges. # # Only supported on Windows Vista or later. # def elevated_security? # default / pre-Vista elevated = false handle = nil begin handle = get_current_process open_process_token(handle, TOKEN_QUERY) do |token_handle| get_token_information(token_handle, :TokenElevation) do |token_info| token_elevation = parse_token_information_as_token_elevation(token_info) # TokenIsElevated member of the TOKEN_ELEVATION struct elevated = token_elevation[:TokenIsElevated] != 0 end end elevated rescue Puppet::Util::Windows::Error => e raise e if e.code != ERROR_NO_SUCH_PRIVILEGE ensure FFI::WIN32.CloseHandle(handle) if handle end end module_function :elevated_security? def windows_major_version ver = 0 FFI::MemoryPointer.new(OSVERSIONINFO.size) do |os_version_ptr| os_version = OSVERSIONINFO.new(os_version_ptr) os_version[:dwOSVersionInfoSize] = OSVERSIONINFO.size result = GetVersionExW(os_version_ptr) if result == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new(_("GetVersionEx failed")) end ver = os_version[:dwMajorVersion] end ver end module_function :windows_major_version # Returns a hash of the current environment variables encoded as UTF-8 # The memory block returned from GetEnvironmentStringsW is double-null terminated and the vars are paired as below; # Var1=Value1\0 # Var2=Value2\0 # VarX=ValueX\0\0 # Note - Some env variable names start with '=' and are excluded from the return value # Note - The env_ptr MUST be freed using the FreeEnvironmentStringsW function # Note - There is no technical limitation to the size of the environment block returned. # However a practical limit of 64K is used as no single environment variable can exceed 32KB def get_environment_strings env_ptr = GetEnvironmentStringsW() # pass :invalid => :replace to the Ruby String#encode to use replacement characters pairs = env_ptr.read_arbitrary_wide_string_up_to(65534, :double_null, { :invalid => :replace }) .split(?\x00) .reject { |env_str| env_str.nil? || env_str.empty? || env_str[0] == '=' } .reject do |env_str| # reject any string containing the Unicode replacement character if env_str.include?("\uFFFD") Puppet.warning(_("Discarding environment variable %{string} which contains invalid bytes") % { string: env_str }) true end end .map { |env_pair| env_pair.split('=', 2) } Hash[ pairs ] ensure if env_ptr && ! env_ptr.null? if FreeEnvironmentStringsW(env_ptr) == FFI::WIN32_FALSE Puppet.debug "FreeEnvironmentStringsW memory leak" end end end module_function :get_environment_strings def set_environment_variable(name, val) raise Puppet::Util::Windows::Error(_('environment variable name must not be nil or empty')) if ! name || name.empty? FFI::MemoryPointer.from_string_to_wide_string(name) do |name_ptr| if (val.nil?) if SetEnvironmentVariableW(name_ptr, FFI::MemoryPointer::NULL) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new(_("Failed to remove environment variable: %{name}") % { name: name }) end else FFI::MemoryPointer.from_string_to_wide_string(val) do |val_ptr| if SetEnvironmentVariableW(name_ptr, val_ptr) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new(_("Failed to set environment variable: %{name}") % { name: name }) end end end end end module_function :set_environment_variable def get_system_default_ui_language GetSystemDefaultUILanguage() end module_function :get_system_default_ui_language # Returns whether or not the OS has the ability to set elevated # token information. # # Returns true on Windows Vista or later, otherwise false # def supports_elevated_security? windows_major_version >= 6 end module_function :supports_elevated_security? ABOVE_NORMAL_PRIORITY_CLASS = 0x0008000 BELOW_NORMAL_PRIORITY_CLASS = 0x0004000 HIGH_PRIORITY_CLASS = 0x0000080 IDLE_PRIORITY_CLASS = 0x0000040 NORMAL_PRIORITY_CLASS = 0x0000020 REALTIME_PRIORITY_CLASS = 0x0000010 ffi_convention :stdcall # https://msdn.microsoft.com/en-us/library/windows/desktop/ms687032(v=vs.85).aspx # DWORD WINAPI WaitForSingleObject( # _In_ HANDLE hHandle, # _In_ DWORD dwMilliseconds # ); ffi_lib :kernel32 attach_function_private :WaitForSingleObject, [:handle, :dword], :dword # https://msdn.microsoft.com/en-us/library/windows/desktop/ms683189(v=vs.85).aspx # BOOL WINAPI GetExitCodeProcess( # _In_ HANDLE hProcess, # _Out_ LPDWORD lpExitCode # ); ffi_lib :kernel32 attach_function_private :GetExitCodeProcess, [:handle, :lpdword], :win32_bool # https://msdn.microsoft.com/en-us/library/windows/desktop/ms683179(v=vs.85).aspx # HANDLE WINAPI GetCurrentProcess(void); ffi_lib :kernel32 attach_function_private :GetCurrentProcess, [], :handle # https://msdn.microsoft.com/en-us/library/windows/desktop/ms683187(v=vs.85).aspx # LPTCH GetEnvironmentStrings(void); ffi_lib :kernel32 attach_function_private :GetEnvironmentStringsW, [], :pointer # https://msdn.microsoft.com/en-us/library/windows/desktop/ms683151(v=vs.85).aspx # BOOL FreeEnvironmentStrings( # _In_ LPTCH lpszEnvironmentBlock # ); ffi_lib :kernel32 attach_function_private :FreeEnvironmentStringsW, [:pointer], :win32_bool # https://msdn.microsoft.com/en-us/library/windows/desktop/ms686206(v=vs.85).aspx # BOOL WINAPI SetEnvironmentVariableW( # _In_ LPCTSTR lpName, # _In_opt_ LPCTSTR lpValue # ); ffi_lib :kernel32 attach_function_private :SetEnvironmentVariableW, [:lpcwstr, :lpcwstr], :win32_bool # https://msdn.microsoft.com/en-us/library/windows/desktop/aa379295(v=vs.85).aspx # BOOL WINAPI OpenProcessToken( # _In_ HANDLE ProcessHandle, # _In_ DWORD DesiredAccess, # _Out_ PHANDLE TokenHandle # ); ffi_lib :advapi32 attach_function_private :OpenProcessToken, [:handle, :dword, :phandle], :win32_bool # https://msdn.microsoft.com/en-us/library/windows/desktop/aa379261(v=vs.85).aspx # typedef struct _LUID { # DWORD LowPart; # LONG HighPart; # } LUID, *PLUID; class LUID < FFI::Struct layout :LowPart, :dword, :HighPart, :win32_long end # https://msdn.microsoft.com/en-us/library/Windows/desktop/aa379180(v=vs.85).aspx # BOOL WINAPI LookupPrivilegeValue( # _In_opt_ LPCTSTR lpSystemName, # _In_ LPCTSTR lpName, # _Out_ PLUID lpLuid # ); ffi_lib :advapi32 attach_function_private :LookupPrivilegeValueW, [:lpcwstr, :lpcwstr, :pointer], :win32_bool # https://msdn.microsoft.com/en-us/library/windows/desktop/aa379626(v=vs.85).aspx TOKEN_INFORMATION_CLASS = enum( :TokenUser, 1, :TokenGroups, :TokenPrivileges, :TokenOwner, :TokenPrimaryGroup, :TokenDefaultDacl, :TokenSource, :TokenType, :TokenImpersonationLevel, :TokenStatistics, :TokenRestrictedSids, :TokenSessionId, :TokenGroupsAndPrivileges, :TokenSessionReference, :TokenSandBoxInert, :TokenAuditPolicy, :TokenOrigin, :TokenElevationType, :TokenLinkedToken, :TokenElevation, :TokenHasRestrictions, :TokenAccessInformation, :TokenVirtualizationAllowed, :TokenVirtualizationEnabled, :TokenIntegrityLevel, :TokenUIAccess, :TokenMandatoryPolicy, :TokenLogonSid, :TokenIsAppContainer, :TokenCapabilities, :TokenAppContainerSid, :TokenAppContainerNumber, :TokenUserClaimAttributes, :TokenDeviceClaimAttributes, :TokenRestrictedUserClaimAttributes, :TokenRestrictedDeviceClaimAttributes, :TokenDeviceGroups, :TokenRestrictedDeviceGroups, :TokenSecurityAttributes, :TokenIsRestricted, :MaxTokenInfoClass ) # https://msdn.microsoft.com/en-us/library/windows/desktop/aa379263(v=vs.85).aspx # typedef struct _LUID_AND_ATTRIBUTES { # LUID Luid; # DWORD Attributes; # } LUID_AND_ATTRIBUTES, *PLUID_AND_ATTRIBUTES; class LUID_AND_ATTRIBUTES < FFI::Struct layout :Luid, LUID, :Attributes, :dword end # https://msdn.microsoft.com/en-us/library/windows/desktop/aa379630(v=vs.85).aspx # typedef struct _TOKEN_PRIVILEGES { # DWORD PrivilegeCount; # LUID_AND_ATTRIBUTES Privileges[ANYSIZE_ARRAY]; # } TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES; class TOKEN_PRIVILEGES < FFI::Struct layout :PrivilegeCount, :dword, :Privileges, [LUID_AND_ATTRIBUTES, 1] # placeholder for offset end # https://msdn.microsoft.com/en-us/library/windows/desktop/bb530717(v=vs.85).aspx # typedef struct _TOKEN_ELEVATION { # DWORD TokenIsElevated; # } TOKEN_ELEVATION, *PTOKEN_ELEVATION; class TOKEN_ELEVATION < FFI::Struct layout :TokenIsElevated, :dword end # https://msdn.microsoft.com/en-us/library/windows/desktop/aa446671(v=vs.85).aspx # BOOL WINAPI GetTokenInformation( # _In_ HANDLE TokenHandle, # _In_ TOKEN_INFORMATION_CLASS TokenInformationClass, # _Out_opt_ LPVOID TokenInformation, # _In_ DWORD TokenInformationLength, # _Out_ PDWORD ReturnLength # ); ffi_lib :advapi32 attach_function_private :GetTokenInformation, [:handle, TOKEN_INFORMATION_CLASS, :lpvoid, :dword, :pdword ], :win32_bool # https://msdn.microsoft.com/en-us/library/windows/desktop/ms724834%28v=vs.85%29.aspx # typedef struct _OSVERSIONINFO { # DWORD dwOSVersionInfoSize; # DWORD dwMajorVersion; # DWORD dwMinorVersion; # DWORD dwBuildNumber; # DWORD dwPlatformId; # TCHAR szCSDVersion[128]; # } OSVERSIONINFO; class OSVERSIONINFO < FFI::Struct layout( :dwOSVersionInfoSize, :dword, :dwMajorVersion, :dword, :dwMinorVersion, :dword, :dwBuildNumber, :dword, :dwPlatformId, :dword, :szCSDVersion, [:wchar, 128] ) end # https://msdn.microsoft.com/en-us/library/windows/desktop/ms724451(v=vs.85).aspx # BOOL WINAPI GetVersionEx( # _Inout_ LPOSVERSIONINFO lpVersionInfo # ); ffi_lib :kernel32 attach_function_private :GetVersionExW, [:pointer], :win32_bool # https://msdn.microsoft.com/en-us/library/windows/desktop/dd318123(v=vs.85).aspx # LANGID GetSystemDefaultUILanguage(void); ffi_lib :kernel32 attach_function_private :GetSystemDefaultUILanguage, [], :word end puppet-5.5.10/lib/puppet/util/windows/registry.rb0000644005276200011600000003327613417161721021754 0ustar jenkinsjenkinsrequire 'puppet/util/windows' module Puppet::Util::Windows module Registry require 'ffi' extend FFI::Library # https://msdn.microsoft.com/en-us/library/windows/desktop/aa384129(v=vs.85).aspx KEY64 = 0x100 KEY32 = 0x200 KEY_READ = 0x20019 KEY_WRITE = 0x20006 KEY_ALL_ACCESS = 0x2003f ERROR_NO_MORE_ITEMS = 259 def root(name) Win32::Registry.const_get(name) rescue NameError raise Puppet::Error, _("Invalid registry key '%{name}'") % { name: name }, $!.backtrace end def open(name, path, mode = KEY_READ | KEY64, &block) hkey = root(name) begin hkey.open(path, mode) do |subkey| return yield subkey end rescue Win32::Registry::Error => error raise Puppet::Util::Windows::Error.new(_("Failed to open registry key '%{key}\\%{path}'") % { key: hkey.keyname, path: path }, error.code, error) end end def keys(key) keys = {} each_key(key) { |subkey, filetime| keys[subkey] = filetime } keys end # subkey is String which contains name of subkey. # wtime is last write time as FILETIME (64-bit integer). (see Registry.wtime2time) def each_key(key, &block) index = 0 subkey = nil subkey_max_len, _ = reg_query_info_key_max_lengths(key) begin subkey, filetime = reg_enum_key(key, index, subkey_max_len) yield subkey, filetime if !subkey.nil? index += 1 end while !subkey.nil? index end def delete_key(key, subkey_name, mode = KEY64) reg_delete_key_ex(key, subkey_name, mode) end def values(key) vals = {} each_value(key) { |subkey, type, data| vals[subkey] = data } vals end # Retrieve a set of values from a registry key given their names # Value names listed but not found in the registry will not be added to the # resultant Hashtable # # @param key [RegistryKey] An open handle to a Registry Key # @param names [String[]] An array of names of registry values to return if they exist # @return [Hashtable] A hashtable of all of the found values in the registry key def values_by_name(key, names) vals = {} names.each do |name| FFI::Pointer.from_string_to_wide_string(name) do |subkeyname_ptr| begin _, vals[name] = read(key, subkeyname_ptr) rescue Puppet::Util::Windows::Error => e # ignore missing names, but raise other errors raise e unless e.code == Puppet::Util::Windows::Error::ERROR_FILE_NOT_FOUND end end end vals end def each_value(key, &block) index = 0 subkey = nil _, value_max_len = reg_query_info_key_max_lengths(key) begin subkey, type, data = reg_enum_value(key, index, value_max_len) yield subkey, type, data if !subkey.nil? index += 1 end while !subkey.nil? index end def delete_value(key, subkey_name) reg_delete_value(key, subkey_name) end private def reg_enum_key(key, index, max_key_length = Win32::Registry::Constants::MAX_KEY_LENGTH) subkey, filetime = nil, nil FFI::MemoryPointer.new(:dword) do |subkey_length_ptr| FFI::MemoryPointer.new(FFI::WIN32::FILETIME.size) do |filetime_ptr| FFI::MemoryPointer.new(:wchar, max_key_length) do |subkey_ptr| subkey_length_ptr.write_dword(max_key_length) # RegEnumKeyEx cannot be called twice to properly size the buffer result = RegEnumKeyExW(key.hkey, index, subkey_ptr, subkey_length_ptr, FFI::Pointer::NULL, FFI::Pointer::NULL, FFI::Pointer::NULL, filetime_ptr) break if result == ERROR_NO_MORE_ITEMS if result != FFI::ERROR_SUCCESS msg = _("Failed to enumerate %{key} registry keys at index %{index}") % { key: key.keyname, index: index } raise Puppet::Util::Windows::Error.new(msg, result) end filetime = FFI::WIN32::FILETIME.new(filetime_ptr) subkey_length = subkey_length_ptr.read_dword subkey = subkey_ptr.read_wide_string(subkey_length) end end end [subkey, filetime] end def reg_enum_value(key, index, max_value_length = Win32::Registry::Constants::MAX_VALUE_LENGTH) subkey, type, data = nil, nil, nil FFI::MemoryPointer.new(:dword) do |subkey_length_ptr| FFI::MemoryPointer.new(:wchar, max_value_length) do |subkey_ptr| # RegEnumValueW cannot be called twice to properly size the buffer subkey_length_ptr.write_dword(max_value_length) result = RegEnumValueW(key.hkey, index, subkey_ptr, subkey_length_ptr, FFI::Pointer::NULL, FFI::Pointer::NULL, FFI::Pointer::NULL, FFI::Pointer::NULL ) break if result == ERROR_NO_MORE_ITEMS if result != FFI::ERROR_SUCCESS msg = _("Failed to enumerate %{key} registry values at index %{index}") % { key: key.keyname, index: index } raise Puppet::Util::Windows::Error.new(msg, result) end subkey_length = subkey_length_ptr.read_dword subkey = subkey_ptr.read_wide_string(subkey_length) type, data = read(key, subkey_ptr) end end [subkey, type, data] end def reg_query_info_key_max_lengths(key) result = nil FFI::MemoryPointer.new(:dword) do |max_subkey_name_length_ptr| FFI::MemoryPointer.new(:dword) do |max_value_name_length_ptr| status = RegQueryInfoKeyW(key.hkey, FFI::MemoryPointer::NULL, FFI::MemoryPointer::NULL, FFI::MemoryPointer::NULL, FFI::MemoryPointer::NULL, max_subkey_name_length_ptr, FFI::MemoryPointer::NULL, FFI::MemoryPointer::NULL, max_value_name_length_ptr, FFI::MemoryPointer::NULL, FFI::MemoryPointer::NULL, FFI::MemoryPointer::NULL ) if status != FFI::ERROR_SUCCESS msg = _("Failed to query registry %{key} for sizes") % { key: key.keyname } raise Puppet::Util::Windows::Error.new(msg, status) end result = [ # Unicode characters *not* including trailing NULL max_subkey_name_length_ptr.read_dword + 1, max_value_name_length_ptr.read_dword + 1, ] end end result end # Read a registry value named name and return array of # [ type, data ]. # When name is nil, the `default' value is read. # type is value type. (see Win32::Registry::Constants module) # data is value data, its class is: # :REG_SZ, REG_EXPAND_SZ # String # :REG_MULTI_SZ # Array of String # :REG_DWORD, REG_DWORD_BIG_ENDIAN, REG_QWORD # Integer # :REG_BINARY # String (contains binary data) # # When rtype is specified, the value type must be included by # rtype array, or TypeError is raised. def read(key, name_ptr, *rtype) result = nil query_value_ex(key, name_ptr) do |type, data_ptr, byte_length| unless rtype.empty? or rtype.include?(type) raise TypeError, _("Type mismatch (expect %{rtype} but %{type} present)") % { rtype: rtype.inspect, type: type } end string_length = 0 # buffer is raw bytes, *not* chars - less a NULL terminator string_length = (byte_length / FFI.type_size(:wchar)) - 1 if byte_length > 0 begin case type when Win32::Registry::REG_SZ, Win32::Registry::REG_EXPAND_SZ result = [ type, data_ptr.read_wide_string(string_length) ] when Win32::Registry::REG_MULTI_SZ result = [ type, data_ptr.read_wide_string(string_length).split(/\0/) ] when Win32::Registry::REG_BINARY result = [ type, data_ptr.read_bytes(byte_length) ] when Win32::Registry::REG_DWORD result = [ type, data_ptr.read_dword ] when Win32::Registry::REG_DWORD_BIG_ENDIAN result = [ type, data_ptr.order(:big).read_dword ] when Win32::Registry::REG_QWORD result = [ type, data_ptr.read_qword ] else raise TypeError, _("Type %{type} is not supported.") % { type: type } end rescue IndexError => ex raise if (ex.message !~ /^Memory access .* is out of bounds$/i) parent_key_name = key.parent ? "#{key.parent.keyname}\\" : "" Puppet.warning _("A value in the registry key %{parent_key_name}%{key} is corrupt or invalid") % { parent_key_name: parent_key_name, key: key.keyname } end end result end def query_value_ex(key, name_ptr, &block) FFI::MemoryPointer.new(:dword) do |type_ptr| FFI::MemoryPointer.new(:dword) do |length_ptr| result = RegQueryValueExW(key.hkey, name_ptr, FFI::Pointer::NULL, type_ptr, FFI::Pointer::NULL, length_ptr) FFI::MemoryPointer.new(:byte, length_ptr.read_dword) do |buffer_ptr| result = RegQueryValueExW(key.hkey, name_ptr, FFI::Pointer::NULL, type_ptr, buffer_ptr, length_ptr) if result != FFI::ERROR_SUCCESS # buffer is raw bytes, *not* chars - less a NULL terminator name_length = (name_ptr.size / FFI.type_size(:wchar)) - 1 if name_ptr.size > 0 msg = _("Failed to read registry value %{value} at %{key}") % { value: name_ptr.read_wide_string(name_length), key: key.keyname } raise Puppet::Util::Windows::Error.new(msg, result) end # allows caller to use FFI MemoryPointer helpers to read / shape yield [type_ptr.read_dword, buffer_ptr, length_ptr.read_dword] end end end end def reg_delete_value(key, name) result = 0 FFI::Pointer.from_string_to_wide_string(name) do |name_ptr| result = RegDeleteValueW(key.hkey, name_ptr) if result != FFI::ERROR_SUCCESS msg = _("Failed to delete registry value %{name} at %{key}") % { name: name, key: key.keyname } raise Puppet::Util::Windows::Error.new(msg, result) end end result end def reg_delete_key_ex(key, name, regsam = KEY64) result = 0 FFI::Pointer.from_string_to_wide_string(name) do |name_ptr| result = RegDeleteKeyExW(key.hkey, name_ptr, regsam, 0) if result != FFI::ERROR_SUCCESS msg = _("Failed to delete registry key %{name} at %{key}") % { name: name, key: key.keyname } raise Puppet::Util::Windows::Error.new(msg, result) end end result end ffi_convention :stdcall # https://msdn.microsoft.com/en-us/library/windows/desktop/ms724862(v=vs.85).aspx # LONG WINAPI RegEnumKeyEx( # _In_ HKEY hKey, # _In_ DWORD dwIndex, # _Out_ LPTSTR lpName, # _Inout_ LPDWORD lpcName, # _Reserved_ LPDWORD lpReserved, # _Inout_ LPTSTR lpClass, # _Inout_opt_ LPDWORD lpcClass, # _Out_opt_ PFILETIME lpftLastWriteTime # ); ffi_lib :advapi32 attach_function_private :RegEnumKeyExW, [:handle, :dword, :lpwstr, :lpdword, :lpdword, :lpwstr, :lpdword, :pointer], :win32_long # https://msdn.microsoft.com/en-us/library/windows/desktop/ms724865(v=vs.85).aspx # LONG WINAPI RegEnumValue( # _In_ HKEY hKey, # _In_ DWORD dwIndex, # _Out_ LPTSTR lpValueName, # _Inout_ LPDWORD lpcchValueName, # _Reserved_ LPDWORD lpReserved, # _Out_opt_ LPDWORD lpType, # _Out_opt_ LPBYTE lpData, # _Inout_opt_ LPDWORD lpcbData # ); ffi_lib :advapi32 attach_function_private :RegEnumValueW, [:handle, :dword, :lpwstr, :lpdword, :lpdword, :lpdword, :lpbyte, :lpdword], :win32_long # https://msdn.microsoft.com/en-us/library/windows/desktop/ms724911(v=vs.85).aspx # LONG WINAPI RegQueryValueExW( # _In_ HKEY hKey, # _In_opt_ LPCTSTR lpValueName, # _Reserved_ LPDWORD lpReserved, # _Out_opt_ LPDWORD lpType, # _Out_opt_ LPBYTE lpData, # _Inout_opt_ LPDWORD lpcbData # ); ffi_lib :advapi32 attach_function_private :RegQueryValueExW, [:handle, :lpcwstr, :lpdword, :lpdword, :lpbyte, :lpdword], :win32_long # LONG WINAPI RegDeleteValue( # _In_ HKEY hKey, # _In_opt_ LPCTSTR lpValueName # ); ffi_lib :advapi32 attach_function_private :RegDeleteValueW, [:handle, :lpcwstr], :win32_long # LONG WINAPI RegDeleteKeyEx( # _In_ HKEY hKey, # _In_ LPCTSTR lpSubKey, # _In_ REGSAM samDesired, # _Reserved_ DWORD Reserved # ); ffi_lib :advapi32 attach_function_private :RegDeleteKeyExW, [:handle, :lpcwstr, :win32_ulong, :dword], :win32_long # https://msdn.microsoft.com/en-us/library/windows/desktop/ms724902(v=vs.85).aspx # LONG WINAPI RegQueryInfoKey( # _In_ HKEY hKey, # _Out_opt_ LPTSTR lpClass, # _Inout_opt_ LPDWORD lpcClass, # _Reserved_ LPDWORD lpReserved, # _Out_opt_ LPDWORD lpcSubKeys, # _Out_opt_ LPDWORD lpcMaxSubKeyLen, # _Out_opt_ LPDWORD lpcMaxClassLen, # _Out_opt_ LPDWORD lpcValues, # _Out_opt_ LPDWORD lpcMaxValueNameLen, # _Out_opt_ LPDWORD lpcMaxValueLen, # _Out_opt_ LPDWORD lpcbSecurityDescriptor, # _Out_opt_ PFILETIME lpftLastWriteTime # ); ffi_lib :advapi32 attach_function_private :RegQueryInfoKeyW, [:handle, :lpwstr, :lpdword, :lpdword, :lpdword, :lpdword, :lpdword, :lpdword, :lpdword, :lpdword, :lpdword, :pointer], :win32_long end end puppet-5.5.10/lib/puppet/util/windows/root_certs.rb0000644005276200011600000000613213417161721022256 0ustar jenkinsjenkinsrequire 'puppet/util/windows' require 'openssl' require 'ffi' # Represents a collection of trusted root certificates. # # @api public class Puppet::Util::Windows::RootCerts include Enumerable extend FFI::Library def initialize(roots) @roots = roots end # Enumerates each root certificate. # @yieldparam cert [OpenSSL::X509::Certificate] each root certificate # @api public def each @roots.each {|cert| yield cert} end # Returns a new instance. # @return [Puppet::Util::Windows::RootCerts] object constructed from current root certificates def self.instance new(self.load_certs) end # Returns an array of root certificates. # # @return [Array<[OpenSSL::X509::Certificate]>] an array of root certificates # @api private def self.load_certs certs = [] # This is based on a patch submitted to openssl: # https://www.mail-archive.com/openssl-dev@openssl.org/msg26958.html ptr = FFI::Pointer::NULL store = CertOpenSystemStoreA(nil, "ROOT") begin while (ptr = CertEnumCertificatesInStore(store, ptr)) and not ptr.null? context = CERT_CONTEXT.new(ptr) cert_buf = context[:pbCertEncoded].read_bytes(context[:cbCertEncoded]) begin certs << OpenSSL::X509::Certificate.new(cert_buf) rescue => detail Puppet.warning(_("Failed to import root certificate: %{detail}") % { detail: detail.inspect }) end end ensure CertCloseStore(store, 0) end certs end ffi_convention :stdcall # typedef void *HCERTSTORE; # https://msdn.microsoft.com/en-us/library/windows/desktop/aa377189(v=vs.85).aspx # typedef struct _CERT_CONTEXT { # DWORD dwCertEncodingType; # BYTE *pbCertEncoded; # DWORD cbCertEncoded; # PCERT_INFO pCertInfo; # HCERTSTORE hCertStore; # } CERT_CONTEXT, *PCERT_CONTEXT;typedef const CERT_CONTEXT *PCCERT_CONTEXT; class CERT_CONTEXT < FFI::Struct layout( :dwCertEncodingType, :dword, :pbCertEncoded, :pointer, :cbCertEncoded, :dword, :pCertInfo, :pointer, :hCertStore, :handle ) end # https://msdn.microsoft.com/en-us/library/windows/desktop/aa376560(v=vs.85).aspx # HCERTSTORE # WINAPI # CertOpenSystemStoreA( # __in_opt HCRYPTPROV_LEGACY hProv, # __in LPCSTR szSubsystemProtocol # ); # typedef ULONG_PTR HCRYPTPROV_LEGACY; ffi_lib :crypt32 attach_function_private :CertOpenSystemStoreA, [:ulong_ptr, :lpcstr], :handle # https://msdn.microsoft.com/en-us/library/windows/desktop/aa376050(v=vs.85).aspx # PCCERT_CONTEXT # WINAPI # CertEnumCertificatesInStore( # __in HCERTSTORE hCertStore, # __in_opt PCCERT_CONTEXT pPrevCertContext # ); ffi_lib :crypt32 attach_function_private :CertEnumCertificatesInStore, [:handle, :pointer], :pointer # https://msdn.microsoft.com/en-us/library/windows/desktop/aa376026(v=vs.85).aspx # BOOL # WINAPI # CertCloseStore( # __in_opt HCERTSTORE hCertStore, # __in DWORD dwFlags # ); ffi_lib :crypt32 attach_function_private :CertCloseStore, [:handle, :dword], :win32_bool end puppet-5.5.10/lib/puppet/util/windows/security.rb0000644005276200011600000010456413417161721021752 0ustar jenkinsjenkins# This class maps POSIX owner, group, and modes to the Windows # security model, and back. # # The primary goal of this mapping is to ensure that owner, group, and # modes can be round-tripped in a consistent and deterministic # way. Otherwise, Puppet might think file resources are out-of-sync # every time it runs. A secondary goal is to provide equivalent # permissions for common use-cases. For example, setting the owner to # "Administrators", group to "Users", and mode to 750 (which also # denies access to everyone else. # # There are some well-known problems mapping windows and POSIX # permissions due to differences between the two security # models. Search for "POSIX permission mapping leak". In POSIX, access # to a file is determined solely based on the most specific class # (user, group, other). So a mode of 460 would deny write access to # the owner even if they are a member of the group. But in Windows, # the entire access control list is walked until the user is # explicitly denied or allowed (denied take precedence, and if neither # occurs they are denied). As a result, a user could be allowed access # based on their group membership. To solve this problem, other people # have used deny access control entries to more closely model POSIX, # but this introduces a lot of complexity. # # In general, this implementation only supports "typical" permissions, # where group permissions are a subset of user, and other permissions # are a subset of group, e.g. 754, but not 467. However, there are # some Windows quirks to be aware of. # # * The owner can be either a user or group SID, and most system files # are owned by the Administrators group. # * The group can be either a user or group SID. # * Unexpected results can occur if the owner and group are the # same, but the user and group classes are different, e.g. 750. In # this case, it is not possible to allow write access to the owner, # but not the group. As a result, the actual permissions set on the # file would be 770. # * In general, only privileged users can set the owner, group, or # change the mode for files they do not own. In 2003, the user must # be a member of the Administrators group. In Vista/2008, the user # must be running with elevated privileges. # * A file/dir can be deleted by anyone with the DELETE access right # OR by anyone that has the FILE_DELETE_CHILD access right for the # parent. See https://support.microsoft.com/kb/238018. But on Unix, # the user must have write access to the file/dir AND execute access # to all of the parent path components. # * Many access control entries are inherited from parent directories, # and it is common for file/dirs to have more than 3 entries, # e.g. Users, Power Users, Administrators, SYSTEM, etc, which cannot # be mapped into the 3 class POSIX model. The get_mode method will # set the S_IEXTRA bit flag indicating that an access control entry # was found whose SID is neither the owner, group, or other. This # enables Puppet to detect when file/dirs are out-of-sync, # especially those that Puppet did not create, but is attempting # to manage. # * A special case of this is S_ISYSTEM_MISSING, which is set when the # SYSTEM permissions are *not* present on the DACL. # * On Unix, the owner and group can be modified without changing the # mode. But on Windows, an access control entry specifies which SID # it applies to. As a result, the set_owner and set_group methods # automatically rebuild the access control list based on the new # (and different) owner or group. require 'puppet/util/windows' require 'pathname' require 'ffi' module Puppet::Util::Windows::Security include Puppet::Util::Windows::String extend Puppet::Util::Windows::Security extend FFI::Library # file modes S_IRUSR = 0000400 S_IRGRP = 0000040 S_IROTH = 0000004 S_IWUSR = 0000200 S_IWGRP = 0000020 S_IWOTH = 0000002 S_IXUSR = 0000100 S_IXGRP = 0000010 S_IXOTH = 0000001 S_IRWXU = 0000700 S_IRWXG = 0000070 S_IRWXO = 0000007 S_ISVTX = 0001000 S_IEXTRA = 02000000 # represents an extra ace S_ISYSTEM_MISSING = 04000000 # constants that are missing from Windows::Security PROTECTED_DACL_SECURITY_INFORMATION = 0x80000000 UNPROTECTED_DACL_SECURITY_INFORMATION = 0x20000000 NO_INHERITANCE = 0x0 SE_DACL_PROTECTED = 0x1000 FILE = Puppet::Util::Windows::File SE_BACKUP_NAME = 'SeBackupPrivilege' SE_RESTORE_NAME = 'SeRestorePrivilege' DELETE = 0x00010000 READ_CONTROL = 0x20000 WRITE_DAC = 0x40000 WRITE_OWNER = 0x80000 OWNER_SECURITY_INFORMATION = 1 GROUP_SECURITY_INFORMATION = 2 DACL_SECURITY_INFORMATION = 4 # Set the owner of the object referenced by +path+ to the specified # +owner_sid+. The owner sid should be of the form "S-1-5-32-544" # and can either be a user or group. Only a user with the # SE_RESTORE_NAME privilege in their process token can overwrite the # object's owner to something other than the current user. def set_owner(owner_sid, path) sd = get_security_descriptor(path) if owner_sid != sd.owner sd.owner = owner_sid set_security_descriptor(path, sd) end end # Get the owner of the object referenced by +path+. The returned # value is a SID string, e.g. "S-1-5-32-544". Any user with read # access to an object can get the owner. Only a user with the # SE_BACKUP_NAME privilege in their process token can get the owner # for objects they do not have read access to. def get_owner(path) return unless supports_acl?(path) get_security_descriptor(path).owner end # Set the owner of the object referenced by +path+ to the specified # +group_sid+. The group sid should be of the form "S-1-5-32-544" # and can either be a user or group. Any user with WRITE_OWNER # access to the object can change the group (regardless of whether # the current user belongs to that group or not). def set_group(group_sid, path) sd = get_security_descriptor(path) if group_sid != sd.group sd.group = group_sid set_security_descriptor(path, sd) end end # Get the group of the object referenced by +path+. The returned # value is a SID string, e.g. "S-1-5-32-544". Any user with read # access to an object can get the group. Only a user with the # SE_BACKUP_NAME privilege in their process token can get the group # for objects they do not have read access to. def get_group(path) return unless supports_acl?(path) get_security_descriptor(path).group end FILE_PERSISTENT_ACLS = 0x00000008 def supports_acl?(path) supported = false root = Pathname.new(path).enum_for(:ascend).to_a.last.to_s # 'A trailing backslash is required' root = "#{root}\\" unless root =~ /[\/\\]$/ FFI::MemoryPointer.new(:pointer, 1) do |flags_ptr| if GetVolumeInformationW(wide_string(root), FFI::Pointer::NULL, 0, FFI::Pointer::NULL, FFI::Pointer::NULL, flags_ptr, FFI::Pointer::NULL, 0) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new(_("Failed to get volume information")) end supported = flags_ptr.read_dword & FILE_PERSISTENT_ACLS == FILE_PERSISTENT_ACLS end supported end MASK_TO_MODE = { FILE::FILE_GENERIC_READ => S_IROTH, FILE::FILE_GENERIC_WRITE => S_IWOTH, (FILE::FILE_GENERIC_EXECUTE & ~FILE::FILE_READ_ATTRIBUTES) => S_IXOTH } def get_aces_for_path_by_sid(path, sid) get_security_descriptor(path).dacl.select { |ace| ace.sid == sid } end # Get the mode of the object referenced by +path+. The returned # integer value represents the POSIX-style read, write, and execute # modes for the user, group, and other classes, e.g. 0640. Any user # with read access to an object can get the mode. Only a user with # the SE_BACKUP_NAME privilege in their process token can get the # mode for objects they do not have read access to. def get_mode(path) return unless supports_acl?(path) well_known_world_sid = Puppet::Util::Windows::SID::Everyone well_known_nobody_sid = Puppet::Util::Windows::SID::Nobody well_known_system_sid = Puppet::Util::Windows::SID::LocalSystem mode = S_ISYSTEM_MISSING sd = get_security_descriptor(path) sd.dacl.each do |ace| next if ace.inherit_only? case ace.sid when sd.owner MASK_TO_MODE.each_pair do |k,v| if (ace.mask & k) == k mode |= (v << 6) end end when sd.group MASK_TO_MODE.each_pair do |k,v| if (ace.mask & k) == k mode |= (v << 3) end end when well_known_world_sid MASK_TO_MODE.each_pair do |k,v| if (ace.mask & k) == k mode |= (v << 6) | (v << 3) | v end end if File.directory?(path) && (ace.mask & (FILE::FILE_WRITE_DATA | FILE::FILE_EXECUTE | FILE::FILE_DELETE_CHILD)) == (FILE::FILE_WRITE_DATA | FILE::FILE_EXECUTE) mode |= S_ISVTX; end when well_known_nobody_sid if (ace.mask & FILE::FILE_APPEND_DATA).nonzero? mode |= S_ISVTX end when well_known_system_sid else #puts "Warning, unable to map SID into POSIX mode: #{ace.sid}" mode |= S_IEXTRA end if ace.sid == well_known_system_sid mode &= ~S_ISYSTEM_MISSING end # if owner and group the same, then user and group modes are the OR of both if sd.owner == sd.group mode |= ((mode & S_IRWXG) << 3) | ((mode & S_IRWXU) >> 3) #puts "owner: #{sd.group}, 0x#{ace.mask.to_s(16)}, #{mode.to_s(8)}" end end #puts "get_mode: #{mode.to_s(8)}" mode end MODE_TO_MASK = { S_IROTH => FILE::FILE_GENERIC_READ, S_IWOTH => FILE::FILE_GENERIC_WRITE, S_IXOTH => (FILE::FILE_GENERIC_EXECUTE & ~FILE::FILE_READ_ATTRIBUTES), } # Set the mode of the object referenced by +path+ to the specified # +mode+. The mode should be specified as POSIX-style read, write, # and execute modes for the user, group, and other classes, # e.g. 0640. The sticky bit, S_ISVTX, is supported, but is only # meaningful for directories. If set, group and others are not # allowed to delete child objects for which they are not the owner. # By default, the DACL is set to protected, meaning it does not # inherit access control entries from parent objects. This can be # changed by setting +protected+ to false. The owner of the object # (with READ_CONTROL and WRITE_DACL access) can always change the # mode. Only a user with the SE_BACKUP_NAME and SE_RESTORE_NAME # privileges in their process token can change the mode for objects # that they do not have read and write access to. def set_mode(mode, path, protected = true) sd = get_security_descriptor(path) well_known_world_sid = Puppet::Util::Windows::SID::Everyone well_known_nobody_sid = Puppet::Util::Windows::SID::Nobody well_known_system_sid = Puppet::Util::Windows::SID::LocalSystem owner_allow = FILE::STANDARD_RIGHTS_ALL | FILE::FILE_READ_ATTRIBUTES | FILE::FILE_WRITE_ATTRIBUTES # this prevents a mode that is not 7 from taking ownership of a file based # on group membership and rewriting it / making it executable group_allow = FILE::STANDARD_RIGHTS_READ | FILE::FILE_READ_ATTRIBUTES | FILE::SYNCHRONIZE other_allow = FILE::STANDARD_RIGHTS_READ | FILE::FILE_READ_ATTRIBUTES | FILE::SYNCHRONIZE nobody_allow = 0 system_allow = 0 MODE_TO_MASK.each do |k,v| if ((mode >> 6) & k) == k owner_allow |= v end if ((mode >> 3) & k) == k group_allow |= v end if (mode & k) == k other_allow |= v end end # With a mode value of '7' for group / other, the value must then include # additional perms beyond STANDARD_RIGHTS_READ to allow DACL modification if ((mode & S_IRWXG) == S_IRWXG) group_allow |= FILE::DELETE | FILE::WRITE_DAC | FILE::WRITE_OWNER end if ((mode & S_IRWXO) == S_IRWXO) other_allow |= FILE::DELETE | FILE::WRITE_DAC | FILE::WRITE_OWNER end if (mode & S_ISVTX).nonzero? nobody_allow |= FILE::FILE_APPEND_DATA; end # caller is NOT managing SYSTEM by using group or owner, so set to FULL if ! [sd.owner, sd.group].include? well_known_system_sid # we don't check S_ISYSTEM_MISSING bit, but automatically carry over existing SYSTEM perms # by default set SYSTEM perms to full system_allow = FILE::FILE_ALL_ACCESS else # It is possible to set SYSTEM with a mode other than Full Control (7) however this makes no sense and in practical terms # should not be done. We can trap these instances and correct them before being applied. if (sd.owner == well_known_system_sid) && (owner_allow != FILE::FILE_ALL_ACCESS) #TRANSLATORS 'SYSTEM' is a Windows name and should not be translated Puppet.debug _("An attempt to set mode %{mode} on item %{path} would result in the owner, SYSTEM, to have less than Full Control rights. This attempt has been corrected to Full Control") % { mode: mode.to_s(8), path: path } owner_allow = FILE::FILE_ALL_ACCESS end if (sd.group == well_known_system_sid) && (group_allow != FILE::FILE_ALL_ACCESS) #TRANSLATORS 'SYSTEM' is a Windows name and should not be translated Puppet.debug _("An attempt to set mode %{mode} on item %{path} would result in the group, SYSTEM, to have less than Full Control rights. This attempt has been corrected to Full Control") % { mode: mode.to_s(8), path: path } group_allow = FILE::FILE_ALL_ACCESS end end # even though FILE_DELETE_CHILD only applies to directories, it can be set on files # this is necessary to do to ensure a file ends up with (F) FullControl if (mode & (S_IWUSR | S_IXUSR)) == (S_IWUSR | S_IXUSR) owner_allow |= FILE::FILE_DELETE_CHILD end if (mode & (S_IWGRP | S_IXGRP)) == (S_IWGRP | S_IXGRP) && (mode & S_ISVTX) == 0 group_allow |= FILE::FILE_DELETE_CHILD end if (mode & (S_IWOTH | S_IXOTH)) == (S_IWOTH | S_IXOTH) && (mode & S_ISVTX) == 0 other_allow |= FILE::FILE_DELETE_CHILD end # if owner and group the same, then map group permissions to the one owner ACE isownergroup = sd.owner == sd.group if isownergroup owner_allow |= group_allow end # if any ACE allows write, then clear readonly bit, but do this before we overwrite # the DACl and lose our ability to set the attribute if ((owner_allow | group_allow | other_allow ) & FILE::FILE_WRITE_DATA) == FILE::FILE_WRITE_DATA FILE.remove_attributes(path, FILE::FILE_ATTRIBUTE_READONLY) end isdir = File.directory?(path) dacl = Puppet::Util::Windows::AccessControlList.new dacl.allow(sd.owner, owner_allow) unless isownergroup dacl.allow(sd.group, group_allow) end dacl.allow(well_known_world_sid, other_allow) dacl.allow(well_known_nobody_sid, nobody_allow) # TODO: system should be first? flags = !isdir ? 0 : Puppet::Util::Windows::AccessControlEntry::CONTAINER_INHERIT_ACE | Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE dacl.allow(well_known_system_sid, system_allow, flags) # add inherit-only aces for child dirs and files that are created within the dir inherit_only = Puppet::Util::Windows::AccessControlEntry::INHERIT_ONLY_ACE if isdir inherit = inherit_only | Puppet::Util::Windows::AccessControlEntry::CONTAINER_INHERIT_ACE dacl.allow(Puppet::Util::Windows::SID::CreatorOwner, owner_allow, inherit) dacl.allow(Puppet::Util::Windows::SID::CreatorGroup, group_allow, inherit) inherit = inherit_only | Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE # allow any previously set bits *except* for these perms_to_strip = ~(FILE::FILE_EXECUTE + FILE::WRITE_OWNER + FILE::WRITE_DAC) dacl.allow(Puppet::Util::Windows::SID::CreatorOwner, owner_allow & perms_to_strip, inherit) dacl.allow(Puppet::Util::Windows::SID::CreatorGroup, group_allow & perms_to_strip, inherit) end new_sd = Puppet::Util::Windows::SecurityDescriptor.new(sd.owner, sd.group, dacl, protected) set_security_descriptor(path, new_sd) nil end ACL_REVISION = 2 def add_access_allowed_ace(acl, mask, sid, inherit = nil) inherit ||= NO_INHERITANCE Puppet::Util::Windows::SID.string_to_sid_ptr(sid) do |sid_ptr| if Puppet::Util::Windows::SID.IsValidSid(sid_ptr) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new(_("Invalid SID")) end if AddAccessAllowedAceEx(acl, ACL_REVISION, inherit, mask, sid_ptr) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new(_("Failed to add access control entry")) end end # ensure this method is void if it doesn't raise nil end def add_access_denied_ace(acl, mask, sid, inherit = nil) inherit ||= NO_INHERITANCE Puppet::Util::Windows::SID.string_to_sid_ptr(sid) do |sid_ptr| if Puppet::Util::Windows::SID.IsValidSid(sid_ptr) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new(_("Invalid SID")) end if AddAccessDeniedAceEx(acl, ACL_REVISION, inherit, mask, sid_ptr) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new(_("Failed to add access control entry")) end end # ensure this method is void if it doesn't raise nil end def parse_dacl(dacl_ptr) # REMIND: need to handle NULL DACL if IsValidAcl(dacl_ptr) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new(_("Invalid DACL")) end dacl_struct = ACL.new(dacl_ptr) ace_count = dacl_struct[:AceCount] dacl = Puppet::Util::Windows::AccessControlList.new # deny all return dacl if ace_count == 0 0.upto(ace_count - 1) do |i| FFI::MemoryPointer.new(:pointer, 1) do |ace_ptr| next if GetAce(dacl_ptr, i, ace_ptr) == FFI::WIN32_FALSE # ACE structures vary depending on the type. We are only concerned with # ACCESS_ALLOWED_ACE and ACCESS_DENIED_ACEs, which have the same layout ace = GENERIC_ACCESS_ACE.new(ace_ptr.get_pointer(0)) #deref LPVOID * ace_type = ace[:Header][:AceType] if ace_type != Puppet::Util::Windows::AccessControlEntry::ACCESS_ALLOWED_ACE_TYPE && ace_type != Puppet::Util::Windows::AccessControlEntry::ACCESS_DENIED_ACE_TYPE Puppet.warning _("Unsupported access control entry type: 0x%{type}") % { type: ace_type.to_s(16) } next end # using pointer addition gives the FFI::Pointer a size, but that's OK here sid = Puppet::Util::Windows::SID.sid_ptr_to_string(ace.pointer + GENERIC_ACCESS_ACE.offset_of(:SidStart)) mask = ace[:Mask] ace_flags = ace[:Header][:AceFlags] case ace_type when Puppet::Util::Windows::AccessControlEntry::ACCESS_ALLOWED_ACE_TYPE dacl.allow(sid, mask, ace_flags) when Puppet::Util::Windows::AccessControlEntry::ACCESS_DENIED_ACE_TYPE dacl.deny(sid, mask, ace_flags) end end end dacl end # Open an existing file with the specified access mode, and execute a # block with the opened file HANDLE. def open_file(path, access, &block) handle = CreateFileW( wide_string(path), access, FILE::FILE_SHARE_READ | FILE::FILE_SHARE_WRITE, FFI::Pointer::NULL, # security_attributes FILE::OPEN_EXISTING, FILE::FILE_FLAG_OPEN_REPARSE_POINT | FILE::FILE_FLAG_BACKUP_SEMANTICS, FFI::Pointer::NULL_HANDLE) # template if handle == Puppet::Util::Windows::File::INVALID_HANDLE_VALUE raise Puppet::Util::Windows::Error.new(_("Failed to open '%{path}'") % { path: path }) end begin yield handle ensure FFI::WIN32.CloseHandle(handle) if handle end # handle has already had CloseHandle called against it, nothing to return nil end # Execute a block with the specified privilege enabled def with_privilege(privilege, &block) set_privilege(privilege, true) yield ensure set_privilege(privilege, false) end SE_PRIVILEGE_ENABLED = 0x00000002 TOKEN_ADJUST_PRIVILEGES = 0x0020 # Enable or disable a privilege. Note this doesn't add any privileges the # user doesn't already has, it just enables privileges that are disabled. def set_privilege(privilege, enable) return unless Puppet.features.root? Puppet::Util::Windows::Process.with_process_token(TOKEN_ADJUST_PRIVILEGES) do |token| Puppet::Util::Windows::Process.lookup_privilege_value(privilege) do |luid| FFI::MemoryPointer.new(Puppet::Util::Windows::Process::LUID_AND_ATTRIBUTES.size) do |luid_and_attributes_ptr| # allocate unmanaged memory for structs that we clean up afterwards luid_and_attributes = Puppet::Util::Windows::Process::LUID_AND_ATTRIBUTES.new(luid_and_attributes_ptr) luid_and_attributes[:Luid] = luid luid_and_attributes[:Attributes] = enable ? SE_PRIVILEGE_ENABLED : 0 FFI::MemoryPointer.new(Puppet::Util::Windows::Process::TOKEN_PRIVILEGES.size) do |token_privileges_ptr| token_privileges = Puppet::Util::Windows::Process::TOKEN_PRIVILEGES.new(token_privileges_ptr) token_privileges[:PrivilegeCount] = 1 token_privileges[:Privileges][0] = luid_and_attributes # size is correct given we only have 1 LUID, otherwise would be: # [:PrivilegeCount].size + [:PrivilegeCount] * LUID_AND_ATTRIBUTES.size if AdjustTokenPrivileges(token, FFI::WIN32_FALSE, token_privileges, token_privileges.size, FFI::MemoryPointer::NULL, FFI::MemoryPointer::NULL) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new(_("Failed to adjust process privileges")) end end end end end # token / luid structs freed by this point, so return true as nothing raised true end def get_security_descriptor(path) sd = nil with_privilege(SE_BACKUP_NAME) do open_file(path, READ_CONTROL) do |handle| FFI::MemoryPointer.new(:pointer, 1) do |owner_sid_ptr_ptr| FFI::MemoryPointer.new(:pointer, 1) do |group_sid_ptr_ptr| FFI::MemoryPointer.new(:pointer, 1) do |dacl_ptr_ptr| FFI::MemoryPointer.new(:pointer, 1) do |sd_ptr_ptr| rv = GetSecurityInfo( handle, :SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, owner_sid_ptr_ptr, group_sid_ptr_ptr, dacl_ptr_ptr, FFI::Pointer::NULL, #sacl sd_ptr_ptr) #sec desc raise Puppet::Util::Windows::Error.new(_("Failed to get security information")) if rv != FFI::ERROR_SUCCESS # these 2 convenience params are not freed since they point inside sd_ptr owner = Puppet::Util::Windows::SID.sid_ptr_to_string(owner_sid_ptr_ptr.get_pointer(0)) group = Puppet::Util::Windows::SID.sid_ptr_to_string(group_sid_ptr_ptr.get_pointer(0)) FFI::MemoryPointer.new(:word, 1) do |control| FFI::MemoryPointer.new(:dword, 1) do |revision| sd_ptr_ptr.read_win32_local_pointer do |sd_ptr| if GetSecurityDescriptorControl(sd_ptr, control, revision) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new(_("Failed to get security descriptor control")) end protect = (control.read_word & SE_DACL_PROTECTED) == SE_DACL_PROTECTED dacl = parse_dacl(dacl_ptr_ptr.get_pointer(0)) sd = Puppet::Util::Windows::SecurityDescriptor.new(owner, group, dacl, protect) end end end end end end end end end sd end def get_max_generic_acl_size(ace_count) # https://msdn.microsoft.com/en-us/library/windows/desktop/aa378853(v=vs.85).aspx # To calculate the initial size of an ACL, add the following together, and then align the result to the nearest DWORD: # * Size of the ACL structure. # * Size of each ACE structure that the ACL is to contain minus the SidStart member (DWORD) of the ACE. # * Length of the SID that each ACE is to contain. ACL.size + ace_count * MAXIMUM_GENERIC_ACE_SIZE end # setting DACL requires both READ_CONTROL and WRITE_DACL access rights, # and their respective privileges, SE_BACKUP_NAME and SE_RESTORE_NAME. def set_security_descriptor(path, sd) FFI::MemoryPointer.new(:byte, get_max_generic_acl_size(sd.dacl.count)) do |acl_ptr| if InitializeAcl(acl_ptr, acl_ptr.size, ACL_REVISION) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new(_("Failed to initialize ACL")) end if IsValidAcl(acl_ptr) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new(_("Invalid DACL")) end with_privilege(SE_BACKUP_NAME) do with_privilege(SE_RESTORE_NAME) do open_file(path, READ_CONTROL | WRITE_DAC | WRITE_OWNER) do |handle| Puppet::Util::Windows::SID.string_to_sid_ptr(sd.owner) do |owner_sid_ptr| Puppet::Util::Windows::SID.string_to_sid_ptr(sd.group) do |group_sid_ptr| sd.dacl.each do |ace| case ace.type when Puppet::Util::Windows::AccessControlEntry::ACCESS_ALLOWED_ACE_TYPE #puts "ace: allow, sid #{Puppet::Util::Windows::SID.sid_to_name(ace.sid)}, mask 0x#{ace.mask.to_s(16)}" add_access_allowed_ace(acl_ptr, ace.mask, ace.sid, ace.flags) when Puppet::Util::Windows::AccessControlEntry::ACCESS_DENIED_ACE_TYPE #puts "ace: deny, sid #{Puppet::Util::Windows::SID.sid_to_name(ace.sid)}, mask 0x#{ace.mask.to_s(16)}" add_access_denied_ace(acl_ptr, ace.mask, ace.sid, ace.flags) else raise "We should never get here" # TODO: this should have been a warning in an earlier commit end end # protected means the object does not inherit aces from its parent flags = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION flags |= sd.protect ? PROTECTED_DACL_SECURITY_INFORMATION : UNPROTECTED_DACL_SECURITY_INFORMATION rv = SetSecurityInfo(handle, :SE_FILE_OBJECT, flags, owner_sid_ptr, group_sid_ptr, acl_ptr, FFI::MemoryPointer::NULL) if rv != FFI::ERROR_SUCCESS raise Puppet::Util::Windows::Error.new(_("Failed to set security information")) end end end end end end end end ffi_convention :stdcall # https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx # HANDLE WINAPI CreateFile( # _In_ LPCTSTR lpFileName, # _In_ DWORD dwDesiredAccess, # _In_ DWORD dwShareMode, # _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, # _In_ DWORD dwCreationDisposition, # _In_ DWORD dwFlagsAndAttributes, # _In_opt_ HANDLE hTemplateFile # ); ffi_lib :kernel32 attach_function_private :CreateFileW, [:lpcwstr, :dword, :dword, :pointer, :dword, :dword, :handle], :handle # https://msdn.microsoft.com/en-us/library/windows/desktop/aa364993(v=vs.85).aspx # BOOL WINAPI GetVolumeInformation( # _In_opt_ LPCTSTR lpRootPathName, # _Out_opt_ LPTSTR lpVolumeNameBuffer, # _In_ DWORD nVolumeNameSize, # _Out_opt_ LPDWORD lpVolumeSerialNumber, # _Out_opt_ LPDWORD lpMaximumComponentLength, # _Out_opt_ LPDWORD lpFileSystemFlags, # _Out_opt_ LPTSTR lpFileSystemNameBuffer, # _In_ DWORD nFileSystemNameSize # ); ffi_lib :kernel32 attach_function_private :GetVolumeInformationW, [:lpcwstr, :lpwstr, :dword, :lpdword, :lpdword, :lpdword, :lpwstr, :dword], :win32_bool # https://msdn.microsoft.com/en-us/library/windows/desktop/aa374951(v=vs.85).aspx # BOOL WINAPI AddAccessAllowedAceEx( # _Inout_ PACL pAcl, # _In_ DWORD dwAceRevision, # _In_ DWORD AceFlags, # _In_ DWORD AccessMask, # _In_ PSID pSid # ); ffi_lib :advapi32 attach_function_private :AddAccessAllowedAceEx, [:pointer, :dword, :dword, :dword, :pointer], :win32_bool # https://msdn.microsoft.com/en-us/library/windows/desktop/aa374964(v=vs.85).aspx # BOOL WINAPI AddAccessDeniedAceEx( # _Inout_ PACL pAcl, # _In_ DWORD dwAceRevision, # _In_ DWORD AceFlags, # _In_ DWORD AccessMask, # _In_ PSID pSid # ); ffi_lib :advapi32 attach_function_private :AddAccessDeniedAceEx, [:pointer, :dword, :dword, :dword, :pointer], :win32_bool # https://msdn.microsoft.com/en-us/library/windows/desktop/aa374931(v=vs.85).aspx # typedef struct _ACL { # BYTE AclRevision; # BYTE Sbz1; # WORD AclSize; # WORD AceCount; # WORD Sbz2; # } ACL, *PACL; class ACL < FFI::Struct layout :AclRevision, :byte, :Sbz1, :byte, :AclSize, :word, :AceCount, :word, :Sbz2, :word end # https://msdn.microsoft.com/en-us/library/windows/desktop/aa374912(v=vs.85).aspx # ACE types # https://msdn.microsoft.com/en-us/library/windows/desktop/aa374919(v=vs.85).aspx # typedef struct _ACE_HEADER { # BYTE AceType; # BYTE AceFlags; # WORD AceSize; # } ACE_HEADER, *PACE_HEADER; class ACE_HEADER < FFI::Struct layout :AceType, :byte, :AceFlags, :byte, :AceSize, :word end # https://msdn.microsoft.com/en-us/library/windows/desktop/aa374892(v=vs.85).aspx # ACCESS_MASK # https://msdn.microsoft.com/en-us/library/windows/desktop/aa374847(v=vs.85).aspx # typedef struct _ACCESS_ALLOWED_ACE { # ACE_HEADER Header; # ACCESS_MASK Mask; # DWORD SidStart; # } ACCESS_ALLOWED_ACE, *PACCESS_ALLOWED_ACE; # # https://msdn.microsoft.com/en-us/library/windows/desktop/aa374879(v=vs.85).aspx # typedef struct _ACCESS_DENIED_ACE { # ACE_HEADER Header; # ACCESS_MASK Mask; # DWORD SidStart; # } ACCESS_DENIED_ACE, *PACCESS_DENIED_ACE; class GENERIC_ACCESS_ACE < FFI::Struct # ACE structures must be aligned on DWORD boundaries. All Windows # memory-management functions return DWORD-aligned handles to memory pack 4 layout :Header, ACE_HEADER, :Mask, :dword, :SidStart, :dword end # https://stackoverflow.com/a/1792930 MAXIMUM_SID_BYTES_LENGTH = 68 MAXIMUM_GENERIC_ACE_SIZE = GENERIC_ACCESS_ACE.offset_of(:SidStart) + MAXIMUM_SID_BYTES_LENGTH # https://msdn.microsoft.com/en-us/library/windows/desktop/aa446634(v=vs.85).aspx # BOOL WINAPI GetAce( # _In_ PACL pAcl, # _In_ DWORD dwAceIndex, # _Out_ LPVOID *pAce # ); ffi_lib :advapi32 attach_function_private :GetAce, [:pointer, :dword, :pointer], :win32_bool # https://msdn.microsoft.com/en-us/library/windows/desktop/aa375202(v=vs.85).aspx # BOOL WINAPI AdjustTokenPrivileges( # _In_ HANDLE TokenHandle, # _In_ BOOL DisableAllPrivileges, # _In_opt_ PTOKEN_PRIVILEGES NewState, # _In_ DWORD BufferLength, # _Out_opt_ PTOKEN_PRIVILEGES PreviousState, # _Out_opt_ PDWORD ReturnLength # ); ffi_lib :advapi32 attach_function_private :AdjustTokenPrivileges, [:handle, :win32_bool, :pointer, :dword, :pointer, :pdword], :win32_bool # https://msdn.microsoft.com/en-us/library/windows/hardware/ff556610(v=vs.85).aspx # https://msdn.microsoft.com/en-us/library/windows/desktop/aa379561(v=vs.85).aspx # https://msdn.microsoft.com/en-us/library/windows/desktop/aa446647(v=vs.85).aspx # typedef WORD SECURITY_DESCRIPTOR_CONTROL, *PSECURITY_DESCRIPTOR_CONTROL; # BOOL WINAPI GetSecurityDescriptorControl( # _In_ PSECURITY_DESCRIPTOR pSecurityDescriptor, # _Out_ PSECURITY_DESCRIPTOR_CONTROL pControl, # _Out_ LPDWORD lpdwRevision # ); ffi_lib :advapi32 attach_function_private :GetSecurityDescriptorControl, [:pointer, :lpword, :lpdword], :win32_bool # https://msdn.microsoft.com/en-us/library/windows/desktop/aa378853(v=vs.85).aspx # BOOL WINAPI InitializeAcl( # _Out_ PACL pAcl, # _In_ DWORD nAclLength, # _In_ DWORD dwAclRevision # ); ffi_lib :advapi32 attach_function_private :InitializeAcl, [:pointer, :dword, :dword], :win32_bool # https://msdn.microsoft.com/en-us/library/windows/desktop/aa379142(v=vs.85).aspx # BOOL WINAPI IsValidAcl( # _In_ PACL pAcl # ); ffi_lib :advapi32 attach_function_private :IsValidAcl, [:pointer], :win32_bool # https://msdn.microsoft.com/en-us/library/windows/desktop/aa379593(v=vs.85).aspx SE_OBJECT_TYPE = enum( :SE_UNKNOWN_OBJECT_TYPE, 0, :SE_FILE_OBJECT, :SE_SERVICE, :SE_PRINTER, :SE_REGISTRY_KEY, :SE_LMSHARE, :SE_KERNEL_OBJECT, :SE_WINDOW_OBJECT, :SE_DS_OBJECT, :SE_DS_OBJECT_ALL, :SE_PROVIDER_DEFINED_OBJECT, :SE_WMIGUID_OBJECT, :SE_REGISTRY_WOW64_32KEY ) # https://msdn.microsoft.com/en-us/library/windows/desktop/aa446654(v=vs.85).aspx # DWORD WINAPI GetSecurityInfo( # _In_ HANDLE handle, # _In_ SE_OBJECT_TYPE ObjectType, # _In_ SECURITY_INFORMATION SecurityInfo, # _Out_opt_ PSID *ppsidOwner, # _Out_opt_ PSID *ppsidGroup, # _Out_opt_ PACL *ppDacl, # _Out_opt_ PACL *ppSacl, # _Out_opt_ PSECURITY_DESCRIPTOR *ppSecurityDescriptor # ); ffi_lib :advapi32 attach_function_private :GetSecurityInfo, [:handle, SE_OBJECT_TYPE, :dword, :pointer, :pointer, :pointer, :pointer, :pointer], :dword # https://msdn.microsoft.com/en-us/library/windows/desktop/aa379588(v=vs.85).aspx # DWORD WINAPI SetSecurityInfo( # _In_ HANDLE handle, # _In_ SE_OBJECT_TYPE ObjectType, # _In_ SECURITY_INFORMATION SecurityInfo, # _In_opt_ PSID psidOwner, # _In_opt_ PSID psidGroup, # _In_opt_ PACL pDacl, # _In_opt_ PACL pSacl # ); ffi_lib :advapi32 # TODO: SECURITY_INFORMATION is actually a bitmask the size of a DWORD attach_function_private :SetSecurityInfo, [:handle, SE_OBJECT_TYPE, :dword, :pointer, :pointer, :pointer, :pointer], :dword end puppet-5.5.10/lib/puppet/util/windows/security_descriptor.rb0000644005276200011600000000367413417161721024210 0ustar jenkinsjenkins# Windows Security Descriptor # # Represents a security descriptor that can be applied to any Windows securable # object, e.g. file, registry key, service, etc. It consists of an owner, group, # flags, DACL, and SACL. The SACL is not currently supported, though it has the # same layout as a DACL. # # @see https://msdn.microsoft.com/en-us/library/windows/desktop/aa379563(v=vs.85).aspx # @api private class Puppet::Util::Windows::SecurityDescriptor require 'puppet/util/windows/security' include Puppet::Util::Windows::SID attr_reader :owner, :group, :dacl attr_accessor :protect # Construct a security descriptor # # @param owner [String] The SID of the owner, e.g. 'S-1-5-18' # @param group [String] The SID of the group # @param dacl [AccessControlList] The ACL specifying the rights granted to # each user for accessing the object that the security descriptor refers to. # @param protect [Boolean] If true, then inheritable access control # entries will be blocked, and not applied to the object. def initialize(owner, group, dacl, protect = false) @owner = owner @group = group @dacl = dacl @protect = protect end # Set the owner. Non-inherited access control entries assigned to the # current owner will be assigned to the new owner. # # @param new_owner [String] The SID of the new owner, e.g. 'S-1-5-18' def owner=(new_owner) if @owner != new_owner @dacl.reassign!(@owner, new_owner) @owner = new_owner end end # Set the group. Non-inherited access control entries assigned to the # current group will be assigned to the new group. # # @param new_group [String] The SID of the new group, e.g. 'S-1-0-0' def group=(new_group) if @group != new_group @dacl.reassign!(@group, new_group) @group = new_group end end def inspect str = sid_to_name(owner) str << "\n" str << sid_to_name(group) str << "\n" str << @dacl.inspect str end end puppet-5.5.10/lib/puppet/util/windows/sid.rb0000644005276200011600000002463113417161721020656 0ustar jenkinsjenkinsrequire 'puppet/util/windows' module Puppet::Util::Windows module SID require 'ffi' extend FFI::Library # missing from Windows::Error ERROR_NONE_MAPPED = 1332 ERROR_INVALID_SID_STRUCTURE = 1337 # Well Known SIDs Null = 'S-1-0' Nobody = 'S-1-0-0' World = 'S-1-1' Everyone = 'S-1-1-0' Local = 'S-1-2' Creator = 'S-1-3' CreatorOwner = 'S-1-3-0' CreatorGroup = 'S-1-3-1' CreatorOwnerServer = 'S-1-3-2' CreatorGroupServer = 'S-1-3-3' NonUnique = 'S-1-4' Nt = 'S-1-5' Dialup = 'S-1-5-1' Network = 'S-1-5-2' Batch = 'S-1-5-3' Interactive = 'S-1-5-4' Service = 'S-1-5-6' Anonymous = 'S-1-5-7' Proxy = 'S-1-5-8' EnterpriseDomainControllers = 'S-1-5-9' PrincipalSelf = 'S-1-5-10' AuthenticatedUsers = 'S-1-5-11' RestrictedCode = 'S-1-5-12' TerminalServerUsers = 'S-1-5-13' LocalSystem = 'S-1-5-18' NtLocal = 'S-1-5-19' NtNetwork = 'S-1-5-20' BuiltinAdministrators = 'S-1-5-32-544' BuiltinUsers = 'S-1-5-32-545' Guests = 'S-1-5-32-546' PowerUsers = 'S-1-5-32-547' AccountOperators = 'S-1-5-32-548' ServerOperators = 'S-1-5-32-549' PrintOperators = 'S-1-5-32-550' BackupOperators = 'S-1-5-32-551' Replicators = 'S-1-5-32-552' # Convert an account name, e.g. 'Administrators' into a SID string, # e.g. 'S-1-5-32-544'. The name can be specified as 'Administrators', # 'BUILTIN\Administrators', or 'S-1-5-32-544', and will return the # SID. Returns nil if the account doesn't exist. def name_to_sid(name) sid = name_to_principal(name) sid ? sid.sid : nil end module_function :name_to_sid # Convert an account name, e.g. 'Administrators' into a Principal::SID object, # e.g. 'S-1-5-32-544'. The name can be specified as 'Administrators', # 'BUILTIN\Administrators', or 'S-1-5-32-544', and will return the # SID object. Returns nil if the account doesn't exist. # This method returns a SID::Principal with the account, domain, SID, etc def name_to_principal(name) # Apparently, we accept a symbol.. name = name.to_s.strip if name # if name is a SID string, convert it to raw bytes for use with lookup_account_sid raw_sid_bytes = nil begin string_to_sid_ptr(name) do |sid_ptr| raw_sid_bytes = sid_ptr.read_array_of_uchar(get_length_sid(sid_ptr)) end rescue end raw_sid_bytes ? Principal.lookup_account_sid(raw_sid_bytes) : Principal.lookup_account_name(name) rescue nil end module_function :name_to_principal class << self; alias name_to_sid_object name_to_principal; end # Converts an octet string array of bytes to a SID::Principal object, # e.g. [1, 1, 0, 0, 0, 0, 0, 5, 18, 0, 0, 0] is the representation for # S-1-5-18, the local 'SYSTEM' account. # Raises an Error for nil or non-array input. # This method returns a SID::Principal with the account, domain, SID, etc def octet_string_to_principal(bytes) if !bytes || !bytes.respond_to?('pack') || bytes.empty? raise Puppet::Util::Windows::Error.new(_("Octet string must be an array of bytes")) end Principal.lookup_account_sid(bytes) end module_function :octet_string_to_principal class << self; alias octet_string_to_sid_object octet_string_to_principal; end # Converts a COM instance of IAdsUser or IAdsGroup to a SID::Principal object, # Raises an Error for nil or an object without an objectSID / Name property. # This method returns a SID::Principal with the account, domain, SID, etc # This method will return instances even when the SID is unresolvable, as # may be the case when domain users have been added to local groups, but # removed from the domain def ads_to_principal(ads_object) if !ads_object || !ads_object.respond_to?(:ole_respond_to?) || !ads_object.ole_respond_to?(:objectSID) || !ads_object.ole_respond_to?(:Name) raise Puppet::Error.new("ads_object must be an IAdsUser or IAdsGroup instance") end octet_string_to_principal(ads_object.objectSID) rescue Puppet::Util::Windows::Error => e # if the error is not a lookup / mapping problem, immediately re-raise raise if e.code != ERROR_NONE_MAPPED # if the Name property isn't formatted like a SID, OR if !valid_sid?(ads_object.Name) || # if the objectSID doesn't match the Name property, also raise ((converted = octet_string_to_sid_string(ads_object.objectSID)) != ads_object.Name) raise Puppet::Error.new("ads_object Name: #{ads_object.Name} invalid or does not match objectSID: #{ads_object.objectSID} (#{converted})", e) end unresolved_principal(ads_object.Name, ads_object.objectSID) end module_function :ads_to_principal # Convert a SID string, e.g. "S-1-5-32-544" to a name, # e.g. 'BUILTIN\Administrators'. Returns nil if an account # for that SID does not exist. def sid_to_name(value) sid_bytes = [] begin string_to_sid_ptr(value) do |ptr| sid_bytes = ptr.read_array_of_uchar(get_length_sid(ptr)) end rescue Puppet::Util::Windows::Error => e raise if e.code != ERROR_INVALID_SID_STRUCTURE end Principal.lookup_account_sid(sid_bytes).domain_account rescue nil end module_function :sid_to_name # https://stackoverflow.com/a/1792930 - 68 bytes, 184 characters in a string MAXIMUM_SID_STRING_LENGTH = 184 # Convert a SID pointer to a SID string, e.g. "S-1-5-32-544". def sid_ptr_to_string(psid) if ! psid.kind_of?(FFI::Pointer) || IsValidSid(psid) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new(_("Invalid SID")) end sid_string = nil FFI::MemoryPointer.new(:pointer, 1) do |buffer_ptr| if ConvertSidToStringSidW(psid, buffer_ptr) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new(_("Failed to convert binary SID")) end buffer_ptr.read_win32_local_pointer do |wide_string_ptr| if wide_string_ptr.null? raise Puppet::Error.new(_("ConvertSidToStringSidW failed to allocate buffer for sid")) end sid_string = wide_string_ptr.read_arbitrary_wide_string_up_to(MAXIMUM_SID_STRING_LENGTH) end end sid_string end module_function :sid_ptr_to_string # Convert a SID string, e.g. "S-1-5-32-544" to a pointer (containing the # address of the binary SID structure). The returned value can be used in # Win32 APIs that expect a PSID, e.g. IsValidSid. The account for this # SID may or may not exist. def string_to_sid_ptr(string_sid, &block) FFI::MemoryPointer.from_string_to_wide_string(string_sid) do |lpcwstr| FFI::MemoryPointer.new(:pointer, 1) do |sid_ptr_ptr| if ConvertStringSidToSidW(lpcwstr, sid_ptr_ptr) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new(_("Failed to convert string SID: %{string_sid}") % { string_sid: string_sid }) end sid_ptr_ptr.read_win32_local_pointer do |sid_ptr| yield sid_ptr end end end # yielded sid_ptr has already had LocalFree called, nothing to return nil end module_function :string_to_sid_ptr # Return true if the string is a valid SID, e.g. "S-1-5-32-544", false otherwise. def valid_sid?(string_sid) valid = false begin string_to_sid_ptr(string_sid) { |ptr| valid = ! ptr.nil? && ! ptr.null? } rescue Puppet::Util::Windows::Error => e raise if e.code != ERROR_INVALID_SID_STRUCTURE end valid end module_function :valid_sid? def get_length_sid(sid_ptr) # MSDN states IsValidSid should be called on pointer first if ! sid_ptr.kind_of?(FFI::Pointer) || IsValidSid(sid_ptr) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new(_("Invalid SID")) end GetLengthSid(sid_ptr) end module_function :get_length_sid def octet_string_to_sid_string(sid_bytes) sid_string = nil FFI::MemoryPointer.new(:byte, sid_bytes.length) do |sid_ptr| sid_ptr.write_array_of_uchar(sid_bytes) sid_string = Puppet::Util::Windows::SID.sid_ptr_to_string(sid_ptr) end sid_string end module_function :octet_string_to_sid_string # @api private def self.unresolved_principal(name, sid_bytes) Principal.new( name + " (unresolvable)", # account sid_bytes, # sid_bytes name, # sid string nil, #domain # https://msdn.microsoft.com/en-us/library/cc245534.aspx?f=255&MSPPError=-2147217396 # Indicates that the type of object could not be determined. For example, no object with that SID exists. :SidTypeUnknown) end ffi_convention :stdcall # https://msdn.microsoft.com/en-us/library/windows/desktop/aa379151(v=vs.85).aspx # BOOL WINAPI IsValidSid( # _In_ PSID pSid # ); ffi_lib :advapi32 attach_function_private :IsValidSid, [:pointer], :win32_bool # https://msdn.microsoft.com/en-us/library/windows/desktop/aa376399(v=vs.85).aspx # BOOL ConvertSidToStringSid( # _In_ PSID Sid, # _Out_ LPTSTR *StringSid # ); ffi_lib :advapi32 attach_function_private :ConvertSidToStringSidW, [:pointer, :pointer], :win32_bool # https://msdn.microsoft.com/en-us/library/windows/desktop/aa376402(v=vs.85).aspx # BOOL WINAPI ConvertStringSidToSid( # _In_ LPCTSTR StringSid, # _Out_ PSID *Sid # ); ffi_lib :advapi32 attach_function_private :ConvertStringSidToSidW, [:lpcwstr, :pointer], :win32_bool # https://msdn.microsoft.com/en-us/library/windows/desktop/aa446642(v=vs.85).aspx # DWORD WINAPI GetLengthSid( # _In_ PSID pSid # ); ffi_lib :advapi32 attach_function_private :GetLengthSid, [:pointer], :dword end end puppet-5.5.10/lib/puppet/util/windows/string.rb0000644005276200011600000000111713417161721021377 0ustar jenkinsjenkinsrequire 'puppet/util/windows' module Puppet::Util::Windows::String def wide_string(str) # if given a nil string, assume caller wants to pass a nil pointer to win32 return nil if str.nil? # ruby (< 2.1) does not respect multibyte terminators, so it is possible # for a string to contain a single trailing null byte, followed by garbage # causing buffer overruns. # # See http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?revision=41920&view=revision newstr = str + "\0".encode(str.encoding) newstr.encode!('UTF-16LE') end module_function :wide_string end puppet-5.5.10/lib/puppet/util/windows/user.rb0000644005276200011600000003005613417161721021053 0ustar jenkinsjenkinsrequire 'puppet/util/windows' require 'facter' require 'ffi' module Puppet::Util::Windows::User extend Puppet::Util::Windows::String extend FFI::Library def admin? return false unless check_token_membership # if Vista or later, check for unrestricted process token elevated_supported = Puppet::Util::Windows::Process.supports_elevated_security? return elevated_supported ? Puppet::Util::Windows::Process.elevated_security? : true end module_function :admin? # https://msdn.microsoft.com/en-us/library/windows/desktop/ee207397(v=vs.85).aspx SECURITY_MAX_SID_SIZE = 68 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms681385(v=vs.85).aspx # These error codes indicate successful authentication but failure to # logon for a separate reason ERROR_ACCOUNT_RESTRICTION = 1327 ERROR_INVALID_LOGON_HOURS = 1328 ERROR_INVALID_WORKSTATION = 1329 ERROR_ACCOUNT_DISABLED = 1331 def check_token_membership is_admin = false FFI::MemoryPointer.new(:byte, SECURITY_MAX_SID_SIZE) do |sid_pointer| FFI::MemoryPointer.new(:dword, 1) do |size_pointer| size_pointer.write_uint32(SECURITY_MAX_SID_SIZE) if CreateWellKnownSid(:WinBuiltinAdministratorsSid, FFI::Pointer::NULL, sid_pointer, size_pointer) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new(_("Failed to create administrators SID")) end end if IsValidSid(sid_pointer) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new(_("Invalid SID")) end FFI::MemoryPointer.new(:win32_bool, 1) do |ismember_pointer| if CheckTokenMembership(FFI::Pointer::NULL_HANDLE, sid_pointer, ismember_pointer) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new(_("Failed to check membership")) end # Is administrators SID enabled in calling thread's access token? is_admin = ismember_pointer.read_win32_bool end end is_admin end module_function :check_token_membership def password_is?(name, password) begin logon_user(name, password) { |token| } rescue Puppet::Util::Windows::Error => detail authenticated_error_codes = Set[ ERROR_ACCOUNT_RESTRICTION, ERROR_INVALID_LOGON_HOURS, ERROR_INVALID_WORKSTATION, ERROR_ACCOUNT_DISABLED, ] return authenticated_error_codes.include?(detail.code) end end module_function :password_is? def logon_user(name, password, &block) fLOGON32_LOGON_NETWORK = 3 fLOGON32_PROVIDER_DEFAULT = 0 token = nil begin FFI::MemoryPointer.new(:handle, 1) do |token_pointer| if LogonUserW(wide_string(name), wide_string('.'), password.nil? ? FFI::Pointer::NULL : wide_string(password), fLOGON32_LOGON_NETWORK, fLOGON32_PROVIDER_DEFAULT, token_pointer) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new(_("Failed to logon user %{name}") % { name: name.inspect }) end yield token = token_pointer.read_handle end ensure FFI::WIN32.CloseHandle(token) if token end # token has been closed by this point true end module_function :logon_user def load_profile(user, password) logon_user(user, password) do |token| FFI::MemoryPointer.from_string_to_wide_string(user) do |lpUserName| pi = PROFILEINFO.new pi[:dwSize] = PROFILEINFO.size pi[:dwFlags] = 1 # PI_NOUI - prevents display of profile error msgs pi[:lpUserName] = lpUserName # Load the profile. Since it doesn't exist, it will be created if LoadUserProfileW(token, pi.pointer) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new(_("Failed to load user profile %{user}") % { user: user.inspect }) end Puppet.debug("Loaded profile for #{user}") if UnloadUserProfile(token, pi[:hProfile]) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new(_("Failed to unload user profile %{user}") % { user: user.inspect }) end end end end module_function :load_profile ffi_convention :stdcall # https://msdn.microsoft.com/en-us/library/windows/desktop/aa378184(v=vs.85).aspx # BOOL LogonUser( # _In_ LPTSTR lpszUsername, # _In_opt_ LPTSTR lpszDomain, # _In_opt_ LPTSTR lpszPassword, # _In_ DWORD dwLogonType, # _In_ DWORD dwLogonProvider, # _Out_ PHANDLE phToken # ); ffi_lib :advapi32 attach_function_private :LogonUserW, [:lpwstr, :lpwstr, :lpwstr, :dword, :dword, :phandle], :win32_bool # https://msdn.microsoft.com/en-us/library/windows/desktop/bb773378(v=vs.85).aspx # typedef struct _PROFILEINFO { # DWORD dwSize; # DWORD dwFlags; # LPTSTR lpUserName; # LPTSTR lpProfilePath; # LPTSTR lpDefaultPath; # LPTSTR lpServerName; # LPTSTR lpPolicyPath; # HANDLE hProfile; # } PROFILEINFO, *LPPROFILEINFO; # technically # NOTE: that for structs, buffer_* (lptstr alias) cannot be used class PROFILEINFO < FFI::Struct layout :dwSize, :dword, :dwFlags, :dword, :lpUserName, :pointer, :lpProfilePath, :pointer, :lpDefaultPath, :pointer, :lpServerName, :pointer, :lpPolicyPath, :pointer, :hProfile, :handle end # https://msdn.microsoft.com/en-us/library/windows/desktop/bb762281(v=vs.85).aspx # BOOL WINAPI LoadUserProfile( # _In_ HANDLE hToken, # _Inout_ LPPROFILEINFO lpProfileInfo # ); ffi_lib :userenv attach_function_private :LoadUserProfileW, [:handle, :pointer], :win32_bool # https://msdn.microsoft.com/en-us/library/windows/desktop/bb762282(v=vs.85).aspx # BOOL WINAPI UnloadUserProfile( # _In_ HANDLE hToken, # _In_ HANDLE hProfile # ); ffi_lib :userenv attach_function_private :UnloadUserProfile, [:handle, :handle], :win32_bool # https://msdn.microsoft.com/en-us/library/windows/desktop/aa376389(v=vs.85).aspx # BOOL WINAPI CheckTokenMembership( # _In_opt_ HANDLE TokenHandle, # _In_ PSID SidToCheck, # _Out_ PBOOL IsMember # ); ffi_lib :advapi32 attach_function_private :CheckTokenMembership, [:handle, :pointer, :pbool], :win32_bool # https://msdn.microsoft.com/en-us/library/windows/desktop/aa379650(v=vs.85).aspx WELL_KNOWN_SID_TYPE = enum( :WinNullSid , 0, :WinWorldSid , 1, :WinLocalSid , 2, :WinCreatorOwnerSid , 3, :WinCreatorGroupSid , 4, :WinCreatorOwnerServerSid , 5, :WinCreatorGroupServerSid , 6, :WinNtAuthoritySid , 7, :WinDialupSid , 8, :WinNetworkSid , 9, :WinBatchSid , 10, :WinInteractiveSid , 11, :WinServiceSid , 12, :WinAnonymousSid , 13, :WinProxySid , 14, :WinEnterpriseControllersSid , 15, :WinSelfSid , 16, :WinAuthenticatedUserSid , 17, :WinRestrictedCodeSid , 18, :WinTerminalServerSid , 19, :WinRemoteLogonIdSid , 20, :WinLogonIdsSid , 21, :WinLocalSystemSid , 22, :WinLocalServiceSid , 23, :WinNetworkServiceSid , 24, :WinBuiltinDomainSid , 25, :WinBuiltinAdministratorsSid , 26, :WinBuiltinUsersSid , 27, :WinBuiltinGuestsSid , 28, :WinBuiltinPowerUsersSid , 29, :WinBuiltinAccountOperatorsSid , 30, :WinBuiltinSystemOperatorsSid , 31, :WinBuiltinPrintOperatorsSid , 32, :WinBuiltinBackupOperatorsSid , 33, :WinBuiltinReplicatorSid , 34, :WinBuiltinPreWindows2000CompatibleAccessSid , 35, :WinBuiltinRemoteDesktopUsersSid , 36, :WinBuiltinNetworkConfigurationOperatorsSid , 37, :WinAccountAdministratorSid , 38, :WinAccountGuestSid , 39, :WinAccountKrbtgtSid , 40, :WinAccountDomainAdminsSid , 41, :WinAccountDomainUsersSid , 42, :WinAccountDomainGuestsSid , 43, :WinAccountComputersSid , 44, :WinAccountControllersSid , 45, :WinAccountCertAdminsSid , 46, :WinAccountSchemaAdminsSid , 47, :WinAccountEnterpriseAdminsSid , 48, :WinAccountPolicyAdminsSid , 49, :WinAccountRasAndIasServersSid , 50, :WinNTLMAuthenticationSid , 51, :WinDigestAuthenticationSid , 52, :WinSChannelAuthenticationSid , 53, :WinThisOrganizationSid , 54, :WinOtherOrganizationSid , 55, :WinBuiltinIncomingForestTrustBuildersSid , 56, :WinBuiltinPerfMonitoringUsersSid , 57, :WinBuiltinPerfLoggingUsersSid , 58, :WinBuiltinAuthorizationAccessSid , 59, :WinBuiltinTerminalServerLicenseServersSid , 60, :WinBuiltinDCOMUsersSid , 61, :WinBuiltinIUsersSid , 62, :WinIUserSid , 63, :WinBuiltinCryptoOperatorsSid , 64, :WinUntrustedLabelSid , 65, :WinLowLabelSid , 66, :WinMediumLabelSid , 67, :WinHighLabelSid , 68, :WinSystemLabelSid , 69, :WinWriteRestrictedCodeSid , 70, :WinCreatorOwnerRightsSid , 71, :WinCacheablePrincipalsGroupSid , 72, :WinNonCacheablePrincipalsGroupSid , 73, :WinEnterpriseReadonlyControllersSid , 74, :WinAccountReadonlyControllersSid , 75, :WinBuiltinEventLogReadersGroup , 76, :WinNewEnterpriseReadonlyControllersSid , 77, :WinBuiltinCertSvcDComAccessGroup , 78, :WinMediumPlusLabelSid , 79, :WinLocalLogonSid , 80, :WinConsoleLogonSid , 81, :WinThisOrganizationCertificateSid , 82, :WinApplicationPackageAuthoritySid , 83, :WinBuiltinAnyPackageSid , 84, :WinCapabilityInternetClientSid , 85, :WinCapabilityInternetClientServerSid , 86, :WinCapabilityPrivateNetworkClientServerSid , 87, :WinCapabilityPicturesLibrarySid , 88, :WinCapabilityVideosLibrarySid , 89, :WinCapabilityMusicLibrarySid , 90, :WinCapabilityDocumentsLibrarySid , 91, :WinCapabilitySharedUserCertificatesSid , 92, :WinCapabilityEnterpriseAuthenticationSid , 93, :WinCapabilityRemovableStorageSid , 94 ) # https://msdn.microsoft.com/en-us/library/windows/desktop/aa446585(v=vs.85).aspx # BOOL WINAPI CreateWellKnownSid( # _In_ WELL_KNOWN_SID_TYPE WellKnownSidType, # _In_opt_ PSID DomainSid, # _Out_opt_ PSID pSid, # _Inout_ DWORD *cbSid # ); ffi_lib :advapi32 attach_function_private :CreateWellKnownSid, [WELL_KNOWN_SID_TYPE, :pointer, :pointer, :lpdword], :win32_bool # https://msdn.microsoft.com/en-us/library/windows/desktop/aa379151(v=vs.85).aspx # BOOL WINAPI IsValidSid( # _In_ PSID pSid # ); ffi_lib :advapi32 attach_function_private :IsValidSid, [:pointer], :win32_bool end puppet-5.5.10/lib/puppet/util/windows/adsi.rb0000644005276200011600000004543113417161722021021 0ustar jenkinsjenkinsmodule Puppet::Util::Windows::ADSI require 'ffi' class << self extend FFI::Library def connectable?(uri) begin !! connect(uri) rescue false end end def connect(uri) begin WIN32OLE.connect(uri) rescue WIN32OLERuntimeError => e raise Puppet::Error.new( _("ADSI connection error: %{e}") % { e: e }, e ) end end def create(name, resource_type) Puppet::Util::Windows::ADSI.connect(computer_uri).Create(resource_type, name) end def delete(name, resource_type) Puppet::Util::Windows::ADSI.connect(computer_uri).Delete(resource_type, name) end # taken from winbase.h MAX_COMPUTERNAME_LENGTH = 31 def computer_name unless @computer_name max_length = MAX_COMPUTERNAME_LENGTH + 1 # NULL terminated FFI::MemoryPointer.new(max_length * 2) do |buffer| # wide string FFI::MemoryPointer.new(:dword, 1) do |buffer_size| buffer_size.write_dword(max_length) # length in TCHARs if GetComputerNameW(buffer, buffer_size) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new(_("Failed to get computer name")) end @computer_name = buffer.read_wide_string(buffer_size.read_dword) end end end @computer_name end def computer_uri(host = '.') "WinNT://#{host}" end def wmi_resource_uri( host = '.' ) "winmgmts:{impersonationLevel=impersonate}!//#{host}/root/cimv2" end # This method should *only* be used to generate WinNT:// style monikers # used for IAdsGroup::Add / IAdsGroup::Remove. These URIs are not usable # to resolve an account with WIN32OLE.connect # Valid input is a SID::Principal, S-X-X style SID string or any valid # account name with or without domain prefix # @api private def sid_uri_safe(sid) return sid_uri(sid) if sid.kind_of?(Puppet::Util::Windows::SID::Principal) begin sid = Puppet::Util::Windows::SID.name_to_principal(sid) sid_uri(sid) rescue Puppet::Util::Windows::Error, Puppet::Error nil end end # This method should *only* be used to generate WinNT:// style monikers # used for IAdsGroup::Add / IAdsGroup::Remove. These URIs are not useable # to resolve an account with WIN32OLE.connect def sid_uri(sid) raise Puppet::Error.new( _("Must use a valid SID::Principal") ) if !sid.kind_of?(Puppet::Util::Windows::SID::Principal) "WinNT://#{sid.sid}" end def uri(resource_name, resource_type, host = '.') "#{computer_uri(host)}/#{resource_name},#{resource_type}" end def wmi_connection connect(wmi_resource_uri) end def execquery(query) wmi_connection.execquery(query) end ffi_convention :stdcall # https://msdn.microsoft.com/en-us/library/windows/desktop/ms724295(v=vs.85).aspx # BOOL WINAPI GetComputerName( # _Out_ LPTSTR lpBuffer, # _Inout_ LPDWORD lpnSize # ); ffi_lib :kernel32 attach_function_private :GetComputerNameW, [:lpwstr, :lpdword], :win32_bool end # Common base class shared by the User and Group # classes below. class ADSIObject extend Enumerable # Define some useful class-level methods class << self # Is either 'user' or 'group' attr_reader :object_class def localized_domains @localized_domains ||= [ # localized version of BUILTIN # for instance VORDEFINIERT on German Windows Puppet::Util::Windows::SID.sid_to_name('S-1-5-32').upcase, # localized version of NT AUTHORITY (can't use S-1-5) # for instance AUTORITE NT on French Windows Puppet::Util::Windows::SID.name_to_principal('SYSTEM').domain.upcase ] end def uri(name, host = '.') host = '.' if (localized_domains << Socket.gethostname.upcase).include?(host.upcase) Puppet::Util::Windows::ADSI.uri(name, @object_class, host) end def parse_name(name) if name =~ /\// raise Puppet::Error.new( _("Value must be in DOMAIN\\%{object_class} style syntax") % { object_class: @object_class } ) end matches = name.scan(/((.*)\\)?(.*)/) domain = matches[0][1] || '.' account = matches[0][2] return account, domain end # returns Puppet::Util::Windows::SID::Principal[] # may contain objects that represent unresolvable SIDs def get_sids(adsi_child_collection) sids = [] adsi_child_collection.each do |m| sids << Puppet::Util::Windows::SID.ads_to_principal(m) end sids end def name_sid_hash(names) return {} if names.nil? || names.empty? sids = names.map do |name| sid = Puppet::Util::Windows::SID.name_to_principal(name) raise Puppet::Error.new( _("Could not resolve name: %{name}") % { name: name } ) if !sid [sid.sid, sid] end Hash[ sids ] end def delete(name) Puppet::Util::Windows::ADSI.delete(name, @object_class) end def exists?(name_or_sid) well_known = false if (sid = Puppet::Util::Windows::SID.name_to_principal(name_or_sid)) # Examples of SidType include SidTypeUser, SidTypeGroup return true if sid.account_type == "SidType#{@object_class.capitalize}".to_sym # 'well known group' is special as it can be a group like Everyone OR a user like SYSTEM # so try to resolve it # https://msdn.microsoft.com/en-us/library/cc234477.aspx well_known = sid.account_type == :SidTypeWellKnownGroup return false if sid.account_type != :SidTypeAlias && !well_known name_or_sid = "#{sid.domain}\\#{sid.account}" end object = Puppet::Util::Windows::ADSI.connect(uri(*parse_name(name_or_sid))) object.Class.downcase == @object_class rescue # special accounts like SYSTEM or special groups like Authenticated Users cannot # resolve via monikers like WinNT://./SYSTEM,user or WinNT://./Authenticated Users,group # -- they'll fail to connect. thus, given a validly resolved SID, this failure is # ambiguous as it may indicate either a group like Service or an account like SYSTEM well_known end def list_all raise NotImplementedError, _("Subclass must implement class-level method 'list_all'!") end def each(&block) objects = [] list_all.each do |o| # Setting WIN32OLE.codepage in the microsoft_windows feature ensures # values are returned as UTF-8 objects << new(o.name) end objects.each(&block) end end attr_reader :name def initialize(name, native_object = nil) @name = name @native_object = native_object end def object_class self.class.object_class end def uri self.class.uri(sid.account, sid.domain) end def native_object @native_object ||= Puppet::Util::Windows::ADSI.connect(self.class.uri(*self.class.parse_name(name))) end def sid @sid ||= Puppet::Util::Windows::SID.octet_string_to_principal(native_object.objectSID) end def [](attribute) # Setting WIN32OLE.codepage in the microsoft_windows feature ensures # values are returned as UTF-8 native_object.Get(attribute) end def []=(attribute, value) native_object.Put(attribute, value) end def commit begin native_object.SetInfo rescue WIN32OLERuntimeError => e # ERROR_BAD_USERNAME 2202L from winerror.h if e.message =~ /8007089A/m raise Puppet::Error.new( _("Puppet is not able to create/delete domain %{object_class} objects with the %{object_class} resource.") % { object_class: object_class }, ) end raise Puppet::Error.new( _("%{object_class} update failed: %{error}") % { object_class: object_class.capitalize, error: e }, e ) end self end end class User < ADSIObject extend FFI::Library require 'puppet/util/windows/sid' # https://msdn.microsoft.com/en-us/library/aa746340.aspx # IADsUser interface @object_class = 'user' class << self def list_all Puppet::Util::Windows::ADSI.execquery('select name from win32_useraccount where localaccount = "TRUE"') end def logon(name, password) Puppet::Util::Windows::User.password_is?(name, password) end def create(name) # Windows error 1379: The specified local group already exists. raise Puppet::Error.new(_("Cannot create user if group '%{name}' exists.") % { name: name }) if Puppet::Util::Windows::ADSI::Group.exists? name new(name, Puppet::Util::Windows::ADSI.create(name, @object_class)) end end def password_is?(password) self.class.logon(name, password) end def add_flag(flag_name, value) flag = native_object.Get(flag_name) rescue 0 native_object.Put(flag_name, flag | value) commit end def password=(password) if !password.nil? native_object.SetPassword(password) commit end fADS_UF_DONT_EXPIRE_PASSWD = 0x10000 add_flag("UserFlags", fADS_UF_DONT_EXPIRE_PASSWD) end def groups # https://msdn.microsoft.com/en-us/library/aa746342.aspx # WIN32OLE objects aren't enumerable, so no map groups = [] # Setting WIN32OLE.codepage in the microsoft_windows feature ensures # values are returned as UTF-8 native_object.Groups.each {|g| groups << g.Name} rescue nil groups end def add_to_groups(*group_names) group_names.each do |group_name| Puppet::Util::Windows::ADSI::Group.new(group_name).add_member_sids(sid) end end alias add_to_group add_to_groups def remove_from_groups(*group_names) group_names.each do |group_name| Puppet::Util::Windows::ADSI::Group.new(group_name).remove_member_sids(sid) end end alias remove_from_group remove_from_groups def add_group_sids(*sids) group_names = sids.map { |s| s.domain_account } add_to_groups(*group_names) end def remove_group_sids(*sids) group_names = sids.map { |s| s.domain_account } remove_from_groups(*group_names) end def group_sids self.class.get_sids(native_object.Groups) end # TODO: This code's pretty similar to set_members in the Group class. Would be nice # to refactor them into the ADSIObject class at some point. This was not done originally # because these use different methods to do stuff that are also aliased to other methods, # so the shared code isn't exactly a 1:1 mapping. def set_groups(desired_groups, minimum = true) return if desired_groups.nil? desired_groups = desired_groups.split(',').map(&:strip) current_hash = Hash[ self.group_sids.map { |sid| [sid.sid, sid] } ] desired_hash = self.class.name_sid_hash(desired_groups) # First we add the user to all the groups it should be in but isn't if !desired_groups.empty? groups_to_add = (desired_hash.keys - current_hash.keys).map { |sid| desired_hash[sid] } add_group_sids(*groups_to_add) end # Then we remove the user from all groups it is in but shouldn't be, if # that's been requested if !minimum if desired_hash.empty? groups_to_remove = current_hash.values else groups_to_remove = (current_hash.keys - desired_hash.keys).map { |sid| current_hash[sid] } end remove_group_sids(*groups_to_remove) end end # Declare all of the available user flags on the system. Note that # ADS_UF is read as ADS_UserFlag # https://docs.microsoft.com/en-us/windows/desktop/api/iads/ne-iads-ads_user_flag # and # https://support.microsoft.com/en-us/help/305144/how-to-use-the-useraccountcontrol-flags-to-manipulate-user-account-pro # for the flag values. ADS_USERFLAGS = { ADS_UF_SCRIPT: 0x0001, ADS_UF_ACCOUNTDISABLE: 0x0002, ADS_UF_HOMEDIR_REQUIRED: 0x0008, ADS_UF_LOCKOUT: 0x0010, ADS_UF_PASSWD_NOTREQD: 0x0020, ADS_UF_PASSWD_CANT_CHANGE: 0x0040, ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED: 0x0080, ADS_UF_TEMP_DUPLICATE_ACCOUNT: 0x0100, ADS_UF_NORMAL_ACCOUNT: 0x0200, ADS_UF_INTERDOMAIN_TRUST_ACCOUNT: 0x0800, ADS_UF_WORKSTATION_TRUST_ACCOUNT: 0x1000, ADS_UF_SERVER_TRUST_ACCOUNT: 0x2000, ADS_UF_DONT_EXPIRE_PASSWD: 0x10000, ADS_UF_MNS_LOGON_ACCOUNT: 0x20000, ADS_UF_SMARTCARD_REQUIRED: 0x40000, ADS_UF_TRUSTED_FOR_DELEGATION: 0x80000, ADS_UF_NOT_DELEGATED: 0x100000, ADS_UF_USE_DES_KEY_ONLY: 0x200000, ADS_UF_DONT_REQUIRE_PREAUTH: 0x400000, ADS_UF_PASSWORD_EXPIRED: 0x800000, ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION: 0x1000000 } def userflag_set?(flag) flag_value = ADS_USERFLAGS[flag] || 0 ! (self['UserFlags'] & flag_value).zero? end # Common helper for set_userflags and unset_userflags. # # @api private def op_userflags(*flags, &block) # Avoid an unnecessary set + commit operation. return if flags.empty? unrecognized_flags = flags.reject { |flag| ADS_USERFLAGS.keys.include?(flag) } unless unrecognized_flags.empty? raise ArgumentError, _("Unrecognized ADS UserFlags: %{unrecognized_flags}") % { unrecognized_flags: unrecognized_flags.join(', ') } end self['UserFlags'] = flags.inject(self['UserFlags'], &block) end def set_userflags(*flags) op_userflags(*flags) { |userflags, flag| userflags | ADS_USERFLAGS[flag] } end def unset_userflags(*flags) op_userflags(*flags) { |userflags, flag| userflags & ~ADS_USERFLAGS[flag] } end def disabled? userflag_set?(:ADS_UF_ACCOUNTDISABLE) end def locked_out? # Note that the LOCKOUT flag is known to be inaccurate when using the # LDAP IADsUser provider, but this class consistently uses the WinNT # provider, which is expected to be accurate. userflag_set?(:ADS_UF_LOCKOUT) end def expired? expires = native_object.Get('AccountExpirationDate') expires && expires < Time.now rescue WIN32OLERuntimeError => e # This OLE error code indicates the property can't be found in the cache raise e unless e.message =~ /8000500D/m false end # UNLEN from lmcons.h - https://stackoverflow.com/a/2155176 MAX_USERNAME_LENGTH = 256 def self.current_user_name user_name = '' max_length = MAX_USERNAME_LENGTH + 1 # NULL terminated FFI::MemoryPointer.new(max_length * 2) do |buffer| # wide string FFI::MemoryPointer.new(:dword, 1) do |buffer_size| buffer_size.write_dword(max_length) # length in TCHARs if GetUserNameW(buffer, buffer_size) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new(_("Failed to get user name")) end # buffer_size includes trailing NULL user_name = buffer.read_wide_string(buffer_size.read_dword - 1) end end user_name end def self.current_user_sid Puppet::Util::Windows::SID.name_to_principal(current_user_name) end ffi_convention :stdcall # https://msdn.microsoft.com/en-us/library/windows/desktop/ms724432(v=vs.85).aspx # BOOL WINAPI GetUserName( # _Out_ LPTSTR lpBuffer, # _Inout_ LPDWORD lpnSize # ); ffi_lib :advapi32 attach_function_private :GetUserNameW, [:lpwstr, :lpdword], :win32_bool end class UserProfile def self.delete(sid) begin Puppet::Util::Windows::ADSI.wmi_connection.Delete("Win32_UserProfile.SID='#{sid}'") rescue WIN32OLERuntimeError => e # https://social.technet.microsoft.com/Forums/en/ITCG/thread/0f190051-ac96-4bf1-a47f-6b864bfacee5 # Prior to Vista SP1, there's no built-in way to programmatically # delete user profiles (except for delprof.exe). So try to delete # but warn if we fail raise e unless e.message.include?('80041010') Puppet.warning _("Cannot delete user profile for '%{sid}' prior to Vista SP1") % { sid: sid } end end end class Group < ADSIObject # https://msdn.microsoft.com/en-us/library/aa706021.aspx # IADsGroup interface @object_class = 'group' class << self def list_all Puppet::Util::Windows::ADSI.execquery('select name from win32_group where localaccount = "TRUE"') end def create(name) # Windows error 2224: The account already exists. raise Puppet::Error.new( _("Cannot create group if user '%{name}' exists.") % { name: name } ) if Puppet::Util::Windows::ADSI::User.exists?(name) new(name, Puppet::Util::Windows::ADSI.create(name, @object_class)) end end def add_member_sids(*sids) sids.each do |sid| native_object.Add(Puppet::Util::Windows::ADSI.sid_uri(sid)) end end def remove_member_sids(*sids) sids.each do |sid| native_object.Remove(Puppet::Util::Windows::ADSI.sid_uri(sid)) end end # returns Puppet::Util::Windows::SID::Principal[] # may contain objects that represent unresolvable SIDs # qualified account names are returned by calling #domain_account def members self.class.get_sids(native_object.Members) end alias member_sids members def set_members(desired_members, inclusive = true) return if desired_members.nil? current_hash = Hash[ self.member_sids.map { |sid| [sid.sid, sid] } ] desired_hash = self.class.name_sid_hash(desired_members) # First we add all missing members if !desired_hash.empty? members_to_add = (desired_hash.keys - current_hash.keys).map { |sid| desired_hash[sid] } add_member_sids(*members_to_add) end # Then we remove all extra members if inclusive if inclusive if desired_hash.empty? members_to_remove = current_hash.values else members_to_remove = (current_hash.keys - desired_hash.keys).map { |sid| current_hash[sid] } end remove_member_sids(*members_to_remove) end end end end puppet-5.5.10/lib/puppet/util/windows/service.rb0000644005276200011600000012044113417161722021534 0ustar jenkinsjenkinsrequire 'puppet/util/windows' require 'ffi' module Puppet::Util::Windows # This module is designed to provide an API between the windows system and puppet for # service management. # # for an overview of the service state transitions see: https://docs.microsoft.com/en-us/windows/desktop/Services/service-status-transitions module Service extend FFI::Library extend Puppet::Util::Windows::String FILE = Puppet::Util::Windows::File # integer value of the floor for timeouts when waiting for service pending states. # puppet will wait the length of dwWaitHint if it is longer than this value, but # no shorter DEFAULT_TIMEOUT = 30 # Service error codes # https://docs.microsoft.com/en-us/windows/desktop/debug/system-error-codes--1000-1299- ERROR_SERVICE_DOES_NOT_EXIST = 0x00000424 # Service control codes # https://docs.microsoft.com/en-us/windows/desktop/api/Winsvc/nf-winsvc-controlserviceexw SERVICE_CONTROL_STOP = 0x00000001 SERVICE_CONTROL_PAUSE = 0x00000002 SERVICE_CONTROL_CONTINUE = 0x00000003 SERVICE_CONTROL_INTERROGATE = 0x00000004 SERVICE_CONTROL_SHUTDOWN = 0x00000005 SERVICE_CONTROL_PARAMCHANGE = 0x00000006 SERVICE_CONTROL_NETBINDADD = 0x00000007 SERVICE_CONTROL_NETBINDREMOVE = 0x00000008 SERVICE_CONTROL_NETBINDENABLE = 0x00000009 SERVICE_CONTROL_NETBINDDISABLE = 0x0000000A SERVICE_CONTROL_DEVICEEVENT = 0x0000000B SERVICE_CONTROL_HARDWAREPROFILECHANGE = 0x0000000C SERVICE_CONTROL_POWEREVENT = 0x0000000D SERVICE_CONTROL_SESSIONCHANGE = 0x0000000E SERVICE_CONTROL_PRESHUTDOWN = 0x0000000F SERVICE_CONTROL_TIMECHANGE = 0x00000010 SERVICE_CONTROL_TRIGGEREVENT = 0x00000020 SERVICE_CONTROL_SIGNALS = { SERVICE_CONTROL_STOP => :SERVICE_CONTROL_STOP, SERVICE_CONTROL_PAUSE => :SERVICE_CONTROL_PAUSE, SERVICE_CONTROL_CONTINUE => :SERVICE_CONTROL_CONTINUE, SERVICE_CONTROL_INTERROGATE => :SERVICE_CONTROL_INTERROGATE, SERVICE_CONTROL_SHUTDOWN => :SERVICE_CONTROL_SHUTDOWN, SERVICE_CONTROL_PARAMCHANGE => :SERVICE_CONTROL_PARAMCHANGE, SERVICE_CONTROL_NETBINDADD => :SERVICE_CONTROL_NETBINDADD, SERVICE_CONTROL_NETBINDREMOVE => :SERVICE_CONTROL_NETBINDREMOVE, SERVICE_CONTROL_NETBINDENABLE => :SERVICE_CONTROL_NETBINDENABLE, SERVICE_CONTROL_NETBINDDISABLE => :SERVICE_CONTROL_NETBINDDISABLE, SERVICE_CONTROL_DEVICEEVENT => :SERVICE_CONTROL_DEVICEEVENT, SERVICE_CONTROL_HARDWAREPROFILECHANGE => :SERVICE_CONTROL_HARDWAREPROFILECHANGE, SERVICE_CONTROL_POWEREVENT => :SERVICE_CONTROL_POWEREVENT, SERVICE_CONTROL_SESSIONCHANGE => :SERVICE_CONTROL_SESSIONCHANGE, SERVICE_CONTROL_PRESHUTDOWN => :SERVICE_CONTROL_PRESHUTDOWN, SERVICE_CONTROL_TIMECHANGE => :SERVICE_CONTROL_TIMECHANGE, SERVICE_CONTROL_TRIGGEREVENT => :SERVICE_CONTROL_TRIGGEREVENT } # Service start type codes # https://docs.microsoft.com/en-us/windows/desktop/api/Winsvc/nf-winsvc-changeserviceconfigw SERVICE_AUTO_START = 0x00000002 SERVICE_BOOT_START = 0x00000000 SERVICE_DEMAND_START = 0x00000003 SERVICE_DISABLED = 0x00000004 SERVICE_SYSTEM_START = 0x00000001 SERVICE_START_TYPES = { SERVICE_AUTO_START => :SERVICE_AUTO_START, SERVICE_BOOT_START => :SERVICE_BOOT_START, SERVICE_DEMAND_START => :SERVICE_DEMAND_START, SERVICE_DISABLED => :SERVICE_DISABLED, SERVICE_SYSTEM_START => :SERVICE_SYSTEM_START, } # Service type codes # https://docs.microsoft.com/en-us/windows/desktop/api/Winsvc/nf-winsvc-changeserviceconfigw SERVICE_FILE_SYSTEM_DRIVER = 0x00000002 SERVICE_KERNEL_DRIVER = 0x00000001 SERVICE_WIN32_OWN_PROCESS = 0x00000010 SERVICE_WIN32_SHARE_PROCESS = 0x00000020 SERVICE_USER_OWN_PROCESS = 0x00000050 SERVICE_USER_SHARE_PROCESS = 0x00000060 # Available only if service is also SERVICE_WIN32_OWN_PROCESS or SERVICE_WIN32_SHARE_PROCESS SERVICE_INTERACTIVE_PROCESS = 0x00000100 ALL_SERVICE_TYPES = SERVICE_FILE_SYSTEM_DRIVER | SERVICE_KERNEL_DRIVER | SERVICE_WIN32_OWN_PROCESS | SERVICE_WIN32_SHARE_PROCESS # Current state codes # https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/ns-winsvc-_service_status_process SERVICE_CONTINUE_PENDING = 0x00000005 SERVICE_PAUSE_PENDING = 0x00000006 SERVICE_PAUSED = 0x00000007 SERVICE_RUNNING = 0x00000004 SERVICE_START_PENDING = 0x00000002 SERVICE_STOP_PENDING = 0x00000003 SERVICE_STOPPED = 0x00000001 UNSAFE_PENDING_STATES = [SERVICE_START_PENDING, SERVICE_STOP_PENDING] FINAL_STATES = { SERVICE_CONTINUE_PENDING => SERVICE_RUNNING, SERVICE_PAUSE_PENDING => SERVICE_PAUSED, SERVICE_START_PENDING => SERVICE_RUNNING, SERVICE_STOP_PENDING => SERVICE_STOPPED } SERVICE_STATES = { SERVICE_CONTINUE_PENDING => :SERVICE_CONTINUE_PENDING, SERVICE_PAUSE_PENDING => :SERVICE_PAUSE_PENDING, SERVICE_PAUSED => :SERVICE_PAUSED, SERVICE_RUNNING => :SERVICE_RUNNING, SERVICE_START_PENDING => :SERVICE_START_PENDING, SERVICE_STOP_PENDING => :SERVICE_STOP_PENDING, SERVICE_STOPPED => :SERVICE_STOPPED, } # Service accepts control codes # https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/ns-winsvc-_service_status_process SERVICE_ACCEPT_STOP = 0x00000001 SERVICE_ACCEPT_PAUSE_CONTINUE = 0x00000002 SERVICE_ACCEPT_SHUTDOWN = 0x00000004 SERVICE_ACCEPT_PARAMCHANGE = 0x00000008 SERVICE_ACCEPT_NETBINDCHANGE = 0x00000010 SERVICE_ACCEPT_HARDWAREPROFILECHANGE = 0x00000020 SERVICE_ACCEPT_POWEREVENT = 0x00000040 SERVICE_ACCEPT_SESSIONCHANGE = 0x00000080 SERVICE_ACCEPT_PRESHUTDOWN = 0x00000100 SERVICE_ACCEPT_TIMECHANGE = 0x00000200 SERVICE_ACCEPT_TRIGGEREVENT = 0x00000400 SERVICE_ACCEPT_USER_LOGOFF = 0x00000800 # Service manager access codes # https://docs.microsoft.com/en-us/windows/desktop/Services/service-security-and-access-rights SC_MANAGER_CREATE_SERVICE = 0x00000002 SC_MANAGER_CONNECT = 0x00000001 SC_MANAGER_ENUMERATE_SERVICE = 0x00000004 SC_MANAGER_LOCK = 0x00000008 SC_MANAGER_MODIFY_BOOT_CONFIG = 0x00000020 SC_MANAGER_QUERY_LOCK_STATUS = 0x00000010 SC_MANAGER_ALL_ACCESS = FILE::STANDARD_RIGHTS_REQUIRED | SC_MANAGER_CREATE_SERVICE | SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE | SC_MANAGER_LOCK | SC_MANAGER_MODIFY_BOOT_CONFIG | SC_MANAGER_QUERY_LOCK_STATUS # Service access codes # https://docs.microsoft.com/en-us/windows/desktop/Services/service-security-and-access-rights SERVICE_CHANGE_CONFIG = 0x0002 SERVICE_ENUMERATE_DEPENDENTS = 0x0008 SERVICE_INTERROGATE = 0x0080 SERVICE_PAUSE_CONTINUE = 0x0040 SERVICE_QUERY_STATUS = 0x0004 SERVICE_QUERY_CONFIG = 0x0001 SERVICE_START = 0x0010 SERVICE_STOP = 0x0020 SERVICE_USER_DEFINED_CONTROL = 0x0100 SERVICE_ALL_ACCESS = FILE::STANDARD_RIGHTS_REQUIRED | SERVICE_CHANGE_CONFIG | SERVICE_ENUMERATE_DEPENDENTS | SERVICE_INTERROGATE | SERVICE_PAUSE_CONTINUE | SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG | SERVICE_START | SERVICE_STOP | SERVICE_USER_DEFINED_CONTROL # Service config codes # From the windows 10 SDK: # // # // Value to indicate no change to an optional parameter # // # #define SERVICE_NO_CHANGE 0xffffffff SERVICE_NO_CHANGE = 0xffffffff # Service enum codes # https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/nf-winsvc-enumservicesstatusexa SERVICE_ACTIVE = 0x00000001 SERVICE_INACTIVE = 0x00000002 SERVICE_STATE_ALL = SERVICE_ACTIVE | SERVICE_INACTIVE # https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/ns-winsvc-_enum_service_status_processw SERVICENAME_MAX = 256 # https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/ns-winsvc-_service_status_process # typedef struct _SERVICE_STATUS_PROCESS { # DWORD dwServiceType; # DWORD dwCurrentState; # DWORD dwControlsAccepted; # DWORD dwWin32ExitCode; # DWORD dwServiceSpecificExitCode; # DWORD dwCheckPoint; # DWORD dwWaitHint; # DWORD dwProcessId; # DWORD dwServiceFlags; # } SERVICE_STATUS_PROCESS, *LPSERVICE_STATUS_PROCESS; class SERVICE_STATUS_PROCESS < FFI::Struct layout( :dwServiceType, :dword, :dwCurrentState, :dword, :dwControlsAccepted, :dword, :dwWin32ExitCode, :dword, :dwServiceSpecificExitCode, :dword, :dwCheckPoint, :dword, :dwWaitHint, :dword, :dwProcessId, :dword, :dwServiceFlags, :dword ) end # https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/ns-winsvc-_enum_service_status_processw # typedef struct _ENUM_SERVICE_STATUS_PROCESSW { # LPWSTR lpServiceName; # LPWSTR lpDisplayName; # SERVICE_STATUS_PROCESS ServiceStatusProcess; # } ENUM_SERVICE_STATUS_PROCESSW, *LPENUM_SERVICE_STATUS_PROCESSW; class ENUM_SERVICE_STATUS_PROCESSW < FFI::Struct layout( :lpServiceName, :pointer, :lpDisplayName, :pointer, :ServiceStatusProcess, SERVICE_STATUS_PROCESS ) end # typedef struct _SERVICE_STATUS { # DWORD dwServiceType; # DWORD dwCurrentState; # DWORD dwControlsAccepted; # DWORD dwWin32ExitCode; # DWORD dwServiceSpecificExitCode; # DWORD dwCheckPoint; # DWORD dwWaitHint; # } SERVICE_STATUS, *LPSERVICE_STATUS; class SERVICE_STATUS < FFI::Struct layout( :dwServiceType, :dword, :dwCurrentState, :dword, :dwControlsAccepted, :dword, :dwWin32ExitCode, :dword, :dwServiceSpecificExitCode, :dword, :dwCheckPoint, :dword, :dwWaitHint, :dword, ) end # typedef struct _QUERY_SERVICE_CONFIGW { # DWORD dwServiceType; # DWORD dwStartType; # DWORD dwErrorControl; # LPWSTR lpBinaryPathName; # LPWSTR lpLoadOrderGroup; # DWORD dwTagId; # LPWSTR lpDependencies; # LPWSTR lpServiceStartName; # LPWSTR lpDisplayName; # } QUERY_SERVICE_CONFIGW, *LPQUERY_SERVICE_CONFIGW; class QUERY_SERVICE_CONFIGW < FFI::Struct layout( :dwServiceType, :dword, :dwStartType, :dword, :dwErrorControl, :dword, :lpBinaryPathName, :pointer, :lpLoadOrderGroup, :pointer, :dwTagId, :dword, :lpDependencies, :pointer, :lpServiceStartName, :pointer, :lpDisplayName, :pointer, ) end # Returns true if the service exists, false otherwise. # # @param [:string] service_name name of the service def exists?(service_name) open_service(service_name, SC_MANAGER_CONNECT, SERVICE_QUERY_STATUS) do |_| true end rescue Puppet::Util::Windows::Error => e return false if e.code == ERROR_SERVICE_DOES_NOT_EXIST raise e end module_function :exists? # Start a windows service # # @param [:string] service_name name of the service to start def start(service_name) Puppet.debug _("Starting the %{service_name} service") % { service_name: service_name } valid_initial_states = [ SERVICE_STOP_PENDING, SERVICE_STOPPED, SERVICE_START_PENDING ] transition_service_state(service_name, valid_initial_states, SERVICE_RUNNING) do |service| if StartServiceW(service, 0, FFI::Pointer::NULL) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error, _("Failed to start the service") end end Puppet.debug _("Successfully started the %{service_name} service") % { service_name: service_name } end module_function :start # Stop a windows service # # @param [:string] service_name name of the service to stop def stop(service_name) Puppet.debug _("Stopping the %{service_name} service") % { service_name: service_name } valid_initial_states = SERVICE_STATES.keys - [SERVICE_STOPPED] transition_service_state(service_name, valid_initial_states, SERVICE_STOPPED) do |service| send_service_control_signal(service, SERVICE_CONTROL_STOP) end Puppet.debug _("Successfully stopped the %{service_name} service") % { service_name: service_name } end module_function :stop # Resume a paused windows service # # @param [:string] service_name name of the service to resume def resume(service_name) Puppet.debug _("Resuming the %{service_name} service") % { service_name: service_name } valid_initial_states = [ SERVICE_PAUSE_PENDING, SERVICE_PAUSED, SERVICE_CONTINUE_PENDING ] transition_service_state(service_name, valid_initial_states, SERVICE_RUNNING) do |service| # The SERVICE_CONTROL_CONTINUE signal can only be sent when # the service is in the SERVICE_PAUSED state wait_on_pending_state(service, SERVICE_PAUSE_PENDING) send_service_control_signal(service, SERVICE_CONTROL_CONTINUE) end Puppet.debug _("Successfully resumed the %{service_name} service") % { service_name: service_name } end module_function :resume # Query the state of a service using QueryServiceStatusEx # # @param [:string] service_name name of the service to query # @return [string] the status of the service def service_state(service_name) state = nil open_service(service_name, SC_MANAGER_CONNECT, SERVICE_QUERY_STATUS) do |service| query_status(service) do |status| state = SERVICE_STATES[status[:dwCurrentState]] end end if state.nil? raise Puppet::Error.new(_("Unknown Service state '%{current_state}' for '%{service_name}'") % { current_state: state.to_s, service_name: service_name}) end state end module_function :service_state # Query the configuration of a service using QueryServiceConfigW # # @param [:string] service_name name of the service to query # @return [QUERY_SERVICE_CONFIGW.struct] the configuration of the service def service_start_type(service_name) start_type = nil open_service(service_name, SC_MANAGER_CONNECT, SERVICE_QUERY_CONFIG) do |service| query_config(service) do |config| start_type = SERVICE_START_TYPES[config[:dwStartType]] end end if start_type.nil? raise Puppet::Error.new(_("Unknown start type '%{start_type}' for '%{service_name}'") % { start_type: start_type.to_s, service_name: service_name}) end start_type end module_function :service_start_type # Change the startup mode of a windows service # # @param [string] service_name the name of the service to modify # @param [Int] startup_type a code corresponding to a start type for # windows service, see the "Service start type codes" section in the # Puppet::Util::Windows::Service file for the list of available codes def set_startup_mode(service_name, startup_type) startup_code = SERVICE_START_TYPES.key(startup_type) if startup_code.nil? raise Puppet::Error.new(_("Unknown start type %{start_type}") % {startup_type: startup_type.to_s}) end open_service(service_name, SC_MANAGER_CONNECT, SERVICE_CHANGE_CONFIG) do |service| # Currently the only thing puppet's API can really manage # in this list is dwStartType (the third param). Thus no # generic function was written to make use of all the params # since the API as-is couldn't use them anyway success = ChangeServiceConfigW( service, SERVICE_NO_CHANGE, # dwServiceType startup_code, # dwStartType SERVICE_NO_CHANGE, # dwErrorControl FFI::Pointer::NULL, # lpBinaryPathName FFI::Pointer::NULL, # lpLoadOrderGroup FFI::Pointer::NULL, # lpdwTagId FFI::Pointer::NULL, # lpDependencies FFI::Pointer::NULL, # lpServiceStartName FFI::Pointer::NULL, # lpPassword FFI::Pointer::NULL # lpDisplayName ) if success == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new(_("Failed to update service configuration")) end end end module_function :set_startup_mode # enumerate over all services in all states and return them as a hash # # @return [Hash] a hash containing services: # { 'service name' => { # 'display_name' => 'display name', # 'service_status_process' => SERVICE_STATUS_PROCESS struct # } # } def services services = {} open_scm(SC_MANAGER_ENUMERATE_SERVICE) do |scm| size_required = 0 services_returned = 0 FFI::MemoryPointer.new(:dword) do |bytes_pointer| FFI::MemoryPointer.new(:dword) do |svcs_ret_ptr| FFI::MemoryPointer.new(:dword) do |resume_ptr| resume_ptr.write_dword(0) # Fetch the bytes of memory required to be allocated # for QueryServiceConfigW to return succesfully. This # is done by sending NULL and 0 for the pointer and size # respectively, letting the command fail, then reading the # value of pcbBytesNeeded # # return value will be false from this call, since it's designed # to fail. Just ignore it EnumServicesStatusExW( scm, :SC_ENUM_PROCESS_INFO, ALL_SERVICE_TYPES, SERVICE_STATE_ALL, FFI::Pointer::NULL, 0, bytes_pointer, svcs_ret_ptr, resume_ptr, FFI::Pointer::NULL ) size_required = bytes_pointer.read_dword FFI::MemoryPointer.new(size_required) do |buffer_ptr| resume_ptr.write_dword(0) svcs_ret_ptr.write_dword(0) success = EnumServicesStatusExW( scm, :SC_ENUM_PROCESS_INFO, ALL_SERVICE_TYPES, SERVICE_STATE_ALL, buffer_ptr, buffer_ptr.size, bytes_pointer, svcs_ret_ptr, resume_ptr, FFI::Pointer::NULL ) if success == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new(_("Failed to fetch services")) end # Now that the buffer is populated with services # we pull the data from memory using pointer arithmetic: # the number of services returned by the function is # available to be read from svcs_ret_ptr, and we iterate # that many times moving the cursor pointer the length of # ENUM_SERVICE_STATUS_PROCESSW.size. This should iterate # over the buffer and extract each struct. services_returned = svcs_ret_ptr.read_dword cursor_ptr = FFI::Pointer.new(ENUM_SERVICE_STATUS_PROCESSW, buffer_ptr) 0.upto(services_returned - 1) do |index| service = ENUM_SERVICE_STATUS_PROCESSW.new(cursor_ptr[index]) services[service[:lpServiceName].read_arbitrary_wide_string_up_to(SERVICENAME_MAX)] = { :display_name => service[:lpDisplayName].read_arbitrary_wide_string_up_to(SERVICENAME_MAX), :service_status_process => service[:ServiceStatusProcess] } end end # buffer_ptr end # resume_ptr end # scvs_ret_ptr end # bytes_ptr end # open_scm services end module_function :services class << self # @api private # Opens a connection to the SCManager on windows then uses that # handle to create a handle to a specific service in windows # corresponding to service_name # # this function takes a block that executes within the context of # the open service handler, and will close the service and SCManager # handles once the block finishes # # @param [string] service_name the name of the service to open # @param [Integer] scm_access code corresponding to the access type requested for the scm # @param [Integer] service_access code corresponding to the access type requested for the service # @yieldparam [:handle] service the windows native handle used to access # the service # @return the result of the block def open_service(service_name, scm_access, service_access, &block) service = FFI::Pointer::NULL_HANDLE result = nil open_scm(scm_access) do |scm| service = OpenServiceW(scm, wide_string(service_name), service_access) raise Puppet::Util::Windows::Error.new(_("Failed to open a handle to the service")) if service == FFI::Pointer::NULL_HANDLE result = yield service end result ensure CloseServiceHandle(service) end private :open_service # @api private # # Opens a handle to the service control manager # # @param [Integer] scm_access code corresponding to the access type requested for the scm def open_scm(scm_access, &block) scm = OpenSCManagerW(FFI::Pointer::NULL, FFI::Pointer::NULL, scm_access) raise Puppet::Util::Windows::Error.new(_("Failed to open a handle to the service control manager")) if scm == FFI::Pointer::NULL_HANDLE yield scm ensure CloseServiceHandle(scm) end private :open_scm # @api private # Transition the service to the specified state. The block should perform # the actual transition. # # @param [String] service_name the name of the service to transition # @param [[Integer]] valid_initial_states an array of valid states that the service can transition from # @param [:Integer] final_state the state that the service will transition to def transition_service_state(service_name, valid_initial_states, final_state, &block) service_access = SERVICE_START | SERVICE_STOP | SERVICE_PAUSE_CONTINUE | SERVICE_QUERY_STATUS open_service(service_name, SC_MANAGER_CONNECT, service_access) do |service| query_status(service) do |status| initial_state = status[:dwCurrentState] # If the service is already in the final_state, then # no further work needs to be done if initial_state == final_state Puppet.debug _("The service is already in the %{final_state} state. No further work needs to be done.") % { final_state: SERVICE_STATES[final_state] } next end # Check that initial_state corresponds to a valid # initial state unless valid_initial_states.include?(initial_state) valid_initial_states_str = valid_initial_states.map do |state| SERVICE_STATES[state] end.join(", ") raise Puppet::Error, _("The service must be in one of the %{valid_initial_states} states to perform this transition. It is currently in the %{current_state} state.") % { valid_initial_states: valid_initial_states_str, current_state: SERVICE_STATES[initial_state] } end # Check if there's a pending transition to the final_state. If so, then wait for # that transition to finish. possible_pending_states = FINAL_STATES.keys.select do |pending_state| # SERVICE_RUNNING has two pending states, SERVICE_START_PENDING and # SERVICE_CONTINUE_PENDING. That is why we need the #select here FINAL_STATES[pending_state] == final_state end if possible_pending_states.include?(initial_state) Puppet.debug _("There is already a pending transition to the %{final_state} state for the %{service_name} service.") % { final_state: SERVICE_STATES[final_state], service_name: service_name } wait_on_pending_state(service, initial_state) next end # If we are in an unsafe pending state like SERVICE_START_PENDING # or SERVICE_STOP_PENDING, then we want to wait for that pending # transition to finish before transitioning the service state. # The reason we do this is because SERVICE_START_PENDING is when # the service thread is being created and initialized, while # SERVICE_STOP_PENDING is when the service thread is being cleaned # up and destroyed. Thus there is a chance that when the service is # in either of these states, its service thread may not yet be ready # to perform the state transition (it may not even exist). if UNSAFE_PENDING_STATES.include?(initial_state) Puppet.debug _("The service is in the %{pending_state} state, which is an unsafe pending state.") % { pending_state: SERVICE_STATES[initial_state] } wait_on_pending_state(service, initial_state) initial_state = FINAL_STATES[initial_state] end Puppet.debug _("Transitioning the %{service_name} service from %{initial_state} to %{final_state}") % { service_name: service_name, initial_state: SERVICE_STATES[initial_state], final_state: SERVICE_STATES[final_state] } yield service Puppet.debug _("Waiting for the transition to finish") wait_on_state_transition(service, initial_state, final_state) end end rescue => detail raise Puppet::Error, _("Failed to transition the %{service_name} service to the %{final_state} state. Detail: %{detail}") % { service_name: service_name, final_state: SERVICE_STATES[final_state], detail: detail }, detail.backtrace end private :transition_service_state # @api private # perform QueryServiceStatusEx on a windows service and return the # result # # @param [:handle] service handle of the service to query # @return [SERVICE_STATUS_PROCESS struct] the result of the query def query_status(service) size_required = nil status = nil # Fetch the bytes of memory required to be allocated # for QueryServiceConfigW to return succesfully. This # is done by sending NULL and 0 for the pointer and size # respectively, letting the command fail, then reading the # value of pcbBytesNeeded FFI::MemoryPointer.new(:lpword) do |bytes_pointer| # return value will be false from this call, since it's designed # to fail. Just ignore it QueryServiceStatusEx( service, :SC_STATUS_PROCESS_INFO, FFI::Pointer::NULL, 0, bytes_pointer ) size_required = bytes_pointer.read_dword FFI::MemoryPointer.new(size_required) do |ssp_ptr| status = SERVICE_STATUS_PROCESS.new(ssp_ptr) success = QueryServiceStatusEx( service, :SC_STATUS_PROCESS_INFO, ssp_ptr, size_required, bytes_pointer ) if success == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new(_("Service query failed")) end yield status end end end private :query_status # @api private # perform QueryServiceConfigW on a windows service and return the # result # # @param [:handle] service handle of the service to query # @return [QUERY_SERVICE_CONFIGW struct] the result of the query def query_config(service, &block) config = nil size_required = nil # Fetch the bytes of memory required to be allocated # for QueryServiceConfigW to return succesfully. This # is done by sending NULL and 0 for the pointer and size # respectively, letting the command fail, then reading the # value of pcbBytesNeeded FFI::MemoryPointer.new(:lpword) do |bytes_pointer| # return value will be false from this call, since it's designed # to fail. Just ignore it QueryServiceConfigW(service, FFI::Pointer::NULL, 0, bytes_pointer) size_required = bytes_pointer.read_dword FFI::MemoryPointer.new(size_required) do |ssp_ptr| config = QUERY_SERVICE_CONFIGW.new(ssp_ptr) success = QueryServiceConfigW( service, ssp_ptr, size_required, bytes_pointer ) if success == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error.new(_("Service query failed")) end yield config end end end private :query_config # @api private # Sends a service control signal to a service # # @param [:handle] service handle to the service # @param [Integer] signal the service control signal to send def send_service_control_signal(service, signal) FFI::MemoryPointer.new(SERVICE_STATUS.size) do |status_ptr| status = SERVICE_STATUS.new(status_ptr) if ControlService(service, signal, status) == FFI::WIN32_FALSE raise Puppet::Util::Windows::Error, _("Failed to send the %{control_signal} signal to the service. Its current state is %{current_state}. Reason for failure:") % { control_signal: SERVICE_CONTROL_SIGNALS[signal], current_state: SERVICE_STATES[status[:dwCurrentState]] } end end end # @api private # Waits for a service to transition from one state to # another state. # # @param [:handle] service handle to the service to wait on # @param [Integer] initial_state the state that the service is transitioning from. # @param [Integer] final_state the state that the service is transitioning to def wait_on_state_transition(service, initial_state, final_state) # Get the pending state for this transition. Note that SERVICE_RUNNING # has two possible pending states, which is why we need this logic. if final_state != SERVICE_RUNNING pending_state = FINAL_STATES.key(final_state) elsif initial_state == SERVICE_STOPPED # SERVICE_STOPPED => SERVICE_RUNNING pending_state = SERVICE_START_PENDING else # SERVICE_PAUSED => SERVICE_RUNNING pending_state = SERVICE_CONTINUE_PENDING end # Wait for the transition to finish state = nil elapsed_time = 0 while elapsed_time <= DEFAULT_TIMEOUT query_status(service) do |status| state = status[:dwCurrentState] return if state == final_state if state == pending_state Puppet.debug _("The service transitioned to the %{pending_state} state.") % { pending_state: SERVICE_STATES[pending_state] } wait_on_pending_state(service, pending_state) return end sleep(1) elapsed_time += 1 end end # Timed out while waiting for the transition to finish. Raise an error # We can still use the state variable read from the FFI struct because # FFI creates new Integer objects during an assignment of an integer value # stored in an FFI struct. We verified that the '=' operater is safe # from the freed memory since the new ruby object created during the # assignment will remain in ruby memory and remain immutable and constant. raise Puppet::Error, _("Timed out while waiting for the service to transition from %{initial_state} to %{final_state} OR from %{initial_state} to %{pending_state} to %{final_state}. The service's current state is %{current_state}.") % { initial_state: SERVICE_STATES[initial_state], final_state: SERVICE_STATES[final_state], pending_state: SERVICE_STATES[pending_state], current_state: SERVICE_STATES[state] } end private :wait_on_state_transition # @api private # Waits for a service to finish transitioning from # a pending state. The service must be in the pending state # before invoking this routine. # # @param [:handle] service handle to the service to wait on # @param [Integer] pending_state the pending state def wait_on_pending_state(service, pending_state) final_state = FINAL_STATES[pending_state] Puppet.debug _("Waiting for the pending transition to the %{final_state} state to finish.") % { final_state: SERVICE_STATES[final_state] } elapsed_time = 0 last_checkpoint = -1 loop do query_status(service) do |status| state = status[:dwCurrentState] checkpoint = status[:dwCheckPoint] wait_hint = status[:dwWaitHint] # Check if our service has finished transitioning to # the final_state OR if an unexpected transition # has occurred return if state == final_state unless state == pending_state raise Puppet::Error, _("Unexpected transition to the %{current_state} state while waiting for the pending transition from %{pending_state} to %{final_state} to finish.") % { current_state: SERVICE_STATES[state], pending_state: SERVICE_STATES[pending_state], final_state: SERVICE_STATES[final_state] } end # Check if any progress has been made since our last sleep # using the dwCheckPoint. If no progress has been made then # check if we've timed out, and raise an error if so if checkpoint > last_checkpoint elapsed_time = 0 last_checkpoint = checkpoint else wait_hint_in_seconds = milliseconds_to_seconds(wait_hint) timeout = wait_hint_in_seconds < DEFAULT_TIMEOUT ? DEFAULT_TIMEOUT : wait_hint_in_seconds if elapsed_time >= timeout raise Puppet::Error, _("Timed out while waiting for the pending transition from %{pending_state} to %{final_state} to finish. The current state is %{current_state}.") % { pending_state: SERVICE_STATES[pending_state], final_state: SERVICE_STATES[final_state], current_state: SERVICE_STATES[state] } end end wait_time = wait_hint_to_wait_time(wait_hint) # Wait a bit before rechecking the service's state sleep(wait_time) elapsed_time += wait_time end end end private :wait_on_pending_state # @api private # # create a usable wait time to wait between querying the service. # # @param [Integer] wait_hint the wait hint of a service in milliseconds # @return [Integer] the time to wait in seconds between querying the service def wait_hint_to_wait_time(wait_hint) # Wait 1/10th the wait_hint, but no less than 1 and # no more than 10 seconds wait_time = milliseconds_to_seconds(wait_hint) / 10; wait_time = 1 if wait_time < 1 wait_time = 10 if wait_time > 10 wait_time end private :wait_hint_to_wait_time # @api private # # process the wait hint listed by a service to something # usable by ruby sleep # # @param [Integer] wait_hint the wait hint of a service in milliseconds # @return [Integer] wait_hint in seconds def milliseconds_to_seconds(wait_hint) wait_hint / 1000; end private :milliseconds_to_seconds end # https://docs.microsoft.com/en-us/windows/desktop/api/Winsvc/nf-winsvc-openscmanagerw # SC_HANDLE OpenSCManagerW( # LPCWSTR lpMachineName, # LPCWSTR lpDatabaseName, # DWORD dwDesiredAccess # ); ffi_lib :advapi32 attach_function_private :OpenSCManagerW, [:lpcwstr, :lpcwstr, :dword], :handle # https://docs.microsoft.com/en-us/windows/desktop/api/Winsvc/nf-winsvc-openservicew # SC_HANDLE OpenServiceW( # SC_HANDLE hSCManager, # LPCWSTR lpServiceName, # DWORD dwDesiredAccess # ); ffi_lib :advapi32 attach_function_private :OpenServiceW, [:handle, :lpcwstr, :dword], :handle # https://docs.microsoft.com/en-us/windows/desktop/api/Winsvc/nf-winsvc-closeservicehandle # BOOL CloseServiceHandle( # SC_HANDLE hSCObject # ); ffi_lib :advapi32 attach_function_private :CloseServiceHandle, [:handle], :win32_bool # https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/nf-winsvc-queryservicestatusex # BOOL QueryServiceStatusEx( # SC_HANDLE hService, # SC_STATUS_TYPE InfoLevel, # LPBYTE lpBuffer, # DWORD cbBufSize, # LPDWORD pcbBytesNeeded # ); SC_STATUS_TYPE = enum( :SC_STATUS_PROCESS_INFO, 0, ) ffi_lib :advapi32 attach_function_private :QueryServiceStatusEx, [:handle, SC_STATUS_TYPE, :lpbyte, :dword, :lpdword], :win32_bool # https://docs.microsoft.com/en-us/windows/desktop/api/Winsvc/nf-winsvc-queryserviceconfigw # BOOL QueryServiceConfigW( # SC_HANDLE hService, # LPQUERY_SERVICE_CONFIGW lpServiceConfig, # DWORD cbBufSize, # LPDWORD pcbBytesNeeded # ); ffi_lib :advapi32 attach_function_private :QueryServiceConfigW, [:handle, :lpbyte, :dword, :lpdword], :win32_bool # https://docs.microsoft.com/en-us/windows/desktop/api/Winsvc/nf-winsvc-startservicew # BOOL StartServiceW( # SC_HANDLE hService, # DWORD dwNumServiceArgs, # LPCWSTR *lpServiceArgVectors # ); ffi_lib :advapi32 attach_function_private :StartServiceW, [:handle, :dword, :pointer], :win32_bool # https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/nf-winsvc-controlservice # BOOL ControlService( # SC_HANDLE hService, # DWORD dwControl, # LPSERVICE_STATUS lpServiceStatus # ); ffi_lib :advapi32 attach_function_private :ControlService, [:handle, :dword, :pointer], :win32_bool # https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/nf-winsvc-changeserviceconfigw # BOOL ChangeServiceConfigW( # SC_HANDLE hService, # DWORD dwServiceType, # DWORD dwStartType, # DWORD dwErrorControl, # LPCWSTR lpBinaryPathName, # LPCWSTR lpLoadOrderGroup, # LPDWORD lpdwTagId, # LPCWSTR lpDependencies, # LPCWSTR lpServiceStartName, # LPCWSTR lpPassword, # LPCWSTR lpDisplayName # ); ffi_lib :advapi32 attach_function_private :ChangeServiceConfigW, [ :handle, :dword, :dword, :dword, :lpcwstr, :lpcwstr, :lpdword, :lpcwstr, :lpcwstr, :lpcwstr, :lpcwstr ], :win32_bool # https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/nf-winsvc-enumservicesstatusexw # BOOL EnumServicesStatusExW( # SC_HANDLE hSCManager, # SC_ENUM_TYPE InfoLevel, # DWORD dwServiceType, # DWORD dwServiceState, # LPBYTE lpServices, # DWORD cbBufSize, # LPDWORD pcbBytesNeeded, # LPDWORD lpServicesReturned, # LPDWORD lpResumeHandle, # LPCWSTR pszGroupName # ); SC_ENUM_TYPE = enum( :SC_ENUM_PROCESS_INFO, 0, ) ffi_lib :advapi32 attach_function_private :EnumServicesStatusExW, [ :handle, SC_ENUM_TYPE, :dword, :dword, :lpbyte, :dword, :lpdword, :lpdword, :lpdword, :lpcwstr ], :win32_bool end end puppet-5.5.10/lib/puppet/util/windows/taskscheduler.rb0000644005276200011600000012763713417161722022753 0ustar jenkinsjenkinsrequire 'puppet/util/windows' # The Win32 module serves as a namespace only module Win32 # The TaskScheduler class encapsulates taskscheduler settings and behavior class TaskScheduler include Puppet::Util::Windows::String require 'ffi' extend FFI::Library # The error class raised if any task scheduler specific calls fail. class Error < Puppet::Util::Windows::Error; end class << self attr_accessor :com_initialized end # :stopdoc: TASK_TIME_TRIGGER_ONCE = :TASK_TIME_TRIGGER_ONCE TASK_TIME_TRIGGER_DAILY = :TASK_TIME_TRIGGER_DAILY TASK_TIME_TRIGGER_WEEKLY = :TASK_TIME_TRIGGER_WEEKLY TASK_TIME_TRIGGER_MONTHLYDATE = :TASK_TIME_TRIGGER_MONTHLYDATE TASK_TIME_TRIGGER_MONTHLYDOW = :TASK_TIME_TRIGGER_MONTHLYDOW TASK_EVENT_TRIGGER_ON_IDLE = :TASK_EVENT_TRIGGER_ON_IDLE TASK_EVENT_TRIGGER_AT_SYSTEMSTART = :TASK_EVENT_TRIGGER_AT_SYSTEMSTART TASK_EVENT_TRIGGER_AT_LOGON = :TASK_EVENT_TRIGGER_AT_LOGON TASK_SUNDAY = 0x1 TASK_MONDAY = 0x2 TASK_TUESDAY = 0x4 TASK_WEDNESDAY = 0x8 TASK_THURSDAY = 0x10 TASK_FRIDAY = 0x20 TASK_SATURDAY = 0x40 TASK_FIRST_WEEK = 1 TASK_SECOND_WEEK = 2 TASK_THIRD_WEEK = 3 TASK_FOURTH_WEEK = 4 TASK_LAST_WEEK = 5 TASK_JANUARY = 0x1 TASK_FEBRUARY = 0x2 TASK_MARCH = 0x4 TASK_APRIL = 0x8 TASK_MAY = 0x10 TASK_JUNE = 0x20 TASK_JULY = 0x40 TASK_AUGUST = 0x80 TASK_SEPTEMBER = 0x100 TASK_OCTOBER = 0x200 TASK_NOVEMBER = 0x400 TASK_DECEMBER = 0x800 TASK_FLAG_INTERACTIVE = 0x1 TASK_FLAG_DELETE_WHEN_DONE = 0x2 TASK_FLAG_DISABLED = 0x4 TASK_FLAG_START_ONLY_IF_IDLE = 0x10 TASK_FLAG_KILL_ON_IDLE_END = 0x20 TASK_FLAG_DONT_START_IF_ON_BATTERIES = 0x40 TASK_FLAG_KILL_IF_GOING_ON_BATTERIES = 0x80 TASK_FLAG_RUN_ONLY_IF_DOCKED = 0x100 TASK_FLAG_HIDDEN = 0x200 TASK_FLAG_RUN_IF_CONNECTED_TO_INTERNET = 0x400 TASK_FLAG_RESTART_ON_IDLE_RESUME = 0x800 TASK_FLAG_SYSTEM_REQUIRED = 0x1000 TASK_FLAG_RUN_ONLY_IF_LOGGED_ON = 0x2000 TASK_TRIGGER_FLAG_HAS_END_DATE = 0x1 TASK_TRIGGER_FLAG_KILL_AT_DURATION_END = 0x2 TASK_TRIGGER_FLAG_DISABLED = 0x4 TASK_MAX_RUN_TIMES = 1440 TASKS_TO_RETRIEVE = 5 # COM CLSID_CTask = FFI::WIN32::GUID['148BD520-A2AB-11CE-B11F-00AA00530503'] IID_ITask = FFI::WIN32::GUID['148BD524-A2AB-11CE-B11F-00AA00530503'] IID_IPersistFile = FFI::WIN32::GUID['0000010b-0000-0000-C000-000000000046'] SCHED_S_TASK_READY = 0x00041300 SCHED_S_TASK_RUNNING = 0x00041301 SCHED_S_TASK_HAS_NOT_RUN = 0x00041303 SCHED_S_TASK_NOT_SCHEDULED = 0x00041305 # HRESULT error codes # https://blogs.msdn.com/b/eldar/archive/2007/04/03/a-lot-of-hresult-codes.aspx # in Ruby, an 0x8XXXXXXX style HRESULT can be resolved to 2s complement # by using "0x8XXXXXXX".to_i(16) - - 0x100000000 SCHED_E_ACCOUNT_INFORMATION_NOT_SET = -2147216625 # 0x8004130F SCHED_E_NO_SECURITY_SERVICES = -2147216622 # 0x80041312 # No mapping between account names and security IDs was done. ERROR_NONE_MAPPED = -2147023564 # 0x80070534 WIN32 Error CODE 1332 (0x534) # :startdoc: # Shorthand constants IDLE = Puppet::Util::Windows::Process::IDLE_PRIORITY_CLASS NORMAL = Puppet::Util::Windows::Process::NORMAL_PRIORITY_CLASS HIGH = Puppet::Util::Windows::Process::HIGH_PRIORITY_CLASS REALTIME = Puppet::Util::Windows::Process::REALTIME_PRIORITY_CLASS BELOW_NORMAL = Puppet::Util::Windows::Process::BELOW_NORMAL_PRIORITY_CLASS ABOVE_NORMAL = Puppet::Util::Windows::Process::ABOVE_NORMAL_PRIORITY_CLASS ONCE = TASK_TIME_TRIGGER_ONCE DAILY = TASK_TIME_TRIGGER_DAILY WEEKLY = TASK_TIME_TRIGGER_WEEKLY MONTHLYDATE = TASK_TIME_TRIGGER_MONTHLYDATE MONTHLYDOW = TASK_TIME_TRIGGER_MONTHLYDOW ON_IDLE = TASK_EVENT_TRIGGER_ON_IDLE AT_SYSTEMSTART = TASK_EVENT_TRIGGER_AT_SYSTEMSTART AT_LOGON = TASK_EVENT_TRIGGER_AT_LOGON FIRST_WEEK = TASK_FIRST_WEEK SECOND_WEEK = TASK_SECOND_WEEK THIRD_WEEK = TASK_THIRD_WEEK FOURTH_WEEK = TASK_FOURTH_WEEK LAST_WEEK = TASK_LAST_WEEK SUNDAY = TASK_SUNDAY MONDAY = TASK_MONDAY TUESDAY = TASK_TUESDAY WEDNESDAY = TASK_WEDNESDAY THURSDAY = TASK_THURSDAY FRIDAY = TASK_FRIDAY SATURDAY = TASK_SATURDAY JANUARY = TASK_JANUARY FEBRUARY = TASK_FEBRUARY MARCH = TASK_MARCH APRIL = TASK_APRIL MAY = TASK_MAY JUNE = TASK_JUNE JULY = TASK_JULY AUGUST = TASK_AUGUST SEPTEMBER = TASK_SEPTEMBER OCTOBER = TASK_OCTOBER NOVEMBER = TASK_NOVEMBER DECEMBER = TASK_DECEMBER INTERACTIVE = TASK_FLAG_INTERACTIVE DELETE_WHEN_DONE = TASK_FLAG_DELETE_WHEN_DONE DISABLED = TASK_FLAG_DISABLED START_ONLY_IF_IDLE = TASK_FLAG_START_ONLY_IF_IDLE KILL_ON_IDLE_END = TASK_FLAG_KILL_ON_IDLE_END DONT_START_IF_ON_BATTERIES = TASK_FLAG_DONT_START_IF_ON_BATTERIES KILL_IF_GOING_ON_BATTERIES = TASK_FLAG_KILL_IF_GOING_ON_BATTERIES RUN_ONLY_IF_DOCKED = TASK_FLAG_RUN_ONLY_IF_DOCKED HIDDEN = TASK_FLAG_HIDDEN RUN_IF_CONNECTED_TO_INTERNET = TASK_FLAG_RUN_IF_CONNECTED_TO_INTERNET RESTART_ON_IDLE_RESUME = TASK_FLAG_RESTART_ON_IDLE_RESUME SYSTEM_REQUIRED = TASK_FLAG_SYSTEM_REQUIRED RUN_ONLY_IF_LOGGED_ON = TASK_FLAG_RUN_ONLY_IF_LOGGED_ON FLAG_HAS_END_DATE = TASK_TRIGGER_FLAG_HAS_END_DATE FLAG_KILL_AT_DURATION_END = TASK_TRIGGER_FLAG_KILL_AT_DURATION_END FLAG_DISABLED = TASK_TRIGGER_FLAG_DISABLED MAX_RUN_TIMES = TASK_MAX_RUN_TIMES # unfortunately MSTask.h does not specify the limits for any settings # so these were determined with some experimentation # if values too large are written, its suspected there may be internal # limits may be exceeded, corrupting the job # used for max application name and path values MAX_PATH = 260 # UNLEN from lmcons.h is 256 # https://technet.microsoft.com/it-it/library/bb726984(en-us).aspx specifies 104 MAX_ACCOUNT_LENGTH = 256 # command line max length is limited to 8191, choose something high but still enough that we don't blow out CLI MAX_PARAMETERS_LENGTH = 4096 # in testing, this value could be set to a length of 99999, but saving / loading the task failed MAX_COMMENT_LENGTH = 8192 # Returns a new TaskScheduler object. If a work_item (and possibly the # the trigger) are passed as arguments then a new work item is created and # associated with that trigger, although you can still activate other tasks # with the same handle. # # This is really just a bit of convenience. Passing arguments to the # constructor is the same as calling TaskScheduler.new plus # TaskScheduler#new_work_item. # def initialize(work_item=nil, trigger=nil) @pITS = nil @pITask = nil if ! self.class.com_initialized Puppet::Util::Windows::COM.InitializeCom() self.class.com_initialized = true end @pITS = COM::TaskScheduler.new at_exit do begin @pITS.Release if @pITS && !@pITS.null? @pITS = nil rescue end end if work_item if trigger raise TypeError unless trigger.is_a?(Hash) new_work_item(work_item, trigger) end end end # Returns an array of scheduled task names. # def enum raise Error.new(_('No current task scheduler. ITaskScheduler is NULL.')) if @pITS.nil? array = [] @pITS.UseInstance(COM::EnumWorkItems, :Enum) do |pIEnum| FFI::MemoryPointer.new(:pointer) do |names_array_ptr_ptr| FFI::MemoryPointer.new(:win32_ulong) do |fetched_count_ptr| # awkward usage, if number requested is available, returns S_OK (0), or if less were returned returns S_FALSE (1) while (pIEnum.Next(TASKS_TO_RETRIEVE, names_array_ptr_ptr, fetched_count_ptr) >= Puppet::Util::Windows::COM::S_OK) count = fetched_count_ptr.read_win32_ulong break if count == 0 names_array_ptr_ptr.read_com_memory_pointer do |names_array_ptr| # iterate over the array of pointers name_ptr_ptr = FFI::Pointer.new(:pointer, names_array_ptr) for i in 0 ... count name_ptr_ptr[i].read_com_memory_pointer do |name_ptr| array << name_ptr.read_arbitrary_wide_string_up_to(MAX_PATH) end end end end end end end array end alias :tasks :enum # Activate the specified task. # def activate(task) raise Error.new(_('No current task scheduler. ITaskScheduler is NULL.')) if @pITS.nil? raise TypeError unless task.is_a?(String) FFI::MemoryPointer.new(:pointer) do |ptr| @pITS.Activate(wide_string(task), IID_ITask, ptr) reset_current_task @pITask = COM::Task.new(ptr.read_pointer) end @pITask end # Delete the specified task name. # def delete(task) raise Error.new(_('No current task scheduler. ITaskScheduler is NULL.')) if @pITS.nil? raise TypeError unless task.is_a?(String) @pITS.Delete(wide_string(task)) true end # Execute the current task. # def run raise Error.new(_('No currently active task. ITask is NULL.')) if @pITask.nil? @pITask.Run end # Saves the current task. Tasks must be saved before they can be activated. # The .job file itself is typically stored in the C:\WINDOWS\Tasks folder. # # If +file+ (an absolute path) is specified then the job is saved to that # file instead. A '.job' extension is recommended but not enforced. # def save(file = nil) raise Error.new(_('No currently active task. ITask is NULL.')) if @pITask.nil? raise Error.new(_('Account information must be set on the current task to save it properly.')) if !@account_information_set reset = true begin @pITask.QueryInstance(COM::PersistFile) do |pIPersistFile| wide_file = wide_string(file) pIPersistFile.Save(wide_file, 1) pIPersistFile.SaveCompleted(wide_file) end rescue reset = false ensure reset_current_task if reset end end # Terminate the current task. # def terminate raise Error.new(_('No currently active task. ITask is NULL.')) if @pITask.nil? @pITask.Terminate end # Set the host on which the various TaskScheduler methods will execute. # def machine=(host) raise Error.new(_('No current task scheduler. ITaskScheduler is NULL.')) if @pITS.nil? raise TypeError unless host.is_a?(String) @pITS.SetTargetComputer(wide_string(host)) host end alias :host= :machine= # Sets the +user+ and +password+ for the given task. If the user and # password are set properly then true is returned. # # In some cases the job may be created, but the account information was # bad. In this case the task is created but a warning is generated and # false is returned. # # Note that if intending to use SYSTEM, specify an empty user and nil password # # Calling task.set_account_information('SYSTEM', nil) will generally not # work, except for one special case where flags are also set like: # task.flags = Win32::TaskScheduler::TASK_FLAG_RUN_ONLY_IF_LOGGED_ON # # This must be done prior to the 1st save() call for the task to be # properly registered and visible through the MMC snap-in / schtasks.exe # def set_account_information(user, password) raise Error.new(_('No current task scheduler. ITaskScheduler is NULL.')) if @pITS.nil? raise Error.new(_('No currently active task. ITask is NULL.')) if @pITask.nil? bool = false begin if (user.nil? || user=="") && (password.nil? || password=="") @pITask.SetAccountInformation(wide_string(""), FFI::Pointer::NULL) else if user.length > MAX_ACCOUNT_LENGTH raise Error.new(_("User has exceeded maximum allowed length %{max}") % { max: MAX_ACCOUNT_LENGTH }) end user = wide_string(user) password = wide_string(password) @pITask.SetAccountInformation(user, password) end @account_information_set = true bool = true rescue Puppet::Util::Windows::Error => e raise e unless e.code == SCHED_E_ACCOUNT_INFORMATION_NOT_SET warn _('job created, but password was invalid') end bool end # Returns the user associated with the task or nil if no user has yet # been associated with the task. # def account_information raise Error.new(_('No current task scheduler. ITaskScheduler is NULL.')) if @pITS.nil? raise Error.new(_('No currently active task. ITask is NULL.')) if @pITask.nil? # default under certain failures user = nil begin FFI::MemoryPointer.new(:pointer) do |ptr| @pITask.GetAccountInformation(ptr) ptr.read_com_memory_pointer do |str_ptr| user = str_ptr.read_arbitrary_wide_string_up_to(MAX_ACCOUNT_LENGTH) if ! str_ptr.null? end end rescue Puppet::Util::Windows::Error => e raise e unless e.code == SCHED_E_ACCOUNT_INFORMATION_NOT_SET || e.code == SCHED_E_NO_SECURITY_SERVICES || e.code == ERROR_NONE_MAPPED end user end # Returns the name of the application associated with the task. # def application_name raise Error.new(_('No current task scheduler. ITaskScheduler is NULL.')) if @pITS.nil? raise Error.new(_('No currently active task. ITask is NULL.')) if @pITask.nil? app = nil FFI::MemoryPointer.new(:pointer) do |ptr| @pITask.GetApplicationName(ptr) ptr.read_com_memory_pointer do |str_ptr| app = str_ptr.read_arbitrary_wide_string_up_to(MAX_PATH) if ! str_ptr.null? end end app end # Sets the application name associated with the task. # def application_name=(app) raise Error.new(_('No current task scheduler. ITaskScheduler is NULL.')) if @pITS.nil? raise Error.new(_('No currently active task. ITask is NULL.')) if @pITask.nil? raise TypeError unless app.is_a?(String) # the application name is written to a .job file on disk, so is subject to path limitations if app.length > MAX_PATH raise Error.new(_("Application name has exceeded maximum allowed length %{max}") % { max: MAX_PATH }) end @pITask.SetApplicationName(wide_string(app)) app end # Returns the command line parameters for the task. # def parameters raise Error.new(_('No current task scheduler. ITaskScheduler is NULL.')) if @pITS.nil? raise Error.new(_('No currently active task. ITask is NULL.')) if @pITask.nil? param = nil FFI::MemoryPointer.new(:pointer) do |ptr| @pITask.GetParameters(ptr) ptr.read_com_memory_pointer do |str_ptr| param = str_ptr.read_arbitrary_wide_string_up_to(MAX_PARAMETERS_LENGTH) if ! str_ptr.null? end end param end # Sets the parameters for the task. These parameters are passed as command # line arguments to the application the task will run. To clear the command # line parameters set it to an empty string. # def parameters=(param) raise Error.new(_('No current task scheduler. ITaskScheduler is NULL.')) if @pITS.nil? raise Error.new(_('No currently active task. ITask is NULL.')) if @pITask.nil? raise TypeError unless param.is_a?(String) if param.length > MAX_PARAMETERS_LENGTH raise Error.new(_("Parameters has exceeded maximum allowed length %{max}") % { max: MAX_PARAMETERS_LENGTH }) end @pITask.SetParameters(wide_string(param)) param end # Returns the working directory for the task. # def working_directory raise Error.new(_('No current task scheduler. ITaskScheduler is NULL.')) if @pITS.nil? raise Error.new(_('No currently active task. ITask is NULL.')) if @pITask.nil? dir = nil FFI::MemoryPointer.new(:pointer) do |ptr| @pITask.GetWorkingDirectory(ptr) ptr.read_com_memory_pointer do |str_ptr| dir = str_ptr.read_arbitrary_wide_string_up_to(MAX_PATH) if ! str_ptr.null? end end dir end # Sets the working directory for the task. # def working_directory=(dir) raise Error.new(_('No current task scheduler. ITaskScheduler is NULL.')) if @pITS.nil? raise Error.new(_('No currently active task. ITask is NULL.')) if @pITask.nil? raise TypeError unless dir.is_a?(String) if dir.length > MAX_PATH raise Error.new(_("Working directory has exceeded maximum allowed length %{max}") % { max: MAX_PATH }) end @pITask.SetWorkingDirectory(wide_string(dir)) dir end # Returns the task's priority level. Possible values are 'idle', # 'normal', 'high', 'realtime', 'below_normal', 'above_normal', # and 'unknown'. # def priority raise Error.new(_('No current task scheduler. ITaskScheduler is NULL.')) if @pITS.nil? raise Error.new(_('No currently active task. ITask is NULL.')) if @pITask.nil? priority_name = '' FFI::MemoryPointer.new(:dword, 1) do |ptr| @pITask.GetPriority(ptr) pri = ptr.read_dword if (pri & IDLE) != 0 priority_name = 'idle' elsif (pri & NORMAL) != 0 priority_name = 'normal' elsif (pri & HIGH) != 0 priority_name = 'high' elsif (pri & REALTIME) != 0 priority_name = 'realtime' elsif (pri & BELOW_NORMAL) != 0 priority_name = 'below_normal' elsif (pri & ABOVE_NORMAL) != 0 priority_name = 'above_normal' else priority_name = 'unknown' end end priority_name end # Sets the priority of the task. The +priority+ should be a numeric # priority constant value. # def priority=(priority) raise Error.new(_('No current task scheduler. ITaskScheduler is NULL.')) if @pITS.nil? raise Error.new(_('No currently active task. ITask is NULL.')) if @pITask.nil? raise TypeError unless priority.is_a?(Numeric) @pITask.SetPriority(priority) priority end # Creates a new work item (scheduled job) with the given +trigger+. The # trigger variable is a hash of options that define when the scheduled # job should run. # def new_work_item(task, trigger) raise TypeError unless trigger.is_a?(Hash) raise Error.new(_('No current task scheduler. ITaskScheduler is NULL.')) if @pITS.nil? # I'm working around github issue #1 here. enum.each{ |name| if name.downcase == task.downcase + '.job' raise Error.new(_("task '%{task}' already exists") % { task: task }) end } FFI::MemoryPointer.new(:pointer) do |ptr| @pITS.NewWorkItem(wide_string(task), CLSID_CTask, IID_ITask, ptr) reset_current_task @pITask = COM::Task.new(ptr.read_pointer) FFI::MemoryPointer.new(:word, 1) do |trigger_index_ptr| # Without the 'enum.include?' check above the code segfaults here if the # task already exists. This should probably be handled properly instead # of simply avoiding the issue. @pITask.UseInstance(COM::TaskTrigger, :CreateTrigger, trigger_index_ptr) do |pITaskTrigger| populate_trigger(pITaskTrigger, trigger) end end end # preload task with the SYSTEM account # empty string '' means 'SYSTEM' per MSDN, so default it # given an account is necessary for creation of a task # note that a user may set SYSTEM explicitly, but that has problems # https://msdn.microsoft.com/en-us/library/windows/desktop/aa381276(v=vs.85).aspx set_account_information('', nil) @pITask end alias :new_task :new_work_item # Returns the number of triggers associated with the active task. # def trigger_count raise Error.new(_('No current task scheduler. ITaskScheduler is NULL.')) if @pITS.nil? raise Error.new(_('No currently active task. ITask is NULL.')) if @pITask.nil? count = 0 FFI::MemoryPointer.new(:word, 1) do |ptr| @pITask.GetTriggerCount(ptr) count = ptr.read_word end count end # Deletes the trigger at the specified index. # def delete_trigger(index) raise Error.new(_('No current task scheduler. ITaskScheduler is NULL.')) if @pITS.nil? raise Error.new(_('No currently active task. ITask is NULL.')) if @pITask.nil? @pITask.DeleteTrigger(index) index end # Returns a hash that describes the trigger at the given index for the # current task. # def trigger(index) raise Error.new(_('No current task scheduler. ITaskScheduler is NULL.')) if @pITS.nil? raise Error.new(_('No currently active task. ITask is NULL.')) if @pITask.nil? trigger = {} @pITask.UseInstance(COM::TaskTrigger, :GetTrigger, index) do |pITaskTrigger| FFI::MemoryPointer.new(COM::TASK_TRIGGER.size) do |task_trigger_ptr| pITaskTrigger.GetTrigger(task_trigger_ptr) trigger = populate_hash_from_trigger(COM::TASK_TRIGGER.new(task_trigger_ptr)) end end trigger end # Sets the trigger for the currently active task. # def trigger=(trigger) raise Error.new(_('No current task scheduler. ITaskScheduler is NULL.')) if @pITS.nil? raise Error.new(_('No currently active task. ITask is NULL.')) if @pITask.nil? raise TypeError unless trigger.is_a?(Hash) FFI::MemoryPointer.new(:word, 1) do |trigger_index_ptr| # Without the 'enum.include?' check above the code segfaults here if the # task already exists. This should probably be handled properly instead # of simply avoiding the issue. @pITask.UseInstance(COM::TaskTrigger, :CreateTrigger, trigger_index_ptr) do |pITaskTrigger| populate_trigger(pITaskTrigger, trigger) end end trigger end # Adds a trigger at the specified index. # def add_trigger(index, trigger) raise Error.new(_('No current task scheduler. ITaskScheduler is NULL.')) if @pITS.nil? raise Error.new(_('No currently active task. ITask is NULL.')) if @pITask.nil? raise TypeError unless trigger.is_a?(Hash) @pITask.UseInstance(COM::TaskTrigger, :GetTrigger, index) do |pITaskTrigger| populate_trigger(pITaskTrigger, trigger) end end # Returns the flags (integer) that modify the behavior of the work item. You # must OR the return value to determine the flags yourself. # def flags raise Error.new(_('No currently active task. ITask is NULL.')) if @pITask.nil? flags = 0 FFI::MemoryPointer.new(:dword, 1) do |ptr| @pITask.GetFlags(ptr) flags = ptr.read_dword end flags end # Sets an OR'd value of flags that modify the behavior of the work item. # def flags=(flags) raise Error.new(_('No current task scheduler. ITaskScheduler is NULL.')) if @pITS.nil? raise Error.new(_('No currently active task. ITask is NULL.')) if @pITask.nil? @pITask.SetFlags(flags) flags end # Returns the status of the currently active task. Possible values are # 'ready', 'running', 'not scheduled' or 'unknown'. # def status raise Error.new(_('No currently active task. ITask is NULL.')) if @pITask.nil? st = nil FFI::MemoryPointer.new(:hresult, 1) do |ptr| @pITask.GetStatus(ptr) st = ptr.read_hresult end case st when SCHED_S_TASK_READY status = 'ready' when SCHED_S_TASK_RUNNING status = 'running' when SCHED_S_TASK_NOT_SCHEDULED status = 'not scheduled' else status = 'unknown' end status end # Returns the exit code from the last scheduled run. # def exit_code raise Error.new(_('No currently active task. ITask is NULL.')) if @pITask.nil? status = 0 begin FFI::MemoryPointer.new(:dword, 1) do |ptr| @pITask.GetExitCode(ptr) status = ptr.read_dword end rescue Puppet::Util::Windows::Error => e raise e unless e.code == SCHED_S_TASK_HAS_NOT_RUN end status end # Returns the comment associated with the task, if any. # def comment raise Error.new(_('No currently active task. ITask is NULL.')) if @pITask.nil? comment = nil FFI::MemoryPointer.new(:pointer) do |ptr| @pITask.GetComment(ptr) ptr.read_com_memory_pointer do |str_ptr| comment = str_ptr.read_arbitrary_wide_string_up_to(MAX_COMMENT_LENGTH) if ! str_ptr.null? end end comment end # Sets the comment for the task. # def comment=(comment) raise Error.new(_('No currently active task. ITask is NULL.')) if @pITask.nil? raise TypeError unless comment.is_a?(String) if comment.length > MAX_COMMENT_LENGTH raise Error.new(_("Comment has exceeded maximum allowed length %{max}") % { max: MAX_COMMENT_LENGTH }) end @pITask.SetComment(wide_string(comment)) comment end # Returns the name of the user who created the task. # def creator raise Error.new(_('No currently active task. ITask is NULL.')) if @pITask.nil? creator = nil FFI::MemoryPointer.new(:pointer) do |ptr| @pITask.GetCreator(ptr) ptr.read_com_memory_pointer do |str_ptr| creator = str_ptr.read_arbitrary_wide_string_up_to(MAX_ACCOUNT_LENGTH) if ! str_ptr.null? end end creator end # Sets the creator for the task. # def creator=(creator) raise Error.new(_('No currently active task. ITask is NULL.')) if @pITask.nil? raise TypeError unless creator.is_a?(String) if creator.length > MAX_ACCOUNT_LENGTH raise Error.new(_("Creator has exceeded maximum allowed length %{max}") % { max: MAX_ACCOUNT_LENGTH }) end @pITask.SetCreator(wide_string(creator)) creator end # Returns a Time object that indicates the next time the task will run. # def next_run_time raise Error.new(_('No currently active task. ITask is NULL.')) if @pITask.nil? time = nil FFI::MemoryPointer.new(WIN32::SYSTEMTIME.size) do |ptr| @pITask.GetNextRunTime(ptr) time = WIN32::SYSTEMTIME.new(ptr).to_local_time end time end # Returns a Time object indicating the most recent time the task ran or # nil if the task has never run. # def most_recent_run_time raise Error.new(_('No currently active task. ITask is NULL.')) if @pITask.nil? time = nil begin FFI::MemoryPointer.new(WIN32::SYSTEMTIME.size) do |ptr| @pITask.GetMostRecentRunTime(ptr) time = WIN32::SYSTEMTIME.new(ptr).to_local_time end rescue Puppet::Util::Windows::Error => e raise e unless e.code == SCHED_S_TASK_HAS_NOT_RUN end time end # Returns the maximum length of time, in milliseconds, that the task # will run before terminating. # def max_run_time raise Error.new(_('No currently active task. ITask is NULL.')) if @pITask.nil? max_run_time = nil FFI::MemoryPointer.new(:dword, 1) do |ptr| @pITask.GetMaxRunTime(ptr) max_run_time = ptr.read_dword end max_run_time end # Sets the maximum length of time, in milliseconds, that the task can run # before terminating. Returns the value you specified if successful. # def max_run_time=(max_run_time) raise Error.new(_('No currently active task. ITask is NULL.')) if @pITask.nil? raise TypeError unless max_run_time.is_a?(Numeric) @pITask.SetMaxRunTime(max_run_time) max_run_time end # Returns whether or not the scheduled task exists. def exists?(job_name) # task name comparison is case insensitive tasks.any? { |name| name.casecmp(job_name + '.job') == 0 } end private # :stopdoc: # Used for the new_work_item method ValidTriggerKeys = [ 'end_day', 'end_month', 'end_year', 'flags', 'minutes_duration', 'minutes_interval', 'random_minutes_interval', 'start_day', 'start_hour', 'start_minute', 'start_month', 'start_year', 'trigger_type', 'type' ] ValidTypeKeys = [ 'days_interval', 'weeks_interval', 'days_of_week', 'months', 'days', 'weeks' ] # Private method that validates keys, and converts all keys to lowercase # strings. # def transform_and_validate(hash) new_hash = {} hash.each{ |key, value| key = key.to_s.downcase if key == 'type' new_type_hash = {} raise ArgumentError unless value.is_a?(Hash) value.each{ |subkey, subvalue| subkey = subkey.to_s.downcase if ValidTypeKeys.include?(subkey) new_type_hash[subkey] = subvalue else raise ArgumentError, _("Invalid type key '%{key}'") % { key: subkey } end } new_hash[key] = new_type_hash else if ValidTriggerKeys.include?(key) new_hash[key] = value else raise ArgumentError, _("Invalid key '%{key}'") % { key: key } end end } new_hash end def reset_current_task # Ensure that COM reference is decremented properly @pITask.Release if @pITask && ! @pITask.null? @pITask = nil @account_information_set = false end def populate_trigger(task_trigger, trigger) raise TypeError unless task_trigger.is_a?(COM::TaskTrigger) trigger = transform_and_validate(trigger) FFI::MemoryPointer.new(COM::TASK_TRIGGER.size) do |trigger_ptr| FFI::MemoryPointer.new(COM::TRIGGER_TYPE_UNION.size) do |trigger_type_union_ptr| trigger_type_union = COM::TRIGGER_TYPE_UNION.new(trigger_type_union_ptr) tmp = trigger['type'].is_a?(Hash) ? trigger['type'] : nil case trigger['trigger_type'] when :TASK_TIME_TRIGGER_DAILY if tmp && tmp['days_interval'] trigger_type_union[:Daily][:DaysInterval] = tmp['days_interval'] end when :TASK_TIME_TRIGGER_WEEKLY if tmp && tmp['weeks_interval'] && tmp['days_of_week'] trigger_type_union[:Weekly][:WeeksInterval] = tmp['weeks_interval'] trigger_type_union[:Weekly][:rgfDaysOfTheWeek] = tmp['days_of_week'] end when :TASK_TIME_TRIGGER_MONTHLYDATE if tmp && tmp['months'] && tmp['days'] trigger_type_union[:MonthlyDate][:rgfDays] = tmp['days'] trigger_type_union[:MonthlyDate][:rgfMonths] = tmp['months'] end when :TASK_TIME_TRIGGER_MONTHLYDOW if tmp && tmp['weeks'] && tmp['days_of_week'] && tmp['months'] trigger_type_union[:MonthlyDOW][:wWhichWeek] = tmp['weeks'] trigger_type_union[:MonthlyDOW][:rgfDaysOfTheWeek] = tmp['days_of_week'] trigger_type_union[:MonthlyDOW][:rgfMonths] = tmp['months'] end when :TASK_TIME_TRIGGER_ONCE # Do nothing. The Type member of the TASK_TRIGGER struct is ignored. else raise Error.new(_("Unknown trigger type %{type}") % { type: trigger['trigger_type'] }) end trigger_struct = COM::TASK_TRIGGER.new(trigger_ptr) trigger_struct[:cbTriggerSize] = COM::TASK_TRIGGER.size now = Time.now trigger_struct[:wBeginYear] = trigger['start_year'] || now.year trigger_struct[:wBeginMonth] = trigger['start_month'] || now.month trigger_struct[:wBeginDay] = trigger['start_day'] || now.day trigger_struct[:wEndYear] = trigger['end_year'] || 0 trigger_struct[:wEndMonth] = trigger['end_month'] || 0 trigger_struct[:wEndDay] = trigger['end_day'] || 0 trigger_struct[:wStartHour] = trigger['start_hour'] || 0 trigger_struct[:wStartMinute] = trigger['start_minute'] || 0 trigger_struct[:MinutesDuration] = trigger['minutes_duration'] || 0 trigger_struct[:MinutesInterval] = trigger['minutes_interval'] || 0 trigger_struct[:rgFlags] = trigger['flags'] || 0 trigger_struct[:TriggerType] = trigger['trigger_type'] || :TASK_TIME_TRIGGER_ONCE trigger_struct[:Type] = trigger_type_union trigger_struct[:wRandomMinutesInterval] = trigger['random_minutes_interval'] || 0 task_trigger.SetTrigger(trigger_struct) end end end def populate_hash_from_trigger(task_trigger) raise TypeError unless task_trigger.is_a?(COM::TASK_TRIGGER) trigger = { 'start_year' => task_trigger[:wBeginYear], 'start_month' => task_trigger[:wBeginMonth], 'start_day' => task_trigger[:wBeginDay], 'end_year' => task_trigger[:wEndYear], 'end_month' => task_trigger[:wEndMonth], 'end_day' => task_trigger[:wEndDay], 'start_hour' => task_trigger[:wStartHour], 'start_minute' => task_trigger[:wStartMinute], 'minutes_duration' => task_trigger[:MinutesDuration], 'minutes_interval' => task_trigger[:MinutesInterval], 'flags' => task_trigger[:rgFlags], 'trigger_type' => task_trigger[:TriggerType], 'random_minutes_interval' => task_trigger[:wRandomMinutesInterval] } case task_trigger[:TriggerType] when :TASK_TIME_TRIGGER_DAILY trigger['type'] = { 'days_interval' => task_trigger[:Type][:Daily][:DaysInterval] } when :TASK_TIME_TRIGGER_WEEKLY trigger['type'] = { 'weeks_interval' => task_trigger[:Type][:Weekly][:WeeksInterval], 'days_of_week' => task_trigger[:Type][:Weekly][:rgfDaysOfTheWeek] } when :TASK_TIME_TRIGGER_MONTHLYDATE trigger['type'] = { 'days' => task_trigger[:Type][:MonthlyDate][:rgfDays], 'months' => task_trigger[:Type][:MonthlyDate][:rgfMonths] } when :TASK_TIME_TRIGGER_MONTHLYDOW trigger['type'] = { 'weeks' => task_trigger[:Type][:MonthlyDOW][:wWhichWeek], 'days_of_week' => task_trigger[:Type][:MonthlyDOW][:rgfDaysOfTheWeek], 'months' => task_trigger[:Type][:MonthlyDOW][:rgfMonths] } when :TASK_TIME_TRIGGER_ONCE trigger['type'] = { 'once' => nil } else raise Error.new(_("Unknown trigger type %{type}") % { type: task_trigger[:TriggerType] }) end trigger end module COM extend FFI::Library com = Puppet::Util::Windows::COM # https://msdn.microsoft.com/en-us/library/windows/desktop/aa381811(v=vs.85).aspx ITaskScheduler = com::Interface[com::IUnknown, FFI::WIN32::GUID['148BD527-A2AB-11CE-B11F-00AA00530503'], SetTargetComputer: [[:lpcwstr], :hresult], # LPWSTR * GetTargetComputer: [[:pointer], :hresult], # IEnumWorkItems ** Enum: [[:pointer], :hresult], # LPCWSTR, REFIID, IUnknown ** Activate: [[:lpcwstr, :pointer, :pointer], :hresult], Delete: [[:lpcwstr], :hresult], # LPCWSTR, REFCLSID, REFIID, IUnknown ** NewWorkItem: [[:lpcwstr, :pointer, :pointer, :pointer], :hresult], # LPCWSTR, IScheduledWorkItem * AddWorkItem: [[:lpcwstr, :pointer], :hresult], # LPCWSTR, REFIID IsOfType: [[:lpcwstr, :pointer], :hresult] ] TaskScheduler = com::Factory[ITaskScheduler, FFI::WIN32::GUID['148BD52A-A2AB-11CE-B11F-00AA00530503']] # https://msdn.microsoft.com/en-us/library/windows/desktop/aa380706(v=vs.85).aspx IEnumWorkItems = com::Interface[com::IUnknown, FFI::WIN32::GUID['148BD528-A2AB-11CE-B11F-00AA00530503'], # ULONG, LPWSTR **, ULONG * Next: [[:win32_ulong, :pointer, :pointer], :hresult], Skip: [[:win32_ulong], :hresult], Reset: [[], :hresult], # IEnumWorkItems ** ppEnumWorkItems Clone: [[:pointer], :hresult] ] EnumWorkItems = com::Instance[IEnumWorkItems] # https://msdn.microsoft.com/en-us/library/windows/desktop/aa381216(v=vs.85).aspx IScheduledWorkItem = com::Interface[com::IUnknown, FFI::WIN32::GUID['a6b952f0-a4b1-11d0-997d-00aa006887ec'], # WORD *, ITaskTrigger ** CreateTrigger: [[:pointer, :pointer], :hresult], DeleteTrigger: [[:word], :hresult], # WORD * GetTriggerCount: [[:pointer], :hresult], # WORD, ITaskTrigger ** GetTrigger: [[:word, :pointer], :hresult], # WORD, LPWSTR * GetTriggerString: [[:word, :pointer], :hresult], # LPSYSTEMTIME, LPSYSTEMTIME, WORD *, LPSYSTEMTIME * GetRunTimes: [[:pointer, :pointer, :pointer, :pointer], :hresult], # SYSTEMTIME * GetNextRunTime: [[:pointer], :hresult], SetIdleWait: [[:word, :word], :hresult], # WORD *, WORD * GetIdleWait: [[:pointer, :pointer], :hresult], Run: [[], :hresult], Terminate: [[], :hresult], EditWorkItem: [[:hwnd, :dword], :hresult], # SYSTEMTIME * GetMostRecentRunTime: [[:pointer], :hresult], # HRESULT * GetStatus: [[:pointer], :hresult], GetExitCode: [[:pdword], :hresult], SetComment: [[:lpcwstr], :hresult], # LPWSTR * GetComment: [[:pointer], :hresult], SetCreator: [[:lpcwstr], :hresult], # LPWSTR * GetCreator: [[:pointer], :hresult], # WORD, BYTE[] SetWorkItemData: [[:word, :buffer_in], :hresult], # WORD *, BYTE ** GetWorkItemData: [[:pointer, :pointer], :hresult], SetErrorRetryCount: [[:word], :hresult], # WORD * GetErrorRetryCount: [[:pointer], :hresult], SetErrorRetryInterval: [[:word], :hresult], # WORD * GetErrorRetryInterval: [[:pointer], :hresult], SetFlags: [[:dword], :hresult], # WORD * GetFlags: [[:pointer], :hresult], SetAccountInformation: [[:lpcwstr, :lpcwstr], :hresult], # LPWSTR * GetAccountInformation: [[:pointer], :hresult] ] # https://msdn.microsoft.com/en-us/library/windows/desktop/aa381311(v=vs.85).aspx ITask = com::Interface[IScheduledWorkItem, FFI::WIN32::GUID['148BD524-A2AB-11CE-B11F-00AA00530503'], SetApplicationName: [[:lpcwstr], :hresult], # LPWSTR * GetApplicationName: [[:pointer], :hresult], SetParameters: [[:lpcwstr], :hresult], # LPWSTR * GetParameters: [[:pointer], :hresult], SetWorkingDirectory: [[:lpcwstr], :hresult], # LPWSTR * GetWorkingDirectory: [[:pointer], :hresult], SetPriority: [[:dword], :hresult], # DWORD * GetPriority: [[:pointer], :hresult], SetTaskFlags: [[:dword], :hresult], # DWORD * GetTaskFlags: [[:pointer], :hresult], SetMaxRunTime: [[:dword], :hresult], # DWORD * GetMaxRunTime: [[:pointer], :hresult] ] Task = com::Instance[ITask] # https://msdn.microsoft.com/en-us/library/windows/desktop/ms688695(v=vs.85).aspx IPersist = com::Interface[com::IUnknown, FFI::WIN32::GUID['0000010c-0000-0000-c000-000000000046'], # CLSID * GetClassID: [[:pointer], :hresult] ] # https://msdn.microsoft.com/en-us/library/windows/desktop/ms687223(v=vs.85).aspx IPersistFile = com::Interface[IPersist, FFI::WIN32::GUID['0000010b-0000-0000-C000-000000000046'], IsDirty: [[], :hresult], Load: [[:lpcolestr, :dword], :hresult], Save: [[:lpcolestr, :win32_bool], :hresult], SaveCompleted: [[:lpcolestr], :hresult], # LPOLESTR * GetCurFile: [[:pointer], :hresult] ] PersistFile = com::Instance[IPersistFile] # https://msdn.microsoft.com/en-us/library/windows/desktop/aa381864(v=vs.85).aspx ITaskTrigger = com::Interface[com::IUnknown, FFI::WIN32::GUID['148BD52B-A2AB-11CE-B11F-00AA00530503'], SetTrigger: [[:pointer], :hresult], GetTrigger: [[:pointer], :hresult], GetTriggerString: [[:pointer], :hresult] ] TaskTrigger = com::Instance[ITaskTrigger] # https://msdn.microsoft.com/en-us/library/windows/desktop/aa383620(v=vs.85).aspx # The TASK_TRIGGER_TYPE field of the TASK_TRIGGER structure determines # which member of the TRIGGER_TYPE_UNION field to use. TASK_TRIGGER_TYPE = enum( :TASK_TIME_TRIGGER_ONCE, 0, # Ignore the Type field :TASK_TIME_TRIGGER_DAILY, 1, :TASK_TIME_TRIGGER_WEEKLY, 2, :TASK_TIME_TRIGGER_MONTHLYDATE, 3, :TASK_TIME_TRIGGER_MONTHLYDOW, 4, :TASK_EVENT_TRIGGER_ON_IDLE, 5, # Ignore the Type field :TASK_EVENT_TRIGGER_AT_SYSTEMSTART, 6, # Ignore the Type field :TASK_EVENT_TRIGGER_AT_LOGON, 7 # Ignore the Type field ) # https://msdn.microsoft.com/en-us/library/windows/desktop/aa446857(v=vs.85).aspx class DAILY < FFI::Struct layout :DaysInterval, :word end # https://msdn.microsoft.com/en-us/library/windows/desktop/aa384014(v=vs.85).aspx class WEEKLY < FFI::Struct layout :WeeksInterval, :word, :rgfDaysOfTheWeek, :word end # https://msdn.microsoft.com/en-us/library/windows/desktop/aa381918(v=vs.85).aspx class MONTHLYDATE < FFI::Struct layout :rgfDays, :dword, :rgfMonths, :word end # https://msdn.microsoft.com/en-us/library/windows/desktop/aa381918(v=vs.85).aspx class MONTHLYDOW < FFI::Struct layout :wWhichWeek, :word, :rgfDaysOfTheWeek, :word, :rgfMonths, :word end # https://msdn.microsoft.com/en-us/library/windows/desktop/aa384002(v=vs.85).aspx class TRIGGER_TYPE_UNION < FFI::Union layout :Daily, DAILY, :Weekly, WEEKLY, :MonthlyDate, MONTHLYDATE, :MonthlyDOW, MONTHLYDOW end # https://msdn.microsoft.com/en-us/library/windows/desktop/aa383618(v=vs.85).aspx class TASK_TRIGGER < FFI::Struct layout :cbTriggerSize, :word, # Structure size. :Reserved1, :word, # Reserved. Must be zero. :wBeginYear, :word, # Trigger beginning date year. :wBeginMonth, :word, # Trigger beginning date month. :wBeginDay, :word, # Trigger beginning date day. :wEndYear, :word, # Optional trigger ending date year. :wEndMonth, :word, # Optional trigger ending date month. :wEndDay, :word, # Optional trigger ending date day. :wStartHour, :word, # Run bracket start time hour. :wStartMinute, :word, # Run bracket start time minute. :MinutesDuration, :dword, # Duration of run bracket. :MinutesInterval, :dword, # Run bracket repetition interval. :rgFlags, :dword, # Trigger flags. :TriggerType, TASK_TRIGGER_TYPE, # Trigger type. :Type, TRIGGER_TYPE_UNION, # Trigger data. :Reserved2, :word, # Reserved. Must be zero. :wRandomMinutesInterval, :word # Maximum number of random minutes after start time end end end end puppet-5.5.10/lib/puppet/util/autoload.rb0000644005276200011600000001745213417161722020221 0ustar jenkinsjenkinsrequire 'pathname' require 'puppet/util/rubygems' require 'puppet/util/warnings' require 'puppet/util/methodhelper' require 'puppet/pops/adaptable' # An adapter that ties the module_directories cache to the environment where the modules are parsed. This # adapter ensures that the life-cycle of this cache doesn't exceed the life-cycle of the environment. # # @api private class Puppet::Util::ModuleDirectoriesAdapter < Puppet::Pops::Adaptable::Adapter attr_accessor :directories end # Autoload paths, either based on names or all at once. class Puppet::Util::Autoload include Puppet::Util::MethodHelper @loaded = {} class << self attr_accessor :loaded def gem_source @gem_source ||= Puppet::Util::RubyGems::Source.new end # Has a given path been loaded? This is used for testing whether a # changed file should be loaded or just ignored. This is only # used in network/client/master, when downloading plugins, to # see if a given plugin is currently loaded and thus should be # reloaded. def loaded?(path) path = cleanpath(path).chomp('.rb') loaded.include?(path) end # Save the fact that a given path has been loaded. This is so # we can load downloaded plugins if they've already been loaded # into memory. def mark_loaded(name, file) name = cleanpath(name).chomp('.rb') ruby_file = name + ".rb" $LOADED_FEATURES << ruby_file unless $LOADED_FEATURES.include?(ruby_file) loaded[name] = [file, File.mtime(file)] end def changed?(name) name = cleanpath(name).chomp('.rb') return true unless loaded.include?(name) file, old_mtime = loaded[name] environment = Puppet.lookup(:current_environment) return true unless file == get_file(name, environment) begin old_mtime.to_i != File.mtime(file).to_i rescue Errno::ENOENT true end end # Load a single plugin by name. We use 'load' here so we can reload a # given plugin. def load_file(name, env) file = get_file(name.to_s, env) return false unless file begin mark_loaded(name, file) Kernel.load file return true rescue SystemExit,NoMemoryError raise rescue Exception => detail message = _("Could not autoload %{name}: %{detail}") % { name: name, detail: detail } Puppet.log_exception(detail, message) raise Puppet::Error, message, detail.backtrace end end def loadall(path, env = nil) # Load every instance of everything we can find. files_to_load(path, env).each do |file| name = file.chomp(".rb") load_file(name, env) unless loaded?(name) end end def reload_changed loaded.keys.each { |file| load_file(file, nil) if changed?(file) } end # Get the correct file to load for a given path # returns nil if no file is found def get_file(name, env) name = name + '.rb' unless name =~ /\.rb$/ path = search_directories(env).find { |dir| Puppet::FileSystem.exist?(File.join(dir, name)) } path and File.join(path, name) end def files_to_load(path, env = nil) search_directories(env).map {|dir| files_in_dir(dir, path) }.flatten.uniq end def files_in_dir(dir, path) dir = Pathname.new(File.expand_path(dir)) Dir.glob(File.join(dir, path, "*.rb")).collect do |file| Pathname.new(file).relative_path_from(dir).to_s end end def module_directories(env) # This is a little bit of a hack. Basically, the autoloader is being # called indirectly during application bootstrapping when we do things # such as check "features". However, during bootstrapping, we haven't # yet parsed all of the command line parameters nor the config files, # and thus we don't yet know with certainty what the module path is. # This should be irrelevant during bootstrapping, because anything that # we are attempting to load during bootstrapping should be something # that we ship with puppet, and thus the module path is irrelevant. # # In the long term, I think the way that we want to handle this is to # have the autoloader ignore the module path in all cases where it is # not specifically requested (e.g., by a constructor param or # something)... because there are very few cases where we should # actually be loading code from the module path. However, until that # happens, we at least need a way to prevent the autoloader from # attempting to access the module path before it is initialized. For # now we are accomplishing that by calling the # "app_defaults_initialized?" method on the main puppet Settings object. # --cprice 2012-03-16 if Puppet.settings.app_defaults_initialized? env ||= Puppet.lookup(:environments).get(Puppet[:environment]) if env # if the app defaults have been initialized then it should be safe to access the module path setting. Puppet::Util::ModuleDirectoriesAdapter.adapt(env) do |a| a.directories ||= env.modulepath.collect do |dir| Dir.entries(dir).reject { |f| f =~ /^\./ }.collect { |f| File.join(dir, f, "lib") } end.flatten.find_all do |d| FileTest.directory?(d) end end.directories else [] end else # if we get here, the app defaults have not been initialized, so we basically use an empty module path. [] end end def libdirs() # See the comments in #module_directories above. Basically, we need to be careful not to try to access the # libdir before we know for sure that all of the settings have been initialized (e.g., during bootstrapping). if (Puppet.settings.app_defaults_initialized?) [Puppet[:libdir]] else [] end end def gem_directories gem_source.directories end def search_directories(env) [gem_directories, module_directories(env), libdirs(), $LOAD_PATH].flatten end # Normalize a path. This converts ALT_SEPARATOR to SEPARATOR on Windows # and eliminates unnecessary parts of a path. def cleanpath(path) # There are two cases here because cleanpath does not handle absolute # paths correctly on windows (c:\ and c:/ are treated as distinct) but # we don't want to convert relative paths to absolute if Puppet::Util.absolute_path?(path) File.expand_path(path) else Pathname.new(path).cleanpath.to_s end end end attr_accessor :object, :path def initialize(obj, path, options = {}) @path = path.to_s raise ArgumentError, _("Autoload paths cannot be fully qualified") if Puppet::Util.absolute_path?(@path) @object = obj set_options(options) end def load(name, env = nil) self.class.load_file(expand(name), env) end # Load all instances from a path of Autoload.search_directories matching the # relative path this Autoloader was initialized with. For example, if we # have created a Puppet::Util::Autoload for Puppet::Type::User with a path of # 'puppet/provider/user', the search_directories path will be searched for # all ruby files matching puppet/provider/user/*.rb and they will then be # loaded from the first directory in the search path providing them. So # earlier entries in the search path may shadow later entries. # # This uses require, rather than load, so that already-loaded files don't get # reloaded unnecessarily. def loadall(env = nil) self.class.loadall(@path, env) end def loaded?(name) self.class.loaded?(expand(name)) end def changed?(name) self.class.changed?(expand(name)) end def files_to_load self.class.files_to_load(@path) end def expand(name) ::File.join(@path, name.to_s) end end puppet-5.5.10/lib/puppet/util/character_encoding.rb0000644005276200011600000001114413417161722022203 0ustar jenkinsjenkins# A module to centralize heuristics/practices for managing character encoding in Puppet module Puppet::Util::CharacterEncoding class << self # Given a string, attempts to convert a copy of the string to UTF-8. Conversion uses # encode - the string's internal byte representation is modifed to UTF-8. # # This method is intended for situations where we generally trust that the # string's bytes are a faithful representation of the current encoding # associated with it, and can use it as a starting point for transcoding # (conversion) to UTF-8. # # @api public # @param [String] string a string to transcode # @return [String] copy of the original string, in UTF-8 if transcodable def convert_to_utf_8(string) original_encoding = string.encoding string_copy = string.dup begin if original_encoding == Encoding::UTF_8 if !string_copy.valid_encoding? Puppet.debug(_("%{value} is already labeled as UTF-8 but this encoding is invalid. It cannot be transcoded by Puppet.") % { value: string.dump }) end # String is already valid UTF-8 - noop return string_copy else # If the string comes to us as BINARY encoded, we don't know what it # started as. However, to encode! we need a starting place, and our # best guess is whatever the system currently is (default_external). # So set external_encoding to default_external before we try to # transcode to UTF-8. string_copy.force_encoding(Encoding.default_external) if original_encoding == Encoding::BINARY return string_copy.encode(Encoding::UTF_8) end rescue EncodingError => detail # Set the encoding on our copy back to its original if we modified it string_copy.force_encoding(original_encoding) if original_encoding == Encoding::BINARY # Catch both our own self-determined failure to transcode as well as any # error on ruby's part, ie Encoding::UndefinedConversionError on a # failure to encode!. Puppet.debug(_("%{error}: %{value} cannot be transcoded by Puppet.") % { error: detail.inspect, value: string.dump }) return string_copy end end # Given a string, tests if that string's bytes represent valid UTF-8, and if # so return a copy of the string with external encoding set to UTF-8. Does # not modify the byte representation of the string. If the string does not # represent valid UTF-8, does not set the external encoding. # # This method is intended for situations where we do not believe that the # encoding associated with a string is an accurate reflection of its actual # bytes, i.e., effectively when we believe Ruby is incorrect in its # assertion of the encoding of the string. # # @api public # @param [String] string to set external encoding (re-label) to utf-8 # @return [String] a copy of string with external encoding set to utf-8, or # a copy of the original string if override would result in invalid encoding. def override_encoding_to_utf_8(string) string_copy = string.dup original_encoding = string_copy.encoding return string_copy if original_encoding == Encoding::UTF_8 if string_copy.force_encoding(Encoding::UTF_8).valid_encoding? return string_copy else Puppet.debug(_("%{value} is not valid UTF-8 and result of overriding encoding would be invalid.") % { value: string.dump }) # Set copy back to its original encoding before returning return string_copy.force_encoding(original_encoding) end end REPLACEMENT_CHAR_MAP = { Encoding::UTF_8 => "\uFFFD", Encoding::UTF_16LE => "\xFD\xFF".force_encoding(Encoding::UTF_16LE), } # Given a string, return a copy of that string with any invalid byte # sequences in its current encoding replaced with the replacement character # "\uFFFD" (UTF-8) if the string is UTF-8 or UTF-16LE, or "?" otherwise. # @param string a string to remove invalid byte sequences from # @return a copy of string invalid byte sequences replaced by the unicode # replacement character or "?" character # @note does not modify encoding, but new string will have different bytes # from original. Only needed for ruby 1.9.3 support. def scrub(string) if string.respond_to?(:scrub) string.scrub else replacement_character = REPLACEMENT_CHAR_MAP[string.encoding] || '?' string.chars.map { |c| c.valid_encoding? ? c : replacement_character }.join end end end end puppet-5.5.10/lib/puppet/util/classgen.rb0000644005276200011600000001677513417161722020217 0ustar jenkinsjenkinsrequire 'puppet/util/methodhelper' module Puppet class ConstantAlreadyDefined < Error; end class SubclassAlreadyDefined < Error; end end # This is a utility module for generating classes. # @api public # module Puppet::Util::ClassGen include Puppet::Util::MethodHelper include Puppet::Util # Create a new class. # @param name [String] the name of the generated class # @param options [Hash] a hash of options # @option options [Array] :array if specified, the generated class is appended to this array # @option options [Hash<{String => Object}>] :attributes a hash that is applied to the generated class # by calling setter methods corresponding to this hash's keys/value pairs. This is done before the given # block is evaluated. # @option options [Proc] :block a block to evaluate in the context of the class (this block can be provided # this way, or as a normal yield block). # @option options [String] :constant (name with first letter capitalized) what to set the constant that references # the generated class to. # @option options [Hash] :hash a hash of existing classes that this class is appended to (name => class). # This hash must be specified if the `:overwrite` option is set to `true`. # @option options [Boolean] :overwrite whether an overwrite of an existing class should be allowed (requires also # defining the `:hash` with existing classes as the test is based on the content of this hash). # @option options [Class] :parent (self) the parent class of the generated class. # @option options [String] ('') :prefix the constant prefix to prepend to the constant name referencing the # generated class. # @return [Class] the generated class # def genclass(name, options = {}, &block) genthing(name, Class, options, block) end # Creates a new module. # @param name [String] the name of the generated module # @param options [Hash] hash with options # @option options [Array] :array if specified, the generated class is appended to this array # @option options [Hash<{String => Object}>] :attributes a hash that is applied to the generated class # by calling setter methods corresponding to this hash's keys/value pairs. This is done before the given # block is evaluated. # @option options [Proc] :block a block to evaluate in the context of the class (this block can be provided # this way, or as a normal yield block). # @option options [String] :constant (name with first letter capitalized) what to set the constant that references # the generated class to. # @option options [Hash] :hash a hash of existing classes that this class is appended to (name => class). # This hash must be specified if the `:overwrite` option is set to `true`. # @option options [Boolean] :overwrite whether an overwrite of an existing class should be allowed (requires also # defining the `:hash` with existing classes as the test is based on the content of this hash). # the capitalized name is appended and the result is set as the constant. # @option options [String] ('') :prefix the constant prefix to prepend to the constant name referencing the # generated class. # @return [Module] the generated Module def genmodule(name, options = {}, &block) genthing(name, Module, options, block) end # Removes an existing class. # @param name [String] the name of the class to remove # @param options [Hash] options # @option options [Hash] :hash a hash of existing classes from which the class to be removed is also removed # @return [Boolean] whether the class was removed or not # def rmclass(name, options) options = symbolize_options(options) const = genconst_string(name, options) retval = false if is_constant_defined?(const) remove_const(const) retval = true end if hash = options[:hash] and hash.include? name hash.delete(name) retval = true end # Let them know whether we did actually delete a subclass. retval end private # Generates the constant to create or remove. # @api private def genconst_string(name, options) unless const = options[:constant] prefix = options[:prefix] || "" const = prefix + name2const(name) end const end # This does the actual work of creating our class or module. It's just a # slightly abstract version of genclass. # @api private def genthing(name, type, options, block) options = symbolize_options(options) name = name.to_s.downcase.intern if type == Module #evalmethod = :module_eval evalmethod = :class_eval # Create the class, with the correct name. klass = Module.new do class << self attr_reader :name end @name = name end else options[:parent] ||= self evalmethod = :class_eval # Create the class, with the correct name. klass = Class.new(options[:parent]) do @name = name end end # Create the constant as appropriation. handleclassconst(klass, name, options) # Initialize any necessary variables. initclass(klass, options) block ||= options[:block] # Evaluate the passed block if there is one. This should usually # define all of the work. klass.send(evalmethod, &block) if block klass.postinit if klass.respond_to? :postinit # Store the class in hashes or arrays or whatever. storeclass(klass, name, options) klass end # @api private # def is_constant_defined?(const) const_defined?(const, false) end # Handle the setting and/or removing of the associated constant. # @api private # def handleclassconst(klass, name, options) const = genconst_string(name, options) if is_constant_defined?(const) if options[:overwrite] Puppet.info _("Redefining %{name} in %{klass}") % { name: name, klass: self } remove_const(const) else raise Puppet::ConstantAlreadyDefined, _("Class %{const} is already defined in %{klass}") % { const: const, klass: self } end end const_set(const, klass) const end # Perform the initializations on the class. # @api private # def initclass(klass, options) klass.initvars if klass.respond_to? :initvars if attrs = options[:attributes] attrs.each do |param, value| method = param.to_s + "=" klass.send(method, value) if klass.respond_to? method end end [:include, :extend].each do |method| if set = options[method] set = [set] unless set.is_a?(Array) set.each do |mod| klass.send(method, mod) end end end klass.preinit if klass.respond_to? :preinit end # Convert our name to a constant. # @api private def name2const(name) name.to_s.capitalize end # Store the class in the appropriate places. # @api private def storeclass(klass, klassname, options) if hash = options[:hash] if hash.include? klassname and ! options[:overwrite] raise Puppet::SubclassAlreadyDefined, _("Already a generated class named %{klassname}") % { klassname: klassname } end hash[klassname] = klass end # If we were told to stick it in a hash, then do so if array = options[:array] if (klass.respond_to? :name and array.find { |c| c.name == klassname } and ! options[:overwrite]) raise Puppet::SubclassAlreadyDefined, _("Already a generated class named %{klassname}") % { klassname: klassname } end array << klass end end end puppet-5.5.10/lib/puppet/util/execution.rb0000644005276200011600000004247613417161722020420 0ustar jenkinsjenkinsrequire 'timeout' require 'puppet/file_system/uniquefile' module Puppet require 'rbconfig' require 'puppet/error' # A command failed to execute. # @api public class ExecutionFailure < Puppet::Error end end # This module defines methods for execution of system commands. It is intended for inclusion # in classes that needs to execute system commands. # @api public module Puppet::Util::Execution # This is the full output from a process. The object itself (a String) is the # stdout of the process. # # @api public class ProcessOutput < String # @return [Integer] The exit status of the process # @api public attr_reader :exitstatus # @api private def initialize(value,exitstatus) super(value) @exitstatus = exitstatus end end # The command can be a simple string, which is executed as-is, or an Array, # which is treated as a set of command arguments to pass through. # # In either case, the command is passed directly to the shell, STDOUT and # STDERR are connected together, and STDOUT will be streamed to the yielded # pipe. # # @param command [String, Array] the command to execute as one string, # or as parts in an array. The parts of the array are joined with one # separating space between each entry when converting to the command line # string to execute. # @param failonfail [Boolean] (true) if the execution should fail with # Exception on failure or not. # @yield [pipe] to a block executing a subprocess # @yieldparam pipe [IO] the opened pipe # @yieldreturn [String] the output to return # @raise [Puppet::ExecutionFailure] if the executed child process did not # exit with status == 0 and `failonfail` is `true`. # @return [String] a string with the output from the subprocess executed by # the given block # # @see Kernel#open for `mode` values # @api public def self.execpipe(command, failonfail = true) # Paste together an array with spaces. We used to paste directly # together, no spaces, which made for odd invocations; the user had to # include whitespace between arguments. # # Having two spaces is really not a big drama, since this passes to the # shell anyhow, while no spaces makes for a small developer cost every # time this is invoked. --daniel 2012-02-13 command_str = command.respond_to?(:join) ? command.join(' ') : command if respond_to? :debug debug "Executing '#{command_str}'" else Puppet.debug "Executing '#{command_str}'" end # force the run of the command with # the user/system locale to "C" (via environment variables LANG and LC_*) # it enables to have non localized output for some commands and therefore # a predictable output english_env = ENV.to_hash.merge( {'LANG' => 'C', 'LC_ALL' => 'C'} ) output = Puppet::Util.withenv(english_env) do open("| #{command_str} 2>&1") do |pipe| yield pipe end end if failonfail && exitstatus != 0 raise Puppet::ExecutionFailure, output.to_s end output end def self.exitstatus $CHILD_STATUS.exitstatus end private_class_method :exitstatus # Wraps execution of {execute} with mapping of exception to given exception (and output as argument). # @raise [exception] under same conditions as {execute}, but raises the given `exception` with the output as argument # @return (see execute) # @api public # @deprecated def self.execfail(command, exception) execute(command) rescue Puppet::ExecutionFailure => detail raise exception, detail.message, detail.backtrace end # Default empty options for {execute} NoOptionsSpecified = {} # Executes the desired command, and return the status and output. # def execute(command, options) # @param command [Array, String] the command to execute. If it is # an Array the first element should be the executable and the rest of the # elements should be the individual arguments to that executable. # @param options [Hash] a Hash of options # @option options [String] :cwd the directory from which to run the command. Raises an error if the directory does not exist. # This option is only available on the agent. It cannot be used on the master, meaning it cannot be used in, for example, # regular functions, hiera backends, or report processors. # @option options [Boolean] :failonfail if this value is set to true, then this method will raise an error if the # command is not executed successfully. # @option options [Integer, String] :uid (nil) the user id of the user that the process should be run as. Will be ignored if the # user id matches the effective user id of the current process. # @option options [Integer, String] :gid (nil) the group id of the group that the process should be run as. Will be ignored if the # group id matches the effective group id of the current process. # @option options [Boolean] :combine sets whether or not to combine stdout/stderr in the output, if false stderr output is discarded # @option options [String] :stdinfile (nil) sets a file that can be used for stdin. Passing a string for stdin is not currently # supported. # @option options [Boolean] :squelch (false) if true, ignore stdout / stderr completely. # @option options [Boolean] :override_locale (true) by default (and if this option is set to true), we will temporarily override # the user/system locale to "C" (via environment variables LANG and LC_*) while we are executing the command. # This ensures that the output of the command will be formatted consistently, making it predictable for parsing. # Passing in a value of false for this option will allow the command to be executed using the user/system locale. # @option options [Hash<{String => String}>] :custom_environment ({}) a hash of key/value pairs to set as environment variables for the duration # of the command. # @return [Puppet::Util::Execution::ProcessOutput] output as specified by options # @raise [Puppet::ExecutionFailure] if the executed chiled process did not exit with status == 0 and `failonfail` is # `true`. # @note Unfortunately, the default behavior for failonfail and combine (since # 0.22.4 and 0.24.7, respectively) depend on whether options are specified # or not. If specified, then failonfail and combine default to false (even # when the options specified are neither failonfail nor combine). If no # options are specified, then failonfail and combine default to true. # @comment See commits efe9a833c and d32d7f30 # @api public # def self.execute(command, options = NoOptionsSpecified) # specifying these here rather than in the method signature to allow callers to pass in a partial # set of overrides without affecting the default values for options that they don't pass in default_options = { :failonfail => NoOptionsSpecified.equal?(options), :uid => nil, :gid => nil, :combine => NoOptionsSpecified.equal?(options), :stdinfile => nil, :squelch => false, :override_locale => true, :custom_environment => {}, :sensitive => false, :suppress_window => false, } options = default_options.merge(options) if options[:sensitive] command_str = '[redacted]' elsif command.is_a?(Array) command = command.flatten.map(&:to_s) command_str = command.join(" ") elsif command.is_a?(String) command_str = command end user_log_s = '' if options[:uid] user_log_s << " uid=#{options[:uid]}" end if options[:gid] user_log_s << " gid=#{options[:gid]}" end if user_log_s != '' user_log_s.prepend(' with') end if respond_to? :debug debug "Executing#{user_log_s}: '#{command_str}'" else Puppet.debug "Executing#{user_log_s}: '#{command_str}'" end null_file = Puppet.features.microsoft_windows? ? 'NUL' : '/dev/null' cwd = options[:cwd] if cwd && ! Puppet::FileSystem.directory?(cwd) raise ArgumentError, _("Working directory %{cwd} does not exist!") % { cwd: cwd } end begin stdin = Puppet::FileSystem.open(options[:stdinfile] || null_file, nil, 'r') # On Windows, continue to use the file-based approach to avoid breaking people's existing # manifests. If they use a script that doesn't background cleanly, such as # `start /b ping 127.0.0.1`, we couldn't handle it with pipes as there's no non-blocking # read available. if options[:squelch] stdout = Puppet::FileSystem.open(null_file, nil, 'w') elsif Puppet.features.posix? reader, stdout = IO.pipe else stdout = Puppet::FileSystem::Uniquefile.new('puppet') end stderr = options[:combine] ? stdout : Puppet::FileSystem.open(null_file, nil, 'w') exec_args = [command, options, stdin, stdout, stderr] output = '' # We close stdin/stdout/stderr immediately after fork/exec as they're no longer needed by # this process. In most cases they could be closed later, but when `stdout` is the "writer" # pipe we must close it or we'll never reach eof on the `reader` pipe. if execution_stub = Puppet::Util::ExecutionStub.current_value child_pid = execution_stub.call(*exec_args) [stdin, stdout, stderr].each {|io| io.close rescue nil} return child_pid elsif Puppet.features.posix? child_pid = nil begin child_pid = execute_posix(*exec_args) [stdin, stdout, stderr].each {|io| io.close rescue nil} if options[:squelch] exit_status = Process.waitpid2(child_pid).last.exitstatus else # Use non-blocking read to check for data. After each attempt, # check whether the child is done. This is done in case the child # forks and inherits stdout, as happens in `foo &`. until results = Process.waitpid2(child_pid, Process::WNOHANG) # If not done, wait for data to read with a timeout # This timeout is selected to keep activity low while waiting on # a long process, while not waiting too long for the pathological # case where stdout is never closed. ready = IO.select([reader], [], [], 0.1) begin output << reader.read_nonblock(4096) if ready rescue Errno::EAGAIN rescue EOFError end end # Read any remaining data. Allow for but don't expect EOF. begin loop do output << reader.read_nonblock(4096) end rescue Errno::EAGAIN rescue EOFError end # Force to external encoding to preserve prior behavior when reading a file. # Wait until after reading all data so we don't encounter corruption when # reading part of a multi-byte unicode character if default_external is UTF-8. output.force_encoding(Encoding.default_external) exit_status = results.last.exitstatus end child_pid = nil rescue Timeout::Error => e # NOTE: For Ruby 2.1+, an explicit Timeout::Error class has to be # passed to Timeout.timeout in order for there to be something for # this block to rescue. unless child_pid.nil? Process.kill(:TERM, child_pid) # Spawn a thread to reap the process if it dies. Thread.new { Process.waitpid(child_pid) } end raise e end elsif Puppet.features.microsoft_windows? process_info = execute_windows(*exec_args) begin [stdin, stderr].each {|io| io.close rescue nil} exit_status = Puppet::Util::Windows::Process.wait_process(process_info.process_handle) # read output in if required unless options[:squelch] output = wait_for_output(stdout) Puppet.warning _("Could not get output") unless output end ensure FFI::WIN32.CloseHandle(process_info.process_handle) FFI::WIN32.CloseHandle(process_info.thread_handle) end end if options[:failonfail] and exit_status != 0 raise Puppet::ExecutionFailure, _("Execution of '%{str}' returned %{exit_status}: %{output}") % { str: command_str, exit_status: exit_status, output: output.strip } end ensure # Make sure all handles are closed in case an exception was thrown attempting to execute. [stdin, stdout, stderr].each {|io| io.close rescue nil} if !options[:squelch] # if we opened a pipe, we need to clean it up. reader.close if reader stdout.close! if Puppet.features.microsoft_windows? end end Puppet::Util::Execution::ProcessOutput.new(output || '', exit_status) end # Returns the path to the ruby executable (available via Config object, even if # it's not in the PATH... so this is slightly safer than just using Puppet::Util.which) # @return [String] the path to the Ruby executable # @api private # def self.ruby_path() File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name'] + RbConfig::CONFIG['EXEEXT']). sub(/.*\s.*/m, '"\&"') end # Because some modules provide their own version of this method. class << self alias util_execute execute end # This is private method. # @comment see call to private_class_method after method definition # @api private # def self.execute_posix(command, options, stdin, stdout, stderr) child_pid = Puppet::Util.safe_posix_fork(stdin, stdout, stderr) do # We can't just call Array(command), and rely on it returning # things like ['foo'], when passed ['foo'], because # Array(command) will call command.to_a internally, which when # given a string can end up doing Very Bad Things(TM), such as # turning "/tmp/foo;\r\n /bin/echo" into ["/tmp/foo;\r\n", " /bin/echo"] command = [command].flatten Process.setsid begin # We need to chdir to our cwd before changing privileges as there's a # chance that the user may not have permissions to access the cwd, which # would cause execute_posix to fail. cwd = options[:cwd] Dir.chdir(cwd) if cwd Puppet::Util::SUIDManager.change_privileges(options[:uid], options[:gid], true) # if the caller has requested that we override locale environment variables, if (options[:override_locale]) then # loop over them and clear them Puppet::Util::POSIX::LOCALE_ENV_VARS.each { |name| ENV.delete(name) } # set LANG and LC_ALL to 'C' so that the command will have consistent, predictable output # it's OK to manipulate these directly rather than, e.g., via "withenv", because we are in # a forked process. ENV['LANG'] = 'C' ENV['LC_ALL'] = 'C' end # unset all of the user-related environment variables so that different methods of starting puppet # (automatic start during boot, via 'service', via /etc/init.d, etc.) won't have unexpected side # effects relating to user / home dir environment vars. # it's OK to manipulate these directly rather than, e.g., via "withenv", because we are in # a forked process. Puppet::Util::POSIX::USER_ENV_VARS.each { |name| ENV.delete(name) } options[:custom_environment] ||= {} Puppet::Util.withenv(options[:custom_environment]) do Kernel.exec(*command) end rescue => detail Puppet.log_exception(detail, _("Could not execute posix command: %{detail}") % { detail: detail }) exit!(1) end end child_pid end private_class_method :execute_posix # This is private method. # @comment see call to private_class_method after method definition # @api private # def self.execute_windows(command, options, stdin, stdout, stderr) command = command.map do |part| part.include?(' ') ? %Q["#{part.gsub(/"/, '\"')}"] : part end.join(" ") if command.is_a?(Array) options[:custom_environment] ||= {} Puppet::Util.withenv(options[:custom_environment], :windows) do Puppet::Util::Windows::Process.execute(command, options, stdin, stdout, stderr) end end private_class_method :execute_windows # This is private method. # @comment see call to private_class_method after method definition # @api private # def self.wait_for_output(stdout) # Make sure the file's actually been written. This is basically a race # condition, and is probably a horrible way to handle it, but, well, oh # well. # (If this method were treated as private / inaccessible from outside of this file, we shouldn't have to worry # about a race condition because all of the places that we call this from are preceded by a call to "waitpid2", # meaning that the processes responsible for writing the file have completed before we get here.) 2.times do |try| if Puppet::FileSystem.exist?(stdout.path) stdout.open begin return stdout.read ensure stdout.close stdout.unlink end else time_to_sleep = try / 2.0 Puppet.warning _("Waiting for output; will sleep %{time_to_sleep} seconds") % { time_to_sleep: time_to_sleep } sleep(time_to_sleep) end end nil end private_class_method :wait_for_output end puppet-5.5.10/lib/puppet/util/feature.rb0000644005276200011600000000514313417161722020036 0ustar jenkinsjenkinsrequire 'puppet' class Puppet::Util::Feature attr_reader :path # Create a new feature test. You have to pass the feature name, # and it must be unique. You can either provide a block that # will get executed immediately to determine if the feature # is present, or you can pass an option to determine it. # Currently, the only supported option is 'libs' (must be # passed as a symbol), which will make sure that each lib loads # successfully. def add(name, options = {}) method = name.to_s + "?" @results.delete(name) if block_given? begin result = yield rescue StandardError,ScriptError => detail warn _("Failed to load feature test for %{name}: %{detail}") % { name: name, detail: detail } result = false end @results[name] = result end meta_def(method) do # we return a cached result if: # * if a block is given (and we just evaluated it above) # * if we already have a positive result # * if we've tested this feature before and it failed, but we're # configured to always cache if block_given? || @results[name] || (@results.has_key?(name) && (!Puppet[:always_retry_plugins])) @results[name] else @results[name] = test(name, options) @results[name] end end end # Create a new feature collection. def initialize(path) @path = path @results = {} @loader = Puppet::Util::Autoload.new(self, @path) end def load @loader.loadall end def method_missing(method, *args) return super unless method.to_s =~ /\?$/ feature = method.to_s.sub(/\?$/, '') @loader.load(feature) respond_to?(method) && self.send(method) end # Actually test whether the feature is present. We only want to test when # someone asks for the feature, so we don't unnecessarily load # files. def test(name, options) return true unless ary = options[:libs] ary = [ary] unless ary.is_a?(Array) ary.each do |lib| return false unless load_library(lib, name) end # We loaded all of the required libraries true end private def load_library(lib, name) raise ArgumentError, _("Libraries must be passed as strings not %{klass}") % { klass: lib.class } unless lib.is_a?(String) @rubygems ||= Puppet::Util::RubyGems::Source.new @rubygems.clear_paths begin require lib rescue ScriptError => detail Puppet.debug _("Failed to load library '%{lib}' for feature '%{name}': %{detail}") % { lib: lib, name: name, detail: detail } return false end true end end puppet-5.5.10/lib/puppet/util/fileparsing.rb0000644005276200011600000002464713417161722020720 0ustar jenkinsjenkins# A mini-language for parsing files. This is only used file the ParsedFile # provider, but it makes more sense to split it out so it's easy to maintain # in one place. # # You can use this module to create simple parser/generator classes. For instance, # the following parser should go most of the way to parsing /etc/passwd: # # class Parser # include Puppet::Util::FileParsing # record_line :user, :fields => %w{name password uid gid gecos home shell}, # :separator => ":" # end # # You would use it like this: # # parser = Parser.new # lines = parser.parse(File.read("/etc/passwd")) # # lines.each do |type, hash| # type will always be :user, since we only have one # p hash # end # # Each line in this case would be a hash, with each field set appropriately. # You could then call 'parser.to_line(hash)' on any of those hashes to generate # the text line again. require 'puppet/util/methodhelper' module Puppet::Util::FileParsing include Puppet::Util attr_writer :line_separator, :trailing_separator class FileRecord include Puppet::Util include Puppet::Util::MethodHelper attr_accessor :absent, :joiner, :rts, :separator, :rollup, :name, :match, :block_eval attr_reader :fields, :optional, :type INVALID_FIELDS = [:record_type, :target, :on_disk] # Customize this so we can do a bit of validation. def fields=(fields) @fields = fields.collect do |field| r = field.intern raise ArgumentError.new(_("Cannot have fields named %{name}") % { name: r }) if INVALID_FIELDS.include?(r) r end end def initialize(type, options = {}, &block) @type = type.intern raise ArgumentError, _("Invalid record type %{record_type}") % { record_type: @type } unless [:record, :text].include?(@type) set_options(options) if self.type == :record # Now set defaults. self.absent ||= "" self.separator ||= /\s+/ self.joiner ||= " " self.optional ||= [] @rollup = true unless defined?(@rollup) end if block_given? @block_eval ||= :process # Allow the developer to specify that a block should be instance-eval'ed. if @block_eval == :instance instance_eval(&block) else meta_def(@block_eval, &block) end end end # Convert a record into a line by joining the fields together appropriately. # This is pulled into a separate method so it can be called by the hooks. def join(details) joinchar = self.joiner fields.collect { |field| # If the field is marked absent, use the appropriate replacement if details[field] == :absent or details[field] == [:absent] or details[field].nil? if self.optional.include?(field) self.absent else raise ArgumentError, _("Field '%{field}' is required") % { field: field } end else details[field].to_s end }.reject { |c| c.nil?}.join(joinchar) end # Customize this so we can do a bit of validation. def optional=(optional) @optional = optional.collect do |field| field.intern end end # Create a hook that modifies the hash resulting from parsing. def post_parse=(block) meta_def(:post_parse, &block) end # Create a hook that modifies the hash just prior to generation. def pre_gen=(block) meta_def(:pre_gen, &block) end # Are we a text type? def text? type == :text end def to_line=(block) meta_def(:to_line, &block) end end # Clear all existing record definitions. Only used for testing. def clear_records @record_types.clear @record_order.clear end def fields(type) if record = record_type(type) record.fields.dup else nil end end # Try to match a specific text line. def handle_text_line(line, record) line =~ record.match ? {:record_type => record.name, :line => line} : nil end # Try to match a record. # # @param [String] line The line to be parsed # @param [Puppet::Util::FileType] record The filetype to use for parsing # # @return [Hash] The parsed elements of the line def handle_record_line(line, record) ret = nil if record.respond_to?(:process) if ret = record.send(:process, line.dup) unless ret.is_a?(Hash) raise Puppet::DevError, _("Process record type %{record_name} returned non-hash") % { record_name: record.name } end else return nil end elsif regex = record.match # In this case, we try to match the whole line and then use the # match captures to get our fields. if match = regex.match(line) ret = {} record.fields.zip(match.captures).each do |field, value| if value == record.absent ret[field] = :absent else ret[field] = value end end else nil end else ret = {} sep = record.separator # String "helpfully" replaces ' ' with /\s+/ in splitting, so we # have to work around it. if sep == " " sep = / / end line_fields = line.split(sep) record.fields.each do |param| value = line_fields.shift if value and value != record.absent ret[param] = value else ret[param] = :absent end end if record.rollup and ! line_fields.empty? last_field = record.fields[-1] val = ([ret[last_field]] + line_fields).join(record.joiner) ret[last_field] = val end end if ret ret[:record_type] = record.name return ret else return nil end end def line_separator @line_separator ||= "\n" @line_separator end # Split text into separate lines using the record separator. def lines(text) # NOTE: We do not have to remove trailing separators because split will ignore # them by default (unless you pass -1 as a second parameter) text.split(self.line_separator) end # Split a bunch of text into lines and then parse them individually. def parse(text) count = 1 lines(text).collect do |line| count += 1 if val = parse_line(line) val else error = Puppet::ResourceError.new(_("Could not parse line %{line}") % { line: line.inspect }) error.line = count raise error end end end # Handle parsing a single line. def parse_line(line) raise Puppet::DevError, _("No record types defined; cannot parse lines") unless records? @record_order.each do |record| # These are basically either text or record lines. method = "handle_#{record.type}_line" if respond_to?(method) if result = send(method, line, record) record.send(:post_parse, result) if record.respond_to?(:post_parse) return result end else raise Puppet::DevError, _("Somehow got invalid line type %{record_type}") % { record_type: record.type } end end nil end # Define a new type of record. These lines get split into hashes. Valid # options are: # * :absent: What to use as value within a line, when a field is # absent. Note that in the record object, the literal :absent symbol is # used, and not this value. Defaults to "". # * :fields: The list of fields, as an array. By default, all # fields are considered required. # * :joiner: How to join fields together. Defaults to '\t'. # * :optional: Which fields are optional. If these are missing, # you'll just get the 'absent' value instead of an ArgumentError. # * :rts: Whether to remove trailing whitespace. Defaults to false. # If true, whitespace will be removed; if a regex, then whatever matches # the regex will be removed. # * :separator: The record separator. Defaults to /\s+/. def record_line(name, options, &block) raise ArgumentError, _("Must include a list of fields") unless options.include?(:fields) record = FileRecord.new(:record, options, &block) record.name = name.intern new_line_type(record) end # Are there any record types defined? def records? defined?(@record_types) and ! @record_types.empty? end # Define a new type of text record. def text_line(name, options, &block) raise ArgumentError, _("You must provide a :match regex for text lines") unless options.include?(:match) record = FileRecord.new(:text, options, &block) record.name = name.intern new_line_type(record) end # Generate a file from a bunch of hash records. def to_file(records) text = records.collect { |record| to_line(record) }.join(line_separator) text += line_separator if trailing_separator text end # Convert our parsed record into a text record. def to_line(details) unless record = record_type(details[:record_type]) raise ArgumentError, _("Invalid record type %{record_type}") % { record_type: details[:record_type].inspect } end if record.respond_to?(:pre_gen) details = details.dup record.send(:pre_gen, details) end case record.type when :text; return details[:line] else return record.to_line(details) if record.respond_to?(:to_line) line = record.join(details) if regex = record.rts # If they say true, then use whitespace; else, use their regex. if regex == true regex = /\s+$/ end return line.sub(regex,'') else return line end end end # Whether to add a trailing separator to the file. Defaults to true def trailing_separator if defined?(@trailing_separator) return @trailing_separator else return true end end def valid_attr?(type, attr) type = type.intern if record = record_type(type) and record.fields.include?(attr.intern) return true else if attr.intern == :ensure return true else false end end end private # Define a new type of record. def new_line_type(record) @record_types ||= {} @record_order ||= [] raise ArgumentError, _("Line type %{name} is already defined") % { name: record.name } if @record_types.include?(record.name) @record_types[record.name] = record @record_order << record record end # Retrieve the record object. def record_type(type) @record_types[type.intern] end end puppet-5.5.10/lib/puppet/util/instance_loader.rb0000644005276200011600000000403613417161722021535 0ustar jenkinsjenkinsrequire 'puppet/util/autoload' require 'puppet/util' # A module that can easily autoload things for us. Uses an instance # of Puppet::Util::Autoload module Puppet::Util::InstanceLoader include Puppet::Util # Are we instance-loading this type? def instance_loading?(type) defined?(@autoloaders) and @autoloaders.include?(type.intern) end # Define a new type of autoloading. def instance_load(type, path, options = {}) @autoloaders ||= {} @instances ||= {} type = type.intern @instances[type] = {} @autoloaders[type] = Puppet::Util::Autoload.new(self, path, options) # Now define our new simple methods unless respond_to?(type) meta_def(type) do |name| loaded_instance(type, name) end end end # Return a list of the names of all instances def loaded_instances(type) @instances[type].keys end # Collect the docs for all of our instances. def instance_docs(type) docs = "" # Load all instances. instance_loader(type).loadall # Use this method so they all get loaded loaded_instances(type).sort { |a,b| a.to_s <=> b.to_s }.each do |name| mod = self.loaded_instance(type, name) docs << "#{name}\n#{"-" * name.to_s.length}\n" docs << Puppet::Util::Docs.scrub(mod.doc) << "\n\n" end docs end # Return the instance hash for our type. def instance_hash(type) @instances[type.intern] end # Return the Autoload object for a given type. def instance_loader(type) @autoloaders[type.intern] end # Retrieve an already-loaded instance, or attempt to load our instance. def loaded_instance(type, name) name = name.intern return nil unless instances = instance_hash(type) unless instances.include? name if instance_loader(type).load(name) unless instances.include? name Puppet.warning(_("Loaded %{type} file for %{name} but %{type} was not defined") % { type: type, name: name }) return nil end else return nil end end instances[name] end end puppet-5.5.10/lib/puppet/util/json.rb0000644005276200011600000000535213417161722017356 0ustar jenkinsjenkinsmodule Puppet::Util module Json class ParseError < StandardError attr_reader :cause, :data def self.build(original_exception, data) new(original_exception.message).tap do |exception| exception.instance_eval do @cause = original_exception set_backtrace original_exception.backtrace @data = data end end end end begin require 'multi_json' # Force backend detection before attempting to use the library # or load any other JSON libraries MultiJson.default_adapter # Preserve core type monkey-patching done by the built-in JSON gem require 'json' rescue LoadError require 'json' end # These methods do similar processing to the fallback implemented by MultiJson # when using the built-in JSON backend, to ensure consistent behavior # whether or not MultiJson can be loaded. def self.load(string, options = {}) if defined? MultiJson begin # This ensures that JrJackson will parse very large or very small # numbers as floats rather than BigDecimals, which are serialized as # strings by the built-in JSON gem and therefore can cause schema errors, # for example, when we are rendering reports to JSON using `to_pson` in # PuppetDB. if MultiJson.adapter.name == "MultiJson::Adapters::JrJackson" options[:use_bigdecimal] = false end MultiJson.load(string, options) rescue MultiJson::ParseError => e raise Puppet::Util::Json::ParseError.build(e, string) end else begin string = string.read if string.respond_to?(:read) options[:symbolize_names] = true if options.delete(:symbolize_keys) ::JSON.parse(string, options) rescue JSON::ParserError => e raise Puppet::Util::Json::ParseError.build(e, string) end end end def self.dump(object, options = {}) if defined? MultiJson # MultiJson calls `merge` on the options it is passed, which relies # on the options' defining a `to_hash` method. In Ruby 1.9.3, # JSON::Ext::Generator::State only defines `to_h`, not `to_hash`, so we # need to convert it first, similar to what is done in the `else` block # below. Later versions of the JSON gem alias `to_h` to `to_hash`, so this # can be removed once we drop Ruby 1.9.3 support. options = options.to_h if options.class.name == "JSON::Ext::Generator::State" MultiJson.dump(object, options) else options.merge!(::JSON::PRETTY_STATE_PROTOTYPE.to_h) if options.delete(:pretty) object.to_json(options) end end end end puppet-5.5.10/lib/puppet/util/logging.rb0000644005276200011600000002710113417161722020027 0ustar jenkinsjenkins# A module to make logging a bit easier. require 'puppet/util/log' require 'puppet/error' require 'facter' module Puppet::Util module Logging def send_log(level, message) Puppet::Util::Log.create({:level => level, :source => log_source, :message => message}.merge(log_metadata)) end # Create a method for each log level. Puppet::Util::Log.eachlevel do |level| # handle debug a special way for performance reasons next if level == :debug define_method(level) do |args| args = args.join(" ") if args.is_a?(Array) send_log(level, args) end end # Output a debug log message if debugging is on (but only then) # If the output is anything except a static string, give the debug # a block - it will be called with all other arguments, and is expected # to return the single string result. # # Use a block at all times for increased performance. # # @example This takes 40% of the time compared to not using a block # Puppet.debug { "This is a string that interpolated #{x} and #{y} }" # def debug(*args) return nil unless Puppet::Util::Log.level == :debug if block_given? send_log(:debug, yield(*args)) else send_log(:debug, args.join(" ")) end end # Log an exception via Puppet.err. Will also log the backtrace if Puppet[:trace] is set. # Parameters: # [exception] an Exception to log # [message] an optional String overriding the message to be logged; by default, we log Exception.message. # If you pass a String here, your string will be logged instead. You may also pass nil if you don't # wish to log a message at all; in this case it is likely that you are only calling this method in order # to take advantage of the backtrace logging. def log_exception(exception, message = :default, options = {}) trace = Puppet[:trace] || options[:trace] if message == :default && exception.is_a?(Puppet::ParseErrorWithIssue) # Retain all detailed info and keep plain message and stacktrace separate backtrace = [] build_exception_trace(backtrace, exception, trace) Puppet::Util::Log.create({ :level => options[:level] || :err, :source => log_source, :message => exception.basic_message, :issue_code => exception.issue_code, :backtrace => backtrace.empty? ? nil : backtrace, :file => exception.file, :line => exception.line, :pos => exception.pos, :environment => exception.environment, :node => exception.node }.merge(log_metadata)) else err(format_exception(exception, message, trace)) end end def build_exception_trace(arr, exception, trace = true) if trace and exception.backtrace exception.backtrace.each do |line| arr << line =~ /^(.+):(\d+.*)$/ ? ("#{Pathname($1).realpath}:#{$2}" rescue line) : line end end if exception.respond_to?(:original) original = exception.original unless original.nil? arr << _('Wrapped exception:') arr << original.message build_exception_trace(arr, original, trace) end end end private :build_exception_trace def format_exception(exception, message = :default, trace = true) arr = [] case message when :default arr << exception.message when nil # don't log anything if they passed a nil; they are just calling for the optional backtrace logging else arr << message end if trace and exception.backtrace arr << Puppet::Util.pretty_backtrace(exception.backtrace) end if exception.respond_to?(:original) and exception.original arr << _("Wrapped exception:") arr << format_exception(exception.original, :default, trace) end arr.flatten.join("\n") end def log_and_raise(exception, message) log_exception(exception, message) raise exception, message + "\n" + exception.to_s, exception.backtrace end class DeprecationWarning < Exception; end # Logs a warning indicating that the Ruby code path is deprecated. Note that # this method keeps track of the offending lines of code that triggered the # deprecation warning, and will only log a warning once per offending line of # code. It will also stop logging deprecation warnings altogether after 100 # unique deprecation warnings have been logged. Finally, if # Puppet[:disable_warnings] includes 'deprecations', it will squelch all # warning calls made via this method. # # @param message [String] The message to log (logs via warning) # @param key [String] Optional key to mark the message as unique. If not # passed in, the originating call line will be used instead. def deprecation_warning(message, key = nil) issue_deprecation_warning(message, key, nil, nil, true) end # Logs a warning whose origin comes from Puppet source rather than somewhere # internal within Puppet. Otherwise the same as deprecation_warning() # # @param message [String] The message to log (logs via warning) # @param options [Hash] # @option options [String] :file File we are warning from # @option options [Integer] :line Line number we are warning from # @option options [String] :key (:file + :line) Alternative key used to mark # warning as unique # # Either :file and :line and/or :key must be passed. def puppet_deprecation_warning(message, options = {}) key = options[:key] file = options[:file] line = options[:line] #TRANSLATORS the literals ":file", ":line", and ":key" should not be translated raise Puppet::DevError, _("Need either :file and :line, or :key") if (key.nil?) && (file.nil? || line.nil?) key ||= "#{file}:#{line}" issue_deprecation_warning(message, key, file, line, false) end # Logs a (non deprecation) warning once for a given key. # # @param kind [String] The kind of warning. The # kind must be one of the defined kinds for the Puppet[:disable_warnings] setting. # @param message [String] The message to log (logs via warning) # @param key [String] Key used to make this warning unique # @param file [String,:default,nil] the File related to the warning # @param line [Integer,:default,nil] the Line number related to the warning # warning as unique # @param level [Symbol] log level to use, defaults to :warning # # Either :file and :line and/or :key must be passed. def warn_once(kind, key, message, file = nil, line = nil, level = :warning) return if Puppet[:disable_warnings].include?(kind) $unique_warnings ||= {} if $unique_warnings.length < 100 then if (! $unique_warnings.has_key?(key)) then $unique_warnings[key] = message call_trace = if file == :default and line == :default # Suppress the file and line number output '' else error_location_str = Puppet::Util::Errors.error_location(file, line) if error_location_str.empty? '\n ' + _('(file & line not available)') else "\n %{error_location}" % { error_location: error_location_str } end end send_log(level, "#{message}#{call_trace}") end end end def get_deprecation_offender() # we have to put this in its own method to simplify testing; we need to be able to mock the offender results in # order to test this class, and our framework does not appear to enjoy it if you try to mock Kernel.caller # # let's find the offending line; we need to jump back up the stack a few steps to find the method that called # the deprecated method if Puppet[:trace] caller()[2..-1] else [caller()[2]] end end def clear_deprecation_warnings $unique_warnings.clear if $unique_warnings $deprecation_warnings.clear if $deprecation_warnings end # TODO: determine whether there might be a potential use for adding a puppet configuration option that would # enable this deprecation logging. # utility method that can be called, e.g., from spec_helper config.after, when tracking down calls to deprecated # code. # Parameters: # [deprecations_file] relative or absolute path of a file to log the deprecations to # [pattern] (default nil) if specified, will only log deprecations whose message matches the provided pattern def log_deprecations_to_file(deprecations_file, pattern = nil) # this method may get called lots and lots of times (e.g., from spec_helper config.after) without the global # list of deprecation warnings being cleared out. We don't want to keep logging the same offenders over and over, # so, we need to keep track of what we've logged. # # It'd be nice if we could just clear out the list of deprecation warnings, but then the very next spec might # find the same offender, and we'd end up logging it again. $logged_deprecation_warnings ||= {} # Deprecation messages are UTF-8 as they are produced by Ruby Puppet::FileSystem.open(deprecations_file, nil, "a:UTF-8") do |f| if ($deprecation_warnings) then $deprecation_warnings.each do |offender, message| if (! $logged_deprecation_warnings.has_key?(offender)) then $logged_deprecation_warnings[offender] = true if ((pattern.nil?) || (message =~ pattern)) then f.puts(message) f.puts(offender) f.puts() end end end end end end # Sets up Facter logging. # This method causes Facter output to be forwarded to Puppet. def self.setup_facter_logging! # Only recent versions of Facter support this feature return false unless Facter.respond_to? :on_message # The current Facter log levels are: :trace, :debug, :info, :warn, :error, and :fatal. # Convert to the corresponding levels in Puppet Facter.on_message do |level, message| case level when :trace, :debug level = :debug when :info # Same as Puppet when :warn level = :warning when :error level = :err when :fatal level = :crit else next end Puppet::Util::Log.create({:level => level, :source => 'Facter', :message => message}) nil end true end private def issue_deprecation_warning(message, key, file, line, use_caller) return if Puppet[:disable_warnings].include?('deprecations') $deprecation_warnings ||= {} if $deprecation_warnings.length < 100 key ||= (offender = get_deprecation_offender) unless $deprecation_warnings.has_key?(key) $deprecation_warnings[key] = message # split out to allow translation call_trace = if use_caller _("(location: %{location})") % { location: (offender || get_deprecation_offender).join('; ') } else Puppet::Util::Errors.error_location_with_unknowns(file, line) end warning("%{message}\n %{call_trace}" % { message: message, call_trace: call_trace }) end end end def is_resource? defined?(Puppet::Type) && is_a?(Puppet::Type) end def is_resource_parameter? defined?(Puppet::Parameter) && is_a?(Puppet::Parameter) end def log_metadata [:file, :line, :tags].inject({}) do |result, attr| result[attr] = send(attr) if respond_to?(attr) result end end def log_source # We need to guard the existence of the constants, since this module is used by the base Puppet module. (is_resource? or is_resource_parameter?) and respond_to?(:path) and return path.to_s to_s end end end puppet-5.5.10/lib/puppet/util/methodhelper.rb0000644005276200011600000000165313417161722021065 0ustar jenkinsjenkins# Where we store helper methods related to, um, methods. module Puppet::Util::MethodHelper def requiredopts(*names) names.each do |name| devfail("#{name} is a required option for #{self.class}") if self.send(name).nil? end end # Iterate over a hash, treating each member as an attribute. def set_options(options) options.each do |param,value| method = param.to_s + "=" if respond_to? method self.send(method, value) else raise ArgumentError, _("Invalid parameter %{parameter} to object class %{class_name}") % { parameter: param, class_name: self.class } end end end # Take a hash and convert all of the keys to symbols if possible. def symbolize_options(options) options.inject({}) do |hash, opts| if opts[0].respond_to? :intern hash[opts[0].intern] = opts[1] else hash[opts[0]] = opts[1] end hash end end end puppet-5.5.10/lib/puppet/util/nagios_maker.rb0000644005276200011600000000572313417161722021046 0ustar jenkinsjenkinsrequire 'puppet/external/nagios' require 'puppet/external/nagios/base' require 'puppet/provider/naginator' module Puppet::Util::NagiosMaker # Create a new nagios type, using all of the parameters # from the parser. def self.create_nagios_type(name) name = name.to_sym full_name = ("nagios_#{name}").to_sym raise Puppet::DevError, _("No nagios type for %{name}") % { name: name } unless nagtype = Nagios::Base.type(name) type = Puppet::Type.newtype(full_name) do # Generate a file resource if necessary. # # @see Puppet::Type::File and its properties owner, group and mode. def generate return nil unless self[:owner] or self[:group] or self[:mode] props = { :name => self[:target] } [ :owner, :group, :mode ].each do |prop| props[prop] = self[prop] if self[prop] end Puppet::Type.type(:file).new(props) end end type.ensurable type.newparam(nagtype.namevar, :namevar => true) do desc "The name of this nagios_#{nagtype.name} resource." end [ :owner, :group, :mode].each do |fileprop| type.newparam(fileprop) do desc "The desired #{fileprop} of the config file for this nagios_#{nagtype.name} resource. NOTE: If the target file is explicitly managed by a file resource in your manifest, this parameter has no effect. If a parent directory of the target is managed by a recursive file resource, this limitation does not apply (i.e., this parameter takes precedence, and if purge is used, the target file is exempt)." end end # We deduplicate the parameters because it makes sense to allow Naginator to have dupes. nagtype.parameters.uniq.each do |param| next if param == nagtype.namevar # We can't turn these parameter names into constants, so at least for now they aren't # supported. next if param.to_s =~ /^[0-9]/ type.newproperty(param) do desc "Nagios configuration file parameter." end end type.newproperty(:target) do desc 'The target.' defaultto do resource.class.defaultprovider.default_target end end target = "/etc/nagios/#{full_name.to_s}.cfg" provider = type.provide(:naginator, :parent => Puppet::Provider::Naginator, :default_target => target) {} provider.nagios_type type.desc "The Nagios type #{name.to_s}. This resource type is autogenerated using the model developed in Naginator, and all of the Nagios types are generated using the same code and the same library. This type generates Nagios configuration statements in Nagios-parseable configuration files. By default, the statements will be added to `#{target}`, but you can send them to a different file by setting their `target` attribute. You can purge Nagios resources using the `resources` type, but *only* in the default file locations. This is an architectural limitation. " end end puppet-5.5.10/lib/puppet/util/platform.rb0000644005276200011600000000173013417161722020225 0ustar jenkinsjenkinsmodule Puppet module Util module Platform FIPS_STATUS_FILE = "/proc/sys/crypto/fips_enabled".freeze def windows? # Ruby only sets File::ALT_SEPARATOR on Windows and the Ruby standard # library uses that to test what platform it's on. In some places we # would use Puppet.features.microsoft_windows?, but this method can be # used to determine the behavior of the underlying system without # requiring features to be initialized and without side effect. !!File::ALT_SEPARATOR end module_function :windows? def default_paths return [] if windows? %w{/usr/sbin /sbin} end module_function :default_paths @fips_enabled = !windows? && File.exist?(FIPS_STATUS_FILE) && File.read(FIPS_STATUS_FILE, 1) == '1' def fips_enabled? @fips_enabled end module_function :fips_enabled? end end end puppet-5.5.10/lib/puppet/util/provider_features.rb0000644005276200011600000001356513417161722022142 0ustar jenkinsjenkins# Provides feature definitions. require 'puppet/util/methodhelper' require 'puppet/util/docs' require 'puppet/util' # This module models provider features and handles checking whether the features # are present. # @todo Unclear what is api and what is private in this module. # module Puppet::Util::ProviderFeatures include Puppet::Util::Docs # This class models provider features and handles checking whether the features # are present. # @todo Unclear what is api and what is private in this class class ProviderFeature include Puppet::Util include Puppet::Util::MethodHelper include Puppet::Util::Docs attr_accessor :name, :docs, :methods # Are all of the requirements met? # Requirements are checked by checking if feature predicate methods have been generated - see {#methods_available?}. # @param obj [Object, Class] the object or class to check if requirements are met # @return [Boolean] whether all requirements for this feature are met or not. def available?(obj) if self.methods return !!methods_available?(obj) else # In this case, the provider has to declare support for this # feature, and that's been checked before we ever get to the # method checks. return false end end def initialize(name, docs, hash) self.name = name.intern self.docs = docs hash = symbolize_options(hash) set_options(hash) end private # Checks whether all feature predicate methods are available. # @param obj [Object, Class] the object or class to check if feature predicates are available or not. # @return [Boolean] Returns whether all of the required methods are available or not in the given object. def methods_available?(obj) methods.each do |m| if obj.is_a?(Class) return false unless obj.public_method_defined?(m) else return false unless obj.respond_to?(m) end end true end end # Defines one feature. # At a minimum, a feature requires a name # and docs, and at this point they should also specify a list of methods # required to determine if the feature is present. # @todo How methods that determine if the feature is present are specified. def feature(name, docs, hash = {}) @features ||= {} raise Puppet::DevError, _("Feature %{name} is already defined") % { name: name } if @features.include?(name) begin obj = ProviderFeature.new(name, docs, hash) @features[obj.name] = obj rescue ArgumentError => detail error = ArgumentError.new( _("Could not create feature %{name}: %{detail}") % { name: name, detail: detail } ) error.set_backtrace(detail.backtrace) raise error end end # @return [String] Returns a string with documentation covering all features. def featuredocs str = "" @features ||= {} return nil if @features.empty? names = @features.keys.sort { |a,b| a.to_s <=> b.to_s } names.each do |name| doc = @features[name].docs.gsub(/\n\s+/, " ") str << "- *#{name}*: #{doc}\n" end if providers.length > 0 headers = ["Provider", names].flatten data = {} providers.each do |provname| data[provname] = [] prov = provider(provname) names.each do |name| if prov.feature?(name) data[provname] << "*X*" else data[provname] << "" end end end str << doctable(headers, data) end str end # @return [Array] Returns a list of features. def features @features ||= {} @features.keys end # Generates a module that sets up the boolean predicate methods to test for given features. # def feature_module unless defined?(@feature_module) @features ||= {} @feature_module = ::Module.new const_set("FeatureModule", @feature_module) features = @features # Create a feature? method that can be passed a feature name and # determine if the feature is present. @feature_module.send(:define_method, :feature?) do |name| method = name.to_s + "?" return !!(respond_to?(method) and send(method)) end # Create a method that will list all functional features. @feature_module.send(:define_method, :features) do return false unless defined?(features) features.keys.find_all { |n| feature?(n) }.sort { |a,b| a.to_s <=> b.to_s } end # Create a method that will determine if a provided list of # features are satisfied by the curred provider. @feature_module.send(:define_method, :satisfies?) do |*needed| ret = true needed.flatten.each do |feature| unless feature?(feature) ret = false break end end ret end # Create a boolean method for each feature so you can test them # individually as you might need. @features.each do |name, feature| method = name.to_s + "?" @feature_module.send(:define_method, method) do (is_a?(Class) ? declared_feature?(name) : self.class.declared_feature?(name)) or feature.available?(self) end end # Allow the provider to declare that it has a given feature. @feature_module.send(:define_method, :has_features) do |*names| @declared_features ||= [] names.each do |name| @declared_features << name.intern end end # Aaah, grammatical correctness @feature_module.send(:alias_method, :has_feature, :has_features) end @feature_module end # @return [ProviderFeature] Returns a provider feature instance by name. # @param name [String] the name of the feature to return # @note Should only be used for testing. # @api private # def provider_feature(name) return nil unless defined?(@features) @features[name] end end puppet-5.5.10/lib/puppet/util/rdoc.rb0000644005276200011600000000352113417161722017330 0ustar jenkinsjenkinsrequire 'puppet/util' module Puppet::Util::RDoc module_function # launch a rdoc documentation process # with the files/dir passed in +files+ def rdoc(outputdir, files, charset = nil) # then rdoc require 'rdoc/rdoc' require 'rdoc/options' # load our parser require 'puppet/util/rdoc/parser' r = RDoc::RDoc.new # specify our own format & where to output options = [ "--fmt", "puppet", "--quiet", "--exclude", "/modules/[^/]*/spec/.*$", "--exclude", "/modules/[^/]*/files/.*$", "--exclude", "/modules/[^/]*/tests/.*$", "--exclude", "/modules/[^/]*/templates/.*$", "--op", outputdir ] options << "--force-update" options += [ "--charset", charset] if charset # Rdoc root default is Dir.pwd, but the win32-dir gem monkey patches Dir.pwd # replacing Ruby's normal / with \. When RDoc generates relative paths it # uses relative_path_from that will generate errors when the slashes don't # properly match. This is a workaround for that issue. if Puppet.features.microsoft_windows? && RDoc::VERSION !~ /^[0-3]\./ options += [ "--root", Dir.pwd.gsub(/\\/, '/')] end options += files # launch the documentation process r.document(options) end # launch an output to console manifest doc def manifestdoc(files) raise _("RDOC SUPPORT FOR MANIFEST HAS BEEN REMOVED - See PUP-3638") end # Outputs to the console the documentation # of a manifest def output(file, ast) raise _("RDOC SUPPORT FOR MANIFEST HAS BEEN REMOVED - See PUP-3638") end def output_astnode_doc(ast) raise _("RDOC SUPPORT FOR MANIFEST HAS BEEN REMOVED - See PUP-3638") end def output_resource_doc(code) raise _("RDOC SUPPORT FOR MANIFEST HAS BEEN REMOVED - See PUP-3638") end end puppet-5.5.10/lib/puppet/util/reference.rb0000644005276200011600000000542513417161722020344 0ustar jenkinsjenkinsrequire 'puppet/util/instance_loader' require 'puppet/util/methodhelper' require 'fileutils' # Manage Reference Documentation. class Puppet::Util::Reference include Puppet::Util include Puppet::Util::MethodHelper include Puppet::Util::Docs extend Puppet::Util::InstanceLoader instance_load(:reference, 'puppet/reference') def self.modes %w{pdf text} end def self.newreference(name, options = {}, &block) ref = self.new(name, options, &block) instance_hash(:reference)[name.intern] = ref ref end def self.page(*sections) depth = 4 # Use the minimum depth sections.each do |name| section = reference(name) or raise _("Could not find section %{name}") % { name: name } depth = section.depth if section.depth < depth end end def self.pdf(text) puts _("creating pdf") rst2latex = which('rst2latex') || which('rst2latex.py') || raise(_("Could not find rst2latex")) cmd = %{#{rst2latex} /tmp/puppetdoc.txt > /tmp/puppetdoc.tex} Puppet::Util.replace_file("/tmp/puppetdoc.txt") {|f| f.puts text } # There used to be an attempt to use secure_open / replace_file to secure # the target, too, but that did nothing: the race was still here. We can # get exactly the same benefit from running this effort: Puppet::FileSystem.unlink('/tmp/puppetdoc.tex') rescue nil output = %x{#{cmd}} unless $CHILD_STATUS == 0 $stderr.puts _("rst2latex failed") $stderr.puts output exit(1) end $stderr.puts output # Now convert to pdf Dir.chdir("/tmp") do %x{texi2pdf puppetdoc.tex >/dev/null 2>/dev/null} end end def self.references instance_loader(:reference).loadall loaded_instances(:reference).sort { |a,b| a.to_s <=> b.to_s } end attr_accessor :page, :depth, :header, :title, :dynamic attr_writer :doc def doc if defined?(@doc) return "#{@name} - #{@doc}" else return @title end end def dynamic? self.dynamic end def initialize(name, options = {}, &block) @name = name set_options(options) meta_def(:generate, &block) # Now handle the defaults @title ||= _("%{name} Reference") % { name: @name.to_s.capitalize } @page ||= @title.gsub(/\s+/, '') @depth ||= 2 @header ||= "" end # Indent every line in the chunk except those which begin with '..'. def indent(text, tab) text.gsub(/(^|\A)/, tab).gsub(/^ +\.\./, "..") end def option(name, value) ":#{name.to_s.capitalize}: #{value}\n" end def text puts output end def to_markdown(withcontents = true) # First the header text = markdown_header(@title, 1) text << _("\n\n**This page is autogenerated; any changes will get overwritten**\n\n") text << @header text << generate text end end puppet-5.5.10/lib/puppet/util/rubygems.rb0000644005276200011600000000343213417161722020237 0ustar jenkinsjenkinsrequire 'puppet/util' module Puppet::Util::RubyGems # Base/factory class for rubygems source. These classes introspec into # rubygems to in order to list where the rubygems system will look for files # to load. class Source class << self # @api private def has_rubygems? # Gems are not actually available when Bundler is loaded, even # though the Gem constant is defined. This is because Bundler # loads in rubygems, but then removes the custom require that # rubygems installs. So when Bundler is around we have to act # as though rubygems is not, e.g. we shouldn't be able to load # a gem that Bundler doesn't want us to see. defined? ::Gem and not defined? ::Bundler end # @api private def source if has_rubygems? Gem::Specification.respond_to?(:latest_specs) ? Gems18Source : OldGemsSource else NoGemsSource end end def new(*args) object = source.allocate object.send(:initialize, *args) object end end end # For RubyGems >= 1.8.0 # @api private class Gems18Source < Source def directories # `require 'mygem'` will consider and potentially load # prerelease gems, so we need to match that behavior. Gem::Specification.latest_specs(true).collect do |spec| File.join(spec.full_gem_path, 'lib') end end def clear_paths Gem.clear_paths end end # RubyGems < 1.8.0 # @api private class OldGemsSource < Source def directories @paths ||= Gem.latest_load_paths end def clear_paths Gem.clear_paths end end # @api private class NoGemsSource < Source def directories [] end def clear_paths; end end end puppet-5.5.10/lib/puppet/util/run_mode.rb0000644005276200011600000000513313417161722020212 0ustar jenkinsjenkinsrequire 'etc' module Puppet module Util class RunMode def initialize(name) @name = name.to_sym end attr :name def self.[](name) @run_modes ||= {} if Puppet.features.microsoft_windows? @run_modes[name] ||= WindowsRunMode.new(name) else @run_modes[name] ||= UnixRunMode.new(name) end end def master? name == :master end def agent? name == :agent end def user? name == :user end def run_dir RunMode[name].run_dir end def log_dir RunMode[name].log_dir end private ## # select the system or the user directory depending on the context of # this process. The most common use is determining filesystem path # values for confdir and vardir. The intended semantics are: # {https://projects.puppetlabs.com/issues/16637 #16637} for Puppet 3.x # # @todo this code duplicates {Puppet::Settings#which\_configuration\_file} # as described in {https://projects.puppetlabs.com/issues/16637 #16637} def which_dir( system, user ) if Puppet.features.root? File.expand_path(system) else File.expand_path(user) end end end class UnixRunMode < RunMode def conf_dir which_dir("/etc/puppetlabs/puppet", "~/.puppetlabs/etc/puppet") end def code_dir which_dir("/etc/puppetlabs/code", "~/.puppetlabs/etc/code") end def var_dir which_dir("/opt/puppetlabs/puppet/cache", "~/.puppetlabs/opt/puppet/cache") end def run_dir which_dir("/var/run/puppetlabs", "~/.puppetlabs/var/run") end def log_dir which_dir("/var/log/puppetlabs/puppet", "~/.puppetlabs/var/log") end end class WindowsRunMode < RunMode def conf_dir which_dir(File.join(windows_common_base("puppet/etc")), "~/.puppetlabs/etc/puppet") end def code_dir which_dir(File.join(windows_common_base("code")), "~/.puppetlabs/etc/code") end def var_dir which_dir(File.join(windows_common_base("puppet/cache")), "~/.puppetlabs/opt/puppet/cache") end def run_dir which_dir(File.join(windows_common_base("puppet/var/run")), "~/.puppetlabs/var/run") end def log_dir which_dir(File.join(windows_common_base("puppet/var/log")), "~/.puppetlabs/var/log") end private def windows_common_base(*extra) [Dir::COMMON_APPDATA, "PuppetLabs"] + extra end end end end puppet-5.5.10/lib/puppet/util/ssl.rb0000644005276200011600000000250413417161722017202 0ustar jenkinsjenkins## # SSL is a private module with class methods that help work with x.509 # subjects. # # @api private module Puppet::Util::SSL @@dn_parsers = nil @@no_name = nil # Given a DN string, parse it into an OpenSSL certificate subject. This # method will flexibly handle both OpenSSL and RFC2253 formats, as given by # nginx and Apache, respectively. # # @param [String] dn the x.509 Distinguished Name (DN) string. # # @return [OpenSSL::X509::Name] the certificate subject def self.subject_from_dn(dn) if is_possibly_valid_dn?(dn) parsers = @@dn_parsers ||= [ OpenSSL::X509::Name.method(:parse_rfc2253), OpenSSL::X509::Name.method(:parse_openssl) ] parsers.each do |parser| begin return parser.call(dn) rescue OpenSSL::X509::NameError end end end @@no_name ||= OpenSSL::X509::Name.new end ## # cn_from_subject extracts the CN from the given OpenSSL certificate # subject. # # @api private # # @param [OpenSSL::X509::Name] subject the subject to extract the CN field from # # @return [String, nil] the CN, or nil if not found def self.cn_from_subject(subject) if subject.respond_to? :to_a (subject.to_a.assoc('CN') || [])[1] end end def self.is_possibly_valid_dn?(dn) dn =~ /=/ end end puppet-5.5.10/lib/puppet/util/storage.rb0000644005276200011600000000520413417161722020045 0ustar jenkinsjenkinsrequire 'yaml' require 'sync' require 'singleton' require 'puppet/util/yaml' # a class for storing state class Puppet::Util::Storage include Singleton include Puppet::Util def self.state @@state end def initialize self.class.load end # Return a hash that will be stored to disk. It's worth noting # here that we use the object's full path, not just the name/type # combination. At the least, this is useful for those non-isomorphic # types like exec, but it also means that if an object changes locations # in the configuration it will lose its cache. def self.cache(object) if object.is_a?(Symbol) name = object else name = object.to_s end @@state[name] ||= {} end def self.clear @@state.clear end def self.init @@state = {} end self.init def self.load Puppet.settings.use(:main) unless FileTest.directory?(Puppet[:statedir]) filename = Puppet[:statefile] unless Puppet::FileSystem.exist?(filename) self.init if @@state.nil? return end unless File.file?(filename) Puppet.warning(_("Checksumfile %{filename} is not a file, ignoring") % { filename: filename }) return end Puppet::Util.benchmark(:debug, "Loaded state in %{seconds} seconds") do begin @@state = Puppet::Util::Yaml.load_file(filename) rescue Puppet::Util::Yaml::YamlLoadError => detail Puppet.err _("Checksumfile %{filename} is corrupt (%{detail}); replacing") % { filename: filename, detail: detail } begin File.rename(filename, filename + ".bad") rescue raise Puppet::Error, _("Could not rename corrupt %{filename}; remove manually") % { filename: filename }, detail.backtrace end end end unless @@state.is_a?(Hash) Puppet.err _("State got corrupted") self.init end end def self.stateinspect @@state.inspect end def self.store Puppet.debug "Storing state" Puppet.info _("Creating state file %{file}") % { file: Puppet[:statefile] } unless Puppet::FileSystem.exist?(Puppet[:statefile]) if Puppet[:statettl] == 0 || Puppet[:statettl] == Float::INFINITY Puppet.debug "Not pruning old state cache entries" else Puppet::Util.benchmark(:debug, "Pruned old state cache entries in %{seconds} seconds") do ttl_cutoff = Time.now - Puppet[:statettl] @@state.reject! do |k,v| @@state[k][:checked] && @@state[k][:checked] < ttl_cutoff end end end Puppet::Util.benchmark(:debug, "Stored state in %{seconds} seconds") do Puppet::Util::Yaml.dump(@@state, Puppet[:statefile]) end end end puppet-5.5.10/lib/puppet/util/suidmanager.rb0000644005276200011600000001266213417161722020706 0ustar jenkinsjenkinsrequire 'facter' require 'puppet/util/warnings' require 'forwardable' require 'etc' module Puppet::Util::SUIDManager include Puppet::Util::Warnings extend Forwardable # Note groups= is handled specially due to a bug in OS X 10.6, 10.7, # and probably upcoming releases... to_delegate_to_process = [ :euid=, :euid, :egid=, :egid, :uid=, :uid, :gid=, :gid, :groups ] to_delegate_to_process.each do |method| def_delegator Process, method module_function method end def osx_maj_ver return @osx_maj_ver unless @osx_maj_ver.nil? @osx_maj_ver = Facter.value('macosx_productversion_major') || false end module_function :osx_maj_ver def groups=(grouplist) begin return Process.groups = grouplist rescue Errno::EINVAL => e #We catch Errno::EINVAL as some operating systems (OS X in particular) can # cause troubles when using Process#groups= to change *this* user / process # list of supplementary groups membership. This is done via Ruby's function # "static VALUE proc_setgroups(VALUE obj, VALUE ary)" which is effectively # a wrapper for "int setgroups(size_t size, const gid_t *list)" (part of SVr4 # and 4.3BSD but not in POSIX.1-2001) that fails and sets errno to EINVAL. # # This does not appear to be a problem with Ruby but rather an issue on the # operating system side. Therefore we catch the exception and look whether # we run under OS X or not -- if so, then we acknowledge the problem and # re-throw the exception otherwise. if osx_maj_ver and not osx_maj_ver.empty? return true else raise e end end end module_function :groups= def self.root? return Process.uid == 0 unless Puppet.features.microsoft_windows? require 'puppet/util/windows/user' Puppet::Util::Windows::User.admin? end # Methods to handle changing uid/gid of the running process. In general, # these will noop or fail on Windows, and require root to change to anything # but the current uid/gid (which is a noop). # Runs block setting euid and egid if provided then restoring original ids. # If running on Windows or without root, the block will be run with the # current euid/egid. def asuser(new_uid=nil, new_gid=nil) return yield if Puppet.features.microsoft_windows? return yield unless root? return yield unless new_uid or new_gid old_euid, old_egid = self.euid, self.egid begin change_privileges(new_uid, new_gid, false) yield ensure change_privileges(new_uid ? old_euid : nil, old_egid, false) end end module_function :asuser # If `permanently` is set, will permanently change the uid/gid of the # process. If not, it will only set the euid/egid. If only uid is supplied, # the primary group of the supplied gid will be used. If only gid is # supplied, only gid will be changed. This method will fail if used on # Windows. def change_privileges(uid=nil, gid=nil, permanently=false) return unless uid or gid unless gid uid = convert_xid(:uid, uid) gid = Etc.getpwuid(uid).gid end change_group(gid, permanently) change_user(uid, permanently) if uid end module_function :change_privileges # Changes the egid of the process if `permanently` is not set, otherwise # changes gid. This method will fail if used on Windows, or attempting to # change to a different gid without root. def change_group(group, permanently=false) gid = convert_xid(:gid, group) raise Puppet::Error, _("No such group %{group}") % { group: group } unless gid return if Process.egid == gid if permanently Process::GID.change_privilege(gid) else Process.egid = gid end end module_function :change_group # As change_group, but operates on uids. If changing user permanently, # supplementary groups will be set the to default groups for the new uid. def change_user(user, permanently=false) uid = convert_xid(:uid, user) raise Puppet::Error, _("No such user %{user}") % { user: user } unless uid return if Process.euid == uid if permanently # If changing uid, we must be root. So initgroups first here. initgroups(uid) Process::UID.change_privilege(uid) else # We must be root to initgroups, so initgroups before dropping euid if # we're root, otherwise elevate euid before initgroups. # change euid (to root) first. if Process.euid == 0 initgroups(uid) Process.euid = uid else Process.euid = uid initgroups(uid) end end end module_function :change_user # Make sure the passed argument is a number. def convert_xid(type, id) return id if id.kind_of? Integer map = {:gid => :group, :uid => :user} raise ArgumentError, _("Invalid id type %{type}") % { type: type } unless map.include?(type) ret = Puppet::Util.send(type, id) if ret == nil raise Puppet::Error, _("Invalid %{klass}: %{id}") % { klass: map[type], id: id } end ret end module_function :convert_xid # Initialize primary and supplemental groups to those of the target user. We # take the UID and manually look up their details in the system database, # including username and primary group. This method will fail on Windows, or # if used without root to initgroups of another user. def initgroups(uid) pwent = Etc.getpwuid(uid) Process.initgroups(pwent.name, pwent.gid) end module_function :initgroups end puppet-5.5.10/lib/puppet/util/tag_set.rb0000644005276200011600000000067613417161722020037 0ustar jenkinsjenkinsrequire 'set' require 'puppet/network/format_support' class Puppet::Util::TagSet < Set include Puppet::Network::FormatSupport def self.from_yaml(yaml) self.new(YAML.load(yaml)) end def to_yaml @hash.keys.to_yaml end def self.from_data_hash(data) self.new(data) end # TODO: A method named #to_data_hash should not return an array def to_data_hash to_a end def join(*args) to_a.join(*args) end end puppet-5.5.10/lib/puppet/util/tagging.rb0000644005276200011600000000716413417161722020030 0ustar jenkinsjenkinsrequire 'puppet/util/tag_set' module Puppet::Util::Tagging ValidTagRegex = /\A[[:alnum:]_][[:alnum:]_:.-]*\Z/u # Add a tag to the current tag set. # When a tag set is used for a scope, these tags will be added to all of # the objects contained in this scope when the objects are finished. # def tag(*ary) @tags ||= new_tags ary.flatten.each do |tag| name = tag.to_s.downcase # Add the tag before testing if it's valid since this means that # we never need to test the same valid tag twice. This speeds things # up since we get a lot of duplicates and rarely fail on bad tags if @tags.add?(name) # not seen before, so now we test if it is valid if valid_tag?(name) if split_qualified_tags? # avoid adding twice by first testing if the string contains '::' @tags.merge(name.split('::')) if name.include?('::') end else @tags.delete(name) fail(Puppet::ParseError, _("Invalid tag '%{name}'") % { name: name }) end end end end # Add a name to the current tag set. Silently ignore names that does not # represent valid tags. # # Use this method instead of doing this: # # tag(name) if is_valid?(name) # # since that results in testing the same string twice # def tag_if_valid(name) if name.is_a?(String) && valid_tag?(name) name = name.downcase @tags ||= new_tags if @tags.add?(name) && name.include?('::') @tags.merge(name.split('::')) end end end # Answers if this resource is tagged with at least one of the given tags. # # The given tags are converted to downcased strings before the match is performed. # # @param *tags [String] splat of tags to look for # @return [Boolean] true if this instance is tagged with at least one of the provided tags # def tagged?(*tags) raw_tagged?(tags.collect {|t| t.to_s.downcase}) end # Answers if this resource is tagged with at least one of the tags given in downcased string form. # # The method is a faster variant of the tagged? method that does no conversion of its # arguments. # # @param tag_array [Array[String]] array of tags to look for # @return [Boolean] true if this instance is tagged with at least one of the provided tags # def raw_tagged?(tag_array) my_tags = self.tags !tag_array.index { |t| my_tags.include?(t) }.nil? end # Only use this method when copying known tags from one Tagging instance to another def set_tags(tag_source) @tags = tag_source.tags end # Return a copy of the tag list, so someone can't ask for our tags # and then modify them. def tags @tags ||= new_tags @tags.dup end # Merge tags from a tagged instance with no attempts to split, downcase # or verify the tags def merge_tags_from(tag_source) @tags ||= new_tags tag_source.merge_into(@tags) end # Merge the tags of this instance into the provide TagSet def merge_into(tag_set) tag_set.merge(@tags) unless @tags.nil? end def tags=(tags) @tags = new_tags return if tags.nil? tags = tags.strip.split(/\s*,\s*/) if tags.is_a?(String) tag(*tags) end def valid_tag?(maybe_tag) begin tag_enc = maybe_tag.encoding if tag_enc == Encoding::UTF_8 || tag_enc == Encoding::ASCII maybe_tag =~ ValidTagRegex else maybe_tag.encode(Encoding::UTF_8) =~ ValidTagRegex end rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError false end end private def split_qualified_tags? true end def new_tags Puppet::Util::TagSet.new end end puppet-5.5.10/lib/puppet/util/windows.rb0000644005276200011600000000220213417161722020066 0ustar jenkinsjenkinsmodule Puppet::Util::Windows module ADSI class ADSIObject; end class User < ADSIObject; end class UserProfile; end class Group < ADSIObject; end end module File; end module Registry end module SID class Principal; end end class EventLog; end if Puppet::Util::Platform.windows? # these reference platform specific gems require 'puppet/util/windows/api_types' require 'puppet/util/windows/string' require 'puppet/util/windows/error' require 'puppet/util/windows/com' require 'puppet/util/windows/sid' require 'puppet/util/windows/principal' require 'puppet/util/windows/file' require 'puppet/util/windows/security' require 'puppet/util/windows/user' require 'puppet/util/windows/process' require 'puppet/util/windows/root_certs' require 'puppet/util/windows/access_control_entry' require 'puppet/util/windows/access_control_list' require 'puppet/util/windows/security_descriptor' require 'puppet/util/windows/adsi' require 'puppet/util/windows/registry' require 'puppet/util/windows/eventlog' require 'puppet/util/windows/service' end end puppet-5.5.10/lib/puppet/util/yaml.rb0000644005276200011600000000162713417161722017350 0ustar jenkinsjenkinsrequire 'yaml' module Puppet::Util::Yaml if defined?(::Psych::SyntaxError) YamlLoadExceptions = [::StandardError, ::Psych::SyntaxError] else YamlLoadExceptions = [::StandardError] end class YamlLoadError < Puppet::Error; end def self.load_file(filename, default_value = false, strip_classes = false) if(strip_classes) then data = YAML::parse_file(filename) data.root.each do |o| if o.respond_to?(:tag=) and o.tag != nil and o.tag.start_with?("!ruby") o.tag = nil end end data.to_ruby || default_value else yaml = YAML.load_file(filename) yaml || default_value end rescue *YamlLoadExceptions => detail raise YamlLoadError.new(detail.message, detail) end def self.dump(structure, filename) Puppet::Util.replace_file(filename, 0660) do |fh| YAML.dump(structure, fh) end end end puppet-5.5.10/lib/puppet/vendor.rb0000644005276200011600000000373613417161721016730 0ustar jenkinsjenkinsmodule Puppet # Simple module to manage vendored code. # # To vendor a library: # # * Download its whole git repo or untar into `lib/puppet/vendor/` # * Create a vendor/puppetload_libraryname.rb file to add its libdir into the $:. # (Look at existing load_xxx files, they should all follow the same pattern). # * Add a /PUPPET_README.md file describing what the library is for # and where it comes from. # * To load the vendored lib upfront, add a `require ''`line to # `vendor/require_vendored.rb`. # * To load the vendored lib on demand, add a comment to `vendor/require_vendored.rb` # to make it clear it should not be loaded upfront. # # At runtime, the #load_vendored method should be called. It will ensure # all vendored libraries are added to the global `$:` path, and # will then call execute the up-front loading specified in `vendor/require_vendored.rb`. # # The intention is to not change vendored libraries and to eventually # make adding them in optional so that distros can simply adjust their # packaging to exclude this directory and the various load_xxx.rb scripts # if they wish to install these gems as native packages. # class Vendor class << self # @api private def vendor_dir File.join([File.dirname(File.expand_path(__FILE__)), "vendor"]) end # @api private def load_entry(entry) Puppet.debug("Loading vendored #{$1}") load "#{vendor_dir}/#{entry}" end # @api private def require_libs require 'puppet/vendor/require_vendored' end # Configures the path for all vendored libraries and loads required libraries. # (This is the entry point for loading vendored libraries). # def load_vendored Dir.entries(vendor_dir).each do |entry| if entry.match(/load_(\w+?)\.rb$/) load_entry entry end end require_libs end end end end puppet-5.5.10/lib/puppet/vendor/0000755005276200011600000000000013417162176016377 5ustar jenkinsjenkinspuppet-5.5.10/lib/puppet/vendor/deep_merge/0000755005276200011600000000000013417162176020473 5ustar jenkinsjenkinspuppet-5.5.10/lib/puppet/vendor/deep_merge/CHANGELOG0000644005276200011600000000205313417161721021700 0ustar jenkinsjenkins2014-01-21 Dan DeLeo * Update knockout behavior to better handle nil (b7de40b5) 2011-08-15 Dan DeLeo * Document how to use w/ Rails 3 via Bundler 2011-07-28 Dan DeLeo * Use a plain ol' gemspec and Rakefile for gem creation * Ship version 1.0.0 2011-05-23 Joe Van Dyk * Added Changelog 2011-05-18 Joe Van Dyk * Merging empty strings should work if String#blank? is defined. * Use unix line endings * Removing extra whitespace 2010-01-11 Dan DeLeo * fix boolean merging according to mdkent's patch explicitly test for nils w/ #nil? instead of negating. Thanks mdkent! 2009-12-25 Dan DeLeo * miscellaneous cleanup * make rails/active_support compat optional * add jeweler rake task for gemability 2009-12-24 Dan DeLeo * VERSION: Version bump to 0.0.1 * VERSION: Version bump to 0.0.0 2009-11-06 Jonathan Weiss * import puppet-5.5.10/lib/puppet/vendor/deep_merge/Gemfile0000644005276200011600000000004713417161721021762 0ustar jenkinsjenkinssource "https://rubygems.org" gemspec puppet-5.5.10/lib/puppet/vendor/deep_merge/LICENSE0000644005276200011600000000211313417161721021470 0ustar jenkinsjenkinsThe MIT License (MIT) Copyright (c) 2008-2104 Steve Midgley, Daniel DeLeo Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. puppet-5.5.10/lib/puppet/vendor/deep_merge/PUPPET_README.md0000644005276200011600000000032613417161721023043 0ustar jenkinsjenkinsDeep_merge - Recursive Merging for Ruby Hashes ============================================= Deep_merge 1.0.0+ Copied from https://github.com/danielsdeleo/deep_merge/tree/f9df6fdb0d0090318e8015814e68e5ca2973b493 puppet-5.5.10/lib/puppet/vendor/deep_merge/README.md0000644005276200011600000001112113417161721021741 0ustar jenkinsjenkinsDeepMerge Overview ================== Deep Merge is a simple set of utility functions for Hash. It permits you to merge elements inside a hash together recursively. The manner by which it does this is somewhat arbitrary (since there is no defining standard for this) but it should end up being pretty intuitive and do what you expect. You can learn a lot more about this by reading the test file. It's pretty well documented and has many examples of various merges from very simple to pretty complex. The primary need that caused me to write this library is the merging of elements coming from HTTP parameters and related stored parameters in session. This lets a user build up a set of parameters over time, modifying individual items. Deep Merge Core Documentation ============================= `deep_merge!` method permits merging of arbitrary child elements. The two top level elements must be hashes. These hashes can contain unlimited (to stack limit) levels of child elements. These child elements to not have to be of the same types. Where child elements are of the same type, `deep_merge` will attempt to merge them together. Where child elements are not of the same type, `deep_merge` will skip or optionally overwrite the destination element with the contents of the source element at that level. So if you have two hashes like this: source = {:x => [1,2,3], :y => 2} dest = {:x => [4,5,'6'], :y => [7,8,9]} dest.deep_merge!(source) Results: {:x => [1,2,3,4,5,'6'], :y => 2} By default, `deep_merge!` will overwrite any unmergeables and merge everything else. To avoid this, use `deep_merge` (no bang/exclamation mark) Options ------- Options are specified in the last parameter passed, which should be in hash format: hash.deep_merge!({:x => [1,2]}, {:knockout_prefix => '--'}) :preserve_unmergeables DEFAULT: false Set to true to skip any unmergeable elements from source :knockout_prefix DEFAULT: nil Set to string value to signify prefix which deletes elements from existing element :sort_merged_arrays DEFAULT: false Set to true to sort all arrays that are merged together :unpack_arrays DEFAULT: nil Set to string value to run "Array::join" then "String::split" against all arrays :merge_hash_arrays DEFAULT: false Set to true to merge hashes within arrays :merge_debug DEFAULT: false Set to true to get console output of merge process for debugging Selected Options Details ------------------------ **:knockout_prefix** The purpose of this is to provide a way to remove elements from existing Hash by specifying them in a special way in incoming hash source = {:x => ['--1', '2']} dest = {:x => ['1', '3']} dest.ko_deep_merge!(source) Results: {:x => ['2','3']} Additionally, if the knockout_prefix is passed alone as a string, it will cause the entire element to be removed: source = {:x => '--'} dest = {:x => [1,2,3]} dest.ko_deep_merge!(source) Results: {:x => ""} **:unpack_arrays** The purpose of this is to permit compound elements to be passed in as strings and to be converted into discrete array elements irsource = {:x => ['1,2,3', '4']} dest = {:x => ['5','6','7,8']} dest.deep_merge!(source, {:unpack_arrays => ','}) Results: {:x => ['1','2','3','4','5','6','7','8'} Why: If receiving data from an HTML form, this makes it easy for a checkbox to pass multiple values from within a single HTML element **:merge_hash_arrays** merge hashes within arrays source = {:x => [{:y => 1}]} dest = {:x => [{:z => 2}]} dest.deep_merge!(source, {:merge_hash_arrays => true}) Results: {:x => [{:y => 1, :z => 2}]} There are many tests for this library - and you can learn more about the features and usages of deep_merge! by just browsing the test examples. Using deep_merge in Rails ========================= To avoid conflict with ActiveSupport, load deep_merge via: require 'deep_merge/rails_compat' In a Gemfile: gem "deep_merge", :require => 'deep_merge/rails_compat' The deep_merge methods will then be defined as Hash#deeper_merge Hash#deeper_merge! Hash#ko_deeper_merge! Simple Example Code =================== require 'deep_merge' x = {:x => [3,4,5]} y = {:x => [1,2,3]} y.deep_merge!(x) # results: y = {:x => [1,2,3,4,5]} Availability ============ `deep_merge` was written by Steve Midgley, and is now maintained by Daniel DeLeo. The official home of `deep_merge` on the internet is now https://github.com/danielsdeleo/deep_merge Copyright (c) 2008 Steve Midgley, released under the MIT license puppet-5.5.10/lib/puppet/vendor/deep_merge/Rakefile0000644005276200011600000000052613417161721022136 0ustar jenkinsjenkinsrequire 'rake/testtask' Rake::TestTask.new do |t| t.libs << FileList['lib/**.rb'] t.test_files = FileList['test/test*.rb'] end task :default => :test begin require 'rubygems' require 'rubygems/package_task' gemspec = eval(IO.read('deep_merge.gemspec')) Gem::PackageTask.new(gemspec).define rescue LoadError #okay, then end puppet-5.5.10/lib/puppet/vendor/deep_merge/deep_merge.gemspec0000644005276200011600000000147513417161721024136 0ustar jenkinsjenkins# -*- encoding: utf-8 -*- Gem::Specification.new do |s| s.name = %q{deep_merge} s.version = "1.0.1" s.authors = ["Steve Midgley"] s.date = %q{2011-07-28} s.description = %q{Recursively merge hashes. Now works with Ruby 1.9 and ActiveSupport} s.email = %q{dan@kallistec.com} s.license = 'MIT' s.extra_rdoc_files = [ "README.md" ] s.files = [ "CHANGELOG", "README.md", "Rakefile", "lib/deep_merge.rb", "lib/deep_merge/core.rb", "lib/deep_merge/deep_merge_hash.rb", "lib/deep_merge/rails_compat.rb", "test/test_deep_merge.rb" ] s.homepage = %q{https://github.com/danielsdeleo/deep_merge} s.require_paths = ["lib"] s.summary = %q{Merge Deeply Nested Hashes} s.test_files = [ "test/test_deep_merge.rb" ] s.add_development_dependency "rake", "~> 10.1" end puppet-5.5.10/lib/puppet/vendor/deep_merge/lib/0000755005276200011600000000000013417162176021241 5ustar jenkinsjenkinspuppet-5.5.10/lib/puppet/vendor/deep_merge/lib/deep_merge.rb0000644005276200011600000000007713417161721023661 0ustar jenkinsjenkinsrequire 'deep_merge/core' require 'deep_merge/deep_merge_hash' puppet-5.5.10/lib/puppet/vendor/deep_merge/lib/deep_merge/0000755005276200011600000000000013417162176023335 5ustar jenkinsjenkinspuppet-5.5.10/lib/puppet/vendor/deep_merge/lib/deep_merge/core.rb0000644005276200011600000002365013417161721024613 0ustar jenkinsjenkinsmodule DeepMerge class InvalidParameter < StandardError; end DEFAULT_FIELD_KNOCKOUT_PREFIX = '--' # Deep Merge core documentation. # deep_merge! method permits merging of arbitrary child elements. The two top level # elements must be hashes. These hashes can contain unlimited (to stack limit) levels # of child elements. These child elements to not have to be of the same types. # Where child elements are of the same type, deep_merge will attempt to merge them together. # Where child elements are not of the same type, deep_merge will skip or optionally overwrite # the destination element with the contents of the source element at that level. # So if you have two hashes like this: # source = {:x => [1,2,3], :y => 2} # dest = {:x => [4,5,'6'], :y => [7,8,9]} # dest.deep_merge!(source) # Results: {:x => [1,2,3,4,5,'6'], :y => 2} # By default, "deep_merge!" will overwrite any unmergeables and merge everything else. # To avoid this, use "deep_merge" (no bang/exclamation mark) # # Options: # Options are specified in the last parameter passed, which should be in hash format: # hash.deep_merge!({:x => [1,2]}, {:knockout_prefix => '--'}) # :preserve_unmergeables DEFAULT: false # Set to true to skip any unmergeable elements from source # :knockout_prefix DEFAULT: nil # Set to string value to signify prefix which deletes elements from existing element # :sort_merged_arrays DEFAULT: false # Set to true to sort all arrays that are merged together # :unpack_arrays DEFAULT: nil # Set to string value to run "Array::join" then "String::split" against all arrays # :merge_hash_arrays DEFAULT: false # Set to true to merge hashes within arrays # :merge_debug DEFAULT: false # Set to true to get console output of merge process for debugging # # Selected Options Details: # :knockout_prefix => The purpose of this is to provide a way to remove elements # from existing Hash by specifying them in a special way in incoming hash # source = {:x => ['--1', '2']} # dest = {:x => ['1', '3']} # dest.ko_deep_merge!(source) # Results: {:x => ['2','3']} # Additionally, if the knockout_prefix is passed alone as a string, it will cause # the entire element to be removed: # source = {:x => '--'} # dest = {:x => [1,2,3]} # dest.ko_deep_merge!(source) # Results: {:x => ""} # :unpack_arrays => The purpose of this is to permit compound elements to be passed # in as strings and to be converted into discrete array elements # irsource = {:x => ['1,2,3', '4']} # dest = {:x => ['5','6','7,8']} # dest.deep_merge!(source, {:unpack_arrays => ','}) # Results: {:x => ['1','2','3','4','5','6','7','8'} # Why: If receiving data from an HTML form, this makes it easy for a checkbox # to pass multiple values from within a single HTML element # # :merge_hash_arrays => merge hashes within arrays # source = {:x => [{:y => 1}]} # dest = {:x => [{:z => 2}]} # dest.deep_merge!(source, {:merge_hash_arrays => true}) # Results: {:x => [{:y => 1, :z => 2}]} # # There are many tests for this library - and you can learn more about the features # and usages of deep_merge! by just browsing the test examples def self.deep_merge!(source, dest, options = {}) # turn on this line for stdout debugging text merge_debug = options[:merge_debug] || false overwrite_unmergeable = !options[:preserve_unmergeables] knockout_prefix = options[:knockout_prefix] || nil raise InvalidParameter, "knockout_prefix cannot be an empty string in deep_merge!" if knockout_prefix == "" raise InvalidParameter, "overwrite_unmergeable must be true if knockout_prefix is specified in deep_merge!" if knockout_prefix && !overwrite_unmergeable # if present: we will split and join arrays on this char before merging array_split_char = options[:unpack_arrays] || false # request that we sort together any arrays when they are merged sort_merged_arrays = options[:sort_merged_arrays] || false # request that arrays of hashes are merged together merge_hash_arrays = options[:merge_hash_arrays] || false di = options[:debug_indent] || '' # do nothing if source is nil return dest if source.nil? # if dest doesn't exist, then simply copy source to it if !(dest) && overwrite_unmergeable dest = source; return dest end puts "#{di}Source class: #{source.class.inspect} :: Dest class: #{dest.class.inspect}" if merge_debug if source.kind_of?(Hash) puts "#{di}Hashes: #{source.inspect} :: #{dest.inspect}" if merge_debug source.each do |src_key, src_value| if dest.kind_of?(Hash) puts "#{di} looping: #{src_key.inspect} => #{src_value.inspect} :: #{dest.inspect}" if merge_debug if dest[src_key] puts "#{di} ==>merging: #{src_key.inspect} => #{src_value.inspect} :: #{dest[src_key].inspect}" if merge_debug dest[src_key] = deep_merge!(src_value, dest[src_key], options.merge(:debug_indent => di + ' ')) else # dest[src_key] doesn't exist so we want to create and overwrite it (but we do this via deep_merge!) puts "#{di} ==>merging over: #{src_key.inspect} => #{src_value.inspect}" if merge_debug # note: we rescue here b/c some classes respond to "dup" but don't implement it (Numeric, TrueClass, FalseClass, NilClass among maybe others) begin src_dup = src_value.dup # we dup src_value if possible because we're going to merge into it (since dest is empty) rescue TypeError src_dup = src_value end dest[src_key] = deep_merge!(src_value, src_dup, options.merge(:debug_indent => di + ' ')) end else # dest isn't a hash, so we overwrite it completely (if permitted) if overwrite_unmergeable puts "#{di} overwriting dest: #{src_key.inspect} => #{src_value.inspect} -over-> #{dest.inspect}" if merge_debug dest = overwrite_unmergeables(source, dest, options) end end end elsif source.kind_of?(Array) puts "#{di}Arrays: #{source.inspect} :: #{dest.inspect}" if merge_debug # if we are instructed, join/split any source arrays before processing if array_split_char puts "#{di} split/join on source: #{source.inspect}" if merge_debug source = source.join(array_split_char).split(array_split_char) if dest.kind_of?(Array) dest = dest.join(array_split_char).split(array_split_char) end end # if there's a naked knockout_prefix in source, that means we are to truncate dest if knockout_prefix && source.index(knockout_prefix) dest = clear_or_nil(dest); source.delete(knockout_prefix) end if dest.kind_of?(Array) if knockout_prefix print "#{di} knocking out: " if merge_debug # remove knockout prefix items from both source and dest source.delete_if do |ko_item| retval = false item = ko_item.respond_to?(:gsub) ? ko_item.gsub(%r{^#{knockout_prefix}}, "") : ko_item if item != ko_item print "#{ko_item} - " if merge_debug dest.delete(item) dest.delete(ko_item) retval = true end retval end puts if merge_debug end puts "#{di} merging arrays: #{source.inspect} :: #{dest.inspect}" if merge_debug source_all_hashes = source.all? { |i| i.kind_of?(Hash) } dest_all_hashes = dest.all? { |i| i.kind_of?(Hash) } if merge_hash_arrays && source_all_hashes && dest_all_hashes # merge hashes in lists list = [] dest.each_index do |i| list[i] = deep_merge!(source[i] || {}, dest[i], options.merge(:debug_indent => di + ' ')) end list += source[dest.count..-1] if source.count > dest.count dest = list else dest = dest | source end dest.sort! if sort_merged_arrays elsif overwrite_unmergeable puts "#{di} overwriting dest: #{source.inspect} -over-> #{dest.inspect}" if merge_debug dest = overwrite_unmergeables(source, dest, options) end else # src_hash is not an array or hash, so we'll have to overwrite dest puts "#{di}Others: #{source.inspect} :: #{dest.inspect}" if merge_debug dest = overwrite_unmergeables(source, dest, options) end puts "#{di}Returning #{dest.inspect}" if merge_debug dest end # deep_merge! # allows deep_merge! to uniformly handle overwriting of unmergeable entities def self.overwrite_unmergeables(source, dest, options) merge_debug = options[:merge_debug] || false overwrite_unmergeable = !options[:preserve_unmergeables] knockout_prefix = options[:knockout_prefix] || false di = options[:debug_indent] || '' if knockout_prefix && overwrite_unmergeable if source.kind_of?(String) # remove knockout string from source before overwriting dest src_tmp = source.gsub(%r{^#{knockout_prefix}},"") elsif source.kind_of?(Array) # remove all knockout elements before overwriting dest src_tmp = source.delete_if {|ko_item| ko_item.kind_of?(String) && ko_item.match(%r{^#{knockout_prefix}}) } else src_tmp = source end if src_tmp == source # if we didn't find a knockout_prefix then we just overwrite dest puts "#{di}#{src_tmp.inspect} -over-> #{dest.inspect}" if merge_debug dest = src_tmp else # if we do find a knockout_prefix, then we just delete dest puts "#{di}\"\" -over-> #{dest.inspect}" if merge_debug dest = "" end elsif overwrite_unmergeable dest = source end dest end def self.clear_or_nil(obj) if obj.respond_to?(:clear) obj.clear else obj = nil end obj end end # module DeepMerge puppet-5.5.10/lib/puppet/vendor/deep_merge/lib/deep_merge/deep_merge_hash.rb0000644005276200011600000000170013417161721026752 0ustar jenkinsjenkinsrequire 'deep_merge/core' module DeepMerge module DeepMergeHash # ko_hash_merge! will merge and knockout elements prefixed with DEFAULT_FIELD_KNOCKOUT_PREFIX def ko_deep_merge!(source, options = {}) default_opts = {:knockout_prefix => "--", :preserve_unmergeables => false} DeepMerge::deep_merge!(source, self, default_opts.merge(options)) end # deep_merge! will merge and overwrite any unmergeables in destination hash def deep_merge!(source, options = {}) default_opts = {:preserve_unmergeables => false} DeepMerge::deep_merge!(source, self, default_opts.merge(options)) end # deep_merge will merge and skip any unmergeables in destination hash def deep_merge(source, options = {}) default_opts = {:preserve_unmergeables => true} DeepMerge::deep_merge!(source, self, default_opts.merge(options)) end end # DeepMergeHashExt end class Hash include DeepMerge::DeepMergeHash end puppet-5.5.10/lib/puppet/vendor/deep_merge/lib/deep_merge/rails_compat.rb0000644005276200011600000000166013417161721026335 0ustar jenkinsjenkinsrequire 'deep_merge/core' module DeepMerge module RailsCompat # ko_hash_merge! will merge and knockout elements prefixed with DEFAULT_FIELD_KNOCKOUT_PREFIX def ko_deeper_merge!(source, options = {}) default_opts = {:knockout_prefix => "--", :preserve_unmergeables => false} DeepMerge::deep_merge!(source, self, default_opts.merge(options)) end # deep_merge! will merge and overwrite any unmergeables in destination hash def deeper_merge!(source, options = {}) default_opts = {:preserve_unmergeables => false} DeepMerge::deep_merge!(source, self, default_opts.merge(options)) end # deep_merge will merge and skip any unmergeables in destination hash def deeper_merge(source, options = {}) default_opts = {:preserve_unmergeables => true} DeepMerge::deep_merge!(source, self, default_opts.merge(options)) end end end class Hash include ::DeepMerge::RailsCompat end puppet-5.5.10/lib/puppet/vendor/deep_merge/test/0000755005276200011600000000000013417162176021452 5ustar jenkinsjenkinspuppet-5.5.10/lib/puppet/vendor/deep_merge/test/test_deep_merge.rb0000644005276200011600000011172113417161721025130 0ustar jenkinsjenkinsrequire 'test/unit' $:.unshift(File.dirname(__FILE__) + '/../lib/') require 'deep_merge' # Assume strings have a blank? method # as they do when ActiveSupport is included. module StringBlank def blank? size == 0 end end class TestDeepMerge < Test::Unit::TestCase def setup end # show that Hash object has deep merge capabilities in form of three methods: # ko_deep_merge! # uses '--' knockout and overwrites unmergeable # deep_merge! # overwrites unmergeable # deep_merge # skips unmergeable def test_hash_deep_merge x = {} assert x.respond_to?('deep_merge!'.to_sym) hash_src = {'id' => [3,4,5]} hash_dest = {'id' => [1,2,3]} assert hash_dest.ko_deep_merge!(hash_src) assert_equal({'id' => [1,2,3,4,5]}, hash_dest) hash_src = {'id' => [3,4,5]} hash_dest = {'id' => [1,2,3]} assert hash_dest.deep_merge!(hash_src) assert_equal({'id' => [1,2,3,4,5]}, hash_dest) hash_src = {'id' => 'xxx'} hash_dest = {'id' => [1,2,3]} assert hash_dest.deep_merge(hash_src) assert_equal({'id' => [1,2,3]}, hash_dest) end FIELD_KNOCKOUT_PREFIX = DeepMerge::DEFAULT_FIELD_KNOCKOUT_PREFIX # tests DeepMerge::deep_merge! function def test_deep_merge # merge tests (moving from basic to more complex) # test merging an hash w/array into blank hash hash_src = {'id' => '2'} hash_dst = {} DeepMerge::deep_merge!(hash_src.dup, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) assert_equal hash_src, hash_dst # test merging an hash w/array into blank hash hash_src = {'region' => {'id' => ['227', '2']}} hash_dst = {} DeepMerge::deep_merge!(hash_src, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) assert_equal hash_src, hash_dst # merge from empty hash hash_src = {} hash_dst = {"property" => ["2","4"]} DeepMerge::deep_merge!(hash_src, hash_dst) assert_equal({"property" => ["2","4"]}, hash_dst) # merge to empty hash hash_src = {"property" => ["2","4"]} hash_dst = {} DeepMerge::deep_merge!(hash_src, hash_dst) assert_equal({"property" => ["2","4"]}, hash_dst) # simple string overwrite hash_src = {"name" => "value"} hash_dst = {"name" => "value1"} DeepMerge::deep_merge!(hash_src, hash_dst) assert_equal({"name" => "value"}, hash_dst) # simple string overwrite of empty hash hash_src = {"name" => "value"} hash_dst = {} DeepMerge::deep_merge!(hash_src, hash_dst) assert_equal(hash_src, hash_dst) # hashes holding array hash_src = {"property" => ["1","3"]} hash_dst = {"property" => ["2","4"]} DeepMerge::deep_merge!(hash_src, hash_dst) assert_equal(["2","4","1","3"], hash_dst['property']) # hashes holding array (sorted) hash_src = {"property" => ["1","3"]} hash_dst = {"property" => ["2","4"]} DeepMerge::deep_merge!(hash_src, hash_dst, {:sort_merged_arrays => true}) assert_equal(["1","2","3","4"].sort, hash_dst['property']) # hashes holding hashes holding arrays (array with duplicate elements is merged with dest then src hash_src = {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["1", "4+"]}} hash_dst = {"property" => {"bedroom_count" => ["3", "2"], "bathroom_count" => ["2"]}} DeepMerge::deep_merge!(hash_src, hash_dst) assert_equal({"property" => {"bedroom_count" => ["3","2","1"], "bathroom_count" => ["2", "1", "4+"]}}, hash_dst) # hash holding hash holding array v string (string is overwritten by array) hash_src = {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["1", "4+"]}} hash_dst = {"property" => {"bedroom_count" => "3", "bathroom_count" => ["2"]}} DeepMerge::deep_merge!(hash_src, hash_dst) assert_equal({"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["2","1","4+"]}}, hash_dst) # hash holding hash holding array v string (string is NOT overwritten by array) hash_src = {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["1", "4+"]}} hash_dst = {"property" => {"bedroom_count" => "3", "bathroom_count" => ["2"]}} DeepMerge::deep_merge!(hash_src, hash_dst, {:preserve_unmergeables => true}) assert_equal({"property" => {"bedroom_count" => "3", "bathroom_count" => ["2","1","4+"]}}, hash_dst) # hash holding hash holding string v array (array is overwritten by string) hash_src = {"property" => {"bedroom_count" => "3", "bathroom_count" => ["1", "4+"]}} hash_dst = {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["2"]}} DeepMerge::deep_merge!(hash_src, hash_dst) assert_equal({"property" => {"bedroom_count" => "3", "bathroom_count" => ["2","1","4+"]}}, hash_dst) # hash holding hash holding string v array (array does NOT overwrite string) hash_src = {"property" => {"bedroom_count" => "3", "bathroom_count" => ["1", "4+"]}} hash_dst = {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["2"]}} DeepMerge::deep_merge!(hash_src, hash_dst, {:preserve_unmergeables => true}) assert_equal({"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["2","1","4+"]}}, hash_dst) # hash holding hash holding hash v array (array is overwritten by hash) hash_src = {"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => 1}, "bathroom_count" => ["1", "4+"]}} hash_dst = {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["2"]}} DeepMerge::deep_merge!(hash_src, hash_dst) assert_equal({"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => 1}, "bathroom_count" => ["2","1","4+"]}}, hash_dst) # hash holding hash holding hash v array (array is NOT overwritten by hash) hash_src = {"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => 1}, "bathroom_count" => ["1", "4+"]}} hash_dst = {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["2"]}} DeepMerge::deep_merge!(hash_src, hash_dst, {:preserve_unmergeables => true}) assert_equal({"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["2","1","4+"]}}, hash_dst) # 3 hash layers holding integers (integers are overwritten by source) hash_src = {"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => 1}, "bathroom_count" => ["1", "4+"]}} hash_dst = {"property" => {"bedroom_count" => {"king_bed" => 2, "queen_bed" => 4}, "bathroom_count" => ["2"]}} DeepMerge::deep_merge!(hash_src, hash_dst) assert_equal({"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => 1}, "bathroom_count" => ["2","1","4+"]}}, hash_dst) # 3 hash layers holding arrays of int (arrays are merged) hash_src = {"property" => {"bedroom_count" => {"king_bed" => [3], "queen_bed" => [1]}, "bathroom_count" => ["1", "4+"]}} hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}} DeepMerge::deep_merge!(hash_src, hash_dst) assert_equal({"property" => {"bedroom_count" => {"king_bed" => [2,3], "queen_bed" => [4,1]}, "bathroom_count" => ["2","1","4+"]}}, hash_dst) # 1 hash overwriting 3 hash layers holding arrays of int hash_src = {"property" => "1"} hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}} DeepMerge::deep_merge!(hash_src, hash_dst) assert_equal({"property" => "1"}, hash_dst) # 1 hash NOT overwriting 3 hash layers holding arrays of int hash_src = {"property" => "1"} hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}} DeepMerge::deep_merge!(hash_src, hash_dst, {:preserve_unmergeables => true}) assert_equal({"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}}, hash_dst) # 3 hash layers holding arrays of int (arrays are merged) but second hash's array is overwritten hash_src = {"property" => {"bedroom_count" => {"king_bed" => [3], "queen_bed" => [1]}, "bathroom_count" => "1"}} hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}} DeepMerge::deep_merge!(hash_src, hash_dst) assert_equal({"property" => {"bedroom_count" => {"king_bed" => [2,3], "queen_bed" => [4,1]}, "bathroom_count" => "1"}}, hash_dst) # 3 hash layers holding arrays of int (arrays are merged) but second hash's array is NOT overwritten hash_src = {"property" => {"bedroom_count" => {"king_bed" => [3], "queen_bed" => [1]}, "bathroom_count" => "1"}} hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}} DeepMerge::deep_merge!(hash_src, hash_dst, {:preserve_unmergeables => true}) assert_equal({"property" => {"bedroom_count" => {"king_bed" => [2,3], "queen_bed" => [4,1]}, "bathroom_count" => ["2"]}}, hash_dst) # 3 hash layers holding arrays of int, but one holds int. This one overwrites, but the rest merge hash_src = {"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => [1]}, "bathroom_count" => ["1"]}} hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}} DeepMerge::deep_merge!(hash_src, hash_dst) assert_equal({"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => [4,1]}, "bathroom_count" => ["2","1"]}}, hash_dst) # 3 hash layers holding arrays of int, but source is incomplete. hash_src = {"property" => {"bedroom_count" => {"king_bed" => [3]}, "bathroom_count" => ["1"]}} hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}} DeepMerge::deep_merge!(hash_src, hash_dst) assert_equal({"property" => {"bedroom_count" => {"king_bed" => [2,3], "queen_bed" => [4]}, "bathroom_count" => ["2","1"]}}, hash_dst) # 3 hash layers holding arrays of int, but source is shorter and has new 2nd level ints. hash_src = {"property" => {"bedroom_count" => {2=>3, "king_bed" => [3]}, "bathroom_count" => ["1"]}} hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}} DeepMerge::deep_merge!(hash_src, hash_dst) assert_equal({"property" => {"bedroom_count" => {2=>3, "king_bed" => [2,3], "queen_bed" => [4]}, "bathroom_count" => ["2","1"]}}, hash_dst) # 3 hash layers holding arrays of int, but source is empty hash_src = {} hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}} DeepMerge::deep_merge!(hash_src, hash_dst) assert_equal({"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}}, hash_dst) # 3 hash layers holding arrays of int, but dest is empty hash_src = {"property" => {"bedroom_count" => {2=>3, "king_bed" => [3]}, "bathroom_count" => ["1"]}} hash_dst = {} DeepMerge::deep_merge!(hash_src, hash_dst) assert_equal({"property" => {"bedroom_count" => {2=>3, "king_bed" => [3]}, "bathroom_count" => ["1"]}}, hash_dst) # 3 hash layers holding arrays of int, but source includes a nil in the array hash_src = {"property" => {"bedroom_count" => {"king_bed" => [nil], "queen_bed" => [1, nil]}, "bathroom_count" => [nil, "1"]}} hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}} DeepMerge::deep_merge!(hash_src, hash_dst) assert_equal({"property" => {"bedroom_count" => {"king_bed" => [2,nil], "queen_bed" => [4, 1, nil]}, "bathroom_count" => ["2", nil, "1"]}}, hash_dst) # 3 hash layers holding arrays of int, but destination includes a nil in the array hash_src = {"property" => {"bedroom_count" => {"king_bed" => [3], "queen_bed" => [1]}, "bathroom_count" => ["1"]}} hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [nil], "queen_bed" => [4, nil]}, "bathroom_count" => [nil,"2"]}} DeepMerge::deep_merge!(hash_src, hash_dst) assert_equal({"property" => {"bedroom_count" => {"king_bed" => [nil, 3], "queen_bed" => [4, nil, 1]}, "bathroom_count" => [nil, "2", "1"]}}, hash_dst) # test parameter management for knockout_prefix and overwrite unmergeable assert_raise(DeepMerge::InvalidParameter) {DeepMerge::deep_merge!(hash_src, hash_dst, {:knockout_prefix => ""})} assert_raise(DeepMerge::InvalidParameter) {DeepMerge::deep_merge!(hash_src, hash_dst, {:preserve_unmergeables => true, :knockout_prefix => ""})} assert_raise(DeepMerge::InvalidParameter) {DeepMerge::deep_merge!(hash_src, hash_dst, {:preserve_unmergeables => true, :knockout_prefix => "--"})} assert_nothing_raised(DeepMerge::InvalidParameter) {DeepMerge::deep_merge!(hash_src, hash_dst, {:knockout_prefix => "--"})} assert_nothing_raised(DeepMerge::InvalidParameter) {DeepMerge::deep_merge!(hash_src, hash_dst)} assert_nothing_raised(DeepMerge::InvalidParameter) {DeepMerge::deep_merge!(hash_src, hash_dst, {:preserve_unmergeables => true})} # hash holding arrays of arrays hash_src = {["1", "2", "3"] => ["1", "2"]} hash_dst = {["4", "5"] => ["3"]} DeepMerge::deep_merge!(hash_src, hash_dst) assert_equal({["1","2","3"] => ["1", "2"], ["4", "5"] => ["3"]}, hash_dst) # test merging of hash with blank hash, and make sure that source array split still functions hash_src = {'property' => {'bedroom_count' => ["1","2,3"]}} hash_dst = {} DeepMerge::deep_merge!(hash_src, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) assert_equal({'property' => {'bedroom_count' => ["1","2","3"]}}, hash_dst) # test merging of hash with blank hash, and make sure that source array split does not function when turned off hash_src = {'property' => {'bedroom_count' => ["1","2,3"]}} hash_dst = {} DeepMerge::deep_merge!(hash_src, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX}) assert_equal({'property' => {'bedroom_count' => ["1","2,3"]}}, hash_dst) # test merging into a blank hash with overwrite_unmergeables turned on hash_src = {"action"=>"browse", "controller"=>"results"} hash_dst = {} DeepMerge::deep_merge!(hash_src, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) assert_equal hash_src, hash_dst # KNOCKOUT_PREFIX testing # the next few tests are looking for correct behavior from specific real-world params/session merges # using the custom modifiers built for param/session merges [nil, ","].each do |ko_split| # typical params/session style hash with knockout_merge elements hash_params = {"property"=>{"bedroom_count"=>[FIELD_KNOCKOUT_PREFIX+"1", "2", "3"]}} hash_session = {"property"=>{"bedroom_count"=>["1", "2", "3"]}} DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ko_split}) assert_equal({"property"=>{"bedroom_count"=>["2", "3"]}}, hash_session) # typical params/session style hash with knockout_merge elements hash_params = {"property"=>{"bedroom_count"=>[FIELD_KNOCKOUT_PREFIX+"1", "2", "3"]}} hash_session = {"property"=>{"bedroom_count"=>["3"]}} DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ko_split}) assert_equal({"property"=>{"bedroom_count"=>["3","2"]}}, hash_session) # typical params/session style hash with knockout_merge elements hash_params = {"property"=>{"bedroom_count"=>[FIELD_KNOCKOUT_PREFIX+"1", "2", "3"]}} hash_session = {"property"=>{"bedroom_count"=>["4"]}} DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ko_split}) assert_equal({"property"=>{"bedroom_count"=>["4","2","3"]}}, hash_session) # typical params/session style hash with knockout_merge elements hash_params = {"property"=>{"bedroom_count"=>[FIELD_KNOCKOUT_PREFIX+"1", "2", "3"]}} hash_session = {"property"=>{"bedroom_count"=>[FIELD_KNOCKOUT_PREFIX+"1", "4"]}} DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ko_split}) assert_equal({"property"=>{"bedroom_count"=>["4","2","3"]}}, hash_session) # typical params/session style hash with knockout_merge elements hash_params = {"amenity"=>{"id"=>[FIELD_KNOCKOUT_PREFIX+"1", FIELD_KNOCKOUT_PREFIX+"2", "3", "4"]}} hash_session = {"amenity"=>{"id"=>["1", "2"]}} DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ko_split}) assert_equal({"amenity"=>{"id"=>["3","4"]}}, hash_session) end # special params/session style hash with knockout_merge elements in form src: ["1","2"] dest:["--1,--2", "3,4"] hash_params = {"amenity"=>{"id"=>[FIELD_KNOCKOUT_PREFIX+"1,"+FIELD_KNOCKOUT_PREFIX+"2", "3,4"]}} hash_session = {"amenity"=>{"id"=>["1", "2"]}} DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) assert_equal({"amenity"=>{"id"=>["3","4"]}}, hash_session) # same as previous but without ko_split value, this merge should fail hash_params = {"amenity"=>{"id"=>[FIELD_KNOCKOUT_PREFIX+"1,"+FIELD_KNOCKOUT_PREFIX+"2", "3,4"]}} hash_session = {"amenity"=>{"id"=>["1", "2"]}} DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX}) assert_equal({"amenity"=>{"id"=>["1","2","3,4"]}}, hash_session) # special params/session style hash with knockout_merge elements in form src: ["1","2"] dest:["--1,--2", "3,4"] hash_params = {"amenity"=>{"id"=>[FIELD_KNOCKOUT_PREFIX+"1,2", "3,4", "--5", "6"]}} hash_session = {"amenity"=>{"id"=>["1", "2"]}} DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) assert_equal({"amenity"=>{"id"=>["2","3","4","6"]}}, hash_session) # special params/session style hash with knockout_merge elements in form src: ["--1,--2", "3,4", "--5", "6"] dest:["1,2", "3,4"] hash_params = {"amenity"=>{"id"=>["#{FIELD_KNOCKOUT_PREFIX}1,#{FIELD_KNOCKOUT_PREFIX}2", "3,4", "#{FIELD_KNOCKOUT_PREFIX}5", "6"]}} hash_session = {"amenity"=>{"id"=>["1", "2", "3", "4"]}} DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) assert_equal({"amenity"=>{"id"=>["3","4","6"]}}, hash_session) hash_src = {"url_regions"=>[], "region"=>{"ids"=>["227,233"]}, "action"=>"browse", "task"=>"browse", "controller"=>"results"} hash_dst = {"region"=>{"ids"=>["227"]}} DeepMerge::deep_merge!(hash_src.dup, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) assert_equal({"url_regions"=>[], "region"=>{"ids"=>["227","233"]}, "action"=>"browse", "task"=>"browse", "controller"=>"results"}, hash_dst) hash_src = {"region"=>{"ids"=>["--","227"], "id"=>"230"}} hash_dst = {"region"=>{"ids"=>["227", "233", "324", "230", "230"], "id"=>"230"}} DeepMerge::deep_merge!(hash_src, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) assert_equal({"region"=>{"ids"=>["227"], "id"=>"230"}}, hash_dst) hash_src = {"region"=>{"ids"=>["--","227", "232", "233"], "id"=>"232"}} hash_dst = {"region"=>{"ids"=>["227", "233", "324", "230", "230"], "id"=>"230"}} DeepMerge::deep_merge!(hash_src, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) assert_equal({"region"=>{"ids"=>["227", "232", "233"], "id"=>"232"}}, hash_dst) hash_src = {"region"=>{"ids"=>["--,227,232,233"], "id"=>"232"}} hash_dst = {"region"=>{"ids"=>["227", "233", "324", "230", "230"], "id"=>"230"}} DeepMerge::deep_merge!(hash_src, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) assert_equal({"region"=>{"ids"=>["227", "232", "233"], "id"=>"232"}}, hash_dst) hash_src = {"region"=>{"ids"=>["--,227,232","233"], "id"=>"232"}} hash_dst = {"region"=>{"ids"=>["227", "233", "324", "230", "230"], "id"=>"230"}} DeepMerge::deep_merge!(hash_src, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) assert_equal({"region"=>{"ids"=>["227", "232", "233"], "id"=>"232"}}, hash_dst) hash_src = {"region"=>{"ids"=>["--,227"], "id"=>"230"}} hash_dst = {"region"=>{"ids"=>["227", "233", "324", "230", "230"], "id"=>"230"}} DeepMerge::deep_merge!(hash_src, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) assert_equal({"region"=>{"ids"=>["227"], "id"=>"230"}}, hash_dst) hash_src = {"region"=>{"ids"=>["--,227"], "id"=>"230"}} hash_dst = {"region"=>{"ids"=>["227", "233", "324", "230", "230"], "id"=>"230"}, "action"=>"browse", "task"=>"browse", "controller"=>"results", "property_order_by"=>"property_type.descr"} DeepMerge::deep_merge!(hash_src, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) assert_equal({"region"=>{"ids"=>["227"], "id"=>"230"}, "action"=>"browse", "task"=>"browse", "controller"=>"results", "property_order_by"=>"property_type.descr"}, hash_dst) hash_src = {"query_uuid"=>"6386333d-389b-ab5c-8943-6f3a2aa914d7", "region"=>{"ids"=>["--,227"], "id"=>"230"}} hash_dst = {"query_uuid"=>"6386333d-389b-ab5c-8943-6f3a2aa914d7", "url_regions"=>[], "region"=>{"ids"=>["227", "233", "324", "230", "230"], "id"=>"230"}, "action"=>"browse", "task"=>"browse", "controller"=>"results", "property_order_by"=>"property_type.descr"} DeepMerge::deep_merge!(hash_src, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) assert_equal({"query_uuid" => "6386333d-389b-ab5c-8943-6f3a2aa914d7", "url_regions"=>[], "region"=>{"ids"=>["227"], "id"=>"230"}, "action"=>"browse", "task"=>"browse", "controller"=>"results", "property_order_by"=>"property_type.descr"}, hash_dst) # knock out entire dest hash if "--" is passed for source hash_params = {'amenity' => "--"} hash_session = {"amenity" => "1"} DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => "--", :unpack_arrays => ","}) assert_equal({'amenity' => ""}, hash_session) # knock out entire dest hash if "--" is passed for source hash_params = {'amenity' => ["--"]} hash_session = {"amenity" => "1"} DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => "--", :unpack_arrays => ","}) assert_equal({'amenity' => []}, hash_session) # knock out entire dest hash if "--" is passed for source hash_params = {'amenity' => "--"} hash_session = {"amenity" => ["1"]} DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => "--", :unpack_arrays => ","}) assert_equal({'amenity' => ""}, hash_session) # knock out entire dest hash if "--" is passed for source hash_params = {'amenity' => ["--"]} hash_session = {"amenity" => ["1"]} DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => "--", :unpack_arrays => ","}) assert_equal({'amenity' => []}, hash_session) # knock out entire dest hash if "--" is passed for source hash_params = {'amenity' => ["--"]} hash_session = {"amenity" => "1"} DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => "--", :unpack_arrays => ","}) assert_equal({'amenity' => []}, hash_session) # knock out entire dest hash if "--" is passed for source hash_params = {'amenity' => ["--", "2"]} hash_session = {'amenity' => ["1", "3", "7+"]} DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => "--", :unpack_arrays => ","}) assert_equal({'amenity' => ["2"]}, hash_session) # knock out entire dest hash if "--" is passed for source hash_params = {'amenity' => ["--", "2"]} hash_session = {'amenity' => "5"} DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => "--", :unpack_arrays => ","}) assert_equal({'amenity' => ['2']}, hash_session) # knock out entire dest hash if "--" is passed for source hash_params = {'amenity' => "--"} hash_session = {"amenity"=>{"id"=>["1", "2", "3", "4"]}} DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => "--", :unpack_arrays => ","}) assert_equal({'amenity' => ""}, hash_session) # knock out entire dest hash if "--" is passed for source hash_params = {'amenity' => ["--"]} hash_session = {"amenity"=>{"id"=>["1", "2", "3", "4"]}} DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => "--", :unpack_arrays => ","}) assert_equal({'amenity' => []}, hash_session) # knock out dest array if "--" is passed for source hash_params = {"region" => {'ids' => FIELD_KNOCKOUT_PREFIX}} hash_session = {"region"=>{"ids"=>["1", "2", "3", "4"]}} DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) assert_equal({'region' => {'ids' => ""}}, hash_session) # knock out dest array but leave other elements of hash intact hash_params = {"region" => {'ids' => FIELD_KNOCKOUT_PREFIX}} hash_session = {"region"=>{"ids"=>["1", "2", "3", "4"], 'id'=>'11'}} DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) assert_equal({'region' => {'ids' => "", 'id'=>'11'}}, hash_session) # knock out entire tree of dest hash hash_params = {"region" => FIELD_KNOCKOUT_PREFIX} hash_session = {"region"=>{"ids"=>["1", "2", "3", "4"], 'id'=>'11'}} DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) assert_equal({'region' => ""}, hash_session) # knock out entire tree of dest hash - retaining array format hash_params = {"region" => {'ids' => [FIELD_KNOCKOUT_PREFIX]}} hash_session = {"region"=>{"ids"=>["1", "2", "3", "4"], 'id'=>'11'}} DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) assert_equal({'region' => {'ids' => [], 'id'=>'11'}}, hash_session) # knock out entire tree of dest hash & replace with new content hash_params = {"region" => {'ids' => ["2", FIELD_KNOCKOUT_PREFIX, "6"]}} hash_session = {"region"=>{"ids"=>["1", "2", "3", "4"], 'id'=>'11'}} DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) assert_equal({'region' => {'ids' => ["2", "6"], 'id'=>'11'}}, hash_session) # knock out entire tree of dest hash & replace with new content hash_params = {"region" => {'ids' => ["7", FIELD_KNOCKOUT_PREFIX, "6"]}} hash_session = {"region"=>{"ids"=>["1", "2", "3", "4"], 'id'=>'11'}} DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) assert_equal({'region' => {'ids' => ["7", "6"], 'id'=>'11'}}, hash_session) # edge test: make sure that when we turn off knockout_prefix that all values are processed correctly hash_params = {"region" => {'ids' => ["7", "--", "2", "6,8"]}} hash_session = {"region"=>{"ids"=>["1", "2", "3", "4"], 'id'=>'11'}} DeepMerge::deep_merge!(hash_params, hash_session, {:unpack_arrays => ","}) assert_equal({'region' => {'ids' => ["1", "2", "3", "4", "7", "--", "6", "8"], 'id'=>'11'}}, hash_session) # edge test 2: make sure that when we turn off source array split that all values are processed correctly hash_params = {"region" => {'ids' => ["7", "3", "--", "6,8"]}} hash_session = {"region"=>{"ids"=>["1", "2", "3", "4"], 'id'=>'11'}} DeepMerge::deep_merge!(hash_params, hash_session) assert_equal({'region' => {'ids' => ["1", "2", "3", "4", "7", "--", "6,8"], 'id'=>'11'}}, hash_session) # Example: src = {'key' => "--1"}, dst = {'key' => "1"} -> merges to {'key' => ""} hash_params = {"amenity"=>"--1"} hash_session = {"amenity"=>"1"} DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX}) assert_equal({"amenity"=>""}, hash_session) # Example: src = {'key' => "--1"}, dst = {'key' => "2"} -> merges to {'key' => ""} hash_params = {"amenity"=>"--1"} hash_session = {"amenity"=>"2"} DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX}) assert_equal({"amenity"=>""}, hash_session) # Example: src = {'key' => "--1"}, dst = {'key' => "1"} -> merges to {'key' => ""} hash_params = {"amenity"=>["--1"]} hash_session = {"amenity"=>"1"} DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX}) assert_equal({"amenity"=>[]}, hash_session) # Example: src = {'key' => "--1"}, dst = {'key' => "1"} -> merges to {'key' => ""} hash_params = {"amenity"=>["--1"]} hash_session = {"amenity"=>["1"]} DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX}) assert_equal({"amenity"=>[]}, hash_session) # Example: src = {'key' => "--1"}, dst = {'key' => "1"} -> merges to {'key' => ""} hash_params = {"amenity"=>"--1"} hash_session = {} DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX}) assert_equal({"amenity"=>""}, hash_session) # Example: src = {'key' => "--1"}, dst = {'key' => "1"} -> merges to {'key' => ""} hash_params = {"amenity"=>"--1"} hash_session = {"amenity"=>["1"]} DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX}) assert_equal({"amenity"=>""}, hash_session) #are unmerged hashes passed unmodified w/out :unpack_arrays? hash_params = {"amenity"=>{"id"=>["26,27"]}} hash_session = {} DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX}) assert_equal({"amenity"=>{"id"=>["26,27"]}}, hash_session) #hash should be merged hash_params = {"amenity"=>{"id"=>["26,27"]}} hash_session = {} DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) assert_equal({"amenity"=>{"id"=>["26","27"]}}, hash_session) # second merge of same values should result in no change in output hash_params = {"amenity"=>{"id"=>["26,27"]}} DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) assert_equal({"amenity"=>{"id"=>["26","27"]}}, hash_session) #hashes with knockout values are suppressed hash_params = {"amenity"=>{"id"=>["#{FIELD_KNOCKOUT_PREFIX}26,#{FIELD_KNOCKOUT_PREFIX}27,28"]}} hash_session = {} DeepMerge::deep_merge!(hash_params, hash_session, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) assert_equal({"amenity"=>{"id"=>["28"]}}, hash_session) hash_src= {'region' =>{'ids'=>['--']}, 'query_uuid' => 'zzz'} hash_dst= {'region' =>{'ids'=>['227','2','3','3']}, 'query_uuid' => 'zzz'} DeepMerge::deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","}) assert_equal({'region' =>{'ids'=>[]}, 'query_uuid' => 'zzz'}, hash_dst) hash_src= {'region' =>{'ids'=>['--']}, 'query_uuid' => 'zzz'} hash_dst= {'region' =>{'ids'=>['227','2','3','3'], 'id' => '3'}, 'query_uuid' => 'zzz'} DeepMerge::deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","}) assert_equal({'region' =>{'ids'=>[], 'id'=>'3'}, 'query_uuid' => 'zzz'}, hash_dst) hash_src= {'region' =>{'ids'=>['--']}, 'query_uuid' => 'zzz'} hash_dst= {'region' =>{'muni_city_id' => '2244', 'ids'=>['227','2','3','3'], 'id'=>'3'}, 'query_uuid' => 'zzz'} DeepMerge::deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","}) assert_equal({'region' =>{'muni_city_id' => '2244', 'ids'=>[], 'id'=>'3'}, 'query_uuid' => 'zzz'}, hash_dst) hash_src= {'region' =>{'ids'=>['--'], 'id' => '5'}, 'query_uuid' => 'zzz'} hash_dst= {'region' =>{'muni_city_id' => '2244', 'ids'=>['227','2','3','3'], 'id'=>'3'}, 'query_uuid' => 'zzz'} DeepMerge::deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","}) assert_equal({'region' =>{'muni_city_id' => '2244', 'ids'=>[], 'id'=>'5'}, 'query_uuid' => 'zzz'}, hash_dst) hash_src= {'region' =>{'ids'=>['--', '227'], 'id' => '5'}, 'query_uuid' => 'zzz'} hash_dst= {'region' =>{'muni_city_id' => '2244', 'ids'=>['227','2','3','3'], 'id'=>'3'}, 'query_uuid' => 'zzz'} DeepMerge::deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","}) assert_equal({'region' =>{'muni_city_id' => '2244', 'ids'=>['227'], 'id'=>'5'}, 'query_uuid' => 'zzz'}, hash_dst) hash_src= {'region' =>{'muni_city_id' => '--', 'ids'=>'--', 'id'=>'5'}, 'query_uuid' => 'zzz'} hash_dst= {'region' =>{'muni_city_id' => '2244', 'ids'=>['227','2','3','3'], 'id'=>'3'}, 'query_uuid' => 'zzz'} DeepMerge::deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","}) assert_equal({'region' =>{'muni_city_id' => '', 'ids'=>'', 'id'=>'5'}, 'query_uuid' => 'zzz'}, hash_dst) hash_src= {'region' =>{'muni_city_id' => '--', 'ids'=>['--'], 'id'=>'5'}, 'query_uuid' => 'zzz'} hash_dst= {'region' =>{'muni_city_id' => '2244', 'ids'=>['227','2','3','3'], 'id'=>'3'}, 'query_uuid' => 'zzz'} DeepMerge::deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","}) assert_equal({'region' =>{'muni_city_id' => '', 'ids'=>[], 'id'=>'5'}, 'query_uuid' => 'zzz'}, hash_dst) hash_src= {'region' =>{'muni_city_id' => '--', 'ids'=>['--','227'], 'id'=>'5'}, 'query_uuid' => 'zzz'} hash_dst= {'region' =>{'muni_city_id' => '2244', 'ids'=>['227','2','3','3'], 'id'=>'3'}, 'query_uuid' => 'zzz'} DeepMerge::deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","}) assert_equal({'region' =>{'muni_city_id' => '', 'ids'=>['227'], 'id'=>'5'}, 'query_uuid' => 'zzz'}, hash_dst) hash_src = {"muni_city_id"=>"--", "id"=>""} hash_dst = {"muni_city_id"=>"", "id"=>""} DeepMerge::deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","}) assert_equal({"muni_city_id"=>"", "id"=>""}, hash_dst) hash_src = {"region"=>{"muni_city_id"=>"--", "id"=>""}} hash_dst = {"region"=>{"muni_city_id"=>"", "id"=>""}} DeepMerge::deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","}) assert_equal({"region"=>{"muni_city_id"=>"", "id"=>""}}, hash_dst) hash_src = {"query_uuid"=>"a0dc3c84-ec7f-6756-bdb0-fff9157438ab", "url_regions"=>[], "region"=>{"muni_city_id"=>"--", "id"=>""}, "property"=>{"property_type_id"=>"", "search_rate_min"=>"", "search_rate_max"=>""}, "task"=>"search", "run_query"=>"Search"} hash_dst = {"query_uuid"=>"a0dc3c84-ec7f-6756-bdb0-fff9157438ab", "url_regions"=>[], "region"=>{"muni_city_id"=>"", "id"=>""}, "property"=>{"property_type_id"=>"", "search_rate_min"=>"", "search_rate_max"=>""}, "task"=>"search", "run_query"=>"Search"} DeepMerge::deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","}) assert_equal({"query_uuid"=>"a0dc3c84-ec7f-6756-bdb0-fff9157438ab", "url_regions"=>[], "region"=>{"muni_city_id"=>"", "id"=>""}, "property"=>{"property_type_id"=>"", "search_rate_min"=>"", "search_rate_max"=>""}, "task"=>"search", "run_query"=>"Search"}, hash_dst) # hash of array of hashes hash_src = {"item" => [{"1" => "3"}, {"2" => "4"}]} hash_dst = {"item" => [{"3" => "5"}]} DeepMerge::deep_merge!(hash_src, hash_dst) assert_equal({"item" => [{"3" => "5"}, {"1" => "3"}, {"2" => "4"}]}, hash_dst) ###################################### # tests for "merge_hash_arrays" option hash_src = {"item" => [{"1" => "3"}]} hash_dst = {"item" => [{"3" => "5"}]} DeepMerge::deep_merge!(hash_src, hash_dst, {:merge_hash_arrays => true}) assert_equal({"item" => [{"3" => "5", "1" => "3"}]}, hash_dst) hash_src = {"item" => [{"1" => "3"}, {"2" => "4"}]} hash_dst = {"item" => [{"3" => "5"}]} DeepMerge::deep_merge!(hash_src, hash_dst, {:merge_hash_arrays => true}) assert_equal({"item" => [{"3" => "5", "1" => "3"}, {"2" => "4"}]}, hash_dst) hash_src = {"item" => [{"1" => "3"}]} hash_dst = {"item" => [{"3" => "5"}, {"2" => "4"}]} DeepMerge::deep_merge!(hash_src, hash_dst, {:merge_hash_arrays => true}) assert_equal({"item" => [{"3" => "5", "1" => "3"}, {"2" => "4"}]}, hash_dst) # if arrays contain non-hash objects, the :merge_hash_arrays option has # no effect. hash_src = {"item" => [{"1" => "3"}, "str"]} # contains "str", non-hash hash_dst = {"item" => [{"3" => "5"}]} DeepMerge::deep_merge!(hash_src, hash_dst, {:merge_hash_arrays => true}) assert_equal({"item" => [{"3" => "5"}, {"1" => "3"}, "str"]}, hash_dst) # Merging empty strings s1, s2 = "hello", "" [s1, s2].each { |s| s.extend StringBlank } hash_dst = {"item" => s1 } hash_src = {"item" => s2 } DeepMerge::deep_merge!(hash_src, hash_dst) assert_equal({"item" => ""}, hash_dst) end # test_deep_merge end puppet-5.5.10/lib/puppet/vendor/load_deep_merge.rb0000644005276200011600000000007413417161721022013 0ustar jenkinsjenkins$: << File.join([File.dirname(__FILE__), "deep_merge/lib"]) puppet-5.5.10/lib/puppet/vendor/load_pathspec.rb0000644005276200011600000000007213417161721021524 0ustar jenkinsjenkins$: << File.join([File.dirname(__FILE__), "pathspec/lib"]) puppet-5.5.10/lib/puppet/vendor/pathspec/0000755005276200011600000000000013417162176020206 5ustar jenkinsjenkinspuppet-5.5.10/lib/puppet/vendor/pathspec/CHANGELOG.md0000644005276200011600000000007213417161721022011 0ustar jenkinsjenkins0.0.2: Misc. Windows/regex fixes. 0.0.1: Initial version. puppet-5.5.10/lib/puppet/vendor/pathspec/LICENSE0000644005276200011600000002607613417161721021221 0ustar jenkinsjenkinsApache License Version 2.0, January 2004 https://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. puppet-5.5.10/lib/puppet/vendor/pathspec/PUPPET_README.md0000644005276200011600000000027513417161721022561 0ustar jenkinsjenkinsPathspec-ruby - Gitignore parsing in Ruby ============================================= Pathspec-ruby version 0.0.2 Copied from https://github.com/highb/pathspec-ruby/releases/tag/0.0.2 puppet-5.5.10/lib/puppet/vendor/pathspec/README.md0000644005276200011600000000333713417161721021466 0ustar jenkinsjenkinspathspec-ruby ============= Match Path Specifications, such as .gitignore, in Ruby! Follows .gitignore syntax defined on [gitscm](https://git-scm.com/docs/gitignore) .gitignore functionality ported from [Python pathspec](https://pypi.python.org/pypi/pathspec/0.2.2) by [@cpburnz](https://github.com/cpburnz/python-path-specification) [Travis Status](https://travis-ci.org/highb/pathspec-ruby) ![Travis CI Status](https://travis-ci.org/highb/pathspec-ruby.svg?branch=master) ## Build/Install from Rubygems ```shell gem install pathspec ``` ## Usage ```ruby require 'pathspec' # Create a .gitignore-style Pathspec by giving it newline separated gitignore # lines, an array of gitignore lines, or any other enumable object that will # give strings matching the .gitignore-style (File, etc.) gitignore = Pathspec.new File.read('.gitignore', 'r') # Our .gitignore in this example contains: # !**/important.txt # abc/** # true, matches "abc/**" gitignore.match 'abc/def.rb' # false, because it has been negated using the line "!**/important.txt" gitignore.match 'abc/important.txt' # Give a path somewhere in the filesystem, and the Pathspec will return all # matching files underneath. # Returns ['/src/repo/abc/', '/src/repo/abc/123'] gitignore.match_tree '/src/repo' # Give an enumerable of paths, and Pathspec will return the ones that match. # Returns ['/abc/123', '/abc/'] gitignore.match_paths ['/abc/123', '/abc/important.txt', '/abc/'] ``` ## Building/Installing from Source ```shell git clone git@github.com:highb/pathspec-ruby.git cd pathspec-ruby && bash ./build_from_source.sh ``` ## Contributing Pull requests, bug reports, and feature requests welcome! :smile: I've tried to write exhaustive tests but who knows what cases I've missed. puppet-5.5.10/lib/puppet/vendor/pathspec/lib/0000755005276200011600000000000013417162176020754 5ustar jenkinsjenkinspuppet-5.5.10/lib/puppet/vendor/pathspec/lib/pathspec.rb0000644005276200011600000000506713417161721023113 0ustar jenkinsjenkinsrequire 'pathspec/gitignorespec' require 'pathspec/regexspec' require 'find' require 'pathname' class PathSpec attr_reader :specs def initialize(lines=nil, type=:git) @specs = [] if lines add(lines, type) end self end # Check if a path matches the pathspecs described # Returns true if there are matches and none are excluded # Returns false if there aren't matches or none are included def match(path) matches = specs_matching(path.to_s) !matches.empty? && matches.all? {|m| m.inclusive?} end def specs_matching(path) @specs.select do |spec| if spec.match(path) spec end end end # Check if any files in a given directory or subdirectories match the specs # Returns matched paths or nil if no paths matched def match_tree(root) rootpath = Pathname.new(root) matching = [] Find.find(root) do |path| relpath = Pathname.new(path).relative_path_from(rootpath).to_s relpath += '/' if File.directory? path if match(relpath) matching << path end end matching end def match_path(path, root='/') rootpath = Pathname.new(drive_letter_to_path(root)) relpath = Pathname.new(drive_letter_to_path(path)).relative_path_from(rootpath).to_s relpath = relpath + '/' if path[-1].chr == '/' match(relpath) end def match_paths(paths, root='/') matching = [] paths.each do |path| if match_path(path, root) matching << path end end matching end def drive_letter_to_path(path) path.gsub(/^([a-zA-Z]):\//, '/\1/') end # Generate specs from a filename, such as a .gitignore def self.from_filename(filename, type=:git) # .gitignore is a UTF-8 file self.from_lines(File.open(filename, 'r', :encoding => Encoding::UTF_8), type) end def self.from_lines(lines, type=:git) self.new lines, type end # Generate specs from lines of text def add(obj, type=:git) spec_class = spec_type(type) if obj.respond_to?(:each_line) obj.each_line do |l| spec = spec_class.new(l.rstrip) if !spec.regex.nil? && !spec.inclusive?.nil? @specs << spec end end elsif obj.respond_to?(:each) obj.each do |l| add(l, type) end else raise 'Cannot make Pathspec from non-string/non-enumerable object.' end self end def empty? @specs.empty? end def spec_type(type) if type == :git GitIgnoreSpec elsif type == :regex RegexSpec else raise "Unknown spec type #{type}" end end end puppet-5.5.10/lib/puppet/vendor/pathspec/lib/pathspec/0000755005276200011600000000000013417162176022563 5ustar jenkinsjenkinspuppet-5.5.10/lib/puppet/vendor/pathspec/lib/pathspec/gitignorespec.rb0000644005276200011600000001730413417161721025752 0ustar jenkinsjenkins# encoding: utf-8 require 'pathspec/regexspec' class GitIgnoreSpec < RegexSpec attr_reader :regex def initialize(pattern) pattern = pattern.strip unless pattern.nil? # A pattern starting with a hash ('#') serves as a comment # (neither includes nor excludes files). Escape the hash with a # back-slash to match a literal hash (i.e., '\#'). if pattern.start_with?('#') @regex = nil @inclusive = nil # A blank pattern is a null-operation (neither includes nor # excludes files). elsif pattern.empty? @regex = nil @inclusive = nil # Patterns containing three or more consecutive stars are invalid and # will be ignored. elsif pattern =~ /\*\*\*+/ @regex = nil @inclusive = nil # We have a valid pattern! else # A pattern starting with an exclamation mark ('!') negates the # pattern (exclude instead of include). Escape the exclamation # mark with a back-slash to match a literal exclamation mark # (i.e., '\!'). if pattern.start_with?('!') @inclusive = false # Remove leading exclamation mark. pattern = pattern[1..-1] else @inclusive = true end # Remove leading back-slash escape for escaped hash ('#') or # exclamation mark ('!'). if pattern.start_with?('\\') pattern = pattern[1..-1] end # Split pattern into segments. -1 to allow trailing slashes. pattern_segs = pattern.split('/', -1) # Normalize pattern to make processing easier. # A pattern beginning with a slash ('/') will only match paths # directly on the root directory instead of any descendant # paths. So, remove empty first segment to make pattern relative # to root. if pattern_segs[0].empty? pattern_segs.shift else # A pattern without a beginning slash ('/') will match any # descendant path. This is equivalent to "**/{pattern}". So, # prepend with double-asterisks to make pattern relative to # root. if pattern_segs.length == 1 && pattern_segs[0] != '**' pattern_segs.insert(0, '**') end end # A pattern ending with a slash ('/') will match all descendant # paths of if it is a directory but not if it is a regular file. # This is equivalent to "{pattern}/**". So, set last segment to # double asterisks to include all descendants. if pattern_segs[-1].empty? pattern_segs[-1] = '**' end # Handle platforms with backslash separated paths if File::SEPARATOR == '\\' path_sep = '\\\\' else path_sep = '/' end # Build regular expression from pattern. regex = '^' need_slash = false regex_end = pattern_segs.size - 1 pattern_segs.each_index do |i| seg = pattern_segs[i] if seg == '**' # A pattern consisting solely of double-asterisks ('**') # will match every path. if i == 0 && i == regex_end regex.concat('.+') # A normalized pattern beginning with double-asterisks # ('**') will match any leading path segments. elsif i == 0 regex.concat("(?:.+#{path_sep})?") need_slash = false # A normalized pattern ending with double-asterisks ('**') # will match any trailing path segments. elsif i == regex_end regex.concat("#{path_sep}.*") # A pattern with inner double-asterisks ('**') will match # multiple (or zero) inner path segments. else regex.concat("(?:#{path_sep}.+)?") need_slash = true end # Match single path segment. elsif seg == '*' if need_slash regex.concat(path_sep) end regex.concat("[^#{path_sep}]+") need_slash = true else # Match segment glob pattern. if need_slash regex.concat(path_sep) end regex.concat(translate_segment_glob(seg)) need_slash = true end end regex.concat('$') super(regex) end end def match(path) super(path) end def translate_segment_glob(pattern) """ Translates the glob pattern to a regular expression. This is used in the constructor to translate a path segment glob pattern to its corresponding regular expression. *pattern* (``str``) is the glob pattern. Returns the regular expression (``str``). """ # NOTE: This is derived from `fnmatch.translate()` and is similar to # the POSIX function `fnmatch()` with the `FNM_PATHNAME` flag set. escape = false regex = '' i = 0 while i < pattern.size # Get next character. char = pattern[i].chr i += 1 # Escape the character. if escape escape = false regex += Regexp.escape(char) # Escape character, escape next character. elsif char == '\\' escape = true # Multi-character wildcard. Match any string (except slashes), # including an empty string. elsif char == '*' regex += '[^/]*' # Single-character wildcard. Match any single character (except # a slash). elsif char == '?' regex += '[^/]' # Bracket expression wildcard. Except for the beginning # exclamation mark, the whole bracket expression can be used # directly as regex but we have to find where the expression # ends. # - "[][!]" matches ']', '[' and '!'. # - "[]-]" matches ']' and '-'. # - "[!]a-]" matches any character except ']', 'a' and '-'. elsif char == '[' j = i # Pass brack expression negation. if j < pattern.size && pattern[j].chr == '!' j += 1 end # Pass first closing bracket if it is at the beginning of the # expression. if j < pattern.size && pattern[j].chr == ']' j += 1 end # Find closing bracket. Stop once we reach the end or find it. while j < pattern.size && pattern[j].chr != ']' j += 1 end if j < pattern.size expr = '[' # Bracket expression needs to be negated. if pattern[i].chr == '!' expr += '^' i += 1 # POSIX declares that the regex bracket expression negation # "[^...]" is undefined in a glob pattern. Python's # `fnmatch.translate()` escapes the caret ('^') as a # literal. To maintain consistency with undefined behavior, # I am escaping the '^' as well. elsif pattern[i].chr == '^' expr += '\\^' i += 1 end # Escape brackets contained within pattern if pattern[i].chr == ']' && i != j expr += '\]' i += 1 end # Build regex bracket expression. Escape slashes so they are # treated as literal slashes by regex as defined by POSIX. expr += pattern[i..j].sub('\\', '\\\\') # Add regex bracket expression to regex result. regex += expr # Found end of bracket expression. Increment j to be one past # the closing bracket: # # [...] # ^ ^ # i j # j += 1 # Set i to one past the closing bracket. i = j # Failed to find closing bracket, treat opening bracket as a # bracket literal instead of as an expression. else regex += '\[' end # Regular character, escape it for regex. else regex << Regexp.escape(char) end end regex end def inclusive? @inclusive end end puppet-5.5.10/lib/puppet/vendor/pathspec/lib/pathspec/regexspec.rb0000644005276200011600000000033213417161721025066 0ustar jenkinsjenkinsrequire 'pathspec/spec' class RegexSpec < Spec def initialize(regex) @regex = Regexp.compile regex super end def inclusive? true end def match(path) @regex.match(path) if @regex end end puppet-5.5.10/lib/puppet/vendor/pathspec/lib/pathspec/spec.rb0000644005276200011600000000022513417161721024034 0ustar jenkinsjenkinsclass Spec attr_reader :regex def initialize(*_) end def match(files) raise "Unimplemented" end def inclusive? true end end puppet-5.5.10/lib/puppet/vendor/require_vendored.rb0000644005276200011600000000050413417161721022260 0ustar jenkinsjenkins# This adds upfront requirements on vendored code found under lib/vendor/x # Add one requirement per vendored package (or a comment if it is loaded on demand). # The vendored library 'rgen' is loaded on demand. # The vendored library 'pathspec' is loaded on demand. # The vendored library 'deep_merge' is loaded on demand. puppet-5.5.10/lib/puppet/vendor/load_semantic.rb0000644005276200011600000000007213417161722021521 0ustar jenkinsjenkins$: << File.join([File.dirname(__FILE__), "semantic/lib"]) puppet-5.5.10/lib/puppet/vendor/load_semantic_puppet.rb0000644005276200011600000000010113417161722023107 0ustar jenkinsjenkins$: << File.join([File.dirname(__FILE__), "semantic_puppet/lib"]) puppet-5.5.10/lib/puppet/vendor/semantic/0000755005276200011600000000000013417162176020202 5ustar jenkinsjenkinspuppet-5.5.10/lib/puppet/vendor/semantic/lib/0000755005276200011600000000000013417162176020750 5ustar jenkinsjenkinspuppet-5.5.10/lib/puppet/vendor/semantic/lib/semantic.rb0000644005276200011600000000040713417161722023075 0ustar jenkinsjenkinsrequire 'puppet/vendor/semantic_puppet/lib/semantic_puppet' $stderr.puts "Warning: Puppet's internal vendored libraries are Private APIs and can change without warning. The 'semantic' library has been replaced with 'semantic_puppet'." Semantic = SemanticPuppet puppet-5.5.10/lib/puppet/vendor/semantic_puppet/0000755005276200011600000000000013417162176021577 5ustar jenkinsjenkinspuppet-5.5.10/lib/puppet/vendor/semantic_puppet/lib/0000755005276200011600000000000013417162176022345 5ustar jenkinsjenkinspuppet-5.5.10/lib/puppet/vendor/semantic_puppet/lib/semantic_puppet.rb0000644005276200011600000000100513417161722026062 0ustar jenkinsjenkinsmodule SemanticPuppet locales_path = File.absolute_path('../locales', File.dirname(__FILE__)) # Only create a translation repository of the relevant translations exist if Puppet::FileSystem.exist?(File.join(locales_path, Puppet::GettextConfig.current_locale)) Puppet::GettextConfig.load_translations('semantic_puppet', locales_path, :po) end autoload :Version, 'semantic_puppet/version' autoload :VersionRange, 'semantic_puppet/version_range' autoload :Dependency, 'semantic_puppet/dependency' end puppet-5.5.10/lib/puppet/vendor/semantic_puppet/lib/semantic_puppet/0000755005276200011600000000000013417162176025545 5ustar jenkinsjenkinspuppet-5.5.10/lib/puppet/vendor/semantic_puppet/lib/semantic_puppet/dependency.rb0000644005276200011600000001412713417161722030211 0ustar jenkinsjenkinsrequire 'semantic_puppet' module SemanticPuppet module Dependency extend self autoload :Graph, 'semantic_puppet/dependency/graph' autoload :GraphNode, 'semantic_puppet/dependency/graph_node' autoload :ModuleRelease, 'semantic_puppet/dependency/module_release' autoload :Source, 'semantic_puppet/dependency/source' autoload :UnsatisfiableGraph, 'semantic_puppet/dependency/unsatisfiable_graph' # @!group Sources # @return [Array] a frozen copy of the {Source} list def sources (@sources ||= []).dup.freeze end # Appends a new {Source} to the current list. # @param source [Source] the {Source} to add # @return [void] def add_source(source) sources @sources << source nil end # Clears the current list of {Source}s. # @return [void] def clear_sources sources @sources.clear nil end # @!endgroup # Fetches a graph of modules and their dependencies from the currently # configured list of {Source}s. # # @todo Return a specialized "Graph" object. # @todo Allow for external constraints to be added to the graph. # @see #sources # @see #add_source # @see #clear_sources # # @param modules [{ String => String }] # @return [Graph] the root of a dependency graph def query(modules) constraints = Hash[modules.map { |k, v| [ k, VersionRange.parse(v) ] }] graph = Graph.new(constraints) fetch_dependencies(graph) return graph end # Given a graph result from {#query}, this method will resolve the graph of # dependencies, if possible, into a flat list of the best suited modules. If # the dependency graph does not have a suitable resolution, this method will # raise an exception to that effect. # # @param graph [Graph] the root of a dependency graph # @return [Array] the list of releases to act on def resolve(graph) catch :next do return walk(graph, graph.dependencies.dup) end raise UnsatisfiableGraph.new(graph) end # Fetches all available releases for the given module name. # # @param name [String] the module name to find releases for # @return [Array] the available releases def fetch_releases(name) releases = {} sources.each do |source| source.fetch(name).each do |dependency| releases[dependency.version] ||= dependency end end return releases.values end private # Iterates over a changing set of dependencies in search of the best # solution available. Fitness is specified as meeting all the constraints # placed on it, being {ModuleRelease#satisfied? satisfied}, and having the # greatest version number (with stability being preferred over prereleases). # # @todo Traversal order is not presently guaranteed. # # @param graph [Graph] the root of a dependency graph # @param dependencies [{ String => Array }] the dependencies # @param considering [Array] the set of releases being tested # @return [Array] the list of releases to use, if successful def walk(graph, dependencies, *considering) return considering if dependencies.empty? # Selecting a dependency from the collection... name = dependencies.keys.sort.first deps = dependencies.delete(name) # ... (and stepping over it if we've seen it before) ... unless (deps & considering).empty? return walk(graph, dependencies, *considering) end # ... we'll iterate through the list of possible versions in order. preferred_releases(deps).reverse_each do |dep| # We should skip any releases that violate any module's constraints. unless [graph, *considering].all? { |x| x.satisfies_constraints?(dep) } next end # We should skip over any releases that violate graph-level constraints. potential_solution = considering.dup << dep unless graph.satisfies_graph? potential_solution next end catch :next do # After adding any new dependencies and imposing our own constraints # on existing dependencies, we'll mark ourselves as "under # consideration" and recurse. merged = dependencies.merge(dep.dependencies) { |_,a,b| a & b } # If all subsequent dependencies resolved well, the recursive call # will return a completed dependency list. If there were problems # resolving our dependencies, we'll catch `:next`, which will cause # us to move to the next possibility. return walk(graph, merged, *potential_solution) end end # Once we've exhausted all of our possible versions, we know that our # last choice was unusable, so we'll unwind the stack and make a new # choice. throw :next end # Given a {ModuleRelease}, this method will iterate through the current # list of {Source}s to find the complete list of versions available for its # dependencies. # # @param node [GraphNode] the node to fetch details for # @return [void] def fetch_dependencies(node, cache = {}) node.dependency_names.each do |name| unless cache.key?(name) cache[name] = fetch_releases(name) cache[name].each { |dep| fetch_dependencies(dep, cache) } end node << cache[name] end end # Given a list of potential releases, this method returns the most suitable # releases for exploration. Only {ModuleRelease#satisfied? satisfied} # releases are considered, and releases with stable versions are preferred. # # @param releases [Array] a list of potential releases # @return [Array] releases open for consideration def preferred_releases(releases) satisfied = releases.select { |x| x.satisfied? } if satisfied.any? { |x| x.version.stable? } return satisfied.select { |x| x.version.stable? } else return satisfied end end end end puppet-5.5.10/lib/puppet/vendor/semantic_puppet/lib/semantic_puppet/dependency/0000755005276200011600000000000013417162176027663 5ustar jenkinsjenkinspuppet-5.5.10/lib/puppet/vendor/semantic_puppet/lib/semantic_puppet/dependency/graph.rb0000644005276200011600000000365013417161722031311 0ustar jenkinsjenkinsrequire 'semantic_puppet/dependency' module SemanticPuppet module Dependency class Graph include GraphNode attr_reader :modules # Create a new instance of a dependency graph. # # @param modules [{String => VersionRange}] the required module # set and their version constraints def initialize(modules = {}) @modules = modules.keys modules.each do |name, range| add_constraint('initialize', name, range.to_s) do |node| range === node.version end add_dependency(name) end end # Constrains graph solutions based on the given block. Graph constraints # are used to describe fundamental truths about the tooling or module # system (e.g.: module names contain a namespace component which is # dropped during install, so module names must be unique excluding the # namespace). # # @example Ensuring a single source for all modules # @graph.add_constraint('installed', mod.name) do |nodes| # nodes.count { |node| node.source } == 1 # end # # @see #considering_solution? # # @param source [String, Symbol] a name describing the source of the # constraint # @yieldparam nodes [Array] the nodes to test the constraint # against # @yieldreturn [Boolean] whether the node passed the constraint # @return [void] def add_graph_constraint(source, &block) constraints[:graph] << [ source, block ] end # Checks the proposed solution (or partial solution) against the graph's # constraints. # # @see #add_graph_constraint # # @return [Boolean] true if none of the graph constraints are violated def satisfies_graph?(solution) constraints[:graph].all? { |_, check| check[solution] } end end end end puppet-5.5.10/lib/puppet/vendor/semantic_puppet/lib/semantic_puppet/dependency/graph_node.rb0000644005276200011600000000630013417161722032311 0ustar jenkinsjenkinsrequire 'semantic_puppet/dependency' require 'set' module SemanticPuppet module Dependency module GraphNode include Comparable def name end # Determines whether the modules dependencies are satisfied by the known # releases. # # @return [Boolean] true if all dependencies are satisfied def satisfied? dependencies.none? { |_, v| v.empty? } end def children @_children ||= {} end def populate_children(nodes) if children.empty? nodes = nodes.select { |node| satisfies_dependency?(node) } nodes.each do |node| children[node.name] = node node.populate_children(nodes) end self.freeze end end # @api internal # @return [{ String => SortedSet }] the satisfactory # dependency nodes def dependencies @_dependencies ||= Hash.new { |h, k| h[k] = SortedSet.new } end # Adds the given dependency name to the list of dependencies. # # @param name [String] the dependency name # @return [void] def add_dependency(name) dependencies[name] end # @return [Array] the list of dependency names def dependency_names dependencies.keys end def constraints @_constraints ||= Hash.new { |h, k| h[k] = [] } end def constraints_for(name) return [] unless constraints.has_key?(name) constraints[name].map do |constraint| { :source => constraint[0], :description => constraint[1], :test => constraint[2], } end end # Constrains the named module to suitable releases, as determined by the # given block. # # @example Version-locking currently installed modules # installed_modules.each do |m| # @graph.add_constraint('installed', m.name, m.version) do |node| # m.version == node.version # end # end # # @param source [String, Symbol] a name describing the source of the # constraint # @param mod [String] the name of the module # @param desc [String] a description of the enforced constraint # @yieldparam node [GraphNode] the node to test the constraint against # @yieldreturn [Boolean] whether the node passed the constraint # @return [void] def add_constraint(source, mod, desc, &block) constraints["#{mod}"] << [ source, desc, block ] end def satisfies_dependency?(node) dependencies.key?(node.name) && satisfies_constraints?(node) end # @param release [ModuleRelease] the release to test def satisfies_constraints?(release) constraints_for(release.name).all? { |x| x[:test].call(release) } end def << (nodes) Array(nodes).each do |node| next unless dependencies.key?(node.name) if satisfies_dependency?(node) dependencies[node.name] << node end end return self end def <=>(other) name <=> other.name end end end end puppet-5.5.10/lib/puppet/vendor/semantic_puppet/lib/semantic_puppet/dependency/module_release.rb0000644005276200011600000000260213417161722033171 0ustar jenkinsjenkinsrequire 'semantic_puppet/dependency' module SemanticPuppet module Dependency class ModuleRelease include GraphNode attr_reader :name, :version # Create a new instance of a module release. # # @param source [SemanticPuppet::Dependency::Source] # @param name [String] # @param version [SemanticPuppet::Version] # @param dependencies [{String => SemanticPuppet::VersionRange}] def initialize(source, name, version, dependencies = {}) @source = source @name = name.freeze @version = version.freeze dependencies.each do |name, range| add_constraint('initialize', name, range.to_s) do |node| range === node.version end add_dependency(name) end end def priority @source.priority end def <=>(oth) our_key = [ priority, name, version ] their_key = [ oth.priority, oth.name, oth.version ] return our_key <=> their_key end def eql?(other) other.is_a?(ModuleRelease) && @name.eql?(other.name) && @version.eql?(other.version) && dependencies.eql?(other.dependencies) end alias == eql? def hash @name.hash ^ @version.hash end def to_s "#<#{self.class} #{name}@#{version}>" end end end end puppet-5.5.10/lib/puppet/vendor/semantic_puppet/lib/semantic_puppet/dependency/source.rb0000644005276200011600000000115713417161722031510 0ustar jenkinsjenkinsrequire 'semantic_puppet/dependency' module SemanticPuppet module Dependency class Source def self.priority 0 end def priority self.class.priority end def create_release(name, version, dependencies = {}) version = Version.parse(version) if version.is_a? String dependencies = dependencies.inject({}) do |hash, (key, value)| hash[key] = VersionRange.parse(value || '>= 0.0.0') hash[key] ||= VersionRange::EMPTY_RANGE hash end ModuleRelease.new(self, name, version, dependencies) end end end end ././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootpuppet-5.5.10/lib/puppet/vendor/semantic_puppet/lib/semantic_puppet/dependency/unsatisfiable_graph.rbpuppet-5.5.10/lib/puppet/vendor/semantic_puppet/lib/semantic_puppet/dependency/unsatisfiable_graph.r0000644005276200011600000000116113417161722034053 0ustar jenkinsjenkinsrequire 'semantic_puppet/dependency' module SemanticPuppet module Dependency class UnsatisfiableGraph < StandardError attr_reader :graph def initialize(graph) @graph = graph deps = sentence_from_list(graph.modules) super "Could not find satisfying releases for #{deps}" end private def sentence_from_list(list) case list.length when 1 list.first when 2 list.join(' and ') else list = list.dup list.push("and #{list.pop}") list.join(', ') end end end end end puppet-5.5.10/lib/puppet/vendor/semantic_puppet/lib/semantic_puppet/gem_version.rb0000644005276200011600000000005613417161722030404 0ustar jenkinsjenkinsmodule SemanticPuppet VERSION = '1.0.1' end puppet-5.5.10/lib/puppet/vendor/semantic_puppet/lib/semantic_puppet/version.rb0000644005276200011600000001417513417161722027563 0ustar jenkinsjenkinsrequire 'semantic_puppet' module SemanticPuppet # @note SemanticPuppet::Version subclasses Numeric so that it has sane Range # semantics in Ruby 1.9+. class Version < Numeric include Comparable class ValidationFailure < ArgumentError; end # Parse a Semantic Version string. # # @param ver [String] the version string to parse # @return [Version] a comparable {Version} object def self.parse(ver) match, major, minor, patch, prerelease, build = *ver.match(REGEX_FULL_RX) raise ValidationFailure, _("Unable to parse '%{version}' as a semantic version identifier") % {version: ver} unless match new(major.to_i, minor.to_i, patch.to_i, parse_prerelease(prerelease), parse_build(build)).freeze end # Validate a Semantic Version string. # # @param ver [String] the version string to validate # @return [bool] whether or not the string represents a valid Semantic Version def self.valid?(ver) match = ver.match(REGEX_FULL_RX) if match.nil? false else prerelease = match[4] prerelease.nil? || prerelease.split('.').all? { |x| !(x =~ /^0\d+$/) } end end def self.parse_build(build) build.nil? ? nil : build.split('.').freeze end def self.parse_prerelease(prerelease) return nil unless prerelease prerelease.split('.').map do |x| if x =~ /^\d+$/ raise ValidationFailure, _('Numeric pre-release identifiers MUST NOT contain leading zeroes') if x.length > 1 && x.start_with?('0') x.to_i else x end end.freeze end attr_reader :major, :minor, :patch def initialize(major, minor, patch, prerelease = nil, build = nil) @major = major @minor = minor @patch = patch @prerelease = prerelease @build = build end def next(part) case part when :major self.class.new(@major.next, 0, 0) when :minor self.class.new(@major, @minor.next, 0) when :patch self.class.new(@major, @minor, @patch.next) end end # @return [String] the `prerelease` identifier as a string without the leading '-' def prerelease (@prerelease.nil? || @prerelease.empty?) ? nil : @prerelease.join('.') end # @return [Boolean] true if this is a stable release def stable? @prerelease.nil? || @prerelease.empty? end # @return [Version] this version stripped from any prerelease identifier. def to_stable @prerelease.nil? ? self : Version.new(@major, @minor, @patch, nil, @build) end # @return [String] the `build` identifier as a string without the leading '+' def build (@build.nil? || @build.empty?) ? nil : @build.join('.') end def <=>(other) return nil unless other.is_a?(Version) cmp = @major <=> other.major if cmp == 0 cmp = @minor <=> other.minor if cmp == 0 cmp = @patch <=> other.patch if cmp == 0 cmp = compare_prerelease(other) end end end cmp end def eql?(other) other.is_a?(Version) && @major.eql?(other.major) && @minor.eql?(other.minor) && @patch.eql?(other.patch) && @prerelease.eql?(other.instance_variable_get(:@prerelease)) && @build.eql?(other.instance_variable_get(:@build)) end alias == eql? def to_s s = "#{@major}.#{@minor}.#{@patch}" # Appending the @prerelease and @build in a thoroughly tested and optimized # way. Using interpolations and/or array joins may look simpler but will slows # things down. Don't change this code without measuring performance of new # solution. unless @prerelease.nil? s << '-' << @prerelease[0].to_s i = 0 l = @prerelease.length while (i += 1) < l s << '.' << @prerelease[i].to_s end end unless @build.nil? s << '+' << @build[0].to_s i = 0 l = @build.length while (i += 1) < l s << '.' << @build[i].to_s end end s end alias inspect to_s def hash (((((@major * 0x100) ^ @minor) * 0x100) ^ @patch) * 0x100) ^ @prerelease.hash end def compare_prerelease(other) mine = @prerelease # Need to use the instance variable here to avoid getting a string yours = other.instance_variable_get(:@prerelease) # A version that has a prerelease is always less than a version that doesn't if mine.nil? yours.nil? ? 0 : 1 elsif yours.nil? -1 else # Compare all prerelease identifier segments that can be compared. Should # all segments compare equal up to the point where one of the prereleases # have no more segments, then the one with more segments is greater. your_max = yours.size mine.each_with_index do |x, idx| # 'mine' win if 'your' list of segments is exhausted return 1 if idx >= your_max y = yours[idx] # Integer always wins over String cmp = if x.is_a?(Integer) y.is_a?(Integer) ? x <=> y : -1 elsif y.is_a?(Integer) 1 else x <=> y end # No need to continue if a diff is found return cmp unless cmp == 0 end # All segments, up to the point where at least one list of segment is exhausted, # compared equal. The one with the highest segment count wins. mine.size <=> your_max end end # Version string matching regexes REGEX_NUMERIC = '(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)'.freeze # Major . Minor . Patch REGEX_PRE = '(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?'.freeze # Prerelease REGEX_BUILD = '(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?'.freeze # Build REGEX_FULL = REGEX_NUMERIC + REGEX_PRE + REGEX_BUILD.freeze REGEX_FULL_RX = /\A#{REGEX_FULL}\Z/.freeze # The lowest precedence Version possible MIN = self.new(0, 0, 0, [].freeze).freeze # The highest precedence Version possible MAX = self.new(Float::INFINITY, 0, 0).freeze end end puppet-5.5.10/lib/puppet/vendor/semantic_puppet/lib/semantic_puppet/version_range.rb0000644005276200011600000005331313417161722030734 0ustar jenkinsjenkinsrequire 'semantic_puppet' module SemanticPuppet # A Semantic Version Range. # # @see https://github.com/npm/node-semver for full specification # @api public class VersionRange UPPER_X = 'X'.freeze LOWER_X = 'x'.freeze STAR = '*'.freeze NR = '0|[1-9][0-9]*'.freeze XR = '(x|X|\*|' + NR + ')'.freeze XR_NC = '(?:x|X|\*|' + NR + ')'.freeze PART = '(?:[0-9A-Za-z-]+)'.freeze PARTS = PART + '(?:\.' + PART + ')*'.freeze QUALIFIER = '(?:-(' + PARTS + '))?(?:\+(' + PARTS + '))?'.freeze QUALIFIER_NC = '(?:-' + PARTS + ')?(?:\+' + PARTS + ')?'.freeze PARTIAL = XR_NC + '(?:\.' + XR_NC + '(?:\.' + XR_NC + QUALIFIER_NC + ')?)?'.freeze # The ~> isn't in the spec but allowed SIMPLE = '([<>=~^]|<=|>=|~>|~=)?(' + PARTIAL + ')'.freeze SIMPLE_EXPR = /\A#{SIMPLE}\z/.freeze SIMPLE_WITH_EXTRA_WS = '([<>=~^]|<=|>=)?\s+(' + PARTIAL + ')'.freeze SIMPLE_WITH_EXTRA_WS_EXPR = /\A#{SIMPLE_WITH_EXTRA_WS}\z/.freeze HYPHEN = '(' + PARTIAL + ')\s+-\s+(' + PARTIAL + ')'.freeze HYPHEN_EXPR = /\A#{HYPHEN}\z/.freeze PARTIAL_EXPR = /\A#{XR}(?:\.#{XR}(?:\.#{XR}#{QUALIFIER})?)?\z/.freeze LOGICAL_OR = /\s*\|\|\s*/.freeze RANGE_SPLIT = /\s+/.freeze # Parses a version range string into a comparable {VersionRange} instance. # # Currently parsed version range string may take any of the following: # forms: # # * Regular Semantic Version strings # * ex. `"1.0.0"`, `"1.2.3-pre"` # * Partial Semantic Version strings # * ex. `"1.0.x"`, `"1"`, `"2.X"`, `"3.*"`, # * Inequalities # * ex. `"> 1.0.0"`, `"<3.2.0"`, `">=4.0.0"` # * Approximate Caret Versions # * ex. `"^1"`, `"^3.2"`, `"^4.1.0"` # * Approximate Tilde Versions # * ex. `"~1.0.0"`, `"~ 3.2.0"`, `"~4.0.0"` # * Inclusive Ranges # * ex. `"1.0.0 - 1.3.9"` # * Range Intersections # * ex. `">1.0.0 <=2.3.0"` # * Combined ranges # * ex, `">=1.0.0 <2.3.0 || >=2.5.0 <3.0.0"` # # @param range_string [String] the version range string to parse # @param strict_semver [Boolean] `false` if pre-releases should be included even when not explicitly appointed # @return [VersionRange] a new {VersionRange} instance # @api public def self.parse(range_string, strict_semver = true) # Remove extra whitespace after operators. Such whitespace should not cause a split range_set = range_string.gsub(/([><=~^])(?:\s+|\s*v)/, '\1') ranges = range_set.split(LOGICAL_OR) return ALL_RANGE if ranges.empty? new(ranges.map do |range| if range =~ HYPHEN_EXPR MinMaxRange.create(GtEqRange.new(parse_version($1)), LtEqRange.new(parse_version($2))) else # Split on whitespace simples = range.split(RANGE_SPLIT).map do |simple| match_data = SIMPLE_EXPR.match(simple) raise ArgumentError, _("Unparsable version range: \"%{range}\"") % { range: range_string } unless match_data operand = match_data[2] # Case based on operator case match_data[1] when '~', '~>', '~=' parse_tilde(operand) when '^' parse_caret(operand) when '>' parse_gt_version(operand) when '>=' GtEqRange.new(parse_version(operand)) when '<' LtRange.new(parse_version(operand)) when '<=' parse_lteq_version(operand) when '=' parse_xrange(operand) else parse_xrange(operand) end end simples.size == 1 ? simples[0] : MinMaxRange.create(*simples) end end.uniq, range_string, strict_semver).freeze end def self.parse_partial(expr) match_data = PARTIAL_EXPR.match(expr) raise ArgumentError, _("Unparsable version range: \"%{expr}\"") % { expr: expr } unless match_data match_data end private_class_method :parse_partial def self.parse_caret(expr) match_data = parse_partial(expr) major = digit(match_data[1]) major == 0 ? allow_patch_updates(major, match_data) : allow_minor_updates(major, match_data) end private_class_method :parse_caret def self.parse_tilde(expr) match_data = parse_partial(expr) allow_patch_updates(digit(match_data[1]), match_data) end private_class_method :parse_tilde def self.parse_xrange(expr) match_data = parse_partial(expr) allow_patch_updates(digit(match_data[1]), match_data, false) end private_class_method :parse_xrange def self.allow_patch_updates(major, match_data, tilde_or_caret = true) return AllRange::SINGLETON unless major minor = digit(match_data[2]) return MinMaxRange.new(GtEqRange.new(Version.new(major, 0, 0)), LtRange.new(Version.new(major + 1, 0, 0))) unless minor patch = digit(match_data[3]) return MinMaxRange.new(GtEqRange.new(Version.new(major, minor, 0)), LtRange.new(Version.new(major, minor + 1, 0))) unless patch version = Version.new(major, minor, patch, Version.parse_prerelease(match_data[4]), Version.parse_build(match_data[5])) return EqRange.new(version) unless tilde_or_caret MinMaxRange.new(GtEqRange.new(version), LtRange.new(Version.new(major, minor + 1, 0))) end private_class_method :allow_patch_updates def self.allow_minor_updates(major, match_data) return AllRange.SINGLETON unless major minor = digit(match_data[2]) if minor patch = digit(match_data[3]) if patch.nil? MinMaxRange.new(GtEqRange.new(Version.new(major, minor, 0)), LtRange.new(Version.new(major + 1, 0, 0))) else if match_data[4].nil? MinMaxRange.new(GtEqRange.new(Version.new(major, minor, patch)), LtRange.new(Version.new(major + 1, 0, 0))) else MinMaxRange.new( GtEqRange.new( Version.new(major, minor, patch, Version.parse_prerelease(match_data[4]), Version.parse_build(match_data[5]))), LtRange.new(Version.new(major + 1, 0, 0))) end end else MinMaxRange.new(GtEqRange.new(Version.new(major, 0, 0)), LtRange.new(Version.new(major + 1, 0, 0))) end end private_class_method :allow_minor_updates def self.digit(str) (str.nil? || UPPER_X == str || LOWER_X == str || STAR == str) ? nil : str.to_i end private_class_method :digit def self.parse_version(expr) match_data = parse_partial(expr) major = digit(match_data[1]) || 0 minor = digit(match_data[2]) || 0 patch = digit(match_data[3]) || 0 Version.new(major, minor, patch, Version.parse_prerelease(match_data[4]), Version.parse_build(match_data[5])) end private_class_method :parse_version def self.parse_gt_version(expr) match_data = parse_partial(expr) major = digit(match_data[1]) return LtRange::MATCH_NOTHING unless major minor = digit(match_data[2]) return GtEqRange.new(Version.new(major + 1, 0, 0)) unless minor patch = digit(match_data[3]) return GtEqRange.new(Version.new(major, minor + 1, 0)) unless patch return GtRange.new(Version.new(major, minor, patch, Version.parse_prerelease(match_data[4]), Version.parse_build(match_data[5]))) end private_class_method :parse_gt_version def self.parse_lteq_version(expr) match_data = parse_partial(expr) major = digit(match_data[1]) return AllRange.SINGLETON unless major minor = digit(match_data[2]) return LtRange.new(Version.new(major + 1, 0, 0)) unless minor patch = digit(match_data[3]) return LtRange.new(Version.new(major, minor + 1, 0)) unless patch return LtEqRange.new(Version.new(major, minor, patch, Version.parse_prerelease(match_data[4]), Version.parse_build(match_data[5]))) end private_class_method :parse_lteq_version # Provides read access to the ranges. For internal use only # @api private attr_reader :ranges # Creates a new version range # @overload initialize(from, to, exclude_end = false) # Creates a new instance using ruby `Range` semantics # @param begin [String,Version] the version denoting the start of the range (always inclusive) # @param end [String,Version] the version denoting the end of the range # @param exclude_end [Boolean] `true` if the `end` version should be excluded from the range # @overload initialize(ranges, string) # Creates a new instance based on parsed content. For internal use only # @param ranges [Array] the ranges to include in this range # @param string [String] the original string representation that was parsed to produce the ranges # @param strict_semver [Boolean] `false` if pre-releases should be included even when not explicitly appointed # # @api private def initialize(ranges, string, exclude_end = nil) if ranges.is_a?(Array) @strict_semver = exclude_end.nil? ? true : exclude_end else lb = GtEqRange.new(ranges) if exclude_end ub = LtRange.new(string) string = ">=#{string} <#{ranges}" else ub = LtEqRange.new(string) string = "#{string} - #{ranges}" end ranges = [MinMaxRange.create(lb, ub)] @strict_semver = true end ranges.compact! merge_happened = true while ranges.size > 1 && merge_happened # merge ranges if possible merge_happened = false result = [] until ranges.empty? unmerged = [] x = ranges.pop result << ranges.reduce(x) do |memo, y| merged = memo.merge(y) if merged.nil? unmerged << y else merge_happened = true memo = merged end memo end ranges = unmerged end ranges = result.reverse! end ranges = [LtRange::MATCH_NOTHING] if ranges.empty? @ranges = ranges @string = string.nil? ? ranges.join(' || ') : string end def eql?(range) range.is_a?(VersionRange) && @ranges.eql?(range.ranges) end alias == eql? def hash @ranges.hash end # Returns the version that denotes the beginning of this range. # # Since this really is an OR between disparate ranges, it may have multiple beginnings. This method # returns `nil` if that is the case. # # @return [Version] the beginning of the range, or `nil` if there are multiple beginnings # @api public def begin @ranges.size == 1 ? @ranges[0].begin : nil end # Returns the version that denotes the end of this range. # # Since this really is an OR between disparate ranges, it may have multiple ends. This method # returns `nil` if that is the case. # # @return [Version] the end of the range, or `nil` if there are multiple ends # @api public def end @ranges.size == 1 ? @ranges[0].end : nil end # Returns `true` if the beginning is excluded from the range. # # Since this really is an OR between disparate ranges, it may have multiple beginnings. This method # returns `nil` if that is the case. # # @return [Boolean] `true` if the beginning is excluded from the range, `false` if included, or `nil` if there are multiple beginnings # @api public def exclude_begin? @ranges.size == 1 ? @ranges[0].exclude_begin? : nil end # Returns `true` if the end is excluded from the range. # # Since this really is an OR between disparate ranges, it may have multiple ends. This method # returns `nil` if that is the case. # # @return [Boolean] `true` if the end is excluded from the range, `false` if not, or `nil` if there are multiple ends # @api public def exclude_end? @ranges.size == 1 ? @ranges[0].exclude_end? : nil end # @return [Boolean] `true` if the given version is included in the range # @api public def include?(version) if @strict_semver @ranges.any? { |range| range.include?(version) && (version.stable? || range.test_prerelease?(version)) } else @ranges.any? { |range| range.include?(version) || !version.stable? && range.stable? && range.include?(version.to_stable) } end end alias member? include? alias cover? include? alias === include? # Computes the intersection of a pair of ranges. If the ranges have no # useful intersection, an empty range is returned. # # @param other [VersionRange] the range to intersect with # @return [VersionRange] the common subset # @api public def intersection(other) raise ArgumentError, _("value must be a %{type}") % { :type => self.class.name } unless other.is_a?(VersionRange) result = @ranges.map { |range| other.ranges.map { |o_range| range.intersection(o_range) } }.flatten result.compact! result.uniq! result.empty? ? EMPTY_RANGE : VersionRange.new(result, nil) end alias :& :intersection # Returns a string representation of this range. This will be the string that was used # when the range was parsed. # # @return [String] a range expression representing this VersionRange # @api public def to_s @string end # Returns a canonical string representation of this range, assembled from the internal # matchers. # # @return [String] a range expression representing this VersionRange # @api public def inspect @ranges.join(' || ') end # @api private class AbstractRange def include?(_) true end def begin Version::MIN end def end Version::MAX end def exclude_begin? false end def exclude_end? false end def eql?(other) other.class.eql?(self.class) end def ==(other) eql?(other) end def lower_bound? false end def upper_bound? false end # Merge two ranges so that the result matches the intersection of all matching versions. # # @param range [AbstractRange] the range to intersect with # @return [AbstractRange,nil] the intersection between the ranges # # @api private def intersection(range) cmp = self.begin <=> range.end if cmp > 0 nil elsif cmp == 0 exclude_begin? || range.exclude_end? ? nil : EqRange.new(self.begin) else cmp = range.begin <=> self.end if cmp > 0 nil elsif cmp == 0 range.exclude_begin? || exclude_end? ? nil : EqRange.new(range.begin) else cmp = self.begin <=> range.begin min = if cmp < 0 range elsif cmp > 0 self else self.exclude_begin? ? self : range end cmp = self.end <=> range.end max = if cmp > 0 range elsif cmp < 0 self else self.exclude_end? ? self : range end if !max.upper_bound? min elsif !min.lower_bound? max else MinMaxRange.new(min, max) end end end end # Merge two ranges so that the result matches the sum of all matching versions. A merge # is only possible when the ranges are either adjacent or have an overlap. # # @param other [AbstractRange] the range to merge with # @return [AbstractRange,nil] the result of the merge # # @api private def merge(other) if include?(other.begin) || other.include?(self.begin) cmp = self.begin <=> other.begin if cmp < 0 min = self.begin excl_begin = exclude_begin? elsif cmp > 0 min = other.begin excl_begin = other.exclude_begin? else min = self.begin excl_begin = exclude_begin? && other.exclude_begin? end cmp = self.end <=> other.end if cmp > 0 max = self.end excl_end = self.exclude_end? elsif cmp < 0 max = other.end excl_end = other.exclude_end? else max = self.end excl_end = exclude_end && other.exclude_end? end MinMaxRange.create(excl_begin ? GtRange.new(min) : GtEqRange.new(min), excl_end ? LtRange.new(max) : LtEqRange.new(max)) elsif exclude_end? && !other.exclude_begin? && self.end == other.begin # Adjacent, self before other from_to(self, other) elsif other.exclude_end? && !exclude_begin? && other.end == self.begin # Adjacent, other before self from_to(other, self) elsif !exclude_end? && !other.exclude_begin? && self.end.next(:patch) == other.begin # Adjacent, self before other from_to(self, other) elsif !other.exclude_end? && !exclude_begin? && other.end.next(:patch) == self.begin # Adjacent, other before self from_to(other, self) else # No overlap nil end end # Checks if this matcher accepts a prerelease with the same major, minor, patch triple as the given version. Only matchers # where this has been explicitly stated will respond `true` to this method # # @return [Boolean] `true` if this matcher accepts a prerelease with the tuple from the given version def test_prerelease?(_) false end def stable? false end private def from_to(a, b) MinMaxRange.create(a.exclude_begin? ? GtRange.new(a.begin) : GtEqRange.new(a.begin), b.exclude_end? ? LtRange.new(b.end) : LtEqRange.new(b.end)) end end # @api private class AllRange < AbstractRange SINGLETON = AllRange.new def intersection(range) range end def merge(range) self end def test_prerelease?(_) true end def stable? true end def to_s '*' end end # @api private class MinMaxRange < AbstractRange attr_reader :min, :max def self.create(*ranges) ranges.reduce { |memo, range| memo.intersection(range) } end def initialize(min, max) @min = min.is_a?(MinMaxRange) ? min.min : min @max = max.is_a?(MinMaxRange) ? max.max : max end def begin @min.begin end def end @max.end end def exclude_begin? @min.exclude_begin? end def exclude_end? @max.exclude_end? end def eql?(other) super && @min.eql?(other.min) && @max.eql?(other.max) end def hash @min.hash ^ @max.hash end def include?(version) @min.include?(version) && @max.include?(version) end def lower_bound? @min.lower_bound? end def upper_bound? @max.upper_bound? end def test_prerelease?(version) @min.test_prerelease?(version) || @max.test_prerelease?(version) end def stable? @min.stable? && @max.stable? end def to_s "#{@min} #{@max}" end alias inspect to_s end # @api private class ComparatorRange < AbstractRange attr_reader :version def initialize(version) @version = version end def eql?(other) super && @version.eql?(other.version) end def hash @class.hash ^ @version.hash end # Checks if this matcher accepts a prerelease with the same major, minor, patch triple as the given version def test_prerelease?(version) !@version.stable? && @version.major == version.major && @version.minor == version.minor && @version.patch == version.patch end def stable? @version.stable? end end # @api private class GtRange < ComparatorRange def include?(version) version > @version end def exclude_begin? true end def begin @version end def lower_bound? true end def to_s ">#{@version}" end end # @api private class GtEqRange < ComparatorRange def include?(version) version >= @version end def begin @version end def lower_bound? @version != Version::MIN end def to_s ">=#{@version}" end end # @api private class LtRange < ComparatorRange MATCH_NOTHING = LtRange.new(Version::MIN) def include?(version) version < @version end def exclude_end? true end def end @version end def upper_bound? true end def to_s self.equal?(MATCH_NOTHING) ? '<0.0.0' : "<#{@version}" end end # @api private class LtEqRange < ComparatorRange def include?(version) version <= @version end def end @version end def upper_bound? @version != Version::MAX end def to_s "<=#{@version}" end end # @api private class EqRange < ComparatorRange def include?(version) version == @version end def begin @version end def lower_bound? @version != Version::MIN end def upper_bound? @version != Version::MAX end def end @version end def to_s @version.to_s end end # A range that matches no versions EMPTY_RANGE = VersionRange.new([], nil).freeze ALL_RANGE = VersionRange.new([AllRange::SINGLETON], '*') end end puppet-5.5.10/lib/puppet/vendor/semantic_puppet/locales/0000755005276200011600000000000013417162176023221 5ustar jenkinsjenkinspuppet-5.5.10/lib/puppet/vendor/semantic_puppet/locales/config.yaml0000644005276200011600000000152513417161722025351 0ustar jenkinsjenkins--- # This is the project-specific configuration file for setting up # fast_gettext for your project. gettext: # This is used for the name of the .pot and .po files; they will be # called .pot? project_name: 'semantic_puppet' # This is used in comments in the .pot and .po files to indicate what # project the files belong to and should be a little more descriptive than # package_name: Semantic Puppet Gem # The locale that the default messages in the .pot file are in default_locale: en # The email used for sending bug reports. bugs_address: docs@puppetlabs.com # The holder of the copyright. copyright_holder: Puppet, Inc. # Patterns for +Dir.glob+ used to find all files that might contain # translatable content, relative to the project root directory source_files: - 'lib/**/*.rb' puppet-5.5.10/lib/puppet/application.rb0000644005276200011600000004126313417161721017733 0ustar jenkinsjenkinsrequire 'optparse' require 'puppet/util/command_line' require 'puppet/util/constant_inflector' require 'puppet/error' require 'puppet/application_support' module Puppet # This class handles all the aspects of a Puppet application/executable # * setting up options # * setting up logs # * choosing what to run # * representing execution status # # === Usage # An application is a subclass of Puppet::Application. # # For legacy compatibility, # Puppet::Application[:example].run # is equivalent to # Puppet::Application::Example.new.run # # # class Puppet::Application::Example < Puppet::Application # # def preinit # # perform some pre initialization # @all = false # end # # # run_command is called to actually run the specified command # def run_command # send Puppet::Util::CommandLine.new.args.shift # end # # # option uses metaprogramming to create a method # # and also tells the option parser how to invoke that method # option("--arg ARGUMENT") do |v| # @args << v # end # # option("--debug", "-d") do |v| # @debug = v # end # # option("--all", "-a:) do |v| # @all = v # end # # def handle_unknown(opt,arg) # # last chance to manage an option # ... # # let's say to the framework we finally handle this option # true # end # # def read # # read action # end # # def write # # writeaction # end # # end # # === Preinit # The preinit block is the first code to be called in your application, before option parsing, # setup or command execution. # # === Options # Puppet::Application uses +OptionParser+ to manage the application options. # Options are defined with the +option+ method to which are passed various # arguments, including the long option, the short option, a description... # Refer to +OptionParser+ documentation for the exact format. # * If the option method is given a block, this one will be called whenever # the option is encountered in the command-line argument. # * If the option method has no block, a default functionality will be used, that # stores the argument (or true/false if the option doesn't require an argument) in # the global (to the application) options array. # * If a given option was not defined by a the +option+ method, but it exists as a Puppet settings: # * if +unknown+ was used with a block, it will be called with the option name and argument # * if +unknown+ wasn't used, then the option/argument is handed to Puppet.settings.handlearg for # a default behavior # # --help is managed directly by the Puppet::Application class, but can be overridden. # # === Setup # Applications can use the setup block to perform any initialization. # The default +setup+ behaviour is to: read Puppet configuration and manage log level and destination # # === What and how to run # If the +dispatch+ block is defined it is called. This block should return the name of the registered command # to be run. # If it doesn't exist, it defaults to execute the +main+ command if defined. # # === Execution state # The class attributes/methods of Puppet::Application serve as a global place to set and query the execution # status of the application: stopping, restarting, etc. The setting of the application status does not directly # affect its running status; it's assumed that the various components within the application will consult these # settings appropriately and affect their own processing accordingly. Control operations (signal handlers and # the like) should set the status appropriately to indicate to the overall system that it's the process of # stopping or restarting (or just running as usual). # # So, if something in your application needs to stop the process, for some reason, you might consider: # # def stop_me! # # indicate that we're stopping # Puppet::Application.stop! # # ...do stuff... # end # # And, if you have some component that involves a long-running process, you might want to consider: # # def my_long_process(giant_list_to_munge) # giant_list_to_munge.collect do |member| # # bail if we're stopping # return if Puppet::Application.stop_requested? # process_member(member) # end # end class Application require 'puppet/util' include Puppet::Util DOCPATTERN = ::File.expand_path(::File.dirname(__FILE__) + "/util/command_line/*" ) CommandLineArgs = Struct.new(:subcommand_name, :args) @loader = Puppet::Util::Autoload.new(self, 'puppet/application') class << self include Puppet::Util attr_accessor :run_status def clear! self.run_status = nil end def stop! self.run_status = :stop_requested end def restart! self.run_status = :restart_requested end # Indicates that Puppet::Application.restart! has been invoked and components should # do what is necessary to facilitate a restart. def restart_requested? :restart_requested == run_status end # Indicates that Puppet::Application.stop! has been invoked and components should do what is necessary # for a clean stop. def stop_requested? :stop_requested == run_status end # Indicates that one of stop! or start! was invoked on Puppet::Application, and some kind of process # shutdown/short-circuit may be necessary. def interrupted? [:restart_requested, :stop_requested].include? run_status end # Indicates that Puppet::Application believes that it's in usual running run_mode (no stop/restart request # currently active). def clear? run_status.nil? end # Only executes the given block if the run status of Puppet::Application is clear (no restarts, stops, # etc. requested). # Upon block execution, checks the run status again; if a restart has been requested during the block's # execution, then controlled_run will send a new HUP signal to the current process. # Thus, long-running background processes can potentially finish their work before a restart. def controlled_run(&block) return unless clear? result = block.call Process.kill(:HUP, $PID) if restart_requested? result end # used to declare code that handle an option def option(*options, &block) long = options.find { |opt| opt =~ /^--/ }.gsub(/^--(?:\[no-\])?([^ =]+).*$/, '\1' ).gsub('-','_') fname = "handle_#{long}".intern if (block_given?) define_method(fname, &block) else define_method(fname) do |value| self.options["#{long}".to_sym] = value end end self.option_parser_commands << [options, fname] end def banner(banner = nil) @banner ||= banner end def option_parser_commands @option_parser_commands ||= ( superclass.respond_to?(:option_parser_commands) ? superclass.option_parser_commands.dup : [] ) @option_parser_commands end # @return [Array] the names of available applications # @api public def available_application_names @loader.files_to_load.map do |fn| ::File.basename(fn, '.rb') end.uniq end # Finds the class for a given application and loads the class. This does # not create an instance of the application, it only gets a handle to the # class. The code for the application is expected to live in a ruby file # `puppet/application/#{name}.rb` that is available on the `$LOAD_PATH`. # # @param application_name [String] the name of the application to find (eg. "apply"). # @return [Class] the Class instance of the application that was found. # @raise [Puppet::Error] if the application class was not found. # @raise [LoadError] if there was a problem loading the application file. # @api public def find(application_name) begin require @loader.expand(application_name.to_s.downcase) rescue LoadError => e Puppet.log_and_raise(e, _("Unable to find application '%{application_name}'. %{error}") % { application_name: application_name, error: e }) end class_name = Puppet::Util::ConstantInflector.file2constant(application_name.to_s) clazz = try_load_class(class_name) ################################################################ #### Begin 2.7.x backward compatibility hack; #### eventually we need to issue a deprecation warning here, #### and then get rid of this stanza in a subsequent release. ################################################################ if (clazz.nil?) class_name = application_name.capitalize clazz = try_load_class(class_name) end ################################################################ #### End 2.7.x backward compatibility hack ################################################################ if clazz.nil? raise Puppet::Error.new(_("Unable to load application class '%{class_name}' from file 'puppet/application/%{application_name}.rb'") % { class_name: class_name, application_name: application_name }) end return clazz end # Given the fully qualified name of a class, attempt to get the class instance. # @param [String] class_name the fully qualified name of the class to try to load # @return [Class] the Class instance, or nil? if it could not be loaded. def try_load_class(class_name) return self.const_defined?(class_name) ? const_get(class_name) : nil end private :try_load_class def [](name) find(name).new end # Sets or gets the run_mode name. Sets the run_mode name if a mode_name is # passed. Otherwise, gets the run_mode or a default run_mode # def run_mode( mode_name = nil) if mode_name Puppet.settings.preferred_run_mode = mode_name end return @run_mode if @run_mode and not mode_name require 'puppet/util/run_mode' @run_mode = Puppet::Util::RunMode[ mode_name || Puppet.settings.preferred_run_mode ] end # Sets environment_mode name # @param mode_name [Symbol] The name of the environment mode to run in. May # be one of :local, :remote, or :not_required. This impacts where the # application looks for its specified environment. If :not_required or # :remote are set, the application will not fail if the environment does # not exist on the local filesystem. def environment_mode(mode_name) raise Puppet::Error, _("Invalid environment mode '%{mode_name}'") % { mode_name: mode_name } unless [:local, :remote, :not_required].include?(mode_name) @environment_mode = mode_name end # Gets environment_mode name. If none is set with `environment_mode=`, # default to :local. def get_environment_mode @environment_mode || :local end # This is for testing only def clear_everything_for_tests @run_mode = @banner = @run_status = @option_parser_commands = nil end end attr_reader :options, :command_line # Every app responds to --version # See also `lib/puppet/util/command_line.rb` for some special case early # handling of this. option("--version", "-V") do |arg| puts "#{Puppet.version}" exit(0) end # Every app responds to --help option("--help", "-h") do |v| puts help exit(0) end def app_defaults() Puppet::Settings.app_defaults_for_run_mode(self.class.run_mode).merge( :name => name ) end def initialize_app_defaults() Puppet.settings.initialize_app_defaults(app_defaults) end # override to execute code before running anything else def preinit end def initialize(command_line = Puppet::Util::CommandLine.new) @command_line = CommandLineArgs.new(command_line.subcommand_name, command_line.args.dup) @options = {} end # call in setup of subclass to deprecate an application def deprecate @deprecated = true end def deprecated? @deprecated end # Execute the application. # @api public # @return [void] def run # I don't really like the names of these lifecycle phases. It would be nice to change them to some more meaningful # names, and make deprecated aliases. --cprice 2012-03-16 exit_on_fail(_("Could not get application-specific default settings")) do initialize_app_defaults end Puppet::ApplicationSupport.push_application_context(self.class.run_mode, self.class.get_environment_mode) exit_on_fail(_("Could not initialize")) { preinit } exit_on_fail(_("Could not parse application options")) { parse_options } exit_on_fail(_("Could not prepare for execution")) { setup } if deprecated? Puppet.deprecation_warning(_("`puppet %{name}` is deprecated and will be removed in a future release.") % { name: name }) end exit_on_fail(_("Could not configure routes from %{route_file}") % { route_file: Puppet[:route_file] }) { configure_indirector_routes } exit_on_fail(_("Could not log runtime debug info")) { log_runtime_environment } exit_on_fail(_("Could not run")) { run_command } end def main raise NotImplementedError, _("No valid command or main") end def run_command main end def setup setup_logs end def setup_logs handle_logdest_arg(Puppet[:logdest]) unless options[:setdest] if options[:debug] || options[:verbose] Puppet::Util::Log.newdestination(:console) end end set_log_level Puppet::Util::Log.setup_default unless options[:setdest] end def set_log_level(opts = nil) opts ||= options if opts[:debug] Puppet::Util::Log.level = :debug elsif opts[:verbose] && !Puppet::Util::Log.sendlevel?(:info) Puppet::Util::Log.level = :info end end def handle_logdest_arg(arg) return if options[:setdest] || arg.nil? begin Puppet[:logdest] = arg Puppet::Util::Log.newdestination(arg) options[:setdest] = true rescue => detail Puppet.log_exception(detail) end end def configure_indirector_routes Puppet::ApplicationSupport.configure_indirector_routes(name.to_s) end # Output basic information about the runtime environment for debugging # purposes. # # @api public # # @param extra_info [Hash{String => #to_s}] a flat hash of extra information # to log. Intended to be passed to super by subclasses. # @return [void] def log_runtime_environment(extra_info=nil) runtime_info = { 'puppet_version' => Puppet.version, 'ruby_version' => RUBY_VERSION, 'run_mode' => self.class.run_mode.name, } runtime_info['default_encoding'] = Encoding.default_external runtime_info.merge!(extra_info) unless extra_info.nil? Puppet.debug 'Runtime environment: ' + runtime_info.map{|k,v| k + '=' + v.to_s}.join(', ') end def parse_options # Create an option parser option_parser = OptionParser.new(self.class.banner) # Here we're building up all of the options that the application may need to handle. The main # puppet settings defined in "defaults.rb" have already been parsed once (in command_line.rb) by # the time we get here; however, our app may wish to handle some of them specially, so we need to # make the parser aware of them again. We might be able to make this a bit more efficient by # re-using the parser object that gets built up in command_line.rb. --cprice 2012-03-16 # Add all global options to it. Puppet.settings.optparse_addargs([]).each do |option| option_parser.on(*option) do |arg| handlearg(option[0], arg) end end # Add options that are local to this application, which were # created using the "option()" metaprogramming method. If there # are any conflicts, this application's options will be favored. self.class.option_parser_commands.each do |options, fname| option_parser.on(*options) do |value| # Call the method that "option()" created. self.send(fname, value) end end # Scan command line. We just hand any exceptions to our upper levels, # rather than printing help and exiting, so that we can meaningfully # respond with context-sensitive help if we want to. --daniel 2011-04-12 option_parser.parse!(self.command_line.args) end def handlearg(opt, val) opt, val = Puppet::Settings.clean_opt(opt, val) send(:handle_unknown, opt, val) if respond_to?(:handle_unknown) end # this is used for testing def self.exit(code) exit(code) end def name self.class.to_s.sub(/.*::/,"").downcase.to_sym end def help _("No help available for puppet %{app_name}") % { app_name: name } end # The description used in top level `puppet help` output # If left empty in implementations, we will attempt to extract # the summary from the help text itself. def summary "" end end end puppet-5.5.10/lib/puppet/application_support.rb0000644005276200011600000000523613417161721021527 0ustar jenkinsjenkinsrequire 'yaml' require 'puppet' require 'puppet/node/environment' require 'puppet/file_system' require 'puppet/indirector' module Puppet module ApplicationSupport # Pushes a Puppet Context configured with a remote environment for an agent # (one that exists at the master end), and a regular environment for other # modes. The configuration is overridden with options from the command line # before being set in a pushed Puppet Context. # # @param run_mode [Puppet::Util::RunMode] Puppet's current Run Mode. # @param environment_mode [Symbol] optional, Puppet's # current Environment Mode. Defaults to :local # @return [void] # @api private def self.push_application_context(run_mode, environment_mode = :local) Puppet.push_context(Puppet.base_context(Puppet.settings), "Update for application settings (#{run_mode})") # This use of configured environment is correct, this is used to establish # the defaults for an application that does not override, or where an override # has not been made from the command line. # configured_environment_name = Puppet[:environment] if run_mode.name == :agent configured_environment = Puppet::Node::Environment.remote(configured_environment_name) elsif environment_mode == :not_required configured_environment = Puppet.lookup(:environments).get(configured_environment_name) || Puppet::Node::Environment.remote(configured_environment_name) else configured_environment = Puppet.lookup(:environments).get!(configured_environment_name) end configured_environment = configured_environment.override_from_commandline(Puppet.settings) # Setup a new context using the app's configuration Puppet.push_context({:current_environment => configured_environment}, "Update current environment from application's configuration") end # Reads the routes YAML settings from the file specified by Puppet[:route_file] # and resets indirector termini for the current application class if listed. # # For instance, PE uses this to set the master facts terminus # to 'puppetdb' and its cache terminus to 'yaml'. # # @param application_name [String] The name of the current application. # @return [void] # @api private def self.configure_indirector_routes(application_name) route_file = Puppet[:route_file] if Puppet::FileSystem.exist?(route_file) routes = YAML.load_file(route_file) application_routes = routes[application_name] Puppet::Indirector.configure_routes(application_routes) if application_routes end end end end puppet-5.5.10/lib/puppet/configurer.rb0000644005276200011600000004334413417161721017575 0ustar jenkinsjenkins# The client for interacting with the puppetmaster config server. require 'sync' require 'timeout' require 'puppet/network/http_pool' require 'puppet/util' require 'securerandom' class Puppet::Configurer require 'puppet/configurer/fact_handler' require 'puppet/configurer/plugin_handler' include Puppet::Configurer::FactHandler # For benchmarking include Puppet::Util attr_reader :compile_time, :environment # Provide more helpful strings to the logging that the Agent does def self.to_s _("Puppet configuration client") end def self.should_pluginsync? if Puppet.settings.set_by_cli?(:pluginsync) || Puppet.settings.set_by_config?(:pluginsync) Puppet[:pluginsync] else if Puppet[:use_cached_catalog] false else true end end end def execute_postrun_command execute_from_setting(:postrun_command) end def execute_prerun_command execute_from_setting(:prerun_command) end # Initialize and load storage def init_storage Puppet::Util::Storage.load @compile_time ||= Puppet::Util::Storage.cache(:configuration)[:compile_time] rescue => detail Puppet.log_exception(detail, _("Removing corrupt state file %{file}: %{detail}") % { file: Puppet[:statefile], detail: detail }) begin Puppet::FileSystem.unlink(Puppet[:statefile]) retry rescue => detail raise Puppet::Error.new(_("Cannot remove %{file}: %{detail}") % { file: Puppet[:statefile], detail: detail }, detail) end end def initialize(transaction_uuid = nil, job_id = nil) @running = false @splayed = false @cached_catalog_status = 'not_used' @environment = Puppet[:environment] @transaction_uuid = transaction_uuid || SecureRandom.uuid @job_id = job_id @static_catalog = true @checksum_type = Puppet[:supported_checksum_types] @handler = Puppet::Configurer::PluginHandler.new() end # Get the remote catalog, yo. Returns nil if no catalog can be found. def retrieve_catalog(query_options) query_options ||= {} if (Puppet[:use_cached_catalog] && result = retrieve_catalog_from_cache(query_options)) @cached_catalog_status = 'explicitly_requested' Puppet.info _("Using cached catalog from environment '%{environment}'") % { environment: result.environment } else result = retrieve_new_catalog(query_options) if !result if !Puppet[:usecacheonfailure] Puppet.warning _("Not using cache on failed catalog") return nil end result = retrieve_catalog_from_cache(query_options) if result # don't use use cached catalog if it doesn't match server specified environment if @node_environment && result.environment != @environment Puppet.err _("Not using cached catalog because its environment '%{catalog_env}' does not match '%{local_env}'") % { catalog_env: result.environment, local_env: @environment } return nil end @cached_catalog_status = 'on_failure' Puppet.info _("Using cached catalog from environment '%{catalog_env}'") % { catalog_env: result.environment } end end end result end # Convert a plain resource catalog into our full host catalog. def convert_catalog(result, duration, options = {}) catalog = nil catalog_conversion_time = thinmark do catalog = result.to_ral catalog.finalize catalog.retrieval_duration = duration catalog.write_class_file catalog.write_resource_file end options[:report].add_times(:convert_catalog, catalog_conversion_time) if options[:report] catalog end def get_facts(options) if options[:pluginsync] plugin_sync_time = thinmark do remote_environment_for_plugins = Puppet::Node::Environment.remote(@environment) download_plugins(remote_environment_for_plugins) Puppet::GettextConfig.reset_text_domain('agent') Puppet::ModuleTranslations.load_from_vardir(Puppet[:vardir]) end options[:report].add_times(:plugin_sync, plugin_sync_time) if options[:report] end facts_hash = {} if Puppet::Resource::Catalog.indirection.terminus_class == :rest # This is a bit complicated. We need the serialized and escaped facts, # and we need to know which format they're encoded in. Thus, we # get a hash with both of these pieces of information. # # facts_for_uploading may set Puppet[:node_name_value] as a side effect facter_time = thinmark do facts_hash = facts_for_uploading end options[:report].add_times(:fact_generation, facter_time) if options[:report] end facts_hash end def prepare_and_retrieve_catalog(options, query_options) # set report host name now that we have the fact options[:report].host = Puppet[:node_name_value] query_options[:transaction_uuid] = @transaction_uuid query_options[:job_id] = @job_id query_options[:static_catalog] = @static_catalog # Query params don't enforce ordered evaluation, so munge this list into a # dot-separated string. query_options[:checksum_type] = @checksum_type.join('.') # apply passes in ral catalog catalog = options.delete(:catalog) return catalog if catalog # retrieve_catalog returns json catalog catalog = retrieve_catalog(query_options) return convert_catalog(catalog, @duration, options) if catalog Puppet.err _("Could not retrieve catalog; skipping run") nil end def prepare_and_retrieve_catalog_from_cache(options = {}) result = retrieve_catalog_from_cache({:transaction_uuid => @transaction_uuid, :static_catalog => @static_catalog}) if result Puppet.info _("Using cached catalog from environment '%{catalog_env}'") % { catalog_env: result.environment } return convert_catalog(result, @duration, options) end nil end # Apply supplied catalog and return associated application report def apply_catalog(catalog, options) report = options[:report] report.configuration_version = catalog.version benchmark(:notice, _("Applied catalog in %{seconds} seconds")) do apply_catalog_time = thinmark do catalog.apply(options) end options[:report].add_times(:catalog_application, apply_catalog_time) end report end # The code that actually runs the catalog. # This just passes any options on to the catalog, # which accepts :tags and :ignoreschedules. def run(options = {}) pool = Puppet::Network::HTTP::Pool.new(Puppet[:http_keepalive_timeout]) # We create the report pre-populated with default settings for # environment and transaction_uuid very early, this is to ensure # they are sent regardless of any catalog compilation failures or # exceptions. options[:report] ||= Puppet::Transaction::Report.new(nil, @environment, @transaction_uuid, @job_id) report = options[:report] init_storage Puppet::Util::Log.newdestination(report) completed = nil begin Puppet.override(:http_pool => pool) do # Skip failover logic if the server_list setting is empty if Puppet.settings[:server_list].nil? || Puppet.settings[:server_list].empty? do_failover = false; else do_failover = true end # When we are passed a catalog, that means we're in apply # mode. We shouldn't try to do any failover in that case. if options[:catalog].nil? && do_failover found = find_functional_server() server = found[:server] if server.nil? Puppet.warning _("Could not select a functional puppet master") server = [nil, nil] end Puppet.override(:server => server[0], :serverport => server[1]) do if !server.first.nil? Puppet.debug "Selected master: #{server[0]}:#{server[1]}" report.master_used = "#{server[0]}:#{server[1]}" end completed = run_internal(options.merge(:node => found[:node])) end else completed = run_internal(options) end end ensure pool.close end completed ? report.exit_status : nil end def run_internal(options) start = Time.now report = options[:report] # If a cached catalog is explicitly requested, attempt to retrieve it. Skip the node request, # don't pluginsync and switch to the catalog's environment if we successfully retrieve it. if Puppet[:use_cached_catalog] Puppet::GettextConfig.reset_text_domain('agent') Puppet::ModuleTranslations.load_from_vardir(Puppet[:vardir]) if catalog = prepare_and_retrieve_catalog_from_cache(options) options[:catalog] = catalog @cached_catalog_status = 'explicitly_requested' if @environment != catalog.environment && !Puppet[:strict_environment_mode] Puppet.notice _("Local environment: '%{local_env}' doesn't match the environment of the cached catalog '%{catalog_env}', switching agent to '%{catalog_env}'.") % { local_env: @environment, catalog_env: catalog.environment } @environment = catalog.environment end report.environment = @environment else # Don't try to retrieve a catalog from the cache again after we've already # failed to do so the first time. Puppet[:use_cached_catalog] = false Puppet[:usecacheonfailure] = false options[:pluginsync] = Puppet::Configurer.should_pluginsync? end end begin unless Puppet[:node_name_fact].empty? query_options = get_facts(options) end configured_environment = Puppet[:environment] if Puppet.settings.set_by_config?(:environment) # We only need to find out the environment to run in if we don't already have a catalog unless (options[:catalog] || Puppet[:strict_environment_mode]) begin node = nil node_retr_time = thinmark do node = options[:node] || Puppet::Node.indirection.find(Puppet[:node_name_value], :environment => Puppet::Node::Environment.remote(@environment), :configured_environment => configured_environment, :ignore_cache => true, :transaction_uuid => @transaction_uuid, :fail_on_404 => true) end options[:report].add_times(:node_retrieval, node_retr_time) if node # If we have deserialized a node from a rest call, we want to set # an environment instance as a simple 'remote' environment reference. if !node.has_environment_instance? && node.environment_name node.environment = Puppet::Node::Environment.remote(node.environment_name) end @node_environment = node.environment.to_s if node.environment.to_s != @environment Puppet.notice _("Local environment: '%{local_env}' doesn't match server specified node environment '%{node_env}', switching agent to '%{node_env}'.") % { local_env: @environment, node_env: node.environment } @environment = node.environment.to_s report.environment = @environment query_options = nil else Puppet.info _("Using configured environment '%{env}'") % { env: @environment } end end rescue StandardError => detail Puppet.warning(_("Unable to fetch my node definition, but the agent run will continue:")) Puppet.warning(detail) end end current_environment = Puppet.lookup(:current_environment) if current_environment.name == @environment.intern local_node_environment = current_environment else local_node_environment = Puppet::Node::Environment.create(@environment, current_environment.modulepath, current_environment.manifest, current_environment.config_version) end Puppet.push_context({:current_environment => local_node_environment}, "Local node environment for configurer transaction") query_options = get_facts(options) unless query_options query_options[:configured_environment] = configured_environment unless catalog = prepare_and_retrieve_catalog(options, query_options) return nil end if Puppet[:strict_environment_mode] && catalog.environment != @environment Puppet.err _("Not using catalog because its environment '%{catalog_env}' does not match agent specified environment '%{local_env}' and strict_environment_mode is set") % { catalog_env: catalog.environment, local_env: @environment } return nil end # Here we set the local environment based on what we get from the # catalog. Since a change in environment means a change in facts, and # facts may be used to determine which catalog we get, we need to # rerun the process if the environment is changed. tries = 0 while catalog.environment and not catalog.environment.empty? and catalog.environment != @environment if tries > 3 raise Puppet::Error, _("Catalog environment didn't stabilize after %{tries} fetches, aborting run") % { tries: tries } end Puppet.notice _("Local environment: '%{local_env}' doesn't match server specified environment '%{catalog_env}', restarting agent run with environment '%{catalog_env}'") % { local_env: @environment, catalog_env: catalog.environment } @environment = catalog.environment report.environment = @environment query_options = get_facts(options) query_options[:configured_environment] = configured_environment return nil unless catalog = prepare_and_retrieve_catalog(options, query_options) tries += 1 end execute_prerun_command or return nil options[:report].code_id = catalog.code_id options[:report].catalog_uuid = catalog.catalog_uuid options[:report].cached_catalog_status = @cached_catalog_status apply_catalog(catalog, options) true rescue => detail Puppet.log_exception(detail, _("Failed to apply catalog: %{detail}") % { detail: detail }) return nil ensure execute_postrun_command or return nil end ensure report.cached_catalog_status ||= @cached_catalog_status report.add_times(:total, Time.now - start) report.finalize_report Puppet::Util::Log.close(report) send_report(report) Puppet.pop_context end private :run_internal def find_functional_server() configured_environment = Puppet[:environment] if Puppet.settings.set_by_config?(:environment) node = nil selected_server = Puppet.settings[:server_list].find do |server| # Puppet.override doesn't return the result of its block, so we # need to handle this manually found = false server[1] ||= Puppet[:masterport] Puppet.override(:server => server[0], :serverport => server[1]) do begin node = Puppet::Node.indirection.find(Puppet[:node_name_value], :environment => Puppet::Node::Environment.remote(@environment), :configured_environment => configured_environment, :ignore_cache => true, :transaction_uuid => @transaction_uuid, :fail_on_404 => false) found = true rescue # Nothing to see here end end found end { :node => node, :server => selected_server } end private :find_functional_server def send_report(report) puts report.summary if Puppet[:summarize] save_last_run_summary(report) Puppet::Transaction::Report.indirection.save(report, nil, :environment => Puppet::Node::Environment.remote(@environment)) if Puppet[:report] rescue => detail Puppet.log_exception(detail, _("Could not send report: %{detail}") % { detail: detail }) end def save_last_run_summary(report) mode = Puppet.settings.setting(:lastrunfile).mode Puppet::Util.replace_file(Puppet[:lastrunfile], mode) do |fh| fh.print YAML.dump(report.raw_summary) end rescue => detail Puppet.log_exception(detail, _("Could not save last run local report: %{detail}") % { detail: detail }) end private def execute_from_setting(setting) return true if (command = Puppet[setting]) == "" begin Puppet::Util::Execution.execute([command]) true rescue => detail Puppet.log_exception(detail, _("Could not run command from %{setting}: %{detail}") % { setting: setting, detail: detail }) false end end def retrieve_catalog_from_cache(query_options) result = nil @duration = thinmark do result = Puppet::Resource::Catalog.indirection.find( Puppet[:node_name_value], query_options.merge( :ignore_terminus => true, :environment => Puppet::Node::Environment.remote(@environment) ) ) end result rescue => detail Puppet.log_exception(detail, _("Could not retrieve catalog from cache: %{detail}") % { detail: detail }) return nil end def retrieve_new_catalog(query_options) result = nil @duration = thinmark do result = Puppet::Resource::Catalog.indirection.find( Puppet[:node_name_value], query_options.merge( :ignore_cache => true, # We never want to update the cached Catalog if we're running in noop mode. :ignore_cache_save => Puppet[:noop], :environment => Puppet::Node::Environment.remote(@environment), :fail_on_404 => true ) ) end result rescue StandardError => detail Puppet.log_exception(detail, _("Could not retrieve catalog from remote server: %{detail}") % { detail: detail }) return nil end def download_plugins(remote_environment_for_plugins) @handler.download_plugins(remote_environment_for_plugins) end end puppet-5.5.10/lib/puppet/daemon.rb0000644005276200011600000001223213417161721016665 0ustar jenkinsjenkinsrequire 'puppet/application' require 'puppet/scheduler' # Run periodic actions and a network server in a daemonized process. # # A Daemon has 3 parts: # * config reparse # * (optional) an agent that responds to #run # * (optional) a server that response to #stop, #start, and #wait_for_shutdown # # The config reparse will occur periodically based on Settings. The server will # be started and is expected to manage its own run loop (and so not block the # start call). The server will, however, still be waited for by using the # #wait_for_shutdown method. The agent is run periodically and a time interval # based on Settings. The config reparse will update this time interval when # needed. # # The Daemon is also responsible for signal handling, starting, stopping, # running the agent on demand, and reloading the entire process. It ensures # that only one Daemon is running by using a lockfile. # # @api private class Puppet::Daemon SIGNAL_CHECK_INTERVAL = 5 attr_accessor :agent, :server, :argv attr_reader :signals def initialize(pidfile, scheduler = Puppet::Scheduler::Scheduler.new()) @scheduler = scheduler @pidfile = pidfile @signals = [] end def daemonname Puppet.run_mode.name end # Put the daemon into the background. def daemonize if pid = fork Process.detach(pid) exit(0) end create_pidfile # Get rid of console logging Puppet::Util::Log.close(:console) Process.setsid Dir.chdir("/") close_streams end # Close stdin/stdout/stderr so that we can finish our transition into 'daemon' mode. # @return nil def self.close_streams() Puppet.debug("Closing streams for daemon mode") begin $stdin.reopen "/dev/null" $stdout.reopen "/dev/null", "a" $stderr.reopen $stdout Puppet::Util::Log.reopen Puppet.debug("Finished closing streams for daemon mode") rescue => detail Puppet.err "Could not start #{Puppet.run_mode.name}: #{detail}" Puppet::Util::replace_file("/tmp/daemonout", 0644) do |f| f.puts "Could not start #{Puppet.run_mode.name}: #{detail}" end exit(12) end end # Convenience signature for calling Puppet::Daemon.close_streams def close_streams() Puppet::Daemon.close_streams end def reexec raise Puppet::DevError, _("Cannot reexec unless ARGV arguments are set") unless argv command = $0 + " " + argv.join(" ") Puppet.notice "Restarting with '#{command}'" stop(:exit => false) exec(command) end def reload return unless agent agent.run({:splay => false}) rescue Puppet::LockError Puppet.notice "Not triggering already-running agent" end def restart Puppet::Application.restart! reexec unless agent and agent.running? end def reopen_logs Puppet::Util::Log.reopen end # Trap a couple of the main signals. This should probably be handled # in a way that anyone else can register callbacks for traps, but, eh. def set_signal_traps [:INT, :TERM].each do |signal| Signal.trap(signal) do Puppet.notice "Caught #{signal}; exiting" stop end end # extended signals not supported under windows if !Puppet.features.microsoft_windows? signals = {:HUP => :restart, :USR1 => :reload, :USR2 => :reopen_logs } signals.each do |signal, method| Signal.trap(signal) do Puppet.notice "Caught #{signal}; storing #{method}" @signals << method end end end end # Stop everything def stop(args = {:exit => true}) Puppet::Application.stop! server.stop if server remove_pidfile Puppet::Util::Log.close_all exit if args[:exit] end def start create_pidfile raise Puppet::DevError, _("Daemons must have an agent, server, or both") unless agent or server # Start the listening server, if required. server.start if server # Finally, loop forever running events - or, at least, until we exit. run_event_loop server.wait_for_shutdown if server end private # Create a pidfile for our daemon, so we can be stopped and others # don't try to start. def create_pidfile raise "Could not create PID file: #{@pidfile.file_path}" unless @pidfile.lock end # Remove the pid file for our daemon. def remove_pidfile @pidfile.unlock end def run_event_loop agent_run = Puppet::Scheduler.create_job(Puppet[:runinterval], Puppet[:splay], Puppet[:splaylimit]) do # Splay for the daemon is handled in the scheduler agent.run(:splay => false) end reparse_run = Puppet::Scheduler.create_job(Puppet[:filetimeout]) do Puppet.settings.reparse_config_files agent_run.run_interval = Puppet[:runinterval] if Puppet[:filetimeout] == 0 reparse_run.disable else reparse_run.run_interval = Puppet[:filetimeout] end end signal_loop = Puppet::Scheduler.create_job(SIGNAL_CHECK_INTERVAL) do while method = @signals.shift Puppet.notice "Processing #{method}" send(method) end end reparse_run.disable if Puppet[:filetimeout] == 0 agent_run.disable unless agent @scheduler.run_loop([reparse_run, agent_run, signal_loop]) end end puppet-5.5.10/lib/puppet/datatypes.rb0000644005276200011600000001715613417161721017432 0ustar jenkinsjenkins# Data types in the Puppet Language can have implementations written in Ruby # and distributed in puppet modules. A data type can be declared together with # its implementation by creating a file in 'lib/puppet/functions/'. # The name of the file must be the downcased name of the data type followed by # the extension '.rb'. # # A data type is created by calling {Puppet::DataTypes.create_type()} # and passing it a block that defines the data type interface and implementation. # # Data types are namespaced inside the modules that contains them. The name of the # data type is prefixed with the name of the module. As with all type names, each # segment of the name must start with an uppercase letter. # # @example A simple data type # Puppet::DataTypes.create_type('Auth::User') do # interface <<-PUPPET # attributes => { # name => String, # email => String # } # PUPPET # end # # The above example does not declare an implementation which makes it equivalent # to adding the following contents in a file named 'user.pp' under the 'types' directory # of the module root. # # type Auth::User = Object[ # attributes => { # name => String, # email => String # }] # # Both declarations are valid and will be found by the module loader. # # Structure of a data type # --- # # A Data Type consists of an interface and an implementation. Unless a registered implementation # is found, the type system will automatically generate one. An automatically generated # implementation is all that is needed when the interface fully defines the behaviour (for # example in the common case when the data type has no other behaviour than having attributes). # # When the automatically generated implementation is not sufficient, one must be implemented and # registered. The implementation can either be done next to the interface definition by passing # a block to `implementation`, or map to an existing implementation class by passing the class # as an argument to `implementation_class`. An implementation class does not have to be special # in other respects than that it must implemented the type's interface. This makes it possible # to use existing Ruby data types as data types in the puppet language. # # Note that when using `implementation_class` there can only be one such implementation across # all environments managed by one puppet server and you must handle and install these # implementations as if they are part of the puppet platform. In contrast; the type # implementations that are done inside of the type's definition are safe to use in different # versions in different environments (given that they do not need additional external logic to # be loaded). # # When using an `implementation_class` it is sometimes desirable to load this class from the # 'lib' directory of the module. The method `load_file` is provided to facilitate such a load. # The `load_file` will use the `Puppet::Util::Autoload` to search for the given file in the 'lib' # directory of the current environment and the 'lib' directory in each included module. # # @example Adding implementation on top of the generated type using `implementation` # Puppet::DataTypes.create_type('Auth::User') do # interface <<-PUPPET # attributes => { # name => String, # email => String, # year_of_birth => Integer, # age => { type => Integer, kind => derived } # } # PUPPET # # implementation do # def age # DateTime.now.year - @year_of_birth # end # end # end # # @example Appointing an already existing implementation class # # Assumes the following class is declared under 'lib/puppetx/auth' in the module: # # class PuppetX::Auth::User # attr_reader :name, :year_of_birth # def initialize(name, year_of_birth) # @name = name # @year_of_birth = year_of_birth # end # # def age # DateTime.now.year - @year_of_birth # end # # def send_text(sender, text) # sender.send_text_from(@name, text) # end # end # # Then the type declaration can look like this: # # Puppet::DataTypes.create_type('Auth::User') do # interface <<-PUPPET # attributes => { # name => String, # email => String, # year_of_birth => Integer, # age => { type => Integer, kind => derived } # }, # functions => { # send_text => Callable[Sender, String[1]] # } # PUPPET # # # This load_file is optional and only needed in case # # the implementation is not loaded by other means. # load_file 'puppetx/auth/user' # # implementation_class PuppetX::Auth::User # end # module Puppet::DataTypes def self.create_type(type_name, &block) # Ruby < 2.1.0 does not have method on Binding, can only do eval # and it will fail unless protected with an if defined? if the local # variable does not exist in the block's binder. # begin loader = block.binding.eval('loader_injected_arg if defined?(loader_injected_arg)') create_loaded_type(type_name, loader, &block) rescue StandardError => e raise ArgumentError, _("Data Type Load Error for type '%{type_name}': %{message}") % {type_name: type_name, message: e.message} end end def self.create_loaded_type(type_name, loader, &block) builder = TypeBuilder.new(type_name.to_s) api = TypeBuilderAPI.new(builder).freeze api.instance_eval(&block) builder.create_type(loader) end # @api private class TypeBuilder attr_accessor :interface, :implementation, :implementation_class def initialize(type_name) @type_name = type_name @implementation = nil @implementation_class = nil end def create_type(loader) raise ArgumentError, _('a data type must have an interface') unless @interface.is_a?(String) created_type = Puppet::Pops::Types::PObjectType.new( @type_name, Puppet::Pops::Parser::EvaluatingParser.new.parse_string("{ #{@interface} }").body) if !@implementation_class.nil? if @implementation_class < Puppet::Pops::Types::PuppetObject @implementation_class.instance_eval do include Puppet::Pops::Types::PuppetObject @_pcore_type = created_type def self._pcore_type @_pcore_type end end else Puppet::Pops::Loaders.implementation_registry.register_implementation(created_type, @implementation_class) end created_type.implementation_class = @implementation_class elsif !@implementation.nil? created_type.implementation_override = @implementation end created_type end def has_implementation? !(@implementation_class.nil? && @implementation.nil?) end end # The TypeBuilderAPI class exposes only those methods that the builder API provides # @api public class TypeBuilderAPI # @api private def initialize(type_builder) @type_builder = type_builder end def interface(type_string) raise ArgumentError, _('a data type can only have one interface') unless @type_builder.interface.nil? @type_builder.interface = type_string end def implementation(&block) raise ArgumentError, _('a data type can only have one implementation') if @type_builder.has_implementation? @type_builder.implementation = block end def implementation_class(ruby_class) raise ArgumentError, _('a data type can only have one implementation') if @type_builder.has_implementation? @type_builder.implementation_class = ruby_class end def load_file(file_name) Puppet::Util::Autoload.load_file(file_name, nil) end end end puppet-5.5.10/lib/puppet/defaults.rb0000644005276200011600000026534613417161721017251 0ustar jenkinsjenkinsrequire 'puppet/util/platform' module Puppet def self.default_diffargs if (Facter.value(:kernel) == "AIX" && Facter.value(:kernelmajversion) == "5300") "" else "-u" end end def self.default_digest_algorithm Puppet::Util::Platform.fips_enabled? ? 'sha256' : 'md5' end def self.valid_digest_algorithms Puppet::Util::Platform.fips_enabled? ? %w[sha256 sha384 sha512 sha224] : %w[md5 sha256 sha384 sha512 sha224] end def self.default_file_checksum_types Puppet::Util::Platform.fips_enabled? ? %w[sha256 sha384 sha512 sha224] : %w[md5 sha256 sha384 sha512 sha224] end def self.valid_file_checksum_types Puppet::Util::Platform.fips_enabled? ? %w[sha256 sha256lite sha384 sha512 sha224 sha1 sha1lite mtime ctime] : %w[md5 md5lite sha256 sha256lite sha384 sha512 sha224 sha1 sha1lite mtime ctime] end ############################################################################################ # NOTE: For information about the available values for the ":type" property of settings, # see the docs for Settings.define_settings ############################################################################################ AS_DURATION = %q{This setting can be a time interval in seconds (30 or 30s), minutes (30m), hours (6h), days (2d), or years (5y).} define_settings(:main, :confdir => { :default => nil, :type => :directory, :desc => "The main Puppet configuration directory. The default for this setting is calculated based on the user. If the process is running as root or the user that Puppet is supposed to run as, it defaults to a system directory, but if it's running as any other user, it defaults to being in the user's home directory.", }, :codedir => { :default => nil, :type => :directory, :desc => "The main Puppet code directory. The default for this setting is calculated based on the user. If the process is running as root or the user that Puppet is supposed to run as, it defaults to a system directory, but if it's running as any other user, it defaults to being in the user's home directory.", }, :vardir => { :default => nil, :type => :directory, :owner => "service", :group => "service", :desc => "Where Puppet stores dynamic and growing data. The default for this setting is calculated specially, like `confdir`_.", }, ### NOTE: this setting is usually being set to a symbol value. We don't officially have a ### setting type for that yet, but we might want to consider creating one. :name => { :default => nil, :desc => "The name of the application, if we are running as one. The default is essentially $0 without the path or `.rb`.", } ) define_settings(:main, :logdir => { :default => nil, :type => :directory, :mode => "0750", :owner => "service", :group => "service", :desc => "The directory in which to store log files", }, :log_level => { :default => 'notice', :type => :enum, :values => ["debug","info","notice","warning","err","alert","emerg","crit"], :desc => "Default logging level for messages from Puppet. Allowed values are: * debug * info * notice * warning * err * alert * emerg * crit ", :hook => proc {|value| Puppet::Util::Log.level = value }, :call_hook => :on_initialize_and_write, }, :disable_warnings => { :default => [], :type => :array, :desc => "A comma-separated list of warning types to suppress. If large numbers of warnings are making Puppet's logs too large or difficult to use, you can temporarily silence them with this setting. If you are preparing to upgrade Puppet to a new major version, you should re-enable all warnings for a while. Valid values for this setting are: * `deprecations` --- disables deprecation warnings. * `undefined_variables` --- disables warnings about non existing variables. * `undefined_resources` --- disables warnings about non existing resources.", :hook => proc do |value| values = munge(value) valid = %w[deprecations undefined_variables undefined_resources] invalid = values - (values & valid) if not invalid.empty? raise ArgumentError, _("Cannot disable unrecognized warning types %{invalid}.") % { invalid: invalid.inspect } + ' ' + _("Valid values are %{values}.") % { values: valid.inspect} end end }, :strict => { :default => :warning, :type => :symbolic_enum, :values => [:off, :warning, :error], :desc => "The strictness level of puppet. Allowed values are: * off - do not perform extra validation, do not report * warning - perform extra validation, report as warning (default) * error - perform extra validation, fail with error The strictness level is for both language semantics and runtime evaluation validation. In addition to controlling the behavior with this master switch some individual warnings may also be controlled by the disable_warnings setting. No new validations will be added to a micro (x.y.z) release, but may be added in minor releases (x.y.0). In major releases it expected that most (if not all) strictness validation become standard behavior.", :hook => proc do |value| munge(value) value.to_sym end }, :disable_i18n => { :default => false, :type => :boolean, :desc => "If true, turns off all translations of Puppet and module log messages, which affects error, warning, and info log messages, as well as any translations in the report and CLI.", :hook => proc do |value| if value require 'puppet/gettext/stubs' Puppet::GettextConfig.disable_gettext end end } ) define_settings(:main, :priority => { :default => nil, :type => :priority, :desc => "The scheduling priority of the process. Valid values are 'high', 'normal', 'low', or 'idle', which are mapped to platform-specific values. The priority can also be specified as an integer value and will be passed as is, e.g. -5. Puppet must be running as a privileged user in order to increase scheduling priority.", }, :trace => { :default => false, :type => :boolean, :desc => "Whether to print stack traces on some errors", :hook => proc do |value| # Enable or disable Facter's trace option too Facter.trace(value) if Facter.respond_to? :trace end }, :profile => { :default => false, :type => :boolean, :desc => "Whether to enable experimental performance profiling", }, :future_features => { :default => false, :type => :boolean, :desc => "Whether or not to enable all features currently being developed for future major releases of Puppet. Should be used with caution, as in development features are experimental and can have unexpected effects." }, :static_catalogs => { :default => true, :type => :boolean, :desc => "Whether to compile a [static catalog](https://puppet.com/docs/puppet/latest/static_catalogs.html#enabling-or-disabling-static-catalogs), which occurs only on a Puppet Server master when the `code-id-command` and `code-content-command` settings are configured in its `puppetserver.conf` file.", }, :strict_environment_mode => { :default => false, :type => :boolean, :desc => "Whether the agent specified environment should be considered authoritative, causing the run to fail if the retrieved catalog does not match it.", }, :autoflush => { :default => true, :type => :boolean, :desc => "Whether log files should always flush to disk.", :hook => proc { |value| Log.autoflush = value } }, :syslogfacility => { :default => "daemon", :desc => "What syslog facility to use when logging to syslog. Syslog has a fixed list of valid facilities, and you must choose one of those; you cannot just make one up." }, :statedir => { :default => "$vardir/state", :type => :directory, :mode => "01755", :desc => "The directory where Puppet state is stored. Generally, this directory can be removed without causing harm (although it might result in spurious service restarts)." }, :rundir => { :default => nil, :type => :directory, :mode => "0755", :owner => "service", :group => "service", :desc => "Where Puppet PID files are kept." }, :genconfig => { :default => false, :type => :boolean, :desc => "When true, causes Puppet applications to print an example config file to stdout and exit. The example will include descriptions of each setting, and the current (or default) value of each setting, incorporating any settings overridden on the CLI (with the exception of `genconfig` itself). This setting only makes sense when specified on the command line as `--genconfig`.", }, :genmanifest => { :default => false, :type => :boolean, :desc => "Whether to just print a manifest to stdout and exit. Only makes sense when specified on the command line as `--genmanifest`. Takes into account arguments specified on the CLI.", }, :configprint => { :default => "", :deprecated => :completely, :desc => "Prints the value of a specific configuration setting. If the name of a setting is provided for this, then the value is printed and puppet exits. Comma-separate multiple values. For a list of all values, specify 'all'. This setting is deprecated, the 'puppet config' command replaces this functionality.", }, :color => { :default => "ansi", :type => :string, :desc => "Whether to use colors when logging to the console. Valid values are `ansi` (equivalent to `true`), `html`, and `false`, which produces no color. Defaults to false on Windows, as its console does not support ansi colors.", }, :mkusers => { :default => false, :type => :boolean, :desc => "Whether to create the necessary user and group that puppet agent will run as.", }, :manage_internal_file_permissions => { :default => ! Puppet::Util::Platform.windows?, :type => :boolean, :desc => "Whether Puppet should manage the owner, group, and mode of files it uses internally", }, :onetime => { :default => false, :type => :boolean, :desc => "Perform one configuration run and exit, rather than spawning a long-running daemon. This is useful for interactively running puppet agent, or running puppet agent from cron.", :short => 'o', }, :path => { :default => "none", :desc => "The shell search path. Defaults to whatever is inherited from the parent process. This setting can only be set in the `[main]` section of puppet.conf; it cannot be set in `[master]`, `[agent]`, or an environment config section.", :call_hook => :on_define_and_write, :hook => proc do |value| Puppet::Util.set_env('PATH', '') if Puppet::Util.get_env('PATH').nil? Puppet::Util.set_env('PATH', value) unless value == 'none' paths = Puppet::Util.get_env('PATH').split(File::PATH_SEPARATOR) Puppet::Util::Platform.default_paths.each do |path| Puppet::Util.set_env('PATH', Puppet::Util.get_env('PATH') + File::PATH_SEPARATOR + path) unless paths.include?(path) end value end }, :libdir => { :type => :directory, :default => "$vardir/lib", :desc => "An extra search path for Puppet. This is only useful for those files that Puppet will load on demand, and is only guaranteed to work for those cases. In fact, the autoload mechanism is responsible for making sure this directory is in Ruby's search path\n", :call_hook => :on_initialize_and_write, :hook => proc do |value| $LOAD_PATH.delete(@oldlibdir) if defined?(@oldlibdir) && $LOAD_PATH.include?(@oldlibdir) @oldlibdir = value $LOAD_PATH << value end }, :environment => { :default => "production", :desc => "The environment in which Puppet is running. For clients, such as `puppet agent`, this determines the environment itself, which Puppet uses to find modules and much more. For servers, such as `puppet master`, this provides the default environment for nodes that Puppet knows nothing about. When defining an environment in the `[agent]` section, this refers to the environment that the agent requests from the master. The environment doesn't have to exist on the local filesystem because the agent fetches it from the master. This definition is used when running `puppet agent`. When defined in the `[user]` section, the environment refers to the path that Puppet uses to search for code and modules related to its execution. This requires the environment to exist locally on the filesystem where puppet is being executed. Puppet subcommands, including `puppet module` and `puppet apply`, use this definition. Given that the context and effects vary depending on the [config section](https://puppet.com/docs/puppet/latest/config_file_main.html#config-sections) in which the `environment` setting is defined, do not set it globally.", :short => "E" }, :environmentpath => { :default => "$codedir/environments", :desc => "A search path for directory environments, as a list of directories separated by the system path separator character. (The POSIX path separator is ':', and the Windows path separator is ';'.) This setting must have a value set to enable **directory environments.** The recommended value is `$codedir/environments`. For more details, see ", :type => :path, }, :always_retry_plugins => { :type => :boolean, :default => true, :desc => <<-'EOT' Affects how we cache attempts to load Puppet resource types and features. If true, then calls to `Puppet.type.?` `Puppet.feature.?` will always attempt to load the type or feature (which can be an expensive operation) unless it has already been loaded successfully. This makes it possible for a single agent run to, e.g., install a package that provides the underlying capabilities for a type or feature, and then later load that type or feature during the same run (even if the type or feature had been tested earlier and had not been available). If this setting is set to false, then types and features will only be checked once, and if they are not available, the negative result is cached and returned for all subsequent attempts to load the type or feature. This behavior is almost always appropriate for the server, and can result in a significant performance improvement for types and features that are checked frequently. EOT }, :diff_args => { :default => lambda { default_diffargs }, :desc => "Which arguments to pass to the diff command when printing differences between files. The command to use can be chosen with the `diff` setting.", }, :diff => { :default => (Puppet.features.microsoft_windows? ? "" : "diff"), :desc => "Which diff command to use when printing differences between files. This setting has no default value on Windows, as standard `diff` is not available, but Puppet can use many third-party diff tools.", }, :show_diff => { :type => :boolean, :default => false, :desc => "Whether to log and report a contextual diff when files are being replaced. This causes partial file contents to pass through Puppet's normal logging and reporting system, so this setting should be used with caution if you are sending Puppet's reports to an insecure destination. This feature currently requires the `diff/lcs` Ruby library.", }, :daemonize => { :type => :boolean, :default => (Puppet.features.microsoft_windows? ? false : true), :desc => "Whether to send the process into the background. This defaults to true on POSIX systems, and to false on Windows (where Puppet currently cannot daemonize).", :short => "D", :hook => proc do |value| if value and Puppet.features.microsoft_windows? raise "Cannot daemonize on Windows" end end }, :maximum_uid => { :default => 4294967290, :desc => "The maximum allowed UID. Some platforms use negative UIDs but then ship with tools that do not know how to handle signed ints, so the UIDs show up as huge numbers that can then not be fed back into the system. This is a hackish way to fail in a slightly more useful way when that happens.", }, :route_file => { :default => "$confdir/routes.yaml", :desc => "The YAML file containing indirector route configuration.", }, :node_terminus => { :type => :terminus, :default => "plain", :desc => <<-'EOT', Which node data plugin to use when compiling node catalogs. When Puppet compiles a catalog, it combines two primary sources of info: the main manifest, and a node data plugin (often called a "node terminus," for historical reasons). Node data plugins provide three things for a given node name: 1. A list of classes to add to that node's catalog (and, optionally, values for their parameters). 2. Which Puppet environment the node should use. 3. A list of additional top-scope variables to set. The three main node data plugins are: * `plain` --- Returns no data, so that the main manifest controls all node configuration. * `exec` --- Uses an [external node classifier (ENC)](https://puppet.com/docs/puppet/latest/nodes_external.html), configured by the `external_nodes` setting. This lets you pull a list of Puppet classes from any external system, using a small glue script to perform the request and format the result as YAML. * `classifier` (formerly `console`) --- Specific to Puppet Enterprise. Uses the PE console for node data." EOT }, :node_cache_terminus => { :type => :terminus, :default => nil, :desc => "How to store cached nodes. Valid values are (none), 'json', 'msgpack', 'yaml' or write only yaml ('write_only_yaml').", }, :data_binding_terminus => { :type => :terminus, :default => "hiera", :desc => "This setting has been deprecated. Use of any value other than 'hiera' should instead be configured in a version 5 hiera.yaml. Until this setting is removed, it controls which data binding terminus to use for global automatic data binding (across all environments). By default this value is 'hiera'. A value of 'none' turns off the global binding.", :call_hook => :on_initialize_and_write, :hook => proc do |value| if Puppet[:strict] != :off s_val = value.to_s # because sometimes the value is a symbol unless s_val == 'hiera' || s_val == 'none' || value == '' || value.nil? #TRANSLATORS 'data_binding_terminus' is a setting and should not be translated message = _("Setting 'data_binding_terminus' is deprecated.") #TRANSLATORS 'hiera' should not be translated message += ' ' + _("Convert custom terminus to hiera 5 API.") Puppet.deprecation_warning(message) end end end }, :hiera_config => { :default => lambda do config = nil codedir = Puppet.settings[:codedir] if codedir.is_a?(String) config = File.expand_path(File.join(codedir, 'hiera.yaml')) config = nil unless Puppet::FileSystem.exist?(config) end config = File.expand_path(File.join(Puppet.settings[:confdir], 'hiera.yaml')) if config.nil? config end, :desc => "The hiera configuration file. Puppet only reads this file on startup, so you must restart the puppet master every time you edit it.", :type => :file, }, :binder_config => { :default => nil, :desc => "The binder configuration file. Puppet reads this file on each request to configure the bindings system. If set to nil (the default), a $confdir/binder_config.yaml is optionally loaded. If it does not exists, a default configuration is used. If the setting :binding_config is specified, it must reference a valid and existing yaml file.", :type => :file, }, :catalog_terminus => { :type => :terminus, :default => "compiler", :desc => "Where to get node catalogs. This is useful to change if, for instance, you'd like to pre-compile catalogs and store them in memcached or some other easily-accessed store.", }, :catalog_cache_terminus => { :type => :terminus, :default => nil, :desc => "How to store cached catalogs. Valid values are 'json', 'msgpack' and 'yaml'. The agent application defaults to 'json'." }, :facts_terminus => { :default => 'facter', :desc => "The node facts terminus.", }, :default_file_terminus => { :type => :terminus, :default => "rest", :desc => "The default source for files if no server is given in a uri, e.g. puppet:///file. The default of `rest` causes the file to be retrieved using the `server` setting. When running `apply` the default is `file_server`, causing requests to be filled locally." }, :http_proxy_host => { :default => "none", :desc => "The HTTP proxy host to use for outgoing connections. Note: You may need to use a FQDN for the server hostname when using a proxy. Environment variable http_proxy or HTTP_PROXY will override this value", }, :http_proxy_port => { :default => 3128, :desc => "The HTTP proxy port to use for outgoing connections", }, :http_proxy_user => { :default => "none", :desc => "The user name for an authenticated HTTP proxy. Requires the `http_proxy_host` setting.", }, :http_proxy_password =>{ :default => "none", :hook => proc do |value| if Puppet.settings[:http_proxy_password] =~ /[@!# \/]/ raise "Passwords set in the http_proxy_password setting must be valid as part of a URL, and any reserved characters must be URL-encoded. We received: #{value}" end end, :desc => "The password for the user of an authenticated HTTP proxy. Requires the `http_proxy_user` setting. Note that passwords must be valid when used as part of a URL. If a password contains any characters with special meanings in URLs (as specified by RFC 3986 section 2.2), they must be URL-encoded. (For example, `#` would become `%23`.)", }, :http_keepalive_timeout => { :default => "4s", :type => :duration, :desc => "The maximum amount of time a persistent HTTP connection can remain idle in the connection pool, before it is closed. This timeout should be shorter than the keepalive timeout used on the HTTP server, e.g. Apache KeepAliveTimeout directive. #{AS_DURATION}" }, :http_debug => { :default => false, :type => :boolean, :desc => "Whether to write HTTP request and responses to stderr. This should never be used in a production environment." }, :http_connect_timeout => { :default => "2m", :type => :duration, :desc => "The maximum amount of time to wait when establishing an HTTP connection. The default value is 2 minutes. #{AS_DURATION}", }, :http_read_timeout => { :type => :duration, :desc => "The time to wait for one block to be read from an HTTP connection. If nothing is read after the elapsed interval then the connection will be closed. The default value is unlimited. #{AS_DURATION}", }, :http_user_agent => { :default => "Puppet/#{Puppet.version} Ruby/#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL} (#{RUBY_PLATFORM})", :desc => "The HTTP User-Agent string to send when making network requests." }, :filetimeout => { :default => "15s", :type => :duration, :desc => "The minimum time to wait between checking for updates in configuration files. This timeout determines how quickly Puppet checks whether a file (such as manifests or templates) has changed on disk. #{AS_DURATION}", }, :environment_timeout => { :default => "0", :type => :ttl, :desc => "How long the Puppet master should cache data it loads from an environment. #{AS_DURATION} A value of `0` will disable caching. This setting can also be set to `unlimited`, which will cache environments until the master is restarted or told to refresh the cache. You should change this setting once your Puppet deployment is doing non-trivial work. We chose the default value of `0` because it lets new users update their code without any extra steps, but it lowers the performance of your Puppet master. We recommend setting this to `unlimited` and explicitly refreshing your Puppet master as part of your code deployment process. * With Puppet Server, you should refresh environments by calling the `environment-cache` API endpoint. See the docs for the Puppet Server administrative API. * With a Rack Puppet master, you should restart the web server or the application server. Passenger lets you touch a `restart.txt` file to refresh an application without restarting Apache; see the Passenger docs for details. We don't recommend using any value other than `0` or `unlimited`, since most Puppet masters use a pool of Ruby interpreters which all have their own cache timers. When these timers drift out of sync, agents can be served inconsistent catalogs." }, :environment_data_provider => { :desc => "The name of a registered environment data provider used when obtaining environment specific data. The three built in and registered providers are 'none' (no data), 'function' (data obtained by calling the function 'environment::data()') and 'hiera' (data obtained using a data provider configured using a hiera.yaml file in root of the environment). Other environment data providers may be registered in modules on the module path. For such custom data providers see the respective module documentation. This setting is deprecated.", :hook => proc { |value| unless value.nil? || Puppet[:strict] == :off #TRANSLATORS 'environment_data_provider' is a setting and should not be translated Puppet.deprecation_warning(_("Setting 'environment_data_provider' is deprecated.")) end } }, :prerun_command => { :default => "", :desc => "A command to run before every agent run. If this command returns a non-zero return code, the entire Puppet run will fail.", }, :postrun_command => { :default => "", :desc => "A command to run after every agent run. If this command returns a non-zero return code, the entire Puppet run will be considered to have failed, even though it might have performed work during the normal run.", }, :freeze_main => { :default => false, :type => :boolean, :desc => "Freezes the 'main' class, disallowing any code to be added to it. This essentially means that you can't have any code outside of a node, class, or definition other than in the site manifest.", }, :trusted_server_facts => { :default => true, :type => :boolean, :deprecated => :completely, :desc => "The 'trusted_server_facts' setting is deprecated and has no effect as the feature this enabled is now always on. The setting will be removed in a future version of puppet.", }, :preview_outputdir => { :default => '$vardir/preview', :type => :directory, :mode => "0750", :owner => "service", :group => "service", :desc => "The directory where catalog previews per node are generated." } ) define_settings(:main, # Whether the application management feature is on or off - now deprecated and always on. :app_management => { :default => false, :type => :boolean, :desc => "This setting has no effect and will be removed in a future Puppet version.", :deprecated => :completely, } ) Puppet.define_settings(:module_tool, :module_repository => { :default => 'https://forgeapi.puppet.com', :desc => "The module repository", }, :module_working_dir => { :default => (Puppet.features.microsoft_windows? ? Dir.tmpdir() : '$vardir/puppet-module'), :desc => "The directory into which module tool data is stored", }, :module_skeleton_dir => { :default => '$module_working_dir/skeleton', :desc => "The directory which the skeleton for module tool generate is stored.", }, :forge_authorization => { :default => nil, :desc => "The authorization key to connect to the Puppet Forge. Leave blank for unauthorized or license based connections", }, :module_groups => { :default => nil, :desc => "Extra module groups to request from the Puppet Forge. This is an internal setting, and users should never change it.", } ) Puppet.define_settings( :main, # We have to downcase the fqdn, because the current ssl stuff (as opposed to in master) doesn't have good facilities for # manipulating naming. :certname => { :default => lambda { Puppet::Settings.default_certname.downcase }, :desc => "The name to use when handling certificates. When a node requests a certificate from the CA puppet master, it uses the value of the `certname` setting as its requested Subject CN. This is the name used when managing a node's permissions in [auth.conf](https://puppet.com/docs/puppet/latest/config_file_auth.html). In most cases, it is also used as the node's name when matching [node definitions](https://puppet.com/docs/puppet/latest/lang_node_definitions.html) and requesting data from an ENC. (This can be changed with the `node_name_value` and `node_name_fact` settings, although you should only do so if you have a compelling reason.) A node's certname is available in Puppet manifests as `$trusted['certname']`. (See [Facts and Built-In Variables](https://puppet.com/docs/puppet/latest/lang_facts_and_builtin_vars.html) for more details.) * For best compatibility, you should limit the value of `certname` to only use lowercase letters, numbers, periods, underscores, and dashes. (That is, it should match `/\A[a-z0-9._-]+\Z/`.) * The special value `ca` is reserved, and can't be used as the certname for a normal node. Defaults to the node's fully qualified domain name.", :hook => proc { |value| raise(ArgumentError, _("Certificate names must be lower case")) unless value == value.downcase }}, :dns_alt_names => { :default => '', :desc => <`) (Note `puppet cert clean` is deprecated and will be replaced with `puppetserver ca clean` in Puppet 6.) * On the server: Delete the old certificate (and any old certificate signing requests) from the [ssldir](https://puppet.com/docs/puppet/latest/dirs_ssldir.html). * On the server: Run `puppet agent -t --ca_server ` to request a new certificate * On the CA server: Sign the certificate request, explicitly allowing alternate names (`puppet cert sign --allow-dns-alt-names `). (Note `puppet cert sign` is deprecated and will be replaced with `puppetserver ca sign` in Puppet 6.) * On the server: Run `puppet agent -t --ca_server ` to retrieve the cert. * On the server: Start Puppet Server again. To see all the alternate names your servers are using, log into your CA server and run `puppet cert list -a`, then check the output for `(alt names: ...)`. Most agent nodes should NOT have alternate names; the only certs that should have them are Puppet Server nodes that you want other agents to trust. EOT }, :csr_attributes => { :default => "$confdir/csr_attributes.yaml", :type => :file, :desc => < { :default => "$ssldir/certs", :type => :directory, :mode => "0755", :owner => "service", :group => "service", :desc => "The certificate directory." }, :ssldir => { :default => "$confdir/ssl", :type => :directory, :mode => "0771", :owner => "service", :group => "service", :desc => "Where SSL certificates are kept." }, :publickeydir => { :default => "$ssldir/public_keys", :type => :directory, :mode => "0755", :owner => "service", :group => "service", :desc => "The public key directory." }, :requestdir => { :default => "$ssldir/certificate_requests", :type => :directory, :mode => "0755", :owner => "service", :group => "service", :desc => "Where host certificate requests are stored." }, :privatekeydir => { :default => "$ssldir/private_keys", :type => :directory, :mode => "0750", :owner => "service", :group => "service", :desc => "The private key directory." }, :privatedir => { :default => "$ssldir/private", :type => :directory, :mode => "0750", :owner => "service", :group => "service", :desc => "Where the client stores private certificate information." }, :passfile => { :default => "$privatedir/password", :type => :file, :mode => "0640", :owner => "service", :group => "service", :desc => "Where puppet agent stores the password for its private key. Generally unused." }, :hostcsr => { :default => "$ssldir/csr_$certname.pem", :type => :file, :mode => "0644", :owner => "service", :group => "service", :desc => "Where individual hosts store and look for their certificate requests." }, :hostcert => { :default => "$certdir/$certname.pem", :type => :file, :mode => "0644", :owner => "service", :group => "service", :desc => "Where individual hosts store and look for their certificates." }, :hostprivkey => { :default => "$privatekeydir/$certname.pem", :type => :file, :mode => "0640", :owner => "service", :group => "service", :desc => "Where individual hosts store and look for their private key." }, :hostpubkey => { :default => "$publickeydir/$certname.pem", :type => :file, :mode => "0644", :owner => "service", :group => "service", :desc => "Where individual hosts store and look for their public key." }, :localcacert => { :default => "$certdir/ca.pem", :type => :file, :mode => "0644", :owner => "service", :group => "service", :desc => "Where each client stores the CA certificate." }, :ssl_client_ca_auth => { :type => :file, :mode => "0644", :owner => "service", :group => "service", :desc => "Certificate authorities who issue server certificates. SSL servers will not be considered authentic unless they possess a certificate issued by an authority listed in this file. If this setting has no value then the Puppet master's CA certificate (localcacert) will be used." }, :ssl_server_ca_auth => { :type => :file, :mode => "0644", :owner => "service", :group => "service", :desc => "Certificate authorities who issue client certificates. SSL clients will not be considered authentic unless they possess a certificate issued by an authority listed in this file. If this setting has no value then the Puppet master's CA certificate (localcacert) will be used." }, :hostcrl => { :default => "$ssldir/crl.pem", :type => :file, :mode => "0644", :owner => "service", :group => "service", :desc => "Where the host's certificate revocation list can be found. This is distinct from the certificate authority's CRL." }, :certificate_revocation => { :default => 'chain', :type => :certificate_revocation, :desc => < { :default => lambda { default_digest_algorithm }, :type => :enum, :values => valid_digest_algorithms, :desc => "Which digest algorithm to use for file resources and the filebucket. Valid values are #{valid_digest_algorithms.join(', ')}. Default is #{default_digest_algorithm}.", }, :supported_checksum_types => { :default => lambda { default_file_checksum_types }, :type => :array, :desc => "Checksum types supported by this agent for use in file resources of a static catalog. Values must be comma-separated. Valid types are #{valid_file_checksum_types.join(', ')}. Default is #{default_file_checksum_types.join(', ')}.", :hook => proc do |value| values = munge(value) invalid = values - Puppet.valid_file_checksum_types if not invalid.empty? raise ArgumentError, _("Invalid value '%{value}' for parameter %{name}. Allowed values are '%{allowed_values}'") % { value: invalid.first, name: @name, allowed_values: Puppet.valid_file_checksum_types.join("', '") } end end }, :logdest => { :type => :string, :desc => "Where to send log messages. Choose between 'syslog' (the POSIX syslog service), 'eventlog' (the Windows Event Log), 'console', or the path to a log file." # Sure would be nice to set the Puppet::Util::Log destination here in an :on_initialize_and_write hook, # unfortunately we have a large number of tests that rely on the logging not resetting itself when the # settings are initialized as they test what gets logged during settings initialization. } ) define_settings( :ca, :ca_name => { :default => "Puppet CA: $certname", :desc => "The name to use the Certificate Authority certificate.", }, :cadir => { :default => "$ssldir/ca", :type => :directory, :owner => "service", :group => "service", :mode => "0755", :desc => "The root directory for the certificate authority.", }, :cacert => { :default => "$cadir/ca_crt.pem", :type => :file, :owner => "service", :group => "service", :mode => "0644", :desc => "The CA certificate.", }, :cakey => { :default => "$cadir/ca_key.pem", :type => :file, :owner => "service", :group => "service", :mode => "0640", :desc => "The CA private key.", }, :capub => { :default => "$cadir/ca_pub.pem", :type => :file, :owner => "service", :group => "service", :mode => "0644", :desc => "The CA public key.", }, :cacrl => { :default => "$cadir/ca_crl.pem", :type => :file, :owner => "service", :group => "service", :mode => "0644", :desc => "The certificate revocation list (CRL) for the CA. Will be used if present but otherwise ignored.", }, :caprivatedir => { :default => "$cadir/private", :type => :directory, :owner => "service", :group => "service", :mode => "0750", :desc => "Where the CA stores private certificate information. This setting is deprecated and will be removed in Puppet 6.", :hook => proc do |value| Puppet.deprecation_warning(_("The 'caprivatedir' setting is deprecated and will be removed in Puppet 6.")) end, }, :csrdir => { :default => "$cadir/requests", :type => :directory, :owner => "service", :group => "service", :mode => "0755", :desc => "Where the CA stores certificate requests.", }, :signeddir => { :default => "$cadir/signed", :type => :directory, :owner => "service", :group => "service", :mode => "0755", :desc => "Where the CA stores signed certificates.", }, :capass => { :default => "$caprivatedir/ca.pass", :type => :file, :owner => "service", :group => "service", :mode => "0640", :desc => "Where the CA stores the password for the private key. This setting is deprecated and will be removed in Puppet 6.", :hook => proc do |value| Puppet.deprecation_warning(_("The 'capass' setting is deprecated and will be removed in Puppet 6.")) end, }, :serial => { :default => "$cadir/serial", :type => :file, :owner => "service", :group => "service", :mode => "0644", :desc => "Where the serial number for certificates is stored.", }, :autosign => { :default => "$confdir/autosign.conf", :type => :autosign, :desc => "Whether (and how) to autosign certificate requests. This setting is only relevant on a puppet master acting as a certificate authority (CA). Valid values are true (autosigns all certificate requests; not recommended), false (disables autosigning certificates), or the absolute path to a file. The file specified in this setting may be either a **configuration file** or a **custom policy executable.** Puppet will automatically determine what it is: If the Puppet user (see the `user` setting) can execute the file, it will be treated as a policy executable; otherwise, it will be treated as a config file. If a custom policy executable is configured, the CA puppet master will run it every time it receives a CSR. The executable will be passed the subject CN of the request _as a command line argument,_ and the contents of the CSR in PEM format _on stdin._ It should exit with a status of 0 if the cert should be autosigned and non-zero if the cert should not be autosigned. If a certificate request is not autosigned, it will persist for review. An admin user can use the `puppet cert sign` command to manually sign it, or can delete the request. For info on autosign configuration files, see [the guide to Puppet's config files](https://puppet.com/docs/puppet/latest/config_about_settings.html).", }, :allow_duplicate_certs => { :default => false, :type => :boolean, :desc => "Whether to allow a new certificate request to overwrite an existing certificate.", }, :ca_ttl => { :default => "5y", :type => :duration, :desc => "The default TTL for new certificates. #{AS_DURATION}", }, :keylength => { :default => 4096, :desc => "The bit length of keys.", }, :cert_inventory => { :default => "$cadir/inventory.txt", :type => :file, :mode => "0644", :owner => "service", :group => "service", :desc => "The inventory file. This is a text file to which the CA writes a complete listing of all certificates.", } ) # Define the config default. define_settings(:application, :config_file_name => { :type => :string, :default => Puppet::Settings.default_config_file_name, :desc => "The name of the puppet config file.", }, :config => { :type => :file, :default => "$confdir/${config_file_name}", :desc => "The configuration file for the current puppet application.", }, :pidfile => { :type => :file, :default => "$rundir/${run_mode}.pid", :desc => "The file containing the PID of a running process. This file is intended to be used by service management frameworks and monitoring systems to determine if a puppet process is still in the process table.", }, :sourceaddress => { :default => nil, :desc => "The address the agent should use to initiate requests.", }, :bindaddress => { :default => "*", :desc => "The address a listening server should bind to.", :deprecated => :completely, } ) define_settings(:environment, :manifest => { :default => nil, :type => :file_or_directory, :desc => "The entry-point manifest for puppet master. This can be one file or a directory of manifests to be evaluated in alphabetical order. Puppet manages this path as a directory if one exists or if the path ends with a / or \\. Setting a global value for `manifest` in puppet.conf is not allowed (but it can be overridden from the commandline). Please use directory environments instead. If you need to use something other than the environment's `manifests` directory as the main manifest, you can set `manifest` in environment.conf. For more info, see ", }, :modulepath => { :default => "", :type => :path, :desc => "The search path for modules, as a list of directories separated by the system path separator character. (The POSIX path separator is ':', and the Windows path separator is ';'.) Setting a global value for `modulepath` in puppet.conf is not allowed (but it can be overridden from the commandline). Please use directory environments instead. If you need to use something other than the default modulepath of `:$basemodulepath`, you can set `modulepath` in environment.conf. For more info, see ", }, :config_version => { :default => "", :desc => "How to determine the configuration version. By default, it will be the time that the configuration is parsed, but you can provide a shell script to override how the version is determined. The output of this script will be added to every log message in the reports, allowing you to correlate changes on your hosts to the source version on the server. Setting a global value for config_version in puppet.conf is not allowed (but it can be overridden from the commandline). Please set a per-environment value in environment.conf instead. For more info, see ", } ) define_settings(:master, :user => { :default => "puppet", :desc => "The user Puppet Server will run as. Used to ensure the agent side processes (agent, apply, etc) create files and directories readable by Puppet Server when necessary.", }, :group => { :default => "puppet", :desc => "The group Puppet Server will run as. Used to ensure the agent side processes (agent, apply, etc) create files and directories readable by Puppet Server when necessary.", }, :default_manifest => { :default => "./manifests", :type => :string, :desc => "The default main manifest for directory environments. Any environment that doesn't set the `manifest` setting in its `environment.conf` file will use this manifest. This setting's value can be an absolute or relative path. An absolute path will make all environments default to the same main manifest; a relative path will allow each environment to use its own manifest, and Puppet will resolve the path relative to each environment's main directory. In either case, the path can point to a single file or to a directory of manifests to be evaluated in alphabetical order.", }, :disable_per_environment_manifest => { :default => false, :type => :boolean, :desc => "Whether to disallow an environment-specific main manifest. When set to `true`, Puppet will use the manifest specified in the `default_manifest` setting for all environments. If an environment specifies a different main manifest in its `environment.conf` file, catalog requests for that environment will fail with an error. This setting requires `default_manifest` to be set to an absolute path.", :hook => proc do |value| if value && !Pathname.new(Puppet[:default_manifest]).absolute? raise(Puppet::Settings::ValidationError, "The 'default_manifest' setting must be set to an absolute path when 'disable_per_environment_manifest' is true") end end, }, :code => { :default => "", :desc => "Code to parse directly. This is essentially only used by `puppet`, and should only be set if you're writing your own Puppet executable.", }, :masterhttplog => { :default => "$logdir/masterhttp.log", :type => :file, :owner => "service", :group => "service", :mode => "0660", :create => true, :deprecated => :completely, :desc => "Where the puppet master web server saves its access log. This is only used when running a WEBrick puppet master. When puppet master is running under a Rack server like Passenger, that web server will have its own logging behavior." }, :masterport => { :default => 8140, :desc => "The default port puppet subcommands use to communicate with Puppet Server. (eg `puppet facts upload`, `puppet agent`). May be overridden by more specific settings (see `ca_port`, `report_port`).", }, :node_name => { :default => "cert", :desc => "How the puppet master determines the client's identity and sets the 'hostname', 'fqdn' and 'domain' facts for use in the manifest, in particular for determining which 'node' statement applies to the client. Possible values are 'cert' (use the subject's CN in the client's certificate) and 'facter' (use the hostname that the client reported in its facts)", }, :bucketdir => { :default => "$vardir/bucket", :type => :directory, :mode => "0750", :owner => "service", :group => "service", :desc => "Where FileBucket files are stored." }, :rest_authconfig => { :default => "$confdir/auth.conf", :type => :file, :deprecated => :completely, :desc => "The configuration file that defines the rights to the different rest indirections. This can be used as a fine-grained authorization system for `puppet master`. The `puppet master` command is deprecated and Puppet Server uses its own auth.conf that must be placed within its configuration directory.", }, :ca => { :default => true, :type => :boolean, :desc => "Whether the master should function as a certificate authority.", :hook => proc do |value| Puppet.deprecation_warning(_("The 'ca' setting is deprecated and will be removed in Puppet 6.")) end, }, :trusted_oid_mapping_file => { :default => "$confdir/custom_trusted_oid_mapping.yaml", :type => :file, :desc => "File that provides mapping between custom SSL oids and user-friendly names" }, :basemodulepath => { :default => lambda do if Puppet::Util::Platform.windows? '$codedir/modules' else '$codedir/modules:/opt/puppetlabs/puppet/modules' end end, :type => :path, :desc => "The search path for **global** modules. Should be specified as a list of directories separated by the system path separator character. (The POSIX path separator is ':', and the Windows path separator is ';'.) These are the modules that will be used by _all_ environments. Note that the `modules` directory of the active environment will have priority over any global directories. For more info, see ", }, :ssl_client_header => { :default => "HTTP_X_CLIENT_DN", :desc => "The header containing an authenticated client's SSL DN. This header must be set by the proxy to the authenticated client's SSL DN (e.g., `/CN=puppet.puppetlabs.com`). Puppet will parse out the Common Name (CN) from the Distinguished Name (DN) and use the value of the CN field for authorization. Note that the name of the HTTP header gets munged by the web server common gateway interface: an `HTTP_` prefix is added, dashes are converted to underscores, and all letters are uppercased. Thus, to use the `X-Client-DN` header, this setting should be `HTTP_X_CLIENT_DN`.", }, :ssl_client_verify_header => { :default => "HTTP_X_CLIENT_VERIFY", :desc => "The header containing the status message of the client verification. This header must be set by the proxy to 'SUCCESS' if the client successfully authenticated, and anything else otherwise. Note that the name of the HTTP header gets munged by the web server common gateway interface: an `HTTP_` prefix is added, dashes are converted to underscores, and all letters are uppercased. Thus, to use the `X-Client-Verify` header, this setting should be `HTTP_X_CLIENT_VERIFY`.", }, # To make sure this directory is created before we try to use it on the server, we need # it to be in the server section (#1138). :yamldir => { :default => "$vardir/yaml", :type => :directory, :owner => "service", :group => "service", :mode => "0750", :desc => "The directory in which YAML data is stored, usually in a subdirectory."}, :server_datadir => { :default => "$vardir/server_data", :type => :directory, :owner => "service", :group => "service", :mode => "0750", :desc => "The directory in which serialized data is stored, usually in a subdirectory."}, :reports => { :default => "store", :desc => "The list of report handlers to use. When using multiple report handlers, their names should be comma-separated, with whitespace allowed. (For example, `reports = http, store`.) This setting is relevant to puppet master and puppet apply. The puppet master will call these report handlers with the reports it receives from agent nodes, and puppet apply will call them with its own report. (In all cases, the node applying the catalog must have `report = true`.) See the report reference for information on the built-in report handlers; custom report handlers can also be loaded from modules. (Report handlers are loaded from the lib directory, at `puppet/reports/NAME.rb`.)", }, :reportdir => { :default => "$vardir/reports", :type => :directory, :mode => "0750", :owner => "service", :group => "service", :desc => "The directory in which to store reports. Each node gets a separate subdirectory in this directory. This setting is only used when the `store` report processor is enabled (see the `reports` setting)."}, :reporturl => { :default => "http://localhost:3000/reports/upload", :desc => "The URL that reports should be forwarded to. This setting is only used when the `http` report processor is enabled (see the `reports` setting).", }, :fileserverconfig => { :default => "$confdir/fileserver.conf", :type => :file, :desc => "Where the fileserver configuration is stored.", }, :strict_hostname_checking => { :default => false, :desc => "Whether to only search for the complete hostname as it is in the certificate when searching for node information in the catalogs.", } ) define_settings(:device, :devicedir => { :default => "$vardir/devices", :type => :directory, :mode => "0750", :owner => "service", :group => "service", :desc => "The root directory of devices' $vardir.", }, :deviceconfig => { :default => "$confdir/device.conf", :desc => "Path to the device config file for puppet device.", } ) define_settings(:agent, :node_name_value => { :default => "$certname", :desc => "The explicit value used for the node name for all requests the agent makes to the master. WARNING: This setting is mutually exclusive with node_name_fact. Changing this setting also requires changes to the default auth.conf configuration on the Puppet Master. Please see http://links.puppet.com/node_name_value for more information." }, :node_name_fact => { :default => "", :desc => "The fact name used to determine the node name used for all requests the agent makes to the master. WARNING: This setting is mutually exclusive with node_name_value. Changing this setting also requires changes to the default auth.conf configuration on the Puppet Master. Please see http://links.puppet.com/node_name_fact for more information.", :hook => proc do |value| if !value.empty? and Puppet[:node_name_value] != Puppet[:certname] raise "Cannot specify both the node_name_value and node_name_fact settings" end end }, :statefile => { :default => "$statedir/state.yaml", :type => :file, :mode => "0660", :desc => "Where puppet agent and puppet master store state associated with the running configuration. In the case of puppet master, this file reflects the state discovered through interacting with clients." }, :statettl => { :default => "32d", :type => :ttl, :desc => "How long the Puppet agent should cache when a resource was last checked or synced. #{AS_DURATION} A value of `0` or `unlimited` will disable cache pruning. This setting affects the usage of `schedule` resources, as the information about when a resource was last checked (and therefore when it needs to be checked again) is stored in the `statefile`. The `statettl` needs to be large enough to ensure that a resource will not trigger multiple times during a schedule due to its entry expiring from the cache." }, :transactionstorefile => { :default => "$statedir/transactionstore.yaml", :type => :file, :mode => "0660", :desc => "Transactional storage file for persisting data between transactions for the purposes of infering information (such as corrective_change) on new data received." }, :clientyamldir => { :default => "$vardir/client_yaml", :type => :directory, :mode => "0750", :desc => "The directory in which client-side YAML data is stored." }, :client_datadir => { :default => "$vardir/client_data", :type => :directory, :mode => "0750", :desc => "The directory in which serialized data is stored on the client." }, :classfile => { :default => "$statedir/classes.txt", :type => :file, :owner => "root", :mode => "0640", :desc => "The file in which puppet agent stores a list of the classes associated with the retrieved configuration. Can be loaded in the separate `puppet` executable using the `--loadclasses` option."}, :resourcefile => { :default => "$statedir/resources.txt", :type => :file, :owner => "root", :mode => "0640", :desc => "The file in which puppet agent stores a list of the resources associated with the retrieved configuration." }, :puppetdlog => { :default => "$logdir/puppetd.log", :type => :file, :owner => "root", :mode => "0640", :desc => "The fallback log file. This is only used when the `--logdest` option is not specified AND Puppet is running on an operating system where both the POSIX syslog service and the Windows Event Log are unavailable. (Currently, no supported operating systems match that description.) Despite the name, both puppet agent and puppet master will use this file as the fallback logging destination. For control over logging destinations, see the `--logdest` command line option in the manual pages for puppet master, puppet agent, and puppet apply. You can see man pages by running `puppet --help`, or read them online at https://puppet.com/docs/puppet/latest/man/." }, :server => { :default => "puppet", :desc => "The puppet master server to which the puppet agent should connect.", :call_hook => :on_initialize_and_write, :hook => proc { |value| if Puppet.settings.set_by_config?(:server) && Puppet.settings.set_by_config?(:server_list) #TRANSLATOR 'server' and 'server_list' are setting names and should not be translated message = _('Attempted to set both server and server_list.') message += ' ' + _('Server setting will not be used.') Puppet.deprecation_warning(message, :SERVER_DUPLICATION) end } }, :server_list => { :default => [], :type => :server_list, :desc => "The list of puppet master servers to which the puppet agent should connect, in the order that they will be tried.", :call_hook => :on_initialize_and_write, :hook => proc { |value| if Puppet.settings.set_by_config?(:server) && Puppet.settings.set_by_config?(:server_list) #TRANSLATOR 'server' and 'server_list' are setting names and should not be translated message = _('Attempted to set both server and server_list.') message += ' ' + _('Server setting will not be used.') Puppet.deprecation_warning(message, :SERVER_DUPLICATION) end } }, :use_srv_records => { :default => false, :type => :boolean, :desc => "Whether the server will search for SRV records in DNS for the current domain.", }, :srv_domain => { :default => lambda { Puppet::Settings.domain_fact }, :desc => "The domain which will be queried to find the SRV records of servers to use.", }, :ignoreschedules => { :default => false, :type => :boolean, :desc => "Boolean; whether puppet agent should ignore schedules. This is useful for initial puppet agent runs.", }, :default_schedules => { :default => true, :type => :boolean, :desc => "Boolean; whether to generate the default schedule resources. Setting this to false is useful for keeping external report processors clean of skipped schedule resources.", }, :noop => { :default => false, :type => :boolean, :desc => "Whether to apply catalogs in noop mode, which allows Puppet to partially simulate a normal run. This setting affects puppet agent and puppet apply. When running in noop mode, Puppet will check whether each resource is in sync, like it does when running normally. However, if a resource attribute is not in the desired state (as declared in the catalog), Puppet will take no action, and will instead report the changes it _would_ have made. These simulated changes will appear in the report sent to the puppet master, or be shown on the console if running puppet agent or puppet apply in the foreground. The simulated changes will not send refresh events to any subscribing or notified resources, although Puppet will log that a refresh event _would_ have been sent. **Important note:** [The `noop` metaparameter](https://puppet.com/docs/puppet/latest/metaparameter.html#noop) allows you to apply individual resources in noop mode, and will override the global value of the `noop` setting. This means a resource with `noop => false` _will_ be changed if necessary, even when running puppet agent with `noop = true` or `--noop`. (Conversely, a resource with `noop => true` will only be simulated, even when noop mode is globally disabled.)", }, :runinterval => { :default => "30m", :type => :duration, :desc => "How often puppet agent applies the catalog. Note that a runinterval of 0 means \"run continuously\" rather than \"never run.\" If you want puppet agent to never run, you should start it with the `--no-client` option. #{AS_DURATION}", }, :runtimeout => { :default => 0, :type => :duration, :desc => "The maximum amount of time an agent run is allowed to take. A Puppet agent run that exceeds this timeout will be aborted. Defaults to 0, which is unlimited. #{AS_DURATION}", }, :ca_server => { :default => "$server", :desc => "The server to use for certificate authority requests. It's a separate server because it cannot and does not need to horizontally scale.", }, :ca_port => { :default => "$masterport", :desc => "The port to use for the certificate authority.", }, :preferred_serialization_format => { :default => "json", :desc => "The preferred means of serializing ruby instances for passing over the wire. This won't guarantee that all instances will be serialized using this method, since not all classes can be guaranteed to support this format, but it will be used for all classes that support it.", }, :agent_catalog_run_lockfile => { :default => "$statedir/agent_catalog_run.lock", :type => :string, # (#2888) Ensure this file is not added to the settings catalog. :desc => "A lock file to indicate that a puppet agent catalog run is currently in progress. The file contains the pid of the process that holds the lock on the catalog run.", }, :agent_disabled_lockfile => { :default => "$statedir/agent_disabled.lock", :type => :file, :desc => "A lock file to indicate that puppet agent runs have been administratively disabled. File contains a JSON object with state information.", }, :usecacheonfailure => { :default => true, :type => :boolean, :desc => "Whether to use the cached configuration when the remote configuration will not compile. This option is useful for testing new configurations, where you want to fix the broken configuration rather than reverting to a known-good one.", }, :use_cached_catalog => { :default => false, :type => :boolean, :desc => "Whether to only use the cached catalog rather than compiling a new catalog on every run. Puppet can be run with this enabled by default and then selectively disabled when a recompile is desired. Because a Puppet agent using cached catalogs does not contact the master for a new catalog, it also does not upload facts at the beginning of the Puppet run.", }, :ignoremissingtypes => { :default => false, :type => :boolean, :desc => "Skip searching for classes and definitions that were missing during a prior compilation. The list of missing objects is maintained per-environment and persists until the environment is cleared or the master is restarted.", }, :ignorecache => { :default => false, :type => :boolean, :desc => "This setting has no effect and will be removed in a future Puppet version.", :deprecated => :completely, }, :splaylimit => { :default => "$runinterval", :type => :duration, :desc => "The maximum time to delay before an agent's first run when `splay` is enabled. Defaults to the agent's `$runinterval`. The `splay` interval is random and recalculated each time the agent is started or restarted. #{AS_DURATION}", }, :splay => { :default => false, :type => :boolean, :desc => "Whether to sleep for a random amount of time, ranging from immediately up to its `$splaylimit`, before performing its first agent run after a service restart. After this period, the agent runs periodically on its `$runinterval`. For example, assume a default 30-minute `$runinterval`, `splay` set to its default of `false`, and an agent starting at :00 past the hour. The agent would check in every 30 minutes at :01 and :31 past the hour. With `splay` enabled, it waits any amount of time up to its `$splaylimit` before its first run. For example, it might randomly wait 8 minutes, then start its first run at :08 past the hour. With the `$runinterval` at its default 30 minutes, its next run will be at :38 past the hour. If you restart an agent's puppet service with `splay` enabled, it recalculates its splay period and delays its first agent run after restarting for this new period. If you simultaneously restart a group of puppet agents with `splay` enabled, their checkins to your puppet masters can be distributed more evenly.", }, :clientbucketdir => { :default => "$vardir/clientbucket", :type => :directory, :mode => "0750", :desc => "Where FileBucket files are stored locally." }, :configtimeout => { :default => "2m", :type => :duration, :desc => "How long the client should wait for the configuration to be retrieved before considering it a failure. This setting is deprecated and has been replaced by http_connect_timeout and http_read_timeout. #{AS_DURATION}", :deprecated => :completely, :hook => proc do |value| Puppet[:http_connect_timeout] = value Puppet[:http_read_timeout] = value end }, :report_server => { :default => "$server", :desc => "The server to send transaction reports to.", }, :report_port => { :default => "$masterport", :desc => "The port to communicate with the report_server.", }, :report => { :default => true, :type => :boolean, :desc => "Whether to send reports after every transaction.", }, :lastrunfile => { :default => "$statedir/last_run_summary.yaml", :type => :file, :mode => "0644", :desc => "Where puppet agent stores the last run report summary in yaml format." }, :lastrunreport => { :default => "$statedir/last_run_report.yaml", :type => :file, :mode => "0640", :desc => "Where puppet agent stores the last run report in yaml format." }, :graph => { :default => false, :type => :boolean, :desc => "Whether to create .dot graph files, which let you visualize the dependency and containment relationships in Puppet's catalog. You can load and view these files with tools like [OmniGraffle](http://www.omnigroup.com/applications/omnigraffle/) (OS X) or [graphviz](http://www.graphviz.org/) (multi-platform). Graph files are created when _applying_ a catalog, so this setting should be used on nodes running `puppet agent` or `puppet apply`. The `graphdir` setting determines where Puppet will save graphs. Note that we don't save graphs for historical runs; Puppet will replace the previous .dot files with new ones every time it applies a catalog. See your graphing software's documentation for details on opening .dot files. If you're using GraphViz's `dot` command, you can do a quick PNG render with `dot -Tpng -o `.", }, :graphdir => { :default => "$statedir/graphs", :type => :directory, :desc => "Where to save .dot-format graphs (when the `graph` setting is enabled).", }, :waitforcert => { :default => "2m", :type => :duration, :desc => "How frequently puppet agent should ask for a signed certificate. When starting for the first time, puppet agent will submit a certificate signing request (CSR) to the server named in the `ca_server` setting (usually the puppet master); this may be autosigned, or may need to be approved by a human, depending on the CA server's configuration. Puppet agent cannot apply configurations until its approved certificate is available. Since the certificate may or may not be available immediately, puppet agent will repeatedly try to fetch it at this interval. You can turn off waiting for certificates by specifying a time of 0, in which case puppet agent will exit if it cannot get a cert. #{AS_DURATION}", }, :ordering => { :type => :enum, :values => ["manifest", "title-hash", "random"], :default => "manifest", :desc => "How unrelated resources should be ordered when applying a catalog. Allowed values are `title-hash`, `manifest`, and `random`. This setting affects puppet agent and puppet apply, but not puppet master. * `manifest` (the default) will use the order in which the resources were declared in their manifest files. * `title-hash` (the default in 3.x) will order resources randomly, but will use the same order across runs and across nodes. It is only of value if you're migrating from 3.x and have errors running with `manifest`. * `random` will order resources randomly and change their order with each run. This can work like a fuzzer for shaking out undeclared dependencies. Regardless of this setting's value, Puppet will always obey explicit dependencies set with the before/require/notify/subscribe metaparameters and the `->`/`~>` chaining arrows; this setting only affects the relative ordering of _unrelated_ resources. This setting is deprecated, and will always have a value of `manifest` in 6.0 and up.", :call_hook => :on_initialize_and_write, :hook => proc { |value| if value != "manifest" Puppet.deprecation_warning(_('Setting %{name} is deprecated.') % { name: 'ordering' }, 'setting-ordering') end } } ) # Plugin information. define_settings( :main, :plugindest => { :type => :directory, :default => "$libdir", :desc => "Where Puppet should store plugins that it pulls down from the central server.", }, :pluginsource => { :default => "puppet:///plugins", :desc => "From where to retrieve plugins. The standard Puppet `file` type is used for retrieval, so anything that is a valid file source can be used here.", }, :pluginfactdest => { :type => :directory, :default => "$vardir/facts.d", :desc => "Where Puppet should store external facts that are being handled by pluginsync", }, :pluginfactsource => { :default => "puppet:///pluginfacts", :desc => "Where to retrieve external facts for pluginsync", }, :localedest => { :type => :directory, :default => "$vardir/locales", :desc => "Where Puppet should store translation files that it pulls down from the central server.", }, :localesource => { :default => "puppet:///locales", :desc => "From where to retrieve translation files. The standard Puppet `file` type is used for retrieval, so anything that is a valid file source can be used here.", }, :pluginsync => { :default => true, :type => :boolean, :desc => "Whether plugins should be synced with the central server. This setting is deprecated.", :hook => proc { |value| #TRANSLATORS 'pluginsync' is a setting and should not be translated Puppet.deprecation_warning(_("Setting 'pluginsync' is deprecated.")) } }, :pluginsignore => { :default => ".svn CVS .git .hg", :desc => "What files to ignore when pulling down plugins.", } ) # Central fact information. define_settings( :main, :factpath => { :type => :path, :default => "$vardir/lib/facter#{File::PATH_SEPARATOR}$vardir/facts", :desc => "Where Puppet should look for facts. Multiple directories should be separated by the system path separator character. (The POSIX path separator is ':', and the Windows path separator is ';'.)", :call_hook => :on_initialize_and_write, # Call our hook with the default value, so we always get the value added to facter. :hook => proc do |value| paths = value.split(File::PATH_SEPARATOR) Facter.search(*paths) end } ) define_settings( :transaction, :tags => { :default => "", :desc => "Tags to use to find resources. If this is set, then only resources tagged with the specified tags will be applied. Values must be comma-separated.", }, :skip_tags => { :default => "", :desc => "Tags to use to filter resources. If this is set, then only resources not tagged with the specified tags will be applied. Values must be comma-separated.", }, :evaltrace => { :default => false, :type => :boolean, :desc => "Whether each resource should log when it is being evaluated. This allows you to interactively see exactly what is being done.", }, :summarize => { :default => false, :type => :boolean, :desc => "Whether to print a transaction summary.", } ) define_settings( :main, :external_nodes => { :default => "none", :desc => "The external node classifier (ENC) script to use for node data. Puppet combines this data with the main manifest to produce node catalogs. To enable this setting, set the `node_terminus` setting to `exec`. This setting's value must be the path to an executable command that can produce node information. The command must: * Take the name of a node as a command-line argument. * Return a YAML hash with up to three keys: * `classes` --- A list of classes, as an array or hash. * `environment` --- A string. * `parameters` --- A list of top-scope variables to set, as a hash. * For unknown nodes, exit with a non-zero exit code. Generally, an ENC script makes requests to an external data source. For more info, see [the ENC documentation](https://puppet.com/docs/puppet/latest/nodes_external.html).", } ) define_settings( :ldap, :ldapssl => { :default => false, :type => :boolean, :desc => "Whether SSL should be used when searching for nodes. Defaults to false because SSL usually requires certificates to be set up on the client side.", }, :ldaptls => { :default => false, :type => :boolean, :desc => "Whether TLS should be used when searching for nodes. Defaults to false because TLS usually requires certificates to be set up on the client side.", }, :ldapserver => { :default => "ldap", :desc => "The LDAP server. Only used if `node_terminus` is set to `ldap`.", }, :ldapport => { :default => 389, :desc => "The LDAP port. Only used if `node_terminus` is set to `ldap`.", }, :ldapstring => { :default => "(&(objectclass=puppetClient)(cn=%s))", :desc => "The search string used to find an LDAP node.", }, :ldapclassattrs => { :default => "puppetclass", :desc => "The LDAP attributes to use to define Puppet classes. Values should be comma-separated.", }, :ldapstackedattrs => { :default => "puppetvar", :desc => "The LDAP attributes that should be stacked to arrays by adding the values in all hierarchy elements of the tree. Values should be comma-separated.", }, :ldapattrs => { :default => "all", :desc => "The LDAP attributes to include when querying LDAP for nodes. All returned attributes are set as variables in the top-level scope. Multiple values should be comma-separated. The value 'all' returns all attributes.", }, :ldapparentattr => { :default => "parentnode", :desc => "The attribute to use to define the parent node.", }, :ldapuser => { :default => "", :desc => "The user to use to connect to LDAP. Must be specified as a full DN.", }, :ldappassword => { :default => "", :desc => "The password to use to connect to LDAP.", }, :ldapbase => { :default => "", :desc => "The search base for LDAP searches. It's impossible to provide a meaningful default here, although the LDAP libraries might have one already set. Generally, it should be the 'ou=Hosts' branch under your main directory.", } ) define_settings(:master, :storeconfigs => { :default => false, :type => :boolean, :desc => "Whether to store each client's configuration, including catalogs, facts, and related data. This also enables the import and export of resources in the Puppet language - a mechanism for exchange resources between nodes. By default this uses the 'puppetdb' backend. You can adjust the backend using the storeconfigs_backend setting.", # Call our hook with the default value, so we always get the libdir set. :call_hook => :on_initialize_and_write, :hook => proc do |value| require 'puppet/node' require 'puppet/node/facts' if value Puppet::Resource::Catalog.indirection.cache_class = :store_configs Puppet.settings.override_default(:catalog_cache_terminus, :store_configs) Puppet::Node::Facts.indirection.cache_class = :store_configs Puppet::Resource.indirection.terminus_class = :store_configs end end }, :storeconfigs_backend => { :type => :terminus, :default => "puppetdb", :desc => "Configure the backend terminus used for StoreConfigs. By default, this uses the PuppetDB store, which must be installed and configured before turning on StoreConfigs." } ) define_settings(:parser, :max_errors => { :default => 10, :desc => <<-'EOT' Sets the max number of logged/displayed parser validation errors in case multiple errors have been detected. A value of 0 is the same as a value of 1; a minimum of one error is always raised. The count is per manifest. EOT }, :max_warnings => { :default => 10, :desc => <<-'EOT' Sets the max number of logged/displayed parser validation warnings in case multiple warnings have been detected. A value of 0 blocks logging of warnings. The count is per manifest. EOT }, :max_deprecations => { :default => 10, :desc => <<-'EOT' Sets the max number of logged/displayed parser validation deprecation warnings in case multiple deprecation warnings have been detected. A value of 0 blocks the logging of deprecation warnings. The count is per manifest. EOT }, :strict_variables => { :default => false, :type => :boolean, :desc => <<-'EOT' Causes an evaluation error when referencing unknown variables. (This does not affect referencing variables that are explicitly set to undef). EOT }, :tasks => { :default => false, :type => :boolean, :desc => <<-'EOT' Turns on experimental support for tasks and plans in the puppet language. This is for internal API use only. Do not change this setting. EOT } ) define_settings(:puppetdoc, :document_all => { :default => false, :type => :boolean, :desc => "Whether to document all resources when using `puppet doc` to generate manifest documentation.", } ) define_settings( :main, :rich_data => { :default => false, :type => :boolean, :hook => proc do |value| envs = Puppet.lookup(:environments) { nil } envs.clear_all unless envs.nil? end, :desc => <<-'EOT' Enables having extended data in the catalog by storing them as a hash with the special key `__pcore_type__`. When enabled, resource containing values of the data types `Binary`, `Regexp`, `SemVer`, `SemVerRange`, `Timespan` and `Timestamp`, as well as instances of types derived from `Object` retain their data type. EOT } ) end puppet-5.5.10/lib/puppet/environments.rb0000644005276200011600000003353513417161721020162 0ustar jenkinsjenkins# @api private module Puppet::Environments class EnvironmentNotFound < Puppet::Error def initialize(environment_name, original = nil) environmentpath = Puppet[:environmentpath] super("Could not find a directory environment named '#{environment_name}' anywhere in the path: #{environmentpath}. Does the directory exist?", original) end end # @api private module EnvironmentCreator # Create an anonymous environment. # # @param module_path [String] A list of module directories separated by the # PATH_SEPARATOR # @param manifest [String] The path to the manifest # @return A new environment with the `name` `:anonymous` # # @api private def for(module_path, manifest) Puppet::Node::Environment.create(:anonymous, module_path.split(File::PATH_SEPARATOR), manifest) end end # Provide any common methods that loaders should have. It requires that any # classes that include this module implement get # @api private module EnvironmentLoader # @!macro loader_get_or_fail def get!(name) environment = get(name) if environment environment else raise EnvironmentNotFound, name end end def clear_all root = Puppet.lookup(:root_environment) { nil } unless root.nil? root.instance_variable_set(:@static_catalogs, nil) root.instance_variable_set(:@rich_data, nil) end end end # @!macro [new] loader_search_paths # A list of indicators of where the loader is getting its environments from. # @return [Array] The URIs of the load locations # # @!macro [new] loader_list # @return [Array] All of the environments known # to the loader # # @!macro [new] loader_get # Find a named environment # # @param name [String,Symbol] The name of environment to find # @return [Puppet::Node::Environment, nil] the requested environment or nil # if it wasn't found # # @!macro [new] loader_get_conf # Attempt to obtain the initial configuration for the environment. Not all # loaders can provide this. # # @param name [String,Symbol] The name of the environment whose configuration # we are looking up # @return [Puppet::Setting::EnvironmentConf, nil] the configuration for the # requested environment, or nil if not found or no configuration is available # # @!macro [new] loader_get_or_fail # Find a named environment or raise # Puppet::Environments::EnvironmentNotFound when the named environment is # does not exist. # # @param name [String,Symbol] The name of environment to find # @return [Puppet::Node::Environment] the requested environment # A source of pre-defined environments. # # @api private class Static include EnvironmentCreator include EnvironmentLoader def initialize(*environments) @environments = environments end # @!macro loader_search_paths def search_paths ["data:text/plain,internal"] end # @!macro loader_list def list @environments end # @!macro loader_get def get(name) @environments.find do |env| env.name == name.intern end end # Returns a basic environment configuration object tied to the environment's # implementation values. Will not interpolate. # # @!macro loader_get_conf def get_conf(name) env = get(name) if env Puppet::Settings::EnvironmentConf.static_for(env, Puppet[:environment_timeout], Puppet[:static_catalogs], Puppet[:rich_data]) else nil end end end # A source of unlisted pre-defined environments. # # Used only for internal bootstrapping environments which are not relevant # to an end user (such as the fall back 'configured' environment). # # @api private class StaticPrivate < Static # Unlisted # # @!macro loader_list def list [] end end class StaticDirectory < Static # Accepts a single environment in the given directory having the given name (not required to be reflected as the name # of the directory) # def initialize(env_name, env_dir, environment) super(environment) @env_dir = env_dir @env_name = env_name end # @!macro loader_get_conf def get_conf(name) return nil unless name == @env_name Puppet::Settings::EnvironmentConf.load_from(@env_dir, '') end end # Reads environments from a directory on disk. Each environment is # represented as a sub-directory. The environment's manifest setting is the # `manifest` directory of the environment directory. The environment's # modulepath setting is the global modulepath (from the `[master]` section # for the master) prepended with the `modules` directory of the environment # directory. # # @api private class Directories include EnvironmentLoader def initialize(environment_dir, global_module_path) @environment_dir = Puppet::FileSystem.expand_path(environment_dir) @global_module_path = global_module_path ? global_module_path.map { |p| Puppet::FileSystem.expand_path(p) } : nil end # Generate an array of directory loaders from a path string. # @param path [String] path to environment directories # @param global_module_path [Array] the global modulepath setting # @return [Array] An array # of configured directory loaders. def self.from_path(path, global_module_path) environments = path.split(File::PATH_SEPARATOR) environments.map do |dir| Puppet::Environments::Directories.new(dir, global_module_path) end end # @!macro loader_search_paths def search_paths ["file://#{@environment_dir}"] end # @!macro loader_list def list valid_directories.collect do |envdir| name = Puppet::FileSystem.basename_string(envdir).intern create_environment(name) end end # @!macro loader_get def get(name) if valid_directory?(File.join(@environment_dir, name.to_s)) create_environment(name) end end # @!macro loader_get_conf def get_conf(name) envdir = File.join(@environment_dir, name.to_s) if valid_directory?(envdir) return Puppet::Settings::EnvironmentConf.load_from(envdir, @global_module_path) end nil end private def create_environment(name) env_symbol = name.intern setting_values = Puppet.settings.values(env_symbol, Puppet.settings.preferred_run_mode) env = Puppet::Node::Environment.create( env_symbol, Puppet::Node::Environment.split_path(setting_values.interpolate(:modulepath)), setting_values.interpolate(:manifest), setting_values.interpolate(:config_version) ) env end def valid_directory?(envdir) name = Puppet::FileSystem.basename_string(envdir) Puppet::FileSystem.directory?(envdir) && Puppet::Node::Environment.valid_name?(name) end def valid_directories if Puppet::FileSystem.directory?(@environment_dir) Puppet::FileSystem.children(@environment_dir).select do |child| valid_directory?(child) end else [] end end end # Combine together multiple loaders to act as one. # @api private class Combined include EnvironmentLoader def initialize(*loaders) @loaders = loaders end # @!macro loader_search_paths def search_paths @loaders.collect(&:search_paths).flatten end # @!macro loader_list def list @loaders.collect(&:list).flatten end # @!macro loader_get def get(name) @loaders.each do |loader| if env = loader.get(name) return env end end nil end # @!macro loader_get_conf def get_conf(name) @loaders.each do |loader| if conf = loader.get_conf(name) return conf end end nil end def clear_all @loaders.each {|loader| loader.clear_all} end end class Cached include EnvironmentLoader class DefaultCacheExpirationService def created(env) end def expired?(env_name) false end def evicted(env_name) end end def self.cache_expiration_service=(service) @cache_expiration_service = service end def self.cache_expiration_service @cache_expiration_service || DefaultCacheExpirationService.new end # Returns the end of time (the next Mesoamerican Long Count cycle-end after 2012 (5125+2012) = 7137, # or for a 32 bit machine using Ruby < 1.9.3, the year 2038. def self.end_of_time begin Time.gm(7137) rescue ArgumentError Time.gm(2038) end end END_OF_TIME = end_of_time START_OF_TIME = Time.gm(1) def initialize(loader) @loader = loader @cache_expiration_service = Puppet::Environments::Cached.cache_expiration_service @cache = {} # Holds expiration times in sorted order - next to expire is first @expirations = SortedSet.new # Infinity since it there are no entries, this is a cache of the first to expire time @next_expiration = END_OF_TIME end # @!macro loader_list def list @loader.list end # @!macro loader_search_paths def search_paths @loader.search_paths end # @!macro loader_get def get(name) # Aggressively evict all that has expired # This strategy favors smaller memory footprint over environment # retrieval time. clear_all_expired if result = @cache[name] # found in cache return result.value elsif (result = @loader.get(name)) # environment loaded, cache it cache_entry = entry(result) @cache_expiration_service.created(result) add_entry(name, cache_entry) result end end # Adds a cache entry to the cache def add_entry(name, cache_entry) Puppet.debug {"Caching environment '#{name}' #{cache_entry.label}"} @cache[name] = cache_entry expires = cache_entry.expires @expirations.add(expires) if @next_expiration > expires @next_expiration = expires end end private :add_entry # Clears the cache of the environment with the given name. # (The intention is that this could be used from a MANUAL cache eviction command (TBD) def clear(name) @cache.delete(name) Puppet::GettextConfig.delete_text_domain(name) end # Clears all cached environments. # (The intention is that this could be used from a MANUAL cache eviction command (TBD) def clear_all() super @cache = {} @expirations.clear @next_expiration = END_OF_TIME Puppet::GettextConfig.delete_environment_text_domains end # Clears all environments that have expired, either by exceeding their time to live, or # through an explicit eviction determined by the cache expiration service. # def clear_all_expired() t = Time.now return if t < @next_expiration && ! @cache.any? {|name, _| @cache_expiration_service.expired?(name.to_sym) } to_expire = @cache.select { |name, entry| entry.expires < t || @cache_expiration_service.expired?(name.to_sym) } to_expire.each do |name, entry| Puppet.debug {"Evicting cache entry for environment '#{name}'"} @cache_expiration_service.evicted(name) clear(name) @expirations.delete(entry.expires) Puppet.settings.clear_environment_settings(name) end @next_expiration = @expirations.first || END_OF_TIME end # This implementation evicts the cache, and always gets the current # configuration of the environment # # TODO: While this is wasteful since it # needs to go on a search for the conf, it is too disruptive to optimize # this. # # @!macro loader_get_conf def get_conf(name) evict_if_expired(name) @loader.get_conf(name) end # Creates a suitable cache entry given the time to live for one environment # def entry(env) ttl = (conf = get_conf(env.name)) ? conf.environment_timeout : Puppet.settings.value(:environment_timeout) case ttl when 0 NotCachedEntry.new(env) # Entry that is always expired (avoids syscall to get time) when Float::INFINITY Entry.new(env) # Entry that never expires (avoids syscall to get time) else TTLEntry.new(env, ttl) end end # Evicts the entry if it has expired # Also clears caches in Settings that may prevent the entry from being updated def evict_if_expired(name) if (result = @cache[name]) && (result.expired? || @cache_expiration_service.expired?(name)) Puppet.debug {"Evicting cache entry for environment '#{name}'"} @cache_expiration_service.evicted(name) clear(name) Puppet.settings.clear_environment_settings(name) end end # Never evicting entry class Entry attr_reader :value def initialize(value) @value = value end def expired? false end def label "" end def expires END_OF_TIME end end # Always evicting entry class NotCachedEntry < Entry def expired? true end def label "(ttl = 0 sec)" end def expires START_OF_TIME end end # Time to Live eviction policy entry class TTLEntry < Entry def initialize(value, ttl_seconds) super value @ttl = Time.now + ttl_seconds @ttl_seconds = ttl_seconds end def expired? Time.now > @ttl end def label "(ttl = #{@ttl_seconds} sec)" end def expires @ttl end end end end puppet-5.5.10/lib/puppet/error.rb0000644005276200011600000000616113417161721016557 0ustar jenkinsjenkinsmodule Puppet # The base class for all Puppet errors. It can wrap another exception class Error < RuntimeError attr_accessor :original def initialize(message, original=nil) super(Puppet::Util::CharacterEncoding.scrub(message)) @original = original end end module ExternalFileError # This module implements logging with a filename and line number. Use this # for errors that need to report a location in a non-ruby file that we # parse. attr_accessor :line, :file, :pos # May be called with 3 arguments for message, file, line, and exception, or # 4 args including the position on the line. # def initialize(message, file=nil, line=nil, pos=nil, original=nil) if pos.kind_of? Exception original = pos pos = nil end super(message, original) @file = file unless (file.is_a?(String) && file.empty?) @line = line @pos = pos end def to_s msg = super @file = nil if (@file.is_a?(String) && @file.empty?) msg += Puppet::Util::Errors.error_location_with_space(@file, @line, @pos) msg end end class ParseError < Puppet::Error include ExternalFileError end class ResourceError < Puppet::Error include ExternalFileError end # Contains an issue code and can be annotated with an environment and a node class ParseErrorWithIssue < Puppet::ParseError attr_reader :issue_code, :basic_message, :arguments attr_accessor :environment, :node # @param message [String] The error message # @param file [String] The path to the file where the error was found # @param line [Integer] The line in the file # @param pos [Integer] The position on the line # @param original [Exception] Original exception # @param issue_code [Symbol] The issue code # @param arguments [Hash{Symbol=>Object}] Issue arguments # def initialize(message, file=nil, line=nil, pos=nil, original=nil, issue_code= nil, arguments = nil) super(message, file, line, pos, original) @issue_code = issue_code @basic_message = message @arguments = arguments end def to_s msg = super msg = _("Could not parse for environment %{environment}: %{message}") % { environment: environment, message: msg } if environment msg = _("%{message} on node %{node}") % { message: msg, node: node } if node msg end def self.from_issue_and_stack(issue, args = {}) filename, line = Puppet::Pops::PuppetStack.top_of_stack self.new( issue.format(args), filename, line, nil, nil, issue.issue_code, args) end end # An error that already contains location information in the message text class PreformattedError < Puppet::ParseErrorWithIssue end # An error class for when I don't know what happened. Automatically # prints a stack trace when in debug mode. class DevError < Puppet::Error include ExternalFileError end class MissingCommand < Puppet::Error end # Raised when we failed to acquire a lock class LockError < Puppet::Error end end puppet-5.5.10/lib/puppet/etc.rb0000644005276200011600000001676613417161721016215 0ustar jenkinsjenkinsrequire 'puppet/util/character_encoding' # Wrapper around Ruby Etc module allowing us to manage encoding in a single # place. # This represents a subset of Ruby's Etc module, only the methods required by Puppet. # On Ruby 2.1.0 and later, Etc returns strings in variable encoding depending on # environment. The string returned will be labeled with the environment's # encoding (Encoding.default_external), with one exception: If the environment # encoding is 7-bit ASCII, and any individual character bit representation is # equal to or greater than 128 - \x80 - 0b10000000 - signifying the smallest # 8-bit big-endian value, the returned string will be in BINARY encoding instead # of environment encoding. # # Barring that exception, the returned string will be labeled as encoding # Encoding.default_external, regardless of validity or byte-width. For example, # ruby will label a string containing a four-byte characters such as "\u{2070E}" # as EUC_KR even though EUC_KR is a two-byte width encoding. # # On Ruby 2.0.x and earlier, Etc will always return string values in BINARY, # ignoring encoding altogether. # # For Puppet we specifically want UTF-8 as our input from the Etc module - which # is our input for many resource instance 'is' values. The associated 'should' # value will basically always be coming from Puppet in UTF-8 - and written to # disk as UTF-8. Etc is defined for Windows but the majority calls to it return # nil and Puppet does not use it. # # That being said, we have cause to retain the original, pre-override string # values. `puppet resource user` # (Puppet::Resource::User.indirection.search('User', {})) uses self.instances to # query for user(s) and then iterates over the results of that query again to # obtain state for each user. If we've overridden the original user name and not # retained the original, we've lost the ability to query the system for it # later. Hence the Puppet::Etc::Passwd and Puppet::Etc::Group structs. # # We only use Etc for retrieving existing property values from the system. For # setting property values, providers leverage system tools (i.e., `useradd`) # # @api private module Puppet::Etc class << self # Etc::getgrent returns an Etc::Group struct object # On first call opens /etc/group and returns parse of first entry. Each subsquent call # returns new struct the next entry or nil if EOF. Call ::endgrent to close file. def getgrent override_field_values_to_utf8(::Etc.getgrent) end # closes handle to /etc/group file def endgrent ::Etc.endgrent end # effectively equivalent to IO#rewind of /etc/group def setgrent ::Etc.setgrent end # Etc::getpwent returns an Etc::Passwd struct object # On first call opens /etc/passwd and returns parse of first entry. Each subsquent call # returns new struct for the next entry or nil if EOF. Call ::endgrent to close file. def getpwent override_field_values_to_utf8(::Etc.getpwent) end # closes handle to /etc/passwd file def endpwent ::Etc.endpwent end #effectively equivalent to IO#rewind of /etc/passwd def setpwent ::Etc.setpwent end # Etc::getpwnam searches /etc/passwd file for an entry corresponding to # username. # returns an Etc::Passwd struct corresponding to the entry or raises # ArgumentError if none def getpwnam(username) override_field_values_to_utf8(::Etc.getpwnam(username)) end # Etc::getgrnam searches /etc/group file for an entry corresponding to groupname. # returns an Etc::Group struct corresponding to the entry or raises # ArgumentError if none def getgrnam(groupname) override_field_values_to_utf8(::Etc.getgrnam(groupname)) end # Etc::getgrid searches /etc/group file for an entry corresponding to id. # returns an Etc::Group struct corresponding to the entry or raises # ArgumentError if none def getgrgid(id) override_field_values_to_utf8(::Etc.getgrgid(id)) end # Etc::getpwuid searches /etc/passwd file for an entry corresponding to id. # returns an Etc::Passwd struct corresponding to the entry or raises # ArgumentError if none def getpwuid(id) override_field_values_to_utf8(::Etc.getpwuid(id)) end # Etc::group returns a Ruby iterator that executes a block for # each entry in the /etc/group file. The code-block is passed # a Group struct. See getgrent above for more details. def group # The implementation here duplicates the logic in https://github.com/ruby/etc/blob/master/ext/etc/etc.c#L523-L537 # Note that we do not call ::Etc.group directly, because we # want to use our wrappers for methods like getgrent, setgrent, # endgrent, etc. return getgrent unless block_given? setgrent begin while cur_group = getgrent yield cur_group end ensure endgrent end end private # @api private # Defines Puppet::Etc::Passwd struct class. Contains all of the original # member fields of Etc::Passwd, and additional "canonical_" versions of # these fields as well. API compatible with Etc::Passwd. Because Struct.new # defines a new Class object, we memoize to avoid superfluous extra Class # instantiations. def puppet_etc_passwd_class @password_class ||= Struct.new(*Etc::Passwd.members, *Etc::Passwd.members.map { |member| "canonical_#{member}".to_sym }) end # @api private # Defines Puppet::Etc::Group struct class. Contains all of the original # member fields of Etc::Group, and additional "canonical_" versions of these # fields as well. API compatible with Etc::Group. Because Struct.new # defines a new Class object, we memoize to avoid superfluous extra Class # instantiations. def puppet_etc_group_class @group_class ||= Struct.new(*Etc::Group.members, *Etc::Group.members.map { |member| "canonical_#{member}".to_sym }) end # Utility method for overriding the String values of a struct returned by # the Etc module to UTF-8. Structs returned by the ruby Etc module contain # members with fields of type String, Integer, or Array of Strings, so we # handle these types. Otherwise ignore fields. # # @api private # @param [Etc::Passwd or Etc::Group struct] # @return [Puppet::Etc::Passwd or Puppet::Etc::Group struct] a new struct # object with the original struct values overridden to UTF-8, if valid. For # invalid values originating in UTF-8, invalid characters are replaced with # '?'. For each member the struct also contains a corresponding # :canonical_ struct member. def override_field_values_to_utf8(struct) return nil if struct.nil? new_struct = struct.is_a?(Etc::Passwd) ? puppet_etc_passwd_class.new : puppet_etc_group_class.new struct.each_pair do |member, value| if value.is_a?(String) new_struct["canonical_#{member}".to_sym] = value.dup new_struct[member] = Puppet::Util::CharacterEncoding.scrub(Puppet::Util::CharacterEncoding.override_encoding_to_utf_8(value)) elsif value.is_a?(Array) new_struct["canonical_#{member}".to_sym] = value.inject([]) { |acc, elem| acc << elem.dup } new_struct[member] = value.inject([]) { |acc, elem| acc << Puppet::Util::CharacterEncoding.scrub(Puppet::Util::CharacterEncoding.override_encoding_to_utf_8(elem)) } else new_struct["canonical_#{member}".to_sym] = value new_struct[member] = value end end new_struct end end end puppet-5.5.10/lib/puppet/forge.rb0000644005276200011600000001711513417161721016531 0ustar jenkinsjenkinsrequire 'puppet/vendor' Puppet::Vendor.load_vendored require 'net/http' require 'tempfile' require 'uri' require 'pathname' require 'puppet/util/json' require 'semantic_puppet' class Puppet::Forge < SemanticPuppet::Dependency::Source require 'puppet/forge/cache' require 'puppet/forge/repository' require 'puppet/forge/errors' include Puppet::Forge::Errors USER_AGENT = "PMT/1.1.1 (v3; Net::HTTP)".freeze # From https://forgeapi.puppet.com/#!/release/getReleases MODULE_RELEASE_EXCLUSIONS=%w[readme changelog license uri module tags supported file_size downloads created_at updated_at deleted_at].join(',').freeze attr_reader :host, :repository def initialize(host = Puppet[:module_repository], strict_semver = true) @host = host @repository = Puppet::Forge::Repository.new(host, USER_AGENT) @strict_semver = strict_semver end # Return a list of module metadata hashes that match the search query. # This return value is used by the module_tool face install search, # and displayed to on the console. # # Example return value: # # [ # { # "author" => "puppetlabs", # "name" => "bacula", # "tag_list" => ["backup", "bacula"], # "releases" => [{"version"=>"0.0.1"}, {"version"=>"0.0.2"}], # "full_name" => "puppetlabs/bacula", # "version" => "0.0.2", # "project_url" => "https://github.com/puppetlabs/puppetlabs-bacula", # "desc" => "bacula" # } # ] # # @param term [String] search term # @return [Array] modules found # @raise [Puppet::Forge::Errors::CommunicationError] if there is a network # related error # @raise [Puppet::Forge::Errors::SSLVerifyError] if there is a problem # verifying the remote SSL certificate # @raise [Puppet::Forge::Errors::ResponseError] if the repository returns a # bad HTTP response def search(term) matches = [] uri = "/v3/modules?query=#{term}" if Puppet[:module_groups] uri += "&module_groups=#{Puppet[:module_groups].gsub('+', ' ')}" end while uri # make_http_request URI encodes parameters response = make_http_request(uri) if response.code == '200' result = Puppet::Util::Json.load(response.body) uri = decode_uri(result['pagination']['next']) matches.concat result['results'] else raise ResponseError.new(:uri => URI.parse(@host).merge(uri), :response => response) end end matches.each do |mod| mod['author'] = mod['owner']['username'] mod['tag_list'] = mod['current_release']['tags'] mod['full_name'] = "#{mod['author']}/#{mod['name']}" mod['version'] = mod['current_release']['version'] mod['project_url'] = mod['homepage_url'] mod['desc'] = mod['current_release']['metadata']['summary'] || '' end end # Fetches {ModuleRelease} entries for each release of the named module. # # @param input [String] the module name to look up # @return [Array] a list of releases for # the given name # @see SemanticPuppet::Dependency::Source#fetch def fetch(input) name = input.tr('/', '-') uri = "/v3/releases?module=#{name}&sort_by=version&exclude_fields=#{MODULE_RELEASE_EXCLUSIONS}" if Puppet[:module_groups] uri += "&module_groups=#{Puppet[:module_groups].gsub('+', ' ')}" end releases = [] while uri # make_http_request URI encodes parameters response = make_http_request(uri) if response.code == '200' response = Puppet::Util::Json.load(response.body) else raise ResponseError.new(:uri => URI.parse(@host).merge(uri), :response => response) end releases.concat(process(response['results'])) uri = decode_uri(response['pagination']['next']) end return releases end def make_http_request(*args) @repository.make_http_request(*args) end class ModuleRelease < SemanticPuppet::Dependency::ModuleRelease attr_reader :install_dir, :metadata def initialize(source, data, strict_semver = true) @data = data @metadata = meta = data['metadata'] name = meta['name'].tr('/', '-') version = SemanticPuppet::Version.parse(meta['version']) release = "#{name}@#{version}" if meta['dependencies'] dependencies = meta['dependencies'].collect do |dep| begin Puppet::ModuleTool::Metadata.new.add_dependency(dep['name'], dep['version_requirement'], dep['repository']) Puppet::ModuleTool.parse_module_dependency(release, dep, strict_semver)[0..1] rescue ArgumentError => e raise ArgumentError, _("Malformed dependency: %{name}.") % { name: dep['name'] } + ' ' + _("Exception was: %{detail}") % { detail: e } end end else dependencies = [] end super(source, name, version, Hash[dependencies]) end def install(dir) staging_dir = self.prepare module_dir = dir + name[/-(.*)/, 1] module_dir.rmtree if module_dir.exist? # Make sure unpacked module has the same ownership as the folder we are moving it into. Puppet::ModuleTool::Applications::Unpacker.harmonize_ownership(dir, staging_dir) FileUtils.mv(staging_dir, module_dir) @install_dir = dir # Return the Pathname object representing the directory where the # module release archive was unpacked the to. return module_dir ensure staging_dir.rmtree if staging_dir.exist? end def prepare return @unpacked_into if @unpacked_into Puppet.warning "#{@metadata['name']} has been deprecated by its author! View module on Puppet Forge for more info." if deprecated? download(@data['file_uri'], tmpfile) validate_checksum(tmpfile, @data['file_md5']) unpack(tmpfile, tmpdir) @unpacked_into = Pathname.new(tmpdir) end private # Obtain a suitable temporary path for unpacking tarballs # # @return [Pathname] path to temporary unpacking location def tmpdir @dir ||= Dir.mktmpdir(name, Puppet::Forge::Cache.base_path) end def tmpfile @file ||= Tempfile.new(name, Puppet::Forge::Cache.base_path).tap do |f| f.binmode end end def download(uri, destination) response = @source.make_http_request(uri, destination) destination.flush and destination.close unless response.code == '200' raise Puppet::Forge::Errors::ResponseError.new(:uri => uri, :response => response) end end def validate_checksum(file, checksum) if Digest::MD5.file(file.path).hexdigest != checksum raise RuntimeError, _("Downloaded release for %{name} did not match expected checksum") % { name: name } end end def unpack(file, destination) begin Puppet::ModuleTool::Applications::Unpacker.unpack(file.path, destination) rescue Puppet::ExecutionFailure => e raise RuntimeError, _("Could not extract contents of module archive: %{message}") % { message: e.message } end end def deprecated? @data['module'] && (@data['module']['deprecated_at'] != nil) end end private def process(list) l = list.map do |release| metadata = release['metadata'] begin ModuleRelease.new(self, release, @strict_semver) rescue ArgumentError => e Puppet.warning _("Cannot consider release %{name}-%{version}: %{error}") % { name: metadata['name'], version: metadata['version'], error: e } false end end l.select { |r| r } end def decode_uri(uri) return if uri.nil? URI.decode(uri.gsub('+', ' ')) end end puppet-5.5.10/lib/puppet/functions.rb0000644005276200011600000006756113417161721017451 0ustar jenkinsjenkins# Functions in the puppet language can be written in Ruby and distributed in # puppet modules. The function is written by creating a file in the module's # `lib/puppet/functions/` directory, where `` is # replaced with the module's name. The file should have the name of the function. # For example, to create a function named `min` in a module named `math` create # a file named `lib/puppet/functions/math/min.rb` in the module. # # A function is implemented by calling {Puppet::Functions.create_function}, and # passing it a block that defines the implementation of the function. # # Functions are namespaced inside the module that contains them. The name of # the function is prefixed with the name of the module. For example, # `math::min`. # # @example A simple function # Puppet::Functions.create_function('math::min') do # def min(a, b) # a <= b ? a : b # end # end # # Anatomy of a function # --- # # Functions are composed of four parts: the name, the implementation methods, # the signatures, and the dispatches. # # The name is the string given to the {Puppet::Functions.create_function} # method. It specifies the name to use when calling the function in the puppet # language, or from other functions. # # The implementation methods are ruby methods (there can be one or more) that # provide that actual implementation of the function's behavior. In the # simplest case the name of the function (excluding any namespace) and the name # of the method are the same. When that is done no other parts (signatures and # dispatches) need to be used. # # Signatures are a way of specifying the types of the function's parameters. # The types of any arguments will be checked against the types declared in the # signature and an error will be produced if they don't match. The types are # defined by using the same syntax for types as in the puppet language. # # Dispatches are how signatures and implementation methods are tied together. # When the function is called, puppet searches the signatures for one that # matches the supplied arguments. Each signature is part of a dispatch, which # specifies the method that should be called for that signature. When a # matching signature is found, the corresponding method is called. # # Special dispatches designed to create error messages for an argument mismatch # can be added using the keyword `argument_mismatch` instead of `dispatch`. The # method appointed by an `argument_mismatch` will be called with arguments # just like a normal `dispatch` would, but the method must produce a string. # The string is then used as the message in the `ArgumentError` that is raised # when the method returns. A block parameter can be given, but it is not # propagated in the method call. # # Documentation for the function should be placed as comments to the # implementation method(s). # # @todo Documentation for individual instances of these new functions is not # yet tied into the puppet doc system. # # @example Dispatching to different methods by type # Puppet::Functions.create_function('math::min') do # dispatch :numeric_min do # param 'Numeric', :a # param 'Numeric', :b # end # # dispatch :string_min do # param 'String', :a # param 'String', :b # end # # def numeric_min(a, b) # a <= b ? a : b # end # # def string_min(a, b) # a.downcase <= b.downcase ? a : b # end # end # # @example Using an argument mismatch handler # Puppet::Functions.create_function('math::min') do # dispatch :numeric_min do # param 'Numeric', :a # param 'Numeric', :b # end # # argument_mismatch :on_error do # param 'Any', :a # param 'Any', :b # end # # def numeric_min(a, b) # a <= b ? a : b # end # # def on_error(a, b) # 'both arguments must be of type Numeric' # end # end # # Specifying Signatures # --- # # If nothing is specified, the number of arguments given to the function must # be the same as the number of parameters, and all of the parameters are of # type 'Any'. # # The following methods can be used to define a parameter # # - _param_ - the argument must be given in the call. # - _optional_param_ - the argument may be missing in the call. May not be followed by a required parameter # - _repeated_param_ - the type specifies a repeating type that occurs 0 to "infinite" number of times. It may only appear last or just before a block parameter. # - _block_param_ - a block must be given in the call. May only appear last. # - _optional_block_param_ - a block may be given in the call. May only appear last. # # The method name _required_param_ is an alias for _param_ and _required_block_param_ is an alias for _block_param_ # # A parameter definition takes 2 arguments: # - _type_ A string that must conform to a type in the puppet language # - _name_ A symbol denoting the parameter name # # Both arguments are optional when defining a block parameter. The _type_ defaults to "Callable" # and the _name_ to :block. # # Note that the dispatch definition is used to match arguments given in a call to the function with the defined # parameters. It then dispatches the call to the implementation method simply passing the given arguments on to # that method without any further processing and it is the responsibility of that method's implementor to ensure # that it can handle those arguments. # # @example Variable number of arguments # Puppet::Functions.create_function('foo') do # dispatch :foo do # param 'Numeric', :first # repeated_param 'Numeric', :values # end # # def foo(first, *values) # # do something # end # end # # There is no requirement for direct mapping between parameter definitions and the parameters in the # receiving implementation method so the following example is also legal. Here the dispatch will ensure # that `*values` in the receiver will be an array with at least one entry of type String and that any # remaining entries are of type Numeric: # # @example Inexact mapping or parameters # Puppet::Functions.create_function('foo') do # dispatch :foo do # param 'String', :first # repeated_param 'Numeric', :values # end # # def foo(*values) # # do something # end # end # # Access to Scope # --- # In general, functions should not need access to scope; they should be # written to act on their given input only. If they absolutely must look up # variable values, they should do so via the closure scope (the scope where # they are defined) - this is done by calling `closure_scope()`. # # Calling other Functions # --- # Calling other functions by name is directly supported via # {Puppet::Pops::Functions::Function#call_function}. This allows a function to # call other functions visible from its loader. # # @api public module Puppet::Functions # @param func_name [String, Symbol] a simple or qualified function name # @param block [Proc] the block that defines the methods and dispatch of the # Function to create # @return [Class] the newly created Function class # # @api public def self.create_function(func_name, function_base = Function, &block) # Ruby < 2.1.0 does not have method on Binding, can only do eval # and it will fail unless protected with an if defined? if the local # variable does not exist in the block's binder. # begin loader = block.binding.eval('loader_injected_arg if defined?(loader_injected_arg)') create_loaded_function(func_name, loader, function_base, &block) rescue StandardError => e raise ArgumentError, _("Function Load Error for function '%{function_name}': %{message}") % {function_name: func_name, message: e.message} end end # Creates a function in, or in a local loader under the given loader. # This method should only be used when manually creating functions # for the sake of testing. Functions that are autoloaded should # always use the `create_function` method and the autoloader will supply # the correct loader. # # @param func_name [String, Symbol] a simple or qualified function name # @param loader [Puppet::Pops::Loaders::Loader] the loader loading the function # @param block [Proc] the block that defines the methods and dispatch of the # Function to create # @return [Class] the newly created Function class # # @api public def self.create_loaded_function(func_name, loader, function_base = Function, &block) if function_base.ancestors.none? { |s| s == Puppet::Pops::Functions::Function } raise ArgumentError, _("Functions must be based on Puppet::Pops::Functions::Function. Got %{function_base}") % { function_base: function_base } end func_name = func_name.to_s # Creates an anonymous class to represent the function # The idea being that it is garbage collected when there are no more # references to it. # # (Do not give the class the block here, as instance variables should be set first) the_class = Class.new(function_base) unless loader.nil? the_class.instance_variable_set(:'@loader', loader.private_loader) end # Make the anonymous class appear to have the class-name # Even if this class is not bound to such a symbol in a global ruby scope and # must be resolved via the loader. # This also overrides any attempt to define a name method in the given block # (Since it redefines it) # # TODO, enforce name in lower case (to further make it stand out since Ruby # class names are upper case) # the_class.instance_eval do @func_name = func_name def name @func_name end def loader @loader end end # The given block can now be evaluated and have access to name and loader # the_class.class_eval(&block) # Automatically create an object dispatcher based on introspection if the # loaded user code did not define any dispatchers. Fail if function name # does not match a given method name in user code. # if the_class.dispatcher.empty? simple_name = func_name.split(/::/)[-1] type, names = default_dispatcher(the_class, simple_name) last_captures_rest = (type.size_range[1] == Float::INFINITY) the_class.dispatcher.add(Puppet::Pops::Functions::Dispatch.new(type, simple_name, names, last_captures_rest)) end # The function class is returned as the result of the create function method the_class end # Creates a default dispatcher configured from a method with the same name as the function # # @api private def self.default_dispatcher(the_class, func_name) unless the_class.method_defined?(func_name) raise ArgumentError, _("Function Creation Error, cannot create a default dispatcher for function '%{func_name}', no method with this name found") % { func_name: func_name } end any_signature(*min_max_param(the_class.instance_method(func_name))) end # @api private def self.min_max_param(method) result = {:req => 0, :opt => 0, :rest => 0 } # count per parameter kind, and get array of names names = method.parameters.map { |p| result[p[0]] += 1 ; p[1].to_s } from = result[:req] to = result[:rest] > 0 ? :default : from + result[:opt] [from, to, names] end # Construct a signature consisting of Object type, with min, and max, and given names. # (there is only one type entry). # # @api private def self.any_signature(from, to, names) # Construct the type for the signature # Tuple[Object, from, to] param_types = Puppet::Pops::Types::PTupleType.new([Puppet::Pops::Types::PAnyType::DEFAULT], Puppet::Pops::Types::PIntegerType.new(from, to)) [Puppet::Pops::Types::PCallableType.new(param_types), names] end # Function # === # This class is the base class for all Puppet 4x Function API functions. A # specialized class is created for each puppet function. # # @api public class Function < Puppet::Pops::Functions::Function # @api private def self.builder DispatcherBuilder.new(dispatcher, Puppet::Pops::Types::PCallableType::DEFAULT, loader) end # Dispatch any calls that match the signature to the provided method name. # # @param meth_name [Symbol] The name of the implementation method to call # when the signature defined in the block matches the arguments to a call # to the function. # @return [Void] # # @api public def self.dispatch(meth_name, &block) builder().instance_eval do dispatch(meth_name, false, &block) end end # Like `dispatch` but used for a specific type of argument mismatch. Will not be include in the list of valid # parameter overloads for the function. # # @param meth_name [Symbol] The name of the implementation method to call # when the signature defined in the block matches the arguments to a call # to the function. # @return [Void] # # @api public def self.argument_mismatch(meth_name, &block) builder().instance_eval do dispatch(meth_name, true, &block) end end # Allows types local to the function to be defined to ease the use of complex types # in a 4.x function. Within the given block, calls to `type` can be made with a string # 'AliasType = ExistingType` can be made to define aliases. The defined aliases are # available for further aliases, and in all dispatchers. # # @since 4.5.0 # @api public # def self.local_types(&block) if loader.nil? raise ArgumentError, _("No loader present. Call create_loaded_function(:myname, loader,...), instead of 'create_function' if running tests") end aliases = LocalTypeAliasesBuilder.new(loader, name) aliases.instance_eval(&block) # Add the loaded types to the builder aliases.local_types.each do |type_alias_expr| # Bind the type alias to the local_loader using the alias t = Puppet::Pops::Loader::TypeDefinitionInstantiator.create_from_model(type_alias_expr, aliases.loader) # Also define a method for convenient access to the defined type alias. # Since initial capital letter in Ruby means a Constant these names use a prefix of # `type`. As an example, the type 'MyType' is accessed by calling `type_MyType`. define_method("type_#{t.name}") { t } end # Store the loader in the class @loader = aliases.loader end # Creates a new function instance in the given closure scope (visibility to variables), and a loader # (visibility to other definitions). The created function will either use the `given_loader` or # (if it has local type aliases) a loader that was constructed from the loader used when loading # the function's class. # # TODO: It would be of value to get rid of the second parameter here, but that would break API. # def self.new(closure_scope, given_loader) super(closure_scope, @loader || given_loader) end end # Base class for all functions implemented in the puppet language class PuppetFunction < Function def self.init_dispatch(a_closure) # A closure is compatible with a dispatcher - they are both callable signatures dispatcher.add(a_closure) end end # Public api methods of the DispatcherBuilder are available within dispatch() # blocks declared in a Puppet::Function.create_function() call. # # @api public class DispatcherBuilder attr_reader :loader # @api private def initialize(dispatcher, all_callables, loader) @all_callables = all_callables @dispatcher = dispatcher @loader = loader end # Defines a required positional parameter with _type_ and _name_. # # @param type [String] The type specification for the parameter. # @param name [Symbol] The name of the parameter. This is primarily used # for error message output and does not have to match an implementation # method parameter. # @return [Void] # # @api public def param(type, name) internal_param(type, name) raise ArgumentError, _('A required parameter cannot be added after an optional parameter') if @min != @max @min += 1 @max += 1 end alias required_param param # Defines an optional positional parameter with _type_ and _name_. # May not be followed by a required parameter. # # @param type [String] The type specification for the parameter. # @param name [Symbol] The name of the parameter. This is primarily used # for error message output and does not have to match an implementation # method parameter. # @return [Void] # # @api public def optional_param(type, name) internal_param(type, name) @max += 1 end # Defines a repeated positional parameter with _type_ and _name_ that may occur 0 to "infinite" number of times. # It may only appear last or just before a block parameter. # # @param type [String] The type specification for the parameter. # @param name [Symbol] The name of the parameter. This is primarily used # for error message output and does not have to match an implementation # method parameter. # @return [Void] # # @api public def repeated_param(type, name) internal_param(type, name, true) @max = :default end alias optional_repeated_param repeated_param # Defines a repeated positional parameter with _type_ and _name_ that may occur 1 to "infinite" number of times. # It may only appear last or just before a block parameter. # # @param type [String] The type specification for the parameter. # @param name [Symbol] The name of the parameter. This is primarily used # for error message output and does not have to match an implementation # method parameter. # @return [Void] # # @api public def required_repeated_param(type, name) internal_param(type, name, true) raise ArgumentError, _('A required repeated parameter cannot be added after an optional parameter') if @min != @max @min += 1 @max = :default end # Defines one required block parameter that may appear last. If type and name is missing the # default type is "Callable", and the name is "block". If only one # parameter is given, then that is the name and the type is "Callable". # # @api public def block_param(*type_and_name) case type_and_name.size when 0 type = @all_callables name = :block when 1 type = @all_callables name = type_and_name[0] when 2 type, name = type_and_name type = Puppet::Pops::Types::TypeParser.singleton.parse(type, loader) unless type.is_a?(Puppet::Pops::Types::PAnyType) else raise ArgumentError, _("block_param accepts max 2 arguments (type, name), got %{size}.") % { size: type_and_name.size } end unless Puppet::Pops::Types::TypeCalculator.is_kind_of_callable?(type, false) raise ArgumentError, _("Expected PCallableType or PVariantType thereof, got %{type_class}") % { type_class: type.class } end unless name.is_a?(Symbol) raise ArgumentError, _("Expected block_param name to be a Symbol, got %{name_class}") % { name_class: name.class } end if @block_type.nil? @block_type = type @block_name = name else raise ArgumentError, _('Attempt to redefine block') end end alias required_block_param block_param # Defines one optional block parameter that may appear last. If type or name is missing the # defaults are "any callable", and the name is "block". The implementor of the dispatch target # must use block = nil when it is optional (or an error is raised when the call is made). # # @api public def optional_block_param(*type_and_name) # same as required, only wrap the result in an optional type required_block_param(*type_and_name) @block_type = Puppet::Pops::Types::TypeFactory.optional(@block_type) end # Defines the return type. Defaults to 'Any' # @param [String] type a reference to a Puppet Data Type # # @api public def return_type(type) unless type.is_a?(String) || type.is_a?(Puppet::Pops::Types::PAnyType) raise ArgumentError, _("Argument to 'return_type' must be a String reference to a Puppet Data Type. Got %{type_class}") % { type_class: type.class } end @return_type = type end private # @api private def internal_param(type, name, repeat = false) raise ArgumentError, _('Parameters cannot be added after a block parameter') unless @block_type.nil? raise ArgumentError, _('Parameters cannot be added after a repeated parameter') if @max == :default if name.is_a?(String) raise ArgumentError, _("Parameter name argument must be a Symbol. Got %{name_class}") % { name_class: name.class } end if type.is_a?(String) || type.is_a?(Puppet::Pops::Types::PAnyType) @types << type @names << name # mark what should be picked for this position when dispatching if repeat @weaving << -@names.size() else @weaving << @names.size()-1 end else raise ArgumentError, _("Parameter 'type' must be a String reference to a Puppet Data Type. Got %{type_class}") % { type_class: type.class } end end # @api private def dispatch(meth_name, argument_mismatch_handler, &block) # an array of either an index into names/types, or an array with # injection information [type, name, injection_name] used when the call # is being made to weave injections into the given arguments. # @types = [] @names = [] @weaving = [] @injections = [] @min = 0 @max = 0 @block_type = nil @block_name = nil @return_type = nil @argument_mismatch_hander = argument_mismatch_handler self.instance_eval(&block) callable_t = create_callable(@types, @block_type, @return_type, @min, @max) @dispatcher.add(Puppet::Pops::Functions::Dispatch.new(callable_t, meth_name, @names, @max == :default, @block_name, @injections, @weaving, @argument_mismatch_hander)) end # Handles creation of a callable type from strings specifications of puppet # types and allows the min/max occurs of the given types to be given as one # or two integer values at the end. The given block_type should be # Optional[Callable], Callable, or nil. # # @api private def create_callable(types, block_type, return_type, from, to) mapped_types = types.map do |t| t.is_a?(Puppet::Pops::Types::PAnyType) ? t : internal_type_parse(t, loader) end param_types = Puppet::Pops::Types::PTupleType.new(mapped_types, from > 0 && from == to ? nil : Puppet::Pops::Types::PIntegerType.new(from, to)) return_type = internal_type_parse(return_type, loader) unless return_type.nil? || return_type.is_a?(Puppet::Pops::Types::PAnyType) Puppet::Pops::Types::PCallableType.new(param_types, block_type, return_type) end def internal_type_parse(type_string, loader) begin Puppet::Pops::Types::TypeParser.singleton.parse(type_string, loader) rescue StandardError => e raise ArgumentError, _("Parsing of type string '\"%{type_string}\"' failed with message: <%{message}>.\n") % { type_string: type_string, message: e.message } end end private :internal_type_parse end # The LocalTypeAliasBuilder is used by the 'local_types' method to collect the individual # type aliases given by the function's author. # class LocalTypeAliasesBuilder attr_reader :local_types, :parser, :loader def initialize(loader, name) @loader = Puppet::Pops::Loader::PredefinedLoader.new(loader, :"local_function_#{name}") @local_types = [] # get the shared parser used by puppet's compiler @parser = Puppet::Pops::Parser::EvaluatingParser.singleton() end # Defines a local type alias, the given string should be a Puppet Language type alias expression # in string form without the leading 'type' keyword. # Calls to local_type must be made before the first parameter definition or an error will # be raised. # # @param assignment_string [String] a string on the form 'AliasType = ExistingType' # @api public # def type(assignment_string) # Get location to use in case of error - this produces ruby filename and where call to 'type' occurred # but strips off the rest of the internal "where" as it is not meaningful to user. # rb_location = caller[0] begin result = parser.parse_string("type #{assignment_string}", nil) rescue StandardError => e rb_location = rb_location.gsub(/:in.*$/, '') # Create a meaningful location for parse errors - show both what went wrong with the parsing # and in which ruby file it was found. raise ArgumentError, _("Parsing of 'type \"%{assignment_string}\"' failed with message: <%{message}>.\n" + "Called from <%{ruby_file_location}>") % { assignment_string: assignment_string, message: e.message, ruby_file_location: rb_location } end unless result.body.kind_of?(Puppet::Pops::Model::TypeAlias) rb_location = rb_location.gsub(/:in.*$/, '') raise ArgumentError, _("Expected a type alias assignment on the form 'AliasType = T', got '%{assignment_string}'.\n"+ "Called from <%{ruby_file_location}>") % { assignment_string: assignment_string, ruby_file_location: rb_location } end @local_types << result.body end end # @note WARNING: This style of creating functions is not public. It is a system # under development that will be used for creating "system" functions. # # This is a private, internal, system for creating functions. It supports # everything that the public function definition system supports as well as a # few extra features such as injection of well known parameters. # # @api private class InternalFunction < Function # @api private def self.builder InternalDispatchBuilder.new(dispatcher, Puppet::Pops::Types::PCallableType::DEFAULT, loader) end # Allows the implementation of a function to call other functions by name and pass the caller # scope. The callable functions are those visible to the same loader that loaded this function # (the calling function). # # @param scope [Puppet::Parser::Scope] The caller scope # @param function_name [String] The name of the function # @param *args [Object] splat of arguments # @return [Object] The result returned by the called function # # @api public def call_function_with_scope(scope, function_name, *args, &block) internal_call_function(scope, function_name, args, &block) end end # Injection and Weaving of parameters # --- # It is possible to inject and weave a set of well known parameters into a call. # These extra parameters are not part of the parameters passed from the Puppet # logic, and they can not be overridden by parameters given as arguments in the # call. They are invisible to the Puppet Language. # # @example using injected parameters # Puppet::Functions.create_function('test') do # dispatch :test do # param 'Scalar', 'a' # param 'Scalar', 'b' # scope_param # end # def test(a, b, scope) # a > b ? scope['a'] : scope['b'] # end # end # # The function in the example above is called like this: # # test(10, 20) # # @api private class InternalDispatchBuilder < DispatcherBuilder # Inject parameter for `Puppet::Parser::Scope` def scope_param inject(:scope) end # Inject parameter for `Puppet::Pal::ScriptCompiler` def script_compiler_param inject(:pal_script_compiler) end private def inject(injection_name) @injections << injection_name # mark what should be picked for this position when dispatching @weaving << [@injections.size()-1] end end end puppet-5.5.10/lib/puppet/graph.rb0000644005276200011600000000053713417161721016530 0ustar jenkinsjenkinsmodule Puppet::Graph require 'puppet/graph/prioritizer' require 'puppet/graph/sequential_prioritizer' require 'puppet/graph/title_hash_prioritizer' require 'puppet/graph/random_prioritizer' require 'puppet/graph/simple_graph' require 'puppet/graph/rb_tree_map' require 'puppet/graph/key' require 'puppet/graph/relationship_graph' end puppet-5.5.10/lib/puppet/interface.rb0000644005276200011600000001614613417161721017372 0ustar jenkinsjenkinsrequire 'puppet' require 'puppet/util/autoload' require 'prettyprint' # @api public class Puppet::Interface require 'puppet/interface/documentation' require 'puppet/interface/face_collection' require 'puppet/interface/action' require 'puppet/interface/action_builder' require 'puppet/interface/action_manager' require 'puppet/interface/option' require 'puppet/interface/option_builder' require 'puppet/interface/option_manager' include FullDocs include Puppet::Interface::ActionManager extend Puppet::Interface::ActionManager include Puppet::Interface::OptionManager extend Puppet::Interface::OptionManager include Puppet::Util class << self # This is just so we can search for actions. We only use its # list of directories to search. # Lists all loaded faces # @return [Array] The names of the loaded faces def faces Puppet::Interface::FaceCollection.faces end # Register a face # @param instance [Puppet::Interface] The face # @return [void] # @api private def register(instance) Puppet::Interface::FaceCollection.register(instance) end # Defines a new Face. # @todo Talk about using Faces DSL inside the block # # @param name [Symbol] the name of the face # @param version [String] the version of the face (this should # conform to {http://semver.org/ Semantic Versioning}) # @overload define(name, version, {|| ... }) # @return [Puppet::Interface] The created face # @api public # @dsl Faces def define(name, version, &block) face = Puppet::Interface::FaceCollection[name, version] if face.nil? then face = self.new(name, version) Puppet::Interface::FaceCollection.register(face) # REVISIT: Shouldn't this be delayed until *after* we evaluate the # current block, not done before? --daniel 2011-04-07 face.load_actions end face.instance_eval(&block) if block_given? return face end # Retrieves a face by name and version. Use `:current` for the # version to get the most recent available version. # # @param name [Symbol] the name of the face # @param version [String, :current] the version of the face # # @return [Puppet::Interface] the face # # @api public def face?(name, version) Puppet::Interface::FaceCollection[name, version] end # Retrieves a face by name and version # # @param name [Symbol] the name of the face # @param version [String] the version of the face # # @return [Puppet::Interface] the face # # @api public def [](name, version) unless face = Puppet::Interface::FaceCollection[name, version] # REVISIT (#18042) no sense in rechecking if version == :current -- josh if Puppet::Interface::FaceCollection[name, :current] raise Puppet::Error, "Could not find version #{version} of #{name}" else raise Puppet::Error, "Could not find Puppet Face #{name.to_s}" end end face end # Retrieves an action for a face # @param name [Symbol] The face # @param action [Symbol] The action name # @param version [String, :current] The version of the face # @return [Puppet::Interface::Action] The action def find_action(name, action, version = :current) Puppet::Interface::FaceCollection.get_action_for_face(name, action, version) end end ######################################################################## # Documentation. We currently have to rewrite both getters because we share # the same instance between build-time and the runtime instance. When that # splits out this should merge into a module that both the action and face # include. --daniel 2011-04-17 # Returns the synopsis for the face. This shows basic usage and global # options. # @return [String] usage synopsis # @api private def synopsis build_synopsis self.name, '' end ######################################################################## # The name of the face # @return [Symbol] # @api private attr_reader :name # The version of the face # @return [SemanticPuppet::Version] attr_reader :version # The autoloader instance for the face # @return [Puppet::Util::Autoload] # @api private attr_reader :loader private :loader # @api private def initialize(name, version, &block) unless SemanticPuppet::Version.valid?(version) raise ArgumentError, _("Cannot create face %{name} with invalid version number '%{version}'!") % { name: name.inspect, version: version } end @name = Puppet::Interface::FaceCollection.underscorize(name) @version = SemanticPuppet::Version.parse(version) # The few bits of documentation we actually demand. The default license # is a favour to our end users; if you happen to get that in a core face # report it as a bug, please. --daniel 2011-04-26 @authors = [] @license = 'All Rights Reserved' @loader = Puppet::Util::Autoload.new(@name, "puppet/face/#{@name}") instance_eval(&block) if block_given? end # Loads all actions defined in other files. # # @return [void] # @api private def load_actions loader.loadall end # Returns a string representation with the face's name and version # @return [String] def to_s "Puppet::Face[#{name.inspect}, #{version.inspect}]" end alias_method :inspect, :to_s # @return [void] def deprecate @deprecated = true end # @return [Boolean] def deprecated? @deprecated end ######################################################################## # Action decoration, whee! You are not expected to care about this code, # which exists to support face building and construction. I marked these # private because the implementation is crude and ugly, and I don't yet know # enough to work out how to make it clean. # # Once we have established that these methods will likely change radically, # to be unrecognizable in the final outcome. At which point we will throw # all this away, replace it with something nice, and work out if we should # be making this visible to the outside world... --daniel 2011-04-14 private # @return [void] # @api private def __invoke_decorations(type, action, passed_args = [], passed_options = {}) [:before, :after].member?(type) or fail "unknown decoration type #{type}" # Collect the decoration methods matching our pass. methods = action.options.select do |name| passed_options.has_key? name end.map do |name| action.get_option(name).__decoration_name(type) end methods.reverse! if type == :after # Exceptions here should propagate up; this implements a hook we can use # reasonably for option validation. methods.each do |hook| respond_to? hook and self.__send__(hook, action, passed_args, passed_options) end end # @return [void] # @api private def __add_method(name, proc) meta_def(name, &proc) method(name).unbind end # @return [void] # @api private def self.__add_method(name, proc) define_method(name, proc) instance_method(name) end end puppet-5.5.10/lib/puppet/loaders.rb0000644005276200011600000000207613417161721017060 0ustar jenkinsjenkinsmodule Puppet module Pops require 'puppet/pops/loaders' module Loader require 'puppet/pops/loader/typed_name' require 'puppet/pops/loader/loader' require 'puppet/pops/loader/base_loader' require 'puppet/pops/loader/gem_support' require 'puppet/pops/loader/module_loaders' require 'puppet/pops/loader/dependency_loader' require 'puppet/pops/loader/null_loader' require 'puppet/pops/loader/static_loader' require 'puppet/pops/loader/runtime3_type_loader' require 'puppet/pops/loader/ruby_function_instantiator' require 'puppet/pops/loader/ruby_data_type_instantiator' require 'puppet/pops/loader/puppet_function_instantiator' require 'puppet/pops/loader/type_definition_instantiator' require 'puppet/pops/loader/puppet_resource_type_impl_instantiator' require 'puppet/pops/loader/loader_paths' require 'puppet/pops/loader/simple_environment_loader' require 'puppet/pops/loader/predefined_loader' require 'puppet/pops/loader/puppet_plan_instantiator' end end end puppet-5.5.10/lib/puppet/module.rb0000644005276200011600000003271213417161721016714 0ustar jenkinsjenkinsrequire 'puppet/util/logging' require 'puppet/module/task' require 'puppet/util/json' require 'semantic_puppet/gem_version' # Support for modules class Puppet::Module class Error < Puppet::Error; end class MissingModule < Error; end class IncompatibleModule < Error; end class UnsupportedPlatform < Error; end class IncompatiblePlatform < Error; end class MissingMetadata < Error; end class FaultyMetadata < Error; end class InvalidName < Error; end class InvalidFilePattern < Error; end include Puppet::Util::Logging FILETYPES = { "manifests" => "manifests", "files" => "files", "templates" => "templates", "plugins" => "lib", "pluginfacts" => "facts.d", "locales" => "locales", } # Find and return the +module+ that +path+ belongs to. If +path+ is # absolute, or if there is no module whose name is the first component # of +path+, return +nil+ def self.find(modname, environment = nil) return nil unless modname # Unless a specific environment is given, use the current environment env = environment ? Puppet.lookup(:environments).get!(environment) : Puppet.lookup(:current_environment) env.module(modname) end def self.is_module_directory?(name, path) # it must be a directory fullpath = File.join(path, name) return false unless Puppet::FileSystem.directory?(fullpath) return is_module_directory_name?(name) end def self.is_module_directory_name?(name) # it must match an installed module name according to forge validator return true if name =~ /^[a-z][a-z0-9_]*$/ return false end def self.is_module_namespaced_name?(name) # it must match the full module name according to forge validator return true if name =~ /^[a-zA-Z0-9]+[-][a-z][a-z0-9_]*$/ return false end # @api private def self.parse_range(range, strict) @parse_range_method ||= SemanticPuppet::VersionRange.method(:parse) if @parse_range_method.arity == 1 @semver_gem_version ||= SemanticPuppet::Version.parse(SemanticPuppet::VERSION) # Give user a heads-up if the desired strict setting cannot be honored if strict if @semver_gem_version.major < 1 Puppet.warn_once('strict_version_ranges', 'version_range_cannot_be_strict', _('VersionRanges will never be strict when using non-vendored SemanticPuppet gem, version %{version}') % { version: @semver_gem_version}, :default, :default, :notice) end else if @semver_gem_version.major >= 1 Puppet.warn_once('strict_version_ranges', 'version_range_always_strict', _('VersionRanges will always be strict when using non-vendored SemanticPuppet gem, version %{version}') % { version: @semver_gem_version}, :default, :default, :notice) end end @parse_range_method.call(range) else @parse_range_method.call(range, strict) end end attr_reader :name, :environment, :path, :metadata, :tasks attr_writer :environment attr_accessor :dependencies, :forge_name attr_accessor :source, :author, :version, :license, :summary, :description, :project_page def initialize(name, path, environment, strict_semver = true) @name = name @path = path @strict_semver = strict_semver @environment = environment assert_validity load_metadata @absolute_path_to_manifests = Puppet::FileSystem::PathPattern.absolute(manifests) end # @deprecated The puppetversion module metadata field is no longer used. def puppetversion nil end # @deprecated The puppetversion module metadata field is no longer used. def puppetversion=(something) end # @deprecated The puppetversion module metadata field is no longer used. def validate_puppet_version return end def has_metadata? begin load_metadata @metadata.is_a?(Hash) && !@metadata.empty? rescue Puppet::Module::MissingMetadata false end end FILETYPES.each do |type, location| # A boolean method to let external callers determine if # we have files of a given type. define_method(type + '?') do type_subpath = subpath(location) unless Puppet::FileSystem.exist?(type_subpath) Puppet.debug("No #{type} found in subpath '#{type_subpath}' " + "(file / directory does not exist)") return false end return true end # A method for returning a given file of a given type. # e.g., file = mod.manifest("my/manifest.pp") # # If the file name is nil, then the base directory for the # file type is passed; this is used for fileserving. define_method(type.sub(/s$/, '')) do |file| # If 'file' is nil then they're asking for the base path. # This is used for things like fileserving. if file full_path = File.join(subpath(location), file) else full_path = subpath(location) end return nil unless Puppet::FileSystem.exist?(full_path) return full_path end # Return the base directory for the given type define_method(type) do subpath(location) end end def tasks_directory subpath("tasks") end def tasks return @tasks if instance_variable_defined?(:@tasks) if Puppet::FileSystem.exist?(tasks_directory) @tasks = Puppet::Module::Task.tasks_in_module(self) else @tasks = [] end end # This is a re-implementation of the Filetypes singular type method (e.g. # `manifest('my/manifest.pp')`. We don't implement the full filetype "API" for # tasks since tasks don't map 1:1 onto files. def task_file(name) # If 'file' is nil then they're asking for the base path. # This is used for things like fileserving. if name full_path = File.join(tasks_directory, name) else full_path = tasks_directory end if Puppet::FileSystem.exist?(full_path) return full_path else return nil end end def license_file return @license_file if defined?(@license_file) return @license_file = nil unless path @license_file = File.join(path, "License") end def read_metadata md_file = metadata_file md_file.nil? ? {} : Puppet::Util::Json.load(File.read(md_file, :encoding => 'utf-8')) rescue Errno::ENOENT {} rescue Puppet::Util::Json::ParseError => e #TRANSLATORS 'metadata.json' is a specific file name and should not be translated. msg = _("%{name} has an invalid and unparsable metadata.json file. The parse error: %{error}") % { name: name, error: e.message } case Puppet[:strict] when :off Puppet.debug(msg) when :warning Puppet.warning(msg) when :error raise FaultyMetadata, msg end {} end def load_metadata return if instance_variable_defined?(:@metadata) @metadata = data = read_metadata return if data.empty? @forge_name = data['name'].gsub('-', '/') if data['name'] [:source, :author, :version, :license, :dependencies].each do |attr| value = data[attr.to_s] raise MissingMetadata, "No #{attr} module metadata provided for #{self.name}" if value.nil? if attr == :dependencies unless value.is_a?(Array) raise MissingMetadata, "The value for the key dependencies in the file metadata.json of the module #{self.name} must be an array, not: '#{value}'" end value.each do |dep| name = dep['name'] dep['name'] = name.tr('-', '/') unless name.nil? dep['version_requirement'] ||= '>= 0.0.0' end end send(attr.to_s + "=", value) end end # Return the list of manifests matching the given glob pattern, # defaulting to 'init.pp' for empty modules. def match_manifests(rest) if rest wanted_manifests = wanted_manifests_from(rest) searched_manifests = wanted_manifests.glob.reject { |f| FileTest.directory?(f) } else searched_manifests = [] end # (#4220) Always ensure init.pp in case class is defined there. init_manifest = manifest("init.pp") if !init_manifest.nil? && !searched_manifests.include?(init_manifest) searched_manifests.unshift(init_manifest) end searched_manifests end def all_manifests return [] unless Puppet::FileSystem.exist?(manifests) Dir.glob(File.join(manifests, '**', '*.pp')) end def metadata_file return @metadata_file if defined?(@metadata_file) return @metadata_file = nil unless path @metadata_file = File.join(path, "metadata.json") end def hiera_conf_file unless defined?(@hiera_conf_file) @hiera_conf_file = path.nil? ? nil : File.join(path, Puppet::Pops::Lookup::HieraConfig::CONFIG_FILE_NAME) end @hiera_conf_file end def has_hiera_conf? hiera_conf_file.nil? ? false : Puppet::FileSystem.exist?(hiera_conf_file) end def modulepath File.dirname(path) if path end # Find all plugin directories. This is used by the Plugins fileserving mount. def plugin_directory subpath("lib") end def plugin_fact_directory subpath("facts.d") end #@return [String] def locale_directory subpath("locales") end # Returns true if the module has translation files for the # given locale. # @param [String] locale the two-letter language code to check # for translations # @return true if the module has a directory for the locale, false # false otherwise def has_translations?(locale) return Puppet::FileSystem.exist?(File.join(locale_directory, locale)) end def has_external_facts? File.directory?(plugin_fact_directory) end def supports(name, version = nil) @supports ||= [] @supports << [name, version] end def to_s result = "Module #{name}" result += "(#{path})" if path result end def dependencies_as_modules dependent_modules = [] dependencies and dependencies.each do |dep| _, dep_name = dep["name"].split('/') found_module = environment.module(dep_name) dependent_modules << found_module if found_module end dependent_modules end def required_by environment.module_requirements[self.forge_name] || {} end # Identify and mark unmet dependencies. A dependency will be marked unmet # for the following reasons: # # * not installed and is thus considered missing # * installed and does not meet the version requirements for this module # * installed and doesn't use semantic versioning # # Returns a list of hashes representing the details of an unmet dependency. # # Example: # # [ # { # :reason => :missing, # :name => 'puppetlabs-mysql', # :version_constraint => 'v0.0.1', # :mod_details => { # :installed_version => '0.0.1' # } # :parent => { # :name => 'puppetlabs-bacula', # :version => 'v1.0.0' # } # } # ] # def unmet_dependencies unmet_dependencies = [] return unmet_dependencies unless dependencies dependencies.each do |dependency| name = dependency['name'] version_string = dependency['version_requirement'] || '>= 0.0.0' dep_mod = begin environment.module_by_forge_name(name) rescue nil end error_details = { :name => name, :version_constraint => version_string.gsub(/^(?=\d)/, "v"), :parent => { :name => self.forge_name, :version => self.version.gsub(/^(?=\d)/, "v") }, :mod_details => { :installed_version => dep_mod.nil? ? nil : dep_mod.version } } unless dep_mod error_details[:reason] = :missing unmet_dependencies << error_details next end if version_string begin required_version_semver_range = self.class.parse_range(version_string, @strict_semver) actual_version_semver = SemanticPuppet::Version.parse(dep_mod.version) rescue ArgumentError error_details[:reason] = :non_semantic_version unmet_dependencies << error_details next end unless required_version_semver_range.include? actual_version_semver error_details[:reason] = :version_mismatch unmet_dependencies << error_details next end end end unmet_dependencies end def ==(other) self.name == other.name && self.version == other.version && self.path == other.path && self.environment == other.environment end def strict_semver? @strict_semver end private def wanted_manifests_from(pattern) begin extended = File.extname(pattern).empty? ? "#{pattern}.pp" : pattern relative_pattern = Puppet::FileSystem::PathPattern.relative(extended) rescue Puppet::FileSystem::PathPattern::InvalidPattern => error raise Puppet::Module::InvalidFilePattern.new( "The pattern \"#{pattern}\" to find manifests in the module \"#{name}\" " + "is invalid and potentially unsafe.", error) end relative_pattern.prefix_with(@absolute_path_to_manifests) end def subpath(type) File.join(path, type) end def assert_validity if !Puppet::Module.is_module_directory_name?(@name) && !Puppet::Module.is_module_namespaced_name?(@name) raise InvalidName, _(<<-ERROR_STRING).chomp % { name: @name } Invalid module name '%{name}'; module names must match either: An installed module name (ex. modulename) matching the expression /^[a-z][a-z0-9_]*$/ -or- A namespaced module name (ex. author-modulename) matching the expression /^[a-zA-Z0-9]+[-][a-z][a-z0-9_]*$/ ERROR_STRING end end end puppet-5.5.10/lib/puppet/module_tool.rb0000644005276200011600000001624713417161721017756 0ustar jenkinsjenkins# encoding: UTF-8 # Load standard libraries require 'pathname' require 'fileutils' require 'puppet/util/colors' module Puppet module ModuleTool require 'puppet/module_tool/tar' extend Puppet::Util::Colors # Directory and names that should not be checksummed. ARTIFACTS = ['pkg', /^\./, /^~/, /^#/, 'coverage', 'checksums.json', 'REVISION'] FULL_MODULE_NAME_PATTERN = /\A([^-\/|.]+)[-|\/](.+)\z/ REPOSITORY_URL = Puppet.settings[:module_repository] # Is this a directory that shouldn't be checksummed? # # TODO: Should this be part of Checksums? # TODO: Rename this method to reflect its purpose? # TODO: Shouldn't this be used when building packages too? def self.artifact?(path) case File.basename(path) when *ARTIFACTS true else false end end # Return the +username+ and +modname+ for a given +full_module_name+, or raise an # ArgumentError if the argument isn't parseable. def self.username_and_modname_from(full_module_name) if matcher = full_module_name.match(FULL_MODULE_NAME_PATTERN) return matcher.captures else raise ArgumentError, _("Not a valid full name: %{full_module_name}") % { full_module_name: full_module_name } end end # Find the module root when given a path by checking each directory up from # its current location until it finds one that satisfies is_module_root? # # @param path [Pathname, String] path to start from # @return [Pathname, nil] the root path of the module directory or nil if # we cannot find one def self.find_module_root(path) path = Pathname.new(path) if path.class == String path.expand_path.ascend do |p| return p if is_module_root?(p) end nil end # Analyse path to see if it is a module root directory by detecting a # file named 'metadata.json' # # @param path [Pathname, String] path to analyse # @return [Boolean] true if the path is a module root, false otherwise def self.is_module_root?(path) path = Pathname.new(path) if path.class == String FileTest.file?(path + 'metadata.json') end # Builds a formatted tree from a list of node hashes containing +:text+ # and +:dependencies+ keys. def self.format_tree(nodes, level = 0) str = '' nodes.each_with_index do |node, i| last_node = nodes.length - 1 == i deps = node[:dependencies] || [] str << (indent = " " * level) str << (last_node ? "└" : "├") str << "─" str << (deps.empty? ? "─" : "┬") str << " #{node[:text]}\n" branch = format_tree(deps, level + 1) branch.gsub!(/^#{indent} /, indent + '│') unless last_node str << branch end return str end def self.build_tree(mods, dir) mods.each do |mod| version_string = mod[:version].to_s.sub(/^(?!v)/, 'v') if mod[:action] == :upgrade previous_version = mod[:previous_version].to_s.sub(/^(?!v)/, 'v') version_string = "#{previous_version} -> #{version_string}" end mod[:text] = "#{mod[:name]} (#{colorize(:cyan, version_string)})" mod[:text] += " [#{mod[:path]}]" unless mod[:path].to_s == dir.to_s deps = (mod[:dependencies] || []) deps.sort! { |a, b| a[:name] <=> b[:name] } build_tree(deps, dir) end end # @param options [Hash] This hash will contain any # command-line arguments that are not Settings, as those will have already # been extracted by the underlying application code. # # @note Unfortunately the whole point of this method is the side effect of # modifying the options parameter. This same hash is referenced both # when_invoked and when_rendering. For this reason, we are not returning # a duplicate. # @todo Validate the above note... # # An :environment_instance and a :target_dir are added/updated in the # options parameter. # # @api private def self.set_option_defaults(options) current_environment = environment_from_options(options) modulepath = [options[:target_dir]] + current_environment.full_modulepath face_environment = current_environment.override_with(:modulepath => modulepath.compact) options[:environment_instance] = face_environment # Note: environment will have expanded the path options[:target_dir] = face_environment.full_modulepath.first # Default false to retain backward compatibility with SemanticPuppet 0.1.4 options[:strict_semver] = false unless options.include?(:strict_semver) end # Given a hash of options, we should discover or create a # {Puppet::Node::Environment} instance that reflects the provided options. # # Generally speaking, the `:modulepath` parameter should supersede all # others, the `:environment` parameter should follow after that, and we # should default to Puppet's current environment. # # @param options [{Symbol => Object}] the options to derive environment from # @return [Puppet::Node::Environment] the environment described by the options def self.environment_from_options(options) if options[:modulepath] path = options[:modulepath].split(File::PATH_SEPARATOR) Puppet::Node::Environment.create(:anonymous, path, '') elsif options[:environment].is_a?(Puppet::Node::Environment) options[:environment] elsif options[:environment] # This use of looking up an environment is correct since it honours # a request to get a particular environment via environment name. Puppet.lookup(:environments).get!(options[:environment]) else Puppet.lookup(:current_environment) end end # Handles parsing of module dependency expressions into proper # {SemanticPuppet::VersionRange}s, including reasonable error handling. # # @param where [String] a description of the thing we're parsing the # dependency expression for # @param dep [Hash] the dependency description to parse # @param strict_semver [Boolean] set to `false` to relax range semantics to include pre-releases # @return [Array(String, SemanticPuppet::VersionRange, String)] a tuple of the # dependent module's name, the version range dependency, and the # unparsed range expression. def self.parse_module_dependency(where, dep, strict_semver = true) dep_name = dep['name'].tr('/', '-') range = dep['version_requirement'] || '>= 0.0.0' begin parsed_range = Module.parse_range(range, strict_semver) rescue ArgumentError => e Puppet.debug "Error in #{where} parsing dependency #{dep_name} (#{e.message}); using empty range." parsed_range = SemanticPuppet::VersionRange::EMPTY_RANGE end [ dep_name, parsed_range, range ] end end end # Load remaining libraries require 'puppet/module_tool/errors' require 'puppet/module_tool/applications' require 'puppet/module_tool/checksums' require 'puppet/module_tool/contents_description' require 'puppet/module_tool/dependency' require 'puppet/module_tool/metadata' require 'puppet/forge/cache' require 'puppet/forge' puppet-5.5.10/lib/puppet/node.rb0000644005276200011600000002244013417161721016351 0ustar jenkinsjenkinsrequire 'puppet/indirector' # A class for managing nodes, including their facts and environment. class Puppet::Node require 'puppet/node/facts' require 'puppet/node/environment' # Set up indirection, so that nodes can be looked for in # the node sources. extend Puppet::Indirector # Asymmetric serialization/deserialization required in this class via to/from datahash include Puppet::Util::PsychSupport # Use the node source as the indirection terminus. indirects :node, :terminus_setting => :node_terminus, :doc => "Where to find node information. A node is composed of its name, its facts, and its environment." attr_accessor :name, :classes, :source, :ipaddress, :parameters, :trusted_data, :environment_name attr_reader :time, :facts attr_reader :server_facts ENVIRONMENT = 'environment'.freeze def initialize_from_hash(data) @name = data['name'] || (raise ArgumentError, _("No name provided in serialized data")) @classes = data['classes'] || [] @parameters = data['parameters'] || {} env_name = data['environment'] || @parameters[ENVIRONMENT] unless env_name.nil? @parameters[ENVIRONMENT] = env_name @environment_name = env_name.intern end end def self.from_data_hash(data) node = new(name) node.initialize_from_hash(data) node end def to_data_hash result = { 'name' => name, 'environment' => environment.name.to_s, } result['classes'] = classes unless classes.empty? serialized_params = self.serializable_parameters result['parameters'] = serialized_params unless serialized_params.empty? result end def serializable_parameters new_params = parameters.dup new_params.delete(ENVIRONMENT) new_params end def environment if @environment @environment else if env = parameters[ENVIRONMENT] self.environment = env elsif environment_name self.environment = environment_name else # This should not be :current_environment, this is the default # for a node when it has not specified its environment # it will be used to establish what the current environment is. # self.environment = Puppet.lookup(:environments).get!(Puppet[:environment]) end @environment end end def environment=(env) if env.is_a?(String) or env.is_a?(Symbol) @environment = Puppet.lookup(:environments).get!(env) else @environment = env end # Keep environment_name attribute and parameter in sync if they have been set unless @environment.nil? # always set the environment parameter. It becomes top scope $environment for a manifest during catalog compilation. @parameters[ENVIRONMENT] = @environment.name.to_s self.environment_name = @environment.name if instance_variable_defined?(:@environment_name) end @environment end def has_environment_instance? !@environment.nil? end def initialize(name, options = {}) raise ArgumentError, _("Node names cannot be nil") unless name @name = name if classes = options[:classes] if classes.is_a?(String) @classes = [classes] else @classes = classes end else @classes = [] end @parameters = options[:parameters] || {} @facts = options[:facts] @server_facts = {} if env = options[:environment] self.environment = env end @time = Time.now end # Merge the node facts with parameters from the node source. # @api public # @param facts [optional, Puppet::Node::Facts] facts to merge into node parameters. # Will query Facts indirection if not supplied. # @raise [Puppet::Error] Raise on failure to retrieve facts if not supplied # @return [nil] def fact_merge(facts = nil) begin @facts = facts.nil? ? Puppet::Node::Facts.indirection.find(name, :environment => environment) : facts rescue => detail error = Puppet::Error.new(_("Could not retrieve facts for %{name}: %{detail}") % { name: name, detail: detail }, detail) error.set_backtrace(detail.backtrace) raise error end if !@facts.nil? @facts.sanitize # facts should never modify the environment parameter orig_param_env = @parameters[ENVIRONMENT] merge(@facts.values) @parameters[ENVIRONMENT] = orig_param_env end end # Merge any random parameters into our parameter list. def merge(params) params.each do |name, value| if @parameters.include?(name) Puppet::Util::Warnings.warnonce(_("The node parameter '%{param_name}' for node '%{node_name}' was already set to '%{value}'. It could not be set to '%{desired_value}'") % { param_name: name, node_name: @name, value: @parameters[name], desired_value: value }) else @parameters[name] = value end end end # Add extra facts, such as facts given to lookup on the command line The # extra facts will override existing ones. # @param extra_facts [Hash{String=>Object}] the facts to tadd # @api private def add_extra_facts(extra_facts) @facts.add_extra_values(extra_facts) @parameters.merge!(extra_facts) nil end def add_server_facts(facts) # Append the current environment to the list of server facts @server_facts = facts.merge({ "environment" => self.environment.name.to_s}) # Merge the server facts into the parameters for the node merge(facts) end # Calculate the list of names we might use for looking # up our node. This is only used for AST nodes. def names return [name] if Puppet.settings[:strict_hostname_checking] names = [] names += split_name(name) if name.include?(".") # First, get the fqdn unless fqdn = parameters["fqdn"] if parameters["hostname"] and parameters["domain"] fqdn = parameters["hostname"] + "." + parameters["domain"] else Puppet.warning _("Host is missing hostname and/or domain: %{name}") % { name: name } end end # Now that we (might) have the fqdn, add each piece to the name # list to search, in order of longest to shortest. names += split_name(fqdn) if fqdn # And make sure the node name is first, since that's the most # likely usage. # The name is usually the Certificate CN, but it can be # set to the 'facter' hostname instead. if Puppet[:node_name] == 'cert' names.unshift name else names.unshift parameters["hostname"] end names.uniq end def split_name(name) list = name.split(".") tmp = [] list.each_with_index do |short, i| tmp << list[0..i].join(".") end tmp.reverse end # Ensures the data is frozen # def trusted_data=(data) Puppet.warning(_("Trusted node data modified for node %{name}") % { name: name }) unless @trusted_data.nil? @trusted_data = data.freeze end # Resurrects and sanitizes trusted information in the node by modifying it and setting # the trusted_data in the node from parameters. # This modifies the node # def sanitize # Resurrect "trusted information" that comes from node/fact terminus. # The current way this is done in puppet db (currently the only one) # is to store the node parameter 'trusted' as a hash of the trusted information. # # Thus here there are two main cases: # 1. This terminus was used in a real agent call (only meaningful if someone curls the request as it would # fail since the result is a hash of two catalogs). # 2 It is a command line call with a given node that use a terminus that: # 2.1 does not include a 'trusted' fact - use local from node trusted information # 2.2 has a 'trusted' fact - this in turn could be # 2.2.1 puppet db having stored trusted node data as a fact (not a great design) # 2.2.2 some other terminus having stored a fact called "trusted" (most likely that would have failed earlier, but could # be spoofed). # # For the reasons above, the resurrection of trusted node data with authenticated => true is only performed # if user is running as root, else it is resurrected as unauthenticated. # trusted_param = @parameters['trusted'] if trusted_param # Blows up if it is a parameter as it will be set as $trusted by the compiler as if it was a variable @parameters.delete('trusted') unless trusted_param.is_a?(Hash) && %w{authenticated certname extensions}.all? {|key| trusted_param.has_key?(key) } # trusted is some kind of garbage, do not resurrect trusted_param = nil end else # trusted may be Boolean false if set as a fact by someone trusted_param = nil end # The options for node.trusted_data in priority order are: # 1) node came with trusted_data so use that # 2) else if there is :trusted_information in the puppet context # 3) else if the node provided a 'trusted' parameter (parsed out above) # 4) last, fallback to local node trusted information # # Note that trusted_data should be a hash, but (2) and (4) are not # hashes, so we to_h at the end if !trusted_data trusted = Puppet.lookup(:trusted_information) do trusted_param || Puppet::Context::TrustedInformation.local(self) end # Ruby 1.9.3 can't apply to_h to a hash, so check first self.trusted_data = (trusted.is_a?(Hash) ? trusted : trusted.to_h) end end end puppet-5.5.10/lib/puppet/parameter.rb0000644005276200011600000005126613417161721017414 0ustar jenkinsjenkinsrequire 'puppet/util/methodhelper' require 'puppet/util/logging' require 'puppet/util/docs' # The Parameter class is the implementation of a resource's attributes of _parameter_ kind. # The Parameter class is also the base class for {Puppet::Property}, and is used to describe meta-parameters # (parameters that apply to all resource types). # A Parameter (in contrast to a Property) has a single value where a property has both a current and a wanted value. # The Parameter class methods are used to configure and create an instance of Parameter that represents # one particular attribute data type; its valid value(s), and conversion to/from internal form. # # The intention is that a new parameter is created by using the DSL method {Puppet::Type.newparam}, or # {Puppet::Type.newmetaparam} if the parameter should be applicable to all resource types. # # A Parameter that does not specify and valid values (via {newvalues}) accepts any value. # # @see Puppet::Type # @see Puppet::Property # @api public # class Puppet::Parameter include Puppet::Util include Puppet::Util::Errors include Puppet::Util::Logging include Puppet::Util::MethodHelper require 'puppet/parameter/value_collection' class << self include Puppet::Util include Puppet::Util::Docs # @return [Symbol] The parameter name as given when it was created. attr_reader :name # @return [Object] The default value of the parameter as determined by the {defaultto} method, or nil if no # default has been set. attr_reader :default # @comment This somewhat odd documentation construct is because the getter and setter are not # orthogonal; the setter uses varargs and this confuses yard. To overcome the problem both the # getter and the setter are documented here. If this issues is fixed, a todo will be displayed # for the setter method, and the setter documentation can be moved there. # Since the attribute is actually RW it should perhaps instead just be implemented as a setter # and a getter method (and no attr_xxx declaration). # # @!attribute [rw] required_features # @return [Array] The names of the _provider features_ required for this parameter to work. # the returned names are always all lower case symbols. # @overload required_features # Returns the required _provider features_ as an array of lower case symbols # @overload required_features=(*args) # @param *args [Symbol] one or more names of required provider features # Sets the required_provider_features_ from one or more values, or array. The given arguments # are flattened, and internalized. # @api public # @dsl type # attr_reader :required_features # @return [Puppet::Parameter::ValueCollection] The set of valid values (or an empty set that accepts any value). # @api private # attr_reader :value_collection # @return [Boolean] Flag indicating whether this parameter is a meta-parameter or not. attr_accessor :metaparam # Defines how the `default` value of a parameter is computed. # The computation of the parameter's default value is defined by providing a value or a block. # A default of `nil` can not be used. # @overload defaultto(value) # Defines the default value with a literal value # @param value [Object] the literal value to use as the default value # @overload defaultto({|| ... }) # Defines that the default value is produced by the given block. The given block # should produce the default value. # @raise [Puppet::DevError] if value is nil, and no block is given. # @return [void] # @see Parameter.default # @dsl type # @api public # def defaultto(value = nil, &block) if block define_method(:default, &block) else if value.nil? raise Puppet::DevError, "Either a default value or block must be provided" end define_method(:default) do value end end end # Produces a documentation string. # If an enumeration of _valid values_ has been defined, it is appended to the documentation # for this parameter specified with the {desc} method. # @return [String] Returns a documentation string. # @api public # def doc @doc ||= "" unless defined?(@addeddocvals) @doc = Puppet::Util::Docs.scrub(@doc) if vals = value_collection.doc @doc << "\n\n#{vals}" end if features = self.required_features @doc << "\n\nRequires features #{features.flatten.collect { |f| f.to_s }.join(" ")}." end @addeddocvals = true end @doc end # Removes the `default` method if defined. # Has no effect if the default method is not defined. # This method is intended to be used in a DSL scenario where a parameter inherits from a parameter # with a default value that is not wanted in the derived parameter (otherwise, simply do not define # a default value method). # # @return [void] # @see desc # @api public # @dsl type # def nodefault undef_method :default if public_method_defined? :default end # Sets the documentation for this parameter. # @param str [String] The documentation string to set # @return [String] the given `str` parameter # @see doc # @dsl type # @api public # def desc(str) @doc = str end # Initializes the instance variables. # Clears the internal value collection (set of allowed values). # @return [void] # @api private # def initvars @value_collection = ValueCollection.new end # @overload munge {|| ... } # Defines an optional method used to convert the parameter value from DSL/string form to an internal form. # If a munge method is not defined, the DSL/string value is used as is. # @note This adds a method with the name `unsafe_munge` in the created parameter class. Later this method is # called in a context where exceptions will be rescued and handled. # @dsl type # @api public # def munge(&block) # I need to wrap the unsafe version in begin/rescue parameterments, # but if I directly call the block then it gets bound to the # class's context, not the instance's, thus the two methods, # instead of just one. define_method(:unsafe_munge, &block) end # @overload unmunge {|| ... } # Defines an optional method used to convert the parameter value to DSL/string form from an internal form. # If an `unmunge` method is not defined, the internal form is used. # @see munge # @note This adds a method with the name `unmunge` in the created parameter class. # @dsl type # @api public # def unmunge(&block) define_method(:unmunge, &block) end # Sets a marker indicating that this parameter is the _namevar_ (unique identifier) of the type # where the parameter is contained. # This also makes the parameter a required value. The marker can not be unset once it has been set. # @return [void] # @dsl type # @api public # def isnamevar @isnamevar = true @required = true end # @return [Boolean] Returns whether this parameter is the _namevar_ or not. # @api public # def isnamevar? @isnamevar end # Sets a marker indicating that this parameter is required. # Once set, it is not possible to make a parameter optional. # @return [void] # @dsl type # @api public # def isrequired @required = true end # @comment This method is not picked up by yard as it has a different signature than # expected for an attribute (varargs). Instead, this method is documented as an overload # of the attribute required_features. (Not ideal, but better than nothing). # @todo If this text appears in documentation - see comment in source and makes corrections - it means # that an issue in yardoc has been fixed. # def required_features=(*args) @required_features = args.flatten.collect { |a| a.to_s.downcase.intern } end # Returns whether this parameter is required or not. # A parameter is required if a call has been made to the DSL method {isrequired}. # @return [Boolean] Returns whether this parameter is required or not. # @api public # def required? @required end # @overload validate {|| ... } # Defines an optional method that is used to validate the parameter's DSL/string value. # Validation should raise appropriate exceptions, the return value of the given block is ignored. # The easiest way to raise an appropriate exception is to call the method {Puppet::Util::Errors.fail} with # the message as an argument. # To validate the munged value instead, just munge the value (`munge(value)`). # # @return [void] # @dsl type # @api public # def validate(&block) define_method(:unsafe_validate, &block) end # Defines valid values for the parameter (enumeration or regular expressions). # The set of valid values for the parameter can be limited to a (mix of) literal values and # regular expression patterns. # @note Each call to this method adds to the set of valid values # @param names [Symbol, Regexp] The set of valid literal values and/or patterns for the parameter. # @return [void] # @dsl type # @api public # def newvalues(*names) @value_collection.newvalues(*names) end # Makes the given `name` an alias for the given `other` name. # Or said differently, the valid value `other` can now also be referred to via the given `name`. # Aliasing may affect how the parameter's value is serialized/stored (it may store the `other` value # instead of the alias). # @api public # @dsl type # def aliasvalue(name, other) @value_collection.aliasvalue(name, other) end end # Creates instance (proxy) methods that delegates to a class method with the same name. # @api private # def self.proxymethods(*values) values.each { |val| define_method(val) do self.class.send(val) end } end # @!method required? # (see required?) # @!method isnamevar? # (see isnamevar?) # proxymethods("required?", "isnamevar?") # @return [Puppet::Resource] A reference to the resource this parameter is an attribute of (the _associated resource_). attr_accessor :resource # @comment LAK 2007-05-09: Keep the @parent around for backward compatibility. # @return [Puppet::Parameter] A reference to the parameter's parent kept for backwards compatibility. # @api private # attr_accessor :parent # @!attribute [rw] sensitive # @return [true, false] If this parameter has been tagged as sensitive. attr_accessor :sensitive # Returns a string representation of the resource's containment path in # the catalog. # @return [String] def path @path ||= '/' + pathbuilder.join('/') end # @return [Integer] Returns the result of calling the same method on the associated resource. def line resource.line end # @return [Integer] Returns the result of calling the same method on the associated resource. def file resource.file end # @return [Integer] Returns the result of calling the same method on the associated resource. def version resource.version end # Initializes the parameter with a required resource reference and optional attribute settings. # The option `:resource` must be specified or an exception is raised. Any additional options passed # are used to initialize the attributes of this parameter by treating each key in the `options` hash as # the name of the attribute to set, and the value as the value to set. # @param options [Hash{Symbol => Object]] Options, where `resource` is required # @option options [Puppet::Resource] :resource The resource this parameter holds a value for. Required. # @raise [Puppet::DevError] If resource is not specified in the options hash. # @api public # @note A parameter should be created via the DSL method {Puppet::Type::newparam} # def initialize(options = {}) options = symbolize_options(options) if resource = options[:resource] self.resource = resource options.delete(:resource) else raise Puppet::DevError, _("No resource set for %{name}") % { name: self.class.name } end set_options(options) end # Writes the given `msg` to the log with the loglevel indicated by the associated resource's # `loglevel` parameter. # @todo is loglevel a metaparameter? it is looked up with `resource[:loglevel]` # @return [void] # @api public def log(msg) send_log(resource[:loglevel], msg) end # @return [Boolean] Returns whether this parameter is a meta-parameter or not. def metaparam? self.class.metaparam end # @!attribute [r] name # @return [Symbol] The parameter's name as given when it was created. # @note Since a Parameter defines the name at the class level, each Parameter class must be # unique within a type's inheritance chain. # @comment each parameter class must define the name method, and parameter # instances do not change that name this implicitly means that a given # object can only have one parameter instance of a given parameter # class def name self.class.name end # @return [Boolean] Returns true if this parameter, the associated resource, or overall puppet mode is `noop`. # @todo How is noop mode set for a parameter? Is this of value in DSL to inhibit a parameter? # def noop @noop ||= false tmp = @noop || self.resource.noop || Puppet[:noop] || false #debug "noop is #{tmp}" tmp end # Returns an array of strings representing the containment hierarchy # (types/classes) that make up the path to the resource from the root # of the catalog. This is mostly used for logging purposes. # # @api private def pathbuilder if @resource return [@resource.pathbuilder, self.name] else return [self.name] end end # This is the default implementation of `munge` that simply produces the value (if it is valid). # The DSL method {munge} should be used to define an overriding method if munging is required. # # @api private # def unsafe_munge(value) self.class.value_collection.munge(value) end # Unmunges the value by transforming it from internal form to DSL form. # This is the default implementation of `unmunge` that simply returns the value without processing. # The DSL method {unmunge} should be used to define an overriding method if required. # @return [Object] the unmunged value # def unmunge(value) value end # Munges the value to internal form. # This implementation of `munge` provides exception handling around the specified munging of this parameter. # @note This method should not be overridden. Use the DSL method {munge} to define a munging method # if required. # @param value [Object] the DSL value to munge # @return [Object] the munged (internal) value # def munge(value) begin ret = unsafe_munge(value) rescue Puppet::Error => detail Puppet.debug "Reraising #{detail}" raise rescue => detail raise Puppet::DevError, _("Munging failed for value %{value} in class %{class_name}: %{detail}") % { value: value.inspect, class_name: self.name, detail: detail }, detail.backtrace end ret end # This is the default implementation of `validate` that may be overridden by the DSL method {validate}. # If no valid values have been defined, the given value is accepted, else it is validated against # the literal values (enumerator) and/or patterns defined by calling {newvalues}. # # @param value [Object] the value to check for validity # @raise [ArgumentError] if the value is not valid # @return [void] # @api private # def unsafe_validate(value) self.class.value_collection.validate(value) end # Performs validation of the given value against the rules defined by this parameter. # @return [void] # @todo Better description of when the various exceptions are raised.ArgumentError is rescued and # changed into Puppet::Error. # @raise [ArgumentError, TypeError, Puppet::DevError, Puppet::Error] under various conditions # A protected validation method that only ever raises useful exceptions. # @api public # def validate(value) begin unsafe_validate(value) rescue ArgumentError => detail self.fail Puppet::Error, detail.to_s, detail rescue Puppet::Error, TypeError raise rescue => detail raise Puppet::DevError, _("Validate method failed for class %{class_name}: %{detail}") % { class_name: self.name, detail: detail }, detail.backtrace end end # Sets the associated resource to nil. # @todo Why - what is the intent/purpose of this? # @return [nil] # def remove @resource = nil end # @return [Object] Gets the value of this parameter after performing any specified unmunging. def value unmunge(@value) unless @value.nil? end # Sets the given value as the value of this parameter. # @todo This original comment _"All of the checking should possibly be # late-binding (e.g., users might not exist when the value is assigned # but might when it is asked for)."_ does not seem to be correct, the implementation # calls both validate and munge on the given value, so no late binding. # # The given value is validated and then munged (if munging has been specified). The result is store # as the value of this parameter. # @return [Object] The given `value` after munging. # @raise (see #validate) # def value=(value) validate(value) @value = munge(value) end # @return [Puppet::Provider] Returns the provider of the associated resource. # @todo The original comment says = _"Retrieve the resource's provider. # Some types don't have providers, in which case we return the resource object itself."_ # This does not seem to be true, the default implementation that sets this value may be # {Puppet::Type.provider=} which always gets either the name of a provider or an instance of one. # def provider @resource.provider end # @return [Array] Returns an array of the associated resource's symbolic tags (including the parameter itself). # Returns an array of the associated resource's symbolic tags (including the parameter itself). # At a minimum, the array contains the name of the parameter. If the associated resource # has tags, these tags are also included in the array. # @todo The original comment says = _"The properties need to return tags so that logs correctly # collect them."_ what if anything of that is of interest to document. Should tags and their relationship # to logs be described. This is a more general concept. # def tags unless defined?(@tags) @tags = [] # This might not be true in testing @tags = @resource.tags if @resource.respond_to? :tags @tags << self.name.to_s end @tags end # @return [String] The name of the parameter in string form. def to_s name.to_s end # Formats the given string and conditionally redacts the provided interpolation variables, depending on if # this property is sensitive. # # @note Because the default implementation of Puppet::Property#is_to_s returns the current value as-is, it # doesn't necessarily return a string. For the sake of sanity we just cast everything to a string for # interpolation so we don't introduce issues with unexpected property values. # # @see String#format # @param fmt [String] The format string to interpolate. # @param args [Array] One or more strings to conditionally redact and interpolate into the format string. # # @return [String] def format(fmt, *args) fmt % args.map { |arg| @sensitive ? "[redacted]" : arg.to_s } end # Produces a String with the value formatted for display to a human. # # The output is created using the StringConverter with format '%#p' to produce # human readable code that is understood by puppet. # # @return [String] The formatted value in string form. # def self.format_value_for_display(value) Puppet::Pops::Types::StringConverter.convert(value, Puppet::Pops::Types::StringConverter::DEFAULT_PARAMETER_FORMAT) end # @comment Document post_compile_hook here as it does not exist anywhere (called from type if implemented) # @!method post_compile() # @since 3.4.0 # @api public # @abstract A subclass may implement this - it is not implemented in the Parameter class # This method may be implemented by a parameter in order to perform actions during compilation # after all resources have been added to the catalog. # @see Puppet::Type#finish # @see Puppet::Parser::Compiler#finish end require 'puppet/parameter/path' puppet-5.5.10/lib/puppet/pops.rb0000644005276200011600000000713413417161721016410 0ustar jenkinsjenkinsmodule Puppet # The Pops language system. This includes the parser, evaluator, AST model, and # Binder. # # @todo Explain how a user should use this to parse and evaluate the puppet # language. # # @note Warning: Pops is still considered experimental, as such the API may # change at any time. # # @api public module Pops EMPTY_HASH = {}.freeze EMPTY_ARRAY = [].freeze EMPTY_STRING = ''.freeze MAX_INTEGER = 0x7fffffffffffffff MIN_INTEGER = -0x8000000000000000 DOUBLE_COLON = '::'.freeze USCORE = '_'.freeze require 'semantic_puppet' require 'puppet/pops/patterns' require 'puppet/pops/utils' require 'puppet/pops/puppet_stack' require 'puppet/pops/adaptable' require 'puppet/pops/adapters' require 'puppet/pops/visitable' require 'puppet/pops/visitor' require 'puppet/pops/issues' require 'puppet/pops/semantic_error' require 'puppet/pops/label_provider' require 'puppet/pops/validation' require 'puppet/pops/issue_reporter' require 'puppet/pops/time/timespan' require 'puppet/pops/time/timestamp' # (the Types module initializes itself) require 'puppet/pops/types/types' require 'puppet/pops/types/string_converter' require 'puppet/pops/lookup' require 'puppet/pops/merge_strategy' module Model require 'puppet/pops/model/ast' require 'puppet/pops/model/tree_dumper' require 'puppet/pops/model/ast_transformer' require 'puppet/pops/model/factory' require 'puppet/pops/model/model_tree_dumper' require 'puppet/pops/model/model_label_provider' end module Resource require 'puppet/pops/resource/resource_type_impl' end module Evaluator require 'puppet/pops/evaluator/literal_evaluator' require 'puppet/pops/evaluator/callable_signature' require 'puppet/pops/evaluator/runtime3_converter' require 'puppet/pops/evaluator/runtime3_resource_support' require 'puppet/pops/evaluator/runtime3_support' require 'puppet/pops/evaluator/evaluator_impl' require 'puppet/pops/evaluator/epp_evaluator' require 'puppet/pops/evaluator/collector_transformer' require 'puppet/pops/evaluator/puppet_proc' module Collectors require 'puppet/pops/evaluator/collectors/abstract_collector' require 'puppet/pops/evaluator/collectors/fixed_set_collector' require 'puppet/pops/evaluator/collectors/catalog_collector' require 'puppet/pops/evaluator/collectors/exported_collector' end end module Parser require 'puppet/pops/parser/eparser' require 'puppet/pops/parser/parser_support' require 'puppet/pops/parser/locator' require 'puppet/pops/parser/locatable' require 'puppet/pops/parser/lexer2' require 'puppet/pops/parser/evaluating_parser' require 'puppet/pops/parser/epp_parser' require 'puppet/pops/parser/code_merger' end module Validation require 'puppet/pops/validation/checker4_0' require 'puppet/pops/validation/validator_factory_4_0' end # Subsystem for puppet functions defined in ruby. # # @api public module Functions require 'puppet/pops/functions/function' require 'puppet/pops/functions/dispatch' require 'puppet/pops/functions/dispatcher' end module Migration require 'puppet/pops/migration/migration_checker' end module Serialization require 'puppet/pops/serialization' end end require 'puppet/parser/ast/pops_bridge' require 'puppet/functions' require 'puppet/datatypes' Puppet::Pops::Model.register_pcore_types end puppet-5.5.10/lib/puppet/provider.rb0000644005276200011600000006023513417161721017262 0ustar jenkinsjenkins# A Provider is an implementation of the actions that manage resources (of some type) on a system. # This class is the base class for all implementation of a Puppet Provider. # # Concepts: #-- # * **Confinement** - confinement restricts providers to only be applicable under certain conditions. # It is possible to confine a provider several different ways: # * the included {#confine} method which provides filtering on fact, feature, existence of files, or a free form # predicate. # * the {commands} method that filters on the availability of given system commands. # * **Property hash** - the important instance variable `@property_hash` contains all current state values # for properties (it is lazily built). It is important that these values are managed appropriately in the # methods {instances}, {prefetch}, and in methods that alters the current state (those that change the # lifecycle (creates, destroys), or alters some value reflected backed by a property). # * **Flush** - is a hook that is called once per resource when everything has been applied. The intent is # that an implementation may defer modification of the current state typically done in property setters # and instead record information that allows flush to perform the changes more efficiently. # * **Execution Methods** - The execution methods provides access to execution of arbitrary commands. # As a convenience execution methods are available on both the instance and the class of a provider since a # lot of provider logic switch between these contexts fairly freely. # * **System Entity/Resource** - this documentation uses the term "system entity" for system resources to make # it clear if talking about a resource on the system being managed (e.g. a file in the file system) # or about a description of such a resource (e.g. a Puppet Resource). # * **Resource Type** - this is an instance of Type that describes a classification of instances of Resource (e.g. # the `File` resource type describes all instances of `file` resources). # (The term is used to contrast with "type" in general, and specifically to contrast with the implementation # class of Resource or a specific Type). # # @note An instance of a Provider is associated with one resource. # # @note Class level methods are only called once to configure the provider (when the type is created), and not # for each resource the provider is operating on. # The instance methods are however called for each resource. # # @api public # class Puppet::Provider include Puppet::Util include Puppet::Util::Errors include Puppet::Util::Warnings extend Puppet::Util::Warnings require 'puppet/confiner' require 'puppet/provider/command' extend Puppet::Confiner Puppet::Util.logmethods(self, true) class << self # Include the util module so we have access to things like 'which' include Puppet::Util, Puppet::Util::Docs include Puppet::Util::Logging # @return [String] The name of the provider attr_accessor :name # # @todo Original = _"The source parameter exists so that providers using the same # source can specify this, so reading doesn't attempt to read the # same package multiple times."_ This seems to be a package type specific attribute. Is this really # used? # # @return [???] The source is WHAT? attr_writer :source # @todo What is this type? A reference to a Puppet::Type ? # @return [Puppet::Type] the resource type (that this provider is ... WHAT?) # attr_accessor :resource_type # @!attribute [r] doc # The (full) documentation for this provider class. The documentation for the provider class itself # should be set with the DSL method {desc=}. Setting the documentation with with {doc=} has the same effect # as setting it with {desc=} (only the class documentation part is set). In essence this means that # there is no getter for the class documentation part (since the getter returns the full # documentation when there are additional contributors). # # @return [String] Returns the full documentation for the provider. # @see Puppet::Utils::Docs # @comment This is puzzling ... a write only doc attribute??? The generated setter never seems to be # used, instead the instance variable @doc is set in the `desc` method. This seems wrong. It is instead # documented as a read only attribute (to get the full documentation). Also see doc below for # desc. # @!attribute [w] desc # Sets the documentation of this provider class. (The full documentation is read via the # {doc} attribute). # # @dsl type # attr_writer :doc end # @return [???] This resource is what? Is an instance of a provider attached to one particular Puppet::Resource? # attr_accessor :resource # Convenience methods - see class method with the same name. # @return (see self.execute) def execute(*args) Puppet::Util::Execution.execute(*args) end # (see Puppet::Util::Execution.execute) def self.execute(*args) Puppet::Util::Execution.execute(*args) end # Convenience methods - see class method with the same name. # @return (see self.execpipe) def execpipe(*args, &block) Puppet::Util::Execution.execpipe(*args, &block) end # (see Puppet::Util::Execution.execpipe) def self.execpipe(*args, &block) Puppet::Util::Execution.execpipe(*args, &block) end # Convenience methods - see class method with the same name. # @return (see self.execfail) # @deprecated def execfail(*args) Puppet::Util::Execution.execfail(*args) end # (see Puppet::Util::Execution.execfail) # @deprecated def self.execfail(*args) Puppet::Util::Execution.execfail(*args) end # Returns the absolute path to the executable for the command referenced by the given name. # @raise [Puppet::DevError] if the name does not reference an existing command. # @return [String] the absolute path to the found executable for the command # @see which # @api public def self.command(name) name = name.intern if defined?(@commands) and command = @commands[name] # nothing elsif superclass.respond_to? :command and command = superclass.command(name) # nothing else raise Puppet::DevError, _("No command %{command} defined for provider %{provider}") % { command: name, provider: self.name } end which(command) end # Confines this provider to be suitable only on hosts where the given commands are present. # Also see {Puppet::Confiner#confine} for other types of confinement of a provider by use of other types of # predicates. # # @note It is preferred if the commands are not entered with absolute paths as this allows puppet # to search for them using the PATH variable. # # @param command_specs [Hash{String => String}] Map of name to command that the provider will # be executing on the system. Each command is specified with a name and the path of the executable. # @return [void] # @see optional_commands # @api public # def self.commands(command_specs) command_specs.each do |name, path| has_command(name, path) end end # Defines optional commands. # Since Puppet 2.7.8 this is typically not needed as evaluation of provider suitability # is lazy (when a resource is evaluated) and the absence of commands # that will be present after other resources have been applied no longer needs to be specified as # optional. # @param [Hash{String => String}] hash Named commands that the provider will # be executing on the system. Each command is specified with a name and the path of the executable. # (@see #has_command) # @see commands # @api public def self.optional_commands(hash) hash.each do |name, target| has_command(name, target) do is_optional end end end # Creates a convenience method for invocation of a command. # # This generates a Provider method that allows easy execution of the command. The generated # method may take arguments that will be passed through to the executable as the command line arguments # when it is invoked. # # @example Use it like this: # has_command(:echo, "/bin/echo") # def some_method # echo("arg 1", "arg 2") # end # @comment the . . . below is intentional to avoid the three dots to become an illegible ellipsis char. # @example . . . or like this # has_command(:echo, "/bin/echo") do # is_optional # environment :HOME => "/var/tmp", :PWD => "/tmp" # end # # @param name [Symbol] The name of the command (will become the name of the generated method that executes the command) # @param path [String] The path to the executable for the command # @yield [ ] A block that configures the command (see {Puppet::Provider::Command}) # @comment a yield [ ] produces {|| ...} in the signature, do not remove the space. # @note the name ´has_command´ looks odd in an API context, but makes more sense when seen in the internal # DSL context where a Provider is declaratively defined. # @api public # def self.has_command(name, path, &block) name = name.intern command = CommandDefiner.define(name, path, self, &block) @commands[name] = command.executable # Now define the class and instance methods. create_class_and_instance_method(name) do |*args| return command.execute(*args) end end # Internal helper class when creating commands - undocumented. # @api private class CommandDefiner private_class_method :new def self.define(name, path, confiner, &block) definer = new(name, path, confiner) definer.instance_eval(&block) if block definer.command end def initialize(name, path, confiner) @name = name @path = path @optional = false @confiner = confiner @custom_environment = {} end def is_optional @optional = true end def environment(env) @custom_environment = @custom_environment.merge(env) end def command if not @optional @confiner.confine :exists => @path, :for_binary => true end Puppet::Provider::Command.new(@name, @path, Puppet::Util, Puppet::Util::Execution, { :failonfail => true, :combine => true, :custom_environment => @custom_environment }) end end # @return [Boolean] Return whether the given feature has been declared or not. def self.declared_feature?(name) defined?(@declared_features) and @declared_features.include?(name) end # @return [Boolean] Returns whether this implementation satisfies all of the default requirements or not. # Returns false if there is no matching defaultfor # @see Provider.defaultfor # def self.default? default_match ? true : false end # Look through the array of defaultfor hashes and return the first match. # @return [Hash<{String => Object}>] the matching hash specified by a defaultfor # @see Provider.defaultfor # @api private def self.default_match @defaults.find do |default| default.all? do |key, values| case key when :feature feature_match(values) else fact_match(key, values) end end end end # Compare a fact value against one or more supplied value # @param [Symbol] fact a fact to query to match against one of the given values # @param [Array, Regexp, String] values one or more values to compare to the # value of the given fact # @return [Boolean] whether or not the fact value matches one of the supplied # values. Given one or more Regexp instances, fact is compared via the basic # pattern-matching operator. def self.fact_match(fact, values) fact_val = Facter.value(fact).to_s.downcase if fact_val.empty? return false else values = [values] unless values.is_a?(Array) values.any? do |value| if value.is_a?(Regexp) fact_val =~ value else fact_val.intern == value.to_s.downcase.intern end end end end def self.feature_match(value) Puppet.features.send(value.to_s + "?") end # Sets a facts filter that determine which of several suitable providers should be picked by default. # This selection only kicks in if there is more than one suitable provider. # To filter on multiple facts the given hash may contain more than one fact name/value entry. # The filter picks the provider if all the fact/value entries match the current set of facts. (In case # there are still more than one provider after this filtering, the first found is picked). # @param hash [Hash<{String => Object}>] hash of fact name to fact value. # @return [void] # def self.defaultfor(hash) @defaults << hash end # @return [Integer] Returns a numeric specificity for this provider based on how many requirements it has # and number of _ancestors_. The higher the number the more specific the provider. # The number of requirements is based on the hash size of the matching {Provider.defaultfor}. # # The _ancestors_ is the Ruby Module::ancestors method and the number of classes returned is used # to boost the score. The intent is that if two providers are equal, but one is more "derived" than the other # (i.e. includes more classes), it should win because it is more specific). # @note Because of how this value is # calculated there could be surprising side effects if a provider included an excessive amount of classes. # def self.specificity # This strange piece of logic attempts to figure out how many parent providers there # are to increase the score. What is will actually do is count all classes that Ruby Module::ancestors # returns (which can be other classes than those the parent chain) - in a way, an odd measure of the # complexity of a provider). match = default_match length = match ? match.length : 0 (length * 100) + ancestors.select { |a| a.is_a? Class }.length end # Initializes defaults and commands (i.e. clears them). # @return [void] def self.initvars @defaults = [] @commands = {} end # Returns a list of system resources (entities) this provider may/can manage. # This is a query mechanism that lists entities that the provider may manage on a given system. It is # is directly used in query services, but is also the foundation for other services; prefetching, and # purging. # # As an example, a package provider lists all installed packages. (In contrast, the File provider does # not list all files on the file-system as that would make execution incredibly slow). An implementation # of this method should be made if it is possible to quickly (with a single system call) provide all # instances. # # An implementation of this method should only cache the values of properties # if they are discovered as part of the process for finding existing resources. # Resource properties that require additional commands (than those used to determine existence/identity) # should be implemented in their respective getter method. (This is important from a performance perspective; # it may be expensive to compute, as well as wasteful as all discovered resources may perhaps not be managed). # # An implementation may return an empty list (naturally with the effect that it is not possible to query # for manageable entities). # # By implementing this method, it is possible to use the `resources´ resource type to specify purging # of all non managed entities. # # @note The returned instances are instance of some subclass of Provider, not resources. # @return [Array] a list of providers referencing the system entities # @abstract this method must be implemented by a subclass and this super method should never be called as it raises an exception. # @raise [Puppet::DevError] Error indicating that the method should have been implemented by subclass. # @see prefetch def self.instances raise Puppet::DevError, _("Provider %{provider} has not defined the 'instances' class method") % { provider: self.name } end # Creates getter- and setter- methods for each property supported by the resource type. # Call this method to generate simple accessors for all properties supported by the # resource type. These simple accessors lookup and sets values in the property hash. # The generated methods may be overridden by more advanced implementations if something # else than a straight forward getter/setter pair of methods is required. # (i.e. define such overriding methods after this method has been called) # # An implementor of a provider that makes use of `prefetch` and `flush` can use this method since it uses # the internal `@property_hash` variable to store values. An implementation would then update the system # state on a call to `flush` based on the current values in the `@property_hash`. # # @return [void] # def self.mk_resource_methods [resource_type.validproperties, resource_type.parameters].flatten.each do |attr| attr = attr.intern next if attr == :name define_method(attr) do if @property_hash[attr].nil? :absent else @property_hash[attr] end end define_method(attr.to_s + "=") do |val| @property_hash[attr] = val end end end self.initvars # This method is used to generate a method for a command. # @return [void] # @api private # def self.create_class_and_instance_method(name, &block) unless singleton_class.method_defined?(name) meta_def(name, &block) end unless method_defined?(name) define_method(name) do |*args| self.class.send(name, *args) end end end private_class_method :create_class_and_instance_method # @return [String] Returns the data source, which is the provider name if no other source has been set. # @todo Unclear what "the source" is used for? def self.source @source ||= self.name end # Returns true if the given attribute/parameter is supported by the provider. # The check is made that the parameter is a valid parameter for the resource type, and then # if all its required features (if any) are supported by the provider. # # @param param [Class, Puppet::Parameter] the parameter class, or a parameter instance # @return [Boolean] Returns whether this provider supports the given parameter or not. # @raise [Puppet::DevError] if the given parameter is not valid for the resource type # def self.supports_parameter?(param) if param.is_a?(Class) klass = param else unless klass = resource_type.attrclass(param) raise Puppet::DevError, _("'%{parameter_name}' is not a valid parameter for %{resource_type}") % { parameter_name: param, resource_type: resource_type.name } end end return true unless features = klass.required_features !!satisfies?(*features) end dochook(:defaults) do if @defaults.length > 0 return @defaults.collect do |d| "Default for " + d.collect do |f, v| "`#{f}` == `#{[v].flatten.join(', ')}`" end.sort.join(" and ") + "." end.join(" ") end end dochook(:commands) do if @commands.length > 0 return "Required binaries: " + @commands.collect do |n, c| "`#{c}`" end.sort.join(", ") + "." end end dochook(:features) do if features.length > 0 return "Supported features: " + features.collect do |f| "`#{f}`" end.sort.join(", ") + "." end end # Clears this provider instance to allow GC to clean up. def clear @resource = nil end # (see command) def command(name) self.class.command(name) end # Returns the value of a parameter value, or `:absent` if it is not defined. # @param param [Puppet::Parameter] the parameter to obtain the value of # @return [Object] the value of the parameter or `:absent` if not defined. # def get(param) @property_hash[param.intern] || :absent end # Creates a new provider that is optionally initialized from a resource or a hash of properties. # If no argument is specified, a new non specific provider is initialized. If a resource is given # it is remembered for further operations. If a hash is used it becomes the internal `@property_hash` # structure of the provider - this hash holds the current state property values of system entities # as they are being discovered by querying or other operations (typically getters). # # @todo The use of a hash as a parameter needs a better explanation; why is this done? What is the intent? # @param resource [Puppet::Resource, Hash] optional resource or hash # def initialize(resource = nil) if resource.is_a?(Hash) # We don't use a duplicate here, because some providers (ParsedFile, at least) # use the hash here for later events. @property_hash = resource elsif resource @resource = resource @property_hash = {} else @property_hash = {} end end # Returns the name of the resource this provider is operating on. # @return [String] the name of the resource instance (e.g. the file path of a File). # @raise [Puppet::DevError] if no resource is set, or no name defined. # def name if n = @property_hash[:name] return n elsif self.resource resource.name else raise Puppet::DevError, _("No resource and no name in property hash in %{class_name} instance") % { class_name: self.class.name } end end # Sets the given parameters values as the current values for those parameters. # Other parameters are unchanged. # @param [Array] params the parameters with values that should be set # @return [void] # def set(params) params.each do |param, value| @property_hash[param.intern] = value end end # @return [String] Returns a human readable string with information about the resource and the provider. def to_s "#{@resource}(provider=#{self.class.name})" end # @return [String] Returns a human readable string with information about the resource and the provider. def inspect to_s end # Makes providers comparable. include Comparable # Compares this provider against another provider. # Comparison is only possible with another provider (no other class). # The ordering is based on the class name of the two providers. # # @return [-1,0,+1, nil] A comparison result -1, 0, +1 if this is before other, equal or after other. Returns # nil oif not comparable to other. # @see Comparable def <=>(other) # We can only have ordering against other providers. return nil unless other.is_a? Puppet::Provider # Otherwise, order by the providers class name. return self.class.name <=> other.class.name end # @comment Document prefetch here as it does not exist anywhere else (called from transaction if implemented) # @!method self.prefetch(resource_hash) # @abstract A subclass may implement this - it is not implemented in the Provider class # This method may be implemented by a provider in order to pre-fetch resource properties. # If implemented it should set the provider instance of the managed resources to a provider with the # fetched state (i.e. what is returned from the {instances} method). # @param resources_hash [Hash<{String => Puppet::Resource}>] map from name to resource of resources to prefetch # @return [void] # @api public # @comment Document post_resource_eval here as it does not exist anywhere else (called from transaction if implemented) # @!method self.post_resource_eval() # @since 3.4.0 # @api public # @abstract A subclass may implement this - it is not implemented in the Provider class # This method may be implemented by a provider in order to perform any # cleanup actions needed. It will be called at the end of the transaction if # any resources in the catalog make use of the provider, regardless of # whether the resources are changed or not and even if resource failures occur. # @return [void] # @comment Document flush here as it does not exist anywhere (called from transaction if implemented) # @!method flush() # @abstract A subclass may implement this - it is not implemented in the Provider class # This method may be implemented by a provider in order to flush properties that has not been individually # applied to the managed entity's current state. # @return [void] # @api public end puppet-5.5.10/lib/puppet/reports.rb0000644005276200011600000000567713417161721017137 0ustar jenkinsjenkinsrequire 'puppet/util/instance_loader' # This class is an implementation of a simple mechanism for loading and returning reports. # The intent is that a user registers a report by calling {register_report} and providing a code # block that performs setup, and defines a method called `process`. The setup, and the `process` method # are called in the context where `self` is an instance of {Puppet::Transaction::Report} which provides the actual # data for the report via its methods. # # @example Minimal scaffolding for a report... # Puppet::Reports::.register_report(:myreport) do # # do setup here # def process # if self.status == 'failed' # msg = "failed puppet run for #{self.host} #{self.status}" # . . . # else # . . . # end # end # end # # Required configuration: # -- # * A .rb file that defines a new report should be placed in the master's directory `lib/puppet/reports` # * The `puppet.conf` file must have `report = true` in the `[agent]` section # # @see Puppet::Transaction::Report # @api public # class Puppet::Reports extend Puppet::Util::ClassGen extend Puppet::Util::InstanceLoader # Set up autoloading and retrieving of reports. instance_load :report, 'puppet/reports' class << self # @api private attr_reader :hooks end # Adds a new report type. # The block should contain setup, and define a method with the name `process`. The `process` method is # called when the report is executed; the `process` method has access to report data via methods available # in its context where `self` is an instance of {Puppet::Transaction::Report}. # # For an example, see the overview of this class. # # @param name [Symbol] the name of the report (do not use whitespace in the name). # @param options [Hash] a hash of options # @option options [Boolean] :useyaml whether yaml should be used or not # @todo Uncertain what the options :useyaml really does; "whether yaml should be used or not", used where/how? # def self.register_report(name, options = {}, &block) name = name.intern mod = genmodule(name, :extend => Puppet::Util::Docs, :hash => instance_hash(:report), :overwrite => true, :block => block) mod.useyaml = true if options[:useyaml] mod.send(:define_method, :report_name) do name end end # Collects the docs for all reports. # @api private def self.reportdocs docs = "" # Use this method so they all get loaded instance_loader(:report).loadall loaded_instances(:report).sort { |a,b| a.to_s <=> b.to_s }.each do |name| mod = self.report(name) docs << "#{name}\n#{"-" * name.to_s.length}\n" docs << Puppet::Util::Docs.scrub(mod.doc) << "\n\n" end docs end # Lists each of the reports. # @api private def self.reports instance_loader(:report).loadall loaded_instances(:report) end end puppet-5.5.10/lib/puppet/resource.rb0000644005276200011600000005227613417161721017265 0ustar jenkinsjenkinsrequire 'puppet' require 'puppet/util/tagging' require 'puppet/parameter' # The simplest resource class. Eventually it will function as the # base class for all resource-like behaviour. # # @api public class Puppet::Resource include Puppet::Util::Tagging include Puppet::Util::PsychSupport include Enumerable attr_accessor :file, :line, :catalog, :exported, :virtual, :strict attr_reader :type, :title, :parameters, :rich_data_enabled # @!attribute [rw] sensitive_parameters # @api private # @return [Array] A list of parameters to be treated as sensitive attr_accessor :sensitive_parameters # @deprecated attr_accessor :validate_parameters require 'puppet/indirector' extend Puppet::Indirector indirects :resource, :terminus_class => :ral EMPTY_ARRAY = [].freeze EMPTY_HASH = {}.freeze ATTRIBUTES = [:file, :line, :exported].freeze TYPE_CLASS = 'Class'.freeze TYPE_NODE = 'Node'.freeze TYPE_SITE = 'Site'.freeze PCORE_TYPE_KEY = '__pcore_type__'.freeze VALUE_KEY = 'value'.freeze def self.from_data_hash(data) resource = self.allocate resource.initialize_from_hash(data) resource end def initialize_from_hash(data) raise ArgumentError, _('No resource type provided in serialized data') unless type = data['type'] raise ArgumentError, _('No resource title provided in serialized data') unless title = data['title'] @type, @title = self.class.type_and_title(type, title) if params = data['parameters'] params = Puppet::Pops::Serialization::FromDataConverter.convert(params) @parameters = {} params.each { |param, value| self[param] = value } else @parameters = EMPTY_HASH end if sensitives = data['sensitive_parameters'] @sensitive_parameters = sensitives.map(&:to_sym) else @sensitive_parameters = EMPTY_ARRAY end if tags = data['tags'] tag(*tags) end ATTRIBUTES.each do |a| value = data[a.to_s] send("#{a}=", value) unless value.nil? end end def inspect "#{@type}[#{@title}]#{to_hash.inspect}" end def to_data_hash data = { 'type' => type, 'title' => title.to_s, 'tags' => tags.to_data_hash } ATTRIBUTES.each do |param| value = send(param) data[param.to_s] = value unless value.nil? end data['exported'] ||= false params = {} self.to_hash.each_pair do |param, value| # Don't duplicate the title as the namevar unless param == namevar && value == title params[param.to_s] = Puppet::Resource.value_to_json_data(value) end end unless params.empty? data['parameters'] = Puppet::Pops::Serialization::ToDataConverter.convert(params, { :rich_data => environment.rich_data?, :symbol_as_string => true, :local_reference => false, :type_by_reference => true, :message_prefix => ref, :semantic => self }) end data['sensitive_parameters'] = sensitive_parameters.map(&:to_s) unless sensitive_parameters.empty? data end def self.value_to_json_data(value) if value.is_a?(Array) value.map{|v| value_to_json_data(v) } elsif value.is_a?(Hash) result = {} value.each_pair { |k, v| result[value_to_json_data(k)] = value_to_json_data(v) } result elsif value.is_a?(Puppet::Resource) value.to_s elsif value.is_a?(Symbol) && value == :undef nil else value end end def yaml_property_munge(x) self.value.to_json_data(x) end # Proxy these methods to the parameters hash. It's likely they'll # be overridden at some point, but this works for now. %w{has_key? keys length delete empty? <<}.each do |method| define_method(method) do |*args| parameters.send(method, *args) end end # Set a given parameter. Converts all passed names # to lower-case symbols. def []=(param, value) validate_parameter(param) if validate_parameters parameters[parameter_name(param)] = value end # Return a given parameter's value. Converts all passed names # to lower-case symbols. def [](param) parameters[parameter_name(param)] end def ==(other) return false unless other.respond_to?(:title) and self.type == other.type and self.title == other.title return false unless to_hash == other.to_hash true end # Compatibility method. def builtin? # TODO: should be deprecated (was only used in one place in puppet codebase) builtin_type? end # Is this a builtin resource type? def builtin_type? # Note - old implementation only checked if the resource_type was a Class resource_type.is_a?(Puppet::CompilableResourceType) end # Iterate over each param/value pair, as required for Enumerable. def each parameters.each { |p,v| yield p, v } end def include?(parameter) super || parameters.keys.include?( parameter_name(parameter) ) end %w{exported virtual strict}.each do |m| define_method(m+"?") do self.send(m) end end def class? @is_class ||= @type == TYPE_CLASS end def stage? @is_stage ||= @type.to_s.downcase == "stage" end # Construct a resource from data. # # Constructs a resource instance with the given `type` and `title`. Multiple # type signatures are possible for these arguments and most will result in an # expensive call to {Puppet::Node::Environment#known_resource_types} in order # to resolve `String` and `Symbol` Types to actual Ruby classes. # # @param type [Symbol, String] The name of the Puppet Type, as a string or # symbol. The actual Type will be looked up using # {Puppet::Node::Environment#known_resource_types}. This lookup is expensive. # @param type [String] The full resource name in the form of # `"Type[Title]"`. This method of calling should only be used when # `title` is `nil`. # @param type [nil] If a `nil` is passed, the title argument must be a string # of the form `"Type[Title]"`. # @param type [Class] A class that inherits from `Puppet::Type`. This method # of construction is much more efficient as it skips calls to # {Puppet::Node::Environment#known_resource_types}. # # @param title [String, :main, nil] The title of the resource. If type is `nil`, may also # be the full resource name in the form of `"Type[Title]"`. # # @api public def initialize(type, title = nil, attributes = EMPTY_HASH) @parameters = {} @sensitive_parameters = [] if type.is_a?(Puppet::Resource) # Copy constructor. Let's avoid munging, extracting, tagging, etc src = type self.file = src.file self.line = src.line self.exported = src.exported self.virtual = src.virtual self.set_tags(src) self.environment = src.environment @rstype = src.resource_type @type = src.type @title = src.title src.to_hash.each do |p, v| if v.is_a?(Puppet::Resource) v = v.copy_as_resource elsif v.is_a?(Array) # flatten resource references arrays v = v.flatten if v.flatten.find { |av| av.is_a?(Puppet::Resource) } v = v.collect do |av| av = av.copy_as_resource if av.is_a?(Puppet::Resource) av end end self[p] = v end @sensitive_parameters.replace(type.sensitive_parameters) else if type.is_a?(Hash) #TRANSLATORS 'Puppet::Resource.new' should not be translated raise ArgumentError, _("Puppet::Resource.new does not take a hash as the first argument.") + ' ' + _("Did you mean (%{type}, %{title}) ?") % { type: (type[:type] || type["type"]).inspect, title: (type[:title] || type["title"]).inspect } end # In order to avoid an expensive search of 'known_resource_types" and # to obey/preserve the implementation of the resource's type - if the # given type is a resource type implementation (one of): # * a "classic" 3.x ruby plugin # * a compatible implementation (e.g. loading from pcore metadata) # * a resolved user defined type # # ...then, modify the parameters to the "old" (agent side compatible) way # of describing the type/title with string/symbols. # # TODO: Further optimizations should be possible as the "type juggling" is # not needed when the type implementation is known. # if type.is_a?(Puppet::CompilableResourceType) || type.is_a?(Puppet::Resource::Type) # set the resource type implementation self.resource_type = type # set the type name to the symbolic name type = type.name end @exported = false # Set things like environment, strictness first. attributes.each do |attr, value| next if attr == :parameters send(attr.to_s + "=", value) end @type, @title = self.class.type_and_title(type, title) rt = resource_type if strict? && rt.nil? if self.class? raise ArgumentError, _("Could not find declared class %{title}") % { title: title } else raise ArgumentError, _("Invalid resource type %{type}") % { type: type } end end params = attributes[:parameters] unless params.nil? || params.empty? extract_parameters(params) if rt && rt.respond_to?(:deprecate_params) rt.deprecate_params(title, params) end end tag(self.type) tag_if_valid(self.title) end end def ref to_s end # Find our resource. def resolve catalog ? catalog.resource(to_s) : nil end # A resource is an application component if it exports or consumes # one or more capabilities, or if it requires a capability resource def is_application_component? return true if ! export.empty? || self[:consume] # Array(self[:require]) does not work for Puppet::Resource instances req = self[:require] || [] req = [ req ] unless req.is_a?(Array) req.any? { |r| r.is_capability? } end # A resource is a capability (instance) if its underlying type is a # capability type def is_capability? !resource_type.nil? && resource_type.is_capability? end # Returns the value of the 'export' metaparam as an Array # @api private def export v = self[:export] || [] v.is_a?(Array) ? v : [ v ] end # The resource's type implementation # @return [Puppet::Type, Puppet::Resource::Type] # @api private def resource_type @rstype ||= self.class.resource_type(type, title, environment) end # The resource's type implementation # @return [Puppet::Type, Puppet::Resource::Type] # @api private def self.resource_type(type, title, environment) case type when TYPE_CLASS; environment.known_resource_types.hostclass(title == :main ? "" : title) when TYPE_NODE; environment.known_resource_types.node(title) when TYPE_SITE; environment.known_resource_types.site(nil) else result = Puppet::Type.type(type) if !result krt = environment.known_resource_types result = krt.definition(type) || krt.application(type) end result end end # Set the resource's type implementation # @param type [Puppet::Type, Puppet::Resource::Type] # @api private def resource_type=(type) @rstype = type end def environment @environment ||= if catalog catalog.environment_instance else Puppet.lookup(:current_environment) { Puppet::Node::Environment::NONE } end end def environment=(environment) @environment = environment end # Produces a hash of attribute to value mappings where the title parsed into its components # acts as the default values overridden by any parameter values explicitly given as parameters. # def to_hash parse_title.merge parameters end def to_s "#{type}[#{title}]" end def uniqueness_key # Temporary kludge to deal with inconsistent use patterns; ensure we don't return nil for namevar/:name h = self.to_hash name = h[namevar] || h[:name] || self.name h[namevar] ||= name h[:name] ||= name h.values_at(*key_attributes.sort_by { |k| k.to_s }) end def key_attributes resource_type.respond_to?(:key_attributes) ? resource_type.key_attributes : [:name] end # Convert our resource to yaml for Hiera purposes. def to_hierayaml # Collect list of attributes to align => and move ensure first attr = parameters.keys attr_max = attr.inject(0) { |max,k| k.to_s.length > max ? k.to_s.length : max } attr.sort! if attr.first != :ensure && attr.include?(:ensure) attr.delete(:ensure) attr.unshift(:ensure) end attributes = attr.collect { |k| v = parameters[k] " %-#{attr_max}s: %s\n" % [k, Puppet::Parameter.format_value_for_display(v)] }.join " %s:\n%s" % [self.title, attributes] end # Convert our resource to Puppet code. def to_manifest # Collect list of attributes to align => and move ensure first attr = parameters.keys attr_max = attr.inject(0) { |max,k| k.to_s.length > max ? k.to_s.length : max } attr.sort! if attr.first != :ensure && attr.include?(:ensure) attr.delete(:ensure) attr.unshift(:ensure) end attributes = attr.collect { |k| v = parameters[k] " %-#{attr_max}s => %s,\n" % [k, Puppet::Parameter.format_value_for_display(v)] }.join escaped = self.title.gsub(/'/,"\\\\'") "%s { '%s':\n%s}" % [self.type.to_s.downcase, escaped, attributes] end def to_ref ref end # Convert our resource to a RAL resource instance. Creates component # instances for resource types that don't exist. def to_ral typeklass = Puppet::Type.type(self.type) || Puppet::Type.type(:component) typeklass.new(self) end def name # this is potential namespace conflict # between the notion of an "indirector name" # and a "resource name" [ type, title ].join('/') end def missing_arguments resource_type.arguments.select do |param, default| the_param = parameters[param.to_sym] the_param.nil? || the_param.value.nil? || the_param.value == :undef end end private :missing_arguments # @deprecated Not used by Puppet # @api private def set_default_parameters(scope) Puppet.deprecation_warning(_('The method Puppet::Resource.set_default_parameters is deprecated and will be removed in the next major release of Puppet.')) return [] unless resource_type and resource_type.respond_to?(:arguments) unless is_a?(Puppet::Parser::Resource) fail Puppet::DevError, _("Cannot evaluate default parameters for %{resource} - not a parser resource") % { resource: self } end missing_arguments.collect do |param, default| rtype = resource_type if rtype.type == :hostclass using_bound_value = false catch(:no_such_key) do bound_value = Puppet::Pops::Lookup.search_and_merge("#{rtype.name}::#{param}", Puppet::Pops::Lookup::Invocation.new(scope), nil) # Assign bound value but don't let an undef trump a default expression unless bound_value.nil? && !default.nil? self[param.to_sym] = bound_value using_bound_value = true end end end unless using_bound_value next if default.nil? self[param.to_sym] = default.safeevaluate(scope) end param end.compact end def copy_as_resource Puppet::Resource.new(self) end def valid_parameter?(name) resource_type.valid_parameter?(name) end # Verify that all required arguments are either present or # have been provided with defaults. # Must be called after 'set_default_parameters'. We can't join the methods # because Type#set_parameters needs specifically ordered behavior. # # @deprecated Not used by Puppet # @api private def validate_complete Puppet.deprecation_warning(_('The method Puppet::Resource.validate_complete is deprecated and will be removed in the next major release of Puppet.')) return unless resource_type and resource_type.respond_to?(:arguments) resource_type.arguments.each do |param, default| param = param.to_sym fail Puppet::ParseError, _("Must pass %{param} to %{resource}") % { param: param, resource: self } unless parameters.include?(param) end # Perform optional type checking arg_types = resource_type.argument_types # Parameters is a map from name, to parameter, and the parameter again has name and value parameters.each do |name, value| next unless t = arg_types[name.to_s] # untyped, and parameters are symbols here (aargh, strings in the type) unless Puppet::Pops::Types::TypeCalculator.instance?(t, value.value) inferred_type = Puppet::Pops::Types::TypeCalculator.infer_set(value.value) actual = inferred_type.generalize() fail Puppet::ParseError, _("Expected parameter '%{name}' of '%{value0}' to have type %{value1}, got %{value2}") % { name: name, value0: self, value1: t.to_s, value2: actual.to_s } end end end def validate_parameter(name) raise Puppet::ParseError.new(_("no parameter named '%{name}'") % { name: name }, file, line) unless valid_parameter?(name) end # This method, together with #file and #line, makes it possible for a Resource to be a 'source_pos' in a reported issue. # @return [Integer] Instances of this class will always return `nil`. def pos nil end def prune_parameters(options = EMPTY_HASH) properties = resource_type.properties.map(&:name) dup.collect do |attribute, value| if value.to_s.empty? or Array(value).empty? delete(attribute) elsif value.to_s == "absent" and attribute.to_s != "ensure" delete(attribute) end parameters_to_include = options[:parameters_to_include] || [] delete(attribute) unless properties.include?(attribute) || parameters_to_include.include?(attribute) end self end # @api private def self.type_and_title(type, title) type, title = extract_type_and_title(type, title) type = munge_type_name(type) if type == TYPE_CLASS title = title == '' ? :main : munge_type_name(title) end [type, title] end def self.extract_type_and_title(argtype, argtitle) if (argtype.nil? || argtype == :component || argtype == :whit) && argtitle =~ /^([^\[\]]+)\[(.+)\]$/m then [ $1, $2 ] elsif argtitle.nil? && argtype =~ /^([^\[\]]+)\[(.+)\]$/m then [ $1, $2 ] elsif argtitle then [ argtype, argtitle ] elsif argtype.is_a?(Puppet::Type) then [ argtype.class.name, argtype.title ] else raise ArgumentError, _("No title provided and %{type} is not a valid resource reference") % { type: argtype.inspect } end end private_class_method :extract_type_and_title def self.munge_type_name(value) return :main if value == :main return TYPE_CLASS if value == '' || value.nil? || value.to_s.casecmp('component') == 0 Puppet::Pops::Types::TypeFormatter.singleton.capitalize_segments(value.to_s) end private_class_method :munge_type_name private # Produce a canonical method name. def parameter_name(param) param = param.to_s.downcase.to_sym if param == :name and namevar param = namevar end param end # The namevar for our resource type. If the type doesn't exist, # always use :name. def namevar if builtin_type? && !(t = resource_type).nil? && t.key_attributes.length == 1 t.key_attributes.first else :name end end def extract_parameters(params) params.each do |param, value| validate_parameter(param) if strict? self[param] = value end end # Produces a hash with { :key => part_of_title } for each entry in title_patterns # for the resource type. A typical result for a title of 'example' is {:name => 'example'}. # A resource type with a complex title to attribute mapping returns one entry in the hash # per part. # def parse_title h = {} type = resource_type if type.respond_to?(:title_patterns) && !type.title_patterns.nil? type.title_patterns.each { |regexp, symbols_and_lambdas| if captures = regexp.match(title.to_s) symbols_and_lambdas.zip(captures[1..-1]).each do |symbol_and_lambda,capture| symbol, proc = symbol_and_lambda # Many types pass "identity" as the proc; we might as well give # them a shortcut to delivering that without the extra cost. # # Especially because the global type defines title_patterns and # uses the identity patterns. # # This was worth about 8MB of memory allocation saved in my # testing, so is worth the complexity for the API. if proc then h[symbol] = proc.call(capture) else h[symbol] = capture end end return h end } # If we've gotten this far, then none of the provided title patterns # matched. Since there's no way to determine the title then the # resource should fail here. raise Puppet::Error, _("No set of title patterns matched the title \"%{title}\".") % { title: title } else return { :name => title.to_s } end end end puppet-5.5.10/lib/puppet/settings.rb0000644005276200011600000014142713417161721017273 0ustar jenkinsjenkinsrequire 'puppet' require 'getoptlong' require 'puppet/util/watched_file' require 'puppet/util/command_line/puppet_option_parser' require 'forwardable' require 'fileutils' # The class for handling configuration files. class Puppet::Settings extend Forwardable include Enumerable require 'puppet/settings/errors' require 'puppet/settings/base_setting' require 'puppet/settings/string_setting' require 'puppet/settings/enum_setting' require 'puppet/settings/symbolic_enum_setting' require 'puppet/settings/array_setting' require 'puppet/settings/file_setting' require 'puppet/settings/directory_setting' require 'puppet/settings/file_or_directory_setting' require 'puppet/settings/path_setting' require 'puppet/settings/boolean_setting' require 'puppet/settings/terminus_setting' require 'puppet/settings/duration_setting' require 'puppet/settings/ttl_setting' require 'puppet/settings/priority_setting' require 'puppet/settings/autosign_setting' require 'puppet/settings/config_file' require 'puppet/settings/value_translator' require 'puppet/settings/environment_conf' require 'puppet/settings/server_list_setting' require 'puppet/settings/certificate_revocation_setting' # local reference for convenience PuppetOptionParser = Puppet::Util::CommandLine::PuppetOptionParser attr_accessor :files attr_reader :timer # These are the settings that every app is required to specify; there are # reasonable defaults defined in application.rb. REQUIRED_APP_SETTINGS = [:logdir, :confdir, :vardir, :codedir] # The acceptable sections of the puppet.conf configuration file. ALLOWED_SECTION_NAMES = ['main', 'master', 'agent', 'user'].freeze NONE = 'none'.freeze # This method is intended for puppet internal use only; it is a convenience method that # returns reasonable application default settings values for a given run_mode. def self.app_defaults_for_run_mode(run_mode) { :name => run_mode.to_s, :run_mode => run_mode.name, :confdir => run_mode.conf_dir, :codedir => run_mode.code_dir, :vardir => run_mode.var_dir, :rundir => run_mode.run_dir, :logdir => run_mode.log_dir, } end def self.default_certname() hostname = hostname_fact domain = domain_fact if domain and domain != "" fqdn = [hostname, domain].join(".") else fqdn = hostname end fqdn.to_s.gsub(/\.$/, '') end def self.hostname_fact() Facter.value :hostname end def self.domain_fact() Facter.value :domain end def self.default_config_file_name "puppet.conf" end # Create a new collection of config settings. def initialize @config = {} @shortnames = {} @created = [] # Keep track of set values. @value_sets = { :cli => Values.new(:cli, @config), :memory => Values.new(:memory, @config), :application_defaults => Values.new(:application_defaults, @config), :overridden_defaults => Values.new(:overridden_defaults, @config), } @configuration_file = nil # And keep a per-environment cache @cache = Hash.new { |hash, key| hash[key] = {} } @values = Hash.new { |hash, key| hash[key] = {} } # The list of sections we've used. @used = [] @hooks_to_call_on_application_initialization = [] @deprecated_setting_names = [] @deprecated_settings_that_have_been_configured = [] @translate = Puppet::Settings::ValueTranslator.new @config_file_parser = Puppet::Settings::ConfigFile.new(@translate) end # Retrieve a config value # @param param [Symbol] the name of the setting # @return [Object] the value of the setting # @api private def [](param) if @deprecated_setting_names.include?(param) issue_deprecation_warning(setting(param), "Accessing '#{param}' as a setting is deprecated.") end value(param) end # Set a config value. This doesn't set the defaults, it sets the value itself. # @param param [Symbol] the name of the setting # @param value [Object] the new value of the setting # @api private def []=(param, value) if @deprecated_setting_names.include?(param) issue_deprecation_warning(setting(param), "Modifying '#{param}' as a setting is deprecated.") end @value_sets[:memory].set(param, value) unsafe_flush_cache end # Create a new default value for the given setting. The default overrides are # higher precedence than the defaults given in defaults.rb, but lower # precedence than any other values for the setting. This allows one setting # `a` to change the default of setting `b`, but still allow a user to provide # a value for setting `b`. # # @param param [Symbol] the name of the setting # @param value [Object] the new default value for the setting # @api private def override_default(param, value) @value_sets[:overridden_defaults].set(param, value) unsafe_flush_cache end # Generate the list of valid arguments, in a format that GetoptLong can # understand, and add them to the passed option list. def addargs(options) # Add all of the settings as valid options. self.each { |name, setting| setting.getopt_args.each { |args| options << args } } options end # Generate the list of valid arguments, in a format that OptionParser can # understand, and add them to the passed option list. def optparse_addargs(options) # Add all of the settings as valid options. self.each { |name, setting| options << setting.optparse_args } options end # Is our setting a boolean setting? def boolean?(param) param = param.to_sym @config.include?(param) and @config[param].kind_of?(BooleanSetting) end # Remove all set values, potentially skipping cli values. def clear unsafe_clear end # Remove all set values, potentially skipping cli values. def unsafe_clear(clear_cli = true, clear_application_defaults = false) if clear_application_defaults @value_sets[:application_defaults] = Values.new(:application_defaults, @config) @app_defaults_initialized = false end if clear_cli @value_sets[:cli] = Values.new(:cli, @config) # Only clear the 'used' values if we were explicitly asked to clear out # :cli values; otherwise, it may be just a config file reparse, # and we want to retain this cli values. @used = [] end @value_sets[:memory] = Values.new(:memory, @config) @value_sets[:overridden_defaults] = Values.new(:overridden_defaults, @config) @deprecated_settings_that_have_been_configured.clear @values.clear @cache.clear end private :unsafe_clear # Clears all cached settings for a particular environment to ensure # that changes to environment.conf are reflected in the settings if # the environment timeout has expired. # # param [String, Symbol] environment the name of environment to clear settings for # # @api private def clear_environment_settings(environment) if environment.nil? return end @cache[environment.to_sym].clear @values[environment.to_sym] = {} end # Clear @cache, @used and the Environment. # # Whenever an object is returned by Settings, a copy is stored in @cache. # As long as Setting attributes that determine the content of returned # objects remain unchanged, Settings can keep returning objects from @cache # without re-fetching or re-generating them. # # Whenever a Settings attribute changes, such as @values or @preferred_run_mode, # this method must be called to clear out the caches so that updated # objects will be returned. def flush_cache unsafe_flush_cache end def unsafe_flush_cache clearused end private :unsafe_flush_cache def clearused @cache.clear @used = [] end def global_defaults_initialized?() @global_defaults_initialized end def initialize_global_settings(args = []) raise Puppet::DevError, _("Attempting to initialize global default settings more than once!") if global_defaults_initialized? # The first two phases of the lifecycle of a puppet application are: # 1) Parse the command line options and handle any of them that are # registered, defined "global" puppet settings (mostly from defaults.rb). # 2) Parse the puppet config file(s). parse_global_options(args) parse_config_files @global_defaults_initialized = true end # This method is called during application bootstrapping. It is responsible for parsing all of the # command line options and initializing the settings accordingly. # # It will ignore options that are not defined in the global puppet settings list, because they may # be valid options for the specific application that we are about to launch... however, at this point # in the bootstrapping lifecycle, we don't yet know what that application is. def parse_global_options(args) # Create an option parser option_parser = PuppetOptionParser.new option_parser.ignore_invalid_options = true # Add all global options to it. self.optparse_addargs([]).each do |option| option_parser.on(*option) do |arg| opt, val = Puppet::Settings.clean_opt(option[0], arg) handlearg(opt, val) end end option_parser.on('--run_mode', "The effective 'run mode' of the application: master, agent, or user.", :REQUIRED) do |arg| Puppet.settings.preferred_run_mode = arg end option_parser.parse(args) # remove run_mode options from the arguments so that later parses don't think # it is an unknown option. while option_index = args.index('--run_mode') do args.delete_at option_index args.delete_at option_index end args.reject! { |arg| arg.start_with? '--run_mode=' } end private :parse_global_options # A utility method (public, is used by application.rb and perhaps elsewhere) that munges a command-line # option string into the format that Puppet.settings expects. (This mostly has to deal with handling the # "no-" prefix on flag/boolean options). # # @param [String] opt the command line option that we are munging # @param [String, TrueClass, FalseClass] val the value for the setting (as determined by the OptionParser) def self.clean_opt(opt, val) # rewrite --[no-]option to --no-option if that's what was given if opt =~ /\[no-\]/ and !val opt = opt.gsub(/\[no-\]/,'no-') end # otherwise remove the [no-] prefix to not confuse everybody opt = opt.gsub(/\[no-\]/, '') [opt, val] end def app_defaults_initialized? @app_defaults_initialized end def initialize_app_defaults(app_defaults) REQUIRED_APP_SETTINGS.each do |key| raise SettingsError, "missing required app default setting '#{key}'" unless app_defaults.has_key?(key) end app_defaults.each do |key, value| if key == :run_mode self.preferred_run_mode = value else @value_sets[:application_defaults].set(key, value) unsafe_flush_cache end end apply_metadata call_hooks_deferred_to_application_initialization issue_deprecations REQUIRED_APP_SETTINGS.each do |key| create_ancestors(Puppet[key]) end @app_defaults_initialized = true end # Create ancestor directories. # # @param dir [String] absolute path for a required application default directory # @api private def create_ancestors(dir) parent_dir = File.dirname(dir) if !File.exist?(parent_dir) FileUtils.mkdir_p(parent_dir) end end private :create_ancestors def call_hooks_deferred_to_application_initialization(options = {}) @hooks_to_call_on_application_initialization.each do |setting| begin setting.handle(self.value(setting.name)) rescue InterpolationError => err raise InterpolationError, err.message, err.backtrace unless options[:ignore_interpolation_dependency_errors] #swallow. We're not concerned if we can't call hooks because dependencies don't exist yet #we'll get another chance after application defaults are initialized end end end private :call_hooks_deferred_to_application_initialization # Return a value's description. def description(name) if obj = @config[name.to_sym] obj.desc else nil end end def_delegators :@config, :each, :each_pair, :each_key # Iterate over each section name. def eachsection yielded = [] @config.each_value do |object| section = object.section unless yielded.include? section yield section yielded << section end end end # Returns a given setting by name # @param name [Symbol] The name of the setting to fetch # @return [Puppet::Settings::BaseSetting] The setting object def setting(param) param = param.to_sym @config[param] end # Handle a command-line argument. def handlearg(opt, value = nil) @cache.clear if value.is_a?(FalseClass) value = "false" elsif value.is_a?(TrueClass) value = "true" end value &&= @translate[value] str = opt.sub(/^--/,'') bool = true newstr = str.sub(/^no-/, '') if newstr != str str = newstr bool = false end str = str.intern if @config[str].is_a?(Puppet::Settings::BooleanSetting) if value == "" or value.nil? value = bool end end if s = @config[str] @deprecated_settings_that_have_been_configured << s if s.completely_deprecated? end @value_sets[:cli].set(str, value) unsafe_flush_cache end def include?(name) name = name.intern if name.is_a? String @config.include?(name) end # Prints the contents of a config file with the available config settings, or it # prints a single value of a config setting. def print_config_options if Puppet::Util::Log.sendlevel?(:info) Puppet::Util::Log.newdestination(:console) message = (_("Using --configprint is deprecated. Use 'puppet config ' instead.")) Puppet.deprecation_warning(message) end env = value(:environment) val = value(:configprint) if val == "all" hash = {} each do |name, obj| val = value(name,env) val = val.inspect if val == "" hash[name] = val end hash.sort { |a,b| a[0].to_s <=> b[0].to_s }.each do |name, v| puts "#{name} = #{v}" end else val.split(/\s*,\s*/).sort.each do |v| if include?(v) #if there is only one value, just print it for back compatibility if v == val puts value(val,env) break end puts "#{v} = #{value(v,env)}" else puts "invalid setting: #{v}" return false end end end true end def generate_config puts to_config true end def generate_manifest puts to_manifest true end def print_configs return print_config_options if value(:configprint) != "" return generate_config if value(:genconfig) generate_manifest if value(:genmanifest) end def print_configs? (value(:configprint) != "" || value(:genconfig) || value(:genmanifest)) && true end # The currently configured run mode that is preferred for constructing the application configuration. def preferred_run_mode @preferred_run_mode_name || :user end # PRIVATE! This only exists because we need a hook to validate the run mode when it's being set, and # it should never, ever, ever, ever be called from outside of this file. # This method is also called when --run_mode MODE is used on the command line to set the default # # @param mode [String|Symbol] the name of the mode to have in effect # @api private def preferred_run_mode=(mode) mode = mode.to_s.downcase.intern raise ValidationError, "Invalid run mode '#{mode}'" unless [:master, :agent, :user].include?(mode) @preferred_run_mode_name = mode # Changing the run mode has far-reaching consequences. Flush any cached # settings so they will be re-generated. flush_cache mode end def parse_config(text, file = "text") begin data = @config_file_parser.parse_file(file, text, ALLOWED_SECTION_NAMES) rescue => detail Puppet.log_exception(detail, "Could not parse #{file}: #{detail}") return end # If we get here and don't have any data, we just return and don't muck with the current state of the world. return if data.nil? # If we get here then we have some data, so we need to clear out any # previous settings that may have come from config files. unsafe_clear(false, false) # Screen settings which have been deprecated and removed from puppet.conf # but are still valid on the command line and/or in environment.conf screen_non_puppet_conf_settings(data) # Make note of deprecated settings we will warn about later in initialization record_deprecations_from_puppet_conf(data) # And now we can repopulate with the values from our last parsing of the config files. @configuration_file = data # Determine our environment, if we have one. if @config[:environment] env = self.value(:environment).to_sym else env = NONE end # Call any hooks we should be calling. value_sets = value_sets_for(env, preferred_run_mode) @config.values.select(&:has_hook?).each do |setting| value_sets.each do |source| if source.include?(setting.name) # We still have to use value to retrieve the value, since # we want the fully interpolated value, not $vardir/lib or whatever. # This results in extra work, but so few of the settings # will have associated hooks that it ends up being less work this # way overall. if setting.call_hook_on_initialize? @hooks_to_call_on_application_initialization |= [ setting ] else setting.handle(ChainedValues.new( preferred_run_mode, env, value_sets, @config).interpolate(setting.name)) end break end end end call_hooks_deferred_to_application_initialization :ignore_interpolation_dependency_errors => true apply_metadata end # Parse the configuration file. Just provides thread safety. def parse_config_files file = which_configuration_file if Puppet::FileSystem.exist?(file) begin text = read_file(file) rescue => detail Puppet.log_exception(detail, "Could not load #{file}: #{detail}") return end else return end parse_config(text, file) end private :parse_config_files def main_config_file if explicit_config_file? return self[:config] else return File.join(Puppet::Util::RunMode[:master].conf_dir, config_file_name) end end private :main_config_file def user_config_file return File.join(Puppet::Util::RunMode[:user].conf_dir, config_file_name) end private :user_config_file # This method is here to get around some life-cycle issues. We need to be # able to determine the config file name before the settings / defaults are # fully loaded. However, we also need to respect any overrides of this value # that the user may have specified on the command line. # # The easiest way to do this is to attempt to read the setting, and if we # catch an error (meaning that it hasn't been set yet), we'll fall back to # the default value. def config_file_name begin return self[:config_file_name] if self[:config_file_name] rescue SettingsError # This just means that the setting wasn't explicitly set on the command line, so we will ignore it and # fall through to the default name. end return self.class.default_config_file_name end private :config_file_name def apply_metadata # We have to do it in the reverse of the search path, # because multiple sections could set the same value # and I'm too lazy to only set the metadata once. if @configuration_file searchpath(nil, preferred_run_mode).reverse_each do |source| if source.type == :section && section = @configuration_file.sections[source.name] apply_metadata_from_section(section) end end end end private :apply_metadata def apply_metadata_from_section(section) section.settings.each do |setting| if setting.has_metadata? && type = @config[setting.name] type.set_meta(setting.meta) end end end SETTING_TYPES = { :string => StringSetting, :file => FileSetting, :directory => DirectorySetting, :file_or_directory => FileOrDirectorySetting, :path => PathSetting, :boolean => BooleanSetting, :terminus => TerminusSetting, :duration => DurationSetting, :ttl => TTLSetting, :array => ArraySetting, :enum => EnumSetting, :symbolic_enum => SymbolicEnumSetting, :priority => PrioritySetting, :autosign => AutosignSetting, :server_list => ServerListSetting, :certificate_revocation => CertificateRevocationSetting } # Create a new setting. The value is passed in because it's used to determine # what kind of setting we're creating, but the value itself might be either # a default or a value, so we can't actually assign it. # # See #define_settings for documentation on the legal values for the ":type" option. def newsetting(hash) klass = nil hash[:section] = hash[:section].to_sym if hash[:section] if type = hash[:type] unless klass = SETTING_TYPES[type] raise ArgumentError, _("Invalid setting type '%{type}'") % { type: type } end hash.delete(:type) else # The only implicit typing we still do for settings is to fall back to "String" type if they didn't explicitly # specify a type. Personally I'd like to get rid of this too, and make the "type" option mandatory... but # there was a little resistance to taking things quite that far for now. --cprice 2012-03-19 klass = StringSetting end hash[:settings] = self setting = klass.new(hash) setting end # This has to be private, because it doesn't add the settings to @config private :newsetting # Iterate across all of the objects in a given section. def persection(section) section = section.to_sym self.each { |name, obj| if obj.section == section yield obj end } end # Reparse our config file, if necessary. def reparse_config_files if files if filename = any_files_changed? Puppet.notice "Config file #{filename} changed; triggering re-parse of all config files." parse_config_files reuse end end end def files return @files if @files @files = [] [main_config_file, user_config_file].each do |path| if Puppet::FileSystem.exist?(path) @files << Puppet::Util::WatchedFile.new(path) end end @files end private :files # Checks to see if any of the config files have been modified # @return the filename of the first file that is found to have changed, or # nil if no files have changed def any_files_changed? files.each do |file| return file.to_str if file.changed? end nil end private :any_files_changed? def reuse return unless defined?(@used) new = @used @used = [] self.use(*new) end class SearchPathElement < Struct.new(:name, :type); end # The order in which to search for values, without defaults. # # @param environment [String,Symbol] symbolic reference to an environment name # @param run_mode [Symbol] symbolic reference to a Puppet run mode # @return [Array] # @api private def configsearchpath(environment = nil, run_mode = preferred_run_mode) searchpath = [ SearchPathElement.new(:memory, :values), SearchPathElement.new(:cli, :values), ] searchpath << SearchPathElement.new(environment.intern, :environment) if environment searchpath << SearchPathElement.new(run_mode, :section) if run_mode searchpath << SearchPathElement.new(:main, :section) end # The order in which to search for values. # # @param environment [String,Symbol] symbolic reference to an environment name # @param run_mode [Symbol] symbolic reference to a Puppet run mode # @return [Array] # @api private def searchpath(environment = nil, run_mode = preferred_run_mode) searchpath = configsearchpath(environment, run_mode) searchpath << SearchPathElement.new(:application_defaults, :values) searchpath << SearchPathElement.new(:overridden_defaults, :values) end def service_user_available? return @service_user_available if defined?(@service_user_available) if self[:user] user = Puppet::Type.type(:user).new :name => self[:user], :audit => :ensure @service_user_available = user.exists? else @service_user_available = false end end def service_group_available? return @service_group_available if defined?(@service_group_available) if self[:group] group = Puppet::Type.type(:group).new :name => self[:group], :audit => :ensure @service_group_available = group.exists? else @service_group_available = false end end # Allow later inspection to determine if the setting was set on the # command line, or through some other code path. Used for the # `dns_alt_names` option during cert generate. --daniel 2011-10-18 def set_by_cli?(param) param = param.to_sym !@value_sets[:cli].lookup(param).nil? end # Get values from a search path entry. # @api private def searchpath_values(source) case source.type when :values @value_sets[source.name] when :section if @configuration_file && section = @configuration_file.sections[source.name] ValuesFromSection.new(source.name, section) end when :environment ValuesFromEnvironmentConf.new(source.name) else raise Puppet::DevError, _("Unknown searchpath case: %{source_type} for the %{source} settings path element.") % { source_type: source.type, source: source} end end # Allow later inspection to determine if the setting was set by user # config, rather than a default setting. def set_by_config?(param, environment = nil, run_mode = preferred_run_mode) param = param.to_sym configsearchpath(environment, run_mode).any? do |source| if vals = searchpath_values(source) vals.lookup(param) end end end # Patches the value for a param in a section. # This method is required to support the use case of unifying --dns-alt-names and # --dns_alt_names in the certificate face. Ideally this should be cleaned up. # See PUP-3684 for more information. # For regular use of setting a value, the method `[]=` should be used. # @api private # def patch_value(param, value, type) if @value_sets[type] @value_sets[type].set(param, value) unsafe_flush_cache end end # Define a group of settings. # # @param [Symbol] section a symbol to use for grouping multiple settings together into a conceptual unit. This value # (and the conceptual separation) is not used very often; the main place where it will have a potential impact # is when code calls Settings#use method. See docs on that method for further details, but basically that method # just attempts to do any preparation that may be necessary before code attempts to leverage the value of a particular # setting. This has the most impact for file/directory settings, where #use will attempt to "ensure" those # files / directories. # @param [Hash[Hash]] defs the settings to be defined. This argument is a hash of hashes; each key should be a symbol, # which is basically the name of the setting that you are defining. The value should be another hash that specifies # the parameters for the particular setting. Legal values include: # [:default] => not required; this is the value for the setting if no other value is specified (via cli, config file, etc.) # For string settings this may include "variables", demarcated with $ or ${} which will be interpolated with values of other settings. # The default value may also be a Proc that will be called only once to evaluate the default when the setting's value is retrieved. # [:desc] => required; a description of the setting, used in documentation / help generation # [:type] => not required, but highly encouraged! This specifies the data type that the setting represents. If # you do not specify it, it will default to "string". Legal values include: # :string - A generic string setting # :boolean - A boolean setting; values are expected to be "true" or "false" # :file - A (single) file path; puppet may attempt to create this file depending on how the settings are used. This type # also supports additional options such as "mode", "owner", "group" # :directory - A (single) directory path; puppet may attempt to create this file depending on how the settings are used. This type # also supports additional options such as "mode", "owner", "group" # :path - This is intended to be used for settings whose value can contain multiple directory paths, represented # as strings separated by the system path separator (e.g. system path, module path, etc.). # [:mode] => an (optional) octal value to be used as the permissions/mode for :file and :directory settings # [:owner] => optional owner username/uid for :file and :directory settings # [:group] => optional group name/gid for :file and :directory settings # def define_settings(section, defs) section = section.to_sym call = [] defs.each do |name, hash| raise ArgumentError, _("setting definition for '%{name}' is not a hash!") % { name: name } unless hash.is_a? Hash name = name.to_sym hash[:name] = name hash[:section] = section raise ArgumentError, _("Setting %{name} is already defined") % { name: name } if @config.include?(name) tryconfig = newsetting(hash) if short = tryconfig.short if other = @shortnames[short] raise ArgumentError, _("Setting %{name} is already using short name '%{short}'") % { name: other.name, short: short } end @shortnames[short] = tryconfig end @config[name] = tryconfig # Collect the settings that need to have their hooks called immediately. # We have to collect them so that we can be sure we're fully initialized before # the hook is called. if tryconfig.has_hook? if tryconfig.call_hook_on_define? call << tryconfig elsif tryconfig.call_hook_on_initialize? @hooks_to_call_on_application_initialization |= [ tryconfig ] end end @deprecated_setting_names << name if tryconfig.deprecated? end call.each do |setting| setting.handle(self.value(setting.name)) end end # Convert the settings we manage into a catalog full of resources that model those settings. def to_catalog(*sections) sections = nil if sections.empty? catalog = Puppet::Resource::Catalog.new("Settings", Puppet::Node::Environment::NONE) @config.keys.find_all { |key| @config[key].is_a?(FileSetting) }.each do |key| file = @config[key] next if file.value.nil? next unless (sections.nil? or sections.include?(file.section)) next unless resource = file.to_resource next if catalog.resource(resource.ref) Puppet.debug {"Using settings: adding file resource '#{key}': '#{resource.inspect}'"} catalog.add_resource(resource) end add_user_resources(catalog, sections) add_environment_resources(catalog, sections) catalog end # Convert our list of config settings into a configuration file. def to_config str = %{The configuration file for #{Puppet.run_mode.name}. Note that this file is likely to have unused settings in it; any setting that's valid anywhere in Puppet can be in any config file, even if it's not used. Every section can specify three special parameters: owner, group, and mode. These parameters affect the required permissions of any files specified after their specification. Puppet will sometimes use these parameters to check its own configured state, so they can be used to make Puppet a bit more self-managing. The file format supports octothorpe-commented lines, but not partial-line comments. Generated on #{Time.now}. }.gsub(/^/, "# ") # Add a section heading that matches our name. str += "[#{preferred_run_mode}]\n" eachsection do |section| persection(section) do |obj| str += obj.to_config + "\n" unless obj.name == :genconfig end end return str end # Convert to a parseable manifest def to_manifest catalog = to_catalog catalog.resource_refs.collect do |ref| catalog.resource(ref).to_manifest end.join("\n\n") end # Create the necessary objects to use a section. This is idempotent; # you can 'use' a section as many times as you want. def use(*sections) sections = sections.collect { |s| s.to_sym } sections = sections.reject { |s| @used.include?(s) } return if sections.empty? Puppet.debug("Applying settings catalog for sections #{sections.join(', ')}") begin catalog = to_catalog(*sections).to_ral rescue => detail Puppet.log_and_raise(detail, "Could not create resources for managing Puppet's files and directories in sections #{sections.inspect}: #{detail}") end catalog.host_config = false catalog.apply do |transaction| if transaction.any_failed? report = transaction.report status_failures = report.resource_statuses.values.select { |r| r.failed? } status_fail_msg = status_failures. collect(&:events). flatten. select { |event| event.status == 'failure' }. collect { |event| "#{event.resource}: #{event.message}" }.join("; ") raise "Got #{status_failures.length} failure(s) while initializing: #{status_fail_msg}" end end sections.each { |s| @used << s } @used.uniq! end def valid?(param) param = param.to_sym @config.has_key?(param) end # Retrieve an object that can be used for looking up values of configuration # settings. # # @param environment [Symbol] The name of the environment in which to lookup # @param section [Symbol] The name of the configuration section in which to lookup # @return [Puppet::Settings::ChainedValues] An object to perform lookups # @api public def values(environment, section) @values[environment][section] ||= ChainedValues.new( section, environment, value_sets_for(environment, section), @config) end # Find the correct value using our search path. # # @param param [String, Symbol] The value to look up # @param environment [String, Symbol] The environment to check for the value # @param bypass_interpolation [true, false] Whether to skip interpolation # # @return [Object] The looked up value # # @raise [InterpolationError] def value(param, environment = nil, bypass_interpolation = false) environment &&= environment.to_sym value_sym(param.to_sym, environment, bypass_interpolation) end # Find the correct value using symbols and our search path. # # @param param [Symbol] The value to look up # @param environment [Symbol] The environment to check for the value # @param bypass_interpolation [true, false] Whether to skip interpolation # # @return [Object] The looked up value # # @raise [InterpolationError] def value_sym(param, environment = nil, bypass_interpolation = false) # Check the cache first. It needs to be a per-environment # cache so that we don't spread values from one env # to another. cached_env = @cache[environment || NONE] # Avoid two lookups in cache_env unless val is nil. When it is, it's important # to check if the key is included so that further processing (that will result # in nil again) is avoided. val = cached_env[param] return val if !val.nil? || cached_env.include?(param) # Short circuit to nil for undefined settings. return nil unless @config.include?(param) vals = values(environment, preferred_run_mode) val = bypass_interpolation ? vals.lookup(param) : vals.interpolate(param) cached_env[param] = val val end ## # (#15337) All of the logic to determine the configuration file to use # should be centralized into this method. The simplified approach is: # # 1. If there is an explicit configuration file, use that. (--confdir or # --config) # 2. If we're running as a root process, use the system puppet.conf # (usually /etc/puppetlabs/puppet/puppet.conf) # 3. Otherwise, use the user puppet.conf (usually ~/.puppetlabs/etc/puppet/puppet.conf) # # @api private # @todo this code duplicates {Puppet::Util::RunMode#which_dir} as described # in {https://projects.puppetlabs.com/issues/16637 #16637} def which_configuration_file if explicit_config_file? or Puppet.features.root? then return main_config_file else return user_config_file end end # This method just turns a file into a new ConfigFile::Conf instance # @param file [String] absolute path to the configuration file # @return [Puppet::Settings::ConfigFile::Conf] # @api private def parse_file(file, allowed_sections = []) @config_file_parser.parse_file(file, read_file(file), allowed_sections) end private DEPRECATION_REFS = { # intentionally empty. This could be repopulated if we deprecate more settings # and have reference links to associate with them }.freeze def screen_non_puppet_conf_settings(puppet_conf) puppet_conf.sections.values.each do |section| forbidden = section.settings.select { |setting| Puppet::Settings::EnvironmentConf::ENVIRONMENT_CONF_ONLY_SETTINGS.include?(setting.name) } raise(SettingsError, "Cannot set #{forbidden.map { |s| s.name }.join(", ")} settings in puppet.conf") if !forbidden.empty? end end # Record that we want to issue a deprecation warning later in the application # initialization cycle when we have settings bootstrapped to the point where # we can read the Puppet[:disable_warnings] setting. # # We are only recording warnings applicable to settings set in puppet.conf # itself. def record_deprecations_from_puppet_conf(puppet_conf) puppet_conf.sections.values.each do |section| section.settings.each do |conf_setting| if setting = self.setting(conf_setting.name) @deprecated_settings_that_have_been_configured << setting if setting.deprecated? end end end end def issue_deprecations @deprecated_settings_that_have_been_configured.each do |setting| issue_deprecation_warning(setting) end end def issue_deprecation_warning(setting, msg = nil) name = setting.name ref = DEPRECATION_REFS.find { |params,reference| params.include?(name) } ref = ref[1] if ref case when msg msg << " #{ref}" if ref Puppet.deprecation_warning(msg) when setting.completely_deprecated? message = _("Setting %{name} is deprecated.") % { name: name } message += " #{ref}" Puppet.deprecation_warning(message, "setting-#{name}") when setting.allowed_on_commandline? #TRANSLATORS 'puppet.conf' is a file name and should not be translated message = _("Setting %{name} is deprecated in puppet.conf.") % { name: name } message += " #{ref}" Puppet.deprecation_warning(message, "puppet-conf-setting-#{name}") end end def add_environment_resources(catalog, sections) path = self[:environmentpath] envdir = path.split(File::PATH_SEPARATOR).first if path configured_environment = self[:environment] if configured_environment == "production" && envdir && Puppet::FileSystem.exist?(envdir) configured_environment_path = File.join(envdir, configured_environment) if !Puppet::FileSystem.symlink?(configured_environment_path) parameters = { :ensure => 'directory' } unless Puppet::FileSystem.exist?(configured_environment_path) parameters.merge!(:mode => '0750') if Puppet.features.root? parameters.merge!(:owner => Puppet[:user]) if service_user_available? parameters.merge!(:group => Puppet[:group]) if service_group_available? end end catalog.add_resource(Puppet::Resource.new(:file, configured_environment_path, :parameters => parameters)) end end end def add_user_resources(catalog, sections) return unless Puppet.features.root? return if Puppet.features.microsoft_windows? return unless self[:mkusers] @config.each do |name, setting| next unless setting.respond_to?(:owner) next unless sections.nil? or sections.include?(setting.section) if user = setting.owner and user != "root" and catalog.resource(:user, user).nil? resource = Puppet::Resource.new(:user, user, :parameters => {:ensure => :present}) resource[:gid] = self[:group] if self[:group] catalog.add_resource resource end if group = setting.group and ! %w{root wheel}.include?(group) and catalog.resource(:group, group).nil? catalog.add_resource Puppet::Resource.new(:group, group, :parameters => {:ensure => :present}) end end end # Yield each search source in turn. def value_sets_for(environment, mode) searchpath(environment, mode).collect { |source| searchpath_values(source) }.compact end # Read the file in. # @api private def read_file(file) return Puppet::FileSystem.read(file, :encoding => 'utf-8') end # Private method for internal test use only; allows to do a comprehensive clear of all settings between tests. # # @return nil def clear_everything_for_tests() unsafe_clear(true, true) @configuration_file = nil @global_defaults_initialized = false @app_defaults_initialized = false end private :clear_everything_for_tests def explicit_config_file? # Figure out if the user has provided an explicit configuration file. If # so, return the path to the file, if not return nil. # # The easiest way to determine whether an explicit one has been specified # is to simply attempt to evaluate the value of ":config". This will # obviously be successful if they've passed an explicit value for :config, # but it will also result in successful interpolation if they've only # passed an explicit value for :confdir. # # If they've specified neither, then the interpolation will fail and we'll # get an exception. # begin return true if self[:config] rescue InterpolationError # This means we failed to interpolate, which means that they didn't # explicitly specify either :config or :confdir... so we'll fall out to # the default value. return false end end private :explicit_config_file? # Lookup configuration setting value through a chain of different value sources. # # @api public class ChainedValues ENVIRONMENT_SETTING = "environment".freeze ENVIRONMENT_INTERPOLATION_ALLOWED = ['config_version'].freeze # @see Puppet::Settings.values # @api private def initialize(mode, environment, value_sets, defaults) @mode = mode @environment = environment @value_sets = value_sets @defaults = defaults end # Lookup the uninterpolated value. # # @param name [Symbol] The configuration setting name to look up # @return [Object] The configuration setting value or nil if the setting is not known # @api public def lookup(name) set = @value_sets.find do |value_set| value_set.include?(name) end if set value = set.lookup(name) if !value.nil? return value end end @defaults[name].default end # Lookup the interpolated value. All instances of `$name` in the value will # be replaced by performing a lookup of `name` and substituting the text # for `$name` in the original value. This interpolation is only performed # if the looked up value is a String. # # @param name [Symbol] The configuration setting name to look up # @return [Object] The configuration setting value or nil if the setting is not known # @api public def interpolate(name) setting = @defaults[name] return nil unless setting lookup_and_convert(name) do |val| setting.munge(val) end end def print(name) setting = @defaults[name] return nil unless setting lookup_and_convert(name) do |val| setting.print(val) end end private def lookup_and_convert(name, &block) val = lookup(name) # if we interpolate code, all hell breaks loose. if name == :code val else # Convert it if necessary begin val = convert(val, name) rescue InterpolationError => err # This happens because we don't have access to the param name when the # exception is originally raised, but we want it in the message raise InterpolationError, _("Error converting value for param '%{name}': %{detail}") % { name: name, detail: err }, err.backtrace end yield val end end def convert(value, setting_name) case value when nil nil when String failed_environment_interpolation = false interpolated_value = value.gsub(/\$(\w+)|\$\{(\w+)\}/) do |expression| varname = $2 || $1 interpolated_expression = if varname != ENVIRONMENT_SETTING || ok_to_interpolate_environment(setting_name) if varname == ENVIRONMENT_SETTING && @environment @environment elsif varname == "run_mode" @mode elsif !(pval = interpolate(varname.to_sym)).nil? pval else raise InterpolationError, _("Could not find value for %{expression}") % { expression: expression } end else failed_environment_interpolation = true expression end interpolated_expression end if failed_environment_interpolation #TRANSLATORS '$environment' is a Puppet specific variable and should not be translated Puppet.warning(_("You cannot interpolate $environment within '%{setting_name}' when using directory environments.") % { setting_name: setting_name } + ' ' + _("Its value will remain %{value}.") % { value: interpolated_value }) end interpolated_value else value end end def ok_to_interpolate_environment(setting_name) ENVIRONMENT_INTERPOLATION_ALLOWED.include?(setting_name.to_s) end end class Values extend Forwardable attr_reader :name def initialize(name, defaults) @name = name @values = {} @defaults = defaults end def_delegator :@values, :include? def_delegator :@values, :[], :lookup def set(name, value) default = @defaults[name] if !default raise ArgumentError, _("Attempt to assign a value to unknown setting %{name}") % { name: name.inspect } end # This little exception-handling dance ensures that a hook is # able to check whether a value for itself has been explicitly # set, while still preserving the existing value if the hook # throws (as was existing behavior) old_value = @values[name] @values[name] = value begin if default.has_hook? default.handle(value) end rescue Exception => e @values[name] = old_value raise e end end def inspect %Q{<#{self.class}:#{self.object_id} @name="#{@name}" @values="#{@values}">} end end class ValuesFromSection attr_reader :name def initialize(name, section) @name = name @section = section end def include?(name) !@section.setting(name).nil? end def lookup(name) setting = @section.setting(name) if setting setting.value end end def inspect %Q{<#{self.class}:#{self.object_id} @name="#{@name}" @section="#{@section}">} end end # @api private class ValuesFromEnvironmentConf def initialize(environment_name) @environment_name = environment_name end def name @environment_name end def include?(name) if Puppet::Settings::EnvironmentConf::VALID_SETTINGS.include?(name) && conf return true end false end def lookup(name) return nil unless Puppet::Settings::EnvironmentConf::VALID_SETTINGS.include?(name) conf.send(name) if conf end def conf @conf ||= if environments = Puppet.lookup(:environments) { nil } environments.get_conf(@environment_name) end end def inspect %Q{<#{self.class}:#{self.object_id} @environment_name="#{@environment_name}" @conf="#{@conf}">} end end end puppet-5.5.10/lib/puppet/ssl.rb0000644005276200011600000000053413417161721016225 0ustar jenkinsjenkins# Just to make the constants work out. require 'puppet' require 'openssl' module Puppet::SSL # :nodoc: CA_NAME = "ca" require 'puppet/ssl/configuration' require 'puppet/ssl/host' require 'puppet/ssl/oids' require 'puppet/ssl/validator' require 'puppet/ssl/validator/no_validator' require 'puppet/ssl/validator/default_validator' end puppet-5.5.10/lib/puppet/type.rb0000644005276200011600000031006513417161722016411 0ustar jenkinsjenkins# -*- coding: utf-8 -*- require 'puppet' require 'puppet/util/log' require 'puppet/util/metric' require 'puppet/property' require 'puppet/parameter' require 'puppet/util' require 'puppet/util/autoload' require 'puppet/metatype/manager' require 'puppet/util/errors' require 'puppet/util/logging' require 'puppet/util/tagging' # see the bottom of the file for the rest of the inclusions module Puppet # The base class for all Puppet types. # # A type describes: #-- # * **Attributes** - properties, parameters, and meta-parameters are different types of attributes of a type. # * **Properties** - these are the properties of the managed resource (attributes of the entity being managed; like # a file's owner, group and mode). A property describes two states; the 'is' (current state) and the 'should' (wanted # state). # * **Ensurable** - a set of traits that control the lifecycle (create, remove, etc.) of a managed entity. # There is a default set of operations associated with being _ensurable_, but this can be changed. # * **Name/Identity** - one property is the name/identity of a resource, the _namevar_ that uniquely identifies # one instance of a type from all others. # * **Parameters** - additional attributes of the type (that does not directly related to an instance of the managed # resource; if an operation is recursive or not, where to look for things, etc.). A Parameter (in contrast to Property) # has one current value where a Property has two (current-state and wanted-state). # * **Meta-Parameters** - parameters that are available across all types. A meta-parameter typically has # additional semantics; like the `require` meta-parameter. A new type typically does not add new meta-parameters, # but you need to be aware of their existence so you do not inadvertently shadow an existing meta-parameters. # * **Parent** - a type can have a super type (that it inherits from). # * **Validation** - If not just a basic data type, or an enumeration of symbolic values, it is possible to provide # validation logic for a type, properties and parameters. # * **Munging** - munging/unmunging is the process of turning a value in external representation (as used # by a provider) into an internal representation and vice versa. A Type supports adding custom logic for these. # * **Auto Requirements** - a type can specify automatic relationships to resources to ensure that if they are being # managed, they will be processed before this type. # * **Providers** - a provider is an implementation of a type's behavior - the management of a resource in the # system being managed. A provider is often platform specific and is selected at runtime based on # criteria/predicates specified in the configured providers. See {Puppet::Provider} for details. # * **Device Support** - A type has some support for being applied to a device; i.e. something that is managed # by running logic external to the device itself. There are several methods that deals with type # applicability for these special cases such as {apply_to_device}. # # Additional Concepts: # -- # * **Resource-type** - A _resource type_ is a term used to denote the type of a resource; internally a resource # is really an instance of a Ruby class i.e. {Puppet::Resource} which defines its behavior as "resource data". # Conceptually however, a resource is an instance of a subclass of Type (e.g. File), where such a class describes # its interface (what can be said/what is known about a resource of this type), # * **Managed Entity** - This is not a term in general use, but is used here when there is a need to make # a distinction between a resource (a description of what/how something should be managed), and what it is # managing (a file in the file system). The term _managed entity_ is a reference to the "file in the file system" # * **Isomorphism** - the quality of being _isomorphic_ means that two resource instances with the same name # refers to the same managed entity. Or put differently; _an isomorphic name is the identity of a resource_. # As an example, `exec` resources (that executes some command) have the command (i.e. the command line string) as # their name, and these resources are said to be non-isomorphic. # # @note The Type class deals with multiple concerns; some methods provide an internal DSL for convenient definition # of types, other methods deal with various aspects while running; wiring up a resource (expressed in Puppet DSL) # with its _resource type_ (i.e. an instance of Type) to enable validation, transformation of values # (munge/unmunge), etc. Lastly, Type is also responsible for dealing with Providers; the concrete implementations # of the behavior that constitutes how a particular Type behaves on a particular type of system (e.g. how # commands are executed on a flavor of Linux, on Windows, etc.). This means that as you are reading through the # documentation of this class, you will be switching between these concepts, as well as switching between # the conceptual level "a resource is an instance of a resource-type" and the actual implementation classes # (Type, Resource, Provider, and various utility and helper classes). # # @api public # # class Type extend Puppet::CompilableResourceType include Puppet::Util include Puppet::Util::Errors include Puppet::Util::Logging include Puppet::Util::Tagging # Comparing type instances. include Comparable # Compares this type against the given _other_ (type) and returns -1, 0, or +1 depending on the order. # @param other [Object] the object to compare against (produces nil, if not kind of Type} # @return [-1, 0, +1, nil] produces -1 if this type is before the given _other_ type, 0 if equals, and 1 if after. # Returns nil, if the given _other_ is not a kind of Type. # @see Comparable # def <=>(other) # Order is only maintained against other types, not arbitrary objects. # The natural order is based on the reference name used when comparing return nil unless other.is_a?(Puppet::CompilableResourceType) || other.class.is_a?(Puppet::CompilableResourceType) # against other type instances. self.ref <=> other.ref end # Code related to resource type attributes. class << self include Puppet::Util::ClassGen include Puppet::Util::Warnings # @return [Array] The list of declared properties for the resource type. # The returned lists contains instances if Puppet::Property or its subclasses. attr_reader :properties end # Allow declaring that a type is actually a capability class << self attr_accessor :is_capability def is_capability? c = is_capability c.nil? ? false : c end end # Returns whether this type represents an application instance; since # only defined types, i.e., instances of Puppet::Resource::Type can # represent application instances, this implementation always returns # +false+. Having this method though makes code checking whether a # resource is an application instance simpler def self.application? false end # Returns all the attribute names of the type in the appropriate order. # The {key_attributes} come first, then the {provider}, then the {properties}, and finally # the {parameters} and {metaparams}, # all in the order they were specified in the respective files. # @return [Array] all type attribute names in a defined order. # def self.allattrs key_attributes | (parameters & [:provider]) | properties.collect { |property| property.name } | parameters | metaparams end # Returns the class associated with the given attribute name. # @param name [String] the name of the attribute to obtain the class for # @return [Class, nil] the class for the given attribute, or nil if the name does not refer to an existing attribute # def self.attrclass(name) @attrclasses ||= {} # We cache the value, since this method gets called such a huge number # of times (as in, hundreds of thousands in a given run). unless @attrclasses.include?(name) @attrclasses[name] = case self.attrtype(name) when :property; @validproperties[name] when :meta; @@metaparamhash[name] when :param; @paramhash[name] end end @attrclasses[name] end # Returns the attribute type (`:property`, `;param`, `:meta`). # @comment What type of parameter are we dealing with? Cache the results, because # this method gets called so many times. # @return [Symbol] a symbol describing the type of attribute (`:property`, `;param`, `:meta`) # def self.attrtype(attr) @attrtypes ||= {} unless @attrtypes.include?(attr) @attrtypes[attr] = case when @validproperties.include?(attr); :property when @paramhash.include?(attr); :param when @@metaparamhash.include?(attr); :meta end end @attrtypes[attr] end # Provides iteration over meta-parameters. # @yieldparam p [Puppet::Parameter] each meta parameter # @return [void] # def self.eachmetaparam @@metaparams.each { |p| yield p.name } end # Creates a new `ensure` property with configured default values or with configuration by an optional block. # This method is a convenience method for creating a property `ensure` with default accepted values. # If no block is specified, the new `ensure` property will accept the default symbolic # values `:present`, and `:absent` - see {Puppet::Property::Ensure}. # If something else is wanted, pass a block and make calls to {Puppet::Property.newvalue} from this block # to define each possible value. If a block is passed, the defaults are not automatically added to the set of # valid values. # # @note This method will be automatically called without a block if the type implements the methods # specified by {ensurable?}. It is recommended to always call this method and not rely on this automatic # specification to clearly state that the type is ensurable. # # @overload ensurable() # @overload ensurable({|| ... }) # @yield [ ] A block evaluated in scope of the new Parameter # @yieldreturn [void] # @return [void] # @dsl type # @api public # def self.ensurable(&block) if block_given? self.newproperty(:ensure, :parent => Puppet::Property::Ensure, &block) else self.newproperty(:ensure, :parent => Puppet::Property::Ensure) do self.defaultvalues end end end # Returns true if the type implements the default behavior expected by being _ensurable_ "by default". # A type is _ensurable_ by default if it responds to `:exists`, `:create`, and `:destroy`. # If a type implements these methods and have not already specified that it is _ensurable_, it will be # made so with the defaults specified in {ensurable}. # @return [Boolean] whether the type is _ensurable_ or not. # def self.ensurable? # If the class has all three of these methods defined, then it's # ensurable. [:exists?, :create, :destroy].all? { |method| self.public_method_defined?(method) } end # @comment These `apply_to` methods are horrible. They should really be implemented # as part of the usual system of constraints that apply to a type and # provider pair, but were implemented as a separate shadow system. # # @comment We should rip them out in favour of a real constraint pattern around the # target device - whatever that looks like - and not have this additional # magic here. --daniel 2012-03-08 # # Makes this type applicable to `:device`. # @return [Symbol] Returns `:device` # @api private # def self.apply_to_device @apply_to = :device end # Makes this type applicable to `:host`. # @return [Symbol] Returns `:host` # @api private # def self.apply_to_host @apply_to = :host end # Makes this type applicable to `:both` (i.e. `:host` and `:device`). # @return [Symbol] Returns `:both` # @api private # def self.apply_to_all @apply_to = :both end # Makes this type apply to `:host` if not already applied to something else. # @return [Symbol] a `:device`, `:host`, or `:both` enumeration # @api private def self.apply_to @apply_to ||= :host end # Returns true if this type is applicable to the given target. # @param target [Symbol] should be :device, :host or :target, if anything else, :host is enforced # @return [Boolean] true # @api private # def self.can_apply_to(target) [ target == :device ? :device : :host, :both ].include?(apply_to) end # Processes the options for a named parameter. # @param name [String] the name of a parameter # @param options [Hash] a hash of options # @option options [Boolean] :boolean if option set to true, an access method on the form _name_? is added for the param # @return [void] # def self.handle_param_options(name, options) # If it's a boolean parameter, create a method to test the value easily if options[:boolean] define_method(name.to_s + "?") do val = self[name] if val == :true or val == true return true end end end end # Is the given parameter a meta-parameter? # @return [Boolean] true if the given parameter is a meta-parameter. # def self.metaparam?(param) @@metaparamhash.include?(param.intern) end # Returns the meta-parameter class associated with the given meta-parameter name. # Accepts a `nil` name, and return nil. # @param name [String, nil] the name of a meta-parameter # @return [Class,nil] the class for the given meta-parameter, or `nil` if no such meta-parameter exists, (or if # the given meta-parameter name is `nil`. # def self.metaparamclass(name) return nil if name.nil? @@metaparamhash[name.intern] end # Returns all meta-parameter names. # @return [Array] all meta-parameter names # def self.metaparams @@metaparams.collect { |param| param.name } end # Returns the documentation for a given meta-parameter of this type. # @param metaparam [Puppet::Parameter] the meta-parameter to get documentation for. # @return [String] the documentation associated with the given meta-parameter, or nil of no such documentation # exists. # @raise if the given metaparam is not a meta-parameter in this type # def self.metaparamdoc(metaparam) @@metaparamhash[metaparam].doc end # Creates a new meta-parameter. # This creates a new meta-parameter that is added to this and all inheriting types. # @param name [Symbol] the name of the parameter # @param options [Hash] a hash with options. # @option options [Class] :parent (Puppet::Parameter) the super class of this parameter # @option options [Hash{String => Object}] :attributes a hash that is applied to the generated class # by calling setter methods corresponding to this hash's keys/value pairs. This is done before the given # block is evaluated. # @option options [Boolean] :boolean (false) specifies if this is a boolean parameter # @option options [Boolean] :namevar (false) specifies if this parameter is the namevar # @option options [Symbol, Array] :required_features specifies required provider features by name # @return [Class] the created parameter # @yield [ ] a required block that is evaluated in the scope of the new meta-parameter # @api public # @dsl type # @todo Verify that this description is ok # def self.newmetaparam(name, options = {}, &block) @@metaparams ||= [] @@metaparamhash ||= {} name = name.intern param = genclass( name, :parent => options[:parent] || Puppet::Parameter, :prefix => "MetaParam", :hash => @@metaparamhash, :array => @@metaparams, :attributes => options[:attributes], &block ) # Grr. param.required_features = options[:required_features] if options[:required_features] handle_param_options(name, options) param.metaparam = true param end # Returns the list of parameters that comprise the composite key / "uniqueness key". # All parameters that return true from #isnamevar? or is named `:name` are included in the returned result. # @see uniqueness_key # @return [Array] WARNING: this return type is uncertain def self.key_attribute_parameters @key_attribute_parameters ||= ( @parameters.find_all { |param| param.isnamevar? or param.name == :name } ) end # Returns cached {key_attribute_parameters} names. # Key attributes are properties and parameters that comprise a composite key # or "uniqueness key". # @return [Array] cached key_attribute names # def self.key_attributes # This is a cache miss around 0.05 percent of the time. --daniel 2012-07-17 @key_attributes_cache ||= key_attribute_parameters.collect { |p| p.name } end # Returns a mapping from the title string to setting of attribute values. # This default implementation provides a mapping of title to the one and only _namevar_ present # in the type's definition. # @note Advanced: some logic requires this mapping to be done differently, using a different # validation/pattern, breaking up the title # into several parts assigning each to an individual attribute, or even use a composite identity where # all namevars are seen as part of the unique identity (such computation is done by the {#uniqueness} method. # These advanced options are rarely used (only one of the built in puppet types use this, and then only # a small part of the available functionality), and the support for these advanced mappings is not # implemented in a straight forward way. For these reasons, this method has been marked as private). # # @raise [Puppet::DevError] if there is no title pattern and there are two or more key attributes # @return [Array>>>, nil] a structure with a regexp and the first key_attribute ??? # @comment This wonderful piece of logic creates a structure used by Resource.parse_title which # has the capability to assign parts of the title to one or more attributes; It looks like an implementation # of a composite identity key (all parts of the key_attributes array are in the key). This can also # be seen in the method uniqueness_key. # The implementation in this method simply assigns the title to the one and only namevar (which is name # or a variable marked as namevar). # If there are multiple namevars (any in addition to :name?) then this method MUST be implemented # as it raises an exception if there is more than 1. Note that in puppet, it is only File that uses this # to create a different pattern for assigning to the :path attribute # This requires further digging. # The entire construct is somewhat strange, since resource checks if the method "title_patterns" is # implemented (it seems it always is) - why take this more expensive regexp mathching route for all # other types? # @api private # def self.title_patterns case key_attributes.length when 0; [] when 1; [ [ /(.*)/m, [ [key_attributes.first] ] ] ] else raise Puppet::DevError, _("you must specify title patterns when there are two or more key attributes") end end # Produces a resource's _uniqueness_key_ (or composite key). # This key is an array of all key attributes' values. Each distinct tuple must be unique for each resource type. # @see key_attributes # @return [Object] an object that is a _uniqueness_key_ for this object # def uniqueness_key self.class.key_attributes.sort_by { |attribute_name| attribute_name.to_s }.map{ |attribute_name| self[attribute_name] } end # Creates a new parameter. # @param name [Symbol] the name of the parameter # @param options [Hash] a hash with options. # @option options [Class] :parent (Puppet::Parameter) the super class of this parameter # @option options [Hash{String => Object}] :attributes a hash that is applied to the generated class # by calling setter methods corresponding to this hash's keys/value pairs. This is done before the given # block is evaluated. # @option options [Boolean] :boolean (false) specifies if this is a boolean parameter # @option options [Boolean] :namevar (false) specifies if this parameter is the namevar # @option options [Symbol, Array] :required_features specifies required provider features by name # @return [Class] the created parameter # @yield [ ] a required block that is evaluated in the scope of the new parameter # @api public # @dsl type # def self.newparam(name, options = {}, &block) options[:attributes] ||= {} param = genclass( name, :parent => options[:parent] || Puppet::Parameter, :attributes => options[:attributes], :block => block, :prefix => "Parameter", :array => @parameters, :hash => @paramhash ) handle_param_options(name, options) # Grr. param.required_features = options[:required_features] if options[:required_features] param.isnamevar if options[:namevar] param end # Creates a new property. # @param name [Symbol] the name of the property # @param options [Hash] a hash with options. # @option options [Symbol] :array_matching (:first) specifies how the current state is matched against # the wanted state. Use `:first` if the property is single valued, and (`:all`) otherwise. # @option options [Class] :parent (Puppet::Property) the super class of this property # @option options [Hash{String => Object}] :attributes a hash that is applied to the generated class # by calling setter methods corresponding to this hash's keys/value pairs. This is done before the given # block is evaluated. # @option options [Boolean] :boolean (false) specifies if this is a boolean parameter # @option options [Symbol] :retrieve the method to call on the provider (or `parent` if `provider` is not set) # to retrieve the current value of this property. # @option options [Symbol, Array] :required_features specifies required provider features by name # @return [Class] the created property # @yield [ ] a required block that is evaluated in the scope of the new property # @api public # @dsl type # def self.newproperty(name, options = {}, &block) name = name.intern # This is here for types that might still have the old method of defining # a parent class. unless options.is_a? Hash raise Puppet::DevError, _("Options must be a hash, not %{type}") % { type: options.inspect } end raise Puppet::DevError, _("Class %{class_name} already has a property named %{property}") % { class_name: self.name, property: name } if @validproperties.include?(name) if parent = options[:parent] options.delete(:parent) else parent = Puppet::Property end # We have to create our own, new block here because we want to define # an initial :retrieve method, if told to, and then eval the passed # block if available. prop = genclass(name, :parent => parent, :hash => @validproperties, :attributes => options) do # If they've passed a retrieve method, then override the retrieve # method on the class. if options[:retrieve] define_method(:retrieve) do provider.send(options[:retrieve]) end end class_eval(&block) if block end # If it's the 'ensure' property, always put it first. if name == :ensure @properties.unshift prop else @properties << prop end prop end def self.paramdoc(param) @paramhash[param].doc end # @return [Array] Returns the parameter names def self.parameters return [] unless defined?(@parameters) @parameters.collect { |klass| klass.name } end # @return [Puppet::Parameter] Returns the parameter class associated with the given parameter name. def self.paramclass(name) @paramhash[name] end # @return [Puppet::Property] Returns the property class ??? associated with the given property name def self.propertybyname(name) @validproperties[name] end # Returns whether or not the given name is the name of a property, parameter or meta-parameter # @return [Boolean] true if the given attribute name is the name of an existing property, parameter or meta-parameter # def self.validattr?(name) name = name.intern return true if name == :name @validattrs ||= {} unless @validattrs.include?(name) @validattrs[name] = !!(self.validproperty?(name) or self.validparameter?(name) or self.metaparam?(name)) end @validattrs[name] end # @return [Boolean] Returns true if the given name is the name of an existing property def self.validproperty?(name) name = name.intern @validproperties.include?(name) && @validproperties[name] end # @return [Array, {}] Returns a list of valid property names, or an empty hash if there are none. # @todo An empty hash is returned if there are no defined parameters (not an empty array). This looks like # a bug. # def self.validproperties return {} unless defined?(@parameters) @validproperties.keys end # @return [Boolean] Returns true if the given name is the name of an existing parameter def self.validparameter?(name) raise Puppet::DevError, _("Class %{class_name} has not defined parameters") % { class_name: self } unless defined?(@parameters) !!(@paramhash.include?(name) or @@metaparamhash.include?(name)) end # (see validattr?) # @note see comment in code - how should this be documented? Are some of the other query methods deprecated? # (or should be). # @comment This is a forward-compatibility method - it's the validity interface we'll use in Puppet::Resource. def self.valid_parameter?(name) validattr?(name) end # @return [Boolean] Returns true if the wanted state of the resource is that it should be absent (i.e. to be deleted). def deleting? obj = @parameters[:ensure] and obj.should == :absent end # Creates a new property value holder for the resource if it is valid and does not already exist # @return [Boolean] true if a new parameter was added, false otherwise def add_property_parameter(prop_name) if self.class.validproperty?(prop_name) && !@parameters[prop_name] self.newattr(prop_name) return true end false end # @return [Symbol, Boolean] Returns the name of the namevar if there is only one or false otherwise. # @comment This is really convoluted and part of the support for multiple namevars (?). # If there is only one namevar, the produced value is naturally this namevar, but if there are several? # The logic caches the name of the namevar if it is a single name, but otherwise always # calls key_attributes, and then caches the first if there was only one, otherwise it returns # false and caches this (which is then subsequently returned as a cache hit). # def name_var return @name_var_cache unless @name_var_cache.nil? key_attributes = self.class.key_attributes @name_var_cache = (key_attributes.length == 1) && key_attributes.first end # Gets the 'should' (wanted state) value of a parameter or property by name. # To explicitly get the 'is' (current state) value use `o.is(:name)`, and to explicitly get the 'should' value # use `o.should(:name)` # @param name [String] the name of the attribute to obtain the 'should' value for. # @return [Object] 'should'/wanted value of the given attribute def [](name) name = name.intern fail("Invalid parameter #{name}(#{name.inspect})") unless self.class.validattr?(name) if name == :name && nv = name_var name = nv end if obj = @parameters[name] # Note that if this is a property, then the value is the "should" value, # not the current value. obj.value else return nil end end # Sets the 'should' (wanted state) value of a property, or the value of a parameter. # @return # @raise [Puppet::Error] if the setting of the value fails, or if the given name is nil. # @raise [Puppet::ResourceError] when the parameter validation raises Puppet::Error or # ArgumentError def []=(name,value) name = name.intern fail("no parameter named '#{name}'") unless self.class.validattr?(name) if name == :name && nv = name_var name = nv end raise Puppet::Error.new("Got nil value for #{name}") if value.nil? property = self.newattr(name) if property begin # make sure the parameter doesn't have any errors property.value = value rescue Puppet::Error, ArgumentError => detail error = Puppet::ResourceError.new(_("Parameter %{name} failed on %{ref}: %{detail}") % { name: name, ref: ref, detail: detail }) adderrorcontext(error, detail) raise error end end nil end # Removes an attribute from the object; useful in testing or in cleanup # when an error has been encountered # @todo Don't know what the attr is (name or Property/Parameter?). Guessing it is a String name... # @todo Is it possible to delete a meta-parameter? # @todo What does delete mean? Is it deleted from the type or is its value state 'is'/'should' deleted? # @param attr [String] the attribute to delete from this object. WHAT IS THE TYPE? # @raise [Puppet::DecError] when an attempt is made to delete an attribute that does not exists. # def delete(attr) attr = attr.intern if @parameters.has_key?(attr) @parameters.delete(attr) else raise Puppet::DevError.new(_("Undefined attribute '%{attribute}' in %{name}") % { attribute: attr, name: self}) end end # Iterates over the properties that were set on this resource. # @yieldparam property [Puppet::Property] each property # @return [void] def eachproperty # properties is a private method properties.each { |property| yield property } end # Return the parameters, metaparams, and properties that have a value or were set by a default. Properties are # included since they are a subclass of parameter. # @return [Array] Array of parameter objects ( or subclass thereof ) def parameters_with_value self.class.allattrs.collect { |attr| parameter(attr) }.compact end # Iterates over all parameters with value currently set. # @yieldparam parameter [Puppet::Parameter] or a subclass thereof # @return [void] def eachparameter parameters_with_value.each { |parameter| yield parameter } end # Creates a transaction event. # Called by Transaction or by a property. # Merges the given options with the options `:resource`, `:file`, `:line`, and `:tags`, initialized from # values in this object. For possible options to pass (if any ????) see {Puppet::Transaction::Event}. # @todo Needs a better explanation "Why should I care who is calling this method?", What do I need to know # about events and how they work? Where can I read about them? # @param options [Hash] options merged with a fixed set of options defined by this method, passed on to {Puppet::Transaction::Event}. # @return [Puppet::Transaction::Event] the created event def event(options = {}) Puppet::Transaction::Event.new({:resource => self, :file => file, :line => line, :tags => tags}.merge(options)) end # @return [Object, nil] Returns the 'should' (wanted state) value for a specified property, or nil if the # given attribute name is not a property (i.e. if it is a parameter, meta-parameter, or does not exist). def should(name) name = name.intern (prop = @parameters[name] and prop.is_a?(Puppet::Property)) ? prop.should : nil end # Registers an attribute to this resource type instance. # Requires either the attribute name or class as its argument. # This is a noop if the named property/parameter is not supported # by this resource. Otherwise, an attribute instance is created # and kept in this resource's parameters hash. # @overload newattr(name) # @param name [Symbol] symbolic name of the attribute # @overload newattr(klass) # @param klass [Class] a class supported as an attribute class, i.e. a subclass of # Parameter or Property # @return [Object] An instance of the named Parameter or Property class associated # to this resource type instance, or nil if the attribute is not supported # def newattr(name) if name.is_a?(Class) klass = name name = klass.name end unless klass = self.class.attrclass(name) raise Puppet::Error, "Resource type #{self.class.name} does not support parameter #{name}" end if provider and ! provider.class.supports_parameter?(klass) missing = klass.required_features.find_all { |f| ! provider.class.feature?(f) } debug "Provider %s does not support features %s; not managing attribute %s" % [provider.class.name, missing.join(", "), name] return nil end return @parameters[name] if @parameters.include?(name) @parameters[name] = klass.new(:resource => self) end # Returns a string representation of the resource's containment path in # the catalog. # @return [String] def path @path ||= '/' + pathbuilder.join('/') end # Returns the value of this object's parameter given by name # @param name [String] the name of the parameter # @return [Object] the value def parameter(name) @parameters[name.to_sym] end # Returns a shallow copy of this object's hash of attributes by name. # Note that his not only comprises parameters, but also properties and metaparameters. # Changes to the contained parameters will have an effect on the parameters of this type, but changes to # the returned hash does not. # @return [Hash{String => Object}] a new hash being a shallow copy of the parameters map name to parameter def parameters @parameters.dup end # @return [Boolean] Returns whether the attribute given by name has been added # to this resource or not. def propertydefined?(name) name = name.intern unless name.is_a? Symbol @parameters.include?(name) end # Returns a {Puppet::Property} instance by name. # To return the value, use 'resource[param]' # @todo LAK:NOTE(20081028) Since the 'parameter' method is now a superset of this method, # this one should probably go away at some point. - Does this mean it should be deprecated ? # @return [Puppet::Property] the property with the given name, or nil if not a property or does not exist. def property(name) (obj = @parameters[name.intern] and obj.is_a?(Puppet::Property)) ? obj : nil end # @todo comment says "For any parameters or properties that have defaults and have not yet been # set, set them now. This method can be handed a list of attributes, # and if so it will only set defaults for those attributes." # @todo Needs a better explanation, and investigation about the claim an array can be passed (it is passed # to self.class.attrclass to produce a class on which a check is made if it has a method class :default (does # not seem to support an array... # @return [void] # def set_default(attr) return unless klass = self.class.attrclass(attr) return unless klass.method_defined?(:default) return if @parameters.include?(klass.name) return unless parameter = newattr(klass.name) if value = parameter.default and ! value.nil? parameter.value = value else @parameters.delete(parameter.name) end end # @todo the comment says: "Convert our object to a hash. This just includes properties." # @todo this is confused, again it is the @parameters instance variable that is consulted, and # each value is copied - does it contain "properties" and "parameters" or both? Does it contain # meta-parameters? # # @return [Hash{ ??? => ??? }] a hash of WHAT?. The hash is a shallow copy, any changes to the # objects returned in this hash will be reflected in the original resource having these attributes. # def to_hash rethash = {} @parameters.each do |name, obj| rethash[name] = obj.value end rethash end # @return [String] the name of this object's class # @todo Would that be "file" for the "File" resource type? of "File" or something else? # def type self.class.name end # @todo Comment says "Return a specific value for an attribute.", as opposed to what "An unspecific value"??? # @todo is this the 'is' or the 'should' value? # @todo why is the return restricted to things that respond to :value? (Only non structural basic data types # supported? # # @return [Object, nil] the value of the attribute having the given name, or nil if the given name is not # an attribute, or the referenced attribute does not respond to `:value`. def value(name) name = name.intern (obj = @parameters[name] and obj.respond_to?(:value)) ? obj.value : nil end # @todo What is this used for? Needs a better explanation. # @return [???] the version of the catalog or 0 if there is no catalog. def version return 0 unless catalog catalog.version end # @return [Array] Returns all of the property objects, in the order specified in the # class. # @todo "what does the 'order specified in the class' mean? The order the properties where added in the # ruby file adding a new type with new properties? # def properties self.class.properties.collect { |prop| @parameters[prop.name] }.compact end # Returns true if the type's notion of name is the identity of a resource. # See the overview of this class for a longer explanation of the concept _isomorphism_. # Defaults to true. # # @return [Boolean] true, if this type's name is isomorphic with the object def self.isomorphic? if defined?(@isomorphic) return @isomorphic else return true end end # @todo check that this gets documentation (it is at the class level as well as instance). # (see isomorphic?) def isomorphic? self.class.isomorphic? end # Returns true if the instance is a managed instance. # A 'yes' here means that the instance was created from the language, vs. being created # in order resolve other questions, such as finding a package in a list. # @note An object that is managed always stays managed, but an object that is not managed # may become managed later in its lifecycle. # @return [Boolean] true if the object is managed def managed? # Once an object is managed, it always stays managed; but an object # that is listed as unmanaged might become managed later in the process, # so we have to check that every time if @managed return @managed else @managed = false properties.each { |property| s = property.should if s and ! property.class.unmanaged @managed = true break end } return @managed end end ############################### # Code related to the container behaviour. # Returns true if the search should be done in depth-first order. # This implementation always returns false. # @todo What is this used for? # # @return [Boolean] true if the search should be done in depth first order. # def depthfirst? false end # Removes this object (FROM WHERE?) # @todo removes if from where? # @return [void] def remove() # This is hackish (mmm, cut and paste), but it works for now, and it's # better than warnings. @parameters.each do |name, obj| obj.remove end @parameters.clear @parent = nil # Remove the reference to the provider. if self.provider @provider.clear @provider = nil end end ############################### # Code related to evaluating the resources. # Returns the ancestors - WHAT? # This implementation always returns an empty list. # @todo WHAT IS THIS ? # @return [Array] returns a list of ancestors. def ancestors [] end # Lifecycle method for a resource. This is called during graph creation. # It should perform any consistency checking of the catalog and raise a # Puppet::Error if the transaction should be aborted. # # It differs from the validate method, since it is called later during # initialization and can rely on self.catalog to have references to all # resources that comprise the catalog. # # @see Puppet::Transaction#add_vertex # @raise [Puppet::Error] If the pre-run check failed. # @return [void] # @abstract a resource type may implement this method to perform # validation checks that can query the complete catalog def pre_run_check end # Flushes the provider if supported by the provider, else no action. # This is called by the transaction. # @todo What does Flushing the provider mean? Why is it interesting to know that this is # called by the transaction? (It is not explained anywhere what a transaction is). # # @return [???, nil] WHAT DOES IT RETURN? GUESS IS VOID def flush self.provider.flush if self.provider and self.provider.respond_to?(:flush) end # Returns true if all contained objects are in sync. # @todo "contained in what?" in the given "in" parameter? # # @todo deal with the comment _"FIXME I don't think this is used on the type instances any more, # it's really only used for testing"_ # @return [Boolean] true if in sync, false otherwise. # def insync?(is) insync = true if property = @parameters[:ensure] unless is.include? property #TRANSLATORS 'is' is a variable name and should not be translated raise Puppet::DevError, _("The 'is' value is not in the 'is' array for '%{name}'") % { name: property.name } end ensureis = is[property] if property.safe_insync?(ensureis) and property.should == :absent return true end end properties.each { |prop| unless is.include? prop #TRANSLATORS 'is' is a variable name and should not be translated raise Puppet::DevError, _("The 'is' value is not in the 'is' array for '%{name}'") % { name: prop.name } end propis = is[prop] unless prop.safe_insync?(propis) prop.debug("Not in sync: #{propis.inspect} vs #{prop.should.inspect}") insync = false #else # property.debug("In sync") end } #self.debug("#{self} sync status is #{insync}") insync end # Says if the ensure property should be retrieved if the resource is ensurable # Defaults to true. Some resource type classes can override it def self.needs_ensure_retrieved true end # Retrieves the current value of all contained properties. # Parameters and meta-parameters are not included in the result. # @todo As opposed to all non contained properties? How is this different than any of the other # methods that also "gets" properties/parameters/etc. ? # @return [Puppet::Resource] array of all property values (mix of types) # @raise [fail???] if there is a provider and it is not suitable for the host this is evaluated for. def retrieve fail "Provider #{provider.class.name} is not functional on this host" if self.provider.is_a?(Puppet::Provider) and ! provider.class.suitable? result = Puppet::Resource.new(self.class, title) # Provide the name, so we know we'll always refer to a real thing result[:name] = self[:name] unless self[:name] == title if ensure_prop = property(:ensure) or (self.class.needs_ensure_retrieved and self.class.validattr?(:ensure) and ensure_prop = newattr(:ensure)) result[:ensure] = ensure_state = ensure_prop.retrieve else ensure_state = nil end properties.each do |property| next if property.name == :ensure if ensure_state == :absent result[property] = :absent else result[property] = property.retrieve end end result end # Retrieve the current state of the system as a Puppet::Resource. For # the base Puppet::Type this does the same thing as #retrieve, but # specific types are free to implement #retrieve as returning a hash, # and this will call #retrieve and convert the hash to a resource. # This is used when determining when syncing a resource. # # @return [Puppet::Resource] A resource representing the current state # of the system. # # @api private def retrieve_resource resource = retrieve resource = Resource.new(self.class, title, :parameters => resource) if resource.is_a? Hash resource end # Given the hash of current properties, should this resource be treated as if it # currently exists on the system. May need to be overridden by types that offer up # more than just :absent and :present. def present?(current_values) current_values[:ensure] != :absent end # Returns a hash of the current properties and their values. # If a resource is absent, its value is the symbol `:absent` # @return [Hash{Puppet::Property => Object}] mapping of property instance to its value # def currentpropvalues # It's important to use the 'properties' method here, as it follows the order # in which they're defined in the class. It also guarantees that 'ensure' # is the first property, which is important for skipping 'retrieve' on # all the properties if the resource is absent. ensure_state = false return properties.inject({}) do | prophash, property| if property.name == :ensure ensure_state = property.retrieve prophash[property] = ensure_state else if ensure_state == :absent prophash[property] = :absent else prophash[property] = property.retrieve end end prophash end end # Returns the `noop` run mode status of this. # @return [Boolean] true if running in noop mode. def noop? # If we're not a host_config, we're almost certainly part of # Settings, and we want to ignore 'noop' return false if catalog and ! catalog.host_config? if defined?(@noop) @noop else Puppet[:noop] end end # (see #noop?) def noop noop? end # Retrieves all known instances. # @todo Retrieves them from where? Known to whom? # Either requires providers or must be overridden. # @raise [Puppet::DevError] when there are no providers and the implementation has not overridden this method. def self.instances raise Puppet::DevError, _("%{name} has no providers and has not overridden 'instances'") % { name: self.name } if provider_hash.empty? # Put the default provider first, then the rest of the suitable providers. provider_instances = {} providers_by_source.collect do |provider| provider.instances.collect do |instance| # We always want to use the "first" provider instance we find, unless the resource # is already managed and has a different provider set if other = provider_instances[instance.name] Puppet.debug "%s %s found in both %s and %s; skipping the %s version" % [self.name.to_s.capitalize, instance.name, other.class.name, instance.class.name, instance.class.name] next end provider_instances[instance.name] = instance result = new(:name => instance.name, :provider => instance) properties.each { |name| result.newattr(name) } result end end.flatten.compact end # Returns a list of one suitable provider per source, with the default provider first. # @todo Needs better explanation; what does "source" mean in this context? # @return [Array] list of providers # def self.providers_by_source # Put the default provider first (can be nil), then the rest of the suitable providers. sources = [] [defaultprovider, suitableprovider].flatten.uniq.collect do |provider| next if provider.nil? next if sources.include?(provider.source) sources << provider.source provider end.compact end # Converts a simple hash into a Resource instance. # @todo as opposed to a complex hash? Other raised exceptions? # @param [Hash{Symbol, String => Object}] hash resource attribute to value map to initialize the created resource from # @return [Puppet::Resource] the resource created from the hash # @raise [Puppet::Error] if a title is missing in the given hash def self.hash2resource(hash) hash = hash.inject({}) { |result, ary| result[ary[0].to_sym] = ary[1]; result } title = hash.delete(:title) title ||= hash[:name] title ||= hash[key_attributes.first] if key_attributes.length == 1 raise Puppet::Error, "Title or name must be provided" unless title # Now create our resource. resource = Puppet::Resource.new(self, title) resource.catalog = hash.delete(:catalog) if sensitive = hash.delete(:sensitive_parameters) resource.sensitive_parameters = sensitive end hash.each do |param, value| resource[param] = value end resource end # Returns an array of strings representing the containment hierarchy # (types/classes) that make up the path to the resource from the root # of the catalog. This is mostly used for logging purposes. # # @api private def pathbuilder if p = parent [p.pathbuilder, self.ref].flatten else [self.ref] end end ############################### # Add all of the meta-parameters. newmetaparam(:noop) do desc "Whether to apply this resource in noop mode. When applying a resource in noop mode, Puppet will check whether it is in sync, like it does when running normally. However, if a resource attribute is not in the desired state (as declared in the catalog), Puppet will take no action, and will instead report the changes it _would_ have made. These simulated changes will appear in the report sent to the puppet master, or be shown on the console if running puppet agent or puppet apply in the foreground. The simulated changes will not send refresh events to any subscribing or notified resources, although Puppet will log that a refresh event _would_ have been sent. **Important note:** [The `noop` setting](https://puppet.com/docs/puppet/latest/configuration.html#noop) allows you to globally enable or disable noop mode, but it will _not_ override the `noop` metaparameter on individual resources. That is, the value of the global `noop` setting will _only_ affect resources that do not have an explicit value set for their `noop` attribute." newvalues(:true, :false) munge do |value| case value when true, :true, "true"; @resource.noop = true when false, :false, "false"; @resource.noop = false end end end newmetaparam(:schedule) do desc "A schedule to govern when Puppet is allowed to manage this resource. The value of this metaparameter must be the `name` of a `schedule` resource. This means you must declare a schedule resource, then refer to it by name; see [the docs for the `schedule` type](https://puppet.com/docs/puppet/latest/type.html#schedule) for more info. schedule { 'everyday': period => daily, range => \"2-4\" } exec { \"/usr/bin/apt-get update\": schedule => 'everyday' } Note that you can declare the schedule resource anywhere in your manifests, as long as it ends up in the final compiled catalog." end newmetaparam(:audit) do desc "Marks a subset of this resource's unmanaged attributes for auditing. Accepts an attribute name, an array of attribute names, or `all`. Auditing a resource attribute has two effects: First, whenever a catalog is applied with puppet apply or puppet agent, Puppet will check whether that attribute of the resource has been modified, comparing its current value to the previous run; any change will be logged alongside any actions performed by Puppet while applying the catalog. Secondly, marking a resource attribute for auditing will include that attribute in inspection reports generated by puppet inspect; see the puppet inspect documentation for more details. Managed attributes for a resource can also be audited, but note that changes made by Puppet will be logged as additional modifications. (I.e. if a user manually edits a file whose contents are audited and managed, puppet agent's next two runs will both log an audit notice: the first run will log the user's edit and then revert the file to the desired state, and the second run will log the edit made by Puppet.)" validate do |list| list = Array(list).collect {|p| p.to_sym} unless list == [:all] list.each do |param| next if @resource.class.validattr?(param) fail "Cannot audit #{param}: not a valid attribute for #{resource}" end end end munge do |args| properties_to_audit(args).each do |param| next unless resource.class.validproperty?(param) resource.newattr(param) end end def all_properties resource.class.properties.find_all do |property| resource.provider.nil? or resource.provider.class.supports_parameter?(property) end.collect do |property| property.name end end def properties_to_audit(list) if !list.kind_of?(Array) && list.to_sym == :all all_properties else Array(list).collect { |p| p.to_sym } end end end newmetaparam(:loglevel) do desc "Sets the level that information will be logged. The log levels have the biggest impact when logs are sent to syslog (which is currently the default). The order of the log levels, in decreasing priority, is: * `emerg` * `alert` * `crit` * `err` * `warning` * `notice` * `info` / `verbose` * `debug` " defaultto :notice newvalues(*Puppet::Util::Log.levels) newvalues(:verbose) munge do |loglevel| val = super(loglevel) if val == :verbose val = :info end val end end newmetaparam(:alias) do desc %q{Creates an alias for the resource. Puppet uses this internally when you provide a symbolic title and an explicit namevar value: file { 'sshdconfig': path => $operatingsystem ? { solaris => '/usr/local/etc/ssh/sshd_config', default => '/etc/ssh/sshd_config', }, source => '...' } service { 'sshd': subscribe => File['sshdconfig'], } When you use this feature, the parser sets `sshdconfig` as the title, and the library sets that as an alias for the file so the dependency lookup in `Service['sshd']` works. You can use this metaparameter yourself, but note that aliases generally only work for creating relationships; anything else that refers to an existing resource (such as amending or overriding resource attributes in an inherited class) must use the resource's exact title. For example, the following code will not work: file { '/etc/ssh/sshd_config': owner => root, group => root, alias => 'sshdconfig', } File['sshdconfig'] { mode => '0644', } There's no way here for the Puppet parser to know that these two stanzas should be affecting the same file. } munge do |aliases| aliases = [aliases] unless aliases.is_a?(Array) raise(ArgumentError, _("Cannot add aliases without a catalog")) unless @resource.catalog aliases.each do |other| if obj = @resource.catalog.resource(@resource.class.name, other) unless obj.object_id == @resource.object_id self.fail("#{@resource.title} can not create alias #{other}: object already exists") end next end # Newschool, add it to the catalog. @resource.catalog.alias(@resource, other) end end end newmetaparam(:tag) do desc "Add the specified tags to the associated resource. While all resources are automatically tagged with as much information as possible (e.g., each class and definition containing the resource), it can be useful to add your own tags to a given resource. Multiple tags can be specified as an array: file {'/etc/hosts': ensure => file, source => 'puppet:///modules/site/hosts', mode => '0644', tag => ['bootstrap', 'minimumrun', 'mediumrun'], } Tags are useful for things like applying a subset of a host's configuration with [the `tags` setting](/puppet/latest/configuration.html#tags) (e.g. `puppet agent --test --tags bootstrap`)." munge do |tags| tags = [tags] unless tags.is_a? Array tags.each do |tag| @resource.tag(tag) end end end # RelationshipMetaparam is an implementation supporting the meta-parameters `:require`, `:subscribe`, # `:notify`, and `:before`. # # class RelationshipMetaparam < Puppet::Parameter class << self attr_accessor :direction, :events, :callback, :subclasses end @subclasses = [] def self.inherited(sub) @subclasses << sub end # @return [Array] turns attribute values into list of resources def munge(references) references = [references] unless references.is_a?(Array) references.collect do |ref| if ref.is_a?(Puppet::Resource) ref else Puppet::Resource.new(ref) end end end # Checks each reference to assert that what it references exists in the catalog. # # @raise [???fail] if the referenced resource can not be found # @return [void] def validate_relationship @value.each do |ref| unless @resource.catalog.resource(ref.to_s) description = self.class.direction == :in ? "dependency" : "dependent" fail ResourceError, _("Could not find %{description} %{ref} for %{resource}") % { description: description, ref: ref, resource: resource.ref } end end end # Creates edges for all relationships. # The `:in` relationships are specified by the event-receivers, and `:out` # relationships are specified by the event generator. # @todo references to "event-receivers" and "event generator" means in this context - are those just # the resources at the two ends of the relationship? # This way 'source' and 'target' are consistent terms in both edges # and events, i.e. an event targets edges whose source matches # the event's source. The direction of the relationship determines # which resource is applied first and which resource is considered # to be the event generator. # @return [Array] # @raise [???fail] when a reference can not be resolved # def to_edges @value.collect do |reference| reference.catalog = resource.catalog # Either of the two retrieval attempts could have returned # nil. unless related_resource = reference.resolve self.fail "Could not retrieve dependency '#{reference}' of #{@resource.ref}" end # Are we requiring them, or vice versa? See the method docs # for further info on this. if self.class.direction == :in source = related_resource target = @resource else source = @resource target = related_resource end if method = self.class.callback subargs = { :event => self.class.events, :callback => method } else # If there's no callback, there's no point in even adding # a label. subargs = nil end ## Corrected syntax of debug statement to reflect the way this was called. # i.e. before, after, subscribe, notify self.debug do relation = case self.class.name when "subscribe" "subscribes" when "notify" "notifies" else self.class.name end "#{relation} to #{related_resource.ref}" end Puppet::Relationship.new(source, target, subargs) end end end # @todo document this, have no clue what this does... it returns "RelationshipMetaparam.subclasses" # def self.relationship_params RelationshipMetaparam.subclasses end # Note that the order in which the relationships params is defined # matters. The labeled params (notify and subscribe) must be later, # so that if both params are used, those ones win. It's a hackish # solution, but it works. newmetaparam(:require, :parent => RelationshipMetaparam, :attributes => {:direction => :in, :events => :NONE}) do desc "One or more resources that this resource depends on, expressed as [resource references](https://puppet.com/docs/puppet/latest/lang_data_resource_reference.html). Multiple resources can be specified as an array of references. When this attribute is present: * The required resources will be applied **before** this resource. This is one of the four relationship metaparameters, along with `before`, `notify`, and `subscribe`. For more context, including the alternate chaining arrow (`->` and `~>`) syntax, see [the language page on relationships](https://puppet.com/docs/puppet/latest/lang_relationships.html)." end newmetaparam(:subscribe, :parent => RelationshipMetaparam, :attributes => {:direction => :in, :events => :ALL_EVENTS, :callback => :refresh}) do desc "One or more resources that this resource depends on, expressed as [resource references](https://puppet.com/docs/puppet/latest/lang_data_resource_reference.html). Multiple resources can be specified as an array of references. When this attribute is present: * The subscribed resources will be applied _before_ this resource. * If Puppet makes changes to any of the subscribed resources, it will cause this resource to _refresh._ (Refresh behavior varies by resource type: services will restart, mounts will unmount and re-mount, etc. Not all types can refresh.) This is one of the four relationship metaparameters, along with `before`, `require`, and `notify`. For more context, including the alternate chaining arrow (`->` and `~>`) syntax, see [the language page on relationships](https://puppet.com/docs/puppet/latest/lang_relationships.html)." end newmetaparam(:before, :parent => RelationshipMetaparam, :attributes => {:direction => :out, :events => :NONE}) do desc "One or more resources that depend on this resource, expressed as [resource references](https://puppet.com/docs/puppet/latest/lang_data_resource_reference.html). Multiple resources can be specified as an array of references. When this attribute is present: * This resource will be applied _before_ the dependent resources. This is one of the four relationship metaparameters, along with `require`, `notify`, and `subscribe`. For more context, including the alternate chaining arrow (`->` and `~>`) syntax, see [the language page on relationships](https://puppet.com/docs/puppet/latest/lang_relationships.html)." end newmetaparam(:notify, :parent => RelationshipMetaparam, :attributes => {:direction => :out, :events => :ALL_EVENTS, :callback => :refresh}) do desc "One or more resources that depend on this resource, expressed as [resource references](https://puppet.com/docs/puppet/latest/lang_data_resource_reference.html). Multiple resources can be specified as an array of references. When this attribute is present: * This resource will be applied _before_ the notified resources. * If Puppet makes changes to this resource, it will cause all of the notified resources to _refresh._ (Refresh behavior varies by resource type: services will restart, mounts will unmount and re-mount, etc. Not all types can refresh.) This is one of the four relationship metaparameters, along with `before`, `require`, and `subscribe`. For more context, including the alternate chaining arrow (`->` and `~>`) syntax, see [the language page on relationships](https://puppet.com/docs/puppet/latest/lang_relationships.html)." end newmetaparam(:stage) do desc %{Which run stage this class should reside in. **Note: This metaparameter can only be used on classes,** and only when declaring them with the resource-like syntax. It cannot be used on normal resources or on classes declared with `include`. By default, all classes are declared in the `main` stage. To assign a class to a different stage, you must: * Declare the new stage as a [`stage` resource](https://puppet.com/docs/puppet/latest/type.html#stage). * Declare an order relationship between the new stage and the `main` stage. * Use the resource-like syntax to declare the class, and set the `stage` metaparameter to the name of the desired stage. For example: stage { 'pre': before => Stage['main'], } class { 'apt-updates': stage => 'pre', } } end newmetaparam(:export, :parent => RelationshipMetaparam, :attributes => {:direction => :out, :events => :NONE}) do desc < Http[main_server] } EOS end newmetaparam(:consume, :parent => RelationshipMetaparam, :attributes => {:direction => :in, :events => :NONE}) do desc < Sql[my_db] } EOS end ############################### # All of the provider plumbing for the resource types. require 'puppet/provider' require 'puppet/util/provider_features' # Add the feature handling module. extend Puppet::Util::ProviderFeatures # The provider that has been selected for the instance of the resource type. # @return [Puppet::Provider,nil] the selected provider or nil, if none has been selected # attr_reader :provider # the Type class attribute accessors class << self # The loader of providers to use when loading providers from disk. # Although it looks like this attribute provides a way to operate with different loaders of # providers that is not the case; the attribute is written when a new type is created, # and should not be changed thereafter. # @api private # attr_accessor :providerloader # @todo Don't know if this is a name, or a reference to a Provider instance (now marked up as an instance # of Provider. # @return [Puppet::Provider, nil] The default provider for this type, or nil if non is defines # attr_writer :defaultprovider end # The default provider, or the most suitable provider if no default provider was set. # @note a warning will be issued if no default provider has been configured and a search for the most # suitable provider returns more than one equally suitable provider. # @return [Puppet::Provider, nil] the default or most suitable provider, or nil if no provider was found # def self.defaultprovider return @defaultprovider if @defaultprovider suitable = suitableprovider # Find which providers are a default for this system. defaults = suitable.find_all { |provider| provider.default? } # If we don't have any default we use suitable providers defaults = suitable if defaults.empty? max = defaults.collect { |provider| provider.specificity }.max defaults = defaults.find_all { |provider| provider.specificity == max } if defaults.length > 1 Puppet.warning(_("Found multiple default providers for %{name}: %{provider_list}; using %{selected_provider}") % { name: self.name, provider_list: defaults.collect { |i| i.name.to_s }.join(", "), selected_provider: defaults[0].name }) end @defaultprovider = defaults.shift unless defaults.empty? end # @return [Hash{??? => Puppet::Provider}] Returns a hash of WHAT EXACTLY for the given type # @todo what goes into this hash? def self.provider_hash_by_type(type) @provider_hashes ||= {} @provider_hashes[type] ||= {} end # @return [Hash{ ??? => Puppet::Provider}] Returns a hash of WHAT EXACTLY for this type. # @see provider_hash_by_type method to get the same for some other type def self.provider_hash Puppet::Type.provider_hash_by_type(self.name) end # Returns the provider having the given name. # This will load a provider if it is not already loaded. The returned provider is the first found provider # having the given name, where "first found" semantics is defined by the {providerloader} in use. # # @param name [String] the name of the provider to get # @return [Puppet::Provider, nil] the found provider, or nil if no provider of the given name was found # def self.provider(name) name = name.intern # If we don't have it yet, try loading it. @providerloader.load(name) unless provider_hash.has_key?(name) provider_hash[name] end # Returns a list of loaded providers by name. # This method will not load/search for available providers. # @return [Array] list of loaded provider names # def self.providers provider_hash.keys end # Returns true if the given name is a reference to a provider and if this is a suitable provider for # this type. # @todo How does the provider know if it is suitable for the type? Is it just suitable for the platform/ # environment where this method is executing? # @param name [String] the name of the provider for which validity is checked # @return [Boolean] true if the given name references a provider that is suitable # def self.validprovider?(name) name = name.intern (provider_hash.has_key?(name) && provider_hash[name].suitable?) end # Creates a new provider of a type. # This method must be called directly on the type that it's implementing. # @todo Fix Confusing Explanations! # Is this a new provider of a Type (metatype), or a provider of an instance of Type (a resource), or # a Provider (the implementation of a Type's behavior). CONFUSED. It calls magically named methods like # "providify" ... # @param name [String, Symbol] the name of the WHAT? provider? type? # @param options [Hash{Symbol => Object}] a hash of options, used by this method, and passed on to {#genclass}, (see # it for additional options to pass). # @option options [Puppet::Provider] :parent the parent provider (what is this?) # @option options [Puppet::Type] :resource_type the resource type, defaults to this type if unspecified # @return [Puppet::Provider] a provider ??? # @raise [Puppet::DevError] when the parent provider could not be found. # def self.provide(name, options = {}, &block) name = name.intern if unprovide(name) Puppet.debug "Reloading #{name} #{self.name} provider" end parent = if pname = options[:parent] options.delete(:parent) if pname.is_a? Class pname else if provider = self.provider(pname) provider else raise Puppet::DevError, _("Could not find parent provider %{parent} of %{name}") % { parent: pname, name: name } end end else Puppet::Provider end options[:resource_type] ||= self self.providify provider = genclass( name, :parent => parent, :hash => provider_hash, :prefix => "Provider", :block => block, :include => feature_module, :extend => feature_module, :attributes => options ) provider end # Ensures there is a `:provider` parameter defined. # Should only be called if there are providers. # @return [void] def self.providify return if @paramhash.has_key? :provider param = newparam(:provider) do # We're using a hacky way to get the name of our type, since there doesn't # seem to be a correct way to introspect this at the time this code is run. # We expect that the class in which this code is executed will be something # like Puppet::Type::Ssh_authorized_key::ParameterProvider. desc <<-EOT The specific backend to use for this `#{self.to_s.split('::')[2].downcase}` resource. You will seldom need to specify this --- Puppet will usually discover the appropriate provider for your platform. EOT # This is so we can refer back to the type to get a list of # providers for documentation. class << self # The reference to a parent type for the parameter `:provider` used to get a list of # providers for documentation purposes. # attr_accessor :parenttype end # Provides the ability to add documentation to a provider. # def self.doc # Since we're mixing @doc with text from other sources, we must normalize # its indentation with scrub. But we don't need to manually scrub the # provider's doc string, since markdown_definitionlist sanitizes its inputs. scrub(@doc) + "Available providers are:\n\n" + parenttype.providers.sort { |a,b| a.to_s <=> b.to_s }.collect { |i| markdown_definitionlist( i, scrub(parenttype().provider(i).doc) ) }.join end # For each resource, the provider param defaults to # the type's default provider defaultto { prov = @resource.class.defaultprovider prov.name if prov } validate do |provider_class| provider_class = provider_class[0] if provider_class.is_a? Array provider_class = provider_class.class.name if provider_class.is_a?(Puppet::Provider) unless @resource.class.provider(provider_class) raise ArgumentError, _("Invalid %{resource} provider '%{provider_class}'") % { resource: @resource.class.name, provider_class: provider_class} end end munge do |provider| provider = provider[0] if provider.is_a? Array provider = provider.intern if provider.is_a? String @resource.provider = provider if provider.is_a?(Puppet::Provider) provider.class.name else provider end end end param.parenttype = self end # @todo this needs a better explanation # Removes the implementation class of a given provider. # @return [Object] returns what {Puppet::Util::ClassGen#rmclass} returns def self.unprovide(name) if @defaultprovider and @defaultprovider.name == name @defaultprovider = nil end rmclass(name, :hash => provider_hash, :prefix => "Provider") end # Returns a list of suitable providers for the given type. # A call to this method will load all providers if not already loaded and ask each if it is # suitable - those that are are included in the result. # @note This method also does some special processing which rejects a provider named `:fake` (for testing purposes). # @return [Array] Returns an array of all suitable providers. # def self.suitableprovider providerloader.loadall if provider_hash.empty? provider_hash.find_all { |name, provider| provider.suitable? }.collect { |name, provider| provider }.reject { |p| p.name == :fake } # For testing end # @return [Boolean] Returns true if this is something else than a `:provider`, or if it # is a provider and it is suitable, or if there is a default provider. Otherwise, false is returned. # def suitable? # If we don't use providers, then we consider it suitable. return true unless self.class.paramclass(:provider) # We have a provider and it is suitable. return true if provider && provider.class.suitable? # We're using the default provider and there is one. if !provider and self.class.defaultprovider self.provider = self.class.defaultprovider.name return true end # We specified an unsuitable provider, or there isn't any suitable # provider. false end # Sets the provider to the given provider/name. # @overload provider=(name) # Sets the provider to the result of resolving the name to an instance of Provider. # @param name [String] the name of the provider # @overload provider=(provider) # Sets the provider to the given instances of Provider. # @param provider [Puppet::Provider] the provider to set # @return [Puppet::Provider] the provider set # @raise [ArgumentError] if the provider could not be found/resolved. # def provider=(name) if name.is_a?(Puppet::Provider) @provider = name @provider.resource = self elsif klass = self.class.provider(name) @provider = klass.new(self) else raise ArgumentError, _("Could not find %{name} provider of %{provider}") % { name: name, provider: self.class.name } end end ############################### # All of the relationship code. # Adds a block producing a single name (or list of names) of the given # resource type name to autorelate. # # The four relationship types require, before, notify, and subscribe are all # supported. # # Be *careful* with notify and subscribe as they may have unintended # consequences. # # Resources in the catalog that have the named type and a title that is # included in the result will be linked to the calling resource as a # requirement. # # @example Autorequire the files File['foo', 'bar'] # autorequire( 'file', {|| ['foo', 'bar'] }) # # @example Autobefore the files File['foo', 'bar'] # autobefore( 'file', {|| ['foo', 'bar'] }) # # @example Autosubscribe the files File['foo', 'bar'] # autosubscribe( 'file', {|| ['foo', 'bar'] }) # # @example Autonotify the files File['foo', 'bar'] # autonotify( 'file', {|| ['foo', 'bar'] }) # # @param name [String] the name of a type of which one or several resources should be autorelated e.g. "file" # @yield [ ] a block returning list of names of given type to auto require # @yieldreturn [String, Array] one or several resource names for the named type # @return [void] # @dsl type # @api public # def self.autorequire(name, &block) @autorequires ||= {} @autorequires[name] = block end def self.autobefore(name, &block) @autobefores ||= {} @autobefores[name] = block end def self.autosubscribe(name, &block) @autosubscribes ||= {} @autosubscribes[name] = block end def self.autonotify(name, &block) @autonotifies ||= {} @autonotifies[name] = block end # Provides iteration over added auto-requirements (see {autorequire}). # @yieldparam type [String] the name of the type to autorequire an instance of # @yieldparam block [Proc] a block producing one or several dependencies to auto require (see {autorequire}). # @yieldreturn [void] # @return [void] def self.eachautorequire @autorequires ||= {} @autorequires.each { |type, block| yield(type, block) } end # Provides iteration over added auto-requirements (see {autobefore}). # @yieldparam type [String] the name of the type to autorequire an instance of # @yieldparam block [Proc] a block producing one or several dependencies to auto require (see {autobefore}). # @yieldreturn [void] # @return [void] def self.eachautobefore @autobefores ||= {} @autobefores.each { |type,block| yield(type, block) } end # Provides iteration over added auto-requirements (see {autosubscribe}). # @yieldparam type [String] the name of the type to autorequire an instance of # @yieldparam block [Proc] a block producing one or several dependencies to auto require (see {autosubscribe}). # @yieldreturn [void] # @return [void] def self.eachautosubscribe @autosubscribes ||= {} @autosubscribes.each { |type,block| yield(type, block) } end # Provides iteration over added auto-requirements (see {autonotify}). # @yieldparam type [String] the name of the type to autorequire an instance of # @yieldparam block [Proc] a block producing one or several dependencies to auto require (see {autonotify}). # @yieldreturn [void] # @return [void] def self.eachautonotify @autonotifies ||= {} @autonotifies.each { |type,block| yield(type, block) } end # Adds dependencies to the catalog from added autorelations. # See {autorequire} for how to add an auto-requirement. # @todo needs details - see the param rel_catalog, and type of this param # @param rel_catalog [Puppet::Resource::Catalog, nil] the catalog to # add dependencies to. Defaults to the current catalog (set when the # type instance was added to a catalog) # @raise [Puppet::DevError] if there is no catalog # def autorelation(rel_type, rel_catalog = nil) rel_catalog ||= catalog raise Puppet::DevError, _("You cannot add relationships without a catalog") unless rel_catalog reqs = [] auto_rel = "eachauto#{rel_type}".to_sym self.class.send(auto_rel) { |type, block| # Ignore any types we can't find, although that would be a bit odd. next unless Puppet::Type.type(type) # Retrieve the list of names from the block. next unless list = self.instance_eval(&block) list = [list] unless list.is_a?(Array) # Collect the current prereqs list.each { |dep| next if dep.nil? # Support them passing objects directly, to save some effort. unless dep.is_a?(Puppet::Type) # Skip autorelation that we aren't managing unless dep = rel_catalog.resource(type, dep) next end end if [:require, :subscribe].include?(rel_type) reqs << Puppet::Relationship.new(dep, self) else reqs << Puppet::Relationship.new(self, dep) end } } reqs end def autorequire(rel_catalog = nil) autorelation(:require, rel_catalog) end def autobefore(rel_catalog = nil) autorelation(:before, rel_catalog) end def autosubscribe(rel_catalog = nil) autorelation(:subscribe, rel_catalog) end def autonotify(rel_catalog = nil) autorelation(:notify, rel_catalog) end # Builds the dependencies associated with this resource. # # @return [Array] list of relationships to other resources def builddepends # Handle the requires self.class.relationship_params.collect do |klass| if param = @parameters[klass.name] param.to_edges end end.flatten.reject { |r| r.nil? } end # Sets the initial list of tags to associate to this resource. # # @return [void] ??? def tags=(list) tag(self.class.name) tag(*list) end # @comment - these two comments were floating around here, and turned up as documentation # for the attribute "title", much to my surprise and amusement. Clearly these comments # are orphaned ... I think they can just be removed as what they say should be covered # by the now added yardoc. (Yo! to quote some of the other actual awesome specific comments applicable # to objects called from elsewhere, or not. ;-) # # @comment Types (which map to resources in the languages) are entirely composed of # attribute value pairs. Generally, Puppet calls any of these things an # 'attribute', but these attributes always take one of three specific # forms: parameters, metaparams, or properties. # @comment In naming methods, I have tried to consistently name the method so # that it is clear whether it operates on all attributes (thus has 'attr' in # the method name, or whether it operates on a specific type of attributes. # The title attribute of WHAT ??? # @todo Figure out what this is the title attribute of (it appears on line 1926 currently). # @return [String] the title attr_writer :title # The noop attribute of WHAT ??? does WHAT??? # @todo Figure out what this is the noop attribute of (it appears on line 1931 currently). # @return [???] the noop WHAT ??? (mode? if so of what, or noop for an instance of the type, or for all # instances of a type, or for what??? # attr_writer :noop include Enumerable # class methods dealing with Type management # The Type class attribute accessors class << self # @return [String] the name of the resource type; e.g., "File" # attr_reader :name # @return [Boolean] true if the type should send itself a refresh event on change. # attr_accessor :self_refresh include Enumerable, Puppet::Util::ClassGen include Puppet::MetaType::Manager include Puppet::Util include Puppet::Util::Logging end # Initializes all of the variables that must be initialized for each subclass. # @todo Does the explanation make sense? # @return [void] def self.initvars # all of the instances of this class @objects = Hash.new @aliases = Hash.new @defaults = {} @parameters ||= [] @validproperties = {} @properties = [] @parameters = [] @paramhash = {} @paramdoc = Hash.new { |hash,key| key = key.intern if key.is_a?(String) if hash.include?(key) hash[key] else "Param Documentation for #{key} not found" end } @doc ||= "" end # Returns the name of this type (if specified) or the parent type #to_s. # The returned name is on the form "Puppet::Type::", where the first letter of name is # capitalized. # @return [String] the fully qualified name Puppet::Type:: where the first letter of name is capitalized # def self.to_s if defined?(@name) "Puppet::Type::#{@name.to_s.capitalize}" else super end end # Creates a `validate` method that is used to validate a resource before it is operated on. # The validation should raise exceptions if the validation finds errors. (It is not recommended to # issue warnings as this typically just ends up in a logfile - you should fail if a validation fails). # The easiest way to raise an appropriate exception is to call the method {Puppet::Util::Errors.fail} with # the message as an argument. # # @yield [ ] a required block called with self set to the instance of a Type class representing a resource. # @return [void] # @dsl type # @api public # def self.validate(&block) define_method(:validate, &block) end # @return [String] The file from which this type originates from attr_accessor :file # @return [Integer] The line in {#file} from which this type originates from attr_accessor :line # @todo what does this mean "this resource" (sounds like this if for an instance of the type, not the meta Type), # but not sure if this is about the catalog where the meta Type is included) # @return [??? TODO] The catalog that this resource is stored in. attr_accessor :catalog # @return [Boolean] Flag indicating if this type is exported attr_accessor :exported # @return [Boolean] Flag indicating if the type is virtual (it should not be). attr_accessor :virtual # Creates a log entry with the given message at the log level specified by the parameter `loglevel` # @return [void] # def log(msg) Puppet::Util::Log.create( :level => @parameters[:loglevel].value, :message => msg, :source => self ) end # instance methods related to instance intrinsics # e.g., initialize and name # @return [Hash] hash of parameters originally defined # @api private attr_reader :original_parameters # Creates an instance of Type from a hash or a {Puppet::Resource}. # @todo Unclear if this is a new Type or a new instance of a given type (the initialization ends # with calling validate - which seems like validation of an instance of a given type, not a new # meta type. # # @todo Explain what the Hash and Resource are. There seems to be two different types of # resources; one that causes the title to be set to resource.title, and one that # causes the title to be resource.ref ("for components") - what is a component? # # @overload initialize(hash) # @param [Hash] hash # @raise [Puppet::ResourceError] when the type validation raises # Puppet::Error or ArgumentError # @overload initialize(resource) # @param resource [Puppet:Resource] # @raise [Puppet::ResourceError] when the type validation raises # Puppet::Error or ArgumentError # def initialize(resource) resource = self.class.hash2resource(resource) unless resource.is_a?(Puppet::Resource) # The list of parameter/property instances. @parameters = {} # Set the title first, so any failures print correctly. if resource.type.to_s.downcase.to_sym == self.class.name self.title = resource.title else # This should only ever happen for components self.title = resource.ref end [:file, :line, :catalog, :exported, :virtual].each do |getter| setter = getter.to_s + "=" if val = resource.send(getter) self.send(setter, val) end end merge_tags_from(resource) @original_parameters = resource.to_hash set_name(@original_parameters) set_default(:provider) set_parameters(@original_parameters) begin self.validate if self.respond_to?(:validate) rescue Puppet::Error, ArgumentError => detail error = Puppet::ResourceError.new("Validation of #{ref} failed: #{detail}") adderrorcontext(error, detail) raise error end set_sensitive_parameters(resource.sensitive_parameters) end protected # Mark parameters associated with this type as sensitive, based on the associated resource. # # Currently, only instances of `Puppet::Property` can be easily marked for sensitive data handling # and information redaction is limited to redacting events generated while synchronizing # properties. While support for redaction will be broadened in the future we can't automatically # deduce how to redact arbitrary parameters, so if a parameter is marked for redaction the best # we can do is warn that we can't handle treating that parameter as sensitive and move on. # # In some unusual cases a given parameter will be marked as sensitive but that sensitive context # needs to be transferred to another parameter. In this case resource types may need to override # this method in order to copy the sensitive context from one parameter to another (and in the # process force the early generation of a parameter that might otherwise be lazily generated.) # See `Puppet::Type.type(:file)#set_sensitive_parameters` for an example of this. # # @note This method visibility is protected since it should only be called by #initialize, but is # marked as public as subclasses may need to override this method. # # @api public # # @param sensitive_parameters [Array] A list of parameters to mark as sensitive. # # @return [void] def set_sensitive_parameters(sensitive_parameters) sensitive_parameters.each do |name| p = parameter(name) if p.is_a?(Puppet::Property) p.sensitive = true elsif p.is_a?(Puppet::Parameter) warning(_("Unable to mark '%{name}' as sensitive: %{name} is a parameter and not a property, and cannot be automatically redacted.") % { name: name }) elsif self.class.attrclass(name) warning(_("Unable to mark '%{name}' as sensitive: the property itself was not assigned a value.") % { name: name }) else err(_("Unable to mark '%{name}' as sensitive: the property itself is not defined on %{type}.") % { name: name, type: type }) end end end private # Sets the name of the resource from a hash containing a mapping of `name_var` to value. # Sets the value of the property/parameter appointed by the `name_var` (if it is defined). The value set is # given by the corresponding entry in the given hash - e.g. if name_var appoints the name `:path` the value # of `:path` is set to the value at the key `:path` in the given hash. As a side effect this key/value is then # removed from the given hash. # # @note This method mutates the given hash by removing the entry with a key equal to the value # returned from name_var! # @param hash [Hash] a hash of what # @return [void] def set_name(hash) self[name_var] = hash.delete(name_var) if name_var end # Sets parameters from the given hash. # Values are set in _attribute order_ i.e. higher priority attributes before others, otherwise in # the order they were specified (as opposed to just setting them in the order they happen to appear in # when iterating over the given hash). # # Attributes that are not included in the given hash are set to their default value. # # @todo Is this description accurate? Is "ensure" an example of such a higher priority attribute? # @return [void] # @raise [Puppet::DevError] when impossible to set the value due to some problem # @raise [ArgumentError, TypeError, Puppet::Error] when faulty arguments have been passed # def set_parameters(hash) # Use the order provided by allattrs, but add in any # extra attributes from the resource so we get failures # on invalid attributes. no_values = [] (self.class.allattrs + hash.keys).uniq.each do |attr| begin # Set any defaults immediately. This is mostly done so # that the default provider is available for any other # property validation. if hash.has_key?(attr) self[attr] = hash[attr] else no_values << attr end rescue ArgumentError, Puppet::Error, TypeError raise rescue => detail error = Puppet::DevError.new(_("Could not set %{attribute} on %{class_name}: %{detail}") % { attribute: attr, class_name: self.class.name, detail: detail }) error.set_backtrace(detail.backtrace) raise error end end no_values.each do |attr| set_default(attr) end end public # Finishes any outstanding processing. # This method should be called as a final step in setup, # to allow the parameters that have associated auto-require needs to be processed. # # @todo what is the expected sequence here - who is responsible for calling this? When? # Is the returned type correct? # @return [Array] the validated list/set of attributes # def finish # Call post_compile hook on every parameter that implements it. This includes all subclasses # of parameter including, but not limited to, regular parameters, metaparameters, relationship # parameters, and properties. eachparameter do |parameter| parameter.post_compile if parameter.respond_to? :post_compile end # Make sure all of our relationships are valid. Again, must be done # when the entire catalog is instantiated. self.class.relationship_params.collect do |klass| if param = @parameters[klass.name] param.validate_relationship end end.flatten.reject { |r| r.nil? } end # @comment For now, leave the 'name' method functioning like it used to. Once 'title' # works everywhere, I'll switch it. # Returns the resource's name # @todo There is a comment in source that this is not quite the same as ':title' and that a switch should # be made... # @return [String] the name of a resource def name self[:name] end # Returns the parent of this in the catalog. In case of an erroneous catalog # where multiple parents have been produced, the first found (non # deterministic) parent is returned. # @return [Puppet::Type, nil] the # containing resource or nil if there is no catalog or no containing # resource. def parent return nil unless catalog @parent ||= if parents = catalog.adjacent(self, :direction => :in) parents.shift else nil end end # Returns a reference to this as a string in "Type[name]" format. # @return [String] a reference to this object on the form 'Type[name]' # def ref # memoizing this is worthwhile ~ 3 percent of calls are the "first time # around" in an average run of Puppet. --daniel 2012-07-17 @ref ||= "#{self.class.name.to_s.capitalize}[#{self.title}]" end # (see self_refresh) # @todo check that meaningful yardoc is produced - this method delegates to "self.class.self_refresh" # @return [Boolean] - ??? returns true when ... what? # def self_refresh? self.class.self_refresh end # Marks the object as "being purged". # This method is used by transactions to forbid deletion when there are dependencies. # @todo what does this mean; "mark that we are purging" (purging what from where). How to use/when? # Is this internal API in transactions? # @see purging? def purging @purging = true end # Returns whether this resource is being purged or not. # This method is used by transactions to forbid deletion when there are dependencies. # @return [Boolean] the current "purging" state # def purging? if defined?(@purging) @purging else false end end # Returns the title of this object, or its name if title was not explicitly set. # If the title is not already set, it will be computed by looking up the {#name_var} and using # that value as the title. # @todo it is somewhat confusing that if the name_var is a valid parameter, it is assumed to # be the name_var called :name, but if it is a property, it uses the name_var. # It is further confusing as Type in some respects supports multiple namevars. # # @return [String] Returns the title of this object, or its name if title was not explicitly set. # @raise [??? devfail] if title is not set, and name_var can not be found. def title unless @title if self.class.validparameter?(name_var) @title = self[:name] elsif self.class.validproperty?(name_var) @title = self.should(name_var) else self.devfail "Could not find namevar #{name_var} for #{self.class.name}" end end @title end # Produces a reference to this in reference format. # @see #ref # def to_s self.ref end # Convert this resource type instance to a Puppet::Resource. # @return [Puppet::Resource] Returns a serializable representation of this resource # def to_resource resource = self.retrieve_resource resource.merge_tags_from(self) @parameters.each do |name, param| # Avoid adding each instance name twice next if param.class.isnamevar? and param.value == self.title # We've already got property values next if param.is_a?(Puppet::Property) resource[name] = param.value end resource end # @return [Boolean] Returns whether the resource is virtual or not def virtual?; !!@virtual; end # @return [Boolean] Returns whether the resource is exported or not def exported?; !!@exported; end # @return [Boolean] Returns whether the resource is applicable to `:device` # Returns true if a resource of this type can be evaluated on a 'network device' kind # of hosts. # @api private def appliable_to_device? self.class.can_apply_to(:device) end # @return [Boolean] Returns whether the resource is applicable to `:host` # Returns true if a resource of this type can be evaluated on a regular generalized computer (ie not an appliance like a network device) # @api private def appliable_to_host? self.class.can_apply_to(:host) end end end puppet-5.5.10/lib/puppet/util.rb0000644005276200011600000006007313417161722016406 0ustar jenkinsjenkins# A module to collect utility functions. require 'English' require 'puppet/error' require 'puppet/util/execution_stub' require 'uri' require 'pathname' require 'ostruct' require 'puppet/util/platform' require 'puppet/util/symbolic_file_mode' require 'puppet/file_system/uniquefile' require 'securerandom' require 'puppet/util/character_encoding' module Puppet module Util require 'puppet/util/monkey_patches' require 'benchmark' # These are all for backward compatibility -- these are methods that used # to be in Puppet::Util but have been moved into external modules. require 'puppet/util/posix' extend Puppet::Util::POSIX # Can't use Puppet.features.microsoft_windows? as it may be mocked out in a test. This can cause test recurring test failures require 'puppet/util/windows/process' if Puppet::Util::Platform.windows? extend Puppet::Util::SymbolicFileMode def default_env Puppet.features.microsoft_windows? ? :windows : :posix end module_function :default_env # @param name [String] The name of the environment variable to retrieve # @param mode [Symbol] Which operating system mode to use e.g. :posix or :windows. Use nil to autodetect # @return [String] Value of the specified environment variable. nil if it does not exist # @api private def get_env(name, mode = default_env) if mode == :windows Puppet::Util::Windows::Process.get_environment_strings.each do |key, value | if name.casecmp(key) == 0 then return value end end return nil else ENV[name] end end module_function :get_env # @param mode [Symbol] Which operating system mode to use e.g. :posix or :windows. Use nil to autodetect # @return [Hash] A hashtable of all environment variables # @api private def get_environment(mode = default_env) case mode when :posix ENV.to_hash when :windows Puppet::Util::Windows::Process.get_environment_strings else raise _("Unable to retrieve the environment for mode %{mode}") % { mode: mode } end end module_function :get_environment # Removes all environment variables # @param mode [Symbol] Which operating system mode to use e.g. :posix or :windows. Use nil to autodetect # @api private def clear_environment(mode = default_env) case mode when :posix ENV.clear when :windows Puppet::Util::Windows::Process.get_environment_strings.each do |key, _| Puppet::Util::Windows::Process.set_environment_variable(key, nil) end else raise _("Unable to clear the environment for mode %{mode}") % { mode: mode } end end module_function :clear_environment # @param name [String] The name of the environment variable to set # @param value [String] The value to set the variable to. nil deletes the environment variable # @param mode [Symbol] Which operating system mode to use e.g. :posix or :windows. Use nil to autodetect # @api private def set_env(name, value = nil, mode = default_env) case mode when :posix ENV[name] = value when :windows Puppet::Util::Windows::Process.set_environment_variable(name,value) else raise _("Unable to set the environment variable %{name} for mode %{mode}") % { name: name, mode: mode } end end module_function :set_env # @param name [Hash] Environment variables to merge into the existing environment. nil values will remove the variable # @param mode [Symbol] Which operating system mode to use e.g. :posix or :windows. Use nil to autodetect # @api private def merge_environment(env_hash, mode = default_env) case mode when :posix env_hash.each { |name, val| ENV[name.to_s] = val } when :windows env_hash.each do |name, val| Puppet::Util::Windows::Process.set_environment_variable(name.to_s, val) end else raise _("Unable to merge given values into the current environment for mode %{mode}") % { mode: mode } end end module_function :merge_environment # Run some code with a specific environment. Resets the environment back to # what it was at the end of the code. # Windows can store Unicode chars in the environment as keys or values, but # Ruby's ENV tries to roundtrip them through the local codepage, which can # cause encoding problems - underlying helpers use Windows APIs on Windows # see https://bugs.ruby-lang.org/issues/8822 def withenv(hash, mode = :posix) saved = get_environment(mode) merge_environment(hash, mode) yield ensure if saved clear_environment(mode) merge_environment(saved, mode) end end module_function :withenv # Execute a given chunk of code with a new umask. def self.withumask(mask) cur = File.umask(mask) begin yield ensure File.umask(cur) end end # Change the process to a different user def self.chuser if group = Puppet[:group] begin Puppet::Util::SUIDManager.change_group(group, true) rescue => detail Puppet.warning _("could not change to group %{group}: %{detail}") % { group: group.inspect, detail: detail } $stderr.puts _("could not change to group %{group}") % { group: group.inspect } # Don't exit on failed group changes, since it's # not fatal #exit(74) end end if user = Puppet[:user] begin Puppet::Util::SUIDManager.change_user(user, true) rescue => detail $stderr.puts _("Could not change to user %{user}: %{detail}") % { user: user, detail: detail } exit(74) end end end # Create instance methods for each of the log levels. This allows # the messages to be a little richer. Most classes will be calling this # method. def self.logmethods(klass, useself = true) Puppet::Util::Log.eachlevel { |level| klass.send(:define_method, level, proc { |args| args = args.join(" ") if args.is_a?(Array) if useself Puppet::Util::Log.create( :level => level, :source => self, :message => args ) else Puppet::Util::Log.create( :level => level, :message => args ) end }) } end # execute a block of work and based on the logging level provided, log the provided message with the seconds taken # The message 'msg' should include string ' in %{seconds} seconds' as part of the message and any content should escape # any percent signs '%' so that they are not interpreted as formatting commands # escaped_str = str.gsub(/%/, '%%') # # @param msg [String] the message to be formated to assigned the %{seconds} seconds take to execute, # other percent signs '%' need to be escaped # @param level [Symbol] the logging level for this message # @param object [Object] The object use for logging the message def benchmark(*args) msg = args.pop level = args.pop object = if args.empty? if respond_to?(level) self else Puppet end else args.pop end #TRANSLATORS 'benchmark' is a method name and should not be translated raise Puppet::DevError, _("Failed to provide level to benchmark") unless level unless level == :none or object.respond_to? level raise Puppet::DevError, _("Benchmarked object does not respond to %{value}") % { value: level } end # Only benchmark if our log level is high enough if level != :none and Puppet::Util::Log.sendlevel?(level) seconds = Benchmark.realtime { yield } object.send(level, msg % { seconds: "%0.2f" % seconds }) return seconds else yield end end module_function :benchmark # Resolve a path for an executable to the absolute path. This tries to behave # in the same manner as the unix `which` command and uses the `PATH` # environment variable. # # @api public # @param bin [String] the name of the executable to find. # @return [String] the absolute path to the found executable. def which(bin) if absolute_path?(bin) return bin if FileTest.file? bin and FileTest.executable? bin else exts = Puppet::Util.get_env('PATHEXT') exts = exts ? exts.split(File::PATH_SEPARATOR) : %w[.COM .EXE .BAT .CMD] Puppet::Util.get_env('PATH').split(File::PATH_SEPARATOR).each do |dir| begin dest = File.expand_path(File.join(dir, bin)) rescue ArgumentError => e # if the user's PATH contains a literal tilde (~) character and HOME is not set, we may get # an ArgumentError here. Let's check to see if that is the case; if not, re-raise whatever error # was thrown. if e.to_s =~ /HOME/ and (Puppet::Util.get_env('HOME').nil? || Puppet::Util.get_env('HOME') == "") # if we get here they have a tilde in their PATH. We'll issue a single warning about this and then # ignore this path element and carry on with our lives. #TRANSLATORS PATH and HOME are environment variables and should not be translated Puppet::Util::Warnings.warnonce(_("PATH contains a ~ character, and HOME is not set; ignoring PATH element '%{dir}'.") % { dir: dir }) elsif e.to_s =~ /doesn't exist|can't find user/ # ...otherwise, we just skip the non-existent entry, and do nothing. #TRANSLATORS PATH is an environment variable and should not be translated Puppet::Util::Warnings.warnonce(_("Couldn't expand PATH containing a ~ character; ignoring PATH element '%{dir}'.") % { dir: dir }) else raise end else if Puppet.features.microsoft_windows? && File.extname(dest).empty? exts.each do |ext| destext = File.expand_path(dest + ext) return destext if FileTest.file? destext and FileTest.executable? destext end end return dest if FileTest.file? dest and FileTest.executable? dest end end end nil end module_function :which # Determine in a platform-specific way whether a path is absolute. This # defaults to the local platform if none is specified. # # Escape once for the string literal, and once for the regex. slash = '[\\\\/]' label = '[^\\\\/]+' AbsolutePathWindows = %r!^(?:(?:[A-Z]:#{slash})|(?:#{slash}#{slash}#{label}#{slash}#{label})|(?:#{slash}#{slash}\?#{slash}#{label}))!io AbsolutePathPosix = %r!^/! def absolute_path?(path, platform=nil) # Ruby only sets File::ALT_SEPARATOR on Windows and the Ruby standard # library uses that to test what platform it's on. Normally in Puppet we # would use Puppet.features.microsoft_windows?, but this method needs to # be called during the initialization of features so it can't depend on # that. # # @deprecated Use ruby's built-in methods to determine if a path is absolute. platform ||= Puppet::Util::Platform.windows? ? :windows : :posix regex = case platform when :windows AbsolutePathWindows when :posix AbsolutePathPosix else raise Puppet::DevError, _("unknown platform %{platform} in absolute_path") % { platform: platform } end !! (path =~ regex) end module_function :absolute_path? # Convert a path to a file URI def path_to_uri(path) return unless path params = { :scheme => 'file' } if Puppet.features.microsoft_windows? path = path.gsub(/\\/, '/') if unc = /^\/\/([^\/]+)(\/.+)/.match(path) params[:host] = unc[1] path = unc[2] elsif path =~ /^[a-z]:\//i path = '/' + path end end # have to split *after* any relevant escaping params[:path], params[:query] = uri_encode(path).split('?') search_for_fragment = params[:query] ? :query : :path if params[search_for_fragment].include?('#') params[search_for_fragment], _, params[:fragment] = params[search_for_fragment].rpartition('#') end begin URI::Generic.build(params) rescue => detail raise Puppet::Error, _("Failed to convert '%{path}' to URI: %{detail}") % { path: path, detail: detail }, detail.backtrace end end module_function :path_to_uri # Get the path component of a URI def uri_to_path(uri) return unless uri.is_a?(URI) # CGI.unescape doesn't handle space rules properly in uri paths # URI.unescape does, but returns strings in their original encoding path = URI.unescape(uri.path.encode(Encoding::UTF_8)) if Puppet.features.microsoft_windows? and uri.scheme == 'file' if uri.host path = "//#{uri.host}" + path # UNC else path.sub!(/^\//, '') end end path end module_function :uri_to_path RFC_3986_URI_REGEX = /^(?([^:\/?#]+):)?(?\/\/([^\/?#]*))?(?[^?#]*)(\?(?[^#]*))?(#(?.*))?$/ # Percent-encodes a URI query parameter per RFC3986 - https://tools.ietf.org/html/rfc3986 # # The output will correctly round-trip through URI.unescape # # @param [String query_string] A URI query parameter that may contain reserved # characters that must be percent encoded for the key or value to be # properly decoded as part of a larger query string: # # query # encodes as : query # # query_with_special=chars like&and * and# plus+this # encodes as: # query_with_special%3Dchars%20like%26and%20%2A%20and%23%20plus%2Bthis # # Note: Also usable by fragments, but not suitable for paths # # @return [String] a new string containing an encoded query string per the # rules of RFC3986. # # In particular, # query will encode + as %2B and space as %20 def uri_query_encode(query_string) return nil if query_string.nil? # query can encode space to %20 OR + # + MUST be encoded as %2B # in RFC3968 both query and fragment are defined as: # = *( pchar / "/" / "?" ) # CGI.escape turns space into + which is the most backward compatible # however it doesn't roundtrip through URI.unescape which prefers %20 CGI.escape(query_string).gsub('+', '%20') end module_function :uri_query_encode # Percent-encodes a URI string per RFC3986 - https://tools.ietf.org/html/rfc3986 # # Properly handles escaping rules for paths, query strings and fragments # independently # # The output is safe to pass to URI.parse or URI::Generic.build and will # correctly round-trip through URI.unescape # # @param [String path] A URI string that may be in the form of: # # http://foo.com/bar?query # file://tmp/foo bar # //foo.com/bar?query # /bar?query # bar?query # bar # . # C:\Windows\Temp # # Note that with no specified scheme, authority or query parameter delimiter # ? that a naked string will be treated as a path. # # Note that if query parameters need to contain data such as & or = # that this method should not be used, as there is no way to differentiate # query parameter data from query delimiters when multiple parameters # are specified # # @param [Hash{Symbol=>String} opts] Options to alter encoding # @option opts [Array] :allow_fragment defaults to false. When false # will treat # as part of a path or query and not a fragment delimiter # # @return [String] a new string containing appropriate portions of the URI # encoded per the rules of RFC3986. # In particular, # path will not encode +, but will encode space as %20 # query will encode + as %2B and space as %20 # fragment behaves like query def uri_encode(path, opts = { :allow_fragment => false }) raise ArgumentError.new(_('path may not be nil')) if path.nil? # ensure string starts as UTF-8 for the sake of Ruby 1.9.3 encoded = ''.encode!(Encoding::UTF_8) # parse uri into named matches, then reassemble properly encoded parts = path.match(RFC_3986_URI_REGEX) encoded += parts[:scheme] unless parts[:scheme].nil? encoded += parts[:authority] unless parts[:authority].nil? # path requires space to be encoded as %20 (NEVER +) # + should be left unencoded # URI::parse and URI::Generic.build don't like paths encoded with CGI.escape # URI.escape does not change / to %2F and : to %3A like CGI.escape encoded += URI.escape(parts[:path]) unless parts[:path].nil? # each query parameter if !parts[:query].nil? query_string = parts[:query].split('&').map do |pair| # can optionally be separated by an = pair.split('=').map do |v| uri_query_encode(v) end.join('=') end.join('&') encoded += '?' + query_string end encoded += ((opts[:allow_fragment] ? '#' : '%23') + uri_query_encode(parts[:fragment])) unless parts[:fragment].nil? encoded end module_function :uri_encode def safe_posix_fork(stdin=$stdin, stdout=$stdout, stderr=$stderr, &block) child_pid = Kernel.fork do STDIN.reopen(stdin) STDOUT.reopen(stdout) STDERR.reopen(stderr) $stdin = STDIN $stdout = STDOUT $stderr = STDERR begin Dir.foreach('/proc/self/fd') do |f| if f != '.' && f != '..' && f.to_i >= 3 IO::new(f.to_i).close rescue nil end end rescue Errno::ENOENT # /proc/self/fd not found 3.upto(256){|fd| IO::new(fd).close rescue nil} end block.call if block end child_pid end module_function :safe_posix_fork def symbolizehash(hash) newhash = {} hash.each do |name, val| name = name.intern if name.respond_to? :intern newhash[name] = val end newhash end module_function :symbolizehash # Just benchmark, with no logging. def thinmark seconds = Benchmark.realtime { yield } seconds end module_function :thinmark # utility method to get the current call stack and format it to a human-readable string (which some IDEs/editors # will recognize as links to the line numbers in the trace) def self.pretty_backtrace(backtrace = caller(1)) backtrace.collect do |line| _, path, rest = /^(.*):(\d+.*)$/.match(line).to_a # If the path doesn't exist - like in one test, and like could happen in # the world - we should just tolerate it and carry on. --daniel 2012-09-05 # Also, if we don't match, just include the whole line. if path path = Pathname(path).realpath rescue path "#{path}:#{rest}" else line end end.join("\n") end # Replace a file, securely. This takes a block, and passes it the file # handle of a file open for writing. Write the replacement content inside # the block and it will safely replace the target file. # # This method will make no changes to the target file until the content is # successfully written and the block returns without raising an error. # # As far as possible the state of the existing file, such as mode, is # preserved. This works hard to avoid loss of any metadata, but will result # in an inode change for the file. # # Arguments: `filename`, `default_mode` # # The filename is the file we are going to replace. # # The default_mode is the mode to use when the target file doesn't already # exist; if the file is present we copy the existing mode/owner/group values # across. The default_mode can be expressed as an octal integer, a numeric string (ie '0664') # or a symbolic file mode. DEFAULT_POSIX_MODE = 0644 DEFAULT_WINDOWS_MODE = nil def replace_file(file, default_mode, &block) raise Puppet::DevError, _("replace_file requires a block") unless block_given? if default_mode unless valid_symbolic_mode?(default_mode) raise Puppet::DevError, _("replace_file default_mode: %{default_mode} is invalid") % { default_mode: default_mode } end mode = symbolic_mode_to_int(normalize_symbolic_mode(default_mode)) else if Puppet.features.microsoft_windows? mode = DEFAULT_WINDOWS_MODE else mode = DEFAULT_POSIX_MODE end end begin file = Puppet::FileSystem.pathname(file) # encoding for Uniquefile is not important here because the caller writes to it as it sees fit tempfile = Puppet::FileSystem::Uniquefile.new(Puppet::FileSystem.basename_string(file), Puppet::FileSystem.dir_string(file)) effective_mode = if !Puppet.features.microsoft_windows? # Grab the current file mode, and fall back to the defaults. if Puppet::FileSystem.exist?(file) stat = Puppet::FileSystem.lstat(file) tempfile.chown(stat.uid, stat.gid) stat.mode else mode end end # OK, now allow the caller to write the content of the file. yield tempfile if effective_mode # We only care about the bottom four slots, which make the real mode, # and not the rest of the platform stat call fluff and stuff. tempfile.chmod(effective_mode & 07777) end # Now, make sure the data (which includes the mode) is safe on disk. tempfile.flush begin tempfile.fsync rescue NotImplementedError # fsync may not be implemented by Ruby on all platforms, but # there is absolutely no recovery path if we detect that. So, we just # ignore the return code. # # However, don't be fooled: that is accepting that we are running in # an unsafe fashion. If you are porting to a new platform don't stub # that out. end tempfile.close if Puppet.features.microsoft_windows? # Windows ReplaceFile needs a file to exist, so touch handles this if !Puppet::FileSystem.exist?(file) Puppet::FileSystem.touch(file) if mode Puppet::Util::Windows::Security.set_mode(mode, Puppet::FileSystem.path_string(file)) end end # Yes, the arguments are reversed compared to the rename in the rest # of the world. Puppet::Util::Windows::File.replace_file(FileSystem.path_string(file), tempfile.path) else File.rename(tempfile.path, Puppet::FileSystem.path_string(file)) end ensure # in case an error occurred before we renamed the temp file, make sure it # gets deleted if tempfile tempfile.close! end end # Ideally, we would now fsync the directory as well, but Ruby doesn't # have support for that, and it doesn't matter /that/ much... # Return something true, and possibly useful. file end module_function :replace_file # Executes a block of code, wrapped with some special exception handling. Causes the ruby interpreter to # exit if the block throws an exception. # # @api public # @param [String] message a message to log if the block fails # @param [Integer] code the exit code that the ruby interpreter should return if the block fails # @yield def exit_on_fail(message, code = 1) yield # First, we need to check and see if we are catching a SystemExit error. These will be raised # when we daemonize/fork, and they do not necessarily indicate a failure case. rescue SystemExit => err raise err # Now we need to catch *any* other kind of exception, because we may be calling third-party # code (e.g. webrick), and we have no idea what they might throw. rescue Exception => err ## NOTE: when debugging spec failures, these two lines can be very useful #puts err.inspect #puts Puppet::Util.pretty_backtrace(err.backtrace) Puppet.log_exception(err, "#{message}: #{err}") Puppet::Util::Log.force_flushqueue() exit(code) end module_function :exit_on_fail def deterministic_rand(seed,max) deterministic_rand_int(seed, max).to_s end module_function :deterministic_rand def deterministic_rand_int(seed,max) Random.new(seed).rand(max) end module_function :deterministic_rand_int end end require 'puppet/util/errors' require 'puppet/util/methodhelper' require 'puppet/util/metaid' require 'puppet/util/classgen' require 'puppet/util/docs' require 'puppet/util/execution' require 'puppet/util/logging' require 'puppet/util/package' require 'puppet/util/warnings' puppet-5.5.10/lib/puppet/version.rb0000644005276200011600000000730013417161722017110 0ustar jenkinsjenkins# The version method and constant are isolated in puppet/version.rb so that a # simple `require 'puppet/version'` allows a rubygems gemspec or bundler # Gemfile to get the Puppet version of the gem install. # # The version can be set programmatically because we want to allow the # Raketasks and such to set the version based on the output of `git describe` module Puppet PUPPETVERSION = '5.5.10' ## # version is a public API method intended to always provide a fast and # lightweight way to determine the version of Puppet. # # The intent is that software external to Puppet be able to determine the # Puppet version with no side-effects. The expected use is: # # require 'puppet/version' # version = Puppet.version # # This function has the following ordering precedence. This precedence list # is designed to facilitate automated packaging tasks by simply writing to # the VERSION file in the same directory as this source file. # # 1. If a version has been explicitly assigned using the Puppet.version= # method, return that version. # 2. If there is a VERSION file, read the contents, trim any # trailing whitespace, and return that version string. # 3. Return the value of the Puppet::PUPPETVERSION constant hard-coded into # the source code. # # If there is no VERSION file, the method must return the version string of # the nearest parent version that is an officially released version. That is # to say, if a branch named 3.1.x contains 25 patches on top of the most # recent official release of 3.1.1, then the version method must return the # string "3.1.1" if no "VERSION" file is present. # # By design the version identifier is _not_ intended to vary during the life # a process. There is no guarantee provided that writing to the VERSION file # while a Puppet process is running will cause the version string to be # updated. On the contrary, the contents of the VERSION are cached to reduce # filesystem accesses. # # The VERSION file is intended to be used by package maintainers who may be # applying patches or otherwise changing the software version in a manner # that warrants a different software version identifier. The VERSION file is # intended to be managed and owned by the release process and packaging # related tasks, and as such should not reside in version control. The # PUPPETVERSION constant is intended to be version controlled in history. # # Ideally, this behavior will allow package maintainers to precisely specify # the version of the software they're packaging as in the following example: # # $ git describe --match "3.0.*" > lib/puppet/VERSION # $ ruby -r puppet -e 'puts Puppet.version' # 3.0.1-260-g9ca4e54 # # @api public # # @return [String] containing the puppet version, e.g. "3.0.1" def self.version version_file = File.join(File.dirname(__FILE__), 'VERSION') return @puppet_version if @puppet_version if version = read_version_file(version_file) @puppet_version = version end @puppet_version ||= PUPPETVERSION end # @return [String] containing the puppet version to minor specificity, e.g. "3.0" def self.minor_version self.version.split('.')[0..1].join('.') end def self.version=(version) @puppet_version = version end ## # read_version_file reads the content of the "VERSION" file that lives in the # same directory as this source code file. # # @api private # # @return [String] for example: "1.6.14-6-gea42046" or nil if the VERSION # file does not exist. def self.read_version_file(path) if File.exist?(path) File.read(path).chomp end end private_class_method :read_version_file end puppet-5.5.10/lib/puppet_x.rb0000644005276200011600000000073313417161721015754 0ustar jenkinsjenkins# The Puppet Extensions Module. # # Submodules of this module should be named after the publisher (e.g. # 'user' part of a Puppet Module name). You should also add the module # name to avoid further conflicts in the same namespace. So the # structure should be # `lib/puppet_x///`. # A Puppet Extension for 'puppetlabs-powershell' would live at # `lib/puppet_x/puppetlabs/powershell/`. # # @api public # module PuppetX end puppet-5.5.10/lib/puppet.rb0000644005276200011600000002541313417161721015427 0ustar jenkinsjenkinsrequire 'puppet/version' if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new("1.9.3") raise LoadError, _("Puppet %{version} requires ruby 1.9.3 or greater.") % { version: Puppet.version } end Puppet::OLDEST_RECOMMENDED_RUBY_VERSION = '2.3.0' # see the bottom of the file for further inclusions # Also see the new Vendor support - towards the end # require 'facter' require 'puppet/error' require 'puppet/util' require 'puppet/util/autoload' require 'puppet/settings' require 'puppet/util/feature' require 'puppet/util/suidmanager' require 'puppet/util/run_mode' # PSON is deprecated, use JSON instead require 'puppet/external/pson/common' require 'puppet/external/pson/version' require 'puppet/external/pson/pure' require 'puppet/gettext/config' #------------------------------------------------------------ # the top-level module # # all this really does is dictate how the whole system behaves, through # preferences for things like debugging # # it's also a place to find top-level commands like 'debug' # The main Puppet class. Everything is contained here. # # @api public module Puppet require 'puppet/file_system' require 'puppet/etc' require 'puppet/context' require 'puppet/environments' class << self Puppet::GettextConfig.setup_locale Puppet::GettextConfig.create_default_text_domain include Puppet::Util attr_reader :features end # the hash that determines how our system behaves @@settings = Puppet::Settings.new # Note: It's important that these accessors (`self.settings`, `self.[]`) are # defined before we try to load any "features" (which happens a few lines below), # because the implementation of the features loading may examine the values of # settings. def self.settings @@settings end # Get the value for a setting # # @param [Symbol] param the setting to retrieve # # @api public def self.[](param) if param == :debug return Puppet::Util::Log.level == :debug else return @@settings[param] end end require 'puppet/util/logging' extend Puppet::Util::Logging # Setup facter's logging Puppet::Util::Logging.setup_facter_logging! # The feature collection @features = Puppet::Util::Feature.new('puppet/feature') # Load the base features. require 'puppet/feature/base' # Store a new default value. def self.define_settings(section, hash) @@settings.define_settings(section, hash) end # setting access and stuff def self.[]=(param,value) @@settings[param] = value end def self.clear @@settings.clear end def self.debug=(value) if value Puppet::Util::Log.level=(:debug) else Puppet::Util::Log.level=(:notice) end end def self.run_mode # This sucks (the existence of this method); there are a lot of places in our code that branch based the value of # "run mode", but there used to be some really confusing code paths that made it almost impossible to determine # when during the lifecycle of a puppet application run the value would be set properly. A lot of the lifecycle # stuff has been cleaned up now, but it still seems frightening that we rely so heavily on this value. # # I'd like to see about getting rid of the concept of "run_mode" entirely, but there are just too many places in # the code that call this method at the moment... so I've settled for isolating it inside of the Settings class # (rather than using a global variable, as we did previously...). Would be good to revisit this at some point. # # --cprice 2012-03-16 Puppet::Util::RunMode[@@settings.preferred_run_mode] end # Load all of the settings. require 'puppet/defaults' # Now that settings are loaded we have the code loaded to be able to issue # deprecation warnings. Warn if we're on a deprecated ruby version. if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new(Puppet::OLDEST_RECOMMENDED_RUBY_VERSION) Puppet.deprecation_warning(_("Support for ruby version %{version} is deprecated and will be removed in a future release. See https://puppet.com/docs/puppet/latest/system_requirements.html for a list of supported ruby versions.") % { version: RUBY_VERSION }) end # Initialize puppet's settings. This is intended only for use by external tools that are not # built off of the Faces API or the Puppet::Util::Application class. It may also be used # to initialize state so that a Face may be used programatically, rather than as a stand-alone # command-line tool. # # @api public # @param args [Array] the command line arguments to use for initialization # @return [void] def self.initialize_settings(args = []) do_initialize_settings_for_run_mode(:user, args) end # private helper method to provide the implementation details of initializing for a run mode, # but allowing us to control where the deprecation warning is issued def self.do_initialize_settings_for_run_mode(run_mode, args) Puppet.settings.initialize_global_settings(args) run_mode = Puppet::Util::RunMode[run_mode] Puppet.settings.initialize_app_defaults(Puppet::Settings.app_defaults_for_run_mode(run_mode)) Puppet.push_context(Puppet.base_context(Puppet.settings), "Initial context after settings initialization") Puppet::Parser::Functions.reset end private_class_method :do_initialize_settings_for_run_mode # Initialize puppet's core facts. It should not be called before initialize_settings. def self.initialize_facts # Add the puppetversion fact; this is done before generating the hash so it is # accessible to custom facts. Facter.add(:puppetversion) do setcode { Puppet.version.to_s } end Facter.add(:agent_specified_environment) do setcode do if Puppet.settings.set_by_config?(:environment) Puppet[:environment] end end end end # Create a new type. Just proxy to the Type class. The mirroring query # code was deprecated in 2008, but this is still in heavy use. I suppose # this can count as a soft deprecation for the next dev. --daniel 2011-04-12 def self.newtype(name, options = {}, &block) Puppet.deprecation_warning(_("Creating %{name} via Puppet.newtype is deprecated and will be removed in a future release. Use Puppet::Type.newtype instead.") % { name: name }) Puppet::Type.newtype(name, options, &block) end # Load vendored (setup paths, and load what is needed upfront). # See the Vendor class for how to add additional vendored gems/code require "puppet/vendor" Puppet::Vendor.load_vendored # The bindings used for initialization of puppet # # @param settings [Puppet::Settings,Hash] either a Puppet::Settings instance # or a Hash of settings key/value pairs. # @api private def self.base_context(settings) environmentpath = settings[:environmentpath] basemodulepath = Puppet::Node::Environment.split_path(settings[:basemodulepath]) if environmentpath.nil? || environmentpath.empty? raise(Puppet::Error, _("The environmentpath setting cannot be empty or nil.")) else loaders = Puppet::Environments::Directories.from_path(environmentpath, basemodulepath) # in case the configured environment (used for the default sometimes) # doesn't exist default_environment = Puppet[:environment].to_sym if default_environment == :production modulepath = settings[:modulepath] modulepath = (modulepath.nil? || '' == modulepath) ? basemodulepath : Puppet::Node::Environment.split_path(modulepath) loaders << Puppet::Environments::StaticPrivate.new( Puppet::Node::Environment.create(default_environment, modulepath, Puppet::Node::Environment::NO_MANIFEST)) end end { :environments => Puppet::Environments::Cached.new(Puppet::Environments::Combined.new(*loaders)), :http_pool => proc { require 'puppet/network/http' Puppet::Network::HTTP::NoCachePool.new }, :ssl_host => proc { Puppet::SSL::Host.localhost }, :certificate_revocation => proc { Puppet[:certificate_revocation] }, :plugins => proc { Puppet::Plugins::Configuration.load_plugins } } end # A simple set of bindings that is just enough to limp along to # initialization where the {base_context} bindings are put in place # @api private def self.bootstrap_context root_environment = Puppet::Node::Environment.create(:'*root*', [], Puppet::Node::Environment::NO_MANIFEST) { :current_environment => root_environment, :root_environment => root_environment } end # @param overrides [Hash] A hash of bindings to be merged with the parent context. # @param description [String] A description of the context. # @api private def self.push_context(overrides, description = "") @context.push(overrides, description) end # Return to the previous context. # @raise [StackUnderflow] if the current context is the root # @api private def self.pop_context @context.pop end # Lookup a binding by name or return a default value provided by a passed block (if given). # @api private def self.lookup(name, &block) @context.lookup(name, &block) end # @param bindings [Hash] A hash of bindings to be merged with the parent context. # @param description [String] A description of the context. # @yield [] A block executed in the context of the temporarily pushed bindings. # @api private def self.override(bindings, description = "", &block) @context.override(bindings, description, &block) end # @param name The name of a context key to ignore; intended for test usage. # @api private def self.ignore(name) @context.ignore(name) end # @param name The name of a previously ignored context key to restore; intended for test usage. # @api private def self.restore(name) @context.restore(name) end # @api private def self.mark_context(name) @context.mark(name) end # @api private def self.rollback_context(name) @context.rollback(name) end require 'puppet/node' # The single instance used for normal operation @context = Puppet::Context.new(bootstrap_context) end # This feels weird to me; I would really like for us to get to a state where there is never a "require" statement # anywhere besides the very top of a file. That would not be possible at the moment without a great deal of # effort, but I think we should strive for it and revisit this at some point. --cprice 2012-03-16 require 'puppet/indirector' require 'puppet/compilable_resource_type' require 'puppet/type' require 'puppet/resource' require 'puppet/parser' require 'puppet/network' require 'puppet/ssl' require 'puppet/module' require 'puppet/data_binding' require 'puppet/util/storage' require 'puppet/status' require 'puppet/file_bucket/file' require 'puppet/plugins/configuration' puppet-5.5.10/lib/puppet_pal.rb0000644005276200011600000011620213417161722016261 0ustar jenkinsjenkins# Puppet as a Library "PAL" # Yes, this requires all of puppet for now because 'settings' and many other things... require 'puppet' require 'puppet/parser/script_compiler' # This is the main entry point for "Puppet As a Library" PAL. # This file should be required instead of "puppet" # Initially, this will require ALL of puppet - over time this will change as the monolithical "puppet" is broken up # into smaller components. # # @example Running a snippet of Puppet Language code # require 'puppet_pal' # result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: ['/tmp/testmodules']) do |pal| # pal.evaluate_script_string('1+2+3') # end # # The result is the value 6 # # @example Calling a function # require 'puppet_pal' # result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: ['/tmp/testmodules']) do |pal| # pal.call_function('mymodule::myfunction', 10, 20) # end # # The result is what 'mymodule::myfunction' returns # module Puppet module Pal # A configured compiler as obtained in the callback from `with_script_compiler`. # (Later, there may also be a catalog compiler available.) # class Compiler attr_reader :internal_compiler protected :internal_compiler attr_reader :internal_evaluator protected :internal_evaluator def initialize(internal_compiler) @internal_compiler = internal_compiler @internal_evaluator = Puppet::Pops::Parser::EvaluatingParser.new end # Calls a function given by name with arguments specified in an `Array`, and optionally accepts a code block. # @param function_name [String] the name of the function to call # @param args [Object] the arguments to the function # @param block [Proc] an optional callable block that is given to the called function # @return [Object] what the called function returns # def call_function(function_name, *args, &block) # TRANSLATORS: do not translate variable name strings in these assertions Pal::assert_non_empty_string(function_name, 'function_name', false) Pal::assert_type(Pal::T_ANY_ARRAY, args, 'args', false) internal_evaluator.evaluator.external_call_function(function_name, args, topscope, &block) end # Returns a Puppet::Pal::FunctionSignature object or nil if function is not found # The returned FunctionSignature has information about all overloaded signatures of the function # # @example using function_signature # # returns true if 'myfunc' is callable with three integer arguments 1, 2, 3 # compiler.function_signature('myfunc').callable_with?([1,2,3]) # # @param function_name [String] the name of the function to get a signature for # @return [Puppet::Pal::FunctionSignature] a function signature, or nil if function not found # def function_signature(function_name) loader = internal_compiler.loaders.private_environment_loader if func = loader.load(:function, function_name) return FunctionSignature.new(func.class) end # Could not find function nil end # Returns an array of TypedName objects for all functions, optionally filtered by a regular expression. # The returned array has more information than just the leaf name - the typical thing is to just get # the name as showing the following example. # # Errors that occur during function discovery will either be logged as warnings or collected by the optional # `error_collector` array. When provided, it will receive {Puppet::DataTypes::Error} instances describing # each error in detail and no warnings will be logged. # # @example getting the names of all functions # compiler.list_functions.map {|tn| tn.name } # # @param filter_regex [Regexp] an optional regexp that filters based on name (matching names are included in the result) # @param error_collector [Array] an optional array that will receive errors during load # @return [Array] an array of typed names # def list_functions(filter_regex = nil, error_collector = nil) list_loadable_kind(:function, filter_regex, error_collector) end # Evaluates a string of puppet language code in top scope. # A "source_file" reference to a source can be given - if not an actual file name, by convention the name should # be bracketed with < > to indicate it is something symbolic; for example `` if the string was given on the # command line. # # If the given `puppet_code` is `nil` or an empty string, `nil` is returned, otherwise the result of evaluating the # puppet language string. The given string must form a complete and valid expression/statement as an error is raised # otherwise. That is, it is not possible to divide a compound expression by line and evaluate each line individually. # # @param puppet_code [String, nil] the puppet language code to evaluate, must be a complete expression/statement # @param source_file [String, nil] an optional reference to a source (a file or symbolic name/location) # @return [Object] what the `puppet_code` evaluates to # def evaluate_string(puppet_code, source_file = nil) return nil if puppet_code.nil? || puppet_code == '' unless puppet_code.is_a?(String) raise ArgumentError, _("The argument 'puppet_code' must be a String, got %{type}") % { type: puppet_code.class } end evaluate(parse_string(puppet_code, source_file)) end # Evaluates a puppet language file in top scope. # The file must exist and contain valid puppet language code or an error is raised. # # @param file [Path, String] an absolute path to a file with puppet language code, must exist # @return [Object] what the last evaluated expression in the file evaluated to # def evaluate_file(file) evaluate(parse_file(file)) end # Evaluates an AST obtained from `parse_string` or `parse_file` in topscope. # If the ast is a `Puppet::Pops::Model::Program` (what is returned from the `parse` methods, any definitions # in the program (that is, any function, plan, etc. that is defined will be made available for use). # # @param ast [Puppet::Pops::Model::PopsObject] typically the returned `Program` from the parse methods, but can be any `Expression` # @returns [Object] whatever the ast evaluates to # def evaluate(ast) if ast.is_a?(Puppet::Pops::Model::Program) loaders = Puppet.lookup(:loaders) loaders.instantiate_definitions(ast, loaders.public_environment_loader) end internal_evaluator.evaluate(topscope, ast) end # Produces a literal value if the AST obtained from `parse_string` or `parse_file` does not require any actual evaluation. # This method is useful if obtaining an AST that represents literal values; string, integer, float, boolean, regexp, array, hash; # for example from having read this from the command line or as values in some file. # # @param ast [Puppet::Pops::Model::PopsObject] typically the returned `Program` from the parse methods, but can be any `Expression` # @returns [Object] whatever the literal value the ast evaluates to # def evaluate_literal(ast) catch :not_literal do return Puppet::Pops::Evaluator::LiteralEvaluator.new().literal(ast) end # TRANSLATORS, the 'ast' is the name of a parameter, do not translate raise ArgumentError, _("The given 'ast' does not represent a literal value") end # Parses and validates a puppet language string and returns an instance of Puppet::Pops::Model::Program on success. # If the content is not valid an error is raised. # # @param code_string [String] a puppet language string to parse and validate # @param source_file [String] an optional reference to a file or other location in angled brackets # @return [Puppet::Pops::Model::Program] returns a `Program` instance on success # def parse_string(code_string, source_file = nil) unless code_string.is_a?(String) raise ArgumentError, _("The argument 'code_string' must be a String, got %{type}") % { type: code_string.class } end internal_evaluator.parse_string(code_string, source_file) end # Parses and validates a puppet language file and returns an instance of Puppet::Pops::Model::Program on success. # If the content is not valid an error is raised. # # @param file [String] a file with puppet language content to parse and validate # @return [Puppet::Pops::Model::Program] returns a `Program` instance on success # def parse_file(file) unless file.is_a?(String) raise ArgumentError, _("The argument 'file' must be a String, got %{type}") % { type: puppet_code.class } end internal_evaluator.parse_file(file) end # Parses a puppet data type given in String format and returns that type, or raises an error. # A type is needed in calls to `new` to create an instance of the data type, or to perform type checking # of values - typically using `type.instance?(obj)` to check if `obj` is an instance of the type. # # @example Verify if obj is an instance of a data type # # evaluates to true # pal.type('Enum[red, blue]').instance?("blue") # # @example Create an instance of a data type # # using an already create type # t = pal.type('Car') # pal.create(t, 'color' => 'black', 'make' => 't-ford') # # # letting 'new_object' parse the type from a string # pal.create('Car', 'color' => 'black', 'make' => 't-ford') # # @param type_string [String] a puppet language data type # @return [Puppet::Pops::Types::PAnyType] the data type # def type(type_string) Puppet::Pops::Types::TypeParser.singleton.parse(type_string) end # Creates a new instance of a given data type. # @param data_type [String, Puppet::Pops::Types::PAnyType] the data type as a data type or in String form. # @param arguments [Object] one or more arguments to the called `new` function # @return [Object] an instance of the given data type, # or raises an error if it was not possible to parse data type or create an instance. # def create(data_type, *arguments) t = data_type.is_a?(String) ? type(data_type) : data_type unless t.is_a?(Puppet::Pops::Types::PAnyType) raise ArgumentError, _("Given data_type value is not a data type, got '%{type}'") % {type: t.class} end call_function('new', t, *arguments) end protected def list_loadable_kind(kind, filter_regex = nil, error_collector = nil) loader = internal_compiler.loaders.private_environment_loader if filter_regex.nil? loader.discover(kind, error_collector) else loader.discover(kind, error_collector) {|f| f.name =~ filter_regex } end end private def topscope internal_compiler.topscope end end class ScriptCompiler < Compiler # Returns the signature of the given plan name # @param plan_name [String] the name of the plan to get the signature of # @return [Puppet::Pal::PlanSignature, nil] returns a PlanSignature, or nil if plan is not found # def plan_signature(plan_name) loader = internal_compiler.loaders.private_environment_loader if func = loader.load(:plan, plan_name) return PlanSignature.new(func) end # Could not find plan nil end # Returns an array of TypedName objects for all plans, optionally filtered by a regular expression. # The returned array has more information than just the leaf name - the typical thing is to just get # the name as showing the following example. # # Errors that occur during plan discovery will either be logged as warnings or collected by the optional # `error_collector` array. When provided, it will receive {Puppet::DataTypes::Error} instances describing # each error in detail and no warnings will be logged. # # @example getting the names of all plans # compiler.list_plans.map {|tn| tn.name } # # @param filter_regex [Regexp] an optional regexp that filters based on name (matching names are included in the result) # @param error_collector [Array] an optional array that will receive errors during load # @return [Array] an array of typed names # def list_plans(filter_regex = nil, error_collector = nil) list_loadable_kind(:plan, filter_regex, error_collector) end # Returns the signature callable of the given task (the arguments it accepts, and the data type it returns) # @param task_name [String] the name of the task to get the signature of # @return [Puppet::Pal::TaskSignature, nil] returns a TaskSignature, or nil if task is not found # def task_signature(task_name) loader = internal_compiler.loaders.private_environment_loader if task = loader.load(:task, task_name) return TaskSignature.new(task) end # Could not find task nil end # Returns an array of TypedName objects for all tasks, optionally filtered by a regular expression. # The returned array has more information than just the leaf name - the typical thing is to just get # the name as showing the following example. # # @example getting the names of all tasks # compiler.list_tasks.map {|tn| tn.name } # # Errors that occur during task discovery will either be logged as warnings or collected by the optional # `error_collector` array. When provided, it will receive {Puppet::DataTypes::Error} instances describing # each error in detail and no warnings will be logged. # # @param filter_regex [Regexp] an optional regexp that filters based on name (matching names are included in the result) # @param error_collector [Array] an optional array that will receive errors during load # @return [Array] an array of typed names # def list_tasks(filter_regex = nil, error_collector = nil) list_loadable_kind(:task, filter_regex, error_collector) end end # A FunctionSignature is returned from `function_signature`. Its purpose is to answer questions about the function's parameters # and if it can be called with a set of parameters. # # It is also possible to get an array of puppet Callable data type where each callable describes one possible way # the function can be called. # # @api public # class FunctionSignature # @api private def initialize(function_class) @func = function_class end # Returns true if the function can be called with the given arguments and false otherwise. # If the function is not callable, and a code block is given, it is given a formatted error message that describes # the type mismatch. That error message can be quite complex if the function has multiple dispatch depending on # given types. # # @param args [Array] The arguments as given to the function call # @param callable [Proc, nil] An optional ruby Proc or puppet lambda given to the function # @yield [String] a formatted error message describing a type mismatch if the function is not callable with given args + block # @return [Boolean] true if the function can be called with given args + block, and false otherwise # @api public # def callable_with?(args, callable=nil) signatures = @func.dispatcher.to_type callables = signatures.is_a?(Puppet::Pops::Types::PVariantType) ? signatures.types : [signatures] return true if callables.any? {|t| t.callable_with?(args) } return false unless block_given? args_type = Puppet::Pops::Types::TypeCalculator.singleton.infer_set(callable.nil? ? args : args + [callable]) error_message = Puppet::Pops::Types::TypeMismatchDescriber.describe_signatures(@func.name, @func.signatures, args_type) yield error_message false end # Returns an array of Callable puppet data type # @return [ArrayObject}] the hash representation of the task def task_hash @task._pcore_init_hash end # Returns the Task instance which can be further explored. It contains all meta-data defined for # the task such as the description, parameters, output, etc. # # @return [Puppet::Pops::Types::PuppetObject] An instance of a dynamically created Task class def task @task end end # A PlanSignature is returned from `plan_signature`. Its purpose is to answer questions about the plans's parameters # and if it can be called with a hash of named parameters. # # @api public # class PlanSignature def initialize(plan_function) @plan_func = plan_function end # Returns true or false depending on if the given PlanSignature is callable with a set of named arguments or not # In addition to returning the boolean outcome, if a block is given, it is called with a string of formatted # error messages that describes the difference between what was given and what is expected. The error message may # have multiple lines of text, and each line is indented one space. # # @example Checking if signature is acceptable # # signature = pal.plan_signature('myplan') # signature.callable_with?({x => 10}) { |errors| raise ArgumentError("Ooops: given arguments does not match\n#{errors}") } # # @api public # def callable_with?(args_hash) dispatcher = @plan_func.class.dispatcher.dispatchers[0] param_scope = {} # Assign all non-nil values, even those that represent non-existent parameters. args_hash.each { |k, v| param_scope[k] = v unless v.nil? } dispatcher.parameters.each do |p| name = p.name arg = args_hash[name] if arg.nil? # Arg either wasn't given, or it was undef if p.value.nil? # No default. Assign nil if the args_hash included it param_scope[name] = nil if args_hash.include?(name) else # parameter does not have a default value, it will be assigned its default when being called # we assume that the default value is of the correct type and therefore simply skip # checking this # param_scope[name] = param_scope.evaluate(name, p.value, closure_scope, @evaluator) end end end errors = Puppet::Pops::Types::TypeMismatchDescriber.singleton.describe_struct_signature(dispatcher.params_struct, param_scope).flatten return true if errors.empty? if block_given? yield errors.map {|e| e.format }.join("\n") end false end # Returns a PStructType describing the parameters as a puppet Struct data type # Note that a `to_s` on the returned structure will result in a human readable Struct datatype as a # description of what a plan expects. # # @return [Puppet::Pops::Types::PStructType] a struct data type describing the parameters and their types # # @api public # def params_type dispatcher = @plan_func.class.dispatcher.dispatchers[0] dispatcher.params_struct end end # Defines a context in which multiple operations in an env with a script compiler can be performed in a given block. # The calls that takes place to PAL inside of the given block are all with the same instance of the compiler. # The parameter `configured_by_env` makes it possible to either use the configuration in the environment, or specify # `manifest_file` or `code_string` manually. If neither is given, an empty `code_string` is used. # # @example define a script compiler without any initial logic # pal.with_script_compiler do | compiler | # # do things with compiler # end # # @example define a script compiler with a code_string containing initial logic # pal.with_script_compiler(code_string: '$myglobal_var = 42') do | compiler | # # do things with compiler # end # # @param configured_by_env [Boolean] when true the environment's settings are used, otherwise the given `manifest_file` or `code_string` # @param manifest_file [String] a Puppet Language file to load and evaluate before calling the given block, mutually exclusive with `code_string` # @param code_string [String] a Puppet Language source string to load and evaluate before calling the given block, mutually exclusive with `manifest_file` # @param facts [Hash] optional map of fact name to fact value - if not given will initialize the facts (which is a slow operation) # If given at the environment level, the facts given here are merged with higher priority. # @param variables [Hash] optional map of fully qualified variable name to value. If given at the environment level, the variables # given here are merged with higher priority. # @param block [Proc] the block performing operations on compiler # @return [Object] what the block returns # @yieldparam [Puppet::Pal::ScriptCompiler] compiler, a ScriptCompiler to perform operations on. # def self.with_script_compiler( configured_by_env: false, manifest_file: nil, code_string: nil, facts: nil, variables: nil, &block ) # TRANSLATORS: do not translate variable name strings in these assertions assert_mutually_exclusive(manifest_file, code_string, 'manifest_file', 'code_string') assert_non_empty_string(manifest_file, 'manifest_file', true) assert_non_empty_string(code_string, 'code_string', true) assert_type(T_BOOLEAN, configured_by_env, "configured_by_env", false) if configured_by_env unless manifest_file.nil? && code_string.nil? # TRANSLATORS: do not translate the variable names in this error message raise ArgumentError, _("manifest_file or code_string cannot be given when configured_by_env is true") end # Use the manifest setting manifest_file = Puppet[:manifest] else # An "undef" code_string is the only way to override Puppet[:manifest] & Puppet[:code] settings since an # empty string is taken as Puppet[:code] not being set. # if manifest_file.nil? && code_string.nil? code_string = 'undef' end end Puppet[:tasks] = true # After the assertions, if code_string is non nil - it has the highest precedence Puppet[:code] = code_string unless code_string.nil? # If manifest_file is nil, the #main method will use the env configured manifest # to do things in the block while a Script Compiler is in effect main(manifest_file, facts, variables, &block) end # Evaluates a Puppet Language script string. # @param code_string [String] a snippet of Puppet Language source code # @return [Object] what the Puppet Language code_string evaluates to # @deprecated Use {#with_script_compiler} and then evaluate_string on the given compiler - to be removed in 1.0 version # def self.evaluate_script_string(code_string) # prevent the default loading of Puppet[:manifest] which is the environment's manifest-dir by default settings # by setting code_string to 'undef' with_script_compiler do |compiler| compiler.evaluate_string(code_string) end end # Evaluates a Puppet Language script (.pp) file. # @param manifest_file [String] a file with Puppet Language source code # @return [Object] what the Puppet Language manifest_file contents evaluates to # @deprecated Use {#with_script_compiler} and then evaluate_file on the given compiler - to be removed in 1.0 version # def self.evaluate_script_manifest(manifest_file) with_script_compiler do |compiler| compiler.evaluate_file(manifest_file) end end # Defines the context in which to perform puppet operations (evaluation, etc) # The code to evaluate in this context is given in a block. # # @param env_name [String] a name to use for the temporary environment - this only shows up in errors # @param modulepath [Array] an array of directory paths containing Puppet modules, may be empty, defaults to empty array # @param settings_hash [Hash] a hash of settings - currently not used for anything, defaults to empty hash # @param facts [Hash] optional map of fact name to fact value - if not given will initialize the facts (which is a slow operation) # @param variables [Hash] optional map of fully qualified variable name to value # @return [Object] returns what the given block returns # @yieldparam [Puppet::Pal] context, a context that responds to Puppet::Pal methods # def self.in_tmp_environment(env_name, modulepath: [], settings_hash: {}, facts: nil, variables: {}, &block ) assert_non_empty_string(env_name, _("temporary environment name")) # TRANSLATORS: do not translate variable name string in these assertions assert_optionally_empty_array(modulepath, 'modulepath') unless block_given? raise ArgumentError, _("A block must be given to 'in_tmp_environment'") # TRANSLATORS 'in_tmp_environment' is a name, do not translate end env = Puppet::Node::Environment.create(env_name, modulepath) in_environment_context( Puppet::Environments::Static.new(env), # The tmp env is the only known env env, facts, variables, &block ) end # Defines the context in which to perform puppet operations (evaluation, etc) # The code to evaluate in this context is given in a block. # # The name of an environment (env_name) is always given. The location of that environment on disk # is then either constructed by: # * searching a given envpath where name is a child of a directory on that path, or # * it is the directory given in env_dir (which must exist). # # The env_dir and envpath options are mutually exclusive. # # @param env_name [String] the name of an existing environment # @param modulepath [Array] an array of directory paths containing Puppet modules, overrides the modulepath of an existing env. # Defaults to `{env_dir}/modules` if `env_dir` is given, # @param pre_modulepath [Array] like modulepath, but is prepended to the modulepath # @param post_modulepath [Array] like modulepath, but is appended to the modulepath # @param settings_hash [Hash] a hash of settings - currently not used for anything, defaults to empty hash # @param env_dir [String] a reference to a directory being the named environment (mutually exclusive with `envpath`) # @param envpath [String] a path of directories in which there are environments to search for `env_name` (mutually exclusive with `env_dir`). # Should be a single directory, or several directories separated with platform specific `File::PATH_SEPARATOR` character. # @param facts [Hash] optional map of fact name to fact value - if not given will initialize the facts (which is a slow operation) # @param variables [Hash] optional map of fully qualified variable name to value # @return [Object] returns what the given block returns # @yieldparam [Puppet::Pal] context, a context that responds to Puppet::Pal methods # def self.in_environment(env_name, modulepath: nil, pre_modulepath: [], post_modulepath: [], settings_hash: {}, env_dir: nil, envpath: nil, facts: nil, variables: {}, &block ) # TRANSLATORS terms in the assertions below are names of terms in code assert_non_empty_string(env_name, 'env_name') assert_optionally_empty_array(modulepath, 'modulepath', true) assert_optionally_empty_array(pre_modulepath, 'pre_modulepath', false) assert_optionally_empty_array(post_modulepath, 'post_modulepath', false) assert_mutually_exclusive(env_dir, envpath, 'env_dir', 'envpath') unless block_given? raise ArgumentError, _("A block must be given to 'in_environment'") # TRANSLATORS 'in_environment' is a name, do not translate end if env_dir unless Puppet::FileSystem.exist?(env_dir) raise ArgumentError, _("The environment directory '%{env_dir}' does not exist") % { env_dir: env_dir } end # a nil modulepath for env_dir means it should use its ./modules directory mid_modulepath = modulepath.nil? ? [Puppet::FileSystem.expand_path(File.join(env_dir, 'modules'))] : modulepath env = Puppet::Node::Environment.create(env_name, pre_modulepath + mid_modulepath + post_modulepath) environments = Puppet::Environments::StaticDirectory.new(env_name, env_dir, env) # The env being used is the only one... else assert_non_empty_string(envpath, 'envpath') # The environment is resolved against the envpath. This is setup without a basemodulepath # The modulepath defaults to the 'modulepath' in the found env when "Directories" is used # if envpath.is_a?(String) && envpath.include?(File::PATH_SEPARATOR) # potentially more than one directory to search env_loaders = Puppet::Environments::Directories.from_path(envpath, []) environments = Puppet::Environments::Combined.new(*env_loaders) else environments = Puppet::Environments::Directories.new(envpath, []) end env = environments.get(env_name) if env.nil? raise ArgumentError, _("No directory found for the environment '%{env_name}' on the path '%{envpath}'") % { env_name: env_name, envpath: envpath } end # A given modulepath should override the default mid_modulepath = modulepath.nil? ? env.modulepath : modulepath env_path = env.configuration.path_to_env env = env.override_with(:modulepath => pre_modulepath + mid_modulepath + post_modulepath) # must configure this in case logic looks up the env by name again (otherwise the looked up env does # not have the same effective modulepath). environments = Puppet::Environments::StaticDirectory.new(env_name, env_path, env) # The env being used is the only one... end in_environment_context(environments, env, facts, variables, &block) end # Prepares the puppet context with pal information - and delegates to the block # No set up is performed at this step - it is delayed until it is known what the # operation is going to be (for example - using a ScriptCompiler). # def self.in_environment_context(environments, env, facts, variables, &block) # Create a default node to use (may be overridden later) node = Puppet::Node.new(Puppet[:node_name_value], :environment => env) Puppet.override( environments: environments, # The env being used is the only one... pal_env: env, # provide as convenience pal_current_node: node, # to allow it to be picked up instead of created pal_variables: variables, # common set of variables across several inner contexts pal_facts: facts # common set of facts across several inner contexts (or nil) ) do # DELAY: prepare_node_facts(node, facts) return block.call(self) end end private_class_method :in_environment_context # Prepares the node for use by giving it node_facts (if given) # If a hash of facts values is given, then the operation of creating a node with facts is much # speeded up (as getting a fresh set of facts is avoided in a later step). # def self.prepare_node_facts(node, facts) # Prepare the node with facts if it does not already have them if node.facts.nil? node_facts = facts.nil? ? nil : Puppet::Node::Facts.new(Puppet[:node_name_value], facts) node.fact_merge(node_facts) # Add server facts so $server_facts[environment] exists when doing a puppet script # SCRIPT TODO: May be needed when running scripts under orchestrator. Leave it for now. # node.add_server_facts({}) end end private_class_method :prepare_node_facts def self.add_variables(scope, variables) return if variables.nil? unless variables.is_a?(Hash) raise ArgumentError, _("Given variables must be a hash, got %{type}") % { type: variables.class } end rich_data_t = Puppet::Pops::Types::TypeFactory.rich_data variables.each_pair do |k,v| unless k =~ Puppet::Pops::Patterns::VAR_NAME raise ArgumentError, _("Given variable '%{varname}' has illegal name") % { varname: k } end unless rich_data_t.instance?(v) raise ArgumentError, _("Given value for '%{varname}' has illegal type - got: %{type}") % { varname: k, type: v.class } end scope.setvar(k, v) end end private_class_method :add_variables # The main routine for script compiler # Picks up information from the puppet context and configures a script compiler which is given to # the provided block # def self.main(manifest, facts, variables) # Configure the load path env = Puppet.lookup(:pal_env) env.each_plugin_directory do |dir| $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) end # Puppet requires Facter, which initializes its lookup paths. Reset Facter to # pickup the new $LOAD_PATH. Facter.reset node = Puppet.lookup(:pal_current_node) pal_facts = Puppet.lookup(:pal_facts) pal_variables = Puppet.lookup(:pal_variables) overrides = {} unless facts.nil? || facts.empty? pal_facts = pal_facts.merge(facts) overrides[:pal_facts] = pal_facts end unless variables.nil? || variables.empty? pal_variables = pal_variables.merge(variables) overrides[:pal_variables] = pal_variables end prepare_node_facts(node, pal_facts) configured_environment = node.environment || Puppet.lookup(:current_environment) apply_environment = manifest ? configured_environment.override_with(:manifest => manifest) : configured_environment # Modify the node descriptor to use the special apply_environment. # It is based on the actual environment from the node, or the locally # configured environment if the node does not specify one. # If a manifest file is passed on the command line, it overrides # the :manifest setting of the apply_environment. node.environment = apply_environment # TRANSLATORS, the string "For puppet PAL" is not user facing Puppet.override({:current_environment => apply_environment}, "For puppet PAL") do begin # support the following features when evaluating puppet code # * $facts with facts from host running the script # * $settings with 'settings::*' namespace populated, and '$settings::all_local' hash # * $trusted as setup when using puppet apply # * an environment # # fixup trusted information node.sanitize() compiler = Puppet::Parser::ScriptCompiler.new(node.environment, node.name) topscope = compiler.topscope # When scripting the trusted data are always local, but set them anyway topscope.set_trusted(node.trusted_data) # Server facts are always about the local node's version etc. topscope.set_server_facts(node.server_facts) # Set $facts for the node running the script facts_hash = node.facts.nil? ? {} : node.facts.values topscope.set_facts(facts_hash) # create the $settings:: variables topscope.merge_settings(node.environment.name, false) add_variables(topscope, pal_variables) # compiler.compile(&block) compiler.compile do | internal_compiler | # wrap the internal compiler to prevent it from leaking in the PAL API if block_given? script_compiler = ScriptCompiler.new(internal_compiler) # Make compiler available to Puppet#lookup overrides[:pal_script_compiler] = script_compiler Puppet.override(overrides, "PAL::with_script_compiler") do # TRANSLATORS: Do not translate, symbolic name yield(script_compiler) end end end rescue Puppet::ParseErrorWithIssue, Puppet::Error # already logged and handled by the compiler for these two cases raise rescue => detail Puppet.log_exception(detail) raise end end end private_class_method :main T_STRING = Puppet::Pops::Types::PStringType::NON_EMPTY T_STRING_ARRAY = Puppet::Pops::Types::TypeFactory.array_of(T_STRING) T_ANY_ARRAY = Puppet::Pops::Types::TypeFactory.array_of_any T_BOOLEAN = Puppet::Pops::Types::PBooleanType::DEFAULT T_GENERIC_TASK_HASH = Puppet::Pops::Types::TypeFactory.hash_kv( Puppet::Pops::Types::TypeFactory.pattern(/\A[a-z][a-z0-9_]*\z/), Puppet::Pops::Types::TypeFactory.data) def self.assert_type(type, value, what, allow_nil=false) Puppet::Pops::Types::TypeAsserter.assert_instance_of(nil, type, value, allow_nil) { _('Puppet Pal: %{what}') % {what: what} } end def self.assert_non_empty_string(s, what, allow_nil=false) assert_type(T_STRING, s, what, allow_nil) end def self.assert_optionally_empty_array(a, what, allow_nil=false) assert_type(T_STRING_ARRAY, a, what, allow_nil) end private_class_method :assert_optionally_empty_array def self.assert_mutually_exclusive(a, b, a_term, b_term) if a && b raise ArgumentError, _("Cannot use '%{a_term}' and '%{b_term}' at the same time") % { a_term: a_term, b_term: b_term } end end private_class_method :assert_mutually_exclusive def self.assert_block_given(block) if block.nil? raise ArgumentError, _("A block must be given") end end private_class_method :assert_block_given end end puppet-5.5.10/conf/0000755005276200011600000000000013417162176013744 5ustar jenkinsjenkinspuppet-5.5.10/conf/auth.conf0000644005276200011600000001255713417161721015561 0ustar jenkinsjenkins# This is the default auth.conf file, which implements the default rules # used by the puppet master. (That is, the rules below will still apply # even if this file is deleted.) # # The ACLs are evaluated in top-down order. More specific stanzas should # be towards the top of the file and more general ones at the bottom; # otherwise, the general rules may "steal" requests that should be # governed by the specific rules. # # See https://puppet.com/docs/puppet/latest/config_file_auth.html # for a more complete description of auth.conf's behavior. # # Supported syntax: # Each stanza in auth.conf starts with a path to match, followed # by optional modifiers, and finally, a series of allow or deny # directives. # # Example Stanza # --------------------------------- # path /path/to/resource # simple prefix match # # path ~ regex # alternately, regex match # [environment envlist] # [method methodlist] # [auth[enthicated] {yes|no|on|off|any}] # allow [host|backreference|*|regex] # deny [host|backreference|*|regex] # allow_ip [ip|cidr|ip_wildcard|*] # deny_ip [ip|cidr|ip_wildcard|*] # # The path match can either be a simple prefix match or a regular # expression. `path /file` would match both `/file_metadata` and # `/file_content`. Regex matches allow the use of backreferences # in the allow/deny directives. # # The regex syntax is the same as for Ruby regex, and captures backreferences # for use in the `allow` and `deny` lines of that stanza # # Examples: # # path ~ ^/puppet/v3/path/to/resource # Equivalent to `path /puppet/v3/path/to/resource`. # allow * # Allow all authenticated nodes (since auth # # defaults to `yes`). # # path ~ ^/puppet/v3/catalog/([^/]+)$ # Permit nodes to access their own catalog (by # allow $1 # certname), but not any other node's catalog. # # path ~ ^/puppet/v3/file_(metadata|content)/extra_files/ # Only allow certain nodes to # auth yes # access the "extra_files" # allow /^(.+)\.example\.com$/ # mount point; note this must # allow_ip 192.168.100.0/24 # go ABOVE the "/file" rule, # # since it is more specific. # # environment:: restrict an ACL to a comma-separated list of environments # method:: restrict an ACL to a comma-separated list of HTTP methods # auth:: restrict an ACL to an authenticated or unauthenticated request # the default when unspecified is to restrict the ACL to authenticated requests # (ie exactly as if auth yes was present). # # CONTROLLING FILE ACCESS (previously in fileserver.conf) # In previous versions of Puppet, you controlled file access by adding # rules to fileserver.conf. In Puppet 5 with Puppet Server, you can control # file access in auth.conf by controlling the /file_metadata(s)/, # /file_content(s)/, and /static_file_content/ paths. See the # Puppet Server documentation at # https://puppet.com/docs/puppetserver/latest/config_file_auth.html. # # If you are not using Puppet Server, or are using Puppet Server but with the # "jruby-puppet.use-legacy-auth-conf" setting set to "true", you could set the # desired file access in a new rule in this file. For example: # # path ~ ^/file_(metadata|content)s?/extra_files/ # auth yes # allow /^(.+)\.example\.com$/ # allow_ip 192.168.100.0/24 # # If added to auth.conf BEFORE the default "path /file" rule, this rule # will add stricter restrictions to the extra_files mount point. ### Authenticated ACLs - these rules apply only when the client ### has a valid certificate and is thus authenticated path /puppet/v3/environments method find allow * # allow nodes to retrieve their own catalog path ~ ^/puppet/v3/catalog/([^/]+)$ method find allow $1 # allow nodes to retrieve their own node definition path ~ ^/puppet/v3/node/([^/]+)$ method find allow $1 # allow all nodes to store their own reports path ~ ^/puppet/v3/report/([^/]+)$ method save allow $1 # allow all nodes to update their own facts path ~ ^/puppet/v3/facts/([^/]+)$ method save allow $1 # Allow all nodes to access all file services; this is necessary for # pluginsync, file serving from modules, and file serving from custom # mount points (see fileserver.conf). Note that the `/file` prefix matches # requests to both the file_metadata and file_content paths. See "Examples" # above if you need more granular access control for custom mount points. path /puppet/v3/file allow * path /puppet/v3/status method find allow * # allow all nodes to access the certificates services path /puppet-ca/v1/certificate_revocation_list/ca method find allow * ### Unauthenticated ACLs, for clients without valid certificates; authenticated ### clients can also access these paths, though they rarely need to. # allow access to the CA certificate; unauthenticated nodes need this # in order to validate the puppet master's certificate path /puppet-ca/v1/certificate/ca auth any method find allow * # allow nodes to retrieve the certificate they requested earlier path /puppet-ca/v1/certificate/ auth any method find allow * # allow nodes to request a new certificate path /puppet-ca/v1/certificate_request auth any method find, save allow * # deny everything else; this ACL is not strictly necessary, but # illustrates the default policy. path / auth any puppet-5.5.10/conf/environment.conf0000644005276200011600000000154113417161721017153 0ustar jenkinsjenkins# Each environment can have an environment.conf file. Its settings will only # affect its own environment. See docs for more info: # https://puppet.com/docs/puppet/latest/config_file_environment.html # Any unspecified settings use default values; some of those defaults are based # on puppet.conf settings. # If these settings include relative file paths, they'll be resolved relative to # this environment's directory. # Allowed settings and default values: # modulepath = ./modules:$basemodulepath # manifest = (default_manifest from puppet.conf, which defaults to ./manifests) # config_version = (no script; Puppet will use the time the catalog was compiled) # environment_timeout = (environment_timeout from puppet.conf, which defaults to 0) # Note: unless you have a specific reason, we recommend only setting # environment_timeout in puppet.conf. puppet-5.5.10/conf/fileserver.conf0000644005276200011600000000262113417161721016755 0ustar jenkinsjenkins# fileserver.conf # Puppet automatically serves PLUGINS and FILES FROM MODULES: anything in # /files/ is available to authenticated nodes at # puppet:///modules//. You do not need to edit this # file to enable this. # MOUNT POINTS # If you need to serve files from a directory that is NOT in a module, # you must create a static mount point in this file: # # [extra_files] # path /etc/puppetlabs/puppet/files # allow * # # In the example above, anything in /etc/puppetlabs/puppet/files/ would be # available to authenticated nodes at puppet:///extra_files/. # # Mount points may also use three placeholders as part of their path: # # %H - The node's certname. # %h - The portion of the node's certname before the first dot. (Usually the # node's short hostname.) # %d - The portion of the node's certname after the first dot. (Usually the # node's domain name.) # PERMISSIONS # The ability to set permissions - for example, using the allow, allow_ip, or # deny directives - has been removed. Instead, you can control file access in # auth.conf by controlling the /file_metadata(s)/ and # /file_content(s)/ paths. # # For details and an example, see the auth.conf file. If you're using Puppet # Server, see the Puppet Server documentation at # https://puppet.com/docs/puppetserver/latest/config_file_auth.html. puppet-5.5.10/conf/hiera.yaml0000644005276200011600000000024113417161721015710 0ustar jenkinsjenkins--- # Hiera 5 Global configuration file version: 5 # defaults: # data_hash: yaml_data # hierarchy: # - name: Common # data_hash: yaml_data hierarchy: [] puppet-5.5.10/conf/puppet.conf0000644005276200011600000000062713417161721016130 0ustar jenkinsjenkins# This file can be used to override the default puppet settings. # See the following links for more details on what settings are available: # - https://puppet.com/docs/puppet/latest/config_important_settings.html # - https://puppet.com/docs/puppet/latest/config_about_settings.html # - https://puppet.com/docs/puppet/latest/config_file_main.html # - https://puppet.com/docs/puppet/latest/configuration.html puppet-5.5.10/man/0000755005276200011600000000000013417162176013572 5ustar jenkinsjenkinspuppet-5.5.10/man/man5/0000755005276200011600000000000013417162176014432 5ustar jenkinsjenkinspuppet-5.5.10/man/man5/puppet.conf.50000644005276200011600000021652513417161722016770 0ustar jenkinsjenkins.\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . .TH "PUPPETCONF" "5" "January 2019" "Puppet, Inc." "Puppet manual" \fBThis page is autogenerated; any changes will get overwritten\fR . .SH "Configuration settings" . .IP "\(bu" 4 Each of these settings can be specified in \fBpuppet\.conf\fR or on the command line\. . .IP "\(bu" 4 Puppet Enterprise (PE) and open source Puppet share the configuration settings that are documented here\. However, PE defaults for some settings differ from the open source Puppet defaults\. Some examples of settings that have different PE defaults include \fBdisable18n\fR, \fBenvironment_timeout\fR, \fBalways_retry_plugins\fR, and the Puppet Server JRuby \fBmax\-active\-instances\fR setting\. To verify PE configuration defaults, check the \fBpuppet\.conf\fR file after installation\. . .IP "\(bu" 4 When using boolean settings on the command line, use \fB\-\-setting\fR and \fB\-\-no\-setting\fR instead of \fB\-\-setting (true|false)\fR\. (Using \fB\-\-setting false\fR results in "Error: Could not parse application options: needless argument"\.) . .IP "\(bu" 4 Settings can be interpolated as \fB$variables\fR in other settings; \fB$environment\fR is special, in that puppet master will interpolate each agent node\'s environment instead of its own\. . .IP "\(bu" 4 Multiple values should be specified as comma\-separated lists; multiple directories should be separated with the system path separator (usually a colon)\. . .IP "\(bu" 4 Settings that represent time intervals should be specified in duration format: an integer immediately followed by one of the units \'y\' (years of 365 days), \'d\' (days), \'h\' (hours), \'m\' (minutes), or \'s\' (seconds)\. The unit cannot be combined with other units, and defaults to seconds when omitted\. Examples are \'3600\' which is equivalent to \'1h\' (one hour), and \'1825d\' which is equivalent to \'5y\' (5 years)\. . .IP "\(bu" 4 If you use the \fBsplay\fR setting, note that the period that it waits changes each time the Puppet agent is restarted\. . .IP "\(bu" 4 Settings that take a single file or directory can optionally set the owner, group, and mode for their value: \fBrundir = $vardir/run { owner = puppet, group = puppet, mode = 644 }\fR . .IP "\(bu" 4 The Puppet executables will ignore any setting that isn\'t relevant to their function\. . .IP "" 0 . .P See the configuration guide \fIhttps://puppet\.com/docs/puppet/latest/config_about_settings\.html\fR for more details\. . .SS "agent_catalog_run_lockfile" A lock file to indicate that a puppet agent catalog run is currently in progress\. The file contains the pid of the process that holds the lock on the catalog run\. . .IP "\(bu" 4 \fIDefault\fR: $statedir/agent_catalog_run\.lock . .IP "" 0 . .SS "agent_disabled_lockfile" A lock file to indicate that puppet agent runs have been administratively disabled\. File contains a JSON object with state information\. . .IP "\(bu" 4 \fIDefault\fR: $statedir/agent_disabled\.lock . .IP "" 0 . .SS "allow_duplicate_certs" Whether to allow a new certificate request to overwrite an existing certificate\. . .IP "\(bu" 4 \fIDefault\fR: false . .IP "" 0 . .SS "always_retry_plugins" Affects how we cache attempts to load Puppet resource types and features\. If true, then calls to \fBPuppet\.type\.?\fR \fBPuppet\.feature\.?\fR will always attempt to load the type or feature (which can be an expensive operation) unless it has already been loaded successfully\. This makes it possible for a single agent run to, e\.g\., install a package that provides the underlying capabilities for a type or feature, and then later load that type or feature during the same run (even if the type or feature had been tested earlier and had not been available)\. . .P If this setting is set to false, then types and features will only be checked once, and if they are not available, the negative result is cached and returned for all subsequent attempts to load the type or feature\. This behavior is almost always appropriate for the server, and can result in a significant performance improvement for types and features that are checked frequently\. . .IP "\(bu" 4 \fIDefault\fR: true . .IP "" 0 . .SS "app_management" This setting has no effect and will be removed in a future Puppet version\. . .IP "\(bu" 4 \fIDefault\fR: false . .IP "" 0 . .SS "autoflush" Whether log files should always flush to disk\. . .IP "\(bu" 4 \fIDefault\fR: true . .IP "" 0 . .SS "autosign" Whether (and how) to autosign certificate requests\. This setting is only relevant on a puppet master acting as a certificate authority (CA)\. . .P Valid values are true (autosigns all certificate requests; not recommended), false (disables autosigning certificates), or the absolute path to a file\. . .P The file specified in this setting may be either a \fBconfiguration file\fR or a \fBcustom policy executable\.\fR Puppet will automatically determine what it is: If the Puppet user (see the \fBuser\fR setting) can execute the file, it will be treated as a policy executable; otherwise, it will be treated as a config file\. . .P If a custom policy executable is configured, the CA puppet master will run it every time it receives a CSR\. The executable will be passed the subject CN of the request \fIas a command line argument,\fR and the contents of the CSR in PEM format \fIon stdin\.\fR It should exit with a status of 0 if the cert should be autosigned and non\-zero if the cert should not be autosigned\. . .P If a certificate request is not autosigned, it will persist for review\. An admin user can use the \fBpuppet cert sign\fR command to manually sign it, or can delete the request\. . .P For info on autosign configuration files, see the guide to Puppet\'s config files \fIhttps://puppet\.com/docs/puppet/latest/config_about_settings\.html\fR\. . .IP "\(bu" 4 \fIDefault\fR: $confdir/autosign\.conf . .IP "" 0 . .SS "basemodulepath" The search path for \fBglobal\fR modules\. Should be specified as a list of directories separated by the system path separator character\. (The POSIX path separator is \':\', and the Windows path separator is \';\'\.) . .P These are the modules that will be used by \fIall\fR environments\. Note that the \fBmodules\fR directory of the active environment will have priority over any global directories\. For more info, see \fIhttps://puppet\.com/docs/puppet/latest/environments_about\.html\fR . .IP "\(bu" 4 \fIDefault\fR: $codedir/modules:/opt/puppetlabs/puppet/modules . .IP "" 0 . .SS "bindaddress" The address a listening server should bind to\. . .IP "\(bu" 4 \fIDefault\fR: * . .IP "" 0 . .SS "binder_config" The binder configuration file\. Puppet reads this file on each request to configure the bindings system\. If set to nil (the default), a $confdir/binder_config\.yaml is optionally loaded\. If it does not exists, a default configuration is used\. If the setting :binding_config is specified, it must reference a valid and existing yaml file\. . .TP \fIDefault\fR: . .SS "bucketdir" Where FileBucket files are stored\. . .IP "\(bu" 4 \fIDefault\fR: $vardir/bucket . .IP "" 0 . .SS "ca" Whether the master should function as a certificate authority\. . .IP "\(bu" 4 \fIDefault\fR: true . .IP "" 0 . .SS "ca_name" The name to use the Certificate Authority certificate\. . .IP "\(bu" 4 \fIDefault\fR: Puppet CA: $certname . .IP "" 0 . .SS "ca_port" The port to use for the certificate authority\. . .IP "\(bu" 4 \fIDefault\fR: $masterport . .IP "" 0 . .SS "ca_server" The server to use for certificate authority requests\. It\'s a separate server because it cannot and does not need to horizontally scale\. . .IP "\(bu" 4 \fIDefault\fR: $server . .IP "" 0 . .SS "ca_ttl" The default TTL for new certificates\. This setting can be a time interval in seconds (30 or 30s), minutes (30m), hours (6h), days (2d), or years (5y)\. . .IP "\(bu" 4 \fIDefault\fR: 5y . .IP "" 0 . .SS "cacert" The CA certificate\. . .IP "\(bu" 4 \fIDefault\fR: $cadir/ca_crt\.pem . .IP "" 0 . .SS "cacrl" The certificate revocation list (CRL) for the CA\. Will be used if present but otherwise ignored\. . .IP "\(bu" 4 \fIDefault\fR: $cadir/ca_crl\.pem . .IP "" 0 . .SS "cadir" The root directory for the certificate authority\. . .IP "\(bu" 4 \fIDefault\fR: $ssldir/ca . .IP "" 0 . .SS "cakey" The CA private key\. . .IP "\(bu" 4 \fIDefault\fR: $cadir/ca_key\.pem . .IP "" 0 . .SS "capass" Where the CA stores the password for the private key\. This setting is deprecated and will be removed in Puppet 6\. . .IP "\(bu" 4 \fIDefault\fR: $caprivatedir/ca\.pass . .IP "" 0 . .SS "caprivatedir" Where the CA stores private certificate information\. This setting is deprecated and will be removed in Puppet 6\. . .IP "\(bu" 4 \fIDefault\fR: $cadir/private . .IP "" 0 . .SS "capub" The CA public key\. . .IP "\(bu" 4 \fIDefault\fR: $cadir/ca_pub\.pem . .IP "" 0 . .SS "catalog_cache_terminus" How to store cached catalogs\. Valid values are \'json\', \'msgpack\' and \'yaml\'\. The agent application defaults to \'json\'\. . .TP \fIDefault\fR: . .SS "catalog_terminus" Where to get node catalogs\. This is useful to change if, for instance, you\'d like to pre\-compile catalogs and store them in memcached or some other easily\-accessed store\. . .IP "\(bu" 4 \fIDefault\fR: compiler . .IP "" 0 . .SS "cert_inventory" The inventory file\. This is a text file to which the CA writes a complete listing of all certificates\. . .IP "\(bu" 4 \fIDefault\fR: $cadir/inventory\.txt . .IP "" 0 . .SS "certdir" The certificate directory\. . .IP "\(bu" 4 \fIDefault\fR: $ssldir/certs . .IP "" 0 . .SS "certificate_revocation" Whether certificate revocation checking should be enabled, and what level of checking should be performed\. . .P When certificate_revocation is set to \'true\' or \'chain\', Puppet will download the CA CRL and will perform revocation checking against each certificate in the chain\. . .P Puppet is unable to load multiple CRLs, so if certificate_revocation is set to \'chain\' and Puppet attempts to verify a certificate signed by a root CA the behavior is equivalent to the \'leaf\' setting, and if Puppet attempts to verify a certificate signed by an intermediate CA then verification will fail as Puppet will be unable to load the multiple CRLs required for full chain checking\. As such the \'chain\' setting is limited in functionality and is meant as a stand in pending the implementation of full chain checking\. . .P When certificate_revocation is set to \'leaf\', Puppet will download the CA CRL and will verify the leaf certificate against that CRL\. CRLs will not be fetched or checked for the rest of the certificates in the chain\. If you are using an intermediate CA certificate and want to enable certificate revocation checking, this setting must be set to \'leaf\'\. . .P When certificate_revocation is set to \'false\', Puppet will disable all certificate revocation checking and will not attempt to download the CRL\. . .IP "\(bu" 4 \fIDefault\fR: chain . .IP "" 0 . .SS "certname" The name to use when handling certificates\. When a node requests a certificate from the CA puppet master, it uses the value of the \fBcertname\fR setting as its requested Subject CN\. . .P This is the name used when managing a node\'s permissions in auth\.conf \fIhttps://puppet\.com/docs/puppet/latest/config_file_auth\.html\fR\. In most cases, it is also used as the node\'s name when matching node definitions \fIhttps://puppet\.com/docs/puppet/latest/lang_node_definitions\.html\fR and requesting data from an ENC\. (This can be changed with the \fBnode_name_value\fR and \fBnode_name_fact\fR settings, although you should only do so if you have a compelling reason\.) . .P A node\'s certname is available in Puppet manifests as \fB$trusted[\'certname\']\fR\. (See Facts and Built\-In Variables \fIhttps://puppet\.com/docs/puppet/latest/lang_facts_and_builtin_vars\.html\fR for more details\.) . .IP "\(bu" 4 For best compatibility, you should limit the value of \fBcertname\fR to only use lowercase letters, numbers, periods, underscores, and dashes\. (That is, it should match \fB/A[a\-z0\-9\._\-]+Z/\fR\.) . .IP "\(bu" 4 The special value \fBca\fR is reserved, and can\'t be used as the certname for a normal node\. . .IP "" 0 . .P Defaults to the node\'s fully qualified domain name\. . .IP "\(bu" 4 \fIDefault\fR: the Host\'s fully qualified domain name, as determined by facter . .IP "" 0 . .SS "classfile" The file in which puppet agent stores a list of the classes associated with the retrieved configuration\. Can be loaded in the separate \fBpuppet\fR executable using the \fB\-\-loadclasses\fR option\. . .IP "\(bu" 4 \fIDefault\fR: $statedir/classes\.txt . .IP "" 0 . .SS "client_datadir" The directory in which serialized data is stored on the client\. . .IP "\(bu" 4 \fIDefault\fR: $vardir/client_data . .IP "" 0 . .SS "clientbucketdir" Where FileBucket files are stored locally\. . .IP "\(bu" 4 \fIDefault\fR: $vardir/clientbucket . .IP "" 0 . .SS "clientyamldir" The directory in which client\-side YAML data is stored\. . .IP "\(bu" 4 \fIDefault\fR: $vardir/client_yaml . .IP "" 0 . .SS "code" Code to parse directly\. This is essentially only used by \fBpuppet\fR, and should only be set if you\'re writing your own Puppet executable\. . .SS "codedir" The main Puppet code directory\. The default for this setting is calculated based on the user\. If the process is running as root or the user that Puppet is supposed to run as, it defaults to a system directory, but if it\'s running as any other user, it defaults to being in the user\'s home directory\. . .IP "\(bu" 4 \fIDefault\fR: Unix/Linux: /etc/puppetlabs/code \-\- Windows: C:\eProgramData\ePuppetLabs\ecode \-\- Non\-root user: ~/\.puppetlabs/etc/code . .IP "" 0 . .SS "color" Whether to use colors when logging to the console\. Valid values are \fBansi\fR (equivalent to \fBtrue\fR), \fBhtml\fR, and \fBfalse\fR, which produces no color\. Defaults to false on Windows, as its console does not support ansi colors\. . .IP "\(bu" 4 \fIDefault\fR: ansi . .IP "" 0 . .SS "confdir" The main Puppet configuration directory\. The default for this setting is calculated based on the user\. If the process is running as root or the user that Puppet is supposed to run as, it defaults to a system directory, but if it\'s running as any other user, it defaults to being in the user\'s home directory\. . .IP "\(bu" 4 \fIDefault\fR: Unix/Linux: /etc/puppetlabs/puppet \-\- Windows: C:\eProgramData\ePuppetLabs\epuppet\eetc \-\- Non\-root user: ~/\.puppetlabs/etc/puppet . .IP "" 0 . .SS "config" The configuration file for the current puppet application\. . .IP "\(bu" 4 \fIDefault\fR: $confdir/${config_file_name} . .IP "" 0 . .SS "config_file_name" The name of the puppet config file\. . .IP "\(bu" 4 \fIDefault\fR: puppet\.conf . .IP "" 0 . .SS "config_version" How to determine the configuration version\. By default, it will be the time that the configuration is parsed, but you can provide a shell script to override how the version is determined\. The output of this script will be added to every log message in the reports, allowing you to correlate changes on your hosts to the source version on the server\. . .P Setting a global value for config_version in puppet\.conf is not allowed (but it can be overridden from the commandline)\. Please set a per\-environment value in environment\.conf instead\. For more info, see \fIhttps://puppet\.com/docs/puppet/latest/environments_about\.html\fR . .SS "configprint" Prints the value of a specific configuration setting\. If the name of a setting is provided for this, then the value is printed and puppet exits\. Comma\-separate multiple values\. For a list of all values, specify \'all\'\. This setting is deprecated, the \'puppet config\' command replaces this functionality\. . .SS "configtimeout" How long the client should wait for the configuration to be retrieved before considering it a failure\. This setting is deprecated and has been replaced by http_connect_timeout and http_read_timeout\. This setting can be a time interval in seconds (30 or 30s), minutes (30m), hours (6h), days (2d), or years (5y)\. . .IP "\(bu" 4 \fIDefault\fR: 2m . .IP "" 0 . .SS "csr_attributes" An optional file containing custom attributes to add to certificate signing requests (CSRs)\. You should ensure that this file does not exist on your CA puppet master; if it does, unwanted certificate extensions may leak into certificates created with the \fBpuppet cert generate\fR command\. . .P If present, this file must be a YAML hash containing a \fBcustom_attributes\fR key and/or an \fBextension_requests\fR key\. The value of each key must be a hash, where each key is a valid OID and each value is an object that can be cast to a string\. . .P Custom attributes can be used by the CA when deciding whether to sign the certificate, but are then discarded\. Attribute OIDs can be any OID value except the standard CSR attributes (i\.e\. attributes described in RFC 2985 section 5\.4)\. This is useful for embedding a pre\-shared key for autosigning policy executables (see the \fBautosign\fR setting), often by using the \fB1\.2\.840\.113549\.1\.9\.7\fR ("challenge password") OID\. . .P Extension requests will be permanently embedded in the final certificate\. Extension OIDs must be in the "ppRegCertExt" (\fB1\.3\.6\.1\.4\.1\.34380\.1\.1\fR) or "ppPrivCertExt" (\fB1\.3\.6\.1\.4\.1\.34380\.1\.2\fR) OID arcs\. The ppRegCertExt arc is reserved for four of the most common pieces of data to embed: \fBpp_uuid\fR (\fB\.1\fR), \fBpp_instance_id\fR (\fB\.2\fR), \fBpp_image_name\fR (\fB\.3\fR), and \fBpp_preshared_key\fR (\fB\.4\fR) \-\-\- in the YAML file, these can be referred to by their short descriptive names instead of their full OID\. The ppPrivCertExt arc is unregulated, and can be used for site\-specific extensions\. . .IP "\(bu" 4 \fIDefault\fR: $confdir/csr_attributes\.yaml . .IP "" 0 . .SS "csrdir" Where the CA stores certificate requests\. . .IP "\(bu" 4 \fIDefault\fR: $cadir/requests . .IP "" 0 . .SS "daemonize" Whether to send the process into the background\. This defaults to true on POSIX systems, and to false on Windows (where Puppet currently cannot daemonize)\. . .IP "\(bu" 4 \fIDefault\fR: true . .IP "" 0 . .SS "data_binding_terminus" This setting has been deprecated\. Use of any value other than \'hiera\' should instead be configured in a version 5 hiera\.yaml\. Until this setting is removed, it controls which data binding terminus to use for global automatic data binding (across all environments)\. By default this value is \'hiera\'\. A value of \'none\' turns off the global binding\. . .IP "\(bu" 4 \fIDefault\fR: hiera . .IP "" 0 . .SS "default_file_terminus" The default source for files if no server is given in a uri, e\.g\. puppet:///file\. The default of \fBrest\fR causes the file to be retrieved using the \fBserver\fR setting\. When running \fBapply\fR the default is \fBfile_server\fR, causing requests to be filled locally\. . .IP "\(bu" 4 \fIDefault\fR: rest . .IP "" 0 . .SS "default_manifest" The default main manifest for directory environments\. Any environment that doesn\'t set the \fBmanifest\fR setting in its \fBenvironment\.conf\fR file will use this manifest\. . .P This setting\'s value can be an absolute or relative path\. An absolute path will make all environments default to the same main manifest; a relative path will allow each environment to use its own manifest, and Puppet will resolve the path relative to each environment\'s main directory\. . .P In either case, the path can point to a single file or to a directory of manifests to be evaluated in alphabetical order\. . .IP "\(bu" 4 \fIDefault\fR: \./manifests . .IP "" 0 . .SS "default_schedules" Boolean; whether to generate the default schedule resources\. Setting this to false is useful for keeping external report processors clean of skipped schedule resources\. . .IP "\(bu" 4 \fIDefault\fR: true . .IP "" 0 . .SS "deviceconfig" Path to the device config file for puppet device\. . .IP "\(bu" 4 \fIDefault\fR: $confdir/device\.conf . .IP "" 0 . .SS "devicedir" The root directory of devices\' $vardir\. . .IP "\(bu" 4 \fIDefault\fR: $vardir/devices . .IP "" 0 . .SS "diff" Which diff command to use when printing differences between files\. This setting has no default value on Windows, as standard \fBdiff\fR is not available, but Puppet can use many third\-party diff tools\. . .IP "\(bu" 4 \fIDefault\fR: diff . .IP "" 0 . .SS "diff_args" Which arguments to pass to the diff command when printing differences between files\. The command to use can be chosen with the \fBdiff\fR setting\. . .IP "\(bu" 4 \fIDefault\fR: \-u . .IP "" 0 . .SS "digest_algorithm" Which digest algorithm to use for file resources and the filebucket\. Valid values are md5, sha256, sha384, sha512, sha224\. Default is md5\. . .IP "\(bu" 4 \fIDefault\fR: md5 . .IP "" 0 . .SS "disable_i18n" If true, turns off all translations of Puppet and module log messages, which affects error, warning, and info log messages, as well as any translations in the report and CLI\. . .IP "\(bu" 4 \fIDefault\fR: false . .IP "" 0 . .SS "disable_per_environment_manifest" Whether to disallow an environment\-specific main manifest\. When set to \fBtrue\fR, Puppet will use the manifest specified in the \fBdefault_manifest\fR setting for all environments\. If an environment specifies a different main manifest in its \fBenvironment\.conf\fR file, catalog requests for that environment will fail with an error\. . .P This setting requires \fBdefault_manifest\fR to be set to an absolute path\. . .IP "\(bu" 4 \fIDefault\fR: false . .IP "" 0 . .SS "disable_warnings" A comma\-separated list of warning types to suppress\. If large numbers of warnings are making Puppet\'s logs too large or difficult to use, you can temporarily silence them with this setting\. . .P If you are preparing to upgrade Puppet to a new major version, you should re\-enable all warnings for a while\. . .P Valid values for this setting are: . .IP "\(bu" 4 \fBdeprecations\fR \-\-\- disables deprecation warnings\. . .IP "\(bu" 4 \fBundefined_variables\fR \-\-\- disables warnings about non existing variables\. . .IP "\(bu" 4 \fBundefined_resources\fR \-\-\- disables warnings about non existing resources\. . .IP "\(bu" 4 \fIDefault\fR: [] . .IP "" 0 . .SS "dns_alt_names" A comma\-separated list of alternate DNS names for Puppet Server\. These are extra hostnames (in addition to its \fBcertname\fR) that the server is allowed to use when serving agents\. Puppet checks this setting when automatically requesting a certificate for Puppet agent or Puppet Server, and when manually generating a certificate with \fBpuppet cert generate\fR\. These can be either IP or DNS, and the type should be specified and followed with a colon\. Untyped inputs will default to DNS\. . .P In order to handle agent requests at a given hostname (like "puppet\.example\.com"), Puppet Server needs a certificate that proves it\'s allowed to use that name; if a server shows a certificate that doesn\'t include its hostname, Puppet agents will refuse to trust it\. If you use a single hostname for Puppet traffic but load\-balance it to multiple Puppet Servers, each of those servers needs to include the official hostname in its list of extra names\. . .P \fBNote:\fR The list of alternate names is locked in when the server\'s certificate is signed\. If you need to change the list later, you can\'t just change this setting; you also need to: . .IP "\(bu" 4 On the server: Stop Puppet Server\. . .IP "\(bu" 4 On the CA server: Revoke and clean the server\'s old certificate\. (\fBpuppet cert clean \fR) (Note \fBpuppet cert clean\fR is deprecated and will be replaced with \fBpuppetserver ca clean\fR in Puppet 6\.) . .IP "\(bu" 4 On the server: Delete the old certificate (and any old certificate signing requests) from the ssldir \fIhttps://puppet\.com/docs/puppet/latest/dirs_ssldir\.html\fR\. . .IP "\(bu" 4 On the server: Run \fBpuppet agent \-t \-\-ca_server \fR to request a new certificate . .IP "\(bu" 4 On the CA server: Sign the certificate request, explicitly allowing alternate names (\fBpuppet cert sign \-\-allow\-dns\-alt\-names \fR)\. (Note \fBpuppet cert sign\fR is deprecated and will be replaced with \fBpuppetserver ca sign\fR in Puppet 6\.) . .IP "\(bu" 4 On the server: Run \fBpuppet agent \-t \-\-ca_server \fR to retrieve the cert\. . .IP "\(bu" 4 On the server: Start Puppet Server again\. . .IP "" 0 . .P To see all the alternate names your servers are using, log into your CA server and run \fBpuppet cert list \-a\fR, then check the output for \fB(alt names: \.\.\.)\fR\. Most agent nodes should NOT have alternate names; the only certs that should have them are Puppet Server nodes that you want other agents to trust\. . .SS "document_all" Whether to document all resources when using \fBpuppet doc\fR to generate manifest documentation\. . .IP "\(bu" 4 \fIDefault\fR: false . .IP "" 0 . .SS "environment" The environment in which Puppet is running\. For clients, such as \fBpuppet agent\fR, this determines the environment itself, which Puppet uses to find modules and much more\. For servers, such as \fBpuppet master\fR, this provides the default environment for nodes that Puppet knows nothing about\. . .P When defining an environment in the \fB[agent]\fR section, this refers to the environment that the agent requests from the master\. The environment doesn\'t have to exist on the local filesystem because the agent fetches it from the master\. This definition is used when running \fBpuppet agent\fR\. . .P When defined in the \fB[user]\fR section, the environment refers to the path that Puppet uses to search for code and modules related to its execution\. This requires the environment to exist locally on the filesystem where puppet is being executed\. Puppet subcommands, including \fBpuppet module\fR and \fBpuppet apply\fR, use this definition\. . .P Given that the context and effects vary depending on the config section \fIhttps://puppet\.com/docs/puppet/latest/config_file_main\.html#config\-sections\fR in which the \fBenvironment\fR setting is defined, do not set it globally\. . .IP "\(bu" 4 \fIDefault\fR: production . .IP "" 0 . .SS "environment_data_provider" The name of a registered environment data provider used when obtaining environment specific data\. The three built in and registered providers are \'none\' (no data), \'function\' (data obtained by calling the function \'environment::data()\') and \'hiera\' (data obtained using a data provider configured using a hiera\.yaml file in root of the environment)\. Other environment data providers may be registered in modules on the module path\. For such custom data providers see the respective module documentation\. This setting is deprecated\. . .TP \fIDefault\fR: . .SS "environment_timeout" How long the Puppet master should cache data it loads from an environment\. This setting can be a time interval in seconds (30 or 30s), minutes (30m), hours (6h), days (2d), or years (5y)\. A value of \fB0\fR will disable caching\. This setting can also be set to \fBunlimited\fR, which will cache environments until the master is restarted or told to refresh the cache\. . .P You should change this setting once your Puppet deployment is doing non\-trivial work\. We chose the default value of \fB0\fR because it lets new users update their code without any extra steps, but it lowers the performance of your Puppet master\. . .P We recommend setting this to \fBunlimited\fR and explicitly refreshing your Puppet master as part of your code deployment process\. . .IP "\(bu" 4 With Puppet Server, you should refresh environments by calling the \fBenvironment\-cache\fR API endpoint\. See the docs for the Puppet Server administrative API\. . .IP "\(bu" 4 With a Rack Puppet master, you should restart the web server or the application server\. Passenger lets you touch a \fBrestart\.txt\fR file to refresh an application without restarting Apache; see the Passenger docs for details\. . .IP "" 0 . .P We don\'t recommend using any value other than \fB0\fR or \fBunlimited\fR, since most Puppet masters use a pool of Ruby interpreters which all have their own cache timers\. When these timers drift out of sync, agents can be served inconsistent catalogs\. . .IP "\(bu" 4 \fIDefault\fR: 0 . .IP "" 0 . .SS "environmentpath" A search path for directory environments, as a list of directories separated by the system path separator character\. (The POSIX path separator is \':\', and the Windows path separator is \';\'\.) . .P This setting must have a value set to enable \fBdirectory environments\.\fR The recommended value is \fB$codedir/environments\fR\. For more details, see \fIhttps://puppet\.com/docs/puppet/latest/environments_about\.html\fR . .IP "\(bu" 4 \fIDefault\fR: $codedir/environments . .IP "" 0 . .SS "evaltrace" Whether each resource should log when it is being evaluated\. This allows you to interactively see exactly what is being done\. . .IP "\(bu" 4 \fIDefault\fR: false . .IP "" 0 . .SS "external_nodes" The external node classifier (ENC) script to use for node data\. Puppet combines this data with the main manifest to produce node catalogs\. . .P To enable this setting, set the \fBnode_terminus\fR setting to \fBexec\fR\. . .P This setting\'s value must be the path to an executable command that can produce node information\. The command must: . .IP "\(bu" 4 Take the name of a node as a command\-line argument\. . .IP "\(bu" 4 . .IP "\(bu" 4 \fBclasses\fR \-\-\- A list of classes, as an array or hash\. . .IP "\(bu" 4 \fBenvironment\fR \-\-\- A string\. . .IP "\(bu" 4 \fBparameters\fR \-\-\- A list of top\-scope variables to set, as a hash\. . .IP "" 0 . .IP "\(bu" 4 For unknown nodes, exit with a non\-zero exit code\. . .IP "" 0 . .P Generally, an ENC script makes requests to an external data source\. . .P For more info, see the ENC documentation \fIhttps://puppet\.com/docs/puppet/latest/nodes_external\.html\fR\. . .IP "\(bu" 4 \fIDefault\fR: none . .IP "" 0 . .SS "factpath" Where Puppet should look for facts\. Multiple directories should be separated by the system path separator character\. (The POSIX path separator is \':\', and the Windows path separator is \';\'\.) . .IP "\(bu" 4 \fIDefault\fR: $vardir/lib/facter:$vardir/facts . .IP "" 0 . .SS "facts_terminus" The node facts terminus\. . .IP "\(bu" 4 \fIDefault\fR: facter . .IP "" 0 . .SS "fileserverconfig" Where the fileserver configuration is stored\. . .IP "\(bu" 4 \fIDefault\fR: $confdir/fileserver\.conf . .IP "" 0 . .SS "filetimeout" The minimum time to wait between checking for updates in configuration files\. This timeout determines how quickly Puppet checks whether a file (such as manifests or templates) has changed on disk\. This setting can be a time interval in seconds (30 or 30s), minutes (30m), hours (6h), days (2d), or years (5y)\. . .IP "\(bu" 4 \fIDefault\fR: 15s . .IP "" 0 . .SS "forge_authorization" The authorization key to connect to the Puppet Forge\. Leave blank for unauthorized or license based connections . .TP \fIDefault\fR: . .SS "freeze_main" Freezes the \'main\' class, disallowing any code to be added to it\. This essentially means that you can\'t have any code outside of a node, class, or definition other than in the site manifest\. . .IP "\(bu" 4 \fIDefault\fR: false . .IP "" 0 . .SS "future_features" Whether or not to enable all features currently being developed for future major releases of Puppet\. Should be used with caution, as in development features are experimental and can have unexpected effects\. . .IP "\(bu" 4 \fIDefault\fR: false . .IP "" 0 . .SS "genconfig" When true, causes Puppet applications to print an example config file to stdout and exit\. The example will include descriptions of each setting, and the current (or default) value of each setting, incorporating any settings overridden on the CLI (with the exception of \fBgenconfig\fR itself)\. This setting only makes sense when specified on the command line as \fB\-\-genconfig\fR\. . .IP "\(bu" 4 \fIDefault\fR: false . .IP "" 0 . .SS "genmanifest" Whether to just print a manifest to stdout and exit\. Only makes sense when specified on the command line as \fB\-\-genmanifest\fR\. Takes into account arguments specified on the CLI\. . .IP "\(bu" 4 \fIDefault\fR: false . .IP "" 0 . .SS "graph" Whether to create \.dot graph files, which let you visualize the dependency and containment relationships in Puppet\'s catalog\. You can load and view these files with tools like OmniGraffle \fIhttp://www\.omnigroup\.com/applications/omnigraffle/\fR (OS X) or graphviz \fIhttp://www\.graphviz\.org/\fR (multi\-platform)\. . .P Graph files are created when \fIapplying\fR a catalog, so this setting should be used on nodes running \fBpuppet agent\fR or \fBpuppet apply\fR\. . .P The \fBgraphdir\fR setting determines where Puppet will save graphs\. Note that we don\'t save graphs for historical runs; Puppet will replace the previous \.dot files with new ones every time it applies a catalog\. . .P See your graphing software\'s documentation for details on opening \.dot files\. If you\'re using GraphViz\'s \fBdot\fR command, you can do a quick PNG render with \fBdot \-Tpng \-o \fR\. . .IP "\(bu" 4 \fIDefault\fR: false . .IP "" 0 . .SS "graphdir" Where to save \.dot\-format graphs (when the \fBgraph\fR setting is enabled)\. . .IP "\(bu" 4 \fIDefault\fR: $statedir/graphs . .IP "" 0 . .SS "group" The group Puppet Server will run as\. Used to ensure the agent side processes (agent, apply, etc) create files and directories readable by Puppet Server when necessary\. . .IP "\(bu" 4 \fIDefault\fR: puppet . .IP "" 0 . .SS "hiera_config" The hiera configuration file\. Puppet only reads this file on startup, so you must restart the puppet master every time you edit it\. . .IP "\(bu" 4 \fIDefault\fR: $confdir/hiera\.yaml\. However, if a file exists at $codedir/hiera\.yaml, Puppet uses that instead\. . .IP "" 0 . .SS "hostcert" Where individual hosts store and look for their certificates\. . .IP "\(bu" 4 \fIDefault\fR: $certdir/$certname\.pem . .IP "" 0 . .SS "hostcrl" Where the host\'s certificate revocation list can be found\. This is distinct from the certificate authority\'s CRL\. . .IP "\(bu" 4 \fIDefault\fR: $ssldir/crl\.pem . .IP "" 0 . .SS "hostcsr" Where individual hosts store and look for their certificate requests\. . .IP "\(bu" 4 \fIDefault\fR: $ssldir/csr_$certname\.pem . .IP "" 0 . .SS "hostprivkey" Where individual hosts store and look for their private key\. . .IP "\(bu" 4 \fIDefault\fR: $privatekeydir/$certname\.pem . .IP "" 0 . .SS "hostpubkey" Where individual hosts store and look for their public key\. . .IP "\(bu" 4 \fIDefault\fR: $publickeydir/$certname\.pem . .IP "" 0 . .SS "http_connect_timeout" The maximum amount of time to wait when establishing an HTTP connection\. The default value is 2 minutes\. This setting can be a time interval in seconds (30 or 30s), minutes (30m), hours (6h), days (2d), or years (5y)\. . .IP "\(bu" 4 \fIDefault\fR: 2m . .IP "" 0 . .SS "http_debug" Whether to write HTTP request and responses to stderr\. This should never be used in a production environment\. . .IP "\(bu" 4 \fIDefault\fR: false . .IP "" 0 . .SS "http_keepalive_timeout" The maximum amount of time a persistent HTTP connection can remain idle in the connection pool, before it is closed\. This timeout should be shorter than the keepalive timeout used on the HTTP server, e\.g\. Apache KeepAliveTimeout directive\. This setting can be a time interval in seconds (30 or 30s), minutes (30m), hours (6h), days (2d), or years (5y)\. . .IP "\(bu" 4 \fIDefault\fR: 4s . .IP "" 0 . .SS "http_proxy_host" The HTTP proxy host to use for outgoing connections\. Note: You may need to use a FQDN for the server hostname when using a proxy\. Environment variable http_proxy or HTTP_PROXY will override this value . .IP "\(bu" 4 \fIDefault\fR: none . .IP "" 0 . .SS "http_proxy_password" The password for the user of an authenticated HTTP proxy\. Requires the \fBhttp_proxy_user\fR setting\. . .P Note that passwords must be valid when used as part of a URL\. If a password contains any characters with special meanings in URLs (as specified by RFC 3986 section 2\.2), they must be URL\-encoded\. (For example, \fB#\fR would become \fB%23\fR\.) . .IP "\(bu" 4 \fIDefault\fR: none . .IP "" 0 . .SS "http_proxy_port" The HTTP proxy port to use for outgoing connections . .IP "\(bu" 4 \fIDefault\fR: 3128 . .IP "" 0 . .SS "http_proxy_user" The user name for an authenticated HTTP proxy\. Requires the \fBhttp_proxy_host\fR setting\. . .IP "\(bu" 4 \fIDefault\fR: none . .IP "" 0 . .SS "http_read_timeout" The time to wait for one block to be read from an HTTP connection\. If nothing is read after the elapsed interval then the connection will be closed\. The default value is unlimited\. This setting can be a time interval in seconds (30 or 30s), minutes (30m), hours (6h), days (2d), or years (5y)\. . .TP \fIDefault\fR: . .SS "http_user_agent" The HTTP User\-Agent string to send when making network requests\. . .IP "\(bu" 4 \fIDefault\fR: Puppet/5\.5\.9 Ruby/2\.4\.1\-p111 (x86_64\-linux) . .IP "" 0 . .SS "ignorecache" This setting has no effect and will be removed in a future Puppet version\. . .IP "\(bu" 4 \fIDefault\fR: false . .IP "" 0 . .SS "ignoremissingtypes" Skip searching for classes and definitions that were missing during a prior compilation\. The list of missing objects is maintained per\-environment and persists until the environment is cleared or the master is restarted\. . .IP "\(bu" 4 \fIDefault\fR: false . .IP "" 0 . .SS "ignoreschedules" Boolean; whether puppet agent should ignore schedules\. This is useful for initial puppet agent runs\. . .IP "\(bu" 4 \fIDefault\fR: false . .IP "" 0 . .SS "keylength" The bit length of keys\. . .IP "\(bu" 4 \fIDefault\fR: 4096 . .IP "" 0 . .SS "lastrunfile" Where puppet agent stores the last run report summary in yaml format\. . .IP "\(bu" 4 \fIDefault\fR: $statedir/last_run_summary\.yaml . .IP "" 0 . .SS "lastrunreport" Where puppet agent stores the last run report in yaml format\. . .IP "\(bu" 4 \fIDefault\fR: $statedir/last_run_report\.yaml . .IP "" 0 . .SS "ldapattrs" The LDAP attributes to include when querying LDAP for nodes\. All returned attributes are set as variables in the top\-level scope\. Multiple values should be comma\-separated\. The value \'all\' returns all attributes\. . .IP "\(bu" 4 \fIDefault\fR: all . .IP "" 0 . .SS "ldapbase" The search base for LDAP searches\. It\'s impossible to provide a meaningful default here, although the LDAP libraries might have one already set\. Generally, it should be the \'ou=Hosts\' branch under your main directory\. . .SS "ldapclassattrs" The LDAP attributes to use to define Puppet classes\. Values should be comma\-separated\. . .IP "\(bu" 4 \fIDefault\fR: puppetclass . .IP "" 0 . .SS "ldapparentattr" The attribute to use to define the parent node\. . .IP "\(bu" 4 \fIDefault\fR: parentnode . .IP "" 0 . .SS "ldappassword" The password to use to connect to LDAP\. . .SS "ldapport" The LDAP port\. Only used if \fBnode_terminus\fR is set to \fBldap\fR\. . .IP "\(bu" 4 \fIDefault\fR: 389 . .IP "" 0 . .SS "ldapserver" The LDAP server\. Only used if \fBnode_terminus\fR is set to \fBldap\fR\. . .IP "\(bu" 4 \fIDefault\fR: ldap . .IP "" 0 . .SS "ldapssl" Whether SSL should be used when searching for nodes\. Defaults to false because SSL usually requires certificates to be set up on the client side\. . .IP "\(bu" 4 \fIDefault\fR: false . .IP "" 0 . .SS "ldapstackedattrs" The LDAP attributes that should be stacked to arrays by adding the values in all hierarchy elements of the tree\. Values should be comma\-separated\. . .IP "\(bu" 4 \fIDefault\fR: puppetvar . .IP "" 0 . .SS "ldapstring" The search string used to find an LDAP node\. . .IP "\(bu" 4 \fIDefault\fR: (&(objectclass=puppetClient)(cn=%s)) . .IP "" 0 . .SS "ldaptls" Whether TLS should be used when searching for nodes\. Defaults to false because TLS usually requires certificates to be set up on the client side\. . .IP "\(bu" 4 \fIDefault\fR: false . .IP "" 0 . .SS "ldapuser" The user to use to connect to LDAP\. Must be specified as a full DN\. . .SS "libdir" An extra search path for Puppet\. This is only useful for those files that Puppet will load on demand, and is only guaranteed to work for those cases\. In fact, the autoload mechanism is responsible for making sure this directory is in Ruby\'s search path . .IP "\(bu" 4 \fIDefault\fR: $vardir/lib . .IP "" 0 . .SS "localcacert" Where each client stores the CA certificate\. . .IP "\(bu" 4 \fIDefault\fR: $certdir/ca\.pem . .IP "" 0 . .SS "localedest" Where Puppet should store translation files that it pulls down from the central server\. . .IP "\(bu" 4 \fIDefault\fR: $vardir/locales . .IP "" 0 . .SS "localesource" From where to retrieve translation files\. The standard Puppet \fBfile\fR type is used for retrieval, so anything that is a valid file source can be used here\. . .IP "\(bu" 4 \fIDefault\fR: puppet:///locales . .IP "" 0 . .SS "log_level" Default logging level for messages from Puppet\. Allowed values are: . .IP "\(bu" 4 debug . .IP "\(bu" 4 info . .IP "\(bu" 4 notice . .IP "\(bu" 4 warning . .IP "\(bu" 4 err . .IP "\(bu" 4 alert . .IP "\(bu" 4 emerg . .IP "\(bu" 4 crit . .IP "\(bu" 4 \fIDefault\fR: notice . .IP "" 0 . .SS "logdest" Where to send log messages\. Choose between \'syslog\' (the POSIX syslog service), \'eventlog\' (the Windows Event Log), \'console\', or the path to a log file\. . .TP \fIDefault\fR: . .SS "logdir" The directory in which to store log files . .IP "\(bu" 4 \fIDefault\fR: Unix/Linux: /var/log/puppetlabs/puppet \-\- Windows: C:\eProgramData\ePuppetLabs\epuppet\evar\elog \-\- Non\-root user: ~/\.puppetlabs/var/log . .IP "" 0 . .SS "manage_internal_file_permissions" Whether Puppet should manage the owner, group, and mode of files it uses internally . .IP "\(bu" 4 \fIDefault\fR: true . .IP "" 0 . .SS "manifest" The entry\-point manifest for puppet master\. This can be one file or a directory of manifests to be evaluated in alphabetical order\. Puppet manages this path as a directory if one exists or if the path ends with a / or \. . .P Setting a global value for \fBmanifest\fR in puppet\.conf is not allowed (but it can be overridden from the commandline)\. Please use directory environments instead\. If you need to use something other than the environment\'s \fBmanifests\fR directory as the main manifest, you can set \fBmanifest\fR in environment\.conf\. For more info, see \fIhttps://puppet\.com/docs/puppet/latest/environments_about\.html\fR . .TP \fIDefault\fR: . .SS "masterhttplog" Where the puppet master web server saves its access log\. This is only used when running a WEBrick puppet master\. When puppet master is running under a Rack server like Passenger, that web server will have its own logging behavior\. . .IP "\(bu" 4 \fIDefault\fR: $logdir/masterhttp\.log . .IP "" 0 . .SS "masterport" The default port puppet subcommands use to communicate with Puppet Server\. (eg \fBpuppet facts upload\fR, \fBpuppet agent\fR)\. May be overridden by more specific settings (see \fBca_port\fR, \fBreport_port\fR)\. . .IP "\(bu" 4 \fIDefault\fR: 8140 . .IP "" 0 . .SS "max_deprecations" Sets the max number of logged/displayed parser validation deprecation warnings in case multiple deprecation warnings have been detected\. A value of 0 blocks the logging of deprecation warnings\. The count is per manifest\. . .IP "\(bu" 4 \fIDefault\fR: 10 . .IP "" 0 . .SS "max_errors" Sets the max number of logged/displayed parser validation errors in case multiple errors have been detected\. A value of 0 is the same as a value of 1; a minimum of one error is always raised\. The count is per manifest\. . .IP "\(bu" 4 \fIDefault\fR: 10 . .IP "" 0 . .SS "max_warnings" Sets the max number of logged/displayed parser validation warnings in case multiple warnings have been detected\. A value of 0 blocks logging of warnings\. The count is per manifest\. . .IP "\(bu" 4 \fIDefault\fR: 10 . .IP "" 0 . .SS "maximum_uid" The maximum allowed UID\. Some platforms use negative UIDs but then ship with tools that do not know how to handle signed ints, so the UIDs show up as huge numbers that can then not be fed back into the system\. This is a hackish way to fail in a slightly more useful way when that happens\. . .IP "\(bu" 4 \fIDefault\fR: 4294967290 . .IP "" 0 . .SS "mkusers" Whether to create the necessary user and group that puppet agent will run as\. . .IP "\(bu" 4 \fIDefault\fR: false . .IP "" 0 . .SS "module_groups" Extra module groups to request from the Puppet Forge\. This is an internal setting, and users should never change it\. . .TP \fIDefault\fR: . .SS "module_repository" The module repository . .IP "\(bu" 4 \fIDefault\fR: https://forgeapi\.puppet\.com . .IP "" 0 . .SS "module_skeleton_dir" The directory which the skeleton for module tool generate is stored\. . .IP "\(bu" 4 \fIDefault\fR: $module_working_dir/skeleton . .IP "" 0 . .SS "module_working_dir" The directory into which module tool data is stored . .IP "\(bu" 4 \fIDefault\fR: $vardir/puppet\-module . .IP "" 0 . .SS "modulepath" The search path for modules, as a list of directories separated by the system path separator character\. (The POSIX path separator is \':\', and the Windows path separator is \';\'\.) . .P Setting a global value for \fBmodulepath\fR in puppet\.conf is not allowed (but it can be overridden from the commandline)\. Please use directory environments instead\. If you need to use something other than the default modulepath of \fB:$basemodulepath\fR, you can set \fBmodulepath\fR in environment\.conf\. For more info, see \fIhttps://puppet\.com/docs/puppet/latest/environments_about\.html\fR . .SS "name" The name of the application, if we are running as one\. The default is essentially $0 without the path or \fB\.rb\fR\. . .TP \fIDefault\fR: . .SS "node_cache_terminus" How to store cached nodes\. Valid values are (none), \'json\', \'msgpack\', \'yaml\' or write only yaml (\'write_only_yaml\')\. . .TP \fIDefault\fR: . .SS "node_name" How the puppet master determines the client\'s identity and sets the \'hostname\', \'fqdn\' and \'domain\' facts for use in the manifest, in particular for determining which \'node\' statement applies to the client\. Possible values are \'cert\' (use the subject\'s CN in the client\'s certificate) and \'facter\' (use the hostname that the client reported in its facts) . .IP "\(bu" 4 \fIDefault\fR: cert . .IP "" 0 . .SS "node_name_fact" The fact name used to determine the node name used for all requests the agent makes to the master\. WARNING: This setting is mutually exclusive with node_name_value\. Changing this setting also requires changes to the default auth\.conf configuration on the Puppet Master\. Please see http://links\.puppet\.com/node_name_fact for more information\. . .SS "node_name_value" The explicit value used for the node name for all requests the agent makes to the master\. WARNING: This setting is mutually exclusive with node_name_fact\. Changing this setting also requires changes to the default auth\.conf configuration on the Puppet Master\. Please see http://links\.puppet\.com/node_name_value for more information\. . .IP "\(bu" 4 \fIDefault\fR: $certname . .IP "" 0 . .SS "node_terminus" Which node data plugin to use when compiling node catalogs\. . .P When Puppet compiles a catalog, it combines two primary sources of info: the main manifest, and a node data plugin (often called a "node terminus," for historical reasons)\. Node data plugins provide three things for a given node name: . .IP "1." 4 A list of classes to add to that node\'s catalog (and, optionally, values for their parameters)\. . .IP "2." 4 Which Puppet environment the node should use\. . .IP "3." 4 A list of additional top\-scope variables to set\. . .IP "" 0 . .P The three main node data plugins are: . .IP "\(bu" 4 \fBplain\fR \-\-\- Returns no data, so that the main manifest controls all node configuration\. . .IP "\(bu" 4 \fBexec\fR \-\-\- Uses an external node classifier (ENC) \fIhttps://puppet\.com/docs/puppet/latest/nodes_external\.html\fR, configured by the \fBexternal_nodes\fR setting\. This lets you pull a list of Puppet classes from any external system, using a small glue script to perform the request and format the result as YAML\. . .IP "\(bu" 4 \fBclassifier\fR (formerly \fBconsole\fR) \-\-\- Specific to Puppet Enterprise\. Uses the PE console for node data\." . .IP "\(bu" 4 \fIDefault\fR: plain . .IP "" 0 . .SS "noop" Whether to apply catalogs in noop mode, which allows Puppet to partially simulate a normal run\. This setting affects puppet agent and puppet apply\. . .P When running in noop mode, Puppet will check whether each resource is in sync, like it does when running normally\. However, if a resource attribute is not in the desired state (as declared in the catalog), Puppet will take no action, and will instead report the changes it \fIwould\fR have made\. These simulated changes will appear in the report sent to the puppet master, or be shown on the console if running puppet agent or puppet apply in the foreground\. The simulated changes will not send refresh events to any subscribing or notified resources, although Puppet will log that a refresh event \fIwould\fR have been sent\. . .P \fBImportant note:\fR The \fBnoop\fR metaparameter \fIhttps://puppet\.com/docs/puppet/latest/metaparameter\.html#noop\fR allows you to apply individual resources in noop mode, and will override the global value of the \fBnoop\fR setting\. This means a resource with \fBnoop => false\fR \fIwill\fR be changed if necessary, even when running puppet agent with \fBnoop = true\fR or \fB\-\-noop\fR\. (Conversely, a resource with \fBnoop => true\fR will only be simulated, even when noop mode is globally disabled\.) . .IP "\(bu" 4 \fIDefault\fR: false . .IP "" 0 . .SS "onetime" Perform one configuration run and exit, rather than spawning a long\-running daemon\. This is useful for interactively running puppet agent, or running puppet agent from cron\. . .IP "\(bu" 4 \fIDefault\fR: false . .IP "" 0 . .SS "ordering" How unrelated resources should be ordered when applying a catalog\. Allowed values are \fBtitle\-hash\fR, \fBmanifest\fR, and \fBrandom\fR\. This setting affects puppet agent and puppet apply, but not puppet master\. . .IP "\(bu" 4 \fBmanifest\fR (the default) will use the order in which the resources were declared in their manifest files\. . .IP "\(bu" 4 \fBtitle\-hash\fR (the default in 3\.x) will order resources randomly, but will use the same order across runs and across nodes\. It is only of value if you\'re migrating from 3\.x and have errors running with \fBmanifest\fR\. . .IP "\(bu" 4 \fBrandom\fR will order resources randomly and change their order with each run\. This can work like a fuzzer for shaking out undeclared dependencies\. . .IP "" 0 . .P Regardless of this setting\'s value, Puppet will always obey explicit dependencies set with the before/require/notify/subscribe metaparameters and the \fB\->\fR/\fB~>\fR chaining arrows; this setting only affects the relative ordering of \fIunrelated\fR resources\. . .P This setting is deprecated, and will always have a value of \fBmanifest\fR in 6\.0 and up\. . .IP "\(bu" 4 \fIDefault\fR: manifest . .IP "" 0 . .SS "passfile" Where puppet agent stores the password for its private key\. Generally unused\. . .IP "\(bu" 4 \fIDefault\fR: $privatedir/password . .IP "" 0 . .SS "path" The shell search path\. Defaults to whatever is inherited from the parent process\. . .P This setting can only be set in the \fB[main]\fR section of puppet\.conf; it cannot be set in \fB[master]\fR, \fB[agent]\fR, or an environment config section\. . .IP "\(bu" 4 \fIDefault\fR: none . .IP "" 0 . .SS "pidfile" The file containing the PID of a running process\. This file is intended to be used by service management frameworks and monitoring systems to determine if a puppet process is still in the process table\. . .IP "\(bu" 4 \fIDefault\fR: $rundir/${run_mode}\.pid . .IP "" 0 . .SS "plugindest" Where Puppet should store plugins that it pulls down from the central server\. . .IP "\(bu" 4 \fIDefault\fR: $libdir . .IP "" 0 . .SS "pluginfactdest" Where Puppet should store external facts that are being handled by pluginsync . .IP "\(bu" 4 \fIDefault\fR: $vardir/facts\.d . .IP "" 0 . .SS "pluginfactsource" Where to retrieve external facts for pluginsync . .IP "\(bu" 4 \fIDefault\fR: puppet:///pluginfacts . .IP "" 0 . .SS "pluginsignore" What files to ignore when pulling down plugins\. . .IP "\(bu" 4 \fIDefault\fR: \.svn CVS \.git \.hg . .IP "" 0 . .SS "pluginsource" From where to retrieve plugins\. The standard Puppet \fBfile\fR type is used for retrieval, so anything that is a valid file source can be used here\. . .IP "\(bu" 4 \fIDefault\fR: puppet:///plugins . .IP "" 0 . .SS "pluginsync" Whether plugins should be synced with the central server\. This setting is deprecated\. . .IP "\(bu" 4 \fIDefault\fR: true . .IP "" 0 . .SS "postrun_command" A command to run after every agent run\. If this command returns a non\-zero return code, the entire Puppet run will be considered to have failed, even though it might have performed work during the normal run\. . .SS "preferred_serialization_format" The preferred means of serializing ruby instances for passing over the wire\. This won\'t guarantee that all instances will be serialized using this method, since not all classes can be guaranteed to support this format, but it will be used for all classes that support it\. . .IP "\(bu" 4 \fIDefault\fR: json . .IP "" 0 . .SS "prerun_command" A command to run before every agent run\. If this command returns a non\-zero return code, the entire Puppet run will fail\. . .SS "preview_outputdir" The directory where catalog previews per node are generated\. . .IP "\(bu" 4 \fIDefault\fR: $vardir/preview . .IP "" 0 . .SS "priority" The scheduling priority of the process\. Valid values are \'high\', \'normal\', \'low\', or \'idle\', which are mapped to platform\-specific values\. The priority can also be specified as an integer value and will be passed as is, e\.g\. \-5\. Puppet must be running as a privileged user in order to increase scheduling priority\. . .TP \fIDefault\fR: . .SS "privatedir" Where the client stores private certificate information\. . .IP "\(bu" 4 \fIDefault\fR: $ssldir/private . .IP "" 0 . .SS "privatekeydir" The private key directory\. . .IP "\(bu" 4 \fIDefault\fR: $ssldir/private_keys . .IP "" 0 . .SS "profile" Whether to enable experimental performance profiling . .IP "\(bu" 4 \fIDefault\fR: false . .IP "" 0 . .SS "publickeydir" The public key directory\. . .IP "\(bu" 4 \fIDefault\fR: $ssldir/public_keys . .IP "" 0 . .SS "puppetdlog" The fallback log file\. This is only used when the \fB\-\-logdest\fR option is not specified AND Puppet is running on an operating system where both the POSIX syslog service and the Windows Event Log are unavailable\. (Currently, no supported operating systems match that description\.) . .P Despite the name, both puppet agent and puppet master will use this file as the fallback logging destination\. . .P For control over logging destinations, see the \fB\-\-logdest\fR command line option in the manual pages for puppet master, puppet agent, and puppet apply\. You can see man pages by running \fBpuppet \-\-help\fR, or read them online at https://puppet\.com/docs/puppet/latest/man/\. . .IP "\(bu" 4 \fIDefault\fR: $logdir/puppetd\.log . .IP "" 0 . .SS "report" Whether to send reports after every transaction\. . .IP "\(bu" 4 \fIDefault\fR: true . .IP "" 0 . .SS "report_port" The port to communicate with the report_server\. . .IP "\(bu" 4 \fIDefault\fR: $masterport . .IP "" 0 . .SS "report_server" The server to send transaction reports to\. . .IP "\(bu" 4 \fIDefault\fR: $server . .IP "" 0 . .SS "reportdir" The directory in which to store reports\. Each node gets a separate subdirectory in this directory\. This setting is only used when the \fBstore\fR report processor is enabled (see the \fBreports\fR setting)\. . .IP "\(bu" 4 \fIDefault\fR: $vardir/reports . .IP "" 0 . .SS "reports" The list of report handlers to use\. When using multiple report handlers, their names should be comma\-separated, with whitespace allowed\. (For example, \fBreports = http, store\fR\.) . .P This setting is relevant to puppet master and puppet apply\. The puppet master will call these report handlers with the reports it receives from agent nodes, and puppet apply will call them with its own report\. (In all cases, the node applying the catalog must have \fBreport = true\fR\.) . .P See the report reference for information on the built\-in report handlers; custom report handlers can also be loaded from modules\. (Report handlers are loaded from the lib directory, at \fBpuppet/reports/NAME\.rb\fR\.) . .IP "\(bu" 4 \fIDefault\fR: store . .IP "" 0 . .SS "reporturl" The URL that reports should be forwarded to\. This setting is only used when the \fBhttp\fR report processor is enabled (see the \fBreports\fR setting)\. . .IP "\(bu" 4 \fIDefault\fR: http://localhost:3000/reports/upload . .IP "" 0 . .SS "requestdir" Where host certificate requests are stored\. . .IP "\(bu" 4 \fIDefault\fR: $ssldir/certificate_requests . .IP "" 0 . .SS "resourcefile" The file in which puppet agent stores a list of the resources associated with the retrieved configuration\. . .IP "\(bu" 4 \fIDefault\fR: $statedir/resources\.txt . .IP "" 0 . .SS "rest_authconfig" The configuration file that defines the rights to the different rest indirections\. This can be used as a fine\-grained authorization system for \fBpuppet master\fR\. The \fBpuppet master\fR command is deprecated and Puppet Server uses its own auth\.conf that must be placed within its configuration directory\. . .IP "\(bu" 4 \fIDefault\fR: $confdir/auth\.conf . .IP "" 0 . .SS "rich_data" Enables having extended data in the catalog by storing them as a hash with the special key \fB__pcore_type__\fR\. When enabled, resource containing values of the data types \fBBinary\fR, \fBRegexp\fR, \fBSemVer\fR, \fBSemVerRange\fR, \fBTimespan\fR and \fBTimestamp\fR, as well as instances of types derived from \fBObject\fR retain their data type\. . .IP "\(bu" 4 \fIDefault\fR: false . .IP "" 0 . .SS "route_file" The YAML file containing indirector route configuration\. . .IP "\(bu" 4 \fIDefault\fR: $confdir/routes\.yaml . .IP "" 0 . .SS "rundir" Where Puppet PID files are kept\. . .IP "\(bu" 4 \fIDefault\fR: Unix/Linux: /var/run/puppetlabs \-\- Windows: C:\eProgramData\ePuppetLabs\epuppet\evar\erun \-\- Non\-root user: ~/\.puppetlabs/var/run . .IP "" 0 . .SS "runinterval" How often puppet agent applies the catalog\. Note that a runinterval of 0 means "run continuously" rather than "never run\." If you want puppet agent to never run, you should start it with the \fB\-\-no\-client\fR option\. This setting can be a time interval in seconds (30 or 30s), minutes (30m), hours (6h), days (2d), or years (5y)\. . .IP "\(bu" 4 \fIDefault\fR: 30m . .IP "" 0 . .SS "runtimeout" The maximum amount of time an agent run is allowed to take\. A Puppet agent run that exceeds this timeout will be aborted\. Defaults to 0, which is unlimited\. This setting can be a time interval in seconds (30 or 30s), minutes (30m), hours (6h), days (2d), or years (5y)\. . .IP "\(bu" 4 \fIDefault\fR: 0 . .IP "" 0 . .SS "serial" Where the serial number for certificates is stored\. . .IP "\(bu" 4 \fIDefault\fR: $cadir/serial . .IP "" 0 . .SS "server" The puppet master server to which the puppet agent should connect\. . .IP "\(bu" 4 \fIDefault\fR: puppet . .IP "" 0 . .SS "server_datadir" The directory in which serialized data is stored, usually in a subdirectory\. . .IP "\(bu" 4 \fIDefault\fR: $vardir/server_data . .IP "" 0 . .SS "server_list" The list of puppet master servers to which the puppet agent should connect, in the order that they will be tried\. . .IP "\(bu" 4 \fIDefault\fR: [] . .IP "" 0 . .SS "show_diff" Whether to log and report a contextual diff when files are being replaced\. This causes partial file contents to pass through Puppet\'s normal logging and reporting system, so this setting should be used with caution if you are sending Puppet\'s reports to an insecure destination\. This feature currently requires the \fBdiff/lcs\fR Ruby library\. . .IP "\(bu" 4 \fIDefault\fR: false . .IP "" 0 . .SS "signeddir" Where the CA stores signed certificates\. . .IP "\(bu" 4 \fIDefault\fR: $cadir/signed . .IP "" 0 . .SS "skip_tags" Tags to use to filter resources\. If this is set, then only resources not tagged with the specified tags will be applied\. Values must be comma\-separated\. . .SS "sourceaddress" The address the agent should use to initiate requests\. . .TP \fIDefault\fR: . .SS "splay" Whether to sleep for a random amount of time, ranging from immediately up to its \fB$splaylimit\fR, before performing its first agent run after a service restart\. After this period, the agent runs periodically on its \fB$runinterval\fR\. . .P For example, assume a default 30\-minute \fB$runinterval\fR, \fBsplay\fR set to its default of \fBfalse\fR, and an agent starting at :00 past the hour\. The agent would check in every 30 minutes at :01 and :31 past the hour\. . .P With \fBsplay\fR enabled, it waits any amount of time up to its \fB$splaylimit\fR before its first run\. For example, it might randomly wait 8 minutes, then start its first run at :08 past the hour\. With the \fB$runinterval\fR at its default 30 minutes, its next run will be at :38 past the hour\. . .P If you restart an agent\'s puppet service with \fBsplay\fR enabled, it recalculates its splay period and delays its first agent run after restarting for this new period\. If you simultaneously restart a group of puppet agents with \fBsplay\fR enabled, their checkins to your puppet masters can be distributed more evenly\. . .IP "\(bu" 4 \fIDefault\fR: false . .IP "" 0 . .SS "splaylimit" The maximum time to delay before an agent\'s first run when \fBsplay\fR is enabled\. Defaults to the agent\'s \fB$runinterval\fR\. The \fBsplay\fR interval is random and recalculated each time the agent is started or restarted\. This setting can be a time interval in seconds (30 or 30s), minutes (30m), hours (6h), days (2d), or years (5y)\. . .IP "\(bu" 4 \fIDefault\fR: $runinterval . .IP "" 0 . .SS "srv_domain" The domain which will be queried to find the SRV records of servers to use\. . .IP "\(bu" 4 \fIDefault\fR: delivery\.puppetlabs\.net . .IP "" 0 . .SS "ssl_client_ca_auth" Certificate authorities who issue server certificates\. SSL servers will not be considered authentic unless they possess a certificate issued by an authority listed in this file\. If this setting has no value then the Puppet master\'s CA certificate (localcacert) will be used\. . .TP \fIDefault\fR: . .SS "ssl_client_header" The header containing an authenticated client\'s SSL DN\. This header must be set by the proxy to the authenticated client\'s SSL DN (e\.g\., \fB/CN=puppet\.puppetlabs\.com\fR)\. Puppet will parse out the Common Name (CN) from the Distinguished Name (DN) and use the value of the CN field for authorization\. . .P Note that the name of the HTTP header gets munged by the web server common gateway interface: an \fBHTTP_\fR prefix is added, dashes are converted to underscores, and all letters are uppercased\. Thus, to use the \fBX\-Client\-DN\fR header, this setting should be \fBHTTP_X_CLIENT_DN\fR\. . .IP "\(bu" 4 \fIDefault\fR: HTTP_X_CLIENT_DN . .IP "" 0 . .SS "ssl_client_verify_header" The header containing the status message of the client verification\. This header must be set by the proxy to \'SUCCESS\' if the client successfully authenticated, and anything else otherwise\. . .P Note that the name of the HTTP header gets munged by the web server common gateway interface: an \fBHTTP_\fR prefix is added, dashes are converted to underscores, and all letters are uppercased\. Thus, to use the \fBX\-Client\-Verify\fR header, this setting should be \fBHTTP_X_CLIENT_VERIFY\fR\. . .IP "\(bu" 4 \fIDefault\fR: HTTP_X_CLIENT_VERIFY . .IP "" 0 . .SS "ssl_server_ca_auth" Certificate authorities who issue client certificates\. SSL clients will not be considered authentic unless they possess a certificate issued by an authority listed in this file\. If this setting has no value then the Puppet master\'s CA certificate (localcacert) will be used\. . .TP \fIDefault\fR: . .SS "ssldir" Where SSL certificates are kept\. . .IP "\(bu" 4 \fIDefault\fR: $confdir/ssl . .IP "" 0 . .SS "statedir" The directory where Puppet state is stored\. Generally, this directory can be removed without causing harm (although it might result in spurious service restarts)\. . .IP "\(bu" 4 \fIDefault\fR: $vardir/state . .IP "" 0 . .SS "statefile" Where puppet agent and puppet master store state associated with the running configuration\. In the case of puppet master, this file reflects the state discovered through interacting with clients\. . .IP "\(bu" 4 \fIDefault\fR: $statedir/state\.yaml . .IP "" 0 . .SS "statettl" How long the Puppet agent should cache when a resource was last checked or synced\. This setting can be a time interval in seconds (30 or 30s), minutes (30m), hours (6h), days (2d), or years (5y)\. A value of \fB0\fR or \fBunlimited\fR will disable cache pruning\. . .P This setting affects the usage of \fBschedule\fR resources, as the information about when a resource was last checked (and therefore when it needs to be checked again) is stored in the \fBstatefile\fR\. The \fBstatettl\fR needs to be large enough to ensure that a resource will not trigger multiple times during a schedule due to its entry expiring from the cache\. . .IP "\(bu" 4 \fIDefault\fR: 32d . .IP "" 0 . .SS "static_catalogs" Whether to compile a static catalog \fIhttps://puppet\.com/docs/puppet/latest/static_catalogs\.html#enabling\-or\-disabling\-static\-catalogs\fR, which occurs only on a Puppet Server master when the \fBcode\-id\-command\fR and \fBcode\-content\-command\fR settings are configured in its \fBpuppetserver\.conf\fR file\. . .IP "\(bu" 4 \fIDefault\fR: true . .IP "" 0 . .SS "storeconfigs" Whether to store each client\'s configuration, including catalogs, facts, and related data\. This also enables the import and export of resources in the Puppet language \- a mechanism for exchange resources between nodes\. . .P By default this uses the \'puppetdb\' backend\. . .P You can adjust the backend using the storeconfigs_backend setting\. . .IP "\(bu" 4 \fIDefault\fR: false . .IP "" 0 . .SS "storeconfigs_backend" Configure the backend terminus used for StoreConfigs\. By default, this uses the PuppetDB store, which must be installed and configured before turning on StoreConfigs\. . .IP "\(bu" 4 \fIDefault\fR: puppetdb . .IP "" 0 . .SS "strict" The strictness level of puppet\. Allowed values are: . .IP "\(bu" 4 off \- do not perform extra validation, do not report . .IP "\(bu" 4 warning \- perform extra validation, report as warning (default) . .IP "\(bu" 4 error \- perform extra validation, fail with error . .IP "" 0 . .P The strictness level is for both language semantics and runtime evaluation validation\. In addition to controlling the behavior with this master switch some individual warnings may also be controlled by the disable_warnings setting\. . .P No new validations will be added to a micro (x\.y\.z) release, but may be added in minor releases (x\.y\.0)\. In major releases it expected that most (if not all) strictness validation become standard behavior\. . .IP "\(bu" 4 \fIDefault\fR: warning . .IP "" 0 . .SS "strict_environment_mode" Whether the agent specified environment should be considered authoritative, causing the run to fail if the retrieved catalog does not match it\. . .IP "\(bu" 4 \fIDefault\fR: false . .IP "" 0 . .SS "strict_hostname_checking" Whether to only search for the complete hostname as it is in the certificate when searching for node information in the catalogs\. . .IP "\(bu" 4 \fIDefault\fR: false . .IP "" 0 . .SS "strict_variables" Causes an evaluation error when referencing unknown variables\. (This does not affect referencing variables that are explicitly set to undef)\. . .IP "\(bu" 4 \fIDefault\fR: false . .IP "" 0 . .SS "summarize" Whether to print a transaction summary\. . .IP "\(bu" 4 \fIDefault\fR: false . .IP "" 0 . .SS "supported_checksum_types" Checksum types supported by this agent for use in file resources of a static catalog\. Values must be comma\-separated\. Valid types are md5, md5lite, sha256, sha256lite, sha384, sha512, sha224, sha1, sha1lite, mtime, ctime\. Default is md5, sha256, sha384, sha512, sha224\. . .IP "\(bu" 4 \fIDefault\fR: ["md5", "sha256", "sha384", "sha512", "sha224"] . .IP "" 0 . .SS "syslogfacility" What syslog facility to use when logging to syslog\. Syslog has a fixed list of valid facilities, and you must choose one of those; you cannot just make one up\. . .IP "\(bu" 4 \fIDefault\fR: daemon . .IP "" 0 . .SS "tags" Tags to use to find resources\. If this is set, then only resources tagged with the specified tags will be applied\. Values must be comma\-separated\. . .SS "tasks" Turns on experimental support for tasks and plans in the puppet language\. This is for internal API use only\. Do not change this setting\. . .IP "\(bu" 4 \fIDefault\fR: false . .IP "" 0 . .SS "trace" Whether to print stack traces on some errors . .IP "\(bu" 4 \fIDefault\fR: false . .IP "" 0 . .SS "transactionstorefile" Transactional storage file for persisting data between transactions for the purposes of infering information (such as corrective_change) on new data received\. . .IP "\(bu" 4 \fIDefault\fR: $statedir/transactionstore\.yaml . .IP "" 0 . .SS "trusted_oid_mapping_file" File that provides mapping between custom SSL oids and user\-friendly names . .IP "\(bu" 4 \fIDefault\fR: $confdir/custom_trusted_oid_mapping\.yaml . .IP "" 0 . .SS "trusted_server_facts" The \'trusted_server_facts\' setting is deprecated and has no effect as the feature this enabled is now always on\. The setting will be removed in a future version of puppet\. . .IP "\(bu" 4 \fIDefault\fR: true . .IP "" 0 . .SS "use_cached_catalog" Whether to only use the cached catalog rather than compiling a new catalog on every run\. Puppet can be run with this enabled by default and then selectively disabled when a recompile is desired\. Because a Puppet agent using cached catalogs does not contact the master for a new catalog, it also does not upload facts at the beginning of the Puppet run\. . .IP "\(bu" 4 \fIDefault\fR: false . .IP "" 0 . .SS "use_srv_records" Whether the server will search for SRV records in DNS for the current domain\. . .IP "\(bu" 4 \fIDefault\fR: false . .IP "" 0 . .SS "usecacheonfailure" Whether to use the cached configuration when the remote configuration will not compile\. This option is useful for testing new configurations, where you want to fix the broken configuration rather than reverting to a known\-good one\. . .IP "\(bu" 4 \fIDefault\fR: true . .IP "" 0 . .SS "user" The user Puppet Server will run as\. Used to ensure the agent side processes (agent, apply, etc) create files and directories readable by Puppet Server when necessary\. . .IP "\(bu" 4 \fIDefault\fR: puppet . .IP "" 0 . .SS "vardir" Where Puppet stores dynamic and growing data\. The default for this setting is calculated specially, like \fBconfdir\fR_\. . .IP "\(bu" 4 \fIDefault\fR: Unix/Linux: /opt/puppetlabs/puppet/cache \-\- Windows: C:\eProgramData\ePuppetLabs\epuppet\ecache \-\- Non\-root user: ~/\.puppetlabs/opt/puppet/cache . .IP "" 0 . .SS "waitforcert" How frequently puppet agent should ask for a signed certificate\. . .P When starting for the first time, puppet agent will submit a certificate signing request (CSR) to the server named in the \fBca_server\fR setting (usually the puppet master); this may be autosigned, or may need to be approved by a human, depending on the CA server\'s configuration\. . .P Puppet agent cannot apply configurations until its approved certificate is available\. Since the certificate may or may not be available immediately, puppet agent will repeatedly try to fetch it at this interval\. You can turn off waiting for certificates by specifying a time of 0, in which case puppet agent will exit if it cannot get a cert\. This setting can be a time interval in seconds (30 or 30s), minutes (30m), hours (6h), days (2d), or years (5y)\. . .IP "\(bu" 4 \fIDefault\fR: 2m . .IP "" 0 . .SS "yamldir" The directory in which YAML data is stored, usually in a subdirectory\. . .IP "\(bu" 4 \fIDefault\fR: $vardir/yaml . .IP "" 0 puppet-5.5.10/man/man8/0000755005276200011600000000000013417162176014435 5ustar jenkinsjenkinspuppet-5.5.10/man/man8/puppet-catalog.80000644005276200011600000001625613417161721017460 0ustar jenkinsjenkins.\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . .TH "PUPPET\-CATALOG" "8" "January 2019" "Puppet, Inc." "Puppet manual" . .SH "NAME" \fBpuppet\-catalog\fR \- Compile, save, view, and convert catalogs\. . .SH "SYNOPSIS" puppet catalog \fIaction\fR [\-\-terminus _TERMINUS] [\-\-extra HASH] . .SH "DESCRIPTION" This subcommand deals with catalogs, which are compiled per\-node artifacts generated from a set of Puppet manifests\. By default, it interacts with the compiling subsystem and compiles a catalog using the default manifest and \fBcertname\fR, but you can change the source of the catalog with the \fB\-\-terminus\fR option\. You can also choose to print any catalog in \'dot\' format (for easy graph viewing with OmniGraffle or Graphviz) with \'\-\-render\-as dot\'\. . .SH "OPTIONS" Note that any setting that\'s valid in the configuration file is also a valid long argument, although it may or may not be relevant to the present action\. For example, \fBserver\fR and \fBrun_mode\fR are valid settings, so you can specify \fB\-\-server \fR, or \fB\-\-run_mode \fR as an argument\. . .P See the configuration file documentation at \fIhttps://puppet\.com/docs/puppet/latest/configuration\.html\fR for the full list of acceptable parameters\. A commented list of all configuration options can also be generated by running puppet with \fB\-\-genconfig\fR\. . .TP \-\-render\-as FORMAT The format in which to render output\. The most common formats are \fBjson\fR, \fBs\fR (string), \fByaml\fR, and \fBconsole\fR, but other options such as \fBdot\fR are sometimes available\. . .TP \-\-verbose Whether to log verbosely\. . .TP \-\-debug Whether to log debug information\. . .TP \-\-extra HASH A terminus can take additional arguments to refine the operation, which are passed as an arbitrary hash to the back\-end\. Anything passed as the extra value is just send direct to the back\-end\. . .TP \-\-terminus _TERMINUS Indirector faces expose indirected subsystems of Puppet\. These subsystems are each able to retrieve and alter a specific type of data (with the familiar actions of \fBfind\fR, \fBsearch\fR, \fBsave\fR, and \fBdestroy\fR) from an arbitrary number of pluggable backends\. In Puppet parlance, these backends are called terminuses\. . .IP Almost all indirected subsystems have a \fBrest\fR terminus that interacts with the puppet master\'s data\. Most of them have additional terminuses for various local data models, which are in turn used by the indirected subsystem on the puppet master whenever it receives a remote request\. . .IP The terminus for an action is often determined by context, but occasionally needs to be set explicitly\. See the "Notes" section of this face\'s manpage for more details\. . .SH "ACTIONS" . .TP \fBapply\fR \- Find and apply a catalog\. \fBSYNOPSIS\fR . .IP puppet catalog apply [\-\-terminus _TERMINUS] [\-\-extra HASH] . .IP \fBDESCRIPTION\fR . .IP Finds and applies a catalog\. This action takes no arguments, but the source of the catalog can be managed with the \fB\-\-terminus\fR option\. . .IP \fBRETURNS\fR . .IP Nothing\. When used from the Ruby API, returns a Puppet::Transaction::Report object\. . .TP \fBdownload\fR \- Download this node\'s catalog from the puppet master server\. \fBSYNOPSIS\fR . .IP puppet catalog download [\-\-terminus _TERMINUS] [\-\-extra HASH] . .IP \fBDESCRIPTION\fR . .IP Retrieves a catalog from the puppet master and saves it to the local yaml cache\. This action always contacts the puppet master and will ignore alternate termini\. . .IP The saved catalog can be used in any subsequent catalog action by specifying \'\-\-terminus yaml\' for that action\. . .IP \fBRETURNS\fR . .IP Nothing\. . .IP \fBNOTES\fR . .IP When used from the Ruby API, this action has a side effect of leaving Puppet::Resource::Catalog\.indirection\.terminus_class set to yaml\. The terminus must be explicitly re\-set for subsequent catalog actions\. . .TP \fBfind\fR \- Retrieve the catalog for a node\. \fBSYNOPSIS\fR . .IP puppet catalog find [\-\-terminus _TERMINUS] [\-\-extra HASH] \fIcertname\fR . .IP \fBDESCRIPTION\fR . .IP Retrieve the catalog for a node\. . .IP \fBRETURNS\fR . .IP A serialized catalog\. When used from the Ruby API, returns a Puppet::Resource::Catalog object\. . .TP \fBinfo\fR \- Print the default terminus class for this face\. \fBSYNOPSIS\fR . .IP puppet catalog info [\-\-terminus _TERMINUS] [\-\-extra HASH] . .IP \fBDESCRIPTION\fR . .IP Prints the default terminus class for this subcommand\. Note that different run modes may have different default termini; when in doubt, specify the run mode with the \'\-\-run_mode\' option\. . .TP \fBsave\fR \- API only: create or overwrite an object\. \fBSYNOPSIS\fR . .IP puppet catalog save [\-\-terminus _TERMINUS] [\-\-extra HASH] \fIkey\fR . .IP \fBDESCRIPTION\fR . .IP API only: create or overwrite an object\. As the Faces framework does not currently accept data from STDIN, save actions cannot currently be invoked from the command line\. . .TP \fBselect\fR \- Retrieve a catalog and filter it for resources of a given type\. \fBSYNOPSIS\fR . .IP puppet catalog select [\-\-terminus _TERMINUS] [\-\-extra HASH] \fIhost\fR \fIresource_type\fR . .IP \fBDESCRIPTION\fR . .IP Retrieves a catalog for the specified host, then searches it for all resources of the requested type\. . .IP \fBRETURNS\fR . .IP A list of resource references ("Type[title]")\. When used from the API, returns an array of Puppet::Resource objects excised from a catalog\. . .IP \fBNOTES\fR . .IP By default, this action will retrieve a catalog from Puppet\'s compiler subsystem; you must call the action with \fB\-\-terminus rest\fR if you wish to retrieve a catalog from the puppet master\. . .IP FORMATTING ISSUES: This action cannot currently render useful yaml; instead, it returns an entire catalog\. Use json instead\. . .SH "EXAMPLES" \fBapply\fR . .P Apply the locally cached catalog: . .P $ puppet catalog apply \-\-terminus yaml . .P Retrieve a catalog from the master and apply it, in one step: . .P $ puppet catalog apply \-\-terminus rest . .P API example: . .IP "" 4 . .nf # \.\.\. Puppet::Face[:catalog, \'0\.0\.1\']\.download # (Termini are singletons; catalog\.download has a side effect of # setting the catalog terminus to yaml) report = Puppet::Face[:catalog, \'0\.0\.1\']\.apply # \.\.\. . .fi . .IP "" 0 . .P \fBdownload\fR . .P Retrieve and store a catalog: . .P $ puppet catalog download . .P API example: . .IP "" 4 . .nf Puppet::Face[:plugin, \'0\.0\.1\']\.download Puppet::Face[:facts, \'0\.0\.1\']\.upload Puppet::Face[:catalog, \'0\.0\.1\']\.download # \.\.\. . .fi . .IP "" 0 . .P \fBselect\fR . .P Ask the puppet master for a list of managed file resources for a node: . .P $ puppet catalog select \-\-terminus rest somenode\.magpie\.lan file . .SH "NOTES" This subcommand is an indirector face, which exposes \fBfind\fR, \fBsearch\fR, \fBsave\fR, and \fBdestroy\fR actions for an indirected subsystem of Puppet\. Valid termini for this face include: . .IP "\(bu" 4 \fBcompiler\fR . .IP "\(bu" 4 \fBjson\fR . .IP "\(bu" 4 \fBmsgpack\fR . .IP "\(bu" 4 \fBrest\fR . .IP "\(bu" 4 \fBstore_configs\fR . .IP "\(bu" 4 \fByaml\fR . .IP "" 0 . .SH "COPYRIGHT AND LICENSE" Copyright 2011 by Puppet Inc\. Apache 2 license; see COPYING puppet-5.5.10/man/man8/puppet-describe.80000644005276200011600000000162113417161721017614 0ustar jenkinsjenkins.\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . .TH "PUPPET\-DESCRIBE" "8" "January 2019" "Puppet, Inc." "Puppet manual" . .SH "NAME" \fBpuppet\-describe\fR \- Display help about resource types . .SH "SYNOPSIS" Prints help about Puppet resource types, providers, and metaparameters\. . .SH "USAGE" puppet describe [\-h|\-\-help] [\-s|\-\-short] [\-p|\-\-providers] [\-l|\-\-list] [\-m|\-\-meta] . .SH "OPTIONS" . .TP \-\-help Print this help text . .TP \-\-providers Describe providers in detail for each type . .TP \-\-list List all types . .TP \-\-meta List all metaparameters . .TP \-\-short List only parameters without detail . .SH "EXAMPLE" . .nf $ puppet describe \-\-list $ puppet describe file \-\-providers $ puppet describe user \-s \-m . .fi . .SH "AUTHOR" David Lutterkort . .SH "COPYRIGHT" Copyright (c) 2011 Puppet Inc\., LLC Licensed under the Apache 2\.0 License puppet-5.5.10/man/man8/puppet-doc.80000644005276200011600000000266213417161721016607 0ustar jenkinsjenkins.\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . .TH "PUPPET\-DOC" "8" "January 2019" "Puppet, Inc." "Puppet manual" . .SH "NAME" \fBpuppet\-doc\fR \- Generate Puppet references . .SH "SYNOPSIS" Generates a reference for all Puppet types\. Largely meant for internal Puppet Inc\. use\. (Deprecated) . .SH "USAGE" puppet doc [\-h|\-\-help] [\-l|\-\-list] [\-r|\-\-reference \fIreference\-name\fR] . .SH "DESCRIPTION" This deprecated command generates a Markdown document to stdout describing all installed Puppet types or all allowable arguments to puppet executables\. It is largely meant for internal use and is used to generate the reference document available on the Puppet Inc\. web site\. . .P For Puppet module documentation (and all other use cases) this command has been superseded by the "puppet\-strings" module \- see https://github\.com/puppetlabs/puppetlabs\-strings for more information\. . .P This command (puppet\-doc) will be removed once the puppetlabs internal documentation processing pipeline is completely based on puppet\-strings\. . .SH "OPTIONS" . .TP \-\-help Print this help message . .TP \-\-reference Build a particular reference\. Get a list of references by running \'puppet doc \-\-list\'\. . .SH "EXAMPLE" . .nf $ puppet doc \-r type > /tmp/type_reference\.markdown . .fi . .SH "AUTHOR" Luke Kanies . .SH "COPYRIGHT" Copyright (c) 2011 Puppet Inc\., LLC Licensed under the Apache 2\.0 License puppet-5.5.10/man/man8/puppet-epp.80000644005276200011600000002742013417161721016625 0ustar jenkinsjenkins.\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . .TH "PUPPET\-EPP" "8" "January 2019" "Puppet, Inc." "Puppet manual" . .SH "NAME" \fBpuppet\-epp\fR \- Interact directly with the EPP template parser/renderer\. . .SH "SYNOPSIS" puppet epp \fIaction\fR . .SH "OPTIONS" Note that any setting that\'s valid in the configuration file is also a valid long argument, although it may or may not be relevant to the present action\. For example, \fBserver\fR and \fBrun_mode\fR are valid settings, so you can specify \fB\-\-server \fR, or \fB\-\-run_mode \fR as an argument\. . .P See the configuration file documentation at \fIhttps://puppet\.com/docs/puppet/latest/configuration\.html\fR for the full list of acceptable parameters\. A commented list of all configuration options can also be generated by running puppet with \fB\-\-genconfig\fR\. . .TP \-\-render\-as FORMAT The format in which to render output\. The most common formats are \fBjson\fR, \fBs\fR (string), \fByaml\fR, and \fBconsole\fR, but other options such as \fBdot\fR are sometimes available\. . .TP \-\-verbose Whether to log verbosely\. . .TP \-\-debug Whether to log debug information\. . .SH "ACTIONS" . .IP "\(bu" 4 \fBdump\fR \- Outputs a dump of the internal template parse tree for debugging: \fBSYNOPSIS\fR . .IP puppet epp dump [\-\-e \fIsource\fR] [\-\-[no\-]validate] [\-\-format \fIold, pn, or json\fR] [\-\-pretty] [\-\-[no\-]header] [\-\-format \fIold|pn|json\fR] [\-\-pretty] { \-e \fIsource\fR | [\fItemplates\fR \.\.\.] } . .IP \fBDESCRIPTION\fR . .IP The dump action parses and validates the EPP syntax and dumps the resulting AST model in a human readable (but not necessarily an easy to understand) format\. . .IP The output format can be controlled using the \-\-format \fIold|pn|json\fR where: . .IP "\(bu" 4 \'old\' is the default, but now deprecated format which is not API\. . .IP "\(bu" 4 \'pn\' is the Puppet Extended S\-Expression Notation\. . .IP "\(bu" 4 \'json\' outputs the same graph as \'pn\' but with JSON syntax\. . .IP "" 0 . .IP The output will be "pretty printed" when the option \-\-pretty is given together with \-\-format \'pn\' or \'json\'\. This option has no effect on the \'old\' format\. . .IP The command accepts one or more templates (\.epp) files, or an \-e followed by the template source text\. The given templates can be paths to template files, or references to templates in modules when given on the form \fImodulename\fR/\fItemplate\-name\fR\.epp\. If no arguments are given, the stdin is read (unless it is attached to a terminal) . .IP If multiple templates are given, they are separated with a header indicating the name of the template\. This can be suppressed with the option \-\-no\-header\. The option \-\-[no\-]header has no effect when a single template is dumped\. . .IP When debugging the epp parser itself, it may be useful to suppress the validation step with the \fB\-\-no\-validate\fR option to observe what the parser produced from the given source\. . .IP This command ignores the \-\-render\-as setting/option\. . .IP \fBOPTIONS\fR \fI\-\-e \- Dump one epp source expression given on the command line\. . .IP \fI\-\-format \- Get result in \'old\' (deprecated format), \'pn\' (new format), or \'json\' (new format in JSON)\. . .IP \fI\-\-[no\-]header\fR \- Whether or not to show a file name header between files\. . .IP \fI\-\-pretty\fR \- Pretty print output\. Only applicable together with \-\-format pn or json . .IP \fI\-\-[no\-]validate\fR \- Whether or not to validate the parsed result, if no\-validate only syntax errors are reported\. . .IP \fBRETURNS\fR . .IP A dump of the resulting AST model unless there are syntax or validation errors\. . .IP "\(bu" 4 \fBrender\fR \- Renders an epp template as text: \fBSYNOPSIS\fR . .IP puppet epp render [\-\-node \fInode_name\fR] [\-\-e \fIsource\fR] [\-\-values \fIvalues_hash\fR] [\-\-values_file \fIpp_or_yaml_file\fR] [\-\-facts \fIfacts_file\fR] [\-\-[no\-]header] \-e \fIsource\fR | [\fItemplates\fR \.\.\.] . .IP \fBDESCRIPTION\fR . .IP This action renders one or more EPP templates\. . .IP The command accepts one or more templates (\.epp files), given the same way as templates are given to the puppet \fBepp\fR function (a full path, or a relative reference on the form \'\fImodulename\fR/\fItemplate\-name\fR\.epp\'), or as a relative path\.args In case the given path matches both a modulename/template and a file, the template from the module is used\. . .IP An inline_epp equivalent can also be performed by giving the template after an \-e, or by piping the EPP source text to the command\. . .IP Values to the template can be defined using the Puppet Language on the command line with \fB\-\-values\fR or in a \.pp or \.yaml file referenced with \fB\-\-values_file\fR\. If specifying both the result is merged with \-\-values having higher precedence\. . .IP The \-\-values option allows a Puppet Language sequence of expressions to be defined on the command line the same way as it may be given in a \.pp file referenced with \fB\-\-values_file\fR\. It may set variable values (that become available in the template), and must produce either \fBundef\fR or a \fBHash\fR of values (the hash may be empty)\. Producing \fBundef\fR simulates that the template is called without an arguments hash and thus only references variables in its outer scope\. When a hash is given, a template is limited to seeing only the global scope\. It is thus possible to simulate the different types of calls to the \fBepp\fR and \fBinline_epp\fR functions, with or without a given hash\. Note that if variables are given, they are always available in this simulation \- to test that the template only references variables given as arguments, produce a hash in \-\-values or the \-\-values_file, do not specify any variables that are not global, and turn on \-\-strict_variables setting\. . .IP If multiple templates are given, the same set of values are given to each template\. If both \-\-values and \-\-value_file are used, the \-\-values are merged on top of those given in the file\. . .IP When multiple templates are rendered, a separating header is output between the templates showing the name of the template before the output\. The header output can be turned off with \fB\-\-no\-header\fR\. This also concatenates the template results without any added newline separators\. . .IP Facts from the node where the command is being run are used by default\.args Facts can be obtained for other nodes if they have called in, and reported their facts by using the \fB\-\-node \fR flag\. . .IP Overriding node facts as well as additional facts can be given in a \.yaml or \.json file and referencing it with the \-\-facts option\. (Values can be obtained in yaml format directly from \fBfacter\fR, or from puppet for a given node)\. Note that it is not possible to simulate the reserved variable name \fB$facts\fR in any other way\. . .IP Note that it is not possible to set variables using the Puppet Language that have the same names as facts as this result in an error; "attempt to redefine a variable" since facts are set first\. . .IP Exits with 0 if there were no validation errors\. On errors, no rendered output is produced for that template file\. . .IP When designing EPP templates, it is strongly recommended to define all template arguments in the template, and to give them in a hash when calling \fBepp\fR or \fBinline_epp\fR and to use as few global variables as possible, preferably only the $facts hash\. This makes templates more free standing and are easier to reuse, and to test\. . .IP \fBOPTIONS\fR \fI\-\-e \- Render one inline epp template given on the command line\. . .IP \fI\-\-facts \- A \.yaml or \.json file containing a hash of facts made available in $facts and $trusted . .IP \fI\-\-[no\-]header\fR \- Whether or not to show a file name header between rendered results\. . .IP \fI\-\-node \- The name of the node for which facts are obtained\. Defaults to facts for the local node\. . .IP \fI\-\-values \- A Hash in Puppet DSL form given as arguments to the template being rendered\. . .IP \fI\-\-values_file \- A \.pp or \.yaml file that is processed to produce a hash of values for the template\. . .IP \fBRETURNS\fR . .IP A rendered result of one or more given templates\. . .IP "\(bu" 4 \fBvalidate\fR \- Validate the syntax of one or more EPP templates\.: \fBSYNOPSIS\fR . .IP puppet epp validate [\-\-[no\-]continue_on_error] [\fItemplate\fR] [\fItemplate\fR \.\.\.] . .IP \fBDESCRIPTION\fR . .IP This action validates EPP syntax without producing any output\. . .IP When validating, multiple issues per file are reported up to the settings of max_error, and max_warnings\. The processing stops after having reported issues for the first encountered file with errors unless the option \-\-continue_on_error is given\. . .IP Files can be given using the \fBmodulename/template\.epp\fR style to lookup the template from a module, or be given as a reference to a file\. If the reference to a file can be resolved against a template in a module, the module version wins \- in this case use an absolute path to reference the template file if the module version is not wanted\. . .IP Exits with 0 if there were no validation errors\. . .IP \fBOPTIONS\fR \fI\-\-[no\-]continue_on_error\fR \- Whether or not to continue after errors are reported for a template\. . .IP \fBRETURNS\fR . .IP Nothing, or encountered syntax errors\. . .IP "" 0 . .SH "EXAMPLES" \fBrender\fR . .P Render the template in module \'mymodule\' called \'mytemplate\.epp\', and give it two arguments \fBa\fR and \fBb\fR: . .IP "" 4 . .nf $ puppet epp render mymodule/mytemplate\.epp \-\-values \'{a => 10, b => 20}\' . .fi . .IP "" 0 . .P Render a template using an absolute path: . .IP "" 4 . .nf $ puppet epp render /tmp/testing/mytemplate\.epp \-\-values \'{a => 10, b => 20}\' . .fi . .IP "" 0 . .P Render a template with data from a \.pp file: . .IP "" 4 . .nf $ puppet epp render /tmp/testing/mytemplate\.epp \-\-values_file mydata\.pp . .fi . .IP "" 0 . .P Render a template with data from a \.pp file and override one value on the command line: . .IP "" 4 . .nf $ puppet epp render /tmp/testing/mytemplate\.epp \-\-values_file mydata\.pp \-\-values \'{a=>10}\' . .fi . .IP "" 0 . .P Render from STDIN: . .IP "" 4 . .nf $ cat template\.epp | puppet epp render \-\-values \'{a => 10, b => 20}\' . .fi . .IP "" 0 . .P Set variables in a \.pp file and render a template that uses variable references: . .IP "" 4 . .nf # data\.pp file $greeted = \'a global var\' undef $ puppet epp render \-e \'hello <%= $greeted %>\' \-\-values_file data\.pp . .fi . .IP "" 0 . .P Render a template that outputs a fact: . .IP "" 4 . .nf $ facter \-\-yaml > data\.yaml $ puppet epp render \-e \'<% $facts[osfamily] %>\' \-\-facts data\.yaml . .fi . .IP "" 0 . .P \fBvalidate\fR . .P Validate the template \'template\.epp\' in module \'mymodule\': . .IP "" 4 . .nf $ puppet epp validate mymodule/template\.epp . .fi . .IP "" 0 . .P Validate two arbitrary template files: . .IP "" 4 . .nf $ puppet epp validate mymodule/template1\.epp yourmodule/something\.epp . .fi . .IP "" 0 . .P Validate a template somewhere in the file system: . .IP "" 4 . .nf $ puppet epp validate /tmp/testing/template1\.epp . .fi . .IP "" 0 . .P Validate a template against a file relative to the current directory: . .IP "" 4 . .nf $ puppet epp validate template1\.epp $ puppet epp validate \./template1\.epp . .fi . .IP "" 0 . .P Validate from STDIN: . .IP "" 4 . .nf $ cat template\.epp | puppet epp validate . .fi . .IP "" 0 . .P Continue on error to see errors for all templates: . .IP "" 4 . .nf $ puppet epp validate mymodule/template1\.epp mymodule/template2\.epp \-\-continue_on_error . .fi . .IP "" 0 . .SH "COPYRIGHT AND LICENSE" Copyright 2014 by Puppet Inc\. Apache 2 license; see COPYING puppet-5.5.10/man/man8/puppet-facts.80000644005276200011600000001216713417161721017143 0ustar jenkinsjenkins.\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . .TH "PUPPET\-FACTS" "8" "January 2019" "Puppet, Inc." "Puppet manual" . .SH "NAME" \fBpuppet\-facts\fR \- Retrieve and store facts\. . .SH "SYNOPSIS" puppet facts \fIaction\fR [\-\-terminus _TERMINUS] [\-\-extra HASH] . .SH "DESCRIPTION" This subcommand manages facts, which are collections of normalized system information used by Puppet\. It can read facts directly from the local system (with the default \fBfacter\fR terminus)\. . .SH "OPTIONS" Note that any setting that\'s valid in the configuration file is also a valid long argument, although it may or may not be relevant to the present action\. For example, \fBserver\fR and \fBrun_mode\fR are valid settings, so you can specify \fB\-\-server \fR, or \fB\-\-run_mode \fR as an argument\. . .P See the configuration file documentation at \fIhttps://puppet\.com/docs/puppet/latest/configuration\.html\fR for the full list of acceptable parameters\. A commented list of all configuration options can also be generated by running puppet with \fB\-\-genconfig\fR\. . .TP \-\-render\-as FORMAT The format in which to render output\. The most common formats are \fBjson\fR, \fBs\fR (string), \fByaml\fR, and \fBconsole\fR, but other options such as \fBdot\fR are sometimes available\. . .TP \-\-verbose Whether to log verbosely\. . .TP \-\-debug Whether to log debug information\. . .TP \-\-extra HASH A terminus can take additional arguments to refine the operation, which are passed as an arbitrary hash to the back\-end\. Anything passed as the extra value is just send direct to the back\-end\. . .TP \-\-terminus _TERMINUS Indirector faces expose indirected subsystems of Puppet\. These subsystems are each able to retrieve and alter a specific type of data (with the familiar actions of \fBfind\fR, \fBsearch\fR, \fBsave\fR, and \fBdestroy\fR) from an arbitrary number of pluggable backends\. In Puppet parlance, these backends are called terminuses\. . .IP Almost all indirected subsystems have a \fBrest\fR terminus that interacts with the puppet master\'s data\. Most of them have additional terminuses for various local data models, which are in turn used by the indirected subsystem on the puppet master whenever it receives a remote request\. . .IP The terminus for an action is often determined by context, but occasionally needs to be set explicitly\. See the "Notes" section of this face\'s manpage for more details\. . .SH "ACTIONS" . .TP \fBfind\fR \- Retrieve a node\'s facts\. \fBSYNOPSIS\fR . .IP puppet facts [\-\-terminus _TERMINUS] [\-\-extra HASH] [\fInode_certname\fR] . .IP \fBDESCRIPTION\fR . .IP Retrieve a node\'s facts\. . .IP \fBRETURNS\fR . .IP A hash containing some metadata and (under the "values" key) the set of facts for the requested node\. When used from the Ruby API: A Puppet::Node::Facts object\. . .IP RENDERING ISSUES: Facts cannot currently be rendered as a string; use yaml or json\. . .IP \fBNOTES\fR . .IP When using the \fBfacter\fR terminus, the host argument is ignored\. . .TP \fBinfo\fR \- Print the default terminus class for this face\. \fBSYNOPSIS\fR . .IP puppet facts info [\-\-terminus _TERMINUS] [\-\-extra HASH] . .IP \fBDESCRIPTION\fR . .IP Prints the default terminus class for this subcommand\. Note that different run modes may have different default termini; when in doubt, specify the run mode with the \'\-\-run_mode\' option\. . .TP \fBsave\fR \- API only: create or overwrite an object\. \fBSYNOPSIS\fR . .IP puppet facts save [\-\-terminus _TERMINUS] [\-\-extra HASH] \fIkey\fR . .IP \fBDESCRIPTION\fR . .IP API only: create or overwrite an object\. As the Faces framework does not currently accept data from STDIN, save actions cannot currently be invoked from the command line\. . .TP \fBupload\fR \- Upload local facts to the puppet master\. \fBSYNOPSIS\fR . .IP puppet facts upload [\-\-terminus _TERMINUS] [\-\-extra HASH] . .IP \fBDESCRIPTION\fR . .IP Reads facts from the local system using the \fBfacter\fR terminus, then saves the returned facts using the rest terminus\. . .IP \fBRETURNS\fR . .IP Nothing\. . .IP \fBNOTES\fR . .IP This action requires that the puppet master\'s \fBauth\.conf\fR file allow \fBPUT\fR or \fBsave\fR access to the \fB/puppet/v3/facts\fR API endpoint\. . .IP For details on configuring Puppet Server\'s \fBauth\.conf\fR, see: . .IP \fIhttps://puppet\.com/docs/puppetserver/latest/config_file_auth\.html\fR . .IP For legacy Rack\-based Puppet Masters, see: . .IP \fIhttps://puppet\.com/docs/puppet/latest/config_file_auth\.html\fR . .SH "EXAMPLES" \fBfind\fR . .P Get facts from the local system: . .P $ puppet facts find . .P \fBupload\fR . .P Upload facts: . .P $ puppet facts upload . .SH "NOTES" This subcommand is an indirector face, which exposes \fBfind\fR, \fBsearch\fR, \fBsave\fR, and \fBdestroy\fR actions for an indirected subsystem of Puppet\. Valid termini for this face include: . .IP "\(bu" 4 \fBfacter\fR . .IP "\(bu" 4 \fBmemory\fR . .IP "\(bu" 4 \fBnetwork_device\fR . .IP "\(bu" 4 \fBrest\fR . .IP "\(bu" 4 \fBstore_configs\fR . .IP "\(bu" 4 \fByaml\fR . .IP "" 0 . .SH "COPYRIGHT AND LICENSE" Copyright 2011 by Puppet Inc\. Apache 2 license; see COPYING puppet-5.5.10/man/man8/puppet-filebucket.80000644005276200011600000001256313417161721020160 0ustar jenkinsjenkins.\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . .TH "PUPPET\-FILEBUCKET" "8" "January 2019" "Puppet, Inc." "Puppet manual" . .SH "NAME" \fBpuppet\-filebucket\fR \- Store and retrieve files in a filebucket . .SH "SYNOPSIS" A stand\-alone Puppet filebucket client\. . .SH "USAGE" puppet filebucket \fImode\fR [\-h|\-\-help] [\-V|\-\-version] [\-d|\-\-debug] [\-v|\-\-verbose] [\-l|\-\-local] [\-r|\-\-remote] [\-s|\-\-server \fIserver\fR] [\-f|\-\-fromdate \fIdate\fR] [\-t|\-\-todate \fIdate\fR] [\-b|\-\-bucket \fIdirectory\fR] \fIfile\fR \fIfile\fR \.\.\. . .P Puppet filebucket can operate in three modes, with only one mode per call: . .P backup: Send one or more files to the specified file bucket\. Each sent file is printed with its resulting md5 sum\. . .P get: Return the text associated with an md5 sum\. The text is printed to stdout, and only one file can be retrieved at a time\. . .P restore: Given a file path and an md5 sum, store the content associated with the sum into the specified file path\. You can specify an entirely new path to this argument; you are not restricted to restoring the content to its original location\. . .P diff: Print a diff in unified format between two checksums in the filebucket or between a checksum and its matching file\. . .P list: List all files in the current local filebucket\. Listing remote filebuckets is not allowed\. . .SH "DESCRIPTION" This is a stand\-alone filebucket client for sending files to a local or central filebucket\. . .P Note that \'filebucket\' defaults to using a network\-based filebucket available on the server named \'puppet\'\. To use this, you\'ll have to be running as a user with valid Puppet certificates\. Alternatively, you can use your local file bucket by specifying \'\-\-local\', or by specifying \'\-\-bucket\' with a local path\. . .SH "OPTIONS" Note that any setting that\'s valid in the configuration file is also a valid long argument\. For example, \'ssldir\' is a valid setting, so you can specify \'\-\-ssldir \fIdirectory\fR\' as an argument\. . .P See the configuration file documentation at https://puppet\.com/docs/puppet/latest/configuration\.html for the full list of acceptable parameters\. A commented list of all configuration options can also be generated by running puppet with \'\-\-genconfig\'\. . .TP \-\-bucket Specify a local filebucket path\. This overrides the default path set in \'$clientbucketdir\'\. . .TP \-\-debug Enable full debugging\. . .TP \-\-fromdate (list only) Select bucket files from \'fromdate\'\. . .TP \-\-help Print this help message\. . .TP \-\-local Use the local filebucket\. This uses the default configuration information and the bucket located at the \'$clientbucketdir\' setting by default\. If \'\-\-bucket\' is set, puppet uses that path instead\. . .TP \-\-remote Use a remote filebucket\. This uses the default configuration information and the bucket located at the \'$bucketdir\' setting by default\. . .TP \-\-server The server to send the file to, instead of locally\. . .TP \-\-todate (list only) Select bucket files until \'todate\'\. . .TP \-\-verbose Print extra information\. . .TP \-\-version Print version information\. . .SH "EXAMPLES" . .nf ## Backup a file to the filebucket, then restore it to a temporary directory $ puppet filebucket backup /etc/passwd /etc/passwd: 429b225650b912a2ee067b0a4cf1e949 $ puppet filebucket restore /tmp/passwd 429b225650b912a2ee067b0a4cf1e949 ## Diff between two files in the filebucket $ puppet filebucket \-l diff d43a6ecaa892a1962398ac9170ea9bf2 7ae322f5791217e031dc60188f4521ef 1a2 > again ## Diff between the file in the filebucket and a local file $ puppet filebucket \-l diff d43a6ecaa892a1962398ac9170ea9bf2 /tmp/testFile 1a2 > again ## Backup a file to the filebucket and observe that it keeps each backup separate $ puppet filebucket \-l list d43a6ecaa892a1962398ac9170ea9bf2 2015\-05\-11 09:27:56 /tmp/TestFile $ echo again >> /tmp/TestFile $ puppet filebucket \-l backup /tmp/TestFile /tmp/TestFile: 7ae322f5791217e031dc60188f4521ef $ puppet filebucket \-l list d43a6ecaa892a1962398ac9170ea9bf2 2015\-05\-11 09:27:56 /tmp/TestFile 7ae322f5791217e031dc60188f4521ef 2015\-05\-11 09:52:15 /tmp/TestFile ## List files in a filebucket within date ranges $ puppet filebucket \-l \-f 2015\-01\-01 \-t 2015\-01\-11 list $ puppet filebucket \-l \-f 2015\-05\-10 list d43a6ecaa892a1962398ac9170ea9bf2 2015\-05\-11 09:27:56 /tmp/TestFile 7ae322f5791217e031dc60188f4521ef 2015\-05\-11 09:52:15 /tmp/TestFile $ puppet filebucket \-l \-f "2015\-05\-11 09:30:00" list 7ae322f5791217e031dc60188f4521ef 2015\-05\-11 09:52:15 /tmp/TestFile $ puppet filebucket \-l \-t "2015\-05\-11 09:30:00" list d43a6ecaa892a1962398ac9170ea9bf2 2015\-05\-11 09:27:56 /tmp/TestFile ## Manage files in a specific local filebucket $ puppet filebucket \-b /tmp/TestBucket backup /tmp/TestFile2 /tmp/TestFile2: d41d8cd98f00b204e9800998ecf8427e $ puppet filebucket \-b /tmp/TestBucket list d41d8cd98f00b204e9800998ecf8427e 2015\-05\-11 09:33:22 /tmp/TestFile2 ## From a Puppet master, list files in the master bucketdir $ puppet filebucket \-b $(puppet config print bucketdir \-\-section master) list d43a6ecaa892a1962398ac9170ea9bf2 2015\-05\-11 09:27:56 /tmp/TestFile 7ae322f5791217e031dc60188f4521ef 2015\-05\-11 09:52:15 /tmp/TestFile . .fi . .SH "AUTHOR" Luke Kanies . .SH "COPYRIGHT" Copyright (c) 2011 Puppet Inc\., LLC Licensed under the Apache 2\.0 License puppet-5.5.10/man/man8/puppet-generate.80000644005276200011600000000430313417161721017626 0ustar jenkinsjenkins.\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . .TH "PUPPET\-GENERATE" "8" "January 2019" "Puppet, Inc." "Puppet manual" . .SH "NAME" \fBpuppet\-generate\fR \- Generates Puppet code from Ruby definitions\. . .SH "SYNOPSIS" puppet generate \fIaction\fR . .SH "OPTIONS" Note that any setting that\'s valid in the configuration file is also a valid long argument, although it may or may not be relevant to the present action\. For example, \fBserver\fR and \fBrun_mode\fR are valid settings, so you can specify \fB\-\-server \fR, or \fB\-\-run_mode \fR as an argument\. . .P See the configuration file documentation at \fIhttps://puppet\.com/docs/puppet/latest/configuration\.html\fR for the full list of acceptable parameters\. A commented list of all configuration options can also be generated by running puppet with \fB\-\-genconfig\fR\. . .TP \-\-render\-as FORMAT The format in which to render output\. The most common formats are \fBjson\fR, \fBs\fR (string), \fByaml\fR, and \fBconsole\fR, but other options such as \fBdot\fR are sometimes available\. . .TP \-\-verbose Whether to log verbosely\. . .TP \-\-debug Whether to log debug information\. . .SH "ACTIONS" . .TP \fBtypes\fR \- Generates Puppet code for custom types \fBSYNOPSIS\fR . .IP puppet generate types [\-\-format \fIformat\fR] [\-\-force] . .IP \fBDESCRIPTION\fR . .IP Generates definitions for custom resource types using Puppet code\. . .IP Types defined in Puppet code can be used to isolate custom type definitions between different environments\. . .IP \fBOPTIONS\fR \fI\-\-force\fR \- Forces the generation of output files (skips up\-to\-date checks)\. . .IP \fI\-\-format \- The generation output format to use\. Supported formats: pcore\. . .SH "EXAMPLES" \fBtypes\fR . .P Generate Puppet type definitions for all custom resource types in the current environment: . .IP "" 4 . .nf $ puppet generate types . .fi . .IP "" 0 . .P Generate Puppet type definitions for all custom resource types in the specified environment: . .IP "" 4 . .nf $ puppet generate types \-\-environment development . .fi . .IP "" 0 . .SH "COPYRIGHT AND LICENSE" Copyright 2016 by Puppet Inc\. Apache 2 license; see COPYING puppet-5.5.10/man/man8/puppet-help.80000644005276200011600000000340313417161721016764 0ustar jenkinsjenkins.\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . .TH "PUPPET\-HELP" "8" "January 2019" "Puppet, Inc." "Puppet manual" . .SH "NAME" \fBpuppet\-help\fR \- Display Puppet help\. . .SH "SYNOPSIS" puppet help \fIaction\fR . .SH "OPTIONS" Note that any setting that\'s valid in the configuration file is also a valid long argument, although it may or may not be relevant to the present action\. For example, \fBserver\fR and \fBrun_mode\fR are valid settings, so you can specify \fB\-\-server \fR, or \fB\-\-run_mode \fR as an argument\. . .P See the configuration file documentation at \fIhttps://puppet\.com/docs/puppet/latest/configuration\.html\fR for the full list of acceptable parameters\. A commented list of all configuration options can also be generated by running puppet with \fB\-\-genconfig\fR\. . .TP \-\-render\-as FORMAT The format in which to render output\. The most common formats are \fBjson\fR, \fBs\fR (string), \fByaml\fR, and \fBconsole\fR, but other options such as \fBdot\fR are sometimes available\. . .TP \-\-verbose Whether to log verbosely\. . .TP \-\-debug Whether to log debug information\. . .SH "ACTIONS" . .TP \fBhelp\fR \- Display help about Puppet subcommands and their actions\. \fBSYNOPSIS\fR . .IP puppet help [\-\-version VERSION] [\fIsubcommand\fR] [\fIaction\fR] . .IP \fBDESCRIPTION\fR . .IP Display help about Puppet subcommands and their actions\. . .IP \fBOPTIONS\fR \fI\-\-version VERSION\fR \- The version of the subcommand for which to show help\. . .IP \fBRETURNS\fR . .IP Short help text for the specified subcommand or action\. . .SH "EXAMPLES" \fBhelp\fR . .P Get help for an action: . .P $ puppet help . .SH "COPYRIGHT AND LICENSE" Copyright 2011 by Puppet Inc\. Apache 2 license; see COPYING puppet-5.5.10/man/man8/puppet-lookup.80000644005276200011600000001464613417161721017360 0ustar jenkinsjenkins.\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . .TH "PUPPET\-LOOKUP" "8" "January 2019" "Puppet, Inc." "Puppet manual" . .SH "NAME" \fBpuppet\-lookup\fR \- Interactive Hiera lookup . .SH "SYNOPSIS" Does Hiera lookups from the command line\. . .P Since this command needs access to your Hiera data, make sure to run it on a node that has a copy of that data\. This usually means logging into a Puppet Server node and running \'puppet lookup\' with sudo\. . .P The most common version of this command is: . .P \'puppet lookup \fIKEY\fR \-\-node \fINAME\fR \-\-environment \fIENV\fR \-\-explain\' . .SH "USAGE" puppet lookup [\-\-help] [\-\-type \fITYPESTRING\fR] [\-\-merge first|unique|hash|deep] [\-\-knock\-out\-prefix \fIPREFIX\-STRING\fR] [\-\-sort\-merged\-arrays] [\-\-merge\-hash\-arrays] [\-\-explain] [\-\-environment \fIENV\fR] [\-\-default \fIVALUE\fR] [\-\-node \fINODE\-NAME\fR] [\-\-facts \fIFILE\fR] [\-\-compile] [\-\-render\-as s|json|yaml|binary|msgpack] \fIkeys\fR . .SH "DESCRIPTION" The lookup command is a CLI for Puppet\'s \'lookup()\' function\. It searches your Hiera data and returns a value for the requested lookup key, so you can test and explore your data\. It is a modern replacement for the \'hiera\' command\. . .P Hiera usually relies on a node\'s facts to locate the relevant data sources\. By default, \'puppet lookup\' uses facts from the node you run the command on, but you can get data for any other node with the \'\-\-node \fINAME\fR\' option\. If possible, the lookup command will use the requested node\'s real stored facts from PuppetDB; if PuppetDB isn\'t configured or you want to provide arbitrary fact values, you can pass alternate facts as a JSON or YAML file with \'\-\-facts \fIFILE\fR\'\. . .P If you\'re debugging your Hiera data and want to see where values are coming from, use the \'\-\-explain\' option\. . .P If \'\-\-explain\' isn\'t specified, lookup exits with 0 if a value was found and 1 otherwise\. With \'\-\-explain\', lookup always exits with 0 unless there is a major error\. . .P You can provide multiple lookup keys to this command, but it only returns a value for the first found key, omitting the rest\. . .P For more details about how Hiera works, see the Hiera documentation: https://puppet\.com/docs/puppet/latest/hiera_intro\.html . .SH "OPTIONS" . .IP "\(bu" 4 \-\-help: Print this help message\. . .IP "\(bu" 4 \-\-explain Explain the details of how the lookup was performed and where the final value came from (or the reason no value was found)\. . .IP "\(bu" 4 \-\-node \fINODE\-NAME\fR Specify which node to look up data for; defaults to the node where the command is run\. Since Hiera\'s purpose is to provide different values for different nodes (usually based on their facts), you\'ll usually want to use some specific node\'s facts to explore your data\. If the node where you\'re running this command is configured to talk to PuppetDB, the command will use the requested node\'s most recent facts\. Otherwise, you can override facts with the \'\-\-facts\' option\. . .IP "\(bu" 4 \-\-facts \fIFILE\fR Specify a \.json or \.yaml file of key => value mappings to override the facts for this lookup\. Any facts not specified in this file maintain their original value\. . .IP "\(bu" 4 \-\-environment \fIENV\fR Like with most Puppet commands, you can specify an environment on the command line\. This is important for lookup because different environments can have different Hiera data\. . .IP "\(bu" 4 \-\-merge first|unique|hash|deep: Specify the merge behavior, overriding any merge behavior from the data\'s lookup_options\. \'first\' returns the first value found\. \'unique\' appends everything to a merged, deduplicated array\. \'hash\' performs a simple hash merge by overwriting keys of lower lookup priority\. \'deep\' performs a deep merge on values of Array and Hash type\. There are additional options that can be used with \'deep\'\. . .IP "\(bu" 4 \-\-knock\-out\-prefix \fIPREFIX\-STRING\fR Can be used with the \'deep\' merge strategy\. Specifies a prefix to indicate a value should be removed from the final result\. . .IP "\(bu" 4 \-\-sort\-merged\-arrays Can be used with the \'deep\' merge strategy\. When this flag is used, all merged arrays are sorted\. . .IP "\(bu" 4 \-\-merge\-hash\-arrays Can be used with the \'deep\' merge strategy\. When this flag is used, hashes WITHIN arrays are deep\-merged with their counterparts by position\. . .IP "\(bu" 4 \-\-explain\-options Explain whether a lookup_options hash affects this lookup, and how that hash was assembled\. (lookup_options is how Hiera configures merge behavior in data\.) . .IP "\(bu" 4 \-\-default \fIVALUE\fR A value to return if Hiera can\'t find a value in data\. For emulating calls to the \'lookup()\' function that include a default\. . .IP "\(bu" 4 \-\-type \fITYPESTRING\fR: Assert that the value has the specified type\. For emulating calls to the \'lookup()\' function that include a data type\. . .IP "\(bu" 4 \-\-compile Perform a full catalog compilation prior to the lookup\. If your hierarchy and data only use the $facts, $trusted, and $server_facts variables, you don\'t need this option; however, if your Hiera configuration uses arbitrary variables set by a Puppet manifest, you might need this option to get accurate data\. No catalog compilation takes place unless this flag is given\. . .IP "\(bu" 4 \-\-render\-as s|json|yaml|binary|msgpack Specify the output format of the results; "s" means plain text\. The default when producing a value is yaml and the default when producing an explanation is s\. . .IP "" 0 . .SH "EXAMPLE" To look up \'key_name\' using the Puppet Server node\'s facts: $ puppet lookup key_name . .P To look up \'key_name\' with agent\.local\'s facts: $ puppet lookup \-\-node agent\.local key_name . .P To get the first value found for \'key_name_one\' and \'key_name_two\' with agent\.local\'s facts while merging values and knocking out the prefix \'foo\' while merging: $ puppet lookup \-\-node agent\.local \-\-merge deep \-\-knock\-out\-prefix foo key_name_one key_name_two . .P To lookup \'key_name\' with agent\.local\'s facts, and return a default value of \'bar\' if nothing was found: $ puppet lookup \-\-node agent\.local \-\-default bar key_name . .P To see an explanation of how the value for \'key_name\' would be found, using agent\.local\'s facts: $ puppet lookup \-\-node agent\.local \-\-explain key_name . .SH "COPYRIGHT" Copyright (c) 2015 Puppet Inc\., LLC Licensed under the Apache 2\.0 License puppet-5.5.10/man/man8/puppet-man.80000644005276200011600000000506713417161721016617 0ustar jenkinsjenkins.\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . .TH "PUPPET\-MAN" "8" "January 2019" "Puppet, Inc." "Puppet manual" . .SH "NAME" \fBpuppet\-man\fR \- Display Puppet manual pages\. . .SH "SYNOPSIS" puppet man \fIaction\fR . .SH "DESCRIPTION" Please use the command \'puppet help \fIsubcommand\fR\' or the system manpage system \'man puppet\-\fIsubcommand\fR\' to display information about Puppet subcommands\. The deprecated man subcommand displays manual pages for all Puppet subcommands\. If the \fBronn\fR gem (\fIhttps://github\.com/rtomayko/ronn/\fR) is installed on your system, puppet man will display fully\-formatted man pages\. If \fBronn\fR is not available, puppet man will display the raw (but human\-readable) source text in a pager\. . .SH "OPTIONS" Note that any setting that\'s valid in the configuration file is also a valid long argument, although it may or may not be relevant to the present action\. For example, \fBserver\fR and \fBrun_mode\fR are valid settings, so you can specify \fB\-\-server \fR, or \fB\-\-run_mode \fR as an argument\. . .P See the configuration file documentation at \fIhttps://puppet\.com/docs/puppet/latest/configuration\.html\fR for the full list of acceptable parameters\. A commented list of all configuration options can also be generated by running puppet with \fB\-\-genconfig\fR\. . .TP \-\-render\-as FORMAT The format in which to render output\. The most common formats are \fBjson\fR, \fBs\fR (string), \fByaml\fR, and \fBconsole\fR, but other options such as \fBdot\fR are sometimes available\. . .TP \-\-verbose Whether to log verbosely\. . .TP \-\-debug Whether to log debug information\. . .SH "ACTIONS" . .TP \fBman\fR \- Display the manual page for a Puppet subcommand\. \fBSYNOPSIS\fR . .IP puppet man \fIsubcommand\fR . .IP \fBDESCRIPTION\fR . .IP Display the manual page for a Puppet subcommand\. . .IP \fBRETURNS\fR . .IP The man data, in Markdown format, suitable for consumption by Ronn\. . .IP RENDERING ISSUES: To skip fancy formatting and output the raw Markdown text (e\.g\. for use in a pipeline), call this action with \'\-\-render\-as s\'\. . .SH "EXAMPLES" \fBman\fR . .P View the installed manual page for the subcommand \'config\': . .P $ man puppet\-config . .P (Deprecated) View the manual page for the subcommand \'config\': . .P $ puppet man config . .SH "NOTES" The pager used for display will be the first found of \fB$MANPAGER\fR, \fB$PAGER\fR, \fBless\fR, \fBmost\fR, or \fBmore\fR\. . .SH "COPYRIGHT AND LICENSE" Copyright 2011 by Puppet Inc\. Apache 2 license; see COPYING puppet-5.5.10/man/man8/puppet-parser.80000644005276200011600000001004613417161721017331 0ustar jenkinsjenkins.\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . .TH "PUPPET\-PARSER" "8" "January 2019" "Puppet, Inc." "Puppet manual" . .SH "NAME" \fBpuppet\-parser\fR \- Interact directly with the parser\. . .SH "SYNOPSIS" puppet parser \fIaction\fR . .SH "OPTIONS" Note that any setting that\'s valid in the configuration file is also a valid long argument, although it may or may not be relevant to the present action\. For example, \fBserver\fR and \fBrun_mode\fR are valid settings, so you can specify \fB\-\-server \fR, or \fB\-\-run_mode \fR as an argument\. . .P See the configuration file documentation at \fIhttps://puppet\.com/docs/puppet/latest/configuration\.html\fR for the full list of acceptable parameters\. A commented list of all configuration options can also be generated by running puppet with \fB\-\-genconfig\fR\. . .TP \-\-render\-as FORMAT The format in which to render output\. The most common formats are \fBjson\fR, \fBs\fR (string), \fByaml\fR, and \fBconsole\fR, but other options such as \fBdot\fR are sometimes available\. . .TP \-\-verbose Whether to log verbosely\. . .TP \-\-debug Whether to log debug information\. . .SH "ACTIONS" . .IP "\(bu" 4 \fBdump\fR \- Outputs a dump of the internal parse tree for debugging: \fBSYNOPSIS\fR . .IP puppet parser dump [\-\-e \fIsource\fR] [\-\-[no\-]validate] [\-\-format \fIold, pn, or json\fR] [\-\-pretty] [\-\-format \fIold|pn|json\fR] [\-\-pretty] { \-e \fIsource\fR | [\fItemplates\fR \.\.\.] } . .IP \fBDESCRIPTION\fR . .IP This action parses and validates the Puppet DSL syntax without compiling a catalog or syncing any resources\. . .IP The output format can be controlled using the \-\-format \fIold|pn|json\fR where: . .IP "\(bu" 4 \'old\' is the default, but now deprecated format which is not API\. . .IP "\(bu" 4 \'pn\' is the Puppet Extended S\-Expression Notation\. . .IP "\(bu" 4 \'json\' outputs the same graph as \'pn\' but with JSON syntax\. . .IP "" 0 . .IP The output will be "pretty printed" when the option \-\-pretty is given together with \-\-format \'pn\' or \'json\'\. This option has no effect on the \'old\' format\. . .IP The command accepts one or more manifests (\.pp) files, or an \-e followed by the puppet source text\. If no arguments are given, the stdin is read (unless it is attached to a terminal) . .IP The output format of the dumped tree is intended for debugging purposes and is not API, it may change from time to time\. . .IP \fBOPTIONS\fR \fI\-\-e \- dump one source expression given on the command line\. . .IP \fI\-\-format \- Get result in \'old\' (deprecated format), \'pn\' (new format), or \'json\' (new format in JSON)\. . .IP \fI\-\-pretty\fR \- Pretty print output\. Only applicable together with \-\-format pn or json . .IP \fI\-\-[no\-]validate\fR \- Whether or not to validate the parsed result, if no\-validate only syntax errors are reported . .IP \fBRETURNS\fR . .IP A dump of the resulting AST model unless there are syntax or validation errors\. . .IP "\(bu" 4 \fBvalidate\fR \- Validate the syntax of one or more Puppet manifests\.: \fBSYNOPSIS\fR . .IP puppet parser validate [\fImanifest\fR] [\fImanifest\fR \.\.\.] . .IP \fBDESCRIPTION\fR . .IP This action validates Puppet DSL syntax without compiling a catalog or syncing any resources\. If no manifest files are provided, it will validate the default site manifest\. . .IP When validating multiple issues per file are reported up to the settings of max_error, and max_warnings\. The processing stops after having reported issues for the first encountered file with errors\. . .IP \fBRETURNS\fR . .IP Nothing, or the first syntax error encountered\. . .IP "" 0 . .SH "EXAMPLES" \fBvalidate\fR . .P Validate the default site manifest at /etc/puppetlabs/puppet/manifests/site\.pp: . .P $ puppet parser validate . .P Validate two arbitrary manifest files: . .P $ puppet parser validate init\.pp vhost\.pp . .P Validate from STDIN: . .P $ cat init\.pp | puppet parser validate . .SH "COPYRIGHT AND LICENSE" Copyright 2014 by Puppet Inc\. Apache 2 license; see COPYING puppet-5.5.10/man/man8/puppet-plugin.80000644005276200011600000000462413417161721017340 0ustar jenkinsjenkins.\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . .TH "PUPPET\-PLUGIN" "8" "January 2019" "Puppet, Inc." "Puppet manual" . .SH "NAME" \fBpuppet\-plugin\fR \- Interact with the Puppet plugin system\. . .SH "SYNOPSIS" puppet plugin \fIaction\fR . .SH "DESCRIPTION" This subcommand provides network access to the puppet master\'s store of plugins\. . .P The puppet master serves Ruby code collected from the \fBlib\fR directories of its modules\. These plugins can be used on agent nodes to extend Facter and implement custom types and providers\. Plugins are normally downloaded by puppet agent during the course of a run\. . .SH "OPTIONS" Note that any setting that\'s valid in the configuration file is also a valid long argument, although it may or may not be relevant to the present action\. For example, \fBserver\fR and \fBrun_mode\fR are valid settings, so you can specify \fB\-\-server \fR, or \fB\-\-run_mode \fR as an argument\. . .P See the configuration file documentation at \fIhttps://puppet\.com/docs/puppet/latest/configuration\.html\fR for the full list of acceptable parameters\. A commented list of all configuration options can also be generated by running puppet with \fB\-\-genconfig\fR\. . .TP \-\-render\-as FORMAT The format in which to render output\. The most common formats are \fBjson\fR, \fBs\fR (string), \fByaml\fR, and \fBconsole\fR, but other options such as \fBdot\fR are sometimes available\. . .TP \-\-verbose Whether to log verbosely\. . .TP \-\-debug Whether to log debug information\. . .SH "ACTIONS" . .TP \fBdownload\fR \- Download plugins from the puppet master\. \fBSYNOPSIS\fR . .IP puppet plugin download . .IP \fBDESCRIPTION\fR . .IP Downloads plugins from the configured puppet master\. Any plugins downloaded in this way will be used in all subsequent Puppet activity\. This action modifies files on disk\. . .IP \fBRETURNS\fR . .IP A list of the files downloaded, or a confirmation that no files were downloaded\. When used from the Ruby API, this action returns an array of the files downloaded, which will be empty if none were retrieved\. . .SH "EXAMPLES" \fBdownload\fR . .P Retrieve plugins from the puppet master: . .P $ puppet plugin download . .P Retrieve plugins from the puppet master (API example): . .P $ Puppet::Face[:plugin, \'0\.0\.1\']\.download . .SH "COPYRIGHT AND LICENSE" Copyright 2011 by Puppet Inc\. Apache 2 license; see COPYING puppet-5.5.10/man/man8/puppet-report.80000644005276200011600000001047213417161721017353 0ustar jenkinsjenkins.\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . .TH "PUPPET\-REPORT" "8" "January 2019" "Puppet, Inc." "Puppet manual" . .SH "NAME" \fBpuppet\-report\fR \- Create, display, and submit reports\. . .SH "SYNOPSIS" puppet report \fIaction\fR [\-\-terminus _TERMINUS] [\-\-extra HASH] . .SH "OPTIONS" Note that any setting that\'s valid in the configuration file is also a valid long argument, although it may or may not be relevant to the present action\. For example, \fBserver\fR and \fBrun_mode\fR are valid settings, so you can specify \fB\-\-server \fR, or \fB\-\-run_mode \fR as an argument\. . .P See the configuration file documentation at \fIhttps://puppet\.com/docs/puppet/latest/configuration\.html\fR for the full list of acceptable parameters\. A commented list of all configuration options can also be generated by running puppet with \fB\-\-genconfig\fR\. . .TP \-\-render\-as FORMAT The format in which to render output\. The most common formats are \fBjson\fR, \fBs\fR (string), \fByaml\fR, and \fBconsole\fR, but other options such as \fBdot\fR are sometimes available\. . .TP \-\-verbose Whether to log verbosely\. . .TP \-\-debug Whether to log debug information\. . .TP \-\-extra HASH A terminus can take additional arguments to refine the operation, which are passed as an arbitrary hash to the back\-end\. Anything passed as the extra value is just send direct to the back\-end\. . .TP \-\-terminus _TERMINUS Indirector faces expose indirected subsystems of Puppet\. These subsystems are each able to retrieve and alter a specific type of data (with the familiar actions of \fBfind\fR, \fBsearch\fR, \fBsave\fR, and \fBdestroy\fR) from an arbitrary number of pluggable backends\. In Puppet parlance, these backends are called terminuses\. . .IP Almost all indirected subsystems have a \fBrest\fR terminus that interacts with the puppet master\'s data\. Most of them have additional terminuses for various local data models, which are in turn used by the indirected subsystem on the puppet master whenever it receives a remote request\. . .IP The terminus for an action is often determined by context, but occasionally needs to be set explicitly\. See the "Notes" section of this face\'s manpage for more details\. . .SH "ACTIONS" . .TP \fBinfo\fR \- Print the default terminus class for this face\. \fBSYNOPSIS\fR . .IP puppet report info [\-\-terminus _TERMINUS] [\-\-extra HASH] . .IP \fBDESCRIPTION\fR . .IP Prints the default terminus class for this subcommand\. Note that different run modes may have different default termini; when in doubt, specify the run mode with the \'\-\-run_mode\' option\. . .TP \fBsave\fR \- API only: submit a report\. \fBSYNOPSIS\fR . .IP puppet report save [\-\-terminus _TERMINUS] [\-\-extra HASH] \fIreport\fR . .IP \fBDESCRIPTION\fR . .IP API only: create or overwrite an object\. As the Faces framework does not currently accept data from STDIN, save actions cannot currently be invoked from the command line\. . .IP \fBRETURNS\fR . .IP Nothing\. . .TP \fBsubmit\fR \- API only: submit a report with error handling\. \fBSYNOPSIS\fR . .IP puppet report submit [\-\-terminus _TERMINUS] [\-\-extra HASH] \fIreport\fR . .IP \fBDESCRIPTION\fR . .IP API only: Submits a report to the puppet master\. This action is essentially a shortcut and wrapper for the \fBsave\fR action with the \fBrest\fR terminus, and provides additional details in the event of a failure\. . .SH "EXAMPLES" \fBsave\fR . .P From the implementation of \fBpuppet report submit\fR (API example): . .P begin Puppet::Transaction::Report\.indirection\.terminus_class = :rest Puppet::Face[:report, "0\.0\.1"]\.save(report) Puppet\.notice "Uploaded report for #{report\.name}" rescue => detail Puppet\.log_exception(detail, "Could not send report: #{detail}") end . .P \fBsubmit\fR . .P API example:report = Puppet::Face[:catalog, \'0\.0\.1\']\.apply Puppet::Face[:report, \'0\.0\.1\']\.submit(report) return report . .SH "NOTES" This subcommand is an indirector face, which exposes \fBfind\fR, \fBsearch\fR, \fBsave\fR, and \fBdestroy\fR actions for an indirected subsystem of Puppet\. Valid termini for this face include: . .IP "\(bu" 4 \fBmsgpack\fR . .IP "\(bu" 4 \fBprocessor\fR . .IP "\(bu" 4 \fBrest\fR . .IP "\(bu" 4 \fByaml\fR . .IP "" 0 . .SH "COPYRIGHT AND LICENSE" Copyright 2011 by Puppet Inc\. Apache 2 license; see COPYING puppet-5.5.10/man/man8/puppet-resource.80000644005276200011600000000537513417161721017675 0ustar jenkinsjenkins.\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . .TH "PUPPET\-RESOURCE" "8" "January 2019" "Puppet, Inc." "Puppet manual" . .SH "NAME" \fBpuppet\-resource\fR \- The resource abstraction layer shell . .SH "SYNOPSIS" Uses the Puppet RAL to directly interact with the system\. . .SH "USAGE" puppet resource [\-h|\-\-help] [\-d|\-\-debug] [\-v|\-\-verbose] [\-e|\-\-edit] [\-p|\-\-param \fIparameter\fR] [\-t|\-\-types] [\-y|\-\-to_yaml] \fItype\fR [\fIname\fR] [\fIattribute\fR=\fIvalue\fR \.\.\.] . .SH "DESCRIPTION" This command provides simple facilities for converting current system state into Puppet code, along with some ability to modify the current state using Puppet\'s RAL\. . .P By default, you must at least provide a type to list, in which case puppet resource will tell you everything it knows about all resources of that type\. You can optionally specify an instance name, and puppet resource will only describe that single instance\. . .P If given a type, a name, and a series of \fIattribute\fR=\fIvalue\fR pairs, puppet resource will modify the state of the specified resource\. Alternately, if given a type, a name, and the \'\-\-edit\' flag, puppet resource will write its output to a file, open that file in an editor, and then apply the saved file as a Puppet transaction\. . .SH "OPTIONS" Note that any setting that\'s valid in the configuration file is also a valid long argument\. For example, \'ssldir\' is a valid setting, so you can specify \'\-\-ssldir \fIdirectory\fR\' as an argument\. . .P See the configuration file documentation at https://puppet\.com/docs/puppet/latest/configuration\.html for the full list of acceptable parameters\. A commented list of all configuration options can also be generated by running puppet with \'\-\-genconfig\'\. . .TP \-\-debug Enable full debugging\. . .TP \-\-edit Write the results of the query to a file, open the file in an editor, and read the file back in as an executable Puppet manifest\. . .TP \-\-help Print this help message\. . .TP \-\-param Add more parameters to be outputted from queries\. . .TP \-\-types List all available types\. . .TP \-\-verbose Print extra information\. . .TP \-\-to_yaml Output found resources in yaml format, suitable to use with Hiera and create_resources\. . .SH "EXAMPLE" This example uses \fBpuppet resource\fR to return a Puppet configuration for the user \fBluke\fR: . .IP "" 4 . .nf $ puppet resource user luke user { \'luke\': home => \'/home/luke\', uid => \'100\', ensure => \'present\', comment => \'Luke Kanies,,,\', gid => \'1000\', shell => \'/bin/bash\', groups => [\'sysadmin\',\'audio\',\'video\',\'puppet\'] } . .fi . .IP "" 0 . .SH "AUTHOR" Luke Kanies . .SH "COPYRIGHT" Copyright (c) 2011 Puppet Inc\., LLC Licensed under the Apache 2\.0 License puppet-5.5.10/man/man8/puppet-status.80000644005276200011600000001034713417161721017364 0ustar jenkinsjenkins.\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . .TH "PUPPET\-STATUS" "8" "January 2019" "Puppet, Inc." "Puppet manual" . .SH "NAME" \fBpuppet\-status\fR \- View puppet server status\. . .SH "SYNOPSIS" puppet status \fIaction\fR [\-\-terminus _TERMINUS] [\-\-extra HASH] . .SH "OPTIONS" Note that any setting that\'s valid in the configuration file is also a valid long argument, although it may or may not be relevant to the present action\. For example, \fBserver\fR and \fBrun_mode\fR are valid settings, so you can specify \fB\-\-server \fR, or \fB\-\-run_mode \fR as an argument\. . .P See the configuration file documentation at \fIhttps://puppet\.com/docs/puppet/latest/configuration\.html\fR for the full list of acceptable parameters\. A commented list of all configuration options can also be generated by running puppet with \fB\-\-genconfig\fR\. . .TP \-\-render\-as FORMAT The format in which to render output\. The most common formats are \fBjson\fR, \fBs\fR (string), \fByaml\fR, and \fBconsole\fR, but other options such as \fBdot\fR are sometimes available\. . .TP \-\-verbose Whether to log verbosely\. . .TP \-\-debug Whether to log debug information\. . .TP \-\-extra HASH A terminus can take additional arguments to refine the operation, which are passed as an arbitrary hash to the back\-end\. Anything passed as the extra value is just send direct to the back\-end\. . .TP \-\-terminus _TERMINUS Indirector faces expose indirected subsystems of Puppet\. These subsystems are each able to retrieve and alter a specific type of data (with the familiar actions of \fBfind\fR, \fBsearch\fR, \fBsave\fR, and \fBdestroy\fR) from an arbitrary number of pluggable backends\. In Puppet parlance, these backends are called terminuses\. . .IP Almost all indirected subsystems have a \fBrest\fR terminus that interacts with the puppet master\'s data\. Most of them have additional terminuses for various local data models, which are in turn used by the indirected subsystem on the puppet master whenever it receives a remote request\. . .IP The terminus for an action is often determined by context, but occasionally needs to be set explicitly\. See the "Notes" section of this face\'s manpage for more details\. . .SH "ACTIONS" . .TP \fBfind\fR \- Check status of puppet master server\. \fBSYNOPSIS\fR . .IP puppet status [\-\-terminus _TERMINUS] [\-\-extra HASH] [\fIkey\fR] . .IP \fBDESCRIPTION\fR . .IP Checks whether a Puppet server is properly receiving and processing HTTP requests\. This action is only useful when used with \'\-\-terminus rest\'; when invoked with the \fBlocal\fR terminus, \fBfind\fR will always return true\. . .IP Over REST, this action will query the configured puppet master by default\. To query other servers, including puppet agent nodes started with the \fI\-\-listen\fR option, you can set the global \fI\-\-server\fR and \fI\-\-masterport\fR options on the command line; note that agent nodes listen on port 8139\. . .IP \fBRETURNS\fR . .IP A "true" response or a low\-level connection error\. When used from the Ruby API: returns a Puppet::Status object\. . .IP \fBNOTES\fR . .IP This action requires that the server\'s \fBauth\.conf\fR file allow find access to the \fBstatus\fR REST terminus\. Puppet agent does not use this facility, and it is turned off by default\. See \fIhttps://puppet\.com/docs/puppet/latest/config_file_auth\.html\fR for more details\. . .TP \fBinfo\fR \- Print the default terminus class for this face\. \fBSYNOPSIS\fR . .IP puppet status info [\-\-terminus _TERMINUS] [\-\-extra HASH] . .IP \fBDESCRIPTION\fR . .IP Prints the default terminus class for this subcommand\. Note that different run modes may have different default termini; when in doubt, specify the run mode with the \'\-\-run_mode\' option\. . .SH "EXAMPLES" \fBfind\fR . .P Check the status of the configured puppet master: . .P $ puppet status find \-\-terminus rest . .SH "NOTES" This subcommand is an indirector face, which exposes \fBfind\fR, \fBsearch\fR, \fBsave\fR, and \fBdestroy\fR actions for an indirected subsystem of Puppet\. Valid termini for this face include: . .IP "\(bu" 4 \fBlocal\fR . .IP "\(bu" 4 \fBrest\fR . .IP "" 0 . .SH "COPYRIGHT AND LICENSE" Copyright 2011 by Puppet Inc\. Apache 2 license; see COPYING puppet-5.5.10/man/man8/puppet-agent.80000644005276200011600000002344713417161722017145 0ustar jenkinsjenkins.\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . .TH "PUPPET\-AGENT" "8" "January 2019" "Puppet, Inc." "Puppet manual" . .SH "NAME" \fBpuppet\-agent\fR \- The puppet agent daemon . .SH "SYNOPSIS" Retrieves the client configuration from the puppet master and applies it to the local host\. . .P This service may be run as a daemon, run periodically using cron (or something similar), or run interactively for testing purposes\. . .SH "USAGE" puppet agent [\-\-certname \fINAME\fR] [\-D|\-\-daemonize|\-\-no\-daemonize] [\-d|\-\-debug] [\-\-detailed\-exitcodes] [\-\-digest \fIDIGEST\fR] [\-\-disable [MESSAGE]] [\-\-enable] [\-\-fingerprint] [\-h|\-\-help] [\-l|\-\-logdest syslog|eventlog|\fIABS FILEPATH\fR|console] [\-\-masterport \fIPORT\fR] [\-\-noop] [\-o|\-\-onetime] [\-\-sourceaddress \fIIP_ADDRESS\fR] [\-t|\-\-test] [\-v|\-\-verbose] [\-V|\-\-version] [\-w|\-\-waitforcert \fISECONDS\fR] . .SH "DESCRIPTION" This is the main puppet client\. Its job is to retrieve the local machine\'s configuration from a remote server and apply it\. In order to successfully communicate with the remote server, the client must have a certificate signed by a certificate authority that the server trusts; the recommended method for this, at the moment, is to run a certificate authority as part of the puppet server (which is the default)\. The client will connect and request a signed certificate, and will continue connecting until it receives one\. . .P Once the client has a signed certificate, it will retrieve its configuration and apply it\. . .SH "USAGE NOTES" \'puppet agent\' does its best to find a compromise between interactive use and daemon use\. Run with no arguments and no configuration, it will go into the background, attempt to get a signed certificate, and retrieve and apply its configuration every 30 minutes\. . .P Some flags are meant specifically for interactive use \-\- in particular, \'test\', \'tags\' and \'fingerprint\' are useful\. . .P \'\-\-test\' does a single run in the foreground with verbose logging, then exits\. It will also exit if it can\'t get a valid catalog\. The exit code after running with \'\-\-test\' is 0 if the catalog was successfully applied, and 1 if the run either failed or wasn\'t attempted (due to another run already in progress)\. . .P \'\-\-tags\' allows you to specify what portions of a configuration you want to apply\. Puppet elements are tagged with all of the class or definition names that contain them, and you can use the \'tags\' flag to specify one of these names, causing only configuration elements contained within that class or definition to be applied\. This is very useful when you are testing new configurations \-\- for instance, if you are just starting to manage \'ntpd\', you would put all of the new elements into an \'ntpd\' class, and call puppet with \'\-\-tags ntpd\', which would only apply that small portion of the configuration during your testing, rather than applying the whole thing\. . .P \'\-\-fingerprint\' is a one\-time flag\. In this mode \'puppet agent\' will run once and display on the console (and in the log) the current certificate (or certificate request) fingerprint\. Providing the \'\-\-digest\' option allows to use a different digest algorithm to generate the fingerprint\. The main use is to verify that before signing a certificate request on the master, the certificate request the master received is the same as the one the client sent (to prevent against man\-in\-the\-middle attacks when signing certificates)\. . .SH "OPTIONS" Note that any Puppet setting that\'s valid in the configuration file is also a valid long argument\. For example, \'server\' is a valid setting, so you can specify \'\-\-server \fIservername\fR\' as an argument\. Boolean settings translate into \'\-\-setting\' and \'\-\-no\-setting\' pairs\. . .P See the configuration file documentation at https://puppet\.com/docs/puppet/latest/configuration\.html for the full list of acceptable settings\. A commented list of all settings can also be generated by running puppet agent with \'\-\-genconfig\'\. . .TP \-\-certname Set the certname (unique ID) of the client\. The master reads this unique identifying string, which is usually set to the node\'s fully\-qualified domain name, to determine which configurations the node will receive\. Use this option to debug setup problems or implement unusual node identification schemes\. (This is a Puppet setting, and can go in puppet\.conf\.) . .TP \-\-daemonize Send the process into the background\. This is the default\. (This is a Puppet setting, and can go in puppet\.conf\. Note the special \'no\-\' prefix for boolean settings on the command line\.) . .TP \-\-no\-daemonize Do not send the process into the background\. (This is a Puppet setting, and can go in puppet\.conf\. Note the special \'no\-\' prefix for boolean settings on the command line\.) . .TP \-\-debug Enable full debugging\. . .TP \-\-detailed\-exitcodes Provide extra information about the run via exit codes; only works if \'\-\-test\' or \'\-\-onetime\' is also specified\. If enabled, \'puppet agent\' will use the following exit codes: . .IP 0: The run succeeded with no changes or failures; the system was already in the desired state\. . .IP 1: The run failed, or wasn\'t attempted due to another run already in progress\. . .IP 2: The run succeeded, and some resources were changed\. . .IP 4: The run succeeded, and some resources failed\. . .IP 6: The run succeeded, and included both changes and failures\. . .TP \-\-digest Change the certificate fingerprinting digest algorithm\. The default is SHA256\. Valid values depends on the version of OpenSSL installed, but will likely contain MD5, MD2, SHA1 and SHA256\. . .TP \-\-disable Disable working on the local system\. This puts a lock file in place, causing \'puppet agent\' not to work on the system until the lock file is removed\. This is useful if you are testing a configuration and do not want the central configuration to override the local state until everything is tested and committed\. . .IP Disable can also take an optional message that will be reported by the \'puppet agent\' at the next disabled run\. . .IP \'puppet agent\' uses the same lock file while it is running, so no more than one \'puppet agent\' process is working at a time\. . .IP \'puppet agent\' exits after executing this\. . .TP \-\-enable Enable working on the local system\. This removes any lock file, causing \'puppet agent\' to start managing the local system again (although it will continue to use its normal scheduling, so it might not start for another half hour)\. . .IP \'puppet agent\' exits after executing this\. . .TP \-\-fingerprint Display the current certificate or certificate signing request fingerprint and then exit\. Use the \'\-\-digest\' option to change the digest algorithm used\. . .TP \-\-help Print this help message . .TP \-\-job\-id Attach the specified job id to the catalog request and the report used for this agent run\. This option only works when \'\-\-onetime\' is used\. . .TP \-\-logdest Where to send log messages\. Choose between \'syslog\' (the POSIX syslog service), \'eventlog\' (the Windows Event Log), \'console\', or the path to a log file\. If debugging or verbosity is enabled, this defaults to \'console\'\. Otherwise, it defaults to \'syslog\' on POSIX systems and \'eventlog\' on Windows\. . .IP A path ending with \'\.json\' will receive structured output in JSON format\. The log file will not have an ending \']\' automatically written to it due to the appending nature of logging\. It must be appended manually to make the content valid JSON\. . .TP \-\-masterport The port on which to contact the puppet master\. (This is a Puppet setting, and can go in puppet\.conf\.) . .TP \-\-noop Use \'noop\' mode where the daemon runs in a no\-op or dry\-run mode\. This is useful for seeing what changes Puppet will make without actually executing the changes\. (This is a Puppet setting, and can go in puppet\.conf\. Note the special \'no\-\' prefix for boolean settings on the command line\.) . .TP \-\-onetime Run the configuration once\. Runs a single (normally daemonized) Puppet run\. Useful for interactively running puppet agent when used in conjunction with the \-\-no\-daemonize option\. (This is a Puppet setting, and can go in puppet\.conf\. Note the special \'no\-\' prefix for boolean settings on the command line\.) . .TP \-\-sourceaddress Set the source IP address for transactions\. This defaults to automatically selected\. (This is a Puppet setting, and can go in puppet\.conf\.) . .TP \-\-test Enable the most common options used for testing\. These are \'onetime\', \'verbose\', \'no\-daemonize\', \'no\-usecacheonfailure\', \'detailed\-exitcodes\', \'no\-splay\', and \'show_diff\'\. . .TP \-\-verbose Turn on verbose reporting\. . .TP \-\-version Print the puppet version number and exit\. . .TP \-\-waitforcert This option only matters for daemons that do not yet have certificates and it is enabled by default, with a value of 120 (seconds)\. This causes \'puppet agent\' to connect to the server every 2 minutes and ask it to sign a certificate request\. This is useful for the initial setup of a puppet client\. You can turn off waiting for certificates by specifying a time of 0\. (This is a Puppet setting, and can go in puppet\.conf\. Note the special \'no\-\' prefix for boolean settings on the command line\.) . .SH "EXAMPLE" . .nf $ puppet agent \-\-server puppet\.domain\.com . .fi . .SH "DIAGNOSTICS" Puppet agent accepts the following signals: . .TP SIGHUP Restart the puppet agent daemon\. . .TP SIGINT and SIGTERM Shut down the puppet agent daemon\. . .TP SIGUSR1 Immediately retrieve and apply configurations from the puppet master\. . .TP SIGUSR2 Close file descriptors for log files and reopen them\. Used with logrotate\. . .SH "AUTHOR" Luke Kanies . .SH "COPYRIGHT" Copyright (c) 2011 Puppet Inc\., LLC Licensed under the Apache 2\.0 License puppet-5.5.10/man/man8/puppet-apply.80000644005276200011600000000776413417161722017200 0ustar jenkinsjenkins.\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . .TH "PUPPET\-APPLY" "8" "January 2019" "Puppet, Inc." "Puppet manual" . .SH "NAME" \fBpuppet\-apply\fR \- Apply Puppet manifests locally . .SH "SYNOPSIS" Applies a standalone Puppet manifest to the local system\. . .SH "USAGE" puppet apply [\-h|\-\-help] [\-V|\-\-version] [\-d|\-\-debug] [\-v|\-\-verbose] [\-e|\-\-execute] [\-\-detailed\-exitcodes] [\-L|\-\-loadclasses] [\-l|\-\-logdest syslog|eventlog|\fIABS FILEPATH\fR|console] [\-\-noop] [\-\-catalog \fIcatalog\fR] [\-\-write\-catalog\-summary] \fIfile\fR . .SH "DESCRIPTION" This is the standalone puppet execution tool; use it to apply individual manifests\. . .P When provided with a modulepath, via command line or config file, puppet apply can effectively mimic the catalog that would be served by puppet master with access to the same modules, although there are some subtle differences\. When combined with scheduling and an automated system for pushing manifests, this can be used to implement a serverless Puppet site\. . .P Most users should use \'puppet agent\' and \'puppet master\' for site\-wide manifests\. . .SH "OPTIONS" Note that any setting that\'s valid in the configuration file is also a valid long argument\. For example, \'tags\' is a valid setting, so you can specify \'\-\-tags \fIclass\fR,\fItag\fR\' as an argument\. . .P See the configuration file documentation at https://puppet\.com/docs/puppet/latest/configuration\.html for the full list of acceptable parameters\. A commented list of all configuration options can also be generated by running puppet with \'\-\-genconfig\'\. . .IP "\(bu" 4 \-\-debug: Enable full debugging\. . .IP "\(bu" 4 \-\-detailed\-exitcodes: Provide extra information about the run via exit codes\. If enabled, \'puppet apply\' will use the following exit codes: . .IP 0: The run succeeded with no changes or failures; the system was already in the desired state\. . .IP 1: The run failed\. . .IP 2: The run succeeded, and some resources were changed\. . .IP 4: The run succeeded, and some resources failed\. . .IP 6: The run succeeded, and included both changes and failures\. . .IP "\(bu" 4 \-\-help: Print this help message . .IP "\(bu" 4 \-\-loadclasses: Load any stored classes\. \'puppet agent\' caches configured classes (usually at /etc/puppetlabs/puppet/classes\.txt), and setting this option causes all of those classes to be set in your puppet manifest\. . .IP "\(bu" 4 \-\-logdest: Where to send log messages\. Choose between \'syslog\' (the POSIX syslog service), \'eventlog\' (the Windows Event Log), \'console\', or the path to a log file\. Defaults to \'console\'\. . .IP A path ending with \'\.json\' will receive structured output in JSON format\. The log file will not have an ending \']\' automatically written to it due to the appending nature of logging\. It must be appended manually to make the content valid JSON\. . .IP "\(bu" 4 \-\-noop: Use \'noop\' mode where Puppet runs in a no\-op or dry\-run mode\. This is useful for seeing what changes Puppet will make without actually executing the changes\. . .IP "\(bu" 4 \-\-execute: Execute a specific piece of Puppet code . .IP "\(bu" 4 \-\-test: Enable the most common options used for testing\. These are \'verbose\', \'detailed\-exitcodes\' and \'show_diff\'\. . .IP "\(bu" 4 \-\-verbose: Print extra information\. . .IP "\(bu" 4 \-\-catalog: Apply a JSON catalog (such as one generated with \'puppet master \-\-compile\')\. You can either specify a JSON file or pipe in JSON from standard input\. . .IP "\(bu" 4 \-\-write\-catalog\-summary After compiling the catalog saves the resource list and classes list to the node in the state directory named classes\.txt and resources\.txt . .IP "" 0 . .SH "EXAMPLE" . .nf $ puppet apply \-l /tmp/manifest\.log manifest\.pp $ puppet apply \-\-modulepath=/root/dev/modules \-e "include ntpd::server" $ puppet apply \-\-catalog catalog\.json . .fi . .SH "AUTHOR" Luke Kanies . .SH "COPYRIGHT" Copyright (c) 2011 Puppet Inc\., LLC Licensed under the Apache 2\.0 License puppet-5.5.10/man/man8/puppet-ca.80000644005276200011600000001535013417161722016424 0ustar jenkinsjenkins.\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . .TH "PUPPET\-CA" "8" "January 2019" "Puppet, Inc." "Puppet manual" . .SH "NAME" \fBpuppet\-ca\fR \- Local Puppet Certificate Authority management\. . .SH "SYNOPSIS" puppet ca \fIaction\fR . .SH "DESCRIPTION" This provides local management of the Puppet Certificate Authority\. . .P You can use this subcommand to sign outstanding certificate requests, list and manage local certificates, and inspect the state of the CA\. . .SH "OPTIONS" Note that any setting that\'s valid in the configuration file is also a valid long argument, although it may or may not be relevant to the present action\. For example, \fBserver\fR and \fBrun_mode\fR are valid settings, so you can specify \fB\-\-server \fR, or \fB\-\-run_mode \fR as an argument\. . .P See the configuration file documentation at \fIhttps://puppet\.com/docs/puppet/latest/configuration\.html\fR for the full list of acceptable parameters\. A commented list of all configuration options can also be generated by running puppet with \fB\-\-genconfig\fR\. . .TP \-\-render\-as FORMAT The format in which to render output\. The most common formats are \fBjson\fR, \fBs\fR (string), \fByaml\fR, and \fBconsole\fR, but other options such as \fBdot\fR are sometimes available\. . .TP \-\-verbose Whether to log verbosely\. . .TP \-\-debug Whether to log debug information\. . .SH "ACTIONS" . .IP "\(bu" 4 \fBdestroy\fR \- Destroy named certificate or pending certificate request\.: \fBSYNOPSIS\fR . .IP puppet ca destroy . .IP \fBDESCRIPTION\fR . .IP Destroy named certificate or pending certificate request\. . .IP "\(bu" 4 \fBfingerprint\fR \- Print the DIGEST (defaults to the signing algorithm) fingerprint of a host\'s certificate\.: \fBSYNOPSIS\fR . .IP puppet ca fingerprint [\-\-digest ALGORITHM] . .IP \fBDESCRIPTION\fR . .IP Print the DIGEST (defaults to the signing algorithm) fingerprint of a host\'s certificate\. . .IP \fBOPTIONS\fR \fI\-\-digest ALGORITHM\fR \- The hash algorithm to use when displaying the fingerprint . .IP "\(bu" 4 \fBgenerate\fR \- Generate a certificate for a named client\.: \fBSYNOPSIS\fR . .IP puppet ca generate [\-\-dns\-alt\-names NAMES] . .IP \fBDESCRIPTION\fR . .IP Generate a certificate for a named client\. . .IP \fBOPTIONS\fR \fI\-\-dns\-alt\-names NAMES\fR \- A comma\-separated list of alternate DNS names for Puppet Server\. These are extra hostnames (in addition to its \fBcertname\fR) that the server is allowed to use when serving agents\. Puppet checks this setting when automatically requesting a certificate for Puppet agent or Puppet Server, and when manually generating a certificate with \fBpuppet cert generate\fR\. These can be either IP or DNS, and the type should be specified and followed with a colon\. Untyped inputs will default to DNS\. . .IP In order to handle agent requests at a given hostname (like "puppet\.example\.com"), Puppet Server needs a certificate that proves it\'s allowed to use that name; if a server shows a certificate that doesn\'t include its hostname, Puppet agents will refuse to trust it\. If you use a single hostname for Puppet traffic but load\-balance it to multiple Puppet Servers, each of those servers needs to include the official hostname in its list of extra names\. . .IP \fBNote:\fR The list of alternate names is locked in when the server\'s certificate is signed\. If you need to change the list later, you can\'t just change this setting; you also need to: . .IP "\(bu" 4 On the server: Stop Puppet Server\. . .IP "\(bu" 4 On the CA server: Revoke and clean the server\'s old certificate\. (\fBpuppet cert clean \fR) (Note \fBpuppet cert clean\fR is deprecated and will be replaced with \fBpuppetserver ca clean\fR in Puppet 6\.) . .IP "\(bu" 4 On the server: Delete the old certificate (and any old certificate signing requests) from the ssldir \fIhttps://puppet\.com/docs/puppet/latest/dirs_ssldir\.html\fR\. . .IP "\(bu" 4 On the server: Run \fBpuppet agent \-t \-\-ca_server \fR to request a new certificate . .IP "\(bu" 4 On the CA server: Sign the certificate request, explicitly allowing alternate names (\fBpuppet cert sign \-\-allow\-dns\-alt\-names \fR)\. (Note \fBpuppet cert sign\fR is deprecated and will be replaced with \fBpuppetserver ca sign\fR in Puppet 6\.) . .IP "\(bu" 4 On the server: Run \fBpuppet agent \-t \-\-ca_server \fR to retrieve the cert\. . .IP "\(bu" 4 On the server: Start Puppet Server again\. . .IP "" 0 . .IP To see all the alternate names your servers are using, log into your CA server and run \fBpuppet cert list \-a\fR, then check the output for \fB(alt names: \.\.\.)\fR\. Most agent nodes should NOT have alternate names; the only certs that should have them are Puppet Server nodes that you want other agents to trust\. . .IP "\(bu" 4 \fBlist\fR \- List certificates and/or certificate requests\.: \fBSYNOPSIS\fR . .IP puppet ca list [\-\-[no\-]all] [\-\-[no\-]pending] [\-\-[no\-]signed] [\-\-digest ALGORITHM] [\-\-subject PATTERN] . .IP \fBDESCRIPTION\fR . .IP This will list the current certificates and certificate signing requests in the Puppet CA\. You will also get the fingerprint, and any certificate verification failure reported\. . .IP \fBOPTIONS\fR \fI\-\-[no\-]all\fR \- Include all certificates and requests\. . .IP \fI\-\-digest ALGORITHM\fR \- The hash algorithm to use when displaying the fingerprint . .IP \fI\-\-[no\-]pending\fR \- Include pending certificate signing requests\. . .IP \fI\-\-[no\-]signed\fR \- Include signed certificates\. . .IP \fI\-\-subject PATTERN\fR \- Only include certificates or requests where subject matches PATTERN\. . .IP PATTERN is interpreted as a regular expression, allowing complex filtering of the content\. . .IP "\(bu" 4 \fBprint\fR \- Print the full\-text version of a host\'s certificate\.: \fBSYNOPSIS\fR . .IP puppet ca print . .IP \fBDESCRIPTION\fR . .IP Print the full\-text version of a host\'s certificate\. . .IP "\(bu" 4 \fBrevoke\fR \- Add certificate to certificate revocation list\.: \fBSYNOPSIS\fR . .IP puppet ca revoke . .IP \fBDESCRIPTION\fR . .IP Add certificate to certificate revocation list\. . .IP "\(bu" 4 \fBsign\fR \- Sign an outstanding certificate request\.: \fBSYNOPSIS\fR . .IP puppet ca sign [\-\-[no\-]allow\-dns\-alt\-names] . .IP \fBDESCRIPTION\fR . .IP Sign an outstanding certificate request\. . .IP \fBOPTIONS\fR \fI\-\-[no\-]allow\-dns\-alt\-names\fR \- Whether or not to accept DNS alt names in the certificate request . .IP "\(bu" 4 \fBverify\fR \- Verify the named certificate against the local CA certificate\.: \fBSYNOPSIS\fR . .IP puppet ca verify . .IP \fBDESCRIPTION\fR . .IP Verify the named certificate against the local CA certificate\. . .IP "" 0 . .SH "COPYRIGHT AND LICENSE" Copyright 2011 by Puppet Inc\. Apache 2 license; see COPYING puppet-5.5.10/man/man8/puppet-cert.80000644005276200011600000001245413417161722017000 0ustar jenkinsjenkins.\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . .TH "PUPPET\-CERT" "8" "January 2019" "Puppet, Inc." "Puppet manual" . .SH "NAME" \fBpuppet\-cert\fR \- Manage certificates and requests (Deprecated) . .SH "SYNOPSIS" Standalone certificate authority\. Capable of generating certificates, but mostly used for signing certificate requests from puppet clients\. . .SH "USAGE" puppet cert \fIaction\fR [\-h|\-\-help] [\-V|\-\-version] [\-d|\-\-debug] [\-v|\-\-verbose] [\-\-digest \fIdigest\fR] [\fIhost\fR] . .SH "DESCRIPTION" Because the puppet master service defaults to not signing client certificate requests, this script is available for signing outstanding requests\. It can be used to list outstanding requests and then either sign them individually or sign all of them\. . .SH "ACTIONS" Every action except \'list\' and \'generate\' requires a hostname to act on, unless the \'\-\-all\' option is set\. . .P The most important actions for day\-to\-day use are \'list\' and \'sign\'\. . .TP clean Revoke a host\'s certificate (if applicable) and remove all files related to that host from puppet cert\'s storage\. This is useful when rebuilding hosts, since new certificate signing requests will only be honored if puppet cert does not have a copy of a signed certificate for that host\. If \'\-\-all\' is specified then all host certificates, both signed and unsigned, will be removed\. . .TP fingerprint Print the DIGEST (defaults to the signing algorithm) fingerprint of a host\'s certificate\. . .TP generate Generate a certificate for a named client\. A certificate/keypair will be generated for each client named on the command line\. . .TP list List outstanding certificate requests\. If \'\-\-all\' is specified, signed certificates are also listed, prefixed by \'+\', and revoked or invalid certificates are prefixed by \'\-\' (the verification outcome is printed in parenthesis)\. If \'\-\-human\-readable\' or \'\-H\' is specified, certificates are formatted in a way to improve human scan\-ability\. If \'\-\-machine\-readable\' or \'\-m\' is specified, output is formatted concisely for consumption by a script\. . .TP print Print the full\-text version of a host\'s certificate\. . .TP revoke Revoke the certificate of a client\. The certificate can be specified either by its serial number (given as a hexadecimal number prefixed by \'0x\') or by its hostname\. The certificate is revoked by adding it to the Certificate Revocation List given by the \'cacrl\' configuration option\. Note that the puppet master needs to be restarted after revoking certificates\. . .TP sign Sign an outstanding certificate request\. If \'\-\-interactive\' or \'\-i\' is supplied the user will be prompted to confirm that they are signing the correct certificate (recommended)\. If \'\-\-assume\-yes\' or \'\-y\' is supplied the interactive prompt will assume the answer of \'yes\'\. . .TP verify Verify the named certificate against the local CA certificate\. . .TP reinventory Build an inventory of the issued certificates\. This will destroy the current inventory file specified by \'cert_inventory\' and recreate it from the certificates found in the \'certdir\'\. Ensure the puppet master is stopped before running this action\. . .SH "OPTIONS" Note that any setting that\'s valid in the configuration file is also a valid long argument\. For example, \'ssldir\' is a valid setting, so you can specify \'\-\-ssldir \fIdirectory\fR\' as an argument\. . .P See the configuration file documentation at https://puppet\.com/docs/puppet/latest/configuration\.html for the full list of acceptable parameters\. A commented list of all configuration options can also be generated by running puppet cert with \'\-\-genconfig\'\. . .TP \-\-all Operate on all items\. Currently only makes sense with the \'sign\', \'list\', and \'fingerprint\' actions\. . .TP \-\-allow\-dns\-alt\-names Sign a certificate request even if it contains one or more alternate DNS names\. If this option isn\'t specified, \'puppet cert sign\' will ignore any requests that contain alternate names\. . .IP In general, ONLY certs intended for a Puppet master server should include alternate DNS names, since Puppet agent relies on those names for identifying its rightful server\. . .IP You can make Puppet agent request a certificate with alternate names by setting \'dns_alt_names\' in puppet\.conf or specifying \'\-\-dns_alt_names\' on the command line\. The output of \'puppet cert list\' shows any requested alt names for pending certificate requests\. . .TP \-\-allow\-authorization\-extensions Enable the signing of a request with authorization extensions\. Such requests are sensitive because they can be used to write access rules in Puppet Server\. Currently, this is the only means by which such requests can be signed\. . .TP \-\-digest Set the digest for fingerprinting (defaults to the digest used when signing the cert)\. Valid values depends on your openssl and openssl ruby extension version\. . .TP \-\-debug Enable full debugging\. . .TP \-\-help Print this help message . .TP \-\-verbose Enable verbosity\. . .TP \-\-version Print the puppet version number and exit\. . .SH "EXAMPLE" . .nf $ puppet cert list culain\.madstop\.com $ puppet cert sign culain\.madstop\.com . .fi . .SH "AUTHOR" Luke Kanies . .SH "COPYRIGHT" Copyright (c) 2011 Puppet Inc\., LLC Licensed under the Apache 2\.0 License puppet-5.5.10/man/man8/puppet-certificate.80000644005276200011600000002155013417161722020322 0ustar jenkinsjenkins.\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . .TH "PUPPET\-CERTIFICATE" "8" "January 2019" "Puppet, Inc." "Puppet manual" . .SH "NAME" \fBpuppet\-certificate\fR \- Provide access to the CA for certificate management\. . .SH "SYNOPSIS" puppet certificate \fIaction\fR [\-\-terminus _TERMINUS] [\-\-extra HASH] \fI\-\-ca\-location LOCATION\fR . .SH "DESCRIPTION" This subcommand interacts with a local or remote Puppet certificate authority\. Currently, its behavior is not a full superset of \fBpuppet cert\fR; specifically, it is unable to mimic puppet cert\'s "clean" option, and its "generate" action submits a CSR rather than creating a signed certificate\. . .SH "OPTIONS" Note that any setting that\'s valid in the configuration file is also a valid long argument, although it may or may not be relevant to the present action\. For example, \fBserver\fR and \fBrun_mode\fR are valid settings, so you can specify \fB\-\-server \fR, or \fB\-\-run_mode \fR as an argument\. . .P See the configuration file documentation at \fIhttps://puppet\.com/docs/puppet/latest/configuration\.html\fR for the full list of acceptable parameters\. A commented list of all configuration options can also be generated by running puppet with \fB\-\-genconfig\fR\. . .TP \-\-render\-as FORMAT The format in which to render output\. The most common formats are \fBjson\fR, \fBs\fR (string), \fByaml\fR, and \fBconsole\fR, but other options such as \fBdot\fR are sometimes available\. . .TP \-\-verbose Whether to log verbosely\. . .TP \-\-debug Whether to log debug information\. . .TP \-\-ca\-location LOCATION Whether to act on the local certificate authority or one provided by a remote puppet master\. Allowed values are \'local\' and \'remote\.\' . .IP This option is required\. . .TP \-\-extra HASH A terminus can take additional arguments to refine the operation, which are passed as an arbitrary hash to the back\-end\. Anything passed as the extra value is just send direct to the back\-end\. . .TP \-\-terminus _TERMINUS Indirector faces expose indirected subsystems of Puppet\. These subsystems are each able to retrieve and alter a specific type of data (with the familiar actions of \fBfind\fR, \fBsearch\fR, \fBsave\fR, and \fBdestroy\fR) from an arbitrary number of pluggable backends\. In Puppet parlance, these backends are called terminuses\. . .IP Almost all indirected subsystems have a \fBrest\fR terminus that interacts with the puppet master\'s data\. Most of them have additional terminuses for various local data models, which are in turn used by the indirected subsystem on the puppet master whenever it receives a remote request\. . .IP The terminus for an action is often determined by context, but occasionally needs to be set explicitly\. See the "Notes" section of this face\'s manpage for more details\. . .SH "ACTIONS" . .IP "\(bu" 4 \fBdestroy\fR \- Delete a certificate\.: \fBSYNOPSIS\fR . .IP puppet certificate destroy [\-\-terminus _TERMINUS] [\-\-extra HASH] \fI\-\-ca\-location LOCATION\fR \fIhost\fR . .IP \fBDESCRIPTION\fR . .IP Deletes a certificate\. This action currently only works on the local CA\. . .IP \fBRETURNS\fR . .IP Nothing\. . .IP "\(bu" 4 \fBfind\fR \- Retrieve a certificate\.: \fBSYNOPSIS\fR . .IP puppet certificate find [\-\-terminus _TERMINUS] [\-\-extra HASH] \fI\-\-ca\-location LOCATION\fR \fIhost\fR . .IP \fBDESCRIPTION\fR . .IP Retrieve a certificate\. . .IP \fBRETURNS\fR . .IP An x509 SSL certificate\. . .IP Note that this action has a side effect of caching a copy of the certificate in Puppet\'s \fBssldir\fR\. . .IP "\(bu" 4 \fBgenerate\fR \- Generate a new certificate signing request\.: \fBSYNOPSIS\fR . .IP puppet certificate generate [\-\-terminus _TERMINUS] [\-\-extra HASH] \fI\-\-ca\-location LOCATION\fR [\-\-dns\-alt\-names NAMES] \fIhost\fR . .IP \fBDESCRIPTION\fR . .IP Generates and submits a certificate signing request (CSR) for the specified host\. This CSR will then have to be signed by a user with the proper authorization on the certificate authority\. . .IP Puppet agent usually handles CSR submission automatically\. This action is primarily useful for requesting certificates for individual users and external applications\. . .IP \fBOPTIONS\fR \fI\-\-dns\-alt\-names NAMES\fR \- A comma\-separated list of alternate DNS names for Puppet Server\. These are extra hostnames (in addition to its \fBcertname\fR) that the server is allowed to use when serving agents\. Puppet checks this setting when automatically requesting a certificate for Puppet agent or Puppet Server, and when manually generating a certificate with \fBpuppet cert generate\fR\. These can be either IP or DNS, and the type should be specified and followed with a colon\. Untyped inputs will default to DNS\. . .IP In order to handle agent requests at a given hostname (like "puppet\.example\.com"), Puppet Server needs a certificate that proves it\'s allowed to use that name; if a server shows a certificate that doesn\'t include its hostname, Puppet agents will refuse to trust it\. If you use a single hostname for Puppet traffic but load\-balance it to multiple Puppet Servers, each of those servers needs to include the official hostname in its list of extra names\. . .IP \fBNote:\fR The list of alternate names is locked in when the server\'s certificate is signed\. If you need to change the list later, you can\'t just change this setting; you also need to: . .IP "\(bu" 4 On the server: Stop Puppet Server\. . .IP "\(bu" 4 On the CA server: Revoke and clean the server\'s old certificate\. (\fBpuppet cert clean \fR) (Note \fBpuppet cert clean\fR is deprecated and will be replaced with \fBpuppetserver ca clean\fR in Puppet 6\.) . .IP "\(bu" 4 On the server: Delete the old certificate (and any old certificate signing requests) from the ssldir \fIhttps://puppet\.com/docs/puppet/latest/dirs_ssldir\.html\fR\. . .IP "\(bu" 4 On the server: Run \fBpuppet agent \-t \-\-ca_server \fR to request a new certificate . .IP "\(bu" 4 On the CA server: Sign the certificate request, explicitly allowing alternate names (\fBpuppet cert sign \-\-allow\-dns\-alt\-names \fR)\. (Note \fBpuppet cert sign\fR is deprecated and will be replaced with \fBpuppetserver ca sign\fR in Puppet 6\.) . .IP "\(bu" 4 On the server: Run \fBpuppet agent \-t \-\-ca_server \fR to retrieve the cert\. . .IP "\(bu" 4 On the server: Start Puppet Server again\. . .IP "" 0 . .IP To see all the alternate names your servers are using, log into your CA server and run \fBpuppet cert list \-a\fR, then check the output for \fB(alt names: \.\.\.)\fR\. Most agent nodes should NOT have alternate names; the only certs that should have them are Puppet Server nodes that you want other agents to trust\. . .IP \fBRETURNS\fR . .IP Nothing\. . .IP "\(bu" 4 \fBinfo\fR \- Print the default terminus class for this face\.: \fBSYNOPSIS\fR . .IP puppet certificate info [\-\-terminus _TERMINUS] [\-\-extra HASH] \fI\-\-ca\-location LOCATION\fR . .IP \fBDESCRIPTION\fR . .IP Prints the default terminus class for this subcommand\. Note that different run modes may have different default termini; when in doubt, specify the run mode with the \'\-\-run_mode\' option\. . .IP "\(bu" 4 \fBlist\fR \- List all certificate signing requests\.: \fBSYNOPSIS\fR . .IP puppet certificate list [\-\-terminus _TERMINUS] [\-\-extra HASH] \fI\-\-ca\-location LOCATION\fR . .IP \fBDESCRIPTION\fR . .IP List all certificate signing requests\. . .IP \fBRETURNS\fR . .IP An array of #inspect output from CSR objects\. This output is currently messy, but does contain the names of nodes requesting certificates\. This action returns #inspect strings even when used from the Ruby API\. . .IP "\(bu" 4 \fBsign\fR \- Sign a certificate signing request for HOST\.: \fBSYNOPSIS\fR . .IP puppet certificate sign [\-\-terminus _TERMINUS] [\-\-extra HASH] \fI\-\-ca\-location LOCATION\fR [\-\-[no\-]allow\-dns\-alt\-names] \fIhost\fR . .IP \fBDESCRIPTION\fR . .IP Sign a certificate signing request for HOST\. . .IP \fBOPTIONS\fR \fI\-\-[no\-]allow\-dns\-alt\-names\fR \- Whether or not to accept DNS alt names in the certificate request . .IP \fBRETURNS\fR . .IP A string that appears to be (but isn\'t) an x509 certificate\. . .IP "" 0 . .SH "EXAMPLES" \fBgenerate\fR . .P Request a certificate for "somenode" from the site\'s CA: . .P $ puppet certificate generate somenode\.puppetlabs\.lan \-\-ca\-location remote . .P \fBsign\fR . .P Sign somenode\.puppetlabs\.lan\'s certificate: . .P $ puppet certificate sign somenode\.puppetlabs\.lan \-\-ca\-location remote . .SH "NOTES" This subcommand is an indirector face, which exposes \fBfind\fR, \fBsearch\fR, \fBsave\fR, and \fBdestroy\fR actions for an indirected subsystem of Puppet\. Valid termini for this face include: . .IP "\(bu" 4 \fBca\fR . .IP "\(bu" 4 \fBdisabled_ca\fR . .IP "\(bu" 4 \fBfile\fR . .IP "\(bu" 4 \fBrest\fR . .IP "" 0 . .SH "COPYRIGHT AND LICENSE" Copyright 2011 by Puppet Inc\. Apache 2 license; see COPYING puppet-5.5.10/man/man8/puppet-certificate_request.80000644005276200011600000001137413417161722022075 0ustar jenkinsjenkins.\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . .TH "PUPPET\-CERTIFICATE_REQUEST" "8" "January 2019" "Puppet, Inc." "Puppet manual" . .SH "NAME" \fBpuppet\-certificate_request\fR \- Manage certificate requests\. . .SH "SYNOPSIS" puppet certificate_request \fIaction\fR [\-\-terminus _TERMINUS] [\-\-extra HASH] . .SH "DESCRIPTION" This subcommand retrieves and submits certificate signing requests (CSRs)\. . .SH "OPTIONS" Note that any setting that\'s valid in the configuration file is also a valid long argument, although it may or may not be relevant to the present action\. For example, \fBserver\fR and \fBrun_mode\fR are valid settings, so you can specify \fB\-\-server \fR, or \fB\-\-run_mode \fR as an argument\. . .P See the configuration file documentation at \fIhttps://puppet\.com/docs/puppet/latest/configuration\.html\fR for the full list of acceptable parameters\. A commented list of all configuration options can also be generated by running puppet with \fB\-\-genconfig\fR\. . .TP \-\-render\-as FORMAT The format in which to render output\. The most common formats are \fBjson\fR, \fBs\fR (string), \fByaml\fR, and \fBconsole\fR, but other options such as \fBdot\fR are sometimes available\. . .TP \-\-verbose Whether to log verbosely\. . .TP \-\-debug Whether to log debug information\. . .TP \-\-extra HASH A terminus can take additional arguments to refine the operation, which are passed as an arbitrary hash to the back\-end\. Anything passed as the extra value is just send direct to the back\-end\. . .TP \-\-terminus _TERMINUS Indirector faces expose indirected subsystems of Puppet\. These subsystems are each able to retrieve and alter a specific type of data (with the familiar actions of \fBfind\fR, \fBsearch\fR, \fBsave\fR, and \fBdestroy\fR) from an arbitrary number of pluggable backends\. In Puppet parlance, these backends are called terminuses\. . .IP Almost all indirected subsystems have a \fBrest\fR terminus that interacts with the puppet master\'s data\. Most of them have additional terminuses for various local data models, which are in turn used by the indirected subsystem on the puppet master whenever it receives a remote request\. . .IP The terminus for an action is often determined by context, but occasionally needs to be set explicitly\. See the "Notes" section of this face\'s manpage for more details\. . .SH "ACTIONS" . .TP \fBfind\fR \- Retrieve a single CSR\. \fBSYNOPSIS\fR . .IP puppet certificate_request find [\-\-terminus _TERMINUS] [\-\-extra HASH] [\fIhost\fR] . .IP \fBDESCRIPTION\fR . .IP Retrieve a single CSR\. . .IP \fBRETURNS\fR . .IP A single certificate request\. When used from the Ruby API, returns a Puppet::SSL::CertificateRequest object\. . .IP Defaults to the current nodes certname\. . .TP \fBinfo\fR \- Print the default terminus class for this face\. \fBSYNOPSIS\fR . .IP puppet certificate_request info [\-\-terminus _TERMINUS] [\-\-extra HASH] . .IP \fBDESCRIPTION\fR . .IP Prints the default terminus class for this subcommand\. Note that different run modes may have different default termini; when in doubt, specify the run mode with the \'\-\-run_mode\' option\. . .TP \fBsave\fR \- API only: submit a certificate signing request\. \fBSYNOPSIS\fR . .IP puppet certificate_request save [\-\-terminus _TERMINUS] [\-\-extra HASH] \fIx509_CSR\fR . .IP \fBDESCRIPTION\fR . .IP API only: create or overwrite an object\. As the Faces framework does not currently accept data from STDIN, save actions cannot currently be invoked from the command line\. . .TP \fBsearch\fR \- Retrieve all outstanding CSRs\. \fBSYNOPSIS\fR . .IP puppet certificate_request search [\-\-terminus _TERMINUS] [\-\-extra HASH] \fIdummy_text\fR . .IP \fBDESCRIPTION\fR . .IP Retrieve all outstanding CSRs\. . .IP \fBRETURNS\fR . .IP A list of certificate requests\. When used from the Ruby API, returns an array of Puppet::SSL::CertificateRequest objects\. . .IP \fBNOTES\fR . .IP Although this action always returns all CSRs, it requires a dummy search key; this is a known bug\. . .SH "EXAMPLES" \fBfind\fR . .P Retrieve a single CSR from the puppet master\'s CA: . .P $ puppet certificate_request find somenode\.puppetlabs\.lan \-\-terminus rest . .P \fBsearch\fR . .P Retrieve all CSRs from the local CA (similar to \'puppet cert list\'): . .P $ puppet certificate_request search x \-\-terminus ca . .SH "NOTES" This subcommand is an indirector face, which exposes \fBfind\fR, \fBsearch\fR, \fBsave\fR, and \fBdestroy\fR actions for an indirected subsystem of Puppet\. Valid termini for this face include: . .IP "\(bu" 4 \fBca\fR . .IP "\(bu" 4 \fBdisabled_ca\fR . .IP "\(bu" 4 \fBfile\fR . .IP "\(bu" 4 \fBmemory\fR . .IP "\(bu" 4 \fBrest\fR . .IP "" 0 . .SH "COPYRIGHT AND LICENSE" Copyright 2011 by Puppet Inc\. Apache 2 license; see COPYING puppet-5.5.10/man/man8/puppet-certificate_revocation_list.80000644005276200011600000001057613417161722023614 0ustar jenkinsjenkins.\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . .TH "PUPPET\-CERTIFICATE_REVOCATION_LIST" "8" "January 2019" "Puppet, Inc." "Puppet manual" . .SH "NAME" \fBpuppet\-certificate_revocation_list\fR \- Manage the list of revoked certificates\. . .SH "SYNOPSIS" puppet certificate_revocation_list \fIaction\fR [\-\-terminus _TERMINUS] [\-\-extra HASH] . .SH "DESCRIPTION" This subcommand is primarily for retrieving the certificate revocation list from the CA\. . .SH "OPTIONS" Note that any setting that\'s valid in the configuration file is also a valid long argument, although it may or may not be relevant to the present action\. For example, \fBserver\fR and \fBrun_mode\fR are valid settings, so you can specify \fB\-\-server \fR, or \fB\-\-run_mode \fR as an argument\. . .P See the configuration file documentation at \fIhttps://puppet\.com/docs/puppet/latest/configuration\.html\fR for the full list of acceptable parameters\. A commented list of all configuration options can also be generated by running puppet with \fB\-\-genconfig\fR\. . .TP \-\-render\-as FORMAT The format in which to render output\. The most common formats are \fBjson\fR, \fBs\fR (string), \fByaml\fR, and \fBconsole\fR, but other options such as \fBdot\fR are sometimes available\. . .TP \-\-verbose Whether to log verbosely\. . .TP \-\-debug Whether to log debug information\. . .TP \-\-extra HASH A terminus can take additional arguments to refine the operation, which are passed as an arbitrary hash to the back\-end\. Anything passed as the extra value is just send direct to the back\-end\. . .TP \-\-terminus _TERMINUS Indirector faces expose indirected subsystems of Puppet\. These subsystems are each able to retrieve and alter a specific type of data (with the familiar actions of \fBfind\fR, \fBsearch\fR, \fBsave\fR, and \fBdestroy\fR) from an arbitrary number of pluggable backends\. In Puppet parlance, these backends are called terminuses\. . .IP Almost all indirected subsystems have a \fBrest\fR terminus that interacts with the puppet master\'s data\. Most of them have additional terminuses for various local data models, which are in turn used by the indirected subsystem on the puppet master whenever it receives a remote request\. . .IP The terminus for an action is often determined by context, but occasionally needs to be set explicitly\. See the "Notes" section of this face\'s manpage for more details\. . .SH "ACTIONS" . .TP \fBdestroy\fR \- Delete the certificate revocation list\. \fBSYNOPSIS\fR . .IP puppet certificate_revocation_list destroy [\-\-terminus _TERMINUS] [\-\-extra HASH] \fIdummy_text\fR . .IP \fBDESCRIPTION\fR . .IP Deletes the certificate revocation list\. This cannot be done over REST, but it is possible to delete the locally cached copy or the local CA\'s copy of the CRL\. . .IP \fBRETURNS\fR . .IP Nothing\. . .IP \fBNOTES\fR . .IP Although this action always deletes the CRL from the specified terminus, it requires a dummy argument; this is a known bug\. . .TP \fBfind\fR \- Retrieve the certificate revocation list\. \fBSYNOPSIS\fR . .IP puppet certificate_revocation_list find [\-\-terminus _TERMINUS] [\-\-extra HASH] [\fIkey\fR] . .IP \fBDESCRIPTION\fR . .IP Retrieve the certificate revocation list\. . .IP \fBRETURNS\fR . .IP The certificate revocation list\. When used from the Ruby API: returns an OpenSSL::X509::CRL object\. . .IP \fBNOTES\fR . .IP Although this action always returns the CRL from the specified terminus\. . .TP \fBinfo\fR \- Print the default terminus class for this face\. \fBSYNOPSIS\fR . .IP puppet certificate_revocation_list info [\-\-terminus _TERMINUS] [\-\-extra HASH] . .IP \fBDESCRIPTION\fR . .IP Prints the default terminus class for this subcommand\. Note that different run modes may have different default termini; when in doubt, specify the run mode with the \'\-\-run_mode\' option\. . .SH "EXAMPLES" \fBfind\fR . .P Retrieve a copy of the puppet master\'s CRL: . .P $ puppet certificate_revocation_list find \-\-terminus rest . .SH "NOTES" This subcommand is an indirector face, which exposes \fBfind\fR, \fBsearch\fR, \fBsave\fR, and \fBdestroy\fR actions for an indirected subsystem of Puppet\. Valid termini for this face include: . .IP "\(bu" 4 \fBca\fR . .IP "\(bu" 4 \fBdisabled_ca\fR . .IP "\(bu" 4 \fBfile\fR . .IP "\(bu" 4 \fBrest\fR . .IP "" 0 . .SH "COPYRIGHT AND LICENSE" Copyright 2011 by Puppet Inc\. Apache 2 license; see COPYING puppet-5.5.10/man/man8/puppet-config.80000644005276200011600000001052313417161722017303 0ustar jenkinsjenkins.\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . .TH "PUPPET\-CONFIG" "8" "January 2019" "Puppet, Inc." "Puppet manual" . .SH "NAME" \fBpuppet\-config\fR \- Interact with Puppet\'s settings\. . .SH "SYNOPSIS" puppet config \fIaction\fR [\-\-section SECTION_NAME] . .SH "DESCRIPTION" This subcommand can inspect and modify settings from Puppet\'s \'puppet\.conf\' configuration file\. For documentation about individual settings, see https://puppet\.com/docs/puppet/latest/configuration\.html\. . .SH "OPTIONS" Note that any setting that\'s valid in the configuration file is also a valid long argument, although it may or may not be relevant to the present action\. For example, \fBserver\fR and \fBrun_mode\fR are valid settings, so you can specify \fB\-\-server \fR, or \fB\-\-run_mode \fR as an argument\. . .P See the configuration file documentation at \fIhttps://puppet\.com/docs/puppet/latest/configuration\.html\fR for the full list of acceptable parameters\. A commented list of all configuration options can also be generated by running puppet with \fB\-\-genconfig\fR\. . .TP \-\-render\-as FORMAT The format in which to render output\. The most common formats are \fBjson\fR, \fBs\fR (string), \fByaml\fR, and \fBconsole\fR, but other options such as \fBdot\fR are sometimes available\. . .TP \-\-verbose Whether to log verbosely\. . .TP \-\-debug Whether to log debug information\. . .TP \-\-section SECTION_NAME The section of the puppet\.conf configuration file to interact with\. . .IP The three most commonly used sections are \'main\', \'master\', and \'agent\'\. \'Main\' is the default, and is used by all Puppet applications\. Other sections can override \'main\' values for specific applications \-\-\- the \'master\' section affects puppet master and puppet cert, and the \'agent\' section affects puppet agent\. . .IP Less commonly used is the \'user\' section, which affects puppet apply\. Any other section will be treated as the name of a legacy environment (a deprecated feature), and can only include the \'manifest\' and \'modulepath\' settings\. . .SH "ACTIONS" . .TP \fBdelete\fR \- Delete a Puppet setting\. \fBSYNOPSIS\fR . .IP puppet config delete [\-\-section SECTION_NAME] \fIsetting\fR . .IP \fBDESCRIPTION\fR . .IP Deletes a setting from the specified section\. (The default is the section \'main\')\. . .IP \fBNOTES\fR . .IP By default, this action deletes the configuration setting from the \'main\' configuration domain\. Use the \'\-\-section\' flags to delete settings from other configuration domains\. . .TP \fBprint\fR \- Examine Puppet\'s current settings\. \fBSYNOPSIS\fR . .IP puppet config print [\-\-section SECTION_NAME] all | \fIsetting\fR [\fIsetting\fR \.\.\.] . .IP \fBDESCRIPTION\fR . .IP Prints the value of a single setting or a list of settings\. . .IP This action is a replacement interface to the information available with \fBpuppet \-\-configprint\fR\. . .IP \fBNOTES\fR . .IP By default, this action reads the general configuration in the \'main\' section\. Use the \'\-\-section\' and \'\-\-environment\' flags to examine other configuration domains\. . .TP \fBset\fR \- Set Puppet\'s settings\. \fBSYNOPSIS\fR . .IP puppet config set [\-\-section SECTION_NAME] [setting_name] [setting_value] . .IP \fBDESCRIPTION\fR . .IP Updates values in the \fBpuppet\.conf\fR configuration file\. . .IP \fBNOTES\fR . .IP By default, this action manipulates the configuration in the \'main\' section\. Use the \'\-\-section\' flag to manipulate other configuration domains\. . .SH "EXAMPLES" \fBdelete\fR . .P Delete the setting \'setting_name\' from the \'main\' configuration domain: . .P $ puppet config delete setting_name . .P Delete the setting \'setting_name\' from the \'master\' configuration domain: . .P $ puppet config delete setting_name \-\-section master . .P \fBprint\fR . .P Get puppet\'s runfile directory: . .P $ puppet config print rundir . .P Get a list of important directories from the master\'s config: . .P $ puppet config print all \-\-section master | grep \-E "(path|dir)" . .P \fBset\fR . .P Set puppet\'s runfile directory: . .P $ puppet config set rundir /var/run/puppetlabs . .P Set the vardir for only the agent: . .P $ puppet config set vardir /opt/puppetlabs/puppet/cache \-\-section agent . .SH "COPYRIGHT AND LICENSE" Copyright 2011 by Puppet Inc\. Apache 2 license; see COPYING puppet-5.5.10/man/man8/puppet-device.80000644005276200011600000001060613417161722017277 0ustar jenkinsjenkins.\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . .TH "PUPPET\-DEVICE" "8" "January 2019" "Puppet, Inc." "Puppet manual" . .SH "NAME" \fBpuppet\-device\fR \- Manage remote network devices . .SH "SYNOPSIS" Retrieves catalogs from the Puppet master and applies them to remote devices\. . .P This subcommand can be run manually; or periodically using cron, a scheduled task, or a similar tool\. . .SH "USAGE" puppet device [\-h|\-\-help] [\-v|\-\-verbose] [\-d|\-\-debug] [\-l|\-\-logdest syslog|\fIfile\fR|console] [\-\-detailed\-exitcodes] [\-\-deviceconfig \fIfile\fR] [\-w|\-\-waitforcert \fIseconds\fR] [\-\-libdir \fIdirectory\fR] [\-a|\-\-apply \fIfile\fR] [\-f|\-\-facts] [\-r|\-\-resource \fItype\fR [name]] [\-t|\-\-target \fIdevice\fR] [\-\-user=\fIuser\fR] [\-V|\-\-version] . .SH "DESCRIPTION" Devices require a proxy Puppet agent to request certificates, collect facts, retrieve and apply catalogs, and store reports\. . .SH "USAGE NOTES" Devices managed by the puppet\-device subcommand on a Puppet agent are configured in device\.conf, which is located at $confdir/device\.conf by default, and is configurable with the $deviceconfig setting\. . .P The device\.conf file is an INI\-like file, with one section per device: . .P [\fIDEVICE_CERTNAME\fR] type \fITYPE\fR url \fIURL\fR debug . .P The section name specifies the certname of the device\. . .P The values for the type and url properties are specific to each type of device\. . .P The optional debug property specifies transport\-level debugging, and is limited to telnet and ssh transports\. . .P See https://puppet\.com/docs/puppet/latest/config_file_device\.html for details\. . .SH "OPTIONS" Note that any setting that\'s valid in the configuration file is also a valid long argument\. For example, \'server\' is a valid configuration parameter, so you can specify \'\-\-server \fIservername\fR\' as an argument\. . .TP \-\-help, \-h Print this help message . .TP \-\-verbose, \-v Turn on verbose reporting\. . .TP \-\-debug, \-d Enable full debugging\. . .TP \-\-logdest, \-l Where to send log messages\. Choose between \'syslog\' (the POSIX syslog service), \'console\', or the path to a log file\. If debugging or verbosity is enabled, this defaults to \'console\'\. Otherwise, it defaults to \'syslog\'\. . .IP A path ending with \'\.json\' will receive structured output in JSON format\. The log file will not have an ending \']\' automatically written to it due to the appending nature of logging\. It must be appended manually to make the content valid JSON\. . .TP \-\-detailed\-exitcodes Provide transaction information via exit codes\. If this is enabled, an exit code of \'1\' means at least one device had a compile failure, an exit code of \'2\' means at least one device had resource changes, and an exit code of \'4\' means at least one device had resource failures\. Exit codes of \'3\', \'5\', \'6\', or \'7\' means that a bitwise combination of the preceding exit codes happened\. . .TP \-\-deviceconfig Path to the device config file for puppet device\. Default: $confdir/device\.conf . .TP \-\-waitforcert, \-w This option only matters for targets that do not yet have certificates and it is enabled by default, with a value of 120 (seconds)\. This causes +puppet device+ to poll the server every 2 minutes and ask it to sign a certificate request\. This is useful for the initial setup of a target\. You can turn off waiting for certificates by specifying a time of 0\. . .TP \-\-libdir Override the per\-device libdir with a local directory\. Specifying a libdir also disables pluginsync\. This is useful for testing\. . .TP \-\-apply Apply a manifest against a remote target\. Target must be specified\. . .TP \-\-facts Displays the facts of a remote target\. Target must be specified\. . .TP \-\-resource Displays a resource state as Puppet code, roughly equivalent to \fBpuppet resource\fR\. Can be filterd by title\. Requires \-\-target be specified\. . .TP \-\-target Target a specific device/certificate in the device\.conf\. Doing so will perform a device run against only that device/certificate\. . .TP \-\-to_yaml Output found resources in yaml format, suitable to use with Hiera and create_resources\. . .TP \-\-user The user to run as\. . .SH "EXAMPLE" . .nf $ puppet device \-\-target remotehost \-\-verbose . .fi . .SH "AUTHOR" Brice Figureau . .SH "COPYRIGHT" Copyright (c) 2011\-2018 Puppet Inc\., LLC Licensed under the Apache 2\.0 License puppet-5.5.10/man/man8/puppet-key.80000644005276200011600000001036113417161722016626 0ustar jenkinsjenkins.\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . .TH "PUPPET\-KEY" "8" "January 2019" "Puppet, Inc." "Puppet manual" . .SH "NAME" \fBpuppet\-key\fR \- Create, save, and remove certificate keys\. . .SH "SYNOPSIS" puppet key \fIaction\fR [\-\-terminus _TERMINUS] [\-\-extra HASH] . .SH "DESCRIPTION" This subcommand manages certificate private keys\. Keys are created automatically by puppet agent and when certificate requests are generated with \'puppet certificate generate\'; it should not be necessary to use this subcommand directly\. . .SH "OPTIONS" Note that any setting that\'s valid in the configuration file is also a valid long argument, although it may or may not be relevant to the present action\. For example, \fBserver\fR and \fBrun_mode\fR are valid settings, so you can specify \fB\-\-server \fR, or \fB\-\-run_mode \fR as an argument\. . .P See the configuration file documentation at \fIhttps://puppet\.com/docs/puppet/latest/configuration\.html\fR for the full list of acceptable parameters\. A commented list of all configuration options can also be generated by running puppet with \fB\-\-genconfig\fR\. . .TP \-\-render\-as FORMAT The format in which to render output\. The most common formats are \fBjson\fR, \fBs\fR (string), \fByaml\fR, and \fBconsole\fR, but other options such as \fBdot\fR are sometimes available\. . .TP \-\-verbose Whether to log verbosely\. . .TP \-\-debug Whether to log debug information\. . .TP \-\-extra HASH A terminus can take additional arguments to refine the operation, which are passed as an arbitrary hash to the back\-end\. Anything passed as the extra value is just send direct to the back\-end\. . .TP \-\-terminus _TERMINUS Indirector faces expose indirected subsystems of Puppet\. These subsystems are each able to retrieve and alter a specific type of data (with the familiar actions of \fBfind\fR, \fBsearch\fR, \fBsave\fR, and \fBdestroy\fR) from an arbitrary number of pluggable backends\. In Puppet parlance, these backends are called terminuses\. . .IP Almost all indirected subsystems have a \fBrest\fR terminus that interacts with the puppet master\'s data\. Most of them have additional terminuses for various local data models, which are in turn used by the indirected subsystem on the puppet master whenever it receives a remote request\. . .IP The terminus for an action is often determined by context, but occasionally needs to be set explicitly\. See the "Notes" section of this face\'s manpage for more details\. . .SH "ACTIONS" . .TP \fBdestroy\fR \- Delete an object\. \fBSYNOPSIS\fR . .IP puppet key destroy [\-\-terminus _TERMINUS] [\-\-extra HASH] \fIkey\fR . .IP \fBDESCRIPTION\fR . .IP Delete an object\. . .TP \fBfind\fR \- Retrieve an object by name\. \fBSYNOPSIS\fR . .IP puppet key find [\-\-terminus _TERMINUS] [\-\-extra HASH] [\fIkey\fR] . .IP \fBDESCRIPTION\fR . .IP Retrieve an object by name\. . .TP \fBinfo\fR \- Print the default terminus class for this face\. \fBSYNOPSIS\fR . .IP puppet key info [\-\-terminus _TERMINUS] [\-\-extra HASH] . .IP \fBDESCRIPTION\fR . .IP Prints the default terminus class for this subcommand\. Note that different run modes may have different default termini; when in doubt, specify the run mode with the \'\-\-run_mode\' option\. . .TP \fBsave\fR \- API only: create or overwrite an object\. \fBSYNOPSIS\fR . .IP puppet key save [\-\-terminus _TERMINUS] [\-\-extra HASH] \fIkey\fR . .IP \fBDESCRIPTION\fR . .IP API only: create or overwrite an object\. As the Faces framework does not currently accept data from STDIN, save actions cannot currently be invoked from the command line\. . .TP \fBsearch\fR \- Search for an object or retrieve multiple objects\. \fBSYNOPSIS\fR . .IP puppet key search [\-\-terminus _TERMINUS] [\-\-extra HASH] \fIquery\fR . .IP \fBDESCRIPTION\fR . .IP Search for an object or retrieve multiple objects\. . .SH "NOTES" This subcommand is an indirector face, which exposes \fBfind\fR, \fBsearch\fR, \fBsave\fR, and \fBdestroy\fR actions for an indirected subsystem of Puppet\. Valid termini for this face include: . .IP "\(bu" 4 \fBca\fR . .IP "\(bu" 4 \fBdisabled_ca\fR . .IP "\(bu" 4 \fBfile\fR . .IP "\(bu" 4 \fBmemory\fR . .IP "" 0 . .SH "COPYRIGHT AND LICENSE" Copyright 2011 by Puppet Inc\. Apache 2 license; see COPYING puppet-5.5.10/man/man8/puppet-master.80000644005276200011600000000620313417161722017331 0ustar jenkinsjenkins.\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . .TH "PUPPET\-MASTER" "8" "January 2019" "Puppet, Inc." "Puppet manual" . .SH "NAME" \fBpuppet\-master\fR \- The puppet master daemon . .SH "SYNOPSIS" The central puppet server\. Functions as a certificate authority by default\. . .SH "USAGE" puppet master [\-D|\-\-daemonize|\-\-no\-daemonize] [\-d|\-\-debug] [\-h|\-\-help] [\-l|\-\-logdest syslog|\fIFILE\fR|console] [\-v|\-\-verbose] [\-V|\-\-version] [\-\-compile \fINODE\-NAME\fR] . .SH "DESCRIPTION" This command starts an instance of puppet master, running as a daemon and using Ruby\'s built\-in Webrick webserver\. Puppet master can also be managed by other application servers; when this is the case, this executable is not used\. . .SH "OPTIONS" Note that any Puppet setting that\'s valid in the configuration file is also a valid long argument\. For example, \'server\' is a valid setting, so you can specify \'\-\-server \fIservername\fR\' as an argument\. Boolean settings translate into \'\-\-setting\' and \'\-\-no\-setting\' pairs\. . .P See the configuration file documentation at https://puppet\.com/docs/puppet/latest/configuration\.html for the full list of acceptable settings\. A commented list of all settings can also be generated by running puppet master with \'\-\-genconfig\'\. . .TP \-\-daemonize Send the process into the background\. This is the default\. (This is a Puppet setting, and can go in puppet\.conf\. Note the special \'no\-\' prefix for boolean settings on the command line\.) . .TP \-\-no\-daemonize Do not send the process into the background\. (This is a Puppet setting, and can go in puppet\.conf\. Note the special \'no\-\' prefix for boolean settings on the command line\.) . .TP \-\-debug Enable full debugging\. . .TP \-\-help Print this help message\. . .TP \-\-logdest Where to send log messages\. Choose between \'syslog\' (the POSIX syslog service), \'console\', or the path to a log file\. If debugging or verbosity is enabled, this defaults to \'console\'\. Otherwise, it defaults to \'syslog\'\. . .IP A path ending with \'\.json\' will receive structured output in JSON format\. The log file will not have an ending \']\' automatically written to it due to the appending nature of logging\. It must be appended manually to make the content valid JSON\. . .TP \-\-masterport The port on which to listen for traffic\. The default port is 8140\. (This is a Puppet setting, and can go in puppet\.conf\.) . .TP \-\-verbose Enable verbosity\. . .TP \-\-version Print the puppet version number and exit\. . .TP \-\-compile Compile a catalogue and output it in JSON from the puppet master\. Uses facts contained in the $vardir/yaml/ directory to compile the catalog\. . .SH "EXAMPLE" puppet master . .SH "DIAGNOSTICS" When running as a standalone daemon, puppet master accepts the following signals: . .TP SIGHUP Restart the puppet master server\. . .TP SIGINT and SIGTERM Shut down the puppet master server\. . .TP SIGUSR2 Close file descriptors for log files and reopen them\. Used with logrotate\. . .SH "AUTHOR" Luke Kanies . .SH "COPYRIGHT" Copyright (c) 2012 Puppet Inc\., LLC Licensed under the Apache 2\.0 License puppet-5.5.10/man/man8/puppet-module.80000644005276200011600000004273313417161722017333 0ustar jenkinsjenkins.\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . .TH "PUPPET\-MODULE" "8" "January 2019" "Puppet, Inc." "Puppet manual" . .SH "NAME" \fBpuppet\-module\fR \- Creates, installs and searches for modules on the Puppet Forge\. . .SH "SYNOPSIS" puppet module \fIaction\fR [\-\-environment production ] [\-\-modulepath ] . .SH "DESCRIPTION" This subcommand can find, install, and manage modules from the Puppet Forge, a repository of user\-contributed Puppet code\. It can also generate empty modules, and prepare locally developed modules for release on the Forge\. . .SH "OPTIONS" Note that any setting that\'s valid in the configuration file is also a valid long argument, although it may or may not be relevant to the present action\. For example, \fBserver\fR and \fBrun_mode\fR are valid settings, so you can specify \fB\-\-server \fR, or \fB\-\-run_mode \fR as an argument\. . .P See the configuration file documentation at \fIhttps://puppet\.com/docs/puppet/latest/configuration\.html\fR for the full list of acceptable parameters\. A commented list of all configuration options can also be generated by running puppet with \fB\-\-genconfig\fR\. . .TP \-\-render\-as FORMAT The format in which to render output\. The most common formats are \fBjson\fR, \fBs\fR (string), \fByaml\fR, and \fBconsole\fR, but other options such as \fBdot\fR are sometimes available\. . .TP \-\-verbose Whether to log verbosely\. . .TP \-\-debug Whether to log debug information\. . .TP \-\-environment production The environment in which Puppet is running\. For clients, such as \fBpuppet agent\fR, this determines the environment itself, which Puppet uses to find modules and much more\. For servers, such as \fBpuppet master\fR, this provides the default environment for nodes that Puppet knows nothing about\. . .IP When defining an environment in the \fB[agent]\fR section, this refers to the environment that the agent requests from the master\. The environment doesn\'t have to exist on the local filesystem because the agent fetches it from the master\. This definition is used when running \fBpuppet agent\fR\. . .IP When defined in the \fB[user]\fR section, the environment refers to the path that Puppet uses to search for code and modules related to its execution\. This requires the environment to exist locally on the filesystem where puppet is being executed\. Puppet subcommands, including \fBpuppet module\fR and \fBpuppet apply\fR, use this definition\. . .IP Given that the context and effects vary depending on the config section \fIhttps://puppet\.com/docs/puppet/latest/config_file_main\.html#config\-sections\fR in which the \fBenvironment\fR setting is defined, do not set it globally\. . .TP \-\-modulepath The search path for modules, as a list of directories separated by the system path separator character\. (The POSIX path separator is \':\', and the Windows path separator is \';\'\.) . .IP Setting a global value for \fBmodulepath\fR in puppet\.conf is not allowed (but it can be overridden from the commandline)\. Please use directory environments instead\. If you need to use something other than the default modulepath of \fB:$basemodulepath\fR, you can set \fBmodulepath\fR in environment\.conf\. For more info, see \fIhttps://puppet\.com/docs/puppet/latest/environments_about\.html\fR . .SH "ACTIONS" . .TP \fBbuild\fR \- Build a module release package\. \fBSYNOPSIS\fR . .IP puppet module build [\fIpath\fR] . .IP \fBDESCRIPTION\fR . .IP Prepares a local module for release on the Puppet Forge by building a ready\-to\-upload archive file\. Note: Module build uses MD5 checksums, which are prohibited on FIPS enabled systems\. . .IP This action uses the metadata\.json file in the module directory to set metadata used by the Forge\. See \fIhttps://puppet\.com/docs/puppet/latest/modules_publishing\.html\fR for more about writing metadata\.json files\. . .IP After being built, the release archive file can be found in the module\'s \fBpkg\fR directory\. . .IP \fBRETURNS\fR . .IP Pathname object representing the path to the release archive\. . .TP \fBchanges\fR \- Show modified files of an installed module\. \fBSYNOPSIS\fR . .IP puppet module changes \fIpath\fR . .IP \fBDESCRIPTION\fR . .IP Shows any files in a module that have been modified since it was installed\. This action compares the files on disk to the md5 checksums included in the module\'s checksums\.json or, if that is missing, in metadata\.json\. . .IP \fBRETURNS\fR . .IP Array of strings representing paths of modified files\. . .TP \fBgenerate\fR \- Generate boilerplate for a new module\. \fBSYNOPSIS\fR . .IP puppet module generate [\-\-skip\-interview] \fIname\fR . .IP \fBDESCRIPTION\fR . .IP Generates boilerplate for a new module by creating the directory structure and files recommended for the Puppet community\'s best practices\. . .IP A module may need additional directories beyond this boilerplate if it provides plugins, files, or templates\. . .IP \fBOPTIONS\fR \fI\-\-skip\-interview\fR \- Do not attempt to perform a metadata interview\. Primarily useful for automatic execution of \fBpuppet module generate\fR\. . .IP \fBRETURNS\fR . .IP Array of Pathname objects representing paths of generated files\. . .TP \fBinstall\fR \- Install a module from the Puppet Forge or a release archive\. \fBSYNOPSIS\fR . .IP puppet module install [\-\-force | \-f] [\-\-target\-dir DIR | \-i DIR] [\-\-ignore\-dependencies] [\-\-version VER | \-v VER] [\-\-strict\-semver] \fIname\fR . .IP \fBDESCRIPTION\fR . .IP Installs a module from the Puppet Forge or from a release archive file\. Note: Module install uses MD5 checksums, which are prohibited on FIPS enabled systems\. . .IP The specified module will be installed into the directory specified with the \fB\-\-target\-dir\fR option, which defaults to the first directory in the modulepath\. . .IP \fBOPTIONS\fR \fI\-\-force\fR | \fI\-f\fR \- Force overwrite of existing module, if any\. Implies \-\-ignore\-dependencies\. . .IP \fI\-\-ignore\-dependencies\fR \- Do not attempt to install dependencies\. Implied by \-\-force\. . .IP \fI\-\-strict\-semver\fR \- Whether version ranges should exclude pre\-release versions . .IP \fI\-\-target\-dir DIR\fR | \fI\-i DIR\fR \- The directory into which modules are installed; defaults to the first directory in the modulepath\. . .IP Specifying this option will change the installation directory, and will use the existing modulepath when checking for dependencies\. If you wish to check a different set of directories for dependencies, you must also use the \fB\-\-environment\fR or \fB\-\-modulepath\fR options\. . .IP \fI\-\-version VER\fR | \fI\-v VER\fR \- Module version to install; can be an exact version or a requirement string, eg \'>= 1\.0\.3\'\. Defaults to latest version\. . .IP \fBRETURNS\fR . .IP Pathname object representing the path to the installed module\. . .TP \fBlist\fR \- List installed modules \fBSYNOPSIS\fR . .IP puppet module list [\-\-tree] [\-\-strict\-semver] . .IP \fBDESCRIPTION\fR . .IP Lists the installed puppet modules\. By default, this action scans the modulepath from puppet\.conf\'s \fB[main]\fR block; use the \-\-modulepath option to change which directories are scanned\. . .IP The output of this action includes information from the module\'s metadata, including version numbers and unmet module dependencies\. . .IP \fBOPTIONS\fR \fI\-\-strict\-semver\fR \- Whether version ranges should exclude pre\-release versions . .IP \fI\-\-tree\fR \- Whether to show dependencies as a tree view . .IP \fBRETURNS\fR . .IP hash of paths to module objects . .TP \fBsearch\fR \- Search the Puppet Forge for a module\. \fBSYNOPSIS\fR . .IP puppet module search \fIsearch_term\fR . .IP \fBDESCRIPTION\fR . .IP Searches a repository for modules whose names, descriptions, or keywords match the provided search term\. . .IP \fBRETURNS\fR . .IP Array of module metadata hashes . .TP \fBuninstall\fR \- Uninstall a puppet module\. \fBSYNOPSIS\fR . .IP puppet module uninstall [\-\-force | \-f] [\-\-ignore\-changes | \-c] [\-\-version=] [\-\-strict\-semver] \fIname\fR . .IP \fBDESCRIPTION\fR . .IP Uninstalls a puppet module from the modulepath (or a specific target directory)\. Note: Module uninstall uses MD5 checksums, which are prohibited on FIPS enabled systems\. . .IP \fBOPTIONS\fR \fI\-\-force\fR | \fI\-f\fR \- Force the uninstall of an installed module even if there are local changes or the possibility of causing broken dependencies\. . .IP \fI\-\-ignore\-changes\fR | \fI\-c\fR \- Uninstall an installed module even if there are local changes to it\. (Implied by \-\-force\.) . .IP \fI\-\-strict\-semver\fR \- Whether version ranges should exclude pre\-release versions . .IP \fI\-\-version=\fR \- The version of the module to uninstall\. When using this option, a module matching the specified version must be installed or else an error is raised\. . .IP \fBRETURNS\fR . .IP Hash of module objects representing uninstalled modules and related errors\. . .TP \fBupgrade\fR \- Upgrade a puppet module\. \fBSYNOPSIS\fR . .IP puppet module upgrade [\-\-force | \-f] [\-\-ignore\-dependencies] [\-\-ignore\-changes | \-c] [\-\-version=] [\-\-strict\-semver] \fIname\fR . .IP \fBDESCRIPTION\fR . .IP Upgrades a puppet module\. Note: Module upgrade uses MD5 checksums, which are prohibited on FIPS enabled systems\. . .IP \fBOPTIONS\fR \fI\-\-force\fR | \fI\-f\fR \- Force the upgrade of an installed module even if there are local changes or the possibility of causing broken dependencies\. Implies \-\-ignore\-dependencies\. . .IP \fI\-\-ignore\-changes\fR | \fI\-c\fR \- Upgrade an installed module even if there are local changes to it\. (Implied by \-\-force\.) . .IP \fI\-\-ignore\-dependencies\fR \- Do not attempt to install dependencies\. Implied by \-\-force\. . .IP \fI\-\-strict\-semver\fR \- Whether version ranges should exclude pre\-release versions . .IP \fI\-\-version=\fR \- The version of the module to upgrade to\. . .IP \fBRETURNS\fR . .IP Hash . .SH "EXAMPLES" \fBbuild\fR . .P Build a module release: . .P $ puppet module build puppetlabs\-apache notice: Building /Users/kelseyhightower/puppetlabs\-apache for release Module built: /Users/kelseyhightower/puppetlabs\-apache/pkg/puppetlabs\-apache\-0\.0\.1\.tar\.gz . .P Build the module in the current working directory: . .P $ cd /Users/kelseyhightower/puppetlabs\-apache $ puppet module build notice: Building /Users/kelseyhightower/puppetlabs\-apache for release Module built: /Users/kelseyhightower/puppetlabs\-apache/pkg/puppetlabs\-apache\-0\.0\.1\.tar\.gz . .P \fBchanges\fR . .P Show modified files of an installed module: . .P $ puppet module changes /etc/puppetlabs/code/modules/vcsrepo/ warning: 1 files modified lib/puppet/provider/vcsrepo\.rb . .P \fBgenerate\fR . .P Generate a new module in the current directory: . .P $ puppet module generate puppetlabs\-ssh We need to create a metadata\.json file for this module\. Please answer the following questions; if the question is not applicable to this module, feel free to leave it blank\. . .P Puppet uses Semantic Versioning (semver\.org) to version modules\. What version is this module? [0\.1\.0] \-\-> . .P Who wrote this module? [puppetlabs] \-\-> . .P What license does this module code fall under? [Apache\-2\.0] \-\-> . .P How would you describe this module in a single sentence? \-\-> . .P Where is this module\'s source code repository? \-\-> . .P Where can others go to learn more about this module? \-\-> . .P Where can others go to file issues about this module? \-\-> . .P { "name": "puppetlabs\-ssh", "version": "0\.1\.0", "author": "puppetlabs", "summary": null, "license": "Apache\-2\.0", "source": "", "project_page": null, "issues_url": null, "dependencies": [ { "name": "puppetlabs\-stdlib", "version_requirement": ">= 1\.0\.0" } ] . .SH "}" About to generate this metadata; continue? [n/Y] \-\-> . .P Notice: Generating module at /Users/username/Projects/puppet/puppetlabs\-ssh\.\.\. Notice: Populating ERB templates\.\.\. Finished; module generated in puppetlabs\-ssh\. puppetlabs\-ssh/manifests puppetlabs\-ssh/manifests/init\.pp puppetlabs\-ssh/metadata\.json puppetlabs\-ssh/README\.md puppetlabs\-ssh/spec puppetlabs\-ssh/spec/spec_helper\.rb puppetlabs\-ssh/tests puppetlabs\-ssh/tests/init\.pp . .P \fBinstall\fR . .P Install a module: . .P $ puppet module install puppetlabs\-vcsrepo Preparing to install into /etc/puppetlabs/code/modules \.\.\. Downloading from https://forgeapi\.puppet\.com \.\.\. Installing \-\- do not interrupt \.\.\. /etc/puppetlabs/code/modules └── puppetlabs\-vcsrepo (v0\.0\.4) . .P Install a module to a specific environment: . .P $ puppet module install puppetlabs\-vcsrepo \-\-environment development Preparing to install into /etc/puppetlabs/code/environments/development/modules \.\.\. Downloading from https://forgeapi\.puppet\.com \.\.\. Installing \-\- do not interrupt \.\.\. /etc/puppetlabs/code/environments/development/modules └── puppetlabs\-vcsrepo (v0\.0\.4) . .P Install a specific module version: . .P $ puppet module install puppetlabs\-vcsrepo \-v 0\.0\.4 Preparing to install into /etc/puppetlabs/modules \.\.\. Downloading from https://forgeapi\.puppet\.com \.\.\. Installing \-\- do not interrupt \.\.\. /etc/puppetlabs/code/modules └── puppetlabs\-vcsrepo (v0\.0\.4) . .P Install a module into a specific directory: . .P $ puppet module install puppetlabs\-vcsrepo \-\-target\-dir=/opt/puppetlabs/puppet/modules Preparing to install into /opt/puppetlabs/puppet/modules \.\.\. Downloading from https://forgeapi\.puppet\.com \.\.\. Installing \-\- do not interrupt \.\.\. /opt/puppetlabs/puppet/modules └── puppetlabs\-vcsrepo (v0\.0\.4) . .P Install a module into a specific directory and check for dependencies in other directories: . .P $ puppet module install puppetlabs\-vcsrepo \-\-target\-dir=/opt/puppetlabs/puppet/modules \-\-modulepath /etc/puppetlabs/code/modules Preparing to install into /opt/puppetlabs/puppet/modules \.\.\. Downloading from https://forgeapi\.puppet\.com \.\.\. Installing \-\- do not interrupt \.\.\. /opt/puppetlabs/puppet/modules └── puppetlabs\-vcsrepo (v0\.0\.4) . .P Install a module from a release archive: . .P $ puppet module install puppetlabs\-vcsrepo\-0\.0\.4\.tar\.gz Preparing to install into /etc/puppetlabs/code/modules \.\.\. Downloading from https://forgeapi\.puppet\.com \.\.\. Installing \-\- do not interrupt \.\.\. /etc/puppetlabs/code/modules └── puppetlabs\-vcsrepo (v0\.0\.4) . .P Install a module from a release archive and ignore dependencies: . .P $ puppet module install puppetlabs\-vcsrepo\-0\.0\.4\.tar\.gz \-\-ignore\-dependencies Preparing to install into /etc/puppetlabs/code/modules \.\.\. Installing \-\- do not interrupt \.\.\. /etc/puppetlabs/code/modules └── puppetlabs\-vcsrepo (v0\.0\.4) . .P \fBlist\fR . .P List installed modules: . .P $ puppet module list /etc/puppetlabs/code/modules ├── bodepd\-create_resources (v0\.0\.1) ├── puppetlabs\-bacula (v0\.0\.2) ├── puppetlabs\-mysql (v0\.0\.1) ├── puppetlabs\-sqlite (v0\.0\.1) └── puppetlabs\-stdlib (v2\.2\.1) /opt/puppetlabs/puppet/modules (no modules installed) . .P List installed modules in a tree view: . .P $ puppet module list \-\-tree /etc/puppetlabs/code/modules └─┬ puppetlabs\-bacula (v0\.0\.2) ├── puppetlabs\-stdlib (v2\.2\.1) ├─┬ puppetlabs\-mysql (v0\.0\.1) │ └── bodepd\-create_resources (v0\.0\.1) └── puppetlabs\-sqlite (v0\.0\.1) /opt/puppetlabs/puppet/modules (no modules installed) . .P List installed modules from a specified environment: . .P $ puppet module list \-\-environment production /etc/puppetlabs/code/modules ├── bodepd\-create_resources (v0\.0\.1) ├── puppetlabs\-bacula (v0\.0\.2) ├── puppetlabs\-mysql (v0\.0\.1) ├── puppetlabs\-sqlite (v0\.0\.1) └── puppetlabs\-stdlib (v2\.2\.1) /opt/puppetlabs/puppet/modules (no modules installed) . .P List installed modules from a specified modulepath: . .P $ puppet module list \-\-modulepath /opt/puppetlabs/puppet/modules /opt/puppetlabs/puppet/modules (no modules installed) . .P \fBsearch\fR . .P Search the Puppet Forge for a module: . .P $ puppet module search puppetlabs NAME DESCRIPTION AUTHOR KEYWORDS bacula This is a generic Apache module @puppetlabs backups . .P \fBuninstall\fR . .P Uninstall a module: . .P $ puppet module uninstall puppetlabs\-ssh Removed /etc/puppetlabs/code/modules/ssh (v1\.0\.0) . .P Uninstall a module from a specific directory: . .P $ puppet module uninstall puppetlabs\-ssh \-\-modulepath /opt/puppetlabs/puppet/modules Removed /opt/puppetlabs/puppet/modules/ssh (v1\.0\.0) . .P Uninstall a module from a specific environment: . .P $ puppet module uninstall puppetlabs\-ssh \-\-environment development Removed /etc/puppetlabs/code/environments/development/modules/ssh (v1\.0\.0) . .P Uninstall a specific version of a module: . .P $ puppet module uninstall puppetlabs\-ssh \-\-version 2\.0\.0 Removed /etc/puppetlabs/code/modules/ssh (v2\.0\.0) . .P \fBupgrade\fR . .P upgrade an installed module to the latest version . .P $ puppet module upgrade puppetlabs\-apache /etc/puppetlabs/puppet/modules └── puppetlabs\-apache (v1\.0\.0 \-> v2\.4\.0) . .P upgrade an installed module to a specific version . .P $ puppet module upgrade puppetlabs\-apache \-\-version 2\.1\.0 /etc/puppetlabs/puppet/modules └── puppetlabs\-apache (v1\.0\.0 \-> v2\.1\.0) . .P upgrade an installed module for a specific environment . .P $ puppet module upgrade puppetlabs\-apache \-\-environment test /etc/puppetlabs/code/environments/test/modules └── puppetlabs\-apache (v1\.0\.0 \-> v2\.4\.0) . .SH "COPYRIGHT AND LICENSE" Copyright 2012 by Puppet Inc\. Apache 2 license; see COPYING puppet-5.5.10/man/man8/puppet-node.80000644005276200011600000001212013417161722016756 0ustar jenkinsjenkins.\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . .TH "PUPPET\-NODE" "8" "January 2019" "Puppet, Inc." "Puppet manual" . .SH "NAME" \fBpuppet\-node\fR \- View and manage node definitions\. . .SH "SYNOPSIS" puppet node \fIaction\fR [\-\-terminus _TERMINUS] [\-\-extra HASH] . .SH "DESCRIPTION" This subcommand interacts with node objects, which are used by Puppet to build a catalog\. A node object consists of the node\'s facts, environment, node parameters (exposed in the parser as top\-scope variables), and classes\. . .SH "OPTIONS" Note that any setting that\'s valid in the configuration file is also a valid long argument, although it may or may not be relevant to the present action\. For example, \fBserver\fR and \fBrun_mode\fR are valid settings, so you can specify \fB\-\-server \fR, or \fB\-\-run_mode \fR as an argument\. . .P See the configuration file documentation at \fIhttps://puppet\.com/docs/puppet/latest/configuration\.html\fR for the full list of acceptable parameters\. A commented list of all configuration options can also be generated by running puppet with \fB\-\-genconfig\fR\. . .TP \-\-render\-as FORMAT The format in which to render output\. The most common formats are \fBjson\fR, \fBs\fR (string), \fByaml\fR, and \fBconsole\fR, but other options such as \fBdot\fR are sometimes available\. . .TP \-\-verbose Whether to log verbosely\. . .TP \-\-debug Whether to log debug information\. . .TP \-\-extra HASH A terminus can take additional arguments to refine the operation, which are passed as an arbitrary hash to the back\-end\. Anything passed as the extra value is just send direct to the back\-end\. . .TP \-\-terminus _TERMINUS Indirector faces expose indirected subsystems of Puppet\. These subsystems are each able to retrieve and alter a specific type of data (with the familiar actions of \fBfind\fR, \fBsearch\fR, \fBsave\fR, and \fBdestroy\fR) from an arbitrary number of pluggable backends\. In Puppet parlance, these backends are called terminuses\. . .IP Almost all indirected subsystems have a \fBrest\fR terminus that interacts with the puppet master\'s data\. Most of them have additional terminuses for various local data models, which are in turn used by the indirected subsystem on the puppet master whenever it receives a remote request\. . .IP The terminus for an action is often determined by context, but occasionally needs to be set explicitly\. See the "Notes" section of this face\'s manpage for more details\. . .SH "ACTIONS" . .TP \fBclean\fR \- Clean up signed certs, cached facts, node objects, and reports for a node stored by the puppetmaster \fBSYNOPSIS\fR . .IP puppet node clean [\-\-terminus _TERMINUS] [\-\-extra HASH] \fIhost1\fR [\fIhost2\fR \.\.\.] . .IP \fBDESCRIPTION\fR . .IP Cleans up the following information a puppet master knows about a node: . .IP \fISigned certificates\fR \- ($vardir/ssl/ca/signed/node\.domain\.pem) . .IP \fICached facts\fR \- ($vardir/yaml/facts/node\.domain\.yaml) . .IP \fICached node objects\fR \- ($vardir/yaml/node/node\.domain\.yaml) . .IP \fIReports\fR \- ($vardir/reports/node\.domain) . .TP \fBfind\fR \- Retrieve a node object\. \fBSYNOPSIS\fR . .IP puppet node find [\-\-terminus _TERMINUS] [\-\-extra HASH] \fIhost\fR . .IP \fBDESCRIPTION\fR . .IP Retrieve a node object\. . .IP \fBRETURNS\fR . .IP A hash containing the node\'s \fBclasses\fR, \fBenvironment\fR, \fBexpiration\fR, \fBname\fR, \fBparameters\fR (its facts, combined with any ENC\-set parameters), and \fBtime\fR\. When used from the Ruby API: a Puppet::Node object\. . .IP RENDERING ISSUES: Rendering as string and json are currently broken; node objects can only be rendered as yaml\. . .TP \fBinfo\fR \- Print the default terminus class for this face\. \fBSYNOPSIS\fR . .IP puppet node info [\-\-terminus _TERMINUS] [\-\-extra HASH] . .IP \fBDESCRIPTION\fR . .IP Prints the default terminus class for this subcommand\. Note that different run modes may have different default termini; when in doubt, specify the run mode with the \'\-\-run_mode\' option\. . .SH "EXAMPLES" \fBfind\fR . .P Retrieve an "empty" (no classes, no ENC\-imposed parameters, and an environment of "production") node: . .P $ puppet node find somenode\.puppetlabs\.lan \-\-terminus plain \-\-render\-as yaml . .P Retrieve a node using the puppet master\'s configured ENC: . .P $ puppet node find somenode\.puppetlabs\.lan \-\-terminus exec \-\-run_mode master \-\-render\-as yaml . .P Retrieve the same node from the puppet master: . .P $ puppet node find somenode\.puppetlabs\.lan \-\-terminus rest \-\-render\-as yaml . .SH "NOTES" This subcommand is an indirector face, which exposes \fBfind\fR, \fBsearch\fR, \fBsave\fR, and \fBdestroy\fR actions for an indirected subsystem of Puppet\. Valid termini for this face include: . .IP "\(bu" 4 \fBexec\fR . .IP "\(bu" 4 \fBldap\fR . .IP "\(bu" 4 \fBmemory\fR . .IP "\(bu" 4 \fBmsgpack\fR . .IP "\(bu" 4 \fBplain\fR . .IP "\(bu" 4 \fBrest\fR . .IP "\(bu" 4 \fBstore_configs\fR . .IP "\(bu" 4 \fBwrite_only_yaml\fR . .IP "\(bu" 4 \fByaml\fR . .IP "" 0 . .SH "COPYRIGHT AND LICENSE" Copyright 2011 by Puppet Inc\. Apache 2 license; see COPYING puppet-5.5.10/man/man8/puppet-script.80000644005276200011600000000454213417161722017346 0ustar jenkinsjenkins.\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . .TH "PUPPET\-SCRIPT" "8" "January 2019" "Puppet, Inc." "Puppet manual" . .SH "NAME" \fBpuppet\-script\fR \- Run a puppet manifests as a script without compiling a catalog . .SH "SYNOPSIS" Runs a puppet language script without compiling a catalog\. . .SH "USAGE" puppet script [\-h|\-\-help] [\-V|\-\-version] [\-d|\-\-debug] [\-v|\-\-verbose] [\-e|\-\-execute] [\-l|\-\-logdest syslog|eventlog|\fIFILE\fR|console] [\-\-noop] \fIfile\fR . .SH "DESCRIPTION" This is a standalone puppet script runner tool; use it to run puppet code without compiling a catalog\. . .P When provided with a modulepath, via command line or config file, puppet script can load functions, types, tasks and plans from modules\. . .SH "OPTIONS" Note that any setting that\'s valid in the configuration file is also a valid long argument\. For example, \'environment\' is a valid setting, so you can specify \'\-\-environment mytest\' as an argument\. . .P See the configuration file documentation at https://puppet\.com/docs/puppet/latest/configuration\.html for the full list of acceptable parameters\. A commented list of all configuration options can also be generated by running puppet with \'\-\-genconfig\'\. . .TP \-\-debug Enable full debugging\. . .TP \-\-help Print this help message . .TP \-\-logdest Where to send log messages\. Choose between \'syslog\' (the POSIX syslog service), \'eventlog\' (the Windows Event Log), \'console\', or the path to a log file\. Defaults to \'console\'\. . .IP A path ending with \'\.json\' will receive structured output in JSON format\. The log file will not have an ending \']\' automatically written to it due to the appending nature of logging\. It must be appended manually to make the content valid JSON\. . .TP \-\-noop Use \'noop\' mode where Puppet runs in a no\-op or dry\-run mode\. This is useful for seeing what changes Puppet will make without actually executing the changes\. Applies to tasks only\. . .TP \-\-execute Execute a specific piece of Puppet code . .TP \-\-verbose Print extra information\. . .SH "EXAMPLE" . .nf $ puppet script \-l /tmp/manifest\.log manifest\.pp $ puppet script \-\-modulepath=/root/dev/modules \-e \'notice("hello world")\' . .fi . .SH "AUTHOR" Henrik Lindberg . .SH "COPYRIGHT" Copyright (c) 2017 Puppet Inc\., LLC Licensed under the Apache 2\.0 License puppet-5.5.10/man/man8/puppet.80000644005276200011600000000353313417161722016043 0ustar jenkinsjenkins.\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . .TH "PUPPET" "8" "January 2019" "Puppet, Inc." "Puppet manual" . .SH "NAME" \fBpuppet\fR . .P Usage: puppet \fIsubcommand\fR [options] \fIaction\fR [options] . .P Available subcommands: . .P agent The puppet agent daemon apply Apply Puppet manifests locally ca Local Puppet Certificate Authority management\. (Deprecated) catalog Compile, save, view, and convert catalogs\. cert Manage certificates and requests (Deprecated) certificate Provide access to the CA for certificate management\. (Deprecated) certificate_request Manage certificate requests\. (Deprecated) certificate_revocation_list Manage the list of revoked certificates\. (Deprecated) config Interact with Puppet\'s settings\. describe Display help about resource types device Manage remote network devices doc Generate Puppet references epp Interact directly with the EPP template parser/renderer\. facts Retrieve and store facts\. filebucket Store and retrieve files in a filebucket generate Generates Puppet code from Ruby definitions\. help Display Puppet help\. key Create, save, and remove certificate keys\. (Deprecated) lookup Interactive Hiera lookup man Display Puppet manual pages\. (Deprecated) master The puppet master daemon module Creates, installs and searches for modules on the Puppet Forge\. node View and manage node definitions\. parser Interact directly with the parser\. plugin Interact with the Puppet plugin system\. report Create, display, and submit reports\. resource The resource abstraction layer shell script Run a puppet manifests as a script without compiling a catalog status View puppet server status\. (Deprecated) . .P See \'puppet help \fIsubcommand\fR \fIaction\fR\' for help on a specific subcommand action\. See \'puppet help \fIsubcommand\fR\' for help on a specific subcommand\. Puppet v5\.5\.9 puppet-5.5.10/examples/0000755005276200011600000000000013417162176014635 5ustar jenkinsjenkinspuppet-5.5.10/examples/hiera/0000755005276200011600000000000013417162176015725 5ustar jenkinsjenkinspuppet-5.5.10/examples/hiera/README.md0000644005276200011600000001012113417161721017172 0ustar jenkinsjenkinsA working demo of Hiera with YAML and Puppet backends. ====================================================== This demo consists of: * A NTP module that has defaults for pool.ntp.org servers * A common data module where module users can create override data in pp files * A YAML data source in etc/hieradb where users can override data in yaml files * A couple of users modules that just notify the fact that they are being included * In Hiera data files a key called _classes_ that decides what to include on a node Below various usage scenarios can be tested using this module. The examples below assume you have Hiera already installed and that you have hiera-puppet cloned from github and running these commands in _hiera-puppet/example_ as cwd. Module from forge with module defaults -------------------------------------- * Move the _modules/data_ directory to _modules/data.bak_ to avoid overrides used further in the example * Run puppet, creates _/etc/ntp.conf_ with ntp.org addresses * The _hiera\_include()_ function includes just _users::common_
$ mv modules/data modules/data.bak
$ puppet apply --config etc/puppet.conf site.pp
notice: /Stage[main]/Ntp::Config/File[/tmp/ntp.conf]/ensure: defined content as '{md5}7045121976147a932a66c7671939a9ad'
notice: /Stage[main]/Users::Common/Notify[Adding users::common]/message: defined 'message' as 'Adding users::common'
$ cat /tmp/ntp.conf
server 1.pool.ntp.org
server 2.pool.ntp.org
Site wide override data in _data::common_ ----------------------------------------- * Restore the _modules/data_ directory that has a class _data::common_ that declares site wide overrides * The _hiera_include()_ function includes just _users::common_
$ mv modules/data.bak modules/data
$ puppet apply --config etc/puppet.conf site.pp
notice: /Stage[main]/Ntp::Config/File[/tmp/ntp.conf]/content: content changed '{md5}7045121976147a932a66c7671939a9addc2' to '{md5}8f9039fe1989a278a0a8e1836acb8d23'
notice: /Stage[main]/Users::Common/Notify[Adding users::common]/message: defined 'message' as 'Adding users::common'
$ cat /tmp/ntp.conf
server ntp1.example.com
server ntp2.example.com
Fact driven overrides for location=dc1 -------------------------------------- * Set a fact location=dc1 that uses the YAML data in _etc/hieradb/dc1.yaml_ to override * Show that machines in dc2 would use site-wide defaults * The _hiera_include()_ function includes _users::common_ and _users::dc1_ as the data file for dc1 adds that
$ FACTER_location=dc1 puppet apply --config etc/puppet.conf site.pp
notice: /Stage[main]/Ntp::Config/File[/tmp/ntp.conf]/content: content changed '{md5}8f9039fe1989a278a0a8e1836acb8d23' to '{md5}074d0e2ac727f6cb9afe3345d574b578'
notice: /Stage[main]/Users::Common/Notify[Adding users::common]/message: defined 'message' as 'Adding users::common'
notice: /Stage[main]/Users::Dc1/Notify[Adding users::dc1]/message: defined 'message' as 'Adding users::dc1'
$ cat /tmp/ntp.conf
server ntp1.dc1.example.com
server ntp2.dc1.example.com
Now simulate a machine in _dc2_, because there is no data for dc2 it uses the site wide defaults and does not include the _users::dc1_ class anymore
$ FACTER_location=dc2 puppet apply --config etc/puppet.conf site.pp
warning: Could not find class data::dc2 for nephilim.ml.org
notice: /Stage[main]/Ntp::Config/File[/tmp/ntp.conf]/content: content changed '{md5}074d0e2ac727f6cb9afe3345d574b578' to '{md5}8f9039fe1989a278a0a8e1836acb8d23'
notice: /Stage[main]/Users::Common/Notify[Adding users::common]/message: defined 'message' as 'Adding users::common'
$ cat /tmp/ntp.conf
server ntp1.example.com
server ntp2.example.com
You could create override data in the following places for a machine in _location=dc2_, they will be searched in this order and the first one with data will match. * file etc/hieradb/dc2.yaml * file etc/hieradb/common.yaml * class data::dc2 * class data::production * class data::common * class ntp::config::data * class ntp::data In this example due to the presence of _common.yaml_ that declares _ntpservers_ the classes will never be searched, it will have precedence. puppet-5.5.10/examples/hiera/etc/0000755005276200011600000000000013417162176016500 5ustar jenkinsjenkinspuppet-5.5.10/examples/hiera/etc/hiera.yaml0000644005276200011600000000024013417161721020443 0ustar jenkinsjenkins--- :backends: - yaml - puppet :hierarchy: - "%{location}" - "%{environment}" - common :yaml: :datadir: etc/hieradb :puppet: :datasource: data puppet-5.5.10/examples/hiera/etc/hieradb/0000755005276200011600000000000013417162176020076 5ustar jenkinsjenkinspuppet-5.5.10/examples/hiera/etc/hieradb/common.yaml0000644005276200011600000000005313417161721022243 0ustar jenkinsjenkinsclasses: - users::common - ntp::config puppet-5.5.10/examples/hiera/etc/hieradb/dc1.yaml0000644005276200011600000000013213417161721021420 0ustar jenkinsjenkins--- ntpservers: - ntp1.dc1.example.com - ntp2.dc1.example.com classes: - users::dc1 puppet-5.5.10/examples/hiera/etc/hieradb/development.yaml0000644005276200011600000000004013417161721023271 0ustar jenkinsjenkins--- classes: users::development puppet-5.5.10/examples/hiera/etc/puppet.conf0000644005276200011600000000003713417161721020657 0ustar jenkinsjenkins[main] modulepath = modules puppet-5.5.10/examples/hiera/modules/0000755005276200011600000000000013417162176017375 5ustar jenkinsjenkinspuppet-5.5.10/examples/hiera/modules/data/0000755005276200011600000000000013417162176020306 5ustar jenkinsjenkinspuppet-5.5.10/examples/hiera/modules/data/manifests/0000755005276200011600000000000013417162176022277 5ustar jenkinsjenkinspuppet-5.5.10/examples/hiera/modules/data/manifests/common.pp0000644005276200011600000000021013417161721024114 0ustar jenkinsjenkins# sets the common (across all puppet conf) ntp servers. class data::common { $ntpservers = ['ntp1.example.com', 'ntp2.example.com'] } puppet-5.5.10/examples/hiera/modules/ntp/0000755005276200011600000000000013417162176020176 5ustar jenkinsjenkinspuppet-5.5.10/examples/hiera/modules/ntp/manifests/0000755005276200011600000000000013417162176022167 5ustar jenkinsjenkinspuppet-5.5.10/examples/hiera/modules/ntp/manifests/config.pp0000644005276200011600000000031713417161721023771 0ustar jenkinsjenkins# lookup ntpservers from hiera, or allow user of class to provide other value class ntp::config($ntpservers = hiera('ntpservers')) { file{'/tmp/ntp.conf': content => template('ntp/ntp.conf.erb') } } puppet-5.5.10/examples/hiera/modules/ntp/manifests/data.pp0000644005276200011600000000020413417161721023430 0ustar jenkinsjenkins# this class will be loaded using hiera's 'puppet' backend class ntp::data { $ntpservers = ['1.pool.ntp.org', '2.pool.ntp.org'] } puppet-5.5.10/examples/hiera/modules/ntp/templates/0000755005276200011600000000000013417162176022174 5ustar jenkinsjenkinspuppet-5.5.10/examples/hiera/modules/ntp/templates/ntp.conf.erb0000644005276200011600000000011513417161721024403 0ustar jenkinsjenkins<% [ntpservers].flatten.each do |server| -%> server <%= server %> <% end -%> puppet-5.5.10/examples/hiera/modules/users/0000755005276200011600000000000013417162176020536 5ustar jenkinsjenkinspuppet-5.5.10/examples/hiera/modules/users/manifests/0000755005276200011600000000000013417162176022527 5ustar jenkinsjenkinspuppet-5.5.10/examples/hiera/modules/users/manifests/common.pp0000644005276200011600000000010613417161721024350 0ustar jenkinsjenkins# notifies class users::common { notify{'Adding users::common': } } puppet-5.5.10/examples/hiera/modules/users/manifests/dc1.pp0000644005276200011600000000010013417161721023521 0ustar jenkinsjenkins# notifies class users::dc1 { notify{'Adding users::dc1': } } puppet-5.5.10/examples/hiera/modules/users/manifests/development.pp0000644005276200011600000000012013417161721025376 0ustar jenkinsjenkins# notifies class users::development { notify{'Adding users::development': } } puppet-5.5.10/examples/hiera/site.pp0000644005276200011600000000005413417161721017224 0ustar jenkinsjenkinsnode default { hiera_include('classes') } puppet-5.5.10/ext/0000755005276200011600000000000013417162176013617 5ustar jenkinsjenkinspuppet-5.5.10/ext/README.environment0000644005276200011600000000064213417161721017037 0ustar jenkinsjenkinsThis is an example environment directory. The environment's modules can be placed in a "modules" subdirectory. The environments initial manifests are placed in a "manifests" subdirectory. By default the environment also has any modules that are installed globally (normally in /etc/puppetlabs/code/modules) for the puppet master. For more information see https://puppet.com/docs/puppet/latest/environments_about.html puppet-5.5.10/ext/build_defaults.yaml0000644005276200011600000000115613417161721017467 0ustar jenkinsjenkins--- packager: 'puppetlabs' gpg_key: '7F438280EF8D349F' # These are the build targets used by the packaging repo. Uncomment to allow use. #final_mocks: 'pl-el-5-i386 pl-el-6-i386 pl-el-7-x86_64' #cows: 'base-lucid-i386.cow base-precise-i386.cow base-squeeze-i386.cow base-trusty-i386.cow base-wheezy-i386.cow' pbuild_conf: '/etc/pbuilderrc' build_gem: TRUE build_dmg: FALSE sign_tar: FALSE yum_host: 'yum.puppetlabs.com' yum_repo_path: '/opt/repository/yum/' apt_signing_server: 'apt.puppetlabs.com' apt_repo_url: 'http://apt.puppetlabs.com' apt_repo_path: '/opt/repository/incoming' tar_host: 'downloads.puppetlabs.com' puppet-5.5.10/ext/cert_inspector0000755005276200011600000001054113417161721016564 0ustar jenkinsjenkins#!/usr/bin/env ruby require 'openssl' class X509Collector include Enumerable def initialize @collected_data = {} end def interpret_contents(contents) cls = case contents.split("\n")[0] when /BEGIN X509 CRL/ then OpenSSL::X509::CRL when /BEGIN CERTIFICATE REQUEST/ then OpenSSL::X509::Request when /BEGIN CERTIFICATE/ then OpenSSL::X509::Certificate when /BEGIN RSA (PRIVATE|PUBLIC) KEY/ then OpenSSL::PKey::RSA else return nil end cls.new(contents) rescue nil end def expected_non_x509_files ['inventory.txt', 'ca.pass', 'serial'] end def investigate_path(path) if File.directory?(path) Dir.foreach(path) do |x| next if ['.', '..'].include? x investigate_path File.join(path, x) end else contents = File.read path meaning = interpret_contents contents unless meaning || expected_non_x509_files.include?(File.basename(path)) puts "WARNING: file #{path.inspect} could not be interpreted" end @collected_data[path] = meaning if meaning end end def each(&block) @collected_data.each(&block) end def extract_public_key_info(path, meaning) case meaning when OpenSSL::PKey::RSA if meaning.private? [meaning.public_key, 2, path] else [meaning, 3, path] end when OpenSSL::X509::Certificate [meaning.public_key, 0, meaning.subject.to_s] when OpenSSL::X509::Request [meaning.public_key, 1, meaning.subject.to_s] end end def who_signed(meaning, key_names, keys) signing_key = keys.find { |key| meaning.verify(key) } if signing_key then "#{key_names[signing_key.to_s]}" else "???" end end def explain(meaning, key_names, keys) case meaning when OpenSSL::PKey::RSA if meaning.private? "Private key for #{key_names[meaning.public_key.to_s]}" else "Public key for #{key_names[meaning.public_key.to_s]}" end when OpenSSL::X509::Certificate signature_desc = who_signed(meaning, key_names, keys) "Certificate assigning name #{meaning.subject.to_s} to #{key_names[meaning.public_key.to_s]}\n serial number #{meaning.serial}\n issued by #{meaning.issuer.to_s}\n signed by #{signature_desc}" when OpenSSL::X509::Request signature_desc = who_signed(meaning, key_names, keys) "Certificate request for #{meaning.subject.to_s} having key #{key_names[meaning.public_key.to_s]}\n signed by #{signature_desc}" when OpenSSL::X509::CRL signature_desc = who_signed(meaning, key_names, keys) revoked_serial_numbers = meaning.revoked.map { |r| r.serial } revoked_desc = if revoked_serial_numbers.count > 0 then "serial numbers #{revoked_serial_numbers.inspect}" else "nothing" end "Certificate revocation list revoking #{revoked_desc}\n issued by #{meaning.issuer.to_s}\n signed by #{signature_desc}" else "Unknown" end end # Yield unique public keys, with a canonical name for each. def collect_public_keys key_data = {} # pem => (priority, name, public_key) @collected_data.collect do |path, meaning| begin next unless public_key_info = extract_public_key_info(path, meaning) public_key, priority, name = public_key_info pem = public_key.to_s existing_priority, _, _ = key_data[pem] next if existing_priority and existing_priority < priority key_data[pem] = priority, name, public_key rescue puts "exception!" end end name_to_key_hash = {} key_data.each do |pem, data| _, name, public_key = data if name_to_key_hash[name] suffix_num = 2 while name_to_key_hash[name + " (#{suffix_num})"] suffix_num += 1 end name = name + " (#{suffix_num})" end name_to_key_hash[name] = public_key end key_names = {} keys = [] name_to_key_hash.each do |name, public_key| key_names[public_key.to_s] = "key<#{name}>" keys << public_key end [key_names, keys] end end collector = X509Collector.new ARGV.each do |path| collector.investigate_path(path) end key_names, keys = collector.collect_public_keys collector.map do |path, meaning| [collector.explain(meaning, key_names, keys), path] end.sort.each do |description, path| puts "#{path}:" puts " #{description}" puts end puppet-5.5.10/ext/dbfix.sql0000644005276200011600000000756513417161721015444 0ustar jenkinsjenkins-- MySQL DB consistency check/fix -- -- Usage: -- cat dbfix.sql | mysql -u user -p puppet -- -- WARNING: perform a database backup before running this script -- Remove duplicate resources, and keep the latest one DELETE bad_rows.* FROM resources AS bad_rows INNER JOIN ( SELECT title,restype,host_id, MAX(id) as max_id FROM resources GROUP BY title,restype,host_id HAVING count(*) > 1 ) AS good_rows ON good_rows.title = bad_rows.title AND good_rows.restype = bad_rows.restype AND good_rows.host_id = bad_rows.host_id AND good_rows.max_id <> bad_rows.id; -- Remove duplicate param_values, and keep the latest one DELETE bad_rows.* FROM param_values AS bad_rows INNER JOIN ( SELECT value,param_name_id,resource_id, MAX(id) as max_id FROM param_values GROUP BY value,param_name_id,resource_id HAVING count(*) > 1 ) AS good_rows ON good_rows.value = bad_rows.value AND good_rows.param_name_id = bad_rows.param_name_id AND good_rows.resource_id = bad_rows.resource_id AND good_rows.max_id <> bad_rows.id; -- rewrite param_values that points to duplicated param_names -- to point to the highest param_name id. UPDATE param_values v INNER JOIN param_names n ON n.id = v.param_name_id INNER JOIN ( SELECT name, MAX(id) as max_id FROM param_names GROUP BY name HAVING count(*) > 1 ) nmax ON n.name = nmax.name SET v.param_name_id = nmax.max_id; -- Remove duplicate param_names, and keep the latest one DELETE bad_rows.* FROM param_names AS bad_rows INNER JOIN ( SELECT name, MAX(id) as max_id FROM param_names GROUP BY name HAVING count(*) > 1 ) AS good_rows ON good_rows.name = bad_rows.name AND good_rows.max_id <> bad_rows.id; -- Remove duplicate resource_tags, and keep the highest one DELETE bad_rows.* FROM resource_tags AS bad_rows INNER JOIN ( SELECT resource_id,puppet_tag_id, MAX(id) as max_id FROM resource_tags GROUP BY resource_id,puppet_tag_id HAVING count(*) > 1 ) AS good_rows ON good_rows.resource_id = bad_rows.resource_id AND good_rows.puppet_tag_id = bad_rows.puppet_tag_id AND good_rows.max_id <> bad_rows.id; -- rewrite resource_tags that points to duplicated puppet_tags -- to point to the highest puppet_tags id. UPDATE resource_tags v INNER JOIN puppet_tags n ON n.id = v.puppet_tag_id INNER JOIN ( SELECT name, MAX(id) as max_id FROM puppet_tags GROUP BY name HAVING count(*) > 1 ) nmax ON n.name = nmax.name SET v.puppet_tag_id = nmax.max_id; -- Remove duplicate puppet_tags, and keep the highest one DELETE bad_rows.* FROM puppet_tags AS bad_rows INNER JOIN ( SELECT name, MAX(id) as max_id FROM puppet_tags GROUP BY name HAVING count(*) > 1 ) AS good_rows ON good_rows.name = bad_rows.name AND good_rows.max_id <> bad_rows.id; -- Fix dangling resources -- note: we use a table to not exceed the number of InnoDB locks if there are two much -- rows to delete. -- this is an alternative to: DELETE resources FROM resources r LEFT JOIN hosts h ON h.id=r.host_id WHERE h.id IS NULL; -- CREATE TABLE resources_c LIKE resources; INSERT INTO resources_c SELECT r.* FROM resources r INNER JOIN hosts h ON h.id=r.host_id; RENAME TABLE resources TO resources_old, resources_c TO resources; DROP TABLE resources_old; -- Fix dangling param_values CREATE TABLE param_values_c LIKE param_values; INSERT INTO param_values_c SELECT v.* FROM param_values v INNER JOIN resources r ON r.id=v.resource_id; RENAME TABLE param_values TO param_values_old, param_values_c TO param_values; DROP TABLE param_values_old; -- Fix dangling resource_tags CREATE TABLE resource_tags_c LIKE resource_tags; INSERT INTO resource_tags_c SELECT t.* FROM resource_tags t INNER JOIN resources r ON r.id=t.resource_id; RENAME TABLE resource_tags TO resource_tags_old, resource_tags_c TO resource_tags; DROP TABLE resource_tags_old; puppet-5.5.10/ext/debian/0000755005276200011600000000000013417162177015042 5ustar jenkinsjenkinspuppet-5.5.10/ext/debian/README.Debian0000644005276200011600000000051213417161721017073 0ustar jenkinsjenkinspuppet for Debian ------------------ The default puppet configuration in Debian will automatically integrate with etckeeper if etckeeper is installed. puppet will automatically commit any changes made to files in /etc via etckeeper before and after its run. -- Mathias Gug Wed, 18 Aug 2010 15:06:06 -0400 puppet-5.5.10/ext/debian/README.source0000644005276200011600000000017213417161721017213 0ustar jenkinsjenkinsThe debian directory is now maintained on Alioth in git. See https://pkg-puppet.alioth.debian.org/ for more information. puppet-5.5.10/ext/debian/TODO.Debian0000644005276200011600000000013613417161721016705 0ustar jenkinsjenkins* clean up initscripts per http://mail.madstop.com/pipermail/puppet-dev/2006-June/001069.html puppet-5.5.10/ext/debian/compat0000644005276200011600000000000213417161721016232 0ustar jenkinsjenkins7 puppet-5.5.10/ext/debian/control0000644005276200011600000001672413417161721016451 0ustar jenkinsjenkinsSource: puppet Section: admin Priority: optional Maintainer: Puppet Labs Uploaders: Micah Anderson , Andrew Pollock , Nigel Kersten , Stig Sandbeck Mathisen Build-Depends-Indep: ruby | ruby-interpreter, libopenssl-ruby | libopenssl-ruby1.9.1 | libruby (>= 1:1.9.3.4), facter (>= 1.7.0), hiera (>= 2.0.0) Build-Depends: debhelper (>= 7.0.0), openssl Standards-Version: 3.9.1 Vcs-Git: git://github.com/puppetlabs/puppet Homepage: https://puppetlabs.com/puppet/puppet-open-source Package: puppet-common Architecture: all Depends: ${misc:Depends}, ruby | ruby-interpreter, libopenssl-ruby | libopenssl-ruby1.9.1 | libruby (>= 1:1.9.3.4), ruby-shadow | libshadow-ruby1.8, libaugeas-ruby | libaugeas-ruby1.9.1 | libaugeas-ruby1.8, lsb-base, sysv-rc (>= 2.86) | file-rc, hiera (>= 2.0.0), facter (>= 1.7.0), libjson-ruby | ruby-json Recommends: lsb-release, debconf-utils Suggests: ruby-selinux | libselinux-ruby1.8 Breaks: puppet (<< 2.6.0~rc2-1), puppetmaster (<< 0.25.4-1) Provides: hiera-puppet Conflicts: hiera-puppet, puppet (<< 3.3.0-1puppetlabs1) Replaces: hiera-puppet Description: Centralized configuration management Puppet lets you centrally manage every important aspect of your system using a cross-platform specification language that manages all the separate elements normally aggregated in different files, like users, cron jobs, and hosts, along with obviously discrete elements like packages, services, and files. . Puppet's simple declarative specification language provides powerful classing abilities for drawing out the similarities between hosts while allowing them to be as specific as necessary, and it handles dependency and prerequisite relationships between objects clearly and explicitly. . This package contains the puppet software and documentation. For the startup scripts needed to run the puppet agent and master, see the "puppet" and "puppetmaster" packages, respectively. Package: puppet Architecture: all Depends: ${misc:Depends}, puppet-common (= ${binary:Version}), ruby | ruby-interpreter Recommends: rdoc Suggests: puppet-el, vim-puppet Conflicts: puppet-common (<< 3.3.0-1puppetlabs1) Description: Centralized configuration management - agent startup and compatibility scripts This package contains the startup script and compatbility scripts for the puppet agent, which is the process responsible for configuring the local node. . Puppet lets you centrally manage every important aspect of your system using a cross-platform specification language that manages all the separate elements normally aggregated in different files, like users, cron jobs, and hosts, along with obviously discrete elements like packages, services, and files. . Puppet's simple declarative specification language provides powerful classing abilities for drawing out the similarities between hosts while allowing them to be as specific as necessary, and it handles dependency and prerequisite relationships between objects clearly and explicitly. Package: puppetmaster-common Architecture: all Depends: ${misc:Depends}, ruby | ruby-interpreter, puppet-common (= ${binary:Version}), facter (>= 1.7.0), lsb-base Breaks: puppet (<< 0.24.7-1), puppetmaster (<< 2.6.1~rc2-1) Replaces: puppetmaster (<< 2.6.1~rc2-1) Conflicts: puppet-common (<< 3.3.0-1puppetlabs1) Suggests: apache2 | nginx, puppet-el, vim-puppet, rdoc, ruby-ldap | libldap-ruby1.8, puppetdb-terminus Description: Puppet master common scripts This package contains common scripts for the puppet master, which is the server hosting manifests and files for the puppet nodes. . Puppet lets you centrally manage every important aspect of your system using a cross-platform specification language that manages all the separate elements normally aggregated in different files, like users, cron jobs, and hosts, along with obviously discrete elements like packages, services, and files. . Puppet's simple declarative specification language provides powerful classing abilities for drawing out the similarities between hosts while allowing them to be as specific as necessary, and it handles dependency and prerequisite relationships between objects clearly and explicitly. Package: puppetmaster Architecture: all Depends: ${misc:Depends}, ruby | ruby-interpreter, puppetmaster-common (= ${source:Version}), facter (>= 1.7.0), lsb-base Breaks: puppet (<< 0.24.7-1) Conflicts: puppet (<< 3.3.0-1puppetlabs1) Suggests: apache2 | nginx, puppet-el, vim-puppet, rdoc, ruby-ldap | libldap-ruby1.8, puppetdb-terminus Description: Centralized configuration management - master startup and compatibility scripts This package contains the startup and compatibility scripts for the puppet master, which is the server hosting manifests and files for the puppet nodes. . Puppet lets you centrally manage every important aspect of your system using a cross-platform specification language that manages all the separate elements normally aggregated in different files, like users, cron jobs, and hosts, along with obviously discrete elements like packages, services, and files. . Puppet's simple declarative specification language provides powerful classing abilities for drawing out the similarities between hosts while allowing them to be as specific as necessary, and it handles dependency and prerequisite relationships between objects clearly and explicitly. Package: puppetmaster-passenger Architecture: all Depends: ${misc:Depends}, ruby | ruby-interpreter, puppetmaster-common (= ${source:Version}), facter (>= 1.7.0), lsb-base, apache2, libapache2-mod-passenger Conflicts: puppetmaster (<< 2.6.1~rc2-1) Replaces: puppetmaster (<< 2.6.1~rc2-1) Description: Centralised configuration management - master setup to run under mod passenger This package provides a puppetmaster running under mod passenger. This configuration offers better performance and scalability. . Puppet lets you centrally manage every important aspect of your system using a cross-platform specification language that manages all the separate elements normally aggregated in different files, like users, cron jobs, and hosts, along with obviously discrete elements like packages, services, and files. . Puppet's simple declarative specification language provides powerful classing abilities for drawing out the similarities between hosts while allowing them to be as specific as necessary, and it handles dependency and prerequisite relationships between objects clearly and explicitly. . Package: vim-puppet Architecture: all Depends: ${misc:Depends}, puppet (= ${source:Version}) Recommends: vim-addon-manager Description: syntax highlighting for puppet manifests in vim The vim-puppet package provides filetype detection and syntax highlighting for puppet manifests (files ending with ".pp"). Package: puppet-el Architecture: all Depends: ${misc:Depends}, emacsen-common, puppet (= ${source:Version}) Description: syntax highlighting for puppet manifests in emacs The puppet-el package provides syntax highlighting for puppet manifests Package: puppet-testsuite Architecture: all Depends: ${misc:Depends}, ruby | ruby-interpreter, puppet-common (= ${source:Version}), facter (>= 1.7.0), lsb-base, rdoc, ruby-ldap | libldap-ruby1.8, ruby-rspec | librspec-ruby, git-core, ruby-mocha | libmocha-ruby1.8 Recommends: cron Description: Centralized configuration management - test suite This package provides all the tests from the upstream puppet source code. The tests are used for improving the QA of the puppet package. puppet-5.5.10/ext/debian/docs0000644005276200011600000000001213417161721015700 0ustar jenkinsjenkinsREADME.md puppet-5.5.10/ext/debian/fileserver.conf0000644005276200011600000000266613417161721020063 0ustar jenkinsjenkins# fileserver.conf # Puppet automatically serves PLUGINS and FILES FROM MODULES: anything in # /files/ is available to authenticated nodes at # puppet:///modules//. You do not need to edit this # file to enable this. # MOUNT POINTS # If you need to serve files from a directory that is NOT in a module, # you must create a static mount point in this file: # # [extra_files] # path /etc/puppet/files # allow * # # In the example above, anything in /etc/puppet/files/ would be # available to authenticated nodes at puppet:///extra_files/. # # Mount points may also use three placeholders as part of their path: # # %H - The node's certname. # %h - The portion of the node's certname before the first dot. (Usually the # node's short hostname.) # %d - The portion of the node's certname after the first dot. (Usually the # node's domain name.) # PERMISSIONS # Every static mount point should have an `allow *` line; setting more # granular permissions in this file is deprecated. Instead, you can # control file access in auth.conf by controlling the # /file_metadata/ and /file_content/ paths: # # path ~ ^/file_(metadata|content)/extra_files/ # auth yes # allow /^(.+)\.example\.com$/ # allow_ip 192.168.100.0/24 # # If added to auth.conf BEFORE the "path /file" rule, the rule above # will add stricter restrictions to the extra_files mount point. puppet-5.5.10/ext/debian/puppet-common.dirs0000644005276200011600000000047113417161721020524 0ustar jenkinsjenkinsetc/puppet etc/puppet/environments etc/puppet/environments/example_env etc/puppet/environments/example_env/modules etc/puppet/environments/example_env/manifests etc/puppet/manifests etc/puppet/templates etc/puppet/modules usr/lib/ruby/vendor_ruby usr/share/puppet/ext var/lib/puppet var/log/puppet var/run/puppet puppet-5.5.10/ext/debian/puppet-common.install0000644005276200011600000000017713417161721021234 0ustar jenkinsjenkinsdebian/puppet.conf etc/puppet debian/tmp/usr/bin/puppet usr/bin debian/tmp/usr/lib/ruby/vendor_ruby/* usr/lib/ruby/vendor_ruby puppet-5.5.10/ext/debian/puppet-common.lintian-overrides0000644005276200011600000000041713417161721023221 0ustar jenkinsjenkins# Man pages are automatically generated, not much to do here puppet-common binary: manpage-has-bad-whatis-entry puppet-common binary: manpage-has-errors-from-man # These are "scripts" but do nothing other than providing documentation puppet-common: script-not-executable puppet-5.5.10/ext/debian/puppet-common.manpages0000644005276200011600000000131613417161721021355 0ustar jenkinsjenkinsman/man5/puppet.conf.5 man/man8/puppet.8 man/man8/puppet-agent.8 man/man8/puppet-apply.8 man/man8/puppet-catalog.8 man/man8/puppet-cert.8 man/man8/puppet-certificate.8 man/man8/puppet-certificate_request.8 man/man8/puppet-certificate_revocation_list.8 man/man8/puppet-config.8 man/man8/puppet-describe.8 man/man8/puppet-device.8 man/man8/puppet-doc.8 man/man8/puppet-facts.8 man/man8/puppet-file.8 man/man8/puppet-filebucket.8 man/man8/puppet-help.8 man/man8/puppet-inspect.8 man/man8/puppet-key.8 man/man8/puppet-kick.8 man/man8/puppet-man.8 man/man8/puppet-module.8 man/man8/puppet-node.8 man/man8/puppet-parser.8 man/man8/puppet-plugin.8 man/man8/puppet-report.8 man/man8/puppet-resource.8 man/man8/puppet-status.8 puppet-5.5.10/ext/debian/puppet-common.postinst0000644005276200011600000000175413417161721021453 0ustar jenkinsjenkins#!/bin/bash set -e if [ "$1" = "configure" ]; then # Create the "puppet" user if ! getent passwd puppet > /dev/null; then useradd --system --user-group --home-dir /var/lib/puppet \ --no-create-home --shell /bin/false \ --comment "Puppet configuration management daemon" \ puppet fi # Set correct permissions and ownership for puppet directories for dir in /var/{run,lib,log}/puppet; do if ! dpkg-statoverride --list "$dir" >/dev/null 2>&1; then dpkg-statoverride --update --add puppet puppet 0750 "$dir" fi done # Create folders common to "puppet" and "puppetmaster", which need # to be owned by the "puppet" user install --owner puppet --group puppet --directory \ /var/lib/puppet/state install --owner puppet --group puppet --directory \ /var/lib/puppet/reports # Handle if [ -d /etc/puppet/ssl ] && [ ! -e /var/lib/puppet/ssl ] && grep -q 'ssldir=/var/lib/puppet/ssl' /etc/puppet/puppet.conf; then mv /etc/puppet/ssl /var/lib/puppet/ssl fi fi #DEBHELPER# puppet-5.5.10/ext/debian/puppet-common.postrm0000644005276200011600000000125713417161721021112 0ustar jenkinsjenkins#!/bin/sh -e case "$1" in purge) # Remove puppetd.conf (used in > 0.24) rm -f /etc/puppet/puppetd.conf # Remove puppet state directory created by the postinst script. # This directory can be removed without causing harm # according to upstream documentation. rm -rf /var/lib/puppet/state rm -rf /var/lib/puppet/reports if [ -d /var/lib/puppet ]; then rmdir --ignore-fail-on-non-empty /var/lib/puppet fi # Remove puppet log files rm -rf /var/log/puppet/ ;; remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) ;; *) echo "postrm called with unknown argument \`$1'" >&2 exit 1 esac #DEBHELPER# exit 0 puppet-5.5.10/ext/debian/puppet-el.dirs0000644005276200011600000000003213417161721017625 0ustar jenkinsjenkinsusr/share/emacs/site-lisp puppet-5.5.10/ext/debian/puppet-el.emacsen-install0000644005276200011600000000113413417161721021747 0ustar jenkinsjenkins#!/bin/sh # # emacsen install script for the Debian GNU/Linux puppet-el package FLAVOR=$1 PACKAGE=puppet-el ELDIR=/usr/share/emacs/site-lisp/ ELCDIR=/usr/share/${FLAVOR}/site-lisp/${PACKAGE} ELFILE="puppet-mode.el" FLAGS="-batch -no-site-file -l path.el -f batch-byte-compile" if [ ${FLAVOR} != emacs ]; then echo install/${PACKAGE}: Byte-compiling for ${FLAVOR} install -m 755 -d ${ELCDIR} cd ${ELDIR} cp ${ELFILE} ${ELCDIR} cd ${ELCDIR} cat << EOF > path.el (setq load-path (cons "." load-path) byte-compile-warnings nil) EOF ${FLAVOR} ${FLAGS} ${ELFILE} rm -f ${ELFILE} path.el fi puppet-5.5.10/ext/debian/puppet-el.emacsen-remove0000644005276200011600000000034013417161721021574 0ustar jenkinsjenkins#!/bin/sh set -e FLAVOR=$1 PACKAGE=puppet-el ELCFILE=puppet-mode.elc if [ ${FLAVOR} != emacs ]; then echo remove/${PACKAGE}: Purging byte-compiled files for ${FLAVOR} rm -f /usr/share/${FLAVOR}/site-lisp/${ELCFILE} fi puppet-5.5.10/ext/debian/puppet-el.emacsen-startup0000644005276200011600000000034513417161721022006 0ustar jenkinsjenkins;; -*-emacs-lisp-*- ;; ;; Emacs startup file for the Debian GNU/Linux puppet-el package (autoload 'puppet-mode "puppet-mode" "Major mode for editing puppet manifests") (add-to-list 'auto-mode-alist '("\\.pp$" . puppet-mode)) puppet-5.5.10/ext/debian/puppet-el.install0000644005276200011600000000006313417161721020336 0ustar jenkinsjenkinsext/emacs/puppet-mode.el usr/share/emacs/site-lisp puppet-5.5.10/ext/debian/puppet-testsuite.install0000644005276200011600000000011613417161721021766 0ustar jenkinsjenkinsspec/* /usr/share/puppet-testsuite/spec Rakefile /usr/share/puppet-testsuite/ puppet-5.5.10/ext/debian/puppet-testsuite.lintian-overrides0000644005276200011600000000026613417161721023764 0ustar jenkinsjenkins# Upstream distributes it like this puppet-testsuite binary: executable-not-elf-or-script puppet-testsuite binary: script-not-executable puppet-testsuite binary: unusual-interpreter puppet-5.5.10/ext/debian/puppet.default0000644005276200011600000000013013417161721017711 0ustar jenkinsjenkins# Defaults for puppet - sourced by /etc/init.d/puppet # Startup options DAEMON_OPTS="" puppet-5.5.10/ext/debian/puppet.init0000644005276200011600000000546213417161721017245 0ustar jenkinsjenkins#! /bin/sh ### BEGIN INIT INFO # Provides: puppet # Required-Start: $network $named $remote_fs $syslog # Required-Stop: $network $named $remote_fs $syslog # Should-Start: puppet # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 ### END INIT INFO DAEMON=/opt/puppetlabs/puppet/bin/puppet DAEMON_OPTS="" NAME="agent" PROCNAME="puppet" DESC="puppet agent" PIDFILE="/var/run/puppetlabs/${NAME}.pid" test -x $DAEMON || exit 0 [ -r /etc/default/puppet ] && . /etc/default/puppet . /lib/lsb/init-functions reload_puppet_agent() { start-stop-daemon --stop --quiet --signal HUP --pidfile $PIDFILE --name $PROCNAME } start_puppet_agent() { start-stop-daemon --start --quiet --pidfile $PIDFILE --startas $DAEMON -- $NAME $DAEMON_OPTS } stop_puppet_agent() { start-stop-daemon --stop --retry TERM/10/KILL/5 --quiet --oknodo --pidfile $PIDFILE --name $PROCNAME rm -f "$PIDFILE" } restart_puppet_agent() { log_begin_msg "Restarting $DESC" stop_puppet_agent start_puppet_agent log_end_msg $? } status_puppet_agent() { if (type status_of_proc > /dev/null 2>&1) ; then status_of_proc -p "${PIDFILE}" "${DAEMON}" "${NAME}" else status_of_proc() { local pidfile daemon name status pidfile= OPTIND=1 while getopts p: opt ; do case "$opt" in p) pidfile="$OPTARG";; esac done shift $(($OPTIND - 1)) if [ -n "$pidfile" ]; then pidfile="-p $pidfile" fi daemon="$1" name="$2" status="0" pidofproc $pidfile $daemon >/dev/null || status="$?" if [ "$status" = 0 ]; then log_success_msg "$name is running" return 0 elif [ "$status" = 4 ]; then log_failure_msg "could not access PID file for $name" return $status else log_failure_msg "$name is not running" return $status fi } status_of_proc -p "${PIDFILE}" "${DAEMON}" "${NAME}" fi } case "$1" in start) log_begin_msg "Starting $DESC" start_puppet_agent log_end_msg $? ;; stop) log_begin_msg "Stopping $DESC" stop_puppet_agent log_end_msg $? ;; reload) log_begin_msg "Reloading $DESC" reload_puppet_agent log_end_msg $? ;; status) status_puppet_agent ;; restart|force-reload) restart_puppet_agent ;; condrestart) if status_puppet_agent >/dev/null 2>&1; then restart_puppet_agent fi ;; *) echo "Usage: $0 {start|stop|status|restart|condrestart|force-reload|reload}" >&2 exit 1 ;; esac puppet-5.5.10/ext/debian/puppet.lintian-overrides0000644005276200011600000000022413417161721021727 0ustar jenkinsjenkins# Man pages are automatically generated, not much to do here puppet binary: manpage-has-bad-whatis-entry puppet binary: manpage-has-errors-from-man puppet-5.5.10/ext/debian/puppet.logrotate0000644005276200011600000000073713417161721020302 0ustar jenkinsjenkins/var/log/puppetlabs/masterhttp.log /var/log/puppet/masterhttp.log { compress rotate 4 missingok notifempty nocreate } /var/log/puppetlabs/puppetd.log /var/log/puppet/puppetd.log { compress rotate 4 missingok notifempty nocreate sharedscripts postrotate ([ -x /etc/init.d/puppet ] && /etc/init.d/puppet reload > /dev/null 2>&1) || ([ -x /usr/bin/systemctl ] && /usr/bin/systemctl kill -s USR2 puppet.service > /dev/null 2>&1) || true endscript } puppet-5.5.10/ext/debian/puppet.postinst0000644005276200011600000000077613417161721020170 0ustar jenkinsjenkins#!/bin/sh set -e # Remove renamed configuration files which are now handled by other # packages if dpkg-maintscript-helper supports rm_conffile 2>/dev/null; then dpkg-maintscript-helper rm_conffile \ /etc/logrotate.d/puppet 2.6.4-2 puppet -- "$@" dpkg-maintscript-helper rm_conffile \ /etc/logcheck/ignore.d.server/puppet 2.6.4-2 puppet -- "$@" dpkg-maintscript-helper rm_conffile \ /etc/emacs/site-start.d/50puppet-mode-init.el 2.6.4-2 puppet -- "$@" fi #DEBHELPER# puppet-5.5.10/ext/debian/puppet.postrm0000644005276200011600000000077613417161721017631 0ustar jenkinsjenkins#!/bin/sh set -e # Remove renamed configuration files which are now handled by other # packages if dpkg-maintscript-helper supports rm_conffile 2>/dev/null; then dpkg-maintscript-helper rm_conffile \ /etc/logrotate.d/puppet 2.6.4-2 puppet -- "$@" dpkg-maintscript-helper rm_conffile \ /etc/logcheck/ignore.d.server/puppet 2.6.4-2 puppet -- "$@" dpkg-maintscript-helper rm_conffile \ /etc/emacs/site-start.d/50puppet-mode-init.el 2.6.4-2 puppet -- "$@" fi #DEBHELPER# puppet-5.5.10/ext/debian/puppet.preinst0000644005276200011600000000077613417161721017771 0ustar jenkinsjenkins#!/bin/sh set -e # Remove renamed configuration files which are now handled by other # packages if dpkg-maintscript-helper supports rm_conffile 2>/dev/null; then dpkg-maintscript-helper rm_conffile \ /etc/logrotate.d/puppet 2.6.4-2 puppet -- "$@" dpkg-maintscript-helper rm_conffile \ /etc/logcheck/ignore.d.server/puppet 2.6.4-2 puppet -- "$@" dpkg-maintscript-helper rm_conffile \ /etc/emacs/site-start.d/50puppet-mode-init.el 2.6.4-2 puppet -- "$@" fi #DEBHELPER# puppet-5.5.10/ext/debian/puppetmaster-common.install0000644005276200011600000000007413417161721022444 0ustar jenkinsjenkinsdebian/fileserver.conf etc/puppet conf/auth.conf etc/puppet puppet-5.5.10/ext/debian/puppetmaster-common.manpages0000644005276200011600000000005613417161721022571 0ustar jenkinsjenkinsman/man8/puppet-ca.8 man/man8/puppet-master.8 puppet-5.5.10/ext/debian/puppetmaster-common.postinst0000755005276200011600000000011213417161721022655 0ustar jenkinsjenkins#!/bin/sh set -e rm -f /etc/init.d/puppetqd rm -f /etc/default/puppetqd puppet-5.5.10/ext/debian/puppetmaster-passenger.dirs0000644005276200011600000000023113417161721022431 0ustar jenkinsjenkinsusr/share/puppet/rack/puppetmasterd usr/share/puppet/rack/puppetmasterd/public usr/share/puppet/rack/puppetmasterd/tmp usr/share/puppetmaster-passenger/ puppet-5.5.10/ext/debian/puppetmaster-passenger.postinst0000644005276200011600000001461313417161721023364 0ustar jenkinsjenkins#!/bin/sh set -e sitename="puppetmaster" apache2_version="$(dpkg-query --showformat='${Version}\n' --show apache2)" # The debian provided a2* utils in Apache 2.4 uses "site name" as # argument, while the version in Apache 2.2 uses "file name". # # For added fun, the Apache 2.4 version requires files to have a # ".conf" suffix, but this must be stripped when using it as argument # for the a2* utilities. # # This will end in tears… # Can be removed when we only support apache >= 2.4 apache2_puppetmaster_sitename() { if dpkg --compare-versions "$apache2_version" gt "2.4~"; then echo "${sitename}.conf" else echo "${sitename}" fi } # Can be removed when we only support apache >= 2.4 restart_apache2() { if [ -x "/etc/init.d/apache2" ]; then # Seems that a restart is needed. reload breaks ssl apparently. if [ -x "`which invoke-rc.d 2>/dev/null`" ]; then invoke-rc.d apache2 restart || exit $? else /etc/init.d/apache2 restart || exit $? fi fi } # We may need to update the passenger directives in the apache vhost because # RailsAutoDetect and RackAutoDetect were removed in passenger 4.0.0 # see http://www.modrails.com/documentation/Users%20guide%20Apache.html#_railsautodetect_rackautodetect_and_wsgiautodetect update_vhost_for_passenger4() { # Get passenger version from dpkg. # This will end in tears… passenger_version="$(dpkg-query --showformat='${Version}\n' --show libapache2-mod-passenger)" if dpkg --compare-versions "$passenger_version" gt "4.0~"; then sed -r -i \ -e "/RailsAutoDetect/d" \ -e "/RackAutoDetect/d" \ $tempfile fi } # In Apache 2.2, if either the SSLCARevocationFile or SSLCARevocationPath # directives were specified then the specified file(s) would be checked when # establishing an SSL connection. Apache 2.4+ the SSLCARevocationCheck directive # was added to control how CRLs were checked when verifying a connection and had # a default value of none. This means that Apache defaults to ignoring CRLs even # if paths are specified to CRL files. # # This function automatically uncomments the SSLCARevocationCheck directive when # the currently installed version of Apache is 2.4. update_vhost_for_apache24() { if dpkg --compare-versions "$apache2_version" gt "2.4~"; then sed -r -i \ -e "/# SSLCARevocationCheck/s/# //" \ $tempfile fi } # Update an existing vhost definition with the SSLCARevocationCheck directive # on Apache 2.4+. This scans an existing vhost file for the SSLCARevocationCheck # directive and adds it to the file after the SSLCARevocationFile directive. # # See https://tickets.puppetlabs.com/browse/PUP-2533 for more information. update_vhost_for_apache24_upgrade() { APACHE2_SITE_FILE="/etc/apache2/sites-available/$(apache2_puppetmaster_sitename)" if dpkg --compare-versions "$apache2_version" gt "2.4~"; then if ! grep -q "^[[:space:]]*SSLCARevocationCheck" $APACHE2_SITE_FILE ; then tempfile=$(mktemp) sed -r \ -e "/SSLCARevocationFile/a\\ SSLCARevocationCheck chain" \ $APACHE2_SITE_FILE > $tempfile mv $tempfile $APACHE2_SITE_FILE fi fi } create_initial_puppetmaster_vhost() { # Check that puppet master --configprint works properly # If it doesn't the following steps to update the vhost will produce a very unhelpful and broken vhost if [ $(puppet master --configprint all 2>&1 | grep "Could not parse" | wc -l) != "0" ]; then echo "Puppet config print not working properly, exiting" exit 1 fi # Initialize puppetmaster CA and generate the master certificate # only if the host doesn't already have any puppet ssl certificate. # The ssl key and cert need to be available (eg generated) before # apache2 is configured and started since apache2 ssl configuration # uses the puppetmaster ssl files. if [ ! -e "$(puppet master --configprint hostcert)" ]; then puppet cert generate $(puppet master --configprint certname) fi # Setup apache2 configuration files APACHE2_SITE_FILE="/etc/apache2/sites-available/$(apache2_puppetmaster_sitename)" if [ ! -e "${APACHE2_SITE_FILE}" ]; then tempfile=$(mktemp) sed -r \ -e "s|(SSLCertificateFile\s+).+$|\1$(puppet master --configprint hostcert)|" \ -e "s|(SSLCertificateKeyFile\s+).+$|\1$(puppet master --configprint hostprivkey)|" \ -e "s|(SSLCACertificateFile\s+).+$|\1$(puppet master --configprint localcacert)|" \ -e "s|(SSLCertificateChainFile\s+).+$|\1$(puppet master --configprint localcacert)|" \ -e "s|(SSLCARevocationFile\s+).+$|\1$(puppet master --configprint cacrl)|" \ -e "s|DocumentRoot /etc/puppet/rack/public|DocumentRoot /usr/share/puppet/rack/puppetmasterd/public|" \ -e "s|||" \ /usr/share/puppetmaster-passenger/apache2.site.conf.tmpl > $tempfile update_vhost_for_passenger4 update_vhost_for_apache24 mv $tempfile "${APACHE2_SITE_FILE}" fi # Enable needed modules a2enmod ssl a2enmod headers a2ensite ${sitename} restart_apache2 } update_existing_puppetmaster_vhost() { if dpkg --compare-versions "${1}" lt "3.6.2~"; then update_vhost_for_apache24_upgrade fi } if [ "$1" = "configure" ]; then # Change the owner of the rack config.ru to be the puppet user # because passenger will suid to that user, see #577366 if ! dpkg-statoverride --list /usr/share/puppet/rack/puppetmasterd/config.ru >/dev/null 2>&1 then dpkg-statoverride --update --add puppet puppet 0644 /usr/share/puppet/rack/puppetmasterd/config.ru fi # Setup puppetmaster passenger vhost if [ "$2" = "" ]; then create_initial_puppetmaster_vhost else update_existing_puppetmaster_vhost $2 fi # Fix CRL file on upgrade to use the CA crl file instead of the host crl. if dpkg --compare-versions "$2" lt-nl "2.6.1-1"; then if [ -e /etc/apache2/sites-available/puppetmaster ]; then sed -r -i 's|SSLCARevocationFile[[:space:]]+/var/lib/puppet/ssl/crl.pem$|SSLCARevocationFile /var/lib/puppet/ssl/ca/ca_crl.pem|' /etc/apache2/sites-available/puppetmaster restart_apache2 fi fi fi #DEBHELPER# puppet-5.5.10/ext/debian/puppetmaster-passenger.postrm0000644005276200011600000000335413417161721023025 0ustar jenkinsjenkins#!/bin/sh set -e sitename="puppetmaster" # The debian provided a2* utils in Apache 2.4 uses "site name" as # argument, while the version in Apache 2.2 uses "file name". # # For added fun, the Apache 2.4 version requires files to have a # ".conf" suffix, but this must be stripped when using it as argument # for the a2* utilities. # # This will end in tears… # Can be removed when we only support apache >= 2.4 apache2_puppetmaster_sitename() { apache2_version="$(dpkg-query --showformat='${Version}\n' --show apache2)" if dpkg --compare-versions "$apache2_version" gt "2.4~"; then echo "${sitename}.conf" else echo "${sitename}" fi } # Can be removed when we only support apache >= 2.4 restart_apache2() { if [ -x "/etc/init.d/apache2" ]; then # Seems that a restart is needed. reload breaks ssl apparently. if [ -x "`which invoke-rc.d 2>/dev/null`" ]; then invoke-rc.d apache2 restart || exit $? else /etc/init.d/apache2 restart || exit $? fi fi } case "$1" in purge) if dpkg-statoverride --list /usr/share/puppet/rack/puppetmasterd/config.ru >/dev/null 2>&1 then dpkg-statoverride --remove /usr/share/puppet/rack/puppetmasterd/config.ru fi # Remove the puppetmaster site configuration on purge rm -f /etc/apache2/sites-available/$(apache2_puppetmaster_sitename) ;; remove) # Disable the puppetmaster apache2 site configuration on package removal a2dissite ${sitename} restart_apache2 ;; upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) ;; *) echo "postrm called with unknown argument \`$1'" >&2 exit 1 esac #DEBHELPER# exit 0 puppet-5.5.10/ext/debian/puppetmaster.README.debian0000644005276200011600000000154213417161721021667 0ustar jenkinsjenkinsBy default, the puppetmaster package is configured in /etc/default/puppetmaster to use the WEBrick server included in the application. This server is single-threaded, very slow, and tends to fall over when you get close to 30 nodes. You will start to see connection-reset or End-of-file errors when this happens. For production deployments, use a server that can scale better than WEBrick, such as Passenger (aka mod_rails), with a front-end web-proxy such as Apache. To run Passenger with Apache, install the puppetmaster-passenger package. It automatically sets up an apache2 virtual host that runs puppetmaster under Passenger. For more information, see https://puppet.com/docs/puppet/latest/passenger.html. For information about the Puppet-designed Puppet Server, which deprecates the Passenger/Apache stack, see https://puppet.com/docs/puppetserver/latest/. puppet-5.5.10/ext/debian/puppetmaster.default0000644005276200011600000000064213417161721021135 0ustar jenkinsjenkins# Defaults for puppetmaster - sourced by /etc/init.d/puppetmaster # Enable puppetmaster service? # Setting this to "yes" allows the puppet master service to run. # Setting this to "no" keeps the puppet master service from running. # # If you are using Passenger, you should have this set to "no." START=yes # Startup options DAEMON_OPTS="" # On what port should the puppet master listen? (default: 8140) PORT=8140 puppet-5.5.10/ext/debian/puppetmaster.init0000644005276200011600000000736113417161721020461 0ustar jenkinsjenkins#! /bin/sh ### BEGIN INIT INFO # Provides: puppetmaster # Required-Start: $network $named $remote_fs $syslog # Required-Stop: $network $named $remote_fs $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 ### END INIT INFO PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin DAEMON=/usr/bin/puppet DAEMON_OPTS="" NAME=master DESC="puppet master" test -x $DAEMON || exit 0 [ -r /etc/default/puppetmaster ] && . /etc/default/puppetmaster . /lib/lsb/init-functions if [ ! -d /var/run/puppet ]; then mkdir -p /var/run/puppet fi chown puppet:puppet /var/run/puppetlabs mongrel_warning="The mongrel servertype is no longer built-in to Puppet. It appears as though mongrel is being used, as the number of puppetmasters is greater than one. Starting the puppetmaster service will not behave as expected until this is resolved. Only the first port has been used in the service, and only one puppetmaster has been started. These settings are defined in /etc/default/puppetmaster" # Warn about removed and unsupported mongrel servertype if ([ -n "$PUPPETMASTERS" ] && [ ${PUPPETMASTERS} -gt 1 ]) || [ "${SERVERTYPE}" = "mongrel" ]; then echo $mongrel_warning echo fi is_true() { if [ "x$1" = "xtrue" -o "x$1" = "xyes" -o "x$1" = "x0" ] ; then return 0 else return 1 fi } start_puppet_master() { if is_true "$START" ; then start-stop-daemon --start --quiet --pidfile /var/run/puppetlabs/${NAME}.pid \ --startas $DAEMON -- $NAME $DAEMON_OPTS --masterport=$PORT else echo "" echo "puppetmaster not configured to start, please edit /etc/default/puppetmaster to enable" fi } stop_puppet_master() { start-stop-daemon --stop --retry TERM/10/KILL/5 --quiet --oknodo --pidfile /var/run/puppetlabs/${NAME}.pid } status_puppet_master() { if ( ! type status_of_proc > /dev/null 2>&1 ) ; then status_of_proc() { local pidfile daemon name status pidfile= OPTIND=1 while getopts p: opt ; do case "$opt" in p) pidfile="$OPTARG";; esac done shift $(($OPTIND - 1)) if [ -n "$pidfile" ]; then pidfile="-p $pidfile" fi daemon="$1" name="$2" status="0" pidofproc $pidfile $daemon >/dev/null || status="$?" if [ "$status" = 0 ]; then log_success_msg "$name is running" return 0 elif [ "$status" = 4 ]; then log_failure_msg "could not access PID file for $name" return $status else log_failure_msg "$name is not running" return $status fi } fi if is_true "$START" ; then status_of_proc -p "/var/run/puppetlabs/${NAME}.pid" "${DAEMON}" "${NAME}" else echo "" echo "puppetmaster not configured to start" status_of_proc -p "/var/run/puppetlabs/${NAME}.pid" "${DAEMON}" "${NAME}" fi } case "$1" in start) log_begin_msg "Starting $DESC" start_puppet_master log_end_msg $? ;; stop) log_begin_msg "Stopping $DESC" stop_puppet_master log_end_msg $? ;; reload) # Do nothing, as Puppetmaster rechecks its config automatically ;; status) status_puppet_master ;; restart|force-reload) log_begin_msg "Restarting $DESC" stop_puppet_master start_puppet_master log_end_msg $? ;; *) echo "Usage: $0 {start|stop|status|restart|force-reload}" >&2 exit 1 ;; esac # vim: tabstop=4:softtabstop=4:shiftwidth=4:expandtab puppet-5.5.10/ext/debian/puppetmaster.lintian-overrides0000644005276200011600000000024013417161721023141 0ustar jenkinsjenkins# Man pages are automatically generated, not much to do here puppetmaster binary: manpage-has-bad-whatis-entry puppetmaster binary: manpage-has-errors-from-man puppet-5.5.10/ext/debian/puppetmaster.postinst0000644005276200011600000000070213417161721021371 0ustar jenkinsjenkins#!/bin/sh set -e # Remove renamed configuration files which are now handled by other # packages if dpkg-maintscript-helper supports rm_conffile 2>/dev/null; then dpkg-maintscript-helper rm_conffile \ /etc/logrotate.d/puppetmaster 2.7.16-1puppetlabs1 puppetmaster -- "$@" # For systems with dpkg < 1.15.7-2 else if [ -f /etc/logrotate.d/puppetmaster ]; then rm /etc/logrotate.d/puppetmaster || true fi fi #DEBHELPER# puppet-5.5.10/ext/debian/puppetmaster.postrm0000644005276200011600000000004213417161721021027 0ustar jenkinsjenkins#!/bin/sh -e #DEBHELPER# exit 0 puppet-5.5.10/ext/debian/puppetmaster.preinst0000644005276200011600000000070413417161721021174 0ustar jenkinsjenkins#!/bin/sh set -e # Remove renamed configuration files which are now handled by other # packages if dpkg-maintscript-helper supports rm_conffile 2>/dev/null; then dpkg-maintscript-helper rm_conffile \ /etc/logrotate.d/puppetmaster 2.7.16-1puppetlabs1 puppetmaster -- "$@" # For systems with dpkg < 1.15.7-2 else if [ -f /etc/logrotate.d/puppetmaster ]; then rm /etc/logrotate.d/puppetmaster || true fi fi #DEBHELPER# puppet-5.5.10/ext/debian/rules0000755005276200011600000001027113417161721016115 0ustar jenkinsjenkins#!/usr/bin/make -f # -*- makefile -*- # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 INSTALL=install -Dp prefix := $(CURDIR)/debian/tmp bindir := $(prefix)/$(shell /usr/bin/ruby -rrbconfig -e 'puts RbConfig::CONFIG["bindir"]') libdir := $(prefix)/usr/lib localstatedir := $(prefix)/var rubylibdir := $(shell /usr/bin/ruby -rrbconfig -e 'puts RbConfig::CONFIG["vendordir"]') sysconfdir := $(prefix)/etc pkgconfdir := $(sysconfdir)/puppet ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS))) CFLAGS += -O0 else CFLAGS += -O2 endif configure: configure-stamp configure-stamp: dh_testdir touch configure-stamp build-arch: build build-indep: build build: build-stamp build-stamp: configure-stamp dh_testdir touch build-stamp clean: dh_testdir dh_testroot rm -f build-stamp configure-stamp rm -f debian/puppet-common.logcheck.ignore.server dh_clean install: build dh_testdir dh_testroot dh_clean -k dh_installdirs $(CURDIR)/install.rb --destdir=debian/tmp --bindir=/usr/bin --sitelibdir=$(rubylibdir) --ruby=/usr/bin/ruby # strip executable bit from all the non-executable files. find $(prefix)/$(rubylibdir) -type f -perm /u+x,g+x,o+x -exec chmod a-x {} \; # fix the permissions on all of the directories find $(prefix)/$(rubylibdir) -type d -exec chmod 755 {} \; # Vim auto-syntax-highlighting stuff $(INSTALL) -m0644 ext/vim/syntax/puppet.vim \ $(CURDIR)/debian/vim-puppet/usr/share/vim/addons/syntax/ $(INSTALL) -m0644 ext/vim/ftdetect/puppet.vim \ $(CURDIR)/debian/vim-puppet/usr/share/vim/addons/ftdetect/ $(INSTALL) -m0644 ext/vim/indent/puppet.vim \ $(CURDIR)/debian/vim-puppet/usr/share/vim/addons/indent/ $(INSTALL) -m0644 ext/vim/ftplugin/puppet.vim \ $(CURDIR)/debian/vim-puppet/usr/share/vim/addons/ftplugin/ $(INSTALL) -m0644 debian/vim-puppet.yaml \ $(CURDIR)/debian/vim-puppet/usr/share/vim/registry/ # Emacs mode $(INSTALL) -m0644 ext/emacs/puppet-mode.el \ $(CURDIR)/debian/puppet-el/usr/share/emacs/site-lisp/puppet-mode.el # Install the config.ru $(INSTALL) -m0644 ext/rack/config.ru \ $(CURDIR)/debian/puppetmaster-passenger/usr/share/puppet/rack/puppetmasterd # Install apache2 site configuration template $(INSTALL) -m0644 ext/rack/example-passenger-vhost.conf \ $(CURDIR)/debian/puppetmaster-passenger/usr/share/puppetmaster-passenger/apache2.site.conf.tmpl # Install example environment README $(INSTALL) -m0644 ext/README.environment \ $(CURDIR)/debian/puppet-common/etc/puppet/environments/example_env/README.environment # Add ext directory cp -pr ext $(CURDIR)/debian/puppet-common/usr/share/puppet # Remove misc packaging artifacts not applicable to debian rm -rf $(CURDIR)/debian/puppet-common/usr/share/puppet/ext/gentoo rm -rf $(CURDIR)/debian/puppet-common/usr/share/puppet/ext/freebsd rm -rf $(CURDIR)/debian/puppet-common/usr/share/puppet/ext/solaris rm -rf $(CURDIR)/debian/puppet-common/usr/share/puppet/ext/suse rm -rf $(CURDIR)/debian/puppet-common/usr/share/puppet/ext/windows rm -rf $(CURDIR)/debian/puppet-common/usr/share/puppet/ext/redhat rm -rf $(CURDIR)/debian/puppet-common/usr/share/puppet/ext/ips rm -rf $(CURDIR)/debian/puppet-common/usr/share/puppet/ext/osx rm -rf $(CURDIR)/debian/puppet-common/usr/share/puppet/ext/build_defaults.yaml rm -rf $(CURDIR)/debian/puppet-common/usr/share/puppet/ext/project_data.yaml dh_installexamples -p puppet-common examples/* $(INSTALL) -d -m0775 $(pkgconfdir)/templates $(INSTALL) -d -m0775 $(pkgconfdir)/modules # Logcheck rules. Gee I wish you could specify a file to source # in dh_installlogcheck. ln ext/logcheck/puppet debian/puppet-common.logcheck.ignore.server # Build architecture-dependent files here. binary-arch: build install # Build architecture-independent files here. binary-indep: build install dh_testdir dh_testroot dh_install -i dh_installdocs -i dh_installemacsen dh_installlogcheck dh_installman dh_installinit -ppuppetmaster dh_installinit -ppuppet --error-handler=true -- defaults 21 dh_installlogrotate -i dh_lintian -i dh_compress -i dh_fixperms -i dh_installdeb -i dh_shlibdeps -i dh_gencontrol -i dh_md5sums -i dh_builddeb -i binary: binary-indep binary-arch .PHONY: build clean binary-indep binary-arch binary install configure puppet-5.5.10/ext/debian/source/0000755005276200011600000000000013417162176016341 5ustar jenkinsjenkinspuppet-5.5.10/ext/debian/source/format0000644005276200011600000000001413417161721017542 0ustar jenkinsjenkins3.0 (quilt) puppet-5.5.10/ext/debian/source/options0000644005276200011600000000002413417161721017746 0ustar jenkinsjenkinssingle-debian-patch puppet-5.5.10/ext/debian/vim-puppet.README.Debian0000644005276200011600000000061313417161721021202 0ustar jenkinsjenkinsDear user, this package provides the vim addon puppet, but it is not enabled per default. If you want to enable it for your user account just execute vim-addons install puppet Similarly, to enable it for all users of this system just execute (as root): vim-addons -w install puppet vim-addons is provided by the vim-addon-manager package, have a look at its manpage for more information. puppet-5.5.10/ext/debian/vim-puppet.dirs0000644005276200011600000000021313417161721020021 0ustar jenkinsjenkinsusr/share/vim/registry usr/share/vim/addons/syntax usr/share/vim/addons/ftdetect usr/share/vim/addons/indent usr/share/vim/addons/ftplugin puppet-5.5.10/ext/debian/vim-puppet.yaml0000644005276200011600000000023713417161721020030 0ustar jenkinsjenkinsaddon: puppet description: "Syntax highlighting for puppet" files: - ftdetect/puppet.vim - syntax/puppet.vim - indent/puppet.vim - ftplugin/puppet.vim puppet-5.5.10/ext/debian/watch0000644005276200011600000000014113417161721016061 0ustar jenkinsjenkinsversion=3 http://pkg-ruby-extras.alioth.debian.org/cgi-bin/gemwatch/puppet .*/puppet-(.*).tar.gz puppet-5.5.10/ext/debian/copyright0000644005276200011600000004210513417161721016771 0ustar jenkinsjenkinsFormat-Specification: http://svn.debian.org/wsvn/dep/web/deps/dep5.mdwn?op=file&rev=135 Name: Puppet Maintainer: Michael Stahnke , Andrew Pollock , Micah Anderson , Nigel Kersten , Stig Sandbeck Mathisen Source: git://github.com/puppetlabs/puppet.git, https://puppet.com/docs/puppet/latest/install_pre.html Copyright: 2005-2015 Puppet Labs License: Apache 2.0 Files: debian/* Copyright: Andrew Pollock , Jamie Wilkinson , Matthew Palmer , Micah Anderson , Nigel Kersten , Stig Sandbeck Mathisen , Thom May License: GPL-2, ASL 2.0 Files: install.rb Copyright: Austin Ziegler License: GPL-2+ Files: conf/gentoo/init.d/puppet* Copyright: Gentoo Foundation License: GPL-2 Files: lib/puppet/util/rdoc/generators/template/puppet/puppet.rb Copyright: The FaerieMUD Consortium. License: CC-BY-1.0 This work is licensed under the Creative Commons Attribution License. To view a copy of this license, visit https://creativecommons.org/licenses/by/1.0/ or send a letter to Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA. Files: conf/osx/createpackage.sh Copyright: Google Inc. License: Apache Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at . https://www.apache.org/licenses/LICENSE-2.0 . Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License . On Debian systems, the full text of the Apache License can be found at "/usr/share/common-licenses/Apache-2.0" Files: lib/puppet/external/event-loop/* Copyright: Daniel Brockman License: GPL-2+ Files: lib/puppet/external/nagios/parser.rb Copyright: Minero Aoki License: other This program is free software. You can distribute/modify this program under the same terms of ruby. . As a special exception, when this code is copied by Racc into a Racc output file, you may use that output file without restriction. Files: lib/puppet/network/http_server/mongrel.rb Copyright: Manuel Holtgrewe, Luke Kanies License: MIT Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: . The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. . THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Files: lib/puppet/provider/augeas/augeas.rb, lib/puppet/type/augeas.rb Copyright: Red Hat Inc. License: GPL-2+ Files: lib/puppet/provider/mcx/mcxcontent.rb Copyright: Jeff McCune License: GPL-2+ Files: lib/puppet/type/mcx.rb, spec/unit/type/mcx.rb Copyright: Jeffrey J McCune. License: GPL-2+ Files: lib/puppet/provider/nameservice/directoryservice.rb Copyright: Jeff McCune License: GPL-2 Files: lib/puppet/provider/package/pkgdmg.rb Copyright: Jeff McCune Jeff McCune License: GPL-2 Files: test/ral/providers/service/debian.rb Copyright: David Schmitt License: missing Files: examples/modules/sample-module/lib/puppet/parser/functions/hostname_to_dn.rb Copyright: David Schmitt License: BSD Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the Author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. . THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. License: GPL-2 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation (version 2 of the License) . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston MA 02110-1301 USA . On Debian systems, the full text of the GNU General Public License version 2 can be found in the file "/usr/share/common-licenses/GPL-2". License: GPL-2+ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. . This file is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. . On Debian systems, the full text of the GNU General Public License version 2 can be found in the file "/usr/share/common-licenses/GPL-2". License: CC-BY-1.0 THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE IS PROHIBITED. . BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. . 1. Definitions . "Collective Work" means a work, such as a periodical issue, anthology or encyclopedia, in which the Work in its entirety in unmodified form, along with a number of other contributions, constituting separate and independent works in themselves, are assembled into a collective whole. A work that constitutes a Collective Work will not be considered a Derivative Work (as defined below) for the purposes of this License. "Derivative Work" means a work based upon the Work or upon the Work and other pre-existing works, such as a translation, musical arrangement, dramatization, fictionalization, motion picture version, sound recording, art reproduction, abridgment, condensation, or any other form in which the Work may be recast, transformed, or adapted, except that a work that constitutes a Collective Work will not be considered a Derivative Work for the purpose of this License. . "Licensor" means the individual or entity that offers the Work under the terms of this License. "Original Author" means the individual or entity who created the Work. . "Work" means the copyrightable work of authorship offered under the terms of this License. "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. . 2. Fair Use Rights. Nothing in this license is intended to reduce, limit, or restrict any rights arising from fair use, first sale or other limitations on the exclusive rights of the copyright owner under copyright law or other applicable laws. . 3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: . a. to reproduce the Work, to incorporate the Work into one or more Collective Works, and to reproduce the Work as incorporated in the Collective Works; . b. to create and reproduce Derivative Works; . c. to distribute copies or phonorecords of, display publicly, perform publicly, and perform publicly by means of a digital audio transmission the Work including as incorporated in Collective Works; . d. to distribute copies or phonorecords of, display publicly, perform publicly, and perform publicly by means of a digital audio transmission Derivative Works; . The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. All rights not expressly granted by Licensor are hereby reserved. . 4. Restrictions. . The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: . a. You may distribute, publicly display, publicly perform, or publicly digitally perform the Work only under the terms of this License, and You must include a copy of, or the Uniform Resource Identifier for, this License with every copy or phonorecord of the Work You distribute, publicly display, publicly perform, or publicly digitally perform. You may not offer or impose any terms on the Work that alter or restrict the terms of this License or the recipients' exercise of the rights granted hereunder. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties. You may not distribute, publicly display, publicly perform, or publicly digitally perform the Work with any technological measures that control access or use of the Work in a manner inconsistent with the terms of this License Agreement. The above applies to the Work as incorporated in a Collective Work, but this does not require the Collective Work apart from the Work itself to be made subject to the terms of this License. If You create a Collective Work, upon notice from any Licensor You must, to the extent practicable, remove from the Collective Work any reference to such Licensor or the Original Author, as requested. If You create a Derivative Work, upon notice from any Licensor You must, to the extent practicable, remove from the Derivative Work any reference to such Licensor or the Original Author, as requested. . b. If you distribute, publicly display, publicly perform, or publicly digitally perform the Work or any Derivative Works or Collective Works, You must keep intact all copyright notices for the Work and give the Original Author credit reasonable to the medium or means You are utilizing by conveying the name (or pseudonym if applicable) of the Original Author if supplied; the title of the Work if supplied; in the case of a Derivative Work, a credit identifying the use of the Work in the Derivative Work (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). Such credit may be implemented in any reasonable manner; provided, however, that in the case of a Derivative Work or Collective Work, at a minimum such credit will appear where any other comparable authorship credit appears and in a manner at least as prominent as such other comparable authorship credit. . 5. Representations, Warranties and Disclaimer . a. By offering the Work for public release under this License, Licensor represents and warrants that, to the best of Licensor's knowledge after reasonable inquiry: . i. Licensor has secured all rights in the Work necessary to grant the license rights hereunder and to permit the lawful exercise of the rights granted hereunder without You having any obligation to pay any royalties, compulsory license fees, residuals or any other payments; . ii. The Work does not infringe the copyright, trademark, publicity rights, common law rights or any other right of any third party or constitute defamation, invasion of privacy or other tortious injury to any third party. . b. EXCEPT AS EXPRESSLY STATED IN THIS LICENSE OR OTHERWISE AGREED IN WRITING OR REQUIRED BY APPLICABLE LAW, THE WORK IS LICENSED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES REGARDING THE CONTENTS OR ACCURACY OF THE WORK. . 6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, AND EXCEPT FOR DAMAGES ARISING FROM LIABILITY TO A THIRD PARTY RESULTING FROM BREACH OF THE WARRANTIES IN SECTION 5, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. . 7. Termination . a. This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Derivative Works or Collective Works from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. . b. Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. . 8. Miscellaneous . Each time You distribute or publicly digitally perform the Work or a Collective Work, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. . Each time You distribute or publicly digitally perform a Derivative Work, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License. . If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. . No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. . This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You. puppet-5.5.10/ext/debian/changelog0000644005276200011600000012462313417162177016724 0ustar jenkinsjenkinspuppet (5.5.10-1puppetlabs1) hardy lucid natty oneiric unstable sid squeeze wheezy precise; urgency=low * Update to version 5.5.10-1puppetlabs1 -- Puppet Labs Release Mon, 14 Jan 2019 19:35:27 +0000 puppet (3.2.3-0.1rc0puppetlabs1) lucid unstable sid squeeze wheezy precise quantal raring; urgency=low * Update ruby-rgen dependency to 0.6.5 -- Matthaus Owens Thu, 27 Jun 2013 14:45:14 +0000 puppet (3.2.0-0.1rc0puppetlabs1) lucid oneiric precise unstable sid squeeze wheezy precise; urgency=low * Add ruby-rgen dependency for new parser in Puppet 3.2 -- Matthaus Owens Fri, 12 Apr 2013 14:45:14 +0000 puppet (3.1.0-0.1rc1puppetlabs1) lucid natty oneiric precise unstable sid squeeze wheezy precise; urgency=low * Add extlookup2hiera manpage to puppet-common.manpages -- Matthaus Owens Fri, 25 Jan 2013 14:45:14 +0000 puppet (2.7.19-1puppetlabs2) hardy jaunty karmic lucid maverick natty oneiric unstable lenny sid squeeze wheezy precise; urgency=low * (#16086) Add version parameter to rm_conffile call in puppetmaster preinst and postinst -- Will Hopper Thu, 23 Aug 2012 12:00:14 +0000 puppet (2.7.19-1puppetlabs1) hardy jaunty karmic lucid maverick natty oneiric unstable lenny sid squeeze wheezy precise; urgency=low * Imported Upstream version 2.7.19 -- Moses Mendoza Tue, 21 Aug 2012 15:09:14 +0000 puppet (2.7.19-0.1rc3puppetlabs1) hardy jaunty karmic lucid maverick natty oneiric unstable lenny sid squeeze wheezy precise; urgency=low * Imported Upstream version 2.7.19rc3 -- Moses Mendoza Tue, 14 Aug 2012 13:00:14 +0000 puppet (2.7.19-0.1rc2puppetlabs1) hardy jaunty karmic lucid maverick natty oneiric unstable lenny sid squeeze wheezy precise; urgency=low * Imported Upstream version 2.7.19rc2 -- Moses Mendoza Tue, 7 Aug 2012 16:04:14 +0000 puppet (2.7.19-0.1rc1puppetlabs1) hardy jaunty karmic lucid maverick natty oneiric unstable lenny sid squeeze wheezy precise; urgency=low * Imported Upstream version 2.7.19rc1 -- Moses Mendoza Wed, 1 Aug 2012 16:32:14 +0000 puppet (2.7.18-1puppetlabs1) hardy jaunty karmic lucid maverick natty oneiric unstable lenny sid squeeze wheezy precise; urgency=low * Imported Upstream version 2.7.18 -- Moses Mendoza Tue, 10 Jun 2012 14:02:14 +0000 puppet (2.7.17-1puppetlabs1) hardy jaunty karmic lucid maverick natty oneiric unstable lenny sid squeeze wheezy precise; urgency=low * Imported Upstream version 2.7.17 -- Matthaus Litteken Tue, 19 Jun 2012 23:43:14 +0000 puppet (2.7.16-2puppetlabs1) hardy jaunty karmic lucid maverick natty oneiric unstable lenny sid squeeze wheezy precise; urgency=low * Combine logrotate configurations for puppetmaster and puppet to resolve duplicate log entry errors -- William Hopper Mon, 18 Jun 2012 17:33:00 +0000 puppet (2.7.16-1puppetlabs1) hardy jaunty karmic lucid maverick natty oneiric unstable lenny sid squeeze wheezy precise; urgency=low * Imported Upstream version 2.7.16 -- Matthaus Litteken Wed, 13 Jun 2012 21:16:17 +0000 puppet (2.7.16-0.1rc1puppetlabs1) hardy jaunty karmic lucid maverick natty oneiric unstable lenny sid squeeze wheezy precise; urgency=low * Imported Upstream version 2.7.16rc1 -- Matthaus Litteken Wed, 06 Jun 2012 23:19:24 +0000 puppet (2.7.15-0.1rc4puppetlabs1) hardy jaunty karmic lucid maverick natty oneiric unstable lenny sid squeeze wheezy precise; urgency=low * Imported Upstream version 2.7.15rc4 -- Matthaus Litteken Fri, 01 Jun 2012 22:20:21 +0000 puppet (2.7.15-0.1rc1puppetlabs1) hardy jaunty karmic lucid maverick natty oneiric unstable lenny sid squeeze wheezy precise; urgency=low * Imported Upstream version 2.7.15rc1 -- Moses Mendoza Tue, 15 May 2012 17:30:38 +0000 puppet (2.7.14-1puppetlabs1) hardy jaunty karmic lucid maverick natty oneiric unstable lenny sid squeeze wheezy precise; urgency=low * Imported Upstream version 2.7.14 -- Moses Mendoza Wed, 02 May 2012 12:28:38 +0000 puppet (2.7.13-1puppetlabs2) hardy jaunty karmic lucid maverick natty oneiric unstable lenny sid squeeze wheezy precise; urgency=low * (#14080) Add suggests on ruby-activerecord | libactiverecord-ruby -- Matthaus Litteken Tue, 01 May 2012 19:52:38 +0000 puppet (2.7.13-1puppetlabs1) hardy jaunty karmic lucid maverick natty oneiric unstable lenny sid squeeze wheezy precise; urgency=low * Imported Upstream version 2.7.13 -- Matthaus Litteken Tue, 10 Apr 2012 20:38:23 +0000 puppet (2.7.12-1puppetlabs1) hardy jaunty karmic lucid maverick natty oneiric unstable lenny sid squeeze wheezy precise; urgency=low * Imported Upstream version 2.7.12 -- Michael Stahnke Mon, 12 Mar 2012 16:49:40 +0000 puppet (2.7.11-1puppetlabs1) hardy jaunty karmic lucid maverick natty oneiric unstable lenny sid squeeze wheezy precise; urgency=low * Imported Upstream version 2.7.11 * CVE-2012-1053/1054 -- Michael Stahnke Wed, 22 Feb 2012 01:11:43 +0000 puppet (2.7.10-1puppetlabs2) hardy jaunty karmic lucid maverick natty oneiric unstable lenny sid squeeze wheezy; urgency=low * (#12498) Add ext directory to puppet-common package. Enable apache2 headers module on passenger install -- Matthaus Litteken Fri, 10 Feb 2012 00:44:48 +0000 puppet (2.7.10-1puppetlabs1) hardy jaunty karmic lucid maverick natty oneiric unstable lenny sid squeeze wheezy; urgency=low * Imported Upstream version 2.7.10 -- Michael Stahnke Wed, 25 Jan 2012 23:33:34 +0000 puppet (2.7.9-1puppetlabs3) hardy jaunty karmic lucid maverick natty oneiric unstable lenny sid squeeze wheezy; urgency=low * (#11509, #11656) Fix puppetmaster-passenger to set up puppet ca with passenger correctly. -- Matthaus Litteken Tue, 2 Jan 2011 9:25:27 +0000 puppet (2.7.9-1puppetlabs2) hardy jaunty karmic lucid maverick natty oneiric unstable lenny sid squeeze wheezy; urgency=low * Fix puppet-passenger post-inst to use the correct puppet commands. -- Matthaus Litteken Tue, 20 Dec 2011 14:30:27 +0000 puppet (2.7.9-1puppetlabs1) hardy jaunty karmic lucid maverick natty oneiric unstable lenny sid squeeze wheezy; urgency=low * Release 2.7.9 -- Matthaus Litteken Fri, 9 Dec 2011 20:15:27 +0000 puppet (2.7.8-1puppetlabs1) hardy jaunty karmic lucid maverick natty oneiric unstable lenny sid squeeze wheezy; urgency=low * Release 2.7.8 -- Matthaus Litteken Thu, 8 Dec 2011 17:55:27 +0000 puppet (2.7.7-1puppetlabs2) hardy jaunty karmic lucid maverick natty oneiric unstable lenny sid squeeze wheezy; urgency=low * Update dependencies. Decrease sysv-rc to 2.86 and update gbp.conf to allow multiple dists per build. -- Matthaus Litteken Fri, 2 Dec 2011 11:55:27 +0000 puppet (2.7.7-1puppetlabs1) hardy jaunty karmic lucid maverick natty oneiric unstable lenny sid squeeze wheezy; urgency=low * Update to 2.7.7 -- Michael Stahnke Tue, 22 Nov 2011 00:07:27 +0000 puppet (2.7.6-1puppetlabs1) hardy jaunty karmic lucid maverick natty oneiric unstable lenny sid squeeze wheezy; urgency=low * Update to 2.7.6 - Includes fix for CVE-2011-3872 -- Michael Stahnke Sun, 23 Oct 2011 15:58:26 -0700 puppet (2.7.3-1puppetlabs1) lenny; urgency=low * Update to 2.7.3 - Ripped out etckeeper - s/Reductive/Puppet/g -- Michael Stahnke Sun, 25 Sep 2011 00:38:02 -0700 puppet (2.7.1-1ubuntu1) oneiric; urgency=low * Merge from debian unstable. Remaining changes: - debian/puppetmaster-passenger.postinst: Use cacrl instead of hostcrl to set the location of the CRL in apache2 configuration. Fix apache2 configuration on upgrade as well (LP: #641001) - move all puppet dependencies to puppet-common since all the code actually located in puppet-common. - move libagueas from a recommend to a dependency. -- Chuck Short Mon, 25 Jul 2011 01:00:37 +0100 puppet (2.7.1-1) UNRELEASED; urgency=low * New upstream version * Bump Standards-Version (no changes) * Adjust debian/source/options to allow for a VCS-generated patch * Tell adduser not to create /var/lib/puppet (Closes: #609896) * Use dpkg-statoverride to handle permissions * Allow the use of file-rc (Closes: #625638) * Use the pkg-ruby-extras watch service -- Stig Sandbeck Mathisen Sun, 17 Jul 2011 17:43:31 +0200 puppet (2.6.8-1ubuntu1) oneiric; urgency=low * Merge from debian unstable. Remaining changes: - debian/puppetmaster-passenger.postinst: Use cacrl instead of hostcrl to set the location of the CRL in apache2 configuration. Fix apache2 configuration on upgrade as well (LP: #641001) - move all puppet dependencies to puppet-common since all the code actually located in puppet-common. - move libagueas from a recommend to a dependency. -- Chuck Short Mon, 02 May 2011 12:37:51 +0100 puppet (2.6.8-1) unstable; urgency=low * New upstream version -- Stig Sandbeck Mathisen Thu, 28 Apr 2011 19:26:00 +0200 puppet (2.6.7-2) unstable; urgency=medium * Fix puppetmaster-passenger.postinst to get proper ssl configs (Closes: #620635) * Fix maintainer scripts ignoring errors -- Micah Anderson Mon, 11 Apr 2011 11:31:23 -0400 puppet (2.6.7-1) unstable; urgency=low * New upstream version -- Stig Sandbeck Mathisen Fri, 25 Mar 2011 21:10:07 +0100 puppet (2.6.6-1) unstable; urgency=low * New upstream release 2.6.6 -- Stig Sandbeck Mathisen Thu, 10 Mar 2011 08:42:00 +0100 puppet (2.6.6~rc1-1) experimental; urgency=low * New upstream release candidate -- Stig Sandbeck Mathisen Thu, 03 Mar 2011 21:00:31 +0100 puppet (2.6.5-1) unstable; urgency=low * New upstream version (Closes: #612894) * Remove renamed configuration files now handled by other packages (Closes: #564947, #611615) -- Stig Sandbeck Mathisen Tue, 01 Mar 2011 13:59:58 +0100 puppet (2.6.4-2ubuntu2) natty; urgency=low * debian/puppetmaster.default - fix remains of automated merge (LP: #726856) -- Andreas Moog Tue, 01 Mar 2011 14:04:06 +0100 puppet (2.6.4-2ubuntu1) natty; urgency=low * Merge from debian unstable. Remaining changes: - debian/puppetmaster-passenger.postinst: Use cacrl instead of hostcrl to set the location of the CRL in apache2 configuration. Fix apache2 configuration on upgrade as well (LP: #641001) - move all puppet dependencies to puppet-common since all the code actually located in puppet-common. - move libagueas from a recommend to a dependency. -- Chuck Short Tue, 08 Feb 2011 00:28:43 +0000 puppet (2.6.4-2) unstable; urgency=low * Release for unstable * Move puppetstoredconfigclean to puppetmaster-common, and set ruby1.8 as parser to match the rest of the puppet suite -- Stig Sandbeck Mathisen Mon, 07 Feb 2011 07:28:19 +0100 puppet (2.6.4-1) experimental; urgency=low [ Micah Anderson ] * Make puppetqd honor flags from /etc/default/puppetqd (Closes: #605510) * Remove the puppetqd PID file on stop (Closes: #605512) * Add ext/puppetstoredconfigclean to puppetmaster:/usr/sbin * Patch ext/logcheck/puppet to handle new puppet-master Compiled log lines (Closes: #602336) * Fix puppetqd initscript PID location * Fix /etc/default/puppetmaster comments to match new section headings * Fix puppetmaster/README.Debian to match new section headings * Fix Should-Start init header in puppet initscript [ Mathias Gug ] * New upstream version. [ Stig Sandbeck Mathisen ] * debian/puppetmaster.logrotate: send SIGUSR2 on log rotation (Closes: #602698) * puppet-common: Add versioned dependency on sysv-rc [ martin f krafft ] * Use update-rc.d enable/disable in the "debian" provider in the "service" type (Closes: #573551) -- Stig Sandbeck Mathisen Wed, 26 Jan 2011 16:10:56 +0100 puppet (2.6.3-1) experimental; urgency=low [ Mathias Gug ] * New upstream version. [ Stig Sandbeck Mathisen ] * debian/control: Adjust dependencies for puppet-testsuite, depend on puppet-common instead of puppet and puppetmaster -- Stig Sandbeck Mathisen Tue, 23 Nov 2010 10:40:31 +0100 puppet (2.6.3-0ubuntu1) natty; urgency=low * New upstream version. -- Mathias Gug Wed, 17 Nov 2010 13:30:18 -0500 puppet (2.6.3~rc3-0ubuntu1) natty; urgency=low * New upstream version -- Mathias Gug Fri, 12 Nov 2010 09:29:36 -0500 puppet (2.6.3~rc2-0ubuntu1) natty; urgency=low * New upstream version -- Mathias Gug Tue, 09 Nov 2010 17:47:53 -0500 puppet (2.6.3~rc1-0ubuntu1) natty; urgency=low * New upstream version * debian/control: - move all puppet dependencies to puppet-common since all the code is actually located in puppet-common. - move libaugeas from a recommend to a dependency. -- Mathias Gug Thu, 21 Oct 2010 12:52:13 -0400 puppet (2.6.1-1) experimental; urgency=low [ Mathias Gug ] * New upstream version: - Fix "Puppet standalone broken" (Closes: #594575) * test/lib/puppettest/fakes.rb: Fix puppettest to use puppet system library. * debian/puppetmaster-passenger.postinst: Use cacrl instead of hostcrl to set the location of the CRL in apache2 configuration. Fix apache2 configuration on upgrade as well (LP: #641001). * debian/control: - move all puppet dependencies to puppet-common since all the code is actually located in puppet-common. - move libaugeas from a recommend to a dependency. [ Stig Sandbeck Mathisen ] * Fix "require" path for puppet queue. * Add dependency on "facter" for "puppet-common" * Make sure the "puppet-common" package can be purged even when not fully installed (Closes: #596163) -- Stig Sandbeck Mathisen Sat, 02 Oct 2010 12:14:37 +0200 puppet (2.6.1-0ubuntu2) maverick; urgency=low * debian/puppetmaster-passenger.postinst: Use cacrl instead of hostcrl to set the location of the CRL in apache2 configuration. Fix apache2 configuration on upgrade as well (LP: #641001). -- Mathias Gug Tue, 21 Sep 2010 13:53:10 -0400 puppet (2.6.1-0ubuntu1) maverick; urgency=low [ Stig Sandbeck Mathisen ] * Add dependency on "facter" for "puppet-common" * Make sure the "puppet-common" package can be purged even when not fully installed (Closes: #596163) [ Mathias Gug ] * New upstream version. -- Mathias Gug Tue, 14 Sep 2010 12:05:29 -0400 puppet (2.6.1~rc4-0ubuntu1) maverick; urgency=low [ Mathias Gug ] * New upstream version: - Fix "Puppet standalone broken" (Closes: #594575) * test/lib/puppettest/fakes.rb: Fix puppettest to use puppet system library. [ Stig Sandbeck Mathisen ] * Fix "require" path for puppet queue. -- Mathias Gug Tue, 07 Sep 2010 10:44:22 -0400 puppet (2.6.1~rc3-1) experimental; urgency=low [ Mathias Gug ] * New upstream version: - fix config.ru file to run puppetmaster as a rack application. (Closes: #593557) * Fix test suite to run from a package install rather then from the source directory: + Rakefile: use system puppet.rb file to detect version. + spec/unit/application/apply_spec.rb: Fix test suite to use puppet system library. + spec/spec_helper.rb: disable gem. * Fix init service provider to correctly check the status of services using upstart jobs (Closes: #584481, LP: #551544). * etckeeper integration (Closes: #571127) [server-lucid-puppet-etckeeper-integration]: + debian/etckeeper-commit-post, debian/etckeeper-commit-pre: Call "etckeeper commit" before and after catalog runs. Silently bail out if etckeeper is not available. + debian/puppet.conf: Call out to the etckeeper hooks using the prerun_command and postrun_command hooks. + debian/rules: Install the etckeeper hook scripts in /etc/puppet. + debian/README.Debian: add note about etckeeper integration. + debian/control: the puppet package suggests etckeeper. * Create puppetmaster-passenger package to automatically setup the puppetmaster to be run under mod passenger and apache2: - create new puppetmaster-common package to share files between puppetmaster (ie webrick) and puppetmaster-passenger. - move puppetqd to puppetmaster-common. - debian/puppet.conf: enable ssl options so that the default configuration works out of the box under passenger. * debian/puppet-common.postinst: set permissions and ownership of puppet log directory. * Move puppetmaster's Recommends to Suggests. [ Stig Sandbeck Mathisen ] * Recommend lsb-release (Closes: #593606) * Recommend debconf-utils (Closes: #593780) * ext/puppetlast: removed from upstream * Cherry-pick updated man pages from upstream -- Stig Sandbeck Mathisen Fri, 27 Aug 2010 14:32:00 +0200 puppet (2.6.0-2) unstable; urgency=low * Bump Standards-Version to 3.9.1 * Release for unstable -- Stig Sandbeck Mathisen Wed, 28 Jul 2010 20:48:21 +0200 puppet (2.6.0-1) experimental; urgency=low * New upstream version * Fix "short package description doesn't differ from binary package puppet", update description of binary package. (Closes: #587364) * Move /usr/bin/puppet to the puppet-common package * debian/control: Convert Conflicts: to Breaks: * debian/control: bump policy version and update project homepage * debian/control: Update dependencies/breakages * debian/copyright: Remove reference to deprecated bsd license file, it is included already * move manpage puppet(8) to package puppet-common * Debian fix: set correct header in puppet.conf(5) * Add puppetqd(8) to the puppetmaster package -- Stig Sandbeck Mathisen Tue, 20 Jul 2010 09:37:22 +0200 puppet (0.25.5-1) unstable; urgency=low [ Stig Sandbeck Mathisen ] * New upstream version: 0.25.5 * Adjust conflicts for puppet-common, to ensure upgrade goes well [ Mathias Gug ] * Add /etc/puppet/templates and /etc/puppet/modules to the puppet- common package (Closes: #571129) * Add binary package "puppet-testsuite" (Closes: #584480) -- Stig Sandbeck Mathisen Fri, 25 Jun 2010 17:27:05 +0200 puppet (0.25.4-6) unstable; urgency=low * clarify passenger options in default/puppetmaster * add patch to ext/rack/files/apache2.conf for debian-specific settings * debian/control: add version depends on librack-ruby * additional start-stop-daemon fix for puppet.init and puppetqd.init * debian/rules: actually install config.ru owned by the puppet user, this is necessary for proper suid of passenger (closes: #577366) -- Micah Anderson Tue, 20 Apr 2010 16:06:38 -0400 puppet (0.25.4-5) unstable; urgency=low * debian/puppetmaster.init: fix invocation of start-stop-daemon (closes: * #578066) -- Andrew Pollock Sat, 17 Apr 2010 20:33:09 -0700 puppet (0.25.4-4) unstable; urgency=low [ Andrew Pollock ] * debian/watch: update for new upstream location * apply patch from Mathias Gug to add /etc/puppet to puppet-common's directories so that it is removed on package purge (if empty). Also removes /var/log/puppet on purge (closes: #571130) [ Micah Anderson ] * add Suggests: libselinux-ruby1.8 as puppet supports it, but only if the library is present * cherry-pick: add puppetmasterd dbconnections option to increase the rails 'max pool size' (redmine: #2568) * fix puppetqd initscript status operation [ Andrew Pollock ] * debian/rules: don't install config.ru owned by the puppet user (closes: #577366) -- Andrew Pollock Thu, 15 Apr 2010 21:18:32 -0700 puppet (0.25.4-3) unstable; urgency=low [ Stig Sandbeck Mathisen ] * Fix "puppetmaster and puppet scripts always return 0" with patch from Mathias Gug, make sure return codes are actually used (Closes: #573473) [ Micah Anderson ] * Disable default puppet.conf option pluginsync=true, see puppet-common.NEWS * Suggest in puppetmaster package: libapache2-mod-passenger, librack-ruby * Create puppetmaster.README with information about the server type * Provide an example apache2.conf for libapache2-mod-passenger * Ship the rack config.ru, and README as README.rack * Fix puppet-el.emacsen-startup script to be properly installed * debian/puppetmaster.init: Fix init stop action to not fail if the puppetmaster is already stopped, Thanks Mathias Gug (Closes: #574677) * Add Suggests: stompserver/libstomp-ruby1.8 - needed for puppetqd * Add README.queueing to puppetmaster package which describes puppetqd * Add /etc/init.d/puppetqd and defaults in /etc/defaults/puppetmaster * Switch to dpkg-source 3.0 (quilt) format -- Micah Anderson Tue, 16 Mar 2010 12:27:07 -0400 puppet (0.25.4-2) unstable; urgency=low [ Stig Sandbeck Mathisen ] * puppet: do not explicitly remove /var/lib/puppet on purge (Closes: #525852) * upstream cherry-pick: Updated man pages and moved puppet.conf.8 to puppet.conf.5 (Closes: #563567) * Fix "Improper ownership of /var/lib/puppet/state", explicitly create this in postinst (and remove in postrm on purge) (Closes: #462551) * Fix "wrong default location for templates", update default settings, and create puppet-common.NEWS with information (Closes: #484659) * Move postinst and postrm handling of shared users and directories to puppet-common (Closes: #570012) -- Stig Sandbeck Mathisen Tue, 16 Feb 2010 06:30:55 +0000 puppet (0.25.4-1) unstable; urgency=low [Nigel Kersten ] * New upstream version 0.25.4 [ Micah Anderson ] * Fix debian/rules typo in install of puppet-mode-init.el * Fix which package puppet-mode-init.el gets installed into * Add Suggests for vim-puppet and puppet-el on binary packages [ Stig Sandbeck Mathisen ] * Update debian/copyright * debian/{puppet,puppetmaster}.init: Add status argument, fix pid file locations (Closes: #545975) * Refactoring: Add binary packages for puppet-common, puppet-el, vim-puppet -- Stig Sandbeck Mathisen Mon, 01 Feb 2010 12:31:58 +0100 puppet (0.25.1-3) unstable; urgency=low [ Nigel Kersten ] * Require modification of /etc/default/puppet to start puppet client daemon. (closes: #518831) * cherry pick upstream fix for puppetrun with tags (closes: #559092) * cherry pick upstream fix for supplementary groups not being reset. (CVE-2009-3564) (closes: #551073) [ Andrew Pollock ] * debian/{puppet,puppetmaster}.pid: Correct the path to the pidfiles (closes: #561231) * debian/control: version the build dependency on facter (closes: #551055) -- Andrew Pollock Wed, 16 Dec 2009 11:36:39 -0800 puppet (0.25.1-2) unstable; urgency=low * Add puppetqd executable to puppetmaster package (closes: #554624) -- Nigel Kersten Thu, 05 Nov 2009 11:23:10 -0800 puppet (0.25.1-1) unstable; urgency=low * New upstream release of 0.25.1 -- Nigel Kersten Tue, 27 Oct 2009 10:35:40 -0700 puppet (0.25.0-1) unstable; urgency=low * New upstream release * Tweak .install files to cope with new use of sbindir from upstream. * Add the new auth.conf config file to the puppetmaster package. -- Nigel Kersten Sun, 16 Aug 2009 05:34:17 -0700 puppet (0.24.8-3) unstable; urgency=low [ Micah Anderson ] * Make logcheck ignore 'Reopening log files' on puppetmaster (Closes: #538721) [ Nigel Kersten ] * switch from unreleased to unstable. -- Nigel Kersten Sun, 16 Aug 2009 05:33:53 -0700 puppet (0.24.8-2) unstable; urgency=high [ Micah Anderson ] * Cherry-pick upstream versioncmp fix (redmine:#2110) [ Andrew Pollock ] * Enable waiting for certificates for the default value (upstream default that was previously disabled or enabled with a 5 second value) * Re-ship the vim syntax file in the correct location (it fell out after the 0.24.5-3 upload) (closes: #530752) * Re-add the 0.24.5-3 changelog entry * debian/puppet.postrm: don't delete the user or group (closes: #528068, #527381) * debian/puppet.{preinst,postinst}: Applied modified patch from Stig Sandbeck Mathisen to call conditionally call adduser in the postinst, if it's available [ Nigel Kersten ] * Switched to use install.rb, primarily to stop shebangs using /usr/bin/env * Stopped using dh_movefiles, moved to dh_install * debian/rules greatly cleaned up due to above two changes [ Andrew Pollock ] * debian/control: add rdoc, libopenssl-ruby and facter to build dependencies * debian/control: depend on ruby1.8 instead of ruby to placate Lintian * debian/puppet.install: brown paper bag release averted; install /usr/lib/ruby/1.8 * debian/rules: ensure permissions on everything under /usr/lib/ruby/1.8 is correct -- Andrew Pollock Tue, 16 Jun 2009 23:37:22 -0700 puppet (0.24.8-1) unstable; urgency=low * New upstream release * debian/control: Add Nigel Kersten and myself as uploaders * debian/changelog: wrap long lines * debian/watch: ignore release candidates * debian/compat: bump to 5 * debian/control: bump Standards-Version (no changes) -- Andrew Pollock Mon, 13 Apr 2009 17:12:47 -0700 puppet (0.24.7-2) experimental; urgency=low * make puppetmaster conflict previous puppet due to man page move -- Micah Anderson Wed, 28 Jan 2009 10:28:23 -0500 puppet (0.24.7-1) experimental; urgency=low * New upstream release * Fixed comment in defaults/puppetmaster (Closes: #510881) * Fixed debian/puppetmaster.manpages and debian/puppet.manpages to distribute puppetrun and puppetca correctly, thanks Savvas Radevic (Closes: #511826) * Added puppetmaster Recommends: libldap-ruby1.8 to silence puppetrun (Closes: #512639) * Added puppet Recommends: libaugeas-ruby1.8 for new Augeas support in this release -- Micah Anderson Fri, 23 Jan 2009 09:27:09 -0500 puppet (0.24.6-1) experimental; urgency=low * New upstream release (Closes: #506129, #504624, #502163) * Distribute filebucket binary (Closes: #499999) * Fix missing check for START variable in defaults files (Closes: #498284) * Fix maintainer scripts so that they do not ignore errors (set -e) * Fix maintainer scripts so they don't have prepended paths (thanks lintian) * Cherry-pick fixes from upstream: - comparison of String with 0 failed (Closes: #500848) - filename cannot handle ++ (Closes: #502163) - tidy must specify size, age or both (Closes: #500852) -- Micah Anderson Sat, 29 Nov 2008 13:59:25 -0500 puppet (0.24.5-3) unstable; urgency=medium * Set wait for cert timeout to 5 secs, to avoid resource abuse (Closes: #509566) * Distribute filebucket binary (Closes: #499999) * Place vim syntax in the correct location (LP: #181960) -- Thom May Wed, 07 Jan 2009 15:15:34 -0500 puppet (0.24.5-2) unstable; urgency=low * Fix puppetlast to work with 0.24.5 * Adjust logcheck to match against new log messages in 0.24.5 * Update standards version to 3.8.0 (no changes) * Update changelog to reduce length of line to make lintian happy -- Micah Anderson Sat, 26 Jul 2008 15:43:45 -0400 puppet (0.24.5-1) unstable; urgency=low * New upstream release * Applied patch from Martin Krafft to improve logcheck file installation -- Thom May Thu, 24 Jul 2008 10:58:08 +0100 puppet (0.24.4-8) unstable; urgency=low * Changed the default port value to 8140 in /etc/default/puppetmaster to be consistent with the client default port. (Closes: #483823) * Cherry-picked various bug fixes from upstream: - further emacs mode updates from Russ Allbery - misleading error if CA private key can not be decrypted (trac:#1271) - fix missing bracket in documentation (trac:#1209) - man pages updates (trac:#1211) - add dump parameter to mount type (trac:#1212) - fixed undefined variable in lib/puppet/util/settings.rb (trac:#1218) - usermod problem on Solaris (trac:#1207) - added native authorized_keys type - test within a template if a variable or fact is defined (trac:#1177) - Fixed Red Hat service disabling (trac:#1219) - fix crontab provider parse error when line begins w/space (trac:#1216) - Fix for latest method in rpm provider (trac:#1224) - puppetd documentation updates (trac:#1227) - Modified the 'factpath' setting to automatically configure Facter to load facts there if a new enough version of Facter is used. - Removing unused file lib/puppet/util/variables.rb (trac:#1229) - Fixing transaction support for prefetching generated resources. Previously, we prefetched then generated, which caused generated resources that needed prefetching not to work. This just reorders the calls, so generated resources now get prefetched. - Respect "replace => false" for symlinks (trac:#1235) - Added cron random function fixing ticket (trac:#311) - No more clear_cache failures (trac:#1247) - Fixed Rakefile to install non-.rb files (trac:#1266) -- Micah Anderson Sat, 31 May 2008 11:39:47 -0400 puppet (0.24.4-7) unstable; urgency=low * Update emacs-mode with changes cherry-picked from rra's repository * Fix typo in puppetmaster.init (Closes: #480019) * Fix variable name in /etc/default/puppetmaster comments * Fix incorrect port increment in puppetmaster initscript when mongrel is used, thanks Francois Deppierraz (Closes: #480263) * Add puppetmaster.postrm to remove /var/log/puppet on purge * Added debian/puppetmaster.dirs containing etc/puppet/manifests * Remove puppet group on purge (Closes: #481511) * Remove old config files and stray directories (Closes: #454681) -- Micah Anderson > Sat, 03 May 2008 16:18:32 -0400 puppet (0.24.4-6) unstable; urgency=low * Remove bashisms in puppetmaster.init * Add puppetlast script -- Micah Anderson > Wed, 30 Apr 2008 07:37:04 -0400 puppet (0.24.4-5) unstable; urgency=low * Fix missing --pidfile piece for mongrel startup and make stop consistent, thanks Bart Cortooms (Closes: #476840) * Add trailing newline missing from default files -- Micah Anderson Sat, 19 Apr 2008 11:03:35 -0400 puppet (0.24.4-4) unstable; urgency=low * Create /etc/default/puppet and /etc/default/puppetmaster * Modify /etc/init.d/puppetmaster to support mongrel instances on multiple ports * Remove no longer necessary .svn cleaning from debian/rules * Added $network and $named appropriated places in the LSB headers in puppet and puppetmaster initscripts, thanks Sam Quigley * Install ralsh (Closes: #476629) * Cherry-pick upstream patches from 0.24.x branch: - Install manpages - Fix shebang issues (#1148) - Updated fix for (#1020) - Fix for (#1174) - Emacs mode updates (#1160) - Debian service [en|dis]able issue (#1161) - User type group list validation enhancement - Fix configtimeout issue (#1176) -- Micah Anderson Sun, 13 Apr 2008 19:18:46 -0400 puppet (0.24.4-3) unstable; urgency=low * Remove pi binary, puppetdoc provides this functionality now (Closes: #472850) -- Micah Anderson Fri, 28 Mar 2008 12:38:30 -0400 puppet (0.24.4-2) unstable; urgency=low * Fix duplicate man8/puppetmasterd.8 install -- Micah Anderson Tue, 25 Mar 2008 22:58:22 -0400 puppet (0.24.4-1) unstable; urgency=low * New upstream release * Install man pages missing from upstream release -- Micah Anderson Tue, 25 Mar 2008 18:17:02 -0400 puppet (0.24.3-1) unstable; urgency=low [ Micah Anderson] * New upstream release * Install man pages (Closes: #385529) * Apply lsb formatted dependency info into initscripts, thanks Petter Reinholdtsen (Closes: #462915) * Install more robust puppet-mode.el * Add factpath and pluginsync=true to the default puppet.conf so that facts added through pluginsync are loaded by puppet * Add [plugins] section to fileserver.conf * Updated outdated debian/control substrvar for puppet to ${source:Version} * Updated link in debian/copyright for new URL to license * Updated copyright in debian/copyright * Bumped standards version to 3.7.3.0 (no changes) * Switch debhelper from Build-Depends-Indep to Build-Depends because it is required to run clean target (lintian check: clean-should-be-satisfied-by-build-depends) * Moved homepage from Description to control field * Added Vcs-Browser and Vcs-Git fields to debian/control [ Thom May ] * If puppet can't start, continue with package install -- Micah Anderson Sun, 09 Mar 2008 14:03:00 -0400 puppet (0.24.1-2) unstable; urgency=low * Set rundir correctly (Closes: #460203, #459579) * Apply patch for puppet#1003 to enable collection of tagged resources -- Thom May Wed, 16 Jan 2008 11:08:55 +0100 puppet (0.24.1-1) unstable; urgency=low * New upstream release (Closes: #445626) * Set maintainer to pkg-puppet-devel -- Thom May Sun, 30 Dec 2007 19:13:47 +0100 puppet (0.24.0-1) unstable; urgency=low * New upstream release -- Thom May Wed, 19 Dec 2007 16:00:34 +0100 puppet (0.23.2-15) unstable; urgency=low * No change upload setting maintainer to me whilst waiting for an alioth project. -- Thom May Thu, 29 Nov 2007 10:44:50 +0100 puppet (0.23.2-14) unstable; urgency=low * Orphaning. * Create /var/lib/puppet in the puppet package. Closes: #452506. * Start the puppet init script after puppetmaster, to silence whiny bug reports. Closes: #452064. * Add a reload command to the Puppet init script. Closes: #452060. -- Matthew Palmer Thu, 29 Nov 2007 10:48:21 +1100 puppet (0.23.2-13) unstable; urgency=low * Drop quotes from an already-quoted value in a query. Closes: #448179. * Remove excessive quoting from puppet/network/handler/master.rb. Closes: #448221. * Force removal of directories during pluginsync. Closes: #448180. -- Matthew Palmer Tue, 30 Oct 2007 14:55:19 +1100 puppet (0.23.2-12) unstable; urgency=low * Create /var/run/puppet and set the perms in the various initscripts, as well as hardcoding the rundir better in configuration.rb and removing the explicit rundir setting from puppet.conf. Closes: #447314. * Apply additional patch given (backwards) to fix export/collect on some database backends. Closes: #445591 (again!) -- Matthew Palmer Sat, 20 Oct 2007 11:28:50 +1000 puppet (0.23.2-11) unstable; urgency=low * Apply patch from puppet#786 to fix a problem with exported resources not being properly detected as needing a rerun. Closes: #445591. * Fix ignore handling for the plugins mount. Closes: #446390. -- Matthew Palmer Mon, 15 Oct 2007 09:11:25 +1000 puppet (0.23.2-10) unstable; urgency=low * Recycle connections when we change (or get) certs. * Catch and retry more transient errors in the XMLRPC wrapper. -- Matthew Palmer Thu, 27 Sep 2007 15:06:11 +1000 puppet (0.23.2-9) unstable; urgency=low * Recycle the HTTP connection if we get an EPIPE during a request. Closes: #444177. Thanks to Jos Backus for helping with testing. -- Matthew Palmer Thu, 27 Sep 2007 09:55:34 +1000 puppet (0.23.2-8) unstable; urgency=low * Remove extraneous debugging output accidentally left behind in the last release. * Fix spelling mistakes in debian/control and debian/puppet.preinst. Closes: #444158. -- Matthew Palmer Thu, 27 Sep 2007 07:45:07 +1000 puppet (0.23.2-7) unstable; urgency=low * Ignore ENOENT errors in the module plugin syncing code, since they're innocuous and expected. * Allow facts that are downloaded through pluginsync to be used like any other fact. * Allow users to still have an old-style plugins mount if they want, by specifying a path for the mount. Also track down a fault in old-style fileserving which did strange slash-stripping. Closes: #443932. -- Matthew Palmer Tue, 25 Sep 2007 16:41:32 +1000 puppet (0.23.2-6) unstable; urgency=low * Patch rails/param_name.rb to stop query failures, as per puppet#784. * Actually honour namevar. * Only set dbuser if explicitly asked for. * Fix annoying database deletion error for ParamValue objects. * Add an accessor for ca_file, since older openssl-ruby only had a writer. * Fix the fileserver to honour ignore. Thanks to Nathan Ward for the bug report on IRC. -- Matthew Palmer Thu, 20 Sep 2007 16:10:41 +1000 puppet (0.23.2-5) unstable; urgency=low * Add some NEWS for the ssldir transition. Should have done that earlier. * Remove the explicit mode change for vardir, and fix up the mode on statedir, as well. Closes: #425496. * Only set some database parameters if they're explicitly set; this makes life easier for PgSQL ident auth. * Allow empty config options. -- Matthew Palmer Thu, 13 Sep 2007 11:09:59 +1000 puppet (0.23.2-4) unstable; urgency=low * Fix puppet#776 in a slightly better way by only flushing the cache when a value is changed, rather than whenever a value is read. * Apply patch from puppet#755 to cache connections to the Puppetmaster, which improves performance by more than a little. * Modify the fileserver so that it provides a 'plugins' mount which exports the union of the plugins directory of all modules. -- Matthew Palmer Fri, 31 Aug 2007 15:32:04 +1000 puppet (0.23.2-3) unstable; urgency=low * Clear the config value cache every time. This is a titchy little performance hit, but it works around puppet#776 rather nicely. -- Matthew Palmer Fri, 24 Aug 2007 16:08:04 +1000 puppet (0.23.2-2) unstable; urgency=low * Move the SSL state directory to a more policy-friendly location, /var/lib/puppet/ssl. -- Matthew Palmer Tue, 21 Aug 2007 12:54:40 +1000 puppet (0.23.2-1) unstable; urgency=low * New upstream release. -- Matthew Palmer Tue, 7 Aug 2007 12:47:49 +1000 puppet (0.23.1-1) unstable; urgency=low * New upstream release. * Switch primary maintainer to me. Thanks jaq. * Make the recommendation for rails >= 1.2.3-2, to avoid incompatibilities. This breaks compatibility with stable, but the rails package from unstable should install cleanly in stable. Closes: #433999 -- Matthew Palmer Sat, 21 Jul 2007 16:34:36 +1000 puppet (0.23.0-1) unstable; urgency=low * New upstream release. - Includes a new configuration file handling system; see NEWS.Debian. -- Matthew Palmer Mon, 25 Jun 2007 09:55:12 +1000 puppet (0.22.4-2) unstable; urgency=low * Depend on libshadow-ruby1.8, for new password modification functionality added to upstream 0.22.4. * Several improvements from Micah Anderson: - Better vim syntax installation process. - Install Emacs syntax highlighting. - Install logcheck rules. Closes: #421851. -- Matthew Palmer Thu, 3 May 2007 15:04:15 +1000 puppet (0.22.4-1) unstable; urgency=low * New upstream release. -- Matthew Palmer Wed, 2 May 2007 12:20:15 +1000 puppet (0.22.3-1) unstable; urgency=low * New upstream release. Closes: #415773. * Switch to using our own logrotate config, and enhance it as per David Schmitt's suggestions. Closes: #414282. * Add puppetrun to the puppetmaster package, and actually put puppetdoc into the puppet package. Closes: #419273. * Copy vim syntax highlighting file into the puppet package, and add a stanza to have Vim automatically highlight .pp files. Closes: #412868. Thanks to David Schmitt for researching how to do all of that. * Add a templatedir setting to the default puppetmasterd.conf to make it obvious that it can be changed. Closes: #407506. -- Matthew Palmer Wed, 18 Apr 2007 14:03:33 +1000 puppet (0.22.1-1) unstable; urgency=low * New upstream release. -- Matthew Palmer Fri, 2 Feb 2007 09:06:46 +1100 puppet (0.22.0-1) unstable; urgency=low * New upstream release. * Use --startas instead of --init in init scripts, which (according to Paul Hampson) makes checking for already-running instances work. Closes: #405912. -- Matthew Palmer Mon, 8 Jan 2007 08:41:35 +1100 puppet (0.20.1-1) unstable; urgency=low * New upstream release. (Closes: #387674) * Rationalise the puppetmasterd init script. * Add inclusion of /etc/default files for init scripts. (Closes: #388178) * Add puppet.conf to match puppetd.conf. (Closes: #385646) -- Matthew Palmer Thu, 30 Nov 2006 10:54:19 +1100 puppet (0.18.4-1) unstable; urgency=low * New upstream release. - Properly detect all services, including those in rcS.d. (Closes: #378351) * Add Homepage: to the long description. (Closes: #377896) -- Matthew Palmer Mon, 24 Jul 2006 19:46:06 +1000 puppet (0.18.3-1) unstable; urgency=low * New upstream version. - Set DEBIAN_FRONTEND=noninteractive when installing Debian packages. (Closes: #378338) -- Matthew Palmer Sun, 16 Jul 2006 10:58:50 +1000 puppet (0.18.1-1) unstable; urgency=low * Make Puppet not wait for a cert at all (to prevent startup hangs). * Cleanup the init scripts to not have NO_START detritus. * Apply puppet.debian-frontend, to set DEBIAN_FRONTEND=noninteractive on package installation. -- Matthew Palmer Tue, 27 Jun 2006 15:05:32 +1000 puppet (0.18.0-1) unstable; urgency=low * Initial release. (Closes: #348625) -- Matthew Palmer Wed, 24 May 2006 13:10:01 +1000 puppet-5.5.10/ext/envpuppet0000755005276200011600000001024613417161721015571 0ustar jenkinsjenkins#! /bin/sh # # Jeff McCune # 2010-10-20 # # Copyright (c) 2010, Puppet Labs # License: BSD 3-clause license # # This script provides a simple way to execute puppet and related tools # directly from a git clone of the upstream repositories. This allows you to # quickly switch branches and test different versions of code without much # friction. # # NOTE: There may be issues if puppet, facter, etc... are already installed # into RUBY's site_ruby directory. If you run into strange problems, make sure # the correct ruby libraries are being loaded... # # Sample Usage: # ============= # cd ~/src # git clone git://github.com/puppetlabs/puppet.git # git clone git://github.com/puppetlabs/facter.git # pushd puppet # git checkout tags/2.6.1 # popd # pushd facter # git checkout tags/1.5.8 # export ENVPUPPET_BASEDIR=/home/jeff/src # envpuppet puppet --version # 2.6.1 # envpuppet facter --version # 1.5.8 set -e set -u if [ "${1:-}" = "--help" ]; then cat < 2011-02-09 Puppet should not be installed in site_ruby because all of \$LOAD_PATH is searched by puppet when loading libraries and the installed version will taint the development version The following enviornment variables configure the behavior of envpuppet ENVPUPPET_BASEDIR="${HOME}/src" the base directory where puppet, facter, etc... live. ENVPUPPET_BLEEDING=true Enables bleeding edge prototypes like puppet-interfaces The PATH and RUBYLIB are the primary environment variables modified by the envpuppet script. If no arguments are given, the environment variables are printed to STDOUT allowing the output to be sourced. For example: eval \$(envpuppet) EO_HELP exit 0 fi if test -d puppet -o -d facter; then ( echo " WARNING!" echo " Strange things happen if puppet or facter are in the" echo " current working directory" echo " (import errors from ruby are a prime example)" echo " WARNING!" echo "" echo "I suggest changing to ~ or /tmp or something..." echo "" echo "Sleeping 2 seconds." echo "" ) 1>&2 sleep 2 fi # Set this to where you check out puppet and facter : ${ENVPUPPET_BASEDIR:="${HOME}/src"} # Are we bleeding edge? : ${ENVPUPPET_BLEEDING:='false'} # git://github.com/puppetlabs/puppet.git mypath="${ENVPUPPET_BASEDIR}/puppet/sbin:${ENVPUPPET_BASEDIR}/puppet/bin" myrubylib="${ENVPUPPET_BASEDIR}/puppet/lib" # git://github.com/puppetlabs/facter.git mypath="${mypath}:${ENVPUPPET_BASEDIR}/facter/bin" myrubylib="${myrubylib}:${ENVPUPPET_BASEDIR}/facter/lib" # git://github.com/puppetlabs/hiera.git mypath="${mypath}:${ENVPUPPET_BASEDIR}/hiera/bin" myrubylib="${myrubylib}:${ENVPUPPET_BASEDIR}/hiera/lib" if [ "${ENVPUPPET_BLEEDING:-}" = "true" ]; then # git://github.com/puppetlabs/facter.git mypath="${mypath}:${ENVPUPPET_BASEDIR}/puppet-interfaces/bin" myrubylib="${myrubylib}:${ENVPUPPET_BASEDIR}/puppet-interfaces/lib" fi # https://github.com/jamtur01/puppet-scaffold.git mypath="${mypath}:${ENVPUPPET_BASEDIR}/puppet-scaffold/bin" myrubylib="${myrubylib}:${ENVPUPPET_BASEDIR}/puppet-scaffold/lib" # https://github.com/puppetlabs/puppet-module-tool.git # Also known as "pmt" Will become "puppet module" mypath="${mypath}:${ENVPUPPET_BASEDIR}/puppet-module-tool/bin" myrubylib="${myrubylib}:${ENVPUPPET_BASEDIR}/puppet-module-tool/lib" # Use the existing environment, if present. # Default to no value to prevent unbound variable issues mypath="${mypath}:${PATH:-}" myrubylib="${myrubylib}:${RUBYLIB:-}" export ENVPUPPET_OLD_PATH="${PATH:-}" export ENVPUPPET_OLD_RUBYLIB="${RUBYLIB:-}" # Trim any trailing colons from the path list. export PATH="${mypath%%:}" export RUBYLIB="${myrubylib%%:}" if [ $# -eq 0 ]; then echo "export ENVPUPPET_OLD_PATH='${ENVPUPPET_OLD_PATH}'" echo "export ENVPUPPET_OLD_RUBYLIB='${ENVPUPPET_OLD_RUBYLIB}'" echo "export ENVPUPPET_BASEDIR='${ENVPUPPET_BASEDIR}'" echo "export ENVPUPPET_BLEEDING='${ENVPUPPET_BLEEDING}'" echo "export PATH='${PATH}'" echo "export RUBYLIB='${RUBYLIB}'" else exec "$@" fi puppet-5.5.10/ext/envpuppet.bat0000644005276200011600000000054513417161721016334 0ustar jenkinsjenkins@echo off SETLOCAL REM net use Z: "\\vmware-host\Shared Folders" /persistent:yes SET PUPPET_DIR=%~dp0.. SET FACTER_DIR=%PUPPET_DIR%\..\facter SET HIERA_DIR=%PUPPET_DIR%\..\hiera SET PATH=%PUPPET_DIR%\bin;%FACTER_DIR%\bin;%HIERA_DIR%\bin;%PATH% SET RUBYLIB=%PUPPET_DIR%\lib;%FACTER_DIR%\lib;%HIERA_DIR%\lib;%RUBYLIB% SET RUBYLIB=%RUBYLIB:\=/% ruby -S %* puppet-5.5.10/ext/freebsd/0000755005276200011600000000000013417162176015231 5ustar jenkinsjenkinspuppet-5.5.10/ext/freebsd/puppetd0000644005276200011600000000074213417161721016633 0ustar jenkinsjenkins#!/bin/sh # # PROVIDE: puppetd # REQUIRE: NETWORK # KEYWORD: FreeBSD shutdown . /etc/rc.subr name=puppetd rcvar=`set_rcvar` # set defaults command=/usr/local/bin/puppetd pidfile="/var/run/$name.pid" #required_files="/usr/local/etc/$name.conf" # read configuration and set defaults load_rc_config "$name" : ${puppetd_enable="NO"} : ${puppetd_config="/usr/local/etc/puppet.conf"} : ${puppetd_flags=""} command_args="--config $puppetd_config $puppetd_flags" run_rc_command "$1" puppet-5.5.10/ext/freebsd/puppetmasterd0000644005276200011600000000102213417161721020037 0ustar jenkinsjenkins#!/bin/sh # # PROVIDE: puppetmasterd # REQUIRE: NETWORK # KEYWORD: FreeBSD shutdown . /etc/rc.subr name=puppetmasterd rcvar=`set_rcvar` # set defaults command=/usr/local/bin/puppetmasterd pidfile="/var/run/$name.pid" #required_files="/usr/local/etc/$name.conf" # read configuration and set defaults load_rc_config "$name" : ${puppetmasterd_enable="NO"} : ${puppetmasterd_config="/usr/local/etc/puppet.conf"} : ${puppetmasterd_flags=""} command_args="--config $puppetmasterd_config $puppetmasterd_flags" run_rc_command "$1" puppet-5.5.10/ext/gentoo/0000755005276200011600000000000013417162176015112 5ustar jenkinsjenkinspuppet-5.5.10/ext/gentoo/conf.d/0000755005276200011600000000000013417162176016261 5ustar jenkinsjenkinspuppet-5.5.10/ext/gentoo/conf.d/puppet0000644005276200011600000000022113417161721017507 0ustar jenkinsjenkins# Location of PID files PUPPET_PID_DIR="/var/run/puppetlabs" # You may specify other parameters to the puppet client here #PUPPET_EXTRA_OPTS="" puppet-5.5.10/ext/gentoo/conf.d/puppetmaster0000644005276200011600000000056413417161721020735 0ustar jenkinsjenkins# Location of PID files PUPPETMASTER_PID_DIR="/var/run/puppetlabs" # Location of the main manifest #PUPPETMASTER_MANIFEST="/etc/puppet/manifests/site.pp" # Where to log general messages to. # Specify syslog to send log messages to the system log. #PUPPETMASTER_LOG="syslog" # You may specify other parameters to the puppetmaster here #PUPPETMASTER_EXTRA_OPTS="--no-ca" puppet-5.5.10/ext/gentoo/init.d/0000755005276200011600000000000013417162176016277 5ustar jenkinsjenkinspuppet-5.5.10/ext/gentoo/init.d/puppet0000644005276200011600000000174513417161721017541 0ustar jenkinsjenkins#!/sbin/runscript # Copyright 1999-2004 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 # $Header$ depend() { need net use dns logger } checkconfig() { if [[ ! -d "${PUPPET_PID_DIR}" ]] ; then eerror "Please make sure PUPPET_PID_DIR is defined and points to an existing directory" return 1 fi return 0 } start() { checkconfig || return $? local options="" [[ -n "${PUPPET_EXTRA_OPTS}" ]] && options="${options} ${PUPPET_EXTRA_OPTS}" ebegin "Starting puppet" start-stop-daemon --start --quiet --exec /usr/bin/ruby /usr/bin/puppetd -- ${options} eend $? "Failed to start puppet" } stop() { ebegin "Stopping puppet" start-stop-daemon --stop --quiet --pidfile ${PUPPET_PID_DIR}/puppetd.pid local ret=$? eend ${ret} "Failed to stop puppet" rm -f ${PUPPET_PID_DIR}/puppetd.pid return ${ret} } puppet-5.5.10/ext/gentoo/init.d/puppetmaster0000755005276200011600000000274113417161721020755 0ustar jenkinsjenkins#!/sbin/runscript # Copyright 1999-2008 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 depend() { need net before puppet use dns logger } checkconfig() { if [[ ! -d "${PUPPETMASTER_PID_DIR}" ]] ; then eerror "Please make sure PUPPETMASTER_PID_DIR is defined and points to an existing directory" return 1 fi local site_manifest="/etc/puppet/manifests/site.pp" [[ -n "${PUPPETMASTER_MANIFEST}" ]] && site_manifest="${PUPPETMASTER_MANIFEST}" if [ ! -f "${site_manifest}" ] ; then eerror "Please create ${site_manifest} before running puppet" return 1 fi return 0 } start() { checkconfig || return $? local options="" [[ -n "${PUPPETMASTER_MANIFEST}" ]] && options="${options} --manifest=${PUPPETMASTER_MANIFEST}" [[ -n "${PUPPETMASTER_LOG}" ]] && options="${options} --logdest=${PUPPETMASTER_LOG}" [[ -n "${PUPPETMASTER_EXTRA_OPTS}" ]] && options="${options} ${PUPPETMASTER_EXTRA_OPTS}" ebegin "Starting puppetmaster" start-stop-daemon --start --quiet --exec /usr/bin/ruby /usr/bin/puppetmasterd \ -- ${options} eend "$?" "Failed to start puppetmaster" } stop() { ebegin "Stopping puppetmaster" start-stop-daemon --stop --quiet \ --pidfile ${PUPPETMASTER_PID_DIR}/puppetmasterd.pid local ret=$? eend "${ret}" "Failed to stop puppetmaster" rm -f "${PUPPETMASTER_PID_DIR}/puppetmasterd.pid" return "${ret}" } puppet-5.5.10/ext/gentoo/puppet/0000755005276200011600000000000013417162176016427 5ustar jenkinsjenkinspuppet-5.5.10/ext/gentoo/puppet/fileserver.conf0000644005276200011600000000266613417161721021451 0ustar jenkinsjenkins# fileserver.conf # Puppet automatically serves PLUGINS and FILES FROM MODULES: anything in # /files/ is available to authenticated nodes at # puppet:///modules//. You do not need to edit this # file to enable this. # MOUNT POINTS # If you need to serve files from a directory that is NOT in a module, # you must create a static mount point in this file: # # [extra_files] # path /etc/puppet/files # allow * # # In the example above, anything in /etc/puppet/files/ would be # available to authenticated nodes at puppet:///extra_files/. # # Mount points may also use three placeholders as part of their path: # # %H - The node's certname. # %h - The portion of the node's certname before the first dot. (Usually the # node's short hostname.) # %d - The portion of the node's certname after the first dot. (Usually the # node's domain name.) # PERMISSIONS # Every static mount point should have an `allow *` line; setting more # granular permissions in this file is deprecated. Instead, you can # control file access in auth.conf by controlling the # /file_metadata/ and /file_content/ paths: # # path ~ ^/file_(metadata|content)/extra_files/ # auth yes # allow /^(.+)\.example\.com$/ # allow_ip 192.168.100.0/24 # # If added to auth.conf BEFORE the "path /file" rule, the rule above # will add stricter restrictions to the extra_files mount point. puppet-5.5.10/ext/hiera/0000755005276200011600000000000013417162176014707 5ustar jenkinsjenkinspuppet-5.5.10/ext/hiera/hiera.yaml0000644005276200011600000000100613417161721016653 0ustar jenkinsjenkins--- version: 5 defaults: # The default value for "datadir" is "data" under the same directory as the hiera.yaml # file (this file) # When specifying a datadir, make sure the directory exists. # See https://puppet.com/docs/puppet/latest/environments_about.html for further details on environments. # datadir: data # data_hash: yaml_data hierarchy: - name: "Per-node data (yaml version)" path: "nodes/%{::trusted.certname}.yaml" - name: "Other YAML hierarchy levels" paths: - "common.yaml" puppet-5.5.10/ext/ips/0000755005276200011600000000000013417162177014413 5ustar jenkinsjenkinspuppet-5.5.10/ext/ips/puppet-agent0000644005276200011600000000210013417161721016732 0ustar jenkinsjenkins#!/sbin/sh . /lib/svc/share/smf_include.sh [[ -z "${SMF_FMRI}" ]] && exit "$SMF_EXIT_ERR" typeset -r CONF_FILE=/etc/puppet/puppet.conf [[ ! -f "${CONF_FILE}" ]] && exit "$SMF_EXIT_ERR_CONFIG" typeset -r PUPPET=/usr/bin/puppet case "$1" in start) exec "$PUPPET" agent ;; stop) # stop sends sigterm first followed by sigkill # smf_kill_contract TERM 1 30 # sends sigterm to all process in ctid and will continue # to do so for 30 seconds with interval of 5 seconds # smf_kill_contract KILL 1 # continues until all processes are killed. # svcs -p lists all processes in the contract. # http://bnsmb.de/solaris/My_Little_SMF_FAQ.html ctid=`svcprop -p restarter/contract "$SMF_FMRI"` if [ -n "$ctid" ]; then smf_kill_contract "$ctid" TERM 1 5 ret=$? [ "$ret" -eq 1 ] && exit "$SMF_EXIT_ERR_FATAL" if [ "$ret" -eq 2 ] ; then smf_kill_contract "$ctid" KILL 1 [ $? -ne 0 ] && exit "$SMF_EXIT_ERR_FATAL" fi fi ;; *) echo "Usage: $0 {start|stop}"; exit "$SMF_EXIT_ERR_FATAL" ;; esac exit "$SMF_EXIT_OK" puppet-5.5.10/ext/ips/puppet-master0000644005276200011600000000211513417161721017135 0ustar jenkinsjenkins#!/sbin/sh . /lib/svc/share/smf_include.sh [[ -z "${SMF_FMRI}" ]] && exit "$SMF_EXIT_ERR" typeset -r CONF_FILE=/etc/puppet/puppet.conf [[ ! -f "${CONF_FILE}" ]] && exit "$SMF_EXIT_ERR_CONFIG" typeset -r PUPPET=/usr/bin/puppet case "$1" in start) exec "$PUPPET" master --daemonize ;; stop) # stop sends sigterm first followed by sigkill # smf_kill_contract TERM 1 30 # sends sigterm to all process in ctid and will continue # to do so for 30 seconds with interval of 5 seconds # smf_kill_contract KILL 1 # continues until all processes are killed. # svcs -p lists all processes in the contract. # http://bnsmb.de/solaris/My_Little_SMF_FAQ.html ctid=`svcprop -p restarter/contract "$SMF_FMRI"` if [ -n "$ctid" ]; then smf_kill_contract "$ctid" TERM 1 5 ret=$? [ "$ret" -eq 1 ] && exit "$SMF_EXIT_ERR_FATAL" if [ "$ret" -eq 2 ] ; then smf_kill_contract "$ctid" KILL 1 [ $? -ne 0 ] && exit "$SMF_EXIT_ERR_FATAL" fi fi ;; *) echo "Usage: $0 {start|stop}"; exit "$SMF_EXIT_ERR_FATAL" ;; esac exit "$SMF_EXIT_OK" puppet-5.5.10/ext/ips/puppetagent.xml0000644005276200011600000000306013417161721017462 0ustar jenkinsjenkins puppet-5.5.10/ext/ips/puppetmaster.xml0000644005276200011600000000306313417161721017662 0ustar jenkinsjenkins puppet-5.5.10/ext/ips/rules0000644005276200011600000000161713417161721015467 0ustar jenkinsjenkins#!/usr/bin/make -f LIBDIR=$(shell /usr/bin/ruby18 -rrbconfig -e 'puts Config::CONFIG["rubylibdir"]') DESTDIR=$(CURDIR)/pkg/ips/proto binary-install/puppet:: /usr/bin/ruby18 install.rb --destdir=$(DESTDIR) --bindir=/usr/bin --sbindir=/usr/sbin --sitelibdir=$(LIBDIR) --mandir=/usr/share/man mkdir -p $(DESTDIR)/var/{lib,log}/puppet/ mkdir -p $(DESTDIR)/var/svc/manifest/network/puppet/ mkdir -p $(DESTDIR)/lib/svc/method/ mkdir -p $(DESTDIR)/etc/puppet/ svccfg validate ext/ips/puppetagent.xml svccfg validate ext/ips/puppetmaster.xml cp ext/ips/puppetagent.xml $(DESTDIR)/var/svc/manifest/network/puppet/ cp ext/ips/puppetmaster.xml $(DESTDIR)/var/svc/manifest/network/puppet/ cp ext/ips/puppet-agent $(DESTDIR)/lib/svc/method/ cp ext/ips/puppet-master $(DESTDIR)/lib/svc/method/ cp ext/ips/puppet.conf $(DESTDIR)/etc/puppet/puppet.conf chmod 700 $(DESTDIR)/lib/svc/method/puppet-{agent,master} puppet-5.5.10/ext/ips/transforms0000644005276200011600000000265513417161721016536 0ustar jenkinsjenkins default facet.doc.man true> add restart_fmri svc:/application/man-index:default> # drop opt and user drop> drop> drop> drop> drop> drop> drop> drop> drop> drop> drop> # drop var/lib var/log drop> # saner dependencies edit fmri "@[^ \t\n\r\f\v]*" ""> # make sure /var/log/puppet and /var/lib/puppet are owned by puppet edit group bin puppet> edit owner root puppet> add restart_fmri svc:/system/manifest-import:default> # we depend on facter emit depend type=require fmri=application/facter@1.7.0> # preserve the old conf file on upgrade. add overlay true> add preserve renamenew> puppet-5.5.10/ext/ips/puppet.p5m0000644005276200011600000000134213417162177016353 0ustar jenkinsjenkinsset name=pkg.fmri value=pkg://puppetlabs.com/system/management/@ set name=pkg.summary value="Puppet, an automated configuration management tool" set name=pkg.human-version value="5.5.10" set name=pkg.description value="Puppet, an automated configuration management tool" set name=info.classification value="org.opensolaris.category.2008:System/Administration and Configuration" set name=org.opensolaris.consolidation value="puppet" set name=description value="Puppet, an automated configuration management tool" set name=variant.opensolaris.zone value=global value=nonglobal set name=variant.arch value=sparc value=i386 license puppet.license license="Apache v2.0" group groupname=puppet user username=puppet password='*NP*' group=puppet puppet-5.5.10/ext/ldap/0000755005276200011600000000000013417162176014537 5ustar jenkinsjenkinspuppet-5.5.10/ext/ldap/puppet.schema0000644005276200011600000000152113417161721017230 0ustar jenkinsjenkinsattributetype ( 1.3.6.1.4.1.34380.1.1.3.10 NAME 'puppetClass' DESC 'Puppet Node Class' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) attributetype ( 1.3.6.1.4.1.34380.1.1.3.9 NAME 'parentNode' DESC 'Puppet Parent Node' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE ) attributetype ( 1.3.6.1.4.1.34380.1.1.3.11 NAME 'environment' DESC 'Puppet Node Environment' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) attributetype ( 1.3.6.1.4.1.34380.1.1.3.12 NAME 'puppetVar' DESC 'A variable setting for puppet' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) objectclass ( 1.3.6.1.4.1.34380.1.1.1.2 NAME 'puppetClient' SUP top AUXILIARY DESC 'Puppet Client objectclass' MAY ( puppetclass $ parentnode $ environment $ puppetvar )) puppet-5.5.10/ext/logcheck/0000755005276200011600000000000013417162176015376 5ustar jenkinsjenkinspuppet-5.5.10/ext/logcheck/puppet0000644005276200011600000000421013417161721016626 0ustar jenkinsjenkins^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-master\[[0-9]+\]: (Handled resources in|Resource comparison took|Searched for (host|resources|resource params and tags) in) [0-9.]+ seconds ^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-master\[[0-9]+\]: Starting Puppet server version [.0-9]+$ ^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-master\[[0-9]+\]: Compiled catalog for [._[:alnum:]-]+ in environment [_[:alnum:]]+ in [.[:digit:]]+ seconds$ ^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-master\[[0-9]+\]: Caught TERM; shutting down$ ^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-master\[[0-9]+\]: Shutting down$ ^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-agent\[[0-9]+\]: Starting Puppet client version [.0-9]+$ ^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-agent\[[0-9]+\]: getting config$ ^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-agent\[[0-9]+\]: Caching configuration at [\/._[:alnum:]-]+$ ^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-agent\[[0-9]+\]: Loaded state in [.0-9]+ seconds$ ^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-agent\[[0-9]+\]: Calling puppetmaster.getconfig$ ^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-agent\[[0-9]+\]: Retrieved configuration in [.0-9]+ seconds$ ^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-agent\[[0-9]+\]: Starting configuration run$ ^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-agent\[[0-9]+\]: Finished configuration run in [.0-9]+ seconds$ ^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-agent\[[0-9]+\]: Caught (TERM|INT); shutting down$ ^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-agent\[[0-9]+\]: Shutting down$ ^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-agent\[[0-9]+\]: Restarting with .*$ ^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-agent\[[0-9]+\]: Starting catalog run$ ^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-agent\[[0-9]+\]: Finished catalog run in [.0-9]+ seconds$ ^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-agent\[[0-9]+\]: Loading fact .*$ ^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-agent\[[0-9]+\]: Ignoring cache$ ^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-agent\[[0-9]+\]: Ignoring --listen on onetime run$ ^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-agent\[[0-9]+\]: Retrieving plugins$ ^\w{3} [ :0-9]{11} [._[:alnum:]-]+ puppet-master\[[0-9]+\]: Reopening log files$ puppet-5.5.10/ext/nagios/0000755005276200011600000000000013417162176015077 5ustar jenkinsjenkinspuppet-5.5.10/ext/nagios/check_puppet.rb0000755005276200011600000000501613417161721020076 0ustar jenkinsjenkins#!/usr/bin/env ruby require 'optparse' require 'sys/proctable' include Sys class CheckPuppet VERSION = '0.1' script_name = File.basename($0) # default options OPTIONS = { :statefile => "/var/lib/puppet/state/state.yaml", :process => "puppetd", :interval => 30, } OptionParser.new do |o| o.set_summary_indent(' ') o.banner = "Usage: #{script_name} [OPTIONS]" o.define_head "The check_puppet Nagios plug-in checks that specified Puppet process is running and the state file is no older than specified interval." o.separator "" o.separator "Mandatory arguments to long options are mandatory for short options too." o.on( "-s", "--statefile=statefile", String, "The state file", "Default: #{OPTIONS[:statefile]}") { |op| OPTIONS[:statefile] = op } o.on( "-p", "--process=processname", String, "The process to check", "Default: #{OPTIONS[:process]}") { |op| OPTIONS[:process] = op } o.on( "-i", "--interval=value", Integer, "Default: #{OPTIONS[:interval]} minutes") { |op| OPTIONS[:interval] = op } o.separator "" o.on_tail("-h", "--help", "Show this help message.") do puts o exit end o.parse!(ARGV) end def check_proc unless ProcTable.ps.find { |p| p.name == OPTIONS[:process]} @proc = 2 else @proc = 0 end end def check_state # Set variables curt = Time.now intv = OPTIONS[:interval] * 60 # Check file time begin @modt = File.mtime("#{OPTIONS[:statefile]}") rescue @file = 3 end diff = (curt - @modt).to_i if diff > intv @file = 2 else @file = 0 end end def output_status case @file when 0 state = "state file status okay updated on " + @modt.strftime("%m/%d/%Y at %H:%M:%S") when 2 state = "state fille is not up to date and is older than #{OPTIONS[:interval]} minutes" when 3 state = "state file status unknown" end case @proc when 0 process = "process #{OPTIONS[:process]} is running" when 2 process = "process #{OPTIONS[:process]} is not running" end case when (@proc == 2 or @file == 2) status = "CRITICAL" exitcode = 2 when (@proc == 0 and @file == 0) status = "OK" exitcode = 0 else status = "UNKNOWN" exitcode = 3 end puts "PUPPET #{status}: #{process}, #{state}" exit(exitcode) end end cp = CheckPuppet.new cp.check_proc cp.check_state cp.output_status puppet-5.5.10/ext/osx/0000755005276200011600000000000013417162176014430 5ustar jenkinsjenkinspuppet-5.5.10/ext/osx/file_mapping.yaml0000644005276200011600000000114713417161721017744 0ustar jenkinsjenkinsdirectories: lib: path: 'Library/Ruby/Site' owner: 'root' group: 'wheel' perms: '0644' bin: path: 'usr/bin' owner: 'root' group: 'wheel' perms: '0755' 'man/man8': path: 'usr/share/man/man8' owner: 'root' group: 'wheel' perms: '0755' files: 'conf/auth.conf': path: 'private/etc/puppet' owner: 'root' group: 'wheel' perms: '0644' 'man/man5/puppet.conf.5': path: 'usr/share/man/man5' owner: 'root' group: 'wheel' perms: '0644' '[A-Z]*': path: 'usr/share/doc/puppet' owner: 'root' group: 'wheel' perms: '0644' puppet-5.5.10/ext/osx/postflight.erb0000755005276200011600000000735113417161721017311 0ustar jenkinsjenkins#!/bin/bash #by doing the below, we are ensuring we are modifying the target system the package is being installed on, #not the OS running the installer declare -x dest_vol="${3}" declare -x dscl="${dest_vol}/usr/bin/dscl" declare -x dspath="${dest_vol}/var/db/dslocal/nodes/Default/" declare -x scripts_dir="${1}/Contents/Resources/" declare -x awk="/usr/bin/awk" declare -x last_user_id='-1' declare -x last_group_id='-1' function idFree() { declare -a idArray=("${!2}") for inc in ${idArray[@]} do if [ $inc == $1 ] then return 1 fi done return 0 } function create_puser () { "${dscl}" -f "${dspath}" localonly -create /Local/Target/Users/puppet "${dscl}" -f "${dspath}" localonly -create /Local/Target/Users/puppet UniqueID $1 "${dscl}" -f "${dspath}" localonly -create /Local/Target/Users/puppet PrimaryGroupID $2 } function create_pgroup () { "${dscl}" -f "${dspath}" localonly -create /Local/Target/Groups/puppet "${dscl}" -f "${dspath}" localonly -create /Local/Target/Groups/puppet PrimaryGroupID $1 } function scan_users () { UniqueIDS=(`"${dscl}" -f "${dspath}" localonly list /Local/Target/Users UniqueID | $awk '{print $2}'`); #first just check for UID 52 if idFree '52' UniqueIDS[@] then last_user_id='52' else for possibleUID in {450..495} do if idFree $possibleUID UniqueIDS[@] then last_user_id=$possibleUID #echo $last_good_id fi done fi } function scan_groups () { GroupIDS=(`"${dscl}" -f "${dspath}" localonly list /Local/Target/Groups PrimaryGroupID | $awk '{print $2}'`); #check for 52 for group, if it's free, take it, don't bother doing the big search if idFree '52' GroupIDS[@] then last_group_id='52' else for groupID in {450..495} do if idFree $groupID GroupIDS[@] then last_group_id=$groupID fi done fi } echo "looking for Puppet User" "${dscl}" -f "${dspath}" localonly -read /Local/Target/Users/puppet puser_exists=$? echo "Looking for Puppet Group" "${dscl}" -f "${dspath}" localonly -read /Local/Target/Groups/puppet pgroup_exists=$? # exit status 56 indicates user/group not found # exit status 0 indicates user/group does exist if [ $pgroup_exists == '0' ] && [ $puser_exists == '0' ]; then #Puppet user and group already exist echo "Puppet User / Group already exist, not adding anything" #storing the existing UID/GID to set permissions for /var/lib/puppet and /etc/puppet/puppet.conf last_group_id=`"${dscl}" -f "${dspath}" localonly -read /Local/Target/Groups/puppet PrimaryGroupID | awk '{print $2}'` last_user_id=`"${dscl}" -f "${dspath}" localonly -read /Local/Target/Users/puppet UniqueID | awk '{print $2}'` elif [ $pgroup_exists == '0' ] && [ $puser_exists == '56' ]; then #puppet group exists, but user does not last_group_id=`"${dscl}" -f "${dspath}" localonly -read /Local/Target/Groups/puppet PrimaryGroupID | awk '{print $2}'` scan_users echo "Creating Puppet User (uid: $last_user_id) in existing Puppet Group (gid: $last_group_id)" create_puser $last_user_id $last_group_id elif [ $pgroup_exists == '56' ] && [ $puser_exists == '0' ]; then #puppet user exists, but group does not last_user_id=`"${dscl}" -f "${dspath}" localonly -read /Local/Target/Users/puppet UniqueID | awk '{print $2}'` scan_groups echo "Creating Puppet Group (gid: $last_group_id), Puppet User exists (uid: $last_user_id)" create_pgroup $last_group_id elif [ $pgroup_exists == '56' ] && [ $puser_exists == '56' ]; then scan_users scan_groups echo "Creating Puppet User (uid: $last_user_id) in new Puppet Group (gid: $last_group_id)" create_pgroup $last_group_id create_puser $last_user_id $last_group_id else echo "Something went wrong and user creation will need to be done manually" fi puppet-5.5.10/ext/osx/preflight.erb0000755005276200011600000000362213417161721017107 0ustar jenkinsjenkins#!/bin/bash # # Make sure that old puppet cruft is removed # This also allows us to downgrade puppet as # it's more likely that installing old versions # over new will cause issues. # # ${3} is the destination volume so that this works correctly # when being installed to volumes other than the current OS. <%- ["@apple_libdir", "@apple_sbindir", "@apple_bindir", "@apple_docdir", "@package_name"].each do |i| -%> <%- val = instance_variable_get(i) -%> <%- raise "Critical variable #{i} is unset!" if val.nil? or val.empty? -%> <%- end -%> # remove ruby library files. Because hiera and puppet both place a "hiera" # directory in the libdir, we have to be sure we only delete the files # belonging to this project. In this case, we only delete files from within # the 'hiera' directory, while deleting the entire puppet directory. <%- Dir.chdir("lib") do -%> <%- [@apple_old_libdir, @apple_libdir].compact.each do |libdir| -%> <%- Dir["hiera/**/*"].select{ |i| File.file?(i) }.each do |file| -%> /bin/rm -f "${3}<%= libdir %>/<%= file %>" <%- end -%> <%- Dir.glob("*").each do |file| -%> <%- next if file == "hiera" -%> <%- if File.directory?(file) -%> /bin/rm -Rf "${3}<%= libdir %>/<%= file %>" <%- else -%> /bin/rm -f "${3}<%= libdir %>/<%= file %>" <%- end -%> <%- end -%> <%- end -%> <%- end -%> # remove bin files <%- Dir.chdir("bin") do -%> <%- Dir.glob("*").each do |file| -%> /bin/rm -f "${3}<%= @apple_bindir %>/<%= file %>" <%- end -%> <%- end -%> # remove old doc files /bin/rm -Rf "${3}<%= @apple_docdir %>/<%= @package_name %>" # These files used to live in the sbindir but were # removed in Pupppet >= 3.0. Remove them /bin/rm -f "${3}<%= @apple_sbindir %>/puppetca" /bin/rm -f "${3}<%= @apple_sbindir %>/puppetd" /bin/rm -f "${3}<%= @apple_sbindir %>/puppetmasterd" /bin/rm -f "${3}<%= @apple_sbindir %>/puppetqd" /bin/rm -f "${3}<%= @apple_sbindir %>/puppetrun" puppet-5.5.10/ext/osx/prototype.plist.erb0000644005276200011600000000212613417161721020315 0ustar jenkinsjenkins CFBundleIdentifier <%= @title %> CFBundleShortVersionString <%= @version %> IFMajorVersion <%= @package_major_version %> IFMinorVersion <%= @package_minor_version %> IFPkgBuildDate <%= @build_date %> IFPkgFlagAllowBackRev IFPkgFlagAuthorizationAction RootAuthorization IFPkgFlagDefaultLocation / IFPkgFlagFollowLinks IFPkgFlagInstallFat IFPkgFlagIsRequired IFPkgFlagOverwritePermissions IFPkgFlagRelocatable IFPkgFlagRestartAction <%= @pm_restart %> IFPkgFlagRootVolumeOnly IFPkgFlagUpdateInstalledLanguages puppet-5.5.10/ext/osx/puppet.plist0000644005276200011600000000201113417161721017007 0ustar jenkinsjenkins EnvironmentVariables LANG en_US.UTF-8 Label puppet KeepAlive ProgramArguments /opt/puppetlabs/bin/puppet agent --verbose --no-daemonize --logdest console RunAtLoad StandardErrorPath /var/log/puppetlabs/puppet/puppet.log StandardOutPath /var/log/puppetlabs/puppet/puppet.log puppet-5.5.10/ext/puppet-test0000755005276200011600000003307513417161721016042 0ustar jenkinsjenkins#!/usr/bin/env ruby # == Synopsis # # Test individual client performance. Can compile configurations, describe # files, or retrieve files. # # = Usage # # puppet-test [-c|--compile] [-D|--describe ] [-d|--debug] # [--fork ] [-h|--help] [-H|--hostname ] [-l|--list] [-r|--repeat ] # [-R|--retrieve ] [-t|--test ] [-V|--version] [-v|--verbose] # # = Description # # This is a simple script meant for doing performance tests with Puppet. By # default it pulls down a compiled configuration, but it supports multiple # other tests. # # = Options # # Note that any setting that's valid in the configuration file # is also a valid long argument. For example, 'server' is a valid configuration # parameter, so you can specify '--server ' as an argument. # # See the configuration file documentation at # https://puppet.com/docs/puppet/latest/config_about_settings.html # for the full list of acceptable parameters. A commented list of all # configuration $options can also be generated by running puppetd with # '--genconfig'. # # compile:: # Compile the client's configuration. The default. # # debug:: # Enable full debugging. # # describe:: # Describe the file being tested. This is a query to get information about # the file from the server, to determine if it should be copied, and is the # first half of every file transfer. # # fork:: # Fork the specified number of times, thus acting as multiple clients. # # fqdn:: # Set the fully-qualified domain name of the client. This is only used for # certificate purposes, but can be used to override the discovered hostname. # If you need to use this flag, it is generally an indication of a setup problem. # # help:: # Print this help message # # list:: # List all available tests. # # node:: # Specify the node to use. This is useful for looking up cached yaml data # in your :clientyaml directory, and forcing a specific host's configuration to # get compiled. # # pause:: # Pause before starting test (useful for testing with dtrace). # # repeat:: # How many times to perform the test. # # retrieve:: # Test file retrieval performance. Retrieves the specified file from the # remote system. Note that the server should be specified via --server, # so the argument to this option is just the remote module name and path, # e.g., "/dist/apps/myapp/myfile", where "dist" is the module and # "apps/myapp/myfile" is the path to the file relative to the module. # # test:: # Specify the test to run. You can see the list of tests by running this command with --list. # # verbose:: # Turn on verbose reporting. # # version:: # Print the puppet version number and exit. # # = Example # # puppet-test --retrieve /module/path/to/file # # = License # Copyright 2011 Luke Kanies # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Do an initial trap, so that cancels don't get a stack trace. trap(:INT) do $stderr.puts "Cancelling startup" exit(1) end require 'puppet' require 'puppet/network/client' require 'getoptlong' class Suite attr_reader :name, :doc @@suites = {} @@tests = {} def self.[](name) @@suites[name] end # Run a test by first finding the suite then running the appropriate test. def self.run(test) unless suite_name = @@tests[test] raise "Unknown test %s" % test end unless suite = @@suites[suite_name] raise "Unknown test suite %s from test %s" % [suite_name, test] end suite.run(test) end # What suites are available? def self.suites @@suites.keys end def forked? defined? @forking end # Create a new test suite. def initialize(name, doc, &block) @name = name @doc = doc @tests = {} @@suites[name] = self raise "You must pass a block to the Test" unless block_given? instance_eval(&block) end # Define a new type of test on this suite. def newtest(name, doc, &block) @tests[name] = doc if @@tests[name] raise "Test names must be unique; cannot redefine %s" % name end @@tests[name] = @name meta_def(name, &block) end # Run the actual test. def run(test) unless doc = @tests[test] raise "Suite %s only supports tests %s; not %s" % [@name, @tests.keys.collect { |k| k.to_s }.join(","), test] end puts "Running %s %s test" % [@name, test] prepare() if respond_to?(:prepare) if $options[:pause] puts "Hit any key to continue" $stdin.readline puts "Continuing with test" end if $options[:fork] > 0 @forking = true $options[:fork].times { if pid = fork $pids << pid else break end } end $options[:repeat].times do |i| @count = i escaped_doc = doc.gsub(/%/, '%%') if forked? msg = escaped_doc + " in PID %{process_id} in %%{seconds} seconds" % { process_id: Process.pid } else msg = escaped_doc + " in %{seconds} seconds" end Puppet::Util.benchmark(:notice, msg) do begin send(test) rescue => detail puts detail.backtrace if Puppet[:trace] Puppet.err "%s failed: %s" % [@name, detail.to_s] end end end end # What tests are available on this suite? def tests @tests.keys end end Suite.new :parser, "Manifest parsing" do newtest :parse, "Parsed files" do @parser = Puppet::Parser::Parser.new(:environment => Puppet[:environment]) @parser.file = Puppet[:manifest] @parser.parse end end Suite.new :local_catalog, "Local catalog handling" do def prepare @node = Puppet::Node.find($options[:nodes][0]) end newtest :compile, "Compiled catalog" do Puppet::Resource::Catalog.find(@node) end end Suite.new :remote_catalog, "Remote catalog handling" do def prepare $args[:cache] = false # Create a config client and pull the config down @client = Puppet::Network::Client.master.new($args) unless @client.read_cert fail "Could not read client certificate" end if tmp = Puppet::Node::Facts.find($options[:nodes][0]) @facts = tmp.values else raise "Could not find facts for %s" % $optins[:nodes][0] end if host = $options[:fqdn] @facts["fqdn"] = host @facts["hostname"] = host.sub(/\..+/, '') @facts["domain"] = host.sub(/^[^.]+\./, '') end @facts = YAML.dump(@facts) end newtest :getconfig, "Compiled catalog" do @client.driver.getconfig(@facts, "yaml") end # This test will always force a false answer. newtest :fresh, "Checked freshness" do @client.driver.freshness end end Suite.new :file, "File interactions" do def prepare unless $options[:file] fail "You must specify a file (using --file ) to interact with on the server" end @client = Puppet::Network::Client.file.new($args) unless @client.read_cert fail "Could not read client certificate" end end newtest :describe, "Described file" do @client.describe($options[:file], :ignore) end newtest :retrieve, "Retrieved file" do @client.retrieve($options[:file], :ignore) end end Suite.new :filebucket, "Filebucket interactions" do def prepare require 'tempfile' @client = Puppet::FileBucket::Dipper.new($args) end newtest :backup, "Backed up file" do Tempfile.open("bucket_testing") do |f| f.print rand(1024) f.close @client.backup(f.path) end end end Suite.new :report, "Reports interactions" do def prepare Puppet::Transaction::Report.terminus_class = :rest end newtest :empty, "send empty report" do report = Puppet::Transaction::Report.new report.time = Time.now report.save end newtest :fake, "send fake report" do report = Puppet::Transaction::Report.new resourcemetrics = { :total => 12, :out_of_sync => 20, :applied => 45, :skipped => 1, :restarted => 23, :failed_restarts => 1, :scheduled => 10 } report.newmetric(:resources, resourcemetrics) timemetrics = { :resource1 => 10, :resource2 => 50, :resource3 => 40, :resource4 => 20, } report.newmetric(:times, timemetrics) report.newmetric(:changes, :total => 20 ) report.time = Time.now report.save end end $cmdargs = [ [ "--compile", "-c", GetoptLong::NO_ARGUMENT ], [ "--describe", GetoptLong::REQUIRED_ARGUMENT ], [ "--retrieve", "-R", GetoptLong::REQUIRED_ARGUMENT ], [ "--fork", GetoptLong::REQUIRED_ARGUMENT ], [ "--fqdn", "-F", GetoptLong::REQUIRED_ARGUMENT ], [ "--suite", "-s", GetoptLong::REQUIRED_ARGUMENT ], [ "--test", "-t", GetoptLong::REQUIRED_ARGUMENT ], [ "--pause", "-p", GetoptLong::NO_ARGUMENT ], [ "--repeat", "-r", GetoptLong::REQUIRED_ARGUMENT ], [ "--node", "-n", GetoptLong::REQUIRED_ARGUMENT ], [ "--debug", "-d", GetoptLong::NO_ARGUMENT ], [ "--help", "-h", GetoptLong::NO_ARGUMENT ], [ "--list", "-l", GetoptLong::NO_ARGUMENT ], [ "--verbose", "-v", GetoptLong::NO_ARGUMENT ], [ "--version", "-V", GetoptLong::NO_ARGUMENT ], ] # Add all of the config parameters as valid $options. Puppet.settings.addargs($cmdargs) Puppet::Util::Log.newdestination(:console) Puppet::Node.terminus_class = :plain Puppet::Node.cache_class = :yaml Puppet::Node::Facts.terminus_class = :facter Puppet::Node::Facts.cache_class = :yaml result = GetoptLong.new(*$cmdargs) $args = {} $options = {:repeat => 1, :fork => 0, :pause => false, :nodes => []} begin result.each { |opt,arg| case opt # First check to see if the argument is a valid setting; # if so, set it. when "--compile" $options[:suite] = :configuration $options[:test] = :compile when "--retrieve" $options[:suite] = :file $options[:test] = :retrieve $options[:file] = arg when "--fork" begin $options[:fork] = Integer(arg) rescue $stderr.puts "The argument to 'fork' must be an integer" exit(14) end when "--describe" $options[:suite] = :file $options[:test] = :describe $options[:file] = arg when "--fqdn" $options[:fqdn] = arg when "--repeat" $options[:repeat] = Integer(arg) when "--help" if Puppet.features.usage? RDoc::usage && exit else puts "No help available unless you have RDoc::usage installed" exit end when "--version" puts "%s" % Puppet.version exit when "--verbose" Puppet::Util::Log.level = :info Puppet::Util::Log.newdestination(:console) when "--debug" Puppet::Util::Log.level = :debug Puppet::Util::Log.newdestination(:console) when "--suite" $options[:suite] = arg.intern when "--test" $options[:test] = arg.intern when "--file" $options[:file] = arg when "--pause" $options[:pause] = true when "--node" $options[:nodes] << arg when "--list" Suite.suites.sort { |a,b| a.to_s <=> b.to_s }.each do |suite_name| suite = Suite[suite_name] tests = suite.tests.sort { |a,b| a.to_s <=> b.to_s }.join(", ") puts "%20s: %s" % [suite_name, tests] end exit(0) else Puppet.settings.handlearg(opt, arg) end } rescue GetoptLong::InvalidOption => detail $stderr.puts detail $stderr.puts "Try '#{$0} --help'" exit(1) end # Now parse the config Puppet.initialize_settings $options[:nodes] << Puppet.settings[:certname] if $options[:nodes].empty? $args[:Server] = Puppet[:server] unless $options[:test] $options[:suite] = :remote_catalog $options[:test] = :getconfig end unless $options[:test] raise "A suite was specified without a test" end $pids = [] Suite.run($options[:test]) if $options[:fork] > 0 Process.waitall end puppet-5.5.10/ext/pure_ruby_dsl/0000755005276200011600000000000013417162176016475 5ustar jenkinsjenkinspuppet-5.5.10/ext/pure_ruby_dsl/dsl_test.rb0000644005276200011600000000016713417161721020642 0ustar jenkinsjenkinshostclass "foobar" do notify "this is a test", "loglevel" => "warning" end node "default" do acquire "foobar" end puppet-5.5.10/ext/redhat/0000755005276200011600000000000013417162177015067 5ustar jenkinsjenkinspuppet-5.5.10/ext/redhat/client.init0000644005276200011600000000705613417161721017234 0ustar jenkinsjenkins#!/bin/bash # puppet Init script for running the puppet client daemon # # Author: Duane Griffin # David Lutterkort # # chkconfig: - 98 02 # # description: Enables periodic system configuration checks through puppet. # processname: puppet # config: /etc/sysconfig/puppet # Source function library. . /etc/rc.d/init.d/functions [ -f /etc/sysconfig/puppet ] && . /etc/sysconfig/puppet lockfile=/var/lock/subsys/puppet piddir=/var/run/puppetlabs pidfile="${piddir}/agent.pid" puppetd=/opt/puppetlabs/puppet/bin/puppet pid=$(cat "$pidfile" 2> /dev/null) RETVAL=0 PUPPET_OPTS="agent " # Determine if we can use the -p option to daemon, killproc, and status. # RHEL < 5 can't. if status | grep -q -- '-p' 2>/dev/null; then daemonopts="--pidfile $pidfile" pidopts="-p $pidfile" USEINITFUNCTIONS=true fi # Figure out if the system just booted. Let's assume # boot doesn't take longer than 5 minutes ## Not used for now ##[ -n "$INIT_VERSION" ] && PUPPET_OPTS="${PUPPET_OPTS} --fullrun" start() { echo -n $"Starting puppet agent: " mkdir -p $piddir daemon $daemonopts $puppetd ${PUPPET_OPTS} ${PUPPET_EXTRA_OPTS} RETVAL=$? echo [ $RETVAL = 0 ] && touch ${lockfile} return $RETVAL } stop() { echo -n $"Stopping puppet agent: " if [ "$USEINITFUNCTIONS" = "true" ]; then killproc $pidopts $puppetd RETVAL=$? else if [ -n "${pid}" ]; then kill -TERM $pid >/dev/null 2>&1 RETVAL=$? fi fi echo [ $RETVAL = 0 ] && rm -f ${lockfile} ${pidfile} return $RETVAL } reload() { echo -n $"Reloading puppet agent: " if [ "$USEINITFUNCTIONS" = "true" ]; then killproc $pidopts $puppetd -HUP RETVAL=$? else if [ -n "${pid}" ]; then kill -HUP $pid >/dev/null 2>&1 RETVAL=$? else RETVAL=0 fi fi echo return $RETVAL } rotate() { echo -n $"Reopening log files for puppet agent: " killproc $pidopts $puppetd -USR2 RETVAL=$? echo return $RETVAL } restart() { stop start } rh_status() { base=puppet if [ "$USEINITFUNCTIONS" = "true" ]; then status $pidopts $puppetd RETVAL=$? return $RETVAL else if [ -n "${pid}" ]; then if `ps -p $pid | grep $pid > /dev/null 2>&1`; then echo "${base} (pid ${pid}) is running..." RETVAL=0 return $RETVAL fi fi if [ -f "${pidfile}" ] ; then echo "${base} dead but pid file exists" RETVAL=1 return $RETVAL fi if [ -f "${lockfile}" ]; then echo "${base} dead but subsys locked" RETVAL=2 return $RETVAL fi echo "${base} is stopped" RETVAL=3 return $RETVAL fi } rh_status_q() { rh_status >/dev/null 2>&1 } genconfig() { echo -n $"Generate puppet agent configuration: " $puppetd ${PUPPET_OPTS} ${PUPPET_EXTRA_OPTS} --genconfig } case "$1" in start) start ;; stop) stop ;; restart) restart ;; rotate) rotate ;; reload|force-reload) reload ;; condrestart|try-restart) rh_status_q || exit 0 restart ;; status) rh_status ;; once) shift $puppetd ${PUPPET_OPTS} --onetime ${PUPPET_EXTRA_OPTS} $@ ;; genconfig) genconfig ;; *) echo $"Usage: $0 {start|stop|status|restart|reload|force-reload|condrestart|rotate|once|genconfig}" exit 1 esac exit $RETVAL puppet-5.5.10/ext/redhat/client.sysconfig0000644005276200011600000000013413417161721020263 0ustar jenkinsjenkins# You may specify parameters to the puppet client here #PUPPET_EXTRA_OPTS=--waitforcert=500 puppet-5.5.10/ext/redhat/fileserver.conf0000644005276200011600000000266613417161721020110 0ustar jenkinsjenkins# fileserver.conf # Puppet automatically serves PLUGINS and FILES FROM MODULES: anything in # /files/ is available to authenticated nodes at # puppet:///modules//. You do not need to edit this # file to enable this. # MOUNT POINTS # If you need to serve files from a directory that is NOT in a module, # you must create a static mount point in this file: # # [extra_files] # path /etc/puppet/files # allow * # # In the example above, anything in /etc/puppet/files/ would be # available to authenticated nodes at puppet:///extra_files/. # # Mount points may also use three placeholders as part of their path: # # %H - The node's certname. # %h - The portion of the node's certname before the first dot. (Usually the # node's short hostname.) # %d - The portion of the node's certname after the first dot. (Usually the # node's domain name.) # PERMISSIONS # Every static mount point should have an `allow *` line; setting more # granular permissions in this file is deprecated. Instead, you can # control file access in auth.conf by controlling the # /file_metadata/ and /file_content/ paths: # # path ~ ^/file_(metadata|content)/extra_files/ # auth yes # allow /^(.+)\.example\.com$/ # allow_ip 192.168.100.0/24 # # If added to auth.conf BEFORE the "path /file" rule, the rule above # will add stricter restrictions to the extra_files mount point. puppet-5.5.10/ext/redhat/logrotate0000644005276200011600000000112313417161721017001 0ustar jenkinsjenkins# Generic logrotate config for Red Hat systems. Works with both official # Fedora and RHEL packages as well as PuppetLabs distributions. /var/log/puppetlabs/masterhttp.log /var/log/puppet/masterhttp.log { compress missingok notifempty nocreate } /var/log/puppetlabs/puppetd.log /var/log/puppet/puppetd.log { compress missingok notifempty nocreate sharedscripts postrotate ([ -x /etc/init.d/puppet ] && /etc/init.d/puppet rotate > /dev/null 2>&1) || ([ -x /usr/bin/systemctl ] && /usr/bin/systemctl kill -s USR2 puppet.service > /dev/null 2>&1) || true endscript } puppet-5.5.10/ext/redhat/server.init0000644005276200011600000000612413417161721017257 0ustar jenkinsjenkins#!/bin/bash # puppetmaster This shell script enables the puppetmaster server. # # Authors: Duane Griffin # Peter Meier (Mongrel enhancements) # # chkconfig: - 65 45 # # description: Server for the puppet system management tool. # processname: puppetmaster PATH=/usr/bin:/sbin:/bin:/usr/sbin export PATH lockfile=/var/lock/subsys/puppetmaster pidfile=/var/run/puppetlabs/master.pid # Source function library. . /etc/rc.d/init.d/functions if [ -f /etc/sysconfig/puppetmaster ]; then . /etc/sysconfig/puppetmaster fi PUPPETMASTER_OPTS="master " [ -n "$PUPPETMASTER_MANIFEST" ] && PUPPETMASTER_OPTS="${PUPPETMASTER_OPTS} --manifest=${PUPPETMASTER_MANIFEST}" [ -n "$PUPPETMASTER_PORTS" ] && PUPPETMASTER_OPTS="${PUPPETMASTER_OPTS} --masterport=${PUPPETMASTER_PORTS[0]}" [ -n "$PUPPETMASTER_LOG" ] && PUPPETMASTER_OPTS="${PUPPETMASTER_OPTS} --logdest ${PUPPETMASTER_LOG}" PUPPETMASTER_OPTS="${PUPPETMASTER_OPTS} \ ${PUPPETMASTER_EXTRA_OPTS}" # Determine if we can use the -p option to daemon, killproc, and status. # RHEL < 5 can't. if status | grep -q -- '-p' 2>/dev/null; then daemonopts="--pidfile $pidfile" pidopts="-p $pidfile" fi mongrel_warning="The mongrel servertype is no longer built-in to Puppet. It appears as though mongrel is being used, as the number of ports is greater than one. Starting the puppetmaster service will not behave as expected until this is resolved. Only the first port has been used in the service. These settings are defined at /etc/sysconfig/puppetmaster" # Warn about removed and unsupported mongrel servertype if [ -n "$PUPPETMASTER_PORTS" ] && [ ${#PUPPETMASTER_PORTS[@]} -gt 1 ]; then echo $mongrel_warning echo fi RETVAL=0 PUPPETMASTER=/usr/bin/puppet start() { echo -n $"Starting puppetmaster: " # Confirm the manifest exists if [ -r $PUPPETMASTER_MANIFEST ]; then daemon $daemonopts $PUPPETMASTER $PUPPETMASTER_OPTS RETVAL=$? else failure $"Manifest does not exist: $PUPPETMASTER_MANIFEST" echo return 1 fi [ $RETVAL -eq 0 ] && touch "$lockfile" echo return $RETVAL } stop() { echo -n $"Stopping puppetmaster: " killproc $pidopts $PUPPETMASTER RETVAL=$? echo [ $RETVAL -eq 0 ] && rm -f "$lockfile" return $RETVAL } restart() { stop start } genconfig() { echo -n $"Generate configuration puppetmaster: " $PUPPETMASTER $PUPPETMASTER_OPTS --genconfig } rh_status() { status $pidopts -l $lockfile $PUPPETMASTER RETVAL=$? return $RETVAL } rh_status_q() { rh_status >/dev/null 2>&1 } case "$1" in start) start ;; stop) stop ;; restart|reload|force-reload) restart ;; condrestart) rh_status_q || exit 0 restart ;; status) rh_status ;; genconfig) genconfig ;; *) echo $"Usage: $0 {start|stop|status|restart|reload|force-reload|condrestart|genconfig}" exit 1 esac exit $RETVAL # vim: tabstop=4:softtabstop=4:shiftwidth=4:expandtab puppet-5.5.10/ext/redhat/server.sysconfig0000644005276200011600000000077613417161721020327 0ustar jenkinsjenkins# Location of the main manifest #PUPPETMASTER_MANIFEST=/etc/puppet/manifests/site.pp # Where to log general messages to. # Specify syslog to send log messages to the system log. #PUPPETMASTER_LOG=syslog # You may specify an alternate port on which your puppetmaster should listen. Default is: 8140 # An example with puppetmaster on a different port, run with standard webrick servertype #PUPPETMASTER_PORTS="8141" # You may specify other parameters to the puppetmaster here #PUPPETMASTER_EXTRA_OPTS=--no-ca puppet-5.5.10/ext/redhat/puppet.spec0000644005276200011600000007325613417162177017275 0ustar jenkinsjenkins# Augeas and SELinux requirements may be disabled at build time by passing # --without augeas and/or --without selinux to rpmbuild or mock # Fedora 17 ships with ruby 1.9, RHEL 7 with ruby 2.0, which use vendorlibdir instead # of sitelibdir. Adjust our target if installing on f17 or rhel7. %if 0%{?fedora} >= 17 || 0%{?rhel} >= 7 || 0%{?amzn} >= 1 %global puppet_libdir %(ruby -rrbconfig -e 'puts RbConfig::CONFIG["vendorlibdir"]') %else %global puppet_libdir %(ruby -rrbconfig -e 'puts RbConfig::CONFIG["sitelibdir"]') %endif %if 0%{?fedora} >= 17 || 0%{?rhel} >= 7 %global _with_systemd 1 %else %global _with_systemd 0 %endif # VERSION is subbed out during rake srpm process %global realversion 5.5.10 %global rpmversion 5.5.10 %global confdir ext/redhat %global pending_upgrade_path %{_localstatedir}/lib/rpm-state/puppet %global pending_upgrade_file %{pending_upgrade_path}/upgrade_pending Name: puppet Version: %{rpmversion} Release: 1%{?dist} Vendor: %{?_host_vendor} Summary: A network tool for managing many disparate systems License: ASL 2.0 URL: https://puppetlabs.com Source0: https://puppetlabs.com/downloads/%{name}/%{name}-%{realversion}.tar.gz Group: System Environment/Base BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) BuildRequires: facter >= 1:1.7.0 # Puppet 3.x drops ruby 1.8.5 support and adds ruby 1.9 support BuildRequires: ruby >= 1.8.7 BuildRequires: hiera >= 2.0.0 BuildArch: noarch Requires: ruby >= 1.8 Requires: ruby-shadow Requires: rubygem-json # Pull in ruby selinux bindings where available %if 0%{?fedora} || 0%{?rhel} >= 6 %{!?_without_selinux:Requires: ruby(selinux), libselinux-utils} %else %if ( 0%{?rhel} && 0%{?rhel} == 5 ) || 0%{?amzn} >= 1 %{!?_without_selinux:Requires: libselinux-ruby, libselinux-utils} %endif %endif Requires: facter >= 1:1.7.0 # Puppet 3.x drops ruby 1.8.5 support and adds ruby 1.9 support # Ruby 1.8.7 available for el5 at: yum.puppetlabs.com/el/5/devel/$ARCH Requires: ruby >= 1.8.7 Requires: hiera >= 2.0.0 Obsoletes: hiera-puppet < 1.0.0 Provides: hiera-puppet >= 1.0.0 %{!?_without_augeas:Requires: ruby-augeas} # Required for %%pre Requires: shadow-utils %if 0%{?_with_systemd} # Required for %%post, %%preun, %%postun Requires: systemd %if 0%{?fedora} >= 18 || 0%{?rhel} >= 7 BuildRequires: systemd %else BuildRequires: systemd-units %endif %else # Required for %%post and %%preun Requires: chkconfig # Required for %%preun and %%postun Requires: initscripts %endif %description Puppet lets you centrally manage every important aspect of your system using a cross-platform specification language that manages all the separate elements normally aggregated in different files, like users, cron jobs, and hosts, along with obviously discrete elements like packages, services, and files. %package server Group: System Environment/Base Summary: Server for the puppet system management tool Requires: puppet = %{version}-%{release} # chkconfig (%%post, %%preun) and initscripts (%%preun %%postun) are required for non systemd # and systemd (%%post, %%preun, and %%postun) are required for systems with systemd as default # They come along transitively with puppet-%{version}-%{release}. %description server Provides the central puppet server daemon which provides manifests to clients. The server can also function as a certificate authority and file server. %prep %setup -q -n %{name}-%{realversion} %build for f in external/nagios.rb relationship.rb; do sed -i -e '1d' lib/puppet/$f done find examples/ -type f | xargs --no-run-if-empty chmod a-x %install rm -rf %{buildroot} ruby install.rb --destdir=%{buildroot} --quick --no-rdoc --sitelibdir=%{puppet_libdir} install -d -m0755 %{buildroot}%{_sysconfdir}/puppet/environments/example_env/manifests install -d -m0755 %{buildroot}%{_sysconfdir}/puppet/environments/example_env/modules install -d -m0755 %{buildroot}%{_sysconfdir}/puppet/manifests install -d -m0755 %{buildroot}%{_datadir}/%{name}/modules install -d -m0755 %{buildroot}%{_localstatedir}/lib/puppet install -d -m0755 %{buildroot}%{_localstatedir}/lib/puppet/state install -d -m0755 %{buildroot}%{_localstatedir}/lib/puppet/reports install -d -m0755 %{buildroot}%{_localstatedir}/run/puppet # As per redhat bz #495096 install -d -m0750 %{buildroot}%{_localstatedir}/log/puppet %if 0%{?_with_systemd} # Systemd for fedora >= 17 or el 7 %{__install} -d -m0755 %{buildroot}%{_unitdir} install -Dp -m0644 ext/systemd/puppet.service %{buildroot}%{_unitdir}/puppet.service ln -s %{_unitdir}/puppet.service %{buildroot}%{_unitdir}/puppetagent.service install -Dp -m0644 ext/systemd/puppetmaster.service %{buildroot}%{_unitdir}/puppetmaster.service %else # Otherwise init.d for fedora < 17 or el 5, 6 install -Dp -m0644 %{confdir}/client.sysconfig %{buildroot}%{_sysconfdir}/sysconfig/puppet install -Dp -m0755 %{confdir}/client.init %{buildroot}%{_initrddir}/puppet install -Dp -m0644 %{confdir}/server.sysconfig %{buildroot}%{_sysconfdir}/sysconfig/puppetmaster install -Dp -m0755 %{confdir}/server.init %{buildroot}%{_initrddir}/puppetmaster %endif install -Dp -m0644 %{confdir}/fileserver.conf %{buildroot}%{_sysconfdir}/puppet/fileserver.conf install -Dp -m0644 %{confdir}/puppet.conf %{buildroot}%{_sysconfdir}/puppet/puppet.conf install -Dp -m0644 %{confdir}/logrotate %{buildroot}%{_sysconfdir}/logrotate.d/puppet install -Dp -m0644 ext/README.environment %{buildroot}%{_sysconfdir}/puppet/environments/example_env/README.environment # Install the ext/ directory to %%{_datadir}/%%{name} install -d %{buildroot}%{_datadir}/%{name} cp -a ext/ %{buildroot}%{_datadir}/%{name} # emacs and vim bits are installed elsewhere rm -rf %{buildroot}%{_datadir}/%{name}/ext/{emacs,vim} # remove misc packaging artifacts not applicable to rpms rm -rf %{buildroot}%{_datadir}/%{name}/ext/{gentoo,freebsd,solaris,suse,windows,osx,ips,debian} rm -f %{buildroot}%{_datadir}/%{name}/ext/redhat/*.init rm -f %{buildroot}%{_datadir}/%{name}/ext/{build_defaults.yaml,project_data.yaml} # Rpmlint fixup chmod 755 %{buildroot}%{_datadir}/%{name}/ext/regexp_nodes/regexp_nodes.rb # Install emacs mode files emacsdir=%{buildroot}%{_datadir}/emacs/site-lisp install -Dp -m0644 ext/emacs/puppet-mode.el $emacsdir/puppet-mode.el install -Dp -m0644 ext/emacs/puppet-mode-init.el \ $emacsdir/site-start.d/puppet-mode-init.el # Install vim syntax files vimdir=%{buildroot}%{_datadir}/vim/vimfiles install -Dp -m0644 ext/vim/ftdetect/puppet.vim $vimdir/ftdetect/puppet.vim install -Dp -m0644 ext/vim/syntax/puppet.vim $vimdir/syntax/puppet.vim install -Dp -m0644 ext/vim/indent/puppet.vim $vimdir/indent/puppet.vim install -Dp -m0644 ext/vim/ftplugin/puppet.vim $vimdir/ftplugin/puppet.vim %if 0%{?fedora} >= 15 || 0%{?rhel} >= 7 # Setup tmpfiles.d config mkdir -p %{buildroot}%{_sysconfdir}/tmpfiles.d echo "D /var/run/%{name} 0755 %{name} %{name} -" > \ %{buildroot}%{_sysconfdir}/tmpfiles.d/%{name}.conf %endif # Create puppet modules directory for puppet module tool mkdir -p %{buildroot}%{_sysconfdir}/%{name}/modules # Masterhttp.log is created on server start and should be distributed # with correct permissions touch %{buildroot}%{_localstatedir}/log/puppet/masterhttp.log %files %defattr(-, root, root, 0755) %doc LICENSE README.md examples %{_bindir}/puppet %{puppet_libdir}/* %if 0%{?_with_systemd} %{_unitdir}/puppet.service %{_unitdir}/puppetagent.service %else %{_initrddir}/puppet %config(noreplace) %{_sysconfdir}/sysconfig/puppet %endif %dir %{_sysconfdir}/puppet %dir %{_sysconfdir}/%{name}/modules %if 0%{?fedora} >= 15 || 0%{?rhel} >= 7 %config(noreplace) %{_sysconfdir}/tmpfiles.d/%{name}.conf %endif %config(noreplace) %{_sysconfdir}/puppet/puppet.conf %config(noreplace) %{_sysconfdir}/puppet/auth.conf %config(noreplace) %{_sysconfdir}/logrotate.d/puppet # We don't want to require emacs or vim, so we need to own these dirs %{_datadir}/emacs %{_datadir}/vim %{_datadir}/%{name} # man pages %{_mandir}/man5/puppet.conf.5.gz %{_mandir}/man8/puppet.8.gz %{_mandir}/man8/puppet-agent.8.gz %{_mandir}/man8/puppet-apply.8.gz %{_mandir}/man8/puppet-catalog.8.gz %{_mandir}/man8/puppet-describe.8.gz %{_mandir}/man8/puppet-ca.8.gz %{_mandir}/man8/puppet-cert.8.gz %{_mandir}/man8/puppet-certificate.8.gz %{_mandir}/man8/puppet-certificate_request.8.gz %{_mandir}/man8/puppet-certificate_revocation_list.8.gz %{_mandir}/man8/puppet-config.8.gz %{_mandir}/man8/puppet-device.8.gz %{_mandir}/man8/puppet-doc.8.gz %{_mandir}/man8/puppet-facts.8.gz %{_mandir}/man8/puppet-file.8.gz %{_mandir}/man8/puppet-filebucket.8.gz %{_mandir}/man8/puppet-help.8.gz %{_mandir}/man8/puppet-inspect.8.gz %{_mandir}/man8/puppet-key.8.gz %{_mandir}/man8/puppet-kick.8.gz %{_mandir}/man8/puppet-man.8.gz %{_mandir}/man8/puppet-module.8.gz %{_mandir}/man8/puppet-node.8.gz %{_mandir}/man8/puppet-parser.8.gz %{_mandir}/man8/puppet-plugin.8.gz %{_mandir}/man8/puppet-report.8.gz %{_mandir}/man8/puppet-resource.8.gz %{_mandir}/man8/puppet-status.8.gz # These need to be owned by puppet so the server can # write to them. The separate %defattr's are required # to work around RH Bugzilla 681540 %defattr(-, puppet, puppet, 0755) %{_localstatedir}/run/puppet %defattr(-, puppet, puppet, 0750) %{_localstatedir}/log/puppet %{_localstatedir}/lib/puppet %{_localstatedir}/lib/puppet/state %{_localstatedir}/lib/puppet/reports # Return the default attributes to 0755 to # prevent incorrect permission assignment on EL6 %defattr(-, root, root, 0755) %files server %defattr(-, root, root, 0755) %if 0%{?_with_systemd} %{_unitdir}/puppetmaster.service %else %{_initrddir}/puppetmaster %config(noreplace) %{_sysconfdir}/sysconfig/puppetmaster %endif %config(noreplace) %{_sysconfdir}/puppet/fileserver.conf %dir %{_sysconfdir}/puppet/manifests %dir %{_sysconfdir}/puppet/environments %dir %{_sysconfdir}/puppet/environments/example_env %dir %{_sysconfdir}/puppet/environments/example_env/manifests %dir %{_sysconfdir}/puppet/environments/example_env/modules %{_sysconfdir}/puppet/environments/example_env/README.environment %{_mandir}/man8/puppet-ca.8.gz %{_mandir}/man8/puppet-master.8.gz %attr(660, puppet, puppet) %{_localstatedir}/log/puppet/masterhttp.log # Fixed uid/gid were assigned in bz 472073 (Fedora), 471918 (RHEL-5), # and 471919 (RHEL-4) %pre getent group puppet &>/dev/null || groupadd -r puppet -g 52 &>/dev/null getent passwd puppet &>/dev/null || \ useradd -r -u 52 -g puppet -d %{_localstatedir}/lib/puppet -s /sbin/nologin \ -c "Puppet" puppet &>/dev/null # ensure that old setups have the right puppet home dir if [ $1 -gt 1 ] ; then usermod -d %{_localstatedir}/lib/puppet puppet &>/dev/null fi exit 0 %post %if 0%{?_with_systemd} /bin/systemctl daemon-reload >/dev/null 2>&1 || : if [ "$1" -ge 1 ]; then # The pidfile changed from 0.25.x to 2.6.x, handle upgrades without leaving # the old process running. oldpid="%{_localstatedir}/run/puppet/puppetd.pid" newpid="%{_localstatedir}/run/puppet/agent.pid" if [ -s "$oldpid" -a ! -s "$newpid" ]; then (kill $(< "$oldpid") && rm -f "$oldpid" && \ /bin/systemctl start puppet.service) >/dev/null 2>&1 || : fi fi %else /sbin/chkconfig --add puppet || : if [ "$1" -ge 1 ]; then # The pidfile changed from 0.25.x to 2.6.x, handle upgrades without leaving # the old process running. oldpid="%{_localstatedir}/run/puppet/puppetd.pid" newpid="%{_localstatedir}/run/puppet/agent.pid" if [ -s "$oldpid" -a ! -s "$newpid" ]; then (kill $(< "$oldpid") && rm -f "$oldpid" && \ /sbin/service puppet start) >/dev/null 2>&1 || : fi # If an old puppet process (one whose binary is located in /sbin) is running, # kill it and then start up a fresh with the new binary. if [ -e "$newpid" ]; then if ps aux | grep `cat "$newpid"` | grep -v grep | awk '{ print $12 }' | grep -q sbin; then (kill $(< "$newpid") && rm -f "$newpid" && \ /sbin/service puppet start) >/dev/null 2>&1 || : fi fi fi %endif %post server %if 0%{?_with_systemd} /bin/systemctl daemon-reload >/dev/null 2>&1 || : if [ "$1" -ge 1 ]; then # The pidfile changed from 0.25.x to 2.6.x, handle upgrades without leaving # the old process running. oldpid="%{_localstatedir}/run/puppet/puppetmasterd.pid" newpid="%{_localstatedir}/run/puppet/master.pid" if [ -s "$oldpid" -a ! -s "$newpid" ]; then (kill $(< "$oldpid") && rm -f "$oldpid" && \ /bin/systemctl start puppetmaster.service) > /dev/null 2>&1 || : fi fi %else /sbin/chkconfig --add puppetmaster || : if [ "$1" -ge 1 ]; then # The pidfile changed from 0.25.x to 2.6.x, handle upgrades without leaving # the old process running. oldpid="%{_localstatedir}/run/puppet/puppetmasterd.pid" newpid="%{_localstatedir}/run/puppet/master.pid" if [ -s "$oldpid" -a ! -s "$newpid" ]; then (kill $(< "$oldpid") && rm -f "$oldpid" && \ /sbin/service puppetmaster start) >/dev/null 2>&1 || : fi fi %endif %preun %if 0%{?_with_systemd} if [ "$1" -eq 0 ] ; then # Package removal, not upgrade /bin/systemctl --no-reload disable puppetagent.service > /dev/null 2>&1 || : /bin/systemctl --no-reload disable puppet.service > /dev/null 2>&1 || : /bin/systemctl stop puppetagent.service > /dev/null 2>&1 || : /bin/systemctl stop puppet.service > /dev/null 2>&1 || : /bin/systemctl daemon-reload >/dev/null 2>&1 || : fi if [ "$1" == "1" ]; then /bin/systemctl is-enabled puppetagent.service > /dev/null 2>&1 if [ "$?" == "0" ]; then /bin/systemctl --no-reload disable puppetagent.service > /dev/null 2>&1 ||: /bin/systemctl stop puppetagent.service > /dev/null 2>&1 ||: /bin/systemctl daemon-reload >/dev/null 2>&1 ||: if [ ! -d %{pending_upgrade_path} ]; then mkdir -p %{pending_upgrade_path} fi if [ ! -e %{pending_upgrade_file} ]; then touch %{pending_upgrade_file} fi fi fi %else if [ "$1" = 0 ] ; then /sbin/service puppet stop > /dev/null 2>&1 /sbin/chkconfig --del puppet || : fi %endif %preun server %if 0%{?_with_systemd} if [ $1 -eq 0 ] ; then # Package removal, not upgrade /bin/systemctl --no-reload disable puppetmaster.service > /dev/null 2>&1 || : /bin/systemctl stop puppetmaster.service > /dev/null 2>&1 || : /bin/systemctl daemon-reload >/dev/null 2>&1 || : fi %else if [ "$1" = 0 ] ; then /sbin/service puppetmaster stop > /dev/null 2>&1 /sbin/chkconfig --del puppetmaster || : fi %endif %postun %if 0%{?_with_systemd} if [ $1 -ge 1 ] ; then if [ -e %{pending_upgrade_file} ]; then /bin/systemctl --no-reload enable puppet.service > /dev/null 2>&1 ||: /bin/systemctl start puppet.service > /dev/null 2>&1 ||: /bin/systemctl daemon-reload >/dev/null 2>&1 ||: rm %{pending_upgrade_file} fi # Package upgrade, not uninstall /bin/systemctl try-restart puppetagent.service >/dev/null 2>&1 || : fi %else if [ "$1" -ge 1 ]; then /sbin/service puppet condrestart >/dev/null 2>&1 || : fi %endif %postun server %if 0%{?_with_systemd} if [ $1 -ge 1 ] ; then # Package upgrade, not uninstall /bin/systemctl try-restart puppetmaster.service >/dev/null 2>&1 || : fi %else if [ "$1" -ge 1 ]; then /sbin/service puppetmaster condrestart >/dev/null 2>&1 || : fi %endif %clean rm -rf %{buildroot} %changelog * Mon Jan 14 2019 Puppet Labs Release - 5.5.10-1 - Build for 5.5.10 * Wed Oct 2 2013 Jason Antman - Move systemd service and unit file names back to "puppet" from erroneous "puppetagent" - Add symlink to puppetagent unit file for compatibility with current bug - Alter package removal actions to deactivate and stop both service names * Thu Jun 27 2013 Matthaus Owens - 3.2.3-0.1rc0 - Bump requires on ruby-rgen to 0.6.5 * Fri Apr 12 2013 Matthaus Owens - 3.2.0-0.1rc0 - Add requires on ruby-rgen for new parser in Puppet 3.2 * Fri Jan 25 2013 Matthaus Owens - 3.1.0-0.1rc1 - Add extlookup2hiera.8.gz to the files list * Wed Jan 9 2013 Ryan Uber - 3.1.0-0.1rc1 - Work-around for RH Bugzilla 681540 * Fri Dec 28 2012 Michael Stahnke - 3.0.2-2 - Added a script for Network Manager for bug https://bugzilla.redhat.com/532085 * Tue Dec 18 2012 Matthaus Owens - Remove for loop on examples/ code which no longer exists. Add --no-run-if-empty to xargs invocations. * Sat Dec 1 2012 Ryan Uber - Fix for logdir perms regression (#17866) * Wed Aug 29 2012 Moses Mendoza - 3.0.0-0.1rc5 - Update for 3.0.0 rc5 * Fri Aug 24 2012 Eric Sorenson - 3.0.0-0.1rc4 - Facter requirement is 1.6.11, not 2.0 - Update for 3.0.0 rc4 * Tue Aug 21 2012 Moses Mendoza - 2.7.19-1 - Update for 2.7.19 * Tue Aug 14 2012 Moses Mendoza - 2.7.19-0.1rc3 - Update for 2.7.19rc3 * Tue Aug 7 2012 Moses Mendoza - 2.7.19-0.1rc2 - Update for 2.7.19rc2 * Wed Aug 1 2012 Moses Mendoza - 2.7.19-0.1rc1 - Update for 2.7.19rc1 * Wed Jul 11 2012 William Hopper - 2.7.18-2 - (#15221) Create /etc/puppet/modules for puppet module tool * Mon Jul 9 2012 Moses Mendoza - 2.7.18-1 - Update for 2.7.18 * Tue Jun 19 2012 Matthaus Litteken - 2.7.17-1 - Update for 2.7.17 * Wed Jun 13 2012 Matthaus Litteken - 2.7.16-1 - Update for 2.7.16 * Fri Jun 08 2012 Moses Mendoza - 2.7.16-0.1rc1.2 - Updated facter 2.0 dep to include epoch 1 * Wed Jun 06 2012 Matthaus Litteken - 2.7.16-0.1rc1 - Update for 2.7.16rc1, added generated manpages * Fri Jun 01 2012 Matthaus Litteken - 3.0.0-0.1rc3 - Puppet 3.0.0rc3 Release * Fri Jun 01 2012 Matthaus Litteken - 2.7.15-0.1rc4 - Update for 2.7.15rc4 * Tue May 29 2012 Moses Mendoza - 2.7.15-0.1rc3 - Update for 2.7.15rc3 * Tue May 22 2012 Matthaus Litteken - 3.0.0-0.1rc2 - Puppet 3.0.0rc2 Release * Thu May 17 2012 Matthaus Litteken - 3.0.0-0.1rc1 - Puppet 3.0.0rc1 Release * Wed May 16 2012 Moses Mendoza - 2.7.15-0.1rc2 - Update for 2.7.15rc2 * Tue May 15 2012 Moses Mendoza - 2.7.15-0.1rc1 - Update for 2.7.15rc1 * Wed May 02 2012 Moses Mendoza - 2.7.14-1 - Update for 2.7.14 * Tue Apr 10 2012 Matthaus Litteken - 2.7.13-1 - Update for 2.7.13 * Mon Mar 12 2012 Michael Stahnke - 2.7.12-1 - Update for 2.7.12 * Fri Feb 24 2012 Matthaus Litteken - 2.7.11-2 - Update 2.7.11 from proper tag, including #12572 * Wed Feb 22 2012 Michael Stahnke - 2.7.11-1 - Update for 2.7.11 * Wed Jan 25 2012 Michael Stahnke - 2.7.10-1 - Update for 2.7.10 * Fri Dec 9 2011 Matthaus Litteken - 2.7.9-1 - Update for 2.7.9 * Thu Dec 8 2011 Matthaus Litteken - 2.7.8-1 - Update for 2.7.8 * Wed Nov 30 2011 Michael Stahnke - 2.7.8-0.1rc1 - Update for 2.7.8rc1 * Mon Nov 21 2011 Michael Stahnke - 2.7.7-1 - Relaese 2.7.7 * Tue Nov 01 2011 Michael Stahnke - 2.7.7-0.1rc1 - Update for 2.7.7rc1 * Fri Oct 21 2011 Michael Stahnke - 2.7.6-1 - 2.7.6 final * Thu Oct 13 2011 Michael Stahnke - 2.7.6-.1rc3 - New RC * Fri Oct 07 2011 Michael Stahnke - 2.7.6-0.1rc2 - New RC * Mon Oct 03 2011 Michael Stahnke - 2.7.6-0.1rc1 - New RC * Fri Sep 30 2011 Michael Stahnke - 2.7.5-1 - Fixes for CVE-2011-3869, 3870, 3871 * Wed Sep 28 2011 Michael Stahnke - 2.7.4-1 - Fix for CVE-2011-3484 * Wed Jul 06 2011 Michael Stahnke - 2.7.2-0.2.rc1 - Clean up rpmlint errors - Put man pages in correct package * Wed Jul 06 2011 Michael Stahnke - 2.7.2-0.1.rc1 - Update to 2.7.2rc1 * Wed Jun 15 2011 Todd Zullinger - 2.6.9-0.1.rc1 - Update rc versioning to ensure 2.6.9 final is newer to rpm - sync changes with Fedora/EPEL * Tue Jun 14 2011 Michael Stahnke - 2.6.9rc1-1 - Update to 2.6.9rc1 * Thu Apr 14 2011 Todd Zullinger - 2.6.8-1 - Update to 2.6.8 * Thu Mar 24 2011 Todd Zullinger - 2.6.7-1 - Update to 2.6.7 * Wed Mar 16 2011 Todd Zullinger - 2.6.6-1 - Update to 2.6.6 - Ensure %%pre exits cleanly - Fix License tag, puppet is now GPLv2 only - Create and own /usr/share/puppet/modules (#615432) - Properly restart puppet agent/master daemons on upgrades from 0.25.x - Require libselinux-utils when selinux support is enabled - Support tmpfiles.d for Fedora >= 15 (#656677) * Wed Feb 09 2011 Fedora Release Engineering - 0.25.5-2 - Rebuilt for https://fedoraproject.org/wiki/Fedora_15_Mass_Rebuild * Mon May 17 2010 Todd Zullinger - 0.25.5-1 - Update to 0.25.5 - Adjust selinux conditional for EL-6 - Apply rundir-perms patch from tarball rather than including it separately - Update URL's to reflect the new puppetlabs.com domain * Fri Jan 29 2010 Todd Zullinger - 0.25.4-1 - Update to 0.25.4 * Tue Jan 19 2010 Todd Zullinger - 0.25.3-2 - Apply upstream patch to fix cron resources (upstream #2845) * Mon Jan 11 2010 Todd Zullinger - 0.25.3-1 - Update to 0.25.3 * Tue Jan 05 2010 Todd Zullinger - 0.25.2-1.1 - Replace %%define with %%global for macros * Tue Jan 05 2010 Todd Zullinger - 0.25.2-1 - Update to 0.25.2 - Fixes CVE-2010-0156, tmpfile security issue (#502881) - Install auth.conf, puppetqd manpage, and queuing examples/docs * Wed Nov 25 2009 Jeroen van Meeuwen - 0.25.1-1 - New upstream version * Tue Oct 27 2009 Todd Zullinger - 0.25.1-0.3 - Update to 0.25.1 - Include the pi program and man page (R.I.Pienaar) * Sat Oct 17 2009 Todd Zullinger - 0.25.1-0.2.rc2 - Update to 0.25.1rc2 * Tue Sep 22 2009 Todd Zullinger - 0.25.1-0.1.rc1 - Update to 0.25.1rc1 - Move puppetca to puppet package, it has uses on client systems - Drop redundant %%doc from manpage %%file listings * Fri Sep 04 2009 Todd Zullinger - 0.25.0-1 - Update to 0.25.0 - Fix permissions on /var/log/puppet (#495096) - Install emacs mode and vim syntax files (#491437) - Install ext/ directory in %%{_datadir}/%%{name} (/usr/share/puppet) * Mon May 04 2009 Todd Zullinger - 0.25.0-0.1.beta1 - Update to 0.25.0beta1 - Make Augeas and SELinux requirements build time options * Mon Mar 23 2009 Todd Zullinger - 0.24.8-1 - Update to 0.24.8 - Quiet output from %%pre - Use upstream install script - Increase required facter version to >= 1.5 * Tue Dec 16 2008 Todd Zullinger - 0.24.7-4 - Remove redundant useradd from %%pre * Tue Dec 16 2008 Jeroen van Meeuwen - 0.24.7-3 - New upstream version - Set a static uid and gid (#472073, #471918, #471919) - Add a conditional requirement on libselinux-ruby for Fedora >= 9 - Add a dependency on ruby-augeas * Wed Oct 22 2008 Todd Zullinger - 0.24.6-1 - Update to 0.24.6 - Require ruby-shadow on Fedora and RHEL >= 5 - Simplify Fedora/RHEL version checks for ruby(abi) and BuildArch - Require chkconfig and initstripts for preun, post, and postun scripts - Conditionally restart puppet in %%postun - Ensure %%preun, %%post, and %%postun scripts exit cleanly - Create puppet user/group according to Fedora packaging guidelines - Quiet a few rpmlint complaints - Remove useless %%pbuild macro - Make specfile more like the Fedora/EPEL template * Mon Jul 28 2008 David Lutterkort - 0.24.5-1 - Add /usr/bin/puppetdoc * Thu Jul 24 2008 Brenton Leanhardt - New version - man pages now ship with tarball - examples/code moved to root examples dir in upstream tarball * Tue Mar 25 2008 David Lutterkort - 0.24.4-1 - Add man pages (from separate tarball, upstream will fix to include in main tarball) * Mon Mar 24 2008 David Lutterkort - 0.24.3-1 - New version * Wed Mar 5 2008 David Lutterkort - 0.24.2-1 - New version * Sat Dec 22 2007 David Lutterkort - 0.24.1-1 - New version * Mon Dec 17 2007 David Lutterkort - 0.24.0-2 - Use updated upstream tarball that contains yumhelper.py * Fri Dec 14 2007 David Lutterkort - 0.24.0-1 - Fixed license - Munge examples/ to make rpmlint happier * Wed Aug 22 2007 David Lutterkort - 0.23.2-1 - New version * Thu Jul 26 2007 David Lutterkort - 0.23.1-1 - Remove old config files * Wed Jun 20 2007 David Lutterkort - 0.23.0-1 - Install one puppet.conf instead of old config files, keep old configs around to ease update - Use plain shell commands in install instead of macros * Wed May 2 2007 David Lutterkort - 0.22.4-1 - New version * Thu Mar 29 2007 David Lutterkort - 0.22.3-1 - Claim ownership of _sysconfdir/puppet (bz 233908) * Mon Mar 19 2007 David Lutterkort - 0.22.2-1 - Set puppet's homedir to /var/lib/puppet, not /var/puppet - Remove no-lockdir patch, not needed anymore * Mon Feb 12 2007 David Lutterkort - 0.22.1-2 - Fix bogus config parameter in puppetd.conf * Sat Feb 3 2007 David Lutterkort - 0.22.1-1 - New version * Fri Jan 5 2007 David Lutterkort - 0.22.0-1 - New version * Mon Nov 20 2006 David Lutterkort - 0.20.1-2 - Make require ruby(abi) and buildarch: noarch conditional for fedora 5 or later to allow building on older fedora releases * Mon Nov 13 2006 David Lutterkort - 0.20.1-1 - New version * Mon Oct 23 2006 David Lutterkort - 0.20.0-1 - New version * Tue Sep 26 2006 David Lutterkort - 0.19.3-1 - New version * Mon Sep 18 2006 David Lutterkort - 0.19.1-1 - New version * Thu Sep 7 2006 David Lutterkort - 0.19.0-1 - New version * Tue Aug 1 2006 David Lutterkort - 0.18.4-2 - Use /usr/bin/ruby directly instead of /usr/bin/env ruby in executables. Otherwise, initscripts break since pidof can't find the right process * Tue Aug 1 2006 David Lutterkort - 0.18.4-1 - New version * Fri Jul 14 2006 David Lutterkort - 0.18.3-1 - New version * Wed Jul 5 2006 David Lutterkort - 0.18.2-1 - New version * Wed Jun 28 2006 David Lutterkort - 0.18.1-1 - Removed lsb-config.patch and yumrepo.patch since they are upstream now * Mon Jun 19 2006 David Lutterkort - 0.18.0-1 - Patch config for LSB compliance (lsb-config.patch) - Changed config moves /var/puppet to /var/lib/puppet, /etc/puppet/ssl to /var/lib/puppet, /etc/puppet/clases.txt to /var/lib/puppet/classes.txt, /etc/puppet/localconfig.yaml to /var/lib/puppet/localconfig.yaml * Fri May 19 2006 David Lutterkort - 0.17.2-1 - Added /usr/bin/puppetrun to server subpackage - Backported patch for yumrepo type (yumrepo.patch) * Wed May 3 2006 David Lutterkort - 0.16.4-1 - Rebuilt * Fri Apr 21 2006 David Lutterkort - 0.16.0-1 - Fix default file permissions in server subpackage - Run puppetmaster as user puppet - rebuilt for 0.16.0 * Mon Apr 17 2006 David Lutterkort - 0.15.3-2 - Don't create empty log files in post-install scriptlet * Fri Apr 7 2006 David Lutterkort - 0.15.3-1 - Rebuilt for new version * Wed Mar 22 2006 David Lutterkort - 0.15.1-1 - Patch0: Run puppetmaster as root; running as puppet is not ready for primetime * Mon Mar 13 2006 David Lutterkort - 0.15.0-1 - Commented out noarch; requires fix for bz184199 * Mon Mar 6 2006 David Lutterkort - 0.14.0-1 - Added BuildRequires for ruby * Wed Mar 1 2006 David Lutterkort - 0.13.5-1 - Removed use of fedora-usermgmt. It is not required for Fedora Extras and makes it unnecessarily hard to use this rpm outside of Fedora. Just allocate the puppet uid/gid dynamically * Sun Feb 19 2006 David Lutterkort - 0.13.0-4 - Use fedora-usermgmt to create puppet user/group. Use uid/gid 24. Fixed problem with listing fileserver.conf and puppetmaster.conf twice * Wed Feb 8 2006 David Lutterkort - 0.13.0-3 - Fix puppetd.conf * Wed Feb 8 2006 David Lutterkort - 0.13.0-2 - Changes to run puppetmaster as user puppet * Mon Feb 6 2006 David Lutterkort - 0.13.0-1 - Don't mark initscripts as config files * Mon Feb 6 2006 David Lutterkort - 0.12.0-2 - Fix BuildRoot. Add dist to release * Tue Jan 17 2006 David Lutterkort - 0.11.0-1 - Rebuild * Thu Jan 12 2006 David Lutterkort - 0.10.2-1 - Updated for 0.10.2 Fixed minor kink in how Source is given * Wed Jan 11 2006 David Lutterkort - 0.10.1-3 - Added basic fileserver.conf * Wed Jan 11 2006 David Lutterkort - 0.10.1-1 - Updated. Moved installation of library files to sitelibdir. Pulled initscripts into separate files. Folded tools rpm into server * Thu Nov 24 2005 Duane Griffin - Added init scripts for the client * Wed Nov 23 2005 Duane Griffin - First packaging puppet-5.5.10/ext/regexp_nodes/0000755005276200011600000000000013417162176016301 5ustar jenkinsjenkinspuppet-5.5.10/ext/regexp_nodes/classes/0000755005276200011600000000000013417162176017736 5ustar jenkinsjenkinspuppet-5.5.10/ext/regexp_nodes/classes/databases0000644005276200011600000000001613417161721021600 0ustar jenkinsjenkinsdb\d{2} mysql puppet-5.5.10/ext/regexp_nodes/classes/webservers0000644005276200011600000000002313417161721022036 0ustar jenkinsjenkins^(web|www) leterel puppet-5.5.10/ext/regexp_nodes/environment/0000755005276200011600000000000013417162176020645 5ustar jenkinsjenkinspuppet-5.5.10/ext/regexp_nodes/environment/development0000644005276200011600000000002213417161721023077 0ustar jenkinsjenkins^dev- prod-canary puppet-5.5.10/ext/regexp_nodes/parameters/0000755005276200011600000000000013417162176020444 5ustar jenkinsjenkinspuppet-5.5.10/ext/regexp_nodes/parameters/service/0000755005276200011600000000000013417162176022104 5ustar jenkinsjenkinspuppet-5.5.10/ext/regexp_nodes/parameters/service/prod0000644005276200011600000000000713417161721022763 0ustar jenkinsjenkins\d{3}$ puppet-5.5.10/ext/regexp_nodes/parameters/service/qa0000644005276200011600000000002113417161721022414 0ustar jenkinsjenkins^qa- ^qa2- ^qa3- puppet-5.5.10/ext/regexp_nodes/parameters/service/sandbox0000644005276200011600000000000613417161721023454 0ustar jenkinsjenkins^dev- puppet-5.5.10/ext/regexp_nodes/regexp_nodes.rb0000644005276200011600000002305113417161721021304 0ustar jenkinsjenkins#!/usr/bin/env ruby # = Synopsis # This is an external node classifier script, after # https://puppet.com/docs/puppet/latest/lang_write_functions_in_puppet.html # # = Usage # regexp_nodes.rb # # = Description # This classifier implements filesystem autoloading: It looks through classes, # parameters, and environment subdirectories, looping through each file it # finds. Each file's contents are a regexp-per-line which, if they match the # hostname passed to the program as ARGV[0], sets a class, parameter value # or environment named the same thing as the file itself. At the end, the # resultant data structure is returned back to the puppet master process as # yaml. # # = Caveats # Since the files are read in directory order, multiple matches for a given # hostname in the parameters/ and environment/ subdirectories will return the # last-read value. (Multiple classes/ matches don't cause a problem; the # class is either incuded or it isn't) # # Unmatched hostnames in any of the environment/ files will cause 'production' # to be emitted; be aware of the complexity surrounding the interaction between # ENC and environments as discussed in https://projects.puppetlabs.com/issues/3910 # # = Examples # Based on the example files in the classes/ and parameters/ subdirectories # in the distribution, classes/database will set the 'database' class for # hosts matching %r{db\d{2}} (that is, 'db' followed by two digits) or with # 'mysql' anywhere in the hostname. Similarly, hosts beginning with 'www' or # 'web' or the hostname 'leterel' (my workstation) will be assigned the # 'webserver' class. # # Under parameters/ there is one subdirectory 'service' which # sets the a parameter called 'service' to the value 'prod' for production # hosts (whose hostnames always end with a three-digit code), 'qa' for # anything that starts with 'qa-' 'qa2-' or 'qa3-', and 'sandbox' for any # development machines whose hostnames start with 'dev-'. # # In the environment/ subdirectory, any hosts matching '^dev-' and a single # production host which serves as 'canary in the coal mine' will be put into # the development environment # # = Author # Eric Sorenson # we need yaml or there's not much point in going on require 'yaml' # Sets are like arrays but automatically de-duplicate elements require 'set' # set up some syslog logging require 'syslog' Syslog.open('extnodes', Syslog::LOG_PID | Syslog::LOG_NDELAY, Syslog::LOG_DAEMON) # change this to LOG_UPTO(Sysslog::LOG_DEBUG) if you want to see everything # but remember your syslog.conf needs to match this or messages will be filtered Syslog.mask = Syslog::LOG_UPTO(Syslog::LOG_INFO) # Helper method to log to syslog; we log at level debug if no level is specified # since those are the most frequent calls to this method def log(message,level=:debug) Syslog.send(level,message) end # set our workingdir to be the directory we're executed from, regardless # of parent's cwd, symlinks, etc. via handy Pathname.realpath method require 'pathname' p = Pathname.new(File.dirname(__FILE__)) WORKINGDIR = "#{p.realpath}" # This class holds all the methods for creating and accessing the properties # of an external node. There are really only two public methods: initialize # and a special version of to_yaml class ExternalNode # Make these instance variables get/set-able with eponymous methods attr_accessor :classes, :parameters, :environment, :hostname # initialize takes three arguments: # hostname:: usually passed in via ARGV[0] but it could be anything # classdir:: directory under WORKINGDIR to look for files named after # classes # parameterdir:: directory under WORKINGDIR to look for directories to set # parameters def initialize(hostname, classdir = 'classes/', parameterdir = 'parameters/', environmentdir = 'environment/') # instance variables that contain the lists of classes and parameters @classes = Set.new @parameters = Hash.new("unknown") # sets a default value of "unknown" @environment = "production" self.parse_argv(hostname) self.match_classes("#{WORKINGDIR}/#{classdir}") self.match_parameters("#{WORKINGDIR}/#{parameterdir}") self.match_environment("#{WORKINGDIR}/#{environmentdir}") end # private method called by initialize which sanity-checks our hostname. # good candidate for overriding in a subclass if you need different checks def parse_argv(hostname) if hostname =~ /^([-\w]+?)\.([-\w\.]+)/ # non-greedy up to the first . is hostname @hostname = $1 elsif hostname =~ /^([-\w]+)$/ # sometimes puppet's @name is just a name @hostname = hostname log("got shortname for [#{hostname}]") else log("didn't receive parsable hostname, got: [#{hostname}]",:err) exit(1) end end # to_yaml massages a copy of the object and outputs clean yaml so we don't # feed weird things back to puppet []< def to_yaml classes = self.classes.to_a if self.parameters.empty? # otherwise to_yaml prints "parameters: {}" parameters = nil else parameters = self.parameters end ({ 'classes' => classes, 'parameters' => parameters, 'environment' => environment}).to_yaml end # Private method that expects an absolute path to a file and a string to # match - it returns true if the string was matched by any of the lines in # the file def matched_in_patternfile?(filepath, matchthis) patternlist = [] begin open(filepath).each do |l| l.chomp! next if l =~ /^$/ next if l =~ /^#/ if l =~ /^\s*(\S+)/ m = Regexp.last_match log("found a non-comment line, transforming [#{l}] into [#{m[1]}]") l.gsub!(l,m[1]) else next l end pattern = %r{#{l}} patternlist << pattern log("appending [#{pattern}] to patternlist for [#{filepath}]") end rescue StandardError log("Problem reading #{filepath}: #{$!}",:err) exit(1) end log("list of patterns for #{filepath}: #{patternlist}") if matchthis =~ Regexp.union(patternlist) log("matched #{$~.to_s} in #{matchthis}, returning true") return true else # hostname didn't match anything in patternlist log("#{matchthis} unmatched, returning false") return nil end end # def # private method - takes a path to look for files, iterates through all # readable, regular files it finds, and matches this instance's @hostname # against each line; if any match, the class will be set for this node. def match_classes(fullpath) Dir.foreach(fullpath) do |patternfile| filepath = "#{fullpath}/#{patternfile}" next unless File.file?(filepath) and File.readable?(filepath) next if patternfile =~ /^\./ log("Attempting to match [#{@hostname}] in [#{filepath}]") if matched_in_patternfile?(filepath,@hostname) @classes << patternfile.to_s log("Appended #{patternfile.to_s} to classes instance variable") end end end # match_environment is similar to match_classes but it overwrites # any previously set value (usually just the default, 'production') # with a match def match_environment(fullpath) Dir.foreach(fullpath) do |patternfile| filepath = "#{fullpath}/#{patternfile}" next unless File.file?(filepath) and File.readable?(filepath) next if patternfile =~ /^\./ log("Attempting to match [#{@hostname}] in [#{filepath}]") if matched_in_patternfile?(filepath,@hostname) @environment = patternfile.to_s log("Wrote #{patternfile.to_s} to environment instance variable") end end end # Parameters are handled slightly differently; we make another level of # directories to get the parameter name, then use the names of the files # contained in there for the values of those parameters. # # ex: cat ./parameters/service/production # ^prodweb # would set parameters["service"] = "production" for prodweb001 def match_parameters(fullpath) Dir.foreach(fullpath) do |parametername| filepath = "#{fullpath}/#{parametername}" next if File.basename(filepath) =~ /^\./ # skip over dotfiles next unless File.directory?(filepath) and File.readable?(filepath) # skip over non-directories log("Considering contents of #{filepath}") Dir.foreach("#{filepath}") do |patternfile| secondlevel = "#{filepath}/#{patternfile}" log("Found parameters patternfile at #{secondlevel}") next unless File.file?(secondlevel) and File.readable?(secondlevel) log("Attempting to match [#{@hostname}] in [#{secondlevel}]") if matched_in_patternfile?(secondlevel, @hostname) @parameters[ parametername.to_s ] = patternfile.to_s log("Set @parameters[#{parametername.to_s}] = #{patternfile.to_s}") end end end end end # Logic for local hacks that don't fit neatly into the autoloading model can # happen as we initialize a subclass class MyExternalNode < ExternalNode def initialize(hostname, classdir = 'classes/', parameterdir = 'parameters/') super # Set "hostclass" parameter based on hostname, # stripped of leading environment prefix and numeric suffix if @hostname =~ /^(\w*?)-?(\D+)(\d{2,3})$/ match = Regexp.last_match hostclass = match[2] log("matched hostclass #{hostclass}") @parameters[ "hostclass" ] = hostclass else log("couldn't figure out class from #{@hostname}",:warning) end end end # Here we begin actual execution by calling methods defined above mynode = MyExternalNode.new(ARGV[0]) puts mynode.to_yaml puppet-5.5.10/ext/solaris/0000755005276200011600000000000013417162176015273 5ustar jenkinsjenkinspuppet-5.5.10/ext/solaris/pkginfo0000644005276200011600000000025413417161721016647 0ustar jenkinsjenkinsPKG=CSWpuppet NAME=puppet - System Automation Framework VERSION=3.0.0 CATEGORY=application VENDOR=https://projects.puppetlabs.com/projects/puppet EMAIL=info@puppetlabs.com puppet-5.5.10/ext/solaris/smf/0000755005276200011600000000000013417162176016060 5ustar jenkinsjenkinspuppet-5.5.10/ext/solaris/smf/puppet0000755005276200011600000000207513417161721017322 0ustar jenkinsjenkins#!/sbin/sh . /lib/svc/share/smf_include.sh [ -z "${SMF_FMRI}" ] && exit "$SMF_EXIT_ERR" CONF_FILE=/etc/puppetlabs/puppet/puppet.conf [ ! -f "${CONF_FILE}" ] && exit "$SMF_EXIT_ERR_CONFIG" PUPPET=/opt/puppetlabs/bin/puppet case "$1" in start) exec "$PUPPET" agent ;; stop) # stop sends sigterm first followed by sigkill # smf_kill_contract TERM 1 30 # sends sigterm to all process in ctid and will continue # to do so for 30 seconds with interval of 5 seconds # smf_kill_contract KILL 1 # continues until all processes are killed. # svcs -p lists all processes in the contract. # http://bnsmb.de/solaris/My_Little_SMF_FAQ.html ctid=`svcprop -p restarter/contract "$SMF_FMRI"` if [ -n "$ctid" ]; then smf_kill_contract "$ctid" TERM 1 30 ret=$? [ "$ret" -eq 1 ] && exit "$SMF_EXIT_ERR_FATAL" if [ "$ret" -eq 2 ] ; then smf_kill_contract "$ctid" KILL 1 [ $? -ne 0 ] && exit "$SMF_EXIT_ERR_FATAL" fi fi ;; *) echo "Usage: $0 {start|stop}"; exit "$SMF_EXIT_ERR_FATAL" ;; esac exit "$SMF_EXIT_OK" puppet-5.5.10/ext/solaris/smf/puppet.xml0000644005276200011600000000312013417161721020106 0ustar jenkinsjenkins puppet-5.5.10/ext/solaris/smf/puppetd.xml0000644005276200011600000000407113417161721020260 0ustar jenkinsjenkins puppet-5.5.10/ext/solaris/smf/puppetmasterd.xml0000644005276200011600000000414313417161721021474 0ustar jenkinsjenkins puppet-5.5.10/ext/solaris/smf/svc-puppetd0000755005276200011600000000224513417161721020256 0ustar jenkinsjenkins#!/bin/sh # This is the /etc/init.d file for puppetd # Modified for CSW # # description: puppetd - Puppet Automation Client # . /lib/svc/share/smf_include.sh prefix=/opt/csw exec_prefix=/opt/csw sysconfdir=/opt/csw/etc sbindir=/opt/csw/sbin if [ -z $SMF_SYSVOL_FS ]; then piddir=/var/run/puppetlabs else piddir=$SMF_SYSVOL_FS/puppetlabs fi; pidfile=$piddir/agent.pid case "$1" in start) cd / # Start daemons. printf "Starting Puppet client services:" mkdir -p $piddir /opt/csw/sbin/puppetd printf " puppetd" echo "" ;; stop) printf "Stopping Puppet client services:" kill `cat "$pidfile"` printf " puppetd" echo "" ;; restart) printf "Restarting Puppet client services:" kill -HUP `cat "$pidfile"` printf " puppetd" echo "" ;; reload) printf "Reloading Puppet client services:" kill -HUP `cat "$pidfile"` printf " puppetd" echo "" ;; status) if [ -f "$pidfile" ]; then pid=`cat "$pidfile"` curpid=`pgrep puppetd` if [ "$pid" -eq "$curpid" ]; then exit 0 else exit 1 fi else exit 1 fi esac exit 0 puppet-5.5.10/ext/solaris/smf/svc-puppetmasterd0000755005276200011600000000212713417161721021471 0ustar jenkinsjenkins#!/bin/sh # . /lib/svc/share/smf_include.sh prefix=/opt/csw exec_prefix=/opt/csw sysconfdir=/opt/csw/etc sbindir=/opt/csw/sbin if [ -z $SMF_SYSVOL_FS ]; then piddir=/var/run/puppetlabs else piddir=$SMF_SYSVOL_FS/puppetlabs fi; pidfile=$piddir/master.pid case "$1" in start) cd / # Start daemons. printf "Starting Puppet server services:" mkdir -p $piddir /opt/csw/sbin/puppetmasterd printf " puppetmaster" echo "" ;; stop) printf "Stopping Puppet server services:" kill `cat "$pidfile"` printf " puppetmasterd" echo "" ;; restart) printf "Restarting Puppet server services:" kill -HUP `cat "$pidfile"` printf " puppetmasterd" echo "" ;; reload) printf "Reloading Puppet server services:" kill -HUP `cat "$pidfile"` printf " puppetmasterd" echo "" ;; status) if [ -f "$pidfile" ]; then pid=`cat "$pidfile"` curpid=`pgrep puppetmasterd` if [ "$pid" -eq "$curpid" ]; then exit 0 else exit 1 fi else exit 1 fi esac exit 0 puppet-5.5.10/ext/suse/0000755005276200011600000000000013417162176014576 5ustar jenkinsjenkinspuppet-5.5.10/ext/suse/client.init0000755005276200011600000001120413417161721016735 0ustar jenkinsjenkins#!/bin/bash # puppet Init script for running the puppet client daemon # # Author: Duane Griffin # David Lutterkort # Martin Vuk (SuSE support) # # chkconfig: - 98 02 # # description: Enables periodic system configuration checks through puppet. # processname: puppet # config: /etc/sysconfig/puppet ### BEGIN INIT INFO # Provides: puppet # Required-Start: $local_fs $remote_fs $network $syslog # Should-Start: puppet # Required-Stop: $local_fs $remote_fs $network $syslog # Should-Stop: puppet # Default-Start: 3 5 # Default-Stop: 0 1 2 6 # Short-Description: puppet # Description: Enables periodic system configuration checks through puppet. ### END INIT INFO # Shell functions sourced from /etc/rc.status: # rc_check check and set local and overall rc status # rc_status check and set local and overall rc status # rc_status -v ditto but be verbose in local rc status # rc_status -v -r ditto and clear the local rc status # rc_failed set local and overall rc status to failed # rc_reset clear local rc status (overall remains) # rc_exit exit appropriate to overall rc status [ -f /etc/rc.status ] && . /etc/rc.status [ -f /etc/sysconfig/puppet ] && . /etc/sysconfig/puppet lockfile=/var/lock/subsys/puppet pidfile=/var/run/puppetlabs/agent.pid puppetd=/opt/puppetlabs/puppet/bin/puppet PUPPET_OPTS="agent" # First reset status of this service rc_reset # Return values acc. to LSB for all commands but status: # 0 - success # 1 - misc error # 2 - invalid or excess args # 3 - unimplemented feature (e.g. reload) # 4 - insufficient privilege # 5 - program not installed # 6 - program not configured # # Note that starting an already running service, stopping # or restarting a not-running service as well as the restart # with force-reload (in case signalling is not supported) are # considered a success. case "$1" in start) echo -n "Starting puppet services." ## Start daemon with startproc(8). If this fails ## the echo return value is set appropriate. ## This accounts for the behavior of startproc on sles 10. ## Check to see if a running process matches the contents ## of the pidfile, and do nothing if true. Otherwise ## force a process start if [ -f "${pidfile}" ]; then PID=$(cat "$pidfile") if [ "$PID" -eq $(pgrep -f "$puppetd") ] ; then rc_status -v rc_exit fi fi startproc -f -w -p "${pidfile}" "${puppetd}" "${PUPPET_OPTS}" ${PUPPET_EXTRA_OPTS} && touch "${lockfile}" # Remember status and be verbose rc_status -v ;; stop) echo -n "Shutting down puppet:" ## Stop daemon with killproc(8) and if this fails ## set echo the echo return value. killproc -QUIT -p "${pidfile}" "${puppetd}" && rm -f "${lockfile}" "${pidfile}" # Remember status and be verbose rc_status -v ;; try-restart|condrestart) ## Stop the service and if this succeeds (i.e. the ## service was running before), start it again. $0 status >/dev/null && $0 restart # Remember status and be quiet rc_status ;; restart) ## Stop the service and regardless of whether it was ## running or not, start it again. $0 stop $0 start # Remember status and be quiet rc_status ;; reload|force-reload) # Reload the service by sending the HUP signal echo -n "Reloading puppet service: " killproc -HUP -p "${pidfile}" "${puppetd}" rc_status -v ;; status) echo -n "Checking for service puppetd: " ## Check status with checkproc(8), if process is running ## checkproc will return with exit status 0. # Status has a slightly different for the status command: # 0 - service running # 1 - service dead, but /var/run/ pid file exists # 2 - service dead, but /var/lock/ lock file exists # 3 - service not running # NOTE: checkproc returns LSB compliant status values. if [ -f "${pidfile}" ]; then checkproc -p "${pidfile}" "${puppetd}" rc_status -v else rc_failed 3 rc_status -v fi ;; once) shift $puppetd "${PUPPET_OPTS}" --onetime ${PUPPET_EXTRA_OPTS} $@ ;; *) echo "Usage: $0 {start|stop|status|try-restart|condrestart|restart|force-reload|reload|once}" exit 1 esac rc_exit puppet-5.5.10/ext/suse/puppet.spec0000644005276200011600000002640613417161721016772 0ustar jenkinsjenkins%{!?ruby_sitelibdir: %define ruby_sitelibdir %(ruby -rrbconfig -e 'puts Config::CONFIG["sitelibdir"]')} %define pbuild %{_builddir}/%{name}-%{version} %define confdir conf/suse Summary: A network tool for managing many disparate systems Name: puppet Version: 3.0.0 Release: 1%{?dist} License: Apache 2.0 Group: Productivity/Networking/System URL: https://puppetlabs.com/projects/puppet/ Source0: https://puppetlabs.com/downloads/puppet/%{name}-%{version}.tar.gz PreReq: %{insserv_prereq} %{fillup_prereq} Requires: ruby >= 1.8.7 Requires: facter >= 1:1.7.0 Requires: cron Requires: logrotate BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) BuildRequires: ruby >= 1.8.7 BuildRequires: klogd BuildRequires: sysconfig %description Puppet lets you centrally manage every important aspect of your system using a cross-platform specification language that manages all the separate elements normally aggregated in different files, like users, cron jobs, and hosts, along with obviously discrete elements like packages, services, and files. %package server Group: Productivity/Networking/System Summary: Server for the puppet system management tool Requires: puppet = %{version}-%{release} %description server Provides the central puppet server daemon which provides manifests to clients. The server can also function as a certificate authority and file server. %prep %setup -q -n %{name}-%{version} %build for f in bin/*; do sed -i -e '1s,^#!.*ruby$,#!/usr/bin/ruby,' $f done %install %{__install} -d -m0755 %{buildroot}%{_bindir} %{__install} -d -m0755 %{buildroot}%{_confdir} %{__install} -d -m0755 %{buildroot}%{ruby_sitelibdir} %{__install} -d -m0755 %{buildroot}%{_sysconfdir}/puppet/manifests %{__install} -d -m0755 %{buildroot}%{_docdir}/%{name}-%{version} %{__install} -d -m0755 %{buildroot}%{_localstatedir}/lib/puppet %{__install} -d -m0755 %{buildroot}%{_localstatedir}/run/puppet %{__install} -d -m0755 %{buildroot}%{_localstatedir}/log/puppet %{__install} -Dp -m0755 %{pbuild}/bin/* %{buildroot}%{_bindir} %{__install} -Dp -m0644 %{pbuild}/lib/puppet.rb %{buildroot}%{ruby_sitelibdir}/puppet.rb %{__cp} -a %{pbuild}/lib/puppet %{buildroot}%{ruby_sitelibdir} find %{buildroot}%{ruby_sitelibdir} -type f -perm +ugo+x -exec chmod a-x '{}' \; %{__cp} -a %{pbuild}/ext/redhat/client.sysconfig %{buildroot}%{_confdir}/client.sysconfig %{__install} -Dp -m0644 %{buildroot}%{_confdir}/client.sysconfig %{buildroot}/var/adm/fillup-templates/sysconfig.puppet %{__cp} -a %{pbuild}/ext/redhat/server.sysconfig %{buildroot}%{_confdir}/server.sysconfig %{__install} -Dp -m0644 %{buildroot}%{_confdir}/server.sysconfig %{buildroot}/var/adm/fillup-templates/sysconfig.puppetmaster %{__cp} -a %{pbuild}/ext/redhat/fileserver.conf %{buildroot}%{_confdir}/fileserver.conf %{__install} -Dp -m0644 %{buildroot}%{_confdir}/fileserver.conf %{buildroot}%{_sysconfdir}/puppet/fileserver.conf %{__cp} -a %{pbuild}/ext/redhat/puppet.conf %{buildroot}%{_confdir}/puppet.conf %{__install} -Dp -m0644 %{buildroot}%{_confdir}/puppet.conf %{buildroot}%{_sysconfdir}/puppet/puppet.conf %{__cp} -a %{pbuild}/ext/redhat/logrotate %{buildroot}%{_confdir}/logrotate %{__install} -Dp -m0644 %{buildroot}%{_confdir}/logrotate %{buildroot}%{_sysconfdir}/logrotate.d/puppet %{__install} -Dp -m0755 %{confdir}/client.init %{buildroot}%{_initrddir}/puppet %{__install} -Dp -m0755 %{confdir}/server.init %{buildroot}%{_initrddir}/puppetmaster %files %defattr(-, root, root, 0755) %{_bindir}/puppet %{ruby_sitelibdir}/* %{_initrddir}/puppet /var/adm/fillup-templates/sysconfig.puppet %config(noreplace) %{_sysconfdir}/puppet/puppet.conf %doc COPYING LICENSE README examples %config(noreplace) %{_sysconfdir}/logrotate.d/puppet %dir %{_sysconfdir}/puppet # These need to be owned by puppet so the server can # write to them %attr(-, puppet, puppet) %{_localstatedir}/run/puppet %attr(-, puppet, puppet) %{_localstatedir}/log/puppet %attr(-, puppet, puppet) %{_localstatedir}/lib/puppet %files server %defattr(-, root, root, 0755) %{_initrddir}/puppetmaster %config(noreplace) %{_sysconfdir}/puppet/* %exclude %{_sysconfdir}/puppet/puppet.conf /var/adm/fillup-templates/sysconfig.puppetmaster %dir %{_sysconfdir}/puppet %pre /usr/sbin/groupadd -r puppet 2>/dev/null || : /usr/sbin/useradd -g puppet -c "Puppet" \ -s /sbin/nologin -r -d /var/puppet puppet 2> /dev/null || : %post %{fillup_and_insserv -y puppet} %post server %{fillup_and_insserv -n -y puppetmaster} %preun %stop_on_removal puppet %preun server %stop_on_removal puppetmaster %postun %restart_on_update puppet %{insserv_cleanup} %postun server %restart_on_update puppetmaster %{insserv_cleanup} %clean %{__rm} -rf %{buildroot} %changelog * Mon Oct 08 2012 Matthaus Owens - 3.0.0-1 - Update for deprecated binary removal, ruby version requirements * Fri Aug 24 2012 Eric Sorenson - 3.0.0-0.1rc4 - Update facter version dependency - Update for 3.0.0-0.1rc4 * Wed May 02 2012 Moses Mendoza - 2.7.14-1 - Update for 2.7.14 * Mon Mar 12 2012 Michael Stahnke - 2.7.12-1 - Update for 2.7.12 * Wed Jan 25 2012 Michael Stahnke - 2.7.10-1 - Update for 2.7.10 * Wed Nov 30 2011 Michael Stahnke - 2.7.8-0.1rc1 - Update for 2.7.8rc1 * Mon Nov 21 2011 Michael Stahnke - 2.7.7-1 - Release 2.7.7 * Wed Jul 06 2011 Michael Stahnke - 2.7.2-0.1rc1 - Updating to 2.7.2rc1 * Tue Sep 14 2010 Ben Kevan - 2.6.1 - New version to 2.6.1 - Add client.init and server.init from source since it's now included in the packages - Change BuildRequires Ruby version to match Requires Ruby version - Removed ruby-env patch, replaced with sed in prep - Update urls to puppetlabs.com * Wed Jul 21 2010 Ben Kevan - 2.6.0 - New version and ruby version bump - Add puppetdoc to %_bindir (unknown why original suse package, excluded or forgot to add) - Corrected patch for ruby environment - Move binaries back to the correct directories * Wed Jul 14 2010 Ben Kevan - 0.25.5 - New version. - Use original client, server.init names - Revert to puppetmaster - Fixed client.init and server.init and included $null and Should-Stop for both * Tue Mar 2 2010 Martin Vuk - 0.25.4 - New version. * Sun Aug 9 2009 Noah Fontes - Fix build on SLES 9. - Enable puppet and puppet-server services by default. * Sat Aug 8 2009 Noah Fontes - Fix a lot of relevant warnings from rpmlint. - Build on OpenSUSE 11.1 correctly. - Rename puppetmaster init scripts to puppet-server to correspond to the package name. * Wed Apr 22 2009 Leo Eraly - 0.24.8 - New version. * Tue Dec 9 2008 Leo Eraly - 0.24.6 - New version. * Fri Sep 5 2008 Leo Eraly - 0.24.5 - New version. * Fri Jun 20 2008 Martin Vuk - 0.24.4 - Removed symlinks to old configuration files * Fri Dec 14 2007 Martin Vuk - 0.24.0 - New version. * Fri Jun 29 2007 Martin Vuk - 0.23.0 - New version. * Wed May 2 2007 Martin Vuk - 0.22.4 - New version. Includes provider for rug package manager. * Wed Apr 25 2007 Martin Vuk - 0.22.3 - New version. Added links /sbin/rcpuppet and /sbin/rcpuppetmaster * Sun Jan 7 2007 Martin Vuk - 0.22.0 - version bump * Tue Oct 3 2006 Martin Vuk - 0.19.3-3 - Made package arch dependant. * Sat Sep 23 2006 Martin Vuk - 0.19.3-1 - New version * Sun Sep 17 2006 Martin Vuk - 0.19.1-1 - New version * Tue Aug 30 2006 Martin Vuk - 0.19.0-1 - New version - No need to patch anymore :-), since my changes went into official release. * Tue Aug 3 2006 Martin Vuk - 0.18.4-3 - Replaced puppet-bin.patch with %build section from David's spec * Tue Aug 1 2006 Martin Vuk - 0.18.4-2 - Added supprot for enabling services in SuSE * Tue Aug 1 2006 Martin Vuk - 0.18.4-1 - New version and support for SuSE * Wed Jul 5 2006 David Lutterkort - 0.18.2-1 - New version * Wed Jun 28 2006 David Lutterkort - 0.18.1-1 - Removed lsb-config.patch and yumrepo.patch since they are upstream now * Mon Jun 19 2006 David Lutterkort - 0.18.0-1 - Patch config for LSB compliance (lsb-config.patch) - Changed config moves /var/puppet to /var/lib/puppet, /etc/puppet/ssl to /var/lib/puppet, /etc/puppet/clases.txt to /var/lib/puppet/classes.txt, /etc/puppet/localconfig.yaml to /var/lib/puppet/localconfig.yaml * Fri May 19 2006 David Lutterkort - 0.17.2-1 - Added /usr/bin/puppetrun to server subpackage - Backported patch for yumrepo type (yumrepo.patch) * Wed May 3 2006 David Lutterkort - 0.16.4-1 - Rebuilt * Fri Apr 21 2006 David Lutterkort - 0.16.0-1 - Fix default file permissions in server subpackage - Run puppetmaster as user puppet - rebuilt for 0.16.0 * Mon Apr 17 2006 David Lutterkort - 0.15.3-2 - Don't create empty log files in post-install scriptlet * Fri Apr 7 2006 David Lutterkort - 0.15.3-1 - Rebuilt for new version * Wed Mar 22 2006 David Lutterkort - 0.15.1-1 - Patch0: Run puppetmaster as root; running as puppet is not ready for primetime * Mon Mar 13 2006 David Lutterkort - 0.15.0-1 - Commented out noarch; requires fix for bz184199 * Mon Mar 6 2006 David Lutterkort - 0.14.0-1 - Added BuildRequires for ruby * Wed Mar 1 2006 David Lutterkort - 0.13.5-1 - Removed use of fedora-usermgmt. It is not required for Fedora Extras and makes it unnecessarily hard to use this rpm outside of Fedora. Just allocate the puppet uid/gid dynamically * Sun Feb 19 2006 David Lutterkort - 0.13.0-4 - Use fedora-usermgmt to create puppet user/group. Use uid/gid 24. Fixed problem with listing fileserver.conf and puppetmaster.conf twice * Wed Feb 8 2006 David Lutterkort - 0.13.0-3 - Fix puppetd.conf * Wed Feb 8 2006 David Lutterkort - 0.13.0-2 - Changes to run puppetmaster as user puppet * Mon Feb 6 2006 David Lutterkort - 0.13.0-1 - Don't mark initscripts as config files * Mon Feb 6 2006 David Lutterkort - 0.12.0-2 - Fix BuildRoot. Add dist to release * Tue Jan 17 2006 David Lutterkort - 0.11.0-1 - Rebuild * Thu Jan 12 2006 David Lutterkort - 0.10.2-1 - Updated for 0.10.2 Fixed minor kink in how Source is given * Wed Jan 11 2006 David Lutterkort - 0.10.1-3 - Added basic fileserver.conf * Wed Jan 11 2006 David Lutterkort - 0.10.1-1 - Updated. Moved installation of library files to sitelibdir. Pulled initscripts into separate files. Folded tools rpm into server * Thu Nov 24 2005 Duane Griffin - Added init scripts for the client * Wed Nov 23 2005 Duane Griffin - First packaging puppet-5.5.10/ext/suse/server.init0000644005276200011600000001315713417161721016773 0ustar jenkinsjenkins#!/bin/bash # puppetmaster This shell script enables the puppetmaster server. # # Author: Duane Griffin # Martin Vuk (SuSE support) # # chkconfig: - 65 45 # # description: Server for the puppet system management tool. # processname: puppetmaster ### BEGIN INIT INFO # Provides: puppetmaster # Required-Start: $local_fs $remote_fs $network $syslog # Should-Start: puppetmaster # Required-Stop: $local_fs $remote_fs $network $syslog # Should-Stop: puppetmaster # Default-Start: 3 5 # Default-Stop: 0 1 2 6 # Short-Description: puppetmaster # Description: Server for the puppet system management tool. ### END INIT INFO # Shell functions sourced from /etc/rc.status: # rc_check check and set local and overall rc status # rc_status check and set local and overall rc status # rc_status -v ditto but be verbose in local rc status # rc_status -v -r ditto and clear the local rc status # rc_failed set local and overall rc status to failed # rc_reset clear local rc status (overall remains) # rc_exit exit appropriate to overall rc status lockfile=/var/lock/subsys/puppetmaster pidfile=/var/run/puppetlabs/master.pid # Source function library. [ -f /etc/rc.status ] && . /etc/rc.status if [ -f /etc/sysconfig/puppetmaster ]; then . /etc/sysconfig/puppetmaster fi PUPPETMASTER_OPTS="master " [ -n "$PUPPETMASTER_MANIFEST" ] && PUPPETMASTER_OPTS="${PUPPETMASTER_OPTS} --manifest=${PUPPETMASTER_MANIFEST}" [ -n "$PUPPETMASTER_PORTS" ] && PUPPETMASTER_OPTS="${PUPPETMASTER_OPTS} --masterport=${PUPPETMASTER_PORTS[0]}" [ -n "$PUPPETMASTER_LOG" ] && PUPPETMASTER_OPTS="${PUPPETMASTER_OPTS} --logdest ${PUPPETMASTER_LOG}" PUPPETMASTER_OPTS="${PUPPETMASTER_OPTS} \ ${PUPPETMASTER_EXTRA_OPTS}" RETVAL=0 PUPPETMASTER=/usr/bin/puppet mongrel_warning="The mongrel servertype is no longer built-in to Puppet. It appears as though mongrel is being used, as the number of ports is greater than one. Starting the puppetmaster service will not behave as expected until this is resolved. Only the first port has been used in the service. These settings are defined at /etc/sysconfig/puppetmaster" # Warn about removed and unsupported mongrel servertype if [ -n "$PUPPETMASTER_PORTS" ] && [ ${#PUPPETMASTER_PORTS[@]} -gt 1 ]; then echo $mongrel_warning echo fi start() { echo -n $"Starting puppetmaster: " echo return $RETVAL } # First reset status of this service rc_reset # Return values acc. to LSB for all commands but status: # 0 - success # 1 - misc error # 2 - invalid or excess args # 3 - unimplemented feature (e.g. reload) # 4 - insufficient privilege # 5 - program not installed # 6 - program not configured # # Note that starting an already running service, stopping # or restarting a not-running service as well as the restart # with force-reload (in case signalling is not supported) are # considered a success. case "$1" in start) echo -n "Starting puppetmaster services." ## Start daemon with startproc(8). If this fails ## the echo return value is set appropriate. # startproc should return 0, even if service is # already running to match LSB spec. # Confirm the manifest exists if [ -r $PUPPETMASTER_MANIFEST ]; then startproc -p ${pidfile} $PUPPETMASTER $PUPPETMASTER_OPTS && touch "$lockfile" else rc_failed echo "Manifest does not exist: $PUPPETMASTER_MANIFEST" fi # Remember status and be verbose rc_status -v ;; stop) echo -n "Shutting down puppetmaster:" ## Stop daemon with killproc(8) and if this fails ## set echo the echo return value. killproc -QUIT -p ${pidfile} $PUPPETMASTER && rm -f ${lockfile} ${pidfile} # Remember status and be verbose rc_status -v ;; try-restart|condrestart) ## Stop the service and if this succeeds (i.e. the ## service was running before), start it again. $0 status >/dev/null && $0 restart # Remember status and be quiet rc_status ;; restart) ## Stop the service and regardless of whether it was ## running or not, start it again. $0 stop $0 start # Remember status and be quiet rc_status ;; force-reload) ## Signal the daemon to reload its config. Most daemons ## do this on signal 1 (SIGHUP). ## If it does not support it, restart. echo -n "Reload service puppet" ## if it supports it: killproc -HUP -p ${pidfile} $PUPPETMASTER rc_status -v ;; reload) ## Like force-reload, but if daemon does not support ## signalling, do nothing (!) # If it supports signalling: echo -n "Reload puppet services." killproc -HUP -p ${pidfile} $PUPPETMASTER rc_status -v ;; status) echo -n "Checking for service puppetmaster: " ## Check status with checkproc(8), if process is running ## checkproc will return with exit status 0. # Status has a slightly different for the status command: # 0 - service running # 1 - service dead, but /var/run/ pid file exists # 2 - service dead, but /var/lock/ lock file exists # 3 - service not running # NOTE: checkproc returns LSB compliant status values. checkproc -p ${pidfile} $PUPPETMASTER rc_status -v ;; *) echo "Usage: $0 {start|stop|status|try-restart|condrestart|restart|force-reload|reload}" exit 1 esac rc_exit puppet-5.5.10/ext/systemd/0000755005276200011600000000000013417162176015307 5ustar jenkinsjenkinspuppet-5.5.10/ext/systemd/puppet.service0000644005276200011600000000151113417161721020177 0ustar jenkinsjenkins# # Local settings can be configured without being overwritten by package upgrades, for example # if you want to increase puppet open-files-limit to 10000, # you need to increase systemd's LimitNOFILE setting, so create a file named # "/etc/systemd/system/puppet.service.d/limits.conf" containing: # [Service] # LimitNOFILE=10000 # You can confirm it worked by running systemctl daemon-reload # then running systemctl show puppet | grep LimitNOFILE # [Unit] Description=Puppet agent Wants=basic.target After=basic.target network.target [Service] EnvironmentFile=-/etc/sysconfig/puppetagent EnvironmentFile=-/etc/sysconfig/puppet EnvironmentFile=-/etc/default/puppet ExecStart=/opt/puppetlabs/puppet/bin/puppet agent $PUPPET_EXTRA_OPTS --no-daemonize ExecReload=/bin/kill -HUP $MAINPID KillMode=process [Install] WantedBy=multi-user.target puppet-5.5.10/ext/upload_facts.rb0000755005276200011600000000604213417161721016610 0ustar jenkinsjenkins#!/usr/bin/env ruby require 'net/https' require 'openssl' require 'openssl/x509' require 'optparse' require 'pathname' require 'yaml' require 'puppet' require 'puppet/network/http_pool' class Puppet::Application::UploadFacts < Puppet::Application run_mode :master option('--debug', '-d') option('--verbose', '-v') option('--logdest DEST', '-l DEST') do |arg| Puppet::Util::Log.newdestination(arg) options[:setdest] = true end option('--minutes MINUTES', '-m MINUTES') do |minutes| options[:time_limit] = 60 * minutes.to_i end def help print <] [-l|--logdest syslog||console] = Description This command will read YAML facts from the puppet master's YAML directory, and save them to the configured facts_terminus. It is intended to be used with the facts_terminus set to inventory_service, in order to ensure facts which have been cached locally due to a temporary failure are still uploaded to the inventory service. = Usage Notes upload_facts is intended to be run from cron, with the facts_terminus set to inventory_service. The +--minutes+ argument should be set to the length of time between upload_facts runs. This will ensure that only new YAML files are uploaded. = Options Note that any setting that's valid in the configuration file is also a valid long argument. For example, 'server' is a valid configuration parameter, so you can specify '--server ' as an argument. See the configuration file documentation at https://puppet.com/docs/puppet/latest/configuration.html for the full list of acceptable parameters. A commented list of all configuration options can also be generated by running puppet agent with '--genconfig'. minutes:: Limit the upload only to YAML files which have been added within the last n minutes. HELP exit end def setup # Handle the logging settings. if options[:debug] or options[:verbose] if options[:debug] Puppet::Util::Log.level = :debug else Puppet::Util::Log.level = :info end Puppet::Util::Log.newdestination(:console) unless options[:setdest] end exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? end def main dir = Pathname.new(Puppet[:yamldir]) + 'facts' cutoff = options[:time_limit] ? Time.now - options[:time_limit] : Time.at(0) files = dir.children.select do |file| file.extname == '.yaml' && file.mtime > cutoff end failed = false terminus = Puppet::Node::Facts.indirection.terminus files.each do |file| facts = YAML.load_file(file) request = Puppet::Indirector::Request.new(:facts, :save, facts, nil) # The terminus warns for us if we fail. if terminus.save(request) Puppet.info "Uploaded facts for #{facts.name} to inventory service" else failed = true end end exit !failed end end Puppet::Application::UploadFacts.new.run puppet-5.5.10/ext/windows/0000755005276200011600000000000013417162176015311 5ustar jenkinsjenkinspuppet-5.5.10/ext/windows/eventlog/0000755005276200011600000000000013417162176017134 5ustar jenkinsjenkinspuppet-5.5.10/ext/windows/eventlog/Rakefile0000755005276200011600000000111013417161721020570 0ustar jenkinsjenkinsrequire 'rubygems' require 'rake' require 'fileutils' require 'rbconfig' BASENAME = "puppetres" task :default do sh 'rake -T' end desc 'Build puppet eventlog message dll' task :dist => ['out', "#{BASENAME}.dll"] directory 'out' rule '.rc' => '.mc' do |t| sh "mc -b -r out -h out #{t.source}" end rule '.res' => '.rc' do |t| sh "rc -nologo -r -fo out/#{t.name} out/#{t.source}" end rule '.dll' => '.res' do |t| sh "link -nologo -dll -noentry -machine:x86 -out:out/#{t.name} out/#{t.source}" end desc 'Delete generated files' task :clean do FileUtils.rm_rf('out') end puppet-5.5.10/ext/windows/eventlog/puppetres.dll0000755005276200011600000000200113417161721021647 0ustar jenkinsjenkinsMZ˙˙¸@¸ş´ Í!¸LÍ!This program cannot be run in DOS mode. $uőŮE›ŠE›ŠE›ŠLlŠD›ŠLl ŠD›ŠRichE›ŠPEL†—|Oŕ!    .rsrc @@ €0€ H`@%1 %1 %1 puppet-5.5.10/ext/windows/eventlog/puppetres.mc0000755005276200011600000000031513417161721021501 0ustar jenkinsjenkinsMessageId=0x1 SymbolicName=PUPPET_INFO Language=English %1 . MessageId=0x2 SymbolicName=PUPPET_WARN Language=English %1 . MessageId=0x3 SymbolicName=PUPPET_ERROR Language=English %1 . puppet-5.5.10/ext/windows/puppet_interactive.bat0000644005276200011600000000016213417161721021705 0ustar jenkinsjenkins@echo off SETLOCAL echo Running Puppet agent on demand ... cd "%~dp0" call puppet.bat agent --test %* PAUSE puppet-5.5.10/ext/windows/puppet_shell.bat0000644005276200011600000000025413417161721020501 0ustar jenkinsjenkins@echo off SETLOCAL if exist "%~dp0environment.bat" ( call "%~dp0environment.bat" %0 %* ) else ( SET "PATH=%~dp0;%PATH%" ) REM Display Ruby version ruby.exe -v puppet-5.5.10/ext/windows/run_puppet_interactive.bat0000644005276200011600000000032413417161721022571 0ustar jenkinsjenkins@echo Running puppet on demand ... @echo off SETLOCAL if exist "%~dp0environment.bat" ( call "%~dp0environment.bat" %0 %* ) else ( SET "PATH=%~dp0;%PATH%" ) elevate.exe "%~dp0puppet_interactive.bat" puppet-5.5.10/ext/windows/service/0000755005276200011600000000000013417162176016751 5ustar jenkinsjenkinspuppet-5.5.10/ext/windows/service/daemon.bat0000644005276200011600000000014013417161721020672 0ustar jenkinsjenkins@echo off SETLOCAL call "%~dp0..\bin\environment.bat" %0 %* ruby -rubygems "%~dp0daemon.rb" %*puppet-5.5.10/ext/windows/service/daemon.rb0000755005276200011600000001277713417161721020555 0ustar jenkinsjenkins#!/usr/bin/env ruby require 'fileutils' require 'win32/daemon' require 'win32/dir' require 'win32/process' # This file defines utilities for logging to eventlog. While it lives inside # Puppet, it is completely independent and loads no other parts of Puppet, so we # can safely require *just* it. require 'puppet/util/windows/eventlog' class WindowsDaemon < Win32::Daemon CREATE_NEW_CONSOLE = 0x00000010 @run_thread = nil @LOG_TO_FILE = false LOG_FILE = File.expand_path(File.join(Dir::COMMON_APPDATA, 'PuppetLabs', 'puppet', 'var', 'log', 'windows.log')) LEVELS = [:debug, :info, :notice, :warning, :err, :alert, :emerg, :crit] LEVELS.each do |level| define_method("log_#{level}") do |msg| log(msg, level) end end def service_init end def service_main(*argsv) argsv = (argsv << ARGV).flatten.compact args = argsv.join(' ') @loglevel = LEVELS.index(argsv.index('--debug') ? :debug : :notice) @LOG_TO_FILE = (argsv.index('--logtofile') ? true : false) if (@LOG_TO_FILE) FileUtils.mkdir_p(File.dirname(LOG_FILE)) args = args.gsub("--logtofile","") end basedir = File.expand_path(File.join(File.dirname(__FILE__), '..')) # The puppet installer registers a 'Puppet' event source. For the moment events will be logged with this key, but # it may be a good idea to split the Service and Puppet events later so it's easier to read in the windows Event Log. # # Example code to register an event source; # eventlogdll = File.expand_path(File.join(basedir, 'puppet', 'ext', 'windows', 'eventlog', 'puppetres.dll')) # if (File.exists?(eventlogdll)) # Win32::EventLog.add_event_source( # 'source' => "Application", # 'key_name' => "Puppet Agent", # 'category_count' => 3, # 'event_message_file' => eventlogdll, # 'category_message_file' => eventlogdll # ) # end puppet = File.join(basedir, 'bin', 'puppet.bat') unless File.exists?(puppet) log_err("File not found: '#{puppet}'") return end log_debug("Using '#{puppet}'") cmdline_debug = argsv.index('--debug') ? :debug : nil @loglevel = parse_log_level(puppet, cmdline_debug) log_notice('Service started') service = self @run_thread = Thread.new do begin while service.running? do runinterval = service.parse_runinterval(puppet) if service.state == RUNNING or service.state == IDLE service.log_notice("Executing agent with arguments: #{args}") pid = Process.create(:command_line => "\"#{puppet}\" agent --onetime #{args}", :creation_flags => CREATE_NEW_CONSOLE).process_id service.log_debug("Process created: #{pid}") else service.log_debug("Service is paused. Not invoking Puppet agent") end service.log_debug("Service worker thread waiting for #{runinterval} seconds") sleep(runinterval) service.log_debug('Service worker thread woken up') end rescue Exception => e service.log_exception(e) end end @run_thread.join rescue Exception => e log_exception(e) ensure log_notice('Service stopped') end def service_stop log_notice('Service stopping / killing worker thread') @run_thread.kill if @run_thread end def service_pause log_notice('Service pausing') end def service_resume log_notice('Service resuming') end def service_shutdown log_notice('Host shutting down') end # Interrogation handler is just for debug. Can be commented out or removed entirely. # def service_interrogate # log_debug('Service is being interrogated') # end def log_exception(e) log_err(e.message) log_err(e.backtrace.join("\n")) end def log(msg, level) if LEVELS.index(level) >= @loglevel if (@LOG_TO_FILE) # without this change its possible that we get Encoding errors trying to write UTF-8 messages in current codepage File.open(LOG_FILE, 'a:UTF-8') { |f| f.puts("#{Time.now} Puppet (#{level}): #{msg}") } end native_type, native_id = Puppet::Util::Windows::EventLog.to_native(level) report_windows_event(native_type, native_id, msg.to_s) end end def report_windows_event(type,id,message) begin eventlog = nil eventlog = Puppet::Util::Windows::EventLog.open("Puppet") eventlog.report_event( :event_type => type, # EVENTLOG_ERROR_TYPE, etc :event_id => id, # 0x01 or 0x02, 0x03 etc. :data => message # "the message" ) rescue Exception # Ignore all errors ensure if (!eventlog.nil?) eventlog.close end end end def parse_runinterval(puppet_path) begin runinterval = %x{ "#{puppet_path}" agent --configprint runinterval }.to_i if runinterval == 0 runinterval = 1800 log_err("Failed to determine runinterval, defaulting to #{runinterval} seconds") end rescue Exception => e log_exception(e) runinterval = 1800 end runinterval end def parse_log_level(puppet_path,cmdline_debug) begin loglevel = %x{ "#{puppet_path}" agent --configprint log_level}.chomp unless loglevel loglevel = :notice log_err("Failed to determine loglevel, defaulting to #{loglevel}") end rescue Exception => e log_exception(e) loglevel = :notice end LEVELS.index(cmdline_debug ? cmdline_debug : loglevel.to_sym) end end if __FILE__ == $0 WindowsDaemon.mainloop end puppet-5.5.10/ext/yaml_nodes.rb0000755005276200011600000000562213417161721016301 0ustar jenkinsjenkins#!/usr/bin/ruby # # = Synopsis # # Use YAML files to provide external node support. # # = Usage # # yaml-nodes # # = Description # # This is a simple example external node script. It allows you to maintain your # node information in yaml files, and it will find a given node's file and produce # it on stdout. It has simple inheritance, in that a node can specify a parent # node, and the node will inherit that parent's classes and parameters. # # = Options # # help:: # Print this help message # # yamldir:: # Specify where the yaml is found. Defaults to 'yaml' in the current directory. # # = License # Copyright 2011 Luke Kanies # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require 'yaml' require 'optparse' BASEDIR = Dir.chdir(File.dirname(__FILE__) + "/..") { Dir.getwd } options = {:yamldir => File.join(BASEDIR, "yaml")} OptionParser.new do |opts| opts.banner = "Usage: yaml-nodes [options] " opts.on("-y dir", "--yamldir dir", "Specify the directory with the YAML files") do |arg| raise "YAML directory #{arg} does not exist or is not a directory" unless FileTest.directory?(arg) options[:yamldir] = arg end opts.on("-h", "--help", "Print this help") do puts opts.help exit(0) end end.parse! # Read in a pure yaml representation of our node. def read_node(node) nodefile = File.join(YAMLDIR, "#{node}.yaml") if FileTest.exist?(nodefile) return YAML.load_file(nodefile) else raise "Could not find information for #{node}" end end node = ARGV[0] info = read_node(node) # Iterate over any provided parents, merging in there information. parents_seen = [] while parent = info["parent"] raise "Found inheritance loop with parent #{parent}" if parents_seen.include?(parent) parents_seen << parent info.delete("parent") parent_info = read_node(parent) # Include any parent classes in our list. if pclasses = parent_info["classes"] info["classes"] += pclasses info["classes"].uniq! end # And inherit parameters from our parent, while preferring our own values. if pparams = parent_info["parameters"] # When using Hash#merge, the hash being merged in wins, and we # want the subnode parameters to be the parent node parameters. info["parameters"] = pparams.merge(info["parameters"]) end # Copy over any parent node name. if pparent = parent_info["parent"] info["parent"] = pparent end end puts YAML.dump(info) puppet-5.5.10/ext/autotest/0000755005276200011600000000000013417162176015467 5ustar jenkinsjenkinspuppet-5.5.10/ext/autotest/Rakefile0000644005276200011600000000023713417161721017131 0ustar jenkinsjenkinsdest = File.expand_path("~/.autotest") file dest => ["config", "Rakefile"] do sh "cp config #{dest}" end task :install => dest task :default => :install puppet-5.5.10/ext/autotest/config0000644005276200011600000000245013417161721016653 0ustar jenkinsjenkins# vim: syntax=ruby # From http://pastie.caboo.se/115692, linked from rickbradley require 'autotest/redgreen' require 'autotest/timestamp' Autotest.send(:alias_method, :real_find_files, :find_files) Autotest.send(:define_method, :find_files) do |*args| real_find_files.reject do |k, v| if (ENV['AUTOTEST'] and !ENV['AUTOTEST'].empty?) !Regexp.new(ENV['AUTOTEST']).match(k) end end end module Autotest::Growl def self.growl title, msg, img, pri=0, sticky="" system "growlnotify -n autotest --image #{img} -p #{pri} -m #{msg.inspect} #{title} #{sticky}" end Autotest.add_hook :ran_command do |at| image_root = "~/.autotest_images" results = [at.results].flatten.join("\n") output = results.slice(/(\d+)\stests,\s(\d+)\sassertions,\s(\d+)\sfailures,\s(\d+)\serrors/) if output if $~[3].to_i > 0 || $~[4].to_i > 0 growl "FAIL", "#{output}", "#{image_root}/fail.png", 2 else growl "Pass", "#{output}", "#{image_root}/pass.png" end end output = results.slice(/(\d+)\sexamples,\s(\d+)\sfailures?(,\s+\d+\s+pending)?/) if output if $~[2].to_i > 0 || $~[4].to_i > 0 growl "FAIL", "#{output}", "#{image_root}/fail.png", 2 else growl "Pass", "#{output}", "#{image_root}/pass.png" end end end end puppet-5.5.10/ext/autotest/readme.rst0000644005276200011600000000136713417161721017460 0ustar jenkinsjenkinsAutotest is a simple tool that automatically links tests with the files being tested, and runs tests automatically when either the test or code has changed. If you are running on a Mac and have growlnotify_ installed, install the ZenTest_ gem, then copy the ``config`` file to ``~/.autotest`` (or just run ``rake`` in this directory). Once you have ``autotest`` installed, change to the root of your Puppet git repository and run ``autotest`` with no arguments. To refresh the list of files to scan, hit ``^c`` (that is, control-c). It's recommended you leave this running in another terminal during all development, preferably on another monitor. .. _zentest: http://www.zenspider.com/ZSS/Products/ZenTest/ .. _growlnotify: http://growl.info/extras.php puppet-5.5.10/ext/project_data.yaml0000644005276200011600000000411613417161721017137 0ustar jenkinsjenkins--- project: 'puppet' author: 'Puppet Labs' email: 'info@puppetlabs.com' homepage: 'https://github.com/puppetlabs/puppet' summary: 'Puppet, an automated configuration management tool' description: 'Puppet, an automated configuration management tool' version_file: 'lib/puppet/version.rb' # files and gem_files are space separated lists files: '[A-Z]* install.rb bin lib conf man examples ext tasks spec locales' # The gem specification bits only work on Puppet >= 3.0rc, NOT 2.7.x and earlier gem_files: '[A-Z]* install.rb bin lib conf man examples ext tasks spec locales' gem_test_files: 'spec/**/*' gem_executables: 'puppet' gem_default_executables: 'puppet' gem_forge_project: 'puppet' gem_required_ruby_version: '>= 1.9.3' gem_required_rubygems_version: '> 1.3.1' gem_runtime_dependencies: facter: ['> 2.0.1', '< 4'] hiera: ['>= 3.2.1', '< 4'] # PUP-7115 - return to a gem dependency in Puppet 5 # semantic_puppet: ['>= 0.1.3', '< 2'] fast_gettext: '~> 1.1.2' locale: '~> 2.1' multi_json: '~> 1.10' gem_rdoc_options: - --title - "Puppet - Configuration Management" - --main - README.md - --line-numbers gem_platform_dependencies: universal-darwin: gem_runtime_dependencies: CFPropertyList: '~> 2.2' x86-mingw32: gem_runtime_dependencies: # Pinning versions that require native extensions # ffi is pinned due to PUP-8438 ffi: '<= 1.9.18' # win32-xxxx gems are pinned due to PUP-6445 win32-dir: '= 0.4.9' win32-process: '= 0.7.5' # Use of win32-security is deprecated win32-security: '= 0.2.5' win32-service: '= 0.8.8' minitar: '~> 0.6.1' x64-mingw32: gem_runtime_dependencies: # ffi is pinned due to PUP-8438 ffi: '<= 1.9.18' # win32-xxxx gems are pinned due to PUP-6445 win32-dir: '= 0.4.9' win32-process: '= 0.7.5' # Use of win32-security is deprecated win32-security: '= 0.2.5' win32-service: '= 0.8.8' minitar: '~> 0.6.1' bundle_platforms: universal-darwin: all x86-mingw32: mingw x64-mingw32: x64_mingw pre_tasks: 'package:apple': 'cfpropertylist' puppet-5.5.10/ext/rack/0000755005276200011600000000000013417162176014537 5ustar jenkinsjenkinspuppet-5.5.10/ext/rack/config.ru0000644005276200011600000000316313417161721016352 0ustar jenkinsjenkins# a config.ru, for use with every rack-compatible webserver. # SSL needs to be handled outside this, though. # if puppet is not in your RUBYLIB: # $LOAD_PATH.unshift('/opt/puppet/lib') $0 = "master" # if you want debugging: # ARGV << "--debug" ARGV << "--rack" # Rack applications typically don't start as root. Set --confdir, --vardir, # --logdir, --rundir to prevent reading configuration from # ~/ based pathing. ARGV << "--confdir" << "/etc/puppetlabs/puppet" ARGV << "--vardir" << "/opt/puppetlabs/server/data/puppetmaster" ARGV << "--logdir" << "/var/log/puppetlabs/puppetmaster" ARGV << "--rundir" << "/var/run/puppetlabs/puppetmaster" ARGV << "--codedir" << "/etc/puppetlabs/code" # disable always_retry_plugsin as a performance improvement. This is safe for a master to # apply. This is intended to allow agents to recognize new features that may be # delivered during catalog compilation. ARGV << "--no-always_retry_plugins" # NOTE: it's unfortunate that we have to use the "CommandLine" class # here to launch the app, but it contains some initialization logic # (such as triggering the parsing of the config file) that is very # important. We should do something less nasty here when we've # gotten our API and settings initialization logic cleaned up. # # Also note that the "$0 = master" line up near the top here is # the magic that allows the CommandLine class to know that it's # supposed to be running master. # # --cprice 2012-05-22 require 'puppet/util/command_line' # we're usually running inside a Rack::Builder.new {} block, # therefore we need to call run *here*. run Puppet::Util::CommandLine.new.execute puppet-5.5.10/ext/rack/example-passenger-vhost.conf0000644005276200011600000000472613417161721022173 0ustar jenkinsjenkins# This Apache 2 virtual host config shows how to use Puppet as a Rack # application via Passenger. See # https://puppet.com/docs/puppet/latest/passenger.html for more information. # You can also use the included config.ru file to run Puppet with other Rack # servers instead of Passenger. # you probably want to tune these settings PassengerHighPerformance on PassengerMaxPoolSize 12 PassengerPoolIdleTime 1500 # PassengerMaxRequests 1000 PassengerStatThrottleRate 120 RackAutoDetect Off RailsAutoDetect Off Listen 8140 SSLEngine on SSLProtocol ALL -SSLv2 -SSLv3 SSLCipherSuite EDH+CAMELLIA:EDH+aRSA:EECDH+aRSA+AESGCM:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH:+CAMELLIA256:+AES256:+CAMELLIA128:+AES128:+SSLv3:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!DSS:!RC4:!SEED:!IDEA:!ECDSA:kEDH:CAMELLIA256-SHA:AES256-SHA:CAMELLIA128-SHA:AES128-SHA SSLHonorCipherOrder on SSLCertificateFile /etc/puppet/ssl/certs/squigley.namespace.at.pem SSLCertificateKeyFile /etc/puppet/ssl/private_keys/squigley.namespace.at.pem SSLCertificateChainFile /etc/puppet/ssl/ca/ca_crt.pem SSLCACertificateFile /etc/puppet/ssl/ca/ca_crt.pem # If Apache complains about invalid signatures on the CRL, you can try disabling # CRL checking by commenting the next line, but this is not recommended. SSLCARevocationFile /etc/puppet/ssl/ca/ca_crl.pem # Apache 2.4 introduces the SSLCARevocationCheck directive and sets it to none # which effectively disables CRL checking; if you are using Apache 2.4+ you must # specify 'SSLCARevocationCheck chain' to actually use the CRL. # SSLCARevocationCheck chain SSLVerifyClient optional SSLVerifyDepth 1 # The `ExportCertData` option is needed for agent certificate expiration warnings SSLOptions +StdEnvVars +ExportCertData # This header needs to be set if using a loadbalancer or proxy RequestHeader unset X-Forwarded-For RequestHeader set X-SSL-Subject %{SSL_CLIENT_S_DN}e RequestHeader set X-Client-DN %{SSL_CLIENT_S_DN}e RequestHeader set X-Client-Verify %{SSL_CLIENT_VERIFY}e DocumentRoot /etc/puppet/rack/public/ RackBaseURI / Options None AllowOverride None Order allow,deny allow from all puppet-5.5.10/tasks/0000755005276200011600000000000013417162176014144 5ustar jenkinsjenkinspuppet-5.5.10/tasks/cfpropertylist.rake0000644005276200011600000000143213417161721020074 0ustar jenkinsjenkinstask 'cfpropertylist' do if defined? Pkg::Config and Pkg::Config.project_root cfp_version = "2.3.5" libdir = File.join(Pkg::Config.project_root, "lib") source = "https://github.com/ckruse/CFPropertyList/archive/cfpropertylist-#{cfp_version}.tar.gz" target_dir = Pkg::Util::File.mktemp target = File.join(target_dir, "cfpropertylist") Pkg::Util::Net.fetch_uri(source, target) Pkg::Util::File.untar_into(target, target_dir, "--strip-components 1") mv(Dir.glob("#{File.join(target_dir, "lib")}/cfpropertylist*"), libdir) mv(Dir.glob("#{target_dir}/{LICENSE,README,THANKS}"), File.join(libdir, "cfpropertylist")) else warn "It looks like the packaging tasks have not been loaded. You'll need to `rake package:bootstrap` before using this task" end end puppet-5.5.10/tasks/generate_ast_model.rake0000644005276200011600000001025513417161721020627 0ustar jenkinsjenkinsbegin require 'puppet' rescue LoadError #nothing to see here else desc "Generate the Pcore model that represents the AST for the Puppet Language" task :gen_pcore_ast do Puppet::Pops.generate_ast end module Puppet::Pops def self.generate_ast Puppet.initialize_settings env = Puppet.lookup(:environments).get(Puppet[:environment]) loaders = Loaders.new(env) ast_pp = Pathname(__FILE__).parent.parent + 'lib/puppet/pops/model/ast.pp' Puppet.override(:current_environment => env, :loaders => loaders) do ast_factory = Parser::Parser.new.parse_file(ast_pp.expand_path.to_s) ast_model = Types::TypeParser.singleton.interpret( ast_factory.model.body, Loader::PredefinedLoader.new(loaders.find_loader(nil), 'TypeSet loader')) ruby = Types::RubyGenerator.new.module_definition_from_typeset(ast_model) # Replace ref() constructs to known Pcore types with directly initialized types. ref() cannot be used # since it requires a parser (chicken-and-egg problem) ruby.gsub!(/^module Parser\nmodule Locator\n.*\nend\nend\nmodule Model\n/m, "module Model\n") # Remove generated RubyMethod annotations. The ruby methods are there now, no need to also have # the annotations present. ruby.gsub!(/^\s+'annotations' => \{\n\s+ref\('RubyMethod'\) => \{\n.*\n\s+\}\n\s+\},\n/, '') ruby.gsub!(/ref\('([A-Za-z]+)'\)/, 'Types::P\1Type::DEFAULT') ruby.gsub!(/ref\('Optional\[([0-9A-Za-z_]+)\]'\)/, 'Types::POptionalType.new(Types::P\1Type::DEFAULT)') ruby.gsub!(/ref\('Array\[([0-9A-Za-z_]+)\]'\)/, 'Types::PArrayType.new(Types::P\1Type::DEFAULT)') ruby.gsub!(/ref\('Optional\[Array\[([0-9A-Za-z_]+)\]\]'\)/, 'Types::POptionalType.new(Types::PArrayType.new(Types::P\1Type::DEFAULT))') ruby.gsub!(/ref\('Enum(\[[^\]]+\])'\)/) do |match| params = $1 params.gsub!(/\\'/, '\'') "Types::PEnumType.new(#{params})" end # Replace ref() constructs with references to _pcore_type of the types in the module namespace ruby.gsub!(/ref\('Puppet::AST::Locator'\)/, 'Parser::Locator::Locator19._pcore_type') ruby.gsub!(/ref\('Puppet::AST::([0-9A-Za-z_]+)'\)/, '\1._pcore_type') ruby.gsub!(/ref\('Optional\[Puppet::AST::([0-9A-Za-z_]+)\]'\)/, 'Types::POptionalType.new(\1._pcore_type)') ruby.gsub!(/ref\('Array\[Puppet::AST::([0-9A-Za-z_]+)\]'\)/, 'Types::PArrayType.new(\1._pcore_type)') ruby.gsub!(/ref\('Array\[Puppet::AST::([0-9A-Za-z_]+), 1, default\]'\)/, 'Types::PArrayType.new(\1._pcore_type, Types::PCollectionType::NOT_EMPTY_SIZE)') # Remove the generated ref() method. It's not needed by this model ruby.gsub!(/ def self\.ref\(type_string\)\n.*\n end\n\n/, '') # Add Program#current method for backward compatibility ruby.gsub!(/(attr_reader :body\n attr_reader :definitions\n attr_reader :locator)/, "\\1\n\n def current\n self\n end") # Replace the generated registration with a registration that uses the static loader. This will # become part of the Puppet bootstrap code and there will be no other loader until we have a # parser. ruby.gsub!(/^Puppet::Pops::Pcore.register_implementations\((\[[^\]]+\])\)/, <<-RUBY) module Model @@pcore_ast_initialized = false def self.register_pcore_types return if @@pcore_ast_initialized @@pcore_ast_initialized = true all_types = \\1 # Create and register a TypeSet that corresponds to all types in the AST model types_map = {} all_types.each do |type| types_map[type._pcore_type.simple_name] = type._pcore_type end type_set = Types::PTypeSetType.new({ 'name' => 'Puppet::AST', 'pcore_version' => '1.0.0', 'types' => types_map }) loc = Puppet::Util.path_to_uri("\#{__FILE__}") Loaders.static_loader.set_entry(Loader::TypedName.new(:type, 'puppet::ast', Pcore::RUNTIME_NAME_AUTHORITY), type_set, URI("\#{loc}?line=1")) Loaders.register_static_implementations(all_types) end end RUBY ast_rb = Pathname(__FILE__).parent.parent + 'lib/puppet/pops/model/ast.rb' File.open(ast_rb.to_s, 'w') { |f| f.write(ruby) } end end end end puppet-5.5.10/tasks/memwalk.rake0000644005276200011600000001347013417161721016445 0ustar jenkinsjenkins# Walks the memory dumped into heap.json, and produces a graph of the memory dumped in diff.json # If a single argument (a hex address to one object) is given, the graph is limited to this object and what references it # The heap dumps should be in the format produced by Ruby ObjectSpace in Ruby version 2.1.0 or later. # # The command produces a .dot file that can be rendered with graphwiz dot into SVG. If a memwalk is performed for all # objects in the diff.json, the output file name is memwalk.dot. If it is produced for a single address, the name of the # output file is memwalk-
.dot # # The dot file can be rendered with something like: dot -Tsvg -omemwalk.svg memwalk.dot # desc "Process a diff.json of object ids, and a heap.json of a Ruby 2.1.0 ObjectSpace dump and produce a graph" task :memwalk, [:id] do |t, args| puts "Memwalk" puts "Computing for #{args[:id] ? args[:id] : 'all'}" @single_id = args[:id] ? args[:id].to_i(16) : nil require 'json' #require 'debug' TYPE = "type".freeze ROOT = "root".freeze ROOT_UC = "ROOT".freeze ADDR = "address".freeze NODE = "NODE".freeze STRING = "STRING".freeze DATA = "DATA".freeze HASH = "HASH".freeze ARRAY = "ARRAY".freeze OBJECT = "OBJECT".freeze CLASS = "CLASS".freeze allocations = {} # An array of integer addresses of the objects to trace bindings for diff_index = {} puts "Reading data" begin puts "Reading diff" lines = 0; File.readlines("diff.json").each do | line | lines += 1 diff = JSON.parse(line) case diff[ TYPE ] when STRING, DATA, HASH, ARRAY # skip the strings else diff_index[ diff[ ADDR ].to_i(16) ] = diff end end puts "Read #{lines} number of diffs" rescue => e raise "ERROR READING DIFF at line #{lines} #{e.message[0, 200]}" end begin puts "Reading heap" lines = 0 allocation = nil File.readlines("heap.json").each do | line | lines += 1 allocation = JSON.parse(line) case allocation[ TYPE ] when ROOT_UC # Graph for single id must include roots, as it may be a root that holds on to the reference # a global variable, thread, etc. # if @single_id allocations[ allocation[ ROOT ] ] = allocation end when NODE # skip the NODE objects - they represent the loaded ruby code when STRING # skip all strings - they are everywhere else allocations[ allocation[ ADDR ].to_i(16) ] = allocation end end puts "Read #{lines} number of entries" rescue => e require 'debug' puts "ERROR READING HEAP #{e.message[0, 200]}" raise e end @heap = allocations puts "Building reference index" # References is an index from a referenced object to an array with addresses to the objects that references it @references = Hash.new { |h, k| h[k] = [] } REFERENCES = "references".freeze allocations.each do |k,v| refs = v[ REFERENCES ] if refs.is_a?(Array) refs.each {|addr| @references[ addr.to_i(16) ] << k } end end @printed = Set.new() def print_object(addr, entry) # only print each node once return unless @printed.add?(addr) begin if addr.is_a?(String) @output.write( "x#{node_name(addr)} [label=\"#{node_label(addr, entry)}\\n#{addr}\"];\n") else @output.write( "x#{node_name(addr)} [label=\"#{node_label(addr, entry)}\\n#{addr.to_s(16)}\"];\n") end rescue => e require 'debug' raise e end end def node_label(addr, entry) if entry[ TYPE ] == OBJECT class_ref = entry[ "class" ].to_i(16) @heap[ class_ref ][ "name" ] elsif entry[ TYPE ] == CLASS "CLASS #{entry[ "name"]}" else entry[TYPE] end end def node_name(addr) return addr if addr.is_a? String addr.to_s(16) end def print_edge(from_addr, to_addr) @output.write("x#{node_name(from_addr)}->x#{node_name(to_addr)};\n") end def closure_and_edges(diff) edges = Set.new() walked = Set.new() puts "Number of diffs referenced = #{diff.count {|k,_| @references[k].is_a?(Array) && @references[k].size() > 0 }}" diff.each {|k,_| walk(k, edges, walked) } edges.each {|e| print_edge(*e) } end def walk(addr, edges, walked) if !@heap[ addr ].nil? print_object(addr, @heap[addr]) @references [ addr ].each do |r| walk_to_object(addr, r, edges, walked) end end end def walk_to_object(to_addr, cursor, edges, walked) return unless walked # if walked to an object, or everything if a single_id is the target if @heap[ cursor ][ TYPE ] == OBJECT || (@single_id && @heap[ cursor ][ TYPE ] == ROOT_UC || @heap[ cursor ][ TYPE ] == CLASS ) # and the edge is unique if edges.add?( [ cursor, to_addr ] ) # then we may not have visited objects this objects is being referred from print_object(cursor, @heap[ cursor ]) # Do not follow what binds a class if @heap[ cursor ][ TYPE ] != CLASS @references[ cursor ].each do |r| walk_to_object(cursor, r, edges, walked.add?(r)) walked.delete(r) end end end else # continue search until Object @references[cursor].each do |r| walk_to_object(to_addr, r, edges, walked.add?(r)) end end end def single_closure_and_edges(the_target) edges = Set.new() walked = Set.new() walk(the_target, edges, walked) edges.each {|e| print_edge(*e) } end puts "creating graph" if @single_id @output = File.open("memwalk-#{@single_id.to_s(16)}.dot", "w") @output.write("digraph root {\n") single_closure_and_edges(@single_id) else @output = File.open("memwalk.dot", "w") @output.write("digraph root {\n") closure_and_edges(diff_index) end @output.write("}\n") @output.close puts "done" end puppet-5.5.10/tasks/parallel.rake0000644005276200011600000002753513417161721016613 0ustar jenkinsjenkins# encoding: utf-8 require 'rubygems' require 'thread' begin require 'rspec' require 'rspec/core/formatters/helpers' require 'facter' rescue LoadError # Don't define the task if we don't have rspec or facter present else module Parallel module RSpec # # Responsible for buffering the output of RSpec's progress formatter. # class ProgressFormatBuffer attr_reader :pending_lines attr_reader :failure_lines attr_reader :examples attr_reader :failures attr_reader :pending attr_reader :failed_example_lines attr_reader :state module OutputState HEADER = 1 PROGRESS = 2 SUMMARY = 3 PENDING = 4 FAILURES = 5 DURATION = 6 COUNTS = 7 FAILED_EXAMPLES = 8 end def initialize(io, color) @io = io @color = color @state = OutputState::HEADER @pending_lines = [] @failure_lines = [] @examples = 0 @failures = 0 @pending = 0 @failed_example_lines = [] end def color? @color end def read # Parse and ignore the one line header if @state == OutputState::HEADER begin @io.readline rescue EOFError return nil end @state = OutputState::PROGRESS return '' end # If the progress has been read, parse the summary if @state == OutputState::SUMMARY parse_summary return nil end # Read the progress output up to 128 bytes at a time # 128 is a small enough number to show some progress, but not too small that # we're constantly writing synchronized output data = @io.read(128) return nil unless data data = @remainder + data if @remainder # Check for the end of the progress line if (index = data.index "\n") @state = OutputState::SUMMARY @remainder = data[(index+1)..-1] data = data[0...index] # Check for partial ANSI escape codes in colorized output elsif @color && !data.end_with?("\e[0m") && (index = data.rindex("\e[", -6)) @remainder = data[index..-1] data = data[0...index] else @remainder = nil end data end private def parse_summary # If there is a remainder, concat it with the next line and handle each line unless @remainder.empty? lines = @remainder eof = false begin lines += @io.readline rescue EOFError eof = true end lines.each_line do |line| parse_summary_line line end return if eof end # Process the rest of the lines begin @io.each_line do |line| parse_summary_line line end rescue EOFError end end def parse_summary_line(line) line.chomp! return if line.empty? if line == 'Pending:' @status = OutputState::PENDING return elsif line == 'Failures:' @status = OutputState::FAILURES return elsif line == 'Failed examples:' @status = OutputState::FAILED_EXAMPLES return elsif (line.match /^Finished in ((\d+\.?\d*) minutes?)? ?(\d+\.?\d*) seconds?$/) @status = OutputState::DURATION return elsif (match = line.gsub(/\e\[\d+m/, '').match /^(\d+) examples?, (\d+) failures?(, (\d+) pending)?$/) @status = OutputState::COUNTS @examples = match[1].to_i @failures = match[2].to_i @pending = (match[4] || 0).to_i return end case @status when OutputState::PENDING @pending_lines << line when OutputState::FAILURES @failure_lines << line when OutputState::FAILED_EXAMPLES @failed_example_lines << line end end end # # Responsible for parallelizing spec testing. # Optional options list will be passed to rspec. # class Parallelizer # Number of processes to use attr_reader :process_count # Approximate size of each group of tests attr_reader :group_size # Options list for rspec attr_reader :options def initialize(process_count, group_size, color, options = []) @process_count = process_count @group_size = group_size @color = color @options = options end def color? @color end def run @start_time = Time.now groups = group_specs fail red('error: no specs were found') if groups.length == 0 begin run_specs(groups, options) ensure groups.each do |file| File.unlink(file) end end end private def group_specs # Spawn the rspec_grouper utility to perform the test grouping # We do this in a separate process to limit this processes' long-running footprint io = IO.popen("ruby util/rspec_grouper #{@group_size}") header = true spec_group_files = [] io.each_line do |line| line.chomp! header = false if line.empty? next if header || line.empty? spec_group_files << line end _, status = Process.waitpid2(io.pid) io.close fail red('error: no specs were found.') unless status.success? spec_group_files end def run_specs(groups, options) puts "Processing #{groups.length} spec group(s) with #{@process_count} worker(s)" interrupted = false success = true worker_threads = [] group_index = -1 pids = Array.new(@process_count) mutex = Mutex.new # Handle SIGINT by killing child processes original_handler = Signal.trap :SIGINT do break if interrupted interrupted = true # Can't synchronize in a trap context, so read dirty pids.each do |pid| begin Process.kill(:SIGKILL, pid) if pid rescue Errno::ESRCH end end puts yellow("\nshutting down...") end buffers = [] process_count.times do |thread_id| worker_threads << Thread.new do while !interrupted do # Get the spec file for this rspec run group = mutex.synchronize { if group_index < groups.length then groups[group_index += 1] else nil end } break unless group && !interrupted # Spawn the worker process with redirected output options_string = options ? options.join(' ') : '' io = IO.popen("ruby util/rspec_runner #{group} #{options_string}") pids[thread_id] = io.pid # TODO: make the buffer pluggable to handle other output formats like documentation buffer = ProgressFormatBuffer.new(io, @color) # Process the output while !interrupted output = buffer.read break unless output && !interrupted next if output.empty? mutex.synchronize { print output } end # Kill the process if we were interrupted, just to be sure if interrupted begin Process.kill(:SIGKILL, pids[thread_id]) rescue Errno::ESRCH end end # Reap the process result = Process.waitpid2(pids[thread_id])[1].success? io.close pids[thread_id] = nil mutex.synchronize do buffers << buffer success &= result end end end end # Join all worker threads worker_threads.each do |thread| thread.join end Signal.trap :SIGINT, original_handler fail yellow('execution was interrupted') if interrupted dump_summary buffers success end def colorize(text, color_code) if @color "#{color_code}#{text}\e[0m" else text end end def red(text) colorize(text, "\e[31m") end def green(text) colorize(text, "\e[32m") end def yellow(text) colorize(text, "\e[33m") end def dump_summary(buffers) puts # Print out the pending tests print_header = true buffers.each do |buffer| next if buffer.pending_lines.empty? if print_header puts "\nPending:" print_header = false end puts buffer.pending_lines end # Print out the failures print_header = true buffers.each do |buffer| next if buffer.failure_lines.empty? if print_header puts "\nFailures:" print_header = false end puts puts buffer.failure_lines end # Print out the run time puts "\nFinished in #{::RSpec::Core::Formatters::Helpers.format_duration(Time.now - @start_time)}" # Count all of the examples examples = 0 failures = 0 pending = 0 buffers.each do |buffer| examples += buffer.examples failures += buffer.failures pending += buffer.pending end if failures > 0 puts red(summary_count_line(examples, failures, pending)) elsif pending > 0 puts yellow(summary_count_line(examples, failures, pending)) else puts green(summary_count_line(examples, failures, pending)) end # Print out the failed examples print_header = true buffers.each do |buffer| next if buffer.failed_example_lines.empty? if print_header puts "\nFailed examples:" print_header = false end puts buffer.failed_example_lines end end def summary_count_line(examples, failures, pending) summary = ::RSpec::Core::Formatters::Helpers.pluralize(examples, "example") summary << ", " << ::RSpec::Core::Formatters::Helpers.pluralize(failures, "failure") summary << ", #{pending} pending" if pending > 0 summary end end end end namespace 'parallel' do def color_output? # Check with RSpec to see if color is enabled config = ::RSpec::Core::Configuration.new config.error_stream = $stderr config.output_stream = $stdout options = ::RSpec::Core::ConfigurationOptions.new [] options.configure config config.color end desc 'Runs specs in parallel. Extra args are passed to rspec.' task 'spec', [:process_count, :group_size] do |_, args| # Default group size in rspec examples DEFAULT_GROUP_SIZE = 1000 process_count = [(args[:process_count] || Facter.value("processorcount")).to_i, 1].max group_size = [(args[:group_size] || DEFAULT_GROUP_SIZE).to_i, 1].max abort unless Parallel::RSpec::Parallelizer.new(process_count, group_size, color_output?, args.extras).run end end end puppet-5.5.10/tasks/yard.rake0000644005276200011600000000266113417161721015747 0ustar jenkinsjenkinsbegin require 'yard' namespace :doc do desc "Clean up generated documentation" task :clean do rm_rf "doc" end desc "Generate public documentation pages for the API" YARD::Rake::YardocTask.new(:api) do |t| t.files = ['lib/**/*.rb'] t.options = %w{ --protected --private --verbose --markup markdown --readme README.md --tag status --transitive-tag status --tag comment --hide-tag comment --tag dsl:"DSL" --no-transitive-tag api --template-path yardoc/templates --files README_DEVELOPER.md,CO*.md,api/**/*.md --api public --api private --hide-void-return } end desc "Generate documentation pages for all of the code" YARD::Rake::YardocTask.new(:all) do |t| t.files = ['lib/**/*.rb'] t.options = %w{ --verbose --markup markdown --readme README.md --tag status --transitive-tag status --tag comment --hide-tag comment --tag dsl:"DSL" --no-transitive-tag api --template-path yardoc/templates --files README_DEVELOPER.md,CO*.md,api/**/*.md --api public --api private --no-api --hide-void-return } end end rescue LoadError => e if verbose STDERR.puts "Document generation not available without yard. #{e.message}" end end puppet-5.5.10/tasks/benchmark.rake0000644005276200011600000001237513417161722016746 0ustar jenkinsjenkinsrequire 'benchmark' require 'tmpdir' require 'csv' require 'objspace' namespace :benchmark do def generate_scenario_tasks(location, name) desc File.read(File.join(location, 'description')) task name => "#{name}:run" # Load a BenchmarkerTask to handle config of the benchmark task_handler_file = File.expand_path(File.join(location, 'benchmarker_task.rb')) if File.exist?(task_handler_file) require task_handler_file run_args = BenchmarkerTask.run_args else run_args = [] end namespace name do task :setup do ENV['ITERATIONS'] ||= '10' ENV['SIZE'] ||= '100' ENV['TARGET'] ||= Dir.mktmpdir(name) ENV['TARGET'] = File.expand_path(ENV['TARGET']) mkdir_p(ENV['TARGET']) require File.expand_path(File.join(location, 'benchmarker.rb')) @benchmark = Benchmarker.new(ENV['TARGET'], ENV['SIZE'].to_i) end task :generate => :setup do @benchmark.generate @benchmark.setup end desc "Run the #{name} scenario." task :run, [*run_args] => :generate do |_, args| report = [] details = [] Benchmark.benchmark(Benchmark::CAPTION, 10, Benchmark::FORMAT, "> total:", "> avg:") do |b| times = [] ENV['ITERATIONS'].to_i.times do |i| start_time = Time.now.to_i times << b.report("Run #{i + 1}") do details << @benchmark.run(args) end report << [to_millis(start_time), to_millis(times.last.real), 200, true, name] end sum = times.inject(Benchmark::Tms.new, &:+) [sum, sum / times.length] end write_csv("#{name}.samples", %w{timestamp elapsed responsecode success name}, report) # report details, if any were produced if details[0].is_a?(Array) && details[0][0].is_a?(Benchmark::Tms) # assume all entries are Tms if the first is # turn each into a hash of label => tms (since labels are lost when doing arithmetic on Tms) hashed = details.reduce([]) do |memo, measures| memo << measures.reduce({}) {|memo2, measure| memo2[measure.label] = measure; memo2} memo end # sum across all hashes result = {} hashed_totals = hashed.reduce {|memo, h| memo.merge(h) {|k, old, new| old + new }} # average the totals hashed_totals.keys.each {|k| hashed_totals[k] /= details.length } min_width = 14 max_width = (hashed_totals.keys.map(&:length) << min_width).max puts "\n" puts sprintf("%2$*1$s %3$s", -max_width, 'Details (avg)', " user system total real") puts "-" * (46 + max_width) hashed_totals.sort.each {|k,v| puts sprintf("%2$*1$s %3$s", -max_width, k, v.format) } end end desc "Profile a single run of the #{name} scenario." task :profile, [:warm_up_runs, *run_args] => :generate do |_, args| warm_up_runs = (args[:warm_up_runs] || '0').to_i warm_up_runs.times do @benchmark.run(args) end require 'ruby-prof' result = RubyProf.profile do @benchmark.run(args) end printer = RubyProf::CallTreePrinter.new(result) printer.print(:profile => name, :path => ENV['TARGET']) path = File.join(ENV['TARGET'], "#{name}.callgrind.out.#{$$}") puts "Generated callgrind file: #{path}" end desc "Print a memory profile of the #{name} scenario." task :memory_profile, [*run_args] => :generate do |_, args| require 'memory_profiler' report = MemoryProfiler.report do @benchmark.run(args) end path = "mem_profile_#{$PID}" report.pretty_print(to_file: path) puts "Generated memory profile: #{File.absolute_path(path)}" end desc "Generate a heap dump with object allocation tracing of the #{name} scenario." task :heap_dump, [*run_args] => :generate do |_, args| ObjectSpace.trace_object_allocations_start if ENV['DISABLE_GC'] GC.disable end @benchmark.run(args) unless ENV['DISABLE_GC'] GC.start end path = "heap_#{$PID}.json" File.open(path, 'w') do |file| ObjectSpace.dump_all(output: file) end puts "Generated heap dump: #{File.absolute_path(path)}" end def to_millis(seconds) (seconds * 1000).round end def write_csv(file, header, data) CSV.open(file, 'w') do |csv| csv << header data.each do |line| csv << line end end end end end scenarios = [] Dir.glob('benchmarks/*') do |location| name = File.basename(location) scenarios << name generate_scenario_tasks(location, File.basename(location)) end namespace :all do desc "Profile all of the scenarios. (#{scenarios.join(', ')})" task :profile do scenarios.each do |name| sh "rake benchmark:#{name}:profile" end end desc "Run all of the scenarios. (#{scenarios.join(', ')})" task :run do scenarios.each do |name| sh "rake benchmark:#{name}:run" end end end end puppet-5.5.10/tasks/ci.rake0000644005276200011600000000152313417161722015400 0ustar jenkinsjenkinsrequire 'yaml' require 'time' namespace "ci" do task :spec do ENV["LOG_SPEC_ORDER"] = "true" sh %{rspec --require rspec/legacy_formatters -r yarjuf -f JUnit -o result.xml -fp spec} end desc "Tar up the acceptance/ directory so that package test runs have tests to run against." task :acceptance_artifacts => :tag_creator do Dir.chdir("acceptance") do rm_f "acceptance-artifacts.tar.gz" sh "tar -czv --exclude .bundle -f acceptance-artifacts.tar.gz *" end end task :tag_creator do Dir.chdir("acceptance") do File.open('creator.txt', 'w') do |fh| YAML.dump({ 'creator_id' => ENV['CREATOR'] || ENV['BUILD_URL'] || 'unknown', 'created_on' => Time.now.iso8601, 'commit' => (`git log -1 --oneline` rescue "unknown: #{$!}") }, fh) end end end end puppet-5.5.10/tasks/manpages.rake0000644005276200011600000000611413417161722016601 0ustar jenkinsjenkins# require 'fileutils' desc "Build Puppet manpages" task :gen_manpages do require 'puppet/face' require 'fileutils' # TODO: this line is unfortunate. In an ideal world, faces would serve # as a clear, well-defined entry-point into the code and could be # responsible for state management all on their own; this really should # not be necessary. When we can, we should get rid of it. # --cprice 2012-05-16 Puppet.initialize_settings() helpface = Puppet::Face[:help, '0.0.1'] manface = Puppet::Face[:man, '0.0.1'] sbins = Dir.glob(%w{sbin/*}) bins = Dir.glob(%w{bin/*}) non_face_applications = helpface.legacy_applications faces = Puppet::Face.faces.map(&:to_s) apps = non_face_applications + faces ronn_args = '--manual="Puppet manual" --organization="Puppet, Inc." -r' # Locate ronn ronn = %x{which ronn}.chomp unless File.executable?(ronn) then fail("Ronn does not appear to be installed.") end # def write_manpage(text, filename) # IO.popen("#{ronn} #{ronn_args} -r > #{filename}") do |fh| fh.write text end # end # Create puppet.conf.5 man page # IO.popen("#{ronn} #{ronn_args} > ./man/man5/puppet.conf.5", 'w') do |fh| # fh.write %x{RUBYLIB=./lib:$RUBYLIB bin/puppetdoc --reference configuration} # end %x{RUBYLIB=./lib:$RUBYLIB bin/puppet doc --reference configuration > ./man/man5/puppetconf.5.ronn} %x{#{ronn} #{ronn_args} ./man/man5/puppetconf.5.ronn} FileUtils.mv("./man/man5/puppetconf.5", "./man/man5/puppet.conf.5") FileUtils.rm("./man/man5/puppetconf.5.ronn") # Create LEGACY binary man pages (i.e. delete me for 2.8.0) binary = bins + sbins binary.each do |bin| b = bin.gsub( /^s?bin\//, "") %x{RUBYLIB=./lib:$RUBYLIB #{bin} --help > ./man/man8/#{b}.8.ronn} %x{#{ronn} #{ronn_args} ./man/man8/#{b}.8.ronn} FileUtils.rm("./man/man8/#{b}.8.ronn") end # Create regular non-face man pages non_face_applications.each do |app| %x{RUBYLIB=./lib:$RUBYLIB bin/puppet #{app} --help > ./man/man8/puppet-#{app}.8.ronn} %x{#{ronn} #{ronn_args} ./man/man8/puppet-#{app}.8.ronn} FileUtils.rm("./man/man8/puppet-#{app}.8.ronn") end # Create face man pages faces.each do |face| File.open("./man/man8/puppet-#{face}.8.ronn", 'w') do |fh| fh.write manface.man("#{face}") end %x{#{ronn} #{ronn_args} ./man/man8/puppet-#{face}.8.ronn} FileUtils.rm("./man/man8/puppet-#{face}.8.ronn") end # Delete orphaned manpages if binary was deleted Dir.glob(%w{./man/man8/puppet-*.8}) do |app| appname = app.match(/puppet-(.*)\.8/)[1] FileUtils.rm("./man/man8/puppet-#{appname}.8") unless apps.include?(appname) end # Vile hack: create puppet resource man page # Currently, the useless resource face wins against puppet resource in puppet # man. (And actually, it even gets removed from the list of legacy # applications.) So we overwrite it with the correct man page at the end. %x{RUBYLIB=./lib:$RUBYLIB bin/puppet resource --help > ./man/man8/puppet-resource.8.ronn} %x{#{ronn} #{ronn_args} ./man/man8/puppet-resource.8.ronn} FileUtils.rm("./man/man8/puppet-resource.8.ronn") end puppet-5.5.10/tasks/parser.rake0000644005276200011600000000077113417161722016305 0ustar jenkinsjenkinsdesc "Generate the 4.x 'future' parser" task :gen_eparser do %x{racc -olib/puppet/pops/parser/eparser.rb lib/puppet/pops/parser/egrammar.ra} end desc "Generate the 4.x 'future' parser with egrammar.output" task :gen_eparser_output do %x{racc -v -olib/puppet/pops/parser/eparser.rb lib/puppet/pops/parser/egrammar.ra} end desc "Generate the 4.x 'future' parser with debugging output" task :gen_eparser_debug do %x{racc -t -olib/puppet/pops/parser/eparser.rb lib/puppet/pops/parser/egrammar.ra} end puppet-5.5.10/spec/0000755005276200011600000000000013417162177013752 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/0000755005276200011600000000000013417162176015622 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/faulty_face/0000755005276200011600000000000013417162176020104 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/faulty_face/puppet/0000755005276200011600000000000013417162176021421 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/faulty_face/puppet/face/0000755005276200011600000000000013417162176022317 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/faulty_face/puppet/face/syntax.rb0000644005276200011600000000040413417161721024163 0ustar jenkinsjenkinsPuppet::Face.define(:syntax, '1.0.0') do action :foo do when_invoked do |whom| "hello, #{whom}" end # This 'end' is deliberately omitted, to induce a syntax error. # Please don't fix that, as it is used for testing. --daniel 2011-05-02 end puppet-5.5.10/spec/fixtures/hiera.yaml0000644005276200011600000000016513417161721017573 0ustar jenkinsjenkins--- :hierarchy: - %{test_suite} - spec_hiera :backends: - yaml - puppet :yaml: :datadir: './spec/fixtures' puppet-5.5.10/spec/fixtures/integration/0000755005276200011600000000000013417162176020145 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/integration/node/0000755005276200011600000000000013417162176021072 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/integration/node/environment/0000755005276200011600000000000013417162176023436 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/integration/node/environment/sitedir/0000755005276200011600000000000013417162176025101 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/integration/node/environment/sitedir/00_a.pp0000644005276200011600000000002213417161721026146 0ustar jenkinsjenkinsclass a {} $a = 10puppet-5.5.10/spec/fixtures/integration/node/environment/sitedir/01_b.pp0000644005276200011600000000035513417161721026161 0ustar jenkinsjenkinsclass b {} # if the files are evaluated in the wrong order, the file 'b' has a reference # to $a (set in file 'a') and with strict variable lookup should raise an error # and fail this test. $b = $a # error if $a not set in strict mode puppet-5.5.10/spec/fixtures/integration/node/environment/sitedir/03_empty.pp0000644005276200011600000000000013417161721027063 0ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/integration/node/environment/sitedir/04_include.pp0000644005276200011600000000007713417161721027367 0ustar jenkinsjenkinsinclude a, b notify { "variables": message => "a: $a, b: $b" } puppet-5.5.10/spec/fixtures/integration/node/environment/sitedir2/0000755005276200011600000000000013417162176025163 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/integration/node/environment/sitedir2/00_a.pp0000644005276200011600000000002213417161721026230 0ustar jenkinsjenkinsclass a {} $a = 10puppet-5.5.10/spec/fixtures/integration/node/environment/sitedir2/02_folder/0000755005276200011600000000000013417162176026737 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/integration/node/environment/sitedir2/02_folder/01_b.pp0000644005276200011600000000035513417161721030017 0ustar jenkinsjenkinsclass b {} # if the files are evaluated in the wrong order, the file 'b' has a reference # to $a (set in file 'a') and with strict variable lookup should raise an error # and fail this test. $b = $a # error if $a not set in strict mode puppet-5.5.10/spec/fixtures/integration/node/environment/sitedir2/03_c.pp0000644005276200011600000000001413417161721026236 0ustar jenkinsjenkins$c = $a + $bpuppet-5.5.10/spec/fixtures/integration/node/environment/sitedir2/04_include.pp0000644005276200011600000000010513417161721027441 0ustar jenkinsjenkinsinclude a, b notify { "variables": message => "a: $a, b: $b c: $c" } puppet-5.5.10/spec/fixtures/integration/provider/0000755005276200011600000000000013417162176021777 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/integration/provider/cron/0000755005276200011600000000000013417162176022740 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/integration/provider/cron/crontab/0000755005276200011600000000000013417162176024370 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/integration/provider/cron/crontab/create_normal_entry0000644005276200011600000000063613417161722030350 0ustar jenkinsjenkins# HEADER: some simple # HEADER: header @daily /bin/unnamed_special_command >> /dev/null 2>&1 # commend with blankline above and below 17-19,22 0-23/2 * * 2 /bin/unnamed_regular_command # Puppet Name: My daily failure MAILTO="" @daily /bin/false # Puppet Name: Monthly job SHELL=/bin/sh MAILTO=mail@company.com 15 14 1 * * $HOME/bin/monthly # Puppet Name: new entry MAILTO="" SHELL=/bin/bash 12 * * * 2 /bin/new puppet-5.5.10/spec/fixtures/integration/provider/cron/crontab/create_special_entry0000644005276200011600000000065213417161722030476 0ustar jenkinsjenkins# HEADER: some simple # HEADER: header @daily /bin/unnamed_special_command >> /dev/null 2>&1 # commend with blankline above and below 17-19,22 0-23/2 * * 2 /bin/unnamed_regular_command # Puppet Name: My daily failure MAILTO="" @daily /bin/false # Puppet Name: Monthly job SHELL=/bin/sh MAILTO=mail@company.com 15 14 1 * * $HOME/bin/monthly # Puppet Name: new special entry MAILTO=bob@company.com @reboot echo "Booted" 1>&2 puppet-5.5.10/spec/fixtures/integration/provider/cron/crontab/crontab_user10000644005276200011600000000052713417161722027062 0ustar jenkinsjenkins# HEADER: some simple # HEADER: header @daily /bin/unnamed_special_command >> /dev/null 2>&1 # commend with blankline above and below 17-19,22 0-23/2 * * 2 /bin/unnamed_regular_command # Puppet Name: My daily failure MAILTO="" @daily /bin/false # Puppet Name: Monthly job SHELL=/bin/sh MAILTO=mail@company.com 15 14 1 * * $HOME/bin/monthly puppet-5.5.10/spec/fixtures/integration/provider/cron/crontab/crontab_user20000644005276200011600000000013613417161722027057 0ustar jenkinsjenkins# HEADER: some simple # HEADER: header # Puppet Name: some_unrelevant job * * * * * /bin/true puppet-5.5.10/spec/fixtures/integration/provider/cron/crontab/modify_entry0000644005276200011600000000045513417161722027023 0ustar jenkinsjenkins# HEADER: some simple # HEADER: header @daily /bin/unnamed_special_command >> /dev/null 2>&1 # commend with blankline above and below 17-19,22 0-23/2 * * 2 /bin/unnamed_regular_command # Puppet Name: My daily failure MAILTO="" @daily /bin/false # Puppet Name: Monthly job @monthly /usr/bin/monthly puppet-5.5.10/spec/fixtures/integration/provider/cron/crontab/moved_cronjob_input10000644005276200011600000000052713417161722030441 0ustar jenkinsjenkins# HEADER: some simple # HEADER: header @daily /bin/unnamed_special_command >> /dev/null 2>&1 # commend with blankline above and below 17-19,22 0-23/2 * * 2 /bin/unnamed_regular_command # Puppet Name: My daily failure MAILTO="" @daily /bin/false # Puppet Name: Monthly job SHELL=/bin/sh MAILTO=mail@company.com 15 14 1 * * $HOME/bin/monthly puppet-5.5.10/spec/fixtures/integration/provider/cron/crontab/moved_cronjob_input20000644005276200011600000000022013417161722030430 0ustar jenkinsjenkins# HEADER: some simple # HEADER: header # Puppet Name: some_unrelevant job * * * * * /bin/true # Puppet Name: My daily failure @daily /bin/false puppet-5.5.10/spec/fixtures/integration/provider/cron/crontab/purged0000644005276200011600000000021113417161722025567 0ustar jenkinsjenkins# HEADER: some simple # HEADER: header # commend with blankline above and below # Puppet Name: only managed entry * * * * * /bin/true puppet-5.5.10/spec/fixtures/integration/provider/cron/crontab/remove_named_resource0000644005276200011600000000043313417161722030657 0ustar jenkinsjenkins# HEADER: some simple # HEADER: header @daily /bin/unnamed_special_command >> /dev/null 2>&1 # commend with blankline above and below 17-19,22 0-23/2 * * 2 /bin/unnamed_regular_command # Puppet Name: Monthly job SHELL=/bin/sh MAILTO=mail@company.com 15 14 1 * * $HOME/bin/monthly puppet-5.5.10/spec/fixtures/integration/provider/cron/crontab/remove_unnamed_resource0000644005276200011600000000044413417161722031224 0ustar jenkinsjenkins# HEADER: some simple # HEADER: header @daily /bin/unnamed_special_command >> /dev/null 2>&1 # commend with blankline above and below # Puppet Name: My daily failure MAILTO="" @daily /bin/false # Puppet Name: Monthly job SHELL=/bin/sh MAILTO=mail@company.com 15 14 1 * * $HOME/bin/monthly puppet-5.5.10/spec/fixtures/integration/provider/cron/crontab/unspecialized0000644005276200011600000000053213417161722027146 0ustar jenkinsjenkins# HEADER: some simple # HEADER: header @daily /bin/unnamed_special_command >> /dev/null 2>&1 # commend with blankline above and below 17-19,22 0-23/2 * * 2 /bin/unnamed_regular_command # Puppet Name: My daily failure MAILTO="" * * * * * /bin/false # Puppet Name: Monthly job SHELL=/bin/sh MAILTO=mail@company.com 15 14 1 * * $HOME/bin/monthly puppet-5.5.10/spec/fixtures/integration/provider/mailalias/0000755005276200011600000000000013417162176023733 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/integration/provider/mailalias/aliases/0000755005276200011600000000000013417162176025354 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/integration/provider/mailalias/aliases/test10000644005276200011600000000130613417161722026333 0ustar jenkinsjenkins# Basic system aliases -- these MUST be present MAILER-DAEMON: postmaster postmaster: root # General redirections for pseudo accounts bin: root daemon: root named: root nobody: root uucp: root www: root ftp-bugs: root postfix: root # Put your local aliases here. # Well-known aliases manager: root dumper: root operator: root abuse: postmaster # trap decode to catch security attacks decode: root # Other tests anothertest: "|/path/to/rt-mailgate --queue 'another test' --action correspond --url http://my.com/" test: "|/path/to/rt-mailgate --queue 'test' --action correspond --url http://my.com/" # Included file incfile: :include: /tmp/somefile puppet-5.5.10/spec/fixtures/integration/provider/sshkey/0000755005276200011600000000000013417162176023305 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/integration/provider/sshkey/sample0000644005276200011600000001325213417161722024510 0ustar jenkinsjenkinshosting2.planetargon.com,64.34.164.77 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAy+f2t52cDMrYkgEKQ6juqfMf/a0nDFry3JAzl+SAWQ0gTklVxNcVbfHx2pkZk66EBGQfrK33Bx1BflZ/iEDyiCwmzVtNba0X9A6ELYjB9WSkWdIqZCfPlKZMu9N//aZ6+3SDVuz/BVFsAVmtqQ4Let2QjOFiSIKXrtPqWvVT/MM= kirby.madstop.com,192.168.0.5 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAw9iHuAa/wepHoUzWqsvhQvSkpE4K7agrdLOWHM9mvyRQ2X3HVq5GqzAvWu4J+f0FvcLPwA9tivpxt1oSt5MOtvDM6HoM+8m3P4daBp0nlNaYR8/vHCAmX6N3RyM8FWfp+VqWyux1SooQwxYxVFy86G78ApTqNsZ+p7cHmnBYqk0= fedora1,192.168.0.51 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAyz1rKcApU4//j8CHYKexq4qnq2WVqLPrZYGnlij1t7FscLiDVKvBuRHVkfyTNIjAM/t7tM1Dj+FuD4iWziCmf7RO9q4wI5y/1zgCiSUegnZVSmH2yxnWGMdHGpXOkN3NXcpy6jylxyBo0M7T22PSezCxyUVfMclesjOEO1jETd0= kirby ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAw9iHuAa/wepHoUzWqsvhQvSkpE4K7agrdLOWHM9mvyRQ2X3HVq5GqzAvWu4J+f0FvcLPwA9tivpxt1oSt5MOtvDM6HoM+8m3P4daBp0nlNaYR8/vHCAmX6N3RyM8FWfp+VqWyux1SooQwxYxVFy86G78ApTqNsZ+p7cHmnBYqk0= sol10b,192.168.0.50 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAs37kiDwKxWqi6EfSdKwRaZXBwh2doOARRqZzyitBaPwESy26DwTx+xdQ2rwB4V2k1WIec+1f3bgTS2ArH75dQSPyba2HKqxaSRBd3Zh4z23+uUxpupEyoRdW1HolMOvuoceheSMsruiuYcuiyct41d4c/Qmr51Dv04Doi00k6Ws= piratehaven.org,64.81.59.88 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA7TYRRkle0wDNohZ0TNZN6R1Zp0svxgX+GJ9umI5yNM1bMxUTgeNRh5nIvZg1HgD1WRXQ57dSxxLzbvRyAqc245g6S8eWWCtenvOFLl0rOF5D3VxbQuw79sOe8/Ac8TC+c8RuWB7aaxpwL5Rv9xfDeazOtoKXj7+uwQW1PUmTaEM= atalanta,192.168.0.10 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAysniuWiJH6OQLXl63XXcS1b/hP2lAgSz0IutjQ6ZUfBrt1BZ8udEgSh57w5QDLsZ1lNvND61u5cy6iDKXI5TIQY4DvUmsoFZhyr4iYJbtT/h6UJSyaZtEnA7ZMRjGhUIMOn+mjbj7Z3zmJMhxtImK3Xo3O2fJ1hnK4jsBwQPSLc= pixie,192.168.0.9 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAzDp588CvET6w11LB2s/vPjc4tX9+u46iYJgNFfhzxrXYMVv4GF7d30IXB5+Hwyi2FOQIG1+h0kUXVGWEv64rAFBT7pD2KMFX0lcDERV4avqT0TRDIIA5OqFOhq9Ff+kOmHS2cB7eFyR5vqbN4ujOnJGTmru9dcqyL+2AcFekvh0= culain,192.168.0.3 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAvJ/IORhQMdAsJ7LB1sI2LlWUHc7HPTCDiEgJ96ij3jFvqaIiYieRFaNkxbbk75mPkj3vIqWIgAsAtHmKX4wDikNG/gyjs4WM4cWFKtl2jiVhqpoxqqCaVxs6Ex+vpKuKhQR6SzFBFDlBZYP9an6DPu1msTLT8/hZH2WwswVmpyU= cs312-server.een.orst.edu,128.193.40.66 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA+t3hsOiyp1mztt013bLdJqIAFCo8lxlz86MYEPz/mADHzWLs3Xv7xpAUv/E8pRbhEOzXo84EddORRBXz6DgVyMah8+v/u3IOkpXuZI0Iu1n5hZyf2l5DGEyGecr3oqqjUdMuM9HeXFLnqXJI3hDE7civBtqf5AJSol+TCcipeE8= freebsd1,192.168.0.52 ssh-dss AAAAB3NzaC1kc3MAAACBAJSiOyQhYlKAi0FDLKy42VzLDq6yJWXGXVCLSfgWyVx7QCq/3+W3C1dtHuAvjbypcjqqvsuGGITgQ1Y6B/+76n5d7FyQnj4SFZ5drOBn/TvslXhrS/Ok5KCcndfNAa+EyMnSZJ21jhoRjZftY4lmb4hy6fEF3RvjuOdf1qBN5FWpAAAAFQDcsWF0zELAW6YUlSjAyO0T0lfPbwAAAIAlOLdOd/WszzVaplCOnH5vF6LWfr6BosZKDkFi0mv6Ec636YGaj4AMxK8sRPusHv6sVByN17ntIJnLo2XD1SuoH28bZ0ZnPIdVnd0l1KqsOCuuow9LZYJUihezoUuYuTvij1jZdrwltuPNJTVLYtsZDnKE5plw+Tjzeb7ImjbXGwAAAIBT40olg3fxhRDiZECle0WK7GitgXCB3njs+4dba8VwveEJb9UuulMc1eR+zQiJR96IUBagC9NiLvUGS1IfiIHpT4FA8O/MK1W9SgxXB9d39Nk/9l8dH3U/fLnbC/hYVo8bN0or/mKxcxQMkdBwpPlWAbELRftod2BkkkvgfQdc+g== 192.168.0.2 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgwCZ+qxpMMRJ3otGsjaYeKTKf6tuCZyK1cD+ns9Eu7V0ZJLJ/LLMxduu7n4H/ufGI5rGV5axzgx8yZhjDRzsrGjLAQYsqlomMkf901YQI6UuieSA4MZa5MDkq/Jt6Vx1kEGTpkgrfw9kRMX5BngECt1QKY4xTgC7Ex+WlFvZwk+tRUT3 openbsd1,192.168.0.54 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAvgRVrtJk0fBg9YsLf7rWR1X32ZjFcva5XBvenDlHruObaHzuGXyyr6iOCAEOc7eCZjlPBYrGZ2potqyk8HlBOHXr1cCBf49t4yAt8KgKswtzWlgdbU1UEwllSRVOpzqULAT0smv/jfaIZdvRoN1rLriqtpn4bQL//ZOHiyXCwdlJ+di8Mza2L8KZ5T75hwIFBhrgL12xfymRp3v+Iy21MjjzsF3pROIkZ7icitNqQF55U9vsHaA37vG8FepVkO10bYYP7IHPZaBPBMPx7qPyRgRDJahEUGBnkIkzwJHEmA+7YMiNTZu6MigezD+CIqY1xOi/eXZwObLwLKXo7eRmpw== 192.168.0.60 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAufcvE7s1eRwhUwMBfZ6uFNxkdSdzjSDEn3vjByOjG/eraNhnYAW3rxV7WIf2pEa6JSOMrE1mqsEL75xEtpXlzC949Ysz4+1OSHY52KonoFm/a+FbmbFp81TVuVPYaLoeWN27STiJh+puC5spkIZe0laqT1GU13M4gj6B+j3NLhU= rh3a,192.168.0.55 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAufcvE7s1eRwhUwMBfZ6uFNxkdSdzjSDEn3vjByOjG/eraNhnYAW3rxV7WIf2pEa6JSOMrE1mqsEL75xEtpXlzC949Ysz4+1OSHY52KonoFm/a+FbmbFp81TVuVPYaLoeWN27STiJh+puC5spkIZe0laqT1GU13M4gj6B+j3NLhU= tsetse,192.168.0.7 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAwCDGn82BEMSCfcE2LZKcwdAoyIBC+u2twVwWNRm3KzyrJMQ+RbTQo4AwGOdyr/QYh6KaxTKTSoDtiBLr132uenMQKwF47gCKMA1T47uiy+TBUehgOGwOxteSYP/pQRpKXFmhOppSPyDPQVq234XvANeJp0iT8ZKEhF2FsWTs6sM= config.sage.org,131.106.3.205 ssh-dss AAAAB3NzaC1kc3MAAACBAL2akEcIfQsfm3zCd2hD6PgH3kWA/tqX/qbrLhL/ipX7iqK/y282GMClZQSQjc1YNi9virvLzb6u/gdZxicZ8K5O3FaJrULQJOZaP62SOHk5CUSHVnvpMCaCnbwB6gEHa2LeMWStcEfWW+g1CC2hzPJw16/S5GISGXbyanO02MnXAAAAFQDomwx/OCjTmmQljMTU5rgNn2E4gwAAAIBmtMSfcs2Tq5iFFKK5cahluv047vVNfXqYIAkeJniceL0Et16MKfuzadpq0H9ocxQYW/5Ir9nUULrdxBUN9LxNKq15/uWkJC9QCSh8PysgvFnjVZeCODua/dn6eZTZnY9DZ3S6v1pT8CP6uWr5fmZJ8FKJGrC3gYX4y1V1ZTCVewAAAIB6e7RCST6vkTS5rgn5wGbrfLK5ad+PW+2i66k77Zv1pjtfRz+0oejBjwJDPNVSc2YNEl7X0nEEMNjo/a5x8Ls+nVqhzJA+NXIwS1e/moKbXFGewW5HAtxd79gtInC8dEIO7hmnWnqF1dBkRHXg1YffYkHrMVJBxpzagw7nYa0BBQ== rh3b,192.168.0.56 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAufcvE7s1eRwhUwMBfZ6uFNxkdSdzjSDEn3vjByOjG/eraNhnYAW3rxV7WIf2pEa6JSOMrE1mqsEL75xEtpXlzC949Ysz4+1OSHY52KonoFm/a+FbmbFp81TVuVPYaLoeWN27STiJh+puC5spkIZe0laqT1GU13M4gj6B+j3NLhU= centos1,192.168.0.57 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA0DXqYF+3Lf68GkWBAjjKBb6UITNnzm4wiDi/AGjv5+DoVXqDcqHvZ8rZFAMgUe1dVob4pWT2ZWLHW0gicoJCdr4UQbPXlWz1F62z8fo2PRRPlG6KN1wmF7pnyml8jr0wBX8lQZJsMqi4InGozf7wFHLH/7DNGRK3MD6tSp3Z4is= doorstop.cafes.net,205.241.238.186 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEApJKeB9/bN5t55zLETHs0MVo/vEkfQ3EzY7178GKLI/yiOFmcn+NvUvUtCQK/xKpod813LBHCODxZPG1Kb0SjlaC/EkFEenb74LNu0o1qXa1GWh3wfaIm0JRNjXqPqxAWTlMs43O2HXwOwmLVhl7SSP3xtTw6h9gREbVKmfBaRdsRfzD0etfz1NCnmGh/1Sh9+j4eeS+IBtwoR5JVhZVhuofHCqs5HZ8gLDgfn8HXP7pMbLkx54cf1R/tmFmn9JGLdTPtEGcSIiu7414XSbfChSC83rGZCDPKHq7ZodiE8GpbWLBnyPXi2AYxTPM7aZMqitIHv3MWf5suV0q0WLGdnQ== host.domain.com,host1.domain.com,192.168.0.1 dss thisismykey1 puppet-5.5.10/spec/fixtures/java.tgz0000644005276200011600000001276313417161721017275 0ustar jenkinsjenkins‹ŕ÷Qpuppetlabs-java-1.0.0.tarí=kw۶’ýĚ_UrŹě®Dńˇ÷&i\Űm“&NŽ•´çnNÖ$A‰1EŞ|ÄQRď§ýkűżv)RK˛]%Ý+$ÇĚ fĚ  4I'–řÔŠëčGZ×UMŐßÝkŇ4­ÓjţŮźšŃź2ÝĐ;­¶fvšŃôVSo}GZ÷ŰŚĺ)ŤASč„›®¬Ő\÷<’Źüóo’&Kű˙ř—ŁłźO_Ľúů^h€<ÚÍćĘţ7µNs®˙ŰmÓüŽh÷B}MúďŢߊňkôyNB— Ňřóô˙'ľôČŁË,WŤ17€Ě§vč©ĂđŁ:ńź(ß“#Çń‚!‰a…QBÜ0"Ż"jűŚś ‹&‘3ň ŇOŠňšA9‰č%#Ź&~>Ąľ?ńé”E±j‡c‰±„. I2U”óĐbó2´4HÉŁh,ľ>Ą­‚~˝:x;8U”—4JĽ€<§öeäŃ??M€‡AĚŕ=˝, b3TGcú€8#$Ť‘e—ÚČΓÇDW;*°÷3ŤX2"çi<FáGŕqČsžŽCü €˙# Ô€%O”!'!Ż5 xdáÓS:ˇö©a4Dú?yźXĚY}*}yĐL]tĚ‚äŐ*=OŇžc’„d:)ŢJ=ß!4pHÂâ$V”3ŠôÉ9ůŤúP×  qAô´§Ă1őüUâ{kSňGJ„úeD#`_Q#řâ’3ÇxůăÓ1s<š°ńÄg‚ÓEś'Ěň 1Ôޡs˝Ź,®qĚ('ÂBźźüÚx~~J&Ŕ< ‘@ÇŞ˘šnÖµ&ü'çĚs€ŁŔaW¶Eńt¦Đ8S¤N4Ő„!ţ=ÔÇ> Łń‚$QŮŚLhDÇ1|$؎ިëz]o‘B ěQh±›â8űľ”Á‰ ·ĐaÄ׫^×Úu˝Mž3×%/íăűŕ<,ĹĄ«-‰KJˢ1 GÇ‹ˇáäŕEj{Îá"~ÍŘSŚ1bűĐ &aě%0ÂH‡8ÂalY>ÇţVÝčnŚßäă<Í9dE,›KÁ,Ç0"žŔ`hŔ  1ôµÍ[*Úλ«ą<+Ĺ ’­ŤIęĽáuĆ–śs( @톾^ů1LĎ.”nM•®.;fˇçP!ywžĄ˘ÔšŁĆqx=ől8 M6–´Sٵ5NM6÷Y#Ô@=ăQůÚ¶kźîž–ű?ł±ëůěžh¬ń˙Ŕé3çýżŽľ÷˙v’â0ŤŔVFI2é7QjM‡ óŃ ©( řÓłßÔK6ýá úúíë×§o.~;=<{uV=TŁ'S.ŹIĺ1yđ ŢÍW~]Q:cć]őżźCíTß+,pČ“ :‡ńUÔŻÔʀŲş]ÖŽâ łë¦X ‡ř^ŚNEeŻľ0Ý8˙U?´/ďNcÍü×ͦ>7˙;mł˝ź˙»H?źľ„9§–°>Y˘PŚÓ&îĂ.¨ëÖ};&ŕť©Ć!Ď”k!Čj«zWä˙B…7sŔý™Ú#Š9ş©š"«TTAˇ¶śä´jčYőŚÔ?ąX“+Änf-[>÷±JSÍĘFÁR(kJ (ˇě+Bó*†ÚSµĂbv=k¦@áĺëÜŮiĘÄ@ćçPظö9ŕŠp±}‚ż ÷ă••€‰ËĹŇ"vžŻóËËXgÍ—¸ŕŃ×Č#Ŕ]B,‰.RĚD‚ňh—Ş(Ż_˝ůéŐůË<iŠrrúúôěäôěřŮé@™u»° 3;Ă»WYŐ©JŢaĹ&ěµüÍiąţńěřôlpzO4ÖéĂ\˙5 cŻ˙w‘^‹ąöúžĽáŁ:‘™2ś„AXóŇ!.„±˘˘‡“iä G0IŹ .'30’`ˇÓĽac; TޡIźxέ6ĺ…gc$Î!i€A—dÄČŚYR#żI×t9Ŕ YT9üe¦ĐŘ) „¤1Ć“Ľ /CŘ'›M ĘŁľGđ{ŻĽdĉHŞňO‰ ´ u)ÔžLqi^¨ÍW¸N“öňęęJť…ᄄgӍގ!ĺmŕ30qű#ő"`Đš:vŘÔ‚ÖůôŠ€śé0bP«v }Áz;ÖHşÉŤ’G7JĘZś+€@앣y6¨ŹĎ5ĺ÷go~yőö ůýčüüčě (\ňęśż:;yöuxú‰ťý“üúěě¤F€ť°íĐ@EÇU0V"ŽŁźQ÷z®gGÁ0ĄCF†!řě<ŠZzěĹ"ŽIÁ×÷˝±—™źvÔ˝ęŢAZ®˙aş{.ĆŽďe'pűý?řŰŢď˙í"­čđČšPőCw¦±Ćţ¦a”ű߀ÎŢţď"};V čUúĹH  •Ę€ –óÁ!rEÜ3‡^&ţŽR mxc†Ą±!M“Q•é‰i8±H}I+Źi4Ĺě—č‡Ăş`e0Í}—( o,Ű‘7Idł7šDáX ]L .BˇmŹ·ăĚa0Ř‹Ĺ;î#|‘«ź%Rnĉă{ćU¤¬/¤“€{!–í Żw ßszÉt" ĺY 4X’ĄcĚ”+ůf>˘˛¬nWwMŰnÓ®fQÖ˙›őłÝj9=GëʶTdAşÍŽÖeT·{VWÓMŤ65×aÝŽŮiw»†Ő›áA#„k›N×ŇšĚpZ-˝Ĺ,Ýjő´&í˛n›šLogpŇKBžŃćuşmÇŇÚŽŃ2-—9ł]Ýj–ˇg ÂSÍČzíŽŢkZzŻMră:=ŰtzĐonu~ztňňT…±té„W|h.¨h¨ÓmY-Ç´uBǢ6ëőZŽŰŃrPXYfä@t=ťąMł­uí–Ó´ ò;¬ĄŃ^ÓiÓV33˘ŕ÷şŢPťL8xǵ̶Óds Íl®eµ{f»g5 ]÷/‘Ŕ-Ms KëZM»Ý1¬vKsMÍ…Îč9mř´Ĺ§oöL­c˛&t‡ŃvAÍv,Ťé–Űíi¶nĐĽKЉkđ x±8ľůB[Ť,DŇi6 łCÝ®ŁA˙8Ý®©őz¶Ö´®M™UBRX Kpͱş=»G›ĐË®Ţ3mŤ55­×‚%Ó{NÎB2Ď{O§fÓt˛ítuðMÍqŰči˝i¶`č*87®ż¶Fý{Ąĺö6żîĆşýźVËXđ˙Ś˝ýßIBŁD˛Tť U%ۦĹ|pTąg$3·°˙UEŘţErUEÚ~Y"Ě?¦_ć®3äUĄ`ů7Ş_4ú¤ş•ÉŻ*Jnď§en¤]ŻÖH53ÝŐot5»|ţgćî~h¬Ý˙m.¬˙´Ö~ţď$IOł8z‹Aő†Ô/_Âť«ËĂń ^Ö(äTłĐß xP…ë“F<ĘŁĆ0]*ŽcĚ뢫Ů#Ĺ•ĂŐ~8ĽŔ“_4ÁÝĺ|™ĐdtÝ˙Ç Ä‚tl±ź¸Ó‹_0j…źcÇ0ŁŻ÷›ĽëŇŠů_ö”ďHcÝü7ŚÎüú_×÷ó'é0đ€ln,‹6’v1’ÎCĺ!Ź˸5·˛YlZU”ă4Š`ĹęOEÔ¬ŁNcŽŃˇČ–ű `Ű/‘¨ÉGy,áöěgĽýî,-ź˙|évo4¶Ź˙¶Ífg˙ÝEZŢ˙É˝Ĺţ1ݢ˙[­}˙ď$ÝÔ˙2örgëěż®-Ř­­ďí˙.’8wő…Tů’ĎxOĐ“ÇO Äą¬bL® Ŕ0×0N ězoŻ˙®éűźĹďLckýó_kîő˙.Ň ý_ÝßĆ:ýĄsýݰ×˙»Hëâ?â X1ë[ŤdîÓmŇúż¸˙w+7ĎůćĽ˙gm?˙w‘ňů_šárĹbŇ/¬‘>ž5@ŻŻ/( ĐđŢ0ŔŁťcU;8|ú}1^Č9äŻÚ¨jögšKĽ“(öów#ŕCJíbŢĺ&˙ö_ŕňkBo¨xÇĂgµ3ě ŹŔF…żŇ‡îçU áHä—FG Ë úüKĚżń?řµZ+@I÷ˇ*k<đ‡_Vłp-śňJu*nŽŕ¨ńÄ i°Änó=:Rž«7!çňą®_+ĹOńWÚĄYď< '!^¨1#˘ćŻ÷Ç~ţ¤uú˙>ήŃ˙­¦ąđţGßß˙¶“ô€Łöîső­÷Ăů}ąq<ůőPEľgňd‘©ě\$´™óWdéÇi¦NkâĄ)ŘŰ1?9N# \ÉŠ(^äî@+ĹÍŘN}€;KŮn=ť]ł#©«YeĎČĂaňŁ…0TŃ×Ţ˙,_şBAŕ Ih¶) ęŔHŕĐČčŠŕüľ!ăeF9őy‹sc3Š&„†/™„óÎ*ŕo0©~…\yľŹk—+č|Ď{z¶ô[ň–žąďňG/[N<;ői”1Vă58RęŹC0¸Ôż˘SŔçǡŔrE~·r†FĐ]p\ ´%ŚAÔ]–d˝íĹ‚b%Ľö ýwB,ć‡W+eË­yQŔpđ;±ř­ąW˛(W2đđ,¨+”„*—’cz °2ÄËQFśgq9Ë °8(`Á*ĽD ŕ9ô˝ÂĺW€FŚýYňW„~{‰U%¬ŕţ_ř-őËą@"źg4y‹ďűaÎĚ='ĄtřĚŁz<;Őü°t¬ąXCNAQ+ó󵸧Ék,tţÚÂ]•5á0C§ř©Ă¤7-˘<ׄł‹dM®ĺmüS*ćü)e˙ů_ďTXš÷hýóQý?űő÷˙ţ°zxŔÓŃřâ’MiôűřT+ ěPş€ĄËů[ăŹÉ,ďJHŢżË)Ľ/c["¬M°ń«1fÂÝ#ăhŻ ^X,%ŕ‚9¨<_0_żŻq࢞–7óĂđ­rW»?÷ČKR|<[?HbY`üý¬LxěbYĂK»¤–ŃzđőÍâÄ%Ŕuś¸Ź&ި{ęÇžĐË'§?˝}ń¦¨â-†W·źÇ,ŕŔË$>CĆä.2]lĆă%M[Ćü|ĄE),—éůŇeůČ-Ęoý¬ ssýÉl*g+ Áńqúá¬1Ť—°F­PHg©4Ju±`ąđš±p 4¶” lĽóAîĄőű şÚç5ęO’ʶâk($3ŘžLáń|©|Vr(fD]zż% Oä´ÚƇţˬm×­˙ňëî@cÍů_X.ś˙7ö÷ď&•ֹÑŻĹôá?(~ş#táóçÓŽßćIfü‡_t(UB~‚B±ß§Ĺöđ&×+†?2óŃg¶ÚBÄ.—s5‚żš†'L‡xSk‹UZY/®˛ĐăżaF‹…Ű4ůŻ áUłčă\Ď ˘L´¶QĽ˙.]ĺ+ďŇ—2żĺU‡ńk^Ńx]X˙Čl* +»Ů›m6-»Ô ó}Ŕ VĎç×F3o‹l«1‚Çf @ayAC¬áó«Xe,Vô!§K0ôĐ„˘] ßĘßCú˘¬ŽÝÎÂŇ+JÜÝÉ—ĺŕÍŻ+Ůé†l_o[̉ĚíbÍb·7Ň cA© =#X$ZGE_ đ[q ±_ Ć=äioěńä`±mr­†ŰńZő<"Úa şúÎef“řbc¶ŹŠÇwÄNŮDÄVCë^ľ3î¸mÚ\Ü Ý¶ąĺÍ˝M›śí®n6۬ ‹›î›4ˇł® …ÁńsÂÎŤe­©–ĘéV—a—§:Öc_Çë6ś–úrá @ł6“âŕAÍť /¶¬F®k€-5«PÜNČŽh-Îůů[Îůl'ţŠą‰ďIÇÓ_…Ă|U; żđ\č”TITeq•˛IAyâ§fK-kó@ĄÓd9Pů”äŤPb‘:Ű ň=«ńáă¸QFŃ€ľidfaßumŽÁ¬{·f0bőŁîPý-8ÍÎ_nÁmůÄĺvL˘ňľľ5sŰŤŐs[ŹŐ2·TwçÇ;«ŇĄ¬ÖŞň·*á›řĄĘĄ3ü1ą§ŢŮBj•%zuXńý‰ëĘfň¬,Ęs â\Ę•żDtnŁ ţ)ľ±…`ĘŻ=l:±Jď,Ünr•PlŞ9˛ćn9 2&cçr+.Eý»±YÂqERrVäo·´ĆćŢS•{mč~óV4řĽ¨näGUĺĆŞ„©T,7ţÝnµÖ ˝h_huĎoę€fő‹ÎçŚY~ 1śnä˙.9K¸ž|ćŰô÷§döiźöé[M˙%'Îx„puppet-5.5.10/spec/fixtures/manifests/0000755005276200011600000000000013417162176017613 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/manifests/site.pp0000644005276200011600000000000013417161721021101 0ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/module.tar.gz0000644005276200011600000001760013417161721020235 0ustar jenkinsjenkins‹„ ÖVí=ŰrŰF–óĚŞý‡.Ĺ5µxu±Ş’Y—XËVL;ŢYÉ%‚@“ÄŸ2µż±oů–|Ę~ÉžKwŁAQ˛•8™ťáÁ"€îÓ§Ď­Ď9}Ngi*‹ČćNűNÇm»íÖ>ďŐn·w¶¶ýÝޡżínŹ˙ŞKtşŰŰ[˝öV{«#ÚťíöććÄÖgĆcĺ5Ë /TĆ‘Śc7÷˛"\ŮšŤF÷ŔQó0˙I®t˙Łpř9eŕAüßé"˙·Ű˝Gţ˙×Jţż>Ú?<=r§Áçč±ÝëÝĹ˙­nŻ˝üďíô¶áę˙Îf·ýŃţ<Ăßý‹ó˙ lŻŐÎçÓ‘łĺ¶ßď‰IQ¤ů^«5źĎÝičgIžŚ ×O¦-;łĽ$ó8JĽ Č ŁÜőňôćOađeow§ű´v~Ř?x-ód–ů2·ŔŤĂb2ś4™Ë,źČ(jŮŤ ‰-g%c'MňBu†ľř(w§yS˙ˇ ÁËüIx-[Ývg«ŐŢmmvZó0$sgęĹŢXNe\8ŁĚ›Ęy’]9[NŰIł$ůEÄđS^‡r'sÇ»†)yĂHҬjµ/ŕođHFâ ‰ €–×:®8?”ąź…)BŽx7ń ć˘H¤¨ÂĐÉ‹1ÇWA"sâß÷ë_đK'(!4j]y–ÉL~? ó°Ú­ˇ'’/ňBN×ňuٶ =ú˛ĄĐ2ÇżŤZ˝ÍaŇđh†5!6đYŹ[j‹90Dś‘úSkhA& Ó-lá°PpžĂ|"őZ“¤P@.@¨w€Ţqw€¨úžÄ Bő>óü+@®Ň=4ŻťT˝^†Ŕł8ČdL˝(7xűĺ3ŐS0˘ă0/˛…ř΋fDÔśßšüĆą¦7Şß~`·$žÓäżc.c鳌`xÔĘI2€Â­Í«‘jĄŕ˝“Cd—Pł÷H\ŽnĽi!“ćüÚ ­×Žä× Â+xą@ů…c€Nb†ě|qp Ž`ĽÉi ÄÄ4w|ÝÜćÄŰČźĆÔdFŘţ W±Ź˝3ý»QۆW/ÂiXR8먼S¸}:‹“Ř‚ĺFĎĐîőŃî×D˘/úŔiÄXÔF‘]¶IËXB«,)Ü ˝ t"dÓ·.N_‹őSB$݆“~!Ž“lę ‘ű9őÄŹŁśÄ@NčŚ3 Đ$⎒(Jć·íŹ”ô˘LzÁB gaÄÂÁ÷ȨY.÷j5GěĂd·*^ĐdÍĂئɖX?wz,p!3+ZĂU@îčŹ8žźę…n™Ä_ńMXĽŻ®mÄDÍ™>™hQ±ŕµHžŤ^Ö‚Đźŕ:Ć~4 $ap®śšKë«:׆w˝…üY[€y@PĺŇ&ÔŇö~yYmŠśĹÄtLîÚBř`ÁÇđtTČ V»Qč=Š`çŇ­ śŤAµŰnoâčqRÄ(É ,›—HDkT­ö÷ż˙˝ĆެQ=QőA©!öŁ…Ś–`ń‘ő«Vűk2ľŁĐŻĘ¸á)1 o„ôüIőu t&ÉK=$9Îť וË:˝‰äľG8 ÝxÁ€Éő5+‘§ŇGˇ\’L×u‰ĄßRS„T‹Š89é‹`2Źâożu@:ľÖááKÄQŕCXNśľĚ@Áęµ™R{žĚ"D3ąn^IVBD€¨łÇC3ĄpVj©R+•řP‡Áë{0ľ” ŻJ šęU¬ĐřŞ‚GS#RŰ_ÖěW‰›‹‰Bu`h+›¤ôjéŰŰpUhD@ŃŻÄˤ€Ć/ŃÁŠ× K$°¸903u¦H=×O ÔnłŔ6Gş 3´ÇáHćEîŠç€8Lźlt«(˘'H ©č<6(Ń˝C˛@jPmrĐ,đLů‰ T6TmÓ«Ă·/ŽúN÷éööűuíOˇ%‘RyŘ7΀`˛e÷ieµ·.66Ţ‚§)!! áJpt-0Qnˇ@{eÓg,˕Ò-ÜŤŤŞę}"Éq}忀ě4µjUôŽ–0WśŚH•!Đx«Ú7«S3eŕ äĄzcWŤ°1üqĐŻtŔ:ö=ŃŹcpŔ¤`Ş*B‚A(0 ¨ f¸ćzŔť'ü(É%N¸;ĄÜ@Žś•Q–LŤăv>Ĺç윬"ëŔ-'gćé°ÔF(yč)ÎpÉá)G«łď‡čVóĚÔ‚ĺ˛Eľ33K ˙€C‚ĹŻ¤ĘY"Řbqe&Ę0˝to¤ľÁ‡˝¤ š-ň=,\ŰŢq_ńí9ßŐ;!Ŕ8áC I7ńcßm/+BµnRcĐâ­"°&eě/RăľŘÝ"^çNű'9š›Ł˙8*_++J˙e™'®ˇö¦^škźu TL®»lëuµŘ¦ đžăzŐŘ+Ł~¶ÚŢë·ÔStÝ·Ó6m´żP›Łîł­ÎÓÎS§ŰŢŮwz›GĎśýŁť]ggsóřéÁöÎÓť®;ő€úÁŢĹăq~X.*oîś*± Ľß|IŘÂxDŢ)J3ĐJ;`ˇ€ţäQž1θŞî 0ÂWÓʼnAQ_zÓë$P}ÔýÄQ­Ž „čŔP–łç‰Y~ŹF…˘Tđ2#Ě)fśüYäefpĺr5q`–°‘đÄ×oO„<´8€.0ܨĚ8—Ry´ýĂ—ÂÂŃ X.!”PY•â‰Âaće‹–çmî´w·z” iŔÚäĎĐ9ĺ‘Rŕ[š˙dëK¬]Ź!2Á×^J¦ÎÜ[ ëä€O‡®Z‘$.€śŕ+đg€h¬ÉHÁ۲˝«‚Ąź9†Ź+Ś@Fiu#ę*’&¸ä଒ŁŢüd:DĐŽ/X·Ą0ZMĆŕÖä”Ę`g^,#×ĚŤĂ;śđ>[TgdŚŮă6iYౕWdűţ öÂŐą–´9ĘHŕĄ(.´XśC\č_‰W°Ś sźăăD=%ľ{­ÝťŢćîÎn«ÓŢÝlÜňVGłŠŻ!?ÍĂCŻđ0(;NŁŰJţD_'ŻÜc>~˙ť–C¸+Ä“´Ô:đdOâëäJ:@Žo‰6Žn,žŚÔ/÷xEÔÜá6kýŁGo8•#Ž_ż:E°Ţ˝{~ôú¨Ľý­™Żľf!@Ęđ;"€ă€AP1ÚY˙VşdĘÁôű? Ć Fžľ!žA‚ťU¸~ζŕ˝M܆ű& §Ŕů¬XoŘťÔÄouÂç ÷µ¤TĆúÚ‡µćÚšu˙#ßWţöąŠ%ZX7łhŠ'ô űÜ8˘ü{Ą°`ż/ĺÜQIS«ťiâš_ "P‚$[ç‚°ľĹt˝á2†§r:”ŮúÚ+°/Z‚ךbMż…ĺ'€ű'/äÍŰđšâĎöŚÚ ˘„ŹľO¨€÷-ÖŽÁŃF‡O2­ň.­ŕŚćŞî‰íÝ5áŚÄ“K"i…6ßA莓ŇĽw2Řřž‰hM±ţ„LQŁQr'ŕŁéĎŠű„ÝŐé8@DŹ7 ń{aK Ú§B&Ŕi©Yj;÷Iľ‘Á8´ŇZi Ŕ š˘ŁżÝtËDĄëíŹI ć˝>]R,×óVRĽVcŻţÓţń›Ëň¸8¤ZäD¦:ţe?|Vɰ@ř ~$…łŕOđ˝/ÄŔ·ŕ™¸žTĺÁŇV€$č^ˇ˝ŰJ?ĆAny­3 ‚Ŕg˙,¨„Ä&;/J·T”ţŁi©SksĄÚň/ĐR&I”™ćĄ'\¬0ăîŃć(ŕKÍ«uyxŁ;ZoÍ#déň1&äMJ.t‡¤™[ă–Ô-,Šl&›¶pܱ R«hÎ × ’ G‹J»+ąŕ’s]čÄ ĐŤ˘]ę-”A%tY«Ľ6 —‚Ú_3ŁéěO?Äżµŕ/rú­.TŽŤđ4yű1Ěň[r¤ęúç%zuďĚkÁ’ťçßýőňĹ«ý—§űĎO^]ô_ży·˙účB!˙Ť\”˝ 7’WěýF&,5@ól Ů˛cËUäyĆŕžspT盺 (ÄRň řŤĐjęE솣©RŞâAÄZJ .‡ŠH¦Źg‡ÔŃ}n‡§Â$Ş´2zvtt\}E3¦WL‡ćGhwHꋢý-ţRDĎtĘ'Zć( şĐş¦őZăCM5še!ľY{ňˇśŕŹ­'ˇ~\Sí(íCíä/.ěVĐě‹Jň ÷ł$’Jîܶ»wăîţ­;đ<…óŐíˇ÷űgÂ}yôFôÜ­{qŘĎÓ—˛čmýBD »Ăý«¸ô‹$Ĺ”­Ľ sŇ<Ĺ3±žšÍRôgú$ů—7J].™» …ęŤîňˇNĐÔk”Ě•îźÂęN”Ë& ę© peŰś 1¬¶8t*«I:YäˇďE,~_­’Ę*-ČWĂ5M1ËPć‘<}¨—ĎďšÖť3Łő©BÔ!âđ‹$[Xí–U ±V2\EőPŐ’ ń}6š'hg ©07­ĘX˙c±÷±±‰é_uJ-+ČýüÓŃM‘y~ńóO€ÝĎ?)z˙ü“Bńçź’Ş˘|2ęIĘůŰŐ;¨˝šŇ¶!Y9 k¦J(*3_)žĺ\kĆm©ćůW2[(küëdŢ^î•vë@î—öŰÓ«ú~n6đs)ËÝüixĂ*m‹Vw9)"Ĺ]ÖD×;Ř…OX’p®Oeá•uĺ6Hř·¶ŃM\ŢŠ°TŞhUz»“b5T Sž”0ĘŁ|HÍ^­v–…¸Ŕ~ŕ»Óă-ť±Ea8ă*žfŮ=rVk˝TáP&ÖvĂm} &^ Ę-ĐtÂŻč8çęĺžją›ŠEG€ň QüŃŢčP‚ĎÄŽoY9EőV+^Qv„ő ÖX ¸‘Á¤ă=hś{Oa.Tˇ&nIŁ ĚCÎA©2ĄĄ}^µ˝˝Čź^2•$ą„‡$űŞîńrJip˛tŠ VČ(L1$3łŕJ–€Ó•@đ­ĐËÂĺ’BD»žk©7uY±!÷Ň;jĄ0ÚeŇF$[Ő )¬‡*_¨Ĺ•Ó <Ăý|DŤ ŞuZR ±nU٨*pYŠL~?hhô“ :¤ »×¶ľą˘V{&}]hžę—ď™ú5”Ź„"ÝŞąĘ©¦Đ®l–ţąÚ@­Tw(˘UiF…k>y""ťđŃHSĎš1ˇ±®·&ąÔhpss~ŤsVĹĐĂ2=b’Ě%f`šF!řT·Ŕ» 﨎Âđ·‰Č`Ať˘$D¶vFâë°x>RÉŹˇI9żśÍ©± üďR5Y0 Ü1^Xoy,ŕĂý`ř%y íšNF&ĂŚ†3žd!¦w÷ŤTŢ.± ¶pä|Ć [Ł\ÚgŐă–.‚9EIhŞf*…‚®nmöđ­­Ío<-I˘Đ_X•düNxŕ±@„ ˛Ş7Ź Zd‘…>k;tŠŃ6ň L˝Ţ4猻ÚSÍ+µW[íökŻ ×^-ďŇ«Hđ‡Ű-X5Çň|ę…ńűÖ)üŰ:D­gÉ,=ǵ‡~˝ßŇb™RbĆĂŇ·“)R§§¬ ĄüoľĆ.¦RbÄŰ ¸TÖÔ$Y˘µ‹]ĂűüBŤ%;.N n~Á–ä;:±ÂI?˝9ë«"dŞA®,K0#6b5őôb¶nšO;¨JŽŃCä‡ĘꨪpµΙiťO”’+í45‹öĚXŞĐľ4‚vI‚†ĹT Jj“|ś,ŐFŚć¨Ů­?˝€OżělnuvÚ.ŻŽPŮDŃ„ńć^‚í‰ĚŤăĎ÷­š#Ý«KzČPŢ,˘¤7ĺĂË: ]1Hy±´¸52fps ¬Îŕ/˝č„ĺM],FűRXi2 ózQÍ, Yhá!ä˛+]đTn ĽÔµ¦bFĺáа•ŐŰ}x5#ôi+BĄ–1UéfŐś9`«…wÔűQOŞŮ»ŁöŻR§üä;Ë UÁćŻ?mŁkHh1++÷ ‰7Ý]÷FÜěnëR{l©‚ć“p<×ô¸•aá>.°Él<±L°°ĘfíA$Đ4ÁÝQHÂuK`X°@]ź‹§ČQ Ěę1×Yg’“_žmΡ%ĺk†Ę+šˇcë\€ĺ¨,© xHśéëŔí¸OÝMÔü"śJ®™üR¨y•Ó C\•ÁŔ «Bo®őBÝÇÂ+Đ$Ń˝IćxÖMąôöQÔ÷7z \«°q ¤/X/ł=jŤµÉxÍ´¶äq”Ĺ˝ŐX‡qw·Űéě¨j¬y†g `)Ŕiš\usAgť{δöÁ5YSë líŘ*[;7†µLÎ? ĺ§÷´÷´Ë(»ÚN2‘¨Ň“O hß+ŔnsÉŃj°©›&ŢÓ»\fס/Ő#u§<"ít¨ĂĹĐůf}µ?`yP‰‚Í!§ć!k‡Ţo$ŁÓľĹ˝äŞĘµ=^M~+dĘ8d”$+övÔ¸•Ś7Ä&آаW©ĆÜă”DźGŞGÂ$±Ş8×|˛g­Ó‡7ĘŞuE¨¦u6¤ś(.±Lf96TuëDć%úDĎ ¤žĹ¸AçmP!›†˘\Í‚i'˸§6Găá™rđ’Xő+SYň©“ŕ8ŇoĂ ý07¸ć W›K–»Řé>Ü]ět­ĹM×ëß­3JYÔ9¶OŇ—›Ź)ŚfJ†ZíVň­:ë(#¬č´X*ÉĄ( 4ůdh7GK mÁX›ů…Żű›Đń~‘â–ĐÜ|>©ŃÉŻşżwˇýmÁţöĹq’\\ Ăřâz¸@.K˘Ř (]‘Ň“«¸3ÎŞĂ%sL®°¦š&`Ů# ‘ęŚ tÎC&ű”ű\ÎPÍaË]ž¶óH°äë]»Ňz1ĂŤz,YR‡\29×űF ž¦@RđĄőÇ/¶eĄäët„>3TŹ<ęR™ó™–r<»śŞa'»Ŕ‰ ˛¦Ńl NGľ}•ßx|+bőă TźŃ1C‘–V4?´ömě±5Q¦.ëł<źroŔ”ô:Zž5˱4'Ś<“ş4Úµd‚Uśű2áĺ’(ŹÂ˘wÄźE6Ľ„ ŤĘ%J[JI°¦Ĺ‚ň˘Ěµ|‘T7OD=+¦©3‹SĘ|ăÚ;ťMÇuÝi2ŇhĽëŠŕ ¦K”¨«Ă§xöP©áqyč¨çvÝîż‹uä$zž`ić˛~Ťf‹Ý¨-`’çá0ŚÂňD aâăz‹|0Çm‰rá GcçgoĎśŢîVďSuP·o€ÎQ’2’Ácyâ$YÍ?‚đÎh÷îvěŔA^ě'Ř :"¬útŁ ŰÚÚ;IĚą 'Y u™áżµľ Ç©¶‡Ěvş[x™;ţaPf3ciŽţ¬íµ#O˘›P ŚË2WÉ&羣®xŽî¤ťŃŞČ˘b«Ö{›‡ Wy&¸îîƆ0ľ©9qÂ*¬w.ŽV3ĺŃÁtMŇ} d|‚‡6%'ÖĆŠ˛&ą˛6dlĚƦ„``ËvZ(™d ¦¸Í1F4Úç`*Ĺú’m<\6VúJ&&I^°%­ '°YôŽŽ5éű·s”ÝP[9fs§eČŐžNˇ>ä‘KL˘.ˇdThÍf\ÔccHi®áZWAжŃÇ€•Úqâ$Iéř&˘Ój~ž›ĚÚë€Â„Ţ”h%}*˝v”J“ąY^Ď»;O\BźęzÎ!đ/ý–fÉQXřĚ!™6g*+V©"!ó`RńŇ÷0»’ÂŤCŇ>Ç4,T•}ŚEĘü^9KťËŹCÜʦśś.gÔLý4˘W‰ŞÂř:a6”™ëĄˇ–*Şő ˛(}“‰–Ë»€T}­ĂíO€Mľˇ†ĄÎ_XéV]ô)ěoOţWÝÔ;˛Ńč h~Ź0>6ÖH‰:ů)Ĺ2@<™dľFčp«éLY•ň&s@ÂwÖąĐ;ŮŞ-.g~q•K˛€M¦BŰUßÜ0ř¨ŐŠčY(…5pÔ+µľ€Ä’kzIĺóěÉaćjă_~SDë§9L2t%eŞx5cÜĽwĹ;"ę˙ü׺ ÇťĚPÝgxꀎ#‚ĹÂ'*§‹,d÷žěĆÁÍ«QÁż°E 1wG»o'zT„Wf±†•O%'ŹáŠ÷S 1%_a\µ<ÜEĚř—]Ţ"6ó•ĘBĺf­t€s6đú ·ejIµ%#9cü< řő:ť$%Ő@IĆÚD¦l΂ɢA¨ř´fizÓ ĐTeQă1Ú±U‡&yK‘<׋¦ĹPFνˇ ´ěoËđrR«mK˝>ă–© 4ĎbPî“6ůH m6;»ďŕ‡ÎŘj?æxlťżôvůěíÉ‹Cw|ń}ٸŤę©©ĚFťáyťÇ°.áÁ¶*ę “sUÜnÁ»ě·‘ˇÁY‰ąˇ–ĄKHŔqÜ9ź$ő2…v›żn`ÎÇę-|[ÄÜîĂC’Nň– ‚?íłęcAeA*})Ąöˇ–ż9c¤˘LWŔ‡!ś éó_­ą;ßK'A©8ăăg€K-śj˘z6ŔÁ *Ä*MŔŽUT-ôó»ńŞsJ)ű(Ö‡Ţ 3Dcĺůg&(i—ĆŐAďDíĆţ®nŐëˇ[𬔋2ö'˘tN7văšő) ‘gQ26§t?ĺ«w…7Ή28:1Áăwš°â&/iĺ¬ßHoŠxŘË Ć AýQĽëżícĹž¬OťÎŽ15Vśá“’8KĆP˝Ď͇řčł]ů,wÔ§m€!ŢĎÁ¤“cą¸Ęw}»gK!^Kô÷¸ÖÉ.Ő¶:4íůi˙ÔQ/QD§Ís_媜~ëlX1JžšýéúË7gíľ—ÎâŰ"iĽó<Â}ògžB\'Á×đݍg]YD˛5$ĽťBáÍ#´?gĄ¸ś’łú3g âLů!X_Ś`”­ńqkž˘)80n §–Ą˘ Č9ö~Űb #-ʦę5MîAĂÔĘE>Ç˝ đ…†”5*“*ËďŠç¦%f+r©dµQ}ľ ŮŘđ‘}Ą°ˇb_JŐ 7Z´ČĐ"1ßżPŞď ÓJ1“a*µ›E‹&h`ŚŤŁD*R©ť ŽîZÚŹÂ@lY)]łó °®ŠřŽŁ(L—>~‰Ľ+Ô‡ůp¦ ýµ0ĂÚU.¦ňŠĐ]mCîł˙Ż^„ę˛íaÖ‚5÷éÎn§·Űx»łóńyí]‡W°řOÄÁ$\ZU÷Cß±Cď+c3Ją®ťA·z R™/úTÂß?<±ć{3łU=¶pâ*sbQšŞqöÄů~Šů=ý )ľSą«®ŰţĹs/NŽ^öʵôgX˙aםßć—7źáCĐűţóŽhwŰ˝­­Çď?˙×Çůχ•ßň‹¤á!üďt˙››ťÇďż˙.×CůʧměD_\$Ä˙Í.đ§Ó}ä˙ďr}ţŰÁÓJax˙{Ŕ˙ÍNołýČ˙ßăú¬ćÚ]l”¦gI©cq·eŕüßŢ‚íÍíN{űźś˙«ë·™ÁŻş~1“ďzár’wÜő÷˙˙puşUţ÷z[ŰÝň˙˙á†ő·™ÜÇ/ ¸ńű ˙öŻ=^Ź×ăőx=^Ź×ăőx=^Ź×ăőx=^Ź×˙űëÍH;-xpuppet-5.5.10/spec/fixtures/releases/0000755005276200011600000000000013417162176017425 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/releases/jamtur01-apache/0000755005276200011600000000000013417162176022307 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/releases/jamtur01-apache/Modulefile0000644005276200011600000000004713417161721024313 0ustar jenkinsjenkinsname 'jamtur01-apache' version '0.0.1' puppet-5.5.10/spec/fixtures/releases/jamtur01-apache/files/0000755005276200011600000000000013417162176023411 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/releases/jamtur01-apache/files/httpd0000644005276200011600000000122613417161721024453 0ustar jenkinsjenkins# Configuration file for the httpd service. # # The default processing model (MPM) is the process-based # 'prefork' model. A thread-based model, 'worker', is also # available, but does not work with some modules (such as PHP). # The service must be stopped before changing this variable. # #HTTPD=/usr/sbin/httpd.worker # # To pass additional options (for instance, -D definitions) to the # httpd binary at startup, set OPTIONS here. # #OPTIONS= #OPTIONS=-DDOWN # # By default, the httpd process is started in the C locale; to # change the locale in which the server runs, the HTTPD_LANG # variable can be set. # #HTTPD_LANG=C export SHORTHOST=`hostname -s` puppet-5.5.10/spec/fixtures/releases/jamtur01-apache/files/test.vhost0000644005276200011600000000062213417161721025450 0ustar jenkinsjenkins# # Test vhost # NameVirtualHost *:80 ServerName testvhost DocumentRoot /tmp/testvhost Options Indexes FollowSymLinks MultiViews AllowOverride None Order allow,deny allow from all ErrorLog /var/log/apache2/error.log LogLevel warn CustomLog /var/log/apache2/access.log combined ServerSignature On puppet-5.5.10/spec/fixtures/releases/jamtur01-apache/lib/0000755005276200011600000000000013417162176023055 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/releases/jamtur01-apache/lib/puppet/0000755005276200011600000000000013417162176024372 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/releases/jamtur01-apache/lib/puppet/provider/0000755005276200011600000000000013417162176026224 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/releases/jamtur01-apache/lib/puppet/provider/a2mod/0000755005276200011600000000000013417162176027226 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/releases/jamtur01-apache/lib/puppet/provider/a2mod/debian.rb0000644005276200011600000000074713417161721031000 0ustar jenkinsjenkinsPuppet::Type.type(:a2mod).provide(:debian) do desc "Manage Apache 2 modules on Debian-like OSes (e.g. Ubuntu)" commands :encmd => "a2enmod" commands :discmd => "a2dismod" defaultfor :operatingsystem => [:debian, :ubuntu] def create encmd resource[:name] end def destroy discmd resource[:name] end def exists? mod= "/etc/apache2/mods-enabled/" + resource[:name] + ".load" Puppet::FileSystem.exist?(mod) end end puppet-5.5.10/spec/fixtures/releases/jamtur01-apache/lib/puppet/type/0000755005276200011600000000000013417162176025353 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/releases/jamtur01-apache/lib/puppet/type/a2mod.rb0000644005276200011600000000027613417161721026702 0ustar jenkinsjenkinsPuppet::Type.newtype(:a2mod) do @doc = "Manage Apache 2 modules" ensurable newparam(:name) do desc "The name of the module to be managed" isnamevar end end puppet-5.5.10/spec/fixtures/releases/jamtur01-apache/manifests/0000755005276200011600000000000013417162176024300 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/releases/jamtur01-apache/manifests/dev.pp0000644005276200011600000000015413417161721025412 0ustar jenkinsjenkinsclass apache::dev { include apache::params package{$apache::params::apache_dev: ensure => installed} } puppet-5.5.10/spec/fixtures/releases/jamtur01-apache/manifests/init.pp0000644005276200011600000000161413417161721025601 0ustar jenkinsjenkins# ensure apache is installed class apache { include apache::params package{'httpd': name => $apache::params::apache_name, ensure => present, } service { 'httpd': name => $apache::params::apache_name, ensure => running, enable => true, subscribe => Package['httpd'], } # # May want to purge all none realize modules using the resources resource type. # A2mod resource type is broken. Look into fixing it and moving it into apache. # A2mod { require => Package['httpd'], notify => Service['httpd']} @a2mod { 'rewrite' : ensure => present; 'headers' : ensure => present; 'expires' : ensure => present; } $vdir = $operatingsystem? { 'ubuntu' => '/etc/apache2/sites-enabled/', default => '/etc/httpd/conf.d', } file { $vdir: ensure => directory, recurse => true, purge => true, notify => Service['httpd'], } } puppet-5.5.10/spec/fixtures/releases/jamtur01-apache/manifests/params.pp0000644005276200011600000000056613417161721026126 0ustar jenkinsjenkinsclass apache::params{ $user = 'www-data' $group = 'www-data' case $operatingsystem { "centos": { $apache_name = httpd $ssl_package = mod_ssl $apache_dev = httpd-devel } "ubuntu": { $apache_name = apache2 $ssl_package = apache-ssl $apache_dev = [ libaprutil1-dev, libapr1-dev, apache2-prefork-dev ] } } } puppet-5.5.10/spec/fixtures/releases/jamtur01-apache/manifests/php.pp0000644005276200011600000000012113417161721025415 0ustar jenkinsjenkinsclass apache::php{ package{'libapache2-mod-php5': ensure => present, } } puppet-5.5.10/spec/fixtures/releases/jamtur01-apache/manifests/ssl.pp0000644005276200011600000000040513417161721025434 0ustar jenkinsjenkinsclass apache::ssl { include apache case $operatingsystem { "centos": { package { $apache::params::ssl_package: require => Package['httpd'], } } "ubuntu": { a2mod { "ssl": ensure => present, } } } } puppet-5.5.10/spec/fixtures/releases/jamtur01-apache/manifests/vhost.pp0000644005276200011600000000072513417161721026003 0ustar jenkinsjenkinsdefine apache::vhost( $port, $docroot, $ssl=true, $template='apache/vhost-default.conf.erb', $priority, $serveraliases = '' ) { include apache $vdir = $operatingsystem? { 'ubuntu' => '/etc/apache2/sites-enabled/', default => '/etc/httpd/conf.d', } file{"${vdir}/${priority}-${name}": content => template($template), owner => 'root', group => 'root', mode => '0777', require => Package['httpd'], notify => Service['httpd'], } } puppet-5.5.10/spec/fixtures/releases/jamtur01-apache/metadata.json0000644005276200011600000000301413417161721024753 0ustar jenkinsjenkins{"dependencies":[],"types":[{"providers":[{"name":"a2mod","doc":"Manage Apache 2 modules on Debian and Ubuntu Required binaries: ``a2enmod``, ``a2dismod``. Default for ``operatingsystem`` == ``debianubuntu``. "}],"parameters":[{"name":"name","doc":"The name of the module to be managed"}],"properties":[{"name":"ensure","doc":"The basic property that the resource should be in. Valid values are ``present``, ``absent``."}],"name":"a2mod","doc":"Manage Apache 2 modules on Debian and Ubuntu"}],"checksums":{"manifests/params.pp":"71734796921dbdbfd58f503622527616","tests/ssl.pp":"191912535199531fd631f911c6329e56","tests/vhost.pp":"1b91e03c8ef89a7ecb6793831ac18399","manifests/php.pp":"b78cc593f1c4cd800c906e0891c9b11f","files/httpd":"295f5e924afe6f752d29327e73fe6d0a","tests/php.pp":"ce7bb9eef69d32b42a32ce32d9653625","lib/puppet/provider/a2mod/a2mod.rb":"18c5bb180b75a2375e95e07f88a94257","files/test.vhost":"0602022c19a7b6b289f218c7b93c1aea","manifests/ssl.pp":"b4334a161a2ba5fa8a62cf7b38f352c8","manifests/dev.pp":"510813942246cc9a7786d8f2d8874a35","manifests/vhost.pp":"cbc4657b0cce5cd432057393d5f6b0c2","tests/init.pp":"4eac4a7ef68499854c54a78879e25535","lib/puppet/type/a2mod.rb":"0e1b4843431413a10320ac1f6a055d15","tests/apache.pp":"4eac4a7ef68499854c54a78879e25535","tests/dev.pp":"4cf15c1fecea3ca86009f182b402c7ab","templates/vhost-default.conf.erb":"9055aed946e1111c30ab81fedac2c8b0","manifests/init.pp":"dc503e26e8021351078813b541c4bd3d","Modulefile":"d43334b4072cd1744121b3b25cd9ed15"},"version":"0.0.1","name":"jamtur01-apache"} puppet-5.5.10/spec/fixtures/releases/jamtur01-apache/templates/0000755005276200011600000000000013417162176024305 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/releases/jamtur01-apache/templates/vhost-default.conf.erb0000644005276200011600000000116313417161721030504 0ustar jenkinsjenkinsNameVirtualHost *:<%= port %> > ServerName <%= name %> <%if serveraliases.is_a? Array -%> <% serveraliases.each do |name| -%><%= " ServerAlias #{name}\n" %><% end -%> <% elsif serveraliases != '' -%> <%= " ServerAlias #{serveraliases}" -%> <% end -%> DocumentRoot <%= docroot %> > Options Indexes FollowSymLinks MultiViews AllowOverride None Order allow,deny allow from all ErrorLog /var/log/apache2/<%= name %>_error.log LogLevel warn CustomLog /var/log/apache2/<%= name %>_access.log combined ServerSignature On puppet-5.5.10/spec/fixtures/releases/jamtur01-apache/tests/0000755005276200011600000000000013417162176023451 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/releases/jamtur01-apache/tests/apache.pp0000644005276200011600000000001713417161721025224 0ustar jenkinsjenkinsinclude apache puppet-5.5.10/spec/fixtures/releases/jamtur01-apache/tests/dev.pp0000644005276200011600000000002413417161721024557 0ustar jenkinsjenkinsinclude apache::dev puppet-5.5.10/spec/fixtures/releases/jamtur01-apache/tests/init.pp0000644005276200011600000000001713417161721024746 0ustar jenkinsjenkinsinclude apache puppet-5.5.10/spec/fixtures/releases/jamtur01-apache/tests/php.pp0000644005276200011600000000002413417161721024570 0ustar jenkinsjenkinsinclude apache::php puppet-5.5.10/spec/fixtures/releases/jamtur01-apache/tests/ssl.pp0000644005276200011600000000002413417161721024602 0ustar jenkinsjenkinsinclude apache::ssl puppet-5.5.10/spec/fixtures/releases/jamtur01-apache/tests/vhost.pp0000644005276200011600000000013713417161721025151 0ustar jenkinsjenkinsinclude apache apache::vhost { 'test.vhost': source => 'puppet:///modules/apache/test.vhost' } puppet-5.5.10/spec/fixtures/stdlib.tgz0000644005276200011600000015457613417161721017646 0ustar jenkinsjenkins‹-”¶Ppuppetlabs-stdlib-3.2.0.tarě[ërŰ6ľßĎz Ś3g˘ěŘ2ďoÚ‰›¤Ť·ąŤťnggÓqA´P¤–¤,+ť>ĐyŽóbűű$EÉv"§Ůž[ůAIŕżĐ|1ź«&çI}P72Ď’wâL¬Ă?}Á˲¬Đ÷™ţým9žůn/f;–ďűú“Y¶ď¸Öź˙%‰¸íZÔ Ż@Ę;•¦‡aĐňŃ˙/ąć·č˙ńłă—ß=}ţę»/€ň<ď6ýCŮľ7Đżýžý[_÷'Ż˙çúw,Ű9°í'běµjTĹ^¨ źçt3™ŃÍŁl6S•ä1ýĂ´…ŚŘźŮ±”¬Rď”hĆXş(D“•ó0N!ŚFşŘýń”KoĎSöPŕîŃ2K2É.g× ó‚©˘^Tę|ÎĹ{~ˇęÇ„Ť# +VbÁ„§Uöž=áůýgÁJž«âQ=/›,]]Ă1ľg‡a>`ł¬ýĽlĆŻśÔlÇľg»nĎŠo°ÜUP@ĺˇ$‹¦¬Ô?YĄgi–«ó<+¤X—‹J¨ű5›ófĘĆ2Jî€=;Äż–őtÁŮ3^ĺĎó,m đ‡ąţ~´öĺĺů®ĚŠó÷jUź7ĺů%ϲ±RVę$Ă߀đéUŁ P©rHgŤ&-+V7UV\ÔZŕS^OAĆ8tGyk“‰,’ówĽZ±çĽĘ>|ŕěáî>Ĺd3Ulž‰÷™đ@J{­E÷Ŕ±0ńÇ,gŹË2W+öp)ôŹGsy5Qr±­=7˝ÁĽR2|ŔT­ąĘ XAĘžsŃ€Ą4˛cϵ-PĹţşČ3HóáL=zWŞwúnRđ™şĆN‡cź-ŞLŐ‚ĎŐ>kJöĂéÉÄÜvâś°S%gdK0kĎŹ!S+ő¸µöëŔń‰:öB<^`čCŠz7ŠÔžŘšőcŘ·Ů«çJ€ą,‡KÖd±‹Ľf–VĺŚ} ¦á ÇŻO<» gÇaąA:$€”ú‚7Í”/jöj ˙†Úű[ččeńúi+Zđor ™ş‰ĺěĆvćŃ2<o\LŞ˘á9›•đV¦Ýą,›s9©¨XO˛0$WĽVź‰T3Ô2€{&«r^łcËŞaË î˙ZOdÎ$Ř ë«Uu©nÜ÷ Ěě›R*Š…7ű«uÝŘş°Ű%í©RĄ0.yNř+>«É±Â(–˝‰Y!” ¨Ď`$J±ďUQoMőíǰŹď9¶ŹH|J˛7¤*#xí\ʰd‘gÍʰ¬`ŞzęÜ-vüĐëŤÍň )ʧUVł×U6spóq:l7]¤„ň’L;//2a ‡­Ă נ·oĆ‘źĆL?Çőś‰˙?ÂőŽ»şžżĹőpź^ÍóLdMľ‚lą„ÓE­$KV×Ěp§ÂµÖI‘PşwAI¬$Ş!iÉR %ĺ˘arA„PDď1ő˘† Ąë„üłQ>ďäŠ- řŹ6eí:ş|ŕŐĹb†ĂĆ^%Ă*cGçÝŕëîÎË-‘DbsuVÜŐH5ć'YÍđרš¬cĘ-;:Ľ&W®"Łągű±íŔh"Ű‹ő›pŽ_¬c–Ő0 Ö•.ifÄ$¨•d v=/vbŰýM¨IĐ“Ęxc–kÎ*5ÇGYBŤQ„h¨˘C`Ô¸Sr&ŢEŰÉ^8»/ÓűËÚué=’Ѷő|şîq"ěůQčĹa˛j6jCÇ “ Ă0ď,:oSk¤­×ý°3ňčČÜź&˘×“Z”(lk•ş@ś¨IÉ(nîٶ…˘ŽzDUËăÔâ#Â&{w—iŃôéË„EM¬R•^¨ĺF:#ŇzîFŻĄ±µµÍs‡ŤgÜ‚Đ!„Fą˝{ ôqdKňŮW_›2Š"Żí†®Bc¦'˘řČ> çjşłQ¤€ď^˘ˇ`{p,ŽŠŃp7m•¤Ü—:KđcXBVdMĆs >‡őT«fŠůçđüsO”HOŔ9+傾[q\@4MNęú=@ĎŤMX|qzÂěI̲ş^´&˝I‰/üTÚ×'ÎuöŰŞEÎÇAŕ¶•lάů µv$‡É-u[RâcŢ׫mŰMŽŽę e| -g&gjŇÓ[>©&ľź¶V×cÝ©ü:‘zŃKwşÖéZ‡:^zCĽŞFšÎđ°ťÖ=]ŁŮ°¦0Ľ …ť•źNN&ěuÇçŐýş§V,ꍗÁeÜéĎm©ĐVÚ”N{‚i í&Ző»JŤÖ¸G'’·-Ŕĺr˛fÂňvgÂŇLś*ZVB*»ś6¬1ŕ͵iF‹qRSKĺóşˇfĽäşÝänoôČ%" ĺ;ô…j†¤4Ź}Ĺv+ö–bÄ.UČžä5w§YCîhţLÚŚ4OL®¦¶‚A· dlí­e«s]ÔirúGm…7Ś‹ÚQ]¨áRĺÚ!Oč€íI4ń[ř´TŞ' !­ßŃĘîöc2˘źĺ ]ř·€ .»¸¦Ĺ±wŞđ,5Üćťl×Ň/T|3\N_ٰýťQĹ…"ömmłškË@µz™ˇG%ârV膺+=Ęš±Čy]«vËÝ»żÍ`R–Cq®DF#ď15yđi$d´Ţ5+)\7z‹§ShíÇ@t×wÚ6ąŐĐş @g ĐŰ ŕsEśr­‡Bw"ČüÂ@´ď Q÷¸ZjGG-ŰZIĆň9‰˘¨S*-ĚŃ÷^Ů˙Ĺë¶ý_üúbgî¶˙Oűż~úě˙˙×mú~ňřéËł§_ÇÇ÷˙цxŰú×ţc˙˙÷¸—óU•]L6~ü@ÇńŤ˘č¤ŁeóšĘ˙9Żšúh4şaŇ÷Ő‡UýˇA°ţ1ËŇUQżĎFŁ!(r-ió´ üÁ›#ę–Ë­1=Ď„*hŚVÍMëv<ç_í›}ö7UéU+ęĆ4`Ż}µ÷ŕ/ŁUą@]ˇxnt×ÔP˝¨›xu%ÔĽˇâśĘÚvíňHł†>ý˝P&TR ! 0L‰h0 äŹPµ36mšůŃáárąśpM%•ŔźzTÝ9ҵwŁŠ\Őu· 79¨IGÍOődΗŚV|/*Eéµ$:—UFeě>6KędFŐd˛h6ÔQN‡ "}ďřŚťśí±oŽĎNÎöG?žĽyöę‡7ěÇăÓÓă—oNžž±W§ěń«—ONŢśĽz‰»oŮńËżłďO^>Ůg âuE{Mf$:…ôL© ä]Ł]ë%›L€ŁâbÜÍ.PćTU®sUÍ2˝ä¨OŚňl–™†şľÎÎ䏜˙oľn‹˙3^d)5ů_  ¸{ţçŹóżËu«ţUĂőÉ,Z~úŤ8>ž˙=ßńí­óźah;ä˙ßăú9lŻMV{GČ&ŃÂööéŐĄI´ôJ†yJÍwŤg˙Đđý‰Ç˛4±cVS’P[í:ť‘*¤Ém(ô>?×ë8mwß&ŰŤÎ}ň¶x[0T"5Ę}ł°Î۶Q*|Ó†GʧdĄďëLŇQPp< Xú¬m#éŞk…v Áäeš«·űq“vá‰&뵾„Ë|Eđú bÎépAAĄBË2ž.5ÔUżŁmÔ3žçĄ ´BYCŔfü=ŰPš-‘(©2 ÄJÇé ±ĘÂh7EĘŠ2fĄr“A§ŮĽ¦KĄzf7™ĐK3FH-=­|ßLU§­«\Cޤô’ŻĘ-űBöÍŠŐłĚÄTݰ«^˘©eiMDz„xĘů@ú¤Ŕ9~»Ô{XűšŃź5ł!żkĘmyŇíá¶ ŇeDP4söK÷±{Ä{ÝŻA›€<ĺ—¦¶eU)ŃÜ év«ţ2„Ąe;aĎĘ%mçíëAtĘÄČÖĺ B-î7 ‰OBŮŔ}?&Úţq”ß˙i˛hřů…Ń‹ŁŁöěý#öëzČÁ×[Ł —4»řÄ ÚšAP0Ł®‹ŠTş–ýŰ=ĂÜŰ˝Vbd¸ĐçZ ´©iöD›ůZçŕÝ“Ö;*u‘D¸ÁšÓźÖS‡Ż;ęjyŰ O˛˙kç´¤śçĺ˛6fŘú[W÷“Ő´ćE{z 8«§ý¦9V˘ŕ©Úë6]R[ĘP‡GŰ6ihëxíźÍD‰0!hvHňmď·ŮéOGm˘űC$ëđ= áĂ0NAŻĹ·ŘZťô@ő$Iłôw˙â×ö×O=Ý8m@>uĐdm^éćéď_÷oÉ3Ś=Őˇ (ś]@ÓËÚY=-­ š#×ít†ę·óŚhőö“2.Ô>˘‹ČzŁc9EbŞçtÎÍž4Ś´0P’ŐüIjŔŃűŽÍ Óúˇ—ÚďŇ›?Ľ=ĘÜîh}ôŁS µn ő' |A‡ ·Ö l˘şÜ(ÍćZŻi9ífŠ>~Ö¤o-ŁÎ@ź^q’ÓŃ0 ¬OHŔĚę…,Ď+¸ĹýŁőfޱő5»Żw˝hlîţţp†@Cţ^łăçĎżăăýŽüőSĎ‹rľüŤč Ä€„—Ż^źťýřäčbĚÍIa”ЦţZUéĂ­ű´M14a$ΫV׌B/U éžlů°ŮňŘÁ…ovŃj‘¬>ć˘wÇđ©*ÉšŠęë`ŃuÄ>RM›ý­î:ö?…ąś“ăę#Ű y·FŃîËT wÁi»o2}^cÓÚIĐyűť."Ůy¦•"*u¸‰ŢyNž®‹ůźץäŁf‘N>ĐŰ[ŘŇ€wćëÍÚëÉ}ÍFťqßŢ6č,rł.´­÷J»… ‚y'"4¦lş5×zźÖ–ݤź~Š ˘z—DŃ»ü­ąâSś$ĽÎD{¬­Ym͑ըĐrĘŢ0 ÓżŃîk˙!CĄäĎíy±ź÷ŮĎč”é×-LÝŔÖ¨»Ó î!~ëÓmkuÖmx=ĎíJú8‰‘ý S¦›ĆËLS.˛ćčđźÓEB˦Ăךúvňb6pš˝›mŁ7Ăů˘ˇ MŞęáíµÔkŁ,Ä [ľzĺŢLr«x2h–ŃÖĎá˘Čš–ęCŘMM'8ş]ăĂ™˘#Fú€ý¤Jş„"°âČW©đCËq·…ď[‘ =†i«›`oíĐ‘¸ÂňxčŰí󎽥–']۵ś{"ő:$´Svx@ujžĂWë)÷m§íĹ<ĺ2LÜ(H” ĺ»Rq•ŞÔuů»€ľ@pjÁůAäıĎSĎŹËOÝđd{ÂKŇ(6Ä18ŘQăyqb žĘ( 'M<Ď–‰ĹmúKŘĆô5[˙ÍŠ.výÄăJřňE$e‘7!›Ł¨íĚÄGH•i¬Sě'qČy;B„a y¸“çϲ.řA'6TA10Mé'–§Ľ(ö‘”Ť°S@­Tš]uÚ掌Çu¬Ó§€şQXÜB„őĂ» >şˇ yC:.”§b_ĆzĘQ6b\9»C–ĺ˛Ř¨đö8DâGY‘:*vŔ†“XP‘‚şŠ öllśwÍC@ň7ř{W¶[Ç‘dßďW\´ŘjPRî‹ŔpëmAnĚKŁ!ä*sšŰpŃXćßçDVeÝ›ä•Uĺn¸óÁ”IŢŞ¬Č8çddE†„˙ Qa+Ň•,€řŞ­—´üÝ'JhžŤ„ÇhŁĽůT‰’bŤ5E_óú§?ńâúčóÖK«P¶ŁW ô¬0ËeĚÜŢVfvŠ;S‚áŤa(D®JEO~U6ě“×ÉŇ_` YŹ'­ŇeÍ śĂ#ěׇ;ŰűxĽ‰'8·<óBéĽŇ‚y%}al‰µCy—^Vx}{Ű”–ěŃ‹tQ˛łŔeă€5±\\\?˛ z±Šö0~ A0ŔK)4-%zb¬&(‹¤¨ (|LpŹM÷ <řq”NH‹ě‘XE2>i ¬.ŃćęâBŰöÍś-`¬ĹßJ‚ĹßgK‰Í –Š…lµ«HĘŐíC'Tqé™NHö@Ş$‹U›h#Ď!–ĽA¨ĐÓŢ~9ÓÇńF“­Š>Io Řp4Č]Śs=ňáöą›Ű‡ű68Ă0­ŮSfRWÖX„,€OŁ©îÜ~¸.Tší%„f|WÝÂg§•‰ $&Ć~†čꀵĆy43鉒Ŕ1Ł—őcĐ%e ÍOáöâazÁj¸>`ů"U“sxL0Ď ýE„UÝŇEëČ-Ňš­ 7!W†4ʸQ•°~#Â/– ĹH}zĦ•:#DŰD‹‚ŔXEüAL;/U)(#I` ­ÁO hş5H!Y®ĘůsĘ ťśD±ŠHČžebĚ.ůŕ”Éc>_e×IÍä«”žWÚ‘ `‚¤ÉŕşŢ®wôšŔ SPĐ5ÚdÔ‚&¨>Şo3®żáŇTëáY9nĽ÷ž9žR„ŘŃ€PRYĹLiGÜś×ńIŚQ#=‹„„`¤Ö ®^7¤Ía«ţ°Ţᑥ$&JKv%)„9p4-áv«rîü_Ź="çLĹ`’+¤B!ˇP«Ó´`ŕ}GŻíôĆ \‚rśVČD Ŕ#™đYŃ”4iď 2„Tö…k® ž˝ác-¸ť”ZÔuâ§‹Q‰ dPeHĘčĘ Ěᓦ‚@¬Rzó» ąÉŢ }‡éf ×tR<˛Ů+vvÔË›%DD8řRW”ń5č¨)¬xč»ę†LEądđyŠu•…Žž9Ä©ŻĘ´€« áÄ`iŻÁ›©Ř]=‘ěká>&žR2E©%_kĂ\öe“"­Ęzf”ÖA‡ |‚÷9KqrrNŻGͬ¬b¦‹dĐ1&ş ť#ť@v§T•0j$Ǧä&x ĂáíĹB‘@~y™°svČM?gŇÉŇvˇ^´®JJ šQmáç.A7Çl7 ÷ţ‡ÇZ/‡8\ń–/j”©Z¸č¬SŹÉ_G”‘şŰë Q†Đɤ¸!ć ŹÉEâô™¤Ö\nNĘÝ ŇiAŹ«–HŤTXÂúńĺÚÝÍ:°0ř7®™5¨,ŕąĆ…& t2ôsŢ=˝Ó2ó¨”LÉÜB1+f%Đ’ ¨‰N¶’ÖX•{oŹDFĐŔĚ`ˇ_mŞ^SŽfNęň*Í}ŹT^~Z®X0l#K%$2HE8R:•u‹XWýrĚeŚC#X2édE‚'9Á™Ő FܰÔ8 Â0HďŚQÁBŚ bPbvéж9!›şŠÚPI 'v(¨¬Áćl›gÚ„)ŁrÍPßz‹}ZHkë3’j:V¦˘F#źž@L ŮĽEŻçŕŕ%—@Ř`µ‡Š7¦ć,‡ xŮacđsĚNP.«"˛Ľ‡-°9A.Ř­=¦čă{ @açPH´Ş.¬”­&Y•aCž:Z‡VNLŠ@Ë@gĐ Z€µÇ`óşőŘ˙Çń¦<ĂŐŁ‰ĺ„ 1d˛bµ„ó‘¬ýYfľ•ž˛”"nç’Mä„15ZbŞ˝ňŃW7×` n§ ,¸I¤îJ=WĺᇛÜAG&ÜÉ!I]- Ë| ,đUë<‡~$Ýq™…9a 2Ťc& uAćBI›ăzŠđ¨”&†ůŞ\1`kI`ĄĐ—€Cfj@D€aIµ […ľËrýńˇă Kp@Ĺ4F 0CP'ăÁ?Ś|"â–ĺłĹ]5ćZ  ä˘x Đ–Tµĺ«$ˇx>L{Ëhűŕ˛ÍGĚ ¤śč]&.ŽD0LjC(?ąţsTŘF m)+ÓŞ’)ńLl«nX™n%ʱ‚š]µU]K­!7;é*íčĎŞ<˝:?,ÇQ†›ęšI`C¤hÂÂ…™Xck¤µ#˘ŕĘ>0ÍŔUbĂ!ýFk}pz„őŹßŢĺ>l`…e`şŠž®m.•ˇTŠĐë|ËâĎ, ŰUŮ»ÁťSŇEdbŕsĆm 3°Çóâ7<BË áâ4˘[ŔUaÖ2żĘĐDí—ŠJőd0yZO !Ĺ’LYĆc1 lĚ/KµÄ(=Q,—´şŹx·˛HË‚Ő ><]Ĺä° łđa‘¬ö娢-ŕ.Čő«¨OqľÇHô´Č®mĐwAľPK ¨"śB˝9W<>\\Î7ůp_hŇuRéb_¦9¤X,Ś{­ˇŇ[ţ˛k˛i%c@żH$ZĹH2¶ÚđB’ńÖ<ţ‰÷žě »!›´äB8`2’°„Q€śpq•ĉ|yĘ]ŰW„±öDé‹6Č?„‹0&@¨¬bů—Mă,r&«§…y·KIx\ÜT«řú¨¸k—ÂTÚ‘‚“O°+D¦tíV°©3íâS%™MxÓIŮb t" ™$䪪XŰÓCVń‚pe”<˛X˛®.~# 9ĂÓËę/*°SWHÜ`“ĄRc«Pî*ôšŢ‚éb˛U†–0¸4" ß"­ŃË •†#.2ÔAf)rh? Ř ‡t¦ş3ÄÜă*Šw`bÎ[Qţé @=íˇ%Y±hÔ†JŢaßEĽżą||xČČ0’Qdˇ˛Ö Î ‚S1Đż`U‹źőÎT-,%ś‡K^ ňµ´źnɵ7' OëÇ]űlî--;0¸ŞĄ°ńŞ^pG&Ś0Ůů1?/PωY:UY¨P‘ČM9AŕDÁn í;jNËŚ‹ˇâ2íŐĐŚö¤H÷Pšß™_˘ÚĆ!Y× /J´×ĚŔ lqÄ…­ˇf‘ó§m%˘aÎi…ŹÚ«¶H„ŕ`áIMđxńýY[ĎaŔ` ř¬L…,d™Ú*ĹĐlŮçʏ˛p 'rśvU¸Q¨¤"Ť˘]xŔż ë\}'Ó0L"Ń^ů7ĎP9űX­Ä?ô¸Â˙Dć`˛ŕnÓţM««Çű‡Ĺk!ŔŚN´ÖT¨h›`%}ćŐ–Ťbwe@ë¸Zĺ\h}Ń´íHÁÁš6qݧËđ„2-ÉS•€ éd, w‚-ÁöţíżĽýöű·Ţ˝˙÷ďŢ~˙ýë«p÷WŞ47 äˇ%µeĘ‚[öA,lŚ `Ö„ śţň B HeÇ"¤Ł@|i¸n‚Đ©»]·˛2­vr¨”†F@¸’·ŰČť †á‚ÚěyÝZťBĐ/č<¸™MŢŔŚşź’)X+Ż;ë›ďČ ®Ă©)”E„L-8A´\ÔşĹ˙—33ZHGâˆ3+uáFŞ2IEŔ‹ë »´>Ź% %¨$Áž-˛DŞ2Ă?Ž«$üPęëă>!]ă Y4xsŰ!ąAݵ >śZ˝˙Ň˝eŁWŕđ´ě R¦!¸…—PBŮ9T ü ˛óSŚň‘(P'ô‡Ň™šA[„—]·ŕ9óže7(śŇg#€%Ž*ąÎŇN*ĂQí~Ő%[ͱ'y)IŃÄ Ą¬E _¬ĄCŠç™–2VŠ×ŁŤť”Ç੨F ąA1HŔaÖ‚ńjt«čIy^xQ¸ń$ywr< “ Â"§Ö#'˘?ušědĚćŚÄS€iD÷(ąĂM!|”´ŐîŇľýăżľ2™ĺ>eĎ´ !p­CŇ*ŇE‘Ř–m“3ǨŁ! JV0W2.ZέBÁĚ„o©†;ć¬Čߤź¸Ě&‘2¤Ł—ľ’ž•7‘W«).˘i;,8µŚö…$6î/.RJĂ>űĘ$´sp¬ĎTďŰž%I%Ů Ţł­Ž0ďŻ|Y k5 ĚŘdâŞË42ŰT<ť°ÜdĆÁt@ű ŐX|E ŤúŐ1ŘfÓŘŻB:ąďJ)L&,ň5ţ®x^µÎrB*ő] ˛DŘžéL•ÁčđŃZŁs«.~BtĽ¨!*%ef˛JµćˇŔ(,˝r[6“´…˘cŮ-ŁfIŇîźŕ=Ąć<‘¨b!ä§™‹ŹCMŻę(•O‚Ae]ˇŤBY}jĂëĎĘZßK[HýŮYč¤Â˛ŕp•R´ÁDŻdő¨ČY€nFŇwô qWJł ˇXĹÔ–±öa×V–g቙s¨DH͆ÝĎĘ,&3A»#ě,< ‚]ǸˇĆąl –”ÂĄ6QۉC ¨+Ö řđÇ·˙*ýîíű čÓ‚GŤ`ß ęŽĆÍŞŇÝR0î™¶cĂŽ©řjÁJ8ěí˙®)ąŞv×—Mş«Ć ˛äi+ p ó˘uŮ2oĂşňűzú:2Ü^ş¬rŮuŇ@špY,鯲AŠ÷ćř^ťścÖ0—’«Ţ»€ëRŤl@_…Gj3ĐFŐ%UŇ5pÔ*Hh 5)ů*{ëq ‘Q]í`‚ BYÉžÉRm ‹§NŻ7öE˛`¤&a ›2ćQM€kÇ‹uÚ‹†ý®ÝüóiĚ$‚,Ëd!ĚÁfYtlłk‘I7¬ł]Ť‰ÓIËčZŚČ'Ą@-„č7 -1t(Čr›ĄşŔřŠ ‘g -+*¶a˙äO#ËeTţ *2e(0®ăB;7Dn2icU¶U zŠCÂ’‚®ÉĘ,T“'/ڏbŮ&źĽ,9č  K†8â"[¨Úú«l™­â2mTĚ«ÇÁBvT0CWéřÁ}A2®ÚąÓ7&v- ě— ě[TÉ˝FC?Đ;7)ŞNŻĚMEĚ>-"±yŃ E&i Ľ«uő«ŤóŽű2[ÝŠ1FŔ\9m۰°o–0ŽŢnošÓŰŤ¤Ý†Ź-©iâý†÷—w˙óŹn”ő˙ô뙩çţo‡Ôů·ßă+ý_©ńŰłţJýÖ˙í×řj=;đuöĚÎvł*Üź5—8ŰÍÝÎ6µ8ŰM}Žďp¶›;ÎíĎýćpý©?Áţě]űË/u'8ŰuOŘź­čťp¶;Î?űłŤŮçl·{ń˘5-?n|pľ§¶!ןżŮ˝8ü§žQdŐ7ôźłóýŮ?˙ÓžOüGOö‰Ż/ĹŻ.ţ=îńµřgÖ>í˙lŮoç?˙*_˝{Ůík˙X®+ËŹľq|şĺ›;¸Ć‡‡p˙×ű˙“ýŰ×–Ż/Ć˙¸˙7Ýă+ý_Wć)ţ[ő[˙÷_ĺëĹĐîý–ľŘíZ_Äąńáü>Ěý>@!őż˝ś›µC8—ös„Âą|*—7·Ô%kşËnä×s»Ĺ޵ő—ššěMýíę 5blíµrľhň:`ÍWˇóᾟΡř¨kűGW*ídŇů ¦ˇműÍźú?Ţő>jăCâ_7µ^¤‹vSzĽk˝¶Ú1jÓZź«#łÍý燚ŢSűö25]=´eźź—˝î(áÓçý|„ië'×íş›íúŁ›Ž˝Ł.¤ô@%Ü%:'e9đŤzB®=,™Ćrľżťjúxëíţ<·ŞźÉĐýëCđOhţů›)üĺ÷Űţţ%hRoĚ˙Ôs¦ ˝?:%j˙űOí\Ą—ű™gŇl|„}[Ź¶Ů¨O­q4IóńK­Ű\ř?Ďň–VŐoď.îËá)ž ~ąíŰ«˛üý›—ý$©0´Ľ»ą¤ÖŻźú“á6óî25;žůűšăůL©Ł¦˛;L˝ŮNw.×í<Ůuk;yKŤoó|ŘďÚ„ĽĽýő×˝™ŹIś żĽĂ¨a—űöäM™ßM'Ó-× wĺ›-źčTłaňvţÚÁt×tjčéyă¨|ú&§oęĺüQAý7zťę¤Ńß˝=źĂ›{Ôµsöúg%}öýx?:O÷ÇöýäG˛Ć|\gKí§é1^’#Q;ş»ű‡†ď"ĺТł5[ś©>8Ë3żhOHűÝp,á‹énăçóM™úw>;/oqŠĹ.·­×ň!WщzěuSóEÉš/pźoÉ?¦Íź^ұWá?oŻ[Žžď‹«67]ţ×·ó-čQär­~l[;Q©ýÝxôü‘p˘Qď‘cűŐv‹#Źw<ä\˛ü~÷ęŐ«Ýűňđx7wě…੟Ý;̇FžOm­§ö™űWR˝Öf ‚†h˙÷z˙'j†˝ {j¤}95˙HGgăŃŰ»IóUç&âóÚŘîŐţ”ç˙đÍ|.ôn×_ŰŁ¶ŻÝw7×0ďE:ý˛„Ö6˛ďő~ů==Ç´”řÍ®†K:€¤žďŮůţúĽ ĺšŇëžíîń«DĎůţóô«ĎĄo«kď'fM7ś¨ß—ÎNťÎ›ĺâúöń ĎqxMhyz–ĺ§sóÚ.—ÓaçÍčóő逑v@]ó‚vlęëÝ2°ů°Źe|Gşž?÷őŇ{ ÓŘpá«›Oóî ť b$ťl١÷VËOÇxýl˙ŰŢ›ď·m, ŁßżWOѡí!éP-1'˛Çńrâ™8ńŤť“9#ë( JI€-+¶ćwßáľá}’[Kwٱ‘ŕ"ZNЉ)čĄz«Ş®®Ą±c®—ßŕ42öŢşżé%ĂO~kfŤs˛óÔ‰ťůťŕ>čĹĚ~>eEÚăűF ô9¤Oý6Ĺ éô•łC*óŰ[`n°gX]ŽÝLęÂŹ1p+ąŽ(hMş‚tFwT¨h“|.ۡfÉg;:¦FŠaĂžż’Ë ŘŃ@ÓâR铣ĺŔŕŇţ¦#Ç+dŢB"'öůcšq#m,áh-Ó¦gňˬ›oŹ #® fož_)(".ťPńVĐ´F?°IHŐ¤ç ´ěł4*е kěŰÖ]'ŰäâŐîH˛6°`y缯 °łwl +‰śü…ŐˇĺV{ôD¤ŁNł¤ýšKx_‰Ś©ýUť@=§ ń±ĘuTÉ[±¬Ą*®ëÚăi¬ÝŚĄŇëśiÁŰZ9;OéÍłŤžĹa„lv“ÍűUmLčń}é:čÝçÎpMµ“ŞUmTűđoPđáĐđ_ińÂí ÎŻç@tf…Ńň0ŕ˘Sىt<´?ŘŇWkQř Ţ$íe”.ť2h Ż”ŔN!4˘öĽM7Âo°¸€1ĆHť4Ł5Ţú ˇĆ×6“&LŽ|{Ę®&®›™Üzém‡'& gN' ÚyBq›Ć¦!Η%˛čŻxGë~®˛%޵™§Ŕv 5‘AÇť0bËÔž%/Ď=n7ŃŰÉe Çś-µłó"î«:^yg.ŁaöÇ'Sí=»±Ă‘Ç$•žÝ"Ă‘j›;ořRÖúŚ˘ÁöÔď YĐ‘XŘö'ŰmňŹŰugGúŰŃFľ čđ8´mŚç.:ëâĘäS«¦4Š’b1ΰcś‘˛v›l€÷l¨>}–:=ÍŮný9[Î0ô1WîÎĎô(0řdž!`‚(49jş`¸–@FDąC!V™˝Uö˛©­’Éŕ’w}ţĺŔfęŘf»ďßs%»ŞśěøÝ5ÚÇ"I­*Ż$ęyp2P¨üĎ{7›Bw#áŇŚMĽC;P h˙yÄXÉíswäyÜ<ń­b8ž^BőzđšÇLwÄ3ŕ  m¤°^eˇ»‰Ür[Ă˙°Gł±Â[ŘĚ@lF‚¤žĎçD…<@C"ż>á ¤*gč[~qĺîGłŔuŻŻ+9`sÉŘđ({„}[ň…ńÝÄ2ÂSľ7;ż0ÖŁą‹`«Ij*'aň⮄KioöY» Á­eŃĆéÓÖ ŕž ‡C"Ľř2ąŐ¸´*¨2göUšküńT^fŞ÷"–I €ôa¤DŚAM…Í5Íł3ą"[çŹU´ŕń\‰|)?ýĽV‘l¸¦ AW°Hx Oó<€Äë—6#VÄÇ>FăŔ“Ůr….Ş›MWIh›*íăĽŕ©s¨+ĚŞ‹¬e1ĐGLW˘UĺđčeÍ+EĐ&tŘ&„ŘA”Řč¦QćGČsü2?¤\ǻٞŇöŢI#°,6bŠa{dÄł3N`rWŮíÖ ;yHÓhSňGóZÁE •R1ʉ"’ ‰6Đi–5ä6Ł'˝FäŠČnCJ>vڧn‰W‰Ń–iGžŢYŤ'·5­ř¶ł™9$E/ńâ•Púty-*Ź›Z?Ü6TĽ°q©¦^xç6+ĄHym1ä;›hIţ3[B í,Ä„Ď ¦I"%9śł€›öÔ&IA˘Â’ç¨JŁbŕ)d ˇfÔ’kôJ&€¨Ö´`Çń)ź hX‹0ŹrŘmöŕ ˙Çă—?p(·„5q] ŮáĐ*DÔšă,3°#…˛„y‚©ç{îZt‹2ĐA)›‹š\I˘§¬q@"yű€Ą÷8Oßć°ul…©Řş×ř#Đçâ %{ŰŁĐ^LćÍÓĄ%¸$µ0ç ÍąôęČŕJĎÍ?Hç'™KNüsIov^â÷€BÔyäš8ľ4DŔ^Hń±o¬6^Ş8gĚ$ľi÷˘8—6@‰,It®ŁlʆŻÁzŞchŰśąB‹’ݹ˷<Š9˘_5n­!«S\뛨tâ7yٞ̚¨5ę4h&d;ż^pI_λ>âÖl¨=Şî+(řÄ B†ŠX§ĘĄă6+Ůě’R—ʶkۤNqAr”Bó¦řŰ÷´ř—VGSüHůť ‰I»˘%3Qńěˇ-Ó Ś‡É`ěđ@üçëź~äëA%±‡Ő¤&8͸‘ňňΰâć5C`yÍBËo>LA îŁA@.(łQĹ('Ůç"ŮF„RGC ĄĐ ¸PJ,8ĄîPO%b–)ć™ć ȤVîuZĄCŹëŔ^ž?AUCĺŽŃĽ7­ăŕp «ˇoM˝1®fK´‚ťyŤ2Ő*­ и•Ü-`Ö;ŤncżqĐ8l5ľi<8Ť¤’­‚EEŤVö”ÄĎ‘pťoáD+°#ą8x3 –r=Ö°…íŇÍâ‹Jţ *qH/`·ÚX żµ[EăYĐ,D4›MőóQ6gŮ1§Á2ÓoĆĹg5ëĆ*{ÝúĹi#a)-ˇ™CĄ‚@(黵|őŽČ©W]ÚĹxťHNŰOĹşŹ1'¸Ę×t=«‹«ĹxcűŻŚśŘŇ.u€‰ßw%đg¬ţ #EF_$Ěđ™Ď{O€öřZ‹˘č‘ÖŚ#ę’9@ISü0č‚ڍ‚&Ă5’°ĘüzTĘÉ"řl켳{x]ۦ«[˘0P´Š_Ş4Žfi/˝ĂĄ[t!ě6„Ľć á*ýŞć÷Đôšş ›Űc.ąűúűÇP”N—x·JÔ·vyáp$CŠĹ’Çź^‹˙ŢŃş ¨ÜjŐ›âo„:Q*%ău+~/a×Ç„L¸ýAk"ŕşß™×>`SЧ0ş"޵j í —PŻ<¤éŠôýBîŕ‘ťe˙ ;¤¸†$N"vR«Ň¬>ćŔŐ9°…=ő’ńSMŐ*÷Ä…ńBhĽ”ÎřŹÝ{“Ý{CĚ~˙9|ť…GEOîYb—Cn÷ű€R1»´íwCKĆѬýöŰë™[•b§{Źe~ŽJ«łšyá·ÎŢϨ~+÷BřO+Şü»xĺśŃĚ9Cµ{•{ sOéZĄµ(eÓXĐ~ˇ‘„]Ď·*$ĺ’O äx4óŻD­ÓBLŃiµČz‡đö©Ą…2µV»Ůě¶e–§”š©Ý›ěÝîÝ»’/쌲 Ń[î»]IFk¬é9KŐ¬‰ÖđâőOâ›ĂV›{ĹëH˝Híó›ďáÍ÷¸Ć%Cµ{ßC&ů:Ŕá׋hĆ›íG­ÝVţ­VŹţżĽyŇä’ŻˇäëŘR™đ\Óü¶d!˘«ođµ˙Kľz“Ŕ/sĚâűŃ$–†öŔ™Xă®Á!EĆFZÚnŠ,Ŕ#Yú(ý+Q<™ŞGŢÚŃšmÄ&—1Ť ÖĽb|¬!ÓłXąa$ PyĄ«MůŔůűË׌‚j÷ěÝ{ýÝ{˙/˙®`ŤŘGÚQpN*ŤĹhźéŠÝ\?ĺp®ŐĎËŚI«É„ Ě®™ůd~Ą)OśĘč[V&EcĎ•Ľ˛ Túż‹”&ú•Ćʸô”ţ2‚7 éćK’3ĆvČU~ő.olŕ¨ÇŤqś7żĚ*,°eDÍnž7Ĺ×­-µ/ţ'V.Xčů=xţűđŢożÝ wNď¤ÎK)®‰•őÔé‰hąo9ăDŕ{Öł1Î'ř`qWúšÁ śˇ=WFHpÔ*řŐ˛,řĚ[V¶ŕX90Čă‰}ĹL¬b¨€'us2A“UĂńů»ÁÓl¸÷ź łÓŚi@ď¨6[’ńÔ–ęŽ&±™ńsąôă*żŻlź.ř¤ĚZfS˛i}/znÁ)$dľP*HŐĘťHµ2hŠČşPs˝ĘŚ6RďdűTpŹäđ;č)ŚUF Á—®$]†5‚Óúť;5u ÄzööĐPÁ™ŕ™.¨'ů«ti4•}1‚“ŞkďxJy=•ÉÔUŹ)M9n#¶ p•ěĐBL¦ Ëi­lţÇFÇ7şŠ·=J㹡TVź¤ĂpRĐý ś(6¨Ţ ĐȦE™P??µ37Iµ©ő §6§«ÉěŰ5EČţ ďVëyďO8ĺdš—ÚK1p3áľQU-·±âí˛* üee lů±@—¨1AyâŃ:~xâŁQĎhánŻÇ|ŹĽżÁ&:Í#l ;v7öR KX‰µFX¦ŔV•yÓ·Yq,¦÷śˇĘ&jهŠ:m¶`Ś=ű_3kĚ7»–+Uź°ĺ„Z9aŹ„;†é¨.#Ď3-@2Uq,u‚— úß9(é˙VúżsY×ZęŚţ#ѧ›¸/~EŻwd’GCŕŘ/9áśő'ĘĚŘ“¶ÎčGT»Ź>łQ(KxŹ|üŮ#Śk1x‡9ČZĎto(ŤOww]ow4Bsě<é[4ÓŤ5ÄSÓvů9›ÜĂĚč”Xżzť°­wěc Ăl÷ýĘ8Ł‘x˙ßÍ4˙§Ůd¨ůÝg){fé†Ňg4\öMHÍPôg“) .]Őˇ$kNE1ßÔt~M„: ňN C¬6 –š Ö}ńËTëGč–‰çQ8|ń¦Ň!ŚŘzüäe’Q#KČ ąóDΫ"+ÔŤ €ŢDµÓôH?…jŠ=ä @ĹuÉŐËP„–TCÄÉ»–ś.±;UŐQzRµzó Á–”jŰ{ň_ «-Z"äŹňŐ,ŕ;’XS|ęůÎ9l‚Ý]xEîcż›9ăˇ4“™ÂrĆ^SéST.K©T^łaбúΖ.ŚŘ©·Ü;˛qłFi»4BźšÔŞçÎtĎ!I‚”“ĂĐĚÜË €€¸P`|ćí˙<üOńO6„cďĚń˙ř2Â˙č˙˝}Řů?â`CíĎMqüź7˙i3m¬0˙ÝŁrţ·’ćĎ |:]˝ŤEńş­N|ţ;­Ă2ţËVŇ-|°´ ŕÄĂ,˛pw"Ý}ąâŢ›;D´?Šę^8™î -7Ŕäč/ĺäoçz÷áNTű č §ä•ŔbÇ&—6ßaÁ;V|l§±s}ŰX·<Íß˙JW@‹ÎÝÎab˙w»ĺţßFRAxňwwäŐ*éiŻUÇ^µŢ«4DŢ»úܲ˙=ŻđĎ/íLĄß&ĽÍnwŽ0h@łť_a^ţĺŰhµ–mKĚkGĆW7ËĚi#;wáúżYŞţo Öb*Ń98(PFîÂő.U˙aTI §řß ‚şR‹řżN·›Ŕ˙íŁV˙g+)˙ëiŻUŰ:Íöá7Ív¶™±ółÔójĘ@ßŮŻëĺ&ŢF*´˙%ť¸±ýßŮOí˙ýr˙o#ÍŰ˙rÚk™¬Jöëzn if$űuI˝·šćďt4ż–č‡Ň˘ýßj%äG‡GĄüg+)±˙?78eÚrš{˙3r>rýš÷ËÉ˙ńţżŐę–ň˙­¤ůóŻ‚»­·V˙öQ»ś˙m¤ąó?ń\ ’@úk`ĺçżÝ.ď˙¶“ćÎ?~4˝i¬×ĆBţďŕ(…˙;%˙·•´»Ëľ wĐk૽ş…G5ĘţŐÎDş2Be­Đ·Ą!Çź.-Ü˙gh‚lűMżżr öűđđ0‰˙[íVą˙·‘††ľBgCMűĂÔr‡ěâ†Ŕ;t3W;;{ţâ‡gggőúNdĐśąÁ…3 ą,ü Q•ŃÉî§‘ĺ„T:?gýpi‘1™  ÝúkK5Ö˙CU9Ôl›…©!<ţůooĆ6Ćäň9©¨Jo>Ń6H0LĽÁ…UÝ9·'˘ęăZĆ‹˘‡Ç4EŮčŐôް#Ŕ šj7Č™±ödL6ăúÍhšĎ9ç¨ű0ôÄ'ţőiUúźżřď—ĎÄĎĎţţâő‹7ä‘´1•q †§<'ŤKO餓-YˇÇĄ:Xď]kÜC/0 …ţő›Žţ~öޱÎpôşĂQGźmÖ,°©.ҧíŰÖ{ŚY‹ó ˝i„2–ťě#jKs¨€«löíÚö(ö–´+¸#ž±ú©Ćk3k0p†čo‘"9A~eÉ+Á‰@…—¶íĘş°d(c_,ĆF•1ďąďÍ(^.L&ôÂA…$eJűŞâćŘű§ýÁöä‹‚Ô6ăôd~Ů[ Ś  xÉÄŔ§?µžľ®¦Ę(âÎiąWgč®C˝7pÖj=,}Ł`“+<ůŕLjÔ;l~˙6·˙çâ˙¬¦ č.Í˙ĂéożUň˙ŰHsç˙~k‘~J č÷ ›Ň˙ %ýßFzöăßOŞĎúŰŮËźžüWőT|út,CěЫǿĽůéÍł×oŕUüĹŻŹß<ůţgx,đE¨{KUÂZU ÷Ťůr\“ ‘=Ŕ8‹#ôö`krřxÁq‡Ń×ĆŻ´ Ĺ$?“9z€~7ś ŞÇł©¤l“«Đ˝ÚŰá×ő·Á׏jč\Ń'ň SßsżÖî¶›ˇwćăcŃŞ‹G˘ňż{M^ígTo°ÇŽGšS÷Ľ"zď±^ůó8`ŠQMeśąď\ďŇĹśŐeIÇĚěŢîĄŘu…ěäî.w«zçcŠ'Ł7őë*9Üů(» żá ÖuA‘ÓpŻvçŁ1®×âÎGŮčµř·:Qš!ćÖr>ť…úţŰťŹđíú·XFfw×Pýó9#Ĺ<:AřH°…$é7ŕ‹‰ ±XBť5ĹTđYÓ °Ä5űQ!pU@á(xX«Ľu+őćŘ’^ĄB™Átz\!îŞ'~ô_U‹“;Ńmr&áú´bQęäJ{FÖЧ v ˙Ć9.‘kâčĆź¨Ń±8ţ_±÷OµŇy{ůuóţ#zyíĹźMmň±Ç€0ăLŤÄGŁôŚÖäĆBÉ9) îSÇožŹ˝~­Břúţý˝ű4G€˛j®[׼¬ůŕ hôiěĎ8ÖlŽK4â~|úÖ…!ĂnņŠęţŇógÓ|ÜČ q˛Ĺ'ń_ü¨„u+¤4Í˙ÜypŘ8ÎĐô®HžyäÜĄă Uůň WäçS¬‡|T]óŮľkŹ›Áض§˘Ý<Ľ(9NşćYßc[zB›ˇ›oŠ>0ţ oڶТť±ňí-{µ—őÁÍ`ÖŻU#·©{xxЏBő°Ţ<ÇŚ{oaÉÝÝ,jýUçÔČ'µDŤęἠřÂŻ5Y¶É+sWEť6čdHÇ­8š OZä)˝dT}Zó~FM¸ŹGŽ+Í®ősSöbi1˙/çeŤ6V¸˙Ůoí—ü˙6Ňâů—;}Ť6–›â˙÷ËůßF*<˙ät54°üţo”÷ż[IĹçß÷Ţ;Ă•VŔňűżÓm—ňź­¤ÂóŹć~+ĺçż˝ŹţżĘůżů´ÜüŁßĎW yŃ6Č˙ZÝNB˙łłß9(ĺ[IwľÚ›ţ^ßq÷l÷=cúWwl;<ńpS×Očß±‰k˘Öăwő¦k_Öz4ňřˇ¨¸á´×ëŰçŹáŔ |=•rE|ćEE:IWţŃŻ„‹"˘ńřŞ˘®’¸Šë‚¦Ě}|,*ŹéńIÔLL¶óą‡ő‹IËím˝˝ X°˙çKě˙Łny˙ż•”ťŽA”ÉČ>ÚĽ›_ŻéÎ…QŃCµ‡çŚ#Ž*Ćĺižö@ţŕAÜţ"ËÓ•T%r`?sÂ’šîśT(şÝj‚®5&Wě… 3tr3b TÓŢäŠAËĚaVećĚďűŢ÷ísűCVč55üO¶ćý»©v9‡Ů°‘3Ő.yĚh›Żń”nGµ qQ ˛2…vÚ˝xú g„ĽD´Ěw´řťŔXšt]ř¸oůń÷ :˝˙'Ľ< 9jők 2,d=ŁŰ˘š‚űţj=í·\»ä˛Ěń™H=ŮůË2{X˙$Cú6¸ŹyôJ¤Q…%sŮ94őç­`:ČZőŃnÓ`ńv3łÎ[÷3Kđ暀LHQŠkŐâĽE„×e!NŻ" »}%t»Ů H!fŤŔŘÍŚ»’t¬G˛Ô«z±n}çAÉĘ%Ďtj#˘{ÎţHtŹüĘlĽ{š¨0ůŘNĎTđiÔKJúÄÉZŢśIŻ^ű_0-ŇŽtŔcťQK.<[Žb#ďź5-#c<üĎŇ•OŻ÷„ßá ih˛™}ş+J°úzoÉř\˛%ôx{¦Ýé›áÜ·Ôýy{Ě0‚ g*oŹŘ •ŃYş”Ą[ČýIb›šŹ÷fT'ť言;îDyZ©ÓDRVŕýÎíPgŤÂ'uíň6 SSÇv:łŚáŚ2dÎa™ůł%Ý:ŠÎâĂáJS¸`ck.6¬Q×€ ŻĘCŐź:-/˙Ő«¦°4pyůß~§[Ţ˙l%­3˙(,*"ţo·:í¤˙ŻĂĽ˙+Ď˙7źŠś˙ŐÔź ĆVäó’óRŮk=\ őH|¨‡š ĚP!˝= V*ÜIz—ĄYN®ńzúSOşŽ˘,N=őhĽ÷{U…ĘVB Śťlj˛ëş+KgáaĚť0ŞËtjMúµBü {şt9ôööŘvsŕMö˘˝µ—Úf{XëŢQ—¶S kF÷ŤxⲪđLĆ+â?B~şB“i“˘^ë’ÍÁŘ ěŻTVMóăÓŐë=×T?vJü{P­šü3fąV'<Ő¤š_h2>ŐÔ’†(ÎVĹĺꏴŰhéŮŚÉ]Ť)zSŰ­E V/«¬ş3şř¤ű6şhRh‡Á­ŕŤ˘hČYŤ1xd0AZ)«ŔZFB5mđö’»Î8pŠĽ1äc ).tZZ´ču#şF0G-ŃKŰě <ę®ÔY×<|㨠HÎ8ľőIŠPnţĎłů…–+QJ ‚Ř«V˝54ž”)aÉŠ ËŰŕ>ÉŠĚ<룕äjg˝I\éE¶k0 ăĐRxÇ‘öŚ…¦™xC–md3Wp3żuI6¶.č[Gţ¶ţ¨¬¶Ë ÁHr«Š…‚Í—©ńhŢ7Ą™y;]‹(ĆĹ:•9;ötŚ’¤HË”¤n~”WÜUŃ_|hxKÄ@ÉÂÎCV€…|u‚Ł(ĐŔlhX¶2™ĂЉ Fţ@@'` ’Iž¤±öڰø16u%Y„ ŤIĆ-Í(Üřbɤźľ=6Kúů…0Ďđ;Wˇk$ Ţ8KM«ÂŽŘS3ęȆ¶LÍ'ŔĐ÷®–Fs©FĽc*"^čÍŤä7,:…G7Ú]Ťµ6Űá2Ěé2ĆRňX:2<¬ wP}˝ůŽ–ćĎ—–Ő˙^Ĺ%ŘňňßîQ˙g;iőůW1k-˙v§“ô˙tŘ:*ő?·’î|%R  b÷µ ĂOC4îčD‹t+>j]T´X­ €ŐőM=.2ôW˘|—!ŁkŤ&eć'~]J\ŠfgµßZ‡Ŕ‰©…ŘTM×*Đ@Ąn*‰ŞWgř&óNVĘUűLÉ8˛Fĺđ·p0nGNj4LckŇZĐAę@Ól­vr©Ô2ßQ“őz&lŔŕ;ŇYđÜ  ţîlŇçPĄ»ŽľaÜ —žŚÝ1ň˝ f±|ßşZH- řNŞVµQíĂżAő´ŃÎŁś ˛”Äă‹Nkă˙böźó𧛊˙vxPĆ˙ÝNÚţżMČ.ć_íŁ·Ź‘}‰šńř;K#ţM`ýEž“ĄÁ žś6”jzí¶62ŞÎk-­Ăޱ!¬@ÚUůA¨/ w{Ľşůôt<*LS@g-WŔ Gµ 5Ť“Rü{J… jA(E0ëóˇP‚Yô¨ŔÄI‡ě[đńJ™ %çýŁşHńÎľRĐ\XÁEaX> 4‰ YĄoř6 o]q=oceu‰Ň’AÉHkĐďŇĹŘ;Đ˙:ę&Ďín»¤˙ŰH› ˙r!Ü@B“Ǩ×[<üÉ&7röSc]ËëÖ+Ź_?:O8V+“Ɽ7d«‹a#Ą)¦Ü%€B ¤ 1ä\!Ą(%•7’VÇ˙äl#÷˙pLÝ˙–ö[IŔ˙´6ü×ÄýIâçwŰĂúÔŢF~v8óáčÁ:ż&4ćI€ĄšË9cÍ‹Zg `jh»»Äsˇ J żÝ´:ţŤ­0´Ý`ţßOĹ˙9<:č–řiř_.„[A$,94@˝Ý-n‚¨a¶ÄŔĂhç(L)bŕŮÝo,DÂ`ş+ŤJżŇ8© đcżěĘiŁ2‚/ç•ÓÓ\•*ĺd1]ŞřŃáMťľ:!a) tyce¦5đ˙ż†î™ď…ÖÂK ÖüřťýŁ$ţ?:,ĺ?ŰI›Ŕ˙ŃB¸"  bďW€ę\ém Îby˛A¤E¶ô[„j»Ú¨vŕ_·zSĐ¤Ł iWŔ0ÚjJ’÷Ld)81°\Dú–É›ąq‡ö •h–ęŃímbÚpmFô~«°¬ä‰źĚŞa$~›źń^ű(Łó [r¤eg’›ľŽ˛ňöFŞ<‹öyšĚŞeŹś2ż®ÜŤÖČÝhÄěźĐ` ¬č—*gLĆSëŹÉÍ„12Vi“DÓDUvđR®†W‚®9 ¬O2JÇ)ÚÖŐĎČ YsÁY7Žéşš ĚSĽ÷‰EJ˝GщĂD¤ú?gÔż•ÎeĺË0ŕţiG˛yŚFťĆh “{XµĘS'Ŕ±Eü){,:ÍĂćXľp¬ă'Í÷¶ B8ţ_±÷ĎÎŰćáŰţ^l”šr˝'&ć¦řoCË BţwßW—Yłtᡭ%Šmv¤˙l|n™˛ÓZü?śŘÖŹ˙Ńž:ÉűźĂvi˙ł•´ţOöë] %8 °ŔČqÝĚČFŘŽ˘fŕ=ÉŤ3%u˝k$íiÖD®dŽç–3&Q Śđ¶čĎÎĹťöÁvgiî%čňř~"wŮÁ;w VWD#ĺUQQu”P_äëܸ1îň2Ź_V »ák:wÄBw‚®§oŐ¸ˇGé^Żoů•†¨P‰Ű6,Ú¬…2ňPŽ,(ĽíÔ!:o|řľýv·úěǧŢčŐ/Ż^={Ź«z„ř8ˇÝëŃŐâG!G»Š*ßÚ9=ôĎŚgCŰČ«_%ć'ĘŃ뙾ÄÂOyżRŐ›hFĐűZUîîˇĂëQŞ˘\âµáĄ-g¸Ő¨ŕKt0ĆŽfÎ˙_™ăYţűö¦ü?í§ü?”úŰI¸˙Ă…p;.ţ’ś?zµ˝«>lnw|4¶č)îrfH ĂIŲ¬~őŕs0 %áжQăCŕł\ĹëtŃRwâĎ–VÇ˙Vpćŕs†ňŚÜŕ8ĂRk5ĆQXŰuL­á(}¬Šv‹5?–­ălěĹŞQ$K×}X ę„E?Fďlâ„K?Ă1ŚjSçüüją*°ą`Y×K¬ĽĄWőR{&ą®Ó`ëŮ㸠Ńäç6˘KŕťG”żpŻ2›m-×jkŮFőPf7ÜY®áβŁĚ ‹„EC!—Sn‹r@s2çrCkK­˙Ť©¬ËŹi¬ĹľsŢwüa‘UÖ…¨¸<¦,Hkň˙Ó3…ÂçH‚đ˙ťv»“ŕ˙1LÉ˙o#m‡˙×ËdsĽ˙¦xř´âüűŤ°wŐ˝«®ĘŢ4'ÉMušíe+Iń3«×g®’śbV¸S"Sß™Xţ•P¤·¦!ĂĚDPćS¤” ąÔfČ­x›ĽwČy7×QŇGČzüň?¤ĚǻץĆßź:­Ž˙ťŕŚ–d!˙ßsń˙ţa+˙ŹÚÝŇ˙ßVŇđżZ·(hrč€~˝=Z šÜ 3Xr ©EUÂ#PNÚ@ňiÁbʰěÖ€ˇUŠ äćş;Ř‚0ôGA‘éžę/ä’v-ü?ô&–ĂŇń÷?óđ÷°ŐNé–ř+i3řßX·† 0ĺÓ3ÓV)‚Ńđ Đ EOÎ}úxE†m‘–]Ě­ňĹ@ŮRĘA AÄB¶ćňp47 ul@dൎQs´Öě çŽľĺŻ8GěHJĺvž@„˛’+ @ĺáę"`ť A0ařc‚Č‚‡‹˝χg•Éc€€Áö‚Ľňč}µ<0žoąçöę+Hł=±™3¦íâjza»+LŰn±yc¶¨Č"—â…9ü V‡ ŰE¸¸ÝŃěŹ?ÖĂKÉ1$ŇĄÁLŘĺ†oMhWş[ŔP®Ĺ˙ŤĆ^đĎ‹Ď˙Ýôůż}TňŰH›á˙h!ÜÎŹ ÉçůřőVą=jňFř<îLD%A¨´šíNîyw‰c˙2gmŐög퍳úęąí/Ó|÷/|ÎĎKká˙‚W€‹ĺżť´ţOé˙c+i3ř˙ö\J`ň±˙–/e‹7(ű-,we8>V¬ ŢňUúđ§“+}]AĽ„ZŢMĘČ,ąRč3m đZř_ŇŕE$`!ţß?Hň˙ű%ţßFÚ ţ— áÖĹćR•a«„@6z瀥řa ÇŽx©“@áCŃr3˙˛ÔI` |µţ—Fů±´ţ/dţ˝P˙?˙—î˙JýŹ-Ą á˝n Р͡QžíÝîÍĐ‚ŻŢď+‹âBŃ€§Ň†ýßmîŻA éÔl`fÚ* `4|ú?ĐĘ2T,TŐęµG= »G#řľ ‚&I Z\.KNćBw^”®lŢĆZp÷K ÜZřß…őî;őĺÉűźŁýňţ+i3ř_.„[ű%<ůx_eŘÎ/‚ňe›ź]ü§áXKü·‚ ‚Ńđéß é DŤ—Â?#­…˙yôj,Â˙ř2˙»ť˙o%m˙ËHĺ·ýK¬‹ýĺű­2üÜćŤđúĹ‘ b>\/EytóE5±6vńµÜěl¨ń%ÔTëJáˇQUz¸i ´:ţ˙Ýs\t\ś…Ţy:É-ş˙9ę$ý˙¶»G%ţßJÚţĎX·d–C˛r.MP¦1˛/mźéCxé-A!2 ¨ť|ĽÎ #.ŇÄb%pĎ}Ű 7 pC®†6ţ›«i ú¤g!Ś„ťDÇKÁŤÖЧs#b Âr€öŔs‡„G Ý Ňüku@Ű+ɤĘžLĂ+éůäÜyo i=ćBVée2NN3ÂŞ¨Ńj@ťĐ*Í !±ĹjHŹ+ťă‡Ľkzý㇮3žŕ˙Š“ŞŐkĂRíô¨Lµß«ž®Ě-¬I˙7ă˙©{”ň˙tŘ.é˙6҆č˙í!řs(ü|ŘÜ&Îz4¶ÚůµĽâç=†!vćČĹ+xđ¨X˝~oPů żĘ´ţ'b¶üßé¦ü˙vŽJýż­¤ ŕâ†nţGHrđ?˝ÚţÇć6(ëÓŔńp“‡TÉc…"Ĺq vň§Ěî _ sK±"E•Ű"WÚĹŰc›ş]µŔëbb… A˛ěs˛ ş»wÜ‘Gîkćy~‰ůl?ßcµą<-r˛k0˛eZ)­Ž˙ÇȆŠż˙OĘ˙:‡‡Ý’˙ßJÚţç…p;(Ă’CäËíQnpt@ŽqaŢ_µ\bîUOßVK–˙/›VÇ˙ëC1öaü§vÚ˙w»˝_â˙m¤ůřż€•p;°?’úńÍöđ>´¶ ¤ŹŃúc ¦)xaĂč?‚ «áŠ5î[ˇďAĄQzçđyáASM€¤=ü¬xÓI_pYM6ľiěÇšů¦¤-·:­˙m\›ŕ˙»í”üçŕ°Ś˙°•´ţźÂ-ˇKŕ—[¤ÔŕŤč|ÉAĐŚ ‚…ÇPňRŔÚŚ6nCl°/Ëâ Lëŕ˙|®Űoťŕ˙n;í˙kżSÚ˙m%­É˙ H ĺ†Â˙QÝ_jđ?ľ ý·0ÝäŠ\u›Ň޺ƪź=$ŕ˘XvĽ\ˇß¤rµ0xťÚ}Pť<[vŻXě®mF•S]®}Éţř2,+éuާő,Ő¬ěľĎ\®Ţ64ÄÂ+€ţ5ťG3S5}äÎŁ–a řĹ0멯¶«28žü˝ůýîĐŰ ß¶ŐouǵrÂńüTďr‰PQ*Eŕ‹çędä˘ĆN ŽSĺĘ„ đ2Îtś1ŤGˇM˝ÖcŇ©ĆwŤ^gôaěćĂh ö44ôěě9á ŁEE˙źš¬÷ÇëÄÎ(Ů´Ű–Öŕ˙ŠŞ˙-–˙¦ůżv«Ô˙ßJÚü÷¶č˙MrŐ˙&[Őţ›lFůoů/6\PţĺÚś ›OË€÷Ëcú­M«ăX ťľçŤ×Ź˙Đi%őżŹZĄüw+iň_µn PĐäýz{Ô@5yrŕöBÁjÔx%Ç!ÝRŃrZË4ă’çFÁůë¦Őń?=ř=đźá˙vÚ˙w·Sâ˙­¤ ŕ˝nĐŕäP€čýöH€ns“1á˙óőO?r<řˇ’fŕlŘ:ňĄFsr,ľýv÷ŮOŻwŕ\`ŃĺZź®×Ęé>ź‡Ě ¸ń3ď.î¤ UŁţvżźPuő´důżŚ´&ţż˛&‹‹ńR˙ă¨Ű.ă?l%m ˙ăB¸EřÁ™‡˙éý–ń?¶ąIü˙ŹÇ/‹˙iN4ţ߀¤á4|’.†ýjü,±˙ź0­˙ťÁ»ÍŘuŇöźĄţÇVŇťŻÖF˙°n ćHň>ľŠaÓjB˘rá}ďĐÉ=G5GĆŤ5Ę#4x×ĚŽSޱ{Oův©öâ®m䣀/aŔ’ †|ÇÄ{oËK^鿏ý’hŤű·Dź©­ŢĚÚŁB­pÖ5[r\{¸Dk}ůmĹ"¸žlËĐ(ŇÍjíŚĚö‰'ČŃAQ*/Q‰©ď˝w†6´ĄÜŕP“Í˝•éíř߇!+d´˙·Ňö?‡ťV‰˙·‘6Á˙ÓB¸%$€`É#ür‹l?5¸I?r°I«^µ‚@Ů×^EGgÓj>7?ŬSĚ;-Yů?{Z˙S܆Mč˙źÂ˙űťŇ˙ËVŇđ?-„Űţ9–H6öçwŰCţÔŢFq?Ś):ë,0E6Oö4ůV4&]hT‡şĎAę?/C`Čцî,]ÔĘnűŇóß±3š©5é»đ‚ĐEż4cç]Z%Jk* 4r ÂV»ŇlŇ·v ÖFčťYYG¬,ŇP%˘“€ý/]yŽ`Îö¶řĂö=ęĐ9G'‘č+’ÝdgĂŮj6Ű­Ĺ µZ•†¨,€i9:˝ţ·‡f ň˙óő?[)üpTŢ˙n%eČ j}ňü߼O°ä!~~ąEĚO nőÓ<1´U"C§ÂF• @ŔuX–Ĺ׿đ‰7Ŕ@MŮ6ŮZâ›\­«PĘsÁź"­˙Ń«Č `ˇü'˙ëđč¨ô˙µ•´ ţźÂmˇL.)ŕ·Ű¤ÔâfŹsa'`şm ÷r~áüţnśď lüîwçâ|4 ¬ŇŘ_'­˙7ć˙±łźň˙~xPĘ˙·’6˙o‘˙Gž˙GŰţýŤůô—ô˙¨Z&Ě/DQ˙Ź™ŤÚÖŕBą^Ę©‹ xćx~üĐW±[Š’m#­Ž˙‹Ůh4Ţ˙ß=Jé˙•ń?¶“6€˙ĺB¸@“CÔŰí‘Ůâ&h€gEŔ˙Ff»4+( ś?Ké5™Q!ˇ†LSްőg!áů(ăÖ,•?tĆcÔe Ž‚$”Ó±Ö*8ëĐ^“"źT*qsg,UŇ”dZ˙ĂĘŘPüŹv2ţďa§”˙l'm˙ĂB¸%ČŃ]ćÇW[DűĐÜ&í}Ő0{KÄűeć‡úÍĂçYíeűąÝB±n»%BţĚi ü”v3řż}”ş˙-íż¶”6˙a!Üüäá|µ ţGoKa~h´ä«Ő °ü8´Ĺ/5Íw@ČwIIKVă…© 5^±ů—ČU«?(ďnWZ˙˙kfŰ…Ž ů˙ŁvĘ˙O«Ô˙ÜJÚţç…pKH“GäŰőťeČ·¸ůŹçâ(Yµ-ő~PĺgP;ç4=ë`€K]hPâ°ţŃh4šK¨ú¤š:*on"­˙CSţßRţZGĺýďVŇ&đż\·„Hhň(€z˝EYlr“ľ¤”˝ŠžŰި^Ž_ca I…ňŻô# YÎäÓm¨hßăó¦őđ`ŤC{\X€ąóéŔüßí¶âřżŰęvKůĎV҆đżąn0ˇšCbŮ6Ev>ŮdÁly9ň räöóĄ(ÝëZyşč[>˙ůc)YWěĘĆŚŰ•« =)\zţC#´»‡bpaůÖ€BaŔdl»çáâřçé^TTÍ Ä¤ÚĚ»&ááv#/WŢŚ FS9ĎÝ•dR·´üX·;Ýý îąíÚľâÚxoŤťa¤;âńxě]˛ Ňu٤žŠľŘCÇD˛ę-–Pżrâz, ŃkĂž„¬&đ˛“ÎĂ+™Ăŕ+ýä{hŚĘbÝřZŽArŇj6ŹNc¦˛|Ólî¶O O$Ç3„ďí8Rß9®ĺ_‰§V(­řB˙Ě€ă1ž”kř 9v\;¨7§Öŕ]­úýýj]ç7€âüęAF ŕ°7Ź‘ŕ$“ÉhVnĺČóÂ"‘K^Ź=¤µKŕ>uÎíÖÖëďĂü4‡ôł¦űôµQµą}„LÇÂĚU^oÎܨ'­Ó4ű‡:6#%xci-ţo:“ŕEç˙¤ýĎQ»Ô˙ŰNÚ ˙G áÖđ}M>żÇŻ·zţ§&×:˙Ϥ±÷CËďÂ9Ŕ:—ă´˝—ľGÄ` mcŕć0E´Ę˝¤N]čť9‘ËvńXŻvűÁA·}p´6`$híća<ŽVĆFúV´$i-`(‡ČBĐť;ďáóŢ?vďMvď —G•ÉXp»·ĂŹű×»đŮ‘źw÷n=ĺZ ˙3˙Yl˙ŮNú?Ü?(ă?m%m˙oĘüg}ÜźküłmŰźŤ™ţ,iůŁ „Őá<Ë|_ŞcüĹÓř˙Ňš¬"â˙ý¤ţ÷Q»Ś˙±ť´ ü/Â-á˙%4yd@˝Ţ"%Mn„¨±.NtëËúî»ÁŕéÓ|šđřqż˙äɰ$ ™´:ţ/&űÁ´˙ď·“ú‡ťý˙o#m˙ßŮĎąĎĘ2źčĘlµ?lH <á|q!ĘW¦ 9Eß>łÜÚ öEöu’Ą|“Ö—!Ío:Klô&rkŕď¬kˇ˙?Ď˙#¬„´ü˙ ”˙o%m"ţ·Z ·„Hhňz˝˝€jr“ €ďľŁçĹČP·]هBů† ­yJÚ˙®7 Ĺw¨rxaÉ–×nČŐfŃşťÖµ3rhýf……n »#ćAĐĘ`čax›DĘ –z:ďí¬Ź‡w…ˇź‹w ëŔ ÜjćĆďţĘČó*›đ2DŚpŐĚ}çz—î2đŮw+řf ľđ´˙w5ÝĚůżÓÍ8˙•÷[I›8˙ĂB¸×HׇݶČńAst˙!5, †Ââ_†¤ W™ë PJ!P+1JQ@%iČîIňo)±ę ‘†ilpđIAx>V¬ĘńĂ6ZË?ě\ç‚U.€C†xH Ž|Zt¦Úůó#kZĹhěYa|8čQašÝüXUŞjhßc[nů° h” ¬ŞĽ¤Ř@ZťţĂËÍ q‹ä˙ÝVJ˙ç°Ľ˙ÝNÚýç…p;„? K _nŹ ŕ7ĂP\Óál:v¨ÍłÂ/Ę (x*HWó0˘¬€„}Ő(Ga ś…•Ţo ­˙ j˙°˙ď¶ŇńźJűĎ­¤Mŕ˙[¤ý3›§ű3۶ćĎlcz?ł%µ~fZçgžüÝ“<… ";hu%^‘'i* 00ݦŕĹ ˙{îřJXFm¤śă:¶;°iy6ăkĂčÚźą¬7C.QtëÍ(›gFmwřéńÓłWŹß|Or»áĐQî^:Í#(€ľB&Ěżő(6ŕěŔ&¨ő€ŢN=wxzŹęMl˝Ö‹÷­Ţäkˇ@şt“őáÄň7c7ĺŚOřó“němU»ó˙4Xx]WăŁ=˛śqĹś`Óńىć}šž`§ŔA)č‰É—‰4_¦«‹ú ľĄ—«śĄD†KÉäŘţ|ÝOٱę Wů‚NVDjŞłüľunłzUô^Ý”ëŚúÇ ţ§ŃŹ‚FůŻIr‘Ă`â.śôŢĐ>…“C%b­«¦ZŻě¤qdŰľú˘ĹG ¤G ±'Ă—X.ű’Ű3yŻŐ:ÚGçq1·Eú«hë=Ťő8¦înôu2‡Î#ŇҢ=Fgt¶Ůĺd‡żýv·úěǧŢčŐ/Ż^={Ź«ş—w„Bžq=‚q¤GUϵхfx ěKô>1~XC ©~%“Oł‡1wRnˇiž/á\ćXý1ť6ŇyŞv^÷ćwě¦Ę_ÖgĄ Đ˙…AĐ˙n7˙±Ű*ýl)%č˙ŢMŇ\(7Lţ9ÁęÔ 䟼ßo“ú+ŐŰäÓ¤źú™GůçP*ľ˘DËňűNčŁăŰč:i!ŮŞĆ!­  ą×jNŕŇpţ°WéůĽoNá~Ľ§A&?}Űv ,Ă Őø.ęEPZůŢó kę0&Ľ=ő[ĘKŕ*hGo7üsÚ@ ) ř7ĘŽNă;*Üy-keĐŻ*t pW3»§m…ađâ^†Šw4ÝłĘtř° jL­OŐ?Éśž‰˝ě«±FaŔ‚ p›¨=-ŤuWëěÖ´¸Ö7AMôŽ®„PKxä ®PĆ>ű–ÓłRGn'»}ëŇ&řZsîéîwS÷˙­’˙ßJÚćý?/”›Öč)WŻ%ń¶oýQ\ `Ł śyŽ @1íČŐŢ”0nHsEÓ›[?ę7ˇ5±`äoŕFśYĂfFnąPť¨OXu‚ô%ŇúrÔnH_"©+“0vN8ľĘU›µzóLĽůţĹkńňń?üß=Oź˝~ńóăď~xVż˝ cnt¨yÇ…~n±ëY˝ěă-č ë™Eku=â˙ äZüßĚάp]˙oťTü÷Łn«ô˙ł•´˙oz!Üą/“/đ•ďדô.ă˙M·ą_Đ™˘đ$ôŻđč=Ł×ľĺžľö‘@{S„>. ….şrŔ|Ú8©twŰR4»‰/"kůöÂŽ¤3 #÷ĐŤ9qN° «ÁĂäOR99ÄkŔŘÚťĄěů.Ľ@â6ćŮć8@ą?Ř‹CŠĺ‚}RiU,™źú`S Ó(ě’Hqśkv˘QÂ?ţŤŕ߹ѩFe÷`q×Tá/ŢůÚôţżŹŽŇńşĄţçVŇĆč˙m"ţs)˙ÖÉţAĹpe±(G †Ź€ąŽVŰ„áK‡đ|éVUĐŁ;âř•^ŘĎĺd·TujÉżÇHX҇/KÎG Wą˘‡µHytvµ^4FRá/rÓ]žä”4ý˛j×´/Fćm\XÝPSÄ~ G®k¶[Í.¬hăŇNÖ­ně‡Č‡x—…>„Ű„ħâbz%@çw>ĘlŃ•–'QÍCČ{t»@Ëóá¬ÇW Ś„Y ÂY? +é™9&ŽgŠ®Ú˘;?<¬ő»ç7&Ž źSäbëĐUµ†Xáa­Ň¬ÔŁH/^ÇřVĽ­ĚÖ Î€b6 :ăóŮĘh‚ÔĚ, z˘±ž×¦±TR ËEۦŠC@ʵO™ňˇ ×…aŔůIŔ€Ź€™ćŔ€Ż‹Â@«#=Zeʇ^Ç`ËŢ俢$Ť·9âĚôVĎŢHËo"ąë+h]#tý_Éi%gőGŢibuFë–şłć(¶Ŕűöˬ9¶"×?oĎkĹ\sZ™łDóZ™ł0ç´4o!&Zú˘…źe*Î˙űžž]x“BăiŃýďţŃQ‚˙ď”ţ¶’˛9}ý49ůć!€±Çčő~†,ßCĆ*šüŢŘÎ/:Łd€Ş–…üŃűĐkÁŻgĐú.Zööz{JPÂy•dí'{x¦!˘:ö*,>ČŮ&ŻóČżépö{ ăXTÎm˛ÂÄk¨K¨Z¬DäN÷!»j9M¨#‚Ő jéŽ$PŞ1†cÇť}(6‚ôuŹ?qřúVÁ¤ŢÍD~˙…äÄxÁ‘ĽĎk‘˘ßÇC »NÂIÔU‰k˘Ń-8¶ŞĐĽńŤň|ác,Łb/7Ň7ŕFĚCnRN‡N.Č?Örcđçez ÓžńŢjm,°˙%z§˙íîaű˙Ív5;ýĹé˙róĎąĎ;$ŰýÍÜ˙´Süßaű¨ä˙¶’ŠńY“źĎ ˛ŘďµĚ'Ť/"ŮńDęg\›ĆÇ9áݤH7»­Fô¤nĘS^zĂŮŘŽÔ„g€Îá‘rŕ•OPűŃšČKC"ż ©ĆhHŐlJ90:˘ŻąNz@ʇŽiâÔĂ”Űm dćʱá›ôžÓ{w±6ąö¨{'§ő¦‹ÖÍ…JýŤMGî\LhČĺë7şFě·±@Q„?Íĺ‹qłşËŃĺdµŠţ ţ=otŚlÁq›×‚’ďBý®+]µÎ˘±˙•Ýł ósń?¦ł8Čö¬±cÎgŐ‡Đ;›î,"XD˙;ť$ţ?8Úß/ń˙6’Ćôtó8}0FÓćźh­îhGz(ś´č®Čż‚=;°PÇô„˙€zHÂR{ˇJ…˘»0Z4˘‡kEô8Kü))Ńô"…š?_ýĄ¤eö˙tÖÇ{Ü?ě3žŢ  pŃţ?8ě&÷§[Úl%ÝŻQhÇ3˝;•Úyä˝e<ö.ĹŚĽŹˇCÄ=wĐ”¨â ~iBC¸Ô©Ý—_˘{ŐŔzoĎdugt#ĺtq¬[°'Óđę‘xÄńŽs ôTčRŽ |gö{k „–«ÝźßxÝđÄLlĂĽąŽ˘UŢNٞąű?Rö“Ć~gĚĽXľ5)zřű?ěżŇô˙ Ô˙ŢNZI˙/Î4ěJ+űč8XÍX.’NXď[¨ëćÍü­.¨Ů…Ąc5ó쵳d´gnÓ¨´VoZîđĚÔá{,uůd§UrÓ~Ľ Ôf<ç¨nš 3ĆŮ C(1ěW5dƢ7őí38!ɠٚǯ˘ĎS<î -·Ň×Ő¤XRçĚéMŻ>N ôiµ!>^ËÎŃ٢†J ő"Ĺű^?Łx,tMˇć㎀sk‹i,;Ćë °í’CLňŁÚôĚŹÉŻ­čApQi‰ĺZç6Ęsń Áţ™ćFŻV×*ţ±Ę=çŮ‘}ŻF„++­ÓŕÜŠs NFĂ^ŤĆýV)˝Żcć>ř˘Î/é?ŹŚűŕ:¬]N`áý˙~;©˙{XĘ·“˛čžţoŮ7č~b™T7ćęG{&Ľ€Ý‡ţ~Lî Š.Ę‚j+k€ęB¤žf2L’”˘*Ç!rÉg4”gx´@újšî $éšÜlWv)ëąóŢŽ\â-î(5¶lo“1 TŁ+´ĆŢąRŐ¦ŔÜŻ^§#Ň̸öş}—ĂCÇ5‚L$+2ą*úrÝ‹<ëĺtśdS(§ śˇ-`‹Ë°ësáŤň"kłŢĂ(+ă|Ogß Ëwí hęąŔza­©Ýä,ž’“ŞÜ@§ŠĘů?€Óoä„Çą©x¦Z5ŮYýăˇE qo˙± _…ÇŻr]d‚)ú l>¶Ó†6qé«™R°Éí—bÉŠŚ_2­2\˛Ŕ8p .Lˇ94ÖÔŘ|Q”|µ´ýW3°YúĐŮOŇ˙ŁýnI˙·‘nćüźX.yg˙đ (˝ç‹Đ ÇóËň¸` BH©ĹÔzúd Ž:Pź\eţ9č5:VŐ›*5Ţý9Ź˙wdČ©‘ăÄá…oŰÉ3ĄY^,µş\ţaŔR•l™Ů*˛ć /Č2xŰ›‚yçý ¦9'|i,Ă|úď|a=6g¬¤¸Ľţ_§Őé–úŰHçź}V›ý•ô?Zťrţ·‘ňć?ÚńpŔ›Óém,ŕ˙[‡ťäýë sPň˙ŰHwřţľ'xöwîě¨Đ™¬ŰÇ3Ż›âĄ„WpŰá`šăCM2Č&)hĘ`"X?+D:–R˛€1ďŕH0D'…ěÉŘ1¨ ďÔ‡¨÷‡Mś>¸šđ+děý“ćÚôđ1]{ôC§Jr 8¬€1EYsŹ»$;ŢOfľ\ĂřŞ!†ĘŘT›^Ďäg)D0(€<•ĺĘ?’Ľ…ž|„SU¬ŠjĄž×ŰeďŮż50Ŕ‚ýß>č&÷»uPú˙ŮJJěµ#<Ŕ‹UˇKoDŘ„˘˝(F:xjSĽ…ŁŠ!Řącî1Ş´A.ŤłU í)0Ó´›[\8çpF|oŹUŁvj gÔĺnżŹĐͦüu‡Oţ°†ÎÄ6rś9îČ·ř48ö®Ě'śĹšNcż—Ç@č1Ű›ή1t\…VA‚Đź µ6¨ôŘrĎg$qĺNČá`Ć€né <¶®€|LtGĽĆ0ʶřŁNJ°P_5·,tXô‘š‚öăŮĐÖ4ź)¤ő»őŢęńŕÓŮJ‚!ě!ßuá©őÄŹK!âĂ|§Ôżˇ˘×ř“Uemkť[5ŃJđ”™÷a˘žJB«ń·ćLgĽÖÓž_tŢËĎ‚ÇWMyř•őĄńÇÚm,Í˙·Ź:űĄý×VŇĽů—ž˙Önc…ůď¶ËóßVRů—žWocůóëă?•óó©ČüłoŇŐWŔ ňź6Ú˙”óó©ŔüăÝŘZD`…ýß9*ĺż[IEçßržż”Űťś˙;G)űďN»Ś˙˛ť¤.łŢŔ$cdTśěZŹg»]Ű«¸L?=úěőĽ=},r­]éŇmľă†xj's[8ĎĎ(PkĘQť(lłčfCűÚ|¬lâáđ…ąíę»\’¶†ůŇjÉ+ýů>)îĄFXę±Y†«Ĺ;<¦Mń˝w‰÷ ĘN xTy!^ÁpbôqöµçĘŮ3*‰µłL‚·“*€]=mę|Z8 Ď{=©f'e śv&2Á€Śśóůy`i˝w\QjŚpŁŻpŻ*Z;ŇĂMŤ#Š“ŘÔ=śUÉť¬Č§ÚďŰ王­#hQĎŁžę’ć[ö©ěT˘VŘ5F·Ży±;Ű.˛â§ÚX¸i%Đj1ÁŘ€Öűc'¸Đ˘%ÜB,•Ú‰­G+Ăśµ^|2TŞ“úŮDyé}oÎcŢëäÔ©ÓȢ&ĄÁŠŤRy…uw;=•–C¦ć·©ă_ŞBQúŹšĎưëV`Đ˙ýöaR˙ KúżŤ”M˙ől× ď-Č<űé -µg„Ż%¶·¤::|S&ńľD9řn2%ŇąigQmP„÷ëhxkjH14ŇˇË ŕ@’‹Üx >q™F q=Cí/8&ľV ÄÉľÄ[ -‚ÔP,Qk8Ś ‡2rą"L($Ľš€•bg^Ş`TęśXW±[ Ŕ0|ÂÝT°čÚ÷*Ťc“gHHߋдž Nφޙč«Ú3ô–€3ą qüžö0°1%)*Ź9îá[ńř‡ŽkđQÇoFĆë­žąŢôr­¦±Łůzőřőë_źöR€Đ×.ßľÚ<*ń ““Óň¬°ťÖ•lů<ö©E@.çMiřq}ďČY'#ŤZé‚„ŁŻO`†{R%.ŇTčąFčąÁXú=†Ż}HÎĽë1Ś]}Śf$}'ôŃąˇnĹ6# Ţ9á•ęˇFäŐ¬'왵‰.ý "r< ]ŔK<Ą¤IA—pcĹV)׿żu«âk=CUgDNżŃ§0jđ! ¬B8ăV%ä@“ď[Ä”R¸Ře´mhz›ÝŤ}˘~Ľ‰ö&ůţźNőQCĎčŘPĚÉqŇ✪˹ě–0OFKT]ĆjÓ 4ŞÖśl•žĆ[Ť‚ײ}\HĘľŚ$ ›JŁ˘‰ŚŹÁq8Âă˙{˙|»·WGĄéTö‰3đ˝Ŕ…gŇŮ!­e›=(Ť…cő˝Ý;ĎÓŻŐß˝zä«ă*Ź QyŽcă(»ôo_‰Í —8 B˝Ő;©Ąëj%+ş´žŚ;âń,ô ¦'3˛ Á‘©Š\:ňVĘYQ)&ZzŃ]Ă MíiÔŚ1;ĆŔs^ś´Sľ’Ž•Í…ďďĐ\c)Zjx>‘ őć”Sô‘¨q+´}´ĺ‹čgűö'’ăx®&ý­đ.š(ňţÎ\ŢtUŞ˘­N>DózPs‰zÚo`Eś~˛{uÔUŨ2s®żPýŘ?{ZFţŻ9‚%ĄÁËË»­Łňţo+iµůG+ˇâÁEňßĂýTü§ýVé˙m+i'vLžţšrîk=śńč08b.1x´Łřű 9¦xü©XâďŢdŠĆCŠžKâĹ/˛ĚFb@‘Ş±Ś 2%”µ“1¸}ĆyŮŮ SČTĽ¤tNoĆ3'!N0ô˝+î!p•ă3fŹążôů ¦lh-‚ř†Fő˛Z§Ń]¨±]4/}8ÇÖŚÚšľŤŽö>~ăÜ×Íß=Ç­U«ő `Ąă) vÜ8óá‰%ópzL8ť-H”ż®Î™Ý y×.úČJ‡ćJč?tF#ŐbĹĄuŐ€ş«m9ŹwřÜřď$Óv˝K8Äł ›Z+SŤçĂHĐH1&ąů.ĘăX¨É€é¨_đCŘŕ±Úžxţ•ÄŞâˇofp™›őńHŤ/ţ'îÓ§cž0XCz”śµz|,X^Ě#§§xd2pÉ·uŃĘď*=9ŔÁ)TËŞ Ě ¦? \Ô„äúĆč)€ňŞ]a(Ú&Ż*’ŚŕKŹĹëpt•x„e Ȥ'ĆẪą>uĺËÄ=¦ŢXŚó6ČŘ Ü[Rl¬žÓ>™ÎBśśXçĄvEOŚł\÷Ž0V—1°Ç˘UϨl§ă˙´Kűź­$ÔçůÜąłłsčťiťĺf:ÜzJ6DŹúńCŃóI.O†ŢĐ…ÁkÂj?KoÝt#Ő§p†ÁîlŇGaÝ…ł¬Wěv÷›‡čnŘ›)˘zčQSĽˇktK ˙1f­sŕmP3{Ŕ,pŐRó@ń37Ŕ1FÖ®‚>1ő Q"Ż8 rźR«÷ÄŻľĚ CK«DE‹F+|ÁP»óQżmÎö5ő®]ŻÔ‘HĹ߉o€+9!Šć˘÷'­Síę†Ú%^LJ&QŕäŤBŰ•®€vmwŕˇP”Ł©"»Ĺ¬P€öďͦOĘAÝśŕĚzT{MEëĺ—Lz÷ţąű¨ö¨÷vřu˙4ńËÇöőÝ˝¨*ąX蝍9µA sK;Qé(îeţÔTĺÔ(“ą`¨Mi5Ę«ĺ*ö‘4ŠWsdU8ÚżÚ|™łműÎ@ Ą™ĐŁ©Ł=sąšŘ+<ż6ä_°ĺŢ;“YĐ„ÁqG—đ޶/[˙÷=oÜA)Jęµ’ř˙őKüó ńż1ź«UĽ %xâąpđ {cIô4…ަ%NmFďµ 9P׍ä®hDC´Âe(×Ăâ-™o×"lvC\q†+›tŞÚ’ )×mŠt(]0•DĂqC_‹b¨qůŚd_Ľ#e…śHµÄ@px¨&$g;|EçĎČ%ŹęhTžŕM穉7…ĺŐĹÉs,ř„µ»Ţ@eň+“Ó¦4-{T#°´ß®|änŽ˘ž= lě62=‘x޼ɧ€<2hď‹Úa¨Ź†kń %$®G Ëĺ‹"OĐý©§G!š‡Čĺ«AĽ}ÓQ.ĐÇşK‚Ď{Ŕ±řťm÷¨^ Ç2i‰šš#ě éň9ÁEťokQZof4LZ•{˙Ľ»‡Žf°W®¬ĺŽx†ÎÖőlă…'ŢęŃK YC­ý)ütő —Ë'úÝ=ÁŐĹ‚U«Ě­OŁOî'Şě“ëE™ŁŘľfnŹOô‰7ö=©ŽHá…4^ă+&Ęx5Ć@^eż¸ď\ďҕީFzĄÓö,Â/ř¶5ÖĄ!RąDßDŮ 'Nó÷:ͨż€†±#"É uöx˙ľ„_<­„˙ńšj ŕBüźŠ˙±ß9(í¶’˙Ë ] őcŮXż ř¶*±öÄ{/qľo<ň#‚ŠC!ňłh9Łtö“DŔŤčŐ$‰A\břúôŢşżiˇ!?ů-A3˛Ĺf)ŽĘźžH¨NţčÝĚ" 1ť†[OVÄ˙Ë ˙…ňżÖaňţoźôżJüóIá˙5Đ˙2Řź/X÷[‹¤u%`ť@±|r;ŰIŰÉZ,UĂĆ‘WATđ··> űŰčÂTÄ•5<žNÇäX ˇ%iŁ+(¶śIJ„ («_ňs@IXȢĂöQäŔµ+*Ć)R°ŔKI˙V*^ćo„~jÓ„)÷ÜC6¤› L”I—`f˙”¤©0eú‚S™¶’Vˇ˙Ńý㞠é˙A’ţµ»ĄţďVŇń±_^ŘRŠĆ(ź-É)ᣝ9óQś%RÝŮÉĺ š1ţ!c--pí®SŠé(ZÎdMČŞú§'ŐĄQ˘mŽ|›4]lÝc6t]†H¦¶ďiîěhĽ@#˸x¬¸qbT”ŁgŹQ'mGm2!–Fm28_eň9#fDŚĘ¨ä_Ý”¦c#Đ‹kMËľ#_ÓÁňâ ëşö±k7+õlS ňN]Ůľ¨©¸!d°&˘TČb2mŹ߲ţÍ“”o +*_­+ÎA?Úa=ĐŻdÍuńé“näXT™Ő­~Ś˘„I„ę^ĘP=ǧŽ;ÔA2tS!9Ą4{,«ÖÔ»ýΖ&f÷§µ?áŐ)Â]ŞŰ%GÖ&Ă!CWľ’-E¬‘ŽDČ%ĄmŕĐîĎÎk•źUP‰W‚}-.-ľB˘ÍÖ„lcٰR® 2ăJ›­?qZŤţŹaÉlÎţ§“ö?íŇ˙óVž˙ő|˘‚źžţTű/˙Ź«ŕŹ¨ÜŻÎxđÇ•ĽsđÔhĂŃťzK`…x~HÇÁ Ër<Ő,-Q`h Ü#>ĄŚěĐ]]XnĚLŚÍ~&X®¬Č ş/Ď{üb‡‘˛ `bů”¨<€_úcz÷™x2|µ“ŞUmTűđo@QŃż/Ťżrüâz‚sާ±ňáéńĂ6=~ŘÁ÷ǻ׹•čěś/VUŐęűÖŔâ r˛ę¨R¦ęBéRÎġţ«cŃ©“Pcáé»"ˇš/cNáRČ-cč4“¦ÓĘ[,Ż Ń‚Ú“Řó¶uRljʲ e4RÖđ=̶)ĘŐä®P­Q >|§3źĂÂúŠň6‡aĆiŔĐĘ.=NĎéö[Ç8ŐfŘşJL‘Ćěu´dň¦ř‡nŚ”ťÉIqńw>@ŇŁksTă˝(Ą­´:ý?łÂ‚,Ŕ"úßIĹ9h•ńź·“"úĎóąĘ€.ż Ń6Źěá­ĽśÄČu>†öbDp»ľł“G{˝… ŰŰÜĽ“'7ď0$,•Î2‘§\ 9ô‘x¬Ńu55WěŤS“IQŃ' ľL¤Y';”ď”ŃGĚäŁ(˙đfL Ąz\xďzî®kź[č8O›hP«É>h98Ü`Ś:čÍ-jä8q·É%6Jâˇ'{W´ˇq¦űŠMEż…rÜD 9!ŽäŁ%GŤ9®&6`ö‡°î hĺâä›Z­‰kFőRuuÖquÎ]OzéQ “}ö9ɰÄ9)Ŕ(ŻnqZ‰ţ{—.)oĆţ§˝”:˙”úż[It˙oLčJô_/@ţcĆ=t&KęyŤÇúľŰq#µ/ Qcď=*kˆU©ą„öĎ~ nöóĎŻ Ąz[č&\e.oĂWÂ˙t±Aýßv*ţß~ç¨Ô˙ÝJBüŻćsÜOe ţäő(wú9YE×DĂ:xťjřB:ĘěÖ@íş«‹ńzCŢVÂî 4É÷ŤaäřH+áşę?“žűű‚YČ˙·“ú?‡ťŁ’˙ßJ"üźšO T|–¦ńvLŞA>Ő'¤5’ J/gěpŕiUšcť  9şűŹéL1‘´ĺ ¨ăPŠ5ä®p=ňţF ÍWÇŇ]Árđ$ÄzsŠ%ď‚$_Ĺ^˝sÜá™7Ň>U”ŻŇ•šcĄÚR—MŁjrůŚĽ„ü žkżh˛žłD5µ“ŞŠŇfÁ|=%!}ş]—Äj^Z˙kGôkâ˙N·›Â˙Ý2ţ÷vŇçÖ˙L¬Ąĺu?#ʱYőOl !B'Ä ¤ŞIˇĽI‚oŚlaíč=±Šjć#Đ“6v7©Ě%Mgř@ľlVß‹‘85$‰>«HáŁÚąłó"c"^y  ÓQĂů!®×Ş}ŔŤrD˘fZFX ŃSlH!'lî\Ľ˝1´&O)–Î8¶ą­‡Ő\Ž6BçíŇŘü­¤_Š5DŞĄř«pIlŐ(Š?w´ľ)úˇ%ŤÓ"ô4CK·ÎŠ©šVf)ňžTî|DŻOŕ/6}ZQĂ c®Šh˘ÇСąŠţŢ_|Y2‰–¶é•tŁŞç0DşGÉ×µ„©iz™řČŁJۇőZ»ď-őU‹§Uč˙hŚűŘ-,\tţë¤í˙[ĄýçVž˙˘ů\E(Kľ‰Y. X‰CŰž˝sAër‡ŢÄ@–ˇŢ[ú‡‡@ŹŢ]űBCk‰ń Hoá]cKy¶:ç@÷&Ăęf5c°1ĺ€äĄ}¤g' Ç”˘ë{şěI«;©÷äÂĽÓr '”‘łá¨f†ßđ§hĐi·§µíľ=VłA l”ą O…1őMđ@ßg7&;*+x¤[kaZ 7’ÜIU›WŐ± —\űµ§4Ů˝Ţ˧Í űĎ}íť‚Φď-żVíŃ.«Öz%ťĘ¨0˝j˝Ž…dX¬N5XoňFŇq±eóÓ…-â’•ÍăhiB qeWęŃaě\2Çę~8Ĺ`Vmż¤§S Mńih7™:cŰoš#Yź ł®&~¤Ôŕňű÷„ŽĂ(ęŁ8nrĺÁĆŻQ§Ďh–ŚĘŇĐ\ÇěĘJYÜíO+âŕH6f˙Ýît’řż[Ę˙¶’â˙ “żĐĺŰŹOżöó3ĽZK"ŮĄ…]¤ţç’oâUŔKý`jé´ç‘wĎžŠđ~äyŚá‰5śĐîő†Vh“ěyÚG;ňűĎŕp’•Ч ŢM”ŕš•_ůY`ŹfcĄĄ¨!‚CF^Ɔť2ŕĚ$›Ŕ ÉgËŔ’FĘxŐ(SĂÁ«Tî~4 ]÷zđş2§\:Ö xÄDQ}BÔĘź#ť9Ήh’„ZE¤î2÷̧bUóőżkăe<ÇŹ‡8xÍč@S‰KĄ®ÂC—ôbËi%üďŰ›ô˙ üŇ˙˙~먌˙¶•„ň?9ź«ţ°čŇ7?můä')Ľđ˝Ůů…!4o|Ü«HnE˘ßAś, B‡NG˛n‚JĽ˛č’§O×<tŰO†Ă!ůîŔ—Ék!.­ ŞĚ ĹŹkúĎ h7ă=#‰‚­,A¤ ńzl†­5í}ĄúźŐ$ČdvŤ˛KiĎ—śVÁ˙Vp†±ôüpE¤v2ź,ş˙ét“öź‡GGĄ˙Ç­$Ä˙éů\…¤kYÂ,HEKŇ>¨€Lä€Ęˇ„ç>ŕ˙``ÄóôӵÉĽŁďÎ4ń/)vvŇ0Ő*Q-@¶zú?@–YůuŐ˝Ý9j¶ŕżvĄŽ(:Bń®ěpĐ$ĺ9×˝‚§BÍ ‹¨jÚ l%őŇ-aćŢwŘĂ^& cŹŰËJ † ŞTŕ>,ٵ‚×bőQ‡ăVLÄ]D·běŮZ=x¨ś^hđŽÄ¸őžWëę ĆůŇĆ>˘¸Š"n!Ż匆ܷÉŐőčVM*—híMóCÇR«™‹^†,Đ臺úi(0cý6\ÜžQrbă0?RrľÉĆŐÔ0o]4L©÷ ©[Ň‹#}O…_3ŔŃ!Ůü­4ó•á/6;ĂaÓ×gw>RîkăĚŞ[‰E„ëű¶őNkÂł+%‡[L+Ó˙é™D‹‹O‚‹ÎÝN'I˙ŰĄ˙Çí$M˙ő|®Lűu +šĆŠ·Đ2ë„+Śýâ•5#g@ž4ľjî$—Ľ:ó|¸ÄşL2XokA$—Ű©j*öâůă'Ď8ôd Ĺą:Ł‚Çâc˛źy®4gĄrôóZç†v$Ř5U°!¨ —jŃ“ë…ZĚ^Úl˛Š2&źd"(N2T ŠTŹ`dWOƱÔ@ů†±Ę—¸Ëď¸ËW®v™ß©'†€ OVčFŻFő…° (=ą(¸v_.°Ź&úŐúMÇE…Áđ:ň"‡U\ŽQvnQ‰A­ŢI¤Č­}úV?X˙/Vú5Ň"üĐ=(ă~¦$ů˙‹ď˙yw.y˙?PN@É­]üłńEnĚ |Îvś0ŁAń2Ý´gvTŤ…7öó™ôÂW"·ĐšSżŠ)çGI…úöąŁôîh-9Ś„j˝C­8> …ęňQ¬3o Ěż€9¦(00X?ĎúW†wO†!e,*;ŁšGrrrź˛ś*Ŕp\|öa`OuŹB˝˙ĹU–<*jźÇŚś“`·óó|b˙-]VÁ˙¸iâ6ă˙ąuÔJá˙Ň˙ó–âc>WˇŞřş^@§Ŕ°qh©¸ç'0˘A¬ÉśŰÁć<Č<şEź&1lţđĚŠűčMŕ F.ŘÖ–˙v:)ýĎĂv«Ô˙ŮJ’ű?>ź+bŁ’q”­ÎĂ"¸rC ~¬1zČň|ź#lbۤ¬żId+h¶‹*hĆ»şUÍvRUSv( Ü?8ĎëţhĐnu˘Űl׍bgëĂźâŹ;ôö¸=>›ŔKů˘m>ŽňvU+ŹÇcďRTš•2yÓCo*Ćö{{,›2q–šJű±¨6«Ş¶źí‰÷Ţ–G} Ł;ôÂć–Čšü«|2ýŞŵÖ„cńË olg4Żuxd%ŇńśR†ň0=lńVIţC#%äE„Ń:=Gm Y©ÔjJň˝~Ď_"ú­lH1†´zđ©‚ěŰÔ´ÍÍţ05ť™ŮOvŰÍćn›bÎVw«ŮyZÍf+3‡<ŤěýóÄÚýăíp÷ôë»{ŽŚôCEă˘"9ôzÔ8”úĂ·!­H˙GcŻpř·…ňźV+e˙wÔ-ů˙­$I˙Ő|®Hů©řMň˙‚Üe§†n¦gú‚QpóËŻ”Ź”Đ;‘qw tŚqŐfŚ“LuĎĹ–iNZ˙/u°Xţ“ô˙ĽPʶ“$ţ_ă @–ľQěĎ÷ëÉ~n\iÚ,%űá[ÓĎ%b^q˙ă}őą]ĐÄBţŻť”˙tĘřżŰIr˙Góą" ¬‹xx@J„X¬ Ü(›şŚ S2‚eş™´*ţ_Âüg±˙‡Ă¤ţ÷ÁŃa‰˙·’ţ7çsU°®Pé?ÔćA¸BĺO•mIŞŁE wV%¤v!dag äâĹ«ÇĐbÂ?˛•ڱйÔÂçęL›Îôýţ#4Ěĺ2HMDNR”&^oIiľ¤´"ţźXÍٶŹŇöźĺýďv’Ä˙ńů\‘•Ü8€¶Š“€ő‘˝ŃµÂöءśÝ´Ťď>oí>8ýŘnt®{7ńŕÄPęëP€’|9iEüKßöťÁ†ä?ű)ţż[Ć˙ŮN’ř?šĎqż¬ŕFĄŔŚo·îeo>źôç8} 5ĄßĺJ†Jd]¦"iEüĎŚZÁŔ…ř˙ …˙ŰűG%ţßF’ř_ĎçŠčźËß(ö— ®y ȵÜBp GÓ™M¤j\‘ĚŁÉ7L˛¸÷ áĎźÓ˘ĄLˤUđ?†Ú¤ýça+e˙ß.ů˙í$Ä˙r>WAýXtiűO,dD“¶źRö3 ¤·{ŠŽ\(ÄsĘ”â^ĹbvvoT sPćöDĹjôĘB˛Đ®4+¸´Ecŕů€/=6`l ÍÍćęĐ­  h„32pśCđ/ךTż„5i0Ťś1@Ú§1Ŕá·¦zś["ĺg 2©N^Ë›Â.×%Aă<’Fµň3Śň=ü2ięŞřý'gŔlІź °˙wö[I˙ĎGťNy˙»•´üźX +’˛ŠAÇ/žv Cç+ħՀ-˝‚©çŁŤdmní­°V˙˛eŕh†Ü˙¬€ü‰đţšâÍ…­ÍR8öcŚí°[‚HF #®M°Â  Úă’SËÉĄP‰©Ĺť\9Ŕj3©ÖIĹÂwm j}üŇ©,;pGüŻF0P%Ĺ­I—¦BłŃ ËŲşčő Ĺĺ´?XČ"R˸Ż2 dFd4¤źi‚F”®™”“%:wĂ}‘>€R1 ś‘t\ä9§©˝Uş¨oiż4Ę%Ž•î&«Áb-č#µÍ^hÄĄëc`GPčřzŇ–ÁŕÍn;RČDÓŰśXSZďď÷®ö®.ľ6Fçkőü}¬­ŰN‚?kZ…ţă|mňüżŇçżRţ·•„ô_Îç*ô‹.#őS»="ňÖ†Ľ;`µŰ9YeŃó`5—čdžTěú¤˘ŘźĹ>z¨1,˙e?Ęô™Ó*ř˝ş^Y“ńĆü˙ĄäGĄţ×VŇŇţ?ŐäŻÔöRüăńË€»†ÖßDżź$ř“´ !o ™ë(8{`°B‹9`ţ?şV8ž2ńĺČÜp˘“+‰ÔUďjŐ=;¨€µěqž&ľ•Řxs‘6uł›Šµ‰cKŢ—Ďp€ŁĐ2;I?—+íśš%"@.â˙ö»©řŹű%˙·•„üźĐş Č… 0x8›bl[´C)¦®:÷ÁÓQČ\áîy¤íp™Ę<ř➀…ŇŐˇóp MśŠOÎ'áÄEâ<ᾊžp¤ßĺZS¬2ÎZŢtK«ŕ˙‰őa™ă˙Bţď “´˙í•ńß·“–Ĺö0÷P=î9óĚáś_ŘŔ«0˛D<<'ĹŇŃťb4"Iŕ—„.cŕ]*°†kSŔ‚d< ŕčZ1¤BŻ ­|îI]"­´˙)`áćřżŁvęü·ß.Ď[IČ˙éůDöďŽxóÓÓźj˙ĺ˙qüÂVůŐţ¸rwî#[ĐAŤŤáPëH Ł”ĹM~}ŻŤ äőťóđ¨E̡G•,Í”r—–ľŠŞĐǦК‰¤Č•úĐËôÍçŇÚ)[ĽźŠ,ÎúŮyůéü‘úŢŠ<±lf# 5Ř1ןŰtzŤä|*(î<şĘđsyŁMAT@ 6ěhŚňÓŔťëxÖeĹ×;hčaśÉjxáŰ6¨†ĐşIč.ő¨bŃŻ·ÚUFşiw¤˘‰ >RD_0jĺ¦gdOԞы4&”TŐŻQěk˘ZĂŮtě PÁ€Â6É+8%5ő3'^Ŕş‘¨ré¸ÍJv"Ĺ k˙´E‚ń0­&ŰÔĽx'%ääńe¸!ă`0›RtXŹ ‘zFşx‡ÎdaZЬ}Š#aćĹ:%%;7öÂ/ĄÉz3®úć†?˘ÁčAüČ64-˘FR˘Áş¦*L_Ş1«$TcÝmR3_ˇä7îŽ÷Ž<±JÍ •]yŐťŽŞů25e*śV˘˙Ë©˙˙$ý˙vŹ:Ąü+iiú_Hßźĺ9 {—źIüS\ÝžŽ=«ŠČü—)WŮ˙0ŽťľçmęţżÓn§ř˙ĂŁRţł•„ňc> I€ŕ4Ü·úŔ8hQĐ!“$íć•ňőĐŤ€uQ1x%1Ž‚lŤsj×K+#˛;Ĺ*mËmŠ˙±} 39đ&-‰Xšę–k‡dÖ>YRŠ–ĚDĹ׺šT=ÚÎ夬;;ČOČZşňNĎÂX0›ęŽNň·Ňĺű…ŮűçŰÝGo‡_ßÝ+ ®1;Şq»ôŞYLµm`J‹]6w‰s±÷ĎÖÝ=Ťł#ą~­€4V›»ą4ĆTŽ ZÝMÁţ¸rěńWN’ĺ^‰©8¤"Kă—‘xxT»ÄÓBF#aĺ=âšz±®ן…gá÷ťë]şz“bds uˇ`weJĄUč?=ř=đŠžë˙Ąý?–÷ż[IH˙Íů\EH—_šL[ ĺ˙|ýÓŹ¤ ®”T?j®E4*ŘC¶ygŔS7ńK÷đ†<aŐ9Tú Ś+4ŶŢ[Î/Ą\Á[”˛bFRů«amő‚H{ĺýż„đÂýźŠ˙|ĐŢ?(÷˙6’Ţ˙r>WŢ˙t‚ó÷?©çď˙0Ťv$·e ”toh?Ťa©ńöHĺ:áđwc,ŮJű߼ۤý×ŃQJ˙x‚r˙o#-Ţëń­S_`—'¶9Ćqp&j‡Đ!|ŕYp°ŘFWĽţżŕK)8“Ŕާ›(>†ěDÖżŇ\0‡;i KY— ČvćíjÓńKĐÝ»D-;5Ľ{Ä‹úsó_`:ę ĚUjdˇ÷4&Íť\ʼn»küÂLZФ÷P=ní1ô§ÖŁ•WĎ~Ś 2őŞÂd@Ę·“/©-bP&쨼4•Q\O­lV|¦űŔH0`ȧĄJ:]&„Ô+áßF‡,›Ó˙;LĹ˙‚Tâ˙m$â˙Ô|®ÄüQáĺ9żétěŕmŹlťlKéoPsRËÎP˛ăBIPŐ))ÎEXĽ'NŞSĚ2ĹŽŞDáEâ> ř„'Ăád ř2i†ÇĺOŚ"§$)4ď— źš±st–5@tU$aš+0®“bÖx•8ő°|yÚ׬ĹÔ CŰGađĎ@=>LUId0ęşP“a@äf0—˙« ^ß2¬U¦MĄŐđ? ő‹Kâ˙nZ˙·Ý-ń˙6ă5ź«*]Č $ĺd“ĎJü¦¤hxŁ´¶H ÍźÝ÷ŹŃÍŤ9˙IřŃ‘MÜ~¶Lk¤•đ˙†ýżu;éűżR˙s;‰üżůëřó7â˙ŤěKGa€×Ł pţv]Ŕ-í¶Í/î¶Í/ݶ}Ž´ ţ.fŁŃxcü§˝źŚ˙˛XĆ˙ÚNBüÍç*ř_–.Â˙[îĐ›ę\tĐ‚žµH€ěĎNŚnŢÔQ`0ö\y…ŹcD"Bů1S5y·żÉpBş«@Í?7 űŮC_ÂŔúQ(L9ŕÇjÄéŚUÜ\ĄÜ©µ-ń^ÚŤ4RĐŹĽýI·¦â|q ŹtsÓ±ÖŞUŚň%)$aRyl¦`zM÷q‚>1›E"NXrĎťş˙˙ý?˙ď?¬,!w§c`€RđčĄ:h”Óůť/"†5 Ă®p0ŠŁ©'żź6ÔWçTĂ ßQŽś OŽ©´Ä ¤Ę˘´ý‡őľAý˙N'e˙łßn—ú?[ID˙y>«˙ŢĄ×.Övüę:{uŤÂ’qd"jÓݨ4ÁÚ˙rTŘŠ!É`F”hă~Ä߉ľ%î•7óą GşD4µp¤ĐhÂ4?GĎZ“ĄBÔ, Ţ‘Ő÷fadĘ„hËÚç–aÚ•`Çű^xˇŐČ´aěYčmw ¤%TÚb±ÖŤ*^ˇÂęş+Ű©ďËâsI_/ěÖĐ8klwoZcíčar`âžľţő‰Č†!Aµ1'aěňÁ6=4.bËäҙǓW–ä|\YD×0SŹĺę 6" í¦uBĚżĐÔť5hŚe”}ŞŹ—7Źę‹»©4]h—.ľ´ý°Aúźa˙ż_Ú˙n)ýçů\éđďůt?v^{hÇk:x&l‡Âରzăć»é YîćkLf SM€ů-Âf ꬣuş O+•:•ć°Ô•8d7e˙ükfŰKÝ˙ě¦ď˙K˙ŰI´˙ő|®„¸ôÜ»…(>ý’<Őú37Đ·?hM3¸°|k’.Ôś u™_ ¤'ß& ś?®ń‡ęĆFÉb(%§tꄢ2±MatĂŁ˛ŚÓYh'ëČ€ĎeÓ0IK«¬…“„ŕ ŢpT‰Ńan4uńŁ:Ş ĺ39¦0<ŻtTB©CG%”@nó2O˛±żh¬ŕ•đčoÔ˙cç Ĺ˙•ňźí$Â˙Ń|®Ddń˘6Ŕ†oĆč*Šś2R&T<ťLC¶× üš}$‚°#cF*Očˇ'®č)8Rş‡€˛UüR%t+/˝ĂĄ[ 1j·Á× ”/`ô«şžIŃvdIúŽ&×âW*X䇊Ď>ë˝{ŐTŕ†‰no`/QąI˙•ŕ°7p"]ž5Ox®–+€1˙Ň©7¬¤5R|˙_x—ě „ĽVđ21Ľ~D˘Ăq#űűPĽ¶ŇLÔ/íMîîÁ T™trˇ;â™áj„ĺg}[ľŚSö*íOá§«O¸D?AĎ#» ĎÚú4úä~˘Š>ą^”5Ă˙eŤ:ü)ň9ˇĚgŚjpPę‡2&±Ó…Câšf0­ťĚeÁâV>TJ‰Đ-M«Ň˙Ŕ‡ö0¸°€f/âť˙Ňńźۇ‡%ýßFRô?1ź«ňf5kň\ŐîëďC]bjŽR!jÚK/ą\˘¸óÓkńßB: ÄC8«µšGő¦řQRRŕ«yWúĘ›1‚>ÇÓ$ިâ;¬“2§}ć!Żő‘PlŚMZtÓ"ýSN,×Ůö,`OPÎ0Ş8ßéĎÂůžëOJĎpCçŞŰáíT ńćtć=BčNó6Ř—¶†'‡ůJ«hčľÎaETžU‘d÷"W$1~¤ňZ:”0đÜs7{päÖ pĐ•ä9;°í!Ţ ˇÔ/:÷ďwŰb@gĐ<ť§k˛L˝9µďj•dť đ”5‚žŇlözĽÂš<ą5ٞŻőŕpéř»¨®zsćR;ŐďďWë,(JKWÄ˙#Ľ)ŰĐůď°›ö˙Ű*í?·“$ţWóą"ާâKű€RAľŘ:žüY  ‹Í7žbń„@7´ălaO˝ÁEOy†Zĺ^€Î*â…†Vh§3ţc÷Ţd÷ŢłßÎúŕUŃźž4˝gÁ&GçxVżďŰď aĽ´íwCëJ¸(¦¬ýöŰë™[•śí˝Ç2?V:«™~ëěýŚę'p2˝ĐţÓŠ*˙.^9g4sÎ,?Ş{ sŁ˙ Ű÷ˇć±7°Ć4t˛˘‘ôm4”Uzj\ň ”|Źfţ•¨uZxę´Zd˝Cxű YXËPÔZífł+Ńá˝§”š©Ý›ěÝîÝ“WÍ÷쌲 Ń[î»]éç¤&ĚšžCgf´4ˇfMÔ°†Żß¶ÚÜ+^G˛čEşhźß|oľG2-Á˘úeg÷ź `”ŢAZÍf§+«z‘U Ý‰°ŰŇŕ÷Ţďń~^Ů–ŹUbĎežw+H4ť±.k=«Ś†ĺ(óŘ ¤Ŕh µťTůÁ5»ĚMS‡ÝěÝKŞŃE2Bcv *r!ÓŹöĺ ˘öÖ•O„§ĎQ:‹ Ö"®‚ÎťNţĘ# ‰ňˇ¨ą–ëqĆČáUÖý6ŃłZW–Iä;¤|ßSůłó=řw“«ł=e»7ĹnŰľ3t,ôW:ÄzžŹ[ďńËjU ^üöŰ«—zľĘ-`M˘Ó‰.ŕCF¤jzkʳؽ˝{/{÷^2óĎ:łZ?µ{ßC&ůUCÔ|žhFśíG­ÝVţ­VŹţżĽyÂ|(´±+^ÇÖĘ„'›&ř°%żX}㢦öVí˝79°AdŽY|‡ ˘d&YŞđ4p"ęěŰČí´›˘KđH–ţJ˙ŠE+«ęĚ|ô8E«¶›]ňÂ2™đBDe#“űPő,VnÉT^ëj[ľpţţň5#ˇÚ={÷^÷Ţ?äËż+X#®›ö”5c'O;ŹŃNÓ˙şą~Ęá\«ź—“V“[¶Ő@ÄŁđŰČüJÓž8ť!)¦"ĚÂBír–¶ÂšáŇ˙]¤4Q°¨4VĆĄŻ ô?pqPgÍEĐßI ˙afTyřŐ¸ľ±?Č U ËyٞŇä&öڍŮÍó¦řşő Ą6Ć˙Ä "yćç÷ŕůl@|żýv0€Ţ: EËŰqJ©yŁ­H©ĄO¤¬Łáücź†1žŮĚ,kG@đ:pF+‡Ă1ó«X@fŽ$Jłö?1Żc0„ŢTR+5‚ŘüMnG–´HŹ©nŠ=µÇZµ?= pzŰ—–”Ǻñ ”jš‰@@Nĺ¸ú‰ęŹç ŕĽĎ~üűIőÍ˙Tµs-b Ď$Ä~ł±GĆŰčGsTşFYžZŇ^ËR%é+íóŚň1h“˛b)ő§Zô*P,_)O^1­xţ_Ćü{ˇü÷ •˛˙î¶Zĺů‰ěż×1˙.jýť<úOĽ÷vd NR_ziżĽ2IćáLgMť~|°CnxŐC‡îS΢M‡Ol%]ÁŻ–e!ĺ2;±•™ŰxWYó–÷Ďor~Ë-΋ś—öćVÂ˙—Ö”®Ĺ7#˙mŢOé˙”ţź¶’˙Gş Ĺ—¦tý†Ąé8gp:ş’b‡až‰Ňe›µŠőÝŕi6>Ü2\ˇ«Fţě8ÝčçVĐú&#˛.O"do‹Q ™ůĎJ(VÁ˙ËÜýaZ¤˙ŮÚOęě·Kţ; ń˙w+Ýű™a¶LifňnŹ$®2͢, Šů¦8Dku¶»íöŁÎáA·m]1P+!2,2ě Zs, Ř´2heZ”ň«ŤČŻî'ÖN·8 wĐŁ:*¶Z°j¶‘ŢčbŁ]6…xăŁ\!,öG˙Ô„žźPŤ ĂŞ÷I„#·ĺěŸ˙É(W™6‘V˘˙ŢY˙*´Mť˙[)űżŁŁŇ˙×VŇŇ_Î}˘Źčć‰ŇíD‘SÔ””ŐĐ “u±/Ţ}TdŕˇçŁýÖCFwoČ_”6đ‹ązbAŐĽ–‘†ęZŃÓŢĘ'ľHA^d_€éŘŤ˛8cđÔ„Ž;ŚóbCÇT»żW;ií>hÚ_ďžŢŻż î×NţŮ˙îôQ}O†˙€ję'í† ¬˘Ž›|ÎÎŹč1ťŚŚĐkdT ¬$\şcdyĎSŁL÷kíożm·ęődî—ąą;ą˙–›»›‘űMnîýŚÜĎrsÄră1ß“|ů(399w>ň—ëČSDy?ďţ_ ˙ĂĆßčů/˙ąÝ)ăn%ŃůŹçsĄó]Ňo奭'Í-Ż—˘+ŰJ±9~oÔcź˛ňşĎW<đµđáů‚żňߤÁŃZt`řŚżů"?¨ ŹK'p@°źŕă†xŽf]ň»”~çśÜđĘůŔqśâ{µ5Ä„j`L#ݤÔON)đ}~9ńÇČÔ.rčôBú†rxy¶‘úšÜ :!r?rL©) ˝2{ÔŇź XĘ)ű·Ş\&ŐTfÍO4ŽuH]B.'U‚h@Ôž)ëT0+sŠy€WÂP|QOź9»îZEŽK%j:ł’ŃÜJhâuÉ8c±Ă`ss*Eťĺ©đŻ’Vˇ˙3×ů×l `!ýď¤ă”ňß­$¤˙z>Wá¸đŞ2`ÔĂŮtŚĘŃpČ#}Ź´ź¨,Ů/7\«XVż?H7•ç«>ś•PMY @ďÚ ˛]7i|rí˝X…UEFĺč¨!ÍJ°¤4[QÁŔbAT×`;dóň«Ć¨—·ÓYőÖ}JÇňËl8HËĎ©í±°Ň]s™–J+Ń˙Ą´ č˙tSç˙ýN©˙ł•DôĺźYQŐź')3tz«*))=@Ąˇqź=Żd~ĄřÓ łÉţă×OźŻEŠ˙Z?łżÎϬ¸ĆĎěO­ď“L+áß±UXĽđţ/Ą˙ _J˙/[I¤˙iN(ĐíožWW ޶‚w„żřcŢöAF$ .đŢO˘Şä­ IS”úĐt¶ŢĹ îĐ-¤Lk„%*˝˝GwNN˙ă«»˙V­ŐďÝřwx¶a˘aŽĹźťnüňó‹¦ě­ÓŕQ®/ !F^ń˛ŘmĄ'«ŕrdçŃ3(ĺŤgđej…ůÄ`ţßďî'íżŽ÷K˙ź[IË"÷칏áőęłź~˙ěçgOzRŐ¸Vş˛ú»,n„ůڬ»Y•CÖ‹±ç/Ř #š¤ĂVľ Đ}Ż A"ŕF[/wč]˛čpć: Ť«1×Čë'TńÁĆ÷.µpGF:ÁűČ^ÜăîäŠ:‡őIoď•ďťűÖDÓ_`:š±|$đ |,úÖŕťt% ßvŃ?zĺKDâCFĺ҉|şë«PŃZ> ˝Gµ^Ľťzü–U®†9€ˇĘ FŚôˇŽ¬Đ⼙®ić­)ZŔĹSŕc®•S×$mV˘úÔ%pÂż«\"ČEņŹ.d`RšŞč=WĎćŚĆŞ?ŻGFÝOmX ôD!ş¦€wĐrÁvF´(Żt°*K˘ţ@w‚}BőIGA:z4»¶RŐ˛’„Kęé˛{h·zF šđĐŇ@9‘ÄhĚ:´c|űÜţŐŚŃ䱨žĽ…´wZŐoČ#ľř'żů:zEuŘČĂ4&XM:ŇĎ{ţW˙¬ŐNďţĎiďÎGjäşţ©¦ľF±•äĎŚŚo%óÔżrL—1Ľ:á‹l|ď«číµéŠĂ ÔľúŞFJ‹˙«úrÂśÖëâÓ§Ě÷˛w#^Śë&—¶©Ýp[´ënZŹ˙'ű…B …ň˙Tü—Ăv·”˙l%-ä˙ó,IXžń·Bň÷.µ˙$‡JńIbąH 4ŔŮšâ1˛¦Ŕ·†00T™É»˘ Ĺ˝’Bdqé· ®Âň3ÇP !Čx†—Čtf0–äęU—©ßN­ä1Ţô˛ŠbŽ3¦`Ő%™cîĂ"¦xSś/µ¶Ž7‚7K¤´"OEuŔôE(Ł H-¶§‹{ÎŚ,s )ŇZřż`Ĺň˙¤ţ×ÁŃA‰˙·’VÇ˙2äËćĐż"“VzúAµśíbÇšPëÇfYR”‹L¬ßqđDFUşYęp—0~DŞě EPU”JĐ,śűÖhéfi !˛%ŇP‹hÖĄ§#Lô7÷ÁRťŹĚücĎýżNŁGĚZSenů§6r(˝[őgř·ÄůScźń˙ž&gěŤ5ÜFÔ˝‘´ţ÷‹i-Ŕ˙ÝÖAŇ˙ĎA©˙ąĄ´ňýŻoFţŻlźÄň2“¬‚nc"'oÂ:·`ď…lö鋉G~Ďgc‹ýcŰđšÂ;5Ď’“kíT€Ľ’›7ĂÁy„Ł˝ŞuP¨ŞĐB–đˇtEért €1ˇĘjĘÉ8Öµ·'†öŘ™ŕ-BPOj¸¦k±đmU&]«gddÄo^UH 긍9AjĂ8’('ŰąÚÄÂF\'Éâ {1ßčşyaŇPv2Ă<ôaq1¬si ?Äa¤úŰv13fś¶źI}•Ž/ŐŞÖ’žúś®gFß®)ńÜ?áďÝjŚňśp.ĘÍŇĽLz 6âzUÝňÄ;Ţ>¶Kí_ř¶AđX\Řăéh6NŚ®4‚fM!ŚŢ‚‘+qFrZşŰë1ľ–A̰©Nó›ÄNŢŤ˝„Î BÉ5 =›)/0,“fđ§taĄŮ€o1P6´óÝ´çŞEĚoŻČP`ěnŠ;Ň­FpŇ9Ĺ3]˛)®ď¤u‘űÄ0Čí(G%2Şú¸Ä i‰ŃCá ń6'żX}× †HŔy¬"˙ +ŠËîĂúv­¬íçě™vóO0Ŕ[Růü›ťŁ}­kí> óiSBŐ¤¶űÄ$*.IöŻé~Ćkşi“×´]ă|ő$s›Y2­‘Öâ˙ŢkĆ˙Üßo§âî–ţź¶’V?˙ËÉ_Q¦8ÇĐ˙®eŰŐšÝ@„·g˙kfŤY ÄbMAI†âQ®´{(:űŔ1‚ݨ#Ą†~ůşčĚ<˛7!YB2E«Pf3Q‰éŮr˘ä×*C' Ý÷úŔ…vĄŃ>Şçĺ=Ie®L<ŻrJeň PŚtc(–…űôÍiűĚAžö]~Aµ2W?—i8ľĄRĎlëԣ¦Ů„f8­óß.Ö—áyŚ>ŔˇśnˇvżZuŤ<ë¸]q‰ű'Š`*†ÎY R®)¤Śm×`t˛c›öísG±jëĂ™ˇ¸íţ ńp‹?z/m8î ©C¸ëí;ýYÄń®ˇna`łk2\•„ ‘EëG°)¶tç" źçűW=4ÁvS±ĐIĄűµ¦µśBç˝­Öš-'cŠ7L¦gräh‘ë™×fěúĺáË`baGOĺÂŹÂLŚ˙äů–´*űxh#Ŕ ˘/>…Rď|ŚúbVß:QCâŰcŁ÷f?iwčnŞRYŇ;y̤wńpŔśÔŢLg“#Î3!ńZVďôXßÜ8ÇŕŤô6é—©»ą™Î4Ż!ś°äÉ8“ŕ䨔~hđ/~ľYŹ˙çđ ‹dŔ ő?ZűIţż[Ę·“Öŕ˙ĺÝŕ  d¸?÷ vSAYtxéeXápžZ”őI˛wC7˛Áś»@ůöDĐ} ýc.m+ŁşµĄ[AŮÜç¸Lr«Ü Jü÷%ß ®˙gĹ˝/Ć˙xô˙RÚn'ˇýżžĎUüżpá¦ţżâQ€ ă-ÖőçF‘(“$9@1H×(ĂĚ]Ą±!7YŐ"­Ť¶˛b©öéwG˙Đď.˙fµnŞvW*r%”ňI~)ĂJîeЉvŁ»–ł7ŮâVü ČJ;™•8‘F6­,1Ç @˝öŔZÄK5Ç5Ü6cřż`Z˙źYáfüż´Rř˙ Ý=(ń˙6R„˙y>W'gčaz!xî¸Ă@»TŚEnhŚF—dΉU7gÝp4 ,€K‹F[®5ľúĂNivŘlŚ7«;Äťˇ`Úq• y¬üȸb€Úč…Źqy´'Td4]Ĺežń¦UQł Üß}PÇüŞ7Üiť ´}ď˝mŻű=ĂŮ Ž]í(W¨U(ÔiCtچ„OđysA©“Jk·M®Je9×üňŤęţŮTO Tg·›Wg>]Ë­ND4_Dç’ÁN쨣+Ëď/śQ8Ď …OVÚEÄPvCÓCí&4ßçŤ;td |’I_ß×ęâŽř›M·e:37›MńĘ{gO`ĺj‡ęŇ'ĽŞĆB~ŤÇŇ*Úô—Kg¬©ď˝Ç wëbĐf©§Ą…â´[°»ŇĄNőç=>5‡ŕlě!?5Gç8Gťâ §ČčČ&{˙¬˝~]Ż˝mâŹ>˝Ý­Ó»{ŃąŽö)”™Än3řŤ7ô¦{jDČÂÄX ŢtNهÎHVöŠÂТ#ű[äˇtRŤE’ˇĺĺ­¦Q˘ť@yĽôPwś Őc¨=±xęÔCąiěŠ6¬˘ç„C&Ä«VYśÚ :†KčŃvúdŘöPíŇě†Z2 ć—~ţqRŚĘ•÷~śzśyĺłżĆĂǢžSâm $JČ"č!ŕĂ`< pe.[ĆĐEΤ˛`ĺ5+ýI™«űŰoąűw´&yÂýÂOîř*yÉŕž'?D}P¦…a‚ öľŢ°ţÍĺ_|"UT¤p°8R3 ˇ'3ľđĚQ¸#^h/]şyŢşbˇ}߆Đ9'k1żF 0U>ZcÎ[ŮfĹGâoQ7ÓĂP|ń¦ć?[ $˝NK_č®3ÖQâs®ÄÄz‡ń2€›qŰp‚öôői‡ĐfŁ ˙fń‰s*®IŰË”˘Ë´ĘůďgşLř§…ú˙i˙ĎÝŁRţ·ť„ç?žĎUN~P˛HäŠß‡ü˘ÂŇäUHëČU'ÓŕÜ…ńOž/}0áŮI–P‘!”>ⓜîÜvmźśă[đú_3}ö­rwUcäˇä‘KÁ|20Đ<ŐŔ›± AÄ‹G§5%k„î© gźüëÂA餺ßŕßaő4#˝ŠŃ®ŔgżB'´~?ŕď]ü~X pÄńĆ\¸*_ˇšps„·btédH ű3dÍ!Ż*)îĐBbQ‹üCvq#RɅDZ,§ýŘĂv\NiĹfě%lÉłšěݧ4y‹ÁŐIÄxîD1žev ‰rŘŞ´± 'ޒꣲ7)dż7 ď©ěŘý$NĐ­©2)68Ąn†3˘„Í÷řşÔ¨ex{:TÖ|ŹŻ1)Żś;9"ŹÄD>ťq«^áuë5Äüˇex‚nO˝ĂMqÍń©ˇęÍďŚMŕ˙ ď’oGĺ•éĺG\WśĄ$Ż6=MYŚQoć´%Ď!w÷:°&†sŘőśKÓ~oXťeÚźÂOWźpŮ|‚q‰Î41QöÖ§Ń'÷UřÉő˘ě%ťźâSt¬Y ^5hxÁËĐß0Tnâl•Ąą+Î 4‡XH-1B< ¶;ţ-}í`5±Ň~">¶Z#:€ŠzđW‰ˇ2Ź˙‘"šüs6ôÂłáR|źJóůżv{˙(©˙ßé´Ű%˙·ŤtG<Ďi‚Ĺt<;'ô…">Ď‚3.ÎĽäĽöěp`. 9„ĹŹČ—Ţ^@㙚;Ča>§Š¤I[B’ţźŻú±!ţńřĺH:ŢŘBv÷+«GŐű=…m ˇ“¸Ä;űꯦp;ÜÖ ÉĐ!ÎG…ÁďLˇń+3 ŘŔ·ÉîZ˘:ˇ šíń‚Ŕ@oŢü€ň$Çž× .HäŔ–a8Ćóůď°zNĹ)9®ĚÚ„ ÍkCI–'aÎ^8™ňťŃŰć•5ˇ-ŕK‡6<„EDĐY17¨d†éjÓcńr…H1 “>”%ë>ŐqâsYüBÜ.bkŰÚ€˝‘ÓĎ.<{˝§^řTâ<éA“p‘*:®:ÖąÚĐńŹ+zeŔlWÜŮ3„^Ąz9Z¤˙ĺĂgô(*Nt[ýHd€w®3Ž" Żš•fř!¬Đaľ?Űü=đ\~€ßđ ö‡źŕ·ë8˝ŔS‡^îuőOż)źŐćzÓ··áG ¦b„Ft{˙D$NúÝ˝ëf|ysbMe@Ń—‹Ę6Ĩ.E€¬QŻ[‘2ń$8vgvÁ x`:Éóë‡čÝSfKʱy|Nt)˛‰ěÍş¦r÷xĺ… =9ÖyĹżý›jOíČGÉ6• ‡’ÖĆ:“rĆň„DʍVŘ•@‚\fz]ŕĂO1ć€Ă‡<řµć×őcü¸»˄齅kěnűß{0˝ŰŮIĺáĐ´†ĂdÇX* & ňä÷#UuťĘWŃÎÖ‘–fĎ> ě)Ý?&KËwk•ç Âů)wâÎG–k šÄť…ŞÜ2Ä5}OŮxŕ¸ăÚĎxÓDM ÜřXČô]K˙”Ř´D™ΗWögý«s{TăůçË—"h"Ý©éµŔ ň:8CO‹aÔďă‹ÁżQćě3—·ś#ľeć±QöeˇbLH4iČG›ćË,˘K 㨼áŇᡑ~ŕ1n6MäA®2„NsĘÓ´¤1U{śnţl“WbÔZ@ä¬"Thí‚’c.Çih÷gçµĘ/SôŹĘÁDěPś"‡*a‚‰ű@¬5Úćć6â«üiMžŇ¬!ëigż3_VŢş•łçoI|ço…ÔÄŘMtÚúÖ@ň0ĚoĂJ=cçd­ĹT.ˇqÂěÁqľfđfŔF]Ż×&µÎ\Ψyé;ˇ]#”4śM¦5Ę]Ď`v˛ 1V/®Ş¨—D đłź"Óף…ĂŤŔż0› ŢŔÎnăB÷ŠEÉÄŁ“ävđÚΑ•(­‹Â¸FäĹŔEcÝ/rŮľQţ„†ăTaŕŢvŰŋ԰ĹÝřKĎÓ:ÚÓáą&Ł2“O z2ňÄ6MĆhćM1~ŞĐÄĆŮçĂŇ×Ŕß•ÔE<fŻ7Ţdš´nMjĄˇČŘ!’TüGü©śXÉ-;AřČÜ@iä¬6 ˘ž[(5̱Z>ΧÜÉžŔ'†"«ĆtˇřLŃéÚhŹŽNÎGÜÇůňäawŕ[Ľ»LéŃ(çÎGĚr-i%EcÍÉĚéqŹÓ7”B"…„lˇ©ÇLţââ~3Ř%wXă÷ ‘†;‹ThńaúčÝtíËZ%CÚDSŽëÜBą‚¨üÎń« 8qA‡ˇ3Ů>iHzîČ9źů¬ ÇU8ĺâĹI]Ě –xđPŘ cxłľQDz`¸÷ä§—/úńěń«WOży|/ĘŃܹ£5 řĂ3)a~$ŞńUeT$ĂHś±<¤‘ęôKŞęőâe˘Ęâe Đ„FQ<$ęŚ ‘s4ŁA=†źŰlgc©€üwjźI·Q+IŢ˙w:ť¤ýgű¨[Ţ˙o%±l¶'śŕ vFsMß'Öďž⸉'STG”'Żňžąyę;tmŚÇčőŽä­ďTµ5ď=†7 G«Ăqhę|ť4(U ,…~üµ<ü0Î •h|+ň0˘ŽňdÂôé’ e śŮQ@ĺRŰ/ÝÔ}ŰTu© ȵž´‰Ĺă;)2ÄŇBČů¬^Ôť:ňRJáŘ´Ťn †EßNĆ-đ“C[~ &˘Q0‘=‚ą|Y[ؼ䀚K 4G]áăjµY­+í„Ŕš;ă3Ű.llÓ~F`;YŔ~nśřWJEč?e9,ĽŰ*,ŔB˙©řßťýŇţk+IŞpđTóšş]ŐF¸Š>KjĂ ‘É(U|<—ď-ý™KFOĽjđJW~łÎŮޢç&iÉĺÁ¶§›]Y;« â ĽXť…:©őF h4v~Ęut ™Ž­-UűH[ďxů!˛T3‰…4ű@§ĂşěÂ\ tŕŔ¦ľ˛ĺ~Gž€ůÔ0ĚŞ’ÂâŮO›ŢĚČxµVgÝ>†,ÜŮŮQW+Z|/7ĆNT»JŽbPÝIŢŻ(ůá=€Ó) ý”*—…qőąĘ±uTÄ»?üôřéŮ«Çoľµ×6×ugżł˙M˝iř'Ş©űřzۨ“ˇšS”¶@÷Iɦkcöp°#0âuâ{;Šş}É®ýś@ICűcođß =ô”€ŃW·»ŃŃŤľAč óěěů‹žťťŐ1Ú8Ć^m¨P¬z° ŃÁ Ą$şÂ°%xT“ĎëQśA;E {1Ü–ÍôȢ”±¤¤O®Vĺ â˝ĺŚqa°ÂSü@ÉY_+ŘQîLN©Gsž“˛E˘X˙ű}/ĽI1gďiţo·Ž’çżĂ2ţÇVę˙Č?v†"Ü0űgß”ÜČ=–4»é(Ç É€":ĺ0Ü´ě%Ę`+ń䑎ÂQ•E: —¸,q«­Ť#wJĘ*ß~ űsŚžkPŇx{W/O–^â/¤-‹îç*Póˇ˙źË!•“ŇA¦ń.+ŁăŔůX^1ć”U śóJÖđ‚鋎H‹X˝“ ă–¬&nˇă§"=˛6)ouBÔ µüY—ÖU3Ţăű7ý]ݵő*ő“Óťy§žżúű3ÍŘ ‹ë’e˙’SüOěĘ:m Ţ?8ČĹ˙„/ăř(Ŕá˙›ęäĽôÇ˙Eç?ÍocýOË»GGĄüw+)FuűËßň‰şÄ,Â+™VÉLČ!0Ë@° ů śXěŕ×uбµŤŤÇ+RŔŐ5IdŽ?O‡2bŠqÚČîŰC:B˛Őžlą%O,şľŔ g2Š:2ö ĘS›¤ŕtŔ\Ž?şI]Ů7PóÔ’öUěŰVŕa`żW¶5:ú ©űbĽ›+u"ÖuIvË ł»+ٱu®Ü—\); Miđ FGOŘžşľŘť›%gLY^®0Lć±CßG&u˙^rQľ2_«đHTbşUäNjI-Ŕ9:d!“ş-c¶—©Le*ÓçK˙?¶‰ŰńÖpuppet-5.5.10/spec/fixtures/unit/0000755005276200011600000000000013417162176016601 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/application/0000755005276200011600000000000013417162176021104 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/application/environments/0000755005276200011600000000000013417162176023633 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/application/environments/production/0000755005276200011600000000000013417162176026021 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/application/environments/production/data/0000755005276200011600000000000013417162176026732 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/application/environments/production/data/common.yaml0000644005276200011600000000045513417161721031105 0ustar jenkinsjenkins--- a: This is A b: This is B c: "This is%{cx}" d: one: two: three: the value e.one.two.three: the value f.one: two.three: - first value - second value - third value ab: "%{hiera('a')} and %{hiera('b')}" g: "This is%{facts.cx} in facts hash" lookup_options: a: first puppet-5.5.10/spec/fixtures/unit/application/environments/production/environment.conf0000644005276200011600000000004313417161721031224 0ustar jenkinsjenkinsenvironment_data_provider = 'hiera'puppet-5.5.10/spec/fixtures/unit/application/environments/production/manifests/0000755005276200011600000000000013417162176030012 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/application/environments/production/manifests/site.pp0000644005276200011600000000003013417161721031303 0ustar jenkinsjenkins$cx = ' C from site.pp' puppet-5.5.10/spec/fixtures/unit/application/environments/puppet_func_provider/0000755005276200011600000000000013417162176030075 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/application/environments/puppet_func_provider/environment.conf0000644005276200011600000000004713417161721033304 0ustar jenkinsjenkinsenvironment_data_provider = 'function' puppet-5.5.10/spec/fixtures/unit/application/environments/puppet_func_provider/functions/0000755005276200011600000000000013417162176032105 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/application/environments/puppet_func_provider/functions/environment/puppet-5.5.10/spec/fixtures/unit/application/environments/puppet_func_provider/functions/environment0000755005276200011600000000000013417162176034372 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015500000000000011604 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/application/environments/puppet_func_provider/functions/environment/data.pppuppet-5.5.10/spec/fixtures/unit/application/environments/puppet_func_provider/functions/environment0000644005276200011600000000032613417161721034370 0ustar jenkinsjenkinsfunction environment::data() { { a => 'This is A', b => 'This is B', c => "This is ${if $cx == undef { 'C from data.pp' } else { $cx }}", lookup_options => { a => 'first' } } } puppet-5.5.10/spec/fixtures/unit/application/environments/puppet_func_provider/manifests/0000755005276200011600000000000013417162176032066 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/application/environments/puppet_func_provider/manifests/site.pp0000644005276200011600000000002713417161721033365 0ustar jenkinsjenkins$cx = 'C from site.pp' puppet-5.5.10/spec/fixtures/unit/data_providers/0000755005276200011600000000000013417162176021607 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/0000755005276200011600000000000013417162176024336 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_bad_syntax_json/0000755005276200011600000000000013417162176030673 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_bad_syntax_json/data/0000755005276200011600000000000013417162176031604 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_bad_syntax_json/data/bad.json0000644005276200011600000000006313417161721033217 0ustar jenkinsjenkins{ "test::param_a": "env data param_a is 10", } puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_bad_syntax_json/environment.conf0000644005276200011600000000012713417161721034101 0ustar jenkinsjenkins# Use the 'sample' env data provider (in this fixture) environment_data_provider=hiera puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_bad_syntax_json/hiera.yaml0000644005276200011600000000010013417161721032631 0ustar jenkinsjenkins--- :version: 4 :hierarchy: - :name: "bad" :backend: json puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_bad_syntax_json/manifests/0000755005276200011600000000000013417162176032664 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_bad_syntax_json/manifests/site.pp0000644005276200011600000000010013417161721034153 0ustar jenkinsjenkinsclass test($param_a) { notify { "$param_a": } } include test puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_bad_syntax_yaml/0000755005276200011600000000000013417162176030664 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_bad_syntax_yaml/data/0000755005276200011600000000000013417162176031575 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_bad_syntax_yaml/data/bad.yaml0000644005276200011600000000006313417161721033201 0ustar jenkinsjenkins--- { one::test::param_c: env data param_c is 300 puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_bad_syntax_yaml/environment.conf0000644005276200011600000000012713417161721034072 0ustar jenkinsjenkins# Use the 'sample' env data provider (in this fixture) environment_data_provider=hiera puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_bad_syntax_yaml/hiera.yaml0000644005276200011600000000010013417161721032622 0ustar jenkinsjenkins--- :version: 4 :hierarchy: - :name: "bad" :backend: yaml puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_bad_syntax_yaml/manifests/0000755005276200011600000000000013417162176032655 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_bad_syntax_yaml/manifests/site.pp0000644005276200011600000000010013417161721034144 0ustar jenkinsjenkinsclass test($param_a) { notify { "$param_a": } } include test puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_defaults/0000755005276200011600000000000013417162176027315 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_defaults/data/0000755005276200011600000000000013417162176030226 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_defaults/data/common.yaml0000644005276200011600000000006013417161721032371 0ustar jenkinsjenkins--- one::test::param_c: env data param_c is 300 puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_defaults/environment.conf0000644005276200011600000000012713417161721032523 0ustar jenkinsjenkins# Use the 'sample' env data provider (in this fixture) environment_data_provider=hiera puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_defaults/manifests/0000755005276200011600000000000013417162176031306 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_defaults/manifests/site.pp0000644005276200011600000000002213417161721032600 0ustar jenkinsjenkinsinclude one::test puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_defaults/modules/0000755005276200011600000000000013417162176030765 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_defaults/modules/one/0000755005276200011600000000000013417162176031546 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_defaults/modules/one/data/0000755005276200011600000000000013417162176032457 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015100000000000011600 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_defaults/modules/one/data/common.yamlpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_defaults/modules/one/data/common.0000644005276200011600000000006313417161721033742 0ustar jenkinsjenkins--- one::test::param_a: module data param_a is 100 puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_defaults/modules/one/manifests/0000755005276200011600000000000013417162176033537 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015200000000000011601 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_defaults/modules/one/manifests/init.pppuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_defaults/modules/one/manifests/in0000644005276200011600000000027113417161721034063 0ustar jenkinsjenkinsclass one { class test($param_a = "param default is 100", $param_b = "param default is 200", $param_c = "param default is 300") { notify { "$param_a, $param_b, $param_c": } } } ././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_defaults/modules/one/metadata.jsonpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_defaults/modules/one/metadata.jso0000644005276200011600000000034213417161721034035 0ustar jenkinsjenkins{ "name": "example/one", "version": "0.0.2", "source": "git@github.com/example/example-one.git", "dependencies": [], "author": "Bob the Builder", "license": "Apache-2.0", "data_provider": "hiera" } puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_env_config/0000755005276200011600000000000013417162176027623 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_env_config/data1/0000755005276200011600000000000013417162176030615 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_env_config/data1/first.json0000644005276200011600000000006213417161721032630 0ustar jenkinsjenkins{ "test::param_a": "env data param_a is 10" } puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_env_config/data1/name.yaml0000644005276200011600000000005213417161721032411 0ustar jenkinsjenkins--- test::param_b: env data param_b is 20 puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_env_config/data1/second.json0000644005276200011600000000006213417161721032754 0ustar jenkinsjenkins{ "test::param_c": "env data param_c is 30" } puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_env_config/data1/single.yaml0000644005276200011600000000005213417161721032752 0ustar jenkinsjenkins--- test::param_d: env data param_d is 40 puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_env_config/data1/third_utf8.json0000644005276200011600000000011113417161721033554 0ustar jenkinsjenkins{ "test::param_json_utf8": "env data param_json_utf8 is ᚠᛇᚻ" } puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_env_config/data1/utf8.yaml0000644005276200011600000000010213417161721032353 0ustar jenkinsjenkins--- test::param_yaml_utf8: env data param_yaml_utf8 is ᛫ᛒᛦ puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_env_config/data2/0000755005276200011600000000000013417162176030616 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_env_config/data2/single.yaml0000644005276200011600000000005213417161721032753 0ustar jenkinsjenkins--- test::param_e: env data param_e is 50 puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_env_config/environment.conf0000644005276200011600000000012713417161721033031 0ustar jenkinsjenkins# Use the 'sample' env data provider (in this fixture) environment_data_provider=hiera puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_env_config/hiera.yaml0000644005276200011600000000061613417161721031575 0ustar jenkinsjenkins--- :version: 4 :hierarchy: - :name: "name" :backend: yaml - :name: "one path" :backend: yaml :path: "single" - :name: "utf8" :backend: yaml :path: "utf8" - :name: "three paths" :backend: json :paths: - "first" - "second" - "third_utf8" - :name: 'other datadir' :backend: yaml :datadir: "data2" :path: "single" :datadir: "data1" puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_env_config/manifests/0000755005276200011600000000000013417162176031614 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_env_config/manifests/site.pp0000644005276200011600000000037213417161721033116 0ustar jenkinsjenkinsclass test($param_a = 1, $param_b = 2, $param_c = 3, $param_d = 4, $param_e = 5, $param_yaml_utf8 = 'hi', $param_json_utf8 = 'hi') { notify { "$param_a, $param_b, $param_c, $param_d, $param_e, $param_yaml_utf8, $param_json_utf8": } } include test puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_misc/0000755005276200011600000000000013417162176026441 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_misc/data/0000755005276200011600000000000013417162176027352 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_misc/data/common.yaml0000644005276200011600000000170613417161721031525 0ustar jenkinsjenkins--- lookup_options: one::lopts_test::hash: merge: deep one::test::param: key1: env 1 one::array: - second - third - fourth one::lopts_test::hash: a: A b: B m: ma: MA mb: MB one::loptsm_test::hash: a: A b: B m: ma: MA mb: MB ukey1: Some value target_lookup: with lookup target_hiera: with hiera target_alias: Value from interpolation with alias km_default: 'Value from interpolation %{target_default}' km_alias: '%{alias("target_alias")}' km_lookup: 'Value from interpolation %{lookup("target_lookup")}' km_hiera: 'Value from interpolation %{hiera("target_hiera")}' km_scope: 'Value from interpolation %{scope("target_scope")}' km_literal: 'Value from interpolation %{literal("with literal")}' km_sqalias: "%{alias('target_alias')}" recursive: '%{r1}' km_qualified: "Value from qualified interpolation OS = %{os.name}" km_multi: "cluster/%{literal('%')}{::cluster}/%{literal('%')}{role}" domain: "-- %{domain} --" puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_misc/environment.conf0000644005276200011600000000012713417161721031647 0ustar jenkinsjenkins# Use the 'sample' env data provider (in this fixture) environment_data_provider=hiera puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_misc/manifests/0000755005276200011600000000000013417162176030432 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_misc/manifests/site.pp0000644005276200011600000000002213417161721031724 0ustar jenkinsjenkinsinclude one::test puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_misc/modules/0000755005276200011600000000000013417162176030111 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_misc/modules/one/0000755005276200011600000000000013417162176030672 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_misc/modules/one/data/0000755005276200011600000000000013417162176031603 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_misc/modules/one/data/common.yaml0000644005276200011600000000054513417161721033756 0ustar jenkinsjenkins--- one::test::param: key2: module 2 one::array: - first - second - third one::lopts_test::hash: a: A c: C m: ma: MA mc: MC one::loptsm_test::hash: a: A c: C m: ma: MA mc: MC lookup_options: one::loptsm_test::hash: merge: deep # should not be found since it's not qualified with module name ukey2: Some value puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_misc/modules/one/manifests/0000755005276200011600000000000013417162176032663 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_misc/modules/one/manifests/init.pppuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_misc/modules/one/manifests/init.p0000644005276200011600000000105213417161721034000 0ustar jenkinsjenkinsclass one { class test($param = { key1 => 'default 1', key2 => 'default 2' }) { notify { "${param[key1]}, ${param[key2]}": } } class lopts_test($hash = { a => 'default A', b => 'default B', c => 'default C', m => {} }) { notify { "${hash[a]}, ${hash[b]}, ${hash[c]}, ${hash[m]['ma']}, ${hash[m]['mb']}, ${hash[m]['mc']}": } } class loptsm_test($hash = { a => 'default A', b => 'default B', c => 'default C', m => {} }) { notify { "${hash[a]}, ${hash[b]}, ${hash[c]}, ${hash[m]['ma']}, ${hash[m]['mb']}, ${hash[m]['mc']}": } } } puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_misc/modules/one/metadata.json0000644005276200011600000000034213417161721033337 0ustar jenkinsjenkins{ "name": "example/one", "version": "0.0.2", "source": "git@github.com/example/example-one.git", "dependencies": [], "author": "Bob the Builder", "license": "Apache-2.0", "data_provider": "hiera" } puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_module_config/0000755005276200011600000000000013417162176030320 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_module_config/data/0000755005276200011600000000000013417162176031231 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_module_config/data/common.yaml0000644005276200011600000000004713417161721033401 0ustar jenkinsjenkins--- users::local: bob: name: Bob puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_module_config/data/specific.yaml0000644005276200011600000000005513417161721033675 0ustar jenkinsjenkins--- users::local: bob: shell: /bin/zsh puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_module_config/environment.conf0000644005276200011600000000012713417161721033526 0ustar jenkinsjenkins# Use the 'sample' env data provider (in this fixture) environment_data_provider=hiera puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_module_config/hiera.yaml0000644005276200011600000000015413417161721032267 0ustar jenkinsjenkins--- :version: 4 :hierarchy: - :name: "common" :backend: yaml - :name: "specific" :backend: yaml puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_module_config/manifests/0000755005276200011600000000000013417162176032311 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_module_config/manifests/site.pp0000644005276200011600000000002213417161721033603 0ustar jenkinsjenkinsinclude one::test puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_module_config/modules/0000755005276200011600000000000013417162176031770 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_module_config/modules/one/0000755005276200011600000000000013417162176032551 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_module_config/modules/one/data1/0000755005276200011600000000000013417162176033543 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015600000000000011605 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_module_config/modules/one/data1/first.jsonpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_module_config/modules/one/data1/f0000644005276200011600000000007313417161721033706 0ustar jenkinsjenkins{ "one::test::param_a": "module data param_a is 100" } ././@LongLink0000644000000000000000000000015500000000000011604 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_module_config/modules/one/data1/name.yamlpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_module_config/modules/one/data1/n0000644005276200011600000000011213417161721033710 0ustar jenkinsjenkinsone::test::param_b: module data param_b is 200 one::my_var: 'In name.yaml'././@LongLink0000644000000000000000000000015700000000000011606 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_module_config/modules/one/data1/second.jsonpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_module_config/modules/one/data1/s0000644005276200011600000000007313417161721033723 0ustar jenkinsjenkins{ "one::test::param_c": "module data param_c is 300" } ././@LongLink0000644000000000000000000000016000000000000011600 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_module_config/modules/one/data1/server1.yamlpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_module_config/modules/one/data1/s0000644005276200011600000000002713417161721033722 0ustar jenkinsjenkinsone::my_var: 'server1' ././@LongLink0000644000000000000000000000016000000000000011600 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_module_config/modules/one/data1/server2.yamlpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_module_config/modules/one/data1/s0000644005276200011600000000002713417161721033722 0ustar jenkinsjenkinsone::my_var: 'server2' ././@LongLink0000644000000000000000000000015700000000000011606 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_module_config/modules/one/data1/single.yamlpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_module_config/modules/one/data1/s0000644005276200011600000000006313417161721033722 0ustar jenkinsjenkins--- one::test::param_d: module data param_d is 400 puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_module_config/modules/one/data2/0000755005276200011600000000000013417162176033544 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015700000000000011606 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_module_config/modules/one/data2/single.yamlpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_module_config/modules/one/data2/s0000644005276200011600000000006313417161721033723 0ustar jenkinsjenkins--- one::test::param_e: module data param_e is 500 ././@LongLink0000644000000000000000000000015000000000000011577 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_module_config/modules/one/hiera.yamlpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_module_config/modules/one/hiera.y0000644005276200011600000000055313417161721034031 0ustar jenkinsjenkins--- :version: 4 :hierarchy: - :name: "%{my_fact}" :backend: yaml - :name: "name" :backend: yaml - :name: "one path" :backend: yaml :path: "single" - :name: "two paths" :backend: json :paths: - "first" - "second" - :name: 'other datadir' :backend: yaml :datadir: "data2" :path: "single" :datadir: "data1" ././@LongLink0000644000000000000000000000015000000000000011577 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_module_config/modules/one/manifests/puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_module_config/modules/one/manifes0000755005276200011600000000000013417162176034114 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015700000000000011606 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_module_config/modules/one/manifests/init.pppuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_module_config/modules/one/manifes0000644005276200011600000000042313417161721034110 0ustar jenkinsjenkinsclass one { class test($param_a = "param default is 100", $param_b = "param default is 200", $param_c = "param default is 300", $param_d = "param default is 400", $param_e = "param default is 500") { notify { "$param_a, $param_b, $param_c, $param_d, $param_e": } } } ././@LongLink0000644000000000000000000000015300000000000011602 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_module_config/modules/one/metadata.jsonpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_module_config/modules/one/metadat0000644005276200011600000000034213417161721034105 0ustar jenkinsjenkins{ "name": "example/one", "version": "0.0.2", "source": "git@github.com/example/example-one.git", "dependencies": [], "author": "Bob the Builder", "license": "Apache-2.0", "data_provider": "hiera" } puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_modules/0000755005276200011600000000000013417162176027156 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_modules/data/0000755005276200011600000000000013417162176030067 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_modules/data/common.yaml0000644005276200011600000000004513417161721032235 0ustar jenkinsjenkins--- one::local: bob: name: Bob puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_modules/data/specific.yaml0000644005276200011600000000005313417161721032531 0ustar jenkinsjenkins--- one::local: bob: shell: /bin/zsh puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_modules/environment.conf0000644005276200011600000000012713417161721032364 0ustar jenkinsjenkins# Use the 'sample' env data provider (in this fixture) environment_data_provider=hiera puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_modules/hiera.yaml0000644005276200011600000000015413417161721031125 0ustar jenkinsjenkins--- :version: 4 :hierarchy: - :name: "common" :backend: yaml - :name: "specific" :backend: yaml puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_modules/manifests/0000755005276200011600000000000013417162176031147 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_modules/manifests/site.pp0000644005276200011600000000002213417161721032441 0ustar jenkinsjenkinsinclude one::test puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_modules/modules/0000755005276200011600000000000013417162176030626 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_modules/modules/one/0000755005276200011600000000000013417162176031407 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_modules/modules/one/data/0000755005276200011600000000000013417162176032320 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015000000000000011577 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_modules/modules/one/data/common.yamlpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_modules/modules/one/data/common.y0000644005276200011600000000014413417161721033774 0ustar jenkinsjenkins--- lookup_options: one::local: merge: strategy: "deep" merge_hash_arrays: true puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_modules/modules/one/hiera.yaml0000644005276200011600000000010313417161721033350 0ustar jenkinsjenkins--- :version: 4 :hierarchy: - :name: "common" :backend: yaml puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_modules/modules/one/manifests/0000755005276200011600000000000013417162176033400 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015100000000000011600 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_modules/modules/one/manifests/init.pppuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_modules/modules/one/manifests/ini0000644005276200011600000000003113417161721034067 0ustar jenkinsjenkinsclass one($local={}) { } puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_modules/modules/one/metadata.json0000644005276200011600000000034213417161721034054 0ustar jenkinsjenkins{ "name": "example/one", "version": "0.0.2", "source": "git@github.com/example/example-one.git", "dependencies": [], "author": "Bob the Builder", "license": "Apache-2.0", "data_provider": "hiera" } puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_modules/modules/two/0000755005276200011600000000000013417162176031437 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_modules/modules/two/data/0000755005276200011600000000000013417162176032350 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015000000000000011577 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_modules/modules/two/data/common.yamlpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_modules/modules/two/data/common.y0000644005276200011600000000006113417161721034022 0ustar jenkinsjenkins--- lookup_options: two::arg: merge: uniquepuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_modules/modules/two/hiera.yaml0000644005276200011600000000010313417161721033400 0ustar jenkinsjenkins--- :version: 4 :hierarchy: - :name: "common" :backend: yaml puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_modules/modules/two/manifests/0000755005276200011600000000000013417162176033430 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015100000000000011600 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_modules/modules/two/manifests/init.pppuppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_modules/modules/two/manifests/ini0000644005276200011600000000004513417161721034124 0ustar jenkinsjenkinsclass two($arg=[]) { include one } puppet-5.5.10/spec/fixtures/unit/data_providers/environments/hiera_modules/modules/two/metadata.json0000644005276200011600000000034213417161721034104 0ustar jenkinsjenkins{ "name": "example/two", "version": "0.0.2", "source": "git@github.com/example/example-two.git", "dependencies": [], "author": "Bob the Builder", "license": "Apache-2.0", "data_provider": "hiera" } puppet-5.5.10/spec/fixtures/unit/data_providers/environments/production/0000755005276200011600000000000013417162176026524 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/production/environment.conf0000644005276200011600000000007613417161721031735 0ustar jenkinsjenkinsenvironment_timeout = 0 environment_data_provider = 'function'puppet-5.5.10/spec/fixtures/unit/data_providers/environments/production/lib/0000755005276200011600000000000013417162176027272 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/production/lib/puppet/0000755005276200011600000000000013417162176030607 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/production/lib/puppet/functions/0000755005276200011600000000000013417162176032617 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015200000000000011601 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/production/lib/puppet/functions/environment/puppet-5.5.10/spec/fixtures/unit/data_providers/environments/production/lib/puppet/functions/environ0000755005276200011600000000000013417162176034220 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000016100000000000011601 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/production/lib/puppet/functions/environment/data.rbpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/production/lib/puppet/functions/environ0000644005276200011600000000036613417161721034222 0ustar jenkinsjenkinsPuppet::Functions.create_function(:'environment::data') do def data() { 'abc::def::test1' => 'env_test1', 'abc::def::test2' => 'env_test2', 'xyz::def::test1' => 'env_test1', 'xyz::def::test2' => 'env_test2' } end endpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/production/modules/0000755005276200011600000000000013417162176030174 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/production/modules/abc/0000755005276200011600000000000013417162176030721 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/production/modules/abc/lib/0000755005276200011600000000000013417162176031467 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/production/modules/abc/lib/puppet/0000755005276200011600000000000013417162176033004 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015200000000000011601 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/production/modules/abc/lib/puppet/functions/puppet-5.5.10/spec/fixtures/unit/data_providers/environments/production/modules/abc/lib/puppet/funct0000755005276200011600000000000013417162176034044 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015600000000000011605 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/production/modules/abc/lib/puppet/functions/abc/puppet-5.5.10/spec/fixtures/unit/data_providers/environments/production/modules/abc/lib/puppet/funct0000755005276200011600000000000013417162176034044 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000016500000000000011605 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/production/modules/abc/lib/puppet/functions/abc/data.rbpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/production/modules/abc/lib/puppet/funct0000644005276200011600000000041513417161721034041 0ustar jenkinsjenkinsPuppet::Functions.create_function(:'abc::data') do def data() { 'abc::def::test1' => 'module_test1', 'abc::def::test2' => 'module_test2', 'abc::def::test3' => 'module_test3', 'abc::def::ipl' => '%{lookup("abc::def::test2")}-ipl' } end end puppet-5.5.10/spec/fixtures/unit/data_providers/environments/production/modules/abc/manifests/0000755005276200011600000000000013417162176032712 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/production/modules/abc/manifests/init.pppuppet-5.5.10/spec/fixtures/unit/data_providers/environments/production/modules/abc/manifests/init.p0000644005276200011600000000025113417161721034027 0ustar jenkinsjenkinsclass abc { include 'abc::def' } class abc::def ($test1, $test2, $test3, $ipl ) { notify { $test1: } notify { $test2: } notify { $test3: } notify { $ipl: } } puppet-5.5.10/spec/fixtures/unit/data_providers/environments/production/modules/abc/metadata.json0000644005276200011600000000034513417161721033371 0ustar jenkinsjenkins{ "name": "example/abc", "version": "0.0.2", "source": "git@github.com/example/example-abc.git", "dependencies": [], "author": "Bob the Builder", "license": "Apache-2.0", "data_provider": "function" } puppet-5.5.10/spec/fixtures/unit/data_providers/environments/production/modules/xyz/0000755005276200011600000000000013417162176031026 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/data_providers/environments/production/modules/xyz/functions/0000755005276200011600000000000013417162176033036 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/production/modules/xyz/functions/data.pppuppet-5.5.10/spec/fixtures/unit/data_providers/environments/production/modules/xyz/functions/data.p0000644005276200011600000000023513417161721034123 0ustar jenkinsjenkinsfunction xyz::data { { 'xyz::def::test1' => 'module_test1', 'xyz::def::test2' => 'module_test2', 'xyz::def::test3' => 'module_test3' } } puppet-5.5.10/spec/fixtures/unit/data_providers/environments/production/modules/xyz/manifests/0000755005276200011600000000000013417162176033017 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/data_providers/environments/production/modules/xyz/manifests/init.pppuppet-5.5.10/spec/fixtures/unit/data_providers/environments/production/modules/xyz/manifests/init.p0000644005276200011600000000021713417161721034136 0ustar jenkinsjenkinsclass xyz { include 'xyz::def' } class xyz::def ($test1, $test2, $test3) { notify { $test1: } notify { $test2: } notify { $test3: } } puppet-5.5.10/spec/fixtures/unit/data_providers/environments/production/modules/xyz/metadata.json0000644005276200011600000000034513417161721033476 0ustar jenkinsjenkins{ "name": "example/abc", "version": "0.0.2", "source": "git@github.com/example/example-abc.git", "dependencies": [], "author": "Bob the Builder", "license": "Apache-2.0", "data_provider": "function" } puppet-5.5.10/spec/fixtures/unit/functions/0000755005276200011600000000000013417162176020611 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/functions/lookup/0000755005276200011600000000000013417162176022122 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/functions/lookup/data/0000755005276200011600000000000013417162176023033 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/functions/lookup/data/common.yaml0000644005276200011600000000027013417161721025201 0ustar jenkinsjenkins--- abc::a: global_a abc::c: - global_c abc::e: k1: global_e1 abc::f: k1: s1: global_f11 k2: s3: global_f23 bca::e: k1: global_e1 no_provider::e: k1: global_e1 puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/0000755005276200011600000000000013417162176023670 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/data/0000755005276200011600000000000013417162176024601 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/data/common.yaml0000644005276200011600000000027013417161721026747 0ustar jenkinsjenkins--- abc::a: global_a abc::c: - global_c abc::e: k1: global_e1 abc::f: k1: s1: global_f11 k2: s3: global_f23 bca::e: k1: global_e1 no_provider::e: k1: global_e1 puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/0000755005276200011600000000000013417162176026417 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/0000755005276200011600000000000013417162176030605 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/environment.conf0000644005276200011600000000007713417161721034017 0ustar jenkinsjenkinsenvironment_timeout = 0 environment_data_provider = 'function' puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/lib/0000755005276200011600000000000013417162176031353 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/lib/puppet/0000755005276200011600000000000013417162176032670 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015000000000000011577 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/lib/puppet/functions/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/lib/puppet/functio0000755005276200011600000000000013417162176034260 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000016400000000000011604 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/lib/puppet/functions/environment/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/lib/puppet/functio0000755005276200011600000000000013417162176034260 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000017300000000000011604 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/lib/puppet/functions/environment/data.rbpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/lib/puppet/functio0000644005276200011600000000110013417161721034245 0ustar jenkinsjenkinsPuppet::Functions.create_function(:'environment::data') do def data() { 'abc::a' => 'env_a', 'abc::c' => 'env_c', 'abc::d' => { 'k1' => 'env_d1', 'k2' => 'env_d2', 'k3' => 'env_d3' }, 'abc::e' => { 'k1' => 'env_e1', 'k3' => 'env_e3' }, 'bca::e' => { 'k1' => 'env_bca_e1', 'k3' => 'env_bca_e3' }, 'no_provider::e' => { 'k1' => 'env_no_provider_e1', 'k3' => 'env_no_provider_e3' }, 'abc::f' => { 'k1' => { 's1' => 'env_f11', 's2' => 'env_f12' }, 'k2' => { 's1' => 'env_f21', 's3' => 'env_f23' }}, 'abc::n' => nil } end end puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/0000755005276200011600000000000013417162176032255 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/abc/0000755005276200011600000000000013417162176033002 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/abc/lib/0000755005276200011600000000000013417162176033550 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015200000000000011601 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/abc/lib/puppet/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/abc/lib/pu0000755005276200011600000000000013417162176034115 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000016400000000000011604 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/abc/lib/puppet/functions/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/abc/lib/pu0000755005276200011600000000000013417162176034115 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000017000000000000011601 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/abc/lib/puppet/functions/abc/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/abc/lib/pu0000755005276200011600000000000013417162176034115 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000017700000000000011610 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/abc/lib/puppet/functions/abc/data.rbpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/abc/lib/pu0000644005276200011600000000052213417161721034111 0ustar jenkinsjenkinsPuppet::Functions.create_function(:'abc::data') do def data() { 'abc::b' => 'module_b', 'abc::c' => 'module_c', 'abc::e' => { 'k1' => 'module_e1', 'k2' => 'module_e2' }, 'abc::f' => { 'k1' => { 's1' => 'module_f11', 's3' => 'module_f13' }, 'k2' => { 's1' => 'module_f21', 's2' => 'module_f22' }}, } end end ././@LongLink0000644000000000000000000000015100000000000011600 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/abc/manifests/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/abc/manife0000755005276200011600000000000013417162176034162 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000016000000000000011600 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/abc/manifests/init.pppuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/abc/manife0000644005276200011600000000026713417161721034164 0ustar jenkinsjenkinsclass abc { if $block != 'no_block_present' { $result = lookup(*$args) |$names| { if $block == true { $names } else { $block } } } else { $result = lookup(*$args) } } ././@LongLink0000644000000000000000000000015400000000000011603 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/abc/metadata.jsonpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/abc/metada0000644005276200011600000000034513417161721034155 0ustar jenkinsjenkins{ "name": "example/abc", "version": "0.0.2", "source": "git@github.com/example/example-abc.git", "dependencies": [], "author": "Bob the Builder", "license": "Apache-2.0", "data_provider": "function" } puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/bad_data/0000755005276200011600000000000013417162176033774 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015000000000000011577 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/bad_data/lib/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/bad_data/l0000755005276200011600000000000013417162176034150 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015700000000000011606 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/bad_data/lib/puppet/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/bad_data/l0000755005276200011600000000000013417162176034150 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000017100000000000011602 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/bad_data/lib/puppet/functions/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/bad_data/l0000755005276200011600000000000013417162176034150 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000020200000000000011575 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/bad_data/lib/puppet/functions/bad_data/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/bad_data/l0000755005276200011600000000000013417162176034150 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000021100000000000011575 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/bad_data/lib/puppet/functions/bad_data/data.rbpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/bad_data/l0000644005276200011600000000032413417161721034144 0ustar jenkinsjenkinsPuppet::Functions.create_function(:'bad_data::data') do def data() { 'b' => 'module_b', # Intentionally bad key (no module prefix) 'bad_data::c' => 'module_c' # Good key. Should be OK } end end ././@LongLink0000644000000000000000000000015600000000000011605 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/bad_data/manifests/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/bad_data/m0000755005276200011600000000000013417162176034151 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000016500000000000011605 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/bad_data/manifests/init.pppuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/bad_data/m0000644005276200011600000000002313417161721034141 0ustar jenkinsjenkinsclass bad_data { } ././@LongLink0000644000000000000000000000016100000000000011601 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/bad_data/metadata.jsonpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/bad_data/m0000644005276200011600000000035713417161721034153 0ustar jenkinsjenkins{ "name": "example/bad_data", "version": "0.0.2", "source": "git@github.com/example/example-bad_data.git", "dependencies": [], "author": "Bob the Builder", "license": "Apache-2.0", "data_provider": "function" } puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/bca/0000755005276200011600000000000013417162176033002 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/bca/lib/0000755005276200011600000000000013417162176033550 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015200000000000011601 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/bca/lib/puppet/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/bca/lib/pu0000755005276200011600000000000013417162176034115 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000016400000000000011604 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/bca/lib/puppet/functions/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/bca/lib/pu0000755005276200011600000000000013417162176034115 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000017000000000000011601 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/bca/lib/puppet/functions/bca/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/bca/lib/pu0000755005276200011600000000000013417162176034115 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000017700000000000011610 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/bca/lib/puppet/functions/bca/data.rbpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/bca/lib/pu0000644005276200011600000000056213417161721034115 0ustar jenkinsjenkinsPuppet::Functions.create_function(:'bca::data') do def data() { 'bca::b' => 'module_bca_b', 'bca::c' => 'module_bca_c', 'bca::e' => { 'k1' => 'module_bca_e1', 'k2' => 'module_bca_e2' }, 'bca::f' => { 'k1' => { 's1' => 'module_bca_f11', 's3' => 'module_bca_f13' }, 'k2' => { 's1' => 'module_bca_f21', 's2' => 'module_bca_f22' }}, } end end ././@LongLink0000644000000000000000000000015100000000000011600 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/bca/manifests/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/bca/manife0000755005276200011600000000000013417162176034162 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000016000000000000011600 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/bca/manifests/init.pppuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/bca/manife0000644005276200011600000000001613417161721034154 0ustar jenkinsjenkinsclass bca { } ././@LongLink0000644000000000000000000000015400000000000011603 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/bca/metadata.jsonpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/bca/metada0000644005276200011600000000034513417161721034155 0ustar jenkinsjenkins{ "name": "example/bca", "version": "0.0.2", "source": "git@github.com/example/example-bca.git", "dependencies": [], "author": "Bob the Builder", "license": "Apache-2.0", "data_provider": "function" } ././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_json/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_json0000755005276200011600000000000013417162176034365 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015300000000000011602 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_json/data/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_json0000755005276200011600000000000013417162176034365 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000016500000000000011605 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_json/data/empty.jsonpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_json0000644005276200011600000000000013417161721034350 0ustar jenkinsjenkins././@LongLink0000644000000000000000000000016000000000000011600 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_json/hiera.yamlpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_json0000644005276200011600000000010013417161721034351 0ustar jenkinsjenkins--- :version: 4 :hierarchy: - :name: empty :backend: json ././@LongLink0000644000000000000000000000016000000000000011600 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_json/manifests/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_json0000755005276200011600000000000013417162176034365 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000016700000000000011607 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_json/manifests/init.pppuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_json0000644005276200011600000000002513417161721034357 0ustar jenkinsjenkinsclass empty_json { } ././@LongLink0000644000000000000000000000016300000000000011603 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_json/metadata.jsonpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_json0000644005276200011600000000036013417161721034361 0ustar jenkinsjenkins{ "name": "example/empty_json", "version": "0.0.2", "source": "git@github.com/example/example-empty_json.git", "dependencies": [], "author": "Bob the Builder", "license": "Apache-2.0", "data_provider": "hiera" } ././@LongLink0000644000000000000000000000015200000000000011601 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_key_json/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_key_0000755005276200011600000000000013417162176034343 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015700000000000011606 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_key_json/data/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_key_0000755005276200011600000000000013417162176034343 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000017500000000000011606 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_key_json/data/empty_key.jsonpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_key_0000644005276200011600000000005413417161721034337 0ustar jenkinsjenkins{ "empty_key_json::has_undef_value": null } ././@LongLink0000644000000000000000000000016400000000000011604 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_key_json/hiera.yamlpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_key_0000644005276200011600000000010413417161721034333 0ustar jenkinsjenkins--- :version: 4 :hierarchy: - :name: empty_key :backend: json ././@LongLink0000644000000000000000000000016400000000000011604 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_key_json/manifests/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_key_0000755005276200011600000000000013417162176034343 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000017300000000000011604 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_key_json/manifests/init.pppuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_key_0000644005276200011600000000003113417161721034332 0ustar jenkinsjenkinsclass empty_key_json { } ././@LongLink0000644000000000000000000000016700000000000011607 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_key_json/metadata.jsonpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_key_0000644005276200011600000000037013417161721034340 0ustar jenkinsjenkins{ "name": "example/empty_key_json", "version": "0.0.2", "source": "git@github.com/example/example-empty_key_json.git", "dependencies": [], "author": "Bob the Builder", "license": "Apache-2.0", "data_provider": "hiera" } ././@LongLink0000644000000000000000000000015200000000000011601 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_key_yaml/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_key_0000755005276200011600000000000013417162176034343 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015700000000000011606 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_key_yaml/data/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_key_0000755005276200011600000000000013417162176034343 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000017500000000000011606 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_key_yaml/data/empty_key.yamlpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_key_0000644005276200011600000000004313417161721034335 0ustar jenkinsjenkinsempty_key_yaml::has_undef_value: ~ ././@LongLink0000644000000000000000000000016400000000000011604 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_key_yaml/hiera.yamlpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_key_0000644005276200011600000000010413417161721034333 0ustar jenkinsjenkins--- :version: 4 :hierarchy: - :name: empty_key :backend: yaml ././@LongLink0000644000000000000000000000016400000000000011604 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_key_yaml/manifests/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_key_0000755005276200011600000000000013417162176034343 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000017300000000000011604 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_key_yaml/manifests/init.pppuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_key_0000644005276200011600000000003113417161721034332 0ustar jenkinsjenkinsclass empty_key_yaml { } ././@LongLink0000644000000000000000000000016700000000000011607 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_key_yaml/metadata.jsonpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_key_0000644005276200011600000000037013417161721034340 0ustar jenkinsjenkins{ "name": "example/empty_key_yaml", "version": "0.0.2", "source": "git@github.com/example/example-empty_key_yaml.git", "dependencies": [], "author": "Bob the Builder", "license": "Apache-2.0", "data_provider": "hiera" } ././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_yaml/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_yaml0000755005276200011600000000000013417162176034356 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015300000000000011602 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_yaml/data/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_yaml0000755005276200011600000000000013417162176034356 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000016500000000000011605 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_yaml/data/empty.yamlpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_yaml0000644005276200011600000000002113417161721034344 0ustar jenkinsjenkins--- empty_key: ~ ././@LongLink0000644000000000000000000000016000000000000011600 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_yaml/hiera.yamlpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_yaml0000644005276200011600000000010013417161721034342 0ustar jenkinsjenkins--- :version: 4 :hierarchy: - :name: empty :backend: yaml ././@LongLink0000644000000000000000000000016000000000000011600 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_yaml/manifests/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_yaml0000755005276200011600000000000013417162176034356 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000016700000000000011607 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_yaml/manifests/init.pppuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_yaml0000644005276200011600000000002513417161721034350 0ustar jenkinsjenkinsclass empty_yaml { } ././@LongLink0000644000000000000000000000016300000000000011603 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_yaml/metadata.jsonpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/empty_yaml0000644005276200011600000000036013417161721034352 0ustar jenkinsjenkins{ "name": "example/empty_yaml", "version": "0.0.2", "source": "git@github.com/example/example-empty_yaml.git", "dependencies": [], "author": "Bob the Builder", "license": "Apache-2.0", "data_provider": "hiera" } ././@LongLink0000644000000000000000000000015100000000000011600 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/hieraprovider/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/hieraprovi0000755005276200011600000000000013417162176034346 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015600000000000011605 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/hieraprovider/data/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/hieraprovi0000755005276200011600000000000013417162176034346 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000017000000000000011601 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/hieraprovider/data/first.jsonpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/hieraprovi0000644005276200011600000000017013417161721034341 0ustar jenkinsjenkins{ "hieraprovider::test::param_a": "module data param_a is 100", "test::param_b": "module data param_a is 100" } ././@LongLink0000644000000000000000000000016300000000000011603 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/hieraprovider/hiera.yamlpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/hieraprovi0000644005276200011600000000017713417161721034350 0ustar jenkinsjenkins--- :version: 4 :hierarchy: - :name: "two paths" :backend: json :paths: - "first" - "second_not_present" ././@LongLink0000644000000000000000000000016300000000000011603 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/hieraprovider/manifests/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/hieraprovi0000755005276200011600000000000013417162176034346 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000017200000000000011603 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/hieraprovider/manifests/init.pppuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/hieraprovi0000644005276200011600000000015113417161721034340 0ustar jenkinsjenkinsclass hieraprovider { class test($param_a = "param default is 100") { notify { "$param_a": } } } ././@LongLink0000644000000000000000000000016600000000000011606 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/hieraprovider/metadata.jsonpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/hieraprovi0000644005276200011600000000036613417161721034350 0ustar jenkinsjenkins{ "name": "example/hieraprovider", "version": "0.0.2", "source": "git@github.com/example/example-hieraprovider.git", "dependencies": [], "author": "Bob the Builder", "license": "Apache-2.0", "data_provider": "hiera" } puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/meta/0000755005276200011600000000000013417162176033203 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/meta/lib/0000755005276200011600000000000013417162176033751 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015300000000000011602 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/meta/lib/puppet/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/meta/lib/p0000755005276200011600000000000013417162176034131 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000016500000000000011605 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/meta/lib/puppet/functions/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/meta/lib/p0000755005276200011600000000000013417162176034131 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000017200000000000011603 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/meta/lib/puppet/functions/meta/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/meta/lib/p0000755005276200011600000000000013417162176034131 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000020100000000000011574 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/meta/lib/puppet/functions/meta/data.rbpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/meta/lib/p0000644005276200011600000000052713417161721034132 0ustar jenkinsjenkinsPuppet::Functions.create_function(:'meta::data') do def data() { 'meta::b' => 'module_b', 'meta::c' => 'module_c', 'meta::e' => { 'k1' => 'module_e1', 'k2' => 'module_e2' }, 'meta::f' => { 'k1' => { 's1' => 'module_f11', 's3' => 'module_f13' }, 'k2' => { 's1' => 'module_f21', 's2' => 'module_f22' }}, } end end ././@LongLink0000644000000000000000000000015200000000000011601 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/meta/manifests/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/meta/manif0000755005276200011600000000000013417162176034216 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000016100000000000011601 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/meta/manifests/init.pppuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/meta/manif0000644005276200011600000000005213417161721034210 0ustar jenkinsjenkinsclass meta { $result = lookup(*$args) } ././@LongLink0000644000000000000000000000015500000000000011604 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/meta/metadata.jsonpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/meta/metad0000644005276200011600000000034713417161721034217 0ustar jenkinsjenkins{ "name": "example/meta", "version": "0.0.2", "source": "git@github.com/example/example-meta.git", "dependencies": [], "author": "Bob the Builder", "license": "Apache-2.0", "data_provider": "function" } ././@LongLink0000644000000000000000000000014700000000000011605 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/no_provider/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/no_provide0000755005276200011600000000000013417162176034342 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000016100000000000011601 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/no_provider/manifests/puppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/no_provide0000755005276200011600000000000013417162176034342 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000017000000000000011601 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/no_provider/manifests/init.pppuppet-5.5.10/spec/fixtures/unit/functions/lookup_fixture/environments/production/modules/no_provide0000644005276200011600000000002613417161721034335 0ustar jenkinsjenkinsclass no_provider { } puppet-5.5.10/spec/fixtures/unit/indirector/0000755005276200011600000000000013417162176020743 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/indirector/data_binding/0000755005276200011600000000000013417162176023346 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/indirector/data_binding/hiera/0000755005276200011600000000000013417162176024436 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/indirector/data_binding/hiera/global.yaml0000644005276200011600000000023213417161721026552 0ustar jenkinsjenkins--- integer: 3000 string: 'apache' hash: user: 'Hightower' group: 'admin' mode: '0644' array: - '0.ntp.puppetlabs.com' - '1.ntp.puppetlabs.com' puppet-5.5.10/spec/fixtures/unit/indirector/data_binding/hiera/invalid.yaml0000644005276200011600000000001313417161721026735 0ustar jenkinsjenkins{ invalid: puppet-5.5.10/spec/fixtures/unit/indirector/hiera/0000755005276200011600000000000013417162176022033 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/indirector/hiera/global.yaml0000644005276200011600000000023213417161721024147 0ustar jenkinsjenkins--- integer: 3000 string: 'apache' hash: user: 'Hightower' group: 'admin' mode: '0644' array: - '0.ntp.puppetlabs.com' - '1.ntp.puppetlabs.com' puppet-5.5.10/spec/fixtures/unit/indirector/hiera/invalid.yaml0000644005276200011600000000001313417161721024332 0ustar jenkinsjenkins{ invalid: puppet-5.5.10/spec/fixtures/unit/module/0000755005276200011600000000000013417162176020066 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/module/trailing-comma.json0000644005276200011600000000101013417161721023647 0ustar jenkinsjenkins{ "name": "puppetlabs/ruby", "version": "0.0.2", "summary": "Manage Ruby", "source": "git@github.com/puppetlabs/puppetlabs-ruby.git", "project_page": "https://github.com/puppetlabs/puppetlabs-ruby", "author": "Puppet Labs", "license": "Apache-2.0", "operatingsystem_support": [ "RedHat", "OpenSUSE", "SLES", "SLED", "Debian", "Ubuntu" ], "puppet_version": [ 2.7, 3.0, 3.1, 3.2, 3.3, ], } puppet-5.5.10/spec/fixtures/unit/parser/0000755005276200011600000000000013417162176020075 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/parser/functions/0000755005276200011600000000000013417162176022105 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/parser/functions/create_resources/0000755005276200011600000000000013417162176025442 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/parser/functions/create_resources/foo/0000755005276200011600000000000013417162176026225 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/parser/functions/create_resources/foo/manifests/0000755005276200011600000000000013417162176030216 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/parser/functions/create_resources/foo/manifests/init.pp0000644005276200011600000000011713417161721031514 0ustar jenkinsjenkinsclass foo { create_resources('foo::wrongdefine', {'blah'=>{'one'=>'two'}}) } puppet-5.5.10/spec/fixtures/unit/parser/functions/create_resources/foo/manifests/wrongdefine.pp0000644005276200011600000000006013417161721033055 0ustar jenkinsjenkinsdefine foo::wrongdefine($one){ $foo = $one, } puppet-5.5.10/spec/fixtures/unit/parser/lexer/0000755005276200011600000000000013417162176021214 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/parser/lexer/aliastest.pp0000644005276200011600000000043013417161721023536 0ustar jenkinsjenkinsfile { "a file": path => "/tmp/aliastest", ensure => file } file { "another": path => "/tmp/aliastest2", ensure => file, require => File["a file"] } file { "a third": path => "/tmp/aliastest3", ensure => file, require => File["/tmp/aliastest"] } puppet-5.5.10/spec/fixtures/unit/parser/lexer/append.pp0000644005276200011600000000025713417161721023023 0ustar jenkinsjenkins$var=['/tmp/file1','/tmp/file2'] class arraytest { $var += ['/tmp/file3', '/tmp/file4'] file { $var: content => "test" } } include arraytest puppet-5.5.10/spec/fixtures/unit/parser/lexer/argumentdefaults.pp0000644005276200011600000000036613417161721025127 0ustar jenkinsjenkins# $Id$ define testargs($file, $mode = '0755') { file { $file: ensure => file, mode => $mode } } testargs { "testingname": file => "/tmp/argumenttest1" } testargs { "testingother": file => "/tmp/argumenttest2", mode => '0644' } puppet-5.5.10/spec/fixtures/unit/parser/lexer/arithmetic_expression.pp0000644005276200011600000000022613417161721026160 0ustar jenkinsjenkins $one = 1.30 $two = 2.034e-2 $result = ((( $two + 2) / $one) + 4 * 5.45) - (6 << 7) + (0x800 + -9) notice("result is $result == 1295.87692307692") puppet-5.5.10/spec/fixtures/unit/parser/lexer/arraytrailingcomma.pp0000644005276200011600000000013113417161721025430 0ustar jenkinsjenkinsfile { ["/tmp/arraytrailingcomma1","/tmp/arraytrailingcomma2", ]: content => "tmp" } puppet-5.5.10/spec/fixtures/unit/parser/lexer/casestatement.pp0000644005276200011600000000272413417161721024415 0ustar jenkinsjenkins# $Id$ $var = "value" case $var { "nope": { file { "/tmp/fakefile": mode => '0644', ensure => file } } "value": { file { "/tmp/existsfile": mode => '0755', ensure => file } } } $ovar = "yayness" case $ovar { "fooness": { file { "/tmp/nostillexistsfile": mode => '0644', ensure => file } } "booness", "yayness": { case $var { "nep": { file { "/tmp/noexistsfile": mode => '0644', ensure => file } } "value": { file { "/tmp/existsfile2": mode => '0755', ensure => file } } } } } case $ovar { "fooness": { file { "/tmp/nostillexistsfile": mode => '0644', ensure => file } } default: { file { "/tmp/existsfile3": mode => '0755', ensure => file } } } $bool = true case $bool { true: { file { "/tmp/existsfile4": mode => '0755', ensure => file } } } $yay = yay $a = yay $b = boo case $yay { $a: { file { "/tmp/existsfile5": mode => '0755', ensure => file } } $b: { file { "/tmp/existsfile5": mode => '0644', ensure => file } } default: { file { "/tmp/existsfile5": mode => '0711', ensure => file } } } $regexvar = "exists regex" case $regexvar { "no match": { file { "/tmp/existsfile6": mode => '0644', ensure => file } } /(.*) regex$/: { file { "/tmp/${1}file6": mode => '0755', ensure => file } } default: { file { "/tmp/existsfile6": mode => '0711', ensure => file } } } puppet-5.5.10/spec/fixtures/unit/parser/lexer/classheirarchy.pp0000644005276200011600000000044413417161721024556 0ustar jenkinsjenkins# $Id$ class base { file { "/tmp/classheir1": ensure => file, mode => '0755' } } class sub1 inherits base { file { "/tmp/classheir2": ensure => file, mode => '0755' } } class sub2 inherits base { file { "/tmp/classheir3": ensure => file, mode => '0755' } } include sub1, sub2 puppet-5.5.10/spec/fixtures/unit/parser/lexer/classincludes.pp0000644005276200011600000000047713417161721024414 0ustar jenkinsjenkins# $Id$ class base { file { "/tmp/classincludes1": ensure => file, mode => '0755' } } class sub1 inherits base { file { "/tmp/classincludes2": ensure => file, mode => '0755' } } class sub2 inherits base { file { "/tmp/classincludes3": ensure => file, mode => '0755' } } $sub = "sub2" include sub1, $sub puppet-5.5.10/spec/fixtures/unit/parser/lexer/classpathtest.pp0000644005276200011600000000023413417161721024431 0ustar jenkinsjenkins# $Id$ define mytype { file { "/tmp/classtest": ensure => file, mode => '0755' } } class testing { mytype { "componentname": } } include testing puppet-5.5.10/spec/fixtures/unit/parser/lexer/collection.pp0000644005276200011600000000026113417161721023702 0ustar jenkinsjenkinsclass one { @file { "/tmp/colltest1": content => "one" } @file { "/tmp/colltest2": content => "two" } } class two { File <| content == "one" |> } include one, two puppet-5.5.10/spec/fixtures/unit/parser/lexer/collection_override.pp0000644005276200011600000000014113417161721025576 0ustar jenkinsjenkins@file { "/tmp/collection": content => "whatever" } File<| |> { mode => '0600' } puppet-5.5.10/spec/fixtures/unit/parser/lexer/collection_within_virtual_definitions.pp0000644005276200011600000000057513417161721031435 0ustar jenkinsjenkinsdefine test($name) { file {"/tmp/collection_within_virtual_definitions1_$name.txt": content => "File name $name\n" } Test2 <||> } define test2() { file {"/tmp/collection_within_virtual_definitions2_$name.txt": content => "This is a test\n" } } node default { @test {"foo": name => "foo" } @test2 {"foo2": } Test <||> } puppet-5.5.10/spec/fixtures/unit/parser/lexer/componentmetaparams.pp0000644005276200011600000000025113417161721025623 0ustar jenkinsjenkinsfile { "/tmp/component1": ensure => file } define thing { file { $name: ensure => file } } thing { "/tmp/component2": require => File["/tmp/component1"] } puppet-5.5.10/spec/fixtures/unit/parser/lexer/componentrequire.pp0000644005276200011600000000043613417161721025152 0ustar jenkinsjenkinsdefine testfile($mode) { file { $name: mode => $mode, ensure => present } } testfile { "/tmp/testing_component_requires2": mode => '0755' } file { "/tmp/testing_component_requires1": mode => '0755', ensure => present, require => Testfile["/tmp/testing_component_requires2"] } puppet-5.5.10/spec/fixtures/unit/parser/lexer/deepclassheirarchy.pp0000644005276200011600000000075413417161721025420 0ustar jenkinsjenkins# $Id$ class base { file { "/tmp/deepclassheir1": ensure => file, mode => '0755' } } class sub1 inherits base { file { "/tmp/deepclassheir2": ensure => file, mode => '0755' } } class sub2 inherits sub1 { file { "/tmp/deepclassheir3": ensure => file, mode => '0755' } } class sub3 inherits sub2 { file { "/tmp/deepclassheir4": ensure => file, mode => '0755' } } class sub4 inherits sub3 { file { "/tmp/deepclassheir5": ensure => file, mode => '0755' } } include sub4 puppet-5.5.10/spec/fixtures/unit/parser/lexer/defineoverrides.pp0000644005276200011600000000041613417161721024726 0ustar jenkinsjenkins# $Id$ $file = "/tmp/defineoverrides1" define myfile($mode) { file { $name: ensure => file, mode => $mode } } class base { myfile { $file: mode => '0644' } } class sub inherits base { Myfile[$file] { mode => '0755', } # test the end-comma } include sub puppet-5.5.10/spec/fixtures/unit/parser/lexer/emptyclass.pp0000644005276200011600000000010113417161721023724 0ustar jenkinsjenkins# $Id$ define component { } class testing { } include testing puppet-5.5.10/spec/fixtures/unit/parser/lexer/emptyexec.pp0000644005276200011600000000010113417161721023543 0ustar jenkinsjenkinsexec { "touch /tmp/emptyexectest": path => "/usr/bin:/bin" } puppet-5.5.10/spec/fixtures/unit/parser/lexer/emptyifelse.pp0000644005276200011600000000010713417161721024074 0ustar jenkinsjenkins if false { } else { # nothing here } if true { # still nothing } puppet-5.5.10/spec/fixtures/unit/parser/lexer/falsevalues.pp0000644005276200011600000000010213417161721024053 0ustar jenkinsjenkins$value = false file { "/tmp/falsevalues$value": ensure => file } puppet-5.5.10/spec/fixtures/unit/parser/lexer/filecreate.pp0000644005276200011600000000033213417161721023651 0ustar jenkinsjenkins# $Id$ file { "/tmp/createatest": ensure => file, mode => '0755'; "/tmp/createbtest": ensure => file, mode => '0755' } file { "/tmp/createctest": ensure => file; "/tmp/createdtest": ensure => file; } puppet-5.5.10/spec/fixtures/unit/parser/lexer/fqdefinition.pp0000644005276200011600000000020313417161721024222 0ustar jenkinsjenkinsdefine one::two($ensure) { file { "/tmp/fqdefinition": ensure => $ensure } } one::two { "/tmp/fqdefinition": ensure => file } puppet-5.5.10/spec/fixtures/unit/parser/lexer/fqparents.pp0000644005276200011600000000030213417161721023546 0ustar jenkinsjenkinsclass base { class one { file { "/tmp/fqparent1": ensure => file } } } class two::three inherits base::one { file { "/tmp/fqparent2": ensure => file } } include two::three puppet-5.5.10/spec/fixtures/unit/parser/lexer/funccomma.pp0000644005276200011600000000020313417161721023513 0ustar jenkinsjenkins@file { ["/tmp/funccomma1","/tmp/funccomma2"]: content => "1" } realize( File["/tmp/funccomma1"], File["/tmp/funccomma2"] , ) puppet-5.5.10/spec/fixtures/unit/parser/lexer/hash.pp0000644005276200011600000000105113417161721022470 0ustar jenkinsjenkins $hash = { "file" => "/tmp/myhashfile1" } file { $hash["file"]: ensure => file, content => "content"; } $hash2 = { "a" => { key => "/tmp/myhashfile2" }} file { $hash2["a"][key]: ensure => file, content => "content"; } define test($a = { "b" => "c" }) { file { $a["b"]: ensure => file, content => "content" } } test { "test": a => { "b" => "/tmp/myhashfile3" } } $hash3 = { mykey => "/tmp/myhashfile4" } $key = "mykey" file { $hash3[$key]: ensure => file, content => "content" } puppet-5.5.10/spec/fixtures/unit/parser/lexer/ifexpression.pp0000644005276200011600000000031413417161721024264 0ustar jenkinsjenkins$one = 1 $two = 2 if ($one < $two) and (($two < 3) or ($two == 2)) { notice("True!") } if "test regex" =~ /(.*) regex/ { file { "/tmp/${1}iftest": ensure => file, mode => '0755' } } puppet-5.5.10/spec/fixtures/unit/parser/lexer/implicititeration.pp0000644005276200011600000000060413417161721025301 0ustar jenkinsjenkins# $Id$ $files = ["/tmp/iterationatest", "/tmp/iterationbtest"] file { $files: ensure => file, mode => '0755' } file { ["/tmp/iterationctest", "/tmp/iterationdtest"]: ensure => file, mode => '0755' } file { ["/tmp/iterationetest", "/tmp/iterationftest"]: ensure => file, mode => '0755'; ["/tmp/iterationgtest", "/tmp/iterationhtest"]: ensure => file, mode => '0755'; } puppet-5.5.10/spec/fixtures/unit/parser/lexer/multilinecomments.pp0000644005276200011600000000023713417161721025322 0ustar jenkinsjenkins /* file { "/tmp/multilinecomments": content => "pouet" } */ /* and another one for #2333, the whitespace after the end comment is here on purpose */ puppet-5.5.10/spec/fixtures/unit/parser/lexer/multipleclass.pp0000644005276200011600000000023013417161721024424 0ustar jenkinsjenkinsclass one { file { "/tmp/multipleclassone": content => "one" } } class one { file { "/tmp/multipleclasstwo": content => "two" } } include one puppet-5.5.10/spec/fixtures/unit/parser/lexer/multipleinstances.pp0000644005276200011600000000031613417161721025313 0ustar jenkinsjenkins# $Id$ file { "/tmp/multipleinstancesa": ensure => file, mode => '0755'; "/tmp/multipleinstancesb": ensure => file, mode => '0755'; "/tmp/multipleinstancesc": ensure => file, mode => '0755'; } puppet-5.5.10/spec/fixtures/unit/parser/lexer/multisubs.pp0000644005276200011600000000040613417161721023577 0ustar jenkinsjenkinsclass base { file { "/tmp/multisubtest": content => "base", mode => '0644' } } class sub1 inherits base { File["/tmp/multisubtest"] { mode => '0755' } } class sub2 inherits base { File["/tmp/multisubtest"] { content => sub2 } } include sub1, sub2 puppet-5.5.10/spec/fixtures/unit/parser/lexer/namevartest.pp0000644005276200011600000000035113417161721024100 0ustar jenkinsjenkinsdefine filetest($mode, $ensure = file) { file { $name: mode => $mode, ensure => $ensure } } filetest { "/tmp/testfiletest": mode => '0644'} filetest { "/tmp/testdirtest": mode => '0755', ensure => directory} puppet-5.5.10/spec/fixtures/unit/parser/lexer/scopetest.pp0000644005276200011600000000024413417161721023561 0ustar jenkinsjenkins $mode = 640 define thing { file { "/tmp/$name": ensure => file, mode => $mode } } class testing { $mode = 755 thing {scopetest: } } include testing puppet-5.5.10/spec/fixtures/unit/parser/lexer/selectorvalues.pp0000644005276200011600000000163713417161721024617 0ustar jenkinsjenkins$value1 = "" $value2 = true $value3 = false $value4 = yay $test = "yay" $mode1 = $value1 ? { "" => 755, default => 644 } $mode2 = $value2 ? { true => 755, default => 644 } $mode3 = $value3 ? { false => 755, default => 644 } $mode4 = $value4 ? { $test => 755, default => 644 } $mode5 = yay ? { $test => 755, default => 644 } $mode6 = $mode5 ? { 755 => 755 } $mode7 = "test regex" ? { /regex$/ => 755, default => 644 } file { "/tmp/selectorvalues1": ensure => file, mode => $mode1 } file { "/tmp/selectorvalues2": ensure => file, mode => $mode2 } file { "/tmp/selectorvalues3": ensure => file, mode => $mode3 } file { "/tmp/selectorvalues4": ensure => file, mode => $mode4 } file { "/tmp/selectorvalues5": ensure => file, mode => $mode5 } file { "/tmp/selectorvalues6": ensure => file, mode => $mode6 } file { "/tmp/selectorvalues7": ensure => file, mode => $mode7 } puppet-5.5.10/spec/fixtures/unit/parser/lexer/simpledefaults.pp0000644005276200011600000000011513417161721024566 0ustar jenkinsjenkins# $Id$ File { mode => '0755' } file { "/tmp/defaulttest": ensure => file } puppet-5.5.10/spec/fixtures/unit/parser/lexer/simpleselector.pp0000644005276200011600000000115013417161721024577 0ustar jenkinsjenkins# $Id$ $var = "value" file { "/tmp/snippetselectatest": ensure => file, mode => $var ? { nottrue => 641, value => 755 } } file { "/tmp/snippetselectbtest": ensure => file, mode => $var ? { nottrue => 644, default => 755 } } $othervar = "complex value" file { "/tmp/snippetselectctest": ensure => file, mode => $othervar ? { "complex value" => 755, default => 644 } } $anothervar = Yayness file { "/tmp/snippetselectdtest": ensure => file, mode => $anothervar ? { Yayness => 755, default => 644 } } puppet-5.5.10/spec/fixtures/unit/parser/lexer/singleary.pp0000644005276200011600000000046413417161721023551 0ustar jenkinsjenkins# $Id$ file { "/tmp/singleary1": ensure => file } file { "/tmp/singleary2": ensure => file } file { "/tmp/singleary3": ensure => file, require => [File["/tmp/singleary1"], File["/tmp/singleary2"]] } file { "/tmp/singleary4": ensure => file, require => [File["/tmp/singleary1"]] } puppet-5.5.10/spec/fixtures/unit/parser/lexer/singlequote.pp0000644005276200011600000000025113417161721024105 0ustar jenkinsjenkins# $Id$ file { "/tmp/singlequote1": ensure => file, content => 'a $quote' } file { "/tmp/singlequote2": ensure => file, content => 'some "\yayness\"' } puppet-5.5.10/spec/fixtures/unit/parser/lexer/singleselector.pp0000644005276200011600000000060313417161721024571 0ustar jenkinsjenkins$value1 = "" $value2 = true $value3 = false $value4 = yay $test = "yay" $mode1 = $value1 ? { "" => 755 } $mode2 = $value2 ? { true => 755 } $mode3 = $value3 ? { default => 755 } file { "/tmp/singleselector1": ensure => file, mode => $mode1 } file { "/tmp/singleselector2": ensure => file, mode => $mode2 } file { "/tmp/singleselector3": ensure => file, mode => $mode3 } puppet-5.5.10/spec/fixtures/unit/parser/lexer/subclass_name_duplication.pp0000644005276200011600000000034213417161721026761 0ustar jenkinsjenkins#!/usr/bin/env puppet class one::fake { file { "/tmp/subclass_name_duplication1": ensure => present } } class two::fake { file { "/tmp/subclass_name_duplication2": ensure => present } } include one::fake, two::fake puppet-5.5.10/spec/fixtures/unit/parser/lexer/tag.pp0000644005276200011600000000017013417161721022321 0ustar jenkinsjenkins# $Id$ $variable = value tag yayness, rahness tag booness, $variable file { "/tmp/settestingness": ensure => file } puppet-5.5.10/spec/fixtures/unit/parser/lexer/tagged.pp0000644005276200011600000000132213417161721023001 0ustar jenkinsjenkins# $Id$ tag testing tag(funtest) class tagdefine { $path = tagged(tagdefine) ? { true => "true", false => "false" } file { "/tmp/taggeddefine$path": ensure => file } } include tagdefine $yayness = tagged(yayness) ? { true => "true", false => "false" } $funtest = tagged(testing) ? { true => "true", false => "false" } $both = tagged(testing, yayness) ? { true => "true", false => "false" } $bothtrue = tagged(testing, testing) ? { true => "true", false => "false" } file { "/tmp/taggedyayness$yayness": ensure => file } file { "/tmp/taggedtesting$funtest": ensure => file } file { "/tmp/taggedboth$both": ensure => file } file { "/tmp/taggedbothtrue$bothtrue": ensure => file } puppet-5.5.10/spec/fixtures/unit/parser/lexer/virtualresources.pp0000644005276200011600000000061213417161721025170 0ustar jenkinsjenkinsclass one { @file { "/tmp/virtualtest1": content => "one" } @file { "/tmp/virtualtest2": content => "two" } @file { "/tmp/virtualtest3": content => "three" } @file { "/tmp/virtualtest4": content => "four" } } class two { File <| content == "one" |> realize File["/tmp/virtualtest2"] realize(File["/tmp/virtualtest3"], File["/tmp/virtualtest4"]) } include one, two puppet-5.5.10/spec/fixtures/unit/pops/0000755005276200011600000000000013417162176017562 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/binder/0000755005276200011600000000000013417162176021025 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/0000755005276200011600000000000013417162176024531 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/0000755005276200011600000000000013417162176025142 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/binder_config.yaml0000644005276200011600000000044713417161721030616 0ustar jenkinsjenkins--- version: 1 layers: [{name: site, include: 'confdir:/confdirtest'}, {name: test, include: 'echo:/quick/brown/fox'}, {name: modules, include: ['module:/*::default'], exclude: 'module:/bad::default/' } ] extensions: scheme_handlers: echo: 'PuppetX::Awesome2::EchoSchemeHandler' puppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/lib/0000755005276200011600000000000013417162176025710 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/lib/puppet/0000755005276200011600000000000013417162176027225 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/lib/puppet/bindings/0000755005276200011600000000000013417162176031022 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/lib/puppet/bindings/confdirtest.rb0000644005276200011600000000025013417161721033663 0ustar jenkinsjenkinsPuppet::Bindings.newbindings('confdirtest') do |scope| bind { name 'has_funny_hat' to 'the pope' } bind { name 'the_meaning_of_life' to 42 } endpuppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/modules/0000755005276200011600000000000013417162176026612 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/modules/awesome2/0000755005276200011600000000000013417162176030334 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/modules/awesome2/lib/0000755005276200011600000000000013417162176031102 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/modules/awesome2/lib/puppet/0000755005276200011600000000000013417162176032417 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015000000000000011577 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/modules/awesome2/lib/puppet/bindings/puppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/modules/awesome2/lib/puppet/bindin0000755005276200011600000000000013417162176033603 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000016100000000000011601 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/modules/awesome2/lib/puppet/bindings/awesome2/puppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/modules/awesome2/lib/puppet/bindin0000755005276200011600000000000013417162176033603 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000017300000000000011604 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/modules/awesome2/lib/puppet/bindings/awesome2/default.rbpuppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/modules/awesome2/lib/puppet/bindin0000644005276200011600000000063613417161721033605 0ustar jenkinsjenkinsPuppet::Bindings.newbindings('awesome2::default') do |scope| bind.name('all your base').to('are belong to us') bind.name('env_meaning_of_life').to(puppet_string("$environment thinks it is 42", __FILE__)) bind { name 'awesome_x' to 'golden' } bind { name 'the_meaning_of_life' to 100 } bind { name 'has_funny_hat' to 'kkk' } bind { name 'good_x' to 'golden' } endpuppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/modules/awesome2/lib/puppet_x/0000755005276200011600000000000013417162176032746 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015200000000000011601 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/modules/awesome2/lib/puppet_x/awesome2/puppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/modules/awesome2/lib/puppet_x/awes0000755005276200011600000000000013417162176033626 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000020000000000000011573 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/modules/awesome2/lib/puppet_x/awesome2/echo_scheme_handler.rbpuppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/modules/awesome2/lib/puppet_x/awes0000644005276200011600000000131313417161721033621 0ustar jenkinsjenkinsrequire 'puppet/plugins/binding_schemes' module PuppetX module Awesome2 # A binding scheme that echos its path # 'echo:/quick/brown/fox' becomes key '::quick::brown::fox' => 'echo: quick brown fox'. # (silly class for testing loading of extension) # class EchoSchemeHandler < Puppet::Plugins::BindingSchemes::BindingsSchemeHandler def contributed_bindings(uri, scope, composer) factory = ::Puppet::Pops::Binder::BindingsFactory bindings = factory.named_bindings("echo") bindings.bind.name(uri.path.gsub(/\//, '::')).to("echo: #{uri.path.gsub(/\//, ' ').strip!}") factory.contributed_bindings("echo", bindings.model) ### , nil) end end end endpuppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/modules/bad/0000755005276200011600000000000013417162176027340 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/modules/bad/lib/0000755005276200011600000000000013417162176030106 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/modules/bad/lib/puppet/0000755005276200011600000000000013417162176031423 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/modules/bad/lib/puppet/bindings/0000755005276200011600000000000013417162176033220 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000014700000000000011605 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/modules/bad/lib/puppet/bindings/bad/puppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/modules/bad/lib/puppet/bindings/ba0000755005276200011600000000000013417162176033523 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000016100000000000011601 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/modules/bad/lib/puppet/bindings/bad/default.rbpuppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/modules/bad/lib/puppet/bindings/ba0000644005276200011600000000031013417161721033512 0ustar jenkinsjenkinsnil + nil + nil # broken on purpose, this file should never be loaded Puppet::Bindings.newbindings('bad::default') do |scope| nil + nil + nil # broken on purpose, this should never be evaluated endpuppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/modules/good/0000755005276200011600000000000013417162176027542 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/modules/good/lib/0000755005276200011600000000000013417162176030310 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/modules/good/lib/puppet/0000755005276200011600000000000013417162176031625 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/modules/good/lib/puppet/bindings/0000755005276200011600000000000013417162176033422 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015100000000000011600 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/modules/good/lib/puppet/bindings/good/puppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/modules/good/lib/puppet/bindings/g0000755005276200011600000000000013417162176033571 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000016300000000000011603 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/modules/good/lib/puppet/bindings/good/default.rbpuppet-5.5.10/spec/fixtures/unit/pops/binder/bindings_composer/ok/modules/good/lib/puppet/bindings/g0000644005276200011600000000016313417161721033566 0ustar jenkinsjenkinsPuppet::Bindings.newbindings('good::default') do |scope| bind { name 'the_meaning_of_life' to 300 } endpuppet-5.5.10/spec/fixtures/unit/pops/binder/config/0000755005276200011600000000000013417162176022272 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/binder/config/binder_config/0000755005276200011600000000000013417162176025062 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/binder/config/binder_config/nolayer/0000755005276200011600000000000013417162176026533 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/binder/config/binder_config/nolayer/binder_config.yaml0000644005276200011600000000015313417161721032201 0ustar jenkinsjenkins--- version: 1 categories: - ['node', '$fqn'] - ['environment', '$environment'] - ['common', 'true'] puppet-5.5.10/spec/fixtures/unit/pops/binder/config/binder_config/ok/0000755005276200011600000000000013417162176025473 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/binder/config/binder_config/ok/binder_config.yaml0000644005276200011600000000035213417161721031142 0ustar jenkinsjenkins--- version: 1 layers: - {name: site, include: 'confdir:/'} - {name: modules, include: 'module:/*::test/', exclude: 'module:/bad::test/' } categories: - ['node', '$fqn'] - ['environment', '$environment'] - ['common', 'true']puppet-5.5.10/spec/fixtures/unit/pops/loaders/0000755005276200011600000000000013417162176021213 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/0000755005276200011600000000000013417162176022644 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/0000755005276200011600000000000013417162176031235 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/0000755005276200011600000000000013417162176032705 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee/0000755005276200011600000000000013417162176033646 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015600000000000011605 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee/functions/puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee/f0000755005276200011600000000000013417162176034014 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000017400000000000011605 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee/functions/usee_puppet.pppuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee/f0000644005276200011600000000011313417161721034004 0ustar jenkinsjenkinsfunction usee::usee_puppet() { "I'm the function usee::usee_puppet()" } ././@LongLink0000644000000000000000000000015000000000000011577 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee/lib/puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee/l0000755005276200011600000000000013417162176034022 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015700000000000011606 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee/lib/puppet/puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee/l0000755005276200011600000000000013417162176034022 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000017100000000000011602 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee/lib/puppet/functions/puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee/l0000755005276200011600000000000013417162176034022 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000017600000000000011607 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee/lib/puppet/functions/usee/puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee/l0000755005276200011600000000000013417162176034022 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000020700000000000011602 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee/lib/puppet/functions/usee/callee.rbpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee/l0000644005276200011600000000017513417161721034022 0ustar jenkinsjenkinsPuppet::Functions.create_function(:'usee::callee') do def callee(value) "usee::callee() was told '#{value}'" end end ././@LongLink0000644000000000000000000000021200000000000011576 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee/lib/puppet/functions/usee/usee_ruby.rbpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee/l0000644005276200011600000000017713417161721034024 0ustar jenkinsjenkinsPuppet::Functions.create_function(:'usee::usee_ruby') do def usee_ruby() "I'm the function usee::usee_ruby()" end end ././@LongLink0000644000000000000000000000016400000000000011604 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee/lib/puppet/type/puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee/l0000755005276200011600000000000013417162176034022 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000020000000000000011573 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee/lib/puppet/type/usee_type.rbpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee/l0000644005276200011600000000023013417161721034012 0ustar jenkinsjenkinsPuppet::Type.newtype(:usee_type) do newparam(:name, :namevar => true) do desc 'An arbitrary name used as the identity of the resource.' end end ././@LongLink0000644000000000000000000000015600000000000011605 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee/manifests/puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee/m0000755005276200011600000000000013417162176034023 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000016500000000000011605 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee/manifests/init.pppuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee/m0000644005276200011600000000017513417161721034023 0ustar jenkinsjenkinsfunction usee_puppet_init() { "I'm the function usee::usee_puppet_init()" } type Usee::One = Integer[1,1] class usee { } ././@LongLink0000644000000000000000000000015200000000000011601 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee/types/puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee/t0000755005276200011600000000000013417162176034032 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000016100000000000011601 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee/types/zero.pppuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee/t0000644005276200011600000000003713417161721034027 0ustar jenkinsjenkinstype Usee::Zero = Integer[0,0] puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee2/0000755005276200011600000000000013417162176033730 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015100000000000011600 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee2/lib/puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee2/0000755005276200011600000000000013417162176033730 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000016000000000000011600 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee2/lib/puppet/puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee2/0000755005276200011600000000000013417162176033730 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000017200000000000011603 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee2/lib/puppet/functions/puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee2/0000755005276200011600000000000013417162176033730 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000020000000000000011573 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee2/lib/puppet/functions/usee2/puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee2/0000755005276200011600000000000013417162176033730 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000021100000000000011575 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee2/lib/puppet/functions/usee2/callee.rbpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/usee2/0000644005276200011600000000017713417161721033732 0ustar jenkinsjenkinsPuppet::Functions.create_function(:'usee2::callee') do def callee(value) "usee2::callee() was told '#{value}'" end end puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/0000755005276200011600000000000013417162176033663 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015600000000000011605 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/functions/puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/f0000755005276200011600000000000013417162176034031 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000020600000000000011601 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/functions/puppet_calling_puppet.pppuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/f0000644005276200011600000000010513417161721034022 0ustar jenkinsjenkinsfunction user::puppet_calling_puppet () { usee::usee_puppet() } ././@LongLink0000644000000000000000000000021300000000000011577 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/functions/puppet_calling_puppet_init.pppuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/f0000644005276200011600000000011113417161721034017 0ustar jenkinsjenkinsfunction user::puppet_calling_puppet_init () { usee_puppet_init() } ././@LongLink0000644000000000000000000000020400000000000011577 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/functions/puppet_calling_ruby.pppuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/f0000644005276200011600000000010113417161721034016 0ustar jenkinsjenkinsfunction user::puppet_calling_ruby() { usee::usee_ruby() } ././@LongLink0000644000000000000000000000015000000000000011577 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/lib/puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/l0000755005276200011600000000000013417162176034037 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015700000000000011606 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/lib/puppet/puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/l0000755005276200011600000000000013417162176034037 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000017100000000000011602 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/lib/puppet/functions/puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/l0000755005276200011600000000000013417162176034037 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000017600000000000011607 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/lib/puppet/functions/user/puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/l0000755005276200011600000000000013417162176034037 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000020700000000000011602 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/lib/puppet/functions/user/caller.rbpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/l0000644005276200011600000000023413417161721034033 0ustar jenkinsjenkinsPuppet::Functions.create_function(:'user::caller') do def caller() call_function('usee::callee', 'passed value') + " + I am user::caller()" end end ././@LongLink0000644000000000000000000000021000000000000011574 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/lib/puppet/functions/user/caller2.rbpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/l0000644005276200011600000000024013417161721034030 0ustar jenkinsjenkinsPuppet::Functions.create_function(:'user::caller2') do def caller2() call_function('usee2::callee', 'passed value') + " + I am user::caller2()" end end ././@LongLink0000644000000000000000000000022400000000000011601 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/lib/puppet/functions/user/ruby_calling_puppet.rbpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/l0000644005276200011600000000022113417161721034027 0ustar jenkinsjenkinsPuppet::Functions.create_function(:'user::ruby_calling_puppet') do def ruby_calling_puppet() call_function('usee::usee_puppet') end end ././@LongLink0000644000000000000000000000023100000000000011577 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/lib/puppet/functions/user/ruby_calling_puppet_init.rbpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/l0000644005276200011600000000023213417161721034031 0ustar jenkinsjenkinsPuppet::Functions.create_function(:'user::ruby_calling_puppet_init') do def ruby_calling_puppet_init() call_function('usee_puppet_init') end end ././@LongLink0000644000000000000000000000022200000000000011577 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/lib/puppet/functions/user/ruby_calling_ruby.rbpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/l0000644005276200011600000000021213417161721034027 0ustar jenkinsjenkinsPuppet::Functions.create_function(:'user::ruby_calling_ruby') do def ruby_calling_ruby() call_function('usee::usee_ruby') end end ././@LongLink0000644000000000000000000000015600000000000011605 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/manifests/puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/m0000755005276200011600000000000013417162176034040 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000016500000000000011605 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/manifests/init.pppuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/m0000644005276200011600000000514313417161722034041 0ustar jenkinsjenkinsfunction puppet_init_calling_puppet() { usee::usee_puppet() } function puppet_init_calling_puppet_init() { usee_puppet_init() } function puppet_init_calling_ruby() { usee::usee_ruby() } class user { # Dummy resource. Added just to assert that a ruby type in another module is loaded correctly # by the auto loader Usee_type { name => 'pelle' } case $::case_number { 1: { # Call a puppet function that resides in usee/functions directly from init.pp # notify { 'case_1': message => usee::usee_puppet() } } 2: { # Call a puppet function that resides in usee/manifests/init.pp directly from init.pp # include usee notify { 'case_2': message => usee_puppet_init() } } 3: { # Call a ruby function that resides in usee directly from init.pp # notify { 'case_3': message => usee::usee_ruby() } } 4: { # Call a puppet function that resides in usee/functions from a puppet function under functions # notify { 'case_4': message => user::puppet_calling_puppet() } } 5: { # Call a puppet function that resides in usee/manifests/init.pp from a puppet function under functions # include usee notify { 'case_5': message => user::puppet_calling_puppet_init() } } 6: { # Call a ruby function that resides in usee from a puppet function under functions # notify { 'case_6': message => user::puppet_calling_ruby() } } 7: { # Call a puppet function that resides in usee/functions from a puppet function in init.pp # notify { 'case_7': message => puppet_init_calling_puppet() } } 8: { # Call a puppet function that resides in usee/manifests/init.pp from a puppet function in init.pp # include usee notify { 'case_8': message => puppet_init_calling_puppet_init() } } 9: { # Call a ruby function that resides in usee from a puppet function in init.pp # notify { 'case_9': message => puppet_init_calling_ruby() } } 10: { # Call a puppet function that resides in usee/functions from a ruby function in this module # notify { 'case_10': message => user::ruby_calling_puppet() } } 11: { # Call a puppet function that resides in usee/manifests/init.pp from a ruby function in this module # include usee notify { 'case_11': message => user::ruby_calling_puppet_init() } } 12: { # Call a ruby function that resides in usee from a ruby function in this module # notify { 'case_12': message => user::ruby_calling_ruby() } } } } ././@LongLink0000644000000000000000000000016100000000000011601 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/metadata.jsonpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/m0000644005276200011600000000022313417161721034032 0ustar jenkinsjenkins{ "name": "test-user", "author": "test", "description": "", "license": "", "source": "", "version": "1.0.0", "dependencies": [ ] } ././@LongLink0000644000000000000000000000015200000000000011601 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/types/puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/t0000755005276200011600000000000013417162176034047 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000017000000000000011601 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/types/withuseeone.pppuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/t0000644005276200011600000000005213417161721034041 0ustar jenkinsjenkinstype User::WithUseeOne = Array[Usee::One] ././@LongLink0000644000000000000000000000017100000000000011602 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/types/withuseezero.pppuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/dependent_modules_with_metadata/modules/user/t0000644005276200011600000000005413417161721034043 0ustar jenkinsjenkinstype User::WithUseeZero = Array[Usee::Zero] puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/0000755005276200011600000000000013417162176027400 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/usee/0000755005276200011600000000000013417162176030341 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/usee/lib/0000755005276200011600000000000013417162176031107 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/usee/lib/puppet/0000755005276200011600000000000013417162176032424 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/usee/lib/puppet/parser/puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/usee/lib/puppet/parser0000755005276200011600000000000013417162176033641 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000016000000000000011600 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/usee/lib/puppet/parser/functions/puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/usee/lib/puppet/parser0000755005276200011600000000000013417162176033641 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000017100000000000011602 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/usee/lib/puppet/parser/functions/callee.rbpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/usee/lib/puppet/parser0000644005276200011600000000030713417161721033636 0ustar jenkinsjenkinsmodule Puppet::Parser::Functions newfunction(:callee, :type => :rvalue, :doc => <<-EOS A function using the 3x API EOS ) do |arguments| "usee::callee() got '#{arguments[0]}'" end end ././@LongLink0000644000000000000000000000017400000000000011605 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/usee/lib/puppet/parser/functions/callee_ws.rbpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/usee/lib/puppet/parser0000644005276200011600000000032713417161721033640 0ustar jenkinsjenkinsmodule Puppet::Parser::Functions newfunction(:callee_ws, :type => :rvalue, :doc => <<-EOS A function using the 3x API EOS ) do |arguments| "usee::callee_ws() got '#{self['passed_in_scope']}'" end endpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/usee/metadata.json0000644005276200011600000000022013417161721033001 0ustar jenkinsjenkins{ "name": "test-usee", "author": "test", "description": "", "license": "", "source": "", "version": "1.0.0", "dependencies": [] } puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/user/0000755005276200011600000000000013417162176030356 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/user/functions/0000755005276200011600000000000013417162176032366 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015500000000000011604 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/user/functions/puppetcalled.pppuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/user/functions/puppetc0000644005276200011600000000011513417161721033761 0ustar jenkinsjenkinsfunction user::puppetcalled($who) { "Did you call to say you love $who?" } ././@LongLink0000644000000000000000000000015500000000000011604 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/user/functions/puppetcaller.pppuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/user/functions/puppetc0000644005276200011600000000011113417161721033755 0ustar jenkinsjenkinsfunction user::puppetcaller { "${callee(first)} - ${callee(second)}" } ././@LongLink0000644000000000000000000000015600000000000011605 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/user/functions/puppetcaller4.pppuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/user/functions/puppetc0000644005276200011600000000006213417161721033762 0ustar jenkinsjenkinsfunction user::puppetcaller4 { user::caller() } puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/user/lib/0000755005276200011600000000000013417162176031124 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/user/lib/puppet/0000755005276200011600000000000013417162176032441 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015100000000000011600 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/user/lib/puppet/functions/puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/user/lib/puppet/functi0000755005276200011600000000000013417162176033652 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015600000000000011605 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/user/lib/puppet/functions/user/puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/user/lib/puppet/functi0000755005276200011600000000000013417162176033652 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000016700000000000011607 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/user/lib/puppet/functions/user/caller.rbpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/user/lib/puppet/functi0000644005276200011600000000024013417161721033643 0ustar jenkinsjenkinsPuppet::Functions.create_function(:'user::caller') do def caller() call_function('callee', 'first') + ' - ' + call_function('callee', 'second') end end ././@LongLink0000644000000000000000000000017200000000000011603 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/user/lib/puppet/functions/user/caller_ws.rbpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/user/lib/puppet/functi0000644005276200011600000000052213417161721033646 0ustar jenkinsjenkinsPuppet::Functions.create_function(:'user::caller_ws', Puppet::Functions::InternalFunction) do dispatch :caller_ws do scope_param param 'String', :value end def caller_ws(scope, value) scope = scope.compiler.newscope(scope) scope['passed_in_scope'] = value call_function_with_scope(scope, 'callee_ws') end end ././@LongLink0000644000000000000000000000017600000000000011607 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/user/lib/puppet/functions/user/callingpuppet.rbpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/user/lib/puppet/functi0000644005276200011600000000021313417161721033643 0ustar jenkinsjenkinsPuppet::Functions.create_function(:'user::callingpuppet') do def callingpuppet() call_function('user::puppetcalled', 'me') end end puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/user/metadata.json0000644005276200011600000000024713417161721033027 0ustar jenkinsjenkins{ "name": "test-user", "author": "test", "description": "", "license": "", "source": "", "version": "1.0.0", "dependencies": [{ "name": "test/usee" }] } puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/module_no_lib/0000755005276200011600000000000013417162176025453 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/module_no_lib/modules/0000755005276200011600000000000013417162176027123 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/module_no_lib/modules/modulea/0000755005276200011600000000000013417162176030551 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/module_no_lib/modules/modulea/functions/0000755005276200011600000000000013417162176032561 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000014700000000000011605 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/module_no_lib/modules/modulea/functions/hello.pppuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/module_no_lib/modules/modulea/functions/hello.0000644005276200011600000000006313417161721033657 0ustar jenkinsjenkinsfunction modulea::hello() { "modulea::hello()" } puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/module_no_lib/modules/modulea/manifests/0000755005276200011600000000000013417162176032542 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/module_no_lib/modules/modulea/manifests/init.pppuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/module_no_lib/modules/modulea/manifests/init.p0000644005276200011600000000002313417161721033654 0ustar jenkinsjenkinsclass modulea { } puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/module_no_lib/modules/modulea/metadata.json0000644005276200011600000000024513417161721033220 0ustar jenkinsjenkins{ "name": "test-modulea", "author": "test", "license": "", "project_page": "", "source": "", "summary": "", "version": "1.0.0", "dependencies": [] } puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/no_modules/0000755005276200011600000000000013417162176025010 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/no_modules/manifests/0000755005276200011600000000000013417162176027001 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/no_modules/manifests/site.pp0000644005276200011600000000017613417161721030305 0ustar jenkinsjenkinsfunction bar() { $value_from_scope } class foo::bar { with(1) |$x| { notice $x } notify { bar(): } } include foo::bar puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/pp_resources/0000755005276200011600000000000013417162176025355 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/single_module/0000755005276200011600000000000013417162176025472 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/0000755005276200011600000000000013417162176027142 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/0000755005276200011600000000000013417162176030570 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/functions/0000755005276200011600000000000013417162176032600 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000014700000000000011605 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/functions/hello.pppuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/functions/hello.0000644005276200011600000000006313417161721033676 0ustar jenkinsjenkinsfunction modulea::hello() { "modulea::hello()" } ././@LongLink0000644000000000000000000000015000000000000011577 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/functions/subspace/puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/functions/subspa0000755005276200011600000000000013417162176034016 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000016000000000000011600 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/functions/subspace/hello.pppuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/functions/subspa0000644005276200011600000000010713417161721034011 0ustar jenkinsjenkinsfunction modulea::subspace::hello() { "modulea::subspace::hello()" } puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/lib/0000755005276200011600000000000013417162176031336 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/lib/puppet/0000755005276200011600000000000013417162176032653 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015200000000000011601 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/lib/puppet/functions/puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/lib/puppet/funct0000755005276200011600000000000013417162176033713 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000016200000000000011602 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/lib/puppet/functions/modulea/puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/lib/puppet/funct0000755005276200011600000000000013417162176033713 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000017600000000000011607 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/lib/puppet/functions/modulea/rb_func_a.rbpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/lib/puppet/funct0000644005276200011600000000016713417161721033714 0ustar jenkinsjenkinsPuppet::Functions.create_function(:'modulea::rb_func_a') do def rb_func_a() "I am modulea::rb_func_a()" end end././@LongLink0000644000000000000000000000016600000000000011606 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/lib/puppet/functions/rb_func_a.rbpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/lib/puppet/funct0000644005276200011600000000014313417161721033706 0ustar jenkinsjenkinsPuppet::Functions.create_function(:rb_func_a) do def rb_func_a() "I am rb_func_a()" end endpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/manifests/0000755005276200011600000000000013417162176032561 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000014600000000000011604 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/manifests/init.pppuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/manifests/init.p0000644005276200011600000000002313417161721033673 0ustar jenkinsjenkinsclass modulea { } puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/single_module/modules/modulea/metadata.json0000644005276200011600000000024513417161721033237 0ustar jenkinsjenkins{ "name": "test-modulea", "author": "test", "license": "", "project_page": "", "source": "", "summary": "", "version": "1.0.0", "dependencies": [] } puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/wo_metadata_module/0000755005276200011600000000000013417162176026476 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/wo_metadata_module/modules/0000755005276200011600000000000013417162176030146 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/wo_metadata_module/modules/moduleb/0000755005276200011600000000000013417162176031575 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/wo_metadata_module/modules/moduleb/lib/0000755005276200011600000000000013417162176032343 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/wo_metadata_module/modules/moduleb/lib/puppet/0000755005276200011600000000000013417162176033660 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015700000000000011606 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/wo_metadata_module/modules/moduleb/lib/puppet/functions/puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/wo_metadata_module/modules/moduleb/lib/puppet/0000755005276200011600000000000013417162176033660 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000016700000000000011607 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/wo_metadata_module/modules/moduleb/lib/puppet/functions/moduleb/puppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/wo_metadata_module/modules/moduleb/lib/puppet/0000755005276200011600000000000013417162176033660 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000020300000000000011576 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/wo_metadata_module/modules/moduleb/lib/puppet/functions/moduleb/rb_func_b.rbpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/wo_metadata_module/modules/moduleb/lib/puppet/0000644005276200011600000000032213417161721033652 0ustar jenkinsjenkinsPuppet::Functions.create_function(:'moduleb::rb_func_b') do def rb_func_b() # Should be able to call modulea::rb_func_a() call_function('modulea::rb_func_a') + " + I am moduleb::rb_func_b()" end endpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/wo_metadata_module/modules/moduleb/manifests/0000755005276200011600000000000013417162176033566 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015300000000000011602 Lustar rootrootpuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/wo_metadata_module/modules/moduleb/manifests/init.pppuppet-5.5.10/spec/fixtures/unit/pops/loaders/loaders/wo_metadata_module/modules/moduleb/manifests/i0000644005276200011600000000002313417161721033727 0ustar jenkinsjenkinsclass moduleb { } puppet-5.5.10/spec/fixtures/unit/pops/parser/0000755005276200011600000000000013417162176021056 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/0000755005276200011600000000000013417162176022175 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/aliastest.pp0000644005276200011600000000043013417161721024517 0ustar jenkinsjenkinsfile { "a file": path => "/tmp/aliastest", ensure => file } file { "another": path => "/tmp/aliastest2", ensure => file, require => File["a file"] } file { "a third": path => "/tmp/aliastest3", ensure => file, require => File["/tmp/aliastest"] } puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/append.pp0000644005276200011600000000025713417161721024004 0ustar jenkinsjenkins$var=['/tmp/file1','/tmp/file2'] class arraytest { $var += ['/tmp/file3', '/tmp/file4'] file { $var: content => "test" } } include arraytest puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/argumentdefaults.pp0000644005276200011600000000036313417161721026105 0ustar jenkinsjenkins# $Id$ define testargs($file, $mode = 755) { file { $file: ensure => file, mode => $mode } } testargs { "testingname": file => "/tmp/argumenttest1" } testargs { "testingother": file => "/tmp/argumenttest2", mode => '0644' } puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/arithmetic_expression.pp0000644005276200011600000000022613417161721027141 0ustar jenkinsjenkins $one = 1.30 $two = 2.034e-2 $result = ((( $two + 2) / $one) + 4 * 5.45) - (6 << 7) + (0x800 + -9) notice("result is $result == 1295.87692307692") puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/arraytrailingcomma.pp0000644005276200011600000000013113417161721026411 0ustar jenkinsjenkinsfile { ["/tmp/arraytrailingcomma1","/tmp/arraytrailingcomma2", ]: content => "tmp" } puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/casestatement.pp0000644005276200011600000000272413417161721025376 0ustar jenkinsjenkins# $Id$ $var = "value" case $var { "nope": { file { "/tmp/fakefile": mode => '0644', ensure => file } } "value": { file { "/tmp/existsfile": mode => '0755', ensure => file } } } $ovar = "yayness" case $ovar { "fooness": { file { "/tmp/nostillexistsfile": mode => '0644', ensure => file } } "booness", "yayness": { case $var { "nep": { file { "/tmp/noexistsfile": mode => '0644', ensure => file } } "value": { file { "/tmp/existsfile2": mode => '0755', ensure => file } } } } } case $ovar { "fooness": { file { "/tmp/nostillexistsfile": mode => '0644', ensure => file } } default: { file { "/tmp/existsfile3": mode => '0755', ensure => file } } } $bool = true case $bool { true: { file { "/tmp/existsfile4": mode => '0755', ensure => file } } } $yay = yay $a = yay $b = boo case $yay { $a: { file { "/tmp/existsfile5": mode => '0755', ensure => file } } $b: { file { "/tmp/existsfile5": mode => '0644', ensure => file } } default: { file { "/tmp/existsfile5": mode => '0711', ensure => file } } } $regexvar = "exists regex" case $regexvar { "no match": { file { "/tmp/existsfile6": mode => '0644', ensure => file } } /(.*) regex$/: { file { "/tmp/${1}file6": mode => '0755', ensure => file } } default: { file { "/tmp/existsfile6": mode => '0711', ensure => file } } } puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/classheirarchy.pp0000644005276200011600000000044413417161721025537 0ustar jenkinsjenkins# $Id$ class base { file { "/tmp/classheir1": ensure => file, mode => '0755' } } class sub1 inherits base { file { "/tmp/classheir2": ensure => file, mode => '0755' } } class sub2 inherits base { file { "/tmp/classheir3": ensure => file, mode => '0755' } } include sub1, sub2 puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/classincludes.pp0000644005276200011600000000047713417161721025375 0ustar jenkinsjenkins# $Id$ class base { file { "/tmp/classincludes1": ensure => file, mode => '0755' } } class sub1 inherits base { file { "/tmp/classincludes2": ensure => file, mode => '0755' } } class sub2 inherits base { file { "/tmp/classincludes3": ensure => file, mode => '0755' } } $sub = "sub2" include sub1, $sub puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/classpathtest.pp0000644005276200011600000000023413417161721025412 0ustar jenkinsjenkins# $Id$ define mytype { file { "/tmp/classtest": ensure => file, mode => '0755' } } class testing { mytype { "componentname": } } include testing puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/collection.pp0000644005276200011600000000026113417161721024663 0ustar jenkinsjenkinsclass one { @file { "/tmp/colltest1": content => "one" } @file { "/tmp/colltest2": content => "two" } } class two { File <| content == "one" |> } include one, two puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/collection_override.pp0000644005276200011600000000014113417161721026557 0ustar jenkinsjenkins@file { "/tmp/collection": content => "whatever" } File<| |> { mode => '0600' } puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/collection_within_virtual_definitions.pp0000644005276200011600000000057513417161721032416 0ustar jenkinsjenkinsdefine test($name) { file {"/tmp/collection_within_virtual_definitions1_$name.txt": content => "File name $name\n" } Test2 <||> } define test2() { file {"/tmp/collection_within_virtual_definitions2_$name.txt": content => "This is a test\n" } } node default { @test {"foo": name => "foo" } @test2 {"foo2": } Test <||> } puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/componentmetaparams.pp0000644005276200011600000000025113417161721026604 0ustar jenkinsjenkinsfile { "/tmp/component1": ensure => file } define thing { file { $name: ensure => file } } thing { "/tmp/component2": require => File["/tmp/component1"] } puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/componentrequire.pp0000644005276200011600000000043613417161721026133 0ustar jenkinsjenkinsdefine testfile($mode) { file { $name: mode => $mode, ensure => present } } testfile { "/tmp/testing_component_requires2": mode => '0755' } file { "/tmp/testing_component_requires1": mode => '0755', ensure => present, require => Testfile["/tmp/testing_component_requires2"] } puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/deepclassheirarchy.pp0000644005276200011600000000075413417161721026401 0ustar jenkinsjenkins# $Id$ class base { file { "/tmp/deepclassheir1": ensure => file, mode => '0755' } } class sub1 inherits base { file { "/tmp/deepclassheir2": ensure => file, mode => '0755' } } class sub2 inherits sub1 { file { "/tmp/deepclassheir3": ensure => file, mode => '0755' } } class sub3 inherits sub2 { file { "/tmp/deepclassheir4": ensure => file, mode => '0755' } } class sub4 inherits sub3 { file { "/tmp/deepclassheir5": ensure => file, mode => '0755' } } include sub4 puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/defineoverrides.pp0000644005276200011600000000041613417161721025707 0ustar jenkinsjenkins# $Id$ $file = "/tmp/defineoverrides1" define myfile($mode) { file { $name: ensure => file, mode => $mode } } class base { myfile { $file: mode => '0644' } } class sub inherits base { Myfile[$file] { mode => '0755', } # test the end-comma } include sub puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/emptyclass.pp0000644005276200011600000000010113417161721024705 0ustar jenkinsjenkins# $Id$ define component { } class testing { } include testing puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/emptyexec.pp0000644005276200011600000000010113417161721024524 0ustar jenkinsjenkinsexec { "touch /tmp/emptyexectest": path => "/usr/bin:/bin" } puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/emptyifelse.pp0000644005276200011600000000010713417161721025055 0ustar jenkinsjenkins if false { } else { # nothing here } if true { # still nothing } puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/falsevalues.pp0000644005276200011600000000010213417161721025034 0ustar jenkinsjenkins$value = false file { "/tmp/falsevalues$value": ensure => file } puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/filecreate.pp0000644005276200011600000000033213417161721024632 0ustar jenkinsjenkins# $Id$ file { "/tmp/createatest": ensure => file, mode => '0755'; "/tmp/createbtest": ensure => file, mode => '0755' } file { "/tmp/createctest": ensure => file; "/tmp/createdtest": ensure => file; } puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/fqdefinition.pp0000644005276200011600000000020313417161721025203 0ustar jenkinsjenkinsdefine one::two($ensure) { file { "/tmp/fqdefinition": ensure => $ensure } } one::two { "/tmp/fqdefinition": ensure => file } puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/fqparents.pp0000644005276200011600000000030213417161721024527 0ustar jenkinsjenkinsclass base { class one { file { "/tmp/fqparent1": ensure => file } } } class two::three inherits base::one { file { "/tmp/fqparent2": ensure => file } } include two::three puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/funccomma.pp0000644005276200011600000000020313417161721024474 0ustar jenkinsjenkins@file { ["/tmp/funccomma1","/tmp/funccomma2"]: content => "1" } realize( File["/tmp/funccomma1"], File["/tmp/funccomma2"] , ) puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/hash.pp0000644005276200011600000000105113417161721023451 0ustar jenkinsjenkins $hash = { "file" => "/tmp/myhashfile1" } file { $hash["file"]: ensure => file, content => "content"; } $hash2 = { "a" => { key => "/tmp/myhashfile2" }} file { $hash2["a"][key]: ensure => file, content => "content"; } define test($a = { "b" => "c" }) { file { $a["b"]: ensure => file, content => "content" } } test { "test": a => { "b" => "/tmp/myhashfile3" } } $hash3 = { mykey => "/tmp/myhashfile4" } $key = "mykey" file { $hash3[$key]: ensure => file, content => "content" } puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/ifexpression.pp0000644005276200011600000000031413417161721025245 0ustar jenkinsjenkins$one = 1 $two = 2 if ($one < $two) and (($two < 3) or ($two == 2)) { notice("True!") } if "test regex" =~ /(.*) regex/ { file { "/tmp/${1}iftest": ensure => file, mode => '0755' } } puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/implicititeration.pp0000644005276200011600000000060413417161721026262 0ustar jenkinsjenkins# $Id$ $files = ["/tmp/iterationatest", "/tmp/iterationbtest"] file { $files: ensure => file, mode => '0755' } file { ["/tmp/iterationctest", "/tmp/iterationdtest"]: ensure => file, mode => '0755' } file { ["/tmp/iterationetest", "/tmp/iterationftest"]: ensure => file, mode => '0755'; ["/tmp/iterationgtest", "/tmp/iterationhtest"]: ensure => file, mode => '0755'; } puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/multilinecomments.pp0000644005276200011600000000023713417161721026303 0ustar jenkinsjenkins /* file { "/tmp/multilinecomments": content => "pouet" } */ /* and another one for #2333, the whitespace after the end comment is here on purpose */ puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/multipleclass.pp0000644005276200011600000000023013417161721025405 0ustar jenkinsjenkinsclass one { file { "/tmp/multipleclassone": content => "one" } } class one { file { "/tmp/multipleclasstwo": content => "two" } } include one puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/multipleinstances.pp0000644005276200011600000000031613417161721026274 0ustar jenkinsjenkins# $Id$ file { "/tmp/multipleinstancesa": ensure => file, mode => '0755'; "/tmp/multipleinstancesb": ensure => file, mode => '0755'; "/tmp/multipleinstancesc": ensure => file, mode => '0755'; } puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/multisubs.pp0000644005276200011600000000040613417161721024560 0ustar jenkinsjenkinsclass base { file { "/tmp/multisubtest": content => "base", mode => '0644' } } class sub1 inherits base { File["/tmp/multisubtest"] { mode => '0755' } } class sub2 inherits base { File["/tmp/multisubtest"] { content => sub2 } } include sub1, sub2 puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/namevartest.pp0000644005276200011600000000035113417161721025061 0ustar jenkinsjenkinsdefine filetest($mode, $ensure = file) { file { $name: mode => $mode, ensure => $ensure } } filetest { "/tmp/testfiletest": mode => '0644'} filetest { "/tmp/testdirtest": mode => '0755', ensure => directory} puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/scopetest.pp0000644005276200011600000000024413417161721024542 0ustar jenkinsjenkins $mode = 640 define thing { file { "/tmp/$name": ensure => file, mode => $mode } } class testing { $mode = 755 thing {scopetest: } } include testing puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/selectorvalues.pp0000644005276200011600000000163713417161721025600 0ustar jenkinsjenkins$value1 = "" $value2 = true $value3 = false $value4 = yay $test = "yay" $mode1 = $value1 ? { "" => 755, default => 644 } $mode2 = $value2 ? { true => 755, default => 644 } $mode3 = $value3 ? { false => 755, default => 644 } $mode4 = $value4 ? { $test => 755, default => 644 } $mode5 = yay ? { $test => 755, default => 644 } $mode6 = $mode5 ? { 755 => 755 } $mode7 = "test regex" ? { /regex$/ => 755, default => 644 } file { "/tmp/selectorvalues1": ensure => file, mode => $mode1 } file { "/tmp/selectorvalues2": ensure => file, mode => $mode2 } file { "/tmp/selectorvalues3": ensure => file, mode => $mode3 } file { "/tmp/selectorvalues4": ensure => file, mode => $mode4 } file { "/tmp/selectorvalues5": ensure => file, mode => $mode5 } file { "/tmp/selectorvalues6": ensure => file, mode => $mode6 } file { "/tmp/selectorvalues7": ensure => file, mode => $mode7 } puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/simpledefaults.pp0000644005276200011600000000011513417161721025547 0ustar jenkinsjenkins# $Id$ File { mode => '0755' } file { "/tmp/defaulttest": ensure => file } puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/simpleselector.pp0000644005276200011600000000115013417161721025560 0ustar jenkinsjenkins# $Id$ $var = "value" file { "/tmp/snippetselectatest": ensure => file, mode => $var ? { nottrue => 641, value => 755 } } file { "/tmp/snippetselectbtest": ensure => file, mode => $var ? { nottrue => 644, default => 755 } } $othervar = "complex value" file { "/tmp/snippetselectctest": ensure => file, mode => $othervar ? { "complex value" => 755, default => 644 } } $anothervar = Yayness file { "/tmp/snippetselectdtest": ensure => file, mode => $anothervar ? { Yayness => 755, default => 644 } } puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/singleary.pp0000644005276200011600000000046413417161721024532 0ustar jenkinsjenkins# $Id$ file { "/tmp/singleary1": ensure => file } file { "/tmp/singleary2": ensure => file } file { "/tmp/singleary3": ensure => file, require => [File["/tmp/singleary1"], File["/tmp/singleary2"]] } file { "/tmp/singleary4": ensure => file, require => [File["/tmp/singleary1"]] } puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/singlequote.pp0000644005276200011600000000025113417161721025066 0ustar jenkinsjenkins# $Id$ file { "/tmp/singlequote1": ensure => file, content => 'a $quote' } file { "/tmp/singlequote2": ensure => file, content => 'some "\yayness\"' } puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/singleselector.pp0000644005276200011600000000060313417161721025552 0ustar jenkinsjenkins$value1 = "" $value2 = true $value3 = false $value4 = yay $test = "yay" $mode1 = $value1 ? { "" => 755 } $mode2 = $value2 ? { true => 755 } $mode3 = $value3 ? { default => 755 } file { "/tmp/singleselector1": ensure => file, mode => $mode1 } file { "/tmp/singleselector2": ensure => file, mode => $mode2 } file { "/tmp/singleselector3": ensure => file, mode => $mode3 } puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/subclass_name_duplication.pp0000644005276200011600000000034213417161721027742 0ustar jenkinsjenkins#!/usr/bin/env puppet class one::fake { file { "/tmp/subclass_name_duplication1": ensure => present } } class two::fake { file { "/tmp/subclass_name_duplication2": ensure => present } } include one::fake, two::fake puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/tag.pp0000644005276200011600000000017013417161721023302 0ustar jenkinsjenkins# $Id$ $variable = value tag yayness, rahness tag booness, $variable file { "/tmp/settestingness": ensure => file } puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/tagged.pp0000644005276200011600000000132213417161721023762 0ustar jenkinsjenkins# $Id$ tag testing tag(funtest) class tagdefine { $path = tagged(tagdefine) ? { true => "true", false => "false" } file { "/tmp/taggeddefine$path": ensure => file } } include tagdefine $yayness = tagged(yayness) ? { true => "true", false => "false" } $funtest = tagged(testing) ? { true => "true", false => "false" } $both = tagged(testing, yayness) ? { true => "true", false => "false" } $bothtrue = tagged(testing, testing) ? { true => "true", false => "false" } file { "/tmp/taggedyayness$yayness": ensure => file } file { "/tmp/taggedtesting$funtest": ensure => file } file { "/tmp/taggedboth$both": ensure => file } file { "/tmp/taggedbothtrue$bothtrue": ensure => file } puppet-5.5.10/spec/fixtures/unit/pops/parser/lexer/virtualresources.pp0000644005276200011600000000061213417161721026151 0ustar jenkinsjenkinsclass one { @file { "/tmp/virtualtest1": content => "one" } @file { "/tmp/virtualtest2": content => "two" } @file { "/tmp/virtualtest3": content => "three" } @file { "/tmp/virtualtest4": content => "four" } } class two { File <| content == "one" |> realize File["/tmp/virtualtest2"] realize(File["/tmp/virtualtest3"], File["/tmp/virtualtest4"]) } include one, two puppet-5.5.10/spec/fixtures/unit/provider/0000755005276200011600000000000013417162176020433 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/aix_object/0000755005276200011600000000000013417162176022542 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/aix_object/aix_colon_list_real_world_input.out0000644005276200011600000000052413417161721031726 0ustar jenkinsjenkinsroot:0:system:system,bin,sys,security,cron,audit,lp:/:/usr/bin/ksh:root:general:true:false:false:true:true:system:nosak:ALL:0:SYSTEM:NONE:22:files:compat:0:0:false:0:0:-1:0:0:0:0:0:0:0:8:0:0:0:-1:-1:-1:-1:-1:-1:-1:1527849270:1533085305:ssh:ssh:fd8c#!:215d#!:178e#!:12#!:290#!:fa72#!:fab2#!:882:10.10.28.247:147:This is some comment I added puppet-5.5.10/spec/fixtures/unit/provider/aix_object/aix_colon_list_real_world_output.out0000644005276200011600000000075113417161721032131 0ustar jenkinsjenkins["root", "0", "system", "system,bin,sys,security,cron,audit,lp", "/", "/usr/bin/ksh", "root", "general", "true", "false", "false", "true", "true", "system", "nosak", "ALL", "0", "SYSTEM", "NONE", "22", "files", "compat", "0", "0", "false", "0", "0", "-1", "0", "0", "0", "0", "0", "0", "0", "8", "0", "0", "0", "-1", "-1", "-1", "-1", "-1", "-1", "-1", "1527849270", "1533085305", "ssh", "ssh", "fd8c:215d:178e:12:290:fa72:fab2:882", "10.10.28.247", "147", "This is some comment I added"] puppet-5.5.10/spec/fixtures/unit/provider/cron/0000755005276200011600000000000013417162176021374 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/cron/crontab/0000755005276200011600000000000013417162176023024 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/cron/crontab/single_line.yaml0000644005276200011600000001220213417161721026170 0ustar jenkinsjenkins--- :longcommment: :text: "# This is a comment" :record: :line: "# This is a comment" :record_type: :comment :special: :text: "@hourly /bin/date" :record: :special: hourly :command: /bin/date :record_type: :crontab :long_name: :text: "# Puppet Name: long_name" :record: :line: "# Puppet Name: long_name" :name: long_name :record_type: :comment :multiple_minutes: :text: 5,15 * * * * /bin/date :record: :minute: - "5" - "15" :command: /bin/date :record_type: :crontab :environment: :text: ONE=TWO :record: :line: ONE=TWO :record_type: :environment :empty: :text: "" :record: :line: "" :record_type: :blank :simple: :text: "* * * * * /bin/date" :record: :command: /bin/date :record_type: :crontab :whitespace: :text: " " :record: :line: " " :record_type: :blank :minute_and_hour: :text: 5 15 * * * /bin/date :record: :minute: - "5" :hour: - "15" :command: /bin/date :record_type: :crontab :lowercase_environment: :text: a=b :record: :line: a=b :record_type: :environment :special_with_spaces: :text: "@daily /bin/echo testing" :record: :special: daily :command: /bin/echo testing :record_type: :crontab :tabs: :text: !binary | CQ== :record: :line: !binary | CQ== :record_type: :blank :multiple_minute_and_hour: :text: 5,10 15,20 * * * /bin/date :record: :minute: - "5" - "10" :hour: - "15" - "20" :command: /bin/date :record_type: :crontab :name: :text: "# Puppet Name: testing" :record: :line: "# Puppet Name: testing" :name: testing :record_type: :comment :another_env: :text: Testing=True :record: :line: Testing=True :record_type: :environment :shortcomment: :text: "#" :record: :line: "#" :record_type: :comment :spaces_in_command: :text: "* * * * * /bin/echo testing" :record: :command: /bin/echo testing :record_type: :crontab :fourth_env: :text: True=False :record: :line: True=False :record_type: :environment :simple_with_minute: :text: 5 * * * * /bin/date :record: :minute: - "5" :command: /bin/date :record_type: :crontab :spaces_in_command_with_times: :text: 5,10 15,20 * * * /bin/echo testing :record: :minute: - "5" - "10" :hour: - "15" - "20" :command: /bin/echo testing :record_type: :crontab :name_with_spaces: :text: "# Puppet Name: another name" :record: :line: "# Puppet Name: another name" :name: another name :record_type: :comment --- :longcommment: :text: "# This is a comment" :record: :line: "# This is a comment" :record_type: :comment :special: :text: "@hourly /bin/date" :record: :special: hourly :command: /bin/date :record_type: :crontab :long_name: :text: "# Puppet Name: long_name" :record: :line: "# Puppet Name: long_name" :name: long_name :record_type: :comment :multiple_minutes: :text: 5,15 * * * * /bin/date :record: :minute: - "5" - "15" :command: /bin/date :record_type: :crontab :environment: :text: ONE=TWO :record: :line: ONE=TWO :record_type: :environment :empty: :text: "" :record: :line: "" :record_type: :blank :simple: :text: "* * * * * /bin/date" :record: :command: /bin/date :record_type: :crontab :whitespace: :text: " " :record: :line: " " :record_type: :blank :minute_and_hour: :text: 5 15 * * * /bin/date :record: :minute: - "5" :hour: - "15" :command: /bin/date :record_type: :crontab :lowercase_environment: :text: a=b :record: :line: a=b :record_type: :environment :special_with_spaces: :text: "@daily /bin/echo testing" :record: :special: daily :command: /bin/echo testing :record_type: :crontab :tabs: :text: !binary | CQ== :record: :line: !binary | CQ== :record_type: :blank :multiple_minute_and_hour: :text: 5,10 15,20 * * * /bin/date :record: :minute: - "5" - "10" :hour: - "15" - "20" :command: /bin/date :record_type: :crontab :name: :text: "# Puppet Name: testing" :record: :line: "# Puppet Name: testing" :name: testing :record_type: :comment :another_env: :text: Testing=True :record: :line: Testing=True :record_type: :environment :shortcomment: :text: "#" :record: :line: "#" :record_type: :comment :spaces_in_command: :text: "* * * * * /bin/echo testing" :record: :command: /bin/echo testing :record_type: :crontab :fourth_env: :text: True=False :record: :line: True=False :record_type: :environment :simple_with_minute: :text: 5 * * * * /bin/date :record: :minute: - "5" :command: /bin/date :record_type: :crontab :spaces_in_command_with_times: :text: 5,10 15,20 * * * /bin/echo testing :record: :minute: - "5" - "10" :hour: - "15" - "20" :command: /bin/echo testing :record_type: :crontab :name_with_spaces: :text: "# Puppet Name: another name" :record: :line: "# Puppet Name: another name" :name: another name :record_type: :comment puppet-5.5.10/spec/fixtures/unit/provider/cron/crontab/vixie_header.txt0000644005276200011600000000026413417161721026216 0ustar jenkinsjenkins# DO NOT EDIT THIS FILE - edit the master and reinstall. # (- installed on Thu Apr 12 12:16:01 2007) # (Cron version V5.0 -- $Id: crontab.c,v 1.12 2004/01/23 18:56:42 vixie Exp $) puppet-5.5.10/spec/fixtures/unit/provider/cron/parsed/0000755005276200011600000000000013417162176022652 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/cron/parsed/managed0000644005276200011600000000022013417161721024156 0ustar jenkinsjenkins# Puppet Name: real_job * * * * * /bin/true # Puppet Name: complex_job MAILTO=foo@example.com SHELL=/bin/sh @reboot /bin/true >> /dev/null 2>&1 puppet-5.5.10/spec/fixtures/unit/provider/cron/parsed/simple0000644005276200011600000000054613417161721024066 0ustar jenkinsjenkins# use /bin/sh to run commands, no matter what /etc/passwd says SHELL=/bin/sh # mail any output to `paul', no matter whose crontab this is MAILTO=paul # # run five minutes after midnight, every day 5 0 * * * $HOME/bin/daily.job >> $HOME/tmp/out 2>&1 # run at 2:15pm on the first of every month -- output mailed to paul 15 14 1 * * $HOME/bin/monthly puppet-5.5.10/spec/fixtures/unit/provider/package/0000755005276200011600000000000013417162176022026 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/package/gem/0000755005276200011600000000000013417162176022576 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/package/gem/gem-list-single-package0000644005276200011600000000004613417161721027105 0ustar jenkinsjenkins *** REMOTE GEMS *** bundler (1.6.2) puppet-5.5.10/spec/fixtures/unit/provider/package/gem/line-with-1.8.5-warning0000644005276200011600000000061013417161721026523 0ustar jenkinsjenkins/home/jenkins/.rvm/gems/ruby-1.8.5-p231@global/gems/rubygems-bundler-0.9.0/lib/rubygems-bundler/regenerate_binstubs_command.rb:34: warning: parenthesize argument(s) for future version *** LOCAL GEMS *** columnize (0.3.2) diff-lcs (1.1.3) metaclass (0.0.1) mocha (0.10.5) rake (0.8.7) rspec-core (2.9.0) rspec-expectations (2.9.1) rspec-mocks (2.9.0) rubygems-bundler (0.9.0) rvm (1.11.3.3) puppet-5.5.10/spec/fixtures/unit/provider/package/openbsd/0000755005276200011600000000000013417162176023460 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/package/openbsd/pkginfo.detail0000644005276200011600000000105713417161721026277 0ustar jenkinsjenkinsInformation for bash-3.1.17 Comment: GNU Bourne Again Shell Description: Bash is the GNU Project's Bourne Again SHell, an sh-compatible command language interpreter that executes commands read from the standard input or from a file. Bash also incorporates useful features from the Korn and C shells (ksh and csh). Bash is intended to be a conformant implementation of the IEEE POSIX Shell and Tools specification (IEEE Working Group 1003.2). Maintainer: Christian Weisgerber WWW: http://cnswww.cns.cwru.edu/~chet/bash/bashtop.html puppet-5.5.10/spec/fixtures/unit/provider/package/openbsd/pkginfo.list0000644005276200011600000000107713417161721026012 0ustar jenkinsjenkinsbash-3.1.17 GNU Bourne Again Shell bzip2-1.0.3 block-sorting file compressor, unencumbered expat-2.0.0 XML 1.0 parser written in C gettext-0.14.5p1 GNU gettext libiconv-1.9.2p3 character set conversion library lzo-1.08p1 portable speedy lossless data compression library openvpn-2.0.6 easy-to-use, robust, and highly configurable VPN python-2.4.3p0 interpreted object-oriented programming language vim-7.0.42-no_x11 vi clone, many additional features wget-1.10.2p0 retrieve files from the web via HTTP, HTTPS and FTP puppet-5.5.10/spec/fixtures/unit/provider/package/openbsd/pkginfo.query0000644005276200011600000000005313417161721026175 0ustar jenkinsjenkinsbash-3.1.17 GNU Bourne Again Shell puppet-5.5.10/spec/fixtures/unit/provider/package/openbsd/pkginfo_flavors.list0000644005276200011600000000014213417161721027536 0ustar jenkinsjenkinsbash-3.1.17-static GNU Bourne Again Shell vim-7.0.42-no_x11 vi clone, many additional features puppet-5.5.10/spec/fixtures/unit/provider/package/pkgng/0000755005276200011600000000000013417162176023134 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/package/pkgng/pkg.version0000644005276200011600000000033313417161721025316 0ustar jenkinsjenkinsshells/bash-completion < needs updating (index has 2.1_3) ftp/curl < needs updating (index has 7.33.0_2) shells/zsh < needs updating (index has 5.0.4) puppet-5.5.10/spec/fixtures/unit/provider/package/pkgng/pkg.info0000644005276200011600000000035613417161722024572 0ustar jenkinsjenkinsca_root_nss 3.15.3.1 security/ca_root_nss curl 7.33.0 ftp/curl gnupg 2.0.22 security/gnupg mcollective 2.2.4 sysutils/mcollective nmap 6.40 security/nmap pkg 1.2.4_1 ports-mgmt/pkg zsh 5.0.2_1 shells/zsh tac_plus F4.0.4.27a net/tac_plus4 puppet-5.5.10/spec/fixtures/unit/provider/package/pkgng/pkg.query0000644005276200011600000000005313417161722024776 0ustar jenkinsjenkinszsh-5.0.2 The Z shell puppet-5.5.10/spec/fixtures/unit/provider/package/pkgng/pkg.query_absent0000644005276200011600000000004113417161722026327 0ustar jenkinsjenkinspkg: No package(s) matching bash puppet-5.5.10/spec/fixtures/unit/provider/package/sun/0000755005276200011600000000000013417162176022633 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/package/sun/dummy.server0000644005276200011600000000051713417161721025214 0ustar jenkinsjenkins PKGINST: SUNWdummy NAME: Dummy server CATEGORY: system ARCH: i386 VERSION: 11.11.0,REV=2010.10.12.04.23 BASEDIR: / VENDOR: Oracle Corporation DESC: Dummy server (9.6.1-P3) INSTDATE: Nov 05 2010 09:14 HOTLINE: Please contact your local service provider STATUS: completely installed puppet-5.5.10/spec/fixtures/unit/provider/package/sun/simple0000644005276200011600000000123713417161721024045 0ustar jenkinsjenkins PKGINST: SUNWdummy NAME: Dummy server CATEGORY: system ARCH: i386 VERSION: 11.11.0,REV=2010.10.12.04.23 BASEDIR: / VENDOR: Oracle Corporation DESC: Dummy server (9.6.1-P3) INSTDATE: Nov 05 2010 09:14 HOTLINE: Please contact your local service provider STATUS: completely installed PKGINST: SUNWdummyc NAME: Dummy client CATEGORY: system ARCH: i386 VERSION: 11.11.0,REV=2010.10.12.04.24 BASEDIR: / VENDOR: Oracle Corporation DESC: Dummy client (9.6.1-P4) INSTDATE: Nov 05 2010 09:14 HOTLINE: Please contact your local service provider STATUS: completely installed puppet-5.5.10/spec/fixtures/unit/provider/package/yum/0000755005276200011600000000000013417162176022640 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/package/yum/yum-check-update-broken-notices.txt0000644005276200011600000003465513417161721031476 0ustar jenkinsjenkinsLoaded plugins: fastestmirror Loading mirror speeds from cached hostfile NetworkManager.x86_64 1:1.0.0-14.git20150121.b4ea599c.el7 base NetworkManager-glib.x86_64 1:1.0.0-14.git20150121.b4ea599c.el7 base NetworkManager-tui.x86_64 1:1.0.0-14.git20150121.b4ea599c.el7 base alsa-firmware.noarch 1.0.28-2.el7 base alsa-lib.x86_64 1.0.28-2.el7 base audit.x86_64 2.4.1-5.el7 base audit-libs.x86_64 2.4.1-5.el7 base authconfig.x86_64 6.2.8-9.el7 base avahi.x86_64 0.6.31-14.el7 base avahi-autoipd.x86_64 0.6.31-14.el7 base avahi-libs.x86_64 0.6.31-14.el7 base bash.x86_64 4.2.46-12.el7 base bind-libs-lite.x86_64 32:9.9.4-18.el7_1.1 updates bind-license.noarch 32:9.9.4-18.el7_1.1 updates binutils.x86_64 2.23.52.0.1-30.el7_1.2 updates biosdevname.x86_64 0.6.1-2.el7 base btrfs-progs.x86_64 3.16.2-1.el7 base ca-certificates.noarch 2015.2.4-70.0.el7_1 updates centos-logos.noarch 70.0.6-2.el7.centos updates centos-release.x86_64 7-1.1503.el7.centos.2.8 base cpp.x86_64 4.8.3-9.el7 base cronie.x86_64 1.4.11-13.el7 base cronie-anacron.x86_64 1.4.11-13.el7 base cryptsetup-libs.x86_64 1.6.6-3.el7 base dbus.x86_64 1:1.6.12-11.el7 base dbus-libs.x86_64 1:1.6.12-11.el7 base device-mapper.x86_64 7:1.02.93-3.el7 base device-mapper-event.x86_64 7:1.02.93-3.el7 base device-mapper-event-libs.x86_64 7:1.02.93-3.el7 base device-mapper-libs.x86_64 7:1.02.93-3.el7 base device-mapper-persistent-data.x86_64 0.4.1-2.el7 base dhclient.x86_64 12:4.2.5-36.el7.centos base dhcp-common.x86_64 12:4.2.5-36.el7.centos base dhcp-libs.x86_64 12:4.2.5-36.el7.centos base dnsmasq.x86_64 2.66-13.el7_1 updates dracut.x86_64 033-241.el7_1.1 updates dracut-config-rescue.x86_64 033-241.el7_1.1 updates dracut-network.x86_64 033-241.el7_1.1 updates e2fsprogs.x86_64 1.42.9-7.el7 base e2fsprogs-libs.x86_64 1.42.9-7.el7 base elfutils-libelf.x86_64 0.160-1.el7 base elfutils-libs.x86_64 0.160-1.el7 base ethtool.x86_64 2:3.15-2.el7 base firewalld.noarch 0.3.9-11.el7 base freetype.x86_64 2.4.11-10.el7_1.1 updates gcc.x86_64 4.8.3-9.el7 base glib-networking.x86_64 2.40.0-1.el7 base glib2.x86_64 2.40.0-4.el7 base glibc.x86_64 2.17-78.el7 base glibc-common.x86_64 2.17-78.el7 base glibc-devel.x86_64 2.17-78.el7 base glibc-headers.x86_64 2.17-78.el7 base gmp.x86_64 1:6.0.0-11.el7 base gnutls.x86_64 3.3.8-12.el7 base grep.x86_64 2.20-1.el7 base grub2.x86_64 1:2.02-0.16.el7.centos base grub2-tools.x86_64 1:2.02-0.16.el7.centos base grubby.x86_64 8.28-11.el7 base hwdata.noarch 0.252-7.5.el7 base hwdata.x86_64 0.252-7.8.el7_1 updates initscripts.x86_64 9.49.24-1.el7 base iproute.x86_64 3.10.0-21.el7 base iprutils.x86_64 2.4.3-3.el7 base irqbalance.x86_64 2:1.0.7-1.el7 base iwl100-firmware.noarch 39.31.5.1-36.el7 base iwl1000-firmware.noarch 1:39.31.5.1-36.el7 base iwl105-firmware.noarch 18.168.6.1-36.el7 base iwl135-firmware.noarch 18.168.6.1-36.el7 base iwl2000-firmware.noarch 18.168.6.1-36.el7 base iwl2030-firmware.noarch 18.168.6.1-36.el7 base iwl3160-firmware.noarch 22.0.7.0-36.el7 base iwl3945-firmware.noarch 15.32.2.9-36.el7 base iwl4965-firmware.noarch 228.61.2.24-36.el7 base iwl5000-firmware.noarch 8.83.5.1_1-36.el7 base iwl5150-firmware.noarch 8.24.2.2-36.el7 base iwl6000-firmware.noarch 9.221.4.1-36.el7 base iwl6000g2a-firmware.noarch 17.168.5.3-36.el7 base iwl6000g2b-firmware.noarch 17.168.5.2-36.el7 base iwl6050-firmware.noarch 41.28.5.1-36.el7 base iwl7260-firmware.noarch 22.0.7.0-36.el7 base kbd.x86_64 1.15.5-11.el7 base kbd-misc.noarch 1.15.5-11.el7 base kernel.x86_64 3.10.0-229.4.2.el7 updates kernel-headers.x86_64 3.10.0-229.4.2.el7 updates kernel-tools.x86_64 3.10.0-229.4.2.el7 updates kernel-tools-libs.x86_64 3.10.0-229.4.2.el7 updates kexec-tools.x86_64 2.0.7-19.el7_1.2 updates kmod.x86_64 14-10.el7 base kmod-libs.x86_64 14-10.el7 base kpartx.x86_64 0.4.9-77.el7 base krb5-libs.x86_64 1.12.2-14.el7 base libblkid.x86_64 2.23.2-22.el7_1 updates libcom_err.x86_64 1.42.9-7.el7 base libdb.x86_64 5.3.21-17.el7_0.1 base libdb-utils.x86_64 5.3.21-17.el7_0.1 base libdrm.x86_64 2.4.56-2.el7 base libgcc.x86_64 4.8.3-9.el7 base libgcrypt.x86_64 1.5.3-12.el7_1.1 updates libgcrypt-devel.x86_64 1.5.3-12.el7_1.1 updates libgomp.x86_64 4.8.3-9.el7 base libgudev1.x86_64 208-20.el7_1.3 updates libmount.x86_64 2.23.2-22.el7_1 updates libnl3.x86_64 3.2.21-8.el7 base libnl3-cli.x86_64 3.2.21-8.el7 base libpcap.x86_64 14:1.5.3-4.el7_1.2 updates libsoup.x86_64 2.46.0-3.el7 base libss.x86_64 1.42.9-7.el7 base libstdc++.x86_64 4.8.3-9.el7 base libtasn1.x86_64 3.8-2.el7 base libteam.x86_64 1.15-1.el7 base libuuid.x86_64 2.23.2-22.el7_1 updates libxml2.x86_64 2.9.1-5.el7_1.2 updates libxml2-devel.x86_64 2.9.1-5.el7_1.2 updates libyaml.x86_64 0.1.4-11.el7_0 base linux-firmware.noarch 20140911-0.1.git365e80c.el7 base lvm2.x86_64 7:2.02.115-3.el7 base lvm2-libs.x86_64 7:2.02.115-3.el7 base mariadb-libs.x86_64 1:5.5.41-2.el7_0 base microcode_ctl.x86_64 2:2.1-10.el7 base nettle.x86_64 2.7.1-4.el7 base nspr.x86_64 4.10.8-1.el7_1 updates nss.x86_64 3.18.0-2.2.el7_1 updates nss-softokn.x86_64 3.16.2.3-9.el7 base nss-softokn-freebl.x86_64 3.16.2.3-9.el7 base nss-sysinit.x86_64 3.18.0-2.2.el7_1 updates nss-tools.x86_64 3.18.0-2.2.el7_1 updates nss-util.x86_64 3.18.0-1.el7_1 updates ntpdate.x86_64 4.2.6p5-19.el7.centos base numactl-libs.x86_64 2.0.9-4.el7 base open-vm-tools.x86_64 9.4.0-6.el7 base openldap.x86_64 2.4.39-6.el7 base openssh.x86_64 6.6.1p1-12.el7_1 updates openssh-clients.x86_64 6.6.1p1-12.el7_1 updates openssh-server.x86_64 6.6.1p1-12.el7_1 updates openssl.x86_64 1:1.0.1e-42.el7.4 updates openssl-libs.x86_64 1:1.0.1e-42.el7.4 updates p11-kit.x86_64 0.20.7-3.el7 base p11-kit-trust.x86_64 0.20.7-3.el7 base pam.x86_64 1.1.8-12.el7 base parted.x86_64 3.1-20.el7 base pcre.x86_64 8.32-14.el7 base plymouth.x86_64 0.8.9-0.13.20140113.el7.centos base plymouth-core-libs.x86_64 0.8.9-0.13.20140113.el7.centos base plymouth-scripts.x86_64 0.8.9-0.13.20140113.el7.centos base policycoreutils.x86_64 2.2.5-15.el7 base procps-ng.x86_64 3.3.10-3.el7 base pygobject3-base.x86_64 3.8.2-6.el7 base python-backports.x86_64 1.0-8.el7 base python-urlgrabber.noarch 3.10-6.el7 base rpm.x86_64 4.11.1-25.el7 base rpm-build-libs.x86_64 4.11.1-25.el7 base rpm-libs.x86_64 4.11.1-25.el7 base rpm-python.x86_64 4.11.1-25.el7 base rsyslog.x86_64 7.4.7-7.el7_0 base rubygem-bigdecimal.x86_64 1.2.0-24.el7 base rubygem-io-console.x86_64 0.4.2-24.el7 base rubygem-json.x86_64 1.7.7-24.el7 base rubygem-psych.x86_64 2.0.0-24.el7 base rubygems.noarch 2.0.14-24.el7 base selinux-policy.noarch 3.13.1-23.el7_1.7 updates selinux-policy-targeted.noarch 3.13.1-23.el7_1.7 updates setup.noarch 2.8.71-5.el7 base shadow-utils.x86_64 2:4.1.5.1-18.el7 base sudo.x86_64 1.8.6p7-13.el7 base systemd.x86_64 208-20.el7_1.3 updates systemd-libs.x86_64 208-20.el7_1.3 updates systemd-sysv.x86_64 208-20.el7_1.3 updates teamd.x86_64 1.15-1.el7 base tuned.noarch 2.4.1-1.el7 base tzdata.noarch 2015d-1.el7 updates util-linux.x86_64 2.23.2-22.el7_1 updates wpa_supplicant.x86_64 1:2.0-13.el7_0 base xfsprogs.x86_64 3.2.1-6.el7 base xz.x86_64 5.1.2-9alpha.el7 base xz-devel.x86_64 5.1.2-9alpha.el7 base xz-libs.x86_64 5.1.2-9alpha.el7 base yum.noarch 3.4.3-125.el7.centos base yum-plugin-fastestmirror.noarch 1.1.31-29.el7 base Update notice RHBA-2014:0722 (from rhel-7-server-rpms) is broken, or a bad duplicate, skipping. You should report this problem to the owner of the rhel-7-server-rpms repository. Update notice RHSA-2014:1971 (from rhel-7-server-rpms) is broken, or a bad duplicate, skipping. Update notice RHSA-2015:1981 (from rhel-7-server-rpms) is broken, or a bad duplicate, skipping. Update notice RHSA-2015:0067 (from rhel-7-server-rpms) is broken, or a bad duplicate, skipping. puppet-5.5.10/spec/fixtures/unit/provider/package/yum/yum-check-update-multiline.txt0000644005276200011600000003715513417161721030554 0ustar jenkinsjenkinsLoaded plugins: fastestmirror, security Loading mirror speeds from cached hostfile * base: mirrors.usinternet.com * extras: mirror.itc.virginia.edu * updates: mirrors.usinternet.com abrt.x86_64 2.0.8-21.el6.centos base abrt-addon-ccpp.x86_64 2.0.8-21.el6.centos base abrt-addon-kerneloops.x86_64 2.0.8-21.el6.centos base abrt-addon-python.x86_64 2.0.8-21.el6.centos base abrt-cli.x86_64 2.0.8-21.el6.centos base abrt-libs.x86_64 2.0.8-21.el6.centos base abrt-tui.x86_64 2.0.8-21.el6.centos base atk.x86_64 1.30.0-1.el6 base audit.x86_64 2.2-4.el6_5 updates audit-libs.x86_64 2.2-4.el6_5 updates augeas-libs.x86_64 1.0.0-5.el6_5.1 updates bash.x86_64 4.1.2-15.el6_4 base bfa-firmware.noarch 3.2.21.1-2.el6 base bind-libs.x86_64 32:9.8.2-0.23.rc1.el6_5.1 updates bind-utils.x86_64 32:9.8.2-0.23.rc1.el6_5.1 updates biosdevname.x86_64 0.5.0-2.el6 base btparser.x86_64 0.17-2.el6 base busybox.x86_64 1:1.15.1-20.el6 base centos-release.x86_64 6-5.el6.centos.11.2 updates chkconfig.x86_64 1.3.49.3-2.el6_4.1 base coreutils.x86_64 8.4-31.el6_5.1 updates coreutils-libs.x86_64 8.4-31.el6_5.1 updates cpp.x86_64 4.4.7-4.el6 base cpuspeed.x86_64 1:1.5-20.el6_4 base cronie.x86_64 1.4.4-12.el6 base cronie-anacron.x86_64 1.4.4-12.el6 base cups-libs.x86_64 1:1.4.2-50.el6_4.5 base curl.x86_64 7.19.7-37.el6_5.3 updates db4.x86_64 4.7.25-18.el6_4 base db4-utils.x86_64 4.7.25-18.el6_4 base dbus-glib.x86_64 0.86-6.el6 base device-mapper.x86_64 1.02.79-8.el6 base device-mapper-event.x86_64 1.02.79-8.el6 base device-mapper-event-libs.x86_64 1.02.79-8.el6 base device-mapper-libs.x86_64 1.02.79-8.el6 base device-mapper-persistent-data.x86_64 0.2.8-4.el6_5 updates dhclient.x86_64 12:4.1.1-38.P1.el6.centos base dhcp-common.x86_64 12:4.1.1-38.P1.el6.centos base dmidecode.x86_64 1:2.12-5.el6_5 updates dracut.noarch 004-336.el6_5.2 updates dracut-kernel.noarch 004-336.el6_5.2 updates e2fsprogs.x86_64 1.41.12-18.el6 base e2fsprogs-libs.x86_64 1.41.12-18.el6 base efibootmgr.x86_64 0.5.4-11.el6 base ethtool.x86_64 2:3.5-1.4.el6_5 updates facter.x86_64 1:2.0.2-1.el6 puppetlabs-products gcc.x86_64 4.4.7-4.el6 base gcc-c++.x86_64 4.4.7-4.el6 base glib2.x86_64 2.26.1-7.el6_5 updates glibc.x86_64 2.12-1.132.el6_5.2 updates glibc-common.x86_64 2.12-1.132.el6_5.2 updates glibc-devel.x86_64 2.12-1.132.el6_5.2 updates glibc-headers.x86_64 2.12-1.132.el6_5.2 updates gnupg2.x86_64 2.0.14-6.el6_4 base gnutls.x86_64 2.8.5-14.el6_5 updates grep.x86_64 2.6.3-4.el6_5.1 updates grub.x86_64 1:0.97-83.el6 base grubby.x86_64 7.0.15-5.el6 base gzip.x86_64 1.3.12-19.el6_4 base hdparm.x86_64 9.43-4.el6 base hiera.noarch 1.3.4-1.el6 puppetlabs-products hwdata.noarch 0.233-9.1.el6 base initscripts.x86_64 9.03.40-2.el6.centos.1 updates iproute.x86_64 2.6.32-32.el6_5 updates iptables.x86_64 1.4.7-11.el6 base iptables-ipv6.x86_64 1.4.7-11.el6 base iputils.x86_64 20071127-17.el6_4.2 base irqbalance.x86_64 2:1.0.4-9.el6_5 updates iw.x86_64 3.10-1.1.el6 base kernel.x86_64 2.6.32-431.17.1.el6 updates kernel-devel.x86_64 2.6.32-431.17.1.el6 updates kernel-firmware.noarch 2.6.32-431.17.1.el6 updates kernel-headers.x86_64 2.6.32-431.17.1.el6 updates kexec-tools.x86_64 2.0.0-273.el6 base kpartx.x86_64 0.4.9-72.el6_5.2 updates krb5-devel.x86_64 1.10.3-15.el6_5.1 updates krb5-libs.x86_64 1.10.3-15.el6_5.1 updates ledmon.x86_64 0.78-1.el6 base libblkid.x86_64 2.17.2-12.14.el6_5 updates libcom_err.x86_64 1.41.12-18.el6 base libcom_err-devel.x86_64 1.41.12-18.el6 base libcurl.x86_64 7.19.7-37.el6_5.3 updates libdrm.x86_64 2.4.45-2.el6 base libgcc.x86_64 4.4.7-4.el6 base libgcrypt.x86_64 1.4.5-11.el6_4 base libgomp.x86_64 4.4.7-4.el6 base libjpeg-turbo.x86_64 1.2.1-3.el6_5 updates libnl.x86_64 1.1.4-2.el6 base libpcap.x86_64 14:1.4.0-1.20130826git2dbcaa1.el6 base libproxy.x86_64 0.3.0-4.el6_3 base libproxy-bin.x86_64 0.3.0-4.el6_3 base libproxy-python.x86_64 0.3.0-4.el6_3 base libreport.x86_64 2.0.9-19.el6.centos base libreport-cli.x86_64 2.0.9-19.el6.centos base libreport-compat.x86_64 2.0.9-19.el6.centos base libreport-plugin-kerneloops.x86_64 2.0.9-19.el6.centos base libreport-plugin-logger.x86_64 2.0.9-19.el6.centos base libreport-plugin-mailx.x86_64 2.0.9-19.el6.centos base libreport-plugin-reportuploader.x86_64 2.0.9-19.el6.centos base libreport-plugin-rhtsupport.x86_64 2.0.9-19.el6.centos base libreport-python.x86_64 2.0.9-19.el6.centos base libselinux.x86_64 2.0.94-5.3.el6_4.1 base libselinux-devel.x86_64 2.0.94-5.3.el6_4.1 base libselinux-ruby.x86_64 2.0.94-5.3.el6_4.1 base libselinux-utils.x86_64 2.0.94-5.3.el6_4.1 base libss.x86_64 1.41.12-18.el6 base libstdc++.x86_64 4.4.7-4.el6 base libstdc++-devel.x86_64 4.4.7-4.el6 base libtar.x86_64 1.2.11-17.el6_4.1 base libtasn1.x86_64 2.3-6.el6_5 updates libtiff.x86_64 3.9.4-10.el6_5 updates libudev.x86_64 147-2.51.el6 base libuuid.x86_64 2.17.2-12.14.el6_5 updates libxml2.x86_64 2.7.6-14.el6_5.1 updates libxml2-python.x86_64 2.7.6-14.el6_5.1 updates logrotate.x86_64 3.7.8-17.el6 base lvm2.x86_64 2.02.100-8.el6 base lvm2-libs.x86_64 2.02.100-8.el6 base mailx.x86_64 12.4-7.el6 base man-pages-overrides.noarch 6.5.3-1.el6_5 updates mdadm.x86_64 3.2.6-7.el6_5.2 updates microcode_ctl.x86_64 1:1.17-17.el6 base module-init-tools.x86_64 3.9-21.el6_4 base mysql-libs.x86_64 5.1.73-3.el6_5 updates ntp.x86_64 4.2.6p5-1.el6.centos base ntpdate.x86_64 4.2.6p5-1.el6.centos base ntsysv.x86_64 1.3.49.3-2.el6_4.1 base numactl.x86_64 2.0.7-8.el6 base openldap.x86_64 2.4.23-34.el6_5.1 updates openssh.x86_64 5.3p1-94.el6 base openssh-clients.x86_64 5.3p1-94.el6 base openssh-server.x86_64 5.3p1-94.el6 base openssl.x86_64 1.0.1e-16.el6_5.14 updates openssl-devel.x86_64 1.0.1e-16.el6_5.14 updates pam.x86_64 1.1.1-17.el6 base parted.x86_64 2.1-21.el6 base perl.x86_64 4:5.10.1-136.el6 base perl-Module-Pluggable.x86_64 1:3.90-136.el6 base perl-Pod-Escapes.x86_64 1:1.04-136.el6 base perl-Pod-Simple.x86_64 1:3.13-136.el6 base perl-libs.x86_64 4:5.10.1-136.el6 base perl-version.x86_64 3:0.77-136.el6 base pixman.x86_64 0.26.2-5.1.el6_5 updates pm-utils.x86_64 1.2.5-10.el6_5.1 updates policycoreutils.x86_64 2.0.83-19.39.el6 base polkit.x86_64 0.96-5.el6_4 base postfix.x86_64 2:2.6.6-6.el6_5 updates prelink.x86_64 0.4.6-3.1.el6_4 base psmisc.x86_64 22.6-19.el6_5 updates python.x86_64 2.6.6-52.el6 updates python-ethtool.x86_64 0.6-5.el6 base python-libs.x86_64 2.6.6-52.el6 updates python-urlgrabber.noarch 3.9.1-9.el6 base ql2400-firmware.noarch 7.00.01-1.el6 base ql2500-firmware.noarch 7.00.01-1.el6 base quota.x86_64 1:3.17-21.el6_5 updates readahead.x86_64 1:1.5.6-2.el6 base rpm.x86_64 4.8.0-37.el6 base rpm-libs.x86_64 4.8.0-37.el6 base rpm-python.x86_64 4.8.0-37.el6 base rsync.x86_64 3.0.6-9.el6_4.1 base rsyslog.x86_64 5.8.10-8.el6 base ruby.x86_64 1.8.7.352-13.el6 updates ruby-augeas.x86_64 0.4.1-3.el6 puppetlabs-deps ruby-devel.x86_64 1.8.7.352-13.el6 updates ruby-irb.x86_64 1.8.7.352-13.el6 updates ruby-libs.x86_64 1.8.7.352-13.el6 updates ruby-rdoc.x86_64 1.8.7.352-13.el6 updates ruby-shadow.x86_64 1:2.2.0-2.el6 puppetlabs-deps rubygem-json.x86_64 1.5.5-1.el6 puppetlabs-deps rubygems.noarch 1.3.7-5.el6 base scl-utils.x86_64 20120927-8.el6 base selinux-policy.noarch 3.7.19-231.el6_5.3 updates selinux-policy-targeted.noarch 3.7.19-231.el6_5.3 updates setup.noarch 2.8.14-20.el6_4.1 base setuptool.x86_64 1.19.9-4.el6 base sg3_utils-libs.x86_64 1.28-5.el6 base sos.noarch 2.2-47.el6.centos.1 updates sudo.x86_64 1.8.6p3-12.el6 base sysstat.x86_64 9.0.4-22.el6 base systemtap-runtime.x86_64 2.3-4.el6_5 updates sysvinit-tools.x86_64 2.87-5.dsf.el6 base tzdata.noarch 2014d-1.el6 updates udev.x86_64 147-2.51.el6 base upstart.x86_64 0.6.5-13.el6_5.3 updates util-linux-ng.x86_64 2.17.2-12.14.el6_5 updates wget.x86_64 1.12-1.11.el6_5 updates xmlrpc-c.x86_64 1.16.24-1210.1840.el6 base xmlrpc-c-client.x86_64 1.16.24-1210.1840.el6 base xorg-x11-drv-ati-firmware.noarch 7.1.0-3.el6 base yum.noarch 3.2.29-43.el6.centos updates yum-plugin-fastestmirror.noarch 1.1.30-17.el6_5 updates yum-plugin-security.noarch 1.1.30-17.el6_5 updates yum-utils.noarch 1.1.30-17.el6_5 updates puppet-5.5.10/spec/fixtures/unit/provider/package/yum/yum-check-update-obsoletes.txt0000644005276200011600000003567313417161721030554 0ustar jenkinsjenkinsLoaded plugins: fastestmirror Loading mirror speeds from cached hostfile NetworkManager.x86_64 1:1.0.0-14.git20150121.b4ea599c.el7 base NetworkManager-glib.x86_64 1:1.0.0-14.git20150121.b4ea599c.el7 base NetworkManager-tui.x86_64 1:1.0.0-14.git20150121.b4ea599c.el7 base alsa-firmware.noarch 1.0.28-2.el7 base alsa-lib.x86_64 1.0.28-2.el7 base audit.x86_64 2.4.1-5.el7 base audit-libs.x86_64 2.4.1-5.el7 base authconfig.x86_64 6.2.8-9.el7 base avahi.x86_64 0.6.31-14.el7 base avahi-autoipd.x86_64 0.6.31-14.el7 base avahi-libs.x86_64 0.6.31-14.el7 base bash.x86_64 4.2.46-12.el7 base bind-libs-lite.x86_64 32:9.9.4-18.el7_1.1 updates bind-license.noarch 32:9.9.4-18.el7_1.1 updates binutils.x86_64 2.23.52.0.1-30.el7_1.2 updates biosdevname.x86_64 0.6.1-2.el7 base btrfs-progs.x86_64 3.16.2-1.el7 base ca-certificates.noarch 2015.2.4-70.0.el7_1 updates centos-logos.noarch 70.0.6-2.el7.centos updates centos-release.x86_64 7-1.1503.el7.centos.2.8 base cpp.x86_64 4.8.3-9.el7 base cronie.x86_64 1.4.11-13.el7 base cronie-anacron.x86_64 1.4.11-13.el7 base cryptsetup-libs.x86_64 1.6.6-3.el7 base dbus.x86_64 1:1.6.12-11.el7 base dbus-libs.x86_64 1:1.6.12-11.el7 base device-mapper.x86_64 7:1.02.93-3.el7 base device-mapper-event.x86_64 7:1.02.93-3.el7 base device-mapper-event-libs.x86_64 7:1.02.93-3.el7 base device-mapper-libs.x86_64 7:1.02.93-3.el7 base device-mapper-persistent-data.x86_64 0.4.1-2.el7 base dhclient.x86_64 12:4.2.5-36.el7.centos base dhcp-common.x86_64 12:4.2.5-36.el7.centos base dhcp-libs.x86_64 12:4.2.5-36.el7.centos base dnsmasq.x86_64 2.66-13.el7_1 updates dracut.x86_64 033-241.el7_1.1 updates dracut-config-rescue.x86_64 033-241.el7_1.1 updates dracut-network.x86_64 033-241.el7_1.1 updates e2fsprogs.x86_64 1.42.9-7.el7 base e2fsprogs-libs.x86_64 1.42.9-7.el7 base elfutils-libelf.x86_64 0.160-1.el7 base elfutils-libs.x86_64 0.160-1.el7 base ethtool.x86_64 2:3.15-2.el7 base firewalld.noarch 0.3.9-11.el7 base freetype.x86_64 2.4.11-10.el7_1.1 updates gcc.x86_64 4.8.3-9.el7 base glib-networking.x86_64 2.40.0-1.el7 base glib2.x86_64 2.40.0-4.el7 base glibc.x86_64 2.17-78.el7 base glibc-common.x86_64 2.17-78.el7 base glibc-devel.x86_64 2.17-78.el7 base glibc-headers.x86_64 2.17-78.el7 base gmp.x86_64 1:6.0.0-11.el7 base gnutls.x86_64 3.3.8-12.el7 base grep.x86_64 2.20-1.el7 base grub2.x86_64 1:2.02-0.16.el7.centos base grub2-tools.x86_64 1:2.02-0.16.el7.centos base grubby.x86_64 8.28-11.el7 base hwdata.noarch 0.252-7.5.el7 base hwdata.x86_64 0.252-7.8.el7_1 updates initscripts.x86_64 9.49.24-1.el7 base iproute.x86_64 3.10.0-21.el7 base iprutils.x86_64 2.4.3-3.el7 base irqbalance.x86_64 2:1.0.7-1.el7 base iwl100-firmware.noarch 39.31.5.1-36.el7 base iwl1000-firmware.noarch 1:39.31.5.1-36.el7 base iwl105-firmware.noarch 18.168.6.1-36.el7 base iwl135-firmware.noarch 18.168.6.1-36.el7 base iwl2000-firmware.noarch 18.168.6.1-36.el7 base iwl2030-firmware.noarch 18.168.6.1-36.el7 base iwl3160-firmware.noarch 22.0.7.0-36.el7 base iwl3945-firmware.noarch 15.32.2.9-36.el7 base iwl4965-firmware.noarch 228.61.2.24-36.el7 base iwl5000-firmware.noarch 8.83.5.1_1-36.el7 base iwl5150-firmware.noarch 8.24.2.2-36.el7 base iwl6000-firmware.noarch 9.221.4.1-36.el7 base iwl6000g2a-firmware.noarch 17.168.5.3-36.el7 base iwl6000g2b-firmware.noarch 17.168.5.2-36.el7 base iwl6050-firmware.noarch 41.28.5.1-36.el7 base iwl7260-firmware.noarch 22.0.7.0-36.el7 base kbd.x86_64 1.15.5-11.el7 base kbd-misc.noarch 1.15.5-11.el7 base kernel.x86_64 3.10.0-229.4.2.el7 updates kernel-headers.x86_64 3.10.0-229.4.2.el7 updates kernel-tools.x86_64 3.10.0-229.4.2.el7 updates kernel-tools-libs.x86_64 3.10.0-229.4.2.el7 updates kexec-tools.x86_64 2.0.7-19.el7_1.2 updates kmod.x86_64 14-10.el7 base kmod-libs.x86_64 14-10.el7 base kpartx.x86_64 0.4.9-77.el7 base krb5-libs.x86_64 1.12.2-14.el7 base libblkid.x86_64 2.23.2-22.el7_1 updates libcom_err.x86_64 1.42.9-7.el7 base libdb.x86_64 5.3.21-17.el7_0.1 base libdb-utils.x86_64 5.3.21-17.el7_0.1 base libdrm.x86_64 2.4.56-2.el7 base libgcc.x86_64 4.8.3-9.el7 base libgcrypt.x86_64 1.5.3-12.el7_1.1 updates libgcrypt-devel.x86_64 1.5.3-12.el7_1.1 updates libgomp.x86_64 4.8.3-9.el7 base libgudev1.x86_64 208-20.el7_1.3 updates libmount.x86_64 2.23.2-22.el7_1 updates libnl3.x86_64 3.2.21-8.el7 base libnl3-cli.x86_64 3.2.21-8.el7 base libpcap.x86_64 14:1.5.3-4.el7_1.2 updates libsoup.x86_64 2.46.0-3.el7 base libss.x86_64 1.42.9-7.el7 base libstdc++.x86_64 4.8.3-9.el7 base libtasn1.x86_64 3.8-2.el7 base libteam.x86_64 1.15-1.el7 base libuuid.x86_64 2.23.2-22.el7_1 updates libxml2.x86_64 2.9.1-5.el7_1.2 updates libxml2-devel.x86_64 2.9.1-5.el7_1.2 updates libyaml.x86_64 0.1.4-11.el7_0 base linux-firmware.noarch 20140911-0.1.git365e80c.el7 base lvm2.x86_64 7:2.02.115-3.el7 base lvm2-libs.x86_64 7:2.02.115-3.el7 base mariadb-libs.x86_64 1:5.5.41-2.el7_0 base microcode_ctl.x86_64 2:2.1-10.el7 base nettle.x86_64 2.7.1-4.el7 base nspr.x86_64 4.10.8-1.el7_1 updates nss.x86_64 3.18.0-2.2.el7_1 updates nss-softokn.x86_64 3.16.2.3-9.el7 base nss-softokn-freebl.x86_64 3.16.2.3-9.el7 base nss-sysinit.x86_64 3.18.0-2.2.el7_1 updates nss-tools.x86_64 3.18.0-2.2.el7_1 updates nss-util.x86_64 3.18.0-1.el7_1 updates ntpdate.x86_64 4.2.6p5-19.el7.centos base numactl-libs.x86_64 2.0.9-4.el7 base open-vm-tools.x86_64 9.4.0-6.el7 base openldap.x86_64 2.4.39-6.el7 base openssh.x86_64 6.6.1p1-12.el7_1 updates openssh-clients.x86_64 6.6.1p1-12.el7_1 updates openssh-server.x86_64 6.6.1p1-12.el7_1 updates openssl.x86_64 1:1.0.1e-42.el7.4 updates openssl-libs.x86_64 1:1.0.1e-42.el7.4 updates p11-kit.x86_64 0.20.7-3.el7 base p11-kit-trust.x86_64 0.20.7-3.el7 base pam.x86_64 1.1.8-12.el7 base parted.x86_64 3.1-20.el7 base pcre.x86_64 8.32-14.el7 base plymouth.x86_64 0.8.9-0.13.20140113.el7.centos base plymouth-core-libs.x86_64 0.8.9-0.13.20140113.el7.centos base plymouth-scripts.x86_64 0.8.9-0.13.20140113.el7.centos base policycoreutils.x86_64 2.2.5-15.el7 base procps-ng.x86_64 3.3.10-3.el7 base pygobject3-base.x86_64 3.8.2-6.el7 base python-backports.x86_64 1.0-8.el7 base python-urlgrabber.noarch 3.10-6.el7 base rpm.x86_64 4.11.1-25.el7 base rpm-build-libs.x86_64 4.11.1-25.el7 base rpm-libs.x86_64 4.11.1-25.el7 base rpm-python.x86_64 4.11.1-25.el7 base rsyslog.x86_64 7.4.7-7.el7_0 base rubygem-bigdecimal.x86_64 1.2.0-24.el7 base rubygem-io-console.x86_64 0.4.2-24.el7 base rubygem-json.x86_64 1.7.7-24.el7 base rubygem-psych.x86_64 2.0.0-24.el7 base rubygems.noarch 2.0.14-24.el7 base selinux-policy.noarch 3.13.1-23.el7_1.7 updates selinux-policy-targeted.noarch 3.13.1-23.el7_1.7 updates setup.noarch 2.8.71-5.el7 base shadow-utils.x86_64 2:4.1.5.1-18.el7 base sudo.x86_64 1.8.6p7-13.el7 base systemd.x86_64 208-20.el7_1.3 updates systemd-libs.x86_64 208-20.el7_1.3 updates systemd-sysv.x86_64 208-20.el7_1.3 updates teamd.x86_64 1.15-1.el7 base tuned.noarch 2.4.1-1.el7 base tzdata.noarch 2015d-1.el7 updates util-linux.x86_64 2.23.2-22.el7_1 updates wpa_supplicant.x86_64 1:2.0-13.el7_0 base xfsprogs.x86_64 3.2.1-6.el7 base xz.x86_64 5.1.2-9alpha.el7 base xz-devel.x86_64 5.1.2-9alpha.el7 base xz-libs.x86_64 5.1.2-9alpha.el7 base yum.noarch 3.4.3-125.el7.centos base yum-plugin-fastestmirror.noarch 1.1.31-29.el7 base Obsoleting Packages NetworkManager.x86_64 1:1.0.0-14.git20150121.b4ea599c.el7 base NetworkManager.x86_64 1:0.9.9.1-25.git20140326.4dba720.el7_0 @updates NetworkManager-adsl.x86_64 1:1.0.0-14.git20150121.b4ea599c.el7 base NetworkManager.x86_64 1:0.9.9.1-25.git20140326.4dba720.el7_0 @updates NetworkManager-bluetooth.x86_64 1:1.0.0-14.git20150121.b4ea599c.el7 base NetworkManager.x86_64 1:0.9.9.1-25.git20140326.4dba720.el7_0 @updates NetworkManager-team.x86_64 1:1.0.0-14.git20150121.b4ea599c.el7 base NetworkManager.x86_64 1:0.9.9.1-25.git20140326.4dba720.el7_0 @updates NetworkManager-wifi.x86_64 1:1.0.0-14.git20150121.b4ea599c.el7 base NetworkManager.x86_64 1:0.9.9.1-25.git20140326.4dba720.el7_0 @updates NetworkManager-wwan.x86_64 1:1.0.0-14.git20150121.b4ea599c.el7 base NetworkManager.x86_64 1:0.9.9.1-25.git20140326.4dba720.el7_0 @updates puppet-5.5.10/spec/fixtures/unit/provider/package/yum/yum-check-update-plugin-output.txt0000644005276200011600000000502613417161721031376 0ustar jenkinsjenkinsLoaded plugins: fastestmirror Loading mirror speeds from cached hostfile NetworkManager.x86_64 1:1.0.0-14.git20150121.b4ea599c.el7 base NetworkManager-glib.x86_64 1:1.0.0-14.git20150121.b4ea599c.el7 base NetworkManager-tui.x86_64 1:1.0.0-14.git20150121.b4ea599c.el7 base alsa-firmware.noarch 1.0.28-2.el7 base alsa-lib.x86_64 1.0.28-2.el7 base audit.x86_64 2.4.1-5.el7 base audit-libs.x86_64 2.4.1-5.el7 base authconfig.x86_64 6.2.8-9.el7 base avahi.x86_64 0.6.31-14.el7 base avahi-autoipd.x86_64 0.6.31-14.el7 base avahi-libs.x86_64 0.6.31-14.el7 base bash.x86_64 4.2.46-12.el7 base bind-libs-lite.x86_64 32:9.9.4-18.el7_1.1 updates bind-license.noarch 32:9.9.4-18.el7_1.1 updates binutils.x86_64 2.23.52.0.1-30.el7_1.2 updates biosdevname.x86_64 0.6.1-2.el7 base btrfs-progs.x86_64 3.16.2-1.el7 base ca-certificates.noarch 2015.2.4-70.0.el7_1 updates centos-logos.noarch 70.0.6-2.el7.centos updates centos-release.x86_64 7-1.1503.el7.centos.2.8 base cpp.x86_64 4.8.3-9.el7 base cronie.x86_64 1.4.11-13.el7 base cronie-anacron.x86_64 1.4.11-13.el7 base cryptsetup-libs.x86_64 1.6.6-3.el7 base dbus.x86_64 1:1.6.12-11.el7 base dbus-libs.x86_64 1:1.6.12-11.el7 base device-mapper.x86_64 7:1.02.93-3.el7 base device-mapper-event.x86_64 7:1.02.93-3.el7 base device-mapper-event-libs.x86_64 7:1.02.93-3.el7 base device-mapper-libs.x86_64 7:1.02.93-3.el7 base Random plugin output Loaded plugins: fastestmirror, product-id, fake-plugin Random plugin failed, is the mirror available? puppet-5.5.10/spec/fixtures/unit/provider/package/yum/yum-check-update-security.txt0000644005276200011600000003416413417161721030416 0ustar jenkinsjenkinsLoaded plugins: fastestmirror Loading mirror speeds from cached hostfile NetworkManager.x86_64 1:1.0.0-14.git20150121.b4ea599c.el7 base NetworkManager-glib.x86_64 1:1.0.0-14.git20150121.b4ea599c.el7 base NetworkManager-tui.x86_64 1:1.0.0-14.git20150121.b4ea599c.el7 base alsa-firmware.noarch 1.0.28-2.el7 base alsa-lib.x86_64 1.0.28-2.el7 base audit.x86_64 2.4.1-5.el7 base audit-libs.x86_64 2.4.1-5.el7 base authconfig.x86_64 6.2.8-9.el7 base avahi.x86_64 0.6.31-14.el7 base avahi-autoipd.x86_64 0.6.31-14.el7 base avahi-libs.x86_64 0.6.31-14.el7 base bash.x86_64 4.2.46-12.el7 base bind-libs-lite.x86_64 32:9.9.4-18.el7_1.1 updates bind-license.noarch 32:9.9.4-18.el7_1.1 updates binutils.x86_64 2.23.52.0.1-30.el7_1.2 updates biosdevname.x86_64 0.6.1-2.el7 base btrfs-progs.x86_64 3.16.2-1.el7 base ca-certificates.noarch 2015.2.4-70.0.el7_1 updates centos-logos.noarch 70.0.6-2.el7.centos updates centos-release.x86_64 7-1.1503.el7.centos.2.8 base cpp.x86_64 4.8.3-9.el7 base cronie.x86_64 1.4.11-13.el7 base cronie-anacron.x86_64 1.4.11-13.el7 base cryptsetup-libs.x86_64 1.6.6-3.el7 base dbus.x86_64 1:1.6.12-11.el7 base dbus-libs.x86_64 1:1.6.12-11.el7 base device-mapper.x86_64 7:1.02.93-3.el7 base device-mapper-event.x86_64 7:1.02.93-3.el7 base device-mapper-event-libs.x86_64 7:1.02.93-3.el7 base device-mapper-libs.x86_64 7:1.02.93-3.el7 base device-mapper-persistent-data.x86_64 0.4.1-2.el7 base dhclient.x86_64 12:4.2.5-36.el7.centos base dhcp-common.x86_64 12:4.2.5-36.el7.centos base dhcp-libs.x86_64 12:4.2.5-36.el7.centos base dnsmasq.x86_64 2.66-13.el7_1 updates dracut.x86_64 033-241.el7_1.1 updates dracut-config-rescue.x86_64 033-241.el7_1.1 updates dracut-network.x86_64 033-241.el7_1.1 updates e2fsprogs.x86_64 1.42.9-7.el7 base e2fsprogs-libs.x86_64 1.42.9-7.el7 base elfutils-libelf.x86_64 0.160-1.el7 base elfutils-libs.x86_64 0.160-1.el7 base ethtool.x86_64 2:3.15-2.el7 base firewalld.noarch 0.3.9-11.el7 base freetype.x86_64 2.4.11-10.el7_1.1 updates gcc.x86_64 4.8.3-9.el7 base glib-networking.x86_64 2.40.0-1.el7 base glib2.x86_64 2.40.0-4.el7 base glibc.x86_64 2.17-78.el7 base glibc-common.x86_64 2.17-78.el7 base glibc-devel.x86_64 2.17-78.el7 base glibc-headers.x86_64 2.17-78.el7 base gmp.x86_64 1:6.0.0-11.el7 base gnutls.x86_64 3.3.8-12.el7 base grep.x86_64 2.20-1.el7 base grub2.x86_64 1:2.02-0.16.el7.centos base grub2-tools.x86_64 1:2.02-0.16.el7.centos base grubby.x86_64 8.28-11.el7 base hwdata.noarch 0.252-7.5.el7 base hwdata.x86_64 0.252-7.8.el7_1 updates initscripts.x86_64 9.49.24-1.el7 base iproute.x86_64 3.10.0-21.el7 base iprutils.x86_64 2.4.3-3.el7 base irqbalance.x86_64 2:1.0.7-1.el7 base iwl100-firmware.noarch 39.31.5.1-36.el7 base iwl1000-firmware.noarch 1:39.31.5.1-36.el7 base iwl105-firmware.noarch 18.168.6.1-36.el7 base iwl135-firmware.noarch 18.168.6.1-36.el7 base iwl2000-firmware.noarch 18.168.6.1-36.el7 base iwl2030-firmware.noarch 18.168.6.1-36.el7 base iwl3160-firmware.noarch 22.0.7.0-36.el7 base iwl3945-firmware.noarch 15.32.2.9-36.el7 base iwl4965-firmware.noarch 228.61.2.24-36.el7 base iwl5000-firmware.noarch 8.83.5.1_1-36.el7 base iwl5150-firmware.noarch 8.24.2.2-36.el7 base iwl6000-firmware.noarch 9.221.4.1-36.el7 base iwl6000g2a-firmware.noarch 17.168.5.3-36.el7 base iwl6000g2b-firmware.noarch 17.168.5.2-36.el7 base iwl6050-firmware.noarch 41.28.5.1-36.el7 base iwl7260-firmware.noarch 22.0.7.0-36.el7 base kbd.x86_64 1.15.5-11.el7 base kbd-misc.noarch 1.15.5-11.el7 base kernel.x86_64 3.10.0-229.4.2.el7 updates kernel-headers.x86_64 3.10.0-229.4.2.el7 updates kernel-tools.x86_64 3.10.0-229.4.2.el7 updates kernel-tools-libs.x86_64 3.10.0-229.4.2.el7 updates kexec-tools.x86_64 2.0.7-19.el7_1.2 updates kmod.x86_64 14-10.el7 base kmod-libs.x86_64 14-10.el7 base kpartx.x86_64 0.4.9-77.el7 base krb5-libs.x86_64 1.12.2-14.el7 base libblkid.x86_64 2.23.2-22.el7_1 updates libcom_err.x86_64 1.42.9-7.el7 base libdb.x86_64 5.3.21-17.el7_0.1 base libdb-utils.x86_64 5.3.21-17.el7_0.1 base libdrm.x86_64 2.4.56-2.el7 base libgcc.x86_64 4.8.3-9.el7 base libgcrypt.x86_64 1.5.3-12.el7_1.1 updates libgcrypt-devel.x86_64 1.5.3-12.el7_1.1 updates libgomp.x86_64 4.8.3-9.el7 base libgudev1.x86_64 208-20.el7_1.3 updates libmount.x86_64 2.23.2-22.el7_1 updates libnl3.x86_64 3.2.21-8.el7 base libnl3-cli.x86_64 3.2.21-8.el7 base libpcap.x86_64 14:1.5.3-4.el7_1.2 updates libsoup.x86_64 2.46.0-3.el7 base libss.x86_64 1.42.9-7.el7 base libstdc++.x86_64 4.8.3-9.el7 base libtasn1.x86_64 3.8-2.el7 base libteam.x86_64 1.15-1.el7 base libuuid.x86_64 2.23.2-22.el7_1 updates libxml2.x86_64 2.9.1-5.el7_1.2 updates libxml2-devel.x86_64 2.9.1-5.el7_1.2 updates libyaml.x86_64 0.1.4-11.el7_0 base linux-firmware.noarch 20140911-0.1.git365e80c.el7 base lvm2.x86_64 7:2.02.115-3.el7 base lvm2-libs.x86_64 7:2.02.115-3.el7 base mariadb-libs.x86_64 1:5.5.41-2.el7_0 base microcode_ctl.x86_64 2:2.1-10.el7 base nettle.x86_64 2.7.1-4.el7 base nspr.x86_64 4.10.8-1.el7_1 updates nss.x86_64 3.18.0-2.2.el7_1 updates nss-softokn.x86_64 3.16.2.3-9.el7 base nss-softokn-freebl.x86_64 3.16.2.3-9.el7 base nss-sysinit.x86_64 3.18.0-2.2.el7_1 updates nss-tools.x86_64 3.18.0-2.2.el7_1 updates nss-util.x86_64 3.18.0-1.el7_1 updates ntpdate.x86_64 4.2.6p5-19.el7.centos base numactl-libs.x86_64 2.0.9-4.el7 base open-vm-tools.x86_64 9.4.0-6.el7 base openldap.x86_64 2.4.39-6.el7 base openssh.x86_64 6.6.1p1-12.el7_1 updates openssh-clients.x86_64 6.6.1p1-12.el7_1 updates openssh-server.x86_64 6.6.1p1-12.el7_1 updates openssl.x86_64 1:1.0.1e-42.el7.4 updates openssl-libs.x86_64 1:1.0.1e-42.el7.4 updates p11-kit.x86_64 0.20.7-3.el7 base p11-kit-trust.x86_64 0.20.7-3.el7 base pam.x86_64 1.1.8-12.el7 base parted.x86_64 3.1-20.el7 base pcre.x86_64 8.32-14.el7 base plymouth.x86_64 0.8.9-0.13.20140113.el7.centos base plymouth-core-libs.x86_64 0.8.9-0.13.20140113.el7.centos base plymouth-scripts.x86_64 0.8.9-0.13.20140113.el7.centos base policycoreutils.x86_64 2.2.5-15.el7 base procps-ng.x86_64 3.3.10-3.el7 base pygobject3-base.x86_64 3.8.2-6.el7 base python-backports.x86_64 1.0-8.el7 base python-urlgrabber.noarch 3.10-6.el7 base rpm.x86_64 4.11.1-25.el7 base rpm-build-libs.x86_64 4.11.1-25.el7 base rpm-libs.x86_64 4.11.1-25.el7 base rpm-python.x86_64 4.11.1-25.el7 base rsyslog.x86_64 7.4.7-7.el7_0 base rubygem-bigdecimal.x86_64 1.2.0-24.el7 base rubygem-io-console.x86_64 0.4.2-24.el7 base rubygem-json.x86_64 1.7.7-24.el7 base rubygem-psych.x86_64 2.0.0-24.el7 base rubygems.noarch 2.0.14-24.el7 base selinux-policy.noarch 3.13.1-23.el7_1.7 updates selinux-policy-targeted.noarch 3.13.1-23.el7_1.7 updates setup.noarch 2.8.71-5.el7 base shadow-utils.x86_64 2:4.1.5.1-18.el7 base sudo.x86_64 1.8.6p7-13.el7 base systemd.x86_64 208-20.el7_1.3 updates systemd-libs.x86_64 208-20.el7_1.3 updates systemd-sysv.x86_64 208-20.el7_1.3 updates teamd.x86_64 1.15-1.el7 base tuned.noarch 2.4.1-1.el7 base tzdata.noarch 2015d-1.el7 updates util-linux.x86_64 2.23.2-22.el7_1 updates wpa_supplicant.x86_64 1:2.0-13.el7_0 base xfsprogs.x86_64 3.2.1-6.el7 base xz.x86_64 5.1.2-9alpha.el7 base xz-devel.x86_64 5.1.2-9alpha.el7 base xz-libs.x86_64 5.1.2-9alpha.el7 base yum.noarch 3.4.3-125.el7.centos base yum-plugin-fastestmirror.noarch 1.1.31-29.el7 base Security: kernel-3.10.0-229.14.1.el7.x86_64 is an installed security update Security: kernel-3.10.0-229.11.1.el7.x86_64 is the currently running version puppet-5.5.10/spec/fixtures/unit/provider/package/yum/yum-check-update-simple.txt0000644005276200011600000000120513417161721030026 0ustar jenkinsjenkinsLoaded plugins: fastestmirror Determining fastest mirrors * base: centos.sonn.com * epel: ftp.osuosl.org * extras: mirror.web-ster.com * updates: centos.sonn.com curl.i686 7.32.0-10.fc20 updates curl.x86_64 7.32.0-10.fc20 updates gawk.i686 4.1.0-3.fc20 updates dhclient.i686 12:4.1.1-38.P1.fc20 updates java-1.8.0-openjdk.x86_64 1:1.8.0.131-2.b11.el7_3 updates selinux-policy.noarch 3.12.1-163.fc20 updates-testing puppet-5.5.10/spec/fixtures/unit/provider/package/zypper/0000755005276200011600000000000013417162176023357 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/package/zypper/zypper-list-updates-SLES11sp1.out0000644005276200011600000012610113417161721031403 0ustar jenkinsjenkinsLoading repository data... Reading installed packages... S | Repository | Name | Current Version | Available Version | Arch --+--------------------+-----------------------------------+-----------------------+---------------------------+------- v | SLES11-SP1-Updates | ConsoleKit | 0.2.10-64.13.6 | 0.2.10-64.65.1 | x86_64 v | SLES11-SP1-Updates | ConsoleKit-32bit | 0.2.10-64.13.6 | 0.2.10-64.65.1 | x86_64 v | SLES11-SP1-Updates | ConsoleKit-x11 | 0.2.10-64.13.6 | 0.2.10-64.65.1 | x86_64 v | SLES11-SP1-Updates | Mesa | 7.7-0.4.41 | 7.7-5.10.1 | x86_64 v | SLES11-SP1-Updates | Mesa-32bit | 7.7-0.4.41 | 7.7-5.10.1 | x86_64 v | SLES11-SP1-Updates | MozillaFirefox | 3.6.16-0.2.1 | 10.0.2-0.4.1 | x86_64 v | SLES11-SP1-Updates | MozillaFirefox-branding-SLED | 3.5-1.2.4 | 7-0.6.7.7 | x86_64 v | SLES11-SP1-Updates | MozillaFirefox-translations | 3.6.16-0.2.1 | 10.0.2-0.4.1 | x86_64 v | SLES11-SP1-Updates | NetworkManager-glib | 0.7.0.r4359-15.25.1 | 0.7.1_git20090811-3.9.9.5 | x86_64 v | SLES11-SP1-Updates | PackageKit | 0.3.14-2.12.105 | 0.3.14-2.14.5.1 | x86_64 v | SLES11-SP1-Updates | PackageKit-lang | 0.3.14-2.12.105 | 0.3.14-2.14.5.1 | x86_64 v | SLES11-SP1-Updates | SuSEfirewall2 | 3.6_SVNr208-2.1 | 3.6_SVNr208-2.5.1 | noarch v | SLES11-SP1-Updates | a2ps | 4.13-1326.33 | 4.13-1326.35.1 | x86_64 v | SLES11-SP1-Updates | acpid | 1.0.6-91.6 | 1.0.6-91.16.1 | x86_64 v | SLES11-SP1-Updates | alsa | 1.0.18-16.9.29 | 1.0.18-16.24.1 | x86_64 v | SLES11-SP1-Updates | alsa-plugins | 1.0.18-7.5 | 1.0.18-7.12.23 | x86_64 v | SLES11-SP1-Updates | alsa-plugins-pulse | 1.0.18-7.5 | 1.0.18-7.12.23 | x86_64 v | SLES11-SP1-Updates | apparmor-parser | 2.3.1-8.14.9 | 2.3.1-8.18.7 | x86_64 v | SLES11-SP1-Updates | apparmor-profiles | 2.3-48.3 | 2.3-48.7.1 | noarch v | SLES11-SP1-Updates | apparmor-utils | 2.3.1-9.6.3 | 2.3.1-9.8.5 | noarch v | SLES11-SP1-Updates | at | 3.1.8-1069.15.53 | 3.1.8-1069.18.2 | x86_64 v | SLES11-SP1-Updates | audit | 1.7.7-5.16 | 1.7.7-5.18.4.1 | x86_64 v | SLES11-SP1-Updates | audit-libs | 1.7.7-5.16 | 1.7.7-5.18.4.1 | x86_64 v | SLES11-SP1-Updates | audit-libs-32bit | 1.7.7-5.16 | 1.7.7-5.18.4.1 | x86_64 v | SLES11-SP1-Updates | bind-libs | 9.5.0P2-20.7.1 | 9.6ESVR5P1-0.2.4.1 | x86_64 v | SLES11-SP1-Updates | bind-libs-32bit | 9.5.0P2-20.7.1 | 9.6ESVR5P1-0.2.4.1 | x86_64 v | SLES11-SP1-Updates | bind-utils | 9.5.0P2-20.7.1 | 9.6ESVR5P1-0.2.4.1 | x86_64 v | SLES11-SP1-Updates | cifs-mount | 3.4.3-1.19.1 | 3.4.3-1.34.1 | x86_64 v | SLES11-SP1-Updates | coreutils | 6.12-32.17 | 6.12-32.35.1 | x86_64 v | SLES11-SP1-Updates | coreutils-lang | 6.12-32.17 | 6.12-32.35.1 | x86_64 v | SLES11-SP1-Updates | cpio | 2.9-75.29.1 | 2.9-75.76.1 | x86_64 v | SLES11-SP1-Updates | cpio-lang | 2.9-75.29.1 | 2.9-75.76.1 | x86_64 v | SLES11-SP1-Updates | cron | 4.1-194.24.4 | 4.1-194.199.1 | x86_64 v | SLES11-SP1-Updates | cups-client | 1.3.9-8.37.1 | 1.3.9-8.44.1 | x86_64 v | SLES11-SP1-Updates | cups-libs | 1.3.9-8.37.1 | 1.3.9-8.44.1 | x86_64 v | SLES11-SP1-Updates | cups-libs-32bit | 1.3.9-8.37.1 | 1.3.9-8.44.1 | x86_64 v | SLES11-SP1-Updates | curl | 7.19.0-11.25.1 | 7.19.7-1.18.1 | x86_64 v | SLES11-SP1-Updates | cvs | 1.12.12-144.21 | 1.12.12-144.23.5.1 | x86_64 v | SLES11-SP1-Updates | dbus-1 | 1.2.10-3.15.1 | 1.2.10-3.23.1 | x86_64 v | SLES11-SP1-Updates | dbus-1-32bit | 1.2.10-3.15.1 | 1.2.10-3.23.1 | x86_64 v | SLES11-SP1-Updates | dbus-1-glib | 0.76-34.18.5 | 0.76-34.22.1 | x86_64 v | SLES11-SP1-Updates | dbus-1-glib-32bit | 0.76-34.18.5 | 0.76-34.22.1 | x86_64 v | SLES11-SP1-Updates | dbus-1-x11 | 1.2.10-3.11.33 | 1.2.10-3.23.1 | x86_64 v | SLES11-SP1-Updates | dejavu | 2.26-1.17 | 2.32-4.2.1 | noarch v | SLES11-SP1-Updates | device-mapper | 1.02.27-8.18.1 | 1.02.27-8.22.2 | x86_64 v | SLES11-SP1-Updates | device-mapper-32bit | 1.02.27-8.18.1 | 1.02.27-8.22.2 | x86_64 v | SLES11-SP1-Updates | dhcpcd | 3.2.3-44.20.1 | 3.2.3-44.28.1 | x86_64 v | SLES11-SP1-Updates | dmraid | 1.0.0.rc16-0.6.18 | 1.0.0.rc16-0.12.1 | x86_64 v | SLES11-SP1-Updates | eject | 2.1.0-115.17 | 2.1.0-115.19.2.1 | x86_64 v | SLES11-SP1-Updates | emacs | 22.3-4.32.4 | 22.3-4.36.1 | x86_64 v | SLES11-SP1-Updates | emacs-info | 22.3-4.32.4 | 22.3-4.36.1 | x86_64 v | SLES11-SP1-Updates | emacs-x11 | 22.3-4.32.4 | 22.3-4.36.1 | x86_64 v | SLES11-SP1-Updates | evolution-data-server | 2.28.2-0.16.1 | 2.28.2-0.22.1 | x86_64 v | SLES11-SP1-Updates | evolution-data-server-32bit | 2.28.2-0.16.1 | 2.28.2-0.22.1 | x86_64 v | SLES11-SP1-Updates | evolution-data-server-lang | 2.28.2-0.16.1 | 2.28.2-0.22.1 | x86_64 v | SLES11-SP1-Updates | file-32bit | 4.24-43.17 | 4.24-43.19.1 | x86_64 v | SLES11-SP1-Updates | findutils | 4.4.0-38.24.11 | 4.4.0-38.26.1 | x86_64 v | SLES11-SP1-Updates | findutils-locate | 4.4.0-38.24.11 | 4.4.0-38.26.1 | x86_64 v | SLES11-SP1-Updates | freetype2 | 2.3.7-25.24.1 | 2.3.7-25.28.1 | x86_64 v | SLES11-SP1-Updates | freetype2-32bit | 2.3.7-25.24.1 | 2.3.7-25.28.1 | x86_64 v | SLES11-SP1-Updates | gawk | 3.1.6-26.1.35 | 3.1.8-2.6.1 | x86_64 v | SLES11-SP1-Updates | gdb | 7.0-0.4.16 | 7.3-0.6.1 | x86_64 v | SLES11-SP1-Updates | gdm | 2.24.0-24.28.1 | 2.24.0-24.77.1 | x86_64 v | SLES11-SP1-Updates | gdm-branding-upstream | 2.24.0-24.28.1 | 2.24.0-24.77.1 | x86_64 v | SLES11-SP1-Updates | gdm-lang | 2.24.0-24.28.1 | 2.24.0-24.77.1 | x86_64 v | SLES11-SP1-Updates | glibc | 2.11.1-0.20.1 | 2.11.1-0.34.1 | x86_64 v | SLES11-SP1-Updates | glibc-32bit | 2.11.1-0.20.1 | 2.11.1-0.34.1 | x86_64 v | SLES11-SP1-Updates | glibc-devel | 2.11.1-0.20.1 | 2.11.1-0.34.1 | x86_64 v | SLES11-SP1-Updates | glibc-devel-32bit | 2.11.1-0.20.1 | 2.11.1-0.34.1 | x86_64 v | SLES11-SP1-Updates | glibc-i18ndata | 2.11.1-0.20.1 | 2.11.1-0.34.1 | x86_64 v | SLES11-SP1-Updates | glibc-info | 2.11.1-0.20.1 | 2.11.1-0.34.1 | x86_64 v | SLES11-SP1-Updates | glibc-locale | 2.11.1-0.20.1 | 2.11.1-0.34.1 | x86_64 v | SLES11-SP1-Updates | glibc-locale-32bit | 2.11.1-0.20.1 | 2.11.1-0.34.1 | x86_64 v | SLES11-SP1-Updates | gnome-control-center | 2.28.1-0.9.1 | 2.28.1-0.15.25 | x86_64 v | SLES11-SP1-Updates | gnome-control-center-lang | 2.28.1-0.9.1 | 2.28.1-0.15.25 | x86_64 v | SLES11-SP1-Updates | gnome-desktop | 2.28.2-0.3.22 | 2.28.2-0.6.18 | x86_64 v | SLES11-SP1-Updates | gnome-desktop-lang | 2.28.2-0.3.22 | 2.28.2-0.6.18 | x86_64 v | SLES11-SP1-Updates | gnome-media | 2.28.5-1.2.97 | 2.28.5-1.7.8 | x86_64 v | SLES11-SP1-Updates | gnome-media-lang | 2.28.5-1.2.97 | 2.28.5-1.7.8 | x86_64 v | SLES11-SP1-Updates | gnome-packagekit | 0.3.14-2.76.59 | 0.3.14-2.82.1 | x86_64 v | SLES11-SP1-Updates | gnome-packagekit-lang | 0.3.14-2.76.59 | 0.3.14-2.82.1 | x86_64 v | SLES11-SP1-Updates | gnome-panel | 2.28.0-2.4.49 | 2.28.0-2.6.4 | x86_64 v | SLES11-SP1-Updates | gnome-panel-32bit | 2.28.0-2.4.49 | 2.28.0-2.6.4 | x86_64 v | SLES11-SP1-Updates | gnome-panel-lang | 2.28.0-2.4.49 | 2.28.0-2.6.4 | x86_64 v | SLES11-SP1-Updates | gnome-power-manager | 2.24.1-17.36.1 | 2.24.1-17.52.2 | x86_64 v | SLES11-SP1-Updates | gnome-power-manager-lang | 2.24.1-17.36.1 | 2.24.1-17.52.2 | x86_64 v | SLES11-SP1-Updates | gnome-screensaver | 2.28.3-0.4.30 | 2.28.3-0.28.1 | x86_64 v | SLES11-SP1-Updates | gnome-screensaver-lang | 2.28.3-0.4.30 | 2.28.3-0.28.1 | x86_64 v | SLES11-SP1-Updates | gnome-settings-daemon | 2.28.2-0.25.2 | 2.28.2-0.33.2 | x86_64 v | SLES11-SP1-Updates | gnome-settings-daemon-lang | 2.28.2-0.25.2 | 2.28.2-0.33.2 | x86_64 v | SLES11-SP1-Updates | gnome-system-monitor | 2.28.0-1.3.26 | 2.28.0-1.7.1 | x86_64 v | SLES11-SP1-Updates | gnome-system-monitor-lang | 2.28.0-1.3.26 | 2.28.0-1.7.1 | x86_64 v | SLES11-SP1-Updates | gok | 2.28.1-0.1.136 | 2.28.1-0.3.51 | x86_64 v | SLES11-SP1-Updates | gok-lang | 2.28.1-0.1.136 | 2.28.1-0.3.51 | x86_64 v | SLES11-SP1-Updates | gpg2 | 2.0.9-25.26.1 | 2.0.9-25.33.27.1 | x86_64 v | SLES11-SP1-Updates | gpg2-lang | 2.0.9-25.26.1 | 2.0.9-25.33.27.1 | x86_64 v | SLES11-SP1-Updates | grub | 0.97-162.10.1 | 0.97-162.13.12.1 | x86_64 v | SLES11-SP1-Updates | gtk2 | 2.18.9-0.5.1 | 2.18.9-0.16.1 | x86_64 v | SLES11-SP1-Updates | gtk2-32bit | 2.18.9-0.5.1 | 2.18.9-0.16.1 | x86_64 v | SLES11-SP1-Updates | gtk2-lang | 2.18.9-0.5.1 | 2.18.9-0.16.1 | x86_64 v | SLES11-SP1-Updates | gvfs | 1.4.3-0.3.13 | 1.4.3-0.13.1 | x86_64 v | SLES11-SP1-Updates | gvfs-backends | 1.4.3-0.3.13 | 1.4.3-0.13.1 | x86_64 v | SLES11-SP1-Updates | gvfs-fuse | 1.4.3-0.3.13 | 1.4.3-0.13.1 | x86_64 v | SLES11-SP1-Updates | gvfs-lang | 1.4.3-0.3.13 | 1.4.3-0.13.1 | x86_64 v | SLES11-SP1-Updates | hal | 0.5.12-23.40.5 | 0.5.12-23.47.4 | x86_64 v | SLES11-SP1-Updates | hal-32bit | 0.5.12-23.40.5 | 0.5.12-23.47.4 | x86_64 v | SLES11-SP1-Updates | hplip-hpijs | 3.9.8-3.5.1 | 3.11.10-0.6.7.1 | x86_64 v | SLES11-SP1-Updates | hwinfo | 15.33-0.2.19 | 15.33-0.4.26 | x86_64 v | SLES11-SP1-Updates | iproute2 | 2.6.29.1-6.5.1 | 2.6.29.1-6.7.7.1 | x86_64 v | SLES11-SP1-Updates | iptables | 1.4.6-2.2.24 | 1.4.6-2.8.3.1 | x86_64 v | SLES11-SP1-Updates | iputils | ss021109-292.26.1 | ss021109-292.28.1 | x86_64 v | SLES11-SP1-Updates | irqbalance | 0.55-120.20.9 | 0.55-120.32.1 | x86_64 v | SLES11-SP1-Updates | kbd | 1.14.1-16.24.25 | 1.14.1-16.31.1 | x86_64 v | SLES11-SP1-Updates | kdump | 0.7.8-1.15.7 | 0.7.8-1.33.32.1 | x86_64 v | SLES11-SP1-Updates | kernel-default | 2.6.32.29-0.3.1 | 2.6.32.54-0.3.1 | x86_64 v | SLES11-SP1-Updates | kernel-default-base | 2.6.32.29-0.3.1 | 2.6.32.54-0.3.1 | x86_64 v | SLES11-SP1-Updates | kernel-default-devel | 2.6.32.29-0.3.1 | 2.6.32.54-0.3.1 | x86_64 v | SLES11-SP1-Updates | kernel-source | 2.6.32.29-0.3.1 | 2.6.32.54-0.3.1 | x86_64 v | SLES11-SP1-Updates | kexec-tools | 2.0.0-53.28.1 | 2.0.0-53.30.1 | x86_64 v | SLES11-SP1-Updates | klogd | 1.4.1-708.25.34 | 1.4.1-708.37.1 | x86_64 v | SLES11-SP1-Updates | kpartx | 0.4.8-40.25.1 | 0.4.8-40.44.1 | x86_64 v | SLES11-SP1-Updates | krb5 | 1.6.3-133.46.1 | 1.6.3-133.48.48.1 | x86_64 v | SLES11-SP1-Updates | krb5-32bit | 1.6.3-133.46.1 | 1.6.3-133.48.48.1 | x86_64 v | SLES11-SP1-Updates | krb5-client | 1.6.3-133.46.1 | 1.6.3-133.48.48.1 | x86_64 v | SLES11-SP1-Updates | ksh | 93t-9.184.1 | 93u-0.6.1 | x86_64 v | SLES11-SP1-Updates | libMagickCore1 | 6.4.3.6-7.20.1 | 6.4.3.6-7.22.1 | x86_64 v | SLES11-SP1-Updates | libapparmor1 | 2.3-51.14 | 2.3-51.16.1 | x86_64 v | SLES11-SP1-Updates | libapr-util1 | 1.3.4-12.20.2 | 1.3.4-12.22.21.2 | x86_64 v | SLES11-SP1-Updates | libapr1 | 1.3.3-11.16.1 | 1.3.3-11.18.19.1 | x86_64 v | SLES11-SP1-Updates | libasound2 | 1.0.18-16.9.29 | 1.0.18-16.24.1 | x86_64 v | SLES11-SP1-Updates | libasound2-32bit | 1.0.18-16.9.29 | 1.0.18-16.24.1 | x86_64 v | SLES11-SP1-Updates | libaugeas0 | 0.5.0-1.1.61 | 0.8.1-7.8.2 | x86_64 v | SLES11-SP1-Updates | libblkid1 | 2.16-6.11.1 | 2.16-6.13.1 | x86_64 v | SLES11-SP1-Updates | libcap2 | 2.11-2.15 | 2.11-2.17.1 | x86_64 v | SLES11-SP1-Updates | libcap2-32bit | 2.11-2.15 | 2.11-2.17.1 | x86_64 v | SLES11-SP1-Updates | libcurl4 | 7.19.0-11.25.1 | 7.19.7-1.18.1 | x86_64 v | SLES11-SP1-Updates | libcurl4-32bit | 7.19.0-11.25.1 | 7.19.7-1.18.1 | x86_64 v | SLES11-SP1-Updates | libdrm | 2.4.17-0.3.12 | 2.4.21-2.2.15 | x86_64 v | SLES11-SP1-Updates | libdrm-32bit | 2.4.17-0.3.12 | 2.4.21-2.2.15 | x86_64 v | SLES11-SP1-Updates | libfprint0 | 0.0.6-9.16 | 0.0.6-18.15.4 | x86_64 v | SLES11-SP1-Updates | libfreebl3 | 3.12.8-1.2.1 | 3.13.1-0.2.1 | x86_64 v | SLES11-SP1-Updates | libfreebl3-32bit | 3.12.8-1.2.1 | 3.13.1-0.2.1 | x86_64 v | SLES11-SP1-Updates | libgnome-desktop-2-11 | 2.28.2-0.3.22 | 2.28.2-0.6.18 | x86_64 v | SLES11-SP1-Updates | libgnome-desktop-2-11-32bit | 2.28.2-0.3.22 | 2.28.2-0.6.18 | x86_64 v | SLES11-SP1-Updates | libgnome-window-settings1 | 2.28.1-0.9.1 | 2.28.1-0.15.25 | x86_64 v | SLES11-SP1-Updates | libgnomesu | 1.0.0-307.5.12 | 1.0.0-307.10.1 | x86_64 v | SLES11-SP1-Updates | libgnomesu-lang | 1.0.0-307.5.12 | 1.0.0-307.10.1 | x86_64 v | SLES11-SP1-Updates | libgnomesu0 | 1.0.0-307.5.12 | 1.0.0-307.10.1 | x86_64 v | SLES11-SP1-Updates | libgnutls26 | 2.4.1-24.32.1 | 2.4.1-24.39.33.1 | x86_64 v | SLES11-SP1-Updates | libgnutls26-32bit | 2.4.1-24.32.1 | 2.4.1-24.39.33.1 | x86_64 v | SLES11-SP1-Updates | libgssglue1 | 0.1-6.22 | 0.1-20.2.1 | x86_64 v | SLES11-SP1-Updates | libgvfscommon0 | 1.4.3-0.3.13 | 1.4.3-0.13.1 | x86_64 v | SLES11-SP1-Updates | libicu | 4.0-7.24.11 | 4.0-7.26.1 | x86_64 v | SLES11-SP1-Updates | libjasper | 1.900.1-134.9 | 1.900.1-134.11.1 | x86_64 v | SLES11-SP1-Updates | libldap-2_4-2 | 2.4.20-0.9.1 | 2.4.26-0.12.1 | x86_64 v | SLES11-SP1-Updates | libldap-2_4-2-32bit | 2.4.20-0.9.1 | 2.4.26-0.12.1 | x86_64 v | SLES11-SP1-Updates | libmysqlclient15 | 5.0.67-13.26.1 | 5.0.94-0.2.4.1 | x86_64 v | SLES11-SP1-Updates | libmysqlclient_r15 | 5.0.67-13.26.1 | 5.0.94-0.2.4.1 | x86_64 v | SLES11-SP1-Updates | libneon27 | 0.28.3-2.12.1 | 0.29.6-6.7.1 | x86_64 v | SLES11-SP1-Updates | libnet | 1.1.2.1-140.22 | 1.1.2.1-140.24.1 | x86_64 v | SLES11-SP1-Updates | libnotify | 0.4.4-173.27.1 | 0.4.4-173.29.28.1 | x86_64 v | SLES11-SP1-Updates | libnotify1 | 0.4.4-173.27.1 | 0.4.4-173.29.28.1 | x86_64 v | SLES11-SP1-Updates | libopenssl0_9_8 | 0.9.8h-30.32.1 | 0.9.8j-0.28.1 | x86_64 v | SLES11-SP1-Updates | libopenssl0_9_8-32bit | 0.9.8h-30.32.1 | 0.9.8j-0.28.1 | x86_64 v | SLES11-SP1-Updates | libpackagekit-glib10 | 0.3.14-2.12.105 | 0.3.14-2.14.5.1 | x86_64 v | SLES11-SP1-Updates | libpcap0 | 0.9.8-50.4.80 | 0.9.8-50.6.2 | x86_64 v | SLES11-SP1-Updates | libpciaccess0 | 7.4-8.24.2 | 7.4_0.11.0-0.4.6.1 | x86_64 v | SLES11-SP1-Updates | libpng12-0 | 1.2.31-5.18.1 | 1.2.31-5.25.1 | x86_64 v | SLES11-SP1-Updates | libpng12-0-32bit | 1.2.31-5.18.1 | 1.2.31-5.25.1 | x86_64 v | SLES11-SP1-Updates | libpulse-browse0 | 0.9.21-1.5.26 | 0.9.21-1.14.8 | x86_64 v | SLES11-SP1-Updates | libpulse-mainloop-glib0 | 0.9.21-1.5.26 | 0.9.21-1.14.8 | x86_64 v | SLES11-SP1-Updates | libpulse0 | 0.9.21-1.5.26 | 0.9.21-1.14.8 | x86_64 v | SLES11-SP1-Updates | libpulse0-32bit | 0.9.21-1.5.26 | 0.9.21-1.14.8 | x86_64 v | SLES11-SP1-Updates | libpython2_6-1_0 | 2.6.0-8.10.1 | 2.6.0-8.14.1 | x86_64 v | SLES11-SP1-Updates | libqt4 | 4.6.2-1.6.9 | 4.6.3-5.12.1 | x86_64 v | SLES11-SP1-Updates | libqt4-32bit | 4.6.2-1.6.9 | 4.6.3-5.12.1 | x86_64 v | SLES11-SP1-Updates | libqt4-qt3support | 4.6.2-1.6.9 | 4.6.3-5.12.1 | x86_64 v | SLES11-SP1-Updates | libqt4-qt3support-32bit | 4.6.2-1.6.9 | 4.6.3-5.12.1 | x86_64 v | SLES11-SP1-Updates | libqt4-sql | 4.6.2-1.6.9 | 4.6.3-5.12.1 | x86_64 v | SLES11-SP1-Updates | libqt4-sql-32bit | 4.6.2-1.6.9 | 4.6.3-5.12.1 | x86_64 v | SLES11-SP1-Updates | libqt4-sql-mysql | 4.6.2-1.6.11 | 4.6.3-5.10.1 | x86_64 v | SLES11-SP1-Updates | libqt4-x11 | 4.6.2-1.6.9 | 4.6.3-5.12.1 | x86_64 v | SLES11-SP1-Updates | libqt4-x11-32bit | 4.6.2-1.6.9 | 4.6.3-5.12.1 | x86_64 v | SLES11-SP1-Updates | librsvg | 2.26.0-2.1.227 | 2.26.0-2.3.1 | x86_64 v | SLES11-SP1-Updates | librsvg-32bit | 2.26.0-2.1.227 | 2.26.0-2.3.1 | x86_64 v | SLES11-SP1-Updates | libslab-lang | 2.27.91-6.10.1 | 2.27.91-6.15.2 | x86_64 v | SLES11-SP1-Updates | libslab0 | 2.27.91-6.10.1 | 2.27.91-6.15.2 | x86_64 v | SLES11-SP1-Updates | libsmbclient0 | 3.4.3-1.19.1 | 3.4.3-1.34.1 | x86_64 v | SLES11-SP1-Updates | libsmbclient0-32bit | 3.4.3-1.19.1 | 3.4.3-1.34.1 | x86_64 v | SLES11-SP1-Updates | libsndfile | 1.0.20-2.1.46 | 1.0.20-2.4.1 | x86_64 v | SLES11-SP1-Updates | libsndfile-32bit | 1.0.20-2.1.46 | 1.0.20-2.4.1 | x86_64 v | SLES11-SP1-Updates | libsnmp15 | 5.4.2.1-8.5.1 | 5.4.2.1-8.12.6.1 | x86_64 v | SLES11-SP1-Updates | libsoup-2_4-1 | 2.28.2-0.1.151 | 2.28.2-0.3.1 | x86_64 v | SLES11-SP1-Updates | libsoup-2_4-1-32bit | 2.28.2-0.1.151 | 2.28.2-0.3.1 | x86_64 v | SLES11-SP1-Updates | libtalloc1 | 3.4.3-1.19.1 | 3.4.3-1.34.1 | x86_64 v | SLES11-SP1-Updates | libtalloc1-32bit | 3.4.3-1.19.1 | 3.4.3-1.34.1 | x86_64 v | SLES11-SP1-Updates | libtdb1 | 3.4.3-1.19.1 | 3.4.3-1.34.1 | x86_64 v | SLES11-SP1-Updates | libtdb1-32bit | 3.4.3-1.19.1 | 3.4.3-1.34.1 | x86_64 v | SLES11-SP1-Updates | libtiff3 | 3.8.2-141.16.1 | 3.8.2-141.142.1 | x86_64 v | SLES11-SP1-Updates | libtiff3-32bit | 3.8.2-141.16.1 | 3.8.2-141.142.1 | x86_64 v | SLES11-SP1-Updates | libtirpc1 | 0.2.1-1.3.1 | 0.2.1-1.5.1 | x86_64 v | SLES11-SP1-Updates | libuuid1 | 2.16-6.11.1 | 2.16-6.13.1 | x86_64 v | SLES11-SP1-Updates | libuuid1-32bit | 2.16-6.11.1 | 2.16-6.13.1 | x86_64 v | SLES11-SP1-Updates | libwbclient0 | 3.4.3-1.19.1 | 3.4.3-1.34.1 | x86_64 v | SLES11-SP1-Updates | libwbclient0-32bit | 3.4.3-1.19.1 | 3.4.3-1.34.1 | x86_64 v | SLES11-SP1-Updates | libxcrypt | 3.0.3-0.1.50 | 3.0.3-0.6.1 | x86_64 v | SLES11-SP1-Updates | libxcrypt-32bit | 3.0.3-0.1.50 | 3.0.3-0.6.1 | x86_64 v | SLES11-SP1-Updates | libxml2 | 2.7.6-0.7.1 | 2.7.6-0.13.1 | x86_64 v | SLES11-SP1-Updates | libxml2-32bit | 2.7.6-0.7.1 | 2.7.6-0.13.1 | x86_64 v | SLES11-SP1-Updates | libzypp | 6.35.3-0.3.1 | 6.37.5-0.5.6 | x86_64 v | SLES11-SP1-Updates | limal-ca-mgm | 1.5.22-0.2.15 | 1.5.23-0.3.2 | x86_64 v | SLES11-SP1-Updates | limal-ca-mgm-perl | 1.5.22-0.2.15 | 1.5.23-0.3.2 | x86_64 v | SLES11-SP1-Updates | logrotate | 3.7.7-10.22 | 3.7.7-10.24.1 | x86_64 v | SLES11-SP1-Updates | lvm2 | 2.02.39-18.32.2 | 2.02.39-18.48.1 | x86_64 v | SLES11-SP1-Updates | man-pages | 3.15-2.8.28 | 3.15-2.14.1 | noarch v | SLES11-SP1-Updates | mcelog | 1.0.2010.03.10-0.2.8 | 1.0.2010.03.10-0.4.1 | x86_64 v | SLES11-SP1-Updates | mdadm | 3.0.3-0.22.4 | 3.0.3-0.30.1 | x86_64 v | SLES11-SP1-Updates | microcode_ctl | 1.17-102.22.1 | 1.17-102.40.1 | x86_64 v | SLES11-SP1-Updates | mkinitrd | 2.4.1-0.14.1 | 2.4.1-0.16.16.1 | x86_64 v | SLES11-SP1-Updates | mozilla-nspr | 4.8.6-1.2.1 | 4.8.9-1.2.2.1 | x86_64 v | SLES11-SP1-Updates | mozilla-nspr-32bit | 4.8.6-1.2.1 | 4.8.9-1.2.2.1 | x86_64 v | SLES11-SP1-Updates | mozilla-nss | 3.12.8-1.2.1 | 3.13.1-0.2.1 | x86_64 v | SLES11-SP1-Updates | mozilla-nss-32bit | 3.12.8-1.2.1 | 3.13.1-0.2.1 | x86_64 v | SLES11-SP1-Updates | mozilla-xulrunner191 | 1.9.1.18-0.2.1 | 1.9.1.19-0.2.1 | x86_64 v | SLES11-SP1-Updates | mozilla-xulrunner191-32bit | 1.9.1.18-0.2.1 | 1.9.1.19-0.2.1 | x86_64 v | SLES11-SP1-Updates | mozilla-xulrunner191-gnomevfs | 1.9.1.18-0.2.1 | 1.9.1.19-0.2.1 | x86_64 v | SLES11-SP1-Updates | mozilla-xulrunner191-translations | 1.9.1.18-0.2.1 | 1.9.1.19-0.2.1 | x86_64 v | SLES11-SP1-Updates | mozilla-xulrunner192 | 1.9.2.16-1.2.1 | 1.9.2.27-0.2.1 | x86_64 v | SLES11-SP1-Updates | mozilla-xulrunner192-translations | 1.9.2.16-1.2.1 | 1.9.2.27-0.2.1 | x86_64 v | SLES11-SP1-Updates | multipath-tools | 0.4.8-40.25.1 | 0.4.8-40.44.1 | x86_64 v | SLES11-SP1-Updates | mysql | 5.0.67-13.26.1 | 5.0.94-0.2.4.1 | x86_64 v | SLES11-SP1-Updates | mysql-client | 5.0.67-13.26.1 | 5.0.94-0.2.4.1 | x86_64 v | SLES11-SP1-Updates | nautilus | 2.28.4-1.6.8 | 2.28.4-1.10.1 | x86_64 v | SLES11-SP1-Updates | nautilus-32bit | 2.28.4-1.6.8 | 2.28.4-1.10.1 | x86_64 v | SLES11-SP1-Updates | nautilus-lang | 2.28.4-1.6.8 | 2.28.4-1.10.1 | x86_64 v | SLES11-SP1-Updates | net-tools | 1.60-725.23.24.39 | 1.60-725.30.1 | x86_64 v | SLES11-SP1-Updates | nfs-client | 1.2.1-2.10.1 | 1.2.1-2.18.1 | x86_64 v | SLES11-SP1-Updates | nfs-doc | 1.2.1-2.10.1 | 1.2.1-2.18.1 | x86_64 v | SLES11-SP1-Updates | notification-daemon | 0.3.7-185.30.1 | 0.3.7-185.34.2 | x86_64 v | SLES11-SP1-Updates | notification-daemon-lang | 0.3.7-185.30.1 | 0.3.7-185.34.2 | x86_64 v | SLES11-SP1-Updates | nscd | 2.11.1-0.20.1 | 2.11.1-0.34.1 | x86_64 v | SLES11-SP1-Updates | ntp | 4.2.4p8-1.6.1 | 4.2.4p8-1.18.1 | x86_64 v | SLES11-SP1-Updates | openldap2-client | 2.4.20-0.9.1 | 2.4.26-0.12.1 | x86_64 v | SLES11-SP1-Updates | openslp | 1.2.0-172.18.1 | 1.2.0-172.22.1 | x86_64 v | SLES11-SP1-Updates | openslp-32bit | 1.2.0-172.18.1 | 1.2.0-172.22.1 | x86_64 v | SLES11-SP1-Updates | openslp-server | 1.2.0-172.18.1 | 1.2.0-172.22.1 | x86_64 v | SLES11-SP1-Updates | openssh-askpass | 5.1p1-41.31.36 | 5.1p1-41.51.1 | x86_64 v | SLES11-SP1-Updates | openssl | 0.9.8h-30.32.1 | 0.9.8j-0.28.1 | x86_64 v | SLES11-SP1-Updates | openssl-certs | 0.9.8h-27.1.30 | 0.9.8h-27.3.1 | noarch v | SLES11-SP1-Updates | opie | 2.4-662.16 | 2.4-662.18.1 | x86_64 v | SLES11-SP1-Updates | opie-32bit | 2.4-662.16 | 2.4-662.18.1 | x86_64 v | SLES11-SP1-Updates | pam | 1.0.4-0.5.12 | 1.0.4-0.7.1 | x86_64 v | SLES11-SP1-Updates | pam-32bit | 1.0.4-0.5.12 | 1.0.4-0.7.1 | x86_64 v | SLES11-SP1-Updates | pam-doc | 1.0.4-0.5.12 | 1.0.4-0.7.1 | x86_64 v | SLES11-SP1-Updates | pam-modules | 11-1.6.15 | 11-1.22.1 | x86_64 v | SLES11-SP1-Updates | pam-modules-32bit | 11-1.6.15 | 11-1.22.1 | x86_64 v | SLES11-SP1-Updates | parted | 1.8.8-102.21.8 | 1.8.8-102.23.1 | x86_64 v | SLES11-SP1-Updates | parted-32bit | 1.8.8-102.21.8 | 1.8.8-102.23.1 | x86_64 v | SLES11-SP1-Updates | perl | 5.10.0-64.53.1 | 5.10.0-64.55.1 | x86_64 v | SLES11-SP1-Updates | perl-32bit | 5.10.0-64.53.1 | 5.10.0-64.55.1 | x86_64 v | SLES11-SP1-Updates | perl-Bootloader | 0.4.89.20-0.3.1 | 0.4.89.29-0.6.1 | x86_64 v | SLES11-SP1-Updates | perl-base | 5.10.0-64.53.1 | 5.10.0-64.55.1 | x86_64 v | SLES11-SP1-Updates | perl-doc | 5.10.0-64.53.1 | 5.10.0-64.55.1 | x86_64 v | SLES11-SP1-Updates | perl-libapparmor | 2.3-51.14 | 2.3-51.16.1 | x86_64 v | SLES11-SP1-Updates | perl-libwww-perl | 5.816-2.15 | 5.816-2.23.1 | x86_64 v | SLES11-SP1-Updates | perl-satsolver | 0.14.18-0.2.1 | 0.14.19-0.3.8 | x86_64 v | SLES11-SP1-Updates | permissions | 2011.2.15-0.3.1 | 2011.6.28-0.3.1 | x86_64 v | SLES11-SP1-Updates | pm-utils | 0.99.4.20071229-12.10 | 0.99.4.20071229-12.14.1 | x86_64 v | SLES11-SP1-Updates | pmtools | 20071116-44.18 | 20071116-44.20.2.1 | x86_64 v | SLES11-SP1-Updates | popt | 1.7-37.25.1 | 1.7-37.29.29.1 | x86_64 v | SLES11-SP1-Updates | popt-32bit | 1.7-37.25.1 | 1.7-37.29.29.1 | x86_64 v | SLES11-SP1-Updates | postfix | 2.5.6-5.4.21 | 2.5.6-5.10.1 | x86_64 v | SLES11-SP1-Updates | ppp | 2.4.5.git-2.23.10 | 2.4.5.git-2.27.1 | x86_64 v | SLES11-SP1-Updates | pulseaudio | 0.9.21-1.5.26 | 0.9.21-1.14.8 | x86_64 v | SLES11-SP1-Updates | pulseaudio-esound-compat | 0.9.21-1.5.26 | 0.9.21-1.14.8 | x86_64 v | SLES11-SP1-Updates | pulseaudio-lang | 0.9.21-1.5.26 | 0.9.21-1.14.8 | x86_64 v | SLES11-SP1-Updates | pulseaudio-module-x11 | 0.9.21-1.5.26 | 0.9.21-1.14.8 | x86_64 v | SLES11-SP1-Updates | pulseaudio-utils | 0.9.21-1.5.26 | 0.9.21-1.14.8 | x86_64 v | SLES11-SP1-Updates | pwdutils | 3.2.8-0.2.35 | 3.2.8-0.4.1 | x86_64 v | SLES11-SP1-Updates | python | 2.6.0-8.10.1 | 2.6.0-8.14.1 | x86_64 v | SLES11-SP1-Updates | python-base | 2.6.0-8.10.1 | 2.6.0-8.14.1 | x86_64 v | SLES11-SP1-Updates | python-satsolver | 0.14.18-0.2.1 | 0.14.19-0.3.8 | x86_64 v | SLES11-SP1-Updates | python-tk | 2.6.0-8.10.1 | 2.6.0-8.14.1 | x86_64 v | SLES11-SP1-Updates | python-xml | 2.6.0-8.10.1 | 2.6.0-8.14.1 | x86_64 v | SLES11-SP1-Updates | rarian | 0.8.1-5.16 | 0.8.1-5.17.9 | x86_64 v | SLES11-SP1-Updates | release-notes-sles | 11.1.1.1-0.2.1 | 11.1.1.9-0.6.1 | x86_64 v | SLES11-SP1-Updates | rpm | 4.4.2.3-37.25.1 | 4.4.2.3-37.29.29.1 | x86_64 v | SLES11-SP1-Updates | rpm-32bit | 4.4.2.3-37.25.1 | 4.4.2.3-37.29.29.1 | x86_64 v | SLES11-SP1-Updates | rsh | 0.17-706.16 | 0.17-706.18.1 | x86_64 v | SLES11-SP1-Updates | rsync | 3.0.4-2.33.82 | 3.0.4-2.38.1 | x86_64 v | SLES11-SP1-Updates | samba | 3.4.3-1.19.1 | 3.4.3-1.34.1 | x86_64 v | SLES11-SP1-Updates | samba-32bit | 3.4.3-1.19.1 | 3.4.3-1.34.1 | x86_64 v | SLES11-SP1-Updates | samba-client | 3.4.3-1.19.1 | 3.4.3-1.34.1 | x86_64 v | SLES11-SP1-Updates | samba-client-32bit | 3.4.3-1.19.1 | 3.4.3-1.34.1 | x86_64 v | SLES11-SP1-Updates | samba-winbind | 3.4.3-1.19.1 | 3.4.3-1.34.1 | x86_64 v | SLES11-SP1-Updates | samba-winbind-32bit | 3.4.3-1.19.1 | 3.4.3-1.34.1 | x86_64 v | SLES11-SP1-Updates | satsolver-tools | 0.14.18-0.2.1 | 0.14.19-0.3.8 | x86_64 v | SLES11-SP1-Updates | sax2 | 8.1-561.29.2 | 8.1-561.548.7 | x86_64 v | SLES11-SP1-Updates | sax2-gui | 8.1-561.29.2 | 8.1-561.548.7 | x86_64 v | SLES11-SP1-Updates | sax2-ident | 8.1-561.29.2 | 8.1-561.548.7 | x86_64 v | SLES11-SP1-Updates | sax2-libsax | 8.1-561.29.2 | 8.1-561.548.7 | x86_64 v | SLES11-SP1-Updates | sax2-libsax-perl | 8.1-561.29.2 | 8.1-561.548.7 | x86_64 v | SLES11-SP1-Updates | sax2-tools | 8.1-561.29.2 | 8.1-561.548.7 | x86_64 v | SLES11-SP1-Updates | sblim-cmpi-base | 1.6.0-0.1.87 | 1.6.0-0.4.1 | x86_64 v | SLES11-SP1-Updates | sblim-sfcb | 1.3.7-0.11.1 | 1.3.7-0.17.1 | x86_64 v | SLES11-SP1-Updates | screen | 4.0.2-162.17 | 4.0.2-162.19.2.1 | x86_64 v | SLES11-SP1-Updates | sg3_utils | 1.28-0.3.5 | 1.28-0.5.1 | x86_64 v | SLES11-SP1-Updates | smartmontools | 5.38.0.20081027-2.14 | 5.38.0.20081027-2.16.2.1 | x86_64 v | SLES11-SP1-Updates | snmp-mibs | 5.4.2.1-8.5.1 | 5.4.2.1-8.12.6.1 | x86_64 v | SLES11-SP1-Updates | star | 1.5final-28.19 | 1.5final-28.21.1 | x86_64 v | SLES11-SP1-Updates | supportutils | 1.20-0.10.1 | 1.20-0.28.27.1 | noarch v | SLES11-SP1-Updates | suseRegister | 1.4-1.11.1 | 1.4-1.13.1 | noarch v | SLES11-SP1-Updates | suspend | 0.80.20081103-1.39.63 | 0.80.20081103-1.45.2 | x86_64 v | SLES11-SP1-Updates | sysconfig | 0.71.30-0.8.1 | 0.71.31-0.7.1 | x86_64 v | SLES11-SP1-Updates | syslog-ng | 2.0.9-27.28.3 | 2.0.9-27.32.1 | x86_64 v | SLES11-SP1-Updates | sysstat | 8.1.5-7.12.1 | 8.1.5-7.27.1 | x86_64 v | SLES11-SP1-Updates | sysvinit | 2.86-200.1 | 2.86-204.1 | x86_64 v | SLES11-SP1-Updates | tar | 1.20-23.23.1 | 1.26-1.2.4.1 | x86_64 v | SLES11-SP1-Updates | tcsh | 6.15.00-93.25.18 | 6.15.00-93.33.1 | x86_64 v | SLES11-SP1-Updates | tightvnc | 1.3.9-81.11.28 | 1.3.9-81.13.1 | x86_64 v | SLES11-SP1-Updates | timezone | 2011d-0.3.1 | 2011m-0.3.1 | x86_64 v | SLES11-SP1-Updates | tk | 8.5.5-3.12 | 8.5.5-3.14.1 | x86_64 v | SLES11-SP1-Updates | tk-32bit | 8.5.5-3.12 | 8.5.5-3.14.1 | x86_64 v | SLES11-SP1-Updates | util-linux | 2.16-6.11.1 | 2.16-6.13.1 | x86_64 v | SLES11-SP1-Updates | util-linux-lang | 2.16-6.11.1 | 2.16-6.13.1 | x86_64 v | SLES11-SP1-Updates | uuid-runtime | 2.16-6.11.1 | 2.16-6.13.1 | x86_64 v | SLES11-SP1-Updates | vino | 2.28.1-2.1.143 | 2.28.1-2.3.1 | x86_64 v | SLES11-SP1-Updates | vino-lang | 2.28.1-2.1.143 | 2.28.1-2.3.1 | x86_64 v | SLES11-SP1-Updates | xinetd | 2.3.14-130.10.1 | 2.3.14-130.131.1 | x86_64 v | SLES11-SP1-Updates | xkeyboard-config | 1.5-4.28.1 | 1.5-4.40.1 | noarch v | SLES11-SP1-Updates | xorg-x11 | 7.4-9.39.1 | 7.4-9.47.1 | x86_64 v | SLES11-SP1-Updates | xorg-x11-Xvnc | 7.4-27.30.1 | 7.4-27.40.56.1 | x86_64 v | SLES11-SP1-Updates | xorg-x11-driver-input | 7.4-13.31.1 | 7.4-13.33.6 | x86_64 v | SLES11-SP1-Updates | xorg-x11-driver-video | 7.4-40.26.20 | 7.4-40.32.4 | x86_64 v | SLES11-SP1-Updates | xorg-x11-libX11 | 7.4-5.5 | 7.4-5.9.1 | x86_64 v | SLES11-SP1-Updates | xorg-x11-libX11-32bit | 7.4-5.5 | 7.4-5.9.1 | x86_64 v | SLES11-SP1-Updates | xorg-x11-libs | 7.4-8.24.2 | 7.4-8.26.32.1 | x86_64 v | SLES11-SP1-Updates | xorg-x11-libs-32bit | 7.4-8.24.2 | 7.4-8.26.32.1 | x86_64 v | SLES11-SP1-Updates | xorg-x11-server | 7.4-27.30.1 | 7.4-27.40.56.1 | x86_64 v | SLES11-SP1-Updates | xorg-x11-server-extra | 7.4-27.30.1 | 7.4-27.40.56.1 | x86_64 v | SLES11-SP1-Updates | xorg-x11-xauth | 7.4-9.39.1 | 7.4-9.47.1 | x86_64 v | SLES11-SP1-Updates | yast2 | 2.17.92-0.2.27 | 2.17.92.2-0.3.1 | x86_64 v | SLES11-SP1-Updates | yast2-ca-management | 2.17.17-0.2.34 | 2.17.22-0.6.1 | noarch v | SLES11-SP1-Updates | yast2-core | 2.17.35-0.2.17 | 2.17.35.3-0.3.1 | x86_64 v | SLES11-SP1-Updates | yast2-country | 2.17.48-0.2.2 | 2.17.50-0.4.1 | x86_64 v | SLES11-SP1-Updates | yast2-country-data | 2.17.48-0.2.2 | 2.17.50-0.4.1 | x86_64 v | SLES11-SP1-Updates | yast2-http-server | 2.17.5-1.53 | 2.17.14-0.2.2 | noarch v | SLES11-SP1-Updates | yast2-installation | 2.17.75-0.2.13 | 2.17.76.1-0.3.1 | noarch v | SLES11-SP1-Updates | yast2-iscsi-client | 2.17.20-0.2.23 | 2.17.22-0.5.1 | noarch v | SLES11-SP1-Updates | yast2-kerberos-server | 2.17.7-0.1.164 | 2.17.8-0.6.2 | noarch v | SLES11-SP1-Updates | yast2-ldap-client | 2.17.22-0.2.1.18 | 2.17.23-0.2.1 | noarch v | SLES11-SP1-Updates | yast2-ncurses | 2.17.18-0.2.1 | 2.17.18.1-0.3.15 | x86_64 v | SLES11-SP1-Updates | yast2-ncurses-pkg | 2.17.17-0.2.2 | 2.17.17.1-0.4.1 | x86_64 v | SLES11-SP1-Updates | yast2-network | 2.17.141-0.3.10 | 2.17.155.2-0.3.8 | x86_64 v | SLES11-SP1-Updates | yast2-packager | 2.17.78-0.2.6 | 2.17.78.3-0.3.11 | x86_64 v | SLES11-SP1-Updates | yast2-pkg-bindings | 2.17.45-0.2.10 | 2.17.45.5-0.3.1 | x86_64 v | SLES11-SP1-Updates | yast2-qt | 2.18.13-0.3.1 | 2.18.14-0.3.1 | x86_64 v | SLES11-SP1-Updates | yast2-registration | 2.17.35-0.5.1 | 2.17.35.3-0.4.2.1 | noarch v | SLES11-SP1-Updates | yast2-registration-branding-SLE | 2.17.35-0.5.1 | 2.17.35.3-0.4.2.1 | noarch v | SLES11-SP1-Updates | yast2-storage | 2.17.99-0.2.5 | 2.17.99.2-0.3.1 | x86_64 v | SLES11-SP1-Updates | yast2-storage-lib | 2.17.99-0.2.5 | 2.17.99.2-0.3.1 | x86_64 v | SLES11-SP1-Updates | yast2-users | 2.17.43-0.2.20 | 2.17.43.1-0.3.1 | x86_64 v | SLES11-SP1-Updates | yast2-wagon | 2.17.17-0.2.56 | 2.17.17.8-0.3.1 | noarch v | SLES11-SP1-Updates | yast2-x11 | 2.17.13-0.2.2 | 2.17.13.1-0.3.1 | noarch v | SLES11-SP1-Updates | zypper | 1.3.12-0.3.1 | 1.3.16-0.3.7 | x86_64 puppet-5.5.10/spec/fixtures/unit/provider/package/zypper/zypper-list-updates-empty.out0000644005276200011600000000011313417161721031177 0ustar jenkinsjenkinsLoading repository data... Reading installed packages... No updates found. puppet-5.5.10/spec/fixtures/unit/provider/parsedfile/0000755005276200011600000000000013417162176022551 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/parsedfile/simple.txt0000644005276200011600000000036513417161721024602 0ustar jenkinsjenkins# This is a sample fixture for the parsedfile provider. # HEADER As added by software from a third party. # Another inconspicuous comment. A generic content line with: a value. A_second_line_with_no_spaces Qexample for bug PUP-4012 Another line puppet-5.5.10/spec/fixtures/unit/provider/service/0000755005276200011600000000000013417162176022073 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/service/base/0000755005276200011600000000000013417162176023005 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/service/base/ps_ef.mixed_encoding0000644005276200011600000000035213417161721026772 0ustar jenkinsjenkinsUID PID PPID C STIME TTY TIME CMD root 4113 26549 0 11:07 pts/0 00:00:00 latin-1 args mäni interesting furry animals root 4056 26549 0 11:06 pts/0 00:00:00 utf-8 args including the majestik møøse puppet-5.5.10/spec/fixtures/unit/provider/service/gentoo/0000755005276200011600000000000013417162176023366 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/service/gentoo/rc_update_show0000644005276200011600000000312413417161721026312 0ustar jenkinsjenkins alsasound | default bootmisc | boot devfs | sysinit dmcrypt | boot dmesg | sysinit fsck | boot hostname | boot hwclock | boot keymaps | boot killprocs | shutdown local | default localmount | boot lvm | boot modules | boot mount-ro | shutdown mtab | boot net.lo | boot netmount | default procfs | boot root | boot rsyslog | boot savecache | shutdown swap | boot swapfiles | boot sysctl | boot termencoding | boot udev | sysinit udev-postmount | default urandom | boot xdm | default puppet-5.5.10/spec/fixtures/unit/provider/service/openbsd/0000755005276200011600000000000013417162176023525 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/service/openbsd/rcctl_getall0000644005276200011600000000016413417161721026103 0ustar jenkinsjenkinsaccounting=NO pf=YES postgresql_flags=-l /var/postgresql/logfile tftpd_flags=/tftpboot wsmoused_flags=NO xdm_flags= puppet-5.5.10/spec/fixtures/unit/provider/service/openrc/0000755005276200011600000000000013417162176023361 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/service/openrc/rcservice_list0000644005276200011600000000012613417161721026316 0ustar jenkinsjenkinsalsasound consolefont lvm-monitoring pydoc-2.7 pydoc-3.2 wpa_supplicant xdm xdm-setup puppet-5.5.10/spec/fixtures/unit/provider/service/openrc/rcstatus0000644005276200011600000000605213417161721025152 0ustar jenkinsjenkinsRunlevel: boot hwclock [ started ] modules [ started ] dmcrypt [ started ] lvm [ started ] fsck [ started ] root [ started ] mtab [ started ] swap [ started ] localmount [ started ] sysctl [ started ] bootmisc [ started ] hostname [ started ] termencoding [ started ] keymaps [ started ] net.lo [ started ] procfs [ started ] rsyslog [ started ] swapfiles [ started ] urandom [ started ] Runlevel: default netmount [ started ] foo_with_very_very_long_servicename_no_still_not_the_end_wait_for_it_almost_there_almost_there_now_finally_the_end [ started ] xdm [ started ] alsasound [ started ] udev-postmount [ started ] local [ started ] Runlevel: shutdown killprocs [ stopped ] savecache [ stopped ] mount-ro [ stopped ] Runlevel: sysinit dmesg [ started ] udev [ started ] devfs [ started ] Dynamic Runlevel: hotplugged net.eth0 [ started ] pcscd [ started ] Dynamic Runlevel: needed sysfs [ started ] udev-mount [ started ] Dynamic Runlevel: manual sshd [ started ] puppet-5.5.10/spec/fixtures/unit/provider/service/smf/0000755005276200011600000000000013417162176022660 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/service/smf/svcs.out0000644005276200011600000000026313417161721024363 0ustar jenkinsjenkinslegacy_run lrc:/etc/rcS_d/S50sk98sol online svc:/system/svc/restarter:default maintenance svc:/network/cswrsyncd:default degraded svc:/network/dns/client:default puppet-5.5.10/spec/fixtures/unit/provider/service/smf/svcs_fmri.out0000644005276200011600000000024613417161721025401 0ustar jenkinsjenkinsfmri svc:/application/tstapp:default name Dummy enabled false state disabled next_state none state_time July 26, 2018 11:57:49 AM PDT puppet-5.5.10/spec/fixtures/unit/provider/service/smf/svcs_multiple_fmris.out0000644005276200011600000000050513417161721027475 0ustar jenkinsjenkinsfmri svc:/application/tstapp:one name Dummy enabled false state disabled next_state none state_time July 26, 2018 11:57:49 AM PDT fmri svc:/application/tstapp:two name Dummy enabled false state disabled next_state none state_time July 26, 2018 11:57:49 AM PDT puppet-5.5.10/spec/fixtures/unit/provider/service/systemd/0000755005276200011600000000000013417162176023563 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/service/systemd/list_unit_files_services0000644005276200011600000000055413417161721030604 0ustar jenkinsjenkinsUNIT FILE STATE arp-ethers.service disabled auditd.service enabled autovt@.service disabled avahi-daemon.service enabled blk-availability.service disabled brandbot.service static puppet-5.5.10/spec/fixtures/unit/provider/user/0000755005276200011600000000000013417162176021411 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/user/aix/0000755005276200011600000000000013417162176022172 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/user/aix/aix_passwd_file.out0000644005276200011600000000056013417161721026060 0ustar jenkinsjenkins test_aix_user: password = some_password lastupdate = last_update no_password_user: lastupdate = another_last_update daemon: password = * bin: password = * sys: password = * adm: password = * uucp: password = * guest: password = * nobody: password = * lpd: password = * puppet-5.5.10/spec/fixtures/unit/provider/augeas/0000755005276200011600000000000013417162176021700 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/augeas/augeas/0000755005276200011600000000000013417162176023145 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/augeas/augeas/augeas/0000755005276200011600000000000013417162176024412 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/augeas/augeas/augeas/lenses/0000755005276200011600000000000013417162176025703 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/augeas/augeas/augeas/lenses/test.aug0000644005276200011600000000054413417161722027357 0ustar jenkinsjenkins(* Simple lens, written to be distributed with Puppet unit tests. Author: Dominic Cleal About: License: This file is licensed under the Apache 2.0 licence, like the rest of Puppet. *) module Test = autoload xfm let lns = [ seq "line" . store /[^\n]+/ . del "\n" "\n" ]* let filter = incl "/etc/test" let xfm = transform lns filter puppet-5.5.10/spec/fixtures/unit/provider/augeas/augeas/etc/0000755005276200011600000000000013417162176023720 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/augeas/augeas/etc/fstab0000644005276200011600000000137013417161722024737 0ustar jenkinsjenkins/dev/vg00/lv00 / ext3 defaults 1 1 LABEL=/boot /boot ext3 defaults 1 2 devpts /dev/pts devpts gid=5,mode=620 0 0 tmpfs /dev/shm tmpfs defaults 0 0 /dev/vg00/home /home ext3 defaults 1 2 proc /proc proc defaults 0 0 sysfs /sys sysfs defaults 0 0 /dev/vg00/local /local ext3 defaults 1 2 /dev/vg00/images /var/lib/xen/images ext3 defaults 1 2 /dev/vg00/swap swap swap defaults 0 0 puppet-5.5.10/spec/fixtures/unit/provider/augeas/augeas/etc/hosts0000644005276200011600000000046513417161722025004 0ustar jenkinsjenkins# Do not remove the following line, or various programs # that require network functionality will fail. 127.0.0.1 localhost.localdomain localhost galia.watzmann.net galia #172.31.122.254 granny.watzmann.net granny puppet #172.31.122.1 galia.watzmann.net galia 172.31.122.14 orange.watzmann.net orange puppet-5.5.10/spec/fixtures/unit/provider/augeas/augeas/etc/test0000644005276200011600000000001413417161722024611 0ustar jenkinsjenkinsfoo bar baz puppet-5.5.10/spec/fixtures/unit/provider/augeas/augeas/test.aug0000644005276200011600000000054413417161722024621 0ustar jenkinsjenkins(* Simple lens, written to be distributed with Puppet unit tests. Author: Dominic Cleal About: License: This file is licensed under the Apache 2.0 licence, like the rest of Puppet. *) module Test = autoload xfm let lns = [ seq "line" . store /[^\n]+/ . del "\n" "\n" ]* let filter = incl "/etc/test" let xfm = transform lns filter puppet-5.5.10/spec/fixtures/unit/provider/host/0000755005276200011600000000000013417162176021410 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/host/parsed/0000755005276200011600000000000013417162176022666 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/host/parsed/valid_hosts0000644005276200011600000000101613417161722025122 0ustar jenkinsjenkins# Some leading comment, that should be ignored # The next line is empty so it should be ignored ::1 localhost # We now try another delimiter: Several tabs 127.0.0.1 localhost # No test trailing spaces 10.0.0.1 host1 # Ok its time to test aliases 2001:252:0:1::2008:8 ipv6host alias1 192.168.0.1 ipv4host alias2 alias3 # Testing inlinecomments now 192.168.0.2 host3 # This is host3 192.168.0.3 host4 alias10 # This is host4 192.168.0.4 host5 alias11 alias12 # This is host5 puppet-5.5.10/spec/fixtures/unit/provider/mount/0000755005276200011600000000000013417162176021575 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/mount/mount-output.aix.txt0000644005276200011600000000103013417161722025604 0ustar jenkinsjenkinsnode mounted mounted over vfs date options ---- ------- ------------ --- ------------ ------------------- /dev/hd0 / jfs Dec 17 08:04 rw, log =/dev/hd8 /dev/hd3 /tmp jfs Dec 17 08:04 rw, log =/dev/hd8 /dev/hd1 /home jfs Dec 17 08:06 rw, log =/dev/hd8 /dev/hd2 /usr jfs Dec 17 08:06 rw, log =/dev/hd8 sue /home/local/src /usr/code nfs Dec 17 08:06 ro, log =/dev/hd8 puppet-5.5.10/spec/fixtures/unit/provider/mount/parsed/0000755005276200011600000000000013417162176023053 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/mount/parsed/aix.filesystems0000644005276200011600000000520413417161722026122 0ustar jenkinsjenkins* IBM_PROLOG_BEGIN_TAG * This is an automatically generated prolog. * * bos61B src/bos/etc/filesystems/filesystems 1.23 * * Licensed Materials - Property of IBM * * COPYRIGHT International Business Machines Corp. 1985,1993 * All Rights Reserved * * US Government Users Restricted Rights - Use, duplication or * disclosure restricted by GSA ADP Schedule Contract with IBM Corp. * * @(#)filesystems @(#)29 1.23 src/bos/etc/filesystems/filesystems, cmdfs, bos61B, b2007_38A8 8/16/07 17:18:35 * IBM_PROLOG_END_TAG * * COMPONENT_NAME: CMDFS * * FUNCTIONS: none * * ORIGINS: 27 * * (C) COPYRIGHT International Business Machines Corp. 1985, 1993 * All Rights Reserved * Licensed Materials - Property of IBM * * US Government Users Restricted Rights - Use, duplication or * disclosure restricted by GSA ADP Schedule Contract with IBM Corp. * * * * This version of /etc/filesystems assumes that only the root file system * is created and ready. As new file systems are added, change the check, * mount, free, log, vol and vfs entries for the appropriate stanza. * /: dev = /dev/hd4 vfs = jfs2 check = false free = true log = NULL mount = automatic quota = no type = bootfs vol = root /home: dev = /dev/hd1 vfs = jfs2 check = true free = false log = NULL mount = true quota = no vol = /home /usr: dev = /dev/hd2 vfs = jfs2 check = false free = false log = NULL mount = automatic quota = no type = bootfs vol = /usr /var: dev = /dev/hd9var vfs = jfs2 check = false free = false log = NULL mount = automatic quota = no type = bootfs vol = /var /tmp: dev = /dev/hd3 vfs = jfs2 check = false free = false log = NULL mount = automatic quota = no vol = /tmp /admin: dev = /dev/hd11admin vfs = jfs2 check = false free = false log = NULL mount = true quota = no vol = /admin /proc: dev = /proc vol = "/proc" mount = true check = false free = false vfs = procfs /opt: dev = /dev/hd10opt vfs = jfs2 log = /dev/hd8 mount = true check = true vol = /opt free = false quota = no /var/adm/ras/livedump: dev = /dev/livedump vfs = jfs2 log = /dev/hd8 mount = true account = false quota = no /stage/middleware: dev = "/stage/middleware" vfs = nfs nodename = 192.168.1.11 mount = true type = nfs options = ro,bg,hard,intr,sec=sys account = false /home/u0010689: dev = "/userdata/20010689" vfs = nfs nodename = 192.168.1.11 mount = true type = nfs options = bg,hard,intr account = false /srv/aix: dev = /srv/aix nodename = mynode vfs = nfs account = false log = NULL mount = true options = vers=2 puppet-5.5.10/spec/fixtures/unit/provider/mount/parsed/aix.mount0000644005276200011600000000146213417161722024717 0ustar jenkinsjenkins node mounted mounted over vfs date options -------- --------------- --------------- ------ ------------ --------------- /dev/hd4 / jfs2 Feb 05 10:27 rw,log=NULL /dev/hd2 /usr jfs2 Feb 05 10:28 rw,log=NULL /dev/hd9var /var jfs2 Feb 05 10:28 rw,log=NULL /dev/hd3 /tmp jfs2 Feb 05 10:28 rw,log=NULL /dev/hd1 /home jfs2 Feb 05 10:28 rw,log=NULL /dev/hd11admin /admin jfs2 Feb 05 10:28 rw,log=NULL /proc /proc procfs Feb 05 10:28 rw /dev/hd10opt /opt jfs2 Feb 05 10:28 rw,log=NULL mynode /srv/aix /srv/aix nfs Mar 19 14:33 vers=2,rw puppet-5.5.10/spec/fixtures/unit/provider/mount/parsed/darwin.mount0000644005276200011600000000043413417161722025420 0ustar jenkinsjenkins/dev/disk0s2 on / (hfs, local, journaled) devfs on /dev (devfs, local, nobrowse) map -hosts on /net (autofs, nosuid, automounted, nobrowse) map auto_home on /home (autofs, automounted, nobrowse) /dev/disk0s3 on /usr (hfs, local, journaled) /dev/fake on /ghost (hfs, local, journaled) puppet-5.5.10/spec/fixtures/unit/provider/mount/parsed/freebsd.fstab0000644005276200011600000000112413417161722025500 0ustar jenkinsjenkins# Device Mountpoint FStype Options Dump Pass# /dev/ad0s1b none swap sw 0 0 /dev/ad0s1a / ufs rw 1 1 /dev/ad0s1e /tmp ufs rw 2 2 /dev/ad0s1f /usr ufs rw 2 2 /dev/ad0s1d /var ufs rw 2 2 /dev/ad0s1g /boot ufs rw 2 2 /dev/acd0 /cdrom cd9660 ro,noauto 0 0 puppet-5.5.10/spec/fixtures/unit/provider/mount/parsed/freebsd.mount0000644005276200011600000000020613417161722025543 0ustar jenkinsjenkins/dev/ad0s1a on / (ufs, local, soft-updates) /dev/ad0s1d on /ghost (ufs, local, soft-updates) devfs on /dev (devfs, local, multilabel) puppet-5.5.10/spec/fixtures/unit/provider/mount/parsed/hpux.mount0000644005276200011600000000314113417161722025116 0ustar jenkinsjenkins/ on rpool/ROOT/opensolaris read/write/setuid/devices/dev=2d90002 on Wed Dec 31 16:00:00 1969 /devices on /devices read/write/setuid/devices/dev=4a00000 on Thu Feb 17 14:34:02 2011 /dev on /dev read/write/setuid/devices/dev=4a40000 on Thu Feb 17 14:34:02 2011 /system/contract on ctfs read/write/setuid/devices/dev=4ac0001 on Thu Feb 17 14:34:02 2011 /proc on proc read/write/setuid/devices/dev=4b00000 on Thu Feb 17 14:34:02 2011 /etc/mnttab on mnttab read/write/setuid/devices/dev=4b40001 on Thu Feb 17 14:34:02 2011 /etc/svc/volatile on swap read/write/setuid/devices/xattr/dev=4b80001 on Thu Feb 17 14:34:02 2011 /system/object on objfs read/write/setuid/devices/dev=4bc0001 on Thu Feb 17 14:34:02 2011 /etc/dfs/sharetab on sharefs read/write/setuid/devices/dev=4c00001 on Thu Feb 17 14:34:02 2011 /lib/libc.so.1 on /usr/lib/libc/libc_hwcap1.so.1 read/write/setuid/devices/dev=2d90002 on Thu Feb 17 14:34:14 2011 /dev/fd on fd read/write/setuid/devices/dev=4d00001 on Thu Feb 17 14:34:18 2011 /tmp on swap read/write/setuid/devices/xattr/dev=4b80002 on Thu Feb 17 14:34:19 2011 /var/run on swap read/write/setuid/devices/xattr/dev=4b80003 on Thu Feb 17 14:34:19 2011 /export on rpool/export read/write/setuid/devices/nonbmand/exec/xattr/atime/dev=2d90006 on Thu Feb 17 14:37:48 2011 /export/home on rpool/export/home read/write/setuid/devices/nonbmand/exec/xattr/atime/dev=2d90007 on Thu Feb 17 14:37:48 2011 /rpool on rpool read/write/setuid/devices/nonbmand/exec/xattr/atime/dev=2d90009 on Thu Feb 17 14:37:48 2011 /ghost on /dev/fake read/write/setuid/devices/nonbmand/exec/xattr/atime/dev=2d90009 on Thu Feb 17 14:37:48 2011 puppet-5.5.10/spec/fixtures/unit/provider/mount/parsed/linux.fstab0000644005276200011600000000144213417161722025230 0ustar jenkinsjenkins# A sample fstab, typical for a Fedora system /dev/vg00/lv00 / ext3 defaults 1 1 LABEL=/boot /boot ext3 defaults 1 2 devpts /dev/pts devpts gid=5,mode=620 0 tmpfs /dev/shm tmpfs defaults 0 LABEL=/home /home ext3 defaults 1 2 /home /homes auto bind 0 2 proc /proc proc defaults 0 0 /dev/vg00/lv01 /spare ext3 defaults 1 2 sysfs /sys sysfs defaults 0 0 LABEL=SWAP-hda6 swap swap defaults 0 0 puppet-5.5.10/spec/fixtures/unit/provider/mount/parsed/linux.mount0000644005276200011600000000041113417161722025266 0ustar jenkinsjenkins/dev/root on / type jfs (rw,noatime) rc-svcdir on /lib64/rc/init.d type tmpfs (rw,nosuid,nodev,noexec,relatime,size=1024k,mode=755) sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime) /dev/sda9 on /usr/portage type jfs (rw) /dev/fake on /ghost type jfs (rw) puppet-5.5.10/spec/fixtures/unit/provider/mount/parsed/netbsd.fstab0000644005276200011600000000123613417161722025351 0ustar jenkinsjenkins# Device Mountpoint FStype Options Dump Pass# /dev/ad0s1b none swap sw 0 0 /dev/ad0s1a / ufs rw 1 1 /dev/ad0s1e /tmp ufs rw 2 2 /dev/ad0s1f /usr ufs rw 2 2 /dev/ad0s1d /var ufs rw 2 2 /dev/ad3s1b none swap sw 0 0 /dev/ad3s1e /data ufs rw 2 2 /dev/ad3s1g /boot ufs rw 2 2 puppet-5.5.10/spec/fixtures/unit/provider/mount/parsed/netbsd.mount0000644005276200011600000000052013417161722025407 0ustar jenkinsjenkins/dev/ad0s1a on / (ufs, local) devfs on /dev (devfs, local) /dev/ad0s1e on /tmp (ufs, local, soft-updates) /dev/ad0s1f on /usr (ufs, local, soft-updates) /dev/ad0s1d on /var (ufs, local, soft-updates) /dev/ad3s1e on /data (ufs, local, soft-updates) /dev/ad3s1h on /ghost (ufs, local, soft-updates) devfs on /var/named/dev (devfs, local) puppet-5.5.10/spec/fixtures/unit/provider/mount/parsed/openbsd.fstab0000644005276200011600000000020013417161722025512 0ustar jenkinsjenkins/dev/wd0a / ffs rw 1 1 /dev/wd0e /home ffs rw,nodev,nosuid 1 2 /dev/wd0d /usr ffs rw,nodev 1 2 /dev/wd0f /boot ffs rw,nodev 1 2 puppet-5.5.10/spec/fixtures/unit/provider/mount/parsed/openbsd.mount0000644005276200011600000000025113417161722025563 0ustar jenkinsjenkins/dev/wd0a on / type ffs (local) /dev/wd0e on /home type ffs (local, nodev, nosuid) /dev/wd0d on /usr type ffs (local, nodev) /dev/wd0g on /ghost type ffs (local, nodev) puppet-5.5.10/spec/fixtures/unit/provider/mount/parsed/solaris.fstab0000644005276200011600000000120013417161722025535 0ustar jenkinsjenkins#device device mount FS fsck mount mount #to mount to fsck point type pass at boot options # fd - /dev/fd fd - no - /proc - /proc proc - no - /dev/dsk/c0d0s0 /dev/rdsk/c0d0s0 / ufs 1 no - /dev/dsk/c0d0p0:boot - /boot pcfs - no - /devices - /devices devfs - no - ctfs - /system/contract ctfs - no - objfs - /system/object objfs - no - #swap - /tmp tmpfs - yes - puppet-5.5.10/spec/fixtures/unit/provider/mount/parsed/solaris.mount0000644005276200011600000000106313417161722025607 0ustar jenkinsjenkins/ on /dev/dsk/c0t0d0s0 read/write/setuid/intr/largefiles/xattr/onerror=panic/dev=2200000 on Mon Mar 18 08:48:45 2002 /proc on /proc read/write/setuid/dev=4300000 on Mon Mar 18 08:48:44 2002 /etc/mnttab on mnttab read/write/setuid/dev=43c0000 on Mon Mar 18 08:48:44 2002 /tmp on swap read/write/setuid/xattr/dev=2 on Mon Mar 18 08:48:52 2002 /export/home on /dev/dsk/c0t0d0s7 read/write/setuid/intr/largefiles/xattr/onerror=panic/dev=2200007 on Mon Mar 18 /ghost on /dev/dsk/c0t1d0s7 read/write/setuid/intr/largefiles/xattr/onerror=panic/dev=2200007 on Mon Mar 18 puppet-5.5.10/spec/fixtures/unit/provider/naginator/0000755005276200011600000000000013417162176022415 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/naginator/define_empty_param0000644005276200011600000000013513417161722026163 0ustar jenkinsjenkins# fixture for the spec test for PUP-1041 define host { parents use linux-server } puppet-5.5.10/spec/fixtures/unit/provider/ssh_authorized_key/0000755005276200011600000000000013417162176024336 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/ssh_authorized_key/parsed/0000755005276200011600000000000013417162176025614 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/ssh_authorized_key/parsed/authorized_keys0000644005276200011600000000764513417161722030760 0ustar jenkinsjenkinsssh-dss AAAAB3NzaC1kc3MAAACBAJkupmdsJSDXfUy5EU5NTRBDr9Woo3w0YnB8KmnJW9ghU8C7SkWPB1fIHVe+esFfd3qWBseb83PoFX63geZJAg6bjV4/Rdn1zEoa9EO2QyUdYUen4+rpsh3vVKZ6HFNsn3+W5+kPYgE1F/N4INqkbjY3sqCkP/W1BL9+sbVVbuJFAAAAFQCfjWDk5XhvGUkPjNWWVqltBYzHtwAAAIEAg/XL7ky7x9Ad5banzPFAfmM+DGFe0A/JEbLDjKmr5KBM5x4RFohtEvZ8ECuVGUOqBWdgAjyYwsG4oRVjLnKrf/rgmbNRzSFgEWkcAye3BVwk7Dt6hh4knEl+mNfOLq+FH0011UhecOiqTcESMzQDtgQ1vJh2VchElBLjl3x/ZugAAACAAh5jGQC338t5ObP8trSlOefkx0sXmmEzUbo3Mt8mGUuGJPx8m+X0L8Xd+l5rQxytqE3SmV/RD+6REqBuPqHM8RQuqAzfjdOeg/Ajdggx1CRMTVhltZsgQoxO30cz9Qo0SdPoL+Jp1fLuaLZq7m/RmsWYvoLT3jebBlpvvQE8YlI= francois.deppierraz@nimag.net ssh-dss AAAAB3NzaC1kc3MAAACBAJkupmdsJSDXfUy5EU5NTRBDr9Woo3w0YnB8KmnJW9ghU8C7SkWPB1fIHVe+esFfd3qWBseb83PoFX63geZJAg6bjV4/Rdn1zEoa9EO2QyUdYUen4+rpsh3vVKZ6HFNsn3+W5+kPYgE1F/N4INqkbjY3sqCkP/W1BL9+sbVVbuJFAAAAFQCfjWDk5XhvGUkPjNWWVqltBYzHtwAAAIEAg/XL7ky7x9Ad5banzPFAfmM+DGFe0A/JEbLDjKmr5KBM5x4RFohtEvZ8ECuVGUOqBWdgAjyYwsG4oRVjLnKrf/rgmbNRzSFgEWkcAye3BVwk7Dt6hh4knEl+mNfOLq+FH0011UhecOiqTcESMzQDtgQ1vJh2VchElBLjl3x/ZugAAACAAh5jGQC338t5ObP8trSlOefkx0sXmmEzUbo3Mt8mGUuGJPx8m+X0L8Xd+l5rQxytqE3SmV/RD+6REqBuPqHM8RQuqAzfjdOeg/Ajdggx1CRMTVhltZsgQoxO30cz9Qo0SdPoL+Jp1fLuaLZq7m/RmsWYvoLT3jebBlpvvQE8YlI= Francois Deppierraz from="192.168.1.1",command="/bin/false",no-pty,no-port-forwarding ssh-dss AAAAB3NzaC1kc3MAAACBAJkupmdsJSDXfUy5EU5NTRBDr9Woo3w0YnB8KmnJW9ghU8C7SkWPB1fIHVe+esFfd3qWBseb83PoFX63geZJAg6bjV4/Rdn1zEoa9EO2QyUdYUen4+rpsh3vVKZ6HFNsn3+W5+kPYgE1F/N4INqkbjY3sqCkP/W1BL9+sbVVbuJFAAAAFQCfjWDk5XhvGUkPjNWWVqltBYzHtwAAAIEAg/XL7ky7x9Ad5banzPFAfmM+DGFe0A/JEbLDjKmr5KBM5x4RFohtEvZ8ECuVGUOqBWdgAjyYwsG4oRVjLnKrf/rgmbNRzSFgEWkcAye3BVwk7Dt6hh4knEl+mNfOLq+FH0011UhecOiqTcESMzQDtgQ1vJh2VchElBLjl3x/ZugAAACAAh5jGQC338t5ObP8trSlOefkx0sXmmEzUbo3Mt8mGUuGJPx8m+X0L8Xd+l5rQxytqE3SmV/RD+6REqBuPqHM8RQuqAzfjdOeg/Ajdggx1CRMTVhltZsgQoxO30cz9Qo0SdPoL+Jp1fLuaLZq7m/RmsWYvoLT3jebBlpvvQE8YlI= Francois Deppierraz from="192.168.1.1, www.reductivelabs.com",command="/bin/false",no-pty,no-port-forwarding ssh-dss AAAAB3NzaC1kc3MAAACBAJkupmdsJSDXfUy5EU5NTRBDr9Woo3w0YnB8KmnJW9ghU8C7SkWPB1fIHVe+esFfd3qWBseb83PoFX63geZJAg6bjV4/Rdn1zEoa9EO2QyUdYUen4+rpsh3vVKZ6HFNsn3+W5+kPYgE1F/N4INqkbjY3sqCkP/W1BL9+sbVVbuJFAAAAFQCfjWDk5XhvGUkPjNWWVqltBYzHtwAAAIEAg/XL7ky7x9Ad5banzPFAfmM+DGFe0A/JEbLDjKmr5KBM5x4RFohtEvZ8ECuVGUOqBWdgAjyYwsG4oRVjLnKrf/rgmbNRzSFgEWkcAye3BVwk7Dt6hh4knEl+mNfOLq+FH0011UhecOiqTcESMzQDtgQ1vJh2VchElBLjl3x/ZugAAACAAh5jGQC338t5ObP8trSlOefkx0sXmmEzUbo3Mt8mGUuGJPx8m+X0L8Xd+l5rQxytqE3SmV/RD+6REqBuPqHM8RQuqAzfjdOeg/Ajdggx1CRMTVhltZsgQoxO30cz9Qo0SdPoL+Jp1fLuaLZq7m/RmsWYvoLT3jebBlpvvQE8YlI= Francois Deppierraz ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA2Vi+TdC3iOGYcIo5vGTvC9P9rjHl9RxCuZmSfn+YDFQ35RXf0waijtjp9I7GYh6R4hBjA5z0u/Pzi95LET5NfRM0Gdc0DJyvBI7K+ALBxIT383Iz6Yz4iKxe1TEJgHGM2he4+7BHkjc3kdIZqIpZjucCk4VsXSxujO4MKKvtaKK2l+kahlLQHHw/vZkDpIgL52iGVsjW9l8RLJaKHZ4mDHJN/Q/Rzn2W4EvcdHUzwhvGMwZlm8clDwITBrSsawYtnivJrQSYcmTRqJuS8wprNDrLIhTGjrwFg5WpruUuMt6fLuCqwe6TeEL+nh3DQ4g554c5aRp3oU6LGBKTvNZGWQ== francois@korn ssh-dss AAAAB3NzaC1kc3MAAACBAMPpCYnjywOemd8LqbbmC+bePNR3/H1rXsiFwjSLhYE3bbOpvclvOzN1DruFc34m0FopVnMkP+aubjdIYF8pijl+5hg9ggB7Kno2dl0Dd1rGN/swvmhA8OpLAQv7Qt7UnXKVho3as08zYZsrHxYFu0wlnkdbsv4cy4aXyQKd4MPVAAAAFQDSyQFWg8Qt3wU05buhZ10psoR7tQAAAIEAmAhguXwUnI3P2FF5NAW/mpJUmUERdL4pyZARUyAgpf7ezwrh9TJqrvGTQNBF97Xqaivyncm5JWQdMIsTBxEFaXZGkmBta02KnWcn447qvIh7iv8XpNL6M9flCkBEZOJ4t9El0ytTSHHaiCz8A20Et+E8evWyi1kXkFDt8ML2dGgAAACBAK0X4ympbdEjgV/ZyOc+BU22u7vOnfSOUJmyar4Ax1MIDNnoyNWKnGvxRutydQcQOKQHZEU0fE8MhPFn6nLF6CoVfEl/oz0EYz3hjV4WPFpHrF5DY/rhvNj8iuneKJ5P0dy/rG6m5qey25PnHyGFVoIRlkHJvBCJT40dHs40YEjI francois@korn ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAut8aOSxenjOqF527dlsdHWV4MNoAsX14l9M297+SQXaQ5Z3BedIxZaoQthkDALlV/25A1COELrg9J2MqJNQc8Xe9XQOIkBQWWinUlD/BXwoOTWEy8C8zSZPHZ3getMMNhGTBO+q/O+qiJx3y5cA4MTbw2zSxukfWC87qWwcZ64UUlegIM056vPsdZWFclS9hsROVEa57YUMrehQ1EGxT4Z5j6zIopufGFiAPjZigq/vqgcAqhAKP6yu4/gwO6S9tatBeEjZ8fafvj1pmvvIplZeMr96gHE7xS3pEEQqnB3nd4RY7AF6j9kFixnsytAUO7STPh/M3pLiVQBN89TvWPQ== puppet-5.5.10/spec/fixtures/unit/provider/ssh_authorized_key/parsed/authorized_keys10000644005276200011600000000321613417161722031027 0ustar jenkinsjenkins1024 35 167576894966720957580065952882142495543043407324667405194097438434880798807651864847570827148390962746149410384891026772700627764845955493549511618975091512997118590589399665757714186775228807376424903966072778732134483862302766419277581465932186641863495699668069439475320990051723279127881281145164415361627 francois@korn 2048 35 27332429396020032283276339339051507284036000350350092862949624519871013308460312287866673933080560923221560798334008554200019645416448528663000202951887890525621015333936122655294782671001073795264378070156670395703161543893088138531854776737799752600933431638059304355933305878665812555436198516842364330938321746086651639330436648850787370397302524667456837036413634152938122227368132322078811602953517461933179827432019932348409533535942749570969101453655028606209719023224268890314608444789012688070463327764203306501923404494017305972543000091038543051645924928018568725584728655193415567703220002707737714942757 francois@korn from="192.168.1.1",command="/bin/false",no-pty,no-port-forwarding 2048 35 27332429396020032283276339339051507284036000350350092862949624519871013308460312287866673933080560923221560798334008554200019645416448528663000202951887890525621015333936122655294782671001073795264378070156670395703161543893088138531854776737799752600933431638059304355933305878665812555436198516842364330938321746086651639330436648850787370397302524667456837036413634152938122227368132322078811602953517461933179827432019932348409533535942749570969101453655028606209719023224268890314608444789012688070463327764203306501923404494017305972543000091038543051645924928018568725584728655193415567703220002707737714942757 francois@korn puppet-5.5.10/spec/fixtures/unit/provider/ssh_authorized_key/parsed/authorized_keys20000644005276200011600000000114713417161722031031 0ustar jenkinsjenkinsfalse ssh-dss AAAAB3NzaC1kc3MAAACBAJkupmdsJSDXfUy5EU5NTRBDr9Woo3w0YnB8KmnJW9ghU8C7SkWPB1fIHVe+esFfd3qWBseb83PoFX63geZJAg6bjV4/Rdn1zEoa9EO2QyUdYUen4+rpsh3vVKZ6HFNsn3+W5+kPYgE1F/N4INqkbjY3sqCkP/W1BL9+sbVVbuJFAAAAFQCfjWDk5XhvGUkPjNWWVqltBYzHtwAAAIEAg/XL7ky7x9Ad5banzPFAfmM+DGFe0A/JEbLDjKmr5KBM5x4RFohtEvZ8ECuVGUOqBWdgAjyYwsG4oRVjLnKrf/rgmbNRzSFgEWkcAye3BVwk7Dt6hh4knEl+mNfOLq+FH0011UhecOiqTcESMzQDtgQ1vJh2VchElBLjl3x/ZugAAACAAh5jGQC338t5ObP8trSlOefkx0sXmmEzUbo3Mt8mGUuGJPx8m+X0L8Xd+l5rQxytqE3SmV/RD+6REqBuPqHM8RQuqAzfjdOeg/Ajdggx1CRMTVhltZsgQoxO30cz9Qo0SdPoL+Jp1fLuaLZq7m/RmsWYvoLT3jebBlpvvQE8YlI= Francois Deppierraz puppet-5.5.10/spec/fixtures/unit/provider/sshkey/0000755005276200011600000000000013417162176021741 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/sshkey/parsed/0000755005276200011600000000000013417162176023217 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/sshkey/parsed/sample0000644005276200011600000001325213417161722024422 0ustar jenkinsjenkinshosting2.planetargon.com,64.34.164.77 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAy+f2t52cDMrYkgEKQ6juqfMf/a0nDFry3JAzl+SAWQ0gTklVxNcVbfHx2pkZk66EBGQfrK33Bx1BflZ/iEDyiCwmzVtNba0X9A6ELYjB9WSkWdIqZCfPlKZMu9N//aZ6+3SDVuz/BVFsAVmtqQ4Let2QjOFiSIKXrtPqWvVT/MM= kirby.madstop.com,192.168.0.5 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAw9iHuAa/wepHoUzWqsvhQvSkpE4K7agrdLOWHM9mvyRQ2X3HVq5GqzAvWu4J+f0FvcLPwA9tivpxt1oSt5MOtvDM6HoM+8m3P4daBp0nlNaYR8/vHCAmX6N3RyM8FWfp+VqWyux1SooQwxYxVFy86G78ApTqNsZ+p7cHmnBYqk0= fedora1,192.168.0.51 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAyz1rKcApU4//j8CHYKexq4qnq2WVqLPrZYGnlij1t7FscLiDVKvBuRHVkfyTNIjAM/t7tM1Dj+FuD4iWziCmf7RO9q4wI5y/1zgCiSUegnZVSmH2yxnWGMdHGpXOkN3NXcpy6jylxyBo0M7T22PSezCxyUVfMclesjOEO1jETd0= kirby ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAw9iHuAa/wepHoUzWqsvhQvSkpE4K7agrdLOWHM9mvyRQ2X3HVq5GqzAvWu4J+f0FvcLPwA9tivpxt1oSt5MOtvDM6HoM+8m3P4daBp0nlNaYR8/vHCAmX6N3RyM8FWfp+VqWyux1SooQwxYxVFy86G78ApTqNsZ+p7cHmnBYqk0= sol10b,192.168.0.50 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAs37kiDwKxWqi6EfSdKwRaZXBwh2doOARRqZzyitBaPwESy26DwTx+xdQ2rwB4V2k1WIec+1f3bgTS2ArH75dQSPyba2HKqxaSRBd3Zh4z23+uUxpupEyoRdW1HolMOvuoceheSMsruiuYcuiyct41d4c/Qmr51Dv04Doi00k6Ws= piratehaven.org,64.81.59.88 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA7TYRRkle0wDNohZ0TNZN6R1Zp0svxgX+GJ9umI5yNM1bMxUTgeNRh5nIvZg1HgD1WRXQ57dSxxLzbvRyAqc245g6S8eWWCtenvOFLl0rOF5D3VxbQuw79sOe8/Ac8TC+c8RuWB7aaxpwL5Rv9xfDeazOtoKXj7+uwQW1PUmTaEM= atalanta,192.168.0.10 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAysniuWiJH6OQLXl63XXcS1b/hP2lAgSz0IutjQ6ZUfBrt1BZ8udEgSh57w5QDLsZ1lNvND61u5cy6iDKXI5TIQY4DvUmsoFZhyr4iYJbtT/h6UJSyaZtEnA7ZMRjGhUIMOn+mjbj7Z3zmJMhxtImK3Xo3O2fJ1hnK4jsBwQPSLc= pixie,192.168.0.9 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAzDp588CvET6w11LB2s/vPjc4tX9+u46iYJgNFfhzxrXYMVv4GF7d30IXB5+Hwyi2FOQIG1+h0kUXVGWEv64rAFBT7pD2KMFX0lcDERV4avqT0TRDIIA5OqFOhq9Ff+kOmHS2cB7eFyR5vqbN4ujOnJGTmru9dcqyL+2AcFekvh0= culain,192.168.0.3 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAvJ/IORhQMdAsJ7LB1sI2LlWUHc7HPTCDiEgJ96ij3jFvqaIiYieRFaNkxbbk75mPkj3vIqWIgAsAtHmKX4wDikNG/gyjs4WM4cWFKtl2jiVhqpoxqqCaVxs6Ex+vpKuKhQR6SzFBFDlBZYP9an6DPu1msTLT8/hZH2WwswVmpyU= cs312-server.een.orst.edu,128.193.40.66 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA+t3hsOiyp1mztt013bLdJqIAFCo8lxlz86MYEPz/mADHzWLs3Xv7xpAUv/E8pRbhEOzXo84EddORRBXz6DgVyMah8+v/u3IOkpXuZI0Iu1n5hZyf2l5DGEyGecr3oqqjUdMuM9HeXFLnqXJI3hDE7civBtqf5AJSol+TCcipeE8= freebsd1,192.168.0.52 ssh-dss AAAAB3NzaC1kc3MAAACBAJSiOyQhYlKAi0FDLKy42VzLDq6yJWXGXVCLSfgWyVx7QCq/3+W3C1dtHuAvjbypcjqqvsuGGITgQ1Y6B/+76n5d7FyQnj4SFZ5drOBn/TvslXhrS/Ok5KCcndfNAa+EyMnSZJ21jhoRjZftY4lmb4hy6fEF3RvjuOdf1qBN5FWpAAAAFQDcsWF0zELAW6YUlSjAyO0T0lfPbwAAAIAlOLdOd/WszzVaplCOnH5vF6LWfr6BosZKDkFi0mv6Ec636YGaj4AMxK8sRPusHv6sVByN17ntIJnLo2XD1SuoH28bZ0ZnPIdVnd0l1KqsOCuuow9LZYJUihezoUuYuTvij1jZdrwltuPNJTVLYtsZDnKE5plw+Tjzeb7ImjbXGwAAAIBT40olg3fxhRDiZECle0WK7GitgXCB3njs+4dba8VwveEJb9UuulMc1eR+zQiJR96IUBagC9NiLvUGS1IfiIHpT4FA8O/MK1W9SgxXB9d39Nk/9l8dH3U/fLnbC/hYVo8bN0or/mKxcxQMkdBwpPlWAbELRftod2BkkkvgfQdc+g== 192.168.0.2 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgwCZ+qxpMMRJ3otGsjaYeKTKf6tuCZyK1cD+ns9Eu7V0ZJLJ/LLMxduu7n4H/ufGI5rGV5axzgx8yZhjDRzsrGjLAQYsqlomMkf901YQI6UuieSA4MZa5MDkq/Jt6Vx1kEGTpkgrfw9kRMX5BngECt1QKY4xTgC7Ex+WlFvZwk+tRUT3 openbsd1,192.168.0.54 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAvgRVrtJk0fBg9YsLf7rWR1X32ZjFcva5XBvenDlHruObaHzuGXyyr6iOCAEOc7eCZjlPBYrGZ2potqyk8HlBOHXr1cCBf49t4yAt8KgKswtzWlgdbU1UEwllSRVOpzqULAT0smv/jfaIZdvRoN1rLriqtpn4bQL//ZOHiyXCwdlJ+di8Mza2L8KZ5T75hwIFBhrgL12xfymRp3v+Iy21MjjzsF3pROIkZ7icitNqQF55U9vsHaA37vG8FepVkO10bYYP7IHPZaBPBMPx7qPyRgRDJahEUGBnkIkzwJHEmA+7YMiNTZu6MigezD+CIqY1xOi/eXZwObLwLKXo7eRmpw== 192.168.0.60 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAufcvE7s1eRwhUwMBfZ6uFNxkdSdzjSDEn3vjByOjG/eraNhnYAW3rxV7WIf2pEa6JSOMrE1mqsEL75xEtpXlzC949Ysz4+1OSHY52KonoFm/a+FbmbFp81TVuVPYaLoeWN27STiJh+puC5spkIZe0laqT1GU13M4gj6B+j3NLhU= rh3a,192.168.0.55 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAufcvE7s1eRwhUwMBfZ6uFNxkdSdzjSDEn3vjByOjG/eraNhnYAW3rxV7WIf2pEa6JSOMrE1mqsEL75xEtpXlzC949Ysz4+1OSHY52KonoFm/a+FbmbFp81TVuVPYaLoeWN27STiJh+puC5spkIZe0laqT1GU13M4gj6B+j3NLhU= tsetse,192.168.0.7 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAwCDGn82BEMSCfcE2LZKcwdAoyIBC+u2twVwWNRm3KzyrJMQ+RbTQo4AwGOdyr/QYh6KaxTKTSoDtiBLr132uenMQKwF47gCKMA1T47uiy+TBUehgOGwOxteSYP/pQRpKXFmhOppSPyDPQVq234XvANeJp0iT8ZKEhF2FsWTs6sM= config.sage.org,131.106.3.205 ssh-dss AAAAB3NzaC1kc3MAAACBAL2akEcIfQsfm3zCd2hD6PgH3kWA/tqX/qbrLhL/ipX7iqK/y282GMClZQSQjc1YNi9virvLzb6u/gdZxicZ8K5O3FaJrULQJOZaP62SOHk5CUSHVnvpMCaCnbwB6gEHa2LeMWStcEfWW+g1CC2hzPJw16/S5GISGXbyanO02MnXAAAAFQDomwx/OCjTmmQljMTU5rgNn2E4gwAAAIBmtMSfcs2Tq5iFFKK5cahluv047vVNfXqYIAkeJniceL0Et16MKfuzadpq0H9ocxQYW/5Ir9nUULrdxBUN9LxNKq15/uWkJC9QCSh8PysgvFnjVZeCODua/dn6eZTZnY9DZ3S6v1pT8CP6uWr5fmZJ8FKJGrC3gYX4y1V1ZTCVewAAAIB6e7RCST6vkTS5rgn5wGbrfLK5ad+PW+2i66k77Zv1pjtfRz+0oejBjwJDPNVSc2YNEl7X0nEEMNjo/a5x8Ls+nVqhzJA+NXIwS1e/moKbXFGewW5HAtxd79gtInC8dEIO7hmnWnqF1dBkRHXg1YffYkHrMVJBxpzagw7nYa0BBQ== rh3b,192.168.0.56 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAufcvE7s1eRwhUwMBfZ6uFNxkdSdzjSDEn3vjByOjG/eraNhnYAW3rxV7WIf2pEa6JSOMrE1mqsEL75xEtpXlzC949Ysz4+1OSHY52KonoFm/a+FbmbFp81TVuVPYaLoeWN27STiJh+puC5spkIZe0laqT1GU13M4gj6B+j3NLhU= centos1,192.168.0.57 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA0DXqYF+3Lf68GkWBAjjKBb6UITNnzm4wiDi/AGjv5+DoVXqDcqHvZ8rZFAMgUe1dVob4pWT2ZWLHW0gicoJCdr4UQbPXlWz1F62z8fo2PRRPlG6KN1wmF7pnyml8jr0wBX8lQZJsMqi4InGozf7wFHLH/7DNGRK3MD6tSp3Z4is= doorstop.cafes.net,205.241.238.186 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEApJKeB9/bN5t55zLETHs0MVo/vEkfQ3EzY7178GKLI/yiOFmcn+NvUvUtCQK/xKpod813LBHCODxZPG1Kb0SjlaC/EkFEenb74LNu0o1qXa1GWh3wfaIm0JRNjXqPqxAWTlMs43O2HXwOwmLVhl7SSP3xtTw6h9gREbVKmfBaRdsRfzD0etfz1NCnmGh/1Sh9+j4eeS+IBtwoR5JVhZVhuofHCqs5HZ8gLDgfn8HXP7pMbLkx54cf1R/tmFmn9JGLdTPtEGcSIiu7414XSbfChSC83rGZCDPKHq7ZodiE8GpbWLBnyPXi2AYxTPM7aZMqitIHv3MWf5suV0q0WLGdnQ== host.domain.com,host1.domain.com,192.168.0.1 dss thisismykey1 puppet-5.5.10/spec/fixtures/unit/provider/sshkey/parsed/sample_with_blank_lines0000644005276200011600000000153013417161722030012 0ustar jenkinsjenkins# A comment # ... and another after a blank line. The following line includes three whitespace characters. hosting2.planetargon.com,64.34.164.77 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAy+f2t52cDMrYkgEKQ6juqfMf/a0nDFry3JAzl+SAWQ0gTklVxNcVbfHx2pkZk66EBGQfrK33Bx1BflZ/ iEDyiCwmzVtNba0X9A6ELYjB9WSkWdIqZCfPlKZMu9N//aZ6+3SDVuz/BVFsAVmtqQ4Let2QjOFiSIKXrtPqWvVT/MM= will.isawesome.com,192.168.0.5 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAw9iHuAa/wepHoUzWqsvhQvSkpE4K7agrdLOWHM9mvyRQ2X3HVq5GqzAvWu4J+f0FvcLPwA9tivpxt1oSt5MOtvDM6HoM+8m3P4daBp0nlNaYR8/vHCAmX6N3RyM8FWfp+VqWyux1SooQwxYxVFy86G78ApTqNsZ+p7cHmnBYqk0= will.isgreat.com,192.168.0.6 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAw9iHuAa/wepHoUzWqsvhQvSkpE4K7agrdLOWHM9mvyRQ2X3HVq5GqzAvWu4J+f0FvcLPwA9tivpxt1oSt5MOtvDM6HoM+8m3P4daBp0nlNaYR8/vHCAmX6N3RyM8FWfp+VqWyux1SooQwxYxVFy86G78ApTqNsZ+p7cHmnBasd= puppet-5.5.10/spec/fixtures/unit/provider/zfs/0000755005276200011600000000000013417162176021235 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/zfs/zfs/0000755005276200011600000000000013417162176022037 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/zfs/zfs/zfs-list.out0000644005276200011600000000015613417161722024341 0ustar jenkinsjenkinsrpool 7.75G 11.8G 32.5K /rpool rpool/ROOT 6.03G 11.8G 21K legacy puppet-5.5.10/spec/fixtures/unit/provider/zpool/0000755005276200011600000000000013417162176021576 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/zpool/zpool/0000755005276200011600000000000013417162176022741 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/provider/zpool/zpool/zpool-list.out0000644005276200011600000000013313417161722025577 0ustar jenkinsjenkinsrpool 19.9G 7.19G 12.7G 36% ONLINE - mypool 19.9G 7.19G 12.7G 36% ONLINE - puppet-5.5.10/spec/fixtures/unit/reports/0000755005276200011600000000000013417162176020277 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/reports/tagmail/0000755005276200011600000000000013417162176021715 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/reports/tagmail/tagmail_email.conf0000644005276200011600000000003113417161721025336 0ustar jenkinsjenkinssecure: user@domain.com puppet-5.5.10/spec/fixtures/unit/reports/tagmail/tagmail_failers.conf0000644005276200011600000000006613417161721025704 0ustar jenkinsjenkinstag: : abuse@domain.com invalid!tag: abuse@domain.com puppet-5.5.10/spec/fixtures/unit/reports/tagmail/tagmail_passers.conf0000644005276200011600000000111113417161721025727 0ustar jenkinsjenkins# A comment # or maybe two # plus some blank lines # with some blanks plus a comment # a simple tag report one: abuse@domain.com # with weird spacing one : abuse@domain.com # with multiple tags one, two: abuse@domain.com # again with the weird syntax one , two : abuse@domain.com # Some negations one, !two: abuse@domain.com # some oddly-formatted tags one, two-three, !four-five, !six: abuse@domain.com # multiple addresses one, two: abuse@domain.com, testing@domain.com # and with weird spacing one, two: abuse@domain.com , testing@domain.com # and a trailing comment puppet-5.5.10/spec/fixtures/unit/ssl/0000755005276200011600000000000013417162176017402 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/ssl/certificate/0000755005276200011600000000000013417162176021664 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/ssl/certificate/old-style-cert-exts.pem0000644005276200011600000000402513417161721026213 0ustar jenkinsjenkins-----BEGIN CERTIFICATE----- MIIFyjCCA7KgAwIBAgIBAzANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDDBRQdXBw ZXQgQ0E6IGxvY2FsaG9zdDAeFw0xNDExMTMwMDQ5MzJaFw0xOTExMTMwMDQ5MzJa MBkxFzAVBgNVBAMMDmZpcmVob3NlLWFnZW50MIICIjANBgkqhkiG9w0BAQEFAAOC Ag8AMIICCgKCAgEA0/mNeudQrpfTFn/+U8/QlFQ5+IAfKVaRuT/9jGJKrTz+oQJ6 wQJvAEJUts2RL8JcxGJNQROGteyo7jgIvmj4Lk+zbjOL9nWZqzV4IcM/Q0vUsZtv 5ejeAxyzgIC7O3t8KWGiEopozIUU2ipN4vEkdhYu4zKc/vEHEzluMbIwmWjCs+58 /f94ZrrCDDeVL+HXxJWeCyrVLa9dkPlLmEl24GHsb38cUdwCWR6Jp2NjdBRcj8DY dZs6Bl+eFyOXhYgz0rzHvHbtERaluP/lQU6f7i4jRRV6N7j2gZio2CjFNg7Lg/Od 7TzAQ3fejWYDkCE9Z34tMEw3k+wFbUNrLNRq1ReKX6qVPQOCxq3Y3fDrwAFaHzFb W+kTMjh9Znh19EfI8pZjBqEM7Nt87oSNULQPrY3KxY/iA5oyrxW5GTSy1qLNKSt0 SGIlUQmTSAoLYud04sbAuYk+7w9PayT2+JARsJD/LGhCGpCa64Uq89QUNKJZHy6F 2fgvXyqrdfZsnSIAV/EAudLZdZNZ0t6zsTzMSSV7wTHNNVNLUu23IU72ptjbvEMh kurKXzK70RtIQZJU0U7EQ4nzihTh7V8tpkU41/ZfX6Aipv2pl3Ii9b68nywXBbbN zp55hx9+Of0i2moi1UMhzEjARJZ2GKb3IGkkiP9gOpjycB5SJfVPFBM9LQUCAwEA AaOCARUwggERMDUGCWCGSAGG+EIBDQQoUHVwcGV0IFJ1YnkvT3BlblNTTCBJbnRl cm5hbCBDZXJ0aWZpY2F0ZTAaBgsrBgEEAYKMTAEBAQQLSS1BTS1BLVVVSUQwGQYL KwYBBAGCjEwBAQIECmlfYW1fYW5faWQwIQYLKwYBBAGCjEwBAQMEEmlfYW1fYW5f aW1hZ2VfbmFtZTAOBgNVHQ8BAf8EBAMCBaAwIAYDVR0lAQH/BBYwFAYIKwYBBQUH AwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFG/V7waEORzLG/9J wQsOFF8+LDvBMB8GA1UdIwQYMBaAFCCZleE/LbK3Z5X34EVnNHL6giIeMA0GCSqG SIb3DQEBCwUAA4ICAQC9Nt5qdOh2kwX8gchqDLy4dyAey+aRdqAmzyOLCMYzEOl9 XNTVqF+1MHfrxsVuqrC19fONg67dJEberZhyx3XYV8gA2WemraP8Pq2oWxm9AIp+ MOT0EEbSbSu5GXF/VOXK4fqRA/A3YeTqocGpnyC2H8VRvPObWEg9RVXCSfDsjBzz ulmrvW6N46u2eLAPNqWf0389Pp8XA/HPQeId4HTUUA46aMwIyYpIj45+Nq21zFD3 25z2Nmd6t0bRM54MTVl9FWH2ZJck2pGKXh+1dqQtnOT1Oo6iRK9zqjCcnRiCyIb7 r4kU9gEucN34MMm0CQFhNVhlI+xKA+wNTRsA+MSQkyp0vn1fAKSJv3tU7+/15Ix7 kSQ3gLFlxEQOokBRZRW3SzxyvBl9iL3893kL0goYIShjg/N/T5oDFAVS3KCS3ppu nVTij2WJA4Gq7ORvmRwKFkXCyHFZh5Ws5z3OGCdm3PXcFtm0RZLptta62ZGRp6rV b5/BvcJryvj0ZnTrqmADhqTK1gpuwjl2O8kYwPfdL0zKIlEINT895uDxs5R51cLG /wy3JBHO9KOuu+P1pyJ+Dag3uMu0VgMOf3d81Ew4AXRvHehsNVJcu/7t+UMem6Xl w1E+VGqvTpEwziQvi9AifhFJXwAI24GsSVYgw09miyJeswSqMMvfTsEPRpb8uw== -----END CERTIFICATE----- puppet-5.5.10/spec/fixtures/unit/ssl/certificate_request/0000755005276200011600000000000013417162176023434 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/ssl/certificate_request/old-style-cert-request.pem0000644005276200011600000000331013417161721030464 0ustar jenkinsjenkins-----BEGIN CERTIFICATE REQUEST----- MIIEyTCCArECAQAwGTEXMBUGA1UEAwwOZmlyZWhvc2UtYWdlbnQwggIiMA0GCSqG SIb3DQEBAQUAA4ICDwAwggIKAoICAQDT+Y1651Cul9MWf/5Tz9CUVDn4gB8pVpG5 P/2MYkqtPP6hAnrBAm8AQlS2zZEvwlzEYk1BE4a17KjuOAi+aPguT7NuM4v2dZmr NXghwz9DS9Sxm2/l6N4DHLOAgLs7e3wpYaISimjMhRTaKk3i8SR2Fi7jMpz+8QcT OW4xsjCZaMKz7nz9/3hmusIMN5Uv4dfElZ4LKtUtr12Q+UuYSXbgYexvfxxR3AJZ HomnY2N0FFyPwNh1mzoGX54XI5eFiDPSvMe8du0RFqW4/+VBTp/uLiNFFXo3uPaB mKjYKMU2DsuD853tPMBDd96NZgOQIT1nfi0wTDeT7AVtQ2ss1GrVF4pfqpU9A4LG rdjd8OvAAVofMVtb6RMyOH1meHX0R8jylmMGoQzs23zuhI1QtA+tjcrFj+IDmjKv FbkZNLLWos0pK3RIYiVRCZNICgti53TixsC5iT7vD09rJPb4kBGwkP8saEIakJrr hSrz1BQ0olkfLoXZ+C9fKqt19mydIgBX8QC50tl1k1nS3rOxPMxJJXvBMc01U0tS 7bchTvam2Nu8QyGS6spfMrvRG0hBklTRTsRDifOKFOHtXy2mRTjX9l9foCKm/amX ciL1vryfLBcFts3OnnmHH345/SLaaiLVQyHMSMBElnYYpvcgaSSI/2A6mPJwHlIl 9U8UEz0tBQIDAQABoGswaQYJKoZIhvcNAQkOMVwwWjAaBgsrBgEEAYKMTAEBAQQL SS1BTS1BLVVVSUQwGQYLKwYBBAGCjEwBAQIECmlfYW1fYW5faWQwIQYLKwYBBAGC jEwBAQMEEmlfYW1fYW5faW1hZ2VfbmFtZTANBgkqhkiG9w0BAQsFAAOCAgEAtA8H A/I+yp8CowMHVN1oqNSas2MyJWczpqDkMRW4DksI/6S3EbVwg9SDRXR9oPcwIhuV 8teqHVkAbCYtthZG+NdNJgBUyGupIw+Jw1xn0ayXYm4ZdHsdUneNKtWRHpYwd1KA e13sjhQocCdFU9hLQGdE3vUvcyE+evdtjKhTJ059bJmuXuJxhoHS6AOLTGVEWy/V bOurda450+YnA+eLDmc+edkJqlb6odO0blPlhb72A/pT6z4fi/KbHizx5Mqs4s9t RRgriyPtIx4hR4qRNTGik87KnJOQ2C5Anak89vzr9vsi08sNun6xOuJ/zDroBtXI Gif+4Uxg75XgnKFysg85XhzEjaYtsD3rjl9V5xPJOo5qwnksuHQs02M4Wh5qP2xZ FmR4gJ1uFsACIKQDDY9dCYrJc0QWPT3G70MS6c5fxaXhAkG3idJBWVxTrRyQw3/v 4z3B+cBAwGSwa/vxyjZoXSp2gR+/D0gHFaiQL4DHfkZ0vZrp8cFPyiQcXVDct0M/ q6zw0uTAoNpp0R0mUlcm3LPKW9muW1rZWTtmxfbPGwMWUk3BPwX8ro1XlkSePao/ Bv04PBh0ph9zBEPHz54skjPYMUia6B9ZsD1WhKR8DZlJvmGliHHCv9OgCkCNxnts Bwfglgz2jKGf1BcZeZCzFKob40W9aRmwHSKRsWw= -----END CERTIFICATE REQUEST----- puppet-5.5.10/spec/fixtures/unit/type/0000755005276200011600000000000013417162176017562 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/type/user/0000755005276200011600000000000013417162176020540 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/type/user/authorized_keys0000644005276200011600000000232413417161721023670 0ustar jenkinsjenkins# fixture for testing ssh key purging ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDTXvM7AslzjNUYrPLiNVBsF5VnqL2RmqrkzscdVdHzVxvieNwmLGeUkg8EfXPiz7j5F/Lr0J8oItTCWzyN2KmM+DhUMjvP4AbELO/VYbnVrZICRiUNYSO3EN9/uapKAuiev88d7ynbonCU0VZoTPg/ug4OondOrLCtcGri5ltF+mausGfAYiFAQVEWqXV+1tyejoawJ884etb3n4ilpsrH9JK6AtOkEWVD3TDrNi29O1mQQ/Cn88g472zAJ+DhsIn+iehtfX5nmOtDNN/1t1bGMIBzkSYEAYwUiRJbRXvbobT7qKZQPA3dh0m8AYQS5/hd4/c4pmlxL8kgr24SnBY5 key1 name ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDTXvM7AslzjNUYrPLiNVBsF5VnqL2RmqrkzscdVdHzVxvieNwmLGeUkg8EfXPiz7j5F/Lr0J8oItTCWzyN2KmM+DhUMjvP4AbELO/VYbnVrZICRiUNYSO3EN9/uapKAuiev88d7ynbonCU0VZoTPg/ug4OondOrLCtcGri5ltF+mausGfAYiFAQVEWqXV+1tyejoawJ884etb3n4ilpsrH9JK6AtOkEWVD3TDrNi29O1mQQ/Cn88g472zAJ+DhsIn+iehtfX5nmOtDNN/1t1bGMIBzkSYEAYwUiRJbRXvbobT7qKZQPA3dh0m8AYQS5/hd4/c4pmlxL8kgr24SnBY5 keyname2 #ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDTXvM7AslzjNUYrPLiNVBsF5VnqL2RmqrkzscdVdHzVxvieNwmLGeUkg8EfXPiz7j5F/Lr0J8oItTCWzyN2KmM+DhUMjvP4AbELO/VYbnVrZICRiUNYSO3EN9/uapKAuiev88d7ynbonCU0VZoTPg/ug4OondOrLCtcGri5ltF+mausGfAYiFAQVEWqXV+1tyejoawJ884etb3n4ilpsrH9JK6AtOkEWVD3TDrNi29O1mQQ/Cn88g472zAJ+DhsIn+iehtfX5nmOtDNN/1t1bGMIBzkSYEAYwUiRJbRXvbobT7qKZQPA3dh0m8AYQS5/hd4/c4pmlxL8kgr24SnBY5 keyname3 ssh-rsa KEY-WITH-NO-NAME puppet-5.5.10/spec/fixtures/unit/util/0000755005276200011600000000000013417162176017556 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/util/filetype/0000755005276200011600000000000013417162176021377 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/util/filetype/aixtab_output0000644005276200011600000000343013417161721024205 0ustar jenkinsjenkins# @(#)08 1.15.1.3 src/bos/usr/sbin/cron/root, cmdcntl, bos530 2/11/94 17:19:47 # IBM_PROLOG_BEGIN_TAG # This is an automatically generated prolog. # # bos530 src/bos/usr/sbin/cron/root 1.15.1.3 # # Licensed Materials - Property of IBM # # (C) COPYRIGHT International Business Machines Corp. 1989,1994 # All Rights Reserved # # US Government Users Restricted Rights - Use, duplication or # disclosure restricted by GSA ADP Schedule Contract with IBM Corp. # # IBM_PROLOG_END_TAG # # COMPONENT_NAME: (CMDCNTL) commands needed for basic system needs # # FUNCTIONS: # # ORIGINS: 27 # # (C) COPYRIGHT International Business Machines Corp. 1989,1994 # All Rights Reserved # Licensed Materials - Property of IBM # # US Government Users Restricted Rights - Use, duplication or # disclosure restricted by GSA ADP Schedule Contract with IBM Corp. # #0 3 * * * /usr/sbin/skulker #45 2 * * 0 /usr/lib/spell/compress #45 23 * * * ulimit 5000; /usr/lib/smdemon.cleanu > /dev/null 0 11 * * * /usr/bin/errclear -d S,O 30 0 12 * * * /usr/bin/errclear -d H 90 0 15 * * * /usr/lib/ras/dumpcheck >/dev/null 2>&1 # SSA warning : Deleting the next two lines may cause errors in redundant # SSA warning : hardware to go undetected. 01 5 * * * /usr/lpp/diagnostics/bin/run_ssa_ela 1>/dev/null 2>/dev/null 0 * * * * /usr/lpp/diagnostics/bin/run_ssa_healthcheck 1>/dev/null 2>/dev/null # SSA warning : Deleting the next line may allow enclosure hardware errors to go undetected 30 * * * * /usr/lpp/diagnostics/bin/run_ssa_encl_healthcheck 1>/dev/null 2>/dev/null # SSA warning : Deleting the next line may allow link speed exceptions to go undetected 30 4 * * * /usr/lpp/diagnostics/bin/run_ssa_link_speed 1>/dev/null 2>/dev/null 55 23 * * * /var/perf/pm/bin/pmcfg >/dev/null 2>&1 #Enable PM Data Collection puppet-5.5.10/spec/fixtures/unit/util/filetype/suntab_output0000644005276200011600000000053413417161721024233 0ustar jenkinsjenkins#ident "@(#)root 1.19 98/07/06 SMI" /* SVr4.0 1.1.3.1 */ # # The root crontab should be used to perform accounting data collection. # # 10 3 * * * /usr/sbin/logadm 15 3 * * 0 /usr/lib/fs/nfs/nfsfind 30 3 * * * [ -x /usr/lib/gss/gsscred_clean ] && /usr/lib/gss/gsscred_clean #10 3 * * * /usr/lib/krb5/kprop_script ___slave_kdcs___ puppet-5.5.10/spec/fixtures/unit/util/monkey_patches/0000755005276200011600000000000013417162176022567 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/util/monkey_patches/x509.pem0000644005276200011600000000372013417161721023774 0ustar jenkinsjenkins-----BEGIN CERTIFICATE----- MIIFmTCCA4GgAwIBAgIQea0WoUqgpa1Mc1j0BxMuZTANBgkqhkiG9w0BAQUFADBf MRMwEQYKCZImiZPyLGQBGRYDY29tMRkwFwYKCZImiZPyLGQBGRYJbWljcm9zb2Z0 MS0wKwYDVQQDEyRNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkw HhcNMDEwNTA5MjMxOTIyWhcNMjEwNTA5MjMyODEzWjBfMRMwEQYKCZImiZPyLGQB GRYDY29tMRkwFwYKCZImiZPyLGQBGRYJbWljcm9zb2Z0MS0wKwYDVQQDEyRNaWNy b3NvZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEB AQUAA4ICDwAwggIKAoICAQDzXfqAZ9Rap6kMLJAg0DUIPHWEzbcHiZyJ2t7Ow2D6 kWhanpRxKRh2fMLgyCV2lA5Y+gQ0Nubfr/eAuulYCyuT5Z0F43cikfc0ZDwikR1e 4QmQvBT+/HVYGeF5tweSo66IWQjYnwfKA1j8aCltMtfSqMtL/OELSDJP5uu4rU/k XG8TlJnbldV126gat5SRtHdb9UgMj2p5fRRwBH1tr5D12nDYR7e/my9s5wW34RFg rHmRFHzF1qbk4X7Vw37lktI8ALU2gt554W3ztW74nzPJy1J9c5g224uha6KVl5uj 3sJNJv8GlmclBsjnrOTuEjOVMZnINQhONMp5U9W1vmMyWUA2wKVOBE0921sHM+RY v+8/U2TYQlk1V/0PRXwkBE2e1jh0EZcikM5oRHSSb9VLb7CG48c2QqDQ/MHAWvmj YbkwR3GWChawkcBCle8Qfyhq4yofseTNAz93cQTHIPxJDx1FiKTXy36IrY4t7EXb xFEEySr87IaemhGXW97OU4jm4rf9rJXCKEDb7wSQ34EzOdmyRaUjhwalVYkxuwYt YA5BGH0fLrWXyxHrFdUkpZTvFRSJ/Utz+jJb/NEzAPlZYnAHMuouq0Ate8rdIWcb MJmPFqojqEHRsG4RmzbE3kB0nOFYZcFgHnpbOMiPuwQmfNQWQOW2a2yqhv0Av87B NQIDAQABo1EwTzALBgNVHQ8EBAMCAcYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E FgQUDqyCYEBWJ5flJRP8KuEKU5VZ5KQwEAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZI hvcNAQEFBQADggIBAMURTQM6YN1dUhF3j7K7NsiyBb+0t6jYIJ1cEwO2HCL6BhM1 tshj1JpHbyZX0lXxBLEmX9apUGigvNK4bszD6azfGc14rFl0rGY0NsQbPmw4TDMO MBINoyb+UVMA/69aToQNDx/kbQUuToVLjWwzb1TSZKu/UK99ejmgN+1jAw/8EwbO FjbUVDuVG1FiOuVNF9QFOZKaJ6hbqr3su77jIIlgcWxWs6UT0G0OI36VA+1oPfLY Y7hrTbboMLXhypRL96KqXZkwsj2nwlFsKCABJCcrSwC3nRFrcL6yEIK8DJto0I07 JIeqmShynTNfWZC99d6TnjpiWjQ54ohVHbkGsMGJay3XacMZEjaE0Mmg2v8vaXiy 5Xra69cMwPe9Yxe4ORM4ojZbe/KFVmodZGLBOOKqv1FmopT1EpxmIhBr8rcwki3y KfA9OxRDaKLxnCk3y844ICVtfGfzfiQSJAMIgUfspZ6X9RjXz7vV73aW7/3O21ad laBC+ZdY4dcxItNfWeY+biIA6kOEtiXb2fMIVmjAZGsdfOy2k6JiV24u2OdYj8Qx SSbd3ik1h/UwcXBbFDxpvYkSfesuo/7Yf56CWlIKK8FDK9kwiJ/IEPuJjeahhXUz fmye23MTZGJppS99ypZtn/gETTCSPW4hFCHJPeDD/YprnUr90aGdmUN3P7Da -----END CERTIFICATE----- puppet-5.5.10/spec/fixtures/unit/util/rdoc/0000755005276200011600000000000013417162176020505 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/unit/util/rdoc/basic.pp0000644005276200011600000000024613417161721022124 0ustar jenkinsjenkins# im a class class foo { file { '/tmp/foo' : ensure => present, } } # im a node node gar { } # im a define define baz { } # im a resource host { 'cow' : } puppet-5.5.10/spec/fixtures/vcr/0000755005276200011600000000000013417162176016414 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/vcr/cassettes/0000755005276200011600000000000013417162176020412 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/vcr/cassettes/Puppet_Network_HTTP_Connection/0000755005276200011600000000000013417162176026416 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/vcr/cassettes/Puppet_Network_HTTP_Connection/when_handling_requests/0000755005276200011600000000000013417162176033156 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000015600000000000011605 Lustar rootrootpuppet-5.5.10/spec/fixtures/vcr/cassettes/Puppet_Network_HTTP_Connection/when_handling_requests/_request_get/puppet-5.5.10/spec/fixtures/vcr/cassettes/Puppet_Network_HTTP_Connection/when_handling_requests/_req0000755005276200011600000000000013417162176034025 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000021300000000000011577 Lustar rootrootpuppet-5.5.10/spec/fixtures/vcr/cassettes/Puppet_Network_HTTP_Connection/when_handling_requests/_request_get/should_yield_to_the_block.ymlpuppet-5.5.10/spec/fixtures/vcr/cassettes/Puppet_Network_HTTP_Connection/when_handling_requests/_req0000644005276200011600000000065713417161721034032 0ustar jenkinsjenkins--- http_interactions: - request: method: get uri: http://my-server:8140/foo body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 200 message: ! 'OK ' headers: {} body: encoding: US-ASCII string: '' http_version: recorded_at: Thu, 19 Mar 2015 22:44:49 GMT recorded_with: VCR 2.9.3 ././@LongLink0000644000000000000000000000015700000000000011606 Lustar rootrootpuppet-5.5.10/spec/fixtures/vcr/cassettes/Puppet_Network_HTTP_Connection/when_handling_requests/_request_head/puppet-5.5.10/spec/fixtures/vcr/cassettes/Puppet_Network_HTTP_Connection/when_handling_requests/_req0000755005276200011600000000000013417162176034025 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000021400000000000011600 Lustar rootrootpuppet-5.5.10/spec/fixtures/vcr/cassettes/Puppet_Network_HTTP_Connection/when_handling_requests/_request_head/should_yield_to_the_block.ymlpuppet-5.5.10/spec/fixtures/vcr/cassettes/Puppet_Network_HTTP_Connection/when_handling_requests/_req0000644005276200011600000000066013417161721034024 0ustar jenkinsjenkins--- http_interactions: - request: method: head uri: http://my-server:8140/foo body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 200 message: ! 'OK ' headers: {} body: encoding: US-ASCII string: '' http_version: recorded_at: Thu, 19 Mar 2015 22:44:49 GMT recorded_with: VCR 2.9.3 ././@LongLink0000644000000000000000000000015700000000000011606 Lustar rootrootpuppet-5.5.10/spec/fixtures/vcr/cassettes/Puppet_Network_HTTP_Connection/when_handling_requests/_request_post/puppet-5.5.10/spec/fixtures/vcr/cassettes/Puppet_Network_HTTP_Connection/when_handling_requests/_req0000755005276200011600000000000013417162176034025 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000021400000000000011600 Lustar rootrootpuppet-5.5.10/spec/fixtures/vcr/cassettes/Puppet_Network_HTTP_Connection/when_handling_requests/_request_post/should_yield_to_the_block.ymlpuppet-5.5.10/spec/fixtures/vcr/cassettes/Puppet_Network_HTTP_Connection/when_handling_requests/_req0000644005276200011600000000067613417161721034033 0ustar jenkinsjenkins--- http_interactions: - request: method: post uri: http://my-server:8140/foo body: encoding: US-ASCII string: ! 'param: value' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 200 message: ! 'OK ' headers: {} body: encoding: US-ASCII string: '' http_version: recorded_at: Thu, 19 Mar 2015 23:15:40 GMT recorded_with: VCR 2.9.3 puppet-5.5.10/spec/fixtures/vcr/cassettes/Puppet_Type_File/0000755005276200011600000000000013417162176023627 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/vcr/cassettes/Puppet_Type_File/when_sourcing/0000755005276200011600000000000013417162176026501 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/vcr/cassettes/Puppet_Type_File/when_sourcing/from_http/0000755005276200011600000000000013417162176030503 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/vcr/cassettes/Puppet_Type_File/when_sourcing/from_http/using_md5/0000755005276200011600000000000013417162176032375 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000020700000000000011602 Lustar rootrootpuppet-5.5.10/spec/fixtures/vcr/cassettes/Puppet_Type_File/when_sourcing/from_http/using_md5/should_fetch_if_not_on_the_local_disk.ymlpuppet-5.5.10/spec/fixtures/vcr/cassettes/Puppet_Type_File/when_sourcing/from_http/using_md5/should_0000644005276200011600000001001113417161721033741 0ustar jenkinsjenkins--- http_interactions: - request: method: head uri: http://my-server/file body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 301 message: ! 'Moved Permanently ' headers: Location: - http://my-server/file/ Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Content-Length: - '44' Connection: - Keep-Alive body: encoding: US-ASCII string: '' http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT - request: method: head uri: http://my-server/file/ body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 200 message: ! 'OK ' headers: Etag: - 62e0b-184a-550f415e Content-Type: - text/html Content-Length: - '6218' Last-Modified: - Sun, 22 Mar 2015 22:25:34 GMT Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Connection: - Keep-Alive body: encoding: US-ASCII string: '' http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT - request: method: head uri: http://my-server/file/ body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 200 message: ! 'OK ' headers: Etag: - 62e0b-184a-550f415e Content-Type: - text/html Content-Length: - '6218' Last-Modified: - Sun, 22 Mar 2015 22:25:34 GMT Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Connection: - Keep-Alive body: encoding: US-ASCII string: '' http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT - request: method: head uri: http://my-server/file body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 301 message: ! 'Moved Permanently ' headers: Location: - http://my-server/file/ Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Content-Length: - '44' Connection: - Keep-Alive body: encoding: US-ASCII string: '' http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT - request: method: head uri: http://my-server/file/ body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 200 message: ! 'OK ' headers: Etag: - 62e0b-184a-550f415e Content-Type: - text/html Content-Length: - '6218' Last-Modified: - Sun, 22 Mar 2015 22:25:34 GMT Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Connection: - Keep-Alive body: encoding: US-ASCII string: '' http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT - request: method: get uri: http://my-server/file/ body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 200 message: ! 'OK ' headers: Etag: - 62e0b-184a-550f415e Content-Type: - text/html Content-Length: - '6218' Last-Modified: - Sun, 22 Mar 2015 22:25:34 GMT Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Connection: - Keep-Alive body: encoding: US-ASCII string: ! "Content via HTTP\n" http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT recorded_with: VCR 2.9.3 ././@LongLink0000644000000000000000000000022400000000000011601 Lustar rootrootpuppet-5.5.10/spec/fixtures/vcr/cassettes/Puppet_Type_File/when_sourcing/from_http/using_md5/should_not_update_if_content_on_disk_is_up-to-date.ymlpuppet-5.5.10/spec/fixtures/vcr/cassettes/Puppet_Type_File/when_sourcing/from_http/using_md5/should_0000644005276200011600000001033113417161721033746 0ustar jenkinsjenkins--- http_interactions: - request: method: head uri: http://my-server/file body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 301 message: ! 'Moved Permanently ' headers: Location: - http://my-server/file/ Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Content-Length: - '44' Connection: - Keep-Alive body: encoding: US-ASCII string: '' http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT - request: method: head uri: http://my-server/file/ body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 200 message: ! 'OK ' headers: Etag: - 62e0b-184a-550f415e Content-Type: - text/html Content-Length: - '6218' Content-MD5: - Es93YfogzPk5EimSmqb9XQ== Last-Modified: - Sun, 22 Mar 2015 22:25:34 GMT Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Connection: - Keep-Alive body: encoding: US-ASCII string: '' http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT - request: method: head uri: http://my-server/file/ body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 200 message: ! 'OK ' headers: Etag: - 62e0b-184a-550f415e Content-Type: - text/html Content-Length: - '6218' Content-MD5: - Es93YfogzPk5EimSmqb9XQ== Last-Modified: - Sun, 22 Mar 2015 22:25:34 GMT Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Connection: - Keep-Alive body: encoding: US-ASCII string: '' http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT - request: method: head uri: http://my-server/file body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 301 message: ! 'Moved Permanently ' headers: Location: - http://my-server/file/ Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Content-Length: - '44' Connection: - Keep-Alive body: encoding: US-ASCII string: '' http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT - request: method: head uri: http://my-server/file/ body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 200 message: ! 'OK ' headers: Etag: - 62e0b-184a-550f415e Content-Type: - text/html Content-Length: - '6218' Content-MD5: - Es93YfogzPk5EimSmqb9XQ== Last-Modified: - Sun, 22 Mar 2015 22:25:34 GMT Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Connection: - Keep-Alive body: encoding: US-ASCII string: '' http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT - request: method: get uri: http://my-server/file/ body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 200 message: ! 'OK ' headers: Etag: - 62e0b-184a-550f415e Content-Type: - text/html Content-Length: - '6218' Content-MD5: - Es93YfogzPk5EimSmqb9XQ== Last-Modified: - Sun, 22 Mar 2015 22:25:34 GMT Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Connection: - Keep-Alive body: encoding: US-ASCII string: ! "Content via HTTP\n" http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT recorded_with: VCR 2.9.3 ././@LongLink0000644000000000000000000000021200000000000011576 Lustar rootrootpuppet-5.5.10/spec/fixtures/vcr/cassettes/Puppet_Type_File/when_sourcing/from_http/using_md5/should_update_if_content_differs_on_disk.ymlpuppet-5.5.10/spec/fixtures/vcr/cassettes/Puppet_Type_File/when_sourcing/from_http/using_md5/should_0000644005276200011600000001033113417161721033746 0ustar jenkinsjenkins--- http_interactions: - request: method: head uri: http://my-server/file body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 301 message: ! 'Moved Permanently ' headers: Location: - http://my-server/file/ Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Content-Length: - '44' Connection: - Keep-Alive body: encoding: US-ASCII string: '' http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT - request: method: head uri: http://my-server/file/ body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 200 message: ! 'OK ' headers: Etag: - 62e0b-184a-550f415e Content-Type: - text/html Content-Length: - '6218' Content-MD5: - Es93YfogzPk5EimSmqb9XQ== Last-Modified: - Sun, 22 Mar 2015 22:25:34 GMT Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Connection: - Keep-Alive body: encoding: US-ASCII string: '' http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT - request: method: head uri: http://my-server/file/ body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 200 message: ! 'OK ' headers: Etag: - 62e0b-184a-550f415e Content-Type: - text/html Content-Length: - '6218' Content-MD5: - Es93YfogzPk5EimSmqb9XQ== Last-Modified: - Sun, 22 Mar 2015 22:25:34 GMT Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Connection: - Keep-Alive body: encoding: US-ASCII string: '' http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT - request: method: head uri: http://my-server/file body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 301 message: ! 'Moved Permanently ' headers: Location: - http://my-server/file/ Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Content-Length: - '44' Connection: - Keep-Alive body: encoding: US-ASCII string: '' http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT - request: method: head uri: http://my-server/file/ body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 200 message: ! 'OK ' headers: Etag: - 62e0b-184a-550f415e Content-Type: - text/html Content-Length: - '6218' Content-MD5: - Es93YfogzPk5EimSmqb9XQ== Last-Modified: - Sun, 22 Mar 2015 22:25:34 GMT Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Connection: - Keep-Alive body: encoding: US-ASCII string: '' http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT - request: method: get uri: http://my-server/file/ body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 200 message: ! 'OK ' headers: Etag: - 62e0b-184a-550f415e Content-Type: - text/html Content-Length: - '6218' Content-MD5: - Es93YfogzPk5EimSmqb9XQ== Last-Modified: - Sun, 22 Mar 2015 22:25:34 GMT Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Connection: - Keep-Alive body: encoding: US-ASCII string: ! "Content via HTTP\n" http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT recorded_with: VCR 2.9.3 puppet-5.5.10/spec/fixtures/vcr/cassettes/Puppet_Type_File/when_sourcing/from_http/using_mtime/0000755005276200011600000000000013417162176033023 5ustar jenkinsjenkins././@LongLink0000644000000000000000000000021200000000000011576 Lustar rootrootpuppet-5.5.10/spec/fixtures/vcr/cassettes/Puppet_Type_File/when_sourcing/from_http/using_mtime/should_fetch_if_mtime_is_older_on_disk.ymlpuppet-5.5.10/spec/fixtures/vcr/cassettes/Puppet_Type_File/when_sourcing/from_http/using_mtime/shoul0000644005276200011600000001001113417161721034064 0ustar jenkinsjenkins--- http_interactions: - request: method: head uri: http://my-server/file body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 301 message: ! 'Moved Permanently ' headers: Location: - http://my-server/file/ Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Content-Length: - '44' Connection: - Keep-Alive body: encoding: US-ASCII string: '' http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT - request: method: head uri: http://my-server/file/ body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 200 message: ! 'OK ' headers: Etag: - 62e0b-184a-550f415e Content-Type: - text/html Content-Length: - '6218' Last-Modified: - Sun, 22 Mar 2015 22:25:34 GMT Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Connection: - Keep-Alive body: encoding: US-ASCII string: '' http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT - request: method: head uri: http://my-server/file/ body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 200 message: ! 'OK ' headers: Etag: - 62e0b-184a-550f415e Content-Type: - text/html Content-Length: - '6218' Last-Modified: - Sun, 22 Mar 2015 22:25:34 GMT Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Connection: - Keep-Alive body: encoding: US-ASCII string: '' http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT - request: method: head uri: http://my-server/file body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 301 message: ! 'Moved Permanently ' headers: Location: - http://my-server/file/ Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Content-Length: - '44' Connection: - Keep-Alive body: encoding: US-ASCII string: '' http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT - request: method: head uri: http://my-server/file/ body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 200 message: ! 'OK ' headers: Etag: - 62e0b-184a-550f415e Content-Type: - text/html Content-Length: - '6218' Last-Modified: - Sun, 22 Mar 2015 22:25:34 GMT Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Connection: - Keep-Alive body: encoding: US-ASCII string: '' http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT - request: method: get uri: http://my-server/file/ body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 200 message: ! 'OK ' headers: Etag: - 62e0b-184a-550f415e Content-Type: - text/html Content-Length: - '6218' Last-Modified: - Sun, 22 Mar 2015 22:25:34 GMT Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Connection: - Keep-Alive body: encoding: US-ASCII string: ! "Content via HTTP\n" http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT recorded_with: VCR 2.9.3 ././@LongLink0000644000000000000000000000020700000000000011602 Lustar rootrootpuppet-5.5.10/spec/fixtures/vcr/cassettes/Puppet_Type_File/when_sourcing/from_http/using_mtime/should_fetch_if_no_header_specified.ymlpuppet-5.5.10/spec/fixtures/vcr/cassettes/Puppet_Type_File/when_sourcing/from_http/using_mtime/shoul0000644005276200011600000000743513417161721034104 0ustar jenkinsjenkins--- http_interactions: - request: method: head uri: http://my-server/file body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 301 message: ! 'Moved Permanently ' headers: Location: - http://my-server/file/ Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Content-Length: - '44' Connection: - Keep-Alive body: encoding: US-ASCII string: '' http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT - request: method: head uri: http://my-server/file/ body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 200 message: ! 'OK ' headers: Etag: - 62e0b-184a-550f415e Content-Type: - text/html Content-Length: - '6218' Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Connection: - Keep-Alive body: encoding: US-ASCII string: '' http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT - request: method: head uri: http://my-server/file/ body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 200 message: ! 'OK ' headers: Etag: - 62e0b-184a-550f415e Content-Type: - text/html Content-Length: - '6218' Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Connection: - Keep-Alive body: encoding: US-ASCII string: '' http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT - request: method: head uri: http://my-server/file body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 301 message: ! 'Moved Permanently ' headers: Location: - http://my-server/file/ Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Content-Length: - '44' Connection: - Keep-Alive body: encoding: US-ASCII string: '' http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT - request: method: head uri: http://my-server/file/ body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 200 message: ! 'OK ' headers: Etag: - 62e0b-184a-550f415e Content-Type: - text/html Content-Length: - '6218' Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Connection: - Keep-Alive body: encoding: US-ASCII string: '' http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT - request: method: get uri: http://my-server/file/ body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 200 message: ! 'OK ' headers: Etag: - 62e0b-184a-550f415e Content-Type: - text/html Content-Length: - '6218' Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Connection: - Keep-Alive body: encoding: US-ASCII string: ! "Content via HTTP\n" http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT recorded_with: VCR 2.9.3 ././@LongLink0000644000000000000000000000021100000000000011575 Lustar rootrootpuppet-5.5.10/spec/fixtures/vcr/cassettes/Puppet_Type_File/when_sourcing/from_http/using_mtime/should_fetch_if_not_on_the_local_disk.ymlpuppet-5.5.10/spec/fixtures/vcr/cassettes/Puppet_Type_File/when_sourcing/from_http/using_mtime/shoul0000644005276200011600000001001113417161721034064 0ustar jenkinsjenkins--- http_interactions: - request: method: head uri: http://my-server/file body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 301 message: ! 'Moved Permanently ' headers: Location: - http://my-server/file/ Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Content-Length: - '44' Connection: - Keep-Alive body: encoding: US-ASCII string: '' http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT - request: method: head uri: http://my-server/file/ body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 200 message: ! 'OK ' headers: Etag: - 62e0b-184a-550f415e Content-Type: - text/html Content-Length: - '6218' Last-Modified: - Sun, 22 Mar 2015 22:25:34 GMT Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Connection: - Keep-Alive body: encoding: US-ASCII string: '' http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT - request: method: head uri: http://my-server/file/ body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 200 message: ! 'OK ' headers: Etag: - 62e0b-184a-550f415e Content-Type: - text/html Content-Length: - '6218' Last-Modified: - Sun, 22 Mar 2015 22:25:34 GMT Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Connection: - Keep-Alive body: encoding: US-ASCII string: '' http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT - request: method: head uri: http://my-server/file body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 301 message: ! 'Moved Permanently ' headers: Location: - http://my-server/file/ Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Content-Length: - '44' Connection: - Keep-Alive body: encoding: US-ASCII string: '' http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT - request: method: head uri: http://my-server/file/ body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 200 message: ! 'OK ' headers: Etag: - 62e0b-184a-550f415e Content-Type: - text/html Content-Length: - '6218' Last-Modified: - Sun, 22 Mar 2015 22:25:34 GMT Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Connection: - Keep-Alive body: encoding: US-ASCII string: '' http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT - request: method: get uri: http://my-server/file/ body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 200 message: ! 'OK ' headers: Etag: - 62e0b-184a-550f415e Content-Type: - text/html Content-Length: - '6218' Last-Modified: - Sun, 22 Mar 2015 22:25:34 GMT Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Connection: - Keep-Alive body: encoding: US-ASCII string: ! "Content via HTTP\n" http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT recorded_with: VCR 2.9.3 ././@LongLink0000644000000000000000000000021700000000000011603 Lustar rootrootpuppet-5.5.10/spec/fixtures/vcr/cassettes/Puppet_Type_File/when_sourcing/from_http/using_mtime/should_not_update_if_mtime_is_newer_on_disk.ymlpuppet-5.5.10/spec/fixtures/vcr/cassettes/Puppet_Type_File/when_sourcing/from_http/using_mtime/shoul0000644005276200011600000001001113417161721034064 0ustar jenkinsjenkins--- http_interactions: - request: method: head uri: http://my-server/file body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 301 message: ! 'Moved Permanently ' headers: Location: - http://my-server/file/ Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Content-Length: - '44' Connection: - Keep-Alive body: encoding: US-ASCII string: '' http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT - request: method: head uri: http://my-server/file/ body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 200 message: ! 'OK ' headers: Etag: - 62e0b-184a-550f415e Content-Type: - text/html Content-Length: - '6218' Last-Modified: - Sun, 22 Mar 2015 22:25:34 GMT Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Connection: - Keep-Alive body: encoding: US-ASCII string: '' http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT - request: method: head uri: http://my-server/file/ body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 200 message: ! 'OK ' headers: Etag: - 62e0b-184a-550f415e Content-Type: - text/html Content-Length: - '6218' Last-Modified: - Sun, 22 Mar 2015 22:25:34 GMT Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Connection: - Keep-Alive body: encoding: US-ASCII string: '' http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT - request: method: head uri: http://my-server/file body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 301 message: ! 'Moved Permanently ' headers: Location: - http://my-server/file/ Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Content-Length: - '44' Connection: - Keep-Alive body: encoding: US-ASCII string: '' http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT - request: method: head uri: http://my-server/file/ body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 200 message: ! 'OK ' headers: Etag: - 62e0b-184a-550f415e Content-Type: - text/html Content-Length: - '6218' Last-Modified: - Sun, 22 Mar 2015 22:25:34 GMT Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Connection: - Keep-Alive body: encoding: US-ASCII string: '' http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT - request: method: get uri: http://my-server/file/ body: encoding: US-ASCII string: '' headers: Accept: - ! '*/*' User-Agent: - Ruby response: status: code: 200 message: ! 'OK ' headers: Etag: - 62e0b-184a-550f415e Content-Type: - text/html Content-Length: - '6218' Last-Modified: - Sun, 22 Mar 2015 22:25:34 GMT Server: - WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22) Date: - Sun, 22 Mar 2015 22:57:44 GMT Connection: - Keep-Alive body: encoding: US-ASCII string: ! "Content via HTTP\n" http_version: recorded_at: Sun, 22 Mar 2015 22:57:44 GMT recorded_with: VCR 2.9.3 puppet-5.5.10/spec/fixtures/yaml/0000755005276200011600000000000013417162176016564 5ustar jenkinsjenkinspuppet-5.5.10/spec/fixtures/yaml/report2.6.x.yaml0000644005276200011600000001240413417161721021453 0ustar jenkinsjenkins--- !ruby/object:Puppet::Transaction::Report external_times: !ruby/sym config_retrieval: 0.170313835144043 host: ubuntu1004desktop.localdomain logs: - !ruby/object:Puppet::Util::Log level: !ruby/sym debug message: Using cached certificate for ca source: Puppet tags: - debug time: 2010-09-23 15:44:06.244173 -07:00 version: &id001 2.6.1 - !ruby/object:Puppet::Util::Log level: !ruby/sym debug message: Using cached certificate for ubuntu1004desktop.localdomain source: Puppet tags: - debug time: 2010-09-23 15:44:06.244764 -07:00 version: *id001 - !ruby/object:Puppet::Util::Log level: !ruby/sym debug message: Using cached certificate_revocation_list for ca source: Puppet tags: - debug time: 2010-09-23 15:44:06.245677 -07:00 version: *id001 - !ruby/object:Puppet::Util::Log level: !ruby/sym debug message: "catalog supports formats: b64_zlib_yaml dot marshal pson raw yaml; using pson" source: Puppet tags: - debug time: 2010-09-23 15:44:06.247069 -07:00 version: *id001 - !ruby/object:Puppet::Util::Log level: !ruby/sym info message: Caching catalog for ubuntu1004desktop.localdomain source: Puppet tags: - info time: 2010-09-23 15:44:06.409109 -07:00 version: *id001 - !ruby/object:Puppet::Util::Log level: !ruby/sym debug message: Creating default schedules source: Puppet tags: - debug time: 2010-09-23 15:44:06.418755 -07:00 version: *id001 - !ruby/object:Puppet::Util::Log level: !ruby/sym debug message: Loaded state in 0.00 seconds source: Puppet tags: - debug time: 2010-09-23 15:44:06.427441 -07:00 version: *id001 - !ruby/object:Puppet::Util::Log level: !ruby/sym info message: Applying configuration version '1285281846' source: Puppet tags: - info time: 2010-09-23 15:44:06.429532 -07:00 version: *id001 metrics: time: !ruby/object:Puppet::Util::Metric label: Time name: time values: - - config_retrieval - Config retrieval - 0.170313835144043 - - schedule - Schedule - 0.00077 - - filebucket - Filebucket - 0.000166 resources: !ruby/object:Puppet::Util::Metric label: Resources name: resources values: - - !ruby/sym total - Total - 7 events: !ruby/object:Puppet::Util::Metric label: Events name: events values: - - !ruby/sym total - Total - 0 changes: !ruby/object:Puppet::Util::Metric label: Changes name: changes values: - - !ruby/sym total - Total - 0 resource_statuses: "Schedule[monthly]": !ruby/object:Puppet::Resource::Status evaluation_time: 0.000121 events: [] file: line: resource: "Schedule[monthly]" source_description: "/Schedule[monthly]" tags: - schedule - monthly time: 2010-09-23 15:44:06.430577 -07:00 version: 1285281846 "Filebucket[puppet]": !ruby/object:Puppet::Resource::Status evaluation_time: 0.000166 events: [] file: line: resource: "Filebucket[puppet]" source_description: "/Filebucket[puppet]" tags: - filebucket - puppet time: 2010-09-23 15:44:06.430998 -07:00 version: 1285281846 "Schedule[never]": !ruby/object:Puppet::Resource::Status evaluation_time: 0.000119 events: [] file: line: resource: "Schedule[never]" source_description: "/Schedule[never]" tags: - schedule - never time: 2010-09-23 15:44:06.433034 -07:00 version: 1285281846 "Schedule[weekly]": !ruby/object:Puppet::Resource::Status evaluation_time: 0.000118 events: [] file: line: resource: "Schedule[weekly]" source_description: "/Schedule[weekly]" tags: - schedule - weekly time: 2010-09-23 15:44:06.431443 -07:00 version: 1285281846 "Schedule[puppet]": !ruby/object:Puppet::Resource::Status evaluation_time: 0.000129 events: [] file: line: resource: "Schedule[puppet]" source_description: "/Schedule[puppet]" tags: - schedule - puppet time: 2010-09-23 15:44:06.432626 -07:00 version: 1285281846 "Schedule[daily]": !ruby/object:Puppet::Resource::Status evaluation_time: 0.000154 events: [] file: line: resource: "Schedule[daily]" source_description: "/Schedule[daily]" tags: - schedule - daily time: 2010-09-23 15:44:06.430130 -07:00 version: 1285281846 "Schedule[hourly]": !ruby/object:Puppet::Resource::Status evaluation_time: 0.000129 events: [] file: line: resource: "Schedule[hourly]" source_description: "/Schedule[hourly]" tags: - schedule - hourly time: 2010-09-23 15:44:06.432185 -07:00 version: 1285281846 time: 2010-09-23 15:44:05.894401 -07:00 puppet-5.5.10/spec/fixtures/yaml/test.local.yaml0000644005276200011600000000071613417161721021517 0ustar jenkinsjenkins--- !ruby/object:Puppet::Node::Facts expiration: 2010-06-07 19:15:36.519351 -07:00 name: test.local values: sp_number_processors: "2" kernelmajversion: "10.3" kernelversion: 10.3.1 sp_secure_vm: secure_vm_enabled ps: ps auxwww macosx_productversion_major: "10.6" hostname: test !ruby/sym _timestamp: Mon Jun 07 18:45:36 -0700 2010 facterversion: 1.5.7 sp_packages: "1" timezone: PDT environment: production puppet-5.5.10/spec/integration/0000755005276200011600000000000013417162176016274 5ustar jenkinsjenkinspuppet-5.5.10/spec/integration/agent/0000755005276200011600000000000013417162176017372 5ustar jenkinsjenkinspuppet-5.5.10/spec/integration/agent/logging_spec.rb0000644005276200011600000001330013417161722022350 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet' require 'puppet/daemon' require 'puppet/application/agent' # The command line flags affecting #20900 and #20919: # # --onetime # --daemonize # --no-daemonize # --logdest # --verbose # --debug # (no flags) (-) # # d and nd are mutally exclusive # # Combinations without logdest, verbose or debug: # # --onetime --daemonize # --onetime --no-daemonize # --onetime # --daemonize # --no-daemonize # - # # 6 cases X [--logdest=console, --logdest=syslog, --logdest=/some/file, ] # = 24 cases to test # # X [--verbose, --debug, ] # = 72 cases to test # # Expectations of behavior are defined in the expected_loggers, expected_level methods, # so adapting to a change in logging behavior should hopefully be mostly a matter of # adjusting the logic in those methods to define new behavior. # # Note that this test does not have anything to say about what happens to logging after # daemonizing. describe 'agent logging' do ONETIME = '--onetime' DAEMONIZE = '--daemonize' NO_DAEMONIZE = '--no-daemonize' LOGDEST_FILE = '--logdest=/dev/null/foo' LOGDEST_SYSLOG = '--logdest=syslog' LOGDEST_CONSOLE = '--logdest=console' VERBOSE = '--verbose' DEBUG = '--debug' DEFAULT_LOG_LEVEL = :notice INFO_LEVEL = :info DEBUG_LEVEL = :debug CONSOLE = :console SYSLOG = :syslog EVENTLOG = :eventlog FILE = :file ONETIME_DAEMONIZE_ARGS = [ [ONETIME], [ONETIME, DAEMONIZE], [ONETIME, NO_DAEMONIZE], [DAEMONIZE], [NO_DAEMONIZE], [], ] LOG_DEST_ARGS = [LOGDEST_FILE, LOGDEST_SYSLOG, LOGDEST_CONSOLE, nil] LOG_LEVEL_ARGS = [VERBOSE, DEBUG, nil] shared_examples "an agent" do |argv, expected| before(:each) do # Don't actually run the agent, bypassing cert checks, forking and the puppet run itself Puppet::Application::Agent.any_instance.stubs(:run_command) # Let exceptions be raised instead of exiting Puppet::Application::Agent.any_instance.stubs(:exit_on_fail).yields end def double_of_bin_puppet_agent_call(argv) argv.unshift('agent') command_line = Puppet::Util::CommandLine.new('puppet', argv) command_line.execute end if Puppet.features.microsoft_windows? && argv.include?(DAEMONIZE) it "should exit on a platform which cannot daemonize if the --daemonize flag is set" do expect { double_of_bin_puppet_agent_call(argv) }.to raise_error(SystemExit) end else if no_log_dest_set_in(argv) it "when evoked with #{argv}, logs to #{expected[:loggers].inspect} at level #{expected[:level]}" do # This logger is created by the Puppet::Settings object which creates and # applies a catalog to ensure that configuration files and users are in # place. # # It's not something we are specifically testing here since it occurs # regardless of user flags. Puppet::Util::Log.expects(:newdestination).with(instance_of(Puppet::Transaction::Report)).at_least_once expected[:loggers].each do |logclass| Puppet::Util::Log.expects(:newdestination).with(logclass).at_least_once end double_of_bin_puppet_agent_call(argv) expect(Puppet::Util::Log.level).to eq(expected[:level]) end end end end def self.no_log_dest_set_in(argv) ([LOGDEST_SYSLOG, LOGDEST_CONSOLE, LOGDEST_FILE] & argv).empty? end def self.verbose_or_debug_set_in_argv(argv) !([VERBOSE, DEBUG] & argv).empty? end def self.log_dest_is_set_to(argv, log_dest) argv.include?(log_dest) end # @param argv Array of commandline flags # @return Set of expected loggers def self.expected_loggers(argv) loggers = Set.new loggers << CONSOLE if verbose_or_debug_set_in_argv(argv) loggers << 'console' if log_dest_is_set_to(argv, LOGDEST_CONSOLE) loggers << '/dev/null/foo' if log_dest_is_set_to(argv, LOGDEST_FILE) if Puppet.features.microsoft_windows? # an explicit call to --logdest syslog on windows is swallowed silently with no # logger created (see #suitable() of the syslog Puppet::Util::Log::Destination subclass) # however Puppet::Util::Log.newdestination('syslog') does get called...so we have # to set an expectation loggers << 'syslog' if log_dest_is_set_to(argv, LOGDEST_SYSLOG) loggers << EVENTLOG if no_log_dest_set_in(argv) else # posix loggers << 'syslog' if log_dest_is_set_to(argv, LOGDEST_SYSLOG) loggers << SYSLOG if no_log_dest_set_in(argv) end return loggers end # @param argv Array of commandline flags # @return Symbol of the expected log level def self.expected_level(argv) case when argv.include?(VERBOSE) then INFO_LEVEL when argv.include?(DEBUG) then DEBUG_LEVEL else DEFAULT_LOG_LEVEL end end # @param argv Array of commandline flags # @return Hash of expected loggers and the expected log level def self.with_expectations_based_on(argv) { :loggers => expected_loggers(argv), :level => expected_level(argv), } end # For running a single spec (by line number): rspec -l150 spec/integration/agent/logging_spec.rb # debug_argv = [] # it_should_behave_like( "an agent", [debug_argv], with_expectations_based_on([debug_argv])) ONETIME_DAEMONIZE_ARGS.each do |onetime_daemonize_args| LOG_LEVEL_ARGS.each do |log_level_args| LOG_DEST_ARGS.each do |log_dest_args| argv = (onetime_daemonize_args + [log_level_args, log_dest_args]).flatten.compact describe "for #{argv}" do it_should_behave_like( "an agent", argv, with_expectations_based_on(argv)) end end end end end puppet-5.5.10/spec/integration/application/0000755005276200011600000000000013417162176020577 5ustar jenkinsjenkinspuppet-5.5.10/spec/integration/application/doc_spec.rb0000644005276200011600000000063713417161721022704 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/files' require 'puppet/application/doc' describe Puppet::Application::Doc do include PuppetSpec::Files it "should respect the -o option" do puppetdoc = Puppet::Application[:doc] puppetdoc.command_line.stubs(:args).returns(['foo', '-o', 'bar']) puppetdoc.parse_options expect(puppetdoc.options[:outputdir]).to eq('bar') end end puppet-5.5.10/spec/integration/application/lookup_spec.rb0000644005276200011600000001322313417161721023443 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/files' require 'puppet_spec/compiler' require 'deep_merge/core' describe 'lookup' do include PuppetSpec::Files context 'with an environment' do let(:env_name) { 'spec' } let(:env_dir) { tmpdir('environments') } let(:environment_files) do { env_name => { 'modules' => {}, 'hiera.yaml' => <<-YAML.unindent, --- version: 5 hierarchy: - name: "Common" data_hash: yaml_data path: "common.yaml" YAML 'data' => { 'common.yaml' => <<-YAML.unindent --- a: value a mod_a::a: value mod_a::a (from environment) mod_a::hash_a: a: value mod_a::hash_a.a (from environment) mod_a::hash_b: a: value mod_a::hash_b.a (from environment) lookup_options: mod_a::hash_b: merge: hash YAML } }, 'someother' => { } } end let(:app) { Puppet::Application[:lookup] } let(:env) { Puppet::Node::Environment.create(env_name.to_sym, [File.join(populated_env_dir, env_name, 'modules')]) } let(:environments) { Puppet::Environments::Directories.new(populated_env_dir, []) } let(:populated_env_dir) do dir_contained_in(env_dir, environment_files) env_dir end def lookup(key, options = {}, explain = false) key = [key] unless key.is_a?(Array) app.command_line.stubs(:args).returns(key) if explain app.options[:explain] = true app.options[:render_as] = :s else app.options[:render_as] = :json end options.each_pair { |k, v| app.options[k] = v } capture = StringIO.new saved_stdout = $stdout begin $stdout = capture expect { app.run_command }.to exit_with(0) ensure $stdout = saved_stdout end out = capture.string.strip if explain out else out.empty? ? nil : JSON.parse("[#{out}]")[0] end end def explain(key, options = {}) lookup(key, options, true) end around(:each) do |example| Puppet.override(:environments => environments, :current_environment => env) do example.run end end it 'finds data in the environment' do expect(lookup('a')).to eql('value a') end context 'uses node_terminus' do require 'puppet/indirector/node/exec' require 'puppet/indirector/node/plain' let(:node) { Puppet::Node.new('testnode', :environment => env) } it ':plain without --compile' do Puppet.settings[:node_terminus] = 'exec' Puppet::Node::Plain.any_instance.expects(:find).returns(node) Puppet::Node::Exec.any_instance.expects(:find).never expect(lookup('a')).to eql('value a') end it 'configured in Puppet settings with --compile' do Puppet.settings[:node_terminus] = 'exec' Puppet::Node::Plain.any_instance.expects(:find).never Puppet::Node::Exec.any_instance.expects(:find).returns(node) expect(lookup('a', :compile => true)).to eql('value a') end end context 'configured with the wrong environment' do let(:env) { Puppet::Node::Environment.create(env_name.to_sym, [File.join(populated_env_dir, env_name, 'modules')]) } it 'does not find data in non-existing environment' do Puppet.override(:environments => environments, :current_environment => 'someother') do expect(lookup('a', {}, true)).to match(/did not find a value for the name 'a'/) end end end context 'and a module' do let(:mod_a_files) do { 'mod_a' => { 'data' => { 'common.yaml' => <<-YAML.unindent --- mod_a::a: value mod_a::a (from mod_a) mod_a::b: value mod_a::b (from mod_a) mod_a::hash_a: a: value mod_a::hash_a.a (from mod_a) b: value mod_a::hash_a.b (from mod_a) mod_a::hash_b: a: value mod_a::hash_b.a (from mod_a) b: value mod_a::hash_b.b (from mod_a) mod_a::interpolated: "-- %{lookup('mod_a::a')} --" mod_a::a_a: "-- %{lookup('mod_a::hash_a.a')} --" mod_a::a_b: "-- %{lookup('mod_a::hash_a.b')} --" mod_a::b_a: "-- %{lookup('mod_a::hash_b.a')} --" mod_a::b_b: "-- %{lookup('mod_a::hash_b.b')} --" 'mod_a::a.quoted.key': 'value mod_a::a.quoted.key (from mod_a)' YAML }, 'hiera.yaml' => <<-YAML.unindent, --- version: 5 hierarchy: - name: "Common" data_hash: yaml_data path: "common.yaml" YAML } } end let(:populated_env_dir) do dir_contained_in(env_dir, DeepMerge.deep_merge!(environment_files, env_name => { 'modules' => mod_a_files })) env_dir end it 'finds data in the module' do expect(lookup('mod_a::b')).to eql('value mod_a::b (from mod_a)') end it 'finds quoted keys in the module' do expect(lookup('"mod_a::a.quoted.key"')).to eql('value mod_a::a.quoted.key (from mod_a)') end it 'merges hashes from environment and module when merge strategy hash is used' do expect(lookup('mod_a::hash_a', :merge => 'hash')).to eql({'a' => 'value mod_a::hash_a.a (from environment)', 'b' => 'value mod_a::hash_a.b (from mod_a)'}) end end end end puppet-5.5.10/spec/integration/application/apply_spec.rb0000644005276200011600000004615413417161722023271 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/files' require 'puppet_spec/compiler' describe "apply" do include PuppetSpec::Files before :each do Puppet[:reports] = "none" end describe "when applying provided catalogs" do it "can apply catalogs provided in a file in json" do file_to_create = tmpfile("json_catalog") catalog = Puppet::Resource::Catalog.new('mine', Puppet.lookup(:environments).get(Puppet[:environment])) resource = Puppet::Resource.new(:file, file_to_create, :parameters => {:content => "my stuff"}) catalog.add_resource resource manifest = file_containing("manifest", catalog.to_json) puppet = Puppet::Application[:apply] puppet.options[:catalog] = manifest puppet.apply expect(Puppet::FileSystem.exist?(file_to_create)).to be_truthy expect(File.read(file_to_create)).to eq("my stuff") end context 'from environment with a pcore defined resource type' do include PuppetSpec::Compiler let!(:envdir) { tmpdir('environments') } let(:env_name) { 'spec' } let(:dir_structure) { { '.resource_types' => { 'applytest.pp' => <<-CODE Puppet::Resource::ResourceType3.new( 'applytest', [Puppet::Resource::Param.new(String, 'message')], [Puppet::Resource::Param.new(String, 'name', true)]) CODE }, 'modules' => { 'amod' => { 'lib' => { 'puppet' => { 'type' => { 'applytest.rb' => <<-CODE Puppet::Type.newtype(:applytest) do newproperty(:message) do def sync Puppet.send(@resource[:loglevel], self.should) end def retrieve :absent end def insync?(is) false end defaultto { @resource[:name] } end newparam(:name) do desc "An arbitrary tag for your own reference; the name of the message." Puppet.notice('the Puppet::Type says hello') isnamevar end end CODE } } } } } } } let(:environments) { Puppet::Environments::Directories.new(envdir, []) } let(:env) { Puppet::Node::Environment.create(:'spec', [File.join(envdir, 'spec', 'modules')]) } let(:node) { Puppet::Node.new('test', :environment => env) } around(:each) do |example| Puppet::Type.rmtype(:applytest) Puppet[:environment] = env_name dir_contained_in(envdir, env_name => dir_structure) Puppet.override(:environments => environments, :current_environment => env) do example.run end end it 'does not load the pcore type' do catalog = compile_to_catalog('applytest { "applytest was here":}', node) apply = Puppet::Application[:apply] apply.options[:catalog] = file_containing('manifest', catalog.to_json) Puppet[:environmentpath] = envdir Puppet::Pops::Loader::Runtime3TypeLoader.any_instance.expects(:find).never expect { apply.run }.to have_printed(/the Puppet::Type says hello.*applytest was here/m) end # Test just to verify that the Pcore Resource Type and not the Ruby one is produced when the catalog is produced it 'loads pcore resource type instead of ruby resource type during compile' do Puppet[:code] = 'applytest { "applytest was here": }' compiler = Puppet::Parser::Compiler.new(node) tn = Puppet::Pops::Loader::TypedName.new(:resource_type_pp, 'applytest') rt = Puppet::Pops::Resource::ResourceTypeImpl.new('applytest', [Puppet::Pops::Resource::Param.new(String, 'message')], [Puppet::Pops::Resource::Param.new(String, 'name', true)]) compiler.loaders.runtime3_type_loader.instance_variable_get(:@resource_3x_loader).expects(:set_entry).once.with(tn, rt, is_a(String)) .returns(Puppet::Pops::Loader::Loader::NamedEntry.new(tn, rt, nil)) expect { compiler.compile }.not_to have_printed(/the Puppet::Type says hello/) end it "does not fail when pcore type is loaded twice" do Puppet[:code] = 'applytest { xyz: alias => aptest }; Resource[applytest]' compiler = Puppet::Parser::Compiler.new(node) expect { compiler.compile }.not_to raise_error end it "does not load the ruby type when using function 'defined()' on a loaded resource that is missing from the catalog" do # Ensure that the Resource[applytest,foo] is loaded' eval_and_collect_notices('applytest { xyz: }', node) # Ensure that: # a) The catalog contains aliases (using a name for the abc resource ensures this) # b) That Resource[applytest,xyz] is not defined in the catalog (although it's loaded) # c) That this doesn't trigger a load of the Puppet::Type notices = eval_and_collect_notices('applytest { abc: name => some_alias }; notice(defined(Resource[applytest,xyz]))', node) expect(notices).to include('false') expect(notices).not_to include('the Puppet::Type says hello') end it 'does not load the ruby type when when referenced from collector during compile' do notices = eval_and_collect_notices("@applytest { 'applytest was here': }\nApplytest<| title == 'applytest was here' |>", node) expect(notices).not_to include('the Puppet::Type says hello') end it 'does not load the ruby type when when referenced from exported collector during compile' do notices = eval_and_collect_notices("@@applytest { 'applytest was here': }\nApplytest<<| |>>", node) expect(notices).not_to include('the Puppet::Type says hello') end end end context 'from environment with pcore object types' do include PuppetSpec::Compiler let!(:envdir) { Puppet[:environmentpath] } let(:env_name) { 'spec' } let(:dir_structure) { { 'environment.conf' => <<-CONF, rich_data = true CONF 'modules' => { 'mod' => { 'types' => { 'streetaddress.pp' => <<-PUPPET, type Mod::StreetAddress = Object[{ attributes => { 'street' => String, 'zipcode' => String, 'city' => String, } }] PUPPET 'address.pp' => <<-PUPPET, type Mod::Address = Object[{ parent => Mod::StreetAddress, attributes => { 'state' => String } }] PUPPET 'contact.pp' => <<-PUPPET, type Mod::Contact = Object[{ attributes => { 'address' => Mod::Address, 'email' => String } }] PUPPET }, 'manifests' => { 'init.pp' => <<-PUPPET, define mod::person(Mod::Contact $contact) { notify { $title: } notify { $contact.address.street: } notify { $contact.address.zipcode: } notify { $contact.address.city: } notify { $contact.address.state: } } class mod { mod::person { 'Test Person': contact => Mod::Contact( Mod::Address('The Street 23', '12345', 'Some City', 'A State'), 'test@example.com') } } PUPPET } } } } } let(:env) { Puppet::Node::Environment.create(env_name.to_sym, [File.join(envdir, env_name, 'modules')]) } let(:node) { Puppet::Node.new('test', :environment => env) } before(:each) do dir_contained_in(envdir, env_name => dir_structure) PuppetSpec::Files.record_tmp(File.join(envdir, env_name)) end it 'can compile the catalog' do compile_to_catalog('include mod', node) end it 'can apply the catalog' do catalog = compile_to_catalog('include mod', node) Puppet[:environment] = env_name apply = Puppet::Application[:apply] apply.options[:catalog] = file_containing('manifest', catalog.to_json) expect { apply.run_command; exit(0) }.to exit_with(0) expect(@logs.map(&:to_s)).to include('The Street 23') end end it "applies a given file even when a directory environment is specified" do manifest = file_containing("manifest.pp", "notice('it was applied')") special = Puppet::Node::Environment.create(:special, []) Puppet.override(:current_environment => special) do Puppet[:environment] = 'special' puppet = Puppet::Application[:apply] puppet.stubs(:command_line).returns(stub('command_line', :args => [manifest])) expect { puppet.run_command }.to exit_with(0) end expect(@logs.map(&:to_s)).to include('it was applied') end it "adds environment to the $server_facts variable" do manifest = file_containing("manifest.pp", "notice(\"$server_facts\")") puppet = Puppet::Application[:apply] puppet.stubs(:command_line).returns(stub('command_line', :args => [manifest])) expect { puppet.run_command }.to exit_with(0) expect(@logs.map(&:to_s)).to include(/{environment =>.*/) end it "applies a given file even when an ENC is configured", :if => !Puppet.features.microsoft_windows? do manifest = file_containing("manifest.pp", "notice('specific manifest applied')") enc = script_containing('enc_script', :windows => '@echo classes: []' + "\n" + '@echo environment: special', :posix => '#!/bin/sh' + "\n" + 'echo "classes: []"' + "\n" + 'echo "environment: special"') Dir.mkdir(File.join(Puppet[:environmentpath], "special"), 0755) special = Puppet::Node::Environment.create(:special, []) Puppet.override(:current_environment => special) do Puppet[:environment] = 'special' Puppet[:node_terminus] = 'exec' Puppet[:external_nodes] = enc puppet = Puppet::Application[:apply] puppet.stubs(:command_line).returns(stub('command_line', :args => [manifest])) expect { puppet.run_command }.to exit_with(0) end expect(@logs.map(&:to_s)).to include('specific manifest applied') end context "handles errors" do it "logs compile errors once" do Puppet.initialize_settings([]) apply = Puppet::Application.find(:apply).new(stub('command_line', :subcommand_name => :apply, :args => [])) apply.options[:code] = '08' msg = 'valid octal' callback = Proc.new do |actual| expect(actual.scan(Regexp.new(msg))).to eq([msg]) end expect do apply.run end.to have_printed(callback).and_exit_with(1) end it "logs compile post processing errors once" do Puppet.initialize_settings([]) apply = Puppet::Application.find(:apply).new(stub('command_line', :subcommand_name => :apply, :args => [])) path = File.expand_path('/tmp/content_file_test.Q634Dlmtime') apply.options[:code] = "file { '#{path}': content => 'This is the test file content', ensure => present, checksum => mtime }" msg = 'You cannot specify content when using checksum' callback = Proc.new do |actual| expect(actual.scan(Regexp.new(msg))).to eq([msg]) end expect do apply.run end.to have_printed(callback).and_exit_with(1) end end context "with a module in an environment" do let(:envdir) { tmpdir('environments') } let(:modulepath) { File.join(envdir, 'spec', 'modules') } let(:execute) { 'include amod' } before(:each) do dir_contained_in(envdir, { "spec" => { "modules" => { "amod" => { "manifests" => { "init.pp" => "class amod{ notice('amod class included') }" } } } } }) Puppet[:environmentpath] = envdir end def init_cli_args_and_apply_app(args, execute) Puppet.initialize_settings(args) puppet = Puppet::Application.find(:apply).new(stub('command_line', :subcommand_name => :apply, :args => args)) puppet.options[:code] = execute return puppet end context "given the --modulepath option" do let(:args) { ['-e', execute, '--modulepath', modulepath] } it "looks in --modulepath even when the default directory environment exists" do apply = init_cli_args_and_apply_app(args, execute) expect do expect { apply.run }.to exit_with(0) end.to have_printed('amod class included') end it "looks in --modulepath even when given a specific directory --environment" do args << '--environment' << 'production' apply = init_cli_args_and_apply_app(args, execute) expect do expect { apply.run }.to exit_with(0) end.to have_printed('amod class included') end it "looks in --modulepath when given multiple paths in --modulepath" do args = ['-e', execute, '--modulepath', [tmpdir('notmodulepath'), modulepath].join(File::PATH_SEPARATOR)] apply = init_cli_args_and_apply_app(args, execute) expect do expect { apply.run }.to exit_with(0) end.to have_printed('amod class included') end end # When executing an ENC script, output cannot be captured using # expect { }.to have_printed(...) # External node script execution will fail, likely due to the tampering # with the basic file descriptors. # Workaround: Define a log destination and merely inspect logs. context "with an ENC" do let(:logdest) { tmpfile('logdest') } let(:args) { ['-e', execute, '--logdest', logdest ] } let(:enc) do script_containing('enc_script', :windows => '@echo environment: spec', :posix => '#!/bin/sh' + "\n" + 'echo "environment: spec"') end before :each do Puppet[:node_terminus] = 'exec' Puppet[:external_nodes] = enc end it "should use the environment that the ENC mandates" do apply = init_cli_args_and_apply_app(args, execute) expect { apply.run }.to exit_with(0) expect(@logs.map(&:to_s)).to include('amod class included') end it "should prefer the ENC environment over the configured one and emit a warning" do apply = init_cli_args_and_apply_app(args + [ '--environment', 'production' ], execute) expect { apply.run }.to exit_with(0) logs = @logs.map(&:to_s) expect(logs).to include('amod class included') expect(logs).to include(match(/doesn't match server specified environment/)) end end end context 'when compiling a provided catalog with rich data and then applying from file' do include PuppetSpec::Compiler let(:env_dir) { tmpdir('environments') } let(:execute) { 'include amod' } let(:rich_data) { false } let(:env_name) { 'spec' } let(:populated_env_dir) do dir_contained_in(env_dir, { env_name => { 'modules' => { 'amod' => { 'manifests' => { 'init.pp' => <<-EOF class amod { notify { rx: message => /[Rr]eg[Ee]xp/ } notify { bin: message => Binary('w5ZzdGVuIG1lZCByw7ZzdGVuCg==') } notify { ver: message => SemVer('2.3.1') } notify { vrange: message => SemVerRange('>=2.3.0') } notify { tspan: message => Timespan(3600) } notify { tstamp: message => Timestamp('2012-03-04T18:15:11.001') } } class amod::bad_type { notify { bogus: message => amod::bogus() } } EOF }, 'lib' => { 'puppet' => { 'functions' => { 'amod' => { 'bogus.rb' => <<-RUBY # Function that leaks an object that is not recognized in the catalog Puppet::Functions.create_function(:'amod::bogus') do def bogus() Time.new(2016, 10, 6, 23, 51, 14, '+02:00') end end RUBY } } } } } } } }) env_dir end let(:env) { Puppet::Node::Environment.create(env_name.to_sym, [File.join(populated_env_dir, 'spec', 'modules')]) } let(:node) { Puppet::Node.new('test', :environment => env) } around(:each) do |example| Puppet[:rich_data] = rich_data Puppet.override(:loaders => Puppet::Pops::Loaders.new(env)) { example.run } end context 'and rich_data is set to false during compile' do it 'will notify a string that is the result of Regexp#inspect (from Runtime3xConverter)' do catalog = compile_to_catalog(execute, node) apply = Puppet::Application[:apply] apply.options[:catalog] = file_containing('manifest', catalog.to_json) apply.expects(:apply_catalog).with do |cat| cat.resource(:notify, 'rx')['message'].is_a?(String) cat.resource(:notify, 'bin')['message'].is_a?(String) cat.resource(:notify, 'ver')['message'].is_a?(String) cat.resource(:notify, 'vrange')['message'].is_a?(String) cat.resource(:notify, 'tspan')['message'].is_a?(String) cat.resource(:notify, 'tstamp')['message'].is_a?(String) end apply.run end it 'will notify a string that is the result of to_s on uknown data types' do json = compile_to_catalog('include amod::bad_type', node).to_json apply = Puppet::Application[:apply] apply.options[:catalog] = file_containing('manifest', json) apply.expects(:apply_catalog).with do |catalog| catalog.resource(:notify, 'bogus')['message'].is_a?(String) end apply.run end it 'will log a warning that a value of unknown type is converted into a string' do logs = [] Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do compile_to_catalog('include amod::bad_type', node).to_json end logs = logs.select { |log| log.level == :warning }.map { |log| log.message } expect(logs.empty?).to be_falsey expect(logs[0]).to eql("Notify[bogus]['message'] contains a Time value. It will be converted to the String '2016-10-06 23:51:14 +0200'") end end context 'and rich_data is set to true during compile' do let(:rich_data) { true } it 'will notify a regexp using Regexp#to_s' do catalog = compile_to_catalog(execute, node) apply = Puppet::Application[:apply] apply.options[:catalog] = file_containing('manifest', catalog.to_json) apply.expects(:apply_catalog).with do |cat| cat.resource(:notify, 'rx')['message'].is_a?(Regexp) cat.resource(:notify, 'bin')['message'].is_a?(Puppet::Pops::Types::PBinaryType::Binary) cat.resource(:notify, 'ver')['message'].is_a?(SemanticPuppet::Version) cat.resource(:notify, 'vrange')['message'].is_a?(SemanticPuppet::VersionRange) cat.resource(:notify, 'tspan')['message'].is_a?(Puppet::Pops::Time::Timespan) cat.resource(:notify, 'tstamp')['message'].is_a?(Puppet::Pops::Time::Timestamp) end apply.run end end end end puppet-5.5.10/spec/integration/directory_environments_spec.rb0000644005276200011600000000443113417161721024443 0ustar jenkinsjenkinsrequire 'spec_helper' describe "directory environments" do let(:args) { ['--configprint', 'modulepath', '--environment', 'direnv'] } let(:puppet) do app = Puppet::Application[:apply] app.stubs(:command_line).returns(stub('command_line', :args => [])) app end context "with a single directory environmentpath" do before(:each) do environmentdir = PuppetSpec::Files.tmpdir('envpath') Puppet[:environmentpath] = environmentdir FileUtils.mkdir_p(environmentdir + "/direnv/modules") end it "config prints the environments modulepath" do Puppet.settings.initialize_global_settings(args) expect do expect { puppet.run }.to exit_with(0) end.to have_printed('/direnv/modules') end it "config prints the cli --modulepath despite environment" do args << '--modulepath' << '/completely/different' Puppet.settings.initialize_global_settings(args) expect do expect { puppet.run }.to exit_with(0) end.to have_printed('/completely/different') end it 'given an 8.3 style path on Windows, will config print an expanded path', :if => Puppet::Util::Platform.windows? do # ensure an 8.3 style path is set for environmentpath shortened = Puppet::Util::Windows::File.get_short_pathname(Puppet[:environmentpath]) expanded = Puppet::FileSystem.expand_path(shortened) Puppet[:environmentpath] = shortened expect(Puppet[:environmentpath]).to match(/~/) Puppet.settings.initialize_global_settings(args) expect do expect { puppet.run }.to exit_with(0) end.to have_printed(expanded) end end context "with an environmentpath having multiple directories" do let(:args) { ['--configprint', 'modulepath', '--environment', 'otherdirenv'] } before(:each) do envdir1 = File.join(Puppet[:confdir], 'env1') envdir2 = File.join(Puppet[:confdir], 'env2') Puppet[:environmentpath] = [envdir1, envdir2].join(File::PATH_SEPARATOR) FileUtils.mkdir_p(envdir2 + "/otherdirenv/modules") end it "config prints a directory environment modulepath" do Puppet.settings.initialize_global_settings(args) expect do expect { puppet.run }.to exit_with(0) end.to have_printed('otherdirenv/modules') end end end puppet-5.5.10/spec/integration/environments/0000755005276200011600000000000013417162176021023 5ustar jenkinsjenkinspuppet-5.5.10/spec/integration/environments/default_manifest_spec.rb0000644005276200011600000001753713417161721025704 0ustar jenkinsjenkinsrequire 'spec_helper' module EnvironmentsDefaultManifestsSpec describe "default manifests" do context "puppet with default_manifest settings" do let(:confdir) { Puppet[:confdir] } let(:environmentpath) { File.expand_path("envdir", confdir) } context "relative default" do let(:testingdir) { File.join(environmentpath, "testing") } before(:each) do FileUtils.mkdir_p(testingdir) end it "reads manifest from ./manifest of a basic directory environment" do manifestsdir = File.join(testingdir, "manifests") FileUtils.mkdir_p(manifestsdir) File.open(File.join(manifestsdir, "site.pp"), "w", :encoding => Encoding::UTF_8) do |f| f.puts("notify { 'ManifestFromRelativeDefault': }") end File.open(File.join(confdir, "puppet.conf"), "w", :encoding => Encoding::UTF_8) do |f| f.puts("environmentpath=#{environmentpath}") end expect(a_catalog_compiled_for_environment('testing')).to( include_resource('Notify[ManifestFromRelativeDefault]') ) end end context "set absolute" do let(:testingdir) { File.join(environmentpath, "testing") } before(:each) do FileUtils.mkdir_p(testingdir) end it "reads manifest from an absolute default_manifest" do manifestsdir = File.expand_path("manifests", confdir) FileUtils.mkdir_p(manifestsdir) File.open(File.join(confdir, "puppet.conf"), "w", :encoding => Encoding::UTF_8) do |f| f.puts(<<-EOF) environmentpath=#{environmentpath} default_manifest=#{manifestsdir} EOF end File.open(File.join(manifestsdir, "site.pp"), "w", :encoding => Encoding::UTF_8) do |f| f.puts("notify { 'ManifestFromAbsoluteDefaultManifest': }") end expect(a_catalog_compiled_for_environment('testing')).to( include_resource('Notify[ManifestFromAbsoluteDefaultManifest]') ) end it "reads manifest from directory environment manifest when environment.conf manifest set" do default_manifestsdir = File.expand_path("manifests", confdir) File.open(File.join(confdir, "puppet.conf"), "w", :encoding => Encoding::UTF_8) do |f| f.puts(<<-EOF) environmentpath=#{environmentpath} default_manifest=#{default_manifestsdir} EOF end manifestsdir = File.join(testingdir, "special_manifests") FileUtils.mkdir_p(manifestsdir) File.open(File.join(manifestsdir, "site.pp"), "w", :encoding => Encoding::UTF_8) do |f| f.puts("notify { 'ManifestFromEnvironmentConfManifest': }") end File.open(File.join(testingdir, "environment.conf"), "w", :encoding => Encoding::UTF_8) do |f| f.puts("manifest=./special_manifests") end expect(a_catalog_compiled_for_environment('testing')).to( include_resource('Notify[ManifestFromEnvironmentConfManifest]') ) expect(Puppet[:default_manifest]).to eq(default_manifestsdir) end it "ignores manifests in the local ./manifests if default_manifest specifies another directory" do default_manifestsdir = File.expand_path("manifests", confdir) FileUtils.mkdir_p(default_manifestsdir) File.open(File.join(confdir, "puppet.conf"), "w", :encoding => Encoding::UTF_8) do |f| f.puts(<<-EOF) environmentpath=#{environmentpath} default_manifest=#{default_manifestsdir} EOF end File.open(File.join(default_manifestsdir, "site.pp"), "w", :encoding => Encoding::UTF_8) do |f| f.puts("notify { 'ManifestFromAbsoluteDefaultManifest': }") end implicit_manifestsdir = File.join(testingdir, "manifests") FileUtils.mkdir_p(implicit_manifestsdir) File.open(File.join(implicit_manifestsdir, "site.pp"), "w", :encoding => Encoding::UTF_8) do |f| f.puts("notify { 'ManifestFromImplicitRelativeEnvironmentManifestDirectory': }") end expect(a_catalog_compiled_for_environment('testing')).to( include_resource('Notify[ManifestFromAbsoluteDefaultManifest]') ) end end context "with disable_per_environment_manifest true" do let(:manifestsdir) { File.expand_path("manifests", confdir) } let(:testingdir) { File.join(environmentpath, "testing") } before(:each) do FileUtils.mkdir_p(testingdir) end before(:each) do FileUtils.mkdir_p(manifestsdir) File.open(File.join(confdir, "puppet.conf"), "w", :encoding => Encoding::UTF_8) do |f| f.puts(<<-EOF) environmentpath=#{environmentpath} default_manifest=#{manifestsdir} disable_per_environment_manifest=true EOF end File.open(File.join(manifestsdir, "site.pp"), "w", :encoding => Encoding::UTF_8) do |f| f.puts("notify { 'ManifestFromAbsoluteDefaultManifest': }") end end it "reads manifest from the default manifest setting" do expect(a_catalog_compiled_for_environment('testing')).to( include_resource('Notify[ManifestFromAbsoluteDefaultManifest]') ) end it "refuses to compile if environment.conf specifies a different manifest" do File.open(File.join(testingdir, "environment.conf"), "w", :encoding => Encoding::UTF_8) do |f| f.puts("manifest=./special_manifests") end expect { a_catalog_compiled_for_environment('testing') }.to( raise_error(Puppet::Error, /disable_per_environment_manifest.*environment.conf.*manifest.*conflict/) ) end it "reads manifest from default_manifest setting when environment.conf has manifest set if setting equals default_manifest setting" do File.open(File.join(testingdir, "environment.conf"), "w", :encoding => Encoding::UTF_8) do |f| f.puts("manifest=#{manifestsdir}") end expect(a_catalog_compiled_for_environment('testing')).to( include_resource('Notify[ManifestFromAbsoluteDefaultManifest]') ) end it "logs errors if environment.conf specifies a different manifest" do File.open(File.join(testingdir, "environment.conf"), "w", :encoding => Encoding::UTF_8) do |f| f.puts("manifest=./special_manifests") end Puppet.initialize_settings expect(Puppet[:environmentpath]).to eq(environmentpath) environment = Puppet.lookup(:environments).get('testing') expect(environment.manifest).to eq(manifestsdir) expect(@logs.first.to_s).to match(%r{disable_per_environment_manifest.*is true, but.*environment.*at #{testingdir}.*has.*environment.conf.*manifest.*#{testingdir}/special_manifests}) end it "raises an error if default_manifest is not absolute" do File.open(File.join(confdir, "puppet.conf"), "w", :encoding => Encoding::UTF_8) do |f| f.puts(<<-EOF) environmentpath=#{environmentpath} default_manifest=./relative disable_per_environment_manifest=true EOF end expect { Puppet.initialize_settings }.to raise_error(Puppet::Settings::ValidationError, /default_manifest.*must be.*absolute.*when.*disable_per_environment_manifest.*true/) end end end RSpec::Matchers.define :include_resource do |expected| match do |actual| actual.resources.map(&:ref).include?(expected) end def failure_message "expected #{@actual.resources.map(&:ref)} to include #{expected}" end def failure_message_when_negated "expected #{@actual.resources.map(&:ref)} not to include #{expected}" end end def a_catalog_compiled_for_environment(envname) Puppet.initialize_settings expect(Puppet[:environmentpath]).to eq(environmentpath) node = Puppet::Node.new('testnode', :environment => 'testing') expect(node.environment).to eq(Puppet.lookup(:environments).get('testing')) Puppet::Parser::Compiler.compile(node) end end end puppet-5.5.10/spec/integration/environments/setting_hooks_spec.rb0000644005276200011600000000162013417161721025234 0ustar jenkinsjenkinsrequire 'spec_helper' describe "setting hooks" do let(:confdir) { Puppet[:confdir] } let(:environmentpath) { File.expand_path("envdir", confdir) } describe "reproducing PUP-3500" do let(:productiondir) { File.join(environmentpath, "production") } before(:each) do FileUtils.mkdir_p(productiondir) end it "accesses correct directory environment settings after initializing a setting with an on_write hook" do expect(Puppet.settings.setting(:certname).call_hook).to eq(:on_write_only) File.open(File.join(confdir, "puppet.conf"), "w:UTF-8") do |f| f.puts("environmentpath=#{environmentpath}") f.puts("certname=something") end Puppet.initialize_settings production_env = Puppet.lookup(:environments).get(:production) expect(Puppet.settings.value(:manifest, production_env)).to eq("#{productiondir}/manifests") end end end puppet-5.5.10/spec/integration/environments/settings_interpolation_spec.rb0000644005276200011600000000711513417161721027170 0ustar jenkinsjenkinsrequire 'pp' require 'spec_helper' require 'puppet_spec/settings' module SettingsInterpolationSpec describe "interpolating $environment" do include PuppetSpec::Settings let(:confdir) { Puppet[:confdir] } let(:cmdline_args) { ['--confdir', confdir, '--vardir', Puppet[:vardir], '--hiera_config', Puppet[:hiera_config]] } before(:each) do FileUtils.mkdir_p(confdir) end shared_examples_for "a setting that does not interpolate $environment" do before(:each) do set_puppet_conf(confdir, <<-EOF) environmentpath=$confdir/environments #{setting}=#{value} EOF end it "does not interpolate $environment" do Puppet.initialize_settings(cmdline_args) expect(Puppet[:environmentpath]).to eq("#{confdir}/environments") expect(Puppet[setting.intern]).to eq(expected) end it "displays the interpolated value in the warning" do Puppet.initialize_settings(cmdline_args) Puppet[setting.intern] expect(@logs).to have_matching_log(/cannot interpolate \$environment within '#{setting}'.*Its value will remain #{Regexp.escape(expected)}/) end end describe "config_version" do it "interpolates $environment" do envname = 'testing' setting = 'config_version' value = '/some/script $environment' expected = "#{File.expand_path('/some/script')} testing" set_puppet_conf(confdir, <<-EOF) environmentpath=$confdir/environments environment=#{envname} EOF set_environment_conf("#{confdir}/environments", envname, <<-EOF) #{setting}=#{value} EOF Puppet.initialize_settings(cmdline_args) expect(Puppet[:environmentpath]).to eq("#{confdir}/environments") environment = Puppet.lookup(:environments).get(envname) expect(environment.config_version).to eq(expected) expect(@logs).to be_empty end end describe "basemodulepath" do let(:setting) { "basemodulepath" } let(:value) { "$confdir/environments/$environment/modules:$confdir/environments/$environment/other_modules" } let(:expected) { "#{confdir}/environments/$environment/modules:#{confdir}/environments/$environment/other_modules" } it_behaves_like "a setting that does not interpolate $environment" it "logs a single warning for multiple instaces of $environment in the setting" do set_puppet_conf(confdir, <<-EOF) environmentpath=$confdir/environments #{setting}=#{value} EOF Puppet.initialize_settings(cmdline_args) expect(@logs.map(&:to_s).grep(/cannot interpolate \$environment within '#{setting}'/).count).to eq(1) end end describe "environment" do let(:setting) { "environment" } let(:value) { "whatareyouthinking$environment" } let(:expected) { value } it_behaves_like "a setting that does not interpolate $environment" end describe "the default_manifest" do let(:setting) { "default_manifest" } let(:value) { "$confdir/manifests/$environment" } let(:expected) { "#{confdir}/manifests/$environment" } it_behaves_like "a setting that does not interpolate $environment" end it "does not interpolate $environment and logs a warning when interpolating environmentpath" do setting = 'environmentpath' value = "$confdir/environments/$environment" expected = "#{confdir}/environments/$environment" set_puppet_conf(confdir, <<-EOF) #{setting}=#{value} EOF Puppet.initialize_settings(cmdline_args) expect(Puppet[setting.intern]).to eq(expected) expect(@logs).to have_matching_log(/cannot interpolate \$environment within '#{setting}'/) end end end puppet-5.5.10/spec/integration/environments/settings_spec.rb0000644005276200011600000001140113417161721024212 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/settings' describe "environment settings" do include PuppetSpec::Settings let(:confdir) { Puppet[:confdir] } let(:cmdline_args) { ['--confdir', confdir, '--vardir', Puppet[:vardir], '--hiera_config', Puppet[:hiera_config]] } let(:environmentpath) { File.expand_path("envdir", confdir) } let(:testingdir) { File.join(environmentpath, "testing") } before(:each) do FileUtils.mkdir_p(testingdir) end def init_puppet_conf(settings = {}) set_puppet_conf(confdir, <<-EOF) environmentpath=#{environmentpath} #{settings.map { |k,v| "#{k}=#{v}" }.join("\n")} EOF Puppet.initialize_settings end it "raises an error if you set manifest in puppet.conf" do expect { init_puppet_conf("manifest" => "/something") }.to raise_error(Puppet::Settings::SettingsError, /Cannot set manifest.*in puppet.conf/) end it "raises an error if you set modulepath in puppet.conf" do expect { init_puppet_conf("modulepath" => "/something") }.to raise_error(Puppet::Settings::SettingsError, /Cannot set modulepath.*in puppet.conf/) end it "raises an error if you set config_version in puppet.conf" do expect { init_puppet_conf("config_version" => "/something") }.to raise_error(Puppet::Settings::SettingsError, /Cannot set config_version.*in puppet.conf/) end context "when given an environment" do before(:each) do init_puppet_conf end context "without an environment.conf" do it "reads manifest from environment.conf defaults" do expect(Puppet.settings.value(:manifest, :testing)).to eq(File.join(testingdir, "manifests")) end it "reads modulepath from environment.conf defaults" do expect(Puppet.settings.value(:modulepath, :testing)).to match(/#{File.join(testingdir, "modules")}/) end it "reads config_version from environment.conf defaults" do expect(Puppet.settings.value(:config_version, :testing)).to eq('') end end context "with an environment.conf" do before(:each) do set_environment_conf(environmentpath, 'testing', <<-EOF) manifest=/special/manifest modulepath=/special/modulepath config_version=/special/config_version EOF end it "reads the configured manifest" do expect(Puppet.settings.value(:manifest, :testing)).to eq(Puppet::FileSystem.expand_path('/special/manifest')) end it "reads the configured modulepath" do expect(Puppet.settings.value(:modulepath, :testing)).to eq(Puppet::FileSystem.expand_path('/special/modulepath')) end it "reads the configured config_version" do expect(Puppet.settings.value(:config_version, :testing)).to eq(Puppet::FileSystem.expand_path('/special/config_version')) end end context "with an environment.conf containing 8.3 style Windows paths", :if => Puppet::Util::Platform.windows? do before(:each) do # set 8.3 style Windows paths @modulepath = Puppet::Util::Windows::File.get_short_pathname(PuppetSpec::Files.tmpdir('fakemodulepath')) # for expansion to work, the file must actually exist @manifest = PuppetSpec::Files.tmpfile('foo.pp', @modulepath) # but tmpfile won't create an empty file Puppet::FileSystem.touch(@manifest) @manifest = Puppet::Util::Windows::File.get_short_pathname(@manifest) set_environment_conf(environmentpath, 'testing', <<-EOF) manifest=#{@manifest} modulepath=#{@modulepath} EOF end it "reads the configured manifest as a fully expanded path" do expect(Puppet.settings.value(:manifest, :testing)).to eq(Puppet::FileSystem.expand_path(@manifest)) end it "reads the configured modulepath as a fully expanded path" do expect(Puppet.settings.value(:modulepath, :testing)).to eq(Puppet::FileSystem.expand_path(@modulepath)) end end context "when environment name collides with a puppet.conf section" do let(:testingdir) { File.join(environmentpath, "main") } it "reads manifest from environment.conf defaults" do expect(Puppet.settings.value(:environmentpath)).to eq(environmentpath) expect(Puppet.settings.value(:manifest, :main)).to eq(File.join(testingdir, "manifests")) end context "and an environment.conf" do before(:each) do set_environment_conf(environmentpath, 'main', <<-EOF) manifest=/special/manifest EOF end it "reads manifest from environment.conf settings" do expect(Puppet.settings.value(:environmentpath)).to eq(environmentpath) expect(Puppet.settings.value(:manifest, :main)).to eq(Puppet::FileSystem.expand_path("/special/manifest")) end end end end end puppet-5.5.10/spec/integration/faces/0000755005276200011600000000000013417162176017355 5ustar jenkinsjenkinspuppet-5.5.10/spec/integration/faces/config_spec.rb0000644005276200011600000000514213417161721022156 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/face' describe Puppet::Face[:config, '0.0.1'] do include PuppetSpec::Files # different UTF-8 widths # 1-byte A # 2-byte Űż - http://www.fileformat.info/info/unicode/char/06ff/index.htm - 0xDB 0xBF / 219 191 # 3-byte áš  - http://www.fileformat.info/info/unicode/char/16A0/index.htm - 0xE1 0x9A 0xA0 / 225 154 160 # 4-byte 𠜎 - http://www.fileformat.info/info/unicode/char/2070E/index.htm - 0xF0 0xA0 0x9C 0x8E / 240 160 156 142 MIXED_UTF8 = "A\u06FF\u16A0\u{2070E}" # Aۿᚠ𠜎 let (:tmp_environment_path) { tmpdir('envpath') } let (:tmp_config) { tmpfile('puppet.conf') } def load_settings(path) test_settings = Puppet::Settings.new test_settings.define_settings(:main, :config => { :type => :file, :default => path, :desc => '', }, :environmentpath => { :default => tmp_environment_path, :desc => '', }, :basemodulepath => { :default => '', :desc => '', }, # definition required to use the value :rando_key => { :default => '', :desc => '' }, MIXED_UTF8.to_sym => { :default => '', :desc => '' }, ) test_settings.initialize_global_settings test_settings end before :each do File.open(tmp_config, 'w', :encoding => 'UTF-8') do |file| file.puts <<-EOF [main] rando_key=foobar #{MIXED_UTF8}=foobar EOF end end context 'when getting / setting UTF8 values' do let(:config) { described_class } def render(action, result) config.get_action(action).when_rendering(:console).call(result) end before :each do subject.stubs(:report_section_and_environment) end # key must be a defined setting ['rando_key', MIXED_UTF8].each do |key| it "can change '#{key}' keyed ASCII value to a UTF-8 value and read it back" do value = "value#{key.reverse}value" # needed for the subject.set to write to correct file Puppet.settings.stubs(:which_configuration_file).returns(tmp_config) subject.set(key, value) # make sure subject.print looks at the newly modified settings test_settings = load_settings(tmp_config) # instead of the default Puppet.settings (implementation detail) Puppet.stubs(:settings).returns(test_settings) result = subject.print() expect(render(:print, result)).to match(/^#{key} = #{value}$/) result = subject.print(key, :section => 'main') expect(render(:print, result)).to eq("#{value}\n") end end end end puppet-5.5.10/spec/integration/faces/documentation_spec.rb0000644005276200011600000000426113417161721023563 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/face' describe "documentation of faces" do it "should generate global help" do help = nil expect { help = Puppet::Face[:help, :current].help }.not_to raise_error expect(help).to be_an_instance_of String expect(help.length).to be > 200 end ######################################################################## # Can we actually generate documentation for the face, and the actions it # has? This avoids situations where the ERB template turns out to have a # bug in it, triggered in something the user might do. context "face help messages" do Puppet::Face.faces.sort.each do |face_name| # REVISIT: We should walk all versions of the face here... let :help do Puppet::Face[:help, :current] end context "generating help" do it "for #{face_name}" do expect { text = help.help(face_name) expect(text).to be_an_instance_of String expect(text.length).to be > 100 }.not_to raise_error end Puppet::Face[face_name, :current].actions.sort.each do |action_name| it "for #{face_name}.#{action_name}" do expect { text = help.help(face_name, action_name) expect(text).to be_an_instance_of String expect(text.length).to be > 100 }.not_to raise_error end end end ######################################################################## # Ensure that we have authorship and copyright information in *our* faces; # if you apply this to third party faces you might well be disappointed. context "licensing of Puppet Inc. face '#{face_name}'" do subject { Puppet::Face[face_name, :current] } its :license do should =~ /Apache\s*2/ end its :copyright do should =~ /Puppet Inc\./ end # REVISIT: This is less that ideal, I think, but right now I am more # comfortable watching us ship with some copyright than without any; we # can redress that when it becomes appropriate. --daniel 2011-04-27 its :copyright do should =~ /20\d{2}/ end end end end end puppet-5.5.10/spec/integration/faces/ca_spec.rb0000644005276200011600000002614313417161722021301 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/face' describe Puppet::Face[:ca, '0.1.0'], :unless => Puppet.features.microsoft_windows? do include PuppetSpec::Files before :each do Puppet.run_mode.stubs(:master?).returns(true) Puppet[:ca] = true Puppet[:ssldir] = tmpdir("face-ca-ssldir") Puppet::SSL::Host.ca_location = :only Puppet[:certificate_revocation] = true # This is way more intimate than I want to be with the implementation, but # there doesn't seem any other way to test this. --daniel 2011-07-18 Puppet::SSL::CertificateAuthority.stubs(:instance).returns( # ...and this actually does the directory creation, etc. Puppet::SSL::CertificateAuthority.new ) end def given_certificate_requests_for(*names) names.each do |name| Puppet::SSL::Host.new(name).generate_certificate_request end end def given_certificates_for(*names) names.each do |name| Puppet::SSL::Host.new(name).generate end end context "#verify" do it "should report if there is no certificate" do expect(subject.verify('random-host')).to eq({ :host => 'random-host', :valid => false, :error => 'Could not find a certificate for random-host' }) end it "should report that it cannot find a certificate when there is only a request" do given_certificate_requests_for('random-host') expect(subject.verify('random-host')).to eq({ :host => 'random-host', :valid => false, :error => 'Could not find a certificate for random-host' }) end it "should verify a signed certificate" do given_certificates_for('random-host') expect(subject.verify('random-host')).to eq({ :host => 'random-host', :valid => true }) end it "should not verify a revoked certificate" do given_certificates_for('random-host') subject.revoke('random-host') expect(subject.verify('random-host')).to eq({ :host => 'random-host', :valid => false, :error => 'certificate revoked' }) end it "should verify a revoked certificate if CRL use was turned off" do given_certificates_for('random-host') subject.revoke('random-host') Puppet[:certificate_revocation] = false expect(subject.verify('random-host')).to eq({ :host => 'random-host', :valid => true }) end end context "#fingerprint" do let(:fingerprint_re) { /^\([0-9A-Z]+\) [0-9A-F:]+$/ } it "should be nil if there is no certificate" do expect(subject.fingerprint('random-host')).to be_nil end it "should fingerprint a CSR" do given_certificate_requests_for('random-host') expect(subject.fingerprint('random-host')).to match(fingerprint_re) end it "should fingerprint a certificate" do given_certificates_for('random-host') expect(subject.fingerprint('random-host')).to match(fingerprint_re) end %w{md5 MD5 sha1 SHA1 RIPEMD160 sha256 sha512}.each do |digest| it "should fingerprint with #{digest.inspect}" do given_certificates_for('random-host') expect(subject.fingerprint('random-host', :digest => digest)).to match(fingerprint_re) end end end context "#print" do it "should be nil if there is no certificate" do expect(subject.print('random-host')).to be_nil end it "should return nothing if there is only a CSR" do given_certificate_requests_for('random-host') expect(subject.print('random-host')).to be_nil end it "should return the certificate content if there is a cert" do given_certificates_for('random-host') text = subject.print('random-host') expect(text).to be_an_instance_of String expect(text).to match(/^Certificate:/) expect(text).to match(/Issuer: CN=Puppet CA: /) expect(text).to match(/Subject: CN=random-host$/) end end context "#sign" do it "should report that there is no CSR" do expect(subject.sign('random-host')).to eq('Could not find certificate request for random-host') end it "should report that there is no CSR when even when there is a certificate" do given_certificates_for('random-host') expect(subject.sign('random-host')).to eq('Could not find certificate request for random-host') end it "should sign a CSR if one exists" do given_certificate_requests_for('random-host') expect(subject.sign('random-host')).to be_an_instance_of Puppet::SSL::Certificate list = subject.list(:signed => true) expect(list.length).to eq(1) expect(list.first.name).to eq('random-host') end describe "when the CSR specifies DNS alt names" do let(:host) { Puppet::SSL::Host.new('someone') } before :each do host.generate_certificate_request(:dns_alt_names => 'some,alt,names') end it "should sign the CSR if DNS alt names are allowed" do subject.sign('someone', :allow_dns_alt_names => true) expect(host.certificate).to be_a(Puppet::SSL::Certificate) end it "should refuse to sign the CSR if DNS alt names are not allowed" do certname = 'someone' expect do subject.sign(certname) end.to raise_error(Puppet::SSL::CertificateAuthority::CertificateSigningError, /CSR '#{certname}' contains subject alternative names \(.*\), which are disallowed. Use `puppet cert --allow-dns-alt-names sign #{certname}` to sign this request./i) expect(host.certificate).to be_nil end end end context "#generate" do it "should generate a certificate if requested" do expect(subject.list(:all => true)).to eq([]) subject.generate('random-host') list = subject.list(:signed => true) expect(list.length).to eq(1) expect(list.first.name).to eq('random-host') end it "should report if a CSR with that name already exists" do given_certificate_requests_for('random-host') expect(subject.generate('random-host')).to match(/already has a certificate request/) end it "should report if the certificate with that name already exists" do given_certificates_for('random-host') expect(subject.generate('random-host')).to match(/already has a certificate/) end it "should include the specified DNS alt names" do subject.generate('some-host', :dns_alt_names => 'some,alt,names') host = subject.list(:signed => true).first expect(host.name).to eq('some-host') expect(host.certificate.subject_alt_names).to match_array(%w[DNS:some DNS:alt DNS:names DNS:some-host]) expect(subject.list(:pending => true)).to be_empty end end context "#revoke" do it "should let the user know what went wrong when there is nothing to revoke" do expect(subject.revoke('nonesuch')).to eq('Nothing was revoked') end it "should revoke a certificate" do given_certificates_for('random-host') subject.revoke('random-host') found = subject.list(:all => true, :subject => 'random-host') expect(subject.get_action(:list).when_rendering(:console).call(found, {})). to match(/^- random-host \(\w+\) [:0-9A-F]+ \(certificate revoked\)/) end end context "#destroy" do it "should let the user know if nothing was deleted" do expect(subject.destroy('nonesuch')).to eq("Nothing was deleted") end it "should destroy a CSR, if we have one" do given_certificate_requests_for('random-host') subject.destroy('random-host') expect(subject.list(:pending => true, :subject => 'random-host')).to eq([]) end it "should destroy a certificate, if we have one" do given_certificates_for('random-host') subject.destroy('random-host') expect(subject.list(:signed => true, :subject => 'random-host')).to eq([]) end it "should tell the user something was deleted" do given_certificates_for('random-host') expect(subject.list(:signed => true, :subject => 'random-host')).not_to eq([]) expect(subject.destroy('random-host')). to eq("Deleted for random-host: Puppet::SSL::Certificate") end end context "#list" do context "with no hosts in CA" do [ {}, { :pending => true }, { :signed => true }, { :all => true }, ].each do |type| it "should return nothing for #{type.inspect}" do expect(subject.list(type)).to eq([]) end it "should return nothing when a matcher is passed" do expect(subject.list(type.merge :subject => '.')).to eq([]) end context "when_rendering :console" do it "should return nothing for #{type.inspect}" do expect(subject.get_action(:list).when_rendering(:console).call(subject.list(type), {})).to eq("") end end end end context "with some hosts" do csr_names = (1..3).map {|n| "csr-#{n}" } crt_names = (1..3).map {|n| "crt-#{n}" } all_names = csr_names + crt_names { {} => csr_names, { :pending => true } => csr_names, { :signed => true } => crt_names, { :all => true } => all_names, { :pending => true, :signed => true } => all_names, }.each do |input, expect| it "should map #{input.inspect} to #{expect.inspect}" do given_certificate_requests_for(*csr_names) given_certificates_for(*crt_names) expect(subject.list(input).map(&:name)).to match_array(expect) end ['', '.', '2', 'none'].each do |pattern| filtered = expect.select {|x| Regexp.new(pattern).match(x) } it "should filter all hosts matching #{pattern.inspect} to #{filtered.inspect}" do given_certificate_requests_for(*csr_names) given_certificates_for(*crt_names) expect(subject.list(input.merge :subject => pattern).map(&:name)).to match_array(filtered) end end end context "when_rendering :console" do { [["csr1.local"], []] => [/^ csr1.local /], [[], ["crt1.local"]] => [/^\+ crt1.local /], [["csr2"], ["crt2"]] => [/^ csr2 /, /^\+ crt2 /] }.each do |input, pattern| it "should render #{input.inspect} to match #{pattern.inspect}" do given_certificate_requests_for(*input[0]) given_certificates_for(*input[1]) text = subject.get_action(:list).when_rendering(:console).call(subject.list(:all => true), {}) pattern.each do |item| expect(text).to match(item) end end end end end end actions = %w{destroy list revoke generate sign print verify fingerprint} actions.each do |action| it { is_expected.to be_action action } it "should fail #{action} when not a CA" do Puppet[:ca] = false expect { case subject.method(action).arity when -1 then subject.send(action) when -2 then subject.send(action, 'dummy') else raise "#{action} has arity #{subject.method(action).arity}" end }.to raise_error(/Not a CA/) end end end puppet-5.5.10/spec/integration/faces/plugin_spec.rb0000644005276200011600000000311413417161722022205 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/face' require 'puppet/file_serving/metadata' require 'puppet/file_serving/content' require 'puppet/indirector/memory' module PuppetFaceIntegrationSpecs describe "Puppet plugin face" do INDIRECTORS = [ Puppet::Indirector::FileMetadata, Puppet::Indirector::FileContent, ] INDIRECTED_CLASSES = [ Puppet::FileServing::Metadata, Puppet::FileServing::Content, Puppet::Node::Facts, ] INDIRECTORS.each do |indirector| class indirector::Memory < Puppet::Indirector::Memory def find(request) model.new('/dev/null', { 'type' => 'directory' }) end end end before do FileUtils.mkdir(File.join(Puppet[:vardir], 'lib')) FileUtils.mkdir(File.join(Puppet[:vardir], 'facts.d')) FileUtils.mkdir(File.join(Puppet[:vardir], 'locales')) @termini_classes = {} INDIRECTED_CLASSES.each do |indirected| @termini_classes[indirected] = indirected.indirection.terminus_class indirected.indirection.terminus_class = :memory end end after do INDIRECTED_CLASSES.each do |indirected| indirected.indirection.terminus_class = @termini_classes[indirected] indirected.indirection.termini.clear end end def init_cli_args_and_apply_app(args = ["download"]) Puppet::Application.find(:plugin).new(stub('command_line', :subcommand_name => :plugin, :args => args)) end it "processes a download request" do app = init_cli_args_and_apply_app expect do expect { app.run }.to exit_with(0) end.to have_printed(/No plugins downloaded/) end end end puppet-5.5.10/spec/integration/file_bucket/0000755005276200011600000000000013417162176020550 5ustar jenkinsjenkinspuppet-5.5.10/spec/integration/file_bucket/file_spec.rb0000644005276200011600000000533713417161722023032 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/file_bucket/file' describe Puppet::FileBucket::File do describe "#indirection" do before :each do # Never connect to the network, no matter what described_class.indirection.terminus(:rest).class.any_instance.stubs(:find) end describe "when running the master application" do before :each do Puppet::Application[:master].setup_terminuses end { "md5/d41d8cd98f00b204e9800998ecf8427e" => :file, "filebucket://puppetmaster:8140/md5/d41d8cd98f00b204e9800998ecf8427e" => :file, }.each do |key, terminus| it "should use the #{terminus} terminus when requesting #{key.inspect}" do described_class.indirection.terminus(terminus).class.any_instance.expects(:find) described_class.indirection.find(key) end end end describe "when running another application" do { "md5/d41d8cd98f00b204e9800998ecf8427e" => :file, "filebucket://puppetmaster:8140/md5/d41d8cd98f00b204e9800998ecf8427e" => :rest, }.each do |key, terminus| it "should use the #{terminus} terminus when requesting #{key.inspect}" do described_class.indirection.terminus(terminus).class.any_instance.expects(:find) described_class.indirection.find(key) end end end end describe "saving binary files" do context "given multiple backups of identical files" do it "does not error given content with binary external encoding" do binary = "\xD1\xF2\r\n\x81NuSc\x00".force_encoding(Encoding::ASCII_8BIT) bucket_file = Puppet::FileBucket::File.new(binary) Puppet::FileBucket::File.indirection.save(bucket_file, bucket_file.name) Puppet::FileBucket::File.indirection.save(bucket_file, bucket_file.name) end it "also does not error if the content is reported with UTF-8 external encoding" do # PUP-7951 - ensure accurate size comparison across encodings If binary # content arrives as a string with UTF-8 default external encoding, its # character count might be different than the same bytes with binary # external encoding. Ensure our equality comparison does not fail due to this. # As would be the case with our friend snowman: # Unicode snowman \u2603 - \xE2 \x98 \x83 # character size 1, if interpreted as UTF-8, 3 "characters" if interpreted as binary utf8 = "\u2603".force_encoding(Encoding::UTF_8) bucket_file = Puppet::FileBucket::File.new(utf8) Puppet::FileBucket::File.indirection.save(bucket_file, bucket_file.name) Puppet::FileBucket::File.indirection.save(bucket_file, bucket_file.name) end end end end puppet-5.5.10/spec/integration/file_serving/0000755005276200011600000000000013417162176020750 5ustar jenkinsjenkinspuppet-5.5.10/spec/integration/file_serving/content_spec.rb0000644005276200011600000000025713417161721023760 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/file_serving/content' describe Puppet::FileServing::Content do it_should_behave_like "a file_serving model" end puppet-5.5.10/spec/integration/file_serving/fileset_spec.rb0000644005276200011600000000052713417161721023741 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/file_serving/fileset' describe Puppet::FileServing::Fileset do it "should be able to recurse on a single file" do @path = Tempfile.new("fileset_integration") fileset = Puppet::FileServing::Fileset.new(@path.path) expect { fileset.files }.not_to raise_error end end puppet-5.5.10/spec/integration/file_serving/metadata_spec.rb0000644005276200011600000000026213417161721024062 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/file_serving/metadata' describe Puppet::FileServing::Metadata do it_should_behave_like "a file_serving model" end puppet-5.5.10/spec/integration/file_serving/terminus_helper_spec.rb0000644005276200011600000000115113417161721025505 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/file_serving/terminus_helper' class TerminusHelperIntegrationTester include Puppet::FileServing::TerminusHelper def model Puppet::FileServing::Metadata end end describe Puppet::FileServing::TerminusHelper do it "should be able to recurse on a single file" do @path = Tempfile.new("fileset_integration") request = Puppet::Indirector::Request.new(:metadata, :find, @path.path, nil, :recurse => true) tester = TerminusHelperIntegrationTester.new expect { tester.path2instances(request, @path.path) }.not_to raise_error end end puppet-5.5.10/spec/integration/file_system/0000755005276200011600000000000013417162176020617 5ustar jenkinsjenkinspuppet-5.5.10/spec/integration/file_system/uniquefile_spec.rb0000644005276200011600000000217713417161722024327 0ustar jenkinsjenkinsrequire 'spec_helper' describe Puppet::FileSystem::Uniquefile do describe "#open_tmp on Windows", :if => Puppet.features.microsoft_windows? do describe "with UTF8 characters" do include PuppetSpec::Files let(:rune_utf8) { "\u16A0\u16C7\u16BB\u16EB\u16D2\u16E6\u16A6\u16EB\u16A0\u16B1\u16A9\u16A0\u16A2\u16B1\u16EB\u16A0\u16C1\u16B1\u16AA\u16EB\u16B7\u16D6\u16BB\u16B9\u16E6\u16DA\u16B3\u16A2\u16D7" } let(:temp_rune_utf8) { tmpdir(rune_utf8) } it "should use UTF8 characters in TMP,TEMP,TMPDIR environment variable" do # Set the temporary environment variables to the UTF8 temp path Puppet::Util::Windows::Process.set_environment_variable('TMPDIR', temp_rune_utf8) Puppet::Util::Windows::Process.set_environment_variable('TMP', temp_rune_utf8) Puppet::Util::Windows::Process.set_environment_variable('TEMP', temp_rune_utf8) # Create a unique file filename = Puppet::FileSystem::Uniquefile.open_tmp('foo') do |file| File.dirname(file.path) end expect(filename).to eq(temp_rune_utf8) end end end end puppet-5.5.10/spec/integration/indirector/0000755005276200011600000000000013417162176020436 5ustar jenkinsjenkinspuppet-5.5.10/spec/integration/indirector/catalog/0000755005276200011600000000000013417162176022050 5ustar jenkinsjenkinspuppet-5.5.10/spec/integration/indirector/catalog/compiler_spec.rb0000644005276200011600000000572313417161721025223 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/resource/catalog' Puppet::Resource::Catalog.indirection.terminus(:compiler) describe Puppet::Resource::Catalog::Compiler do before do Facter.stubs(:value).returns "something" @catalog = Puppet::Resource::Catalog.new("testing", Puppet::Node::Environment::NONE) @catalog.add_resource(@one = Puppet::Resource.new(:file, "/one")) @catalog.add_resource(@two = Puppet::Resource.new(:file, "/two")) end it "should remove virtual resources when filtering" do @one.virtual = true expect(Puppet::Resource::Catalog.indirection.terminus.filter(@catalog).resource_refs).to eq([ @two.ref ]) end it "should not remove exported resources when filtering" do @one.exported = true expect(Puppet::Resource::Catalog.indirection.terminus.filter(@catalog).resource_refs.sort).to eq([ @one.ref, @two.ref ]) end it "should remove virtual exported resources when filtering" do @one.exported = true @one.virtual = true expect(Puppet::Resource::Catalog.indirection.terminus.filter(@catalog).resource_refs).to eq([ @two.ref ]) end it "should filter out virtual resources when finding a catalog" do Puppet[:node_terminus] = :memory Puppet::Node.indirection.save(Puppet::Node.new("mynode")) Puppet::Resource::Catalog.indirection.terminus.stubs(:extract_facts_from_request) Puppet::Resource::Catalog.indirection.terminus.stubs(:compile).returns(@catalog) @one.virtual = true expect(Puppet::Resource::Catalog.indirection.find("mynode").resource_refs).to eq([ @two.ref ]) end it "should not filter out exported resources when finding a catalog" do Puppet[:node_terminus] = :memory Puppet::Node.indirection.save(Puppet::Node.new("mynode")) Puppet::Resource::Catalog.indirection.terminus.stubs(:extract_facts_from_request) Puppet::Resource::Catalog.indirection.terminus.stubs(:compile).returns(@catalog) @one.exported = true expect(Puppet::Resource::Catalog.indirection.find("mynode").resource_refs.sort).to eq([ @one.ref, @two.ref ]) end it "should filter out virtual exported resources when finding a catalog" do Puppet[:node_terminus] = :memory Puppet::Node.indirection.save(Puppet::Node.new("mynode")) Puppet::Resource::Catalog.indirection.terminus.stubs(:extract_facts_from_request) Puppet::Resource::Catalog.indirection.terminus.stubs(:compile).returns(@catalog) @one.exported = true @one.virtual = true expect(Puppet::Resource::Catalog.indirection.find("mynode").resource_refs).to eq([ @two.ref ]) end it "filters out virtual exported resources using the agent's production environment" do Puppet[:node_terminus] = :memory Puppet::Node.indirection.save(Puppet::Node.new("mynode")) Puppet::Parser::Resource::Catalog.any_instance.expects(:to_resource).with do |catalog| expect(Puppet.lookup(:current_environment).name).to eq(:production) end Puppet::Resource::Catalog.indirection.find("mynode") end end puppet-5.5.10/spec/integration/indirector/direct_file_server_spec.rb0000644005276200011600000000671113417161721025634 0ustar jenkinsjenkinsrequire 'spec_helper' require 'matchers/include' require 'puppet/indirector/file_content/file' require 'puppet/indirector/file_metadata/file' describe Puppet::Indirector::DirectFileServer, " when interacting with the filesystem and the model" do include PuppetSpec::Files before do # We just test a subclass, since it's close enough. @terminus = Puppet::Indirector::FileContent::File.new end it "should return an instance of the model" do filepath = make_absolute("/path/to/my/file") Puppet::FileSystem.expects(:exist?).with(filepath).returns(true) expect(@terminus.find(@terminus.indirection.request(:find, Puppet::Util.path_to_uri(filepath).to_s, nil))).to be_instance_of(Puppet::FileServing::Content) end it "should return an instance capable of returning its content" do filename = file_containing("testfile", "my content") instance = @terminus.find(@terminus.indirection.request(:find, Puppet::Util.path_to_uri(filename).to_s, nil)) expect(instance.content).to eq("my content") end end describe Puppet::Indirector::DirectFileServer, " when interacting with FileServing::Fileset and the model" do include PuppetSpec::Files include Matchers::Include matcher :file_with_content do |name, content| match do |actual| actual.full_path == name && actual.content == content end end matcher :directory_named do |name| match do |actual| actual.full_path == name end end it "should return an instance for every file in the fileset" do path = tmpdir('direct_file_server_testing') File.open(File.join(path, "one"), "w") { |f| f.print "one content" } File.open(File.join(path, "two"), "w") { |f| f.print "two content" } terminus = Puppet::Indirector::FileContent::File.new request = terminus.indirection.request(:search, Puppet::Util.path_to_uri(path).to_s, nil, :recurse => true) expect(terminus.search(request)).to include_in_any_order( file_with_content(File.join(path, "one"), "one content"), file_with_content(File.join(path, "two"), "two content"), directory_named(path)) end end describe Puppet::Indirector::DirectFileServer, " when interacting with filesystem metadata" do include PuppetSpec::Files include_context 'with supported checksum types' before do @terminus = Puppet::Indirector::FileMetadata::File.new end with_checksum_types("file_metadata", "testfile") do it "should return the correct metadata" do request = @terminus.indirection.request(:find, Puppet::Util.path_to_uri(checksum_file).to_s, nil, :checksum_type => checksum_type) result = @terminus.find(request) expect_correct_checksum(result, checksum_type, checksum, Puppet::FileServing::Metadata) end end with_checksum_types("direct_file_server_testing", "testfile") do it "search of FileServing::Fileset should return the correct metadata" do request = @terminus.indirection.request(:search, Puppet::Util.path_to_uri(env_path).to_s, nil, :recurse => true, :checksum_type => checksum_type) result = @terminus.search(request) expect(result).to_not be_nil expect(result.length).to eq(2) file, dir = result.partition {|x| x.relative_path == 'testfile'} expect(file.length).to eq(1) expect(dir.length).to eq(1) expect_correct_checksum(dir[0], 'ctime', "#{CHECKSUM_STAT_TIME}", Puppet::FileServing::Metadata) expect_correct_checksum(file[0], checksum_type, checksum, Puppet::FileServing::Metadata) end end end puppet-5.5.10/spec/integration/indirector/facts/0000755005276200011600000000000013417162176021536 5ustar jenkinsjenkinspuppet-5.5.10/spec/integration/indirector/facts/facter_spec.rb0000644005276200011600000000757613417161722024354 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/files' require 'puppet_spec/compiler' describe Puppet::Node::Facts::Facter do include PuppetSpec::Files include PuppetSpec::Compiler it "preserves case in fact values" do Facter.add(:downcase_test) do setcode do "AaBbCc" end end Facter.stubs(:reset) cat = compile_to_catalog('notify { $downcase_test: }', Puppet::Node.indirection.find('foo')) expect(cat.resource("Notify[AaBbCc]")).to be end context "resolving file based facts" do let(:factdir) { tmpdir('factdir') } it "should resolve custom facts" do test_module = File.join(factdir, 'module', 'lib', 'facter') FileUtils.mkdir_p(test_module) File.open(File.join(test_module, 'custom.rb'), 'wb') { |file| file.write(<<-EOF)} Facter.add(:custom) do setcode do Facter.value('puppetversion') end end EOF Puppet.initialize_settings(['--modulepath', factdir]) apply = Puppet::Application.find(:apply).new(stub('command_line', :subcommand_name => :apply, :args => ['--modulepath', factdir, '-e', 'notify { $custom: }'])) expect do expect { apply.run }.to exit_with(0) end.to have_printed(Puppet.version) end it "should resolve external facts" do external_fact = File.join(factdir, 'external') if Puppet.features.microsoft_windows? external_fact += '.bat' File.open(external_fact, 'wb') { |file| file.write(<<-EOF)} @echo foo=bar EOF else File.open(external_fact, 'wb') { |file| file.write(<<-EOF)} #!/bin/sh echo "foo=bar" EOF Puppet::FileSystem.chmod(0755, external_fact) end Puppet.initialize_settings(['--pluginfactdest', factdir]) apply = Puppet::Application.find(:apply).new(stub('command_line', :subcommand_name => :apply, :args => ['--pluginfactdest', factdir, '-e', 'notify { $foo: }'])) expect do expect { apply.run }.to exit_with(0) end.to have_printed('bar') end end it "adds the puppetversion fact" do Facter.stubs(:reset) cat = compile_to_catalog('notify { $::puppetversion: }', Puppet::Node.indirection.find('foo')) expect(cat.resource("Notify[#{Puppet.version.to_s}]")).to be end it "the agent_specified_environment fact is nil when not set" do expect do compile_to_catalog('notify { $::agent_specified_environment: }', Puppet::Node.indirection.find('foo')) end.to raise_error(Puppet::PreformattedError) end it "adds the agent_specified_environment fact when set in puppet.conf" do FileUtils.mkdir_p(Puppet[:confdir]) File.open(File.join(Puppet[:confdir], 'puppet.conf'), 'w') do |f| f.puts("environment=bar") end Puppet.initialize_settings cat = compile_to_catalog('notify { $::agent_specified_environment: }', Puppet::Node.indirection.find('foo')) expect(cat.resource("Notify[bar]")).to be end it "adds the agent_specified_environment fact when set via command-line" do Puppet.initialize_settings(['--environment', 'bar']) cat = compile_to_catalog('notify { $::agent_specified_environment: }', Puppet::Node.indirection.find('foo')) expect(cat.resource("Notify[bar]")).to be end it "adds the agent_specified_environment fact, preferring cli, when set in puppet.conf and via command-line" do FileUtils.mkdir_p(Puppet[:confdir]) File.open(File.join(Puppet[:confdir], 'puppet.conf'), 'w') do |f| f.puts("environment=bar") end Puppet.initialize_settings(['--environment', 'baz']) cat = compile_to_catalog('notify { $::agent_specified_environment: }', Puppet::Node.indirection.find('foo')) expect(cat.resource("Notify[baz]")).to be end end puppet-5.5.10/spec/integration/indirector/file_content/0000755005276200011600000000000013417162176023107 5ustar jenkinsjenkinspuppet-5.5.10/spec/integration/indirector/file_content/file_server_spec.rb0000644005276200011600000000733313417161721026754 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/file_content/file_server' require 'shared_behaviours/file_server_terminus' require 'puppet_spec/files' describe Puppet::Indirector::FileContent::FileServer, " when finding files" do it_should_behave_like "Puppet::Indirector::FileServerTerminus" include PuppetSpec::Files before do @terminus = Puppet::Indirector::FileContent::FileServer.new @test_class = Puppet::FileServing::Content Puppet::FileServing::Configuration.instance_variable_set(:@configuration, nil) end it "should find plugin file content in the environment specified in the request" do path = tmpfile("file_content_with_env") Dir.mkdir(path) modpath = File.join(path, "mod") FileUtils.mkdir_p(File.join(modpath, "lib")) file = File.join(modpath, "lib", "file.rb") File.open(file, "wb") { |f| f.write "1\r\n" } Puppet.settings[:modulepath] = "/no/such/file" env = Puppet::Node::Environment.create(:foo, [path]) result = Puppet::FileServing::Content.indirection.search("plugins", :environment => env, :recurse => true) expect(result).not_to be_nil expect(result.length).to eq(2) result.map {|x| expect(x).to be_instance_of(Puppet::FileServing::Content) } expect(result.find {|x| x.relative_path == 'file.rb' }.content).to eq("1\r\n") end it "should find file content in modules" do path = tmpfile("file_content") Dir.mkdir(path) modpath = File.join(path, "mymod") FileUtils.mkdir_p(File.join(modpath, "files")) file = File.join(modpath, "files", "myfile") File.open(file, "wb") { |f| f.write "1\r\n" } env = Puppet::Node::Environment.create(:foo, [path]) result = Puppet::FileServing::Content.indirection.find("modules/mymod/myfile", :environment => env) expect(result).not_to be_nil expect(result).to be_instance_of(Puppet::FileServing::Content) expect(result.content).to eq("1\r\n") end it "should find file content of tasks in modules" do path = tmpfile("task_file_content") Dir.mkdir(path) modpath = File.join(path, "myothermod") FileUtils.mkdir_p(File.join(modpath, "tasks")) file = File.join(modpath, "tasks", "mytask") File.open(file, "wb") { |f| f.write "I'm a task" } env = Puppet::Node::Environment.create(:foo, [path]) result = Puppet::FileServing::Content.indirection.find("tasks/myothermod/mytask", :environment => env) expect(result).not_to be_nil expect(result).to be_instance_of(Puppet::FileServing::Content) expect(result.content).to eq("I'm a task") end it "should find file content in files when node name expansions are used" do Puppet::FileSystem.stubs(:exist?).returns true Puppet::FileSystem.stubs(:exist?).with(Puppet[:fileserverconfig]).returns(true) path = tmpfile("file_server_testing") Dir.mkdir(path) subdir = File.join(path, "mynode") Dir.mkdir(subdir) File.open(File.join(subdir, "myfile"), "wb") { |f| f.write "1\r\n" } # Use a real mount, so the integration is a bit deeper. mount1 = Puppet::FileServing::Configuration::Mount::File.new("one") mount1.stubs(:globalallow?).returns true mount1.stubs(:allowed?).returns true mount1.path = File.join(path, "%h") parser = stub 'parser', :changed? => false parser.stubs(:parse).returns("one" => mount1) Puppet::FileServing::Configuration::Parser.stubs(:new).returns(parser) path = File.join(path, "myfile") env = Puppet::Node::Environment.create(:foo, []) result = Puppet::FileServing::Content.indirection.find("one/myfile", :environment => env, :node => "mynode") expect(result).not_to be_nil expect(result).to be_instance_of(Puppet::FileServing::Content) expect(result.content).to eq("1\r\n") end end puppet-5.5.10/spec/integration/indirector/file_metadata/0000755005276200011600000000000013417162176023215 5ustar jenkinsjenkinspuppet-5.5.10/spec/integration/indirector/file_metadata/file_server_spec.rb0000644005276200011600000000663213417161721027063 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/file_metadata/file_server' require 'shared_behaviours/file_server_terminus' require 'puppet_spec/files' describe Puppet::Indirector::FileMetadata::FileServer, " when finding files" do it_should_behave_like "Puppet::Indirector::FileServerTerminus" include PuppetSpec::Files include_context 'with supported checksum types' before do @terminus = Puppet::Indirector::FileMetadata::FileServer.new @test_class = Puppet::FileServing::Metadata Puppet::FileServing::Configuration.instance_variable_set(:@configuration, nil) end describe "with a plugin environment specified in the request" do with_checksum_types("file_content_with_env", "mod/lib/file.rb") do it "should return the correct metadata" do Puppet.settings[:modulepath] = "/no/such/file" env = Puppet::Node::Environment.create(:foo, [env_path]) result = Puppet::FileServing::Metadata.indirection.search("plugins", :environment => env, :checksum_type => checksum_type, :recurse => true) expect(result).to_not be_nil expect(result.length).to eq(2) result.map {|x| expect(x).to be_instance_of(Puppet::FileServing::Metadata)} expect_correct_checksum(result.find {|x| x.relative_path == 'file.rb'}, checksum_type, checksum, Puppet::FileServing::Metadata) end end end describe "in modules" do with_checksum_types("file_content", "mymod/files/myfile") do it "should return the correct metadata" do env = Puppet::Node::Environment.create(:foo, [env_path]) result = Puppet::FileServing::Metadata.indirection.find("modules/mymod/myfile", :environment => env, :checksum_type => checksum_type) expect_correct_checksum(result, checksum_type, checksum, Puppet::FileServing::Metadata) end end end describe "that are tasks in modules" do with_checksum_types("task_file_content", "mymod/tasks/mytask") do it "should return the correct metadata" do env = Puppet::Node::Environment.create(:foo, [env_path]) result = Puppet::FileServing::Metadata.indirection.find("tasks/mymod/mytask", :environment => env, :checksum_type => checksum_type) expect_correct_checksum(result, checksum_type, checksum, Puppet::FileServing::Metadata) end end end describe "when node name expansions are used" do with_checksum_types("file_server_testing", "mynode/myfile") do it "should return the correct metadata" do Puppet::FileSystem.stubs(:exist?).with(checksum_file).returns true Puppet::FileSystem.stubs(:exist?).with(Puppet[:fileserverconfig]).returns(true) # Use a real mount, so the integration is a bit deeper. mount1 = Puppet::FileServing::Configuration::Mount::File.new("one") mount1.stubs(:globalallow?).returns true mount1.stubs(:allowed?).returns true mount1.path = File.join(env_path, "%h") parser = stub 'parser', :changed? => false parser.stubs(:parse).returns("one" => mount1) Puppet::FileServing::Configuration::Parser.stubs(:new).returns(parser) env = Puppet::Node::Environment.create(:foo, []) result = Puppet::FileServing::Metadata.indirection.find("one/myfile", :environment => env, :node => "mynode", :checksum_type => checksum_type) expect_correct_checksum(result, checksum_type, checksum, Puppet::FileServing::Metadata) end end end end puppet-5.5.10/spec/integration/indirector/node/0000755005276200011600000000000013417162176021363 5ustar jenkinsjenkinspuppet-5.5.10/spec/integration/indirector/node/ldap_spec.rb0000644005276200011600000000071113417161722023635 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/node/ldap' describe Puppet::Node::Ldap do it "should use a restrictive filter when searching for nodes in a class" do ldap = Puppet::Node.indirection.terminus(:ldap) Puppet::Node.indirection.stubs(:terminus).returns ldap ldap.expects(:ldapsearch).with("(&(objectclass=puppetClient)(puppetclass=foo))") Puppet::Node.indirection.search "eh", :class => "foo" end end puppet-5.5.10/spec/integration/module_tool/0000755005276200011600000000000013417162176020616 5ustar jenkinsjenkinspuppet-5.5.10/spec/integration/module_tool/tar/0000755005276200011600000000000013417162176021404 5ustar jenkinsjenkinspuppet-5.5.10/spec/integration/module_tool/tar/mini_spec.rb0000644005276200011600000000213213417161721023670 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/module_tool' require 'puppet_spec/files' describe Puppet::ModuleTool::Tar::Mini, :if => (Puppet.features.minitar? && Puppet.features.zlib?) do let(:minitar) { described_class.new } describe "Extracts tars with long and short pathnames" do let (:sourcetar) { File.expand_path('../../../../fixtures/module.tar.gz', __FILE__) } let (:longfilepath) { "puppetlabs-dsc-1.0.0/lib/puppet_x/dsc_resources/xWebAdministration/DSCResources/MSFT_xWebAppPoolDefaults/MSFT_xWebAppPoolDefaults.schema.mof" } let (:shortfilepath) { "puppetlabs-dsc-1.0.0/README.md" } it "unpacks a tar with a short path length" do extractdir = PuppetSpec::Files.tmpdir('minitar') minitar.unpack(sourcetar,extractdir,'module') expect(File).to exist(File.expand_path("#{extractdir}/#{shortfilepath}")) end it "unpacks a tar with a long path length" do extractdir = PuppetSpec::Files.tmpdir('minitar') minitar.unpack(sourcetar,extractdir,'module') expect(File).to exist(File.expand_path("#{extractdir}/#{longfilepath}")) end end endpuppet-5.5.10/spec/integration/network/0000755005276200011600000000000013417162176017765 5ustar jenkinsjenkinspuppet-5.5.10/spec/integration/network/authconfig_spec.rb0000644005276200011600000001452213417161721023452 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/network/authconfig' require 'puppet/network/auth_config_parser' RSpec::Matchers.define :allow do |params| match do |auth| begin auth.check_authorization(*params) true rescue Puppet::Network::AuthorizationError false end end failure_message do |instance| "expected #{params[2][:node]}/#{params[2][:ip]} to be allowed" end failure_message_when_negated do |instance| "expected #{params[2][:node]}/#{params[2][:ip]} to be forbidden" end end describe Puppet::Network::AuthConfig do include PuppetSpec::Files def add_rule(rule) parser = Puppet::Network::AuthConfigParser.new( "path /test\n#{rule}\n" ) @auth = parser.parse end def add_regex_rule(regex, rule) parser = Puppet::Network::AuthConfigParser.new( "path ~ #{regex}\n#{rule}\n" ) @auth = parser.parse end def add_raw_stanza(stanza) parser = Puppet::Network::AuthConfigParser.new( stanza ) @auth = parser.parse end def request(args = {}) args = { :key => 'key', :node => 'host.domain.com', :ip => '10.1.1.1', :authenticated => true }.merge(args) [:find, "/test/#{args[:key]}", args] end describe "allow" do it "should not match IP addresses" do add_rule("allow 10.1.1.1") expect(@auth).not_to allow(request) end it "should not accept CIDR IPv4 address" do expect { add_rule("allow 10.0.0.0/8") }.to raise_error Puppet::ConfigurationError, /Invalid pattern 10\.0\.0\.0\/8/ end it "should not match wildcard IPv4 address" do expect { add_rule("allow 10.1.1.*") }.to raise_error Puppet::ConfigurationError, /Invalid pattern 10\.1\.1\.*/ end it "should not match IPv6 address" do expect { add_rule("allow 2001:DB8::8:800:200C:417A") }.to raise_error Puppet::ConfigurationError, /Invalid pattern 2001/ end it "should support hostname" do add_rule("allow host.domain.com") expect(@auth).to allow(request) end it "should support wildcard host" do add_rule("allow *.domain.com") expect(@auth).to allow(request) end it 'should warn about missing path before allow_ip in stanza' do expect { add_raw_stanza("allow_ip 10.0.0.1\n") }.to raise_error Puppet::ConfigurationError, /Missing or invalid 'path' before right directive at \(line: .*\)/ end it 'should warn about missing path before allow in stanza' do expect { add_raw_stanza("allow host.domain.com\n") }.to raise_error Puppet::ConfigurationError, /Missing or invalid 'path' before right directive at \(line: .*\)/ end it "should support hostname backreferences" do add_regex_rule('^/test/([^/]+)$', "allow $1.domain.com") expect(@auth).to allow(request(:key => 'host')) end it "should support opaque strings" do add_rule("allow this-is-opaque@or-not") expect(@auth).to allow(request(:node => 'this-is-opaque@or-not')) end it "should support opaque strings and backreferences" do add_regex_rule('^/test/([^/]+)$', "allow $1") expect(@auth).to allow(request(:key => 'this-is-opaque@or-not', :node => 'this-is-opaque@or-not')) end it "should support hostname ending with '.'" do pending('bug #7589') add_rule("allow host.domain.com.") expect(@auth).to allow(request(:node => 'host.domain.com.')) end it "should support hostname ending with '.' and backreferences" do pending('bug #7589') add_regex_rule('^/test/([^/]+)$',"allow $1") expect(@auth).to allow(request(:node => 'host.domain.com.')) end it "should support trailing whitespace" do add_rule('allow host.domain.com ') expect(@auth).to allow(request) end it "should support inlined comments" do add_rule('allow host.domain.com # will it work?') expect(@auth).to allow(request) end it "should deny non-matching host" do add_rule("allow inexistent") expect(@auth).not_to allow(request) end end describe "allow_ip" do it "should not warn when matches against IP addresses fail" do add_rule("allow_ip 10.1.1.2") expect(@auth).not_to allow(request) expect(@logs).not_to be_any {|log| log.level == :warning and log.message =~ /Authentication based on IP address is deprecated/} end it "should support IPv4 address" do add_rule("allow_ip 10.1.1.1") expect(@auth).to allow(request) end it "should support CIDR IPv4 address" do add_rule("allow_ip 10.0.0.0/8") expect(@auth).to allow(request) end it "should support wildcard IPv4 address" do add_rule("allow_ip 10.1.1.*") expect(@auth).to allow(request) end it "should support IPv6 address" do add_rule("allow_ip 2001:DB8::8:800:200C:417A") expect(@auth).to allow(request(:ip => '2001:DB8::8:800:200C:417A')) end it "should support hostname" do expect { add_rule("allow_ip host.domain.com") }.to raise_error Puppet::ConfigurationError, /Invalid IP pattern host.domain.com/ end end describe "deny" do it "should deny denied hosts" do add_rule <<-EOALLOWRULE deny host.domain.com allow *.domain.com EOALLOWRULE expect(@auth).not_to allow(request) end it "denies denied hosts after allowing them" do add_rule <<-EOALLOWRULE allow *.domain.com deny host.domain.com EOALLOWRULE expect(@auth).not_to allow(request) end it "should not deny based on IP" do add_rule <<-EOALLOWRULE deny 10.1.1.1 allow host.domain.com EOALLOWRULE expect(@auth).to allow(request) end it "should not deny based on IP (ordering #2)" do add_rule <<-EOALLOWRULE allow host.domain.com deny 10.1.1.1 EOALLOWRULE expect(@auth).to allow(request) end end describe "deny_ip" do it "should deny based on IP" do add_rule <<-EOALLOWRULE deny_ip 10.1.1.1 allow host.domain.com EOALLOWRULE expect(@auth).not_to allow(request) end it "should deny based on IP (ordering #2)" do add_rule <<-EOALLOWRULE allow host.domain.com deny_ip 10.1.1.1 EOALLOWRULE expect(@auth).not_to allow(request) end end end puppet-5.5.10/spec/integration/network/formats_spec.rb0000644005276200011600000000523013417161721022772 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/network/formats' class PsonIntTest attr_accessor :string def ==(other) other.class == self.class and string == other.string end def self.from_data_hash(data) new(data[0]) end def initialize(string) @string = string end def to_pson(*args) { 'type' => self.class.name, 'data' => [@string] }.to_pson(*args) end def self.canonical_order(s) s.gsub(/\{"data":\[(.*?)\],"type":"PsonIntTest"\}/,'{"type":"PsonIntTest","data":[\1]}') end end describe Puppet::Network::FormatHandler.format(:s) do before do @format = Puppet::Network::FormatHandler.format(:s) end it "should support certificates" do expect(@format).to be_supported(Puppet::SSL::Certificate) end it "should not support catalogs" do expect(@format).not_to be_supported(Puppet::Resource::Catalog) end end describe Puppet::Network::FormatHandler.format(:pson) do before do @pson = Puppet::Network::FormatHandler.format(:pson) end it "should be able to render an instance to pson" do instance = PsonIntTest.new("foo") expect(PsonIntTest.canonical_order(@pson.render(instance))).to eq(PsonIntTest.canonical_order('{"type":"PsonIntTest","data":["foo"]}' )) end it "should be able to render arrays to pson" do expect(@pson.render([1,2])).to eq('[1,2]') end it "should be able to render arrays containing hashes to pson" do expect(@pson.render([{"one"=>1},{"two"=>2}])).to eq('[{"one":1},{"two":2}]') end it "should be able to render multiple instances to pson" do one = PsonIntTest.new("one") two = PsonIntTest.new("two") expect(PsonIntTest.canonical_order(@pson.render([one,two]))).to eq(PsonIntTest.canonical_order('[{"type":"PsonIntTest","data":["one"]},{"type":"PsonIntTest","data":["two"]}]')) end it "should be able to intern pson into an instance" do expect(@pson.intern(PsonIntTest, '{"type":"PsonIntTest","data":["foo"]}')).to eq(PsonIntTest.new("foo")) end it "should be able to intern pson with no class information into an instance" do expect(@pson.intern(PsonIntTest, '["foo"]')).to eq(PsonIntTest.new("foo")) end it "should be able to intern multiple instances from pson" do expect(@pson.intern_multiple(PsonIntTest, '[{"type": "PsonIntTest", "data": ["one"]},{"type": "PsonIntTest", "data": ["two"]}]')).to eq([ PsonIntTest.new("one"), PsonIntTest.new("two") ]) end it "should be able to intern multiple instances from pson with no class information" do expect(@pson.intern_multiple(PsonIntTest, '[["one"],["two"]]')).to eq([ PsonIntTest.new("one"), PsonIntTest.new("two") ]) end end puppet-5.5.10/spec/integration/network/http/0000755005276200011600000000000013417162176020744 5ustar jenkinsjenkinspuppet-5.5.10/spec/integration/network/http/api/0000755005276200011600000000000013417162176021515 5ustar jenkinsjenkinspuppet-5.5.10/spec/integration/network/http/api/indirected_routes_spec.rb0000644005276200011600000000711213417161722026564 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/network/http' require 'puppet/network/http/api/indirected_routes' require 'rack/mock' if Puppet.features.rack? require 'puppet/network/http/rack/rest' require 'puppet/indirector_proxy' require 'puppet_spec/files' require 'puppet_spec/network' require 'puppet/util/json' describe Puppet::Network::HTTP::API::IndirectedRoutes do include PuppetSpec::Files include PuppetSpec::Network include_context 'with supported checksum types' describe "when running the master application" do before :each do Puppet::Application[:master].setup_terminuses end describe "using Puppet API to request file metadata" do let(:handler) { Puppet::Network::HTTP::API::IndirectedRoutes.new } let(:response) { Puppet::Network::HTTP::MemoryResponse.new } with_checksum_types 'file_content', 'lib/files/file.rb' do before :each do Puppet.settings[:modulepath] = env_path end it "should find the file metadata with expected checksum" do request = a_request_that_finds(Puppet::IndirectorProxy.new("modules/lib/file.rb", "file_metadata"), {:accept_header => 'unknown, text/pson'}, {:environment => 'production', :checksum_type => checksum_type}) handler.call(request, response) resp = Puppet::Util::Json.load(response.body) expect(resp['checksum']['type']).to eq(checksum_type) expect(checksum_valid(checksum_type, checksum, resp['checksum']['value'])).to be_truthy end it "should search for the file metadata with expected checksum" do request = a_request_that_searches(Puppet::IndirectorProxy.new("modules/lib", "file_metadata"), {:accept_header => 'unknown, text/pson'}, {:environment => 'production', :checksum_type => checksum_type, :recurse => 'yes'}) handler.call(request, response) resp = Puppet::Util::Json.load(response.body) expect(resp.length).to eq(2) file = resp.find {|x| x['relative_path'] == 'file.rb'} expect(file['checksum']['type']).to eq(checksum_type) expect(checksum_valid(checksum_type, checksum, file['checksum']['value'])).to be_truthy end end end describe "an error from IndirectedRoutes", :if => Puppet.features.rack? do let(:handler) { Puppet::Network::HTTP::RackREST.new } describe "returns json" do it "when a standard error" do response = Rack::Response.new request = Rack::Request.new( Rack::MockRequest.env_for("/puppet/v3/invalid-indirector")) handler.process(request, response) expect(response.header["Content-Type"]).to match(/json/) resp = Puppet::Util::Json.load(response.body.first) expect(resp["message"]).to match(/The indirection name must be purely alphanumeric/) expect(resp["issue_kind"]).to be_a_kind_of(String) end it "when a server error" do response = Rack::Response.new request = Rack::Request.new( Rack::MockRequest.env_for("/puppet/v3/unknown_indirector")) handler.process(request, response) expect(response.header["Content-Type"]).to match(/json/) resp = Puppet::Util::Json.load(response.body.first) expect(resp["message"]).to match(/Could not find indirection/) expect(resp["issue_kind"]).to be_a_kind_of(String) end end end end end puppet-5.5.10/spec/integration/node/0000755005276200011600000000000013417162176017221 5ustar jenkinsjenkinspuppet-5.5.10/spec/integration/node/facts_spec.rb0000644005276200011600000000302413417161721021652 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Node::Facts do describe "when using the indirector" do it "should expire any cached node instances when it is saved" do Puppet::Node::Facts.indirection.stubs(:terminus_class).returns :yaml expect(Puppet::Node::Facts.indirection.terminus(:yaml)).to equal(Puppet::Node::Facts.indirection.terminus(:yaml)) terminus = Puppet::Node::Facts.indirection.terminus(:yaml) terminus.stubs :save Puppet::Node.indirection.expects(:expire).with("me", optionally(instance_of(Hash))) facts = Puppet::Node::Facts.new("me") Puppet::Node::Facts.indirection.save(facts) end it "should be able to delegate to the :yaml terminus" do Puppet::Node::Facts.indirection.stubs(:terminus_class).returns :yaml # Load now, before we stub the exists? method. terminus = Puppet::Node::Facts.indirection.terminus(:yaml) terminus.expects(:path).with("me").returns "/my/yaml/file" Puppet::FileSystem.expects(:exist?).with("/my/yaml/file").returns false expect(Puppet::Node::Facts.indirection.find("me")).to be_nil end it "should be able to delegate to the :facter terminus" do Puppet::Node::Facts.indirection.stubs(:terminus_class).returns :facter Facter.expects(:to_hash).returns "facter_hash" facts = Puppet::Node::Facts.new("me") Puppet::Node::Facts.expects(:new).with("me", "facter_hash").returns facts expect(Puppet::Node::Facts.indirection.find("me")).to equal(facts) end end end puppet-5.5.10/spec/integration/node/environment_spec.rb0000644005276200011600000001306113417161722023121 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/files' require 'puppet_spec/scope' require 'matchers/resource' describe Puppet::Node::Environment do include PuppetSpec::Files include Matchers::Resource def a_module_in(name, dir) Dir.mkdir(dir) moddir = File.join(dir, name) Dir.mkdir(moddir) moddir end it "should be able to return each module from its environment with the environment, name, and path set correctly" do base = tmpfile("env_modules") Dir.mkdir(base) dirs = [] mods = {} %w{1 2}.each do |num| dir = File.join(base, "dir#{num}") dirs << dir mods["mod#{num}"] = a_module_in("mod#{num}", dir) end environment = Puppet::Node::Environment.create(:foo, dirs) environment.modules.each do |mod| expect(mod.environment).to eq(environment) expect(mod.path).to eq(mods[mod.name]) end end it "should expand 8.3 paths on Windows when creating an environment", :if => Puppet::Util::Platform.windows? do # asking for short names only works on paths that exist base = Puppet::Util::Windows::File.get_short_pathname(tmpdir("env_modules")) parent_modules_dir = File.join(base, 'testmoduledir') # make sure the paths have ~ in them, indicating unexpanded 8.3 paths expect(parent_modules_dir).to match(/~/) module_dir = a_module_in('testmodule', parent_modules_dir) # create the environment with unexpanded 8.3 paths environment = Puppet::Node::Environment.create(:foo, [parent_modules_dir]) # and expect fully expanded paths inside the environment # necessary for comparing module paths internally by the parser expect(environment.modulepath).to eq([Puppet::FileSystem.expand_path(parent_modules_dir)]) expect(environment.modules.first.path).to eq(Puppet::FileSystem.expand_path(module_dir)) end it "should not yield the same module from different module paths" do base = tmpfile("env_modules") Dir.mkdir(base) dirs = [] %w{1 2}.each do |num| dir = File.join(base, "dir#{num}") dirs << dir a_module_in("mod", dir) end environment = Puppet::Node::Environment.create(:foo, dirs) mods = environment.modules expect(mods.length).to eq(1) expect(mods[0].path).to eq(File.join(base, "dir1", "mod")) end shared_examples_for "the environment's initial import" do |settings| it "a manifest referring to a directory invokes parsing of all its files in sorted order" do settings.each do |name, value| Puppet[name] = value end # fixture has three files 00_a.pp, 01_b.pp, and 02_c.pp. The 'b' file # depends on 'a' being evaluated first. The 'c' file is empty (to ensure # empty things do not break the directory import). # dirname = my_fixture('sitedir') # Set the manifest to the directory to make it parse and combine them when compiling node = Puppet::Node.new('testnode', :environment => Puppet::Node::Environment.create(:testing, [], dirname)) catalog = Puppet::Parser::Compiler.compile(node) expect(catalog).to have_resource('Class[A]') expect(catalog).to have_resource('Class[B]') expect(catalog).to have_resource('Notify[variables]').with_parameter(:message, "a: 10, b: 10") end end shared_examples_for "the environment's initial import in 4x" do |settings| it "a manifest referring to a directory invokes recursive parsing of all its files in sorted order" do settings.each do |name, value| Puppet[name] = value end # fixture has three files 00_a.pp, 01_b.pp, and 02_c.pp. The 'b' file # depends on 'a' being evaluated first. The 'c' file is empty (to ensure # empty things do not break the directory import). # dirname = my_fixture('sitedir2') # Set the manifest to the directory to make it parse and combine them when compiling node = Puppet::Node.new('testnode', :environment => Puppet::Node::Environment.create(:testing, [], dirname)) catalog = Puppet::Parser::Compiler.compile(node) expect(catalog).to have_resource('Class[A]') expect(catalog).to have_resource('Class[B]') expect(catalog).to have_resource('Notify[variables]').with_parameter(:message, "a: 10, b: 10 c: 20") end end describe 'using 4x parser' do it_behaves_like "the environment's initial import", # fixture uses variables that are set in a particular order (this ensures # that files are parsed and combined in the right order or an error will # be raised if 'b' is evaluated before 'a'). :strict_variables => true end describe 'using 4x parser' do it_behaves_like "the environment's initial import in 4x", # fixture uses variables that are set in a particular order (this ensures # that files are parsed and combined in the right order or an error will # be raised if 'b' is evaluated before 'a'). :strict_variables => true end describe "#extralibs on Windows", :if => Puppet.features.microsoft_windows? do describe "with UTF8 characters in PUPPETLIB" do let(:rune_utf8) { "\u16A0\u16C7\u16BB\u16EB\u16D2\u16E6\u16A6\u16EB\u16A0\u16B1\u16A9\u16A0\u16A2\u16B1\u16EB\u16A0\u16C1\u16B1\u16AA\u16EB\u16B7\u16D6\u16BB\u16B9\u16E6\u16DA\u16B3\u16A2\u16D7" } before { Puppet::Util::Windows::Process.set_environment_variable('PUPPETLIB', rune_utf8) } it "should use UTF8 characters in PUPPETLIB environment variable" do expect(Puppet::Node::Environment.extralibs()).to eq([rune_utf8]) end end end end puppet-5.5.10/spec/integration/parser/0000755005276200011600000000000013417162176017570 5ustar jenkinsjenkinspuppet-5.5.10/spec/integration/parser/class_spec.rb0000644005276200011600000000223713417161721022233 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/language' describe "Class expressions" do extend PuppetSpec::Language produces( "class hi { }" => '!defined(Class[hi])', "class hi { } include hi" => 'defined(Class[hi])', "include(hi) class hi { }" => 'defined(Class[hi])', "class hi { } class { hi: }" => 'defined(Class[hi])', "class { hi: } class hi { }" => 'defined(Class[hi])', "class bye { } class hi inherits bye { } include hi" => 'defined(Class[hi]) and defined(Class[bye])') produces(<<-EXAMPLE => 'defined(Notify[foo]) and defined(Notify[bar]) and !defined(Notify[foo::bar])') class bar { notify { 'bar': } } class foo::bar { notify { 'foo::bar': } } class foo inherits bar { notify { 'foo': } } include foo EXAMPLE produces(<<-EXAMPLE => 'defined(Notify[foo]) and defined(Notify[bar]) and !defined(Notify[foo::bar])') class bar { notify { 'bar': } } class foo::bar { notify { 'foo::bar': } } class foo inherits ::bar { notify { 'foo': } } include foo EXAMPLE end puppet-5.5.10/spec/integration/parser/collection_spec.rb0000644005276200011600000003051713417161721023263 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/compiler' describe 'collectors' do include PuppetSpec::Compiler def expect_the_message_to_be(expected_messages, code, node = Puppet::Node.new('the node')) catalog = compile_to_catalog(code, node) messages = catalog.resources.find_all { |resource| resource.type == 'Notify' }. collect { |notify| notify[:message] } expect(messages).to include(*expected_messages) end def warnings @logs.select { |log| log.level == :warning }.map { |log| log.message } end context "virtual resource collection" do it "matches everything when no query given" do expect_the_message_to_be(["the other message", "the message"], <<-MANIFEST) @notify { "testing": message => "the message" } @notify { "other": message => "the other message" } Notify <| |> MANIFEST end it "matches regular resources " do expect_the_message_to_be(["changed", "changed"], <<-MANIFEST) notify { "testing": message => "the message" } notify { "other": message => "the other message" } Notify <| |> { message => "changed" } MANIFEST end it "matches on tags" do expect_the_message_to_be(["wanted"], <<-MANIFEST) @notify { "testing": tag => ["one"], message => "wanted" } @notify { "other": tag => ["two"], message => "unwanted" } Notify <| tag == one |> MANIFEST end it "matches on title" do expect_the_message_to_be(["the message"], <<-MANIFEST) @notify { "testing": message => "the message" } Notify <| title == "testing" |> MANIFEST end it "matches on other parameters" do expect_the_message_to_be(["the message"], <<-MANIFEST) @notify { "testing": message => "the message" } @notify { "other testing": message => "the wrong message" } Notify <| message == "the message" |> MANIFEST end it "matches against elements of an array valued parameter" do expect_the_message_to_be([["the", "message"]], <<-MANIFEST) @notify { "testing": message => ["the", "message"] } @notify { "other testing": message => ["not", "here"] } Notify <| message == "message" |> MANIFEST end it "matches with bare word" do expect_the_message_to_be(["wanted"], <<-MANIFEST) @notify { "testing": tag => ["one"], message => "wanted" } Notify <| tag == one |> MANIFEST end it "matches with single quoted string" do expect_the_message_to_be(["wanted"], <<-MANIFEST) @notify { "testing": tag => ["one"], message => "wanted" } Notify <| tag == 'one' |> MANIFEST end it "matches with double quoted string" do expect_the_message_to_be(["wanted"], <<-MANIFEST) @notify { "testing": tag => ["one"], message => "wanted" } Notify <| tag == "one" |> MANIFEST end it "matches with double quoted string with interpolated expression" do expect_the_message_to_be(["wanted"], <<-MANIFEST) @notify { "testing": tag => ["one"], message => "wanted" } $x = 'one' Notify <| tag == "$x" |> MANIFEST end it "matches with resource references" do expect_the_message_to_be(["wanted"], <<-MANIFEST) @notify { "foobar": } @notify { "testing": require => Notify["foobar"], message => "wanted" } Notify <| require == Notify["foobar"] |> MANIFEST end it "allows criteria to be combined with 'and'" do expect_the_message_to_be(["the message"], <<-MANIFEST) @notify { "testing": message => "the message" } @notify { "other": message => "the message" } Notify <| title == "testing" and message == "the message" |> MANIFEST end it "allows criteria to be combined with 'or'" do expect_the_message_to_be(["the message", "other message"], <<-MANIFEST) @notify { "testing": message => "the message" } @notify { "other": message => "other message" } @notify { "yet another": message => "different message" } Notify <| title == "testing" or message == "other message" |> MANIFEST end it "allows criteria to be combined with 'or'" do expect_the_message_to_be(["the message", "other message"], <<-MANIFEST) @notify { "testing": message => "the message" } @notify { "other": message => "other message" } @notify { "yet another": message => "different message" } Notify <| title == "testing" or message == "other message" |> MANIFEST end it "allows criteria to be grouped with parens" do expect_the_message_to_be(["the message", "different message"], <<-MANIFEST) @notify { "testing": message => "different message", withpath => true } @notify { "other": message => "the message" } @notify { "yet another": message => "the message", withpath => true } Notify <| (title == "testing" or message == "the message") and withpath == true |> MANIFEST end it "does not do anything if nothing matches" do expect_the_message_to_be([], <<-MANIFEST) @notify { "testing": message => "different message" } Notify <| title == "does not exist" |> MANIFEST end it "excludes items with inequalities" do expect_the_message_to_be(["good message"], <<-MANIFEST) @notify { "testing": message => "good message" } @notify { "the wrong one": message => "bad message" } Notify <| title != "the wrong one" |> MANIFEST end it "does not exclude resources with unequal arrays" do expect_the_message_to_be(["message", ["not this message", "or this one"]], <<-MANIFEST) @notify { "testing": message => "message" } @notify { "the wrong one": message => ["not this message", "or this one"] } Notify <| message != "not this message" |> MANIFEST end it "does not exclude tags with inequalities" do expect_the_message_to_be(["wanted message", "the way it works"], <<-MANIFEST) @notify { "testing": tag => ["wanted"], message => "wanted message" } @notify { "other": tag => ["why"], message => "the way it works" } Notify <| tag != "why" |> MANIFEST end it "does not collect classes" do node = Puppet::Node.new('the node') expect do compile_to_catalog(<<-MANIFEST, node) class theclass { @notify { "testing": message => "good message" } } Class <| |> MANIFEST end.to raise_error(/Classes cannot be collected/) end it "does not collect resources that don't exist" do node = Puppet::Node.new('the node') expect do compile_to_catalog(<<-MANIFEST, node) class theclass { @notify { "testing": message => "good message" } } SomeResource <| |> MANIFEST end.to raise_error(/Resource type someresource doesn't exist/) end it 'allows query for literal undef' do expect_the_message_to_be(["foo::baz::quux"], <<-MANIFEST) define foo ($x = undef, $y = undef) { notify { 'testing': message => "foo::${x}::${y}" } } foo { 'bar': y => 'quux' } Foo <| x == undef |> { x => 'baz' } MANIFEST end context "overrides" do it "modifies an existing array" do expect_the_message_to_be([["original message", "extra message"]], <<-MANIFEST) @notify { "testing": message => ["original message"] } Notify <| |> { message +> "extra message" } MANIFEST end it "converts a scalar to an array" do expect_the_message_to_be([["original message", "extra message"]], <<-MANIFEST) @notify { "testing": message => "original message" } Notify <| |> { message +> "extra message" } MANIFEST end it "collects with override when inside a class (#10963)" do expect_the_message_to_be(["overridden message"], <<-MANIFEST) @notify { "testing": message => "original message" } include collector_test class collector_test { Notify <| |> { message => "overridden message" } } MANIFEST end it "collects with override when inside a define (#10963)" do expect_the_message_to_be(["overridden message"], <<-MANIFEST) @notify { "testing": message => "original message" } collector_test { testing: } define collector_test() { Notify <| |> { message => "overridden message" } } MANIFEST end # Catches regression in implemented behavior, this is not to be taken as this is the wanted behavior # but it has been this way for a long time. it "collects and overrides user defined resources immediately (before queue is evaluated)" do expect_the_message_to_be(["overridden"], <<-MANIFEST) define foo($message) { notify { "testing": message => $message } } foo { test: message => 'given' } Foo <| |> { message => 'overridden' } MANIFEST end # Catches regression in implemented behavior, this is not to be taken as this is the wanted behavior # but it has been this way for a long time. it "collects and overrides user defined resources immediately (virtual resources not queued)" do expect_the_message_to_be(["overridden"], <<-MANIFEST) define foo($message) { @notify { "testing": message => $message } } foo { test: message => 'given' } Notify <| |> # must be collected or the assertion does not find it Foo <| |> { message => 'overridden' } MANIFEST end # Catches regression in implemented behavior, this is not to be taken as this is the wanted behavior # but it has been this way for a long time. # Note difference from none +> case where the override takes effect it "collects and overrides user defined resources with +>" do expect_the_message_to_be([["given", "overridden"]], <<-MANIFEST) define foo($message) { notify { "$name": message => $message } } foo { test: message => ['given'] } Notify <| |> { message +> ['overridden'] } MANIFEST end it "collects and overrides virtual resources multiple times using multiple collects" do expect_the_message_to_be(["overridden2"], <<-MANIFEST) @notify { "testing": message => "original" } Notify <| |> { message => 'overridden1' } Notify <| |> { message => 'overridden2' } MANIFEST end it "collects and overrides non virtual resources multiple times using multiple collects" do expect_the_message_to_be(["overridden2"], <<-MANIFEST) notify { "testing": message => "original" } Notify <| |> { message => 'overridden1' } Notify <| |> { message => 'overridden2' } MANIFEST end context 'when overriding an already evaluated resource' do let(:manifest) { <<-MANIFEST } define foo($message) { notify { "testing": message => $message } } foo { test: message => 'given' } define delayed { Foo <| |> { message => 'overridden' } } delayed {'do it now': } MANIFEST it 'and --strict=off, it silently skips the override' do Puppet[:strict] = :off expect_the_message_to_be(['given'], manifest) expect(warnings).to be_empty end it 'and --strict=warning, it warns about the attempt to override and skips it' do Puppet[:strict] = :warning expect_the_message_to_be(['given'], manifest) expect(warnings).to include( /Attempt to override an already evaluated resource, defined at \(line: 4\), with new values \(line: 6\)/) end it 'and --strict=error, it fails compilation' do Puppet[:strict] = :error expect { compile_to_catalog(manifest) }.to raise_error( /Attempt to override an already evaluated resource, defined at \(line: 4\), with new values \(line: 6\)/) expect(warnings).to be_empty end end end end end puppet-5.5.10/spec/integration/parser/conditionals_spec.rb0000644005276200011600000000416513417161721023616 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' describe "Evaluation of Conditionals" do include PuppetSpec::Compiler include Matchers::Resource context "a catalog built with conditionals" do it "evaluates an if block correctly" do catalog = compile_to_catalog(<<-CODE) if( 1 == 1) { notify { 'if': } } elsif(2 == 2) { notify { 'elsif': } } else { notify { 'else': } } CODE expect(catalog).to have_resource("Notify[if]") end it "evaluates elsif block" do catalog = compile_to_catalog(<<-CODE) if( 1 == 3) { notify { 'if': } } elsif(2 == 2) { notify { 'elsif': } } else { notify { 'else': } } CODE expect(catalog).to have_resource("Notify[elsif]") end it "reaches the else clause if no expressions match" do catalog = compile_to_catalog(<<-CODE) if( 1 == 2) { notify { 'if': } } elsif(2 == 3) { notify { 'elsif': } } else { notify { 'else': } } CODE expect(catalog).to have_resource("Notify[else]") end it "evalutes false to false" do catalog = compile_to_catalog(<<-CODE) if false { } else { notify { 'false': } } CODE expect(catalog).to have_resource("Notify[false]") end it "evaluates the string 'false' as true" do catalog = compile_to_catalog(<<-CODE) if 'false' { notify { 'true': } } else { notify { 'false': } } CODE expect(catalog).to have_resource("Notify[true]") end it "evaluates undefined variables as false" do catalog = compile_to_catalog(<<-CODE) if $undef_var { } else { notify { 'undef': } } CODE expect(catalog).to have_resource("Notify[undef]") end it "evaluates empty string as true" do catalog = compile_to_catalog(<<-CODE) if '' { notify { 'true': } } else { notify { 'empty': } } CODE expect(catalog).to have_resource("Notify[true]") end end end puppet-5.5.10/spec/integration/parser/dynamic_scoping_spec.rb0000644005276200011600000000313313417161721024270 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' require 'puppet/parser/parser_factory' require 'puppet_spec/compiler' require 'puppet_spec/pops' require 'puppet_spec/scope' require 'matchers/resource' # These tests are in a separate file since othr compiler related tests have # been dramatically changed between 3.x and 4.x and it is a pain to merge # them. # describe "Puppet::Parser::Compiler when dealing with relative naming" do include PuppetSpec::Compiler include Matchers::Resource describe "the compiler when using 4.x parser and evaluator" do it "should use absolute references even if references are not anchored" do node = Puppet::Node.new("testnodex") catalog = compile_to_catalog(<<-PP, node) class foo::thing { notify {"from foo::thing":} } class thing { notify {"from ::thing":} } class foo { # include thing class {'thing':} } include foo PP catalog = Puppet::Parser::Compiler.compile(node) expect(catalog).to have_resource("Notify[from ::thing]") end it "should use absolute references when references are absolute" do node = Puppet::Node.new("testnodex") catalog = compile_to_catalog(<<-PP, node) class foo::thing { notify {"from foo::thing":} } class thing { notify {"from ::thing":} } class foo { # include thing class {'::thing':} } include foo PP catalog = Puppet::Parser::Compiler.compile(node) expect(catalog).to have_resource("Notify[from ::thing]") end end end puppet-5.5.10/spec/integration/parser/environment_spec.rb0000644005276200011600000000263413417161721023473 0ustar jenkinsjenkinsrequire 'spec_helper' describe "A parser environment setting" do let(:confdir) { Puppet[:confdir] } let(:environmentpath) { File.expand_path("envdir", confdir) } let(:testingdir) { File.join(environmentpath, "testing") } before(:each) do FileUtils.mkdir_p(testingdir) end it "selects the given parser when compiling" do manifestsdir = File.expand_path("manifests", confdir) FileUtils.mkdir_p(manifestsdir) File.open(File.join(testingdir, "environment.conf"), "w") do |f| f.puts(<<-ENVCONF) parser='future' manifest =#{manifestsdir} ENVCONF end File.open(File.join(confdir, "puppet.conf"), "w") do |f| f.puts(<<-EOF) environmentpath=#{environmentpath} parser='current' EOF end File.open(File.join(manifestsdir, "site.pp"), "w") do |f| f.puts("notice( [1,2,3].map |$x| { $x*10 })") end expect { a_catalog_compiled_for_environment('testing') }.to_not raise_error end def a_catalog_compiled_for_environment(envname) Puppet.initialize_settings expect(Puppet[:environmentpath]).to eq(environmentpath) node = Puppet::Node.new('testnode', :environment => 'testing') expect(node.environment).to eq(Puppet.lookup(:environments).get('testing')) Puppet.override(:current_environment => Puppet.lookup(:environments).get('testing')) do Puppet::Parser::Compiler.compile(node) end end end puppet-5.5.10/spec/integration/parser/node_spec.rb0000644005276200011600000001276113417161721022056 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' describe 'node statements' do include PuppetSpec::Compiler include Matchers::Resource context 'nodes' do it 'selects a node where the name is just a number' do # Future parser doesn't allow a number in this position catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("5")) node 5 { notify { 'matched': } } MANIFEST expect(catalog).to have_resource('Notify[matched]') end it 'selects the node with a matching name' do catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodename")) node noden {} node nodename { notify { matched: } } node name {} MANIFEST expect(catalog).to have_resource('Notify[matched]') end it 'prefers a node with a literal name over one with a regex' do catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodename")) node /noden.me/ { notify { ignored: } } node nodename { notify { matched: } } MANIFEST expect(catalog).to have_resource('Notify[matched]') end it 'selects a node where one of the names matches' do catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodename")) node different, nodename, other { notify { matched: } } MANIFEST expect(catalog).to have_resource('Notify[matched]') end it 'arbitrarily selects one of the matching nodes' do catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodename")) node /not/ { notify { 'is not matched': } } node /name.*/ { notify { 'could be matched': } } node /na.e/ { notify { 'could also be matched': } } MANIFEST expect([catalog.resource('Notify[could be matched]'), catalog.resource('Notify[could also be matched]')].compact).to_not be_empty end it 'selects a node where one of the names matches with a mixture of literals and regex' do catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodename")) node different, /name/, other { notify { matched: } } MANIFEST expect(catalog).to have_resource('Notify[matched]') end it 'that have regex names should not collide with matching class names' do catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("foo")) class foo { $bar = 'one' } node /foo/ { $bar = 'two' include foo notify{"${::foo::bar}":} } MANIFEST expect(catalog).to have_resource('Notify[one]') end it 'does not raise an error with regex and non-regex node names are the same' do expect do compile_to_catalog(<<-MANIFEST) node /a.*(c)?/ { } node 'a.c' { } MANIFEST end.not_to raise_error end it 'errors when two nodes with regexes collide after some regex syntax is removed' do expect do compile_to_catalog(<<-MANIFEST) node /a.*(c)?/ { } node /a.*c/ { } MANIFEST end.to raise_error(Puppet::Error, /Node '__node_regexp__a.c' is already defined/) end it 'provides captures from the regex in the node body' do catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodename")) node /(.*)/ { notify { "$1": } } MANIFEST expect(catalog).to have_resource('Notify[nodename]') end it 'selects the node with the matching regex' do catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodename")) node /node.*/ { notify { matched: } } MANIFEST expect(catalog).to have_resource('Notify[matched]') end it 'selects a node that is a literal string' do catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("node.name")) node 'node.name' { notify { matched: } } MANIFEST expect(catalog).to have_resource('Notify[matched]') end it 'selects a node that is a prefix of the agent name' do Puppet[:strict_hostname_checking] = false catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("node.name.com")) node 'node.name' { notify { matched: } } MANIFEST expect(catalog).to have_resource('Notify[matched]') end it 'does not treat regex symbols as a regex inside a string literal' do catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodexname")) node 'node.name' { notify { 'not matched': } } node 'nodexname' { notify { 'matched': } } MANIFEST expect(catalog).to have_resource('Notify[matched]') end it 'errors when two nodes have the same name' do expect do compile_to_catalog(<<-MANIFEST) node name { } node 'name' { } MANIFEST end.to raise_error(Puppet::Error, /Node 'name' is already defined/) end it 'is unable to parse a name that is an invalid number' do expect do compile_to_catalog('node 5name {} ') end.to raise_error(Puppet::Error, /Illegal number '5name'/) end it 'parses a node name that is dotted numbers' do catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("1.2.3.4")) node 1.2.3.4 { notify { matched: } } MANIFEST expect(catalog).to have_resource('Notify[matched]') end it 'raises error for node inheritance' do expect do compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodename")) node default {} node nodename inherits default { } MANIFEST end.to raise_error(/Node inheritance is not supported in Puppet >= 4\.0\.0/) end end end puppet-5.5.10/spec/integration/parser/parameter_defaults_spec.rb0000644005276200011600000002740313417161721024777 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/language' ['Function', 'EPP'].each do |call_type| describe "#{call_type} parameter default expressions" do let! (:func_bodies) { [ '{}', '{ notice("\$a == ${a}") }', '{ notice("\$a == ${a}") notice("\$b == ${b}") }', '{ notice("\$a == ${a}") notice("\$b == ${b}") notice("\$c == ${c}") }' ] } let! (:epp_bodies) { [ '', '<% notice("\$a == ${a}") %>', '<% notice("\$a == ${a}") notice("\$b == ${b}") %>', '<% notice("\$a == ${a}") notice("\$b == ${b}") notice("\$c == ${c}") %>' ] } let! (:param_names) { ('a'..'c').to_a } let (:call_type) { call_type } let (:compiler) { Puppet::Parser::Compiler.new(Puppet::Node.new('specification')) } let (:topscope) { compiler.topscope } def collect_notices(code) logs = [] Puppet[:code] = code Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do compiler.compile do |catalog| yield catalog end end logs.select { |log| log.level == :notice }.map { |log| log.message } end # @param arg_list [String] comma separated parameter declarations. Not enclosed in parenthesis # @param body [String,Integer] verbatim body or index of an entry in func_bodies # @param call_params [Array[#to_s]] array of call parameters # @return [Array] array of notice output entries # def eval_collect_notices(arg_list, body, call_params) call = call_params.is_a?(String) ? call_params : "example(#{call_params.map {|p| p.nil? ? 'undef' : (p.is_a?(String) ? "'#{p}'" : p )}.join(',')})" body = func_bodies[body] if body.is_a?(Integer) evaluator = Puppet::Pops::Parser::EvaluatingParser.new() collect_notices("function example(#{arg_list}) #{body}") do evaluator.evaluate_string(compiler.topscope, call) end end # @param arg_list [String] comma separated parameter declarations. Not enclosed in parenthesis # @param body [String,Integer] verbatim body or index of an entry in bodies # @param call_params [Array] array of call parameters # @param code [String] code to evaluate # @return [Array] array of notice output entries # def epp_eval_collect_notices(arg_list, body, call_params, code, inline_epp) body = body.is_a?(Integer) ? epp_bodies[body] : body.strip source = "<%| #{arg_list} |%>#{body}" named_params = call_params.reduce({}) {|h, v| h[param_names[h.size]] = v; h } collect_notices(code) do if inline_epp Puppet::Pops::Evaluator::EppEvaluator.inline_epp(compiler.topscope, source, named_params) else file = Tempfile.new(['epp-script', '.epp']) begin file.write(source) file.close Puppet::Pops::Evaluator::EppEvaluator.epp(compiler.topscope, file.path, 'test', named_params) ensure file.unlink end end end end # evaluates a function or EPP call, collects notice output in a log and compares log to expected result # # @param decl [Array[]] two element array with argument list declaration and body. Body can a verbatim string # or an integer index of an entry in bodies # @param call_params [Array[#to_s]] array of call parameters # @param code [String] code to evaluate. Only applicable when call_type == 'EPP' # @param inline_epp [Boolean] true for inline_epp, false for file based epp, Only applicable when call_type == 'EPP' # @return [Array] array of notice output entries # def expect_log(decl, call_params, result, code = 'undef', inline_epp = true) if call_type == 'Function' expect(eval_collect_notices(decl[0], decl[1], call_params)).to include(*result) else expect(epp_eval_collect_notices(decl[0], decl[1], call_params, code, inline_epp)).to include(*result) end end # evaluates a function or EPP call and expects a failure # # @param decl [Array[]] two element array with argument list declaration and body. Body can a verbatim string # or an integer index of an entry in bodies # @param call_params [Array[#to_s]] array of call parameters # @param text [String,Regexp] expected error message def expect_fail(decl, call, text, inline_epp = true) if call_type == 'Function' expect{eval_collect_notices(decl[0], decl[1], call) }.to raise_error(StandardError, text) else expect{epp_eval_collect_notices(decl[0], decl[1], call, 'undef', inline_epp) }.to raise_error(StandardError, text) end end context 'that references a parameter to the left that has no default' do let!(:params) { [ <<-SOURCE, 2 ] $a, $b = $a SOURCE } it 'fails when no value is provided for required first parameter', :if => call_type == 'Function' do expect_fail(params, [], /expects between 1 and 2 arguments, got none/) end it 'fails when no value is provided for required first parameter', :if => call_type == 'EPP' do expect_fail(params, [], /expects a value for parameter \$a/) end it "will use the referenced parameter's given value" do expect_log(params, [2], ['$a == 2', '$b == 2']) end it 'will not be evaluated when a value is given' do expect_log(params, [2, 5], ['$a == 2', '$b == 5']) end end context 'that references a parameter to the left that has a default' do let!(:params) { [ <<-SOURCE, 2 ] $a = 10, $b = $a SOURCE } it "will use the referenced parameter's default value when no value is given for the referenced parameter" do expect_log(params, [], ['$a == 10', '$b == 10']) end it "will use the referenced parameter's given value" do expect_log(params, [2], ['$a == 2', '$b == 2']) end it 'will not be evaluated when a value is given' do expect_log(params, [2, 5], ['$a == 2', '$b == 5']) end end context 'that references a variable to the right' do let!(:params) { [ <<-SOURCE, 3 ] $a = 10, $b = $c, $c = 20 SOURCE } it 'fails when the reference is evaluated' do expect_fail(params, [1], /default expression for \$b tries to illegally access not yet evaluated \$c/) end it 'does not fail when a value is given for the culprit parameter' do expect_log(params, [1,2], ['$a == 1', '$b == 2', '$c == 20']) end it 'does not fail when all values are given' do expect_log(params, [1,2,3], ['$a == 1', '$b == 2', '$c == 3']) end end context 'with regular expressions' do it "evaluates unset match scope parameter's to undef" do expect_log([<<-SOURCE, 2], [], ['$a == ', '$b == ']) $a = $0, $b = $1 SOURCE end it 'does not leak match variables from one expression to the next' do expect_log([<<-SOURCE, 2], [], ['$a == [true, h, ello]', '$b == ']) $a = ['hello' =~ /(h)(.*)/, $1, $2], $b = $1 SOURCE end it 'can evaluate expressions in separate match scopes' do expect_log([<<-SOURCE, 3], [], ['$a == [true, h, ell, o]', '$b == [true, h, i, ]', '$c == ']) $a = ['hello' =~ /(h)(.*)(o)/, $1, $2, $3], $b = ['hi' =~ /(h)(.*)/, $1, $2, $3], $c = $1 SOURCE end it 'can have nested match expressions' do expect_log([<<-SOURCE, 2], [], ['$a == [true, h, oo, h, i]', '$b == '] ) $a = ['hi' =~ /(h)(.*)/, $1, if'foo' =~ /f(oo)/ { $1 }, $1, $2], $b = $0 SOURCE end it 'can not see match scope from calling scope', :if => call_type == 'Function' do expect_log([<<-SOURCE, <<-BODY], <<-CALL, ['$a == ']) $a = $0 SOURCE { notice("\\$a == ${a}") } function caller() { example() } BODY $tmp = 'foo' =~ /(f)(o)(o)/ caller() CALL end context 'matches in calling scope', :if => call_type == 'EPP' do it 'are available when using inlined epp' do # Note that CODE is evaluated before the EPP is evaluated # expect_log([<<-SOURCE, <<-BODY], [], ['$ax == true', '$bx == foo'], <<-CODE, true) $a = $tmp, $b = $0 SOURCE <% called_from_template($a, $b) %> BODY function called_from_template($ax, $bx) { notice("\\$ax == $ax") notice("\\$bx == $bx") } $tmp = 'foo' =~ /(f)(o)(o)/ CODE end it 'are not available when using epp file' do # Note that CODE is evaluated before the EPP is evaluated # expect_log([<<-SOURCE, <<-BODY], [], ['$ax == true', '$bx == '], <<-CODE, false) $a = $tmp, $b = $0 SOURCE <% called_from_template($a, $b) %> BODY function called_from_template($ax, $bx) { notice("\\$ax == $ax") notice("\\$bx == $bx") } $tmp = 'foo' =~ /(f)(o)(o)/ CODE end end it 'will allow nested lambdas to access enclosing match scope' do expect_log([<<-SOURCE, 1], [], ['$a == [1-ello, 2-ello, 3-ello]']) $a = case "hello" { /(h)(.*)/ : { [1,2,3].map |$x| { "$x-$2" } } } SOURCE end it "will not make match scope available to #{call_type} body" do expect_log([<<-SOURCE, call_type == 'Function' ? <<-BODY : <<-EPP_BODY], [], ['Yes']) $a = "hello" =~ /.*/ SOURCE { notice("Y${0}es") } BODY <% notice("Y${0}es") %> EPP_BODY end it 'can access earlier match results when produced using the match function' do expect_log([<<-SOURCE, 3], [], ['$a == [hello, h, ello]', '$b == hello', '$c == h']) $a = 'hello'.match(/(h)(.*)/), $b = $a[0], $c = $a[1] SOURCE end end context 'will not permit assignments' do it 'at top level' do expect_fail([<<-SOURCE, 0], [], /Syntax error at '='/) $a = $x = $0 SOURCE end it 'in arrays' do expect_fail([<<-SOURCE, 0], [], /Assignment not allowed here/) $a = [$x = 3] SOURCE end it 'of variable with the same name as a subsequently declared parameter' do expect_fail([<<-SOURCE, 0], [], /Assignment not allowed here/) $a = ($b = 3), $b = 5 SOURCE end it 'of variable with the same name as a previously declared parameter' do expect_fail([<<-SOURCE, 0], [], /Assignment not allowed here/) $a = 10, $b = ($a = 10) SOURCE end end it 'will permit assignments in nested scope' do expect_log([<<-SOURCE, 3], [], ['$a == [1, 2, 3]', '$b == 0', '$c == [6, 12, 18]']) $a = [1,2,3], $b = 0, $c = $a.map |$x| { $b = $x; $b * $a.reduce |$x, $y| {$x + $y} } SOURCE end it 'will not permit duplicate parameter names' do expect_fail([<<-SOURCE, 0], [], /The parameter 'a' is declared more than once/ ) $a = 2, $a = 5 SOURCE end it 'will permit undef for optional parameters' do expect_log([<<-SOURCE, 1], [nil], ['$a == ']) Optional[Integer] $a SOURCE end it 'undef will override parameter default', :if => call_type == 'Function' do expect_log([<<-SOURCE, 1], [nil], ['$a == ']) Optional[Integer] $a = 4 SOURCE end it 'undef will not override parameter default', :unless => call_type == 'Function' do expect_log([<<-SOURCE, 1], [nil], ['$a == 4']) Optional[Integer] $a = 4 SOURCE end end end puppet-5.5.10/spec/integration/parser/pcore_resource_spec.rb0000644005276200011600000002035613417161721024147 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/files' require 'puppet_spec/compiler' require 'puppet/face' describe 'when pcore described resources types are in use' do include PuppetSpec::Files include PuppetSpec::Compiler let(:genface) { Puppet::Face[:generate, :current] } context "in an environment with two modules" do let(:dir) do dir_containing('environments', { 'production' => { 'environment.conf' => "modulepath = modules", 'manifests' => { 'site.pp' => "" }, 'modules' => { 'm1' => { 'lib' => { 'puppet' => { 'type' => { 'test1.rb' => <<-EOF module Puppet Type.newtype(:test1) do @doc = "Docs for resource" newproperty(:message) do desc "Docs for 'message' property" end newparam(:name) do desc "Docs for 'name' parameter" isnamevar end newparam(:whatever) do desc "Docs for 'whatever' parameter" end end; end EOF } } }, }, 'm2' => { 'lib' => { 'puppet' => { 'type' => { 'test2.rb' => <<-EOF, module Puppet Type.newtype(:test2) do @doc = "Docs for resource" @isomorphic = false newproperty(:message) do desc "Docs for 'message' property" end newparam(:name) do desc "Docs for 'name' parameter" isnamevar end newparam(:color) do desc "Docs for 'color' parameter" newvalues(:red, :green, :blue, /#[0-9A-Z]{6}/) end end;end EOF 'test3.rb' => <<-RUBY, Puppet::Type.newtype(:test3) do newproperty(:message) newparam(:a) { isnamevar } newparam(:b) { isnamevar } newparam(:c) { isnamevar } def self.title_patterns [ [ /^((.+)\\/(.*))$/, [[:a], [:b], [:c]]] ] end end RUBY 'cap.rb' => <<-EOF module Puppet Type.newtype(:cap, :is_capability => true) do @doc = "Docs for capability" @isomorphic = false newproperty(:message) do desc "Docs for 'message' property" end end;end EOF } } }, } }}}) end let(:modulepath) do File.join(dir, 'production', 'modules') end let(:m1) do File.join(modulepath, 'm1') end let(:m2) do File.join(modulepath, 'm2') end let(:outputdir) do File.join(dir, 'production', '.resource_types') end around(:each) do |example| Puppet.settings.initialize_global_settings Puppet[:manifest] = '' loader = Puppet::Environments::Directories.new(dir, []) Puppet.override(:environments => loader) do Puppet.override(:current_environment => loader.get('production')) do example.run end end end it 'can use generated types to compile a catalog' do genface.types catalog = compile_to_catalog(<<-MANIFEST) test1 { 'a': message => 'a works' } # Several instances of the type can be created - implicit test test1 { 'another a': message => 'another a works' } test2 { 'b': message => 'b works' } test3 { 'x/y': message => 'x/y works' } cap { 'c': message => 'c works' } MANIFEST expect(catalog.resource(:test1, "a")['message']).to eq('a works') expect(catalog.resource(:test2, "b")['message']).to eq('b works') expect(catalog.resource(:test3, "x/y")['message']).to eq('x/y works') expect(catalog.resource(:cap, "c")['message']).to eq('c works') end it 'the validity of attribute names are checked' do genface.types expect do compile_to_catalog(<<-MANIFEST) test1 { 'a': mezzage => 'a works' } MANIFEST end.to raise_error(/no parameter named 'mezzage'/) end it 'meta-parameters such as noop can be used' do genface.types catalog = compile_to_catalog(<<-MANIFEST) test1 { 'a': message => 'noop works', noop => true } MANIFEST expect(catalog.resource(:test1, "a")['noop']).to eq(true) end it 'capability is propagated to the catalog' do genface.types catalog = compile_to_catalog(<<-MANIFEST) test2 { 'r': message => 'a resource' } cap { 'c': message => 'a cap' } MANIFEST expect(catalog.resource(:test2, "r").is_capability?).to eq(false) expect(catalog.resource(:cap, "c").is_capability?).to eq(true) end it 'a generated type describes if it is isomorphic' do generate_and_in_a_compilers_context do |compiler| t1 = find_resource_type(compiler.topscope, 'test1') expect(t1.isomorphic?).to be(true) t2 = find_resource_type(compiler.topscope, 'test2') expect(t2.isomorphic?).to be(false) end end it 'a generated type describes if it is a capability' do generate_and_in_a_compilers_context do |compiler| t1 = find_resource_type(compiler.topscope, 'test1') expect(t1.is_capability?).to be(false) t2 = find_resource_type(compiler.topscope, 'cap') expect(t2.is_capability?).to be(true) end end it 'a generated type returns parameters defined in pcore' do generate_and_in_a_compilers_context do |compiler| t1 = find_resource_type(compiler.topscope, 'test1') expect(t1.parameters.size).to be(2) expect(t1.parameters[0].name).to eql('name') expect(t1.parameters[1].name).to eql('whatever') end end it 'a generated type picks up and returns if a parameter is a namevar' do generate_and_in_a_compilers_context do |compiler| t1 = find_resource_type(compiler.topscope, 'test1') expect(t1.parameters[0].name_var).to be(true) expect(t1.parameters[1].name_var).to be(false) end end it 'a generated type returns properties defined in pcore' do generate_and_in_a_compilers_context do |compiler| t1 = find_resource_type(compiler.topscope, 'test1') expect(t1.properties.size).to be(1) expect(t1.properties[0].name).to eql('message') end end it 'a generated type returns [[/(.*)/m, ]] as default title_pattern when there is a namevar but no pattern specified' do generate_and_in_a_compilers_context do |compiler| t1 = find_resource_type(compiler.topscope, 'test1') expect(t1.title_patterns.size).to be(1) expect(t1.title_patterns[0][0]).to eql(/(?m-ix:(.*))/) end end it "the compiler asserts the type of parameters" do pending "assertion of parameter types not yet implemented" genface.types expect { compile_to_catalog(<<-MANIFEST) test2 { 'b': color => 'white is not a color' } MANIFEST }.to raise_error(/an error indicating that color cannot have that value/) # ERROR TBD. end end def find_resource_type(scope, name) Puppet::Pops::Evaluator::Runtime3ResourceSupport.find_resource_type(scope, name) end def generate_and_in_a_compilers_context(&block) genface.types # Since an instance of a compiler is needed and it starts an initial import that evaluates # code, and that code will be loaded from manifests with a glob (go figure) # the only way to stop that is to set 'code' to something as that overrides "importing" files. Puppet[:code] = "undef" node = Puppet::Node.new('test') # All loading must be done in a context configured as the compiler does it. # (Therefore: use the context a compiler creates as this test logic must otherwise # know how to do this). # compiler = Puppet::Parser::Compiler.new(node) Puppet::override(compiler.context_overrides) do block.call(compiler) end end end puppet-5.5.10/spec/integration/parser/resource_expressions_spec.rb0000644005276200011600000002617213417161721025423 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/language' describe "Puppet resource expressions" do extend PuppetSpec::Language produces( "$a = notify $b = example $c = { message => hello } @@Resource[$a] { $b: * => $c } realize(Resource[$a, $b]) " => "Notify[example][message] == 'hello'") context "resource titles" do produces( "notify { thing: }" => "defined(Notify[thing])", "$x = thing notify { $x: }" => "defined(Notify[thing])", "notify { [thing]: }" => "defined(Notify[thing])", "$x = [thing] notify { $x: }" => "defined(Notify[thing])", "notify { [[nested, array]]: }" => "defined(Notify[nested]) and defined(Notify[array])", "$x = [[nested, array]] notify { $x: }" => "defined(Notify[nested]) and defined(Notify[array])", "notify { []: }" => [], # this asserts nothing added "$x = [] notify { $x: }" => [], # this asserts nothing added "notify { default: }" => "!defined(Notify['default'])", # nothing created because this is just a local default "$x = default notify { $x: }" => "!defined(Notify['default'])") fails( "notify { '': }" => /Empty string title/, "$x = '' notify { $x: }" => /Empty string title/, "notify { 1: }" => /Illegal title type.*Expected String, got Integer/, "$x = 1 notify { $x: }" => /Illegal title type.*Expected String, got Integer/, "notify { [1]: }" => /Illegal title type.*Expected String, got Integer/, "$x = [1] notify { $x: }" => /Illegal title type.*Expected String, got Integer/, "notify { 3.0: }" => /Illegal title type.*Expected String, got Float/, "$x = 3.0 notify { $x: }" => /Illegal title type.*Expected String, got Float/, "notify { [3.0]: }" => /Illegal title type.*Expected String, got Float/, "$x = [3.0] notify { $x: }" => /Illegal title type.*Expected String, got Float/, "notify { true: }" => /Illegal title type.*Expected String, got Boolean/, "$x = true notify { $x: }" => /Illegal title type.*Expected String, got Boolean/, "notify { [true]: }" => /Illegal title type.*Expected String, got Boolean/, "$x = [true] notify { $x: }" => /Illegal title type.*Expected String, got Boolean/, "notify { [false]: }" => /Illegal title type.*Expected String, got Boolean/, "$x = [false] notify { $x: }" => /Illegal title type.*Expected String, got Boolean/, "notify { undef: }" => /Missing title.*undef/, "$x = undef notify { $x: }" => /Missing title.*undef/, "notify { [undef]: }" => /Missing title.*undef/, "$x = [undef] notify { $x: }" => /Missing title.*undef/, "notify { {nested => hash}: }" => /Illegal title type.*Expected String, got Hash/, "$x = {nested => hash} notify { $x: }" => /Illegal title type.*Expected String, got Hash/, "notify { [{nested => hash}]: }" => /Illegal title type.*Expected String, got Hash/, "$x = [{nested => hash}] notify { $x: }" => /Illegal title type.*Expected String, got Hash/, "notify { /regexp/: }" => /Illegal title type.*Expected String, got Regexp/, "$x = /regexp/ notify { $x: }" => /Illegal title type.*Expected String, got Regexp/, "notify { [/regexp/]: }" => /Illegal title type.*Expected String, got Regexp/, "$x = [/regexp/] notify { $x: }" => /Illegal title type.*Expected String, got Regexp/, "notify { [dupe, dupe]: }" => /The title 'dupe' has already been used/, "notify { dupe:; dupe: }" => /The title 'dupe' has already been used/, "notify { [dupe]:; dupe: }" => /The title 'dupe' has already been used/, "notify { [default, default]:}" => /The title 'default' has already been used/, "notify { default:; default:}" => /The title 'default' has already been used/, "notify { [default]:; default:}" => /The title 'default' has already been used/) end context "type names" do produces( "notify { testing: }" => "defined(Notify[testing])") produces( "$a = notify; Resource[$a] { testing: }" => "defined(Notify[testing])") produces( "Resource['notify'] { testing: }" => "defined(Notify[testing])") produces( "Resource[sprintf('%s', 'notify')] { testing: }" => "defined(Notify[testing])") produces( "$a = ify; Resource[\"not$a\"] { testing: }" => "defined(Notify[testing])") produces( "Notify { testing: }" => "defined(Notify[testing])") produces( "Resource[Notify] { testing: }" => "defined(Notify[testing])") produces( "Resource['Notify'] { testing: }" => "defined(Notify[testing])") produces( "class a { notify { testing: } } class { a: }" => "defined(Notify[testing])") produces( "class a { notify { testing: } } Class { a: }" => "defined(Notify[testing])") produces( "class a { notify { testing: } } Resource['class'] { a: }" => "defined(Notify[testing])") produces( "define a::b { notify { testing: } } a::b { title: }" => "defined(Notify[testing])") produces( "define a::b { notify { testing: } } A::B { title: }" => "defined(Notify[testing])") produces( "define a::b { notify { testing: } } Resource['a::b'] { title: }" => "defined(Notify[testing])") fails( "'class' { a: }" => /Illegal Resource Type expression.*got String/) fails( "'' { testing: }" => /Illegal Resource Type expression.*got String/) fails( "1 { testing: }" => /Illegal Resource Type expression.*got Integer/) fails( "3.0 { testing: }" => /Illegal Resource Type expression.*got Float/) fails( "true { testing: }" => /Illegal Resource Type expression.*got Boolean/) fails( "'not correct' { testing: }" => /Illegal Resource Type expression.*got String/) fails( "Notify[hi] { testing: }" => /Illegal Resource Type expression.*got Notify\['hi'\]/) fails( "[Notify, File] { testing: }" => /Illegal Resource Type expression.*got Array\[Type\[Resource\]\]/) fails( "define a::b { notify { testing: } } 'a::b' { title: }" => /Illegal Resource Type expression.*got String/) fails( "Does::Not::Exist { title: }" => /Resource type not found: Does::Not::Exist/) end context "local defaults" do produces( "notify { example:; default: message => defaulted }" => "Notify[example][message] == 'defaulted'", "notify { example: message => specific; default: message => defaulted }" => "Notify[example][message] == 'specific'", "notify { example: message => undef; default: message => defaulted }" => "Notify[example][message] == undef", "notify { [example, other]: ; default: message => defaulted }" => "Notify[example][message] == 'defaulted' and Notify[other][message] == 'defaulted'", "notify { [example, default]: message => set; other: }" => "Notify[example][message] == 'set' and Notify[other][message] == 'set'") end context "order of evaluation" do fails("notify { hi: message => value; bye: message => Notify[hi][message] }" => /Resource not found: Notify\['hi'\]/) produces("notify { hi: message => (notify { param: message => set }); bye: message => Notify[param][message] }" => "defined(Notify[hi]) and Notify[bye][message] == 'set'") fails("notify { bye: message => Notify[param][message]; hi: message => (notify { param: message => set }) }" => /Resource not found: Notify\['param'\]/) end context "parameters" do produces( "notify { title: message => set }" => "Notify[title][message] == 'set'", "$x = set notify { title: message => $x }" => "Notify[title][message] == 'set'", "notify { title: *=> { message => set } }" => "Notify[title][message] == 'set'", "$x = { message => set } notify { title: * => $x }" => "Notify[title][message] == 'set'", # picks up defaults "$x = { owner => the_x } $y = { mode => '0666' } $t = '/tmp/x' file { default: * => $x; $t: path => '/somewhere', * => $y }" => "File[$t][mode] == '0666' and File[$t][owner] == 'the_x' and File[$t][path] == '/somewhere'", # explicit wins over default - no error "$x = { owner => the_x, mode => '0777' } $y = { mode => '0666' } $t = '/tmp/x' file { default: * => $x; $t: path => '/somewhere', * => $y }" => "File[$t][mode] == '0666' and File[$t][owner] == 'the_x' and File[$t][path] == '/somewhere'") produces("notify{title:}; Notify[title] { * => { message => set}}" => "Notify[title][message] == 'set'") produces("Notify { * => { message => set}}; notify{title:}" => "Notify[title][message] == 'set'") produces('define foo($x) { notify { "title": message =>"aaa${x}bbb"} } foo{ test: x => undef }' => "Notify[title][message] == 'aaabbb'") produces('define foo($x="xx") { notify { "title": message =>"aaa${x}bbb"} } foo{ test: x => undef }' => "Notify[title][message] == 'aaaxxbbb'") fails("notify { title: unknown => value }" => /no parameter named 'unknown'/) # this really needs to be a better error message. fails("notify { title: * => { hash => value }, message => oops }" => /no parameter named 'hash'/) # should this be a better error message? fails("notify { title: message => oops, * => { hash => value } }" => /no parameter named 'hash'/) fails("notify { title: * => { unknown => value } }" => /no parameter named 'unknown'/) fails(" $x = { mode => '0666' } $y = { owner => the_y } $t = '/tmp/x' file { $t: * => $x, * => $y }" => /Unfolding of attributes from Hash can only be used once per resource body/) end context "virtual" do produces( "@notify { example: }" => "!defined(Notify[example])", "@notify { example: } realize(Notify[example])" => "defined(Notify[example])", "@notify { virtual: message => set } notify { real: message => Notify[virtual][message] }" => "Notify[real][message] == 'set'") end context "exported" do produces( "@@notify { example: }" => "!defined(Notify[example])", "@@notify { example: } realize(Notify[example])" => "defined(Notify[example])", "@@notify { exported: message => set } notify { real: message => Notify[exported][message] }" => "Notify[real][message] == 'set'") end context "explicit undefs" do # PUP-3505 produces(" $x = 10 define foo($x = undef) { notify { example: message => \"'$x'\" } } foo {'blah': x => undef } " => "Notify[example][message] == \"''\"") end end puppet-5.5.10/spec/integration/parser/scope_spec.rb0000644005276200011600000005056313417161721022244 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/compiler' describe "Two step scoping for variables" do include PuppetSpec::Compiler def expect_the_message_to_be(message, node = Puppet::Node.new('the node')) catalog = compile_to_catalog(yield, node) expect(catalog.resource('Notify', 'something')[:message]).to eq(message) end def expect_the_message_not_to_be(message, node = Puppet::Node.new('the node')) catalog = compile_to_catalog(yield, node) expect(catalog.resource('Notify', 'something')[:message]).to_not eq(message) end before :each do Puppet.expects(:deprecation_warning).never end describe "using unsupported operators" do it "issues an error for +=" do expect do compile_to_catalog(<<-MANIFEST) $var = ["top_msg"] node default { $var += ["override"] } MANIFEST end.to raise_error(/The operator '\+=' is no longer supported/) end it "issues an error for -=" do expect do compile_to_catalog(<<-MANIFEST) $var = ["top_msg"] node default { $var -= ["top_msg"] } MANIFEST end.to raise_error(/The operator '-=' is no longer supported/) end it "issues error about built-in variable when reassigning to name" do enc_node = Puppet::Node.new("the_node", { :parameters => { } }) expect { compile_to_catalog("$name = 'never in a 0xF4240 years'", enc_node) }.to raise_error( Puppet::Error, /Cannot reassign built in \(or already assigned\) variable '\$name' \(line: 1(, column: 7)?\) on node the_node/ ) end it "issues error about built-in variable when reassigning to title" do enc_node = Puppet::Node.new("the_node", { :parameters => { } }) expect { compile_to_catalog("$title = 'never in a 0xF4240 years'", enc_node) }.to raise_error( Puppet::Error, /Cannot reassign built in \(or already assigned\) variable '\$title' \(line: 1(, column: 8)?\) on node the_node/ ) end it "when using a template ignores the dynamic value of the var when using the @varname syntax" do expect_the_message_to_be('node_msg') do <<-MANIFEST node default { $var = "node_msg" include foo } class foo { $var = "foo_msg" include bar } class bar { notify { 'something': message => inline_template("<%= @var %>"), } } MANIFEST end end it "when using a template gets the var from an inherited class when using the @varname syntax" do expect_the_message_to_be('Barbamama') do <<-MANIFEST node default { $var = "node_msg" include bar_bamama include foo } class bar_bamama { $var = "Barbamama" } class foo { $var = "foo_msg" include bar } class bar inherits bar_bamama { notify { 'something': message => inline_template("<%= @var %>"), } } MANIFEST end end it "when using a template ignores the dynamic var when it is not present in an inherited class" do expect_the_message_to_be('node_msg') do <<-MANIFEST node default { $var = "node_msg" include bar_bamama include foo } class bar_bamama { } class foo { $var = "foo_msg" include bar } class bar inherits bar_bamama { notify { 'something': message => inline_template("<%= @var %>"), } } MANIFEST end end describe 'handles 3.x/4.x functions' do it 'can call a 3.x function via call_function' do expect_the_message_to_be('yes') do <<-MANIFEST $msg = inline_template('<%= scope().call_function("fqdn_rand", [30]).to_i <= 30 ? "yes" : "no" %>') notify { 'something': message => $msg } MANIFEST end end it 'it can call a 4.x function via call_function' do expect_the_message_to_be('yes') do <<-MANIFEST $msg = inline_template('<%= scope().call_function("with", ["yes"]) { |x| x } %>') notify { 'something': message => $msg } MANIFEST end end end end describe "fully qualified variable names" do it "keeps nodescope separate from topscope" do expect_the_message_to_be('topscope') do <<-MANIFEST $c = "topscope" node default { $c = "nodescope" notify { 'something': message => $::c } } MANIFEST end end end describe "when colliding class and variable names" do it "finds a topscope variable with the same name as a class" do expect_the_message_to_be('topscope') do <<-MANIFEST $c = "topscope" class c { } node default { include c notify { 'something': message => $c } } MANIFEST end end it "finds a node scope variable with the same name as a class" do expect_the_message_to_be('nodescope') do <<-MANIFEST class c { } node default { $c = "nodescope" include c notify { 'something': message => $c } } MANIFEST end end it "finds a class variable when the class collides with a nodescope variable" do expect_the_message_to_be('class') do <<-MANIFEST class c { $b = "class" } node default { $c = "nodescope" include c notify { 'something': message => $c::b } } MANIFEST end end it "finds a class variable when the class collides with a topscope variable" do expect_the_message_to_be('class') do <<-MANIFEST $c = "topscope" class c { $b = "class" } node default { include c notify { 'something': message => $::c::b } } MANIFEST end end end describe "when using shadowing and inheritance" do it "finds values in its local scope" do expect_the_message_to_be('local_msg') do <<-MANIFEST node default { include baz } class foo { } class bar inherits foo { $var = "local_msg" notify { 'something': message => $var, } } class baz { include bar } MANIFEST end end it "finds values in its inherited scope" do expect_the_message_to_be('foo_msg') do <<-MANIFEST node default { include baz } class foo { $var = "foo_msg" } class bar inherits foo { notify { 'something': message => $var, } } class baz { include bar } MANIFEST end end it "prefers values in its local scope over values in the inherited scope" do expect_the_message_to_be('local_msg') do <<-MANIFEST include bar class foo { $var = "inherited" } class bar inherits foo { $var = "local_msg" notify { 'something': message => $var, } } MANIFEST end end it "finds a qualified variable by following inherited scope of the specified scope" do expect_the_message_to_be("from parent") do <<-MANIFEST class c { notify { 'something': message => "$a::b" } } class parent { $b = 'from parent' } class a inherits parent { } node default { $b = "from node" include a include c } MANIFEST end end ['a:.b', '::a::b'].each do |ref| it "does not resolve a qualified name on the form #{ref} against top scope" do expect_the_message_not_to_be("from topscope") do <<-"MANIFEST" class c { notify { 'something': message => "$#{ref}" } } class parent { $not_b = 'from parent' } class a inherits parent { } $b = "from topscope" node default { include a include c } MANIFEST end end end ['a:.b', '::a::b'].each do |ref| it "does not resolve a qualified name on the form #{ref} against node scope" do expect_the_message_not_to_be("from node") do <<-MANIFEST class c { notify { 'something': message => "$a::b" } } class parent { $not_b = 'from parent' } class a inherits parent { } node default { $b = "from node" include a include c } MANIFEST end end end it 'resolves a qualified name in class parameter scope' do expect_the_message_to_be('Does it work? Yes!') do <<-PUPPET class a ( $var1 = 'Does it work?', $var2 = "${a::var1} Yes!" ) { notify { 'something': message => $var2 } } include a PUPPET end end it "finds values in its inherited scope when the inherited class is qualified to the top" do expect_the_message_to_be('foo_msg') do <<-MANIFEST node default { include baz } class foo { $var = "foo_msg" } class bar inherits ::foo { notify { 'something': message => $var, } } class baz { include bar } MANIFEST end end it "prefers values in its local scope over values in the inherited scope when the inherited class is fully qualified" do expect_the_message_to_be('local_msg') do <<-MANIFEST include bar class foo { $var = "inherited" } class bar inherits ::foo { $var = "local_msg" notify { 'something': message => $var, } } MANIFEST end end it "finds values in top scope when the inherited class is qualified to the top" do expect_the_message_to_be('top msg') do <<-MANIFEST $var = "top msg" class foo { } class bar inherits ::foo { notify { 'something': message => $var, } } include bar MANIFEST end end it "finds values in its inherited scope when the inherited class is a nested class that shadows another class at the top" do expect_the_message_to_be('inner baz') do <<-MANIFEST node default { include foo::bar } class baz { $var = "top baz" } class foo { class baz { $var = "inner baz" } class bar inherits foo::baz { notify { 'something': message => $var, } } } MANIFEST end end it "finds values in its inherited scope when the inherited class is qualified to a nested class and qualified to the top" do expect_the_message_to_be('top baz') do <<-MANIFEST node default { include foo::bar } class baz { $var = "top baz" } class foo { class baz { $var = "inner baz" } class bar inherits ::baz { notify { 'something': message => $var, } } } MANIFEST end end it "finds values in its inherited scope when the inherited class is qualified" do expect_the_message_to_be('foo_msg') do <<-MANIFEST node default { include bar } class foo { class baz { $var = "foo_msg" } } class bar inherits foo::baz { notify { 'something': message => $var, } } MANIFEST end end it "prefers values in its inherited scope over those in the node (with intermediate inclusion)" do expect_the_message_to_be('foo_msg') do <<-MANIFEST node default { $var = "node_msg" include baz } class foo { $var = "foo_msg" } class bar inherits foo { notify { 'something': message => $var, } } class baz { include bar } MANIFEST end end it "prefers values in its inherited scope over those in the node (without intermediate inclusion)" do expect_the_message_to_be('foo_msg') do <<-MANIFEST node default { $var = "node_msg" include bar } class foo { $var = "foo_msg" } class bar inherits foo { notify { 'something': message => $var, } } MANIFEST end end it "prefers values in its inherited scope over those from where it is included" do expect_the_message_to_be('foo_msg') do <<-MANIFEST node default { include baz } class foo { $var = "foo_msg" } class bar inherits foo { notify { 'something': message => $var, } } class baz { $var = "baz_msg" include bar } MANIFEST end end it "does not used variables from classes included in the inherited scope" do expect_the_message_to_be('node_msg') do <<-MANIFEST node default { $var = "node_msg" include bar } class quux { $var = "quux_msg" } class foo inherits quux { } class baz { include foo } class bar inherits baz { notify { 'something': message => $var, } } MANIFEST end end it "does not use a variable from a scope lexically enclosing it" do expect_the_message_to_be('node_msg') do <<-MANIFEST node default { $var = "node_msg" include other::bar } class other { $var = "other_msg" class bar { notify { 'something': message => $var, } } } MANIFEST end end it "finds values in its node scope" do expect_the_message_to_be('node_msg') do <<-MANIFEST node default { $var = "node_msg" include baz } class foo { } class bar inherits foo { notify { 'something': message => $var, } } class baz { include bar } MANIFEST end end it "finds values in its top scope" do expect_the_message_to_be('top_msg') do <<-MANIFEST $var = "top_msg" node default { include baz } class foo { } class bar inherits foo { notify { 'something': message => $var, } } class baz { include bar } MANIFEST end end it "prefers variables from the node over those in the top scope" do expect_the_message_to_be('node_msg') do <<-MANIFEST $var = "top_msg" node default { $var = "node_msg" include foo } class foo { notify { 'something': message => $var, } } MANIFEST end end it "finds top scope variables referenced inside a defined type" do expect_the_message_to_be('top_msg') do <<-MANIFEST $var = "top_msg" node default { foo { "testing": } } define foo() { notify { 'something': message => $var, } } MANIFEST end end it "finds node scope variables referenced inside a defined type" do expect_the_message_to_be('node_msg') do <<-MANIFEST $var = "top_msg" node default { $var = "node_msg" foo { "testing": } } define foo() { notify { 'something': message => $var, } } MANIFEST end end end describe "in situations that used to have dynamic lookup" do it "ignores the dynamic value of the var" do expect_the_message_to_be('node_msg') do <<-MANIFEST node default { $var = "node_msg" include foo } class baz { $var = "baz_msg" include bar } class foo inherits baz { } class bar { notify { 'something': message => $var, } } MANIFEST end end it "finds nil when the only set variable is in the dynamic scope" do expect_the_message_to_be(nil) do <<-MANIFEST node default { include baz } class foo { } class bar inherits foo { notify { 'something': message => $var, } } class baz { $var = "baz_msg" include bar } MANIFEST end end it "ignores the value in the dynamic scope for a defined type" do expect_the_message_to_be('node_msg') do <<-MANIFEST node default { $var = "node_msg" include foo } class foo { $var = "foo_msg" bar { "testing": } } define bar() { notify { 'something': message => $var, } } MANIFEST end end it "when using a template ignores the dynamic value of the var when using scope.lookupvar" do expect_the_message_to_be('node_msg') do <<-MANIFEST node default { $var = "node_msg" include foo } class foo { $var = "foo_msg" include bar } class bar { notify { 'something': message => inline_template("<%= scope.lookupvar('var') %>"), } } MANIFEST end end end describe "when using an enc" do it "places enc parameters in top scope" do enc_node = Puppet::Node.new("the node", { :parameters => { "var" => 'from_enc' } }) expect_the_message_to_be('from_enc', enc_node) do <<-MANIFEST notify { 'something': message => $var, } MANIFEST end end it "does not allow the enc to specify an existing top scope var" do enc_node = Puppet::Node.new("the_node", { :parameters => { "var" => 'from_enc' } }) expect { compile_to_catalog("$var = 'top scope'", enc_node) }.to raise_error( Puppet::Error, /Cannot reassign variable '\$var' \(line: 1(, column: 6)?\) on node the_node/ ) end it "evaluates enc classes in top scope when there is no node" do enc_node = Puppet::Node.new("the node", { :classes => ['foo'], :parameters => { "var" => 'from_enc' } }) expect_the_message_to_be('from_enc', enc_node) do <<-MANIFEST class foo { notify { 'something': message => $var, } } MANIFEST end end it "overrides enc variables from a node scope var" do enc_node = Puppet::Node.new("the_node", { :classes => ['foo'], :parameters => { 'enc_var' => 'Set from ENC.' } }) expect_the_message_to_be('ENC overridden in node', enc_node) do <<-MANIFEST node the_node { $enc_var = "ENC overridden in node" } class foo { notify { 'something': message => $enc_var, } } MANIFEST end end end end puppet-5.5.10/spec/integration/parser/script_compiler_spec.rb0000644005276200011600000000653613417161721024332 0ustar jenkinsjenkinsrequire 'puppet' require 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' require 'puppet/parser/script_compiler' describe 'the script compiler' do include PuppetSpec::Compiler include PuppetSpec::Files include Matchers::Resource before(:each) do Puppet[:tasks] = true end context "when used" do let(:env_name) { 'testenv' } let(:environments_dir) { Puppet[:environmentpath] } let(:env_dir) { File.join(environments_dir, env_name) } let(:manifest) { Puppet::Node::Environment::NO_MANIFEST } let(:env) { Puppet::Node::Environment.create(env_name.to_sym, [File.join(populated_env_dir, 'modules')], manifest) } let(:node) { Puppet::Node.new("test", :environment => env) } let(:env_dir_files) { { 'manifests' => { 'good.pp' => "'good'\n" }, 'modules' => { 'test' => { 'plans' => { 'run_me.pp' => 'plan test::run_me() { "worked2" }' } } } } } let(:populated_env_dir) do dir_contained_in(environments_dir, env_name => env_dir_files) PuppetSpec::Files.record_tmp(env_dir) env_dir end let(:script_compiler) do Puppet::Parser::ScriptCompiler.new(env, node.name) end context 'is configured such that' do it 'returns what the script_compiler returns' do Puppet[:code] = <<-CODE 42 CODE expect(script_compiler.compile).to eql(42) end it 'referencing undefined variables raises an error' do expect do Puppet[:code] = <<-CODE notice $rubyversion CODE Puppet::Parser::ScriptCompiler.new(env, 'test_node_name').compile end.to raise_error(/Unknown variable: 'rubyversion'/) end it 'has strict=error behavior' do expect do Puppet[:code] = <<-CODE notice({a => 10, a => 20}) CODE Puppet::Parser::ScriptCompiler.new(env, 'test_node_name').compile end.to raise_error(/The key 'a' is declared more than once/) end it 'performing a multi assign from a class reference raises an error' do expect do Puppet[:code] = <<-CODE [$a] = Class[the_dalit] CODE Puppet::Parser::ScriptCompiler.new(env, 'test_node_name').compile end.to raise_error(/The catalog operation 'multi var assignment from class' is only available when compiling a catalog/) end end context 'when using environment manifest' do context 'set to single file' do let (:manifest) { "#{env_dir}/manifests/good.pp" } it 'loads and evaluates' do expect(script_compiler.compile).to eql('good') end end context 'set to directory' do let (:manifest) { "#{env_dir}/manifests" } it 'fails with an error' do expect{script_compiler.compile}.to raise_error(/manifest of environment 'testenv' appoints directory '.*\/manifests'. It must be a file/) end end context 'set to non existing path' do let (:manifest) { "#{env_dir}/manyfiests/good.pp" } it 'fails with an error' do expect{script_compiler.compile}.to raise_error(/manifest of environment 'testenv' appoints '.*\/good.pp'. It does not exist/) end end end end end puppet-5.5.10/spec/integration/parser/undef_param_spec.rb0000644005276200011600000000574613417161721023417 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/compiler' describe "Parameter passing" do include PuppetSpec::Compiler before :each do # DataBinding will be consulted before falling back to a default value, # but we aren't testing that here Puppet::DataBinding.indirection.stubs(:find) end def expect_the_message_to_be(message, node = Puppet::Node.new('the node')) catalog = compile_to_catalog(yield, node) expect(catalog.resource('Notify', 'something')[:message]).to eq(message) end def expect_puppet_error(message, node = Puppet::Node.new('the node')) expect { compile_to_catalog(yield, node) }.to raise_error(Puppet::Error, message) end it "overrides the default when a value is given" do expect_the_message_to_be('2') do <<-MANIFEST define a($x='1') { notify { 'something': message => $x }} a {'a': x => '2'} MANIFEST end end it "shadows an inherited variable with the default value when undef is passed" do expect_the_message_to_be('default') do <<-MANIFEST class a { $x = 'inherited' } class b($x='default') inherits a { notify { 'something': message => $x }} class { 'b': x => undef} MANIFEST end end it "uses a default value that comes from an inherited class when the parameter is undef" do expect_the_message_to_be('inherited') do <<-MANIFEST class a { $x = 'inherited' } class b($y=$x) inherits a { notify { 'something': message => $y }} class { 'b': y => undef} MANIFEST end end it "uses a default value that references another variable when the parameter is passed as undef" do expect_the_message_to_be('a') do <<-MANIFEST define a($a = $title) { notify { 'something': message => $a }} a {'a': a => undef} MANIFEST end end it "uses the default when 'undef' is given'" do expect_the_message_to_be('1') do <<-MANIFEST define a($x='1') { notify { 'something': message => $x }} a {'a': x => undef} MANIFEST end end it "uses the default when no parameter is provided" do expect_the_message_to_be('1') do <<-MANIFEST define a($x='1') { notify { 'something': message => $x }} a {'a': } MANIFEST end end it "uses a value of undef when the default is undef and no parameter is provided" do expect_the_message_to_be(true) do <<-MANIFEST define a($x=undef) { notify { 'something': message => $x == undef}} a {'a': } MANIFEST end end it "errors when no parameter is provided and there is no default" do expect_puppet_error(/A\[a\]: expects a value for parameter 'x'/) do <<-MANIFEST define a($x) { notify { 'something': message => $x }} a {'a': } MANIFEST end end it "uses a given undef and do not require a default expression" do expect_the_message_to_be(true) do <<-MANIFEST define a(Optional[Integer] $x) { notify { 'something': message => $x == undef}} a {'a': x => undef } MANIFEST end end end puppet-5.5.10/spec/integration/parser/catalog_spec.rb0000644005276200011600000000635613417161722022547 0ustar jenkinsjenkinsrequire 'spec_helper' require 'matchers/include_in_order' require 'puppet_spec/compiler' require 'puppet/indirector/catalog/compiler' describe "A catalog" do include PuppetSpec::Compiler context "when compiled" do let(:env) { Puppet::Node::Environment.create(:testing, []) } let(:node) { Puppet::Node.new('test', :environment => env) } let(:loaders) { Puppet::Pops::Loaders.new(env) } around :each do |example| Puppet::Parser::Compiler.any_instance.stubs(:loaders).returns(loaders) Puppet.override(:loaders => loaders, :current_environment => env) do example.run Puppet::Pops::Loaders.clear end end context "when transmitted to the agent" do it "preserves the order in which the resources are added to the catalog" do resources_in_declaration_order = ["Class[First]", "Second[position]", "Class[Third]", "Fourth[position]"] master_catalog, agent_catalog = master_and_agent_catalogs_for(<<-EOM) define fourth() { } class third { } define second() { fourth { "position": } } class first { second { "position": } class { "third": } } include first EOM expect(resources_in(master_catalog)). to include_in_order(*resources_in_declaration_order) expect(resources_in(agent_catalog)). to include_in_order(*resources_in_declaration_order) end it "does not contain unrealized, virtual resources" do virtual_resources = ["Unrealized[unreal]", "Class[Unreal]"] master_catalog, agent_catalog = master_and_agent_catalogs_for(<<-EOM) class unreal { } define unrealized() { } class real { @unrealized { "unreal": } @class { "unreal": } } include real EOM expect(resources_in(master_catalog)).to_not include(*virtual_resources) expect(resources_in(agent_catalog)).to_not include(*virtual_resources) end it "does not contain unrealized, exported resources" do exported_resources = ["Unrealized[unreal]", "Class[Unreal]"] master_catalog, agent_catalog = master_and_agent_catalogs_for(<<-EOM) class unreal { } define unrealized() { } class real { @@unrealized { "unreal": } @@class { "unreal": } } include real EOM expect(resources_in(master_catalog)).to_not include(*exported_resources) expect(resources_in(agent_catalog)).to_not include(*exported_resources) end end end def master_catalog_for(manifest) Puppet::Resource::Catalog::Compiler.new.filter(compile_to_catalog(manifest, node)) end def master_and_agent_catalogs_for(manifest) compiler = Puppet::Resource::Catalog::Compiler.new master_catalog = compiler.filter(compile_to_catalog(manifest, node)) agent_catalog = Puppet::Resource::Catalog.convert_from(:json, master_catalog.render(:json)) [master_catalog, agent_catalog] end def resources_in(catalog) catalog.resources.map(&:ref) end end puppet-5.5.10/spec/integration/parser/compiler_spec.rb0000644005276200011600000012112613417161722022740 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' # COPY OF UNIT TEST class CompilerTestResource attr_accessor :builtin, :virtual, :evaluated, :type, :title def initialize(type, title) @type = type @title = title end def [](attr) return nil if (attr == :stage || attr == :alias) :main end def ref "#{type.to_s.capitalize}[#{title}]" end def evaluated? @evaluated end def builtin_type? @builtin end def virtual? @virtual end def class? false end def stage? false end def evaluate end def file "/fake/file/goes/here" end def line "42" end def resource_type self.class end end describe Puppet::Parser::Compiler do include PuppetSpec::Files include Matchers::Resource def resource(type, title) Puppet::Parser::Resource.new(type, title, :scope => @scope) end let(:environment) { Puppet::Node::Environment.create(:testing, []) } before :each do # Push me faster, I wanna go back in time! (Specifically, freeze time # across the test since we have a bunch of version == timestamp code # hidden away in the implementation and we keep losing the race.) # --daniel 2011-04-21 now = Time.now Time.stubs(:now).returns(now) @node = Puppet::Node.new("testnode", :facts => Puppet::Node::Facts.new("facts", {}), :environment => environment) @known_resource_types = environment.known_resource_types @compiler = Puppet::Parser::Compiler.new(@node) @scope = Puppet::Parser::Scope.new(@compiler, :source => stub('source')) @scope_resource = Puppet::Parser::Resource.new(:file, "/my/file", :scope => @scope) @scope.resource = @scope_resource end # NEW INTEGRATION TEST describe "when evaluating collections" do it 'matches on container inherited tags' do Puppet[:code] = <<-MANIFEST class xport_test { tag('foo_bar') @notify { 'nbr1': message => 'explicitly tagged', tag => 'foo_bar' } @notify { 'nbr2': message => 'implicitly tagged' } Notify <| tag == 'foo_bar' |> { message => 'overridden' } } include xport_test MANIFEST catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) expect(catalog).to have_resource("Notify[nbr1]").with_parameter(:message, 'overridden') expect(catalog).to have_resource("Notify[nbr2]").with_parameter(:message, 'overridden') end end describe "when evaluating node classes" do include PuppetSpec::Compiler describe "when provided classes in hash format" do it 'looks up default parameter values from inherited class (PUP-2532)' do catalog = compile_to_catalog(<<-CODE) class a { Notify { message => "defaulted" } include c notify { bye: } } class b { Notify { message => "inherited" } } class c inherits b { notify { hi: } } include a notify {hi_test: message => Notify[hi][message] } notify {bye_test: message => Notify[bye][message] } CODE expect(catalog).to have_resource("Notify[hi_test]").with_parameter(:message, "inherited") expect(catalog).to have_resource("Notify[bye_test]").with_parameter(:message, "defaulted") end end end context "when converting catalog to resource" do it "the same environment is used for compilation as for transformation to resource form" do Puppet[:code] = <<-MANIFEST notify { 'dummy': } MANIFEST Puppet::Parser::Resource::Catalog.any_instance.expects(:to_resource).with do |catalog| Puppet.lookup(:current_environment).name == :production end Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) end end context 'when working with $settings name space' do include PuppetSpec::Compiler it 'makes $settings::strict available as string' do node = Puppet::Node.new("testing") catalog = compile_to_catalog(<<-MANIFEST, node) notify { 'test': message => $settings::strict == 'warning' } MANIFEST expect(catalog).to have_resource("Notify[test]").with_parameter(:message, true) end it 'can return boolean settings as Boolean' do node = Puppet::Node.new("testing") catalog = compile_to_catalog(<<-MANIFEST, node) notify { 'test': message => $settings::storeconfigs == false } MANIFEST expect(catalog).to have_resource("Notify[test]").with_parameter(:message, true) end it 'makes all server settings available as $settings::all_local hash' do node = Puppet::Node.new("testing") catalog = compile_to_catalog(<<-MANIFEST, node) notify { 'test': message => $settings::all_local['strict'] == 'warning' } MANIFEST expect(catalog).to have_resource("Notify[test]").with_parameter(:message, true) end end context 'when working with $server_facts' do include PuppetSpec::Compiler it '$trusted is available' do node = Puppet::Node.new("testing") node.add_server_facts({ "server_fact" => "foo" }) catalog = compile_to_catalog(<<-MANIFEST, node) notify { 'test': message => $server_facts[server_fact] } MANIFEST expect(catalog).to have_resource("Notify[test]").with_parameter(:message, "foo") end it 'does not allow assignment to $server_facts' do node = Puppet::Node.new("testing") node.add_server_facts({ "server_fact" => "foo" }) expect do compile_to_catalog(<<-MANIFEST, node) $server_facts = 'changed' notify { 'test': message => $server_facts == 'changed' } MANIFEST end.to raise_error(Puppet::PreformattedError, /Attempt to assign to a reserved variable name: '\$server_facts'.*/) end end describe "the compiler when using 4.x language constructs" do include PuppetSpec::Compiler if Puppet.features.microsoft_windows? it "should be able to determine the configuration version from a local version control repository" do pending("Bug #14071 about semantics of Puppet::Util::Execute on Windows") # This should always work, because we should always be # in the puppet repo when we run this. version = %x{git rev-parse HEAD}.chomp Puppet.settings[:config_version] = 'git rev-parse HEAD' compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("testnode")) compiler.catalog.version.should == version end end it 'assigns multiple variables from a class' do node = Puppet::Node.new("testnodex") catalog = compile_to_catalog(<<-PP, node) class foo::bar::example($x = 100) { $a = 10 $c = undef } include foo::bar::example [$a, $x, $c] = Class['foo::bar::example'] notify{'check_me': message => "$a, $x, -${c}-" } PP expect(catalog).to have_resource("Notify[check_me]").with_parameter(:message, "10, 100, --") end it 'errors on attempt to assigns multiple variables from a class when variable does not exist' do node = Puppet::Node.new("testnodex") expect do compile_to_catalog(<<-PP, node) class foo::bar::example($x = 100) { $ah = 10 $c = undef } include foo::bar::example [$a, $x, $c] = Class['foo::bar::example'] notify{'check_me': message => "$a, $x, -${c}-" } PP end.to raise_error(/No value for required variable '\$foo::bar::example::a'/) end it "should not create duplicate resources when a class is referenced both directly and indirectly by the node classifier (4792)" do node = Puppet::Node.new("testnodex") node.classes = ['foo', 'bar'] compile_to_catalog(<<-PP, node) class foo { notify { foo_notify: } include bar } class bar { notify { bar_notify: } } PP catalog = Puppet::Parser::Compiler.compile(node) expect(catalog).to have_resource("Notify[foo_notify]") expect(catalog).to have_resource("Notify[bar_notify]") end it 'applies defaults for defines with qualified names (PUP-2302)' do catalog = compile_to_catalog(<<-CODE) define my::thing($msg = 'foo') { notify {'check_me': message => $msg } } My::Thing { msg => 'evoe' } my::thing { 'name': } CODE expect(catalog).to have_resource("Notify[check_me]").with_parameter(:message, "evoe") end it 'Applies defaults from dynamic scopes (3x and future with reverted PUP-867)' do catalog = compile_to_catalog(<<-CODE) class a { Notify { message => "defaulted" } include b notify { bye: } } class b { notify { hi: } } include a CODE expect(catalog).to have_resource("Notify[hi]").with_parameter(:message, "defaulted") expect(catalog).to have_resource("Notify[bye]").with_parameter(:message, "defaulted") end it 'gets default from inherited class (PUP-867)' do catalog = compile_to_catalog(<<-CODE) class a { Notify { message => "defaulted" } include c notify { bye: } } class b { Notify { message => "inherited" } } class c inherits b { notify { hi: } } include a CODE expect(catalog).to have_resource("Notify[hi]").with_parameter(:message, "inherited") expect(catalog).to have_resource("Notify[bye]").with_parameter(:message, "defaulted") end it 'looks up default parameter values from inherited class (PUP-2532)' do catalog = compile_to_catalog(<<-CODE) class a { Notify { message => "defaulted" } include c notify { bye: } } class b { Notify { message => "inherited" } } class c inherits b { notify { hi: } } include a notify {hi_test: message => Notify[hi][message] } notify {bye_test: message => Notify[bye][message] } CODE expect(catalog).to have_resource("Notify[hi_test]").with_parameter(:message, "inherited") expect(catalog).to have_resource("Notify[bye_test]").with_parameter(:message, "defaulted") end it 'does not allow override of class parameters using a resource override expression' do expect do compile_to_catalog(<<-CODE) Class[a] { x => 2} CODE end.to raise_error(/Resource Override can only.*got: Class\[a\].*/) end describe 'when resolving class references' do include Matchers::Resource { 'string' => 'myWay', 'class reference' => 'Class["myWay"]', 'resource reference' => 'Resource["class", "myWay"]' }.each do |label, code| it "allows camel cased class name reference in 'include' using a #{label}" do catalog = compile_to_catalog(<<-"PP") class myWay { notify { 'I did it': message => 'my way'} } include #{code} PP expect(catalog).to have_resource("Notify[I did it]") end end describe 'and classname is a Resource Reference' do # tested with strict == off since this was once conditional on strict # can be removed in a later version. before(:each) do Puppet[:strict] = :off end it 'is reported as an error' do expect { compile_to_catalog(<<-PP) notice Class[ToothFairy] PP }.to raise_error(/Illegal Class name in class reference. A TypeReference\['ToothFairy'\]-Type cannot be used where a String is expected/) end end it "should not favor local scope (with class included in topscope)" do catalog = compile_to_catalog(<<-PP) class experiment { class baz { } notify {"x" : require => Class['baz'] } notify {"y" : require => Class['experiment::baz'] } } class baz { } include baz include experiment include experiment::baz PP expect(catalog).to have_resource("Notify[x]").with_parameter(:require, be_resource("Class[Baz]")) expect(catalog).to have_resource("Notify[y]").with_parameter(:require, be_resource("Class[Experiment::Baz]")) end it "should not favor local name scope" do expect { compile_to_catalog(<<-PP) class experiment { class baz { } notify {"x" : require => Class['baz'] } notify {"y" : require => Class['experiment::baz'] } } class baz { } include experiment include experiment::baz PP }.to raise_error(/Could not find resource 'Class\[Baz\]' in parameter 'require'/) end end describe "(ticket #13349) when explicitly specifying top scope" do ["class {'::bar::baz':}", "include ::bar::baz"].each do |include| describe "with #{include}" do it "should find the top level class" do catalog = compile_to_catalog(<<-MANIFEST) class { 'foo::test': } class foo::test { #{include} } class bar::baz { notify { 'good!': } } class foo::bar::baz { notify { 'bad!': } } MANIFEST expect(catalog).to have_resource("Class[Bar::Baz]") expect(catalog).to have_resource("Notify[good!]") expect(catalog).not_to have_resource("Class[Foo::Bar::Baz]") expect(catalog).not_to have_resource("Notify[bad!]") end end end end it 'should recompute the version after input files are re-parsed' do Puppet[:code] = 'class foo { }' first_time = Time.at(1) second_time = Time.at(200) Time.stubs(:now).returns(first_time) node = Puppet::Node.new('mynode') expect(Puppet::Parser::Compiler.compile(node).version).to eq(first_time.to_i) Time.stubs(:now).returns(second_time) expect(Puppet::Parser::Compiler.compile(node).version).to eq(first_time.to_i) # no change because files didn't change Puppet[:code] = nil expect(Puppet::Parser::Compiler.compile(node).version).to eq(second_time.to_i) end ['define', 'class', 'node'].each do |thing| it "'#{thing}' is not allowed inside evaluated conditional constructs" do expect do compile_to_catalog(<<-PP) if true { #{thing} foo { } notify { decoy: } } PP end.to raise_error(Puppet::Error, /Classes, definitions, and nodes may only appear at toplevel/) end it "'#{thing}' is not allowed inside un-evaluated conditional constructs" do expect do compile_to_catalog(<<-PP) if false { #{thing} foo { } notify { decoy: } } PP end.to raise_error(Puppet::Error, /Classes, definitions, and nodes may only appear at toplevel/) end end describe "relationships to non existing resources (even with strict==off)" do # At some point in the future, this test can be modified to simply ignore the strict flag, # but since the current version is a change from being under control of strict, this is now # explicit - the standard setting is strict == warning, here setting it to off # before(:each) do Puppet[:strict] = :off end [ 'before', 'subscribe', 'notify', 'require'].each do |meta_param| it "are reported as an error when formed via meta parameter #{meta_param}" do expect { compile_to_catalog(<<-PP) notify{ x : #{meta_param} => Notify[tooth_fairy] } PP }.to raise_error(/Could not find resource 'Notify\[tooth_fairy\]' in parameter '#{meta_param}'/) end end it 'is not reported for virtual resources' do expect { compile_to_catalog(<<-PP) @notify{ x : require => Notify[tooth_fairy] } PP }.to_not raise_error end it 'is reported for a realized virtual resources' do expect { compile_to_catalog(<<-PP) @notify{ x : require => Notify[tooth_fairy] } realize(Notify['x']) PP }.to raise_error(/Could not find resource 'Notify\[tooth_fairy\]' in parameter 'require'/) end it 'faulty references are reported with source location' do expect { compile_to_catalog(<<-PP) notify{ x : require => tooth_fairy } PP }.to raise_error(/"tooth_fairy" is not a valid resource reference.*\(line: 2\)/) end end describe "relationships can be formed" do def extract_name(ref) ref.sub(/File\[(\w+)\]/, '\1') end def assert_creates_relationships(relationship_code, expectations) base_manifest = <<-MANIFEST file { [a,b,c]: mode => '0644', } file { [d,e]: mode => '0755', } MANIFEST catalog = compile_to_catalog(base_manifest + relationship_code) resources = catalog.resources.select { |res| res.type == 'File' } actual_relationships, actual_subscriptions = [:before, :notify].map do |relation| resources.map do |res| dependents = Array(res[relation]) dependents.map { |ref| [res.title, extract_name(ref)] } end.inject(&:concat) end expect(actual_relationships).to match_array(expectations[:relationships] || []) expect(actual_subscriptions).to match_array(expectations[:subscriptions] || []) end it "of regular type" do assert_creates_relationships("File[a] -> File[b]", :relationships => [['a','b']]) end it "of subscription type" do assert_creates_relationships("File[a] ~> File[b]", :subscriptions => [['a', 'b']]) end it "between multiple resources expressed as resource with multiple titles" do assert_creates_relationships("File[a,b] -> File[c,d]", :relationships => [['a', 'c'], ['b', 'c'], ['a', 'd'], ['b', 'd']]) end it "between collection expressions" do assert_creates_relationships("File <| mode == '0644' |> -> File <| mode == '0755' |>", :relationships => [['a', 'd'], ['b', 'd'], ['c', 'd'], ['a', 'e'], ['b', 'e'], ['c', 'e']]) end it "between resources expressed as Strings" do assert_creates_relationships("'File[a]' -> 'File[b]'", :relationships => [['a', 'b']]) end it "between resources expressed as variables" do assert_creates_relationships(<<-MANIFEST, :relationships => [['a', 'b']]) $var = File[a] $var -> File[b] MANIFEST end it "between resources expressed as case statements" do assert_creates_relationships(<<-MANIFEST, :relationships => [['s1', 't2']]) $var = 10 case $var { 10: { file { s1: } } 12: { file { s2: } } } -> case $var + 2 { 10: { file { t1: } } 12: { file { t2: } } } MANIFEST end it "using deep access in array" do assert_creates_relationships(<<-MANIFEST, :relationships => [['a', 'b']]) $var = [ [ [ File[a], File[b] ] ] ] $var[0][0][0] -> $var[0][0][1] MANIFEST end it "using deep access in hash" do assert_creates_relationships(<<-MANIFEST, :relationships => [['a', 'b']]) $var = {'foo' => {'bar' => {'source' => File[a], 'target' => File[b]}}} $var[foo][bar][source] -> $var[foo][bar][target] MANIFEST end it "using resource declarations" do assert_creates_relationships("file { l: } -> file { r: }", :relationships => [['l', 'r']]) end it "between entries in a chain of relationships" do assert_creates_relationships("File[a] -> File[b] ~> File[c] <- File[d] <~ File[e]", :relationships => [['a', 'b'], ['d', 'c']], :subscriptions => [['b', 'c'], ['e', 'd']]) end it 'should close the gap created by an intermediate empty set produced by collection' do source = "file { [aa, bb]: } [File[a], File[aa]] -> Notify<| tag == 'na' |> ~> [File[b], File[bb]]" assert_creates_relationships(source, :relationships => [ ], :subscriptions => [['a', 'b'],['aa', 'b'],['a', 'bb'], ['aa', 'bb']]) end it 'should close the gap created by empty set followed by empty collection' do source = "file { [aa, bb]: } [File[a], File[aa]] -> [] -> Notify<| tag == 'na' |> ~> [File[b], File[bb]]" assert_creates_relationships(source, :relationships => [ ], :subscriptions => [['a', 'b'],['aa', 'b'],['a', 'bb'], ['aa', 'bb']]) end it 'should close the gap created by empty collection surrounded by empty sets' do source = "file { [aa, bb]: } [File[a], File[aa]] -> [] -> Notify<| tag == 'na' |> -> [] ~> [File[b], File[bb]]" assert_creates_relationships(source, :relationships => [ ], :subscriptions => [['a', 'b'],['aa', 'b'],['a', 'bb'], ['aa', 'bb']]) end it 'should close the gap created by several intermediate empty sets produced by collection' do source = "file { [aa, bb]: } [File[a], File[aa]] -> Notify<| tag == 'na' |> -> Notify<| tag == 'na' |> ~> [File[b], File[bb]]" assert_creates_relationships(source, :relationships => [ ], :subscriptions => [['a', 'b'],['aa', 'b'],['a', 'bb'], ['aa', 'bb']]) end end context "when dealing with variable references" do it 'an initial underscore in a variable name is ok' do catalog = compile_to_catalog(<<-MANIFEST) class a { $_a = 10} include a notify { 'test': message => $a::_a } MANIFEST expect(catalog).to have_resource("Notify[test]").with_parameter(:message, 10) end it 'an initial underscore in not ok if elsewhere than last segment' do expect do compile_to_catalog(<<-MANIFEST) class a { $_a = 10} include a notify { 'test': message => $_a::_a } MANIFEST end.to raise_error(/Illegal variable name/) end it 'a missing variable as default value becomes undef' do # strict variables not on catalog = compile_to_catalog(<<-MANIFEST) class a ($b=$x) { notify {test: message=>"yes ${undef == $b}" } } include a MANIFEST expect(catalog).to have_resource("Notify[test]").with_parameter(:message, "yes true") end end context "when dealing with resources (e.g. File) that modifies its name from title" do [['', ''], ['', '/'], ['/', ''], ['/', '/']].each do |t, r| it "a circular reference can be compiled with endings: title='#{t}' and ref='#{r}'" do expect { node = Puppet::Node.new("testing") compile_to_catalog(<<-"MANIFEST", node) file { '/tmp/bazinga.txt#{t}': content => 'henrik testing', require => File['/tmp/bazinga.txt#{r}'] } MANIFEST }.not_to raise_error end end end context 'when working with the trusted data hash' do context 'and have opted in to hashed_node_data' do it 'should make $trusted available' do node = Puppet::Node.new("testing") node.trusted_data = { "data" => "value" } catalog = compile_to_catalog(<<-MANIFEST, node) notify { 'test': message => $trusted[data] } MANIFEST expect(catalog).to have_resource("Notify[test]").with_parameter(:message, "value") end it 'should not allow assignment to $trusted' do node = Puppet::Node.new("testing") node.trusted_data = { "data" => "value" } expect do compile_to_catalog(<<-MANIFEST, node) $trusted = 'changed' notify { 'test': message => $trusted == 'changed' } MANIFEST end.to raise_error(Puppet::PreformattedError, /Attempt to assign to a reserved variable name: '\$trusted'/) end end end context 'when using typed parameters in definition' do it 'accepts type compliant arguments' do catalog = compile_to_catalog(<<-MANIFEST) define foo(String $x) { } foo { 'test': x =>'say friend' } MANIFEST expect(catalog).to have_resource("Foo[test]").with_parameter(:x, 'say friend') end it 'accepts undef as the default for an Optional argument' do catalog = compile_to_catalog(<<-MANIFEST) define foo(Optional[String] $x = undef) { notify { "expected": message => $x == undef } } foo { 'test': } MANIFEST expect(catalog).to have_resource("Notify[expected]").with_parameter(:message, true) end it 'accepts anything when parameters are untyped' do expect do compile_to_catalog(<<-MANIFEST) define foo($a, $b, $c) { } foo { 'test': a => String, b=>10, c=>undef } MANIFEST end.to_not raise_error() end it 'denies non type compliant arguments' do expect do compile_to_catalog(<<-MANIFEST) define foo(Integer $x) { } foo { 'test': x =>'say friend' } MANIFEST end.to raise_error(/Foo\[test\]: parameter 'x' expects an Integer value, got String/) end it 'denies undef for a non-optional type' do expect do compile_to_catalog(<<-MANIFEST) define foo(Integer $x) { } foo { 'test': x => undef } MANIFEST end.to raise_error(/Foo\[test\]: parameter 'x' expects an Integer value, got Undef/) end it 'denies non type compliant default argument' do expect do compile_to_catalog(<<-MANIFEST) define foo(Integer $x = 'pow') { } foo { 'test': } MANIFEST end.to raise_error(/Foo\[test\]: parameter 'x' expects an Integer value, got String/) end it 'denies undef as the default for a non-optional type' do expect do compile_to_catalog(<<-MANIFEST) define foo(Integer $x = undef) { } foo { 'test': } MANIFEST end.to raise_error(/Foo\[test\]: parameter 'x' expects an Integer value, got Undef/) end it 'accepts a Resource as a Type' do catalog = compile_to_catalog(<<-MANIFEST) define bar($text) { } define foo(Type[Bar] $x) { notify { 'test': message => $x[text] } } bar { 'joke': text => 'knock knock' } foo { 'test': x => Bar[joke] } MANIFEST expect(catalog).to have_resource("Notify[test]").with_parameter(:message, 'knock knock') end it 'uses infer_set when reporting type mismatch' do expect do compile_to_catalog(<<-MANIFEST) define foo(Struct[{b => Integer, d=>String}] $a) { } foo{ bar: a => {b => 5, c => 'stuff'}} MANIFEST end.to raise_error(/Foo\[bar\]:\s+parameter 'a' expects a value for key 'd'\s+parameter 'a' unrecognized key 'c'/m) end end context 'when using typed parameters in class' do it 'accepts type compliant arguments' do catalog = compile_to_catalog(<<-MANIFEST) class foo(String $x) { } class { 'foo': x =>'say friend' } MANIFEST expect(catalog).to have_resource("Class[Foo]").with_parameter(:x, 'say friend') end it 'accepts undef as the default for an Optional argument' do catalog = compile_to_catalog(<<-MANIFEST) class foo(Optional[String] $x = undef) { notify { "expected": message => $x == undef } } class { 'foo': } MANIFEST expect(catalog).to have_resource("Notify[expected]").with_parameter(:message, true) end it 'accepts anything when parameters are untyped' do expect do compile_to_catalog(<<-MANIFEST) class foo($a, $b, $c) { } class { 'foo': a => String, b=>10, c=>undef } MANIFEST end.to_not raise_error() end it 'denies non type compliant arguments' do expect do compile_to_catalog(<<-MANIFEST) class foo(Integer $x) { } class { 'foo': x =>'say friend' } MANIFEST end.to raise_error(/Class\[Foo\]: parameter 'x' expects an Integer value, got String/) end it 'denies undef for a non-optional type' do expect do compile_to_catalog(<<-MANIFEST) class foo(Integer $x) { } class { 'foo': x => undef } MANIFEST end.to raise_error(/Class\[Foo\]: parameter 'x' expects an Integer value, got Undef/) end it 'denies non type compliant default argument' do expect do compile_to_catalog(<<-MANIFEST) class foo(Integer $x = 'pow') { } class { 'foo': } MANIFEST end.to raise_error(/Class\[Foo\]: parameter 'x' expects an Integer value, got String/) end it 'denies undef as the default for a non-optional type' do expect do compile_to_catalog(<<-MANIFEST) class foo(Integer $x = undef) { } class { 'foo': } MANIFEST end.to raise_error(/Class\[Foo\]: parameter 'x' expects an Integer value, got Undef/) end it 'denies a regexp (rich data) argument given to class String parameter (even if later encoding of it is a string)' do expect do compile_to_catalog(<<-MANIFEST) class foo(String $x) { } class { 'foo': x => /I am a regexp and I don't want to be a String/} MANIFEST end.to raise_error(/Class\[Foo\]: parameter 'x' expects a String value, got Regexp/) end it 'denies a regexp (rich data) argument given to define String parameter (even if later encoding of it is a string)' do expect do compile_to_catalog(<<-MANIFEST) define foo(String $x) { } foo { 'foo': x => /I am a regexp and I don't want to be a String/} MANIFEST end.to raise_error(/Foo\[foo\]: parameter 'x' expects a String value, got Regexp/) end it 'accepts a Resource as a Type' do catalog = compile_to_catalog(<<-MANIFEST) define bar($text) { } class foo(Type[Bar] $x) { notify { 'test': message => $x[text] } } bar { 'joke': text => 'knock knock' } class { 'foo': x => Bar[joke] } MANIFEST expect(catalog).to have_resource("Notify[test]").with_parameter(:message, 'knock knock') end end context 'when using typed parameters in lambdas' do it 'accepts type compliant arguments' do catalog = compile_to_catalog(<<-MANIFEST) with('value') |String $x| { notify { "$x": } } MANIFEST expect(catalog).to have_resource("Notify[value]") end it 'handles an array as a single argument' do catalog = compile_to_catalog(<<-MANIFEST) with(['value', 'second']) |$x| { notify { "${x[0]} ${x[1]}": } } MANIFEST expect(catalog).to have_resource("Notify[value second]") end it 'denies when missing required arguments' do expect do compile_to_catalog(<<-MANIFEST) with(1) |$x, $y| { } MANIFEST end.to raise_error(/Parameter \$y is required but no value was given/m) end it 'accepts anything when parameters are untyped' do catalog = compile_to_catalog(<<-MANIFEST) ['value', 1, true, undef].each |$x| { notify { "value: $x": } } MANIFEST expect(catalog).to have_resource("Notify[value: value]") expect(catalog).to have_resource("Notify[value: 1]") expect(catalog).to have_resource("Notify[value: true]") expect(catalog).to have_resource("Notify[value: ]") end it 'accepts type-compliant, slurped arguments' do catalog = compile_to_catalog(<<-MANIFEST) with(1, 2) |Integer *$x| { notify { "${$x[0] + $x[1]}": } } MANIFEST expect(catalog).to have_resource("Notify[3]") end it 'denies non-type-compliant arguments' do expect do compile_to_catalog(<<-MANIFEST) with(1) |String $x| { } MANIFEST end.to raise_error(/block parameter 'x' expects a String value, got Integer/m) end it 'denies non-type-compliant, slurped arguments' do expect do compile_to_catalog(<<-MANIFEST) with(1, "hello") |Integer *$x| { } MANIFEST end.to raise_error(/block parameter 'x' expects an Integer value, got String/m) end it 'denies non-type-compliant default argument' do expect do compile_to_catalog(<<-MANIFEST) with(1) |$x, String $defaulted = 1| { notify { "${$x + $defaulted}": }} MANIFEST end.to raise_error(/block parameter 'defaulted' expects a String value, got Integer/m) end it 'raises an error when a default argument value is an incorrect type and there are no arguments passed' do expect do compile_to_catalog(<<-MANIFEST) with() |String $defaulted = 1| {} MANIFEST end.to raise_error(/block parameter 'defaulted' expects a String value, got Integer/m) end it 'raises an error when the default argument for a slurped parameter is an incorrect type' do expect do compile_to_catalog(<<-MANIFEST) with() |String *$defaulted = 1| {} MANIFEST end.to raise_error(/block parameter 'defaulted' expects a String value, got Integer/m) end it 'allows using an array as the default slurped value' do catalog = compile_to_catalog(<<-MANIFEST) with() |String *$defaulted = [hi]| { notify { $defaulted[0]: } } MANIFEST expect(catalog).to have_resource('Notify[hi]') end it 'allows using a value of the type as the default slurped value' do catalog = compile_to_catalog(<<-MANIFEST) with() |String *$defaulted = hi| { notify { $defaulted[0]: } } MANIFEST expect(catalog).to have_resource('Notify[hi]') end it 'allows specifying the type of a slurped parameter as an array' do catalog = compile_to_catalog(<<-MANIFEST) with() |Array[String] *$defaulted = hi| { notify { $defaulted[0]: } } MANIFEST expect(catalog).to have_resource('Notify[hi]') end it 'raises an error when the number of default values does not match the parameter\'s size specification' do expect do compile_to_catalog(<<-MANIFEST) with() |Array[String, 2] *$defaulted = hi| { } MANIFEST end.to raise_error(/block expects at least 2 arguments, got 1/m) end it 'raises an error when the number of passed values does not match the parameter\'s size specification' do expect do compile_to_catalog(<<-MANIFEST) with(hi) |Array[String, 2] *$passed| { } MANIFEST end.to raise_error(/block expects at least 2 arguments, got 1/m) end it 'matches when the number of arguments passed for a slurp parameter match the size specification' do catalog = compile_to_catalog(<<-MANIFEST) with(hi, bye) |Array[String, 2] *$passed| { $passed.each |$n| { notify { $n: } } } MANIFEST expect(catalog).to have_resource('Notify[hi]') expect(catalog).to have_resource('Notify[bye]') end it 'raises an error when the number of allowed slurp parameters exceeds the size constraint' do expect do compile_to_catalog(<<-MANIFEST) with(hi, bye) |Array[String, 1, 1] *$passed| { } MANIFEST end.to raise_error(/block expects 1 argument, got 2/m) end it 'allows passing slurped arrays by specifying an array of arrays' do catalog = compile_to_catalog(<<-MANIFEST) with([hi], [bye]) |Array[Array[String, 1, 1]] *$passed| { notify { $passed[0][0]: } notify { $passed[1][0]: } } MANIFEST expect(catalog).to have_resource('Notify[hi]') expect(catalog).to have_resource('Notify[bye]') end it 'raises an error when a required argument follows an optional one' do expect do compile_to_catalog(<<-MANIFEST) with() |$y = first, $x, Array[String, 1] *$passed = bye| {} MANIFEST end.to raise_error(/Parameter \$x is required/) end it 'raises an error when the minimum size of a slurped argument makes it required and it follows an optional argument' do expect do compile_to_catalog(<<-MANIFEST) with() |$x = first, Array[String, 1] *$passed| {} MANIFEST end.to raise_error(/Parameter \$passed is required/) end it 'allows slurped arguments with a minimum size of 0 after an optional argument' do catalog = compile_to_catalog(<<-MANIFEST) with() |$x = first, Array[String, 0] *$passed| { notify { $x: } } MANIFEST expect(catalog).to have_resource('Notify[first]') end it 'accepts a Resource as a Type' do catalog = compile_to_catalog(<<-MANIFEST) define bar($text) { } bar { 'joke': text => 'knock knock' } with(Bar[joke]) |Type[Bar] $joke| { notify { "${joke[text]}": } } MANIFEST expect(catalog).to have_resource("Notify[knock knock]") end end end describe "the compiler when handling aliases" do include PuppetSpec::Compiler def extract_name(ref) ref.sub(/.*\[(\w+)\]/, '\1') end def assert_created_relationships(catalog, type_name, expectations) resources = catalog.resources.select { |res| res.type == type_name } actual_relationships, actual_subscriptions = [:before, :notify].map do |relation| resources.map do |res| dependents = Array(res[relation]) dependents.map { |ref| [res.title, extract_name(ref)] } end.inject(&:concat) end expect(actual_relationships).to match_array(expectations[:relationships] || []) expect(actual_subscriptions).to match_array(expectations[:subscriptions] || []) end it 'allows a relationship to be formed using metaparam relationship' do node = Puppet::Node.new("testnodex") catalog = compile_to_catalog(<<-PP, node) notify { 'actual_2': before => 'Notify[alias_1]' } notify { 'actual_1': alias => 'alias_1' } PP assert_created_relationships(catalog, 'Notify', { :relationships => [['actual_2', 'alias_1']] }) end it 'allows a relationship to be formed using -> operator and alias' do node = Puppet::Node.new("testnodex") catalog = compile_to_catalog(<<-PP, node) notify { 'actual_2': } notify { 'actual_1': alias => 'alias_1' } Notify[actual_2] -> Notify[alias_1] PP assert_created_relationships(catalog, 'Notify', { :relationships => [['actual_2', 'alias_1']] }) end it 'errors when an alias cannot be found when relationship is formed with -> operator' do node = Puppet::Node.new("testnodex") expect { compile_to_catalog(<<-PP, node) notify { 'actual_2': } notify { 'actual_1': alias => 'alias_1' } Notify[actual_2] -> Notify[alias_2] PP }.to raise_error(/Could not find resource 'Notify\[alias_2\]'/) end end describe 'the compiler when using collection and override' do include PuppetSpec::Compiler it 'allows an override when there is a default present' do catalog = compile_to_catalog(<<-MANIFEST) Package { require => Class['bar'] } class bar { } class foo { package { 'python': } package { 'pip': require => Package['python'] } Package <| title == 'pip' |> { name => "python-pip", category => undef, } } include foo include bar MANIFEST expect(catalog.resource('Package', 'pip')[:require].to_s).to eql('Package[python]') end end end puppet-5.5.10/spec/integration/provider/0000755005276200011600000000000013417162176020126 5ustar jenkinsjenkinspuppet-5.5.10/spec/integration/provider/service/0000755005276200011600000000000013417162176021566 5ustar jenkinsjenkinspuppet-5.5.10/spec/integration/provider/service/init_spec.rb0000644005276200011600000000235213417161722024066 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' provider = Puppet::Type.type(:service).provider(:init) describe provider do describe "when running on FreeBSD" do before :each do Facter.stubs(:value).with(:operatingsystem).returns 'FreeBSD' end it "should set its default path to include /etc/rc.d and /usr/local/etc/rc.d" do expect(provider.defpath).to eq(["/etc/rc.d", "/usr/local/etc/rc.d"]) end end describe "when running on HP-UX" do before :each do Facter.stubs(:value).with(:operatingsystem).returns 'HP-UX' end it "should set its default path to include /sbin/init.d" do expect(provider.defpath).to eq("/sbin/init.d") end end describe "when running on Archlinux" do before :each do Facter.stubs(:value).with(:operatingsystem).returns 'Archlinux' end it "should set its default path to include /etc/rc.d" do expect(provider.defpath).to eq("/etc/rc.d") end end describe "when not running on FreeBSD, HP-UX or Archlinux" do before :each do Facter.stubs(:value).with(:operatingsystem).returns 'RedHat' end it "should set its default path to include /etc/init.d" do expect(provider.defpath).to eq("/etc/init.d") end end end puppet-5.5.10/spec/integration/provider/service/systemd_spec.rb0000644005276200011600000000146413417161722024616 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:service).provider(:systemd), '(integration)' do # TODO: Unfortunately there does not seem a way to stub the executable # checks in the systemd provider because they happen at load time. it "should be considered suitable if /bin/systemctl is present", :if => File.executable?('/bin/systemctl') do expect(described_class).to be_suitable end it "should be considered suitable if /usr/bin/systemctl is present", :if => File.executable?('/usr/bin/systemctl') do expect(described_class).to be_suitable end it "should not be cosidered suitable if systemctl is absent", :unless => (File.executable?('/bin/systemctl') or File.executable?('/usr/bin/systemctl')) do expect(described_class).not_to be_suitable end end puppet-5.5.10/spec/integration/provider/service/windows_spec.rb0000644005276200011600000000261413417161722024616 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:service).provider(:windows), '(integration)', :if => Puppet.features.microsoft_windows? do require 'puppet/util/windows' before :each do Puppet::Type.type(:service).stubs(:defaultprovider).returns described_class end context 'should return valid values when querying a service that does not exist' do let(:service) do Puppet::Type.type(:service).new(:name => 'foobarservice1234') end it "with :false when asked if enabled" do expect(service.provider.enabled?).to eql(:false) end it "with :stopped when asked about status" do expect(service.provider.status).to eql(:stopped) end end context 'should return valid values when querying a service that does exist' do let(:service) do # This service should be ubiquitous across all supported Windows platforms Puppet::Type.type(:service).new(:name => 'lmhosts') end it "with a valid enabled? value when asked if enabled" do expect([:true, :false, :manual]).to include(service.provider.enabled?) end it "with a valid status when asked about status" do expect([ :running, :'continue pending', :'pause pending', :paused, :running, :'start pending', :'stop pending', :stopped]).to include(service.provider.status) end end end puppet-5.5.10/spec/integration/provider/cron/0000755005276200011600000000000013417162176021067 5ustar jenkinsjenkinspuppet-5.5.10/spec/integration/provider/cron/crontab_spec.rb0000644005276200011600000001654713417161722024067 0ustar jenkinsjenkins#!/usr/bin/env ruby require 'spec_helper' require 'puppet/file_bucket/dipper' require 'puppet_spec/compiler' describe Puppet::Type.type(:cron).provider(:crontab), '(integration)', :unless => Puppet.features.microsoft_windows? do include PuppetSpec::Files include PuppetSpec::Compiler before :each do Puppet::Type.type(:cron).stubs(:defaultprovider).returns described_class described_class.stubs(:suitable?).returns true Puppet::FileBucket::Dipper.any_instance.stubs(:backup) # Don't backup to filebucket # I don't want to execute anything described_class.stubs(:filetype).returns Puppet::Util::FileType::FileTypeFlat described_class.stubs(:default_target).returns crontab_user1 # I don't want to stub Time.now to get a static header because I don't know # where Time.now is used elsewhere, so just go with a very simple header described_class.stubs(:header).returns "# HEADER: some simple\n# HEADER: header\n" FileUtils.cp(my_fixture('crontab_user1'), crontab_user1) FileUtils.cp(my_fixture('crontab_user2'), crontab_user2) end after :each do described_class.clear end let :crontab_user1 do tmpfile('cron_integration_specs') end let :crontab_user2 do tmpfile('cron_integration_specs') end def expect_output(fixture_name) expect(File.read(crontab_user1)).to eq(File.read(my_fixture(fixture_name))) end describe "when managing a cron entry" do it "should be able to purge unmanaged entries" do apply_with_error_check(<<-MANIFEST) cron { 'only managed entry': ensure => 'present', command => '/bin/true', target => '#{crontab_user1}', } resources { 'cron': purge => 'true' } MANIFEST expect_output('purged') end describe "with ensure absent" do it "should do nothing if entry already absent" do apply_with_error_check(<<-MANIFEST) cron { 'no_such_entry': ensure => 'absent', target => '#{crontab_user1}', } MANIFEST expect_output('crontab_user1') end it "should remove the resource from crontab if present" do apply_with_error_check(<<-MANIFEST) cron { 'My daily failure': ensure => 'absent', target => '#{crontab_user1}', } MANIFEST expect_output('remove_named_resource') end it "should remove a matching cronentry if present" do apply_with_error_check(<<-MANIFEST) cron { 'no_such_named_resource_in_crontab': ensure => absent, minute => [ '17-19', '22' ], hour => [ '0-23/2' ], weekday => 'Tue', command => '/bin/unnamed_regular_command', target => '#{crontab_user1}', } MANIFEST expect_output('remove_unnamed_resource') end end describe "with ensure present" do context "and no command specified" do it "should work if the resource is already present" do apply_with_error_check(<<-MANIFEST) cron { 'My daily failure': special => 'daily', target => '#{crontab_user1}', } MANIFEST expect_output('crontab_user1') end it "should fail if the resource needs creating" do manifest = <<-MANIFEST cron { 'Entirely new resource': special => 'daily', target => '#{crontab_user1}', } MANIFEST apply_compiled_manifest(manifest) do |res| if res.ref == 'Cron[Entirely new resource]' res.expects(:err).with(regexp_matches(/no command/)) else res.expects(:err).never end end end end it "should do nothing if entry already present" do apply_with_error_check(<<-MANIFEST) cron { 'My daily failure': special => 'daily', command => '/bin/false', target => '#{crontab_user1}', } MANIFEST expect_output('crontab_user1') end it "should work correctly when managing 'target' but not 'user'" do apply_with_error_check(<<-MANIFEST) cron { 'My daily failure': special => 'daily', command => '/bin/false', target => '#{crontab_user1}', } MANIFEST expect_output('crontab_user1') end it "should do nothing if a matching entry already present" do apply_with_error_check(<<-MANIFEST) cron { 'no_such_named_resource_in_crontab': ensure => present, minute => [ '17-19', '22' ], hour => [ '0-23/2' ], command => '/bin/unnamed_regular_command', target => '#{crontab_user1}', } MANIFEST expect_output('crontab_user1') end it "should add a new normal entry if currently absent" do apply_with_error_check(<<-MANIFEST) cron { 'new entry': ensure => present, minute => '12', weekday => 'Tue', command => '/bin/new', environment => [ 'MAILTO=""', 'SHELL=/bin/bash' ], target => '#{crontab_user1}', } MANIFEST expect_output('create_normal_entry') end it "should add a new special entry if currently absent" do apply_with_error_check(<<-MANIFEST) cron { 'new special entry': ensure => present, special => 'reboot', command => 'echo "Booted" 1>&2', environment => 'MAILTO=bob@company.com', target => '#{crontab_user1}', } MANIFEST expect_output('create_special_entry') end it "should change existing entry if out of sync" do apply_with_error_check(<<-MANIFEST) cron { 'Monthly job': ensure => present, special => 'monthly', #minute => ['22'], command => '/usr/bin/monthly', environment => [], target => '#{crontab_user1}', } MANIFEST expect_output('modify_entry') end it "should change a special schedule to numeric if requested" do apply_with_error_check(<<-MANIFEST) cron { 'My daily failure': special => 'absent', command => '/bin/false', target => '#{crontab_user1}', } MANIFEST expect_output('unspecialized') end it "should not try to move an entry from one file to another" do # force the parsedfile provider to also parse user1's crontab apply_with_error_check(<<-MANIFEST) cron { 'foo': ensure => absent, target => '#{crontab_user1}'; 'My daily failure': special => 'daily', command => "/bin/false", target => '#{crontab_user2}', } MANIFEST expect(File.read(crontab_user1)).to eq(File.read(my_fixture('moved_cronjob_input1'))) expect(File.read(crontab_user2)).to eq(File.read(my_fixture('moved_cronjob_input2'))) end end end end puppet-5.5.10/spec/integration/provider/mailalias/0000755005276200011600000000000013417162176022062 5ustar jenkinsjenkinspuppet-5.5.10/spec/integration/provider/mailalias/aliases_spec.rb0000644005276200011600000000051113417161722025033 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'shared_behaviours/all_parsedfile_providers' provider_class = Puppet::Type.type(:mailalias).provider(:aliases) describe provider_class do # #1560, in which we corrupt the format of complex mail aliases. it_should_behave_like "all parsedfile providers", provider_class end puppet-5.5.10/spec/integration/provider/mount_spec.rb0000644005276200011600000001421613417161722022627 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/file_bucket/dipper' describe "mount provider (integration)", :unless => Puppet.features.microsoft_windows? do include PuppetSpec::Files family = Facter.value(:osfamily) def create_fake_fstab(initially_contains_entry) File.open(@fake_fstab, 'w') do |f| if initially_contains_entry f.puts("/dev/disk1s1\t/Volumes/foo_disk\tmsdos\tlocal\t0\t0") end end end before :each do @fake_fstab = tmpfile('fstab') @current_options = "local" @current_device = "/dev/disk1s1" Puppet[:digest_algorithm] = 'md5' Puppet::Type.type(:mount).defaultprovider.stubs(:default_target).returns(@fake_fstab) Facter.stubs(:value).with(:hostname).returns('some_host') Facter.stubs(:value).with(:domain).returns('some_domain') Facter.stubs(:value).with(:kernel).returns('Linux') Facter.stubs(:value).with(:operatingsystem).returns('RedHat') Facter.stubs(:value).with(:osfamily).returns('RedHat') Facter.stubs(:value).with(:fips_enabled).returns(false) Puppet::Util::ExecutionStub.set do |command, options| case command[0] when %r{/s?bin/mount} if command.length == 1 if @mounted "#{@current_device} on /Volumes/foo_disk (msdos, #{@current_options})\n" else '' end else expect(command.last).to eq('/Volumes/foo_disk') @current_device = check_fstab(true) @mounted = true '' end when %r{/s?bin/umount} expect(command.length).to eq(2) expect(command[1]).to eq('/Volumes/foo_disk') expect(@mounted).to eq(true) # "umount" doesn't work when device not mounted (see #6632) @mounted = false '' else fail "Unexpected command #{command.inspect} executed" end end end after :each do Puppet::Type::Mount::ProviderParsed.clear # Work around bug #6628 end def check_fstab(expected_to_be_present) # Verify that the fake fstab has the expected data in it fstab_contents = File.read(@fake_fstab).split("\n").reject { |x| x =~ /^#|^$/ } if expected_to_be_present expect(fstab_contents.length()).to eq(1) device, rest_of_line = fstab_contents[0].split(/\t/,2) expect(rest_of_line).to eq("/Volumes/foo_disk\tmsdos\t#{@desired_options}\t0\t0") device else expect(fstab_contents.length()).to eq(0) nil end end def run_in_catalog(settings) resource = Puppet::Type.type(:mount).new(settings.merge(:name => "/Volumes/foo_disk", :device => "/dev/disk1s1", :fstype => "msdos")) Puppet::FileBucket::Dipper.any_instance.stubs(:backup) # Don't backup to the filebucket resource.expects(:err).never catalog = Puppet::Resource::Catalog.new catalog.host_config = false # Stop Puppet from doing a bunch of magic catalog.add_resource resource catalog.apply end [false, true].each do |initial_state| describe "When initially #{initial_state ? 'mounted' : 'unmounted'}" do before :each do @mounted = initial_state end [false, true].each do |initial_fstab_entry| describe "When there is #{initial_fstab_entry ? 'an' : 'no'} initial fstab entry" do before :each do create_fake_fstab(initial_fstab_entry) end [:defined, :present, :mounted, :unmounted, :absent].each do |ensure_setting| expected_final_state = case ensure_setting when :mounted true when :unmounted, :absent false when :defined, :present initial_state else fail "Unknown ensure_setting #{ensure_setting}" end expected_fstab_data = (ensure_setting != :absent) describe "When setting ensure => #{ensure_setting}" do ["local", "journaled", "", nil].each do |options_setting| describe "When setting options => '#{options_setting}'" do it "should leave the system in the #{expected_final_state ? 'mounted' : 'unmounted'} state, #{expected_fstab_data ? 'with' : 'without'} data in /etc/fstab" do if family == "Solaris" skip("Solaris: The mock :operatingsystem value does not get changed in lib/puppet/provider/mount/parsed.rb") else if options_setting && options_setting.empty? expect { run_in_catalog(:ensure=>ensure_setting, :options => options_setting) }.to raise_error Puppet::ResourceError else if options_setting @desired_options = options_setting run_in_catalog(:ensure=>ensure_setting, :options => options_setting) else if initial_fstab_entry @desired_options = @current_options else @desired_options = 'defaults' end run_in_catalog(:ensure=>ensure_setting) end expect(@mounted).to eq(expected_final_state) if expected_fstab_data expect(check_fstab(expected_fstab_data)).to eq("/dev/disk1s1") else expect(check_fstab(expected_fstab_data)).to eq(nil) end end end end end end end end end end end end describe "When the wrong device is mounted" do it "should remount the correct device" do pending "Due to bug 6309" @mounted = true @current_device = "/dev/disk2s2" create_fake_fstab(true) @desired_options = "local" run_in_catalog(:ensure=>:mounted, :options=>'local') expect(@current_device).to eq("/dev/disk1s1") expect(@mounted).to eq(true) expect(@current_options).to eq('local') expect(check_fstab(true)).to eq("/dev/disk1s1") end end end puppet-5.5.10/spec/integration/provider/ssh_authorized_key_spec.rb0000644005276200011600000002073413417161722025372 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/file_bucket/dipper' describe Puppet::Type.type(:ssh_authorized_key).provider(:parsed), '(integration)', :unless => Puppet.features.microsoft_windows? do include PuppetSpec::Files let :fake_userfile do tmpfile('authorized_keys.user') end let :fake_rootfile do tmpfile('authorized_keys.root') end let :sample_rsa_keys do [ 'AAAAB3NzaC1yc2EAAAADAQABAAAAgQCi18JBZOq10X3w4f67nVhO0O3s5Y1vHH4UgMSM3ZnQwbC5hjGyYSi9UULOoQQoQynI/a0I9NL423/Xk/XJVIKCHcS8q6V2Wmjd+fLNelOjxxoW6mbIytEt9rDvwgq3Mof3/m21L3t2byvegR00a+ikKbmInPmKwjeWZpexCIsHzQ==', # 1024 bit 'AAAAB3NzaC1yc2EAAAADAQABAAAAgQDLClyvi3CsJw5Id6khZs2/+s11qOH4Gdp6iDioDsrIp0m8kSiPr71VGyQYAfPzzvHemHS7Xg0NkG1Kc8u9tRqBQfTvz7ubq0AT/g01+4P2hQ/soFkuwlUG/HVnnaYb6N0Qp5SHWvD5vBE2nFFQVpP5GrSctPtHSjzJq/i+6LYhmQ==', # 1024 bit 'AAAAB3NzaC1yc2EAAAADAQABAAABAQDLygAO6txXkh9FNV8xSsBkATeqLbHzS7sFjGI3gt0Dx6q3LjyKwbhQ1RLf28kd5G6VWiXmClU/RtiPdUz8nrGuun++2mrxzrXrvpR9dq1lygLQ2wn2cI35dN5bjRMtXy3decs6HUhFo9MoNwX250rUWfdCyNPhGIp6OOfmjdy+UeLGNxq9wDx6i4bT5tVVSqVRtsEfw9+ICXchzl85QudjneVVpP+thriPZXfXA5eaGwAo/dmoKOIhUwF96gpdLqzNtrGQuxPbV80PTbGv9ZtAtTictxaDz8muXO7he9pXmchUpxUKtMFjHkL0FAZ9tRPmv3RA30sEr2fZ8+LKvnE50w0' #2048 Bit ] end let :sample_dsa_keys do [ 'AAAAB3NzaC1kc3MAAACBAOPck2O8MIDSqxPSnvENt6tzRrKJ5oOhB6Nc6oEcWm+VEH1gvuxdiRqwoMgRwyEf1yUd+UAcLw3a6Jn+EtFyEBN/5WF+4Tt4xTxZ0Pfik2Wc5uqHbQ2dkmOoXiAOYPiD3JUQ1Xwm/J0CgetjitoLfzAGdCNhMqguqAuHcVJ78ZZbAAAAFQCIBKFYZ+I18I+dtgteirXh+VVEEwAAAIEAs1yvQ/wnLLrRCM660pF4kBiw3D6dJfMdCXWQpn0hZmkBQSIzZv4Wuk3giei5luxscDxNc+y3CTXtnyG4Kt1Yi2sOdvhRI3rX8tD+ejn8GHazM05l5VIo9uu4AQPIE32iV63IqgApSBbJ6vDJW91oDH0J492WdLCar4BS/KE3cRwAAACBAN0uSDyJqYLRsfYcFn4HyVf6TJxQm1IcwEt6GcJVzgjri9VtW7FqY5iBqa9B9Zdh5XXAYJ0XLsWQCcrmMHM2XGHGpA4gL9VlCJ/0QvOcXxD2uK7IXwAVUA7g4V4bw8EVnFv2Flufozhsp+4soo1xiYc5jiFVHwVlk21sMhAtKAeF' # 1024 Bit ] end let :sample_lines do [ "ssh-rsa #{sample_rsa_keys[1]} root@someotherhost", "ssh-dss #{sample_dsa_keys[0]} root@anywhere", "ssh-rsa #{sample_rsa_keys[2]} paul", "ssh-rsa #{sample_rsa_keys[2]} dummy" ] end let :dummy do Puppet::Type.type(:ssh_authorized_key).new( :name => 'dummy', :target => fake_userfile, :user => 'nobody', :ensure => :absent ) end before :each do File.stubs(:chown) File.stubs(:chmod) Puppet::Util::SUIDManager.stubs(:asuser).yields end after :each do described_class.clear # Work around bug #6628 end def create_fake_key(username, content) filename = (username == :root ? fake_rootfile : fake_userfile ) File.open(filename, 'w') do |f| content.each do |line| f.puts line end end end def check_fake_key(username, expected_content) filename = (username == :root ? fake_rootfile : fake_userfile ) content = File.readlines(filename).map(&:chomp).sort.reject{ |x| x =~ /^# HEADER:/ } expect(content.join("\n")).to eq(expected_content.sort.join("\n")) end def run_in_catalog(*resources) Puppet::FileBucket::Dipper.any_instance.stubs(:backup) # Don't backup to the filebucket catalog = Puppet::Resource::Catalog.new catalog.host_config = false resources.each do |resource| resource.expects(:err).never catalog.add_resource(resource) end catalog.apply end it "should not complain about empty lines and comments" do described_class.expects(:flush).never sample = ['',sample_lines[0],' ',sample_lines[1],'# just a comment','#and another'] create_fake_key(:user,sample) run_in_catalog(dummy) check_fake_key(:user, sample) end it "should keep empty lines and comments when modifying a file" do create_fake_key(:user, ['',sample_lines[0],' ',sample_lines[3],'# just a comment','#and another']) run_in_catalog(dummy) check_fake_key(:user, ['',sample_lines[0],' ','# just a comment','#and another']) end describe "when managing one resource" do describe "with ensure set to absent" do let :resource do Puppet::Type.type(:ssh_authorized_key).new( :name => 'root@hostname', :type => :rsa, :key => sample_rsa_keys[0], :target => fake_rootfile, :user => 'root', :ensure => :absent ) end it "should not modify root's keyfile if resource is currently not present" do create_fake_key(:root, sample_lines) run_in_catalog(resource) check_fake_key(:root, sample_lines) end it "remove the key from root's keyfile if resource is currently present" do create_fake_key(:root, sample_lines + ["ssh-rsa #{sample_rsa_keys[0]} root@hostname"]) run_in_catalog(resource) check_fake_key(:root, sample_lines) end end describe "when ensure is present" do let :resource do Puppet::Type.type(:ssh_authorized_key).new( :name => 'root@hostname', :type => :rsa, :key => sample_rsa_keys[0], :target => fake_rootfile, :user => 'root', :ensure => :present ) end # just a dummy so the parsedfile provider is aware # of the user's authorized_keys file it "should add the key if it is not present" do create_fake_key(:root, sample_lines) run_in_catalog(resource) check_fake_key(:root, sample_lines + ["ssh-rsa #{sample_rsa_keys[0]} root@hostname" ]) end it "should modify the type if type is out of sync" do create_fake_key(:root,sample_lines + [ "ssh-dss #{sample_rsa_keys[0]} root@hostname" ]) run_in_catalog(resource) check_fake_key(:root, sample_lines + [ "ssh-rsa #{sample_rsa_keys[0]} root@hostname" ]) end it "should modify the key if key is out of sync" do create_fake_key(:root,sample_lines + [ "ssh-rsa #{sample_rsa_keys[1]} root@hostname" ]) run_in_catalog(resource) check_fake_key(:root, sample_lines + [ "ssh-rsa #{sample_rsa_keys[0]} root@hostname" ]) end it "should remove the key from old file if target is out of sync" do create_fake_key(:user, [ sample_lines[0], "ssh-rsa #{sample_rsa_keys[0]} root@hostname" ]) create_fake_key(:root, [ sample_lines[1], sample_lines[2] ]) run_in_catalog(resource, dummy) check_fake_key(:user, [ sample_lines[0] ]) #check_fake_key(:root, [ sample_lines[1], sample_lines[2], "ssh-rsa #{sample_rsa_keys[0]} root@hostname" ]) end it "should add the key to new file if target is out of sync" do create_fake_key(:user, [ sample_lines[0], "ssh-rsa #{sample_rsa_keys[0]} root@hostname" ]) create_fake_key(:root, [ sample_lines[1], sample_lines[2] ]) run_in_catalog(resource, dummy) #check_fake_key(:user, [ sample_lines[0] ]) check_fake_key(:root, [ sample_lines[1], sample_lines[2], "ssh-rsa #{sample_rsa_keys[0]} root@hostname" ]) end it "should modify options if options are out of sync" do resource[:options]=[ 'from="*.domain1,host1.domain2"', 'no-port-forwarding', 'no-pty' ] create_fake_key(:root, sample_lines + [ "from=\"*.false,*.false2\",no-port-forwarding,no-pty ssh-rsa #{sample_rsa_keys[0]} root@hostname"]) run_in_catalog(resource) check_fake_key(:root, sample_lines + [ "from=\"*.domain1,host1.domain2\",no-port-forwarding,no-pty ssh-rsa #{sample_rsa_keys[0]} root@hostname"] ) end end end describe "when managing two resource" do let :examples do resources = [] resources << Puppet::Type.type(:ssh_authorized_key).new( :name => 'root@hostname', :type => :rsa, :key => sample_rsa_keys[0], :target => fake_rootfile, :user => 'root', :ensure => :present ) resources << Puppet::Type.type(:ssh_authorized_key).new( :name => 'user@hostname', :key => sample_rsa_keys[1], :type => :rsa, :target => fake_userfile, :user => 'nobody', :ensure => :present ) resources end describe "and both keys are absent" do before :each do create_fake_key(:root, sample_lines) create_fake_key(:user, sample_lines) end it "should add both keys" do run_in_catalog(*examples) check_fake_key(:root, sample_lines + [ "ssh-rsa #{sample_rsa_keys[0]} root@hostname" ]) check_fake_key(:user, sample_lines + [ "ssh-rsa #{sample_rsa_keys[1]} user@hostname" ]) end end end end puppet-5.5.10/spec/integration/provider/sshkey_spec.rb0000644005276200011600000001323113417161722022767 0ustar jenkinsjenkins#!/usr/bin/env ruby require 'spec_helper' require 'puppet/file_bucket/dipper' require 'puppet_spec/files' require 'puppet_spec/compiler' describe Puppet::Type.type(:sshkey).provider(:parsed), '(integration)', :unless => Puppet.features.microsoft_windows? do include PuppetSpec::Files include PuppetSpec::Compiler before :each do # Don't backup to filebucket Puppet::FileBucket::Dipper.any_instance.stubs(:backup) # We don't want to execute anything described_class.stubs(:filetype). returns Puppet::Util::FileType::FileTypeFlat @sshkey_file = tmpfile('sshkey_integration_specs') FileUtils.cp(my_fixture('sample'), @sshkey_file) end after :each do # sshkey provider class described_class.clear end let(:type_under_test) { 'sshkey' } describe "when managing a ssh known hosts file it..." do let(:super_unique) { "my.super.unique.host" } it "should create a new known_hosts file with mode 0644" do target = tmpfile('ssh_known_hosts') manifest = "#{type_under_test} { '#{super_unique}': ensure => 'present', type => 'rsa', key => 'TESTKEY', target => '#{target}' }" apply_with_error_check(manifest) expect_file_mode(target, "644") end it "should create an SSH host key entry (ensure present)" do manifest = "#{type_under_test} { '#{super_unique}': ensure => 'present', type => 'rsa', key => 'mykey', target => '#{@sshkey_file}' }" apply_with_error_check(manifest) expect(File.read(@sshkey_file)).to match(/#{super_unique}.*mykey/) end let(:sshkey_name) { 'kirby.madstop.com' } it "should delete an entry for an SSH host key" do manifest = "#{type_under_test} { '#{sshkey_name}': ensure => 'absent', target => '#{@sshkey_file}' }" apply_with_error_check(manifest) expect(File.read(@sshkey_file)).not_to match(/#{sshkey_name}.*Yqk0=/) end it "should update an entry for an SSH host key" do manifest = "#{type_under_test} { '#{sshkey_name}': ensure => 'present', type => 'rsa', key => 'mynewshinykey', target => '#{@sshkey_file}' }" apply_with_error_check(manifest) expect(File.read(@sshkey_file)).to match(/#{sshkey_name}.*mynewshinykey/) expect(File.read(@sshkey_file)).not_to match(/#{sshkey_name}.*Yqk0=/) end # test all key types types = ["ssh-dss", "dsa", "ssh-ed25519", "ed25519", "ssh-rsa", "rsa", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp521"] # these types are treated as aliases for sshkey type # so they are populated as the *values* below aliases = {"dsa" => "ssh-dss", "ed25519" => "ssh-ed25519", "rsa" => "ssh-rsa"} types.each do |type| it "should update an entry with #{type} type" do manifest = "#{type_under_test} { '#{sshkey_name}': ensure => 'present', type => '#{type}', key => 'mynewshinykey', target => '#{@sshkey_file}' }" apply_with_error_check(manifest) if aliases.has_key?(type) full_type = aliases[type] expect(File.read(@sshkey_file)). to match(/#{sshkey_name}.*#{full_type}.*mynew/) else expect(File.read(@sshkey_file)). to match(/#{sshkey_name}.*#{type}.*mynew/) end end end # test unknown key type fails let(:invalid_type) { 'ssh-er0ck' } it "should raise an error with an unknown type" do manifest = "#{type_under_test} { '#{sshkey_name}': ensure => 'present', type => '#{invalid_type}', key => 'mynewshinykey', target => '#{@sshkey_file}' }" expect { apply_compiled_manifest(manifest) }.to raise_error(Puppet::ResourceError, /Invalid value "#{invalid_type}"/) end #single host_alias let(:host_alias) { 'r0ckdata.com' } it "should update an entry with new host_alias" do manifest = "#{type_under_test} { '#{sshkey_name}': ensure => 'present', host_aliases => '#{host_alias}', target => '#{@sshkey_file}' }" apply_with_error_check(manifest) expect(File.read(@sshkey_file)).to match(/#{sshkey_name},#{host_alias}\s/) expect(File.read(@sshkey_file)).not_to match(/#{sshkey_name}\s/) end #array host_alias let(:host_aliases) { "r0ckdata.com,erict.net" } it "should update an entry with new host_alias" do manifest = "#{type_under_test} { '#{sshkey_name}': ensure => 'present', host_aliases => '#{host_alias}', target => '#{@sshkey_file}' }" apply_with_error_check(manifest) expect(File.read(@sshkey_file)).to match(/#{sshkey_name},#{host_alias}\s/) expect(File.read(@sshkey_file)).not_to match(/#{sshkey_name}\s/) end #puppet resource sshkey it "should fetch an entry from resources" do @resource_app = Puppet::Application[:resource] @resource_app.preinit @resource_app.command_line.stubs(:args). returns([type_under_test, sshkey_name, "target=#{@sshkey_file}"]) @resource_app.expects(:puts).with do |args| expect(args).to match(/#{sshkey_name}/) end @resource_app.main end end end puppet-5.5.10/spec/integration/provider/yumrepo_spec.rb0000644005276200011600000001206213417161722023162 0ustar jenkinsjenkins#!/usr/bin/env ruby require 'spec_helper' require 'puppet/file_bucket/dipper' require 'puppet_spec/files' require 'puppet_spec/compiler' describe Puppet::Type.type(:yumrepo).provider(:inifile), '(integration)', :unless => Puppet.features.microsoft_windows? do include PuppetSpec::Files include PuppetSpec::Compiler before :each do # Don't backup to filebucket Puppet::FileBucket::Dipper.any_instance.stubs(:backup) # We don't want to execute anything described_class.stubs(:filetype). returns Puppet::Util::FileType::FileTypeFlat @yumrepo_dir = tmpdir('yumrepo_integration_specs') @yumrepo_file = tmpfile('yumrepo_file', @yumrepo_dir) @yumrepo_conf_file = tmpfile('yumrepo_conf_file', @yumrepo_dir) # this mocks the reposdir logic in the provider and thus won't test for # issues like PUP-2916. Cover these types of issues in acceptance described_class.stubs(:reposdir).returns [@yumrepo_dir] described_class.stubs(:repofiles).returns [@yumrepo_conf_file] end after :each do # yumrepo provider class described_class.clear end let(:type_under_test) { :yumrepo } describe 'when managing a yumrepo file it...' do let(:super_creative) { 'my.super.creati.ve' } let(:manifest) { "#{type_under_test} { '#{super_creative}': baseurl => 'http://#{super_creative}', target => '#{@yumrepo_file}' }" } let(:expected_match) { "\[#{super_creative}\]\nbaseurl=http:\/\/my\.super\.creati\.ve" } it 'should create a new yumrepo file with mode 0644 and yumrepo entry' do apply_with_error_check(manifest) expect_file_mode(File.join(@yumrepo_dir, super_creative + '.repo'), "644") expect(File.read(File.join(@yumrepo_dir, super_creative + '.repo'))). to match(expected_match + "\n") end it 'should remove a managed yumrepo entry' do apply_with_error_check(manifest) manifest = "#{type_under_test} { '#{super_creative}': ensure => absent, target => '#{@yumrepo_file}' }" apply_with_error_check(manifest) expect(File.read(File.join(@yumrepo_dir, super_creative + '.repo'))). to be_empty end it 'should update a managed yumrepo entry' do apply_with_error_check(manifest) manifest = "#{type_under_test} { '#{super_creative}': baseurl => 'http://#{super_creative}.updated', target => '#{@yumrepo_file}' }" apply_with_error_check(manifest) expect(File.read(File.join(@yumrepo_dir, super_creative + '.repo'))). to match(expected_match + ".updated\n") end it 'should create all properties of a yumrepo entry' do manifest = "#{type_under_test} { '#{super_creative}': baseurl => 'http://#{super_creative}', target => '#{@yumrepo_file}' }" apply_with_error_check(manifest) expect(File.read(File.join(@yumrepo_dir, super_creative + '.repo'))). to match("\[#{super_creative}\]") end # The unit-tests cover all properties # and we have to hard-code the "should" values here. # Puppet::Type.type(:yumrepo).validproperties contains the full list # but we can't get the property "should" values from the yumrepo-type # without having an instance of type, which is what yumrepo defines... # Just cover the most probable used properties. properties = {"bandwidth" => "42M", "baseurl" => "http://er0ck", "cost" => "42", "enabled" => "yes", "exclude" => "er0ckSet2.0", "failovermethod" => "roundrobin", "include" => "https://er0ck", "mirrorlist" => "https://er0ckMirr0r.co", "priority" => "99", "retries" => "413189", "timeout" => "666" } it "should create an entry with various properties" do manifest = "#{type_under_test} { '#{super_creative}': target => '#{@yumrepo_file}',\n" properties.each do |property_key, property_value| manifest << "#{property_key} => '#{property_value}',\n" end manifest << "}" apply_with_error_check(manifest) file_lines = File.read(File.join(@yumrepo_dir, super_creative + '.repo')) properties.each do |property_key, property_value| if property_value =~ /^(true|false|no|yes)$/ property_value = property_value.capitalize end expect(file_lines).to match(/^#{property_key}=#{Regexp.escape(property_value)}$/) end end ##puppet resource yumrepo it "should fetch the yumrepo entries from resource face" do @resource_app = Puppet::Application[:resource] @resource_app.preinit @resource_app.command_line.stubs(:args). returns([type_under_test, super_creative]) @resource_app.expects(:puts).with do |args| expect(args).to match(/#{super_creative}/) end @resource_app.main end end end puppet-5.5.10/spec/integration/reference/0000755005276200011600000000000013417162176020232 5ustar jenkinsjenkinspuppet-5.5.10/spec/integration/reference/providers_spec.rb0000644005276200011600000000047013417161722023603 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/reference' reference = Puppet::Util::Reference.reference(:providers) describe reference do it "should exist" do expect(reference).not_to be_nil end it "should be able to be rendered as markdown" do reference.to_markdown end end puppet-5.5.10/spec/integration/reports_spec.rb0000644005276200011600000000045613417161721021331 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/reports' describe Puppet::Reports, " when using report types" do before do Puppet.settings.stubs(:use) end it "should load report types as modules" do expect(Puppet::Reports.report(:store)).to be_instance_of(Module) end end puppet-5.5.10/spec/integration/resource/0000755005276200011600000000000013417162176020123 5ustar jenkinsjenkinspuppet-5.5.10/spec/integration/resource/catalog_spec.rb0000644005276200011600000000335013417161721023070 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Resource::Catalog do describe "when using the indirector" do before do # This is so the tests work w/out networking. Facter.stubs(:to_hash).returns({"hostname" => "foo.domain.com"}) Facter.stubs(:value).returns("eh") end it "should be able to delegate to the :yaml terminus" do Puppet::Resource::Catalog.indirection.stubs(:terminus_class).returns :yaml # Load now, before we stub the exists? method. terminus = Puppet::Resource::Catalog.indirection.terminus(:yaml) terminus.expects(:path).with("me").returns "/my/yaml/file" Puppet::FileSystem.expects(:exist?).with("/my/yaml/file").returns false expect(Puppet::Resource::Catalog.indirection.find("me")).to be_nil end it "should be able to delegate to the :compiler terminus" do Puppet::Resource::Catalog.indirection.stubs(:terminus_class).returns :compiler # Load now, before we stub the exists? method. compiler = Puppet::Resource::Catalog.indirection.terminus(:compiler) node = mock 'node' node.stub_everything Puppet::Node.indirection.expects(:find).returns(node) compiler.expects(:compile).with(node, anything).returns nil expect(Puppet::Resource::Catalog.indirection.find("me")).to be_nil end it "should pass provided node information directly to the terminus" do terminus = mock 'terminus' Puppet::Resource::Catalog.indirection.stubs(:terminus).returns terminus node = mock 'node' terminus.stubs(:validate) terminus.expects(:find).with { |request| request.options[:use_node] == node } Puppet::Resource::Catalog.indirection.find("me", :use_node => node) end end end puppet-5.5.10/spec/integration/resource/type_collection_spec.rb0000644005276200011600000000566313417161721024663 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/files' require 'puppet/resource/type_collection' describe Puppet::Resource::TypeCollection do describe "when autoloading from modules" do include PuppetSpec::Files before do @dir = tmpfile("autoload_testing") FileUtils.mkdir_p @dir loader = Object.new loader.stubs(:load).returns nil loader.stubs(:set_entry) loaders = Object.new loaders.expects(:runtime3_type_loader).at_most_once.returns loader Puppet::Pops::Loaders.expects(:loaders).at_most_once.returns loaders environment = Puppet::Node::Environment.create(:env, [@dir]) @code = environment.known_resource_types end # Setup a module. def mk_module(name, files = {}) mdir = File.join(@dir, name) mandir = File.join(mdir, "manifests") FileUtils.mkdir_p mandir defs = files.delete(:define) Dir.chdir(mandir) do files.each do |file, classes| File.open("#{file}.pp", "w") do |f| classes.each { |klass| if defs f.puts "define #{klass} {}" else f.puts "class #{klass} {}" end } end end end end it "should return nil when a class can't be found or loaded" do expect(@code.find_hostclass('nosuchclass')).to be_nil end it "should load the module's init file first" do name = "simple" mk_module(name, :init => [name]) expect(@code.find_hostclass(name).name).to eq(name) end it "should be able to load definitions from the module base file" do name = "simpdef" mk_module(name, :define => true, :init => [name]) expect(@code.find_definition(name).name).to eq(name) end it "should be able to load qualified classes from the module base file" do mk_module('both', :init => %w{both both::sub}) expect(@code.find_hostclass("both::sub").name).to eq("both::sub") end it "should be able load classes from a separate file" do mk_module('separate', :init => %w{separate}, :sub => %w{separate::sub}) expect(@code.find_hostclass("separate::sub").name).to eq("separate::sub") end it "should not fail when loading from a separate file if there is no module file" do mk_module('alone', :sub => %w{alone::sub}) expect { @code.find_hostclass("alone::sub") }.not_to raise_error end it "should be able to load definitions from their own file" do name = "mymod" mk_module(name, :define => true, :mydefine => ["mymod::mydefine"]) expect(@code.find_definition("mymod::mydefine").name).to eq("mymod::mydefine") end it 'should be able to load definitions from their own file using uppercased name' do name = 'mymod' mk_module(name, :define => true, :mydefine => ['mymod::mydefine']) expect(@code.find_definition('Mymod::Mydefine')).not_to be_nil end end end puppet-5.5.10/spec/integration/ssl/0000755005276200011600000000000013417162176017075 5ustar jenkinsjenkinspuppet-5.5.10/spec/integration/ssl/autosign_spec.rb0000644005276200011600000001300513417161722022260 0ustar jenkinsjenkinsrequire 'spec_helper' describe "autosigning" do include PuppetSpec::Files let(:puppet_dir) { tmpdir("ca_autosigning") } let(:utf8_value) { "utf8_\u06FF\u16A0\u{2070E}" } let(:utf8_value_binary_string) { utf8_value.force_encoding(Encoding::BINARY) } let(:csr_attributes_content) do { 'custom_attributes' => { '1.3.6.1.4.1.34380.2.0' => 'hostname.domain.com', '1.3.6.1.4.1.34380.2.1' => 'my passphrase', '1.3.6.1.4.1.34380.2.2' => # system IPs in hex [ 0xC0A80001, # 192.168.0.1 0xC0A80101 ], # 192.168.1.1 # different UTF-8 widths # 1-byte A # 2-byte Űż - http://www.fileformat.info/info/unicode/char/06ff/index.htm - 0xDB 0xBF / 219 191 # 3-byte áš  - http://www.fileformat.info/info/unicode/char/16A0/index.htm - 0xE1 0x9A 0xA0 / 225 154 160 # 4-byte 𠜎 - http://www.fileformat.info/info/unicode/char/2070E/index.htm - 0xF0 0xA0 0x9C 0x8E / 240 160 156 142 '1.2.840.113549.1.9.7' => utf8_value, }, 'extension_requests' => { 'pp_uuid' => 'abcdef', '1.3.6.1.4.1.34380.1.1.2' => utf8_value, # pp_instance_id '1.3.6.1.4.1.34380.1.2.1' => 'some-value', # private extension }, } end let(:host) { Puppet::SSL::Host.new } before do Puppet.settings[:confdir] = puppet_dir Puppet.settings[:vardir] = puppet_dir # This is necessary so the terminus instances don't lie around. Puppet::SSL::Key.indirection.termini.clear end def write_csr_attributes(yaml) File.open(Puppet.settings[:csr_attributes], 'w') do |file| file.puts YAML.dump(yaml) end end context "when the csr_attributes file is valid, but empty" do it "generates a CSR when the file is empty" do Puppet::FileSystem.touch(Puppet.settings[:csr_attributes]) host.generate_certificate_request end it "generates a CSR when the file contains whitespace" do File.open(Puppet.settings[:csr_attributes], 'w') do |file| file.puts "\n\n" end host.generate_certificate_request end end context "when the csr_attributes file doesn't contain a YAML encoded hash" do it "raises when the file contains a string" do write_csr_attributes('a string') expect { host.generate_certificate_request }.to raise_error(Puppet::Error, /invalid CSR attributes, expected instance of Hash, received instance of String/) end it "raises when the file contains an empty array" do write_csr_attributes([]) expect { host.generate_certificate_request }.to raise_error(Puppet::Error, /invalid CSR attributes, expected instance of Hash, received instance of Array/) end end context "with extension requests from csr_attributes file" do let(:ca) { Puppet::SSL::CertificateAuthority.new } it "generates a CSR when the csr_attributes file is an empty hash" do write_csr_attributes(csr_attributes_content) host.generate_certificate_request end context "and subjectAltName" do it "raises an error if you include subjectAltName in csr_attributes" do csr_attributes_content['extension_requests']['subjectAltName'] = 'foo' write_csr_attributes(csr_attributes_content) expect { host.generate_certificate_request }.to raise_error(Puppet::Error, /subjectAltName.*conflicts with internally used extension request/) end it "properly merges subjectAltName when in settings" do Puppet.settings[:dns_alt_names] = 'althostname.nowhere' write_csr_attributes(csr_attributes_content) host.generate_certificate_request csr = Puppet::SSL::CertificateRequest.indirection.find(host.name) expect(csr.subject_alt_names).to include('DNS:althostname.nowhere') end end context "without subjectAltName" do before do write_csr_attributes(csr_attributes_content) host.generate_certificate_request end it "pulls extension attributes from the csr_attributes file into the certificate" do csr = Puppet::SSL::CertificateRequest.indirection.find(host.name) expect(csr.request_extensions).to have(3).items expect(csr.request_extensions).to include('oid' => 'pp_uuid', 'value' => 'abcdef') expect(csr.request_extensions).to include('oid' => 'pp_instance_id', 'value' => utf8_value_binary_string) expect(csr.request_extensions).to include('oid' => '1.3.6.1.4.1.34380.1.2.1', 'value' => 'some-value') end it "pulls custom attributes from the csr_attributes file into the certificate" do csr = Puppet::SSL::CertificateRequest.indirection.find(host.name) expect(csr.custom_attributes).to have(4).items expect(csr.custom_attributes).to include('oid' => 'challengePassword', 'value' => utf8_value_binary_string) end it "copies extension requests to certificate" do cert = ca.sign(host.name) expect(cert.custom_extensions).to include('oid' => 'pp_uuid', 'value' => 'abcdef') expect(cert.custom_extensions).to include('oid' => 'pp_instance_id', 'value' => utf8_value_binary_string) expect(cert.custom_extensions).to include('oid' => '1.3.6.1.4.1.34380.1.2.1', 'value' => 'some-value') end it "does not copy custom attributes to certificate" do cert = ca.sign(host.name) cert.custom_extensions.each do |ext| expect(Puppet::SSL::Oids.subtree_of?('1.3.6.1.4.1.34380.2', ext['oid'])).to be_falsey expect(ext['oid']).to_not eq('1.2.840.113549.1.9.7') end end end end end puppet-5.5.10/spec/integration/ssl/certificate_authority_spec.rb0000644005276200011600000001166313417161722025031 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/ssl/certificate_authority' describe Puppet::SSL::CertificateAuthority, :unless => Puppet.features.microsoft_windows? do include PuppetSpec::Files let(:ca) { @ca } before do dir = tmpdir("ca_integration_testing") Puppet.settings[:confdir] = dir Puppet.settings[:vardir] = dir Puppet::SSL::Host.ca_location = :local # this has the side-effect of creating the various directories that we need @ca = Puppet::SSL::CertificateAuthority.new end it "should be able to generate a new host certificate" do ca.generate("newhost") expect(Puppet::SSL::Certificate.indirection.find("newhost")).to be_instance_of(Puppet::SSL::Certificate) end it "should be able to revoke a host certificate" do ca.generate("newhost") ca.revoke("newhost") expect { ca.verify("newhost") }.to raise_error(Puppet::SSL::CertificateAuthority::CertificateVerificationError, "certificate revoked") end describe "when signing certificates" do it "should save the signed certificate" do certificate_request_for("luke.madstop.com") ca.sign("luke.madstop.com") expect(Puppet::SSL::Certificate.indirection.find("luke.madstop.com")).to be_instance_of(Puppet::SSL::Certificate) end it "should be able to sign multiple certificates" do certificate_request_for("luke.madstop.com") certificate_request_for("other.madstop.com") ca.sign("luke.madstop.com") ca.sign("other.madstop.com") expect(Puppet::SSL::Certificate.indirection.find("other.madstop.com")).to be_instance_of(Puppet::SSL::Certificate) expect(Puppet::SSL::Certificate.indirection.find("luke.madstop.com")).to be_instance_of(Puppet::SSL::Certificate) end it "should save the signed certificate to the :signeddir" do certificate_request_for("luke.madstop.com") ca.sign("luke.madstop.com") client_cert = File.join(Puppet[:signeddir], "luke.madstop.com.pem") expect(File.read(client_cert)).to eq(Puppet::SSL::Certificate.indirection.find("luke.madstop.com").content.to_s) end it "should save valid certificates" do certificate_request_for("luke.madstop.com") ca.sign("luke.madstop.com") unless Puppet::Util::which('openssl') pending "No ssl available" else ca_cert = Puppet[:cacert] client_cert = File.join(Puppet[:signeddir], "luke.madstop.com.pem") %x{openssl verify -CAfile #{ca_cert} #{client_cert}} expect($CHILD_STATUS).to eq(0) end end it "should verify proof of possession when signing certificates" do host = certificate_request_for("luke.madstop.com") csr = host.certificate_request wrong_key = Puppet::SSL::Key.new(host.name) wrong_key.generate csr.content.public_key = wrong_key.content.public_key # The correct key has to be removed so we can save the incorrect one Puppet::SSL::CertificateRequest.indirection.destroy(host.name) Puppet::SSL::CertificateRequest.indirection.save(csr) expect { ca.sign(host.name) }.to raise_error( Puppet::SSL::CertificateAuthority::CertificateSigningError, "CSR contains a public key that does not correspond to the signing key" ) end end describe "when revoking certificate" do it "should work for one certificate" do certificate_request_for("luke.madstop.com") ca.sign("luke.madstop.com") ca.revoke("luke.madstop.com") expect { ca.verify("luke.madstop.com") }.to raise_error( Puppet::SSL::CertificateAuthority::CertificateVerificationError, "certificate revoked" ) end it "should work for several certificates" do 3.times.each do |c| certificate_request_for("luke.madstop.com") ca.sign("luke.madstop.com") ca.destroy("luke.madstop.com") end ca.revoke("luke.madstop.com") expect(ca.crl.content.revoked.map { |r| r.serial }).to eq([2,3,4]) # ca has serial 1 end end it "allows autosigning certificates concurrently", :unless => Puppet::Util::Platform.windows? do Puppet[:autosign] = true hosts = (0..4).collect { |i| certificate_request_for("host#{i}") } run_in_parallel(5) do |i| ca.autosign(Puppet::SSL::CertificateRequest.indirection.find(hosts[i].name)) end certs = hosts.collect { |host| Puppet::SSL::Certificate.indirection.find(host.name).content } serial_numbers = certs.collect(&:serial) expect(serial_numbers.sort).to eq([2, 3, 4, 5, 6]) # serial 1 is the ca certificate end def certificate_request_for(hostname) key = Puppet::SSL::Key.new(hostname) key.generate host = Puppet::SSL::Host.new(hostname) host.key = key host.generate_certificate_request host end def run_in_parallel(number) children = [] number.times do |i| children << Kernel.fork do yield i end end children.each { |pid| Process.wait(pid) } end end puppet-5.5.10/spec/integration/ssl/certificate_request_spec.rb0000644005276200011600000000251113417161722024461 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/ssl/certificate_request' describe Puppet::SSL::CertificateRequest do include PuppetSpec::Files before do # Get a safe temporary file dir = tmpdir("csr_integration_testing") Puppet.settings[:confdir] = dir Puppet.settings[:vardir] = dir Puppet::SSL::Host.ca_location = :none @csr = Puppet::SSL::CertificateRequest.new("luke.madstop.com") @key = OpenSSL::PKey::RSA.new(512) # This is necessary so the terminus instances don't lie around. Puppet::SSL::CertificateRequest.indirection.termini.clear end it "should be able to generate CSRs" do @csr.generate(@key) end it "should be able to save CSRs" do Puppet::SSL::CertificateRequest.indirection.save(@csr) end it "should be able to find saved certificate requests via the Indirector" do @csr.generate(@key) Puppet::SSL::CertificateRequest.indirection.save(@csr) expect(Puppet::SSL::CertificateRequest.indirection.find("luke.madstop.com")).to be_instance_of(Puppet::SSL::CertificateRequest) end it "should save the completely CSR when saving" do @csr.generate(@key) Puppet::SSL::CertificateRequest.indirection.save(@csr) expect(Puppet::SSL::CertificateRequest.indirection.find("luke.madstop.com").content.to_s).to eq(@csr.content.to_s) end end puppet-5.5.10/spec/integration/ssl/certificate_revocation_list_spec.rb0000644005276200011600000000155413417161722026203 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/ssl/certificate_revocation_list' describe Puppet::SSL::CertificateRevocationList do include PuppetSpec::Files before do # Get a safe temporary file dir = tmpdir("ca_integration_testing") Puppet.settings[:confdir] = dir Puppet.settings[:vardir] = dir Puppet::SSL::Host.ca_location = :local end after { Puppet::SSL::Host.ca_location = :none # This is necessary so the terminus instances don't lie around. Puppet::SSL::Host.indirection.termini.clear } it "should be able to read in written out CRLs with no revoked certificates" do Puppet::SSL::CertificateAuthority.new raise "CRL not created" unless Puppet::FileSystem.exist?(Puppet[:hostcrl]) crl = Puppet::SSL::CertificateRevocationList.new("crl_int_testing") crl.read(Puppet[:hostcrl]) end end puppet-5.5.10/spec/integration/ssl/host_spec.rb0000644005276200011600000000471313417161722021412 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/ssl/host' describe Puppet::SSL::Host do include PuppetSpec::Files before do # Get a safe temporary file dir = tmpdir("host_integration_testing") Puppet.settings[:confdir] = dir Puppet.settings[:vardir] = dir Puppet::SSL::Host.ca_location = :local @host = Puppet::SSL::Host.new("luke.madstop.com") @ca = Puppet::SSL::CertificateAuthority.new end after { Puppet::SSL::Host.ca_location = :none } it "should be considered a CA host if its name is equal to 'ca'" do expect(Puppet::SSL::Host.new(Puppet::SSL::CA_NAME)).to be_ca end describe "when managing its key" do it "should be able to generate and save a key" do @host.generate_key end it "should save the key such that the Indirector can find it" do @host.generate_key expect(Puppet::SSL::Key.indirection.find(@host.name).content.to_s).to eq(@host.key.to_s) end it "should save the private key into the :privatekeydir" do @host.generate_key expect(File.read(File.join(Puppet.settings[:privatekeydir], "luke.madstop.com.pem"))).to eq(@host.key.to_s) end end describe "when managing its certificate request" do it "should be able to generate and save a certificate request" do @host.generate_certificate_request end it "should save the certificate request such that the Indirector can find it" do @host.generate_certificate_request expect(Puppet::SSL::CertificateRequest.indirection.find(@host.name).content.to_s).to eq(@host.certificate_request.to_s) end it "should save the private certificate request into the :privatekeydir" do @host.generate_certificate_request expect(File.read(File.join(Puppet.settings[:requestdir], "luke.madstop.com.pem"))).to eq(@host.certificate_request.to_s) end end describe "when the CA host" do it "should never store its key in the :privatekeydir" do Puppet.settings.use(:main, :ssl, :ca) @ca = Puppet::SSL::Host.new(Puppet::SSL::Host.ca_name) @ca.generate_key expect(Puppet::FileSystem.exist?(File.join(Puppet[:privatekeydir], "ca.pem"))).to be_falsey end end it "should pass the verification of its own SSL store", :unless => Puppet.features.microsoft_windows? do @host.generate @ca = Puppet::SSL::CertificateAuthority.new @ca.sign(@host.name) expect(@host.ssl_store.verify(@host.certificate.content)).to be_truthy end end puppet-5.5.10/spec/integration/ssl/key_spec.rb0000644005276200011600000000715413417161722021227 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/ssl/key' describe Puppet::SSL::Key do include PuppetSpec::Files # different UTF-8 widths # 1-byte A # 2-byte Űż - http://www.fileformat.info/info/unicode/char/06ff/index.htm - 0xDB 0xBF / 219 191 # 3-byte áš  - http://www.fileformat.info/info/unicode/char/16A0/index.htm - 0xE1 0x9A 0xA0 / 225 154 160 # 4-byte ÜŽ - http://www.fileformat.info/info/unicode/char/2070E/index.htm - 0xF0 0xA0 0x9C 0x8E / 240 160 156 142 let (:mixed_utf8) { "A\u06FF\u16A0\u{2070E}" } # AŰżáš ÜŽ before do # Get a safe temporary file dir = tmpdir('key_integration_testing') Puppet.settings[:confdir] = dir Puppet.settings[:vardir] = dir # This is necessary so the terminus instances don't lie around. # and so that Puppet::SSL::Key.indirection.save may be used Puppet::SSL::Key.indirection.termini.clear end describe 'with a custom user-specified passfile' do before do # write custom password file to where Puppet expects password_file = tmpfile('passfile') Puppet[:passfile] = password_file Puppet::FileSystem.open(password_file, nil, 'w:UTF-8') { |f| f.print(mixed_utf8) } end it 'should use the configured password file if it is not the CA key' do key = Puppet::SSL::Key.new('test') expect(key.password_file).to eq(Puppet[:passfile]) expect(key.password).to eq(mixed_utf8.force_encoding(Encoding::BINARY)) end it "should be able to read an existing private key given the correct password" do key_name = 'test' # use OpenSSL APIs to generate a private key private_key = OpenSSL::PKey::RSA.generate(512) # stash it in Puppets private key directory FileUtils.mkdir_p(Puppet[:privatekeydir]) pem_path = File.join(Puppet[:privatekeydir], "#{key_name}.pem") Puppet::FileSystem.open(pem_path, nil, 'w:UTF-8') do |f| # with password protection enabled pem = private_key.to_pem(OpenSSL::Cipher::DES.new(:EDE3, :CBC), mixed_utf8) f.print(pem) end # indirector loads existing .pem off disk instead of replacing it host = Puppet::SSL::Host.new(key_name) host.generate # newly loaded host private key matches the manually created key # Private-Key: (512 bit) style data expect(host.key.content.to_text).to eq(private_key.to_text) # -----BEGIN RSA PRIVATE KEY----- expect(host.key.content.to_s).to eq(private_key.to_s) expect(host.key.password).to eq(mixed_utf8.force_encoding(Encoding::BINARY)) end it 'should export the private key to PEM using the password' do key_name = 'test' # uses specified :passfile when writing the private key key = Puppet::SSL::Key.new(key_name) key.generate Puppet::SSL::Key.indirection.save(key) # indirector writes file here pem_path = File.join(Puppet[:privatekeydir], "#{key_name}.pem") # note incorrect password is an error expect do Puppet::FileSystem.open(pem_path, nil, 'r:ASCII') do |f| OpenSSL::PKey::RSA.new(f.read, 'invalid_password') end end.to raise_error(OpenSSL::PKey::RSAError) # but when specifying the correct password reloaded_key = nil Puppet::FileSystem.open(pem_path, nil, 'r:ASCII') do |f| reloaded_key = OpenSSL::PKey::RSA.new(f.read, mixed_utf8) end # the original key matches the manually reloaded key # Private-Key: (512 bit) style data expect(key.content.to_text).to eq(reloaded_key.to_text) # -----BEGIN RSA PRIVATE KEY----- expect(key.content.to_s).to eq(reloaded_key.to_s) end end end puppet-5.5.10/spec/integration/test/0000755005276200011600000000000013417162176017253 5ustar jenkinsjenkinspuppet-5.5.10/spec/integration/test/test_helper_spec.rb0000644005276200011600000000314413417161722023126 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe "Windows UTF8 environment variables", :if => Puppet.features.microsoft_windows? do # The Puppet::Util::Windows::Process class is used to manipulate environment variables as it is known to handle UTF8 characters. Where as the implementation of ENV in ruby does not. # before and end all are used to inject environment variables before the test helper 'before_each_test' function is called # Do not use before and after hooks in these tests as it may have unintended consequences before(:all) { @varname = 'test_helper_spec-test_variable' @rune_utf8 = "\u16A0\u16C7\u16BB\u16EB\u16D2\u16E6\u16A6\u16EB\u16A0\u16B1\u16A9\u16A0\u16A2\u16B1\u16EB\u16A0\u16C1\u16B1\u16AA\u16EB\u16B7\u16D6\u16BB\u16B9\u16E6\u16DA\u16B3\u16A2\u16D7" Puppet::Util::Windows::Process.set_environment_variable(@varname, @rune_utf8) } after(:all) { # Need to cleanup this environment variable otherwise it contaminates any subsequent tests Puppet::Util::Windows::Process.set_environment_variable(@varname, nil) } it "#after_each_test should preserve UTF8 environment variables" do envhash = Puppet::Util::Windows::Process.get_environment_strings expect(envhash[@varname]).to eq(@rune_utf8) # Change the value in the test to force test_helper to restore the environment ENV[@varname] = 'bad foo' # Prematurely trigger the after_each_test method Puppet::Test::TestHelper.after_each_test envhash = Puppet::Util::Windows::Process.get_environment_strings expect(envhash[@varname]).to eq(@rune_utf8) end end puppet-5.5.10/spec/integration/transaction/0000755005276200011600000000000013417162176020621 5ustar jenkinsjenkinspuppet-5.5.10/spec/integration/transaction/report_spec.rb0000644005276200011600000006761613417161722023507 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/files' describe Puppet::Transaction::Report do before :each do # Enable persistence during tests Puppet::Transaction::Persistence.any_instance.stubs(:enabled?).returns(true) end describe "when using the indirector" do after do Puppet.settings.stubs(:use) end it "should be able to delegate to the :processor terminus" do Puppet::Transaction::Report.indirection.stubs(:terminus_class).returns :processor terminus = Puppet::Transaction::Report.indirection.terminus(:processor) Facter.stubs(:value).returns "host.domain.com" report = Puppet::Transaction::Report.new terminus.expects(:process).with(report) Puppet::Transaction::Report.indirection.save(report) end end describe "when dumping to YAML" do it "should not contain TagSet objects" do resource = Puppet::Resource.new(:notify, "Hello") ral_resource = resource.to_ral status = Puppet::Resource::Status.new(ral_resource) log = Puppet::Util::Log.new(:level => :info, :message => "foo") report = Puppet::Transaction::Report.new report.add_resource_status(status) report << log expect(YAML.dump(report)).to_not match('Puppet::Util::TagSet') end end describe "inference checking" do include PuppetSpec::Files require 'puppet/configurer' def run_catalogs(resources1, resources2, noop1 = false, noop2 = false, &block) last_run_report = nil Puppet::Transaction::Report.indirection.expects(:save).twice.with do |report, x| last_run_report = report true end Puppet[:report] = true Puppet[:noop] = noop1 configurer = Puppet::Configurer.new configurer.run :catalog => new_catalog(resources1) yield block if block last_report = last_run_report Puppet[:noop] = noop2 configurer = Puppet::Configurer.new configurer.run :catalog => new_catalog(resources2) expect(last_report).not_to eq(last_run_report) return last_run_report end def new_blank_catalog Puppet::Resource::Catalog.new("testing", Puppet.lookup(:environments).get(Puppet[:environment])) end def new_catalog(resources = []) new_cat = new_blank_catalog [resources].flatten.each do |resource| new_cat.add_resource(resource) end new_cat end def get_cc_count(report) report.metrics["resources"].values.each do |v| if v[0] == "corrective_change" return v[2] end end return nil end describe "for agent runs that contain" do it "notifies with catalog change" do report = run_catalogs(Puppet::Type.type(:notify).new(:title => "testing", :message => "foo"), Puppet::Type.type(:notify).new(:title => "testing", :message => "foobar")) expect(report.status).to eq("changed") rs = report.resource_statuses["Notify[testing]"] expect(rs.events.size).to eq(1) expect(rs.events[0].corrective_change).to eq(false) expect(rs.corrective_change).to eq(false) expect(report.corrective_change).to eq(false) expect(get_cc_count(report)).to eq(0) end it "notifies with no catalog change" do report = run_catalogs(Puppet::Type.type(:notify).new(:title => "testing", :message => "foo"), Puppet::Type.type(:notify).new(:title => "testing", :message => "foo")) expect(report.status).to eq("changed") rs = report.resource_statuses["Notify[testing]"] expect(rs.events.size).to eq(1) expect(rs.events[0].corrective_change).to eq(false) expect(rs.corrective_change).to eq(false) expect(report.corrective_change).to eq(false) expect(get_cc_count(report)).to eq(0) end it "new file resource" do file = tmpfile("test_file") report = run_catalogs([], Puppet::Type.type(:file).new(:title => file, :content => "mystuff")) expect(report.status).to eq("changed") rs = report.resource_statuses["File[#{file}]"] expect(rs.events.size).to eq(1) expect(rs.events[0].corrective_change).to eq(false) expect(rs.corrective_change).to eq(false) expect(report.corrective_change).to eq(false) expect(get_cc_count(report)).to eq(0) end it "removal of a file resource" do file = tmpfile("test_file") report = run_catalogs(Puppet::Type.type(:file).new(:title => file, :content => "mystuff"), []) expect(report.status).to eq("unchanged") expect(report.resource_statuses["File[#{file}]"]).to eq(nil) expect(report.corrective_change).to eq(false) expect(get_cc_count(report)).to eq(0) end it "file with a title change" do file1 = tmpfile("test_file") file2 = tmpfile("test_file") report = run_catalogs(Puppet::Type.type(:file).new(:title => file1, :content => "mystuff"), Puppet::Type.type(:file).new(:title => file2, :content => "mystuff")) expect(report.status).to eq("changed") expect(report.resource_statuses["File[#{file1}]"]).to eq(nil) rs = report.resource_statuses["File[#{file2}]"] expect(rs.events.size).to eq(1) expect(rs.events[0].corrective_change).to eq(false) expect(rs.corrective_change).to eq(false) expect(report.corrective_change).to eq(false) expect(get_cc_count(report)).to eq(0) end it "file with no catalog change" do file = tmpfile("test_file") report = run_catalogs(Puppet::Type.type(:file).new(:title => file, :content => "mystuff"), Puppet::Type.type(:file).new(:title => file, :content => "mystuff")) expect(report.status).to eq("unchanged") rs = report.resource_statuses["File[#{file}]"] expect(rs.events.size).to eq(0) expect(rs.corrective_change).to eq(false) expect(report.corrective_change).to eq(false) expect(get_cc_count(report)).to eq(0) end it "file with a new parameter" do file = tmpfile("test_file") report = run_catalogs(Puppet::Type.type(:file).new(:title => file, :content => "mystuff"), Puppet::Type.type(:file).new(:title => file, :content => "mystuff", :loglevel => :debug)) expect(report.status).to eq("unchanged") rs = report.resource_statuses["File[#{file}]"] expect(rs.events.size).to eq(0) expect(rs.corrective_change).to eq(false) expect(report.corrective_change).to eq(false) expect(get_cc_count(report)).to eq(0) end it "file with a removed parameter" do file = tmpfile("test_file") report = run_catalogs(Puppet::Type.type(:file).new(:title => file, :content => "mystuff", :loglevel => :debug), Puppet::Type.type(:file).new(:title => file, :content => "mystuff")) expect(report.status).to eq("unchanged") rs = report.resource_statuses["File[#{file}]"] expect(rs.events.size).to eq(0) expect(rs.corrective_change).to eq(false) expect(report.corrective_change).to eq(false) expect(get_cc_count(report)).to eq(0) end it "file with a property no longer managed" do file = tmpfile("test_file") report = run_catalogs(Puppet::Type.type(:file).new(:title => file, :content => "mystuff"), Puppet::Type.type(:file).new(:title => file)) expect(report.status).to eq("unchanged") rs = report.resource_statuses["File[#{file}]"] expect(rs.events.size).to eq(0) expect(rs.corrective_change).to eq(false) expect(report.corrective_change).to eq(false) expect(get_cc_count(report)).to eq(0) end it "file with no catalog change, but file changed between runs" do file = tmpfile("test_file") report = run_catalogs(Puppet::Type.type(:file).new(:title => file, :content => "mystuff"), Puppet::Type.type(:file).new(:title => file, :content => "mystuff")) do File.open(file, 'w') do |f| f.puts "some content" end end expect(report.status).to eq("changed") rs = report.resource_statuses["File[#{file}]"] expect(rs.events.size).to eq(1) expect(rs.events[0].corrective_change).to eq(true) expect(rs.corrective_change).to eq(true) expect(report.corrective_change).to eq(true) expect(get_cc_count(report)).to eq(1) end it "file with catalog change, but file changed between runs that matched catalog change" do file = tmpfile("test_file") report = run_catalogs(Puppet::Type.type(:file).new(:title => file, :content => "mystuff"), Puppet::Type.type(:file).new(:title => file, :content => "some content")) do File.open(file, 'w') do |f| f.write "some content" end end expect(report.status).to eq("unchanged") rs = report.resource_statuses["File[#{file}]"] expect(rs.events.size).to eq(0) expect(rs.corrective_change).to eq(false) expect(report.corrective_change).to eq(false) expect(get_cc_count(report)).to eq(0) end it "file with catalog change, but file changed between runs that did not match catalog change" do file = tmpfile("test_file") report = run_catalogs(Puppet::Type.type(:file).new(:title => file, :content => "mystuff1"), Puppet::Type.type(:file).new(:title => file, :content => "mystuff2")) do File.open(file, 'w') do |f| f.write "some content" end end expect(report.status).to eq("changed") rs = report.resource_statuses["File[#{file}]"] expect(rs.events.size).to eq(1) expect(rs.events[0].corrective_change).to eq(true) expect(rs.corrective_change).to eq(true) expect(report.corrective_change).to eq(true) expect(get_cc_count(report)).to eq(1) end it "file with catalog change" do file = tmpfile("test_file") report = run_catalogs(Puppet::Type.type(:file).new(:title => file, :content => "mystuff1"), Puppet::Type.type(:file).new(:title => file, :content => "mystuff2")) expect(report.status).to eq("changed") rs = report.resource_statuses["File[#{file}]"] expect(rs.events.size).to eq(1) expect(rs.events[0].corrective_change).to eq(false) expect(rs.corrective_change).to eq(false) expect(report.corrective_change).to eq(false) expect(get_cc_count(report)).to eq(0) end it "file with ensure property set to present" do file = tmpfile("test_file") report = run_catalogs(Puppet::Type.type(:file).new(:title => file, :ensure => :present), Puppet::Type.type(:file).new(:title => file, :ensure => :present)) expect(report.status).to eq("unchanged") rs = report.resource_statuses["File[#{file}]"] expect(rs.events.size).to eq(0) expect(rs.corrective_change).to eq(false) expect(report.corrective_change).to eq(false) expect(get_cc_count(report)).to eq(0) end it "file with ensure property change file => absent" do file = tmpfile("test_file") report = run_catalogs(Puppet::Type.type(:file).new(:title => file, :ensure => :file), Puppet::Type.type(:file).new(:title => file, :ensure => :absent)) expect(report.status).to eq("changed") rs = report.resource_statuses["File[#{file}]"] expect(rs.events.size).to eq(1) expect(rs.events[0].corrective_change).to eq(false) expect(rs.corrective_change).to eq(false) expect(report.corrective_change).to eq(false) expect(get_cc_count(report)).to eq(0) end it "file with ensure property change present => absent" do file = tmpfile("test_file") report = run_catalogs(Puppet::Type.type(:file).new(:title => file, :ensure => :present), Puppet::Type.type(:file).new(:title => file, :ensure => :absent)) expect(report.status).to eq("changed") rs = report.resource_statuses["File[#{file}]"] expect(rs.events.size).to eq(1) expect(rs.events[0].corrective_change).to eq(false) expect(rs.corrective_change).to eq(false) expect(report.corrective_change).to eq(false) expect(get_cc_count(report)).to eq(0) end it "link with ensure property change present => absent", :unless => Puppet.features.microsoft_windows? do file = tmpfile("test_file") FileUtils.symlink(file, tmpfile("test_link")) report = run_catalogs(Puppet::Type.type(:file).new(:title => file, :ensure => :present), Puppet::Type.type(:file).new(:title => file, :ensure => :absent)) expect(report.status).to eq("changed") rs = report.resource_statuses["File[#{file}]"] expect(rs.events.size).to eq(1) expect(rs.events[0].corrective_change).to eq(false) expect(rs.corrective_change).to eq(false) expect(report.corrective_change).to eq(false) expect(get_cc_count(report)).to eq(0) end it "file with ensure property change absent => present" do file = tmpfile("test_file") report = run_catalogs(Puppet::Type.type(:file).new(:title => file, :ensure => :absent), Puppet::Type.type(:file).new(:title => file, :ensure => :present)) expect(report.status).to eq("changed") rs = report.resource_statuses["File[#{file}]"] expect(rs.events.size).to eq(1) expect(rs.events[0].corrective_change).to eq(false) expect(rs.corrective_change).to eq(false) expect(report.corrective_change).to eq(false) expect(get_cc_count(report)).to eq(0) end it "new resource in catalog" do file = tmpfile("test_file") report = run_catalogs([], Puppet::Type.type(:file).new(:title => file, :content => "mystuff asdf")) expect(report.status).to eq("changed") rs = report.resource_statuses["File[#{file}]"] expect(rs.events.size).to eq(1) expect(rs.events[0].corrective_change).to eq(false) expect(rs.corrective_change).to eq(false) expect(report.corrective_change).to eq(false) expect(get_cc_count(report)).to eq(0) end it "exec with idempotence issue", :unless => Puppet.features.microsoft_windows? do report = run_catalogs(Puppet::Type.type(:exec).new(:title => "exec1", :command => "/bin/echo foo"), Puppet::Type.type(:exec).new(:title => "exec1", :command => "/bin/echo foo")) expect(report.status).to eq("changed") # Of note here, is that the main idempotence issues lives in 'returns' rs = report.resource_statuses["Exec[exec1]"] expect(rs.events.size).to eq(1) expect(rs.events[0].corrective_change).to eq(true) expect(rs.corrective_change).to eq(true) expect(report.corrective_change).to eq(true) expect(get_cc_count(report)).to eq(1) end it "exec with no idempotence issue", :unless => Puppet.features.microsoft_windows? do report = run_catalogs(Puppet::Type.type(:exec).new(:title => "exec1", :command => "echo foo", :path => "/bin", :unless => "ls"), Puppet::Type.type(:exec).new(:title => "exec1", :command => "echo foo", :path => "/bin", :unless => "ls")) expect(report.status).to eq("unchanged") # Of note here, is that the main idempotence issues lives in 'returns' rs = report.resource_statuses["Exec[exec1]"] expect(rs.events.size).to eq(0) expect(rs.corrective_change).to eq(false) expect(report.corrective_change).to eq(false) expect(get_cc_count(report)).to eq(0) end it "noop on second run, file with no catalog change, but file changed between runs" do file = tmpfile("test_file") report = run_catalogs(Puppet::Type.type(:file).new(:title => file, :content => "mystuff"), Puppet::Type.type(:file).new(:title => file, :content => "mystuff"), false, true) do File.open(file, 'w') do |f| f.puts "some content" end end expect(report.status).to eq("unchanged") rs = report.resource_statuses["File[#{file}]"] expect(rs.events[0].corrective_change).to eq(true) expect(rs.events.size).to eq(1) expect(rs.corrective_change).to eq(true) expect(report.corrective_change).to eq(true) expect(get_cc_count(report)).to eq(1) end it "noop on all subsequent runs, file with no catalog change, but file changed between run 1 and 2" do file = tmpfile("test_file") report = run_catalogs(Puppet::Type.type(:file).new(:title => file, :content => "mystuff"), Puppet::Type.type(:file).new(:title => file, :content => "mystuff"), false, true) do File.open(file, 'w') do |f| f.puts "some content" end end expect(report.status).to eq("unchanged") rs = report.resource_statuses["File[#{file}]"] expect(rs.events[0].corrective_change).to eq(true) expect(rs.events.size).to eq(1) expect(rs.corrective_change).to eq(true) expect(report.corrective_change).to eq(true) expect(get_cc_count(report)).to eq(1) # Simply run the catalog twice again, but this time both runs are noop to # test if the corrective field is still set. report = run_catalogs(Puppet::Type.type(:file).new(:title => file, :content => "mystuff"), Puppet::Type.type(:file).new(:title => file, :content => "mystuff"), true, true) expect(report.status).to eq("unchanged") rs = report.resource_statuses["File[#{file}]"] expect(rs.events[0].corrective_change).to eq(true) expect(rs.events.size).to eq(1) expect(rs.corrective_change).to eq(true) expect(report.corrective_change).to eq(true) expect(get_cc_count(report)).to eq(1) end it "noop on first run, file with no catalog change, but file changed between runs" do file = tmpfile("test_file") report = run_catalogs(Puppet::Type.type(:file).new(:title => file, :content => "mystuff"), Puppet::Type.type(:file).new(:title => file, :content => "mystuff"), true, false) do File.open(file, 'w') do |f| f.puts "some content" end end expect(report.status).to eq("changed") rs = report.resource_statuses["File[#{file}]"] expect(rs.events[0].corrective_change).to eq(true) expect(rs.events.size).to eq(1) expect(rs.corrective_change).to eq(true) expect(report.corrective_change).to eq(true) expect(get_cc_count(report)).to eq(1) end it "noop on both runs, file with no catalog change, but file changed between runs" do file = tmpfile("test_file") report = run_catalogs(Puppet::Type.type(:file).new(:title => file, :content => "mystuff"), Puppet::Type.type(:file).new(:title => file, :content => "mystuff"), true, true) do File.open(file, 'w') do |f| f.puts "some content" end end expect(report.status).to eq("unchanged") rs = report.resource_statuses["File[#{file}]"] expect(rs.events.size).to eq(1) expect(rs.events[0].corrective_change).to eq(true) expect(rs.corrective_change).to eq(true) expect(report.corrective_change).to eq(true) expect(get_cc_count(report)).to eq(1) end it "noop on 4 runs, file with no catalog change, but file changed between runs 1 and 2" do file = tmpfile("test_file") report = run_catalogs(Puppet::Type.type(:file).new(:title => file, :content => "mystuff"), Puppet::Type.type(:file).new(:title => file, :content => "mystuff"), true, true) do File.open(file, 'w') do |f| f.puts "some content" end end expect(report.status).to eq("unchanged") rs = report.resource_statuses["File[#{file}]"] expect(rs.events.size).to eq(1) expect(rs.events[0].corrective_change).to eq(true) expect(rs.corrective_change).to eq(true) expect(report.corrective_change).to eq(true) expect(get_cc_count(report)).to eq(1) report = run_catalogs(Puppet::Type.type(:file).new(:title => file, :content => "mystuff"), Puppet::Type.type(:file).new(:title => file, :content => "mystuff"), true, true) expect(report.status).to eq("unchanged") rs = report.resource_statuses["File[#{file}]"] expect(rs.events.size).to eq(1) expect(rs.events[0].corrective_change).to eq(true) expect(rs.corrective_change).to eq(true) expect(report.corrective_change).to eq(true) expect(get_cc_count(report)).to eq(1) end it "noop on both runs, file already exists but with catalog change each time" do file = tmpfile("test_file") File.open(file, 'w') do |f| f.puts "some content" end report = run_catalogs(Puppet::Type.type(:file).new(:title => file, :content => "a"), Puppet::Type.type(:file).new(:title => file, :content => "b"), true, true) expect(report.status).to eq("unchanged") rs = report.resource_statuses["File[#{file}]"] expect(rs.events.size).to eq(1) expect(rs.events[0].corrective_change).to eq(false) expect(rs.corrective_change).to eq(false) expect(report.corrective_change).to eq(false) expect(get_cc_count(report)).to eq(0) end it "file failure should not return corrective_change" do # Making the path a child path (with no parent) forces a failure file = tmpfile("test_file") + "/foo" report = run_catalogs(Puppet::Type.type(:file).new(:title => file, :content => "a"), Puppet::Type.type(:file).new(:title => file, :content => "b"), false, false) expect(report.status).to eq("failed") rs = report.resource_statuses["File[#{file}]"] expect(rs.events.size).to eq(1) expect(rs.events[0].corrective_change).to eq(false) expect(rs.corrective_change).to eq(false) expect(report.corrective_change).to eq(false) expect(get_cc_count(report)).to eq(0) end it "file skipped with file change between runs will not show corrective_change" do # Making the path a child path (with no parent) forces a failure file = tmpfile("test_file") + "/foo" resources1 = [ Puppet::Type.type(:file).new(:title => file, :content => "a", :notify => "Notify['foo']"), Puppet::Type.type(:notify).new(:title => "foo") ] resources2 = [ Puppet::Type.type(:file).new(:title => file, :content => "a", :notify => "Notify[foo]"), Puppet::Type.type(:notify).new(:title => "foo", :message => "foo") ] report = run_catalogs(resources1, resources2, false, false) expect(report.status).to eq("failed") rs = report.resource_statuses["File[#{file}]"] expect(rs.events.size).to eq(1) expect(rs.events[0].corrective_change).to eq(false) expect(rs.corrective_change).to eq(false) rs = report.resource_statuses["Notify[foo]"] expect(rs.events.size).to eq(0) expect(rs.corrective_change).to eq(false) expect(report.corrective_change).to eq(false) expect(get_cc_count(report)).to eq(0) end end end end puppet-5.5.10/spec/integration/type/0000755005276200011600000000000013417162176017255 5ustar jenkinsjenkinspuppet-5.5.10/spec/integration/type/tidy_spec.rb0000644005276200011600000000214413417161721021561 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/compiler' require 'puppet_spec/files' require 'puppet/file_bucket/dipper' describe Puppet::Type.type(:tidy) do include PuppetSpec::Files include PuppetSpec::Compiler before do Puppet::Util::Storage.stubs(:store) end it "should be able to recursively remove directories" do dir = tmpfile("tidy_testing") FileUtils.mkdir_p(File.join(dir, "foo", "bar")) apply_compiled_manifest(<<-MANIFEST) tidy { '#{dir}': recurse => true, rmdirs => true, } MANIFEST expect(Puppet::FileSystem.directory?(dir)).to be_falsey end # Testing #355. it "should be able to remove dead links", :if => Puppet.features.manages_symlinks? do dir = tmpfile("tidy_link_testing") link = File.join(dir, "link") target = tmpfile("no_such_file_tidy_link_testing") Dir.mkdir(dir) Puppet::FileSystem.symlink(target, link) apply_compiled_manifest(<<-MANIFEST) tidy { '#{dir}': recurse => true, } MANIFEST expect(Puppet::FileSystem.symlink?(link)).to be_falsey end end puppet-5.5.10/spec/integration/type/exec_spec.rb0000644005276200011600000000337013417161722021537 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/files' describe Puppet::Type.type(:exec) do include PuppetSpec::Files let(:catalog) { Puppet::Resource::Catalog.new } let(:path) { tmpfile('exec_provider') } let(:command) { "ruby -e 'File.open(\"#{path}\", \"w\") { |f| f.print \"foo\" }'" } before :each do catalog.host_config = false end it "should execute the command" do exec = described_class.new :command => command, :path => ENV['PATH'] catalog.add_resource exec catalog.apply expect(File.read(path)).to eq('foo') end it "should not execute the command if onlyif returns non-zero" do exec = described_class.new( :command => command, :onlyif => "ruby -e 'exit 44'", :path => ENV['PATH'] ) catalog.add_resource exec catalog.apply expect(Puppet::FileSystem.exist?(path)).to be_falsey end it "should execute the command if onlyif returns zero" do exec = described_class.new( :command => command, :onlyif => "ruby -e 'exit 0'", :path => ENV['PATH'] ) catalog.add_resource exec catalog.apply expect(File.read(path)).to eq('foo') end it "should execute the command if unless returns non-zero" do exec = described_class.new( :command => command, :unless => "ruby -e 'exit 45'", :path => ENV['PATH'] ) catalog.add_resource exec catalog.apply expect(File.read(path)).to eq('foo') end it "should not execute the command if unless returns zero" do exec = described_class.new( :command => command, :unless => "ruby -e 'exit 0'", :path => ENV['PATH'] ) catalog.add_resource exec catalog.apply expect(Puppet::FileSystem.exist?(path)).to be_falsey end end puppet-5.5.10/spec/integration/type/file_spec.rb0000644005276200011600000021054313417161722021534 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/files' if Puppet.features.microsoft_windows? require 'puppet/util/windows' class WindowsSecurity extend Puppet::Util::Windows::Security end end describe Puppet::Type.type(:file), :uses_checksums => true do include PuppetSpec::Files include_context 'with supported checksum types' let(:catalog) { Puppet::Resource::Catalog.new } let(:path) do # we create a directory first so backups of :path that are stored in # the same directory will also be removed after the tests parent = tmpdir('file_spec') File.join(parent, 'file_testing') end let(:dir) do # we create a directory first so backups of :path that are stored in # the same directory will also be removed after the tests parent = tmpdir('file_spec') File.join(parent, 'dir_testing') end if Puppet.features.posix? def set_mode(mode, file) File.chmod(mode, file) end def get_mode(file) Puppet::FileSystem.lstat(file).mode end def get_owner(file) Puppet::FileSystem.lstat(file).uid end def get_group(file) Puppet::FileSystem.lstat(file).gid end else class SecurityHelper extend Puppet::Util::Windows::Security end def set_mode(mode, file) SecurityHelper.set_mode(mode, file) end def get_mode(file) SecurityHelper.get_mode(file) end def get_owner(file) SecurityHelper.get_owner(file) end def get_group(file) SecurityHelper.get_group(file) end def get_aces_for_path_by_sid(path, sid) SecurityHelper.get_aces_for_path_by_sid(path, sid) end end around :each do |example| Puppet.override(:environments => Puppet::Environments::Static.new) do example.run end end before do # stub this to not try to create state.yaml Puppet::Util::Storage.stubs(:store) Puppet::Type.type(:file).any_instance.stubs(:file).returns('my/file.pp') Puppet::Type.type(:file).any_instance.stubs(:line).returns 5 end it "should not attempt to manage files that do not exist if no means of creating the file is specified" do source = tmpfile('source') catalog.add_resource described_class.new :path => source, :mode => '0755' status = catalog.apply.report.resource_statuses["File[#{source}]"] expect(status).not_to be_failed expect(status).not_to be_changed expect(Puppet::FileSystem.exist?(source)).to be_falsey end describe "when ensure is present using an empty file" do before(:each) do catalog.add_resource(described_class.new(:path => path, :ensure => :present, :backup => :false)) end context "file is present" do before(:each) do FileUtils.touch(path) end it "should do nothing" do report = catalog.apply.report expect(report.resource_statuses["File[#{path}]"]).not_to be_failed expect(Puppet::FileSystem.exist?(path)).to be_truthy end it "should log nothing" do logs = catalog.apply.report.logs expect(logs).to be_empty end end context "file is not present" do it "should create the file" do report = catalog.apply.report expect(report.resource_statuses["File[#{path}]"]).not_to be_failed expect(Puppet::FileSystem.exist?(path)).to be_truthy end it "should log that the file was created" do logs = catalog.apply.report.logs expect(logs.first.source).to eq("/File[#{path}]/ensure") expect(logs.first.message).to eq("created") end end end describe "when ensure is absent" do before(:each) do catalog.add_resource(described_class.new(:path => path, :ensure => :absent, :backup => :false)) end context "file is present" do before(:each) do FileUtils.touch(path) end it "should remove the file" do report = catalog.apply.report expect(report.resource_statuses["File[#{path}]"]).not_to be_failed expect(Puppet::FileSystem.exist?(path)).to be_falsey end it "should log that the file was removed" do logs = catalog.apply.report.logs expect(logs.first.source).to eq("/File[#{path}]/ensure") expect(logs.first.message).to eq("removed") end end context "file is not present" do it "should do nothing" do report = catalog.apply.report expect(report.resource_statuses["File[#{path}]"]).not_to be_failed expect(Puppet::FileSystem.exist?(path)).to be_falsey end it "should log nothing" do logs = catalog.apply.report.logs expect(logs).to be_empty end end # issue #14599 it "should not fail if parts of path aren't directories" do FileUtils.touch(path) catalog.add_resource(described_class.new(:path => File.join(path,'no_such_file'), :ensure => :absent, :backup => :false)) report = catalog.apply.report expect(report.resource_statuses["File[#{File.join(path,'no_such_file')}]"]).not_to be_failed end end describe "when setting permissions" do it "should set the owner" do target = tmpfile_with_contents('target', '') owner = get_owner(target) catalog.add_resource described_class.new( :name => target, :owner => owner ) catalog.apply expect(get_owner(target)).to eq(owner) end it "should set the group" do target = tmpfile_with_contents('target', '') group = get_group(target) catalog.add_resource described_class.new( :name => target, :group => group ) catalog.apply expect(get_group(target)).to eq(group) end describe "when setting mode" do describe "for directories" do let(:target) { tmpdir('dir_mode') } it "should set executable bits for newly created directories" do catalog.add_resource described_class.new(:path => target, :ensure => :directory, :mode => '0600') catalog.apply expect(get_mode(target) & 07777).to eq(0700) end it "should set executable bits for existing readable directories" do set_mode(0600, target) catalog.add_resource described_class.new(:path => target, :ensure => :directory, :mode => '0644') catalog.apply expect(get_mode(target) & 07777).to eq(0755) end it "should not set executable bits for unreadable directories" do begin catalog.add_resource described_class.new(:path => target, :ensure => :directory, :mode => '0300') catalog.apply expect(get_mode(target) & 07777).to eq(0300) ensure # so we can cleanup set_mode(0700, target) end end it "should set user, group, and other executable bits" do catalog.add_resource described_class.new(:path => target, :ensure => :directory, :mode => '0664') catalog.apply expect(get_mode(target) & 07777).to eq(0775) end it "should set executable bits when overwriting a non-executable file" do target_path = tmpfile_with_contents('executable', '') set_mode(0444, target_path) catalog.add_resource described_class.new(:path => target_path, :ensure => :directory, :mode => '0666', :backup => false) catalog.apply expect(get_mode(target_path) & 07777).to eq(0777) expect(File).to be_directory(target_path) end end describe "for files" do it "should not set executable bits" do catalog.add_resource described_class.new(:path => path, :ensure => :file, :mode => '0666') catalog.apply expect(get_mode(path) & 07777).to eq(0666) end it "should not set executable bits when replacing an executable directory (#10365)" do pending("bug #10365") FileUtils.mkdir(path) set_mode(0777, path) catalog.add_resource described_class.new(:path => path, :ensure => :file, :mode => '0666', :backup => false, :force => true) catalog.apply expect(get_mode(path) & 07777).to eq(0666) end end describe "for links", :if => described_class.defaultprovider.feature?(:manages_symlinks) do let(:link) { tmpfile('link_mode') } describe "when managing links" do let(:link_target) { tmpfile('target') } before :each do FileUtils.touch(link_target) File.chmod(0444, link_target) Puppet::FileSystem.symlink(link_target, link) end it "should not set the executable bit on the link target" do catalog.add_resource described_class.new(:path => link, :ensure => :link, :mode => '0666', :target => link_target, :links => :manage) catalog.apply expected_target_permissions = Puppet::Util::Platform.windows? ? 0700 : 0444 expect(Puppet::FileSystem.stat(link_target).mode & 07777).to eq(expected_target_permissions) end it "should ignore dangling symlinks (#6856)" do File.delete(link_target) catalog.add_resource described_class.new(:path => link, :ensure => :link, :mode => '0666', :target => link_target, :links => :manage) catalog.apply expect(Puppet::FileSystem.exist?(link)).to be_falsey end it "should create a link to the target if ensure is omitted" do FileUtils.touch(link_target) catalog.add_resource described_class.new(:path => link, :target => link_target) catalog.apply expect(Puppet::FileSystem.exist?(link)).to be_truthy expect(Puppet::FileSystem.lstat(link).ftype).to eq('link') expect(Puppet::FileSystem.readlink(link)).to eq(link_target) end end describe "when following links" do it "should ignore dangling symlinks (#6856)" do target = tmpfile('dangling') FileUtils.touch(target) Puppet::FileSystem.symlink(target, link) File.delete(target) catalog.add_resource described_class.new(:path => path, :source => link, :mode => '0600', :links => :follow) catalog.apply end describe "to a directory" do let(:link_target) { tmpdir('dir_target') } before :each do File.chmod(0600, link_target) Puppet::FileSystem.symlink(link_target, link) end after :each do File.chmod(0750, link_target) end describe "that is readable" do it "should set the executable bits when creating the destination (#10315)" do catalog.add_resource described_class.new(:path => path, :source => link, :mode => '0666', :links => :follow) catalog.apply expect(File).to be_directory(path) expect(get_mode(path) & 07777).to eq(0777) end it "should set the executable bits when overwriting the destination (#10315)" do FileUtils.touch(path) catalog.add_resource described_class.new(:path => path, :source => link, :mode => '0666', :links => :follow, :backup => false) catalog.apply expect(File).to be_directory(path) expect(get_mode(path) & 07777).to eq(0777) end end describe "that is not readable" do before :each do set_mode(0300, link_target) end # so we can cleanup after :each do set_mode(0700, link_target) end it "should set executable bits when creating the destination (#10315)" do catalog.add_resource described_class.new(:path => path, :source => link, :mode => '0666', :links => :follow) catalog.apply expect(File).to be_directory(path) expect(get_mode(path) & 07777).to eq(0777) end it "should set executable bits when overwriting the destination" do FileUtils.touch(path) catalog.add_resource described_class.new(:path => path, :source => link, :mode => '0666', :links => :follow, :backup => false) catalog.apply expect(File).to be_directory(path) expect(get_mode(path) & 07777).to eq(0777) end end end describe "to a file" do let(:link_target) { tmpfile('file_target') } before :each do FileUtils.touch(link_target) Puppet::FileSystem.symlink(link_target, link) end it "should create the file, not a symlink (#2817, #10315)" do catalog.add_resource described_class.new(:path => path, :source => link, :mode => '0600', :links => :follow) catalog.apply expect(File).to be_file(path) expect(get_mode(path) & 07777).to eq(0600) end it "should not give a deprecation warning about using a checksum in content when using source to define content" do FileUtils.touch(path) Puppet.expects(:puppet_deprecation_warning).never catalog.add_resource described_class.new(:path => path, :source => link, :links => :follow) catalog.apply end context "overwriting a file" do before :each do FileUtils.touch(path) set_mode(0644, path) catalog.add_resource described_class.new(:path => path, :source => link, :mode => '0600', :links => :follow) end it "should overwrite the file" do catalog.apply expect(File).to be_file(path) expect(get_mode(path) & 07777).to eq(0600) end it "should log that the mode changed" do report = catalog.apply.report expect(report.logs.first.message).to eq("mode changed '0644' to '0600'") expect(report.logs.first.source).to eq("/File[#{path}]/mode") end end end describe "to a link to a directory" do let(:real_target) { tmpdir('real_target') } let(:target) { tmpfile('target') } before :each do File.chmod(0666, real_target) # link -> target -> real_target Puppet::FileSystem.symlink(real_target, target) Puppet::FileSystem.symlink(target, link) end after :each do File.chmod(0750, real_target) end describe "when following all links" do it "should create the destination and apply executable bits (#10315)" do catalog.add_resource described_class.new(:path => path, :source => link, :mode => '0600', :links => :follow) catalog.apply expect(File).to be_directory(path) expect(get_mode(path) & 07777).to eq(0700) end it "should overwrite the destination and apply executable bits" do FileUtils.mkdir(path) catalog.add_resource described_class.new(:path => path, :source => link, :mode => '0600', :links => :follow) catalog.apply expect(File).to be_directory(path) expect(get_mode(path) & 0111).to eq(0100) end end end end end end end describe "when writing files" do shared_examples "files are backed up" do |resource_options| it "should backup files to a filebucket when one is configured" do |example| if Puppet::Util::Platform.windows? && ['sha512', 'sha384'].include?(example.metadata[:digest_algorithm]) skip "PUP-8257: Skip file bucket test on windows for #{example.metadata[:digest_algorithm]} due to long path names" end filebucket = Puppet::Type.type(:filebucket).new :path => tmpfile("filebucket"), :name => "mybucket" file = described_class.new({:path => path, :backup => "mybucket", :content => "foo"}.merge(resource_options)) catalog.add_resource file catalog.add_resource filebucket File.open(file[:path], "w") { |f| f.write("bar") } d = filebucket_digest.call(IO.binread(file[:path])) catalog.apply expect(filebucket.bucket.getfile(d)).to eq("bar") end it "should backup files in the local directory when a backup string is provided" do file = described_class.new({:path => path, :backup => ".bak", :content => "foo"}.merge(resource_options)) catalog.add_resource file File.open(file[:path], "w") { |f| f.puts "bar" } catalog.apply backup = file[:path] + ".bak" expect(Puppet::FileSystem.exist?(backup)).to be_truthy expect(File.read(backup)).to eq("bar\n") end it "should fail if no backup can be performed" do dir = tmpdir("backups") file = described_class.new({:path => File.join(dir, "testfile"), :backup => ".bak", :content => "foo"}.merge(resource_options)) catalog.add_resource file File.open(file[:path], 'w') { |f| f.puts "bar" } # Create a directory where the backup should be so that writing to it fails Dir.mkdir(File.join(dir, "testfile.bak")) Puppet::Util::Log.stubs(:newmessage) catalog.apply expect(File.read(file[:path])).to eq("bar\n") end it "should not backup symlinks", :if => described_class.defaultprovider.feature?(:manages_symlinks) do link = tmpfile("link") dest1 = tmpfile("dest1") dest2 = tmpfile("dest2") bucket = Puppet::Type.type(:filebucket).new :path => tmpfile("filebucket"), :name => "mybucket" file = described_class.new({:path => link, :target => dest2, :ensure => :link, :backup => "mybucket"}.merge(resource_options)) catalog.add_resource file catalog.add_resource bucket File.open(dest1, "w") { |f| f.puts "whatever" } Puppet::FileSystem.symlink(dest1, link) catalog.apply expect(Puppet::FileSystem.readlink(link)).to eq(dest2) expect(Puppet::FileSystem.exist?(bucket[:path])).to be_falsey end it "should backup directories to the local filesystem by copying the whole directory" do file = described_class.new({:path => path, :backup => ".bak", :content => "foo", :force => true}.merge(resource_options)) catalog.add_resource file Dir.mkdir(path) otherfile = File.join(path, "foo") File.open(otherfile, "w") { |f| f.print "yay" } catalog.apply backup = "#{path}.bak" expect(FileTest).to be_directory(backup) expect(File.read(File.join(backup, "foo"))).to eq("yay") end it "should backup directories to filebuckets by backing up each file separately" do |example| if Puppet::Util::Platform.windows? && ['sha512', 'sha384'].include?(example.metadata[:digest_algorithm]) skip "PUP-8257: Skip file bucket test on windows for #{example.metadata[:digest_algorithm]} due to long path names" end bucket = Puppet::Type.type(:filebucket).new :path => tmpfile("filebucket"), :name => "mybucket" file = described_class.new({:path => tmpfile("bucket_backs"), :backup => "mybucket", :content => "foo", :force => true}.merge(resource_options)) catalog.add_resource file catalog.add_resource bucket Dir.mkdir(file[:path]) foofile = File.join(file[:path], "foo") barfile = File.join(file[:path], "bar") File.open(foofile, "w") { |f| f.print "fooyay" } File.open(barfile, "w") { |f| f.print "baryay" } food = filebucket_digest.call(File.read(foofile)) bard = filebucket_digest.call(File.read(barfile)) catalog.apply expect(bucket.bucket.getfile(food)).to eq("fooyay") expect(bucket.bucket.getfile(bard)).to eq("baryay") end end it "should not give a checksum deprecation warning when given actual content" do Puppet.expects(:puppet_deprecation_warning).never catalog.add_resource described_class.new(:path => path, :content => 'this is content') catalog.apply end with_digest_algorithms do it_should_behave_like "files are backed up", {} do let(:filebucket_digest) { method(:digest) } end it "should give a checksum deprecation warning" do Puppet.expects(:puppet_deprecation_warning).with('Using a checksum in a file\'s "content" property is deprecated. The ability to use a checksum to retrieve content from the filebucket using the "content" property will be removed in a future release. The literal value of the "content" property will be written to the file. The checksum retrieval functionality is being replaced by the use of static catalogs. See https://puppet.com/docs/puppet/latest/static_catalogs.html for more information.', {:file => 'my/file.pp', :line => 5}) d = digest("this is some content") catalog.add_resource described_class.new(:path => path, :content => "{#{digest_algorithm}}#{d}") catalog.apply end it "should not give a checksum deprecation warning when no content is specified while checksum and checksum value are used" do Puppet.expects(:puppet_deprecation_warning).never d = digest("this is some content") catalog.add_resource described_class.new(:path => path, :checksum => digest_algorithm, :checksum_value => d) catalog.apply end end CHECKSUM_TYPES_TO_TRY.each do |checksum_type, checksum| describe "when checksum_type is #{checksum_type}" do # FileBucket uses the globally configured default for lookup by digest, which right now is MD5. it_should_behave_like "files are backed up", {:checksum => checksum_type} do let(:filebucket_digest) { Proc.new {|x| Puppet::Util::Checksums.md5(x)} } end end end end describe "when recursing" do def build_path(dir) Dir.mkdir(dir) File.chmod(0750, dir) @dirs = [dir] @files = [] %w{one two}.each do |subdir| fdir = File.join(dir, subdir) Dir.mkdir(fdir) File.chmod(0750, fdir) @dirs << fdir %w{three}.each do |file| ffile = File.join(fdir, file) @files << ffile File.open(ffile, "w") { |f| f.puts "test #{file}" } File.chmod(0640, ffile) end end end it "should be able to recurse over a nonexistent file" do @file = described_class.new( :name => path, :mode => '0644', :recurse => true, :backup => false ) catalog.add_resource @file expect { @file.eval_generate }.not_to raise_error end it "should be able to recursively set properties on existing files" do path = tmpfile("file_integration_tests") build_path(path) file = described_class.new( :name => path, :mode => '0644', :recurse => true, :backup => false ) catalog.add_resource file catalog.apply expect(@dirs).not_to be_empty @dirs.each do |dir| expect(get_mode(dir) & 007777).to eq(0755) end expect(@files).not_to be_empty @files.each do |dir| expect(get_mode(dir) & 007777).to eq(0644) end end it "should be able to recursively make links to other files", :if => described_class.defaultprovider.feature?(:manages_symlinks) do source = tmpfile("file_link_integration_source") build_path(source) dest = tmpfile("file_link_integration_dest") @file = described_class.new(:name => dest, :target => source, :recurse => true, :ensure => :link, :backup => false) catalog.add_resource @file catalog.apply @dirs.each do |path| link_path = path.sub(source, dest) expect(Puppet::FileSystem.lstat(link_path)).to be_directory end @files.each do |path| link_path = path.sub(source, dest) expect(Puppet::FileSystem.lstat(link_path).ftype).to eq("link") end end it "should be able to recursively copy files" do source = tmpfile("file_source_integration_source") build_path(source) dest = tmpfile("file_source_integration_dest") @file = described_class.new(:name => dest, :source => source, :recurse => true, :backup => false) catalog.add_resource @file catalog.apply @dirs.each do |path| newpath = path.sub(source, dest) expect(Puppet::FileSystem.lstat(newpath)).to be_directory end @files.each do |path| newpath = path.sub(source, dest) expect(Puppet::FileSystem.lstat(newpath).ftype).to eq("file") end end it "should not recursively manage files set to be ignored" do srcdir = tmpfile("ignore_vs_recurse_1") dstdir = tmpfile("ignore_vs_recurse_2") FileUtils.mkdir_p(srcdir) FileUtils.mkdir_p(dstdir) srcfile = File.join(srcdir, "file.src") cpyfile = File.join(dstdir, "file.src") ignfile = File.join(srcdir, "file.ign") File.open(srcfile, "w") { |f| f.puts "don't ignore me" } File.open(ignfile, "w") { |f| f.puts "you better ignore me" } catalog.add_resource described_class.new( :name => srcdir, :ensure => 'directory', :mode => '0755',) catalog.add_resource described_class.new( :name => dstdir, :ensure => 'directory', :mode => "755", :source => srcdir, :recurse => true, :ignore => '*.ign',) catalog.apply expect(Puppet::FileSystem.exist?(srcdir)).to be_truthy expect(Puppet::FileSystem.exist?(dstdir)).to be_truthy expect(File.read(srcfile).strip).to eq("don't ignore me") expect(File.read(cpyfile).strip).to eq("don't ignore me") expect(Puppet::FileSystem.exist?("#{dstdir}/file.ign")).to be_falsey end it "should not recursively manage files managed by a more specific explicit file" do dir = tmpfile("recursion_vs_explicit_1") subdir = File.join(dir, "subdir") file = File.join(subdir, "file") FileUtils.mkdir_p(subdir) File.open(file, "w") { |f| f.puts "" } base = described_class.new(:name => dir, :recurse => true, :backup => false, :mode => "755") sub = described_class.new(:name => subdir, :recurse => true, :backup => false, :mode => "644") catalog.add_resource base catalog.add_resource sub catalog.apply expect(get_mode(file) & 007777).to eq(0644) end it "should recursively manage files even if there is an explicit file whose name is a prefix of the managed file" do managed = File.join(path, "file") generated = File.join(path, "file_with_a_name_starting_with_the_word_file") FileUtils.mkdir_p(path) FileUtils.touch(managed) FileUtils.touch(generated) catalog.add_resource described_class.new(:name => path, :recurse => true, :backup => false, :mode => '0700') catalog.add_resource described_class.new(:name => managed, :recurse => true, :backup => false, :mode => "644") catalog.apply expect(get_mode(generated) & 007777).to eq(0700) end describe "when recursing remote directories" do describe "for the 2nd time" do with_checksum_types "one", "x" do let(:target_file) { File.join(path, 'x') } let(:second_catalog) { Puppet::Resource::Catalog.new } before(:each) do @options = { :path => path, :ensure => :directory, :backup => false, :recurse => true, :checksum => checksum_type, :source => env_path } end it "should not update the target directory" do # Ensure the test believes the source file was written in the past. FileUtils.touch checksum_file, :mtime => Time.now - 20 catalog.add_resource Puppet::Type.send(:newfile, @options) catalog.apply expect(File).to be_directory(path) expect(Puppet::FileSystem.exist?(target_file)).to be_truthy # The 2nd time the resource should not change. second_catalog.add_resource Puppet::Type.send(:newfile, @options) result = second_catalog.apply status = result.report.resource_statuses["File[#{target_file}]"] expect(status).not_to be_failed expect(status).not_to be_changed end it "should update the target directory if contents change" do pending "a way to appropriately mock ctime checks for a particular file" if checksum_type == 'ctime' catalog.add_resource Puppet::Type.send(:newfile, @options) catalog.apply expect(File).to be_directory(path) expect(Puppet::FileSystem.exist?(target_file)).to be_truthy # Change the source file. File.open(checksum_file, "wb") { |f| f.write "some content" } FileUtils.touch target_file, :mtime => Time.now - 20 # The 2nd time should update the resource. second_catalog.add_resource Puppet::Type.send(:newfile, @options) result = second_catalog.apply status = result.report.resource_statuses["File[#{target_file}]"] expect(status).not_to be_failed expect(status).to be_changed end end end describe "when sourceselect first" do describe "for a directory" do it "should recursively copy the first directory that exists" do one = File.expand_path('thisdoesnotexist') two = tmpdir('two') FileUtils.mkdir_p(File.join(two, 'three')) FileUtils.touch(File.join(two, 'three', 'four')) catalog.add_resource Puppet::Type.newfile( :path => path, :ensure => :directory, :backup => false, :recurse => true, :sourceselect => :first, :source => [one, two] ) catalog.apply expect(File).to be_directory(path) expect(Puppet::FileSystem.exist?(File.join(path, 'one'))).to be_falsey expect(Puppet::FileSystem.exist?(File.join(path, 'three', 'four'))).to be_truthy end it "should recursively copy an empty directory" do one = File.expand_path('thisdoesnotexist') two = tmpdir('two') three = tmpdir('three') file_in_dir_with_contents(three, 'a', '') catalog.add_resource Puppet::Type.newfile( :path => path, :ensure => :directory, :backup => false, :recurse => true, :sourceselect => :first, :source => [one, two, three] ) catalog.apply expect(File).to be_directory(path) expect(Puppet::FileSystem.exist?(File.join(path, 'a'))).to be_falsey end it "should only recurse one level" do one = tmpdir('one') FileUtils.mkdir_p(File.join(one, 'a', 'b')) FileUtils.touch(File.join(one, 'a', 'b', 'c')) two = tmpdir('two') FileUtils.mkdir_p(File.join(two, 'z')) FileUtils.touch(File.join(two, 'z', 'y')) catalog.add_resource Puppet::Type.newfile( :path => path, :ensure => :directory, :backup => false, :recurse => true, :recurselimit => 1, :sourceselect => :first, :source => [one, two] ) catalog.apply expect(Puppet::FileSystem.exist?(File.join(path, 'a'))).to be_truthy expect(Puppet::FileSystem.exist?(File.join(path, 'a', 'b'))).to be_falsey expect(Puppet::FileSystem.exist?(File.join(path, 'z'))).to be_falsey end end describe "for a file" do it "should copy the first file that exists" do one = File.expand_path('thisdoesnotexist') two = tmpfile_with_contents('two', 'yay') three = tmpfile_with_contents('three', 'no') catalog.add_resource Puppet::Type.newfile( :path => path, :ensure => :file, :backup => false, :sourceselect => :first, :source => [one, two, three] ) catalog.apply expect(File.read(path)).to eq('yay') end it "should copy an empty file" do one = File.expand_path('thisdoesnotexist') two = tmpfile_with_contents('two', '') three = tmpfile_with_contents('three', 'no') catalog.add_resource Puppet::Type.newfile( :path => path, :ensure => :file, :backup => false, :sourceselect => :first, :source => [one, two, three] ) catalog.apply expect(File.read(path)).to eq('') end end end describe "when sourceselect all" do describe "for a directory" do it "should recursively copy all sources from the first valid source" do dest = tmpdir('dest') one = tmpdir('one') two = tmpdir('two') three = tmpdir('three') four = tmpdir('four') file_in_dir_with_contents(one, 'a', one) file_in_dir_with_contents(two, 'a', two) file_in_dir_with_contents(two, 'b', two) file_in_dir_with_contents(three, 'a', three) file_in_dir_with_contents(three, 'c', three) obj = Puppet::Type.newfile( :path => dest, :ensure => :directory, :backup => false, :recurse => true, :sourceselect => :all, :source => [one, two, three, four] ) catalog.add_resource obj catalog.apply expect(File.read(File.join(dest, 'a'))).to eq(one) expect(File.read(File.join(dest, 'b'))).to eq(two) expect(File.read(File.join(dest, 'c'))).to eq(three) end it "should only recurse one level from each valid source" do one = tmpdir('one') FileUtils.mkdir_p(File.join(one, 'a', 'b')) FileUtils.touch(File.join(one, 'a', 'b', 'c')) two = tmpdir('two') FileUtils.mkdir_p(File.join(two, 'z')) FileUtils.touch(File.join(two, 'z', 'y')) obj = Puppet::Type.newfile( :path => path, :ensure => :directory, :backup => false, :recurse => true, :recurselimit => 1, :sourceselect => :all, :source => [one, two] ) catalog.add_resource obj catalog.apply expect(Puppet::FileSystem.exist?(File.join(path, 'a'))).to be_truthy expect(Puppet::FileSystem.exist?(File.join(path, 'a', 'b'))).to be_falsey expect(Puppet::FileSystem.exist?(File.join(path, 'z'))).to be_truthy expect(Puppet::FileSystem.exist?(File.join(path, 'z', 'y'))).to be_falsey end end end end end describe "when generating resources" do before do source = tmpdir("generating_in_catalog_source") file_in_dir_with_contents(source, "one", "uno") file_in_dir_with_contents(source, "two", "dos") @file = described_class.new( :name => path, :source => source, :recurse => true, :backup => false ) catalog.add_resource @file end it "should add each generated resource to the catalog" do catalog.apply do |trans| expect(catalog.resource(:file, File.join(path, "one"))).to be_a(described_class) expect(catalog.resource(:file, File.join(path, "two"))).to be_a(described_class) end end it "should have an edge to each resource in the relationship graph" do catalog.apply do |trans| one = catalog.resource(:file, File.join(path, "one")) expect(catalog.relationship_graph).to be_edge(@file, one) two = catalog.resource(:file, File.join(path, "two")) expect(catalog.relationship_graph).to be_edge(@file, two) end end end describe "when copying files" do it "should be able to copy files with pound signs in their names (#285)" do source = tmpfile_with_contents("filewith#signs", "foo") dest = tmpfile("destwith#signs") catalog.add_resource described_class.new(:name => dest, :source => source) catalog.apply expect(File.read(dest)).to eq("foo") end it "should be able to copy files with spaces in their names" do dest = tmpfile("destwith spaces") source = tmpfile_with_contents("filewith spaces", "foo") catalog.add_resource described_class.new(:path => dest, :source => source) catalog.apply expect(File.read(dest)).to eq("foo") end it "should maintain source URIs as UTF-8 with Unicode characters in their names and be able to copy such files" do # different UTF-8 widths # 1-byte A # 2-byte Űż - http://www.fileformat.info/info/unicode/char/06ff/index.htm - 0xDB 0xBF / 219 191 # 3-byte áš  - http://www.fileformat.info/info/unicode/char/16A0/index.htm - 0xE1 0x9A 0xA0 / 225 154 160 # 4-byte - http://www.fileformat.info/info/unicode/char/2070E/index.htm - 0xF0 0xA0 0x9C 0x8E / 240 160 156 142 mixed_utf8 = "A\u06FF\u16A0\u{2070E}" # AŰżáš  dest = tmpfile("destwith #{mixed_utf8}") source = tmpfile_with_contents("filewith #{mixed_utf8}", "foo") catalog.add_resource described_class.new(:path => dest, :source => source) catalog.apply # find the resource and verify resource = catalog.resources.first { |r| r.title == "File[#{dest}]" } uri_path = resource.parameters[:source].uri.path # note that Windows file:// style URIs get an extra / in front of c:/ like /c:/ source_prefix = Puppet.features.microsoft_windows? ? '/' : '' # the URI can be round-tripped through unescape expect(URI.unescape(uri_path)).to eq(source_prefix + source) # and is properly UTF-8 expect(uri_path.encoding).to eq (Encoding::UTF_8) expect(File.read(dest)).to eq('foo') end it "should be able to copy individual files even if recurse has been specified" do source = tmpfile_with_contents("source", "foo") dest = tmpfile("dest") catalog.add_resource described_class.new(:name => dest, :source => source, :recurse => true) catalog.apply expect(File.read(dest)).to eq("foo") end end CHECKSUM_TYPES_TO_TRY.each do |checksum_type, checksum| describe "when checksum_type is #{checksum_type}" do before(:each) do @options = {:path => path, :content => CHECKSUM_PLAINTEXT, :checksum => checksum_type} end context "when changing the content" do before :each do FileUtils.touch(path) catalog.add_resource described_class.send(:new, @options) end it "should overwrite contents" do catalog.apply expect(Puppet::FileSystem.binread(path)).to eq(CHECKSUM_PLAINTEXT) end it "should log that content changed" do report = catalog.apply.report expect(report.logs.first.source).to eq("/File[#{path}]/content") expect(report.logs.first.message).to match(/content changed '{#{checksum_type}}[0-9a-f]*' to '{#{checksum_type}}#{checksum}'/) end end context "ensure is present" do before(:each) do @options[:ensure] = "present" end it "should create a file with content" do catalog.add_resource described_class.send(:new, @options) catalog.apply expect(Puppet::FileSystem.binread(path)).to eq(CHECKSUM_PLAINTEXT) second_catalog = Puppet::Resource::Catalog.new second_catalog.add_resource described_class.send(:new, @options) status = second_catalog.apply.report.resource_statuses["File[#{path}]"] expect(status).not_to be_failed expect(status).not_to be_changed end it "should log the content checksum" do catalog.add_resource described_class.send(:new, @options) report = catalog.apply.report expect(report.logs.first.source).to eq("/File[#{path}]/ensure") expect(report.logs.first.message).to eq("defined content as '{#{checksum_type}}#{checksum}'") second_catalog = Puppet::Resource::Catalog.new second_catalog.add_resource described_class.send(:new, @options) logs = second_catalog.apply.report.logs expect(logs).to be_empty end end context "ensure is omitted" do it "should create a file with content" do catalog.add_resource described_class.send(:new, @options) catalog.apply expect(Puppet::FileSystem.binread(path)).to eq(CHECKSUM_PLAINTEXT) second_catalog = Puppet::Resource::Catalog.new second_catalog.add_resource described_class.send(:new, @options) status = second_catalog.apply.report.resource_statuses["File[#{path}]"] expect(status).not_to be_failed expect(status).not_to be_changed end it "should log the content checksum" do catalog.add_resource described_class.send(:new, @options) report = catalog.apply.report expect(report.logs.first.source).to eq("/File[#{path}]/ensure") expect(report.logs.first.message).to eq("defined content as '{#{checksum_type}}#{checksum}'") second_catalog = Puppet::Resource::Catalog.new second_catalog.add_resource described_class.send(:new, @options) logs = second_catalog.apply.report.logs expect(logs).to be_empty end end context "both content and ensure are set" do before(:each) do @options[:ensure] = "file" end it "should create files with content" do catalog.add_resource described_class.send(:new, @options) catalog.apply expect(Puppet::FileSystem.binread(path)).to eq(CHECKSUM_PLAINTEXT) second_catalog = Puppet::Resource::Catalog.new second_catalog.add_resource described_class.send(:new, @options) status = second_catalog.apply.report.resource_statuses["File[#{path}]"] expect(status).not_to be_failed expect(status).not_to be_changed end it "should log the content checksum" do catalog.add_resource described_class.send(:new, @options) report = catalog.apply.report expect(report.logs.first.source).to eq("/File[#{path}]/ensure") expect(report.logs.first.message).to eq("defined content as '{#{checksum_type}}#{checksum}'") second_catalog = Puppet::Resource::Catalog.new second_catalog.add_resource described_class.send(:new, @options) logs = second_catalog.apply.report.logs expect(logs).to be_empty end end end end it "should delete files with sources but that are set for deletion" do source = tmpfile_with_contents("source_source_with_ensure", "yay") dest = tmpfile_with_contents("source_source_with_ensure", "boo") file = described_class.new( :path => dest, :ensure => :absent, :source => source, :backup => false ) catalog.add_resource file catalog.apply expect(Puppet::FileSystem.exist?(dest)).to be_falsey end describe "when sourcing" do it "should give a deprecation warning when the user sets source_permissions" do Puppet.expects(:puppet_deprecation_warning).with( 'The `source_permissions` parameter is deprecated. Explicitly set `owner`, `group`, and `mode`.', {:file => 'my/file.pp', :line => 5}) catalog.add_resource described_class.new(:path => path, :content => 'this is content', :source_permissions => :use_when_creating) catalog.apply end it "should not give a deprecation warning when the user does not set source_permissions" do Puppet.expects(:puppet_deprecation_warning).never catalog.add_resource described_class.new(:path => path, :content => 'this is content') catalog.apply end with_checksum_types "source", "default_values" do before(:each) do set_mode(0770, checksum_file) @options = { :path => path, :ensure => :file, :source => checksum_file, :checksum => checksum_type, :backup => false } end describe "on POSIX systems", :if => Puppet.features.posix? do it "should apply the source metadata values" do @options[:source_permissions] = :use catalog.add_resource described_class.send(:new, @options) catalog.apply expect(get_owner(path)).to eq(get_owner(checksum_file)) expect(get_group(path)).to eq(get_group(checksum_file)) expect(get_mode(path) & 07777).to eq(0770) second_catalog = Puppet::Resource::Catalog.new second_catalog.add_resource described_class.send(:new, @options) status = second_catalog.apply.report.resource_statuses["File[#{path}]"] expect(status).not_to be_failed expect(status).not_to be_changed end end it "should override the default metadata values" do @options[:mode] = '0440' catalog.add_resource described_class.send(:new, @options) catalog.apply expect(get_mode(path) & 07777).to eq(0440) second_catalog = Puppet::Resource::Catalog.new second_catalog.add_resource described_class.send(:new, @options) status = second_catalog.apply.report.resource_statuses["File[#{path}]"] expect(status).not_to be_failed expect(status).not_to be_changed end end let(:source) { tmpfile_with_contents("source_default_values", "yay") } describe "from http" do let(:http_source) { "http://my-server/file" } let(:httppath) { "#{path}http" } context "using mtime", :vcr => true do let(:resource) do described_class.new( :path => httppath, :ensure => :file, :source => http_source, :backup => false, :checksum => :mtime ) end it "should fetch if not on the local disk" do catalog.add_resource resource catalog.apply expect(Puppet::FileSystem.exist?(httppath)).to be_truthy expect(File.read(httppath)).to eq "Content via HTTP\n" end # The fixture has neither last-modified nor content-checksum headers. # Such upstream ressources are treated as "really fresh" and get # downloaded during every run. it "should fetch if no header specified" do File.open(httppath, "wb") { |f| f.puts "Content originally on disk\n" } # make sure the mtime is not "right now", lest we get a race FileUtils.touch httppath, :mtime => Time.parse("Sun, 22 Mar 2015 22:57:43 GMT") catalog.add_resource resource catalog.apply expect(Puppet::FileSystem.exist?(httppath)).to be_truthy expect(File.read(httppath)).to eq "Content via HTTP\n" end it "should fetch if mtime is older on disk" do File.open(httppath, "wb") { |f| f.puts "Content originally on disk\n" } # fixture has Last-Modified: Sun, 22 Mar 2015 22:25:34 GMT FileUtils.touch httppath, :mtime => Time.parse("Sun, 22 Mar 2015 22:22:34 GMT") catalog.add_resource resource catalog.apply expect(Puppet::FileSystem.exist?(httppath)).to be_truthy expect(File.read(httppath)).to eq "Content via HTTP\n" end it "should not update if mtime is newer on disk" do File.open(httppath, "wb") { |f| f.puts "Content via HTTP\n" } mtime = File.stat(httppath).mtime catalog.add_resource resource catalog.apply expect(Puppet::FileSystem.exist?(httppath)).to be_truthy expect(File.read(httppath)).to eq "Content via HTTP\n" expect(File.stat(httppath).mtime).to eq mtime end end context "using md5", :vcr => true do let(:resource) do described_class.new( :path => httppath, :ensure => :file, :source => http_source, :backup => false, ) end it "should fetch if not on the local disk" do catalog.add_resource resource catalog.apply expect(Puppet::FileSystem.exist?(httppath)).to be_truthy expect(File.read(httppath)).to eq "Content via HTTP\n" end it "should update if content differs on disk" do File.open(httppath, "wb") { |f| f.puts "Content originally on disk\n" } catalog.add_resource resource catalog.apply expect(Puppet::FileSystem.exist?(httppath)).to be_truthy expect(File.read(httppath)).to eq "Content via HTTP\n" end it "should not update if content on disk is up-to-date" do File.open(httppath, "wb") { |f| f.puts "Content via HTTP\n" } disk_mtime = Time.parse("Sun, 22 Mar 2015 22:22:34 GMT") FileUtils.touch httppath, :mtime => disk_mtime catalog.add_resource resource catalog.apply expect(Puppet::FileSystem.exist?(httppath)).to be_truthy expect(File.read(httppath)).to eq "Content via HTTP\n" expect(File.stat(httppath).mtime).to eq disk_mtime end end end describe "on Windows systems", :if => Puppet.features.microsoft_windows? do def expects_sid_granted_full_access_explicitly(path, sid) inherited_ace = Puppet::Util::Windows::AccessControlEntry::INHERITED_ACE aces = get_aces_for_path_by_sid(path, sid) expect(aces).not_to be_empty aces.each do |ace| expect(ace.mask).to eq(Puppet::Util::Windows::File::FILE_ALL_ACCESS) expect(ace.flags & inherited_ace).not_to eq(inherited_ace) end end def expects_system_granted_full_access_explicitly(path) expects_sid_granted_full_access_explicitly(path, @sids[:system]) end def expects_at_least_one_inherited_ace_grants_full_access(path, sid) inherited_ace = Puppet::Util::Windows::AccessControlEntry::INHERITED_ACE aces = get_aces_for_path_by_sid(path, sid) expect(aces).not_to be_empty expect(aces.any? do |ace| ace.mask == Puppet::Util::Windows::File::FILE_ALL_ACCESS && (ace.flags & inherited_ace) == inherited_ace end).to be_truthy end def expects_at_least_one_inherited_system_ace_grants_full_access(path) expects_at_least_one_inherited_ace_grants_full_access(path, @sids[:system]) end describe "when processing SYSTEM ACEs" do before do @sids = { :current_user => Puppet::Util::Windows::ADSI::User.current_user_sid.sid, :system => Puppet::Util::Windows::SID::LocalSystem, :users => Puppet::Util::Windows::SID::BuiltinUsers, :power_users => Puppet::Util::Windows::SID::PowerUsers, :none => Puppet::Util::Windows::SID::Nobody } end describe "on files" do before :each do @file = described_class.new( :path => path, :ensure => :file, :source => source, :backup => false ) catalog.add_resource @file end describe "when permissions are not insync?" do before :each do @file[:owner] = @sids[:none] @file[:group] = @sids[:none] end it "preserves the inherited SYSTEM ACE for an existing file" do FileUtils.touch(path) expects_at_least_one_inherited_system_ace_grants_full_access(path) catalog.apply expects_at_least_one_inherited_system_ace_grants_full_access(path) end it "applies the inherited SYSTEM ACEs for a new file" do catalog.apply expects_at_least_one_inherited_system_ace_grants_full_access(path) end end describe "created with SYSTEM as the group" do before :each do @file[:owner] = @sids[:users] @file[:group] = @sids[:system] @file[:mode] = '0644' catalog.apply end it "should not allow the user to explicitly set the mode to 4 ,and correct to 7" do system_aces = get_aces_for_path_by_sid(path, @sids[:system]) expect(system_aces).not_to be_empty system_aces.each do |ace| expect(ace.mask).to eq(Puppet::Util::Windows::File::FILE_ALL_ACCESS) end end it "prepends SYSTEM ace when changing group from system to power users" do @file[:group] = @sids[:power_users] catalog.apply system_aces = get_aces_for_path_by_sid(path, @sids[:system]) expect(system_aces.size).to eq(1) end end describe "with :links set to :follow" do it "should not fail to apply" do # at minimal, we need an owner and/or group @file[:owner] = @sids[:users] @file[:links] = :follow catalog.apply do |transaction| if transaction.any_failed? pretty_transaction_error(transaction) end end end end end describe "on directories" do before :each do @directory = described_class.new( :path => dir, :ensure => :directory ) catalog.add_resource @directory end def grant_everyone_full_access(path) sd = Puppet::Util::Windows::Security.get_security_descriptor(path) sd.dacl.allow( 'S-1-1-0', #everyone Puppet::Util::Windows::File::FILE_ALL_ACCESS, Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE | Puppet::Util::Windows::AccessControlEntry::CONTAINER_INHERIT_ACE) Puppet::Util::Windows::Security.set_security_descriptor(path, sd) end after :each do grant_everyone_full_access(dir) end describe "when permissions are not insync?" do before :each do @directory[:owner] = @sids[:none] @directory[:group] = @sids[:none] end it "preserves the inherited SYSTEM ACEs for an existing directory" do FileUtils.mkdir(dir) expects_at_least_one_inherited_system_ace_grants_full_access(dir) catalog.apply expects_at_least_one_inherited_system_ace_grants_full_access(dir) end it "applies the inherited SYSTEM ACEs for a new directory" do catalog.apply expects_at_least_one_inherited_system_ace_grants_full_access(dir) end describe "created with SYSTEM as the group" do before :each do @directory[:owner] = @sids[:users] @directory[:group] = @sids[:system] @directory[:mode] = '0644' catalog.apply end it "should not allow the user to explicitly set the mode to 4, and correct to 7" do system_aces = get_aces_for_path_by_sid(dir, @sids[:system]) expect(system_aces).not_to be_empty system_aces.each do |ace| # unlike files, Puppet sets execute bit on directories that are readable expect(ace.mask).to eq(Puppet::Util::Windows::File::FILE_ALL_ACCESS) end end it "prepends SYSTEM ace when changing group from system to power users" do @directory[:group] = @sids[:power_users] catalog.apply system_aces = get_aces_for_path_by_sid(dir, @sids[:system]) expect(system_aces.size).to eq(1) end end describe "with :links set to :follow" do it "should not fail to apply" do # at minimal, we need an owner and/or group @directory[:owner] = @sids[:users] @directory[:links] = :follow catalog.apply do |transaction| if transaction.any_failed? pretty_transaction_error(transaction) end end end end end end end end end describe "when purging files" do before do sourcedir = tmpdir("purge_source") destdir = tmpdir("purge_dest") sourcefile = File.join(sourcedir, "sourcefile") @copiedfile = File.join(destdir, "sourcefile") @localfile = File.join(destdir, "localfile") @purgee = File.join(destdir, "to_be_purged") File.open(@localfile, "w") { |f| f.print "oldtest" } File.open(sourcefile, "w") { |f| f.print "funtest" } # this file should get removed File.open(@purgee, "w") { |f| f.print "footest" } lfobj = Puppet::Type.newfile( :title => "localfile", :path => @localfile, :content => "rahtest", :ensure => :file, :backup => false ) destobj = Puppet::Type.newfile( :title => "destdir", :path => destdir, :source => sourcedir, :backup => false, :purge => true, :recurse => true ) catalog.add_resource lfobj, destobj catalog.apply end it "should still copy remote files" do expect(File.read(@copiedfile)).to eq('funtest') end it "should not purge managed, local files" do expect(File.read(@localfile)).to eq('rahtest') end it "should purge files that are neither remote nor otherwise managed" do expect(Puppet::FileSystem.exist?(@purgee)).to be_falsey end end describe "when using validate_cmd" do it "should fail the file resource if command fails" do catalog.add_resource(described_class.new(:path => path, :content => "foo", :validate_cmd => "/usr/bin/env false")) Puppet::Util::Execution.expects(:execute).with("/usr/bin/env false", {:combine => true, :failonfail => true}).raises(Puppet::ExecutionFailure, "Failed") report = catalog.apply.report expect(report.resource_statuses["File[#{path}]"]).to be_failed expect(Puppet::FileSystem.exist?(path)).to be_falsey end it "should succeed the file resource if command succeeds" do catalog.add_resource(described_class.new(:path => path, :content => "foo", :validate_cmd => "/usr/bin/env true")) Puppet::Util::Execution.expects(:execute) .with("/usr/bin/env true", {:combine => true, :failonfail => true}) .returns Puppet::Util::Execution::ProcessOutput.new('', 0) report = catalog.apply.report expect(report.resource_statuses["File[#{path}]"]).not_to be_failed expect(Puppet::FileSystem.exist?(path)).to be_truthy end end def tmpfile_with_contents(name, contents) file = tmpfile(name) File.open(file, "w") { |f| f.write contents } file end def file_in_dir_with_contents(dir, name, contents) full_name = File.join(dir, name) File.open(full_name, "w") { |f| f.write contents } full_name end def pretty_transaction_error(transaction) report = transaction.report status_failures = report.resource_statuses.values.select { |r| r.failed? } status_fail_msg = status_failures. collect(&:events). flatten. select { |event| event.status == 'failure' }. collect { |event| "#{event.resource}: #{event.message}" }.join("; ") raise "Got #{status_failures.length} failure(s) while applying: #{status_fail_msg}" end describe "copying a file that is a link to a file", :if => Puppet.features.manages_symlinks? do let(:target) { tmpfile('target') } let(:link) { tmpfile('link') } let(:copy) { tmpfile('copy') } it "should copy the target of the link if :links => follow" do catalog.add_resource described_class.new( :name => target, :ensure => "present", :content => "Jenny I got your number / I need to make you mine") catalog.add_resource described_class.new( :name => link, :ensure => "link", :target => target) catalog.add_resource described_class.new( :name => copy, :ensure => "present", :source => link, :links => "follow") catalog.apply expect(Puppet::FileSystem).to be_file(copy) expect(File.read(target)).to eq(File.read(copy)) end it "should copy the link itself if :links => manage" do catalog.add_resource described_class.new( :name => target, :ensure => "present", :content => "Jenny I got your number / I need to make you mine") catalog.add_resource described_class.new( :name => link, :ensure => "link", :target => target) catalog.add_resource described_class.new( :name => copy, :ensure => "present", :source => link, :links => "manage") catalog.apply expect(Puppet::FileSystem).to be_symlink(copy) expect(File.read(link)).to eq(File.read(copy)) end end describe "copying a file that is a link to a directory", :if => Puppet.features.manages_symlinks? do let(:target) { tmpdir('target') } let(:link) { tmpfile('link') } let(:copy) { tmpfile('copy') } context "when the recurse attribute is false" do it "should copy the top-level directory if :links => follow" do catalog.add_resource described_class.new( :name => target, :ensure => "directory") catalog.add_resource described_class.new( :name => link, :ensure => "link", :target => target) catalog.add_resource described_class.new( :name => copy, :ensure => "present", :source => link, :recurse => false, :links => "follow") catalog.apply expect(Puppet::FileSystem).to be_directory(copy) end it "should copy the link itself if :links => manage" do catalog.add_resource described_class.new( :name => target, :ensure => "directory") catalog.add_resource described_class.new( :name => link, :ensure => "link", :target => target) catalog.add_resource described_class.new( :name => copy, :ensure => "present", :source => link, :recurse => false, :links => "manage") catalog.apply expect(Puppet::FileSystem).to be_symlink(copy) expect(Dir.entries(link)).to eq(Dir.entries(copy)) end end context "and the recurse attribute is true" do it "should recursively copy the directory if :links => follow" do catalog.add_resource described_class.new( :name => target, :ensure => "directory") catalog.add_resource described_class.new( :name => link, :ensure => "link", :target => target) catalog.add_resource described_class.new( :name => copy, :ensure => "present", :source => link, :recurse => true, :links => "follow") catalog.apply expect(Puppet::FileSystem).to be_directory(copy) expect(Dir.entries(target)).to eq(Dir.entries(copy)) end it "should copy the link itself if :links => manage" do catalog.add_resource described_class.new( :name => target, :ensure => "directory") catalog.add_resource described_class.new( :name => link, :ensure => "link", :target => target) catalog.add_resource described_class.new( :name => copy, :ensure => "present", :source => link, :recurse => true, :links => "manage") catalog.apply expect(Puppet::FileSystem).to be_symlink(copy) expect(Dir.entries(link)).to eq(Dir.entries(copy)) end end end [:md5, :sha256, :md5lite, :sha256lite, :sha384, :sha512, :sha224].each do |checksum| describe "setting checksum_value explicitly with checksum #{checksum}" do let(:path) { tmpfile('target') } let(:contents) { 'yay' } before :each do @options = { :path => path, :ensure => :file, :checksum => checksum, :checksum_value => Puppet::Util::Checksums.send(checksum, contents) } end def verify_file(transaction) status = transaction.report.resource_statuses["File[#{path}]"] expect(status).not_to be_failed expect(Puppet::FileSystem).to be_file(path) expect(File.read(path)).to eq(contents) status end [:source, :content].each do |prop| context "from #{prop}" do let(:source) { tmpfile_with_contents("source_default_values", contents) } before :each do @options[prop] = {:source => source, :content => contents}[prop] end it "should create a new file" do catalog.add_resource described_class.new(@options) status = verify_file catalog.apply expect(status).to be_changed end it "should overwrite an existing file" do File.open(path, "w") { |f| f.write('bar') } catalog.add_resource described_class.new(@options) status = verify_file catalog.apply expect(status).to be_changed end it "should not overwrite the same file" do File.open(path, "w") { |f| f.write(contents) } catalog.add_resource described_class.new(@options) status = verify_file catalog.apply expect(status).to_not be_changed end it "should not create a file when ensuring absent" do @options[:ensure] = :absent catalog.add_resource described_class.new(@options) catalog.apply expect(Puppet::FileSystem).to_not be_file(path) end end end end end describe "setting checksum_value explicitly with checksum mtime" do let(:path) { tmpfile('target_dir') } let(:time) { Time.now } before :each do @options = { :path => path, :ensure => :directory, :checksum => :mtime, :checksum_value => time } end it "should create a new directory" do catalog.add_resource described_class.new(@options) status = catalog.apply.report.resource_statuses["File[#{path}]"] expect(status).not_to be_failed expect(status).to be_changed expect(Puppet::FileSystem).to be_directory(path) end it "should not update mtime on an old directory" do disk_mtime = Time.parse("Sun, 22 Mar 2015 22:22:34 GMT") FileUtils.mkdir_p path FileUtils.touch path, :mtime => disk_mtime status = catalog.apply.report.resource_statuses["File[#{path}]"] expect(status).to be_nil expect(Puppet::FileSystem).to be_directory(path) expect(File.stat(path).mtime).to eq(disk_mtime) end end end puppet-5.5.10/spec/integration/type/nagios_spec.rb0000644005276200011600000000376113417161722022077 0ustar jenkinsjenkins#!/usr/bin/env ruby require 'spec_helper' require 'puppet/file_bucket/dipper' describe "Nagios file creation" do include PuppetSpec::Files let(:initial_mode) { 0600 } before :each do FileUtils.touch(target_file) Puppet::FileSystem.chmod(initial_mode, target_file) Puppet::FileBucket::Dipper.any_instance.stubs(:backup) # Don't backup to filebucket end let :target_file do tmpfile('nagios_integration_specs') end # Copied from the crontab integration spec. # # @todo This should probably live in the PuppetSpec module instead then. def run_in_catalog(*resources) catalog = Puppet::Resource::Catalog.new catalog.host_config = false resources.each do |resource| resource.expects(:err).never catalog.add_resource(resource) end # the resources are not properly contained and generated resources # will end up with dangling edges without this stubbing: catalog.stubs(:container_of).returns resources[0] catalog.apply end context "when creating a nagios config file" do context "which is not managed" do it "should choose the file mode if requested" do resource = Puppet::Type.type(:nagios_host).new( :name => 'spechost', :use => 'spectemplate', :ensure => 'present', :target => target_file, :mode => '0640' ) run_in_catalog(resource) expect_file_mode(target_file, "640") end end context "which is managed" do it "should not override the mode" do file_res = Puppet::Type.type(:file).new( :name => target_file, :ensure => :present ) nag_res = Puppet::Type.type(:nagios_host).new( :name => 'spechost', :use => 'spectemplate', :ensure => :present, :target => target_file, :mode => '0640' ) run_in_catalog(file_res, nag_res) expect_file_mode(target_file, initial_mode.to_s(8)) end end end end puppet-5.5.10/spec/integration/type/package_spec.rb0000644005276200011600000001413513417161722022207 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:package), "when choosing a default package provider" do before do # the default provider is cached. Puppet::Type.type(:package).defaultprovider = nil end def provider_name(os) case os when 'Solaris' if Puppet::Util::Package.versioncmp(Facter.value(:kernelrelease), '5.11') >= 0 :pkg else :sun end when 'Ubuntu' :apt when 'Debian' :apt when 'Darwin' :pkgdmg when 'RedHat' if ['2.1', '3', '4'].include?(Facter.value(:lsbdistrelease)) :up2date else :yum end when 'Fedora' if Puppet::Util::Package.versioncmp(Facter.value(:operatingsystemmajrelease), '22') >= 0 :dnf else :yum end when 'Suse' if Puppet::Util::Package.versioncmp(Facter.value(:operatingsystemmajrelease), '10') >= 0 :zypper else :rug end when 'FreeBSD' :ports when 'OpenBSD' :openbsd when 'DragonFly' :pkgng when 'OpenWrt' :opkg end end it "should have a default provider" do expect(Puppet::Type.type(:package).defaultprovider).not_to be_nil end it "should choose the correct provider each platform" do unless default_provider = provider_name(Facter.value(:operatingsystem)) pending("No default provider specified in this test for #{Facter.value(:operatingsystem)}") end expect(Puppet::Type.type(:package).defaultprovider.name).to eq(default_provider) end end describe Puppet::Type.type(:package), "when packages with the same name are sourced" do before :each do Process.stubs(:euid).returns 0 @provider = stub( 'provider', :class => Puppet::Type.type(:package).defaultprovider, :clear => nil, :satisfies? => true, :name => :mock, :validate_source => nil ) Puppet::Type.type(:package).defaultprovider.stubs(:new).returns(@provider) Puppet::Type.type(:package).defaultprovider.stubs(:instances).returns([]) @package = Puppet::Type.type(:package).new(:name => "yay", :ensure => :present) @catalog = Puppet::Resource::Catalog.new @catalog.add_resource(@package) end describe "with same title" do before { @alt_package = Puppet::Type.type(:package).new(:name => "yay", :ensure => :present) } it "should give an error" do expect { @catalog.add_resource(@alt_package) }.to raise_error Puppet::Resource::Catalog::DuplicateResourceError, 'Duplicate declaration: Package[yay] is already declared; cannot redeclare' end end describe "with different title" do before :each do @alt_package = Puppet::Type.type(:package).new(:name => "yay", :title => "gem-yay", :ensure => :present) end it "should give an error" do provider_name = Puppet::Type.type(:package).defaultprovider.name expect { @catalog.add_resource(@alt_package) }.to raise_error ArgumentError, "Cannot alias Package[gem-yay] to [\"yay\", :#{provider_name}]; resource [\"Package\", \"yay\", :#{provider_name}] already declared" end end describe "from multiple providers" do provider_class = Puppet::Type.type(:package).provider(:gem) before :each do @alt_provider = provider_class.new @alt_package = Puppet::Type.type(:package).new(:name => "yay", :title => "gem-yay", :provider => @alt_provider, :ensure => :present) @catalog.add_resource(@alt_package) end describe "when it should be present" do [:present, :latest, "1.0"].each do |state| it "should do nothing if it is #{state.to_s}" do @provider.expects(:properties).returns(:ensure => state).at_least_once @alt_provider.expects(:properties).returns(:ensure => state).at_least_once @catalog.apply end end [:purged, :absent].each do |state| it "should install if it is #{state.to_s}" do @provider.stubs(:properties).returns(:ensure => state) @provider.expects(:install) @alt_provider.stubs(:properties).returns(:ensure => state) @alt_provider.expects(:install) @catalog.apply end end end end end describe Puppet::Type.type(:package), 'logging package state transitions' do let(:catalog) { Puppet::Resource::Catalog.new } let(:provider) { stub('provider', :class => Puppet::Type.type(:package).defaultprovider, :clear => nil, :validate_source => nil) } before :each do Process.stubs(:euid).returns 0 provider.stubs(:satisfies?).with([:purgeable]).returns(true) provider.class.stubs(:instances).returns([]) provider.stubs(:install).returns nil provider.stubs(:uninstall).returns nil provider.stubs(:purge).returns nil Puppet::Type.type(:package).defaultprovider.stubs(:new).returns(provider) end after :each do Puppet::Type.type(:package).defaultprovider = nil end # Map of old state -> {new state -> change} states = { # 'installed' transitions to 'removed' or 'purged' :installed => { :installed => nil, :absent => 'removed', :purged => 'purged' }, # 'absent' transitions to 'created' or 'purged' :absent => { :installed => 'created', :absent => nil, :purged => 'purged' }, # 'purged' transitions to 'created' :purged => { :installed => 'created', :absent => nil, :purged => nil } } states.each do |old, new_states| describe "#{old} package" do before :each do provider.stubs(:properties).returns(:ensure => old) end new_states.each do |new, status| it "ensure => #{new} should log #{status ? status : 'nothing'}" do catalog.add_resource(described_class.new(:name => 'yay', :ensure => new)) catalog.apply logs = catalog.apply.report.logs if status expect(logs.first.source).to eq("/Package[yay]/ensure") expect(logs.first.message).to eq(status) else expect(logs.first).to be_nil end end end end end end puppet-5.5.10/spec/integration/type/user_spec.rb0000644005276200011600000000451213417161722021570 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/files' require 'puppet_spec/compiler' describe Puppet::Type.type(:user), '(integration)', :unless => Puppet.features.microsoft_windows? do include PuppetSpec::Files include PuppetSpec::Compiler context "when set to purge ssh keys from a file" do # different UTF-8 widths # 1-byte A # 2-byte Űż - http://www.fileformat.info/info/unicode/char/06ff/index.htm - 0xDB 0xBF / 219 191 # 3-byte áš  - http://www.fileformat.info/info/unicode/char/16A0/index.htm - 0xE1 0x9A 0xA0 / 225 154 160 # 4-byte 𠜎 - http://www.fileformat.info/info/unicode/char/2070E/index.htm - 0xF0 0xA0 0x9C 0x8E / 240 160 156 142 let (:mixed_utf8) { "A\u06FF\u16A0\u{2070E}" } # Aۿᚠ𠜎 let(:tempfile) do file_containing('user_spec', <<-EOF) # comment #{mixed_utf8} ssh-rsa KEY-DATA key-name ssh-rsa KEY-DATA key name EOF end # must use an existing user, or the generated key resource # will fail on account of an invalid user for the key # - root should be a safe default let(:manifest) { "user { 'root': purge_ssh_keys => '#{tempfile}' }" } it "should purge authorized ssh keys" do apply_compiled_manifest(manifest) expect(File.read(tempfile, :encoding => Encoding::UTF_8)).not_to match(/key-name/) end it "should purge keys with spaces in the comment string" do apply_compiled_manifest(manifest) expect(File.read(tempfile, :encoding => Encoding::UTF_8)).not_to match(/key name/) end context "with other prefetching resources evaluated first" do let(:manifest) { "host { 'test': before => User[root] } user { 'root': purge_ssh_keys => '#{tempfile}' }" } it "should purge authorized ssh keys" do apply_compiled_manifest(manifest) expect(File.read(tempfile, :encoding => Encoding::UTF_8)).not_to match(/key-name/) end end context "with multiple unnamed keys" do let(:tempfile) do file_containing('user_spec', <<-EOF) # comment #{mixed_utf8} ssh-rsa KEY-DATA1 ssh-rsa KEY-DATA2 EOF end it "should purge authorized ssh keys" do apply_compiled_manifest(manifest) expect(File.read(tempfile, :encoding => Encoding::UTF_8)).not_to match(/KEY-DATA/) end end end end puppet-5.5.10/spec/integration/type_spec.rb0000644005276200011600000000134413417161721020611 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/type' describe Puppet::Type do it "should not lose its provider list when it is reloaded" do type = Puppet::Type.newtype(:integration_test) do newparam(:name) {} end provider = type.provide(:myprovider) {} # reload it type = Puppet::Type.newtype(:integration_test) do newparam(:name) {} end expect(type.provider(:myprovider)).to equal(provider) end it "should not lose its provider parameter when it is reloaded" do type = Puppet::Type.newtype(:reload_test_type) type.provide(:test_provider) # reload it type = Puppet::Type.newtype(:reload_test_type) expect(type.parameters).to include(:provider) end end puppet-5.5.10/spec/integration/util/0000755005276200011600000000000013417162176017251 5ustar jenkinsjenkinspuppet-5.5.10/spec/integration/util/rdoc/0000755005276200011600000000000013417162176020200 5ustar jenkinsjenkinspuppet-5.5.10/spec/integration/util/rdoc/parser_spec.rb0000644005276200011600000001143113417161722023027 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/rdoc' describe "RDoc::Parser", :unless => Puppet.features.microsoft_windows? do require 'puppet_spec/files' include PuppetSpec::Files let(:document_all) { false } let(:tmp_dir) { tmpdir('rdoc_parser_tmp') } let(:doc_dir) { File.join(tmp_dir, 'doc') } let(:manifests_dir) { File.join(tmp_dir, 'manifests') } let(:modules_dir) { File.join(tmp_dir, 'modules') } let(:modules_and_manifests) do { :site => [ File.join(manifests_dir, 'site.pp'), <<-EOF # The test class comment class test { # The virtual resource comment @notify { virtual: } # The a_notify_resource comment notify { a_notify_resource: message => "a_notify_resource message" } } # The includes_another class comment class includes_another { include another } # The requires_another class comment class requires_another { require another } # node comment node foo { include test $a_var = "var_value" realize Notify[virtual] notify { bar: } } EOF ], :module_readme => [ File.join(modules_dir, 'a_module', 'README'), <<-EOF The a_module README docs. EOF ], :module_init => [ File.join(modules_dir, 'a_module', 'manifests', 'init.pp'), <<-EOF # The a_module class comment class a_module {} class another {} EOF ], :module_type => [ File.join(modules_dir, 'a_module', 'manifests', 'a_type.pp'), <<-EOF # The a_type type comment define a_module::a_type() {} EOF ], :module_plugin => [ File.join(modules_dir, 'a_module', 'lib', 'puppet', 'type', 'a_plugin.rb'), <<-EOF # The a_plugin type comment Puppet::Type.newtype(:a_plugin) do @doc = "Not presented" end EOF ], :module_function => [ File.join(modules_dir, 'a_module', 'lib', 'puppet', 'parser', 'a_function.rb'), <<-EOF # The a_function function comment module Puppet::Parser::Functions newfunction(:a_function, :type => :rvalue) do return end end EOF ], :module_fact => [ File.join(modules_dir, 'a_module', 'lib', 'facter', 'a_fact.rb'), <<-EOF # The a_fact fact comment Facter.add("a_fact") do end EOF ], } end def write_file(file, content) FileUtils.mkdir_p(File.dirname(file)) File.open(file, 'w') do |f| f.puts(content) end end def prepare_manifests_and_modules modules_and_manifests.each do |key,array| write_file(*array) end end def file_exists_and_matches_content(file, *content_patterns) expect(Puppet::FileSystem.exist?(file)).to(be_truthy, "Cannot find #{file}") content_patterns.each do |pattern| content = File.read(file) expect(content).to match(pattern) end end def some_file_exists_with_matching_content(glob, *content_patterns) expect(Dir.glob(glob).select do |f| contents = File.read(f) content_patterns.all? { |p| p.match(contents) } end).not_to(be_empty, "Could not match #{content_patterns} in any of the files found in #{glob}") end around(:each) do |example| env = Puppet::Node::Environment.create(:doc_test_env, [modules_dir], manifests_dir) Puppet.override({:environments => Puppet::Environments::Static.new(env), :current_environment => env}) do example.run end end before :each do prepare_manifests_and_modules Puppet.settings[:document_all] = document_all Puppet.settings[:modulepath] = modules_dir Puppet::Util::RDoc.rdoc(doc_dir, [modules_dir, manifests_dir]) end module RdocTesters def has_plugin_rdoc(module_name, type, name) file_exists_and_matches_content(plugin_path(module_name, type, name), /The .*?#{name}.*?\s*#{type} comment/m, /Type.*?#{type}/m) end end shared_examples_for :an_rdoc_site do # PUP-3274 / PUP-3638 not sure if this should be kept or not - it is now broken # it "documents the __site__ module" do # has_module_rdoc("__site__") # end # PUP-3274 / PUP-3638 not sure if this should be kept or not - it is now broken # it "documents the a_module module" do # has_module_rdoc("a_module", /The .*?a_module.*? .*?README.*?docs/m) # end it "documents the a_module::a_plugin type" do has_plugin_rdoc("a_module", :type, 'a_plugin') end it "documents the a_module::a_function function" do has_plugin_rdoc("a_module", :function, 'a_function') end it "documents the a_module::a_fact fact" do has_plugin_rdoc("a_module", :fact, 'a_fact') end end describe "rdoc2 support" do def module_path(module_name); "#{doc_dir}/#{module_name}.html" end def plugin_path(module_name, type, name); "#{doc_dir}/#{module_name}/__#{type}s__.html" end include RdocTesters it_behaves_like :an_rdoc_site end end puppet-5.5.10/spec/integration/util/windows/0000755005276200011600000000000013417162176020743 5ustar jenkinsjenkinspuppet-5.5.10/spec/integration/util/windows/adsi_spec.rb0000644005276200011600000001732213417161722023223 0ustar jenkinsjenkins#!/usr/bin/env ruby require 'spec_helper' require 'puppet/util/windows' describe Puppet::Util::Windows::ADSI::User, :if => Puppet.features.microsoft_windows? do describe ".initialize" do it "cannot reference BUILTIN accounts like SYSTEM due to WinNT moniker limitations" do system = Puppet::Util::Windows::ADSI::User.new('SYSTEM') # trying to retrieve COM object should fail to load with a localized version of: # ADSI connection error: failed to parse display name of moniker `WinNT://./SYSTEM,user' # HRESULT error code:0x800708ad # The user name could not be found. # Matching on error code alone is sufficient expect { system.native_object }.to raise_error(/0x800708ad/) end end describe '.each' do it 'should return a list of users with UTF-8 names' do begin original_codepage = Encoding.default_external Encoding.default_external = Encoding::CP850 # Western Europe Puppet::Util::Windows::ADSI::User.each do |user| expect(user.name.encoding).to be(Encoding::UTF_8) end ensure Encoding.default_external = original_codepage end end end describe '.[]' do it 'should return string attributes as UTF-8' do administrator = Puppet::Util::Windows::ADSI::User.new('Administrator') expect(administrator['Description'].encoding).to eq(Encoding::UTF_8) end end describe '.groups' do it 'should return a list of groups with UTF-8 names' do begin original_codepage = Encoding.default_external Encoding.default_external = Encoding::CP850 # Western Europe # lookup by English name Administrator is OK on localized Windows administrator = Puppet::Util::Windows::ADSI::User.new('Administrator') administrator.groups.each do |name| expect(name.encoding).to be(Encoding::UTF_8) end ensure Encoding.default_external = original_codepage end end end end describe Puppet::Util::Windows::ADSI::Group, :if => Puppet.features.microsoft_windows? do let (:administrator_bytes) { [1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0] } let (:administrators_principal) { Puppet::Util::Windows::SID::Principal.lookup_account_sid(administrator_bytes) } describe '.each' do it 'should return a list of groups with UTF-8 names' do begin original_codepage = Encoding.default_external Encoding.default_external = Encoding::CP850 # Western Europe Puppet::Util::Windows::ADSI::Group.each do |group| expect(group.name.encoding).to be(Encoding::UTF_8) end ensure Encoding.default_external = original_codepage end end end describe '.members' do it 'should return a list of members resolvable with Puppet::Util::Windows::ADSI::Group.name_sid_hash' do temp_groupname = "g#{SecureRandom.uuid}" temp_username = "u#{SecureRandom.uuid}"[0..12] # select a virtual account that requires an authority to be able to resolve to SID # the Dhcp service is chosen for no particular reason aside from it's a service available on all Windows versions dhcp_virtualaccount = Puppet::Util::Windows::SID.name_to_principal('NT SERVICE\Dhcp') # adding :SidTypeGroup as a group member will cause error in IAdsUser::Add # adding :SidTypeDomain (such as S-1-5-80 / NT SERVICE or computer name) won't error # but also won't be returned as a group member # uncertain how to obtain :SidTypeComputer (perhaps AD? the local machine is :SidTypeDomain) users = [ # Use sid_to_name to get localized names of SIDs - BUILTIN, SYSTEM, NT AUTHORITY, Everyone are all localized # :SidTypeWellKnownGroup # SYSTEM is prefixed with the NT Authority authority, resolveable with or without authority { :sid => 'S-1-5-18', :name => Puppet::Util::Windows::SID.sid_to_name('S-1-5-18') }, # Everyone is not prefixed with an authority, resolveable with or without NT AUTHORITY authority { :sid => 'S-1-1-0', :name => Puppet::Util::Windows::SID.sid_to_name('S-1-1-0') }, # Dhcp service account is prefixed with NT SERVICE authority, requires authority to resolve SID # behavior is similar to IIS APPPOOL\DefaultAppPool { :sid => dhcp_virtualaccount.sid, :name => dhcp_virtualaccount.domain_account }, # :SidTypeAlias with authority component # Administrators group is prefixed with BUILTIN authority, can be resolved with or without authority { :sid => 'S-1-5-32-544', :name => Puppet::Util::Windows::SID.sid_to_name('S-1-5-32-544') }, ] begin # :SidTypeUser as user on localhost, can be resolved with or without authority prefix user = Puppet::Util::Windows::ADSI::User.create(temp_username) user.commit() users.push({ :sid => user.sid.sid, :name => Puppet::Util::Windows::ADSI.computer_name + '\\' + temp_username }) # create a test group and add above 5 members by SID group = described_class.create(temp_groupname) group.commit() group.set_members(users.map { |u| u[:sid]} ) # most importantly make sure that all name are convertible to SIDs expect { described_class.name_sid_hash(group.members) }.to_not raise_error # also verify the names returned are as expected expected_usernames = users.map { |u| u[:name] } expect(group.members.map(&:domain_account)).to eq(expected_usernames) ensure described_class.delete(temp_groupname) if described_class.exists?(temp_groupname) Puppet::Util::Windows::ADSI::User.delete(temp_username) if Puppet::Util::Windows::ADSI::User.exists?(temp_username) end end it 'should return a list of Principal objects even with unresolvable SIDs' do members = [ # NULL SID is not localized stub('WIN32OLE', { :objectSID => [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], :Name => 'NULL SID', :ole_respond_to? => true, }), # unresolvable SID is a different story altogether stub('WIN32OLE', { # completely valid SID, but Name is just a stringified version :objectSID => [1, 5, 0, 0, 0, 0, 0, 5, 21, 0, 0, 0, 5, 113, 65, 218, 15, 127, 9, 57, 219, 4, 84, 126, 88, 4, 0, 0], :Name => 'S-1-5-21-3661721861-956923663-2119435483-1112', :ole_respond_to? => true, }) ] admins_name = Puppet::Util::Windows::SID.sid_to_name('S-1-5-32-544') admins = Puppet::Util::Windows::ADSI::Group.new(admins_name) # touch the native_object member to have it lazily loaded, so COM objects can be stubbed admins.native_object admins.native_object.stubs(:Members).returns(members) # well-known NULL SID expect(admins.members[0].sid).to eq('S-1-0-0') expect(admins.members[0].account_type).to eq(:SidTypeWellKnownGroup) # unresolvable SID expect(admins.members[1].sid).to eq('S-1-5-21-3661721861-956923663-2119435483-1112') expect(admins.members[1].account).to eq('S-1-5-21-3661721861-956923663-2119435483-1112 (unresolvable)') expect(admins.members[1].account_type).to eq(:SidTypeUnknown) end it 'should return a list of members with UTF-8 names' do begin original_codepage = Encoding.default_external Encoding.default_external = Encoding::CP850 # Western Europe # lookup by English name Administrators is not OK on localized Windows admins = Puppet::Util::Windows::ADSI::Group.new(administrators_principal.account) admins.members.map(&:domain_account).each do |name| expect(name.encoding).to be(Encoding::UTF_8) end ensure Encoding.default_external = original_codepage end end end end puppet-5.5.10/spec/integration/util/windows/principal_spec.rb0000644005276200011600000003011313417161722024255 0ustar jenkinsjenkins#!/usr/bin/env ruby require 'spec_helper' require 'puppet/util/windows' describe Puppet::Util::Windows::SID::Principal, :if => Puppet.features.microsoft_windows? do let (:current_user_sid) { Puppet::Util::Windows::ADSI::User.current_user_sid } let (:system_bytes) { [1, 1, 0, 0, 0, 0, 0, 5, 18, 0, 0, 0] } let (:null_sid_bytes) { [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] } let (:administrator_bytes) { [1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0] } let (:computer_sid) { Puppet::Util::Windows::SID.name_to_principal(Puppet::Util::Windows::ADSI.computer_name) } # BUILTIN is localized on German Windows, but not French # looking this up like this dilutes the values of the tests as we're comparing two mechanisms # for returning the same values, rather than to a known good let (:builtin_localized) { Puppet::Util::Windows::SID.sid_to_name('S-1-5-32') } describe ".lookup_account_name" do it "should create an instance from a well-known account name" do principal = Puppet::Util::Windows::SID::Principal.lookup_account_name('NULL SID') expect(principal.account).to eq('NULL SID') expect(principal.sid_bytes).to eq(null_sid_bytes) expect(principal.sid).to eq('S-1-0-0') expect(principal.domain).to eq('') expect(principal.domain_account).to eq('NULL SID') expect(principal.account_type).to eq(:SidTypeWellKnownGroup) expect(principal.to_s).to eq('NULL SID') end it "should create an instance from a well-known account prefixed with NT AUTHORITY" do # a special case that can be used to lookup an account on a localized Windows principal = Puppet::Util::Windows::SID::Principal.lookup_account_name('NT AUTHORITY\\SYSTEM') expect(principal.sid_bytes).to eq(system_bytes) expect(principal.sid).to eq('S-1-5-18') # guard these 3 checks on a US Windows with 1033 - primary language id of 9 primary_language_id = 9 # even though lookup in English, returned values may be localized # French Windows returns AUTORITE NT\\Syst\u00E8me, German Windows returns NT-AUTORIT\u00C4T\\SYSTEM if (Puppet::Util::Windows::Process.get_system_default_ui_language & primary_language_id == primary_language_id) expect(principal.account).to eq('SYSTEM') expect(principal.domain).to eq('NT AUTHORITY') expect(principal.domain_account).to eq('NT AUTHORITY\\SYSTEM') expect(principal.to_s).to eq('NT AUTHORITY\\SYSTEM') end # Windows API LookupAccountSid behaves differently if current user is SYSTEM if current_user_sid.sid_bytes != system_bytes account_type = :SidTypeWellKnownGroup else account_type = :SidTypeUser end expect(principal.account_type).to eq(account_type) end it "should create an instance from a local account prefixed with hostname" do running_as_system = (current_user_sid.sid_bytes == system_bytes) username = running_as_system ? # need to return localized name of Administrator account Puppet::Util::Windows::SID.sid_to_name(computer_sid.sid + '-500').split('\\').last : current_user_sid.account user_exists = Puppet::Util::Windows::ADSI::User.exists?(".\\#{username}") # when running as SYSTEM (in Jenkins CI), then Administrator should be used # otherwise running in AppVeyor there is no Administrator and a the current local user can be used skip if (running_as_system && !user_exists) hostname = Puppet::Util::Windows::ADSI.computer_name principal = Puppet::Util::Windows::SID::Principal.lookup_account_name("#{hostname}\\#{username}") expect(principal.account).to match(/^#{Regexp.quote(username)}$/i) # skip SID and bytes in this case since the most interesting thing here is domain_account expect(principal.domain).to match(/^#{Regexp.quote(hostname)}$/i) expect(principal.domain_account).to match(/^#{Regexp.quote(hostname)}\\#{Regexp.quote(username)}$/i) expect(principal.account_type).to eq(:SidTypeUser) end it "should create an instance from a well-known BUILTIN alias" do # by looking up the localized name of the account, the test value is diluted # this localizes Administrators AND BUILTIN qualified_name = Puppet::Util::Windows::SID.sid_to_name('S-1-5-32-544') domain, name = qualified_name.split('\\') principal = Puppet::Util::Windows::SID::Principal.lookup_account_name(name) expect(principal.account).to eq(name) expect(principal.sid_bytes).to eq(administrator_bytes) expect(principal.sid).to eq('S-1-5-32-544') expect(principal.domain).to eq(domain) expect(principal.domain_account).to eq(qualified_name) expect(principal.account_type).to eq(:SidTypeAlias) expect(principal.to_s).to eq(qualified_name) end it "should raise an error when trying to lookup an account that doesn't exist" do principal = Puppet::Util::Windows::SID::Principal expect { principal.lookup_account_name('ConanTheBarbarian') }.to raise_error do |error| expect(error).to be_a(Puppet::Util::Windows::Error) expect(error.code).to eq(1332) # ERROR_NONE_MAPPED end end it "should return a BUILTIN domain principal for empty account names" do principal = Puppet::Util::Windows::SID::Principal.lookup_account_name('') expect(principal.account_type).to eq(:SidTypeDomain) expect(principal.sid).to eq('S-1-5-32') expect(principal.account).to eq(builtin_localized) expect(principal.domain).to eq(builtin_localized) expect(principal.domain_account).to eq(builtin_localized) expect(principal.to_s).to eq(builtin_localized) end it "should return a BUILTIN domain principal for BUILTIN account names" do principal = Puppet::Util::Windows::SID::Principal.lookup_account_name(builtin_localized) expect(principal.account_type).to eq(:SidTypeDomain) expect(principal.sid).to eq('S-1-5-32') expect(principal.account).to eq(builtin_localized) expect(principal.domain).to eq(builtin_localized) expect(principal.domain_account).to eq(builtin_localized) expect(principal.to_s).to eq(builtin_localized) end end describe ".lookup_account_sid" do it "should create an instance from a user SID" do # take the computer account bytes and append the equivalent of -501 for Guest bytes = (computer_sid.sid_bytes + [245, 1, 0, 0]) # computer SID bytes start with [1, 4, ...] but need to be [1, 5, ...] bytes[1] = 5 principal = Puppet::Util::Windows::SID::Principal.lookup_account_sid(bytes) # use the returned SID to lookup localized Guest account name in Windows guest_name = Puppet::Util::Windows::SID.sid_to_name(principal.sid) expect(principal.sid_bytes).to eq(bytes) expect(principal.sid).to eq(computer_sid.sid + '-501') expect(principal.account).to eq(guest_name.split('\\')[1]) expect(principal.domain).to eq(computer_sid.domain) expect(principal.domain_account).to eq(guest_name) expect(principal.account_type).to eq(:SidTypeUser) expect(principal.to_s).to eq(guest_name) end it "should create an instance from a well-known group SID" do principal = Puppet::Util::Windows::SID::Principal.lookup_account_sid(null_sid_bytes) expect(principal.sid_bytes).to eq(null_sid_bytes) expect(principal.sid).to eq('S-1-0-0') expect(principal.account).to eq('NULL SID') expect(principal.domain).to eq('') expect(principal.domain_account).to eq('NULL SID') expect(principal.account_type).to eq(:SidTypeWellKnownGroup) expect(principal.to_s).to eq('NULL SID') end it "should create an instance from a well-known BUILTIN Alias SID" do principal = Puppet::Util::Windows::SID::Principal.lookup_account_sid(administrator_bytes) # by looking up the localized name of the account, the test value is diluted # this localizes Administrators AND BUILTIN qualified_name = Puppet::Util::Windows::SID.sid_to_name('S-1-5-32-544') domain, name = qualified_name.split('\\') expect(principal.account).to eq(name) expect(principal.sid_bytes).to eq(administrator_bytes) expect(principal.sid).to eq('S-1-5-32-544') expect(principal.domain).to eq(domain) expect(principal.domain_account).to eq(qualified_name) expect(principal.account_type).to eq(:SidTypeAlias) expect(principal.to_s).to eq(qualified_name) end it "should raise an error when trying to lookup nil" do principal = Puppet::Util::Windows::SID::Principal expect { principal.lookup_account_sid(nil) }.to raise_error(Puppet::Util::Windows::Error, /must not be nil/) end it "should raise an error when trying to lookup non-byte array" do principal = Puppet::Util::Windows::SID::Principal expect { principal.lookup_account_sid('ConanTheBarbarian') }.to raise_error(Puppet::Util::Windows::Error, /array/) end it "should raise an error when trying to lookup an empty array" do principal = Puppet::Util::Windows::SID::Principal expect { principal.lookup_account_sid([]) }.to raise_error(Puppet::Util::Windows::Error, /at least 1 byte long/) end # https://technet.microsoft.com/en-us/library/cc962011.aspx # "... The structure used in all SIDs created by Windows NT and Windows 2000 is revision level 1. ..." # Therefore a value of zero for the revision, is not a valid SID it "should raise an error when trying to lookup completely invalid SID bytes" do principal = Puppet::Util::Windows::SID::Principal expect { principal.lookup_account_sid([0]) }.to raise_error do |error| expect(error).to be_a(Puppet::Util::Windows::Error) expect(error.code).to eq(87) # ERROR_INVALID_PARAMETER end end it "should raise an error when trying to lookup a valid SID that doesn't have a matching account" do principal = Puppet::Util::Windows::SID::Principal expect { # S-1-1-1 which is not a valid account principal.lookup_account_sid([1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0]) }.to raise_error do |error| expect(error).to be_a(Puppet::Util::Windows::Error) expect(error.code).to eq(1332) # ERROR_NONE_MAPPED end end it "should return a domain principal for BUILTIN SID S-1-5-32" do principal = Puppet::Util::Windows::SID::Principal.lookup_account_sid([1, 1, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0]) expect(principal.account_type).to eq(:SidTypeDomain) expect(principal.sid).to eq('S-1-5-32') expect(principal.account).to eq(builtin_localized) expect(principal.domain).to eq(builtin_localized) expect(principal.domain_account).to eq(builtin_localized) expect(principal.to_s).to eq(builtin_localized) end end describe "it should create matching Principal objects" do let(:builtin_sid) { [1, 1, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0] } let(:sid_principal) { Puppet::Util::Windows::SID::Principal.lookup_account_sid(builtin_sid) } ['.', ''].each do |name| it "when comparing the one looked up via SID S-1-5-32 to one looked up via non-canonical name #{name} for the BUILTIN domain" do name_principal = Puppet::Util::Windows::SID::Principal.lookup_account_name(name) # compares canonical sid expect(sid_principal).to eq(name_principal) # compare all properties that have public accessors sid_principal.public_methods(false).reject { |m| m == :== }.each do |method| expect(sid_principal.send(method)).to eq(name_principal.send(method)) end end end it "when comparing the one looked up via SID S-1-5-32 to one looked up via non-canonical localized name for the BUILTIN domain" do name_principal = Puppet::Util::Windows::SID::Principal.lookup_account_name(builtin_localized) # compares canonical sid expect(sid_principal).to eq(name_principal) # compare all properties that have public accessors sid_principal.public_methods(false).reject { |m| m == :== }.each do |method| expect(sid_principal.send(method)).to eq(name_principal.send(method)) end end end end puppet-5.5.10/spec/integration/util/windows/process_spec.rb0000644005276200011600000001071213417161722023755 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'facter' describe "Puppet::Util::Windows::Process", :if => Puppet.features.microsoft_windows? do describe "as an admin" do it "should have the SeCreateSymbolicLinkPrivilege necessary to create symlinks on Vista / 2008+", :if => Facter.value(:kernelmajversion).to_f >= 6.0 && Puppet.features.microsoft_windows? do # this is a bit of a lame duck test since it requires running user to be admin # a better integration test would create a new user with the privilege and verify expect(Puppet::Util::Windows::User).to be_admin expect(Puppet::Util::Windows::Process.process_privilege_symlink?).to be_truthy end it "should not have the SeCreateSymbolicLinkPrivilege necessary to create symlinks on 2003 and earlier", :if => Facter.value(:kernelmajversion).to_f < 6.0 && Puppet.features.microsoft_windows? do expect(Puppet::Util::Windows::User).to be_admin expect(Puppet::Util::Windows::Process.process_privilege_symlink?).to be_falsey end it "should be able to lookup a standard Windows process privilege" do Puppet::Util::Windows::Process.lookup_privilege_value('SeShutdownPrivilege') do |luid| expect(luid).not_to be_nil expect(luid).to be_instance_of(Puppet::Util::Windows::Process::LUID) end end it "should raise an error for an unknown privilege name" do expect { Puppet::Util::Windows::Process.lookup_privilege_value('foo') }.to raise_error do |error| expect(error).to be_a(Puppet::Util::Windows::Error) expect(error.code).to eq(1313) # ERROR_NO_SUCH_PRIVILEGE end end end describe "when reading environment variables" do after :each do # spec\integration\test\test_helper_spec.rb calls set_environment_strings # after :all and thus needs access to the real APIs once again Puppet::Util::Windows::Process.unstub(:GetEnvironmentStringsW) Puppet::Util::Windows::Process.unstub(:FreeEnvironmentStringsW) end it "will ignore only keys or values with corrupt byte sequences" do arraydest = [] Puppet::Util::Log.newdestination(Puppet::Test::LogCollector.new(arraydest)) env_vars = {} # Create a UTF-16LE version of the below null separated environment string # "a=b\x00c=d\x00e=\xDD\xDD\x00f=g\x00\x00" env_var_block = "a=b\x00".encode(Encoding::UTF_16LE) + "c=d\x00".encode(Encoding::UTF_16LE) + 'e='.encode(Encoding::UTF_16LE) + "\xDD\xDD".force_encoding(Encoding::UTF_16LE) + "\x00".encode(Encoding::UTF_16LE) + "f=g\x00\x00".encode(Encoding::UTF_16LE) env_var_block_bytes = env_var_block.bytes.to_a FFI::MemoryPointer.new(:byte, env_var_block_bytes.count) do |ptr| # uchar here is synonymous with byte ptr.put_array_of_uchar(0, env_var_block_bytes) # stub the block of memory that the Win32 API would typically return via pointer Puppet::Util::Windows::Process.expects(:GetEnvironmentStringsW).returns(ptr) # stub out the real API call to free memory, else process crashes Puppet::Util::Windows::Process.expects(:FreeEnvironmentStringsW) env_vars = Puppet::Util::Windows::Process.get_environment_strings end # based on corrupted memory, the e=\xDD\xDD should have been removed from the set expect(env_vars).to eq({'a' => 'b', 'c' => 'd', 'f' => 'g'}) # and Puppet should emit a warning about it expect(arraydest.last.level).to eq(:warning) expect(arraydest.last.message).to eq("Discarding environment variable e=\uFFFD which contains invalid bytes") end end describe "when setting environment variables" do it "can properly handle env var values with = in them" do begin name = SecureRandom.uuid value = 'foo=bar' Puppet::Util::Windows::Process.set_environment_variable(name, value) env = Puppet::Util::Windows::Process.get_environment_strings expect(env[name]).to eq(value) ensure Puppet::Util::Windows::Process.set_environment_variable(name, nil) end end it "can properly handle empty env var values" do begin name = SecureRandom.uuid Puppet::Util::Windows::Process.set_environment_variable(name, '') env = Puppet::Util::Windows::Process.get_environment_strings expect(env[name]).to eq('') ensure Puppet::Util::Windows::Process.set_environment_variable(name, nil) end end end end puppet-5.5.10/spec/integration/util/windows/registry_spec.rb0000644005276200011600000002446613417161722024162 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/windows' if Puppet::Util::Platform.windows? describe Puppet::Util::Windows::Registry do subject do class TestRegistry include Puppet::Util::Windows::Registry extend FFI::Library ffi_lib :advapi32 attach_function :RegSetValueExW, [:handle, :pointer, :dword, :dword, :pointer, :dword], :win32_long def write_corrupt_dword(reg, valuename) # Normally DWORDs contain 4 bytes. This bad data only has 2 bad_data = [0, 0] FFI::Pointer.from_string_to_wide_string(valuename) do |name_ptr| FFI::MemoryPointer.new(:uchar, bad_data.length) do |data_ptr| data_ptr.write_array_of_uchar(bad_data) if RegSetValueExW(reg.hkey, name_ptr, 0, Win32::Registry::REG_DWORD, data_ptr, data_ptr.size) != 0 raise Puppet::Util::Windows::Error.new("Failed to write registry value") end end end end end TestRegistry.new end let(:name) { 'HKEY_LOCAL_MACHINE' } let(:path) { 'Software\Microsoft' } context "#root" do it "should lookup the root hkey" do expect(subject.root(name)).to be_instance_of(Win32::Registry::PredefinedKey) end it "should raise for unknown root keys" do expect { subject.root('HKEY_BOGUS') }.to raise_error(Puppet::Error, /Invalid registry key/) end end context "#open" do let(:hkey) { stub 'hklm' } let(:subkey) { stub 'subkey' } before :each do subject.stubs(:root).returns(hkey) end it "should yield the opened the subkey" do hkey.expects(:open).with do |p, _| expect(p).to eq(path) end.yields(subkey) yielded = nil subject.open(name, path) {|reg| yielded = reg} expect(yielded).to eq(subkey) end if Puppet::Util::Platform.windows? [described_class::KEY64, described_class::KEY32].each do |access| it "should open the key for read access 0x#{access.to_s(16)}" do mode = described_class::KEY_READ | access hkey.expects(:open).with(path, mode) subject.open(name, path, mode) {|reg| } end end end it "should default to KEY64" do hkey.expects(:open).with(path, described_class::KEY_READ | described_class::KEY64) subject.open(hkey, path) {|hkey| } end it "should raise for a path that doesn't exist" do hkey.expects(:keyname).returns('HKEY_LOCAL_MACHINE') hkey.expects(:open).raises(Win32::Registry::Error.new(2)) # file not found expect do subject.open(hkey, 'doesnotexist') {|hkey| } end.to raise_error(Puppet::Error, /Failed to open registry key 'HKEY_LOCAL_MACHINE\\doesnotexist'/) end end context "#values" do let(:key) { stub('uninstall') } def expects_registry_value(array) key.expects(:each_value).never subject.expects(:each_value).with(key).multiple_yields(array) subject.values(key).first[1] end it "should return each value's name and data" do key.expects(:each_value).never subject.expects(:each_value).with(key).multiple_yields( ['string', 1, 'foo'], ['dword', 4, 0] ) expect(subject.values(key)).to eq({ 'string' => 'foo', 'dword' => 0 }) end it "should return an empty hash if there are no values" do key.expects(:each_value).never subject.expects(:each_value).with(key) expect(subject.values(key)).to eq({}) end it "passes REG_DWORD through" do reg_value = ['dword', Win32::Registry::REG_DWORD, '1'] value = expects_registry_value(reg_value) expect(Integer(value)).to eq(1) end context "when reading non-ASCII values" do ENDASH_UTF_8 = [0xE2, 0x80, 0x93] ENDASH_UTF_16 = [0x2013] TM_UTF_8 = [0xE2, 0x84, 0xA2] TM_UTF_16 = [0x2122] let (:hklm) { Win32::Registry::HKEY_LOCAL_MACHINE } let (:puppet_key) { "SOFTWARE\\Puppet Labs"} let (:subkey_name) { "PuppetRegistryTest#{SecureRandom.uuid}" } let (:guid) { SecureRandom.uuid } let (:regsam) { Puppet::Util::Windows::Registry::KEY32 } after(:each) do # Ruby 2.1.5 has bugs with deleting registry keys due to using ANSI # character APIs, but passing wide strings to them (facepalm) # https://github.com/ruby/ruby/blob/v2_1_5/ext/win32/lib/win32/registry.rb#L323-L329 # therefore, use our own built-in registry helper code hklm.open(puppet_key, Win32::Registry::KEY_ALL_ACCESS | regsam) do |reg| subject.delete_key(reg, subkey_name, regsam) end end # proof that local encodings (such as IBM437 are no longer relevant) it "will return a UTF-8 string from a REG_SZ registry value (written as UTF-16LE)", :if => Puppet::Util::Platform.windows? && RUBY_VERSION >= '2.1' do # create a UTF-16LE byte array representing "–™" utf_16_bytes = ENDASH_UTF_16 + TM_UTF_16 utf_16_str = utf_16_bytes.pack('s*').force_encoding(Encoding::UTF_16LE) # and it's UTF-8 equivalent bytes utf_8_bytes = ENDASH_UTF_8 + TM_UTF_8 utf_8_str = utf_8_bytes.pack('c*').force_encoding(Encoding::UTF_8) # this problematic Ruby codepath triggers a conversion of UTF-16LE to # a local codepage which can totally break when that codepage has no # conversion from the given UTF-16LE characters to local codepage # a prime example is that IBM437 has no conversion from a Unicode en-dash Win32::Registry.expects(:export_string).never # also, expect that we're using our variants of keys / values, not Rubys Win32::Registry.expects(:each_key).never Win32::Registry.expects(:each_value).never hklm.create("#{puppet_key}\\#{subkey_name}", Win32::Registry::KEY_ALL_ACCESS | regsam) do |reg| reg.write("#{guid}", Win32::Registry::REG_SZ, utf_16_str) # trigger Puppet::Util::Windows::Registry FFI calls keys = subject.keys(reg) vals = subject.values(reg) expect(keys).to be_empty expect(vals).to have_key(guid) # The UTF-16LE string written should come back as the equivalent UTF-8 written = vals[guid] expect(written).to eq(utf_8_str) expect(written.encoding).to eq(Encoding::UTF_8) end end end context "when reading values" do let (:hklm) { Win32::Registry::HKEY_LOCAL_MACHINE } let (:puppet_key) { "SOFTWARE\\Puppet Labs"} let (:subkey_name) { "PuppetRegistryTest#{SecureRandom.uuid}" } let (:value_name) { SecureRandom.uuid } after(:each) do hklm.open(puppet_key, Win32::Registry::KEY_ALL_ACCESS) do |reg| subject.delete_key(reg, subkey_name) end end [ {:name => 'REG_SZ', :type => Win32::Registry::REG_SZ, :value => 'reg sz string'}, {:name => 'REG_EXPAND_SZ', :type => Win32::Registry::REG_EXPAND_SZ, :value => 'reg expand string'}, {:name => 'REG_MULTI_SZ', :type => Win32::Registry::REG_MULTI_SZ, :value => ['string1', 'string2']}, {:name => 'REG_BINARY', :type => Win32::Registry::REG_BINARY, :value => 'abinarystring'}, {:name => 'REG_DWORD', :type => Win32::Registry::REG_DWORD, :value => 0xFFFFFFFF}, {:name => 'REG_DWORD_BIG_ENDIAN', :type => Win32::Registry::REG_DWORD_BIG_ENDIAN, :value => 0xFFFF}, {:name => 'REG_QWORD', :type => Win32::Registry::REG_QWORD, :value => 0xFFFFFFFFFFFFFFFF}, ].each do |pair| it "should return #{pair[:name]} values" do hklm.create("#{puppet_key}\\#{subkey_name}", Win32::Registry::KEY_ALL_ACCESS) do |reg| reg.write(value_name, pair[:type], pair[:value]) end hklm.open("#{puppet_key}\\#{subkey_name}", Win32::Registry::KEY_READ) do |reg| vals = subject.values(reg) expect(vals).to have_key(value_name) subject.each_value(reg) do |subkey, type, data| expect(type).to eq(pair[:type]) end written = vals[value_name] expect(written).to eq(pair[:value]) end end end end context "when reading corrupt values" do let (:hklm) { Win32::Registry::HKEY_LOCAL_MACHINE } let (:puppet_key) { "SOFTWARE\\Puppet Labs"} let (:subkey_name) { "PuppetRegistryTest#{SecureRandom.uuid}" } let (:value_name) { SecureRandom.uuid } before(:each) do hklm.create("#{puppet_key}\\#{subkey_name}", Win32::Registry::KEY_ALL_ACCESS) do |reg_key| subject.write_corrupt_dword(reg_key, value_name) end end after(:each) do hklm.open(puppet_key, Win32::Registry::KEY_ALL_ACCESS) do |reg_key| subject.delete_key(reg_key, subkey_name) end end it "should return nil for a corrupt DWORD" do hklm.open("#{puppet_key}\\#{subkey_name}", Win32::Registry::KEY_ALL_ACCESS) do |reg_key| vals = subject.values(reg_key) expect(vals).to have_key(value_name) expect(vals[value_name]).to be_nil end end end end context "#values_by_name" do let(:hkey) { stub 'hklm' } let(:subkey) { stub 'subkey' } before :each do subject.stubs(:root).returns(hkey) end context "when reading values" do let (:hklm) { Win32::Registry::HKEY_LOCAL_MACHINE } let (:puppet_key) { "SOFTWARE\\Puppet Labs"} let (:subkey_name) { "PuppetRegistryTest#{SecureRandom.uuid}" } before(:each) do hklm.create("#{puppet_key}\\#{subkey_name}", Win32::Registry::KEY_ALL_ACCESS) do |reg| reg.write('valuename1', Win32::Registry::REG_SZ, 'value1') reg.write('valuename2', Win32::Registry::REG_SZ, 'value2') end end after(:each) do hklm.open(puppet_key, Win32::Registry::KEY_ALL_ACCESS) do |reg| subject.delete_key(reg, subkey_name) end end it "should return only the values for the names specified" do hklm.open("#{puppet_key}\\#{subkey_name}", Win32::Registry::KEY_ALL_ACCESS) do |reg_key| vals = subject.values_by_name(reg_key, ['valuename1', 'missingname']) expect(vals).to have_key('valuename1') expect(vals).to_not have_key('valuename2') expect(vals['valuename1']).to eq('value1') expect(vals['missingname']).to be_nil end end end end end end puppet-5.5.10/spec/integration/util/windows/security_spec.rb0000644005276200011600000011303013417161722024143 0ustar jenkinsjenkins#!/usr/bin/env ruby require 'spec_helper' if Puppet.features.microsoft_windows? class WindowsSecurityTester require 'puppet/util/windows/security' include Puppet::Util::Windows::Security end end describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_windows? do include PuppetSpec::Files before :all do # necessary for localized name of guests guests_name = Puppet::Util::Windows::SID.sid_to_name('S-1-5-32-546') guests = Puppet::Util::Windows::ADSI::Group.new(guests_name) @sids = { :current_user => Puppet::Util::Windows::SID.name_to_sid(Puppet::Util::Windows::ADSI::User.current_user_name), :system => Puppet::Util::Windows::SID::LocalSystem, :administrators => Puppet::Util::Windows::SID::BuiltinAdministrators, :guest => Puppet::Util::Windows::SID.name_to_sid(guests.members[0]), :users => Puppet::Util::Windows::SID::BuiltinUsers, :power_users => Puppet::Util::Windows::SID::PowerUsers, :none => Puppet::Util::Windows::SID::Nobody, :everyone => Puppet::Util::Windows::SID::Everyone } # The TCP/IP NetBIOS Helper service (aka 'lmhosts') has ended up # disabled on some VMs for reasons we couldn't track down. This # condition causes tests which rely on resolving UNC style paths # (like \\localhost) to fail with unhelpful error messages. # Put a check for this upfront to aid debug should this strike again. service = Puppet::Type.type(:service).new(:name => 'lmhosts') expect(service.provider.status).to eq(:running), 'lmhosts service is not running' end let (:sids) { @sids } let (:winsec) { WindowsSecurityTester.new } let (:klass) { Puppet::Util::Windows::File } def set_group_depending_on_current_user(path) if sids[:current_user] == sids[:system] # if the current user is SYSTEM, by setting the group to # guest, SYSTEM is automagically given full control, so instead # override that behavior with SYSTEM as group and a specific mode winsec.set_group(sids[:system], path) mode = winsec.get_mode(path) winsec.set_mode(mode & ~WindowsSecurityTester::S_IRWXG, path) else winsec.set_group(sids[:guest], path) end end def grant_everyone_full_access(path) sd = winsec.get_security_descriptor(path) everyone = 'S-1-1-0' inherit = Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE | Puppet::Util::Windows::AccessControlEntry::CONTAINER_INHERIT_ACE sd.dacl.allow(everyone, klass::FILE_ALL_ACCESS, inherit) winsec.set_security_descriptor(path, sd) end shared_examples_for "only child owner" do it "should allow child owner" do winsec.set_owner(sids[:guest], parent) winsec.set_group(sids[:current_user], parent) winsec.set_mode(0700, parent) check_delete(path) end it "should deny parent owner" do winsec.set_owner(sids[:guest], path) winsec.set_group(sids[:current_user], path) winsec.set_mode(0700, path) expect { check_delete(path) }.to raise_error(Errno::EACCES) end it "should deny group" do winsec.set_owner(sids[:guest], path) winsec.set_group(sids[:current_user], path) winsec.set_mode(0700, path) expect { check_delete(path) }.to raise_error(Errno::EACCES) end it "should deny other" do winsec.set_owner(sids[:guest], path) winsec.set_group(sids[:current_user], path) winsec.set_mode(0700, path) expect { check_delete(path) }.to raise_error(Errno::EACCES) end end shared_examples_for "a securable object" do describe "on a volume that doesn't support ACLs" do [:owner, :group, :mode].each do |p| it "should return nil #{p}" do winsec.stubs(:supports_acl?).returns false expect(winsec.send("get_#{p}", path)).to be_nil end end end describe "on a volume that supports ACLs" do describe "for a normal user" do before :each do Puppet.features.stubs(:root?).returns(false) end after :each do winsec.set_mode(WindowsSecurityTester::S_IRWXU, parent) begin winsec.set_mode(WindowsSecurityTester::S_IRWXU, path) rescue nil end end describe "#supports_acl?" do %w[c:/ c:\\ c:/windows/system32 \\\\localhost\\C$ \\\\127.0.0.1\\C$\\foo].each do |path| it "should accept #{path}" do expect(winsec).to be_supports_acl(path) end end it "should raise an exception if it cannot get volume information" do expect { winsec.supports_acl?('foobar') }.to raise_error(Puppet::Error, /Failed to get volume information/) end end describe "#owner=" do it "should allow setting to the current user" do winsec.set_owner(sids[:current_user], path) end it "should raise an exception when setting to a different user" do expect { winsec.set_owner(sids[:guest], path) }.to raise_error do |error| expect(error).to be_a(Puppet::Util::Windows::Error) expect(error.code).to eq(1307) # ERROR_INVALID_OWNER end end end describe "#owner" do it "it should not be empty" do expect(winsec.get_owner(path)).not_to be_empty end it "should raise an exception if an invalid path is provided" do expect { winsec.get_owner("c:\\doesnotexist.txt") }.to raise_error do |error| expect(error).to be_a(Puppet::Util::Windows::Error) expect(error.code).to eq(2) # ERROR_FILE_NOT_FOUND end end end describe "#group=" do it "should allow setting to a group the current owner is a member of" do winsec.set_group(sids[:users], path) end # Unlike unix, if the user has permission to WRITE_OWNER, which the file owner has by default, # then they can set the primary group to a group that the user does not belong to. it "should allow setting to a group the current owner is not a member of" do winsec.set_group(sids[:power_users], path) end it "should consider a mode of 7 for group to be FullControl (F)" do winsec.set_group(sids[:power_users], path) winsec.set_mode(0070, path) group_ace = winsec.get_aces_for_path_by_sid(path, sids[:power_users]) # there should only be a single ace for the given group expect(group_ace.count).to eq(1) expect(group_ace[0].mask).to eq(klass::FILE_ALL_ACCESS) # ensure that mode is still read as 070 (written as 70) expect((winsec.get_mode(path) & 0777).to_s(8).rjust(3, '0')).to eq("070") end end describe "#group" do it "should not be empty" do expect(winsec.get_group(path)).not_to be_empty end it "should raise an exception if an invalid path is provided" do expect { winsec.get_group("c:\\doesnotexist.txt") }.to raise_error do |error| expect(error).to be_a(Puppet::Util::Windows::Error) expect(error.code).to eq(2) # ERROR_FILE_NOT_FOUND end end end it "should preserve inherited full control for SYSTEM when setting owner and group" do # new file has SYSTEM system_aces = winsec.get_aces_for_path_by_sid(path, sids[:system]) expect(system_aces).not_to be_empty # when running under SYSTEM account, multiple ACEs come back # so we only care that we have at least one of these expect(system_aces.any? do |ace| ace.mask == klass::FILE_ALL_ACCESS end).to be_truthy # changing the owner/group will no longer make the SD protected winsec.set_group(sids[:power_users], path) winsec.set_owner(sids[:administrators], path) expect(system_aces.find do |ace| ace.mask == klass::FILE_ALL_ACCESS && ace.inherited? end).not_to be_nil end describe "#mode=" do (0000..0700).step(0100) do |mode| it "should enforce mode #{mode.to_s(8)}" do winsec.set_mode(mode, path) check_access(mode, path) end end it "should round-trip all 128 modes that do not require deny ACEs, where owner and group are different" do # windows defaults set Administrators, None when Administrator # or Administrators, SYSTEM when System # but we can guarantee group is different by explicitly setting to Users winsec.set_group(sids[:users], path) 0.upto(1).each do |s| 0.upto(7).each do |u| 0.upto(u).each do |g| 0.upto(g).each do |o| # if user is superset of group, and group superset of other, then # no deny ace is required, and mode can be converted to win32 # access mask, and back to mode without loss of information # (provided the owner and group are not the same) next if ((u & g) != g) or ((g & o) != o) mode = (s << 9 | u << 6 | g << 3 | o << 0) winsec.set_mode(mode, path) expect(winsec.get_mode(path).to_s(8)).to eq(mode.to_s(8)) end end end end end it "should round-trip all 54 modes that do not require deny ACEs, where owner and group are same" do winsec.set_group(winsec.get_owner(path), path) 0.upto(1).each do |s| 0.upto(7).each do |ug| 0.upto(ug).each do |o| # if user and group superset of other, then # no deny ace is required, and mode can be converted to win32 # access mask, and back to mode without loss of information # (provided the owner and group are the same) next if ((ug & o) != o) mode = (s << 9 | ug << 6 | ug << 3 | o << 0) winsec.set_mode(mode, path) expect(winsec.get_mode(path).to_s(8)).to eq(mode.to_s(8)) end end end end # The SYSTEM user is a special case therefore we need to test that we round trip correctly when set it "should round-trip all 128 modes that do not require deny ACEs, when simulating a SYSTEM service" do # The owner and group for files/dirs created, when running as a service under Local System are # Owner = Administrators # Group = SYSTEM winsec.set_owner(sids[:administrators], path) winsec.set_group(sids[:system], path) 0.upto(1).each do |s| 0.upto(7).each do |u| 0.upto(u).each do |g| 0.upto(g).each do |o| # if user is superset of group, and group superset of other, then # no deny ace is required, and mode can be converted to win32 # access mask, and back to mode without loss of information # (provided the owner and group are not the same) next if ((u & g) != g) or ((g & o) != o) applied_mode = (s << 9 | u << 6 | g << 3 | o << 0) # SYSTEM must always be Full Control (7) expected_mode = (s << 9 | u << 6 | 7 << 3 | o << 0) winsec.set_mode(applied_mode, path) expect(winsec.get_mode(path).to_s(8)).to eq(expected_mode.to_s(8)) end end end end end it "should preserve full control for SYSTEM when setting mode" do # new file has SYSTEM system_aces = winsec.get_aces_for_path_by_sid(path, sids[:system]) expect(system_aces).not_to be_empty # when running under SYSTEM account, multiple ACEs come back # so we only care that we have at least one of these expect(system_aces.any? do |ace| ace.mask == klass::FILE_ALL_ACCESS end).to be_truthy # changing the mode will make the SD protected winsec.set_group(sids[:none], path) winsec.set_mode(0600, path) # and should have a non-inherited SYSTEM ACE(s) system_aces = winsec.get_aces_for_path_by_sid(path, sids[:system]) system_aces.each do |ace| expect(ace.mask).to eq(klass::FILE_ALL_ACCESS) expect(ace).not_to be_inherited end if Puppet::FileSystem.directory?(path) system_aces.each do |ace| expect(ace).to be_object_inherit expect(ace).to be_container_inherit end # it's critically important that this file be default created # and that this file not have it's owner / group / mode set by winsec nested_file = File.join(path, 'nested_file') File.new(nested_file, 'w').close system_aces = winsec.get_aces_for_path_by_sid(nested_file, sids[:system]) # even when SYSTEM is the owner (in CI), there should be an inherited SYSTEM expect(system_aces.any? do |ace| ace.mask == klass::FILE_ALL_ACCESS && ace.inherited? end).to be_truthy end end describe "for modes that require deny aces" do it "should map everyone to group and owner" do winsec.set_mode(0426, path) expect(winsec.get_mode(path).to_s(8)).to eq("666") end it "should combine user and group modes when owner and group sids are equal" do winsec.set_group(winsec.get_owner(path), path) winsec.set_mode(0410, path) expect(winsec.get_mode(path).to_s(8)).to eq("550") end end describe "for read-only objects" do before :each do winsec.set_group(sids[:none], path) winsec.set_mode(0600, path) Puppet::Util::Windows::File.add_attributes(path, klass::FILE_ATTRIBUTE_READONLY) expect(Puppet::Util::Windows::File.get_attributes(path) & klass::FILE_ATTRIBUTE_READONLY).to be_nonzero end it "should make them writable if any sid has write permission" do winsec.set_mode(WindowsSecurityTester::S_IWUSR, path) expect(Puppet::Util::Windows::File.get_attributes(path) & klass::FILE_ATTRIBUTE_READONLY).to eq(0) end it "should leave them read-only if no sid has write permission and should allow full access for SYSTEM" do winsec.set_mode(WindowsSecurityTester::S_IRUSR | WindowsSecurityTester::S_IXGRP, path) expect(Puppet::Util::Windows::File.get_attributes(path) & klass::FILE_ATTRIBUTE_READONLY).to be_nonzero system_aces = winsec.get_aces_for_path_by_sid(path, sids[:system]) # when running under SYSTEM account, and set_group / set_owner hasn't been called # SYSTEM full access will be restored expect(system_aces.any? do |ace| ace.mask == klass::FILE_ALL_ACCESS end).to be_truthy end end it "should raise an exception if an invalid path is provided" do expect { winsec.set_mode(sids[:guest], "c:\\doesnotexist.txt") }.to raise_error do |error| expect(error).to be_a(Puppet::Util::Windows::Error) expect(error.code).to eq(2) # ERROR_FILE_NOT_FOUND end end end describe "#mode" do it "should report when extra aces are encounted" do sd = winsec.get_security_descriptor(path) (544..547).each do |rid| sd.dacl.allow("S-1-5-32-#{rid}", klass::STANDARD_RIGHTS_ALL) end winsec.set_security_descriptor(path, sd) mode = winsec.get_mode(path) expect(mode & WindowsSecurityTester::S_IEXTRA).to eq(WindowsSecurityTester::S_IEXTRA) end it "should return deny aces" do sd = winsec.get_security_descriptor(path) sd.dacl.deny(sids[:guest], klass::FILE_GENERIC_WRITE) winsec.set_security_descriptor(path, sd) guest_aces = winsec.get_aces_for_path_by_sid(path, sids[:guest]) expect(guest_aces.find do |ace| ace.type == Puppet::Util::Windows::AccessControlEntry::ACCESS_DENIED_ACE_TYPE end).not_to be_nil end it "should skip inherit-only ace" do sd = winsec.get_security_descriptor(path) dacl = Puppet::Util::Windows::AccessControlList.new dacl.allow( sids[:current_user], klass::STANDARD_RIGHTS_ALL | klass::SPECIFIC_RIGHTS_ALL ) dacl.allow( sids[:everyone], klass::FILE_GENERIC_READ, Puppet::Util::Windows::AccessControlEntry::INHERIT_ONLY_ACE | Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE ) winsec.set_security_descriptor(path, sd) expect(winsec.get_mode(path) & WindowsSecurityTester::S_IRWXO).to eq(0) end it "should raise an exception if an invalid path is provided" do expect { winsec.get_mode("c:\\doesnotexist.txt") }.to raise_error do |error| expect(error).to be_a(Puppet::Util::Windows::Error) expect(error.code).to eq(2) # ERROR_FILE_NOT_FOUND end end end describe "inherited access control entries" do it "should be absent when the access control list is protected, and should not remove SYSTEM" do winsec.set_mode(WindowsSecurityTester::S_IRWXU, path) mode = winsec.get_mode(path) [ WindowsSecurityTester::S_IEXTRA, WindowsSecurityTester::S_ISYSTEM_MISSING ].each do |flag| expect(mode & flag).not_to eq(flag) end end it "should be present when the access control list is unprotected" do # add a bunch of aces to the parent with permission to add children allow = klass::STANDARD_RIGHTS_ALL | klass::SPECIFIC_RIGHTS_ALL inherit = Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE | Puppet::Util::Windows::AccessControlEntry::CONTAINER_INHERIT_ACE sd = winsec.get_security_descriptor(parent) sd.dacl.allow( "S-1-1-0", #everyone allow, inherit ) (544..547).each do |rid| sd.dacl.allow( "S-1-5-32-#{rid}", klass::STANDARD_RIGHTS_ALL, inherit ) end winsec.set_security_descriptor(parent, sd) # unprotect child, it should inherit from parent winsec.set_mode(WindowsSecurityTester::S_IRWXU, path, false) expect(winsec.get_mode(path) & WindowsSecurityTester::S_IEXTRA).to eq(WindowsSecurityTester::S_IEXTRA) end end end describe "for an administrator", :if => (Puppet.features.root? && Puppet.features.microsoft_windows?) do before :each do is_dir = Puppet::FileSystem.directory?(path) winsec.set_mode(WindowsSecurityTester::S_IRWXU | WindowsSecurityTester::S_IRWXG, path) set_group_depending_on_current_user(path) winsec.set_owner(sids[:guest], path) expected_error = RUBY_VERSION =~ /^2\./ && is_dir ? Errno::EISDIR : Errno::EACCES expect { File.open(path, 'r') }.to raise_error(expected_error) end after :each do if Puppet::FileSystem.exist?(path) winsec.set_owner(sids[:current_user], path) winsec.set_mode(WindowsSecurityTester::S_IRWXU, path) end end describe "#owner=" do it "should accept the guest sid" do winsec.set_owner(sids[:guest], path) expect(winsec.get_owner(path)).to eq(sids[:guest]) end it "should accept a user sid" do winsec.set_owner(sids[:current_user], path) expect(winsec.get_owner(path)).to eq(sids[:current_user]) end it "should accept a group sid" do winsec.set_owner(sids[:power_users], path) expect(winsec.get_owner(path)).to eq(sids[:power_users]) end it "should raise an exception if an invalid sid is provided" do expect { winsec.set_owner("foobar", path) }.to raise_error(Puppet::Error, /Failed to convert string SID/) end it "should raise an exception if an invalid path is provided" do expect { winsec.set_owner(sids[:guest], "c:\\doesnotexist.txt") }.to raise_error do |error| expect(error).to be_a(Puppet::Util::Windows::Error) expect(error.code).to eq(2) # ERROR_FILE_NOT_FOUND end end end describe "#group=" do it "should accept the test group" do winsec.set_group(sids[:guest], path) expect(winsec.get_group(path)).to eq(sids[:guest]) end it "should accept a group sid" do winsec.set_group(sids[:power_users], path) expect(winsec.get_group(path)).to eq(sids[:power_users]) end it "should accept a user sid" do winsec.set_group(sids[:current_user], path) expect(winsec.get_group(path)).to eq(sids[:current_user]) end it "should combine owner and group rights when they are the same sid" do winsec.set_owner(sids[:power_users], path) winsec.set_group(sids[:power_users], path) winsec.set_mode(0610, path) expect(winsec.get_owner(path)).to eq(sids[:power_users]) expect(winsec.get_group(path)).to eq(sids[:power_users]) # note group execute permission added to user ace, and then group rwx value # reflected to match # Exclude missing system ace, since that's not relevant expect((winsec.get_mode(path) & 0777).to_s(8)).to eq("770") end it "should raise an exception if an invalid sid is provided" do expect { winsec.set_group("foobar", path) }.to raise_error(Puppet::Error, /Failed to convert string SID/) end it "should raise an exception if an invalid path is provided" do expect { winsec.set_group(sids[:guest], "c:\\doesnotexist.txt") }.to raise_error do |error| expect(error).to be_a(Puppet::Util::Windows::Error) expect(error.code).to eq(2) # ERROR_FILE_NOT_FOUND end end end describe "when the sid is NULL" do it "should retrieve an empty owner sid" it "should retrieve an empty group sid" end describe "when the sid refers to a deleted trustee" do it "should retrieve the user sid" do sid = nil user = Puppet::Util::Windows::ADSI::User.create("puppet#{rand(10000)}") user.password = 'PUPPET_RULeZ_123!' user.commit begin sid = Puppet::Util::Windows::ADSI::User.new(user.name).sid.sid winsec.set_owner(sid, path) winsec.set_mode(WindowsSecurityTester::S_IRWXU, path) ensure Puppet::Util::Windows::ADSI::User.delete(user.name) end expect(winsec.get_owner(path)).to eq(sid) expect(winsec.get_mode(path)).to eq(WindowsSecurityTester::S_IRWXU) end it "should retrieve the group sid" do sid = nil group = Puppet::Util::Windows::ADSI::Group.create("puppet#{rand(10000)}") group.commit begin sid = Puppet::Util::Windows::ADSI::Group.new(group.name).sid.sid winsec.set_group(sid, path) winsec.set_mode(WindowsSecurityTester::S_IRWXG, path) ensure Puppet::Util::Windows::ADSI::Group.delete(group.name) end expect(winsec.get_group(path)).to eq(sid) expect(winsec.get_mode(path)).to eq(WindowsSecurityTester::S_IRWXG) end end describe "#mode" do it "should deny all access when the DACL is empty, including SYSTEM" do sd = winsec.get_security_descriptor(path) # don't allow inherited aces to affect the test protect = true new_sd = Puppet::Util::Windows::SecurityDescriptor.new(sd.owner, sd.group, [], protect) winsec.set_security_descriptor(path, new_sd) expect(winsec.get_mode(path)).to eq(WindowsSecurityTester::S_ISYSTEM_MISSING) end # REMIND: ruby crashes when trying to set a NULL DACL # it "should allow all when it is nil" do # winsec.set_owner(sids[:current_user], path) # winsec.open_file(path, WindowsSecurityTester::READ_CONTROL | WindowsSecurityTester::WRITE_DAC) do |handle| # winsec.set_security_info(handle, WindowsSecurityTester::DACL_SECURITY_INFORMATION | WindowsSecurityTester::PROTECTED_DACL_SECURITY_INFORMATION, nil) # end # winsec.get_mode(path).to_s(8).should == "777" # end end describe "#mode=" do # setting owner to SYSTEM requires root it "should round-trip all 54 modes that do not require deny ACEs, when simulating a SYSTEM scheduled task" do # The owner and group for files/dirs created, when running as a Scheduled Task as Local System are # Owner = SYSTEM # Group = SYSTEM winsec.set_group(sids[:system], path) winsec.set_owner(sids[:system], path) 0.upto(1).each do |s| 0.upto(7).each do |ug| 0.upto(ug).each do |o| # if user and group superset of other, then # no deny ace is required, and mode can be converted to win32 # access mask, and back to mode without loss of information # (provided the owner and group are the same) next if ((ug & o) != o) applied_mode = (s << 9 | ug << 6 | ug << 3 | o << 0) # SYSTEM must always be Full Control (7) expected_mode = (s << 9 | 7 << 6 | 7 << 3 | o << 0) winsec.set_mode(applied_mode, path) expect(winsec.get_mode(path).to_s(8)).to eq(expected_mode.to_s(8)) end end end end end describe "when the parent directory" do before :each do winsec.set_owner(sids[:current_user], parent) winsec.set_owner(sids[:current_user], path) winsec.set_mode(0777, path, false) end describe "is writable and executable" do describe "and sticky bit is set" do it "should allow child owner" do winsec.set_owner(sids[:guest], parent) winsec.set_group(sids[:current_user], parent) winsec.set_mode(01700, parent) check_delete(path) end it "should allow parent owner" do winsec.set_owner(sids[:current_user], parent) winsec.set_group(sids[:guest], parent) winsec.set_mode(01700, parent) winsec.set_owner(sids[:current_user], path) winsec.set_group(sids[:guest], path) winsec.set_mode(0700, path) check_delete(path) end it "should deny group" do winsec.set_owner(sids[:guest], parent) winsec.set_group(sids[:current_user], parent) winsec.set_mode(01770, parent) winsec.set_owner(sids[:guest], path) winsec.set_group(sids[:current_user], path) winsec.set_mode(0700, path) expect { check_delete(path) }.to raise_error(Errno::EACCES) end it "should deny other" do winsec.set_owner(sids[:guest], parent) winsec.set_group(sids[:current_user], parent) winsec.set_mode(01777, parent) winsec.set_owner(sids[:guest], path) winsec.set_group(sids[:current_user], path) winsec.set_mode(0700, path) expect { check_delete(path) }.to raise_error(Errno::EACCES) end end describe "and sticky bit is not set" do it "should allow child owner" do winsec.set_owner(sids[:guest], parent) winsec.set_group(sids[:current_user], parent) winsec.set_mode(0700, parent) check_delete(path) end it "should allow parent owner" do winsec.set_owner(sids[:current_user], parent) winsec.set_group(sids[:guest], parent) winsec.set_mode(0700, parent) winsec.set_owner(sids[:current_user], path) winsec.set_group(sids[:guest], path) winsec.set_mode(0700, path) check_delete(path) end it "should allow group" do winsec.set_owner(sids[:guest], parent) winsec.set_group(sids[:current_user], parent) winsec.set_mode(0770, parent) winsec.set_owner(sids[:guest], path) winsec.set_group(sids[:current_user], path) winsec.set_mode(0700, path) check_delete(path) end it "should allow other" do winsec.set_owner(sids[:guest], parent) winsec.set_group(sids[:current_user], parent) winsec.set_mode(0777, parent) winsec.set_owner(sids[:guest], path) winsec.set_group(sids[:current_user], path) winsec.set_mode(0700, path) check_delete(path) end end end describe "is not writable" do before :each do winsec.set_group(sids[:current_user], parent) winsec.set_mode(0555, parent) end it_behaves_like "only child owner" end describe "is not executable" do before :each do winsec.set_group(sids[:current_user], parent) winsec.set_mode(0666, parent) end it_behaves_like "only child owner" end end end end end describe "file" do let (:parent) do tmpdir('win_sec_test_file') end let (:path) do path = File.join(parent, 'childfile') File.new(path, 'w').close path end after :each do # allow temp files to be cleaned up grant_everyone_full_access(parent) end it_behaves_like "a securable object" do def check_access(mode, path) if (mode & WindowsSecurityTester::S_IRUSR).nonzero? check_read(path) else expect { check_read(path) }.to raise_error(Errno::EACCES) end if (mode & WindowsSecurityTester::S_IWUSR).nonzero? check_write(path) else expect { check_write(path) }.to raise_error(Errno::EACCES) end if (mode & WindowsSecurityTester::S_IXUSR).nonzero? expect { check_execute(path) }.to raise_error(Errno::ENOEXEC) else expect { check_execute(path) }.to raise_error(Errno::EACCES) end end def check_read(path) File.open(path, 'r').close end def check_write(path) File.open(path, 'w').close end def check_execute(path) Kernel.exec(path) end def check_delete(path) File.delete(path) end end describe "locked files" do let (:explorer) { File.join(Dir::WINDOWS, "explorer.exe") } it "should get the owner" do expect(winsec.get_owner(explorer)).to match(/^S-1-5-/) end it "should get the group" do expect(winsec.get_group(explorer)).to match(/^S-1-5-/) end it "should get the mode" do expect(winsec.get_mode(explorer)).to eq(WindowsSecurityTester::S_IRWXU | WindowsSecurityTester::S_IRWXG | WindowsSecurityTester::S_IEXTRA) end end end describe "directory" do let (:parent) do tmpdir('win_sec_test_dir') end let (:path) do path = File.join(parent, 'childdir') Dir.mkdir(path) path end after :each do # allow temp files to be cleaned up grant_everyone_full_access(parent) end it_behaves_like "a securable object" do def check_access(mode, path) if (mode & WindowsSecurityTester::S_IRUSR).nonzero? check_read(path) else expect { check_read(path) }.to raise_error(Errno::EACCES) end if (mode & WindowsSecurityTester::S_IWUSR).nonzero? check_write(path) else expect { check_write(path) }.to raise_error(Errno::EACCES) end if (mode & WindowsSecurityTester::S_IXUSR).nonzero? check_execute(path) else expect { check_execute(path) }.to raise_error(Errno::EACCES) end end def check_read(path) Dir.entries(path) end def check_write(path) Dir.mkdir(File.join(path, "subdir")) end def check_execute(path) Dir.chdir(path) {} end def check_delete(path) Dir.rmdir(path) end end describe "inheritable aces" do it "should be applied to child objects" do mode640 = WindowsSecurityTester::S_IRUSR | WindowsSecurityTester::S_IWUSR | WindowsSecurityTester::S_IRGRP winsec.set_mode(mode640, path) newfile = File.join(path, "newfile.txt") File.new(newfile, "w").close newdir = File.join(path, "newdir") Dir.mkdir(newdir) [newfile, newdir].each do |p| mode = winsec.get_mode(p) expect((mode & 07777).to_s(8)).to eq(mode640.to_s(8)) end end end end context "security descriptor" do let(:path) { tmpfile('sec_descriptor') } let(:read_execute) { 0x201FF } let(:synchronize) { 0x100000 } before :each do FileUtils.touch(path) end it "preserves aces for other users" do dacl = Puppet::Util::Windows::AccessControlList.new sids_in_dacl = [sids[:current_user], sids[:users]] sids_in_dacl.each do |sid| dacl.allow(sid, read_execute) end sd = Puppet::Util::Windows::SecurityDescriptor.new(sids[:guest], sids[:guest], dacl, true) winsec.set_security_descriptor(path, sd) aces = winsec.get_security_descriptor(path).dacl.to_a expect(aces.map(&:sid)).to eq(sids_in_dacl) expect(aces.map(&:mask).all? { |mask| mask == read_execute }).to be_truthy end it "changes the sid for all aces that were assigned to the old owner" do sd = winsec.get_security_descriptor(path) expect(sd.owner).not_to eq(sids[:guest]) sd.dacl.allow(sd.owner, read_execute) sd.dacl.allow(sd.owner, synchronize) sd.owner = sids[:guest] winsec.set_security_descriptor(path, sd) dacl = winsec.get_security_descriptor(path).dacl aces = dacl.find_all { |ace| ace.sid == sids[:guest] } # only non-inherited aces will be reassigned to guest, so # make sure we find at least the two we added expect(aces.size).to be >= 2 end it "preserves INHERIT_ONLY_ACEs" do # inherit only aces can only be set on directories dir = tmpdir('inheritonlyace') inherit_flags = Puppet::Util::Windows::AccessControlEntry::INHERIT_ONLY_ACE | Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE | Puppet::Util::Windows::AccessControlEntry::CONTAINER_INHERIT_ACE sd = winsec.get_security_descriptor(dir) sd.dacl.allow(sd.owner, klass::FILE_ALL_ACCESS, inherit_flags) winsec.set_security_descriptor(dir, sd) sd = winsec.get_security_descriptor(dir) winsec.set_owner(sids[:guest], dir) sd = winsec.get_security_descriptor(dir) expect(sd.dacl.find do |ace| ace.sid == sids[:guest] && ace.inherit_only? end).not_to be_nil end it "allows deny ACEs with inheritance" do # inheritance can only be set on directories dir = tmpdir('denyaces') inherit_flags = Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE | Puppet::Util::Windows::AccessControlEntry::CONTAINER_INHERIT_ACE sd = winsec.get_security_descriptor(dir) sd.dacl.deny(sids[:guest], klass::FILE_ALL_ACCESS, inherit_flags) winsec.set_security_descriptor(dir, sd) sd = winsec.get_security_descriptor(dir) expect(sd.dacl.find do |ace| ace.sid == sids[:guest] && ace.flags != 0 end).not_to be_nil end context "when managing mode" do it "removes aces for sids that are neither the owner nor group" do # add a guest ace, it's never owner or group sd = winsec.get_security_descriptor(path) sd.dacl.allow(sids[:guest], read_execute) winsec.set_security_descriptor(path, sd) # setting the mode, it should remove extra aces winsec.set_mode(0770, path) # make sure it's gone dacl = winsec.get_security_descriptor(path).dacl aces = dacl.find_all { |ace| ace.sid == sids[:guest] } expect(aces).to be_empty end end end end puppet-5.5.10/spec/integration/util/windows/user_spec.rb0000644005276200011600000001503713417161722023262 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe "Puppet::Util::Windows::User", :if => Puppet.features.microsoft_windows? do describe "2003 without UAC" do before :each do Puppet::Util::Windows::Process.stubs(:windows_major_version).returns(5) Puppet::Util::Windows::Process.stubs(:supports_elevated_security?).returns(false) end it "should be an admin if user's token contains the Administrators SID" do Puppet::Util::Windows::User.expects(:check_token_membership).returns(true) expect(Puppet::Util::Windows::User).to be_admin end it "should not be an admin if user's token doesn't contain the Administrators SID" do Puppet::Util::Windows::User.expects(:check_token_membership).returns(false) expect(Puppet::Util::Windows::User).not_to be_admin end it "should raise an exception if we can't check token membership" do Puppet::Util::Windows::User.expects(:check_token_membership).raises(Puppet::Util::Windows::Error, "Access denied.") expect { Puppet::Util::Windows::User.admin? }.to raise_error(Puppet::Util::Windows::Error, /Access denied./) end end context "2008 with UAC" do before :each do Puppet::Util::Windows::Process.stubs(:windows_major_version).returns(6) Puppet::Util::Windows::Process.stubs(:supports_elevated_security?).returns(true) end describe "in local administrators group" do before :each do Puppet::Util::Windows::User.stubs(:check_token_membership).returns(true) end it "should be an admin if user is running with elevated privileges" do Puppet::Util::Windows::Process.stubs(:elevated_security?).returns(true) expect(Puppet::Util::Windows::User).to be_admin end it "should not be an admin if user is not running with elevated privileges" do Puppet::Util::Windows::Process.stubs(:elevated_security?).returns(false) expect(Puppet::Util::Windows::User).not_to be_admin end it "should raise an exception if the process fails to open the process token" do Puppet::Util::Windows::Process.stubs(:elevated_security?).raises(Puppet::Util::Windows::Error, "Access denied.") expect { Puppet::Util::Windows::User.admin? }.to raise_error(Puppet::Util::Windows::Error, /Access denied./) end end describe "not in local administrators group" do before :each do Puppet::Util::Windows::User.stubs(:check_token_membership).returns(false) end it "should not be an admin if user is running with elevated privileges" do Puppet::Util::Windows::Process.stubs(:elevated_security?).returns(true) expect(Puppet::Util::Windows::User).not_to be_admin end it "should not be an admin if user is not running with elevated privileges" do Puppet::Util::Windows::Process.stubs(:elevated_security?).returns(false) expect(Puppet::Util::Windows::User).not_to be_admin end end end describe "module function" do let(:username) { 'fabio' } let(:bad_password) { 'goldilocks' } let(:logon_fail_msg) { /Failed to logon user "fabio": Logon failure: unknown user name or bad password./ } def expect_logon_failure_error(&block) expect { yield }.to raise_error { |error| expect(error).to be_a(Puppet::Util::Windows::Error) # https://msdn.microsoft.com/en-us/library/windows/desktop/ms681385(v=vs.85).aspx # ERROR_LOGON_FAILURE 1326 expect(error.code).to eq(1326) } end describe "load_profile" do it "should raise an error when provided with an incorrect username and password" do expect_logon_failure_error { Puppet::Util::Windows::User.load_profile(username, bad_password) } end it "should raise an error when provided with an incorrect username and nil password" do expect_logon_failure_error { Puppet::Util::Windows::User.load_profile(username, nil) } end end describe "logon_user" do it "should raise an error when provided with an incorrect username and password" do expect_logon_failure_error { Puppet::Util::Windows::User.logon_user(username, bad_password) } end it "should raise an error when provided with an incorrect username and nil password" do expect_logon_failure_error { Puppet::Util::Windows::User.logon_user(username, nil) } end end describe "password_is?" do it "should return false given an incorrect username and password" do expect(Puppet::Util::Windows::User.password_is?(username, bad_password)).to be_falsey end it "should return false given a nil username and an incorrect password" do expect(Puppet::Util::Windows::User.password_is?(nil, bad_password)).to be_falsey end context "with a correct password" do it "should return true even if account restrictions are in place " do error = Puppet::Util::Windows::Error.new('', Puppet::Util::Windows::User::ERROR_ACCOUNT_RESTRICTION) Puppet::Util::Windows::User.stubs(:logon_user).raises(error) expect(Puppet::Util::Windows::User.password_is?(username, 'p@ssword')).to be(true) end it "should return true even for an account outside of logon hours" do error = Puppet::Util::Windows::Error.new('', Puppet::Util::Windows::User::ERROR_INVALID_LOGON_HOURS) Puppet::Util::Windows::User.stubs(:logon_user).raises(error) expect(Puppet::Util::Windows::User.password_is?(username, 'p@ssword')).to be(true) end it "should return true even for an account not allowed to log into this workstation" do error = Puppet::Util::Windows::Error.new('', Puppet::Util::Windows::User::ERROR_INVALID_WORKSTATION) Puppet::Util::Windows::User.stubs(:logon_user).raises(error) expect(Puppet::Util::Windows::User.password_is?(username, 'p@ssword')).to be(true) end it "should return true even for a disabled account" do error = Puppet::Util::Windows::Error.new('', Puppet::Util::Windows::User::ERROR_ACCOUNT_DISABLED) Puppet::Util::Windows::User.stubs(:logon_user).raises(error) expect(Puppet::Util::Windows::User.password_is?(username, 'p@ssword')).to be(true) end end end describe "check_token_membership" do it "should not raise an error" do # added just to call an FFI code path on all platforms expect { Puppet::Util::Windows::User.check_token_membership }.not_to raise_error end end end end puppet-5.5.10/spec/integration/util/autoload_spec.rb0000644005276200011600000000535313417161722022422 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/autoload' require 'fileutils' class AutoloadIntegrator @things = [] def self.newthing(name) @things << name end def self.thing?(name) @things.include? name end def self.clear @things.clear end end require 'puppet_spec/files' describe Puppet::Util::Autoload do include PuppetSpec::Files def with_file(name, *path) path = File.join(*path) # Now create a file to load File.open(path, "w") { |f| f.puts "\nAutoloadIntegrator.newthing(:#{name.to_s})\n" } yield File.delete(path) end def with_loader(name, path) dir = tmpfile(name + path) $LOAD_PATH << dir Dir.mkdir(dir) rbdir = File.join(dir, path.to_s) Dir.mkdir(rbdir) loader = Puppet::Util::Autoload.new(name, path) yield rbdir, loader Dir.rmdir(rbdir) Dir.rmdir(dir) $LOAD_PATH.pop AutoloadIntegrator.clear end it "should not fail when asked to load a missing file" do expect(Puppet::Util::Autoload.new("foo", "bar").load(:eh)).to be_falsey end it "should load and return true when it successfully loads a file" do with_loader("foo", "bar") { |dir,loader| with_file(:mything, dir, "mything.rb") { expect(loader.load(:mything)).to be_truthy expect(loader.class).to be_loaded("bar/mything") expect(AutoloadIntegrator).to be_thing(:mything) } } end it "should consider a file loaded when asked for the name without an extension" do with_loader("foo", "bar") { |dir,loader| with_file(:noext, dir, "noext.rb") { loader.load(:noext) expect(loader.class).to be_loaded("bar/noext") } } end it "should consider a file loaded when asked for the name with an extension" do with_loader("foo", "bar") { |dir,loader| with_file(:noext, dir, "withext.rb") { loader.load(:withext) expect(loader.class).to be_loaded("bar/withext.rb") } } end it "should be able to load files directly from modules" do ## modulepath can't be used until after app settings are initialized, so we need to simulate that: Puppet.settings.expects(:app_defaults_initialized?).returns(true).at_least_once modulepath = tmpfile("autoload_module_testing") libdir = File.join(modulepath, "mymod", "lib", "foo") FileUtils.mkdir_p(libdir) file = File.join(libdir, "plugin.rb") Puppet.override(:environments => Puppet::Environments::Static.new(Puppet::Node::Environment.create(:production, [modulepath]))) do with_loader("foo", "foo") do |dir, loader| with_file(:plugin, file.split("/")) do loader.load(:plugin) expect(loader.class).to be_loaded("foo/plugin.rb") end end end end end puppet-5.5.10/spec/integration/util/execution_spec.rb0000644005276200011600000000437413417161722022617 0ustar jenkinsjenkinsrequire 'spec_helper' describe Puppet::Util::Execution do include PuppetSpec::Files describe "#execpipe" do it "should set LANG to C avoid localized output", :if => !Puppet.features.microsoft_windows? do out = "" Puppet::Util::Execution.execpipe('echo $LANG'){ |line| out << line.read.chomp } expect(out).to eq("C") end it "should set LC_ALL to C avoid localized output", :if => !Puppet.features.microsoft_windows? do out = "" Puppet::Util::Execution.execpipe('echo $LC_ALL'){ |line| out << line.read.chomp } expect(out).to eq("C") end it "should raise an ExecutionFailure with a missing command and :failonfail set to true" do expect { failonfail = true # NOTE: critical to return l in the block for `output` in method to be # Puppet::Util::Execution.execpipe('conan_the_librarion', failonfail) { |l| l } }.to raise_error(Puppet::ExecutionFailure) end end describe "#execute (non-Windows)", :if => !Puppet.features.microsoft_windows? do it "should execute basic shell command" do result = Puppet::Util::Execution.execute("ls /tmp", :failonfail => true) expect(result.exitstatus).to eq(0) expect(result.to_s).to_not be_nil end end describe "#execute (Windows)", :if => Puppet.features.microsoft_windows? do let(:utf8text) do # Japanese Lorem Ipsum snippet "utf8testfile" + [227, 131, 171, 227, 131, 147, 227, 131, 179, 227, 131, 132, 227, 130, 162, 227, 130, 166, 227, 130, 167, 227, 131, 150, 227, 130, 162, 227, 129, 181, 227, 129, 185, 227, 129, 139, 227, 130, 137, 227, 129, 154, 227, 130, 187, 227, 130, 183, 227, 131, 147, 227, 131, 170, 227, 131, 134].pack('c*').force_encoding(Encoding::UTF_8) end let(:temputf8filename) do script_containing(utf8text, :windows => "@ECHO OFF\r\nECHO #{utf8text}\r\nEXIT 100") end it "should execute with non-english characters in command line" do result = Puppet::Util::Execution.execute("cmd /c \"#{temputf8filename}\"", :failonfail => false) expect(temputf8filename.encoding.name).to eq('UTF-8') expect(result.exitstatus).to eq(100) end end end puppet-5.5.10/spec/integration/util/settings_spec.rb0000644005276200011600000000512413417161722022446 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/files' describe Puppet::Settings do include PuppetSpec::Files def minimal_default_settings { :noop => {:default => false, :desc => "noop"} } end def define_settings(section, settings_hash) settings.define_settings(section, minimal_default_settings.update(settings_hash)) end let(:settings) { Puppet::Settings.new } it "should be able to make needed directories" do define_settings(:main, :maindir => { :default => tmpfile("main"), :type => :directory, :desc => "a", } ) settings.use(:main) expect(File.directory?(settings[:maindir])).to be_truthy end it "should make its directories with the correct modes" do Puppet[:manage_internal_file_permissions] = true define_settings(:main, :maindir => { :default => tmpfile("main"), :type => :directory, :desc => "a", :mode => 0750 } ) settings.use(:main) expect(Puppet::FileSystem.stat(settings[:maindir]).mode & 007777).to eq(0750) end it "will properly parse a UTF-8 configuration file" do rune_utf8 = "\u16A0\u16C7\u16BB" # ᚠᛇᚻ config = tmpfile("config") define_settings(:main, :config => { :type => :file, :default => config, :desc => "a" }, :environment => { :default => 'dingos', :desc => 'test', } ) File.open(config, 'w') do |file| file.puts <<-EOF [main] environment=#{rune_utf8} EOF end settings.initialize_global_settings expect(settings[:environment]).to eq(rune_utf8) end it "reparses configuration if configuration file is touched", :if => !Puppet.features.microsoft_windows? do config = tmpfile("config") define_settings(:main, :config => { :type => :file, :default => config, :desc => "a" }, :environment => { :default => 'dingos', :desc => 'test', } ) Puppet[:filetimeout] = '1s' File.open(config, 'w') do |file| file.puts <<-EOF [main] environment=toast EOF end settings.initialize_global_settings expect(settings[:environment]).to eq('toast') # First reparse establishes WatchedFiles settings.reparse_config_files sleep 1 File.open(config, 'w') do |file| file.puts <<-EOF [main] environment=bacon EOF end # Second reparse if later than filetimeout, reparses if changed settings.reparse_config_files expect(settings[:environment]).to eq('bacon') end end puppet-5.5.10/spec/integration/configurer_spec.rb0000644005276200011600000000434013417161722021773 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/configurer' describe Puppet::Configurer do include PuppetSpec::Files describe "when running" do before(:each) do @catalog = Puppet::Resource::Catalog.new("testing", Puppet.lookup(:environments).get(Puppet[:environment])) @catalog.add_resource(Puppet::Type.type(:notify).new(:title => "testing")) # Make sure we don't try to persist the local state after the transaction ran, # because it will fail during test (the state file is in a not-existing directory) # and we need the transaction to be successful to be able to produce a summary report @catalog.host_config = false @configurer = Puppet::Configurer.new end it "should send a transaction report with valid data" do @configurer.stubs(:save_last_run_summary) Puppet::Transaction::Report.indirection.expects(:save).with do |report, x| report.time.class == Time and report.logs.length > 0 end Puppet[:report] = true @configurer.run :catalog => @catalog end it "should save a correct last run summary" do report = Puppet::Transaction::Report.new Puppet::Transaction::Report.indirection.stubs(:save) Puppet[:lastrunfile] = tmpfile("lastrunfile") Puppet.settings.setting(:lastrunfile).mode = 0666 Puppet[:report] = true # We only record integer seconds in the timestamp, and truncate # backwards, so don't use a more accurate timestamp in the test. # --daniel 2011-03-07 t1 = Time.now.tv_sec @configurer.run :catalog => @catalog, :report => report t2 = Time.now.tv_sec # sticky bit only applies to directories in windows file_mode = Puppet.features.microsoft_windows? ? '666' : '100666' expect(Puppet::FileSystem.stat(Puppet[:lastrunfile]).mode.to_s(8)).to eq(file_mode) summary = nil File.open(Puppet[:lastrunfile], "r") do |fd| summary = YAML.load(fd.read) end expect(summary).to be_a(Hash) %w{time changes events resources}.each do |key| expect(summary).to be_key(key) end expect(summary["time"]).to be_key("notify") expect(summary["time"]["last_run"]).to be_between(t1, t2) end end end puppet-5.5.10/spec/integration/data_binding_spec.rb0000644005276200011600000001256513417161722022243 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/compiler' describe "Data binding" do include PuppetSpec::Files include PuppetSpec::Compiler let(:dir) { tmpdir("puppetdir") } let(:data) {{ 'global' => { 'testing::binding::value' => 'the value', 'testing::binding::calling_class' => '%{calling_class}', 'testing::binding::calling_class_path' => '%{calling_class_path}' } }} let(:hash_data) {{ 'global' => { 'testing::hash::options' => { 'key' => 'value', 'port' => '80', 'bind' => 'localhost' } }, 'agent.example.com' => { 'testing::hash::options' => { 'key' => 'new value', 'port' => '443' } } }} let(:hash_data_with_lopts) {{ 'global' => { 'testing::hash::options' => { 'key' => 'value', 'port' => '80', 'bind' => 'localhost' } }, 'agent.example.com' => { 'lookup_options' => { 'testing::hash::options' => { 'merge' => 'deep' } }, 'testing::hash::options' => { 'key' => 'new value', 'port' => '443' } } }} before do # Drop all occurances of cached hiera instances. This will reset @hiera in Puppet::Indirector::Hiera, Testing::DataBinding::Hiera, # and Puppet::DataBinding::Hiera. Different classes are active as indirection depending on configuration ObjectSpace.each_object(Class).select {|klass| klass <= Puppet::Indirector::Hiera }.each { |klass| klass.instance_variable_set(:@hiera, nil) } Puppet[:data_binding_terminus] = 'hiera' Puppet[:modulepath] = dir end context "with testing::binding and global data only" do it "looks up global data from hiera" do configure_hiera_for_one_tier(data) create_manifest_in_module("testing", "binding.pp", <<-MANIFEST) class testing::binding($value, $calling_class, $calling_class_path, $calling_module = $module_name) {} MANIFEST catalog = compile_to_catalog("include testing::binding") resource = catalog.resource('Class[testing::binding]') expect(resource[:value]).to eq("the value") expect(resource[:calling_class]).to eq("testing::binding") expect(resource[:calling_class_path]).to eq("testing/binding") expect(resource[:calling_module]).to eq("testing") end end context "with testing::hash and global data only" do it "looks up global data from hiera" do configure_hiera_for_one_tier(hash_data) create_manifest_in_module("testing", "hash.pp", <<-MANIFEST) class testing::hash($options) {} MANIFEST catalog = compile_to_catalog("include testing::hash") resource = catalog.resource('Class[testing::hash]') expect(resource[:options]).to eq({ 'key' => 'value', 'port' => '80', 'bind' => 'localhost' }) end end context "with custom clientcert" do it "merges global data with agent.example.com data from hiera" do configure_hiera_for_two_tier(hash_data) create_manifest_in_module("testing", "hash.pp", <<-MANIFEST) class testing::hash($options) {} MANIFEST catalog = compile_to_catalog("include testing::hash") resource = catalog.resource('Class[testing::hash]') expect(resource[:options]).to eq({ 'key' => 'new value', 'port' => '443', }) end end context "with custom clientcert and with lookup_options" do it "merges global data with agent.example.com data from hiera" do configure_hiera_for_two_tier(hash_data_with_lopts) create_manifest_in_module("testing", "hash.pp", <<-MANIFEST) class testing::hash($options) {} MANIFEST catalog = compile_to_catalog("include testing::hash") resource = catalog.resource('Class[testing::hash]') expect(resource[:options]).to eq({ 'key' => 'new value', 'port' => '443', 'bind' => 'localhost', }) end end def configure_hiera_for_one_tier(data) hiera_config_file = tmpfile("hiera.yaml") File.open(hiera_config_file, 'w:UTF-8') do |f| f.write("--- :yaml: :datadir: #{dir} :hierarchy: ['global'] :logger: 'noop' :backends: ['yaml'] ") end data.each do | file, contents | File.open(File.join(dir, "#{file}.yaml"), 'w:UTF-8') do |f| f.write(YAML.dump(contents)) end end Puppet[:hiera_config] = hiera_config_file end def configure_hiera_for_two_tier(data) hiera_config_file = tmpfile("hiera.yaml") File.open(hiera_config_file, 'w:UTF-8') do |f| f.write("--- :yaml: :datadir: #{dir} :hierarchy: ['agent.example.com', 'global'] :logger: 'noop' :backends: ['yaml'] ") end data.each do | file, contents | File.open(File.join(dir, "#{file}.yaml"), 'w:UTF-8') do |f| f.write(YAML.dump(contents)) end end Puppet[:hiera_config] = hiera_config_file end def create_manifest_in_module(module_name, name, manifest) module_dir = File.join(dir, module_name, 'manifests') FileUtils.mkdir_p(module_dir) File.open(File.join(module_dir, name), 'w:UTF-8') do |f| f.write(manifest) end end end puppet-5.5.10/spec/integration/defaults_spec.rb0000644005276200011600000002254713417161722021450 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/defaults' describe "Puppet defaults" do describe "when default_manifest is set" do it "returns ./manifests by default" do expect(Puppet[:default_manifest]).to eq('./manifests') end end describe "when disable_per_environment_manifest is set" do it "returns false by default" do expect(Puppet[:disable_per_environment_manifest]).to eq(false) end it "errors when set to true and default_manifest is not an absolute path" do expect { Puppet[:default_manifest] = './some/relative/manifest.pp' Puppet[:disable_per_environment_manifest] = true }.to raise_error Puppet::Settings::ValidationError, /'default_manifest' setting must be.*absolute/ end end describe "when setting the :factpath" do it "should add the :factpath to Facter's search paths" do Facter.expects(:search).with("/my/fact/path") Puppet.settings[:factpath] = "/my/fact/path" end end describe "when setting the :certname" do it "should fail if the certname is not downcased" do expect { Puppet.settings[:certname] = "Host.Domain.Com" }.to raise_error(ArgumentError) end end describe "when setting :node_name_value" do it "should default to the value of :certname" do Puppet.settings[:certname] = 'blargle' expect(Puppet.settings[:node_name_value]).to eq('blargle') end end describe "when setting the :node_name_fact" do it "should fail when also setting :node_name_value" do expect do Puppet.settings[:node_name_value] = "some value" Puppet.settings[:node_name_fact] = "some_fact" end.to raise_error("Cannot specify both the node_name_value and node_name_fact settings") end it "should not fail when using the default for :node_name_value" do expect do Puppet.settings[:node_name_fact] = "some_fact" end.not_to raise_error end end it "should have a clientyamldir setting" do expect(Puppet.settings[:clientyamldir]).not_to be_nil end it "should have different values for the yamldir and clientyamldir" do expect(Puppet.settings[:yamldir]).not_to eq(Puppet.settings[:clientyamldir]) end it "should have a client_datadir setting" do expect(Puppet.settings[:client_datadir]).not_to be_nil end it "should have different values for the server_datadir and client_datadir" do expect(Puppet.settings[:server_datadir]).not_to eq(Puppet.settings[:client_datadir]) end # See #1232 it "should not specify a user or group for the clientyamldir" do expect(Puppet.settings.setting(:clientyamldir).owner).to be_nil expect(Puppet.settings.setting(:clientyamldir).group).to be_nil end it "should use the service user and group for the yamldir" do Puppet.settings.stubs(:service_user_available?).returns true Puppet.settings.stubs(:service_group_available?).returns true expect(Puppet.settings.setting(:yamldir).owner).to eq(Puppet.settings[:user]) expect(Puppet.settings.setting(:yamldir).group).to eq(Puppet.settings[:group]) end it "should specify that the host private key should be owned by the service user" do Puppet.settings.stubs(:service_user_available?).returns true expect(Puppet.settings.setting(:hostprivkey).owner).to eq(Puppet.settings[:user]) end it "should specify that the host certificate should be owned by the service user" do Puppet.settings.stubs(:service_user_available?).returns true expect(Puppet.settings.setting(:hostcert).owner).to eq(Puppet.settings[:user]) end [:modulepath, :factpath].each do |setting| it "should configure '#{setting}' not to be a file setting, so multi-directory settings are acceptable" do expect(Puppet.settings.setting(setting)).to be_instance_of(Puppet::Settings::PathSetting) end end describe "on a Unix-like platform it", :if => Puppet.features.posix? do it "should add /usr/sbin and /sbin to the path if they're not there" do Puppet::Util.withenv("PATH" => "/usr/bin#{File::PATH_SEPARATOR}/usr/local/bin") do Puppet.settings[:path] = "none" # this causes it to ignore the setting expect(ENV["PATH"].split(File::PATH_SEPARATOR)).to be_include("/usr/sbin") expect(ENV["PATH"].split(File::PATH_SEPARATOR)).to be_include("/sbin") end end end describe "on a Windows-like platform it", :if => Puppet.features.microsoft_windows? do let (:rune_utf8) { "\u16A0\u16C7\u16BB\u16EB\u16D2\u16E6\u16A6\u16EB\u16A0\u16B1\u16A9\u16A0\u16A2\u16B1\u16EB\u16A0\u16C1\u16B1\u16AA\u16EB\u16B7\u16D6\u16BB\u16B9\u16E6\u16DA\u16B3\u16A2\u16D7" } it "path should not add anything" do path = "c:\\windows\\system32#{File::PATH_SEPARATOR}c:\\windows" Puppet::Util.withenv( {"PATH" => path }, :windows ) do Puppet.settings[:path] = "none" # this causes it to ignore the setting expect(ENV["PATH"]).to eq(path) end end it "path should support UTF8 characters" do path = "c:\\windows\\system32#{File::PATH_SEPARATOR}c:\\windows#{File::PATH_SEPARATOR}C:\\" + rune_utf8 Puppet::Util.withenv( {"PATH" => path }, :windows) do Puppet.settings[:path] = "none" # this causes it to ignore the setting envhash = Puppet::Util::Windows::Process.get_environment_strings expect(envhash['Path']).to eq(path) end end end it "should default to json for the preferred serialization format" do expect(Puppet.settings.value(:preferred_serialization_format)).to eq("json") end it "should have a setting for determining the configuration version and should default to an empty string" do expect(Puppet.settings[:config_version]).to eq("") end describe "when enabling reports" do it "should use the default server value when report server is unspecified" do Puppet.settings[:server] = "server" expect(Puppet.settings[:report_server]).to eq("server") end it "should use the default masterport value when report port is unspecified" do Puppet.settings[:masterport] = "1234" expect(Puppet.settings[:report_port]).to eq("1234") end it "should use report_port when set" do Puppet.settings[:masterport] = "1234" Puppet.settings[:report_port] = "5678" expect(Puppet.settings[:report_port]).to eq("5678") end end it "should have a :caname setting that defaults to the cert name" do Puppet.settings[:certname] = "foo" expect(Puppet.settings[:ca_name]).to eq("Puppet CA: foo") end it "should have a 'prerun_command' that defaults to the empty string" do expect(Puppet.settings[:prerun_command]).to eq("") end it "should have a 'postrun_command' that defaults to the empty string" do expect(Puppet.settings[:postrun_command]).to eq("") end it "should have a 'certificate_revocation' setting that defaults to true" do expect(Puppet.settings[:certificate_revocation]).to be_truthy end describe "reportdir" do subject { Puppet.settings[:reportdir] } it { is_expected.to eq("#{Puppet[:vardir]}/reports") } end describe "reporturl" do subject { Puppet.settings[:reporturl] } it { is_expected.to eq("http://localhost:3000/reports/upload") } end describe "when configuring color" do subject { Puppet.settings[:color] } it { is_expected.to eq("ansi") } end describe "daemonize" do it "should default to true", :unless => Puppet.features.microsoft_windows? do expect(Puppet.settings[:daemonize]).to eq(true) end describe "on Windows", :if => Puppet.features.microsoft_windows? do it "should default to false" do expect(Puppet.settings[:daemonize]).to eq(false) end it "should raise an error if set to true" do expect { Puppet.settings[:daemonize] = true }.to raise_error(/Cannot daemonize on Windows/) end end end describe "diff" do it "should default to 'diff' on POSIX", :unless => Puppet.features.microsoft_windows? do expect(Puppet.settings[:diff]).to eq('diff') end it "should default to '' on Windows", :if => Puppet.features.microsoft_windows? do expect(Puppet.settings[:diff]).to eq('') end end describe "when configuring hiera" do it "should have a hiera_config setting" do expect(Puppet.settings[:hiera_config]).not_to be_nil end end describe "when configuring the data_binding terminus" do it "should have a data_binding_terminus setting" do expect(Puppet.settings[:data_binding_terminus]).not_to be_nil end it "should be set to hiera by default" do expect(Puppet.settings[:data_binding_terminus]).to eq(:hiera) end it "to be neither 'hiera' nor 'none', a deprecation warning is logged" do expect(@logs).to eql([]) Puppet[:data_binding_terminus] = 'magic' expect(@logs[0].to_s).to match(/Setting 'data_binding_terminus' is deprecated/) end it "to not log a warning if set to 'none' or 'hiera'" do expect(@logs).to eql([]) Puppet[:data_binding_terminus] = 'none' Puppet[:data_binding_terminus] = 'hiera' expect(@logs).to eql([]) end end describe "agent_catalog_run_lockfile" do it "(#2888) is not a file setting so it is absent from the Settings catalog" do expect(Puppet.settings.setting(:agent_catalog_run_lockfile)).not_to be_a_kind_of Puppet::Settings::FileSetting expect(Puppet.settings.setting(:agent_catalog_run_lockfile)).to be_a Puppet::Settings::StringSetting end end end puppet-5.5.10/spec/integration/node_spec.rb0000644005276200011600000000512113417161722020553 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/node' describe Puppet::Node do describe "when delegating indirection calls" do before do Puppet::Node.indirection.reset_terminus_class Puppet::Node.indirection.cache_class = nil @name = "me" @node = Puppet::Node.new(@name) end it "should be able to use the yaml terminus" do Puppet::Node.indirection.stubs(:terminus_class).returns :yaml # Load now, before we stub the exists? method. terminus = Puppet::Node.indirection.terminus(:yaml) terminus.expects(:path).with(@name).returns "/my/yaml/file" Puppet::FileSystem.expects(:exist?).with("/my/yaml/file").returns false expect(Puppet::Node.indirection.find(@name)).to be_nil end it "should have an ldap terminus" do expect(Puppet::Node.indirection.terminus(:ldap)).not_to be_nil end it "should be able to use the plain terminus" do Puppet::Node.indirection.stubs(:terminus_class).returns :plain # Load now, before we stub the exists? method. Puppet::Node.indirection.terminus(:plain) Puppet::Node.expects(:new).with(@name).returns @node expect(Puppet::Node.indirection.find(@name)).to equal(@node) end describe "and using the memory terminus" do before do @name = "me" @terminus = Puppet::Node.indirection.terminus(:memory) Puppet::Node.indirection.stubs(:terminus).returns @terminus @node = Puppet::Node.new(@name) end after do @terminus.instance_variable_set(:@instances, {}) end it "should find no nodes by default" do expect(Puppet::Node.indirection.find(@name)).to be_nil end it "should be able to find nodes that were previously saved" do Puppet::Node.indirection.save(@node) expect(Puppet::Node.indirection.find(@name)).to equal(@node) end it "should replace existing saved nodes when a new node with the same name is saved" do Puppet::Node.indirection.save(@node) two = Puppet::Node.new(@name) Puppet::Node.indirection.save(two) expect(Puppet::Node.indirection.find(@name)).to equal(two) end it "should be able to remove previously saved nodes" do Puppet::Node.indirection.save(@node) Puppet::Node.indirection.destroy(@node.name) expect(Puppet::Node.indirection.find(@name)).to be_nil end it "should fail when asked to destroy a node that does not exist" do expect { Puppet::Node.indirection.destroy(@node) }.to raise_error(ArgumentError) end end end end puppet-5.5.10/spec/integration/transaction_spec.rb0000644005276200011600000004071113417161722022157 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/compiler' require 'puppet/transaction' describe Puppet::Transaction do include PuppetSpec::Files include PuppetSpec::Compiler before do Puppet::Util::Storage.stubs(:store) end def mk_catalog(*resources) catalog = Puppet::Resource::Catalog.new(Puppet::Node.new("mynode")) resources.each { |res| catalog.add_resource res } catalog end def touch_path Puppet.features.microsoft_windows? ? "#{ENV['windir']}\\system32" : "/usr/bin:/bin" end def usr_bin_touch(path) Puppet.features.microsoft_windows? ? "#{ENV['windir']}\\system32\\cmd.exe /c \"type NUL >> \"#{path}\"\"" : "/usr/bin/touch #{path}" end def touch(path) Puppet.features.microsoft_windows? ? "cmd.exe /c \"type NUL >> \"#{path}\"\"" : "touch #{path}" end it "should not apply generated resources if the parent resource fails" do catalog = Puppet::Resource::Catalog.new resource = Puppet::Type.type(:file).new :path => make_absolute("/foo/bar"), :backup => false catalog.add_resource resource child_resource = Puppet::Type.type(:file).new :path => make_absolute("/foo/bar/baz"), :backup => false resource.expects(:eval_generate).returns([child_resource]) transaction = Puppet::Transaction.new(catalog, nil, Puppet::Graph::RandomPrioritizer.new) resource.expects(:retrieve).raises "this is a failure" resource.stubs(:err) child_resource.expects(:retrieve).never transaction.evaluate end it "should not apply virtual resources" do catalog = Puppet::Resource::Catalog.new resource = Puppet::Type.type(:file).new :path => make_absolute("/foo/bar"), :backup => false resource.virtual = true catalog.add_resource resource transaction = Puppet::Transaction.new(catalog, nil, Puppet::Graph::RandomPrioritizer.new) resource.expects(:evaluate).never transaction.evaluate end it "should apply exported resources" do catalog = Puppet::Resource::Catalog.new path = tmpfile("exported_files") resource = Puppet::Type.type(:file).new :path => path, :backup => false, :ensure => :file resource.exported = true catalog.add_resource resource catalog.apply expect(Puppet::FileSystem.exist?(path)).to be_truthy end it "should not apply virtual exported resources" do catalog = Puppet::Resource::Catalog.new resource = Puppet::Type.type(:file).new :path => make_absolute("/foo/bar"), :backup => false resource.exported = true resource.virtual = true catalog.add_resource resource transaction = Puppet::Transaction.new(catalog, nil, Puppet::Graph::RandomPrioritizer.new) resource.expects(:evaluate).never transaction.evaluate end it "should not apply device resources on normal host" do catalog = Puppet::Resource::Catalog.new resource = Puppet::Type.type(:interface).new :name => "FastEthernet 0/1" catalog.add_resource resource transaction = Puppet::Transaction.new(catalog, nil, Puppet::Graph::RandomPrioritizer.new) transaction.for_network_device = false transaction.expects(:apply).never.with(resource, nil) transaction.evaluate expect(transaction.resource_status(resource)).to be_skipped end it "should not apply host resources on device" do catalog = Puppet::Resource::Catalog.new resource = Puppet::Type.type(:file).new :path => make_absolute("/foo/bar"), :backup => false catalog.add_resource resource transaction = Puppet::Transaction.new(catalog, nil, Puppet::Graph::RandomPrioritizer.new) transaction.for_network_device = true transaction.expects(:apply).never.with(resource, nil) transaction.evaluate expect(transaction.resource_status(resource)).to be_skipped end it "should apply device resources on device" do catalog = Puppet::Resource::Catalog.new resource = Puppet::Type.type(:interface).new :name => "FastEthernet 0/1" catalog.add_resource resource transaction = Puppet::Transaction.new(catalog, nil, Puppet::Graph::RandomPrioritizer.new) transaction.for_network_device = true transaction.expects(:apply).with(resource, nil) transaction.evaluate expect(transaction.resource_status(resource)).not_to be_skipped end it "should apply resources appliable on host and device on a device" do catalog = Puppet::Resource::Catalog.new resource = Puppet::Type.type(:schedule).new :name => "test" catalog.add_resource resource transaction = Puppet::Transaction.new(catalog, nil, Puppet::Graph::RandomPrioritizer.new) transaction.for_network_device = true transaction.expects(:apply).with(resource, nil) transaction.evaluate expect(transaction.resource_status(resource)).not_to be_skipped end # Verify that one component requiring another causes the contained # resources in the requiring component to get refreshed. it "should propagate events from a contained resource through its container to its dependent container's contained resources" do file = Puppet::Type.type(:file).new :path => tmpfile("event_propagation"), :ensure => :present execfile = File.join(tmpdir("exec_event"), "exectestingness2") exec = Puppet::Type.type(:exec).new :command => touch(execfile), :path => ENV['PATH'] catalog = mk_catalog(file) fcomp = Puppet::Type.type(:component).new(:name => "Foo[file]") catalog.add_resource fcomp catalog.add_edge(fcomp, file) ecomp = Puppet::Type.type(:component).new(:name => "Foo[exec]") catalog.add_resource ecomp catalog.add_resource exec catalog.add_edge(ecomp, exec) ecomp[:subscribe] = Puppet::Resource.new(:foo, "file") exec[:refreshonly] = true exec.expects(:refresh) catalog.apply end # Make sure that multiple subscriptions get triggered. it "should propagate events to all dependent resources" do path = tmpfile("path") file1 = tmpfile("file1") file2 = tmpfile("file2") file = Puppet::Type.type(:file).new( :path => path, :ensure => "file" ) exec1 = Puppet::Type.type(:exec).new( :path => ENV["PATH"], :command => touch(file1), :refreshonly => true, :subscribe => Puppet::Resource.new(:file, path) ) exec2 = Puppet::Type.type(:exec).new( :path => ENV["PATH"], :command => touch(file2), :refreshonly => true, :subscribe => Puppet::Resource.new(:file, path) ) catalog = mk_catalog(file, exec1, exec2) catalog.apply expect(Puppet::FileSystem.exist?(file1)).to be_truthy expect(Puppet::FileSystem.exist?(file2)).to be_truthy end it "does not refresh resources that have 'noop => true'" do path = tmpfile("path") notify = Puppet::Type.type(:notify).new( :name => "trigger", :notify => Puppet::Resource.new(:exec, "noop exec") ) noop_exec = Puppet::Type.type(:exec).new( :name => "noop exec", :path => ENV["PATH"], :command => touch(path), :noop => true ) catalog = mk_catalog(notify, noop_exec) catalog.apply expect(Puppet::FileSystem.exist?(path)).to be_falsey end it "should apply no resources whatsoever if a pre_run_check fails" do path = tmpfile("path") file = Puppet::Type.type(:file).new( :path => path, :ensure => "file" ) notify = Puppet::Type.type(:notify).new( :title => "foo" ) notify.expects(:pre_run_check).raises(Puppet::Error, "fail for testing") catalog = mk_catalog(file, notify) expect { catalog.apply }.to raise_error(Puppet::Error, /Some pre-run checks failed/) expect(Puppet::FileSystem.exist?(path)).not_to be_truthy end it "failed refresh should result in dependent refreshes being skipped" do path = tmpfile("path") newfile = tmpfile("file") file = Puppet::Type.type(:file).new( :path => path, :ensure => "file" ) exec1 = Puppet::Type.type(:exec).new( :path => ENV["PATH"], :command => touch(File.expand_path("/this/cannot/possibly/exist")), :logoutput => true, :refreshonly => true, :subscribe => file, :title => "one" ) exec2 = Puppet::Type.type(:exec).new( :path => ENV["PATH"], :command => touch(newfile), :logoutput => true, :refreshonly => true, :subscribe => [file, exec1], :title => "two" ) exec1.stubs(:err) catalog = mk_catalog(file, exec1, exec2) catalog.apply expect(Puppet::FileSystem.exist?(newfile)).to be_falsey end # Ensure when resources have been generated with eval_generate that event # propagation still works when filtering with tags context "when filtering with tags" do context "when resources are dependent on dynamically generated resources" do it "should trigger (only) appropriately tagged dependent resources" do source = dir_containing('sourcedir', {'foo' => 'bar'}) target = tmpdir('targetdir') file1 = tmpfile("file1") file2 = tmpfile("file2") file = Puppet::Type.type(:file).new( :path => target, :source => source, :ensure => :present, :recurse => true, :tag => "foo_tag", ) exec1 = Puppet::Type.type(:exec).new( :path => ENV["PATH"], :command => touch(file1), :refreshonly => true, :subscribe => file, :tag => "foo_tag", ) exec2 = Puppet::Type.type(:exec).new( :path => ENV["PATH"], :command => touch(file2), :refreshonly => true, :subscribe => file, ) Puppet[:tags] = "foo_tag" catalog = mk_catalog(file, exec1, exec2) catalog.apply expect(Puppet::FileSystem.exist?(file1)).to be_truthy expect(Puppet::FileSystem.exist?(file2)).to be_falsey end it "should trigger implicitly tagged dependent resources, ie via type name" do file1 = tmpfile("file1") file2 = tmpfile("file2") exec1 = Puppet::Type.type(:exec).new( :name => "exec1", :path => ENV["PATH"], :command => touch(file1), ) exec1.stubs(:eval_generate).returns( [ (Puppet::Type.type(:notify).new :name => "eval1_notify")] ) exec2 = Puppet::Type.type(:exec).new( :name => "exec2", :path => ENV["PATH"], :command => touch(file2), :refreshonly => true, :subscribe => exec1, ) exec2.stubs(:eval_generate).returns( [ (Puppet::Type.type(:notify).new :name => "eval2_notify")] ) Puppet[:tags] = "exec" catalog = mk_catalog(exec1, exec2) catalog.apply expect(Puppet::FileSystem.exist?(file1)).to be_truthy expect(Puppet::FileSystem.exist?(file2)).to be_truthy end end it "should propagate events correctly from a tagged container when running with tags" do file1 = tmpfile("original_tag") file2 = tmpfile("tag_propagation") command1 = usr_bin_touch(file1) command2 = usr_bin_touch(file2) manifest = <<-"MANIFEST" class foo { exec { 'notify test': command => '#{command1}', refreshonly => true, } } class test { include foo exec { 'test': command => '#{command2}', notify => Class['foo'], } } include test MANIFEST Puppet[:tags] = 'test' apply_compiled_manifest(manifest) expect(Puppet::FileSystem.exist?(file1)).to be_truthy expect(Puppet::FileSystem.exist?(file2)).to be_truthy end end describe "skipping resources" do let(:fname) { tmpfile("exec") } let(:file) do Puppet::Type.type(:file).new( :name => tmpfile("file"), :ensure => "file", :backup => false ) end let(:exec) do Puppet::Type.type(:exec).new( :name => touch(fname), :path => touch_path, :subscribe => Puppet::Resource.new("file", file.name) ) end it "does not trigger unscheduled resources" do catalog = mk_catalog catalog.add_resource(*Puppet::Type.type(:schedule).mkdefaultschedules) Puppet[:ignoreschedules] = false exec[:schedule] = "monthly" catalog.add_resource(file, exec) # Run it once so further runs don't schedule the resource catalog.apply expect(Puppet::FileSystem.exist?(fname)).to be_truthy # Now remove it, so it can get created again Puppet::FileSystem.unlink(fname) file[:content] = "some content" catalog.apply expect(Puppet::FileSystem.exist?(fname)).to be_falsey end it "does not trigger untagged resources" do catalog = mk_catalog Puppet[:tags] = "runonly" file.tag("runonly") catalog.add_resource(file, exec) catalog.apply expect(Puppet::FileSystem.exist?(fname)).to be_falsey end it "does not trigger skip-tagged resources" do catalog = mk_catalog Puppet[:skip_tags] = "skipme" exec.tag("skipme") catalog.add_resource(file, exec) catalog.apply expect(Puppet::FileSystem.exist?(fname)).to be_falsey end it "does not trigger resources with failed dependencies" do catalog = mk_catalog file[:path] = make_absolute("/foo/bar/baz") catalog.add_resource(file, exec) catalog.apply expect(Puppet::FileSystem.exist?(fname)).to be_falsey end end it "should not attempt to evaluate resources with failed dependencies" do exec = Puppet::Type.type(:exec).new( :command => "#{File.expand_path('/bin/mkdir')} /this/path/cannot/possibly/exist", :title => "mkdir" ) file1 = Puppet::Type.type(:file).new( :title => "file1", :path => tmpfile("file1"), :require => exec, :ensure => :file ) file2 = Puppet::Type.type(:file).new( :title => "file2", :path => tmpfile("file2"), :require => file1, :ensure => :file ) catalog = mk_catalog(exec, file1, file2) transaction = catalog.apply expect(Puppet::FileSystem.exist?(file1[:path])).to be_falsey expect(Puppet::FileSystem.exist?(file2[:path])).to be_falsey expect(transaction.resource_status(file1).skipped).to be_truthy expect(transaction.resource_status(file2).skipped).to be_truthy expect(transaction.resource_status(file1).failed_dependencies).to eq([exec]) expect(transaction.resource_status(file2).failed_dependencies).to eq([exec]) end it "on failure, skips dynamically-generated dependents" do exec = Puppet::Type.type(:exec).new( :command => "#{File.expand_path('/bin/mkdir')} /this/path/cannot/possibly/exist", :title => "mkdir" ) tmp = tmpfile("dir1") FileUtils.mkdir_p(tmp) FileUtils.mkdir_p(File.join(tmp, "foo")) purge_dir = Puppet::Type.type(:file).new( :title => "dir1", :path => tmp, :require => exec, :ensure => :directory, :recurse => true, :purge => true ) catalog = mk_catalog(exec, purge_dir) txn = catalog.apply expect(txn.resource_status(purge_dir).skipped).to be_truthy children = catalog.relationship_graph.direct_dependents_of(purge_dir) children.each do |child| expect(txn.resource_status(child).skipped).to be_truthy end expect(Puppet::FileSystem.exist?(File.join(tmp, "foo"))).to be_truthy end it "should not trigger subscribing resources on failure" do file1 = tmpfile("file1") file2 = tmpfile("file2") create_file1 = Puppet::Type.type(:exec).new( :command => usr_bin_touch(file1) ) exec = Puppet::Type.type(:exec).new( :command => "#{File.expand_path('/bin/mkdir')} /this/path/cannot/possibly/exist", :title => "mkdir", :notify => create_file1 ) create_file2 = Puppet::Type.type(:exec).new( :command => usr_bin_touch(file2), :subscribe => exec ) catalog = mk_catalog(exec, create_file1, create_file2) catalog.apply expect(Puppet::FileSystem.exist?(file1)).to be_falsey expect(Puppet::FileSystem.exist?(file2)).to be_falsey end # #801 -- resources only checked in noop should be rescheduled immediately. it "should immediately reschedule noop resources" do Puppet::Type.type(:schedule).mkdefaultschedules resource = Puppet::Type.type(:notify).new(:name => "mymessage", :noop => true) catalog = Puppet::Resource::Catalog.new catalog.add_resource resource trans = catalog.apply expect(trans.resource_harness).to be_scheduled(resource) end end puppet-5.5.10/spec/integration/util_spec.rb0000644005276200011600000001166013417161722020610 0ustar jenkinsjenkins#!/usr/bin/env ruby require 'spec_helper' describe Puppet::Util do include PuppetSpec::Files describe "#execute" do it "should properly allow stdout and stderr to share a file" do command = "ruby -e '(1..10).each {|i| (i%2==0) ? $stdout.puts(i) : $stderr.puts(i)}'" expect(Puppet::Util::Execution.execute(command, :combine => true).split).to match_array([*'1'..'10']) end it "should return output and set $CHILD_STATUS" do command = "ruby -e 'puts \"foo\"; exit 42'" output = Puppet::Util::Execution.execute(command, {:failonfail => false}) expect(output).to eq("foo\n") expect($CHILD_STATUS.exitstatus).to eq(42) end it "should raise an error if non-zero exit status is returned" do command = "ruby -e 'exit 43'" expect { Puppet::Util::Execution.execute(command) }.to raise_error(Puppet::ExecutionFailure, /Execution of '#{command}' returned 43: /) expect($CHILD_STATUS.exitstatus).to eq(43) end end describe "#replace_file on Windows", :if => Puppet.features.microsoft_windows? do it "replace_file should preserve original ACEs from existing replaced file on Windows" do file = tmpfile("somefile") FileUtils.touch(file) admins = 'S-1-5-32-544' dacl = Puppet::Util::Windows::AccessControlList.new dacl.allow(admins, Puppet::Util::Windows::File::FILE_ALL_ACCESS) protect = true expected_sd = Puppet::Util::Windows::SecurityDescriptor.new(admins, admins, dacl, protect) Puppet::Util::Windows::Security.set_security_descriptor(file, expected_sd) ignored_mode = 0644 Puppet::Util.replace_file(file, ignored_mode) do |temp_file| ignored_sd = Puppet::Util::Windows::Security.get_security_descriptor(temp_file.path) users = 'S-1-5-11' ignored_sd.dacl.allow(users, Puppet::Util::Windows::File::FILE_GENERIC_READ) Puppet::Util::Windows::Security.set_security_descriptor(temp_file.path, ignored_sd) end replaced_sd = Puppet::Util::Windows::Security.get_security_descriptor(file) expect(replaced_sd.dacl).to eq(expected_sd.dacl) end it "replace_file should use reasonable default ACEs on a new file on Windows" do dir = tmpdir('DACL_playground') protected_sd = Puppet::Util::Windows::Security.get_security_descriptor(dir) protected_sd.protect = true Puppet::Util::Windows::Security.set_security_descriptor(dir, protected_sd) sibling_path = File.join(dir, 'sibling_file') FileUtils.touch(sibling_path) expected_sd = Puppet::Util::Windows::Security.get_security_descriptor(sibling_path) new_file_path = File.join(dir, 'new_file') ignored_mode = nil Puppet::Util.replace_file(new_file_path, ignored_mode) { |tmp_file| } new_sd = Puppet::Util::Windows::Security.get_security_descriptor(new_file_path) expect(new_sd.dacl).to eq(expected_sd.dacl) end it "replace_file should work with filenames that include - and . (PUP-1389)" do expected_content = 'some content' dir = tmpdir('ReplaceFile_playground') destination_file = File.join(dir, 'some-file.xml') Puppet::Util.replace_file(destination_file, nil) do |temp_file| temp_file.open temp_file.write(expected_content) end actual_content = File.read(destination_file) expect(actual_content).to eq(expected_content) end it "replace_file should work with filenames that include special characters (PUP-1389)" do expected_content = 'some content' dir = tmpdir('ReplaceFile_playground') # http://www.fileformat.info/info/unicode/char/00e8/index.htm # dest_name = "somèfile.xml" dest_name = "som\u00E8file.xml" destination_file = File.join(dir, dest_name) Puppet::Util.replace_file(destination_file, nil) do |temp_file| temp_file.open temp_file.write(expected_content) end actual_content = File.read(destination_file) expect(actual_content).to eq(expected_content) end end describe "#which on Windows", :if => Puppet.features.microsoft_windows? do let (:rune_utf8) { "\u16A0\u16C7\u16BB\u16EB\u16D2\u16E6\u16A6\u16EB\u16A0\u16B1\u16A9\u16A0\u16A2\u16B1\u16EB\u16A0\u16C1\u16B1\u16AA\u16EB\u16B7\u16D6\u16BB\u16B9\u16E6\u16DA\u16B3\u16A2\u16D7" } let (:filename) { 'foo.exe' } let (:filepath) { File.expand_path('C:\\' + rune_utf8 + '\\' + filename) } before :each do FileTest.stubs(:file?).returns false FileTest.stubs(:file?).with(filepath).returns true FileTest.stubs(:executable?).returns false FileTest.stubs(:executable?).with(filepath).returns true end it "should be able to use UTF8 characters in the path" do path = "C:\\" + rune_utf8 + "#{File::PATH_SEPARATOR}c:\\windows\\system32#{File::PATH_SEPARATOR}c:\\windows" Puppet::Util.withenv( { "PATH" => path } , :windows) do expect(Puppet::Util.which(filename)).to eq(filepath) end end end end puppet-5.5.10/spec/lib/0000755005276200011600000000000013417162176014517 5ustar jenkinsjenkinspuppet-5.5.10/spec/lib/matchers/0000755005276200011600000000000013417162176016325 5ustar jenkinsjenkinspuppet-5.5.10/spec/lib/matchers/containment_matchers.rb0000644005276200011600000000241713417161721023056 0ustar jenkinsjenkinsmodule ContainmentMatchers class ContainClass def initialize(containee) @containee = containee end def in(container) @container = container self end def matches?(catalog) @catalog = catalog raise ArgumentError, "You must set the container using #in" unless @container @container_resource = catalog.resource("Class", @container) @containee_resource = catalog.resource("Class", @containee) if @containee_resource && @container_resource catalog.edge?(@container_resource, @containee_resource) else false end end def failure_message message = "Expected #{@catalog.to_dot} to contain Class #{@containee.inspect} inside of Class #{@container.inspect} but " missing = [] if @container_resource.nil? missing << @container end if @containee_resource.nil? missing << @containee end if ! missing.empty? message << "the catalog does not contain #{missing.map(&:inspect).join(' or ')}" else message << "no containment relationship exists" end message end end # expect(catalog).to contain_class(containee).in(container) def contain_class(containee) ContainClass.new(containee) end end puppet-5.5.10/spec/lib/matchers/include.rb0000644005276200011600000000134113417161721020267 0ustar jenkinsjenkinsmodule Matchers; module Include extend RSpec::Matchers::DSL matcher :include_in_any_order do |*matchers| match do |enumerable| @not_matched = [] expected_as_array.each do |matcher| if enumerable.empty? break end if found = enumerable.find { |elem| matcher.matches?(elem) } enumerable = enumerable.reject { |elem| elem == found } else @not_matched << matcher end end @not_matched.empty? && enumerable.empty? end failure_message do |enumerable| "did not match #{@not_matched.collect(&:description).join(', ')} in #{enumerable.inspect}: <#{@not_matched.collect(&:failure_message).join('>, <')}>" end end end; end puppet-5.5.10/spec/lib/matchers/include_in_order.rb0000644005276200011600000000066513417161721022160 0ustar jenkinsjenkinsRSpec::Matchers.define :include_in_order do |*expected| match do |actual| elements = expected.dup actual.each do |elt| if elt == elements.first elements.shift end end elements.empty? end def failure_message "expected #{@actual.inspect} to include#{expected} in order" end def failure_message_when_negated "expected #{@actual.inspect} not to include#{expected} in order" end end puppet-5.5.10/spec/lib/matchers/include_in_order_spec.rb0000644005276200011600000000141113417161721023160 0ustar jenkinsjenkinsrequire 'spec_helper' require 'matchers/include_in_order' describe "Matching whether elements are included in order" do context "an empty array" do it "is included in an empty array" do expect([]).to include_in_order() end it "is included in a non-empty array" do expect([1]).to include_in_order() end end it "[1,2,3] is included in [0,1,2,3,4]" do expect([0,1,2,3,4]).to include_in_order(1,2,3) end it "[2,1] is not included in order in [1,2]" do expect([1,2]).not_to include_in_order(2,1) end it "[2,4,6] is included in order in [1,2,3,4,5,6]" do expect([1,2,3,4,5,6]).to include_in_order(2,4,6) end it "overlapping ordered array is not included" do expect([1,2,3]).not_to include_in_order(2,3,4) end end puppet-5.5.10/spec/lib/matchers/include_spec.rb0000644005276200011600000000163713417161721021311 0ustar jenkinsjenkinsrequire 'spec_helper' require 'matchers/include' describe "include matchers" do include Matchers::Include context :include_in_any_order do it "matches an empty list" do expect([]).to include_in_any_order() end it "matches a list with a single element" do expect([1]).to include_in_any_order(eq(1)) end it "does not match when an expected element is missing" do expect([1]).to_not include_in_any_order(eq(2)) end it "matches a list with 2 elements in a different order from the expectation" do expect([1, 2]).to include_in_any_order(eq(2), eq(1)) end it "does not match when there are more than just the expected elements" do expect([1, 2]).to_not include_in_any_order(eq(1)) end it "matches multiple, equal elements when there are multiple, equal exepectations" do expect([1, 1]).to include_in_any_order(eq(1), eq(1)) end end end puppet-5.5.10/spec/lib/matchers/match_tokens2.rb0000644005276200011600000000377213417161721021417 0ustar jenkinsjenkins# Matches tokens produced by lexer # The given exepected is one or more entries where an entry is one of # - a token symbol # - an Array with a token symbol and the text value # - an Array with a token symbol and a Hash specifying all attributes of the token # - nil (ignore) # RSpec::Matchers.define :match_tokens2 do | *expected | match do | actual | expected.zip(actual).all? do |e, a| compare(e, a) end end def failure_message msg = ["Expected (#{expected.size}):"] expected.each {|e| msg << e.to_s } zipped = expected.zip(actual) msg << "\nGot (#{actual.size}):" actual.each_with_index do |e, idx| if zipped[idx] zipped_expected = zipped[idx][0] zipped_actual = zipped[idx][1] prefix = compare(zipped_expected, zipped_actual) ? ' ' : '*' msg2 = ["#{prefix}[:"] msg2 << e[0].to_s msg2 << ', ' if e[1] == false msg2 << 'false' else msg2 << e[1][:value].to_s.dump end # If expectation has options, output them if zipped_expected.is_a?(Array) && zipped_expected[2] && zipped_expected[2].is_a?(Hash) msg2 << ", {" msg3 = [] zipped_expected[2].each do |k,v| prefix = e[1][k] != v ? "*" : '' msg3 << "#{prefix}:#{k}=>#{e[1][k]}" end msg2 << msg3.join(", ") msg2 << "}" end msg2 << ']' msg << msg2.join('') end end msg.join("\n") end def compare(e, a) # if expected ends before actual return true if !e # If actual ends before expected return false if !a # Simple - only expect token to match return true if a[0] == e # Expect value and optional attributes to match if e.is_a? Array # tokens must match return false unless a[0] == e[0] if e[2].is_a?(Hash) e[2].each {|k,v| return false unless a[1][k] == v } end return (a[1] == e[1] || (a[1][:value] == e[1])) end false end end puppet-5.5.10/spec/lib/matchers/relationship_graph_matchers.rb0000644005276200011600000000234113417161721024415 0ustar jenkinsjenkinsmodule RelationshipGraphMatchers class EnforceOrderWithEdge def initialize(before, after) @before = before @after = after end def matches?(actual_graph) @actual_graph = actual_graph @reverse_edge = actual_graph.edge?( vertex_called(actual_graph, @after), vertex_called(actual_graph, @before)) @forward_edge = actual_graph.edge?( vertex_called(actual_graph, @before), vertex_called(actual_graph, @after)) @forward_edge && !@reverse_edge end def failure_message "expect #{@actual_graph.to_dot_graph} to only contain an edge from #{@before} to #{@after} but #{[forward_failure_message, reverse_failure_message].compact.join(' and ')}" end def forward_failure_message if !@forward_edge "did not contain an edge from #{@before} to #{@after}" end end def reverse_failure_message if @reverse_edge "contained an edge from #{@after} to #{@before}" end end private def vertex_called(graph, name) graph.vertices.find { |v| v.ref =~ /#{Regexp.escape(name)}/ } end end def enforce_order_with_edge(before, after) EnforceOrderWithEdge.new(before, after) end end puppet-5.5.10/spec/lib/matchers/resource.rb0000644005276200011600000000353613417161721020503 0ustar jenkinsjenkinsmodule Matchers; module Resource extend RSpec::Matchers::DSL matcher :have_resource do |expected_resource| def resource_match(expected_resource, actual_resource) matched = true failures = [] if actual_resource.ref != expected_resource matched = false failures << "expected #{expected_resource} but was #{actual_resource.ref}" end @params ||= {} @params.each do |name, value| case value when RSpec::Matchers::DSL::Matcher if !value.matches?(actual_resource[name]) matched = false failures << "expected #{name} to match '#{value.description}' but was '#{actual_resource[name]}'" end else if actual_resource[name] != value matched = false failures << "expected #{name} to be '#{value}' but was '#{actual_resource[name]}'" end end end @mismatch = failures.join("\n") matched end match do |actual_catalog| @mismatch = "" if resource = actual_catalog.resource(expected_resource) resource_match(expected_resource, resource) else @mismatch = "expected #{@actual.to_dot} to include #{expected_resource}" false end end chain :with_parameter do |name, value| @params ||= {} @params[name] = value end def failure_message @mismatch end end matcher :be_resource do |expected_resource| def resource_match(expected_resource, actual_resource) if actual_resource.ref == expected_resource true else @mismatch = "expected #{expected_resource} but was #{actual_resource.ref}" false end end match do |actual_resource| resource_match(expected_resource, actual_resource) end def failure_message @mismatch end end end; end puppet-5.5.10/spec/lib/matchers/json.rb0000644005276200011600000000630113417161722017617 0ustar jenkinsjenkinsmodule JSONMatchers class SetJsonAttribute def initialize(attributes) @attributes = attributes end def format @format ||= Puppet::Network::FormatHandler.format('json') end def json(instance) Puppet::Util::Json.load(instance.to_json) end def attr_value(attrs, instance) attrs = attrs.dup hash = json(instance) while attrs.length > 0 name = attrs.shift hash = hash[name] end hash end def to(value) @value = value self end def matches?(instance) @instance = instance result = attr_value(@attributes, instance) if @value result == @value else ! result.nil? end end def failure_message if @value "expected #{@instance.inspect} to set #{@attributes.inspect} to #{@value.inspect}; got #{attr_value(@attributes, @instance).inspect}" else "expected #{@instance.inspect} to set #{@attributes.inspect} but was nil" end end def failure_message_when_negated if @value "expected #{@instance.inspect} not to set #{@attributes.inspect} to #{@value.inspect}" else "expected #{@instance.inspect} not to set #{@attributes.inspect} to nil" end end end class ReadJsonAttribute def initialize(attribute) @attribute = attribute end def format @format ||= Puppet::Network::FormatHandler.format('json') end def from(value) @json = value self end def as(as) @value = as self end def matches?(klass) raise "Must specify json with 'from'" unless @json @klass = klass @instance = format.intern(klass, @json) if @value @instance.send(@attribute) == @value else ! @instance.send(@attribute).nil? end end def failure_message if @value "expected #{@klass} to read #{@attribute} from #{@json} as #{@value.inspect}; got #{@instance.send(@attribute).inspect}" else "expected #{@klass} to read #{@attribute} from #{@json} but was nil" end end def failure_message_when_negated if @value "expected #{@klass} not to set #{@attribute} to #{@value}" else "expected #{@klass} not to set #{@attribute} to nil" end end end if !Puppet.features.microsoft_windows? require 'puppet/util/json' require 'json-schema' class SchemaMatcher JSON_META_SCHEMA = Puppet::Util::Json.load(File.read('api/schemas/json-meta-schema.json')) def initialize(schema) @schema = schema end def matches?(json) JSON::Validator.validate!(JSON_META_SCHEMA, @schema) JSON::Validator.validate!(@schema, json) end end end def validate_against(schema_file) if Puppet.features.microsoft_windows? pending("Schema checks cannot be done on windows because of json-schema problems") else schema = Puppet::Util::Json.load(File.read(schema_file)) SchemaMatcher.new(schema) end end def set_json_attribute(*attributes) SetJsonAttribute.new(attributes) end def read_json_attribute(attribute) ReadJsonAttribute.new(attribute) end end puppet-5.5.10/spec/lib/puppet/0000755005276200011600000000000013417162176016034 5ustar jenkinsjenkinspuppet-5.5.10/spec/lib/puppet/face/0000755005276200011600000000000013417162176016732 5ustar jenkinsjenkinspuppet-5.5.10/spec/lib/puppet/face/1.0.0/0000755005276200011600000000000013417162176017366 5ustar jenkinsjenkinspuppet-5.5.10/spec/lib/puppet/face/1.0.0/huzzah.rb0000644005276200011600000000051713417161721021222 0ustar jenkinsjenkinsrequire 'puppet/face' Puppet::Face.define(:huzzah, '1.0.0') do copyright "Puppet Inc.", 2011 license "Apache 2 license; see COPYING" summary "life is a thing for celebration" action(:obsolete_in_core) { when_invoked { |_| "you are in obsolete core now!" } } action(:call_newer) { when_invoked { |_| method_on_newer } } end puppet-5.5.10/spec/lib/puppet/face/basetest.rb0000644005276200011600000000241413417161721021065 0ustar jenkinsjenkinsrequire 'puppet/face' Puppet::Face.define(:basetest, '0.0.1') do copyright "Puppet Inc.", 2011 license "Apache 2 license; see COPYING" summary "This is just so tests don't fail" option "--[no-]boolean" option "--mandatory ARGUMENT" action :foo do option("--action") when_invoked do |*args| args.length end end action :return_true do summary "just returns true" when_invoked do |options| true end end action :return_false do summary "just returns false" when_invoked do |options| false end end action :return_nil do summary "just returns nil" when_invoked do |options| nil end end action :raise do summary "just raises an exception" when_invoked do |options| raise ArgumentError, "your failure" end end action :with_s_rendering_hook do summary "has a rendering hook for 's'" when_invoked do |options| "this is not the hook you are looking for" end when_rendering :s do |value| "you invoked the 's' rendering hook" end end action :count_args do summary "return the count of arguments given" when_invoked do |*args| args.length - 1 end end action :with_specific_exit_code do summary "just call exit with the desired exit code" when_invoked do |options| exit(5) end end end puppet-5.5.10/spec/lib/puppet/face/huzzah.rb0000644005276200011600000000050313417161721020561 0ustar jenkinsjenkinsrequire 'puppet/face' Puppet::Face.define(:huzzah, '2.0.1') do copyright "Puppet Inc.", 2011 license "Apache 2 license; see COPYING" summary "life is a thing for celebration" action(:bar) { when_invoked { |options| "is where beer comes from" } } action(:call_older) { when_invoked { |_| method_on_older } } end puppet-5.5.10/spec/lib/puppet/face/huzzah/0000755005276200011600000000000013417162176020243 5ustar jenkinsjenkinspuppet-5.5.10/spec/lib/puppet/face/huzzah/obsolete.rb0000644005276200011600000000026013417161721022375 0ustar jenkinsjenkinsPuppet::Face.define(:huzzah, '1.0.0') do action :obsolete do summary "This is an action on version 1.0.0 of the face" when_invoked do |options| options end end end puppet-5.5.10/spec/lib/puppet/face/version_matching.rb0000644005276200011600000000075213417161721022615 0ustar jenkinsjenkinsrequire 'puppet/face' # The set of versions here are used explicitly in the interface_spec; if you # change this you need to ensure that is still correct. --daniel 2011-04-21 ['1.0.0', '1.0.1', '1.1.0', '1.1.1', '2.0.0'].each do |version| Puppet::Face.define(:version_matching, version) do copyright "Puppet Inc.", 2011 license "Apache 2 license; see COPYING" summary "version matching face #{version}" action(:version) { when_invoked { |options| version } } end end puppet-5.5.10/spec/lib/puppet/indirector/0000755005276200011600000000000013417162176020176 5ustar jenkinsjenkinspuppet-5.5.10/spec/lib/puppet/indirector/indirector_testing/0000755005276200011600000000000013417162176024075 5ustar jenkinsjenkinspuppet-5.5.10/spec/lib/puppet/indirector/indirector_testing/json.rb0000644005276200011600000000026013417161721025364 0ustar jenkinsjenkinsrequire 'puppet/indirector_testing' require 'puppet/indirector/json' class Puppet::IndirectorTesting::JSON < Puppet::Indirector::JSON desc "Testing the JSON indirector" end puppet-5.5.10/spec/lib/puppet/indirector/indirector_testing/memory.rb0000644005276200011600000000023413417161721025724 0ustar jenkinsjenkinsrequire 'puppet/indirector/memory' class Puppet::IndirectorTesting::Memory < Puppet::Indirector::Memory def supports_remote_requests? true end end puppet-5.5.10/spec/lib/puppet/indirector/indirector_testing/msgpack.rb0000644005276200011600000000030013417161721026033 0ustar jenkinsjenkinsrequire 'puppet/indirector_testing' require 'puppet/indirector/msgpack' class Puppet::IndirectorTesting::Msgpack < Puppet::Indirector::Msgpack desc "Testing the MessagePack indirector" end puppet-5.5.10/spec/lib/puppet/indirector_proxy.rb0000644005276200011600000000121613417161721021757 0ustar jenkinsjenkinsclass Puppet::IndirectorProxy class ProxyId attr_accessor :name def initialize(name) self.name = name end end # We should have some way to identify if we got a valid object back with the # current values, no? attr_accessor :value, :proxyname alias_method :name, :value alias_method :name=, :value= def initialize(value, proxyname) self.value = value self.proxyname = proxyname end def self.indirection ProxyId.new("file_metadata") end def self.from_binary(raw) new(raw) end def self.from_data_hash(data) new(data['value']) end def to_data_hash { 'value' => value } end end puppet-5.5.10/spec/lib/puppet/indirector_testing.rb0000644005276200011600000000105713417161721022256 0ustar jenkinsjenkinsrequire 'puppet/indirector' class Puppet::IndirectorTesting extend Puppet::Indirector indirects :indirector_testing # We should have some way to identify if we got a valid object back with the # current values, no? attr_accessor :value alias_method :name, :value alias_method :name=, :value= def initialize(value) self.value = value end def self.from_binary(raw) new(raw) end def self.from_data_hash(data) new(data['value']) end def to_binary value end def to_data_hash { 'value' => value } end end puppet-5.5.10/spec/lib/puppet_spec/0000755005276200011600000000000013417162176017046 5ustar jenkinsjenkinspuppet-5.5.10/spec/lib/puppet_spec/character_encoding.rb0000644005276200011600000000051513417161721023171 0ustar jenkinsjenkins# A support module for testing character encoding module PuppetSpec::CharacterEncoding def self.with_external_encoding(encoding, &blk) original_encoding = Encoding.default_external begin Encoding.default_external = encoding yield ensure Encoding.default_external = original_encoding end end end puppet-5.5.10/spec/lib/puppet_spec/fixtures.rb0000644005276200011600000000156213417161721021243 0ustar jenkinsjenkinsmodule PuppetSpec::Fixtures def fixtures(*rest) File.join(PuppetSpec::FIXTURE_DIR, *rest) end def my_fixture_dir callers = caller while line = callers.shift do next unless found = line.match(%r{/spec/(.*)_spec\.rb:}) return fixtures(found[1]) end fail "sorry, I couldn't work out your path from the caller stack!" end def my_fixture(name) file = File.join(my_fixture_dir, name) unless File.readable? file then fail Puppet::DevError, "fixture '#{name}' for #{my_fixture_dir} is not readable" end return file end def my_fixtures(glob = '*', flags = 0) files = Dir.glob(File.join(my_fixture_dir, glob), flags) unless files.length > 0 then fail Puppet::DevError, "fixture '#{glob}' for #{my_fixture_dir} had no files!" end block_given? and files.each do |file| yield file end files end end puppet-5.5.10/spec/lib/puppet_spec/handler.rb0000644005276200011600000000150613417161721021005 0ustar jenkinsjenkinsrequire 'puppet/network/http/handler' class PuppetSpec::Handler include Puppet::Network::HTTP::Handler def initialize(* routes) register(routes) end def set_content_type(response, format) response[:content_type_header] = format end def set_response(response, body, status = 200) response[:body] = body response[:status] = status end def http_method(request) request[:method] end def path(request) request[:path] end def params(request) request[:params] end def client_cert(request) request[:client_cert] end def body(request) request[:body] end def headers(request) request[:headers] || {} end end class PuppetSpec::HandlerProfiler def start(metric, description) end def finish(context, metric, description) end def shutdown() end end puppet-5.5.10/spec/lib/puppet_spec/language.rb0000644005276200011600000000516713417161721021162 0ustar jenkinsjenkinsrequire 'puppet_spec/compiler' require 'matchers/resource' module PuppetSpec::Language extend RSpec::Matchers::DSL def produces(expectations) calledFrom = caller expectations.each do |manifest, resources| it "evaluates #{manifest} to produce #{resources}" do begin case resources when String node = Puppet::Node.new('specification') Puppet[:code] = manifest compiler = Puppet::Parser::Compiler.new(node) evaluator = Puppet::Pops::Parser::EvaluatingParser.new() # see lib/puppet/indirector/catalog/compiler.rb#filter catalog = compiler.compile.filter { |r| r.virtual? } compiler.send(:instance_variable_set, :@catalog, catalog) Puppet.override(:loaders => compiler.loaders) do expect(evaluator.evaluate_string(compiler.topscope, resources)).to eq(true) end when Array catalog = PuppetSpec::Compiler.compile_to_catalog(manifest) if resources.empty? base_resources = ["Class[Settings]", "Class[main]", "Stage[main]"] expect(catalog.resources.collect(&:ref) - base_resources).to eq([]) else resources.each do |reference| if reference.is_a?(Array) matcher = Matchers::Resource.have_resource(reference[0]) reference[1].each do |name, value| matcher = matcher.with_parameter(name, value) end else matcher = Matchers::Resource.have_resource(reference) end expect(catalog).to matcher end end else raise "Unsupported creates specification: #{resources.inspect}" end rescue Puppet::Error, RSpec::Expectations::ExpectationNotMetError => e # provide the backtrace from the caller, or it is close to impossible to find some originators e.set_backtrace(calledFrom) raise end end end end def fails(expectations) calledFrom = caller expectations.each do |manifest, pattern| it "fails to evaluate #{manifest} with message #{pattern}" do begin expect do PuppetSpec::Compiler.compile_to_catalog(manifest) end.to raise_error(Puppet::Error, pattern) rescue RSpec::Expectations::ExpectationNotMetError => e # provide the backgrace from the caller, or it is close to impossible to find some originators e.set_backtrace(calledFrom) raise end end end end end puppet-5.5.10/spec/lib/puppet_spec/matchers.rb0000644005276200011600000000774513417161721021211 0ustar jenkinsjenkinsrequire 'stringio' ######################################################################## # Backward compatibility for Jenkins outdated environment. module RSpec module Matchers module BlockAliases alias_method :to, :should unless method_defined? :to alias_method :to_not, :should_not unless method_defined? :to_not alias_method :not_to, :should_not unless method_defined? :not_to end end end ######################################################################## # Custom matchers... RSpec::Matchers.define :have_matching_element do |expected| match do |actual| actual.any? { |item| item =~ expected } end end RSpec::Matchers.define :have_matching_log do |expected| match do |actual| actual.map(&:to_s).any? { |item| item =~ expected } end end RSpec::Matchers.define :exit_with do |expected| actual = nil match do |block| begin block.call rescue SystemExit => e actual = e.status end actual and actual == expected end supports_block_expectations failure_message do |block| "expected exit with code #{expected} but " + (actual.nil? ? " exit was not called" : "we exited with #{actual} instead") end failure_message_when_negated do |block| "expected that exit would not be called with #{expected}" end description do "expect exit with #{expected}" end end RSpec::Matchers.define :have_printed do |expected| case expected when String, Regexp, Proc expected = expected else expected = expected.to_s end chain :and_exit_with do |code| @expected_exit_code = code end define_method :matches_exit_code? do |actual| @expected_exit_code.nil? || @expected_exit_code == actual end define_method :matches_output? do |actual| return false unless actual case expected when String actual.include?(expected) when Regexp expected.match(actual) when Proc expected.call(actual) else raise ArgumentError, "No idea how to match a #{actual.class.name}" end end match do |block| $stderr = $stdout = StringIO.new $stdout.set_encoding('UTF-8') if $stdout.respond_to?(:set_encoding) begin block.call rescue SystemExit => e raise unless @expected_exit_code @actual_exit_code = e.status ensure $stdout.rewind @actual = $stdout.read $stdout = STDOUT $stderr = STDERR end matches_output?(@actual) && matches_exit_code?(@actual_exit_code) end supports_block_expectations failure_message do |actual| if actual.nil? then "expected #{expected.inspect}, but nothing was printed" else if !@expected_exit_code.nil? && matches_output?(actual) "expected exit with code #{@expected_exit_code} but " + (@actual_exit_code.nil? ? " exit was not called" : "exited with #{@actual_exit_code} instead") else "expected #{expected.inspect} to be printed; got:\n#{actual}" end end end failure_message_when_negated do |actual| if @expected_exit_code && matches_exit_code?(@actual_exit_code) "expected exit code to not be #{@actual_exit_code}" else "expected #{expected.inspect} to not be printed; got:\n#{actual}" end end description do "expect #{expected.inspect} to be printed" + (@expected_exit_code.nil ? '' : " with exit code #{@expected_exit_code}") end end RSpec::Matchers.define :equal_attributes_of do |expected| match do |actual| actual.instance_variables.all? do |attr| actual.instance_variable_get(attr) == expected.instance_variable_get(attr) end end end RSpec::Matchers.define :equal_resource_attributes_of do |expected| match do |actual| actual.keys do |attr| actual[attr] == expected[attr] end end end RSpec::Matchers.define :be_one_of do |*expected| match do |actual| expected.include? actual end failure_message do |actual| "expected #{actual.inspect} to be one of #{expected.map(&:inspect).join(' or ')}" end end puppet-5.5.10/spec/lib/puppet_spec/module_tool/0000755005276200011600000000000013417162176021370 5ustar jenkinsjenkinspuppet-5.5.10/spec/lib/puppet_spec/module_tool/shared_functions.rb0000644005276200011600000000313113417161721025244 0ustar jenkinsjenkinsrequire 'puppet/util/json' module PuppetSpec module ModuleTool module SharedFunctions def remote_release(name, version) remote_source.available_releases[name][version] end def preinstall(name, version, options = { :into => primary_dir }) release = remote_release(name, version) raise "Could not preinstall #{name} v#{version}" if release.nil? name = release.name[/-(.*)/, 1] moddir = File.join(options[:into], name) FileUtils.mkdir_p(moddir) File.open(File.join(moddir, 'metadata.json'), 'w') do |file| file.puts(Puppet::Util::Json.dump(release.metadata)) end end def mark_changed(path) app = Puppet::ModuleTool::Applications::Checksummer app.stubs(:run).with(path).returns(['README']) end def graph_should_include(name, options) releases = flatten_graph(subject[:graph] || []) release = releases.find { |x| x[:name] == name } if options.nil? expect(release).to be_nil else from = options.keys.find { |k| k.nil? || k.is_a?(SemanticPuppet::Version) } to = options.delete(from) if to or from options[:previous_version] ||= from options[:version] ||= to end expect(release).not_to be_nil expect(release).to include options end end def flatten_graph(graph) graph + graph.map { |sub| flatten_graph(sub[:dependencies]) }.flatten end def v(str) SemanticPuppet::Version.parse(str) end end end end puppet-5.5.10/spec/lib/puppet_spec/module_tool/stub_source.rb0000644005276200011600000001413313417161721024247 0ustar jenkinsjenkinsmodule PuppetSpec module ModuleTool class StubSource < SemanticPuppet::Dependency::Source def inspect; "Stub Source"; end def host "http://nowhe.re" end def fetch(name) available_releases[name.tr('/', '-')].values end def available_releases return @available_releases if defined? @available_releases @available_releases = { 'puppetlabs-java' => { '10.0.0' => { 'puppetlabs/stdlib' => '4.1.0' }, }, 'puppetlabs-stdlib' => { '4.1.0' => {}, }, 'pmtacceptance-stdlib' => { "4.1.0" => {}, "3.2.0" => {}, "3.1.0" => {}, "3.0.0" => {}, "2.6.0" => {}, "2.5.1" => {}, "2.5.0" => {}, "2.4.0" => {}, "2.3.2" => {}, "2.3.1" => {}, "2.3.0" => {}, "2.2.1" => {}, "2.2.0" => {}, "2.1.3" => {}, "2.0.0" => {}, "1.1.0" => {}, "1.0.0" => {}, }, 'pmtacceptance-keystone' => { '3.0.0-rc2' => { "pmtacceptance/mysql" => ">=0.6.1 <1.0.0", "pmtacceptance/stdlib" => ">= 2.5.0" }, '3.0.0-rc1' => { "pmtacceptance/mysql" => ">=0.6.1 <1.0.0", "pmtacceptance/stdlib" => ">= 2.5.0" }, '2.2.0' => { "pmtacceptance/mysql" => ">=0.6.1 <1.0.0", "pmtacceptance/stdlib" => ">= 2.5.0" }, '2.2.0-rc1' => { "pmtacceptance/mysql" => ">=0.6.1 <1.0.0", "pmtacceptance/stdlib" => ">= 2.5.0" }, '2.1.0' => { "pmtacceptance/mysql" => ">=0.6.1 <1.0.0", "pmtacceptance/stdlib" => ">= 2.5.0" }, '2.0.0' => { "pmtacceptance/mysql" => ">= 0.6.1" }, '1.2.0' => { "pmtacceptance/mysql" => ">= 0.5.0" }, '1.1.1' => { "pmtacceptance/mysql" => ">= 0.5.0" }, '1.1.0' => { "pmtacceptance/mysql" => ">= 0.5.0" }, '1.0.1' => { "pmtacceptance/mysql" => ">= 0.5.0" }, '1.0.0' => { "pmtacceptance/mysql" => ">= 0.5.0" }, '0.2.0' => { "pmtacceptance/mysql" => ">= 0.5.0" }, '0.1.0' => { "pmtacceptance/mysql" => ">= 0.3.0" }, }, 'pmtacceptance-mysql' => { "2.1.0" => { "pmtacceptance/stdlib" => ">= 2.2.1" }, "2.0.1" => { "pmtacceptance/stdlib" => ">= 2.2.1" }, "2.0.0" => { "pmtacceptance/stdlib" => ">= 2.2.1" }, "2.0.0-rc5" => { "pmtacceptance/stdlib" => ">= 2.2.1" }, "2.0.0-rc4" => { "pmtacceptance/stdlib" => ">= 2.2.1" }, "2.0.0-rc3" => { "pmtacceptance/stdlib" => ">= 2.2.1" }, "2.0.0-rc2" => { "pmtacceptance/stdlib" => ">= 2.2.1" }, "2.0.0-rc1" => { "pmtacceptance/stdlib" => ">= 2.2.1" }, "1.0.0" => { "pmtacceptance/stdlib" => ">= 2.2.1" }, "0.9.0" => { "pmtacceptance/stdlib" => ">= 2.2.1" }, "0.8.1" => { "pmtacceptance/stdlib" => ">= 2.2.1" }, "0.8.0" => { "pmtacceptance/stdlib" => ">= 2.2.1" }, "0.7.1" => { "pmtacceptance/stdlib" => ">= 2.2.1" }, "0.7.0" => { "pmtacceptance/stdlib" => ">= 2.2.1" }, "0.6.1" => { "pmtacceptance/stdlib" => ">= 2.2.1" }, "0.6.0" => { "pmtacceptance/stdlib" => ">= 2.2.1" }, "0.5.0" => { "pmtacceptance/stdlib" => ">= 2.2.1" }, "0.4.0" => {}, "0.3.0" => {}, "0.2.0" => {}, }, 'pmtacceptance-apache' => { "0.10.0" => { "pmtacceptance/stdlib" => ">= 2.4.0" }, "0.9.0" => { "pmtacceptance/stdlib" => ">= 2.4.0" }, "0.8.1" => { "pmtacceptance/stdlib" => ">= 2.2.1" }, "0.8.0" => { "pmtacceptance/stdlib" => ">= 2.2.1" }, "0.7.0" => { "pmtacceptance/stdlib" => ">= 2.2.1" }, "0.6.0" => { "pmtacceptance/stdlib" => ">= 2.2.1" }, "0.5.0-rc1" => { "pmtacceptance/stdlib" => ">= 2.2.1" }, "0.4.0" => { "pmtacceptance/stdlib" => ">= 2.2.1" }, "0.3.0" => { "pmtacceptance/stdlib" => ">= 2.2.1" }, "0.2.2" => { "pmtacceptance/stdlib" => ">= 2.2.1" }, "0.2.1" => { "pmtacceptance/stdlib" => ">= 2.2.1" }, "0.2.0" => { "pmtacceptance/stdlib" => ">= 2.2.1" }, "0.1.1" => { "pmtacceptance/stdlib" => ">= 2.2.1" }, "0.0.4" => {}, "0.0.3" => {}, "0.0.2" => {}, "0.0.1" => {}, }, 'pmtacceptance-bacula' => { "0.0.3" => { "pmtacceptance/stdlib" => ">= 2.2.0", "pmtacceptance/mysql" => ">= 1.0.0" }, "0.0.2" => { "pmtacceptance/stdlib" => ">= 2.2.0", "pmtacceptance/mysql" => ">= 0.0.1" }, "0.0.1" => { "pmtacceptance/stdlib" => ">= 2.2.0" }, }, 'puppetlabs-oneversion' => { "0.0.1" => {} } } @available_releases.each do |name, versions| versions.each do |version, deps| deps, metadata = deps.partition { |k,v| k.is_a? String } dependencies = Hash[deps.map { |k, v| [ k.tr('/', '-'), v ] }] versions[version] = create_release(name, version, dependencies).tap do |release| release.meta_def(:prepare) { } release.meta_def(:install) { |x| @install_dir = x.to_s } release.meta_def(:install_dir) { @install_dir } release.meta_def(:metadata) do metadata = Hash[metadata].merge( :name => name, :version => version, :source => '', # GRR, Puppet! :author => '', # GRR, Puppet! :license => '', # GRR, Puppet! :dependencies => dependencies.map do |dep, range| { :name => dep, :version_requirement => range } end ) Hash[metadata.map { |k,v| [ k.to_s, v ] }] end end end end end end end end puppet-5.5.10/spec/lib/puppet_spec/network.rb0000644005276200011600000000570313417161721021064 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/network/http' require 'puppet/network/http/api/indirected_routes' require 'puppet/indirector_testing' module PuppetSpec::Network def not_found_error Puppet::Network::HTTP::Error::HTTPNotFoundError end def not_acceptable_error Puppet::Network::HTTP::Error::HTTPNotAcceptableError end def bad_request_error Puppet::Network::HTTP::Error::HTTPBadRequestError end def not_authorized_error Puppet::Network::HTTP::Error::HTTPNotAuthorizedError end def method_not_allowed_error Puppet::Network::HTTP::Error::HTTPMethodNotAllowedError end def unsupported_media_type_error Puppet::Network::HTTP::Error::HTTPUnsupportedMediaTypeError end def params { :environment => "production" } end def master_url_prefix "#{Puppet::Network::HTTP::MASTER_URL_PREFIX}/v3" end def ca_url_prefix "#{Puppet::Network::HTTP::CA_URL_PREFIX}/v1" end def a_request_that_heads(data, request = {}, params = params()) Puppet::Network::HTTP::Request.from_hash({ :headers => { 'accept' => request[:accept_header], 'content-type' => "application/json" }, :method => "HEAD", :path => "#{master_url_prefix}/#{data.class.indirection.name}/#{data.value}", :params => params, }) end def a_request_that_submits(data, request = {}, params = params()) Puppet::Network::HTTP::Request.from_hash({ :headers => { 'accept' => request[:accept_header], 'content-type' => request[:content_type_header] || "application/json" }, :method => "PUT", :path => "#{master_url_prefix}/#{data.class.indirection.name}/#{data.value}", :params => params, :body => request[:body].nil? ? data.render("json") : request[:body] }) end def a_request_that_destroys(data, request = {}, params = params()) Puppet::Network::HTTP::Request.from_hash({ :headers => { 'accept' => request[:accept_header], 'content-type' => "application/json" }, :method => "DELETE", :path => "#{master_url_prefix}/#{data.class.indirection.name}/#{data.value}", :params => params, :body => '' }) end def a_request_that_finds(data, request = {}, params = params()) Puppet::Network::HTTP::Request.from_hash({ :headers => { 'accept' => request[:accept_header], 'content-type' => "application/json" }, :method => "GET", :path => "#{master_url_prefix}/#{data.class.indirection.name}/#{data.value}", :params => params, :body => '' }) end def a_request_that_searches(data, request = {}, params = params()) Puppet::Network::HTTP::Request.from_hash({ :headers => { 'accept' => request[:accept_header], 'content-type' => "application/json" }, :method => "GET", :path => "#{master_url_prefix}/#{data.class.indirection.name}s/#{data.name}", :params => params, :body => '' }) end end puppet-5.5.10/spec/lib/puppet_spec/pops.rb0000644005276200011600000000203013417161721020342 0ustar jenkinsjenkinsmodule PuppetSpec::Pops extend RSpec::Matchers::DSL # Checks if an Acceptor has a specific issue in its list of diagnostics matcher :have_issue do |expected| match do |actual| actual.diagnostics.index { |i| i.issue == expected } != nil end failure_message do |actual| "expected Acceptor[#{actual.diagnostics.collect { |i| i.issue.issue_code }.join(',')}] to contain issue #{expected.issue_code}" end failure_message_when_negated do |actual| "expected Acceptor[#{actual.diagnostics.collect { |i| i.issue.issue_code }.join(',')}] to not contain issue #{expected.issue_code}" end end # Checks if an Acceptor has any issues matcher :have_any_issues do match do |actual| !actual.diagnostics.empty? end failure_message do |actual| 'expected Acceptor[] to contain at least one issue' end failure_message_when_negated do |actual| "expected Acceptor[#{actual.diagnostics.collect { |i| i.issue.issue_code }.join(',')}] to not contain any issues" end end end puppet-5.5.10/spec/lib/puppet_spec/scope.rb0000644005276200011600000000057513417161721020506 0ustar jenkinsjenkins module PuppetSpec::Scope # Initialize a new scope suitable for testing. # def create_test_scope_for_node(node_name) node = Puppet::Node.new(node_name) compiler = Puppet::Parser::Compiler.new(node) scope = Puppet::Parser::Scope.new(compiler) scope.source = Puppet::Resource::Type.new(:node, node_name) scope.parent = compiler.topscope scope end endpuppet-5.5.10/spec/lib/puppet_spec/settings.rb0000644005276200011600000000255713417161721021237 0ustar jenkinsjenkinsmodule PuppetSpec::Settings # It would probably be preferable to refactor defaults.rb such that the real definitions of # these settings were available as a variable, which was then accessible for use during tests. # However, I'm not doing that yet because I don't want to introduce any additional moving parts # to this already very large changeset. # Would be nice to clean this up later. --cprice 2012-03-20 TEST_APP_DEFAULT_DEFINITIONS = { :name => { :default => "test", :desc => "name" }, :logdir => { :type => :directory, :default => "test", :desc => "logdir" }, :confdir => { :type => :directory, :default => "test", :desc => "confdir" }, :codedir => { :type => :directory, :default => "test", :desc => "codedir" }, :vardir => { :type => :directory, :default => "test", :desc => "vardir" }, :rundir => { :type => :directory, :default => "test", :desc => "rundir" }, } def set_puppet_conf(confdir, settings) write_file(File.join(confdir, "puppet.conf"), settings) end def set_environment_conf(environmentpath, environment, settings) envdir = File.join(environmentpath, environment) FileUtils.mkdir_p(envdir) write_file(File.join(envdir, 'environment.conf'), settings) end def write_file(file, contents) File.open(file, "w") do |f| f.puts(contents) end end end puppet-5.5.10/spec/lib/puppet_spec/unindent.rb0000644005276200011600000000017313417161721021213 0ustar jenkinsjenkinsclass String def unindent(left_padding = '') gsub(/^#{scan(/^\s*/).min_by{ |l| l.length }}/, left_padding) end end puppet-5.5.10/spec/lib/puppet_spec/verbose.rb0000644005276200011600000000031713417161721021034 0ustar jenkinsjenkins# Support code for running stuff with warnings disabled. module Kernel def with_verbose_disabled verbose, $VERBOSE = $VERBOSE, nil result = yield $VERBOSE = verbose return result end end puppet-5.5.10/spec/lib/puppet_spec/compiler.rb0000644005276200011600000000510113417161722021176 0ustar jenkinsjenkinsmodule PuppetSpec::Compiler module_function def compile_to_catalog(string, node = Puppet::Node.new('test')) Puppet[:code] = string # see lib/puppet/indirector/catalog/compiler.rb#filter Puppet::Parser::Compiler.compile(node).filter { |r| r.virtual? } end # Does not removed virtual resources in compiled catalog (i.e. keeps unrealized) def compile_to_catalog_unfiltered(string, node = Puppet::Node.new('test')) Puppet[:code] = string # see lib/puppet/indirector/catalog/compiler.rb#filter Puppet::Parser::Compiler.compile(node) end def compile_to_ral(manifest, node = Puppet::Node.new('test')) catalog = compile_to_catalog(manifest, node) ral = catalog.to_ral ral.finalize ral end def compile_to_relationship_graph(manifest, prioritizer = Puppet::Graph::SequentialPrioritizer.new) ral = compile_to_ral(manifest) graph = Puppet::Graph::RelationshipGraph.new(prioritizer) graph.populate_from(ral) graph end def apply_compiled_manifest(manifest, prioritizer = Puppet::Graph::SequentialPrioritizer.new) catalog = compile_to_ral(manifest) if block_given? catalog.resources.each { |res| yield res } end transaction = Puppet::Transaction.new(catalog, Puppet::Transaction::Report.new, prioritizer) transaction.evaluate transaction.report.finalize_report transaction end def apply_with_error_check(manifest) apply_compiled_manifest(manifest) do |res| res.expects(:err).never end end def order_resources_traversed_in(relationships) order_seen = [] relationships.traverse { |resource| order_seen << resource.ref } order_seen end def collect_notices(code, node = Puppet::Node.new('foonode')) Puppet[:code] = code compiler = Puppet::Parser::Compiler.new(node) node.environment.check_for_reparse logs = [] Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do yield(compiler) end logs = logs.select { |log| log.level == :notice }.map { |log| log.message } logs end def eval_and_collect_notices(code, node = Puppet::Node.new('foonode'), topscope_vars = {}) collect_notices(code, node) do |compiler| unless topscope_vars.empty? scope = compiler.topscope topscope_vars.each {|k,v| scope.setvar(k, v) } end if block_given? compiler.compile do |catalog| yield(compiler.topscope, catalog) catalog end else compiler.compile end end end end puppet-5.5.10/spec/lib/puppet_spec/files.rb0000644005276200011600000000540013417161722020470 0ustar jenkinsjenkinsrequire 'fileutils' require 'tempfile' require 'tmpdir' require 'pathname' # A support module for testing files. module PuppetSpec::Files def self.cleanup $global_tempfiles ||= [] while path = $global_tempfiles.pop do begin Dir.unstub(:entries) FileUtils.rm_rf path, :secure => true rescue Errno::ENOENT # nothing to do end end end def make_absolute(path) PuppetSpec::Files.make_absolute(path) end def self.make_absolute(path) path = File.expand_path(path) path[0] = 'c' if Puppet.features.microsoft_windows? path end def tmpfile(name, dir = nil) PuppetSpec::Files.tmpfile(name, dir) end def self.tmpfile(name, dir = nil) # Generate a temporary file, just for the name... source = dir ? Tempfile.new(name, dir) : Tempfile.new(name) path = Puppet::FileSystem.expand_path(source.path.encode(Encoding::UTF_8)) source.close! record_tmp(File.expand_path(path)) path end def file_containing(name, contents) PuppetSpec::Files.file_containing(name, contents) end def self.file_containing(name, contents) file = tmpfile(name) File.open(file, 'wb') { |f| f.write(contents) } file end def script_containing(name, contents) PuppetSpec::Files.script_containing(name, contents) end def self.script_containing(name, contents) file = tmpfile(name) if Puppet.features.microsoft_windows? file += '.bat' text = contents[:windows] else text = contents[:posix] end File.open(file, 'wb') { |f| f.write(text) } Puppet::FileSystem.chmod(0755, file) file end def tmpdir(name) PuppetSpec::Files.tmpdir(name) end def self.tmpdir(name) dir = Puppet::FileSystem.expand_path(Dir.mktmpdir(name).encode!(Encoding::UTF_8)) record_tmp(dir) dir end def dir_containing(name, contents_hash) PuppetSpec::Files.dir_containing(name, contents_hash) end def self.dir_containing(name, contents_hash) dir_contained_in(tmpdir(name), contents_hash) end def dir_contained_in(dir, contents_hash) PuppetSpec::Files.dir_contained_in(dir, contents_hash) end def self.dir_contained_in(dir, contents_hash) contents_hash.each do |k,v| if v.is_a?(Hash) Dir.mkdir(tmp = File.join(dir,k)) dir_contained_in(tmp, v) else file = File.join(dir, k) File.open(file, 'wb') {|f| f.write(v) } end end dir end def self.record_tmp(tmp) # ...record it for cleanup, $global_tempfiles ||= [] $global_tempfiles << tmp end def expect_file_mode(file, mode) actual_mode = "%o" % Puppet::FileSystem.stat(file).mode target_mode = if Puppet.features.microsoft_windows? mode else "10" + "%04i" % mode.to_i end expect(actual_mode).to eq(target_mode) end end puppet-5.5.10/spec/lib/puppet_spec/modules.rb0000644005276200011600000000242713417161722021044 0ustar jenkinsjenkinsmodule PuppetSpec::Modules class << self def create(name, dir, options = {}) module_dir = File.join(dir, name) FileUtils.mkdir_p(module_dir) environment = options[:environment] if metadata = options[:metadata] metadata[:source] ||= 'github' metadata[:author] ||= 'puppetlabs' metadata[:version] ||= '9.9.9' metadata[:license] ||= 'to kill' metadata[:dependencies] ||= [] metadata[:name] = "#{metadata[:author]}/#{name}" File.open(File.join(module_dir, 'metadata.json'), 'w') do |f| f.write(metadata.to_json) end end if tasks = options[:tasks] tasks_dir = File.join(module_dir, 'tasks') FileUtils.mkdir_p(tasks_dir) tasks.each do |task_files| task_files.each do |task_file| FileUtils.touch(File.join(tasks_dir, task_file)) end end end Puppet::Module.new(name, module_dir, environment) end def generate_files(name, dir, options = {}) module_dir = File.join(dir, name) FileUtils.mkdir_p(module_dir) if metadata = options[:metadata] File.open(File.join(module_dir, 'metadata.json'), 'w') do |f| f.write(metadata.to_json) end end end end end puppet-5.5.10/spec/shared_behaviours/0000755005276200011600000000000013417162176017446 5ustar jenkinsjenkinspuppet-5.5.10/spec/shared_behaviours/all_parsedfile_providers.rb0000644005276200011600000000114113417161721025026 0ustar jenkinsjenkinsshared_examples_for "all parsedfile providers" do |provider, *files| if files.empty? then files = my_fixtures end files.flatten.each do |file| it "should rewrite #{file} reasonably unchanged" do provider.stubs(:default_target).returns(file) provider.prefetch text = provider.to_file(provider.target_records(file)) text.gsub!(/^# HEADER.+\n/, '') oldlines = File.readlines(file) newlines = text.chomp.split "\n" oldlines.zip(newlines).each do |old, new| expect(new.gsub(/\s+/, '')).to eq(old.chomp.gsub(/\s+/, '')) end end end end puppet-5.5.10/spec/shared_behaviours/an_indirector_face.rb0000644005276200011600000000031113417161721023557 0ustar jenkinsjenkinsshared_examples_for "an indirector face" do [:find, :search, :save, :destroy, :info].each do |action| it { is_expected.to be_action action } it { is_expected.to respond_to action } end end puppet-5.5.10/spec/shared_behaviours/documentation_on_faces.rb0000644005276200011600000002224213417161721024476 0ustar jenkinsjenkins# encoding: UTF-8 shared_examples_for "documentation on faces" do defined?(Attrs) or Attrs = [:summary, :description, :examples, :short_description, :notes, :author] defined?(SingleLineAttrs) or SingleLineAttrs = [:summary, :author] # Simple, procedural tests that apply to a bunch of methods. Attrs.each do |attr| it "should accept a #{attr}" do expect { subject.send("#{attr}=", "hello") }.not_to raise_error expect(subject.send(attr)).to eq("hello") end it "should accept a long (single line) value for #{attr}" do text = "I never know when to stop with the word banana" + ("na" * 1000) expect { subject.send("#{attr}=", text) }.to_not raise_error expect(subject.send(attr)).to eq(text) end end Attrs.each do |getter| setter = "#{getter}=".to_sym context "#{getter}" do it "should strip leading whitespace on a single line" do subject.send(setter, " death to whitespace") expect(subject.send(getter)).to eq("death to whitespace") end it "should strip trailing whitespace on a single line" do subject.send(setter, "death to whitespace ") expect(subject.send(getter)).to eq("death to whitespace") end it "should strip whitespace at both ends at once" do subject.send(setter, " death to whitespace ") expect(subject.send(getter)).to eq("death to whitespace") end multiline_text = "with\nnewlines" if SingleLineAttrs.include? getter then it "should not accept multiline values" do expect { subject.send(setter, multiline_text) }. to raise_error ArgumentError, /#{getter} should be a single line/ expect(subject.send(getter)).to be_nil end else it "should accept multiline values" do expect { subject.send(setter, multiline_text) }.not_to raise_error expect(subject.send(getter)).to eq(multiline_text) end [1, 2, 4, 7, 25].each do |length| context "#{length} chars indent" do indent = ' ' * length it "should strip leading whitespace on multiple lines" do text = "this\nis\the\final\outcome" subject.send(setter, text.gsub(/^/, indent)) expect(subject.send(getter)).to eq(text) end it "should not remove formatting whitespace, only global indent" do text = "this\n is\n the\n ultimate\ntest" subject.send(setter, text.gsub(/^/, indent)) expect(subject.send(getter)).to eq(text) end end end it "should strip whitespace with a blank line" do subject.send(setter, " this\n\n should outdent") expect(subject.send(getter)).to eq("this\n\nshould outdent") end end end end describe "#short_description" do it "should return the set value if set after description" do subject.description = "hello\ngoodbye" subject.short_description = "whatever" expect(subject.short_description).to eq("whatever") end it "should return the set value if set before description" do subject.short_description = "whatever" subject.description = "hello\ngoodbye" expect(subject.short_description).to eq("whatever") end it "should return nothing if not set and no description" do expect(subject.short_description).to be_nil end it "should return the first paragraph of description if not set (where it is one line long)" do subject.description = "hello" expect(subject.short_description).to eq(subject.description) end it "should return the first paragraph of description if not set (where there is no paragraph break)" do subject.description = "hello\ngoodbye" expect(subject.short_description).to eq(subject.description) end it "should return the first paragraph of description if not set (where there is a paragraph break)" do subject.description = "hello\ngoodbye\n\nmore\ntext\nhere\n\nfinal\nparagraph" expect(subject.short_description).to eq("hello\ngoodbye") end it "should trim a very, very long first paragraph and add ellipsis" do line = "this is a very, very, very long long line full of text\n" subject.description = line * 20 + "\n\nwhatever, dude." expect(subject.short_description).to eq((line * 5).chomp + ' [...]') end it "should trim a very very long only paragraph even if it is followed by a new paragraph" do line = "this is a very, very, very long long line full of text\n" subject.description = line * 20 expect(subject.short_description).to eq((line * 5).chomp + ' [...]') end end describe "multiple authors" do authors = %w{John Paul George Ringo} context "in the DSL" do it "should support multiple authors" do authors.each {|name| subject.author name } expect(subject.authors).to match_array(authors) expect(subject.author).to eq(authors.join("\n")) end it "should reject author as an array" do expect { subject.author ["Foo", "Bar"] }. to raise_error ArgumentError, /author must be a string/ end end context "#author=" do it "should accept a single name" do subject.author = "Fred" expect(subject.author).to eq("Fred") end it "should accept an array of names" do subject.author = authors expect(subject.authors).to match_array(authors) expect(subject.author).to eq(authors.join("\n")) end it "should not append when set multiple times" do subject.author = "Fred" subject.author = "John" expect(subject.author).to eq("John") end it "should reject arrays with embedded newlines" do expect { subject.author = ["Fred\nJohn"] }. to raise_error ArgumentError, /author should be a single line/ end end end describe "#license" do it "should default to reserving rights" do expect(subject.license).to match(/All Rights Reserved/) end it "should accept an arbitrary license string on the object" do subject.license = "foo" expect(subject.license).to eq("foo") end it "should accept symbols to specify existing licenses..." end describe "#copyright" do it "should fail with just a name" do expect { subject.copyright("invalid") }. to raise_error ArgumentError, /copyright takes the owners names, then the years covered/ end [1997, "1997"].each do |year| it "should accept an entity name and a #{year.class.name} year" do subject.copyright("me", year) expect(subject.copyright).to match(/\bme\b/) expect(subject.copyright).to match(/#{year}/) end it "should accept multiple entity names and a #{year.class.name} year" do subject.copyright ["me", "you"], year expect(subject.copyright).to match(/\bme\b/) expect(subject.copyright).to match(/\byou\b/) expect(subject.copyright).to match(/#{year}/) end end ["1997-2003", "1997 - 2003", 1997..2003].each do |range| it "should accept a #{range.class.name} range of years" do subject.copyright("me", range) expect(subject.copyright).to match(/\bme\b/) expect(subject.copyright).to match(/1997-2003/) end it "should accept a #{range.class.name} range of years" do subject.copyright ["me", "you"], range expect(subject.copyright).to match(/\bme\b/) expect(subject.copyright).to match(/\byou\b/) expect(subject.copyright).to match(/1997-2003/) end end [[1997, 2003], ["1997", 2003], ["1997", "2003"]].each do |input| it "should accept the set of years #{input.inspect} in an array" do subject.copyright "me", input expect(subject.copyright).to match(/\bme\b/) expect(subject.copyright).to match(/1997, 2003/) end it "should accept the set of years #{input.inspect} in an array" do subject.copyright ["me", "you"], input expect(subject.copyright).to match(/\bme\b/) expect(subject.copyright).to match(/\byou\b/) expect(subject.copyright).to match(/1997, 2003/) end end it "should warn if someone does math accidentally on the range of years" do expect { subject.copyright "me", 1997-2003 }. to raise_error ArgumentError, /copyright with a year before 1970 is very strange; did you accidentally add or subtract two years\?/ end it "should accept complex copyright years" do years = [1997, 1999, 2000..2002, 2005].reverse subject.copyright "me", years expect(subject.copyright).to match(/\bme\b/) expect(subject.copyright).to match(/1997, 1999, 2000-2002, 2005/) end end # Things that are automatically generated. [:name, :options, :synopsis].each do |attr| describe "##{attr}" do it "should not allow you to set #{attr}" do expect(subject).not_to respond_to :"#{attr}=" end it "should have a #{attr}" do expect(subject.send(attr)).not_to be_nil end it "'s #{attr} should not be empty..." do expect(subject.send(attr)).not_to eq('') end end end end puppet-5.5.10/spec/shared_behaviours/file_server_terminus.rb0000644005276200011600000000260713417161721024226 0ustar jenkinsjenkins#! /usr/bin/env ruby shared_examples_for "Puppet::Indirector::FileServerTerminus" do # This only works if the shared behaviour is included before # the 'before' block in the including context. before do Puppet::FileServing::Configuration.instance_variable_set(:@configuration, nil) Puppet::FileSystem.stubs(:exist?).returns true Puppet::FileSystem.stubs(:exist?).with(Puppet[:fileserverconfig]).returns(true) @path = Tempfile.new("file_server_testing") path = @path.path @path.close! @path = path Dir.mkdir(@path) File.open(File.join(@path, "myfile"), "w") { |f| f.print "my content" } # Use a real mount, so the integration is a bit deeper. @mount1 = Puppet::FileServing::Configuration::Mount::File.new("one") @mount1.path = @path @parser = stub 'parser', :changed? => false @parser.stubs(:parse).returns("one" => @mount1) Puppet::FileServing::Configuration::Parser.stubs(:new).returns(@parser) # Stub out the modules terminus @modules = mock 'modules terminus' @request = Puppet::Indirector::Request.new(:indirection, :method, "puppet://myhost/one/myfile", nil) end it "should use the file server configuration to find files" do @modules.stubs(:find).returns(nil) @terminus.indirection.stubs(:terminus).with(:modules).returns(@modules) expect(@terminus.find(@request)).to be_instance_of(@test_class) end end puppet-5.5.10/spec/shared_behaviours/file_serving.rb0000644005276200011600000000454613417161721022453 0ustar jenkinsjenkins#! /usr/bin/env ruby shared_examples_for "Puppet::FileServing::Files" do |indirection| %w[find search].each do |method| let(:request) { Puppet::Indirector::Request.new(indirection, method, 'foo', nil) } describe "##{method}" do it "should proxy to file terminus if the path is absolute" do request.key = make_absolute('/tmp/foo') described_class.indirection.terminus(:file).class.any_instance.expects(method).with(request) subject.send(method, request) end it "should proxy to file terminus if the protocol is file" do request.protocol = 'file' described_class.indirection.terminus(:file).class.any_instance.expects(method).with(request) subject.send(method, request) end describe "when the protocol is puppet" do before :each do request.protocol = 'puppet' end describe "and a server is specified" do before :each do request.server = 'puppet_server' end it "should proxy to rest terminus if default_file_terminus is rest" do Puppet[:default_file_terminus] = "rest" described_class.indirection.terminus(:rest).class.any_instance.expects(method).with(request) subject.send(method, request) end it "should proxy to rest terminus if default_file_terminus is not rest" do Puppet[:default_file_terminus] = 'file_server' described_class.indirection.terminus(:rest).class.any_instance.expects(method).with(request) subject.send(method, request) end end describe "and no server is specified" do before :each do request.server = nil end it "should proxy to file_server if default_file_terminus is 'file_server'" do Puppet[:default_file_terminus] = 'file_server' described_class.indirection.terminus(:file_server).class.any_instance.expects(method).with(request) subject.send(method, request) end it "should proxy to rest if default_file_terminus is 'rest'" do Puppet[:default_file_terminus] = "rest" described_class.indirection.terminus(:rest).class.any_instance.expects(method).with(request) subject.send(method, request) end end end end end end puppet-5.5.10/spec/shared_behaviours/hiera_indirections.rb0000644005276200011600000000552513417161721023637 0ustar jenkinsjenkinsshared_examples_for "Hiera indirection" do |test_klass, fixture_dir| include PuppetSpec::Files def write_hiera_config(config_file, datadir) File.open(config_file, 'w') do |f| f.write("--- :yaml: :datadir: #{datadir} :hierarchy: ['global', 'invalid'] :logger: 'noop' :backends: ['yaml'] ") end end def request(key) Puppet::Indirector::Request.new(:hiera, :find, key, nil) end before do hiera_config_file = tmpfile("hiera.yaml") Puppet.settings[:hiera_config] = hiera_config_file write_hiera_config(hiera_config_file, fixture_dir) end after do test_klass.instance_variable_set(:@hiera, nil) end it "should be the default data_binding terminus" do expect(Puppet.settings[:data_binding_terminus]).to eq(:hiera) end it "should raise an error if we don't have the hiera feature" do Puppet.features.expects(:hiera?).returns(false) expect { test_klass.new }.to raise_error RuntimeError, "Hiera terminus not supported without hiera library" end describe "the behavior of the hiera_config method", :if => Puppet.features.hiera? do it "should override the logger and set it to puppet" do expect(test_klass.hiera_config[:logger]).to eq("puppet") end context "when the Hiera configuration file does not exist" do let(:path) { File.expand_path('/doesnotexist') } before do Puppet.settings[:hiera_config] = path end it "should log a warning" do Puppet.expects(:warning).with( "Config file #{path} not found, using Hiera defaults") test_klass.hiera_config end it "should only configure the logger and set it to puppet" do Puppet.expects(:warning).with( "Config file #{path} not found, using Hiera defaults") expect(test_klass.hiera_config).to eq({ :logger => 'puppet' }) end end end describe "the behavior of the find method", :if => Puppet.features.hiera? do let(:data_binder) { test_klass.new } it "should support looking up an integer" do expect(data_binder.find(request("integer"))).to eq(3000) end it "should support looking up a string" do expect(data_binder.find(request("string"))).to eq('apache') end it "should support looking up an array" do expect(data_binder.find(request("array"))).to eq([ '0.ntp.puppetlabs.com', '1.ntp.puppetlabs.com', ]) end it "should support looking up a hash" do expect(data_binder.find(request("hash"))).to eq({ 'user' => 'Hightower', 'group' => 'admin', 'mode' => '0644' }) end it "raises a data binding error if hiera cannot parse the yaml data" do expect do data_binder.find(request('invalid')) end.to raise_error(Puppet::DataBinding::LookupError) end end end puppet-5.5.10/spec/shared_behaviours/iterative_functions.rb0000644005276200011600000000426113417161721024055 0ustar jenkinsjenkins shared_examples_for 'all iterative functions hash handling' do |func| it 'passes a hash entry as an array of the key and value' do catalog = compile_to_catalog(<<-MANIFEST) {a=>1}.#{func} |$v| { notify { "${v[0]} ${v[1]}": } } MANIFEST expect(catalog.resource(:notify, "a 1")).not_to be_nil end end shared_examples_for 'all iterative functions argument checks' do |func| it 'raises an error when used against an unsupported type' do expect do compile_to_catalog(<<-MANIFEST) 3.14.#{func} |$k, $v| { } MANIFEST end.to raise_error(Puppet::Error, /expects an Iterable value, got Float/) end it 'raises an error when called with any parameters besides a block' do expect do compile_to_catalog(<<-MANIFEST) [1].#{func}(1,2) |$v,$y| { } MANIFEST end.to raise_error(Puppet::Error, /expects (?:between 1 and 2 arguments|1 argument), got 3/) end it 'raises an error when called without a block' do expect do compile_to_catalog(<<-MANIFEST) [1].#{func} MANIFEST end.to raise_error(Puppet::Error, /expects a block/) end it 'raises an error when called with something that is not a block' do expect do compile_to_catalog(<<-MANIFEST) [1].#{func}(1,2) MANIFEST end.to raise_error(Puppet::Error, /expects (?:between 1 and 2 arguments|1 argument), got 3/) end it 'raises an error when called with a block with too many required parameters' do expect do compile_to_catalog(<<-MANIFEST) [1].#{func}() |$v1, $v2, $v3| { } MANIFEST end.to raise_error(Puppet::Error, /block expects(?: between 1 and)? 2 arguments, got 3/) end it 'raises an error when called with a block with too few parameters' do expect do compile_to_catalog(<<-MANIFEST) [1].#{func}() | | { } MANIFEST end.to raise_error(Puppet::Error, /block expects(?: between 1 and)? 2 arguments, got none/) end it 'does not raise an error when called with a block with too many but optional arguments' do expect do compile_to_catalog(<<-MANIFEST) [1].#{func}() |$v1, $v2, $v3=extra| { } MANIFEST end.to_not raise_error end end puppet-5.5.10/spec/shared_behaviours/memory_terminus.rb0000644005276200011600000000167713417161721023237 0ustar jenkinsjenkinsshared_examples_for "A Memory Terminus" do it "should find no instances by default" do expect(@searcher.find(@request)).to be_nil end it "should be able to find instances that were previously saved" do @searcher.save(@request) expect(@searcher.find(@request)).to equal(@instance) end it "should replace existing saved instances when a new instance with the same name is saved" do @searcher.save(@request) two = stub 'second', :name => @name trequest = stub 'request', :key => @name, :instance => two @searcher.save(trequest) expect(@searcher.find(@request)).to equal(two) end it "should be able to remove previously saved instances" do @searcher.save(@request) @searcher.destroy(@request) expect(@searcher.find(@request)).to be_nil end it "should fail when asked to destroy an instance that does not exist" do expect { @searcher.destroy(@request) }.to raise_error(ArgumentError) end end puppet-5.5.10/spec/shared_behaviours/store_configs_terminus.rb0000644005276200011600000000114113417161721024555 0ustar jenkinsjenkinsshared_examples_for "a StoreConfigs terminus" do before :each do Puppet[:storeconfigs] = true Puppet[:storeconfigs_backend] = "store_configs_testing" end api = [:find, :search, :save, :destroy, :head] api.each do |name| it { is_expected.to respond_to(name) } end it "should fail if an invalid backend is configured" do Puppet[:storeconfigs_backend] = "synergy" expect { subject }.to raise_error(ArgumentError, /could not find terminus synergy/i) end it "should wrap the declared backend" do expect(subject.target.class.name).to eq(:store_configs_testing) end end puppet-5.5.10/spec/shared_behaviours/things_that_declare_options.rb0000644005276200011600000001673113417161721025544 0ustar jenkinsjenkins# encoding: UTF-8 shared_examples_for "things that declare options" do it "should support options without arguments" do thing = add_options_to { option "--bar" } expect(thing).to be_option :bar end it "should support options with an empty block" do thing = add_options_to do option "--foo" do # this section deliberately left blank end end expect(thing).to be expect(thing).to be_option :foo end { "--foo=" => :foo }.each do |input, option| it "should accept #{name.inspect}" do thing = add_options_to { option input } expect(thing).to be_option option end end it "should support option documentation" do text = "Sturm und Drang (German pronunciation: [ËĘtĘŠĘm ĘŠnt ËdĘaĹ‹]) …" thing = add_options_to do option "--foo" do description text summary text end end expect(thing.get_option(:foo).description).to eq(text) end it "should list all the options" do thing = add_options_to do option "--foo" option "--bar", '-b' option "-q", "--quux" option "-f" option "--baz" end expect(thing.options).to eq([:foo, :bar, :quux, :f, :baz]) end it "should detect conflicts in long options" do expect { add_options_to do option "--foo" option "--foo" end }.to raise_error ArgumentError, /Option foo conflicts with existing option foo/i end it "should detect conflicts in short options" do expect { add_options_to do option "-f" option "-f" end }.to raise_error ArgumentError, /Option f conflicts with existing option f/ end ["-f", "--foo"].each do |option| ["", " FOO", "=FOO", " [FOO]", "=[FOO]"].each do |argument| input = option + argument it "should detect conflicts within a single option like #{input.inspect}" do expect { add_options_to do option input, input end }.to raise_error ArgumentError, /duplicates existing alias/ end end end # Verify the range of interesting conflicts to check for ordering causing # the behaviour to change, or anything exciting like that. [ %w{--foo}, %w{-f}, %w{-f --foo}, %w{--baz -f}, %w{-f --baz}, %w{-b --foo}, %w{--foo -b} ].each do |conflict| base = %w{--foo -f} it "should detect conflicts between #{base.inspect} and #{conflict.inspect}" do expect { add_options_to do option(*base) option(*conflict) end }.to raise_error ArgumentError, /conflicts with existing option/ end end it "should fail if we are not consistent about taking an argument" do expect { add_options_to do option "--foo=bar", "--bar" end }. to raise_error ArgumentError, /inconsistent about taking an argument/ end it "should not accept optional arguments" do expect do thing = add_options_to do option "--foo=[baz]", "--bar=[baz]" end [:foo, :bar].each do |name| expect(thing).to be_option name end end.to raise_error(ArgumentError, /optional arguments are not supported/) end describe "#takes_argument?" do it "should detect an argument being absent" do thing = add_options_to do option "--foo" end expect(thing.get_option(:foo)).not_to be_takes_argument end ["=FOO", " FOO"].each do |input| it "should detect an argument given #{input.inspect}" do thing = add_options_to do option "--foo#{input}" end expect(thing.get_option(:foo)).to be_takes_argument end end end describe "#optional_argument?" do it "should be false if no argument is present" do option = add_options_to do option "--foo" end.get_option(:foo) expect(option).not_to be_takes_argument expect(option).not_to be_optional_argument end ["=FOO", " FOO"].each do |input| it "should be false if the argument is mandatory (like #{input.inspect})" do option = add_options_to do option "--foo#{input}" end.get_option(:foo) expect(option).to be_takes_argument expect(option).not_to be_optional_argument end end ["=[FOO]", " [FOO]"].each do |input| it "should fail if the argument is optional (like #{input.inspect})" do expect do option = add_options_to do option "--foo#{input}" end.get_option(:foo) expect(option).to be_takes_argument expect(option).to be_optional_argument end.to raise_error(ArgumentError, /optional arguments are not supported/) end end end describe "#default_to" do it "should not have a default value by default" do option = add_options_to do option "--foo" end.get_option(:foo) expect(option).not_to be_has_default end it "should accept a block for the default value" do option = add_options_to do option "--foo" do default_to do 12 end end end.get_option(:foo) expect(option).to be_has_default end it "should invoke the block when asked for the default value" do invoked = false option = add_options_to do option "--foo" do default_to do invoked = true end end end.get_option(:foo) expect(option).to be_has_default expect(option.default).to be_truthy expect(invoked).to be_truthy end it "should return the value of the block when asked for the default" do option = add_options_to do option "--foo" do default_to do 12 end end end.get_option(:foo) expect(option).to be_has_default expect(option.default).to eq(12) end it "should invoke the block every time the default is requested" do option = add_options_to do option "--foo" do default_to do {} end end end.get_option(:foo) first = option.default.object_id second = option.default.object_id third = option.default.object_id expect(first).not_to eq(second) expect(first).not_to eq(third) expect(second).not_to eq(third) end it "should fail if the option has a default and is required" do expect { add_options_to do option "--foo" do required default_to do 12 end end end }.to raise_error ArgumentError, /can't be optional and have a default value/ expect { add_options_to do option "--foo" do default_to do 12 end required end end }.to raise_error ArgumentError, /can't be optional and have a default value/ end it "should fail if default_to has no block" do expect { add_options_to do option "--foo" do default_to end end }. to raise_error ArgumentError, /default_to requires a block/ end it "should fail if default_to is invoked twice" do expect { add_options_to do option "--foo" do default_to do 12 end default_to do "fun" end end end }.to raise_error ArgumentError, /already has a default value/ end [ "one", "one, two", "one, *two" ].each do |input| it "should fail if the block has the wrong arity (#{input})" do expect { add_options_to do option "--foo" do eval "default_to do |#{input}| 12 end" end end }.to raise_error ArgumentError, /should not take any arguments/ end end end end puppet-5.5.10/spec/shared_behaviours/file_serving_model.rb0000644005276200011600000000423113417161722023623 0ustar jenkinsjenkins#! /usr/bin/env ruby shared_examples_for "a file_serving model" do include PuppetSpec::Files describe "#indirection" do localpath = PuppetSpec::Files.make_absolute("/etc/sudoers") localurl = "file://" + localpath before :each do # Never connect to the network, no matter what described_class.indirection.terminus(:rest).class.any_instance.stubs(:find) end describe "when running the master application" do before :each do Puppet::Application[:master].setup_terminuses end { localpath => :file_server, localurl => :file_server, "puppet:///modules/foo/bar" => :file_server, "puppet://server/modules/foo/bar" => :file_server, }.each do |key, terminus| it "should use the #{terminus} terminus when requesting #{key.inspect}" do described_class.indirection.terminus(terminus).class.any_instance.expects(:find) described_class.indirection.find(key) end end end describe "when running the apply application" do before :each do Puppet[:default_file_terminus] = 'file_server' end { localpath => :file, localurl => :file, "puppet:///modules/foo/bar" => :file_server, "puppet://server/modules/foo/bar" => :rest, }.each do |key, terminus| it "should use the #{terminus} terminus when requesting #{key.inspect}" do described_class.indirection.terminus(terminus).class.any_instance.expects(:find) described_class.indirection.find(key) end end end describe "when running another application" do before :each do Puppet[:default_file_terminus] = 'rest' end { localpath => :file, localurl => :file, "puppet:///modules/foo/bar" => :rest, "puppet://server/modules/foo/bar" => :rest, }.each do |key, terminus| it "should use the #{terminus} terminus when requesting #{key.inspect}" do described_class.indirection.terminus(terminus).class.any_instance.expects(:find) described_class.indirection.find(key) end end end end end puppet-5.5.10/spec/shared_behaviours/path_parameters.rb0000644005276200011600000001234113417161722023147 0ustar jenkinsjenkins# In order to use this correctly you must define a method to get an instance # of the type being tested, so that this code can remain generic: # # it_should_behave_like "all path parameters", :path do # def instance(path) # Puppet::Type.type(:example).new( # :name => 'foo', :require => 'bar', :path_param => path # ) # end # # That method will be invoked for each test to create the instance that we # subsequently test through the system; you should ensure that the minimum of # possible attributes are set to keep the tests clean. # # You must also pass the symbolic name of the parameter being tested to the # block, and optionally can pass a hash of additional options to the block. # # The known options are: # :array :: boolean, does this support arrays of paths, default true. shared_examples_for "all pathname parameters with arrays" do |win32| path_types = { "unix absolute" => %q{/foo/bar}, "unix relative" => %q{foo/bar}, "win32 non-drive absolute" => %q{\foo\bar}, "win32 non-drive relative" => %q{foo\bar}, "win32 drive absolute" => %q{c:\foo\bar}, "win32 drive relative" => %q{c:foo\bar} } describe "when given an array of paths" do (1..path_types.length).each do |n| path_types.keys.combination(n) do |set| data = path_types.collect { |k, v| set.member?(k) ? v : nil } .compact has_relative = set.find { |k| k =~ /relative/ or k =~ /non-drive/ } has_windows = set.find { |k| k =~ /win32/ } has_unix = set.find { |k| k =~ /unix/ } if has_relative or (has_windows and !win32) or (has_unix and win32) reject = true else reject = false end it "should #{reject ? 'reject' : 'accept'} #{set.join(", ")}" do if reject then expect { instance(data) }. to raise_error Puppet::Error, /fully qualified/ else instance = instance(data) expect(instance[@param]).to eq(data) end end it "should #{reject ? 'reject' : 'accept'} #{set.join(", ")} doubled" do if reject then expect { instance(data + data) }. to raise_error Puppet::Error, /fully qualified/ else instance = instance(data + data) expect(instance[@param]).to eq(data + data) end end end end end end shared_examples_for "all path parameters" do |param, options| # Extract and process options to the block. options ||= {} array = options[:array].nil? ? true : options.delete(:array) if options.keys.length > 0 then fail "unknown options for 'all path parameters': " + options.keys.sort.join(', ') end def instance(path) fail "we didn't implement the 'instance(path)' method in the it_should_behave_like block" end ######################################################################## # The actual testing code... before :all do @param = param end describe "on a Unix-like platform it", :if => Puppet.features.posix? do if array then it_should_behave_like "all pathname parameters with arrays", false end it "should accept a fully qualified path" do path = File.join('', 'foo') instance = instance(path) expect(instance[@param]).to eq(path) end it "should give a useful error when the path is not absolute" do path = 'foo' expect { instance(path) }. to raise_error Puppet::Error, /fully qualified/ end { "Unix" => '/', "Win32" => '\\' }.each do |style, slash| %w{q Q a A z Z c C}.sort.each do |drive| it "should reject drive letter '#{drive}' with #{style} path separators" do path = "#{drive}:#{slash}Program Files" expect { instance(path) }. to raise_error Puppet::Error, /fully qualified/ end end end end describe "on a Windows-like platform it", :if => Puppet.features.microsoft_windows? do if array then it_should_behave_like "all pathname parameters with arrays", true end it "should reject a fully qualified unix path" do path = '/foo' expect { instance(path) }.to raise_error(Puppet::Error, /fully qualified/) end it "should give a useful error when the path is not absolute" do path = 'foo' expect { instance(path) }. to raise_error Puppet::Error, /fully qualified/ end it "also accepts Unix style path separators" do path = 'C:/Program Files' instance = instance(path) expect(instance[@param]).to eq(path) end { "Unix" => '/', "Win32" => '\\' }.each do |style, slash| %w{q Q a A z Z c C}.sort.each do |drive| it "should accept drive letter '#{drive}' with #{style} path separators " do path = "#{drive}:#{slash}Program Files" instance = instance(path) expect(instance[@param]).to eq(path) end end end { "UNC paths" => %q{\\\\foo\bar}, "unparsed local paths" => %q{\\\\?\c:\foo}, "unparsed UNC paths" => %q{\\\\?\foo\bar} }.each do |name, path| it "should accept #{name} as absolute" do instance = instance(path) expect(instance[@param]).to eq(path) end end end end puppet-5.5.10/spec/shared_contexts/0000755005276200011600000000000013417162176017146 5ustar jenkinsjenkinspuppet-5.5.10/spec/shared_contexts/checksum.rb0000644005276200011600000000510013417161721021264 0ustar jenkinsjenkins# Shared contexts for testing against all supported checksum types. # # These helpers define nested rspec example groups to test code against all our # supported checksum types. Example groups that need to be run against all # types should use the `with_checksum_types` helper which will # create a new example group for each types and will run the given block # in each example group. CHECKSUM_PLAINTEXT = "1\r\n"*4000 CHECKSUM_TYPES_TO_TRY = [ ['md5', 'a7a169ac84bb863b30484d0aa03139c1'], ['md5lite', '22b4182363e81b326e98231fde616782'], ['sha256', '47fcae62967db2fb5cba2fc0d9cf3e6767035d763d825ecda535a7b1928b9746'], ['sha256lite', 'fd50217a2b0286ba25121bf2297bbe6c197933992de67e4e568f19861444ecf8'], ['sha224', '6894cd976b60b2caa825bc699b54f715853659f0243f67cda4dd7ac4'], ['sha384', 'afc3d952fe1a4d3aa083d438ea464f6e7456c048d34ff554340721b463b38547e5ee7c964513dfba0d65dd91ac97deb5'], ['sha512', 'a953dcd95824cfa2a555651585d3980b1091a740a785d52ee5e72a55c9038242433e55026758636b0a29d0e5f9e77f24bc888ea5d5e01ab36d2bbcb3d3163859'] ] CHECKSUM_STAT_TIME = Time.now TIME_TYPES_TO_TRY = [ ['ctime', "#{CHECKSUM_STAT_TIME}"], ['mtime', "#{CHECKSUM_STAT_TIME}"] ] shared_context('with supported checksum types') do def self.with_checksum_types(path, file, &block) def checksum_valid(checksum_type, expected_checksum, actual_checksum_signature) case checksum_type when 'mtime', 'ctime' expect(DateTime.parse(actual_checksum_signature)).to be >= DateTime.parse(expected_checksum) else expect(actual_checksum_signature).to eq("{#{checksum_type}}#{expected_checksum}") end end def expect_correct_checksum(meta, checksum_type, checksum, type) expect(meta).to_not be_nil expect(meta).to be_instance_of(type) expect(meta.checksum_type).to eq(checksum_type) expect(checksum_valid(checksum_type, checksum, meta.checksum)).to be_truthy end (CHECKSUM_TYPES_TO_TRY + TIME_TYPES_TO_TRY).each do |checksum_type, checksum| describe("when checksum_type is #{checksum_type}") do let(:checksum_type) { checksum_type } let(:plaintext) { CHECKSUM_PLAINTEXT } let(:checksum) { checksum } let(:env_path) { tmpfile(path) } let(:checksum_file) { File.join(env_path, file) } def digest(content) Puppet::Util::Checksums.send(checksum_type, content) end before(:each) do FileUtils.mkdir_p(File.dirname(checksum_file)) File.open(checksum_file, "wb") { |f| f.write plaintext } end instance_eval(&block) end end end end puppet-5.5.10/spec/shared_contexts/digests.rb0000644005276200011600000000737613417161721021145 0ustar jenkinsjenkins# Shared contexts for testing against all supported digest algorithms. # # These helpers define nested rspec example groups to test code against all our # supported digest algorithms. Example groups that need to be run against all # algorithms should use the `with_digest_algorithms` helper which will # create a new example group for each algorithm and will run the given block # in each example group. # # For each algorithm a shared context is defined for the given algorithm that # has precomputed checksum values and paths. These contexts are included # automatically based on the rspec metadata selected with # `with_digest_algorithms`. DIGEST_ALGORITHMS_TO_TRY = ['md5', 'sha256', 'sha384', 'sha512', 'sha224'] shared_context('with supported digest algorithms', :uses_checksums => true) do def self.with_digest_algorithms(&block) DIGEST_ALGORITHMS_TO_TRY.each do |digest_algorithm| describe("when digest_algorithm is #{digest_algorithm}", :digest_algorithm => digest_algorithm) do instance_eval(&block) end end end end shared_context("when digest_algorithm is set to sha256", :digest_algorithm => 'sha256') do before { Puppet[:digest_algorithm] = 'sha256' } after { Puppet[:digest_algorithm] = nil } let(:digest_algorithm) { 'sha256' } let(:plaintext) { "my\r\ncontents" } let(:checksum) { '409a11465ed0938227128b1756c677a8480a8b84814f1963853775e15a74d4b4' } let(:bucket_dir) { '4/0/9/a/1/1/4/6/409a11465ed0938227128b1756c677a8480a8b84814f1963853775e15a74d4b4' } def digest(content) Puppet::Util::Checksums.sha256(content) end end shared_context("when digest_algorithm is set to md5", :digest_algorithm => 'md5') do before { Puppet[:digest_algorithm] = 'md5' } after { Puppet[:digest_algorithm] = nil } let(:digest_algorithm) { 'md5' } let(:plaintext) { "my\r\ncontents" } let(:checksum) { 'f0d7d4e480ad698ed56aeec8b6bd6dea' } let(:bucket_dir) { 'f/0/d/7/d/4/e/4/f0d7d4e480ad698ed56aeec8b6bd6dea' } def digest(content) Puppet::Util::Checksums.md5(content) end end shared_context("when digest_algorithm is set to sha512", :digest_algorithm => 'sha512') do before { Puppet[:digest_algorithm] = 'sha512' } after { Puppet[:digest_algorithm] = nil } let(:digest_algorithm) { 'sha512' } let(:plaintext) { "my\r\ncontents" } let(:checksum) { 'ed9b62ae313c8e4e3e6a96f937101e85f8f8af8d51dea7772177244087e5d6152778605ad6bdb42886ff1436abaec4fa44acbfe171fda755959b52b0e4e015d4' } let(:bucket_dir) { 'e/d/9/b/6/2/a/e/ed9b62ae313c8e4e3e6a96f937101e85f8f8af8d51dea7772177244087e5d6152778605ad6bdb42886ff1436abaec4fa44acbfe171fda755959b52b0e4e015d4' } def digest(content) Puppet::Util::Checksums.sha512(content) end end shared_context("when digest_algorithm is set to sha384", :digest_algorithm => 'sha384') do before { Puppet[:digest_algorithm] = 'sha384' } after { Puppet[:digest_algorithm] = nil } let(:digest_algorithm) { 'sha384' } let(:plaintext) { "my\r\ncontents" } let(:checksum) { 'f40debfec135e4f2b9fb92110c53aadb8e9bda28bb05f09901480fd70126fe3b70f9f074ce6182ec8184eb1bcabe4440' } let(:bucket_dir) { 'f/4/0/d/e/b/f/e/f40debfec135e4f2b9fb92110c53aadb8e9bda28bb05f09901480fd70126fe3b70f9f074ce6182ec8184eb1bcabe4440' } def digest(content) Puppet::Util::Checksums.sha384(content) end end shared_context("when digest_algorithm is set to sha224", :digest_algorithm => 'sha224') do before { Puppet[:digest_algorithm] = 'sha224' } after { Puppet[:digest_algorithm] = nil } let(:digest_algorithm) { 'sha224' } let(:plaintext) { "my\r\ncontents" } let(:checksum) { 'b8c05079b24c37a0e03f03e611167a3ea24455db3ad638a3a0c7e9cb' } let(:bucket_dir) { 'b/8/c/0/5/0/7/9/b8c05079b24c37a0e03f03e611167a3ea24455db3ad638a3a0c7e9cb' } def digest(content) Puppet::Util::Checksums.sha224(content) end end puppet-5.5.10/spec/shared_contexts/types_setup.rb0000644005276200011600000001436413417161721022062 0ustar jenkinsjenkinsshared_context 'types_setup' do # Do not include the special type Unit in this list # Do not include the type Variant in this list as it needs to be parameterized to be meaningful def self.all_types [ Puppet::Pops::Types::PAnyType, Puppet::Pops::Types::PUndefType, Puppet::Pops::Types::PNotUndefType, Puppet::Pops::Types::PScalarType, Puppet::Pops::Types::PScalarDataType, Puppet::Pops::Types::PStringType, Puppet::Pops::Types::PNumericType, Puppet::Pops::Types::PIntegerType, Puppet::Pops::Types::PFloatType, Puppet::Pops::Types::PRegexpType, Puppet::Pops::Types::PBooleanType, Puppet::Pops::Types::PCollectionType, Puppet::Pops::Types::PArrayType, Puppet::Pops::Types::PHashType, Puppet::Pops::Types::PIterableType, Puppet::Pops::Types::PIteratorType, Puppet::Pops::Types::PRuntimeType, Puppet::Pops::Types::PClassType, Puppet::Pops::Types::PResourceType, Puppet::Pops::Types::PPatternType, Puppet::Pops::Types::PEnumType, Puppet::Pops::Types::PStructType, Puppet::Pops::Types::PTupleType, Puppet::Pops::Types::PCallableType, Puppet::Pops::Types::PTypeType, Puppet::Pops::Types::POptionalType, Puppet::Pops::Types::PDefaultType, Puppet::Pops::Types::PTypeReferenceType, Puppet::Pops::Types::PTypeAliasType, Puppet::Pops::Types::PSemVerType, Puppet::Pops::Types::PSemVerRangeType, Puppet::Pops::Types::PTimespanType, Puppet::Pops::Types::PTimestampType, Puppet::Pops::Types::PSensitiveType, Puppet::Pops::Types::PBinaryType, Puppet::Pops::Types::PInitType, Puppet::Pops::Types::PURIType, ] end def all_types self.class.all_types end # Do not include the Variant type in this list - while it is abstract it is also special in that # it must be parameterized to be meaningful. # def self.abstract_types [ Puppet::Pops::Types::PAnyType, Puppet::Pops::Types::PCallableType, Puppet::Pops::Types::PEnumType, Puppet::Pops::Types::PClassType, Puppet::Pops::Types::PDefaultType, Puppet::Pops::Types::PCollectionType, Puppet::Pops::Types::PInitType, Puppet::Pops::Types::PIterableType, Puppet::Pops::Types::PIteratorType, Puppet::Pops::Types::PNotUndefType, Puppet::Pops::Types::PResourceType, Puppet::Pops::Types::PRuntimeType, Puppet::Pops::Types::POptionalType, Puppet::Pops::Types::PPatternType, Puppet::Pops::Types::PScalarType, Puppet::Pops::Types::PScalarDataType, Puppet::Pops::Types::PUndefType, Puppet::Pops::Types::PTypeReferenceType, Puppet::Pops::Types::PTypeAliasType, ] end def abstract_types self.class.abstract_types end # Internal types. Not meaningful in pp def self.internal_types [ Puppet::Pops::Types::PTypeReferenceType, Puppet::Pops::Types::PTypeAliasType, ] end def internal_types self.class.internal_types end def self.scalar_data_types # PVariantType is also scalar data, if its types are all ScalarData [ Puppet::Pops::Types::PScalarDataType, Puppet::Pops::Types::PStringType, Puppet::Pops::Types::PNumericType, Puppet::Pops::Types::PIntegerType, Puppet::Pops::Types::PFloatType, Puppet::Pops::Types::PBooleanType, Puppet::Pops::Types::PEnumType, Puppet::Pops::Types::PPatternType, ] end def scalar_data_types self.class.scalar_data_types end def self.scalar_types # PVariantType is also scalar, if its types are all Scalar [ Puppet::Pops::Types::PScalarType, Puppet::Pops::Types::PScalarDataType, Puppet::Pops::Types::PStringType, Puppet::Pops::Types::PNumericType, Puppet::Pops::Types::PIntegerType, Puppet::Pops::Types::PFloatType, Puppet::Pops::Types::PRegexpType, Puppet::Pops::Types::PBooleanType, Puppet::Pops::Types::PPatternType, Puppet::Pops::Types::PEnumType, Puppet::Pops::Types::PSemVerType, Puppet::Pops::Types::PTimespanType, Puppet::Pops::Types::PTimestampType, ] end def scalar_types self.class.scalar_types end def self.numeric_types # PVariantType is also numeric, if its types are all numeric [ Puppet::Pops::Types::PNumericType, Puppet::Pops::Types::PIntegerType, Puppet::Pops::Types::PFloatType, ] end def numeric_types self.class.numeric_types end def self.string_types # PVariantType is also string type, if its types are all compatible [ Puppet::Pops::Types::PStringType, Puppet::Pops::Types::PPatternType, Puppet::Pops::Types::PEnumType, ] end def string_types self.class.string_types end def self.collection_types # PVariantType is also string type, if its types are all compatible [ Puppet::Pops::Types::PCollectionType, Puppet::Pops::Types::PHashType, Puppet::Pops::Types::PArrayType, Puppet::Pops::Types::PStructType, Puppet::Pops::Types::PTupleType, ] end def collection_types self.class.collection_types end def self.data_compatible_types tf = Puppet::Pops::Types::TypeFactory result = scalar_data_types result << Puppet::Pops::Types::PArrayType.new(tf.data) result << Puppet::Pops::Types::PHashType.new(Puppet::Pops::Types::PStringType::DEFAULT, tf.data) result << Puppet::Pops::Types::PUndefType result << Puppet::Pops::Types::PTupleType.new([tf.data]) result end def data_compatible_types self.class.data_compatible_types end def self.rich_data_compatible_types tf = Puppet::Pops::Types::TypeFactory result = scalar_types result << Puppet::Pops::Types::PArrayType.new(tf.rich_data) result << Puppet::Pops::Types::PHashType.new(tf.rich_data_key, tf.rich_data) result << Puppet::Pops::Types::PUndefType result << Puppet::Pops::Types::PDefaultType result << Puppet::Pops::Types::PURIType result << Puppet::Pops::Types::PTupleType.new([tf.rich_data]) result << Puppet::Pops::Types::PObjectType result end def rich_data_compatible_types self.class.rich_data_compatible_types end def self.type_from_class(c) c.is_a?(Class) ? c::DEFAULT : c end def type_from_class(c) self.class.type_from_class(c) end end puppet-5.5.10/spec/shared_examples/0000755005276200011600000000000013417162176017115 5ustar jenkinsjenkinspuppet-5.5.10/spec/shared_examples/rhel_package_provider.rb0000644005276200011600000004012413417161721023755 0ustar jenkinsjenkinsshared_examples "RHEL package provider" do |provider_class, provider_name| describe provider_name do let(:name) { 'mypackage' } let(:resource) do Puppet::Type.type(:package).new( :name => name, :ensure => :installed, :provider => provider_name ) end let(:provider) do provider = provider_class.new provider.resource = resource provider end let(:arch) { 'x86_64' } let(:arch_resource) do Puppet::Type.type(:package).new( :name => "#{name}.#{arch}", :ensure => :installed, :provider => provider_name ) end let(:arch_provider) do provider = provider_class.new provider.resource = arch_resource provider end case provider_name when 'yum' let(:error_level) { '0' } when 'dnf' let(:error_level) { '1' } when 'tdnf' let(:error_level) { '1' } end case provider_name when 'yum' let(:upgrade_command) { 'update' } when 'dnf' let(:upgrade_command) { 'upgrade' } when 'tdnf' let(:upgrade_command) { 'upgrade' } end before do provider_class.stubs(:command).with(:cmd).returns("/usr/bin/#{provider_name}") provider.stubs(:rpm).returns 'rpm' provider.stubs(:get).with(:version).returns '1' provider.stubs(:get).with(:release).returns '1' provider.stubs(:get).with(:arch).returns 'i386' end describe 'provider features' do it { is_expected.to be_versionable } it { is_expected.to be_install_options } it { is_expected.to be_virtual_packages } end # provider should repond to the following methods [:install, :latest, :update, :purge, :install_options].each do |method| it "should have a(n) #{method}" do expect(provider).to respond_to(method) end end describe 'when installing' do before(:each) do Puppet::Util.stubs(:which).with("rpm").returns("/bin/rpm") provider.stubs(:which).with("rpm").returns("/bin/rpm") Puppet::Util::Execution.expects(:execute).with(["/bin/rpm", "--version"], {:combine => true, :custom_environment => {}, :failonfail => true}).returns(Puppet::Util::Execution::ProcessOutput.new("4.10.1\n", 0)).at_most_once Facter.stubs(:value).with(:operatingsystemmajrelease).returns('6') end it "should call #{provider_name} install for :installed" do resource.stubs(:should).with(:ensure).returns :installed Puppet::Util::Execution.expects(:execute).with(["/usr/bin/#{provider_name}", '-d', '0', '-e', error_level, '-y', :install, 'mypackage']) provider.install end if provider_name == 'yum' context 'on el-5' do before(:each) do Facter.stubs(:value).with(:operatingsystemmajrelease).returns('5') end it "should catch #{provider_name} install failures when status code is wrong" do resource.stubs(:should).with(:ensure).returns :installed Puppet::Util::Execution.expects(:execute).with(["/usr/bin/#{provider_name}", '-e', error_level, '-y', :install, name]).returns(Puppet::Util::Execution::ProcessOutput.new("No package #{name} available.", 0)) expect { provider.install }.to raise_error(Puppet::Error, "Could not find package #{name}") end end end it 'should use :install to update' do provider.expects(:install) provider.update end it 'should be able to set version' do version = '1.2' resource[:ensure] = version Puppet::Util::Execution.expects(:execute).with(["/usr/bin/#{provider_name}", '-d', '0', '-e', error_level, '-y', :install, "#{name}-#{version}"]) provider.stubs(:query).returns :ensure => version provider.install end it 'should handle partial versions specified' do version = '1.3.4' resource[:ensure] = version Puppet::Util::Execution.expects(:execute).with(["/usr/bin/#{provider_name}", '-d', '0', '-e', error_level, '-y', :install, 'mypackage-1.3.4']) provider.stubs(:query).returns :ensure => '1.3.4-1.el6' provider.install end it 'should be able to downgrade' do current_version = '1.2' version = '1.0' resource[:ensure] = '1.0' Puppet::Util::Execution.expects(:execute).with(["/usr/bin/#{provider_name}", '-d', '0', '-e', error_level, '-y', :downgrade, "#{name}-#{version}"]) provider.stubs(:query).returns(:ensure => current_version).then.returns(:ensure => version) provider.install end it 'should be able to upgrade' do current_version = '1.0' version = '1.2' resource[:ensure] = '1.2' Puppet::Util::Execution.expects(:execute).with(["/usr/bin/#{provider_name}", '-d', '0', '-e', error_level, '-y', upgrade_command, "#{name}-#{version}"]) provider.stubs(:query).returns(:ensure => current_version).then.returns(:ensure => version) provider.install end it 'should not run upgrade command if absent and ensure latest' do current_version = '' version = '1.2' resource[:ensure] = :latest Puppet::Util::Execution.expects(:execute).with(["/usr/bin/#{provider_name}", '-d', '0', '-e', error_level, '-y', :install, name]) provider.stubs(:query).returns(:ensure => current_version).then.returns(:ensure => version) provider.install end it 'should run upgrade command if present and ensure latest' do current_version = '1.0' version = '1.2' resource[:ensure] = :latest Puppet::Util::Execution.expects(:execute).with(["/usr/bin/#{provider_name}", '-d', '0', '-e', error_level, '-y', upgrade_command, name]) provider.stubs(:query).returns(:ensure => current_version).then.returns(:ensure => version) provider.install end it 'should accept install options' do resource[:ensure] = :installed resource[:install_options] = ['-t', {'-x' => 'expackage'}] Puppet::Util::Execution.expects(:execute).with(["/usr/bin/#{provider_name}", '-d', '0', '-e', error_level, '-y', ['-t', '-x=expackage'], :install, name]) provider.install end it 'allow virtual packages' do resource[:ensure] = :installed resource[:allow_virtual] = true Puppet::Util::Execution.expects(:execute).with(["/usr/bin/#{provider_name}", '-d', '0', '-e', error_level, '-y', :list, name]).never Puppet::Util::Execution.expects(:execute).with(["/usr/bin/#{provider_name}", '-d', '0', '-e', error_level, '-y', :install, name]) provider.install end it 'moves architecture to end of version' do version = '1.2.3' arch_resource[:ensure] = version Puppet::Util::Execution.expects(:execute).with(["/usr/bin/#{provider_name}", '-d', '0', '-e', error_level, '-y', :install, "#{name}-#{version}.#{arch}"]) arch_provider.stubs(:query).returns :ensure => version arch_provider.install end end describe 'when uninstalling' do it 'should use erase to purge' do Puppet::Util::Execution.expects(:execute).with(["/usr/bin/#{provider_name}", '-y', :erase, name]) provider.purge end end it 'should be versionable' do expect(provider).to be_versionable end describe 'determining the latest version available for a package' do it "passes the value of enablerepo install_options when querying" do resource[:install_options] = [ {'--enablerepo' => 'contrib'}, {'--enablerepo' => 'centosplus'}, ] provider.stubs(:properties).returns({:ensure => '3.4.5'}) described_class.expects(:latest_package_version).with(name, [], ['contrib', 'centosplus'], []) provider.latest end it "passes the value of disablerepo install_options when querying" do resource[:install_options] = [ {'--disablerepo' => 'updates'}, {'--disablerepo' => 'centosplus'}, ] provider.stubs(:properties).returns({:ensure => '3.4.5'}) described_class.expects(:latest_package_version).with(name, ['updates', 'centosplus'], [], []) provider.latest end it "passes the value of disableexcludes install_options when querying" do resource[:install_options] = [ {'--disableexcludes' => 'main'}, {'--disableexcludes' => 'centosplus'}, ] provider.stubs(:properties).returns({:ensure => '3.4.5'}) described_class.expects(:latest_package_version).with(name, [], [], ['main', 'centosplus']) provider.latest end describe 'and a newer version is not available' do before :each do described_class.stubs(:latest_package_version).with(name, [], [], []).returns nil end it 'raises an error the package is not installed' do provider.stubs(:properties).returns({:ensure => :absent}) expect { provider.latest }.to raise_error(Puppet::DevError, 'Tried to get latest on a missing package') end it 'returns version of the currently installed package' do provider.stubs(:properties).returns({:ensure => '3.4.5'}) expect(provider.latest).to eq('3.4.5') end end describe 'and a newer version is available' do let(:latest_version) do { :name => name, :epoch => '1', :version => '2.3.4', :release => '5', :arch => 'i686', } end it 'includes the epoch in the version string' do described_class.stubs(:latest_package_version).with(name, [], [], []).returns(latest_version) expect(provider.latest).to eq('1:2.3.4-5') end end end describe "lazy loading of latest package versions" do before { described_class.clear } after { described_class.clear } let(:mypackage_version) do { :name => name, :epoch => '1', :version => '2.3.4', :release => '5', :arch => 'i686', } end let(:mypackage_newerversion) do { :name => name, :epoch => '1', :version => '4.5.6', :release => '7', :arch => 'i686', } end let(:latest_versions) { {name => [mypackage_version]} } let(:enabled_versions) { {name => [mypackage_newerversion]} } it "returns the version hash if the package was found" do described_class.expects(:check_updates).with([], [], []).once.returns(latest_versions) version = described_class.latest_package_version(name, [], [], []) expect(version).to eq(mypackage_version) end it "is nil if the package was not found in the query" do described_class.expects(:check_updates).with([], [], []).once.returns(latest_versions) version = described_class.latest_package_version('nopackage', [], [], []) expect(version).to be_nil end it "caches the package list and reuses that for subsequent queries" do described_class.expects(:check_updates).with([], [], []).once.returns(latest_versions) 2.times { version = described_class.latest_package_version(name, [], [], []) expect(version).to eq mypackage_version } end it "caches separate lists for each combination of 'disablerepo' and 'enablerepo' and 'disableexcludes'" do described_class.expects(:check_updates).with([], [], []).once.returns(latest_versions) described_class.expects(:check_updates).with(['disabled'], ['enabled'], ['disableexcludes']).once.returns(enabled_versions) 2.times { version = described_class.latest_package_version(name, [], [], []) expect(version).to eq mypackage_version } 2.times { version = described_class.latest_package_version(name, ['disabled'], ['enabled'], ['disableexcludes']) expect(version).to eq(mypackage_newerversion) } end end describe "executing #{provider_name} check-update" do it "passes repos to enable to '#{provider_name} check-update'" do Puppet::Util::Execution.expects(:execute).with do |args, *rest| expect(args).to eq %W[/usr/bin/#{provider_name} check-update --enablerepo=updates --enablerepo=centosplus] end.returns(stub(:exitstatus => 0)) described_class.check_updates([], %W[updates centosplus], []) end it "passes repos to disable to '#{provider_name} check-update'" do Puppet::Util::Execution.expects(:execute).with do |args, *rest| expect(args).to eq %W[/usr/bin/#{provider_name} check-update --disablerepo=updates --disablerepo=centosplus] end.returns(stub(:exitstatus => 0)) described_class.check_updates(%W[updates centosplus], [], []) end it "passes a combination of repos to enable and disable to '#{provider_name} check-update'" do Puppet::Util::Execution.expects(:execute).with do |args, *rest| expect(args).to eq %W[/usr/bin/#{provider_name} check-update --disablerepo=updates --disablerepo=centosplus --enablerepo=os --enablerepo=contrib ] end.returns(stub(:exitstatus => 0)) described_class.check_updates(%W[updates centosplus], %W[os contrib], []) end it "passes disableexcludes to '#{provider_name} check-update'" do Puppet::Util::Execution.expects(:execute).with do |args, *rest| expect(args).to eq %W[/usr/bin/#{provider_name} check-update --disableexcludes=main --disableexcludes=centosplus] end.returns(stub(:exitstatus => 0)) described_class.check_updates([], [], %W[main centosplus]) end it "passes all options to '#{provider_name} check-update'" do Puppet::Util::Execution.expects(:execute).with do |args, *rest| expect(args).to eq %W[/usr/bin/#{provider_name} check-update --disablerepo=a --disablerepo=b --enablerepo=c --enablerepo=d --disableexcludes=e --disableexcludes=f] end.returns(stub(:exitstatus => 0)) described_class.check_updates(%W[a b], %W[c d], %W[e f]) end it "returns an empty hash if '#{provider_name} check-update' returned 0" do Puppet::Util::Execution.expects(:execute).returns(stub :exitstatus => 0) expect(described_class.check_updates([], [], [])).to be_empty end it "returns a populated hash if '#{provider_name} check-update returned 100'" do output = stub(:exitstatus => 100) Puppet::Util::Execution.expects(:execute).returns(output) described_class.expects(:parse_updates).with(output).returns({:has => :updates}) expect(described_class.check_updates([], [], [])).to eq({:has => :updates}) end it "returns an empty hash if '#{provider_name} check-update' returned an exit code that was not 0 or 100" do Puppet::Util::Execution.expects(:execute).returns(stub(:exitstatus => 1)) described_class.expects(:warning).with("Could not check for updates, \'/usr/bin/#{provider_name} check-update\' exited with 1") expect(described_class.check_updates([], [], [])).to eq({}) end end describe "parsing a line from #{provider_name} check-update" do it "splits up the package name and architecture fields" do checkupdate = %W[curl.i686 7.32.0-10.fc20] parsed = described_class.update_to_hash(*checkupdate) expect(parsed[:name]).to eq 'curl' expect(parsed[:arch]).to eq 'i686' end it "splits up the epoch, version, and release fields" do checkupdate = %W[dhclient.i686 12:4.1.1-38.P1.el6.centos] parsed = described_class.update_to_hash(*checkupdate) expect(parsed[:epoch]).to eq '12' expect(parsed[:version]).to eq '4.1.1' expect(parsed[:release]).to eq '38.P1.el6.centos' end it "sets the epoch to 0 when an epoch is not specified" do checkupdate = %W[curl.i686 7.32.0-10.fc20] parsed = described_class.update_to_hash(*checkupdate) expect(parsed[:epoch]).to eq '0' expect(parsed[:version]).to eq '7.32.0' expect(parsed[:release]).to eq '10.fc20' end end end end puppet-5.5.10/spec/unit/0000755005276200011600000000000013417162177014731 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/agent/0000755005276200011600000000000013417162176016026 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/agent/disabler_spec.rb0000644005276200011600000000401313417161721021143 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/agent' require 'puppet/agent/locker' class DisablerTester include Puppet::Agent::Disabler end describe Puppet::Agent::Disabler do before do @disabler = DisablerTester.new end ## These tests are currently very implementation-specific, and they rely heavily on ## having access to the "disable_lockfile" method. However, I've made this method private ## because it really shouldn't be exposed outside of our implementation... therefore ## these tests have to use a lot of ".send" calls. They should probably be cleaned up ## but for the moment I wanted to make sure not to lose any of the functionality of ## the tests. --cprice 2012-04-16 it "should use an JsonLockfile instance as its disable_lockfile" do expect(@disabler.send(:disable_lockfile)).to be_instance_of(Puppet::Util::JsonLockfile) end it "should use puppet's :agent_disabled_lockfile' setting to determine its lockfile path" do lockfile = File.expand_path("/my/lock.disabled") Puppet[:agent_disabled_lockfile] = lockfile lock = Puppet::Util::JsonLockfile.new(lockfile) Puppet::Util::JsonLockfile.expects(:new).with(lockfile).returns lock @disabler.send(:disable_lockfile) end it "should reuse the same lock file each time" do expect(@disabler.send(:disable_lockfile)).to equal(@disabler.send(:disable_lockfile)) end it "should lock the file when disabled" do @disabler.send(:disable_lockfile).expects(:lock) @disabler.disable end it "should unlock the file when enabled" do @disabler.send(:disable_lockfile).expects(:unlock) @disabler.enable end it "should check the lock if it is disabled" do @disabler.send(:disable_lockfile).expects(:locked?) @disabler.disabled? end it "should report the disable message when disabled" do Puppet[:agent_disabled_lockfile] = PuppetSpec::Files.tmpfile("lock") msg = "I'm busy, go away" @disabler.disable(msg) expect(@disabler.disable_message).to eq(msg) end end puppet-5.5.10/spec/unit/agent/locker_spec.rb0000644005276200011600000000641713417161721020647 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/agent' require 'puppet/agent/locker' class LockerTester include Puppet::Agent::Locker end describe Puppet::Agent::Locker do before do @locker = LockerTester.new end ## These tests are currently very implementation-specific, and they rely heavily on ## having access to the lockfile object. However, I've made this method private ## because it really shouldn't be exposed outside of our implementation... therefore ## these tests have to use a lot of ".send" calls. They should probably be cleaned up ## but for the moment I wanted to make sure not to lose any of the functionality of ## the tests. --cprice 2012-04-16 it "should use a Pidlock instance as its lockfile" do expect(@locker.send(:lockfile)).to be_instance_of(Puppet::Util::Pidlock) end it "should use puppet's agent_catalog_run_lockfile' setting to determine its lockfile path" do lockfile = File.expand_path("/my/lock") Puppet[:agent_catalog_run_lockfile] = lockfile lock = Puppet::Util::Pidlock.new(lockfile) Puppet::Util::Pidlock.expects(:new).with(lockfile).returns lock @locker.send(:lockfile) end it "#lockfile_path provides the path to the lockfile" do lockfile = File.expand_path("/my/lock") Puppet[:agent_catalog_run_lockfile] = lockfile expect(@locker.lockfile_path).to eq(File.expand_path("/my/lock")) end it "should reuse the same lock file each time" do expect(@locker.send(:lockfile)).to equal(@locker.send(:lockfile)) end it "should have a method that yields when a lock is attained" do @locker.send(:lockfile).expects(:lock).returns true yielded = false @locker.lock do yielded = true end expect(yielded).to be_truthy end it "should return the block result when the lock method successfully locked" do @locker.send(:lockfile).expects(:lock).returns true expect(@locker.lock { :result }).to eq(:result) end it "should raise LockError when the lock method does not receive the lock" do @locker.send(:lockfile).expects(:lock).returns false expect { @locker.lock {} }.to raise_error(Puppet::LockError) end it "should not yield when the lock method does not receive the lock" do @locker.send(:lockfile).expects(:lock).returns false yielded = false expect { @locker.lock { yielded = true } }.to raise_error(Puppet::LockError) expect(yielded).to be_falsey end it "should not unlock when a lock was not received" do @locker.send(:lockfile).expects(:lock).returns false @locker.send(:lockfile).expects(:unlock).never expect { @locker.lock {} }.to raise_error(Puppet::LockError) end it "should unlock after yielding upon obtaining a lock" do @locker.send(:lockfile).stubs(:lock).returns true @locker.send(:lockfile).expects(:unlock) @locker.lock {} end it "should unlock after yielding upon obtaining a lock, even if the block throws an exception" do @locker.send(:lockfile).stubs(:lock).returns true @locker.send(:lockfile).expects(:unlock) expect { @locker.lock { raise "foo" } }.to raise_error(RuntimeError) end it "should be considered running if the lockfile is locked" do @locker.send(:lockfile).expects(:locked?).returns true expect(@locker).to be_running end end puppet-5.5.10/spec/unit/application/0000755005276200011600000000000013417162176017233 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/application/config_spec.rb0000644005276200011600000000066213417161721022036 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/application/config' describe Puppet::Application::Config do it "should be a subclass of Puppet::Application::FaceBase" do expect(Puppet::Application::Config.superclass).to equal(Puppet::Application::FaceBase) end it "should set `environment_mode` to :not_required" do expect(Puppet::Application::Config.get_environment_mode).to equal(:not_required) end end puppet-5.5.10/spec/unit/application/describe_spec.rb0000644005276200011600000000504113417161721022345 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/application/describe' describe Puppet::Application::Describe do before :each do @describe = Puppet::Application[:describe] end it "should declare a main command" do expect(@describe).to respond_to(:main) end it "should declare a preinit block" do expect(@describe).to respond_to(:preinit) end [:providers,:list,:meta].each do |option| it "should declare handle_#{option} method" do expect(@describe).to respond_to("handle_#{option}".to_sym) end it "should store argument value when calling handle_#{option}" do @describe.options.expects(:[]=).with("#{option}".to_sym, 'arg') @describe.send("handle_#{option}".to_sym, 'arg') end end describe "in preinit" do it "should set options[:parameters] to true" do @describe.preinit expect(@describe.options[:parameters]).to be_truthy end end describe "when handling parameters" do it "should set options[:parameters] to false" do @describe.handle_short(nil) expect(@describe.options[:parameters]).to be_falsey end end describe "during setup" do it "should collect arguments in options[:types]" do @describe.command_line.stubs(:args).returns(['1','2']) @describe.setup expect(@describe.options[:types]).to eq(['1','2']) end end describe "when running" do before :each do @typedoc = stub 'type_doc' TypeDoc.stubs(:new).returns(@typedoc) end it "should call list_types if options list is set" do @describe.options[:list] = true @typedoc.expects(:list_types) @describe.run_command end it "should call format_type for each given types" do @describe.options[:list] = false @describe.options[:types] = ['type'] @typedoc.expects(:format_type).with('type', @describe.options) @describe.run_command end end it "should format text with long non-space runs without garbling" do @f = Formatter.new(76) @teststring = < 0, :scrub => true}) expect(result).to eql(@expected_result) end end puppet-5.5.10/spec/unit/application/doc_spec.rb0000644005276200011600000002370613417161721021342 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/application/doc' require 'puppet/util/reference' require 'puppet/util/rdoc' describe Puppet::Application::Doc do before :each do @doc = Puppet::Application[:doc] @doc.stubs(:puts) @doc.preinit Puppet::Util::Log.stubs(:newdestination) end it "should declare an other command" do expect(@doc).to respond_to(:other) end it "should declare a rdoc command" do expect(@doc).to respond_to(:rdoc) end it "should declare a fallback for unknown options" do expect(@doc).to respond_to(:handle_unknown) end it "should declare a preinit block" do expect(@doc).to respond_to(:preinit) end describe "in preinit" do it "should set references to []" do @doc.preinit expect(@doc.options[:references]).to eq([]) end it "should init mode to text" do @doc.preinit expect(@doc.options[:mode]).to eq(:text) end it "should init format to to_markdown" do @doc.preinit expect(@doc.options[:format]).to eq(:to_markdown) end end describe "when handling options" do [:all, :outputdir, :verbose, :debug, :charset].each do |option| it "should declare handle_#{option} method" do expect(@doc).to respond_to("handle_#{option}".to_sym) end it "should store argument value when calling handle_#{option}" do @doc.options.expects(:[]=).with(option, 'arg') @doc.send("handle_#{option}".to_sym, 'arg') end end it "should store the format if valid" do Puppet::Util::Reference.stubs(:method_defined?).with('to_format').returns(true) @doc.handle_format('format') expect(@doc.options[:format]).to eq('to_format') end it "should raise an error if the format is not valid" do Puppet::Util::Reference.stubs(:method_defined?).with('to_format').returns(false) expect { @doc.handle_format('format') }.to raise_error(RuntimeError, /Invalid output format/) end it "should store the mode if valid" do Puppet::Util::Reference.stubs(:modes).returns(stub('mode', :include? => true)) @doc.handle_mode('mode') expect(@doc.options[:mode]).to eq(:mode) end it "should store the mode if :rdoc" do Puppet::Util::Reference.modes.stubs(:include?).with('rdoc').returns(false) @doc.handle_mode('rdoc') expect(@doc.options[:mode]).to eq(:rdoc) end it "should raise an error if the mode is not valid" do Puppet::Util::Reference.modes.stubs(:include?).with('unknown').returns(false) expect { @doc.handle_mode('unknown') }.to raise_error(RuntimeError, /Invalid output mode/) end it "should list all references on list and exit" do reference = stubs 'reference' ref = stubs 'ref' Puppet::Util::Reference.stubs(:references).returns([reference]) Puppet::Util::Reference.expects(:reference).with(reference).returns(ref) ref.expects(:doc) expect { @doc.handle_list(nil) }.to exit_with 0 end it "should add reference to references list with --reference" do @doc.options[:references] = [:ref1] @doc.handle_reference('ref2') expect(@doc.options[:references]).to eq([:ref1,:ref2]) end end describe "during setup" do before :each do Puppet::Log.stubs(:newdestination) @doc.command_line.stubs(:args).returns([]) end it "should default to rdoc mode if there are command line arguments" do @doc.command_line.stubs(:args).returns(["1"]) @doc.stubs(:setup_rdoc) @doc.setup expect(@doc.options[:mode]).to eq(:rdoc) end it "should call setup_rdoc in rdoc mode" do @doc.options[:mode] = :rdoc @doc.expects(:setup_rdoc) @doc.setup end it "should call setup_reference if not rdoc" do @doc.options[:mode] = :test @doc.expects(:setup_reference) @doc.setup end describe "configuring logging" do before :each do Puppet::Util::Log.stubs(:newdestination) end describe "with --debug" do before do @doc.options[:debug] = true end it "should set log level to debug" do @doc.setup expect(Puppet::Util::Log.level).to eq(:debug) end it "should set log destination to console" do Puppet::Util::Log.expects(:newdestination).with(:console) @doc.setup end end describe "with --verbose" do before do @doc.options[:verbose] = true end it "should set log level to info" do @doc.setup expect(Puppet::Util::Log.level).to eq(:info) end it "should set log destination to console" do Puppet::Util::Log.expects(:newdestination).with(:console) @doc.setup end end describe "without --debug or --verbose" do before do @doc.options[:debug] = false @doc.options[:verbose] = false end it "should set log level to warning" do @doc.setup expect(Puppet::Util::Log.level).to eq(:warning) end it "should set log destination to console" do Puppet::Util::Log.expects(:newdestination).with(:console) @doc.setup end end end describe "in non-rdoc mode" do it "should get all non-dynamic reference if --all" do @doc.options[:all] = true static = stub 'static', :dynamic? => false dynamic = stub 'dynamic', :dynamic? => true Puppet::Util::Reference.stubs(:reference).with(:static).returns(static) Puppet::Util::Reference.stubs(:reference).with(:dynamic).returns(dynamic) Puppet::Util::Reference.stubs(:references).returns([:static,:dynamic]) @doc.setup_reference expect(@doc.options[:references]).to eq([:static]) end it "should default to :type if no references" do @doc.setup_reference expect(@doc.options[:references]).to eq([:type]) end end describe "in rdoc mode" do describe "when there are unknown args" do it "should expand --modulepath if any" do @doc.unknown_args = [ { :opt => "--modulepath", :arg => "path" } ] Puppet.settings.stubs(:handlearg) @doc.setup_rdoc expect(@doc.unknown_args[0][:arg]).to eq(File.expand_path('path')) end it "should give them to Puppet.settings" do @doc.unknown_args = [ { :opt => :option, :arg => :argument } ] Puppet.settings.expects(:handlearg).with(:option,:argument) @doc.setup_rdoc end end it "should operate in master run_mode" do expect(@doc.class.run_mode.name).to eq(:master) @doc.setup_rdoc end end end describe "when running" do describe "in rdoc mode" do include PuppetSpec::Files let(:envdir) { tmpdir('env') } let(:modules) { File.join(envdir, "modules") } let(:modules2) { File.join(envdir, "modules2") } let(:manifests) { File.join(envdir, "manifests") } before :each do @doc.manifest = false Puppet.stubs(:info) Puppet[:trace] = false Puppet[:modulepath] = modules Puppet[:manifest] = manifests @doc.options[:all] = false @doc.options[:outputdir] = 'doc' @doc.options[:charset] = nil Puppet.settings.stubs(:define_settings) Puppet::Util::RDoc.stubs(:rdoc) @doc.command_line.stubs(:args).returns([]) end around(:each) do |example| FileUtils.mkdir_p(modules) env = Puppet::Node::Environment.create(Puppet[:environment].to_sym, [modules], "#{manifests}/site.pp") Puppet.override({:environments => Puppet::Environments::Static.new(env), :current_environment => env}) do example.run end end it "should set document_all on --all" do @doc.options[:all] = true Puppet.settings.expects(:[]=).with(:document_all, true) expect { @doc.rdoc }.to exit_with(0) end it "should call Puppet::Util::RDoc.rdoc in full mode" do Puppet::Util::RDoc.expects(:rdoc).with('doc', [modules, manifests], nil) expect { @doc.rdoc }.to exit_with(0) end it "should call Puppet::Util::RDoc.rdoc with a charset if --charset has been provided" do @doc.options[:charset] = 'utf-8' Puppet::Util::RDoc.expects(:rdoc).with('doc', [modules, manifests], "utf-8") expect { @doc.rdoc }.to exit_with(0) end it "should call Puppet::Util::RDoc.rdoc in full mode with outputdir set to doc if no --outputdir" do @doc.options[:outputdir] = false Puppet::Util::RDoc.expects(:rdoc).with('doc', [modules, manifests], nil) expect { @doc.rdoc }.to exit_with(0) end it "should call Puppet::Util::RDoc.manifestdoc in manifest mode" do @doc.manifest = true Puppet::Util::RDoc.expects(:manifestdoc) expect { @doc.rdoc }.to exit_with(0) end it "should get modulepath and manifest values from the environment" do FileUtils.mkdir_p(modules) FileUtils.mkdir_p(modules2) env = Puppet::Node::Environment.create(Puppet[:environment].to_sym, [modules, modules2], "envmanifests/site.pp") Puppet.override({:environments => Puppet::Environments::Static.new(env), :current_environment => env}) do Puppet::Util::RDoc.stubs(:rdoc).with('doc', [modules.to_s, modules2.to_s, env.manifest.to_s], nil) expect { @doc.rdoc }.to exit_with(0) end end end describe "in the other modes" do it "should get reference in given format" do reference = stub 'reference' @doc.options[:mode] = :none @doc.options[:references] = [:ref] Puppet::Util::Reference.expects(:reference).with(:ref).returns(reference) @doc.options[:format] = :format @doc.stubs(:exit) reference.expects(:send).with { |format,contents| format == :format }.returns('doc') @doc.other end end end end puppet-5.5.10/spec/unit/application/facts_spec.rb0000644005276200011600000000123413417161721021665 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/application/facts' describe Puppet::Application::Facts do before :each do subject.command_line.stubs(:subcommand_name).returns 'facts' end it "should return facts if a key is given to find" do Puppet::Node::Facts.indirection.reset_terminus_class Puppet::Node::Facts.indirection.expects(:find).returns(Puppet::Node::Facts.new('whatever', {})) subject.command_line.stubs(:args).returns %w{find whatever --render-as yaml} expect { expect { subject.run }.to exit_with(0) }.to have_printed(/object:Puppet::Node::Facts/) expect(@logs).to be_empty end end puppet-5.5.10/spec/unit/application/filebucket_spec.rb0000644005276200011600000002142013417161721022701 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/application/filebucket' require 'puppet/file_bucket/dipper' describe Puppet::Application::Filebucket do before :each do @filebucket = Puppet::Application[:filebucket] end it "should declare a get command" do expect(@filebucket).to respond_to(:get) end it "should declare a backup command" do expect(@filebucket).to respond_to(:backup) end it "should declare a restore command" do expect(@filebucket).to respond_to(:restore) end it "should declare a diff command" do expect(@filebucket).to respond_to(:diff) end it "should declare a list command" do expect(@filebucket).to respond_to(:list) end [:bucket, :debug, :local, :remote, :verbose, :fromdate, :todate].each do |option| it "should declare handle_#{option} method" do expect(@filebucket).to respond_to("handle_#{option}".to_sym) end it "should store argument value when calling handle_#{option}" do @filebucket.options.expects(:[]=).with("#{option}".to_sym, 'arg') @filebucket.send("handle_#{option}".to_sym, 'arg') end end describe "during setup" do before :each do Puppet::Log.stubs(:newdestination) Puppet.stubs(:settraps) Puppet::FileBucket::Dipper.stubs(:new) @filebucket.options.stubs(:[]).with(any_parameters) end it "should set console as the log destination" do Puppet::Log.expects(:newdestination).with(:console) @filebucket.setup end it "should trap INT" do Signal.expects(:trap).with(:INT) @filebucket.setup end it "should set log level to debug if --debug was passed" do @filebucket.options.stubs(:[]).with(:debug).returns(true) @filebucket.setup expect(Puppet::Log.level).to eq(:debug) end it "should set log level to info if --verbose was passed" do @filebucket.options.stubs(:[]).with(:verbose).returns(true) @filebucket.setup expect(Puppet::Log.level).to eq(:info) end it "should print puppet config if asked to in Puppet config" do Puppet.settings.stubs(:print_configs?).returns(true) Puppet.settings.expects(:print_configs).returns(true) expect { @filebucket.setup }.to exit_with 0 end it "should exit after printing puppet config if asked to in Puppet config" do Puppet.settings.stubs(:print_configs?).returns(true) expect { @filebucket.setup }.to exit_with 1 end describe "with local bucket" do let(:path) { File.expand_path("path") } before :each do @filebucket.options.stubs(:[]).with(:local).returns(true) end it "should create a client with the default bucket if none passed" do Puppet[:clientbucketdir] = path Puppet[:bucketdir] = path + "2" Puppet::FileBucket::Dipper.expects(:new).with { |h| h[:Path] == path } @filebucket.setup end it "should create a local Dipper with the given bucket" do @filebucket.options.stubs(:[]).with(:bucket).returns(path) Puppet::FileBucket::Dipper.expects(:new).with { |h| h[:Path] == path } @filebucket.setup end end describe "with remote bucket" do it "should create a remote Client to the configured server" do Puppet[:server] = "puppet.reductivelabs.com" Puppet::FileBucket::Dipper.expects(:new).with { |h| h[:Server] == "puppet.reductivelabs.com" } @filebucket.setup end it "should default to the first server_list entry if set" do Puppet[:server_list] = "foo,bar,baz" Puppet::FileBucket::Dipper.expects(:new).with { |h| h[:Server] == "foo" } @filebucket.setup end it "should fall back to server if server_list is empty" do Puppet[:server_list] = "" Puppet::FileBucket::Dipper.expects(:new).with { |h| h[:Server] == "puppet" } @filebucket.setup end it "should take both the server and port specified in server_list" do Puppet[:server_list] = "foo:632,bar:6215,baz:351" Puppet::FileBucket::Dipper.expects(:new).with({ :Server => "foo", :Port => "632" }) @filebucket.setup end end end describe "when running" do before :each do Puppet::Log.stubs(:newdestination) Puppet.stubs(:settraps) Puppet::FileBucket::Dipper.stubs(:new) @filebucket.options.stubs(:[]).with(any_parameters) @client = stub 'client' Puppet::FileBucket::Dipper.stubs(:new).returns(@client) @filebucket.setup end it "should use the first non-option parameter as the dispatch" do @filebucket.command_line.stubs(:args).returns(['get']) @filebucket.expects(:get) @filebucket.run_command end describe "the command get" do before :each do @filebucket.stubs(:print) @filebucket.stubs(:args).returns([]) end it "should call the client getfile method" do @client.expects(:getfile) @filebucket.get end it "should call the client getfile method with the given md5" do md5="DEADBEEF" @filebucket.stubs(:args).returns([md5]) @client.expects(:getfile).with(md5) @filebucket.get end it "should print the file content" do @client.stubs(:getfile).returns("content") @filebucket.expects(:print).returns("content") @filebucket.get end end describe "the command backup" do it "should fail if no arguments are specified" do @filebucket.stubs(:args).returns([]) expect { @filebucket.backup }.to raise_error(RuntimeError, /You must specify a file to back up/) end it "should call the client backup method for each given parameter" do @filebucket.stubs(:puts) Puppet::FileSystem.stubs(:exist?).returns(true) FileTest.stubs(:readable?).returns(true) @filebucket.stubs(:args).returns(["file1", "file2"]) @client.expects(:backup).with("file1") @client.expects(:backup).with("file2") @filebucket.backup end end describe "the command restore" do it "should call the client getfile method with the given md5" do md5="DEADBEEF" file="testfile" @filebucket.stubs(:args).returns([file, md5]) @client.expects(:restore).with(file,md5) @filebucket.restore end end describe "the command diff" do it "should call the client diff method with 2 given checksums" do md5a="DEADBEEF" md5b="BEEF" Puppet::FileSystem.stubs(:exist?).returns(false) @filebucket.stubs(:args).returns([md5a, md5b]) @client.expects(:diff).with(md5a,md5b, nil, nil) @filebucket.diff end it "should call the clien diff with a path if the second argument is a file" do md5a="DEADBEEF" md5b="BEEF" Puppet::FileSystem.stubs(:exist?).with(md5a).returns(false) Puppet::FileSystem.stubs(:exist?).with(md5b).returns(true) @filebucket.stubs(:args).returns([md5a, md5b]) @client.expects(:diff).with(md5a, nil, nil, md5b) @filebucket.diff end it "should call the clien diff with a path if the first argument is a file" do md5a="DEADBEEF" md5b="BEEF" Puppet::FileSystem.stubs(:exist?).with(md5a).returns(true) Puppet::FileSystem.stubs(:exist?).with(md5b).returns(false) @filebucket.stubs(:args).returns([md5a, md5b]) @client.expects(:diff).with(nil, md5b, md5a, nil) @filebucket.diff end it "should call the clien diff with paths if the both arguments are files" do md5a="DEADBEEF" md5b="BEEF" Puppet::FileSystem.stubs(:exist?).with(md5a).returns(true) Puppet::FileSystem.stubs(:exist?).with(md5b).returns(true) @filebucket.stubs(:args).returns([md5a, md5b]) @client.expects(:diff).with(nil, nil, md5a, md5b) @filebucket.diff end it "should fail if only one checksum is given" do md5a="DEADBEEF" @filebucket.stubs(:args).returns([md5a]) expect { @filebucket.diff }.to raise_error Puppet::Error end end describe "the command list" do it "should call the client list method with nil dates" do @client.expects(:list).with(nil, nil) @filebucket.list end it "should call the client list method with the given dates" do # 3 Hours ago threehours = 60*60*3 fromdate = (Time.now - threehours).strftime("%F %T") # 1 Hour ago onehour = 60*60 todate = (Time.now - onehour).strftime("%F %T") @filebucket.options.stubs(:[]).with(:fromdate).returns(fromdate) @filebucket.options.stubs(:[]).with(:todate).returns(todate) @client.expects(:list).with(fromdate, todate) @filebucket.list end end end end puppet-5.5.10/spec/unit/application/indirection_base_spec.rb0000644005276200011600000000353313417161721024072 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/command_line' require 'puppet/application/indirection_base' require 'puppet/indirector/face' ######################################################################## # Stub for testing; the names are critical, sadly. --daniel 2011-03-30 class Puppet::Application::TestIndirection < Puppet::Application::IndirectionBase end ######################################################################## describe Puppet::Application::IndirectionBase do before :all do @face = Puppet::Indirector::Face.define(:test_indirection, '0.0.1') do summary "fake summary" copyright "Puppet Labs", 2011 license "Apache 2 license; see COPYING" end # REVISIT: This horror is required because we don't allow anything to be # :current except for if it lives on, and is loaded from, disk. --daniel 2011-03-29 @face.instance_variable_set('@version', :current) Puppet::Face.register(@face) end after :all do # Delete the face so that it doesn't interfere with other specs Puppet::Interface::FaceCollection.instance_variable_get(:@faces).delete Puppet::Interface::FaceCollection.underscorize(@face.name) end it "should accept a terminus command line option" do # It would be nice not to have to stub this, but whatever... writing an # entire indirection stack would cause us more grief. --daniel 2011-03-31 terminus = stub_everything("test indirection terminus") terminus.stubs(:name).returns(:test_indirection) Puppet::Indirector::Indirection.expects(:instance). with(:test_indirection).returns(terminus) command_line = Puppet::Util::CommandLine.new("puppet", %w{test_indirection --terminus foo save bar}) application = Puppet::Application::TestIndirection.new(command_line) expect { application.run }.to exit_with 0 end end puppet-5.5.10/spec/unit/application/resource_spec.rb0000644005276200011600000001536413417161721022425 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/application/resource' require 'puppet_spec/character_encoding' describe Puppet::Application::Resource do include PuppetSpec::Files before :each do @resource_app = Puppet::Application[:resource] Puppet::Util::Log.stubs(:newdestination) end describe "in preinit" do it "should init extra_params to empty array" do @resource_app.preinit expect(@resource_app.extra_params).to eq([]) end end describe "when handling options" do [:debug, :verbose, :edit].each do |option| it "should store argument value when calling handle_#{option}" do @resource_app.options.expects(:[]=).with(option, 'arg') @resource_app.send("handle_#{option}".to_sym, 'arg') end end it "should load a display all types with types option" do type1 = stub_everything 'type1', :name => :type1 type2 = stub_everything 'type2', :name => :type2 Puppet::Type.stubs(:loadall) Puppet::Type.stubs(:eachtype).multiple_yields(type1,type2) @resource_app.expects(:puts).with(['type1','type2']) expect { @resource_app.handle_types(nil) }.to exit_with 0 end it "should add param to extra_params list" do @resource_app.extra_params = [ :param1 ] @resource_app.handle_param("whatever") expect(@resource_app.extra_params).to eq([ :param1, :whatever ]) end it "should get a parameter in the printed data if extra_params are passed" do tty = stub("tty", :tty? => true ) path = tmpfile('testfile') command_line = Puppet::Util::CommandLine.new("puppet", [ 'resource', 'file', path ], tty ) @resource_app.stubs(:command_line).returns command_line # provider is a parameter that should always be available @resource_app.extra_params = [ :provider ] expect { @resource_app.main }.to have_printed(/provider\s+=>/) end end describe "during setup" do before :each do Puppet::Log.stubs(:newdestination) end it "should set console as the log destination" do Puppet::Log.expects(:newdestination).with(:console) @resource_app.setup end it "should set log level to debug if --debug was passed" do @resource_app.options.stubs(:[]).with(:debug).returns(true) @resource_app.setup expect(Puppet::Log.level).to eq(:debug) end it "should set log level to info if --verbose was passed" do @resource_app.options.stubs(:[]).with(:debug).returns(false) @resource_app.options.stubs(:[]).with(:verbose).returns(true) @resource_app.setup expect(Puppet::Log.level).to eq(:info) end end describe "when running" do before :each do @type = stub_everything 'type', :properties => [] @resource_app.command_line.stubs(:args).returns(['mytype']) Puppet::Type.stubs(:type).returns(@type) @res = stub_everything "resource" @res.stubs(:prune_parameters).returns(@res) @res.stubs(:to_manifest).returns("resource") @report = stub_everything "report" @resource_app.stubs(:puts) Puppet::Resource.indirection.stubs(:find ).never Puppet::Resource.indirection.stubs(:search).never Puppet::Resource.indirection.stubs(:save ).never end it "should raise an error if no type is given" do @resource_app.command_line.stubs(:args).returns([]) expect { @resource_app.main }.to raise_error(RuntimeError, "You must specify the type to display") end it "should raise an error if the type is not found" do Puppet::Type.stubs(:type).returns(nil) expect { @resource_app.main }.to raise_error(RuntimeError, 'Could not find type mytype') end it "should search for resources" do Puppet::Resource.indirection.expects(:search).with('mytype/', {}).returns([]) @resource_app.main end it "should describe the given resource" do @resource_app.command_line.stubs(:args).returns(['type','name']) Puppet::Resource.indirection.expects(:find).with('type/name').returns(@res) @resource_app.main end it "should add given parameters to the object" do @resource_app.command_line.stubs(:args).returns(['type','name','param=temp']) Puppet::Resource.indirection.expects(:save).with(@res, 'type/name').returns([@res, @report]) Puppet::Resource.expects(:new).with('type', 'name', :parameters => {'param' => 'temp'}).returns(@res) @resource_app.main end end describe "when printing output" do it "should ensure all values to be printed are in the external encoding" do resources = [ Puppet::Type.type(:user).new(:name => "\u2603".force_encoding(Encoding::UTF_8)).to_resource, Puppet::Type.type(:user).new(:name => "Jos\xE9".force_encoding(Encoding::ISO_8859_1)).to_resource ] Puppet::Resource.indirection.expects(:search).with('user/', {}).returns(resources) @resource_app.command_line.stubs(:args).returns(['user']) # All of our output should be in external encoding @resource_app.expects(:puts).with { |args| expect(args.encoding).to eq(Encoding::ISO_8859_1) } # This would raise an error if we weren't handling it PuppetSpec::CharacterEncoding.with_external_encoding(Encoding::ISO_8859_1) do expect { @resource_app.main }.not_to raise_error end end end describe "when handling file type" do before :each do Facter.stubs(:loadfacts) @resource_app.preinit end it "should raise an exception if no file specified" do @resource_app.command_line.stubs(:args).returns(['file']) expect { @resource_app.main }.to raise_error(RuntimeError, /Listing all file instances is not supported/) end it "should output a file resource when given a file path" do path = File.expand_path('/etc') res = Puppet::Type.type(:file).new(:path => path).to_resource Puppet::Resource.indirection.expects(:find).returns(res) @resource_app.command_line.stubs(:args).returns(['file', path]) @resource_app.expects(:puts).with do |args| expect(args).to match(/file \{ '#{Regexp.escape(path)}'/m) end @resource_app.main end end describe 'when handling a custom type' do it 'the Puppet::Pops::Loaders instance is available' do Puppet::Type.newtype(:testing) do newparam(:name) do isnamevar end def self.instances fail('Loader not found') unless Puppet::Pops::Loaders.find_loader(nil).is_a?(Puppet::Pops::Loader::Loader) @instances ||= [new(:name => name)] end end @resource_app.command_line.stubs(:args).returns(['testing', 'hello']) @resource_app.expects(:puts).with { |args| expect(args).to eql("testing { 'hello':\n}") } expect { @resource_app.main }.not_to raise_error end end end puppet-5.5.10/spec/unit/application/agent_spec.rb0000644005276200011600000004027113417161722021670 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/agent' require 'puppet/application/agent' require 'puppet/network/server' require 'puppet/daemon' describe Puppet::Application::Agent do include PuppetSpec::Files before :each do @puppetd = Puppet::Application[:agent] @daemon = Puppet::Daemon.new(nil) @daemon.stubs(:daemonize) @daemon.stubs(:start) @daemon.stubs(:stop) Puppet::Daemon.stubs(:new).returns(@daemon) Puppet[:daemonize] = false @agent = stub_everything 'agent' Puppet::Agent.stubs(:new).returns(@agent) @puppetd.preinit Puppet::Util::Log.stubs(:newdestination) @ssl_host = stub_everything 'ssl host' Puppet::SSL::Host.stubs(:new).returns(@ssl_host) Puppet::Node.indirection.stubs(:terminus_class=) Puppet::Node.indirection.stubs(:cache_class=) Puppet::Node::Facts.indirection.stubs(:terminus_class=) $stderr.expects(:puts).never Puppet.settings.stubs(:use) end it "should operate in agent run_mode" do expect(@puppetd.class.run_mode.name).to eq(:agent) end it "should declare a main command" do expect(@puppetd).to respond_to(:main) end it "should declare a onetime command" do expect(@puppetd).to respond_to(:onetime) end it "should declare a fingerprint command" do expect(@puppetd).to respond_to(:fingerprint) end it "should declare a preinit block" do expect(@puppetd).to respond_to(:preinit) end describe "in preinit" do it "should catch INT" do Signal.expects(:trap).with { |arg,block| arg == :INT } @puppetd.preinit end it "should init fqdn to nil" do @puppetd.preinit expect(@puppetd.options[:fqdn]).to be_nil end it "should init serve to []" do @puppetd.preinit expect(@puppetd.options[:serve]).to eq([]) end it "should use SHA256 as default digest algorithm" do @puppetd.preinit expect(@puppetd.options[:digest]).to eq('SHA256') end it "should not fingerprint by default" do @puppetd.preinit expect(@puppetd.options[:fingerprint]).to be_falsey end it "should init waitforcert to nil" do @puppetd.preinit expect(@puppetd.options[:waitforcert]).to be_nil end end describe "when handling options" do before do @puppetd.command_line.stubs(:args).returns([]) end [:enable, :debug, :fqdn, :test, :verbose, :digest].each do |option| it "should declare handle_#{option} method" do expect(@puppetd).to respond_to("handle_#{option}".to_sym) end it "should store argument value when calling handle_#{option}" do @puppetd.send("handle_#{option}".to_sym, 'arg') expect(@puppetd.options[option]).to eq('arg') end end describe "when handling --disable" do it "should set disable to true" do @puppetd.handle_disable('') expect(@puppetd.options[:disable]).to eq(true) end it "should store disable message" do @puppetd.handle_disable('message') expect(@puppetd.options[:disable_message]).to eq('message') end end it "should set waitforcert to 0 with --onetime and if --waitforcert wasn't given" do @agent.stubs(:run).returns(2) Puppet[:onetime] = true @ssl_host.expects(:wait_for_cert).with(0) expect { execute_agent }.to exit_with 0 end it "should use supplied waitforcert when --onetime is specified" do @agent.stubs(:run).returns(2) Puppet[:onetime] = true @puppetd.handle_waitforcert(60) @ssl_host.expects(:wait_for_cert).with(60) expect { execute_agent }.to exit_with 0 end it "should use a default value for waitforcert when --onetime and --waitforcert are not specified" do @ssl_host.expects(:wait_for_cert).with(120) execute_agent end it "should use the waitforcert setting when checking for a signed certificate" do Puppet[:waitforcert] = 10 @ssl_host.expects(:wait_for_cert).with(10) execute_agent end it "should set the log destination with --logdest" do Puppet::Log.expects(:newdestination).with("console") @puppetd.handle_logdest("console") end it "should put the setdest options to true" do @puppetd.handle_logdest("console") expect(@puppetd.options[:setdest]).to eq(true) end it "should parse the log destination from the command line" do @puppetd.command_line.stubs(:args).returns(%w{--logdest /my/file}) Puppet::Util::Log.expects(:newdestination).with("/my/file") @puppetd.parse_options end it "should store the waitforcert options with --waitforcert" do @puppetd.handle_waitforcert("42") expect(@puppetd.options[:waitforcert]).to eq(42) end end describe "during setup" do before :each do Puppet.stubs(:info) Puppet[:libdir] = "/dev/null/lib" Puppet::Transaction::Report.indirection.stubs(:terminus_class=) Puppet::Transaction::Report.indirection.stubs(:cache_class=) Puppet::Resource::Catalog.indirection.stubs(:terminus_class=) Puppet::Resource::Catalog.indirection.stubs(:cache_class=) Puppet::Node::Facts.indirection.stubs(:terminus_class=) Puppet.stubs(:settraps) end it "should not run with extra arguments" do @puppetd.command_line.stubs(:args).returns(%w{disable}) expect{@puppetd.setup}.to raise_error ArgumentError, /does not take parameters/ end describe "with --test" do it "should call setup_test" do @puppetd.options[:test] = true @puppetd.expects(:setup_test) @puppetd.setup end it "should set options[:verbose] to true" do @puppetd.setup_test expect(@puppetd.options[:verbose]).to eq(true) end it "should set options[:onetime] to true" do Puppet[:onetime] = false @puppetd.setup_test expect(Puppet[:onetime]).to eq(true) end it "should set options[:detailed_exitcodes] to true" do @puppetd.setup_test expect(@puppetd.options[:detailed_exitcodes]).to eq(true) end end it "should call setup_logs" do @puppetd.expects(:setup_logs) @puppetd.setup end describe "when setting up logs" do before :each do Puppet::Util::Log.stubs(:newdestination) end it "should set log level to debug if --debug was passed" do @puppetd.options[:debug] = true @puppetd.setup_logs expect(Puppet::Util::Log.level).to eq(:debug) end it "should set log level to info if --verbose was passed" do @puppetd.options[:verbose] = true @puppetd.setup_logs expect(Puppet::Util::Log.level).to eq(:info) end [:verbose, :debug].each do |level| it "should set console as the log destination with level #{level}" do @puppetd.options[level] = true Puppet::Util::Log.expects(:newdestination).at_least_once Puppet::Util::Log.expects(:newdestination).with(:console).once @puppetd.setup_logs end end it "should set a default log destination if no --logdest" do @puppetd.options[:setdest] = false Puppet::Util::Log.expects(:setup_default) @puppetd.setup_logs end end it "should print puppet config if asked to in Puppet config" do Puppet[:configprint] = "pluginsync" Puppet.settings.expects(:print_configs).returns true expect { execute_agent }.to exit_with 0 end it "should exit after printing puppet config if asked to in Puppet config" do path = make_absolute('/my/path') Puppet[:modulepath] = path Puppet[:configprint] = "modulepath" Puppet::Settings.any_instance.expects(:puts).with(path) expect { execute_agent }.to exit_with 0 end it "should use :main, :puppetd, and :ssl" do Puppet.settings.unstub(:use) Puppet.settings.expects(:use).with(:main, :agent, :ssl) @puppetd.setup end it "should install a remote ca location" do Puppet::SSL::Host.expects(:ca_location=).with(:remote) @puppetd.setup end it "should install a none ca location in fingerprint mode" do @puppetd.options[:fingerprint] = true Puppet::SSL::Host.expects(:ca_location=).with(:none) @puppetd.setup end it "should tell the report handler to use REST" do Puppet::Transaction::Report.indirection.expects(:terminus_class=).with(:rest) @puppetd.setup end it "should tell the report handler to cache locally as yaml" do Puppet::Transaction::Report.indirection.expects(:cache_class=).with(:yaml) @puppetd.setup end it "should default catalog_terminus setting to 'rest'" do @puppetd.initialize_app_defaults expect(Puppet[:catalog_terminus]).to eq(:rest) end it "should default node_terminus setting to 'rest'" do @puppetd.initialize_app_defaults expect(Puppet[:node_terminus]).to eq(:rest) end it "has an application default :catalog_cache_terminus setting of 'json'" do Puppet::Resource::Catalog.indirection.expects(:cache_class=).with(:json) @puppetd.initialize_app_defaults @puppetd.setup end it "should tell the catalog cache class based on the :catalog_cache_terminus setting" do Puppet[:catalog_cache_terminus] = "yaml" Puppet::Resource::Catalog.indirection.expects(:cache_class=).with(:yaml) @puppetd.initialize_app_defaults @puppetd.setup end it "should not set catalog cache class if :catalog_cache_terminus is explicitly nil" do Puppet[:catalog_cache_terminus] = nil Puppet::Resource::Catalog.indirection.unstub(:cache_class=) Puppet::Resource::Catalog.indirection.expects(:cache_class=).never @puppetd.initialize_app_defaults @puppetd.setup end it "should default facts_terminus setting to 'facter'" do @puppetd.initialize_app_defaults expect(Puppet[:facts_terminus]).to eq(:facter) end it "should create an agent" do Puppet::Agent.stubs(:new).with(Puppet::Configurer) @puppetd.setup end [:enable, :disable].each do |action| it "should delegate to enable_disable_client if we #{action} the agent" do @puppetd.options[action] = true @puppetd.expects(:enable_disable_client).with(@agent) @puppetd.setup end end describe "when enabling or disabling agent" do [:enable, :disable].each do |action| it "should call client.#{action}" do @puppetd.options[action] = true @agent.expects(action) expect { execute_agent }.to exit_with 0 end end it "should pass the disable message when disabling" do @puppetd.options[:disable] = true @puppetd.options[:disable_message] = "message" @agent.expects(:disable).with("message") expect { execute_agent }.to exit_with 0 end it "should pass the default disable message when disabling without a message" do @puppetd.options[:disable] = true @puppetd.options[:disable_message] = nil @agent.expects(:disable).with("reason not specified") expect { execute_agent }.to exit_with 0 end end it "should inform the daemon about our agent if :client is set to 'true'" do @puppetd.options[:client] = true execute_agent expect(@daemon.agent).to eq(@agent) end it "should daemonize if needed" do Puppet.features.stubs(:microsoft_windows?).returns false Puppet[:daemonize] = true @daemon.expects(:daemonize) execute_agent end it "should wait for a certificate" do @puppetd.options[:waitforcert] = 123 @ssl_host.expects(:wait_for_cert).with(123) execute_agent end it "should not wait for a certificate in fingerprint mode" do @puppetd.options[:fingerprint] = true @puppetd.options[:waitforcert] = 123 @puppetd.options[:digest] = 'MD5' certificate = mock 'certificate' certificate.stubs(:digest).with('MD5').returns('ABCDE') @ssl_host.stubs(:certificate).returns(certificate) @ssl_host.expects(:wait_for_cert).never @puppetd.expects(:puts).with('ABCDE') execute_agent end describe "when setting up for fingerprint" do before(:each) do @puppetd.options[:fingerprint] = true end it "should not setup as an agent" do @puppetd.expects(:setup_agent).never @puppetd.setup end it "should not create an agent" do Puppet::Agent.stubs(:new).with(Puppet::Configurer).never @puppetd.setup end it "should not daemonize" do @daemon.expects(:daemonize).never @puppetd.setup end end describe "when configuring agent for catalog run" do it "should set should_fork as true when running normally" do Puppet::Agent.expects(:new).with(anything, true) @puppetd.setup end it "should not set should_fork as false for --onetime" do Puppet[:onetime] = true Puppet::Agent.expects(:new).with(anything, false) @puppetd.setup end end end describe "when running" do before :each do @puppetd.options[:fingerprint] = false end it "should dispatch to fingerprint if --fingerprint is used" do @puppetd.options[:fingerprint] = true @puppetd.stubs(:fingerprint) execute_agent end it "should dispatch to onetime if --onetime is used" do @puppetd.options[:onetime] = true @puppetd.stubs(:onetime) execute_agent end it "should dispatch to main if --onetime and --fingerprint are not used" do @puppetd.options[:onetime] = false @puppetd.stubs(:main) execute_agent end describe "with --onetime" do before :each do @agent.stubs(:run).returns(:report) Puppet[:onetime] = true @puppetd.options[:client] = :client @puppetd.options[:detailed_exitcodes] = false end it "should setup traps" do @daemon.expects(:set_signal_traps) expect { execute_agent }.to exit_with 0 end it "should let the agent run" do @agent.expects(:run).returns(:report) expect { execute_agent }.to exit_with 0 end it "should run the agent with the supplied job_id" do @puppetd.options[:job_id] = 'special id' @agent.expects(:run).with(:job_id => 'special id').returns(:report) expect { execute_agent }.to exit_with 0 end it "should stop the daemon" do @daemon.expects(:stop).with(:exit => false) expect { execute_agent }.to exit_with 0 end describe "and --detailed-exitcodes" do before :each do @puppetd.options[:detailed_exitcodes] = true end it "should exit with agent computed exit status" do Puppet[:noop] = false @agent.stubs(:run).returns(666) expect { execute_agent }.to exit_with 666 end it "should exit with the agent's exit status, even if --noop is set." do Puppet[:noop] = true @agent.stubs(:run).returns(666) expect { execute_agent }.to exit_with 666 end end end describe "with --fingerprint" do before :each do @cert = mock 'cert' @puppetd.options[:fingerprint] = true @puppetd.options[:digest] = :MD5 end it "should fingerprint the certificate if it exists" do @ssl_host.stubs(:certificate).returns(@cert) @cert.stubs(:digest).with('MD5').returns "fingerprint" @puppetd.expects(:puts).with "fingerprint" @puppetd.fingerprint end it "should fingerprint the certificate request if no certificate have been signed" do @ssl_host.stubs(:certificate).returns(nil) @ssl_host.stubs(:certificate_request).returns(@cert) @cert.stubs(:digest).with('MD5').returns "fingerprint" @puppetd.expects(:puts).with "fingerprint" @puppetd.fingerprint end end describe "without --onetime and --fingerprint" do before :each do Puppet.stubs(:notice) end it "should start our daemon" do @daemon.expects(:start) execute_agent end end end def execute_agent @puppetd.setup @puppetd.run_command end end puppet-5.5.10/spec/unit/application/apply_spec.rb0000644005276200011600000004266313417161722021726 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/application/apply' require 'puppet/file_bucket/dipper' require 'puppet/configurer' require 'fileutils' describe Puppet::Application::Apply do include PuppetSpec::Files before :each do @apply = Puppet::Application[:apply] Puppet::Util::Log.stubs(:newdestination) Puppet[:reports] = "none" end after :each do Puppet::Node::Facts.indirection.reset_terminus_class Puppet::Node::Facts.indirection.cache_class = nil Puppet::Node.indirection.reset_terminus_class Puppet::Node.indirection.cache_class = nil end [:debug,:loadclasses,:test,:verbose,:use_nodes,:detailed_exitcodes,:catalog, :write_catalog_summary].each do |option| it "should declare handle_#{option} method" do expect(@apply).to respond_to("handle_#{option}".to_sym) end it "should store argument value when calling handle_#{option}" do @apply.options.expects(:[]=).with(option, 'arg') @apply.send("handle_#{option}".to_sym, 'arg') end end it "should set the code to the provided code when :execute is used" do @apply.options.expects(:[]=).with(:code, 'arg') @apply.send("handle_execute".to_sym, 'arg') end describe "when applying options" do it "should set the log destination with --logdest" do Puppet::Log.expects(:newdestination).with("console") @apply.handle_logdest("console") end it "should set the setdest options to true" do @apply.options.expects(:[]=).with(:setdest,true) @apply.handle_logdest("console") end end describe "during setup" do before :each do Puppet::Log.stubs(:newdestination) Puppet::FileBucket::Dipper.stubs(:new) STDIN.stubs(:read) Puppet::Transaction::Report.indirection.stubs(:cache_class=) end describe "with --test" do it "should call setup_test" do @apply.options[:test] = true @apply.expects(:setup_test) @apply.setup end it "should set options[:verbose] to true" do @apply.setup_test expect(@apply.options[:verbose]).to eq(true) end it "should set options[:show_diff] to true" do Puppet.settings.override_default(:show_diff, false) @apply.setup_test expect(Puppet[:show_diff]).to eq(true) end it "should set options[:detailed_exitcodes] to true" do @apply.setup_test expect(@apply.options[:detailed_exitcodes]).to eq(true) end end it "should set console as the log destination if logdest option wasn't provided" do Puppet::Log.expects(:newdestination).with(:console) @apply.setup end it "sets the log destination if logdest is provided via settings" do Puppet::Log.expects(:newdestination).with("set_via_config") Puppet[:logdest] = "set_via_config" @apply.setup end it "should set INT trap" do Signal.expects(:trap).with(:INT) @apply.setup end it "should set log level to debug if --debug was passed" do @apply.options[:debug] = true @apply.setup expect(Puppet::Log.level).to eq(:debug) end it "should set log level to info if --verbose was passed" do @apply.options[:verbose] = true @apply.setup expect(Puppet::Log.level).to eq(:info) end it "should print puppet config if asked to in Puppet config" do Puppet.settings.stubs(:print_configs?).returns true Puppet.settings.expects(:print_configs).returns true expect { @apply.setup }.to exit_with 0 end it "should exit after printing puppet config if asked to in Puppet config" do Puppet.settings.stubs(:print_configs?).returns(true) expect { @apply.setup }.to exit_with 1 end it "should use :main, :puppetd, and :ssl" do Puppet.settings.unstub(:use) Puppet.settings.expects(:use).with(:main, :agent, :ssl) @apply.setup end it "should tell the report handler to cache locally as yaml" do Puppet::Transaction::Report.indirection.expects(:cache_class=).with(:yaml) @apply.setup end it "configures a profiler when profiling is enabled" do Puppet[:profile] = true @apply.setup expect(Puppet::Util::Profiler.current).to satisfy do |ps| ps.any? {|p| p.is_a? Puppet::Util::Profiler::WallClock } end end it "does not have a profiler if profiling is disabled" do Puppet[:profile] = false @apply.setup expect(Puppet::Util::Profiler.current.length).to be 0 end it "should set default_file_terminus to `file_server` to be local" do expect(@apply.app_defaults[:default_file_terminus]).to eq(:file_server) end end describe "when executing" do it "should dispatch to 'apply' if it was called with 'apply'" do @apply.options[:catalog] = "foo" @apply.expects(:apply) @apply.run_command end it "should dispatch to main otherwise" do @apply.stubs(:options).returns({}) @apply.expects(:main) @apply.run_command end describe "the main command" do before :each do Puppet[:prerun_command] = '' Puppet[:postrun_command] = '' Puppet::Node::Facts.indirection.terminus_class = :memory Puppet::Node::Facts.indirection.cache_class = :memory Puppet::Node.indirection.terminus_class = :memory Puppet::Node.indirection.cache_class = :memory @facts = Puppet::Node::Facts.new(Puppet[:node_name_value]) Puppet::Node::Facts.indirection.save(@facts) @node = Puppet::Node.new(Puppet[:node_name_value]) Puppet::Node.indirection.save(@node) @catalog = Puppet::Resource::Catalog.new("testing", Puppet.lookup(:environments).get(Puppet[:environment])) @catalog.stubs(:to_ral).returns(@catalog) Puppet::Resource::Catalog.indirection.stubs(:find).returns(@catalog) STDIN.stubs(:read) @transaction = stub('transaction') @catalog.stubs(:apply).returns(@transaction) Puppet::Util::Storage.stubs(:load) Puppet::Configurer.any_instance.stubs(:save_last_run_summary) # to prevent it from trying to write files end after :each do Puppet::Node::Facts.indirection.reset_terminus_class Puppet::Node::Facts.indirection.cache_class = nil end around :each do |example| Puppet.override(:current_environment => Puppet::Node::Environment.create(:production, [])) do example.run end end it "should set the code to run from --code" do @apply.options[:code] = "code to run" Puppet.expects(:[]=).with(:code,"code to run") expect { @apply.main }.to exit_with 0 end it "should set the code to run from STDIN if no arguments" do @apply.command_line.stubs(:args).returns([]) STDIN.stubs(:read).returns("code to run") Puppet.expects(:[]=).with(:code,"code to run") expect { @apply.main }.to exit_with 0 end it "should raise an error if a file is passed on command line and the file does not exist" do noexist = tmpfile('noexist.pp') @apply.command_line.stubs(:args).returns([noexist]) expect { @apply.main }.to raise_error(RuntimeError, "Could not find file #{noexist}") end it "should set the manifest to the first file and warn other files will be skipped" do manifest = tmpfile('starwarsIV') FileUtils.touch(manifest) @apply.command_line.stubs(:args).returns([manifest, 'starwarsI', 'starwarsII']) expect { @apply.main }.to exit_with 0 msg = @logs.find {|m| m.message =~ /Only one file can be applied per run/ } expect(msg.message).to eq('Only one file can be applied per run. Skipping starwarsI, starwarsII') expect(msg.level).to eq(:warning) end it "should splay" do @apply.expects(:splay) expect { @apply.main }.to exit_with 0 end it "should raise an error if we can't find the node" do Puppet::Node.indirection.expects(:find).returns(nil) expect { @apply.main }.to raise_error(RuntimeError, /Could not find node/) end it "should load custom classes if loadclasses" do @apply.options[:loadclasses] = true classfile = tmpfile('classfile') File.open(classfile, 'w') { |c| c.puts 'class' } Puppet[:classfile] = classfile @node.expects(:classes=).with(['class']) expect { @apply.main }.to exit_with 0 end it "should compile the catalog" do Puppet::Resource::Catalog.indirection.expects(:find).returns(@catalog) expect { @apply.main }.to exit_with 0 end it 'should make the Puppet::Pops::Loaders available when applying the compiled catalog' do Puppet::Resource::Catalog.indirection.expects(:find).returns(@catalog) @apply.expects(:apply_catalog).with(@catalog) do fail('Loaders not found') unless Puppet.lookup(:loaders) { nil }.is_a?(Puppet::Pops::Loaders) true end.returns(0) expect { @apply.main }.to exit_with 0 end it "should transform the catalog to ral" do @catalog.expects(:to_ral).returns(@catalog) expect { @apply.main }.to exit_with 0 end it "should finalize the catalog" do @catalog.expects(:finalize) expect { @apply.main }.to exit_with 0 end it "should not save the classes or resource file by default" do @catalog.expects(:write_class_file).never @catalog.expects(:write_resource_file).never expect { @apply.main }.to exit_with 0 end it "should save the classes and resources files when requested" do @apply.options[:write_catalog_summary] = true @catalog.expects(:write_class_file).once @catalog.expects(:write_resource_file).once expect { @apply.main }.to exit_with 0 end it "should call the prerun and postrun commands on a Configurer instance" do Puppet::Configurer.any_instance.expects(:execute_prerun_command).returns(true) Puppet::Configurer.any_instance.expects(:execute_postrun_command).returns(true) expect { @apply.main }.to exit_with 0 end it "should apply the catalog" do @catalog.expects(:apply).returns(stub_everything('transaction')) expect { @apply.main }.to exit_with 0 end it "should save the last run summary" do Puppet[:noop] = false report = Puppet::Transaction::Report.new Puppet::Transaction::Report.stubs(:new).returns(report) Puppet::Configurer.any_instance.expects(:save_last_run_summary).with(report) expect { @apply.main }.to exit_with 0 end describe "when using node_name_fact" do before :each do @facts = Puppet::Node::Facts.new(Puppet[:node_name_value], 'my_name_fact' => 'other_node_name') Puppet::Node::Facts.indirection.save(@facts) @node = Puppet::Node.new('other_node_name') Puppet::Node.indirection.save(@node) Puppet[:node_name_fact] = 'my_name_fact' end it "should set the facts name based on the node_name_fact" do expect { @apply.main }.to exit_with 0 expect(@facts.name).to eq('other_node_name') end it "should set the node_name_value based on the node_name_fact" do expect { @apply.main }.to exit_with 0 expect(Puppet[:node_name_value]).to eq('other_node_name') end it "should merge in our node the loaded facts" do @facts.values.merge!('key' => 'value') expect { @apply.main }.to exit_with 0 expect(@node.parameters['key']).to eq('value') end it "should raise an error if we can't find the facts" do Puppet::Node::Facts.indirection.expects(:find).returns(nil) expect { @apply.main }.to raise_error(RuntimeError, /Could not find facts/) end end describe "with detailed_exitcodes" do before :each do @apply.options[:detailed_exitcodes] = true end it "should exit with report's computed exit status" do Puppet[:noop] = false Puppet::Transaction::Report.any_instance.stubs(:exit_status).returns(666) expect { @apply.main }.to exit_with 666 end it "should exit with report's computed exit status, even if --noop is set" do Puppet[:noop] = true Puppet::Transaction::Report.any_instance.stubs(:exit_status).returns(666) expect { @apply.main }.to exit_with 666 end it "should always exit with 0 if option is disabled" do Puppet[:noop] = false report = stub 'report', :exit_status => 666 @transaction.stubs(:report).returns(report) expect { @apply.main }.to exit_with 0 end it "should always exit with 0 if --noop" do Puppet[:noop] = true report = stub 'report', :exit_status => 666 @transaction.stubs(:report).returns(report) expect { @apply.main }.to exit_with 0 end end end describe "the 'apply' command" do # We want this memoized, and to be able to adjust the content, so we # have to do it ourselves. def temporary_catalog(content = '"something"') @tempfile = Tempfile.new('catalog.json') @tempfile.write(content) @tempfile.close @tempfile.path end it "should read the catalog in from disk if a file name is provided" do @apply.options[:catalog] = temporary_catalog catalog = Puppet::Resource::Catalog.new("testing", Puppet::Node::Environment::NONE) Puppet::Resource::Catalog.stubs(:convert_from).with(:json,'"something"').returns(catalog) @apply.apply end it "should read the catalog in from stdin if '-' is provided" do @apply.options[:catalog] = "-" $stdin.expects(:read).returns '"something"' catalog = Puppet::Resource::Catalog.new("testing", Puppet::Node::Environment::NONE) Puppet::Resource::Catalog.stubs(:convert_from).with(:json,'"something"').returns(catalog) @apply.apply end it "should deserialize the catalog from the default format" do @apply.options[:catalog] = temporary_catalog Puppet::Resource::Catalog.stubs(:default_format).returns :rot13_piglatin catalog = Puppet::Resource::Catalog.new("testing", Puppet::Node::Environment::NONE) Puppet::Resource::Catalog.stubs(:convert_from).with(:rot13_piglatin,'"something"').returns(catalog) @apply.apply end it "should fail helpfully if deserializing fails" do @apply.options[:catalog] = temporary_catalog('something syntactically invalid') expect { @apply.apply }.to raise_error(Puppet::Error) end it "should convert the catalog to a RAL catalog and use a Configurer instance to apply it" do @apply.options[:catalog] = temporary_catalog catalog = Puppet::Resource::Catalog.new("testing", Puppet::Node::Environment::NONE) Puppet::Resource::Catalog.stubs(:convert_from).with(:json,'"something"').returns catalog catalog.expects(:to_ral).returns "mycatalog" configurer = stub 'configurer' Puppet::Configurer.expects(:new).returns configurer configurer.expects(:run). with(:catalog => "mycatalog", :pluginsync => false) @apply.apply end it 'should make the Puppet::Pops::Loaders available when applying a catalog' do @apply.options[:catalog] = temporary_catalog catalog = Puppet::Resource::Catalog.new("testing", Puppet::Node::Environment::NONE) @apply.expects(:read_catalog).with('something') do fail('Loaders not found') unless Puppet.lookup(:loaders) { nil }.is_a?(Puppet::Pops::Loaders) true end.returns(catalog) @apply.expects(:apply_catalog).with(catalog) do fail('Loaders not found') unless Puppet.lookup(:loaders) { nil }.is_a?(Puppet::Pops::Loaders) true end expect { @apply.apply }.not_to raise_error end end end describe "when really executing" do let(:testfile) { tmpfile('secret_file_name') } let(:resourcefile) { tmpfile('resourcefile') } let(:classfile) { tmpfile('classfile') } it "should not expose sensitive data in the relationship file" do @apply.options[:code] = <<-CODE $secret = Sensitive('cat #{testfile}') exec { 'do it': command => $secret, path => '/bin/' } CODE @apply.options[:write_catalog_summary] = true Puppet.settings[:resourcefile] = resourcefile Puppet.settings[:classfile] = classfile #We don't actually need the resource to do anything, we are using it's properties in other parts of the workflow. Puppet::Util::Execution.stubs(:execute) expect { @apply.main }.to exit_with 0 result = File.read(resourcefile) expect(result).not_to match(/secret_file_name/) expect(result).to match(/do it/) end end describe "apply_catalog" do it "should call the configurer with the catalog" do catalog = "I am a catalog" Puppet::Configurer.any_instance.expects(:run). with(:catalog => catalog, :pluginsync => false) @apply.send(:apply_catalog, catalog) end end it "should honor the catalog_cache_terminus setting" do Puppet.settings[:catalog_cache_terminus] = "json" Puppet::Resource::Catalog.indirection.expects(:cache_class=).with(:json) @apply.initialize_app_defaults @apply.setup end end puppet-5.5.10/spec/unit/application/cert_spec.rb0000644005276200011600000002150313417161722021524 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/application/cert' describe Puppet::Application::Cert => true do before :each do @cert_app = Puppet::Application[:cert] Puppet::Util::Log.stubs(:newdestination) end it "should operate in master run_mode" do expect(@cert_app.class.run_mode.name).to equal(:master) end it "should declare a main command" do expect(@cert_app).to respond_to(:main) end Puppet::SSL::CertificateAuthority::Interface::INTERFACE_METHODS.reject{ |m| m == :destroy }.each do |method| it "should declare option --#{method}" do expect(@cert_app).to respond_to("handle_#{method}".to_sym) end end it "should set log level to info with the --verbose option" do @cert_app.handle_verbose(0) expect(Puppet::Log.level).to eq(:info) end it "should set log level to debug with the --debug option" do @cert_app.handle_debug(0) expect(Puppet::Log.level).to eq(:debug) end it "should set the fingerprint digest with the --digest option" do @cert_app.handle_digest(:digest) expect(@cert_app.digest).to eq(:digest) end it "should set cert_mode to :destroy for --clean" do @cert_app.handle_clean(0) expect(@cert_app.subcommand).to eq(:destroy) end it "should set all to true for --all" do @cert_app.handle_all(0) expect(@cert_app.all).to be_truthy end it "should set signed to true for --signed" do @cert_app.handle_signed(0) expect(@cert_app.signed).to be_truthy end it "should set human to true for --human-readable" do @cert_app.handle_human_readable(0) expect(@cert_app.options[:format]).to be :human end it "should set machine to true for --machine-readable" do @cert_app.handle_machine_readable(0) expect(@cert_app.options[:format]).to be :machine end it "should set interactive to true for --interactive" do @cert_app.handle_interactive(0) expect(@cert_app.options[:interactive]).to be_truthy end it "should set yes to true for --assume-yes" do @cert_app.handle_assume_yes(0) expect(@cert_app.options[:yes]).to be_truthy end Puppet::SSL::CertificateAuthority::Interface::INTERFACE_METHODS.reject { |m| m == :destroy }.each do |method| it "should set cert_mode to #{method} with option --#{method}" do @cert_app.send("handle_#{method}".to_sym, nil) expect(@cert_app.subcommand).to eq(method) end end describe "during setup" do before :each do Puppet::Log.stubs(:newdestination) Puppet::SSL::Host.stubs(:ca_location=) Puppet::SSL::CertificateAuthority.stubs(:new) end it "should set console as the log destination" do Puppet::Log.expects(:newdestination).with(:console) @cert_app.setup end it "should print puppet config if asked to in Puppet config" do Puppet.settings.stubs(:print_configs?).returns(true) Puppet.settings.expects(:print_configs).returns true expect { @cert_app.setup }.to exit_with 0 end it "should exit after printing puppet config if asked to in Puppet config" do Puppet.settings.stubs(:print_configs?).returns(true) expect { @cert_app.setup }.to exit_with 1 end it "should set the CA location to 'only'" do Puppet::SSL::Host.expects(:ca_location=).with(:only) @cert_app.setup end it "should create a new certificate authority" do Puppet::SSL::CertificateAuthority.expects(:new) @cert_app.setup end it "should set the ca_location to :local if the cert_mode is generate" do @cert_app.subcommand = 'generate' Puppet::SSL::Host.expects(:ca_location=).with(:local) @cert_app.setup end it "should set the ca_location to :local if the cert_mode is destroy" do @cert_app.subcommand = 'destroy' Puppet::SSL::Host.expects(:ca_location=).with(:local) @cert_app.setup end it "should set the ca_location to :only if the cert_mode is print" do @cert_app.subcommand = 'print' Puppet::SSL::Host.expects(:ca_location=).with(:only) @cert_app.setup end end describe "when running" do before :each do @cert_app.all = false @ca = stub_everything( 'ca', :waiting? => ['unsigned-node'] ) @cert_app.ca = @ca @cert_app.command_line.stubs(:args).returns([]) @iface = stub_everything 'iface' Puppet::SSL::CertificateAuthority::Interface.stubs(:new).returns(@iface) end it "should delegate to the CertificateAuthority" do @iface.expects(:apply) @cert_app.main end it "should delegate with :all if option --all was given" do @cert_app.handle_all(0) Puppet::SSL::CertificateAuthority::Interface.expects(:new).returns(@iface).with { |cert_mode,to| to[:to] == :all } @cert_app.main end it "should delegate to ca.apply with the hosts given on command line" do @cert_app.command_line.stubs(:args).returns(["host"]) Puppet::SSL::CertificateAuthority::Interface.expects(:new).returns(@iface).with { |cert_mode,to| to[:to] == ["host"]} @cert_app.main end it "should send the currently set digest" do @cert_app.command_line.stubs(:args).returns(["host"]) @cert_app.handle_digest(:digest) Puppet::SSL::CertificateAuthority::Interface.expects(:new).returns(@iface).with { |cert_mode,to| to[:digest] == :digest} @cert_app.main end it "should revoke cert if cert_mode is clean" do @cert_app.subcommand = :destroy @cert_app.command_line.stubs(:args).returns(["host"]) Puppet::SSL::CertificateAuthority::Interface.expects(:new).returns(@iface).with { |cert_mode,to| cert_mode == :revoke } Puppet::SSL::CertificateAuthority::Interface.expects(:new).returns(@iface).with { |cert_mode,to| cert_mode == :destroy } @cert_app.main end it "should not revoke cert if node does not have a signed certificate" do @cert_app.subcommand = :destroy @cert_app.command_line.stubs(:args).returns(["unsigned-node"]) Puppet::SSL::CertificateAuthority::Interface.unstub(:new) Puppet::SSL::CertificateAuthority::Interface.expects(:new).with(:revoke, anything).never Puppet::SSL::CertificateAuthority::Interface.expects(:new).with(:destroy, {:to => ['unsigned-node'], :digest => nil}).returns(@iface) @cert_app.main end it "should only revoke signed certificate and destroy certificate signing requests" do @cert_app.subcommand = :destroy @cert_app.command_line.stubs(:args).returns(["host","unsigned-node"]) Puppet::SSL::CertificateAuthority::Interface.expects(:new).returns(@iface).with do |cert_mode,to| cert_mode == :revoke && to[:to] == ["host"] end Puppet::SSL::CertificateAuthority::Interface.expects(:new).returns(@iface).with do |cert_mode,to| cert_mode == :destroy && to[:to] == ["host","unsigned-node"] end @cert_app.main end it "should refuse to destroy all certificates" do @cert_app.subcommand = :destroy @cert_app.all = true Puppet::SSL::CertificateAuthority::Interface.unstub(:new) Puppet::SSL::CertificateAuthority::Interface.expects(:new).never Puppet.expects(:log_exception).with {|e| e.message == "Refusing to destroy all certs, provide an explicit list of certs to destroy"} expect { @cert_app.main }.to exit_with(24) end end describe "when identifying subcommands" do before :each do @cert_app.all = false @ca = stub_everything 'ca' @cert_app.ca = @ca end %w{list revoke generate sign print verify fingerprint}.each do |cmd| short = cmd[0,1] [cmd, "--#{cmd}", "-#{short}"].each do |option| # In our command line '-v' was eaten by 'verbose', so we can't consume # it here; this is a special case from our otherwise standard # processing. --daniel 2011-02-22 next if option == "-v" it "should recognise '#{option}'" do args = [option, "fun.example.com"] @cert_app.command_line.stubs(:args).returns(args) @cert_app.parse_options expect(@cert_app.subcommand).to eq(cmd.to_sym) expect(args).to eq(["fun.example.com"]) end end end %w{clean --clean -c}.each do |ugly| it "should recognise the '#{ugly}' option as destroy" do args = [ugly, "fun.example.com"] @cert_app.command_line.stubs(:args).returns(args) @cert_app.parse_options expect(@cert_app.subcommand).to eq(:destroy) expect(args).to eq(["fun.example.com"]) end end it "should print help and exit if there is no subcommand" do args = [] @cert_app.command_line.stubs(:args).returns(args) @cert_app.stubs(:help).returns("I called for help!") @cert_app.expects(:puts).with("I called for help!") expect { @cert_app.parse_options }.to exit_with 0 expect(@cert_app.subcommand).to be_nil end end end puppet-5.5.10/spec/unit/application/certificate_spec.rb0000644005276200011600000000134713417161722023055 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/application/certificate' describe Puppet::Application::Certificate do it "should have a 'ca-location' option" do # REVISIT: This is delegated from the face, and we will have a test there, # so is this actually a valuable test? --daniel 2011-04-07 subject.command_line.stubs(:args).returns %w{list} subject.preinit subject.parse_options expect(subject).to respond_to(:handle_ca_location) end it "should accept the ca-location option" do subject.command_line.stubs(:args).returns %w{--ca-location local list} subject.preinit subject.parse_options subject.setup expect(subject.arguments).to eq([{ :ca_location => "local" }]) end end puppet-5.5.10/spec/unit/application/device_spec.rb0000644005276200011600000005476713417161722022050 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/application/device' require 'puppet/util/network_device/config' require 'ostruct' require 'puppet/configurer' require 'puppet/application/apply' describe Puppet::Application::Device do include PuppetSpec::Files before :each do @device = Puppet::Application[:device] @device.preinit Puppet::Util::Log.stubs(:newdestination) Puppet::Node.indirection.stubs(:terminus_class=) Puppet::Node.indirection.stubs(:cache_class=) Puppet::Node::Facts.indirection.stubs(:terminus_class=) end it "should operate in agent run_mode" do expect(@device.class.run_mode.name).to eq(:agent) end it "should declare a main command" do expect(@device).to respond_to(:main) end it "should declare a preinit block" do expect(@device).to respond_to(:preinit) end describe "in preinit" do before :each do @device.stubs(:trap) end it "should catch INT" do Signal.expects(:trap).with { |arg,block| arg == :INT } @device.preinit end it "should init waitforcert to nil" do @device.preinit expect(@device.options[:waitforcert]).to be_nil end it "should init target to nil" do @device.preinit expect(@device.options[:target]).to be_nil end end describe "when handling options" do before do @device.command_line.stubs(:args).returns([]) end [:centrallogging, :debug, :verbose,].each do |option| it "should declare handle_#{option} method" do expect(@device).to respond_to("handle_#{option}".to_sym) end it "should store argument value when calling handle_#{option}" do @device.options.expects(:[]=).with(option, 'arg') @device.send("handle_#{option}".to_sym, 'arg') end end it "should set waitforcert to 0 with --onetime and if --waitforcert wasn't given" do Puppet[:onetime] = true Puppet::SSL::Host.any_instance.expects(:wait_for_cert).with(0) @device.setup_host end it "should use supplied waitforcert when --onetime is specified" do Puppet[:onetime] = true @device.handle_waitforcert(60) Puppet::SSL::Host.any_instance.expects(:wait_for_cert).with(60) @device.setup_host end it "should use a default value for waitforcert when --onetime and --waitforcert are not specified" do Puppet::SSL::Host.any_instance.expects(:wait_for_cert).with(120) @device.setup_host end it "should use the waitforcert setting when checking for a signed certificate" do Puppet[:waitforcert] = 10 Puppet::SSL::Host.any_instance.expects(:wait_for_cert).with(10) @device.setup_host end it "should set the log destination with --logdest" do @device.options.stubs(:[]=).with { |opt,val| opt == :setdest } Puppet::Log.expects(:newdestination).with("console") @device.handle_logdest("console") end it "should put the setdest options to true" do @device.options.expects(:[]=).with(:setdest,true) @device.handle_logdest("console") end it "should parse the log destination from the command line" do @device.command_line.stubs(:args).returns(%w{--logdest /my/file}) Puppet::Util::Log.expects(:newdestination).with("/my/file") @device.parse_options end it "should store the waitforcert options with --waitforcert" do @device.options.expects(:[]=).with(:waitforcert,42) @device.handle_waitforcert("42") end it "should set args[:Port] with --port" do @device.handle_port("42") expect(@device.args[:Port]).to eq("42") end it "should store the target options with --target" do @device.options.expects(:[]=).with(:target,'test123') @device.handle_target('test123') end it "should store the resource options with --resource" do @device.options.expects(:[]=).with(:resource,true) @device.handle_resource(true) end it "should store the facts options with --facts" do @device.options.expects(:[]=).with(:facts,true) @device.handle_facts(true) end end describe "during setup" do before :each do @device.options.stubs(:[]) Puppet[:libdir] = "/dev/null/lib" Puppet::SSL::Host.stubs(:ca_location=) Puppet::Transaction::Report.indirection.stubs(:terminus_class=) Puppet::Resource::Catalog.indirection.stubs(:terminus_class=) Puppet::Resource::Catalog.indirection.stubs(:cache_class=) Puppet::Node::Facts.indirection.stubs(:terminus_class=) @host = stub_everything 'host' Puppet::SSL::Host.stubs(:new).returns(@host) Puppet.stubs(:settraps) end it "should call setup_logs" do @device.expects(:setup_logs) @device.setup end describe "when setting up logs" do before :each do Puppet::Util::Log.stubs(:newdestination) end it "should set log level to debug if --debug was passed" do @device.options.stubs(:[]).with(:debug).returns(true) @device.setup_logs expect(Puppet::Util::Log.level).to eq(:debug) end it "should set log level to info if --verbose was passed" do @device.options.stubs(:[]).with(:verbose).returns(true) @device.setup_logs expect(Puppet::Util::Log.level).to eq(:info) end [:verbose, :debug].each do |level| it "should set console as the log destination with level #{level}" do @device.options.stubs(:[]).with(level).returns(true) Puppet::Util::Log.expects(:newdestination).with(:console) @device.setup_logs end end it "should set a default log destination if no --logdest" do @device.options.stubs(:[]).with(:setdest).returns(false) Puppet::Util::Log.expects(:setup_default) @device.setup_logs end end it "should set a central log destination with --centrallogs" do @device.options.stubs(:[]).with(:centrallogs).returns(true) Puppet[:server] = "puppet.reductivelabs.com" Puppet::Util::Log.stubs(:newdestination).with(:syslog) Puppet::Util::Log.expects(:newdestination).with("puppet.reductivelabs.com") @device.setup end it "should use :main, :agent, :device and :ssl config" do Puppet.settings.expects(:use).with(:main, :agent, :device, :ssl) @device.setup end it "should install a remote ca location" do Puppet::SSL::Host.expects(:ca_location=).with(:remote) @device.setup end it "should tell the report handler to use REST" do Puppet::Transaction::Report.indirection.expects(:terminus_class=).with(:rest) @device.setup end it "should default the catalog_terminus setting to 'rest'" do @device.initialize_app_defaults expect(Puppet[:catalog_terminus]).to eq(:rest) end it "should default the node_terminus setting to 'rest'" do @device.initialize_app_defaults expect(Puppet[:node_terminus]).to eq(:rest) end it "has an application default :catalog_cache_terminus setting of 'json'" do Puppet::Resource::Catalog.indirection.expects(:cache_class=).with(:json) @device.initialize_app_defaults @device.setup end it "should tell the catalog cache class based on the :catalog_cache_terminus setting" do Puppet[:catalog_cache_terminus] = "yaml" Puppet::Resource::Catalog.indirection.expects(:cache_class=).with(:yaml) @device.initialize_app_defaults @device.setup end it "should not set catalog cache class if :catalog_cache_terminus is explicitly nil" do Puppet[:catalog_cache_terminus] = nil Puppet::Resource::Catalog.indirection.expects(:cache_class=).never @device.initialize_app_defaults @device.setup end it "should default the facts_terminus setting to 'network_device'" do @device.initialize_app_defaults expect(Puppet[:facts_terminus]).to eq(:network_device) end end describe "when initializing each devices SSL" do before(:each) do @host = stub_everything 'host' Puppet::SSL::Host.stubs(:new).returns(@host) end it "should create a new ssl host" do Puppet::SSL::Host.expects(:new).returns(@host) @device.setup_host end it "should wait for a certificate" do @device.options.stubs(:[]).with(:waitforcert).returns(123) @host.expects(:wait_for_cert).with(123) @device.setup_host end end describe "when running" do before :each do @device.options.stubs(:[]).with(:fingerprint).returns(false) Puppet.stubs(:notice) @device.options.stubs(:[]).with(:detailed_exitcodes).returns(false) @device.options.stubs(:[]).with(:target).returns(nil) @device.options.stubs(:[]).with(:apply).returns(nil) @device.options.stubs(:[]).with(:facts).returns(false) @device.options.stubs(:[]).with(:resource).returns(false) @device.options.stubs(:[]).with(:to_yaml).returns(false) @device.options.stubs(:[]).with(:libdir).returns(nil) @device.options.stubs(:[]).with(:client) @device.command_line.stubs(:args).returns([]) Puppet::Util::NetworkDevice::Config.stubs(:devices).returns({}) end it "should dispatch to main" do @device.stubs(:main) @device.run_command end it "should exit if resource is requested without target" do @device.options.stubs(:[]).with(:resource).returns(true) expect { @device.main }.to raise_error(RuntimeError, "resource command requires target") end it "should exit if facts is requested without target" do @device.options.stubs(:[]).with(:facts).returns(true) expect { @device.main }.to raise_error(RuntimeError, "facts command requires target") end it "should get the device list" do device_hash = stub_everything 'device hash' Puppet::Util::NetworkDevice::Config.expects(:devices).returns(device_hash) expect { @device.main }.to exit_with 1 end it "should get a single device, when a valid target parameter is passed" do @device.options.stubs(:[]).with(:target).returns('device1') device_hash = { "device1" => OpenStruct.new(:name => "device1", :url => "ssh://user:pass@testhost", :provider => "cisco"), "device2" => OpenStruct.new(:name => "device2", :url => "https://user:pass@testhost/some/path", :provider => "rest"), } Puppet::Util::NetworkDevice::Config.expects(:devices).returns(device_hash) URI.expects(:parse).with("ssh://user:pass@testhost") URI.expects(:parse).with("https://user:pass@testhost/some/path").never expect { @device.main }.to exit_with 1 end it "should exit, when an invalid target parameter is passed" do @device.options.stubs(:[]).with(:target).returns('bla') device_hash = { "device1" => OpenStruct.new(:name => "device1", :url => "ssh://user:pass@testhost", :provider => "cisco"), } Puppet::Util::NetworkDevice::Config.expects(:devices).returns(device_hash) Puppet.expects(:info).with(regexp_matches(/starting applying configuration to/)).never expect { @device.main }.to raise_error(RuntimeError, /Target device \/ certificate 'bla' not found in .*\.conf/) end it "should error if target is passed and the apply path is incorrect" do @device.options.stubs(:[]).with(:apply).returns('file.pp') @device.options.stubs(:[]).with(:target).returns('device1') File.expects(:file?).returns(false) expect { @device.main }.to raise_error(RuntimeError, /does not exist, cannot apply/) end it "should run an apply, and not create the state folder" do @device.options.stubs(:[]).with(:apply).returns('file.pp') @device.options.stubs(:[]).with(:target).returns('device1') device_hash = { "device1" => OpenStruct.new(:name => "device1", :url => "ssh://user:pass@testhost", :provider => "cisco"), } Puppet::Util::NetworkDevice::Config.expects(:devices).returns(device_hash) Puppet::Util::NetworkDevice.stubs(:init) File.expects(:file?).returns(true) ::File.stubs(:directory?).returns false state_path = tmpfile('state') Puppet[:statedir] = state_path File.expects(:directory?).with(state_path).returns true FileUtils.expects(:mkdir_p).with(state_path).never Puppet::Util::CommandLine.expects(:new).once Puppet::Application::Apply.expects(:new).once Puppet::Configurer.expects(:new).never expect { @device.main }.to exit_with 1 end it "should run an apply, and create the state folder" do @device.options.stubs(:[]).with(:apply).returns('file.pp') @device.options.stubs(:[]).with(:target).returns('device1') device_hash = { "device1" => OpenStruct.new(:name => "device1", :url => "ssh://user:pass@testhost", :provider => "cisco"), } Puppet::Util::NetworkDevice::Config.expects(:devices).returns(device_hash) Puppet::Util::NetworkDevice.stubs(:init) File.expects(:file?).returns(true) FileUtils.expects(:mkdir_p).once Puppet::Util::CommandLine.expects(:new).once Puppet::Application::Apply.expects(:new).once Puppet::Configurer.expects(:new).never expect { @device.main }.to exit_with 1 end it "should exit if the device list is empty" do expect { @device.main }.to exit_with 1 end describe "for each device" do before(:each) do Puppet[:vardir] = make_absolute("/dummy") Puppet[:confdir] = make_absolute("/dummy") Puppet[:certname] = "certname" @device_hash = { "device1" => OpenStruct.new(:name => "device1", :url => "ssh://user:pass@testhost", :provider => "cisco"), "device2" => OpenStruct.new(:name => "device2", :url => "https://user:pass@testhost/some/path", :provider => "rest"), } Puppet::Util::NetworkDevice::Config.stubs(:devices).returns(@device_hash) Puppet.stubs(:[]=) Puppet.settings.stubs(:use) @device.stubs(:setup_host) Puppet::Util::NetworkDevice.stubs(:init) @configurer = stub_everything 'configurer' Puppet::Configurer.stubs(:new).returns(@configurer) end it "should set vardir to the device vardir" do Puppet.expects(:[]=).with(:vardir, make_absolute("/dummy/devices/device1")) expect { @device.main }.to exit_with 1 end it "should set confdir to the device confdir" do Puppet.expects(:[]=).with(:confdir, make_absolute("/dummy/devices/device1")) expect { @device.main }.to exit_with 1 end it "should set certname to the device certname" do Puppet.expects(:[]=).with(:certname, "device1") Puppet.expects(:[]=).with(:certname, "device2") expect { @device.main }.to exit_with 1 end it "should raise an error if no type is given" do @device.options.stubs(:[]).with(:resource).returns(true) @device.options.stubs(:[]).with(:target).returns('device1') @device.command_line.stubs(:args).returns([]) Puppet.expects(:log_exception).with {|e| e.message == "You must specify the type to display"} expect { @device.main }.to exit_with 1 end it "should raise an error if the type is not found" do @device.options.stubs(:[]).with(:resource).returns(true) @device.options.stubs(:[]).with(:target).returns('device1') @device.command_line.stubs(:args).returns(['nope']) Puppet.expects(:log_exception).with {|e| e.message == "Could not find type nope"} expect { @device.main }.to exit_with 1 end it "should retrieve all resources of a type" do @device.options.stubs(:[]).with(:resource).returns(true) @device.options.stubs(:[]).with(:target).returns('device1') @device.command_line.stubs(:args).returns(['user']) Puppet::Resource.indirection.expects(:search).with('user/', {}).returns([]) expect { @device.main }.to exit_with 0 end it "should retrieve named resources of a type" do resource = Puppet::Type.type(:user).new(:name => "jim").to_resource @device.options.stubs(:[]).with(:resource).returns(true) @device.options.stubs(:[]).with(:target).returns('device1') @device.command_line.stubs(:args).returns(['user', 'jim']) Puppet::Resource.indirection.expects(:find).with('user/jim').returns(resource) @device.expects(:puts).with("user { 'jim':\n}") expect { @device.main }.to exit_with 0 end it "should output resources as YAML" do resources = [ Puppet::Type.type(:user).new(:name => "title").to_resource, ] @device.options.stubs(:[]).with(:resource).returns(true) @device.options.stubs(:[]).with(:target).returns('device1') @device.options.stubs(:[]).with(:to_yaml).returns(true) @device.command_line.stubs(:args).returns(['user']) Puppet::Resource.indirection.expects(:search).with('user/', {}).returns(resources) @device.expects(:puts).with("user:\n title:\n") expect { @device.main }.to exit_with 0 end it "should retrieve facts" do indirection_fact_values = {"operatingsystem"=>"cisco_ios","clientcert"=>"3750"} indirection_facts = Puppet::Node::Facts.new("nil", indirection_fact_values) @device.options.stubs(:[]).with(:facts).returns(true) @device.options.stubs(:[]).with(:target).returns('device1') Puppet::Node::Facts.indirection.expects(:find).with(nil, anything()).returns(indirection_facts) @device.expects(:puts).with(regexp_matches(/name.*3750.*\n.*values.*\n.*operatingsystem.*cisco_ios/)) expect { @device.main }.to exit_with 0 end it "should make sure all the required folders and files are created" do Puppet.settings.expects(:use).with(:main, :agent, :ssl).twice expect { @device.main }.to exit_with 1 end it "should initialize the device singleton" do Puppet::Util::NetworkDevice.expects(:init).with(@device_hash["device1"]).then.with(@device_hash["device2"]) expect { @device.main }.to exit_with 1 end it "should retrieve plugins and print the device url scheme, host, and port" do Puppet.stubs(:info) Puppet.expects(:info).with "Retrieving pluginfacts" Puppet.expects(:info).with "starting applying configuration to device1 at ssh://testhost" Puppet.expects(:info).with "starting applying configuration to device2 at https://testhost:443/some/path" expect { @device.main }.to exit_with 1 end it "should setup the SSL context" do @device.expects(:setup_host).twice expect { @device.main }.to exit_with 1 end it "should launch a configurer for this device" do @configurer.expects(:run).twice expect { @device.main }.to exit_with 1 end it "exits 1 when configurer raises error" do @configurer.stubs(:run).raises(Puppet::Error).then.returns(0) expect { @device.main }.to exit_with 1 end it "exits 0 when run happens without puppet errors but with failed run" do @configurer.stubs(:run).returns(6,2) expect { @device.main }.to exit_with 0 end it "should make the Puppet::Pops::Loaaders available" do @configurer.expects(:run).with(:network_device => true, :pluginsync => true) do fail('Loaders not available') unless Puppet.lookup(:loaders) { nil }.is_a?(Puppet::Pops::Loaders) true end.at_least_once.returns(6,2) expect { @device.main }.to exit_with 0 end it "exits 2 when --detailed-exitcodes and successful runs" do @device.options.stubs(:[]).with(:detailed_exitcodes).returns(true) @configurer.stubs(:run).returns(0,2) expect { @device.main }.to exit_with 2 end it "exits 1 when --detailed-exitcodes and failed parse" do @configurer = stub_everything 'configurer' Puppet::Configurer.stubs(:new).returns(@configurer) @device.options.stubs(:[]).with(:detailed_exitcodes).returns(true) @configurer.stubs(:run).returns(6,1) expect { @device.main }.to exit_with 7 end it "exits 6 when --detailed-exitcodes and failed run" do @configurer = stub_everything 'configurer' Puppet::Configurer.stubs(:new).returns(@configurer) @device.options.stubs(:[]).with(:detailed_exitcodes).returns(true) @configurer.stubs(:run).returns(6,2) expect { @device.main }.to exit_with 6 end [:vardir, :confdir].each do |setting| it "should cleanup the #{setting} setting after the run" do all_devices = Set.new(@device_hash.keys.map do |device_name| make_absolute("/dummy/devices/#{device_name}") end) found_devices = Set.new() # a block to use in a few places later to validate the updated settings p = Proc.new do |my_setting, my_value| if my_setting == setting && all_devices.include?(my_value) found_devices.add(my_value) true else false end end seq = sequence("clean up dirs") all_devices.size.times do ## one occurrence of set / run / set("/dummy") for each device Puppet.expects(:[]=).with(&p).in_sequence(seq) @configurer.expects(:run).in_sequence(seq) Puppet.expects(:[]=).with(setting, make_absolute("/dummy")).in_sequence(seq) end expect { @device.main }.to exit_with 1 expect(found_devices).to eq(all_devices) end end it "should cleanup the certname setting after the run" do all_devices = Set.new(@device_hash.keys) found_devices = Set.new() # a block to use in a few places later to validate the updated settings p = Proc.new do |my_setting, my_value| if my_setting == :certname && all_devices.include?(my_value) found_devices.add(my_value) true else false end end seq = sequence("clean up certname") all_devices.size.times do ## one occurrence of set / run / set("certname") for each device Puppet.expects(:[]=).with(&p).in_sequence(seq) @configurer.expects(:run).in_sequence(seq) Puppet.expects(:[]=).with(:certname, "certname").in_sequence(seq) end expect { @device.main }.to exit_with 1 # make sure that we were called with each of the defined devices expect(found_devices).to eq(all_devices) end it "should expire all cached attributes" do Puppet::SSL::Host.expects(:reset).twice expect { @device.main }.to exit_with 1 end end end end puppet-5.5.10/spec/unit/application/face_base_spec.rb0000644005276200011600000004021713417161722022462 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/application/face_base' require 'tmpdir' class Puppet::Application::FaceBase::Basetest < Puppet::Application::FaceBase end describe Puppet::Application::FaceBase do let :app do app = Puppet::Application::FaceBase::Basetest.new app.command_line.stubs(:subcommand_name).returns('subcommand') Puppet::Util::Log.stubs(:newdestination) app end after :each do app.class.clear_everything_for_tests end describe "#find_global_settings_argument" do it "should not match --ca to --ca-location" do option = mock('ca option', :optparse_args => ["--ca"]) Puppet.settings.expects(:each).yields(:ca, option) expect(app.find_global_settings_argument("--ca-location")).to be_nil end end describe "#parse_options" do before :each do app.command_line.stubs(:args).returns %w{} end describe "with just an action" do before(:each) do # We have to stub Signal.trap to avoid a crazy mess where we take # over signal handling and make it impossible to cancel the test # suite run. # # It would be nice to fix this elsewhere, but it is actually hard to # capture this in rspec 2.5 and all. :( --daniel 2011-04-08 Signal.stubs(:trap) app.command_line.stubs(:args).returns %w{foo} app.preinit app.parse_options end it "should set the face based on the type" do expect(app.face.name).to eq(:basetest) end it "should find the action" do expect(app.action).to be expect(app.action.name).to eq(:foo) end end it "should stop if the first thing found is not an action" do app.command_line.stubs(:args).returns %w{banana count_args} expect { app.run }.to exit_with(1) expect(@logs.map(&:message)).to eq(["'basetest' has no 'banana' action. See `puppet help basetest`."]) end it "should use the default action if not given any arguments" do app.command_line.stubs(:args).returns [] action = stub(:options => [], :render_as => nil) Puppet::Face[:basetest, '0.0.1'].expects(:get_default_action).returns(action) app.stubs(:main) app.run expect(app.action).to eq(action) expect(app.arguments).to eq([ { } ]) end it "should use the default action if not given a valid one" do app.command_line.stubs(:args).returns %w{bar} action = stub(:options => [], :render_as => nil) Puppet::Face[:basetest, '0.0.1'].expects(:get_default_action).returns(action) app.stubs(:main) app.run expect(app.action).to eq(action) expect(app.arguments).to eq([ 'bar', { } ]) end it "should have no action if not given a valid one and there is no default action" do app.command_line.stubs(:args).returns %w{bar} Puppet::Face[:basetest, '0.0.1'].expects(:get_default_action).returns(nil) app.stubs(:main) expect { app.run }.to exit_with(1) expect(@logs.first.message).to match(/has no 'bar' action./) end [%w{something_I_cannot_do}, %w{something_I_cannot_do argument}].each do |input| it "should report unknown actions nicely" do app.command_line.stubs(:args).returns input Puppet::Face[:basetest, '0.0.1'].expects(:get_default_action).returns(nil) app.stubs(:main) expect { app.run }.to exit_with(1) expect(@logs.first.message).to match(/has no 'something_I_cannot_do' action/) end end [%w{something_I_cannot_do --unknown-option}, %w{something_I_cannot_do argument --unknown-option}].each do |input| it "should report unknown actions even if there are unknown options" do app.command_line.stubs(:args).returns input Puppet::Face[:basetest, '0.0.1'].expects(:get_default_action).returns(nil) app.stubs(:main) expect { app.run }.to exit_with(1) expect(@logs.first.message).to match(/has no 'something_I_cannot_do' action/) end end it "should report a sensible error when options with = fail" do app.command_line.stubs(:args).returns %w{--action=bar foo} expect { app.preinit; app.parse_options }. to raise_error(OptionParser::InvalidOption, /invalid option: --action/) end it "should fail if an action option is before the action" do app.command_line.stubs(:args).returns %w{--action foo} expect { app.preinit; app.parse_options }. to raise_error(OptionParser::InvalidOption, /invalid option: --action/) end it "should fail if an unknown option is before the action" do app.command_line.stubs(:args).returns %w{--bar foo} expect { app.preinit; app.parse_options }. to raise_error(OptionParser::InvalidOption, /invalid option: --bar/) end it "should fail if an unknown option is after the action" do app.command_line.stubs(:args).returns %w{foo --bar} expect { app.preinit; app.parse_options }. to raise_error(OptionParser::InvalidOption, /invalid option: --bar/) end it "should accept --bar as an argument to a mandatory option after action" do app.command_line.stubs(:args).returns %w{foo --mandatory --bar} app.preinit app.parse_options expect(app.action.name).to eq(:foo) expect(app.options).to eq({ :mandatory => "--bar" }) end it "should accept --bar as an argument to a mandatory option before action" do app.command_line.stubs(:args).returns %w{--mandatory --bar foo} app.preinit app.parse_options expect(app.action.name).to eq(:foo) expect(app.options).to eq({ :mandatory => "--bar" }) end it "should not skip when --foo=bar is given" do app.command_line.stubs(:args).returns %w{--mandatory=bar --bar foo} expect { app.preinit; app.parse_options }. to raise_error(OptionParser::InvalidOption, /invalid option: --bar/) end it "does not skip when a puppet global setting is given as one item" do app.command_line.stubs(:args).returns %w{--confdir=/tmp/puppet foo} app.preinit app.parse_options expect(app.action.name).to eq(:foo) expect(app.options).to eq({}) end it "does not skip when a puppet global setting is given as two items" do app.command_line.stubs(:args).returns %w{--confdir /tmp/puppet foo} app.preinit app.parse_options expect(app.action.name).to eq(:foo) expect(app.options).to eq({}) end it "should not add :debug to the application-level options" do app.command_line.stubs(:args).returns %w{--confdir /tmp/puppet foo --debug} app.preinit app.parse_options expect(app.action.name).to eq(:foo) expect(app.options).to eq({}) end it "should not add :verbose to the application-level options" do app.command_line.stubs(:args).returns %w{--confdir /tmp/puppet foo --verbose} app.preinit app.parse_options expect(app.action.name).to eq(:foo) expect(app.options).to eq({}) end { "boolean options before" => %w{--trace foo}, "boolean options after" => %w{foo --trace} }.each do |name, args| it "should accept global boolean settings #{name} the action" do app.command_line.stubs(:args).returns args Puppet.settings.initialize_global_settings(args) app.preinit app.parse_options expect(Puppet[:trace]).to be_truthy end end { "before" => %w{--syslogfacility user1 foo}, " after" => %w{foo --syslogfacility user1} }.each do |name, args| it "should accept global settings with arguments #{name} the action" do app.command_line.stubs(:args).returns args Puppet.settings.initialize_global_settings(args) app.preinit app.parse_options expect(Puppet[:syslogfacility]).to eq("user1") end end it "should handle application-level options" do app.command_line.stubs(:args).returns %w{--verbose return_true} app.preinit app.parse_options expect(app.face.name).to eq(:basetest) end end describe "#setup" do it "should remove the action name from the arguments" do app.command_line.stubs(:args).returns %w{--mandatory --bar foo} app.preinit app.parse_options app.setup expect(app.arguments).to eq([{ :mandatory => "--bar" }]) end it "should pass positional arguments" do myargs = %w{--mandatory --bar foo bar baz quux} app.command_line.stubs(:args).returns(myargs) app.preinit app.parse_options app.setup expect(app.arguments).to eq(['bar', 'baz', 'quux', { :mandatory => "--bar" }]) end end describe "#main" do before :each do app.stubs(:puts) # don't dump text to screen. app.face = Puppet::Face[:basetest, '0.0.1'] app.action = app.face.get_action(:foo) app.arguments = ["myname", "myarg"] end it "should send the specified verb and name to the face" do app.face.expects(:foo).with(*app.arguments) expect { app.main }.to exit_with(0) end it "should lookup help when it cannot do anything else" do app.action = nil Puppet::Face[:help, :current].expects(:help).with(:basetest) expect { app.main }.to exit_with(1) end it "should use its render method to render any result" do app.expects(:render).with(app.arguments.length + 1, ["myname", "myarg"]) expect { app.main }.to exit_with(0) end it "should issue a deprecation warning if the face is deprecated" do # since app is shared across examples, stub to avoid affecting shared context app.face.stubs(:deprecated?).returns(true) app.face.expects(:foo).with(*app.arguments) Puppet.expects(:deprecation_warning).with(regexp_matches(/'puppet basetest' is deprecated/)) expect { app.main }.to exit_with(0) end it "should not issue a deprecation warning if the face is not deprecated" do Puppet.expects(:deprecation_warning).never # since app is shared across examples, stub to avoid affecting shared context app.face.stubs(:deprecated?).returns(false) app.face.expects(:foo).with(*app.arguments) expect { app.main }.to exit_with(0) end end describe "error reporting" do before :each do app.stubs(:puts) # don't dump text to screen. app.render_as = :json app.face = Puppet::Face[:basetest, '0.0.1'] app.arguments = [{}] # we always have options in there... end it "should exit 0 when the action returns true" do app.action = app.face.get_action :return_true expect { app.main }.to exit_with(0) end it "should exit 0 when the action returns false" do app.action = app.face.get_action :return_false expect { app.main }.to exit_with(0) end it "should exit 0 when the action returns nil" do app.action = app.face.get_action :return_nil expect { app.main }.to exit_with(0) end it "should exit non-0 when the action raises" do app.action = app.face.get_action :return_raise expect { app.main }.not_to exit_with(0) end it "should use the exit code set by the action" do app.action = app.face.get_action :with_specific_exit_code expect { app.main }.to exit_with(5) end end describe "#render" do before :each do app.face = Puppet::Interface.new('basetest', '0.0.1') app.action = Puppet::Interface::Action.new(app.face, :foo) end context "default rendering" do before :each do app.setup end ["hello", 1, 1.0].each do |input| it "should just return a #{input.class.name}" do expect(app.render(input, {})).to eq(input) end end [[1, 2], ["one"], [{ 1 => 1 }]].each do |input| it "should render Array as one item per line" do expect(app.render(input, {})).to eq(input.collect { |item| item.to_s + "\n" }.join('')) end end it "should render a non-trivially-keyed Hash with using pretty printed JSON" do hash = { [1,2] => 3, [2,3] => 5, [3,4] => 7 } expect(app.render(hash, {})).to eq(Puppet::Util::Json.dump(hash, :pretty => true).chomp) end it "should render a {String,Numeric}-keyed Hash into a table" do object = Object.new hash = { "one" => 1, "two" => [], "three" => {}, "four" => object, 5 => 5, 6.0 => 6 } # Gotta love ASCII-betical sort order. Hope your objects are better # structured for display than my test one is. --daniel 2011-04-18 expect(app.render(hash, {})).to eq < { "1" => '1' * 40, "2" => '2' * 40, '3' => '3' * 40 }, "text" => { "a" => 'a' * 40, 'b' => 'b' * 40, 'c' => 'c' * 40 } } expect(app.render(hash, {})).to eq <"1111111111111111111111111111111111111111", "2"=>"2222222222222222222222222222222222222222", "3"=>"3333333333333333333333333333333333333333"} text {"a"=>"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "b"=>"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "c"=>"cccccccccccccccccccccccccccccccccccccccc"} EOT end describe "when setting the rendering method" do after do # need to reset the when_rendering block so that other tests can set it later app.action.instance_variable_set("@when_rendering", {}) end it "should invoke the action rendering hook while rendering" do app.action.set_rendering_method_for(:console, proc { |value| "bi-winning!" }) expect(app.render("bi-polar?", {})).to eq("bi-winning!") end it "should invoke the action rendering hook with args and options while rendering" do app.action.instance_variable_set("@when_rendering", {}) app.action.when_invoked = proc { |name, options| 'just need to match arity for rendering' } app.action.set_rendering_method_for( :console, proc { |value, name, options| "I'm #{name}, no wait, I'm #{options[:altername]}" } ) expect(app.render("bi-polar?", ['bob', {:altername => 'sue'}])).to eq("I'm bob, no wait, I'm sue") end end it "should render JSON when asked for json" do app.render_as = :json json = app.render({ :one => 1, :two => 2 }, {}) expect(json).to match(/"one":\s*1\b/) expect(json).to match(/"two":\s*2\b/) expect(JSON.parse(json)).to eq({ "one" => 1, "two" => 2 }) end end it "should fail early if asked to render an invalid format" do app.command_line.stubs(:args).returns %w{--render-as interpretive-dance return_true} # We shouldn't get here, thanks to the exception, and our expectation on # it, but this helps us fail if that slips up and all. --daniel 2011-04-27 Puppet::Face[:help, :current].expects(:help).never Puppet.expects(:err).with("Could not parse application options: I don't know how to render 'interpretive-dance'") expect { app.run }.to exit_with(1) end it "should work if asked to render json" do app.command_line.stubs(:args).returns %w{count_args a b c --render-as json} expect { expect { app.run }.to exit_with(0) }.to have_printed(/3/) end it "should invoke when_rendering hook 's' when asked to render-as 's'" do app.command_line.stubs(:args).returns %w{with_s_rendering_hook --render-as s} app.action = app.face.get_action(:with_s_rendering_hook) expect { expect { app.run }.to exit_with(0) }.to have_printed(/you invoked the 's' rendering hook/) end end describe "#help" do it "should generate help for --help" do app.command_line.stubs(:args).returns %w{--help} Puppet::Face[:help, :current].expects(:help) expect { app.run }.to exit_with(0) end it "should generate help for -h" do app.command_line.stubs(:args).returns %w{-h} Puppet::Face[:help, :current].expects(:help) expect { app.run }.to exit_with(0) end end end puppet-5.5.10/spec/unit/application/lookup_spec.rb0000644005276200011600000005023413417161722022103 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/application/lookup' require 'puppet/pops/lookup' describe Puppet::Application::Lookup do def run_lookup(lookup) capture = StringIO.new saved_stdout = $stdout begin $stdout = capture expect { lookup.run_command }.to exit_with(0) ensure $stdout = saved_stdout end # Drop end of line and an optional yaml end of document capture.string.gsub(/\n(\.\.\.\n)?\Z/m, '') end context "when running with incorrect command line options" do let (:lookup) { Puppet::Application[:lookup] } it "errors if no keys are given via the command line" do lookup.options[:node] = 'dantooine.local' expected_error = "No keys were given to lookup." expect { lookup.run_command }.to raise_error(RuntimeError, expected_error) end it "does not allow invalid arguments for '--merge'" do lookup.options[:node] = 'dantooine.local' lookup.options[:merge] = 'something_bad' lookup.command_line.stubs(:args).returns(['atton', 'kreia']) expected_error = "The --merge option only accepts 'first', 'hash', 'unique', or 'deep'\nRun 'puppet lookup --help' for more details" expect { lookup.run_command }.to raise_error(RuntimeError, expected_error) end it "does not allow deep merge options if '--merge' was not set to deep" do lookup.options[:node] = 'dantooine.local' lookup.options[:merge_hash_arrays] = true lookup.options[:merge] = 'hash' lookup.command_line.stubs(:args).returns(['atton', 'kreia']) expected_error = "The options --knock-out-prefix, --sort-merged-arrays, and --merge-hash-arrays are only available with '--merge deep'\nRun 'puppet lookup --help' for more details" expect { lookup.run_command }.to raise_error(RuntimeError, expected_error) end end context "when running with correct command line options" do let (:lookup) { Puppet::Application[:lookup] } it "calls the lookup method with the correct arguments" do lookup.options[:node] = 'dantooine.local' lookup.options[:render_as] = :s; lookup.options[:merge_hash_arrays] = true lookup.options[:merge] = 'deep' lookup.command_line.stubs(:args).returns(['atton', 'kreia']) lookup.stubs(:generate_scope).yields('scope') expected_merge = { "strategy" => "deep", "sort_merged_arrays" => false, "merge_hash_arrays" => true } (Puppet::Pops::Lookup).expects(:lookup).with(['atton', 'kreia'], nil, nil, false, expected_merge, anything).returns('rand') expect(run_lookup(lookup)).to eql("rand") end %w(first unique hash deep).each do |opt| it "accepts --merge #{opt}" do lookup.options[:node] = 'dantooine.local' lookup.options[:merge] = opt lookup.command_line.stubs(:args).returns(['atton', 'kreia']) lookup.stubs(:generate_scope).yields('scope') Puppet::Pops::Lookup.stubs(:lookup).returns('rand') expect(run_lookup(lookup)).to eql("--- rand") end end it "prints the value found by lookup" do lookup.options[:node] = 'dantooine.local' lookup.command_line.stubs(:args).returns(['atton', 'kreia']) lookup.stubs(:generate_scope).yields('scope') Puppet::Pops::Lookup.stubs(:lookup).returns('rand') expect(run_lookup(lookup)).to eql("--- rand") end end context 'when given a valid configuration' do let (:lookup) { Puppet::Application[:lookup] } # There is a fully configured 'sample' environment in fixtures at this location let(:environmentpath) { File.absolute_path(File.join(my_fixture_dir(), '../environments')) } let(:facts) { Puppet::Node::Facts.new("facts", {}) } let(:node) { Puppet::Node.new("testnode", :facts => facts, :environment => 'production') } let(:expected_json_hash) { { 'branches' => [ { 'branches'=> [ { 'key'=>'lookup_options', 'event'=>'not_found', 'type'=>'data_provider', 'name'=>'Global Data Provider (hiera configuration version 5)' }, { 'branches'=> [ { 'branches'=> [ { 'key' => 'lookup_options', 'value' => {'a'=>'first'}, 'event'=>'found', 'type'=>'path', 'original_path'=>'common.yaml', 'path'=>"#{environmentpath}/production/data/common.yaml" } ], 'type'=>'data_provider', 'name'=>'Hierarchy entry "Common"' } ], 'type'=>'data_provider', 'name'=>'Environment Data Provider (hiera configuration version 5)' } ], 'key'=>'lookup_options', 'type'=>'root' }, { 'branches'=> [ { 'key'=>'a', 'event'=>'not_found', 'type'=>'data_provider', 'name'=>'Global Data Provider (hiera configuration version 5)' }, { 'branches'=> [ { 'branches'=> [ { 'key'=>'a', 'value'=>'This is A', 'event'=>'found', 'type'=>'path', 'original_path'=>'common.yaml', 'path'=>"#{environmentpath}/production/data/common.yaml" } ], 'type'=>'data_provider', 'name'=>'Hierarchy entry "Common"' } ], 'type'=>'data_provider', 'name'=>'Environment Data Provider (hiera configuration version 5)' } ], 'key'=>'a', 'type'=>'root' } ] } } let(:expected_yaml_hash) { { :branches => [ { :branches=> [ { :key=>'lookup_options', :event=>:not_found, :type=>:data_provider, :name=>'Global Data Provider (hiera configuration version 5)' }, { :branches=> [ { :branches=> [ { :key => 'lookup_options', :value => {'a'=>'first'}, :event=>:found, :type=>:path, :original_path=>'common.yaml', :path=>"#{environmentpath}/production/data/common.yaml" } ], :type=>:data_provider, :name=>'Hierarchy entry "Common"' } ], :type=>:data_provider, :name=>'Environment Data Provider (hiera configuration version 5)' } ], :key=>'lookup_options', :type=>:root }, { :branches=> [ { :key=>'a', :event=>:not_found, :type=>:data_provider, :name=>'Global Data Provider (hiera configuration version 5)' }, { :branches=> [ { :branches=> [ { :key=>'a', :value=>'This is A', :event=>:found, :type=>:path, :original_path=>'common.yaml', :path=>"#{environmentpath}/production/data/common.yaml" } ], :type=>:data_provider, :name=>'Hierarchy entry "Common"' } ], :type=>:data_provider, :name=>'Environment Data Provider (hiera configuration version 5)' } ], :key=>'a', :type=>:root } ] } } around(:each) do |example| # Initialize settings to get a full compile as close as possible to a real # environment load Puppet.settings.initialize_global_settings loader = Puppet::Environments::Directories.new(environmentpath, []) Puppet.override(:environments => loader) do example.run end end it '--explain produces human readable text by default and does not produce output to debug logger' do lookup.options[:node] = node lookup.options[:explain] = true lookup.command_line.stubs(:args).returns(['a']) logs = [] Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do expect(run_lookup(lookup)).to eql(<<-EXPLANATION.chomp) Searching for "lookup_options" Global Data Provider (hiera configuration version 5) No such key: "lookup_options" Environment Data Provider (hiera configuration version 5) Hierarchy entry "Common" Path "#{environmentpath}/production/data/common.yaml" Original path: "common.yaml" Found key: "lookup_options" value: { "a" => "first" } Searching for "a" Global Data Provider (hiera configuration version 5) No such key: "a" Environment Data Provider (hiera configuration version 5) Hierarchy entry "Common" Path "#{environmentpath}/production/data/common.yaml" Original path: "common.yaml" Found key: "a" value: "This is A" EXPLANATION end expect(logs.any? { |log| log.level == :debug }).to be_falsey end it '--debug using multiple interpolation functions produces output to the logger' do lookup.options[:node] = node lookup.command_line.stubs(:args).returns(['ab']) Puppet.debug = true logs = [] begin Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do expect { lookup.run_command }.to output(<<-VALUE.unindent).to_stdout --- This is A and This is B ... VALUE end rescue SystemExit => e expect(e.status).to eq(0) end logs = logs.select { |log| log.level == :debug }.map { |log| log.message } expect(logs).to include(/Found key: "ab" value: "This is A and This is B"/) end it '--explain produces human readable text by default and --debug produces the same output to debug logger' do lookup.options[:node] = node lookup.options[:explain] = true lookup.command_line.stubs(:args).returns(['a']) Puppet.debug = true logs = [] Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do expect(run_lookup(lookup)).to eql(<<-EXPLANATION.chomp) Searching for "lookup_options" Global Data Provider (hiera configuration version 5) No such key: "lookup_options" Environment Data Provider (hiera configuration version 5) Hierarchy entry "Common" Path "#{environmentpath}/production/data/common.yaml" Original path: "common.yaml" Found key: "lookup_options" value: { "a" => "first" } Searching for "a" Global Data Provider (hiera configuration version 5) No such key: "a" Environment Data Provider (hiera configuration version 5) Hierarchy entry "Common" Path "#{environmentpath}/production/data/common.yaml" Original path: "common.yaml" Found key: "a" value: "This is A" EXPLANATION end logs = logs.select { |log| log.level == :debug }.map { |log| log.message } expect(logs).to include(<<-EXPLANATION.chomp) Lookup of 'a' Searching for "lookup_options" Global Data Provider (hiera configuration version 5) No such key: "lookup_options" Environment Data Provider (hiera configuration version 5) Hierarchy entry "Common" Path "#{environmentpath}/production/data/common.yaml" Original path: "common.yaml" Found key: "lookup_options" value: { "a" => "first" } Searching for "a" Global Data Provider (hiera configuration version 5) No such key: "a" Environment Data Provider (hiera configuration version 5) Hierarchy entry "Common" Path "#{environmentpath}/production/data/common.yaml" Original path: "common.yaml" Found key: "a" value: "This is A" EXPLANATION end it '--explain-options produces human readable text of a hash merge' do lookup.options[:node] = node lookup.options[:explain_options] = true expect(run_lookup(lookup)).to eql(<<-EXPLANATION.chomp) Merge strategy hash Global Data Provider (hiera configuration version 5) No such key: "lookup_options" Environment Data Provider (hiera configuration version 5) Hierarchy entry "Common" Path "#{environmentpath}/production/data/common.yaml" Original path: "common.yaml" Found key: "lookup_options" value: { "a" => "first" } Merged result: { "a" => "first" } EXPLANATION end it '--explain-options produces human readable text of a hash merge and --debug produces the same output to debug logger' do lookup.options[:node] = node lookup.options[:explain_options] = true Puppet.debug = true logs = [] Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do expect(run_lookup(lookup)).to eql(<<-EXPLANATION.chomp) Merge strategy hash Global Data Provider (hiera configuration version 5) No such key: "lookup_options" Environment Data Provider (hiera configuration version 5) Hierarchy entry "Common" Path "#{environmentpath}/production/data/common.yaml" Original path: "common.yaml" Found key: "lookup_options" value: { "a" => "first" } Merged result: { "a" => "first" } EXPLANATION logs = logs.select { |log| log.level == :debug }.map { |log| log.message } expect(logs).to include(<<-EXPLANATION.chomp) Lookup of '__global__' Merge strategy hash Global Data Provider (hiera configuration version 5) No such key: "lookup_options" Environment Data Provider (hiera configuration version 5) Hierarchy entry "Common" Path "#{environmentpath}/production/data/common.yaml" Original path: "common.yaml" Found key: "lookup_options" value: { "a" => "first" } Merged result: { "a" => "first" } EXPLANATION end end it '--explain produces human readable text of a hash merge when using both --explain and --explain-options' do lookup.options[:node] = node lookup.options[:explain] = true lookup.options[:explain_options] = true lookup.command_line.stubs(:args).returns(['a']) expect(run_lookup(lookup)).to eql(<<-EXPLANATION.chomp) Searching for "lookup_options" Global Data Provider (hiera configuration version 5) No such key: "lookup_options" Environment Data Provider (hiera configuration version 5) Hierarchy entry "Common" Path "#{environmentpath}/production/data/common.yaml" Original path: "common.yaml" Found key: "lookup_options" value: { "a" => "first" } Searching for "a" Global Data Provider (hiera configuration version 5) No such key: "a" Environment Data Provider (hiera configuration version 5) Hierarchy entry "Common" Path "#{environmentpath}/production/data/common.yaml" Original path: "common.yaml" Found key: "a" value: "This is A" EXPLANATION end it 'can produce a yaml explanation' do lookup.options[:node] = node lookup.options[:explain] = true lookup.options[:render_as] = :yaml lookup.command_line.stubs(:args).returns(['a']) output = run_lookup(lookup) expect(YAML.load(output)).to eq(expected_yaml_hash) end it 'can produce a json explanation' do lookup.options[:node] = node lookup.options[:explain] = true lookup.options[:render_as] = :json lookup.command_line.stubs(:args).returns(['a']) output = run_lookup(lookup) expect(JSON.parse(output)).to eq(expected_json_hash) end it 'can access values using dotted keys' do lookup.options[:node] = node lookup.options[:render_as] = :json lookup.command_line.stubs(:args).returns(['d.one.two.three']) output = run_lookup(lookup) expect(JSON.parse("[#{output}]")).to eq(['the value']) end it 'can access values using quoted dotted keys' do lookup.options[:node] = node lookup.options[:render_as] = :json lookup.command_line.stubs(:args).returns(['"e.one.two.three"']) output = run_lookup(lookup) expect(JSON.parse("[#{output}]")).to eq(['the value']) end it 'can access values using mix of dotted keys and quoted dotted keys' do lookup.options[:node] = node lookup.options[:render_as] = :json lookup.command_line.stubs(:args).returns(['"f.one"."two.three".1']) output = run_lookup(lookup) expect(JSON.parse("[#{output}]")).to eq(['second value']) end context 'the global scope' do include PuppetSpec::Files it "is unaffected by global variables unless '--compile' is used" do lookup.options[:node] = node lookup.command_line.stubs(:args).returns(['c']) expect(run_lookup(lookup)).to eql("--- This is") end it "is affected by global variables when '--compile' is used" do lookup.options[:node] = node lookup.options[:compile] = true lookup.command_line.stubs(:args).returns(['c']) expect(run_lookup(lookup)).to eql("--- This is C from site.pp") end it 'receives extra facts in top scope' do file_path = tmpdir('lookup_spec') filename = File.join(file_path, "facts.yaml") File.open(filename, "w+") { |f| f.write(<<-YAML.unindent) } --- cx: ' C from facts' YAML lookup.options[:node] = node lookup.options[:fact_file] = filename lookup.command_line.stubs(:args).returns(['c']) expect(run_lookup(lookup)).to eql("--- This is C from facts") end it 'receives extra facts in the facts hash' do file_path = tmpdir('lookup_spec') filename = File.join(file_path, "facts.yaml") File.open(filename, "w+") { |f| f.write(<<-YAML.unindent) } --- cx: ' G from facts' YAML lookup.options[:node] = node lookup.options[:fact_file] = filename lookup.command_line.stubs(:args).returns(['g']) expect(run_lookup(lookup)).to eql("--- This is G from facts in facts hash") end end context 'using a puppet function as data provider' do let(:node) { Puppet::Node.new("testnode", :facts => facts, :environment => 'puppet_func_provider') } it "works OK in the absense of '--compile'" do lookup.options[:node] = node lookup.command_line.stubs(:args).returns(['c']) expect(run_lookup(lookup)).to eql("--- This is C from data.pp") end it "global scope is affected by global variables when '--compile' is used" do lookup.options[:node] = node lookup.options[:compile] = true lookup.command_line.stubs(:args).returns(['c']) expect(run_lookup(lookup)).to eql("--- This is C from site.pp") end end end end puppet-5.5.10/spec/unit/application/master_spec.rb0000644005276200011600000003106013417161722022061 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/application/master' require 'puppet/daemon' require 'puppet/network/server' describe Puppet::Application::Master, :unless => Puppet.features.microsoft_windows? do before :each do Puppet[:bindaddress] = '127.0.0.1' @master = Puppet::Application[:master] @daemon = stub_everything 'daemon' Puppet::Daemon.stubs(:new).returns(@daemon) Puppet::Util::Log.stubs(:newdestination) Puppet::Node.indirection.stubs(:terminus_class=) Puppet::Node::Facts.indirection.stubs(:terminus_class=) Puppet::Node::Facts.indirection.stubs(:cache_class=) Puppet::Transaction::Report.indirection.stubs(:terminus_class=) Puppet::Resource::Catalog.indirection.stubs(:terminus_class=) Puppet::SSL::Host.stubs(:ca_location=) end it "should operate in master run_mode" do expect(@master.class.run_mode.name).to equal(:master) end it "should declare a main command" do expect(@master).to respond_to(:main) end it "should declare a compile command" do expect(@master).to respond_to(:compile) end it "should declare a preinit block" do expect(@master).to respond_to(:preinit) end describe "during preinit" do before :each do @master.stubs(:trap) end it "should catch INT" do @master.stubs(:trap).with { |arg,block| arg == :INT } @master.preinit end end [:debug,:verbose].each do |option| it "should declare handle_#{option} method" do expect(@master).to respond_to("handle_#{option}".to_sym) end it "should store argument value when calling handle_#{option}" do @master.options.expects(:[]=).with(option, 'arg') @master.send("handle_#{option}".to_sym, 'arg') end end describe "when applying options" do before do @master.command_line.stubs(:args).returns([]) end it "should set the log destination with --logdest" do Puppet::Log.expects(:newdestination).with("console") @master.handle_logdest("console") end it "should put the setdest options to true" do @master.options.expects(:[]=).with(:setdest,true) @master.handle_logdest("console") end it "should parse the log destination from ARGV" do @master.command_line.stubs(:args).returns(%w{--logdest /my/file}) Puppet::Util::Log.expects(:newdestination).with("/my/file") @master.parse_options end it "should support dns alt names from ARGV" do Puppet.settings.initialize_global_settings(["--dns_alt_names", "foo,bar,baz"]) @master.preinit @master.parse_options expect(Puppet[:dns_alt_names]).to eq("foo,bar,baz") end end describe "during setup" do before :each do Puppet::Log.stubs(:newdestination) Puppet.stubs(:settraps) Puppet::SSL::CertificateAuthority.stubs(:instance) Puppet::SSL::CertificateAuthority.stubs(:ca?) Puppet.settings.stubs(:use) @master.options.stubs(:[]).with(any_parameters) end it "should abort stating that the master is not supported on Windows" do Puppet.features.stubs(:microsoft_windows?).returns(true) expect { @master.setup }.to raise_error(Puppet::Error, /Puppet master is not supported on Microsoft Windows/) end describe "setting up logging" do it "sets the log level" do @master.expects(:set_log_level) @master.setup end describe "when the log destination is not explicitly configured" do before do @master.options.stubs(:[]).with(:setdest).returns false end it "should log to the console when --compile is given" do @master.options.stubs(:[]).with(:node).returns "default" Puppet::Util::Log.expects(:newdestination).with(:console) @master.setup end it "should log to the console when the master is not daemonized or run with rack" do Puppet::Util::Log.expects(:newdestination).with(:console) Puppet[:daemonize] = false @master.options.stubs(:[]).with(:rack).returns(false) @master.setup end it "should log to syslog when the master is daemonized" do Puppet::Util::Log.expects(:newdestination).with(:console).never Puppet::Util::Log.expects(:newdestination).with(:syslog) Puppet[:daemonize] = true @master.options.stubs(:[]).with(:rack).returns(false) @master.setup end it "should log to syslog when the master is run with rack" do Puppet::Util::Log.expects(:newdestination).with(:console).never Puppet::Util::Log.expects(:newdestination).with(:syslog) Puppet[:daemonize] = false @master.options.stubs(:[]).with(:rack).returns(true) @master.setup end end it "sets the log destination using settings" do Puppet::Util::Log.expects(:newdestination).with("set_via_config") Puppet[:logdest] = "set_via_config" @master.setup end end it "should print puppet config if asked to in Puppet config" do Puppet.settings.stubs(:print_configs?).returns(true) Puppet.settings.expects(:print_configs).returns(true) expect { @master.setup }.to exit_with 0 end it "should exit after printing puppet config if asked to in Puppet config" do Puppet.settings.stubs(:print_configs?).returns(true) expect { @master.setup }.to exit_with 1 end it "should tell Puppet.settings to use :main,:ssl,:master and :metrics category" do Puppet.settings.expects(:use).with(:main,:master,:ssl,:metrics) @master.setup end describe "with no ca" do it "should set the ca_location to none" do Puppet::SSL::Host.expects(:ca_location=).with(:none) @master.setup end end describe "with a ca configured" do before :each do Puppet::SSL::CertificateAuthority.stubs(:ca?).returns(true) end it "should set the ca_location to local" do Puppet::SSL::Host.expects(:ca_location=).with(:local) @master.setup end it "should tell Puppet.settings to use :ca category" do Puppet.settings.expects(:use).with(:ca) @master.setup end it "should instantiate the CertificateAuthority singleton" do Puppet::SSL::CertificateAuthority.expects(:instance) @master.setup end end it "should not set Puppet[:node_cache_terminus] by default" do # This is normally called early in the application lifecycle but in our # spec testing we don't actually do a full application initialization so # we call it here to validate the (possibly) overridden settings are as we # expect @master.initialize_app_defaults @master.setup expect(Puppet[:node_cache_terminus]).to be(nil) end it "should honor Puppet[:node_cache_terminus] by setting the cache_class to its value" do # PUP-6060 - ensure we honor this value if specified @master.initialize_app_defaults Puppet[:node_cache_terminus] = 'plain' @master.setup expect(Puppet::Node.indirection.cache_class).to eq(:plain) end end describe "when running" do before do @master.preinit end it "should dispatch to compile if called with --compile" do @master.options[:node] = "foo" @master.expects(:compile) @master.run_command end it "should dispatch to main otherwise" do @master.options[:node] = nil @master.expects(:main) @master.run_command end describe "the compile command" do before do Puppet[:manifest] = "site.pp" Puppet.stubs(:err) @master.stubs(:puts) end it "should compile a catalog for the specified node" do @master.options[:node] = "foo" Puppet::Resource::Catalog.indirection.expects(:find).with("foo").returns Puppet::Resource::Catalog.new expect { @master.compile }.to exit_with 0 end it "should convert the catalog to a pure-resource catalog and use 'JSON::pretty_generate' to pretty-print the catalog" do catalog = Puppet::Resource::Catalog.new JSON.stubs(:pretty_generate) Puppet::Resource::Catalog.indirection.expects(:find).returns catalog catalog.expects(:to_resource).returns("rescat") @master.options[:node] = "foo" JSON.expects(:pretty_generate).with('rescat', :allow_nan => true, :max_nesting => false) expect { @master.compile }.to exit_with 0 end it "should exit with error code 30 if no catalog can be found" do @master.options[:node] = "foo" Puppet::Resource::Catalog.indirection.expects(:find).returns nil Puppet.expects(:log_exception) expect { @master.compile }.to exit_with 30 end it "should exit with error code 30 if there's a failure" do @master.options[:node] = "foo" Puppet::Resource::Catalog.indirection.expects(:find).raises ArgumentError Puppet.expects(:log_exception) expect { @master.compile }.to exit_with 30 end end describe "the main command" do before :each do @master.preinit @server = stub_everything 'server' Puppet::Network::Server.stubs(:new).returns(@server) @app = stub_everything 'app' Puppet::SSL::Host.stubs(:localhost) Puppet::SSL::CertificateAuthority.stubs(:ca?) Process.stubs(:uid).returns(1000) Puppet.stubs(:service) Puppet[:daemonize] = false Puppet.stubs(:notice) Puppet.stubs(:start) Puppet::Util.stubs(:chuser) end it "should create a Server" do Puppet::Network::Server.expects(:new) @master.main end it "should give the server to the daemon" do @daemon.expects(:server=).with(@server) @master.main end it "should generate a SSL cert for localhost" do Puppet::SSL::Host.expects(:localhost) @master.main end it "should make sure to *only* hit the CA for data" do Puppet::SSL::CertificateAuthority.stubs(:ca?).returns(true) Puppet::SSL::Host.expects(:ca_location=).with(:only) @master.main end def a_user_type_for(username) user = mock 'user' Puppet::Type.type(:user).expects(:new).with { |args| args[:name] == username }.returns user user end context "user privileges" do it "should drop privileges if running as root and the puppet user exists" do Puppet.features.stubs(:root?).returns true a_user_type_for("puppet").expects(:exists?).returns true Puppet::Util.expects(:chuser) @master.main end it "should exit and log an error if running as root and the puppet user does not exist" do Puppet.features.stubs(:root?).returns true a_user_type_for("puppet").expects(:exists?).returns false Puppet.expects(:err).with('Could not change user to puppet. User does not exist and is required to continue.') expect { @master.main }.to exit_with 74 end end it "should log a deprecation notice when running a WEBrick server" do Puppet.expects(:deprecation_warning).with("The WEBrick Puppet master server is deprecated and will be removed in a future release. Please use Puppet Server instead. See http://links.puppet.com/deprecate-rack-webrick-servers for more information.") Puppet.expects(:deprecation_warning).with("Accessing 'bindaddress' as a setting is deprecated.") @master.main end it "should daemonize if needed" do Puppet[:daemonize] = true @daemon.expects(:daemonize) @master.main end it "should start the service" do @daemon.expects(:start) @master.main end describe "with --rack", :if => Puppet.features.rack? do before do require 'puppet/network/http/rack' Puppet::Network::HTTP::Rack.stubs(:new).returns(@app) @master.options.stubs(:[]).with(:rack).returns(:true) end it "it should not start a daemon" do @daemon.expects(:start).never @master.main end it "it should return the app" do app = @master.main expect(app).to equal(@app) end it "should log a deprecation notice" do Puppet.expects(:deprecation_warning).with("The Rack Puppet master server is deprecated and will be removed in a future release. Please use Puppet Server instead. See http://links.puppet.com/deprecate-rack-webrick-servers for more information.") @master.main end end end end end puppet-5.5.10/spec/unit/configurer/0000755005276200011600000000000013417162176017073 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/configurer/fact_handler_spec.rb0000644005276200011600000001454613417161721023051 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/configurer' require 'puppet/configurer/fact_handler' require 'matchers/json' class FactHandlerTester include Puppet::Configurer::FactHandler attr_accessor :environment def initialize(environment) self.environment = environment end def reload_facter # don't want to do this in tests end end describe Puppet::Configurer::FactHandler do include JSONMatchers let(:facthandler) { FactHandlerTester.new('production') } before :each do Puppet::Node::Facts.indirection.terminus_class = :memory end describe "when finding facts" do it "should use the node name value to retrieve the facts" do foo_facts = Puppet::Node::Facts.new('foo') bar_facts = Puppet::Node::Facts.new('bar') Puppet::Node::Facts.indirection.save(foo_facts) Puppet::Node::Facts.indirection.save(bar_facts) Puppet[:certname] = 'foo' Puppet[:node_name_value] = 'bar' expect(facthandler.find_facts).to eq(bar_facts) end it "should set the facts name based on the node_name_fact" do facts = Puppet::Node::Facts.new(Puppet[:node_name_value], 'my_name_fact' => 'other_node_name') Puppet::Node::Facts.indirection.save(facts) Puppet[:node_name_fact] = 'my_name_fact' expect(facthandler.find_facts.name).to eq('other_node_name') end it "should set the node_name_value based on the node_name_fact" do facts = Puppet::Node::Facts.new(Puppet[:node_name_value], 'my_name_fact' => 'other_node_name') Puppet::Node::Facts.indirection.save(facts) Puppet[:node_name_fact] = 'my_name_fact' facthandler.find_facts expect(Puppet[:node_name_value]).to eq('other_node_name') end it "should fail if finding facts fails" do Puppet::Node::Facts.indirection.expects(:find).raises RuntimeError expect { facthandler.find_facts }.to raise_error(Puppet::Error, /Could not retrieve local facts/) end it "should only load fact plugins once" do Puppet::Node::Facts.indirection.expects(:find).once facthandler.find_facts end end context "when serializing" do facts_with_special_characters = [ { :hash => { 'afact' => 'a+b' }, :encoded => '%22values%22%3A%7B%22afact%22%3A%22' + 'a%2Bb' + '%22%7D' }, { :hash => { 'afact' => 'a b' }, :encoded => '%22values%22%3A%7B%22afact%22%3A%22' + 'a%20b' + '%22%7D' }, { :hash => { 'afact' => 'a&b' }, :encoded => '%22values%22%3A%7B%22afact%22%3A%22' + 'a%26b' + '%22%7D' }, { :hash => { 'afact' => 'a*b' }, :encoded => '%22values%22%3A%7B%22afact%22%3A%22' + 'a%2Ab' + '%22%7D' }, { :hash => { 'afact' => 'a=b' }, :encoded => '%22values%22%3A%7B%22afact%22%3A%22' + 'a%3Db' + '%22%7D' }, # different UTF-8 widths # 1-byte A # 2-byte ۿ - http://www.fileformat.info/info/unicode/char/06ff/index.htm - 0xDB 0xBF / 219 191 # 3-byte ᚠ - http://www.fileformat.info/info/unicode/char/16A0/index.htm - 0xE1 0x9A 0xA0 / 225 154 160 # 4-byte 𠜎 - http://www.fileformat.info/info/unicode/char/2070E/index.htm - 0xF0 0xA0 0x9C 0x8E / 240 160 156 142 { :hash => { 'afact' => "A\u06FF\u16A0\u{2070E}" }, :encoded => '%22values%22%3A%7B%22afact%22%3A%22' + 'A%DB%BF%E1%9A%A0%F0%A0%9C%8E' + '%22%7D' }, ] context "as pson" do before :each do Puppet[:preferred_serialization_format] = 'pson' end it "should serialize and CGI escape the fact values for uploading" do facts = Puppet::Node::Facts.new(Puppet[:node_name_value], 'my_name_fact' => 'other_node_name') Puppet::Node::Facts.indirection.save(facts) text = Puppet::Util.uri_query_encode(facthandler.find_facts.render(:pson)) expect(text).to include('%22values%22%3A%7B%22my_name_fact%22%3A%22other_node_name%22%7D') expect(facthandler.facts_for_uploading).to eq({:facts_format => :pson, :facts => text}) end facts_with_special_characters.each do |test_fact| it "should properly accept the fact #{test_fact[:hash]}" do facts = Puppet::Node::Facts.new(Puppet[:node_name_value], test_fact[:hash]) Puppet::Node::Facts.indirection.save(facts) text = Puppet::Util.uri_query_encode(facthandler.find_facts.render(:pson)) to_upload = facthandler.facts_for_uploading expect(to_upload).to eq({:facts_format => :pson, :facts => text}) expect(text).to include(test_fact[:encoded]) # this is not sufficient to test whether these values are sent via HTTP GET or HTTP POST in actual catalog request expect(JSON.parse(URI.unescape(to_upload[:facts]))['values']).to eq(test_fact[:hash]) end end end context "as json" do it "should serialize and CGI escape the fact values for uploading" do facts = Puppet::Node::Facts.new(Puppet[:node_name_value], 'my_name_fact' => 'other_node_name') Puppet::Node::Facts.indirection.save(facts) text = Puppet::Util.uri_query_encode(facthandler.find_facts.render(:json)) expect(text).to include('%22values%22%3A%7B%22my_name_fact%22%3A%22other_node_name%22%7D') expect(facthandler.facts_for_uploading).to eq({:facts_format => 'application/json', :facts => text}) end facts_with_special_characters.each do |test_fact| it "should properly accept the fact #{test_fact[:hash]}" do facts = Puppet::Node::Facts.new(Puppet[:node_name_value], test_fact[:hash]) Puppet::Node::Facts.indirection.save(facts) text = Puppet::Util.uri_query_encode(facthandler.find_facts.render(:json)) to_upload = facthandler.facts_for_uploading expect(to_upload).to eq({:facts_format => 'application/json', :facts => text}) expect(text).to include(test_fact[:encoded]) expect(JSON.parse(URI.unescape(to_upload[:facts]))['values']).to eq(test_fact[:hash]) end end end it "should generate valid facts data against the facts schema" do facts = Puppet::Node::Facts.new(Puppet[:node_name_value], 'my_name_fact' => 'other_node_name') Puppet::Node::Facts.indirection.save(facts) # prefer URI.unescape but validate CGI also works encoded_facts = facthandler.facts_for_uploading[:facts] expect(URI.unescape(encoded_facts)).to validate_against('api/schemas/facts.json') expect(CGI.unescape(encoded_facts)).to validate_against('api/schemas/facts.json') end end end puppet-5.5.10/spec/unit/configurer/plugin_handler_spec.rb0000644005276200011600000000500513417161721023420 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/configurer' require 'puppet/configurer/plugin_handler' describe Puppet::Configurer::PluginHandler do let(:pluginhandler) { Puppet::Configurer::PluginHandler.new() } let(:environment) { Puppet::Node::Environment.create(:myenv, []) } context "server agent version is 5.3.4" do before :each do # PluginHandler#load_plugin has an extra-strong rescue clause # this mock is to make sure that we don't silently ignore errors Puppet.expects(:err).never # Server_agent version needs to be at 5.3.4 in order to mount locales Puppet.push_context({:server_agent_version => "5.3.4"}) end it "downloads plugins, facts, and locales" do Puppet::Configurer::Downloader.any_instance.expects(:evaluate).times(3).returns([]) pluginhandler.download_plugins(environment) end it "returns downloaded plugin, fact, and locale filenames" do Puppet::Configurer::Downloader.any_instance.expects(:evaluate).times(3).returns(%w[/a]).then.returns(%w[/b]).then.returns(%w[/c]) expect(pluginhandler.download_plugins(environment)).to match_array(%w[/a /b /c]) end end context "server agent version is 5.3.3" do before :each do # PluginHandler#load_plugin has an extra-strong rescue clause # this mock is to make sure that we don't silently ignore errors Puppet.expects(:err).never # Server_agent version needs to be at 5.3.4 in order to mount locales Puppet.push_context({:server_agent_version => "5.3.3"}) end it "returns downloaded plugin, fact, but not locale filenames" do Puppet::Configurer::Downloader.any_instance.expects(:evaluate).times(2).returns(%w[/a]).then.returns(%w[/b]) expect(pluginhandler.download_plugins(environment)).to match_array(%w[/a /b]) end end context "blank server agent version" do before :each do # PluginHandler#load_plugin has an extra-strong rescue clause # this mock is to make sure that we don't silently ignore errors Puppet.expects(:err).never # Server_agent version needs to be at 5.3.4 in order to mount locales # A blank version will default to 0.0 Puppet.push_context({:server_agent_version => ""}) end it "returns downloaded plugin, fact, but not locale filenames" do Puppet::Configurer::Downloader.any_instance.expects(:evaluate).times(2).returns(%w[/a]).then.returns(%w[/b]) expect(pluginhandler.download_plugins(environment)).to match_array(%w[/a /b]) end end end puppet-5.5.10/spec/unit/configurer/downloader_spec.rb0000644005276200011600000001526613417161722022576 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/configurer/downloader' describe Puppet::Configurer::Downloader do require 'puppet_spec/files' include PuppetSpec::Files let(:path) { Puppet[:plugindest] } let(:source) { 'puppet://puppet/plugins' } it "should require a name" do expect { Puppet::Configurer::Downloader.new }.to raise_error(ArgumentError) end it "should require a path and a source at initialization" do expect { Puppet::Configurer::Downloader.new("name") }.to raise_error(ArgumentError) end it "should set the name, path and source appropriately" do dler = Puppet::Configurer::Downloader.new("facts", "path", "source") expect(dler.name).to eq("facts") expect(dler.path).to eq("path") expect(dler.source).to eq("source") end def downloader(options = {}) options[:name] ||= "facts" options[:path] ||= path options[:source_permissions] ||= :ignore Puppet::Configurer::Downloader.new(options[:name], options[:path], source, options[:ignore], options[:environment], options[:source_permissions]) end def generate_file_resource(options = {}) dler = downloader(options) dler.file end describe "when creating the file that does the downloading" do it "should create a file instance with the right path and source" do file = generate_file_resource(:path => path, :source => source) expect(file[:path]).to eq(path) expect(file[:source]).to eq([source]) end it "should tag the file with the downloader name" do name = "mydownloader" file = generate_file_resource(:name => name) expect(file[:tag]).to eq([name]) end it "should always recurse" do file = generate_file_resource expect(file[:recurse]).to be_truthy end it "should follow links by default" do file = generate_file_resource expect(file[:links]).to eq(:follow) end it "should always purge" do file = generate_file_resource expect(file[:purge]).to be_truthy end it "should never be in noop" do file = generate_file_resource expect(file[:noop]).to be_falsey end it "should set source_permissions to ignore by default" do file = generate_file_resource expect(file[:source_permissions]).to eq(:ignore) end describe "on POSIX", :if => Puppet.features.posix? do it "should allow source_permissions to be overridden" do file = generate_file_resource(:source_permissions => :use) expect(file[:source_permissions]).to eq(:use) end it "should always set the owner to the current UID" do Process.expects(:uid).returns 51 file = generate_file_resource(:path => '/path') expect(file[:owner]).to eq(51) end it "should always set the group to the current GID" do Process.expects(:gid).returns 61 file = generate_file_resource(:path => '/path') expect(file[:group]).to eq(61) end end describe "on Windows", :if => Puppet.features.microsoft_windows? do it "should omit the owner" do file = generate_file_resource(:path => 'C:/path') expect(file[:owner]).to be_nil end it "should omit the group" do file = generate_file_resource(:path => 'C:/path') expect(file[:group]).to be_nil end end it "should always force the download" do file = generate_file_resource expect(file[:force]).to be_truthy end it "should never back up when downloading" do file = generate_file_resource expect(file[:backup]).to be_falsey end it "should support providing an 'ignore' parameter" do file = generate_file_resource(:ignore => '.svn') expect(file[:ignore]).to eq(['.svn']) end it "should split the 'ignore' parameter on whitespace" do file = generate_file_resource(:ignore => '.svn CVS') expect(file[:ignore]).to eq(['.svn', 'CVS']) end end describe "when creating the catalog to do the downloading" do before do @path = make_absolute("/download/path") @dler = Puppet::Configurer::Downloader.new("foo", @path, make_absolute("source")) end it "should create a catalog and add the file to it" do catalog = @dler.catalog expect(catalog.resources.size).to eq(1) expect(catalog.resources.first.class).to eq(Puppet::Type::File) expect(catalog.resources.first.name).to eq(@path) end it "should specify that it is not managing a host catalog" do expect(@dler.catalog.host_config).to eq(false) end it "should not issue a deprecation warning for source_permissions" do Puppet.expects(:puppet_deprecation_warning).never catalog = @dler.catalog expect(catalog.resources.size).to eq(1) # Must consume catalog to fix warnings end end describe "when downloading" do before do @dl_name = tmpfile("downloadpath") source_name = tmpfile("source") File.open(source_name, 'w') {|f| f.write('hola mundo') } env = Puppet::Node::Environment.remote('foo') @dler = Puppet::Configurer::Downloader.new("foo", @dl_name, source_name, Puppet[:pluginsignore], env) end it "should not skip downloaded resources when filtering on tags" do Puppet[:tags] = 'maytag' @dler.evaluate expect(Puppet::FileSystem.exist?(@dl_name)).to be_truthy end it "should log that it is downloading" do Puppet.expects(:info) @dler.evaluate end it "should return all changed file paths" do trans = mock 'transaction' catalog = mock 'catalog' @dler.expects(:catalog).returns(catalog) catalog.expects(:apply).yields(trans) resource = mock 'resource' resource.expects(:[]).with(:path).returns "/changed/file" trans.expects(:changed?).returns([resource]) expect(@dler.evaluate).to eq(%w{/changed/file}) end it "should yield the resources if a block is given" do trans = mock 'transaction' catalog = mock 'catalog' @dler.expects(:catalog).returns(catalog) catalog.expects(:apply).yields(trans) resource = mock 'resource' resource.expects(:[]).with(:path).returns "/changed/file" trans.expects(:changed?).returns([resource]) yielded = nil @dler.evaluate { |r| yielded = r } expect(yielded).to eq(resource) end it "should catch and log exceptions" do Puppet.expects(:err) # The downloader creates a new catalog for each apply, and really the only object # that it is possible to stub for the purpose of generating a puppet error Puppet::Resource::Catalog.any_instance.stubs(:apply).raises(Puppet::Error, "testing") expect { @dler.evaluate }.not_to raise_error end end end puppet-5.5.10/spec/unit/confine/0000755005276200011600000000000013417162176016351 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/confine/exists_spec.rb0000644005276200011600000000510213417161721021220 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/confine/exists' describe Puppet::Confine::Exists do before do @confine = Puppet::Confine::Exists.new("/my/file") @confine.label = "eh" end it "should be named :exists" do expect(Puppet::Confine::Exists.name).to eq(:exists) end it "should not pass if exists is nil" do confine = Puppet::Confine::Exists.new(nil) confine.label = ":exists => nil" confine.expects(:pass?).with(nil) expect(confine).not_to be_valid end it "should use the 'pass?' method to test validity" do @confine.expects(:pass?).with("/my/file") @confine.valid? end it "should return false if the value is false" do expect(@confine.pass?(false)).to be_falsey end it "should return false if the value does not point to a file" do Puppet::FileSystem.expects(:exist?).with("/my/file").returns false expect(@confine.pass?("/my/file")).to be_falsey end it "should return true if the value points to a file" do Puppet::FileSystem.expects(:exist?).with("/my/file").returns true expect(@confine.pass?("/my/file")).to be_truthy end it "should produce a message saying that a file is missing" do expect(@confine.message("/my/file")).to be_include("does not exist") end describe "and the confine is for binaries" do before { @confine.stubs(:for_binary).returns true } it "should use its 'which' method to look up the full path of the file" do @confine.expects(:which).returns nil @confine.pass?("/my/file") end it "should return false if no executable can be found" do @confine.expects(:which).with("/my/file").returns nil expect(@confine.pass?("/my/file")).to be_falsey end it "should return true if the executable can be found" do @confine.expects(:which).with("/my/file").returns "/my/file" expect(@confine.pass?("/my/file")).to be_truthy end end it "should produce a summary containing all missing files" do Puppet::FileSystem.stubs(:exist?).returns true Puppet::FileSystem.expects(:exist?).with("/two").returns false Puppet::FileSystem.expects(:exist?).with("/four").returns false confine = Puppet::Confine::Exists.new %w{/one /two /three /four} expect(confine.summary).to eq(%w{/two /four}) end it "should summarize multiple instances by returning a flattened array of their summaries" do c1 = mock '1', :summary => %w{one} c2 = mock '2', :summary => %w{two} c3 = mock '3', :summary => %w{three} expect(Puppet::Confine::Exists.summarize([c1, c2, c3])).to eq(%w{one two three}) end end puppet-5.5.10/spec/unit/confine/false_spec.rb0000644005276200011600000000304513417161721020777 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/confine/false' describe Puppet::Confine::False do it "should be named :false" do expect(Puppet::Confine::False.name).to eq(:false) end it "should require a value" do expect { Puppet::Confine.new }.to raise_error(ArgumentError) end describe "when testing values" do before { @confine = Puppet::Confine::False.new("foo") } it "should use the 'pass?' method to test validity" do @confine = Puppet::Confine::False.new("foo") @confine.label = "eh" @confine.expects(:pass?).with("foo") @confine.valid? end it "should return true if the value is false" do expect(@confine.pass?(false)).to be_truthy end it "should return false if the value is not false" do expect(@confine.pass?("else")).to be_falsey end it "should produce a message that a value is true" do @confine = Puppet::Confine::False.new("foo") expect(@confine.message("eh")).to be_include("true") end end it "should be able to produce a summary with the number of incorrectly true values" do confine = Puppet::Confine::False.new %w{one two three four} confine.expects(:pass?).times(4).returns(true).returns(false).returns(true).returns(false) expect(confine.summary).to eq(2) end it "should summarize multiple instances by summing their summaries" do c1 = mock '1', :summary => 1 c2 = mock '2', :summary => 2 c3 = mock '3', :summary => 3 expect(Puppet::Confine::False.summarize([c1, c2, c3])).to eq(6) end end puppet-5.5.10/spec/unit/confine/feature_spec.rb0000644005276200011600000000331213417161721021335 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/confine/feature' describe Puppet::Confine::Feature do it "should be named :feature" do expect(Puppet::Confine::Feature.name).to eq(:feature) end it "should require a value" do expect { Puppet::Confine::Feature.new }.to raise_error(ArgumentError) end it "should always convert values to an array" do expect(Puppet::Confine::Feature.new("/some/file").values).to be_instance_of(Array) end describe "when testing values" do before do @confine = Puppet::Confine::Feature.new("myfeature") @confine.label = "eh" end it "should use the Puppet features instance to test validity" do Puppet.features.expects(:myfeature?) @confine.valid? end it "should return true if the feature is present" do Puppet.features.add(:myfeature) do true end expect(@confine.pass?("myfeature")).to be_truthy end it "should return false if the value is false" do Puppet.features.add(:myfeature) do false end expect(@confine.pass?("myfeature")).to be_falsey end it "should log that a feature is missing" do expect(@confine.message("myfeat")).to be_include("missing") end end it "should summarize multiple instances by returning a flattened array of all missing features" do confines = [] confines << Puppet::Confine::Feature.new(%w{one two}) confines << Puppet::Confine::Feature.new(%w{two}) confines << Puppet::Confine::Feature.new(%w{three four}) features = mock 'feature' features.stub_everything Puppet.stubs(:features).returns features expect(Puppet::Confine::Feature.summarize(confines).sort).to eq(%w{one two three four}.sort) end end puppet-5.5.10/spec/unit/confine/true_spec.rb0000644005276200011600000000270313417161721020664 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/confine/true' describe Puppet::Confine::True do it "should be named :true" do expect(Puppet::Confine::True.name).to eq(:true) end it "should require a value" do expect { Puppet::Confine::True.new }.to raise_error(ArgumentError) end describe "when testing values" do before do @confine = Puppet::Confine::True.new("foo") @confine.label = "eh" end it "should use the 'pass?' method to test validity" do @confine.expects(:pass?).with("foo") @confine.valid? end it "should return true if the value is not false" do expect(@confine.pass?("else")).to be_truthy end it "should return false if the value is false" do expect(@confine.pass?(nil)).to be_falsey end it "should produce the message that a value is false" do expect(@confine.message("eh")).to be_include("false") end end it "should produce the number of false values when asked for a summary" do @confine = Puppet::Confine::True.new %w{one two three four} @confine.expects(:pass?).times(4).returns(true).returns(false).returns(true).returns(false) expect(@confine.summary).to eq(2) end it "should summarize multiple instances by summing their summaries" do c1 = mock '1', :summary => 1 c2 = mock '2', :summary => 2 c3 = mock '3', :summary => 3 expect(Puppet::Confine::True.summarize([c1, c2, c3])).to eq(6) end end puppet-5.5.10/spec/unit/confine/variable_spec.rb0000644005276200011600000000644713417161721021503 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/confine/variable' describe Puppet::Confine::Variable do it "should be named :variable" do expect(Puppet::Confine::Variable.name).to eq(:variable) end it "should require a value" do expect { Puppet::Confine::Variable.new }.to raise_error(ArgumentError) end it "should always convert values to an array" do expect(Puppet::Confine::Variable.new("/some/file").values).to be_instance_of(Array) end it "should have an accessor for its name" do expect(Puppet::Confine::Variable.new(:bar)).to respond_to(:name) end describe "when testing values" do before do @confine = Puppet::Confine::Variable.new("foo") @confine.name = :myvar end it "should use settings if the variable name is a valid setting" do Puppet.settings.expects(:valid?).with(:myvar).returns true Puppet.settings.expects(:value).with(:myvar).returns "foo" @confine.valid? end it "should use Facter if the variable name is not a valid setting" do Puppet.settings.expects(:valid?).with(:myvar).returns false Facter.expects(:value).with(:myvar).returns "foo" @confine.valid? end it "should be valid if the value matches the facter value" do @confine.expects(:test_value).returns "foo" expect(@confine).to be_valid end it "should return false if the value does not match the facter value" do @confine.expects(:test_value).returns "fee" expect(@confine).not_to be_valid end it "should be case insensitive" do @confine.expects(:test_value).returns "FOO" expect(@confine).to be_valid end it "should not care whether the value is a string or symbol" do @confine.expects(:test_value).returns "FOO" expect(@confine).to be_valid end it "should produce a message that the fact value is not correct" do @confine = Puppet::Confine::Variable.new(%w{bar bee}) @confine.name = "eh" message = @confine.message("value") expect(message).to be_include("facter") expect(message).to be_include("bar,bee") end it "should be valid if the test value matches any of the provided values" do @confine = Puppet::Confine::Variable.new(%w{bar bee}) @confine.expects(:test_value).returns "bee" expect(@confine).to be_valid end end describe "when summarizing multiple instances" do it "should return a hash of failing variables and their values" do c1 = Puppet::Confine::Variable.new("one") c1.name = "uno" c1.expects(:valid?).returns false c2 = Puppet::Confine::Variable.new("two") c2.name = "dos" c2.expects(:valid?).returns true c3 = Puppet::Confine::Variable.new("three") c3.name = "tres" c3.expects(:valid?).returns false expect(Puppet::Confine::Variable.summarize([c1, c2, c3])).to eq({"uno" => %w{one}, "tres" => %w{three}}) end it "should combine the values of multiple confines with the same fact" do c1 = Puppet::Confine::Variable.new("one") c1.name = "uno" c1.expects(:valid?).returns false c2 = Puppet::Confine::Variable.new("two") c2.name = "uno" c2.expects(:valid?).returns false expect(Puppet::Confine::Variable.summarize([c1, c2])).to eq({"uno" => %w{one two}}) end end end puppet-5.5.10/spec/unit/confine_collection_spec.rb0000644005276200011600000001114313417161721022116 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/confine_collection' describe Puppet::ConfineCollection do it "should be able to add confines" do expect(Puppet::ConfineCollection.new("label")).to respond_to(:confine) end it "should require a label at initialization" do expect { Puppet::ConfineCollection.new }.to raise_error(ArgumentError) end it "should make its label available" do expect(Puppet::ConfineCollection.new("mylabel").label).to eq("mylabel") end describe "when creating confine instances" do it "should create an instance of the named test with the provided values" do test_class = mock 'test_class' test_class.expects(:new).with(%w{my values}).returns(stub('confine', :label= => nil)) Puppet::Confine.expects(:test).with(:foo).returns test_class Puppet::ConfineCollection.new("label").confine :foo => %w{my values} end it "should copy its label to the confine instance" do confine = mock 'confine' test_class = mock 'test_class' test_class.expects(:new).returns confine Puppet::Confine.expects(:test).returns test_class confine.expects(:label=).with("label") Puppet::ConfineCollection.new("label").confine :foo => %w{my values} end describe "and the test cannot be found" do it "should create a Facter test with the provided values and set the name to the test name" do confine = Puppet::Confine.test(:variable).new(%w{my values}) confine.expects(:name=).with(:foo) confine.class.expects(:new).with(%w{my values}).returns confine Puppet::ConfineCollection.new("label").confine(:foo => %w{my values}) end end describe "and the 'for_binary' option was provided" do it "should mark the test as a binary confine" do confine = Puppet::Confine.test(:exists).new(:bar) confine.expects(:for_binary=).with true Puppet::Confine.test(:exists).expects(:new).with(:bar).returns confine Puppet::ConfineCollection.new("label").confine :exists => :bar, :for_binary => true end end end it "should be valid if no confines are present" do expect(Puppet::ConfineCollection.new("label")).to be_valid end it "should be valid if all confines pass" do c1 = stub 'c1', :valid? => true, :label= => nil c2 = stub 'c2', :valid? => true, :label= => nil Puppet::Confine.test(:true).expects(:new).returns(c1) Puppet::Confine.test(:false).expects(:new).returns(c2) confiner = Puppet::ConfineCollection.new("label") confiner.confine :true => :bar, :false => :bee expect(confiner).to be_valid end it "should not be valid if any confines fail" do c1 = stub 'c1', :valid? => true, :label= => nil c2 = stub 'c2', :valid? => false, :label= => nil Puppet::Confine.test(:true).expects(:new).returns(c1) Puppet::Confine.test(:false).expects(:new).returns(c2) confiner = Puppet::ConfineCollection.new("label") confiner.confine :true => :bar, :false => :bee expect(confiner).not_to be_valid end describe "when providing a summary" do before do @confiner = Puppet::ConfineCollection.new("label") end it "should return a hash" do expect(@confiner.summary).to be_instance_of(Hash) end it "should return an empty hash if the confiner is valid" do expect(@confiner.summary).to eq({}) end it "should add each test type's summary to the hash" do @confiner.confine :true => :bar, :false => :bee Puppet::Confine.test(:true).expects(:summarize).returns :tsumm Puppet::Confine.test(:false).expects(:summarize).returns :fsumm expect(@confiner.summary).to eq({:true => :tsumm, :false => :fsumm}) end it "should not include tests that return 0" do @confiner.confine :true => :bar, :false => :bee Puppet::Confine.test(:true).expects(:summarize).returns 0 Puppet::Confine.test(:false).expects(:summarize).returns :fsumm expect(@confiner.summary).to eq({:false => :fsumm}) end it "should not include tests that return empty arrays" do @confiner.confine :true => :bar, :false => :bee Puppet::Confine.test(:true).expects(:summarize).returns [] Puppet::Confine.test(:false).expects(:summarize).returns :fsumm expect(@confiner.summary).to eq({:false => :fsumm}) end it "should not include tests that return empty hashes" do @confiner.confine :true => :bar, :false => :bee Puppet::Confine.test(:true).expects(:summarize).returns({}) Puppet::Confine.test(:false).expects(:summarize).returns :fsumm expect(@confiner.summary).to eq({:false => :fsumm}) end end end puppet-5.5.10/spec/unit/confine_spec.rb0000644005276200011600000000546613417161721017716 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/confine' describe Puppet::Confine do it "should require a value" do expect { Puppet::Confine.new }.to raise_error(ArgumentError) end it "should always convert values to an array" do expect(Puppet::Confine.new("/some/file").values).to be_instance_of(Array) end it "should have a 'true' test" do expect(Puppet::Confine.test(:true)).to be_instance_of(Class) end it "should have a 'false' test" do expect(Puppet::Confine.test(:false)).to be_instance_of(Class) end it "should have a 'feature' test" do expect(Puppet::Confine.test(:feature)).to be_instance_of(Class) end it "should have an 'exists' test" do expect(Puppet::Confine.test(:exists)).to be_instance_of(Class) end it "should have a 'variable' test" do expect(Puppet::Confine.test(:variable)).to be_instance_of(Class) end describe "when testing all values" do before do @confine = Puppet::Confine.new(%w{a b c}) @confine.label = "foo" end it "should be invalid if any values fail" do @confine.stubs(:pass?).returns true @confine.expects(:pass?).with("b").returns false expect(@confine).not_to be_valid end it "should be valid if all values pass" do @confine.stubs(:pass?).returns true expect(@confine).to be_valid end it "should short-cut at the first failing value" do @confine.expects(:pass?).once.returns false @confine.valid? end it "should log failing confines with the label and message" do @confine.stubs(:pass?).returns false @confine.expects(:message).returns "My message" @confine.expects(:label).returns "Mylabel" Puppet.expects(:debug).with("Mylabel: My message") @confine.valid? end end describe "when testing the result of the values" do before { @confine = Puppet::Confine.new(%w{a b c d}) } it "should return an array with the result of the test for each value" do @confine.stubs(:pass?).returns true @confine.expects(:pass?).with("b").returns false @confine.expects(:pass?).with("d").returns false expect(@confine.result).to eq([true, false, true, false]) end end describe "when requiring" do it "does not cache failed requires when always_retry_plugins is true" do Puppet[:always_retry_plugins] = true Puppet::Confine.expects(:require).with('puppet/confine/osfamily').twice.raises(LoadError) Puppet::Confine.test(:osfamily) Puppet::Confine.test(:osfamily) end it "caches failed requires when always_retry_plugins is false" do Puppet[:always_retry_plugins] = false Puppet::Confine.expects(:require).with('puppet/confine/osfamily').once.raises(LoadError) Puppet::Confine.test(:osfamily) Puppet::Confine.test(:osfamily) end end end puppet-5.5.10/spec/unit/confiner_spec.rb0000644005276200011600000000350313417161721020066 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/confiner' describe Puppet::Confiner do before do @object = Object.new @object.extend(Puppet::Confiner) end it "should have a method for defining confines" do expect(@object).to respond_to(:confine) end it "should have a method for returning its confine collection" do expect(@object).to respond_to(:confine_collection) end it "should have a method for testing suitability" do expect(@object).to respond_to(:suitable?) end it "should delegate its confine method to its confine collection" do coll = mock 'collection' @object.stubs(:confine_collection).returns coll coll.expects(:confine).with(:foo => :bar, :bee => :baz) @object.confine(:foo => :bar, :bee => :baz) end it "should create a new confine collection if one does not exist" do Puppet::ConfineCollection.expects(:new).with("mylabel").returns "mycoll" @object.expects(:to_s).returns "mylabel" expect(@object.confine_collection).to eq("mycoll") end it "should reuse the confine collection" do expect(@object.confine_collection).to equal(@object.confine_collection) end describe "when testing suitability" do before do @coll = mock 'collection' @object.stubs(:confine_collection).returns @coll end it "should return true if the confine collection is valid" do @coll.expects(:valid?).returns true expect(@object).to be_suitable end it "should return false if the confine collection is invalid" do @coll.expects(:valid?).returns false expect(@object).not_to be_suitable end it "should return the summary of the confine collection if a long result is asked for" do @coll.expects(:summary).returns "myresult" expect(@object.suitable?(false)).to eq("myresult") end end end puppet-5.5.10/spec/unit/context/0000755005276200011600000000000013417162176016414 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/context/trusted_information_spec.rb0000644005276200011600000001113013417161722024042 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/context/trusted_information' describe Puppet::Context::TrustedInformation do let(:key) do key = Puppet::SSL::Key.new("myname") key.generate key end let(:csr) do csr = Puppet::SSL::CertificateRequest.new("csr") csr.generate(key, :extension_requests => { '1.3.6.1.4.1.15.1.2.1' => 'Ignored CSR extension', '1.3.6.1.4.1.34380.1.2.1' => 'CSR specific info', '1.3.6.1.4.1.34380.1.2.2' => 'more CSR specific info', }) csr end let(:cert) do cert = Puppet::SSL::Certificate.from_instance(Puppet::SSL::CertificateFactory.build('ca', csr, csr.content, 1)) # The cert must be signed so that it can be successfully be DER-decoded later signer = Puppet::SSL::CertificateSigner.new signer.sign(cert.content, key.content) cert end context "when remote" do it "has no cert information when it isn't authenticated" do trusted = Puppet::Context::TrustedInformation.remote(false, 'ignored', nil) expect(trusted.authenticated).to eq(false) expect(trusted.certname).to be_nil expect(trusted.extensions).to eq({}) end it "is remote and has certificate information when it is authenticated" do trusted = Puppet::Context::TrustedInformation.remote(true, 'cert name', cert) expect(trusted.authenticated).to eq('remote') expect(trusted.certname).to eq('cert name') expect(trusted.extensions).to eq({ '1.3.6.1.4.1.34380.1.2.1' => 'CSR specific info', '1.3.6.1.4.1.34380.1.2.2' => 'more CSR specific info', }) expect(trusted.hostname).to eq('cert name') expect(trusted.domain).to be_nil end it "is remote but lacks certificate information when it is authenticated" do Puppet.expects(:info).once.with("TrustedInformation expected a certificate, but none was given.") trusted = Puppet::Context::TrustedInformation.remote(true, 'cert name', nil) expect(trusted.authenticated).to eq('remote') expect(trusted.certname).to eq('cert name') expect(trusted.extensions).to eq({}) end end context "when local" do it "is authenticated local with the nodes clientcert" do node = Puppet::Node.new('testing', :parameters => { 'clientcert' => 'cert name' }) trusted = Puppet::Context::TrustedInformation.local(node) expect(trusted.authenticated).to eq('local') expect(trusted.certname).to eq('cert name') expect(trusted.extensions).to eq({}) expect(trusted.hostname).to eq('cert name') expect(trusted.domain).to be_nil end it "is authenticated local with no clientcert when there is no node" do trusted = Puppet::Context::TrustedInformation.local(nil) expect(trusted.authenticated).to eq('local') expect(trusted.certname).to be_nil expect(trusted.extensions).to eq({}) expect(trusted.hostname).to be_nil expect(trusted.domain).to be_nil end end it "converts itself to a hash" do trusted = Puppet::Context::TrustedInformation.remote(true, 'cert name', cert) expect(trusted.to_h).to eq({ 'authenticated' => 'remote', 'certname' => 'cert name', 'extensions' => { '1.3.6.1.4.1.34380.1.2.1' => 'CSR specific info', '1.3.6.1.4.1.34380.1.2.2' => 'more CSR specific info', }, 'hostname' => 'cert name', 'domain' => nil }) end it "extracts domain and hostname from certname" do trusted = Puppet::Context::TrustedInformation.remote(true, 'hostname.domain.long', cert) expect(trusted.to_h).to eq({ 'authenticated' => 'remote', 'certname' => 'hostname.domain.long', 'extensions' => { '1.3.6.1.4.1.34380.1.2.1' => 'CSR specific info', '1.3.6.1.4.1.34380.1.2.2' => 'more CSR specific info', }, 'hostname' => 'hostname', 'domain' => 'domain.long' }) end it "freezes the hash" do trusted = Puppet::Context::TrustedInformation.remote(true, 'cert name', cert) expect(trusted.to_h).to be_deeply_frozen end matcher :be_deeply_frozen do match do |actual| unfrozen_items(actual).empty? end failure_message do |actual| "expected all items to be frozen but <#{unfrozen_items(actual).join(', ')}> was not" end define_method :unfrozen_items do |actual| unfrozen = [] stack = [actual] while item = stack.pop if !item.frozen? unfrozen.push(item) end case item when Hash stack.concat(item.keys) stack.concat(item.values) when Array stack.concat(item) end end unfrozen end end end puppet-5.5.10/spec/unit/context_spec.rb0000644005276200011600000000675213417161721017760 0ustar jenkinsjenkinsrequire 'spec_helper' describe Puppet::Context do let(:context) { Puppet::Context.new({ :testing => "value" }) } context "with the implicit test_helper.rb pushed context" do it "fails to lookup a value that does not exist" do expect { context.lookup("a") }.to raise_error(Puppet::Context::UndefinedBindingError) end it "calls a provided block for a default value when none is found" do expect(context.lookup("a") { "default" }).to eq("default") end it "behaves as if pushed a {} if you push nil" do context.push(nil) expect(context.lookup(:testing)).to eq("value") context.pop end it "fails if you try to pop off the top of the stack" do expect { context.pop }.to raise_error(Puppet::Context::StackUnderflow) end end describe "with additional context" do before :each do context.push("a" => 1) end it "holds values for later lookup" do expect(context.lookup("a")).to eq(1) end it "allows rebinding values in a nested context" do inner = nil context.override("a" => 2) do inner = context.lookup("a") end expect(inner).to eq(2) end it "outer bindings are available in an overridden context" do inner_a = nil inner_b = nil context.override("b" => 2) do inner_a = context.lookup("a") inner_b = context.lookup("b") end expect(inner_a).to eq(1) expect(inner_b).to eq(2) end it "overridden bindings do not exist outside of the override" do context.override("a" => 2) do end expect(context.lookup("a")).to eq(1) end it "overridden bindings do not exist outside of the override even when leaving via an error" do begin context.override("a" => 2) do raise "this should still cause the bindings to leave" end rescue end expect(context.lookup("a")).to eq(1) end end context "a rollback" do it "returns to the mark" do context.push("a" => 1) context.mark("start") context.push("a" => 2) context.push("a" => 3) context.pop context.rollback("start") expect(context.lookup("a")).to eq(1) end it "rolls back to the mark across a scoped override" do context.push("a" => 1) context.mark("start") context.override("a" => 3) do context.rollback("start") expect(context.lookup("a")).to eq(1) end expect(context.lookup("a")).to eq(1) end it "fails to rollback to an unknown mark" do expect do context.rollback("unknown") end.to raise_error(Puppet::Context::UnknownRollbackMarkError) end it "does not allow the same mark to be set twice" do context.mark("duplicate") expect do context.mark("duplicate") end.to raise_error(Puppet::Context::DuplicateRollbackMarkError) end end context 'support lazy entries' do it 'by evaluating a bound proc' do result = nil context.override(:a => lambda {|| 'yay'}) do result = context.lookup(:a) end expect(result).to eq('yay') end it 'by memoizing the bound value' do result1 = nil result2 = nil original = 'yay' context.override(:a => lambda {|| tmp = original; original = 'no'; tmp}) do result1 = context.lookup(:a) result2 = context.lookup(:a) end expect(result1).to eq('yay') expect(original).to eq('no') expect(result2).to eq('yay') end end end puppet-5.5.10/spec/unit/data_binding_spec.rb0000644005276200011600000000051613417161721020667 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/data_binding' describe Puppet::DataBinding do describe "when indirecting" do it "should default to the 'hiera' data_binding terminus" do Puppet::DataBinding.indirection.reset_terminus_class expect(Puppet::DataBinding.indirection.terminus_class).to eq(:hiera) end end end puppet-5.5.10/spec/unit/data_providers/0000755005276200011600000000000013417162176017736 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/data_providers/function_data_provider_spec.rb0000644005276200011600000001240213417161721026017 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/compiler' describe "when using function data provider" do include PuppetSpec::Compiler # There is a fully configured environment in fixtures in this location let(:environmentpath) { parent_fixture('environments') } around(:each) do |example| # Initialize settings to get a full compile as close as possible to a real # environment load Puppet.settings.initialize_global_settings # Initialize loaders based on the environmentpath. It does not work to # just set the setting environmentpath for some reason - this achieves the same: # - first a loader is created, loading directory environments from the fixture (there is # one such environment, 'production', which will be loaded since the node references this # environment by name). # - secondly, the created env loader is set as 'environments' in the puppet context. # loader = Puppet::Environments::Directories.new(environmentpath, []) Puppet.override(:environments => loader) do example.run end end # The environment configured in the fixture has one module called 'abc'. Its class abc, includes # a class called 'def'. This class has three parameters test1, test2, and test3 and it creates # three notify with name set to the value of the three parameters. # # Since the abc class does not provide any parameter values to its def class, it attempts to # get them from data lookup. The fixture has an environment that is configured to load data from # a function called environment::data, this data sets test1, and test2. # The module 'abc' is configured to get data by calling the function abc::data(), this function # returns data for all three parameters test1-test3, now with the prefix 'module'. # # The result should be that the data set in the environment wins over those set in the # module. # it 'gets data from module and environment functions and combines them with env having higher precedence' do Puppet[:code] = 'include abc' node = Puppet::Node.new("testnode", :facts => Puppet::Node::Facts.new("facts", {}), :environment => 'production') compiler = Puppet::Parser::Compiler.new(node) catalog = compiler.compile() resources_created_in_fixture = ["Notify[env_test1]", "Notify[env_test2]", "Notify[module_test3]", "Notify[env_test2-ipl]"] expect(resources_in(catalog)).to include(*resources_created_in_fixture) end it 'gets data from module having a puppet function delivering module data' do Puppet[:code] = 'include xyz' node = Puppet::Node.new("testnode", :facts => Puppet::Node::Facts.new("facts", {}), :environment => 'production') compiler = Puppet::Parser::Compiler.new(node) catalog = compiler.compile() resources_created_in_fixture = ["Notify[env_test1]", "Notify[env_test2]", "Notify[module_test3]"] expect(resources_in(catalog)).to include(*resources_created_in_fixture) end it 'gets data from puppet function delivering environment data' do Puppet[:code] = <<-CODE function environment::data() { { 'cls::test1' => 'env_puppet1', 'cls::test2' => 'env_puppet2' } } class cls ($test1, $test2) { notify { $test1: } notify { $test2: } } include cls CODE node = Puppet::Node.new('testnode', :facts => Puppet::Node::Facts.new('facts', {}), :environment => 'production') catalog = Puppet::Parser::Compiler.new(node).compile expect(resources_in(catalog)).to include('Notify[env_puppet1]', 'Notify[env_puppet2]') end it 'raises an error if the environment data function does not return a hash' do Puppet[:code] = 'include abc' # find the loaders to patch with faulty function node = Puppet::Node.new("testnode", :facts => Puppet::Node::Facts.new("facts", {}), :environment => 'production') compiler = Puppet::Parser::Compiler.new(node) loaders = compiler.loaders() env_loader = loaders.private_environment_loader() f = Puppet::Functions.create_function('environment::data') do def data() 'this is not a hash' end end env_loader.add_entry(:function, 'environment::data', f.new(compiler.topscope, env_loader), nil) expect do compiler.compile() end.to raise_error(/Value returned from deprecated API function 'environment::data' has wrong type/) end it 'raises an error if the module data function does not return a hash' do Puppet[:code] = 'include abc' # find the loaders to patch with faulty function node = Puppet::Node.new("testnode", :facts => Puppet::Node::Facts.new("facts", {}), :environment => 'production') compiler = Puppet::Parser::Compiler.new(node) loaders = compiler.loaders() module_loader = loaders.public_loader_for_module('abc') f = Puppet::Functions.create_function('abc::data') do def data() 'this is not a hash' end end module_loader.add_entry(:function, 'abc::data', f.new(compiler.topscope, module_loader), nil) expect do compiler.compile() end.to raise_error(/Value returned from deprecated API function 'abc::data' has wrong type/) end def parent_fixture(dir_name) File.absolute_path(File.join(my_fixture_dir(), "../#{dir_name}")) end def resources_in(catalog) catalog.resources.map(&:ref) end end puppet-5.5.10/spec/unit/data_providers/hiera_data_provider_spec.rb0000644005276200011600000003572413417161721025276 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/compiler' describe "when using a hiera data provider" do include PuppetSpec::Compiler # There is a fully configured 'sample' environment in fixtures at this location let(:environmentpath) { parent_fixture('environments') } let(:facts) { Puppet::Node::Facts.new("facts", {}) } around(:each) do |example| # Initialize settings to get a full compile as close as possible to a real # environment load Puppet.settings.initialize_global_settings # Initialize loaders based on the environmentpath. It does not work to # just set the setting environmentpath for some reason - this achieves the same: # - first a loader is created, loading directory environments from the fixture (there is # one environment, 'sample', which will be loaded since the node references this # environment by name). # - secondly, the created env loader is set as 'environments' in the puppet context. # loader = Puppet::Environments::Directories.new(environmentpath, []) Puppet.override(:environments => loader) do example.run end end def compile_and_get_notifications(environment, code = nil) extract_notifications(compile(environment, code)) end def compile(environment, code = nil) Puppet[:code] = code if code node = Puppet::Node.new("testnode", :facts => facts, :environment => environment) compiler = Puppet::Parser::Compiler.new(node) compiler.topscope['domain'] = 'example.com' block_given? ? compiler.compile { |catalog| yield(compiler); catalog } : compiler.compile end def extract_notifications(catalog) catalog.resources.map(&:ref).select { |r| r.start_with?('Notify[') }.map { |r| r[7..-2] } end it 'uses default configuration for environment and module data' do resources = compile_and_get_notifications('hiera_defaults') expect(resources).to include('module data param_a is 100, param default is 200, env data param_c is 300') end it 'reads hiera.yaml in environment root and configures multiple json and yaml providers' do resources = compile_and_get_notifications('hiera_env_config') expect(resources).to include("env data param_a is 10, env data param_b is 20, env data param_c is 30, env data param_d is 40, env data param_e is 50, env data param_yaml_utf8 is \u16EB\u16D2\u16E6, env data param_json_utf8 is \u16A0\u16C7\u16BB") end it 'reads hiera.yaml in module root and configures multiple json and yaml providers' do resources = compile_and_get_notifications('hiera_module_config') expect(resources).to include('module data param_a is 100, module data param_b is 200, module data param_c is 300, module data param_d is 400, module data param_e is 500') end it 'keeps lookup_options in one module separate from lookup_options in another' do resources1 = compile('hiera_modules', 'include one').resources.select {|r| r.ref.start_with?('Class[One]')} resources2 = compile('hiera_modules', 'include two').resources.select {|r| r.ref.start_with?('Class[One]')} expect(resources1).to eq(resources2) end it 'does not perform merge of values declared in environment and module when resolving parameters' do resources = compile_and_get_notifications('hiera_misc') expect(resources).to include('env 1, ') end it 'performs hash merge of values declared in environment and module' do resources = compile_and_get_notifications('hiera_misc', '$r = lookup(one::test::param, Hash[String,String], hash) notify{"${r[key1]}, ${r[key2]}":}') expect(resources).to include('env 1, module 2') end it 'performs unique merge of values declared in environment and module' do resources = compile_and_get_notifications('hiera_misc', '$r = lookup(one::array, Array[String], unique) notify{"${r}":}') expect(resources.size).to eq(1) expect(resources[0][1..-2].split(', ')).to contain_exactly('first', 'second', 'third', 'fourth') end it 'performs merge found in lookup_options in environment of values declared in environment and module' do resources = compile_and_get_notifications('hiera_misc', 'include one::lopts_test') expect(resources.size).to eq(1) expect(resources[0]).to eq('A, B, C, MA, MB, MC') end it 'performs merge found in lookup_options in module of values declared in environment and module' do resources = compile_and_get_notifications('hiera_misc', 'include one::loptsm_test') expect(resources.size).to eq(1) expect(resources[0]).to eq('A, B, C, MA, MB, MC') end it "will not find 'lookup_options' as a regular value" do expect { compile_and_get_notifications('hiera_misc', '$r = lookup("lookup_options")') }.to raise_error(Puppet::DataBinding::LookupError, /did not find a value/) end it 'does find unqualified keys in the environment' do resources = compile_and_get_notifications('hiera_misc', 'notify{lookup(ukey1):}') expect(resources).to include('Some value') end it 'does not find unqualified keys in the module' do expect do compile_and_get_notifications('hiera_misc', 'notify{lookup(ukey2):}') end.to raise_error(Puppet::ParseError, /did not find a value for the name 'ukey2'/) end it 'can use interpolation lookup method "alias"' do resources = compile_and_get_notifications('hiera_misc', 'notify{lookup(km_alias):}') expect(resources).to include('Value from interpolation with alias') end it 'can use interpolation lookup method "lookup"' do resources = compile_and_get_notifications('hiera_misc', 'notify{lookup(km_lookup):}') expect(resources).to include('Value from interpolation with lookup') end it 'can use interpolation lookup method "hiera"' do resources = compile_and_get_notifications('hiera_misc', 'notify{lookup(km_hiera):}') expect(resources).to include('Value from interpolation with hiera') end it 'can use interpolation lookup method "literal"' do resources = compile_and_get_notifications('hiera_misc', 'notify{lookup(km_literal):}') expect(resources).to include('Value from interpolation with literal') end it 'can use interpolation lookup method "scope"' do resources = compile_and_get_notifications('hiera_misc', '$target_scope = "with scope" notify{lookup(km_scope):}') expect(resources).to include('Value from interpolation with scope') end it 'can use interpolation using default lookup method (scope)' do resources = compile_and_get_notifications('hiera_misc', '$target_default = "with default" notify{lookup(km_default):}') expect(resources).to include('Value from interpolation with default') end it 'performs lookup using qualified expressions in interpolation' do resources = compile_and_get_notifications('hiera_misc', "$os = { name => 'Fedora' } notify{lookup(km_qualified):}") expect(resources).to include('Value from qualified interpolation OS = Fedora') end it 'can have multiple interpolate expressions in one value' do resources = compile_and_get_notifications('hiera_misc', 'notify{lookup(km_multi):}') expect(resources).to include('cluster/%{::cluster}/%{role}') end it 'performs single quoted interpolation' do resources = compile_and_get_notifications('hiera_misc', 'notify{lookup(km_sqalias):}') expect(resources).to include('Value from interpolation with alias') end it 'uses compiler lifecycle for caching' do Puppet[:code] = 'notify{lookup(one::my_var):}' node = Puppet::Node.new('testnode', :facts => facts, :environment => 'hiera_module_config') compiler = Puppet::Parser::Compiler.new(node) compiler.topscope['my_fact'] = 'server1' expect(extract_notifications(compiler.compile)).to include('server1') compiler = Puppet::Parser::Compiler.new(node) compiler.topscope['my_fact'] = 'server2' expect(extract_notifications(compiler.compile)).to include('server2') compiler = Puppet::Parser::Compiler.new(node) expect(extract_notifications(compiler.compile)).to include('In name.yaml') end it 'traps endless interpolate recursion' do expect do compile_and_get_notifications('hiera_misc', '$r1 = "%{r2}" $r2 = "%{r1}" notify{lookup(recursive):}') end.to raise_error(Puppet::DataBinding::RecursiveLookupError, /detected in \[recursive, scope:r1, scope:r2\]/) end it 'does not consider use of same key in the lookup and scope namespaces as recursion' do resources = compile_and_get_notifications('hiera_misc', 'notify{lookup(domain):}') expect(resources).to include('-- example.com --') end it 'traps bad alias declarations' do expect do compile_and_get_notifications('hiera_misc', "$r1 = 'Alias within string %{alias(\"r2\")}' $r2 = '%{r1}' notify{lookup(recursive):}") end.to raise_error(Puppet::DataBinding::LookupError, /'alias' interpolation is only permitted if the expression is equal to the entire string/) end it 'reports syntax errors for JSON files' do expect do compile_and_get_notifications('hiera_bad_syntax_json') end.to raise_error(Puppet::DataBinding::LookupError, /Unable to parse \(#{environmentpath}[^)]+\):/) end it 'reports syntax errors for YAML files' do expect do compile_and_get_notifications('hiera_bad_syntax_yaml') end.to raise_error(Puppet::DataBinding::LookupError, /Unable to parse \(#{environmentpath}[^)]+\):/) end describe 'when using explain' do it 'will report config path (original and resolved), data path (original and resolved), and interpolation (before and after)' do compile('hiera_misc', '$target_scope = "with scope"') do |compiler| lookup_invocation = Puppet::Pops::Lookup::Invocation.new(compiler.topscope, {}, {}, true) Puppet::Pops::Lookup.lookup('km_scope', nil, nil, nil, nil, lookup_invocation) expect(lookup_invocation.explainer.explain).to include(<<-EOS) Path "#{environmentpath}/hiera_misc/data/common.yaml" Original path: "common.yaml" Interpolation on "Value from interpolation %{scope("target_scope")}" Global Scope Found key: "target_scope" value: "with scope" Found key: "km_scope" value: "Value from interpolation with scope" EOS end end it 'will report that merge options was found in the lookup_options hash' do compile('hiera_misc', '$target_scope = "with scope"') do |compiler| lookup_invocation = Puppet::Pops::Lookup::Invocation.new(compiler.topscope, {}, {}, true) Puppet::Pops::Lookup.lookup('one::loptsm_test::hash', nil, nil, nil, nil, lookup_invocation) expect(lookup_invocation.explainer.explain).to include("Using merge options from \"lookup_options\" hash") end end it 'will report lookup_options details in combination with details of found value' do compile('hiera_misc', '$target_scope = "with scope"') do |compiler| lookup_invocation = Puppet::Pops::Lookup::Invocation.new(compiler.topscope, {}, {}, Puppet::Pops::Lookup::Explainer.new(true)) Puppet::Pops::Lookup.lookup('one::loptsm_test::hash', nil, nil, nil, nil, lookup_invocation) expect(lookup_invocation.explainer.explain).to eq(< { "merge" => "deep" } } Module "one" Data Provider (hiera configuration version 5) Hierarchy entry "Common" Path "#{environmentpath}/hiera_misc/modules/one/data/common.yaml" Original path: "common.yaml" Found key: "lookup_options" value: { "one::loptsm_test::hash" => { "merge" => "deep" } } Merge strategy hash Global and Environment Found key: "lookup_options" value: { "one::lopts_test::hash" => { "merge" => "deep" } } Module one Found key: "lookup_options" value: { "one::loptsm_test::hash" => { "merge" => "deep" } } Merged result: { "one::loptsm_test::hash" => { "merge" => "deep" }, "one::lopts_test::hash" => { "merge" => "deep" } } Using merge options from "lookup_options" hash Searching for "one::loptsm_test::hash" Merge strategy deep Global Data Provider (hiera configuration version 5) No such key: "one::loptsm_test::hash" Environment Data Provider (hiera configuration version 5) Hierarchy entry "Common" Path "#{environmentpath}/hiera_misc/data/common.yaml" Original path: "common.yaml" Found key: "one::loptsm_test::hash" value: { "a" => "A", "b" => "B", "m" => { "ma" => "MA", "mb" => "MB" } } Module "one" Data Provider (hiera configuration version 5) Hierarchy entry "Common" Path "#{environmentpath}/hiera_misc/modules/one/data/common.yaml" Original path: "common.yaml" Found key: "one::loptsm_test::hash" value: { "a" => "A", "c" => "C", "m" => { "ma" => "MA", "mc" => "MC" } } Merged result: { "a" => "A", "c" => "C", "m" => { "ma" => "MA", "mc" => "MC", "mb" => "MB" }, "b" => "B" } EOS end end it 'will report config path (original and resolved), data path (original and resolved), and interpolation (before and after)' do compile('hiera_misc', '$target_scope = "with scope"') do |compiler| lookup_invocation = Puppet::Pops::Lookup::Invocation.new(compiler.topscope, {}, {}, Puppet::Pops::Lookup::Explainer.new(true, true)) Puppet::Pops::Lookup.lookup('one::loptsm_test::hash', nil, nil, nil, nil, lookup_invocation) expect(lookup_invocation.explainer.explain).to eq(< { "merge" => "deep" } } Module "one" Data Provider (hiera configuration version 5) Hierarchy entry "Common" Path "#{environmentpath}/hiera_misc/modules/one/data/common.yaml" Original path: "common.yaml" Found key: "lookup_options" value: { "one::loptsm_test::hash" => { "merge" => "deep" } } Merged result: { "one::loptsm_test::hash" => { "merge" => "deep" }, "one::lopts_test::hash" => { "merge" => "deep" } } EOS end end end def parent_fixture(dir_name) File.absolute_path(File.join(my_fixture_dir(), "../#{dir_name}")) end def resources_in(catalog) catalog.resources.map(&:ref) end end puppet-5.5.10/spec/unit/datatypes_spec.rb0000644005276200011600000002316613417161721020270 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/compiler' require 'puppet_spec/files' require 'puppet/pops' module PuppetSpec::DataTypes describe "Puppet::DataTypes" do include PuppetSpec::Compiler include PuppetSpec::Files let(:modules) { { 'mytest' => mytest } } let(:datatypes) { {} } let(:environments_dir) { Puppet[:environmentpath] } let(:mytest) {{ 'lib' => { 'puppet' => { 'datatypes' => mytest_datatypes, 'functions' => mytest_functions }, 'puppetx' => { 'mytest' => mytest_classes }, } }} let(:mytest_datatypes) { {} } let(:mytest_classes) { {} } let(:mytest_functions) { { 'mytest' => { 'to_data.rb' => <<-RUBY.unindent, Puppet::Functions.create_function('mytest::to_data') do def to_data(data) Puppet::Pops::Serialization::ToDataConverter.convert(data, { :rich_data => true, :symbol_as_string => true, :type_by_reference => true, :message_prefix => 'test' }) end end RUBY 'from_data.rb' => <<-RUBY.unindent, Puppet::Functions.create_function('mytest::from_data') do def from_data(data) Puppet::Pops::Serialization::FromDataConverter.convert(data) end end RUBY 'serialize.rb' => <<-RUBY.unindent, Puppet::Functions.create_function('mytest::serialize') do def serialize(data) buffer = '' serializer = Puppet::Pops::Serialization::Serializer.new( Puppet::Pops::Serialization::JSON::Writer.new(buffer)) serializer.write(data) serializer.finish buffer end end RUBY 'deserialize.rb' => <<-RUBY.unindent, Puppet::Functions.create_function('mytest::deserialize') do def deserialize(data) deserializer = Puppet::Pops::Serialization::Deserializer.new( Puppet::Pops::Serialization::JSON::Reader.new(data), Puppet::Pops::Loaders.find_loader(nil)) deserializer.read end end RUBY } } } let(:testing_env_dir) do dir_contained_in(environments_dir, testing_env) env_dir = File.join(environments_dir, 'testing') PuppetSpec::Files.record_tmp(env_dir) env_dir end let(:modules_dir) { File.join(testing_env_dir, 'modules') } let(:env) { Puppet::Node::Environment.create(:testing, [modules_dir]) } let(:node) { Puppet::Node.new('test', :environment => env) } let(:testing_env) do { 'testing' => { 'lib' => { 'puppet' => { 'datatypes' => datatypes } }, 'modules' => modules, } } end before(:each) do Puppet[:environment] = 'testing' end context 'when creating type with derived attributes using implementation' do let(:datatypes) { { 'mytype.rb' => <<-RUBY.unindent, Puppet::DataTypes.create_type('Mytype') do interface <<-PUPPET attributes => { name => { type => String }, year_of_birth => { type => Integer }, age => { type => Integer, kind => derived }, } PUPPET implementation do def age DateTime.now.year - @year_of_birth end end end RUBY } } it 'loads and returns value of attribute' do expect(eval_and_collect_notices('notice(Mytype("Bob", 1984).age)', node)).to eql(["#{DateTime.now.year - 1984}"]) end it 'can convert value to and from data' do expect(eval_and_collect_notices(<<-PUPPET.unindent, node)).to eql(['false', 'true', 'true', "#{DateTime.now.year - 1984}"]) $m = Mytype("Bob", 1984) $d = $m.mytest::to_data notice($m == $d) notice($d =~ Data) $m2 = $d.mytest::from_data notice($m == $m2) notice($m2.age) PUPPET end end context 'when creating type for an already implemented class' do let(:datatypes) { { 'mytest.rb' => <<-RUBY.unindent, Puppet::DataTypes.create_type('Mytest') do interface <<-PUPPET attributes => { name => { type => String }, year_of_birth => { type => Integer }, age => { type => Integer, kind => derived }, }, functions => { '[]' => Callable[[String[1]], Variant[String, Integer]] } PUPPET implementation_class PuppetSpec::DataTypes::MyTest end RUBY } } before(:each) do class ::PuppetSpec::DataTypes::MyTest attr_reader :name, :year_of_birth def initialize(name, year_of_birth) @name = name @year_of_birth = year_of_birth end def age DateTime.now.year - @year_of_birth end def [](key) case key when 'name' @name when 'year_of_birth' @year_of_birth when 'age' age else nil end end def ==(o) self.class == o.class && @name == o.name && @year_of_birth == o.year_of_birth end end end after(:each) do ::PuppetSpec::DataTypes.send(:remove_const, :MyTest) end it 'loads and returns value of attribute' do expect(eval_and_collect_notices('notice(Mytest("Bob", 1984).age)', node)).to eql(["#{DateTime.now.year - 1984}"]) end it 'can convert value to and from data' do expect(eval_and_collect_notices(<<-PUPPET.unindent, node)).to eql(['true', 'true', "#{DateTime.now.year - 1984}"]) $m = Mytest("Bob", 1984) $d = $m.mytest::to_data notice($d =~ Data) $m2 = $d.mytest::from_data notice($m == $m2) notice($m2.age) PUPPET end it 'can access using implemented [] method' do expect(eval_and_collect_notices(<<-PUPPET.unindent, node)).to eql(['Bob', "#{DateTime.now.year - 1984}"]) $m = Mytest("Bob", 1984) notice($m['name']) notice($m['age']) PUPPET end it 'can serialize and deserialize data' do expect(eval_and_collect_notices(<<-PUPPET.unindent, node)).to eql(['true', 'true', "#{DateTime.now.year - 1984}"]) $m = Mytest("Bob", 1984) $d = $m.mytest::serialize notice($d =~ String) $m2 = $d.mytest::deserialize notice($m == $m2) notice($m2.age) PUPPET end end context 'when creating type with custom new_function' do let(:datatypes) { { 'mytest.rb' => <<-RUBY.unindent, Puppet::DataTypes.create_type('Mytest') do interface <<-PUPPET attributes => { strings => { type => Array[String] }, ints => { type => Array[Integer] }, } PUPPET implementation_class PuppetSpec::DataTypes::MyTest end RUBY } } before(:each) do class ::PuppetSpec::DataTypes::MyTest def self.create_new_function(t) Puppet::Functions.create_function('new_%s' % t.name) do dispatch :create do repeated_param 'Variant[String,Integer]', :args end def create(*args) ::PuppetSpec::DataTypes::MyTest.new(*args.partition { |arg| arg.is_a?(String) }) end end end attr_reader :strings, :ints def initialize(strings, ints) @strings = strings @ints = ints end end end after(:each) do ::PuppetSpec::DataTypes.send(:remove_const, :MyTest) end it 'loads and calls custom new function' do expect(eval_and_collect_notices('notice(Mytest("A", 32, "B", 20).ints)', node)).to eql(['[32, 20]']) end end context 'with data type and class defined in a module' do let(:mytest_classes) { { 'position.rb' => <<-RUBY module PuppetX; module Mytest; class Position attr_reader :x, :y def initialize(x, y) @x = x @y = y end end; end; end RUBY } } after(:each) do ::PuppetX.send(:remove_const, :Mytest) end context 'in module namespace' do let(:mytest_datatypes) { { 'mytest' => { 'position.rb' => <<-RUBY Puppet::DataTypes.create_type('Mytest::Position') do interface <<-PUPPET attributes => { x => Integer, y => Integer } PUPPET load_file('puppetx/mytest/position') implementation_class PuppetX::Mytest::Position end RUBY } } } it 'loads and returns value of attribute' do expect(eval_and_collect_notices('notice(Mytest::Position(23, 12).x)', node)).to eql(['23']) end end context 'in top namespace' do let(:mytest_datatypes) { { 'position.rb' => <<-RUBY Puppet::DataTypes.create_type('Position') do interface <<-PUPPET attributes => { x => Integer, y => Integer } PUPPET load_file('puppetx/mytest/position') implementation_class PuppetX::Mytest::Position end RUBY } } it 'loads and returns value of attribute' do expect(eval_and_collect_notices('notice(Position(23, 12).x)', node)).to eql(['23']) end end end end end puppet-5.5.10/spec/unit/environments_spec.rb0000644005276200011600000006751613417161721021030 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/environments' require 'puppet/file_system' require 'matchers/include' require 'matchers/include_in_order' module PuppetEnvironments describe Puppet::Environments do include Matchers::Include FS = Puppet::FileSystem before(:each) do Puppet.settings.initialize_global_settings Puppet[:environment_timeout] = "unlimited" end let(:directory_tree) do FS::MemoryFile.a_directory(File.expand_path("envdir"), [ FS::MemoryFile.a_regular_file_containing("ignored_file", ''), FS::MemoryFile.a_directory("an_environment", [ FS::MemoryFile.a_missing_file("environment.conf"), FS::MemoryFile.a_directory("modules"), FS::MemoryFile.a_directory("manifests"), ]), FS::MemoryFile.a_directory("another_environment", [ FS::MemoryFile.a_missing_file("environment.conf"), ]), FS::MemoryFile.a_missing_file("doesnotexist"), ]) end describe "directories loader" do it "lists environments" do global_path_1_location = File.expand_path("global_path_1") global_path_2_location = File.expand_path("global_path_2") global_path_1 = FS::MemoryFile.a_directory(global_path_1_location) global_path_2 = FS::MemoryFile.a_directory(global_path_2_location) loader_from(:filesystem => [directory_tree, global_path_1, global_path_2], :directory => directory_tree, :modulepath => [global_path_1_location, global_path_2_location]) do |loader| expect(loader.list).to include_in_any_order( environment(:an_environment). with_manifest("#{FS.path_string(directory_tree)}/an_environment/manifests"). with_modulepath(["#{FS.path_string(directory_tree)}/an_environment/modules", global_path_1_location, global_path_2_location]), environment(:another_environment)) end end it "has search_paths" do loader_from(:filesystem => [directory_tree], :directory => directory_tree) do |loader| expect(loader.search_paths).to eq(["file://#{directory_tree}"]) end end it "ignores directories that are not valid env names (alphanumeric and _)" do envdir = FS::MemoryFile.a_directory(File.expand_path("envdir"), [ FS::MemoryFile.a_directory(".foo"), FS::MemoryFile.a_directory("bar-thing"), FS::MemoryFile.a_directory("with spaces"), FS::MemoryFile.a_directory("some.thing"), FS::MemoryFile.a_directory("env1", [ FS::MemoryFile.a_missing_file("environment.conf"), ]), FS::MemoryFile.a_directory("env2", [ FS::MemoryFile.a_missing_file("environment.conf"), ]), ]) loader_from(:filesystem => [envdir], :directory => envdir) do |loader| expect(loader.list).to include_in_any_order(environment(:env1), environment(:env2)) end end it "gets a particular environment" do loader_from(:filesystem => [directory_tree], :directory => directory_tree) do |loader| expect(loader.get("an_environment")).to environment(:an_environment) end end it "raises error when environment not found" do loader_from(:filesystem => [directory_tree], :directory => directory_tree) do |loader| expect do loader.get!("doesnotexist") end.to raise_error(Puppet::Environments::EnvironmentNotFound) end end it "returns nil if an environment can't be found" do loader_from(:filesystem => [directory_tree], :directory => directory_tree) do |loader| expect(loader.get("doesnotexist")).to be_nil end end context "with an environment.conf" do let(:envdir) do FS::MemoryFile.a_directory(File.expand_path("envdir"), [ FS::MemoryFile.a_directory("env1", [ FS::MemoryFile.a_regular_file_containing("environment.conf", content), ]), ]) end let(:manifestdir) { FS::MemoryFile.a_directory(File.expand_path("/some/manifest/path")) } let(:modulepath) do [ FS::MemoryFile.a_directory(File.expand_path("/some/module/path")), FS::MemoryFile.a_directory(File.expand_path("/some/other/path")), ] end let(:content) do <<-EOF manifest=#{manifestdir} modulepath=#{modulepath.join(File::PATH_SEPARATOR)} config_version=/some/script static_catalogs=false EOF end it "reads environment.conf settings" do loader_from(:filesystem => [envdir, manifestdir, modulepath].flatten, :directory => envdir) do |loader| expect(loader.get("env1")).to environment(:env1). with_manifest(manifestdir.path). with_modulepath(modulepath.map(&:path)) end end it "does not append global_module_path to environment.conf modulepath setting" do global_path_location = File.expand_path("global_path") global_path = FS::MemoryFile.a_directory(global_path_location) loader_from(:filesystem => [envdir, manifestdir, modulepath, global_path].flatten, :directory => envdir, :modulepath => [global_path]) do |loader| expect(loader.get("env1")).to environment(:env1). with_manifest(manifestdir.path). with_modulepath(modulepath.map(&:path)) end end it "reads config_version setting" do loader_from(:filesystem => [envdir, manifestdir, modulepath].flatten, :directory => envdir) do |loader| expect(loader.get("env1")).to environment(:env1). with_manifest(manifestdir.path). with_modulepath(modulepath.map(&:path)). with_config_version(File.expand_path('/some/script')) end end it "reads static_catalogs setting" do loader_from(:filesystem => [envdir, manifestdir, modulepath].flatten, :directory => envdir) do |loader| expect(loader.get("env1")).to environment(:env1). with_manifest(manifestdir.path). with_modulepath(modulepath.map(&:path)). with_config_version(File.expand_path('/some/script')). with_static_catalogs(false) end end it "accepts an empty environment.conf without warning" do content = nil envdir = FS::MemoryFile.a_directory(File.expand_path("envdir"), [ FS::MemoryFile.a_directory("env1", [ FS::MemoryFile.a_regular_file_containing("environment.conf", content), ]), ]) manifestdir = FS::MemoryFile.a_directory(File.join(envdir, "env1", "manifests")) modulesdir = FS::MemoryFile.a_directory(File.join(envdir, "env1", "modules")) global_path_location = File.expand_path("global_path") global_path = FS::MemoryFile.a_directory(global_path_location) loader_from(:filesystem => [envdir, manifestdir, modulesdir, global_path].flatten, :directory => envdir, :modulepath => [global_path]) do |loader| expect(loader.get("env1")).to environment(:env1). with_manifest("#{FS.path_string(envdir)}/env1/manifests"). with_modulepath(["#{FS.path_string(envdir)}/env1/modules", global_path_location]). with_config_version(nil). with_static_catalogs(true) end expect(@logs).to be_empty end it "logs a warning, but processes the main settings if there are extraneous sections" do content << "[foo]" loader_from(:filesystem => [envdir, manifestdir, modulepath].flatten, :directory => envdir) do |loader| expect(loader.get("env1")).to environment(:env1). with_manifest(manifestdir.path). with_modulepath(modulepath.map(&:path)). with_config_version(File.expand_path('/some/script')) end expect(@logs.map(&:to_s).join).to match(/Invalid.*at.*\/env1.*may not have sections.*ignored: 'foo'/) end it "logs a warning, but processes the main settings if there are any extraneous settings" do content << "dog=arf\n" content << "cat=mew\n" loader_from(:filesystem => [envdir, manifestdir, modulepath].flatten, :directory => envdir) do |loader| expect(loader.get("env1")).to environment(:env1). with_manifest(manifestdir.path). with_modulepath(modulepath.map(&:path)). with_config_version(File.expand_path('/some/script')) end expect(@logs.map(&:to_s).join).to match(/Invalid.*at.*\/env1.*unknown setting.*dog, cat/) end it "logs a warning, but processes the main settings if there are any ignored sections" do content << "dog=arf\n" content << "cat=mew\n" content << "[ignored]\n" content << "cow=moo\n" loader_from(:filesystem => [envdir, manifestdir, modulepath].flatten, :directory => envdir) do |loader| expect(loader.get("env1")).to environment(:env1). with_manifest(manifestdir.path). with_modulepath(modulepath.map(&:path)). with_config_version(File.expand_path('/some/script')) end expect(@logs.map(&:to_s).join).to match(/Invalid.*at.*\/env1.*The following sections are being ignored: 'ignored'/) expect(@logs.map(&:to_s).join).to match(/Invalid.*at.*\/env1.*unknown setting.*dog, cat/) end it "interpretes relative paths from the environment's directory" do content = <<-EOF manifest=relative/manifest modulepath=relative/modules config_version=relative/script EOF envdir = FS::MemoryFile.a_directory(File.expand_path("envdir"), [ FS::MemoryFile.a_directory("env1", [ FS::MemoryFile.a_regular_file_containing("environment.conf", content), FS::MemoryFile.a_missing_file("modules"), FS::MemoryFile.a_directory('relative', [ FS::MemoryFile.a_directory('modules'), ]), ]), ]) loader_from(:filesystem => [envdir], :directory => envdir) do |loader| expect(loader.get("env1")).to environment(:env1). with_manifest(File.join(envdir, 'env1', 'relative', 'manifest')). with_modulepath([File.join(envdir, 'env1', 'relative', 'modules')]). with_config_version(File.join(envdir, 'env1', 'relative', 'script')) end end it "interprets glob modulepaths from the environment's directory" do Dir.stubs(:glob).with(File.join(envdir, 'env1', 'other', '*', 'modules')).returns([ File.join(envdir, 'env1', 'other', 'foo', 'modules'), File.join(envdir, 'env1', 'other', 'bar', 'modules') ]) content = <<-EOF manifest=relative/manifest modulepath=relative/modules#{File::PATH_SEPARATOR}other/*/modules config_version=relative/script EOF envdir = FS::MemoryFile.a_directory(File.expand_path("envdir"), [ FS::MemoryFile.a_directory("env1", [ FS::MemoryFile.a_regular_file_containing("environment.conf", content), FS::MemoryFile.a_missing_file("modules"), FS::MemoryFile.a_directory('relative', [ FS::MemoryFile.a_directory('modules'), ]), FS::MemoryFile.a_directory('other', [ FS::MemoryFile.a_directory('foo', [ FS::MemoryFile.a_directory('modules'), ]), FS::MemoryFile.a_directory('bar', [ FS::MemoryFile.a_directory('modules'), ]), ]), ]), ]) loader_from(:filesystem => [envdir], :directory => envdir) do |loader| expect(loader.get("env1")).to environment(:env1). with_manifest(File.join(envdir, 'env1', 'relative', 'manifest')). with_modulepath([File.join(envdir, 'env1', 'relative', 'modules'), File.join(envdir, 'env1', 'other', 'foo', 'modules'), File.join(envdir, 'env1', 'other', 'bar', 'modules')]). with_config_version(File.join(envdir, 'env1', 'relative', 'script')) end end it "interpolates other setting values correctly" do modulepath = [ File.expand_path('/some/absolute'), '$basemodulepath', 'modules' ].join(File::PATH_SEPARATOR) content = <<-EOF manifest=$confdir/whackymanifests modulepath=#{modulepath} config_version=$vardir/random/scripts EOF some_absolute_dir = FS::MemoryFile.a_directory(File.expand_path('/some/absolute')) base_module_dirs = Puppet[:basemodulepath].split(File::PATH_SEPARATOR).map do |path| FS::MemoryFile.a_directory(path) end envdir = FS::MemoryFile.a_directory(File.expand_path("envdir"), [ FS::MemoryFile.a_directory("env1", [ FS::MemoryFile.a_regular_file_containing("environment.conf", content), FS::MemoryFile.a_directory("modules"), ]), ]) loader_from(:filesystem => [envdir, some_absolute_dir, base_module_dirs].flatten, :directory => envdir) do |loader| expect(loader.get("env1")).to environment(:env1). with_manifest(File.join(Puppet[:confdir], 'whackymanifests')). with_modulepath([some_absolute_dir.path, base_module_dirs.map { |d| d.path }, File.join(envdir, 'env1', 'modules')].flatten). with_config_version(File.join(Puppet[:vardir], 'random', 'scripts')) end end it "uses environment.conf settings regardless of existence of modules and manifests subdirectories" do envdir = FS::MemoryFile.a_directory(File.expand_path("envdir"), [ FS::MemoryFile.a_directory("env1", [ FS::MemoryFile.a_regular_file_containing("environment.conf", content), FS::MemoryFile.a_directory("modules"), FS::MemoryFile.a_directory("manifests"), ]), ]) loader_from(:filesystem => [envdir, manifestdir, modulepath].flatten, :directory => envdir) do |loader| expect(loader.get("env1")).to environment(:env1). with_manifest(manifestdir.path). with_modulepath(modulepath.map(&:path)). with_config_version(File.expand_path('/some/script')) end end it "should update environment settings if environment.conf has changed and timeout has expired" do base_dir = File.expand_path("envdir") original_envdir = FS::MemoryFile.a_directory(base_dir, [ FS::MemoryFile.a_directory("env3", [ FS::MemoryFile.a_regular_file_containing("environment.conf", <<-EOF) manifest=/manifest_orig modulepath=/modules_orig environment_timeout=0 EOF ]), ]) FS.overlay(original_envdir) do dir_loader = Puppet::Environments::Directories.new(original_envdir, []) loader = Puppet::Environments::Cached.new(dir_loader) Puppet.override(:environments => loader) do original_env = loader.get("env3") # force the environment.conf to be read changed_envdir = FS::MemoryFile.a_directory(base_dir, [ FS::MemoryFile.a_directory("env3", [ FS::MemoryFile.a_regular_file_containing("environment.conf", <<-EOF) manifest=/manifest_changed modulepath=/modules_changed environment_timeout=0 EOF ]), ]) FS.overlay(changed_envdir) do changed_env = loader.get("env3") expect(original_env).to environment(:env3). with_manifest(File.expand_path("/manifest_orig")). with_full_modulepath([File.expand_path("/modules_orig")]) expect(changed_env).to environment(:env3). with_manifest(File.expand_path("/manifest_changed")). with_full_modulepath([File.expand_path("/modules_changed")]) end end end end context "custom cache expiration service" do it "consults the custom service to expire the cache" do loader_from(:filesystem => [directory_tree], :directory => directory_tree) do |loader| service = ReplayExpirationService.new([true]) using_expiration_service(service) do cached = Puppet::Environments::Cached.new(loader) cached.get(:an_environment) cached.get(:an_environment) expect(service.created_envs).to include(:an_environment) expect(service.expired_envs).to include(:an_environment) expect(service.evicted_envs).to include(:an_environment) end end end end end end describe "static loaders" do let(:static1) { Puppet::Node::Environment.create(:static1, []) } let(:static2) { Puppet::Node::Environment.create(:static2, []) } let(:loader) { Puppet::Environments::Static.new(static1, static2) } it "lists environments" do expect(loader.list).to eq([static1, static2]) end it "has search_paths" do expect(loader.search_paths).to eq(["data:text/plain,internal"]) end it "gets an environment" do expect(loader.get(:static2)).to eq(static2) end it "returns nil if env not found" do expect(loader.get(:doesnotexist)).to be_nil end it "raises error if environment is not found" do expect do loader.get!(:doesnotexist) end.to raise_error(Puppet::Environments::EnvironmentNotFound) end it "gets a basic conf" do conf = loader.get_conf(:static1) expect(conf.modulepath).to eq('') expect(conf.manifest).to eq(:no_manifest) expect(conf.config_version).to be_nil expect(conf.static_catalogs).to eq(true) end it "returns nil if you request a configuration from an env that doesn't exist" do expect(loader.get_conf(:doesnotexist)).to be_nil end it "gets the conf environment_timeout if one is specified" do Puppet[:environment_timeout] = 8675 conf = loader.get_conf(:static1) expect(conf.environment_timeout).to eq(8675) end context "that are private" do let(:private_env) { Puppet::Node::Environment.create(:private, []) } let(:loader) { Puppet::Environments::StaticPrivate.new(private_env) } it "lists nothing" do expect(loader.list).to eq([]) end end end describe "combined loaders" do let(:static1) { Puppet::Node::Environment.create(:static1, []) } let(:static2) { Puppet::Node::Environment.create(:static2, []) } let(:static_loader) { Puppet::Environments::Static.new(static1, static2) } let(:directory_tree) do FS::MemoryFile.a_directory(File.expand_path("envdir"), [ FS::MemoryFile.a_directory("an_environment", [ FS::MemoryFile.a_missing_file("environment.conf"), FS::MemoryFile.a_directory("modules"), FS::MemoryFile.a_directory("manifests"), ]), FS::MemoryFile.a_missing_file("env_does_not_exist"), FS::MemoryFile.a_missing_file("static2"), ]) end it "lists environments" do loader_from(:filesystem => [directory_tree], :directory => directory_tree) do |loader| envs = Puppet::Environments::Combined.new(loader, static_loader).list expect(envs[0]).to environment(:an_environment) expect(envs[1]).to environment(:static1) expect(envs[2]).to environment(:static2) end end it "has search_paths" do loader_from(:filesystem => [directory_tree], :directory => directory_tree) do |loader| expect(Puppet::Environments::Combined.new(loader, static_loader).search_paths).to eq(["file://#{directory_tree}","data:text/plain,internal"]) end end it "gets an environment" do loader_from(:filesystem => [directory_tree], :directory => directory_tree) do |loader| expect(Puppet::Environments::Combined.new(loader, static_loader).get(:an_environment)).to environment(:an_environment) expect(Puppet::Environments::Combined.new(loader, static_loader).get(:static2)).to environment(:static2) end end it "returns nil if env not found" do loader_from(:filesystem => [directory_tree], :directory => directory_tree) do |loader| expect(Puppet::Environments::Combined.new(loader, static_loader).get(:env_does_not_exist)).to be_nil end end it "raises an error if environment is not found" do loader_from(:filesystem => [directory_tree], :directory => directory_tree) do |loader| expect do Puppet::Environments::Combined.new(loader, static_loader).get!(:env_does_not_exist) end.to raise_error(Puppet::Environments::EnvironmentNotFound) end end it "gets an environment.conf" do loader_from(:filesystem => [directory_tree], :directory => directory_tree) do |loader| expect(Puppet::Environments::Combined.new(loader, static_loader).get_conf(:an_environment)).to match_environment_conf(:an_environment). with_env_path(directory_tree). with_global_module_path([]) end end end describe "cached loaders" do it "lists environments" do loader_from(:filesystem => [directory_tree], :directory => directory_tree) do |loader| expect(Puppet::Environments::Cached.new(loader).list).to include_in_any_order( environment(:an_environment), environment(:another_environment)) end end it "has search_paths" do loader_from(:filesystem => [directory_tree], :directory => directory_tree) do |loader| expect(Puppet::Environments::Cached.new(loader).search_paths).to eq(["file://#{directory_tree}"]) end end context "#get" do it "gets an environment" do loader_from(:filesystem => [directory_tree], :directory => directory_tree) do |loader| expect(Puppet::Environments::Cached.new(loader).get(:an_environment)).to environment(:an_environment) end end it "does not reload the environment if it isn't expired" do env = Puppet::Node::Environment.create(:cached, []) mocked_loader = mock('loader') mocked_loader.expects(:get).with(:cached).returns(env).once mocked_loader.expects(:get_conf).with(:cached).returns(Puppet::Settings::EnvironmentConf.static_for(env, 20)).once cached = Puppet::Environments::Cached.new(mocked_loader) cached.get(:cached) cached.get(:cached) end it "returns nil if env not found" do loader_from(:filesystem => [directory_tree], :directory => directory_tree) do |loader| expect(Puppet::Environments::Cached.new(loader).get(:doesnotexist)).to be_nil end end end context "#get!" do it "gets an environment" do loader_from(:filesystem => [directory_tree], :directory => directory_tree) do |loader| expect(Puppet::Environments::Cached.new(loader).get!(:an_environment)).to environment(:an_environment) end end it "does not reload the environment if it isn't expired" do env = Puppet::Node::Environment.create(:cached, []) mocked_loader = mock('loader') mocked_loader.expects(:get).with(:cached).returns(env).once mocked_loader.expects(:get_conf).with(:cached).returns(Puppet::Settings::EnvironmentConf.static_for(env, 20)).once cached = Puppet::Environments::Cached.new(mocked_loader) cached.get!(:cached) cached.get!(:cached) end it "raises error if environment is not found" do loader_from(:filesystem => [directory_tree], :directory => directory_tree) do |loader| expect do Puppet::Environments::Cached.new(loader).get!(:doesnotexist) end.to raise_error(Puppet::Environments::EnvironmentNotFound) end end end it "gets an environment.conf" do loader_from(:filesystem => [directory_tree], :directory => directory_tree) do |loader| expect(Puppet::Environments::Cached.new(loader).get_conf(:an_environment)).to match_environment_conf(:an_environment). with_env_path(directory_tree). with_global_module_path([]) end end end RSpec::Matchers.define :environment do |name| match do |env| env.name == name && (!@manifest || @manifest == env.manifest) && (!@modulepath || @modulepath == env.modulepath) && (!@full_modulepath || @full_modulepath == env.full_modulepath) && (!@config_version || @config_version == env.config_version) && (!@static_catalogs || @static_catalogs == env.static_catalogs?) end chain :with_manifest do |manifest| @manifest = manifest end chain :with_modulepath do |modulepath| @modulepath = modulepath end chain :with_full_modulepath do |full_modulepath| @full_modulepath = full_modulepath end chain :with_config_version do |config_version| @config_version = config_version end chain :with_static_catalogs do |static_catalogs| @static_catalogs = static_catalogs end description do "environment #{expected}" + (@manifest ? " with manifest #{@manifest}" : "") + (@modulepath ? " with modulepath [#{@modulepath.join(', ')}]" : "") + (@full_modulepath ? " with full_modulepath [#{@full_modulepath.join(', ')}]" : "") + (@config_version ? " with config_version #{@config_version}" : "") + (@static_catalogs ? " with static_catalogs #{@static_catalogs}" : "") end failure_message do |env| "expected <#{env.name}: modulepath = [#{env.full_modulepath.join(', ')}], manifest = #{env.manifest}, config_version = #{env.config_version}>, static_catalogs = #{env.static_catalogs?} to be #{description}" end end RSpec::Matchers.define :match_environment_conf do |env_name| match do |env_conf| env_conf.path_to_env =~ /#{env_name}$/ && (!@env_path || File.join(@env_path,env_name.to_s) == env_conf.path_to_env) && (!@global_modulepath || @global_module_path == env_conf.global_module_path) end chain :with_env_path do |env_path| @env_path = env_path.to_s end chain :with_global_module_path do |global_module_path| @global_module_path = global_module_path end description do "EnvironmentConf #{expected}" + " with path_to_env: #{@env_path ? @env_path : "*"}/#{env_name}" + (@global_module_path ? " with global_module_path [#{@global_module_path.join(', ')}]" : "") end failure_message do |env_conf| "expected #{env_conf.inspect} to be #{description}" end end def loader_from(options, &block) FS.overlay(*options[:filesystem]) do environments = Puppet::Environments::Directories.new( options[:directory], options[:modulepath] || [] ) Puppet.override(:environments => environments) do yield environments end end end def using_expiration_service(service) begin orig_svc = Puppet::Environments::Cached.cache_expiration_service Puppet::Environments::Cached.cache_expiration_service = service yield ensure Puppet::Environments::Cached.cache_expiration_service = orig_svc end end class ReplayExpirationService attr_reader :created_envs, :expired_envs, :evicted_envs def initialize(expiration_sequence) @created_envs = [] @expired_envs = [] @evicted_envs = [] @expiration_sequence = expiration_sequence end def created(env) @created_envs << env.name end def expired?(env_name) # make expired? idempotent return true if @expired_envs.include? (env_name) @expired_envs << env_name @expiration_sequence.pop end def evicted(env_name) @evicted_envs << env_name end end end end puppet-5.5.10/spec/unit/external/0000755005276200011600000000000013417162176016552 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/external/pson_spec.rb0000644005276200011600000000316413417161721021067 0ustar jenkinsjenkins#! /usr/bin/env ruby # Encoding: UTF-8 require 'spec_helper' require 'puppet/external/pson/common' describe PSON do { 'foo' => '"foo"', 1 => '1', "\x80" => "\"\x80\"", [] => '[]' }.each do |str, expect| it "should be able to encode #{str.inspect}" do got = str.to_pson if got.respond_to? :force_encoding expect(got.force_encoding('binary')).to eq(expect.force_encoding('binary')) else expect(got).to eq(expect) end end end it "should be able to handle arbitrary binary data" do bin_string = (1..20000).collect { |i| ((17*i+13*i*i) % 255).chr }.join parsed = PSON.parse(%Q{{ "type": "foo", "data": #{bin_string.to_pson} }})["data"] if parsed.respond_to? :force_encoding parsed.force_encoding('binary') bin_string.force_encoding('binary') end expect(parsed).to eq(bin_string) end it "should be able to handle UTF8 that isn't a real unicode character" do s = ["\355\274\267"] expect(PSON.parse( [s].to_pson )).to eq([s]) end it "should be able to handle UTF8 for \\xFF" do s = ["\xc3\xbf"] expect(PSON.parse( [s].to_pson )).to eq([s]) end it "should be able to handle invalid UTF8 bytes" do s = ["\xc3\xc3"] expect(PSON.parse( [s].to_pson )).to eq([s]) end it "should be able to parse JSON containing UTF-8 characters in strings" do s = '{ "foö": "bár" }' expect { PSON.parse s }.not_to raise_error end it 'ignores "document_type" during parsing' do text = '{"data":{},"document_type":"Node"}' expect(PSON.parse(text)).to eq({"data" => {}, "document_type" => "Node"}) end end puppet-5.5.10/spec/unit/face/0000755005276200011600000000000013417162176015626 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/face/catalog_spec.rb0000644005276200011600000000023613417161721020573 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/face' describe Puppet::Face[:catalog, '0.0.1'] do it "should actually have some testing..." end puppet-5.5.10/spec/unit/face/config_spec.rb0000644005276200011600000003120013417161721020421 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/face' module PuppetFaceSpecs describe Puppet::Face[:config, '0.0.1'] do let(:config) { described_class } def render(action, result) config.get_action(action).when_rendering(:console).call(result) end FS = Puppet::FileSystem it "prints a single setting without the name" do Puppet[:trace] = true result = subject.print("trace") expect(render(:print, result)).to eq("true\n") end it "prints multiple settings with the names" do Puppet[:trace] = true Puppet[:syslogfacility] = "file" result = subject.print("trace", "syslogfacility") expect(render(:print, result)).to eq(<<-OUTPUT) syslogfacility = file trace = true OUTPUT end it "prints environment_timeout=unlimited correctly" do Puppet[:environment_timeout] = "unlimited" result = subject.print("environment_timeout") expect(render(:print, result)).to eq("unlimited\n") end it "prints arrays correctly" do pending "Still doesn't print arrays like they would appear in config" Puppet[:server_list] = %w{server1 server2} result = subject.print("server_list") expect(render(:print, result)).to eq("server1, server2\n") end it "prints the setting from the selected section" do Puppet.settings.parse_config(<<-CONF) [user] syslogfacility = file CONF result = subject.print("syslogfacility", :section => "user") expect(render(:print, result)).to eq("file\n") end it "prints the section and environment, and not a warning, when a section is given and verbose is set" do Puppet.settings.parse_config(<<-CONF) [user] syslogfacility = file CONF #This has to be after the settings above, which resets the value Puppet[:log_level] = 'info' Puppet.expects(:warning).never expect { result = subject.print("syslogfacility", :section => "user") expect(render(:print, result)).to eq("file\n") }.to output("\e[1;33mResolving settings from section 'user' in environment 'production'\e[0m\n").to_stderr end it "prints a warning and the section and environment when no section is given and verbose is set" do Puppet[:log_level] = 'info' Puppet[:trace] = true Puppet.expects(:warning).with("No section specified; defaulting to 'main'.\nSet the config section " + "by using the `--section` flag.\nFor example, `puppet config --section user print foo`.\nFor more " + "information, see https://puppet.com/docs/puppet/latest/configuration.html") expect { result = subject.print("trace") expect(render(:print, result)).to eq("true\n") }.to output("\e[1;33mResolving settings from section 'main' in environment 'production'\e[0m\n").to_stderr end it "does not print a warning or the section and environment when no section is given and verbose is not set" do Puppet[:log_level] = 'notice' Puppet[:trace] = true Puppet.expects(:warning).never expect { result = subject.print("trace") expect(render(:print, result)).to eq("true\n") }.to_not output.to_stderr end it "defaults to all when no arguments are given" do result = subject.print expect(render(:print, result).lines.to_a.length).to eq(Puppet.settings.to_a.length) end it "prints out all of the settings when asked for 'all'" do result = subject.print('all') expect(render(:print, result).lines.to_a.length).to eq(Puppet.settings.to_a.length) end it "stringifies all keys for network format handlers to consume" do Puppet[:syslogfacility] = "file" result = subject.print expect(result["syslogfacility"]).to eq("file") expect(result.keys).to all(be_a(String)) end it "stringifies multiple keys for network format handlers to consume" do Puppet[:trace] = true Puppet[:syslogfacility] = "file" expect(subject.print("trace", "syslogfacility")).to eq({"syslogfacility" => "file", "trace" => true}) end it "stringifies single key for network format handlers to consume" do Puppet[:trace] = true expect(subject.print("trace")).to eq({"trace" => true}) end context "when setting config values" do let(:config_file) { '/foo/puppet.conf' } let(:path) { Pathname.new(config_file).expand_path } before(:each) do Puppet[:config] = config_file Puppet::FileSystem.stubs(:pathname).with(path.to_s).returns(path) Puppet::FileSystem.stubs(:touch) end it "prints the section and environment when no section is given and verbose is set" do Puppet[:log_level] = 'info' Puppet::FileSystem.stubs(:open).with(path, anything, anything).yields(StringIO.new) expect { subject.set('foo', 'bar') }.to output("\e[1;33mResolving settings from section 'main' in environment 'production'\e[0m\n").to_stderr end it "prints the section and environment when a section is given and verbose is set" do Puppet[:log_level] = 'info' Puppet::FileSystem.stubs(:open).with(path, anything, anything).yields(StringIO.new) expect { subject.set('foo', 'bar', {:section => "baz"}) }.to output("\e[1;33mResolving settings from section 'baz' in environment 'production'\e[0m\n").to_stderr end it "writes to the correct puppet config file" do Puppet::FileSystem.expects(:open).with(path, anything, anything) subject.set('foo', 'bar') end it "creates a config file if one does not exist" do Puppet::FileSystem.stubs(:open).with(path, anything, anything).yields(StringIO.new) Puppet::FileSystem.expects(:touch).with(path) subject.set('foo', 'bar') end it "sets the supplied config/value in the default section (main)" do Puppet::FileSystem.stubs(:open).with(path, anything, anything).yields(StringIO.new) config = Puppet::Settings::IniFile.new([Puppet::Settings::IniFile::DefaultSection.new]) manipulator = Puppet::Settings::IniFile::Manipulator.new(config) Puppet::Settings::IniFile::Manipulator.stubs(:new).returns(manipulator) manipulator.expects(:set).with("main", "foo", "bar") subject.set('foo', 'bar') end it "sets the value in the supplied section" do Puppet::FileSystem.stubs(:open).with(path, anything, anything).yields(StringIO.new) config = Puppet::Settings::IniFile.new([Puppet::Settings::IniFile::DefaultSection.new]) manipulator = Puppet::Settings::IniFile::Manipulator.new(config) Puppet::Settings::IniFile::Manipulator.stubs(:new).returns(manipulator) manipulator.expects(:set).with("baz", "foo", "bar") subject.set('foo', 'bar', {:section => "baz"}) end it "does not duplicate an existing default section when a section is not specified" do contents = <<-CONF [main] myport = 4444 CONF myfile = StringIO.new(contents) Puppet::FileSystem.stubs(:open).with(path, anything, anything).yields(myfile) subject.set('foo', 'bar') expect(myfile.string).to match(/foo = bar/) expect(myfile.string).not_to match(/main.*main/) end it "opens the file with UTF-8 encoding" do Puppet::FileSystem.expects(:open).with(path, nil, 'r+:UTF-8') subject.set('foo', 'bar') end end context 'when the puppet.conf file does not exist' do let(:config_file) { '/foo/puppet.conf' } let(:path) { Pathname.new(config_file).expand_path } before(:each) do Puppet[:config] = config_file Puppet::FileSystem.stubs(:pathname).with(path.to_s).returns(path) end it 'prints a message when the puppet.conf file does not exist' do Puppet::FileSystem.stubs(:exist?).with(path).returns(false) Puppet.expects(:warning).with("The puppet.conf file does not exist #{path.to_s}") subject.delete('setting', {:section => 'main'}) end end context 'when deleting config values' do let(:config_file) { '/foo/puppet.conf' } let(:path) { Pathname.new(config_file).expand_path } before(:each) do Puppet[:config] = config_file Puppet::FileSystem.stubs(:pathname).with(path.to_s).returns(path) Puppet::FileSystem.stubs(:exist?).with(path).returns(true) end it 'prints a message about what was deleted' do Puppet::FileSystem.stubs(:open).with(path, anything, anything).yields(StringIO.new) config = Puppet::Settings::IniFile.new([Puppet::Settings::IniFile::DefaultSection.new]) manipulator = Puppet::Settings::IniFile::Manipulator.new(config) Puppet::Settings::IniFile::Manipulator.stubs(:new).returns(manipulator) manipulator.expects(:delete).with('main', 'setting').returns(' setting=value') expect { subject.delete('setting', {:section => 'main'}) }.to have_printed("Deleted setting from 'main': 'setting=value'") end it 'prints a warning when a setting is not found to delete' do Puppet::FileSystem.stubs(:open).with(path, anything, anything).yields(StringIO.new) config = Puppet::Settings::IniFile.new([Puppet::Settings::IniFile::DefaultSection.new]) manipulator = Puppet::Settings::IniFile::Manipulator.new(config) Puppet::Settings::IniFile::Manipulator.stubs(:new).returns(manipulator) manipulator.expects(:delete).with('main', 'setting').returns(nil) Puppet.expects(:warning).with("No setting found in configuration file for section 'main' setting name 'setting'") subject.delete('setting', {:section => 'main'}) end end shared_examples_for :config_printing_a_section do |section| def add_section_option(args, section) args << { :section => section } if section args end it "prints directory env settings for an env that exists" do FS.overlay( FS::MemoryFile.a_directory(File.expand_path("/dev/null/environments"), [ FS::MemoryFile.a_directory("production", [ FS::MemoryFile.a_missing_file("environment.conf"), ]), ]) ) do args = "environmentpath","manifest","modulepath","environment","basemodulepath" result = subject.print(*add_section_option(args, section)) expect(render(:print, result)).to eq(<<-OUTPUT) basemodulepath = #{File.expand_path("/some/base")} environment = production environmentpath = #{File.expand_path("/dev/null/environments")} manifest = #{File.expand_path("/dev/null/environments/production/manifests")} modulepath = #{File.expand_path("/dev/null/environments/production/modules")}#{File::PATH_SEPARATOR}#{File.expand_path("/some/base")} OUTPUT end end it "interpolates settings in environment.conf" do FS.overlay( FS::MemoryFile.a_directory(File.expand_path("/dev/null/environments"), [ FS::MemoryFile.a_directory("production", [ FS::MemoryFile.a_regular_file_containing("environment.conf", <<-CONTENT), modulepath=/custom/modules#{File::PATH_SEPARATOR}$basemodulepath CONTENT ]), ]) ) do args = "environmentpath","manifest","modulepath","environment","basemodulepath" result = subject.print(*add_section_option(args, section)) expect(render(:print, result)).to eq(<<-OUTPUT) basemodulepath = #{File.expand_path("/some/base")} environment = production environmentpath = #{File.expand_path("/dev/null/environments")} manifest = #{File.expand_path("/dev/null/environments/production/manifests")} modulepath = #{File.expand_path("/custom/modules")}#{File::PATH_SEPARATOR}#{File.expand_path("/some/base")} OUTPUT end end it "prints the default configured env settings for an env that does not exist" do Puppet[:environment] = 'doesnotexist' FS.overlay( FS::MemoryFile.a_directory(File.expand_path("/dev/null/environments"), [ FS::MemoryFile.a_missing_file("doesnotexist") ]) ) do args = "environmentpath","manifest","modulepath","environment","basemodulepath" result = subject.print(*add_section_option(args, section)) expect(render(:print, result)).to eq(<<-OUTPUT) basemodulepath = #{File.expand_path("/some/base")} environment = doesnotexist environmentpath = #{File.expand_path("/dev/null/environments")} manifest = modulepath = OUTPUT end end end context "when printing environment settings" do context "from main section" do before(:each) do Puppet.settings.parse_config(<<-CONF) [main] environmentpath=$confdir/environments basemodulepath=/some/base CONF end it_behaves_like :config_printing_a_section, nil end context "from master section" do before(:each) do Puppet.settings.parse_config(<<-CONF) [master] environmentpath=$confdir/environments basemodulepath=/some/base CONF end it_behaves_like :config_printing_a_section, :master end end end end puppet-5.5.10/spec/unit/face/epp_face_spec.rb0000644005276200011600000003536413417161721020735 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/files' require 'puppet/face' describe Puppet::Face[:epp, :current] do include PuppetSpec::Files let(:eppface) { Puppet::Face[:epp, :current] } context "validate" do context "from an interactive terminal" do before :each do from_an_interactive_terminal end it "validates the template referenced as an absolute file" do template_name = 'template1.epp' dir = dir_containing('templates', { template_name => "<%= |$a $b |%>" }) template = File.join(dir, template_name) expect { eppface.validate(template) }.to raise_exception(Puppet::Error, /Errors while validating epp/) end it "runs error free when there are no validation errors from an absolute file" do template_name = 'template1.epp' dir = dir_containing('templates', { template_name => "just text" }) template = File.join(dir, template_name) expect { eppface.validate(template) }.to_not raise_exception() end it "reports missing files" do expect do eppface.validate("missing.epp") end.to raise_error(Puppet::Error, /One or more file\(s\) specified did not exist.*missing\.epp/m) end context "in an environment with templates" do let(:dir) do dir_containing('environments', { 'production' => { 'modules' => { 'm1' => { 'templates' => { 'greetings.epp' => "<% |$subject = world| %>hello <%= $subject -%>", 'broken.epp' => "<% | $a $b | %> I am broken", 'broken2.epp' => "<% | $a $b | %> I am broken too" }}, 'm2' => { 'templates' => { 'goodbye.epp' => "<% | $subject = world |%>goodbye <%= $subject -%>", 'broken3.epp' => "<% | $a $b | %> I am broken too" }} }}}) end around(:each) do |example| Puppet.settings.initialize_global_settings loader = Puppet::Environments::Directories.new(dir, []) Puppet.override(:environments => loader) do example.run end end it "parses supplied template files in different modules of a directory environment" do expect(eppface.validate('m1/greetings.epp')).to be_nil expect(eppface.validate('m2/goodbye.epp')).to be_nil end it "finds errors in supplied template file in the context of a directory environment" do expect { eppface.validate('m1/broken.epp') }.to raise_exception(Puppet::Error, /Errors while validating epp/) expect(@logs.join).to match(/Syntax error at 'b'/) end it "stops on first error by default" do expect { eppface.validate('m1/broken.epp', 'm1/broken2.epp') }.to raise_exception(Puppet::Error, /Errors while validating epp/) expect(@logs.join).to match(/Syntax error at 'b'.*broken\.epp/) expect(@logs.join).to_not match(/Syntax error at 'b'.*broken2\.epp/) end it "continues after error when --continue_on_error is given" do expect { eppface.validate('m1/broken.epp', 'm1/broken2.epp', :continue_on_error => true) }.to raise_exception(Puppet::Error, /Errors while validating epp/) expect(@logs.join).to match(/Syntax error at 'b'.*broken\.epp/) expect(@logs.join).to match(/Syntax error at 'b'.*broken2\.epp/) end it "validates all templates in the environment" do pending "NOT IMPLEMENTED YET" expect { eppface.validate(:continue_on_error => true) }.to raise_exception(Puppet::Error, /Errors while validating epp/) expect(@logs.join).to match(/Syntax error at 'b'.*broken\.epp/) expect(@logs.join).to match(/Syntax error at 'b'.*broken2\.epp/) expect(@logs.join).to match(/Syntax error at 'b'.*broken3\.epp/) end end end it "validates the contents of STDIN when no files given and STDIN is not a tty" do from_a_piped_input_of("<% | $a $oh_no | %> I am broken") expect { eppface.validate() }.to raise_exception(Puppet::Error, /Errors while validating epp/) expect(@logs.join).to match(/Syntax error at 'oh_no'/) end it "validates error free contents of STDIN when no files given and STDIN is not a tty" do from_a_piped_input_of("look, just text") expect(eppface.validate()).to be_nil end end context "dump" do it "prints the AST of a template given with the -e option" do expect(eppface.dump({ :e => 'hello world' })).to eq("(lambda (epp (block\n (render-s 'hello world')\n)))\n") end it "prints the AST of a template given as an absolute file" do template_name = 'template1.epp' dir = dir_containing('templates', { template_name => "hello world" }) template = File.join(dir, template_name) expect(eppface.dump(template)).to eq("(lambda (epp (block\n (render-s 'hello world')\n)))\n") end it "adds a header between dumps by default" do template_name1 = 'template1.epp' template_name2 = 'template2.epp' dir = dir_containing('templates', { template_name1 => "hello world", template_name2 => "hello again"} ) template1 = File.join(dir, template_name1) template2 = File.join(dir, template_name2) # Do not move the text block, the left margin and indentation matters expect(eppface.dump(template1, template2)).to eq( <<-"EOT" ) --- #{template1} (lambda (epp (block (render-s 'hello world') ))) --- #{template2} (lambda (epp (block (render-s 'hello again') ))) EOT end it "dumps non validated content when given --no-validate" do template_name = 'template1.epp' dir = dir_containing('templates', { template_name => "<% 1 2 3 %>" }) template = File.join(dir, template_name) expect(eppface.dump(template, :validate => false)).to eq("(lambda (epp (block\n 1\n 2\n 3\n)))\n") end it "validated content when given --validate" do template_name = 'template1.epp' dir = dir_containing('templates', { template_name => "<% 1 2 3 %>" }) template = File.join(dir, template_name) expect(eppface.dump(template, :validate => true)).to eq("") expect(@logs.join).to match(/This Literal Integer has no effect.*\(file: .*\/template1\.epp, line: 1, column: 4\)/) end it "validated content by default" do template_name = 'template1.epp' dir = dir_containing('templates', { template_name => "<% 1 2 3 %>" }) template = File.join(dir, template_name) expect(eppface.dump(template)).to eq("") expect(@logs.join).to match(/This Literal Integer has no effect.*\(file: .*\/template1\.epp, line: 1, column: 4\)/) end it "informs the user of files that don't exist" do expected_message = /One or more file\(s\) specified did not exist:\n\s*does_not_exist_here\.epp/m expect { eppface.dump('does_not_exist_here.epp') }.to raise_exception(Puppet::Error, expected_message) end it "dumps the AST of STDIN when no files given and STDIN is not a tty" do from_a_piped_input_of("hello world") expect(eppface.dump()).to eq("(lambda (epp (block\n (render-s 'hello world')\n)))\n") end it "logs an error if the input cannot be parsed even if validation is off" do from_a_piped_input_of("<% |$a $b| %> oh no") expect(eppface.dump(:validate => false)).to eq("") expect(@logs[0].message).to match(/Syntax error at 'b'/) expect(@logs[0].level).to eq(:err) end context "using 'pn' format" do it "prints the AST of the given expression in PN format" do expect(eppface.dump({ :format => 'pn', :e => 'hello world' })).to eq( '(lambda {:body [(epp (render-s "hello world"))]})') end it "pretty prints the AST of the given expression in PN format when --pretty is given" do expect(eppface.dump({ :pretty => true, :format => 'pn', :e => 'hello world' })).to eq(<<-RESULT.unindent[0..-2]) (lambda { :body [ (epp (render-s "hello world"))]}) RESULT end end context "using 'json' format" do it "prints the AST of the given expression in JSON based on the PN format" do expect(eppface.dump({ :format => 'json', :e => 'hello world' })).to eq( '{"^":["lambda",{"#":["body",[{"^":["epp",{"^":["render-s","hello world"]}]}]]}]}') end it "pretty prints the AST of the given expression in JSON based on the PN format when --pretty is given" do expect(eppface.dump({ :pretty => true, :format => 'json', :e => 'hello world' })).to eq(<<-RESULT.unindent[0..-2]) { "^": [ "lambda", { "#": [ "body", [ { "^": [ "epp", { "^": [ "render-s", "hello world" ] } ] } ] ] } ] } RESULT end end end context "render" do it "renders input from stdin" do from_a_piped_input_of("hello world") expect(eppface.render()).to eq("hello world") end it "renders input from command line" do expect(eppface.render(:e => 'hello world')).to eq("hello world") end it "renders input from an absolute file" do template_name = 'template1.epp' dir = dir_containing('templates', { template_name => "absolute world" }) template = File.join(dir, template_name) expect(eppface.render(template)).to eq("absolute world") end it "renders expressions" do expect(eppface.render(:e => '<% $x = "mr X"%>hello <%= $x %>')).to eq("hello mr X") end it "adds values given in a puppet hash given on command line with --values" do expect(eppface.render(:e => 'hello <%= $x %>', :values => '{x => "mr X"}')).to eq("hello mr X") end it "adds fully qualified values given in a puppet hash given on command line with --values" do expect(eppface.render(:e => 'hello <%= $mr::x %>', :values => '{mr::x => "mr X"}')).to eq("hello mr X") end it "adds fully qualified values with leading :: given in a puppet hash given on command line with --values" do expect(eppface.render(:e => 'hello <%= $::mr %>', :values => '{"::mr" => "mr X"}')).to eq("hello mr X") end it "adds values given in a puppet hash produced by a .pp file given with --values_file" do file_name = 'values.pp' dir = dir_containing('values', { file_name => '{x => "mr X"}' }) values_file = File.join(dir, file_name) expect(eppface.render(:e => 'hello <%= $x %>', :values_file => values_file)).to eq("hello mr X") end it "adds values given in a yaml hash given with --values_file" do file_name = 'values.yaml' dir = dir_containing('values', { file_name => "---\n x: 'mr X'" }) values_file = File.join(dir, file_name) expect(eppface.render(:e => 'hello <%= $x %>', :values_file => values_file)).to eq("hello mr X") end it "merges values from values file and command line with command line having higher precedence" do file_name = 'values.yaml' dir = dir_containing('values', { file_name => "---\n x: 'mr X'\n word: 'goodbye'" }) values_file = File.join(dir, file_name) expect(eppface.render(:e => '<%= $word %> <%= $x %>', :values_file => values_file, :values => '{x => "mr Y"}') ).to eq("goodbye mr Y") end it "sets $facts" do expect(eppface.render({ :e => 'facts is hash: <%= $facts =~ Hash %>' })).to eql("facts is hash: true") end it "sets $trusted" do expect(eppface.render({ :e => 'trusted is hash: <%= $trusted =~ Hash %>' })).to eql("trusted is hash: true") end it 'initializes the 4x loader' do expect(eppface.render({ :e => <<-EPP.unindent })).to eql("\nString\n\nInteger\n\nBoolean\n") <% $data = [type('a',generalized), type(2,generalized), type(true,generalized)] -%> <% $data.each |$value| { %> <%= $value %> <% } -%> EPP end it "facts can be added to" do expect(eppface.render({ :facts => {'the_crux' => 'biscuit'}, :e => '<%= $facts[the_crux] %>', })).to eql("biscuit") end it "facts can be overridden" do expect(eppface.render({ :facts => {'operatingsystem' => 'Merwin'}, :e => '<%= $facts[operatingsystem] %>', })).to eql("Merwin") end context "in an environment with templates" do let(:dir) do dir_containing('environments', { 'production' => { 'modules' => { 'm1' => { 'templates' => { 'greetings.epp' => "<% |$subject = world| %>hello <%= $subject -%>", 'factshash.epp' => "fact = <%= $facts[the_fact] -%>", 'fact.epp' => "fact = <%= $the_fact -%>", }}, 'm2' => { 'templates' => { 'goodbye.epp' => "<% | $subject = world |%>goodbye <%= $subject -%>", }} }, 'extra' => { 'facts.yaml' => "---\n the_fact: 42" } }}) end around(:each) do |example| Puppet.settings.initialize_global_settings loader = Puppet::Environments::Directories.new(dir, []) Puppet.override(:environments => loader) do example.run end end it "renders supplied template files in different modules of a directory environment" do expect(eppface.render('m1/greetings.epp')).to eq("hello world") expect(eppface.render('m2/goodbye.epp')).to eq("goodbye world") end it "makes facts available in $facts" do facts_file = File.join(dir, 'production', 'extra', 'facts.yaml') expect(eppface.render('m1/factshash.epp', :facts => facts_file)).to eq("fact = 42") end it "makes facts available individually" do facts_file = File.join(dir, 'production', 'extra', 'facts.yaml') expect(eppface.render('m1/fact.epp', :facts => facts_file)).to eq("fact = 42") end it "renders multiple files separated by headers by default" do # chomp the last newline, it is put there by heredoc expect(eppface.render('m1/greetings.epp', 'm2/goodbye.epp')).to eq(<<-EOT.chomp) --- m1/greetings.epp hello world --- m2/goodbye.epp goodbye world EOT end it "outputs multiple files verbatim when --no-headers is given" do expect(eppface.render('m1/greetings.epp', 'm2/goodbye.epp', :header => false)).to eq("hello worldgoodbye world") end end end def from_an_interactive_terminal STDIN.stubs(:tty?).returns(true) end def from_a_piped_input_of(contents) STDIN.stubs(:tty?).returns(false) STDIN.stubs(:read).returns(contents) end end puppet-5.5.10/spec/unit/face/facts_spec.rb0000644005276200011600000000355013417161721020263 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/face' require 'puppet/indirector/facts/facter' require 'puppet/indirector/facts/rest' describe Puppet::Face[:facts, '0.0.1'] do describe "#find" do it { is_expected.to be_action :find } end describe '#upload' do let(:model) { Puppet::Node::Facts } let(:test_data) { model.new('puppet.node.test', {test_fact: 'test value'}) } let(:facter_terminus) { model.indirection.terminus(:facter) } let(:rest_terminus) { model.indirection.terminus(:rest) } before(:each) do Puppet.settings.parse_config(<<-CONF) [main] server=puppet.server.invalid certname=puppet.node.invalid [agent] server=puppet.server.test node_name_value=puppet.node.test CONF # Faces start in :user run mode Puppet.settings.preferred_run_mode = :user facter_terminus.stubs(:find).with(instance_of(Puppet::Indirector::Request)).returns(test_data) rest_terminus.stubs(:save).with(instance_of(Puppet::Indirector::Request)).returns(nil) end it { is_expected.to be_action :upload } it "finds facts from terminus_class :facter" do facter_terminus.expects(:find).with(instance_of(Puppet::Indirector::Request)).returns(test_data) subject.upload end it "saves facts to terminus_class :rest" do rest_terminus.expects(:save).with(instance_of(Puppet::Indirector::Request)).returns(nil) subject.upload end it "uses settings from the agent section of puppet.conf" do facter_terminus.expects(:find).with(responds_with(:key, 'puppet.node.test')).returns(test_data) subject.upload end it "logs the name of the server that received the upload" do subject.upload expect(@logs).to be_any {|log| log.level == :notice && log.message =~ /Uploading facts for '.*' to: 'puppet\.server\.test'/} end end end puppet-5.5.10/spec/unit/face/generate_spec.rb0000644005276200011600000002143713417161721020761 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/files' require 'puppet/face' describe Puppet::Face[:generate, :current] do include PuppetSpec::Files let(:genface) { Puppet::Face[:generate, :current] } # * Format is 'pcore' by default # * Format is accepted as 'pcore' # * Any format expect 'pcore' is an error # * Produces output to '/.resource_types' # * Produces all types found on the module path (that are not in puppet core) # * Output files match input # * Removes files for which there is no input # * Updates a pcore file if it is out of date # * The --force flag overwrite the output even if it is up to date # * Environment is set with --environment (setting) (not tested explicitly) # Writes output for: # - isomorphic # - parameters # - properties # - title patterns # - type information is written when the type is X, Y, or Z # # Additional features # - blacklist? whitelist? types to exclude/include # - generate one resource type (somewhere on modulepath) # - output to directory of choice # - clean, clean the output directory (similar to force) # [:types].each do |action| it { is_expected.to be_action(action) } it { is_expected.to respond_to(action) } end context "when used from an interactive terminal" do before :each do from_an_interactive_terminal end context "in an environment with two modules containing resource types" do let(:dir) do dir_containing('environments', { 'testing_generate' => { 'environment.conf' => "modulepath = modules", 'manifests' => { 'site.pp' => "" }, 'modules' => { 'm1' => { 'lib' => { 'puppet' => { 'type' => { 'test1.rb' => <<-EOF module Puppet Type.newtype(:test1) do @doc = "Docs for resource" newproperty(:message) do desc "Docs for 'message' property" end newparam(:name) do desc "Docs for 'name' parameter" isnamevar end end; end EOF } } }, }, 'm2' => { 'lib' => { 'puppet' => { 'type' => { 'test2.rb' => <<-EOF module Puppet Type.newtype(:test2) do @doc = "Docs for resource" newproperty(:message) do desc "Docs for 'message' property" end newparam(:name) do desc "Docs for 'name' parameter" isnamevar end end;end EOF } } }, } }}}) end let(:modulepath) do File.join(dir, 'testing_generate', 'modules') end let(:m1) do File.join(modulepath, 'm1') end let(:m2) do File.join(modulepath, 'm2') end let(:outputdir) do File.join(dir, 'testing_generate', '.resource_types') end around(:each) do |example| Puppet.settings.initialize_global_settings Puppet[:manifest] = '' loader = Puppet::Environments::Directories.new(dir, []) Puppet.override(:environments => loader) do Puppet.override(:current_environment => loader.get('testing_generate')) do example.run end end end it 'error if format is given as something other than pcore' do expect { genface.types(:format => 'json') }.to raise_exception(ArgumentError, /'json' is not a supported format for type generation/) end it 'accepts --format pcore as a format' do expect { genface.types(:format => 'pcore') }.not_to raise_error end it 'sets pcore as the default format' do Puppet::Generate::Type.expects(:find_inputs).with(:pcore).returns([]) genface.types() end it 'finds all files to generate types for' do # using expects and returning what the side effect should have been # (There is no way to call the original when mocking expected parameters). input1 = Puppet::Generate::Type::Input.new(m1, File.join(m1, 'lib', 'puppet', 'type', 'test1.rb'), :pcore) input2 = Puppet::Generate::Type::Input.new(m1, File.join(m2, 'lib', 'puppet', 'type', 'test2.rb'), :pcore) Puppet::Generate::Type::Input.expects(:new).with(m1, File.join(m1, 'lib', 'puppet', 'type', 'test1.rb'), :pcore).returns(input1) Puppet::Generate::Type::Input.expects(:new).with(m2, File.join(m2, 'lib', 'puppet', 'type', 'test2.rb'), :pcore).returns(input2) genface.types end it 'creates output directory /.resource_types/ if it does not exist' do expect(Puppet::FileSystem.exist?(outputdir)).to be(false) genface.types expect(Puppet::FileSystem.dir_exist?(outputdir)).to be(true) end it 'creates output with matching names for each input' do expect(Puppet::FileSystem.exist?(outputdir)).to be(false) genface.types children = Puppet::FileSystem.children(outputdir).map {|p| Puppet::FileSystem.basename_string(p) } expect(children.sort).to eql(['test1.pp', 'test2.pp']) end it 'tolerates that /.resource_types/ directory exists' do Puppet::FileSystem.mkpath(outputdir) expect(Puppet::FileSystem.exist?(outputdir)).to be(true) genface.types expect(Puppet::FileSystem.dir_exist?(outputdir)).to be(true) end it 'errors if /.resource_types exists and is not a directory' do expect(Puppet::FileSystem.exist?(outputdir)).to be(false) # assert it is not already there Puppet::FileSystem.touch(outputdir) expect(Puppet::FileSystem.exist?(outputdir)).to be(true) expect(Puppet::FileSystem.directory?(outputdir)).to be(false) expect { genface.types }.to raise_error(ArgumentError, /The output directory '#{outputdir}' exists and is not a directory/) end it 'does not overwrite if files exists and are up to date' do # create them (first run) genface.types stats_before = [Puppet::FileSystem.stat(File.join(outputdir, 'test1.pp')), Puppet::FileSystem.stat(File.join(outputdir, 'test2.pp'))] # generate again genface.types stats_after = [Puppet::FileSystem.stat(File.join(outputdir, 'test1.pp')), Puppet::FileSystem.stat(File.join(outputdir, 'test2.pp'))] expect(stats_before <=> stats_after).to be(0) end it 'overwrites if files exists that are not up to date while keeping up to date files' do # create them (first run) genface.types stats_before = [Puppet::FileSystem.stat(File.join(outputdir, 'test1.pp')), Puppet::FileSystem.stat(File.join(outputdir, 'test2.pp'))] # fake change in input test1 - sorry about the sleep (which there was a better way to change the modtime sleep(1) Puppet::FileSystem.touch(File.join(m1, 'lib', 'puppet', 'type', 'test1.rb')) # generate again genface.types # assert that test1 was overwritten (later) but not test2 (same time) stats_after = [Puppet::FileSystem.stat(File.join(outputdir, 'test1.pp')), Puppet::FileSystem.stat(File.join(outputdir, 'test2.pp'))] expect(stats_before[1] <=> stats_after[1]).to be(0) expect(stats_before[0] <=> stats_after[0]).to be(-1) end it 'overwrites all files when called with --force' do # create them (first run) genface.types stats_before = [Puppet::FileSystem.stat(File.join(outputdir, 'test1.pp')), Puppet::FileSystem.stat(File.join(outputdir, 'test2.pp'))] # generate again sleep(1) # sorry, if there is no delay the stats will be the same genface.types(:force => true) stats_after = [Puppet::FileSystem.stat(File.join(outputdir, 'test1.pp')), Puppet::FileSystem.stat(File.join(outputdir, 'test2.pp'))] expect(stats_before <=> stats_after).to be(-1) end it 'removes previously generated files from output when there is no input for it' do # create them (first run) genface.types stat_before = Puppet::FileSystem.stat(File.join(outputdir, 'test2.pp')) # remove input Puppet::FileSystem.unlink(File.join(m1, 'lib', 'puppet', 'type', 'test1.rb')) # generate again genface.types # assert that test1 was deleted but not test2 (same time) expect(Puppet::FileSystem.exist?(File.join(outputdir, 'test1.pp'))).to be(false) stats_after = Puppet::FileSystem.stat(File.join(outputdir, 'test2.pp')) expect(stat_before <=> stats_after).to be(0) end end end def from_an_interactive_terminal STDIN.stubs(:tty?).returns(true) end end puppet-5.5.10/spec/unit/face/help_spec.rb0000755005276200011600000001657013417161721020124 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/face' describe Puppet::Face[:help, '0.0.1'] do it 'has a help action' do expect(subject).to be_action :help end it 'has a default action of help' do expect(subject.get_action('help')).to be_default end it 'accepts a call with no arguments' do expect { subject.help() }.to_not raise_error end it 'accepts a face name' do expect { subject.help(:help) }.to_not raise_error end it 'accepts a face and action name' do expect { subject.help(:help, :help) }.to_not raise_error end it 'fails if more than a face and action are given' do expect { subject.help(:help, :help, :for_the_love_of_god) }.to raise_error ArgumentError end it "treats :current and 'current' identically" do expect(subject.help(:help, :version => :current)).to eq( subject.help(:help, :version => 'current') ) end it 'raises an error when the face is unavailable' do expect { subject.help(:huzzah, :bar, :version => '17.0.0') }.to raise_error(ArgumentError, /Could not find version 17\.0\.0/) end it 'finds a face by version' do face = Puppet::Face[:huzzah, :current] expect(subject.help(:huzzah, :version => face.version)). to eq(subject.help(:huzzah, :version => :current)) end context 'rendering has an error' do it 'raises an ArgumentError if the face raises a StandardError' do face = Puppet::Face[:module, :current] face.stubs(:short_description).raises(StandardError, 'whoops') expect { subject.help(:module) }.to raise_error(ArgumentError, /Detail: "whoops"/) end it 'raises an ArgumentError if the face raises a LoadError' do face = Puppet::Face[:module, :current] face.stubs(:short_description).raises(LoadError, 'cannot load such file -- yard') expect { subject.help(:module) }.to raise_error(ArgumentError, /Detail: "cannot load such file -- yard"/) end context 'with face actions' do it 'returns an error if we can not get an action for the module' do face = Puppet::Face[:module, :current] face.stubs(:get_action).returns(nil) expect {subject.help('module', 'list')}.to raise_error(ArgumentError, /Unable to load action list from Puppet::Face/) end end end context 'when listing subcommands' do subject { Puppet::Face[:help, :current].help } RSpec::Matchers.define :have_a_summary do match do |instance| instance.summary.is_a?(String) end end # Check a precondition for the next block; if this fails you have # something odd in your set of face, and we skip testing things that # matter. --daniel 2011-04-10 it 'has at least one face with a summary' do expect(Puppet::Face.faces).to be_any do |name| Puppet::Face[name, :current].summary end end it 'lists all faces which are runnable from the command line' do help_face = Puppet::Face[:help, :current] # The main purpose of the help face is to provide documentation for # command line users. It shouldn't show documentation for faces # that can't be run from the command line, so, rather than iterating # over all available faces, we need to iterate over the subcommands # that are available from the command line. Puppet::Application.available_application_names.each do |name| next unless help_face.is_face_app?(name) next if help_face.exclude_from_docs?(name) face = Puppet::Face[name, :current] summary = face.summary expect(subject).to match(%r{ #{name} }) summary and expect(subject).to match(%r{ #{name} +#{summary}}) end end context 'face summaries' do it 'can generate face summaries' do faces = Puppet::Face.faces expect(faces.length).to be > 0 faces.each do |name| expect(Puppet::Face[name, :current]).to have_a_summary end end end it 'lists all legacy applications' do Puppet::Face[:help, :current].legacy_applications.each do |appname| expect(subject).to match(%r{ #{appname} }) summary = Puppet::Face[:help, :current].horribly_extract_summary_from(appname) summary_regex = Regexp.escape(summary) summary and expect(subject).to match(%r{ #{summary_regex}$}) end end end context 'deprecated faces' do it 'prints a deprecation warning for deprecated faces' do Puppet::Face[:module, :current].stubs(:deprecated?).returns(true) expect(Puppet::Face[:help, :current].help(:module)).to match(/Warning: 'puppet module' is deprecated/) end end context '#all_application_summaries' do it 'appends a deprecation warning for deprecated faces' do # Stub the module face as deprecated Puppet::Face[:module, :current].expects(:deprecated?).returns(true) Puppet::Face[:help, :current].all_application_summaries.each do |appname,summary| expect(summary).to match(/Deprecated/) if appname == 'module' end end end context '#legacy_applications' do subject { Puppet::Face[:help, :current].legacy_applications } # If we don't, these tests are ... less than useful, because they assume # it. When this breaks you should consider ditching the entire feature # and tests, but if not work out how to fake one. --daniel 2011-04-11 it { is_expected.to have_at_least(1).item } # Meh. This is nasty, but we can't control the other list; the specific # bug that caused these to be listed is annoyingly subtle and has a nasty # fix, so better to have a "fail if you do something daft" trigger in # place here, I think. --daniel 2011-04-11 %w{face_base indirection_base}.each do |name| it { is_expected.not_to include name } end end context 'help for legacy applications' do subject { Puppet::Face[:help, :current] } let :appname do subject.legacy_applications.first end # This test is purposely generic, so that as we eliminate legacy commands # we don't get into a loop where we either test a face-based replacement # and fail to notice breakage, or where we have to constantly rewrite this # test and all. --daniel 2011-04-11 it 'returns the legacy help when given the subcommand' do help = subject.help(appname) expect(help).to match(/puppet-#{appname}/) %w{SYNOPSIS USAGE DESCRIPTION OPTIONS COPYRIGHT}.each do |heading| expect(help).to match(/^#{heading}$/) end end it 'fails when asked for an action on a legacy command' do expect { subject.help(appname, :whatever) }. to raise_error(ArgumentError, /The legacy subcommand '#{appname}' does not support supplying an action/) end context 'rendering has an error' do it 'raises an ArgumentError if a legacy application raises a StandardError' do Puppet::Application[appname].class.any_instance.stubs(:help).raises(StandardError, 'whoops') expect { subject.help(appname) }.to raise_error(ArgumentError, /Detail: "whoops"/) end it 'raises an ArgumentError if a legacy application raises a LoadError' do Puppet::Application[appname].class.any_instance.stubs(:help).raises(LoadError, 'cannot load such file -- yard') expect { subject.help(appname) }.to raise_error(ArgumentError, /Detail: "cannot load such file -- yard"/) end end end end puppet-5.5.10/spec/unit/face/key_spec.rb0000644005276200011600000000030113417161721017742 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/face' describe Puppet::Face[:key, '0.0.1'] do it "should be deprecated" do expect(subject.deprecated?).to be_truthy end end puppet-5.5.10/spec/unit/face/man_spec.rb0000755005276200011600000000124013417161721017733 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/face' describe Puppet::Face[:man, '0.0.1'] do it 'should be deprecated' do expect(subject.deprecated?).to be_truthy end it 'has a man action' do expect(subject).to be_action(:man) end it 'has a default action of man' do expect(subject.get_action('man')).to be_default end it 'accepts a call with no arguments' do expect { subject.man() }.to have_printed(/USAGE: puppet man /) end it 'raises an ArgumentError when given to many arguments' do subject.stubs(:print_man_help) expect { subject.man(:man, 'cert', 'extra') }.to raise_error(ArgumentError) end end puppet-5.5.10/spec/unit/face/module/0000755005276200011600000000000013417162176017113 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/face/module/install_spec.rb0000644005276200011600000000247313417161721022121 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/face' require 'puppet/module_tool' describe "puppet module install" do include PuppetSpec::Files describe "action" do let(:name) { stub(:name) } let(:target_dir) { tmpdir('module install face action') } let(:options) { { :target_dir => target_dir } } it 'should invoke the Installer app' do Puppet::ModuleTool.expects(:set_option_defaults).with(options) Puppet::ModuleTool::Applications::Installer.expects(:run).with do |*args| mod, target, opts = args expect(mod).to eql(name) expect(opts).to eql(options) expect(target).to be_a(Puppet::ModuleTool::InstallDirectory) expect(target.target).to eql(Pathname.new(target_dir)) end Puppet::Face[:module, :current].install(name, options) end end describe "inline documentation" do subject { Puppet::Face.find_action(:module, :install) } its(:summary) { should =~ /install.*module/im } its(:description) { should =~ /install.*module/im } its(:returns) { should =~ /pathname/i } its(:examples) { should_not be_empty } %w{ license copyright summary description returns examples }.each do |doc| context "of the" do its(doc.to_sym) { should_not =~ /(FIXME|REVISIT|TODO)/ } end end end end puppet-5.5.10/spec/unit/face/module/uninstall_spec.rb0000644005276200011600000000267013417161721022463 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/face' require 'puppet/module_tool' describe "puppet module uninstall" do include PuppetSpec::Files describe "action" do let(:name) { 'module-name' } let(:options) { Hash.new } it 'should invoke the Uninstaller app' do args = [ name, options ] Puppet::ModuleTool.expects(:set_option_defaults).with(options) Puppet::ModuleTool::Applications::Uninstaller.expects(:run).with(*args) Puppet::Face[:module, :current].uninstall(name, options) end context 'slash-separated module name' do let(:name) { 'module/name' } it 'should invoke the Uninstaller app' do args = [ 'module-name', options ] Puppet::ModuleTool.expects(:set_option_defaults).with(options) Puppet::ModuleTool::Applications::Uninstaller.expects(:run).with(*args) Puppet::Face[:module, :current].uninstall(name, options) end end end describe "inline documentation" do subject { Puppet::Face.find_action(:module, :uninstall) } its(:summary) { should =~ /uninstall.*module/im } its(:description) { should =~ /uninstall.*module/im } its(:returns) { should =~ /uninstalled modules/i } its(:examples) { should_not be_empty } %w{ license copyright summary description returns examples }.each do |doc| context "of the" do its(doc.to_sym) { should_not =~ /(FIXME|REVISIT|TODO)/ } end end end end puppet-5.5.10/spec/unit/face/module/upgrade_spec.rb0000644005276200011600000000127113417161721022075 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/face' require 'puppet/module_tool' describe "puppet module upgrade" do subject { Puppet::Face[:module, :current] } let(:options) do {} end describe "inline documentation" do subject { Puppet::Face[:module, :current].get_action :upgrade } its(:summary) { should =~ /upgrade.*module/im } its(:description) { should =~ /upgrade.*module/im } its(:returns) { should =~ /hash/i } its(:examples) { should_not be_empty } %w{ license copyright summary description returns examples }.each do |doc| context "of the" do its(doc.to_sym) { should_not =~ /(FIXME|REVISIT|TODO)/ } end end end end puppet-5.5.10/spec/unit/face/module/build_spec.rb0000644005276200011600000000603013417161722021544 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/face' require 'puppet/module_tool' describe "puppet module build" do subject { Puppet::Face[:module, :current] } describe "when called without any options" do it "if current directory is a module root should call builder with it" do Dir.expects(:pwd).returns('/a/b/c') Puppet::ModuleTool.expects(:find_module_root).with('/a/b/c').returns('/a/b/c') Puppet::ModuleTool.expects(:set_option_defaults).returns({}) Puppet::ModuleTool::Applications::Builder.expects(:run).with('/a/b/c', {}) subject.build end it "if parent directory of current dir is a module root should call builder with it" do Dir.expects(:pwd).returns('/a/b/c') Puppet::ModuleTool.expects(:find_module_root).with('/a/b/c').returns('/a/b') Puppet::ModuleTool.expects(:set_option_defaults).returns({}) Puppet::ModuleTool::Applications::Builder.expects(:run).with('/a/b', {}) subject.build end it "if current directory or parents contain no module root, should return exception" do Dir.expects(:pwd).returns('/a/b/c') Puppet::ModuleTool.expects(:find_module_root).returns(nil) expect { subject.build }.to raise_error RuntimeError, "Unable to find metadata.json in module root /a/b/c or parent directories. See for required file format." end end describe "when called with a path" do it "if path is a module root should call builder with it" do Puppet::ModuleTool.expects(:is_module_root?).with('/a/b/c').returns(true) Puppet::ModuleTool.expects(:set_option_defaults).returns({}) Puppet::ModuleTool::Applications::Builder.expects(:run).with('/a/b/c', {}) subject.build('/a/b/c') end it "if path is not a module root should raise exception" do Puppet::ModuleTool.expects(:is_module_root?).with('/a/b/c').returns(false) expect { subject.build('/a/b/c') }.to raise_error RuntimeError, "Unable to find metadata.json in module root /a/b/c or parent directories. See for required file format." end end describe "with options" do it "should pass through options to builder when provided" do Puppet::ModuleTool.stubs(:is_module_root?).returns(true) Puppet::ModuleTool.expects(:set_option_defaults).returns({}) Puppet::ModuleTool::Applications::Builder.expects(:run).with('/a/b/c', {:modulepath => '/x/y/z'}) subject.build('/a/b/c', :modulepath => '/x/y/z') end end describe "inline documentation" do subject { Puppet::Face[:module, :current].get_action :build } its(:summary) { should =~ /build.*module/im } its(:description) { should =~ /build.*module/im } its(:returns) { should =~ /pathname/i } its(:examples) { should_not be_empty } %w{ license copyright summary description returns examples }.each do |doc| context "of the" do its(doc.to_sym) { should_not =~ /(FIXME|REVISIT|TODO)/ } end end end end puppet-5.5.10/spec/unit/face/module/list_spec.rb0000644005276200011600000002540513417161722021427 0ustar jenkinsjenkins# encoding: UTF-8 require 'spec_helper' require 'puppet/face' require 'puppet/module_tool' require 'puppet_spec/modules' describe "puppet module list" do include PuppetSpec::Files around do |example| dir = tmpdir("deep_path") FileUtils.mkdir_p(@modpath1 = File.join(dir, "modpath1")) FileUtils.mkdir_p(@modpath2 = File.join(dir, "modpath2")) FileUtils.mkdir_p(@modpath3 = File.join(dir, "modpath3")) env = Puppet::Node::Environment.create(:env, [@modpath1, @modpath2]) Puppet.override(:current_environment => env) do example.run end end it "should return an empty list per dir in path if there are no modules" do expect(Puppet::Face[:module, :current].list[:modules_by_path]).to eq({ @modpath1 => [], @modpath2 => [] }) end it "should include modules separated by the environment's modulepath" do foomod1 = PuppetSpec::Modules.create('foo', @modpath1) barmod1 = PuppetSpec::Modules.create('bar', @modpath1) foomod2 = PuppetSpec::Modules.create('foo', @modpath2) usedenv = Puppet::Node::Environment.create(:useme, [@modpath1, @modpath2, @modpath3]) Puppet.override(:environments => Puppet::Environments::Static.new(usedenv)) do expect(Puppet::Face[:module, :current].list(:environment => 'useme')[:modules_by_path]).to eq({ @modpath1 => [ Puppet::Module.new('bar', barmod1.path, usedenv), Puppet::Module.new('foo', foomod1.path, usedenv) ], @modpath2 => [Puppet::Module.new('foo', foomod2.path, usedenv)], @modpath3 => [], }) end end it "should use the specified environment" do foomod = PuppetSpec::Modules.create('foo', @modpath1) barmod = PuppetSpec::Modules.create('bar', @modpath1) usedenv = Puppet::Node::Environment.create(:useme, [@modpath1, @modpath2, @modpath3]) Puppet.override(:environments => Puppet::Environments::Static.new(usedenv)) do expect(Puppet::Face[:module, :current].list(:environment => 'useme')[:modules_by_path]).to eq({ @modpath1 => [ Puppet::Module.new('bar', barmod.path, usedenv), Puppet::Module.new('foo', foomod.path, usedenv) ], @modpath2 => [], @modpath3 => [], }) end end it "should use the specified modulepath" do foomod = PuppetSpec::Modules.create('foo', @modpath1) barmod = PuppetSpec::Modules.create('bar', @modpath2) modules = Puppet::Face[:module, :current].list(:modulepath => "#{@modpath1}#{File::PATH_SEPARATOR}#{@modpath2}")[:modules_by_path] expect(modules[@modpath1].first.name).to eq('foo') expect(modules[@modpath1].first.path).to eq(foomod.path) expect(modules[@modpath1].first.environment.modulepath).to eq([@modpath1, @modpath2]) expect(modules[@modpath2].first.name).to eq('bar') expect(modules[@modpath2].first.path).to eq(barmod.path) expect(modules[@modpath2].first.environment.modulepath).to eq([@modpath1, @modpath2]) end it "prefers a given modulepath over the modulepath from the given environment" do foomod = PuppetSpec::Modules.create('foo', @modpath1) barmod = PuppetSpec::Modules.create('bar', @modpath2) modules = Puppet::Face[:module, :current].list(:environment => 'myenv', :modulepath => "#{@modpath1}#{File::PATH_SEPARATOR}#{@modpath2}")[:modules_by_path] expect(modules[@modpath1].first.name).to eq('foo') expect(modules[@modpath1].first.path).to eq(foomod.path) expect(modules[@modpath1].first.environment.modulepath).to eq([@modpath1, @modpath2]) expect(modules[@modpath1].first.environment.name).to_not eq(:myenv) expect(modules[@modpath2].first.name).to eq('bar') expect(modules[@modpath2].first.path).to eq(barmod.path) expect(modules[@modpath2].first.environment.modulepath).to eq([@modpath1, @modpath2]) expect(modules[@modpath2].first.environment.name).to_not eq(:myenv) end describe "inline documentation" do subject { Puppet::Face[:module, :current].get_action(:list) } its(:summary) { should =~ /list.*module/im } its(:description) { should =~ /list.*module/im } its(:returns) { should =~ /hash of paths to module objects/i } its(:examples) { should_not be_empty } end describe "when rendering to console" do let(:face) { Puppet::Face[:module, :current] } let(:action) { face.get_action(:list) } def console_output(options={}) result = face.list(options) action.when_rendering(:console).call(result, options) end it "should explicitly state when a modulepath is empty" do empty_modpath = tmpdir('empty') expected = <<-HEREDOC.gsub(' ', '') #{empty_modpath} (no modules installed) HEREDOC expect(console_output(:modulepath => empty_modpath)).to eq(expected) end it "should print both modules with and without metadata" do modpath = tmpdir('modpath') PuppetSpec::Modules.create('nometadata', modpath) PuppetSpec::Modules.create('metadata', modpath, :metadata => {:author => 'metaman'}) env = Puppet::Node::Environment.create(:environ, [modpath]) Puppet.override(:current_environment => env) do expected = <<-HEREDOC.gsub(' ', '') #{modpath} ├── metaman-metadata (\e[0;36mv9.9.9\e[0m) └── nometadata (\e[0;36m???\e[0m) HEREDOC expect(console_output).to eq(expected) end end it "should print the modulepaths in the order they are in the modulepath setting" do path1 = tmpdir('b') path2 = tmpdir('c') path3 = tmpdir('a') env = Puppet::Node::Environment.create(:environ, [path1, path2, path3]) Puppet.override(:current_environment => env) do expected = <<-HEREDOC.gsub(' ', '') #{path1} (no modules installed) #{path2} (no modules installed) #{path3} (no modules installed) HEREDOC expect(console_output).to eq(expected) end end it "should print dependencies as a tree" do PuppetSpec::Modules.create('dependable', @modpath1, :metadata => { :version => '0.0.5'}) PuppetSpec::Modules.create( 'other_mod', @modpath1, :metadata => { :version => '1.0.0', :dependencies => [{ "version_requirement" => ">= 0.0.5", "name" => "puppetlabs/dependable" }] } ) expected = <<-HEREDOC.gsub(' ', '') #{@modpath1} └─┬ puppetlabs-other_mod (\e[0;36mv1.0.0\e[0m) └── puppetlabs-dependable (\e[0;36mv0.0.5\e[0m) #{@modpath2} (no modules installed) HEREDOC expect(console_output(:tree => true)).to eq(expected) end it "should print both modules with and without metadata as a tree" do PuppetSpec::Modules.create('nometadata', @modpath1) PuppetSpec::Modules.create('metadata', @modpath1, :metadata => {:author => 'metaman'}) expected = <<-HEREDOC.gsub(' ', '') #{@modpath1} ├── metaman-metadata (\e[0;36mv9.9.9\e[0m) └── nometadata (\e[0;36m???\e[0m) #{@modpath2} (no modules installed) HEREDOC expect(console_output).to eq(expected) end it "should warn about missing dependencies" do PuppetSpec::Modules.create('depender', @modpath1, :metadata => { :version => '1.0.0', :dependencies => [{ "version_requirement" => ">= 0.0.5", "name" => "puppetlabs/dependable" }] }) warning_expectations = [ regexp_matches(/Missing dependency 'puppetlabs-dependable'/), regexp_matches(/'puppetlabs-depender' \(v1\.0\.0\) requires 'puppetlabs-dependable' \(>= 0\.0\.5\)/) ] Puppet.expects(:warning).with(all_of(*warning_expectations)) console_output(:tree => true) end it 'should not warn about dependent module with pre-release version by default' do PuppetSpec::Modules.create('depender', @modpath1, :metadata => { :version => '1.0.0', :dependencies => [{ "version_requirement" => ">= 1.0.0", "name" => "puppetlabs/dependable" }] }) PuppetSpec::Modules.create('dependable', @modpath1, :metadata => { :version => '1.0.0-rc1' }) expected = <<-OUTPUT.unindent #{@modpath1} ├── puppetlabs-dependable (\e[0;36mv1.0.0-rc1\e[0m) └── puppetlabs-depender (\e[0;36mv1.0.0\e[0m) #{@modpath2} (no modules installed) OUTPUT expect(console_output).to eq(expected) end it 'should warn about dependent module with pre-release version by if pre-release is less than given pre-release' do PuppetSpec::Modules.create('depender', @modpath1, :metadata => { :version => '1.0.0', :dependencies => [{ "version_requirement" => ">= 1.0.0-rc1", "name" => "puppetlabs/dependable" }] }) PuppetSpec::Modules.create('dependable', @modpath1, :metadata => { :version => '1.0.0-rc0' }) expected = <<-OUTPUT.unindent #{@modpath1} ├── puppetlabs-dependable (\e[0;36mv1.0.0-rc0\e[0m) \e[0;31minvalid\e[0m └── puppetlabs-depender (\e[0;36mv1.0.0\e[0m) #{@modpath2} (no modules installed) OUTPUT expect(console_output).to eq(expected) end it 'should warn about dependent module with pre-release version when using strict SemVer' do PuppetSpec::Modules.create('depender', @modpath1, :metadata => { :version => '1.0.0', :dependencies => [{ "version_requirement" => ">= 1.0.0", "name" => "puppetlabs/dependable" }] }) PuppetSpec::Modules.create('dependable', @modpath1, :metadata => { :version => '1.0.0-rc1' }) expected = <<-OUTPUT.unindent #{@modpath1} ├── puppetlabs-dependable (\e[0;36mv1.0.0-rc1\e[0m) \e[0;31minvalid\e[0m └── puppetlabs-depender (\e[0;36mv1.0.0\e[0m) #{@modpath2} (no modules installed) OUTPUT expect(console_output(:strict_semver => true)).to eq(expected) end it "should warn about out of range dependencies" do PuppetSpec::Modules.create('dependable', @modpath1, :metadata => { :version => '0.0.1'}) PuppetSpec::Modules.create('depender', @modpath1, :metadata => { :version => '1.0.0', :dependencies => [{ "version_requirement" => ">= 0.0.5", "name" => "puppetlabs/dependable" }] }) warning_expectations = [ regexp_matches(/Module 'puppetlabs-dependable' \(v0\.0\.1\) fails to meet some dependencies/), regexp_matches(/'puppetlabs-depender' \(v1\.0\.0\) requires 'puppetlabs-dependable' \(>= 0\.0\.5\)/) ] Puppet.expects(:warning).with(all_of(*warning_expectations)) console_output(:tree => true) end end end puppet-5.5.10/spec/unit/face/module/search_spec.rb0000644005276200011600000001715713417161722021726 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/face' require 'puppet/application/module' require 'puppet/module_tool' describe "puppet module search" do subject { Puppet::Face[:module, :current] } let(:options) do {} end describe Puppet::Application::Module do subject do app = Puppet::Application::Module.new app.stubs(:action).returns(Puppet::Face.find_action(:module, :search)) app end before { subject.render_as = :console } before { Puppet::Util::Terminal.stubs(:width).returns(100) } it 'should output nothing when receiving an empty dataset' do expect(subject.render({:answers => [], :result => :success}, ['apache', {}])).to eq("No results found for 'apache'.") end it 'should return error and exit when error returned' do results = { :result => :failure, :error => { :oneline => 'Something failed', :multiline => 'Something failed', } } expect { subject.render(results, ['apache', {}]) }.to raise_error 'Something failed' end it 'should output a header when receiving a non-empty dataset' do results = { :result => :success, :answers => [ {'full_name' => '', 'author' => '', 'desc' => '', 'tag_list' => [] }, ], } expect(subject.render(results, ['apache', {}])).to match(/NAME/) expect(subject.render(results, ['apache', {}])).to match(/DESCRIPTION/) expect(subject.render(results, ['apache', {}])).to match(/AUTHOR/) expect(subject.render(results, ['apache', {}])).to match(/KEYWORDS/) end it 'should output the relevant fields when receiving a non-empty dataset' do results = { :result => :success, :answers => [ {'full_name' => 'Name', 'author' => 'Author', 'desc' => 'Summary', 'tag_list' => ['tag1', 'tag2'] }, ] } expect(subject.render(results, ['apache', {}])).to match(/Name/) expect(subject.render(results, ['apache', {}])).to match(/Author/) expect(subject.render(results, ['apache', {}])).to match(/Summary/) expect(subject.render(results, ['apache', {}])).to match(/tag1/) expect(subject.render(results, ['apache', {}])).to match(/tag2/) end it 'should mark deprecated modules in search results' do results = { :result => :success, :answers => [ {'full_name' => 'puppetlabs-corosync', 'deprecated_at' => Time.new, 'author' => 'Author', 'desc' => 'Summary', 'tag_list' => ['tag1', 'tag2'] }, ] } expect(subject.render(results, ['apache', {}])).to match(/puppetlabs-corosync.*DEPRECATED/i) end it 'should elide really long descriptions' do results = { :result => :success, :answers => [ { 'full_name' => 'Name', 'author' => 'Author', 'desc' => 'This description is really too long to fit in a single data table, guys -- we should probably set about truncating it', 'tag_list' => ['tag1', 'tag2'], }, ] } expect(subject.render(results, ['apache', {}])).to match(/\.{3} @Author/) end it 'should never truncate the module name' do results = { :result => :success, :answers => [ { 'full_name' => 'This-module-has-a-really-really-long-name', 'author' => 'Author', 'desc' => 'Description', 'tag_list' => ['tag1', 'tag2'], }, ] } expect(subject.render(results, ['apache', {}])).to match(/This-module-has-a-really-really-long-name/) end it 'should never truncate the author name' do results = { :result => :success, :answers => [ { 'full_name' => 'Name', 'author' => 'This-author-has-a-really-really-long-name', 'desc' => 'Description', 'tag_list' => ['tag1', 'tag2'], }, ] } expect(subject.render(results, ['apache', {}])).to match(/@This-author-has-a-really-really-long-name/) end it 'should never remove tags that match the search term' do results = { :results => :success, :answers => [ { 'full_name' => 'Name', 'author' => 'Author', 'desc' => 'Description', 'tag_list' => ['Supercalifragilisticexpialidocious'] + (1..100).map { |i| "tag#{i}" }, }, ] } expect(subject.render(results, ['Supercalifragilisticexpialidocious', {}])).to match(/Supercalifragilisticexpialidocious/) expect(subject.render(results, ['Supercalifragilisticexpialidocious', {}])).not_to match(/tag/) end { 100 => "NAME DESCRIPTION AUTHOR KEYWORDS#{' '*15}\n"\ "Name This description is really too long to fit ... @JohnnyApples tag1 tag2 taggitty3#{' '*4}\n", 70 => "NAME DESCRIPTION AUTHOR KEYWORDS#{' '*5}\n"\ "Name This description is rea... @JohnnyApples tag1 tag2#{' '*4}\n", 80 => "NAME DESCRIPTION AUTHOR KEYWORDS#{' '*8}\n"\ "Name This description is really too... @JohnnyApples tag1 tag2#{' '*7}\n", 200 => "NAME DESCRIPTION AUTHOR KEYWORDS#{' '*48}\n"\ "Name This description is really too long to fit in a single data table, guys -- we should probably set about trunca... @JohnnyApples tag1 tag2 taggitty3#{' '*37}\n" }.each do |width, expectation| it "should resize the table to fit the screen, when #{width} columns" do results = { :result => :success, :answers => [ { 'full_name' => 'Name', 'author' => 'JohnnyApples', 'desc' => 'This description is really too long to fit in a single data table, guys -- we should probably set about truncating it', 'tag_list' => ['tag1', 'tag2', 'taggitty3'], }, ] } Puppet::Util::Terminal.expects(:width).returns(width) result = subject.render(results, ['apache', {}]) expect(result.lines.sort_by(&:length).last.chomp.length).to be <= width expect(result).to eq(expectation) end end end describe "option validation" do context "without any options" do it "should require a search term" do pattern = /wrong number of arguments/ expect { subject.search }.to raise_error ArgumentError, pattern end end it "should accept the --module_repository option" do forge = mock("Puppet::Forge") searcher = mock("Searcher") options[:module_repository] = "http://forge.example.com" Puppet::Forge.expects(:new).with("http://forge.example.com", false).returns(forge) Puppet::ModuleTool::Applications::Searcher.expects(:new).with("puppetlabs-apache", forge, has_entries(options)).returns(searcher) searcher.expects(:run) subject.search("puppetlabs-apache", options) end end describe "inline documentation" do subject { Puppet::Face[:module, :current].get_action :search } its(:summary) { should =~ /search.*module/im } its(:description) { should =~ /search.*module/im } its(:returns) { should =~ /array/i } its(:examples) { should_not be_empty } %w{ license copyright summary description returns examples }.each do |doc| context "of the" do its(doc.to_sym) { should_not =~ /(FIXME|REVISIT|TODO)/ } end end end end puppet-5.5.10/spec/unit/face/module_spec.rb0000644005276200011600000000025513417161721020447 0ustar jenkinsjenkins# For face related tests, look in the spec/unit/faces/module folder. # For integration tests, which test the behavior of module, look in the # spec/unit/integration folder. puppet-5.5.10/spec/unit/face/plugin_spec.rb0000644005276200011600000000534513417161721020465 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/face' describe Puppet::Face[:plugin, :current] do let(:pluginface) { described_class } let(:action) { pluginface.get_action(:download) } def render(result) action.when_rendering(:console).call(result) end context "download" do before :each do #Server_agent version needs to be at 5.3.4 in order to mount locales Puppet.push_context({:server_agent_version => "5.3.4"}) end it "downloads plugins, external facts, and locales" do Puppet::Configurer::Downloader.any_instance.expects(:evaluate).times(3).returns([]) pluginface.download end it "renders 'No plugins downloaded' if nothing was downloaded" do Puppet::Configurer::Downloader.any_instance.expects(:evaluate).times(3).returns([]) result = pluginface.download expect(render(result)).to eq('No plugins downloaded.') end it "renders comma separate list of downloaded file names" do Puppet::Configurer::Downloader.any_instance.expects(:evaluate).times(3).returns(%w[/a]).then.returns(%w[/b]).then.returns(%w[/c]) result = pluginface.download expect(render(result)).to eq('Downloaded these plugins: /a, /b, /c') end end context "download when server_agent_version is 5.3.3" do before :each do #Server_agent version needs to be at 5.3.4 in order to mount locales Puppet.push_context({:server_agent_version => "5.3.3"}) end it "downloads plugins, and external facts, but not locales" do Puppet::Configurer::Downloader.any_instance.expects(:evaluate).times(2).returns([]) pluginface.download end it "renders comma separate list of downloaded file names that does not include locales" do Puppet::Configurer::Downloader.any_instance.expects(:evaluate).times(2).returns(%w[/a]).then.returns(%w[/b]) result = pluginface.download expect(render(result)).to eq('Downloaded these plugins: /a, /b') end end context "download when server_agent_version is blank" do before :each do #Server_agent version needs to be at 5.3.4 in order to mount locales #A blank version will default to 0.0 Puppet.push_context({:server_agent_version => ""}) end it "downloads plugins, and external facts, but not locales" do Puppet::Configurer::Downloader.any_instance.expects(:evaluate).times(2).returns([]) pluginface.download end it "renders comma separate list of downloaded file names that does not include locales" do Puppet::Configurer::Downloader.any_instance.expects(:evaluate).times(2).returns(%w[/a]).then.returns(%w[/b]) result = pluginface.download expect(render(result)).to eq('Downloaded these plugins: /a, /b') end end end puppet-5.5.10/spec/unit/face/status_spec.rb0000644005276200011600000000030413417161721020500 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/face' describe Puppet::Face[:status, '0.0.1'] do it "should be deprecated" do expect(subject.deprecated?).to be_truthy end end puppet-5.5.10/spec/unit/face/ca_spec.rb0000644005276200011600000000030013417161722017535 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/face' describe Puppet::Face[:ca, '0.1.0'] do it "should be deprecated" do expect(subject.deprecated?).to be_truthy end end puppet-5.5.10/spec/unit/face/certificate_request_spec.rb0000644005276200011600000000032113417161722023207 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/face' describe Puppet::Face[:certificate_request, '0.0.1'] do it "should be deprecated" do expect(subject.deprecated?).to be_truthy end end puppet-5.5.10/spec/unit/face/certificate_revocation_list_spec.rb0000644005276200011600000000033113417161722024724 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/face' describe Puppet::Face[:certificate_revocation_list, '0.0.1'] do it "should be deprecated" do expect(subject.deprecated?).to be_truthy end end puppet-5.5.10/spec/unit/face/certificate_spec.rb0000644005276200011600000001740313417161722021450 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/face' require 'puppet/ssl/host' describe Puppet::Face[:certificate, '0.0.1'] do include PuppetSpec::Files let(:ca) { Puppet::SSL::CertificateAuthority.instance } # different UTF-8 widths # 1-byte A # 2-byte ۿ - http://www.fileformat.info/info/unicode/char/06ff/index.htm - 0xDB 0xBF / 219 191 # 3-byte ᚠ - http://www.fileformat.info/info/unicode/char/16A0/index.htm - 0xE1 0x9A 0xA0 / 225 154 160 # 4-byte ܎ - http://www.fileformat.info/info/unicode/char/2070E/index.htm - 0xF0 0xA0 0x9C 0x8E / 240 160 156 142 let (:mixed_utf8) { "A\u06FF\u16A0\u{2070E}" } # Aۿᚠ܎ before :each do Puppet[:confdir] = tmpdir('conf') Puppet::SSL::CertificateAuthority.stubs(:ca?).returns true Puppet::SSL::Host.ca_location = :local # We can't cache the CA between tests, because each one has its own SSL dir. ca = Puppet::SSL::CertificateAuthority.new Puppet::SSL::CertificateAuthority.stubs(:new).returns ca Puppet::SSL::CertificateAuthority.stubs(:instance).returns ca end it "should have a ca-location option" do expect(subject).to be_option :ca_location end it "should set the ca location when invoked" do Puppet::SSL::Host.expects(:ca_location=).with(:local) ca.expects(:sign).with do |name,options| name == "hello, friend" end subject.sign "hello, friend", :ca_location => :local end it "(#7059) should set the ca location when an inherited action is invoked" do Puppet::SSL::Host.expects(:ca_location=).with(:local) subject.indirection.expects(:find) subject.find "hello, friend", :ca_location => :local end it "should validate the option as required" do expect do subject.find 'hello, friend' end.to raise_exception ArgumentError, /required/i end it "should validate the option as a supported value" do expect do subject.find 'hello, friend', :ca_location => :foo end.to raise_exception ArgumentError, /valid values/i end describe "#generate" do let(:options) { {:ca_location => 'local'} } let(:host) { Puppet::SSL::Host.new(hostname) } let(:csr) { host.certificate_request } before :each do Puppet[:autosign] = false end describe "for the current host" do let(:hostname) { Puppet[:certname] } it "should generate a CSR for this host" do subject.generate(hostname, options) expect(csr.content.subject.to_s).to eq("/CN=#{Puppet[:certname]}") expect(csr.name).to eq(Puppet[:certname]) end it "should add dns_alt_names from the global config if not otherwise specified" do Puppet[:dns_alt_names] = 'from,the,config' subject.generate(hostname, options) expected = %W[DNS:from DNS:the DNS:config DNS:#{hostname}] expect(csr.subject_alt_names).to match_array(expected) end it "should add the provided dns_alt_names if they are specified" do Puppet[:dns_alt_names] = 'from,the,config' subject.generate(hostname, options.merge(:dns_alt_names => "explicit,alt,#{mixed_utf8}")) # CSRs will return subject_alt_names as BINARY strings expected = %W[DNS:explicit DNS:alt DNS:#{mixed_utf8.force_encoding(Encoding::BINARY)} DNS:#{hostname}] expect(csr.subject_alt_names).to match_array(expected) end end describe "for another host" do let(:hostname) { Puppet[:certname] + 'different' } it "should generate a CSR for the specified host" do subject.generate(hostname, options) expect(csr.content.subject.to_s).to eq("/CN=#{hostname}") expect(csr.name).to eq(hostname) end it "should fail if a CSR already exists for the host" do subject.generate(hostname, options) expect do subject.generate(hostname, options) end.to raise_error(RuntimeError, /#{hostname} already has a requested certificate; ignoring certificate request/) end it "should add not dns_alt_names from the config file" do Puppet[:dns_alt_names] = 'from,the,config' subject.generate(hostname, options) expect(csr.subject_alt_names).to be_empty end it "should add the provided dns_alt_names if they are specified" do Puppet[:dns_alt_names] = 'from,the,config' subject.generate(hostname, options.merge(:dns_alt_names => 'explicit,alt,names')) expected = %W[DNS:explicit DNS:alt DNS:names DNS:#{hostname}] expect(csr.subject_alt_names).to match_array(expected) end it "should use the global setting if set by CLI" do Puppet.settings.patch_value(:dns_alt_names, 'from,the,cli', :cli) subject.generate(hostname, options) expected = %W[DNS:from DNS:the DNS:cli DNS:#{hostname}] expect(csr.subject_alt_names).to match_array(expected) end it "should generate an error if both set on CLI" do Puppet.settings.patch_value(:dns_alt_names, 'from,the,cli', :cli) expect do subject.generate(hostname, options.merge(:dns_alt_names => 'explicit,alt,names')) end.to raise_error ArgumentError, /Can't specify both/ end end end describe "#sign" do let(:options) { {:ca_location => 'local'} } let(:host) { Puppet::SSL::Host.new(hostname) } let(:hostname) { "foobar" } it "should sign the certificate request if one is waiting", :unless => Puppet.features.microsoft_windows? do subject.generate(hostname, options) subject.sign(hostname, options) expect(host.certificate_request).to be_nil expect(host.certificate).to be_a(Puppet::SSL::Certificate) expect(host.state).to eq('signed') end it "should fail if there is no waiting certificate request" do expect do subject.sign(hostname, options) end.to raise_error(ArgumentError, /Could not find certificate request for #{hostname}/) end describe "when ca_location is local", :unless => Puppet.features.microsoft_windows? do describe "when the request has dns alt names" do before :each do subject.generate(hostname, options.merge(:dns_alt_names => 'some,alt,names')) end it "should refuse to sign the request if allow_dns_alt_names is not set" do expect do subject.sign(hostname, options) end.to raise_error(Puppet::SSL::CertificateAuthority::CertificateSigningError, /CSR '#{hostname}' contains subject alternative names \(.*?\), which are disallowed. Use `puppet cert --allow-dns-alt-names sign #{hostname}` to sign this request./i) expect(host.state).to eq('requested') end it "should sign the request if allow_dns_alt_names is set" do expect do subject.sign(hostname, options.merge(:allow_dns_alt_names => true)) end.not_to raise_error expect(host.state).to eq('signed') end end describe "when the request has no dns alt names" do before :each do subject.generate(hostname, options) end it "should sign the request if allow_dns_alt_names is set" do expect { subject.sign(hostname, options.merge(:allow_dns_alt_names => true)) }.not_to raise_error expect(host.state).to eq('signed') end it "should sign the request if allow_dns_alt_names is not set" do expect { subject.sign(hostname, options) }.not_to raise_error expect(host.state).to eq('signed') end end end describe "when ca_location is remote" do let(:options) { {:ca_location => :remote} } it "should fail if allow-dns-alt-names is specified" do expect do subject.sign(hostname, options.merge(:allow_dns_alt_names => true)) end end end end end puppet-5.5.10/spec/unit/face/node_spec.rb0000644005276200011600000001120713417161722020107 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/face' describe Puppet::Face[:node, '0.0.1'] do after :all do Puppet::SSL::Host.ca_location = :none end describe '#cleanup' do it "should clean everything" do { "cert" => ['hostname'], "cached_facts" => ['hostname'], "cached_node" => ['hostname'], "reports" => ['hostname'], }.each { |k, v| subject.expects("clean_#{k}".to_sym).with(*v) } subject.cleanup('hostname') end end describe 'when running #clean' do before :each do Puppet::Node::Facts.indirection.stubs(:terminus_class=) Puppet::Node::Facts.indirection.stubs(:cache_class=) Puppet::Node.stubs(:terminus_class=) Puppet::Node.stubs(:cache_class=) end it 'should invoke #cleanup' do subject.expects(:cleanup).with('hostname', nil) subject.clean('hostname') end end describe "clean action" do before :each do Puppet::Node::Facts.indirection.stubs(:terminus_class=) Puppet::Node::Facts.indirection.stubs(:cache_class=) Puppet::Node.stubs(:terminus_class=) Puppet::Node.stubs(:cache_class=) subject.stubs(:cleanup) end it "should have a clean action" do expect(subject).to be_action :clean end it "should not accept a call with no arguments" do expect { subject.clean() }.to raise_error(RuntimeError, /At least one node should be passed/) end it "should accept a node name" do expect { subject.clean('hostname') }.to_not raise_error end it "should accept more than one node name" do expect do subject.clean('hostname', 'hostname2', {}) end.to_not raise_error expect do subject.clean('hostname', 'hostname2', 'hostname3') end.to_not raise_error end context "clean action" do subject { Puppet::Face[:node, :current] } before :each do Puppet::Util::Log.stubs(:newdestination) Puppet::Util::Log.stubs(:level=) end describe "during setup" do it "should set facts terminus and cache class to yaml" do Puppet::Node::Facts.indirection.expects(:terminus_class=).with(:yaml) Puppet::Node::Facts.indirection.expects(:cache_class=).with(:yaml) subject.clean('hostname') end it "should run in master mode" do subject.clean('hostname') expect(Puppet.run_mode).to be_master end it "should set node cache as yaml" do Puppet::Node.indirection.expects(:terminus_class=).with(:yaml) Puppet::Node.indirection.expects(:cache_class=).with(:yaml) subject.clean('hostname') end it "should manage the certs if the host is a CA" do Puppet::SSL::CertificateAuthority.stubs(:ca?).returns(true) Puppet::SSL::Host.expects(:ca_location=).with(:local) subject.clean('hostname') end it "should not manage the certs if the host is not a CA" do Puppet::SSL::CertificateAuthority.stubs(:ca?).returns(false) Puppet::SSL::Host.expects(:ca_location=).with(:none) subject.clean('hostname') end end describe "when cleaning certificate" do before :each do Puppet::SSL::Host.stubs(:destroy) @ca = mock() Puppet::SSL::CertificateAuthority.stubs(:instance).returns(@ca) end it "should send the :destroy order to the ca if we are a CA" do Puppet::SSL::CertificateAuthority.stubs(:ca?).returns(true) @ca.expects(:revoke).with(@host) @ca.expects(:destroy).with(@host) subject.clean_cert(@host) end it "should not destroy the certs if we are not a CA" do Puppet::SSL::CertificateAuthority.stubs(:ca?).returns(false) @ca.expects(:revoke).never @ca.expects(:destroy).never subject.clean_cert(@host) end end describe "when cleaning cached facts" do it "should destroy facts" do @host = 'node' Puppet::Node::Facts.indirection.expects(:destroy).with(@host) subject.clean_cached_facts(@host) end end describe "when cleaning cached node" do it "should destroy the cached node" do Puppet::Node.indirection.expects(:destroy).with(@host) subject.clean_cached_node(@host) end end describe "when cleaning archived reports" do it "should tell the reports to remove themselves" do Puppet::Transaction::Report.indirection.stubs(:destroy).with(@host) subject.clean_reports(@host) end end end end end puppet-5.5.10/spec/unit/face/parser_spec.rb0000644005276200011600000001754213417161722020466 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/files' require 'puppet/face' describe Puppet::Face[:parser, :current] do include PuppetSpec::Files let(:parser) { Puppet::Face[:parser, :current] } context "validate" do context "from an interactive terminal" do before :each do from_an_interactive_terminal end after(:each) do # Reset cache of loaders (many examples run in the *root* environment # which exists in "eternity") Puppet.lookup(:current_environment).loaders = nil end it "validates the configured site manifest when no files are given" do manifest = file_containing('site.pp', "{ invalid =>") configured_environment = Puppet::Node::Environment.create(:default, [], manifest) Puppet.override(:current_environment => configured_environment) do expect { parser.validate() }.to exit_with(1) end end it "validates the given file" do manifest = file_containing('site.pp', "{ invalid =>") expect { parser.validate(manifest) }.to exit_with(1) end it "runs error free when there are no validation errors" do expect { manifest = file_containing('site.pp', "notify { valid: }") parser.validate(manifest) }.to_not raise_error end it "runs error free when there is a puppet function in manifest being validated" do expect { manifest = file_containing('site.pp', "function valid() { 'valid' } notify{ valid(): }") parser.validate(manifest) }.to_not raise_error end it "runs error free when there is a type alias in a manifest that requires type resolution" do expect { manifest = file_containing('site.pp', "type A = String; type B = Array[A]; function valid(B $x) { $x } notify{ valid([valid]): }") parser.validate(manifest) }.to_not raise_error end it "reports missing files" do expect do parser.validate("missing.pp") end.to raise_error(Puppet::Error, /One or more file\(s\) specified did not exist.*missing\.pp/m) end it "parses supplied manifest files in the context of a directory environment" do manifest = file_containing('test.pp', "{ invalid =>") env = Puppet::Node::Environment.create(:special, []) env_loader = Puppet::Environments::Static.new(env) Puppet.override({:environments => env_loader, :current_environment => env}) do expect { parser.validate(manifest) }.to exit_with(1) end expect(@logs.join).to match(/environment special.*Syntax error at end of input/) end end it "validates the contents of STDIN when no files given and STDIN is not a tty" do from_a_piped_input_of("{ invalid =>") Puppet.override(:current_environment => Puppet::Node::Environment.create(:special, [])) do expect { parser.validate() }.to exit_with(1) end end end context "dump" do it "prints the AST of the passed expression" do expect(parser.dump({ :e => 'notice hi' })).to eq("(invoke notice hi)\n") end it "prints the AST of the code read from the passed files" do first_manifest = file_containing('site.pp', "notice hi") second_manifest = file_containing('site2.pp', "notice bye") output = parser.dump(first_manifest, second_manifest) expect(output).to match(/site\.pp.*\(invoke notice hi\)/) expect(output).to match(/site2\.pp.*\(invoke notice bye\)/) end it "informs the user of files that don't exist" do expect(parser.dump('does_not_exist_here.pp')).to match(/did not exist:\s*does_not_exist_here\.pp/m) end it "prints the AST of STDIN when no files given and STDIN is not a tty" do from_a_piped_input_of("notice hi") Puppet.override(:current_environment => Puppet::Node::Environment.create(:special, [])) do expect(parser.dump()).to eq("(invoke notice hi)\n") end end it "logs an error if the input cannot be parsed" do output = parser.dump({ :e => '{ invalid =>' }) expect(output).to eq("") expect(@logs[0].message).to eq("Syntax error at end of input") expect(@logs[0].level).to eq(:err) end it "logs an error if the input begins with a UTF-8 BOM (Byte Order Mark)" do utf8_bom_manifest = file_containing('utf8_bom.pp', "\uFEFFnotice hi") output = parser.dump(utf8_bom_manifest) expect(output).to eq("") expect(@logs[1].message).to eq("Illegal UTF-8 Byte Order mark at beginning of input: [EF BB BF] - remove these from the puppet source") expect(@logs[1].level).to eq(:err) end it "runs error free when there is a puppet function in manifest being dumped" do expect { manifest = file_containing('site.pp', "function valid() { 'valid' } notify{ valid(): }") parser.dump(manifest) }.to_not raise_error end it "runs error free when there is a type alias in a manifest that requires type resolution" do expect { manifest = file_containing('site.pp', "type A = String; type B = Array[A]; function valid(B $x) { $x } notify{ valid([valid]): }") parser.dump(manifest) }.to_not raise_error end context "using 'pn' format" do it "prints the AST of the given expression in PN format" do expect(parser.dump({ :format => 'pn', :e => 'if $x { "hi ${x[2]}" }' })).to eq( '(if {:test (var "x") :then [(concat "hi " (str (access (var "x") 2)))]})') end it "pretty prints the AST of the given expression in PN format when --pretty is given" do expect(parser.dump({ :pretty => true, :format => 'pn', :e => 'if $x { "hi ${x[2]}" }' })).to eq(<<-RESULT.unindent[0..-2]) (if { :test (var "x") :then [ (concat "hi " (str (access (var "x") 2)))]}) RESULT end end context "using 'json' format" do it "prints the AST of the given expression in JSON based on the PN format" do expect(parser.dump({ :format => 'json', :e => 'if $x { "hi ${x[2]}" }' })).to eq( '{"^":["if",{"#":["test",{"^":["var","x"]},"then",[{"^":["concat","hi ",{"^":["str",{"^":["access",{"^":["var","x"]},2]}]}]}]]}]}') end it "pretty prints the AST of the given expression in JSON based on the PN format when --pretty is given" do expect(parser.dump({ :pretty => true, :format => 'json', :e => 'if $x { "hi ${x[2]}" }' })).to eq(<<-RESULT.unindent[0..-2]) { "^": [ "if", { "#": [ "test", { "^": [ "var", "x" ] }, "then", [ { "^": [ "concat", "hi ", { "^": [ "str", { "^": [ "access", { "^": [ "var", "x" ] }, 2 ] } ] } ] } ] ] } ] } RESULT end end end def from_an_interactive_terminal STDIN.stubs(:tty?).returns(true) end def from_a_piped_input_of(contents) STDIN.stubs(:tty?).returns(false) STDIN.stubs(:read).returns(contents) end end puppet-5.5.10/spec/unit/face_spec.rb0000644005276200011600000000004713417161721017161 0ustar jenkinsjenkins# You should look at interface_spec.rb puppet-5.5.10/spec/unit/file_bucket/0000755005276200011600000000000013417162176017204 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/file_bucket/file_spec.rb0000644005276200011600000000407013417161721021456 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/file_bucket/file' describe Puppet::FileBucket::File, :uses_checksums => true do include PuppetSpec::Files # this is the default from spec_helper, but it keeps getting reset at odd times let(:bucketdir) { Puppet[:bucketdir] = tmpdir('bucket') } it "defaults to serializing to `:binary`" do expect(Puppet::FileBucket::File.default_format).to eq(:binary) end it "only accepts binary" do expect(Puppet::FileBucket::File.supported_formats).to eq([:binary]) end describe "making round trips through network formats" do with_digest_algorithms do it "can make a round trip through `binary`" do file = Puppet::FileBucket::File.new(plaintext) tripped = Puppet::FileBucket::File.convert_from(:binary, file.render) expect(tripped.contents).to eq(plaintext) end end end it "should require contents to be a string" do expect { Puppet::FileBucket::File.new(5) }.to raise_error(ArgumentError, /contents must be a String or Pathname, got a (?:Fixnum|Integer)$/) end it "should complain about options other than :bucket_path" do expect { Puppet::FileBucket::File.new('5', :crazy_option => 'should not be passed') }.to raise_error(ArgumentError, /Unknown option\(s\): crazy_option/) end with_digest_algorithms do it "it uses #{metadata[:digest_algorithm]} as the configured digest algorithm" do file = Puppet::FileBucket::File.new(plaintext) expect(file.contents).to eq(plaintext) expect(file.checksum_type).to eq(digest_algorithm) expect(file.checksum).to eq("{#{digest_algorithm}}#{checksum}") expect(file.name).to eq("#{digest_algorithm}/#{checksum}") end end describe "when using back-ends" do it "should redirect using Puppet::Indirector" do expect(Puppet::Indirector::Indirection.instance(:file_bucket_file).model).to equal(Puppet::FileBucket::File) end it "should have a :save instance method" do expect(Puppet::FileBucket::File.indirection).to respond_to(:save) end end end puppet-5.5.10/spec/unit/file_bucket/dipper_spec.rb0000644005276200011600000004022713417161722022027 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'pathname' require 'puppet/file_bucket/dipper' require 'puppet/indirector/file_bucket_file/rest' require 'puppet/indirector/file_bucket_file/file' require 'puppet/util/checksums' shared_examples_for "a restorable file" do let(:dest) { tmpfile('file_bucket_dest') } describe "restoring the file" do with_digest_algorithms do it "should restore the file" do request = nil klass.any_instance.expects(:find).with { |r| request = r }.returns(Puppet::FileBucket::File.new(plaintext)) expect(dipper.restore(dest, checksum)).to eq(checksum) expect(digest(Puppet::FileSystem.binread(dest))).to eq(checksum) expect(request.key).to eq("#{digest_algorithm}/#{checksum}") expect(request.server).to eq(server) expect(request.port).to eq(port) end it "should skip restoring if existing file has the same checksum" do File.open(dest, 'wb') {|f| f.print(plaintext) } dipper.expects(:getfile).never expect(dipper.restore(dest, checksum)).to be_nil end it "should overwrite existing file if it has different checksum" do klass.any_instance.expects(:find).returns(Puppet::FileBucket::File.new(plaintext)) File.open(dest, 'wb') {|f| f.print('other contents') } expect(dipper.restore(dest, checksum)).to eq(checksum) end end end end describe Puppet::FileBucket::Dipper, :uses_checksums => true do include PuppetSpec::Files def make_tmp_file(contents) file = tmpfile("file_bucket_file") File.open(file, 'wb') { |f| f.write(contents) } file end it "should fail in an informative way when there are failures checking for the file on the server" do @dipper = Puppet::FileBucket::Dipper.new(:Path => make_absolute("/my/bucket")) file = make_tmp_file('contents') Puppet::FileBucket::File.indirection.expects(:head).raises ArgumentError expect { @dipper.backup(file) }.to raise_error(Puppet::Error) end it "should fail in an informative way when there are failures backing up to the server" do @dipper = Puppet::FileBucket::Dipper.new(:Path => make_absolute("/my/bucket")) file = make_tmp_file('contents') Puppet::FileBucket::File.indirection.expects(:head).returns false Puppet::FileBucket::File.indirection.expects(:save).raises ArgumentError expect { @dipper.backup(file) }.to raise_error(Puppet::Error) end describe "when diffing on a local filebucket" do describe "in non-windows environments", :unless => Puppet.features.microsoft_windows? do with_digest_algorithms do it "should fail in an informative way when one or more checksum doesn't exists" do @dipper = Puppet::FileBucket::Dipper.new(:Path => tmpdir("bucket")) wrong_checksum = "DEADBEEF" # First checksum fails expect { @dipper.diff(wrong_checksum, "WEIRDCKSM", nil, nil) }.to raise_error(RuntimeError, "Invalid checksum #{wrong_checksum.inspect}") file = make_tmp_file(plaintext) @dipper.backup(file) #Diff_with checksum fails expect { @dipper.diff(checksum, wrong_checksum, nil, nil) }.to raise_error(RuntimeError, "could not find diff_with #{wrong_checksum}") end it "should properly diff files on the filebucket" do file1 = make_tmp_file("OriginalContent\n") file2 = make_tmp_file("ModifiedContent\n") @dipper = Puppet::FileBucket::Dipper.new(:Path => tmpdir("bucket")) checksum1 = @dipper.backup(file1) checksum2 = @dipper.backup(file2) # Diff without the context # Lines we need to see match 'Content' instead of trimming diff output filter out # surrounding noise...or hard code the check values if Facter.value(:osfamily) == 'Solaris' && Puppet::Util::Package.versioncmp(Facter.value(:operatingsystemrelease), '11.0') >= 0 # Use gdiff on Solaris diff12 = Puppet::Util::Execution.execute("gdiff -uN #{file1} #{file2}| grep Content") diff21 = Puppet::Util::Execution.execute("gdiff -uN #{file2} #{file1}| grep Content") else diff12 = Puppet::Util::Execution.execute("diff -uN #{file1} #{file2}| grep Content") diff21 = Puppet::Util::Execution.execute("diff -uN #{file2} #{file1}| grep Content") end expect(@dipper.diff(checksum1, checksum2, nil, nil)).to include(diff12) expect(@dipper.diff(checksum1, nil, nil, file2)).to include(diff12) expect(@dipper.diff(nil, checksum2, file1, nil)).to include(diff12) expect(@dipper.diff(nil, nil, file1, file2)).to include(diff12) expect(@dipper.diff(checksum2, checksum1, nil, nil)).to include(diff21) expect(@dipper.diff(checksum2, nil, nil, file1)).to include(diff21) expect(@dipper.diff(nil, checksum1, file2, nil)).to include(diff21) expect(@dipper.diff(nil, nil, file2, file1)).to include(diff21) end end describe "in windows environment", :if => Puppet.features.microsoft_windows? do it "should fail in an informative way when trying to diff" do @dipper = Puppet::FileBucket::Dipper.new(:Path => tmpdir("bucket")) wrong_checksum = "DEADBEEF" # First checksum fails expect { @dipper.diff(wrong_checksum, "WEIRDCKSM", nil, nil) }.to raise_error(RuntimeError, "Diff is not supported on this platform") # Diff_with checksum fails expect { @dipper.diff(checksum, wrong_checksum, nil, nil) }.to raise_error(RuntimeError, "Diff is not supported on this platform") end end end end it "should fail in an informative way when there are failures listing files on the server" do @dipper = Puppet::FileBucket::Dipper.new(:Path => "/unexistent/bucket") Puppet::FileBucket::File.indirection.expects(:find).returns nil expect { @dipper.list(nil, nil) }.to raise_error(Puppet::Error) end describe "listing files in local filebucket" do with_digest_algorithms do it "should list all files present" do if Puppet::Util::Platform.windows? && digest_algorithm == "sha512" skip "PUP-8257: Skip file bucket test on windows for #{digest_algorithm} due to long path names" end Puppet[:bucketdir] = "/my/bucket" file_bucket = tmpdir("bucket") @dipper = Puppet::FileBucket::Dipper.new(:Path => file_bucket) #First File file1 = make_tmp_file(plaintext) real_path = Pathname.new(file1).realpath expect(digest(plaintext)).to eq(checksum) expect(@dipper.backup(file1)).to eq(checksum) expected_list1_1 = /#{checksum} \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} #{real_path}\n/ File.open(file1, 'w') {|f| f.write("Blahhhh")} new_checksum = digest("Blahhhh") expect(@dipper.backup(file1)).to eq(new_checksum) expected_list1_2 = /#{new_checksum} \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} #{real_path}\n/ #Second File content = "DummyFileWithNonSenseTextInIt" file2 = make_tmp_file(content) real_path = Pathname.new(file2).realpath checksum = digest(content) expect(@dipper.backup(file2)).to eq(checksum) expected_list2 = /#{checksum} \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} #{real_path}\n/ #Third file : Same as the first one with a different path file3 = make_tmp_file(plaintext) real_path = Pathname.new(file3).realpath checksum = digest(plaintext) expect(digest(plaintext)).to eq(checksum) expect(@dipper.backup(file3)).to eq(checksum) expected_list3 = /#{checksum} \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} #{real_path}\n/ result = @dipper.list(nil, nil) expect(result).to match(expected_list1_1) expect(result).to match(expected_list1_2) expect(result).to match(expected_list2) expect(result).to match(expected_list3) end it "should filter with the provided dates" do if Puppet::Util::Platform.windows? && digest_algorithm == "sha512" skip "PUP-8257: Skip file bucket test on windows for #{digest_algorithm} due to long path names" end Puppet[:bucketdir] = "/my/bucket" file_bucket = tmpdir("bucket") twentyminutes=60*20 thirtyminutes=60*30 onehour=60*60 twohours=onehour*2 threehours=onehour*3 # First File created now @dipper = Puppet::FileBucket::Dipper.new(:Path => file_bucket) file1 = make_tmp_file(plaintext) real_path = Pathname.new(file1).realpath expect(digest(plaintext)).to eq(checksum) expect(@dipper.backup(file1)).to eq(checksum) expected_list1 = /#{checksum} \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} #{real_path}\n/ # Second File created an hour ago content = "DummyFileWithNonSenseTextInIt" file2 = make_tmp_file(content) real_path = Pathname.new(file2).realpath checksum = digest(content) expect(@dipper.backup(file2)).to eq(checksum) # Modify mtime of the second file to be an hour ago onehourago = Time.now - onehour bucketed_paths_file = Dir.glob("#{file_bucket}/**/#{checksum}/paths") FileUtils.touch(bucketed_paths_file, :mtime => onehourago) expected_list2 = /#{checksum} \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} #{real_path}\n/ now = Time.now #Future expect(@dipper.list((now + threehours).strftime("%F %T"), nil )).to eq("") #Epoch -> Future = Everything (Sorted (desc) by date) expect(@dipper.list(nil, (now + twohours).strftime("%F %T"))).to match(expected_list1) expect(@dipper.list(nil, (now + twohours).strftime("%F %T"))).to match(expected_list2) #Now+1sec -> Future = Nothing expect(@dipper.list((now + 1).strftime("%F %T"), (now + twohours).strftime("%F %T"))).to eq("") #Now-30mins -> Now-20mins = Nothing expect(@dipper.list((now - thirtyminutes).strftime("%F %T"), (now - twentyminutes).strftime("%F %T"))).to eq("") #Now-2hours -> Now-30mins = Second file only expect(@dipper.list((now - twohours).strftime("%F %T"), (now - thirtyminutes).strftime("%F %T"))).to match(expected_list2) expect(@dipper.list((now - twohours).strftime("%F %T"), (now - thirtyminutes).strftime("%F %T"))).not_to match(expected_list1) #Now-30minutes -> Now = First file only expect(@dipper.list((now - thirtyminutes).strftime("%F %T"), now.strftime("%F %T"))).to match(expected_list1) expect(@dipper.list((now - thirtyminutes).strftime("%F %T"), now.strftime("%F %T"))).not_to match(expected_list2) end end end describe "when diffing on a remote filebucket" do describe "in non-windows environments", :unless => Puppet.features.microsoft_windows? do with_digest_algorithms do it "should fail in an informative way when one or more checksum doesn't exists" do @dipper = Puppet::FileBucket::Dipper.new(:Server => "puppetmaster", :Port => "31337") wrong_checksum = "DEADBEEF" Puppet::FileBucketFile::Rest.any_instance.expects(:find).returns(nil) expect { @dipper.diff(wrong_checksum, "WEIRDCKSM", nil, nil) }.to raise_error(Puppet::Error, "Failed to diff files") end it "should properly diff files on the filebucket" do @dipper = Puppet::FileBucket::Dipper.new(:Server => "puppetmaster", :Port => "31337") Puppet::FileBucketFile::Rest.any_instance.expects(:find).returns("Probably valid diff") expect(@dipper.diff("checksum1", "checksum2", nil, nil)).to eq("Probably valid diff") end end end describe "in windows environment", :if => Puppet.features.microsoft_windows? do it "should fail in an informative way when trying to diff" do @dipper = Puppet::FileBucket::Dipper.new(:Server => "puppetmaster", :Port => "31337") wrong_checksum = "DEADBEEF" expect { @dipper.diff(wrong_checksum, "WEIRDCKSM", nil, nil) }.to raise_error(RuntimeError, "Diff is not supported on this platform") expect { @dipper.diff(wrong_checksum, nil, nil, nil) }.to raise_error(RuntimeError, "Diff is not supported on this platform") end end end describe "listing files in remote filebucket" do it "is not allowed" do @dipper = Puppet::FileBucket::Dipper.new(:Server => "puppetmaster", :Port=> "31337") expect { @dipper.list(nil, nil) }.to raise_error(Puppet::Error, "Listing remote file buckets is not allowed") end end describe "backing up and retrieving local files" do with_digest_algorithms do it "should backup files to a local bucket" do if Puppet::Util::Platform.windows? && digest_algorithm == "sha512" skip "PUP-8257: Skip file bucket test on windows for #{digest_algorithm} due to long path names" end Puppet[:bucketdir] = "/non/existent/directory" file_bucket = tmpdir("bucket") @dipper = Puppet::FileBucket::Dipper.new(:Path => file_bucket) file = make_tmp_file(plaintext) expect(digest(plaintext)).to eq(checksum) expect(@dipper.backup(file)).to eq(checksum) expect(Puppet::FileSystem.exist?("#{file_bucket}/#{bucket_dir}/contents")).to eq(true) end it "should not backup a file that is already in the bucket" do @dipper = Puppet::FileBucket::Dipper.new(:Path => "/my/bucket") file = make_tmp_file(plaintext) Puppet::FileBucket::File.indirection.expects(:head).with( regexp_matches(%r{#{digest_algorithm}/#{checksum}}), :bucket_path => "/my/bucket" ).returns true Puppet::FileBucket::File.indirection.expects(:save).never expect(@dipper.backup(file)).to eq(checksum) end it "should retrieve files from a local bucket" do @dipper = Puppet::FileBucket::Dipper.new(:Path => "/my/bucket") request = nil Puppet::FileBucketFile::File.any_instance.expects(:find).with{ |r| request = r }.once.returns(Puppet::FileBucket::File.new(plaintext)) expect(@dipper.getfile(checksum)).to eq(plaintext) expect(request.key).to eq("#{digest_algorithm}/#{checksum}") end end end describe "backing up and retrieving remote files" do with_digest_algorithms do it "should backup files to a remote server" do @dipper = Puppet::FileBucket::Dipper.new(:Server => "puppetmaster", :Port => "31337") file = make_tmp_file(plaintext) real_path = Pathname.new(file).realpath request1 = nil request2 = nil Puppet::FileBucketFile::Rest.any_instance.expects(:head).with { |r| request1 = r }.once.returns(nil) Puppet::FileBucketFile::Rest.any_instance.expects(:save).with { |r| request2 = r }.once expect(@dipper.backup(file)).to eq(checksum) [request1, request2].each do |r| expect(r.server).to eq('puppetmaster') expect(r.port).to eq(31337) expect(r.key).to eq("#{digest_algorithm}/#{checksum}/#{real_path}") end end it "should retrieve files from a remote server" do @dipper = Puppet::FileBucket::Dipper.new(:Server => "puppetmaster", :Port => "31337") request = nil Puppet::FileBucketFile::Rest.any_instance.expects(:find).with { |r| request = r }.returns(Puppet::FileBucket::File.new(plaintext)) expect(@dipper.getfile(checksum)).to eq(plaintext) expect(request.server).to eq('puppetmaster') expect(request.port).to eq(31337) expect(request.key).to eq("#{digest_algorithm}/#{checksum}") end end end describe "#restore" do describe "when restoring from a remote server" do let(:klass) { Puppet::FileBucketFile::Rest } let(:server) { "puppetmaster" } let(:port) { 31337 } it_behaves_like "a restorable file" do let (:dipper) { Puppet::FileBucket::Dipper.new(:Server => server, :Port => port.to_s) } end end describe "when restoring from a local server" do let(:klass) { Puppet::FileBucketFile::File } let(:server) { nil } let(:port) { nil } it_behaves_like "a restorable file" do let (:dipper) { Puppet::FileBucket::Dipper.new(:Path => "/my/bucket") } end end end end puppet-5.5.10/spec/unit/file_serving/0000755005276200011600000000000013417162176017404 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/file_serving/configuration/0000755005276200011600000000000013417162176022253 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/file_serving/configuration/parser_spec.rb0000644005276200011600000001341213417161721025102 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/file_serving/configuration/parser' module FSConfigurationParserTesting def write_config_file(content) # We want an array, but we actually want our carriage returns on all of it. File.open(@path, 'w') {|f| f.puts content} end end describe Puppet::FileServing::Configuration::Parser do include PuppetSpec::Files before :each do @path = tmpfile('fileserving_config') FileUtils.touch(@path) @parser = Puppet::FileServing::Configuration::Parser.new(@path) end describe Puppet::FileServing::Configuration::Parser, " when parsing" do include FSConfigurationParserTesting it "should allow comments" do write_config_file("# this is a comment\n") expect { @parser.parse }.not_to raise_error end it "should allow blank lines" do write_config_file("\n") expect { @parser.parse }.not_to raise_error end it "should return a hash of the created mounts" do mount1 = mock 'one', :validate => true mount2 = mock 'two', :validate => true Puppet::FileServing::Mount::File.expects(:new).with("one").returns(mount1) Puppet::FileServing::Mount::File.expects(:new).with("two").returns(mount2) write_config_file "[one]\n[two]\n" result = @parser.parse expect(result["one"]).to equal(mount1) expect(result["two"]).to equal(mount2) end it "should only allow mount names that are alphanumeric plus dashes" do write_config_file "[a*b]\n" expect { @parser.parse }.to raise_error(ArgumentError) end it "should fail if the value for path/allow/deny starts with an equals sign" do write_config_file "[one]\npath = /testing" expect { @parser.parse }.to raise_error(ArgumentError) end it "should validate each created mount" do mount1 = mock 'one' Puppet::FileServing::Mount::File.expects(:new).with("one").returns(mount1) write_config_file "[one]\n" mount1.expects(:validate) @parser.parse end it "should fail if any mount does not pass validation" do mount1 = mock 'one' Puppet::FileServing::Mount::File.expects(:new).with("one").returns(mount1) write_config_file "[one]\n" mount1.expects(:validate).raises RuntimeError expect { @parser.parse }.to raise_error(RuntimeError) end it "should return comprehensible error message, if invalid line detected" do write_config_file "[one]\n\n\x01path /etc/puppetlabs/puppet/files\n\x01allow *\n" expect { @parser.parse }.to raise_error(ArgumentError, /Invalid entry at \(file: .*, line: 3\): .*/) end end describe Puppet::FileServing::Configuration::Parser, " when parsing mount attributes" do include FSConfigurationParserTesting before do @mount = stub 'testmount', :name => "one", :validate => true Puppet::FileServing::Mount::File.expects(:new).with("one").returns(@mount) @parser.stubs(:add_modules_mount) end it "should set the mount path to the path attribute from that section" do write_config_file "[one]\npath /some/path\n" @mount.expects(:path=).with("/some/path") @parser.parse end [:allow,:deny].each { |acl_type| it "should support inline comments in #{acl_type}" do write_config_file "[one]\n#{acl_type} something \# will it work?\n" @mount.expects(:info) @mount.expects(acl_type).with("something") @parser.parse end it "should tell the mount to #{acl_type} from ACLs with varying spacing around commas" do write_config_file "[one]\n#{acl_type} someone,sometwo, somethree , somefour ,somefive\n" @mount.expects(:info).times(5) @mount.expects(acl_type).times(5).with(any_of('someone','sometwo','somethree','somefour','somefive')) @parser.parse end # each ip, with glob in the various octet positions ['100','4','42','*'].permutation.map {|permutes| permutes.join('.') }.each { |ip_pattern| it "should tell the mount to #{acl_type} from ACLs with glob at #{ip_pattern}" do write_config_file "[one]\n#{acl_type} #{ip_pattern}\n" @mount.expects(:info) @mount.expects(acl_type).with(ip_pattern) @parser.parse end } } it "should return comprehensible error message, if failed on invalid attribute" do write_config_file "[one]\ndo something\n" expect { @parser.parse }.to raise_error(ArgumentError, /Invalid argument 'do' at \(file: .*, line: 2\)/) end end describe Puppet::FileServing::Configuration::Parser, " when parsing the modules mount" do include FSConfigurationParserTesting before do @mount = stub 'modulesmount', :name => "modules", :validate => true end it "should create an instance of the Modules Mount class" do write_config_file "[modules]\n" Puppet::FileServing::Mount::Modules.expects(:new).with("modules").returns @mount @parser.parse end it "should warn if a path is set" do write_config_file "[modules]\npath /some/path\n" Puppet::FileServing::Mount::Modules.expects(:new).with("modules").returns(@mount) Puppet.expects(:warning) @parser.parse end end describe Puppet::FileServing::Configuration::Parser, " when parsing the plugins mount" do include FSConfigurationParserTesting before do @mount = stub 'pluginsmount', :name => "plugins", :validate => true end it "should create an instance of the Plugins Mount class" do write_config_file "[plugins]\n" Puppet::FileServing::Mount::Plugins.expects(:new).with("plugins").returns @mount @parser.parse end it "should warn if a path is set" do write_config_file "[plugins]\npath /some/path\n" Puppet.expects(:warning) @parser.parse end end end puppet-5.5.10/spec/unit/file_serving/configuration_spec.rb0000644005276200011600000002124213417161721023606 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/file_serving/configuration' describe Puppet::FileServing::Configuration do include PuppetSpec::Files before :each do @path = make_absolute("/path/to/configuration/file.conf") Puppet[:trace] = false Puppet[:fileserverconfig] = @path end after :each do Puppet::FileServing::Configuration.instance_variable_set(:@configuration, nil) end it "should make :new a private method" do expect { Puppet::FileServing::Configuration.new }.to raise_error(NoMethodError, /private method `new' called/) end it "should return the same configuration each time 'configuration' is called" do expect(Puppet::FileServing::Configuration.configuration).to equal(Puppet::FileServing::Configuration.configuration) end describe "when initializing" do it "should work without a configuration file" do Puppet::FileSystem.stubs(:exist?).with(@path).returns(false) expect { Puppet::FileServing::Configuration.configuration }.to_not raise_error end it "should parse the configuration file if present" do Puppet::FileSystem.stubs(:exist?).with(@path).returns(true) @parser = mock 'parser' @parser.expects(:parse).returns({}) Puppet::FileServing::Configuration::Parser.stubs(:new).returns(@parser) Puppet::FileServing::Configuration.configuration end it "should determine the path to the configuration file from the Puppet settings" do Puppet::FileServing::Configuration.configuration end end describe "when parsing the configuration file" do before do Puppet::FileSystem.stubs(:exist?).with(@path).returns(true) @parser = mock 'parser' Puppet::FileServing::Configuration::Parser.stubs(:new).returns(@parser) end it "should set the mount list to the results of parsing" do @parser.expects(:parse).returns("one" => mock("mount")) config = Puppet::FileServing::Configuration.configuration expect(config.mounted?("one")).to be_truthy end it "should not raise exceptions" do @parser.expects(:parse).raises(ArgumentError) expect { Puppet::FileServing::Configuration.configuration }.to_not raise_error end it "should replace the existing mount list with the results of reparsing" do @parser.expects(:parse).returns("one" => mock("mount")) config = Puppet::FileServing::Configuration.configuration expect(config.mounted?("one")).to be_truthy # Now parse again @parser.expects(:parse).returns("two" => mock('other')) config.send(:readconfig, false) expect(config.mounted?("one")).to be_falsey expect(config.mounted?("two")).to be_truthy end it "should not replace the mount list until the file is entirely parsed successfully" do @parser.expects(:parse).returns("one" => mock("mount")) @parser.expects(:parse).raises(ArgumentError) config = Puppet::FileServing::Configuration.configuration # Now parse again, so the exception gets thrown config.send(:readconfig, false) expect(config.mounted?("one")).to be_truthy end it "should add modules, plugins, and tasks mounts even if the file does not exist" do Puppet::FileSystem.expects(:exist?).returns false # the file doesn't exist config = Puppet::FileServing::Configuration.configuration expect(config.mounted?("modules")).to be_truthy expect(config.mounted?("plugins")).to be_truthy expect(config.mounted?("tasks")).to be_truthy end it "should allow all access to modules, plugins, and tasks if no fileserver.conf exists" do Puppet::FileSystem.expects(:exist?).returns false # the file doesn't exist modules = stub 'modules', :empty? => true Puppet::FileServing::Mount::Modules.stubs(:new).returns(modules) modules.expects(:allow).with('*') plugins = stub 'plugins', :empty? => true Puppet::FileServing::Mount::Plugins.stubs(:new).returns(plugins) plugins.expects(:allow).with('*') tasks = stub 'tasks', :empty? => true Puppet::FileServing::Mount::Tasks.stubs(:new).returns(tasks) tasks.expects(:allow).with('*') Puppet::FileServing::Configuration.configuration end it "should not allow access from all to modules, plugins, and tasks if the fileserver.conf provided some rules" do Puppet::FileSystem.expects(:exist?).returns false # the file doesn't exist modules = stub 'modules', :empty? => false Puppet::FileServing::Mount::Modules.stubs(:new).returns(modules) modules.expects(:allow).with('*').never plugins = stub 'plugins', :empty? => false Puppet::FileServing::Mount::Plugins.stubs(:new).returns(plugins) plugins.expects(:allow).with('*').never tasks = stub 'tasks', :empty? => false Puppet::FileServing::Mount::Tasks.stubs(:new).returns(tasks) tasks.expects(:allow).with('*').never Puppet::FileServing::Configuration.configuration end it "should add modules, plugins, and tasks mounts even if they are not returned by the parser" do @parser.expects(:parse).returns("one" => mock("mount")) Puppet::FileSystem.expects(:exist?).returns true # the file doesn't exist config = Puppet::FileServing::Configuration.configuration expect(config.mounted?("modules")).to be_truthy expect(config.mounted?("plugins")).to be_truthy expect(config.mounted?("tasks")).to be_truthy end end describe "when finding the specified mount" do it "should choose the named mount if one exists" do config = Puppet::FileServing::Configuration.configuration config.expects(:mounts).returns("one" => "foo") expect(config.find_mount("one", mock('env'))).to eq("foo") end it "should return nil if there is no such named mount" do config = Puppet::FileServing::Configuration.configuration env = mock 'environment' mount = mock 'mount' config.stubs(:mounts).returns("modules" => mount) expect(config.find_mount("foo", env)).to be_nil end end describe "#split_path" do let(:config) { Puppet::FileServing::Configuration.configuration } let(:request) { stub 'request', :key => "foo/bar/baz", :options => {}, :node => nil, :environment => mock("env") } before do config.stubs(:find_mount) end it "should reread the configuration" do config.expects(:readconfig) config.split_path(request) end it "should treat the first field of the URI path as the mount name" do config.expects(:find_mount).with { |name, node| name == "foo" } config.split_path(request) end it "should fail if the mount name is not alpha-numeric" do request.expects(:key).returns "foo&bar/asdf" expect { config.split_path(request) }.to raise_error(ArgumentError) end it "should support dashes in the mount name" do request.expects(:key).returns "foo-bar/asdf" expect { config.split_path(request) }.to_not raise_error end it "should use the mount name and environment to find the mount" do config.expects(:find_mount).with { |name, env| name == "foo" and env == request.environment } request.stubs(:node).returns("mynode") config.split_path(request) end it "should return nil if the mount cannot be found" do config.expects(:find_mount).returns nil expect(config.split_path(request)).to be_nil end it "should return the mount and the relative path if the mount is found" do mount = stub 'mount', :name => "foo" config.expects(:find_mount).returns mount expect(config.split_path(request)).to eq([mount, "bar/baz"]) end it "should remove any double slashes" do request.stubs(:key).returns "foo/bar//baz" mount = stub 'mount', :name => "foo" config.expects(:find_mount).returns mount expect(config.split_path(request)).to eq([mount, "bar/baz"]) end it "should fail if the path contains .." do request.stubs(:key).returns 'module/foo/../../bar' expect do config.split_path(request) end.to raise_error(ArgumentError, /Invalid relative path/) end it "should return the relative path as nil if it is an empty string" do request.expects(:key).returns "foo" mount = stub 'mount', :name => "foo" config.expects(:find_mount).returns mount expect(config.split_path(request)).to eq([mount, nil]) end it "should add 'modules/' to the relative path if the modules mount is used but not specified, for backward compatibility" do request.expects(:key).returns "foo/bar" mount = stub 'mount', :name => "modules" config.expects(:find_mount).returns mount expect(config.split_path(request)).to eq([mount, "foo/bar"]) end end end puppet-5.5.10/spec/unit/file_serving/content_spec.rb0000644005276200011600000000664513417161721022423 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/file_serving/content' describe Puppet::FileServing::Content do let(:path) { File.expand_path('/path') } it "should be a subclass of Base" do expect(Puppet::FileServing::Content.superclass).to equal(Puppet::FileServing::Base) end it "should indirect file_content" do expect(Puppet::FileServing::Content.indirection.name).to eq(:file_content) end it "should only support the binary format" do expect(Puppet::FileServing::Content.supported_formats).to eq([:binary]) end it "should have a method for collecting its attributes" do expect(Puppet::FileServing::Content.new(path)).to respond_to(:collect) end it "should not retrieve and store its contents when its attributes are collected" do content = Puppet::FileServing::Content.new(path) File.expects(:read).with(path).never content.collect expect(content.instance_variable_get("@content")).to be_nil end it "should have a method for setting its content" do content = Puppet::FileServing::Content.new(path) expect(content).to respond_to(:content=) end it "should make content available when set externally" do content = Puppet::FileServing::Content.new(path) content.content = "foo/bar" expect(content.content).to eq("foo/bar") end it "should be able to create a content instance from binary file contents" do expect(Puppet::FileServing::Content).to respond_to(:from_binary) end it "should create an instance with a fake file name and correct content when converting from binary" do instance = mock 'instance' Puppet::FileServing::Content.expects(:new).with("/this/is/a/fake/path").returns instance instance.expects(:content=).with "foo/bar" expect(Puppet::FileServing::Content.from_binary("foo/bar")).to equal(instance) end it "should return an opened File when converted to binary" do content = Puppet::FileServing::Content.new(path) File.expects(:new).with(path, "rb").returns :file expect(content.to_binary).to eq(:file) end end describe Puppet::FileServing::Content, "when returning the contents" do let(:path) { File.expand_path('/my/path') } let(:content) { Puppet::FileServing::Content.new(path, :links => :follow) } it "should fail if the file is a symlink and links are set to :manage" do content.links = :manage Puppet::FileSystem.expects(:lstat).with(path).returns stub("stat", :ftype => "symlink") expect { content.content }.to raise_error(ArgumentError) end it "should fail if a path is not set" do expect { content.content }.to raise_error(Errno::ENOENT) end it "should raise Errno::ENOENT if the file is absent" do content.path = File.expand_path("/there/is/absolutely/no/chance/that/this/path/exists") expect { content.content }.to raise_error(Errno::ENOENT) end it "should return the contents of the path if the file exists" do Puppet::FileSystem.expects(:stat).with(path).returns(stub('stat', :ftype => 'file')) Puppet::FileSystem.expects(:binread).with(path).returns(:mycontent) expect(content.content).to eq(:mycontent) end it "should cache the returned contents" do Puppet::FileSystem.expects(:stat).with(path).returns(stub('stat', :ftype => 'file')) Puppet::FileSystem.expects(:binread).with(path).returns(:mycontent) content.content # The second run would throw a failure if the content weren't being cached. content.content end end puppet-5.5.10/spec/unit/file_serving/fileset_spec.rb0000644005276200011600000003277013417161721022402 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/file_serving/fileset' describe Puppet::FileServing::Fileset do include PuppetSpec::Files let(:somefile) { make_absolute("/some/file") } context "when initializing" do it "requires a path" do expect { Puppet::FileServing::Fileset.new }.to raise_error(ArgumentError) end it "fails if its path is not fully qualified" do expect { Puppet::FileServing::Fileset.new("some/file") }.to raise_error(ArgumentError, "Fileset paths must be fully qualified: some/file") end it "removes a trailing file path separator" do path_with_separator = "#{somefile}#{File::SEPARATOR}" Puppet::FileSystem.expects(:lstat).with(somefile).returns stub('stat') fileset = Puppet::FileServing::Fileset.new(path_with_separator) expect(fileset.path).to eq(somefile) end it "can be created from the root directory" do path = File.expand_path(File::SEPARATOR) Puppet::FileSystem.expects(:lstat).with(path).returns stub('stat') fileset = Puppet::FileServing::Fileset.new(path) expect(fileset.path).to eq(path) end it "fails if its path does not exist" do Puppet::FileSystem.expects(:lstat).with(somefile).raises(Errno::ENOENT) expect { Puppet::FileServing::Fileset.new(somefile) }.to raise_error(ArgumentError, "Fileset paths must exist") end it "accepts a 'recurse' option" do Puppet::FileSystem.expects(:lstat).with(somefile).returns stub('stat') set = Puppet::FileServing::Fileset.new(somefile, :recurse => true) expect(set.recurse).to be_truthy end it "accepts a 'recurselimit' option" do Puppet::FileSystem.expects(:lstat).with(somefile).returns stub('stat') set = Puppet::FileServing::Fileset.new(somefile, :recurselimit => 3) expect(set.recurselimit).to eq(3) end it "accepts an 'ignore' option" do Puppet::FileSystem.expects(:lstat).with(somefile).returns stub('stat') set = Puppet::FileServing::Fileset.new(somefile, :ignore => ".svn") expect(set.ignore).to eq([".svn"]) end it "accepts a 'links' option" do Puppet::FileSystem.expects(:lstat).with(somefile).returns stub('stat') set = Puppet::FileServing::Fileset.new(somefile, :links => :manage) expect(set.links).to eq(:manage) end it "accepts a 'checksum_type' option" do Puppet::FileSystem.expects(:lstat).with(somefile).returns stub('stat') set = Puppet::FileServing::Fileset.new(somefile, :checksum_type => :test) expect(set.checksum_type).to eq(:test) end it "fails if 'links' is set to anything other than :manage or :follow" do expect { Puppet::FileServing::Fileset.new(somefile, :links => :whatever) }.to raise_error(ArgumentError, "Invalid :links value 'whatever'") end it "defaults to 'false' for recurse" do Puppet::FileSystem.expects(:lstat).with(somefile).returns stub('stat') expect(Puppet::FileServing::Fileset.new(somefile).recurse).to eq(false) end it "defaults to :infinite for recurselimit" do Puppet::FileSystem.expects(:lstat).with(somefile).returns stub('stat') expect(Puppet::FileServing::Fileset.new(somefile).recurselimit).to eq(:infinite) end it "defaults to an empty ignore list" do Puppet::FileSystem.expects(:lstat).with(somefile).returns stub('stat') expect(Puppet::FileServing::Fileset.new(somefile).ignore).to eq([]) end it "defaults to :manage for links" do Puppet::FileSystem.expects(:lstat).with(somefile).returns stub('stat') expect(Puppet::FileServing::Fileset.new(somefile).links).to eq(:manage) end describe "using an indirector request" do let(:values) { { :links => :manage, :ignore => %w{a b}, :recurse => true, :recurselimit => 1234 } } before :each do Puppet::FileSystem.expects(:lstat).with(somefile).returns stub('stat') end [:recurse, :recurselimit, :ignore, :links].each do |option| it "passes the #{option} option on to the fileset if present" do request = Puppet::Indirector::Request.new(:file_serving, :find, "foo", nil, {option => values[option]}) expect(Puppet::FileServing::Fileset.new(somefile, request).send(option)).to eq(values[option]) end end it "converts the integer as a string to their integer counterpart when setting options" do request = Puppet::Indirector::Request.new(:file_serving, :find, "foo", nil, {:recurselimit => "1234"}) expect(Puppet::FileServing::Fileset.new(somefile, request).recurselimit).to eq(1234) end it "converts the string 'true' to the boolean true when setting options" do request = Puppet::Indirector::Request.new(:file_serving, :find, "foo", nil, {:recurse => "true"}) expect(Puppet::FileServing::Fileset.new(somefile, request).recurse).to eq(true) end it "converts the string 'false' to the boolean false when setting options" do request = Puppet::Indirector::Request.new(:file_serving, :find, "foo", nil, {:recurse => "false"}) expect(Puppet::FileServing::Fileset.new(somefile, request).recurse).to eq(false) end end end context "when recursing" do before do @path = make_absolute("/my/path") Puppet::FileSystem.stubs(:lstat).with(@path).returns stub('stat', :directory? => true) @fileset = Puppet::FileServing::Fileset.new(@path) @dirstat = stub 'dirstat', :directory? => true @filestat = stub 'filestat', :directory? => false end def mock_dir_structure(path, stat_method = :lstat) Puppet::FileSystem.stubs(stat_method).with(path).returns @dirstat # Keep track of the files we're stubbing. @files = %w{.} top_names = %w{one two .svn CVS} sub_names = %w{file1 file2 .svn CVS 0 false} Dir.stubs(:entries).with(path, encoding: Encoding::UTF_8).returns(top_names) top_names.each do |subdir| @files << subdir # relative path subpath = File.join(path, subdir) Puppet::FileSystem.stubs(stat_method).with(subpath).returns @dirstat Dir.stubs(:entries).with(subpath, encoding: Encoding::UTF_8).returns(sub_names) sub_names.each do |file| @files << File.join(subdir, file) # relative path subfile_path = File.join(subpath, file) Puppet::FileSystem.stubs(stat_method).with(subfile_path).returns(@filestat) end end end MockStat = Struct.new(:path, :directory) do # struct doesn't support thing ending in ? def directory? directory end end MockDirectory = Struct.new(:name, :entries) do def mock(base_path) extend Mocha::API path = File.join(base_path, name) Puppet::FileSystem.stubs(:lstat).with(path).returns MockStat.new(path, true) Dir.stubs(:entries).with(path, encoding: Encoding::UTF_8).returns(['.', '..'] + entries.map(&:name)) entries.each do |entry| entry.mock(path) end end end MockFile = Struct.new(:name) do def mock(base_path) extend Mocha::API path = File.join(base_path, name) Puppet::FileSystem.stubs(:lstat).with(path).returns MockStat.new(path, false) end end it "doesn't ignore pending directories when the last entry at the top level is a file" do structure = MockDirectory.new('path', [MockDirectory.new('dir1', [MockDirectory.new('a', [MockFile.new('f')])]), MockFile.new('file')]) structure.mock(make_absolute('/your')) fileset = Puppet::FileServing::Fileset.new(make_absolute('/your/path')) fileset.recurse = true fileset.links = :manage expect(fileset.files).to eq([".", "dir1", "file", "dir1/a", "dir1/a/f"]) end it "recurses through the whole file tree if :recurse is set to 'true'" do mock_dir_structure(@path) @fileset.recurse = true expect(@fileset.files.sort).to eq(@files.sort) end it "does not recurse if :recurse is set to 'false'" do mock_dir_structure(@path) @fileset.recurse = false expect(@fileset.files).to eq(%w{.}) end it "recurses to the level set by :recurselimit" do mock_dir_structure(@path) @fileset.recurse = true @fileset.recurselimit = 1 expect(@fileset.files).to eq(%w{. one two .svn CVS}) end it "ignores the '.' and '..' directories in subdirectories" do mock_dir_structure(@path) @fileset.recurse = true expect(@fileset.files.sort).to eq(@files.sort) end it "does not fail if the :ignore value provided is nil" do mock_dir_structure(@path) @fileset.recurse = true @fileset.ignore = nil expect { @fileset.files }.to_not raise_error end it "ignores files that match a single pattern in the ignore list" do mock_dir_structure(@path) @fileset.recurse = true @fileset.ignore = ".svn" expect(@fileset.files.find { |file| file.include?(".svn") }).to be_nil end it "ignores files that match any of multiple patterns in the ignore list" do mock_dir_structure(@path) @fileset.recurse = true @fileset.ignore = %w{.svn CVS} expect(@fileset.files.find { |file| file.include?(".svn") or file.include?("CVS") }).to be_nil end it "ignores files that match a pattern given as a number" do mock_dir_structure(@path) @fileset.recurse = true @fileset.ignore = [0] expect(@fileset.files.find { |file| file.include?("0") }).to be_nil end it "ignores files that match a pattern given as a boolean" do mock_dir_structure(@path) @fileset.recurse = true @fileset.ignore = [false] expect(@fileset.files.find { |file| file.include?("false") }).to be_nil end it "uses Puppet::FileSystem#stat if :links is set to :follow" do mock_dir_structure(@path, :stat) @fileset.recurse = true @fileset.links = :follow expect(@fileset.files.sort).to eq(@files.sort) end it "uses Puppet::FileSystem#lstat if :links is set to :manage" do mock_dir_structure(@path, :lstat) @fileset.recurse = true @fileset.links = :manage expect(@fileset.files.sort).to eq(@files.sort) end it "works when paths have regexp significant characters" do @path = make_absolute("/my/path/rV1x2DafFr0R6tGG+1bbk++++TM") stat = stub('dir_stat', :directory? => true) Puppet::FileSystem.expects(:lstat).with(@path).returns stub(@path, :stat => stat, :lstat => stat) @fileset = Puppet::FileServing::Fileset.new(@path) mock_dir_structure(@path) @fileset.recurse = true expect(@fileset.files.sort).to eq(@files.sort) end end it "manages the links to missing files" do path = make_absolute("/my/path") stat = stub 'stat', :directory? => true Puppet::FileSystem.expects(:stat).with(path).returns stat Puppet::FileSystem.expects(:lstat).with(path).returns stat link_path = File.join(path, "mylink") Puppet::FileSystem.expects(:stat).with(link_path).raises(Errno::ENOENT) Dir.stubs(:entries).with(path, encoding: Encoding::UTF_8).returns(["mylink"]) fileset = Puppet::FileServing::Fileset.new(path) fileset.links = :follow fileset.recurse = true expect(fileset.files.sort).to eq(%w{. mylink}.sort) end context "when merging other filesets" do before do @paths = [make_absolute("/first/path"), make_absolute("/second/path"), make_absolute("/third/path")] Puppet::FileSystem.stubs(:lstat).returns stub('stat', :directory? => false) @filesets = @paths.collect do |path| Puppet::FileSystem.stubs(:lstat).with(path).returns stub('stat', :directory? => true) Puppet::FileServing::Fileset.new(path, :recurse => true) end Dir.stubs(:entries).returns [] end it "returns a hash of all files in each fileset with the value being the base path" do Dir.expects(:entries).with(make_absolute("/first/path"), encoding: Encoding::UTF_8).returns(%w{one uno}) Dir.expects(:entries).with(make_absolute("/second/path"), encoding: Encoding::UTF_8).returns(%w{two dos}) Dir.expects(:entries).with(make_absolute("/third/path"), encoding: Encoding::UTF_8).returns(%w{three tres}) expect(Puppet::FileServing::Fileset.merge(*@filesets)).to eq({ "." => make_absolute("/first/path"), "one" => make_absolute("/first/path"), "uno" => make_absolute("/first/path"), "two" => make_absolute("/second/path"), "dos" => make_absolute("/second/path"), "three" => make_absolute("/third/path"), "tres" => make_absolute("/third/path"), }) end it "includes the base directory from the first fileset" do Dir.expects(:entries).with(make_absolute("/first/path"), encoding: Encoding::UTF_8).returns(%w{one}) Dir.expects(:entries).with(make_absolute("/second/path"), encoding: Encoding::UTF_8).returns(%w{two}) expect(Puppet::FileServing::Fileset.merge(*@filesets)["."]).to eq(make_absolute("/first/path")) end it "uses the base path of the first found file when relative file paths conflict" do Dir.expects(:entries).with(make_absolute("/first/path"), encoding: Encoding::UTF_8).returns(%w{one}) Dir.expects(:entries).with(make_absolute("/second/path"), encoding: Encoding::UTF_8).returns(%w{one}) expect(Puppet::FileServing::Fileset.merge(*@filesets)["one"]).to eq(make_absolute("/first/path")) end end end puppet-5.5.10/spec/unit/file_serving/http_metadata_spec.rb0000644005276200011600000000572313417161721023564 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/file_serving/http_metadata' require 'matchers/json' require 'net/http' require 'digest' describe Puppet::FileServing::HttpMetadata do let(:foobar) { File.expand_path('/foo/bar') } it "should be a subclass of Metadata" do expect( described_class.superclass ).to be Puppet::FileServing::Metadata end describe "when initializing" do let(:http_response) { Net::HTTPOK.new(1.0, '200', 'OK') } it "can be instantiated from a HTTP response object" do expect( described_class.new(http_response) ).to_not be_nil end it "represents a plain file" do expect( described_class.new(http_response).ftype ).to eq 'file' end it "carries no information on owner, group and mode" do metadata = described_class.new(http_response) expect( metadata.owner ).to be_nil expect( metadata.group ).to be_nil expect( metadata.mode ).to be_nil end context "with no Last-Modified or Content-MD5 header from the server" do before do http_response.stubs(:[]).with('last-modified').returns nil http_response.stubs(:[]).with('content-md5').returns nil end it "should use :mtime as the checksum type, based on current time" do # Stringifying Time.now does some rounding; do so here so we don't end up with a time # that's greater than the stringified version returned by collect. time = Time.parse(Time.now.to_s) metadata = described_class.new(http_response) metadata.collect expect( metadata.checksum_type ).to eq :mtime checksum = metadata.checksum expect( checksum[0...7] ).to eq '{mtime}' expect( Time.parse(checksum[7..-1]) ).to be >= time end end context "with a Last-Modified header from the server" do let(:time) { Time.now.utc } before do http_response.stubs(:[]).with('content-md5').returns nil end it "should use :mtime as the checksum type, based on Last-Modified" do # HTTP uses "GMT" not "UTC" http_response.stubs(:[]).with('last-modified').returns(time.strftime("%a, %d %b %Y %T GMT")) metadata = described_class.new(http_response) metadata.collect expect( metadata.checksum_type ).to eq :mtime expect( metadata.checksum ).to eq "{mtime}#{time.to_time.utc}" end end context "with a Content-MD5 header being received" do let(:input) { Time.now.to_s } let(:base64) { Digest::MD5.new.base64digest input } let(:hex) { Digest::MD5.new.hexdigest input } before do http_response.stubs(:[]).with('last-modified').returns nil http_response.stubs(:[]).with('content-md5').returns base64 end it "should use the md5 checksum" do metadata = described_class.new(http_response) metadata.collect expect( metadata.checksum_type ).to eq :md5 expect( metadata.checksum ).to eq "{md5}#{hex}" end end end end puppet-5.5.10/spec/unit/file_serving/mount/0000755005276200011600000000000013417162176020546 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/file_serving/mount/file_spec.rb0000644005276200011600000001455213417161721023026 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/file_serving/mount/file' module FileServingMountTesting def stub_facter(hostname) Facter.stubs(:value).with("hostname").returns(hostname.sub(/\..+/, '')) Facter.stubs(:value).with("domain").returns(hostname.sub(/^[^.]+\./, '')) end end describe Puppet::FileServing::Mount::File do it "should be invalid if it does not have a path" do expect { Puppet::FileServing::Mount::File.new("foo").validate }.to raise_error(ArgumentError) end it "should be valid if it has a path" do FileTest.stubs(:directory?).returns true FileTest.stubs(:readable?).returns true mount = Puppet::FileServing::Mount::File.new("foo") mount.path = "/foo" expect { mount.validate }.not_to raise_error end describe "when setting the path" do before do @mount = Puppet::FileServing::Mount::File.new("test") @dir = "/this/path/does/not/exist" end it "should fail if the path is not a directory" do FileTest.expects(:directory?).returns(false) expect { @mount.path = @dir }.to raise_error(ArgumentError) end it "should fail if the path is not readable" do FileTest.expects(:directory?).returns(true) FileTest.expects(:readable?).returns(false) expect { @mount.path = @dir }.to raise_error(ArgumentError) end end describe "when substituting hostnames and ip addresses into file paths" do include FileServingMountTesting before do FileTest.stubs(:directory?).returns(true) FileTest.stubs(:readable?).returns(true) @mount = Puppet::FileServing::Mount::File.new("test") @host = "host.domain.com" end after :each do Puppet::FileServing::Mount::File.instance_variable_set(:@localmap, nil) end it "should replace incidences of %h in the path with the client's short name" do @mount.path = "/dir/%h/yay" expect(@mount.path(@host)).to eq("/dir/host/yay") end it "should replace incidences of %H in the path with the client's fully qualified name" do @mount.path = "/dir/%H/yay" expect(@mount.path(@host)).to eq("/dir/host.domain.com/yay") end it "should replace incidences of %d in the path with the client's domain name" do @mount.path = "/dir/%d/yay" expect(@mount.path(@host)).to eq("/dir/domain.com/yay") end it "should perform all necessary replacements" do @mount.path = "/%h/%d/%H" expect(@mount.path(@host)).to eq("/host/domain.com/host.domain.com") end it "should use local host information if no client data is provided" do stub_facter("myhost.mydomain.com") @mount.path = "/%h/%d/%H" expect(@mount.path).to eq("/myhost/mydomain.com/myhost.mydomain.com") end end describe "when determining the complete file path" do include FileServingMountTesting before do Puppet::FileSystem.stubs(:exist?).returns(true) FileTest.stubs(:directory?).returns(true) FileTest.stubs(:readable?).returns(true) @mount = Puppet::FileServing::Mount::File.new("test") @mount.path = "/mount" stub_facter("myhost.mydomain.com") @host = "host.domain.com" end it "should return nil if the file is absent" do Puppet::FileSystem.stubs(:exist?).returns(false) expect(@mount.complete_path("/my/path", nil)).to be_nil end it "should write a log message if the file is absent" do Puppet::FileSystem.stubs(:exist?).returns(false) Puppet.expects(:info).with("File does not exist or is not accessible: /mount/my/path") @mount.complete_path("/my/path", nil) end it "should return the file path if the file is present" do Puppet::FileSystem.stubs(:exist?).with("/my/path").returns(true) expect(@mount.complete_path("/my/path", nil)).to eq("/mount/my/path") end it "should treat a nil file name as the path to the mount itself" do Puppet::FileSystem.stubs(:exist?).returns(true) expect(@mount.complete_path(nil, nil)).to eq("/mount") end it "should use the client host name if provided in the options" do @mount.path = "/mount/%h" expect(@mount.complete_path("/my/path", @host)).to eq("/mount/host/my/path") end it "should perform replacements on the base path" do @mount.path = "/blah/%h" expect(@mount.complete_path("/my/stuff", @host)).to eq("/blah/host/my/stuff") end it "should not perform replacements on the per-file path" do @mount.path = "/blah" expect(@mount.complete_path("/%h/stuff", @host)).to eq("/blah/%h/stuff") end it "should look for files relative to its base directory" do expect(@mount.complete_path("/my/stuff", @host)).to eq("/mount/my/stuff") end end describe "when finding files" do include FileServingMountTesting before do Puppet::FileSystem.stubs(:exist?).returns(true) FileTest.stubs(:directory?).returns(true) FileTest.stubs(:readable?).returns(true) @mount = Puppet::FileServing::Mount::File.new("test") @mount.path = "/mount" stub_facter("myhost.mydomain.com") @host = "host.domain.com" @request = stub 'request', :node => "foo" end it "should return the results of the complete file path" do Puppet::FileSystem.stubs(:exist?).returns(false) @mount.expects(:complete_path).with("/my/path", "foo").returns "eh" expect(@mount.find("/my/path", @request)).to eq("eh") end end describe "when searching for files" do include FileServingMountTesting before do Puppet::FileSystem.stubs(:exist?).returns(true) FileTest.stubs(:directory?).returns(true) FileTest.stubs(:readable?).returns(true) @mount = Puppet::FileServing::Mount::File.new("test") @mount.path = "/mount" stub_facter("myhost.mydomain.com") @host = "host.domain.com" @request = stub 'request', :node => "foo" end it "should return the results of the complete file path as an array" do Puppet::FileSystem.stubs(:exist?).returns(false) @mount.expects(:complete_path).with("/my/path", "foo").returns "eh" expect(@mount.search("/my/path", @request)).to eq(["eh"]) end it "should return nil if the complete path is nil" do Puppet::FileSystem.stubs(:exist?).returns(false) @mount.expects(:complete_path).with("/my/path", "foo").returns nil expect(@mount.search("/my/path", @request)).to be_nil end end end puppet-5.5.10/spec/unit/file_serving/mount/locales_spec.rb0000644005276200011600000000464713417161721023535 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/file_serving/mount/locales' describe Puppet::FileServing::Mount::Locales do before do @mount = Puppet::FileServing::Mount::Locales.new("locales") @environment = stub 'environment', :module => nil @options = { :recurse => true } @request = stub 'request', :environment => @environment, :options => @options end describe "when finding files" do it "should use the provided environment to find the modules" do @environment.expects(:modules).returns [] @mount.find("foo", @request) end it "should return nil if no module can be found with a matching locale" do mod = mock 'module' mod.stubs(:locale).with("foo/bar").returns nil @environment.stubs(:modules).returns [mod] expect(@mount.find("foo/bar", @request)).to be_nil end it "should return the file path from the module" do mod = mock 'module' mod.stubs(:locale).with("foo/bar").returns "eh" @environment.stubs(:modules).returns [mod] expect(@mount.find("foo/bar", @request)).to eq("eh") end end describe "when searching for files" do it "should use the node's environment to find the modules" do @environment.expects(:modules).at_least_once.returns [] @environment.stubs(:modulepath).returns ["/tmp/modules"] @mount.search("foo", @request) end it "should return modulepath if no modules can be found that have locales" do mod = mock 'module' mod.stubs(:locales?).returns false @environment.stubs(:modules).returns [] @environment.stubs(:modulepath).returns ["/"] @options.expects(:[]=).with(:recurse, false) expect(@mount.search("foo/bar", @request)).to eq(["/"]) end it "should return nil if no modules can be found that have locales and modulepath is invalid" do mod = mock 'module' mod.stubs(:locales?).returns false @environment.stubs(:modules).returns [] @environment.stubs(:modulepath).returns [] expect(@mount.search("foo/bar", @request)).to be_nil end it "should return the locale paths for each module that has locales" do one = stub 'module', :locales? => true, :locale_directory => "/one" two = stub 'module', :locales? => true, :locale_directory => "/two" @environment.stubs(:modules).returns [one, two] expect(@mount.search("foo/bar", @request)).to eq(%w{/one /two}) end end end puppet-5.5.10/spec/unit/file_serving/mount/modules_spec.rb0000644005276200011600000000435513417161721023557 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/file_serving/mount/modules' describe Puppet::FileServing::Mount::Modules do before do @mount = Puppet::FileServing::Mount::Modules.new("modules") @environment = stub 'environment', :module => nil @request = stub 'request', :environment => @environment end describe "when finding files" do it "should fail if no module is specified" do expect { @mount.find("", @request) }.to raise_error(/No module specified/) end it "should use the provided environment to find the module" do @environment.expects(:module) @mount.find("foo", @request) end it "should treat the first field of the relative path as the module name" do @environment.expects(:module).with("foo") @mount.find("foo/bar/baz", @request) end it "should return nil if the specified module does not exist" do @environment.expects(:module).with("foo").returns nil @mount.find("foo/bar/baz", @request) end it "should return the file path from the module" do mod = mock 'module' mod.expects(:file).with("bar/baz").returns "eh" @environment.expects(:module).with("foo").returns mod expect(@mount.find("foo/bar/baz", @request)).to eq("eh") end end describe "when searching for files" do it "should fail if no module is specified" do expect { @mount.search("", @request) }.to raise_error(/No module specified/) end it "should use the node's environment to search the module" do @environment.expects(:module) @mount.search("foo", @request) end it "should treat the first field of the relative path as the module name" do @environment.expects(:module).with("foo") @mount.search("foo/bar/baz", @request) end it "should return nil if the specified module does not exist" do @environment.expects(:module).with("foo").returns nil @mount.search("foo/bar/baz", @request) end it "should return the file path as an array from the module" do mod = mock 'module' mod.expects(:file).with("bar/baz").returns "eh" @environment.expects(:module).with("foo").returns mod expect(@mount.search("foo/bar/baz", @request)).to eq(["eh"]) end end end puppet-5.5.10/spec/unit/file_serving/mount/pluginfacts_spec.rb0000644005276200011600000000473113417161721024424 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/file_serving/mount/pluginfacts' describe Puppet::FileServing::Mount::PluginFacts do before do @mount = Puppet::FileServing::Mount::PluginFacts.new("pluginfacts") @environment = stub 'environment', :module => nil @options = { :recurse => true } @request = stub 'request', :environment => @environment, :options => @options end describe "when finding files" do it "should use the provided environment to find the modules" do @environment.expects(:modules).returns [] @mount.find("foo", @request) end it "should return nil if no module can be found with a matching plugin" do mod = mock 'module' mod.stubs(:pluginfact).with("foo/bar").returns nil @environment.stubs(:modules).returns [mod] expect(@mount.find("foo/bar", @request)).to be_nil end it "should return the file path from the module" do mod = mock 'module' mod.stubs(:pluginfact).with("foo/bar").returns "eh" @environment.stubs(:modules).returns [mod] expect(@mount.find("foo/bar", @request)).to eq("eh") end end describe "when searching for files" do it "should use the node's environment to find the modules" do @environment.expects(:modules).at_least_once.returns [] @environment.stubs(:modulepath).returns ["/tmp/modules"] @mount.search("foo", @request) end it "should return modulepath if no modules can be found that have plugins" do mod = mock 'module' mod.stubs(:pluginfacts?).returns false @environment.stubs(:modules).returns [] @environment.stubs(:modulepath).returns ["/"] @options.expects(:[]=).with(:recurse, false) expect(@mount.search("foo/bar", @request)).to eq(["/"]) end it "should return nil if no modules can be found that have plugins and modulepath is invalid" do mod = mock 'module' mod.stubs(:pluginfacts?).returns false @environment.stubs(:modules).returns [] @environment.stubs(:modulepath).returns [] expect(@mount.search("foo/bar", @request)).to be_nil end it "should return the plugin paths for each module that has plugins" do one = stub 'module', :pluginfacts? => true, :plugin_fact_directory => "/one" two = stub 'module', :pluginfacts? => true, :plugin_fact_directory => "/two" @environment.stubs(:modules).returns [one, two] expect(@mount.search("foo/bar", @request)).to eq(%w{/one /two}) end end end puppet-5.5.10/spec/unit/file_serving/mount/plugins_spec.rb0000644005276200011600000000464713417161721023574 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/file_serving/mount/plugins' describe Puppet::FileServing::Mount::Plugins do before do @mount = Puppet::FileServing::Mount::Plugins.new("plugins") @environment = stub 'environment', :module => nil @options = { :recurse => true } @request = stub 'request', :environment => @environment, :options => @options end describe "when finding files" do it "should use the provided environment to find the modules" do @environment.expects(:modules).returns [] @mount.find("foo", @request) end it "should return nil if no module can be found with a matching plugin" do mod = mock 'module' mod.stubs(:plugin).with("foo/bar").returns nil @environment.stubs(:modules).returns [mod] expect(@mount.find("foo/bar", @request)).to be_nil end it "should return the file path from the module" do mod = mock 'module' mod.stubs(:plugin).with("foo/bar").returns "eh" @environment.stubs(:modules).returns [mod] expect(@mount.find("foo/bar", @request)).to eq("eh") end end describe "when searching for files" do it "should use the node's environment to find the modules" do @environment.expects(:modules).at_least_once.returns [] @environment.stubs(:modulepath).returns ["/tmp/modules"] @mount.search("foo", @request) end it "should return modulepath if no modules can be found that have plugins" do mod = mock 'module' mod.stubs(:plugins?).returns false @environment.stubs(:modules).returns [] @environment.stubs(:modulepath).returns ["/"] @options.expects(:[]=).with(:recurse, false) expect(@mount.search("foo/bar", @request)).to eq(["/"]) end it "should return nil if no modules can be found that have plugins and modulepath is invalid" do mod = mock 'module' mod.stubs(:plugins?).returns false @environment.stubs(:modules).returns [] @environment.stubs(:modulepath).returns [] expect(@mount.search("foo/bar", @request)).to be_nil end it "should return the plugin paths for each module that has plugins" do one = stub 'module', :plugins? => true, :plugin_directory => "/one" two = stub 'module', :plugins? => true, :plugin_directory => "/two" @environment.stubs(:modules).returns [one, two] expect(@mount.search("foo/bar", @request)).to eq(%w{/one /two}) end end end puppet-5.5.10/spec/unit/file_serving/mount/tasks_spec.rb0000644005276200011600000000457313417161721023236 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/file_serving/mount/tasks' describe Puppet::FileServing::Mount::Tasks do before do @mount = Puppet::FileServing::Mount::Tasks.new("tasks") @environment = stub 'environment', :module => nil @request = stub 'request', :environment => @environment end describe "when finding task files" do it "should fail if no task is specified" do expect { @mount.find("", @request) }.to raise_error(/No task specified/) end it "should use the request's environment to find the module" do mod_name = 'foo' @environment.expects(:module).with(mod_name) @mount.find(mod_name, @request) end it "should use the first segment of the request's path as the module name" do @environment.expects(:module).with("foo") @mount.find("foo/bartask", @request) end it "should return nil if the module in the path doesn't exist" do @environment.expects(:module).with("foo").returns(nil) expect(@mount.find("foo/bartask", @request)).to be_nil end it "should return the file path from the module" do mod = mock('module') mod.expects(:task_file).with("bartask").returns("mocked") @environment.expects(:module).with("foo").returns(mod) expect(@mount.find("foo/bartask", @request)).to eq("mocked") end end describe "when searching for task files" do it "should fail if no module is specified" do expect { @mount.search("", @request) }.to raise_error(/No task specified/) end it "should use the request's environment to find the module" do mod_name = 'foo' @environment.expects(:module).with(mod_name) @mount.search(mod_name, @request) end it "should use the first segment of the request's path as the module name" do @environment.expects(:module).with("foo") @mount.search("foo/bartask", @request) end it "should return nil if the module in the path doesn't exist" do @environment.expects(:module).with("foo").returns(nil) expect(@mount.search("foo/bartask", @request)).to be_nil end it "should return the file path from the module" do mod = mock('module') mod.expects(:task_file).with("bartask").returns("mocked") @environment.expects(:module).with("foo").returns(mod) expect(@mount.search("foo/bartask", @request)).to eq(["mocked"]) end end end puppet-5.5.10/spec/unit/file_serving/mount_spec.rb0000644005276200011600000000200413417161721022074 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/file_serving/mount' describe Puppet::FileServing::Mount do it "should use 'mount[$name]' as its string form" do expect(Puppet::FileServing::Mount.new("foo").to_s).to eq("mount[foo]") end end describe Puppet::FileServing::Mount, " when initializing" do it "should fail on non-alphanumeric name" do expect { Puppet::FileServing::Mount.new("non alpha") }.to raise_error(ArgumentError) end it "should allow dashes in its name" do expect(Puppet::FileServing::Mount.new("non-alpha").name).to eq("non-alpha") end end describe Puppet::FileServing::Mount, " when finding files" do it "should fail" do expect { Puppet::FileServing::Mount.new("test").find("foo", :one => "two") }.to raise_error(NotImplementedError) end end describe Puppet::FileServing::Mount, " when searching for files" do it "should fail" do expect { Puppet::FileServing::Mount.new("test").search("foo", :one => "two") }.to raise_error(NotImplementedError) end end puppet-5.5.10/spec/unit/file_serving/terminus_helper_spec.rb0000644005276200011600000001043513417161721024146 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/file_serving/terminus_helper' describe Puppet::FileServing::TerminusHelper do before do @helper = Object.new @helper.extend(Puppet::FileServing::TerminusHelper) @model = mock 'model' @helper.stubs(:model).returns(@model) @request = stub 'request', :key => "url", :options => {} @fileset = stub 'fileset', :files => [], :path => "/my/file" Puppet::FileServing::Fileset.stubs(:new).with("/my/file", {}).returns(@fileset) end it "should find a file with absolute path" do file = stub 'file', :collect => nil file.expects(:collect).with(nil) @model.expects(:new).with("/my/file", {:relative_path => nil}).returns(file) @helper.path2instance(@request, "/my/file") end it "should pass through links, checksum_type, and source_permissions" do file = stub 'file', :checksum_type= => nil, :links= => nil, :collect => nil [[:checksum_type, :sha256], [:links, true], [:source_permissions, :use]].each {|k, v| file.expects(k.to_s+'=').with(v) @request.options[k] = v } file.expects(:collect) @model.expects(:new).with("/my/file", {:relative_path => :file}).returns(file) @helper.path2instance(@request, "/my/file", {:relative_path => :file}) end it "should use a fileset to find paths" do @fileset = stub 'fileset', :files => [], :path => "/my/files" Puppet::FileServing::Fileset.expects(:new).with { |key, options| key == "/my/file" }.returns(@fileset) @helper.path2instances(@request, "/my/file") end it "should support finding across multiple paths by merging the filesets" do first = stub 'fileset', :files => [], :path => "/first/file" Puppet::FileServing::Fileset.expects(:new).with { |path, options| path == "/first/file" }.returns(first) second = stub 'fileset', :files => [], :path => "/second/file" Puppet::FileServing::Fileset.expects(:new).with { |path, options| path == "/second/file" }.returns(second) Puppet::FileServing::Fileset.expects(:merge).with(first, second).returns({}) @helper.path2instances(@request, "/first/file", "/second/file") end it "should pass the indirection request to the Fileset at initialization" do Puppet::FileServing::Fileset.expects(:new).with { |path, options| options == @request }.returns @fileset @helper.path2instances(@request, "/my/file") end describe "when creating instances" do before do @request.stubs(:key).returns "puppet://host/mount/dir" @one = stub 'one', :links= => nil, :collect => nil @two = stub 'two', :links= => nil, :collect => nil @fileset = stub 'fileset', :files => %w{one two}, :path => "/my/file" Puppet::FileServing::Fileset.stubs(:new).returns(@fileset) end it "should set each returned instance's path to the original path" do @model.expects(:new).with { |key, options| key == "/my/file" }.returns(@one) @model.expects(:new).with { |key, options| key == "/my/file" }.returns(@two) @helper.path2instances(@request, "/my/file") end it "should set each returned instance's relative path to the file-specific path" do @model.expects(:new).with { |key, options| options[:relative_path] == "one" }.returns(@one) @model.expects(:new).with { |key, options| options[:relative_path] == "two" }.returns(@two) @helper.path2instances(@request, "/my/file") end it "should set the links value on each instance if one is provided" do @one.expects(:links=).with :manage @two.expects(:links=).with :manage @model.expects(:new).returns(@one) @model.expects(:new).returns(@two) @request.options[:links] = :manage @helper.path2instances(@request, "/my/file") end it "should set the request checksum_type if one is provided" do @one.expects(:checksum_type=).with :test @two.expects(:checksum_type=).with :test @model.expects(:new).returns(@one) @model.expects(:new).returns(@two) @request.options[:checksum_type] = :test @helper.path2instances(@request, "/my/file") end it "should collect the instance's attributes" do @one.expects(:collect) @two.expects(:collect) @model.expects(:new).returns(@one) @model.expects(:new).returns(@two) @helper.path2instances(@request, "/my/file") end end end puppet-5.5.10/spec/unit/file_serving/terminus_selector_spec.rb0000644005276200011600000000452113417161721024506 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/file_serving/terminus_selector' describe Puppet::FileServing::TerminusSelector do before do @object = Object.new @object.extend(Puppet::FileServing::TerminusSelector) @request = stub 'request', :key => "mymod/myfile", :options => {:node => "whatever"}, :server => nil, :protocol => nil end describe "when being used to select termini" do it "should return :file if the request key is fully qualified" do @request.expects(:key).returns File.expand_path('/foo') expect(@object.select(@request)).to eq(:file) end it "should return :file if the URI protocol is set to 'file'" do @request.expects(:protocol).returns "file" expect(@object.select(@request)).to eq(:file) end it "should return :http if the URI protocol is set to 'http'" do @request.expects(:protocol).returns "http" expect(@object.select(@request)).to eq :http end it "should return :http if the URI protocol is set to 'https'" do @request.expects(:protocol).returns "https" expect(@object.select(@request)).to eq :http end it "should fail when a protocol other than :puppet, :http(s) or :file is used" do @request.stubs(:protocol).returns "ftp" expect { @object.select(@request) }.to raise_error(ArgumentError) end describe "and the protocol is 'puppet'" do before do @request.stubs(:protocol).returns "puppet" end it "should choose :rest when a server is specified" do @request.stubs(:protocol).returns "puppet" @request.expects(:server).returns "foo" expect(@object.select(@request)).to eq(:rest) end # This is so a given file location works when bootstrapping with no server. it "should choose :rest when default_file_terminus is rest" do @request.stubs(:protocol).returns "puppet" Puppet[:server] = 'localhost' expect(@object.select(@request)).to eq(:rest) end it "should choose :file_server when default_file_terminus is file_server and no server is specified on the request" do @request.expects(:protocol).returns "puppet" @request.expects(:server).returns nil Puppet[:default_file_terminus] = 'file_server' expect(@object.select(@request)).to eq(:file_server) end end end end puppet-5.5.10/spec/unit/file_serving/base_spec.rb0000644005276200011600000001336213417161722021656 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/file_serving/base' describe Puppet::FileServing::Base do let(:path) { File.expand_path('/module/dir/file') } let(:file) { File.expand_path('/my/file') } it "should accept a path" do expect(Puppet::FileServing::Base.new(path).path).to eq(path) end it "should require that paths be fully qualified" do expect { Puppet::FileServing::Base.new("module/dir/file") }.to raise_error(ArgumentError) end it "should allow specification of whether links should be managed" do expect(Puppet::FileServing::Base.new(path, :links => :manage).links).to eq(:manage) end it "should have a :source attribute" do file = Puppet::FileServing::Base.new(path) expect(file).to respond_to(:source) expect(file).to respond_to(:source=) end it "should consider :ignore links equivalent to :manage links" do expect(Puppet::FileServing::Base.new(path, :links => :ignore).links).to eq(:manage) end it "should fail if :links is set to anything other than :manage, :follow, or :ignore" do expect { Puppet::FileServing::Base.new(path, :links => :else) }.to raise_error(ArgumentError) end it "should allow links values to be set as strings" do expect(Puppet::FileServing::Base.new(path, :links => "follow").links).to eq(:follow) end it "should default to :manage for :links" do expect(Puppet::FileServing::Base.new(path).links).to eq(:manage) end it "should allow specification of a path" do Puppet::FileSystem.stubs(:exist?).returns(true) expect(Puppet::FileServing::Base.new(path, :path => file).path).to eq(file) end it "should allow specification of a relative path" do Puppet::FileSystem.stubs(:exist?).returns(true) expect(Puppet::FileServing::Base.new(path, :relative_path => "my/file").relative_path).to eq("my/file") end it "should have a means of determining if the file exists" do expect(Puppet::FileServing::Base.new(file)).to respond_to(:exist?) end it "should correctly indicate if the file is present" do Puppet::FileSystem.expects(:lstat).with(file).returns stub('stat') expect(Puppet::FileServing::Base.new(file).exist?).to be_truthy end it "should correctly indicate if the file is absent" do Puppet::FileSystem.expects(:lstat).with(file).raises RuntimeError expect(Puppet::FileServing::Base.new(file).exist?).to be_falsey end describe "when setting the relative path" do it "should require that the relative path be unqualified" do @file = Puppet::FileServing::Base.new(path) Puppet::FileSystem.stubs(:exist?).returns(true) expect { @file.relative_path = File.expand_path("/qualified/file") }.to raise_error(ArgumentError) end end describe "when determining the full file path" do let(:path) { File.expand_path('/this/file') } let(:file) { Puppet::FileServing::Base.new(path) } it "should return the path if there is no relative path" do expect(file.full_path).to eq(path) end it "should return the path if the relative_path is set to ''" do file.relative_path = "" expect(file.full_path).to eq(path) end it "should return the path if the relative_path is set to '.'" do file.relative_path = "." expect(file.full_path).to eq(path) end it "should return the path joined with the relative path if there is a relative path and it is not set to '/' or ''" do file.relative_path = "not/qualified" expect(file.full_path).to eq(File.join(path, "not/qualified")) end it "should strip extra slashes" do file = Puppet::FileServing::Base.new(File.join(File.expand_path('/'), "//this//file")) expect(file.full_path).to eq(path) end end describe "when handling a UNC file path on Windows" do let(:path) { '//server/share/filename' } let(:file) { Puppet::FileServing::Base.new(path) } it "should preserve double slashes at the beginning of the path" do Puppet.features.stubs(:microsoft_windows?).returns(true) expect(file.full_path).to eq(path) end it "should strip double slashes not at the beginning of the path" do Puppet.features.stubs(:microsoft_windows?).returns(true) file = Puppet::FileServing::Base.new('//server//share//filename') expect(file.full_path).to eq(path) end end describe "when stat'ing files" do let(:path) { File.expand_path('/this/file') } let(:file) { Puppet::FileServing::Base.new(path) } let(:stat) { stub('stat', :ftype => 'file' ) } let(:stubbed_file) { stub(path, :stat => stat, :lstat => stat)} it "should stat the file's full path" do Puppet::FileSystem.expects(:lstat).with(path).returns stat file.stat end it "should fail if the file does not exist" do Puppet::FileSystem.expects(:lstat).with(path).raises(Errno::ENOENT) expect { file.stat }.to raise_error(Errno::ENOENT) end it "should use :lstat if :links is set to :manage" do Puppet::FileSystem.expects(:lstat).with(path).returns stubbed_file file.stat end it "should use :stat if :links is set to :follow" do Puppet::FileSystem.expects(:stat).with(path).returns stubbed_file file.links = :follow file.stat end end describe "#absolute?" do it "should be accept POSIX paths" do expect(Puppet::FileServing::Base).to be_absolute('/') end it "should accept Windows paths on Windows" do Puppet.features.stubs(:microsoft_windows?).returns(true) Puppet.features.stubs(:posix?).returns(false) expect(Puppet::FileServing::Base).to be_absolute('c:/foo') end it "should reject Windows paths on POSIX" do Puppet.features.stubs(:microsoft_windows?).returns(false) expect(Puppet::FileServing::Base).not_to be_absolute('c:/foo') end end end puppet-5.5.10/spec/unit/file_serving/metadata_spec.rb0000644005276200011600000004540213417161722022524 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/file_serving/metadata' require 'matchers/json' describe Puppet::FileServing::Metadata do let(:foobar) { File.expand_path('/foo/bar') } it "should be a subclass of Base" do expect(Puppet::FileServing::Metadata.superclass).to equal(Puppet::FileServing::Base) end it "should indirect file_metadata" do expect(Puppet::FileServing::Metadata.indirection.name).to eq(:file_metadata) end it "should have a method that triggers attribute collection" do expect(Puppet::FileServing::Metadata.new(foobar)).to respond_to(:collect) end it "should default to json" do expect(Puppet::FileServing::Metadata.default_format).to eq(:json) end it "should support json, pson, yaml" do # msgpack is optional, so using include instead of eq expect(Puppet::FileServing::Metadata.supported_formats).to include(:json, :pson, :yaml) end it "should support deserialization" do expect(Puppet::FileServing::Metadata).to respond_to(:from_data_hash) end describe "when serializing" do let(:metadata) { Puppet::FileServing::Metadata.new(foobar) } it "the data should include the path, relative_path, links, owner, group, mode, checksum, type, and destination" do expect(metadata.to_data_hash.keys.sort).to eq(%w{ path relative_path links owner group mode checksum type destination }.sort) end it "should pass the path in the hash verbatim" do expect(metadata.to_data_hash['path']).to eq(metadata.path) end it "should pass the relative_path in the hash verbatim" do expect(metadata.to_data_hash['relative_path']).to eq(metadata.relative_path) end it "should pass the links in the hash as a string" do expect(metadata.to_data_hash['links']).to eq(metadata.links.to_s) end it "should pass the path owner in the hash verbatim" do expect(metadata.to_data_hash['owner']).to eq(metadata.owner) end it "should pass the group in the hash verbatim" do expect(metadata.to_data_hash['group']).to eq(metadata.group) end it "should pass the mode in the hash verbatim" do expect(metadata.to_data_hash['mode']).to eq(metadata.mode) end it "should pass the ftype in the hash verbatim as the 'type'" do expect(metadata.to_data_hash['type']).to eq(metadata.ftype) end it "should pass the destination verbatim" do expect(metadata.to_data_hash['destination']).to eq(metadata.destination) end it "should pass the checksum in the hash as a nested hash" do expect(metadata.to_data_hash['checksum']).to be_is_a(Hash) end it "should pass the checksum_type in the hash verbatim as the checksum's type" do expect(metadata.to_data_hash['checksum']['type']).to eq(metadata.checksum_type) end it "should pass the checksum in the hash verbatim as the checksum's value" do expect(metadata.to_data_hash['checksum']['value']).to eq(metadata.checksum) end describe "when a source and content_uri are set" do before do metadata.source = '/foo' metadata.content_uri = 'puppet:///foo' end it "the data should include the path, relative_path, links, owner, group, mode, checksum, type, destination, source, and content_uri" do expect(metadata.to_data_hash.keys.sort).to eq(%w{ path relative_path links owner group mode checksum type destination source content_uri }.sort) end it "should pass the source in the hash verbatim" do expect(metadata.to_data_hash['source']).to eq(metadata.source) end it "should pass the content_uri in the hash verbatim" do expect(metadata.to_data_hash['content_uri']).to eq(metadata.content_uri) end end describe "when assigning a content_uri" do it "should fail if uri is invalid" do expect { metadata.content_uri = '://' }.to raise_error ArgumentError, /Could not understand URI :\/\// end it "should accept characters that require percent-encoding" do uri = 'puppet:///modules/foo/files/ %:?#[]@!$&\'()*+,;=' metadata.content_uri = uri expect(metadata.content_uri).to eq(uri) end it "should accept UTF-8 characters" do # different UTF-8 widths # 1-byte A # 2-byte ۿ - http://www.fileformat.info/info/unicode/char/06ff/index.htm - 0xDB 0xBF / 219 191 # 3-byte ᚠ - http://www.fileformat.info/info/unicode/char/16A0/index.htm - 0xE1 0x9A 0xA0 / 225 154 160 # 4-byte - http://www.fileformat.info/info/unicode/char/2070E/index.htm - 0xF0 0xA0 0x9C 0x8E / 240 160 156 142 mixed_utf8 = "A\u06FF\u16A0\u{2070E}" # Aۿᚠ uri = "puppet:///modules/foo/files/ #{mixed_utf8}" metadata.content_uri = uri expect(metadata.content_uri).to eq(uri) expect(metadata.content_uri.encoding).to eq(Encoding::UTF_8) end it "should always set it as UTF-8" do uri = "puppet:///modules/foo/files/".encode(Encoding::ASCII) metadata.content_uri = uri expect(metadata.content_uri).to eq(uri) expect(metadata.content_uri.encoding).to eq(Encoding::UTF_8) end it "should fail if uri is opaque" do expect { metadata.content_uri = 'scheme:www.example.com' }.to raise_error ArgumentError, "Cannot use opaque URLs 'scheme:www.example.com'" end it "should fail if uri is not a puppet scheme" do expect { metadata.content_uri = 'http://www.example.com' }.to raise_error ArgumentError, "Must use URLs of type puppet as content URI" end end end end describe Puppet::FileServing::Metadata, :uses_checksums => true do include JSONMatchers include PuppetSpec::Files shared_examples_for "metadata collector" do let(:metadata) do data = described_class.new(path) data.collect data end describe "when collecting attributes" do describe "when managing files" do let(:path) { tmpfile('file_serving_metadata') } let(:time) { Time.now } before :each do FileUtils.touch(path) end describe "checksumming" do with_digest_algorithms do before :each do File.open(path, "wb") {|f| f.print(plaintext)} end it "should default to a checksum of the proper type with the file's current checksum" do expect(metadata.checksum).to eq("{#{digest_algorithm}}#{checksum}") end it "should give a #{Puppet[:digest_algorithm]} when checksum_type is set" do Puppet[:digest_algorithm] = nil metadata.checksum_type = digest_algorithm metadata.collect expect(metadata.checksum).to eq("{#{digest_algorithm}}#{checksum}") end end it "should give a mtime checksum when checksum_type is set" do metadata.checksum_type = "mtime" metadata.expects(:mtime_file).returns(time) metadata.collect expect(metadata.checksum).to eq("{mtime}#{time}") end it "should give a ctime checksum when checksum_type is set" do metadata.checksum_type = "ctime" metadata.expects(:ctime_file).returns(time) metadata.collect expect(metadata.checksum).to eq("{ctime}#{time}") end end it "should validate against the schema" do expect(metadata.to_json).to validate_against('api/schemas/file_metadata.json') end describe "when a source and content_uri are set" do before do metadata.source = '/foo' metadata.content_uri = 'puppet:///foo' end it "should validate against the schema" do expect(metadata.to_json).to validate_against('api/schemas/file_metadata.json') end end end describe "when managing directories" do let(:path) { tmpdir('file_serving_metadata_dir') } let(:time) { Time.now } before :each do metadata.expects(:ctime_file).returns(time) end it "should only use checksums of type 'ctime' for directories" do metadata.collect expect(metadata.checksum).to eq("{ctime}#{time}") end it "should only use checksums of type 'ctime' for directories even if checksum_type set" do metadata.checksum_type = "mtime" metadata.expects(:mtime_file).never metadata.collect expect(metadata.checksum).to eq("{ctime}#{time}") end it "should validate against the schema" do metadata.collect expect(metadata.to_json).to validate_against('api/schemas/file_metadata.json') end end end end describe "WindowsStat", :if => Puppet.features.microsoft_windows? do include PuppetSpec::Files it "should return default owner, group and mode when the given path has an invalid DACL (such as a non-NTFS volume)" do invalid_error = Puppet::Util::Windows::Error.new('Invalid DACL', 1336) path = tmpfile('foo') FileUtils.touch(path) Puppet::Util::Windows::Security.stubs(:get_owner).with(path).raises(invalid_error) Puppet::Util::Windows::Security.stubs(:get_group).with(path).raises(invalid_error) Puppet::Util::Windows::Security.stubs(:get_mode).with(path).raises(invalid_error) stat = Puppet::FileSystem.stat(path) win_stat = Puppet::FileServing::Metadata::WindowsStat.new(stat, path, :ignore) expect(win_stat.owner).to eq('S-1-5-32-544') expect(win_stat.group).to eq('S-1-0-0') expect(win_stat.mode).to eq(0644) end it "should still raise errors that are not the result of an 'Invalid DACL'" do invalid_error = ArgumentError.new('bar') path = tmpfile('bar') FileUtils.touch(path) Puppet::Util::Windows::Security.stubs(:get_owner).with(path, :use).raises(invalid_error) Puppet::Util::Windows::Security.stubs(:get_group).with(path, :use).raises(invalid_error) Puppet::Util::Windows::Security.stubs(:get_mode).with(path, :use).raises(invalid_error) stat = Puppet::FileSystem.stat(path) expect { Puppet::FileServing::Metadata::WindowsStat.new(stat, path, :use) }.to raise_error("Unsupported Windows source permissions option use") end end shared_examples_for "metadata collector symlinks" do let(:metadata) do data = described_class.new(path) data.collect data end describe "when collecting attributes" do describe "when managing links" do # 'path' is a link that points to 'target' let(:path) { tmpfile('file_serving_metadata_link') } let(:target) { tmpfile('file_serving_metadata_target') } let(:fmode) { Puppet::FileSystem.lstat(path).mode & 0777 } before :each do File.open(target, "wb") {|f| f.print('some content')} set_mode(0644, target) Puppet::FileSystem.symlink(target, path) end it "should read links instead of returning their checksums" do expect(metadata.destination).to eq(target) end it "should validate against the schema" do expect(metadata.to_json).to validate_against('api/schemas/file_metadata.json') end end end describe Puppet::FileServing::Metadata, " when finding the file to use for setting attributes" do let(:path) { tmpfile('file_serving_metadata_find_file') } before :each do File.open(path, "wb") {|f| f.print('some content')} set_mode(0755, path) end it "should accept a base path to which the file should be relative" do dir = tmpdir('metadata_dir') metadata = described_class.new(dir) metadata.relative_path = 'relative_path' FileUtils.touch(metadata.full_path) metadata.collect end it "should use the set base path if one is not provided" do metadata.collect end it "should raise an exception if the file does not exist" do File.delete(path) expect { metadata.collect}.to raise_error(Errno::ENOENT) end it "should validate against the schema" do expect(metadata.to_json).to validate_against('api/schemas/file_metadata.json') end end end describe "on POSIX systems", :if => Puppet.features.posix? do let(:owner) {10} let(:group) {20} before :each do File::Stat.any_instance.stubs(:uid).returns owner File::Stat.any_instance.stubs(:gid).returns group end describe "when collecting attributes when managing files" do let(:metadata) do data = described_class.new(path) data.collect data end let(:path) { tmpfile('file_serving_metadata') } before :each do FileUtils.touch(path) end it "should set the owner to the Process's current owner" do expect(metadata.owner).to eq(Process.euid) end it "should set the group to the Process's current group" do expect(metadata.group).to eq(Process.egid) end it "should set the mode to the default mode" do set_mode(33261, path) expect(metadata.mode).to eq(0644) end end it_should_behave_like "metadata collector" it_should_behave_like "metadata collector symlinks" def set_mode(mode, path) File.chmod(mode, path) end end describe "on Windows systems", :if => Puppet.features.microsoft_windows? do let(:owner) {'S-1-1-50'} let(:group) {'S-1-1-51'} before :each do require 'puppet/util/windows/security' Puppet::Util::Windows::Security.stubs(:get_owner).returns owner Puppet::Util::Windows::Security.stubs(:get_group).returns group end describe "when collecting attributes when managing files" do let(:metadata) do data = described_class.new(path) data.collect data end let(:path) { tmpfile('file_serving_metadata') } before :each do FileUtils.touch(path) end it "should set the owner to the Process's current owner" do expect(metadata.owner).to eq("S-1-5-32-544") end it "should set the group to the Process's current group" do expect(metadata.group).to eq("S-1-0-0") end it "should set the mode to the default mode" do set_mode(33261, path) expect(metadata.mode).to eq(0644) end end it_should_behave_like "metadata collector" it_should_behave_like "metadata collector symlinks" if Puppet.features.manages_symlinks? describe "if ACL metadata cannot be collected" do let(:path) { tmpdir('file_serving_metadata_acl') } let(:metadata) do data = described_class.new(path) data.collect data end let (:invalid_dacl_error) do Puppet::Util::Windows::Error.new('Invalid DACL', 1336) end it "should default owner" do Puppet::Util::Windows::Security.stubs(:get_owner).returns nil expect(metadata.owner).to eq('S-1-5-32-544') end it "should default group" do Puppet::Util::Windows::Security.stubs(:get_group).returns nil expect(metadata.group).to eq('S-1-0-0') end it "should default mode" do Puppet::Util::Windows::Security.stubs(:get_mode).returns nil expect(metadata.mode).to eq(0644) end describe "when the path raises an Invalid ACL error" do # these simulate the behavior of a symlink file whose target does not support ACLs it "should default owner" do Puppet::Util::Windows::Security.stubs(:get_owner).raises(invalid_dacl_error) expect(metadata.owner).to eq('S-1-5-32-544') end it "should default group" do Puppet::Util::Windows::Security.stubs(:get_group).raises(invalid_dacl_error) expect(metadata.group).to eq('S-1-0-0') end it "should default mode" do Puppet::Util::Windows::Security.stubs(:get_mode).raises(invalid_dacl_error) expect(metadata.mode).to eq(0644) end end end def set_mode(mode, path) Puppet::Util::Windows::Security.set_mode(mode, path) end end end describe Puppet::FileServing::Metadata, " when pointing to a link", :if => Puppet.features.manages_symlinks?, :uses_checksums => true do with_digest_algorithms do describe "when links are managed" do before do path = "/base/path/my/file" @file = Puppet::FileServing::Metadata.new(path, :links => :manage) stat = stub("stat", :uid => 1, :gid => 2, :ftype => "link", :mode => 0755) Puppet::FileSystem.expects(:lstat).with(path).at_least_once.returns stat Puppet::FileSystem.expects(:readlink).with(path).at_least_once.returns "/some/other/path" @file.stubs("#{digest_algorithm}_file".intern).returns(checksum) # Remove these when :managed links are no longer checksumed. if Puppet.features.microsoft_windows? win_stat = stub('win_stat', :owner => 'snarf', :group => 'thundercats', :ftype => 'link', :mode => 0755) Puppet::FileServing::Metadata::WindowsStat.stubs(:new).returns win_stat end end it "should store the destination of the link in :destination if links are :manage" do @file.collect expect(@file.destination).to eq("/some/other/path") end pending "should not collect the checksum if links are :manage" do # We'd like this to be true, but we need to always collect the checksum because in the server/client/server round trip we lose the distintion between manage and follow. @file.collect expect(@file.checksum).to be_nil end it "should collect the checksum if links are :manage" do # see pending note above @file.collect expect(@file.checksum).to eq("{#{digest_algorithm}}#{checksum}") end end describe "when links are followed" do before do path = "/base/path/my/file" @file = Puppet::FileServing::Metadata.new(path, :links => :follow) stat = stub("stat", :uid => 1, :gid => 2, :ftype => "file", :mode => 0755) Puppet::FileSystem.expects(:stat).with(path).at_least_once.returns stat Puppet::FileSystem.expects(:readlink).never if Puppet.features.microsoft_windows? win_stat = stub('win_stat', :owner => 'snarf', :group => 'thundercats', :ftype => 'file', :mode => 0755) Puppet::FileServing::Metadata::WindowsStat.stubs(:new).returns win_stat end @file.stubs("#{digest_algorithm}_file".intern).returns(checksum) end it "should not store the destination of the link in :destination if links are :follow" do @file.collect expect(@file.destination).to be_nil end it "should collect the checksum if links are :follow" do @file.collect expect(@file.checksum).to eq("{#{digest_algorithm}}#{checksum}") end end end end puppet-5.5.10/spec/unit/file_system/0000755005276200011600000000000013417162176017253 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/file_system/path_pattern_spec.rb0000644005276200011600000001262013417161722023300 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/files' require 'puppet/file_system' describe Puppet::FileSystem::PathPattern do include PuppetSpec::Files InvalidPattern = Puppet::FileSystem::PathPattern::InvalidPattern describe 'relative' do it "can not be created with a traversal up the directory tree" do expect do Puppet::FileSystem::PathPattern.relative("my/../other") end.to raise_error(InvalidPattern, "PathPatterns cannot be created with directory traversals.") end it "can be created with a '..' prefixing a filename" do expect(Puppet::FileSystem::PathPattern.relative("my/..other").to_s).to eq("my/..other") end it "can be created with a '..' suffixing a filename" do expect(Puppet::FileSystem::PathPattern.relative("my/other..").to_s).to eq("my/other..") end it "can be created with a '..' embedded in a filename" do expect(Puppet::FileSystem::PathPattern.relative("my/ot..her").to_s).to eq("my/ot..her") end it "can not be created with a \\0 byte embedded" do expect do Puppet::FileSystem::PathPattern.relative("my/\0/other") end.to raise_error(InvalidPattern, "PathPatterns cannot be created with a zero byte.") end it "can not be created with a windows drive" do expect do Puppet::FileSystem::PathPattern.relative("c:\\relative\\path") end.to raise_error(InvalidPattern, "A relative PathPattern cannot be prefixed with a drive.") end it "can not be created with a windows drive (with space)" do expect do Puppet::FileSystem::PathPattern.relative(" c:\\relative\\path") end.to raise_error(InvalidPattern, "A relative PathPattern cannot be prefixed with a drive.") end it "can not create an absolute relative path" do expect do Puppet::FileSystem::PathPattern.relative("/no/absolutes") end.to raise_error(InvalidPattern, "A relative PathPattern cannot be an absolute path.") end it "can not create an absolute relative path (with space)" do expect do Puppet::FileSystem::PathPattern.relative("\t/no/absolutes") end.to raise_error(InvalidPattern, "A relative PathPattern cannot be an absolute path.") end it "can not create a relative path that is a windows path relative to the current drive" do expect do Puppet::FileSystem::PathPattern.relative("\\no\relatives") end.to raise_error(InvalidPattern, "A PathPattern cannot be a Windows current drive relative path.") end it "creates a relative PathPattern from a valid relative path" do expect(Puppet::FileSystem::PathPattern.relative("a/relative/path").to_s).to eq("a/relative/path") end it "is not absolute" do expect(Puppet::FileSystem::PathPattern.relative("a/relative/path")).to_not be_absolute end end describe 'absolute' do it "can not create a relative absolute path" do expect do Puppet::FileSystem::PathPattern.absolute("no/relatives") end.to raise_error(InvalidPattern, "An absolute PathPattern cannot be a relative path.") end it "can not create an absolute path that is a windows path relative to the current drive" do expect do Puppet::FileSystem::PathPattern.absolute("\\no\\relatives") end.to raise_error(InvalidPattern, "A PathPattern cannot be a Windows current drive relative path.") end it "creates an absolute PathPattern from a valid absolute path" do expect(Puppet::FileSystem::PathPattern.absolute("/an/absolute/path").to_s).to eq("/an/absolute/path") end it "creates an absolute PathPattern from a valid Windows absolute path" do expect(Puppet::FileSystem::PathPattern.absolute("c:/absolute/windows/path").to_s).to eq("c:/absolute/windows/path") end it "can be created with a '..' embedded in a filename on windows", :if => Puppet.features.microsoft_windows? do expect(Puppet::FileSystem::PathPattern.absolute(%q{c:\..my\ot..her\one..}).to_s).to eq(%q{c:\..my\ot..her\one..}) end it "is absolute" do expect(Puppet::FileSystem::PathPattern.absolute("c:/absolute/windows/path")).to be_absolute end end it "prefixes the relative path pattern with another path" do pattern = Puppet::FileSystem::PathPattern.relative("docs/*_thoughts.txt") prefix = Puppet::FileSystem::PathPattern.absolute("/prefix") absolute_pattern = pattern.prefix_with(prefix) expect(absolute_pattern).to be_absolute expect(absolute_pattern.to_s).to eq(File.join("/prefix", "docs/*_thoughts.txt")) end it "refuses to prefix with a relative pattern" do pattern = Puppet::FileSystem::PathPattern.relative("docs/*_thoughts.txt") prefix = Puppet::FileSystem::PathPattern.relative("prefix") expect do pattern.prefix_with(prefix) end.to raise_error(InvalidPattern, "An absolute PathPattern cannot be a relative path.") end it "applies the pattern to the filesystem as a glob" do dir = tmpdir('globtest') create_file_in(dir, "found_one") create_file_in(dir, "found_two") create_file_in(dir, "third_not_found") pattern = Puppet::FileSystem::PathPattern.relative("found_*").prefix_with( Puppet::FileSystem::PathPattern.absolute(dir)) expect(pattern.glob).to match_array([File.join(dir, "found_one"), File.join(dir, "found_two")]) end def create_file_in(dir, name) File.open(File.join(dir, name), "w") { |f| f.puts "data" } end end puppet-5.5.10/spec/unit/file_system/uniquefile_spec.rb0000644005276200011600000001232213417161722022754 0ustar jenkinsjenkinsrequire 'spec_helper' describe Puppet::FileSystem::Uniquefile do it "makes the name of the file available" do Puppet::FileSystem::Uniquefile.open_tmp('foo') do |file| expect(file.path).to match(/foo/) end end it "provides a writeable file" do Puppet::FileSystem::Uniquefile.open_tmp('foo') do |file| file.write("stuff") file.flush expect(Puppet::FileSystem.read(file.path)).to eq("stuff") end end it "returns the value of the block" do the_value = Puppet::FileSystem::Uniquefile.open_tmp('foo') do |file| "my value" end expect(the_value).to eq("my value") end it "unlinks the temporary file" do filename = Puppet::FileSystem::Uniquefile.open_tmp('foo') do |file| file.path end expect(Puppet::FileSystem.exist?(filename)).to be_falsey end it "unlinks the temporary file even if the block raises an error" do filename = nil begin Puppet::FileSystem::Uniquefile.open_tmp('foo') do |file| filename = file.path raise "error!" end rescue end expect(Puppet::FileSystem.exist?(filename)).to be_falsey end it "propagates lock creation failures" do # use an arbitrary exception so as not accidentally collide # with the ENOENT that occurs when trying to call rmdir Puppet::FileSystem::Uniquefile.stubs(:mkdir).raises 'arbitrary failure' Puppet::FileSystem::Uniquefile.expects(:rmdir).never expect { Puppet::FileSystem::Uniquefile.open_tmp('foo') { |tmp| } }.to raise_error('arbitrary failure') end it "only removes lock files that exist" do # prevent the .lock directory from being created Puppet::FileSystem::Uniquefile.stubs(:mkdir) { } # and expect cleanup to be skipped Puppet::FileSystem::Uniquefile.expects(:rmdir).never Puppet::FileSystem::Uniquefile.open_tmp('foo') { |tmp| } end context "Ruby 1.9.3 Tempfile tests" do # the remaining tests in this file are ported directly from the ruby 1.9.3 source, # since most of this file was ported from there # see: https://github.com/ruby/ruby/blob/v1_9_3_547/test/test_tempfile.rb def tempfile(*args, &block) t = Puppet::FileSystem::Uniquefile.new(*args, &block) @tempfile = (t unless block) end after(:each) do if @tempfile @tempfile.close! end end it "creates tempfiles" do t = tempfile("foo") path = t.path t.write("hello world") t.close expect(File.read(path)).to eq("hello world") end it "saves in tmpdir by default" do t = tempfile("foo") expect(Dir.tmpdir).to eq(File.dirname(t.path)) end it "saves in given directory" do subdir = File.join(Dir.tmpdir, "tempfile-test-#{rand}") Dir.mkdir(subdir) begin tempfile = Tempfile.new("foo", subdir) tempfile.close begin expect(subdir).to eq(File.dirname(tempfile.path)) ensure tempfile.unlink end ensure Dir.rmdir(subdir) end end it "supports basename" do t = tempfile("foo") expect(File.basename(t.path)).to match(/^foo/) end it "supports basename with suffix" do t = tempfile(["foo", ".txt"]) expect(File.basename(t.path)).to match(/^foo/) expect(File.basename(t.path)).to match(/\.txt$/) end it "supports unlink" do t = tempfile("foo") path = t.path t.close expect(File.exist?(path)).to eq(true) t.unlink expect(File.exist?(path)).to eq(false) expect(t.path).to eq(nil) end it "supports closing" do t = tempfile("foo") expect(t.closed?).to eq(false) t.close expect(t.closed?).to eq(true) end it "supports closing and unlinking via boolean argument" do t = tempfile("foo") path = t.path t.close(true) expect(t.closed?).to eq(true) expect(t.path).to eq(nil) expect(File.exist?(path)).to eq(false) end context "on unix platforms", :unless => Puppet.features.microsoft_windows? do it "close doesn't unlink if already unlinked" do t = tempfile("foo") path = t.path t.unlink File.open(path, "w").close begin t.close(true) expect(File.exist?(path)).to eq(true) ensure File.unlink(path) rescue nil end end end it "supports close!" do t = tempfile("foo") path = t.path t.close! expect(t.closed?).to eq(true) expect(t.path).to eq(nil) expect(File.exist?(path)).to eq(false) end context "on unix platforms", :unless => Puppet.features.microsoft_windows? do it "close! doesn't unlink if already unlinked" do t = tempfile("foo") path = t.path t.unlink File.open(path, "w").close begin t.close! expect(File.exist?(path)).to eq(true) ensure File.unlink(path) rescue nil end end end it "close does not make path nil" do t = tempfile("foo") t.close expect(t.path.nil?).to eq(false) end it "close flushes buffer" do t = tempfile("foo") t.write("hello") t.close expect(File.size(t.path)).to eq(5) end end end puppet-5.5.10/spec/unit/forge/0000755005276200011600000000000013417162176016032 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/forge/errors_spec.rb0000644005276200011600000000561313417161721020705 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/forge' describe Puppet::Forge::Errors do describe 'SSLVerifyError' do subject { Puppet::Forge::Errors::SSLVerifyError } let(:exception) { subject.new(:uri => 'https://fake.com:1111') } it 'should return a valid single line error' do expect(exception.message).to eq('Unable to verify the SSL certificate at https://fake.com:1111') end it 'should return a valid multiline error' do expect(exception.multiline).to eq <<-EOS.chomp Could not connect via HTTPS to https://fake.com:1111 Unable to verify the SSL certificate The certificate may not be signed by a valid CA The CA bundle included with OpenSSL may not be valid or up to date EOS end end describe 'CommunicationError' do subject { Puppet::Forge::Errors::CommunicationError } let(:socket_exception) { SocketError.new('There was a problem') } let(:exception) { subject.new(:uri => 'http://fake.com:1111', :original => socket_exception) } it 'should return a valid single line error' do expect(exception.message).to eq('Unable to connect to the server at http://fake.com:1111. Detail: There was a problem.') end it 'should return a valid multiline error' do expect(exception.multiline).to eq <<-EOS.chomp Could not connect to http://fake.com:1111 There was a network communications problem The error we caught said 'There was a problem' Check your network connection and try again EOS end end describe 'ResponseError' do subject { Puppet::Forge::Errors::ResponseError } let(:response) { stub(:body => '{}', :code => '404', :message => "not found") } context 'without message' do let(:exception) { subject.new(:uri => 'http://fake.com:1111', :response => response, :input => 'user/module') } it 'should return a valid single line error' do expect(exception.message).to eq('Request to Puppet Forge failed. Detail: 404 not found.') end it 'should return a valid multiline error' do expect(exception.multiline).to eq <<-eos.chomp Request to Puppet Forge failed. The server being queried was http://fake.com:1111 The HTTP response we received was '404 not found' eos end end context 'with message' do let(:exception) { subject.new(:uri => 'http://fake.com:1111', :response => response, :input => 'user/module', :message => 'no such module') } it 'should return a valid single line error' do expect(exception.message).to eq('Request to Puppet Forge failed. Detail: no such module / 404 not found.') end it 'should return a valid multiline error' do expect(exception.multiline).to eq <<-eos.chomp Request to Puppet Forge failed. The server being queried was http://fake.com:1111 The HTTP response we received was '404 not found' The message we received said 'no such module' eos end end end end puppet-5.5.10/spec/unit/forge/forge_spec.rb0000644005276200011600000001041413417161721020466 0ustar jenkinsjenkins# encoding: utf-8 require 'spec_helper' require 'net/http' require 'puppet/forge/repository' describe Puppet::Forge do before(:all) do # any local http proxy will break these tests ENV['http_proxy'] = nil ENV['HTTP_PROXY'] = nil end let(:host) { 'fake.com' } let(:forge) { Puppet::Forge.new("http://#{host}") } # creates a repository like Puppet::Forge::Repository.new('http://fake.com', USER_AGENT) # different UTF-8 widths # 1-byte A # 2-byte ۿ - http://www.fileformat.info/info/unicode/char/06ff/index.htm - 0xDB 0xBF / 219 191 # 3-byte ᚠ - http://www.fileformat.info/info/unicode/char/16A0/index.htm - 0xE1 0x9A 0xA0 / 225 154 160 # 4-byte ܎ - http://www.fileformat.info/info/unicode/char/2070E/index.htm - 0xF0 0xA0 0x9C 0x8E / 240 160 156 142 let (:mixed_utf8_query_param) { "foo + A\u06FF\u16A0\u{2070E}" } # Aۿᚠ let (:mixed_utf8_query_param_encoded) { "foo%20%2B%20A%DB%BF%E1%9A%A0%F0%A0%9C%8E"} let (:empty_json) { '{ "results": [], "pagination" : { "next" : null } }' } let (:ok_response) { stub('response', :code => '200', :body => empty_json) } describe "making a" do before :each do proxy_settings_of("proxy", 1234) end context "search request" do it "includes any defined module_groups, ensuring to only encode them once in the URI" do Puppet[:module_groups] = 'base+pe' # ignores Puppet::Forge::Repository#read_response, provides response to search performs_an_http_request(ok_response) do |http| encoded_uri = "/v3/modules?query=#{mixed_utf8_query_param_encoded}&module_groups=base%20pe" http.expects(:request).with(responds_with(:path, encoded_uri)) end forge.search(mixed_utf8_query_param) end it "single encodes the search term in the URI" do # ignores Puppet::Forge::Repository#read_response, provides response to search performs_an_http_request(ok_response) do |http| encoded_uri = "/v3/modules?query=#{mixed_utf8_query_param_encoded}" http.expects(:request).with(responds_with(:path, encoded_uri)) end forge.search(mixed_utf8_query_param) end end context "fetch request" do it "includes any defined module_groups, ensuring to only encode them once in the URI" do Puppet[:module_groups] = 'base+pe' module_name = 'puppetlabs-acl' exclusions = "readme%2Cchangelog%2Clicense%2Curi%2Cmodule%2Ctags%2Csupported%2Cfile_size%2Cdownloads%2Ccreated_at%2Cupdated_at%2Cdeleted_at" # ignores Puppet::Forge::Repository#read_response, provides response to fetch performs_an_http_request(ok_response) do |http| encoded_uri = "/v3/releases?module=#{module_name}&sort_by=version&exclude_fields=#{exclusions}&module_groups=base%20pe" http.expects(:request).with(responds_with(:path, encoded_uri)) end forge.fetch(module_name) end it "single encodes the module name term in the URI" do module_name = "puppetlabs-#{mixed_utf8_query_param}" exclusions = "readme%2Cchangelog%2Clicense%2Curi%2Cmodule%2Ctags%2Csupported%2Cfile_size%2Cdownloads%2Ccreated_at%2Cupdated_at%2Cdeleted_at" # ignores Puppet::Forge::Repository#read_response, provides response to fetch performs_an_http_request(ok_response) do |http| encoded_uri = "/v3/releases?module=puppetlabs-#{mixed_utf8_query_param_encoded}&sort_by=version&exclude_fields=#{exclusions}" http.expects(:request).with(responds_with(:path, encoded_uri)) end forge.fetch(module_name) end end def performs_an_http_request(result = nil, &block) proxy_args = ["proxy", 1234, nil, nil] mock_proxy(80, proxy_args, result, &block) end end def proxy_settings_of(host, port) Puppet[:http_proxy_host] = host Puppet[:http_proxy_port] = port end def mock_proxy(port, proxy_args, result, &block) http = mock("http client") proxy = mock("http proxy") proxy_class = mock("http proxy class") Net::HTTP.expects(:Proxy).with(*proxy_args).returns(proxy_class) proxy_class.expects(:new).with(host, port).returns(proxy) proxy.expects(:open_timeout=) proxy.expects(:read_timeout=) proxy.expects(:start).yields(http).returns(result) yield http proxy end end puppet-5.5.10/spec/unit/forge/module_release_spec.rb0000644005276200011600000002200613417161721022351 0ustar jenkinsjenkins# encoding: utf-8 require 'spec_helper' require 'puppet/forge' require 'net/http' require 'puppet/module_tool' describe Puppet::Forge::ModuleRelease do let(:agent) { "Test/1.0" } let(:repository) { Puppet::Forge::Repository.new('http://fake.com', agent) } let(:ssl_repository) { Puppet::Forge::Repository.new('https://fake.com', agent) } let(:api_version) { "v3" } let(:module_author) { "puppetlabs" } let(:module_name) { "stdlib" } let(:module_version) { "4.1.0" } let(:module_full_name) { "#{module_author}-#{module_name}" } let(:module_full_name_versioned) { "#{module_full_name}-#{module_version}" } let(:module_md5) { "bbf919d7ee9d278d2facf39c25578bf8" } let(:uri) { " "} let(:release) { Puppet::Forge::ModuleRelease.new(ssl_repository, JSON.parse(release_json)) } let(:mock_file) { mock_io = StringIO.new mock_io.stubs(:path).returns('/dev/null') mock_io } let(:mock_dir) { '/tmp' } shared_examples 'a module release' do def mock_digest_file_with_md5(md5) Digest::MD5.stubs(:file).returns(stub(:hexdigest => md5)) end describe '#prepare' do before :each do release.stubs(:tmpfile).returns(mock_file) release.stubs(:tmpdir).returns(mock_dir) end it 'should call sub methods with correct params' do release.expects(:download).with("/#{api_version}/files/#{module_full_name_versioned}.tar.gz", mock_file) release.expects(:validate_checksum).with(mock_file, module_md5) release.expects(:unpack).with(mock_file, mock_dir) release.prepare end end describe '#tmpfile' do it 'should be opened in binary mode' do Puppet::Forge::Cache.stubs(:base_path).returns(Dir.tmpdir) expect(release.send(:tmpfile).binmode?).to be_truthy end end describe '#download' do it 'should call make_http_request with correct params' do # valid URI comes from file_uri in JSON blob above ssl_repository.expects(:make_http_request).with("/#{api_version}/files/#{module_full_name_versioned}.tar.gz", mock_file).returns(stub(:body => '{}', :code => '200')) release.send(:download, "/#{api_version}/files/#{module_full_name_versioned}.tar.gz", mock_file) end it 'should raise a response error when it receives an error from forge' do ssl_repository.stubs(:make_http_request).returns(stub(:body => '{"errors": ["error"]}', :code => '500', :message => 'server error')) expect { release.send(:download, "/some/path", mock_file)}. to raise_error Puppet::Forge::Errors::ResponseError end end describe '#verify_checksum' do it 'passes md5 check when valid' do # valid hash comes from file_md5 in JSON blob above mock_digest_file_with_md5(module_md5) release.send(:validate_checksum, mock_file, module_md5) end it 'fails md5 check when invalid' do mock_digest_file_with_md5('ffffffffffffffffffffffffffffffff') expect { release.send(:validate_checksum, mock_file, module_md5) }.to raise_error(RuntimeError, /did not match expected checksum/) end end describe '#unpack' do it 'should call unpacker with correct params' do Puppet::ModuleTool::Applications::Unpacker.expects(:unpack).with(mock_file.path, mock_dir).returns(true) release.send(:unpack, mock_file, mock_dir) end end end context 'standard forge module' do let(:release_json) do %Q{ { "uri": "/#{api_version}/releases/#{module_full_name_versioned}", "module": { "uri": "/#{api_version}/modules/#{module_full_name}", "name": "#{module_name}", "owner": { "uri": "/#{api_version}/users/#{module_author}", "username": "#{module_author}", "gravatar_id": "fdd009b7c1ec96e088b389f773e87aec" } }, "version": "#{module_version}", "metadata": { "types": [ ], "license": "Apache 2.0", "checksums": { }, "version": "#{module_version}", "description": "Standard Library for Puppet Modules", "source": "git://github.com/puppetlabs/puppetlabs-stdlib.git", "project_page": "https://github.com/puppetlabs/puppetlabs-stdlib", "summary": "Puppet Module Standard Library", "dependencies": [ ], "author": "#{module_author}", "name": "#{module_full_name}" }, "tags": [ "puppetlabs", "library", "stdlib", "standard", "stages" ], "file_uri": "/#{api_version}/files/#{module_full_name_versioned}.tar.gz", "file_size": 67586, "file_md5": "#{module_md5}", "downloads": 610751, "readme": "", "changelog": "", "license": "", "created_at": "2013-05-13 08:31:19 -0700", "updated_at": "2013-05-13 08:31:19 -0700", "deleted_at": null } } end it_behaves_like 'a module release' end context 'forge module with no dependencies field' do let(:release_json) do %Q{ { "uri": "/#{api_version}/releases/#{module_full_name_versioned}", "module": { "uri": "/#{api_version}/modules/#{module_full_name}", "name": "#{module_name}", "owner": { "uri": "/#{api_version}/users/#{module_author}", "username": "#{module_author}", "gravatar_id": "fdd009b7c1ec96e088b389f773e87aec" } }, "version": "#{module_version}", "metadata": { "types": [ ], "license": "Apache 2.0", "checksums": { }, "version": "#{module_version}", "description": "Standard Library for Puppet Modules", "source": "git://github.com/puppetlabs/puppetlabs-stdlib.git", "project_page": "https://github.com/puppetlabs/puppetlabs-stdlib", "summary": "Puppet Module Standard Library", "author": "#{module_author}", "name": "#{module_full_name}" }, "tags": [ "puppetlabs", "library", "stdlib", "standard", "stages" ], "file_uri": "/#{api_version}/files/#{module_full_name_versioned}.tar.gz", "file_size": 67586, "file_md5": "#{module_md5}", "downloads": 610751, "readme": "", "changelog": "", "license": "", "created_at": "2013-05-13 08:31:19 -0700", "updated_at": "2013-05-13 08:31:19 -0700", "deleted_at": null } } end it_behaves_like 'a module release' end context 'forge module with the minimal set of fields' do let(:release_json) do %Q{ { "uri": "/#{api_version}/releases/#{module_full_name_versioned}", "module": { "uri": "/#{api_version}/modules/#{module_full_name}", "name": "#{module_name}" }, "metadata": { "version": "#{module_version}", "name": "#{module_full_name}" }, "file_uri": "/#{api_version}/files/#{module_full_name_versioned}.tar.gz", "file_size": 67586, "file_md5": "#{module_md5}" } } end it_behaves_like 'a module release' end context 'deprecated forge module' do let(:release_json) do %Q{ { "uri": "/#{api_version}/releases/#{module_full_name_versioned}", "module": { "uri": "/#{api_version}/modules/#{module_full_name}", "name": "#{module_name}", "deprecated_at": "2017-10-10 10:21:32 -0700", "owner": { "uri": "/#{api_version}/users/#{module_author}", "username": "#{module_author}", "gravatar_id": "fdd009b7c1ec96e088b389f773e87aec" } }, "version": "#{module_version}", "metadata": { "types": [ ], "license": "Apache 2.0", "checksums": { }, "version": "#{module_version}", "description": "Standard Library for Puppet Modules", "source": "git://github.com/puppetlabs/puppetlabs-stdlib.git", "project_page": "https://github.com/puppetlabs/puppetlabs-stdlib", "summary": "Puppet Module Standard Library", "dependencies": [ ], "author": "#{module_author}", "name": "#{module_full_name}" }, "tags": [ "puppetlabs", "library", "stdlib", "standard", "stages" ], "file_uri": "/#{api_version}/files/#{module_full_name_versioned}.tar.gz", "file_size": 67586, "file_md5": "#{module_md5}", "downloads": 610751, "readme": "", "changelog": "", "license": "", "created_at": "2013-05-13 08:31:19 -0700", "updated_at": "2013-05-13 08:31:19 -0700", "deleted_at": null } } end it_behaves_like 'a module release' describe '#prepare' do before :each do release.stubs(:tmpfile).returns(mock_file) release.stubs(:tmpdir).returns(mock_dir) release.stubs(:download) release.stubs(:validate_checksum) release.stubs(:unpack) end it 'should emit warning about module deprecation' do Puppet.expects(:warning).with(regexp_matches(/#{Regexp.escape(module_full_name)}.*deprecated/i)) release.prepare end end end end puppet-5.5.10/spec/unit/forge/repository_spec.rb0000644005276200011600000002065213417161721021610 0ustar jenkinsjenkins# encoding: utf-8 require 'spec_helper' require 'net/http' require 'puppet/forge/repository' require 'puppet/forge/cache' require 'puppet/forge/errors' describe Puppet::Forge::Repository do before(:all) do # any local http proxy will break these tests ENV['http_proxy'] = nil ENV['HTTP_PROXY'] = nil end let(:agent) { "Test/1.0" } let(:repository) { Puppet::Forge::Repository.new('http://fake.com', agent) } let(:ssl_repository) { Puppet::Forge::Repository.new('https://fake.com', agent) } it "retrieve accesses the cache" do path = '/module/foo.tar.gz' repository.cache.expects(:retrieve) repository.retrieve(path) end it "retrieve merges forge URI and path specified" do host = 'http://fake.com/test' path = '/module/foo.tar.gz' uri = [ host, path ].join('') repository = Puppet::Forge::Repository.new(host, agent) repository.cache.expects(:retrieve).with(uri) repository.retrieve(path) end describe "making a request" do before :each do proxy_settings_of("proxy", 1234) end it "returns the result object from the request" do result = "#{Object.new}" performs_an_http_request result do |http| http.expects(:request).with(responds_with(:path, "the_path")) end expect(repository.make_http_request("the_path")).to eq(result) end it "merges forge URI and path specified" do result = "#{Object.new}" performs_an_http_request result do |http| http.expects(:request).with(responds_with(:path, "/test/the_path/")) end repository = Puppet::Forge::Repository.new('http://fake.com/test', agent) expect(repository.make_http_request("/the_path/")).to eq(result) end it "handles trailing slashes when merging URI and path" do result = "#{Object.new}" performs_an_http_request result do |http| http.expects(:request).with(responds_with(:path, "/test/the_path")) end repository = Puppet::Forge::Repository.new('http://fake.com/test/', agent) expect(repository.make_http_request("/the_path")).to eq(result) end it 'returns the result object from a request with ssl' do result = "#{Object.new}" performs_an_https_request result do |http| http.expects(:request).with(responds_with(:path, "the_path")) end expect(ssl_repository.make_http_request("the_path")).to eq(result) end it 'return a valid exception when there is an SSL verification problem' do performs_an_https_request "#{Object.new}" do |http| http.expects(:request).with(responds_with(:path, "the_path")).raises OpenSSL::SSL::SSLError.new("certificate verify failed") end expect { ssl_repository.make_http_request("the_path") }.to raise_error Puppet::Forge::Errors::SSLVerifyError, 'Unable to verify the SSL certificate at https://fake.com' end it 'return a valid exception when there is a communication problem' do performs_an_http_request "#{Object.new}" do |http| http.expects(:request).with(responds_with(:path, "the_path")).raises SocketError end expect { repository.make_http_request("the_path") }. to raise_error Puppet::Forge::Errors::CommunicationError, 'Unable to connect to the server at http://fake.com. Detail: SocketError.' end it "sets the user agent for the request" do path = 'the_path' request = repository.get_request_object(path) expect(request['User-Agent']).to match(/\b#{agent}\b/) expect(request['User-Agent']).to match(/\bPuppet\b/) expect(request['User-Agent']).to match(/\bRuby\b/) end it "Does not set Authorization header by default" do Puppet.features.stubs(:pe_license?).returns(false) Puppet[:forge_authorization] = nil request = repository.get_request_object("the_path") expect(request['Authorization']).to eq(nil) end it "Sets Authorization header from config" do token = 'bearer some token' Puppet[:forge_authorization] = token request = repository.get_request_object("the_path") expect(request['Authorization']).to eq(token) end it "encodes the received URI" do unescaped_uri = "héllo world !! ç à" performs_an_http_request do |http| http.expects(:request).with(responds_with(:path, Puppet::Util.uri_encode(unescaped_uri))) end repository.make_http_request(unescaped_uri) end def performs_an_http_request(result = nil, &block) proxy_args = ["proxy", 1234, nil, nil] mock_proxy(80, proxy_args, result, &block) end def performs_an_https_request(result = nil, &block) proxy_args = ["proxy", 1234, nil, nil] proxy = mock_proxy(443, proxy_args, result, &block) proxy.expects(:use_ssl=).with(true) proxy.expects(:cert_store=) proxy.expects(:verify_mode=).with(OpenSSL::SSL::VERIFY_PEER) end end describe "making a request against an authentiated proxy" do before :each do authenticated_proxy_settings_of("proxy", 1234, 'user1', 'password') end it "returns the result object from the request" do result = "#{Object.new}" performs_an_authenticated_http_request result do |http| http.expects(:request).with(responds_with(:path, "the_path")) end expect(repository.make_http_request("the_path")).to eq(result) end it 'returns the result object from a request with ssl' do result = "#{Object.new}" performs_an_authenticated_https_request result do |http| http.expects(:request).with(responds_with(:path, "the_path")) end expect(ssl_repository.make_http_request("the_path")).to eq(result) end it 'return a valid exception when there is an SSL verification problem' do performs_an_authenticated_https_request "#{Object.new}" do |http| http.expects(:request).with(responds_with(:path, "the_path")).raises OpenSSL::SSL::SSLError.new("certificate verify failed") end expect { ssl_repository.make_http_request("the_path") }.to raise_error Puppet::Forge::Errors::SSLVerifyError, 'Unable to verify the SSL certificate at https://fake.com' end it 'return a valid exception when there is a communication problem' do performs_an_authenticated_http_request "#{Object.new}" do |http| http.expects(:request).with(responds_with(:path, "the_path")).raises SocketError end expect { repository.make_http_request("the_path") }. to raise_error Puppet::Forge::Errors::CommunicationError, 'Unable to connect to the server at http://fake.com. Detail: SocketError.' end it "sets the user agent for the request" do path = 'the_path' request = repository.get_request_object(path) expect(request['User-Agent']).to match(/\b#{agent}\b/) expect(request['User-Agent']).to match(/\bPuppet\b/) expect(request['User-Agent']).to match(/\bRuby\b/) end it "encodes the received URI" do unescaped_uri = "héllo world !! ç à" performs_an_authenticated_http_request do |http| http.expects(:request).with(responds_with(:path, Puppet::Util.uri_encode(unescaped_uri))) end repository.make_http_request(unescaped_uri) end def performs_an_authenticated_http_request(result = nil, &block) proxy_args = ["proxy", 1234, 'user1', 'password'] mock_proxy(80, proxy_args, result, &block) end def performs_an_authenticated_https_request(result = nil, &block) proxy_args = ["proxy", 1234, 'user1', 'password'] proxy = mock_proxy(443, proxy_args, result, &block) proxy.expects(:use_ssl=).with(true) proxy.expects(:cert_store=) proxy.expects(:verify_mode=).with(OpenSSL::SSL::VERIFY_PEER) end end def proxy_settings_of(host, port) Puppet[:http_proxy_host] = host Puppet[:http_proxy_port] = port end def authenticated_proxy_settings_of(host, port, user, password) Puppet[:http_proxy_host] = host Puppet[:http_proxy_port] = port Puppet[:http_proxy_user] = user Puppet[:http_proxy_password] = password end def mock_proxy(port, proxy_args, result, &block) http = mock("http client") proxy = mock("http proxy") proxy_class = mock("http proxy class") Net::HTTP.expects(:Proxy).with(*proxy_args).returns(proxy_class) proxy_class.expects(:new).with("fake.com", port).returns(proxy) proxy.expects(:open_timeout=) proxy.expects(:read_timeout=) proxy.expects(:start).yields(http).returns(result) yield http proxy end end puppet-5.5.10/spec/unit/forge_spec.rb0000644005276200011600000002133713417161721017372 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/forge' require 'net/http' require 'puppet/module_tool' describe Puppet::Forge do let(:http_response) do <<-EOF { "pagination": { "limit": 1, "offset": 0, "first": "/v3/modules?limit=1&offset=0", "previous": null, "current": "/v3/modules?limit=1&offset=0", "next": null, "total": 1832 }, "results": [ { "uri": "/v3/modules/puppetlabs-bacula", "name": "bacula", "downloads": 640274, "created_at": "2011-05-24 18:34:58 -0700", "updated_at": "2013-12-03 15:24:20 -0800", "owner": { "uri": "/v3/users/puppetlabs", "username": "puppetlabs", "gravatar_id": "fdd009b7c1ec96e088b389f773e87aec" }, "current_release": { "uri": "/v3/releases/puppetlabs-bacula-0.0.2", "module": { "uri": "/v3/modules/puppetlabs-bacula", "name": "bacula", "owner": { "uri": "/v3/users/puppetlabs", "username": "puppetlabs", "gravatar_id": "fdd009b7c1ec96e088b389f773e87aec" } }, "version": "0.0.2", "metadata": { "types": [], "license": "Apache 2.0", "checksums": { }, "version": "0.0.2", "source": "git://github.com/puppetlabs/puppetlabs-bacula.git", "project_page": "https://github.com/puppetlabs/puppetlabs-bacula", "summary": "bacula", "dependencies": [ ], "author": "puppetlabs", "name": "puppetlabs-bacula" }, "tags": [ "backup", "bacula" ], "file_uri": "/v3/files/puppetlabs-bacula-0.0.2.tar.gz", "file_size": 67586, "file_md5": "bbf919d7ee9d278d2facf39c25578bf8", "downloads": 565041, "readme": "", "changelog": "", "license": "", "created_at": "2013-05-13 08:31:19 -0700", "updated_at": "2013-05-13 08:31:19 -0700", "deleted_at": null }, "releases": [ { "uri": "/v3/releases/puppetlabs-bacula-0.0.2", "version": "0.0.2" }, { "uri": "/v3/releases/puppetlabs-bacula-0.0.1", "version": "0.0.1" } ], "homepage_url": "https://github.com/puppetlabs/puppetlabs-bacula", "issues_url": "https://projects.puppetlabs.com/projects/bacula/issues" } ] } EOF end let(:search_results) do JSON.parse(http_response)['results'].map do |hash| hash.merge( "author" => "puppetlabs", "name" => "bacula", "tag_list" => ["backup", "bacula"], "full_name" => "puppetlabs/bacula", "version" => "0.0.2", "project_url" => "https://github.com/puppetlabs/puppetlabs-bacula", "desc" => "bacula" ) end end let(:forge) { Puppet::Forge.new } def repository_responds_with(response) Puppet::Forge::Repository.any_instance.stubs(:make_http_request).returns(response) end it "returns a list of matches from the forge when there are matches for the search term" do repository_responds_with(stub(:body => http_response, :code => '200')) expect(forge.search('bacula')).to eq(search_results) end context "when module_groups are defined" do let(:release_response) do releases = JSON.parse(http_response) releases['results'] = [] JSON.dump(releases) end before :each do repository_responds_with(stub(:body => release_response, :code => '200')).with {|uri| uri =~ /module_groups=foo/} Puppet[:module_groups] = "foo" end it "passes module_groups with search" do forge.search('bacula') end it "passes module_groups with fetch" do forge.fetch('puppetlabs-bacula') end end # See PUP-8008 context "when multiple module_groups are defined" do let(:release_response) do releases = JSON.parse(http_response) releases['results'] = [] JSON.dump(releases) end context "with space seperator" do before :each do repository_responds_with(stub(:body => release_response, :code => '200')).with {|uri| uri =~ /module_groups=foo bar/} Puppet[:module_groups] = "foo bar" end it "passes module_groups with search" do forge.search('bacula') end it "passes module_groups with fetch" do forge.fetch('puppetlabs-bacula') end end context "with plus seperator" do before :each do repository_responds_with(stub(:body => release_response, :code => '200')).with {|uri| uri =~ /module_groups=foo bar/} Puppet[:module_groups] = "foo+bar" end it "passes module_groups with search" do forge.search('bacula') end it "passes module_groups with fetch" do forge.fetch('puppetlabs-bacula') end end # See PUP-8008 context "when there are multiple pages of results" do before(:each) do Puppet::Forge::Repository.any_instance.expects(:make_http_request).with {|uri| uri =~ /module_groups=foo bar/ && uri !=~ /offset/ }.returns(stub(:body => first_page, :code => '200')) # Request for second page should not have module_groups already encoded Puppet::Forge::Repository.any_instance.expects(:make_http_request).with {|uri| uri =~ /module_groups=foo bar/ && uri =~ /offset=1/ }.returns(stub(:body => last_page, :code => '200')) end context "with space seperator" do before(:each) do Puppet[:module_groups] = "foo bar" end let(:first_page) do resp = JSON.parse(http_response) resp['results'] = [] resp['pagination']['next'] = "/v3/modules?limit=1&offset=1&module_groups=foo%20bar" JSON.dump(resp) end let(:last_page) do resp = JSON.parse(http_response) resp['results'] = [] resp['pagination']['current'] = "/v3/modules?limit=1&offset=1&module_groups=foo%20bar" JSON.dump(resp) end it "traverses pages during search" do forge.search('bacula') end it "traverses pages during fetch" do forge.fetch('puppetlabs-bacula') end end context "with plus seperator" do before(:each) do Puppet[:module_groups] = "foo+bar" end let(:first_page) do resp = JSON.parse(http_response) resp['results'] = [] resp['pagination']['next'] = "/v3/modules?limit=1&offset=1&module_groups=foo+bar" JSON.dump(resp) end let(:last_page) do resp = JSON.parse(http_response) resp['results'] = [] resp['pagination']['current'] = "/v3/modules?limit=1&offset=1&module_groups=foo+bar" JSON.dump(resp) end it "traverses pages during search" do forge.search('bacula') end it "traverses pages during fetch" do forge.fetch('puppetlabs-bacula') end end end end context "when the connection to the forge fails" do before :each do repository_responds_with(stub(:body => '{}', :code => '404', :message => "not found")) end it "raises an error for search" do expect { forge.search('bacula') }.to raise_error Puppet::Forge::Errors::ResponseError, "Request to Puppet Forge failed. Detail: 404 not found." end it "raises an error for fetch" do expect { forge.fetch('puppetlabs/bacula') }.to raise_error Puppet::Forge::Errors::ResponseError, "Request to Puppet Forge failed. Detail: 404 not found." end end context "when the API responds with an error" do before :each do repository_responds_with(stub(:body => '{"error":"invalid module"}', :code => '410', :message => "Gone")) end it "raises an error for fetch" do expect { forge.fetch('puppetlabs/bacula') }.to raise_error Puppet::Forge::Errors::ResponseError, "Request to Puppet Forge failed. Detail: 410 Gone." end end context "when the forge returns a module with unparseable dependencies" do before :each do response = JSON.parse(http_response) release = response['results'][0]['current_release'] release['metadata']['dependencies'] = [{'name' => 'broken-garbage >= 1.0.0', 'version_requirement' => 'banana'}] response['results'] = [release] repository_responds_with(stub(:body => JSON.dump(response), :code => '200')) end it "ignores modules with unparseable dependencies" do expect(forge.fetch('puppetlabs/bacula')).to be_empty end end end puppet-5.5.10/spec/unit/functions/0000755005276200011600000000000013417162176016740 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/functions/all_spec.rb0000644005276200011600000000530513417161721021045 0ustar jenkinsjenkinsrequire 'puppet' require 'spec_helper' require 'puppet_spec/compiler' require 'shared_behaviours/iterative_functions' describe 'the all method' do include PuppetSpec::Compiler context "should be callable as" do it 'all on an array' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,2,3] $n = $a.all |$v| { $v > 0 } file { "$n": ensure => present } MANIFEST expect(catalog.resource(:file, "true")['ensure']).to eq('present') end it 'all on an array with index' do catalog = compile_to_catalog(<<-MANIFEST) $a = [0,2,4] $n = $a.all |$i, $v| { $v == $i * 2 } file { "$n": ensure => present } MANIFEST expect(catalog.resource(:file, "true")['ensure']).to eq('present') end it 'all on a hash selecting entries' do catalog = compile_to_catalog(<<-MANIFEST) $a = {0=>0,1=>2,2=>4} $n = $a.all |$e| { $e[1] == $e[0]*2 } file { "$n": ensure => present } MANIFEST expect(catalog.resource(:file, "true")['ensure']).to eq('present') end it 'all on a hash selecting key and value' do catalog = compile_to_catalog(<<-MANIFEST) $a = {0=>0,1=>2,2=>4} $n = $a.all |$k,$v| { $v == $k*2 } file { "$n": ensure => present } MANIFEST expect(catalog.resource(:file, "true")['ensure']).to eq('present') end end context "produces a boolean" do it 'true when boolean true is found' do catalog = compile_to_catalog(<<-MANIFEST) $a = [6,6,6] $n = $a.all |$v| { true } file { "$n": ensure => present } MANIFEST expect(catalog.resource(:file, "true")['ensure']).to eq('present') end it 'true when truthy is found' do catalog = compile_to_catalog(<<-MANIFEST) $a = [6,6,6] $n = $a.all |$v| { 42 } file { "$n": ensure => present } MANIFEST expect(catalog.resource(:file, "true")['ensure']).to eq('present') end it 'false when truthy is not found (all undef)' do catalog = compile_to_catalog(<<-MANIFEST) $a = [6,6,6] $n = $a.all |$v| { undef } file { "$n": ensure => present } MANIFEST expect(catalog.resource(:file, "false")['ensure']).to eq('present') end it 'false when truthy is not found (all false)' do catalog = compile_to_catalog(<<-MANIFEST) $a = [6,6,6] $n = $a.all |$v| { false } file { "$n": ensure => present } MANIFEST expect(catalog.resource(:file, "false")['ensure']).to eq('present') end end it_should_behave_like 'all iterative functions argument checks', 'any' it_should_behave_like 'all iterative functions hash handling', 'any' end puppet-5.5.10/spec/unit/functions/annotate_spec.rb0000644005276200011600000001314313417161721022105 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' require 'puppet_spec/compiler' describe 'the annotate function' do include PuppetSpec::Compiler let(:annotation) { <<-PUPPET } type MyAdapter = Object[{ parent => Annotation, attributes => { id => Integer, value => String[1] } }] PUPPET let(:annotation2) { <<-PUPPET } type MyAdapter2 = Object[{ parent => Annotation, attributes => { id => Integer, value => String[1] } }] PUPPET context 'with object and hash arguments' do it 'creates new annotation on object' do code = <<-PUPPET #{annotation} type MyObject = Object[{ }] $my_object = MyObject({}) MyAdapter.annotate($my_object, { 'id' => 2, 'value' => 'annotation value' }) notice(MyAdapter.annotate($my_object).value) PUPPET expect(eval_and_collect_notices(code)).to eql(['annotation value']) end it 'forces creation of new annotation' do code = <<-PUPPET #{annotation} type MyObject = Object[{ }] $my_object = MyObject({}) MyAdapter.annotate($my_object, { 'id' => 2, 'value' => 'annotation value' }) notice(MyAdapter.annotate($my_object).value) MyAdapter.annotate($my_object, { 'id' => 2, 'value' => 'annotation value 2' }) notice(MyAdapter.annotate($my_object).value) PUPPET expect(eval_and_collect_notices(code)).to eql(['annotation value', 'annotation value 2']) end end context 'with object and block arguments' do it 'creates new annotation on object' do code = <<-PUPPET #{annotation} type MyObject = Object[{ }] $my_object = MyObject({}) MyAdapter.annotate($my_object) || { { 'id' => 2, 'value' => 'annotation value' } } notice(MyAdapter.annotate($my_object).value) PUPPET expect(eval_and_collect_notices(code)).to eql(['annotation value']) end it 'does not recreate annotation' do code = <<-PUPPET #{annotation} type MyObject = Object[{ }] $my_object = MyObject({}) MyAdapter.annotate($my_object) || { notice('should call this'); { 'id' => 2, 'value' => 'annotation value' } } MyAdapter.annotate($my_object) || { notice('should not call this'); { 'id' => 2, 'value' => 'annotation value 2' } } notice(MyAdapter.annotate($my_object).value) PUPPET expect(eval_and_collect_notices(code)).to eql(['should call this', 'annotation value']) end end it "with object and 'clear' arguments, clears and returns annotation" do code = <<-PUPPET #{annotation} type MyObject = Object[{ }] $my_object = MyObject({}) MyAdapter.annotate($my_object, { 'id' => 2, 'value' => 'annotation value' }) notice(MyAdapter.annotate($my_object, clear).value) notice(MyAdapter.annotate($my_object) == undef) PUPPET expect(eval_and_collect_notices(code)).to eql(['annotation value', 'true']) end context 'when object is an annotated Type' do it 'finds annotation declared in the type' do code = <<-PUPPET #{annotation} type MyObject = Object[{ annotations => { MyAdapter => { 'id' => 2, 'value' => 'annotation value' } } }] notice(MyAdapter.annotate(MyObject).value) PUPPET expect(eval_and_collect_notices(code)).to eql(['annotation value']) end it 'fails attempts to clear a declared annotation' do code = <<-PUPPET #{annotation} type MyObject = Object[{ annotations => { MyAdapter => { 'id' => 2, 'value' => 'annotation value' } } }] notice(MyAdapter.annotate(MyObject).value) notice(MyAdapter.annotate(MyObject, clear).value) PUPPET expect { eval_and_collect_notices(code) }.to raise_error(/attempt to clear MyAdapter annotation declared on MyObject/) end it 'fails attempts to redefine a declared annotation' do code = <<-PUPPET #{annotation} type MyObject = Object[{ annotations => { MyAdapter => { 'id' => 2, 'value' => 'annotation value' } } }] notice(MyAdapter.annotate(MyObject).value) notice(MyAdapter.annotate(MyObject, { 'id' => 3, 'value' => 'some other value' }).value) PUPPET expect { eval_and_collect_notices(code) }.to raise_error(/attempt to redefine MyAdapter annotation declared on MyObject/) end it 'allows annotation that are not declared in the type' do code = <<-PUPPET #{annotation} #{annotation2} type MyObject = Object[{ annotations => { MyAdapter => { 'id' => 2, 'value' => 'annotation value' } } }] notice(MyAdapter.annotate(MyObject).value) notice(MyAdapter2.annotate(MyObject, { 'id' => 3, 'value' => 'some other value' }).value) PUPPET expect(eval_and_collect_notices(code)).to eql(['annotation value', 'some other value']) end end it 'used on Pcore, can add multiple annotations an object' do code = <<-PUPPET #{annotation} #{annotation2} type MyObject = Object[{ }] $my_object = Pcore.annotate(MyObject({}), { MyAdapter => { 'id' => 2, 'value' => 'annotation value' }, MyAdapter2 => { 'id' => 3, 'value' => 'second annotation value' } }) notice(MyAdapter.annotate($my_object).value) notice(MyAdapter2.annotate($my_object).value) PUPPET expect(eval_and_collect_notices(code)).to eql(['annotation value', 'second annotation value']) end end puppet-5.5.10/spec/unit/functions/any_spec.rb0000644005276200011600000000613613417161721021067 0ustar jenkinsjenkinsrequire 'puppet' require 'spec_helper' require 'puppet_spec/compiler' require 'shared_behaviours/iterative_functions' describe 'the any method' do include PuppetSpec::Compiler context "should be callable as" do it 'any on an array' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,2,3] $n = $a.any |$v| { $v == 2 } file { "$n": ensure => present } MANIFEST expect(catalog.resource(:file, "true")['ensure']).to eq('present') end it 'any on an array with index' do catalog = compile_to_catalog(<<-MANIFEST) $a = [6,6,6] $n = $a.any |$i, $v| { $i == 2 } file { "$n": ensure => present } MANIFEST expect(catalog.resource(:file, "true")['ensure']).to eq('present') end it 'any on a hash selecting entries' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'a'=>'ah','b'=>'be','c'=>'ce'} $n = $a.any |$e| { $e[1] == 'be' } file { "$n": ensure => present } MANIFEST expect(catalog.resource(:file, "true")['ensure']).to eq('present') end it 'any on a hash selecting key and value' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'a'=>'ah','b'=>'be','c'=>'ce'} $n = $a.any |$k, $v| { $v == 'be' } file { "$n": ensure => present } MANIFEST expect(catalog.resource(:file, "true")['ensure']).to eq('present') end end context 'stops iteration when result is known' do it 'true when boolean true is found' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,2,3] $n = $a.any |$v| { if $v == 1 { true } else { fail("unwanted") } } file { "$n": ensure => present } MANIFEST expect(catalog.resource(:file, "true")['ensure']).to eq('present') end end context "produces a boolean" do it 'true when boolean true is found' do catalog = compile_to_catalog(<<-MANIFEST) $a = [6,6,6] $n = $a.any |$v| { true } file { "$n": ensure => present } MANIFEST expect(catalog.resource(:file, "true")['ensure']).to eq('present') end it 'true when truthy is found' do catalog = compile_to_catalog(<<-MANIFEST) $a = [6,6,6] $n = $a.any |$v| { 42 } file { "$n": ensure => present } MANIFEST expect(catalog.resource(:file, "true")['ensure']).to eq('present') end it 'false when truthy is not found (all undef)' do catalog = compile_to_catalog(<<-MANIFEST) $a = [6,6,6] $n = $a.any |$v| { undef } file { "$n": ensure => present } MANIFEST expect(catalog.resource(:file, "false")['ensure']).to eq('present') end it 'false when truthy is not found (all false)' do catalog = compile_to_catalog(<<-MANIFEST) $a = [6,6,6] $n = $a.any |$v| { false } file { "$n": ensure => present } MANIFEST expect(catalog.resource(:file, "false")['ensure']).to eq('present') end end it_should_behave_like 'all iterative functions argument checks', 'any' it_should_behave_like 'all iterative functions hash handling', 'any' end puppet-5.5.10/spec/unit/functions/assert_type_spec.rb0000644005276200011600000000643213417161721022641 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' require 'puppet/loaders' require 'puppet_spec/compiler' describe 'the assert_type function' do include PuppetSpec::Compiler after(:all) { Puppet::Pops::Loaders.clear } let(:loaders) { Puppet::Pops::Loaders.new(Puppet::Node::Environment.create(:testing, [])) } let(:func) { loaders.puppet_system_loader.load(:function, 'assert_type') } it 'asserts compliant type by returning the value' do expect(func.call({}, type(String), 'hello world')).to eql('hello world') end it 'accepts type given as a String' do expect(func.call({}, 'String', 'hello world')).to eql('hello world') end it 'asserts non compliant type by raising an error' do expect do func.call({}, type(Integer), 'hello world') end.to raise_error(Puppet::Pops::Types::TypeAssertionError, /expects an Integer value, got String/) end it 'checks that first argument is a type' do expect do func.call({}, 10, 10) end.to raise_error(ArgumentError, "'assert_type' expects one of: (Type type, Any value, Callable[Type, Type] block?) rejected: parameter 'type' expects a Type value, got Integer (String type_string, Any value, Callable[Type, Type] block?) rejected: parameter 'type_string' expects a String value, got Integer") end it 'allows the second arg to be undef/nil)' do expect do func.call({}, optional(String), nil) end.to_not raise_error end it 'can be called with a callable that receives a specific type' do expected, actual, actual2 = func.call({}, 'Optional[String]', 1) { |expctd, actul| [expctd, actul, actul] } expect(expected.to_s).to eql('Optional[String]') expect(actual.to_s).to eql('Integer[1, 1]') expect(actual2.to_s).to eql('Integer[1, 1]') end def optional(type_ref) Puppet::Pops::Types::TypeFactory.optional(type(type_ref)) end def type(type_ref) Puppet::Pops::Types::TypeFactory.type_of(type_ref) end it 'can validate a resource type' do expect(eval_and_collect_notices("assert_type(Type[Resource], File['/tmp/test']) notice('ok')")).to eq(['ok']) end it 'can validate a type alias' do code = <<-CODE type UnprivilegedPort = Integer[1024,65537] assert_type(UnprivilegedPort, 5432) notice('ok') CODE expect(eval_and_collect_notices(code)).to eq(['ok']) end it 'can validate a type alias passed as a String' do code = <<-CODE type UnprivilegedPort = Integer[1024,65537] assert_type('UnprivilegedPort', 5432) notice('ok') CODE expect(eval_and_collect_notices(code)).to eq(['ok']) end it 'can validate and fail using a type alias' do code = <<-CODE type UnprivilegedPort = Integer[1024,65537] assert_type(UnprivilegedPort, 345) notice('ok') CODE expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /expects an UnprivilegedPort = Integer\[1024, 65537\] value, got Integer\[345, 345\]/) end it 'will use infer_set to report detailed information about complex mismatches' do code = <<-CODE assert_type(Struct[{a=>Integer,b=>Boolean}], {a=>hej,x=>s}) CODE expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /entry 'a' expects an Integer value, got String.*expects a value for key 'b'.*unrecognized key 'x'/m) end end puppet-5.5.10/spec/unit/functions/binary_file_spec.rb0000644005276200011600000000307313417161721022560 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' require 'puppet_spec/files' describe 'the binary_file function' do include PuppetSpec::Compiler include Matchers::Resource include PuppetSpec::Files def with_file_content(content) path = tmpfile('find-file-function') file = File.new(path, 'wb') file.sync = true file.print content yield path end it 'reads an existing absolute file' do with_file_content('one') do |one| # Note that Binary to String produced Base64 encoded version of 'one' which is 'b23l' expect(compile_to_catalog("notify { String(binary_file('#{one}')):}")).to have_resource("Notify[b25l]") end end it 'errors on non existing files' do expect do with_file_content('one') do |one| compile_to_catalog("notify { binary_file('#{one}/nope'):}") end end.to raise_error(/The given file '.+\/nope' does not exist/) end it 'reads an existing file in a module' do with_file_content('binary_data') do |name| mod = mock 'module' mod.stubs(:file).with('myfile').returns(name) Puppet[:code] = "notify { String(binary_file('mymod/myfile')):}" node = Puppet::Node.new('localhost') compiler = Puppet::Parser::Compiler.new(node) compiler.environment.stubs(:module).with('mymod').returns(mod) # Note that the Binary to string produces Base64 encoded version of 'binary_data' which is 'YmluYXJ5X2RhdGE=' expect(compiler.compile().filter { |r| r.virtual? }).to have_resource("Notify[YmluYXJ5X2RhdGE=]") end end end puppet-5.5.10/spec/unit/functions/break_spec.rb0000644005276200011600000001627213417161721021366 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' describe 'the break function' do include PuppetSpec::Compiler include Matchers::Resource context do it 'breaks iteration as if at end of input in a map for an array' do expect(compile_to_catalog(<<-CODE)).to have_resource('Notify[[1, 2]]') function please_break() { [1,2,3].map |$x| { if $x == 3 { break() } $x } } notify { String(please_break()): } CODE end it 'breaks iteration as if at end of input in a map for a hash' do expect(compile_to_catalog(<<-CODE)).to have_resource('Notify[[1, 2]]') function please_break() { {'a' => 1, 'b' => 2, 'c' => 3}.map |$x, $y| { if $y == 3 { break() } $y } } notify { String(please_break()): } CODE end it 'breaks iteration as if at end of input in a reduce for an array' do expect(compile_to_catalog(<<-CODE)).to have_resource('Notify[6]') function please_break() { [1,2,3,4].reduce |$memo, $x| { if $x == 4 { break() } $memo + $x } } notify { String(please_break()): } CODE end it 'breaks iteration as if at end of input in a reduce for a hash' do expect(compile_to_catalog(<<-CODE)).to have_resource("Notify[['abc', 6]]") function please_break() { {'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4}.reduce |$memo, $x| { if $x[1] == 4 { break() } $string = "${memo[0]}${x[0]}" $number = $memo[1] + $x[1] [$string, $number] } } notify { String(please_break()): } CODE end it 'breaks iteration as if at end of input in an each for an array' do expect(compile_to_catalog(<<-CODE)).to_not have_resource('Notify[3]') function please_break() { [1,2,3].each |$x| { if $x == 3 { break() } notify { "$x": } } } please_break() CODE end it 'breaks iteration as if at end of input in an each for a hash' do expect(compile_to_catalog(<<-CODE)).to_not have_resource('Notify[3]') function please_break() { {'a' => 1, 'b' => 2, 'c' => 3}.each |$x, $y| { if $y == 3 { break() } notify { "$y": } } } please_break() CODE end it 'breaks iteration as if at end of input in a reverse_each' do expect(compile_to_catalog(<<-CODE)).to have_resource('Notify[2]') function please_break() { [1,2,3].reverse_each |$x| { if $x == 1 { break() } notify { "$x": } } } please_break() CODE end it 'breaks iteration as if at end of input in a map for a hash' do expect(compile_to_catalog(<<-CODE)).to have_resource('Notify[[1, 2]]') function please_break() { {'a' => 1, 'b' => 2, 'c' => 3}.map |$x, $y| { if $y == 3 { break() } $y } } notify { String(please_break()): } CODE end it 'breaks iteration as if at end of input in a reduce for an array' do expect(compile_to_catalog(<<-CODE)).to have_resource('Notify[6]') function please_break() { [1,2,3,4].reduce |$memo, $x| { if $x == 4 { break() } $memo + $x } } notify { String(please_break()): } CODE end it 'breaks iteration as if at end of input in a reduce for a hash' do expect(compile_to_catalog(<<-CODE)).to have_resource("Notify[['abc', 6]]") function please_break() { {'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4}.reduce |$memo, $x| { if $x[1] == 4 { break() } $string = "${memo[0]}${x[0]}" $number = $memo[1] + $x[1] [$string, $number] } } notify { String(please_break()): } CODE end it 'breaks iteration as if at end of input in an each for an array' do expect(compile_to_catalog(<<-CODE)).to_not have_resource('Notify[3]') function please_break() { [1,2,3].each |$x| { if $x == 3 { break() } notify { "$x": } } } please_break() CODE end it 'breaks iteration as if at end of input in an each for a hash' do expect(compile_to_catalog(<<-CODE)).to_not have_resource('Notify[3]') function please_break() { {'a' => 1, 'b' => 2, 'c' => 3}.each |$x, $y| { if $y == 3 { break() } notify { "$y": } } } please_break() CODE end it 'breaks iteration as if at end of input in a reverse_each' do expect(compile_to_catalog(<<-CODE)).to have_resource('Notify[2]') function please_break() { [1,2,3].reverse_each |$x| { if $x == 1 { break() } notify { "$x": } } } please_break() CODE end it 'does not provide early exit from a class' do # A break would semantically mean that the class should not be included - as if the # iteration over class names should stop. That is too magic and should # be done differently by the user. # expect do compile_to_catalog(<<-CODE) class does_break { notice 'a' if 1 == 1 { break() } # avoid making next line statically unreachable notice 'b' } include(does_break) CODE end.to raise_error(/break\(\) from context where this is illegal \(file: unknown, line: 3\) on node.*/) end it 'does not provide early exit from a define' do # A break would semantically mean that the resource should not be created - as if the # iteration over resource titles should stop. That is too magic and should # be done differently by the user. # expect do compile_to_catalog(<<-CODE) define does_break { notice 'a' if 1 == 1 { break() } # avoid making next line statically unreachable notice 'b' } does_break { 'no_you_cannot': } CODE end.to raise_error(/break\(\) from context where this is illegal \(file: unknown, line: 3\) on node.*/) end it 'can be called when nested in a function to make that function behave as a break' do # This allows functions like break_when(...) to be implemented by calling break() conditionally # expect(eval_and_collect_notices(<<-CODE)).to eql(['[100]']) function nested_break($x) { if $x == 2 { break() } else { $x * 100 } } function example() { [1,2,3].map |$x| { nested_break($x) } } notice example() CODE end it 'can not be called nested from top scope' do expect do compile_to_catalog(<<-CODE) # line 1 # line 2 $result = with(1) |$x| { with($x) |$x| {break() }} notice $result CODE end.to raise_error(/break\(\) from context where this is illegal \(file: unknown, line: 3\) on node.*/) end it 'can not be called from top scope' do expect do compile_to_catalog(<<-CODE) # line 1 # line 2 break() CODE end.to raise_error(/break\(\) from context where this is illegal \(file: unknown, line: 3\) on node.*/) end end end puppet-5.5.10/spec/unit/functions/contain_spec.rb0000644005276200011600000001556413417161721021740 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/compiler' require 'puppet/parser/functions' require 'matchers/containment_matchers' require 'matchers/resource' require 'matchers/include_in_order' require 'unit/functions/shared' describe 'The "contain" function' do include PuppetSpec::Compiler include ContainmentMatchers include Matchers::Resource before(:each) do compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("foo")) @scope = Puppet::Parser::Scope.new(compiler) end it "includes the class" do catalog = compile_to_catalog(<<-MANIFEST) class contained { notify { "contained": } } class container { contain contained } include container MANIFEST expect(catalog.classes).to include("contained") end it "includes the class when using a fully qualified anchored name" do catalog = compile_to_catalog(<<-MANIFEST) class contained { notify { "contained": } } class container { contain ::contained } include container MANIFEST expect(catalog.classes).to include("contained") end it "ensures that the edge is with the correct class" do catalog = compile_to_catalog(<<-MANIFEST) class outer { class named { } contain outer::named } class named { } include named include outer MANIFEST expect(catalog).to have_resource("Class[Named]") expect(catalog).to have_resource("Class[Outer]") expect(catalog).to have_resource("Class[Outer::Named]") expect(catalog).to contain_class("outer::named").in("outer") end it "makes the class contained in the current class" do catalog = compile_to_catalog(<<-MANIFEST) class contained { notify { "contained": } } class container { contain contained } include container MANIFEST expect(catalog).to contain_class("contained").in("container") end it "can contain multiple classes" do catalog = compile_to_catalog(<<-MANIFEST) class a { notify { "a": } } class b { notify { "b": } } class container { contain a, b } include container MANIFEST expect(catalog).to contain_class("a").in("container") expect(catalog).to contain_class("b").in("container") end context "when containing a class in multiple classes" do it "creates a catalog with all containment edges" do catalog = compile_to_catalog(<<-MANIFEST) class contained { notify { "contained": } } class container { contain contained } class another { contain contained } include container include another MANIFEST expect(catalog).to contain_class("contained").in("container") expect(catalog).to contain_class("contained").in("another") end it "and there are no dependencies applies successfully" do manifest = <<-MANIFEST class contained { notify { "contained": } } class container { contain contained } class another { contain contained } include container include another MANIFEST expect { apply_compiled_manifest(manifest) }.not_to raise_error end it "and there are explicit dependencies on the containing class causes a dependency cycle" do manifest = <<-MANIFEST class contained { notify { "contained": } } class container { contain contained } class another { contain contained } include container include another Class["container"] -> Class["another"] MANIFEST expect { apply_compiled_manifest(manifest) }.to raise_error( Puppet::Error, /One or more resource dependency cycles detected in graph/ ) end end it "does not create duplicate edges" do catalog = compile_to_catalog(<<-MANIFEST) class contained { notify { "contained": } } class container { contain contained contain contained } include container MANIFEST contained = catalog.resource("Class", "contained") container = catalog.resource("Class", "container") expect(catalog.edges_between(container, contained)).to have(1).item end context "when a containing class has a dependency order" do it "the contained class is applied in that order" do catalog = compile_to_relationship_graph(<<-MANIFEST) class contained { notify { "contained": } } class container { contain contained } class first { notify { "first": } } class last { notify { "last": } } include container, first, last Class["first"] -> Class["container"] -> Class["last"] MANIFEST expect(order_resources_traversed_in(catalog)).to include_in_order( "Notify[first]", "Notify[contained]", "Notify[last]" ) end end it 'produces an array with a single class references given a single argument' do catalog = compile_to_catalog(<<-MANIFEST) class a { notify { "a": } } class container { $x = contain(a) Array[Type[Class], 1, 1].assert_type($x) notify { 'feedback': message => "$x" } } include container MANIFEST feedback = catalog.resource("Notify", "feedback") expect(feedback[:message]).to eql("[Class[a]]") end it 'produces an array with class references given multiple arguments' do catalog = compile_to_catalog(<<-MANIFEST) class a { notify { "a": } } class b { notify { "b": } } class container { $x = contain(a, b) Array[Type[Class], 2, 2].assert_type($x) notify { 'feedback': message => "$x" } } include container MANIFEST feedback = catalog.resource("Notify", "feedback") expect(feedback[:message]).to eql("[Class[a], Class[b]]") end it 'allows the result to be used in a relationship operation' do catalog = compile_to_catalog(<<-MANIFEST) class a { notify { "a": } } class b { notify { "b": } } notify { 'c': } class container { contain(a, b) -> Notify[c] } include container MANIFEST # Assert relationships are formed expect(catalog.resource("Class", "a")[:before][0]).to eql('Notify[c]') expect(catalog.resource("Class", "b")[:before][0]).to eql('Notify[c]') end it_should_behave_like 'all functions transforming relative to absolute names', :contain it_should_behave_like 'an inclusion function, regardless of the type of class reference,', :contain it_should_behave_like 'an inclusion function, when --tasks is on,', :contain end puppet-5.5.10/spec/unit/functions/defined_spec.rb0000644005276200011600000002447413417161721021703 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' require 'puppet/loaders' describe "the 'defined' function" do after(:all) { Puppet::Pops::Loaders.clear } # This loads the function once and makes it easy to call it # It does not matter that it is not bound to the env used later since the function # looks up everything via the scope that is given to it. # The individual tests needs to have a fresh env/catalog set up # let(:loaders) { Puppet::Pops::Loaders.new(Puppet::Node::Environment.create(:testing, [])) } let(:func) { loaders.puppet_system_loader.load(:function, 'defined') } before :each do # A fresh environment is needed for each test since tests creates types and resources environment = Puppet::Node::Environment.create(:testing, []) @node = Puppet::Node.new('yaynode', :environment => environment) @known_resource_types = environment.known_resource_types @compiler = Puppet::Parser::Compiler.new(@node) @scope = Puppet::Parser::Scope.new(@compiler) end def newclass(name) @known_resource_types.add Puppet::Resource::Type.new(:hostclass, name) end def newdefine(name) @known_resource_types.add Puppet::Resource::Type.new(:definition, name) end def newresource(type, title) resource = Puppet::Resource.new(type, title) @compiler.add_resource(@scope, resource) resource end #--- CLASS # context 'can determine if a class' do context 'is defined' do it 'by using the class name in string form' do newclass 'yayness' expect(func.call(@scope, 'yayness')).to be_truthy end it 'by using a Type[Class[name]] type reference' do name = 'yayness' newclass name class_type = Puppet::Pops::Types::TypeFactory.host_class(name) type_type = Puppet::Pops::Types::TypeFactory.type_type(class_type) expect(func.call(@scope, type_type)).to be_truthy end end context 'is not defined' do it 'by using the class name in string form' do expect(func.call(@scope, 'yayness')).to be_falsey end it 'even if there is a define, by using a Type[Class[name]] type reference' do name = 'yayness' newdefine name class_type = Puppet::Pops::Types::TypeFactory.host_class(name) type_type = Puppet::Pops::Types::TypeFactory.type_type(class_type) expect(func.call(@scope, type_type)).to be_falsey end end context 'is defined and realized' do it 'by using a Class[name] reference' do name = 'cowabunga' newclass name newresource(:class, name) class_type = Puppet::Pops::Types::TypeFactory.host_class(name) expect(func.call(@scope, class_type)).to be_truthy end end context 'is not realized' do it '(although defined) by using a Class[name] reference' do name = 'cowabunga' newclass name class_type = Puppet::Pops::Types::TypeFactory.host_class(name) expect(func.call(@scope, class_type)).to be_falsey end it '(and not defined) by using a Class[name] reference' do name = 'cowabunga' class_type = Puppet::Pops::Types::TypeFactory.host_class(name) expect(func.call(@scope, class_type)).to be_falsey end end end #---RESOURCE TYPE # context 'can determine if a resource type' do context 'is defined' do it 'by using the type name (of a built in type) in string form' do expect(func.call(@scope, 'file')).to be_truthy end it 'by using the type name (of a resource type) in string form' do newdefine 'yayness' expect(func.call(@scope, 'yayness')).to be_truthy end it 'by using a File type reference (built in type)' do resource_type = Puppet::Pops::Types::TypeFactory.resource('file') type_type = Puppet::Pops::Types::TypeFactory.type_type(resource_type) expect(func.call(@scope, type_type)).to be_truthy end it 'by using a Type[File] type reference' do resource_type = Puppet::Pops::Types::TypeFactory.resource('file') type_type = Puppet::Pops::Types::TypeFactory.type_type(resource_type) expect(func.call(@scope, type_type)).to be_truthy end it 'by using a Resource[T] type reference (defined type)' do name = 'yayness' newdefine name resource_type = Puppet::Pops::Types::TypeFactory.resource(name) expect(func.call(@scope, resource_type)).to be_truthy end it 'by using a Type[Resource[T]] type reference (defined type)' do name = 'yayness' newdefine name resource_type = Puppet::Pops::Types::TypeFactory.resource(name) type_type = Puppet::Pops::Types::TypeFactory.type_type(resource_type) expect(func.call(@scope, type_type)).to be_truthy end end context 'is not defined' do it 'by using the resource name in string form' do expect(func.call(@scope, 'notatype')).to be_falsey end it 'even if there is a class with the same name, by using a Type[Resource[T]] type reference' do name = 'yayness' newclass name resource_type = Puppet::Pops::Types::TypeFactory.resource(name) type_type = Puppet::Pops::Types::TypeFactory.type_type(resource_type) expect(func.call(@scope, type_type)).to be_falsey end end context 'is defined and instance realized' do it 'by using a Resource[T, title] reference for a built in type' do type_name = 'file' title = '/tmp/myfile' newdefine type_name newresource(type_name, title) class_type = Puppet::Pops::Types::TypeFactory.resource(type_name, title) expect(func.call(@scope, class_type)).to be_truthy end it 'by using a Resource[T, title] reference for a defined type' do type_name = 'meme' title = 'cowabunga' newdefine type_name newresource(type_name, title) class_type = Puppet::Pops::Types::TypeFactory.resource(type_name, title) expect(func.call(@scope, class_type)).to be_truthy end end context 'is not realized' do it '(although defined) by using a Resource[T, title] reference or Type[Resource[T, title]] reference' do type_name = 'meme' title = 'cowabunga' newdefine type_name resource_type = Puppet::Pops::Types::TypeFactory.resource(type_name, title) expect(func.call(@scope, resource_type)).to be_falsey type_type = Puppet::Pops::Types::TypeFactory.type_type(resource_type) expect(func.call(@scope, type_type)).to be_falsey end it '(and not defined) by using a Resource[T, title] reference or Type[Resource[T, title]] reference' do type_name = 'meme' title = 'cowabunga' resource_type = Puppet::Pops::Types::TypeFactory.resource(type_name, title) expect(func.call(@scope, resource_type)).to be_falsey type_type = Puppet::Pops::Types::TypeFactory.type_type(resource_type) expect(func.call(@scope, type_type)).to be_falsey end end end #---VARIABLES # context 'can determine if a variable' do context 'is defined' do it 'by giving the variable in string form' do @scope['x'] = 'something' expect(func.call(@scope, '$x')).to be_truthy end it 'by giving a :: prefixed variable in string form' do @compiler.topscope['x'] = 'something' expect(func.call(@scope, '$::x')).to be_truthy end it 'by giving a numeric variable in string form (when there is a match scope)' do # with no match scope, there are no numeric variables defined expect(func.call(@scope, '$0')).to be_falsey expect(func.call(@scope, '$42')).to be_falsey pattern = Regexp.new('.*') @scope.new_match_scope(pattern.match('anything')) # with a match scope, all numeric variables are set (the match defines if they have a value or not, but they are defined) # even if their value is undef. expect(func.call(@scope, '$0')).to be_truthy expect(func.call(@scope, '$42')).to be_truthy end end context 'is undefined' do it 'by giving a :: prefixed or regular variable in string form' do expect(func.call(@scope, '$x')).to be_falsey expect(func.call(@scope, '$::x')).to be_falsey end end end context 'has any? semantics when given multiple arguments' do it 'and one of the names is a defined user defined type' do newdefine 'yayness' expect(func.call(@scope, 'meh', 'yayness', 'booness')).to be_truthy end it 'and one of the names is a built type' do expect(func.call(@scope, 'meh', 'file', 'booness')).to be_truthy end it 'and one of the names is a defined class' do newclass 'yayness' expect(func.call(@scope, 'meh', 'yayness', 'booness')).to be_truthy end it 'is true when at least one variable exists in scope' do @scope['x'] = 'something' expect(func.call(@scope, '$y', '$x', '$z')).to be_truthy end it 'is false when none of the names are defined' do expect(func.call(@scope, 'meh', 'yayness', 'booness')).to be_falsey end end it 'raises an argument error when asking if Resource type is defined' do resource_type = Puppet::Pops::Types::TypeFactory.resource expect { func.call(@scope, resource_type)}.to raise_error(ArgumentError, /reference to all.*type/) end it 'raises an argument error if you ask if Class is defined' do class_type = Puppet::Pops::Types::TypeFactory.host_class expect { func.call(@scope, class_type) }.to raise_error(ArgumentError, /reference to all.*class/) end it 'raises error if referencing undef' do expect{func.call(@scope, nil)}.to raise_error(ArgumentError, /'defined' parameter 'vals' expects a value of type String, Type\[CatalogEntry\], or Type\[Type\], got Undef/) end it 'raises error if referencing a number' do expect{func.call(@scope, 42)}.to raise_error(ArgumentError, /'defined' parameter 'vals' expects a value of type String, Type\[CatalogEntry\], or Type\[Type\], got Integer/) end it 'is false if referencing empty string' do expect(func.call(@scope, '')).to be_falsey end it "is true if referencing 'main'" do # mimic what compiler does with "main" in intial import newclass '' newresource :class, '' expect(func.call(@scope, 'main')).to be_truthy end end puppet-5.5.10/spec/unit/functions/dig_spec.rb0000644005276200011600000000377413417161721021050 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' describe 'the dig function' do include PuppetSpec::Compiler include Matchers::Resource it 'returns a value from an array index via integer index' do expect(compile_to_catalog("notify { [testing].dig(0): }")).to have_resource('Notify[testing]') end it 'returns undef if given an undef key' do expect(compile_to_catalog(<<-SOURCE)).to have_resource('Notify[test-Undef-ing]') notify { "test-${type([testing].dig(undef))}-ing": } SOURCE end it 'returns undef if starting with undef' do expect(compile_to_catalog(<<-SOURCE)).to have_resource('Notify[test-Undef-ing]') notify { "test-${type(undef.dig(undef))}-ing": } SOURCE end it 'returns a value from an hash key via given key' do expect(compile_to_catalog("notify { {key => testing}.dig(key): }")).to have_resource('Notify[testing]') end it 'continues digging if result is an array' do expect(compile_to_catalog("notify { [nope, [testing]].dig(1, 0): }")).to have_resource('Notify[testing]') end it 'continues digging if result is a hash' do expect(compile_to_catalog("notify { [nope, {yes => testing}].dig(1, yes): }")).to have_resource('Notify[testing]') end it 'stops digging when step is undef' do expect(compile_to_catalog(<<-SOURCE)).to have_resource('Notify[testing]') $result = [nope, {yes => testing}].dig(1, no, 2) notify { "test${result}ing": } SOURCE end it 'errors if step is neither Array nor Hash' do expect { compile_to_catalog(<<-SOURCE)}.to raise_error(/The given data does not contain a Collection at \[1, "yes"\], got 'String'/) $result = [nope, {yes => testing}].dig(1, yes, 2) notify { "test${result}ing": } SOURCE end it 'errors if not given a non Collection as the starting point' do expect { compile_to_catalog(<<-SOURCE)}.to raise_error(/'dig' parameter 'data' expects a value of type Undef or Collection, got String/) "hello".dig(1, yes, 2) SOURCE end end puppet-5.5.10/spec/unit/functions/each_spec.rb0000644005276200011600000000712713417161721021201 0ustar jenkinsjenkinsrequire 'puppet' require 'spec_helper' require 'puppet_spec/compiler' require 'shared_behaviours/iterative_functions' describe 'the each method' do include PuppetSpec::Compiler context "should be callable as" do it 'each on an array selecting each value' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,2,3] $a.each |$v| { file { "/file_$v": ensure => present } } MANIFEST expect(catalog.resource(:file, "/file_1")['ensure']).to eq('present') expect(catalog.resource(:file, "/file_2")['ensure']).to eq('present') expect(catalog.resource(:file, "/file_3")['ensure']).to eq('present') end it 'each on an array selecting each value - function call style' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,2,3] each ($a) |$index, $v| { file { "/file_$v": ensure => present } } MANIFEST expect(catalog.resource(:file, "/file_1")['ensure']).to eq('present') expect(catalog.resource(:file, "/file_2")['ensure']).to eq('present') expect(catalog.resource(:file, "/file_3")['ensure']).to eq('present') end it 'each on an array with index' do catalog = compile_to_catalog(<<-MANIFEST) $a = [present, absent, present] $a.each |$k,$v| { file { "/file_${$k+1}": ensure => $v } } MANIFEST expect(catalog.resource(:file, "/file_1")['ensure']).to eq('present') expect(catalog.resource(:file, "/file_2")['ensure']).to eq('absent') expect(catalog.resource(:file, "/file_3")['ensure']).to eq('present') end it 'each on a hash selecting entries' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'a'=>'present','b'=>'absent','c'=>'present'} $a.each |$e| { file { "/file_${e[0]}": ensure => $e[1] } } MANIFEST expect(catalog.resource(:file, "/file_a")['ensure']).to eq('present') expect(catalog.resource(:file, "/file_b")['ensure']).to eq('absent') expect(catalog.resource(:file, "/file_c")['ensure']).to eq('present') end it 'each on a hash selecting key and value' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'a'=>present,'b'=>absent,'c'=>present} $a.each |$k, $v| { file { "/file_$k": ensure => $v } } MANIFEST expect(catalog.resource(:file, "/file_a")['ensure']).to eq('present') expect(catalog.resource(:file, "/file_b")['ensure']).to eq('absent') expect(catalog.resource(:file, "/file_c")['ensure']).to eq('present') end it 'each on a hash selecting key and value (using captures-last parameter)' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'a'=>present,'b'=>absent,'c'=>present} $a.each |*$kv| { file { "/file_${kv[0]}": ensure => $kv[1] } } MANIFEST expect(catalog.resource(:file, "/file_a")['ensure']).to eq('present') expect(catalog.resource(:file, "/file_b")['ensure']).to eq('absent') expect(catalog.resource(:file, "/file_c")['ensure']).to eq('present') end end context "should produce receiver" do it 'each checking produced value using single expression' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1, 3, 2] $b = $a.each |$x| { "unwanted" } file { "/file_${b[1]}": ensure => present } MANIFEST expect(catalog.resource(:file, "/file_3")['ensure']).to eq('present') end end it_should_behave_like 'all iterative functions argument checks', 'each' it_should_behave_like 'all iterative functions hash handling', 'each' end puppet-5.5.10/spec/unit/functions/empty_spec.rb0000644005276200011600000000536613417161721021442 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' describe 'the empty function' do include PuppetSpec::Compiler include Matchers::Resource let(:logs) { [] } let(:warnings) { logs.select { |log| log.level == :warning }.map { |log| log.message } } context 'for an array it' do it 'returns true when empty' do expect(compile_to_catalog("notify { String(empty([])): }")).to have_resource('Notify[true]') end it 'returns false when not empty' do expect(compile_to_catalog("notify { String(empty([1])): }")).to have_resource('Notify[false]') end end context 'for a hash it' do it 'returns true when empty' do expect(compile_to_catalog("notify { String(empty({})): }")).to have_resource('Notify[true]') end it 'returns false when not empty' do expect(compile_to_catalog("notify { String(empty({1=>1})): }")).to have_resource('Notify[false]') end end context 'for numeric values it' do it 'always returns false for integer values (including 0)' do Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do expect(compile_to_catalog("notify { String(empty(0)): }")).to have_resource('Notify[false]') end expect(warnings).to include(/Calling function empty\(\) with Numeric value is deprecated/) end it 'always returns false for float values (including 0.0)' do Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do expect(compile_to_catalog("notify { String(empty(0.0)): }")).to have_resource('Notify[false]') end expect(warnings).to include(/Calling function empty\(\) with Numeric value is deprecated/) end end context 'for a string it' do it 'returns true when empty' do expect(compile_to_catalog("notify { String(empty('')): }")).to have_resource('Notify[true]') end it 'returns false when not empty' do expect(compile_to_catalog("notify { String(empty(' ')): }")).to have_resource('Notify[false]') end end context 'for a binary it' do it 'returns true when empty' do expect(compile_to_catalog("notify { String(empty(Binary(''))): }")).to have_resource('Notify[true]') end it 'returns false when not empty' do expect(compile_to_catalog("notify { String(empty(Binary('b25l'))): }")).to have_resource('Notify[false]') end end context 'for undef it' do it 'returns true without deprecation warning' do Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do expect(compile_to_catalog("notify { String(empty(undef)): }")).to have_resource('Notify[true]') end expect(warnings).to_not include(/Calling function empty\(\) with Undef value is deprecated/) end end end puppet-5.5.10/spec/unit/functions/epp_spec.rb0000644005276200011600000001527713417161721021072 0ustar jenkinsjenkinsrequire 'spec_helper' describe "the epp function" do include PuppetSpec::Files let :node do Puppet::Node.new('localhost') end let :compiler do Puppet::Parser::Compiler.new(node) end let :scope do compiler.topscope end context "when accessing scope variables as $ variables" do it "looks up the value from the scope" do scope["what"] = "are belong" expect(eval_template("all your base <%= $what %> to us")).to eq("all your base are belong to us") end it "looks up a fully qualified value from the scope" do scope["what::is"] = "are belong" expect(eval_template("all your base <%= $what::is %> to us")).to eq("all your base are belong to us") end it "get nil accessing a variable that does not exist" do expect(eval_template("<%= $kryptonite == undef %>")).to eq("true") end it "gets error accessing a variable that is malformed" do expect { eval_template("<%= $kryptonite::bbbbbbbbbbbb::cccccccc::ddd::USER %>")}.to raise_error( /Illegal variable name, The given name 'kryptonite::bbbbbbbbbbbb::cccccccc::ddd::USER' does not conform to the naming rule/) end it "gets error accessing a variable that is malformed as reported in PUP-7848" do expect { eval_template("USER='<%= $hg_oais::archivematica::requirements::automation_tools::USER %>'")}.to raise_error( /Illegal variable name, The given name 'hg_oais::archivematica::requirements::automation_tools::USER' does not conform to the naming rule/) end it "get nil accessing a variable that is undef" do scope['undef_var'] = nil expect(eval_template("<%= $undef_var == undef %>")).to eq("true") end it "gets shadowed variable if args are given" do scope['phantom'] = 'of the opera' expect(eval_template_with_args("<%= $phantom == dragos %>", 'phantom' => 'dragos')).to eq("true") end it "can use values from the global scope for defaults" do scope['phantom'] = 'of the opera' expect(eval_template("<%- |$phantom = $::phantom| -%><%= $phantom %>")).to eq("of the opera") end it "will not use values from the enclosing scope for defaults" do scope['the_phantom'] = 'of the state opera' scope.new_ephemeral(true) scope['the_phantom'] = 'of the local opera' expect(scope['the_phantom']).to eq('of the local opera') expect(eval_template("<%- |$phantom = $the_phantom| -%><%= $phantom %>")).to eq("of the state opera") end it "uses the default value if the given value is undef/nil" do expect(eval_template_with_args("<%- |$phantom = 'inside your mind'| -%><%= $phantom %>", 'phantom' => nil)).to eq("inside your mind") end it "gets shadowed variable if args are given and parameters are specified" do scope['x'] = 'wrong one' expect(eval_template_with_args("<%- |$x| -%><%= $x == correct %>", 'x' => 'correct')).to eq("true") end it "raises an error if required variable is not given" do scope['x'] = 'wrong one' expect do eval_template("<%-| $x |-%><%= $x == correct %>") end.to raise_error(/expects a value for parameter 'x'/) end it 'raises an error if invalid arguments are given' do scope['x'] = 'wrong one' expect do eval_template_with_args("<%-| $x |-%><%= $x == correct %>", 'x' => 'correct', 'y' => 'surplus') end.to raise_error(/has no parameter named 'y'/) end end context "when given an empty template" do it "allows the template file to be empty" do expect(eval_template("")).to eq("") end it "allows the template to have empty body after parameters" do expect(eval_template_with_args("<%-|$x|%>", 'x'=>1)).to eq("") end end context "when using typed parameters" do it "allows a passed value that matches the parameter's type" do expect(eval_template_with_args("<%-|String $x|-%><%= $x == correct %>", 'x' => 'correct')).to eq("true") end it "does not allow slurped parameters" do expect do eval_template_with_args("<%-|*$x|-%><%= $x %>", 'x' => 'incorrect') end.to raise_error(/'captures rest' - not supported in an Epp Template/) end it "raises an error when the passed value does not match the parameter's type" do expect do eval_template_with_args("<%-|Integer $x|-%><%= $x %>", 'x' => 'incorrect') end.to raise_error(/parameter 'x' expects an Integer value, got String/) end it "raises an error when the default value does not match the parameter's type" do expect do eval_template("<%-|Integer $x = 'nope'|-%><%= $x %>") end.to raise_error(/parameter 'x' expects an Integer value, got String/) end it "allows an parameter to default to undef" do expect(eval_template("<%-|Optional[Integer] $x = undef|-%><%= $x == undef %>")).to eq("true") end end it "preserves CRLF when reading the template" do expect(eval_template("some text that\r\nis static with CRLF")).to eq("some text that\r\nis static with CRLF") end # although never a problem with epp it "is not interfered with by having a variable named 'string' (#14093)" do scope['string'] = "this output should not be seen" expect(eval_template("some text that is static")).to eq("some text that is static") end it "has access to a variable named 'string' (#14093)" do scope['string'] = "the string value" expect(eval_template("string was: <%= $string %>")).to eq("string was: the string value") end describe 'when loading from modules' do include PuppetSpec::Files it 'an epp template is found' do modules_dir = dir_containing('modules', { 'testmodule' => { 'templates' => { 'the_x.epp' => 'The x is <%= $x %>' } }}) Puppet.override({:current_environment => (env = Puppet::Node::Environment.create(:testload, [ modules_dir ]))}, "test") do node.environment = env expect(epp_function.call(scope, 'testmodule/the_x.epp', { 'x' => '3'} )).to eql("The x is 3") end end end def eval_template_with_args(content, args_hash) file_path = tmpdir('epp_spec_content') filename = File.join(file_path, "template.epp") File.open(filename, "wb+") { |f| f.write(content) } Puppet::Parser::Files.stubs(:find_template).returns(filename) epp_function.call(scope, 'template', args_hash) end def eval_template(content) file_path = tmpdir('epp_spec_content') filename = File.join(file_path, "template.epp") File.open(filename, "wb+") { |f| f.write(content) } Puppet::Parser::Files.stubs(:find_template).returns(filename) epp_function.call(scope, 'template') end def epp_function() scope.compiler.loaders.public_environment_loader.load(:function, 'epp') end end puppet-5.5.10/spec/unit/functions/find_file_spec.rb0000644005276200011600000000447313417161721022221 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' require 'puppet_spec/files' describe 'the find_file function' do include PuppetSpec::Compiler include Matchers::Resource include PuppetSpec::Files def with_file_content(content) path = tmpfile('find-file-function') file = File.new(path, 'wb') file.sync = true file.print content yield path end it 'finds an existing absolute file when given arguments individually' do with_file_content('one') do |one| with_file_content('two') do |two| expect(compile_to_catalog("notify { find_file('#{one}', '#{two}'):}")).to have_resource("Notify[#{one}]") end end end it 'skips non existing files' do with_file_content('one') do |one| with_file_content('two') do |two| expect(compile_to_catalog("notify { find_file('#{one}/nope', '#{two}'):}")).to have_resource("Notify[#{two}]") end end end it 'accepts arguments given as an array' do with_file_content('one') do |one| with_file_content('two') do |two| expect(compile_to_catalog("notify { find_file(['#{one}', '#{two}']):}")).to have_resource("Notify[#{one}]") end end end it 'finds an existing file in a module' do with_file_content('file content') do |name| mod = mock 'module' mod.stubs(:file).with('myfile').returns(name) Puppet[:code] = "notify { find_file('mymod/myfile'):}" node = Puppet::Node.new('localhost') compiler = Puppet::Parser::Compiler.new(node) compiler.environment.stubs(:module).with('mymod').returns(mod) expect(compiler.compile().filter { |r| r.virtual? }).to have_resource("Notify[#{name}]") end end it 'returns undef when none of the paths were found' do mod = mock 'module' mod.stubs(:file).with('myfile').returns(nil) Puppet[:code] = "notify { String(type(find_file('mymod/myfile', 'nomod/nofile'))):}" node = Puppet::Node.new('localhost') compiler = Puppet::Parser::Compiler.new(node) # For a module that does not have the file compiler.environment.stubs(:module).with('mymod').returns(mod) # For a module that does not exist compiler.environment.stubs(:module).with('nomod').returns(nil) expect(compiler.compile().filter { |r| r.virtual? }).to have_resource("Notify[Undef]") end end puppet-5.5.10/spec/unit/functions/flatten_spec.rb0000644005276200011600000000241213417161721021726 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' describe 'the flatten function' do include PuppetSpec::Compiler include Matchers::Resource let(:array_fmt) { { 'format' => "%(a", 'separator'=>""} } it 'returns flattened array of all its given arguments' do expect(compile_to_catalog("notify { String([1,[2,[3]]].flatten, Array => #{array_fmt}): }")).to have_resource('Notify[(123)]') end it 'accepts a single non array value which results in it being wrapped in an array' do expect(compile_to_catalog("notify { String(flatten(1), Array => #{array_fmt}): }")).to have_resource('Notify[(1)]') end it 'accepts a single array value - (which is a noop)' do expect(compile_to_catalog("notify { String(flatten([1]), Array => #{array_fmt}): }")).to have_resource('Notify[(1)]') end it 'it does not flatten a hash - it is a value that gets wrapped' do expect(compile_to_catalog("notify { String(flatten({a=>1}), Array => #{array_fmt}): }")).to have_resource("Notify[({'a' => 1})]") end it 'accepts mix of array and non array arguments and concatenates and flattens them' do expect(compile_to_catalog("notify { String(flatten([1],2,[[3,4]]), Array => #{array_fmt}): }")).to have_resource('Notify[(1234)]') end end puppet-5.5.10/spec/unit/functions/include_spec.rb0000644005276200011600000001130413417161721021714 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/compiler' require 'puppet/parser/functions' require 'matchers/containment_matchers' require 'matchers/resource' require 'matchers/include_in_order' require 'unit/functions/shared' describe 'The "include" function' do include PuppetSpec::Compiler include ContainmentMatchers include Matchers::Resource before(:each) do compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("foo")) @scope = Puppet::Parser::Scope.new(compiler) end it "includes a class" do catalog = compile_to_catalog(<<-MANIFEST) class included { notify { "included": } } include included MANIFEST expect(catalog.classes).to include("included") end it "includes a class when using a fully qualified anchored name" do catalog = compile_to_catalog(<<-MANIFEST) class included { notify { "included": } } include ::included MANIFEST expect(catalog.classes).to include("included") end it "includes multiple classes" do catalog = compile_to_catalog(<<-MANIFEST) class included { notify { "included": } } class included_too { notify { "included_too": } } include included, included_too MANIFEST expect(catalog.classes).to include("included") expect(catalog.classes).to include("included_too") end it "includes multiple classes given as an array" do catalog = compile_to_catalog(<<-MANIFEST) class included { notify { "included": } } class included_too { notify { "included_too": } } include [included, included_too] MANIFEST expect(catalog.classes).to include("included") expect(catalog.classes).to include("included_too") end it "flattens nested arrays" do catalog = compile_to_catalog(<<-MANIFEST) class included { notify { "included": } } class included_too { notify { "included_too": } } include [[[included], [[[included_too]]]]] MANIFEST expect(catalog.classes).to include("included") expect(catalog.classes).to include("included_too") end it "raises an error if class does not exist" do expect { compile_to_catalog(<<-MANIFEST) include the_god_in_your_religion MANIFEST }.to raise_error(Puppet::Error) end { "''" => 'empty string', 'undef' => 'undef', "['']" => 'empty string', "[undef]" => 'undef' }.each_pair do |value, name_kind| it "raises an error if class is #{name_kind}" do expect { compile_to_catalog(<<-MANIFEST) include #{value} MANIFEST }.to raise_error(/Cannot use #{name_kind}/) end end it "does not contained the included class in the current class" do catalog = compile_to_catalog(<<-MANIFEST) class not_contained { notify { "not_contained": } } class container { include not_contained } include container MANIFEST expect(catalog).to_not contain_class("not_contained").in("container") end it 'produces an array with a single class references given a single argument' do catalog = compile_to_catalog(<<-MANIFEST) class a { notify { "a": } } $x = include(a) Array[Type[Class], 1, 1].assert_type($x) notify { 'feedback': message => "$x" } MANIFEST feedback = catalog.resource("Notify", "feedback") expect(feedback[:message]).to eql("[Class[a]]") end it 'produces an array with class references given multiple arguments' do catalog = compile_to_catalog(<<-MANIFEST) class a { notify { "a": } } class b { notify { "b": } } $x = include(a, b) Array[Type[Class], 2, 2].assert_type($x) notify { 'feedback': message => "$x" } MANIFEST feedback = catalog.resource("Notify", "feedback") expect(feedback[:message]).to eql("[Class[a], Class[b]]") end it 'allows the result to be used in a relationship operation' do catalog = compile_to_catalog(<<-MANIFEST) class a { notify { "a": } } class b { notify { "b": } } notify { 'c': } include(a, b) -> Notify[c] MANIFEST # Assert relationships are formed expect(catalog.resource("Class", "a")[:before][0]).to eql('Notify[c]') expect(catalog.resource("Class", "b")[:before][0]).to eql('Notify[c]') end it_should_behave_like 'all functions transforming relative to absolute names', :include it_should_behave_like 'an inclusion function, regardless of the type of class reference,', :include it_should_behave_like 'an inclusion function, when --tasks is on,', :include end puppet-5.5.10/spec/unit/functions/inline_epp_spec.rb0000644005276200011600000000565013417161721022422 0ustar jenkinsjenkins require 'spec_helper' describe "the inline_epp function" do include PuppetSpec::Files let :node do Puppet::Node.new('localhost') end let :compiler do Puppet::Parser::Compiler.new(node) end let :scope do Puppet::Parser::Scope.new(compiler) end context "when accessing scope variables as $ variables" do it "looks up the value from the scope" do scope["what"] = "are belong" expect(eval_template("all your base <%= $what %> to us")).to eq("all your base are belong to us") end it "get nil accessing a variable that does not exist" do expect(eval_template("<%= $kryptonite == undef %>")).to eq("true") end it "get nil accessing a variable that is undef" do scope['undef_var'] = :undef expect(eval_template("<%= $undef_var == undef %>")).to eq("true") end it "gets shadowed variable if args are given" do scope['phantom'] = 'of the opera' expect(eval_template_with_args("<%= $phantom == dragos %>", 'phantom' => 'dragos')).to eq("true") end it "gets shadowed variable if args are given and parameters are specified" do scope['x'] = 'wrong one' expect(eval_template_with_args("<%-| $x |-%><%= $x == correct %>", 'x' => 'correct')).to eq("true") end it "raises an error if required variable is not given" do scope['x'] = 'wrong one' expect { eval_template_with_args("<%-| $x |-%><%= $x == correct %>", {}) }.to raise_error(/expects a value for parameter 'x'/) end it 'raises an error if unexpected arguments are given' do scope['x'] = 'wrong one' expect { eval_template_with_args("<%-| $x |-%><%= $x == correct %>", 'x' => 'correct', 'y' => 'surplus') }.to raise_error(/has no parameter named 'y'/) end end context "when given an empty template" do it "allows the template file to be empty" do expect(eval_template("")).to eq("") end it "allows the template to have empty body after parameters" do expect(eval_template_with_args("<%-|$x|%>", 'x'=>1)).to eq("") end end it "renders a block expression" do expect(eval_template_with_args("<%= { $y = $x $x + 1} %>", 'x' => 2)).to eq("3") end # although never a problem with epp it "is not interfered with by having a variable named 'string' (#14093)" do scope['string'] = "this output should not be seen" expect(eval_template("some text that is static")).to eq("some text that is static") end it "has access to a variable named 'string' (#14093)" do scope['string'] = "the string value" expect(eval_template("string was: <%= $string %>")).to eq("string was: the string value") end def eval_template_with_args(content, args_hash) epp_function.call(scope, content, args_hash) end def eval_template(content) epp_function.call(scope, content) end def epp_function() scope.compiler.loaders.public_environment_loader.load(:function, 'inline_epp') end end puppet-5.5.10/spec/unit/functions/join_spec.rb0000644005276200011600000000215213417161721021231 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' describe 'the join function' do include PuppetSpec::Compiler include Matchers::Resource it 'joins an array with empty string delimiter if delimiter is not given' do expect(compile_to_catalog("notify { join([1,2,3]): }")).to have_resource('Notify[123]') end it 'joins an array with given string delimiter' do expect(compile_to_catalog("notify { join([1,2,3],'x'): }")).to have_resource('Notify[1x2x3]') end it 'results in empty string if array is empty' do expect(compile_to_catalog('notify { "x${join([])}y": }')).to have_resource('Notify[xy]') end it 'flattens nested arrays' do expect(compile_to_catalog("notify { join([1,2,[3,4]]): }")).to have_resource('Notify[1234]') end it 'does not flatten arrays nested in hashes' do expect(compile_to_catalog("notify { join([1,2,{a => [3,4]}]): }")).to have_resource('Notify[12{"a"=>[3, 4]}]') end it 'formats nil/undef as empty string' do expect(compile_to_catalog('notify { join([undef, undef], "x"): }')).to have_resource('Notify[x]') end end puppet-5.5.10/spec/unit/functions/keys_spec.rb0000644005276200011600000000165713417161721021256 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' describe 'the keys function' do include PuppetSpec::Compiler include Matchers::Resource it 'returns the keys in the hash in the order they appear in a hash iteration' do expect(compile_to_catalog(<<-'SRC'.unindent)).to have_resource('Notify[apples & oranges]') $k = {'apples' => 1, 'oranges' => 2}.keys notify { "${k[0]} & ${k[1]}": } SRC end it 'returns an empty array for an empty hash' do expect(compile_to_catalog(<<-'SRC'.unindent)).to have_resource('Notify[0]') $v = {}.keys.reduce(0) |$m, $v| { $m+1 } notify { "${v}": } SRC end it 'includes an undef key if one is present in the hash' do expect(compile_to_catalog(<<-'SRC'.unindent)).to have_resource('Notify[Undef]') $types = {undef => 1}.keys.map |$v| { $v.type } notify { "${types[0]}": } SRC end end puppet-5.5.10/spec/unit/functions/length_spec.rb0000644005276200011600000000316113417161721021554 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' describe 'the length function' do include PuppetSpec::Compiler include Matchers::Resource context 'for an array it' do it 'returns 0 when empty' do expect(compile_to_catalog("notify { String(length([])): }")).to have_resource('Notify[0]') end it 'returns number of elements when not empty' do expect(compile_to_catalog("notify { String(length([1, 2, 3])): }")).to have_resource('Notify[3]') end end context 'for a hash it' do it 'returns 0 empty' do expect(compile_to_catalog("notify { String(length({})): }")).to have_resource('Notify[0]') end it 'returns number of elements when not empty' do expect(compile_to_catalog("notify { String(length({1=>1,2=>2})): }")).to have_resource('Notify[2]') end end context 'for a string it' do it 'returns 0 when empty' do expect(compile_to_catalog("notify { String(length('')): }")).to have_resource('Notify[0]') end it 'returns number of characters when not empty' do # note the multibyte characters - åäö each taking two bytes in UTF-8 expect(compile_to_catalog('notify { String(length("\u00e5\u00e4\u00f6")): }')).to have_resource('Notify[3]') end end context 'for a binary it' do it 'returns 0 when empty' do expect(compile_to_catalog("notify { String(length(Binary(''))): }")).to have_resource('Notify[0]') end it 'returns number of bytes when not empty' do expect(compile_to_catalog("notify { String(length(Binary('b25l'))): }")).to have_resource('Notify[3]') end end end puppet-5.5.10/spec/unit/functions/lest_spec.rb0000644005276200011600000000207013417161721021240 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' describe 'the lest function' do include PuppetSpec::Compiler include Matchers::Resource it 'calls a lambda passing no argument' do expect(compile_to_catalog("lest(undef) || { notify { testing: } }")).to have_resource('Notify[testing]') end it 'produces what lambda returns if value is undef' do expect(compile_to_catalog("notify{ lest(undef) || { testing }: }")).to have_resource('Notify[testing]') end it 'does not call lambda if argument is not undef' do expect(compile_to_catalog('lest(1) || { notify { "failed": } }')).to_not have_resource('Notify[failed]') end it 'produces given argument if given not undef' do expect(compile_to_catalog(<<-SOURCE)).to have_resource('Notify[test_yay_ing]') notify{ "test${lest('_yay_') || { '_oh_no_' }}ing": } SOURCE end it 'errors when lambda wants too many args' do expect do compile_to_catalog('lest(1) |$x| { }') end.to raise_error(/'lest' block expects no arguments, got 1/m) end end puppet-5.5.10/spec/unit/functions/lookup_fixture_spec.rb0000644005276200011600000007646513417161721023373 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/compiler' require 'puppet_spec/files' require 'puppet/pops' require 'deep_merge/core' # Tests the lookup function using fixtures describe 'The lookup function' do include PuppetSpec::Compiler # Assembles code that includes the *abc* class and compiles it into a catalog. This class will use the global # variable $args to perform a lookup and assign the result to $abc::result. Unless the $block is set to # the string 'no_block_present', it will be passed as a lambda to the lookup. The assembled code will declare # a notify resource with a name that is formed by interpolating the result into a format string. # # The method performs the folloging steps. # # - Build the code that: # - sets the $args variable from _lookup_args_ # - sets the $block parameter to the given block or the string 'no_block_present' # - includes the abc class # - assigns the $abc::result to $r # - interpolates a string using _fmt_ (which is assumed to use $r) # - declares a notify resource from the interpolated string # - Compile the code into a catalog # - Return the name of all Notify resources in that catalog # # @param fmt [String] The puppet interpolated string used when creating the notify title # @param *args [String] splat of args that will be concatenated to form the puppet args sent to lookup # @return [Array] List of names of Notify resources in the resulting catalog # def assemble_and_compile(fmt, *lookup_args, &block) assemble_and_compile_with_block(fmt, "'no_block_present'", *lookup_args, &block) end def assemble_and_compile_with_block(fmt, block, *lookup_args, &cblock) compile_and_get_notifications(<<-END.unindent, &cblock) $args = [#{lookup_args.join(',')}] $block = #{block} include abc $r = if $abc::result == undef { 'no_value' } else { $abc::result } notify { \"#{fmt}\": } END end def compile_and_get_notifications(code) Puppet[:code] = code node.environment.check_for_reparse catalog = block_given? ? compiler.compile { |cat| yield(compiler.topscope); cat } : compiler.compile catalog.resources.map(&:ref).select { |r| r.start_with?('Notify[') }.map { |r| r[7..-2] } end # There is a fully configured 'production' environment in fixtures at this location let(:environmentpath) { File.join(my_fixture_dir, 'environments') } let(:node) { Puppet::Node.new("testnode", :facts => Puppet::Node::Facts.new("facts", {}), :environment => 'production') } let(:compiler) { Puppet::Parser::Compiler.new(node) } around(:each) do |example| # Initialize settings to get a full compile as close as possible to a real # environment load Puppet.settings.initialize_global_settings # Initialize loaders based on the environmentpath. It does not work to # just set the setting environmentpath for some reason - this achieves the same: # - first a loader is created, loading directory environments from the fixture (there is # one environment, 'production', which will be loaded since the node references this # environment by name). # - secondly, the created env loader is set as 'environments' in the puppet context. # environments = Puppet::Environments::Directories.new(environmentpath, []) Puppet.override(:environments => environments) do example.run end end context 'using valid parameters' do it 'can lookup value provided by the environment' do resources = assemble_and_compile('${r}', "'abc::a'") expect(resources).to include('env_a') end it 'can lookup value provided by the module' do resources = assemble_and_compile('${r}', "'abc::b'") expect(resources).to include('module_b') end it "can lookup value provided by the module that has 'function' data_provider entry in metadata.json" do resources = compile_and_get_notifications("$args = ['meta::b']\ninclude meta\nnotify { $meta::result: }\n") expect(resources).to include('module_b') end it 'can lookup value provided in global scope' do Puppet.settings[:hiera_config] = File.join(my_fixture_dir, 'hiera.yaml') resources = assemble_and_compile('${r}', "'abc::a'") expect(resources).to include('global_a') end it 'will stop at first found name when several names are provided' do resources = assemble_and_compile('${r}', "['abc::b', 'abc::a']") expect(resources).to include('module_b') end it 'can lookup value provided by the module that is overriden by environment' do resources = assemble_and_compile('${r}', "'abc::c'") expect(resources).to include('env_c') end it "can 'unique' merge values provided by both the module and the environment" do resources = assemble_and_compile('${r[0]}_${r[1]}', "'abc::c'", 'Array[String]', "'unique'") expect(resources).to include('env_c_module_c') end it "can 'hash' merge values provided by the environment only" do resources = assemble_and_compile('${r[k1]}_${r[k2]}_${r[k3]}', "'abc::d'", 'Hash[String,String]', "'hash'") expect(resources).to include('env_d1_env_d2_env_d3') end it "can 'hash' merge values provided by both the environment and the module" do resources = assemble_and_compile('${r[k1]}_${r[k2]}_${r[k3]}', "'abc::e'", 'Hash[String,String]', "'hash'") expect(resources).to include('env_e1_module_e2_env_e3') end it "can 'hash' merge values provided by global, environment, and module" do Puppet.settings[:hiera_config] = File.join(my_fixture_dir, 'hiera.yaml') resources = assemble_and_compile('${r[k1]}_${r[k2]}_${r[k3]}', "'abc::e'", 'Hash[String,String]', "'hash'") expect(resources).to include('global_e1_module_e2_env_e3') end it "can pass merge parameter in the form of a hash with a 'strategy=>unique'" do resources = assemble_and_compile('${r[0]}_${r[1]}', "'abc::c'", 'Array[String]', "{strategy => 'unique'}") expect(resources).to include('env_c_module_c') end it "can pass merge parameter in the form of a hash with 'strategy=>hash'" do resources = assemble_and_compile('${r[k1]}_${r[k2]}_${r[k3]}', "'abc::e'", 'Hash[String,String]', "{strategy => 'hash'}") expect(resources).to include('env_e1_module_e2_env_e3') end it "can pass merge parameter in the form of a hash with a 'strategy=>deep'" do resources = assemble_and_compile('${r[k1]}_${r[k2]}_${r[k3]}', "'abc::e'", 'Hash[String,String]', "{strategy => 'deep'}") expect(resources).to include('env_e1_module_e2_env_e3') end it "will fail unless merge in the form of a hash contains a 'strategy'" do expect do assemble_and_compile('${r[k1]}_${r[k2]}_${r[k3]}', "'abc::e'", 'Hash[String,String]', "{merge_key => 'hash'}") end.to raise_error(Puppet::ParseError, /hash given as 'merge' must contain the name of a strategy/) end it 'will raise an exception when value is not found for single key and no default is provided' do expect do assemble_and_compile('${r}', "'abc::x'") end.to raise_error(Puppet::ParseError, /did not find a value for the name 'abc::x'/) end it 'can lookup an undef value' do resources = assemble_and_compile('${r}', "'abc::n'") expect(resources).to include('no_value') end it 'will not replace an undef value with a given default' do resources = assemble_and_compile('${r}', "'abc::n'", 'undef', 'undef', '"default_n"') expect(resources).to include('no_value') end it 'will not accept a succesful lookup of an undef value when the type rejects it' do expect do assemble_and_compile('${r}', "'abc::n'", 'String') end.to raise_error(Puppet::ParseError, /Found value has wrong type, expects a String value, got Undef/) end it 'will raise an exception when value is not found for array key and no default is provided' do expect do assemble_and_compile('${r}', "['abc::x', 'abc::y']") end.to raise_error(Puppet::ParseError, /did not find a value for any of the names \['abc::x', 'abc::y'\]/) end it 'can lookup and deep merge shallow values provided by the environment only' do resources = assemble_and_compile('${r[k1]}_${r[k2]}_${r[k3]}', "'abc::d'", 'Hash[String,String]', "'deep'") expect(resources).to include('env_d1_env_d2_env_d3') end it 'can lookup and deep merge shallow values provided by both the module and the environment' do resources = assemble_and_compile('${r[k1]}_${r[k2]}_${r[k3]}', "'abc::e'", 'Hash[String,String]', "'deep'") expect(resources).to include('env_e1_module_e2_env_e3') end it 'can lookup and deep merge deep values provided by global, environment, and module' do Puppet.settings[:hiera_config] = File.join(my_fixture_dir, 'hiera.yaml') resources = assemble_and_compile('${r[k1][s1]}_${r[k1][s2]}_${r[k1][s3]}_${r[k2][s1]}_${r[k2][s2]}_${r[k2][s3]}', "'abc::f'", 'Hash[String,Hash[String,String]]', "'deep'") expect(resources).to include('global_f11_env_f12_module_f13_env_f21_module_f22_global_f23') end it 'will propagate resolution_type :array to Hiera when merge == \'unique\'' do Puppet.settings[:hiera_config] = File.join(my_fixture_dir, 'hiera.yaml') resources = assemble_and_compile('${r[0]}_${r[1]}_${r[2]}', "'abc::c'", 'Array[String]', "'unique'") expect(resources).to include('global_c_env_c_module_c') end it 'will propagate a Hash resolution_type with :behavior => :native to Hiera when merge == \'hash\'' do Puppet.settings[:hiera_config] = File.join(my_fixture_dir, 'hiera.yaml') resources = assemble_and_compile('${r[k1]}_${r[k2]}_${r[k3]}', "'abc::e'", 'Hash[String,String]', "{strategy => 'hash'}") expect(resources).to include('global_e1_module_e2_env_e3') end it 'will propagate a Hash resolution_type with :behavior => :deeper to Hiera when merge == \'deep\'' do Puppet.settings[:hiera_config] = File.join(my_fixture_dir, 'hiera.yaml') resources = assemble_and_compile('${r[k1][s1]}_${r[k1][s2]}_${r[k1][s3]}_${r[k2][s1]}_${r[k2][s2]}_${r[k2][s3]}', "'abc::f'", 'Hash[String,Hash[String,String]]', "'deep'") expect(resources).to include('global_f11_env_f12_module_f13_env_f21_module_f22_global_f23') end it 'will propagate a Hash resolution_type with symbolic deep merge options to Hiera' do Puppet.settings[:hiera_config] = File.join(my_fixture_dir, 'hiera.yaml') resources = assemble_and_compile('${r[k1][s1]}_${r[k1][s2]}_${r[k1][s3]}_${r[k2][s1]}_${r[k2][s2]}_${r[k2][s3]}', "'abc::f'", 'Hash[String,Hash[String,String]]', "{ 'strategy' => 'deep', 'knockout_prefix' => '--' }") expect(resources).to include('global_f11_env_f12_module_f13_env_f21_module_f22_global_f23') end context 'with provided default' do it 'will return default when lookup fails' do resources = assemble_and_compile('${r}', "'abc::x'", 'String', 'undef', "'dflt_x'") expect(resources).to include('dflt_x') end it 'can precede default parameter with undef as the value_type and undef as the merge type' do resources = assemble_and_compile('${r}', "'abc::x'", 'undef', 'undef', "'dflt_x'") expect(resources).to include('dflt_x') end it 'can use array' do resources = assemble_and_compile('${r[0]}_${r[1]}', "'abc::x'", 'Array[String]', 'undef', "['dflt_x', 'dflt_y']") expect(resources).to include('dflt_x_dflt_y') end it 'can use hash' do resources = assemble_and_compile('${r[a]}_${r[b]}', "'abc::x'", 'Hash[String,String]', 'undef', "{'a' => 'dflt_x', 'b' => 'dflt_y'}") expect(resources).to include('dflt_x_dflt_y') end it 'fails unless default is an instance of value_type' do expect do assemble_and_compile('${r[a]}_${r[b]}', "'abc::x'", 'Hash[String,String]', 'undef', "{'a' => 'dflt_x', 'b' => 32}") end.to raise_error(Puppet::ParseError, /Default value has wrong type, entry 'b' expects a String value, got Integer/) end end context 'with a default block' do it 'will be called when lookup fails' do resources = assemble_and_compile_with_block('${r}', "'dflt_x'", "'abc::x'") expect(resources).to include('dflt_x') end it 'will not called when lookup succeeds but the found value is nil' do resources = assemble_and_compile_with_block('${r}', "'dflt_x'", "'abc::n'") expect(resources).to include('no_value') end it 'can use array' do resources = assemble_and_compile_with_block('${r[0]}_${r[1]}', "['dflt_x', 'dflt_y']", "'abc::x'") expect(resources).to include('dflt_x_dflt_y') end it 'can use hash' do resources = assemble_and_compile_with_block('${r[a]}_${r[b]}', "{'a' => 'dflt_x', 'b' => 'dflt_y'}", "'abc::x'") expect(resources).to include('dflt_x_dflt_y') end it 'can return undef from block' do resources = assemble_and_compile_with_block('${r}', 'undef', "'abc::x'") expect(resources).to include('no_value') end it 'fails unless block returns an instance of value_type' do expect do assemble_and_compile_with_block('${r[a]}_${r[b]}', "{'a' => 'dflt_x', 'b' => 32}", "'abc::x'", 'Hash[String,String]') end.to raise_error(Puppet::ParseError, /Value returned from default block has wrong type, entry 'b' expects a String value, got Integer/) end it 'receives a single name parameter' do resources = assemble_and_compile_with_block('${r}', 'true', "'name_x'") expect(resources).to include('name_x') end it 'receives an array name parameter' do resources = assemble_and_compile_with_block('${r[0]}_${r[1]}', 'true', "['name_x', 'name_y']") expect(resources).to include('name_x_name_y') end end end context 'and using dotted keys' do it 'can access values in data using dot notation' do source = <<-PUPPET function environment::data() { { a => { b => { c => 'the data' }}} } notice(lookup('a.b.c')) PUPPET expect(eval_and_collect_notices(source)).to include('the data') end it 'can find data using quoted dot notation' do source = <<-PUPPET function environment::data() { { 'a.b.c' => 'the data' } } notice(lookup('"a.b.c"')) PUPPET expect(eval_and_collect_notices(source)).to include('the data') end it 'can access values in data using a mix of dot notation and quoted dot notation' do source = <<-PUPPET function environment::data() { { 'a' => { 'b.c' => 'the data' }} } notice(lookup('a."b.c"')) PUPPET expect(eval_and_collect_notices(source)).to include('the data') end end context 'passing a hash as the only parameter' do it 'can pass a single name correctly' do resources = assemble_and_compile('${r}', "{name => 'abc::a'}") expect(resources).to include('env_a') end it 'can pass a an array of names correctly' do resources = assemble_and_compile('${r}', "{name => ['abc::b', 'abc::a']}") expect(resources).to include('module_b') end it 'can pass an override map and find values there even though they would be found' do resources = assemble_and_compile('${r}', "{name => 'abc::a', override => { abc::a => 'override_a'}}") expect(resources).to include('override_a') end it 'can pass an default_values_hash and find values there correctly' do resources = assemble_and_compile('${r}', "{name => 'abc::x', default_values_hash => { abc::x => 'extra_x'}}") expect(resources).to include('extra_x') end it 'can pass an default_values_hash but not use it when value is found elsewhere' do resources = assemble_and_compile('${r}', "{name => 'abc::a', default_values_hash => { abc::a => 'extra_a'}}") expect(resources).to include('env_a') end it 'can pass an default_values_hash but not use it when value is found elsewhere even when found value is undef' do resources = assemble_and_compile('${r}', "{name => 'abc::n', default_values_hash => { abc::n => 'extra_n'}}") expect(resources).to include('no_value') end it 'can pass an override and an default_values_hash and find the override value' do resources = assemble_and_compile('${r}', "{name => 'abc::x', override => { abc::x => 'override_x'}, default_values_hash => { abc::x => 'extra_x'}}") expect(resources).to include('override_x') end it 'will raise an exception when value is not found for single key and no default is provided' do expect do assemble_and_compile('${r}', "{name => 'abc::x'}") end.to raise_error(Puppet::ParseError, /did not find a value for the name 'abc::x'/) end it 'will not raise an exception when value is not found default value is nil' do resources = assemble_and_compile('${r}', "{name => 'abc::x', default_value => undef}") expect(resources).to include('no_value') end end context 'accessing from outside a module' do it 'will both log a warning and raise an exception when key in the function provided module data is not prefixed' do logs = [] Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do Puppet[:code] = "include bad_data\nlookup('bad_data::b')" expect { compiler.compile }.to raise_error(Puppet::ParseError, /did not find a value for the name 'bad_data::b'/) end warnings = logs.select { |log| log.level == :warning }.map { |log| log.message } expect(warnings).to include("Module 'bad_data': Value returned from deprecated API function 'bad_data::data' must use keys qualified with the name of the module") end it 'will succeed finding prefixed keys even when a key in the function provided module data is not prefixed' do logs = [] resources = nil Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do resources = compile_and_get_notifications(<<-PUPPET.unindent) include bad_data notify { lookup('bad_data::c'): } PUPPET expect(resources).to include('module_c') end warnings = logs.select { |log| log.level == :warning }.map { |log| log.message } expect(warnings).to include("Module 'bad_data': Value returned from deprecated API function 'bad_data::data' must use keys qualified with the name of the module") end it 'will resolve global, environment, and module correctly' do Puppet.settings[:hiera_config] = File.join(my_fixture_dir, 'hiera.yaml') resources = compile_and_get_notifications(<<-PUPPET.unindent) include bca $r = lookup(bca::e, Hash[String,String], hash) notify { "${r[k1]}_${r[k2]}_${r[k3]}": } PUPPET expect(resources).to include('global_e1_module_bca_e2_env_bca_e3') end it 'will resolve global and environment correctly when module has no provider' do Puppet.settings[:hiera_config] = File.join(my_fixture_dir, 'hiera.yaml') resources = compile_and_get_notifications(<<-PUPPET.unindent) include no_provider $r = lookup(no_provider::e, Hash[String,String], hash) notify { "${r[k1]}_${r[k2]}_${r[k3]}": } PUPPET expect(resources).to include('global_e1__env_no_provider_e3') # k2 is missing end end context 'accessing bad data' do it 'a warning will be logged when key in the function provided module data is not prefixed' do Puppet[:code] = "include bad_data\nlookup('bad_data::c')" logs = [] Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do compiler.compile end warnings = logs.select { |log| log.level == :warning }.map { |log| log.message } expect(warnings).to include("Module 'bad_data': Value returned from deprecated API function 'bad_data::data' must use keys qualified with the name of the module") end it 'a warning will be logged when key in the hiera provided module data is not prefixed' do Puppet[:code] = "include hieraprovider\nlookup('hieraprovider::test::param_a')" logs = [] Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do compiler.compile end warnings = logs.select { |log| log.level == :warning }.map { |log| log.message } expect(warnings).to include("Module 'hieraprovider': Value returned from data_hash function 'json_data', when using location '#{environmentpath}/production/modules/hieraprovider/data/first.json', must use keys qualified with the name of the module") end end context 'accessing empty files' do # An empty YAML file is OK and should be treated as a file that contains no keys it "will fail normally with a 'did not find a value' error when a yaml file is empty" do Puppet[:code] = "include empty_yaml\nlookup('empty_yaml::a')" expect { compiler.compile }.to raise_error(Puppet::ParseError, /did not find a value for the name 'empty_yaml::a'/) end # An empty JSON file is not OK. Should yield a parse error it "will fail with a LookupError indicating a parser failure when a json file is empty" do Puppet[:code] = "include empty_json\nlookup('empty_json::a')" expect { compiler.compile }.to raise_error(Puppet::DataBinding::LookupError, /Unable to parse/) end end context 'accessing nil values' do it 'will find a key with undef value in a yaml file' do Puppet[:code] = 'include empty_key_yaml' compiler.compile do |catalog| lookup_invocation = Puppet::Pops::Lookup::Invocation.new(compiler.topscope, {}, {}, true) begin Puppet::Pops::Lookup.lookup('empty_key_yaml::has_undef_value', nil, nil, false, nil, lookup_invocation) rescue Puppet::Error end expect(lookup_invocation.explainer.explain).to include(<<-EOS.unindent(' ')) Path "#{environmentpath}/production/modules/empty_key_yaml/data/empty_key.yaml" Original path: "empty_key" Found key: "empty_key_yaml::has_undef_value" value: nil EOS end end it 'will find a key with undef value in a json file' do Puppet[:code] = 'include empty_key_json' compiler.compile do |catalog| lookup_invocation = Puppet::Pops::Lookup::Invocation.new(compiler.topscope, {}, {}, true) begin Puppet::Pops::Lookup.lookup('empty_key_json::has_undef_value', nil, nil, false, nil, lookup_invocation) rescue Puppet::Error end expect(lookup_invocation.explainer.explain).to include(<<-EOS.unindent(' ')) Path "#{environmentpath}/production/modules/empty_key_json/data/empty_key.json" Original path: "empty_key" Found key: "empty_key_json::has_undef_value" value: nil EOS end end end context 'using explain' do it 'will explain that module is not found' do Puppet[:code] = 'undef' compiler.compile do |catalog| lookup_invocation = Puppet::Pops::Lookup::Invocation.new(compiler.topscope, {}, {}, true) begin Puppet::Pops::Lookup.lookup('ppx::e', nil, nil, false, nil, lookup_invocation) rescue Puppet::Error end expect(lookup_invocation.explainer.explain).to include('Module "ppx" not found') end end it 'will explain that module does not find a key' do Puppet[:code] = 'undef' compiler.compile do |catalog| lookup_invocation = Puppet::Pops::Lookup::Invocation.new(compiler.topscope, {}, {}, true) begin Puppet::Pops::Lookup.lookup('abc::x', nil, nil, false, nil, lookup_invocation) rescue Puppet::Error end expect(lookup_invocation.explainer.explain).to include(<<-EOS.unindent(' ')) Module "abc" Data Provider (hiera configuration version 5) Deprecated API function "abc::data" No such key: "abc::x" EOS end end it 'will explain deep merge results without options' do assemble_and_compile('${r}', "'abc::a'") do |scope| lookup_invocation = Puppet::Pops::Lookup::Invocation.new(scope, {}, {}, true) Puppet::Pops::Lookup.lookup('abc::e', Puppet::Pops::Types::TypeParser.singleton.parse('Hash[String,String]'), nil, false, 'deep', lookup_invocation) expect(lookup_invocation.explainer.explain).to eq(<<-EOS.unindent) Searching for "abc::e" Merge strategy deep Global Data Provider (hiera configuration version 5) No such key: "abc::e" Environment Data Provider (hiera configuration version 5) Deprecated API function "environment::data" Found key: "abc::e" value: { "k1" => "env_e1", "k3" => "env_e3" } Module "abc" Data Provider (hiera configuration version 5) Deprecated API function "abc::data" Found key: "abc::e" value: { "k1" => "module_e1", "k2" => "module_e2" } Merged result: { "k1" => "env_e1", "k2" => "module_e2", "k3" => "env_e3" } EOS end end it 'will explain deep merge results with options' do assemble_and_compile('${r}', "'abc::a'") do |scope| lookup_invocation = Puppet::Pops::Lookup::Invocation.new(scope, {}, {}, true) Puppet::Pops::Lookup.lookup('abc::e', Puppet::Pops::Types::TypeParser.singleton.parse('Hash[String,String]'), nil, false, { 'strategy' => 'deep', 'merge_hash_arrays' => true }, lookup_invocation) expect(lookup_invocation.explainer.explain).to include(<<-EOS.unindent(' ')) Merge strategy deep Options: { "merge_hash_arrays" => true } EOS end end it 'will handle merge when no entries are not found' do assemble_and_compile('${r}', "'hieraprovider::test::param_a'") do |scope| lookup_invocation = Puppet::Pops::Lookup::Invocation.new(scope, {}, {}, true) begin Puppet::Pops::Lookup.lookup('hieraprovider::test::not_found', nil, nil, false, 'deep', lookup_invocation) rescue Puppet::DataBinding::LookupError end expect(lookup_invocation.explainer.explain).to eq(<<-EOS.unindent) Searching for "hieraprovider::test::not_found" Merge strategy deep Global Data Provider (hiera configuration version 5) No such key: "hieraprovider::test::not_found" Environment Data Provider (hiera configuration version 5) Deprecated API function "environment::data" No such key: "hieraprovider::test::not_found" Module "hieraprovider" Data Provider (hiera configuration version 4) Using configuration "#{environmentpath}/production/modules/hieraprovider/hiera.yaml" Hierarchy entry "two paths" Merge strategy deep Path "#{environmentpath}/production/modules/hieraprovider/data/first.json" Original path: "first" No such key: "hieraprovider::test::not_found" Path "#{environmentpath}/production/modules/hieraprovider/data/second_not_present.json" Original path: "second_not_present" Path not found EOS end end it 'will explain value access caused by dot notation in key' do assemble_and_compile('${r}', "'abc::a'") do |scope| lookup_invocation = Puppet::Pops::Lookup::Invocation.new(scope, {}, {}, true) Puppet::Pops::Lookup.lookup('abc::f.k1.s1', Puppet::Pops::Types::TypeParser.singleton.parse('String'), nil, false, nil, lookup_invocation) expect(lookup_invocation.explainer.explain).to include(<<-EOS.unindent(' ')) Sub key: "k1.s1" Found key: "k1" value: { "s1" => "env_f11", "s2" => "env_f12" } Found key: "s1" value: "env_f11" EOS end end it 'will provide a hash containing all explanation elements' do assemble_and_compile('${r}', "'abc::a'") do |scope| lookup_invocation = Puppet::Pops::Lookup::Invocation.new(scope, {}, {}, true) Puppet::Pops::Lookup.lookup('abc::e', Puppet::Pops::Types::TypeParser.singleton.parse('Hash[String,String]'), nil, false, { 'strategy' => 'deep', 'merge_hash_arrays' => true }, lookup_invocation) expect(lookup_invocation.explainer.to_hash).to eq( { :type => :root, :key => 'abc::e', :branches => [ { :value => { 'k1' => 'env_e1', 'k2' => 'module_e2', 'k3' => 'env_e3' }, :event => :result, :merge => :deep, :options => { 'merge_hash_arrays' => true }, :type => :merge, :branches => [ { :key => 'abc::e', :event => :not_found, :type => :data_provider, :name => 'Global Data Provider (hiera configuration version 5)' }, { :type => :data_provider, :name => 'Environment Data Provider (hiera configuration version 5)', :branches => [ { :type => :data_provider, :name => 'Deprecated API function "environment::data"', :key => 'abc::e', :value => { 'k1' => 'env_e1', 'k3' => 'env_e3' }, :event => :found } ] }, { :type => :data_provider, :name => 'Module "abc" Data Provider (hiera configuration version 5)', :module => 'abc', :branches => [ { :type => :data_provider, :name => 'Deprecated API function "abc::data"', :key => 'abc::e', :event => :found, :value => { 'k1' => 'module_e1', 'k2' => 'module_e2' } } ] } ] } ] } ) end end it 'will explain that "lookup_options" is an invalid key' do assemble_and_compile('${r}', "'abc::a'") do |scope| lookup_invocation = Puppet::Pops::Lookup::Invocation.new(scope, {}, {}, true) begin Puppet::Pops::Lookup.lookup('lookup_options', nil, nil, false, nil, lookup_invocation) rescue Puppet::Error end expect(lookup_invocation.explainer.explain.chomp).to eq('Invalid key "lookup_options"') end end it 'will explain that "lookup_options" is an invalid key for any key starting with "lookup_options."' do assemble_and_compile('${r}', "'abc::a'") do |scope| lookup_invocation = Puppet::Pops::Lookup::Invocation.new(scope, {}, {}, true) begin Puppet::Pops::Lookup.lookup('lookup_options.subkey', nil, nil, false, nil, lookup_invocation) rescue Puppet::Error end expect(lookup_invocation.explainer.explain.chomp).to eq('Invalid key "lookup_options"') end end end end puppet-5.5.10/spec/unit/functions/map_spec.rb0000644005276200011600000001452413417161721021055 0ustar jenkinsjenkinsrequire 'puppet' require 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' require 'shared_behaviours/iterative_functions' describe 'the map method can' do include PuppetSpec::Compiler include Matchers::Resource it 'map on an array (multiplying each value by 2)' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,2,3] $a.map |$x|{ $x*2}.each |$v|{ file { "/file_$v": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_4]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present') end it 'map on an enumerable type (multiplying each value by 2)' do catalog = compile_to_catalog(<<-MANIFEST) $a = Integer[1,3] $a.map |$x|{ $x*2}.each |$v|{ file { "/file_$v": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_4]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present') end it 'map on an integer (multiply each by 3)' do catalog = compile_to_catalog(<<-MANIFEST) 3.map |$x|{ $x*3}.each |$v|{ file { "/file_$v": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_0]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present') end it 'map on a string' do catalog = compile_to_catalog(<<-MANIFEST) $a = {a=>x, b=>y} "ab".map |$x|{$a[$x]}.each |$v|{ file { "/file_$v": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_x]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_y]").with_parameter(:ensure, 'present') end it 'map on an array (multiplying value by 10 in even index position)' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,2,3] $a.map |$i, $x|{ if $i % 2 == 0 {$x} else {$x*10}}.each |$v|{ file { "/file_$v": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_1]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_20]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present') end it 'map on a hash selecting keys' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'a'=>1,'b'=>2,'c'=>3} $a.map |$x|{ $x[0]}.each |$k|{ file { "/file_$k": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_a]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_b]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_c]").with_parameter(:ensure, 'present') end it 'map on a hash selecting keys - using two block parameters' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'a'=>1,'b'=>2,'c'=>3} $a.map |$k,$v|{ file { "/file_$k": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_a]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_b]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_c]").with_parameter(:ensure, 'present') end it 'map on a hash using captures-last parameter' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'a'=>present,'b'=>absent,'c'=>present} $a.map |*$kv|{ file { "/file_${kv[0]}": ensure => $kv[1] } } MANIFEST expect(catalog).to have_resource("File[/file_a]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_b]").with_parameter(:ensure, 'absent') expect(catalog).to have_resource("File[/file_c]").with_parameter(:ensure, 'present') end it 'each on a hash selecting value' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'a'=>1,'b'=>2,'c'=>3} $a.map |$x|{ $x[1]}.each |$k|{ file { "/file_$k": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_1]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present') end it 'each on a hash selecting value - using two block parameters' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'a'=>1,'b'=>2,'c'=>3} $a.map |$k,$v|{ file { "/file_$v": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_1]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present') end context "handles data type corner cases" do it "map gets values that are false" do catalog = compile_to_catalog(<<-MANIFEST) $a = [false,false] $a.map |$x| { $x }.each |$i, $v| { file { "/file_$i.$v": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_0.false]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_1.false]").with_parameter(:ensure, 'present') end it "map gets values that are nil" do Puppet::Parser::Functions.newfunction(:nil_array, :type => :rvalue) do |args| [nil] end catalog = compile_to_catalog(<<-MANIFEST) $a = nil_array() $a.map |$x| { $x }.each |$i, $v| { file { "/file_$i.$v": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_0.]").with_parameter(:ensure, 'present') end end it_should_behave_like 'all iterative functions argument checks', 'map' it_should_behave_like 'all iterative functions hash handling', 'map' end puppet-5.5.10/spec/unit/functions/module_directory_spec.rb0000644005276200011600000000334313417161721023646 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' require 'puppet_spec/files' describe 'the module_directory function' do include PuppetSpec::Compiler include Matchers::Resource include PuppetSpec::Files it 'returns first found module from one or more given names' do mod = mock 'module' mod.stubs(:path).returns('expected_path') Puppet[:code] = "notify { module_directory('one', 'two'):}" node = Puppet::Node.new('localhost') compiler = Puppet::Parser::Compiler.new(node) compiler.environment.stubs(:module).with('one').returns(nil) compiler.environment.stubs(:module).with('two').returns(mod) expect(compiler.compile()).to have_resource("Notify[expected_path]") end it 'returns first found module from one or more given names in an array' do mod = mock 'module' mod.stubs(:path).returns('expected_path') Puppet[:code] = "notify { module_directory(['one', 'two']):}" node = Puppet::Node.new('localhost') compiler = Puppet::Parser::Compiler.new(node) compiler.environment.stubs(:module).with('one').returns(nil) compiler.environment.stubs(:module).with('two').returns(mod) expect(compiler.compile()).to have_resource("Notify[expected_path]") end it 'returns undef when none of the modules were found' do mod = mock 'module' mod.stubs(:path).returns('expected_path') Puppet[:code] = "notify { String(type(module_directory('one', 'two'))):}" node = Puppet::Node.new('localhost') compiler = Puppet::Parser::Compiler.new(node) compiler.environment.stubs(:module).with('one').returns(nil) compiler.environment.stubs(:module).with('two').returns(nil) expect(compiler.compile()).to have_resource("Notify[Undef]") end end puppet-5.5.10/spec/unit/functions/new_spec.rb0000644005276200011600000006320013417161721021064 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' describe 'the new function' do include PuppetSpec::Compiler include Matchers::Resource it 'yields converted value if given a block' do expect(compile_to_catalog(<<-MANIFEST $x = Integer.new('42') |$x| { $x+2 } notify { "${type($x, generalized)}, $x": } MANIFEST )).to have_resource('Notify[Integer, 44]') end it 'produces undef if given an undef value and type accepts it' do expect(compile_to_catalog(<<-MANIFEST $x = Optional[Integer].new(undef) notify { "one${x}word": } MANIFEST )).to have_resource('Notify[oneword]') end it 'errors if given undef and type does not accept the value' do expect{compile_to_catalog(<<-MANIFEST $x = Integer.new(undef) notify { "one${x}word": } MANIFEST )}.to raise_error(Puppet::Error, /of type Undef cannot be converted to Integer/) end it 'errors if converted value is not assignable to the type' do expect{compile_to_catalog(<<-MANIFEST $x = Integer[1,5].new('42') notify { "one${x}word": } MANIFEST )}.to raise_error(Puppet::Error, /expects an Integer\[1, 5\] value, got Integer\[42, 42\]/) end it 'accepts and returns a second parameter that is an instance of the first, even when the type has no backing new_function' do expect(eval_and_collect_notices(<<-MANIFEST)).to eql(%w(true true true true true true)) notice(undef == Undef(undef)) notice(default == Default(default)) notice(Any == Type(Any)) $b = Binary('YmluYXI=') notice($b == Binary($b)) $t = Timestamp('2012-03-04T09:10:11.001') notice($t == Timestamp($t)) type MyObject = Object[{attributes => {'type' => String}}] $o = MyObject('Remote') notice($o == MyObject($o)) MANIFEST end context 'when invoked on NotUndef' do it 'produces an instance of the NotUndef nested type' do expect(compile_to_catalog(<<-MANIFEST $x = NotUndef[Integer].new(42) notify { "${type($x, generalized)}, $x": } MANIFEST )).to have_resource('Notify[Integer, 42]') end it 'produces the given value when there is no type specified' do expect(compile_to_catalog(<<-MANIFEST $x = NotUndef.new(42) notify { "${type($x, generalized)}, $x": } MANIFEST )).to have_resource('Notify[Integer, 42]') end end context 'when invoked on an Integer' do it 'produces 42 when given the integer 42' do expect(compile_to_catalog(<<-MANIFEST $x = Integer.new(42) notify { "${type($x, generalized)}, $x": } MANIFEST )).to have_resource('Notify[Integer, 42]') end it 'produces 3 when given the float 3.1415' do expect(compile_to_catalog(<<-MANIFEST $x = Integer.new(3.1415) notify { "${type($x, generalized)}, $x": } MANIFEST )).to have_resource('Notify[Integer, 3]') end it 'produces 0 from false' do expect(compile_to_catalog(<<-MANIFEST $x = Integer.new(false) notify { "${type($x, generalized)}, $x": } MANIFEST )).to have_resource('Notify[Integer, 0]') end it 'produces 1 from true' do expect(compile_to_catalog(<<-MANIFEST $x = Integer.new(true) notify { "${type($x, generalized)}, $x": } MANIFEST )).to have_resource('Notify[Integer, 1]') end it "produces an absolute value when third argument is 'true'" do expect(eval_and_collect_notices(<<-MANIFEST notice(Integer.new(-42, 10, true)) MANIFEST )).to eql(['42']) end it "does not produce an absolute value when third argument is 'false'" do expect(eval_and_collect_notices(<<-MANIFEST notice(Integer.new(-42, 10, false)) MANIFEST )).to eql(['-42']) end it "produces an absolute value from hash {from => val, abs => true}" do expect(eval_and_collect_notices(<<-MANIFEST notice(Integer.new({from => -42, abs => true})) MANIFEST )).to eql(['42']) end it "does not produce an absolute value from hash {from => val, abs => false}" do expect(eval_and_collect_notices(<<-MANIFEST notice(Integer.new({from => -42, abs => false})) MANIFEST )).to eql(['-42']) end context 'when prefixed by a sign' do { '+1' => 1, '-1' => -1, '+ 1' => 1, '- 1' => -1, '+0x10' => 16, '+ 0x10' => 16, '-0x10' => -16, '- 0x10' => -16 }.each do |str, result| it "produces #{result} from the string '#{str}'" do expect(compile_to_catalog(<<-"MANIFEST" $x = Integer.new("#{str}") notify { "${type($x, generalized)}, $x": } MANIFEST )).to have_resource("Notify[Integer, #{result}]") end end end context "when radix is not set it uses default and" do { "10" => 10, "010" => 8, "0x10" => 16, "0X10" => 16, '0B111' => 7, '0b111' => 7 }.each do |str, result| it "produces #{result} from the string '#{str}'" do expect(compile_to_catalog(<<-"MANIFEST" $x = Integer.new("#{str}") notify { "${type($x, generalized)}, $x": } MANIFEST )).to have_resource("Notify[Integer, #{result}]") end end end context "when radix is explicitly set to 'default' it" do { "10" => 10, "010" => 8, "0x10" => 16, "0X10" => 16, '0B111' => 7, '0b111' => 7 }.each do |str, result| it "produces #{result} from the string '#{str}'" do expect(compile_to_catalog(<<-"MANIFEST" $x = Integer.new("#{str}", default) notify { "${type($x, generalized)}, $x": } MANIFEST )).to have_resource("Notify[Integer, #{result}]") end end end context "when radix is explicitly set to '2' it" do { "10" => 2, "010" => 2, "00010" => 2, '0B111' => 7, '0b111' => 7, '+0B111' => 7, '-0b111' => -7, '+ 0B111'=> 7, '- 0b111'=> -7 }.each do |str, result| it "produces #{result} from the string '#{str}'" do expect(compile_to_catalog(<<-"MANIFEST" $x = Integer.new("#{str}", 2) notify { "${type($x, generalized)}, $x": } MANIFEST )).to have_resource("Notify[Integer, #{result}]") end end { '0x10' => :error, '0X10' => :error, '+0X10' => :error, '-0X10' => :error, '+ 0X10'=> :error, '- 0X10'=> :error }.each do |str, result| it "errors when given the non binary value compliant string '#{str}'" do expect{compile_to_catalog(<<-"MANIFEST" $x = Integer.new("#{str}", 2) MANIFEST )}.to raise_error(Puppet::Error, /invalid value/) end end end context "when radix is explicitly set to '8' it" do { "10" => 8, "010" => 8, "00010" => 8, '+00010' => 8, '-00010' => -8, '+ 00010'=> 8, '- 00010'=> -8, }.each do |str, result| it "produces #{result} from the string '#{str}'" do expect(compile_to_catalog(<<-"MANIFEST" $x = Integer.new("#{str}", 8) notify { "${type($x, generalized)}, $x": } MANIFEST )).to have_resource("Notify[Integer, #{result}]") end end { "0x10" => :error, '0X10' => :error, '0B10' => :error, '0b10' => :error, '+0b10' => :error, '-0b10' => :error, '+ 0b10'=> :error, '- 0b10'=> :error, }.each do |str, result| it "errors when given the non octal value compliant string '#{str}'" do expect{compile_to_catalog(<<-"MANIFEST" $x = Integer.new("#{str}", 8) MANIFEST )}.to raise_error(Puppet::Error, /invalid value/) end end end context "when radix is explicitly set to '16' it" do { "10" => 16, "010" => 16, "00010" => 16, "0x10" => 16, "0X10" => 16, "0b1" => 16*11+1, "0B1" => 16*11+1, '+0B1' => 16*11+1, '-0B1' => -16*11-1, '+ 0B1' => 16*11+1, '- 0B1' => -16*11-1, }.each do |str, result| it "produces #{result} from the string '#{str}'" do expect(compile_to_catalog(<<-"MANIFEST" $x = Integer.new("#{str}", 16) notify { "${type($x, generalized)}, $x": } MANIFEST )).to have_resource("Notify[Integer, #{result}]") end end { '0XGG' => :error, '+0XGG' => :error, '-0XGG' => :error, '+ 0XGG'=> :error, '- 0XGG'=> :error, }.each do |str, result| it "errors when given the non hexadecimal value compliant string '#{str}'" do expect{compile_to_catalog(<<-"MANIFEST" $x = Integer.new("#{str}", 8) MANIFEST )}.to raise_error(Puppet::Error, /The string '#{Regexp.escape(str)}' cannot be converted to Integer/) end end end context "when radix is explicitly set to '10' it" do { "10" => 10, "010" => 10, "00010" => 10, }.each do |str, result| it "produces #{result} from the string '#{str}'" do expect(compile_to_catalog(<<-"MANIFEST" $x = Integer.new("#{str}", 10) notify { "${type($x, generalized)}, $x": } MANIFEST )).to have_resource("Notify[Integer, #{result}]") end end { '0X10' => :error, '0b10' => :error, '0B10' => :error, }.each do |str, result| it "errors when given the non binary value compliant string '#{str}'" do expect{compile_to_catalog(<<-"MANIFEST" $x = Integer.new("#{str}", 10) MANIFEST )}.to raise_error(Puppet::Error, /invalid value/) end end end context "input can be given in long form " do { {'from' => "10", 'radix' => 2} => 2, {'from' => "10", 'radix' => 8} => 8, {'from' => "10", 'radix' => 10} => 10, {'from' => "10", 'radix' => 16} => 16, {'from' => "10", 'radix' => :default} => 10, }.each do |options, result| it "produces #{result} from the long form '#{options}'" do src = <<-"MANIFEST" $x = Integer.new(#{options.to_s.gsub(/:/, '')}) notify { "${type($x, generalized)}, $x": } MANIFEST expect(compile_to_catalog(src)).to have_resource("Notify[Integer, #{result}]") end end end context 'errors when' do it 'radix is wrong and when given directly' do expect{compile_to_catalog(<<-"MANIFEST" $x = Integer.new('10', 3) MANIFEST )}.to raise_error(Puppet::Error, /Illegal radix/) end it 'radix is wrong and when given in long form' do expect{compile_to_catalog(<<-"MANIFEST" $x = Integer.new({from =>'10', radix=>3}) MANIFEST )}.to raise_error(Puppet::Error, /Illegal radix/) end it 'value is not numeric and given directly' do expect{compile_to_catalog(<<-"MANIFEST" $x = Integer.new('eleven', 10) MANIFEST )}.to raise_error(Puppet::Error, /The string 'eleven' cannot be converted to Integer/) end it 'value is not numeric and given in long form' do expect{compile_to_catalog(<<-"MANIFEST" $x = Integer.new({from => 'eleven', radix => 10}) MANIFEST )}.to raise_error(Puppet::Error, /The string 'eleven' cannot be converted to Integer/) end end end context 'when invoked on Numeric' do { 42 => "Notify[Integer, 42]", 42.3 => "Notify[Float, 42.3]", "42.0" => "Notify[Float, 42.0]", "+42.0" => "Notify[Float, 42.0]", "-42.0" => "Notify[Float, -42.0]", "+ 42.0" => "Notify[Float, 42.0]", "- 42.0" => "Notify[Float, -42.0]", "42.3" => "Notify[Float, 42.3]", "0x10" => "Notify[Integer, 16]", "010" => "Notify[Integer, 8]", "0.10" => "Notify[Float, 0.1]", "0b10" => "Notify[Integer, 2]", "0" => "Notify[Integer, 0]", false => "Notify[Integer, 0]", true => "Notify[Integer, 1]", }.each do |input, result| it "produces #{result} when given the value #{input.inspect}" do expect(compile_to_catalog(<<-MANIFEST $x = Numeric.new(#{input.inspect}) notify { "${type($x, generalized)}, $x": } MANIFEST )).to have_resource(result) end end it "produces a result when long from hash {from => val} is used" do expect(compile_to_catalog(<<-MANIFEST $x = Numeric.new({from=>'42'}) notify { "${type($x, generalized)}, $x": } MANIFEST )).to have_resource('Notify[Integer, 42]') end it "produces an absolute value when second argument is 'true'" do expect(eval_and_collect_notices(<<-MANIFEST notice(Numeric.new(-42.3, true)) MANIFEST )).to eql(['42.3']) end it "does not produce an absolute value when second argument is 'false'" do expect(eval_and_collect_notices(<<-MANIFEST notice(Numeric.new(-42.3, false)) MANIFEST )).to eql(['-42.3']) end it "produces an absolute value from hash {from => val, abs => true}" do expect(eval_and_collect_notices(<<-MANIFEST notice(Numeric.new({from => -42.3, abs => true})) MANIFEST )).to eql(['42.3']) end it "does not produce an absolute value from hash {from => val, abs => false}" do expect(eval_and_collect_notices(<<-MANIFEST notice(Numeric.new({from => -42.3, abs => false})) MANIFEST )).to eql(['-42.3']) end end context 'when invoked on Float' do { 42 => "Notify[Float, 42.0]", 42.3 => "Notify[Float, 42.3]", "42.0" => "Notify[Float, 42.0]", "+42.0" => "Notify[Float, 42.0]", "-42.0" => "Notify[Float, -42.0]", "+ 42.0" => "Notify[Float, 42.0]", "- 42.0" => "Notify[Float, -42.0]", "42.3" => "Notify[Float, 42.3]", "0x10" => "Notify[Float, 16.0]", "010" => "Notify[Float, 10.0]", "0.10" => "Notify[Float, 0.1]", false => "Notify[Float, 0.0]", true => "Notify[Float, 1.0]", '0b10' => "Notify[Float, 2.0]", '0B10' => "Notify[Float, 2.0]", }.each do |input, result| it "produces #{result} when given the value #{input.inspect}" do expect(compile_to_catalog(<<-MANIFEST $x = Float.new(#{input.inspect}) notify { "${type($x, generalized)}, $x": } MANIFEST )).to have_resource(result) end end it "produces a result when long from hash {from => val} is used" do expect(compile_to_catalog(<<-MANIFEST $x = Float.new({from=>42}) notify { "${type($x, generalized)}, $x": } MANIFEST )).to have_resource('Notify[Float, 42.0]') end it "produces an absolute value when second argument is 'true'" do expect(eval_and_collect_notices(<<-MANIFEST notice(Float.new(-42.3, true)) MANIFEST )).to eql(['42.3']) end it "does not produce an absolute value when second argument is 'false'" do expect(eval_and_collect_notices(<<-MANIFEST notice(Float.new(-42.3, false)) MANIFEST )).to eql(['-42.3']) end it "produces an absolute value from hash {from => val, abs => true}" do expect(eval_and_collect_notices(<<-MANIFEST notice(Float.new({from => -42.3, abs => true})) MANIFEST )).to eql(['42.3']) end it "does not produce an absolute value from hash {from => val, abs => false}" do expect(eval_and_collect_notices(<<-MANIFEST notice(Float.new({from => -42.3, abs => false})) MANIFEST )).to eql(['-42.3']) end end context 'when invoked on Boolean' do { true => 'Notify[Boolean, true]', false => 'Notify[Boolean, false]', 0 => 'Notify[Boolean, false]', 1 => 'Notify[Boolean, true]', 0.0 => 'Notify[Boolean, false]', 1.0 => 'Notify[Boolean, true]', 'true' => 'Notify[Boolean, true]', 'TrUe' => 'Notify[Boolean, true]', 'yes' => 'Notify[Boolean, true]', 'YeS' => 'Notify[Boolean, true]', 'y' => 'Notify[Boolean, true]', 'Y' => 'Notify[Boolean, true]', 'false' => 'Notify[Boolean, false]', 'no' => 'Notify[Boolean, false]', 'n' => 'Notify[Boolean, false]', 'FalSE' => 'Notify[Boolean, false]', 'nO' => 'Notify[Boolean, false]', 'N' => 'Notify[Boolean, false]', }.each do |input, result| it "produces #{result} when given the value #{input.inspect}" do expect(compile_to_catalog(<<-MANIFEST $x = Boolean.new(#{input.inspect}) notify { "${type($x, generalized)}, $x": } MANIFEST )).to have_resource(result) end end it "errors when given an non boolean representation like the string 'hello'" do expect{compile_to_catalog(<<-"MANIFEST" $x = Boolean.new('hello') MANIFEST )}.to raise_error(Puppet::Error, /The string 'hello' cannot be converted to Boolean/) end it "does not convert an undef (as may be expected, but is handled as every other undef)" do expect{compile_to_catalog(<<-"MANIFEST" $x = Boolean.new(undef) MANIFEST )}.to raise_error(Puppet::Error, /of type Undef cannot be converted to Boolean/) end end context 'when invoked on Array' do { [] => 'Notify[Array[Unit], []]', [true] => 'Notify[Array[Boolean], [true]]', {'a'=>true, 'b' => false} => 'Notify[Array[Array[ScalarData]], [[a, true], [b, false]]]', 'abc' => 'Notify[Array[String[1, 1]], [a, b, c]]', 3 => 'Notify[Array[Integer], [0, 1, 2]]', }.each do |input, result| it "produces #{result} when given the value #{input.inspect} and wrap is not given" do expect(compile_to_catalog(<<-MANIFEST $x = Array.new(#{input.inspect}) notify { "${type($x, generalized)}, $x": } MANIFEST )).to have_resource(result) end end { true => /of type Boolean cannot be converted to Array/, 42.3 => /of type Float cannot be converted to Array/, }.each do |input, error_match| it "errors when given an non convertible #{input.inspect} when wrap is not given" do expect{compile_to_catalog(<<-"MANIFEST" $x = Array.new(#{input.inspect}) MANIFEST )}.to raise_error(Puppet::Error, error_match) end end { [] => 'Notify[Array[Unit], []]', [true] => 'Notify[Array[Boolean], [true]]', {'a'=>true} => 'Notify[Array[Hash[String, Boolean]], [{a => true}]]', 'hello' => 'Notify[Array[String], [hello]]', true => 'Notify[Array[Boolean], [true]]', 42 => 'Notify[Array[Integer], [42]]', }.each do |input, result| it "produces #{result} when given the value #{input.inspect} and wrap is given" do expect(compile_to_catalog(<<-MANIFEST $x = Array.new(#{input.inspect}, true) notify { "${type($x, generalized)}, $x": } MANIFEST )).to have_resource(result) end end it 'produces an array of byte integer values when given a Binary' do expect(compile_to_catalog(<<-MANIFEST $x = Array.new(Binary('ABC', '%s')) notify { "${type($x, generalized)}, $x": } MANIFEST )).to have_resource('Notify[Array[Integer], [65, 66, 67]]') end it 'wraps a binary when given extra argument true' do expect(compile_to_catalog(<<-MANIFEST $x = Array[Any].new(Binary('ABC', '%s'), true) notify { "${type($x, generalized)}, $x": } MANIFEST )).to have_resource('Notify[Array[Binary], [QUJD]]') end end context 'when invoked on Tuple' do { 'abc' => 'Notify[Array[String[1, 1]], [a, b, c]]', 3 => 'Notify[Array[Integer], [0, 1, 2]]', }.each do |input, result| it "produces #{result} when given the value #{input.inspect} and wrap is not given" do expect(compile_to_catalog(<<-MANIFEST $x = Tuple[Any,3].new(#{input.inspect}) notify { "${type($x, generalized)}, $x": } MANIFEST )).to have_resource(result) end end it "errors when tuple requirements are not met" do expect{compile_to_catalog(<<-"MANIFEST" $x = Tuple[Integer,6].new(3) MANIFEST )}.to raise_error(Puppet::Error, /expects size to be at least 6, got 3/) end end context 'when invoked on Hash' do { {} => 'Notify[Hash[0, 0], {}]', [] => 'Notify[Hash[0, 0], {}]', {'a'=>true} => 'Notify[Hash[String, Boolean], {a => true}]', [1,2,3,4] => 'Notify[Hash[Integer, Integer], {1 => 2, 3 => 4}]', [[1,2],[3,4]] => 'Notify[Hash[Integer, Integer], {1 => 2, 3 => 4}]', 'abcd' => 'Notify[Hash[String[1, 1], String[1, 1]], {a => b, c => d}]', 4 => 'Notify[Hash[Integer, Integer], {0 => 1, 2 => 3}]', }.each do |input, result| it "produces #{result} when given the value #{input.inspect}" do expect(compile_to_catalog(<<-MANIFEST $x = Hash.new(#{input.inspect}) notify { "${type($x, generalized)}, $x": } MANIFEST )).to have_resource(result) end end { true => /Value of type Boolean cannot be converted to Hash/, [1,2,3] => /odd number of arguments for Hash/, }.each do |input, error_match| it "errors when given an non convertible #{input.inspect}" do expect{compile_to_catalog(<<-"MANIFEST" $x = Hash.new(#{input.inspect}) MANIFEST )}.to raise_error(Puppet::Error, error_match) end end context 'when using the optional "tree" format' do it 'can convert a tree in flat form to a hash' do expect(compile_to_catalog(<<-"MANIFEST" $x = Hash.new([[[0], a],[[1,0], b],[[1,1], c],[[2,0], d]], tree) notify { test: message => $x } MANIFEST )).to have_resource('Notify[test]').with_parameter(:message, { 0 => 'a', 1 => { 0 => 'b', 1=> 'c'}, 2 => {0 => 'd'} }) end it 'preserves array in flattened tree but overwrites entries if they are present' do expect(compile_to_catalog(<<-"MANIFEST" $x = Hash.new([[[0], a],[[1,0], b],[[1,1], c],[[2], [overwritten, kept]], [[2,0], d]], tree) notify { test: message => $x } MANIFEST )).to have_resource('Notify[test]').with_parameter(:message, { 0 => 'a', 1 => { 0 => 'b', 1=> 'c'}, 2 => ['d', 'kept'] }) end it 'preserves hash in flattened tree but overwrites entries if they are present' do expect(compile_to_catalog(<<-"MANIFEST" $x = Hash.new([[[0], a],[[1,0], b],[[1,1], c],[[2], {0 => 0, kept => 1}], [[2,0], d]], tree) notify { test: message => $x } MANIFEST )).to have_resource('Notify[test]').with_parameter(:message, { 0 => 'a', 1 => { 0 => 'b', 1=> 'c'}, 2 => {0=>'d', 'kept'=>1} }) end end context 'when using the optional "tree_hash" format' do it 'turns array in flattened tree into hash' do expect(compile_to_catalog(<<-"MANIFEST" $x = Hash.new([[[0], a],[[1,0], b],[[1,1], c],[[2], [overwritten, kept]], [[2,0], d]], hash_tree) notify { test: message => $x } MANIFEST )).to have_resource('Notify[test]').with_parameter(:message, { 0=>'a', 1=>{ 0=>'b', 1=>'c'}, 2=>{0=>'d', 1=>'kept'}}) end end end context 'when invoked on Struct' do { {'a' => 2} => 'Notify[Struct[{\'a\' => Integer[2, 2]}], {a => 2}]', }.each do |input, result| it "produces #{result} when given the value #{input.inspect}" do expect(compile_to_catalog(<<-MANIFEST $x = Struct[{a => Integer[2]}].new(#{input.inspect}) notify { "${type($x)}, $x": } MANIFEST )).to have_resource(result) end end it "errors when tuple requirements are not met" do expect{compile_to_catalog(<<-"MANIFEST" $x = Struct[{a => Integer[2]}].new({a => 0}) MANIFEST )}.to raise_error(Puppet::Error, /entry 'a' expects an Integer\[2\]/) end end context 'when invoked on String' do { {} => 'Notify[String, {}]', [] => 'Notify[String, []]', {'a'=>true} => "Notify[String, {'a' => true}]", [1,2,3,4] => 'Notify[String, [1, 2, 3, 4]]', [[1,2],[3,4]] => 'Notify[String, [[1, 2], [3, 4]]]', 'abcd' => 'Notify[String, abcd]', 4 => 'Notify[String, 4]', }.each do |input, result| it "produces #{result} when given the value #{input.inspect}" do expect(compile_to_catalog(<<-MANIFEST $x = String.new(#{input.inspect}) notify { "${type($x, generalized)}, $x": } MANIFEST )).to have_resource(result) end end end context 'when invoked on a type alias' do it 'delegates the new to the aliased type' do expect(compile_to_catalog(<<-MANIFEST type X = Boolean $x = X.new('yes') notify { "${type($x, generalized)}, $x": } MANIFEST )).to have_resource('Notify[Boolean, true]') end end context 'when invoked on a Type' do it 'creates a Type from its string representation' do expect(compile_to_catalog(<<-MANIFEST $x = Type.new('Integer[3,10]') notify { "${type($x)}": } MANIFEST )).to have_resource('Notify[Type[Integer[3, 10]]]') end end end puppet-5.5.10/spec/unit/functions/next_spec.rb0000644005276200011600000000632713417161721021260 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' describe 'the next function' do include PuppetSpec::Compiler include Matchers::Resource context 'exits a block yielded to iteratively' do it 'with a given value as result for this iteration' do expect(compile_to_catalog(<<-CODE)).to have_resource('Notify[[100, 4, 6]]') $result = String([1,2,3].map |$x| { if $x == 1 { next(100) } $x*2 }) notify { $result: } CODE end it 'with undef value as result for this iteration when next is not given an argument' do expect(compile_to_catalog(<<-CODE)).to have_resource('Notify[[undef, 4, 6]]') $result = String([1,2,3].map |$x| { if $x == 1 { next() } $x*2 }) notify { $result: } CODE end end it 'can be called without parentheses around the argument' do expect(compile_to_catalog(<<-CODE)).to have_resource('Notify[[100, 4, 6]]') $result = String([1,2,3].map |$x| { if $x == 1 { next 100 } $x*2 }) notify { $result: } CODE end it 'has the same effect as a return when called from within a block not used in an iteration' do expect(compile_to_catalog(<<-CODE)).to have_resource('Notify[100]') $result = String(with(1) |$x| { if $x == 1 { next(100) } 200 }) notify { $result: } CODE end it 'has the same effect as a return when called from within a function' do expect(compile_to_catalog(<<-CODE)).to have_resource('Notify[[102, 200, 300]]') function do_next() { next(100) } $result = String([1,2,3].map |$x| { if $x == 1 { next do_next()+2 } $x*do_next() }) notify { $result: } CODE end it 'provides early exit from a class and keeps the class' do expect(eval_and_collect_notices(<<-CODE)).to eql(['a', 'c', 'true', 'true']) class notices_c { notice 'c' } class does_next { notice 'a' if 1 == 1 { next() } # avoid making next line statically unreachable notice 'b' } # include two classes to check that next does not do an early return from # the include function. include(does_next, notices_c) notice defined(does_next) notice defined(notices_c) CODE end it 'provides early exit from a user defined resource and keeps the resource' do expect(eval_and_collect_notices(<<-CODE)).to eql(['the_doer_of_next', 'copy_cat', 'true', 'true']) define does_next { notice $title if 1 == 1 { next() } # avoid making next line statically unreachable notice 'b' } define checker { notice defined(Does_next['the_doer_of_next']) notice defined(Does_next['copy_cat']) } # create two instances to ensure next does not break the entire # resource expression does_next { ['the_doer_of_next', 'copy_cat']: } checker { 'needed_because_evaluation_order': } CODE end it 'can not be called from top scope' do expect do compile_to_catalog(<<-CODE) # line 1 # line 2 next() CODE end.to raise_error(/next\(\) from context where this is illegal \(file: unknown, line: 3\) on node.*/) end end puppet-5.5.10/spec/unit/functions/reduce_spec.rb0000644005276200011600000000527613417161721021553 0ustar jenkinsjenkinsrequire 'puppet' require 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' require 'shared_behaviours/iterative_functions' describe 'the reduce method' do include PuppetSpec::Compiler include Matchers::Resource before :each do node = Puppet::Node.new("floppy", :environment => 'production') @compiler = Puppet::Parser::Compiler.new(node) @scope = Puppet::Parser::Scope.new(@compiler) @topscope = @scope.compiler.topscope @scope.parent = @topscope end context "should be callable as" do it 'reduce on an array' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,2,3] $b = $a.reduce |$memo, $x| { $memo + $x } file { "/file_$b": ensure => present } MANIFEST expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present') end it 'reduce on an array with captures rest in lambda' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,2,3] $b = $a.reduce |*$mx| { $mx[0] + $mx[1] } file { "/file_$b": ensure => present } MANIFEST expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present') end it 'reduce on enumerable type' do catalog = compile_to_catalog(<<-MANIFEST) $a = Integer[1,3] $b = $a.reduce |$memo, $x| { $memo + $x } file { "/file_$b": ensure => present } MANIFEST expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present') end it 'reduce on an array with start value' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,2,3] $b = $a.reduce(4) |$memo, $x| { $memo + $x } file { "/file_$b": ensure => present } MANIFEST expect(catalog).to have_resource("File[/file_10]").with_parameter(:ensure, 'present') end it 'reduce on a hash' do catalog = compile_to_catalog(<<-MANIFEST) $a = {a=>1, b=>2, c=>3} $start = [ignored, 4] $b = $a.reduce |$memo, $x| {['sum', $memo[1] + $x[1]] } file { "/file_${$b[0]}_${$b[1]}": ensure => present } MANIFEST expect(catalog).to have_resource("File[/file_sum_6]").with_parameter(:ensure, 'present') end it 'reduce on a hash with start value' do catalog = compile_to_catalog(<<-MANIFEST) $a = {a=>1, b=>2, c=>3} $start = ['ignored', 4] $b = $a.reduce($start) |$memo, $x| { ['sum', $memo[1] + $x[1]] } file { "/file_${$b[0]}_${$b[1]}": ensure => present } MANIFEST expect(catalog).to have_resource("File[/file_sum_10]").with_parameter(:ensure, 'present') end end it_should_behave_like 'all iterative functions argument checks', 'reduce' end puppet-5.5.10/spec/unit/functions/require_spec.rb0000644005276200011600000000501113417161721021743 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/compiler' require 'puppet/parser/functions' require 'matchers/containment_matchers' require 'matchers/resource' require 'matchers/include_in_order' require 'unit/functions/shared' describe 'The "require" function' do include PuppetSpec::Compiler include ContainmentMatchers include Matchers::Resource before(:each) do compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("foo")) @scope = compiler.topscope end it 'includes a class that is not already included' do catalog = compile_to_catalog(<<-MANIFEST) class required { notify { "required": } } require required MANIFEST expect(catalog.classes).to include("required") end it 'sets the require attribute on the requiring resource' do catalog = compile_to_catalog(<<-MANIFEST) class required { notify { "required": } } class requiring { require required } include requiring MANIFEST requiring = catalog.resource("Class", "requiring") expect(requiring["require"]).to be_instance_of(Array) expect(requiring["require"][0]).to be_instance_of(Puppet::Resource) expect(requiring["require"][0].to_s).to eql("Class[Required]") end it 'appends to the require attribute on the requiring resource if it already has requirements' do catalog = compile_to_catalog(<<-MANIFEST) class required { } class also_required { } class requiring { require required require also_required } include requiring MANIFEST requiring = catalog.resource("Class", "requiring") expect(requiring["require"]).to be_instance_of(Array) expect(requiring["require"][0]).to be_instance_of(Puppet::Resource) expect(requiring["require"][0].to_s).to eql("Class[Required]") expect(requiring["require"][1]).to be_instance_of(Puppet::Resource) expect(requiring["require"][1].to_s).to eql("Class[Also_required]") end it "includes the class when using a fully qualified anchored name" do catalog = compile_to_catalog(<<-MANIFEST) class required { notify { "required": } } require ::required MANIFEST expect(catalog.classes).to include("required") end it_should_behave_like 'all functions transforming relative to absolute names', :require it_should_behave_like 'an inclusion function, regardless of the type of class reference,', :require it_should_behave_like 'an inclusion function, when --tasks is on,', :require end puppet-5.5.10/spec/unit/functions/return_spec.rb0000644005276200011600000000651613417161721021621 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' describe 'the return function' do include PuppetSpec::Compiler include Matchers::Resource context 'returns from outer function when called from nested block' do it 'with a given value as function result' do expect(compile_to_catalog(<<-CODE)).to have_resource('Notify[100]') function please_return() { [1,2,3].map |$x| { if $x == 1 { return(100) } 200 } 300 } notify { String(please_return()): } CODE end it 'with undef value as function result when not given an argument' do expect(compile_to_catalog(<<-CODE)).to have_resource('Notify[xy]') function please_return() { [1,2,3].map |$x| { if $x == 1 { return() } 200 } 300 } notify { "x${please_return}y": } CODE end end it 'can be called without parentheses around the argument' do expect(compile_to_catalog(<<-CODE)).to have_resource('Notify[100]') function please_return() { if 1 == 1 { return 100 } 200 } notify { String(please_return()): } CODE end it 'provides early exit from a class and keeps the class' do expect(eval_and_collect_notices(<<-CODE)).to eql(['a', 'c', 'true', 'true']) class notices_c { notice 'c' } class does_next { notice 'a' if 1 == 1 { return() } # avoid making next line statically unreachable notice 'b' } # include two classes to check that next does not do an early return from # the include function. include(does_next, notices_c) notice defined(does_next) notice defined(notices_c) CODE end it 'provides early exit from a user defined resource and keeps the resource' do expect(eval_and_collect_notices(<<-CODE)).to eql(['the_doer_of_next', 'copy_cat', 'true', 'true']) define does_next { notice $title if 1 == 1 { return() } # avoid making next line statically unreachable notice 'b' } define checker { notice defined(Does_next['the_doer_of_next']) notice defined(Does_next['copy_cat']) } # create two instances to ensure next does not break the entire # resource expression does_next { ['the_doer_of_next', 'copy_cat']: } checker { 'needed_because_evaluation_order': } CODE end it 'can be called when nested in a function to make that function return' do expect(eval_and_collect_notices(<<-CODE)).to eql(['100']) function nested_return() { with(1) |$x| { with($x) |$x| {return(100) }} } notice nested_return() CODE end it 'can not be called nested from top scope' do expect do compile_to_catalog(<<-CODE) # line 1 # line 2 $result = with(1) |$x| { with($x) |$x| {return(100) }} notice $result CODE end.to raise_error(/return\(\) from context where this is illegal \(file: unknown, line: 3\) on node.*/) end it 'can not be called from top scope' do expect do compile_to_catalog(<<-CODE) # line 1 # line 2 return() CODE end.to raise_error(/return\(\) from context where this is illegal \(file: unknown, line: 3\) on node.*/) end end puppet-5.5.10/spec/unit/functions/reverse_each_spec.rb0000644005276200011600000000744713417161721022741 0ustar jenkinsjenkinsrequire 'puppet' require 'spec_helper' require 'puppet_spec/compiler' require 'shared_behaviours/iterative_functions' describe 'the reverse_each function' do include PuppetSpec::Compiler it 'raises an error when given a type that cannot be iterated' do expect do compile_to_catalog(<<-MANIFEST) 3.14.reverse_each |$v| { } MANIFEST end.to raise_error(Puppet::Error, /expects an Iterable value, got Float/) end it 'raises an error when called with more than one argument and without a block' do expect do compile_to_catalog(<<-MANIFEST) [1].reverse_each(1) MANIFEST end.to raise_error(Puppet::Error, /expects 1 argument, got 2/) end it 'raises an error when called with more than one argument and a block' do expect do compile_to_catalog(<<-MANIFEST) [1].reverse_each(1) |$v| { } MANIFEST end.to raise_error(Puppet::Error, /expects 1 argument, got 2/) end it 'raises an error when called with a block with too many required parameters' do expect do compile_to_catalog(<<-MANIFEST) [1].reverse_each() |$v1, $v2| { } MANIFEST end.to raise_error(Puppet::Error, /block expects 1 argument, got 2/) end it 'raises an error when called with a block with too few parameters' do expect do compile_to_catalog(<<-MANIFEST) [1].reverse_each() | | { } MANIFEST end.to raise_error(Puppet::Error, /block expects 1 argument, got none/) end it 'does not raise an error when called with a block with too many but optional arguments' do expect do compile_to_catalog(<<-MANIFEST) [1].reverse_each() |$v1, $v2=extra| { } MANIFEST end.to_not raise_error end it 'returns an Undef when called with a block' do expect do compile_to_catalog(<<-MANIFEST) assert_type(Undef, [1].reverse_each |$x| { $x }) MANIFEST end.not_to raise_error end it 'returns an Iterable when called without a block' do expect do compile_to_catalog(<<-MANIFEST) assert_type(Iterable, [1].reverse_each) MANIFEST end.not_to raise_error end it 'should produce "times" interval of integer in reverse' do expect(eval_and_collect_notices('5.reverse_each |$x| { notice($x) }')).to eq(['4', '3', '2', '1', '0']) end it 'should produce range Integer[5,8] in reverse' do expect(eval_and_collect_notices('Integer[5,8].reverse_each |$x| { notice($x) }')).to eq(['8', '7', '6', '5']) end it 'should produce the choices of [first,second,third] in reverse' do expect(eval_and_collect_notices('[first,second,third].reverse_each |$x| { notice($x) }')).to eq(%w(third second first)) end it 'should produce the choices of {first => 1,second => 2,third => 3} in reverse' do expect(eval_and_collect_notices('{first => 1,second => 2,third => 3}.reverse_each |$t| { notice($t[0]) }')).to eq(%w(third second first)) end it 'should produce the choices of Enum[first,second,third] in reverse' do expect(eval_and_collect_notices('Enum[first,second,third].reverse_each |$x| { notice($x) }')).to eq(%w(third second first)) end it 'should produce nth element in reverse of range Integer[5,20] when chained after a step' do expect(eval_and_collect_notices('Integer[5,20].step(4).reverse_each |$x| { notice($x) }') ).to eq(['17', '13', '9', '5']) end it 'should produce nth element in reverse of times 5 when chained after a step' do expect(eval_and_collect_notices('5.step(2).reverse_each |$x| { notice($x) }')).to eq(['4', '2', '0']) end it 'should produce nth element in reverse of range Integer[5,20] when chained after a step' do expect(eval_and_collect_notices( '[5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20].step(4).reverse_each |$x| { notice($x) }') ).to eq(['17', '13', '9', '5']) end end puppet-5.5.10/spec/unit/functions/scanf_spec.rb0000644005276200011600000000212613417161721021365 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' describe 'the scanf function' do include PuppetSpec::Compiler include Matchers::Resource it 'scans a value and returns an array' do expect(compile_to_catalog("$x = '42'.scanf('%i')[0] + 1; notify { \"test$x\": }")).to have_resource('Notify[test43]') end it 'scans a value and returns result of a code block' do expect(compile_to_catalog("$x = '42'.scanf('%i')|$x|{$x[0]} + 1; notify { \"test$x\": }")).to have_resource('Notify[test43]') end it 'returns empty array if nothing was scanned' do expect(compile_to_catalog("$x = 'no'.scanf('%i')[0]; notify { \"test${x}test\": }")).to have_resource('Notify[testtest]') end it 'produces result up to first unsuccessful scan' do expect(compile_to_catalog("$x = '42 no'.scanf('%i'); notify { \"test${x[0]}${x[1]}test\": }")).to have_resource('Notify[test42test]') end it 'errors when not given enough arguments' do expect do compile_to_catalog("'42'.scanf()") end.to raise_error(/'scanf' expects 2 arguments, got 1/) end end puppet-5.5.10/spec/unit/functions/shared.rb0000644005276200011600000000704413417161721020533 0ustar jenkinsjenkinsshared_examples_for 'all functions transforming relative to absolute names' do |func_name| before(:each) do # mock that the class 'myclass' exists which are needed for the 'require' functions # as it checks the existence of the required class @klass = stub 'class', :name => "myclass" @scope.environment.known_resource_types.stubs(:find_hostclass).returns(@klass) @resource = Puppet::Parser::Resource.new(:file, "/my/file", :scope => @scope, :source => "source") @scope.stubs(:resource).returns @resource end it 'accepts a Class[name] type' do @scope.compiler.expects(:evaluate_classes).with(["::myclass"], @scope, false) @scope.call_function(func_name, [Puppet::Pops::Types::TypeFactory.host_class('myclass')]) end it 'accepts a Resource[class, name] type' do @scope.compiler.expects(:evaluate_classes).with(["::myclass"], @scope, false) @scope.call_function(func_name, [Puppet::Pops::Types::TypeFactory.resource('class', 'myclass')]) end it 'raises and error for unspecific Class' do expect { @scope.call_function(func_name, [Puppet::Pops::Types::TypeFactory.host_class()]) }.to raise_error(ArgumentError, /Cannot use an unspecific Class\[\] Type/) end it 'raises and error for Resource that is not of class type' do expect { @scope.call_function(func_name, [Puppet::Pops::Types::TypeFactory.resource('file')]) }.to raise_error(ArgumentError, /Cannot use a Resource\[File\] where a Resource\['class', name\] is expected/) end it 'raises and error for Resource that is unspecific' do expect { @scope.call_function(func_name, [Puppet::Pops::Types::TypeFactory.resource()]) }.to raise_error(ArgumentError, /Cannot use an unspecific Resource\[\] where a Resource\['class', name\] is expected/) end it 'raises and error for Resource[class] that is unspecific' do expect { @scope.call_function(func_name, [Puppet::Pops::Types::TypeFactory.resource('class')]) }.to raise_error(ArgumentError, /Cannot use an unspecific Resource\['class'\] where a Resource\['class', name\] is expected/) end end shared_examples_for 'an inclusion function, regardless of the type of class reference,' do |function| it "and #{function} a class absolutely, even when a relative namespaced class of the same name is present" do catalog = compile_to_catalog(<<-MANIFEST) class foo { class bar { } #{function} bar } class bar { } include foo MANIFEST expect(catalog.classes).to include('foo','bar') end it "and #{function} a class absolutely by Class['type'] reference" do catalog = compile_to_catalog(<<-MANIFEST) class foo { class bar { } #{function} Class['bar'] } class bar { } include foo MANIFEST expect(catalog.classes).to include('foo','bar') end it "and #{function} a class absolutely by Resource['type','title'] reference" do catalog = compile_to_catalog(<<-MANIFEST) class foo { class bar { } #{function} Resource['class','bar'] } class bar { } include foo MANIFEST expect(catalog.classes).to include('foo','bar') end end shared_examples_for 'an inclusion function, when --tasks is on,' do |function| it "is not available when --tasks is on" do Puppet[:tasks] = true expect do compile_to_catalog(<<-MANIFEST) #{function}(bar) MANIFEST end.to raise_error(Puppet::ParseError, /is only available when compiling a catalog/) end end puppet-5.5.10/spec/unit/functions/slice_spec.rb0000644005276200011600000001143113417161721021371 0ustar jenkinsjenkinsrequire 'puppet' require 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' describe 'methods' do include PuppetSpec::Compiler include Matchers::Resource before :each do node = Puppet::Node.new("floppy", :environment => 'production') @compiler = Puppet::Parser::Compiler.new(node) @scope = Puppet::Parser::Scope.new(@compiler) @topscope = @scope.compiler.topscope @scope.parent = @topscope end context "should be callable on array as" do it 'slice with explicit parameters' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1, present, 2, absent, 3, present] $a.slice(2) |$k,$v| { file { "/file_${$k}": ensure => $v } } MANIFEST expect(catalog).to have_resource("File[/file_1]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'absent') expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present') end it 'slice with captures last' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1, present, 2, absent, 3, present] $a.slice(2) |*$kv| { file { "/file_${$kv[0]}": ensure => $kv[1] } } MANIFEST expect(catalog).to have_resource("File[/file_1]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'absent') expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present') end it 'slice with one parameter' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1, present, 2, absent, 3, present] $a.slice(2) |$k| { file { "/file_${$k[0]}": ensure => $k[1] } } MANIFEST expect(catalog).to have_resource("File[/file_1]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'absent') expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present') end it 'slice with shorter last slice' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1, present, 2, present, 3, absent] $a.slice(4) |$a, $b, $c, $d| { file { "/file_$a.$c": ensure => $b } } MANIFEST expect(catalog).to have_resource("File[/file_1.2]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_3.]").with_parameter(:ensure, 'absent') end end context "should be callable on hash as" do it 'slice with explicit parameters, missing are empty' do catalog = compile_to_catalog(<<-MANIFEST) $a = {1=>present, 2=>present, 3=>absent} $a.slice(2) |$a,$b| { file { "/file_${a[0]}.${b[0]}": ensure => $a[1] } } MANIFEST expect(catalog).to have_resource("File[/file_1.2]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_3.]").with_parameter(:ensure, 'absent') end end context "should be callable on enumerable types as" do it 'slice with integer range' do catalog = compile_to_catalog(<<-MANIFEST) $a = Integer[1,4] $a.slice(2) |$a,$b| { file { "/file_${a}.${b}": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_1.2]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_3.4]").with_parameter(:ensure, 'present') end it 'slice with integer' do catalog = compile_to_catalog(<<-MANIFEST) 4.slice(2) |$a,$b| { file { "/file_${a}.${b}": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_0.1]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_2.3]").with_parameter(:ensure, 'present') end it 'slice with string' do catalog = compile_to_catalog(<<-MANIFEST) 'abcd'.slice(2) |$a,$b| { file { "/file_${a}.${b}": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_a.b]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_c.d]").with_parameter(:ensure, 'present') end end context "when called without a block" do it "should produce an array with the result" do catalog = compile_to_catalog(<<-MANIFEST) $a = [1, present, 2, absent, 3, present] $a.slice(2).each |$k| { file { "/file_${$k[0]}": ensure => $k[1] } } MANIFEST expect(catalog).to have_resource("File[/file_1]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_2]").with_parameter(:ensure, 'absent') expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present') end end end puppet-5.5.10/spec/unit/functions/step_spec.rb0000644005276200011600000000737313417161721021257 0ustar jenkinsjenkinsrequire 'puppet' require 'spec_helper' require 'puppet_spec/compiler' require 'shared_behaviours/iterative_functions' describe 'the step method' do include PuppetSpec::Compiler it 'raises an error when given a type that cannot be iterated' do expect do compile_to_catalog(<<-MANIFEST) 3.14.step(1) |$v| { } MANIFEST end.to raise_error(Puppet::Error, /expects an Iterable value, got Float/) end it 'raises an error when called with more than two arguments and a block' do expect do compile_to_catalog(<<-MANIFEST) [1].step(1,2) |$v| { } MANIFEST end.to raise_error(Puppet::Error, /expects 2 arguments, got 3/) end it 'raises an error when called with more than two arguments and without a block' do expect do compile_to_catalog(<<-MANIFEST) [1].step(1,2) MANIFEST end.to raise_error(Puppet::Error, /expects 2 arguments, got 3/) end it 'raises an error when called with a block with too many required parameters' do expect do compile_to_catalog(<<-MANIFEST) [1].step(1) |$v1, $v2| { } MANIFEST end.to raise_error(Puppet::Error, /block expects 1 argument, got 2/) end it 'raises an error when called with a block with too few parameters' do expect do compile_to_catalog(<<-MANIFEST) [1].step(1) | | { } MANIFEST end.to raise_error(Puppet::Error, /block expects 1 argument, got none/) end it 'raises an error when called with step == 0' do expect do compile_to_catalog(<<-MANIFEST) [1].step(0) |$x| { } MANIFEST end.to raise_error(Puppet::Error, /'step' expects an Integer\[1\] value, got Integer\[0, 0\]/) end it 'raises an error when step is not an integer' do expect do compile_to_catalog(<<-MANIFEST) [1].step('three') |$x| { } MANIFEST end.to raise_error(Puppet::Error, /'step' expects an Integer value, got String/) end it 'does not raise an error when called with a block with too many but optional arguments' do expect do compile_to_catalog(<<-MANIFEST) [1].step(1) |$v1, $v2=extra| { } MANIFEST end.to_not raise_error end it 'returns Undef when called with a block' do expect do compile_to_catalog(<<-MANIFEST) assert_type(Undef, [1].step(2) |$x| { $x }) MANIFEST end.not_to raise_error end it 'returns an Iterable when called without a block' do expect do compile_to_catalog(<<-MANIFEST) assert_type(Iterable, [1].step(2)) MANIFEST end.not_to raise_error end it 'should produce "times" interval of integer according to step' do expect(eval_and_collect_notices('10.step(2) |$x| { notice($x) }')).to eq(['0', '2', '4', '6', '8']) end it 'should produce interval of Integer[5,20] according to step' do expect(eval_and_collect_notices('Integer[5,20].step(4) |$x| { notice($x) }')).to eq(['5', '9', '13', '17']) end it 'should produce the elements of [a,b,c,d,e,f,g,h] according to step' do expect(eval_and_collect_notices('[a,b,c,d,e,f,g,h].step(2) |$x| { notice($x) }')).to eq(%w(a c e g)) end it 'should produce the elements {a=>1,b=>2,c=>3,d=>4,e=>5,f=>6,g=>7,h=>8} according to step' do expect(eval_and_collect_notices('{a=>1,b=>2,c=>3,d=>4,e=>5,f=>6,g=>7,h=>8}.step(2) |$t| { notice($t[1]) }')).to eq(%w(1 3 5 7)) end it 'should produce the choices of Enum[a,b,c,d,e,f,g,h] according to step' do expect(eval_and_collect_notices('Enum[a,b,c,d,e,f,g,h].step(2) |$x| { notice($x) }')).to eq(%w(a c e g)) end it 'should produce descending interval of Integer[5,20] when chained after a reverse_each' do expect(eval_and_collect_notices('Integer[5,20].reverse_each.step(4) |$x| { notice($x) }')).to eq(['20', '16', '12', '8']) end end puppet-5.5.10/spec/unit/functions/strftime_spec.rb0000644005276200011600000001430213417161721022127 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/compiler' describe 'the strftime function' do include PuppetSpec::Compiler def test_format(ctor_arg, format, expected) expect(eval_and_collect_notices("notice(strftime(Timespan(#{ctor_arg}), '#{format}'))")).to eql(["#{expected}"]) end context 'when applied to a Timespan' do [ ['hours', 'H', 2], ['minutes', 'M', 2], ['seconds', 'S', 2], ].each do |field, fmt, dflt_width| ctor_arg = "{#{field}=>3}" it "%#{fmt} width defaults to #{dflt_width}" do test_format(ctor_arg, "%#{fmt}", sprintf("%0#{dflt_width}d", 3)) end it "%_#{fmt} pads with space" do test_format(ctor_arg, "%_#{fmt}", sprintf("% #{dflt_width}d", 3)) end it "%-#{fmt} does not pad" do test_format(ctor_arg, "%-#{fmt}", '3') end it "%10#{fmt} pads with zeroes to specified width" do test_format(ctor_arg, "%10#{fmt}", sprintf("%010d", 3)) end it "%_10#{fmt} pads with space to specified width" do test_format(ctor_arg, "%_10#{fmt}", sprintf("% 10d", 3)) end it "%-10#{fmt} does not pad even if width is specified" do test_format(ctor_arg, "%-10#{fmt}", '3') end end [ ['milliseconds', 'L', 3], ['nanoseconds', 'N', 9], ['milliseconds', '3N', 3], ['microseconds', '6N', 6], ['nanoseconds', '9N', 9], ].each do |field, fmt, dflt_width| ctor_arg = "{#{field}=>3000}" it "%#{fmt} width defaults to #{dflt_width}" do test_format(ctor_arg, "%#{fmt}", sprintf("%-#{dflt_width}d", 3000)) end it "%_#{fmt} pads with space" do test_format(ctor_arg, "%_#{fmt}", sprintf("%-#{dflt_width}d", 3000)) end it "%-#{fmt} does not pad" do test_format(ctor_arg, "%-#{fmt}", '3000') end end it 'can use a format containing all format characters, flags, and widths' do test_format("{string => '100-14:02:24.123456000', format => '%D-%H:%M:%S.%9N'}", '%_10D%%%03H:%-M:%S.%9N', ' 100%014:2:24.123456000') end it 'can format and strip excess zeroes from fragment using no-padding flag' do test_format("{string => '100-14:02:24.123456000', format => '%D-%H:%M:%S.%N'}", '%D-%H:%M:%S.%-N', '100-14:02:24.123456') end it 'can format and replace excess zeroes with spaces from fragment using space-padding flag and default widht' do test_format("{string => '100-14:02:24.123456000', format => '%D-%H:%M:%S.%N'}", '%D-%H:%M:%S.%_N', '100-14:02:24.123456 ') end it 'can format and replace excess zeroes with spaces from fragment using space-padding flag and specified width' do test_format("{string => '100-14:02:24.123400000', format => '%D-%H:%M:%S.%N'}", '%D-%H:%M:%S.%_6N', '100-14:02:24.1234 ') end it 'can format and retain excess zeroes in fragment using default width' do test_format("{string => '100-14:02:24.123400000', format => '%D-%H:%M:%S.%N'}", '%D-%H:%M:%S.%N', '100-14:02:24.123400000') end it 'can format and retain excess zeroes in fragment using specified width' do test_format("{string => '100-14:02:24.123400000', format => '%D-%H:%M:%S.%N'}", '%D-%H:%M:%S.%6N', '100-14:02:24.123400') end end def test_timestamp_format(ctor_arg, format, expected) expect(eval_and_collect_notices("notice(strftime(Timestamp('#{ctor_arg}'), '#{format}'))")).to eql(["#{expected}"]) end def test_timestamp_format_tz(ctor_arg, format, tz, expected) expect(eval_and_collect_notices("notice(strftime(Timestamp('#{ctor_arg}'), '#{format}', '#{tz}'))")).to eql(["#{expected}"]) end def collect_log(code, node = Puppet::Node.new('foonode')) Puppet[:code] = code compiler = Puppet::Parser::Compiler.new(node) node.environment.check_for_reparse logs = [] Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do compiler.compile end logs end context 'when applied to a Timestamp' do it 'can format a timestamp with a format pattern' do test_timestamp_format('2016-09-23T13:14:15.123 UTC', '%Y-%m-%d %H:%M:%S.%L %z', '2016-09-23 13:14:15.123 +0000') end it 'can format a timestamp using a specific timezone' do test_timestamp_format_tz('2016-09-23T13:14:15.123 UTC', '%Y-%m-%d %H:%M:%S.%L %z', 'EST', '2016-09-23 08:14:15.123 -0500') end end context 'when used with dispatcher covering legacy stdlib API (String format, String timeszone = undef)' do it 'produces the current time when used with one argument' do before_eval = Time.now notices = eval_and_collect_notices("notice(strftime('%F %T'))") expect(notices).not_to be_empty expect(notices[0]).to match(/\A\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\z/) parsed_time = DateTime.strptime(notices[0], '%F %T').to_time expect(Time.now.to_i >= parsed_time.to_i && parsed_time.to_i >= before_eval.to_i).to be_truthy end it 'emits a deprecation warning when used with one argument' do log = collect_log("notice(strftime('%F %T'))") warnings = log.select { |log_entry| log_entry.level == :warning }.map { |log_entry| log_entry.message } expect(warnings).not_to be_empty expect(warnings[0]).to match(/The argument signature \(String format, \[String timezone\]\) is deprecated for #strftime/) end it 'produces the current time formatted with specific timezone when used with two arguments' do before_eval = Time.now notices = eval_and_collect_notices("notice(strftime('%F %T %:z', 'EST'))") expect(notices).not_to be_empty expect(notices[0]).to match(/\A\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} -05:00\z/) parsed_time = DateTime.strptime(notices[0], '%F %T %z').to_time expect(Time.now.to_i >= parsed_time.to_i && parsed_time.to_i >= before_eval.to_i).to be_truthy end it 'emits a deprecation warning when using legacy format with two arguments' do log = collect_log("notice(strftime('%F %T', 'EST'))") warnings = log.select { |log_entry| log_entry.level == :warning }.map { |log_entry| log_entry.message } expect(warnings).not_to be_empty expect(warnings[0]).to match(/The argument signature \(String format, \[String timezone\]\) is deprecated for #strftime/) end end end puppet-5.5.10/spec/unit/functions/then_spec.rb0000644005276200011600000000236713417161721021240 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' describe 'the then function' do include PuppetSpec::Compiler include Matchers::Resource it 'calls a lambda passing one argument' do expect(compile_to_catalog("then(testing) |$x| { notify { $x: } }")).to have_resource('Notify[testing]') end it 'produces what lambda returns if value is not undef' do expect(compile_to_catalog("notify{ then(1) |$x| { testing }: }")).to have_resource('Notify[testing]') end it 'does not call lambda if argument is undef' do expect(compile_to_catalog('then(undef) |$x| { notify { "failed": } }')).to_not have_resource('Notify[failed]') end it 'produces undef if given value is undef' do expect(compile_to_catalog(<<-SOURCE)).to have_resource('Notify[test-Undef-ing]') notify{ "test-${type(then(undef) |$x| { testing })}-ing": } SOURCE end it 'errors when lambda wants too many args' do expect do compile_to_catalog('then(1) |$x, $y| { }') end.to raise_error(/'then' block expects 1 argument, got 2/m) end it 'errors when lambda wants too few args' do expect do compile_to_catalog('then(1) || { }') end.to raise_error(/'then' block expects 1 argument, got none/m) end end puppet-5.5.10/spec/unit/functions/tree_each_spec.rb0000644005276200011600000004036013417161721022214 0ustar jenkinsjenkinsrequire 'puppet' require 'spec_helper' require 'puppet_spec/compiler' require 'shared_behaviours/iterative_functions' describe 'the tree_each function' do include PuppetSpec::Compiler context "can be called on" do it 'an Array, yielding path and value when lambda has arity 2' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,2,3] $msg = inline_epp(@(TEMPLATE)) <% $a.tree_each() |$path, $v| { -%> path: <%= $path %> value: <%= $v %> <% } -%> | TEMPLATE notify {'test': message => $msg} MANIFEST expect(catalog.resource(:notify, 'test')['message']).to eq( [ 'path: [] value: [1, 2, 3]', 'path: [0] value: 1', 'path: [1] value: 2', 'path: [2] value: 3', '' ].join("\n")) end it 'an Array, yielding only value when lambda has arity 1' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,2,3] $msg = inline_epp(@(TEMPLATE)) <% $a.tree_each() | $v| { -%> path: - value: <%= $v %> <% } -%> | TEMPLATE notify {'test': message => $msg} MANIFEST expect(catalog.resource(:notify, 'test')['message']).to eq( [ 'path: - value: [1, 2, 3]', 'path: - value: 1', 'path: - value: 2', 'path: - value: 3', '' ].join("\n")) end it 'a Hash, yielding path and value when lambda has arity 2' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'a'=>'apple','b'=>'banana'} $msg = inline_epp(@(TEMPLATE)) <% $a.tree_each() |$path, $v| { -%> path: <%= $path %> value: <%= $v %> <% } -%> | TEMPLATE notify {'test': message => $msg} MANIFEST expect(catalog.resource(:notify, 'test')['message']).to eq( [ 'path: [] value: {a => apple, b => banana}', 'path: [a] value: apple', 'path: [b] value: banana', '' ].join("\n")) end it 'a Hash, yielding only value when lambda has arity 1' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'a'=>'apple','b'=>'banana'} $msg = inline_epp(@(TEMPLATE)) <% $a.tree_each() | $v| { -%> path: - value: <%= $v %> <% } -%> | TEMPLATE notify {'test': message => $msg} MANIFEST expect(catalog.resource(:notify, 'test')['message']).to eq( [ 'path: - value: {a => apple, b => banana}', 'path: - value: apple', 'path: - value: banana', '' ].join("\n")) end it 'an Object, yielding path and value when lambda has arity 2' do # this also tests that include_refs => true includes references catalog = compile_to_catalog(<<-MANIFEST) type Person = Object[{attributes => { name => String, father => Optional[Person], mother => { kind => reference, type => Optional[Person] } }}] $adam = Person({name => 'Adam'}) $eve = Person({name => 'Eve'}) $cain = Person({name => 'Cain', mother => $eve, father => $adam}) $awan = Person({name => 'Awan', mother => $eve, father => $adam}) $enoch = Person({name => 'Enoch', mother => $awan, father => $cain}) $msg = inline_epp(@(TEMPLATE)) <% $enoch.tree_each({include_containers=>false, include_refs => true}) |$path, $v| { unless $v =~ Undef {-%> path: <%= $path %> value: <%= $v %> <% }} -%> | TEMPLATE notify {'with_refs': message => $msg} MANIFEST expect(catalog.resource(:notify, 'with_refs')['message']).to eq( [ 'path: [name] value: Enoch', 'path: [father, name] value: Cain', 'path: [father, father, name] value: Adam', 'path: [father, mother, name] value: Eve', 'path: [mother, name] value: Awan', 'path: [mother, father, name] value: Adam', 'path: [mother, mother, name] value: Eve', '' ].join("\n")) end end context 'a yielded path' do it 'holds integer values for Array index at each level' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,[2,[3]]] $msg = inline_epp(@(TEMPLATE)) <% $a.tree_each() |$path, $v| { -%> path: <%= $path %> t: <%= $path.map |$x| { type($x, generalized) } %> value: <%= $v %> <% } -%> | TEMPLATE notify {'test': message => $msg} MANIFEST expect(catalog.resource(:notify, 'test')['message']).to eq( [ 'path: [] t: [] value: [1, [2, [3]]]', 'path: [0] t: [Integer] value: 1', 'path: [1] t: [Integer] value: [2, [3]]', 'path: [1, 0] t: [Integer, Integer] value: 2', 'path: [1, 1] t: [Integer, Integer] value: [3]', 'path: [1, 1, 0] t: [Integer, Integer, Integer] value: 3', '' ].join("\n")) end it 'holds Any values for Hash keys at each level' do catalog = compile_to_catalog(<<-MANIFEST) $a = {a => 1, /fancy/=> {c => 2, d=>{[e] => 3}}} $msg = inline_epp(@(TEMPLATE)) <% $a.tree_each() |$path, $v| { -%> path: <%= $path %> t: <%= $path.map |$x| { type($x, generalized) } %> value: <%= $v %> <% } -%> | TEMPLATE notify {'test': message => $msg} MANIFEST expect(catalog.resource(:notify, 'test')['message']).to eq( [ 'path: [] t: [] value: {a => 1, /fancy/ => {c => 2, d => {[e] => 3}}}', 'path: [a] t: [String] value: 1', 'path: [/fancy/] t: [Regexp[/fancy/]] value: {c => 2, d => {[e] => 3}}', 'path: [/fancy/, c] t: [Regexp[/fancy/], String] value: 2', 'path: [/fancy/, d] t: [Regexp[/fancy/], String] value: {[e] => 3}', 'path: [/fancy/, d, [e]] t: [Regexp[/fancy/], String, Array[String]] value: 3', '' ].join("\n")) end end it 'errors when asked to operate on a String' do expect { compile_to_catalog(<<-MANIFEST) "hello".tree_each() |$path, $v| { notice "$v" } MANIFEST }.to raise_error(/expects a value of type Iterator, Array, Hash, or Object/) end context 'produces' do it 'the receiver when given a lambda' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1, 3, 2] $b = $a.tree_each |$path, $x| { "unwanted" } file { "/file_${b[1]}": ensure => present } MANIFEST expect(catalog.resource(:file, "/file_3")['ensure']).to eq('present') end it 'an Iterator when not given a lambda' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1, 3, 2] $b = $a.tree_each file { "/file_${$b =~ Iterator}": ensure => present } MANIFEST expect(catalog.resource(:file, "/file_true")['ensure']).to eq('present') end end context 'a produced iterator' do ['depth_first', 'breadth_first'].each do |order| context "for #{order} can be unrolled by creating an Array using" do it "the () operator" do catalog = compile_to_catalog(<<-MANIFEST) $a = [1, 3, 2] $b = Array($a.tree_each({order => #{order}})) $msg = inline_epp(@(TEMPLATE)) <% $b.each() |$v| { -%> path: <%= $v[0] %> value: <%= $v[1] %> <% } -%> | TEMPLATE notify {'test': message => $msg} MANIFEST expect(catalog.resource(:notify, "test")['message']).to eq([ 'path: [] value: [1, 3, 2]', 'path: [0] value: 1', 'path: [1] value: 3', 'path: [2] value: 2', '' ].join("\n")) end it "the splat operator" do catalog = compile_to_catalog(<<-MANIFEST) $a = [1, 3, 2] $b = *$a.tree_each({order => #{order}}) assert_type(Array[Array], $b) $msg = inline_epp(@(TEMPLATE)) <% $b.each() |$v| { -%> path: <%= $v[0] %> value: <%= $v[1] %> <% } -%> | TEMPLATE notify {'test': message => $msg} MANIFEST expect(catalog.resource(:notify, "test")['message']).to eq([ 'path: [] value: [1, 3, 2]', 'path: [0] value: 1', 'path: [1] value: 3', 'path: [2] value: 2', '' ].join("\n")) end end end end context 'recursively yields under the control of options such that' do it 'both containers and leafs are included by default' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,[2,[3]]] $msg = inline_epp(@(TEMPLATE)) <% $a.tree_each() |$path, $v| { -%> path: <%= $path %> value: <%= $v %> <% } -%> | TEMPLATE notify {'test': message => $msg} MANIFEST expect(catalog.resource(:notify, 'test')['message']).to eq( [ 'path: [] value: [1, [2, [3]]]', 'path: [0] value: 1', 'path: [1] value: [2, [3]]', 'path: [1, 0] value: 2', 'path: [1, 1] value: [3]', 'path: [1, 1, 0] value: 3', '' ].join("\n")) end it 'containers are skipped when option include_containers=false is used' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,[2,[3]]] $msg = inline_epp(@(TEMPLATE)) <% $a.tree_each({include_containers => false}) |$path, $v| { -%> path: <%= $path %> value: <%= $v %> <% } -%> | TEMPLATE notify {'test': message => $msg} MANIFEST expect(catalog.resource(:notify, 'test')['message']).to eq( [ 'path: [0] value: 1', 'path: [1, 0] value: 2', 'path: [1, 1, 0] value: 3', '' ].join("\n")) end it 'values are skipped when option include_values=false is used' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,[2,[3]]] $msg = inline_epp(@(TEMPLATE)) <% $a.tree_each({include_values => false}) |$path, $v| { -%> path: <%= $path %> value: <%= $v %> <% } -%> | TEMPLATE notify {'test': message => $msg} MANIFEST expect(catalog.resource(:notify, 'test')['message']).to eq( [ 'path: [] value: [1, [2, [3]]]', 'path: [1] value: [2, [3]]', 'path: [1, 1] value: [3]', '' ].join("\n")) end it 'the root container is skipped when option include_root=false is used' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,[2,[3]]] $msg = inline_epp(@(TEMPLATE)) <% $a.tree_each({include_root => false, include_values => false}) |$path, $v| { -%> path: <%= $path %> value: <%= $v %> <% } -%> | TEMPLATE notify {'test': message => $msg} MANIFEST expect(catalog.resource(:notify, 'test')['message']).to eq( [ 'path: [1] value: [2, [3]]', 'path: [1, 1] value: [3]', '' ].join("\n")) end it 'containers must be included for root to be included' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,[2,[3]]] $msg = inline_epp(@(TEMPLATE)) <% $a.tree_each({include_containers => false, include_root => true}) |$path, $v| { -%> path: <%= $path %> value: <%= $v %> <% } -%> | TEMPLATE notify {'test': message => $msg} MANIFEST expect(catalog.resource(:notify, 'test')['message']).to eq( [ 'path: [0] value: 1', 'path: [1, 0] value: 2', 'path: [1, 1, 0] value: 3', '' ].join("\n")) end it 'errors when asked to exclude both containers and values' do expect { compile_to_catalog(<<-MANIFEST) [1,2,3].tree_each({include_containers => false, include_values => false}) |$path, $v| { notice "$v" } MANIFEST }.to raise_error(/Options 'include_containers' and 'include_values' cannot both be false/) end it 'tree nodes are yielded in depth first order if option order=depth_first' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,[2,[3], 4], 5] $msg = inline_epp(@(TEMPLATE)) <% $a.tree_each({order => depth_first, include_containers => false}) |$path, $v| { -%> path: <%= $path %> value: <%= $v %> <% } -%> | TEMPLATE notify {'test': message => $msg} MANIFEST expect(catalog.resource(:notify, 'test')['message']).to eq( [ 'path: [0] value: 1', 'path: [1, 0] value: 2', 'path: [1, 1, 0] value: 3', 'path: [1, 2] value: 4', 'path: [2] value: 5', '' ].join("\n")) end it 'tree nodes are yielded in breadth first order if option order=breadth_first' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,[2,[3], 4], 5] $msg = inline_epp(@(TEMPLATE)) <% $a.tree_each({order => breadth_first, include_containers => false}) |$path, $v| { -%> path: <%= $path %> value: <%= $v %> <% } -%> | TEMPLATE notify {'test': message => $msg} MANIFEST expect(catalog.resource(:notify, 'test')['message']).to eq( [ 'path: [0] value: 1', 'path: [2] value: 5', 'path: [1, 0] value: 2', 'path: [1, 2] value: 4', 'path: [1, 1, 0] value: 3', '' ].join("\n")) end it 'attributes of an Object of "reference" kind are not yielded by default' do catalog = compile_to_catalog(<<-MANIFEST) type Person = Object[{attributes => { name => String, father => Optional[Person], mother => { kind => reference, type => Optional[Person] } }}] $adam = Person({name => 'Adam'}) $eve = Person({name => 'Eve'}) $cain = Person({name => 'Cain', mother => $eve, father => $adam}) $awan = Person({name => 'Awan', mother => $eve, father => $adam}) $enoch = Person({name => 'Enoch', mother => $awan, father => $cain}) $msg = inline_epp(@(TEMPLATE)) <% $enoch.tree_each({include_containers=>false }) |$path, $v| { unless $v =~ Undef {-%> path: <%= $path %> value: <%= $v %> <% }} -%> | TEMPLATE notify {'by_default': message => $msg} $msg2 = inline_epp(@(TEMPLATE)) <% $enoch.tree_each({include_containers=>false, include_refs => false}) |$path, $v| { unless $v =~ Undef {-%> path: <%= $path %> value: <%= $v %> <% }} -%> | TEMPLATE notify {'when_false': message => $msg2} MANIFEST expected_refs_excluded_result = [ 'path: [name] value: Enoch', 'path: [father, name] value: Cain', 'path: [father, father, name] value: Adam', '' ].join("\n") expect(catalog.resource(:notify, 'by_default')['message']).to eq(expected_refs_excluded_result) expect(catalog.resource(:notify, 'when_false')['message']).to eq(expected_refs_excluded_result) end end context 'can be chained' do it 'with reverse_each()' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,[2,[3]]] $msg = inline_epp(@(TEMPLATE)) <% $a.tree_each({include_containers => false}).reverse_each |$v| { -%> path: <%= $v[0] %> value: <%= $v[1] %> <% } -%> | TEMPLATE notify {'test': message => $msg} MANIFEST expect(catalog.resource(:notify, 'test')['message']).to eq( [ 'path: [1, 1, 0] value: 3', 'path: [1, 0] value: 2', 'path: [0] value: 1', '' ].join("\n")) end it 'with step()' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,[2,[3,[4,[5]]]]] $msg = inline_epp(@(TEMPLATE)) <% $a.tree_each({include_containers => false}).step(2) |$v| { -%> path: <%= $v[0] %> value: <%= $v[1] %> <% } -%> | TEMPLATE notify {'test': message => $msg} MANIFEST expect(catalog.resource(:notify, 'test')['message']).to eq( [ 'path: [0] value: 1', 'path: [1, 1, 0] value: 3', 'path: [1, 1, 1, 1, 0] value: 5', '' ].join("\n")) end end end puppet-5.5.10/spec/unit/functions/type_spec.rb0000644005276200011600000000253413417161721021257 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' describe 'the type function' do include PuppetSpec::Compiler include Matchers::Resource it 'produces the type of a given value with default detailed quality' do expect(compile_to_catalog('notify { "${ type([2, 3.14]) }": }')).to have_resource( 'Notify[Tuple[Integer[2, 2], Float[3.14, 3.14]]]') end it 'produces the type of a give value with detailed quality when quality is given' do expect(compile_to_catalog('notify { "${ type([2, 3.14], detailed) }": }')).to have_resource( 'Notify[Tuple[Integer[2, 2], Float[3.14, 3.14]]]') end it 'produces the type of a given value with reduced quality when quality is given' do expect(compile_to_catalog('notify { "${ type([2, 3.14], reduced) }": }')).to have_resource( 'Notify[Array[Numeric, 2, 2]]') end it 'produces the type of a given value with generalized quality when quality is given' do expect(compile_to_catalog('notify { "${ type([2, 3.14], generalized) }": }')).to have_resource( 'Notify[Array[Numeric]]') end it 'errors when given a fault inference quality' do expect do compile_to_catalog("notify { type([2, 4.14], gobbledygooked): }") end.to raise_error(/expects a match for Enum\['detailed', 'generalized', 'reduced'\], got 'gobbledygooked'/) end end puppet-5.5.10/spec/unit/functions/unique_spec.rb0000644005276200011600000001072613417161721021606 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' describe 'the unique function' do include PuppetSpec::Compiler include Matchers::Resource context 'produces the unique set of chars from a String such that' do it 'same case is considered unique' do expect(compile_to_catalog(<<-SOURCE)).to have_resource('Notify[test]').with_parameter(:message, "abc") notify{ 'test': message => 'abcbbcc'.unique } SOURCE end it 'different case is not considered unique' do expect(compile_to_catalog(<<-SOURCE)).to have_resource('Notify[test]').with_parameter(:message, "abcABC") notify{ 'test': message => 'abcAbbBccC'.unique } SOURCE end it 'case independent matching can be performed with a lambda' do expect(compile_to_catalog(<<-SOURCE)).to have_resource('Notify[test]').with_parameter(:message, "abc") notify{ 'test': message => 'abcAbbBccC'.unique |$x| { String($x, '%d') } } SOURCE end it 'the first found value in the unique set is used' do expect(compile_to_catalog(<<-SOURCE)).to have_resource('Notify[test]').with_parameter(:message, "AbC") notify{ 'test': message => 'AbCAbbBccC'.unique |$x| { String($x, '%d') } } SOURCE end end context 'produces the unique set of values from an Array such that' do it 'ruby equality is used to compute uniqueness by default' do expect(compile_to_catalog(<<-SOURCE)).to have_resource('Notify[test]').with_parameter(:message, ['a', 'b', 'c', 'B', 'C']) notify{ 'test': message => [a, b, c, a, 'B', 'C'].unique } SOURCE end it 'accepts a lambda to perform the value to use for uniqueness' do expect(compile_to_catalog(<<-SOURCE)).to have_resource('Notify[test]').with_parameter(:message, ['a', 'b', 'c']) notify{ 'test': message => [a, b, c, a, 'B', 'C'].unique |$x| { String($x, '%d') }} SOURCE end it 'the first found value in the unique set is used' do expect(compile_to_catalog(<<-SOURCE)).to have_resource('Notify[test]').with_parameter(:message, ['A', 'b', 'C']) notify{ 'test': message => ['A', b, 'C', a, 'B', 'c'].unique |$x| { String($x, '%d') }} SOURCE end end context 'produces the unique set of values from an Hash such that' do it 'resulting keys and values in hash are arrays' do expect(compile_to_catalog(<<-SOURCE)).to have_resource('Notify[test]').with_parameter(:message, {['a'] => [10], ['b']=>[20]}) notify{ 'test': message => {a => 10, b => 20}.unique } SOURCE end it 'resulting keys contain all keys with same value' do expect(compile_to_catalog(<<-SOURCE)).to have_resource('Notify[test]').with_parameter(:message, {['a', 'b'] => [10], ['c']=>[20]}) notify{ 'test': message => {a => 10, b => 10, c => 20}.unique } SOURCE end it 'resulting values contain Ruby == unique set of values' do expect(compile_to_catalog(<<-SOURCE)).to have_resource('Notify[test]').with_parameter(:message, {['a'] => [10], ['b', 'c']=>[11, 20]}) notify{ 'test': message => {a => 10, b => 11, c => 20}.unique |$x| { if $x > 10 {bigly} else { $x }}} SOURCE end end context 'produces the unique set of values from an Iterable' do it 'such as reverse_each - in reverse order' do expect(compile_to_catalog(<<-SOURCE)).to have_resource('Notify[test]').with_parameter(:message, ['B','b','a']) notify{ 'test': message => ['a', 'b', 'B'].reverse_each.unique } SOURCE end it 'such as Integer[1,5]' do expect(compile_to_catalog(<<-SOURCE)).to have_resource('Notify[test]').with_parameter(:message, [1,2,3,4,5]) notify{ 'test': message => Integer[1,5].unique } SOURCE end it 'such as the Integer 3' do expect(compile_to_catalog(<<-SOURCE)).to have_resource('Notify[test]').with_parameter(:message, [0,1,2]) notify{ 'test': message => 3.unique } SOURCE end it 'allows lambda to be used with Iterable' do expect(compile_to_catalog(<<-SOURCE)).to have_resource('Notify[test]').with_parameter(:message, ['B','a']) notify{ 'test': message => ['a', 'b', 'B'].reverse_each.unique |$x| { String($x, '%d') }} SOURCE end end it 'errors when given unsupported data type as input' do expect do compile_to_catalog(<<-SOURCE) undef.unique SOURCE end.to raise_error(/expects an Iterable value, got Undef/) end end puppet-5.5.10/spec/unit/functions/unwrap_spec.rb0000644005276200011600000000147713417161721021617 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' describe 'the unwrap function' do include PuppetSpec::Compiler include Matchers::Resource it 'unwraps a sensitive value' do code = <<-CODE $sensitive = Sensitive.new("12345") notice("unwrapped value is ${sensitive.unwrap}") CODE expect(eval_and_collect_notices(code)).to eq(['unwrapped value is 12345']) end it 'unwraps a sensitive value when given a code block' do code = <<-CODE $sensitive = Sensitive.new("12345") $split = $sensitive.unwrap |$unwrapped| { notice("unwrapped value is $unwrapped") $unwrapped.split(/3/) } notice("split is $split") CODE expect(eval_and_collect_notices(code)).to eq(['unwrapped value is 12345', 'split is [12, 45]']) end end puppet-5.5.10/spec/unit/functions/values_spec.rb0000644005276200011600000000165513417161721021600 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' describe 'the values function' do include PuppetSpec::Compiler include Matchers::Resource it 'returns the values in the hash in the order they appear in a hash iteration' do expect(compile_to_catalog(<<-'SRC'.unindent)).to have_resource('Notify[1 & 2]') $k = {'apples' => 1, 'oranges' => 2}.values notify { "${k[0]} & ${k[1]}": } SRC end it 'returns an empty array for an empty hash' do expect(compile_to_catalog(<<-'SRC'.unindent)).to have_resource('Notify[0]') $v = {}.values.reduce(0) |$m, $v| { $m+1 } notify { "${v}": } SRC end it 'includes an undef value if one is present in the hash' do expect(compile_to_catalog(<<-'SRC'.unindent)).to have_resource('Notify[Undef]') $types = {a => undef}.values.map |$v| { $v.type } notify { "${types[0]}": } SRC end end puppet-5.5.10/spec/unit/functions/with_spec.rb0000644005276200011600000000214313417161721021245 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' describe 'the with function' do include PuppetSpec::Compiler include Matchers::Resource it 'calls a lambda passing no arguments' do expect(compile_to_catalog("with() || { notify { testing: } }")).to have_resource('Notify[testing]') end it 'calls a lambda passing a single argument' do expect(compile_to_catalog('with(1) |$x| { notify { "testing$x": } }')).to have_resource('Notify[testing1]') end it 'calls a lambda passing more than one argument' do expect(compile_to_catalog('with(1, 2) |*$x| { notify { "testing${x[0]}, ${x[1]}": } }')).to have_resource('Notify[testing1, 2]') end it 'passes a type reference to a lambda' do expect(compile_to_catalog('notify { test: message => "data" } with(Notify[test]) |$x| { notify { "${x[message]}": } }')).to have_resource('Notify[data]') end it 'errors when not given enough arguments for the lambda' do expect do compile_to_catalog('with(1) |$x, $y| { }') end.to raise_error(/Parameter \$y is required but no value was given/m) end end puppet-5.5.10/spec/unit/functions/call_spec.rb0000644005276200011600000000437413417161722021216 0ustar jenkinsjenkinsrequire 'puppet' require 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' describe 'the call method' do include PuppetSpec::Compiler include PuppetSpec::Files include Matchers::Resource context "should be callable as" do let(:env_name) { 'testenv' } let(:environments_dir) { Puppet[:environmentpath] } let(:env_dir) { File.join(environments_dir, env_name) } let(:env) { Puppet::Node::Environment.create(env_name.to_sym, [File.join(populated_env_dir, 'modules')]) } let(:node) { Puppet::Node.new("test", :environment => env) } let(:env_dir_files) { { 'modules' => { 'test' => { 'functions' => { 'call_me.pp' => 'function test::call_me() { "called" }' } } } } } let(:populated_env_dir) do dir_contained_in(environments_dir, env_name => env_dir_files) PuppetSpec::Files.record_tmp(env_dir) env_dir end it 'call on a built-in 4x Ruby API function' do expect(compile_to_catalog(<<-CODE)).to have_resource('Notify[a]') $a = call('split', 'a-b-c', '-') notify { $a[0]: } CODE end it 'call on a Puppet language function with no arguments' do expect(compile_to_catalog(<<-CODE, node)).to have_resource('Notify[called]') notify { test::call_me(): } CODE end it 'call a Ruby 4x API built-in with block' do catalog = compile_to_catalog(<<-CODE) $a = 'each' $b = [1,2,3] call($a, $b) |$index, $v| { file { "/file_$v": ensure => present } } CODE expect(catalog.resource(:file, "/file_1")['ensure']).to eq('present') expect(catalog.resource(:file, "/file_2")['ensure']).to eq('present') expect(catalog.resource(:file, "/file_3")['ensure']).to eq('present') end it 'call with the calling context' do expect(eval_and_collect_notices(<<-CODE)).to eq(['a']) class a { call('notice', $title) } include a CODE end it 'call on a non-existent function name' do expect { compile_to_catalog(<<-CODE) }.to raise_error(Puppet::Error, /Unknown function/) $a = call('not_a_function_name') notify { $a: } CODE end end end puppet-5.5.10/spec/unit/functions/convert_to_spec.rb0000644005276200011600000000151413417161722022456 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' describe 'the convert_to function' do include PuppetSpec::Compiler include Matchers::Resource it 'converts and returns the converted when no lambda is given' do expect(compile_to_catalog('notify{ "testing-${[a,1].convert_to(Hash) =~ Hash}": }')).to have_resource('Notify[testing-true]') end it 'converts given value to instance of type and calls a lambda with converted value' do expect(compile_to_catalog('"1".convert_to(Integer) |$x| { notify { "testing-${x.type(generalized)}": } }')).to have_resource('Notify[testing-Integer]') end it 'returns the lambda return when lambda is given' do expect(compile_to_catalog('notify{ "testing-${[a,1].convert_to(Hash) |$x| { yay }}": }')).to have_resource('Notify[testing-yay]') end end puppet-5.5.10/spec/unit/functions/filter_spec.rb0000644005276200011600000001325013417161722021561 0ustar jenkinsjenkinsrequire 'puppet' require 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' require 'shared_behaviours/iterative_functions' describe 'the filter method' do include PuppetSpec::Compiler include Matchers::Resource it 'should filter on an array (all berries)' do catalog = compile_to_catalog(<<-MANIFEST) $a = ['strawberry','blueberry','orange'] $a.filter |$x|{ $x =~ /berry$/}.each |$v|{ file { "/file_$v": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_strawberry]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_blueberry]").with_parameter(:ensure, 'present') end it 'should filter on enumerable type (Integer)' do catalog = compile_to_catalog(<<-MANIFEST) $a = Integer[1,10] $a.filter |$x|{ $x % 3 == 0}.each |$v|{ file { "/file_$v": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_3]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_9]").with_parameter(:ensure, 'present') end it 'should filter on enumerable type (Integer) using two args index/value' do catalog = compile_to_catalog(<<-MANIFEST) $a = Integer[10,18] $a.filter |$i, $x|{ $i % 3 == 0}.each |$v|{ file { "/file_$v": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_10]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_13]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_16]").with_parameter(:ensure, 'present') end it 'should produce an array when acting on an array' do catalog = compile_to_catalog(<<-MANIFEST) $a = ['strawberry','blueberry','orange'] $b = $a.filter |$x|{ $x =~ /berry$/} file { "/file_${b[0]}": ensure => present } file { "/file_${b[1]}": ensure => present } MANIFEST expect(catalog).to have_resource("File[/file_strawberry]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_blueberry]").with_parameter(:ensure, 'present') end it 'can filter array using index and value' do catalog = compile_to_catalog(<<-MANIFEST) $a = ['strawberry','blueberry','orange'] $b = $a.filter |$index, $x|{ $index == 0 or $index ==2} file { "/file_${b[0]}": ensure => present } file { "/file_${b[1]}": ensure => present } MANIFEST expect(catalog).to have_resource("File[/file_strawberry]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_orange]").with_parameter(:ensure, 'present') end it 'can filter array using index and value (using captures-rest)' do catalog = compile_to_catalog(<<-MANIFEST) $a = ['strawberry','blueberry','orange'] $b = $a.filter |*$ix|{ $ix[0] == 0 or $ix[0] ==2} file { "/file_${b[0]}": ensure => present } file { "/file_${b[1]}": ensure => present } MANIFEST expect(catalog).to have_resource("File[/file_strawberry]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_orange]").with_parameter(:ensure, 'present') end it 'filters on a hash (all berries) by key' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'strawberry'=>'red','blueberry'=>'blue','orange'=>'orange'} $a.filter |$x|{ $x[0] =~ /berry$/}.each |$v|{ file { "/file_${v[0]}": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_strawberry]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_blueberry]").with_parameter(:ensure, 'present') end it 'should produce a hash when acting on a hash' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'strawberry'=>'red','blueberry'=>'blue','orange'=>'orange'} $b = $a.filter |$x|{ $x[0] =~ /berry$/} file { "/file_${b['strawberry']}": ensure => present } file { "/file_${b['blueberry']}": ensure => present } file { "/file_${b['orange']}": ensure => present } MANIFEST expect(catalog).to have_resource("File[/file_red]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_blue]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_]").with_parameter(:ensure, 'present') end it 'filters on a hash (all berries) by value' do catalog = compile_to_catalog(<<-MANIFEST) $a = {'strawb'=>'red berry','blueb'=>'blue berry','orange'=>'orange fruit'} $a.filter |$x|{ $x[1] =~ /berry$/}.each |$v|{ file { "/file_${v[0]}": ensure => present } } MANIFEST expect(catalog).to have_resource("File[/file_strawb]").with_parameter(:ensure, 'present') expect(catalog).to have_resource("File[/file_blueb]").with_parameter(:ensure, 'present') end it 'filters on an array will not include elements for which the block returns truthy but not true' do catalog = compile_to_catalog(<<-MANIFEST) $r = [1, 2, 3].filter |$v| { $v } == [] notify { "eval_${$r}": } MANIFEST expect(catalog).to have_resource('Notify[eval_true]') end it 'filters on a hash will not include elements for which the block returns truthy but not true' do catalog = compile_to_catalog(<<-MANIFEST) $r = {a => 1, b => 2, c => 3}.filter |$k, $v| { $v } == {} notify { "eval_${$r}": } MANIFEST expect(catalog).to have_resource('Notify[eval_true]') end it_should_behave_like 'all iterative functions argument checks', 'filter' it_should_behave_like 'all iterative functions hash handling', 'filter' end puppet-5.5.10/spec/unit/functions/hiera_spec.rb0000644005276200011600000003747313417161722021401 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/compiler' require 'puppet/pops' describe 'when calling' do include PuppetSpec::Compiler include PuppetSpec::Files let(:global_dir) { tmpdir('global') } let(:env_config) { {} } let(:hiera_yaml) { <<-YAML.unindent } --- :backends: - yaml - custom :yaml: :datadir: #{global_dir}/hieradata :hierarchy: - first - second YAML let(:ruby_stuff_files) do { 'hiera' => { 'backend' => { 'custom_backend.rb' => <<-RUBY.unindent class Hiera::Backend::Custom_backend def initialize(cache = nil) Hiera.debug('Custom_backend starting') end def lookup(key, scope, order_override, resolution_type, context) case key when 'datasources' Hiera::Backend.datasources(scope, order_override) { |source| source } when 'resolution_type' if resolution_type == :hash { key => resolution_type.to_s } elsif resolution_type == :array [ key, resolution_type.to_s ] else "resolution_type=\#{resolution_type}" end else throw :no_such_key end end end RUBY } } } end let(:hieradata_files) do { 'first.yaml' => <<-YAML.unindent, --- a: first a class_name: "-- %{calling_class} --" class_path: "-- %{calling_class_path} --" module: "-- %{calling_module} --" mod_name: "-- %{module_name} --" database_user: name: postgres uid: 500 gid: 500 groups: db: 520 b: b1: first b1 b2: first b2 fbb: - mod::foo - mod::bar - mod::baz empty_array: [] nested_array: first: - 10 - 11 second: - 21 - 22 dotted.key: a: dotted.key a b: dotted.key b dotted.array: - a - b YAML 'second.yaml' => <<-YAML.unindent, --- a: second a b: b1: second b1 b3: second b3 YAML 'the_override.yaml' => <<-YAML.unindent --- key: foo_result YAML } end let(:environment_files) do { 'test' => { 'modules' => { 'mod' => { 'manifests' => { 'foo.pp' => <<-PUPPET.unindent, class mod::foo { notice(hiera('class_name')) notice(hiera('class_path')) notice(hiera('module')) notice(hiera('mod_name')) } PUPPET 'bar.pp' => <<-PUPPET.unindent, class mod::bar {} PUPPET 'baz.pp' => <<-PUPPET.unindent class mod::baz {} PUPPET }, 'hiera.yaml' => <<-YAML.unindent, --- version: 5 YAML 'data' => { 'common.yaml' => <<-YAML.unindent mod::c: mod::c (from module) YAML } } } }.merge(env_config) } end let(:global_files) do { 'hiera.yaml' => hiera_yaml, 'ruby_stuff' => ruby_stuff_files, 'hieradata' => hieradata_files, 'environments' => environment_files } end let(:logs) { [] } let(:warnings) { logs.select { |log| log.level == :warning }.map { |log| log.message } } let(:env_dir) { File.join(global_dir, 'environments') } let(:env) { Puppet::Node::Environment.create(:test, [File.join(env_dir, 'test', 'modules')]) } let(:environments) { Puppet::Environments::Directories.new(env_dir, []) } let(:node) { Puppet::Node.new('test_hiera', :environment => env) } let(:compiler) { Puppet::Parser::Compiler.new(node) } let(:the_func) { Puppet.lookup(:loaders).puppet_system_loader.load(:function, 'hiera') } before(:each) do Puppet.settings[:codedir] = global_dir Puppet.settings[:hiera_config] = File.join(global_dir, 'hiera.yaml') end around(:each) do |example| # Faking the load path to enable 'require' to load from 'ruby_stuff'. It removes the need for a static fixture # for the custom backend dir_contained_in(global_dir, global_files) $LOAD_PATH.unshift(File.join(global_dir, 'ruby_stuff')) begin Puppet.override(:environments => environments, :current_environment => env) do example.run end ensure Hiera::Backend.send(:remove_const, :Custom_backend) if Hiera::Backend.const_defined?(:Custom_backend) $LOAD_PATH.shift end end def with_scope(code = 'undef') result = nil Puppet[:code] = 'undef' Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do compiler.compile do |catalog| result = yield(compiler.topscope) catalog end end result end def func(*args, &block) with_scope { |scope| the_func.call(scope, *args, &block) } end context 'hiera' do it 'should require a key argument' do expect { func([]) }.to raise_error(ArgumentError) end it 'should raise a useful error when nil is returned' do expect { func('badkey') }.to raise_error(Puppet::DataBinding::LookupError, /did not find a value for the name 'badkey'/) end it 'should use the "first" merge strategy' do expect(func('a')).to eql('first a') end it 'should allow lookup with quoted dotted key' do expect(func("'dotted.key'")).to eql({'a' => 'dotted.key a', 'b' => 'dotted.key b'}) end it 'should allow lookup with dotted key' do expect(func('database_user.groups.db')).to eql(520) end it 'should not find data in module' do expect(func('mod::c', 'default mod::c')).to eql('default mod::c') end it 'should propagate optional override' do ovr = 'the_override' expect(func('key', nil, ovr)).to eql('foo_result') end it 'backend data sources, including optional overrides, are propagated to custom backend' do expect(func('datasources', nil, 'the_override')).to eql(['the_override', 'first', 'second']) end it 'a hiera v3 scope is used' do expect(eval_and_collect_notices(<<-PUPPET, node)).to eql(['-- testing --', '-- mod::foo --', '-- mod/foo --', '-- mod --', '-- mod --']) class testing () { notice(hiera('class_name')) } include testing include mod::foo PUPPET end it 'should return default value nil when key is not found' do expect(func('foo', nil)).to be_nil end it "should return default value '' when key is not found" do expect(func('foo', '')).to eq('') end it 'should use default block' do expect(func('foo') { |k| "default for key '#{k}'" }).to eql("default for key 'foo'") end it 'should propagate optional override when combined with default block' do ovr = 'the_override' with_scope do |scope| expect(the_func.call(scope, 'key', ovr) { |k| "default for key '#{k}'" }).to eql('foo_result') expect(the_func.call(scope, 'foo.bar', ovr) { |k| "default for key '#{k}'" }).to eql("default for key 'foo.bar'") end end it 'should log deprecation errors' do func('a') expect(warnings).to include(/The function 'hiera' is deprecated in favor of using 'lookup'. See https:/) end context 'with environment with configured data provider' do let(:env_config) { { 'hiera.yaml' => <<-YAML.unindent, --- version: 5 YAML 'data' => { 'common.yaml' => <<-YAML.unindent --- a: a (from environment) e: e (from environment) YAML } } } it 'should find data globally' do expect(func('a')).to eql('first a') end it 'should find data in the environment' do expect(func('e')).to eql('e (from environment)') end it 'should find data in module' do expect(func('mod::c')).to eql('mod::c (from module)') end end it 'should not be disabled by data_binding_terminus setting' do Puppet[:data_binding_terminus] = 'none' expect(func('a')).to eql('first a') end end context 'hiera_array' do let(:the_func) { Puppet.lookup(:loaders).puppet_system_loader.load(:function, 'hiera_array') } it 'should require a key argument' do expect { func([]) }.to raise_error(ArgumentError) end it 'should raise a useful error when nil is returned' do expect { func('badkey') }.to raise_error(Puppet::DataBinding::LookupError, /did not find a value for the name 'badkey'/) end it 'should log deprecation errors' do func('fbb') expect(warnings).to include(/The function 'hiera_array' is deprecated in favor of using 'lookup'/) end it 'should use the array resolution_type' do expect(func('fbb', {'fbb' => 'foo_result'})).to eql(%w[mod::foo mod::bar mod::baz]) end it 'should allow lookup with quoted dotted key' do expect(func("'dotted.array'")).to eql(['a', 'b']) end it 'should fail lookup with dotted key' do expect{ func('nested_array.0.first') }.to raise_error(/Resolution type :array is illegal when accessing values using dotted keys. Offending key was 'nested_array.0.first'/) end it 'should use default block' do expect(func('foo') { |k| ['key', k] }).to eql(%w[key foo]) end end context 'hiera_hash' do let(:the_func) { Puppet.lookup(:loaders).puppet_system_loader.load(:function, 'hiera_hash') } it 'should require a key argument' do expect { func([]) }.to raise_error(ArgumentError) end it 'should raise a useful error when nil is returned' do expect { func('badkey') }.to raise_error(Puppet::DataBinding::LookupError, /did not find a value for the name 'badkey'/) end it 'should use the hash resolution_type' do expect(func('b', {'b' => 'foo_result'})).to eql({ 'b1' => 'first b1', 'b2' => 'first b2', 'b3' => 'second b3'}) end it 'should lookup and return a hash' do expect(func('database_user')).to eql({ 'name' => 'postgres', 'uid' => 500, 'gid' => 500, 'groups' => { 'db' => 520 }}) end it 'should allow lookup with quoted dotted key' do expect(func("'dotted.key'")).to eql({'a' => 'dotted.key a', 'b' => 'dotted.key b'}) end it 'should fail lookup with dotted key' do expect{ func('database_user.groups') }.to raise_error(/Resolution type :hash is illegal when accessing values using dotted keys. Offending key was 'database_user.groups'/) end it 'should log deprecation errors' do func('b') expect(warnings).to include(/The function 'hiera_hash' is deprecated in favor of using 'lookup'. See https:/) end it 'should use default block' do expect(func('foo') { |k| {'key' => k} }).to eql({'key' => 'foo'}) end end context 'hiera_include' do let(:the_func) { Puppet.lookup(:loaders).puppet_system_loader.load(:function, 'hiera_include') } it 'should require a key argument' do expect { func([]) }.to raise_error(ArgumentError) end it 'should raise a useful error when nil is returned' do expect { func('badkey') }.to raise_error(Puppet::DataBinding::LookupError, /did not find a value for the name 'badkey'/) end it 'should use the array resolution_type to include classes' do expect(func('fbb').map { |c| c.class_name }).to eql(%w[mod::foo mod::bar mod::baz]) end it 'should log deprecation errors' do func('fbb') expect(warnings).to include(/The function 'hiera_include' is deprecated in favor of using 'lookup'. See https:/) end it 'should not raise an error if the resulting hiera lookup returns an empty array' do expect { func('empty_array') }.to_not raise_error end it 'should use default block array to include classes' do expect(func('foo') { |k| ['mod::bar', "mod::#{k}"] }.map { |c| c.class_name }).to eql(%w[mod::bar mod::foo]) end end context 'with custom backend and merge_behavior declared in hiera.yaml' do let(:merge_behavior) { 'deeper' } let(:hiera_yaml) do <<-YAML.unindent --- :backends: - yaml - custom :yaml: :datadir: #{global_dir}/hieradata :hierarchy: - common - other :merge_behavior: #{merge_behavior} :deep_merge_options: :unpack_arrays: ',' YAML end let(:global_files) do { 'hiera.yaml' => hiera_yaml, 'hieradata' => { 'common.yaml' => <<-YAML.unindent, da: - da 0 - da 1 dm: dm1: dm11: value of dm11 (from common) dm12: value of dm12 (from common) dm2: dm21: value of dm21 (from common) hash: array: - x1,x2 array: - x1,x2 YAML 'other.yaml' => <<-YAML.unindent, da: - da 2,da 3 dm: dm1: dm11: value of dm11 (from other) dm13: value of dm13 (from other) dm3: dm31: value of dm31 (from other) hash: array: - x3 - x4 array: - x3 - x4 YAML }, 'ruby_stuff' => ruby_stuff_files } end context 'hiera_hash' do let(:the_func) { Puppet.lookup(:loaders).puppet_system_loader.load(:function, 'hiera_hash') } context "using 'deeper'" do it 'declared merge_behavior is honored' do expect(func('dm')).to eql({ 'dm1' => { 'dm11' => 'value of dm11 (from common)', 'dm12' => 'value of dm12 (from common)', 'dm13' => 'value of dm13 (from other)' }, 'dm2' => { 'dm21' => 'value of dm21 (from common)' }, 'dm3' => { 'dm31' => 'value of dm31 (from other)' } }) end it "merge behavior is propagated to a custom backend as 'hash'" do expect(func('resolution_type')).to eql({ 'resolution_type' => 'hash' }) end it 'fails on attempts to merge an array' do expect {func('da')}.to raise_error(/expects a Hash value/) end it 'honors option :unpack_arrays: (unsupported by puppet)' do expect(func('hash')).to eql({'array' => %w(x3 x4 x1 x2)}) end end context "using 'deep'" do let(:merge_behavior) { 'deep' } it 'honors option :unpack_arrays: (unsupported by puppet)' do expect(func('hash')).to eql({'array' => %w(x1 x2 x3 x4)}) end end end context 'hiera_array' do let(:the_func) { Puppet.lookup(:loaders).puppet_system_loader.load(:function, 'hiera_array') } it 'declared merge_behavior is ignored' do expect(func('da')).to eql(['da 0', 'da 1', 'da 2,da 3']) end it "merge behavior is propagated to a custom backend as 'array'" do expect(func('resolution_type')).to eql(['resolution_type', 'array']) end end context 'hiera' do let(:the_func) { Puppet.lookup(:loaders).puppet_system_loader.load(:function, 'hiera') } it 'declared merge_behavior is ignored' do expect(func('da')).to eql(['da 0', 'da 1']) end it "no merge behavior is propagated to a custom backend" do expect(func('resolution_type')).to eql('resolution_type=') end end end end puppet-5.5.10/spec/unit/functions/logging_spec.rb0000644005276200011600000000303413417161722021721 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/compiler' describe 'the log function' do include PuppetSpec::Compiler def collect_logs(code) Puppet[:code] = code node = Puppet::Node.new('logtest') compiler = Puppet::Parser::Compiler.new(node) node.environment.check_for_reparse logs = [] Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do compiler.compile end logs end def expect_log(code, log_level, message) logs = collect_logs(code) expect(logs.size).to eql(1) expect(logs[0].level).to eql(log_level) expect(logs[0].message).to eql(message) end before(:each) do Puppet[:log_level] = 'debug' end Puppet::Util::Log.levels.each do |level| context "for log level '#{level}'" do it 'can be called' do expect_log("#{level.to_s}('yay')", level, 'yay') end it 'joins multiple arguments using space' do # Not using the evaluator would result in yay {"a"=>"b", "c"=>"d"} expect_log("#{level.to_s}('a', 'b', 3)", level, 'a b 3') end it 'uses the evaluator to format output' do # Not using the evaluator would result in yay {"a"=>"b", "c"=>"d"} expect_log("#{level.to_s}('yay', {a => b, c => d})", level, 'yay {a => b, c => d}') end it 'returns undef value' do logs = collect_logs("notice(type(#{level.to_s}('yay')))") expect(logs.size).to eql(2) expect(logs[1].level).to eql(:notice) expect(logs[1].message).to eql('Undef') end end end end puppet-5.5.10/spec/unit/functions/lookup_spec.rb0000644005276200011600000036656013417161722021624 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/compiler' require 'puppet_spec/files' require 'puppet/pops' require 'deep_merge/core' describe "The lookup function" do include PuppetSpec::Compiler include PuppetSpec::Files let(:env_name) { 'spec' } let(:code_dir_files) { {} } let(:code_dir) { tmpdir('code') } let(:ruby_dir) { tmpdir('ruby') } let(:env_modules) { {} } let(:env_hiera_yaml) do <<-YAML.unindent --- version: 5 hierarchy: - name: "Common" data_hash: yaml_data path: "common.yaml" YAML end let(:env_data) { {} } let(:environment_files) do { env_name => { 'modules' => env_modules, 'hiera.yaml' => env_hiera_yaml, 'data' => env_data } } end let(:ruby_dir_files) { {} } let(:logs) { [] } let(:scope_additions ) { {} } let(:notices) { logs.select { |log| log.level == :notice }.map { |log| log.message } } let(:warnings) { logs.select { |log| log.level == :warning }.map { |log| log.message } } let(:debugs) { logs.select { |log| log.level == :debug }.map { |log| log.message } } let(:env) { Puppet::Node::Environment.create(env_name.to_sym, [File.join(populated_env_dir, env_name, 'modules')]) } let(:environments) { Puppet::Environments::Directories.new(populated_env_dir, []) } let(:node) { Puppet::Node.new('test_lookup', :environment => env) } let(:compiler) { Puppet::Parser::Compiler.new(node) } let(:lookup_func) { Puppet.lookup(:loaders).puppet_system_loader.load(:function, 'lookup') } let(:invocation_with_explain) { Puppet::Pops::Lookup::Invocation.new(compiler.topscope, {}, {}, true) } let(:explanation) { invocation_with_explain.explainer.explain } let(:populated_code_dir) do dir_contained_in(code_dir, code_dir_files) code_dir end let(:populated_ruby_dir) do dir_contained_in(ruby_dir, ruby_dir_files) ruby_dir end let(:env_dir) do d = File.join(populated_code_dir, 'environments') Dir.mkdir(d) d end let(:populated_env_dir) do dir_contained_in(env_dir, environment_files) env_dir end before(:each) do Puppet.settings[:codedir] = code_dir Puppet.push_context(:environments => environments, :current_environment => env) end after(:each) do Puppet.pop_context if Object.const_defined?(:Hiera) Hiera.send(:remove_instance_variable, :@config) if Hiera.instance_variable_defined?(:@config) Hiera.send(:remove_instance_variable, :@logger) if Hiera.instance_variable_defined?(:@logger) if Hiera.const_defined?(:Config) Hiera::Config.send(:remove_instance_variable, :@config) if Hiera::Config.instance_variable_defined?(:@config) end if Hiera.const_defined?(:Backend) && Hiera::Backend.respond_to?(:clear!) Hiera::Backend.clear! end end end def collect_notices(code, explain = false, &block) Puppet[:code] = code Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do scope = compiler.topscope scope['domain'] = 'example.com' scope_additions.each_pair { |k, v| scope[k] = v } if explain begin invocation_with_explain.lookup('dummy', nil) do if block_given? compiler.compile { |catalog| block.call(compiler.topscope); catalog } else compiler.compile end end rescue RuntimeError => e invocation_with_explain.report_text { e.message } end else if block_given? compiler.compile { |catalog| block.call(compiler.topscope); catalog } else compiler.compile end end end nil end def lookup(key, options = {}, explain = false) nc_opts = options.empty? ? '' : ", #{Puppet::Pops::Types::TypeFormatter.string(options)}" keys = key.is_a?(Array) ? key : [key] collect_notices(keys.map { |k| "notice(String(lookup('#{k}'#{nc_opts}), '%p'))" }.join("\n"), explain) if explain explanation else result = notices.map { |n| Puppet::Pops::Types::TypeParser.singleton.parse_literal(n) } key.is_a?(Array) ? result : result[0] end end def explain(key, options = {}) lookup(key, options, true)[1] explanation end context 'with faulty hiera.yaml configuration' do context 'in global layer' do let(:global_data) do { 'common.yaml' => <<-YAML.unindent a: value a (from global) YAML } end let(:code_dir_files) do { 'hiera.yaml' => hiera_yaml, 'data' => global_data } end before(:each) do # Need to set here since spec_helper defines these settings in its "before each" Puppet.settings[:codedir] = populated_code_dir Puppet.settings[:hiera_config] = File.join(code_dir, 'hiera.yaml') end context 'using a not yet supported hiera version' do let(:hiera_yaml) { <<-YAML.unindent } version: 6 YAML it 'fails and reports error' do expect { lookup('a') }.to raise_error("This runtime does not support hiera.yaml version 6 (file: #{code_dir}/hiera.yaml)") end end context 'with multiply defined backend using hiera version 3' do let(:hiera_yaml) { <<-YAML.unindent } :version: 3 :backends: - yaml - json - yaml YAML it 'fails and reports error' do expect { lookup('a') }.to raise_error( "Backend 'yaml' is defined more than once. First defined at (line: 3) (file: #{code_dir}/hiera.yaml, line: 5)") end end context 'using hiera version 4' do let(:hiera_yaml) { <<-YAML.unindent } version: 4 YAML it 'fails and reports error' do expect { lookup('a') }.to raise_error( "hiera.yaml version 4 cannot be used in the global layer (file: #{code_dir}/hiera.yaml)") end end context 'using hiera version 5' do context 'with multiply defined hierarchy' do let(:hiera_yaml) { <<-YAML.unindent } version: 5 hierarchy: - name: Common path: common.yaml - name: Other path: other.yaml - name: Common path: common.yaml YAML it 'fails and reports error' do expect { lookup('a') }.to raise_error( "Hierarchy name 'Common' defined more than once. First defined at (line: 3) (file: #{code_dir}/hiera.yaml, line: 7)") end end context 'with hiera3_backend that is provided as data_hash function' do let(:hiera_yaml) { <<-YAML.unindent } version: 5 hierarchy: - name: Common hiera3_backend: hocon path: common.conf YAML it 'fails and reports error' do expect { lookup('a') }.to raise_error( "Use \"data_hash: hocon_data\" instead of \"hiera3_backend: hocon\" (file: #{code_dir}/hiera.yaml, line: 4)") end end context 'with no data provider function defined' do let(:hiera_yaml) { <<-YAML.unindent } version: 5 defaults: datadir: data hierarchy: - name: Common path: common.txt YAML it 'fails and reports error' do expect { lookup('a') }.to raise_error( "One of data_hash, lookup_key, data_dig, or hiera3_backend must be defined in hierarchy 'Common' (file: #{code_dir}/hiera.yaml)") end end context 'with multiple data providers in defaults' do let(:hiera_yaml) { <<-YAML.unindent } version: 5 defaults: data_hash: yaml_data lookup_key: eyaml_lookup_key datadir: data hierarchy: - name: Common path: common.txt YAML it 'fails and reports error' do expect { lookup('a') }.to raise_error( "Only one of data_hash, lookup_key, data_dig, or hiera3_backend can be defined in defaults (file: #{code_dir}/hiera.yaml)") end end context 'with non existing data provider function' do let(:hiera_yaml) { <<-YAML.unindent } version: 5 hierarchy: - name: Common data_hash: nonesuch_txt_data path: common.yaml YAML it 'fails and reports error' do Puppet[:strict] = :error expect { lookup('a') }.to raise_error( "Unable to find 'data_hash' function named 'nonesuch_txt_data' (file: #{code_dir}/hiera.yaml)") end end context 'with a declared default_hierarchy' do let(:hiera_yaml) { <<-YAML.unindent } version: 5 hierarchy: - name: Common path: common.yaml default_hierarchy: - name: Defaults path: defaults.yaml YAML it 'fails and reports error' do Puppet[:strict] = :error expect { lookup('a') }.to raise_error( "'default_hierarchy' is only allowed in the module layer (file: #{code_dir}/hiera.yaml, line: 5)") end end context 'with missing variables' do let(:scope_additions) { { 'fqdn' => 'test.example.com' } } let(:hiera_yaml) { <<-YAML.unindent } version: 5 hierarchy: - name: Common # don't report this line %{::nonesuch} path: "%{::fqdn}/%{::nonesuch}/data.yaml" YAML it 'fails and reports errors when strict == error' do Puppet[:strict] = :error expect { lookup('a') }.to raise_error("Undefined variable '::nonesuch' (file: #{code_dir}/hiera.yaml, line: 4)") end end context 'using interpolation functions' do let(:hiera_yaml) { <<-YAML.unindent } version: 5 hierarchy: - name: Common # don't report this line %{::nonesuch} path: "%{lookup('fqdn')}/data.yaml" YAML it 'fails and reports errors when strict == error' do Puppet[:strict] = :error expect { lookup('a') }.to raise_error("Interpolation using method syntax is not allowed in this context (file: #{code_dir}/hiera.yaml)") end end end end context 'in environment layer' do context 'using hiera version 4' do context 'with an unknown backend' do let(:env_hiera_yaml) { <<-YAML.unindent } version: 4 hierarchy: - name: Common backend: nonesuch path: common.yaml YAML it 'fails and reports error' do expect { lookup('a') }.to raise_error( "No data provider is registered for backend 'nonesuch' (file: #{env_dir}/spec/hiera.yaml, line: 4)") end end context 'with multiply defined hierarchy' do let(:env_hiera_yaml) { <<-YAML.unindent } version: 4 hierarchy: - name: Common backend: yaml path: common.yaml - name: Other backend: yaml path: other.yaml - name: Common backend: yaml path: common.yaml YAML it 'fails and reports error' do expect { lookup('a') }.to raise_error( "Hierarchy name 'Common' defined more than once. First defined at (line: 3) (file: #{env_dir}/spec/hiera.yaml, line: 9)") end end end context 'using hiera version 5' do context 'with a hiera3_backend declaration' do let(:env_hiera_yaml) { <<-YAML.unindent } version: 5 hierarchy: - name: Common hiera3_backend: something YAML it 'fails and reports error' do expect { lookup('a') }.to raise_error( "'hiera3_backend' is only allowed in the global layer (file: #{env_dir}/spec/hiera.yaml, line: 4)") end end context 'with a declared default_hierarchy' do let(:env_hiera_yaml) { <<-YAML.unindent } version: 5 hierarchy: - name: Common path: common.yaml default_hierarchy: - name: Defaults path: defaults.yaml YAML it 'fails and reports error' do Puppet[:strict] = :error expect { lookup('a') }.to raise_error( "'default_hierarchy' is only allowed in the module layer (file: #{env_dir}/spec/hiera.yaml, line: 5)") end end end end end context 'with an environment' do let(:env_data) do { 'common.yaml' => <<-YAML.unindent --- a: value a (from environment) c: c_b: value c_b (from environment) mod_a::a: value mod_a::a (from environment) mod_a::hash_a: a: value mod_a::hash_a.a (from environment) mod_a::hash_b: a: value mod_a::hash_b.a (from environment) hash_b: hash_ba: bab: value hash_b.hash_ba.bab (from environment) hash_c: hash_ca: caa: value hash_c.hash_ca.caa (from environment) lookup_options: mod_a::hash_b: merge: hash hash_c: merge: hash YAML } end it 'finds data in the environment' do expect(lookup('a')).to eql('value a (from environment)') end context 'with log-level debug' do before(:each) { Puppet[:log_level] = 'debug' } it 'does not report a regular lookup as APL' do expect(lookup('a')).to eql('value a (from environment)') expect(debugs.count { |dbg| dbg =~ /\A\s*Automatic Parameter Lookup of/ }).to eql(0) end it 'reports regular lookup as lookup' do expect(lookup('a')).to eql('value a (from environment)') expect(debugs.count { |dbg| dbg =~ /\A\s*Lookup of/ }).to eql(1) end it 'does not report APL as lookup' do collect_notices("class mod_a($a) { notice($a) }; include mod_a") expect(debugs.count { |dbg| dbg =~ /\A\s*Lookup of/ }).to eql(0) end it 'reports APL as APL' do collect_notices("class mod_a($a) { notice($a) }; include mod_a") expect(debugs.count { |dbg| dbg =~ /\A\s*Automatic Parameter Lookup of/ }).to eql(1) end end context 'that has no lookup configured' do let(:environment_files) do { env_name => { 'data' => env_data } } end it 'does not find data in the environment' do expect { lookup('a') }.to raise_error(Puppet::DataBinding::LookupError, /did not find a value for the name 'a'/) end context "but an environment.conf with 'environment_data_provider=hiera'" do let(:environment_files) do { env_name => { 'environment.conf' => "environment_data_provider=hiera\n", 'data' => env_data } } end it 'finds data in the environment and reports deprecation warning for environment.conf' do expect(lookup('a')).to eql('value a (from environment)') expect(warnings).to include(/Defining environment_data_provider='hiera' in environment.conf is deprecated. A 'hiera.yaml' file should be used instead/) end context 'and a hiera.yaml file' do let(:env_hiera_yaml) do <<-YAML.unindent --- version: 4 hierarchy: - name: common backend: yaml YAML end let(:environment_files) do { env_name => { 'hiera.yaml' => env_hiera_yaml, 'environment.conf' => "environment_data_provider=hiera\n", 'data' => env_data } } end it 'finds data in the environment and reports deprecation warnings for both environment.conf and hiera.yaml' do expect(lookup('a')).to eql('value a (from environment)') expect(warnings).to include(/Defining environment_data_provider='hiera' in environment.conf is deprecated/) expect(warnings).to include(/Use of 'hiera.yaml' version 4 is deprecated. It should be converted to version 5/) end end end context "but an environment.conf with 'environment_data_provider=function'" do let(:environment_files) do { env_name => { 'environment.conf' => "environment_data_provider=function\n", 'functions' => { 'environment' => { 'data.pp' => <<-PUPPET.unindent } function environment::data() { { 'a' => 'value a' } } PUPPET } } } end it 'finds data in the environment and reports deprecation warning for environment.conf' do expect(lookup('a')).to eql('value a') expect(warnings).to include(/Defining environment_data_provider='function' in environment.conf is deprecated. A 'hiera.yaml' file should be used instead/) expect(warnings).to include(/Using of legacy data provider function 'environment::data'. Please convert to a 'data_hash' function/) end end end context 'that has interpolated paths configured' do let(:env_hiera_yaml) do <<-YAML.unindent --- version: 5 hierarchy: - name: "Varying" data_hash: yaml_data path: "#{data_path}" YAML end let(:environment_files) do { env_name => { 'hiera.yaml' => env_hiera_yaml, 'modules' => {}, 'data' => { 'x.yaml' => <<-YAML.unindent, y: value y from x YAML 'x_d.yaml' => <<-YAML.unindent, y: value y from x_d YAML 'x_e.yaml' => <<-YAML.unindent, y: value y from x_e YAML } } } end context 'using local variable reference' do let(:data_path) { 'x%{var.sub}.yaml' } it 'reloads the configuration if interpolated values change' do Puppet[:log_level] = 'debug' collect_notices("notice('success')") do |scope| expect(lookup_func.call(scope, 'y')).to eql('value y from x') scope['var'] = { 'sub' => '_d' } expect(lookup_func.call(scope, 'y')).to eql('value y from x_d') nested_scope = scope.compiler.newscope(scope) nested_scope['var'] = { 'sub' => '_e' } expect(lookup_func.call(nested_scope, 'y')).to eql('value y from x_e') end expect(notices).to eql(['success']) expect(debugs.any? { |m| m =~ /Hiera configuration recreated due to change of scope variables used in interpolation expressions/ }).to be_truthy end it 'does not include the lookups performed during stability check in explain output' do Puppet[:log_level] = 'debug' collect_notices("notice('success')") do |scope| var = { 'sub' => '_d' } scope['var'] = var expect(lookup_func.call(scope, 'y')).to eql('value y from x_d') # Second call triggers the check expect(lookup_func.call(scope, 'y')).to eql('value y from x_d') end expect(notices).to eql(['success']) expect(debugs.any? { |m| m =~ /Sub key: "sub"/ }).to be_falsey end end context 'using global variable reference' do let(:data_path) { 'x%{::var.sub}.yaml' } it 'reloads the configuration if interpolated that was previously undefined, gets defined' do Puppet[:log_level] = 'debug' collect_notices("notice('success')") do |scope| expect(lookup_func.call(scope, 'y')).to eql('value y from x') scope['var'] = { 'sub' => '_d' } expect(lookup_func.call(scope, 'y')).to eql('value y from x_d') end expect(notices).to eql(['success']) expect(debugs.any? { |m| m =~ /Hiera configuration recreated due to change of scope variables used in interpolation expressions/ }).to be_truthy end it 'does not reload the configuration if value changes locally' do Puppet[:log_level] = 'debug' collect_notices("notice('success')") do |scope| scope['var'] = { 'sub' => '_d' } expect(lookup_func.call(scope, 'y')).to eql('value y from x_d') nested_scope = scope.compiler.newscope(scope) nested_scope['var'] = { 'sub' => '_e' } expect(lookup_func.call(nested_scope, 'y')).to eql('value y from x_d') end expect(notices).to eql(['success']) expect(debugs.any? { |m| m =~ /Hiera configuration recreated due to change of scope variables used in interpolation expressions/ }).to be_falsey end end end context 'that uses reserved' do let(:environment_files) do { env_name => { 'hiera.yaml' => hiera_yaml } } end context 'option' do let(:hiera_yaml) { <<-YAML.unindent } version: 5 hierarchy: - name: "Illegal" options: #{opt_spec} data_hash: yaml_data YAML context 'path' do let(:opt_spec) { 'path: data/foo.yaml' } it 'fails and reports the reserved option key' do expect { lookup('a') }.to raise_error do |e| expect(e.message).to match(/Option key 'path' used in hierarchy 'Illegal' is reserved by Puppet/) end end end context 'uri' do let(:opt_spec) { 'uri: file:///data/foo.yaml' } it 'fails and reports the reserved option key' do expect { lookup('a') }.to raise_error do |e| expect(e.message).to match(/Option key 'uri' used in hierarchy 'Illegal' is reserved by Puppet/) end end end end context 'default option' do let(:hiera_yaml) { <<-YAML.unindent } --- version: 5 defaults: options: #{opt_spec} hierarchy: - name: "Illegal" data_hash: yaml_data YAML context 'path' do let(:opt_spec) { 'path: data/foo.yaml' } it 'fails and reports the reserved option key' do expect { lookup('a') }.to raise_error do |e| expect(e.message).to match(/Option key 'path' used in defaults is reserved by Puppet/) end end end context 'uri' do let(:opt_spec) { 'uri: file:///data/foo.yaml' } it 'fails and reports the reserved option key' do expect { lookup('a') }.to raise_error do |e| expect(e.message).to match(/Option key 'uri' used in defaults is reserved by Puppet/) end end end end end context 'with yaml data file' do let(:environment_files) do { env_name => { 'hiera.yaml' => <<-YAML.unindent, --- version: 5 YAML 'data' => { 'common.yaml' => common_yaml } } } end context 'that contains hash values with interpolated keys' do let(:common_yaml) do <<-YAML.unindent --- a: "%{key}": "the %{value}" b: "Detail in %{lookup('a.a_key')}" YAML end it 'interpolates both key and value"' do Puppet[:log_level] = 'debug' collect_notices("notice('success')") do |scope| expect(lookup_func.call(scope, 'a')).to eql({'' => 'the '}) scope['key'] = 'a_key' scope['value'] = 'interpolated value' expect(lookup_func.call(scope, 'a')).to eql({'a_key' => 'the interpolated value'}) end expect(notices).to eql(['success']) end it 'navigates to a value behind an interpolated key"' do Puppet[:log_level] = 'debug' collect_notices("notice('success')") do |scope| scope['key'] = 'a_key' scope['value'] = 'interpolated value' expect(lookup_func.call(scope, 'a.a_key')).to eql('the interpolated value') end expect(notices).to eql(['success']) end it 'navigates to a value behind an interpolated key using an interpolated value"' do Puppet[:log_level] = 'debug' collect_notices("notice('success')") do |scope| scope['key'] = 'a_key' scope['value'] = 'interpolated value' expect(lookup_func.call(scope, 'b')).to eql('Detail in the interpolated value') end expect(notices).to eql(['success']) end end context 'that is empty' do let(:common_yaml) { '' } it 'fails with a "did not find"' do expect { lookup('a') }.to raise_error do |e| expect(e.message).to match(/did not find a value for the name 'a'/) end end it 'logs a warning that the file does not contain a hash' do expect { lookup('a') }.to raise_error(Puppet::DataBinding::LookupError) expect(warnings).to include(/spec\/data\/common.yaml: file does not contain a valid yaml hash/) end end context 'that contains illegal yaml' do let(:common_yaml) { "@!#%**&:\n" } it 'fails lookup and that the key is not found' do expect { lookup('a') }.to raise_error do |e| expect(e.message).to match(/Unable to parse/) end end end context 'that contains a legal yaml that is not a hash' do let(:common_yaml) { "- A list\n- of things" } it 'fails with a "did not find"' do expect { lookup('a') }.to raise_error do |e| expect(e.message).to match(/did not find a value for the name 'a'/) end end it 'logs a warning that the file does not contain a hash' do expect { lookup('a') }.to raise_error(Puppet::DataBinding::LookupError) expect(warnings).to include(/spec\/data\/common.yaml: file does not contain a valid yaml hash/) end end context 'that contains a legal yaml hash with illegal types' do let(:common_yaml) do <<-YAML.unindent --- a: !ruby/object:Puppet::Graph::Key value: x YAML end it 'fails lookup and reports a type mismatch' do expect { lookup('a') }.to raise_error do |e| expect(e.message).to match(/key 'a'.*data_hash function 'yaml_data'.*using location.*wrong type, expects Puppet::LookupValue, got Runtime/) end end end context 'that contains illegal interpolations' do context 'in the form of an alias that is not the entire string' do let(:common_yaml) { <<-YAML.unindent } a: "%{alias('x')} and then some" x: value x YAML it 'fails lookup and reports a type mismatch' do expect { lookup('a') }.to raise_error("'alias' interpolation is only permitted if the expression is equal to the entire string") end end context 'in the form of an unknown function name' do let(:common_yaml) { <<-YAML.unindent } a: "%{what('x')}" x: value x YAML it 'fails lookup and reports a type mismatch' do expect { lookup('a') }.to raise_error("Unknown interpolation method 'what'") end end end context 'that contains an array with duplicates' do let(:common_yaml) { <<-YAML.unindent } a: - alpha - bravo - charlie - bravo YAML it 'retains the duplicates when using default merge strategy' do expect(lookup('a')).to eql(%w(alpha bravo charlie bravo)) end it 'does deduplification when using merge strategy "unique"' do expect(lookup('a', :merge => 'unique')).to eql(%w(alpha bravo charlie)) end end end context 'with lookup_options configured using patterns' do let(:mod_common) { <<-YAML.unindent mod::hash_a: aa: aaa: aaa (from module) ab: aba: aba (from module) mod::hash_b: ba: baa: baa (from module) bb: bba: bba (from module) lookup_options: '^mod::ha.*_a': merge: deep '^mod::ha.*_b': merge: deep YAML } let(:mod_base) do { 'hiera.yaml' => <<-YAML.unindent, version: 5 YAML 'data' => { 'common.yaml' => mod_common } } end let(:env_modules) do { 'mod' => mod_base } end let(:env_hiera_yaml) do <<-YAML.unindent --- version: 5 hierarchy: - name: X paths: - first.yaml - second.yaml YAML end let(:env_lookup_options) { <<-YAML.unindent } lookup_options: b: merge: hash '^[^b]$': merge: deep '^c': merge: first '^b': merge: first '^mod::ha.*_b': merge: hash YAML let(:env_data) do { 'first.yaml' => <<-YAML.unindent + env_lookup_options, a: aa: aaa: a.aa.aaa b: ba: baa: b.ba.baa bb: bba: b.bb.bba c: ca: caa: c.ca.caa mod::hash_a: aa: aab: aab (from environment) ab: aba: aba (from environment) abb: abb (from environment) mod::hash_b: ba: bab: bab (from environment) bc: bca: bca (from environment) sa: sa1: ['e', 'd', '--f'] YAML 'second.yaml' => <<-YAML.unindent, a: aa: aab: a.aa.aab b: ba: bab: b.ba.bab bb: bbb: b.bb.bbb c: ca: cab: c.ca.cab sa: sa1: ['b', 'a', 'f', 'c'] YAML } end it 'finds lookup_options that matches a pattern' do expect(lookup('a')).to eql({'aa' => { 'aaa' => 'a.aa.aaa', 'aab' => 'a.aa.aab' }}) end it 'gives a direct key match higher priority than a matching pattern' do expect(lookup('b')).to eql({'ba' => { 'baa' => 'b.ba.baa' }, 'bb' => { 'bba'=>'b.bb.bba' }}) end it 'uses the first matching pattern' do expect(lookup('c')).to eql({'ca' => { 'caa' => 'c.ca.caa', 'cab' => 'c.ca.cab' }}) end it 'uses lookup_option found by pattern from module' do expect(lookup('mod::hash_a')).to eql({ 'aa' => { 'aaa' => 'aaa (from module)', 'aab' => 'aab (from environment)' }, 'ab' => { 'aba' => 'aba (from environment)', 'abb' => 'abb (from environment)' } }) end it 'merges lookup_options found by pattern in environment and module (environment wins)' do expect(lookup('mod::hash_b')).to eql({ 'ba' => { 'bab' => 'bab (from environment)' }, 'bb' => { 'bba' => 'bba (from module)' }, 'bc' => { 'bca' => 'bca (from environment)' } }) end context 'and patterns in module are not limited to module keys' do let(:mod_common) { <<-YAML.unindent mod::hash_a: aa: aaa: aaa (from module) ab: aba: aba (from module) lookup_options: '^.*_a': merge: deep YAML } it 'fails with error' do expect { lookup('mod::a') }.to raise_error(Puppet::DataBinding::LookupError, /all lookup_options patterns must match a key starting with module name/) end end context 'and there are no lookup options that do not use patterns' do let(:env_lookup_options) { <<-YAML.unindent } lookup_options: '^[^b]$': merge: deep '^c': merge: first '^b': merge: first '^mod::ha.*_b': merge: hash YAML it 'finds lookup_options that matches a pattern' do expect(lookup('a')).to eql({'aa' => { 'aaa' => 'a.aa.aaa', 'aab' => 'a.aa.aab' }}) end end context 'and lookup options use a hash' do let(:env_lookup_options) { <<-YAML.unindent } lookup_options: 'sa': merge: strategy: deep knockout_prefix: -- sort_merged_arrays: true YAML it 'applies knockout_prefix and sort_merged_arrays' do expect(lookup('sa')).to eql({ 'sa1' => %w(a b c d e) }) end it 'overrides knockout_prefix and sort_merged_arrays with explicitly given values' do expect( lookup('sa', 'merge' => { 'strategy' => 'deep', 'knockout_prefix' => '##', 'sort_merged_arrays' => false })).to( eql({ 'sa1' => %w(b a f c e d --f) })) end end end context 'and an environment Hiera v5 configuration using globs' do let(:env_hiera_yaml) do <<-YAML.unindent --- version: 5 hierarchy: - name: Globs globs: - "globs/*.yaml" - "globs_%{domain}/*.yaml" YAML end let(:env_data) do { 'globs' => { 'a.yaml' => <<-YAML.unindent, glob_a: value glob_a YAML 'b.yaml' => <<-YAML.unindent glob_b: a: value glob_b.a b: value glob_b.b YAML }, 'globs_example.com' => { 'a.yaml' => <<-YAML.unindent, glob_c: value glob_a YAML 'b.yaml' => <<-YAML.unindent glob_d: a: value glob_d.a b: value glob_d.b YAML } } end it 'finds environment data using globs' do expect(lookup('glob_a')).to eql('value glob_a') expect(warnings).to be_empty end it 'finds environment data using interpolated globs' do expect(lookup('glob_d.a')).to eql('value glob_d.a') expect(warnings).to be_empty end end context 'and an environment Hiera v5 configuration using uris' do let(:env_hiera_yaml) do <<-YAML.unindent --- version: 5 hierarchy: - name: Uris uris: - "http://test.example.com" - "/some/arbitrary/path" - "urn:with:opaque:path" - "dothis%20-f%20bar" data_hash: mod::uri_test_func YAML end let(:env_modules) do { 'mod' => { 'lib' => { 'puppet' => { 'functions' => { 'mod' => { 'uri_test_func.rb' => <<-RUBY } } } } } Puppet::Functions.create_function(:'mod::uri_test_func') do dispatch :uri_test_func do param 'Hash', :options param 'Puppet::LookupContext', :context end def uri_test_func(options, context) { 'uri' => [ options['uri'] ] } end end RUBY } end it 'The uris are propagated in the options hash' do expect(lookup('uri', 'merge' => 'unique')).to eql( %w(http://test.example.com /some/arbitrary/path urn:with:opaque:path dothis%20-f%20bar)) expect(warnings).to be_empty end context 'and a uri uses bad syntax' do let(:env_hiera_yaml) do <<-YAML.unindent --- version: 5 hierarchy: - name: Uris uri: "dothis -f bar" data_hash: mod::uri_test_func YAML end it 'an attempt to lookup raises InvalidURIError' do expect{ lookup('uri', 'merge' => 'unique') }.to raise_error(/bad URI/) end end end context 'and an environment Hiera v5 configuration using mapped_paths' do let(:scope_additions) do { 'mapped' => { 'array_var' => ['a', 'b', 'c'], 'hash_var' => { 'x' => 'a', 'y' => 'b', 'z' => 'c' }, 'string_var' => 's' }, 'var' => 'global_var' # overridden by mapped path variable } end let(:env_hiera_yaml) do <<-YAML.unindent --- version: 5 hierarchy: - name: Mapped Paths mapped_paths: #{mapped_paths} - name: Global Path path: "%{var}.yaml" YAML end let(:environment_files) do { env_name => { 'hiera.yaml' => env_hiera_yaml, 'data' => env_data } } end context 'that originates from an array' do let (:mapped_paths) { '[mapped.array_var, var, "paths/%{var}.yaml"]' } let(:env_data) do { 'paths' => { 'a.yaml' => <<-YAML.unindent, path_a: value path_a path_h: a: value path_h.a c: value path_h.c YAML 'b.yaml' => <<-YAML.unindent, path_h: b: value path_h.b d: value path_h.d YAML 'd.yaml' => <<-YAML.unindent path_h: b: value path_h.b (from d.yaml) d: value path_h.d (from d.yaml) YAML }, 'global_var.yaml' => <<-YAML.unindent, path_h: e: value path_h.e YAML 'other_var.yaml' => <<-YAML.unindent path_h: e: value path_h.e (from other_var.yaml) YAML } end it 'finds environment data using mapped_paths' do expect(lookup('path_a')).to eql('value path_a') expect(warnings).to be_empty end it 'includes mapped path in explain output' do explanation = explain('path_h', 'merge' => 'deep') ['a', 'b', 'c'].each do |var| expect(explanation).to match(/^\s+Path "#{env_dir}\/spec\/data\/paths\/#{var}\.yaml"\n\s+Original path: "paths\/%\{var\}\.yaml"/) end expect(warnings).to be_empty end it 'performs merges between mapped paths and global path interpolated using same key' do expect(lookup('path_h', 'merge' => 'hash')).to eql( { 'a' => 'value path_h.a', 'b' => 'value path_h.b', 'c' => 'value path_h.c', 'd' => 'value path_h.d', 'e' => 'value path_h.e' }) expect(warnings).to be_empty end it 'keeps track of changes in key overridden by interpolated key' do Puppet[:log_level] = 'debug' collect_notices("notice('success')") do |scope| expect(lookup_func.call(scope, 'path_h', 'merge' => 'hash')).to eql( { 'a' => 'value path_h.a', 'b' => 'value path_h.b', 'c' => 'value path_h.c', 'd' => 'value path_h.d', 'e' => 'value path_h.e' }) scope.with_local_scope('var' => 'other_var') do expect(lookup_func.call(scope, 'path_h', 'merge' => 'hash')).to eql( { 'a' => 'value path_h.a', 'b' => 'value path_h.b', 'c' => 'value path_h.c', 'd' => 'value path_h.d', 'e' => 'value path_h.e (from other_var.yaml)' }) end end expect(notices).to eql(['success']) expect(debugs.any? { |m| m =~ /Hiera configuration recreated due to change of scope variables used in interpolation expressions/ }).to be_truthy end it 'keeps track of changes in elements of mapped key' do Puppet[:log_level] = 'debug' collect_notices("notice('success')") do |scope| expect(lookup_func.call(scope, 'path_h', 'merge' => 'hash')).to eql( { 'a' => 'value path_h.a', 'b' => 'value path_h.b', 'c' => 'value path_h.c', 'd' => 'value path_h.d', 'e' => 'value path_h.e' }) scope['mapped']['array_var'] = ['a', 'c', 'd'] expect(lookup_func.call(scope, 'path_h', 'merge' => 'hash')).to eql( { 'a' => 'value path_h.a', 'b' => 'value path_h.b (from d.yaml)', 'c' => 'value path_h.c', 'd' => 'value path_h.d (from d.yaml)', 'e' => 'value path_h.e' }) end expect(notices).to eql(['success']) expect(debugs.any? { |m| m =~ /Hiera configuration recreated due to change of scope variables used in interpolation expressions/ }).to be_truthy end end context 'that originates from a hash' do let (:mapped_paths) { '[mapped.hash_var, var, "paths/%{var.0}.%{var.1}.yaml"]' } let(:env_data) do { 'paths' => { 'x.a.yaml' => <<-YAML.unindent, path_xa: value path_xa path_m: a: value path_m.a c: value path_m.c YAML 'y.b.yaml' => <<-YAML.unindent path_m: b: value path_m.b d: value path_m.d YAML }, 'global_var.yaml' => <<-YAML.unindent path_m: e: value path_m.e YAML } end it 'finds environment data using mapped_paths' do expect(lookup('path_xa')).to eql('value path_xa') expect(warnings).to be_empty end it 'includes mapped path in explain output' do explanation = explain('path_h', 'merge' => 'deep') ['x\.a', 'y\.b', 'z\.c'].each do |var| expect(explanation).to match(/^\s+Path "#{env_dir}\/spec\/data\/paths\/#{var}\.yaml"\n\s+Original path: "paths\/%\{var\.0\}\.%\{var\.1\}\.yaml"/) end expect(warnings).to be_empty end it 'performs merges between mapped paths' do expect(lookup('path_m', 'merge' => 'hash')).to eql( { 'a' => 'value path_m.a', 'b' => 'value path_m.b', 'c' => 'value path_m.c', 'd' => 'value path_m.d', 'e' => 'value path_m.e' }) expect(warnings).to be_empty end end context 'that originates from a string' do let (:mapped_paths) { '[mapped.string_var, var, "paths/%{var}.yaml"]' } let(:env_data) do { 'paths' => { 's.yaml' => <<-YAML.unindent, path_s: value path_s YAML } } end it 'includes mapped path in explain output' do expect(explain('path_s')).to match(/^\s+Path "#{env_dir}\/spec\/data\/paths\/s\.yaml"\n\s+Original path: "paths\/%\{var\}\.yaml"/) expect(warnings).to be_empty end it 'finds environment data using mapped_paths' do expect(lookup('path_s')).to eql('value path_s') expect(warnings).to be_empty end end context 'where the enty does not exist' do let (:mapped_paths) { '[mapped.nosuch_var, var, "paths/%{var}.yaml"]' } it 'finds environment data using mapped_paths' do expect(explain('hello')).to match(/No such key: "hello"/) expect(warnings).to be_empty end end end context 'and an environment Hiera v3 configuration' do let(:env_hiera_yaml) do <<-YAML.unindent --- :backends: yaml :yaml: :datadir: #{env_dir}/#{env_name}/hieradata YAML end let(:environment_files) do { env_name => { 'hiera.yaml' => env_hiera_yaml, 'hieradata' => { 'common.yaml' => <<-YAML.unindent, g: Value g YAML } } } end it 'will raise an error if --strict is set to error' do Puppet[:strict] = :error expect { lookup('g') }.to raise_error(Puppet::Error, /hiera.yaml version 3 cannot be used in an environment/) end it 'will log a warning and ignore the file if --strict is set to warning' do Puppet[:strict] = :warning expect { lookup('g') }.to raise_error(Puppet::Error, /did not find a value for the name 'g'/) end it 'will not log a warning and ignore the file if --strict is set to off' do Puppet[:strict] = :off expect { lookup('g') }.to raise_error(Puppet::Error, /did not find a value for the name 'g'/) expect(warnings).to include(/hiera.yaml version 3 found at the environment root was ignored/) end it 'will use the configuration if appointed by global setting but still warn when encountered by environment data provider' do Puppet[:strict] = :warning Puppet.settings[:hiera_config] = File.join(env_dir, env_name, 'hiera.yaml') expect(lookup('g')).to eql('Value g') expect(warnings).to include(/hiera.yaml version 3 found at the environment root was ignored/) end end context 'and a global empty Hiera configuration' do let(:hiera_yaml_path) { File.join(code_dir, 'hiera.yaml') } let(:code_dir_files) do { 'hiera.yaml' => '', } end let(:environment_files) do { env_name => { 'hieradata' => { 'common.yaml' => <<-YAML.unindent, x: value x (from environment) YAML } } } end before(:each) do # Need to set here since spec_helper defines these settings in its "before each" Puppet.settings[:hiera_config] = hiera_yaml_path end it 'uses a Hiera version 3 defaults' do expect(lookup('x')).to eql('value x (from environment)') end context 'obtained using /dev/null', :unless => Puppet.features.microsoft_windows? do let(:code_dir_files) { {} } it 'uses a Hiera version 3 defaults' do Puppet[:hiera_config] = '/dev/null' expect(lookup('x')).to eql('value x (from environment)') end end end context 'and a global configuration' do let(:hiera_yaml) do <<-YAML.unindent --- :backends: - yaml - json - custom - hocon :yaml: :datadir: #{code_dir}/hieradata :json: :datadir: #{code_dir}/hieradata :hocon: :datadir: #{code_dir}/hieradata :hierarchy: - common - "%{domain}" :merge_behavior: deeper YAML end let(:code_dir_files) do { 'hiera.yaml' => hiera_yaml, 'hieradata' => { 'common.yaml' => <<-YAML.unindent, a: value a (from global) hash_b: hash_ba: bab: value hash_b.hash_ba.bab (from global) hash_c: hash_ca: cab: value hash_c.hash_ca.cab (from global) ipl_hiera_env: "environment value '%{hiera('mod_a::hash_a.a')}'" ipl_hiera_mod: "module value '%{hiera('mod_a::abc')}'" ipl_hiera_modc: "module value '%{hiera('mod_a::caller')}'" YAML 'example.com.yaml' => <<-YAML.unindent, x: value x (from global example.com.yaml) YAML 'common.json' => <<-JSON.unindent, { "hash_b": { "hash_ba": { "bac": "value hash_b.hash_ba.bac (from global json)" } }, "hash_c": { "hash_ca": { "cac": "value hash_c.hash_ca.cac (from global json)" } } } JSON 'common.conf' => <<-HOCON.unindent, // The 'xs' is a value used for testing xs = { subkey = value xs.subkey (from global hocon) } HOCON } } end let(:ruby_dir_files) do { 'hiera' => { 'backend' => { 'custom_backend.rb' => <<-RUBY.unindent, class Hiera::Backend::Custom_backend def lookup(key, scope, order_override, resolution_type, context) case key when 'hash_c' { 'hash_ca' => { 'cad' => 'value hash_c.hash_ca.cad (from global custom)' }} when 'hash' { 'array' => [ 'x5,x6' ] } when 'array' [ 'x5,x6' ] when 'datasources' Hiera::Backend.datasources(scope, order_override) { |source| source } when 'dotted.key' 'custom backend received request for dotted.key value' else throw :no_such_key end end end RUBY 'other_backend.rb' => <<-RUBY.unindent, class Hiera::Backend::Other_backend def lookup(key, scope, order_override, resolution_type, context) value = Hiera::Config[:other][key.to_sym] throw :no_such_key if value.nil? value end end RUBY } } } end before(:each) do # Need to set here since spec_helper defines these settings in its "before each" Puppet.settings[:codedir] = populated_code_dir Puppet.settings[:hiera_config] = File.join(code_dir, 'hiera.yaml') end around(:each) do |example| # Faking the load path to enable 'require' to load from 'ruby_stuff'. It removes the need for a static fixture # for the custom backend $LOAD_PATH.unshift(populated_ruby_dir) begin Puppet.override(:environments => environments, :current_environment => env) do example.run end ensure if Kernel.const_defined?(:Hiera) && Hiera.const_defined?(:Backend) Hiera::Backend.send(:remove_const, :Custom_backend) if Hiera::Backend.const_defined?(:Custom_backend) Hiera::Backend.send(:remove_const, :Other_backend) if Hiera::Backend.const_defined?(:Other_backend) end $LOAD_PATH.shift end end context 'version 3' do it 'finds data in in global layer and reports deprecation warnings for hiera.yaml' do expect(lookup('a')).to eql('value a (from global)') expect(warnings).to include(/Use of 'hiera.yaml' version 3 is deprecated. It should be converted to version 5/) end it 'explain contains output from global layer' do explanation = explain('a') expect(explanation).to include('Global Data Provider (hiera configuration version 3)') expect(explanation).to include('Hierarchy entry "yaml"') expect(explanation).to include('Hierarchy entry "json"') expect(explanation).to include('Found key: "a" value: "value a (from global)"') end it 'ignores merge behavior specified in global hiera.yaml' do expect(lookup('hash_b')).to eql( { 'hash_ba' => { 'bab' => 'value hash_b.hash_ba.bab (from global)'} }) end it 'uses the merge from lookup options to merge all layers' do expect(lookup('hash_c')).to eql( { 'hash_ca' => { 'cab' => 'value hash_c.hash_ca.cab (from global)' } }) end it 'uses the explicitly given merge to override lookup options and to merge all layers' do expect(lookup('hash_c', 'merge' => 'deep')).to eql( { 'hash_ca' => { 'caa' => 'value hash_c.hash_ca.caa (from environment)', 'cab' => 'value hash_c.hash_ca.cab (from global)', 'cac' => 'value hash_c.hash_ca.cac (from global json)', 'cad' => 'value hash_c.hash_ca.cad (from global custom)' } }) end it 'paths are interpolated' do expect(lookup('x')).to eql('value x (from global example.com.yaml)') end it 'backend data sources are propagated to custom backend' do expect(lookup('datasources')).to eql(['common', 'example.com']) end it 'delegates configured hocon backend to hocon_data function' do expect(explain('xs')).to match(/Hierarchy entry "hocon"\n.*\n.*\n.*"common"\n\s*Found key: "xs"/m) end it 'can dig down into subkeys provided by hocon_data function' do expect(lookup('xs.subkey')).to eql('value xs.subkey (from global hocon)') end context 'with a module data provider' do let(:module_files) do { 'mod_a' => { 'hiera.yaml' => <<-YAML.unindent, version: 5 hierarchy: - name: Common path: common.yaml YAML 'data' => { 'common.yaml' => <<-YAML.unindent mod_a::abc: value mod_a::abc (from module) mod_a::caller: "calling module is %{calling_module}" YAML } } } end let(:environment_files) do { env_name => { 'hiera.yaml' => env_hiera_yaml, 'data' => env_data, 'modules' => module_files } } end it "interpolation function 'hiera' finds values in environment" do expect(lookup('ipl_hiera_env')).to eql("environment value 'value mod_a::hash_a.a (from environment)'") end it "interpolation function 'hiera' finds values in module" do expect(lookup('ipl_hiera_mod')).to eql("module value 'value mod_a::abc (from module)'") end it "interpolation function 'hiera' finds values in module and that module does not find %{calling_module}" do expect(lookup('ipl_hiera_modc')).to eql("module value 'calling module is '") end context 'but no environment data provider' do let(:environment_files) do { env_name => { 'modules' => module_files } } end it "interpolation function 'hiera' does not find values in a module" do expect(lookup('ipl_hiera_mod')).to eql("module value ''") end end end context 'using an eyaml backend' do let(:private_key_name) { 'private_key.pkcs7.pem' } let(:public_key_name) { 'public_key.pkcs7.pem' } let(:private_key) do <<-PKCS7.unindent -----BEGIN RSA PRIVATE KEY----- MIIEogIBAAKCAQEAwHB3GvImq59em4LV9DMfP0Zjs21eW3Jd5I9fuY0jLJhIkH6f CR7tyOpYV6xUj+TF8giq9WLxZI7sourMJMWjEWhVjgUr5lqp1RLv4lwfDv3Wk4XC 2LUuqj1IAErUXKeRz8i3lUSZW1Pf4CaMpnIiPdWbz6f0KkaJSFi9bqexONBx4fKQ NlgZwm2/aYjjrYng788I0QhWDKUqsQOi5mZKlHNRsDlk7J3Afhsx/jTLrCX/G8+2 tPtLsHyRN39kluM5vYHbKXDsCG/a88Z2yUE2+r4Clp0FUKffiEDBPm0/H0sQ4Q1o EfQFDQRKaIkhpsm0nOnLYTy3/xJc5uqDNkLiawIDAQABAoIBAE98pNXOe8ab93oI mtNZYmjCbGAqprTjEoFb71A3SfYbmK2Gf65GxjUdBwx/tBYTiuekSOk+yzKcDoZk sZnmwKpqDByzaiSmAkxunANFxdZtZvpcX9UfUX0j/t+QCROUa5gF8j6HrUiZ5nkx sxr1PcuItekaGLJ1nDLz5JsWTQ+H4M+GXQw7/t96x8v8g9el4exTiAHGk6Fv16kD 017T02M9qTTmV3Ab/enDIBmKVD42Ta36K/wc4l1aoUQNiRbIGVh96Cgd1CFXLF3x CsaNbYT4SmRXaYqoj6MKq+QFEGxadFmJy48NoSd4joirIn2lUjHxJebw3lLbNLDR uvQnQ2ECgYEA/nD94wEMr6078uMv6nKxPpNGq7fihwSKf0G/PQDqrRmjUCewuW+k /iXMe1Y/y0PjFeNlSbUsUvKQ5xF7F/1AnpuPHIrn3cjGVLb71W+zen1m8SnhsW/f 7dPgtcb4SCvfhmLgoov+P34YcNfGi6qgPUu6319IqoB3BIi7PvfEomkCgYEAwZ4+ V0bMjFdDn2hnYzjTNcF2aUQ1jPvtuETizGwyCbbMLl9522lrjC2DrH41vvqX35ct CBJkhQFbtHM8Gnmozv0vxhI2jP+u14mzfePZsaXuYrEgWRj+BCsYUHodXryxnEWj yVrTNskab1B5jFm2SCJDmKcycBOYpRBLCMx6W7MCgYBA99z7/6KboOIzzKrJdGup jLV410UyMIikoccQ7pD9jhRTPS80yjsY4dHqlEVJw5XSWvPb9DTTITi6p44EvBep 6BKMuTMnQELUEr0O7KypVCfa4FTOl8BX28f+4kU3OGykxc6R8qkC0VGwTohV1UWB ITsgGhZV4uOA9uDI3T8KMQKBgEnQY2HwmuDSD/TA39GDA3qV8+ez2lqSXRGIKZLX mMf9SaBQQ+uzKA4799wWDbVuYeIbB07xfCL83pJP8FUDlqi6+7Celu9wNp7zX1ua Nw8z/ErhzjxJe+Xo7A8aTwIkG+5A2m1UU/up9YsEeiJYvVaIwY58B42U2vfq20BS fD9jAoGAX2MscBzIsmN+U9R0ptL4SXcPiVnOl8mqvQWr1B4OLgxX7ghht5Fs956W bHipxOWMFCPJA/AhNB8q1DvYiD1viZbIALSCJVUkzs4AEFIjiPsCBKxerl7jF6Xp 1WYSaCmfvoCVEpFNt8cKp4Gq+zEBYAV4Q6TkcD2lDtEW49MuN8A= -----END RSA PRIVATE KEY----- PKCS7 end let(:public_key) do <<-PKCS7.unindent -----BEGIN CERTIFICATE----- MIIC2TCCAcGgAwIBAgIBATANBgkqhkiG9w0BAQUFADAAMCAXDTE3MDExMzA5MTY1 MloYDzIwNjcwMTAxMDkxNjUyWjAAMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB CgKCAQEAwHB3GvImq59em4LV9DMfP0Zjs21eW3Jd5I9fuY0jLJhIkH6fCR7tyOpY V6xUj+TF8giq9WLxZI7sourMJMWjEWhVjgUr5lqp1RLv4lwfDv3Wk4XC2LUuqj1I AErUXKeRz8i3lUSZW1Pf4CaMpnIiPdWbz6f0KkaJSFi9bqexONBx4fKQNlgZwm2/ aYjjrYng788I0QhWDKUqsQOi5mZKlHNRsDlk7J3Afhsx/jTLrCX/G8+2tPtLsHyR N39kluM5vYHbKXDsCG/a88Z2yUE2+r4Clp0FUKffiEDBPm0/H0sQ4Q1oEfQFDQRK aIkhpsm0nOnLYTy3/xJc5uqDNkLiawIDAQABo1wwWjAPBgNVHRMBAf8EBTADAQH/ MB0GA1UdDgQWBBSejWrVnw7QaBjNFCHMNFi+doSOcTAoBgNVHSMEITAfgBSejWrV nw7QaBjNFCHMNFi+doSOcaEEpAIwAIIBATANBgkqhkiG9w0BAQUFAAOCAQEAAe85 BQ1ydAHFqo0ib38VRPOwf5xPHGbYGhvQi4/sU6aTuR7pxaOJPYz05jLhS+utEmy1 sknBq60G67yhQE7IHcfwrl1arirG2WmKGvAbjeYL2K1UiU0pVD3D+Klkv/pK6jIQ eOJRGb3qNUn0Sq9EoYIOXiGXQ641F0bZZ0+5H92kT1lmnF5oLfCb84ImD9T3snH6 pIr5RKRx/0YmJIcv3WdpoPT903rOJiRIEgIj/hDk9QZTBpm222Ul5yQQ5pBywpSp xh0bmJKAQWhQm7QlybKfyaQmg5ot1jEzWAvD2I5FjHQxmAlchjb6RreaRhExj+JE 5O117dMBdzDBjcNMOA== -----END CERTIFICATE----- PKCS7 end let(:keys_dir) do keys = tmpdir('keys') dir_contained_in(keys, { private_key_name => private_key, public_key_name => public_key }) keys end let(:private_key_path) { File.join(keys_dir, private_key_name) } let(:public_key_path) { File.join(keys_dir, public_key_name) } let(:hiera_yaml) do <<-YAML.unindent :backends: - eyaml - yaml :eyaml: :datadir: #{code_dir}/hieradata :pkcs7_private_key: #{private_key_path} :pkcs7_public_key: #{public_key_path} :yaml: :datadir: #{code_dir}/hieradata :hierarchy: - common YAML end let(:data_files) do { 'common.yaml' => <<-YAML.unindent, b: value 'b' (from global) c: c_a: value c_a (from global) YAML 'common.eyaml' => <<-YAML.unindent a: > ENC[PKCS7,MIIBmQYJKoZIhvcNAQcDoIIBijCCAYYCAQAxggEhMIIBHQIBADAFMAACAQEw DQYJKoZIhvcNAQEBBQAEggEAH457bsfL8kYw9O50roE3dcE21nCnmPnQ2XSX LYRJ2C78LarbfFonKz0gvDW7tyhsLWASFCFaiU8T1QPBd2b3hoQK8E4B2Ual xga/K7r9y3OSgRomTm9tpTltC6re0Ubh3Dy71H61obwxEdNVTqjPe95+m2b8 6zWZVnzZzXXsTG1S17yJn1zaB/LXHbWNy4KyLLKCGAml+Gfl6ZMjmaplTmUA QIC5rI8abzbPP3TDMmbLOGNkrmLqI+3uS8tSueTMoJmWaMF6c+H/cA7oRxmV QCeEUVXjyFvCHcmbA+keS/RK9XF+vc07/XS4XkYSPs/I5hLQji1y9bkkGAs0 tehxQjBcBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBDHpA6Fcl/R16aIYcow oiO4gDAvfFH6jLUwXkcYtagnwdmhkd9TQJtxNWcIwMpvmk036MqIoGwwhQdg gV4beiCFtLU=] a_ref: "A reference to %{hiera('a')}" b_ref: "A reference to %{hiera('b')}" c_ref: "%{alias('c')}" YAML } end let(:code_dir_files) do { 'hiera.yaml' => hiera_yaml, 'hieradata' => data_files } end before(:each) do Puppet.settings[:hiera_config] = File.join(code_dir, 'hiera.yaml') end it 'finds data in the global layer' do expect(lookup('a')).to eql("Encrypted value 'a' (from global)") end it 'can use a hiera interpolation' do expect(lookup('a_ref')).to eql("A reference to Encrypted value 'a' (from global)") end it 'can use a hiera interpolation that refers back to yaml' do expect(lookup('b_ref')).to eql("A reference to value 'b' (from global)") end it 'can use a hiera interpolation that refers back to yaml, but only in global layer' do expect(lookup(['c', 'c_ref'], 'merge' => 'deep')).to eql([{'c_a' => 'value c_a (from global)', 'c_b' => 'value c_b (from environment)'}, { 'c_a' => 'value c_a (from global)' }]) end it 'delegates configured eyaml backend to eyaml_lookup_key function' do expect(explain('a')).to match(/Hierarchy entry "eyaml"\n.*\n.*\n.*"common"\n\s*Found key: "a"/m) end end context 'using deep_merge_options' do let(:hiera_yaml) do <<-YAML.unindent --- :backends: - yaml :yaml: :datadir: #{code_dir}/hieradata :hierarchy: - common - other :merge_behavior: deeper :deep_merge_options: :unpack_arrays: ',' YAML end let(:code_dir_files) do { 'hiera.yaml' => hiera_yaml, 'hieradata' => { 'common.yaml' => <<-YAML.unindent, hash: array: - x1,x2 array: - x1,x2 str: a string mixed: x: hx y: hy YAML 'other.yaml' => <<-YAML.unindent, hash: array: - x3 - x4 array: - x3 - x4 str: another string mixed: - h1 - h2 YAML } } end it 'ignores configured merge_behavior when looking up arrays' do expect(lookup('array')).to eql(['x1,x2']) end it 'ignores configured merge_behavior when merging arrays' do expect(lookup('array', 'merge' => 'unique')).to eql(['x1,x2', 'x3', 'x4']) end it 'ignores configured merge_behavior when looking up hashes' do expect(lookup('hash')).to eql({'array' => ['x1,x2']}) end it 'ignores configured merge_behavior when merging hashes' do expect(lookup('hash', 'merge' => 'hash')).to eql({'array' => ['x1,x2']}) end end context 'using relative datadir paths' do let(:hiera_yaml) do <<-YAML.unindent --- :backends: - yaml :yaml: :datadir: relative_data :hierarchy: - common YAML end let(:populated_code_dir) do dir_contained_in(code_dir, code_dir_files.merge({ 'fake_cwd' => { 'relative_data' => { 'common.yaml' => <<-YAML.unindent a: value a (from fake_cwd/relative_data/common.yaml) YAML } } })) code_dir end around(:each) do |example| cwd = Dir.pwd Dir.chdir(File.join(code_dir, 'fake_cwd')) begin example.run ensure Dir.chdir(cwd) end end it 'finds data from data file beneath relative datadir' do expect(lookup('a')).to eql('value a (from fake_cwd/relative_data/common.yaml)') end end end context 'version 5' do let(:scope_additions) { { 'ipl_datadir' => 'hieradata' } } let(:hiera_yaml) do <<-YAML.unindent --- version: 5 defaults: datadir: "%{ipl_datadir}" hierarchy: - name: Yaml data_hash: yaml_data paths: - common.yaml - "%{domain}.yaml" - name: Json data_hash: json_data paths: - common.json - "%{domain}.json" - name: Hocon data_hash: hocon_data paths: - common.conf - "%{domain}.conf" - name: Custom hiera3_backend: custom paths: - common.custom - "%{domain}.custom" - name: Other hiera3_backend: other options: other_option: value of other_option paths: - common.other - "%{domain}.other" YAML end it 'finds global data and reports no deprecation warnings' do expect(lookup('a')).to eql('value a (from global)') expect(warnings).to be_empty end it 'explain contains output from global layer' do explanation = explain('a') expect(explanation).to include('Global Data Provider (hiera configuration version 5)') expect(explanation).to include('Hierarchy entry "Yaml"') expect(explanation).to include('Hierarchy entry "Json"') expect(explanation).to include('Hierarchy entry "Hocon"') expect(explanation).to include('Hierarchy entry "Custom"') expect(explanation).to include('Found key: "a" value: "value a (from global)"') end it 'uses the explicitly given merge to override lookup options and to merge all layers' do expect(lookup('hash_c', 'merge' => 'deep')).to eql( { 'hash_ca' => { 'caa' => 'value hash_c.hash_ca.caa (from environment)', 'cab' => 'value hash_c.hash_ca.cab (from global)', 'cac' => 'value hash_c.hash_ca.cac (from global json)', 'cad' => 'value hash_c.hash_ca.cad (from global custom)' } }) end it 'backend data sources are propagated to custom backend' do expect(lookup('datasources')).to eql(['common', 'example.com']) end it 'backend specific options are propagated to custom backend' do expect(lookup('other_option')).to eql('value of other_option') end it 'dotted keys are passed down to custom backend' do expect(lookup('dotted.key')).to eql('custom backend received request for dotted.key value') end it 'multiple hiera3_backend declarations can be used and are merged into the generated config' do expect(lookup(['datasources', 'other_option'])).to eql([['common', 'example.com'], 'value of other_option']) expect(Hiera::Config.instance_variable_get(:@config)).to eql( { :backends => ['custom', 'other'], :hierarchy => ['common', '%{domain}'], :custom => { :datadir => "#{code_dir}/hieradata" }, :other => { :other_option => 'value of other_option', :datadir=>"#{code_dir}/hieradata" }, :logger => 'puppet' }) end it 'provides a sensible error message when the hocon library is not loaded' do Puppet.features.stubs(:hocon?).returns(false) expect { lookup('a') }.to raise_error do |e| expect(e.message).to match(/Lookup using Hocon data_hash function is not supported without hocon library/) end end context 'with missing path declaraion' do context 'and yaml_data function' do let(:hiera_yaml) { <<-YAML.unindent } version: 5 hierarchy: - name: Yaml data_hash: yaml_data YAML it 'fails and reports the missing path' do expect { lookup('a') }.to raise_error(/one of 'path', 'paths' 'glob', 'globs' or 'mapped_paths' must be declared in hiera.yaml when using this data_hash function/) end end context 'and json_data function' do let(:hiera_yaml) { <<-YAML.unindent } version: 5 hierarchy: - name: Json data_hash: json_data YAML it 'fails and reports the missing path' do expect { lookup('a') }.to raise_error(/one of 'path', 'paths' 'glob', 'globs' or 'mapped_paths' must be declared in hiera.yaml when using this data_hash function/) end end context 'and hocon_data function' do let(:hiera_yaml) { <<-YAML.unindent } version: 5 hierarchy: - name: Hocon data_hash: hocon_data YAML it 'fails and reports the missing path' do expect { lookup('a') }.to raise_error(/one of 'path', 'paths' 'glob', 'globs' or 'mapped_paths' must be declared in hiera.yaml when using this data_hash function/) end end context 'and eyaml_lookup_key function' do let(:hiera_yaml) { <<-YAML.unindent } version: 5 hierarchy: - name: Yaml lookup_key: eyaml_lookup_key YAML it 'fails and reports the missing path' do expect { lookup('a') }.to raise_error(/one of 'path', 'paths' 'glob', 'globs' or 'mapped_paths' must be declared in hiera.yaml when using this lookup_key function/) end end end end context 'with a hiera3_backend that has no paths' do let(:hiera_yaml) do <<-YAML.unindent --- version: 5 hierarchy: - name: Custom hiera3_backend: custom YAML end it 'calls the backend' do expect(lookup('hash_c')).to eql( { 'hash_ca' => { 'cad' => 'value hash_c.hash_ca.cad (from global custom)' }}) end end end context 'and a module' do let(:mod_a_files) { {} } let(:populated_env_dir) do dir_contained_in(env_dir, DeepMerge.deep_merge!(environment_files, env_name => { 'modules' => mod_a_files })) env_dir end context 'that has no lookup configured' do let(:mod_a_files) do { 'mod_a' => { 'data' => { 'common.yaml' => <<-YAML.unindent --- mod_a::b: value mod_a::b (from mod_a) YAML } } } end it 'does not find data in the module' do expect { lookup('mod_a::b') }.to raise_error(Puppet::DataBinding::LookupError, /did not find a value for the name 'mod_a::b'/) end context 'with a Hiera v3 configuration' do let(:mod_a_files) do { 'mod_a' => { 'hiera.yaml' => <<-YAML.unindent --- :backends: yaml YAML } } end it 'raises a warning' do expect(lookup('mod_a::a')).to eql('value mod_a::a (from environment)') expect(warnings).to include(/hiera.yaml version 3 found at module root was ignored/) end end context "but a metadata.json with 'module_data_provider=hiera'" do let(:mod_a_files_1) { DeepMerge.deep_merge!(mod_a_files, 'mod_a' => { 'metadata.json' => <<-JSON.unindent }) } { "name": "example/mod_a", "version": "0.0.2", "source": "git@github.com/example/mod_a.git", "dependencies": [], "author": "Bob the Builder", "license": "Apache-2.0", "data_provider": "hiera" } JSON let(:populated_env_dir) do dir_contained_in(env_dir, DeepMerge.deep_merge!(environment_files, env_name => { 'modules' => mod_a_files_1 })) env_dir end it 'finds data in the module and reports deprecation warning for metadata.json' do expect(lookup('mod_a::b')).to eql('value mod_a::b (from mod_a)') expect(warnings).to include(/Defining "data_provider": "hiera" in metadata.json is deprecated. A 'hiera.yaml' file should be used instead/) end context 'and a hiera.yaml file' do let(:mod_a_files_2) { DeepMerge.deep_merge!(mod_a_files_1, 'mod_a' => { 'hiera.yaml' => <<-YAML.unindent }) } --- version: 4 hierarchy: - name: common backend: yaml YAML let(:populated_env_dir) do dir_contained_in(env_dir, DeepMerge.deep_merge!(environment_files, env_name => { 'modules' => mod_a_files_2 })) env_dir end it 'finds data in the module and reports deprecation warnings for both metadata.json and hiera.yaml' do expect(lookup('mod_a::b')).to eql('value mod_a::b (from mod_a)') expect(warnings).to include(/Defining "data_provider": "hiera" in metadata.json is deprecated/) expect(warnings).to include(/Use of 'hiera.yaml' version 4 is deprecated. It should be converted to version 5/) end end end end context 'using deep merge and module values that aliases environment values' do let(:mod_a_files) do { 'mod_a' => { 'data' => { 'common.yaml' => <<-YAML.unindent, --- mod_a::hash: b: value b (from module) lookup_options: mod_a::hash: merge: deep YAML }, 'hiera.yaml' => <<-YAML.unindent, --- version: 5 hierarchy: - name: "Common" path: "common.yaml" - name: "Other" path: "other.yaml" YAML } } end let(:env_data) do { 'common.yaml' => <<-YAML.unindent a: value a (from environment) mod_a::hash: a: value mod_a::hash.a (from environment) c: '%{alias("a")}' YAML } end it 'continues with module lookup after alias is resolved in environment' do expect(lookup('mod_a::hash')).to eql( { 'a' => 'value mod_a::hash.a (from environment)', 'b' => 'value b (from module)', 'c' => 'value a (from environment)' }) end end context 'using a data_hash that reads a yaml file' do let(:defaults) { { 'mod_a::xd' => 'value mod_a::xd (from default)', 'mod_a::xd_found' => 'value mod_a::xd_found (from default)', 'scope_xd' => 'value scope_xd (from default)' }} let(:overrides) { { 'mod_a::xo' => 'value mod_a::xo (from override)', 'scope_xo' => 'value scope_xo (from override)' }} let(:scope_additions) do { 'scope_scalar' => 'scope scalar value', 'scope_hash' => { 'a' => 'scope hash a', 'b' => 'scope hash b' } } end let(:mod_a_files) do { 'mod_a' => { 'data' => { 'common.yaml' => <<-YAML.unindent --- mod_a::a: value mod_a::a (from mod_a) mod_a::b: value mod_a::b (from mod_a) mod_a::xo: value mod_a::xo (from mod_a) mod_a::xd_found: value mod_a::xd_found (from mod_a) mod_a::interpolate_xo: "-- %{lookup('mod_a::xo')} --" mod_a::interpolate_xd: "-- %{lookup('mod_a::xd')} --" mod_a::interpolate_scope_xo: "-- %{scope_xo} --" mod_a::interpolate_scope_xd: "-- %{scope_xd} --" mod_a::hash_a: a: value mod_a::hash_a.a (from mod_a) b: value mod_a::hash_a.b (from mod_a) mod_a::hash_b: a: value mod_a::hash_b.a (from mod_a) b: value mod_a::hash_b.b (from mod_a) mod_a::interpolated: "-- %{lookup('mod_a::a')} --" mod_a::a_a: "-- %{lookup('mod_a::hash_a.a')} --" mod_a::a_b: "-- %{lookup('mod_a::hash_a.b')} --" mod_a::b_a: "-- %{lookup('mod_a::hash_b.a')} --" mod_a::b_b: "-- %{lookup('mod_a::hash_b.b')} --" mod_a::interpolate_array: - "-- %{lookup('mod_a::a')} --" - "-- %{lookup('mod_a::b')} --" mod_a::interpolate_literal: "-- %{literal('hello')} --" mod_a::interpolate_scope: "-- %{scope_scalar} --" mod_a::interpolate_scope_not_found: "-- %{scope_nope} --" mod_a::interpolate_scope_dig: "-- %{scope_hash.a} --" mod_a::interpolate_scope_dig_not_found: "-- %{scope_hash.nope} --" mod_a::quoted_interpolation: '-- %{lookup(''"mod_a::a.quoted.key"'')} --' "mod_a::a.quoted.key": "value mod_a::a.quoted.key (from mod_a)" YAML }, 'hiera.yaml' => <<-YAML.unindent, --- version: 5 hierarchy: - name: "Common" data_hash: yaml_data path: "common.yaml" YAML } } end it 'finds data in the module' do expect(lookup('mod_a::b')).to eql('value mod_a::b (from mod_a)') end it 'environment data has higher priority than module data' do expect(lookup('mod_a::a')).to eql('value mod_a::a (from environment)') end it 'environment data has higher priority than module data in interpolated module data' do expect(lookup('mod_a::interpolated')).to eql('-- value mod_a::a (from environment) --') end it 'overrides have higher priority than found data' do expect(lookup('mod_a::xo', { 'override' => overrides })).to eql('value mod_a::xo (from override)') end it 'overrides have higher priority than found data in lookup interpolations' do expect(lookup('mod_a::interpolate_xo', { 'override' => overrides })).to eql('-- value mod_a::xo (from override) --') end it 'overrides have higher priority than found data in scope interpolations' do expect(lookup('mod_a::interpolate_scope_xo', { 'override' => overrides })).to eql('-- value scope_xo (from override) --') end it 'defaults have lower priority than found data' do expect(lookup('mod_a::xd_found', { 'default_values_hash' => defaults })).to eql('value mod_a::xd_found (from mod_a)') end it 'defaults are used when data is not found' do expect(lookup('mod_a::xd', { 'default_values_hash' => defaults })).to eql('value mod_a::xd (from default)') end it 'defaults are used when data is not found in lookup interpolations' do expect(lookup('mod_a::interpolate_xd', { 'default_values_hash' => defaults })).to eql('-- value mod_a::xd (from default) --') end it 'defaults are used when data is not found in scope interpolations' do expect(lookup('mod_a::interpolate_scope_xd', { 'default_values_hash' => defaults })).to eql('-- value scope_xd (from default) --') end it 'merges hashes from environment and module unless strategy hash is used' do expect(lookup('mod_a::hash_a')).to eql({'a' => 'value mod_a::hash_a.a (from environment)'}) end it 'merges hashes from environment and module when merge strategy hash is used' do expect(lookup('mod_a::hash_a', :merge => 'hash')).to eql( {'a' => 'value mod_a::hash_a.a (from environment)', 'b' => 'value mod_a::hash_a.b (from mod_a)'}) end it 'will not merge hashes from environment and module in interpolated expressions' do expect(lookup(['mod_a::a_a', 'mod_a::a_b'])).to eql( ['-- value mod_a::hash_a.a (from environment) --', '-- --']) # root key found in environment, no hash merge is performed end it 'interpolates arrays' do expect(lookup('mod_a::interpolate_array')).to eql(['-- value mod_a::a (from environment) --', '-- value mod_a::b (from mod_a) --']) end it 'can dig into arrays using subkeys' do expect(lookup('mod_a::interpolate_array.1')).to eql('-- value mod_a::b (from mod_a) --') end it 'treats an out of range subkey as not found' do expect(explain('mod_a::interpolate_array.2')).to match(/No such key: "2"/) end it 'interpolates a literal' do expect(lookup('mod_a::interpolate_literal')).to eql('-- hello --') end it 'interpolates scalar from scope' do expect(lookup('mod_a::interpolate_scope')).to eql('-- scope scalar value --') end it 'interpolates not found in scope as empty string' do expect(lookup('mod_a::interpolate_scope_not_found')).to eql('-- --') end it 'interpolates dotted key from scope' do expect(lookup('mod_a::interpolate_scope_dig')).to eql('-- scope hash a --') end it 'treates interpolated dotted key but not found in scope as empty string' do expect(lookup('mod_a::interpolate_scope_dig_not_found')).to eql('-- --') end it 'can use quoted keys in interpolation' do expect(lookup('mod_a::quoted_interpolation')).to eql('-- value mod_a::a.quoted.key (from mod_a) --') # root key found in environment, no hash merge is performed end it 'merges hashes from environment and module in interpolated expressions if hash merge is specified in lookup options' do expect(lookup(['mod_a::b_a', 'mod_a::b_b'])).to eql( ['-- value mod_a::hash_b.a (from environment) --', '-- value mod_a::hash_b.b (from mod_a) --']) end end context 'using a lookup_key that uses a path' do let(:mod_a_files) do { 'mod_a' => { 'functions' => { 'pp_lookup_key.pp' => <<-PUPPET.unindent function mod_a::pp_lookup_key($key, $options, $context) { if !$context.cache_has_key(undef) { $context.cache_all(yaml_data($options, $context)) $context.cache(undef, true) } if $context.cache_has_key($key) { $context.cached_value($key) } else { $context.not_found } } PUPPET }, 'hiera.yaml' => <<-YAML.unindent, --- version: 5 hierarchy: - name: "Common" lookup_key: mod_a::pp_lookup_key path: common.yaml YAML 'data' => { 'common.yaml' => <<-YAML.unindent mod_a::b: value mod_a::b (from mod_a) YAML } } } end it 'finds data in the module' do expect(lookup('mod_a::b')).to eql('value mod_a::b (from mod_a)') end end context 'using a lookup_key that is a puppet function' do let(:puppet_function) { <<-PUPPET.unindent } function mod_a::pp_lookup_key(Puppet::LookupKey $key, Hash[String,String] $options, Puppet::LookupContext $context) >> Puppet::LookupValue { case $key { 'mod_a::really_interpolated': { $context.interpolate("-- %{lookup('mod_a::a')} --") } 'mod_a::recursive': { lookup($key) } default: { if $context.cache_has_key(mod_a::a) { $context.explain || { 'reusing cache' } } else { $context.explain || { 'initializing cache' } $context.cache_all({ mod_a::a => 'value mod_a::a (from mod_a)', mod_a::b => 'value mod_a::b (from mod_a)', mod_a::c => 'value mod_a::c (from mod_a)', mod_a::hash_a => { a => 'value mod_a::hash_a.a (from mod_a)', b => 'value mod_a::hash_a.b (from mod_a)' }, mod_a::hash_b => { a => 'value mod_a::hash_b.a (from mod_a)', b => 'value mod_a::hash_b.b (from mod_a)' }, mod_a::interpolated => "-- %{lookup('mod_a::a')} --", mod_a::a_a => "-- %{lookup('mod_a::hash_a.a')} --", mod_a::a_b => "-- %{lookup('mod_a::hash_a.b')} --", mod_a::b_a => "-- %{lookup('mod_a::hash_b.a')} --", mod_a::b_b => "-- %{lookup('mod_a::hash_b.b')} --", 'mod_a::a.quoted.key' => 'value mod_a::a.quoted.key (from mod_a)', mod_a::sensitive => Sensitive('reduct me please'), mod_a::type => Object[{name => 'FindMe', 'attributes' => {'x' => String}}], mod_a::version => SemVer('3.4.1'), mod_a::version_range => SemVerRange('>=3.4.1'), mod_a::timestamp => Timestamp("1994-03-25T19:30:00"), mod_a::timespan => Timespan("3-10:00:00") }) } if !$context.cache_has_key($key) { $context.not_found } $context.explain || { "returning value for $key" } $context.cached_value($key) } } } PUPPET let(:mod_a_files) do { 'mod_a' => { 'functions' => { 'pp_lookup_key.pp' => puppet_function }, 'hiera.yaml' => <<-YAML.unindent, --- version: 5 hierarchy: - name: "Common" lookup_key: mod_a::pp_lookup_key YAML } } end it 'finds data in the module' do expect(lookup('mod_a::b')).to eql('value mod_a::b (from mod_a)') end it 'environment data has higher priority than module data' do expect(lookup('mod_a::a')).to eql('value mod_a::a (from environment)') end it 'finds quoted keys in the module' do expect(lookup('"mod_a::a.quoted.key"')).to eql('value mod_a::a.quoted.key (from mod_a)') end it 'will not resolve interpolated expressions' do expect(lookup('mod_a::interpolated')).to eql("-- %{lookup('mod_a::a')} --") end it 'resolves interpolated expressions using Context#interpolate' do expect(lookup('mod_a::really_interpolated')).to eql("-- value mod_a::a (from environment) --") end it 'will not merge hashes from environment and module unless strategy hash is used' do expect(lookup('mod_a::hash_a')).to eql({ 'a' => 'value mod_a::hash_a.a (from environment)' }) end it 'merges hashes from environment and module when merge strategy hash is used' do expect(lookup('mod_a::hash_a', :merge => 'hash')).to eql({ 'a' => 'value mod_a::hash_a.a (from environment)', 'b' => 'value mod_a::hash_a.b (from mod_a)' }) end it 'traps recursive lookup trapped' do expect(explain('mod_a::recursive')).to include('Recursive lookup detected') end it 'private cache is persisted over multiple calls' do collect_notices("notice(lookup('mod_a::b')) notice(lookup('mod_a::c'))", true) expect(notices).to eql(['value mod_a::b (from mod_a)', 'value mod_a::c (from mod_a)']) expect(explanation).to match(/initializing cache.*reusing cache/m) expect(explanation).not_to match(/initializing cache.*initializing cache/m) end it 'the same key is requested only once' do collect_notices("notice(lookup('mod_a::b')) notice(lookup('mod_a::b'))", true) expect(notices).to eql(['value mod_a::b (from mod_a)', 'value mod_a::b (from mod_a)']) expect(explanation).to match(/Found key: "mod_a::b".*Found key: "mod_a::b"/m) expect(explanation).to match(/returning value for mod_a::b/m) expect(explanation).not_to match(/returning value for mod_a::b.*returning value for mod_a::b/m) end context 'and calling function via API' do it 'finds and delivers rich data' do collect_notices("notice('success')") do |scope| expect(lookup_func.call(scope, 'mod_a::sensitive')).to be_a(Puppet::Pops::Types::PSensitiveType::Sensitive) expect(lookup_func.call(scope, 'mod_a::type')).to be_a(Puppet::Pops::Types::PObjectType) expect(lookup_func.call(scope, 'mod_a::version')).to eql(SemanticPuppet::Version.parse('3.4.1')) expect(lookup_func.call(scope, 'mod_a::version_range')).to eql(SemanticPuppet::VersionRange.parse('>=3.4.1')) expect(lookup_func.call(scope, 'mod_a::timestamp')).to eql(Puppet::Pops::Time::Timestamp.parse('1994-03-25T19:30:00')) expect(lookup_func.call(scope, 'mod_a::timespan')).to eql(Puppet::Pops::Time::Timespan.parse('3-10:00:00')) end expect(notices).to eql(['success']) end end context 'with declared but incompatible return_type' do let(:puppet_function) { <<-PUPPET.unindent } function mod_a::pp_lookup_key(Puppet::LookupKey $key, Hash[String,String] $options, Puppet::LookupContext $context) >> Runtime['ruby','Symbol'] { undef } PUPPET it 'fails and reports error' do expect{lookup('mod_a::a')}.to raise_error( "Return type of 'lookup_key' function named 'mod_a::pp_lookup_key' is incorrect, expects a RichData value, got Runtime") end end end context 'using a data_dig that is a ruby function' do let(:mod_a_files) do { 'mod_a' => { 'lib' => { 'puppet' => { 'functions' => { 'mod_a' => { 'ruby_dig.rb' => <<-RUBY.unindent Puppet::Functions.create_function(:'mod_a::ruby_dig') do dispatch :ruby_dig do param 'Array[String[1]]', :segments param 'Hash[String,Any]', :options param 'Puppet::LookupContext', :context return_type 'Puppet::LookupValue' end def ruby_dig(segments, options, context) sub_segments = segments.dup root_key = sub_segments.shift case root_key when 'mod_a::options' hash = { 'mod_a::options' => options } when 'mod_a::lookup' return call_function('lookup', segments.join('.')) else hash = { 'mod_a::a' => 'value mod_a::a (from mod_a)', 'mod_a::b' => 'value mod_a::b (from mod_a)', 'mod_a::hash_a' => { 'a' => 'value mod_a::hash_a.a (from mod_a)', 'b' => 'value mod_a::hash_a.b (from mod_a)' }, 'mod_a::hash_b' => { 'a' => 'value mod_a::hash_b.a (from mod_a)', 'b' => 'value mod_a::hash_b.b (from mod_a)' }, 'mod_a::interpolated' => "-- %{lookup('mod_a::a')} --", 'mod_a::really_interpolated' => "-- %{lookup('mod_a::a')} --", 'mod_a::a_a' => "-- %{lookup('mod_a::hash_a.a')} --", 'mod_a::a_b' => "-- %{lookup('mod_a::hash_a.b')} --", 'mod_a::b_a' => "-- %{lookup('mod_a::hash_b.a')} --", 'mod_a::b_b' => "-- %{lookup('mod_a::hash_b.b')} --", 'mod_a::bad_type' => :oops, 'mod_a::bad_type_in_hash' => { 'a' => :oops }, } end context.not_found unless hash.include?(root_key) value = sub_segments.reduce(hash[root_key]) do |memo, segment| context.not_found unless memo.is_a?(Hash) && memo.include?(segment) memo[segment] end root_key == 'mod_a::really_interpolated' ? context.interpolate(value) : value end end RUBY } } } }, 'hiera.yaml' => <<-YAML.unindent, --- version: 5 defaults: options: option_b: z: Default option value b.z hierarchy: - name: "Common" data_dig: mod_a::ruby_dig uri: "http://www.example.com/passed/as/option" options: option_a: Option value a option_b: x: Option value b.x y: Option value b.y - name: "Extra" data_dig: mod_a::ruby_dig YAML } } end it 'finds data in the module' do expect(lookup('mod_a::b')).to eql('value mod_a::b (from mod_a)') end it 'environment data has higher priority than module data' do expect(lookup('mod_a::a')).to eql('value mod_a::a (from environment)') end it 'will not resolve interpolated expressions' do expect(lookup('mod_a::interpolated')).to eql("-- %{lookup('mod_a::a')} --") end it 'resolves interpolated expressions using Context#interpolate' do expect(lookup('mod_a::really_interpolated')).to eql("-- value mod_a::a (from environment) --") end it 'does not accept return of runtime type from function' do # Message is produced by the called function, not by the lookup framework expect(explain('mod_a::bad_type')).to include("value returned from function 'ruby_dig' has wrong type") end it 'does not accept return of runtime type embedded in hash from function' do # Message is produced by the called function, not by the lookup framework expect(explain('mod_a::bad_type_in_hash')).to include("value returned from function 'ruby_dig' has wrong type") end it 'will not merge hashes from environment and module unless strategy hash is used' do expect(lookup('mod_a::hash_a')).to eql({'a' => 'value mod_a::hash_a.a (from environment)'}) end it 'hierarchy entry options are passed to the function' do expect(lookup('mod_a::options.option_b.x')).to eql('Option value b.x') end it 'default options are passed to the function' do expect(lookup('mod_a::options.option_b.z')).to eql('Default option value b.z') end it 'default options are not merged with hierarchy options' do expect(lookup('mod_a::options')).to eql( { 'option_a' => 'Option value a', 'option_b' => { 'y' => 'Option value b.y', 'x' => 'Option value b.x' }, 'uri' => 'http://www.example.com/passed/as/option' }) end it 'hierarchy entry "uri" is passed as location option to the function' do expect(lookup('mod_a::options.uri')).to eql('http://www.example.com/passed/as/option') end it 'recursive lookup is trapped' do expect(explain('mod_a::lookup.mod_a::lookup')).to include('Recursive lookup detected') end context 'with merge strategy hash' do it 'merges hashes from environment and module' do expect(lookup('mod_a::hash_a', :merge => 'hash')).to eql({'a' => 'value mod_a::hash_a.a (from environment)', 'b' => 'value mod_a::hash_a.b (from mod_a)'}) end it 'will "undig" value from data_dig function, merge root hashes, and then dig to get values by subkey' do expect(lookup(['mod_a::hash_a.a', 'mod_a::hash_a.b'], :merge => 'hash')).to eql( ['value mod_a::hash_a.a (from environment)', 'value mod_a::hash_a.b (from mod_a)']) end end end context 'that has a default_hierarchy' do let(:mod_a_hiera_yaml) { <<-YAML.unindent } version: 5 hierarchy: - name: "Common" path: common.yaml - name: "Common 2" path: common2.yaml default_hierarchy: - name: "Default" path: defaults.yaml - name: "Default 2" path: defaults2.yaml YAML let(:mod_a_common) { <<-YAML.unindent } mod_a::a: value mod_a::a (from module) mod_a::d: a: value mod_a::d.a (from module) mod_a::f: a: a: value mod_a::f.a.a (from module) mod_a::to_array1: 'hello' mod_a::to_array2: 'hello' mod_a::to_int: 'bananas' mod_a::to_bad_type: 'pyjamas' mod_a::undef_value: null lookup_options: mod_a::e: merge: deep mod_a::to_array1: merge: deep convert_to: "Array" mod_a::to_array2: convert_to: - "Array" - true mod_a::to_int: convert_to: "Integer" mod_a::to_bad_type: convert_to: "ComicSans" mod_a::undef_value: convert_to: - "Array" - true YAML let(:mod_a_common2) { <<-YAML.unindent } mod_a::b: value mod_a::b (from module) mod_a::d: c: value mod_a::d.c (from module) mod_a::f: a: b: value mod_a::f.a.b (from module) YAML let(:mod_a_defaults) { <<-YAML.unindent } mod_a::a: value mod_a::a (from module defaults) mod_a::b: value mod_a::b (from module defaults) mod_a::c: value mod_a::c (from module defaults) mod_a::d: b: value mod_a::d.b (from module defaults) mod_a::e: a: a: value mod_a::e.a.a (from module defaults) mod_a::g: a: a: value mod_a::g.a.a (from module defaults) lookup_options: mod_a::d: merge: hash mod_a::g: merge: deep YAML let(:mod_a_defaults2) { <<-YAML.unindent } mod_a::e: a: b: value mod_a::e.a.b (from module defaults) mod_a::g: a: b: value mod_a::g.a.b (from module defaults) YAML let(:mod_a_files) do { 'mod_a' => { 'data' => { 'common.yaml' => mod_a_common, 'common2.yaml' => mod_a_common2, 'defaults.yaml' => mod_a_defaults, 'defaults2.yaml' => mod_a_defaults2 }, 'hiera.yaml' => mod_a_hiera_yaml } } end it 'the default hierarchy does not interfere with environment hierarchy' do expect(lookup('mod_a::a')).to eql('value mod_a::a (from environment)') end it 'the default hierarchy does not interfere with regular hierarchy in module' do expect(lookup('mod_a::b')).to eql('value mod_a::b (from module)') end it 'the default hierarchy is consulted when no value is found elsewhere' do expect(lookup('mod_a::c')).to eql('value mod_a::c (from module defaults)') end it 'the default hierarchy does not participate in a merge' do expect(lookup('mod_a::d', 'merge' => 'hash')).to eql('a' => 'value mod_a::d.a (from module)', 'c' => 'value mod_a::d.c (from module)') end it 'lookup_options from regular hierarchy does not effect values found in the default hierarchy' do expect(lookup('mod_a::e')).to eql('a' => { 'a' => 'value mod_a::e.a.a (from module defaults)' }) end it 'lookup_options from default hierarchy affects values found in the default hierarchy' do expect(lookup('mod_a::g')).to eql('a' => { 'a' => 'value mod_a::g.a.a (from module defaults)', 'b' => 'value mod_a::g.a.b (from module defaults)'}) end it 'merge parameter does not override lookup_options defined in the default hierarchy' do expect(lookup('mod_a::g', 'merge' => 'hash')).to eql( 'a' => { 'a' => 'value mod_a::g.a.a (from module defaults)', 'b' => 'value mod_a::g.a.b (from module defaults)'}) end it 'lookup_options from default hierarchy does not effect values found in the regular hierarchy' do expect(lookup('mod_a::d')).to eql('a' => 'value mod_a::d.a (from module)') end context "and conversion via convert_to" do it 'converts with a single data type value' do expect(lookup('mod_a::to_array1')).to eql(['h', 'e', 'l', 'l', 'o']) end it 'converts with an array of arguments to the convert_to call' do expect(lookup('mod_a::to_array2')).to eql(['hello']) end it 'converts an undef/nil value that has convert_to option' do expect(lookup('mod_a::undef_value')).to eql([nil]) end it 'errors if a convert_to lookup_option cannot be performed because value does not match type' do expect{lookup('mod_a::to_int')}.to raise_error(/The convert_to lookup_option for key 'mod_a::to_int' raised error.*The string 'bananas' cannot be converted to Integer/) end it 'errors if a convert_to lookup_option cannot be performed because type does not exist' do expect{lookup('mod_a::to_bad_type')}.to raise_error(/The convert_to lookup_option for key 'mod_a::to_bad_type' raised error.*Creation of new instance of type 'TypeReference\['ComicSans'\]' is not supported/) end it 'adds explanation that conversion took place with a type' do explanation = explain('mod_a::to_array1') expect(explanation).to include('Applying convert_to lookup_option with arguments [Array]') end it 'adds explanation that conversion took place with a type and arguments' do explanation = explain('mod_a::to_array2') expect(explanation).to include('Applying convert_to lookup_option with arguments [Array, true]') end end it 'the default hierarchy lookup is included in the explain output' do explanation = explain('mod_a::c') expect(explanation).to match(/Searching default_hierarchy of module "mod_a".+Original path: "defaults.yaml"/m) end end end context 'and an eyaml lookup_key function' do let(:private_key_name) { 'private_key.pkcs7.pem' } let(:public_key_name) { 'public_key.pkcs7.pem' } let(:private_key) do <<-PKCS7.unindent -----BEGIN RSA PRIVATE KEY----- MIIEogIBAAKCAQEAwHB3GvImq59em4LV9DMfP0Zjs21eW3Jd5I9fuY0jLJhIkH6f CR7tyOpYV6xUj+TF8giq9WLxZI7sourMJMWjEWhVjgUr5lqp1RLv4lwfDv3Wk4XC 2LUuqj1IAErUXKeRz8i3lUSZW1Pf4CaMpnIiPdWbz6f0KkaJSFi9bqexONBx4fKQ NlgZwm2/aYjjrYng788I0QhWDKUqsQOi5mZKlHNRsDlk7J3Afhsx/jTLrCX/G8+2 tPtLsHyRN39kluM5vYHbKXDsCG/a88Z2yUE2+r4Clp0FUKffiEDBPm0/H0sQ4Q1o EfQFDQRKaIkhpsm0nOnLYTy3/xJc5uqDNkLiawIDAQABAoIBAE98pNXOe8ab93oI mtNZYmjCbGAqprTjEoFb71A3SfYbmK2Gf65GxjUdBwx/tBYTiuekSOk+yzKcDoZk sZnmwKpqDByzaiSmAkxunANFxdZtZvpcX9UfUX0j/t+QCROUa5gF8j6HrUiZ5nkx sxr1PcuItekaGLJ1nDLz5JsWTQ+H4M+GXQw7/t96x8v8g9el4exTiAHGk6Fv16kD 017T02M9qTTmV3Ab/enDIBmKVD42Ta36K/wc4l1aoUQNiRbIGVh96Cgd1CFXLF3x CsaNbYT4SmRXaYqoj6MKq+QFEGxadFmJy48NoSd4joirIn2lUjHxJebw3lLbNLDR uvQnQ2ECgYEA/nD94wEMr6078uMv6nKxPpNGq7fihwSKf0G/PQDqrRmjUCewuW+k /iXMe1Y/y0PjFeNlSbUsUvKQ5xF7F/1AnpuPHIrn3cjGVLb71W+zen1m8SnhsW/f 7dPgtcb4SCvfhmLgoov+P34YcNfGi6qgPUu6319IqoB3BIi7PvfEomkCgYEAwZ4+ V0bMjFdDn2hnYzjTNcF2aUQ1jPvtuETizGwyCbbMLl9522lrjC2DrH41vvqX35ct CBJkhQFbtHM8Gnmozv0vxhI2jP+u14mzfePZsaXuYrEgWRj+BCsYUHodXryxnEWj yVrTNskab1B5jFm2SCJDmKcycBOYpRBLCMx6W7MCgYBA99z7/6KboOIzzKrJdGup jLV410UyMIikoccQ7pD9jhRTPS80yjsY4dHqlEVJw5XSWvPb9DTTITi6p44EvBep 6BKMuTMnQELUEr0O7KypVCfa4FTOl8BX28f+4kU3OGykxc6R8qkC0VGwTohV1UWB ITsgGhZV4uOA9uDI3T8KMQKBgEnQY2HwmuDSD/TA39GDA3qV8+ez2lqSXRGIKZLX mMf9SaBQQ+uzKA4799wWDbVuYeIbB07xfCL83pJP8FUDlqi6+7Celu9wNp7zX1ua Nw8z/ErhzjxJe+Xo7A8aTwIkG+5A2m1UU/up9YsEeiJYvVaIwY58B42U2vfq20BS fD9jAoGAX2MscBzIsmN+U9R0ptL4SXcPiVnOl8mqvQWr1B4OLgxX7ghht5Fs956W bHipxOWMFCPJA/AhNB8q1DvYiD1viZbIALSCJVUkzs4AEFIjiPsCBKxerl7jF6Xp 1WYSaCmfvoCVEpFNt8cKp4Gq+zEBYAV4Q6TkcD2lDtEW49MuN8A= -----END RSA PRIVATE KEY----- PKCS7 end let(:public_key) do <<-PKCS7.unindent -----BEGIN CERTIFICATE----- MIIC2TCCAcGgAwIBAgIBATANBgkqhkiG9w0BAQUFADAAMCAXDTE3MDExMzA5MTY1 MloYDzIwNjcwMTAxMDkxNjUyWjAAMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB CgKCAQEAwHB3GvImq59em4LV9DMfP0Zjs21eW3Jd5I9fuY0jLJhIkH6fCR7tyOpY V6xUj+TF8giq9WLxZI7sourMJMWjEWhVjgUr5lqp1RLv4lwfDv3Wk4XC2LUuqj1I AErUXKeRz8i3lUSZW1Pf4CaMpnIiPdWbz6f0KkaJSFi9bqexONBx4fKQNlgZwm2/ aYjjrYng788I0QhWDKUqsQOi5mZKlHNRsDlk7J3Afhsx/jTLrCX/G8+2tPtLsHyR N39kluM5vYHbKXDsCG/a88Z2yUE2+r4Clp0FUKffiEDBPm0/H0sQ4Q1oEfQFDQRK aIkhpsm0nOnLYTy3/xJc5uqDNkLiawIDAQABo1wwWjAPBgNVHRMBAf8EBTADAQH/ MB0GA1UdDgQWBBSejWrVnw7QaBjNFCHMNFi+doSOcTAoBgNVHSMEITAfgBSejWrV nw7QaBjNFCHMNFi+doSOcaEEpAIwAIIBATANBgkqhkiG9w0BAQUFAAOCAQEAAe85 BQ1ydAHFqo0ib38VRPOwf5xPHGbYGhvQi4/sU6aTuR7pxaOJPYz05jLhS+utEmy1 sknBq60G67yhQE7IHcfwrl1arirG2WmKGvAbjeYL2K1UiU0pVD3D+Klkv/pK6jIQ eOJRGb3qNUn0Sq9EoYIOXiGXQ641F0bZZ0+5H92kT1lmnF5oLfCb84ImD9T3snH6 pIr5RKRx/0YmJIcv3WdpoPT903rOJiRIEgIj/hDk9QZTBpm222Ul5yQQ5pBywpSp xh0bmJKAQWhQm7QlybKfyaQmg5ot1jEzWAvD2I5FjHQxmAlchjb6RreaRhExj+JE 5O117dMBdzDBjcNMOA== -----END CERTIFICATE----- PKCS7 end let(:keys_dir) do keys = tmpdir('keys') dir_contained_in(keys, { private_key_name => private_key, public_key_name => public_key }) keys end let(:private_key_path) { File.join(keys_dir, private_key_name) } let(:public_key_path) { File.join(keys_dir, public_key_name) } let(:env_hiera_yaml) do <<-YAML.unindent version: 5 hierarchy: - name: EYaml path: common.eyaml lookup_key: eyaml_lookup_key options: pkcs7_private_key: #{private_key_path} pkcs7_public_key: #{public_key_path} YAML end let(:scope_additions) { { 'ipl_suffix' => 'aa' } } let(:data_files) do { 'common.eyaml' => <<-YAML.unindent # a: Encrypted value 'a' (from environment) a: > ENC[PKCS7,MIIBmQYJKoZIhvcNAQcDoIIBijCCAYYCAQAxggEhMIIBHQIBADAFMAACAQEw DQYJKoZIhvcNAQEBBQAEggEAUwwNRA5ZKM87SLnjnJfzDFRQbeheSYMTOhcr sgTPCGtzEAzvRBrkdIRAvDZVRfadV9OB+bJsYrhWIkU1bYiOn1m78ginh96M 44RuspnIZYnL9Dhs+JyC8VvB5nlvlEph2RGt+KYg9iU4JYhwZ2+8+yxB6/UK H5HGKDCjBbEc8o9MbCckLsciIh11hKKgT6K0yhKB/nBxxM78nrX0BxmAHX2u bejKDRa9S/0uS7Y91nvnbIkaQpZ4KteSQ+J4/lQBMlMAeE+2F9ncM8jFKnQC rzzdbn1O/zwsEt5J5CRP1Sc+8hM644+IqkLs+17segxArHVGOsEqyDcHbXEK 9jspfzBcBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBCIq/L5HeJgA9XQm67j JHUngDDS5s52FsuSIMin7Z/pV+XuaJGFkL80ia4bXnCWilmtM8oUa/DZuBje dCILO7I8QqU=] hash_a: "hash_%{ipl_suffix}": # aaa: Encrypted value hash_a.hash_aa.aaa (from environment) aaa: > ENC[PKCS7,MIIBqQYJKoZIhvcNAQcDoIIBmjCCAZYCAQAxggEhMIIBHQIBADAFMAACAQEw DQYJKoZIhvcNAQEBBQAEggEAhvGXL5RxVUs9wdqJvpCyXtfCHrm2HbG/u30L n8EuRD9ravlsgIISAnd27JPtrxA+0rZq4EQRGz6OcovnH9vTg86/lVBhhPnz b83ArptGJhRvTYUJ19GZI3AYjJbhWj/Jo5NL56oQJaPBccqHxMApm/U0wlus QtASL94cLuh4toVIBQCQzD5/Bx51p2wQobm9p4WKSl1zJhDceurmoLZXqhuN JwwEBwXopJvgid3ZDPbdX8nI6vHhb/8wDq9yb5DOsrkgqDqQgwPU9sUUioQj Hr1pGyeOWnbEe99iEb2+m7TWsC0NN7OBo06mAgFNbBLjvn2k4PiCxrOOgJ8S LI5eXjBsBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBCWNS6j3m/Xvrp5RFaN ovm/gEB4oPlYJswoXuWqcEBfwZzbpy96x3b2Le/yoa72ylbPAUc5GfLENvFQ zXpTtSmQE0fixY4JMaBTke65ZRvoiOQO] array_a: # - "array_a[0]" - > ENC[PKCS7,MIIBeQYJKoZIhvcNAQcDoIIBajCCAWYCAQAxggEhMIIBHQIBADAFMAACAQEw DQYJKoZIhvcNAQEBBQAEggEAmXZfyfU77vVCZqHpR10qhD0Jy9DpMGBgal97 vUO2VHX7KlCgagK0kz8/uLRIthkYzcpn8ISmw/+CIAny3jOjxOsylJiujqyu hx/JEFl8bOKOg40Bd0UuBaw/qZ+CoAtOorhiIW7x6t7DpknItC6gkH/cSJ4/ p3MdhoARRuwj2fvuaChVsD39l2rXjgJj0OJOaDXdbuisG75VRZf5l8IH6+44 Q7m6W7BU69LX+ozn+W3filQoiJ5MPf8w/KXAObMSbKYIDsrZUyIWyyNUbpW0 MieIkHj93bX3gIEcenECLdWaEzcPa7MHgl6zevQKg4H0JVmcvKYyfHYqcrVE PqizKDA8BgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBDf259KZEay1widVSFy I9zGgBAICjm0x2GeqoCnHdiAA+jt] # - "array_a[1]" - > ENC[PKCS7,MIIBeQYJKoZIhvcNAQcDoIIBajCCAWYCAQAxggEhMIIBHQIBADAFMAACAQEw DQYJKoZIhvcNAQEBBQAEggEATVy4hHG356INFKOswAhoravh66iJljp+Vn3o UVD1kyRiqY5tz3UVSptzUmzD+YssX/f73AKCjUI3HrPNL7kAxsk6fWS7nDEj AuxtCqGYeBha6oYJYziSGIHfAdY3MiJUI1C9g/OQB4TTvKdrlDArPiY8THJi bzLLMbVQYJ6ixSldwkdKD75vtikyamx+1LSyVBSg8maVyPvLHtLZJuT71rln WON3Ious9PIbd+izbcCzaoqh5UnTfDCjOuAYliXalBxamIIwNzSV1sdR8/qf t22zpYK4J8lgCBV2gKfrOWSi9MAs6JhCeOb8wNLMmAUTbc0WrFJxoCwAPX0z MAjsNjA8BgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBC4v4bNE4gFlbLmVY+9 BtSLgBBm7U0wu6d6s9wF9Ek9IHPe] # ref_a: "A resolved = '%{hiera('a')}'" ref_a: > ENC[PKCS7,MIIBiQYJKoZIhvcNAQcDoIIBejCCAXYCAQAxggEhMIIBHQIBADAFMAACAQEw DQYJKoZIhvcNAQEBBQAEggEAFSuUp+yk+oaA7b5ekT0u360CQ9Q2sIQ/bTcM jT3XLjm8HIGYPcysOEnuo8WcAxJFY5iya4yQ7Y/UhMWXaTi7Vzv/6BmyPDwz +7Z2Mf0r0PvS5+ylue6aem/3bXPOmXTKTf68OCehTRXlDUs8/av9gnsDzojp yiUTBZvKxhIP2n//GyoHgyATveHT0lxPVpdMycB347DtWS7IduCxx0+KiOOw DXYFlYbIVxVInwgERxtsfYSr+Fu0/mkjtRsQm+dPzMQOATE9Val2gGKsV6bi kdm1OM9HrwVsFj6Lma6FYmr89Bcm/1uEc8fiOMtNK3z2+nwunWBMNCGneMYD C5IJejBMBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBAeiZDGQyXHkZlV5ceT iCxpgCDDatuVvbPEEi8rKOC7xhPHZ22zLEEV//l7C9jxq+DZcA==] YAML } end let(:env_data) { data_files } context 'and a module using eyaml with different options' do let(:private_module_key) do <<-PKCS7.unindent -----BEGIN RSA PRIVATE KEY----- MIIEogIBAAKCAQEAuqVpctipK4OMWM+RwKcd/mR4pg6qE3+ItPVC9TlvBrmDaN/y YZRjQR+XovXSGuy/CneSQ9Qss0Ff3FKAmEeH0qN0V47a81hgLpjhLCX1n+Ov7r1Q DC1ciTpVzHE4krN3rJ/RmDohitIqT1IYYhdcEdaMG9E26HIzn1QIwaDiYU3mfqWM 8CZExa0CeIsEzHRLSxuMi/xX0ENImCRUzY9GH88Cu2gUhpKlbVzJmVqGPgp94pJY YM+SUb0XP1yRySpJMnVg98oCUrQO2OoE/Gax/djAi6hrJUzejPsEKdZ1yxM6OyJW NjWZYs8izAxBqm7pv1hx5+X7AIPqwZTMVrB7TQIDAQABAoIBAHIex13QOYeAlGSM 7bpUtBMiTV6DItxvIyA5wen8ZvU+oqmSHDorp5BfB7E9Cm0qChkVSRot9fLYawtk anoxakuRY4ZRs3AMvipfkXYT854CckTP/cykQ6soPuOU6plQIEEtKtMf3/hoTjRX ps77J3FEtEAh6Kexg/zMPdpeS2xgULhk0P9ZQEg+JhLA5dq0p0nz3SBkuzcxei79 +Za/Tg1osD0AINOajdvPnKxvlmWJN0+LpGwVjFNhkoUXPeDyvq0z2V/Uqwz4HP2I UGv4tz3SbzFc3Ie4lzgUZzCQgUK3u60pq1uyA6BRtxwdEmpn5v++jGXBGJZpWwcW UNblESUCgYEA4aTH9+LHsNjLPs2FmSc7hNjwHG1rAHcDXTX2ccySjRcQvH4Z7xBL di+SzZ2Tf8gSLycPRgRVCbrgCODpjoV2D5wWnyUHfWm4+GQxHURYa4UDx69tsSKE OTRASJo7/Mz0M1a6YzgCzVRM/TO676ucmawzKUY5OUm1oehtODAiZOcCgYEA08GM AMBOznys02xREJI5nLR6AveuTbIjF2efEidoxoW+1RrMOkcqaDTrJQ5PLM+oDDwD iPzVjnroSbwJzFB71atIg7b7TwltgkXy7wNTedO2cm5u/I0q8tY2Jaa4Mz8JUnbe yafvtS0/mY6A5k+8/2UIMFin2rqU9NC9EUPIo6sCgYBhOvAwELibq89osIbxB8bN 5+0PUtbYzG/WqnoXb193DIlZr7zdFththPJtR4lXdo7fYqViNluuZahEKyZ5E2lc MJZO3VXs5LGf1wyS3/B55EdMtHs/6O+w9qL8pflTZb2UobqPJoOOltTWBoR24iwI y/r/vhLKbMini9AEdjlb4QKBgGdYsax4Lr4GCQ8ScSnmQ6ngRyAFo5MV2pyEnRTu GOuywKUe9AeJTgAXu5+VMT0Mh9aYv5zu0Ic+IvpBhIKr0RRCCR0Hg/VaA5Et9FeE RwxRMFz+2rn1Z72moDyV9pZEMJeHnknK5WmGEOEvtGczCWmX9Hwr+Jf+sc4dxfiU HWsLAoGAXWSX73p/6R4eRfF5zU2UFJPvDzhmwObAuvU4zKs9x7PMxZfvyt/eBCO1 fj2+hIR72RxVuHbLApF1BT6gPVLtNdvaNuCs8YlHcnx/Oi088F0ni7fL/xYBUvaB 7wTf188UJxP1ofVMZW00P4I9mR6BrOulv455gCwsmg2X7WtJU48= -----END RSA PRIVATE KEY----- PKCS7 end let(:public_module_key) do <<-PKCS7.unindent -----BEGIN CERTIFICATE----- MIIC2TCCAcGgAwIBAgIBATANBgkqhkiG9w0BAQUFADAAMCAXDTE3MDUzMTE2Mjc0 M1oYDzIwNjcwNTE5MTYyNzQzWjAAMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB CgKCAQEAuqVpctipK4OMWM+RwKcd/mR4pg6qE3+ItPVC9TlvBrmDaN/yYZRjQR+X ovXSGuy/CneSQ9Qss0Ff3FKAmEeH0qN0V47a81hgLpjhLCX1n+Ov7r1QDC1ciTpV zHE4krN3rJ/RmDohitIqT1IYYhdcEdaMG9E26HIzn1QIwaDiYU3mfqWM8CZExa0C eIsEzHRLSxuMi/xX0ENImCRUzY9GH88Cu2gUhpKlbVzJmVqGPgp94pJYYM+SUb0X P1yRySpJMnVg98oCUrQO2OoE/Gax/djAi6hrJUzejPsEKdZ1yxM6OyJWNjWZYs8i zAxBqm7pv1hx5+X7AIPqwZTMVrB7TQIDAQABo1wwWjAPBgNVHRMBAf8EBTADAQH/ MB0GA1UdDgQWBBQkhoMgOyPzEe7tOOimNH2//PYF2TAoBgNVHSMEITAfgBQkhoMg OyPzEe7tOOimNH2//PYF2aEEpAIwAIIBATANBgkqhkiG9w0BAQUFAAOCAQEAhRWc Nz3PcUJllao5G/v4AyvjLgwB2JgjJgh6D3ILoOe9TrDSXD7ZV3F30vFae+Eztk86 pmM8x57E0HsuuY+Owf6/hvELtwbzf9N/lc9ySZSogGFoQeJ8rnCJAQ0FaPjqb7AN xTaY9HTzr4dZG1f+sw32RUu2fDe7Deqgf85uMSZ1mtRTt9zvo8lMQxVA2nVOfwz2 Nxf+qSNYSCtf0/6iwfzHy0qPjaJnywgBCi3Lg2IMSqGUatxzH+9HWrBgD+ZYxmDz 2gW+EIU1Y/We/tbjIWaR1PD+IzeRJi5fHq60RKHPSdp7TGtV48bQRvyZXC7sVCRa yxfX1IGYhCDzbFRQNg== -----END CERTIFICATE----- PKCS7 end let(:module_keys_dir) do keys = tmpdir('keys') dir_contained_in(keys, { private_key_name => private_module_key, public_key_name => public_module_key }) keys end let(:private_module_key_path) { File.join(module_keys_dir, private_key_name) } let(:public_module_key_path) { File.join(module_keys_dir, public_key_name) } let(:mod_a_files) do { 'mod_a' => { 'hiera.yaml' => <<-YAML.unindent, version: 5 hierarchy: - name: EYaml path: common.eyaml lookup_key: eyaml_lookup_key options: pkcs7_private_key: #{private_module_key_path} pkcs7_public_key: #{public_module_key_path} YAML 'data' => { 'common.eyaml' => <<-YAML.unindent --- # "%{lookup('a')} (from module)" mod_a::a: > ENC[PKCS7,MIIBiQYJKoZIhvcNAQcDoIIBejCCAXYCAQAxggEhMIIBHQIBADAFMAACAQEw DQYJKoZIhvcNAQEBBQAEggEAC+lvda8mX6XkgCBstNw4IQUDyFcS6M0mS9gZ ev4VBDeUK4AUNVnzzdbW0Mnj9LbqlpzFx96VGqSxsRBpe7BVD0kVo5jQsEMn nbrWOD1lvXYrXZMXBeD9xJbMbH5EiiFhbaXcEKRAVGaLVQKjXDENDQ/On+it 1+wmmVwJynDJR0lsCz6dcSKvw6wnxBcv32qFyePvJuIf04CHMhaS4ykedYHK vagUn5uVXOv/8G0JPlZnQLyxjE0v0heb0Zj0mvcP2+Y5BSW50AQVrMWJNtdW aFEg6H5hpjduQfQh3iWVuDLnWhbP0sY2Grn5dTOxQP8aTDSsiTUcSeIAmjr/ K8YRCjBMBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBAjL7InlBjRuohLLcBx 686ogCDkhCan8bCE7aX2nr75QtLF3q89pFIR4/NGl5+oGEO+qQ==] YAML } } } end let(:populated_env_dir) do dir_contained_in(env_dir, DeepMerge.deep_merge!(environment_files, env_name => { 'modules' => mod_a_files })) env_dir end it 'repeatedly finds data in environment and module' do expect(lookup(['array_a', 'mod_a::a', 'hash_a'])).to eql([ ['array_a[0]', 'array_a[1]'], "Encrypted value 'a' (from environment) (from module)", {'hash_aa'=>{'aaa'=>'Encrypted value hash_a.hash_aa.aaa (from environment)'}}]) end end it 'finds data in the environment' do expect(lookup('a')).to eql("Encrypted value 'a' (from environment)") end it 'evaluates interpolated keys' do expect(lookup('hash_a')).to include('hash_aa') end it 'evaluates interpolations in encrypted values' do expect(lookup('ref_a')).to eql("A resolved = 'Encrypted value 'a' (from environment)'") end it 'can read encrypted values inside a hash' do expect(lookup('hash_a.hash_aa.aaa')).to eql('Encrypted value hash_a.hash_aa.aaa (from environment)') end it 'can read encrypted values inside an array' do expect(lookup('array_a')).to eql(['array_a[0]', 'array_a[1]']) end context 'declared in global scope as a Hiera v3 backend' do let(:environment_files) { {} } let(:data_file_content) { <<-YAML.unindent } a: > ENC[PKCS7,MIIBmQYJKoZIhvcNAQcDoIIBijCCAYYCAQAxggEhMIIBHQIBADAFMAACAQEw DQYJKoZIhvcNAQEBBQAEggEAH457bsfL8kYw9O50roE3dcE21nCnmPnQ2XSX LYRJ2C78LarbfFonKz0gvDW7tyhsLWASFCFaiU8T1QPBd2b3hoQK8E4B2Ual xga/K7r9y3OSgRomTm9tpTltC6re0Ubh3Dy71H61obwxEdNVTqjPe95+m2b8 6zWZVnzZzXXsTG1S17yJn1zaB/LXHbWNy4KyLLKCGAml+Gfl6ZMjmaplTmUA QIC5rI8abzbPP3TDMmbLOGNkrmLqI+3uS8tSueTMoJmWaMF6c+H/cA7oRxmV QCeEUVXjyFvCHcmbA+keS/RK9XF+vc07/XS4XkYSPs/I5hLQji1y9bkkGAs0 tehxQjBcBgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBDHpA6Fcl/R16aIYcow oiO4gDAvfFH6jLUwXkcYtagnwdmhkd9TQJtxNWcIwMpvmk036MqIoGwwhQdg gV4beiCFtLU=] YAML let(:hiera_yaml) do <<-YAML.unindent :backends: eyaml :eyaml: :datadir: #{code_dir}/hieradata :pkcs7_private_key: #{private_key_path} :pkcs7_public_key: #{public_key_path} :hierarchy: - common YAML end let(:data_files) do { 'common.eyaml' => data_file_content } end let(:code_dir_files) do { 'hiera.yaml' => hiera_yaml, 'hieradata' => data_files } end before(:each) do Puppet.settings[:hiera_config] = File.join(code_dir, 'hiera.yaml') end it 'finds data in the global layer' do expect(lookup('a')).to eql("Encrypted value 'a' (from global)") end it 'delegates configured eyaml backend to eyaml_lookup_key function' do expect(explain('a')).to match(/Hierarchy entry "eyaml"\n.*\n.*\n.*"common"\n\s*Found key: "a"/m) end context 'using intepolated paths to the key pair' do let(:scope_additions) { { 'priv_path' => private_key_path, 'pub_path' => public_key_path } } let(:hiera_yaml) do <<-YAML.unindent :backends: eyaml :eyaml: :datadir: #{code_dir}/hieradata :pkcs7_private_key: "%{priv_path}" :pkcs7_public_key: "%{pub_path}" :hierarchy: - common YAML end it 'finds data in the global layer' do expect(lookup('a')).to eql("Encrypted value 'a' (from global)") end end context 'with special extension declared in options' do let(:environment_files) { {} } let(:hiera_yaml) do <<-YAML.unindent :backends: eyaml :eyaml: :extension: xyaml :datadir: #{code_dir}/hieradata :pkcs7_private_key: #{private_key_path} :pkcs7_public_key: #{public_key_path} :hierarchy: - common YAML end let(:data_files) do { 'common.xyaml' => data_file_content } end it 'finds data in the global layer' do expect(lookup('a')).to eql("Encrypted value 'a' (from global)") end it 'delegates configured eyaml backend to eyaml_lookup_key function' do expect(explain('a')).to match(/Hierarchy entry "eyaml"\n.*\n.*\n.*"common"\n\s*Found key: "a"/m) end end end end end end puppet-5.5.10/spec/unit/functions/match_spec.rb0000644005276200011600000000466113417161722021376 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' require 'puppet/loaders' describe 'the match function' do before(:all) do loaders = Puppet::Pops::Loaders.new(Puppet::Node::Environment.create(:testing, [])) Puppet.push_context({:loaders => loaders}, "test-examples") end after(:all) do Puppet::Pops::Loaders.clear Puppet::pop_context() end let(:func) do Puppet.lookup(:loaders).puppet_system_loader.load(:function, 'match') end let(:type_parser) { Puppet::Pops::Types::TypeParser.singleton } it 'matches string and regular expression without captures' do expect(func.call({}, 'abc123', /[a-z]+[1-9]+/)).to eql(['abc123']) end it 'matches string and regular expression with captures' do expect(func.call({}, 'abc123', /([a-z]+)([1-9]+)/)).to eql(['abc123', 'abc', '123']) end it 'produces nil if match is not found' do expect(func.call({}, 'abc123', /([x]+)([6]+)/)).to be_nil end [ 'Pattern[/([a-z]+)([1-9]+)/]', # regexp 'Pattern["([a-z]+)([1-9]+)"]', # string 'Regexp[/([a-z]+)([1-9]+)/]', # regexp type 'Pattern[/x9/, /([a-z]+)([1-9]+)/]', # regexp, first found matches ].each do |pattern| it "matches string and type #{pattern} with captures" do expect(func.call({}, 'abc123', type(pattern))).to eql(['abc123', 'abc', '123']) end it "matches string with an alias type for #{pattern} with captures" do expect(func.call({}, 'abc123', alias_type("MyAlias", type(pattern)))).to eql(['abc123', 'abc', '123']) end it "matches string with a matching variant type for #{pattern} with captures" do expect(func.call({}, 'abc123', variant_type(type(pattern)))).to eql(['abc123', 'abc', '123']) end end it 'matches an array of strings and yields a map of the result' do expect(func.call({}, ['abc123', '2a', 'xyz2'], /([a-z]+)[1-9]+/)).to eql([['abc123', 'abc'], nil, ['xyz2', 'xyz']]) end it 'raises error if Regexp type without regexp is used' do expect{func.call({}, 'abc123', type('Regexp'))}.to raise_error(ArgumentError, /Given Regexp Type has no regular expression/) end def variant_type(*t) Puppet::Pops::Types::PVariantType.new(t) end def alias_type(name, t) # Create an alias using a nil AST (which is never used because it is given a type as resolution) Puppet::Pops::Types::PTypeAliasType.new(name, nil, t) end def type(s) Puppet::Pops::Types::TypeParser.singleton.parse(s) end end puppet-5.5.10/spec/unit/functions/regsubst_spec.rb0000644005276200011600000001046013417161722022132 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' require 'puppet/loaders' describe 'the regsubst function' do before(:all) do loaders = Puppet::Pops::Loaders.new(Puppet::Node::Environment.create(:testing, [])) Puppet.push_context({:loaders => loaders}, "test-examples") end after(:all) do Puppet::Pops::Loaders.clear Puppet::pop_context() end def regsubst(*args) Puppet.lookup(:loaders).puppet_system_loader.load(:function, 'regsubst').call({}, *args) end let(:type_parser) { Puppet::Pops::Types::TypeParser.singleton } context 'when using a string pattern' do it 'should raise an Error if there is less than 3 arguments' do expect { regsubst('foo', 'bar') }.to raise_error(/expects between 3 and 5 arguments, got 2/) end it 'should raise an Error if there is more than 5 arguments' do expect { regsubst('foo', 'bar', 'gazonk', 'G', 'U', 'y') }.to raise_error(/expects between 3 and 5 arguments, got 6/) end it 'should raise an Error if given a bad flag' do expect { regsubst('foo', 'bar', 'gazonk', 'X') }.to raise_error(/parameter 'flags' expects an undef value or a match for Pattern\[\/\^\[GEIM\]\*\$\/\], got 'X'/) end it 'should raise an Error if given a bad encoding' do expect { regsubst('foo', 'bar', 'gazonk', nil, 'X') }.to raise_error(/parameter 'encoding' expects a match for Enum\['E', 'N', 'S', 'U'\], got 'X'/) end it 'should raise an Error if given a bad regular expression' do expect { regsubst('foo', '(', 'gazonk') }.to raise_error(/pattern with unmatched parenthesis/) end it 'should handle case insensitive flag' do expect(regsubst('the monkey breaks baNAna trees', 'b[an]+a', 'coconut', 'I')).to eql('the monkey breaks coconut trees') end it 'should allow hash as replacement' do expect(regsubst('tuto', '[uo]', { 'u' => 'o', 'o' => 'u' }, 'G')).to eql('totu') end end context 'when using a regexp pattern' do it 'should raise an Error if there is less than 3 arguments' do expect { regsubst('foo', /bar/) }.to raise_error(/expects between 3 and 5 arguments, got 2/) end it 'should raise an Error if there is more than 5 arguments' do expect { regsubst('foo', /bar/, 'gazonk', 'G', 'E', 'y') }.to raise_error(/expects between 3 and 5 arguments, got 6/) end it 'should raise an Error if given a flag other thant G' do expect { regsubst('foo', /bar/, 'gazonk', 'I') }.to raise_error(/expects one of/) end it 'should handle global substitutions' do expect(regsubst("the monkey breaks\tbanana trees", /[ \t]/, '--', 'G')).to eql('the--monkey--breaks--banana--trees') end it 'should accept Type[Regexp]' do expect(regsubst('abc', type_parser.parse("Regexp['b']"), '_')).to eql('a_c') end it 'should treat Regexp as Regexp[//]' do expect(regsubst('abc', type_parser.parse("Regexp"), '_', 'G')).to eql('_a_b_c_') end it 'should allow hash as replacement' do expect(regsubst('tuto', /[uo]/, { 'u' => 'o', 'o' => 'u' }, 'G')).to eql('totu') end end context 'when using an array target' do it 'should perform substitutions in all elements and return array when using regexp pattern' do expect(regsubst(['a#a', 'b#b', 'c#c'], /#/, '_')).to eql(['a_a', 'b_b', 'c_c']) end it 'should perform substitutions in all elements when using string pattern' do expect(regsubst(['a#a', 'b#b', 'c#c'], '#', '_')).to eql(['a_a', 'b_b', 'c_c']) end it 'should perform substitutions in all elements when using Type[Regexp] pattern' do expect(regsubst(['a#a', 'b#b', 'c#c'], type_parser.parse('Regexp[/#/]'), '_')).to eql(['a_a', 'b_b', 'c_c']) end it 'should handle global substitutions with groups on all elements' do expect(regsubst( ['130.236.254.10', 'foo.example.com', 'coconut', '10.20.30.40'], /([^.]+)/, '<\1>', 'G') ).to eql(['<130>.<236>.<254>.<10>', '..','', '<10>.<20>.<30>.<40>']) end it 'should return an empty array if given an empty array and string pattern' do expect(regsubst([], '', '')).to eql([]) end it 'should return an empty array if given an empty array and regexp pattern' do expect(regsubst([], //, '')).to eql([]) end end end puppet-5.5.10/spec/unit/functions/split_spec.rb0000644005276200011600000000322513417161722021430 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' require 'puppet/loaders' describe 'the split function' do before(:all) do loaders = Puppet::Pops::Loaders.new(Puppet::Node::Environment.create(:testing, [])) Puppet.push_context({:loaders => loaders}, "test-examples") end after(:all) do Puppet::Pops::Loaders.clear Puppet::pop_context() end def split(*args) Puppet.lookup(:loaders).puppet_system_loader.load(:function, 'split').call({}, *args) end let(:type_parser) { Puppet::Pops::Types::TypeParser.singleton } it 'should raise an Error if there is less than 2 arguments' do expect { split('a,b') }.to raise_error(/'split' expects 2 arguments, got 1/) end it 'should raise an Error if there is more than 2 arguments' do expect { split('a,b','foo', 'bar') }.to raise_error(/'split' expects 2 arguments, got 3/) end it 'should raise a RegexpError if the regexp is malformed' do expect { split('a,b',')') }.to raise_error(/unmatched close parenthesis/) end it 'should handle pattern in string form' do expect(split('a,b',',')).to eql(['a', 'b']) end it 'should handle pattern in Regexp form' do expect(split('a,b',/,/)).to eql(['a', 'b']) end it 'should handle pattern in Regexp Type form' do expect(split('a,b',type_parser.parse('Regexp[/,/]'))).to eql(['a', 'b']) end it 'should handle pattern in Regexp Type form with empty regular expression' do expect(split('ab',type_parser.parse('Regexp[//]'))).to eql(['a', 'b']) end it 'should handle pattern in Regexp Type form with missing regular expression' do expect(split('ab',type_parser.parse('Regexp'))).to eql(['a', 'b']) end end puppet-5.5.10/spec/unit/functions/versioncmp_spec.rb0000644005276200011600000000207713417161722022466 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' require 'puppet/loaders' describe "the versioncmp function" do before(:all) do loaders = Puppet::Pops::Loaders.new(Puppet::Node::Environment.create(:testing, [])) Puppet.push_context({:loaders => loaders}, "test-examples") end after(:all) do Puppet::Pops::Loaders.clear Puppet::pop_context() end def versioncmp(*args) Puppet.lookup(:loaders).puppet_system_loader.load(:function, 'versioncmp').call({}, *args) end let(:type_parser) { Puppet::Pops::Types::TypeParser.singleton } it 'should raise an Error if there is less than 2 arguments' do expect { versioncmp('a,b') }.to raise_error(/expects 2 arguments, got 1/) end it 'should raise an Error if there is more than 2 arguments' do expect { versioncmp('a,b','foo', 'bar') }.to raise_error(/expects 2 arguments, got 3/) end it "should call Puppet::Util::Package.versioncmp (included in scope)" do Puppet::Util::Package.expects(:versioncmp).with('1.2', '1.3').returns(-1) expect(versioncmp('1.2', '1.3')).to eq(-1) end end puppet-5.5.10/spec/unit/gettext/0000755005276200011600000000000013417162176016414 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/gettext/config_spec.rb0000644005276200011600000001236013417161721021215 0ustar jenkinsjenkinsrequire 'puppet/gettext/config' require 'spec_helper' describe Puppet::GettextConfig do require 'puppet_spec/files' include PuppetSpec::Files include Puppet::GettextConfig let(:local_path) do local_path ||= Puppet::GettextConfig::LOCAL_PATH end let(:windows_path) do windows_path ||= Puppet::GettextConfig::WINDOWS_PATH end let(:posix_path) do windows_path ||= Puppet::GettextConfig::POSIX_PATH end before(:each) do Puppet::GettextConfig.stubs(:gettext_loaded?).returns true end after(:each) do Puppet::GettextConfig.set_locale('en') Puppet::GettextConfig.delete_all_text_domains end describe 'setting and getting the locale' do it 'should return "en" when gettext is unavailable' do Puppet::GettextConfig.stubs(:gettext_loaded?).returns(false) expect(Puppet::GettextConfig.current_locale).to eq('en') end it 'should allow the locale to be set' do Puppet::GettextConfig.set_locale('hu') expect(Puppet::GettextConfig.current_locale).to eq('hu') end end describe 'translation mode selection' do it 'should select PO mode when given a local config path' do expect(Puppet::GettextConfig.translation_mode(local_path)).to eq(:po) end it 'should select PO mode when given a non-package config path' do expect(Puppet::GettextConfig.translation_mode('../fake/path')).to eq(:po) end it 'should select MO mode when given a Windows package config path' do expect(Puppet::GettextConfig.translation_mode(windows_path)).to eq(:mo) end it 'should select MO mode when given a POSIX package config path' do expect(Puppet::GettextConfig.translation_mode(posix_path)).to eq(:mo) end end describe 'loading translations' do context 'when given a nil locale path' do it 'should return false' do expect(Puppet::GettextConfig.load_translations('puppet', nil, :po)).to be false end end context 'when given a valid locale file location' do it 'should return true' do Puppet::GettextConfig.expects(:add_repository_to_domain).with('puppet', local_path, :po, anything) expect(Puppet::GettextConfig.load_translations('puppet', local_path, :po)).to be true end end context 'when given a bad file format' do it 'should raise an exception' do expect { Puppet::GettextConfig.load_translations('puppet', local_path, :bad_format) }.to raise_error(Puppet::Error) end end end describe "setting up text domains" do it 'can create the default text domain after another is set' do Puppet::GettextConfig.delete_all_text_domains FastGettext.text_domain = 'other' Puppet::GettextConfig.create_default_text_domain end it 'should add puppet translations to the default text domain' do Puppet::GettextConfig.expects(:load_translations).with('puppet', local_path, :po, Puppet::GettextConfig::DEFAULT_TEXT_DOMAIN).returns(true) Puppet::GettextConfig.create_default_text_domain expect(Puppet::GettextConfig.loaded_text_domains).to include(Puppet::GettextConfig::DEFAULT_TEXT_DOMAIN) end it 'should copy default translations when creating a non-default text domain' do Puppet::GettextConfig.reset_text_domain(:test) expect(Puppet::GettextConfig.loaded_text_domains).to include(Puppet::GettextConfig::DEFAULT_TEXT_DOMAIN, :test) end it 'should normalize domain name when creating a non-default text domain' do Puppet::GettextConfig.reset_text_domain('test') expect(Puppet::GettextConfig.loaded_text_domains).to include(Puppet::GettextConfig::DEFAULT_TEXT_DOMAIN, :test) end end describe "clearing the configured text domain" do it 'succeeds' do Puppet::GettextConfig.clear_text_domain expect(FastGettext.text_domain).to eq(FastGettext.default_text_domain) end it 'falls back to default' do Puppet::GettextConfig.reset_text_domain(:test) expect(FastGettext.text_domain).to eq(:test) Puppet::GettextConfig.clear_text_domain expect(FastGettext.text_domain).to eq(Puppet::GettextConfig::DEFAULT_TEXT_DOMAIN) end end describe "deleting text domains" do it 'can delete a text domain by name' do Puppet::GettextConfig.reset_text_domain(:test) expect(Puppet::GettextConfig.loaded_text_domains).to include(Puppet::GettextConfig::DEFAULT_TEXT_DOMAIN, :test) Puppet::GettextConfig.delete_text_domain(:test) expect(Puppet::GettextConfig.loaded_text_domains).to eq([Puppet::GettextConfig::DEFAULT_TEXT_DOMAIN]) end it 'can delete all non-default text domains' do Puppet::GettextConfig.reset_text_domain(:test) expect(Puppet::GettextConfig.loaded_text_domains).to include(Puppet::GettextConfig::DEFAULT_TEXT_DOMAIN, :test) Puppet::GettextConfig.delete_environment_text_domains expect(Puppet::GettextConfig.loaded_text_domains).to eq([Puppet::GettextConfig::DEFAULT_TEXT_DOMAIN]) end it 'can delete all text domains' do Puppet::GettextConfig.reset_text_domain(:test) expect(Puppet::GettextConfig.loaded_text_domains).to include(Puppet::GettextConfig::DEFAULT_TEXT_DOMAIN, :test) Puppet::GettextConfig.delete_all_text_domains expect(Puppet::GettextConfig.loaded_text_domains).to be_empty end end end puppet-5.5.10/spec/unit/gettext/module_loading_spec.rb0000644005276200011600000000306613417161721022735 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/modules' require 'puppet_spec/files' require 'puppet/gettext/module_translations' describe Puppet::ModuleTranslations do include PuppetSpec::Files describe "loading translations from the module path" do let(:modpath) { tmpdir('modpath') } let(:module_a) { PuppetSpec::Modules.create( "mod_a", modpath, :metadata => { :author => 'foo' }, :environment => mock("environment")) } let(:module_b) { PuppetSpec::Modules.create( "mod_b", modpath, :metadata => { :author => 'foo' }, :environment => mock("environment")) } it "should attempt to load translations only for modules that have them" do module_a.expects(:has_translations?).returns(false) module_b.expects(:has_translations?).returns(true) Puppet::GettextConfig.expects(:load_translations).with("foo-mod_b", File.join(modpath, "mod_b", "locales"), :po).returns(true) Puppet::ModuleTranslations.load_from_modulepath([module_a, module_b]) end end describe "loading translations from $vardir" do let(:vardir) { dir_containing("vardir", { "locales" => { "ja" => { "foo-mod_a.po" => "" } } }) } it "should attempt to load translations for the current locale" do Puppet::GettextConfig.expects(:current_locale).returns("ja") Puppet::GettextConfig.expects(:load_translations).with("foo-mod_a", File.join(vardir, "locales"), :po).returns(true) Puppet::ModuleTranslations.load_from_vardir(vardir) end end end puppet-5.5.10/spec/unit/graph/0000755005276200011600000000000013417162176016031 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/graph/key_spec.rb0000644005276200011600000000162413417161721020156 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/graph' describe Puppet::Graph::Key do it "produces the next in the sequence" do key = Puppet::Graph::Key.new expect(key.next).to be > key end it "produces a key after itself but before next" do key = Puppet::Graph::Key.new expect(key.down).to be > key expect(key.down).to be < key.next end it "downward keys of the same group are in sequence" do key = Puppet::Graph::Key.new first = key.down middle = key.down.next last = key.down.next.next expect(first).to be < middle expect(middle).to be < last expect(last).to be < key.next end it "downward keys in sequential groups are in sequence" do key = Puppet::Graph::Key.new first = key.down middle = key.next last = key.next.down expect(first).to be < middle expect(middle).to be < last expect(last).to be < key.next.next end end puppet-5.5.10/spec/unit/graph/rb_tree_map_spec.rb0000644005276200011600000003151413417161721021646 0ustar jenkinsjenkins#!/usr/bin/env ruby require 'spec_helper' require 'puppet/graph' describe Puppet::Graph::RbTreeMap do describe "#push" do it "should allow a new element to be added" do subject[5] = 'foo' expect(subject.size).to eq(1) expect(subject[5]).to eq('foo') end it "should replace the old value if the key is in tree already" do subject[0] = 10 subject[0] = 20 expect(subject[0]).to eq(20) expect(subject.size).to eq(1) end it "should be able to add a large number of elements" do (1..1000).each {|i| subject[i] = i.to_s} expect(subject.size).to eq(1000) end it "should create a root node if the tree was empty" do expect(subject.instance_variable_get(:@root)).to be_nil subject[5] = 'foo' expect(subject.instance_variable_get(:@root)).to be_a(Puppet::Graph::RbTreeMap::Node) end end describe "#size" do it "should be 0 for en empty tree" do expect(subject.size).to eq(0) end it "should correctly report the size for a non-empty tree" do (1..10).each {|i| subject[i] = i.to_s} expect(subject.size).to eq(10) end end describe "#has_key?" do it "should be true if the tree contains the key" do subject[1] = 2 expect(subject).to be_has_key(1) end it "should be true if the tree contains the key and its value is nil" do subject[0] = nil expect(subject).to be_has_key(0) end it "should be false if the tree does not contain the key" do subject[1] = 2 expect(subject).not_to be_has_key(2) end it "should be false if the tree is empty" do expect(subject).not_to be_has_key(5) end end describe "#get" do it "should return the value at the key" do subject[1] = 2 subject[3] = 4 expect(subject.get(1)).to eq(2) expect(subject.get(3)).to eq(4) end it "should return nil if the tree is empty" do expect(subject[1]).to be_nil end it "should return nil if the key is not in the tree" do subject[1] = 2 expect(subject[3]).to be_nil end it "should return nil if the value at the key is nil" do subject[1] = nil expect(subject[1]).to be_nil end end describe "#min_key" do it "should return the smallest key in the tree" do [4,8,12,3,6,2,-4,7].each do |i| subject[i] = i.to_s end expect(subject.min_key).to eq(-4) end it "should return nil if the tree is empty" do expect(subject.min_key).to be_nil end end describe "#max_key" do it "should return the largest key in the tree" do [4,8,12,3,6,2,-4,7].each do |i| subject[i] = i.to_s end expect(subject.max_key).to eq(12) end it "should return nil if the tree is empty" do expect(subject.max_key).to be_nil end end describe "#delete" do before :each do subject[1] = '1' subject[0] = '0' subject[2] = '2' end it "should return the value at the key deleted" do expect(subject.delete(0)).to eq('0') expect(subject.delete(1)).to eq('1') expect(subject.delete(2)).to eq('2') expect(subject.size).to eq(0) end it "should be able to delete the last node" do tree = described_class.new tree[1] = '1' expect(tree.delete(1)).to eq('1') expect(tree).to be_empty end it "should be able to delete the root node" do expect(subject.delete(1)).to eq('1') expect(subject.size).to eq(2) expect(subject.to_hash).to eq({ :node => { :key => 2, :value => '2', :color => :black, }, :left => { :node => { :key => 0, :value => '0', :color => :red, } } }) end it "should be able to delete the left child" do expect(subject.delete(0)).to eq('0') expect(subject.size).to eq(2) expect(subject.to_hash).to eq({ :node => { :key => 2, :value => '2', :color => :black, }, :left => { :node => { :key => 1, :value => '1', :color => :red, } } }) end it "should be able to delete the right child" do expect(subject.delete(2)).to eq('2') expect(subject.size).to eq(2) expect(subject.to_hash).to eq({ :node => { :key => 1, :value => '1', :color => :black, }, :left => { :node => { :key => 0, :value => '0', :color => :red, } } }) end it "should be able to delete the left child if it is a subtree" do (3..6).each {|i| subject[i] = i.to_s} expect(subject.delete(1)).to eq('1') expect(subject.to_hash).to eq({ :node => { :key => 5, :value => '5', :color => :black, }, :left => { :node => { :key => 3, :value => '3', :color => :red, }, :left => { :node => { :key => 2, :value => '2', :color => :black, }, :left => { :node => { :key => 0, :value => '0', :color => :red, }, }, }, :right => { :node => { :key => 4, :value => '4', :color => :black, }, }, }, :right => { :node => { :key => 6, :value => '6', :color => :black, }, }, }) end it "should be able to delete the right child if it is a subtree" do (3..6).each {|i| subject[i] = i.to_s} expect(subject.delete(5)).to eq('5') expect(subject.to_hash).to eq({ :node => { :key => 3, :value => '3', :color => :black, }, :left => { :node => { :key => 1, :value => '1', :color => :red, }, :left => { :node => { :key => 0, :value => '0', :color => :black, }, }, :right => { :node => { :key => 2, :value => '2', :color => :black, }, }, }, :right => { :node => { :key => 6, :value => '6', :color => :black, }, :left => { :node => { :key => 4, :value => '4', :color => :red, }, }, }, }) end it "should return nil if the tree is empty" do tree = described_class.new expect(tree.delete(14)).to be_nil expect(tree.size).to eq(0) end it "should return nil if the key is not in the tree" do (0..4).each {|i| subject[i] = i.to_s} expect(subject.delete(2.5)).to be_nil expect(subject.size).to eq(5) end it "should return nil if the key is larger than the maximum key" do expect(subject.delete(100)).to be_nil expect(subject.size).to eq(3) end it "should return nil if the key is smaller than the minimum key" do expect(subject.delete(-1)).to be_nil expect(subject.size).to eq(3) end end describe "#empty?" do it "should return true if the tree is empty" do expect(subject).to be_empty end it "should return false if the tree is not empty" do subject[5] = 10 expect(subject).not_to be_empty end end describe "#delete_min" do it "should delete the smallest element of the tree" do (1..15).each {|i| subject[i] = i.to_s} expect(subject.delete_min).to eq('1') expect(subject.size).to eq(14) end it "should return nil if the tree is empty" do expect(subject.delete_min).to be_nil end end describe "#delete_max" do it "should delete the largest element of the tree" do (1..15).each {|i| subject[i] = i.to_s} expect(subject.delete_max).to eq('15') expect(subject.size).to eq(14) end it "should return nil if the tree is empty" do expect(subject.delete_max).to be_nil end end describe "#each" do it "should yield each pair in the tree in order if a block is provided" do # Insert in reverse to demonstrate they aren't being yielded in insertion order (1..5).to_a.reverse_each {|i| subject[i] = i.to_s} nodes = [] subject.each do |key,value| nodes << [key,value] end expect(nodes).to eq((1..5).map {|i| [i, i.to_s]}) end it "should do nothing if the tree is empty" do subject.each do |key,value| raise "each on an empty tree incorrectly yielded #{key}, #{value}" end end end describe "#isred" do it "should return true if the node is red" do node = Puppet::Graph::RbTreeMap::Node.new(1,2) node.color = :red expect(subject.send(:isred, node)).to eq(true) end it "should return false if the node is black" do node = Puppet::Graph::RbTreeMap::Node.new(1,2) node.color = :black expect(subject.send(:isred, node)).to eq(false) end it "should return false if the node is nil" do expect(subject.send(:isred, nil)).to eq(false) end end end describe Puppet::Graph::RbTreeMap::Node do let(:tree) { Puppet::Graph::RbTreeMap.new } let(:subject) { tree.instance_variable_get(:@root) } before :each do (1..3).each {|i| tree[i] = i.to_s} end describe "#red?" do it "should return true if the node is red" do subject.color = :red expect(subject).to be_red end it "should return false if the node is black" do subject.color = :black expect(subject).not_to be_red end end describe "#colorflip" do it "should switch the color of the node and its children" do expect(subject.color).to eq(:black) expect(subject.left.color).to eq(:black) expect(subject.right.color).to eq(:black) subject.colorflip expect(subject.color).to eq(:red) expect(subject.left.color).to eq(:red) expect(subject.right.color).to eq(:red) end end describe "#rotate_left" do it "should rotate the tree once to the left" do (4..7).each {|i| tree[i] = i.to_s} root = tree.instance_variable_get(:@root) root.rotate_left expect(tree.to_hash).to eq({ :node => { :key => 6, :value => '6', :color => :black, }, :left => { :node => { :key => 4, :value => '4', :color => :red, }, :left => { :node => { :key => 2, :value => '2', :color => :black, }, :left => { :node => { :key => 1, :value => '1', :color => :black, }, }, :right => { :node => { :key => 3, :value => '3', :color => :black, }, }, }, :right => { :node => { :key => 5, :value => '5', :color => :black, }, }, }, :right => { :node => { :key => 7, :value => '7', :color => :black, }, }, }) end end describe "#rotate_right" do it "should rotate the tree once to the right" do (4..7).each {|i| tree[i] = i.to_s} root = tree.instance_variable_get(:@root) root.rotate_right expect(tree.to_hash).to eq({ :node => { :key => 2, :value => '2', :color => :black, }, :left => { :node => { :key => 1, :value => '1', :color => :black, }, }, :right => { :node => { :key => 4, :value => '4', :color => :red, }, :left => { :node => { :key => 3, :value => '3', :color => :black, }, }, :right => { :node => { :key => 6, :value => '6', :color => :black, }, :left => { :node => { :key => 5, :value => '5', :color => :black, }, }, :right => { :node => { :key => 7, :value => '7', :color => :black, }, }, }, }, }) end end end puppet-5.5.10/spec/unit/graph/relationship_graph_spec.rb0000644005276200011600000003002613417161721023246 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/graph' require 'puppet_spec/compiler' require 'matchers/include_in_order' require 'matchers/relationship_graph_matchers' describe Puppet::Graph::RelationshipGraph do include PuppetSpec::Files include PuppetSpec::Compiler include RelationshipGraphMatchers let(:graph) { Puppet::Graph::RelationshipGraph.new(Puppet::Graph::SequentialPrioritizer.new) } it "allows adding a new vertex with a specific priority" do vertex = stub_vertex('something') graph.add_vertex(vertex, 2) expect(graph.resource_priority(vertex)).to eq(2) end it "returns resource priority based on the order added" do # strings chosen so the old hex digest method would put these in the # wrong order first = stub_vertex('aa') second = stub_vertex('b') graph.add_vertex(first) graph.add_vertex(second) expect(graph.resource_priority(first)).to be < graph.resource_priority(second) end it "retains the first priority when a resource is added more than once" do first = stub_vertex(1) second = stub_vertex(2) graph.add_vertex(first) graph.add_vertex(second) graph.add_vertex(first) expect(graph.resource_priority(first)).to be < graph.resource_priority(second) end it "forgets the priority of a removed resource" do vertex = stub_vertex(1) graph.add_vertex(vertex) graph.remove_vertex!(vertex) expect(graph.resource_priority(vertex)).to be_nil end it "does not give two resources the same priority" do first = stub_vertex(1) second = stub_vertex(2) third = stub_vertex(3) graph.add_vertex(first) graph.add_vertex(second) graph.remove_vertex!(first) graph.add_vertex(third) expect(graph.resource_priority(second)).to be < graph.resource_priority(third) end context "order of traversal" do it "traverses independent resources in the order they are added" do relationships = compile_to_relationship_graph(<<-MANIFEST) notify { "first": } notify { "second": } notify { "third": } notify { "fourth": } notify { "fifth": } MANIFEST expect(order_resources_traversed_in(relationships)).to( include_in_order("Notify[first]", "Notify[second]", "Notify[third]", "Notify[fourth]", "Notify[fifth]")) end it "traverses resources generated during catalog creation in the order inserted" do relationships = compile_to_relationship_graph(<<-MANIFEST) create_resources(notify, { "first" => {} }) create_resources(notify, { "second" => {} }) notify{ "third": } create_resources(notify, { "fourth" => {} }) create_resources(notify, { "fifth" => {} }) MANIFEST expect(order_resources_traversed_in(relationships)).to( include_in_order("Notify[first]", "Notify[second]", "Notify[third]", "Notify[fourth]", "Notify[fifth]")) end it "traverses all independent resources before traversing dependent ones" do relationships = compile_to_relationship_graph(<<-MANIFEST) notify { "first": require => Notify[third] } notify { "second": } notify { "third": } MANIFEST expect(order_resources_traversed_in(relationships)).to( include_in_order("Notify[second]", "Notify[third]", "Notify[first]")) end it "traverses all independent resources before traversing dependent ones (with a backwards require)" do relationships = compile_to_relationship_graph(<<-MANIFEST) notify { "first": } notify { "second": } notify { "third": require => Notify[second] } notify { "fourth": } MANIFEST expect(order_resources_traversed_in(relationships)).to( include_in_order("Notify[first]", "Notify[second]", "Notify[third]", "Notify[fourth]")) end it "traverses resources in classes in the order they are added" do relationships = compile_to_relationship_graph(<<-MANIFEST) class c1 { notify { "a": } notify { "b": } } class c2 { notify { "c": require => Notify[b] } } class c3 { notify { "d": } } include c2 include c1 include c3 MANIFEST expect(order_resources_traversed_in(relationships)).to( include_in_order("Notify[a]", "Notify[b]", "Notify[c]", "Notify[d]")) end it "traverses resources in defines in the order they are added" do relationships = compile_to_relationship_graph(<<-MANIFEST) define d1() { notify { "a": } notify { "b": } } define d2() { notify { "c": require => Notify[b]} } define d3() { notify { "d": } } d2 { "c": } d1 { "d": } d3 { "e": } MANIFEST expect(order_resources_traversed_in(relationships)).to( include_in_order("Notify[a]", "Notify[b]", "Notify[c]", "Notify[d]")) end end describe "when interrupting traversal" do def collect_canceled_resources(relationships, trigger_on) continue = true continue_while = lambda { continue } canceled_resources = [] canceled_resource_handler = lambda { |resource| canceled_resources << resource.ref } relationships.traverse(:while => continue_while, :canceled_resource_handler => canceled_resource_handler) do |resource| if resource.ref == trigger_on continue = false end end canceled_resources end it "enumerates the remaining resources" do relationships = compile_to_relationship_graph(<<-MANIFEST) notify { "a": } notify { "b": } notify { "c": } MANIFEST resources = collect_canceled_resources(relationships, 'Notify[b]') expect(resources).to include('Notify[c]') end it "enumerates the remaining blocked resources" do relationships = compile_to_relationship_graph(<<-MANIFEST) notify { "a": } notify { "b": } notify { "c": } notify { "d": require => Notify["c"] } MANIFEST resources = collect_canceled_resources(relationships, 'Notify[b]') expect(resources).to include('Notify[d]') end end describe "when constructing dependencies" do let(:child) { make_absolute('/a/b') } let(:parent) { make_absolute('/a') } it "does not create an automatic relationship that would interfere with a manual relationship" do relationship_graph = compile_to_relationship_graph(<<-MANIFEST) file { "#{child}": } file { "#{parent}": require => File["#{child}"] } MANIFEST expect(relationship_graph).to enforce_order_with_edge("File[#{child}]", "File[#{parent}]") end it "creates automatic relationships defined by the type" do relationship_graph = compile_to_relationship_graph(<<-MANIFEST) file { "#{child}": } file { "#{parent}": } MANIFEST expect(relationship_graph).to enforce_order_with_edge("File[#{parent}]", "File[#{child}]") end end describe "when reconstructing containment relationships" do def admissible_sentinel_of(ref) "Admissible_#{ref}" end def completed_sentinel_of(ref) "Completed_#{ref}" end it "an empty container's completed sentinel should depend on its admissible sentinel" do relationship_graph = compile_to_relationship_graph(<<-MANIFEST) class a { } include a MANIFEST expect(relationship_graph).to enforce_order_with_edge( admissible_sentinel_of("class[A]"), completed_sentinel_of("class[A]")) end it "a container with children does not directly connect the completed sentinel to its admissible sentinel" do relationship_graph = compile_to_relationship_graph(<<-MANIFEST) class a { notify { "a": } } include a MANIFEST expect(relationship_graph).not_to enforce_order_with_edge( admissible_sentinel_of("class[A]"), completed_sentinel_of("class[A]")) end it "all contained objects should depend on their container's admissible sentinel" do relationship_graph = compile_to_relationship_graph(<<-MANIFEST) class a { notify { "class a": } } include a MANIFEST expect(relationship_graph).to enforce_order_with_edge( admissible_sentinel_of("class[A]"), "Notify[class a]") end it "completed sentinels should depend on their container's contents" do relationship_graph = compile_to_relationship_graph(<<-MANIFEST) class a { notify { "class a": } } include a MANIFEST expect(relationship_graph).to enforce_order_with_edge( "Notify[class a]", completed_sentinel_of("class[A]")) end it "admissible and completed sentinels should inherit the same tags" do relationship_graph = compile_to_relationship_graph(<<-MANIFEST) class a { tag "test_tag" } include a MANIFEST expect(vertex_called(relationship_graph, admissible_sentinel_of("class[A]")).tagged?("test_tag")). to eq(true) expect(vertex_called(relationship_graph, completed_sentinel_of("class[A]")).tagged?("test_tag")). to eq(true) end it "should remove all Component objects from the dependency graph" do relationship_graph = compile_to_relationship_graph(<<-MANIFEST) class a { notify { "class a": } } define b() { notify { "define b": } } include a b { "testing": } MANIFEST expect(relationship_graph.vertices.find_all { |v| v.is_a?(Puppet::Type.type(:component)) }).to be_empty end it "should remove all Stage resources from the dependency graph" do relationship_graph = compile_to_relationship_graph(<<-MANIFEST) notify { "class a": } MANIFEST expect(relationship_graph.vertices.find_all { |v| v.is_a?(Puppet::Type.type(:stage)) }).to be_empty end it "should retain labels on non-containment edges" do relationship_graph = compile_to_relationship_graph(<<-MANIFEST) class a { notify { "class a": } } define b() { notify { "define b": } } include a Class[a] ~> b { "testing": } MANIFEST expect(relationship_graph.edges_between( vertex_called(relationship_graph, completed_sentinel_of("class[A]")), vertex_called(relationship_graph, admissible_sentinel_of("b[testing]")))[0].label). to eq({:callback => :refresh, :event => :ALL_EVENTS}) end it "should not add labels to edges that have none" do relationship_graph = compile_to_relationship_graph(<<-MANIFEST) class a { notify { "class a": } } define b() { notify { "define b": } } include a Class[a] -> b { "testing": } MANIFEST expect(relationship_graph.edges_between( vertex_called(relationship_graph, completed_sentinel_of("class[A]")), vertex_called(relationship_graph, admissible_sentinel_of("b[testing]")))[0].label). to be_empty end it "should copy notification labels to all created edges" do relationship_graph = compile_to_relationship_graph(<<-MANIFEST) class a { notify { "class a": } } define b() { notify { "define b": } } include a Class[a] ~> b { "testing": } MANIFEST expect(relationship_graph.edges_between( vertex_called(relationship_graph, admissible_sentinel_of("b[testing]")), vertex_called(relationship_graph, "Notify[define b]"))[0].label). to eq({:callback => :refresh, :event => :ALL_EVENTS}) end end def vertex_called(graph, name) graph.vertices.find { |v| v.ref =~ /#{Regexp.escape(name)}/ } end def stub_vertex(name) stub "vertex #{name}", :ref => name end end puppet-5.5.10/spec/unit/graph/sequential_prioritizer_spec.rb0000644005276200011600000000210013417161721024170 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/graph' describe Puppet::Graph::SequentialPrioritizer do let(:priorities) { Puppet::Graph::SequentialPrioritizer.new } it "generates priorities that maintain the sequence" do first = priorities.generate_priority_for("one") second = priorities.generate_priority_for("two") third = priorities.generate_priority_for("three") expect(first).to be < second expect(second).to be < third end it "prioritizes contained keys after the container" do parent = priorities.generate_priority_for("one") child = priorities.generate_priority_contained_in("one", "child 1") sibling = priorities.generate_priority_contained_in("one", "child 2") uncle = priorities.generate_priority_for("two") expect(parent).to be < child expect(child).to be < sibling expect(sibling).to be < uncle end it "fails to prioritize a key contained in an unknown container" do expect do priorities.generate_priority_contained_in("unknown", "child 1") end.to raise_error(NoMethodError, /`down' for nil/) end end puppet-5.5.10/spec/unit/graph/simple_graph_spec.rb0000644005276200011600000006057213417161722022050 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/graph' describe Puppet::Graph::SimpleGraph do it "should return the number of its vertices as its length" do @graph = Puppet::Graph::SimpleGraph.new @graph.add_vertex("one") @graph.add_vertex("two") expect(@graph.size).to eq(2) end it "should consider itself a directed graph" do expect(Puppet::Graph::SimpleGraph.new.directed?).to be_truthy end it "should provide a method for reversing the graph" do @graph = Puppet::Graph::SimpleGraph.new @graph.add_edge(:one, :two) expect(@graph.reversal.edge?(:two, :one)).to be_truthy end it "should be able to produce a dot graph" do @graph = Puppet::Graph::SimpleGraph.new class FauxVertex def ref "never mind" end end v1 = FauxVertex.new v2 = FauxVertex.new @graph.add_edge(v1, v2) expect { @graph.to_dot_graph }.to_not raise_error end describe "when managing vertices" do before do @graph = Puppet::Graph::SimpleGraph.new end it "should provide a method to add a vertex" do @graph.add_vertex(:test) expect(@graph.vertex?(:test)).to be_truthy end it "should reset its reversed graph when vertices are added" do rev = @graph.reversal @graph.add_vertex(:test) expect(@graph.reversal).not_to equal(rev) end it "should ignore already-present vertices when asked to add a vertex" do @graph.add_vertex(:test) expect { @graph.add_vertex(:test) }.to_not raise_error end it "should return true when asked if a vertex is present" do @graph.add_vertex(:test) expect(@graph.vertex?(:test)).to be_truthy end it "should return false when asked if a non-vertex is present" do expect(@graph.vertex?(:test)).to be_falsey end it "should return all set vertices when asked" do @graph.add_vertex(:one) @graph.add_vertex(:two) expect(@graph.vertices.length).to eq(2) expect(@graph.vertices).to include(:one) expect(@graph.vertices).to include(:two) end it "should remove a given vertex when asked" do @graph.add_vertex(:one) @graph.remove_vertex!(:one) expect(@graph.vertex?(:one)).to be_falsey end it "should do nothing when a non-vertex is asked to be removed" do expect { @graph.remove_vertex!(:one) }.to_not raise_error end end describe "when managing edges" do before do @graph = Puppet::Graph::SimpleGraph.new end it "should provide a method to test whether a given vertex pair is an edge" do expect(@graph).to respond_to(:edge?) end it "should reset its reversed graph when edges are added" do rev = @graph.reversal @graph.add_edge(:one, :two) expect(@graph.reversal).not_to equal(rev) end it "should provide a method to add an edge as an instance of the edge class" do edge = Puppet::Relationship.new(:one, :two) @graph.add_edge(edge) expect(@graph.edge?(:one, :two)).to be_truthy end it "should provide a method to add an edge by specifying the two vertices" do @graph.add_edge(:one, :two) expect(@graph.edge?(:one, :two)).to be_truthy end it "should provide a method to add an edge by specifying the two vertices and a label" do @graph.add_edge(:one, :two, :callback => :awesome) expect(@graph.edge?(:one, :two)).to be_truthy end describe "when retrieving edges between two nodes" do it "should handle the case of nodes not in the graph" do expect(@graph.edges_between(:one, :two)).to eq([]) end it "should handle the case of nodes with no edges between them" do @graph.add_vertex(:one) @graph.add_vertex(:two) expect(@graph.edges_between(:one, :two)).to eq([]) end it "should handle the case of nodes connected by a single edge" do edge = Puppet::Relationship.new(:one, :two) @graph.add_edge(edge) expect(@graph.edges_between(:one, :two).length).to eq(1) expect(@graph.edges_between(:one, :two)[0]).to equal(edge) end it "should handle the case of nodes connected by multiple edges" do edge1 = Puppet::Relationship.new(:one, :two, :callback => :foo) edge2 = Puppet::Relationship.new(:one, :two, :callback => :bar) @graph.add_edge(edge1) @graph.add_edge(edge2) expect(Set.new(@graph.edges_between(:one, :two))).to eq(Set.new([edge1, edge2])) end end it "should add the edge source as a vertex if it is not already" do edge = Puppet::Relationship.new(:one, :two) @graph.add_edge(edge) expect(@graph.vertex?(:one)).to be_truthy end it "should add the edge target as a vertex if it is not already" do edge = Puppet::Relationship.new(:one, :two) @graph.add_edge(edge) expect(@graph.vertex?(:two)).to be_truthy end it "should return all edges as edge instances when asked" do one = Puppet::Relationship.new(:one, :two) two = Puppet::Relationship.new(:two, :three) @graph.add_edge(one) @graph.add_edge(two) edges = @graph.edges expect(edges).to be_instance_of(Array) expect(edges.length).to eq(2) expect(edges).to include(one) expect(edges).to include(two) end it "should remove an edge when asked" do edge = Puppet::Relationship.new(:one, :two) @graph.add_edge(edge) @graph.remove_edge!(edge) expect(@graph.edge?(edge.source, edge.target)).to be_falsey end it "should remove all related edges when a vertex is removed" do one = Puppet::Relationship.new(:one, :two) two = Puppet::Relationship.new(:two, :three) @graph.add_edge(one) @graph.add_edge(two) @graph.remove_vertex!(:two) expect(@graph.edge?(:one, :two)).to be_falsey expect(@graph.edge?(:two, :three)).to be_falsey expect(@graph.edges.length).to eq(0) end end describe "when finding adjacent vertices" do before do @graph = Puppet::Graph::SimpleGraph.new @one_two = Puppet::Relationship.new(:one, :two) @two_three = Puppet::Relationship.new(:two, :three) @one_three = Puppet::Relationship.new(:one, :three) @graph.add_edge(@one_two) @graph.add_edge(@one_three) @graph.add_edge(@two_three) end it "should return adjacent vertices" do adj = @graph.adjacent(:one) expect(adj).to be_include(:three) expect(adj).to be_include(:two) end it "should default to finding :out vertices" do expect(@graph.adjacent(:two)).to eq([:three]) end it "should support selecting :in vertices" do expect(@graph.adjacent(:two, :direction => :in)).to eq([:one]) end it "should default to returning the matching vertices as an array of vertices" do expect(@graph.adjacent(:two)).to eq([:three]) end it "should support returning an array of matching edges" do expect(@graph.adjacent(:two, :type => :edges)).to eq([@two_three]) end # Bug #2111 it "should not consider a vertex adjacent just because it was asked about previously" do @graph = Puppet::Graph::SimpleGraph.new @graph.add_vertex("a") @graph.add_vertex("b") @graph.edge?("a", "b") expect(@graph.adjacent("a")).to eq([]) end end describe "when clearing" do before do @graph = Puppet::Graph::SimpleGraph.new one = Puppet::Relationship.new(:one, :two) two = Puppet::Relationship.new(:two, :three) @graph.add_edge(one) @graph.add_edge(two) @graph.clear end it "should remove all vertices" do expect(@graph.vertices).to be_empty end it "should remove all edges" do expect(@graph.edges).to be_empty end end describe "when reversing graphs" do before do @graph = Puppet::Graph::SimpleGraph.new end it "should provide a method for reversing the graph" do @graph.add_edge(:one, :two) expect(@graph.reversal.edge?(:two, :one)).to be_truthy end it "should add all vertices to the reversed graph" do @graph.add_edge(:one, :two) expect(@graph.vertex?(:one)).to be_truthy expect(@graph.vertex?(:two)).to be_truthy end it "should retain labels on edges" do @graph.add_edge(:one, :two, :callback => :awesome) edge = @graph.reversal.edges_between(:two, :one)[0] expect(edge.label).to eq({:callback => :awesome}) end end describe "when reporting cycles in the graph" do before do @graph = Puppet::Graph::SimpleGraph.new end # This works with `add_edges` to auto-vivify the resource instances. let :vertex do Hash.new do |hash, key| hash[key] = Puppet::Type.type(:notify).new(:name => key.to_s) end end def add_edges(hash) hash.each do |a,b| @graph.add_edge(vertex[a], vertex[b]) end end def simplify(cycles) cycles.map do |cycle| cycle.map do |resource| resource.name end end end def expect_cycle_to_include(cycle, *resource_names) resource_names.each_with_index do |resource, index| expect(cycle[index].ref).to eq("Notify[#{resource}]") end end it "should report two-vertex loops" do add_edges :a => :b, :b => :a Puppet.expects(:err).with(regexp_matches(/Found 1 dependency cycle:\n\(Notify\[a\] => Notify\[b\] => Notify\[a\]\)/)) cycle = @graph.report_cycles_in_graph.first expect_cycle_to_include(cycle, :a, :b) end it "should report multi-vertex loops" do add_edges :a => :b, :b => :c, :c => :a Puppet.expects(:err).with(regexp_matches(/Found 1 dependency cycle:\n\(Notify\[a\] => Notify\[b\] => Notify\[c\] => Notify\[a\]\)/)) cycle = @graph.report_cycles_in_graph.first expect_cycle_to_include(cycle, :a, :b, :c) end it "should report when a larger tree contains a small cycle" do add_edges :a => :b, :b => :a, :c => :a, :d => :c Puppet.expects(:err).with(regexp_matches(/Found 1 dependency cycle:\n\(Notify\[a\] => Notify\[b\] => Notify\[a\]\)/)) cycle = @graph.report_cycles_in_graph.first expect_cycle_to_include(cycle, :a, :b) end it "should succeed on trees with no cycles" do add_edges :a => :b, :b => :e, :c => :a, :d => :c Puppet.expects(:err).never expect(@graph.report_cycles_in_graph).to be_nil end it "cycle discovery should be the minimum cycle for a simple graph" do add_edges "a" => "b" add_edges "b" => "a" add_edges "b" => "c" expect(simplify(@graph.find_cycles_in_graph)).to eq([["a", "b"]]) end it "cycle discovery handles a self-loop cycle" do add_edges :a => :a expect(simplify(@graph.find_cycles_in_graph)).to eq([["a"]]) end it "cycle discovery should handle two distinct cycles" do add_edges "a" => "a1", "a1" => "a" add_edges "b" => "b1", "b1" => "b" expect(simplify(@graph.find_cycles_in_graph)).to eq([["a1", "a"], ["b1", "b"]]) end it "cycle discovery should handle two cycles in a connected graph" do add_edges "a" => "b", "b" => "c", "c" => "d" add_edges "a" => "a1", "a1" => "a" add_edges "c" => "c1", "c1" => "c2", "c2" => "c3", "c3" => "c" expect(simplify(@graph.find_cycles_in_graph)).to eq([%w{a1 a}, %w{c1 c2 c3 c}]) end it "cycle discovery should handle a complicated cycle" do add_edges "a" => "b", "b" => "c" add_edges "a" => "c" add_edges "c" => "c1", "c1" => "a" add_edges "c" => "c2", "c2" => "b" expect(simplify(@graph.find_cycles_in_graph)).to eq([%w{a b c1 c2 c}]) end it "cycle discovery should not fail with large data sets" do limit = 3000 (1..(limit - 1)).each do |n| add_edges n.to_s => (n+1).to_s end expect(simplify(@graph.find_cycles_in_graph)).to eq([]) end it "path finding should work with a simple cycle" do add_edges "a" => "b", "b" => "c", "c" => "a" cycles = @graph.find_cycles_in_graph paths = @graph.paths_in_cycle(cycles.first, 100) expect(simplify(paths)).to eq([%w{a b c a}]) end it "path finding should work with two independent cycles" do add_edges "a" => "b1" add_edges "a" => "b2" add_edges "b1" => "a", "b2" => "a" cycles = @graph.find_cycles_in_graph expect(cycles.length).to eq(1) paths = @graph.paths_in_cycle(cycles.first, 100) expect(simplify(paths)).to eq([%w{a b1 a}, %w{a b2 a}]) end it "path finding should prefer shorter paths in cycles" do add_edges "a" => "b", "b" => "c", "c" => "a" add_edges "b" => "a" cycles = @graph.find_cycles_in_graph expect(cycles.length).to eq(1) paths = @graph.paths_in_cycle(cycles.first, 100) expect(simplify(paths)).to eq([%w{a b a}, %w{a b c a}]) end it "path finding should respect the max_path value" do (1..20).each do |n| add_edges "a" => "b#{n}", "b#{n}" => "a" end cycles = @graph.find_cycles_in_graph expect(cycles.length).to eq(1) (1..20).each do |n| paths = @graph.paths_in_cycle(cycles.first, n) expect(paths.length).to eq(n) end paths = @graph.paths_in_cycle(cycles.first, 21) expect(paths.length).to eq(20) end end describe "when writing dot files" do before do @graph = Puppet::Graph::SimpleGraph.new @name = :test @file = File.join(Puppet[:graphdir], @name.to_s + ".dot") end it "should only write when graphing is enabled" do File.expects(:open).with(@file).never Puppet[:graph] = false @graph.write_graph(@name) end it "should write a dot file based on the passed name" do File.expects(:open).with(@file, "w:UTF-8").yields(stub("file", :puts => nil)) @graph.expects(:to_dot).with("name" => @name.to_s.capitalize) Puppet[:graph] = true @graph.write_graph(@name) end end describe Puppet::Graph::SimpleGraph do before do @graph = Puppet::Graph::SimpleGraph.new end it "should correctly clear vertices and edges when asked" do @graph.add_edge("a", "b") @graph.add_vertex "c" @graph.clear expect(@graph.vertices).to be_empty expect(@graph.edges).to be_empty end end describe "when matching edges" do before do @graph = Puppet::Graph::SimpleGraph.new # Resource is a String here although not for realz. Stub [] to always return nil # because indexing a String with a non-Integer throws an exception (and none of # these tests need anything meaningful from []). resource = "a" resource.stubs(:[]) @event = Puppet::Transaction::Event.new(:name => :yay, :resource => resource) @none = Puppet::Transaction::Event.new(:name => :NONE, :resource => resource) @edges = {} @edges["a/b"] = Puppet::Relationship.new("a", "b", {:event => :yay, :callback => :refresh}) @edges["a/c"] = Puppet::Relationship.new("a", "c", {:event => :yay, :callback => :refresh}) @graph.add_edge(@edges["a/b"]) end it "should match edges whose source matches the source of the event" do expect(@graph.matching_edges(@event)).to eq([@edges["a/b"]]) end it "should match always match nothing when the event is :NONE" do expect(@graph.matching_edges(@none)).to be_empty end it "should match multiple edges" do @graph.add_edge(@edges["a/c"]) edges = @graph.matching_edges(@event) expect(edges).to be_include(@edges["a/b"]) expect(edges).to be_include(@edges["a/c"]) end end describe "when determining dependencies" do before do @graph = Puppet::Graph::SimpleGraph.new @graph.add_edge("a", "b") @graph.add_edge("a", "c") @graph.add_edge("b", "d") end it "should find all dependents when they are on multiple levels" do expect(@graph.dependents("a").sort).to eq(%w{b c d}.sort) end it "should find single dependents" do expect(@graph.dependents("b").sort).to eq(%w{d}.sort) end it "should return an empty array when there are no dependents" do expect(@graph.dependents("c").sort).to eq([].sort) end it "should find all dependencies when they are on multiple levels" do expect(@graph.dependencies("d").sort).to eq(%w{a b}) end it "should find single dependencies" do expect(@graph.dependencies("c").sort).to eq(%w{a}) end it "should return an empty array when there are no dependencies" do expect(@graph.dependencies("a").sort).to eq([]) end end it "should serialize to YAML using the old format by default" do expect(Puppet::Graph::SimpleGraph.use_new_yaml_format).to eq(false) end describe "(yaml tests)" do def empty_graph(graph) end def one_vertex_graph(graph) graph.add_vertex('a') end def graph_without_edges(graph) ['a', 'b', 'c'].each { |x| graph.add_vertex(x) } end def one_edge_graph(graph) graph.add_edge('a', 'b') end def many_edge_graph(graph) graph.add_edge('a', 'b') graph.add_edge('a', 'c') graph.add_edge('b', 'd') graph.add_edge('c', 'd') end def labeled_edge_graph(graph) graph.add_edge('a', 'b', :callback => :foo, :event => :bar) end def overlapping_edge_graph(graph) graph.add_edge('a', 'b', :callback => :foo, :event => :bar) graph.add_edge('a', 'b', :callback => :biz, :event => :baz) end def self.all_test_graphs [:empty_graph, :one_vertex_graph, :graph_without_edges, :one_edge_graph, :many_edge_graph, :labeled_edge_graph, :overlapping_edge_graph] end def object_ids(enumerable) # Return a sorted list of the object id's of the elements of an # enumerable. enumerable.collect { |x| x.object_id }.sort end def graph_to_yaml(graph, which_format) previous_use_new_yaml_format = Puppet::Graph::SimpleGraph.use_new_yaml_format Puppet::Graph::SimpleGraph.use_new_yaml_format = (which_format == :new) if block_given? yield else YAML.dump(graph) end ensure Puppet::Graph::SimpleGraph.use_new_yaml_format = previous_use_new_yaml_format end # Test serialization of graph to YAML. [:old, :new].each do |which_format| all_test_graphs.each do |graph_to_test| it "should be able to serialize #{graph_to_test} to YAML (#{which_format} format)" do graph = Puppet::Graph::SimpleGraph.new send(graph_to_test, graph) yaml_form = graph_to_yaml(graph, which_format) # Hack the YAML so that objects in the Puppet namespace get # changed to YAML::DomainType objects. This lets us inspect # the serialized objects easily without invoking any # yaml_initialize hooks. yaml_form.gsub!('!ruby/object:Puppet::', '!hack/object:Puppet::') serialized_object = YAML.load(yaml_form) # Check that the object contains instance variables @edges and # @vertices only. @reversal is also permitted, but we don't # check it, because it is going to be phased out. expect(serialized_object.keys.reject { |x| x == 'reversal' }.sort).to eq(['edges', 'vertices']) # Check edges by forming a set of tuples (source, target, # callback, event) based on the graph and the YAML and make sure # they match. edges = serialized_object['edges'] expect(edges).to be_a(Array) expected_edge_tuples = graph.edges.collect { |edge| [edge.source, edge.target, edge.callback, edge.event] } actual_edge_tuples = edges.collect do |edge| %w{source target}.each { |x| expect(edge.keys).to include(x) } edge.keys.each { |x| expect(['source', 'target', 'callback', 'event']).to include(x) } %w{source target callback event}.collect { |x| edge[x] } end expect(Set.new(actual_edge_tuples)).to eq(Set.new(expected_edge_tuples.map { |tuple| tuple.map {|e| e.nil? ? nil : e.to_s }})) expect(actual_edge_tuples.length).to eq(expected_edge_tuples.length) # Check vertices one by one. vertices = serialized_object['vertices'] if which_format == :old expect(vertices).to be_a(Hash) expect(Set.new(vertices.keys)).to eq(Set.new(graph.vertices)) vertices.each do |key, value| expect(value.keys.sort).to eq(%w{adjacencies vertex}) expect(value['vertex']).to eq(key) adjacencies = value['adjacencies'] expect(adjacencies).to be_a(Hash) expect(Set.new(adjacencies.keys)).to eq(Set.new(['in', 'out'])) [:in, :out].each do |direction| direction_hash = adjacencies[direction.to_s] expect(direction_hash).to be_a(Hash) expected_adjacent_vertices = Set.new(graph.adjacent(key, :direction => direction, :type => :vertices)) expect(Set.new(direction_hash.keys)).to eq(expected_adjacent_vertices) direction_hash.each do |adj_key, adj_value| # Since we already checked edges, just check consistency # with edges. desired_source = direction == :in ? adj_key : key desired_target = direction == :in ? key : adj_key expected_edges = edges.select do |edge| edge['source'] == desired_source && edge['target'] == desired_target end expect(adj_value).to be_a(Array) if adj_value != expected_edges raise "For vertex #{key.inspect}, direction #{direction.inspect}: expected adjacencies #{expected_edges.inspect} but got #{adj_value.inspect}" end end end end else expect(vertices).to be_a(Array) expect(Set.new(vertices)).to eq(Set.new(graph.vertices)) expect(vertices.length).to eq(graph.vertices.length) end end end # Test deserialization of graph from YAML. This presumes the # correctness of serialization to YAML, which has already been # tested. all_test_graphs.each do |graph_to_test| it "should be able to deserialize #{graph_to_test} from YAML (#{which_format} format)" do reference_graph = Puppet::Graph::SimpleGraph.new send(graph_to_test, reference_graph) yaml_form = graph_to_yaml(reference_graph, which_format) recovered_graph = YAML.load(yaml_form) # Test that the recovered vertices match the vertices in the # reference graph. expected_vertices = reference_graph.vertices.to_a recovered_vertices = recovered_graph.vertices.to_a expect(Set.new(recovered_vertices)).to eq(Set.new(expected_vertices)) expect(recovered_vertices.length).to eq(expected_vertices.length) # Test that the recovered edges match the edges in the # reference graph. expected_edge_tuples = reference_graph.edges.collect do |edge| [edge.source, edge.target, edge.callback, edge.event] end recovered_edge_tuples = recovered_graph.edges.collect do |edge| [edge.source, edge.target, edge.callback, edge.event] end expect(Set.new(recovered_edge_tuples)).to eq(Set.new(expected_edge_tuples)) expect(recovered_edge_tuples.length).to eq(expected_edge_tuples.length) # We ought to test that the recovered graph is self-consistent # too. But we're not going to bother with that yet because # the internal representation of the graph is about to change. end end end it "should serialize properly when used as a base class" do class Puppet::TestDerivedClass < Puppet::Graph::SimpleGraph attr_accessor :foo def initialize_from_hash(hash) super(hash) @foo = hash['foo'] end def to_data_hash super.merge('foo' => @foo) end end derived = Puppet::TestDerivedClass.new derived.add_edge('a', 'b') derived.foo = 1234 yaml = YAML.dump(derived) recovered_derived = YAML.load(yaml) expect(recovered_derived.class).to equal(Puppet::TestDerivedClass) expect(recovered_derived.edges.length).to eq(1) expect(recovered_derived.edges[0].source).to eq('a') expect(recovered_derived.edges[0].target).to eq('b') expect(recovered_derived.vertices.length).to eq(2) expect(recovered_derived.foo).to eq(1234) end end end puppet-5.5.10/spec/unit/graph/title_hash_prioritizer_spec.rb0000644005276200011600000000350113417161722024151 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/graph' describe Puppet::Graph::TitleHashPrioritizer do it "produces different priorities for different resource references" do prioritizer = Puppet::Graph::TitleHashPrioritizer.new expect(prioritizer.generate_priority_for(resource(:notify, "one"))).to_not( eq(prioritizer.generate_priority_for(resource(:notify, "two")))) end it "always produces the same priority for the same resource ref" do a_prioritizer = Puppet::Graph::TitleHashPrioritizer.new another_prioritizer = Puppet::Graph::TitleHashPrioritizer.new expect(a_prioritizer.generate_priority_for(resource(:notify, "one"))).to( eq(another_prioritizer.generate_priority_for(resource(:notify, "one")))) end it "does not use the container when generating priorities" do prioritizer = Puppet::Graph::TitleHashPrioritizer.new expect(prioritizer.generate_priority_contained_in(nil, resource(:notify, "one"))).to( eq(prioritizer.generate_priority_for(resource(:notify, "one")))) end it "can retrieve a previously provided priority with the same resource" do prioritizer = Puppet::Graph::TitleHashPrioritizer.new resource = resource(:notify, "title") generated = prioritizer.generate_priority_for(resource) expect(prioritizer.priority_of(resource)).to eq(generated) end it "can not retrieve the priority of a resource with a different resource with the same title" do prioritizer = Puppet::Graph::TitleHashPrioritizer.new resource = resource(:notify, "title") different_resource = resource(:notify, "title") prioritizer.generate_priority_for(resource) expect(prioritizer.priority_of(resource)).not_to be_nil expect(prioritizer.priority_of(different_resource)).to be_nil end def resource(type, title) Puppet::Resource.new(type, title) end end puppet-5.5.10/spec/unit/hiera/0000755005276200011600000000000013417162176016020 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/hiera/scope_spec.rb0000644005276200011600000000556513417161721020476 0ustar jenkinsjenkinsrequire 'spec_helper' require 'hiera/scope' require 'puppet_spec/scope' describe Hiera::Scope do include PuppetSpec::Scope let(:real) { create_test_scope_for_node("test_node") } let(:scope) { Hiera::Scope.new(real) } describe "#initialize" do it "should store the supplied puppet scope" do expect(scope.real).to eq(real) end end describe "#[]" do it "should return nil when no value is found" do expect(scope["foo"]).to eq(nil) end it "should treat '' as nil" do real["foo"] = "" expect(scope["foo"]).to eq(nil) end it "should return found data" do real["foo"] = "bar" expect(scope["foo"]).to eq("bar") end it "preserves the case of a string that is found" do real["foo"] = "CAPITAL!" expect(scope["foo"]).to eq("CAPITAL!") end it "aliases $module_name as calling_module" do real["module_name"] = "the_module" expect(scope["calling_module"]).to eq("the_module") end it "uses the name of the of the scope's class as the calling_class" do real.source = Puppet::Resource::Type.new(:hostclass, "testing", :module_name => "the_module") expect(scope["calling_class"]).to eq("testing") end it "downcases the calling_class" do real.source = Puppet::Resource::Type.new(:hostclass, "UPPER CASE", :module_name => "the_module") expect(scope["calling_class"]).to eq("upper case") end it "looks for the class which includes the defined type as the calling_class" do parent = create_test_scope_for_node("parent") real.parent = parent parent.source = Puppet::Resource::Type.new(:hostclass, "name_of_the_class_including_the_definition", :module_name => "class_module") real.source = Puppet::Resource::Type.new(:definition, "definition_name", :module_name => "definition_module") expect(scope["calling_class"]).to eq("name_of_the_class_including_the_definition") end end describe "#exist?" do it "should correctly report missing data" do real["nil_value"] = nil real["blank_value"] = "" expect(scope.exist?("nil_value")).to eq(true) expect(scope.exist?("blank_value")).to eq(true) expect(scope.exist?("missing_value")).to eq(false) end it "should always return true for calling_class and calling_module" do expect(scope.include?("calling_class")).to eq(true) expect(scope.include?("calling_class_path")).to eq(true) expect(scope.include?("calling_module")).to eq(true) end end end puppet-5.5.10/spec/unit/hiera_puppet_spec.rb0000644005276200011600000001411013417161721020744 0ustar jenkinsjenkinsrequire 'spec_helper' require 'hiera_puppet' require 'puppet_spec/scope' describe 'HieraPuppet' do include PuppetSpec::Scope after(:all) do HieraPuppet.instance_variable_set(:@hiera, nil) end describe 'HieraPuppet#hiera_config' do let(:hiera_config_data) do { :backend => 'yaml' } end context "when the hiera_config_file exists" do before do Hiera::Config.expects(:load).returns(hiera_config_data) HieraPuppet.expects(:hiera_config_file).returns(true) end it "should return a configuration hash" do expected_results = { :backend => 'yaml', :logger => 'puppet' } expect(HieraPuppet.send(:hiera_config)).to eq(expected_results) end end context "when the hiera_config_file does not exist" do before do Hiera::Config.expects(:load).never HieraPuppet.expects(:hiera_config_file).returns(nil) end it "should return a configuration hash" do expect(HieraPuppet.send(:hiera_config)).to eq({ :logger => 'puppet' }) end end end describe 'HieraPuppet#hiera_config_file' do it "should return nil when we cannot derive the hiera config file from Puppet.settings" do begin Puppet.settings[:hiera_config] = nil rescue ArgumentError => detail raise unless detail.message =~ /unknown setting/ end expect(HieraPuppet.send(:hiera_config_file)).to be_nil end it "should use Puppet.settings[:hiera_config] as the hiera config file" do begin Puppet.settings[:hiera_config] = "/dev/null/my_hiera.yaml" rescue ArgumentError => detail raise unless detail.message =~ /unknown setting/ pending("This example does not apply to Puppet #{Puppet.version} because it does not have this setting") end Puppet::FileSystem.stubs(:exist?).with(Puppet[:hiera_config]).returns(true) expect(HieraPuppet.send(:hiera_config_file)).to eq(Puppet[:hiera_config]) end context 'when hiera_config is not set' do let(:code_hiera_config) { File.join(Puppet[:codedir], 'hiera.yaml') } let(:conf_hiera_config) { File.join(Puppet[:confdir], 'hiera.yaml') } before(:each) do Puppet.settings.setting(:hiera_config).send(:remove_instance_variable, :@evaluated_default) Puppet.settings[:hiera_config] = nil Puppet.settings[:codedir] = '/dev/null/puppetlabs/code' Puppet.settings[:confdir] = '/dev/null/puppetlabs/puppet' end it "should use Puppet.settings[:codedir]/hiera.yaml when '$codedir/hiera.yaml' exists and '$confdir/hiera.yaml' does not exist" do Puppet::FileSystem.stubs(:exist?).with(code_hiera_config).returns(true) Puppet::FileSystem.stubs(:exist?).with(conf_hiera_config).returns(false) expect(HieraPuppet.send(:hiera_config_file)).to eq(code_hiera_config) end it "should use Puppet.settings[:confdir]/hiera.yaml when '$codedir/hiera.yaml' does not exist and '$confdir/hiera.yaml' exists" do Puppet::FileSystem.stubs(:exist?).with(code_hiera_config).returns(false) Puppet::FileSystem.stubs(:exist?).with(conf_hiera_config).returns(true) expect(HieraPuppet.send(:hiera_config_file)).to eq(conf_hiera_config) end it "should use Puppet.settings[:codedir]/hiera.yaml when '$codedir/hiera.yaml' exists and '$confdir/hiera.yaml' exists" do Puppet::FileSystem.stubs(:exist?).with(code_hiera_config).returns(true) Puppet::FileSystem.stubs(:exist?).with(conf_hiera_config).returns(true) expect(HieraPuppet.send(:hiera_config_file)).to eq(code_hiera_config) end it "should return nil when neither '$codedir/hiera.yaml' nor '$confdir/hiera.yaml' exists" do Puppet::FileSystem.stubs(:exist?).with(code_hiera_config).returns(false) Puppet::FileSystem.stubs(:exist?).with(conf_hiera_config).returns(false) expect(HieraPuppet.send(:hiera_config_file)).to eq(nil) end it "should return explicitly set option even if both '$codedir/hiera.yaml' and '$confdir/hiera.yaml' exists" do if Puppet::Util::Platform.windows? explicit_hiera_config = 'C:/an/explicit/hiera.yaml' else explicit_hiera_config = '/an/explicit/hiera.yaml' end Puppet.settings[:hiera_config] = explicit_hiera_config Puppet::FileSystem.stubs(:exist?).with(explicit_hiera_config).returns(true) Puppet::FileSystem.stubs(:exist?).with(code_hiera_config).returns(true) Puppet::FileSystem.stubs(:exist?).with(conf_hiera_config).returns(true) expect(HieraPuppet.send(:hiera_config_file)).to eq(explicit_hiera_config) end end end describe 'HieraPuppet#lookup' do let :scope do create_test_scope_for_node('foo') end before :each do Puppet[:hiera_config] = PuppetSpec::Files.tmpfile('hiera_config') end it "should return the value from Hiera" do Hiera.any_instance.stubs(:lookup).returns('8080') expect(HieraPuppet.lookup('port', nil, scope, nil, :priority)).to eq('8080') Hiera.any_instance.stubs(:lookup).returns(['foo', 'bar']) expect(HieraPuppet.lookup('ntpservers', nil, scope, nil, :array)).to eq(['foo', 'bar']) Hiera.any_instance.stubs(:lookup).returns({'uid' => '1000'}) expect(HieraPuppet.lookup('user', nil, scope, nil, :hash)).to eq({'uid' => '1000'}) end it "should raise a useful error when the answer is nil" do Hiera.any_instance.stubs(:lookup).returns(nil) expect do HieraPuppet.lookup('port', nil, scope, nil, :priority) end.to raise_error(Puppet::ParseError, /Could not find data item port in any Hiera data file and no default supplied/) end end describe 'HieraPuppet#parse_args' do it 'should return a 3 item array' do args = ['foo', '8080', nil, nil] expect(HieraPuppet.parse_args(args)).to eq(['foo', '8080', nil]) end it 'should raise a useful error when no key is supplied' do expect { HieraPuppet.parse_args([]) }.to raise_error(Puppet::ParseError, /Please supply a parameter to perform a Hiera lookup/) end end end puppet-5.5.10/spec/unit/indirector/0000755005276200011600000000000013417162176017072 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/indirector/catalog/0000755005276200011600000000000013417162176020504 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/indirector/catalog/compiler_spec.rb0000644005276200011600000011762213417161721023661 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' require 'puppet/indirector/catalog/compiler' describe Puppet::Resource::Catalog::Compiler do let(:compiler) { described_class.new } let(:node_name) { "foo" } let(:node) { Puppet::Node.new(node_name)} before do Facter.stubs(:to_hash).returns({}) end describe "when initializing" do before do Puppet.expects(:version).returns(1) Facter.expects(:value).with('fqdn').returns("my.server.com") Facter.expects(:value).with('ipaddress').returns("my.ip.address") end it "should gather data about itself" do Puppet::Resource::Catalog::Compiler.new end it "should cache the server metadata and reuse it" do Puppet[:node_terminus] = :memory Puppet::Node.indirection.save(Puppet::Node.new("node1")) Puppet::Node.indirection.save(Puppet::Node.new("node2")) compiler.stubs(:compile) compiler.find(Puppet::Indirector::Request.new(:catalog, :find, 'node1', nil, :node => 'node1')) compiler.find(Puppet::Indirector::Request.new(:catalog, :find, 'node2', nil, :node => 'node2')) end end describe "when finding catalogs" do before do Facter.stubs(:value).returns("whatever") node.stubs(:merge) Puppet::Node.indirection.stubs(:find).returns(node) @request = Puppet::Indirector::Request.new(:catalog, :find, node_name, nil, :node => node_name) end it "should directly use provided nodes for a local request" do Puppet::Node.indirection.expects(:find).never compiler.expects(:compile).with(node, anything) @request.stubs(:options).returns(:use_node => node) @request.stubs(:remote?).returns(false) compiler.find(@request) end it "rejects a provided node if the request is remote" do @request.stubs(:options).returns(:use_node => node) @request.stubs(:remote?).returns(true) expect { compiler.find(@request) }.to raise_error Puppet::Error, /invalid option use_node/i end it "should use the authenticated node name if no request key is provided" do @request.stubs(:key).returns(nil) Puppet::Node.indirection.expects(:find).with(node_name, anything).returns(node) compiler.expects(:compile).with(node, anything) compiler.find(@request) end it "should use the provided node name by default" do @request.expects(:key).returns "my_node" Puppet::Node.indirection.expects(:find).with("my_node", anything).returns node compiler.expects(:compile).with(node, anything) compiler.find(@request) end it "should fail if no node is passed and none can be found" do Puppet::Node.indirection.stubs(:find).with(node_name, anything).returns(nil) expect { compiler.find(@request) }.to raise_error(ArgumentError) end it "should fail intelligently when searching for a node raises an exception" do Puppet::Node.indirection.stubs(:find).with(node_name, anything).raises "eh" expect { compiler.find(@request) }.to raise_error(Puppet::Error) end it "should pass the found node to the compiler for compiling" do Puppet::Node.indirection.expects(:find).with(node_name, anything).returns(node) Puppet::Parser::Compiler.expects(:compile).with(node, anything) compiler.find(@request) end it "should pass node containing percent character to the compiler" do node_with_percent_character = Puppet::Node.new "%6de" Puppet::Node.indirection.stubs(:find).returns(node_with_percent_character) Puppet::Parser::Compiler.expects(:compile).with(node_with_percent_character, anything) compiler.find(@request) end it "should extract any facts from the request" do Puppet::Node.indirection.expects(:find).with(node_name, anything).returns node compiler.expects(:extract_facts_from_request).with(@request) Puppet::Parser::Compiler.stubs(:compile) compiler.find(@request) end it "requires `facts_format` option if facts are passed in" do facts = Puppet::Node::Facts.new("mynode", :afact => "avalue") request = Puppet::Indirector::Request.new(:catalog, :find, "mynode", nil, :facts => facts) expect { compiler.find(request) }.to raise_error ArgumentError, /no fact format provided for mynode/ end it "rejects facts in the request from a different node" do facts = Puppet::Node::Facts.new("differentnode", :afact => "avalue") request = Puppet::Indirector::Request.new( :catalog, :find, "mynode", nil, :facts => facts, :facts_format => "unused" ) expect { compiler.find(request) }.to raise_error Puppet::Error, /fact definition for the wrong node/i end it "should return the results of compiling as the catalog" do Puppet::Node.indirection.stubs(:find).returns(node) catalog = Puppet::Resource::Catalog.new(node.name) Puppet::Parser::Compiler.stubs(:compile).returns catalog expect(compiler.find(@request)).to equal(catalog) end it "passes the code_id from the request to the compiler" do Puppet::Node.indirection.stubs(:find).returns(node) code_id = 'b59e5df0578ef411f773ee6c33d8073c50e7b8fe' @request.options[:code_id] = code_id Puppet::Parser::Compiler.expects(:compile).with(anything, code_id) compiler.find(@request) end it "returns a catalog with the code_id from the request" do Puppet::Node.indirection.stubs(:find).returns(node) code_id = 'b59e5df0578ef411f773ee6c33d8073c50e7b8fe' @request.options[:code_id] = code_id catalog = Puppet::Resource::Catalog.new(node.name, node.environment, code_id) Puppet::Parser::Compiler.stubs(:compile).returns catalog expect(compiler.find(@request).code_id).to eq(code_id) end it "does not inline metadata when the static_catalog option is false" do Puppet::Node.indirection.stubs(:find).returns(node) @request.options[:static_catalog] = false @request.options[:code_id] = 'some_code_id' node.environment.stubs(:static_catalogs?).returns true catalog = Puppet::Resource::Catalog.new(node.name, node.environment) Puppet::Parser::Compiler.stubs(:compile).returns catalog compiler.expects(:inline_metadata).never compiler.find(@request) end it "does not inline metadata when static_catalogs are disabled" do Puppet::Node.indirection.stubs(:find).returns(node) @request.options[:static_catalog] = true @request.options[:checksum_type] = 'md5' @request.options[:code_id] = 'some_code_id' node.environment.stubs(:static_catalogs?).returns false catalog = Puppet::Resource::Catalog.new(node.name, node.environment) Puppet::Parser::Compiler.stubs(:compile).returns catalog compiler.expects(:inline_metadata).never compiler.find(@request) end it "does not inline metadata when code_id is not specified" do Puppet::Node.indirection.stubs(:find).returns(node) @request.options[:static_catalog] = true @request.options[:checksum_type] = 'md5' node.environment.stubs(:static_catalogs?).returns true catalog = Puppet::Resource::Catalog.new(node.name, node.environment) Puppet::Parser::Compiler.stubs(:compile).returns catalog compiler.expects(:inline_metadata).never expect(compiler.find(@request)).to eq(catalog) end it "inlines metadata when the static_catalog option is true, static_catalogs are enabled, and a code_id is provided" do Puppet::Node.indirection.stubs(:find).returns(node) @request.options[:static_catalog] = true @request.options[:checksum_type] = 'sha256' @request.options[:code_id] = 'some_code_id' node.environment.stubs(:static_catalogs?).returns true catalog = Puppet::Resource::Catalog.new(node.name, node.environment) Puppet::Parser::Compiler.stubs(:compile).returns catalog compiler.expects(:inline_metadata).with(catalog, :sha256).returns catalog compiler.find(@request) end it "inlines metadata with the first common checksum type" do Puppet::Node.indirection.stubs(:find).returns(node) @request.options[:static_catalog] = true @request.options[:checksum_type] = 'atime.md5.sha256.mtime' @request.options[:code_id] = 'some_code_id' node.environment.stubs(:static_catalogs?).returns true catalog = Puppet::Resource::Catalog.new(node.name, node.environment) Puppet::Parser::Compiler.stubs(:compile).returns catalog compiler.expects(:inline_metadata).with(catalog, :md5).returns catalog compiler.find(@request) end it "errors if checksum_type contains no shared checksum types" do Puppet::Node.indirection.stubs(:find).returns(node) @request.options[:static_catalog] = true @request.options[:checksum_type] = 'atime.md2' @request.options[:code_id] = 'some_code_id' node.environment.stubs(:static_catalogs?).returns true expect { compiler.find(@request) }.to raise_error Puppet::Error, "Unable to find a common checksum type between agent 'atime.md2' and master '[:sha256, :sha256lite, :md5, :md5lite, :sha1, :sha1lite, :sha512, :sha384, :sha224, :mtime, :ctime, :none]'." end it "errors if checksum_type contains no shared checksum types" do Puppet::Node.indirection.stubs(:find).returns(node) @request.options[:static_catalog] = true @request.options[:checksum_type] = nil @request.options[:code_id] = 'some_code_id' node.environment.stubs(:static_catalogs?).returns true expect { compiler.find(@request) }.to raise_error Puppet::Error, "Unable to find a common checksum type between agent '' and master '[:sha256, :sha256lite, :md5, :md5lite, :sha1, :sha1lite, :sha512, :sha384, :sha224, :mtime, :ctime, :none]'." end end describe "when handling a request with facts" do before do Puppet::Node::Facts.indirection.terminus_class = :memory Facter.stubs(:value).returns "something" @facts = Puppet::Node::Facts.new('hostname', "fact" => "value", "architecture" => "i386") end def a_legacy_request_that_contains(facts, format = :pson) request = Puppet::Indirector::Request.new(:catalog, :find, "hostname", nil) request.options[:facts_format] = format.to_s request.options[:facts] = Puppet::Util.uri_query_encode(facts.render(format)) request end def a_request_that_contains(facts) request = Puppet::Indirector::Request.new(:catalog, :find, "hostname", nil) request.options[:facts_format] = "application/json" request.options[:facts] = Puppet::Util.uri_query_encode(facts.render('json')) request end context "when extracting facts from the request" do it "should do nothing if no facts are provided" do request = Puppet::Indirector::Request.new(:catalog, :find, "hostname", nil) request.options[:facts] = nil expect(compiler.extract_facts_from_request(request)).to be_nil end it "should deserialize the facts without changing the timestamp" do time = Time.now @facts.timestamp = time request = a_request_that_contains(@facts) facts = compiler.extract_facts_from_request(request) expect(facts.timestamp).to eq(time) end it "accepts PSON facts from older agents" do request = a_legacy_request_that_contains(@facts) facts = compiler.extract_facts_from_request(request) expect(facts).to eq(@facts) end it "rejects YAML facts" do request = a_legacy_request_that_contains(@facts, :yaml) expect { compiler.extract_facts_from_request(request) }.to raise_error(ArgumentError, /Unsupported facts format/) end it "rejects unknown fact formats" do request = a_request_that_contains(@facts) request.options[:facts_format] = 'unknown-format' expect { compiler.extract_facts_from_request(request) }.to raise_error(ArgumentError, /Unsupported facts format/) end end context "when saving facts from the request" do it "should save facts if they were issued by the request" do request = a_request_that_contains(@facts) options = { :environment => request.environment, :transaction_uuid => request.options[:transaction_uuid], } Puppet::Node::Facts.indirection.expects(:save).with(equals(@facts), nil, options) compiler.find(request) end it "should skip saving facts if none were supplied" do request = Puppet::Indirector::Request.new(:catalog, :find, "hostname", nil) options = { :environment => request.environment, :transaction_uuid => request.options[:transaction_uuid], } Puppet::Node::Facts.indirection.expects(:save).with(equals(@facts), nil, options).never compiler.find(request) end end end describe "when finding nodes" do it "should look node information up via the Node class with the provided key" do Facter.stubs(:value).returns("whatever") request = Puppet::Indirector::Request.new(:catalog, :find, node_name, nil) compiler.stubs(:compile) Puppet::Node.indirection.expects(:find).with(node_name, anything).returns(node) compiler.find(request) end it "should pass the transaction_uuid to the node indirection" do uuid = '793ff10d-89f8-4527-a645-3302cbc749f3' compiler.stubs(:compile) request = Puppet::Indirector::Request.new(:catalog, :find, node_name, nil, :transaction_uuid => uuid) Puppet::Node.indirection.expects(:find).with( node_name, has_entries(:transaction_uuid => uuid) ).returns(node) compiler.find(request) end it "should pass the configured_environment to the node indirection" do environment = 'foo' compiler.stubs(:compile) request = Puppet::Indirector::Request.new(:catalog, :find, node_name, nil, :configured_environment => environment) Puppet::Node.indirection.expects(:find).with( node_name, has_entries(:configured_environment => environment) ).returns(node) compiler.find(request) end it "should pass a facts object from the original request facts to the node indirection" do facts = Puppet::Node::Facts.new("hostname", :afact => "avalue") compiler.expects(:extract_facts_from_request).returns(facts) compiler.expects(:save_facts_from_request) request = Puppet::Indirector::Request.new(:catalog, :find, "hostname", nil, :facts_format => "application/json", :facts => facts.render('json')) Puppet::Node.indirection.expects(:find).with("hostname", has_entries(:facts => facts)).returns(node) compiler.find(request) end end describe "after finding nodes" do before do Puppet.expects(:version).returns(1) Facter.expects(:value).with('fqdn').returns("my.server.com") Facter.expects(:value).with('ipaddress').returns("my.ip.address") @request = Puppet::Indirector::Request.new(:catalog, :find, node_name, nil) compiler.stubs(:compile) Puppet::Node.indirection.stubs(:find).with(node_name, anything).returns(node) end it "should add the server's Puppet version to the node's parameters as 'serverversion'" do node.expects(:merge).with { |args| args["serverversion"] == "1" } compiler.find(@request) end it "should add the server's fqdn to the node's parameters as 'servername'" do node.expects(:merge).with { |args| args["servername"] == "my.server.com" } compiler.find(@request) end it "should add the server's IP address to the node's parameters as 'serverip'" do node.expects(:merge).with { |args| args["serverip"] == "my.ip.address" } compiler.find(@request) end end describe "when filtering resources" do before :each do Facter.stubs(:value) @catalog = stub_everything 'catalog' @catalog.stubs(:respond_to?).with(:filter).returns(true) end it "should delegate to the catalog instance filtering" do @catalog.expects(:filter) compiler.filter(@catalog) end it "should filter out virtual resources" do resource = mock 'resource', :virtual? => true @catalog.stubs(:filter).yields(resource) compiler.filter(@catalog) end it "should return the same catalog if it doesn't support filtering" do @catalog.stubs(:respond_to?).with(:filter).returns(false) expect(compiler.filter(@catalog)).to eq(@catalog) end it "should return the filtered catalog" do catalog = stub 'filtered catalog' @catalog.stubs(:filter).returns(catalog) expect(compiler.filter(@catalog)).to eq(catalog) end end describe "when inlining metadata" do include PuppetSpec::Compiler let(:node) { Puppet::Node.new 'me' } let(:checksum_type) { 'md5' } let(:checksum_value) { 'b1946ac92492d2347c6235b4d2611184' } let(:path) { File.expand_path('/foo') } let(:source) { 'puppet:///modules/mymodule/config_file.txt' } def stubs_resource_metadata(ftype, relative_path, full_path = nil) full_path ||= File.join(Puppet[:environmentpath], 'production', relative_path) metadata = stub 'metadata' metadata.stubs(:ftype).returns(ftype) metadata.stubs(:full_path).returns(full_path) metadata.stubs(:relative_path).returns(relative_path) metadata.stubs(:source).returns("puppet:///#{relative_path}") metadata.stubs(:source=) metadata.stubs(:content_uri=) metadata end def stubs_file_metadata(checksum_type, sha, relative_path, full_path = nil) metadata = stubs_resource_metadata('file', relative_path, full_path) metadata.stubs(:checksum).returns("{#{checksum_type}}#{sha}") metadata.stubs(:checksum_type).returns(checksum_type) metadata end def stubs_link_metadata(relative_path, destination) metadata = stubs_resource_metadata('link', relative_path) metadata.stubs(:destination).returns(destination) metadata end def stubs_directory_metadata(relative_path) metadata = stubs_resource_metadata('directory', relative_path) metadata.stubs(:relative_path).returns('.') metadata end it "inlines metadata for a file" do catalog = compile_to_catalog(<<-MANIFEST, node) file { '#{path}': ensure => file, source => '#{source}' } MANIFEST metadata = stubs_file_metadata(checksum_type, checksum_value, 'modules/mymodule/files/config_file.txt') metadata.expects(:source=).with(source) metadata.expects(:content_uri=).with('puppet:///modules/mymodule/files/config_file.txt') options = { :environment => catalog.environment_instance, :links => :manage, :checksum_type => checksum_type.to_sym, :source_permissions => :ignore } Puppet::FileServing::Metadata.indirection.expects(:find).with(source, options).returns(metadata) compiler.send(:inline_metadata, catalog, checksum_type) expect(catalog.metadata[path]).to eq(metadata) expect(catalog.recursive_metadata).to be_empty end it "uses resource parameters when inlining metadata" do catalog = compile_to_catalog(<<-MANIFEST, node) file { '#{path}': ensure => link, source => '#{source}', links => follow, source_permissions => use, } MANIFEST metadata = stubs_link_metadata('modules/mymodule/files/config_file.txt', '/tmp/some/absolute/path') metadata.expects(:source=).with(source) metadata.expects(:content_uri=).with('puppet:///modules/mymodule/files/config_file.txt') options = { :environment => catalog.environment_instance, :links => :follow, :checksum_type => checksum_type.to_sym, :source_permissions => :use } Puppet::FileServing::Metadata.indirection.expects(:find).with(source, options).returns(metadata) compiler.send(:inline_metadata, catalog, checksum_type) expect(catalog.metadata[path]).to eq(metadata) expect(catalog.recursive_metadata).to be_empty end it "uses file parameters which match the true file type defaults" do catalog = compile_to_catalog(<<-MANIFEST, node) file { '#{path}': ensure => file, source => '#{source}' } MANIFEST if Puppet::Util::Platform.windows? default_file = Puppet::Type.type(:file).new(:name => 'C:\defaults') else default_file = Puppet::Type.type(:file).new(:name => '/defaults') end metadata = stubs_file_metadata(checksum_type, checksum_value, 'modules/mymodule/files/config_file.txt') options = { :environment => catalog.environment_instance, :links => default_file[:links], :checksum_type => checksum_type.to_sym, :source_permissions => default_file[:source_permissions] } Puppet::FileServing::Metadata.indirection.expects(:find).with(source, options).returns(metadata) compiler.send(:inline_metadata, catalog, checksum_type) end it "inlines metadata for the first source found" do alt_source = 'puppet:///modules/files/other.txt' catalog = compile_to_catalog(<<-MANIFEST, node) file { '#{path}': ensure => file, source => ['#{alt_source}', '#{source}'], } MANIFEST metadata = stubs_file_metadata(checksum_type, checksum_value, 'modules/mymodule/files/config_file.txt') metadata.expects(:source=).with(source) metadata.expects(:content_uri=).with('puppet:///modules/mymodule/files/config_file.txt') Puppet::FileServing::Metadata.indirection.expects(:find).with(source, anything).returns(metadata) Puppet::FileServing::Metadata.indirection.expects(:find).with(alt_source, anything).returns(nil) compiler.send(:inline_metadata, catalog, checksum_type) expect(catalog.metadata[path]).to eq(metadata) expect(catalog.recursive_metadata).to be_empty end [['md5', 'b1946ac92492d2347c6235b4d2611184'], ['sha256', '5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03']].each do |checksum_type, sha| describe "with agent requesting checksum_type #{checksum_type}" do it "sets checksum and checksum_value for resources with puppet:// source URIs" do catalog = compile_to_catalog(<<-MANIFEST, node) file { '#{path}': ensure => file, source => '#{source}' } MANIFEST metadata = stubs_file_metadata(checksum_type, sha, 'modules/mymodule/files/config_file.txt') options = { :environment => catalog.environment_instance, :links => :manage, :checksum_type => checksum_type.to_sym, :source_permissions => :ignore } Puppet::FileServing::Metadata.indirection.expects(:find).with(source, options).returns(metadata) compiler.send(:inline_metadata, catalog, checksum_type) expect(catalog.metadata[path]).to eq(metadata) expect(catalog.recursive_metadata).to be_empty end end end it "preserves source host and port in the content_uri" do source = 'puppet://myhost:8888/modules/mymodule/config_file.txt' catalog = compile_to_catalog(<<-MANIFEST, node) file { '#{path}': ensure => file, source => '#{source}' } MANIFEST metadata = stubs_file_metadata(checksum_type, checksum_value, 'modules/mymodule/files/config_file.txt') metadata.stubs(:source).returns(source) metadata.expects(:content_uri=).with('puppet://myhost:8888/modules/mymodule/files/config_file.txt') Puppet::FileServing::Metadata.indirection.expects(:find).with(source, anything).returns(metadata) compiler.send(:inline_metadata, catalog, checksum_type) end it "skips absent resources" do catalog = compile_to_catalog(<<-MANIFEST, node) file { '#{path}': ensure => absent, } MANIFEST compiler.send(:inline_metadata, catalog, checksum_type) expect(catalog.metadata).to be_empty expect(catalog.recursive_metadata).to be_empty end it "skips resources without a source" do catalog = compile_to_catalog(<<-MANIFEST, node) file { '#{path}': ensure => file, } MANIFEST compiler.send(:inline_metadata, catalog, checksum_type) expect(catalog.metadata).to be_empty expect(catalog.recursive_metadata).to be_empty end it "skips resources with a local source" do local_source = File.expand_path('/tmp/source') catalog = compile_to_catalog(<<-MANIFEST, node) file { '#{path}': ensure => file, source => '#{local_source}', } MANIFEST compiler.send(:inline_metadata, catalog, checksum_type) expect(catalog.metadata).to be_empty expect(catalog.recursive_metadata).to be_empty end it "skips resources with a http source" do catalog = compile_to_catalog(<<-MANIFEST, node) file { '#{path}': ensure => file, source => ['http://foo.source.io', 'https://foo.source.io'] } MANIFEST compiler.send(:inline_metadata, catalog, checksum_type) expect(catalog.metadata).to be_empty expect(catalog.recursive_metadata).to be_empty end it "skips resources with a source outside the environment path" do catalog = compile_to_catalog(<<-MANIFEST, node) file { '#{path}': ensure => file, source => '#{source}' } MANIFEST full_path = File.join(Puppet[:codedir], "modules/mymodule/files/config_file.txt") metadata = stubs_file_metadata(checksum_type, checksum_value, 'modules/mymodule/files/config_file.txt', full_path) Puppet::FileServing::Metadata.indirection.expects(:find).with(source, anything).returns(metadata) compiler.send(:inline_metadata, catalog, checksum_type) expect(catalog.metadata).to be_empty expect(catalog.recursive_metadata).to be_empty end it "skips resources whose mount point is not 'modules'" do source = 'puppet:///secure/data' catalog = compile_to_catalog(<<-MANIFEST, node) file { '#{path}': ensure => file, source => '#{source}', } MANIFEST metadata = stubs_file_metadata(checksum_type, checksum_value, 'secure/files/data.txt') Puppet::FileServing::Metadata.indirection.expects(:find).with(source, anything).returns(metadata) compiler.send(:inline_metadata, catalog, checksum_type) expect(catalog.metadata).to be_empty expect(catalog.recursive_metadata).to be_empty end it "skips resources with 'modules' mount point resolving to a path not in 'modules/*/files'" do catalog = compile_to_catalog(<<-MANIFEST, node) file { '#{path}': ensure => file, source => '#{source}', } MANIFEST metadata = stubs_file_metadata(checksum_type, checksum_value, 'modules/mymodule/not_in_files/config_file.txt') Puppet::FileServing::Metadata.indirection.expects(:find).with(source, anything).returns(metadata) compiler.send(:inline_metadata, catalog, checksum_type) expect(catalog.metadata).to be_empty expect(catalog.recursive_metadata).to be_empty end it "skips resources with 'modules' mount point resolving to a path with an empty module name" do catalog = compile_to_catalog(<<-MANIFEST, node) file { '#{path}': ensure => file, source => '#{source}', } MANIFEST # note empty module name "modules//files" metadata = stubs_file_metadata(checksum_type, checksum_value, 'modules//files/config_file.txt') Puppet::FileServing::Metadata.indirection.expects(:find).with(source, anything).returns(metadata) compiler.send(:inline_metadata, catalog, checksum_type) expect(catalog.metadata).to be_empty expect(catalog.recursive_metadata).to be_empty end it "inlines resources in 'modules' mount point resolving to a 'site' directory within the per-environment codedir" do # example taken from https://github.com/puppetlabs/control-repo/blob/508b9cc/site/profile/manifests/puppetmaster.pp#L45-L49 source = 'puppet:///modules/profile/puppetmaster/update-classes.sh' catalog = compile_to_catalog(<<-MANIFEST, node) file { '#{path}': ensure => file, source => '#{source}' } MANIFEST # See https://github.com/puppetlabs/control-repo/blob/508b9cc/site/profile/files/puppetmaster/update-classes.sh metadata = stubs_file_metadata(checksum_type, checksum_value, 'site/profile/files/puppetmaster/update-classes.sh') metadata.stubs(:source).returns(source) Puppet::FileServing::Metadata.indirection.expects(:find).with(source, anything).returns(metadata) compiler.send(:inline_metadata, catalog, checksum_type) expect(catalog.metadata[path]).to eq(metadata) expect(catalog.recursive_metadata).to be_empty end # It's bizarre to strip trailing slashes for a file, but it's how # puppet currently behaves, so match that. it "inlines resources with a trailing slash" do source = 'puppet:///modules/mymodule/myfile' catalog = compile_to_catalog(<<-MANIFEST, node) file { '#{path}': ensure => file, source => '#{source}/' } MANIFEST metadata = stubs_file_metadata(checksum_type, checksum_value, 'modules/mymodule/files/myfile') metadata.stubs(:source).returns(source) Puppet::FileServing::Metadata.indirection.expects(:find).with(source, anything).returns(metadata) compiler.send(:inline_metadata, catalog, checksum_type) expect(catalog.metadata[path]).to eq(metadata) expect(catalog.recursive_metadata).to be_empty end describe "when inlining directories" do let(:source_dir) { 'puppet:///modules/mymodule/directory' } let(:metadata) { stubs_directory_metadata('modules/mymodule/files/directory') } describe "when recurse is false" do it "skips children" do catalog = compile_to_catalog(<<-MANIFEST, node) file { '#{path}': ensure => directory, source => '#{source_dir}' } MANIFEST metadata.expects(:content_uri=).with('puppet:///modules/mymodule/files/directory') Puppet::FileServing::Metadata.indirection.expects(:find).with(source_dir, anything).returns(metadata) compiler.send(:inline_metadata, catalog, checksum_type) expect(catalog.metadata[path]).to eq(metadata) expect(catalog.recursive_metadata).to be_empty end end describe "when recurse is true" do let(:child_metadata) { stubs_file_metadata(checksum_type, checksum_value, 'myfile.txt') } it "inlines child metadata" do catalog = compile_to_catalog(<<-MANIFEST, node) file { '#{path}': ensure => directory, recurse => true, source => '#{source_dir}' } MANIFEST metadata.expects(:content_uri=).with('puppet:///modules/mymodule/files/directory') child_metadata.expects(:content_uri=).with('puppet:///modules/mymodule/files/directory/myfile.txt') options = { :environment => catalog.environment_instance, :links => :manage, :checksum_type => checksum_type.to_sym, :source_permissions => :ignore, :recurse => true, :recurselimit => nil, :ignore => nil, } Puppet::FileServing::Metadata.indirection.expects(:search).with(source_dir, options).returns([metadata, child_metadata]) compiler.send(:inline_metadata, catalog, checksum_type) expect(catalog.metadata[path]).to be_nil expect(catalog.recursive_metadata[path][source_dir]).to eq([metadata, child_metadata]) end it "uses resource parameters when inlining metadata" do catalog = compile_to_catalog(<<-MANIFEST, node) file { '#{path}': ensure => directory, recurse => true, source => '#{source_dir}', checksum => sha256, source_permissions => use_when_creating, recurselimit => 2, ignore => 'foo.+', links => follow, } MANIFEST options = { :environment => catalog.environment_instance, :links => :follow, :checksum_type => :sha256, :source_permissions => :use_when_creating, :recurse => true, :recurselimit => 2, :ignore => 'foo.+', } Puppet::FileServing::Metadata.indirection.expects(:search).with(source_dir, options).returns([metadata, child_metadata]) compiler.send(:inline_metadata, catalog, checksum_type) expect(catalog.metadata[path]).to be_nil expect(catalog.recursive_metadata[path][source_dir]).to eq([metadata, child_metadata]) end it "inlines metadata for all sources if source_select is all" do alt_source_dir = 'puppet:///modules/mymodule/other_directory' catalog = compile_to_catalog(<<-MANIFEST, node) file { '#{path}': ensure => directory, recurse => true, source => ['#{source_dir}', '#{alt_source_dir}'], sourceselect => all, } MANIFEST Puppet::FileServing::Metadata.indirection.expects(:search).with(source_dir, anything).returns([metadata, child_metadata]) Puppet::FileServing::Metadata.indirection.expects(:search).with(alt_source_dir, anything).returns([metadata, child_metadata]) compiler.send(:inline_metadata, catalog, checksum_type) expect(catalog.metadata[path]).to be_nil expect(catalog.recursive_metadata[path][source_dir]).to eq([metadata, child_metadata]) expect(catalog.recursive_metadata[path][alt_source_dir]).to eq([metadata, child_metadata]) end it "inlines metadata for the first valid source if source_select is first" do alt_source_dir = 'puppet:///modules/mymodule/other_directory' catalog = compile_to_catalog(<<-MANIFEST, node) file { '#{path}': ensure => directory, recurse => true, source => ['#{source_dir}', '#{alt_source_dir}'], } MANIFEST Puppet::FileServing::Metadata.indirection.expects(:search).with(source_dir, anything).returns(nil) Puppet::FileServing::Metadata.indirection.expects(:search).with(alt_source_dir, anything).returns([metadata, child_metadata]) compiler.send(:inline_metadata, catalog, checksum_type) expect(catalog.metadata[path]).to be_nil expect(catalog.recursive_metadata[path][source_dir]).to be_nil expect(catalog.recursive_metadata[path][alt_source_dir]).to eq([metadata, child_metadata]) end it "skips resources whose mount point is not 'modules'" do source = 'puppet:///secure/data' catalog = compile_to_catalog(<<-MANIFEST, node) file { '#{path}': ensure => directory, recurse => true, source => '#{source}', } MANIFEST metadata = stubs_directory_metadata('secure/files/data') metadata.stubs(:source).returns(source) Puppet::FileServing::Metadata.indirection.expects(:search).with(source, anything).returns([metadata]) compiler.send(:inline_metadata, catalog, checksum_type) expect(catalog.metadata).to be_empty expect(catalog.recursive_metadata).to be_empty end it "skips resources with 'modules' mount point resolving to a path not in 'modules/*/files'" do source = 'puppet:///modules/mymodule/directory' catalog = compile_to_catalog(<<-MANIFEST, node) file { '#{path}': ensure => directory, recurse => true, source => '#{source}', } MANIFEST metadata = stubs_directory_metadata('modules/mymodule/not_in_files/directory') Puppet::FileServing::Metadata.indirection.expects(:search).with(source, anything).returns([metadata]) compiler.send(:inline_metadata, catalog, checksum_type) expect(catalog.metadata).to be_empty expect(catalog.recursive_metadata).to be_empty end it "inlines resources in 'modules' mount point resolving to a 'site' directory within the per-environment codedir" do # example adopted from https://github.com/puppetlabs/control-repo/blob/508b9cc/site/profile/manifests/puppetmaster.pp#L45-L49 source = 'puppet:///modules/profile/puppetmaster' catalog = compile_to_catalog(<<-MANIFEST, node) file { '#{path}': ensure => file, recurse => true, source => '#{source}' } MANIFEST # See https://github.com/puppetlabs/control-repo/blob/508b9cc/site/profile/files/puppetmaster/update-classes.sh dir_metadata = stubs_directory_metadata('site/profile/files/puppetmaster') dir_metadata.stubs(:source).returns(source) child_metadata = stubs_file_metadata(checksum_type, checksum_value, './update-classes.sh') child_metadata.stubs(:source).returns("#{source}/update-classes.sh") Puppet::FileServing::Metadata.indirection.expects(:search).with(source, anything).returns([dir_metadata, child_metadata]) compiler.send(:inline_metadata, catalog, checksum_type) expect(catalog.metadata).to be_empty expect(catalog.recursive_metadata[path][source]).to eq([dir_metadata, child_metadata]) end it "inlines resources with a trailing slash" do source = 'puppet:///modules/mymodule/directory' catalog = compile_to_catalog(<<-MANIFEST, node) file { '#{path}': ensure => directory, recurse => true, source => '#{source}/' } MANIFEST dir_metadata = stubs_directory_metadata('modules/mymodule/files/directory') dir_metadata.stubs(:source).returns(source) child_metadata = stubs_file_metadata(checksum_type, checksum_value, './file') child_metadata.stubs(:source).returns("#{source}/file") Puppet::FileServing::Metadata.indirection.expects(:search).with(source, anything).returns([dir_metadata, child_metadata]) compiler.send(:inline_metadata, catalog, checksum_type) expect(catalog.metadata).to be_empty expect(catalog.recursive_metadata[path][source]).to eq([dir_metadata, child_metadata]) end end end it "skips non-file resources" do catalog = compile_to_catalog(<<-MANIFEST, node) notify { 'hi': } MANIFEST compiler.send(:inline_metadata, catalog, checksum_type) expect(catalog.metadata).to be_empty expect(catalog.recursive_metadata).to be_empty end it "inlines windows file paths", :if => Puppet.features.posix? do catalog = compile_to_catalog(<<-MANIFEST, node) file { 'c:/foo': ensure => file, source => '#{source}' } MANIFEST metadata = stubs_file_metadata(checksum_type, checksum_value, 'modules/mymodule/files/config_file.txt') Puppet::FileServing::Metadata.indirection.expects(:find).with(source, anything).returns(metadata) compiler.send(:inline_metadata, catalog, checksum_type) expect(catalog.metadata['c:/foo']).to eq(metadata) expect(catalog.recursive_metadata).to be_empty end end end puppet-5.5.10/spec/unit/indirector/catalog/msgpack_spec.rb0000644005276200011600000000071413417161721023465 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/resource/catalog' require 'puppet/indirector/catalog/msgpack' describe Puppet::Resource::Catalog::Msgpack, :if => Puppet.features.msgpack? do # This is it for local functionality: we don't *do* anything else. it "should be registered with the catalog store indirection" do expect(Puppet::Resource::Catalog.indirection.terminus(:msgpack)). to be_an_instance_of described_class end end puppet-5.5.10/spec/unit/indirector/catalog/rest_spec.rb0000644005276200011600000000044113417161721023012 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/catalog/rest' describe Puppet::Resource::Catalog::Rest do it "should be a sublcass of Puppet::Indirector::REST" do expect(Puppet::Resource::Catalog::Rest.superclass).to equal(Puppet::Indirector::REST) end end puppet-5.5.10/spec/unit/indirector/catalog/store_configs_spec.rb0000644005276200011600000000075313417161721024707 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/node' require 'puppet/indirector/memory' require 'puppet/indirector/catalog/store_configs' class Puppet::Resource::Catalog::StoreConfigsTesting < Puppet::Indirector::Memory end describe Puppet::Resource::Catalog::StoreConfigs do after :each do Puppet::Resource::Catalog.indirection.reset_terminus_class Puppet::Resource::Catalog.indirection.cache_class = nil end it_should_behave_like "a StoreConfigs terminus" end puppet-5.5.10/spec/unit/indirector/catalog/yaml_spec.rb0000644005276200011600000000136213417161721023002 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/resource/catalog' require 'puppet/indirector/catalog/yaml' describe Puppet::Resource::Catalog::Yaml do it "should be a subclass of the Yaml terminus" do expect(Puppet::Resource::Catalog::Yaml.superclass).to equal(Puppet::Indirector::Yaml) end it "should have documentation" do expect(Puppet::Resource::Catalog::Yaml.doc).not_to be_nil end it "should be registered with the catalog store indirection" do indirection = Puppet::Indirector::Indirection.instance(:catalog) expect(Puppet::Resource::Catalog::Yaml.indirection).to equal(indirection) end it "should have its name set to :yaml" do expect(Puppet::Resource::Catalog::Yaml.name).to eq(:yaml) end end puppet-5.5.10/spec/unit/indirector/catalog/json_spec.rb0000644005276200011600000000405413417161722023013 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/files' require 'puppet/resource/catalog' require 'puppet/indirector/catalog/json' describe Puppet::Resource::Catalog::Json do include PuppetSpec::Files # This is it for local functionality it "should be registered with the catalog store indirection" do expect(Puppet::Resource::Catalog.indirection.terminus(:json)). to be_an_instance_of described_class end describe "when handling requests" do let(:binary) { "\xC0\xFF".force_encoding(Encoding::BINARY) } let(:key) { 'foo' } let(:file) { subject.path(key) } let(:catalog) do catalog = Puppet::Resource::Catalog.new(key, Puppet::Node::Environment.create(:testing, [])) catalog.add_resource(Puppet::Resource.new(:file, '/tmp/a_file', :parameters => { :content => binary })) catalog end before :each do Puppet.run_mode.stubs(:master?).returns(true) Puppet[:server_datadir] = tmpdir('jsondir') FileUtils.mkdir_p(File.join(Puppet[:server_datadir], 'indirector_testing')) end it 'saves a catalog containing binary content' do request = subject.indirection.request(:save, key, catalog) subject.save(request) end it 'finds a catalog containing binary content' do request = subject.indirection.request(:save, key, catalog) subject.save(request) request = subject.indirection.request(:find, key, nil) parsed_catalog = subject.find(request) content = parsed_catalog.resource(:file, '/tmp/a_file')[:content] expect(content.bytes.to_a).to eq(binary.bytes.to_a) end it 'searches for catalogs contains binary content' do request = subject.indirection.request(:save, key, catalog) subject.save(request) request = subject.indirection.request(:search, '*', nil) parsed_catalogs = subject.search(request) expect(parsed_catalogs.size).to eq(1) content = parsed_catalogs.first.resource(:file, '/tmp/a_file')[:content] expect(content.bytes.to_a).to eq(binary.bytes.to_a) end end end puppet-5.5.10/spec/unit/indirector/certificate/0000755005276200011600000000000013417162176021354 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/indirector/certificate/rest_spec.rb0000644005276200011600000000454713417161721023675 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/certificate/rest' describe Puppet::SSL::Certificate::Rest do before do @searcher = Puppet::SSL::Certificate::Rest.new end it "should be a sublcass of Puppet::Indirector::REST" do expect(Puppet::SSL::Certificate::Rest.superclass).to equal(Puppet::Indirector::REST) end it "should set server_setting to :ca_server" do expect(Puppet::SSL::Certificate::Rest.server_setting).to eq(:ca_server) end it "should set port_setting to :ca_port" do expect(Puppet::SSL::Certificate::Rest.port_setting).to eq(:ca_port) end it "should use the :ca SRV service" do expect(Puppet::SSL::Certificate::Rest.srv_service).to eq(:ca) end it "should make sure found certificates have their names set to the search string" do terminus = Puppet::SSL::Certificate::Rest.new # This has 'boo.com' in the CN cert_string = "-----BEGIN CERTIFICATE----- MIICPzCCAaigAwIBAgIBBDANBgkqhkiG9w0BAQUFADAWMRQwEgYDVQQDDAtidWNr eS5sb2NhbDAeFw0wOTA5MTcxNzI1MzJaFw0xNDA5MTYxNzI1MzJaMBIxEDAOBgNV BAMMB2Jvby5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKG9B+DkTCNh F5xHchNDfnbC9NzWKM600oxrr84pgUVAG6B2wAZcdfoEtXszhsY9Jzpwqkvxk4Mx AbYqo9+TCi4UoiH6e+vAKOOJD3DHrlf+/RW4hGtyaI41DBhf4+B4/oFz5PH9mvKe NSfHFI/yPW+1IXYjxKLQNwF9E7q3JbnzAgMBAAGjgaAwgZ0wOAYJYIZIAYb4QgEN BCsWKVB1cHBldCBSdWJ5L09wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMAwG A1UdEwEB/wQCMAAwHQYDVR0OBBYEFJOxEUeyf4cNOBmf9zIaE1JTuNdLMAsGA1Ud DwQEAwIFoDAnBgNVHSUEIDAeBggrBgEFBQcDAQYIKwYBBQUHAwIGCCsGAQUFBwME MA0GCSqGSIb3DQEBBQUAA4GBAFTJxKprMg6tfhGnvEvURPmlJrINn9c2b5Y4AGYp tO86PFFkWw/EIJvvJzbj3s+Butr+eUo//+f1xxX7UCwwGqGxKqjtVS219oU/wkx8 h7rW4Xk7MrLl0auSS1p4wLcAMm+ZImf94+j8Cj+tkr8eGozZceRV13b8+EkdaE3S rn/G -----END CERTIFICATE----- " network = stub 'network' terminus.stubs(:network).returns network response = stub 'response', :code => "200", :body => cert_string response.stubs(:[]).with('content-type').returns "text/plain" response.stubs(:[]).with('content-encoding') response.stubs(:[]).with(Puppet::Network::HTTP::HEADER_PUPPET_VERSION).returns(Puppet.version) network.stubs(:verify_callback=) network.expects(:get).returns response request = Puppet::Indirector::Request.new(:certificate, :find, "foo.com", nil) result = terminus.find(request) expect(result).not_to be_nil expect(result.name).to eq("foo.com") end end puppet-5.5.10/spec/unit/indirector/certificate/ca_spec.rb0000644005276200011600000000135713417161722023300 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/certificate/ca' describe Puppet::SSL::Certificate::Ca do it "should have documentation" do expect(Puppet::SSL::Certificate::Ca.doc).to be_instance_of(String) end it "should use the :signeddir as the collection directory" do Puppet[:signeddir] = File.expand_path("/cert/dir") expect(Puppet::SSL::Certificate::Ca.collection_directory).to eq(Puppet[:signeddir]) end it "should store the ca certificate at the :cacert location" do Puppet.settings.stubs(:use) Puppet[:cacert] = File.expand_path("/ca/cert") file = Puppet::SSL::Certificate::Ca.new file.stubs(:ca?).returns true expect(file.path("whatever")).to eq(Puppet[:cacert]) end end puppet-5.5.10/spec/unit/indirector/certificate/disabled_ca_spec.rb0000644005276200011600000000162013417161722025120 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/certificate/disabled_ca' describe Puppet::SSL::Certificate::DisabledCa do def request(type, remote) r = Puppet::Indirector::Request.new(:certificate, type, "foo.com", nil) if remote r.ip = '10.0.0.1' r.node = 'agent.example.com' end r end context "when not a CA" do before :each do Puppet[:ca] = false Puppet::SSL::Host.ca_location = :none end [:find, :head, :search, :save, :destroy].each do |name| it "should fail remote #{name} requests" do expect { subject.send(name, request(name, true)) }. to raise_error Puppet::Error, /is not a CA/ end it "should forward local #{name} requests" do Puppet::SSL::Certificate.indirection.terminus(:file).expects(name) subject.send(name, request(name, false)) end end end end puppet-5.5.10/spec/unit/indirector/certificate/file_spec.rb0000644005276200011600000000140213417161722023623 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/certificate/file' describe Puppet::SSL::Certificate::File do it "should have documentation" do expect(Puppet::SSL::Certificate::File.doc).to be_instance_of(String) end it "should use the :certdir as the collection directory" do Puppet[:certdir] = File.expand_path("/cert/dir") expect(Puppet::SSL::Certificate::File.collection_directory).to eq(Puppet[:certdir]) end it "should store the ca certificate at the :localcacert location" do Puppet.settings.stubs(:use) Puppet[:localcacert] = File.expand_path("/ca/cert") file = Puppet::SSL::Certificate::File.new file.stubs(:ca?).returns true expect(file.path("whatever")).to eq(Puppet[:localcacert]) end end puppet-5.5.10/spec/unit/indirector/certificate_request/0000755005276200011600000000000013417162176023124 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/indirector/certificate_request/file_spec.rb0000644005276200011600000000076713417161721025407 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/certificate_request/file' describe Puppet::SSL::CertificateRequest::File do it "should have documentation" do expect(Puppet::SSL::CertificateRequest::File.doc).to be_instance_of(String) end it "should use the :requestdir as the collection directory" do Puppet[:requestdir] = File.expand_path("/request/dir") expect(Puppet::SSL::CertificateRequest::File.collection_directory).to eq(Puppet[:requestdir]) end end puppet-5.5.10/spec/unit/indirector/certificate_request/rest_spec.rb0000644005276200011600000000141713417161721025436 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/certificate_request/rest' describe Puppet::SSL::CertificateRequest::Rest do before do @searcher = Puppet::SSL::CertificateRequest::Rest.new end it "should be a sublcass of Puppet::Indirector::REST" do expect(Puppet::SSL::CertificateRequest::Rest.superclass).to equal(Puppet::Indirector::REST) end it "should set server_setting to :ca_server" do expect(Puppet::SSL::CertificateRequest::Rest.server_setting).to eq(:ca_server) end it "should set port_setting to :ca_port" do expect(Puppet::SSL::CertificateRequest::Rest.port_setting).to eq(:ca_port) end it "should use the :ca SRV service" do expect(Puppet::SSL::CertificateRequest::Rest.srv_service).to eq(:ca) end end puppet-5.5.10/spec/unit/indirector/certificate_request/ca_spec.rb0000644005276200011600000000374013417161722025046 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/ssl/host' require 'puppet/indirector/certificate_request/ca' describe Puppet::SSL::CertificateRequest::Ca, :unless => Puppet.features.microsoft_windows? do include PuppetSpec::Files before :each do Puppet[:ssldir] = tmpdir('ssl') Puppet::SSL::Host.ca_location = :local Puppet[:localcacert] = Puppet[:cacert] @ca = Puppet::SSL::CertificateAuthority.new end after :all do Puppet::SSL::Host.ca_location = :none end it "should have documentation" do expect(Puppet::SSL::CertificateRequest::Ca.doc).to be_instance_of(String) end it "should use the :csrdir as the collection directory" do Puppet[:csrdir] = File.expand_path("/request/dir") expect(Puppet::SSL::CertificateRequest::Ca.collection_directory).to eq(Puppet[:csrdir]) end it "should overwrite the previous certificate request if allow_duplicate_certs is true" do Puppet[:allow_duplicate_certs] = true host = Puppet::SSL::Host.new("foo") host.generate_certificate_request @ca.sign(host.name) Puppet::SSL::Host.indirection.find("foo").generate_certificate_request expect(Puppet::SSL::Certificate.indirection.find("foo").name).to eq("foo") expect(Puppet::SSL::CertificateRequest.indirection.find("foo").name).to eq("foo") expect(Puppet::SSL::Host.indirection.find("foo").state).to eq("requested") end it "should reject a new certificate request if allow_duplicate_certs is false" do Puppet[:allow_duplicate_certs] = false host = Puppet::SSL::Host.new("bar") host.generate_certificate_request @ca.sign(host.name) expect { Puppet::SSL::Host.indirection.find("bar").generate_certificate_request }.to raise_error(/ignoring certificate request/) expect(Puppet::SSL::Certificate.indirection.find("bar").name).to eq("bar") expect(Puppet::SSL::CertificateRequest.indirection.find("bar")).to be_nil expect(Puppet::SSL::Host.indirection.find("bar").state).to eq("signed") end end puppet-5.5.10/spec/unit/indirector/certificate_request/disabled_ca_spec.rb0000644005276200011600000000165613417161722026701 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/certificate_request/disabled_ca' describe Puppet::SSL::CertificateRequest::DisabledCa do def request(type, remote) r = Puppet::Indirector::Request.new(:certificate_request, type, "foo.com", nil) if remote r.ip = '10.0.0.1' r.node = 'agent.example.com' end r end context "when not a CA" do before :each do Puppet[:ca] = false Puppet::SSL::Host.ca_location = :none end [:find, :head, :search, :save, :destroy].each do |name| it "should fail remote #{name} requests" do expect { subject.send(name, request(name, true)) }. to raise_error Puppet::Error, /is not a CA/ end it "should forward local #{name} requests" do Puppet::SSL::CertificateRequest.indirection.terminus(:file).expects(name) subject.send(name, request(name, false)) end end end end puppet-5.5.10/spec/unit/indirector/data_binding/0000755005276200011600000000000013417162176021475 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/indirector/data_binding/hiera_spec.rb0000644005276200011600000000116613417161721024123 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/indirector/data_binding/hiera' describe Puppet::DataBinding::Hiera do it "should have documentation" do expect(Puppet::DataBinding::Hiera.doc).not_to be_nil end it "should be registered with the data_binding indirection" do indirection = Puppet::Indirector::Indirection.instance(:data_binding) expect(Puppet::DataBinding::Hiera.indirection).to equal(indirection) end it "should have its name set to :hiera" do expect(Puppet::DataBinding::Hiera.name).to eq(:hiera) end it_should_behave_like "Hiera indirection", Puppet::DataBinding::Hiera, my_fixture_dir end puppet-5.5.10/spec/unit/indirector/data_binding/none_spec.rb0000644005276200011600000000162313417161721023770 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/indirector/data_binding/none' describe Puppet::DataBinding::None do it "should be a subclass of the None terminus" do expect(Puppet::DataBinding::None.superclass).to equal(Puppet::Indirector::None) end it "should have documentation" do expect(Puppet::DataBinding::None.doc).not_to be_nil end it "should be registered with the data_binding indirection" do indirection = Puppet::Indirector::Indirection.instance(:data_binding) expect(Puppet::DataBinding::None.indirection).to equal(indirection) end it "should have its name set to :none" do expect(Puppet::DataBinding::None.name).to eq(:none) end describe "the behavior of the find method" do it "should just throw :no_such_key" do data_binding = Puppet::DataBinding::None.new expect { data_binding.find('fake_request') }.to throw_symbol(:no_such_key) end end end puppet-5.5.10/spec/unit/indirector/envelope_spec.rb0000644005276200011600000000170013417161721022237 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/envelope' describe Puppet::Indirector::Envelope do before do @instance = Object.new @instance.extend(Puppet::Indirector::Envelope) end describe "when testing if it is expired" do it "should return false if there is no expiration set" do expect(@instance).not_to be_expired end it "should return true if the current date is after the expiration date" do @instance.expiration = Time.now - 10 expect(@instance).to be_expired end it "should return false if the current date is prior to the expiration date" do @instance.expiration = Time.now + 10 expect(@instance).not_to be_expired end it "should return false if the current date is equal to the expiration date" do now = Time.now Time.stubs(:now).returns(now) @instance.expiration = now expect(@instance).not_to be_expired end end end puppet-5.5.10/spec/unit/indirector/face_spec.rb0000644005276200011600000000476113417161721021332 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/face' describe Puppet::Indirector::Face do subject do instance = Puppet::Indirector::Face.new(:test, '0.0.1') indirection = stub('indirection', :name => :stub_indirection, :reset_terminus_class => nil) instance.stubs(:indirection).returns indirection instance end it { is_expected.to be_option :extra } it "should be able to return a list of indirections" do expect(Puppet::Indirector::Face.indirections).to be_include("catalog") end it "should return the sorted to_s list of terminus classes" do Puppet::Indirector::Terminus.expects(:terminus_classes).returns([ :yaml, :compiler, :rest ]) expect(Puppet::Indirector::Face.terminus_classes(:catalog)).to eq([ 'compiler', 'rest', 'yaml' ]) end describe "as an instance" do it "should be able to determine its indirection" do # Loading actions here can get, um, complicated Puppet::Face.stubs(:load_actions) expect(Puppet::Indirector::Face.new(:catalog, '0.0.1').indirection).to equal(Puppet::Resource::Catalog.indirection) end end [:find, :search, :save, :destroy].each do |method| def params(method, options) if method == :save [nil, options] else [options] end end it "should define a '#{method}' action" do expect(Puppet::Indirector::Face).to be_action(method) end it "should call the indirection method with options when the '#{method}' action is invoked" do subject.indirection.expects(method).with(:test, *params(method, {})) subject.send(method, :test) end it "should forward passed options" do subject.indirection.expects(method).with(:test, *params(method, {'one'=>'1'})) subject.send(method, :test, :extra => {'one'=>'1'}) end end it "should default key to certname for find action" do subject.indirection.expects(:find).with(Puppet[:certname], {'one'=>'1'}) subject.send(:find, :extra => {'one'=>'1'}) end it "should be able to override its indirection name" do subject.set_indirection_name :foo expect(subject.indirection_name).to eq(:foo) end it "should be able to set its terminus class" do subject.indirection.expects(:terminus_class=).with(:myterm) subject.set_terminus(:myterm) end it "should define a class-level 'info' action" do expect(Puppet::Indirector::Face).to be_action(:info) end end puppet-5.5.10/spec/unit/indirector/facts/0000755005276200011600000000000013417162176020172 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/indirector/facts/facter_spec.rb0000644005276200011600000001322113417161721022767 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/facts/facter' module NodeFactsFacterSpec describe Puppet::Node::Facts::Facter do FS = Puppet::FileSystem it "should be a subclass of the Code terminus" do expect(Puppet::Node::Facts::Facter.superclass).to equal(Puppet::Indirector::Code) end it "should have documentation" do expect(Puppet::Node::Facts::Facter.doc).not_to be_nil end it "should be registered with the configuration store indirection" do indirection = Puppet::Indirector::Indirection.instance(:facts) expect(Puppet::Node::Facts::Facter.indirection).to equal(indirection) end it "should have its name set to :facter" do expect(Puppet::Node::Facts::Facter.name).to eq(:facter) end before :each do Puppet::Node::Facts::Facter.stubs(:reload_facter) @facter = Puppet::Node::Facts::Facter.new Facter.stubs(:to_hash).returns({}) @name = "me" @request = stub 'request', :key => @name @environment = stub 'environment' @request.stubs(:environment).returns(@environment) @request.environment.stubs(:modules).returns([]) @request.environment.stubs(:modulepath).returns([]) end describe 'when finding facts' do it 'should reset facts' do reset = sequence 'reset' Facter.expects(:reset).in_sequence(reset) Puppet::Node::Facts::Facter.expects(:setup_search_paths).in_sequence(reset) @facter.find(@request) end it 'should add the puppetversion and agent_specified_environment facts' do reset = sequence 'reset' Facter.expects(:reset).in_sequence(reset) Facter.expects(:add).with(:puppetversion) Facter.expects(:add).with(:agent_specified_environment) @facter.find(@request) end it 'should include external facts' do reset = sequence 'reset' Facter.expects(:reset).in_sequence(reset) Puppet::Node::Facts::Facter.expects(:setup_external_search_paths).in_sequence(reset) Puppet::Node::Facts::Facter.expects(:setup_search_paths).in_sequence(reset) @facter.find(@request) end it "should return a Facts instance" do expect(@facter.find(@request)).to be_instance_of(Puppet::Node::Facts) end it "should return a Facts instance with the provided key as the name" do expect(@facter.find(@request).name).to eq(@name) end it "should return the Facter facts as the values in the Facts instance" do Facter.expects(:to_hash).returns("one" => "two") facts = @facter.find(@request) expect(facts.values["one"]).to eq("two") end it "should add local facts" do facts = Puppet::Node::Facts.new("foo") Puppet::Node::Facts.expects(:new).returns facts facts.expects(:add_local_facts) @facter.find(@request) end it "should sanitize facts" do facts = Puppet::Node::Facts.new("foo") Puppet::Node::Facts.expects(:new).returns facts facts.expects(:sanitize) @facter.find(@request) end end it 'should fail when saving facts' do expect { @facter.save(@facts) }.to raise_error(Puppet::DevError) end it 'should fail when destroying facts' do expect { @facter.destroy(@facts) }.to raise_error(Puppet::DevError) end describe 'when setting up search paths' do let(:factpath1) { File.expand_path 'one' } let(:factpath2) { File.expand_path 'two' } let(:factpath) { [factpath1, factpath2].join(File::PATH_SEPARATOR) } let(:modulepath) { File.expand_path 'module/foo' } let(:modulelibfacter) { File.expand_path 'module/foo/lib/facter' } let(:modulepluginsfacter) { File.expand_path 'module/foo/plugins/facter' } before :each do FileTest.expects(:directory?).with(factpath1).returns true FileTest.expects(:directory?).with(factpath2).returns true @request.environment.stubs(:modulepath).returns [modulepath] Dir.expects(:glob).with("#{modulepath}/*/lib/facter").returns [modulelibfacter] Dir.expects(:glob).with("#{modulepath}/*/plugins/facter").returns [modulepluginsfacter] Puppet[:factpath] = factpath end it 'should skip files' do FileTest.expects(:directory?).with(modulelibfacter).returns false FileTest.expects(:directory?).with(modulepluginsfacter).returns false Facter.expects(:search).with(factpath1, factpath2) Puppet::Node::Facts::Facter.setup_search_paths @request end it 'should add directories' do FileTest.expects(:directory?).with(modulelibfacter).returns true FileTest.expects(:directory?).with(modulepluginsfacter).returns true Facter.expects(:search).with(modulelibfacter, modulepluginsfacter, factpath1, factpath2) Puppet::Node::Facts::Facter.setup_search_paths @request end end describe 'when setting up external search paths' do let(:pluginfactdest) { File.expand_path 'plugin/dest' } let(:modulepath) { File.expand_path 'module/foo' } let(:modulefactsd) { File.expand_path 'module/foo/facts.d' } before :each do FileTest.expects(:directory?).with(pluginfactdest).returns true mod = Puppet::Module.new('foo', modulepath, @request.environment) @request.environment.stubs(:modules).returns [mod] Puppet[:pluginfactdest] = pluginfactdest end it 'should skip files' do File.expects(:directory?).with(modulefactsd).returns false Facter.expects(:search_external).with [pluginfactdest] Puppet::Node::Facts::Facter.setup_external_search_paths @request end it 'should add directories' do File.expects(:directory?).with(modulefactsd).returns true Facter.expects(:search_external).with [modulefactsd, pluginfactdest] Puppet::Node::Facts::Facter.setup_external_search_paths @request end end end end puppet-5.5.10/spec/unit/indirector/facts/network_device_spec.rb0000644005276200011600000000465613417161721024547 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/network_device' require 'puppet/indirector/facts/network_device' describe Puppet::Node::Facts::NetworkDevice do it "should be a subclass of the Code terminus" do expect(Puppet::Node::Facts::NetworkDevice.superclass).to equal(Puppet::Indirector::Code) end it "should have documentation" do expect(Puppet::Node::Facts::NetworkDevice.doc).not_to be_nil end it "should be registered with the configuration store indirection" do indirection = Puppet::Indirector::Indirection.instance(:facts) expect(Puppet::Node::Facts::NetworkDevice.indirection).to equal(indirection) end it "should have its name set to :facter" do expect(Puppet::Node::Facts::NetworkDevice.name).to eq(:network_device) end end describe Puppet::Node::Facts::NetworkDevice do before :each do @remote_device = stub 'remote_device', :facts => {} Puppet::Util::NetworkDevice.stubs(:current).returns(@remote_device) @device = Puppet::Node::Facts::NetworkDevice.new @name = "me" @request = stub 'request', :key => @name end describe Puppet::Node::Facts::NetworkDevice, " when finding facts" do it "should return a Facts instance" do expect(@device.find(@request)).to be_instance_of(Puppet::Node::Facts) end it "should return a Facts instance with the provided key as the name" do expect(@device.find(@request).name).to eq(@name) end it "should return the device facts as the values in the Facts instance" do @remote_device.expects(:facts).returns("one" => "two") facts = @device.find(@request) expect(facts.values["one"]).to eq("two") end it "should add local facts" do facts = Puppet::Node::Facts.new("foo") Puppet::Node::Facts.expects(:new).returns facts facts.expects(:add_local_facts) @device.find(@request) end it "should sanitize facts" do facts = Puppet::Node::Facts.new("foo") Puppet::Node::Facts.expects(:new).returns facts facts.expects(:sanitize) @device.find(@request) end end describe Puppet::Node::Facts::NetworkDevice, " when saving facts" do it "should fail" do expect { @device.save(@facts) }.to raise_error(Puppet::DevError) end end describe Puppet::Node::Facts::NetworkDevice, " when destroying facts" do it "should fail" do expect { @device.destroy(@facts) }.to raise_error(Puppet::DevError) end end end puppet-5.5.10/spec/unit/indirector/facts/rest_spec.rb0000644005276200011600000000303613417161721022503 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/facts/rest' describe Puppet::Node::Facts::Rest do it "should be a sublcass of Puppet::Indirector::REST" do expect(Puppet::Node::Facts::Rest.superclass).to equal(Puppet::Indirector::REST) end let(:model) { Puppet::Node::Facts } before(:each) { model.indirection.terminus_class = :rest } def mock_response(code, body, content_type='text/plain', encoding=nil) obj = stub('http response', :code => code.to_s, :body => body) obj.stubs(:[]).with('content-type').returns(content_type) obj.stubs(:[]).with('content-encoding').returns(encoding) obj.stubs(:[]).with(Puppet::Network::HTTP::HEADER_PUPPET_VERSION).returns(Puppet.version) obj end describe '#save' do subject { model.indirection.terminus(:rest) } let(:connection) { stub('mock http connection', :verify_callback= => nil) } let(:node_name) { 'puppet.node.test' } let(:data) { model.new(node_name, {test_fact: 'test value'}) } let(:request) { Puppet::Indirector::Request.new(:facts, :save, node_name, data) } before :each do subject.stubs(:network).returns(connection) end context 'when a 404 response is received' do let(:response) { mock_response(404, '{}', 'test/json') } before(:each) { connection.expects(:put).returns response } it 'riases with HTTP 404' do expect{ subject.save(request) }.to raise_error(Net::HTTPError, /Error 404 on SERVER/) end end end end puppet-5.5.10/spec/unit/indirector/facts/store_configs_spec.rb0000644005276200011600000000072013417161721024367 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/node' require 'puppet/indirector/memory' require 'puppet/indirector/facts/store_configs' class Puppet::Node::Facts::StoreConfigsTesting < Puppet::Indirector::Memory end describe Puppet::Node::Facts::StoreConfigs do after :all do Puppet::Node::Facts.indirection.reset_terminus_class Puppet::Node::Facts.indirection.cache_class = nil end it_should_behave_like "a StoreConfigs terminus" end puppet-5.5.10/spec/unit/indirector/facts/yaml_spec.rb0000644005276200011600000003323313417161722022473 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/node/facts' require 'puppet/indirector/facts/yaml' describe Puppet::Node::Facts::Yaml do it "should be a subclass of the Yaml terminus" do expect(Puppet::Node::Facts::Yaml.superclass).to equal(Puppet::Indirector::Yaml) end it "should have documentation" do expect(Puppet::Node::Facts::Yaml.doc).not_to be_nil expect(Puppet::Node::Facts::Yaml.doc).not_to be_empty end it "should be registered with the facts indirection" do indirection = Puppet::Indirector::Indirection.instance(:facts) expect(Puppet::Node::Facts::Yaml.indirection).to equal(indirection) end it "should have its name set to :yaml" do expect(Puppet::Node::Facts::Yaml.name).to eq(:yaml) end it "should allow network requests" do # Doesn't allow yaml as a network format, but allows `puppet facts upload` # to update the YAML cache on a master. expect(Puppet::Node::Facts::Yaml.new.allow_remote_requests?).to be(true) end describe "#search" do def assert_search_matches(matching, nonmatching, query) request = Puppet::Indirector::Request.new(:inventory, :search, nil, nil, query) Dir.stubs(:glob).returns(matching.keys + nonmatching.keys) [matching, nonmatching].each do |examples| examples.each do |key, value| YAML.stubs(:load_file).with(key).returns value end end expect(Puppet::Node::Facts::Yaml.new.search(request)).to match_array(matching.values.map {|facts| facts.name}) end it "should return node names that match the search query options" do assert_search_matches({ '/path/to/matching.yaml' => Puppet::Node::Facts.new("matchingnode", "architecture" => "i386", 'processor_count' => '4'), '/path/to/matching1.yaml' => Puppet::Node::Facts.new("matchingnode1", "architecture" => "i386", 'processor_count' => '4', 'randomfact' => 'foo') }, { "/path/to/nonmatching.yaml" => Puppet::Node::Facts.new("nonmatchingnode", "architecture" => "powerpc", 'processor_count' => '4'), "/path/to/nonmatching1.yaml" => Puppet::Node::Facts.new("nonmatchingnode1", "architecture" => "powerpc", 'processor_count' => '5'), "/path/to/nonmatching2.yaml" => Puppet::Node::Facts.new("nonmatchingnode2", "architecture" => "i386", 'processor_count' => '5'), "/path/to/nonmatching3.yaml" => Puppet::Node::Facts.new("nonmatchingnode3", 'processor_count' => '4'), }, {'facts.architecture' => 'i386', 'facts.processor_count' => '4'} ) end it "should return empty array when no nodes match the search query options" do assert_search_matches({}, { "/path/to/nonmatching.yaml" => Puppet::Node::Facts.new("nonmatchingnode", "architecture" => "powerpc", 'processor_count' => '10'), "/path/to/nonmatching1.yaml" => Puppet::Node::Facts.new("nonmatchingnode1", "architecture" => "powerpc", 'processor_count' => '5'), "/path/to/nonmatching2.yaml" => Puppet::Node::Facts.new("nonmatchingnode2", "architecture" => "i386", 'processor_count' => '5'), "/path/to/nonmatching3.yaml" => Puppet::Node::Facts.new("nonmatchingnode3", 'processor_count' => '4'), }, {'facts.processor_count.lt' => '4', 'facts.processor_count.gt' => '4'} ) end it "should return node names that match the search query options with the greater than operator" do assert_search_matches({ '/path/to/matching.yaml' => Puppet::Node::Facts.new("matchingnode", "architecture" => "i386", 'processor_count' => '5'), '/path/to/matching1.yaml' => Puppet::Node::Facts.new("matchingnode1", "architecture" => "powerpc", 'processor_count' => '10', 'randomfact' => 'foo') }, { "/path/to/nonmatching.yaml" => Puppet::Node::Facts.new("nonmatchingnode", "architecture" => "powerpc", 'processor_count' => '4'), "/path/to/nonmatching2.yaml" => Puppet::Node::Facts.new("nonmatchingnode2", "architecture" => "i386", 'processor_count' => '3'), "/path/to/nonmatching3.yaml" => Puppet::Node::Facts.new("nonmatchingnode3" ), }, {'facts.processor_count.gt' => '4'} ) end it "should return node names that match the search query options with the less than operator" do assert_search_matches({ '/path/to/matching.yaml' => Puppet::Node::Facts.new("matchingnode", "architecture" => "i386", 'processor_count' => '5'), '/path/to/matching1.yaml' => Puppet::Node::Facts.new("matchingnode1", "architecture" => "powerpc", 'processor_count' => '30', 'randomfact' => 'foo') }, { "/path/to/nonmatching.yaml" => Puppet::Node::Facts.new("nonmatchingnode", "architecture" => "powerpc", 'processor_count' => '50' ), "/path/to/nonmatching2.yaml" => Puppet::Node::Facts.new("nonmatchingnode2", "architecture" => "i386", 'processor_count' => '100'), "/path/to/nonmatching3.yaml" => Puppet::Node::Facts.new("nonmatchingnode3" ), }, {'facts.processor_count.lt' => '50'} ) end it "should return node names that match the search query options with the less than or equal to operator" do assert_search_matches({ '/path/to/matching.yaml' => Puppet::Node::Facts.new("matchingnode", "architecture" => "i386", 'processor_count' => '5'), '/path/to/matching1.yaml' => Puppet::Node::Facts.new("matchingnode1", "architecture" => "powerpc", 'processor_count' => '50', 'randomfact' => 'foo') }, { "/path/to/nonmatching.yaml" => Puppet::Node::Facts.new("nonmatchingnode", "architecture" => "powerpc", 'processor_count' => '100' ), "/path/to/nonmatching2.yaml" => Puppet::Node::Facts.new("nonmatchingnode2", "architecture" => "i386", 'processor_count' => '5000'), "/path/to/nonmatching3.yaml" => Puppet::Node::Facts.new("nonmatchingnode3" ), }, {'facts.processor_count.le' => '50'} ) end it "should return node names that match the search query options with the greater than or equal to operator" do assert_search_matches({ '/path/to/matching.yaml' => Puppet::Node::Facts.new("matchingnode", "architecture" => "i386", 'processor_count' => '100'), '/path/to/matching1.yaml' => Puppet::Node::Facts.new("matchingnode1", "architecture" => "powerpc", 'processor_count' => '50', 'randomfact' => 'foo') }, { "/path/to/nonmatching.yaml" => Puppet::Node::Facts.new("nonmatchingnode", "architecture" => "powerpc", 'processor_count' => '40'), "/path/to/nonmatching2.yaml" => Puppet::Node::Facts.new("nonmatchingnode2", "architecture" => "i386", 'processor_count' => '9' ), "/path/to/nonmatching3.yaml" => Puppet::Node::Facts.new("nonmatchingnode3" ), }, {'facts.processor_count.ge' => '50'} ) end it "should return node names that match the search query options with the not equal operator" do assert_search_matches({ '/path/to/matching.yaml' => Puppet::Node::Facts.new("matchingnode", "architecture" => 'arm' ), '/path/to/matching1.yaml' => Puppet::Node::Facts.new("matchingnode1", "architecture" => 'powerpc', 'randomfact' => 'foo') }, { "/path/to/nonmatching.yaml" => Puppet::Node::Facts.new("nonmatchingnode", "architecture" => "i386" ), "/path/to/nonmatching2.yaml" => Puppet::Node::Facts.new("nonmatchingnode2", "architecture" => "i386", 'processor_count' => '9' ), "/path/to/nonmatching3.yaml" => Puppet::Node::Facts.new("nonmatchingnode3" ), }, {'facts.architecture.ne' => 'i386'} ) end def apply_timestamp(facts, timestamp) facts.timestamp = timestamp facts end it "should be able to query based on meta.timestamp.gt" do assert_search_matches({ '/path/to/2010-11-01.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-11-01", {}), Time.parse("2010-11-01")), '/path/to/2010-11-10.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-11-10", {}), Time.parse("2010-11-10")), }, { '/path/to/2010-10-01.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-01", {}), Time.parse("2010-10-01")), '/path/to/2010-10-10.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-10", {}), Time.parse("2010-10-10")), '/path/to/2010-10-15.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-15", {}), Time.parse("2010-10-15")), }, {'meta.timestamp.gt' => '2010-10-15'} ) end it "should be able to query based on meta.timestamp.le" do assert_search_matches({ '/path/to/2010-10-01.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-01", {}), Time.parse("2010-10-01")), '/path/to/2010-10-10.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-10", {}), Time.parse("2010-10-10")), '/path/to/2010-10-15.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-15", {}), Time.parse("2010-10-15")), }, { '/path/to/2010-11-01.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-11-01", {}), Time.parse("2010-11-01")), '/path/to/2010-11-10.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-11-10", {}), Time.parse("2010-11-10")), }, {'meta.timestamp.le' => '2010-10-15'} ) end it "should be able to query based on meta.timestamp.lt" do assert_search_matches({ '/path/to/2010-10-01.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-01", {}), Time.parse("2010-10-01")), '/path/to/2010-10-10.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-10", {}), Time.parse("2010-10-10")), }, { '/path/to/2010-11-01.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-11-01", {}), Time.parse("2010-11-01")), '/path/to/2010-11-10.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-11-10", {}), Time.parse("2010-11-10")), '/path/to/2010-10-15.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-15", {}), Time.parse("2010-10-15")), }, {'meta.timestamp.lt' => '2010-10-15'} ) end it "should be able to query based on meta.timestamp.ge" do assert_search_matches({ '/path/to/2010-11-01.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-11-01", {}), Time.parse("2010-11-01")), '/path/to/2010-11-10.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-11-10", {}), Time.parse("2010-11-10")), '/path/to/2010-10-15.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-15", {}), Time.parse("2010-10-15")), }, { '/path/to/2010-10-01.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-01", {}), Time.parse("2010-10-01")), '/path/to/2010-10-10.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-10", {}), Time.parse("2010-10-10")), }, {'meta.timestamp.ge' => '2010-10-15'} ) end it "should be able to query based on meta.timestamp.eq" do assert_search_matches({ '/path/to/2010-10-15.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-15", {}), Time.parse("2010-10-15")), }, { '/path/to/2010-11-01.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-11-01", {}), Time.parse("2010-11-01")), '/path/to/2010-11-10.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-11-10", {}), Time.parse("2010-11-10")), '/path/to/2010-10-01.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-01", {}), Time.parse("2010-10-01")), '/path/to/2010-10-10.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-10", {}), Time.parse("2010-10-10")), }, {'meta.timestamp.eq' => '2010-10-15'} ) end it "should be able to query based on meta.timestamp" do assert_search_matches({ '/path/to/2010-10-15.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-15", {}), Time.parse("2010-10-15")), }, { '/path/to/2010-11-01.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-11-01", {}), Time.parse("2010-11-01")), '/path/to/2010-11-10.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-11-10", {}), Time.parse("2010-11-10")), '/path/to/2010-10-01.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-01", {}), Time.parse("2010-10-01")), '/path/to/2010-10-10.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-10", {}), Time.parse("2010-10-10")), }, {'meta.timestamp' => '2010-10-15'} ) end it "should be able to query based on meta.timestamp.ne" do assert_search_matches({ '/path/to/2010-11-01.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-11-01", {}), Time.parse("2010-11-01")), '/path/to/2010-11-10.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-11-10", {}), Time.parse("2010-11-10")), '/path/to/2010-10-01.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-01", {}), Time.parse("2010-10-01")), '/path/to/2010-10-10.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-10", {}), Time.parse("2010-10-10")), }, { '/path/to/2010-10-15.yaml' => apply_timestamp(Puppet::Node::Facts.new("2010-10-15", {}), Time.parse("2010-10-15")), }, {'meta.timestamp.ne' => '2010-10-15'} ) end end end puppet-5.5.10/spec/unit/indirector/file_bucket_file/0000755005276200011600000000000013417162176022345 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/indirector/file_bucket_file/rest_spec.rb0000644005276200011600000000044413417161721024656 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/file_bucket_file/rest' describe Puppet::FileBucketFile::Rest do it "should be a sublcass of Puppet::Indirector::REST" do expect(Puppet::FileBucketFile::Rest.superclass).to equal(Puppet::Indirector::REST) end end puppet-5.5.10/spec/unit/indirector/file_bucket_file/selector_spec.rb0000644005276200011600000000151113417161721025515 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/file_bucket_file/selector' require 'puppet/indirector/file_bucket_file/file' require 'puppet/indirector/file_bucket_file/rest' describe Puppet::FileBucketFile::Selector do %w[head find save search destroy].each do |method| describe "##{method}" do it "should proxy to rest terminus for https requests" do request = stub 'request', :protocol => 'https' Puppet::FileBucketFile::Rest.any_instance.expects(method).with(request) subject.send(method, request) end it "should proxy to file terminus for other requests" do request = stub 'request', :protocol => 'file' Puppet::FileBucketFile::File.any_instance.expects(method).with(request) subject.send(method, request) end end end end puppet-5.5.10/spec/unit/indirector/file_bucket_file/file_spec.rb0000644005276200011600000005002613417161722024622 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/file_bucket_file/file' require 'puppet/util/platform' describe Puppet::FileBucketFile::File, :uses_checksums => true do include PuppetSpec::Files describe "non-stubbing tests" do include PuppetSpec::Files def save_bucket_file(contents, path = "/who_cares") bucket_file = Puppet::FileBucket::File.new(contents) Puppet::FileBucket::File.indirection.save(bucket_file, "#{bucket_file.name}#{path}") bucket_file.checksum_data end describe "when servicing a save request" do it "should return a result whose content is empty" do bucket_file = Puppet::FileBucket::File.new('stuff') result = Puppet::FileBucket::File.indirection.save(bucket_file, "md5/c13d88cb4cb02003daedb8a84e5d272a") expect(result.contents).to be_empty end it "deals with multiple processes saving at the same time", :unless => Puppet::Util::Platform.windows? do bucket_file = Puppet::FileBucket::File.new("contents") children = [] 5.times do |count| children << Kernel.fork do save_bucket_file("contents", "/testing") exit(0) end end children.each { |child| Process.wait(child) } paths = File.read("#{Puppet[:bucketdir]}/9/8/b/f/7/d/8/c/98bf7d8c15784f0a3d63204441e1e2aa/paths").lines.to_a expect(paths.length).to eq(1) expect(Puppet::FileBucket::File.indirection.head("#{bucket_file.checksum_type}/#{bucket_file.checksum_data}/testing")).to be_truthy end it "fails if the contents collide with existing contents" do # This is the shortest known MD5 collision (little endian). See https://eprint.iacr.org/2010/643.pdf first_contents = [0x6165300e,0x87a79a55,0xf7c60bd0,0x34febd0b, 0x6503cf04,0x854f709e,0xfb0fc034,0x874c9c65, 0x2f94cc40,0x15a12deb,0x5c15f4a3,0x490786bb, 0x6d658673,0xa4341f7d,0x8fd75920,0xefd18d5a].pack("V" * 16) collision_contents = [0x6165300e,0x87a79a55,0xf7c60bd0,0x34febd0b, 0x6503cf04,0x854f749e,0xfb0fc034,0x874c9c65, 0x2f94cc40,0x15a12deb,0xdc15f4a3,0x490786bb, 0x6d658673,0xa4341f7d,0x8fd75920,0xefd18d5a].pack("V" * 16) checksum_value = save_bucket_file(first_contents, "/foo/bar") # We expect Puppet to log an error with the path to the file Puppet.expects(:err).with(regexp_matches(/Unable to verify existing FileBucket backup at '#{Puppet[:bucketdir]}.*#{checksum_value}\/contents'/)) # But the exception should not contain it expect do save_bucket_file(collision_contents, "/foo/bar") end.to raise_error(Puppet::FileBucket::BucketError, /\AExisting backup and new file have different content but same checksum, {md5}#{checksum_value}\. Verify existing backup and remove if incorrect\.\Z/) end # See PUP-1334 context "when the contents file exists but is corrupted and does not match the expected checksum" do let(:original_contents) { "a file that will get corrupted" } let(:bucket_file) { Puppet::FileBucket::File.new(original_contents) } let(:contents_file) { "#{Puppet[:bucketdir]}/8/e/6/4/f/8/5/d/8e64f85dd54a412f65edabcafe44d491/contents" } before(:each) do # Ensure we're starting with a clean slate - no pre-existing backup Puppet::FileSystem.unlink(contents_file) if Puppet::FileSystem.exist?(contents_file) # Create initial "correct" backup Puppet::FileBucket::File.indirection.save(bucket_file) # Modify the contents file so that it no longer matches the SHA, simulating a corrupt backup Puppet::FileSystem.unlink(contents_file) # bucket_files are read-only Puppet::Util.replace_file(contents_file, 0600) { |fh| fh.puts "now with corrupted content" } end it "issues a warning that the backup will be overwritten" do Puppet.expects(:warning).with(regexp_matches(/Existing backup does not match its expected sum, #{bucket_file.checksum}/)) Puppet::FileBucket::File.indirection.save(bucket_file) end it "overwrites the existing contents file (backup)" do Puppet::FileBucket::File.indirection.save(bucket_file) expect(Puppet::FileSystem.read(contents_file)).to eq(original_contents) end end describe "when supplying a path" do with_digest_algorithms do it "should store the path if not already stored" do if Puppet::Util::Platform.windows? && (['sha512', 'sha384'].include? digest_algorithm) skip "PUP-8257: Skip file bucket test on windows for #{digest_algorithm} due to long path names" else save_bucket_file(plaintext, "/foo/bar") dir_path = "#{Puppet[:bucketdir]}/#{bucket_dir}" contents_file = "#{dir_path}/contents" paths_file = "#{dir_path}/paths" expect(Puppet::FileSystem.binread(contents_file)).to eq(plaintext) expect(Puppet::FileSystem.read(paths_file)).to eq("foo/bar\n") end end it "should leave the paths file alone if the path is already stored" do if Puppet::Util::Platform.windows? && (['sha512', 'sha384'].include? digest_algorithm) skip "PUP-8257: Skip file bucket test on windows for #{digest_algorithm} due to long path names" else checksum = save_bucket_file(plaintext, "/foo/bar") checksum = save_bucket_file(plaintext, "/foo/bar") dir_path = "#{Puppet[:bucketdir]}/#{bucket_dir}" expect(Puppet::FileSystem.binread("#{dir_path}/contents")).to eq(plaintext) expect(File.read("#{dir_path}/paths")).to eq("foo/bar\n") end end it "should store an additional path if the new path differs from those already stored" do if Puppet::Util::Platform.windows? && (['sha512', 'sha384'].include? digest_algorithm) skip "PUP-8257: Skip file bucket test on windows for #{digest_algorithm} due to long path names" else checksum = save_bucket_file(plaintext, "/foo/bar") checksum = save_bucket_file(plaintext, "/foo/baz") dir_path = "#{Puppet[:bucketdir]}/#{bucket_dir}" expect(Puppet::FileSystem.binread("#{dir_path}/contents")).to eq(plaintext) expect(File.read("#{dir_path}/paths")).to eq("foo/bar\nfoo/baz\n") end end # end end end describe "when not supplying a path" do with_digest_algorithms do it "should save the file and create an empty paths file" do if Puppet::Util::Platform.windows? && (['sha512', 'sha384'].include? digest_algorithm) skip "PUP-8257: Skip file bucket test on windows for #{digest_algorithm} due to long path names" else save_bucket_file(plaintext, "") dir_path = "#{Puppet[:bucketdir]}/#{bucket_dir}" expect(Puppet::FileSystem.binread("#{dir_path}/contents")).to eq(plaintext) expect(File.read("#{dir_path}/paths")).to eq("") end end end end end describe "when servicing a head/find request" do with_digest_algorithms do let(:not_bucketed_plaintext) { "other stuff" } let(:not_bucketed_checksum) { digest(not_bucketed_plaintext) } describe "when listing the filebucket" do it "should return false/nil when the bucket is empty" do expect(Puppet::FileBucket::File.indirection.find("#{digest_algorithm}/#{not_bucketed_checksum}/foo/bar", :list_all => true)).to eq(nil) end it "raises when the request is remote" do Puppet[:bucketdir] = tmpdir('bucket') request = Puppet::Indirector::Request.new(:file_bucket_file, :find, "#{digest_algorithm}/#{checksum}/foo/bar", nil, :list_all => true) request.node = 'client.example.com' expect { Puppet::FileBucketFile::File.new.find(request) }.to raise_error(Puppet::Error, "Listing remote file buckets is not allowed") end it "should return the list of bucketed files in a human readable way" do if Puppet::Util::Platform.windows? && (['sha512', 'sha384'].include? digest_algorithm) skip "PUP-8257: Skip file bucket test on windows for #{digest_algorithm} due to long path names" else checksum1 = save_bucket_file("I'm the contents of a file", '/foo/bar1') checksum2 = save_bucket_file("I'm the contents of another file", '/foo/bar2') checksum3 = save_bucket_file("I'm the modified content of a existing file", '/foo/bar1') # Use the first checksum as we know it's stored in the bucket find_result = Puppet::FileBucket::File.indirection.find("#{digest_algorithm}/#{checksum1}/foo/bar1", :list_all => true) # The list is sort order from date and file name, so first and third checksums come before the second date_pattern = '\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}' expect(find_result.to_s).to match(Regexp.new("^(#{checksum1}|#{checksum3}) #{date_pattern} foo/bar1\\n(#{checksum3}|#{checksum1}) #{date_pattern} foo/bar1\\n#{checksum2} #{date_pattern} foo/bar2\\n$")) end end it "should fail in an informative way when provided dates are not in the right format" do if Puppet::Util::Platform.windows? && (['sha512', 'sha384'].include? digest_algorithm) skip "PUP-8257: Skip file bucket test on windows for #{digest_algorithm} due to long path names" else contents = "I'm the contents of a file" save_bucket_file(contents, '/foo/bar1') expect { Puppet::FileBucket::File.indirection.find( "#{digest_algorithm}/#{not_bucketed_checksum}/foo/bar", :list_all => true, :todate => "0:0:0 1-1-1970", :fromdate => "WEIRD" ) }.to raise_error(Puppet::Error, /fromdate/) expect { Puppet::FileBucket::File.indirection.find( "#{digest_algorithm}/#{not_bucketed_checksum}/foo/bar", :list_all => true, :todate => "WEIRD", :fromdate => Time.now ) }.to raise_error(Puppet::Error, /todate/) end end end describe "when supplying a path" do it "should return false/nil if the file isn't bucketed" do expect(Puppet::FileBucket::File.indirection.head("#{digest_algorithm}/#{not_bucketed_checksum}/foo/bar")).to eq(false) expect(Puppet::FileBucket::File.indirection.find("#{digest_algorithm}/#{not_bucketed_checksum}/foo/bar")).to eq(nil) end it "should return false/nil if the file is bucketed but with a different path" do if Puppet::Util::Platform.windows? && (['sha512', 'sha384'].include? digest_algorithm) skip "PUP-8257: Skip file bucket test on windows for #{digest_algorithm} due to long path names" else checksum = save_bucket_file("I'm the contents of a file", '/foo/bar') expect(Puppet::FileBucket::File.indirection.head("#{digest_algorithm}/#{checksum}/foo/baz")).to eq(false) expect(Puppet::FileBucket::File.indirection.find("#{digest_algorithm}/#{checksum}/foo/baz")).to eq(nil) end end it "should return true/file if the file is already bucketed with the given path" do if Puppet::Util::Platform.windows? && (['sha512', 'sha384'].include? digest_algorithm) skip "PUP-8257: Skip file bucket test on windows for #{digest_algorithm} due to long path names" else contents = "I'm the contents of a file" checksum = save_bucket_file(contents, '/foo/bar') expect(Puppet::FileBucket::File.indirection.head("#{digest_algorithm}/#{checksum}/foo/bar")).to eq(true) find_result = Puppet::FileBucket::File.indirection.find("#{digest_algorithm}/#{checksum}/foo/bar") expect(find_result.checksum).to eq("{#{digest_algorithm}}#{checksum}") expect(find_result.to_s).to eq(contents) end end end describe "when not supplying a path" do [false, true].each do |trailing_slash| describe "#{trailing_slash ? 'with' : 'without'} a trailing slash" do trailing_string = trailing_slash ? '/' : '' it "should return false/nil if the file isn't bucketed" do expect(Puppet::FileBucket::File.indirection.head("#{digest_algorithm}/#{not_bucketed_checksum}#{trailing_string}")).to eq(false) expect(Puppet::FileBucket::File.indirection.find("#{digest_algorithm}/#{not_bucketed_checksum}#{trailing_string}")).to eq(nil) end it "should return true/file if the file is already bucketed" do # this one replaces most of the lets in the "when # digest_digest_algorithm is set..." shared context, but it still needs digest_algorithm if Puppet::Util::Platform.windows? && (['sha512', 'sha384'].include? digest_algorithm) skip "PUP-8257: Skip file bucket test on windows for #{digest_algorithm} due to long path names" else contents = "I'm the contents of a file" checksum = save_bucket_file(contents, '/foo/bar') expect(Puppet::FileBucket::File.indirection.head("#{digest_algorithm}/#{checksum}#{trailing_string}")).to eq(true) find_result = Puppet::FileBucket::File.indirection.find("#{digest_algorithm}/#{checksum}#{trailing_string}") expect(find_result.checksum).to eq("{#{digest_algorithm}}#{checksum}") expect(find_result.to_s).to eq(contents) end end end end end end end describe "when diffing files", :unless => Puppet.features.microsoft_windows? do with_digest_algorithms do let(:not_bucketed_plaintext) { "other stuff" } let(:not_bucketed_checksum) { digest(not_bucketed_plaintext) } it "should generate an empty string if there is no diff" do if Puppet::Util::Platform.windows? && (['sha512', 'sha384'].include? digest_algorithm) skip "PUP-8257: Skip file bucket test on windows for #{digest_algorithm} due to long path names" else checksum = save_bucket_file("I'm the contents of a file") expect(Puppet::FileBucket::File.indirection.find("#{digest_algorithm}/#{checksum}", :diff_with => checksum)).to eq('') end end it "should generate a proper diff if there is a diff" do if Puppet::Util::Platform.windows? && (['sha512', 'sha384'].include? digest_algorithm) skip "PUP-8257: Skip file bucket test on windows for #{digest_algorithm} due to long path names" else checksum1 = save_bucket_file("foo\nbar\nbaz") checksum2 = save_bucket_file("foo\nbiz\nbaz") diff = Puppet::FileBucket::File.indirection.find("#{digest_algorithm}/#{checksum1}", :diff_with => checksum2) expect(diff).to include("-bar\n+biz\n") end end it "should raise an exception if the hash to diff against isn't found" do if Puppet::Util::Platform.windows? && (['sha512', 'sha384'].include? digest_algorithm) skip "PUP-8257: Skip file bucket test on windows for #{digest_algorithm} due to long path names" else checksum = save_bucket_file("whatever") expect do Puppet::FileBucket::File.indirection.find("#{digest_algorithm}/#{checksum}", :diff_with => not_bucketed_checksum) end.to raise_error "could not find diff_with #{not_bucketed_checksum}" end end it "should return nil if the hash to diff from isn't found" do if Puppet::Util::Platform.windows? && (['sha512', 'sha384'].include? digest_algorithm) skip "PUP-8257: Skip file bucket test on windows for #{digest_algorithm} due to long path names" else checksum = save_bucket_file("whatever") expect(Puppet::FileBucket::File.indirection.find("#{digest_algorithm}/#{not_bucketed_checksum}", :diff_with => checksum)).to eq(nil) end end end end end [true, false].each do |override_bucket_path| describe "when bucket path #{override_bucket_path ? 'is' : 'is not'} overridden" do [true, false].each do |supply_path| describe "when #{supply_path ? 'supplying' : 'not supplying'} a path" do with_digest_algorithms do before :each do Puppet.settings.stubs(:use) @store = Puppet::FileBucketFile::File.new @bucket_top_dir = tmpdir("bucket") if override_bucket_path Puppet[:bucketdir] = "/bogus/path" # should not be used else Puppet[:bucketdir] = @bucket_top_dir end @dir = "#{@bucket_top_dir}/#{bucket_dir}" @contents_path = "#{@dir}/contents" end describe "when retrieving files" do before :each do request_options = {} if override_bucket_path request_options[:bucket_path] = @bucket_top_dir end key = "#{digest_algorithm}/#{checksum}" if supply_path key += "/path/to/file" end @request = Puppet::Indirector::Request.new(:indirection_name, :find, key, nil, request_options) end def make_bucketed_file FileUtils.mkdir_p(@dir) File.open(@contents_path, 'wb') { |f| f.write plaintext } end it "should return an instance of Puppet::FileBucket::File created with the content if the file exists" do make_bucketed_file if supply_path expect(@store.find(@request)).to eq(nil) expect(@store.head(@request)).to eq(false) # because path didn't match else bucketfile = @store.find(@request) expect(bucketfile).to be_a(Puppet::FileBucket::File) expect(bucketfile.contents).to eq(plaintext) expect(@store.head(@request)).to eq(true) end end it "should return nil if no file is found" do expect(@store.find(@request)).to be_nil expect(@store.head(@request)).to eq(false) end end describe "when saving files" do it "should save the contents to the calculated path" do skip("Windows Long File Name support is incomplete PUP-8257, this doesn't fail reliably so it should be skipped.") if Puppet::Util::Platform.windows? && (['sha512', 'sha384'].include? digest_algorithm) options = {} if override_bucket_path options[:bucket_path] = @bucket_top_dir end key = "#{digest_algorithm}/#{checksum}" if supply_path key += "//path/to/file" end file_instance = Puppet::FileBucket::File.new(plaintext, options) request = Puppet::Indirector::Request.new(:indirection_name, :save, key, file_instance) @store.save(request) expect(Puppet::FileSystem.binread("#{@dir}/contents")).to eq(plaintext) end end end end end end end end puppet-5.5.10/spec/unit/indirector/file_content/0000755005276200011600000000000013417162176021543 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/indirector/file_content/file_server_spec.rb0000644005276200011600000000103513417161721025401 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/file_content/file_server' describe Puppet::Indirector::FileContent::FileServer do it "should be registered with the file_content indirection" do expect(Puppet::Indirector::Terminus.terminus_class(:file_content, :file_server)).to equal(Puppet::Indirector::FileContent::FileServer) end it "should be a subclass of the FileServer terminus" do expect(Puppet::Indirector::FileContent::FileServer.superclass).to equal(Puppet::Indirector::FileServer) end end puppet-5.5.10/spec/unit/indirector/file_content/file_spec.rb0000644005276200011600000000101113417161721024005 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/file_content/file' describe Puppet::Indirector::FileContent::File do it "should be registered with the file_content indirection" do expect(Puppet::Indirector::Terminus.terminus_class(:file_content, :file)).to equal(Puppet::Indirector::FileContent::File) end it "should be a subclass of the DirectFileServer terminus" do expect(Puppet::Indirector::FileContent::File.superclass).to equal(Puppet::Indirector::DirectFileServer) end end puppet-5.5.10/spec/unit/indirector/file_content/rest_spec.rb0000644005276200011600000000060413417161721024052 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/file_content/rest' describe Puppet::Indirector::FileContent::Rest do it "should add the node's cert name to the arguments" it "should set the content type to text/plain" it "should use the :fileserver SRV service" do expect(Puppet::Indirector::FileContent::Rest.srv_service).to eq(:fileserver) end end puppet-5.5.10/spec/unit/indirector/file_content/selector_spec.rb0000644005276200011600000000037213417161721024717 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/file_content/selector' describe Puppet::Indirector::FileContent::Selector do include PuppetSpec::Files it_should_behave_like "Puppet::FileServing::Files", :file_content end puppet-5.5.10/spec/unit/indirector/file_metadata/0000755005276200011600000000000013417162176021651 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/indirector/file_metadata/file_server_spec.rb0000644005276200011600000000104313417161721025506 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/file_metadata/file_server' describe Puppet::Indirector::FileMetadata::FileServer do it "should be registered with the file_metadata indirection" do expect(Puppet::Indirector::Terminus.terminus_class(:file_metadata, :file_server)).to equal(Puppet::Indirector::FileMetadata::FileServer) end it "should be a subclass of the FileServer terminus" do expect(Puppet::Indirector::FileMetadata::FileServer.superclass).to equal(Puppet::Indirector::FileServer) end end puppet-5.5.10/spec/unit/indirector/file_metadata/file_spec.rb0000644005276200011600000000423213417161721024123 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/file_metadata/file' describe Puppet::Indirector::FileMetadata::File do it "should be registered with the file_metadata indirection" do expect(Puppet::Indirector::Terminus.terminus_class(:file_metadata, :file)).to equal(Puppet::Indirector::FileMetadata::File) end it "should be a subclass of the DirectFileServer terminus" do expect(Puppet::Indirector::FileMetadata::File.superclass).to equal(Puppet::Indirector::DirectFileServer) end describe "when creating the instance for a single found file" do before do @metadata = Puppet::Indirector::FileMetadata::File.new @path = File.expand_path('/my/local') @uri = Puppet::Util.path_to_uri(@path).to_s @data = mock 'metadata' @data.stubs(:collect) Puppet::FileSystem.expects(:exist?).with(@path).returns true @request = Puppet::Indirector::Request.new(:file_metadata, :find, @uri, nil) end it "should collect its attributes when a file is found" do @data.expects(:collect) Puppet::FileServing::Metadata.expects(:new).returns(@data) expect(@metadata.find(@request)).to eq(@data) end end describe "when searching for multiple files" do before do @metadata = Puppet::Indirector::FileMetadata::File.new @path = File.expand_path('/my/local') @uri = Puppet::Util.path_to_uri(@path).to_s @request = Puppet::Indirector::Request.new(:file_metadata, :find, @uri, nil) end it "should collect the attributes of the instances returned" do Puppet::FileSystem.expects(:exist?).with(@path).returns true Puppet::FileServing::Fileset.expects(:new).with(@path, @request).returns mock("fileset") Puppet::FileServing::Fileset.expects(:merge).returns [["one", @path], ["two", @path]] one = mock("one", :collect => nil) Puppet::FileServing::Metadata.expects(:new).with(@path, {:relative_path => "one"}).returns one two = mock("two", :collect => nil) Puppet::FileServing::Metadata.expects(:new).with(@path, {:relative_path => "two"}).returns two expect(@metadata.search(@request)).to eq([one, two]) end end end puppet-5.5.10/spec/unit/indirector/file_metadata/rest_spec.rb0000644005276200011600000000057513417161721024167 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/file_metadata' require 'puppet/indirector/file_metadata/rest' describe "Puppet::Indirector::Metadata::Rest" do it "should add the node's cert name to the arguments" it "should use the :fileserver SRV service" do expect(Puppet::Indirector::FileMetadata::Rest.srv_service).to eq(:fileserver) end end puppet-5.5.10/spec/unit/indirector/file_metadata/selector_spec.rb0000644005276200011600000000037613417161721025031 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/file_metadata/selector' describe Puppet::Indirector::FileMetadata::Selector do include PuppetSpec::Files it_should_behave_like "Puppet::FileServing::Files", :file_metadata end puppet-5.5.10/spec/unit/indirector/hiera_spec.rb0000644005276200011600000000054313417161721021516 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/data_binding' require 'puppet/indirector/hiera' require 'hiera/backend' describe Puppet::Indirector::Hiera do module Testing module DataBinding class Hiera < Puppet::Indirector::Hiera end end end it_should_behave_like "Hiera indirection", Testing::DataBinding::Hiera, my_fixture_dir end puppet-5.5.10/spec/unit/indirector/json_spec.rb0000644005276200011600000001553713417161721021410 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/files' require 'puppet/indirector/indirector_testing/json' describe Puppet::Indirector::JSON do include PuppetSpec::Files subject { Puppet::IndirectorTesting::JSON.new } let :model do Puppet::IndirectorTesting end let :indirection do model.indirection end context "#path" do before :each do Puppet[:server_datadir] = '/sample/datadir/master' Puppet[:client_datadir] = '/sample/datadir/client' end it "uses the :server_datadir setting if this is the master" do Puppet.run_mode.stubs(:master?).returns(true) expected = File.join(Puppet[:server_datadir], 'indirector_testing', 'testing.json') expect(subject.path('testing')).to eq(expected) end it "uses the :client_datadir setting if this is not the master" do Puppet.run_mode.stubs(:master?).returns(false) expected = File.join(Puppet[:client_datadir], 'indirector_testing', 'testing.json') expect(subject.path('testing')).to eq(expected) end it "overrides the default extension with a supplied value" do Puppet.run_mode.stubs(:master?).returns(true) expected = File.join(Puppet[:server_datadir], 'indirector_testing', 'testing.not-json') expect(subject.path('testing', '.not-json')).to eq(expected) end ['../foo', '..\\foo', './../foo', '.\\..\\foo', '/foo', '//foo', '\\foo', '\\\\goo', "test\0/../bar", "test\0\\..\\bar", "..\\/bar", "/tmp/bar", "/tmp\\bar", "tmp\\bar", " / bar", " /../ bar", " \\..\\ bar", "c:\\foo", "c:/foo", "\\\\?\\UNC\\bar", "\\\\foo\\bar", "\\\\?\\c:\\foo", "//?/UNC/bar", "//foo/bar", "//?/c:/foo", ].each do |input| it "should resist directory traversal attacks (#{input.inspect})" do expect { subject.path(input) }.to raise_error ArgumentError, 'invalid key' end end end context "handling requests" do before :each do Puppet.run_mode.stubs(:master?).returns(true) Puppet[:server_datadir] = tmpdir('jsondir') FileUtils.mkdir_p(File.join(Puppet[:server_datadir], 'indirector_testing')) end let :file do subject.path(request.key) end def with_content(text) FileUtils.mkdir_p(File.dirname(file)) File.binwrite(file, text) yield if block_given? end it "data saves and then loads again correctly" do subject.save(indirection.request(:save, 'example', model.new('banana'))) expect(subject.find(indirection.request(:find, 'example', nil)).value).to eq('banana') end context "#find" do let :request do indirection.request(:find, 'example', nil) end it "returns nil if the file doesn't exist" do expect(subject.find(request)).to be_nil end it "raises a descriptive error when the file can't be read" do with_content(model.new('foo').to_json) do # I don't like this, but there isn't a credible alternative that # also works on Windows, so a stub it is. At least the expectation # will fail if the implementation changes. Sorry to the next dev. Puppet::FileSystem.expects(:read).with(file, anything).raises(Errno::EPERM) expect { subject.find(request) }. to raise_error Puppet::Error, /Could not read JSON/ end end it "raises a descriptive error when the file content is invalid" do with_content("this is totally invalid JSON") do expect { subject.find(request) }. to raise_error Puppet::Error, /Could not parse JSON data/ end end it "raises if the content contains binary" do binary = "\xC0\xFF".force_encoding(Encoding::BINARY) with_content(binary) do expect { subject.find(request) }.to raise_error Puppet::Error, /Could not parse JSON data/ end end it "should return an instance of the indirected object when valid" do with_content(model.new(1).to_json) do instance = subject.find(request) expect(instance).to be_an_instance_of model expect(instance.value).to eq(1) end end end context "#save" do let :instance do model.new(4) end let :request do indirection.request(:find, 'example', instance) end it "should save the instance of the request as JSON to disk" do subject.save(request) content = File.read(file) expect(content).to match(/"value"\s*:\s*4/) end it "should create the indirection directory if required" do target = File.join(Puppet[:server_datadir], 'indirector_testing') Dir.rmdir(target) subject.save(request) expect(File).to be_directory(target) end end context "#destroy" do let :request do indirection.request(:find, 'example', nil) end it "removes an existing file" do with_content('hello') do subject.destroy(request) end expect(Puppet::FileSystem.exist?(file)).to be_falsey end it "silently succeeds when files don't exist" do Puppet::FileSystem.unlink(file) rescue nil expect(subject.destroy(request)).to be_truthy end it "raises an informative error for other failures" do Puppet::FileSystem.stubs(:unlink).with(file).raises(Errno::EPERM, 'fake permission problem') with_content('hello') do expect { subject.destroy(request) }.to raise_error(Puppet::Error) end Puppet::FileSystem.unstub(:unlink) # thanks, mocha end end end context "#search" do before :each do Puppet.run_mode.stubs(:master?).returns(true) Puppet[:server_datadir] = tmpdir('jsondir') FileUtils.mkdir_p(File.join(Puppet[:server_datadir], 'indirector_testing')) end def request(glob) indirection.request(:search, glob, nil) end def create_file(name, value = 12) File.open(subject.path(name, ''), 'wb') do |f| f.puts Puppet::IndirectorTesting.new(value).to_json end end it "returns an empty array when nothing matches the key as a glob" do expect(subject.search(request('*'))).to eq([]) end it "returns an array with one item if one item matches" do create_file('foo.json', 'foo') create_file('bar.json', 'bar') expect(subject.search(request('f*')).map(&:value)).to eq(['foo']) end it "returns an array of items when more than one item matches" do create_file('foo.json', 'foo') create_file('bar.json', 'bar') create_file('baz.json', 'baz') expect(subject.search(request('b*')).map(&:value)).to match_array(['bar', 'baz']) end it "only items with the .json extension" do create_file('foo.json', 'foo-json') create_file('foo.pson', 'foo-pson') create_file('foo.json~', 'foo-backup') expect(subject.search(request('f*')).map(&:value)).to eq(['foo-json']) end end end puppet-5.5.10/spec/unit/indirector/key/0000755005276200011600000000000013417162176017662 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/indirector/key/ca_spec.rb0000644005276200011600000000124213417161722021577 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/key/ca' describe Puppet::SSL::Key::Ca do it "should have documentation" do expect(Puppet::SSL::Key::Ca.doc).to be_instance_of(String) end it "should use the :privatekeydir as the collection directory" do Puppet[:privatekeydir] = "/key/dir" expect(Puppet::SSL::Key::Ca.collection_directory).to eq(Puppet[:privatekeydir]) end it "should store the ca key at the :cakey location" do Puppet.settings.stubs(:use) Puppet[:cakey] = "/ca/key" file = Puppet::SSL::Key::Ca.new file.stubs(:ca?).returns true expect(file.path("whatever")).to eq(Puppet[:cakey]) end end puppet-5.5.10/spec/unit/indirector/key/disabled_ca_spec.rb0000644005276200011600000000156013417161722023431 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/key/disabled_ca' describe Puppet::SSL::Key::DisabledCa do def request(type, remote) r = Puppet::Indirector::Request.new(:key, type, "foo.com", nil) if remote r.ip = '10.0.0.1' r.node = 'agent.example.com' end r end context "when not a CA" do before :each do Puppet[:ca] = false Puppet::SSL::Host.ca_location = :none end [:find, :head, :search, :save, :destroy].each do |name| it "should fail remote #{name} requests" do expect { subject.send(name, request(name, true)) }. to raise_error Puppet::Error, /is not a CA/ end it "should forward local #{name} requests" do Puppet::SSL::Key.indirection.terminus(:file).expects(name) subject.send(name, request(name, false)) end end end end puppet-5.5.10/spec/unit/indirector/key/file_spec.rb0000644005276200011600000000723313417161722022141 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/key/file' describe Puppet::SSL::Key::File do it "should have documentation" do expect(Puppet::SSL::Key::File.doc).to be_instance_of(String) end it "should use the :privatekeydir as the collection directory" do Puppet[:privatekeydir] = File.expand_path("/key/dir") expect(Puppet::SSL::Key::File.collection_directory).to eq(Puppet[:privatekeydir]) end it "should store the ca key at the :cakey location" do Puppet.settings.stubs(:use) Puppet[:cakey] = File.expand_path("/ca/key") file = Puppet::SSL::Key::File.new file.stubs(:ca?).returns true expect(file.path("whatever")).to eq(Puppet[:cakey]) end describe "when choosing the path for the public key" do it "should use the :capub setting location if the key is for the certificate authority" do Puppet[:capub] = File.expand_path("/ca/pubkey") Puppet.settings.stubs(:use) @searcher = Puppet::SSL::Key::File.new @searcher.stubs(:ca?).returns true expect(@searcher.public_key_path("whatever")).to eq(Puppet[:capub]) end it "should use the host name plus '.pem' in :publickeydir for normal hosts" do Puppet[:privatekeydir] = File.expand_path("/private/key/dir") Puppet[:publickeydir] = File.expand_path("/public/key/dir") Puppet.settings.stubs(:use) @searcher = Puppet::SSL::Key::File.new @searcher.stubs(:ca?).returns false expect(@searcher.public_key_path("whatever")).to eq(File.expand_path("/public/key/dir/whatever.pem")) end end describe "when managing private keys" do before do @searcher = Puppet::SSL::Key::File.new @private_key_path = File.join("/fake/key/path") @public_key_path = File.join("/other/fake/key/path") @searcher.stubs(:public_key_path).returns @public_key_path @searcher.stubs(:path).returns @private_key_path FileTest.stubs(:directory?).returns true FileTest.stubs(:writable?).returns true @public_key = stub 'public_key' @real_key = stub 'sslkey', :public_key => @public_key @key = stub 'key', :name => "myname", :content => @real_key @request = stub 'request', :key => "myname", :instance => @key end it "should save the public key when saving the private key" do fh = StringIO.new Puppet.settings.setting(:publickeydir).expects(:open_file).with(@public_key_path, 'w:ASCII').yields fh Puppet.settings.setting(:privatekeydir).stubs(:open_file) @public_key.expects(:to_pem).returns "my pem" @searcher.save(@request) expect(fh.string).to eq("my pem") end it "should destroy the public key when destroying the private key" do Puppet::FileSystem.expects(:unlink).with(Puppet::FileSystem.pathname(@private_key_path)) Puppet::FileSystem.expects(:exist?).with(Puppet::FileSystem.pathname(@private_key_path)).returns true Puppet::FileSystem.expects(:exist?).with(Puppet::FileSystem.pathname(@public_key_path)).returns true Puppet::FileSystem.expects(:unlink).with(Puppet::FileSystem.pathname(@public_key_path)) @searcher.destroy(@request) end it "should not fail if the public key does not exist when deleting the private key" do Puppet::FileSystem.stubs(:unlink).with(Puppet::FileSystem.pathname(@private_key_path)) Puppet::FileSystem.stubs(:exist?).with(Puppet::FileSystem.pathname(@private_key_path)).returns true Puppet::FileSystem.expects(:exist?).with(Puppet::FileSystem.pathname(@public_key_path)).returns false Puppet::FileSystem.expects(:unlink).with(Puppet::FileSystem.pathname(@public_key_path)).never @searcher.destroy(@request) end end end puppet-5.5.10/spec/unit/indirector/memory_spec.rb0000644005276200011600000000142613417161721021737 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/memory' require 'shared_behaviours/memory_terminus' describe Puppet::Indirector::Memory do it_should_behave_like "A Memory Terminus" before do Puppet::Indirector::Terminus.stubs(:register_terminus_class) @model = mock 'model' @indirection = stub 'indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) module Testing; end @memory_class = class Testing::MyMemory < Puppet::Indirector::Memory self end @searcher = @memory_class.new @name = "me" @instance = stub 'instance', :name => @name @request = stub 'request', :key => @name, :instance => @instance end end puppet-5.5.10/spec/unit/indirector/msgpack_spec.rb0000644005276200011600000001577613417161721022071 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/files' require 'puppet/indirector/indirector_testing/msgpack' describe Puppet::Indirector::Msgpack, :if => Puppet.features.msgpack? do include PuppetSpec::Files subject { Puppet::IndirectorTesting::Msgpack.new } let :model do Puppet::IndirectorTesting end let :indirection do model.indirection end context "#path" do before :each do Puppet[:server_datadir] = '/sample/datadir/master' Puppet[:client_datadir] = '/sample/datadir/client' end it "uses the :server_datadir setting if this is the master" do Puppet.run_mode.stubs(:master?).returns(true) expected = File.join(Puppet[:server_datadir], 'indirector_testing', 'testing.msgpack') expect(subject.path('testing')).to eq(expected) end it "uses the :client_datadir setting if this is not the master" do Puppet.run_mode.stubs(:master?).returns(false) expected = File.join(Puppet[:client_datadir], 'indirector_testing', 'testing.msgpack') expect(subject.path('testing')).to eq(expected) end it "overrides the default extension with a supplied value" do Puppet.run_mode.stubs(:master?).returns(true) expected = File.join(Puppet[:server_datadir], 'indirector_testing', 'testing.not-msgpack') expect(subject.path('testing', '.not-msgpack')).to eq(expected) end ['../foo', '..\\foo', './../foo', '.\\..\\foo', '/foo', '//foo', '\\foo', '\\\\goo', "test\0/../bar", "test\0\\..\\bar", "..\\/bar", "/tmp/bar", "/tmp\\bar", "tmp\\bar", " / bar", " /../ bar", " \\..\\ bar", "c:\\foo", "c:/foo", "\\\\?\\UNC\\bar", "\\\\foo\\bar", "\\\\?\\c:\\foo", "//?/UNC/bar", "//foo/bar", "//?/c:/foo", ].each do |input| it "should resist directory traversal attacks (#{input.inspect})" do expect { subject.path(input) }.to raise_error ArgumentError, 'invalid key' end end end context "handling requests" do before :each do Puppet.run_mode.stubs(:master?).returns(true) Puppet[:server_datadir] = tmpdir('msgpackdir') FileUtils.mkdir_p(File.join(Puppet[:server_datadir], 'indirector_testing')) end let :file do subject.path(request.key) end def with_content(text) FileUtils.mkdir_p(File.dirname(file)) File.open(file, 'w') {|f| f.write text } yield if block_given? end it "data saves and then loads again correctly" do subject.save(indirection.request(:save, 'example', model.new('banana'))) expect(subject.find(indirection.request(:find, 'example', nil)).value).to eq('banana') end context "#find" do let :request do indirection.request(:find, 'example', nil) end it "returns nil if the file doesn't exist" do expect(subject.find(request)).to be_nil end it "can load a UTF-8 file from disk" do rune_utf8 = "\u16A0\u16C7\u16BB" # ᚠᛇᚻ with_content(model.new(rune_utf8).to_msgpack) do instance = subject.find(request) expect(instance).to be_an_instance_of model expect(instance.value).to eq(rune_utf8) end end it "raises a descriptive error when the file can't be read" do with_content(model.new('foo').to_msgpack) do # I don't like this, but there isn't a credible alternative that # also works on Windows, so a stub it is. At least the expectation # will fail if the implementation changes. Sorry to the next dev. Puppet::FileSystem.expects(:read).with(file, {:encoding => 'utf-8'}).raises(Errno::EPERM) expect { subject.find(request) }. to raise_error Puppet::Error, /Could not read MessagePack/ end end it "raises a descriptive error when the file content is invalid" do with_content("this is totally invalid MessagePack") do expect { subject.find(request) }. to raise_error Puppet::Error, /Could not parse MessagePack data/ end end it "should return an instance of the indirected object when valid" do with_content(model.new(1).to_msgpack) do instance = subject.find(request) expect(instance).to be_an_instance_of model expect(instance.value).to eq(1) end end end context "#save" do let :instance do model.new(4) end let :request do indirection.request(:find, 'example', instance) end it "should save the instance of the request as MessagePack to disk" do subject.save(request) content = File.read(file) expect(MessagePack.unpack(content)['value']).to eq(4) end it "should create the indirection directory if required" do target = File.join(Puppet[:server_datadir], 'indirector_testing') Dir.rmdir(target) subject.save(request) expect(File).to be_directory(target) end end context "#destroy" do let :request do indirection.request(:find, 'example', nil) end it "removes an existing file" do with_content('hello') do subject.destroy(request) end expect(Puppet::FileSystem.exist?(file)).to be_falsey end it "silently succeeds when files don't exist" do Puppet::FileSystem.unlink(file) rescue nil expect(subject.destroy(request)).to be_truthy end it "raises an informative error for other failures" do Puppet::FileSystem.stubs(:unlink).with(file).raises(Errno::EPERM, 'fake permission problem') with_content('hello') do expect { subject.destroy(request) }.to raise_error(Puppet::Error) end Puppet::FileSystem.unstub(:unlink) # thanks, mocha end end end context "#search" do before :each do Puppet.run_mode.stubs(:master?).returns(true) Puppet[:server_datadir] = tmpdir('msgpackdir') FileUtils.mkdir_p(File.join(Puppet[:server_datadir], 'indirector_testing')) end def request(glob) indirection.request(:search, glob, nil) end def create_file(name, value = 12) File.open(subject.path(name, ''), 'w') do |f| f.write Puppet::IndirectorTesting.new(value).to_msgpack end end it "returns an empty array when nothing matches the key as a glob" do expect(subject.search(request('*'))).to eq([]) end it "returns an array with one item if one item matches" do create_file('foo.msgpack', 'foo') create_file('bar.msgpack', 'bar') expect(subject.search(request('f*')).map(&:value)).to eq(['foo']) end it "returns an array of items when more than one item matches" do create_file('foo.msgpack', 'foo') create_file('bar.msgpack', 'bar') create_file('baz.msgpack', 'baz') expect(subject.search(request('b*')).map(&:value)).to match_array(['bar', 'baz']) end it "only items with the .msgpack extension" do create_file('foo.msgpack', 'foo-msgpack') create_file('foo.msgpack~', 'foo-backup') expect(subject.search(request('f*')).map(&:value)).to eq(['foo-msgpack']) end end end puppet-5.5.10/spec/unit/indirector/node/0000755005276200011600000000000013417162176020017 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/indirector/node/memory_spec.rb0000644005276200011600000000062613417161721022665 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/node/memory' require 'shared_behaviours/memory_terminus' describe Puppet::Node::Memory do before do @name = "me" @searcher = Puppet::Node::Memory.new @instance = stub 'instance', :name => @name @request = stub 'request', :key => @name, :instance => @instance end it_should_behave_like "A Memory Terminus" end puppet-5.5.10/spec/unit/indirector/node/msgpack_spec.rb0000644005276200011600000000134413417161721023000 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/node' require 'puppet/indirector/node/msgpack' describe Puppet::Node::Msgpack, :if => Puppet.features.msgpack? do it "should be a subclass of the Msgpack terminus" do expect(Puppet::Node::Msgpack.superclass).to equal(Puppet::Indirector::Msgpack) end it "should have documentation" do expect(Puppet::Node::Msgpack.doc).not_to be_nil end it "should be registered with the configuration store indirection" do indirection = Puppet::Indirector::Indirection.instance(:node) expect(Puppet::Node::Msgpack.indirection).to equal(indirection) end it "should have its name set to :msgpack" do expect(Puppet::Node::Msgpack.name).to eq(:msgpack) end end puppet-5.5.10/spec/unit/indirector/node/plain_spec.rb0000644005276200011600000000270613417161721022461 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/node/plain' describe Puppet::Node::Plain do let(:nodename) { "mynode" } let(:indirection_fact_values) { {:afact => "a value"} } let(:indirection_facts) { Puppet::Node::Facts.new(nodename, indirection_fact_values) } let(:request_fact_values) { {:foo => "bar" } } let(:request_facts) { Puppet::Node::Facts.new(nodename, request_fact_values)} let(:environment) { Puppet::Node::Environment.create(:myenv, []) } let(:request) { Puppet::Indirector::Request.new(:node, :find, nodename, nil, :environment => environment) } let(:node_indirection) { Puppet::Node::Plain.new } it "should merge facts from the request if supplied" do Puppet::Node::Facts.indirection.expects(:find).never request.options[:facts] = request_facts node = node_indirection.find(request) expect(node.parameters).to include(request_fact_values) expect(node.facts).to eq(request_facts) end it "should find facts if none are supplied" do Puppet::Node::Facts.indirection.expects(:find).with(nodename, :environment => environment).returns(indirection_facts) request.options.delete(:facts) node = node_indirection.find(request) expect(node.parameters).to include(indirection_fact_values) expect(node.facts).to eq(indirection_facts) end it "should set the node environment from the request" do expect(node_indirection.find(request).environment).to eq(environment) end end puppet-5.5.10/spec/unit/indirector/node/rest_spec.rb0000644005276200011600000000026113417161721022325 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/node/rest' describe Puppet::Node::Rest do before do @searcher = Puppet::Node::Rest.new end end puppet-5.5.10/spec/unit/indirector/node/store_configs_spec.rb0000644005276200011600000000066413417161721024223 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/node' require 'puppet/indirector/memory' require 'puppet/indirector/node/store_configs' class Puppet::Node::StoreConfigsTesting < Puppet::Indirector::Memory end describe Puppet::Node::StoreConfigs do after :each do Puppet::Node.indirection.reset_terminus_class Puppet::Node.indirection.cache_class = nil end it_should_behave_like "a StoreConfigs terminus" end puppet-5.5.10/spec/unit/indirector/node/yaml_spec.rb0000644005276200011600000000124513417161721022315 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/node' require 'puppet/indirector/node/yaml' describe Puppet::Node::Yaml do it "should be a subclass of the Yaml terminus" do expect(Puppet::Node::Yaml.superclass).to equal(Puppet::Indirector::Yaml) end it "should have documentation" do expect(Puppet::Node::Yaml.doc).not_to be_nil end it "should be registered with the configuration store indirection" do indirection = Puppet::Indirector::Indirection.instance(:node) expect(Puppet::Node::Yaml.indirection).to equal(indirection) end it "should have its name set to :node" do expect(Puppet::Node::Yaml.name).to eq(:yaml) end end puppet-5.5.10/spec/unit/indirector/node/exec_spec.rb0000644005276200011600000000547713417161722022313 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/node/exec' require 'puppet/indirector/request' describe Puppet::Node::Exec do before do @indirection = mock 'indirection' Puppet.settings[:external_nodes] = File.expand_path("/echo") @searcher = Puppet::Node::Exec.new end describe "when constructing the command to run" do it "should use the external_node script as the command" do Puppet[:external_nodes] = "/bin/echo" expect(@searcher.command).to eq(%w{/bin/echo}) end it "should throw an exception if no external node command is set" do Puppet[:external_nodes] = "none" expect { @searcher.find(stub('request', :key => "foo")) }.to raise_error(ArgumentError) end end describe "when handling the results of the command" do let(:testing_env) { Puppet::Node::Environment.create(:testing, []) } let(:other_env) { Puppet::Node::Environment.create(:other, []) } let(:request) { Puppet::Indirector::Request.new(:node, :find, @name, nil) } before do @name = "yay" @node = Puppet::Node.new(@name) @node.stubs(:fact_merge) Puppet::Node.expects(:new).with(@name).returns(@node) @result = {} # Use a local variable so the reference is usable in the execute definition. result = @result @searcher.meta_def(:execute) do |command, arguments| return YAML.dump(result) end end around do |example| envs = Puppet::Environments::Static.new(testing_env, other_env) Puppet.override(:environments => envs) do example.run end end it "should translate the YAML into a Node instance" do # Use an empty hash expect(@searcher.find(request)).to equal(@node) end it "should set the resulting parameters as the node parameters" do @result[:parameters] = {"a" => "b", "c" => "d"} @searcher.find(request) expect(@node.parameters).to eq({"a" => "b", "c" => "d", "environment" => "*root*"}) end it "should set the resulting classes as the node classes" do @result[:classes] = %w{one two} @searcher.find(request) expect(@node.classes).to eq([ 'one', 'two' ]) end it "should merge facts from the request if supplied" do facts = Puppet::Node::Facts.new('test', 'foo' => 'bar') request.options[:facts] = facts @node.expects(:fact_merge).with(facts) @searcher.find(request) end it "should set the node's environment if one is provided" do @result[:environment] = "testing" @searcher.find(request) expect(@node.environment.name).to eq(:testing) end it "should set the node's environment based on the request if not otherwise provided" do request.environment = "other" @searcher.find(request) expect(@node.environment.name).to eq(:other) end end end puppet-5.5.10/spec/unit/indirector/node/ldap_spec.rb0000644005276200011600000004677113417161722022311 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/node/ldap' describe Puppet::Node::Ldap do let(:nodename) { "mynode.domain.com" } let(:node_indirection) { Puppet::Node::Ldap.new } let(:environment) { Puppet::Node::Environment.create(:myenv, []) } let(:fact_values) { {:afact => "a value", "one" => "boo"} } let(:facts) { Puppet::Node::Facts.new(nodename, fact_values) } before do Puppet::Node::Facts.indirection.stubs(:find).with(nodename, :environment => environment).returns(facts) end describe "when searching for a single node" do let(:request) { Puppet::Indirector::Request.new(:node, :find, nodename, nil, :environment => environment) } it "should convert the hostname into a search filter" do node_indirection.stubs(:ldap_entry_to_hash).returns({}) entry = stub 'entry', :dn => 'cn=mynode.domain.com,ou=hosts,dc=madstop,dc=com', :vals => %w{} node_indirection.expects(:ldapsearch).with("(&(objectclass=puppetClient)(cn=#{nodename}))").yields entry node_indirection.name2hash(nodename) end it "should convert any found entry into a hash" do node_indirection.stubs(:ldap_entry_to_hash).returns({}) entry = stub 'entry', :dn => 'cn=mynode.domain.com,ou=hosts,dc=madstop,dc=com', :vals => %w{} node_indirection.expects(:ldapsearch).with("(&(objectclass=puppetClient)(cn=#{nodename}))").yields entry myhash = {"myhash" => true} node_indirection.expects(:entry2hash).with(entry).returns myhash expect(node_indirection.name2hash(nodename)).to eq(myhash) end # This heavily tests our entry2hash method, so we don't have to stub out the stupid entry information any more. describe "when an ldap entry is found" do before do @entry = stub 'entry', :dn => 'cn=mynode,ou=hosts,dc=madstop,dc=com', :vals => %w{} node_indirection.stubs(:ldap_entry_to_hash).returns({}) node_indirection.stubs(:ldapsearch).yields @entry end it "should convert the entry to a hash" do expect(node_indirection.entry2hash(@entry)).to be_instance_of(Hash) end it "should add the entry's common name to the hash if fqdn if false" do expect(node_indirection.entry2hash(@entry, false)[:name]).to eq("mynode") end it "should add the entry's fqdn name to the hash if fqdn if true" do expect(node_indirection.entry2hash(@entry, true)[:name]).to eq("mynode.madstop.com") end it "should add all of the entry's classes to the hash" do @entry.stubs(:vals).with("puppetclass").returns %w{one two} expect(node_indirection.entry2hash(@entry)[:classes]).to eq(%w{one two}) end it "should deduplicate class values" do node_indirection.stubs(:ldap_entry_to_hash).returns({}) node_indirection.stubs(:class_attributes).returns(%w{one two}) @entry.stubs(:vals).with("one").returns(%w{a b}) @entry.stubs(:vals).with("two").returns(%w{b c}) expect(node_indirection.entry2hash(@entry)[:classes]).to eq(%w{a b c}) end it "should add the entry's environment to the hash" do node_indirection.stubs(:ldap_entry_to_hash).returns(:environment => %w{production}) expect(node_indirection.entry2hash(@entry)[:environment]).to eq("production") end it "should add all stacked parameters as parameters in the hash" do @entry.stubs(:vals).with("puppetvar").returns(%w{one=two three=four}) result = node_indirection.entry2hash(@entry) expect(result[:parameters]["one"]).to eq("two") expect(result[:parameters]["three"]).to eq("four") end it "should not add the stacked parameter as a normal parameter" do @entry.stubs(:vals).with("puppetvar").returns(%w{one=two three=four}) node_indirection.stubs(:ldap_entry_to_hash).returns("puppetvar" => %w{one=two three=four}) expect(node_indirection.entry2hash(@entry)[:parameters]["puppetvar"]).to be_nil end it "should add all other attributes as parameters in the hash" do node_indirection.stubs(:ldap_entry_to_hash).returns("foo" => %w{one two}) expect(node_indirection.entry2hash(@entry)[:parameters]["foo"]).to eq(%w{one two}) end it "should return single-value parameters as strings, not arrays" do node_indirection.stubs(:ldap_entry_to_hash).returns("foo" => %w{one}) expect(node_indirection.entry2hash(@entry)[:parameters]["foo"]).to eq("one") end it "should convert 'true' values to the boolean 'true'" do node_indirection.stubs(:ldap_entry_to_hash).returns({"one" => ["true"]}) expect(node_indirection.entry2hash(@entry)[:parameters]["one"]).to eq(true) end it "should convert 'false' values to the boolean 'false'" do node_indirection.stubs(:ldap_entry_to_hash).returns({"one" => ["false"]}) expect(node_indirection.entry2hash(@entry)[:parameters]["one"]).to eq(false) end it "should convert 'true' values to the boolean 'true' inside an array" do node_indirection.stubs(:ldap_entry_to_hash).returns({"one" => ["true", "other"]}) expect(node_indirection.entry2hash(@entry)[:parameters]["one"]).to eq([true, "other"]) end it "should convert 'false' values to the boolean 'false' inside an array" do node_indirection.stubs(:ldap_entry_to_hash).returns({"one" => ["false", "other"]}) expect(node_indirection.entry2hash(@entry)[:parameters]["one"]).to eq([false, "other"]) end it "should add the parent's name if present" do @entry.stubs(:vals).with("parentnode").returns(%w{foo}) expect(node_indirection.entry2hash(@entry)[:parent]).to eq("foo") end it "should fail if more than one parent is specified" do @entry.stubs(:vals).with("parentnode").returns(%w{foo}) expect(node_indirection.entry2hash(@entry)[:parent]).to eq("foo") end end it "should search first for the provided key" do node_indirection.expects(:name2hash).with("mynode.domain.com").returns({}) node_indirection.find(request) end it "should search for the short version of the provided key if the key looks like a hostname and no results are found for the key itself" do node_indirection.expects(:name2hash).with("mynode.domain.com").returns(nil) node_indirection.expects(:name2hash).with("mynode").returns({}) node_indirection.find(request) end it "should search for default information if no information can be found for the key" do node_indirection.expects(:name2hash).with("mynode.domain.com").returns(nil) node_indirection.expects(:name2hash).with("mynode").returns(nil) node_indirection.expects(:name2hash).with("default").returns({}) node_indirection.find(request) end it "should return nil if no results are found in ldap" do node_indirection.stubs(:name2hash).returns nil expect(node_indirection.find(request)).to be_nil end it "should return a node object if results are found in ldap" do node_indirection.stubs(:name2hash).returns({}) expect(node_indirection.find(request)).to be end describe "and node information is found in LDAP" do before do @result = {} node_indirection.stubs(:name2hash).returns @result end it "should create the node with the correct name, even if it was found by a different name" do node_indirection.expects(:name2hash).with(nodename).returns nil node_indirection.expects(:name2hash).with("mynode").returns @result expect(node_indirection.find(request).name).to eq(nodename) end it "should add any classes from ldap" do classes = %w{a b c d} @result[:classes] = classes expect(node_indirection.find(request).classes).to eq(classes) end it "should add all entry attributes as node parameters" do params = {"one" => "two", "three" => "four"} @result[:parameters] = params expect(node_indirection.find(request).parameters).to include(params) end it "should set the node's environment to the environment of the results" do result_env = Puppet::Node::Environment.create(:local_test, []) Puppet::Node::Facts.indirection.stubs(:find).with(nodename, :environment => result_env).returns(facts) @result[:environment] = "local_test" Puppet.override(:environments => Puppet::Environments::Static.new(result_env)) do expect(node_indirection.find(request).environment).to eq(result_env) end end it "should retain false parameter values" do @result[:parameters] = {} @result[:parameters]["one"] = false expect(node_indirection.find(request).parameters).to include({"one" => false}) end context("when merging facts") do let(:request_facts) { Puppet::Node::Facts.new('test', 'foo' => 'bar') } let(:indirection_facts) { Puppet::Node::Facts.new('test', 'baz' => 'qux') } it "should merge facts from the request if supplied" do request.options[:facts] = request_facts Puppet::Node::Facts.stubs(:find) { indirection_facts } expect(node_indirection.find(request).parameters).to include(request_facts.values) expect(node_indirection.find(request).facts).to eq(request_facts) end it "should find facts if none are supplied" do Puppet::Node::Facts.indirection.stubs(:find).with(nodename, :environment => environment).returns(indirection_facts) request.options.delete(:facts) expect(node_indirection.find(request).parameters).to include(indirection_facts.values) expect(node_indirection.find(request).facts).to eq(indirection_facts) end it "should merge the node's facts after the parameters from ldap are assigned" do # Make sure we've got data to start with, so the parameters are actually set. params = {"one" => "yay", "two" => "hooray"} @result[:parameters] = params # Node implements its own merge so that an existing param takes # precedence over facts. We get the same result here by merging params # into facts expect(node_indirection.find(request).parameters).to eq(facts.values.merge(params)) end end describe "and a parent node is specified" do before do @entry = {:classes => [], :parameters => {}} @parent = {:classes => [], :parameters => {}} @parent_parent = {:classes => [], :parameters => {}} node_indirection.stubs(:name2hash).with(nodename).returns(@entry) node_indirection.stubs(:name2hash).with('parent').returns(@parent) node_indirection.stubs(:name2hash).with('parent_parent').returns(@parent_parent) node_indirection.stubs(:parent_attribute).returns(:parent) end it "should search for the parent node" do @entry[:parent] = "parent" node_indirection.expects(:name2hash).with(nodename).returns @entry node_indirection.expects(:name2hash).with('parent').returns @parent node_indirection.find(request) end it "should fail if the parent cannot be found" do @entry[:parent] = "parent" node_indirection.expects(:name2hash).with('parent').returns nil expect { node_indirection.find(request) }.to raise_error(Puppet::Error, /Could not find parent node/) end it "should add any parent classes to the node's classes" do @entry[:parent] = "parent" @entry[:classes] = %w{a b} @parent[:classes] = %w{c d} expect(node_indirection.find(request).classes).to eq(%w{a b c d}) end it "should add any parent parameters to the node's parameters" do @entry[:parent] = "parent" @entry[:parameters]["one"] = "two" @parent[:parameters]["three"] = "four" expect(node_indirection.find(request).parameters).to include({"one" => "two", "three" => "four"}) end it "should prefer node parameters over parent parameters" do @entry[:parent] = "parent" @entry[:parameters]["one"] = "two" @parent[:parameters]["one"] = "three" expect(node_indirection.find(request).parameters).to include({"one" => "two"}) end it "should use the parent's environment if the node has none" do env = Puppet::Node::Environment.create(:parent, []) @entry[:parent] = "parent" @parent[:environment] = "parent" Puppet::Node::Facts.indirection.stubs(:find).with(nodename, :environment => env).returns(facts) Puppet.override(:environments => Puppet::Environments::Static.new(env)) do expect(node_indirection.find(request).environment).to eq(env) end end it "should prefer the node's environment to the parent's" do child_env = Puppet::Node::Environment.create(:child, []) @entry[:parent] = "parent" @entry[:environment] = "child" @parent[:environment] = "parent" Puppet::Node::Facts.indirection.stubs(:find).with(nodename, :environment => child_env).returns(facts) Puppet.override(:environments => Puppet::Environments::Static.new(child_env)) do expect(node_indirection.find(request).environment).to eq(child_env) end end it "should recursively look up parent information" do @entry[:parent] = "parent" @entry[:parameters]["one"] = "two" @parent[:parent] = "parent_parent" @parent[:parameters]["three"] = "four" @parent_parent[:parameters]["five"] = "six" expect(node_indirection.find(request).parameters).to include("one" => "two", "three" => "four", "five" => "six") end it "should not allow loops in parent declarations" do @entry[:parent] = "parent" @parent[:parent] = nodename expect { node_indirection.find(request) }.to raise_error(ArgumentError) end end end end describe "when searching for multiple nodes" do let(:options) { {:environment => environment} } let(:request) { Puppet::Indirector::Request.new(:node, :find, nodename, nil, options) } before :each do Puppet::Node::Facts.indirection.stubs(:terminus_class).returns :yaml end it "should find all nodes if no arguments are provided" do node_indirection.expects(:ldapsearch).with("(objectclass=puppetClient)") # LAK:NOTE The search method requires an essentially bogus key. It's # an API problem that I don't really know how to fix. node_indirection.search request end describe "and a class is specified" do it "should find all nodes that are members of that class" do node_indirection.expects(:ldapsearch).with("(&(objectclass=puppetClient)(puppetclass=one))") options[:class] = "one" node_indirection.search request end end describe "multiple classes are specified" do it "should find all nodes that are members of all classes" do node_indirection.expects(:ldapsearch).with("(&(objectclass=puppetClient)(puppetclass=one)(puppetclass=two))") options[:class] = %w{one two} node_indirection.search request end end it "should process each found entry" do # .yields can't be used to yield multiple values :/ node_indirection.expects(:ldapsearch).yields("one") node_indirection.expects(:entry2hash).with("one",nil).returns(:name => nodename) node_indirection.search request end it "should return a node for each processed entry with the name from the entry" do node_indirection.expects(:ldapsearch).yields("whatever") node_indirection.expects(:entry2hash).with("whatever",nil).returns(:name => nodename) result = node_indirection.search(request) expect(result[0]).to be_instance_of(Puppet::Node) expect(result[0].name).to eq(nodename) end it "should merge each node's facts" do node_indirection.stubs(:ldapsearch).yields("one") node_indirection.stubs(:entry2hash).with("one",nil).returns(:name => nodename) expect(node_indirection.search(request)[0].parameters).to include(fact_values) end it "should pass the request's fqdn option to entry2hash" do options[:fqdn] = :hello node_indirection.stubs(:ldapsearch).yields("one") node_indirection.expects(:entry2hash).with("one",:hello).returns(:name => nodename) node_indirection.search(request) end end describe Puppet::Node::Ldap, " when developing the search query" do it "should return the value of the :ldapclassattrs split on commas as the class attributes" do Puppet[:ldapclassattrs] = "one,two" expect(node_indirection.class_attributes).to eq(%w{one two}) end it "should return nil as the parent attribute if the :ldapparentattr is set to an empty string" do Puppet[:ldapparentattr] = "" expect(node_indirection.parent_attribute).to be_nil end it "should return the value of the :ldapparentattr as the parent attribute" do Puppet[:ldapparentattr] = "pere" expect(node_indirection.parent_attribute).to eq("pere") end it "should use the value of the :ldapstring as the search filter" do Puppet[:ldapstring] = "mystring" expect(node_indirection.search_filter("testing")).to eq("mystring") end it "should replace '%s' with the node name in the search filter if it is present" do Puppet[:ldapstring] = "my%sstring" expect(node_indirection.search_filter("testing")).to eq("mytestingstring") end it "should not modify the global :ldapstring when replacing '%s' in the search filter" do filter = mock 'filter' filter.expects(:include?).with("%s").returns(true) filter.expects(:gsub).with("%s", "testing").returns("mynewstring") Puppet[:ldapstring] = filter expect(node_indirection.search_filter("testing")).to eq("mynewstring") end end describe Puppet::Node::Ldap, " when deciding attributes to search for" do it "should use 'nil' if the :ldapattrs setting is 'all'" do Puppet[:ldapattrs] = "all" expect(node_indirection.search_attributes).to be_nil end it "should split the value of :ldapattrs on commas and use the result as the attribute list" do Puppet[:ldapattrs] = "one,two" node_indirection.stubs(:class_attributes).returns([]) node_indirection.stubs(:parent_attribute).returns(nil) expect(node_indirection.search_attributes).to eq(%w{one two}) end it "should add the class attributes to the search attributes if not returning all attributes" do Puppet[:ldapattrs] = "one,two" node_indirection.stubs(:class_attributes).returns(%w{three four}) node_indirection.stubs(:parent_attribute).returns(nil) # Sort them so i don't have to care about return order expect(node_indirection.search_attributes.sort).to eq(%w{one two three four}.sort) end it "should add the parent attribute to the search attributes if not returning all attributes" do Puppet[:ldapattrs] = "one,two" node_indirection.stubs(:class_attributes).returns([]) node_indirection.stubs(:parent_attribute).returns("parent") expect(node_indirection.search_attributes.sort).to eq(%w{one two parent}.sort) end it "should not add nil parent attributes to the search attributes" do Puppet[:ldapattrs] = "one,two" node_indirection.stubs(:class_attributes).returns([]) node_indirection.stubs(:parent_attribute).returns(nil) expect(node_indirection.search_attributes).to eq(%w{one two}) end end end puppet-5.5.10/spec/unit/indirector/node/write_only_yaml_spec.rb0000644005276200011600000000062013417161722024565 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/node' require 'puppet/indirector/node/write_only_yaml' describe Puppet::Node::WriteOnlyYaml do it "should be deprecated" do Puppet.expects(:warn_once).with('deprecations', 'Puppet::Node::WriteOnlyYaml', 'Puppet::Node::WriteOnlyYaml is deprecated and will be removed in a future release of Puppet.') described_class.new end end puppet-5.5.10/spec/unit/indirector/none_spec.rb0000644005276200011600000000157613417161721021374 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/indirector/none' describe Puppet::Indirector::None do before do Puppet::Indirector::Terminus.stubs(:register_terminus_class) Puppet::Indirector::Indirection.stubs(:instance).returns(indirection) module Testing; end @none_class = class Testing::None < Puppet::Indirector::None self end @data_binder = @none_class.new end let(:model) { mock('model') } let(:request) { stub('request', :key => "port") } let(:indirection) do stub('indirection', :name => :none, :register_terminus_type => nil, :model => model) end it "should not be the default data_binding_terminus" do expect(Puppet.settings[:data_binding_terminus]).not_to eq('none') end describe "the behavior of the find method" do it "should just return nil" do expect(@data_binder.find(request)).to be_nil end end end puppet-5.5.10/spec/unit/indirector/plain_spec.rb0000644005276200011600000000146313417161721021533 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/plain' describe Puppet::Indirector::Plain do before do Puppet::Indirector::Terminus.stubs(:register_terminus_class) @model = mock 'model' @indirection = stub 'indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) module Testing; end @plain_class = class Testing::MyPlain < Puppet::Indirector::Plain self end @searcher = @plain_class.new @request = stub 'request', :key => "yay" end it "should return return an instance of the indirected model" do object = mock 'object' @model.expects(:new).with(@request.key).returns object expect(@searcher.find(@request)).to equal(object) end end puppet-5.5.10/spec/unit/indirector/report/0000755005276200011600000000000013417162176020405 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/indirector/report/msgpack_spec.rb0000644005276200011600000000170313417161721023365 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/transaction/report' require 'puppet/indirector/report/msgpack' describe Puppet::Transaction::Report::Msgpack, :if => Puppet.features.msgpack? do it "should be a subclass of the Msgpack terminus" do expect(Puppet::Transaction::Report::Msgpack.superclass).to equal(Puppet::Indirector::Msgpack) end it "should have documentation" do expect(Puppet::Transaction::Report::Msgpack.doc).not_to be_nil end it "should be registered with the report indirection" do indirection = Puppet::Indirector::Indirection.instance(:report) expect(Puppet::Transaction::Report::Msgpack.indirection).to equal(indirection) end it "should have its name set to :msgpack" do expect(Puppet::Transaction::Report::Msgpack.name).to eq(:msgpack) end it "should unconditionally save/load from the --lastrunreport setting" do expect(subject.path(:me)).to eq(Puppet[:lastrunreport]) end end puppet-5.5.10/spec/unit/indirector/report/processor_spec.rb0000644005276200011600000000602713417161721023763 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/report/processor' describe Puppet::Transaction::Report::Processor do before do Puppet.settings.stubs(:use).returns(true) end it "should provide a method for saving reports" do expect(Puppet::Transaction::Report::Processor.new).to respond_to(:save) end it "should provide a method for cleaning reports" do expect(Puppet::Transaction::Report::Processor.new).to respond_to(:destroy) end end describe Puppet::Transaction::Report::Processor, " when processing a report" do before do Puppet.settings.stubs(:use) @reporter = Puppet::Transaction::Report::Processor.new @request = stub 'request', :instance => stub("report", :host => 'hostname'), :key => 'node' end it "should not save the report if reports are set to 'none'" do Puppet::Reports.expects(:report).never Puppet[:reports] = 'none' request = Puppet::Indirector::Request.new(:indirection_name, :head, "key", nil) report = Puppet::Transaction::Report.new request.instance = report @reporter.save(request) end it "should save the report with each configured report type" do Puppet[:reports] = "one,two" expect(@reporter.send(:reports)).to eq(%w{one two}) Puppet::Reports.expects(:report).with('one') Puppet::Reports.expects(:report).with('two') @reporter.save(@request) end it "should destroy reports for each processor that responds to destroy" do Puppet[:reports] = "http,store" http_report = mock() store_report = mock() store_report.expects(:destroy).with(@request.key) Puppet::Reports.expects(:report).with('http').returns(http_report) Puppet::Reports.expects(:report).with('store').returns(store_report) @reporter.destroy(@request) end end describe Puppet::Transaction::Report::Processor, " when processing a report" do before do Puppet[:reports] = "one" Puppet.settings.stubs(:use) @reporter = Puppet::Transaction::Report::Processor.new @report_type = mock 'one' @dup_report = mock 'dupe report' @dup_report.stubs(:process) @report = Puppet::Transaction::Report.new @report.expects(:dup).returns(@dup_report) @request = stub 'request', :instance => @report Puppet::Reports.expects(:report).with("one").returns(@report_type) @dup_report.expects(:extend).with(@report_type) end # LAK:NOTE This is stupid, because the code is so short it doesn't # make sense to split it out, which means I just do the same test # three times so the spec looks right. it "should process a duplicate of the report, not the original" do @reporter.save(@request) end it "should extend the report with the report type's module" do @reporter.save(@request) end it "should call the report type's :process method" do @dup_report.expects(:process) @reporter.save(@request) end it "should not raise exceptions" do Puppet[:trace] = false @dup_report.expects(:process).raises(ArgumentError) expect { @reporter.save(@request) }.not_to raise_error end end puppet-5.5.10/spec/unit/indirector/report/rest_spec.rb0000644005276200011600000000661713417161721022726 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/report/rest' describe Puppet::Transaction::Report::Rest do it "should be a subclass of Puppet::Indirector::REST" do expect(Puppet::Transaction::Report::Rest.superclass).to equal(Puppet::Indirector::REST) end it "should use the :report_server setting in preference to :server" do Puppet.settings[:server] = "server" Puppet.settings[:report_server] = "report_server" expect(Puppet::Transaction::Report::Rest.server).to eq("report_server") end it "should have a value for report_server and report_port" do expect(Puppet::Transaction::Report::Rest.server).not_to be_nil expect(Puppet::Transaction::Report::Rest.port).not_to be_nil end it "should use the :report SRV service" do expect(Puppet::Transaction::Report::Rest.srv_service).to eq(:report) end let(:model) { Puppet::Transaction::Report } let(:terminus_class) { Puppet::Transaction::Report::Rest } let(:terminus) { model.indirection.terminus(:rest) } let(:indirection) { model.indirection } before(:each) do Puppet::Transaction::Report.indirection.terminus_class = :rest end def mock_response(code, body, content_type='text/plain', encoding=nil) obj = stub('http 200 ok', :code => code.to_s, :body => body) obj.stubs(:[]).with('content-type').returns(content_type) obj.stubs(:[]).with('content-encoding').returns(encoding) obj.stubs(:[]).with(Puppet::Network::HTTP::HEADER_PUPPET_VERSION).returns(Puppet.version) obj end def save_request(key, instance, options={}) Puppet::Indirector::Request.new(:report, :find, key, instance, options) end describe "#save" do let(:response) { mock_response(200, 'body') } let(:connection) { stub('mock http connection', :put => response, :verify_callback= => nil) } let(:instance) { model.new('the thing', 'some contents') } let(:request) { save_request(instance.name, instance) } let(:body) { ["store", "http"].to_pson } before :each do terminus.stubs(:network).returns(connection) end it "deserializes the response as an array of report processor names" do response = mock_response('200', body, 'text/pson') connection.expects(:put).returns response expect(terminus.save(request)).to eq(["store", "http"]) end describe "when handling the response" do describe "when the server major version is less than 5" do it "raises if the save fails and we're not using pson" do Puppet[:preferred_serialization_format] = "json" response = mock_response('500', '{}', 'text/pson') response.stubs(:[]).with(Puppet::Network::HTTP::HEADER_PUPPET_VERSION).returns("4.10.1") connection.expects(:put).returns response expect { terminus.save(request) }.to raise_error(Puppet::Error, /Server version 4.10.1 does not accept reports in 'json'/) end it "raises with HTTP 500 if the save fails and we're already using pson" do Puppet[:preferred_serialization_format] = "pson" response = mock_response('500', '{}', 'text/pson') response.stubs(:[]).with(Puppet::Network::HTTP::HEADER_PUPPET_VERSION).returns("4.10.1") connection.expects(:put).returns response expect { terminus.save(request) }.to raise_error(Net::HTTPError, /Error 500 on SERVER/) end end end end end puppet-5.5.10/spec/unit/indirector/report/yaml_spec.rb0000644005276200011600000000160413417161721022702 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/transaction/report' require 'puppet/indirector/report/yaml' describe Puppet::Transaction::Report::Yaml do it "should be a subclass of the Yaml terminus" do expect(Puppet::Transaction::Report::Yaml.superclass).to equal(Puppet::Indirector::Yaml) end it "should have documentation" do expect(Puppet::Transaction::Report::Yaml.doc).not_to be_nil end it "should be registered with the report indirection" do indirection = Puppet::Indirector::Indirection.instance(:report) expect(Puppet::Transaction::Report::Yaml.indirection).to equal(indirection) end it "should have its name set to :yaml" do expect(Puppet::Transaction::Report::Yaml.name).to eq(:yaml) end it "should unconditionally save/load from the --lastrunreport setting" do expect(subject.path(:me)).to eq(Puppet[:lastrunreport]) end end puppet-5.5.10/spec/unit/indirector/resource/0000755005276200011600000000000013417162176020721 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/indirector/resource/store_configs_spec.rb0000644005276200011600000000107713417161721025124 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/resource' require 'puppet/indirector/memory' require 'puppet/indirector/resource/store_configs' class Puppet::Resource::StoreConfigsTesting < Puppet::Indirector::Memory end describe Puppet::Resource::StoreConfigs do it_should_behave_like "a StoreConfigs terminus" before :each do Puppet[:storeconfigs] = true Puppet[:storeconfigs_backend] = "store_configs_testing" end it "disallows remote requests" do expect(Puppet::Resource::StoreConfigs.new.allow_remote_requests?).to eq(false) end end puppet-5.5.10/spec/unit/indirector/resource/ral_spec.rb0000644005276200011600000001164213417161722023036 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe "Puppet::Resource::Ral" do it "disallows remote requests" do expect(Puppet::Resource::Ral.new.allow_remote_requests?).to eq(false) end describe "find" do before do @request = stub 'request', :key => "user/root" end it "should find an existing instance" do my_resource = stub "my user resource" wrong_instance = stub "wrong user", :name => "bob" my_instance = stub "my user", :name => "root", :to_resource => my_resource require 'puppet/type/user' Puppet::Type::User.expects(:instances).returns([ wrong_instance, my_instance, wrong_instance ]) expect(Puppet::Resource::Ral.new.find(@request)).to eq(my_resource) end it "should produce Puppet::Error instead of ArgumentError" do @bad_request = stub 'thiswillcauseanerror', :key => "thiswill/causeanerror" expect{Puppet::Resource::Ral.new.find(@bad_request)}.to raise_error(Puppet::Error) end it "if there is no instance, it should create one" do wrong_instance = stub "wrong user", :name => "bob" root = mock "Root User" root_resource = mock "Root Resource" require 'puppet/type/user' Puppet::Type::User.expects(:instances).returns([ wrong_instance, wrong_instance ]) Puppet::Type::User.expects(:new).with(has_entry(:name => "root")).returns(root) root.expects(:to_resource).returns(root_resource) result = Puppet::Resource::Ral.new.find(@request) expect(result).to eq(root_resource) end end describe "search" do before do @request = stub 'request', :key => "user/", :options => {} end it "should convert ral resources into regular resources" do my_resource = stub "my user resource" my_instance = stub "my user", :name => "root", :to_resource => my_resource require 'puppet/type/user' Puppet::Type::User.expects(:instances).returns([ my_instance ]) expect(Puppet::Resource::Ral.new.search(@request)).to eq([my_resource]) end it "should filter results by name if there's a name in the key" do my_resource = stub "my user resource" my_resource.stubs(:to_resource).returns(my_resource) my_resource.stubs(:[]).with(:name).returns("root") wrong_resource = stub "wrong resource" wrong_resource.stubs(:to_resource).returns(wrong_resource) wrong_resource.stubs(:[]).with(:name).returns("bad") my_instance = stub "my user", :to_resource => my_resource wrong_instance = stub "wrong user", :to_resource => wrong_resource @request = stub 'request', :key => "user/root", :options => {} require 'puppet/type/user' Puppet::Type::User.expects(:instances).returns([ my_instance, wrong_instance ]) expect(Puppet::Resource::Ral.new.search(@request)).to eq([my_resource]) end it "should filter results by query parameters" do wrong_resource = stub "my user resource" wrong_resource.stubs(:to_resource).returns(wrong_resource) wrong_resource.stubs(:[]).with(:name).returns("root") my_resource = stub "wrong resource" my_resource.stubs(:to_resource).returns(my_resource) my_resource.stubs(:[]).with(:name).returns("bob") my_instance = stub "my user", :to_resource => my_resource wrong_instance = stub "wrong user", :to_resource => wrong_resource @request = stub 'request', :key => "user/", :options => {:name => "bob"} require 'puppet/type/user' Puppet::Type::User.expects(:instances).returns([ my_instance, wrong_instance ]) expect(Puppet::Resource::Ral.new.search(@request)).to eq([my_resource]) end it "should return sorted results" do a_resource = stub "alice resource" a_resource.stubs(:to_resource).returns(a_resource) a_resource.stubs(:title).returns("alice") b_resource = stub "bob resource" b_resource.stubs(:to_resource).returns(b_resource) b_resource.stubs(:title).returns("bob") a_instance = stub "alice user", :to_resource => a_resource b_instance = stub "bob user", :to_resource => b_resource @request = stub 'request', :key => "user/", :options => {} require 'puppet/type/user' Puppet::Type::User.expects(:instances).returns([ b_instance, a_instance ]) expect(Puppet::Resource::Ral.new.search(@request)).to eq([a_resource, b_resource]) end end describe "save" do it "returns a report covering the application of the given resource to the system" do resource = Puppet::Resource.new(:notify, "the title") ral = Puppet::Resource::Ral.new applied_resource, report = ral.save(Puppet::Indirector::Request.new(:ral, :save, 'testing', resource, :environment => Puppet::Node::Environment.remote(:testing))) expect(applied_resource.title).to eq("the title") expect(report.environment).to eq("testing") expect(report.resource_statuses["Notify[the title]"].changed).to eq(true) end end end puppet-5.5.10/spec/unit/indirector/rest_spec.rb0000644005276200011600000006734413417161721021417 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/json' require 'puppet/indirector' require 'puppet/indirector/errors' require 'puppet/indirector/rest' require 'puppet/util/psych_support' HTTP_ERROR_CODES = [300, 400, 500] # Just one from each category since the code makes no real distinctions shared_examples_for "a REST terminus method" do |terminus_method| describe "when handling the response" do let(:response) do mock_response(200, 'OK') end it "falls back to pson for future requests" do response.stubs(:[]).with(Puppet::Network::HTTP::HEADER_PUPPET_VERSION).returns("4.10.1") terminus.send(terminus_method, request) expect(Puppet[:preferred_serialization_format]).to eq("pson") end it "doesn't change the serialization format if the X-Puppet-Version header is missing" do response.stubs(:[]).with(Puppet::Network::HTTP::HEADER_PUPPET_VERSION).returns(nil) terminus.send(terminus_method, request) expect(Puppet[:preferred_serialization_format]).to eq("json") end it "doesn't change the serialization format if the server major version is 5" do response.stubs(:[]).with(Puppet::Network::HTTP::HEADER_PUPPET_VERSION).returns("5.0.3") terminus.send(terminus_method, request) expect(Puppet[:preferred_serialization_format]).to eq("json") end it "doesn't change the serialization format if the current format is already pson" do response.stubs(:[]).with(Puppet::Network::HTTP::HEADER_PUPPET_VERSION).returns("4.10.1") Puppet[:preferred_serialization_format] = "pson" terminus.send(terminus_method, request) expect(Puppet[:preferred_serialization_format]).to eq("pson") end end HTTP_ERROR_CODES.each do |code| describe "when the response code is #{code}" do let(:message) { 'error messaged!!!' } let(:body) do Puppet::Util::Json.dump({ :issue_kind => 'server-error', :message => message }) end let(:response) { mock_response(code, body, 'application/json') } describe "when the response is plain text" do let(:response) { mock_response(code, message) } it "raises an http error with the body of the response when plain text" do expect { terminus.send(terminus_method, request) }.to raise_error(Net::HTTPError, "Error #{code} on SERVER: #{message}") end end it "raises an http error with the body's message field when json" do expect { terminus.send(terminus_method, request) }.to raise_error(Net::HTTPError, "Error #{code} on SERVER: #{message}") end it "does not attempt to deserialize the response into a model" do model.expects(:convert_from).never expect { terminus.send(terminus_method, request) }.to raise_error(Net::HTTPError) end # I'm not sure what this means or if it's used it "if the body is empty raises an http error with the response header" do response.stubs(:body).returns "" response.stubs(:message).returns "fhqwhgads" expect { terminus.send(terminus_method, request) }.to raise_error(Net::HTTPError, "Error #{code} on SERVER: #{response.message}") end describe "and the body is compressed" do it "raises an http error with the decompressed body of the response" do compressed_body = Zlib::Deflate.deflate(body) compressed_response = mock_response(code, compressed_body, 'application/json', 'deflate') connection.expects(http_method).returns(compressed_response) expect { terminus.send(terminus_method, request) }.to raise_error(Net::HTTPError, "Error #{code} on SERVER: #{message}") end end end end end shared_examples_for "a deserializing terminus method" do |terminus_method| describe "when the response has no content-type" do let(:response) { mock_response(200, "body", nil, nil) } it "raises an error" do expect { terminus.send(terminus_method, request) }.to raise_error(RuntimeError, "No content type in http response; cannot parse") end end it "doesn't catch errors in deserialization" do model.expects(:convert_from).raises(Puppet::Error, "Whoa there") expect { terminus.send(terminus_method, request) }.to raise_error(Puppet::Error, "Whoa there") end end describe Puppet::Indirector::REST do before :all do class Puppet::TestModel include Puppet::Util::PsychSupport extend Puppet::Indirector indirects :test_model attr_accessor :name, :data def initialize(name = "name", data = '') @name = name @data = data end def self.convert_from(format, string) new('', string) end def self.convert_from_multiple(format, string) string.split(',').collect { |s| convert_from(format, s) } end def to_data_hash { 'name' => @name, 'data' => @data } end def ==(other) other.is_a? Puppet::TestModel and other.name == name and other.data == data end end # The subclass must not be all caps even though the superclass is class Puppet::TestModel::Rest < Puppet::Indirector::REST end Puppet::TestModel.indirection.terminus_class = :rest end after :all do Puppet::TestModel.indirection.delete # Remove the class, unlinking it from the rest of the system. Puppet.send(:remove_const, :TestModel) end let(:terminus_class) { Puppet::TestModel::Rest } let(:terminus) { Puppet::TestModel.indirection.terminus(:rest) } let(:indirection) { Puppet::TestModel.indirection } let(:model) { Puppet::TestModel } let(:url_prefix) { "#{Puppet::Network::HTTP::MASTER_URL_PREFIX}/v3"} around(:each) do |example| Puppet.override(:current_environment => Puppet::Node::Environment.create(:production, [])) do example.run end end def mock_response(code, body, content_type='text/plain', encoding=nil) obj = stub('http 200 ok', :code => code.to_s, :body => body) obj.stubs(:[]).with('content-type').returns(content_type) obj.stubs(:[]).with('content-encoding').returns(encoding) obj.stubs(:[]).with(Puppet::Network::HTTP::HEADER_PUPPET_VERSION).returns(Puppet.version) obj end def find_request(key, options={}) Puppet::Indirector::Request.new(:test_model, :find, key, nil, options) end def head_request(key, options={}) Puppet::Indirector::Request.new(:test_model, :head, key, nil, options) end def search_request(key, options={}) Puppet::Indirector::Request.new(:test_model, :search, key, nil, options) end def delete_request(key, options={}) Puppet::Indirector::Request.new(:test_model, :destroy, key, nil, options) end def save_request(key, instance, options={}) Puppet::Indirector::Request.new(:test_model, :save, key, instance, options) end it "should have a method for specifying what setting a subclass should use to retrieve its server" do expect(terminus_class).to respond_to(:use_server_setting) end it "should use any specified setting to pick the server" do terminus_class.expects(:server_setting).returns :ca_server Puppet[:ca_server] = "myserver" expect(terminus_class.server).to eq("myserver") end it "should default to :server for the server setting" do terminus_class.expects(:server_setting).returns nil Puppet[:server] = "myserver" expect(terminus_class.server).to eq("myserver") end it "should have a method for specifying what setting a subclass should use to retrieve its port" do expect(terminus_class).to respond_to(:use_port_setting) end it "should use any specified setting to pick the port" do terminus_class.expects(:port_setting).returns :ca_port Puppet[:ca_port] = "321" expect(terminus_class.port).to eq(321) end it "should default to :port for the port setting" do terminus_class.expects(:port_setting).returns nil Puppet[:masterport] = "543" expect(terminus_class.port).to eq(543) end it "should use a failover-selected server if set" do terminus_class.expects(:server_setting).returns nil Puppet.override(:server => "myserver") do expect(terminus_class.server).to eq("myserver") end end it "should use a failover-selected port if set" do terminus_class.expects(:port_setting).returns nil Puppet.override(:serverport => 321) do expect(terminus_class.port).to eq(321) end end it "should use server_list for server when available" do terminus_class.expects(:server_setting).returns nil Puppet[:server_list] = [["foo", "123"]] expect(terminus_class.server).to eq("foo") end it "should prefer failover-selected server from server list" do terminus_class.expects(:server_setting).returns nil Puppet[:server_list] = [["foo", "123"],["bar", "321"]] Puppet.override(:server => "bar") do expect(terminus_class.server).to eq("bar") end end it "should use server_list for port when available" do terminus_class.expects(:port_setting).returns nil Puppet[:server_list] = [["foo", "123"]] expect(terminus_class.port).to eq(123) end it "should prefer failover-selected port from server list" do terminus_class.expects(:port_setting).returns nil Puppet[:server_list] = [["foo", "123"],["bar", "321"]] Puppet.override(:serverport => "321") do expect(terminus_class.port).to eq(321) end end it "should use an explicitly specified more-speciic server when failover is active" do terminus_class.expects(:server_setting).returns :ca_server Puppet[:ca_server] = "myserver" Puppet.override(:server => "anotherserver") do expect(terminus_class.server).to eq("myserver") end end it "should use an explicitly specified more-specific port when failover is active" do terminus_class.expects(:port_setting).returns :ca_port Puppet[:ca_port] = 321 Puppet.override(:serverport => 543) do expect(terminus_class.port).to eq(321) end end it "should use a default port when a more-specific server is set" do terminus_class.expects(:server_setting).returns :ca_server terminus_class.expects(:port_setting).returns :ca_port Puppet[:ca_server] = "myserver" Puppet.override(:server => "anotherserver", :port => 666) do expect(terminus_class.port).to eq(8140) end end it 'should default to :puppet for the srv_service' do expect(Puppet::Indirector::REST.srv_service).to eq(:puppet) end it 'excludes yaml from the Accept header' do model.expects(:supported_formats).returns([:json, :pson, :yaml, :binary]) expect(terminus.headers['Accept']).to eq('application/json, text/pson, application/octet-stream') end it 'excludes b64_zlib_yaml from the Accept header' do model.expects(:supported_formats).returns([:json, :pson, :b64_zlib_yaml]) expect(terminus.headers['Accept']).to eq('application/json, text/pson') end it 'excludes dot from the Accept header' do model.expects(:supported_formats).returns([:json, :dot]) expect(terminus.headers['Accept']).to eq('application/json') end describe "when creating an HTTP client" do it "should use the class's server and port if the indirection request provides neither" do @request = stub 'request', :key => "foo", :server => nil, :port => nil terminus.class.expects(:port).returns 321 terminus.class.expects(:server).returns "myserver" Puppet::Network::HttpPool.expects(:http_instance).with("myserver", 321).returns "myconn" expect(terminus.network(@request)).to eq("myconn") end it "should use the server from the indirection request if one is present" do @request = stub 'request', :key => "foo", :server => "myserver", :port => nil terminus.class.stubs(:port).returns 321 Puppet::Network::HttpPool.expects(:http_instance).with("myserver", 321).returns "myconn" expect(terminus.network(@request)).to eq("myconn") end it "should use the port from the indirection request if one is present" do @request = stub 'request', :key => "foo", :server => nil, :port => 321 terminus.class.stubs(:server).returns "myserver" Puppet::Network::HttpPool.expects(:http_instance).with("myserver", 321).returns "myconn" expect(terminus.network(@request)).to eq("myconn") end end describe "#find" do let(:http_method) { :get } let(:response) { mock_response(200, 'body') } let(:connection) { stub('mock http connection', :get => response, :verify_callback= => nil) } let(:request) { find_request('foo') } before :each do terminus.stubs(:network).returns(connection) end it_behaves_like 'a REST terminus method', :find it_behaves_like 'a deserializing terminus method', :find describe "with a long set of parameters" do it "calls post on the connection with the query params in the body" do params = {} 'aa'.upto('zz') do |s| params[s] = 'foo' end # The request special-cases this parameter, and it # won't be passed on to the server, so we remove it here # to avoid a failure. params.delete('ip') params["environment"] = "production" request = find_request('whoa', params) connection.expects(:post).with do |uri, body| body.split("&").sort == params.map {|key,value| "#{key}=#{value}"}.sort end.returns(mock_response(200, 'body')) terminus.find(request) end end describe "with no parameters" do it "calls get on the connection" do request = find_request('foo bar') connection.expects(:get).with("#{url_prefix}/test_model/foo%20bar?environment=production&", anything).returns(mock_response('200', 'response body')) expect(terminus.find(request)).to eq(model.new('foo bar', 'response body')) end end it "returns nil on 404" do response = mock_response('404', nil) connection.expects(:get).returns(response) expect(terminus.find(request)).to eq(nil) end it 'raises no warning for a 404 (when not asked to do so)' do response = mock_response('404', 'this is the notfound you are looking for') connection.expects(:get).returns(response) expect{terminus.find(request)}.to_not raise_error() end context 'when fail_on_404 is used in request' do it 'raises an error for a 404 when asked to do so' do request = find_request('foo', :fail_on_404 => true) response = mock_response('404', 'this is the notfound you are looking for') connection.expects(:get).returns(response) expect do terminus.find(request) end.to raise_error( Puppet::Error, "Find #{url_prefix}/test_model/foo?environment=production&fail_on_404=true resulted in 404 with the message: this is the notfound you are looking for") end it 'truncates the URI when it is very long' do request = find_request('foo', :fail_on_404 => true, :long_param => ('A' * 100) + 'B') response = mock_response('404', 'this is the notfound you are looking for') connection.expects(:get).returns(response) expect do terminus.find(request) end.to raise_error( Puppet::Error, /\/test_model\/foo.*\?environment=production&.*long_param=A+\.\.\..*resulted in 404 with the message/) end it 'does not truncate the URI when logging debug information' do Puppet.debug = true request = find_request('foo', :fail_on_404 => true, :long_param => ('A' * 100) + 'B') response = mock_response('404', 'this is the notfound you are looking for') connection.expects(:get).returns(response) expect do terminus.find(request) end.to raise_error( Puppet::Error, /\/test_model\/foo.*\?environment=production&.*long_param=A+B.*resulted in 404 with the message/) end end it "asks the model to deserialize the response body and sets the name on the resulting object to the find key" do connection.expects(:get).returns response model.expects(:convert_from).with(response['content-type'], response.body).returns( model.new('overwritten', 'decoded body') ) expect(terminus.find(request)).to eq(model.new('foo', 'decoded body')) end it "doesn't require the model to support name=" do connection.expects(:get).returns response instance = model.new('name', 'decoded body') model.expects(:convert_from).with(response['content-type'], response.body).returns(instance) instance.expects(:respond_to?).with(:name=).returns(false) instance.expects(:name=).never expect(terminus.find(request)).to eq(model.new('name', 'decoded body')) end it "provides an Accept header containing the list of supported mime types joined with commas" do connection.expects(:get).with(anything, has_entry("Accept" => "application/json, text/pson")).returns(response) terminus.model.expects(:supported_formats).returns [:json, :pson] terminus.find(request) end it "provides a version header with the current puppet version" do connection.expects(:get).with(anything, has_entry(Puppet::Network::HTTP::HEADER_PUPPET_VERSION => Puppet.version)).returns(response) terminus.find(request) end it "adds an Accept-Encoding header" do terminus.expects(:add_accept_encoding).returns({"accept-encoding" => "gzip"}) connection.expects(:get).with(anything, has_entry("accept-encoding" => "gzip")).returns(response) terminus.find(request) end it "uses only the mime-type from the content-type header when asking the model to deserialize" do response = mock_response('200', 'mydata', "text/plain; charset=utf-8") connection.expects(:get).returns(response) model.expects(:convert_from).with("text/plain", "mydata").returns "myobject" expect(terminus.find(request)).to eq("myobject") end it "decompresses the body before passing it to the model for deserialization" do uncompressed_body = "Why hello there" compressed_body = Zlib::Deflate.deflate(uncompressed_body) response = mock_response('200', compressed_body, 'text/plain', 'deflate') connection.expects(:get).returns(response) model.expects(:convert_from).with("text/plain", uncompressed_body).returns "myobject" expect(terminus.find(request)).to eq("myobject") end end describe "#head" do let(:http_method) { :head } let(:response) { mock_response(200, nil) } let(:connection) { stub('mock http connection', :head => response, :verify_callback= => nil) } let(:request) { head_request('foo') } before :each do terminus.stubs(:network).returns(connection) end it_behaves_like 'a REST terminus method', :head it "returns true if there was a successful http response" do connection.expects(:head).returns mock_response('200', nil) expect(terminus.head(request)).to eq(true) end it "returns false on a 404 response" do connection.expects(:head).returns mock_response('404', nil) expect(terminus.head(request)).to eq(false) end it "provides a version header with the current puppet version" do connection.expects(:head).with(anything, has_entry(Puppet::Network::HTTP::HEADER_PUPPET_VERSION => Puppet.version)).returns(response) terminus.head(request) end end describe "#search" do let(:http_method) { :get } let(:response) { mock_response(200, 'data1,data2,data3') } let(:connection) { stub('mock http connection', :get => response, :verify_callback= => nil) } let(:request) { search_request('foo') } before :each do terminus.stubs(:network).returns(connection) end it_behaves_like 'a REST terminus method', :search it_behaves_like 'a deserializing terminus method', :search it "should call the GET http method on a network connection" do connection.expects(:get).with("#{url_prefix}/test_models/foo?environment=production&", has_key('Accept')).returns mock_response(200, 'data3, data4') terminus.search(request) end it "returns an empty list on 404" do response = mock_response('404', nil) connection.expects(:get).returns(response) expect(terminus.search(request)).to eq([]) end it "asks the model to deserialize the response body into multiple instances" do expect(terminus.search(request)).to eq([model.new('', 'data1'), model.new('', 'data2'), model.new('', 'data3')]) end it "should provide an Accept header containing the list of supported formats joined with commas" do connection.expects(:get).with(anything, has_entry("Accept" => "application/json, text/pson")).returns(mock_response(200, '')) terminus.model.expects(:supported_formats).returns [:json, :pson] terminus.search(request) end it "provides a version header with the current puppet version" do connection.expects(:get).with(anything, has_entry(Puppet::Network::HTTP::HEADER_PUPPET_VERSION => Puppet.version)).returns(mock_response(200, '')) terminus.search(request) end it "should return an empty array if serialization returns nil" do model.stubs(:convert_from_multiple).returns nil expect(terminus.search(request)).to eq([]) end end describe "#destroy" do let(:http_method) { :delete } let(:response) { mock_response(200, 'body') } let(:connection) { stub('mock http connection', :delete => response, :verify_callback= => nil) } let(:request) { delete_request('foo') } before :each do terminus.stubs(:network).returns(connection) end it_behaves_like 'a REST terminus method', :destroy it_behaves_like 'a deserializing terminus method', :destroy it "should call the DELETE http method on a network connection" do connection.expects(:delete).with("#{url_prefix}/test_model/foo?environment=production&", has_key('Accept')).returns(response) terminus.destroy(request) end it "should fail if any options are provided, since DELETE apparently does not support query options" do request = delete_request('foo', :one => "two", :three => "four") expect { terminus.destroy(request) }.to raise_error(ArgumentError) end it "should deserialize and return the http response" do connection.expects(:delete).returns response expect(terminus.destroy(request)).to eq(model.new('', 'body')) end it "returns nil on 404" do response = mock_response('404', nil) connection.expects(:delete).returns(response) expect(terminus.destroy(request)).to eq(nil) end it "should provide an Accept header containing the list of supported formats joined with commas" do connection.expects(:delete).with(anything, has_entry("Accept" => "application/json, text/pson")).returns(response) terminus.model.expects(:supported_formats).returns [:json, :pson] terminus.destroy(request) end it "provides a version header with the current puppet version" do connection.expects(:delete).with(anything, has_entry(Puppet::Network::HTTP::HEADER_PUPPET_VERSION => Puppet.version)).returns(response) terminus.destroy(request) end end describe "#save" do let(:http_method) { :put } let(:response) { mock_response(200, 'body') } let(:connection) { stub('mock http connection', :put => response, :verify_callback= => nil) } let(:instance) { model.new('the thing', 'some contents') } let(:request) { save_request(instance.name, instance) } before :each do terminus.stubs(:network).returns(connection) end it_behaves_like 'a REST terminus method', :save it "should call the PUT http method on a network connection" do connection.expects(:put).with("#{url_prefix}/test_model/the%20thing?environment=production&", anything, has_key("Content-Type")).returns response terminus.save(request) end it "should fail if any options are provided, since PUT apparently does not support query options" do request = save_request(instance.name, instance, :one => "two", :three => "four") expect { terminus.save(request) }.to raise_error(ArgumentError) end it "should serialize the instance using the default format and pass the result as the body of the request" do instance.expects(:render).returns "serial_instance" connection.expects(:put).with(anything, "serial_instance", anything).returns response terminus.save(request) end it "returns nil on 404" do response = mock_response('404', nil) connection.expects(:put).returns(response) expect(terminus.save(request)).to eq(nil) end it "returns nil" do connection.expects(:put).returns response expect(terminus.save(request)).to be_nil end it "should provide an Accept header containing the list of supported formats joined with commas" do connection.expects(:put).with(anything, anything, has_entry("Accept" => "application/json, text/pson")).returns(response) instance.expects(:render).returns('') model.expects(:supported_formats).returns [:json, :pson] instance.expects(:mime).returns "supported" terminus.save(request) end it "provides a version header with the current puppet version" do connection.expects(:put).with(anything, anything, has_entry(Puppet::Network::HTTP::HEADER_PUPPET_VERSION => Puppet.version)).returns(response) terminus.save(request) end it "should provide a Content-Type header containing the mime-type of the sent object" do instance.expects(:mime).returns "mime" connection.expects(:put).with(anything, anything, has_entry('Content-Type' => "mime")).returns(response) terminus.save(request) end end describe '#handle_response' do # There are multiple request types to choose from, this may not be the one I want for this situation let(:response) { mock_response(200, 'body') } let(:connection) { stub('mock http connection', :put => response, :verify_callback= => nil) } let(:instance) { model.new('the thing', 'some contents') } let(:request) { save_request(instance.name, instance) } before :each do terminus.stubs(:network).returns(connection) end it 'adds server_agent_version to the context if not already set' do Puppet.expects(:push_context).with(:server_agent_version => Puppet.version) terminus.handle_response(request, response) end it 'does not add server_agent_version to the context if it is already set' do Puppet.override(:server_agent_version => "5.3.4") do Puppet.expects(:push_context).never terminus.handle_response(request, response) end end it 'downgrades to pson and emits a warning' do response.stubs(:[]).with(Puppet::Network::HTTP::HEADER_PUPPET_VERSION).returns('4.2.8') Puppet[:preferred_serialization_format] = 'other' Puppet.expects(:warning).with('Downgrading to PSON for future requests') terminus.handle_response(request, response) expect(Puppet[:preferred_serialization_format]).to eq('pson') end it 'preserves the set serialization format' do Puppet[:preferred_serialization_format] = 'other' expect(Puppet[:preferred_serialization_format]).to eq('other') terminus.handle_response(request, response) end end context 'dealing with SRV settings' do [ :destroy, :find, :head, :save, :search ].each do |method| it "##{method} passes the SRV service, and fall-back server & port to the request's do_request method" do request = Puppet::Indirector::Request.new(:indirection, method, 'key', nil) stub_response = mock_response('200', 'body') request.expects(:do_request).with(terminus.class.srv_service, terminus.class.server, terminus.class.port).returns(stub_response) terminus.send(method, request) end end end end puppet-5.5.10/spec/unit/indirector/status/0000755005276200011600000000000013417162176020415 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/indirector/status/local_spec.rb0000644005276200011600000000047413417161721023046 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/status/local' describe Puppet::Indirector::Status::Local do it "should set the puppet version" do Puppet::Status.indirection.terminus_class = :local expect(Puppet::Status.indirection.find('*').version).to eq(Puppet.version) end end puppet-5.5.10/spec/unit/indirector/status/rest_spec.rb0000644005276200011600000000044213417161721022724 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/status/rest' describe Puppet::Indirector::Status::Rest do it "should be a subclass of Puppet::Indirector::REST" do expect(Puppet::Indirector::Status::Rest.superclass).to equal(Puppet::Indirector::REST) end end puppet-5.5.10/spec/unit/indirector/store_configs_spec.rb0000644005276200011600000000061113417161721023266 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/store_configs' describe Puppet::Indirector::StoreConfigs do pending "REVISIT: creating an instance requires ludicrous amounts of stubbing, and there is relatively little to actually test here. What to do? Shared behaviours allow us to push down a lot of the testing into the implementation class tests anyhow..." end puppet-5.5.10/spec/unit/indirector/certificate_revocation_list/0000755005276200011600000000000013417162176024640 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/indirector/certificate_revocation_list/ca_spec.rb0000644005276200011600000000103513417161722026555 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/certificate_revocation_list/ca' describe Puppet::SSL::CertificateRevocationList::Ca do it "should have documentation" do expect(Puppet::SSL::CertificateRevocationList::Ca.doc).to be_instance_of(String) end it "should use the :cacrl setting as the crl location" do Puppet.settings.stubs(:use) Puppet[:cacrl] = File.expand_path("/request/dir") expect(Puppet::SSL::CertificateRevocationList::Ca.new.path("whatever")).to eq(Puppet[:cacrl]) end end puppet-5.5.10/spec/unit/indirector/certificate_revocation_list/disabled_ca_spec.rb0000644005276200011600000000171413417161722030410 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/certificate_revocation_list/disabled_ca' describe Puppet::SSL::CertificateRevocationList::DisabledCa do def request(type, remote) r = Puppet::Indirector::Request.new(:certificate_revocation_list, type, "foo.com", nil) if remote r.ip = '10.0.0.1' r.node = 'agent.example.com' end r end context "when not a CA" do before :each do Puppet[:ca] = false Puppet::SSL::Host.ca_location = :none end [:find, :head, :search, :save, :destroy].each do |name| it "should fail remote #{name} requests" do expect { subject.send(name, request(name, true)) }. to raise_error Puppet::Error, /is not a CA/ end it "should forward local #{name} requests" do Puppet::SSL::CertificateRevocationList.indirection.terminus(:file).expects(name) subject.send(name, request(name, false)) end end end end puppet-5.5.10/spec/unit/indirector/certificate_revocation_list/file_spec.rb0000644005276200011600000000104013417161722027105 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/certificate_revocation_list/file' describe Puppet::SSL::CertificateRevocationList::File do it "should have documentation" do expect(Puppet::SSL::CertificateRevocationList::File.doc).to be_instance_of(String) end it "should always store the file to :hostcrl location" do crl = File.expand_path("/host/crl") Puppet[:hostcrl] = crl Puppet.settings.stubs(:use) expect(Puppet::SSL::CertificateRevocationList::File.file_location).to eq(crl) end end puppet-5.5.10/spec/unit/indirector/certificate_revocation_list/rest_spec.rb0000644005276200011600000000224213417161722027150 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/certificate_revocation_list/rest' describe Puppet::SSL::CertificateRevocationList::Rest do before do @searcher = Puppet::SSL::CertificateRevocationList::Rest.new end it "should be a sublcass of Puppet::Indirector::REST" do expect(Puppet::SSL::CertificateRevocationList::Rest.superclass).to equal(Puppet::Indirector::REST) end it "should set server_setting to :ca_server" do expect(Puppet::SSL::CertificateRevocationList::Rest.server_setting).to eq(:ca_server) end it "should set port_setting to :ca_port" do expect(Puppet::SSL::CertificateRevocationList::Rest.port_setting).to eq(:ca_port) end it "should use the :ca SRV service" do expect(Puppet::SSL::CertificateRevocationList::Rest.srv_service).to eq(:ca) end it "temporarily disables revocation checking when finding a CRL and no CRL is available" do Puppet::FileSystem.expects(:exist?).with(Puppet[:hostcrl]).returns false Puppet.override({:certificate_revocation => :chain}) do Puppet.expects(:override).with({:certificate_revocation => false}, anything) subject.find(nil) end end end puppet-5.5.10/spec/unit/indirector/certificate_status/0000755005276200011600000000000013417162176022757 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/indirector/certificate_status/file_spec.rb0000644005276200011600000001517413417161722025241 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/ssl/host' require 'puppet/indirector/certificate_status' require 'tempfile' describe "Puppet::Indirector::CertificateStatus::File" do include PuppetSpec::Files before :all do Puppet::SSL::Host.configure_indirection(:file) end before do Puppet::SSL::CertificateAuthority.stubs(:ca?).returns true @terminus = Puppet::SSL::Host.indirection.terminus(:file) @tmpdir = tmpdir("certificate_status_ca_testing") Puppet[:confdir] = @tmpdir Puppet[:vardir] = @tmpdir # localcacert is where each client stores the CA certificate # cacert is where the master stores the CA certificate # Since we need to play the role of both for testing we need them to be the same and exist Puppet[:cacert] = Puppet[:localcacert] end def generate_csr(host) host.generate_key csr = Puppet::SSL::CertificateRequest.new(host.name) csr.generate(host.key.content) Puppet::SSL::CertificateRequest.indirection.save(csr) end def sign_csr(host) host.desired_state = "signed" @terminus.save(Puppet::Indirector::Request.new(:certificate_status, :save, host.name, host)) end def generate_signed_cert(host) generate_csr(host) sign_csr(host) @terminus.find(Puppet::Indirector::Request.new(:certificate_status, :find, host.name, host)) end def generate_revoked_cert(host) generate_signed_cert(host) host.desired_state = "revoked" @terminus.save(Puppet::Indirector::Request.new(:certificate_status, :save, host.name, host)) end it "should be a terminus on SSL::Host" do expect(@terminus).to be_instance_of(Puppet::Indirector::CertificateStatus::File) end it "should create a CA instance if none is present" do expect(@terminus.ca).to be_instance_of(Puppet::SSL::CertificateAuthority) end describe "when creating the CA" do it "should fail if it is not a valid CA" do Puppet::SSL::CertificateAuthority.expects(:ca?).returns false expect { @terminus.ca }.to raise_error(ArgumentError, "This process is not configured as a certificate authority") end end it "should be indirected with the name 'certificate_status'" do expect(Puppet::SSL::Host.indirection.name).to eq(:certificate_status) end describe "when finding" do before do @host = Puppet::SSL::Host.new("foo") Puppet.settings.use(:main) end it "should return the Puppet::SSL::Host when a CSR exists for the host" do generate_csr(@host) request = Puppet::Indirector::Request.new(:certificate_status, :find, "foo", @host) retrieved_host = @terminus.find(request) expect(retrieved_host.name).to eq(@host.name) expect(retrieved_host.certificate_request.content.to_s.chomp).to eq(@host.certificate_request.content.to_s.chomp) end it "should return the Puppet::SSL::Host when a public key exists for the host" do generate_signed_cert(@host) request = Puppet::Indirector::Request.new(:certificate_status, :find, "foo", @host) retrieved_host = @terminus.find(request) expect(retrieved_host.name).to eq(@host.name) expect(retrieved_host.certificate.content.to_s.chomp).to eq(@host.certificate.content.to_s.chomp) end it "should return nil when neither a CSR nor public key exist for the host" do request = Puppet::Indirector::Request.new(:certificate_status, :find, "foo", @host) expect(@terminus.find(request)).to eq(nil) end end describe "when saving" do before do @host = Puppet::SSL::Host.new("foobar") Puppet.settings.use(:main) end describe "when signing a cert" do before do @host.desired_state = "signed" @request = Puppet::Indirector::Request.new(:certificate_status, :save, "foobar", @host) end it "should fail if no CSR is on disk" do expect { @terminus.save(@request) }.to raise_error(Puppet::Error, /certificate request/) end it "should sign the on-disk CSR when it is present" do signed_host = generate_signed_cert(@host) expect(signed_host.state).to eq("signed") expect(Puppet::SSL::Certificate.indirection.find("foobar")).to be_instance_of(Puppet::SSL::Certificate) end end describe "when revoking a cert" do before do @request = Puppet::Indirector::Request.new(:certificate_status, :save, "foobar", @host) end it "should fail if no certificate is on disk" do @host.desired_state = "revoked" expect { @terminus.save(@request) }.to raise_error(Puppet::Error, /Cannot revoke/) end it "should revoke the certificate when it is present" do generate_revoked_cert(@host) expect(@host.state).to eq('revoked') end end end describe "when deleting" do before do Puppet.settings.use(:main) end it "should not delete anything if no certificate, request, or key is on disk" do host = Puppet::SSL::Host.new("clean_me") request = Puppet::Indirector::Request.new(:certificate_status, :delete, "clean_me", host) expect(@terminus.destroy(request)).to eq("Nothing was deleted") end it "should clean certs, cert requests, keys" do signed_host = Puppet::SSL::Host.new("clean_signed_cert") generate_signed_cert(signed_host) signed_request = Puppet::Indirector::Request.new(:certificate_status, :delete, "clean_signed_cert", signed_host) expect(@terminus.destroy(signed_request)).to eq("Deleted for clean_signed_cert: Puppet::SSL::Certificate, Puppet::SSL::Key") requested_host = Puppet::SSL::Host.new("clean_csr") generate_csr(requested_host) csr_request = Puppet::Indirector::Request.new(:certificate_status, :delete, "clean_csr", requested_host) expect(@terminus.destroy(csr_request)).to eq("Deleted for clean_csr: Puppet::SSL::CertificateRequest, Puppet::SSL::Key") end end describe "when searching" do it "should return a list of all hosts with certificate requests, signed certs, or revoked certs" do Puppet.settings.use(:main) signed_host = Puppet::SSL::Host.new("signed_host") generate_signed_cert(signed_host) requested_host = Puppet::SSL::Host.new("requested_host") generate_csr(requested_host) revoked_host = Puppet::SSL::Host.new("revoked_host") generate_revoked_cert(revoked_host) retrieved_hosts = @terminus.search(Puppet::Indirector::Request.new(:certificate_status, :search, "all", signed_host)) results = retrieved_hosts.map {|h| [h.name, h.state]}.sort{ |h,i| h[0] <=> i[0] } expect(results).to eq([["ca","signed"],["requested_host","requested"],["revoked_host","revoked"],["signed_host","signed"]]) end end end puppet-5.5.10/spec/unit/indirector/certificate_status/rest_spec.rb0000644005276200011600000000100613417161722025264 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/ssl/host' require 'puppet/indirector/certificate_status' describe "Puppet::CertificateStatus::Rest" do before do @terminus = Puppet::SSL::Host.indirection.terminus(:rest) end it "should be a terminus on Puppet::SSL::Host" do expect(@terminus).to be_instance_of(Puppet::Indirector::CertificateStatus::Rest) end it "should use the :ca SRV service" do expect(Puppet::Indirector::CertificateStatus::Rest.srv_service).to eq(:ca) end end puppet-5.5.10/spec/unit/indirector/code_spec.rb0000644005276200011600000000155413417161722021344 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/code' describe Puppet::Indirector::Code do before :all do Puppet::Indirector::Terminus.stubs(:register_terminus_class) @model = mock 'model' @indirection = stub 'indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) module Testing; end @code_class = class Testing::MyCode < Puppet::Indirector::Code self end @searcher = @code_class.new end it "should not have a find() method defined" do expect(@searcher).not_to respond_to(:find) end it "should not have a save() method defined" do expect(@searcher).not_to respond_to(:save) end it "should not have a destroy() method defined" do expect(@searcher).not_to respond_to(:destroy) end end puppet-5.5.10/spec/unit/indirector/direct_file_server_spec.rb0000644005276200011600000000617513417161722024275 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/direct_file_server' describe Puppet::Indirector::DirectFileServer do before :all do Puppet::Indirector::Terminus.stubs(:register_terminus_class) @model = mock 'model' @indirection = stub 'indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) module Testing; end @direct_file_class = class Testing::Mytype < Puppet::Indirector::DirectFileServer self end @server = @direct_file_class.new @path = File.expand_path('/my/local') @uri = Puppet::Util.path_to_uri(@path).to_s @request = Puppet::Indirector::Request.new(:mytype, :find, @uri, nil) end describe Puppet::Indirector::DirectFileServer, "when finding a single file" do it "should return nil if the file does not exist" do Puppet::FileSystem.expects(:exist?).with(@path).returns false expect(@server.find(@request)).to be_nil end it "should return a Content instance created with the full path to the file if the file exists" do Puppet::FileSystem.expects(:exist?).with(@path).returns true mycontent = stub 'content', :collect => nil mycontent.expects(:collect) @model.expects(:new).returns(mycontent) expect(@server.find(@request)).to eq(mycontent) end end describe Puppet::Indirector::DirectFileServer, "when creating the instance for a single found file" do before do @data = mock 'content' @data.stubs(:collect) Puppet::FileSystem.expects(:exist?).with(@path).returns true end it "should pass the full path to the instance" do @model.expects(:new).with { |key, options| key == @path }.returns(@data) @server.find(@request) end it "should pass the :links setting on to the created Content instance if the file exists and there is a value for :links" do @model.expects(:new).returns(@data) @data.expects(:links=).with(:manage) @request.stubs(:options).returns(:links => :manage) @server.find(@request) end it "should set 'checksum_type' on the instances if it is set in the request options" do @model.expects(:new).returns(@data) @data.expects(:checksum_type=).with :checksum @request.stubs(:options).returns(:checksum_type => :checksum) @server.find(@request) end end describe Puppet::Indirector::DirectFileServer, "when searching for multiple files" do it "should return nil if the file does not exist" do Puppet::FileSystem.expects(:exist?).with(@path).returns false expect(@server.find(@request)).to be_nil end it "should use :path2instances from the terminus_helper to return instances if the file exists" do Puppet::FileSystem.expects(:exist?).with(@path).returns true @server.expects(:path2instances) @server.search(@request) end it "should pass the original request to :path2instances" do Puppet::FileSystem.expects(:exist?).with(@path).returns true @server.expects(:path2instances).with(@request, @path) @server.search(@request) end end end puppet-5.5.10/spec/unit/indirector/exec_spec.rb0000644005276200011600000000357413417161722021362 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/exec' describe Puppet::Indirector::Exec do before :all do @indirection = stub 'indirection', :name => :testing Puppet::Indirector::Indirection.expects(:instance).with(:testing).returns(@indirection) module Testing; end @exec_class = class Testing::MyTesting < Puppet::Indirector::Exec attr_accessor :command self end end let(:path) { File.expand_path('/echo') } let(:arguments) { {:failonfail => true, :combine => false } } before :each do @searcher = @exec_class.new @searcher.command = [path] @request = stub 'request', :key => "foo" end it "should throw an exception if the command is not an array" do @searcher.command = path expect { @searcher.find(@request) }.to raise_error(Puppet::DevError) end it "should throw an exception if the command is not fully qualified" do @searcher.command = ["mycommand"] expect { @searcher.find(@request) }.to raise_error(ArgumentError) end it "should execute the command with the object name as the only argument" do @searcher.expects(:execute).with([path, 'foo'], arguments) @searcher.find(@request) end it "should return the output of the script" do @searcher.expects(:execute).with([path, 'foo'], arguments).returns("whatever") expect(@searcher.find(@request)).to eq("whatever") end it "should return nil when the command produces no output" do @searcher.expects(:execute).with([path, 'foo'], arguments).returns(nil) expect(@searcher.find(@request)).to be_nil end it "should raise an exception if there's an execution failure" do @searcher.expects(:execute).with([path, 'foo'], arguments).raises(Puppet::ExecutionFailure.new("message")) expect { @searcher.find(@request) }.to raise_exception(Puppet::Error, 'Failed to find foo via exec: message') end end puppet-5.5.10/spec/unit/indirector/file_server_spec.rb0000644005276200011600000002367413417161722022746 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/file_server' require 'puppet/file_serving/configuration' describe Puppet::Indirector::FileServer do before :all do Puppet::Indirector::Terminus.stubs(:register_terminus_class) @model = mock 'model' @indirection = stub 'indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) module Testing; end @file_server_class = class Testing::MyFileServer < Puppet::Indirector::FileServer self end end before :each do @file_server = @file_server_class.new @uri = "puppet://host/my/local/file" @configuration = mock 'configuration' Puppet::FileServing::Configuration.stubs(:configuration).returns(@configuration) @request = Puppet::Indirector::Request.new(:myind, :mymethod, @uri, :environment => "myenv") end describe "when finding files" do before do @mount = stub 'mount', :find => nil @instance = stub('instance', :links= => nil, :collect => nil) end it "should use the configuration to find the mount and relative path" do @configuration.expects(:split_path).with(@request) @file_server.find(@request) end it "should return nil if it cannot find the mount" do @configuration.expects(:split_path).with(@request).returns(nil, nil) expect(@file_server.find(@request)).to be_nil end it "should use the mount to find the full path" do @configuration.expects(:split_path).with(@request).returns([@mount, "rel/path"]) @mount.expects(:find).with { |key, request| key == "rel/path" } @file_server.find(@request) end it "should pass the request when finding a file" do @configuration.expects(:split_path).with(@request).returns([@mount, "rel/path"]) @mount.expects(:find).with { |key, request| request == @request } @file_server.find(@request) end it "should return nil if it cannot find a full path" do @configuration.expects(:split_path).with(@request).returns([@mount, "rel/path"]) @mount.expects(:find).with { |key, request| key == "rel/path" }.returns nil expect(@file_server.find(@request)).to be_nil end it "should create an instance with the found path" do @configuration.expects(:split_path).with(@request).returns([@mount, "rel/path"]) @mount.expects(:find).with { |key, request| key == "rel/path" }.returns "/my/file" @model.expects(:new).with("/my/file", {:relative_path => nil}).returns @instance expect(@file_server.find(@request)).to equal(@instance) end it "should set 'links' on the instance if it is set in the request options" do @request.options[:links] = true @configuration.expects(:split_path).with(@request).returns([@mount, "rel/path"]) @mount.expects(:find).with { |key, request| key == "rel/path" }.returns "/my/file" @model.expects(:new).with("/my/file", {:relative_path => nil}).returns @instance @instance.expects(:links=).with(true) expect(@file_server.find(@request)).to equal(@instance) end it "should collect the instance" do @request.options[:links] = true @configuration.expects(:split_path).with(@request).returns([@mount, "rel/path"]) @mount.expects(:find).with { |key, request| key == "rel/path" }.returns "/my/file" @model.expects(:new).with("/my/file", {:relative_path => nil}).returns @instance @instance.expects(:collect) expect(@file_server.find(@request)).to equal(@instance) end end describe "when searching for instances" do before do @mount = stub 'mount', :search => nil @instance = stub('instance', :links= => nil, :collect => nil) end it "should use the configuration to search the mount and relative path" do @configuration.expects(:split_path).with(@request) @file_server.search(@request) end it "should return nil if it cannot search the mount" do @configuration.expects(:split_path).with(@request).returns(nil, nil) expect(@file_server.search(@request)).to be_nil end it "should use the mount to search for the full paths" do @configuration.expects(:split_path).with(@request).returns([@mount, "rel/path"]) @mount.expects(:search).with { |key, request| key == "rel/path" } @file_server.search(@request) end it "should pass the request" do @configuration.stubs(:split_path).returns([@mount, "rel/path"]) @mount.expects(:search).with { |key, request| request == @request } @file_server.search(@request) end it "should return nil if searching does not find any full paths" do @configuration.expects(:split_path).with(@request).returns([@mount, "rel/path"]) @mount.expects(:search).with { |key, request| key == "rel/path" }.returns nil expect(@file_server.search(@request)).to be_nil end it "should create a fileset with each returned path and merge them" do @configuration.expects(:split_path).with(@request).returns([@mount, "rel/path"]) @mount.expects(:search).with { |key, request| key == "rel/path" }.returns %w{/one /two} Puppet::FileSystem.stubs(:exist?).returns true one = mock 'fileset_one' Puppet::FileServing::Fileset.expects(:new).with("/one", @request).returns(one) two = mock 'fileset_two' Puppet::FileServing::Fileset.expects(:new).with("/two", @request).returns(two) Puppet::FileServing::Fileset.expects(:merge).with(one, two).returns [] @file_server.search(@request) end it "should create an instance with each path resulting from the merger of the filesets" do @configuration.expects(:split_path).with(@request).returns([@mount, "rel/path"]) @mount.expects(:search).with { |key, request| key == "rel/path" }.returns [] Puppet::FileSystem.stubs(:exist?).returns true Puppet::FileServing::Fileset.expects(:merge).returns("one" => "/one", "two" => "/two") one = stub 'one', :collect => nil @model.expects(:new).with("/one", :relative_path => "one").returns one two = stub 'two', :collect => nil @model.expects(:new).with("/two", :relative_path => "two").returns two # order can't be guaranteed result = @file_server.search(@request) expect(result).to be_include(one) expect(result).to be_include(two) expect(result.length).to eq(2) end it "should set 'links' on the instances if it is set in the request options" do @configuration.expects(:split_path).with(@request).returns([@mount, "rel/path"]) @mount.expects(:search).with { |key, request| key == "rel/path" }.returns [] Puppet::FileSystem.stubs(:exist?).returns true Puppet::FileServing::Fileset.expects(:merge).returns("one" => "/one") one = stub 'one', :collect => nil @model.expects(:new).with("/one", :relative_path => "one").returns one one.expects(:links=).with true @request.options[:links] = true @file_server.search(@request) end it "should set 'checksum_type' on the instances if it is set in the request options" do @configuration.expects(:split_path).with(@request).returns([@mount, "rel/path"]) @mount.expects(:search).with { |key, request| key == "rel/path" }.returns [] Puppet::FileSystem.stubs(:exist?).returns true Puppet::FileServing::Fileset.expects(:merge).returns("one" => "/one") one = stub 'one', :collect => nil @model.expects(:new).with("/one", :relative_path => "one").returns one one.expects(:checksum_type=).with :checksum @request.options[:checksum_type] = :checksum @file_server.search(@request) end it "should collect the instances" do @configuration.expects(:split_path).with(@request).returns([@mount, "rel/path"]) @mount.expects(:search).with { |key, options| key == "rel/path" }.returns [] Puppet::FileSystem.stubs(:exist?).returns true Puppet::FileServing::Fileset.expects(:merge).returns("one" => "/one") one = mock 'one' @model.expects(:new).with("/one", :relative_path => "one").returns one one.expects(:collect) @file_server.search(@request) end end describe "when checking authorization" do before do @request.method = :find @mount = stub 'mount' @configuration.stubs(:split_path).with(@request).returns([@mount, "rel/path"]) @request.stubs(:node).returns("mynode") @request.stubs(:ip).returns("myip") @mount.stubs(:name).returns "myname" @mount.stubs(:allowed?).with("mynode", "myip").returns "something" end it "should return false when destroying" do @request.method = :destroy expect(@file_server).not_to be_authorized(@request) end it "should return false when saving" do @request.method = :save expect(@file_server).not_to be_authorized(@request) end it "should use the configuration to find the mount and relative path" do @configuration.expects(:split_path).with(@request) @file_server.authorized?(@request) end it "should return false if it cannot find the mount" do @configuration.expects(:split_path).with(@request).returns(nil, nil) expect(@file_server).not_to be_authorized(@request) end it "should return true when no auth directives are defined for the mount point" do @mount.stubs(:empty?).returns true @mount.stubs(:globalallow?).returns nil expect(@file_server).to be_authorized(@request) end it "should return true when a global allow directive is defined for the mount point" do @mount.stubs(:empty?).returns false @mount.stubs(:globalallow?).returns true expect(@file_server).to be_authorized(@request) end it "should return false when a non-global allow directive is defined for the mount point" do @mount.stubs(:empty?).returns false @mount.stubs(:globalallow?).returns false expect(@file_server).not_to be_authorized(@request) end end end puppet-5.5.10/spec/unit/indirector/indirection_spec.rb0000644005276200011600000007320313417161722022741 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/indirection' shared_examples_for "Indirection Delegator" do it "should create a request object with the appropriate method name and all of the passed arguments" do request = Puppet::Indirector::Request.new(:indirection, :find, "me", nil) @indirection.expects(:request).with(@method, "mystuff", nil, :one => :two).returns request @terminus.stubs(@method) @indirection.send(@method, "mystuff", :one => :two) end it "should choose the terminus returned by the :terminus_class" do @indirection.expects(:terminus_class).returns :test_terminus @terminus.expects(@method) @indirection.send(@method, "me") end it "should let the appropriate terminus perform the lookup" do @terminus.expects(@method).with { |r| r.is_a?(Puppet::Indirector::Request) } @indirection.send(@method, "me") end end shared_examples_for "Delegation Authorizer" do before do # So the :respond_to? turns out correctly. class << @terminus def authorized? end end end it "should not check authorization if a node name is not provided" do @terminus.expects(:authorized?).never @terminus.stubs(@method) # The quotes are necessary here, else it looks like a block. @request.stubs(:options).returns({}) @indirection.send(@method, "/my/key") end it "should pass the request to the terminus's authorization method" do @terminus.expects(:authorized?).with { |r| r.is_a?(Puppet::Indirector::Request) }.returns(true) @terminus.stubs(@method) @indirection.send(@method, "/my/key", :node => "mynode") end it "should fail if authorization returns false" do @terminus.expects(:authorized?).returns(false) @terminus.stubs(@method) expect { @indirection.send(@method, "/my/key", :node => "mynode") }.to raise_error(ArgumentError) end it "should continue if authorization returns true" do @terminus.expects(:authorized?).returns(true) @terminus.stubs(@method) @indirection.send(@method, "/my/key", :node => "mynode") end end shared_examples_for "Request validator" do it "asks the terminus to validate the request" do @terminus.expects(:validate).raises(Puppet::Indirector::ValidationError, "Invalid") @terminus.expects(@method).never expect { @indirection.send(@method, "key") }.to raise_error Puppet::Indirector::ValidationError end end describe Puppet::Indirector::Indirection do describe "when initializing" do # (LAK) I've no idea how to test this, really. it "should store a reference to itself before it consumes its options" do expect { @indirection = Puppet::Indirector::Indirection.new(Object.new, :testingness, :not_valid_option) }.to raise_error(NoMethodError, /undefined method/) expect(Puppet::Indirector::Indirection.instance(:testingness)).to be_instance_of(Puppet::Indirector::Indirection) Puppet::Indirector::Indirection.instance(:testingness).delete end it "should keep a reference to the indirecting model" do model = mock 'model' @indirection = Puppet::Indirector::Indirection.new(model, :myind) expect(@indirection.model).to equal(model) end it "should set the name" do @indirection = Puppet::Indirector::Indirection.new(mock('model'), :myind) expect(@indirection.name).to eq(:myind) end it "should require indirections to have unique names" do @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) expect { Puppet::Indirector::Indirection.new(:test) }.to raise_error(ArgumentError) end it "should extend itself with any specified module" do mod = Module.new @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test, :extend => mod) expect(@indirection.singleton_class.included_modules).to include(mod) end after do @indirection.delete if defined?(@indirection) end end describe "when an instance" do before :each do @terminus_class = mock 'terminus_class' @terminus = mock 'terminus' @terminus.stubs(:validate) @terminus_class.stubs(:new).returns(@terminus) @cache = stub 'cache', :name => "mycache" @cache_class = mock 'cache_class' Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :cache_terminus).returns(@cache_class) Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :test_terminus).returns(@terminus_class) @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) @indirection.terminus_class = :test_terminus @instance = stub 'instance', :expiration => nil, :expiration= => nil, :name => "whatever" @name = :mything #@request = stub 'instance', :key => "/my/key", :instance => @instance, :options => {} @request = mock 'instance' end it "should allow setting the ttl" do @indirection.ttl = 300 expect(@indirection.ttl).to eq(300) end it "should default to the :runinterval setting, converted to an integer, for its ttl" do Puppet[:runinterval] = 1800 expect(@indirection.ttl).to eq(1800) end it "should calculate the current expiration by adding the TTL to the current time" do @indirection.stubs(:ttl).returns(100) now = Time.now Time.stubs(:now).returns now expect(@indirection.expiration).to eq(Time.now + 100) end it "should have a method for creating an indirection request instance" do expect(@indirection).to respond_to(:request) end describe "creates a request" do it "should create it with its name as the request's indirection name" do Puppet::Indirector::Request.expects(:new).with { |name, *other| @indirection.name == name } @indirection.request(:funtest, "yayness") end it "should require a method and key" do Puppet::Indirector::Request.expects(:new).with { |name, method, key, *other| method == :funtest and key == "yayness" } @indirection.request(:funtest, "yayness") end it "should support optional arguments" do Puppet::Indirector::Request.expects(:new).with { |name, method, key, other| other == {:one => :two} } @indirection.request(:funtest, "yayness", :one => :two) end it "should not pass options if none are supplied" do Puppet::Indirector::Request.expects(:new).with { |*args| args.length < 4 } @indirection.request(:funtest, "yayness") end it "should return the request" do request = mock 'request' Puppet::Indirector::Request.expects(:new).returns request expect(@indirection.request(:funtest, "yayness")).to equal(request) end end describe "and looking for a model instance" do before { @method = :find } it_should_behave_like "Indirection Delegator" it_should_behave_like "Delegation Authorizer" it_should_behave_like "Request validator" it "should return the results of the delegation" do @terminus.expects(:find).returns(@instance) expect(@indirection.find("me")).to equal(@instance) end it "should return false if the instance is false" do @terminus.expects(:find).returns(false) expect(@indirection.find("me")).to equal(false) end it "should set the expiration date on any instances without one set" do @terminus.stubs(:find).returns(@instance) @indirection.expects(:expiration).returns :yay @instance.expects(:expiration).returns(nil) @instance.expects(:expiration=).with(:yay) @indirection.find("/my/key") end it "should not override an already-set expiration date on returned instances" do @terminus.stubs(:find).returns(@instance) @indirection.expects(:expiration).never @instance.expects(:expiration).returns(:yay) @instance.expects(:expiration=).never @indirection.find("/my/key") end it "should filter the result instance if the terminus supports it" do @terminus.stubs(:find).returns(@instance) @terminus.stubs(:respond_to?).with(:filter).returns(true) @terminus.expects(:filter).with(@instance) @indirection.find("/my/key") end describe "when caching is enabled" do before do @indirection.cache_class = :cache_terminus @cache_class.stubs(:new).returns(@cache) @instance.stubs(:expired?).returns false end it "should first look in the cache for an instance" do @terminus.stubs(:find).never @cache.expects(:find).returns @instance @indirection.find("/my/key") end it "should not look in the cache if the request specifies not to use the cache" do @terminus.expects(:find).returns @instance @cache.expects(:find).never @cache.stubs(:save) @indirection.find("/my/key", :ignore_cache => true) end it "should still save to the cache even if the cache is being ignored during readin" do @terminus.expects(:find).returns @instance @cache.expects(:save) @indirection.find("/my/key", :ignore_cache => true) end it "should not save to the cache if told to skip updating the cache" do @terminus.expects(:find).returns @instance @cache.expects(:find).returns nil @cache.expects(:save).never @indirection.find("/my/key", :ignore_cache_save => true) end it "should only look in the cache if the request specifies not to use the terminus" do @terminus.expects(:find).never @cache.expects(:find) @indirection.find("/my/key", :ignore_terminus => true) end it "should use a request to look in the cache for cached objects" do @cache.expects(:find).with { |r| r.method == :find and r.key == "/my/key" }.returns @instance @cache.stubs(:save) @indirection.find("/my/key") end it "should return the cached object if it is not expired" do @instance.stubs(:expired?).returns false @cache.stubs(:find).returns @instance expect(@indirection.find("/my/key")).to equal(@instance) end it "should not fail if the cache fails" do @terminus.stubs(:find).returns @instance @cache.expects(:find).raises ArgumentError @cache.stubs(:save) expect { @indirection.find("/my/key") }.not_to raise_error end it "should look in the main terminus if the cache fails" do @terminus.expects(:find).returns @instance @cache.expects(:find).raises ArgumentError @cache.stubs(:save) expect(@indirection.find("/my/key")).to equal(@instance) end it "should send a debug log if it is using the cached object" do Puppet.expects(:debug) @cache.stubs(:find).returns @instance @indirection.find("/my/key") end it "should not return the cached object if it is expired" do @instance.stubs(:expired?).returns true @cache.stubs(:find).returns @instance @terminus.stubs(:find).returns nil expect(@indirection.find("/my/key")).to be_nil end it "should send an info log if it is using the cached object" do Puppet.expects(:info) @instance.stubs(:expired?).returns true @cache.stubs(:find).returns @instance @terminus.stubs(:find).returns nil @indirection.find("/my/key") end it "should cache any objects not retrieved from the cache" do @cache.expects(:find).returns nil @terminus.expects(:find).returns(@instance) @cache.expects(:save) @indirection.find("/my/key") end it "should use a request to look in the cache for cached objects" do @cache.expects(:find).with { |r| r.method == :find and r.key == "/my/key" }.returns nil @terminus.stubs(:find).returns(@instance) @cache.stubs(:save) @indirection.find("/my/key") end it "should cache the instance using a request with the instance set to the cached object" do @cache.stubs(:find).returns nil @terminus.stubs(:find).returns(@instance) @cache.expects(:save).with { |r| r.method == :save and r.instance == @instance } @indirection.find("/my/key") end it "should send an info log that the object is being cached" do @cache.stubs(:find).returns nil @terminus.stubs(:find).returns(@instance) @cache.stubs(:save) Puppet.expects(:info) @indirection.find("/my/key") end it "should fail if saving to the cache fails but log the exception" do @cache.stubs(:find).returns nil @terminus.stubs(:find).returns(@instance) @cache.stubs(:save).raises RuntimeError Puppet.expects(:log_exception) expect { @indirection.find("/my/key") }.to raise_error RuntimeError end end end describe "and doing a head operation" do before { @method = :head } it_should_behave_like "Indirection Delegator" it_should_behave_like "Delegation Authorizer" it_should_behave_like "Request validator" it "should return true if the head method returned true" do @terminus.expects(:head).returns(true) expect(@indirection.head("me")).to eq(true) end it "should return false if the head method returned false" do @terminus.expects(:head).returns(false) expect(@indirection.head("me")).to eq(false) end describe "when caching is enabled" do before do @indirection.cache_class = :cache_terminus @cache_class.stubs(:new).returns(@cache) @instance.stubs(:expired?).returns false end it "should first look in the cache for an instance" do @terminus.stubs(:find).never @terminus.stubs(:head).never @cache.expects(:find).returns @instance expect(@indirection.head("/my/key")).to eq(true) end it "should not save to the cache" do @cache.expects(:find).returns nil @cache.expects(:save).never @terminus.expects(:head).returns true expect(@indirection.head("/my/key")).to eq(true) end it "should not fail if the cache fails" do @terminus.stubs(:head).returns true @cache.expects(:find).raises ArgumentError expect { @indirection.head("/my/key") }.not_to raise_error end it "should look in the main terminus if the cache fails" do @terminus.expects(:head).returns true @cache.expects(:find).raises ArgumentError expect(@indirection.head("/my/key")).to eq(true) end it "should send a debug log if it is using the cached object" do Puppet.expects(:debug) @cache.stubs(:find).returns @instance @indirection.head("/my/key") end it "should not accept the cached object if it is expired" do @instance.stubs(:expired?).returns true @cache.stubs(:find).returns @instance @terminus.stubs(:head).returns false expect(@indirection.head("/my/key")).to eq(false) end end end describe "and storing a model instance" do before { @method = :save } it "should return the result of the save" do @terminus.stubs(:save).returns "foo" expect(@indirection.save(@instance)).to eq("foo") end describe "when caching is enabled" do before do @indirection.cache_class = :cache_terminus @cache_class.stubs(:new).returns(@cache) @instance.stubs(:expired?).returns false end it "should return the result of saving to the terminus" do request = stub 'request', :instance => @instance, :node => nil, :ignore_cache_save? => false @indirection.expects(:request).returns request @cache.stubs(:save) @terminus.stubs(:save).returns @instance expect(@indirection.save(@instance)).to equal(@instance) end it "should use a request to save the object to the cache" do request = stub 'request', :instance => @instance, :node => nil, :ignore_cache_save? => false @indirection.expects(:request).returns request @cache.expects(:save).with(request) @terminus.stubs(:save) @indirection.save(@instance) end it "should not save to the cache if the normal save fails" do request = stub 'request', :instance => @instance, :node => nil @indirection.expects(:request).returns request @cache.expects(:save).never @terminus.expects(:save).raises "eh" expect { @indirection.save(@instance) }.to raise_error(RuntimeError, /eh/) end it "should not save to the cache if told to ignore saving to the cache" do @terminus.expects(:save) @cache.expects(:save).never @indirection.save(@instance, '/my/key', :ignore_cache_save => true) end end end describe "and removing a model instance" do before { @method = :destroy } it_should_behave_like "Indirection Delegator" it_should_behave_like "Delegation Authorizer" it_should_behave_like "Request validator" it "should return the result of removing the instance" do @terminus.stubs(:destroy).returns "yayness" expect(@indirection.destroy("/my/key")).to eq("yayness") end describe "when caching is enabled" do before do @indirection.cache_class = :cache_terminus @cache_class.expects(:new).returns(@cache) @instance.stubs(:expired?).returns false end it "should use a request instance to search in and remove objects from the cache" do destroy = stub 'destroy_request', :key => "/my/key", :node => nil find = stub 'destroy_request', :key => "/my/key", :node => nil @indirection.expects(:request).with(:destroy, "/my/key", nil, optionally(instance_of(Hash))).returns destroy @indirection.expects(:request).with(:find, "/my/key", nil, optionally(instance_of(Hash))).returns find cached = mock 'cache' @cache.expects(:find).with(find).returns cached @cache.expects(:destroy).with(destroy) @terminus.stubs(:destroy) @indirection.destroy("/my/key") end end end describe "and searching for multiple model instances" do before { @method = :search } it_should_behave_like "Indirection Delegator" it_should_behave_like "Delegation Authorizer" it_should_behave_like "Request validator" it "should set the expiration date on any instances without one set" do @terminus.stubs(:search).returns([@instance]) @indirection.expects(:expiration).returns :yay @instance.expects(:expiration).returns(nil) @instance.expects(:expiration=).with(:yay) @indirection.search("/my/key") end it "should not override an already-set expiration date on returned instances" do @terminus.stubs(:search).returns([@instance]) @indirection.expects(:expiration).never @instance.expects(:expiration).returns(:yay) @instance.expects(:expiration=).never @indirection.search("/my/key") end it "should return the results of searching in the terminus" do @terminus.expects(:search).returns([@instance]) expect(@indirection.search("/my/key")).to eq([@instance]) end end describe "and expiring a model instance" do describe "when caching is not enabled" do it "should do nothing" do @cache_class.expects(:new).never @indirection.expire("/my/key") end end describe "when caching is enabled" do before do @indirection.cache_class = :cache_terminus @cache_class.expects(:new).returns(@cache) @instance.stubs(:expired?).returns false @cached = stub 'cached', :expiration= => nil, :name => "/my/key" end it "should use a request to find within the cache" do @cache.expects(:find).with { |r| r.is_a?(Puppet::Indirector::Request) and r.method == :find } @indirection.expire("/my/key") end it "should do nothing if no such instance is cached" do @cache.expects(:find).returns nil @indirection.expire("/my/key") end it "should log when expiring a found instance" do @cache.expects(:find).returns @cached @cache.stubs(:save) Puppet.expects(:info) @indirection.expire("/my/key") end it "should set the cached instance's expiration to a time in the past" do @cache.expects(:find).returns @cached @cache.stubs(:save) @cached.expects(:expiration=).with { |t| t < Time.now } @indirection.expire("/my/key") end it "should save the now expired instance back into the cache" do @cache.expects(:find).returns @cached @cached.expects(:expiration=).with { |t| t < Time.now } @cache.expects(:save) @indirection.expire("/my/key") end it "does not expire an instance if told to skip cache saving" do @indirection.cache.expects(:find).never @indirection.cache.expects(:save).never @indirection.expire("/my/key", :ignore_cache_save => true) end it "should use a request to save the expired resource to the cache" do @cache.expects(:find).returns @cached @cached.expects(:expiration=).with { |t| t < Time.now } @cache.expects(:save).with { |r| r.is_a?(Puppet::Indirector::Request) and r.instance == @cached and r.method == :save }.returns(@cached) @indirection.expire("/my/key") end end end after :each do @indirection.delete end end describe "when managing indirection instances" do it "should allow an indirection to be retrieved by name" do @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) expect(Puppet::Indirector::Indirection.instance(:test)).to equal(@indirection) end it "should return nil when the named indirection has not been created" do expect(Puppet::Indirector::Indirection.instance(:test)).to be_nil end it "should allow an indirection's model to be retrieved by name" do mock_model = mock('model') @indirection = Puppet::Indirector::Indirection.new(mock_model, :test) expect(Puppet::Indirector::Indirection.model(:test)).to equal(mock_model) end it "should return nil when no model matches the requested name" do expect(Puppet::Indirector::Indirection.model(:test)).to be_nil end after do @indirection.delete if defined?(@indirection) end end describe "when routing to the correct the terminus class" do before do @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) @terminus = mock 'terminus' @terminus_class = stub 'terminus class', :new => @terminus Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :default).returns(@terminus_class) end it "should fail if no terminus class can be picked" do expect { @indirection.terminus_class }.to raise_error(Puppet::DevError) end it "should choose the default terminus class if one is specified" do @indirection.terminus_class = :default expect(@indirection.terminus_class).to equal(:default) end it "should use the provided Puppet setting if told to do so" do Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :my_terminus).returns(mock("terminus_class2")) Puppet[:node_terminus] = :my_terminus @indirection.terminus_setting = :node_terminus expect(@indirection.terminus_class).to equal(:my_terminus) end it "should fail if the provided terminus class is not valid" do Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :nosuchclass).returns(nil) expect { @indirection.terminus_class = :nosuchclass }.to raise_error(ArgumentError) end after do @indirection.delete if defined?(@indirection) end end describe "when specifying the terminus class to use" do before do @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) @terminus = mock 'terminus' @terminus.stubs(:validate) @terminus_class = stub 'terminus class', :new => @terminus end it "should allow specification of a terminus type" do expect(@indirection).to respond_to(:terminus_class=) end it "should fail to redirect if no terminus type has been specified" do expect { @indirection.find("blah") }.to raise_error(Puppet::DevError) end it "should fail when the terminus class name is an empty string" do expect { @indirection.terminus_class = "" }.to raise_error(ArgumentError) end it "should fail when the terminus class name is nil" do expect { @indirection.terminus_class = nil }.to raise_error(ArgumentError) end it "should fail when the specified terminus class cannot be found" do Puppet::Indirector::Terminus.expects(:terminus_class).with(:test, :foo).returns(nil) expect { @indirection.terminus_class = :foo }.to raise_error(ArgumentError) end it "should select the specified terminus class if a terminus class name is provided" do Puppet::Indirector::Terminus.expects(:terminus_class).with(:test, :foo).returns(@terminus_class) expect(@indirection.terminus(:foo)).to equal(@terminus) end it "should use the configured terminus class if no terminus name is specified" do Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :foo).returns(@terminus_class) @indirection.terminus_class = :foo expect(@indirection.terminus).to equal(@terminus) end after do @indirection.delete if defined?(@indirection) end end describe "when managing terminus instances" do before do @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) @terminus = mock 'terminus' @terminus_class = mock 'terminus class' Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :foo).returns(@terminus_class) end it "should create an instance of the chosen terminus class" do @terminus_class.stubs(:new).returns(@terminus) expect(@indirection.terminus(:foo)).to equal(@terminus) end # Make sure it caches the terminus. it "should return the same terminus instance each time for a given name" do @terminus_class.stubs(:new).returns(@terminus) expect(@indirection.terminus(:foo)).to equal(@terminus) expect(@indirection.terminus(:foo)).to equal(@terminus) end it "should not create a terminus instance until one is actually needed" do Puppet::Indirector.expects(:terminus).never Puppet::Indirector::Indirection.new(mock('model'), :lazytest) end after do @indirection.delete end end describe "when deciding whether to cache" do before do @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) @terminus = mock 'terminus' @terminus_class = mock 'terminus class' @terminus_class.stubs(:new).returns(@terminus) Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :foo).returns(@terminus_class) @indirection.terminus_class = :foo end it "should provide a method for setting the cache terminus class" do expect(@indirection).to respond_to(:cache_class=) end it "should fail to cache if no cache type has been specified" do expect { @indirection.cache }.to raise_error(Puppet::DevError) end it "should fail to set the cache class when the cache class name is an empty string" do expect { @indirection.cache_class = "" }.to raise_error(ArgumentError) end it "should allow resetting the cache_class to nil" do @indirection.cache_class = nil expect(@indirection.cache_class).to be_nil end it "should fail to set the cache class when the specified cache class cannot be found" do Puppet::Indirector::Terminus.expects(:terminus_class).with(:test, :foo).returns(nil) expect { @indirection.cache_class = :foo }.to raise_error(ArgumentError) end after do @indirection.delete end end describe "when using a cache" do before :each do @terminus_class = mock 'terminus_class' @terminus = mock 'terminus' @terminus_class.stubs(:new).returns(@terminus) @cache = mock 'cache' @cache_class = mock 'cache_class' Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :cache_terminus).returns(@cache_class) Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :test_terminus).returns(@terminus_class) @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) @indirection.terminus_class = :test_terminus end describe "and managing the cache terminus" do it "should not create a cache terminus at initialization" do # This is weird, because all of the code is in the setup. If we got # new called on the cache class, we'd get an exception here. end it "should reuse the cache terminus" do @cache_class.expects(:new).returns(@cache) @indirection.cache_class = :cache_terminus expect(@indirection.cache).to equal(@cache) expect(@indirection.cache).to equal(@cache) end end describe "and saving" do end describe "and finding" do end after :each do @indirection.delete end end end puppet-5.5.10/spec/unit/indirector/ldap_spec.rb0000644005276200011600000001176313417161722021355 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/ldap' describe Puppet::Indirector::Ldap do before do @indirection = stub 'indirection', :name => :testing Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) module Testing; end @ldap_class = class Testing::MyLdap < Puppet::Indirector::Ldap self end @connection = mock 'ldap' end describe "when instantiating ldap" do it "should be deprecated" do Puppet.expects(:deprecation_warning).with("Puppet::Indirector::Ldap is deprecated and will be removed in a future release of Puppet.") @ldap_class.new end it "should not emit a deprecation warning when they are disabled" do Puppet.expects(:warning).with(regexp_matches(/Puppet::Indirector::Ldap is deprecated/)).never Puppet[:disable_warnings] = ['deprecations'] @ldap_class.new end it "should only emit the deprecation warning once" do Puppet.expects(:warning).with(regexp_matches(/Puppet::Indirector::Ldap is deprecated/)).once @ldap_class.new @ldap_class.new end end describe "when searching ldap" do before do @searcher = @ldap_class.new # Stub everything, and we can selectively replace with an expect as # we need to for testing. @searcher.stubs(:connection).returns(@connection) @searcher.stubs(:search_filter).returns(:filter) @searcher.stubs(:search_base).returns(:base) @searcher.stubs(:process) @request = stub 'request', :key => "yay" end it "should call the ldapsearch method with the search filter" do @searcher.expects(:search_filter).with("yay").returns("yay's filter") @searcher.expects(:ldapsearch).with("yay's filter") @searcher.find @request end it "should fail if no block is passed to the ldapsearch method" do expect { @searcher.ldapsearch("blah") }.to raise_error(ArgumentError) end it "should use the results of the ldapbase method as the ldap search base" do @searcher.stubs(:search_base).returns("mybase") @connection.expects(:search).with do |*args| expect(args[0]).to eq("mybase") true end @searcher.find @request end it "should default to the value of the :search_base setting as the result of the ldapbase method" do Puppet[:ldapbase] = "myldapbase" searcher = @ldap_class.new expect(searcher.search_base).to eq("myldapbase") end it "should use the results of the :search_attributes method as the list of attributes to return" do @searcher.stubs(:search_attributes).returns(:myattrs) @connection.expects(:search).with do |*args| expect(args[3]).to eq(:myattrs) true end @searcher.find @request end it "should use depth 2 when searching" do @connection.expects(:search).with do |*args| expect(args[1]).to eq(2) true end @searcher.find @request end it "should call process() on the first found entry" do @connection.expects(:search).yields("myresult") @searcher.expects(:process).with("myresult") @searcher.find @request end it "should reconnect and retry the search if there is a failure" do run = false @connection.stubs(:search).with do |*args| if run true else run = true raise "failed" end end.yields("myresult") @searcher.expects(:process).with("myresult") @searcher.find @request end it "should not reconnect on failure more than once" do count = 0 @connection.stubs(:search).with do |*args| count += 1 raise ArgumentError, "yay" end expect { @searcher.find(@request) }.to raise_error(Puppet::Error) expect(count).to eq(2) end it "should return true if an entry is found" do @connection.expects(:search).yields("result") expect(@searcher.ldapsearch("whatever") { |r| }).to be_truthy end end describe "when connecting to ldap", :if => Puppet.features.ldap? do it "should create and start a Util::Ldap::Connection instance" do conn = double 'connection', :connection => "myconn", :start => nil Puppet::Util::Ldap::Connection.expects(:instance).returns conn expect(@searcher.connection).to eq("myconn") end it "should only create the ldap connection when asked for it the first time" do conn = double 'connection', :connection => "myconn", :start => nil Puppet::Util::Ldap::Connection.expects(:instance).returns conn @searcher.connection end it "should cache the connection" do conn = double 'connection', :connection => "myconn", :start => nil Puppet::Util::Ldap::Connection.expects(:instance).returns conn expect(@searcher.connection).to equal(@searcher.connection) end end describe "when reconnecting to ldap", :if => (Puppet.features.root? and Facter.value("hostname") == "culain") do it "should reconnect to ldap when connections are lost" end end puppet-5.5.10/spec/unit/indirector/request_spec.rb0000644005276200011600000004762413417161722022132 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'matchers/json' require 'puppet/indirector/request' describe Puppet::Indirector::Request do include JSONMatchers describe "when initializing" do it "should always convert the indirection name to a symbol" do expect(Puppet::Indirector::Request.new("ind", :method, "mykey", nil).indirection_name).to eq(:ind) end it "should use provided value as the key if it is a string" do expect(Puppet::Indirector::Request.new(:ind, :method, "mykey", nil).key).to eq("mykey") end it "should use provided value as the key if it is a symbol" do expect(Puppet::Indirector::Request.new(:ind, :method, :mykey, nil).key).to eq(:mykey) end it "should use the name of the provided instance as its key if an instance is provided as the key instead of a string" do instance = mock 'instance', :name => "mykey" request = Puppet::Indirector::Request.new(:ind, :method, nil, instance) expect(request.key).to eq("mykey") expect(request.instance).to equal(instance) end it "should support options specified as a hash" do expect { Puppet::Indirector::Request.new(:ind, :method, :key, nil, :one => :two) }.to_not raise_error end it "should support nil options" do expect { Puppet::Indirector::Request.new(:ind, :method, :key, nil, nil) }.to_not raise_error end it "should support unspecified options" do expect { Puppet::Indirector::Request.new(:ind, :method, :key, nil) }.to_not raise_error end it "should use an empty options hash if nil was provided" do expect(Puppet::Indirector::Request.new(:ind, :method, :key, nil, nil).options).to eq({}) end it "should default to a nil node" do expect(Puppet::Indirector::Request.new(:ind, :method, :key, nil).node).to be_nil end it "should set its node attribute if provided in the options" do expect(Puppet::Indirector::Request.new(:ind, :method, :key, nil, :node => "foo.com").node).to eq("foo.com") end it "should default to a nil ip" do expect(Puppet::Indirector::Request.new(:ind, :method, :key, nil).ip).to be_nil end it "should set its ip attribute if provided in the options" do expect(Puppet::Indirector::Request.new(:ind, :method, :key, nil, :ip => "192.168.0.1").ip).to eq("192.168.0.1") end it "should default to being unauthenticated" do expect(Puppet::Indirector::Request.new(:ind, :method, :key, nil)).not_to be_authenticated end it "should set be marked authenticated if configured in the options" do expect(Puppet::Indirector::Request.new(:ind, :method, :key, nil, :authenticated => "eh")).to be_authenticated end it "should keep its options as a hash even if a node is specified" do expect(Puppet::Indirector::Request.new(:ind, :method, :key, nil, :node => "eh").options).to be_instance_of(Hash) end it "should keep its options as a hash even if another option is specified" do expect(Puppet::Indirector::Request.new(:ind, :method, :key, nil, :foo => "bar").options).to be_instance_of(Hash) end it "should treat options other than :ip, :node, and :authenticated as options rather than attributes" do expect(Puppet::Indirector::Request.new(:ind, :method, :key, nil, :server => "bar").options[:server]).to eq("bar") end it "should normalize options to use symbols as keys" do expect(Puppet::Indirector::Request.new(:ind, :method, :key, nil, "foo" => "bar").options[:foo]).to eq("bar") end describe "and the request key is a URI" do let(:file) { File.expand_path("/my/file with spaces") } let(:an_environment) { Puppet::Node::Environment.create(:an_environment, []) } let(:env_loaders) { Puppet::Environments::Static.new(an_environment) } around(:each) do |example| Puppet.override({ :environments => env_loaders }, "Static environment loader for specs") do example.run end end describe "and the URI is a 'file' URI" do before do @request = Puppet::Indirector::Request.new(:ind, :method, "#{URI.unescape(Puppet::Util.path_to_uri(file).to_s)}", nil) end it "should set the request key to the unescaped full file path" do expect(@request.key).to eq(file) end it "should not set the protocol" do expect(@request.protocol).to be_nil end it "should not set the port" do expect(@request.port).to be_nil end it "should not set the server" do expect(@request.server).to be_nil end end it "should set the protocol to the URI scheme" do expect(Puppet::Indirector::Request.new(:ind, :method, "http://host/", nil).protocol).to eq("http") end it "should set the server if a server is provided" do expect(Puppet::Indirector::Request.new(:ind, :method, "http://host/", nil).server).to eq("host") end it "should set the server and port if both are provided" do expect(Puppet::Indirector::Request.new(:ind, :method, "http://host:543/", nil).port).to eq(543) end it "should default to the masterport if the URI scheme is 'puppet'" do Puppet[:masterport] = "321" expect(Puppet::Indirector::Request.new(:ind, :method, "puppet://host/", nil).port).to eq(321) end it "should use the provided port if the URI scheme is not 'puppet'" do expect(Puppet::Indirector::Request.new(:ind, :method, "http://host/", nil).port).to eq(80) end it "should set the request key to the unescaped path from the URI" do expect(Puppet::Indirector::Request.new(:ind, :method, "http://host/stuff with spaces", nil).key).to eq("stuff with spaces") end it "should set the request key to the unescaped path from the URI, in UTF-8 encoding" do path = "\u4e07" uri = "http://host/#{path}" request = Puppet::Indirector::Request.new(:ind, :method, uri, nil) expect(request.key).to eq(path) expect(request.key.encoding).to eq(Encoding::UTF_8) end it "should set the request key properly given a UTF-8 URI" do # different UTF-8 widths # 1-byte A # 2-byte Űż - http://www.fileformat.info/info/unicode/char/06ff/index.htm - 0xDB 0xBF / 219 191 # 3-byte áš  - http://www.fileformat.info/info/unicode/char/16A0/index.htm - 0xE1 0x9A 0xA0 / 225 154 160 # 4-byte - http://www.fileformat.info/info/unicode/char/2070E/index.htm - 0xF0 0xA0 0x9C 0x8E / 240 160 156 142 mixed_utf8 = "A\u06FF\u16A0\u{2070E}" # AŰżáš  key = "a/path/stu ff/#{mixed_utf8}" req = Puppet::Indirector::Request.new(:ind, :method, "http:///#{key}", nil) expect(req.key).to eq(key) expect(req.key.encoding).to eq(Encoding::UTF_8) expect(req.uri).to eq("http:///#{key}") end it "should set the :uri attribute to the full URI" do expect(Puppet::Indirector::Request.new(:ind, :method, "http:///a/path/stu ff", nil).uri).to eq('http:///a/path/stu ff') end it "should not parse relative URI" do expect(Puppet::Indirector::Request.new(:ind, :method, "foo/bar", nil).uri).to be_nil end it "should not parse opaque URI" do expect(Puppet::Indirector::Request.new(:ind, :method, "mailto:joe", nil).uri).to be_nil end end it "should allow indication that it should not read a cached instance" do expect(Puppet::Indirector::Request.new(:ind, :method, :key, nil, :ignore_cache => true)).to be_ignore_cache end it "should default to not ignoring the cache" do expect(Puppet::Indirector::Request.new(:ind, :method, :key, nil)).not_to be_ignore_cache end it "should allow indication that it should not not read an instance from the terminus" do expect(Puppet::Indirector::Request.new(:ind, :method, :key, nil, :ignore_terminus => true)).to be_ignore_terminus end it "should default to not ignoring the terminus" do expect(Puppet::Indirector::Request.new(:ind, :method, :key, nil)).not_to be_ignore_terminus end end it "should look use the Indirection class to return the appropriate indirection" do ind = mock 'indirection' Puppet::Indirector::Indirection.expects(:instance).with(:myind).returns ind request = Puppet::Indirector::Request.new(:myind, :method, :key, nil) expect(request.indirection).to equal(ind) end it "should use its indirection to look up the appropriate model" do ind = mock 'indirection' Puppet::Indirector::Indirection.expects(:instance).with(:myind).returns ind request = Puppet::Indirector::Request.new(:myind, :method, :key, nil) ind.expects(:model).returns "mymodel" expect(request.model).to eq("mymodel") end it "should fail intelligently when asked to find a model but the indirection cannot be found" do Puppet::Indirector::Indirection.expects(:instance).with(:myind).returns nil request = Puppet::Indirector::Request.new(:myind, :method, :key, nil) expect { request.model }.to raise_error(ArgumentError) end it "should have a method for determining if the request is plural or singular" do expect(Puppet::Indirector::Request.new(:myind, :method, :key, nil)).to respond_to(:plural?) end it "should be considered plural if the method is 'search'" do expect(Puppet::Indirector::Request.new(:myind, :search, :key, nil)).to be_plural end it "should not be considered plural if the method is not 'search'" do expect(Puppet::Indirector::Request.new(:myind, :find, :key, nil)).not_to be_plural end it "should use its uri, if it has one, as its description" do Puppet.override({ :environments => Puppet::Environments::Static.new( Puppet::Node::Environment.create(:baz, []) )}, "Static loader for spec") do expect(Puppet::Indirector::Request.new(:myind, :find, "foo://bar/baz", nil).description).to eq("foo://bar/baz") end end it "should use its indirection name and key, if it has no uri, as its description" do expect(Puppet::Indirector::Request.new(:myind, :find, "key", nil).description).to eq("/myind/key") end it "should set its environment to an environment instance when a string is specified as its environment" do env = Puppet::Node::Environment.create(:foo, []) Puppet.override(:environments => Puppet::Environments::Static.new(env)) do expect(Puppet::Indirector::Request.new(:myind, :find, "my key", nil, :environment => "foo").environment).to eq(env) end end it "should use any passed in environment instances as its environment" do env = Puppet::Node::Environment.create(:foo, []) expect(Puppet::Indirector::Request.new(:myind, :find, "my key", nil, :environment => env).environment).to equal(env) end it "should use the current environment when none is provided" do Puppet[:environment] = "foo" expect(Puppet::Indirector::Request.new(:myind, :find, "my key", nil).environment).to eq(Puppet.lookup(:current_environment)) end it "should support converting its options to a hash" do expect(Puppet::Indirector::Request.new(:myind, :find, "my key", nil )).to respond_to(:to_hash) end it "should include all of its attributes when its options are converted to a hash" do expect(Puppet::Indirector::Request.new(:myind, :find, "my key", nil, :node => 'foo').to_hash[:node]).to eq('foo') end describe "when building a query string from its options" do def a_request_with_options(options) Puppet::Indirector::Request.new(:myind, :find, "my key", nil, options) end def the_parsed_query_string_from(request) CGI.parse(request.query_string.sub(/^\?/, '')) end it "should return an empty query string if there are no options" do request = a_request_with_options(nil) expect(request.query_string).to eq("") end it "should return an empty query string if the options are empty" do request = a_request_with_options({}) expect(request.query_string).to eq("") end it "should include all options in the query string, separated by '&'" do request = a_request_with_options(:one => "two", :three => "four") expect(the_parsed_query_string_from(request)).to eq({ "one" => ["two"], "three" => ["four"] }) end it "should ignore nil options" do request = a_request_with_options(:one => "two", :three => nil) expect(the_parsed_query_string_from(request)).to eq({ "one" => ["two"] }) end it "should convert 'true' option values into strings" do request = a_request_with_options(:one => true) expect(the_parsed_query_string_from(request)).to eq({ "one" => ["true"] }) end it "should convert 'false' option values into strings" do request = a_request_with_options(:one => false) expect(the_parsed_query_string_from(request)).to eq({ "one" => ["false"] }) end it "should convert to a string all option values that are integers" do request = a_request_with_options(:one => 50) expect(the_parsed_query_string_from(request)).to eq({ "one" => ["50"] }) end it "should convert to a string all option values that are floating point numbers" do request = a_request_with_options(:one => 1.2) expect(the_parsed_query_string_from(request)).to eq({ "one" => ["1.2"] }) end it "should CGI-escape all option values that are strings" do request = a_request_with_options(:one => "one two") expect(the_parsed_query_string_from(request)).to eq({ "one" => ["one two"] }) end it "should convert an array of values into multiple entries for the same key" do request = a_request_with_options(:one => %w{one two}) expect(the_parsed_query_string_from(request)).to eq({ "one" => ["one", "two"] }) end it "should stringify simple data types inside an array" do request = a_request_with_options(:one => ['one', nil]) expect(the_parsed_query_string_from(request)).to eq({ "one" => ["one"] }) end it "should error if an array contains another array" do request = a_request_with_options(:one => ['one', ["not allowed"]]) expect { request.query_string }.to raise_error(ArgumentError) end it "should error if an array contains illegal data" do request = a_request_with_options(:one => ['one', { :not => "allowed" }]) expect { request.query_string }.to raise_error(ArgumentError) end it "should convert to a string and CGI-escape all option values that are symbols" do request = a_request_with_options(:one => :"sym bol") expect(the_parsed_query_string_from(request)).to eq({ "one" => ["sym bol"] }) end it "should fail if options other than booleans or strings are provided" do request = a_request_with_options(:one => { :one => :two }) expect { request.query_string }.to raise_error(ArgumentError) end end context '#do_request' do before :each do @request = Puppet::Indirector::Request.new(:myind, :find, "my key", nil) end context 'when not using SRV records' do before :each do Puppet.settings[:use_srv_records] = false end it "yields the request with the default server and port when no server or port were specified on the original request" do count = 0 rval = @request.do_request(:puppet, 'puppet.example.com', '90210') do |got| count += 1 expect(got.server).to eq('puppet.example.com') expect(got.port).to eq('90210') 'Block return value' end expect(count).to eq(1) expect(rval).to eq('Block return value') end end context 'when using SRV records' do before :each do Puppet.settings[:use_srv_records] = true Puppet.settings[:srv_domain] = 'example.com' end it "yields the request with the original server and port unmodified" do @request.server = 'puppet.example.com' @request.port = '90210' count = 0 rval = @request.do_request do |got| count += 1 expect(got.server).to eq('puppet.example.com') expect(got.port).to eq('90210') 'Block return value' end expect(count).to eq(1) expect(rval).to eq('Block return value') end context "when SRV returns servers" do before :each do @dns_mock = mock('dns') Resolv::DNS.expects(:new).returns(@dns_mock) @port = 7205 @host = '_x-puppet._tcp.example.com' @srv_records = [Resolv::DNS::Resource::IN::SRV.new(0, 0, @port, @host)] @dns_mock.expects(:getresources). with("_x-puppet._tcp.#{Puppet.settings[:srv_domain]}", Resolv::DNS::Resource::IN::SRV). returns(@srv_records) end it "yields a request using the server and port from the SRV record" do count = 0 rval = @request.do_request do |got| count += 1 expect(got.server).to eq('_x-puppet._tcp.example.com') expect(got.port).to eq(7205) @block_return end expect(count).to eq(1) expect(rval).to eq(@block_return) end it "should fall back to the default server when the block raises a SystemCallError" do count = 0 second_pass = nil rval = @request.do_request(:puppet, 'puppet', 8140) do |got| count += 1 if got.server == '_x-puppet._tcp.example.com' then raise SystemCallError, "example failure" else second_pass = got end @block_return end expect(second_pass.server).to eq('puppet') expect(second_pass.port).to eq(8140) expect(count).to eq(2) expect(rval).to eq(@block_return) end end end end describe "#remote?" do def request(options = {}) Puppet::Indirector::Request.new('node', 'find', 'localhost', nil, options) end it "should not be unless node or ip is set" do expect(request).not_to be_remote end it "should be remote if node is set" do expect(request(:node => 'example.com')).to be_remote end it "should be remote if ip is set" do expect(request(:ip => '127.0.0.1')).to be_remote end it "should be remote if node and ip are set" do expect(request(:node => 'example.com', :ip => '127.0.0.1')).to be_remote end end describe "failover" do it "should use the provided failover host and port" do Puppet.override(:server => 'myhost', :serverport => 666) do req = Puppet::Indirector::Request.new('node', 'find', 'localhost', nil) req.do_request() do |request| expect(request.server).to eq('myhost') expect(request.port).to eq(666) end end end it "should not use raw settings when failover fails" do Puppet.override(:server => nil, :serverport => nil) do req = Puppet::Indirector::Request.new('node', 'find', 'localhost', nil) req.do_request() do |request| expect(request.server).to be_nil expect(request.port).to be_nil expect(Puppet.settings[:server]).not_to be_nil expect(Puppet.settings[:masterport]).not_to be_nil end end end it "should use server_list when set and failover has not occured" do Puppet.settings[:server_list] = [['myhost',666]] req = Puppet::Indirector::Request.new('node', 'find', 'localhost', nil) req.do_request() do |request| expect(request.server).to eq('myhost') expect(request.port).to eq(666) end end it "should use server when server_list is not set" do req = Puppet::Indirector::Request.new('node', 'find', 'localhost', nil) req.do_request() do |request| expect(request.server).to eq(Puppet.settings[:server]) expect(request.port).to eq(Puppet.settings[:masterport]) end end end end puppet-5.5.10/spec/unit/indirector/ssl_file_spec.rb0000644005276200011600000002560313417161722022233 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/ssl_file' describe Puppet::Indirector::SslFile do include PuppetSpec::Files before :all do @indirection = stub 'indirection', :name => :testing, :model => @model Puppet::Indirector::Indirection.expects(:instance).with(:testing).returns(@indirection) module Testing; end @file_class = class Testing::MyType < Puppet::Indirector::SslFile self end end before :each do @model = mock 'model' @setting = :certdir @file_class.store_in @setting @file_class.store_at nil @file_class.store_ca_at nil @path = make_absolute("/thisdoesntexist/my_directory") Puppet[:noop] = false Puppet[@setting] = @path Puppet[:trace] = false end after :each do @file_class.store_in nil @file_class.store_at nil @file_class.store_ca_at nil end it "should use :main and :ssl upon initialization" do Puppet.settings.expects(:use).with(:main, :ssl) @file_class.new end it "should return a nil collection directory if no directory setting has been provided" do @file_class.store_in nil expect(@file_class.collection_directory).to be_nil end it "should return a nil file location if no location has been provided" do @file_class.store_at nil expect(@file_class.file_location).to be_nil end it "should fail if no store directory or file location has been set" do Puppet.settings.expects(:use).with(:main, :ssl) @file_class.store_in nil @file_class.store_at nil expect { @file_class.new }.to raise_error(Puppet::DevError, /No file or directory setting provided/) end describe "when managing ssl files" do before do Puppet.settings.stubs(:use) @searcher = @file_class.new @cert = stub 'certificate', :name => "myname" @certpath = File.join(@path, "myname.pem") @request = stub 'request', :key => @cert.name, :instance => @cert end it "should consider the file a ca file if the name is equal to what the SSL::Host class says is the CA name" do Puppet::SSL::Host.expects(:ca_name).returns "amaca" expect(@searcher).to be_ca("amaca") end describe "when choosing the location for certificates" do it "should set them at the ca setting's path if a ca setting is available and the name resolves to the CA name" do @file_class.store_in nil @file_class.store_at :mysetting @file_class.store_ca_at :cakey Puppet[:cakey] = File.expand_path("/ca/file") @searcher.expects(:ca?).with(@cert.name).returns true expect(@searcher.path(@cert.name)).to eq(Puppet[:cakey]) end it "should set them at the file location if a file setting is available" do @file_class.store_in nil @file_class.store_at :cacrl Puppet[:cacrl] = File.expand_path("/some/file") expect(@searcher.path(@cert.name)).to eq(Puppet[:cacrl]) end it "should set them in the setting directory, with the certificate name plus '.pem', if a directory setting is available" do expect(@searcher.path(@cert.name)).to eq(@certpath) end ['../foo', '..\\foo', './../foo', '.\\..\\foo', '/foo', '//foo', '\\foo', '\\\\goo', "test\0/../bar", "test\0\\..\\bar", "..\\/bar", "/tmp/bar", "/tmp\\bar", "tmp\\bar", " / bar", " /../ bar", " \\..\\ bar", "c:\\foo", "c:/foo", "\\\\?\\UNC\\bar", "\\\\foo\\bar", "\\\\?\\c:\\foo", "//?/UNC/bar", "//foo/bar", "//?/c:/foo", ].each do |input| it "should resist directory traversal attacks (#{input.inspect})" do expect { @searcher.path(input) }.to raise_error(ArgumentError, /invalid key/) end end # REVISIT: Should probably test MS-DOS reserved names here, too, since # they would represent a vulnerability on a Win32 system, should we ever # support that path. Don't forget that 'CON.foo' == 'CON' # --daniel 2011-09-24 end describe "when finding certificates on disk" do describe "and no certificate is present" do it "should return nil" do Puppet::FileSystem.expects(:exist?).with(@path).returns(true) Dir.expects(:entries).with(@path).returns([]) Puppet::FileSystem.expects(:exist?).with(@certpath).returns(false) expect(@searcher.find(@request)).to be_nil end end describe "and a certificate is present" do let(:cert) { mock 'cert' } let(:model) { mock 'model' } before(:each) do @file_class.stubs(:model).returns model end context "is readable" do it "should return an instance of the model, which it should use to read the certificate" do Puppet::FileSystem.expects(:exist?).with(@certpath).returns true model.expects(:new).with("myname").returns cert cert.expects(:read).with(@certpath) expect(@searcher.find(@request)).to equal(cert) end end context "is unreadable" do it "should raise an exception" do Puppet::FileSystem.expects(:exist?).with(@certpath).returns(true) model.expects(:new).with("myname").returns cert cert.expects(:read).with(@certpath).raises(Errno::EACCES) expect { @searcher.find(@request) }.to raise_error(Errno::EACCES) end end end describe "and a certificate is present but has uppercase letters" do before do @request = stub 'request', :key => "myhost" end # This is kind of more an integration test; it's for #1382, until # the support for upper-case certs can be removed around mid-2009. it "should rename the existing file to the lower-case path" do @path = @searcher.path("myhost") Puppet::FileSystem.expects(:exist?).with(@path).returns(false) dir, file = File.split(@path) Puppet::FileSystem.expects(:exist?).with(dir).returns true Dir.expects(:entries).with(dir).returns [".", "..", "something.pem", file.upcase] File.expects(:rename).with(File.join(dir, file.upcase), @path) cert = mock 'cert' model = mock 'model' @searcher.stubs(:model).returns model @searcher.model.expects(:new).with("myhost").returns cert cert.expects(:read).with(@path) @searcher.find(@request) end end end describe "when saving certificates to disk" do before do FileTest.stubs(:directory?).returns true FileTest.stubs(:writable?).returns true end it "should fail if the directory is absent" do FileTest.expects(:directory?).with(File.dirname(@certpath)).returns false expect { @searcher.save(@request) }.to raise_error(Puppet::Error) end it "should fail if the directory is not writeable" do FileTest.stubs(:directory?).returns true FileTest.expects(:writable?).with(File.dirname(@certpath)).returns false expect { @searcher.save(@request) }.to raise_error(Puppet::Error) end it "should save to the path the output of converting the certificate to a string" do fh = mock 'filehandle' fh.expects(:print).with("mycert") @searcher.stubs(:write).yields fh @cert.expects(:to_s).returns "mycert" @searcher.save(@request) end describe "and a directory setting is set" do it "should use the Settings class to write the file" do @searcher.class.store_in @setting fh = mock 'filehandle' fh.stubs :print Puppet.settings.setting(@setting).expects(:open_file).with(@certpath, 'w:ASCII').yields fh @searcher.save(@request) end end describe "and a file location is set" do it "should use the filehandle provided by the Settings" do @searcher.class.store_at @setting fh = mock 'filehandle' fh.stubs :print Puppet.settings.setting(@setting).expects(:open).with('w:ASCII').yields fh @searcher.save(@request) end end describe "and the name is the CA name and a ca setting is set" do it "should use the filehandle provided by the Settings" do @searcher.class.store_at @setting @searcher.class.store_ca_at :cakey Puppet[:cakey] = "castuff stub" fh = mock 'filehandle' fh.stubs :print Puppet.settings.setting(:cakey).expects(:open).with('w:ASCII').yields fh @searcher.stubs(:ca?).returns true @searcher.save(@request) end end end describe "when destroying certificates" do describe "that do not exist" do before do Puppet::FileSystem.expects(:exist?).with(Puppet::FileSystem.pathname(@certpath)).returns false end it "should return false" do expect(@searcher.destroy(@request)).to be_falsey end end describe "that exist" do it "should unlink the certificate file" do path = Puppet::FileSystem.pathname(@certpath) Puppet::FileSystem.expects(:exist?).with(path).returns true Puppet::FileSystem.expects(:unlink).with(path) @searcher.destroy(@request) end it "should log that is removing the file" do Puppet::FileSystem.stubs(:exist?).returns true Puppet::FileSystem.stubs(:unlink) Puppet.expects(:notice) @searcher.destroy(@request) end end end describe "when searching for certificates" do let(:one) { stub 'one' } let(:two) { stub 'two' } let(:one_path) { File.join(@path, 'one.pem') } let(:two_path) { File.join(@path, 'two.pem') } let(:model) { mock 'model' } before :each do @file_class.stubs(:model).returns model end it "should return a certificate instance for all files that exist" do Dir.expects(:entries).with(@path).returns(%w{. .. one.pem two.pem}) model.expects(:new).with("one").returns one one.expects(:read).with(one_path) model.expects(:new).with("two").returns two two.expects(:read).with(two_path) expect(@searcher.search(@request)).to eq([one, two]) end it "should raise an exception if any file is unreadable" do Dir.expects(:entries).with(@path).returns(%w{. .. one.pem two.pem}) model.expects(:new).with("one").returns(one) one.expects(:read).with(one_path) model.expects(:new).with("two").returns(two) two.expects(:read).raises(Errno::EACCES) expect { @searcher.search(@request) }.to raise_error(Errno::EACCES) end it "should skip any files that do not match /\.pem$/" do Dir.expects(:entries).with(@path).returns(%w{. .. one two.notpem}) model.expects(:new).never expect(@searcher.search(@request)).to eq([]) end end end end puppet-5.5.10/spec/unit/indirector/terminus_spec.rb0000644005276200011600000002126313417161722022277 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/defaults' require 'puppet/indirector' require 'puppet/indirector/memory' describe Puppet::Indirector::Terminus do before :all do class Puppet::AbstractConcept extend Puppet::Indirector indirects :abstract_concept attr_accessor :name def initialize(name = "name") @name = name end end class Puppet::AbstractConcept::Freedom < Puppet::Indirector::Code end end after :all do # Remove the class, unlinking it from the rest of the system. Puppet.send(:remove_const, :AbstractConcept) end let :terminus_class do Puppet::AbstractConcept::Freedom end let :terminus do terminus_class.new end let :indirection do Puppet::AbstractConcept.indirection end let :model do Puppet::AbstractConcept end it "should provide a method for setting terminus class documentation" do expect(terminus_class).to respond_to(:desc) end it "should support a class-level name attribute" do expect(terminus_class).to respond_to(:name) end it "should support a class-level indirection attribute" do expect(terminus_class).to respond_to(:indirection) end it "should support a class-level terminus-type attribute" do expect(terminus_class).to respond_to(:terminus_type) end it "should support a class-level model attribute" do expect(terminus_class).to respond_to(:model) end it "should accept indirection instances as its indirection" do # The test is that this shouldn't raise, and should preserve the object # instance exactly, hence "equal", not just "==". terminus_class.indirection = indirection expect(terminus_class.indirection).to equal indirection end it "should look up indirection instances when only a name has been provided" do terminus_class.indirection = :abstract_concept expect(terminus_class.indirection).to equal indirection end it "should fail when provided a name that does not resolve to an indirection" do expect { terminus_class.indirection = :exploding_whales }.to raise_error(ArgumentError, /Could not find indirection instance/) # We should still have the default indirection. expect(terminus_class.indirection).to equal indirection end describe "when a terminus instance" do it "should return the class's name as its name" do expect(terminus.name).to eq(:freedom) end it "should return the class's indirection as its indirection" do expect(terminus.indirection).to equal indirection end it "should set the instances's type to the abstract terminus type's name" do expect(terminus.terminus_type).to eq(:code) end it "should set the instances's model to the indirection's model" do expect(terminus.model).to equal indirection.model end end describe "when managing terminus classes" do it "should provide a method for registering terminus classes" do expect(Puppet::Indirector::Terminus).to respond_to(:register_terminus_class) end it "should provide a method for returning terminus classes by name and type" do terminus = stub 'terminus_type', :name => :abstract, :indirection_name => :whatever Puppet::Indirector::Terminus.register_terminus_class(terminus) expect(Puppet::Indirector::Terminus.terminus_class(:whatever, :abstract)).to equal(terminus) end it "should set up autoloading for any terminus class types requested" do Puppet::Indirector::Terminus.expects(:instance_load).with(:test2, "puppet/indirector/test2") Puppet::Indirector::Terminus.terminus_class(:test2, :whatever) end it "should load terminus classes that are not found" do # Set up instance loading; it would normally happen automatically Puppet::Indirector::Terminus.instance_load :test1, "puppet/indirector/test1" Puppet::Indirector::Terminus.instance_loader(:test1).expects(:load).with(:yay) Puppet::Indirector::Terminus.terminus_class(:test1, :yay) end it "should fail when no indirection can be found" do Puppet::Indirector::Indirection.expects(:instance).with(:abstract_concept).returns(nil) expect { class Puppet::AbstractConcept::Physics < Puppet::Indirector::Code end }.to raise_error(ArgumentError, /Could not find indirection instance/) end it "should register the terminus class with the terminus base class" do Puppet::Indirector::Terminus.expects(:register_terminus_class).with do |type| type.indirection_name == :abstract_concept and type.name == :intellect end begin class Puppet::AbstractConcept::Intellect < Puppet::Indirector::Code end ensure Puppet::AbstractConcept.send(:remove_const, :Intellect) rescue nil end end end describe "when parsing class constants for indirection and terminus names" do before :each do Puppet::Indirector::Terminus.stubs(:register_terminus_class) end let :subclass do subclass = mock 'subclass' subclass.stubs(:to_s).returns("TestInd::OneTwo") subclass.stubs(:mark_as_abstract_terminus) subclass end it "should fail when anonymous classes are used" do expect { Puppet::Indirector::Terminus.inherited(Class.new) }.to raise_error(Puppet::DevError, /Terminus subclasses must have associated constants/) end it "should use the last term in the constant for the terminus class name" do subclass.expects(:name=).with(:one_two) subclass.stubs(:indirection=) Puppet::Indirector::Terminus.inherited(subclass) end it "should convert the terminus name to a downcased symbol" do subclass.expects(:name=).with(:one_two) subclass.stubs(:indirection=) Puppet::Indirector::Terminus.inherited(subclass) end it "should use the second to last term in the constant for the indirection name" do subclass.expects(:indirection=).with(:test_ind) subclass.stubs(:name=) subclass.stubs(:terminus_type=) Puppet::Indirector::Memory.inherited(subclass) end it "should convert the indirection name to a downcased symbol" do subclass.expects(:indirection=).with(:test_ind) subclass.stubs(:name=) subclass.stubs(:terminus_type=) Puppet::Indirector::Memory.inherited(subclass) end it "should convert camel case to lower case with underscores as word separators" do subclass.expects(:name=).with(:one_two) subclass.stubs(:indirection=) Puppet::Indirector::Terminus.inherited(subclass) end end describe "when creating terminus class types" do before :all do Puppet::Indirector::Terminus.stubs(:register_terminus_class) class Puppet::Indirector::Terminus::TestTerminusType < Puppet::Indirector::Terminus end end after :all do Puppet::Indirector::Terminus.send(:remove_const, :TestTerminusType) end let :subclass do Puppet::Indirector::Terminus::TestTerminusType end it "should set the name of the abstract subclass to be its class constant" do expect(subclass.name).to eq(:test_terminus_type) end it "should mark abstract terminus types as such" do expect(subclass).to be_abstract_terminus end it "should not allow instances of abstract subclasses to be created" do expect { subclass.new }.to raise_error(Puppet::DevError) end end describe "when listing terminus classes" do it "should list the terminus files available to load" do Puppet::Util::Autoload.any_instance.stubs(:files_to_load).returns ["/foo/bar/baz", "/max/runs/marathon"] expect(Puppet::Indirector::Terminus.terminus_classes('my_stuff')).to eq([:baz, :marathon]) end end describe "when validating a request" do let :request do Puppet::Indirector::Request.new(indirection.name, :find, "the_key", instance) end describe "`instance.name` does not match the key in the request" do let(:instance) { model.new("wrong_key") } it "raises an error " do expect { terminus.validate(request) }.to raise_error( Puppet::Indirector::ValidationError, /Instance name .* does not match requested key/ ) end end describe "`instance` is not an instance of the model class" do let(:instance) { mock "instance" } it "raises an error" do expect { terminus.validate(request) }.to raise_error( Puppet::Indirector::ValidationError, /Invalid instance type/ ) end end describe "the instance key and class match the request key and model class" do let(:instance) { model.new("the_key") } it "passes" do terminus.validate(request) end end end end puppet-5.5.10/spec/unit/indirector/yaml_spec.rb0000644005276200011600000001341313417161722021371 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector/yaml' describe Puppet::Indirector::Yaml do include PuppetSpec::Files class TestSubject attr_accessor :name end before :all do @indirection = stub 'indirection', :name => :my_yaml, :register_terminus_type => nil Puppet::Indirector::Indirection.expects(:instance).with(:my_yaml).returns(@indirection) module MyYaml; end @store_class = class MyYaml::MyType < Puppet::Indirector::Yaml self end end before :each do @store = @store_class.new @subject = TestSubject.new @subject.name = :me @dir = tmpdir("yaml_indirector") Puppet[:clientyamldir] = @dir Puppet.run_mode.stubs(:master?).returns false @request = stub 'request', :key => :me, :instance => @subject end let(:serverdir) { File.expand_path("/server/yaml/dir") } let(:clientdir) { File.expand_path("/client/yaml/dir") } describe "when choosing file location" do it "should use the server_datadir if the run_mode is master" do Puppet.run_mode.stubs(:master?).returns true Puppet[:yamldir] = serverdir expect(@store.path(:me)).to match(/^#{serverdir}/) end it "should use the client yamldir if the run_mode is not master" do Puppet.run_mode.stubs(:master?).returns false Puppet[:clientyamldir] = clientdir expect(@store.path(:me)).to match(/^#{clientdir}/) end it "should use the extension if one is specified" do Puppet.run_mode.stubs(:master?).returns true Puppet[:yamldir] = serverdir expect(@store.path(:me,'.farfignewton')).to match(%r{\.farfignewton$}) end it "should assume an extension of .yaml if none is specified" do Puppet.run_mode.stubs(:master?).returns true Puppet[:yamldir] = serverdir expect(@store.path(:me)).to match(%r{\.yaml$}) end it "should store all files in a single file root set in the Puppet defaults" do expect(@store.path(:me)).to match(%r{^#{@dir}}) end it "should use the terminus name for choosing the subdirectory" do expect(@store.path(:me)).to match(%r{^#{@dir}/my_yaml}) end it "should use the object's name to determine the file name" do expect(@store.path(:me)).to match(%r{me.yaml$}) end ['../foo', '..\\foo', './../foo', '.\\..\\foo', '/foo', '//foo', '\\foo', '\\\\goo', "test\0/../bar", "test\0\\..\\bar", "..\\/bar", "/tmp/bar", "/tmp\\bar", "tmp\\bar", " / bar", " /../ bar", " \\..\\ bar", "c:\\foo", "c:/foo", "\\\\?\\UNC\\bar", "\\\\foo\\bar", "\\\\?\\c:\\foo", "//?/UNC/bar", "//foo/bar", "//?/c:/foo", ].each do |input| it "should resist directory traversal attacks (#{input.inspect})" do expect { @store.path(input) }.to raise_error(ArgumentError) end end end describe "when storing objects as YAML" do it "should only store objects that respond to :name" do @request.stubs(:instance).returns Object.new expect { @store.save(@request) }.to raise_error(ArgumentError) end end describe "when retrieving YAML" do it "should read YAML in from disk and convert it to Ruby objects" do @store.save(Puppet::Indirector::Request.new(:my_yaml, :save, "testing", @subject)) expect(@store.find(Puppet::Indirector::Request.new(:my_yaml, :find, "testing", nil)).name).to eq(:me) end it "should fail coherently when the stored YAML is invalid" do saved_structure = Struct.new(:name).new("testing") @store.save(Puppet::Indirector::Request.new(:my_yaml, :save, "testing", saved_structure)) File.open(@store.path(saved_structure.name), "w") do |file| file.puts "{ invalid" end expect { @store.find(Puppet::Indirector::Request.new(:my_yaml, :find, "testing", nil)) }.to raise_error(Puppet::Error, /Could not parse YAML data/) end end describe "when searching" do it "should return an array of fact instances with one instance for each file when globbing *" do @request = stub 'request', :key => "*", :instance => @subject @one = mock 'one' @two = mock 'two' @store.expects(:path).with(@request.key,'').returns :glob Dir.expects(:glob).with(:glob).returns(%w{one.yaml two.yaml}) YAML.expects(:load_file).with("one.yaml").returns @one; YAML.expects(:load_file).with("two.yaml").returns @two; expect(@store.search(@request)).to contain_exactly(@one, @two) end it "should return an array containing a single instance of fact when globbing 'one*'" do @request = stub 'request', :key => "one*", :instance => @subject @one = mock 'one' @store.expects(:path).with(@request.key,'').returns :glob Dir.expects(:glob).with(:glob).returns(%w{one.yaml}) YAML.expects(:load_file).with("one.yaml").returns @one; expect(@store.search(@request)).to eq([@one]) end it "should return an empty array when the glob doesn't match anything" do @request = stub 'request', :key => "f*ilglobcanfail*", :instance => @subject @store.expects(:path).with(@request.key,'').returns :glob Dir.expects(:glob).with(:glob).returns [] expect(@store.search(@request)).to eq([]) end describe "when destroying" do let(:path) do File.join(@dir, @store.class.indirection_name.to_s, @request.key.to_s + ".yaml") end it "should unlink the right yaml file if it exists" do Puppet::FileSystem.expects(:exist?).with(path).returns true Puppet::FileSystem.expects(:unlink).with(path) @store.destroy(@request) end it "should not unlink the yaml file if it does not exists" do Puppet::FileSystem.expects(:exist?).with(path).returns false Puppet::FileSystem.expects(:unlink).with(path).never @store.destroy(@request) end end end end puppet-5.5.10/spec/unit/indirector_spec.rb0000644005276200011600000000765613417161721020442 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/defaults' require 'puppet/indirector' describe Puppet::Indirector, "when configuring routes" do before :each do Puppet::Node.indirection.reset_terminus_class Puppet::Node.indirection.cache_class = nil end after :each do Puppet::Node.indirection.reset_terminus_class Puppet::Node.indirection.cache_class = nil end it "should configure routes as requested" do routes = { "node" => { "terminus" => "exec", "cache" => "plain" } } Puppet::Indirector.configure_routes(routes) expect(Puppet::Node.indirection.terminus_class).to eq("exec") expect(Puppet::Node.indirection.cache_class).to eq("plain") end it "should fail when given an invalid indirection" do routes = { "fake_indirection" => { "terminus" => "exec", "cache" => "plain" } } expect { Puppet::Indirector.configure_routes(routes) }.to raise_error(/fake_indirection does not exist/) end it "should fail when given an invalid terminus" do routes = { "node" => { "terminus" => "fake_terminus", "cache" => "plain" } } expect { Puppet::Indirector.configure_routes(routes) }.to raise_error(/Could not find terminus fake_terminus/) end it "should fail when given an invalid cache" do routes = { "node" => { "terminus" => "exec", "cache" => "fake_cache" } } expect { Puppet::Indirector.configure_routes(routes) }.to raise_error(/Could not find terminus fake_cache/) end end describe Puppet::Indirector, " when available to a model" do before do @thingie = Class.new do extend Puppet::Indirector end end it "should provide a way for the model to register an indirection under a name" do expect(@thingie).to respond_to(:indirects) end end describe Puppet::Indirector, "when registering an indirection" do before do @thingie = Class.new do extend Puppet::Indirector # override Class#name, since we're not naming this ephemeral class def self.name 'Thingie' end attr_reader :name def initialize(name) @name = name end end end it "should require a name when registering a model" do expect {@thingie.send(:indirects) }.to raise_error(ArgumentError) end it "should create an indirection instance to manage each indirecting model" do @indirection = @thingie.indirects(:test) expect(@indirection).to be_instance_of(Puppet::Indirector::Indirection) end it "should not allow a model to register under multiple names" do # Keep track of the indirection instance so we can delete it on cleanup @indirection = @thingie.indirects :first expect { @thingie.indirects :second }.to raise_error(ArgumentError) end it "should make the indirection available via an accessor" do @indirection = @thingie.indirects :first expect(@thingie.indirection).to equal(@indirection) end it "should pass any provided options to the indirection during initialization" do Puppet::Indirector::Indirection.expects(:new).with(@thingie, :first, {:some => :options, :indirected_class => 'Thingie'}) @indirection = @thingie.indirects :first, :some => :options end it "should extend the class to handle serialization" do @indirection = @thingie.indirects :first expect(@thingie).to respond_to(:convert_from) end after do @indirection.delete if @indirection end end describe Puppet::Indirector, "when redirecting a model" do before do @thingie = Class.new do extend Puppet::Indirector attr_reader :name def initialize(name) @name = name end end @indirection = @thingie.send(:indirects, :test) end it "should include the Envelope module in the model" do expect(@thingie.ancestors).to be_include(Puppet::Indirector::Envelope) end after do @indirection.delete end end puppet-5.5.10/spec/unit/interface/0000755005276200011600000000000013417162176016670 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/interface/action_builder_spec.rb0000644005276200011600000001623513417161721023214 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/interface' require 'puppet/network/format_handler' describe Puppet::Interface::ActionBuilder do let :face do Puppet::Interface.new(:puppet_interface_actionbuilder, '0.0.1') end it "should build an action" do action = Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end end expect(action).to be_a(Puppet::Interface::Action) expect(action.name).to eq(:foo) end it "should define a method on the face which invokes the action" do face = Puppet::Interface.new(:action_builder_test_interface, '0.0.1') do action(:foo) { when_invoked { |options| "invoked the method" } } end expect(face.foo).to eq("invoked the method") end it "should require a block" do expect { Puppet::Interface::ActionBuilder.build(nil, :foo) }.to raise_error("Action :foo must specify a block") end it "should require an invocation block" do expect { Puppet::Interface::ActionBuilder.build(face, :foo) {} }.to raise_error(/actions need to know what to do when_invoked; please add the block/) end describe "when handling options" do it "should have a #option DSL function" do method = nil Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end method = self.method(:option) end expect(method).to be_an_instance_of Method end it "should define an option without a block" do action = Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end option "--bar" end expect(action).to be_option :bar end it "should accept an empty block" do action = Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end option "--bar" do # This space left deliberately blank. end end expect(action).to be_option :bar end end context "inline documentation" do it "should set the summary" do action = Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end summary "this is some text" end expect(action.summary).to eq("this is some text") end end context "action defaulting" do it "should set the default to true" do action = Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end default end expect(action.default).to be_truthy end it "should not be default by, er, default. *cough*" do action = Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end end expect(action.default).to be_falsey end end context "#when_rendering" do it "should fail if no rendering format is given" do expect { Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end when_rendering do true end end }.to raise_error ArgumentError, /must give a rendering format to when_rendering/ end it "should fail if no block is given" do expect { Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end when_rendering :json end }.to raise_error ArgumentError, /must give a block to when_rendering/ end it "should fail if the block takes no arguments" do expect { Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end when_rendering :json do true end end }.to raise_error ArgumentError, /the puppet_interface_actionbuilder face foo action takes .* not/ end it "should fail if the when_rendering block takes a different number of arguments than when_invoked" do expect { Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end when_rendering :json do |a, b, c| true end end }.to raise_error ArgumentError, /the puppet_interface_actionbuilder face foo action takes .* not 3/ end it "should fail if the block takes a variable number of arguments" do expect { Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end when_rendering :json do |*args| true end end }.to raise_error ArgumentError, /the puppet_interface_actionbuilder face foo action takes .* not/ end it "should stash a rendering block" do action = Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end when_rendering :json do |a| true end end expect(action.when_rendering(:json)).to be_an_instance_of Method end it "should fail if you try to set the same rendering twice" do expect { Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end when_rendering :json do |a| true end when_rendering :json do |a| true end end }.to raise_error ArgumentError, /You can't define a rendering method for json twice/ end it "should work if you set two different renderings" do action = Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end when_rendering :json do |a| true end when_rendering :yaml do |a| true end end expect(action.when_rendering(:json)).to be_an_instance_of Method expect(action.when_rendering(:yaml)).to be_an_instance_of Method end it "should be bound to the face when called" do action = Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end when_rendering :json do |a| self end end expect(action.when_rendering(:json).call(true)).to eq(face) end end context "#render_as" do it "should default to nil (eg: based on context)" do action = Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end end expect(action.render_as).to be_nil end it "should fail if not rendering format is given" do expect { Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end render_as end }.to raise_error ArgumentError, /must give a rendering format to render_as/ end Puppet::Network::FormatHandler.formats.each do |name| it "should accept #{name.inspect} format" do action = Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end render_as name end expect(action.render_as).to eq(name) end end [:if_you_define_this_format_you_frighten_me, "json", 12].each do |input| it "should fail if given #{input.inspect}" do expect { Puppet::Interface::ActionBuilder.build(face, :foo) do when_invoked do |options| true end render_as input end }.to raise_error ArgumentError, /#{input.inspect} is not a valid rendering format/ end end end end puppet-5.5.10/spec/unit/interface/action_manager_spec.rb0000644005276200011600000001711313417161721023174 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/interface' class ActionManagerTester include Puppet::Interface::ActionManager end describe Puppet::Interface::ActionManager do subject { ActionManagerTester.new } describe "when included in a class" do it "should be able to define an action" do subject.action(:foo) do when_invoked { |options| "something "} end end it "should be able to list defined actions" do subject.action(:foo) do when_invoked { |options| "something" } end subject.action(:bar) do when_invoked { |options| "something" } end expect(subject.actions).to match_array([:foo, :bar]) end it "should be able to indicate when an action is defined" do subject.action(:foo) do when_invoked { |options| "something" } end expect(subject).to be_action(:foo) end it "should correctly treat action names specified as strings" do subject.action(:foo) do when_invoked { |options| "something" } end expect(subject).to be_action("foo") end end describe "when used to extend a class" do subject { Class.new.extend(Puppet::Interface::ActionManager) } it "should be able to define an action" do subject.action(:foo) do when_invoked { |options| "something "} end end it "should be able to list defined actions" do subject.action(:foo) do when_invoked { |options| "something" } end subject.action(:bar) do when_invoked { |options| "something" } end expect(subject.actions).to include(:bar) expect(subject.actions).to include(:foo) end it "should be able to indicate when an action is defined" do subject.action(:foo) { when_invoked do |options| true end } expect(subject).to be_action(:foo) end end describe "when used both at the class and instance level" do before do @klass = Class.new do include Puppet::Interface::ActionManager extend Puppet::Interface::ActionManager def __invoke_decorations(*args) true end def options() [] end end @instance = @klass.new end it "should be able to define an action at the class level" do @klass.action(:foo) do when_invoked { |options| "something "} end end it "should create an instance method when an action is defined at the class level" do @klass.action(:foo) do when_invoked { |options| "something" } end expect(@instance.foo).to eq("something") end it "should be able to define an action at the instance level" do @instance.action(:foo) do when_invoked { |options| "something "} end end it "should create an instance method when an action is defined at the instance level" do @instance.action(:foo) do when_invoked { |options| "something" } end expect(@instance.foo).to eq("something") end it "should be able to list actions defined at the class level" do @klass.action(:foo) do when_invoked { |options| "something" } end @klass.action(:bar) do when_invoked { |options| "something" } end expect(@klass.actions).to include(:bar) expect(@klass.actions).to include(:foo) end it "should be able to list actions defined at the instance level" do @instance.action(:foo) do when_invoked { |options| "something" } end @instance.action(:bar) do when_invoked { |options| "something" } end expect(@instance.actions).to include(:bar) expect(@instance.actions).to include(:foo) end it "should be able to list actions defined at both instance and class level" do @klass.action(:foo) do when_invoked { |options| "something" } end @instance.action(:bar) do when_invoked { |options| "something" } end expect(@instance.actions).to include(:bar) expect(@instance.actions).to include(:foo) end it "should be able to indicate when an action is defined at the class level" do @klass.action(:foo) do when_invoked { |options| "something" } end expect(@instance).to be_action(:foo) end it "should be able to indicate when an action is defined at the instance level" do @klass.action(:foo) do when_invoked { |options| "something" } end expect(@instance).to be_action(:foo) end context "with actions defined in superclass" do before :each do @subclass = Class.new(@klass) @instance = @subclass.new @klass.action(:parent) do when_invoked { |options| "a" } end @subclass.action(:sub) do when_invoked { |options| "a" } end @instance.action(:instance) do when_invoked { |options| "a" } end end it "should list actions defined in superclasses" do expect(@instance).to be_action(:parent) expect(@instance).to be_action(:sub) expect(@instance).to be_action(:instance) end it "should list inherited actions" do expect(@instance.actions).to match_array([:instance, :parent, :sub]) end it "should not duplicate instance actions after fetching them (#7699)" do expect(@instance.actions).to match_array([:instance, :parent, :sub]) @instance.get_action(:instance) expect(@instance.actions).to match_array([:instance, :parent, :sub]) end it "should not duplicate subclass actions after fetching them (#7699)" do expect(@instance.actions).to match_array([:instance, :parent, :sub]) @instance.get_action(:sub) expect(@instance.actions).to match_array([:instance, :parent, :sub]) end it "should not duplicate superclass actions after fetching them (#7699)" do expect(@instance.actions).to match_array([:instance, :parent, :sub]) @instance.get_action(:parent) expect(@instance.actions).to match_array([:instance, :parent, :sub]) end end it "should create an instance method when an action is defined in a superclass" do @subclass = Class.new(@klass) @instance = @subclass.new @klass.action(:foo) do when_invoked { |options| "something" } end expect(@instance.foo).to eq("something") end end describe "#action" do it 'should add an action' do subject.action(:foo) { when_invoked do |options| true end } expect(subject.get_action(:foo)).to be_a Puppet::Interface::Action end it 'should support default actions' do subject.action(:foo) { when_invoked do |options| true end; default } expect(subject.get_default_action).to eq(subject.get_action(:foo)) end it 'should not support more than one default action' do subject.action(:foo) { when_invoked do |options| true end; default } expect { subject.action(:bar) { when_invoked do |options| true end default } }.to raise_error(/cannot both be default/) end end describe "#get_action" do let :parent_class do parent_class = Class.new(Puppet::Interface) parent_class.action(:foo) { when_invoked do |options| true end } parent_class end it "should check that we can find inherited actions when we are a class" do expect(Class.new(parent_class).get_action(:foo).name).to eq(:foo) end it "should check that we can find inherited actions when we are an instance" do instance = parent_class.new(:foo, '0.0.0') expect(instance.get_action(:foo).name).to eq(:foo) end end end puppet-5.5.10/spec/unit/interface/action_spec.rb0000644005276200011600000005656013417161721021513 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/interface' describe Puppet::Interface::Action do describe "when validating the action name" do [nil, '', 'foo bar', '-foobar'].each do |input| it "should treat #{input.inspect} as an invalid name" do expect { Puppet::Interface::Action.new(nil, input) }.to raise_error(/is an invalid action name/) end end end describe "#when_invoked=" do it "should fail if the block has arity 0" do expect { Puppet::Interface.new(:action_when_invoked, '1.0.0') do action :foo do when_invoked { } end end }.to raise_error ArgumentError, /foo/ end it "should work with arity 1 blocks" do face = Puppet::Interface.new(:action_when_invoked, '1.0.0') do action :foo do when_invoked {|one| } end end # -1, because we use option defaulting. :( expect(face.method(:foo).arity).to eq(-1) end it "should work with arity 2 blocks" do face = Puppet::Interface.new(:action_when_invoked, '1.0.0') do action :foo do when_invoked {|one, two| } end end # -2, because we use option defaulting. :( expect(face.method(:foo).arity).to eq(-2) end it "should work with arity 1 blocks that collect arguments" do face = Puppet::Interface.new(:action_when_invoked, '1.0.0') do action :foo do when_invoked {|*one| } end end # -1, because we use only varargs expect(face.method(:foo).arity).to eq(-1) end it "should work with arity 2 blocks that collect arguments" do face = Puppet::Interface.new(:action_when_invoked, '1.0.0') do action :foo do when_invoked {|one, *two| } end end # -2, because we take one mandatory argument, and one varargs expect(face.method(:foo).arity).to eq(-2) end end describe "when invoking" do it "should be able to call other actions on the same object" do face = Puppet::Interface.new(:my_face, '0.0.1') do action(:foo) do when_invoked { |options| 25 } end action(:bar) do when_invoked { |options| "the value of foo is '#{foo}'" } end end expect(face.foo).to eq(25) expect(face.bar).to eq("the value of foo is '25'") end # bar is a class action calling a class action # quux is a class action calling an instance action # baz is an instance action calling a class action # qux is an instance action calling an instance action it "should be able to call other actions on the same object when defined on a class" do class Puppet::Interface::MyInterfaceBaseClass < Puppet::Interface action(:foo) do when_invoked { |options| 25 } end action(:bar) do when_invoked { |options| "the value of foo is '#{foo}'" } end action(:quux) do when_invoked { |options| "qux told me #{qux}" } end end face = Puppet::Interface::MyInterfaceBaseClass.new(:my_inherited_face, '0.0.1') do action(:baz) do when_invoked { |options| "the value of foo in baz is '#{foo}'" } end action(:qux) do when_invoked { |options| baz } end end expect(face.foo).to eq(25) expect(face.bar).to eq("the value of foo is '25'") expect(face.quux).to eq("qux told me the value of foo in baz is '25'") expect(face.baz).to eq("the value of foo in baz is '25'") expect(face.qux).to eq("the value of foo in baz is '25'") end context "when calling the Ruby API" do let :face do Puppet::Interface.new(:ruby_api, '1.0.0') do action :bar do option "--bar" when_invoked do |*args| args.last end end end end it "should work when no options are supplied" do options = face.bar expect(options).to eq({}) end it "should work when options are supplied" do options = face.bar(:bar => "beer") expect(options).to eq({ :bar => "beer" }) end it "should call #validate_and_clean on the action when invoked" do face.get_action(:bar).expects(:validate_and_clean).with({}).returns({}) face.bar 1, :two, 'three' end end end describe "with action-level options" do it "should support options with an empty block" do face = Puppet::Interface.new(:action_level_options, '0.0.1') do action :foo do when_invoked do |options| true end option "--bar" do # this line left deliberately blank end end end expect(face).not_to be_option :bar expect(face.get_action(:foo)).to be_option :bar end it "should return only action level options when there are no face options" do face = Puppet::Interface.new(:action_level_options, '0.0.1') do action :foo do when_invoked do |options| true end option "--bar" end end expect(face.get_action(:foo).options).to match_array([:bar]) end describe "option aliases" do let :option do action.get_option :bar end let :action do face.get_action :foo end let :face do Puppet::Interface.new(:action_level_options, '0.0.1') do action :foo do when_invoked do |options| options end option "--bar", "--foo", "-b" end end end it "should only list options and not aliases" do expect(action.options).to match_array([:bar]) end it "should use the canonical option name when passed aliases" do name = option.name option.aliases.each do |input| expect(face.foo(input => 1)).to eq({ name => 1 }) end end end describe "with both face and action options" do let :face do Puppet::Interface.new(:action_level_options, '0.0.1') do action :foo do when_invoked do |options| true end ; option "--bar" end action :baz do when_invoked do |options| true end ; option "--bim" end option "--quux" end end it "should return combined face and action options" do expect(face.get_action(:foo).options).to match_array([:bar, :quux]) end it "should fetch options that the face inherited" do parent = Class.new(Puppet::Interface) parent.option "--foo" child = parent.new(:inherited_options, '0.0.1') do option "--bar" action :action do when_invoked do |options| true end option "--baz" end end action = child.get_action(:action) expect(action).to be [:baz, :bar, :foo].each do |name| expect(action.get_option(name)).to be_an_instance_of Puppet::Interface::Option end end it "should get an action option when asked" do expect(face.get_action(:foo).get_option(:bar)). to be_an_instance_of Puppet::Interface::Option end it "should get a face option when asked" do expect(face.get_action(:foo).get_option(:quux)). to be_an_instance_of Puppet::Interface::Option end it "should return options only for this action" do expect(face.get_action(:baz).options).to match_array([:bim, :quux]) end end it_should_behave_like "things that declare options" do def add_options_to(&block) face = Puppet::Interface.new(:with_options, '0.0.1') do action(:foo) do when_invoked do |options| true end self.instance_eval(&block) end end face.get_action(:foo) end end it "should fail when a face option duplicates an action option" do expect { Puppet::Interface.new(:action_level_options, '0.0.1') do option "--foo" action :bar do option "--foo" end end }.to raise_error ArgumentError, /Option foo conflicts with existing option foo/i end it "should fail when a required action option is not provided" do face = Puppet::Interface.new(:required_action_option, '0.0.1') do action(:bar) do option('--foo') { required } when_invoked {|options| } end end expect { face.bar }.to raise_error ArgumentError, /The following options are required: foo/ end it "should fail when a required face option is not provided" do face = Puppet::Interface.new(:required_face_option, '0.0.1') do option('--foo') { required } action(:bar) { when_invoked {|options| } } end expect { face.bar }.to raise_error ArgumentError, /The following options are required: foo/ end end context "with decorators" do context "declared locally" do let :face do Puppet::Interface.new(:action_decorators, '0.0.1') do action :bar do when_invoked do |options| true end end def reported; @reported; end def report(arg) (@reported ||= []) << arg end end end it "should execute before advice on action options in declaration order" do face.action(:boo) do option("--foo") { before_action { |_,_,_| report :foo } } option("--bar", '-b') { before_action { |_,_,_| report :bar } } option("-q", "--quux") { before_action { |_,_,_| report :quux } } option("-f") { before_action { |_,_,_| report :f } } option("--baz") { before_action { |_,_,_| report :baz } } when_invoked {|options| } end face.boo :foo => 1, :bar => 1, :quux => 1, :f => 1, :baz => 1 expect(face.reported).to eq([ :foo, :bar, :quux, :f, :baz ]) end it "should execute after advice on action options in declaration order" do face.action(:boo) do option("--foo") { after_action { |_,_,_| report :foo } } option("--bar", '-b') { after_action { |_,_,_| report :bar } } option("-q", "--quux") { after_action { |_,_,_| report :quux } } option("-f") { after_action { |_,_,_| report :f } } option("--baz") { after_action { |_,_,_| report :baz } } when_invoked {|options| } end face.boo :foo => 1, :bar => 1, :quux => 1, :f => 1, :baz => 1 expect(face.reported).to eq([ :foo, :bar, :quux, :f, :baz ].reverse) end it "should execute before advice on face options in declaration order" do face.instance_eval do option("--foo") { before_action { |_,_,_| report :foo } } option("--bar", '-b') { before_action { |_,_,_| report :bar } } option("-q", "--quux") { before_action { |_,_,_| report :quux } } option("-f") { before_action { |_,_,_| report :f } } option("--baz") { before_action { |_,_,_| report :baz } } end face.action(:boo) { when_invoked { |options| } } face.boo :foo => 1, :bar => 1, :quux => 1, :f => 1, :baz => 1 expect(face.reported).to eq([ :foo, :bar, :quux, :f, :baz ]) end it "should execute after advice on face options in declaration order" do face.instance_eval do option("--foo") { after_action { |_,_,_| report :foo } } option("--bar", '-b') { after_action { |_,_,_| report :bar } } option("-q", "--quux") { after_action { |_,_,_| report :quux } } option("-f") { after_action { |_,_,_| report :f } } option("--baz") { after_action { |_,_,_| report :baz } } end face.action(:boo) { when_invoked { |options| } } face.boo :foo => 1, :bar => 1, :quux => 1, :f => 1, :baz => 1 expect(face.reported).to eq([ :foo, :bar, :quux, :f, :baz ].reverse) end it "should execute before advice on face options before action options" do face.instance_eval do option("--face-foo") { before_action { |_,_,_| report :face_foo } } option("--face-bar", '-r') { before_action { |_,_,_| report :face_bar } } action(:boo) do option("--action-foo") { before_action { |_,_,_| report :action_foo } } option("--action-bar", '-b') { before_action { |_,_,_| report :action_bar } } option("-q", "--action-quux") { before_action { |_,_,_| report :action_quux } } option("-a") { before_action { |_,_,_| report :a } } option("--action-baz") { before_action { |_,_,_| report :action_baz } } when_invoked {|options| } end option("-u", "--face-quux") { before_action { |_,_,_| report :face_quux } } option("-f") { before_action { |_,_,_| report :f } } option("--face-baz") { before_action { |_,_,_| report :face_baz } } end expected_calls = [ :face_foo, :face_bar, :face_quux, :f, :face_baz, :action_foo, :action_bar, :action_quux, :a, :action_baz ] face.boo Hash[ *expected_calls.zip([]).flatten ] expect(face.reported).to eq(expected_calls) end it "should execute after advice on face options in declaration order" do face.instance_eval do option("--face-foo") { after_action { |_,_,_| report :face_foo } } option("--face-bar", '-r') { after_action { |_,_,_| report :face_bar } } action(:boo) do option("--action-foo") { after_action { |_,_,_| report :action_foo } } option("--action-bar", '-b') { after_action { |_,_,_| report :action_bar } } option("-q", "--action-quux") { after_action { |_,_,_| report :action_quux } } option("-a") { after_action { |_,_,_| report :a } } option("--action-baz") { after_action { |_,_,_| report :action_baz } } when_invoked {|options| } end option("-u", "--face-quux") { after_action { |_,_,_| report :face_quux } } option("-f") { after_action { |_,_,_| report :f } } option("--face-baz") { after_action { |_,_,_| report :face_baz } } end expected_calls = [ :face_foo, :face_bar, :face_quux, :f, :face_baz, :action_foo, :action_bar, :action_quux, :a, :action_baz ] face.boo Hash[ *expected_calls.zip([]).flatten ] expect(face.reported).to eq(expected_calls.reverse) end it "should not invoke a decorator if the options are empty" do face.option("--foo FOO") { before_action { |_,_,_| report :before_action } } face.expects(:report).never face.bar end context "passing a subset of the options" do before :each do face.option("--foo") { before_action { |_,_,_| report :foo } } face.option("--bar") { before_action { |_,_,_| report :bar } } end it "should invoke only foo's advice when passed only 'foo'" do face.bar(:foo => true) expect(face.reported).to eq([ :foo ]) end it "should invoke only bar's advice when passed only 'bar'" do face.bar(:bar => true) expect(face.reported).to eq([ :bar ]) end it "should invoke advice for all passed options" do face.bar(:foo => true, :bar => true) expect(face.reported).to eq([ :foo, :bar ]) end end end context "and inheritance" do let :parent do Class.new(Puppet::Interface) do action(:on_parent) { when_invoked { |options| :on_parent } } def reported; @reported; end def report(arg) (@reported ||= []) << arg end end end let :child do parent.new(:inherited_decorators, '0.0.1') do action(:on_child) { when_invoked { |options| :on_child } } end end context "locally declared face options" do subject do child.option("--foo=") { before_action { |_,_,_| report :child_before } } child end it "should be invoked when calling a child action" do expect(subject.on_child(:foo => true)).to eq(:on_child) expect(subject.reported).to eq([ :child_before ]) end it "should be invoked when calling a parent action" do expect(subject.on_parent(:foo => true)).to eq(:on_parent) expect(subject.reported).to eq([ :child_before ]) end end context "inherited face option decorators" do subject do parent.option("--foo=") { before_action { |_,_,_| report :parent_before } } child end it "should be invoked when calling a child action" do expect(subject.on_child(:foo => true)).to eq(:on_child) expect(subject.reported).to eq([ :parent_before ]) end it "should be invoked when calling a parent action" do expect(subject.on_parent(:foo => true)).to eq(:on_parent) expect(subject.reported).to eq([ :parent_before ]) end end context "with both inherited and local face options" do # Decorations should be invoked in declaration order, according to # inheritance (e.g. parent class options should be handled before # subclass options). subject do child.option "-c" do before_action { |action, args, options| report :c_before } after_action { |action, args, options| report :c_after } end parent.option "-a" do before_action { |action, args, options| report :a_before } after_action { |action, args, options| report :a_after } end child.option "-d" do before_action { |action, args, options| report :d_before } after_action { |action, args, options| report :d_after } end parent.option "-b" do before_action { |action, args, options| report :b_before } after_action { |action, args, options| report :b_after } end child.action(:decorations) { when_invoked { |options| report :invoked } } child end it "should invoke all decorations when calling a child action" do subject.decorations(:a => 1, :b => 1, :c => 1, :d => 1) expect(subject.reported).to eq([ :a_before, :b_before, :c_before, :d_before, :invoked, :d_after, :c_after, :b_after, :a_after ]) end it "should invoke all decorations when calling a parent action" do subject.decorations(:a => 1, :b => 1, :c => 1, :d => 1) expect(subject.reported).to eq([ :a_before, :b_before, :c_before, :d_before, :invoked, :d_after, :c_after, :b_after, :a_after ]) end end end end it_should_behave_like "documentation on faces" do subject do face = Puppet::Interface.new(:action_documentation, '0.0.1') do action :documentation do when_invoked do |options| true end end end face.get_action(:documentation) end end context "#when_rendering" do it "should fail if no type is given when_rendering" it "should accept a when_rendering block" it "should accept multiple when_rendering blocks" it "should fail if when_rendering gets a non-symbol identifier" it "should fail if a second block is given for the same type" it "should return the block if asked" end context "#validate_and_clean" do subject do Puppet::Interface.new(:validate_args, '1.0.0') do action(:test) { when_invoked { |options| options } } end end it "should fail if a required option is not passed" do subject.option "--foo" do required end expect { subject.test }.to raise_error ArgumentError, /options are required/ end it "should fail if two aliases to one option are passed" do subject.option "--foo", "-f" expect { subject.test :foo => true, :f => true }. to raise_error ArgumentError, /Multiple aliases for the same option/ end it "should fail if an unknown option is passed" do expect { subject.test :unknown => true }. to raise_error ArgumentError, /Unknown options passed: unknown/ end it "should report all the unknown options passed" do expect { subject.test :unknown => true, :unseen => false }. to raise_error ArgumentError, /Unknown options passed: unknown, unseen/ end it "should accept 'global' options from settings" do expect { expect(subject.test(:certname => "true")).to eq({ :certname => "true" }) }.not_to raise_error end end context "default option values" do subject do Puppet::Interface.new(:default_option_values, '1.0.0') do action :foo do option "--foo" do end option "--bar" do end when_invoked do |options| options end end end end let :action do subject.get_action :foo end let :option do action.get_option :foo end it "should not add options without defaults" do expect(subject.foo).to eq({}) end it "should not add options without defaults, if options are given" do expect(subject.foo(:bar => 1)).to eq({ :bar => 1 }) end it "should add the option default value when set" do option.default = proc { 12 } expect(subject.foo).to eq({ :foo => 12 }) end it "should add the option default value when set, if other options are given" do option.default = proc { 12 } expect(subject.foo(:bar => 1)).to eq({ :foo => 12, :bar => 1 }) end it "should invoke the same default proc every time called" do option.default = proc { @foo ||= {} } expect(subject.foo[:foo].object_id).to eq(subject.foo[:foo].object_id) end [nil, 0, 1, true, false, {}, []].each do |input| it "should not override a passed option (#{input.inspect})" do option.default = proc { :fail } expect(subject.foo(:foo => input)).to eq({ :foo => input }) end end end context "runtime manipulations" do subject do Puppet::Interface.new(:runtime_manipulations, '1.0.0') do action :foo do when_invoked do |options| options end end end end let :action do subject.get_action :foo end it "should be the face default action if default is set true" do expect(subject.get_default_action).to be_nil action.default = true expect(subject.get_default_action).to eq(action) end end context "when deprecating a face action" do let :face do Puppet::Interface.new(:foo, '1.0.0') do action :bar do option "--bar" when_invoked do |options| options end end end end let :action do face.get_action :bar end describe "#deprecate" do it "should set the deprecated value to true" do expect(action).not_to be_deprecated action.deprecate expect(action).to be_deprecated end end describe "#deprecated?" do it "should return a nil (falsey) value by default" do expect(action.deprecated?).to be_falsey end it "should return true if the action has been deprecated" do expect(action).not_to be_deprecated action.deprecate expect(action).to be_deprecated end end end end puppet-5.5.10/spec/unit/interface/documentation_spec.rb0000644005276200011600000000146113417161721023075 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/interface' class Puppet::Interface::TinyDocs::Test include Puppet::Interface::TinyDocs attr_accessor :name, :options, :display_global_options def initialize self.name = "tinydoc-test" self.options = [] self.display_global_options = [] end def get_option(name) Puppet::Interface::Option.new(nil, "--#{name}") end end describe Puppet::Interface::TinyDocs do subject { Puppet::Interface::TinyDocs::Test.new } context "#build_synopsis" do before :each do subject.options = [:foo, :bar] end it { is_expected.to respond_to :build_synopsis } it "should put a space between options (#7828)" do expect(subject.build_synopsis('baz')).to match(/#{Regexp.quote('[--foo] [--bar]')}/) end end end puppet-5.5.10/spec/unit/interface/face_collection_spec.rb0000644005276200011600000001643513417161721023344 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'tmpdir' require 'puppet/interface' describe Puppet::Interface::FaceCollection do # To prevent conflicts with other specs that use faces, we must save and restore global state. # Because there are specs that do 'describe Puppet::Face[...]', we must restore the same objects otherwise # the 'subject' of the specs will differ. before :all do # Save FaceCollection's global state faces = described_class.instance_variable_get(:@faces) @faces = faces.dup faces.each do |k, v| @faces[k] = v.dup end @faces_loaded = described_class.instance_variable_get(:@loaded) # Save the already required face files @required = [] $".each do |path| @required << path if path =~ /face\/.*\.rb$/ end # Save Autoload's global state @loaded = Puppet::Util::Autoload.instance_variable_get(:@loaded).dup end after :all do # Restore global state described_class.instance_variable_set :@faces, @faces described_class.instance_variable_set :@loaded, @faces_loaded $".delete_if { |path| path =~ /face\/.*\.rb$/ } @required.each { |path| $".push path unless $".include? path } Puppet::Util::Autoload.instance_variable_set(:@loaded, @loaded) end before :each do # Before each test, clear the faces subject.instance_variable_get(:@faces).clear subject.instance_variable_set(:@loaded, false) Puppet::Util::Autoload.instance_variable_get(:@loaded).clear $".delete_if { |path| path =~ /face\/.*\.rb$/ } end describe "::[]" do before :each do subject.instance_variable_get("@faces")[:foo][SemanticPuppet::Version.parse('0.0.1')] = 10 end it "should return the face with the given name" do expect(subject["foo", '0.0.1']).to eq(10) end it "should attempt to load the face if it isn't found" do subject.expects(:require).once.with('puppet/face/bar') subject.expects(:require).once.with('puppet/face/0.0.1/bar') subject["bar", '0.0.1'] end it "should attempt to load the default face for the specified version :current" do subject.expects(:require).with('puppet/face/fozzie') subject['fozzie', :current] end it "should return true if the face specified is registered" do subject.instance_variable_get("@faces")[:foo][SemanticPuppet::Version.parse('0.0.1')] = 10 expect(subject["foo", '0.0.1']).to eq(10) end it "should attempt to require the face if it is not registered" do subject.expects(:require).with do |file| subject.instance_variable_get("@faces")[:bar][SemanticPuppet::Version.parse('0.0.1')] = true file == 'puppet/face/bar' end expect(subject["bar", '0.0.1']).to be_truthy end it "should return false if the face is not registered" do subject.stubs(:require).returns(true) expect(subject["bar", '0.0.1']).to be_falsey end it "should return false if the face file itself is missing" do subject.stubs(:require). raises(LoadError, 'no such file to load -- puppet/face/bar').then. raises(LoadError, 'no such file to load -- puppet/face/0.0.1/bar') expect(subject["bar", '0.0.1']).to be_falsey end it "should register the version loaded by `:current` as `:current`" do subject.expects(:require).with do |file| subject.instance_variable_get("@faces")[:huzzah]['2.0.1'] = :huzzah_face file == 'puppet/face/huzzah' end subject["huzzah", :current] expect(subject.instance_variable_get("@faces")[:huzzah][:current]).to eq(:huzzah_face) end context "with something on disk" do it "should register the version loaded from `puppet/face/{name}` as `:current`" do expect(subject["huzzah", '2.0.1']).to be expect(subject["huzzah", :current]).to be expect(Puppet::Face[:huzzah, '2.0.1']).to eq(Puppet::Face[:huzzah, :current]) end it "should index :current when the code was pre-required" do expect(subject.instance_variable_get("@faces")[:huzzah]).not_to be_key :current require 'puppet/face/huzzah' expect(subject[:huzzah, :current]).to be_truthy end end it "should not cause an invalid face to be enumerated later" do expect(subject[:there_is_no_face, :current]).to be_falsey expect(subject.faces).not_to include :there_is_no_face end end describe "::get_action_for_face" do it "should return an action on the current face" do expect(Puppet::Face::FaceCollection.get_action_for_face(:huzzah, :bar, :current)). to be_an_instance_of Puppet::Interface::Action end it "should return an action on an older version of a face" do action = Puppet::Face::FaceCollection. get_action_for_face(:huzzah, :obsolete, :current) expect(action).to be_an_instance_of Puppet::Interface::Action expect(action.face.version).to eq(SemanticPuppet::Version.parse('1.0.0')) end it "should load the full older version of a face" do action = Puppet::Face::FaceCollection. get_action_for_face(:huzzah, :obsolete, :current) expect(action.face.version).to eq(SemanticPuppet::Version.parse('1.0.0')) expect(action.face).to be_action :obsolete_in_core end it "should not add obsolete actions to the current version" do action = Puppet::Face::FaceCollection. get_action_for_face(:huzzah, :obsolete, :current) expect(action.face.version).to eq(SemanticPuppet::Version.parse('1.0.0')) expect(action.face).to be_action :obsolete_in_core current = Puppet::Face[:huzzah, :current] expect(current.version).to eq(SemanticPuppet::Version.parse('2.0.1')) expect(current).not_to be_action :obsolete_in_core expect(current).not_to be_action :obsolete end end describe "::register" do it "should store the face by name" do face = Puppet::Face.new(:my_face, '0.0.1') subject.register(face) expect(subject.instance_variable_get("@faces")).to eq({ :my_face => { face.version => face } }) end end describe "::underscorize" do faulty = [1, "23foo", "#foo", "$bar", "sturm und drang", :"sturm und drang"] valid = { "Foo" => :foo, :Foo => :foo, "foo_bar" => :foo_bar, :foo_bar => :foo_bar, "foo-bar" => :foo_bar, :"foo-bar" => :foo_bar, "foo_bar23" => :foo_bar23, :foo_bar23 => :foo_bar23, } valid.each do |input, expect| it "should map #{input.inspect} to #{expect.inspect}" do result = subject.underscorize(input) expect(result).to eq(expect) end end faulty.each do |input| it "should fail when presented with #{input.inspect} (#{input.class})" do expect { subject.underscorize(input) }. to raise_error ArgumentError, /not a valid face name/ end end end context "faulty faces" do before :each do $:.unshift "#{PuppetSpec::FIXTURE_DIR}/faulty_face" end after :each do $:.delete_if {|x| x == "#{PuppetSpec::FIXTURE_DIR}/faulty_face"} end it "should not die if a face has a syntax error" do expect(subject.faces).to be_include :help expect(subject.faces).not_to be_include :syntax expect(@logs).not_to be_empty expect(@logs.first.message).to match(/syntax error/) end end end puppet-5.5.10/spec/unit/interface/option_builder_spec.rb0000644005276200011600000000536513417161721023251 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/interface' describe Puppet::Interface::OptionBuilder do let :face do Puppet::Interface.new(:option_builder_testing, '0.0.1') end it "should be able to construct an option without a block" do expect(Puppet::Interface::OptionBuilder.build(face, "--foo")). to be_an_instance_of Puppet::Interface::Option end Puppet.settings.each do |name, value| it "should fail when option #{name.inspect} already exists in puppet core" do expect do Puppet::Interface::OptionBuilder.build(face, "--#{name}") end.to raise_error ArgumentError, /already defined/ end end it "should work with an empty block" do option = Puppet::Interface::OptionBuilder.build(face, "--foo") do # This block deliberately left blank. end expect(option).to be_an_instance_of Puppet::Interface::Option end [:description, :summary].each do |doc| it "should support #{doc} declarations" do text = "this is the #{doc}" option = Puppet::Interface::OptionBuilder.build(face, "--foo") do self.send doc, text end expect(option).to be_an_instance_of Puppet::Interface::Option expect(option.send(doc)).to eq(text) end end context "before_action hook" do it "should support a before_action hook" do option = Puppet::Interface::OptionBuilder.build(face, "--foo") do before_action do |a,b,c| :whatever end end expect(option.before_action).to be_an_instance_of UnboundMethod end it "should fail if the hook block takes too few arguments" do expect do Puppet::Interface::OptionBuilder.build(face, "--foo") do before_action do |one, two| true end end end.to raise_error ArgumentError, /takes three arguments/ end it "should fail if the hook block takes too many arguments" do expect do Puppet::Interface::OptionBuilder.build(face, "--foo") do before_action do |one, two, three, four| true end end end.to raise_error ArgumentError, /takes three arguments/ end it "should fail if the hook block takes a variable number of arguments" do expect do Puppet::Interface::OptionBuilder.build(face, "--foo") do before_action do |*blah| true end end end.to raise_error ArgumentError, /takes three arguments/ end it "should support simple required declarations" do opt = Puppet::Interface::OptionBuilder.build(face, "--foo") do required end expect(opt).to be_required end it "should support arguments to the required property" do opt = Puppet::Interface::OptionBuilder.build(face, "--foo") do required(false) end expect(opt).not_to be_required end end end puppet-5.5.10/spec/unit/interface/option_spec.rb0000644005276200011600000001210313417161721021527 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/interface' describe Puppet::Interface::Option do let :face do Puppet::Interface.new(:option_testing, '0.0.1') end describe "#optparse_to_name" do ["", "=BAR", " BAR", "=bar", " bar"].each do |postfix| { "--foo" => :foo, "-f" => :f }.each do |base, expect| input = base + postfix it "should map #{input.inspect} to #{expect.inspect}" do option = Puppet::Interface::Option.new(face, input) expect(option.name).to eq(expect) end end end [:foo, 12, nil, {}, []].each do |input| it "should fail sensible when given #{input.inspect}" do expect { Puppet::Interface::Option.new(face, input) }.to raise_error ArgumentError, /is not valid for an option argument/ end end ["-foo", "-foo=BAR", "-foo BAR"].each do |input| it "should fail with a single dash for long option #{input.inspect}" do expect { Puppet::Interface::Option.new(face, input) }.to raise_error ArgumentError, /long options need two dashes \(--\)/ end end end it "requires a face when created" do expect { Puppet::Interface::Option.new }.to raise_error ArgumentError, /wrong number of arguments/ end it "also requires some declaration arguments when created" do expect { Puppet::Interface::Option.new(face) }.to raise_error ArgumentError, /No option declarations found/ end it "should infer the name from an optparse string" do option = Puppet::Interface::Option.new(face, "--foo") expect(option.name).to eq(:foo) end it "should infer the name when multiple optparse string are given" do option = Puppet::Interface::Option.new(face, "--foo", "-f") expect(option.name).to eq(:foo) end it "should prefer the first long option name over a short option name" do option = Puppet::Interface::Option.new(face, "-f", "--foo") expect(option.name).to eq(:foo) end it "should create an instance when given a face and name" do expect(Puppet::Interface::Option.new(face, "--foo")). to be_instance_of Puppet::Interface::Option end Puppet.settings.each do |name, value| it "should fail when option #{name.inspect} already exists in puppet core" do expect do Puppet::Interface::Option.new(face, "--#{name}") end.to raise_error ArgumentError, /already defined/ end end describe "#to_s" do it "should transform a symbol into a string" do option = Puppet::Interface::Option.new(face, "--foo") expect(option.name).to eq(:foo) expect(option.to_s).to eq("foo") end it "should use - rather than _ to separate words in strings but not symbols" do option = Puppet::Interface::Option.new(face, "--foo-bar") expect(option.name).to eq(:foo_bar) expect(option.to_s).to eq("foo-bar") end end %w{before after}.each do |side| describe "#{side} hooks" do subject { Puppet::Interface::Option.new(face, "--foo") } let :proc do Proc.new do :from_proc end end it { is_expected.to respond_to "#{side}_action" } it { is_expected.to respond_to "#{side}_action=" } it "should set the #{side}_action hook" do expect(subject.send("#{side}_action")).to be_nil subject.send("#{side}_action=", proc) expect(subject.send("#{side}_action")).to be_an_instance_of UnboundMethod end data = [1, "foo", :foo, Object.new, method(:hash), method(:hash).unbind] data.each do |input| it "should fail if a #{input.class} is added to the #{side} hooks" do expect { subject.send("#{side}_action=", input) }. to raise_error ArgumentError, /not a proc/ end end end end context "defaults" do subject { Puppet::Interface::Option.new(face, "--foo") } it "should work sanely if member variables are used for state" do subject.default = proc { @foo ||= 0; @foo += 1 } expect(subject.default).to eq(1) expect(subject.default).to eq(2) expect(subject.default).to eq(3) end context "with no default" do it { is_expected.not_to be_has_default } its :default do should be_nil end it "should set a proc as default" do expect { subject.default = proc { 12 } }.to_not raise_error end [1, {}, [], Object.new, "foo"].each do |input| it "should reject anything but a proc (#{input.class})" do expect { subject.default = input }.to raise_error ArgumentError, /not a proc/ end end end context "with a default" do before :each do subject.default = proc { [:foo] } end it { is_expected.to be_has_default } its :default do should == [:foo] end it "should invoke the block every time" do expect(subject.default.object_id).not_to eq(subject.default.object_id) expect(subject.default).to eq(subject.default) end it "should allow replacing the default proc" do expect(subject.default).to eq([:foo]) subject.default = proc { :bar } expect(subject.default).to eq(:bar) end end end end puppet-5.5.10/spec/unit/interface_spec.rb0000644005276200011600000002273313417161721020231 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/face' require 'puppet/interface' describe Puppet::Interface do subject { Puppet::Interface } before :each do @faces = Puppet::Interface::FaceCollection. instance_variable_get("@faces").dup @dq = $".dup $".delete_if do |path| path =~ %r{/face/.*\.rb$} end Puppet::Interface::FaceCollection.instance_variable_get("@faces").clear end after :each do Puppet::Interface::FaceCollection.instance_variable_set("@faces", @faces) $".clear ; @dq.each do |item| $" << item end end describe "#[]" do it "should fail when no version is requested" do expect { subject[:huzzah] }.to raise_error ArgumentError end it "should raise an exception when the requested version is unavailable" do expect { subject[:huzzah, '17.0.0'] }.to raise_error(Puppet::Error, /Could not find version/) end it "should raise an exception when the requested face doesn't exist" do expect { subject[:burrble_toot, :current] }.to raise_error(Puppet::Error, /Could not find Puppet Face/) end describe "version matching" do { '1' => '1.1.1', '1.0' => '1.0.1', '1.0.1' => '1.0.1', '1.1' => '1.1.1', '1.1.1' => '1.1.1' }.each do |input, expect| it "should match #{input.inspect} to #{expect.inspect}" do face = subject[:version_matching, input] expect(face).to be expect(face.version).to eq(expect) end end %w{1.0.2 1.2}.each do |input| it "should not match #{input.inspect} to any version" do expect { subject[:version_matching, input] }. to raise_error Puppet::Error, /Could not find version/ end end end end describe "#define" do it "should register the face" do face = subject.define(:face_test_register, '0.0.1') expect(face).to eq(subject[:face_test_register, '0.0.1']) end it "should load actions" do subject.any_instance.expects(:load_actions) subject.define(:face_test_load_actions, '0.0.1') end it "should require a version number" do expect { subject.define(:no_version) }.to raise_error ArgumentError end it "should support summary builder and accessor methods" do expect(subject.new(:foo, '1.0.0')).to respond_to(:summary).with(0).arguments expect(subject.new(:foo, '1.0.0')).to respond_to(:summary=).with(1).arguments end # Required documentation methods... { :summary => "summary", :description => "This is the description of the stuff\n\nWhee", :examples => "This is my example", :short_description => "This is my custom short description", :notes => "These are my notes...", :author => "This is my authorship data", }.each do |attr, value| it "should support #{attr} in the builder" do face = subject.new(:builder, '1.0.0') do self.send(attr, value) end expect(face.send(attr)).to eq(value) end end end describe "#initialize" do it "should require a version number" do expect { subject.new(:no_version) }.to raise_error ArgumentError end it "should require a valid version number" do expect { subject.new(:bad_version, 'Rasins') }. to raise_error ArgumentError end it "should instance-eval any provided block" do face = subject.new(:face_test_block, '0.0.1') do action(:something) do when_invoked {|_| "foo" } end end expect(face.something).to eq("foo") end end it "should have a name" do expect(subject.new(:me, '0.0.1').name).to eq(:me) end it "should stringify with its own name" do expect(subject.new(:me, '0.0.1').to_s).to match(/\bme\b/) end it "should try to require faces that are not known" do subject::FaceCollection.expects(:load_face).with(:foo, :current) subject::FaceCollection.expects(:load_face).with(:foo, '0.0.1') expect { subject[:foo, '0.0.1'] }.to raise_error Puppet::Error end describe 'when raising NoMethodErrors' do subject { described_class.new(:foo, '1.0.0') } it 'includes the face name in the error message' do expect { subject.boombaz }.to raise_error(NoMethodError, /#{subject.name}/) end it 'includes the face version in the error message' do expect { subject.boombaz }.to raise_error(NoMethodError, /#{subject.version}/) end end it_should_behave_like "things that declare options" do def add_options_to(&block) subject.new(:with_options, '0.0.1', &block) end end context "when deprecating a face" do let(:face) { subject.new(:foo, '0.0.1') } describe "#deprecate" do it "should respond to #deprecate" do expect(subject.new(:foo, '0.0.1')).to respond_to(:deprecate) end it "should set the deprecated value to true" do expect(face.deprecated?).to be_falsey face.deprecate expect(face.deprecated?).to be_truthy end end describe "#deprecated?" do it "should return a nil (falsey) value by default" do expect(face.deprecated?).to be_falsey end it "should return true if the face has been deprecated" do expect(face.deprecated?).to be_falsey face.deprecate expect(face.deprecated?).to be_truthy end end end describe "with face-level display_global_options" do it "should not return any action level display_global_options" do face = subject.new(:with_display_global_options, '0.0.1') do display_global_options "environment" action :baz do when_invoked {|_| true } display_global_options "modulepath" end end face.display_global_options =~ ["environment"] end it "should not fail when a face d_g_o duplicates an action d_g_o" do expect { subject.new(:action_level_display_global_options, '0.0.1') do action :bar do when_invoked {|_| true } display_global_options "environment" end display_global_options "environment" end }.to_not raise_error end it "should work when two actions have the same d_g_o" do face = subject.new(:with_display_global_options, '0.0.1') do action :foo do when_invoked {|_| true} ; display_global_options "environment" end action :bar do when_invoked {|_| true} ; display_global_options "environment" end end face.get_action(:foo).display_global_options =~ ["environment"] face.get_action(:bar).display_global_options =~ ["environment"] end end describe "with inherited display_global_options" do end describe "with face-level options" do it "should not return any action-level options" do face = subject.new(:with_options, '0.0.1') do option "--foo" option "--bar" action :baz do when_invoked {|_| true } option "--quux" end end expect(face.options).to match_array([:foo, :bar]) end it "should fail when a face option duplicates an action option" do expect { subject.new(:action_level_options, '0.0.1') do action :bar do when_invoked {|_| true } option "--foo" end option "--foo" end }.to raise_error ArgumentError, /Option foo conflicts with existing option foo on/i end it "should work when two actions have the same option" do face = subject.new(:with_options, '0.0.1') do action :foo do when_invoked {|_| true } ; option "--quux" end action :bar do when_invoked {|_| true } ; option "--quux" end end expect(face.get_action(:foo).options).to match_array([:quux]) expect(face.get_action(:bar).options).to match_array([:quux]) end it "should only list options and not aliases" do face = subject.new(:face_options, '0.0.1') do option "--bar", "-b", "--foo-bar" end expect(face.options).to match_array([:bar]) end end describe "with inherited options" do let :parent do parent = Class.new(subject) parent.option("--inherited") parent.action(:parent_action) do when_invoked {|_| true } end parent end let :face do face = parent.new(:example, '0.2.1') face.option("--local") face.action(:face_action) do when_invoked {|_| true } end face end describe "#options" do it "should list inherited options" do expect(face.options).to match_array([:inherited, :local]) end it "should see all options on face actions" do expect(face.get_action(:face_action).options).to match_array([:inherited, :local]) end it "should see all options on inherited actions accessed on the subclass" do expect(face.get_action(:parent_action).options).to match_array([:inherited, :local]) end it "should not see subclass actions on the parent class" do expect(parent.options).to match_array([:inherited]) end it "should not see subclass actions on actions accessed on the parent class" do expect(parent.get_action(:parent_action).options).to match_array([:inherited]) end end describe "#get_option" do it "should return an inherited option object" do expect(face.get_option(:inherited)).to be_an_instance_of subject::Option end end end it_should_behave_like "documentation on faces" do subject do Puppet::Interface.new(:face_documentation, '0.0.1') end end end puppet-5.5.10/spec/unit/man_spec.rb0000644005276200011600000000165513417161721017044 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/face' describe Puppet::Face[:man, :current] do let(:pager) { '/path/to/our/pager' } around do |example| oldpager = ENV['MANPAGER'] ENV['MANPAGER'] = pager example.run ENV['MANPAGER'] = oldpager end it "exits with 0 when generating man documentation for each available application" do Puppet::Util.stubs(:which).with('ronn').returns(nil) Puppet::Util.stubs(:which).with(pager).returns(pager) Puppet::Application.available_application_names.each do |name| next if %w{man face_base indirection_base}.include? name klass = Puppet::Application.find('man') app = klass.new(Puppet::Util::CommandLine.new('puppet', ['man', name])) expect do IO.stubs(:popen).with(pager, 'w:UTF-8').yields($stdout) expect { app.run }.to exit_with(0) end.to_not have_printed(/undefined method `gsub'/) end end end puppet-5.5.10/spec/unit/module_tool/0000755005276200011600000000000013417162176017252 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/module_tool/application_spec.rb0000644005276200011600000000144613417161721023114 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/module_tool' describe Puppet::ModuleTool::Applications::Application do describe 'app' do good_versions = %w{ 1.2.4 0.0.1 0.0.0 0.0.2-git-8-g3d316d1 0.0.3-b1 10.100.10000 0.1.2-rc1 0.1.2-dev-1 0.1.2-svn12345 0.1.2-3 } bad_versions = %w{ 0.1 0 0.1.2.3 dev 0.1.2beta } let :app do Class.new(described_class).new end good_versions.each do |ver| it "should accept version string #{ver}" do app.parse_filename("puppetlabs-ntp-#{ver}") end end bad_versions.each do |ver| it "should not accept version string #{ver}" do expect { app.parse_filename("puppetlabs-ntp-#{ver}") }.to raise_error(ArgumentError, /(Invalid version format|Could not parse filename)/) end end end end puppet-5.5.10/spec/unit/module_tool/applications/0000755005276200011600000000000013417162176021740 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/module_tool/applications/checksummer_spec.rb0000644005276200011600000000734313417161721025607 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/module_tool/applications' require 'puppet_spec/files' require 'pathname' describe Puppet::ModuleTool::Applications::Checksummer do let(:tmpdir) do Pathname.new(PuppetSpec::Files.tmpdir('checksummer')) end let(:checksums) { Puppet::ModuleTool::Checksums.new(tmpdir).data } subject do described_class.run(tmpdir) end before do File.open(tmpdir + 'README', 'w') { |f| f.puts "This is a README!" } File.open(tmpdir + 'CHANGES', 'w') { |f| f.puts "This is a changelog!" } File.open(tmpdir + 'DELETEME', 'w') { |f| f.puts "I've got a really good feeling about this!" } Dir.mkdir(tmpdir + 'pkg') File.open(tmpdir + 'pkg' + 'build-artifact', 'w') { |f| f.puts "I'm unimportant!" } File.open(tmpdir + 'metadata.json', 'w') { |f| f.puts '{"name": "package-name", "version": "1.0.0"}' } File.open(tmpdir + 'checksums.json', 'w') { |f| f.puts '{}' } end context 'with checksums.json' do before do File.open(tmpdir + 'checksums.json', 'w') { |f| f.puts checksums.to_json } File.open(tmpdir + 'CHANGES', 'w') { |f| f.puts "This is a changed log!" } File.open(tmpdir + 'pkg' + 'build-artifact', 'w') { |f| f.puts "I'm still unimportant!" } (tmpdir + 'DELETEME').unlink end it 'reports changed files' do expect(subject).to include 'CHANGES' end it 'reports removed files' do expect(subject).to include 'DELETEME' end it 'does not report unchanged files' do expect(subject).to_not include 'README' end it 'does not report build artifacts' do expect(subject).to_not include 'pkg/build-artifact' end it 'does not report checksums.json' do expect(subject).to_not include 'checksums.json' end end context 'without checksums.json' do context 'but with metadata.json containing checksums' do before do (tmpdir + 'checksums.json').unlink File.open(tmpdir + 'metadata.json', 'w') { |f| f.puts "{\"checksums\":#{checksums.to_json}}" } File.open(tmpdir + 'CHANGES', 'w') { |f| f.puts "This is a changed log!" } File.open(tmpdir + 'pkg' + 'build-artifact', 'w') { |f| f.puts "I'm still unimportant!" } (tmpdir + 'DELETEME').unlink end it 'reports changed files' do expect(subject).to include 'CHANGES' end it 'reports removed files' do expect(subject).to include 'DELETEME' end it 'does not report unchanged files' do expect(subject).to_not include 'README' end it 'does not report build artifacts' do expect(subject).to_not include 'pkg/build-artifact' end it 'does not report checksums.json' do expect(subject).to_not include 'checksums.json' end end context 'and with metadata.json that does not contain checksums' do before do (tmpdir + 'checksums.json').unlink File.open(tmpdir + 'CHANGES', 'w') { |f| f.puts "This is a changed log!" } File.open(tmpdir + 'pkg' + 'build-artifact', 'w') { |f| f.puts "I'm still unimportant!" } (tmpdir + 'DELETEME').unlink end it 'fails' do expect { subject }.to raise_error(ArgumentError, 'No file containing checksums found.') end end context 'and without metadata.json' do before do (tmpdir + 'checksums.json').unlink (tmpdir + 'metadata.json').unlink File.open(tmpdir + 'CHANGES', 'w') { |f| f.puts "This is a changed log!" } File.open(tmpdir + 'pkg' + 'build-artifact', 'w') { |f| f.puts "I'm still unimportant!" } (tmpdir + 'DELETEME').unlink end it 'fails' do expect { subject }.to raise_error(ArgumentError, 'No file containing checksums found.') end end end end puppet-5.5.10/spec/unit/module_tool/applications/searcher_spec.rb0000644005276200011600000000213113417161721025063 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/module_tool/applications' require 'puppet_spec/modules' describe Puppet::ModuleTool::Applications::Searcher do include PuppetSpec::Files describe "when searching" do let(:forge) { mock 'forge', :host => 'http://nowhe.re' } let(:searcher) do described_class.new('search_term', forge) end it "should return results from a forge query when successful" do results = 'mock results' forge.expects(:search).with('search_term').returns(results) search_result = searcher.run expect(search_result).to eq({ :result => :success, :answers => results, }) end it "should return an error when the forge query throws an exception" do forge.expects(:search).with('search_term').raises Puppet::Forge::Errors::ForgeError.new("something went wrong") search_result = searcher.run expect(search_result).to eq({ :result => :failure, :error => { :oneline => 'something went wrong', :multiline => 'something went wrong', }, }) end end end puppet-5.5.10/spec/unit/module_tool/applications/uninstaller_spec.rb0000644005276200011600000001162313417161721025635 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/module_tool' require 'tmpdir' require 'puppet_spec/module_tool/shared_functions' require 'puppet_spec/module_tool/stub_source' describe Puppet::ModuleTool::Applications::Uninstaller do include PuppetSpec::ModuleTool::SharedFunctions include PuppetSpec::Files before do FileUtils.mkdir_p(primary_dir) FileUtils.mkdir_p(secondary_dir) end let(:environment) do Puppet.lookup(:current_environment).override_with( :vardir => vardir, :modulepath => [ primary_dir, secondary_dir ] ) end let(:vardir) { tmpdir('uninstaller') } let(:primary_dir) { File.join(vardir, "primary") } let(:secondary_dir) { File.join(vardir, "secondary") } let(:remote_source) { PuppetSpec::ModuleTool::StubSource.new } let(:module) { 'module-not_installed' } let(:application) do opts = options Puppet::ModuleTool.set_option_defaults(opts) Puppet::ModuleTool::Applications::Uninstaller.new(self.module, opts) end def options { :environment => environment } end subject { application.run } context "when the module is not installed" do it "should fail" do expect(subject).to include :result => :failure end end context "when the module is installed" do let(:module) { 'pmtacceptance-stdlib' } before { preinstall('pmtacceptance-stdlib', '1.0.0') } before { preinstall('pmtacceptance-apache', '0.0.4') } it "should uninstall the module" do expect(subject[:affected_modules].first.forge_name).to eq("pmtacceptance/stdlib") end it "should only uninstall the requested module" do subject[:affected_modules].length == 1 end context 'in two modulepaths' do before { preinstall('pmtacceptance-stdlib', '2.0.0', :into => secondary_dir) } it "should fail if a module exists twice in the modpath" do expect(subject).to include :result => :failure end end context "when options[:version] is specified" do def options super.merge(:version => '1.0.0') end it "should uninstall the module if the version matches" do expect(subject[:affected_modules].length).to eq(1) expect(subject[:affected_modules].first.version).to eq("1.0.0") end context 'but not matched' do def options super.merge(:version => '2.0.0') end it "should not uninstall the module if the version does not match" do expect(subject).to include :result => :failure end end end context "when the module metadata is missing" do before { File.unlink(File.join(primary_dir, 'stdlib', 'metadata.json')) } it "should not uninstall the module" do expect(application.run[:result]).to eq(:failure) end end context "when the module has local changes" do before do mark_changed(File.join(primary_dir, 'stdlib')) end it "should not uninstall the module" do expect(subject).to include :result => :failure end end context "when uninstalling the module will cause broken dependencies" do before { preinstall('pmtacceptance-apache', '0.10.0') } it "should not uninstall the module" do expect(subject).to include :result => :failure end end context 'with --ignore-changes' do def options super.merge(:ignore_changes => true) end context 'with local changes' do before do mark_changed(File.join(primary_dir, 'stdlib')) end it 'overwrites the installed module with the greatest version matching that range' do expect(subject).to include :result => :success end end context 'without local changes' do it 'overwrites the installed module with the greatest version matching that range' do expect(subject).to include :result => :success end end end context "when using the --force flag" do def options super.merge(:force => true) end context "with local changes" do before do mark_changed(File.join(primary_dir, 'stdlib')) end it "should ignore local changes" do expect(subject[:affected_modules].length).to eq(1) expect(subject[:affected_modules].first.forge_name).to eq("pmtacceptance/stdlib") end end context "while depended upon" do before { preinstall('pmtacceptance-apache', '0.10.0') } it "should ignore broken dependencies" do expect(subject[:affected_modules].length).to eq(1) expect(subject[:affected_modules].first.forge_name).to eq("pmtacceptance/stdlib") end end end end context 'when in FIPS mode...' do it 'module uninstaller refuses to run' do Facter.stubs(:value).with(:fips_enabled).returns(true) expect {application.run}.to raise_error(/Module uninstall is prohibited in FIPS mode/) end end end puppet-5.5.10/spec/unit/module_tool/applications/unpacker_spec.rb0000644005276200011600000000610213417161721025101 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/util/json' require 'puppet/module_tool/applications' require 'puppet/file_system' require 'puppet_spec/modules' describe Puppet::ModuleTool::Applications::Unpacker do include PuppetSpec::Files let(:target) { tmpdir("unpacker") } let(:module_name) { 'myusername-mytarball' } let(:filename) { tmpdir("module") + "/module.tar.gz" } let(:working_dir) { tmpdir("working_dir") } before :each do Puppet.settings.stubs(:[]) Puppet.settings.stubs(:[]).with(:module_working_dir).returns(working_dir) end it "should attempt to untar file to temporary location" do untar = mock('Tar') untar.expects(:unpack).with(filename, anything()) do |src, dest, _| FileUtils.mkdir(File.join(dest, 'extractedmodule')) File.open(File.join(dest, 'extractedmodule', 'metadata.json'), 'w+') do |file| file.puts Puppet::Util::Json.dump('name' => module_name, 'version' => '1.0.0') end true end Puppet::ModuleTool::Tar.expects(:instance).returns(untar) Puppet::ModuleTool::Applications::Unpacker.run(filename, :target_dir => target) expect(File).to be_directory(File.join(target, 'mytarball')) end it "should warn about symlinks", :if => Puppet.features.manages_symlinks? do untar = mock('Tar') untar.expects(:unpack).with(filename, anything()) do |src, dest, _| FileUtils.mkdir(File.join(dest, 'extractedmodule')) File.open(File.join(dest, 'extractedmodule', 'metadata.json'), 'w+') do |file| file.puts Puppet::Util::Json.dump('name' => module_name, 'version' => '1.0.0') end FileUtils.touch(File.join(dest, 'extractedmodule/tempfile')) Puppet::FileSystem.symlink(File.join(dest, 'extractedmodule/tempfile'), File.join(dest, 'extractedmodule/tempfile2')) true end Puppet::ModuleTool::Tar.expects(:instance).returns(untar) Puppet.expects(:warning).with(regexp_matches(/symlinks/i)) Puppet::ModuleTool::Applications::Unpacker.run(filename, :target_dir => target) expect(File).to be_directory(File.join(target, 'mytarball')) end it "should warn about symlinks in subdirectories", :if => Puppet.features.manages_symlinks? do untar = mock('Tar') untar.expects(:unpack).with(filename, anything()) do |src, dest, _| FileUtils.mkdir(File.join(dest, 'extractedmodule')) File.open(File.join(dest, 'extractedmodule', 'metadata.json'), 'w+') do |file| file.puts Puppet::Util::Json.dump('name' => module_name, 'version' => '1.0.0') end FileUtils.mkdir(File.join(dest, 'extractedmodule/manifests')) FileUtils.touch(File.join(dest, 'extractedmodule/manifests/tempfile')) Puppet::FileSystem.symlink(File.join(dest, 'extractedmodule/manifests/tempfile'), File.join(dest, 'extractedmodule/manifests/tempfile2')) true end Puppet::ModuleTool::Tar.expects(:instance).returns(untar) Puppet.expects(:warning).with(regexp_matches(/symlinks/i)) Puppet::ModuleTool::Applications::Unpacker.run(filename, :target_dir => target) expect(File).to be_directory(File.join(target, 'mytarball')) end end puppet-5.5.10/spec/unit/module_tool/applications/builder_spec.rb0000644005276200011600000003423413417161722024727 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/file_system' require 'puppet/module_tool/applications' require 'puppet_spec/modules' describe Puppet::ModuleTool::Applications::Builder do include PuppetSpec::Files let(:path) { tmpdir("working_dir") } let(:module_name) { 'mymodule-mytarball' } let(:version) { '0.0.1' } let(:release_name) { "#{module_name}-#{version}" } let(:tarball) { File.join(path, 'pkg', release_name) + ".tar.gz" } let(:builder) { Puppet::ModuleTool::Applications::Builder.new(path) } shared_examples "a packagable module" do def target_exists?(file) File.exist?(File.join(path, "pkg", "#{module_name}-#{version}", file)) end def build tarrer = mock('tarrer') Puppet::ModuleTool::Tar.expects(:instance).returns(tarrer) Dir.expects(:chdir).with(File.join(path, 'pkg')).yields tarrer.expects(:pack).with(release_name, tarball) builder.run end def create_regular_files Puppet::FileSystem.touch(File.join(path, '.dotfile')) Puppet::FileSystem.touch(File.join(path, 'file.foo')) Puppet::FileSystem.touch(File.join(path, 'REVISION')) Puppet::FileSystem.touch(File.join(path, '~file')) Puppet::FileSystem.touch(File.join(path, '#file')) Puppet::FileSystem.mkpath(File.join(path, 'pkg')) Puppet::FileSystem.mkpath(File.join(path, 'coverage')) Puppet::FileSystem.mkpath(File.join(path, 'sub')) Puppet::FileSystem.touch(File.join(path, 'sub/.dotfile')) Puppet::FileSystem.touch(File.join(path, 'sub/file.foo')) Puppet::FileSystem.touch(File.join(path, 'sub/REVISION')) Puppet::FileSystem.touch(File.join(path, 'sub/~file')) Puppet::FileSystem.touch(File.join(path, 'sub/#file')) Puppet::FileSystem.mkpath(File.join(path, 'sub/pkg')) Puppet::FileSystem.mkpath(File.join(path, 'sub/coverage')) end def create_symlinks Puppet::FileSystem.touch(File.join(path, 'symlinkedfile')) Puppet::FileSystem.symlink(File.join(path, 'symlinkedfile'), File.join(path, 'symlinkfile')) end def create_ignored_files Puppet::FileSystem.touch(File.join(path, 'gitignored.foo')) Puppet::FileSystem.mkpath(File.join(path, 'gitdirectory/sub')) Puppet::FileSystem.touch(File.join(path, 'gitdirectory/gitartifact')) Puppet::FileSystem.touch(File.join(path, 'gitdirectory/gitimportantfile')) Puppet::FileSystem.touch(File.join(path, 'gitdirectory/sub/artifact')) Puppet::FileSystem.touch(File.join(path, "git\u16A0\u16C7\u16BB")) Puppet::FileSystem.touch(File.join(path, 'pmtignored.foo')) Puppet::FileSystem.mkpath(File.join(path, 'pmtdirectory/sub')) Puppet::FileSystem.touch(File.join(path, 'pmtdirectory/pmtimportantfile')) Puppet::FileSystem.touch(File.join(path, 'pmtdirectory/pmtartifact')) Puppet::FileSystem.touch(File.join(path, 'pmtdirectory/sub/artifact')) Puppet::FileSystem.touch(File.join(path, "pmt\u16A0\u16C7\u16BB")) end def create_pmtignore_file File.open(File.join(path, '.pmtignore'), 'w', 0600, :encoding => 'utf-8') do |f| f << <<-PMTIGNORE pmtignored.* pmtdirectory/sub/** pmtdirectory/pmt* !pmtimportantfile pmt\u16A0\u16C7\u16BB PMTIGNORE end end def create_gitignore_file File.open(File.join(path, '.gitignore'), 'w', 0600, :encoding => 'utf-8') do |f| f << <<-GITIGNORE gitignored.* gitdirectory/sub/** gitdirectory/git* !gitimportantfile git\u16A0\u16C7\u16BB GITIGNORE end end def create_symlink_gitignore_file File.open(File.join(path, '.gitignore'), 'w', 0600, :encoding => 'utf-8') do |f| f << <<-GITIGNORE symlinkfile GITIGNORE end end shared_examples "regular files are present" do it "has metadata" do expect(target_exists?('metadata.json')).to eq true end it "has checksums" do expect(target_exists?('checksums.json')).to eq true end it "copies regular files" do expect(target_exists?('file.foo')).to eq true end end shared_examples "default artifacts are removed in module dir but not in subdirs" do it "ignores dotfiles" do expect(target_exists?('.dotfile')).to eq false expect(target_exists?('sub/.dotfile')).to eq true end it "does not have .gitignore" do expect(target_exists?('.gitignore')).to eq false end it "does not have .pmtignore" do expect(target_exists?('.pmtignore')).to eq false end it "does not have pkg" do expect(target_exists?('pkg')).to eq false expect(target_exists?('sub/pkg')).to eq true end it "does not have coverage" do expect(target_exists?('coverage')).to eq false expect(target_exists?('sub/coverage')).to eq true end it "does not have REVISION" do expect(target_exists?('REVISION')).to eq false expect(target_exists?('sub/REVISION')).to eq true end it "does not have ~files" do expect(target_exists?('~file')).to eq false expect(target_exists?('sub/~file')).to eq true end it "does not have #files" do expect(target_exists?('#file')).to eq false expect(target_exists?('sub/#file')).to eq true end end shared_examples "gitignored files are present" do it "leaves regular files" do expect(target_exists?('gitignored.foo')).to eq true end it "leaves UTF-8 files" do expect(target_exists?("git\u16A0\u16C7\u16BB")).to eq true end it "leaves directories" do expect(target_exists?('gitdirectory')).to eq true end it "leaves files in directories" do expect(target_exists?('gitdirectory/gitartifact')).to eq true end it "leaves exceptional files" do expect(target_exists?('gitdirectory/gitimportantfile')).to eq true end it "leaves subdirectories" do expect(target_exists?('gitdirectory/sub')).to eq true end it "leaves files in subdirectories" do expect(target_exists?('gitdirectory/sub/artifact')).to eq true end end shared_examples "gitignored files are not present" do it "ignores regular files" do expect(target_exists?('gitignored.foo')).to eq false end it "ignores UTF-8 files" do expect(target_exists?("git\u16A0\u16C7\u16BB")).to eq false end it "ignores directories" do expect(target_exists?('gitdirectory')).to eq true end it "ignores files in directories" do expect(target_exists?('gitdirectory/gitartifact')).to eq false end it "copies exceptional files" do expect(target_exists?('gitdirectory/gitimportantfile')).to eq true end it "ignores subdirectories" do expect(target_exists?('gitdirectory/sub')).to eq false end it "ignores files in subdirectories" do expect(target_exists?('gitdirectory/sub/artifact')).to eq false end end shared_examples "pmtignored files are present" do it "leaves regular files" do expect(target_exists?('pmtignored.foo')).to eq true end it "leaves UTF-8 files" do expect(target_exists?("pmt\u16A0\u16C7\u16BB")).to eq true end it "leaves directories" do expect(target_exists?('pmtdirectory')).to eq true end it "ignores files in directories" do expect(target_exists?('pmtdirectory/pmtartifact')).to eq true end it "leaves exceptional files" do expect(target_exists?('pmtdirectory/pmtimportantfile')).to eq true end it "leaves subdirectories" do expect(target_exists?('pmtdirectory/sub')).to eq true end it "leaves files in subdirectories" do expect(target_exists?('pmtdirectory/sub/artifact')).to eq true end end shared_examples "pmtignored files are not present" do it "ignores regular files" do expect(target_exists?('pmtignored.foo')).to eq false end it "ignores UTF-8 files" do expect(target_exists?("pmt\u16A0\u16C7\u16BB")).to eq false end it "ignores directories" do expect(target_exists?('pmtdirectory')).to eq true end it "copies exceptional files" do expect(target_exists?('pmtdirectory/pmtimportantfile')).to eq true end it "ignores files in directories" do expect(target_exists?('pmtdirectory/pmtartifact')).to eq false end it "ignores subdirectories" do expect(target_exists?('pmtdirectory/sub')).to eq false end it "ignores files in subdirectories" do expect(target_exists?('pmtdirectory/sub/artifact')).to eq false end end context "with no ignore files" do before :each do create_regular_files create_ignored_files build end it_behaves_like "regular files are present" it_behaves_like "default artifacts are removed in module dir but not in subdirs" it_behaves_like "pmtignored files are present" it_behaves_like "gitignored files are present" end context "with .gitignore file" do before :each do create_regular_files create_ignored_files create_gitignore_file build end it_behaves_like "regular files are present" it_behaves_like "default artifacts are removed in module dir but not in subdirs" it_behaves_like "pmtignored files are present" it_behaves_like "gitignored files are not present" end context "with .pmtignore file" do before :each do create_regular_files create_ignored_files create_pmtignore_file build end it_behaves_like "regular files are present" it_behaves_like "default artifacts are removed in module dir but not in subdirs" it_behaves_like "gitignored files are present" it_behaves_like "pmtignored files are not present" end context "with .pmtignore and .gitignore file" do before :each do create_regular_files create_ignored_files create_pmtignore_file create_gitignore_file build end it_behaves_like "regular files are present" it_behaves_like "default artifacts are removed in module dir but not in subdirs" it_behaves_like "gitignored files are present" it_behaves_like "pmtignored files are not present" end context "with unignored symlinks", :if => Puppet.features.manages_symlinks? do before :each do create_regular_files create_symlinks create_ignored_files end it "give an error about symlinks" do expect { builder.run }.to raise_error(Puppet::ModuleTool::Errors::ModuleToolError, /Found symlinks/) end end context "with .gitignore file and ignored symlinks", :if => Puppet.features.manages_symlinks? do before :each do create_regular_files create_symlinks create_ignored_files create_symlink_gitignore_file end it "does not give an error about symlinks" do expect { build }.not_to raise_error end end end context 'with metadata.json' do # different UTF-8 widths # 1-byte A # 2-byte Űż - http://www.fileformat.info/info/unicode/char/06ff/index.htm - 0xDB 0xBF / 219 191 # 3-byte áš  - http://www.fileformat.info/info/unicode/char/16A0/index.htm - 0xE1 0x9A 0xA0 / 225 154 160 # 4-byte 𠜎 - http://www.fileformat.info/info/unicode/char/2070E/index.htm - 0xF0 0xA0 0x9C 0x8E / 240 160 156 142 MIXED_UTF8 = "A\u06FF\u16A0\u{2070E}" # Aۿᚠ𠜎 before :each do File.open(File.join(path, 'metadata.json'), 'w') do |f| f.puts({ "name" => "#{module_name}", "version" => "#{version}", "source" => "https://github.com/testing/#{module_name}", "author" => "testing", "license" => "Apache License Version 2.0", "summary" => "#{MIXED_UTF8}", "description" => "This module can be used for basic testing", "project_page" => "https://github.com/testing/#{module_name}" }.to_json) end end it_behaves_like "a packagable module" it "does not package with a symlink", :if => Puppet.features.manages_symlinks? do FileUtils.touch(File.join(path, 'tempfile')) Puppet::FileSystem.symlink(File.join(path, 'tempfile'), File.join(path, 'tempfile2')) expect { builder.run }.to raise_error Puppet::ModuleTool::Errors::ModuleToolError, /symlinks/i end it "does not package with a symlink in a subdir", :if => Puppet.features.manages_symlinks? do FileUtils.mkdir(File.join(path, 'manifests')) FileUtils.touch(File.join(path, 'manifests/tempfile.pp')) Puppet::FileSystem.symlink(File.join(path, 'manifests/tempfile.pp'), File.join(path, 'manifests/tempfile2.pp')) expect { builder.run }.to raise_error Puppet::ModuleTool::Errors::ModuleToolError, /symlinks/i end it "writes UTF-8 metdata correctly" do # file is written initially in before block, then by builders write_json method builder.run metadata_path = File.join(path, 'metadata.json') summary = JSON.parse(Puppet::FileSystem.read(metadata_path, :encoding => Encoding::UTF_8))["summary"] expect(summary).to eq(MIXED_UTF8) end end context 'with metadata.json containing checksums' do before :each do File.open(File.join(path, 'metadata.json'), 'w') do |f| f.puts({ "name" => "#{module_name}", "version" => "#{version}", "source" => "https://github.com/testing/#{module_name}", "author" => "testing", "license" => "Apache License Version 2.0", "summary" => "Puppet testing module", "description" => "This module can be used for basic testing", "project_page" => "https://github.com/testing/#{module_name}", "checksums" => {"README.md" => "deadbeef"} }.to_json) end end it_behaves_like "a packagable module" end context 'when in FIPS mode...' do it 'module builder refuses to run' do Facter.stubs(:value).with(:fips_enabled).returns(true) expect { builder.run }.to raise_error(/Module building is prohibited in FIPS mode/) end end end puppet-5.5.10/spec/unit/module_tool/applications/installer_spec.rb0000644005276200011600000003076413417161722025302 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/module_tool/applications' require 'puppet_spec/module_tool/shared_functions' require 'puppet_spec/module_tool/stub_source' require 'tmpdir' describe Puppet::ModuleTool::Applications::Installer do include PuppetSpec::ModuleTool::SharedFunctions include PuppetSpec::Files include PuppetSpec::Fixtures before do FileUtils.mkdir_p(primary_dir) FileUtils.mkdir_p(secondary_dir) end let(:vardir) { tmpdir('installer') } let(:primary_dir) { File.join(vardir, "primary") } let(:secondary_dir) { File.join(vardir, "secondary") } let(:remote_source) { PuppetSpec::ModuleTool::StubSource.new } let(:install_dir) do mock("Puppet::ModuleTool::InstallDirectory").tap do |dir| dir.stubs(:prepare) dir.stubs(:target).returns(primary_dir) end end before do SemanticPuppet::Dependency.clear_sources installer = Puppet::ModuleTool::Applications::Installer.any_instance installer.stubs(:module_repository).returns(remote_source) end if Puppet.features.microsoft_windows? before :each do Puppet.settings.stubs(:[]) Puppet.settings.stubs(:[]).with(:module_working_dir).returns(Dir.mktmpdir('installertmp')) end end def installer(modname, target_dir, options) Puppet::ModuleTool.set_option_defaults(options) Puppet::ModuleTool::Applications::Installer.new(modname, target_dir, options) end let(:environment) do Puppet.lookup(:current_environment).override_with( :vardir => vardir, :modulepath => [ primary_dir, secondary_dir ] ) end context '#run' do let(:module) { 'pmtacceptance-stdlib' } def options { :environment => environment } end let(:application) { installer(self.module, install_dir, options) } subject { application.run } it 'installs the specified module' do expect(subject).to include :result => :success graph_should_include 'pmtacceptance-stdlib', nil => v('4.1.0') end context 'with a tarball file' do let(:module) { fixtures('stdlib.tgz') } it 'installs the specified tarball' do expect(subject).to include :result => :success graph_should_include 'puppetlabs-stdlib', nil => v('3.2.0') end context 'with --ignore-dependencies' do def options super.merge(:ignore_dependencies => true) end it 'installs the specified tarball' do remote_source.expects(:fetch).never expect(subject).to include :result => :success graph_should_include 'puppetlabs-stdlib', nil => v('3.2.0') end end context 'with dependencies' do let(:module) { fixtures('java.tgz') } it 'installs the specified tarball' do expect(subject).to include :result => :success graph_should_include 'puppetlabs-java', nil => v('1.0.0') graph_should_include 'puppetlabs-stdlib', nil => v('4.1.0') end context 'with --ignore-dependencies' do def options super.merge(:ignore_dependencies => true) end it 'installs the specified tarball without dependencies' do remote_source.expects(:fetch).never expect(subject).to include :result => :success graph_should_include 'puppetlabs-java', nil => v('1.0.0') graph_should_include 'puppetlabs-stdlib', nil end end end end context 'with dependencies' do let(:module) { 'pmtacceptance-apache' } it 'installs the specified module and its dependencies' do expect(subject).to include :result => :success graph_should_include 'pmtacceptance-apache', nil => v('0.10.0') graph_should_include 'pmtacceptance-stdlib', nil => v('4.1.0') end context 'and using --ignore_dependencies' do def options super.merge(:ignore_dependencies => true) end it 'installs only the specified module' do expect(subject).to include :result => :success graph_should_include 'pmtacceptance-apache', nil => v('0.10.0') graph_should_include 'pmtacceptance-stdlib', nil end end context 'that are already installed' do context 'and satisfied' do before { preinstall('pmtacceptance-stdlib', '4.1.0') } it 'installs only the specified module' do expect(subject).to include :result => :success graph_should_include 'pmtacceptance-apache', nil => v('0.10.0') graph_should_include 'pmtacceptance-stdlib', :path => primary_dir end context '(outdated but suitable version)' do before { preinstall('pmtacceptance-stdlib', '2.4.0') } it 'installs only the specified module' do expect(subject).to include :result => :success graph_should_include 'pmtacceptance-apache', nil => v('0.10.0') graph_should_include 'pmtacceptance-stdlib', v('2.4.0') => v('2.4.0'), :path => primary_dir end end context '(outdated and unsuitable version)' do before { preinstall('pmtacceptance-stdlib', '1.0.0') } it 'installs a version that is compatible with the installed dependencies' do expect(subject).to include :result => :success graph_should_include 'pmtacceptance-apache', nil => v('0.0.4') graph_should_include 'pmtacceptance-stdlib', nil end end end context 'but not satisfied' do let(:module) { 'pmtacceptance-keystone' } def options super.merge(:version => '2.0.0') end before { preinstall('pmtacceptance-mysql', '2.1.0') } it 'installs only the specified module' do expect(subject).to include :result => :success graph_should_include 'pmtacceptance-keystone', nil => v('2.0.0') graph_should_include 'pmtacceptance-mysql', v('2.1.0') => v('2.1.0') graph_should_include 'pmtacceptance-stdlib', nil end end end context 'that are already installed in other modulepath directories' do before { preinstall('pmtacceptance-stdlib', '1.0.0', :into => secondary_dir) } let(:module) { 'pmtacceptance-apache' } context 'without dependency updates' do it 'installs the module only' do expect(subject).to include :result => :success graph_should_include 'pmtacceptance-apache', nil => v('0.0.4') graph_should_include 'pmtacceptance-stdlib', nil end end context 'with dependency updates' do before { preinstall('pmtacceptance-stdlib', '2.0.0', :into => secondary_dir) } it 'installs the module and upgrades dependencies in-place' do expect(subject).to include :result => :success graph_should_include 'pmtacceptance-apache', nil => v('0.10.0') graph_should_include 'pmtacceptance-stdlib', v('2.0.0') => v('2.6.0'), :path => secondary_dir end end end end context 'with a specified' do context 'version' do def options super.merge(:version => '3.0.0') end it 'installs the specified release (or a prerelease thereof)' do expect(subject).to include :result => :success graph_should_include 'pmtacceptance-stdlib', nil => v('3.0.0') end end context 'version range' do def options super.merge(:version => '3.x') end it 'installs the greatest available version matching that range' do expect(subject).to include :result => :success graph_should_include 'pmtacceptance-stdlib', nil => v('3.2.0') end end end context 'when depended upon' do before { preinstall('pmtacceptance-keystone', '2.1.0') } let(:module) { 'pmtacceptance-mysql' } it 'installs the greatest available version meeting the dependency constraints' do expect(subject).to include :result => :success graph_should_include 'pmtacceptance-mysql', nil => v('0.9.0') end context 'with a --version that can satisfy' do def options super.merge(:version => '0.8.0') end it 'installs the greatest available version satisfying both constraints' do expect(subject).to include :result => :success graph_should_include 'pmtacceptance-mysql', nil => v('0.8.0') end end context 'with a --version that cannot satisfy' do def options super.merge(:version => '> 1.0.0') end it 'fails to install, since there is no version that can satisfy both constraints' do expect(subject).to include :result => :failure end context 'with --ignore-dependencies' do def options super.merge(:ignore_dependencies => true) end it 'fails to install, since ignore_dependencies should still respect dependencies from installed modules' do expect(subject).to include :result => :failure end end context 'with --force' do def options super.merge(:force => true) end it 'installs the greatest available version, ignoring dependencies' do expect(subject).to include :result => :success graph_should_include 'pmtacceptance-mysql', nil => v('2.1.0') end end end end context 'when already installed' do before { preinstall('pmtacceptance-stdlib', '1.0.0') } context 'but matching the requested version' do it 'does nothing, since the installed version satisfies' do expect(subject).to include :result => :noop end context 'with --force' do def options super.merge(:force => true) end it 'does reinstall the module' do expect(subject).to include :result => :success graph_should_include 'pmtacceptance-stdlib', v('1.0.0') => v('4.1.0') end end context 'with local changes' do before do release = application.send(:installed_modules)['pmtacceptance-stdlib'] mark_changed(release.mod.path) end it 'does nothing, since local changes do not affect that' do expect(subject).to include :result => :noop end context 'with --force' do def options super.merge(:force => true) end it 'does reinstall the module, since --force ignores local changes' do expect(subject).to include :result => :success graph_should_include 'pmtacceptance-stdlib', v('1.0.0') => v('4.1.0') end end end end context 'but not matching the requested version' do def options super.merge(:version => '2.x') end it 'fails to install the module, since it is already installed' do expect(subject).to include :result => :failure expect(subject[:error]).to include :oneline => "'pmtacceptance-stdlib' (v2.x) requested; 'pmtacceptance-stdlib' (v1.0.0) already installed" end context 'with --force' do def options super.merge(:force => true) end it 'installs the greatest version matching the new version range' do expect(subject).to include :result => :success graph_should_include 'pmtacceptance-stdlib', v('1.0.0') => v('2.6.0') end end end end context 'when a module with the same name is already installed' do let(:module) { 'pmtacceptance-stdlib' } before { preinstall('puppetlabs-stdlib', '4.1.0') } it 'fails to install, since two modules with the same name cannot be installed simultaneously' do expect(subject).to include :result => :failure end context 'using --force' do def options super.merge(:force => true) end it 'overwrites the existing module with the greatest version of the requested module' do expect(subject).to include :result => :success graph_should_include 'pmtacceptance-stdlib', nil => v('4.1.0') end end end context 'when in FIPS mode...' do it 'module installer refuses to run' do Facter.stubs(:value).with(:fips_enabled).returns(true) expect {application.run}.to raise_error(/Module install is prohibited in FIPS mode./) end end end end puppet-5.5.10/spec/unit/module_tool/applications/upgrader_spec.rb0000644005276200011600000003141713417161722025112 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/module_tool/applications' require 'puppet_spec/module_tool/shared_functions' require 'puppet_spec/module_tool/stub_source' require 'tmpdir' describe Puppet::ModuleTool::Applications::Upgrader do include PuppetSpec::ModuleTool::SharedFunctions include PuppetSpec::Files before do FileUtils.mkdir_p(primary_dir) FileUtils.mkdir_p(secondary_dir) end let(:vardir) { tmpdir('upgrader') } let(:primary_dir) { File.join(vardir, "primary") } let(:secondary_dir) { File.join(vardir, "secondary") } let(:remote_source) { PuppetSpec::ModuleTool::StubSource.new } let(:environment) do Puppet.lookup(:current_environment).override_with( :vardir => vardir, :modulepath => [ primary_dir, secondary_dir ] ) end before do SemanticPuppet::Dependency.clear_sources installer = Puppet::ModuleTool::Applications::Upgrader.any_instance installer.stubs(:module_repository).returns(remote_source) end if Puppet.features.microsoft_windows? before :each do Puppet.settings.stubs(:[]) Puppet.settings.stubs(:[]).with(:module_working_dir).returns(Dir.mktmpdir('upgradertmp')) end end def upgrader(name, options = {}) Puppet::ModuleTool.set_option_defaults(options) Puppet::ModuleTool::Applications::Upgrader.new(name, options) end describe '#run' do let(:module) { 'pmtacceptance-stdlib' } def options { :environment => environment } end let(:application) { upgrader(self.module, options) } subject { application.run } it 'fails if the module is not already installed' do expect(subject).to include :result => :failure expect(subject[:error]).to include :oneline => "Could not upgrade '#{self.module}'; module is not installed" end context 'for an installed module' do context 'with only one version' do before { preinstall('puppetlabs-oneversion', '0.0.1') } let(:module) { 'puppetlabs-oneversion' } it 'declines to upgrade' do expect(subject).to include :result => :noop expect(subject[:error][:multiline]).to match(/already the latest version/) end end context 'without dependencies' do before { preinstall('pmtacceptance-stdlib', '1.0.0') } context 'without options' do it 'properly upgrades the module to the greatest version' do expect(subject).to include :result => :success graph_should_include 'pmtacceptance-stdlib', v('1.0.0') => v('4.1.0') end end context 'with version range' do def options super.merge(:version => '3.x') end context 'not matching the installed version' do it 'properly upgrades the module to the greatest version within that range' do expect(subject).to include :result => :success graph_should_include 'pmtacceptance-stdlib', v('1.0.0') => v('3.2.0') end end context 'matching the installed version' do context 'with more recent version' do before { preinstall('pmtacceptance-stdlib', '3.0.0')} it 'properly upgrades the module to the greatest version within that range' do expect(subject).to include :result => :success graph_should_include 'pmtacceptance-stdlib', v('3.0.0') => v('3.2.0') end end context 'without more recent version' do before { preinstall('pmtacceptance-stdlib', '3.2.0')} context 'without options' do it 'declines to upgrade' do expect(subject).to include :result => :noop expect(subject[:error][:multiline]).to match(/already the latest version/) end end context 'with --force' do def options super.merge(:force => true) end it 'overwrites the installed module with the greatest version matching that range' do expect(subject).to include :result => :success graph_should_include 'pmtacceptance-stdlib', v('3.2.0') => v('3.2.0') end end end end end end context 'that is depended upon' do # pmtacceptance-keystone depends on pmtacceptance-mysql >=0.6.1 <1.0.0 before { preinstall('pmtacceptance-keystone', '2.1.0') } before { preinstall('pmtacceptance-mysql', '0.9.0') } let(:module) { 'pmtacceptance-mysql' } context 'and out of date' do before { preinstall('pmtacceptance-mysql', '0.8.0') } it 'properly upgrades to the greatest version matching the dependency' do expect(subject).to include :result => :success graph_should_include 'pmtacceptance-mysql', v('0.8.0') => v('0.9.0') end end context 'and up to date' do it 'declines to upgrade' do expect(subject).to include :result => :failure end end context 'when specifying a violating version range' do def options super.merge(:version => '2.1.0') end it 'fails to upgrade the module' do # TODO: More helpful error message? expect(subject).to include :result => :failure expect(subject[:error]).to include :oneline => "Could not upgrade '#{self.module}' (v0.9.0 -> v2.1.0); no version satisfies all dependencies" end context 'using --force' do def options super.merge(:force => true) end it 'overwrites the installed module with the specified version' do expect(subject).to include :result => :success graph_should_include 'pmtacceptance-mysql', v('0.9.0') => v('2.1.0') end end end end context 'with local changes' do before { preinstall('pmtacceptance-stdlib', '1.0.0') } before do release = application.send(:installed_modules)['pmtacceptance-stdlib'] mark_changed(release.mod.path) end it 'fails to upgrade' do expect(subject).to include :result => :failure expect(subject[:error]).to include :oneline => "Could not upgrade '#{self.module}'; module has had changes made locally" end context 'with --ignore-changes' do def options super.merge(:ignore_changes => true) end it 'overwrites the installed module with the greatest version matching that range' do expect(subject).to include :result => :success graph_should_include 'pmtacceptance-stdlib', v('1.0.0') => v('4.1.0') end end end context 'with dependencies' do context 'that are unsatisfied' do def options super.merge(:version => '0.1.1') end before { preinstall('pmtacceptance-apache', '0.0.3') } let(:module) { 'pmtacceptance-apache' } it 'upgrades the module and installs the missing dependencies' do expect(subject).to include :result => :success graph_should_include 'pmtacceptance-apache', v('0.0.3') => v('0.1.1') graph_should_include 'pmtacceptance-stdlib', nil => v('4.1.0'), :action => :install end end context 'with older major versions' do # pmtacceptance-apache 0.0.4 has no dependency on pmtacceptance-stdlib # the next available version (0.1.1) and all subsequent versions depend on pmtacceptance-stdlib >= 2.2.1 before { preinstall('pmtacceptance-apache', '0.0.3') } before { preinstall('pmtacceptance-stdlib', '1.0.0') } let(:module) { 'pmtacceptance-apache' } it 'refuses to upgrade the installed dependency to a new major version, but upgrades the module to the greatest compatible version' do expect(subject).to include :result => :success graph_should_include 'pmtacceptance-apache', v('0.0.3') => v('0.0.4') end context 'using --ignore_dependencies' do def options super.merge(:ignore_dependencies => true) end it 'upgrades the module to the greatest available version' do expect(subject).to include :result => :success graph_should_include 'pmtacceptance-apache', v('0.0.3') => v('0.10.0') end end end context 'with satisfying major versions' do before { preinstall('pmtacceptance-apache', '0.0.3') } before { preinstall('pmtacceptance-stdlib', '2.0.0') } let(:module) { 'pmtacceptance-apache' } it 'upgrades the module and its dependencies to their greatest compatible versions' do expect(subject).to include :result => :success graph_should_include 'pmtacceptance-apache', v('0.0.3') => v('0.10.0') graph_should_include 'pmtacceptance-stdlib', v('2.0.0') => v('2.6.0') end end context 'with satisfying versions' do before { preinstall('pmtacceptance-apache', '0.0.3') } before { preinstall('pmtacceptance-stdlib', '2.4.0') } let(:module) { 'pmtacceptance-apache' } it 'upgrades the module to the greatest available version' do expect(subject).to include :result => :success graph_should_include 'pmtacceptance-apache', v('0.0.3') => v('0.10.0') graph_should_include 'pmtacceptance-stdlib', nil end end context 'with current versions' do before { preinstall('pmtacceptance-apache', '0.0.3') } before { preinstall('pmtacceptance-stdlib', '2.6.0') } let(:module) { 'pmtacceptance-apache' } it 'upgrades the module to the greatest available version' do expect(subject).to include :result => :success graph_should_include 'pmtacceptance-apache', v('0.0.3') => v('0.10.0') graph_should_include 'pmtacceptance-stdlib', nil end end context 'with shared dependencies' do # bacula 0.0.3 depends on stdlib >= 2.2.0 and pmtacceptance/mysql >= 1.0.0 # bacula 0.0.2 depends on stdlib >= 2.2.0 and pmtacceptance/mysql >= 0.0.1 # bacula 0.0.1 depends on stdlib >= 2.2.0 # keystone 2.1.0 depends on pmtacceptance/stdlib >= 2.5.0 and pmtacceptance/mysql >=0.6.1 <1.0.0 before { preinstall('pmtacceptance-bacula', '0.0.1') } before { preinstall('pmtacceptance-mysql', '0.9.0') } before { preinstall('pmtacceptance-keystone', '2.1.0') } let(:module) { 'pmtacceptance-bacula' } it 'upgrades the module to the greatest version compatible with all other installed modules' do expect(subject).to include :result => :success graph_should_include 'pmtacceptance-bacula', v('0.0.1') => v('0.0.2') end context 'using --force' do def options super.merge(:force => true) end it 'upgrades the module to the greatest version available' do expect(subject).to include :result => :success graph_should_include 'pmtacceptance-bacula', v('0.0.1') => v('0.0.3') end end end context 'in other modulepath directories' do before { preinstall('pmtacceptance-apache', '0.0.3') } before { preinstall('pmtacceptance-stdlib', '1.0.0', :into => secondary_dir) } let(:module) { 'pmtacceptance-apache' } context 'with older major versions' do it 'upgrades the module to the greatest version compatible with the installed modules' do expect(subject).to include :result => :success graph_should_include 'pmtacceptance-apache', v('0.0.3') => v('0.0.4') graph_should_include 'pmtacceptance-stdlib', nil end end context 'with satisfying major versions' do before { preinstall('pmtacceptance-stdlib', '2.0.0', :into => secondary_dir) } it 'upgrades the module and its dependencies to their greatest compatible versions, in-place' do expect(subject).to include :result => :success graph_should_include 'pmtacceptance-apache', v('0.0.3') => v('0.10.0') graph_should_include 'pmtacceptance-stdlib', v('2.0.0') => v('2.6.0'), :path => secondary_dir end end end end end context 'when in FIPS mode...' do it 'module unpgrader refuses to run' do Facter.stubs(:value).with(:fips_enabled).returns(true) expect { application.run }.to raise_error(/Module upgrade is prohibited in FIPS mode/) end end end end puppet-5.5.10/spec/unit/module_tool/install_directory_spec.rb0000644005276200011600000000467513417161721024352 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/module_tool/install_directory' describe Puppet::ModuleTool::InstallDirectory do def expect_normal_results results = installer.run expect(results[:installed_modules].length).to eq 1 expect(results[:installed_modules][0][:module]).to eq("pmtacceptance-stdlib") expect(results[:installed_modules][0][:version][:vstring]).to eq("1.0.0") results end it "(#15202) creates the install directory" do target_dir = the_directory('foo', :directory? => false, :exist? => false) target_dir.expects(:mkpath) install = Puppet::ModuleTool::InstallDirectory.new(target_dir) install.prepare('pmtacceptance-stdlib', '1.0.0') end it "(#15202) errors when the directory is not accessible" do target_dir = the_directory('foo', :directory? => false, :exist? => false) target_dir.expects(:mkpath).raises(Errno::EACCES) install = Puppet::ModuleTool::InstallDirectory.new(target_dir) expect { install.prepare('module', '1.0.1') }.to raise_error( Puppet::ModuleTool::Errors::PermissionDeniedCreateInstallDirectoryError ) end it "(#15202) errors when an entry along the path is not a directory" do target_dir = the_directory("foo/bar", :exist? => false, :directory? => false) target_dir.expects(:mkpath).raises(Errno::EEXIST) install = Puppet::ModuleTool::InstallDirectory.new(target_dir) expect { install.prepare('module', '1.0.1') }.to raise_error(Puppet::ModuleTool::Errors::InstallPathExistsNotDirectoryError) end it "(#15202) simply re-raises an unknown error" do target_dir = the_directory("foo/bar", :exist? => false, :directory? => false) target_dir.expects(:mkpath).raises("unknown error") install = Puppet::ModuleTool::InstallDirectory.new(target_dir) expect { install.prepare('module', '1.0.1') }.to raise_error("unknown error") end it "(#15202) simply re-raises an unknown system call error" do target_dir = the_directory("foo/bar", :exist? => false, :directory? => false) target_dir.expects(:mkpath).raises(SystemCallError, "unknown") install = Puppet::ModuleTool::InstallDirectory.new(target_dir) expect { install.prepare('module', '1.0.1') }.to raise_error(SystemCallError) end def the_directory(name, options) dir = mock("Pathname<#{name}>") dir.stubs(:exist?).returns(options.fetch(:exist?, true)) dir.stubs(:directory?).returns(options.fetch(:directory?, true)) dir end end puppet-5.5.10/spec/unit/module_tool/installed_modules_spec.rb0000644005276200011600000000443613417161721024322 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/module_tool/installed_modules' require 'puppet_spec/modules' describe Puppet::ModuleTool::InstalledModules do include PuppetSpec::Files around do |example| dir = tmpdir("deep_path") FileUtils.mkdir_p(@modpath = File.join(dir, "modpath")) @env = Puppet::Node::Environment.create(:env, [@modpath]) Puppet.override(:current_environment => @env) do example.run end end it 'works when given a semantic version' do mod = PuppetSpec::Modules.create('goodsemver', @modpath, :metadata => {:version => '1.2.3'}) installed = described_class.new(@env) expect(installed.modules["puppetlabs-#{mod.name}"].version).to eq(SemanticPuppet::Version.parse('1.2.3')) end it 'defaults when not given a semantic version' do mod = PuppetSpec::Modules.create('badsemver', @modpath, :metadata => {:version => 'banana'}) Puppet.expects(:warning).with(regexp_matches(/Semantic Version/)) installed = described_class.new(@env) expect(installed.modules["puppetlabs-#{mod.name}"].version).to eq(SemanticPuppet::Version.parse('0.0.0')) end it 'defaults when not given a full semantic version' do mod = PuppetSpec::Modules.create('badsemver', @modpath, :metadata => {:version => '1.2'}) Puppet.expects(:warning).with(regexp_matches(/Semantic Version/)) installed = described_class.new(@env) expect(installed.modules["puppetlabs-#{mod.name}"].version).to eq(SemanticPuppet::Version.parse('0.0.0')) end it 'still works if there is an invalid version in one of the modules' do mod1 = PuppetSpec::Modules.create('badsemver', @modpath, :metadata => {:version => 'banana'}) mod2 = PuppetSpec::Modules.create('goodsemver', @modpath, :metadata => {:version => '1.2.3'}) mod3 = PuppetSpec::Modules.create('notquitesemver', @modpath, :metadata => {:version => '1.2'}) Puppet.expects(:warning).with(regexp_matches(/Semantic Version/)).twice installed = described_class.new(@env) expect(installed.modules["puppetlabs-#{mod1.name}"].version).to eq(SemanticPuppet::Version.parse('0.0.0')) expect(installed.modules["puppetlabs-#{mod2.name}"].version).to eq(SemanticPuppet::Version.parse('1.2.3')) expect(installed.modules["puppetlabs-#{mod3.name}"].version).to eq(SemanticPuppet::Version.parse('0.0.0')) end end puppet-5.5.10/spec/unit/module_tool/metadata_spec.rb0000644005276200011600000002551713417161721022376 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/module_tool' describe Puppet::ModuleTool::Metadata do let(:data) { {} } let(:metadata) { Puppet::ModuleTool::Metadata.new } describe 'property lookups' do subject { metadata } %w[ name version author summary license source project_page issues_url dependencies dashed_name release_name description data_provider].each do |prop| describe "##{prop}" do it "responds to the property" do subject.send(prop) end end end end describe "#update" do subject { metadata.update(data) } context "with a valid name" do let(:data) { { 'name' => 'billgates-mymodule' } } it "extracts the author name from the name field" do expect(subject.to_hash['author']).to eq('billgates') end it "extracts a module name from the name field" do expect(subject.module_name).to eq('mymodule') end context "and existing author" do before { metadata.update('author' => 'foo') } it "avoids overwriting the existing author" do expect(subject.to_hash['author']).to eq('foo') end end end context "with a valid name and author" do let(:data) { { 'name' => 'billgates-mymodule', 'author' => 'foo' } } it "use the author name from the author field" do expect(subject.to_hash['author']).to eq('foo') end context "and preexisting author" do before { metadata.update('author' => 'bar') } it "avoids overwriting the existing author" do expect(subject.to_hash['author']).to eq('foo') end end end context "with an invalid name" do context "(short module name)" do let(:data) { { 'name' => 'mymodule' } } it "raises an exception" do expect { subject }.to raise_error(ArgumentError, "Invalid 'name' field in metadata.json: the field must be a namespaced module name") end end context "(missing namespace)" do let(:data) { { 'name' => '/mymodule' } } it "raises an exception" do expect { subject }.to raise_error(ArgumentError, "Invalid 'name' field in metadata.json: the field must be a namespaced module name") end end context "(missing module name)" do let(:data) { { 'name' => 'namespace/' } } it "raises an exception" do expect { subject }.to raise_error(ArgumentError, "Invalid 'name' field in metadata.json: the field must be a namespaced module name") end end context "(invalid namespace)" do let(:data) { { 'name' => "dolla'bill$-mymodule" } } it "raises an exception" do expect { subject }.to raise_error(ArgumentError, "Invalid 'name' field in metadata.json: the namespace contains non-alphanumeric characters") end end context "(non-alphanumeric module name)" do let(:data) { { 'name' => "dollabils-fivedolla'" } } it "raises an exception" do expect { subject }.to raise_error(ArgumentError, "Invalid 'name' field in metadata.json: the module name contains non-alphanumeric (or underscore) characters") end end context "(module name starts with a number)" do let(:data) { { 'name' => "dollabills-5dollars" } } it "raises an exception" do expect { subject }.to raise_error(ArgumentError, "Invalid 'name' field in metadata.json: the module name must begin with a letter") end end end context "with an invalid version" do let(:data) { { 'version' => '3.0' } } it "raises an exception" do expect { subject }.to raise_error(ArgumentError, "Invalid 'version' field in metadata.json: version string cannot be parsed as a valid Semantic Version") end end context "with a valid source" do context "which is a GitHub URL" do context "with a scheme" do before { metadata.update('source' => 'https://github.com/billgates/amazingness') } it "predicts a default project_page" do expect(subject.to_hash['project_page']).to eq('https://github.com/billgates/amazingness') end it "predicts a default issues_url" do expect(subject.to_hash['issues_url']).to eq('https://github.com/billgates/amazingness/issues') end end context "without a scheme" do before { metadata.update('source' => 'github.com/billgates/amazingness') } it "predicts a default project_page" do expect(subject.to_hash['project_page']).to eq('https://github.com/billgates/amazingness') end it "predicts a default issues_url" do expect(subject.to_hash['issues_url']).to eq('https://github.com/billgates/amazingness/issues') end end end context "which is not a GitHub URL" do before { metadata.update('source' => 'https://notgithub.com/billgates/amazingness') } it "does not predict a default project_page" do expect(subject.to_hash['project_page']).to be nil end it "does not predict a default issues_url" do expect(subject.to_hash['issues_url']).to be nil end end context "which is not a URL" do before { metadata.update('source' => 'my brain') } it "does not predict a default project_page" do expect(subject.to_hash['project_page']).to be nil end it "does not predict a default issues_url" do expect(subject.to_hash['issues_url']).to be nil end end end context "with a valid dependency" do let(:data) { {'dependencies' => [{'name' => 'puppetlabs-goodmodule'}] }} it "adds the dependency" do expect(subject.dependencies.size).to eq(1) end end context "with a invalid dependency name" do let(:data) { {'dependencies' => [{'name' => 'puppetlabsbadmodule'}] }} it "raises an exception" do expect { subject }.to raise_error(ArgumentError) end end context "with a valid dependency version range" do let(:data) { {'dependencies' => [{'name' => 'puppetlabs-badmodule', 'version_requirement' => '>= 2.0.0'}] }} it "adds the dependency" do expect(subject.dependencies.size).to eq(1) end end context "with a invalid version range" do let(:data) { {'dependencies' => [{'name' => 'puppetlabsbadmodule', 'version_requirement' => '>= banana'}] }} it "raises an exception" do expect { subject }.to raise_error(ArgumentError) end end context "with duplicate dependencies" do let(:data) { {'dependencies' => [{'name' => 'puppetlabs-dupmodule', 'version_requirement' => '1.0.0'}, {'name' => 'puppetlabs-dupmodule', 'version_requirement' => '0.0.1'}] } } it "raises an exception" do expect { subject }.to raise_error(ArgumentError) end end context "adding a duplicate dependency" do let(:data) { {'dependencies' => [{'name' => 'puppetlabs-origmodule', 'version_requirement' => '1.0.0'}] }} it "with a different version raises an exception" do metadata.add_dependency('puppetlabs-origmodule', '>= 0.0.1') expect { subject }.to raise_error(ArgumentError) end it "with the same version does not add another dependency" do metadata.add_dependency('puppetlabs-origmodule', '1.0.0') expect(subject.dependencies.size).to eq(1) end end context 'with valid data_provider' do let(:data) { {'data_provider' => 'the_name'} } it 'validates the provider correctly' do expect { subject }.not_to raise_error end it 'returns the provider' do expect(subject.data_provider).to eq('the_name') end end context 'with invalid data_provider' do let(:data) { } it "raises exception unless argument starts with a letter" do expect { metadata.update('data_provider' => '_the_name') }.to raise_error(ArgumentError, /field 'data_provider' must begin with a letter/) expect { metadata.update('data_provider' => '') }.to raise_error(ArgumentError, /field 'data_provider' must begin with a letter/) end it "raises exception if argument contains non-alphanumeric characters" do expect { metadata.update('data_provider' => 'the::name') }.to raise_error(ArgumentError, /field 'data_provider' contains non-alphanumeric characters/) end it "raises exception unless argument is a string" do expect { metadata.update('data_provider' => 23) }.to raise_error(ArgumentError, /field 'data_provider' must be a string/) end end end describe '#dashed_name' do it 'returns nil in the absence of a module name' do expect(metadata.update('version' => '1.0.0').release_name).to be_nil end it 'returns a hyphenated string containing namespace and module name' do data = metadata.update('name' => 'foo-bar') expect(data.dashed_name).to eq('foo-bar') end it 'properly handles slash-separated names' do data = metadata.update('name' => 'foo/bar') expect(data.dashed_name).to eq('foo-bar') end it 'is unaffected by author name' do data = metadata.update('name' => 'foo/bar', 'author' => 'me') expect(data.dashed_name).to eq('foo-bar') end end describe '#release_name' do it 'returns nil in the absence of a module name' do expect(metadata.update('version' => '1.0.0').release_name).to be_nil end it 'returns nil in the absence of a version' do expect(metadata.update('name' => 'foo/bar').release_name).to be_nil end it 'returns a hyphenated string containing module name and version' do data = metadata.update('name' => 'foo/bar', 'version' => '1.0.0') expect(data.release_name).to eq('foo-bar-1.0.0') end it 'is unaffected by author name' do data = metadata.update('name' => 'foo/bar', 'version' => '1.0.0', 'author' => 'me') expect(data.release_name).to eq('foo-bar-1.0.0') end end describe "#to_hash" do subject { metadata.to_hash } it "contains the default set of keys" do expect(subject.keys.sort).to eq(%w[ name version author summary license source issues_url project_page dependencies data_provider].sort) end describe "['license']" do it "defaults to Apache 2" do expect(subject['license']).to eq("Apache-2.0") end end describe "['dependencies']" do it "defaults to an empty set" do expect(subject['dependencies']).to eq(Set.new) end end context "when updated with non-default data" do subject { metadata.update('license' => 'MIT', 'non-standard' => 'yup').to_hash } it "overrides the defaults" do expect(subject['license']).to eq('MIT') end it 'contains unanticipated values' do expect(subject['non-standard']).to eq('yup') end end end end puppet-5.5.10/spec/unit/module_tool/tar/0000755005276200011600000000000013417162176020040 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/module_tool/tar/gnu_spec.rb0000644005276200011600000000204213417161721022161 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/module_tool' describe Puppet::ModuleTool::Tar::Gnu do let(:sourcefile) { '/space path/the/module.tar.gz' } let(:destdir) { '/space path/the/dest/dir' } let(:sourcedir) { '/space path/the/src/dir' } let(:destfile) { '/space path/the/dest/file.tar.gz' } it "unpacks a tar file" do Dir.expects(:chdir).with(File.expand_path(destdir)).yields(mock) Puppet::Util::Execution.expects(:execute).with("gzip -dc #{Shellwords.shellescape(File.expand_path(sourcefile))} | tar xof -") Puppet::Util::Execution.expects(:execute).with("find . -type d -exec chmod 755 {} +") Puppet::Util::Execution.expects(:execute).with("find . -type f -exec chmod u+rw,g+r,a-st {} +") Puppet::Util::Execution.expects(:execute).with("chown -R .") subject.unpack(sourcefile, destdir, '') end it "packs a tar file" do Puppet::Util::Execution.expects(:execute).with("tar cf - #{sourcedir} | gzip -c > #{File.basename(destfile)}") subject.pack(sourcedir, destfile) end end puppet-5.5.10/spec/unit/module_tool/tar/mini_spec.rb0000644005276200011600000000607513417161721022336 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/module_tool' describe Puppet::ModuleTool::Tar::Mini, :if => (Puppet.features.minitar? and Puppet.features.zlib?) do let(:sourcefile) { '/the/module.tar.gz' } let(:destdir) { File.expand_path '/the/dest/dir' } let(:sourcedir) { '/the/src/dir' } let(:destfile) { '/the/dest/file.tar.gz' } let(:minitar) { described_class.new } class MockFileStatEntry def initialize(mode = 0100) @mode = mode end end it "unpacks a tar file with correct permissions" do entry = unpacks_the_entry(:file_start, 'thefile') minitar.unpack(sourcefile, destdir, 'uid') expect(entry.instance_variable_get(:@mode)).to eq(0755) end it "does not allow an absolute path" do unpacks_the_entry(:file_start, '/thefile') expect { minitar.unpack(sourcefile, destdir, 'uid') }.to raise_error(Puppet::ModuleTool::Errors::InvalidPathInPackageError, "Attempt to install file with an invalid path into \"/thefile\" under \"#{destdir}\"") end it "does not allow a file to be written outside the destination directory" do unpacks_the_entry(:file_start, '../../thefile') expect { minitar.unpack(sourcefile, destdir, 'uid') }.to raise_error(Puppet::ModuleTool::Errors::InvalidPathInPackageError, "Attempt to install file with an invalid path into \"#{File.expand_path('/the/thefile')}\" under \"#{destdir}\"") end it "does not allow a directory to be written outside the destination directory" do unpacks_the_entry(:dir, '../../thedir') expect { minitar.unpack(sourcefile, destdir, 'uid') }.to raise_error(Puppet::ModuleTool::Errors::InvalidPathInPackageError, "Attempt to install file with an invalid path into \"#{File.expand_path('/the/thedir')}\" under \"#{destdir}\"") end it "unpacks on Windows" do unpacks_the_entry(:file_start, 'thefile', nil) entry = minitar.unpack(sourcefile, destdir, 'uid') # Windows does not use these permissions. expect(entry.instance_variable_get(:@mode)).to eq(nil) end it "packs a tar file" do writer = stub('GzipWriter') Zlib::GzipWriter.expects(:open).with(destfile).yields(writer) stats = {:mode => 0222} Archive::Tar::Minitar.expects(:pack).with(sourcedir, writer).yields(:file_start, 'abc', stats) minitar.pack(sourcedir, destfile) end it "packs a tar file on Windows" do writer = stub('GzipWriter') Zlib::GzipWriter.expects(:open).with(destfile).yields(writer) Archive::Tar::Minitar.expects(:pack).with(sourcedir, writer). yields(:file_start, 'abc', {:entry => MockFileStatEntry.new(nil)}) minitar.pack(sourcedir, destfile) end def unpacks_the_entry(type, name, mode = 0100) reader = stub('GzipReader') Zlib::GzipReader.expects(:open).with(sourcefile).yields(reader) minitar.expects(:find_valid_files).with(reader).returns([name]) entry = MockFileStatEntry.new(mode) Archive::Tar::Minitar.expects(:unpack).with(reader, destdir, [name]). yields(type, name, {:entry => entry}) entry end end puppet-5.5.10/spec/unit/module_tool/tar_spec.rb0000644005276200011600000000271313417161721021375 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/module_tool/tar' describe Puppet::ModuleTool::Tar do [ { :name => 'ObscureLinuxDistro', :win => false }, { :name => 'Windows', :win => true } ].each do |os| it "always prefers minitar if it and zlib are present, even with tar available" do Facter.stubs(:value).with('osfamily').returns os[:name] Puppet::Util.stubs(:which).with('tar').returns '/usr/bin/tar' Puppet::Util::Platform.stubs(:windows?).returns os[:win] Puppet.stubs(:features).returns(stub(:minitar? => true, :zlib? => true)) expect(described_class.instance).to be_a_kind_of Puppet::ModuleTool::Tar::Mini end end it "falls back to tar when minitar not present and not on Windows" do Facter.stubs(:value).with('osfamily').returns 'ObscureLinuxDistro' Puppet::Util.stubs(:which).with('tar').returns '/usr/bin/tar' Puppet::Util::Platform.stubs(:windows?).returns false Puppet.stubs(:features).returns(stub(:minitar? => false)) expect(described_class.instance).to be_a_kind_of Puppet::ModuleTool::Tar::Gnu end it "fails when there is no possible implementation" do Facter.stubs(:value).with('osfamily').returns 'Windows' Puppet::Util.stubs(:which).with('tar') Puppet::Util::Platform.stubs(:windows?).returns true Puppet.stubs(:features).returns(stub(:minitar? => false, :zlib? => false)) expect { described_class.instance }.to raise_error RuntimeError, /No suitable tar/ end end puppet-5.5.10/spec/unit/network/0000755005276200011600000000000013417162176016421 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/network/auth_config_parser_spec.rb0000644005276200011600000001004613417161721023616 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/network/auth_config_parser' require 'puppet/network/authconfig' describe Puppet::Network::AuthConfigParser do include PuppetSpec::Files let(:fake_authconfig) do "path ~ ^/catalog/([^/])\nmethod find\nallow *\n" end describe "Basic Parser" do it "should accept a string by default" do expect(described_class.new(fake_authconfig).parse).to be_a_kind_of Puppet::Network::AuthConfig end end describe "when parsing rights" do it "skips comments" do expect(described_class.new(' # comment\n').parse_rights).to be_empty end it "increments line number even on commented lines" do expect(described_class.new(" # comment\npath /").parse_rights['/'].line).to eq(2) end it "skips blank lines" do expect(described_class.new(' ').parse_rights).to be_empty end it "increments line number even on blank lines" do expect(described_class.new(" \npath /").parse_rights['/'].line).to eq(2) end it "does not throw an error if the same path appears twice" do expect { described_class.new("path /hello\npath /hello").parse_rights }.to_not raise_error end it "should create a new right for each found path line" do expect(described_class.new('path /certificates').parse_rights['/certificates']).to be end it "should create a new right for each found regex line" do expect(described_class.new('path ~ .rb$').parse_rights['.rb$']).to be end it "should strip whitespace around ACE" do Puppet::Network::Rights::Right.any_instance.expects(:allow).with('127.0.0.1') Puppet::Network::Rights::Right.any_instance.expects(:allow).with('172.16.10.0') described_class.new("path /\n allow 127.0.0.1 , 172.16.10.0 ").parse_rights end it "should allow ACE inline comments" do Puppet::Network::Rights::Right.any_instance.expects(:allow).with('127.0.0.1') described_class.new("path /\n allow 127.0.0.1 # will it work?").parse_rights end it "should create an allow ACE on each subsequent allow" do Puppet::Network::Rights::Right.any_instance.expects(:allow).with('127.0.0.1') described_class.new("path /\nallow 127.0.0.1").parse_rights end it "should create a deny ACE on each subsequent deny" do Puppet::Network::Rights::Right.any_instance.expects(:deny).with('127.0.0.1') described_class.new("path /\ndeny 127.0.0.1").parse_rights end it "should inform the current ACL if we get the 'method' directive" do Puppet::Network::Rights::Right.any_instance.expects(:restrict_method).with('search') Puppet::Network::Rights::Right.any_instance.expects(:restrict_method).with('find') described_class.new("path /certificates\nmethod search,find").parse_rights end it "should inform the current ACL if we get the 'environment' directive" do Puppet::Network::Rights::Right.any_instance.expects(:restrict_environment).with('production') Puppet::Network::Rights::Right.any_instance.expects(:restrict_environment).with('development') described_class.new("path /certificates\nenvironment production,development").parse_rights end it "should inform the current ACL if we get the 'auth' directive" do Puppet::Network::Rights::Right.any_instance.expects(:restrict_authenticated).with('yes') described_class.new("path /certificates\nauth yes").parse_rights end it "should also allow the long form 'authenticated' directive" do Puppet::Network::Rights::Right.any_instance.expects(:restrict_authenticated).with('yes') described_class.new("path /certificates\nauthenticated yes").parse_rights end end describe "when parsing rights from files" do it "can read UTF-8" do rune_path = "/\u16A0\u16C7\u16BB" # ᚠᛇᚻ config = tmpfile('config') File.open(config, 'w', :encoding => 'utf-8') do |file| file.puts <<-EOF path #{rune_path} EOF end expect(described_class.new_from_file(config).parse_rights[rune_path]).to be end end end puppet-5.5.10/spec/unit/network/authconfig_spec.rb0000644005276200011600000001132313417161721022102 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/network/authconfig' describe Puppet::Network::DefaultAuthProvider do before :each do Puppet::FileSystem.stubs(:stat).returns stub('stat', :ctime => :now) Time.stubs(:now).returns Time.now Puppet::Network::DefaultAuthProvider.any_instance.stubs(:exists?).returns(true) # FIXME @authprovider = Puppet::Network::DefaultAuthProvider.new("dummy") end describe "when initializing" do it "inserts default ACLs after setting initial rights" do Puppet::Network::DefaultAuthProvider.any_instance.expects(:insert_default_acl) Puppet::Network::DefaultAuthProvider.new end end describe "when defining an acl with mk_acl" do before :each do Puppet::Network::DefaultAuthProvider.any_instance.stubs(:insert_default_acl) @authprovider = Puppet::Network::DefaultAuthProvider.new end it "should create a new right for each default acl" do @authprovider.mk_acl(:acl => '/') expect(@authprovider.rights['/']).to be end it "allows everyone for each default right" do @authprovider.mk_acl(:acl => '/') expect(@authprovider.rights['/']).to be_globalallow end it "accepts an argument to restrict the method" do @authprovider.mk_acl(:acl => '/', :method => :find) expect(@authprovider.rights['/'].methods).to eq([:find]) end it "creates rights with authentication set to true by default" do @authprovider.mk_acl(:acl => '/') expect(@authprovider.rights['/'].authentication).to be_truthy end it "accepts an argument to set the authentication requirement" do @authprovider.mk_acl(:acl => '/', :authenticated => :any) expect(@authprovider.rights['/'].authentication).to be_falsey end end describe "when adding default ACLs" do before :each do Puppet::Network::DefaultAuthProvider.any_instance.stubs(:insert_default_acl) @authprovider = Puppet::Network::DefaultAuthProvider.new Puppet::Network::DefaultAuthProvider.any_instance.unstub(:insert_default_acl) end Puppet::Network::DefaultAuthProvider::default_acl.each do |acl| it "should create a default right for #{acl[:acl]}" do @authprovider.stubs(:mk_acl) @authprovider.expects(:mk_acl).with(acl) @authprovider.insert_default_acl end end it "should log at info loglevel" do Puppet.expects(:info).at_least_once @authprovider.insert_default_acl end it "creates an empty catch-all rule for '/' for any authentication request state" do @authprovider.stubs(:mk_acl) @authprovider.insert_default_acl expect(@authprovider.rights['/']).to be_empty expect(@authprovider.rights['/'].authentication).to be_falsey end it '(CVE-2013-2275) allows report submission only for the node matching the certname by default' do acl = { :acl => "~ ^#{Puppet::Network::HTTP::MASTER_URL_PREFIX}\/v3\/report\/([^\/]+)$", :method => :save, :allow => '$1', :authenticated => true } @authprovider.stubs(:mk_acl) @authprovider.expects(:mk_acl).with(acl) @authprovider.insert_default_acl end end describe "when checking authorization" do it "should ask for authorization to the ACL subsystem" do params = { :ip => "127.0.0.1", :node => "me", :environment => :env, :authenticated => true } Puppet::Network::Rights.any_instance.expects(:is_request_forbidden_and_why?).with(:save, "/path/to/resource", params) described_class.new.check_authorization(:save, "/path/to/resource", params) end end end describe Puppet::Network::AuthConfig do after :each do Puppet::Network::AuthConfig.authprovider_class = nil end class TestAuthProvider def initialize(rights=nil); end def check_authorization(method, path, params); end end it "instantiates authprovider_class with rights" do Puppet::Network::AuthConfig.authprovider_class = TestAuthProvider rights = Puppet::Network::Rights.new TestAuthProvider.expects(:new).with(rights) described_class.new(rights) end it "delegates authorization check to authprovider_class" do Puppet::Network::AuthConfig.authprovider_class = TestAuthProvider TestAuthProvider.any_instance.expects(:check_authorization).with(:save, '/path/to/resource', {}) described_class.new.check_authorization(:save, '/path/to/resource', {}) end it "uses DefaultAuthProvider by default" do Puppet::Network::AuthConfig.authprovider_class = nil Puppet::Network::DefaultAuthProvider.any_instance.expects(:check_authorization).with(:save, '/path/to/resource', {}) described_class.new.check_authorization(:save, '/path/to/resource', {}) end end puppet-5.5.10/spec/unit/network/authorization_spec.rb0000644005276200011600000000406713417161721022662 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/network/http' require 'puppet/network/http/api/indirected_routes' require 'puppet/network/authorization' describe Puppet::Network::Authorization do class AuthTest include Puppet::Network::Authorization end subject { AuthTest.new } context "when creating an authconfig object" do before :each do # Other tests may have created an authconfig, so we have to undo that. @orig_auth_config = Puppet::Network::AuthConfigLoader.instance_variable_get(:@auth_config) @orig_auth_config_file = Puppet::Network::AuthConfigLoader.instance_variable_get(:@auth_config_file) Puppet::Network::AuthConfigLoader.instance_variable_set(:@auth_config, nil) Puppet::Network::AuthConfigLoader.instance_variable_set(:@auth_config_file, nil) end after :each do Puppet::Network::AuthConfigLoader.instance_variable_set(:@auth_config, @orig_auth_config) Puppet::Network::AuthConfigLoader.instance_variable_set(:@auth_config_file, @orig_auth_config_file) end it "creates default ACL entries if no file has been read" do Puppet::Network::AuthConfigParser.expects(:new_from_file).raises Errno::ENOENT Puppet::Network::DefaultAuthProvider.any_instance.expects(:insert_default_acl) subject.authconfig end end class TestAuthConfig def check_authorization(method, path, params); end end class TestAuthConfigLoader def self.authconfig TestAuthConfig.new end end context "when checking authorization" do after :each do Puppet::Network::Authorization.authconfigloader_class = nil end it "delegates to the authconfig object" do Puppet::Network::Authorization.authconfigloader_class = TestAuthConfigLoader TestAuthConfig.any_instance.expects(:check_authorization).with( :save, '/mypath', {:param1 => "value1"}).returns("yay, it worked!") expect(subject.check_authorization( :save, '/mypath', {:param1 => "value1"})).to eq("yay, it worked!") end end end puppet-5.5.10/spec/unit/network/authstore_spec.rb0000644005276200011600000003304713417161721022000 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'rbconfig' require 'puppet/network/authconfig' describe Puppet::Network::AuthStore do before :each do @authstore = Puppet::Network::AuthStore.new @authstore.reset_interpolation end describe "when checking if the acl has some entries" do it "should be empty if no ACE have been entered" do expect(@authstore).to be_empty end it "should not be empty if it is a global allow" do @authstore.allow('*') expect(@authstore).not_to be_empty end it "should not be empty if at least one allow has been entered" do @authstore.allow_ip('1.1.1.*') expect(@authstore).not_to be_empty end it "should not be empty if at least one deny has been entered" do @authstore.deny_ip('1.1.1.*') expect(@authstore).not_to be_empty end end describe "when checking global allow" do it "should not be enabled by default" do expect(@authstore).not_to be_globalallow expect(@authstore).not_to be_allowed('foo.bar.com', '192.168.1.1') end it "should always allow when enabled" do @authstore.allow('*') expect(@authstore).to be_globalallow expect(@authstore).to be_allowed('foo.bar.com', '192.168.1.1') end end describe "when checking a regex type of allow" do before :each do @authstore.allow('/^(test-)?host[0-9]+\.other-domain\.(com|org|net)$|some-domain\.com/') @ip = '192.168.1.1' end ['host5.other-domain.com', 'test-host12.other-domain.net', 'foo.some-domain.com'].each { |name| it "should allow the host #{name}" do expect(@authstore).to be_allowed(name, @ip) end } ['host0.some-other-domain.com',''].each { |name| it "should not allow the host #{name}" do expect(@authstore).not_to be_allowed(name, @ip) end } end end describe Puppet::Network::AuthStore::Declaration do ['100.101.99.98','100.100.100.100','1.2.3.4','11.22.33.44'].each { |ip| describe "when the pattern is a simple numeric IP such as #{ip}" do before :each do @declaration = Puppet::Network::AuthStore::Declaration.new(:allow_ip,ip) end it "should match the specified IP" do expect(@declaration).to be_match('www.testsite.org',ip) end it "should not match other IPs" do expect(@declaration).not_to be_match('www.testsite.org','200.101.99.98') end end (1..3).each { |n| describe "when the pattern is an IP mask with #{n} numeric segments and a *" do before :each do @ip_pattern = ip.split('.')[0,n].join('.')+'.*' @declaration = Puppet::Network::AuthStore::Declaration.new(:allow_ip,@ip_pattern) end it "should match an IP in the range" do expect(@declaration).to be_match('www.testsite.org',ip) end it "should not match other IPs" do expect(@declaration).not_to be_match('www.testsite.org','200.101.99.98') end it "should not match IPs that differ in the last non-wildcard segment" do other = ip.split('.') other[n-1].succ! expect(@declaration).not_to be_match('www.testsite.org',other.join('.')) end end } } describe "when the pattern is a numeric IP with a back reference" do pending("implementation of backreferences for IP") do before :each do @ip = '100.101.$1' @declaration = Puppet::Network::AuthStore::Declaration.new(:allow_ip,@ip).interpolate('12.34'.match(/(.*)/)) end it "should match an IP with the appropriate interpolation" do @declaration.should be_match('www.testsite.org',@ip.sub(/\$1/,'12.34')) end it "should not match other IPs" do @declaration.should_not be_match('www.testsite.org',@ip.sub(/\$1/,'66.34')) end end end [ "02001:0000:1234:0000:0000:C1C0:ABCD:0876", "2001:0000:1234:0000:00001:C1C0:ABCD:0876", " 2001:0000:1234:0000:0000:C1C0:ABCD:0876 0", "2001:0000:1234: 0000:0000:C1C0:ABCD:0876", "3ffe:0b00:0000:0001:0000:0000:000a", "FF02:0000:0000:0000:0000:0000:0000:0000:0001", "3ffe:b00::1::a", "1:2:3::4:5::7:8", "12345::6:7:8", "1::5:400.2.3.4", "1::5:260.2.3.4", "1::5:256.2.3.4", "1::5:1.256.3.4", "1::5:1.2.256.4", "1::5:1.2.3.256", "1::5:300.2.3.4", "1::5:1.300.3.4", "1::5:1.2.300.4", "1::5:1.2.3.300", "1::5:900.2.3.4", "1::5:1.900.3.4", "1::5:1.2.900.4", "1::5:1.2.3.900", "1::5:300.300.300.300", "1::5:3000.30.30.30", "1::400.2.3.4", "1::260.2.3.4", "1::256.2.3.4", "1::1.256.3.4", "1::1.2.256.4", "1::1.2.3.256", "1::300.2.3.4", "1::1.300.3.4", "1::1.2.300.4", "1::1.2.3.300", "1::900.2.3.4", "1::1.900.3.4", "1::1.2.900.4", "1::1.2.3.900", "1::300.300.300.300", "1::3000.30.30.30", "::400.2.3.4", "::260.2.3.4", "::256.2.3.4", "::1.256.3.4", "::1.2.256.4", "::1.2.3.256", "::300.2.3.4", "::1.300.3.4", "::1.2.300.4", "::1.2.3.300", "::900.2.3.4", "::1.900.3.4", "::1.2.900.4", "::1.2.3.900", "::300.300.300.300", "::3000.30.30.30", "2001:DB8:0:0:8:800:200C:417A:221", # unicast, full "FF01::101::2" # multicast, compressed ].each { |invalid_ip| describe "when the pattern is an invalid IPv6 address such as #{invalid_ip}" do it "should raise an exception" do expect { Puppet::Network::AuthStore::Declaration.new(:allow,invalid_ip) }.to raise_error(Puppet::AuthStoreError, /Invalid pattern/) end end } [ "1.2.3.4", "2001:0000:1234:0000:0000:C1C0:ABCD:0876", "3ffe:0b00:0000:0000:0001:0000:0000:000a", "FF02:0000:0000:0000:0000:0000:0000:0001", "0000:0000:0000:0000:0000:0000:0000:0001", "0000:0000:0000:0000:0000:0000:0000:0000", "::ffff:192.168.1.26", "2::10", "ff02::1", "fe80::", "2002::", "2001:db8::", "2001:0db8:1234::", "::ffff:0:0", "::1", "::ffff:192.168.1.1", "1:2:3:4:5:6:7:8", "1:2:3:4:5:6::8", "1:2:3:4:5::8", "1:2:3:4::8", "1:2:3::8", "1:2::8", "1::8", "1::2:3:4:5:6:7", "1::2:3:4:5:6", "1::2:3:4:5", "1::2:3:4", "1::2:3", "1::8", "::2:3:4:5:6:7", "::2:3:4:5:6", "::2:3:4:5", "::2:3:4", "::2:3", "::8", "1:2:3:4:5:6::", "1:2:3:4:5::", "1:2:3:4::", "1:2:3::", "1:2::", "1::", "1:2:3:4:5::7:8", "1:2:3:4::7:8", "1:2:3::7:8", "1:2::7:8", "1::7:8", "1:2:3:4:5:6:1.2.3.4", "1:2:3:4:5::1.2.3.4", "1:2:3:4::1.2.3.4", "1:2:3::1.2.3.4", "1:2::1.2.3.4", "1::1.2.3.4", "1:2:3:4::5:1.2.3.4", "1:2:3::5:1.2.3.4", "1:2::5:1.2.3.4", "1::5:1.2.3.4", "1::5:11.22.33.44", "fe80::217:f2ff:254.7.237.98", "fe80::217:f2ff:fe07:ed62", "2001:DB8:0:0:8:800:200C:417A", # unicast, full "FF01:0:0:0:0:0:0:101", # multicast, full "0:0:0:0:0:0:0:1", # loopback, full "0:0:0:0:0:0:0:0", # unspecified, full "2001:DB8::8:800:200C:417A", # unicast, compressed "FF01::101", # multicast, compressed "::1", # loopback, compressed, non-routable "::", # unspecified, compressed, non-routable "0:0:0:0:0:0:13.1.68.3", # IPv4-compatible IPv6 address, full, deprecated "0:0:0:0:0:FFFF:129.144.52.38", # IPv4-mapped IPv6 address, full "::13.1.68.3", # IPv4-compatible IPv6 address, compressed, deprecated "::FFFF:129.144.52.38", # IPv4-mapped IPv6 address, compressed "2001:0DB8:0000:CD30:0000:0000:0000:0000/60", # full, with prefix "2001:0DB8::CD30:0:0:0:0/60", # compressed, with prefix "2001:0DB8:0:CD30::/60", # compressed, with prefix #2 "::/128", # compressed, unspecified address type, non-routable "::1/128", # compressed, loopback address type, non-routable "FF00::/8", # compressed, multicast address type "FE80::/10", # compressed, link-local unicast, non-routable "FEC0::/10", # compressed, site-local unicast, deprecated "127.0.0.1", # standard IPv4, loopback, non-routable "0.0.0.0", # standard IPv4, unspecified, non-routable "255.255.255.255", # standard IPv4 "fe80:0000:0000:0000:0204:61ff:fe9d:f156", "fe80:0:0:0:204:61ff:fe9d:f156", "fe80::204:61ff:fe9d:f156", "fe80:0000:0000:0000:0204:61ff:254.157.241.086", "fe80:0:0:0:204:61ff:254.157.241.86", "fe80::204:61ff:254.157.241.86", "::1", "fe80::", "fe80::1" ].each { |ip| describe "when the pattern is a valid IP such as #{ip}" do before :each do @declaration = Puppet::Network::AuthStore::Declaration.new(:allow_ip,ip) end it "should match the specified IP" do expect(@declaration).to be_match('www.testsite.org',ip) end it "should not match other IPs" do expect(@declaration).not_to be_match('www.testsite.org','200.101.99.98') end end unless ip =~ /:.*\./ # Hybrid IPs aren't supported by ruby's ipaddr } [ "::2:3:4:5:6:7:8", ].each { |ip| describe "when the pattern is a valid IP such as #{ip}" do let(:declaration) do Puppet::Network::AuthStore::Declaration.new(:allow_ip,ip) end issue_7477 = !(IPAddr.new(ip) rescue false) describe "on rubies with a fix for issue [7477](https://goo.gl/Bb1LU)", :if => issue_7477 it "should match the specified IP" do expect(declaration).to be_match('www.testsite.org',ip) end it "should not match other IPs" do expect(declaration).not_to be_match('www.testsite.org','200.101.99.98') end end } { 'spirit.mars.nasa.gov' => 'a PQDN', 'ratchet.2ndsiteinc.com' => 'a PQDN with digits', 'a.c.ru' => 'a PQDN with short segments', }.each {|pqdn,desc| describe "when the pattern is #{desc}" do before :each do @host = pqdn @declaration = Puppet::Network::AuthStore::Declaration.new(:allow,@host) end it "should match the specified PQDN" do expect(@declaration).to be_match(@host,'200.101.99.98') end it "should not match a similar FQDN" do pending "FQDN consensus" expect(@declaration).not_to be_match(@host+'.','200.101.99.98') end end } ['abc.12seps.edu.phisher.biz','www.google.com','slashdot.org'].each { |host| (1...(host.split('.').length)).each { |n| describe "when the pattern is #{"*."+host.split('.')[-n,n].join('.')}" do before :each do @pattern = "*."+host.split('.')[-n,n].join('.') @declaration = Puppet::Network::AuthStore::Declaration.new(:allow,@pattern) end it "should match #{host}" do expect(@declaration).to be_match(host,'1.2.3.4') end it "should not match www.testsite.gov" do expect(@declaration).not_to be_match('www.testsite.gov','200.101.99.98') end it "should not match hosts that differ in the first non-wildcard segment" do other = host.split('.') other[-n].succ! expect(@declaration).not_to be_match(other.join('.'),'1.2.3.4') end end } } describe "when the pattern is a FQDN" do before :each do @host = 'spirit.mars.nasa.gov.' @declaration = Puppet::Network::AuthStore::Declaration.new(:allow,@host) end it "should match the specified FQDN" do pending "FQDN consensus" expect(@declaration).to be_match(@host,'200.101.99.98') end it "should not match a similar PQDN" do expect(@declaration).not_to be_match(@host[0..-2],'200.101.99.98') end end describe "when the pattern is an opaque string with a back reference" do before :each do @host = 'c216f41a-f902-4bfb-a222-850dd957bebb' @item = "/catalog/#{@host}" @pattern = %{^/catalog/([^/]+)$} @declaration = Puppet::Network::AuthStore::Declaration.new(:allow,'$1') end it "should match an IP with the appropriate interpolation" do expect(@declaration.interpolate(@item.match(@pattern))).to be_match(@host,'10.0.0.5') end end describe "when the pattern is an opaque string with a back reference and the matched data contains dots" do before :each do @host = 'admin.mgmt.nym1' @item = "/catalog/#{@host}" @pattern = %{^/catalog/([^/]+)$} @declaration = Puppet::Network::AuthStore::Declaration.new(:allow,'$1') end it "should match a name with the appropriate interpolation" do expect(@declaration.interpolate(@item.match(@pattern))).to be_match(@host,'10.0.0.5') end end describe "when the pattern is an opaque string with a back reference and the matched data contains dots with an initial prefix that looks like an IP address" do before :each do @host = '01.admin.mgmt.nym1' @item = "/catalog/#{@host}" @pattern = %{^/catalog/([^/]+)$} @declaration = Puppet::Network::AuthStore::Declaration.new(:allow,'$1') end it "should match a name with the appropriate interpolation" do expect(@declaration.interpolate(@item.match(@pattern))).to be_match(@host,'10.0.0.5') end end describe "when comparing patterns" do before :each do @ip = Puppet::Network::AuthStore::Declaration.new(:allow,'127.0.0.1') @host_name = Puppet::Network::AuthStore::Declaration.new(:allow,'www.hard_knocks.edu') @opaque = Puppet::Network::AuthStore::Declaration.new(:allow,'hey_dude') end it "should consider ip addresses before host names" do expect(@ip < @host_name).to be_truthy end it "should consider ip addresses before opaque strings" do expect(@ip < @opaque).to be_truthy end it "should consider host_names before opaque strings" do expect(@host_name < @opaque).to be_truthy end end end puppet-5.5.10/spec/unit/network/format_handler_spec.rb0000644005276200011600000000662313417161721022747 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/network/format_handler' describe Puppet::Network::FormatHandler do before(:each) do @saved_formats = Puppet::Network::FormatHandler.instance_variable_get(:@formats).dup Puppet::Network::FormatHandler.instance_variable_set(:@formats, {}) end after(:each) do Puppet::Network::FormatHandler.instance_variable_set(:@formats, @saved_formats) end describe "when creating formats" do it "should instance_eval any block provided when creating a format" do format = Puppet::Network::FormatHandler.create(:test_format) do def asdfghjkl; end end expect(format).to respond_to(:asdfghjkl) end end describe "when retrieving formats" do let!(:format) { Puppet::Network::FormatHandler.create(:the_format, :extension => "foo", :mime => "foo/bar") } it "should be able to retrieve a format by name" do expect(Puppet::Network::FormatHandler.format(:the_format)).to equal(format) end it "should be able to retrieve a format by extension" do expect(Puppet::Network::FormatHandler.format_by_extension("foo")).to equal(format) end it "should return nil if asked to return a format by an unknown extension" do expect(Puppet::Network::FormatHandler.format_by_extension("yayness")).to be_nil end it "should be able to retrieve formats by name irrespective of case" do expect(Puppet::Network::FormatHandler.format(:The_Format)).to equal(format) end it "should be able to retrieve a format by mime type" do expect(Puppet::Network::FormatHandler.mime("foo/bar")).to equal(format) end it "should be able to retrieve a format by mime type irrespective of case" do expect(Puppet::Network::FormatHandler.mime("Foo/Bar")).to equal(format) end end describe "#most_suitable_formats_for" do before :each do Puppet::Network::FormatHandler.create(:one, :extension => "foo", :mime => "text/one") Puppet::Network::FormatHandler.create(:two, :extension => "bar", :mime => "application/two") end let(:format_one) { Puppet::Network::FormatHandler.format(:one) } let(:format_two) { Puppet::Network::FormatHandler.format(:two) } def suitable_in_setup_formats(accepted) Puppet::Network::FormatHandler.most_suitable_formats_for(accepted, [:one, :two]) end it "finds the most preferred format when anything is acceptable" do expect(Puppet::Network::FormatHandler.most_suitable_formats_for(["*/*"], [:two, :one])).to eq([format_two]) end it "finds no format when none are acceptable" do expect(suitable_in_setup_formats(["three"])).to eq([]) end it "returns only the accepted and supported format" do expect(suitable_in_setup_formats(["three", "two"])).to eq([format_two]) end it "returns only accepted and supported formats, in order of accepted" do expect(suitable_in_setup_formats(["three", "two", "one"])).to eq([format_two, format_one]) end it "allows specifying acceptable formats by mime type" do expect(suitable_in_setup_formats(["text/one"])).to eq([format_one]) end it "ignores quality specifiers" do expect(suitable_in_setup_formats(["two;q=0.8", "text/one;q=0.9"])).to eq([format_two, format_one]) end it "allows specifying acceptable formats by canonical name" do expect(suitable_in_setup_formats([:one])).to eq([format_one]) end end end puppet-5.5.10/spec/unit/network/format_spec.rb0000644005276200011600000001726313417161721021254 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/network/format' # A class with all of the necessary # hooks. class FormatRenderer def self.to_multiple_my_format(list) end def self.from_multiple_my_format(text) end def self.from_my_format(text) end def to_my_format end end describe Puppet::Network::Format do describe "when initializing" do it "should require a name" do expect { Puppet::Network::Format.new }.to raise_error(ArgumentError) end it "should be able to provide its name" do expect(Puppet::Network::Format.new(:my_format).name).to eq(:my_format) end it "should always convert its name to a downcased symbol" do expect(Puppet::Network::Format.new(:My_Format).name).to eq(:my_format) end it "should be able to set its downcased mime type at initialization" do format = Puppet::Network::Format.new(:my_format, :mime => "Foo/Bar") expect(format.mime).to eq("foo/bar") end it "should default to text plus the name of the format as the mime type" do expect(Puppet::Network::Format.new(:my_format).mime).to eq("text/my_format") end it "should fail if unsupported options are provided" do expect { Puppet::Network::Format.new(:my_format, :foo => "bar") }.to raise_error(ArgumentError) end end describe "instances" do before do @format = Puppet::Network::Format.new(:my_format) end it "should support being confined" do expect(@format).to respond_to(:confine) end it "should not be considered suitable if confinement conditions are not met" do @format.confine :true => false expect(@format).not_to be_suitable end it "should be able to determine if a class is supported" do expect(@format).to respond_to(:supported?) end it "should consider a class to be supported if it has the individual and multiple methods for rendering and interning" do expect(@format).to be_supported(FormatRenderer) end it "should default to its required methods being the individual and multiple methods for rendering and interning" do expect(Puppet::Network::Format.new(:foo).required_methods.sort { |a,b| a.to_s <=> b.to_s }).to eq([:intern_method, :intern_multiple_method, :render_multiple_method, :render_method].sort { |a,b| a.to_s <=> b.to_s }) end it "should consider a class supported if the provided class has all required methods present" do format = Puppet::Network::Format.new(:foo) [:intern_method, :intern_multiple_method, :render_multiple_method, :render_method].each do |method| format.expects(:required_method_present?).with { |name, klass, type| name == method and klass == String }.returns true end expect(format).to be_required_methods_present(String) end it "should consider a class not supported if any required methods are missing from the provided class" do format = Puppet::Network::Format.new(:foo) format.stubs(:required_method_present?).returns true format.expects(:required_method_present?).with { |name, *args| name == :intern_method }.returns false expect(format).not_to be_required_methods_present(String) end it "should be able to specify the methods required for support" do expect(Puppet::Network::Format.new(:foo, :required_methods => [:render_method, :intern_method]).required_methods).to eq([:render_method, :intern_method]) end it "should only test for required methods if specific methods are specified as required" do format = Puppet::Network::Format.new(:foo, :required_methods => [:intern_method]) format.expects(:required_method_present?).with { |name, klass, type| name == :intern_method } format.required_methods_present?(String) end it "should not consider a class supported unless the format is suitable" do @format.expects(:suitable?).returns false expect(@format).not_to be_supported(FormatRenderer) end it "should always downcase mimetypes" do @format.mime = "Foo/Bar" expect(@format.mime).to eq("foo/bar") end it "should support having a weight" do expect(@format).to respond_to(:weight) end it "should default to a weight of of 5" do expect(@format.weight).to eq(5) end it "should be able to override its weight at initialization" do expect(Puppet::Network::Format.new(:foo, :weight => 1).weight).to eq(1) end it "should default to its extension being equal to its name" do expect(Puppet::Network::Format.new(:foo).extension).to eq("foo") end it "should support overriding the extension" do expect(Puppet::Network::Format.new(:foo, :extension => "bar").extension).to eq("bar") end it "doesn't support charset by default" do expect(Puppet::Network::Format.new(:foo).charset).to be_nil end it "allows charset to be set to 'utf-8'" do expect(Puppet::Network::Format.new(:foo, :charset => Encoding::UTF_8).charset).to eq(Encoding::UTF_8) end [:intern_method, :intern_multiple_method, :render_multiple_method, :render_method].each do |method| it "should allow assignment of the #{method}" do expect(Puppet::Network::Format.new(:foo, method => :foo).send(method)).to eq(:foo) end end end describe "when converting between instances and formatted text" do before do @format = Puppet::Network::Format.new(:my_format) @instance = FormatRenderer.new end it "should have a method for rendering a single instance" do expect(@format).to respond_to(:render) end it "should have a method for rendering multiple instances" do expect(@format).to respond_to(:render_multiple) end it "should have a method for interning text" do expect(@format).to respond_to(:intern) end it "should have a method for interning text into multiple instances" do expect(@format).to respond_to(:intern_multiple) end it "should return the results of calling the instance-specific render method if the method is present" do @instance.expects(:to_my_format).returns "foo" expect(@format.render(@instance)).to eq("foo") end it "should return the results of calling the class-specific render_multiple method if the method is present" do @instance.class.expects(:to_multiple_my_format).returns ["foo"] expect(@format.render_multiple([@instance])).to eq(["foo"]) end it "should return the results of calling the class-specific intern method if the method is present" do FormatRenderer.expects(:from_my_format).with("foo").returns @instance expect(@format.intern(FormatRenderer, "foo")).to equal(@instance) end it "should return the results of calling the class-specific intern_multiple method if the method is present" do FormatRenderer.expects(:from_multiple_my_format).with("foo").returns [@instance] expect(@format.intern_multiple(FormatRenderer, "foo")).to eq([@instance]) end it "should fail if asked to render and the instance does not respond to 'to_'" do expect { @format.render("foo") }.to raise_error(NotImplementedError) end it "should fail if asked to intern and the class does not respond to 'from_'" do expect { @format.intern(String, "foo") }.to raise_error(NotImplementedError) end it "should fail if asked to intern multiple and the class does not respond to 'from_multiple_'" do expect { @format.intern_multiple(String, "foo") }.to raise_error(NotImplementedError) end it "should fail if asked to render multiple and the instance does not respond to 'to_multiple_'" do expect { @format.render_multiple(["foo", "bar"]) }.to raise_error(NotImplementedError) end end end puppet-5.5.10/spec/unit/network/format_support_spec.rb0000644005276200011600000001630313417161721023042 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/network/format_handler' require 'puppet/network/format_support' class FormatTester include Puppet::Network::FormatSupport end describe Puppet::Network::FormatHandler do before(:each) do @saved_formats = Puppet::Network::FormatHandler.instance_variable_get(:@formats).dup Puppet::Network::FormatHandler.instance_variable_set(:@formats, {}) end after(:each) do Puppet::Network::FormatHandler.instance_variable_set(:@formats, @saved_formats) end describe "when listing formats" do before(:each) do one = Puppet::Network::FormatHandler.create(:one, :weight => 1) one.stubs(:supported?).returns(true) two = Puppet::Network::FormatHandler.create(:two, :weight => 6) two.stubs(:supported?).returns(true) three = Puppet::Network::FormatHandler.create(:three, :weight => 2) three.stubs(:supported?).returns(true) four = Puppet::Network::FormatHandler.create(:four, :weight => 8) four.stubs(:supported?).returns(false) end it "should return all supported formats in decreasing order of weight" do expect(FormatTester.supported_formats).to eq([:two, :three, :one]) end end it "should return the first format as the default format" do FormatTester.expects(:supported_formats).returns [:one, :two] expect(FormatTester.default_format).to eq(:one) end describe "with a preferred serialization format setting" do before do one = Puppet::Network::FormatHandler.create(:one, :weight => 1) one.stubs(:supported?).returns(true) two = Puppet::Network::FormatHandler.create(:two, :weight => 6) two.stubs(:supported?).returns(true) end describe "that is supported" do before do Puppet[:preferred_serialization_format] = :one end it "should return the preferred serialization format first" do expect(FormatTester.supported_formats).to eq([:one, :two]) end end describe "that is not supported" do before do Puppet[:preferred_serialization_format] = :unsupported end it "should return the default format first" do expect(FormatTester.supported_formats).to eq([:two, :one]) end it "should log a debug message" do Puppet.expects(:debug).with("Value of 'preferred_serialization_format' (unsupported) is invalid for FormatTester, using default (two)") Puppet.expects(:debug).with("FormatTester supports formats: two one") FormatTester.supported_formats end end end describe "when using formats" do let(:format) { Puppet::Network::FormatHandler.create(:my_format, :mime => "text/myformat") } it "should use the Format to determine whether a given format is supported" do format.expects(:supported?).with(FormatTester) FormatTester.support_format?(:my_format) end it "should call the format-specific converter when asked to convert from a given format" do format.expects(:intern).with(FormatTester, "mydata") FormatTester.convert_from(:my_format, "mydata") end it "should call the format-specific converter when asked to convert from a given format by mime-type" do format.expects(:intern).with(FormatTester, "mydata") FormatTester.convert_from("text/myformat", "mydata") end it "should call the format-specific converter when asked to convert from a given format by format instance" do format.expects(:intern).with(FormatTester, "mydata") FormatTester.convert_from(format, "mydata") end it "should raise a FormatError when an exception is encountered when converting from a format" do format.expects(:intern).with(FormatTester, "mydata").raises "foo" expect do FormatTester.convert_from(:my_format, "mydata") end.to raise_error( Puppet::Network::FormatHandler::FormatError, 'Could not intern from my_format: foo' ) end it "should be able to use a specific hook for converting into multiple instances" do format.expects(:intern_multiple).with(FormatTester, "mydata") FormatTester.convert_from_multiple(:my_format, "mydata") end it "should raise a FormatError when an exception is encountered when converting multiple items from a format" do format.expects(:intern_multiple).with(FormatTester, "mydata").raises "foo" expect do FormatTester.convert_from_multiple(:my_format, "mydata") end.to raise_error(Puppet::Network::FormatHandler::FormatError, 'Could not intern_multiple from my_format: foo') end it "should be able to use a specific hook for rendering multiple instances" do format.expects(:render_multiple).with("mydata") FormatTester.render_multiple(:my_format, "mydata") end it "should raise a FormatError when an exception is encountered when rendering multiple items into a format" do format.expects(:render_multiple).with("mydata").raises "foo" expect do FormatTester.render_multiple(:my_format, "mydata") end.to raise_error(Puppet::Network::FormatHandler::FormatError, 'Could not render_multiple to my_format: foo') end end describe "when an instance" do let(:format) { Puppet::Network::FormatHandler.create(:foo, :mime => "text/foo") } it "should list as supported a format that reports itself supported" do format.expects(:supported?).returns true expect(FormatTester.new.support_format?(:foo)).to be_truthy end it "should raise a FormatError when a rendering error is encountered" do tester = FormatTester.new format.expects(:render).with(tester).raises "eh" expect do tester.render(:foo) end.to raise_error(Puppet::Network::FormatHandler::FormatError, 'Could not render to foo: eh') end it "should call the format-specific converter when asked to convert to a given format" do tester = FormatTester.new format.expects(:render).with(tester).returns "foo" expect(tester.render(:foo)).to eq("foo") end it "should call the format-specific converter when asked to convert to a given format by mime-type" do tester = FormatTester.new format.expects(:render).with(tester).returns "foo" expect(tester.render("text/foo")).to eq("foo") end it "should call the format converter when asked to convert to a given format instance" do tester = FormatTester.new format.expects(:render).with(tester).returns "foo" expect(tester.render(format)).to eq("foo") end it "should render to the default format if no format is provided when rendering" do FormatTester.expects(:default_format).returns :foo tester = FormatTester.new format.expects(:render).with(tester) tester.render end it "should call the format-specific converter when asked for the mime-type of a given format" do tester = FormatTester.new format.expects(:mime).returns "text/foo" expect(tester.mime(:foo)).to eq("text/foo") end it "should return the default format mime-type if no format is provided" do FormatTester.expects(:default_format).returns :foo tester = FormatTester.new format.expects(:mime).returns "text/foo" expect(tester.mime).to eq("text/foo") end end end puppet-5.5.10/spec/unit/network/http/0000755005276200011600000000000013417162176017400 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/network/http/api/0000755005276200011600000000000013417162176020151 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/network/http/api/master/0000755005276200011600000000000013417162176021444 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/network/http/api/master/v3/0000755005276200011600000000000013417162176021774 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/network/http/api/master/v3/environments_spec.rb0000644005276200011600000000512413417161721026057 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/node/environment' require 'puppet/network/http' require 'matchers/json' describe Puppet::Network::HTTP::API::Master::V3::Environments do include JSONMatchers it "responds with all of the available environments" do environment = Puppet::Node::Environment.create(:production, ["/first", "/second"], '/manifests') loader = Puppet::Environments::Static.new(environment) handler = Puppet::Network::HTTP::API::Master::V3::Environments.new(loader) response = Puppet::Network::HTTP::MemoryResponse.new handler.call(Puppet::Network::HTTP::Request.from_hash(:headers => { 'accept' => 'application/json' }), response) expect(response.code).to eq(200) expect(response.type).to eq("application/json") expect(JSON.parse(response.body)).to eq({ "search_paths" => loader.search_paths, "environments" => { "production" => { "settings" => { "modulepath" => [File.expand_path("/first"), File.expand_path("/second")], "manifest" => File.expand_path("/manifests"), "environment_timeout" => 0, "config_version" => "" } } } }) end it "the response conforms to the environments schema for unlimited timeout" do conf_stub = stub 'conf_stub' conf_stub.expects(:environment_timeout).returns(Float::INFINITY) environment = Puppet::Node::Environment.create(:production, []) env_loader = Puppet::Environments::Static.new(environment) env_loader.expects(:get_conf).with(:production).returns(conf_stub) handler = Puppet::Network::HTTP::API::Master::V3::Environments.new(env_loader) response = Puppet::Network::HTTP::MemoryResponse.new handler.call(Puppet::Network::HTTP::Request.from_hash(:headers => { 'accept' => 'application/json' }), response) expect(response.body).to validate_against('api/schemas/environments.json') end it "the response conforms to the environments schema for integer timeout" do conf_stub = stub 'conf_stub' conf_stub.expects(:environment_timeout).returns(1) environment = Puppet::Node::Environment.create(:production, []) env_loader = Puppet::Environments::Static.new(environment) env_loader.expects(:get_conf).with(:production).returns(conf_stub) handler = Puppet::Network::HTTP::API::Master::V3::Environments.new(env_loader) response = Puppet::Network::HTTP::MemoryResponse.new handler.call(Puppet::Network::HTTP::Request.from_hash(:headers => { 'accept' => 'application/json' }), response) expect(response.body).to validate_against('api/schemas/environments.json') end end puppet-5.5.10/spec/unit/network/http/api/master/v3/authorization_spec.rb0000644005276200011600000000273213417161722026233 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/network/http' describe Puppet::Network::HTTP::API::Master::V3::Authorization do HTTP = Puppet::Network::HTTP let(:response) { HTTP::MemoryResponse.new } let(:authz) { HTTP::API::Master::V3::Authorization.new } let(:noop_handler) { lambda do |request, response| end } it "accepts v3 api requests that match allowed authconfig entries" do request = HTTP::Request.from_hash({ :path => "/v3/environments", :method => "GET", :params => { :authenticated => true, :node => "testing", :ip => "127.0.0.1" } }) authz.stubs(:authconfig).returns(Puppet::Network::AuthConfigParser.new(<<-AUTH).parse) path /v3/environments method find allow * AUTH handler = authz.wrap do noop_handler end expect do handler.call(request, response) end.to_not raise_error end it "rejects v3 api requests that are disallowed by authconfig entries" do request = HTTP::Request.from_hash({ :path => "/v3/environments", :method => "GET", :params => { :authenticated => true, :node => "testing", :ip => "127.0.0.1" } }) authz.stubs(:authconfig).returns(Puppet::Network::AuthConfigParser.new(<<-AUTH).parse) path /v3/environments method find auth any deny testing AUTH handler = authz.wrap do noop_handler end expect do handler.call(request, response) end.to raise_error(HTTP::Error::HTTPNotAuthorizedError, /Forbidden request/) end end puppet-5.5.10/spec/unit/network/http/api/master/v3/environment_spec.rb0000644005276200011600000001354313417161722025701 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/network/http' describe Puppet::Network::HTTP::API::Master::V3::Environment do let(:response) { Puppet::Network::HTTP::MemoryResponse.new } let(:environment) { Puppet::Node::Environment.create(:production, [], '/manifests') } let(:loader) { Puppet::Environments::Static.new(environment) } around :each do |example| Puppet[:app_management] = true Puppet.override(:environments => loader) do Puppet::Type.newtype :sql, :is_capability => true do newparam :name, :namevar => true end Puppet::Type.newtype :http, :is_capability => true do newparam :name, :namevar => true end example.run end end it "returns the environment catalog" do request = Puppet::Network::HTTP::Request.from_hash(:headers => { 'accept' => 'application/json' }, :routing_path => "environment/production") subject.call(request, response) expect(response.code).to eq(200) catalog = JSON.parse(response.body) expect(catalog['environment']).to eq('production') expect(catalog['applications']).to eq({}) end describe "processing the environment catalog" do def compile_site_to_catalog(site, code_id=nil) Puppet[:code] = <<-MANIFEST define db() { } Db produces Sql { } define web() { } Web consumes Sql { } Web produces Http { } application myapp() { db { $name: export => Sql[$name], } web { $name: consume => Sql[$name], export => Http[$name], } } site { #{site} } MANIFEST Puppet::Parser::EnvironmentCompiler.compile(environment, code_id).filter { |r| r.virtual? } end it "includes specified applications" do catalog = compile_site_to_catalog <<-MANIFEST myapp { 'test': nodes => { Node['foo.example.com'] => Db['test'], Node['bar.example.com'] => Web['test'], }, } MANIFEST result = subject.build_environment_graph(catalog) expect(result[:applications]).to eq({'Myapp[test]' => {'Db[test]' => {:produces => ['Sql[test]'], :consumes => [], :node => 'foo.example.com'}, 'Web[test]' => {:produces => ['Http[test]'], :consumes => ['Sql[test]'], :node => 'bar.example.com'}}}) end it "fails if a component isn't mapped to a node" do catalog = compile_site_to_catalog <<-MANIFEST myapp { 'test': nodes => { Node['foo.example.com'] => Db['test'], } } MANIFEST expect { subject.build_environment_graph(catalog) }.to raise_error(Puppet::ParseError, /has components without assigned nodes/) end it "fails if a non-existent component is mapped to a node" do catalog = compile_site_to_catalog <<-MANIFEST myapp { 'test': nodes => { Node['foo.example.com'] => [ Db['test'], Web['test'], Web['foobar'] ], } } MANIFEST expect { subject.build_environment_graph(catalog) }.to raise_error(Puppet::ParseError, /assigns nodes to non-existent components/) end it "fails if a component is mapped twice" do catalog = compile_site_to_catalog <<-MANIFEST myapp { 'test': nodes => { Node['foo.example.com'] => [ Db['test'], Web['test'] ], Node['bar.example.com'] => [ Web['test'] ], } } MANIFEST expect { subject.build_environment_graph(catalog) }.to raise_error(Puppet::ParseError, /assigns multiple nodes to component/) end it "fails if an application maps components from other applications" do catalog = compile_site_to_catalog <<-MANIFEST myapp { 'test': nodes => { Node['foo.example.com'] => [ Db['test'], Web['test'] ], } } myapp { 'other': nodes => { Node['foo.example.com'] => [ Db['other'], Web['other'], Web['test'] ], } } MANIFEST expect { subject.build_environment_graph(catalog) }.to raise_error(Puppet::ParseError, /assigns nodes to non-existent components/) end it "doesn't fail if the catalog contains a node cycle" do catalog = compile_site_to_catalog <<-MANIFEST myapp { 'test': nodes => { Node['foo.example.com'] => [ Db['test'] ], Node['bar.example.com'] => [ Web['test'] ], } } myapp { 'other': nodes => { Node['foo.example.com'] => [ Web['other'] ], Node['bar.example.com'] => [ Db['other'] ], } } MANIFEST expect { subject.build_environment_graph(catalog) }.not_to raise_error end end it "returns 404 if the environment doesn't exist" do request = Puppet::Network::HTTP::Request.from_hash(:routing_path => "environment/development") expect { subject.call(request, response) }.to raise_error(Puppet::Network::HTTP::Error::HTTPNotFoundError, /development is not a known environment/) end it "omits code_id if unspecified" do request = Puppet::Network::HTTP::Request.from_hash(:routing_path => "environment/production") subject.call(request, response) expect(JSON.parse(response.body)['code_id']).to be_nil end it "includes code_id if specified" do request = Puppet::Network::HTTP::Request.from_hash(:params => {:code_id => '12345'}, :routing_path => "environment/production") subject.call(request, response) expect(JSON.parse(response.body)['code_id']).to eq('12345') end it "uses code_id from the catalog if it differs from the request" do request = Puppet::Network::HTTP::Request.from_hash(:params => {:code_id => '12345'}, :routing_path => "environment/production") Puppet::Resource::Catalog.any_instance.stubs(:code_id).returns('67890') subject.call(request, response) expect(JSON.parse(response.body)['code_id']).to eq('67890') end end puppet-5.5.10/spec/unit/network/http/api/master/v3_spec.rb0000644005276200011600000000411713417161721023331 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/network/http' require 'puppet_spec/network' describe Puppet::Network::HTTP::API::Master::V3 do include PuppetSpec::Network let(:response) { Puppet::Network::HTTP::MemoryResponse.new } let(:master_url_prefix) { "#{Puppet::Network::HTTP::MASTER_URL_PREFIX}/v3" } let(:master_routes) { Puppet::Network::HTTP::Route. path(Regexp.new("#{Puppet::Network::HTTP::MASTER_URL_PREFIX}/")). any. chain(Puppet::Network::HTTP::API::Master::V3.routes) } it "mounts the environments endpoint" do request = Puppet::Network::HTTP::Request.from_hash(:path => "#{master_url_prefix}/environments") master_routes.process(request, response) expect(response.code).to eq(200) end it "mounts the environment endpoint" do request = Puppet::Network::HTTP::Request.from_hash(:path => "#{master_url_prefix}/environment/production") master_routes.process(request, response) expect(response.code).to eq(200) end it "matches only complete routes" do request = Puppet::Network::HTTP::Request.from_hash(:path => "#{master_url_prefix}/foo/environments") expect { master_routes.process(request, response) }.to raise_error(Puppet::Network::HTTP::Error::HTTPNotFoundError) request = Puppet::Network::HTTP::Request.from_hash(:path => "#{master_url_prefix}/foo/environment/production") expect { master_routes.process(request, response) }.to raise_error(Puppet::Network::HTTP::Error::HTTPNotFoundError) end it "mounts indirected routes" do request = Puppet::Network::HTTP::Request. from_hash(:path => "#{master_url_prefix}/node/foo", :params => {:environment => "production"}, :headers => {"accept" => "application/json"}) master_routes.process(request, response) expect(response.code).to eq(200) end it "responds to unknown paths by raising not_found_error" do request = Puppet::Network::HTTP::Request.from_hash(:path => "#{master_url_prefix}/unknown") expect { master_routes.process(request, response) }.to raise_error(not_found_error) end end puppet-5.5.10/spec/unit/network/http/api/ca/0000755005276200011600000000000013417162176020534 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/network/http/api/ca/v1_spec.rb0000644005276200011600000000147713417161722022426 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/network/http' describe Puppet::Network::HTTP::API::CA::V1 do let(:response) { Puppet::Network::HTTP::MemoryResponse.new } let(:ca_url_prefix) { "#{Puppet::Network::HTTP::CA_URL_PREFIX}/v1"} let(:ca_routes) { Puppet::Network::HTTP::Route. path(Regexp.new("#{Puppet::Network::HTTP::CA_URL_PREFIX}/")). any. chain(Puppet::Network::HTTP::API::CA::V1.routes) } it "mounts ca routes" do Puppet::SSL::Certificate.indirection.stubs(:find).returns "foo" request = Puppet::Network::HTTP::Request. from_hash(:path => "#{ca_url_prefix}/certificate/foo", :params => {:environment => "production"}, :headers => {"accept" => "s"}) ca_routes.process(request, response) expect(response.code).to eq(200) end end puppet-5.5.10/spec/unit/network/http/api/indirected_routes_spec.rb0000644005276200011600000005163713417161722025233 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/network/http' require 'puppet/network/http/api/indirected_routes' require 'puppet/indirector_testing' require 'puppet_spec/network' describe Puppet::Network::HTTP::API::IndirectedRoutes do include PuppetSpec::Network let(:indirection) { Puppet::IndirectorTesting.indirection } let(:handler) { Puppet::Network::HTTP::API::IndirectedRoutes.new } let(:response) { Puppet::Network::HTTP::MemoryResponse.new } before do Puppet::IndirectorTesting.indirection.terminus_class = :memory Puppet::IndirectorTesting.indirection.terminus.clear handler.stubs(:warn_if_near_expiration) end describe "when converting a URI into a request" do let(:environment) { Puppet::Node::Environment.create(:env, []) } let(:env_loaders) { Puppet::Environments::Static.new(environment) } let(:params) { { :environment => "env" } } before do handler.stubs(:handler).returns "foo" end around do |example| Puppet.override(:environments => env_loaders) do example.run end end it "should get the environment from a query parameter" do expect(handler.uri2indirection("GET", "#{master_url_prefix}/node/bar", params)[3][:environment].to_s).to eq("env") end it "should fail if there is no environment specified" do expect(lambda { handler.uri2indirection("GET", "#{master_url_prefix}/node/bar", {}) }).to raise_error(bad_request_error) end it "should fail if the environment is not alphanumeric" do expect(lambda { handler.uri2indirection("GET", "#{master_url_prefix}/node/bar", {:environment => "env ness"}) }).to raise_error(bad_request_error) end it "should fail if the indirection does not match the prefix" do expect(lambda { handler.uri2indirection("GET", "#{master_url_prefix}/certificate/foo", params) }).to raise_error(bad_request_error) end it "should fail if the indirection does not have the correct version" do expect(lambda { handler.uri2indirection("GET", "#{Puppet::Network::HTTP::CA_URL_PREFIX}/v3/certificate/foo", params) }).to raise_error(bad_request_error) end it "should not pass a buck_path parameter through (See Bugs #13553, #13518, #13511)" do expect(handler.uri2indirection("GET", "#{master_url_prefix}/node/bar", { :environment => "env", :bucket_path => "/malicious/path" })[3]).not_to include({ :bucket_path => "/malicious/path" }) end it "should pass allowed parameters through" do expect(handler.uri2indirection("GET", "#{master_url_prefix}/node/bar", { :environment => "env", :allowed_param => "value" })[3]).to include({ :allowed_param => "value" }) end it "should return the environment as a Puppet::Node::Environment" do expect(handler.uri2indirection("GET", "#{master_url_prefix}/node/bar", params)[3][:environment]).to be_a(Puppet::Node::Environment) end it "should use the first field of the URI as the indirection name" do expect(handler.uri2indirection("GET", "#{master_url_prefix}/node/bar", params)[0].name).to eq(:node) end it "should fail if the indirection name is not alphanumeric" do expect(lambda { handler.uri2indirection("GET", "#{master_url_prefix}/foo ness/bar", params) }).to raise_error(bad_request_error) end it "should use the remainder of the URI as the indirection key" do expect(handler.uri2indirection("GET", "#{master_url_prefix}/node/bar", params)[2]).to eq("bar") end it "should support the indirection key being a /-separated file path" do expect(handler.uri2indirection("GET", "#{master_url_prefix}/node/bee/baz/bomb", params)[2]).to eq("bee/baz/bomb") end it "should fail if no indirection key is specified" do expect(lambda { handler.uri2indirection("GET", "#{master_url_prefix}/node", params) }).to raise_error(bad_request_error) end it "should choose 'find' as the indirection method if the http method is a GET and the indirection name is singular" do expect(handler.uri2indirection("GET", "#{master_url_prefix}/node/bar", params)[1]).to eq(:find) end it "should choose 'find' as the indirection method if the http method is a POST and the indirection name is singular" do expect(handler.uri2indirection("POST", "#{master_url_prefix}/node/bar", params)[1]).to eq(:find) end it "should choose 'head' as the indirection method if the http method is a HEAD and the indirection name is singular" do expect(handler.uri2indirection("HEAD", "#{master_url_prefix}/node/bar", params)[1]).to eq(:head) end it "should choose 'search' as the indirection method if the http method is a GET and the indirection name is plural" do expect(handler.uri2indirection("GET", "#{master_url_prefix}/nodes/bar", params)[1]).to eq(:search) end it "should choose 'save' as the indirection method if the http method is a PUT and the indirection name is facts" do expect(handler.uri2indirection("PUT", "#{master_url_prefix}/facts/puppet.node.test", params)[0].name).to eq(:facts) end it "should change indirection name to 'status' if the http method is a GET and the indirection name is statuses" do expect(handler.uri2indirection("GET", "#{master_url_prefix}/statuses/bar", params)[0].name).to eq(:status) end it "should change indirection name to 'node' if the http method is a GET and the indirection name is nodes" do expect(handler.uri2indirection("GET", "#{master_url_prefix}/nodes/bar", params)[0].name).to eq(:node) end it "should choose 'delete' as the indirection method if the http method is a DELETE and the indirection name is singular" do expect(handler.uri2indirection("DELETE", "#{master_url_prefix}/node/bar", params)[1]).to eq(:destroy) end it "should choose 'save' as the indirection method if the http method is a PUT and the indirection name is singular" do expect(handler.uri2indirection("PUT", "#{master_url_prefix}/node/bar", params)[1]).to eq(:save) end it "should fail if an indirection method cannot be picked" do expect(lambda { handler.uri2indirection("UPDATE", "#{master_url_prefix}/node/bar", params) }).to raise_error(method_not_allowed_error) end it "should not URI unescape the indirection key" do escaped = Puppet::Util.uri_encode("foo bar") _, _, key, _ = handler.uri2indirection("GET", "#{master_url_prefix}/node/#{escaped}", params) expect(key).to eq(escaped) end it "should not unescape the URI passed through in a call to check_authorization" do key_escaped = Puppet::Util.uri_encode("foo bar") uri_escaped = "#{master_url_prefix}/node/#{key_escaped}" handler.expects(:check_authorization).with(anything, uri_escaped, anything) _, _, _, _ = handler.uri2indirection("GET", uri_escaped, params) end it "should not pass through an environment to check_authorization and fail if the environment is unknown" do handler.expects(:check_authorization).with(anything, anything, Not(has_entry(:environment))) expect(lambda { handler.uri2indirection("GET", "#{master_url_prefix}/node/bar", {:environment => 'bogus'}) }).to raise_error(not_found_error) end it "should not URI unescape the indirection key as passed through to a call to check_authorization" do handler.expects(:check_authorization).with(anything, anything, all_of( has_entry(:environment, is_a(Puppet::Node::Environment)), has_entry(:environment, responds_with(:name, :env)))) handler.uri2indirection("GET", "#{master_url_prefix}/node/bar", params) end end describe "when converting a request into a URI" do let(:environment) { Puppet::Node::Environment.create(:myenv, []) } let(:request) { Puppet::Indirector::Request.new(:foo, :find, "with spaces", nil, :foo => :bar, :environment => environment) } before do handler.stubs(:handler).returns "foo" end it "should include the environment in the query string of the URI" do expect(handler.class.request_to_uri(request)).to eq("#{master_url_prefix}/foo/with%20spaces?environment=myenv&foo=bar") end it "should include the correct url prefix if it is a ca request" do request.stubs(:indirection_name).returns("certificate") expect(handler.class.request_to_uri(request)).to eq("#{ca_url_prefix}/certificate/with%20spaces?environment=myenv&foo=bar") end it "should pluralize the indirection name if the method is 'search'" do request.stubs(:method).returns :search expect(handler.class.request_to_uri(request).split("/")[3]).to eq("foos") end it "should add the query string to the URI" do request.expects(:query_string).returns "query" expect(handler.class.request_to_uri(request)).to match(/\&query$/) end end describe "when converting a request into a URI with body" do let(:environment) { Puppet::Node::Environment.create(:myenv, []) } let(:request) { Puppet::Indirector::Request.new(:foo, :find, "with spaces", nil, :foo => :bar, :environment => environment) } it "should use the indirection as the first field of the URI" do expect(handler.class.request_to_uri_and_body(request).first.split("/")[3]).to eq("foo") end it "should use the escaped key as the remainder of the URI" do escaped = Puppet::Util.uri_encode("with spaces") expect(handler.class.request_to_uri_and_body(request).first.split("/")[4].sub(/\?.+/, '')).to eq(escaped) end it "should include the correct url prefix if it is a master request" do expect(handler.class.request_to_uri_and_body(request).first).to eq("#{master_url_prefix}/foo/with%20spaces") end it "should include the correct url prefix if it is a ca request" do request.stubs(:indirection_name).returns("certificate") expect(handler.class.request_to_uri_and_body(request).first).to eq("#{ca_url_prefix}/certificate/with%20spaces") end it "should return the URI and body separately" do expect(handler.class.request_to_uri_and_body(request)).to eq(["#{master_url_prefix}/foo/with%20spaces", "environment=myenv&foo=bar"]) end end describe "when processing a request" do it "should raise not_authorized_error when authorization fails" do data = Puppet::IndirectorTesting.new("my data") indirection.save(data, "my data") request = a_request_that_heads(data) handler.expects(:check_authorization).raises(Puppet::Network::AuthorizationError.new("forbidden")) expect { handler.call(request, response) }.to raise_error(not_authorized_error) end it "should raise not_found_error if the indirection does not support remote requests" do request = a_request_that_heads(Puppet::IndirectorTesting.new("my data")) indirection.expects(:allow_remote_requests?).returns(false) expect { handler.call(request, response) }.to raise_error(not_found_error) end it "should raise not_found_error if the environment does not exist" do Puppet.override(:environments => Puppet::Environments::Static.new()) do request = a_request_that_heads(Puppet::IndirectorTesting.new("my data")) expect { handler.call(request, response) }.to raise_error(not_found_error) end end end describe "when finding a model instance" do it "uses the first supported format for the response" do data = Puppet::IndirectorTesting.new("my data") indirection.save(data, "my data") request = a_request_that_finds(data, :accept_header => "unknown, application/json") handler.call(request, response) expect(response.body).to eq(data.render(:json)) expect(response.type).to eq(Puppet::Network::FormatHandler.format(:json)) end it "falls back to the next supported format" do data = Puppet::IndirectorTesting.new("my data") indirection.save(data, "my data") request = a_request_that_finds(data, :accept_header => "application/json, text/pson") data.stubs(:to_json).raises(Puppet::Network::FormatHandler::FormatError, 'Could not render to Puppet::Network::Format[json]: source sequence is illegal/malformed utf-8') handler.call(request, response) expect(response.body).to eq(data.render(:pson)) expect(response.type).to eq(Puppet::Network::FormatHandler.format(:pson)) end it "should pass the result through without rendering it if the result is a string" do data = Puppet::IndirectorTesting.new("my data") data_string = "my data string" request = a_request_that_finds(data, :accept_header => "application/json") indirection.expects(:find).returns(data_string) handler.call(request, response) expect(response.body).to eq(data_string) expect(response.type).to eq(Puppet::Network::FormatHandler.format(:json)) end it "should raise not_found_error when no model instance can be found" do data = Puppet::IndirectorTesting.new("my data") request = a_request_that_finds(data, :accept_header => "unknown, application/json") expect { handler.call(request, response) }.to raise_error(not_found_error) end end describe "when searching for model instances" do it "uses the first supported format for the response" do data = Puppet::IndirectorTesting.new("my data") indirection.save(data, "my data") request = a_request_that_searches(Puppet::IndirectorTesting.new("my"), :accept_header => "unknown, application/json") handler.call(request, response) expect(response.type).to eq(Puppet::Network::FormatHandler.format(:json)) expect(response.body).to eq(Puppet::IndirectorTesting.render_multiple(:json, [data])) end it "falls back to the next supported format" do data = Puppet::IndirectorTesting.new("my data") indirection.save(data, "my data") request = a_request_that_searches(Puppet::IndirectorTesting.new("my"), :accept_header => "application/json, text/pson") data.stubs(:to_json).raises(Puppet::Network::FormatHandler::FormatError, 'Could not render to Puppet::Network::Format[json]: source sequence is illegal/malformed utf-8') handler.call(request, response) expect(response.type).to eq(Puppet::Network::FormatHandler.format(:pson)) expect(response.body).to eq(Puppet::IndirectorTesting.render_multiple(:pson, [data])) end it "raises 406 not acceptable if no formats are accceptable" do data = Puppet::IndirectorTesting.new("my data") indirection.save(data, "my data") request = a_request_that_searches(Puppet::IndirectorTesting.new("my"), :accept_header => "application/json, text/pson") data.stubs(:to_json).raises(Puppet::Network::FormatHandler::FormatError, 'Could not render to Puppet::Network::Format[json]: source sequence is illegal/malformed utf-8') data.stubs(:to_pson).raises(Puppet::Network::FormatHandler::FormatError, 'Could not render to Puppet::Network::Format[pson]: source sequence is illegal/malformed utf-8') expect { handler.call(request, response) }.to raise_error(Puppet::Network::HTTP::Error::HTTPNotAcceptableError, /No supported formats are acceptable/) end it "should return [] when searching returns an empty array" do request = a_request_that_searches(Puppet::IndirectorTesting.new("nothing"), :accept_header => "unknown, application/json") handler.call(request, response) expect(response.body).to eq("[]") expect(response.type).to eq(Puppet::Network::FormatHandler.format(:json)) end it "should raise not_found_error when searching returns nil" do request = a_request_that_searches(Puppet::IndirectorTesting.new("nothing"), :accept_header => "unknown, application/json") indirection.expects(:search).returns(nil) expect { handler.call(request, response) }.to raise_error(not_found_error) end end describe "when destroying a model instance" do it "destroys the data indicated in the request" do data = Puppet::IndirectorTesting.new("my data") indirection.save(data, "my data") request = a_request_that_destroys(data) handler.call(request, response) expect(Puppet::IndirectorTesting.indirection.find("my data")).to be_nil end it "uses the first supported format for the response" do data = Puppet::IndirectorTesting.new("my data") indirection.save(data, "my data") request = a_request_that_destroys(data, :accept_header => "unknown, application/json") handler.call(request, response) expect(response.body).to eq(data.render(:json)) expect(response.type).to eq(Puppet::Network::FormatHandler.format(:json)) end it "raises an error and does not destroy when no accepted formats are known" do data = Puppet::IndirectorTesting.new("my data") indirection.save(data, "my data") request = a_request_that_destroys(data, :accept_header => "unknown, also/unknown") expect { handler.call(request, response) }.to raise_error(not_acceptable_error) expect(Puppet::IndirectorTesting.indirection.find("my data")).not_to be_nil end end describe "when saving a model instance" do it "allows an empty body when the format supports it" do class Puppet::IndirectorTesting::Nonvalidatingmemory < Puppet::IndirectorTesting::Memory def validate_key(_) # nothing end end indirection.terminus_class = :nonvalidatingmemory data = Puppet::IndirectorTesting.new("test") request = a_request_that_submits(data, :content_type_header => "application/octet-stream", :body => '') handler.call(request, response) saved = Puppet::IndirectorTesting.indirection.find("test") expect(saved.name).to eq('') end it "saves the data sent in the request" do data = Puppet::IndirectorTesting.new("my data") request = a_request_that_submits(data) handler.call(request, response) saved = Puppet::IndirectorTesting.indirection.find("my data") expect(saved.name).to eq(data.name) end it "responds with bad request when failing to parse the body" do data = Puppet::IndirectorTesting.new("my data") request = a_request_that_submits(data, :content_type_header => 'application/json', :body => "this is invalid json content") expect { handler.call(request, response) }.to raise_error(bad_request_error, /The request body is invalid: Could not intern from json/) end it "responds with unsupported media type error when submitted content is known, but not supported by the model" do data = Puppet::IndirectorTesting.new("my data") request = a_request_that_submits(data, :content_type_header => 's') expect(data).to_not be_support_format('s') expect { handler.call(request, response) }.to raise_error(unsupported_media_type_error, /Client sent a mime-type \(s\) that doesn't correspond to a format we support/) end it "uses the first supported format for the response" do data = Puppet::IndirectorTesting.new("my data") request = a_request_that_submits(data, :accept_header => "unknown, application/json") handler.call(request, response) expect(response.body).to eq(data.render(:json)) expect(response.type).to eq(Puppet::Network::FormatHandler.format(:json)) end it "raises an error and does not save when no accepted formats are known" do data = Puppet::IndirectorTesting.new("my data") request = a_request_that_submits(data, :accept_header => "unknown, also/unknown") expect { handler.call(request, response) }.to raise_error(not_acceptable_error) expect(Puppet::IndirectorTesting.indirection.find("my data")).to be_nil end end describe "when performing head operation" do it "should not generate a response when a model head call succeeds" do data = Puppet::IndirectorTesting.new("my data") indirection.save(data, "my data") request = a_request_that_heads(data) handler.call(request, response) expect(response.code).to eq(nil) end it "should raise not_found_error when the model head call returns false" do data = Puppet::IndirectorTesting.new("my data") request = a_request_that_heads(data) expect { handler.call(request, response) }.to raise_error(not_found_error) end end end puppet-5.5.10/spec/unit/network/http/error_spec.rb0000644005276200011600000000156413417161721022071 0ustar jenkinsjenkinsrequire 'spec_helper' require 'matchers/json' require 'puppet/network/http' describe Puppet::Network::HTTP::Error do include JSONMatchers describe Puppet::Network::HTTP::Error::HTTPError do it "should serialize to JSON that matches the error schema" do error = Puppet::Network::HTTP::Error::HTTPError.new("I don't like the looks of you", 400, :SHIFTY_USER) expect(error.to_json).to validate_against('api/schemas/error.json') end end describe Puppet::Network::HTTP::Error::HTTPServerError do it "should serialize to JSON that matches the error schema" do begin raise Exception, "a wild Exception appeared!" rescue Exception => e culpable = e end error = Puppet::Network::HTTP::Error::HTTPServerError.new(culpable) expect(error.to_json).to validate_against('api/schemas/error.json') end end end puppet-5.5.10/spec/unit/network/http/handler_spec.rb0000644005276200011600000001303313417161721022347 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/handler' require 'puppet/indirector_testing' require 'puppet/network/authorization' require 'puppet/network/http' describe Puppet::Network::HTTP::Handler do before :each do Puppet::IndirectorTesting.indirection.terminus_class = :memory end let(:indirection) { Puppet::IndirectorTesting.indirection } def a_request(method = "HEAD", path = "/production/#{indirection.name}/unknown") { :accept_header => "application/json", :content_type_header => "application/json", :method => method, :path => path, :params => {}, :client_cert => nil, :headers => {}, :body => nil } end let(:handler) { PuppetSpec::Handler.new() } describe "the HTTP Handler" do def respond(text) lambda { |req, res| res.respond_with(200, "text/plain", text) } end it "hands the request to the first route that matches the request path" do handler = PuppetSpec::Handler.new( Puppet::Network::HTTP::Route.path(%r{^/foo}).get(respond("skipped")), Puppet::Network::HTTP::Route.path(%r{^/vtest}).get(respond("used")), Puppet::Network::HTTP::Route.path(%r{^/vtest/foo}).get(respond("ignored"))) req = a_request("GET", "/vtest/foo") res = {} handler.process(req, res) expect(res[:body]).to eq("used") end it "raises an error if multiple routes with the same path regex are registered" do expect do PuppetSpec::Handler.new( Puppet::Network::HTTP::Route.path(%r{^/foo}).get(respond("ignored")), Puppet::Network::HTTP::Route.path(%r{^/foo}).post(respond("also ignored")) ) end.to raise_error(ArgumentError) end it "raises an HTTP not found error if no routes match" do handler = PuppetSpec::Handler.new req = a_request("GET", "/vtest/foo") res = {} handler.process(req, res) res_body = JSON(res[:body]) expect(res[:content_type_header]).to eq("application/json; charset=utf-8") expect(res_body["issue_kind"]).to eq("HANDLER_NOT_FOUND") expect(res_body["message"]).to eq("Not Found: No route for GET /vtest/foo") expect(res[:status]).to eq(404) end it "returns a structured error response when the server encounters an internal error" do error = StandardError.new("the sky is falling!") original_stacktrace = ['a.rb', 'b.rb'] error.set_backtrace(original_stacktrace) handler = PuppetSpec::Handler.new( Puppet::Network::HTTP::Route.path(/.*/).get(lambda { |_, _| raise error})) # Stacktraces should be included in logs Puppet.expects(:err).with("Server Error: the sky is falling!\na.rb\nb.rb") req = a_request("GET", "/vtest/foo") res = {} handler.process(req, res) res_body = JSON(res[:body]) expect(res[:content_type_header]).to eq("application/json; charset=utf-8") expect(res_body["issue_kind"]).to eq(Puppet::Network::HTTP::Issues::RUNTIME_ERROR.to_s) expect(res_body["message"]).to eq("Server Error: the sky is falling!") expect(res[:status]).to eq(500) end end describe "when processing a request" do let(:response) do { :status => 200 } end before do handler.stubs(:check_authorization) handler.stubs(:warn_if_near_expiration) end it "should setup a profiler when the puppet-profiling header exists" do request = a_request request[:headers][Puppet::Network::HTTP::HEADER_ENABLE_PROFILING.downcase] = "true" p = PuppetSpec::HandlerProfiler.new Puppet::Util::Profiler.expects(:add_profiler).with { |profiler| profiler.is_a? Puppet::Util::Profiler::WallClock }.returns(p) Puppet::Util::Profiler.expects(:remove_profiler).with { |profiler| profiler == p } handler.process(request, response) end it "should not setup profiler when the profile parameter is missing" do request = a_request request[:params] = { } Puppet::Util::Profiler.expects(:add_profiler).never handler.process(request, response) end it "should still find the correct format if content type contains charset information" do request = Puppet::Network::HTTP::Request.new({ 'content-type' => "text/plain; charset=UTF-8" }, {}, 'GET', '/', nil) expect(request.formatter.name).to eq(:s) end # PUP-3272 # This used to be for YAML, and doing a to_yaml on an array. # The result with to_json is something different, the result is a string # Which seems correct. Looks like this was some kind of nesting option "yaml inside yaml" ? # Removing the test # it "should deserialize JSON parameters" do # params = {'my_param' => [1,2,3].to_json} # # decoded_params = handler.send(:decode_params, params) # # decoded_params.should == {:my_param => [1,2,3]} # end end describe "when resolving node" do it "should use a look-up from the ip address" do Resolv.expects(:getname).with("1.2.3.4").returns("host.domain.com") handler.resolve_node(:ip => "1.2.3.4") end it "should return the look-up result" do Resolv.stubs(:getname).with("1.2.3.4").returns("host.domain.com") expect(handler.resolve_node(:ip => "1.2.3.4")).to eq("host.domain.com") end it "should return the ip address if resolving fails" do Resolv.stubs(:getname).with("1.2.3.4").raises(RuntimeError, "no such host") expect(handler.resolve_node(:ip => "1.2.3.4")).to eq("1.2.3.4") end end end puppet-5.5.10/spec/unit/network/http/nocache_pool_spec.rb0000644005276200011600000000224113417161721023362 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/network/http' require 'puppet/network/http/connection' describe Puppet::Network::HTTP::NoCachePool do let(:site) { Puppet::Network::HTTP::Site.new('https', 'rubygems.org', 443) } let(:verify) { stub('verify', :setup_connection => nil) } it 'yields a connection' do http = stub('http') factory = Puppet::Network::HTTP::Factory.new factory.stubs(:create_connection).returns(http) pool = Puppet::Network::HTTP::NoCachePool.new(factory) expect { |b| pool.with_connection(site, verify, &b) }.to yield_with_args(http) end it 'yields a new connection each time' do http1 = stub('http1') http2 = stub('http2') factory = Puppet::Network::HTTP::Factory.new factory.stubs(:create_connection).returns(http1).then.returns(http2) pool = Puppet::Network::HTTP::NoCachePool.new(factory) expect { |b| pool.with_connection(site, verify, &b) }.to yield_with_args(http1) expect { |b| pool.with_connection(site, verify, &b) }.to yield_with_args(http2) end it 'has a close method' do Puppet::Network::HTTP::NoCachePool.new.close end end puppet-5.5.10/spec/unit/network/http/pool_spec.rb0000644005276200011600000002170013417161721021703 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'openssl' require 'puppet/network/http' require 'puppet/network/http_pool' describe Puppet::Network::HTTP::Pool do before :each do Puppet::SSL::Key.indirection.terminus_class = :memory Puppet::SSL::CertificateRequest.indirection.terminus_class = :memory end let(:site) do Puppet::Network::HTTP::Site.new('https', 'rubygems.org', 443) end let(:different_site) do Puppet::Network::HTTP::Site.new('https', 'github.com', 443) end let(:verify) do stub('verify', :setup_connection => nil) end def create_pool Puppet::Network::HTTP::Pool.new end def create_pool_with_connections(site, *connections) pool = Puppet::Network::HTTP::Pool.new connections.each do |conn| pool.release(site, conn) end pool end def create_pool_with_expired_connections(site, *connections) # setting keepalive timeout to -1 ensures any newly added # connections have already expired pool = Puppet::Network::HTTP::Pool.new(-1) connections.each do |conn| pool.release(site, conn) end pool end def create_connection(site) stub(site.addr, :started? => false, :start => nil, :finish => nil, :use_ssl? => true, :verify_mode => OpenSSL::SSL::VERIFY_PEER) end context 'when yielding a connection' do it 'yields a connection' do conn = create_connection(site) pool = create_pool_with_connections(site, conn) expect { |b| pool.with_connection(site, verify, &b) }.to yield_with_args(conn) end it 'returns the connection to the pool' do conn = create_connection(site) pool = create_pool pool.release(site, conn) pool.with_connection(site, verify) { |c| } expect(pool.pool[site].first.connection).to eq(conn) end it 'can yield multiple connections to the same site' do lru_conn = create_connection(site) mru_conn = create_connection(site) pool = create_pool_with_connections(site, lru_conn, mru_conn) pool.with_connection(site, verify) do |a| expect(a).to eq(mru_conn) pool.with_connection(site, verify) do |b| expect(b).to eq(lru_conn) end end end it 'propagates exceptions' do conn = create_connection(site) pool = create_pool pool.release(site, conn) expect { pool.with_connection(site, verify) do |c| raise IOError, 'connection reset' end }.to raise_error(IOError, 'connection reset') end it 'does not re-cache connections when an error occurs' do # we're not distinguishing between network errors that would # suggest we close the socket, and other errors conn = create_connection(site) pool = create_pool pool.release(site, conn) pool.expects(:release).with(site, conn).never pool.with_connection(site, verify) do |c| raise IOError, 'connection reset' end rescue nil end it 'sets keepalive bit on network socket' do pool = create_pool s = Socket.new(Socket::PF_INET, Socket::SOCK_STREAM) pool.setsockopts(Net::BufferedIO.new(s)) # On windows, Socket.getsockopt() doesn't return exactly the same data # as an equivalent Socket::Option.new() statement, so we strip off the # unrelevant bits only on this platform. # # To make sure we're not voiding the test case by doing this, we check # both with and without the keepalive bit set. # # This workaround can be removed once all the ruby versions we care about # have the patch from https://bugs.ruby-lang.org/issues/11958 applied. # if Puppet::Util::Platform.windows? keepalive = Socket::Option.bool(:INET, :SOCKET, :KEEPALIVE, true).data[0] nokeepalive = Socket::Option.bool(:INET, :SOCKET, :KEEPALIVE, false).data[0] expect(s.getsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE).data).to eq(keepalive) expect(s.getsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE).data).to_not eq(nokeepalive) else expect(s.getsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE).bool).to eq(true) end end context 'when releasing connections' do it 'releases HTTP connections' do conn = create_connection(site) conn.expects(:use_ssl?).returns(false) pool = create_pool_with_connections(site, conn) pool.expects(:release).with(site, conn) pool.with_connection(site, verify) {|c| } end it 'releases secure HTTPS connections' do conn = create_connection(site) conn.expects(:use_ssl?).returns(true) conn.expects(:verify_mode).returns(OpenSSL::SSL::VERIFY_PEER) pool = create_pool_with_connections(site, conn) pool.expects(:release).with(site, conn) pool.with_connection(site, verify) {|c| } end it 'closes insecure HTTPS connections' do conn = create_connection(site) conn.expects(:use_ssl?).returns(true) conn.expects(:verify_mode).returns(OpenSSL::SSL::VERIFY_NONE) pool = create_pool_with_connections(site, conn) pool.expects(:release).with(site, conn).never pool.with_connection(site, verify) {|c| } end end end context 'when borrowing' do it 'returns a new connection if the pool is empty' do conn = create_connection(site) pool = create_pool pool.factory.expects(:create_connection).with(site).returns(conn) pool.expects(:setsockopts) expect(pool.borrow(site, verify)).to eq(conn) end it 'returns a matching connection' do conn = create_connection(site) pool = create_pool_with_connections(site, conn) pool.factory.expects(:create_connection).never expect(pool.borrow(site, verify)).to eq(conn) end it 'returns a new connection if there are no matching sites' do different_conn = create_connection(different_site) pool = create_pool_with_connections(different_site, different_conn) conn = create_connection(site) pool.factory.expects(:create_connection).with(site).returns(conn) pool.expects(:setsockopts) expect(pool.borrow(site, verify)).to eq(conn) end it 'returns started connections' do conn = create_connection(site) conn.expects(:start) pool = create_pool pool.factory.expects(:create_connection).with(site).returns(conn) pool.expects(:setsockopts) expect(pool.borrow(site, verify)).to eq(conn) end it "doesn't start a cached connection" do conn = create_connection(site) conn.expects(:start).never pool = create_pool_with_connections(site, conn) pool.borrow(site, verify) end it 'returns the most recently used connection from the pool' do least_recently_used = create_connection(site) most_recently_used = create_connection(site) pool = create_pool_with_connections(site, least_recently_used, most_recently_used) expect(pool.borrow(site, verify)).to eq(most_recently_used) end it 'finishes expired connections' do conn = create_connection(site) conn.expects(:finish) pool = create_pool_with_expired_connections(site, conn) pool.factory.expects(:create_connection => stub('conn', :start => nil)) pool.expects(:setsockopts) pool.borrow(site, verify) end it 'logs an exception if it fails to close an expired connection' do Puppet.expects(:log_exception).with(is_a(IOError), "Failed to close connection for #{site}: read timeout") conn = create_connection(site) conn.expects(:finish).raises(IOError, 'read timeout') pool = create_pool_with_expired_connections(site, conn) pool.factory.expects(:create_connection => stub('open_conn', :start => nil)) pool.expects(:setsockopts) pool.borrow(site, verify) end end context 'when releasing a connection' do it 'adds the connection to an empty pool' do conn = create_connection(site) pool = create_pool pool.release(site, conn) expect(pool.pool[site].first.connection).to eq(conn) end it 'adds the connection to a pool with a connection for the same site' do pool = create_pool pool.release(site, create_connection(site)) pool.release(site, create_connection(site)) expect(pool.pool[site].count).to eq(2) end it 'adds the connection to a pool with a connection for a different site' do pool = create_pool pool.release(site, create_connection(site)) pool.release(different_site, create_connection(different_site)) expect(pool.pool[site].count).to eq(1) expect(pool.pool[different_site].count).to eq(1) end end context 'when closing' do it 'clears the pool' do pool = create_pool pool.close expect(pool.pool).to be_empty end it 'closes all cached connections' do conn = create_connection(site) conn.expects(:finish) pool = create_pool_with_connections(site, conn) pool.close end end end puppet-5.5.10/spec/unit/network/http/request_spec.rb0000644005276200011600000001033713417161721022426 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/network' require 'puppet/network/http' describe Puppet::Network::HTTP::Request do include PuppetSpec::Network let(:json_formatter) { Puppet::Network::FormatHandler.format(:json) } let(:pson_formatter) { Puppet::Network::FormatHandler.format(:pson) } def headers { 'accept' => 'application/json', 'content-type' => 'application/json' } end def a_request(headers, body = "") described_class.from_hash( :method => "PUT", :path => "/path/to/endpoint", :body => body, :headers => headers ) end context "when resolving the formatter for the request body" do it "returns the formatter for that Content-Type" do request = a_request(headers.merge("content-type" => "application/json")) expect(request.formatter).to eq(json_formatter) end it "raises HTTP 400 if Content-Type is missing" do request = a_request({}) expect { request.formatter }.to raise_error(bad_request_error, /No Content-Type header was received, it isn't possible to unserialize the request/) end it "raises HTTP 415 if Content-Type is unsupported" do request = a_request(headers.merge('content-type' => 'application/ogg')) expect { request.formatter }.to raise_error(unsupported_media_type_error, /Unsupported Media Type: Client sent a mime-type \(application\/ogg\) that doesn't correspond to a format we support/) end it "raises HTTP 415 if Content-Type is unsafe yaml" do request = a_request(headers.merge('content-type' => 'yaml')) expect { request.formatter }.to raise_error(unsupported_media_type_error, /Unsupported Media Type: Client sent a mime-type \(yaml\) that doesn't correspond to a format we support/) end it "raises HTTP 415 if Content-Type is unsafe b64_zlib_yaml" do request = a_request(headers.merge('content-type' => 'b64_zlib_yaml')) expect { request.formatter }.to raise_error(unsupported_media_type_error, /Unsupported Media Type: Client sent a mime-type \(b64_zlib_yaml\) that doesn't correspond to a format we support/) end end context "when resolving the formatter for the response body" do context "when the client doesn't specify an Accept header" do it "raises HTTP 400 if the server doesn't specify a default" do request = a_request({}) expect { request.response_formatters_for([:json]) }.to raise_error(bad_request_error, /Missing required Accept header/) end it "uses the server default" do request = a_request({}) expect(request.response_formatters_for([:json], 'application/json')).to eq([json_formatter]) end end it "returns accepted and supported formats, in the accepted order" do request = a_request(headers.merge('accept' => 'application/json, application/x-msgpack, text/pson')) expect(request.response_formatters_for([:pson, :json])).to eq([json_formatter, pson_formatter]) end it "selects the second format if the first one isn't supported by the server" do request = a_request(headers.merge('accept' => 'application/json, text/pson')) expect(request.response_formatters_for([:pson])).to eq([pson_formatter]) end it "raises HTTP 406 if Accept doesn't include any server-supported formats" do request = a_request(headers.merge('accept' => 'application/ogg')) expect { request.response_formatters_for([:json]) }.to raise_error(not_acceptable_error, /No supported formats are acceptable \(Accept: application\/ogg\)/) end it "raises HTTP 406 if Accept resolves to unsafe yaml" do request = a_request(headers.merge('accept' => 'yaml')) expect { request.response_formatters_for([:json]) }.to raise_error(not_acceptable_error, /No supported formats are acceptable \(Accept: yaml\)/) end it "raises HTTP 406 if Accept resolves to unsafe b64_zlib_yaml" do request = a_request(headers.merge('accept' => 'b64_zlib_yaml')) expect { request.response_formatters_for([:json]) }.to raise_error(not_acceptable_error, /No supported formats are acceptable \(Accept: b64_zlib_yaml\)/) end end end puppet-5.5.10/spec/unit/network/http/response_spec.rb0000644005276200011600000000726613417161721022603 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/files' require 'puppet_spec/handler' require 'puppet/network/http' describe Puppet::Network::HTTP::Response do include PuppetSpec::Files let(:handler) { PuppetSpec::Handler.new } let(:response) { {} } let(:subject) { described_class.new(handler, response) } let(:body_utf8) { JSON.dump({ "foo" => "bar"}).encode('UTF-8') } let(:body_shift_jis) { [130, 174].pack('C*').force_encoding(Encoding::Shift_JIS) } let(:invalid_shift_jis) { "\xC0\xFF".force_encoding(Encoding::Shift_JIS) } context "when passed a response body" do it "passes the status code and body to the handler" do handler.expects(:set_response).with(response, body_utf8, 200) subject.respond_with(200, 'application/json', body_utf8) end it "accepts a File body" do file = tmpfile('response_spec') handler.expects(:set_response).with(response, file, 200) subject.respond_with(200, 'application/octet-stream', file) end end context "when passed a content type" do it "accepts a mime string" do handler.expects(:set_content_type).with(response, 'application/json; charset=utf-8') subject.respond_with(200, 'application/json', body_utf8) end it "accepts a format object" do formatter = Puppet::Network::FormatHandler.format(:json) handler.expects(:set_content_type).with(response, 'application/json; charset=utf-8') subject.respond_with(200, formatter, body_utf8) end end context "when resolving charset" do context "with binary content" do it "omits the charset" do body_binary = [0xDEADCAFE].pack('L') formatter = Puppet::Network::FormatHandler.format(:binary) handler.expects(:set_content_type).with(response, 'application/octet-stream') subject.respond_with(200, formatter, body_binary) end end context "with text/plain content" do let(:formatter) { Puppet::Network::FormatHandler.format(:s) } it "sets the charset to UTF-8 for content already in that format" do body_pem = "BEGIN CERTIFICATE".encode('UTF-8') handler.expects(:set_content_type).with(response, 'text/plain; charset=utf-8') subject.respond_with(200, formatter, body_pem) end it "encodes the content to UTF-8 for content not already in UTF-8" do handler.expects(:set_content_type).with(response, 'text/plain; charset=utf-8') handler.expects(:set_response).with(response, body_shift_jis.encode('utf-8'), 200) subject.respond_with(200, formatter, body_shift_jis) end it "raises an exception if transcoding fails" do expect { subject.respond_with(200, formatter, invalid_shift_jis) }.to raise_error(EncodingError, /"\\xFF" on Shift_JIS/) end end context "with application/json content" do let(:formatter) { Puppet::Network::FormatHandler.format(:json) } it "sets the charset to UTF-8 for content already in that format" do handler.expects(:set_content_type).with(response, 'application/json; charset=utf-8') subject.respond_with(200, formatter, body_utf8) end it "encodes the content to UTF-8 for content not already in UTF-8" do handler.expects(:set_content_type).with(response, 'application/json; charset=utf-8') handler.expects(:set_response).with(response, body_shift_jis.encode('utf-8'), 200) subject.respond_with(200, formatter, body_shift_jis) end it "raises an exception if transcoding fails" do expect { subject.respond_with(200, formatter, invalid_shift_jis) }.to raise_error(EncodingError, /"\\xFF" on Shift_JIS/) end end end end puppet-5.5.10/spec/unit/network/http/route_spec.rb0000644005276200011600000000606713417161721022101 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/indirector_testing' require 'puppet/network/http' describe Puppet::Network::HTTP::Route do def request(method, path) Puppet::Network::HTTP::Request.from_hash({ :method => method, :path => path, :routing_path => path }) end def respond(text) lambda { |req, res| res.respond_with(200, "text/plain", text) } end let(:req) { request("GET", "/vtest/foo") } let(:res) { Puppet::Network::HTTP::MemoryResponse.new } describe "an HTTP Route" do it "can match a request" do route = Puppet::Network::HTTP::Route.path(%r{^/vtest}) expect(route.matches?(req)).to be_truthy end it "will raise a Method Not Allowed error when no handler for the request's method is given" do route = Puppet::Network::HTTP::Route.path(%r{^/vtest}).post(respond("ignored")) expect do route.process(req, res) end.to raise_error(Puppet::Network::HTTP::Error::HTTPMethodNotAllowedError) end it "can match any HTTP method" do route = Puppet::Network::HTTP::Route.path(%r{^/vtest/foo}).any(respond("used")) expect(route.matches?(req)).to be_truthy route.process(req, res) expect(res.body).to eq("used") end it "processes DELETE requests" do route = Puppet::Network::HTTP::Route.path(%r{^/vtest/foo}).delete(respond("used")) route.process(request("DELETE", "/vtest/foo"), res) expect(res.body).to eq("used") end it "does something when it doesn't know the verb" do route = Puppet::Network::HTTP::Route.path(%r{^/vtest/foo}) expect do route.process(request("UNKNOWN", "/vtest/foo"), res) end.to raise_error(Puppet::Network::HTTP::Error::HTTPMethodNotAllowedError, /UNKNOWN/) end it "calls the method handlers in turn" do call_count = 0 handler = lambda { |request, response| call_count += 1 } route = Puppet::Network::HTTP::Route.path(%r{^/vtest/foo}).get(handler, handler) route.process(req, res) expect(call_count).to eq(2) end it "stops calling handlers if one of them raises an error" do ignored_called = false ignored = lambda { |req, res| ignored_called = true } raise_error = lambda { |req, res| raise Puppet::Network::HTTP::Error::HTTPNotAuthorizedError, "go away" } route = Puppet::Network::HTTP::Route.path(%r{^/vtest/foo}).get(raise_error, ignored) expect do route.process(req, res) end.to raise_error(Puppet::Network::HTTP::Error::HTTPNotAuthorizedError) expect(ignored_called).to be_falsey end it "chains to other routes after calling its handlers" do inner_route = Puppet::Network::HTTP::Route.path(%r{^/inner}).any(respond("inner")) unused_inner_route = Puppet::Network::HTTP::Route.path(%r{^/unused_inner}).any(respond("unused")) top_route = Puppet::Network::HTTP::Route.path(%r{^/vtest}).any(respond("top")).chain(unused_inner_route, inner_route) top_route.process(request("GET", "/vtest/inner"), res) expect(res.body).to eq("topinner") end end end puppet-5.5.10/spec/unit/network/http/session_spec.rb0000644005276200011600000000210313417161721022411 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/network/http' describe Puppet::Network::HTTP::Session do let(:connection) { stub('connection') } def create_session(connection, expiration_time = nil) expiration_time ||= Time.now + 60 * 60 Puppet::Network::HTTP::Session.new(connection, expiration_time) end it 'provides access to its connection' do session = create_session(connection) expect(session.connection).to eq(connection) end it 'expires a connection whose expiration time is in the past' do now = Time.now past = now - 1 session = create_session(connection, past) expect(session.expired?(now)).to be_truthy end it 'expires a connection whose expiration time is now' do now = Time.now session = create_session(connection, now) expect(session.expired?(now)).to be_truthy end it 'does not expire a connection whose expiration time is in the future' do now = Time.now future = now + 1 session = create_session(connection, future) expect(session.expired?(now)).to be_falsey end end puppet-5.5.10/spec/unit/network/http/site_spec.rb0000644005276200011600000000443313417161721021702 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/network/http' describe Puppet::Network::HTTP::Site do let(:scheme) { 'https' } let(:host) { 'rubygems.org' } let(:port) { 443 } def create_site(scheme, host, port) Puppet::Network::HTTP::Site.new(scheme, host, port) end it 'accepts scheme, host, and port' do site = create_site(scheme, host, port) expect(site.scheme).to eq(scheme) expect(site.host).to eq(host) expect(site.port).to eq(port) end it 'generates an external URI string' do site = create_site(scheme, host, port) expect(site.addr).to eq("https://rubygems.org:443") end it 'considers sites to be different when the scheme is different' do https_site = create_site('https', host, port) http_site = create_site('http', host, port) expect(https_site).to_not eq(http_site) end it 'considers sites to be different when the host is different' do rubygems_site = create_site(scheme, 'rubygems.org', port) github_site = create_site(scheme, 'github.com', port) expect(rubygems_site).to_not eq(github_site) end it 'considers sites to be different when the port is different' do site_443 = create_site(scheme, host, 443) site_80 = create_site(scheme, host, 80) expect(site_443).to_not eq(site_80) end it 'compares values when determining equality' do site = create_site(scheme, host, port) sites = {} sites[site] = site another_site = create_site(scheme, host, port) expect(sites.include?(another_site)).to be_truthy end it 'computes the same hash code for equivalent objects' do site = create_site(scheme, host, port) same_site = create_site(scheme, host, port) expect(site.hash).to eq(same_site.hash) end it 'uses ssl with https' do site = create_site('https', host, port) expect(site).to be_use_ssl end it 'does not use ssl with http' do site = create_site('http', host, port) expect(site).to_not be_use_ssl end it 'moves to a new URI location' do site = create_site('http', 'host1', 80) uri = URI.parse('https://host2:443/some/where/else') new_site = site.move_to(uri) expect(new_site.scheme).to eq('https') expect(new_site.host).to eq('host2') expect(new_site.port).to eq(443) end end puppet-5.5.10/spec/unit/network/http/api_spec.rb0000644005276200011600000001225613417161722021512 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/handler' require 'puppet/network/http' require 'puppet/version' describe Puppet::Network::HTTP::API do def respond(text) lambda { |req, res| res.respond_with(200, "text/plain", text) } end describe "#not_found" do let(:response) { Puppet::Network::HTTP::MemoryResponse.new } let(:routes) { Puppet::Network::HTTP::Route.path(Regexp.new("foo")). any. chain(Puppet::Network::HTTP::Route.path(%r{^/bar$}).get(respond("bar")), Puppet::Network::HTTP::API.not_found) } it "mounts the chained routes" do request = Puppet::Network::HTTP::Request.from_hash(:path => "foo/bar") routes.process(request, response) expect(response.code).to eq(200) expect(response.body).to eq("bar") end it "responds to unknown paths with a 404" do request = Puppet::Network::HTTP::Request.from_hash(:path => "foo/unknown") expect do routes.process(request, response) end.to raise_error(Puppet::Network::HTTP::Error::HTTPNotFoundError) end end describe "Puppet API" do let(:handler) { PuppetSpec::Handler.new(Puppet::Network::HTTP::API.master_routes, Puppet::Network::HTTP::API.ca_routes, Puppet::Network::HTTP::API.not_found_upgrade) } let(:master_prefix) { Puppet::Network::HTTP::MASTER_URL_PREFIX } let(:ca_prefix) { Puppet::Network::HTTP::CA_URL_PREFIX } it "raises a not-found error for non-CA or master routes and suggests an upgrade" do req = Puppet::Network::HTTP::Request.from_hash(:path => "/unknown") res = {} handler.process(req, res) expect(res[:status]).to eq(404) expect(res[:body]).to include("Puppet version: #{Puppet.version}") end describe "when processing Puppet 3 routes" do it "gives an upgrade message for master routes" do req = Puppet::Network::HTTP::Request.from_hash(:path => "/production/node/foo") res = {} handler.process(req, res) expect(res[:status]).to eq(404) expect(res[:body]).to include("Puppet version: #{Puppet.version}") expect(res[:body]).to include("Supported /puppet API versions: #{Puppet::Network::HTTP::MASTER_URL_VERSIONS}") expect(res[:body]).to include("Supported /puppet-ca API versions: #{Puppet::Network::HTTP::CA_URL_VERSIONS}") end it "gives an upgrade message for CA routes" do req = Puppet::Network::HTTP::Request.from_hash(:path => "/production/certificate/foo") res = {} handler.process(req, res) expect(res[:status]).to eq(404) expect(res[:body]).to include("Puppet version: #{Puppet.version}") expect(res[:body]).to include("Supported /puppet API versions: #{Puppet::Network::HTTP::MASTER_URL_VERSIONS}") expect(res[:body]).to include("Supported /puppet-ca API versions: #{Puppet::Network::HTTP::CA_URL_VERSIONS}") end end describe "when processing master routes" do it "responds to v3 indirector requests" do req = Puppet::Network::HTTP::Request.from_hash(:path => "#{master_prefix}/v3/node/foo", :params => {:environment => "production"}, :headers => {'accept' => "application/json"}) res = {} handler.process(req, res) expect(res[:status]).to eq(200) end it "responds to v3 environments requests" do req = Puppet::Network::HTTP::Request.from_hash(:path => "#{master_prefix}/v3/environments") res = {} handler.process(req, res) expect(res[:status]).to eq(200) end it "responds with a not found error to non-v3 requests and does not suggest an upgrade" do req = Puppet::Network::HTTP::Request.from_hash(:path => "#{master_prefix}/unknown") res = {} handler.process(req, res) expect(res[:status]).to eq(404) expect(res[:body]).to include("No route for GET #{master_prefix}/unknown") expect(res[:body]).not_to include("Puppet version: #{Puppet.version}") end end describe "when processing CA routes" do it "responds to v1 indirector requests" do Puppet::SSL::Certificate.indirection.stubs(:find).returns "foo" req = Puppet::Network::HTTP::Request.from_hash(:path => "#{ca_prefix}/v1/certificate/foo", :params => {:environment => "production"}, :headers => {'accept' => "s"}) res = {} handler.process(req, res) expect(res[:body]).to eq("foo") expect(res[:status]).to eq(200) end it "responds with a not found error to non-v1 requests and does not suggest an upgrade" do req = Puppet::Network::HTTP::Request.from_hash(:path => "#{ca_prefix}/unknown") res = {} handler.process(req, res) expect(res[:status]).to eq(404) expect(res[:body]).to include("No route for GET #{ca_prefix}/unknown") expect(res[:body]).not_to include("Puppet version: #{Puppet.version}") end end end end puppet-5.5.10/spec/unit/network/http/compression_spec.rb0000644005276200011600000002124413417161722023277 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe "http compression" do let(:data) { "uncompresseddata" } let(:response) { stub 'response' } let(:compressed_zlib) { Zlib::Deflate.deflate(data) } let(:compressed_gzip) do str = StringIO.new writer = Zlib::GzipWriter.new(str) writer.write(data) writer.close.string end def stubs_response_with(response, content_encoding, body) response.stubs(:[]).with('content-encoding').returns(content_encoding) response.stubs(:body).returns(body) end describe "when zlib is not available" do before(:each) do Puppet.features.stubs(:zlib?).returns false require 'puppet/network/http/compression' class HttpUncompressor include Puppet::Network::HTTP::Compression::None end @uncompressor = HttpUncompressor.new end it "should have a module function that returns the None underlying module" do expect(Puppet::Network::HTTP::Compression.module).to eq(Puppet::Network::HTTP::Compression::None) end it "should not add any Accept-Encoding header" do expect(@uncompressor.add_accept_encoding({})).to eq({}) end it "should not tamper the body" do response = stub 'response', :body => data expect(@uncompressor.uncompress_body(response)).to eq(data) end it "should yield an identity uncompressor" do response = stub 'response' @uncompressor.uncompress(response) { |u| expect(u).to be_instance_of(Puppet::Network::HTTP::Compression::IdentityAdapter) } end end describe "when zlib is available" do require 'puppet/network/http/compression' class ActiveUncompressor include Puppet::Network::HTTP::Compression::Active end let(:uncompressor) { ActiveUncompressor.new } it "should have a module function that returns the Active underlying module" do expect(Puppet::Network::HTTP::Compression.module).to eq(Puppet::Network::HTTP::Compression::Active) end it "should add an Accept-Encoding header supporting compression" do headers = uncompressor.add_accept_encoding({}) expect(headers).to have_key('accept-encoding') expect(headers['accept-encoding']).to match(/gzip/) expect(headers['accept-encoding']).to match(/deflate/) expect(headers['accept-encoding']).to match(/identity/) end describe "when uncompressing response body" do context "without compression" do it "should return untransformed response body with no content-encoding" do stubs_response_with(response, nil, data) expect(uncompressor.uncompress_body(response)).to eq(data) end it "should return untransformed response body with 'identity' content-encoding" do stubs_response_with(response, 'identity', data) expect(uncompressor.uncompress_body(response)).to eq(data) end end context "with 'zlib' content-encoding" do it "should use a Zlib inflater" do stubs_response_with(response, 'deflate', compressed_zlib) expect(uncompressor.uncompress_body(response)).to eq(data) end end context "with 'gzip' content-encoding" do it "should use a GzipReader" do stubs_response_with(response, 'gzip', compressed_gzip) expect(uncompressor.uncompress_body(response)).to eq(data) end it "should correctly decompress PSON containing UTF-8 in Binary Encoding" do # Simulate a compressed response body containing PSON containing UTF-8 # using different UTF-8 widths: # \u06ff - Űż - http://www.fileformat.info/info/unicode/char/06ff/index.htm - 0xDB 0xBF / 219 191 # \u16A0 - áš  - http://www.fileformat.info/info/unicode/char/16A0/index.htm - 0xE1 0x9A 0xA0 / 225 154 160 # \u{2070E} - 𠜎 - http://www.fileformat.info/info/unicode/char/2070E/index.htm - 0xF0 0xA0 0x9C 0x8E / 240 160 156 142 pson = "foo\u06ff\u16A0\u{2070E}".to_pson # unicode expression eqivalent of "foo\xDB\xBF\xE1\x9A\xA0\xF0\xA0\x9C\x8E\" per above writer = Zlib::GzipWriter.new(StringIO.new) writer.write(pson) compressed_body = writer.close.string begin default_external = Encoding.default_external Encoding.default_external = Encoding::ISO_8859_1 stubs_response_with(response, 'gzip', compressed_body) uncompressed = uncompressor.uncompress_body(response) # By default Zlib::GzipReader decompresses into Encoding.default_external, and we want to ensure our result is BINARY too expect(uncompressed.encoding).to eq(Encoding::BINARY) expect(uncompressed).to eq("\"foo\xDB\xBF\xE1\x9A\xA0\xF0\xA0\x9C\x8E\"".force_encoding(Encoding::BINARY)) ensure Encoding.default_external = default_external end end end end describe "when uncompressing by chunk" do it "should yield an identity uncompressor with no content-encoding" do stubs_response_with(response, nil, data) expect { |b| uncompressor.uncompress(response).yield_once_with(Puppet::Network::HTTP::Compression::IdentityAdapter, &b) } end it "should yield an identity uncompressor with 'identity' content-encoding" do stubs_response_with(response, 'identity', data) expect { |b| uncompressor.uncompress(response).yield_once_with(Puppet::Network::HTTP::Compression::IdentityAdapter, &b) } end it "should yield a Zlib uncompressor with 'gzip' content-encoding" do stubs_response_with(response, 'gzip', compressed_gzip) expect { |b| uncompressor.uncompress(response).yield_once_with(Puppet::Network::HTTP::Compression::ZlibAdapter, &b) } end it "should yield a Zlib uncompressor with 'deflate' content-encoding" do stubs_response_with(response, 'deflate', compressed_zlib) expect { |b| uncompressor.uncompress(response).yield_once_with(Puppet::Network::HTTP::Compression::ZlibAdapter, &b) } end it "should close the underlying adapter" do stubs_response_with(response, 'identity', data) adapter = stub_everything 'adapter' Puppet::Network::HTTP::Compression::IdentityAdapter.expects(:new).returns(adapter) adapter.expects(:close) uncompressor.uncompress(response) { |u| } end it "should close the underlying adapter if the yielded block raises" do stubs_response_with(response, 'identity', data) adapter = stub_everything 'adapter' Puppet::Network::HTTP::Compression::IdentityAdapter.expects(:new).returns(adapter) adapter.expects(:close) expect { uncompressor.uncompress(response) { |u| raise ArgumentError, "whoops" } }.to raise_error(ArgumentError, "whoops") end end describe "zlib adapter" do it "should initialize the underlying inflater with gzip/zlib header parsing" do Zlib::Inflate.expects(:new).with(15+32) Puppet::Network::HTTP::Compression::Active::ZlibAdapter.new end it "should return the given chunk" do adapter = Puppet::Network::HTTP::Compression::Active::ZlibAdapter.new expect(adapter.uncompress(compressed_zlib)).to eq(data) end it "should try a 'regular' inflater on Zlib::DataError" do inflater = Zlib::Inflate.new(15 + 32) inflater.expects(:inflate).raises(Zlib::DataError.new("not a zlib stream")) adapter = Puppet::Network::HTTP::Compression::Active::ZlibAdapter.new(inflater) expect(adapter.uncompress(compressed_zlib)).to eq(data) end it "should raise the error the second time" do inflater = Zlib::Inflate.new(15 + 32) inflater.expects(:inflate).raises(Zlib::DataError.new("not a zlib stream")) adapter = Puppet::Network::HTTP::Compression::Active::ZlibAdapter.new(inflater) expect { adapter.uncompress("this is not compressed data") }.to raise_error(Zlib::DataError, /incorrect header check/) end it "should finish and close the stream" do inflater = stub 'inflater' inflater.expects(:finish) inflater.expects(:close) adapter = Puppet::Network::HTTP::Compression::Active::ZlibAdapter.new(inflater) adapter.close end it "should close the stream even if finish raises" do inflater = stub 'inflater' inflater.expects(:finish).raises(Zlib::BufError) inflater.expects(:close) adapter = Puppet::Network::HTTP::Compression::Active::ZlibAdapter.new(inflater) expect { adapter.close }.to raise_error(Zlib::BufError) end end end end puppet-5.5.10/spec/unit/network/http/connection_spec.rb0000644005276200011600000003130213417161722023071 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/network/http/connection' describe Puppet::Network::HTTP::Connection do let (:host) { "me" } let (:port) { 54321 } subject { Puppet::Network::HTTP::Connection.new(host, port, :verify => Puppet::SSL::Validator.no_validator) } let (:httpok) { Net::HTTPOK.new('1.1', 200, '') } context "when providing HTTP connections" do context "when initializing http instances" do it "should return an http instance created with the passed host and port" do conn = Puppet::Network::HTTP::Connection.new(host, port, :verify => Puppet::SSL::Validator.no_validator) expect(conn.address).to eq(host) expect(conn.port).to eq(port) end it "should enable ssl on the http instance by default" do conn = Puppet::Network::HTTP::Connection.new(host, port, :verify => Puppet::SSL::Validator.no_validator) expect(conn).to be_use_ssl end it "can disable ssl using an option" do conn = Puppet::Network::HTTP::Connection.new(host, port, :use_ssl => false, :verify => Puppet::SSL::Validator.no_validator) expect(conn).to_not be_use_ssl end it "can enable ssl using an option" do conn = Puppet::Network::HTTP::Connection.new(host, port, :use_ssl => true, :verify => Puppet::SSL::Validator.no_validator) expect(conn).to be_use_ssl end it "should raise Puppet::Error when invalid options are specified" do expect { Puppet::Network::HTTP::Connection.new(host, port, :invalid_option => nil) }.to raise_error(Puppet::Error, 'Unrecognized option(s): :invalid_option') end end end context "when handling requests", :vcr do let (:host) { "my-server" } let (:port) { 8140 } let (:subject) { Puppet::Network::HTTP::Connection.new(host, port, :use_ssl => false, :verify => Puppet::SSL::Validator.no_validator) } { :request_get => {}, :request_head => {}, :request_post => "param: value" }.each do |method,body| context "##{method}" do it "should yield to the block" do block_executed = false subject.send(method, "/foo", body) do |response| block_executed = true end expect(block_executed).to eq(true) end end end end class ConstantErrorValidator def initialize(args) @fails_with = args[:fails_with] @error_string = args[:error_string] || "" @peer_certs = args[:peer_certs] || [] end def setup_connection(connection) connection.stubs(:start).raises(OpenSSL::SSL::SSLError.new(@fails_with)) end def peer_certs @peer_certs end def verify_errors [@error_string] end end class NoProblemsValidator def initialize(cert) @cert = cert end def setup_connection(connection) end def peer_certs [@cert] end def verify_errors [] end end shared_examples_for 'ssl verifier' do include PuppetSpec::Files let (:host) { "my_server" } let (:port) { 8140 } before :all do WebMock.disable! end after :all do WebMock.enable! end it "should provide a useful error message when one is available and certificate validation fails", :unless => Puppet.features.microsoft_windows? do connection = Puppet::Network::HTTP::Connection.new( host, port, :verify => ConstantErrorValidator.new(:fails_with => 'certificate verify failed', :error_string => 'shady looking signature')) expect do connection.get('request') end.to raise_error(Puppet::Error, "certificate verify failed: [shady looking signature]") end it "should provide a helpful error message when hostname was not match with server certificate", :unless => Puppet.features.microsoft_windows? do Puppet[:confdir] = tmpdir('conf') connection = Puppet::Network::HTTP::Connection.new( host, port, :verify => ConstantErrorValidator.new( :fails_with => 'hostname was not match with server certificate', :peer_certs => [Puppet::SSL::CertificateAuthority.new.generate( 'not_my_server', :dns_alt_names => 'foo,bar,baz')])) expect do connection.get('request') end.to raise_error(Puppet::Error) do |error| error.message =~ /\AServer hostname 'my_server' did not match server certificate; expected one of (.+)/ expect($1.split(', ')).to match_array(%w[DNS:foo DNS:bar DNS:baz DNS:not_my_server not_my_server]) end end it "should pass along the error message otherwise" do connection = Puppet::Network::HTTP::Connection.new( host, port, :verify => ConstantErrorValidator.new(:fails_with => 'some other message')) expect do connection.get('request') end.to raise_error(/some other message/) end it "should check all peer certificates for upcoming expiration", :unless => Puppet.features.microsoft_windows? do Puppet[:confdir] = tmpdir('conf') cert = Puppet::SSL::CertificateAuthority.new.generate( 'server', :dns_alt_names => 'foo,bar,baz') connection = Puppet::Network::HTTP::Connection.new( host, port, :verify => NoProblemsValidator.new(cert)) Net::HTTP.any_instance.stubs(:start) Net::HTTP.any_instance.stubs(:request).returns(httpok) Puppet::Network::HTTP::Pool.any_instance.stubs(:setsockopts) connection.get('request') end end context "when using single use HTTPS connections" do it_behaves_like 'ssl verifier' do end end context "when using persistent HTTPS connections" do around :each do |example| pool = Puppet::Network::HTTP::Pool.new Puppet.override(:http_pool => pool) do example.run end pool.close end it_behaves_like 'ssl verifier' do end end context "when response is a redirect" do let (:site) { Puppet::Network::HTTP::Site.new('http', 'my_server', 8140) } let (:other_site) { Puppet::Network::HTTP::Site.new('http', 'redirected', 9292) } let (:other_path) { "other-path" } let (:verify) { Puppet::SSL::Validator.no_validator } let (:subject) { Puppet::Network::HTTP::Connection.new(site.host, site.port, :use_ssl => false, :verify => verify) } let (:httpredirection) do response = Net::HTTPFound.new('1.1', 302, 'Moved Temporarily') response['location'] = "#{other_site.addr}/#{other_path}" response.stubs(:read_body).returns("This resource has moved") response end def create_connection(site, options) options[:use_ssl] = site.use_ssl? Puppet::Network::HTTP::Connection.new(site.host, site.port, options) end it "should redirect to the final resource location" do http = stub('http') http.stubs(:request).returns(httpredirection).then.returns(httpok) seq = sequence('redirection') pool = Puppet.lookup(:http_pool) pool.expects(:with_connection).with(site, anything).yields(http).in_sequence(seq) pool.expects(:with_connection).with(other_site, anything).yields(http).in_sequence(seq) conn = create_connection(site, :verify => verify) conn.get('/foo') end def expects_redirection(conn, &block) http = stub('http') http.stubs(:request).returns(httpredirection) pool = Puppet.lookup(:http_pool) pool.expects(:with_connection).with(site, anything).yields(http) pool end def expects_limit_exceeded(conn) expect { conn.get('/') }.to raise_error(Puppet::Network::HTTP::RedirectionLimitExceededException) end it "should not redirect when the limit is 0" do conn = create_connection(site, :verify => verify, :redirect_limit => 0) pool = expects_redirection(conn) pool.expects(:with_connection).with(other_site, anything).never expects_limit_exceeded(conn) end it "should redirect only once" do conn = create_connection(site, :verify => verify, :redirect_limit => 1) pool = expects_redirection(conn) pool.expects(:with_connection).with(other_site, anything).once expects_limit_exceeded(conn) end it "should raise an exception when the redirect limit is exceeded" do conn = create_connection(site, :verify => verify, :redirect_limit => 3) pool = expects_redirection(conn) pool.expects(:with_connection).with(other_site, anything).times(3) expects_limit_exceeded(conn) end end context "when response indicates an overloaded server" do let(:http) { stub('http') } let(:site) { Puppet::Network::HTTP::Site.new('http', 'my_server', 8140) } let(:verify) { Puppet::SSL::Validator.no_validator } let(:httpunavailable) { Net::HTTPServiceUnavailable.new('1.1', 503, 'Service Unavailable') } subject { Puppet::Network::HTTP::Connection.new(site.host, site.port, :use_ssl => false, :verify => verify) } context "when parsing Retry-After headers" do # Private method. Create a reference that can be called by tests. let(:header_parser) { subject.method(:parse_retry_after_header) } it "returns 0 when parsing a RFC 2822 date that has passed" do test_date = 'Wed, 13 Apr 2005 15:18:05 GMT' expect(header_parser.call(test_date)).to eq(0) end end it "should return a 503 response if Retry-After is not set" do http.stubs(:request).returns(httpunavailable) pool = Puppet.lookup(:http_pool) pool.expects(:with_connection).with(site, anything).yields(http) result = subject.get('/foo') expect(result.code).to eq(503) end it "should return a 503 response if Retry-After is not convertible to an Integer or RFC 2822 Date" do httpunavailable['Retry-After'] = 'foo' http.stubs(:request).returns(httpunavailable) pool = Puppet.lookup(:http_pool) pool.expects(:with_connection).with(site, anything).yields(http) result = subject.get('/foo') expect(result.code).to eq(503) end it "should sleep and retry if Retry-After is an Integer" do httpunavailable['Retry-After'] = '42' http.stubs(:request).returns(httpunavailable).then.returns(httpok) pool = Puppet.lookup(:http_pool) pool.expects(:with_connection).with(site, anything).twice.yields(http) ::Kernel.expects(:sleep).with(42) result = subject.get('/foo') expect(result.code).to eq(200) end it "should sleep and retry if Retry-After is an RFC 2822 Date" do httpunavailable['Retry-After'] = 'Wed, 13 Apr 2005 15:18:05 GMT' http.stubs(:request).returns(httpunavailable).then.returns(httpok) now = DateTime.new(2005, 4, 13, 8, 17, 5, '-07:00') DateTime.stubs(:now).returns(now) pool = Puppet.lookup(:http_pool) pool.expects(:with_connection).with(site, anything).twice.yields(http) ::Kernel.expects(:sleep).with(60) result = subject.get('/foo') expect(result.code).to eq(200) end it "should sleep for no more than the Puppet runinterval" do httpunavailable['Retry-After'] = '60' http.stubs(:request).returns(httpunavailable).then.returns(httpok) Puppet[:runinterval] = 30 pool = Puppet.lookup(:http_pool) pool.expects(:with_connection).with(site, anything).twice.yields(http) ::Kernel.expects(:sleep).with(30) subject.get('/foo') end end it "allows setting basic auth on get requests" do expect_request_with_basic_auth subject.get('/path', nil, :basic_auth => { :user => 'user', :password => 'password' }) end it "allows setting basic auth on post requests" do expect_request_with_basic_auth subject.post('/path', 'data', nil, :basic_auth => { :user => 'user', :password => 'password' }) end it "allows setting basic auth on head requests" do expect_request_with_basic_auth subject.head('/path', nil, :basic_auth => { :user => 'user', :password => 'password' }) end it "allows setting basic auth on delete requests" do expect_request_with_basic_auth subject.delete('/path', nil, :basic_auth => { :user => 'user', :password => 'password' }) end it "allows setting basic auth on put requests" do expect_request_with_basic_auth subject.put('/path', 'data', nil, :basic_auth => { :user => 'user', :password => 'password' }) end def expect_request_with_basic_auth Net::HTTP.any_instance.expects(:request).with do |request| expect(request['authorization']).to match(/^Basic/) end.returns(httpok) end it "sets HTTP User-Agent header" do puppet_ua = "Puppet/#{Puppet.version} Ruby/#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL} (#{RUBY_PLATFORM})" Net::HTTP.any_instance.expects(:request).with do |request| expect(request['User-Agent']).to eq(puppet_ua) end.returns(httpok) subject.get('/path') end end puppet-5.5.10/spec/unit/network/http/factory_spec.rb0000644005276200011600000000675713417161722022421 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/network/http' require 'puppet/util/http_proxy' describe Puppet::Network::HTTP::Factory do before(:all) do ENV['http_proxy'] = nil ENV['HTTP_PROXY'] = nil end before :each do Puppet::SSL::Key.indirection.terminus_class = :memory Puppet::SSL::CertificateRequest.indirection.terminus_class = :memory end let(:site) { Puppet::Network::HTTP::Site.new('https', 'www.example.com', 443) } def create_connection(site) factory = Puppet::Network::HTTP::Factory.new factory.create_connection(site) end it 'creates a connection for the site' do conn = create_connection(site) expect(conn.use_ssl?).to be_truthy expect(conn.address).to eq(site.host) expect(conn.port).to eq(site.port) end it 'creates a connection that has not yet been started' do conn = create_connection(site) expect(conn).to_not be_started end it 'creates a connection supporting at least HTTP 1.1' do conn = create_connection(site) expect(any_of(conn.class.version_1_1?, conn.class.version_1_1?)).to be_truthy end context "proxy settings" do let(:proxy_host) { 'myhost' } let(:proxy_port) { 432 } it "should not set a proxy if the value is 'none'" do Puppet[:http_proxy_host] = 'none' Puppet::Util::HttpProxy.expects(:no_proxy?).returns false conn = create_connection(site) expect(conn.proxy_address).to be_nil end it 'should not set a proxy if a no_proxy env var matches the destination' do Puppet[:http_proxy_host] = proxy_host Puppet[:http_proxy_port] = proxy_port Puppet::Util::HttpProxy.expects(:no_proxy?).returns true conn = create_connection(site) expect(conn.proxy_address).to be_nil expect(conn.proxy_port).to be_nil end it 'sets proxy_address' do Puppet[:http_proxy_host] = proxy_host Puppet::Util::HttpProxy.expects(:no_proxy?).returns false conn = create_connection(site) expect(conn.proxy_address).to eq(proxy_host) end it 'sets proxy address and port' do Puppet[:http_proxy_host] = proxy_host Puppet[:http_proxy_port] = proxy_port Puppet::Util::HttpProxy.expects(:no_proxy?).returns false conn = create_connection(site) expect(conn.proxy_port).to eq(proxy_port) end end context 'socket timeouts' do it 'sets open timeout' do Puppet[:http_connect_timeout] = "10s" conn = create_connection(site) expect(conn.open_timeout).to eq(10) end it 'sets read timeout' do Puppet[:http_read_timeout] = "2m" conn = create_connection(site) expect(conn.read_timeout).to eq(120) end end it "disables ruby's http_keepalive_timeout on Ruby 2" do skip "Requires Ruby >= 2.0" unless RUBY_VERSION.to_i >= 2 conn = create_connection(site) expect(conn.keep_alive_timeout).to eq(2147483647) end context 'source address' do it 'defaults to system-defined' do skip "Requires Ruby >= 2.0" unless RUBY_VERSION.to_i >= 2 conn = create_connection(site) expect(conn.local_host).to be(nil) end it 'sets the local_host address' do Puppet[:sourceaddress] = "127.0.0.1" if RUBY_VERSION.to_i >= 2 conn = create_connection(site) expect(conn.local_host).to eq('127.0.0.1') else expect { create_connection(site) }.to raise_error(ArgumentError, "Setting 'sourceaddress' is unsupported by this version of Net::HTTP.") end end end end puppet-5.5.10/spec/unit/network/http/rack/0000755005276200011600000000000013417162176020320 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/network/http/rack/rest_spec.rb0000644005276200011600000002773713417161722022650 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/network/http/rack' if Puppet.features.rack? require 'puppet/network/http/rack/rest' describe "Puppet::Network::HTTP::RackREST", :if => Puppet.features.rack? do it "should include the Puppet::Network::HTTP::Handler module" do expect(Puppet::Network::HTTP::RackREST.ancestors).to be_include(Puppet::Network::HTTP::Handler) end describe "when serving a request" do before :all do @model_class = stub('indirected model class') Puppet::Indirector::Indirection.stubs(:model).with(:foo).returns(@model_class) end before :each do @response = Rack::Response.new @handler = Puppet::Network::HTTP::RackREST.new(:handler => :foo) end def mk_req(uri, opts = {}) env = Rack::MockRequest.env_for(uri, opts) Rack::Request.new(env) end let(:minimal_certificate) do key = OpenSSL::PKey::RSA.new(512) signer = Puppet::SSL::CertificateSigner.new cert = OpenSSL::X509::Certificate.new cert.version = 2 cert.serial = 0 cert.not_before = Time.now cert.not_after = Time.now + 3600 cert.public_key = key cert.subject = OpenSSL::X509::Name.parse("/CN=testing") signer.sign(cert, key) cert end describe "#headers" do it "should return the headers (parsed from env with prefix 'HTTP_')" do req = mk_req('/', {'HTTP_Accept' => 'myaccept', 'HTTP_X_Custom_Header' => 'mycustom', 'NOT_HTTP_foo' => 'not an http header'}) expect(@handler.headers(req)).to eq({"accept" => 'myaccept', "x-custom-header" => 'mycustom', "content-type" => nil }) end end describe "and using the HTTP Handler interface" do it "should return the CONTENT_TYPE parameter as the content type header" do req = mk_req('/', 'CONTENT_TYPE' => 'mycontent') expect(@handler.headers(req)['content-type']).to eq("mycontent") end it "should use the REQUEST_METHOD as the http method" do req = mk_req('/', :method => 'MYMETHOD') expect(@handler.http_method(req)).to eq("MYMETHOD") end it "should return the request path as the path" do req = mk_req('/foo/bar') expect(@handler.path(req)).to eq("/foo/bar") end it "should return the unescaped path for an escaped request path" do unescaped_path = '/foo/bar baz' escaped_path = Puppet::Util.uri_encode(unescaped_path) req = mk_req(escaped_path) expect(@handler.path(req)).to eq(unescaped_path) end it "should return the request body as the body" do req = mk_req('/foo/bar', :input => 'mybody') expect(@handler.body(req)).to eq("mybody") end it "should return the an Puppet::SSL::Certificate instance as the client_cert" do req = mk_req('/foo/bar', 'SSL_CLIENT_CERT' => minimal_certificate.to_pem) expect(@handler.client_cert(req).content.to_pem).to eq(minimal_certificate.to_pem) end it "returns nil when SSL_CLIENT_CERT is empty" do req = mk_req('/foo/bar', 'SSL_CLIENT_CERT' => '') expect(@handler.client_cert(req)).to be_nil end it "should set the response's content-type header when setting the content type" do @header = mock 'header' @response.expects(:header).returns @header @header.expects(:[]=).with('Content-Type', "mytype") @handler.set_content_type(@response, "mytype") end it "should set the status and write the body when setting the response for a request" do @response.expects(:status=).with(400) @response.expects(:write).with("mybody") @handler.set_response(@response, "mybody", 400) end describe "when result is a File" do before :each do stat = stub 'stat', :size => 100 @file = stub 'file', :stat => stat, :path => "/tmp/path" @file.stubs(:is_a?).with(File).returns(true) end it "should set the Content-Length header as a string" do @response.expects(:[]=).with("Content-Length", '100') @handler.set_response(@response, @file, 200) end it "should return a RackFile adapter as body" do @response.expects(:body=).with { |val| val.is_a?(Puppet::Network::HTTP::RackREST::RackFile) } @handler.set_response(@response, @file, 200) end end it "should ensure the body has been read on success" do req = mk_req('/production/report/foo', :method => 'PUT') req.body.expects(:read).at_least_once Puppet::Transaction::Report.stubs(:save) @handler.process(req, @response) end it "should ensure the body has been partially read on failure" do req = mk_req('/production/report/foo') req.body.expects(:read).with(1) @handler.stubs(:headers).raises(StandardError) @handler.process(req, @response) end end describe "and determining the request parameters" do it "should include the HTTP request parameters, with the keys as symbols" do req = mk_req('/?foo=baz&bar=xyzzy') result = @handler.params(req) expect(result[:foo]).to eq("baz") expect(result[:bar]).to eq("xyzzy") end it "should return multi-values params as an array of the values" do req = mk_req('/?foo=baz&foo=xyzzy') result = @handler.params(req) expect(result[:foo]).to eq(["baz", "xyzzy"]) end it "should return parameters from the POST body" do req = mk_req("/", :method => 'POST', :input => 'foo=baz&bar=xyzzy') result = @handler.params(req) expect(result[:foo]).to eq("baz") expect(result[:bar]).to eq("xyzzy") end it "should not return multi-valued params in a POST body as an array of values" do req = mk_req("/", :method => 'POST', :input => 'foo=baz&foo=xyzzy') result = @handler.params(req) expect(result[:foo]).to be_one_of("baz", "xyzzy") end it "should CGI-decode the HTTP parameters" do encoding = Puppet::Util.uri_query_encode("foo bar") req = mk_req("/?foo=#{encoding}") result = @handler.params(req) expect(result[:foo]).to eq("foo bar") end it "should convert the string 'true' to the boolean" do req = mk_req("/?foo=true") result = @handler.params(req) expect(result[:foo]).to be_truthy end it "should convert the string 'false' to the boolean" do req = mk_req("/?foo=false") result = @handler.params(req) expect(result[:foo]).to be_falsey end it "should convert integer arguments to Integers" do req = mk_req("/?foo=15") result = @handler.params(req) expect(result[:foo]).to eq(15) end it "should convert floating point arguments to Floats" do req = mk_req("/?foo=1.5") result = @handler.params(req) expect(result[:foo]).to eq(1.5) end it "should treat YAML encoded parameters like it was any string" do escaping = Puppet::Util.uri_query_encode(YAML.dump(%w{one two})) req = mk_req("/?foo=#{escaping}") expect(@handler.params(req)[:foo]).to eq("---\n- one\n- two\n") end it "should not allow the client to set the node via the query string" do req = mk_req("/?node=foo") expect(@handler.params(req)[:node]).to be_nil end it "should not allow the client to set the IP address via the query string" do req = mk_req("/?ip=foo") expect(@handler.params(req)[:ip]).to be_nil end it "should pass the client's ip address to model find" do req = mk_req("/", 'REMOTE_ADDR' => 'ipaddress') expect(@handler.params(req)[:ip]).to eq("ipaddress") end it "should set 'authenticated' to false if no certificate is present" do req = mk_req('/') expect(@handler.params(req)[:authenticated]).to be_falsey end end describe "with pre-validated certificates" do it "should retrieve the hostname by finding the CN given in :ssl_client_header, in the format returned by Apache (RFC2253)" do Puppet[:ssl_client_header] = "myheader" req = mk_req('/', "myheader" => "O=Foo\\, Inc,CN=host.domain.com") expect(@handler.params(req)[:node]).to eq("host.domain.com") end it "should retrieve the hostname by finding the CN given in :ssl_client_header, in the format returned by nginx" do Puppet[:ssl_client_header] = "myheader" req = mk_req('/', "myheader" => "/CN=host.domain.com") expect(@handler.params(req)[:node]).to eq("host.domain.com") end it "should retrieve the hostname by finding the CN given in :ssl_client_header, ignoring other fields" do Puppet[:ssl_client_header] = "myheader" req = mk_req('/', "myheader" => 'ST=Denial,CN=host.domain.com,O=Domain\\, Inc.') expect(@handler.params(req)[:node]).to eq("host.domain.com") end it "should use the :ssl_client_header to determine the parameter for checking whether the host certificate is valid" do Puppet[:ssl_client_header] = "certheader" Puppet[:ssl_client_verify_header] = "myheader" req = mk_req('/', "myheader" => "SUCCESS", "certheader" => "CN=host.domain.com") expect(@handler.params(req)[:authenticated]).to be_truthy end it "should consider the host unauthenticated if the validity parameter does not contain 'SUCCESS'" do Puppet[:ssl_client_header] = "certheader" Puppet[:ssl_client_verify_header] = "myheader" req = mk_req('/', "myheader" => "whatever", "certheader" => "CN=host.domain.com") expect(@handler.params(req)[:authenticated]).to be_falsey end it "should consider the host unauthenticated if no certificate information is present" do Puppet[:ssl_client_header] = "certheader" Puppet[:ssl_client_verify_header] = "myheader" req = mk_req('/', "myheader" => nil, "certheader" => "CN=host.domain.com") expect(@handler.params(req)[:authenticated]).to be_falsey end it "should resolve the node name with an ip address look-up if no certificate is present" do Puppet[:ssl_client_header] = "myheader" req = mk_req('/', "myheader" => nil) @handler.expects(:resolve_node).returns("host.domain.com") expect(@handler.params(req)[:node]).to eq("host.domain.com") end it "should resolve the node name with an ip address look-up if a certificate without a CN is present" do Puppet[:ssl_client_header] = "myheader" req = mk_req('/', "myheader" => "O=no CN") @handler.expects(:resolve_node).returns("host.domain.com") expect(@handler.params(req)[:node]).to eq("host.domain.com") end it "should not allow authentication via the verify header if there is no CN available" do Puppet[:ssl_client_header] = "dn_header" Puppet[:ssl_client_verify_header] = "verify_header" req = mk_req('/', "dn_header" => "O=no CN", "verify_header" => 'SUCCESS') @handler.expects(:resolve_node).returns("host.domain.com") expect(@handler.params(req)[:authenticated]).to be_falsey end end end end describe Puppet::Network::HTTP::RackREST::RackFile do before(:each) do stat = stub 'stat', :size => 100 @file = stub 'file', :stat => stat, :path => "/tmp/path" @rackfile = Puppet::Network::HTTP::RackREST::RackFile.new(@file) end it "should have an each method" do expect(@rackfile).to be_respond_to(:each) end it "should yield file chunks by chunks" do @file.expects(:read).times(3).with(8192).returns("1", "2", nil) i = 1 @rackfile.each do |chunk| expect(chunk.to_i).to eq(i) i += 1 end end it "should have a close method" do expect(@rackfile).to be_respond_to(:close) end it "should delegate close to File close" do @file.expects(:close) @rackfile.close end end puppet-5.5.10/spec/unit/network/http/rack_spec.rb0000644005276200011600000000253713417161722021662 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/network/http/rack' if Puppet.features.rack? describe "Puppet::Network::HTTP::Rack", :if => Puppet.features.rack? do describe "when called" do before :all do @app = Puppet::Network::HTTP::Rack.new() # let's use Rack::Lint to verify that we're OK with the rack specification @linted = Rack::Lint.new(@app) end before :each do @env = Rack::MockRequest.env_for('/') end it "should create a Request object" do request = Rack::Request.new(@env) Rack::Request.expects(:new).returns request @linted.call(@env) end it "should create a Response object" do Rack::Response.expects(:new).returns stub_everything @app.call(@env) # can't lint when Rack::Response is a stub end it "should let RackREST process the request" do Puppet::Network::HTTP::RackREST.any_instance.expects(:process).once @linted.call(@env) end it "should catch unhandled exceptions from RackREST" do Puppet::Network::HTTP::RackREST.any_instance.expects(:process).raises(ArgumentError, 'test error') expect { @linted.call(@env) }.not_to raise_error end it "should finish() the Response" do Rack::Response.any_instance.expects(:finish).once @app.call(@env) # can't lint when finish is a stub end end end puppet-5.5.10/spec/unit/network/http/webrick/0000755005276200011600000000000013417162176021026 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/network/http/webrick/rest_spec.rb0000644005276200011600000002037213417161722023342 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/network/http' require 'webrick' require 'puppet/network/http/webrick/rest' describe Puppet::Network::HTTP::WEBrickREST do it "should include the Puppet::Network::HTTP::Handler module" do expect(Puppet::Network::HTTP::WEBrickREST.ancestors).to be_include(Puppet::Network::HTTP::Handler) end describe "when receiving a request" do before do @request = stub('webrick http request', :query => {}, :query_string => 'environment=production', :peeraddr => %w{eh boo host ip}, :request_method => 'GET', :client_cert => nil) @response = mock('webrick http response') @model_class = stub('indirected model class') @webrick = stub('webrick http server', :mount => true, :[] => {}) Puppet::Indirector::Indirection.stubs(:model).with(:foo).returns(@model_class) @handler = Puppet::Network::HTTP::WEBrickREST.new(@webrick) end it "should delegate its :service method to its :process method" do @handler.expects(:process).with(@request, @response).returns "stuff" expect(@handler.service(@request, @response)).to eq("stuff") end describe "#headers" do let(:fake_request) { {"Foo" => "bar", "BAZ" => "bam" } } it "should iterate over the request object using #each" do fake_request.expects(:each) @handler.headers(fake_request) end it "should return a hash with downcased header names" do result = @handler.headers(fake_request) expect(result).to eq(fake_request.inject({}) { |m,(k,v)| m[k.downcase] = v; m }) end end describe "when using the Handler interface" do it "should use the request method as the http method" do @request.expects(:request_method).returns "FOO" expect(@handler.http_method(@request)).to eq("FOO") end it "should return the request path as the path" do @request.expects(:path).returns "/foo/bar" expect(@handler.path(@request)).to eq("/foo/bar") end it "should return the request body as the body" do @request.stubs(:request_method).returns "POST" @request.expects(:body).returns "my body" expect(@handler.body(@request)).to eq("my body") end it "should set the response's 'content-type' header when setting the content type" do @response.expects(:[]=).with("content-type", "text/html") @handler.set_content_type(@response, "text/html") end it "should set the status and body on the response when setting the response for a successful query" do @response.expects(:status=).with 200 @response.expects(:body=).with "mybody" @handler.set_response(@response, "mybody", 200) end it "serves a file" do stat = stub 'stat', :size => 100 @file = stub 'file', :stat => stat, :path => "/tmp/path" @file.stubs(:is_a?).with(File).returns(true) @response.expects(:[]=).with('content-length', 100) @response.expects(:status=).with 200 @response.expects(:body=).with @file @handler.set_response(@response, @file, 200) end it "should set the status and message on the response when setting the response for a failed query" do @response.expects(:status=).with 400 @response.expects(:body=).with "mybody" @handler.set_response(@response, "mybody", 400) end end describe "and determining the request parameters" do def query_of(options) request = Puppet::Indirector::Request.new(:myind, :find, "my key", nil, options) WEBrick::HTTPUtils.parse_query(request.query_string.sub(/^\?/, '')) end def a_request_querying(query_data) @request.expects(:query).returns(query_of(query_data)) @request end def certificate_with_subject(subj) cert = OpenSSL::X509::Certificate.new cert.subject = OpenSSL::X509::Name.parse(subj) cert end it "has no parameters when there is no query string" do only_server_side_information = [:authenticated, :ip, :node] @request.stubs(:query).returns(nil) result = @handler.params(@request) expect(result.keys.sort).to eq(only_server_side_information) end it "should prefer duplicate params from the body over the query string" do @request.stubs(:request_method).returns "PUT" @request.stubs(:query).returns(WEBrick::HTTPUtils.parse_query("foo=bar&environment=posted_env")) expect(@handler.params(@request)[:environment]).to eq("posted_env") end it "should include the HTTP request parameters, with the keys as symbols" do request = a_request_querying("foo" => "baz", "bar" => "xyzzy") result = @handler.params(request) expect(result[:foo]).to eq("baz") expect(result[:bar]).to eq("xyzzy") end it "should handle parameters with no value" do request = a_request_querying('foo' => "") result = @handler.params(request) expect(result[:foo]).to eq("") end it "should convert the string 'true' to the boolean" do request = a_request_querying('foo' => "true") result = @handler.params(request) expect(result[:foo]).to eq(true) end it "should convert the string 'false' to the boolean" do request = a_request_querying('foo' => "false") result = @handler.params(request) expect(result[:foo]).to eq(false) end it "should reconstruct arrays" do request = a_request_querying('foo' => ["a", "b", "c"]) result = @handler.params(request) expect(result[:foo]).to eq(["a", "b", "c"]) end it "should convert values inside arrays into primitive types" do request = a_request_querying('foo' => ["true", "false", "1", "1.2"]) result = @handler.params(request) expect(result[:foo]).to eq([true, false, 1, 1.2]) end it "should treat YAML-load values that are YAML-encoded as any other String" do request = a_request_querying('foo' => YAML.dump(%w{one two})) expect(@handler.params(request)[:foo]).to eq("---\n- one\n- two\n") end it "should not allow clients to set the node via the request parameters" do request = a_request_querying("node" => "foo") @handler.stubs(:resolve_node) expect(@handler.params(request)[:node]).to be_nil end it "should not allow clients to set the IP via the request parameters" do request = a_request_querying("ip" => "foo") expect(@handler.params(request)[:ip]).not_to eq("foo") end it "should pass the client's ip address to model find" do @request.stubs(:peeraddr).returns(%w{noidea dunno hostname ipaddress}) expect(@handler.params(@request)[:ip]).to eq("ipaddress") end it "should set 'authenticated' to true if a certificate is present" do cert = stub 'cert', :subject => [%w{CN host.domain.com}] @request.stubs(:client_cert).returns cert expect(@handler.params(@request)[:authenticated]).to be_truthy end it "should set 'authenticated' to false if no certificate is present" do @request.stubs(:client_cert).returns nil expect(@handler.params(@request)[:authenticated]).to be_falsey end it "should pass the client's certificate name to model method if a certificate is present" do @request.stubs(:client_cert).returns(certificate_with_subject("/CN=host.domain.com")) expect(@handler.params(@request)[:node]).to eq("host.domain.com") end it "should resolve the node name with an ip address look-up if no certificate is present" do @request.stubs(:client_cert).returns nil @handler.expects(:resolve_node).returns(:resolved_node) expect(@handler.params(@request)[:node]).to eq(:resolved_node) end it "should resolve the node name with an ip address look-up if CN parsing fails" do @request.stubs(:client_cert).returns(certificate_with_subject("/C=company")) @handler.expects(:resolve_node).returns(:resolved_node) expect(@handler.params(@request)[:node]).to eq(:resolved_node) end end end end puppet-5.5.10/spec/unit/network/http/webrick_spec.rb0000644005276200011600000002047113417161722022365 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/network/http' require 'puppet/network/http/webrick' describe Puppet::Network::HTTP::WEBrick, "after initializing" do it "should not be listening" do expect(Puppet::Network::HTTP::WEBrick.new).not_to be_listening end end describe Puppet::Network::HTTP::WEBrick do include PuppetSpec::Files let(:address) { '127.0.0.1' } let(:port) { 31337 } let(:server) { Puppet::Network::HTTP::WEBrick.new } let(:localcacert) { make_absolute("/ca/crt") } let(:ssl_server_ca_auth) { make_absolute("/ca/ssl_server_auth_file") } let(:key) { stub 'key', :content => "mykey" } let(:cert) { stub 'cert', :content => "mycert" } let(:host) { stub 'host', :key => key, :certificate => cert, :name => "yay", :ssl_store => "mystore" } let(:mock_ssl_context) do stub('ssl_context', :ciphers= => nil) end let(:socket) { mock('socket') } let(:mock_webrick) do server = stub('webrick', :[] => {}, :listeners => [], :status => :Running, :mount => nil, :shutdown => nil, :ssl_context => mock_ssl_context) server.stubs(:start).yields(socket) IO.stubs(:select).with([socket], nil, nil, anything).returns(true) socket.stubs(:accept) server.stubs(:run).with(socket) server end before :each do WEBrick::HTTPServer.stubs(:new).returns(mock_webrick) Puppet::SSL::Certificate.indirection.stubs(:find).with('ca').returns cert Puppet::SSL::Host.stubs(:localhost).returns host end describe "when turning on listening" do it "should fail if already listening" do server.listen(address, port) expect { server.listen(address, port) }.to raise_error(RuntimeError, /server is already listening/) end it "should tell webrick to listen on the specified address and port" do WEBrick::HTTPServer.expects(:new).with( has_entries(:Port => 31337, :BindAddress => "127.0.0.1") ).returns(mock_webrick) server.listen(address, port) end it "should not perform reverse lookups" do WEBrick::HTTPServer.expects(:new).with( has_entry(:DoNotReverseLookup => true) ).returns(mock_webrick) BasicSocket.expects(:do_not_reverse_lookup=).with(true) server.listen(address, port) end it "should configure a logger for webrick" do server.expects(:setup_logger).returns(:Logger => :mylogger) WEBrick::HTTPServer.expects(:new).with {|args| args[:Logger] == :mylogger }.returns(mock_webrick) server.listen(address, port) end it "should configure SSL for webrick" do server.expects(:setup_ssl).returns(:Ssl => :testing, :Other => :yay) WEBrick::HTTPServer.expects(:new).with {|args| args[:Ssl] == :testing and args[:Other] == :yay }.returns(mock_webrick) server.listen(address, port) end it "should be listening" do server.listen(address, port) expect(server).to be_listening end it "is passed a yet to be accepted socket" do socket.expects(:accept) server.listen(address, port) server.unlisten end describe "when the REST protocol is requested" do it "should register the REST handler at /" do # We don't care about the options here. mock_webrick.expects(:mount).with("/", Puppet::Network::HTTP::WEBrickREST, anything) server.listen(address, port) end end end describe "when turning off listening" do it "should fail unless listening" do expect { server.unlisten }.to raise_error(RuntimeError, /server is not listening/) end it "should order webrick server to stop" do mock_webrick.expects(:shutdown) server.listen(address, port) server.unlisten end it "should no longer be listening" do server.listen(address, port) server.unlisten expect(server).not_to be_listening end end describe "when configuring an http logger" do let(:server) { Puppet::Network::HTTP::WEBrick.new } before :each do Puppet.settings.stubs(:use) @filehandle = stub 'handle', :fcntl => nil, :sync= => nil File.stubs(:open).returns @filehandle end it "should use the settings for :main, :ssl, and :application" do Puppet.settings.expects(:use).with(:main, :ssl, :application) server.setup_logger end it "should use the masterhttplog" do log = make_absolute("/master/log") Puppet[:masterhttplog] = log File.expects(:open).with(log, "a+:UTF-8").returns @filehandle server.setup_logger end describe "and creating the logging filehandle" do it "should set the close-on-exec flag if supported" do if defined? Fcntl::FD_CLOEXEC @filehandle.expects(:fcntl).with(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) else @filehandle.expects(:fcntl).never end server.setup_logger end it "should sync the filehandle" do @filehandle.expects(:sync=).with(true) server.setup_logger end end it "should create a new WEBrick::Log instance with the open filehandle" do WEBrick::Log.expects(:new).with(@filehandle) server.setup_logger end it "should set debugging if the current loglevel is :debug" do Puppet::Util::Log.expects(:level).returns :debug WEBrick::Log.expects(:new).with { |handle, debug| debug == WEBrick::Log::DEBUG } server.setup_logger end it "should return the logger as the main log" do logger = mock 'logger' WEBrick::Log.expects(:new).returns logger expect(server.setup_logger[:Logger]).to eq(logger) end it "should return the logger as the access log using both the Common and Referer log format" do logger = mock 'logger' WEBrick::Log.expects(:new).returns logger expect(server.setup_logger[:AccessLog]).to eq([ [logger, WEBrick::AccessLog::COMMON_LOG_FORMAT], [logger, WEBrick::AccessLog::REFERER_LOG_FORMAT] ]) end end describe "when configuring ssl" do it "should use the key from the localhost SSL::Host instance" do Puppet::SSL::Host.expects(:localhost).returns host host.expects(:key).returns key expect(server.setup_ssl[:SSLPrivateKey]).to eq("mykey") end it "should configure the certificate" do expect(server.setup_ssl[:SSLCertificate]).to eq("mycert") end it "should fail if no CA certificate can be found" do Puppet::SSL::Certificate.indirection.stubs(:find).with('ca').returns nil expect { server.setup_ssl }.to raise_error(Puppet::Error, /Could not find CA certificate/) end it "should specify the path to the CA certificate" do Puppet.settings[:hostcrl] = 'false' Puppet.settings[:localcacert] = localcacert expect(server.setup_ssl[:SSLCACertificateFile]).to eq(localcacert) end it "should specify the path to the CA certificate" do Puppet.settings[:hostcrl] = 'false' Puppet.settings[:localcacert] = localcacert Puppet.settings[:ssl_server_ca_auth] = ssl_server_ca_auth expect(server.setup_ssl[:SSLCACertificateFile]).to eq(ssl_server_ca_auth) end it "should not start ssl immediately" do expect(server.setup_ssl[:SSLStartImmediately]).to eq(false) end it "should enable ssl" do expect(server.setup_ssl[:SSLEnable]).to be_truthy end it "should reject SSLv2" do options = server.setup_ssl[:SSLOptions] expect(options & OpenSSL::SSL::OP_NO_SSLv2).to eq(OpenSSL::SSL::OP_NO_SSLv2) end it "should reject SSLv3" do options = server.setup_ssl[:SSLOptions] expect(options & OpenSSL::SSL::OP_NO_SSLv3).to eq(OpenSSL::SSL::OP_NO_SSLv3) end it "should configure the verification method as 'OpenSSL::SSL::VERIFY_PEER'" do expect(server.setup_ssl[:SSLVerifyClient]).to eq(OpenSSL::SSL::VERIFY_PEER) end it "should add an x509 store" do host.expects(:ssl_store).returns "mystore" expect(server.setup_ssl[:SSLCertificateStore]).to eq("mystore") end it "should set the certificate name to 'nil'" do expect(server.setup_ssl[:SSLCertName]).to be_nil end it "specifies the allowable ciphers" do mock_ssl_context.expects(:ciphers=).with(server.class::CIPHERS) server.create_server('localhost', '8888') end end end puppet-5.5.10/spec/unit/network/http_pool_spec.rb0000644005276200011600000000700513417161721021765 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/network/http_pool' describe Puppet::Network::HttpPool do before :each do Puppet::SSL::Key.indirection.terminus_class = :memory Puppet::SSL::CertificateRequest.indirection.terminus_class = :memory end describe "when managing http instances" do it "should return an http instance created with the passed host and port" do http = Puppet::Network::HttpPool.http_instance("me", 54321) expect(http).to be_an_instance_of Puppet::Network::HTTP::Connection expect(http.address).to eq('me') expect(http.port).to eq(54321) end it "should support using an alternate http client implementation" do begin class FooClient def initialize(host, port, options = {}) @host = host @port = port end attr_reader :host, :port end orig_class = Puppet::Network::HttpPool.http_client_class Puppet::Network::HttpPool.http_client_class = FooClient http = Puppet::Network::HttpPool.http_instance("me", 54321) expect(http).to be_an_instance_of FooClient expect(http.host).to eq('me') expect(http.port).to eq(54321) ensure Puppet::Network::HttpPool.http_client_class = orig_class end end it "should enable ssl on the http instance by default" do expect(Puppet::Network::HttpPool.http_instance("me", 54321)).to be_use_ssl end it "can set ssl using an option" do expect(Puppet::Network::HttpPool.http_instance("me", 54321, false)).not_to be_use_ssl expect(Puppet::Network::HttpPool.http_instance("me", 54321, true)).to be_use_ssl end describe 'peer verification' do def setup_standard_ssl_configuration ca_cert_file = File.expand_path('/path/to/ssl/certs/ca_cert.pem') Puppet[:ssl_client_ca_auth] = ca_cert_file Puppet::FileSystem.stubs(:exist?).with(ca_cert_file).returns(true) end def setup_standard_hostcert host_cert_file = File.expand_path('/path/to/ssl/certs/host_cert.pem') Puppet::FileSystem.stubs(:exist?).with(host_cert_file).returns(true) Puppet[:hostcert] = host_cert_file end def setup_standard_ssl_host cert = stub('cert', :content => 'real_cert') key = stub('key', :content => 'real_key') host = stub('host', :certificate => cert, :key => key, :ssl_store => stub('store')) Puppet::SSL::Host.stubs(:localhost).returns(host) end before do setup_standard_ssl_configuration setup_standard_hostcert setup_standard_ssl_host end it 'enables peer verification by default' do response = Net::HTTPOK.new('1.1', 200, 'body') conn = Puppet::Network::HttpPool.http_instance("me", 54321, true) conn.expects(:execute_request).with { |http, request| expect(http.verify_mode).to eq(OpenSSL::SSL::VERIFY_PEER) }.returns(response) conn.get('/') end it 'can disable peer verification' do response = Net::HTTPOK.new('1.1', 200, 'body') conn = Puppet::Network::HttpPool.http_instance("me", 54321, true, false) conn.expects(:execute_request).with { |http, request| expect(http.verify_mode).to eq(OpenSSL::SSL::VERIFY_NONE) }.returns(response) conn.get('/') end end it "should not cache http instances" do expect(Puppet::Network::HttpPool.http_instance("me", 54321)). not_to equal(Puppet::Network::HttpPool.http_instance("me", 54321)) end end end puppet-5.5.10/spec/unit/network/http_spec.rb0000644005276200011600000000037713417161721020741 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/network/http' describe Puppet::Network::HTTP do it 'defines an http_pool context' do pool = Puppet.lookup(:http_pool) expect(pool).to be_a(Puppet::Network::HTTP::NoCachePool) end end puppet-5.5.10/spec/unit/network/rights_spec.rb0000644005276200011600000003573513417161721021270 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/network/rights' describe Puppet::Network::Rights do before do @right = Puppet::Network::Rights.new end describe "when validating a :head request" do [:find, :save].each do |allowed_method| it "should allow the request if only #{allowed_method} is allowed" do rights = Puppet::Network::Rights.new right = rights.newright("/") right.allow("*") right.restrict_method(allowed_method) right.restrict_authenticated(:any) expect(rights.is_request_forbidden_and_why?(:head, "/indirection_name/key", {})).to eq(nil) end end it "should disallow the request if neither :find nor :save is allowed" do rights = Puppet::Network::Rights.new why_forbidden = rights.is_request_forbidden_and_why?(:head, "/indirection_name/key", {}) expect(why_forbidden).to be_instance_of(Puppet::Network::AuthorizationError) expect(why_forbidden.to_s).to eq("Forbidden request: /indirection_name/key [find]") end end it "should throw an error if type can't be determined" do expect { @right.newright("name") }.to raise_error(ArgumentError, /Unknown right type/) end describe "when creating new path ACLs" do it "should not throw an error if the ACL already exists" do @right.newright("/name") expect { @right.newright("/name")}.not_to raise_error end it "should throw an error if the acl uri path is not absolute" do expect { @right.newright("name")}.to raise_error(ArgumentError, /Unknown right type/) end it "should create a new ACL with the correct path" do @right.newright("/name") expect(@right["/name"]).not_to be_nil end it "should create an ACL of type Puppet::Network::AuthStore" do @right.newright("/name") expect(@right["/name"]).to be_a_kind_of(Puppet::Network::AuthStore) end end describe "when creating new regex ACLs" do it "should not throw an error if the ACL already exists" do @right.newright("~ .rb$") expect { @right.newright("~ .rb$")}.not_to raise_error end it "should create a new ACL with the correct regex" do @right.newright("~ .rb$") expect(@right.include?(".rb$")).not_to be_nil end it "should be able to lookup the regex" do @right.newright("~ .rb$") expect(@right[".rb$"]).not_to be_nil end it "should be able to lookup the regex by its full name" do @right.newright("~ .rb$") expect(@right["~ .rb$"]).not_to be_nil end it "should create an ACL of type Puppet::Network::AuthStore" do expect(@right.newright("~ .rb$")).to be_a_kind_of(Puppet::Network::AuthStore) end end describe "when checking ACLs existence" do it "should return false if there are no matching rights" do expect(@right.include?("name")).to be_falsey end it "should return true if a path right exists" do @right.newright("/name") expect(@right.include?("/name")).to be_truthy end it "should return false if no matching path rights exist" do @right.newright("/name") expect(@right.include?("/differentname")).to be_falsey end it "should return true if a regex right exists" do @right.newright("~ .rb$") expect(@right.include?(".rb$")).to be_truthy end it "should return false if no matching path rights exist" do @right.newright("~ .rb$") expect(@right.include?(".pp$")).to be_falsey end end describe "when checking if right is allowed" do before :each do @right.stubs(:right).returns(nil) @pathacl = stub 'pathacl', :"<=>" => 1, :line => 0, :file => 'dummy' Puppet::Network::Rights::Right.stubs(:new).returns(@pathacl) end it "should delegate to is_forbidden_and_why?" do @right.expects(:is_forbidden_and_why?).with("namespace", :node => "host.domain.com", :ip => "127.0.0.1").returns(nil) @right.allowed?("namespace", "host.domain.com", "127.0.0.1") end it "should return true if is_forbidden_and_why? returns nil" do @right.stubs(:is_forbidden_and_why?).returns(nil) expect(@right.allowed?("namespace", :args)).to be_truthy end it "should return false if is_forbidden_and_why? returns an AuthorizationError" do @right.stubs(:is_forbidden_and_why?).returns(Puppet::Network::AuthorizationError.new("forbidden")) expect(@right.allowed?("namespace", :args1, :args2)).to be_falsey end it "should pass the match? return to allowed?" do @right.newright("/path/to/there") @pathacl.expects(:match?).returns(:match) @pathacl.expects(:allowed?).with { |node,ip,h| h[:match] == :match }.returns(true) expect(@right.is_forbidden_and_why?("/path/to/there", {})).to eq(nil) end describe "with path acls" do before :each do @long_acl = stub 'longpathacl', :name => "/path/to/there", :line => 0, :file => 'dummy' Puppet::Network::Rights::Right.stubs(:new).with("/path/to/there", 0, nil).returns(@long_acl) @short_acl = stub 'shortpathacl', :name => "/path/to", :line => 0, :file => 'dummy' Puppet::Network::Rights::Right.stubs(:new).with("/path/to", 0, nil).returns(@short_acl) @long_acl.stubs(:"<=>").with(@short_acl).returns(0) @short_acl.stubs(:"<=>").with(@long_acl).returns(0) end it "should select the first match" do @right.newright("/path/to", 0) @right.newright("/path/to/there", 0) @long_acl.stubs(:match?).returns(true) @short_acl.stubs(:match?).returns(true) @short_acl.expects(:allowed?).returns(true) @long_acl.expects(:allowed?).never expect(@right.is_forbidden_and_why?("/path/to/there/and/there", {})).to eq(nil) end it "should select the first match that doesn't return :dunno" do @right.newright("/path/to/there", 0, nil) @right.newright("/path/to", 0, nil) @long_acl.stubs(:match?).returns(true) @short_acl.stubs(:match?).returns(true) @long_acl.expects(:allowed?).returns(:dunno) @short_acl.expects(:allowed?).returns(true) expect(@right.is_forbidden_and_why?("/path/to/there/and/there", {})).to eq(nil) end it "should not select an ACL that doesn't match" do @right.newright("/path/to/there", 0) @right.newright("/path/to", 0) @long_acl.stubs(:match?).returns(false) @short_acl.stubs(:match?).returns(true) @long_acl.expects(:allowed?).never @short_acl.expects(:allowed?).returns(true) expect(@right.is_forbidden_and_why?("/path/to/there/and/there", {})).to eq(nil) end it "should not raise an AuthorizationError if allowed" do @right.newright("/path/to/there", 0) @long_acl.stubs(:match?).returns(true) @long_acl.stubs(:allowed?).returns(true) expect(@right.is_forbidden_and_why?("/path/to/there/and/there", {})).to eq(nil) end it "should raise an AuthorizationError if the match is denied" do @right.newright("/path/to/there", 0, nil) @long_acl.stubs(:match?).returns(true) @long_acl.stubs(:allowed?).returns(false) expect(@right.is_forbidden_and_why?("/path/to/there", {})).to be_instance_of(Puppet::Network::AuthorizationError) end it "should raise an AuthorizationError if no path match" do expect(@right.is_forbidden_and_why?("/nomatch", {})).to be_instance_of(Puppet::Network::AuthorizationError) end end describe "with regex acls" do before :each do @regex_acl1 = stub 'regex_acl1', :name => "/files/(.*)/myfile", :line => 0, :file => 'dummy' Puppet::Network::Rights::Right.stubs(:new).with("~ /files/(.*)/myfile", 0, nil).returns(@regex_acl1) @regex_acl2 = stub 'regex_acl2', :name => "/files/(.*)/myfile/", :line => 0, :file => 'dummy' Puppet::Network::Rights::Right.stubs(:new).with("~ /files/(.*)/myfile/", 0, nil).returns(@regex_acl2) @regex_acl1.stubs(:"<=>").with(@regex_acl2).returns(0) @regex_acl2.stubs(:"<=>").with(@regex_acl1).returns(0) end it "should select the first match" do @right.newright("~ /files/(.*)/myfile", 0) @right.newright("~ /files/(.*)/myfile/", 0) @regex_acl1.stubs(:match?).returns(true) @regex_acl2.stubs(:match?).returns(true) @regex_acl1.expects(:allowed?).returns(true) @regex_acl2.expects(:allowed?).never expect(@right.is_forbidden_and_why?("/files/repository/myfile/other", {})).to eq(nil) end it "should select the first match that doesn't return :dunno" do @right.newright("~ /files/(.*)/myfile", 0) @right.newright("~ /files/(.*)/myfile/", 0) @regex_acl1.stubs(:match?).returns(true) @regex_acl2.stubs(:match?).returns(true) @regex_acl1.expects(:allowed?).returns(:dunno) @regex_acl2.expects(:allowed?).returns(true) expect(@right.is_forbidden_and_why?("/files/repository/myfile/other", {})).to eq(nil) end it "should not select an ACL that doesn't match" do @right.newright("~ /files/(.*)/myfile", 0) @right.newright("~ /files/(.*)/myfile/", 0) @regex_acl1.stubs(:match?).returns(false) @regex_acl2.stubs(:match?).returns(true) @regex_acl1.expects(:allowed?).never @regex_acl2.expects(:allowed?).returns(true) expect(@right.is_forbidden_and_why?("/files/repository/myfile/other", {})).to eq(nil) end it "should not raise an AuthorizationError if allowed" do @right.newright("~ /files/(.*)/myfile", 0) @regex_acl1.stubs(:match?).returns(true) @regex_acl1.stubs(:allowed?).returns(true) expect(@right.is_forbidden_and_why?("/files/repository/myfile/other", {})).to eq(nil) end it "should raise an error if no regex acl match" do expect(@right.is_forbidden_and_why?("/path", {})).to be_instance_of(Puppet::Network::AuthorizationError) end it "should raise an AuthorizedError on deny" do expect(@right.is_forbidden_and_why?("/path", {})).to be_instance_of(Puppet::Network::AuthorizationError) end end end describe Puppet::Network::Rights::Right do before :each do @acl = Puppet::Network::Rights::Right.new("/path",0, nil) end describe "with path" do it "should match up to its path length" do expect(@acl.match?("/path/that/works")).not_to be_nil end it "should match up to its path length" do expect(@acl.match?("/paththatalsoworks")).not_to be_nil end it "should return nil if no match" do expect(@acl.match?("/notpath")).to be_nil end end describe "with regex" do before :each do @acl = Puppet::Network::Rights::Right.new("~ .rb$",0, nil) end it "should match as a regex" do expect(@acl.match?("this should work.rb")).not_to be_nil end it "should return nil if no match" do expect(@acl.match?("do not match")).to be_nil end end it "should allow all rest methods by default" do expect(@acl.methods).to eq(Puppet::Network::Rights::Right::ALL) end it "should allow only authenticated request by default" do expect(@acl.authentication).to be_truthy end it "should allow modification of the methods filters" do @acl.restrict_method(:save) expect(@acl.methods).to eq([:save]) end it "should stack methods filters" do @acl.restrict_method(:save) @acl.restrict_method(:destroy) expect(@acl.methods).to eq([:save, :destroy]) end it "should raise an error if the method is already filtered" do @acl.restrict_method(:save) expect { @acl.restrict_method(:save) }.to raise_error(ArgumentError, /'save' is already in the '\/path'/) end it "should allow setting an environment filters" do env = Puppet::Node::Environment.create(:acltest, []) Puppet.override(:environments => Puppet::Environments::Static.new(env)) do @acl.restrict_environment(:acltest) expect(@acl.environment).to eq([env]) end end ["on", "yes", "true", true].each do |auth| it "should allow filtering on authenticated requests with '#{auth}'" do @acl.restrict_authenticated(auth) expect(@acl.authentication).to be_truthy end end ["off", "no", "false", false, "all", "any", :all, :any].each do |auth| it "should allow filtering on authenticated or unauthenticated requests with '#{auth}'" do @acl.restrict_authenticated(auth) expect(@acl.authentication).to be_falsey end end describe "when checking right authorization" do it "should return :dunno if this right is not restricted to the given method" do @acl.restrict_method(:destroy) expect(@acl.allowed?("me","127.0.0.1", { :method => :save } )).to eq(:dunno) end it "should return true if this right is restricted to the given method" do @acl.restrict_method(:save) @acl.allow("me") expect(@acl.allowed?("me","127.0.0.1", { :method => :save, :authenticated => true })).to eq true end it "should return :dunno if this right is not restricted to the given environment" do prod = Puppet::Node::Environment.create(:production, []) dev = Puppet::Node::Environment.create(:development, []) Puppet.override(:environments => Puppet::Environments::Static.new(prod, dev)) do @acl.restrict_environment(:production) expect(@acl.allowed?("me","127.0.0.1", { :method => :save, :environment => dev })).to eq(:dunno) end end it "returns true if the request is permitted for this environment" do @acl.allow("me") prod = Puppet::Node::Environment.create(:production, []) Puppet.override(:environments => Puppet::Environments::Static.new(prod)) do @acl.restrict_environment(:production) expect(@acl.allowed?("me", "127.0.0.1", { :method => :save, :authenticated => true, :environment => prod })).to eq true end end it "should return :dunno if this right is not restricted to the given request authentication state" do @acl.restrict_authenticated(true) expect(@acl.allowed?("me","127.0.0.1", { :method => :save, :authenticated => false })).to eq(:dunno) end it "returns true if this right is restricted to the given request authentication state" do @acl.restrict_authenticated(false) @acl.allow("me") expect(@acl.allowed?("me","127.0.0.1", {:method => :save, :authenticated => false })).to eq true end it "should interpolate allow/deny patterns with the given match" do @acl.expects(:interpolate).with(:match) @acl.allowed?("me","127.0.0.1", { :method => :save, :match => :match, :authenticated => true }) end it "should reset interpolation after the match" do @acl.expects(:reset_interpolation) @acl.allowed?("me","127.0.0.1", { :method => :save, :match => :match, :authenticated => true }) end end end end puppet-5.5.10/spec/unit/network/formats_spec.rb0000755005276200011600000003521713417161722021442 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/network/formats' require 'puppet/network/format_support' class FormatsTest include Puppet::Network::FormatSupport attr_accessor :string def ==(other) string == other.string end def self.from_data_hash(data) new(data['string']) end def initialize(string) @string = string end def to_data_hash(*args) { 'string' => @string } end def to_binary string end def self.from_binary(data) self.new(data) end end describe "Puppet Network Format" do it "should include a msgpack format", :if => Puppet.features.msgpack? do expect(Puppet::Network::FormatHandler.format(:msgpack)).not_to be_nil end describe "msgpack", :if => Puppet.features.msgpack? do let(:msgpack) { Puppet::Network::FormatHandler.format(:msgpack) } it "should have its mime type set to application/x-msgpack" do expect(msgpack.mime).to eq("application/x-msgpack") end it "should have a nil charset" do expect(msgpack.charset).to be_nil end it "should have a weight of 20" do expect(msgpack.weight).to eq(20) end it "should fail when one element does not have a from_data_hash" do expect do msgpack.intern_multiple(Hash, MessagePack.pack(["foo"])) end.to raise_error(NoMethodError) end it "should be able to serialize a catalog" do cat = Puppet::Resource::Catalog.new('foo', Puppet::Node::Environment.create(:testing, [])) cat.add_resource(Puppet::Resource.new(:file, 'my_file')) catunpack = MessagePack.unpack(cat.to_msgpack) expect(catunpack).to include( "tags"=>[], "name"=>"foo", "version"=>nil, "environment"=>"testing", "edges"=>[], "classes"=>[] ) expect(catunpack["resources"][0]).to include( "type"=>"File", "title"=>"my_file", "exported"=>false ) expect(catunpack["resources"][0]["tags"]).to include( "file", "my_file" ) end end describe "yaml" do let(:yaml) { Puppet::Network::FormatHandler.format(:yaml) } it "should have its mime type set to text/yaml" do expect(yaml.mime).to eq("text/yaml") end # we shouldn't be using yaml on the network it "should have a nil charset" do expect(yaml.charset).to be_nil end it "should be supported on Strings" do expect(yaml).to be_supported(String) end it "should render by calling 'to_yaml' on the instance" do instance = mock 'instance' instance.expects(:to_yaml).returns "foo" expect(yaml.render(instance)).to eq("foo") end it "should render multiple instances by calling 'to_yaml' on the array" do instances = [mock('instance')] instances.expects(:to_yaml).returns "foo" expect(yaml.render_multiple(instances)).to eq("foo") end it "should deserialize YAML" do expect(yaml.intern(String, YAML.dump("foo"))).to eq("foo") end it "should deserialize symbols as strings" do expect { yaml.intern(String, YAML.dump(:foo))}.to raise_error(Puppet::Network::FormatHandler::FormatError) end it "should skip data_to_hash if data is already an instance of the specified class" do # The rest terminus for the report indirected type relies on this behavior data = YAML.dump([1, 2]) instance = yaml.intern(Array, data) expect(instance).to eq([1, 2]) end it "should load from yaml when deserializing an array" do text = YAML.dump(["foo"]) expect(yaml.intern_multiple(String, text)).to eq(["foo"]) end it "fails intelligibly instead of calling to_json with something other than a hash" do expect do yaml.intern(Puppet::Node, '') end.to raise_error(Puppet::Network::FormatHandler::FormatError, /did not contain a valid instance/) end it "fails intelligibly when intern_multiple is called and yaml doesn't decode to an array" do expect do yaml.intern_multiple(Puppet::Node, '') end.to raise_error(Puppet::Network::FormatHandler::FormatError, /did not contain a collection/) end it "fails intelligibly instead of calling to_json with something other than a hash when interning multiple" do expect do yaml.intern_multiple(Puppet::Node, YAML.dump(["hello"])) end.to raise_error(Puppet::Network::FormatHandler::FormatError, /did not contain a valid instance/) end end describe "plaintext" do let(:text) { Puppet::Network::FormatHandler.format(:s) } it "should have its mimetype set to text/plain" do expect(text.mime).to eq("text/plain") end it "should use 'utf-8' charset" do expect(text.charset).to eq(Encoding::UTF_8) end it "should use 'txt' as its extension" do expect(text.extension).to eq("txt") end end describe "dot" do let(:dot) { Puppet::Network::FormatHandler.format(:dot) } it "should have its mimetype set to text/dot" do expect(dot.mime).to eq("text/dot") end end describe Puppet::Network::FormatHandler.format(:binary) do let(:binary) { Puppet::Network::FormatHandler.format(:binary) } it "should exist" do expect(binary).not_to be_nil end it "should have its mimetype set to application/octet-stream" do expect(binary.mime).to eq("application/octet-stream") end it "should have a nil charset" do expect(binary.charset).to be_nil end it "should not be supported by default" do expect(binary).to_not be_supported(String) end it "should render an instance as binary" do instance = FormatsTest.new("foo") expect(binary.render(instance)).to eq("foo") end it "should intern an instance from a JSON hash" do instance = binary.intern(FormatsTest, "foo") expect(instance.string).to eq("foo") end it "should fail if its multiple_render method is used" do expect { binary.render_multiple("foo") }.to raise_error(NotImplementedError, /can not render multiple instances to application\/octet-stream/) end it "should fail if its multiple_intern method is used" do expect { binary.intern_multiple(String, "foo") }.to raise_error(NotImplementedError, /can not intern multiple instances from application\/octet-stream/) end it "should have a weight of 1" do expect(binary.weight).to eq(1) end end describe "pson" do let(:pson) { Puppet::Network::FormatHandler.format(:pson) } it "should include a pson format" do expect(pson).not_to be_nil end it "should have its mime type set to text/pson" do expect(pson.mime).to eq("text/pson") end it "should have a nil charset" do expect(pson.charset).to be_nil end it "should require the :render_method" do expect(pson.required_methods).to be_include(:render_method) end it "should require the :intern_method" do expect(pson.required_methods).to be_include(:intern_method) end it "should have a weight of 10" do expect(pson.weight).to eq(10) end it "should render an instance as pson" do instance = FormatsTest.new("foo") expect(pson.render(instance)).to eq({"string" => "foo"}.to_pson) end it "should render multiple instances as pson" do instances = [FormatsTest.new("foo")] expect(pson.render_multiple(instances)).to eq([{"string" => "foo"}].to_pson) end it "should intern an instance from a pson hash" do text = PSON.dump({"string" => "parsed_pson"}) instance = pson.intern(FormatsTest, text) expect(instance.string).to eq("parsed_pson") end it "should skip data_to_hash if data is already an instance of the specified class" do # The rest terminus for the report indirected type relies on this behavior data = PSON.dump([1, 2]) instance = pson.intern(Array, data) expect(instance).to eq([1, 2]) end it "should intern multiple instances from a pson array" do text = PSON.dump( [ { "string" => "BAR" }, { "string" => "BAZ" } ] ) expect(pson.intern_multiple(FormatsTest, text)).to eq([FormatsTest.new('BAR'), FormatsTest.new('BAZ')]) end it "should unwrap the data from legacy clients" do text = PSON.dump( { "type" => "FormatsTest", "data" => { "string" => "parsed_json" } } ) instance = pson.intern(FormatsTest, text) expect(instance.string).to eq("parsed_json") end it "fails intelligibly when given invalid data" do expect do pson.intern(Puppet::Node, '') end.to raise_error(PSON::ParserError, /source did not contain any PSON/) end end describe "json" do let(:json) { Puppet::Network::FormatHandler.format(:json) } it "should include a json format" do expect(json).not_to be_nil end it "should have its mime type set to application/json" do expect(json.mime).to eq("application/json") end it "should use 'utf-8' charset" do expect(json.charset).to eq(Encoding::UTF_8) end it "should require the :render_method" do expect(json.required_methods).to be_include(:render_method) end it "should require the :intern_method" do expect(json.required_methods).to be_include(:intern_method) end it "should have a weight of 15" do expect(json.weight).to eq(15) end it "should render an instance as JSON" do instance = FormatsTest.new("foo") expect(json.render(instance)).to eq({"string" => "foo"}.to_json) end it "should render multiple instances as a JSON array of hashes" do instances = [FormatsTest.new("foo")] expect(json.render_multiple(instances)).to eq([{"string" => "foo"}].to_json) end it "should intern an instance from a JSON hash" do text = Puppet::Util::Json.dump({"string" => "parsed_json"}) instance = json.intern(FormatsTest, text) expect(instance.string).to eq("parsed_json") end it "should skip data_to_hash if data is already an instance of the specified class" do # The rest terminus for the report indirected type relies on this behavior data = Puppet::Util::Json.dump([1, 2]) instance = json.intern(Array, data) expect(instance).to eq([1, 2]) end it "should intern multiple instances from a JSON array of hashes" do text = Puppet::Util::Json.dump( [ { "string" => "BAR" }, { "string" => "BAZ" } ] ) expect(json.intern_multiple(FormatsTest, text)).to eq([FormatsTest.new('BAR'), FormatsTest.new('BAZ')]) end it "should reject wrapped data from legacy clients as they've never supported JSON" do text = Puppet::Util::Json.dump( { "type" => "FormatsTest", "data" => { "string" => "parsed_json" } } ) instance = json.intern(FormatsTest, text) expect(instance.string).to be_nil end it "fails intelligibly when given invalid data" do expect do json.intern(Puppet::Node, '') end.to raise_error(Puppet::Util::Json::ParseError) end end describe ":console format" do let(:console) { Puppet::Network::FormatHandler.format(:console) } it "should include a console format" do expect(console).to be_an_instance_of Puppet::Network::Format end [:intern, :intern_multiple].each do |method| it "should not implement #{method}" do expect { console.send(method, String, 'blah') }.to raise_error NotImplementedError end end context "when rendering ruby types" do ["hello", 1, 1.0].each do |input| it "should just return a #{input.inspect}" do expect(console.render(input)).to eq(input) end end { true => "true", false => "false", nil => "null", }.each_pair do |input, output| it "renders #{input.class} as '#{output}'" do expect(console.render(input)).to eq(output) end end it "renders an Object as its quoted inspect value" do obj = Object.new expect(console.render(obj)).to eq("\"#{obj.inspect}\"") end end context "when rendering arrays" do { [] => "", [1, 2] => "1\n2\n", ["one"] => "one\n", [{1 => 1}] => "{1=>1}\n", [[1, 2], [3, 4]] => "[1, 2]\n[3, 4]\n" }.each_pair do |input, output| it "should render #{input.inspect} as one item per line" do expect(console.render(input)).to eq(output) end end end context "when rendering hashes" do { {} => "", {1 => 2} => "1 2\n", {"one" => "two"} => "one \"two\"\n", # odd that two is quoted but one isn't {[1,2] => 3, [2,3] => 5, [3,4] => 7} => "{\n \"[1, 2]\": 3,\n \"[2, 3]\": 5,\n \"[3, 4]\": 7\n}", {{1 => 2} => {3 => 4}} => "{\n \"{1=>2}\": {\n \"3\": 4\n }\n}" }.each_pair do |input, output| it "should render #{input.inspect}" do expect(console.render(input)).to eq(output) end end it "should render a {String,Numeric}-keyed Hash into a table" do json = Puppet::Network::FormatHandler.format(:json) object = Object.new hash = { "one" => 1, "two" => [], "three" => {}, "four" => object, 5 => 5, 6.0 => 6 } # Gotta love ASCII-betical sort order. Hope your objects are better # structured for display than my test one is. --daniel 2011-04-18 expect(console.render(hash)).to eq < "x86_64", "os" => { "release" => { "full" => "15.6.0" } }, "system_uptime" => { "seconds" => 505532 } } facts = Puppet::Node::Facts.new("foo", values) facts.timestamp = tm # For some reason, render omits the last newline, seems like a bug expect(console.render(facts)).to eq(< ["puppet1.domain.com", "puppet2.domain.com"], 1 => ["puppet3.domain.com"], 2 => ["puppet4.domain.com"] } @dns_mock_object.expects(:getresources).with( "_x-puppet._tcp.#{@test_srv_domain}", @rr_type ).returns(@test_records) Puppet::Network::Resolver.each_srv_record(@test_srv_domain) do |hostname, port| expected_priority = order.keys.min expect(order[expected_priority]).to include(hostname) expect(port).not_to be(@test_port) # Remove the host from our expected hosts order[expected_priority].delete hostname # Remove this priority level if we're done with it order.delete expected_priority if order[expected_priority] == [] end end it "should fall back to the :puppet service if no records are found for a more specific service" do # The order of the records that should be returned, # an array means unordered (for weight) order = { 0 => ["puppet1.domain.com", "puppet2.domain.com"], 1 => ["puppet3.domain.com"], 2 => ["puppet4.domain.com"] } @dns_mock_object.expects(:getresources).with( "_x-puppet-report._tcp.#{@test_srv_domain}", @rr_type ).returns([]) @dns_mock_object.expects(:getresources).with( "_x-puppet._tcp.#{@test_srv_domain}", @rr_type ).returns(@test_records) Puppet::Network::Resolver.each_srv_record(@test_srv_domain, :report) do |hostname, port| expected_priority = order.keys.min expect(order[expected_priority]).to include(hostname) expect(port).not_to be(@test_port) # Remove the host from our expected hosts order[expected_priority].delete hostname # Remove this priority level if we're done with it order.delete expected_priority if order[expected_priority] == [] end end it "should use SRV records from the specific service if they exist" do # The order of the records that should be returned, # an array means unordered (for weight) order = { 0 => ["puppet1.domain.com", "puppet2.domain.com"], 1 => ["puppet3.domain.com"], 2 => ["puppet4.domain.com"] } bad_records = [ # priority, weight, port, hostname Resolv::DNS::Resource::IN::SRV.new(0, 20, 8140, "puppet1.bad.domain.com"), Resolv::DNS::Resource::IN::SRV.new(0, 80, 8140, "puppet2.bad.domain.com"), Resolv::DNS::Resource::IN::SRV.new(1, 1, 8140, "puppet3.bad.domain.com"), Resolv::DNS::Resource::IN::SRV.new(4, 1, 8140, "puppet4.bad.domain.com") ] @dns_mock_object.expects(:getresources).with( "_x-puppet-report._tcp.#{@test_srv_domain}", @rr_type ).returns(@test_records) @dns_mock_object.stubs(:getresources).with( "_x-puppet._tcp.#{@test_srv_domain}", @rr_type ).returns(bad_records) Puppet::Network::Resolver.each_srv_record(@test_srv_domain, :report) do |hostname, port| expected_priority = order.keys.min expect(order[expected_priority]).to include(hostname) expect(port).not_to be(@test_port) # Remove the host from our expected hosts order[expected_priority].delete hostname # Remove this priority level if we're done with it order.delete expected_priority if order[expected_priority] == [] end end end describe "when finding weighted servers" do it "should return nil when no records were found" do expect(Puppet::Network::Resolver.find_weighted_server([])).to eq(nil) end it "should return the first record when one record is passed" do result = Puppet::Network::Resolver.find_weighted_server([@test_records.first]) expect(result).to eq(@test_records.first) end { "all have weights" => [1, 3, 2, 4], "some have weights" => [2, 0, 1, 0], "none have weights" => [0, 0, 0, 0], }.each do |name, weights| it "should return correct results when #{name}" do records = [] count = 0 weights.each do |w| count += 1 # priority, weight, port, server records << Resolv::DNS::Resource::IN::SRV.new(0, w, 1, count.to_s) end seen = Hash.new(0) total_weight = records.inject(0) do |sum, record| sum + Puppet::Network::Resolver.weight(record) end total_weight.times do |n| Kernel.expects(:rand).once.with(total_weight).returns(n) server = Puppet::Network::Resolver.find_weighted_server(records) seen[server] += 1 end expect(seen.length).to eq(records.length) records.each do |record| expect(seen[record]).to eq(Puppet::Network::Resolver.weight(record)) end end end end end puppet-5.5.10/spec/unit/network/server_spec.rb0000644005276200011600000000470013417161722021263 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/network/server' describe Puppet::Network::Server do let(:port) { 8140 } let(:address) { '0.0.0.0' } let(:server) { Puppet::Network::Server.new(address, port) } before do @mock_http_server = mock('http server') Puppet.settings.stubs(:use) Puppet::Network::HTTP::WEBrick.stubs(:new).returns(@mock_http_server) end describe "when initializing" do before do Puppet[:masterport] = '' end it "should not be listening after initialization" do expect(Puppet::Network::Server.new(address, port)).not_to be_listening end it "should use the :main setting section" do Puppet.settings.expects(:use).with { |*args| args.include?(:main) } Puppet::Network::Server.new(address, port) end it "should use the :application setting section" do Puppet.settings.expects(:use).with { |*args| args.include?(:application) } Puppet::Network::Server.new(address, port) end end describe "when not yet started" do before do @mock_http_server.stubs(:listen) end it "should indicate that it is not listening" do expect(server).not_to be_listening end it "should not allow server to be stopped" do expect { server.stop }.to raise_error(RuntimeError) end it "should allow server to be started" do expect { server.start }.to_not raise_error end end describe "when server is on" do before do @mock_http_server.stubs(:listen) @mock_http_server.stubs(:unlisten) server.start end it "should indicate that it is listening" do expect(server).to be_listening end it "should not allow server to be started again" do expect { server.start }.to raise_error(RuntimeError) end it "should allow server to be stopped" do expect { server.stop }.to_not raise_error end end describe "when server is being started" do it "should cause the HTTP server to listen" do server = Puppet::Network::Server.new(address, port) @mock_http_server.expects(:listen).with(address, port) server.start end end describe "when server is being stopped" do before do @mock_http_server.stubs(:listen) server.stubs(:http_server).returns(@mock_http_server) server.start end it "should cause the HTTP server to stop listening" do @mock_http_server.expects(:unlisten) server.stop end end end puppet-5.5.10/spec/unit/node/0000755005276200011600000000000013417162176015655 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/node/environment_spec.rb0000644005276200011600000004772313417161722021571 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'tmpdir' require 'puppet/node/environment' require 'puppet/util/execution' require 'puppet_spec/modules' require 'puppet/parser/parser_factory' describe Puppet::Node::Environment do let(:env) { Puppet::Node::Environment.create("testing", []) } include PuppetSpec::Files context 'the environment' do it "converts an environment to string when converting to YAML" do expect(env.to_yaml).to match(/--- testing/) end describe ".create" do it "creates equivalent environments whether specifying name as a symbol or a string" do expect(Puppet::Node::Environment.create(:one, [])).to eq(Puppet::Node::Environment.create("one", [])) end it "interns name" do expect(Puppet::Node::Environment.create("one", []).name).to equal(:one) end it "does not produce environment singletons" do expect(Puppet::Node::Environment.create("one", [])).to_not equal(Puppet::Node::Environment.create("one", [])) end end it "returns its name when converted to a string" do expect(env.to_s).to eq("testing") end it "has an inspect method for debugging" do e = Puppet::Node::Environment.create(:test, ['/modules/path', '/other/modules'], '/manifests/path') expect("a #{e} env").to eq("a test env") expect(e.inspect).to match(%r{}) end describe "equality" do it "works as a hash key" do base = Puppet::Node::Environment.create(:first, ["modules"], "manifests") same = Puppet::Node::Environment.create(:first, ["modules"], "manifests") different = Puppet::Node::Environment.create(:first, ["different"], "manifests") hash = {} hash[base] = "base env" hash[same] = "same env" hash[different] = "different env" expect(hash[base]).to eq("same env") expect(hash[different]).to eq("different env") expect(hash).to have(2).item end it "is equal when name, modules, and manifests are the same" do base = Puppet::Node::Environment.create(:base, ["modules"], "manifests") different_name = Puppet::Node::Environment.create(:different, base.full_modulepath, base.manifest) expect(base).to_not eq("not an environment") expect(base).to eq(base) expect(base.hash).to eq(base.hash) expect(base.override_with(:modulepath => ["different"])).to_not eq(base) expect(base.override_with(:modulepath => ["different"]).hash).to_not eq(base.hash) expect(base.override_with(:manifest => "different")).to_not eq(base) expect(base.override_with(:manifest => "different").hash).to_not eq(base.hash) expect(different_name).to_not eq(base) expect(different_name.hash).to_not eq(base.hash) end end describe "overriding an existing environment" do let(:original_path) { [tmpdir('original')] } let(:new_path) { [tmpdir('new')] } let(:environment) { Puppet::Node::Environment.create(:overridden, original_path, 'orig.pp', '/config/script') } it "overrides modulepath" do overridden = environment.override_with(:modulepath => new_path) expect(overridden).to_not be_equal(environment) expect(overridden.name).to eq(:overridden) expect(overridden.manifest).to eq(File.expand_path('orig.pp')) expect(overridden.modulepath).to eq(new_path) expect(overridden.config_version).to eq('/config/script') end it "overrides manifest" do overridden = environment.override_with(:manifest => 'new.pp') expect(overridden).to_not be_equal(environment) expect(overridden.name).to eq(:overridden) expect(overridden.manifest).to eq(File.expand_path('new.pp')) expect(overridden.modulepath).to eq(original_path) expect(overridden.config_version).to eq('/config/script') end it "overrides config_version" do overridden = environment.override_with(:config_version => '/new/script') expect(overridden).to_not be_equal(environment) expect(overridden.name).to eq(:overridden) expect(overridden.manifest).to eq(File.expand_path('orig.pp')) expect(overridden.modulepath).to eq(original_path) expect(overridden.config_version).to eq('/new/script') end end describe "when managing known resource types" do before do env.stubs(:perform_initial_import).returns(Puppet::Parser::AST::Hostclass.new('')) end it "creates a resource type collection if none exists" do expect(env.known_resource_types).to be_kind_of(Puppet::Resource::TypeCollection) end it "memoizes resource type collection" do expect(env.known_resource_types).to equal(env.known_resource_types) end it "performs the initial import when creating a new collection" do env.expects(:perform_initial_import).returns(Puppet::Parser::AST::Hostclass.new('')) env.known_resource_types end it "generates a new TypeCollection if the current one requires reparsing" do old_type_collection = env.known_resource_types old_type_collection.stubs(:parse_failed?).returns true env.check_for_reparse new_type_collection = env.known_resource_types expect(new_type_collection).to be_a Puppet::Resource::TypeCollection expect(new_type_collection).to_not equal(old_type_collection) end end it "validates the modulepath directories" do real_file = tmpdir('moduledir') path = ['/one', '/two', real_file] env = Puppet::Node::Environment.create(:test, path) expect(env.modulepath).to eq([real_file]) end it "prefixes the value of the 'PUPPETLIB' environment variable to the module path if present" do first_puppetlib = tmpdir('puppetlib1') second_puppetlib = tmpdir('puppetlib2') first_moduledir = tmpdir('moduledir1') second_moduledir = tmpdir('moduledir2') Puppet::Util.withenv("PUPPETLIB" => [first_puppetlib, second_puppetlib].join(File::PATH_SEPARATOR)) do env = Puppet::Node::Environment.create(:testing, [first_moduledir, second_moduledir]) expect(env.modulepath).to eq([first_puppetlib, second_puppetlib, first_moduledir, second_moduledir]) end end describe "validating manifest settings" do before(:each) do Puppet[:default_manifest] = "/default/manifests/site.pp" end it "has no validation errors when disable_per_environment_manifest is false" do expect(Puppet::Node::Environment.create(:directory, [], '/some/non/default/manifest.pp').validation_errors).to be_empty end context "when disable_per_environment_manifest is true" do let(:config) { mock('config') } let(:global_modulepath) { ["/global/modulepath"] } let(:envconf) { Puppet::Settings::EnvironmentConf.new("/some/direnv", config, global_modulepath) } before(:each) do Puppet[:disable_per_environment_manifest] = true end def assert_manifest_conflict(expectation, envconf_manifest_value) config.expects(:setting).with(:manifest).returns( mock('setting', :value => envconf_manifest_value) ) environment = Puppet::Node::Environment.create(:directory, [], '/default/manifests/site.pp') loader = Puppet::Environments::Static.new(environment) loader.stubs(:get_conf).returns(envconf) Puppet.override(:environments => loader) do if expectation expect(environment.validation_errors).to have_matching_element(/The 'disable_per_environment_manifest' setting is true.*and the.*environment.*conflicts/) else expect(environment.validation_errors).to be_empty end end end it "has conflicting_manifest_settings when environment.conf manifest was set" do assert_manifest_conflict(true, '/some/envconf/manifest/site.pp') end it "does not have conflicting_manifest_settings when environment.conf manifest is empty" do assert_manifest_conflict(false, '') end it "does not have conflicting_manifest_settings when environment.conf manifest is nil" do assert_manifest_conflict(false, nil) end it "does not have conflicting_manifest_settings when environment.conf manifest is an exact, uninterpolated match of default_manifest" do assert_manifest_conflict(false, '/default/manifests/site.pp') end end end describe "when modeling a specific environment" do let(:first_modulepath) { tmpdir('firstmodules') } let(:second_modulepath) { tmpdir('secondmodules') } let(:env) { Puppet::Node::Environment.create(:modules_test, [first_modulepath, second_modulepath]) } let(:module_options) { { :environment => env, :metadata => { :author => 'puppetlabs', }, } } describe "module data" do describe ".module" do it "returns an individual module that exists in its module path" do one = PuppetSpec::Modules.create('one', first_modulepath, module_options) expect(env.module('one')).to eq(one) end it "returns nil if asked for a module that does not exist in its path" do expect(env.module("doesnotexist")).to be_nil end end describe "#modules_by_path" do it "returns an empty list if there are no modules" do expect(env.modules_by_path).to eq({ first_modulepath => [], second_modulepath => [] }) end it "includes modules even if they exist in multiple dirs in the modulepath" do one = PuppetSpec::Modules.create('one', first_modulepath, module_options) two = PuppetSpec::Modules.create('two', second_modulepath, module_options) expect(env.modules_by_path).to eq({ first_modulepath => [one], second_modulepath => [two], }) end it "ignores modules with invalid names" do PuppetSpec::Modules.generate_files('foo', first_modulepath) PuppetSpec::Modules.generate_files('.foo', first_modulepath) PuppetSpec::Modules.generate_files('foo2', first_modulepath) PuppetSpec::Modules.generate_files('foo-bar', first_modulepath) PuppetSpec::Modules.generate_files('foo_bar', first_modulepath) PuppetSpec::Modules.generate_files('foo=bar', first_modulepath) PuppetSpec::Modules.generate_files('foo bar', first_modulepath) PuppetSpec::Modules.generate_files('foo.bar', first_modulepath) PuppetSpec::Modules.generate_files('-foo', first_modulepath) PuppetSpec::Modules.generate_files('foo-', first_modulepath) PuppetSpec::Modules.generate_files('foo--bar', first_modulepath) expect(env.modules_by_path[first_modulepath].collect{|mod| mod.name}.sort).to eq(%w{foo foo2 foo_bar}) end end describe "#module_requirements" do it "returns a list of what modules depend on other modules" do PuppetSpec::Modules.create( 'foo', first_modulepath, :metadata => { :author => 'puppetlabs', :dependencies => [{ 'name' => 'puppetlabs/bar', "version_requirement" => ">= 1.0.0" }] } ) PuppetSpec::Modules.create( 'bar', second_modulepath, :metadata => { :author => 'puppetlabs', :dependencies => [{ 'name' => 'puppetlabs/foo', "version_requirement" => "<= 2.0.0" }] } ) PuppetSpec::Modules.create( 'baz', first_modulepath, :metadata => { :author => 'puppetlabs', :dependencies => [{ 'name' => 'puppetlabs-bar', "version_requirement" => "3.0.0" }] } ) PuppetSpec::Modules.create( 'alpha', first_modulepath, :metadata => { :author => 'puppetlabs', :dependencies => [{ 'name' => 'puppetlabs/bar', "version_requirement" => "~3.0.0" }] } ) expect(env.module_requirements).to eq({ 'puppetlabs/alpha' => [], 'puppetlabs/foo' => [ { "name" => "puppetlabs/bar", "version" => "9.9.9", "version_requirement" => "<= 2.0.0" } ], 'puppetlabs/bar' => [ { "name" => "puppetlabs/alpha", "version" => "9.9.9", "version_requirement" => "~3.0.0" }, { "name" => "puppetlabs/baz", "version" => "9.9.9", "version_requirement" => "3.0.0" }, { "name" => "puppetlabs/foo", "version" => "9.9.9", "version_requirement" => ">= 1.0.0" } ], 'puppetlabs/baz' => [] }) end end describe ".module_by_forge_name" do it "finds modules by forge_name" do mod = PuppetSpec::Modules.create( 'baz', first_modulepath, module_options ) expect(env.module_by_forge_name('puppetlabs/baz')).to eq(mod) end it "does not find modules with same name by the wrong author" do PuppetSpec::Modules.create( 'baz', first_modulepath, :metadata => {:author => 'sneakylabs'}, :environment => env ) expect(env.module_by_forge_name('puppetlabs/baz')).to eq(nil) end it "returns nil when the module can't be found" do expect(env.module_by_forge_name('ima/nothere')).to be_nil end end describe ".modules" do it "returns an empty list if there are no modules" do expect(env.modules).to eq([]) end it "returns a module named for every directory in each module path" do %w{foo bar}.each do |mod_name| PuppetSpec::Modules.generate_files(mod_name, first_modulepath) end %w{bee baz}.each do |mod_name| PuppetSpec::Modules.generate_files(mod_name, second_modulepath) end expect(env.modules.collect{|mod| mod.name}.sort).to eq(%w{foo bar bee baz}.sort) end it "removes duplicates" do PuppetSpec::Modules.generate_files('foo', first_modulepath) PuppetSpec::Modules.generate_files('foo', second_modulepath) expect(env.modules.collect{|mod| mod.name}.sort).to eq(%w{foo}) end it "ignores modules with invalid names" do PuppetSpec::Modules.generate_files('foo', first_modulepath) PuppetSpec::Modules.generate_files('.foo', first_modulepath) PuppetSpec::Modules.generate_files('foo2', first_modulepath) PuppetSpec::Modules.generate_files('foo-bar', first_modulepath) PuppetSpec::Modules.generate_files('foo_bar', first_modulepath) PuppetSpec::Modules.generate_files('foo=bar', first_modulepath) PuppetSpec::Modules.generate_files('foo bar', first_modulepath) expect(env.modules.collect{|mod| mod.name}.sort).to eq(%w{foo foo2 foo_bar}) end it "creates modules with the correct environment" do PuppetSpec::Modules.generate_files('foo', first_modulepath) env.modules.each do |mod| expect(mod.environment).to eq(env) end end it "logs an exception if a module contains invalid metadata" do PuppetSpec::Modules.generate_files( 'foo', first_modulepath, :metadata => { :author => 'puppetlabs' # missing source, version, etc } ) Puppet.expects(:log_exception).with(is_a(Puppet::Module::MissingMetadata)) env.modules end end end end describe "when performing initial import" do let(:loaders) { Puppet::Pops::Loaders.new(env) } around :each do |example| Puppet::Parser::Compiler.any_instance.stubs(:loaders).returns(loaders) Puppet.override(:loaders => loaders, :current_environment => env) do example.run Puppet::Pops::Loaders.clear end end it "loads from Puppet[:code]" do Puppet[:code] = "define foo {}" krt = env.known_resource_types expect(krt.find_definition('foo')).to be_kind_of(Puppet::Resource::Type) end it "parses from the the environment's manifests if Puppet[:code] is not set" do filename = tmpfile('a_manifest.pp') File.open(filename, 'w') do |f| f.puts("define from_manifest {}") end env = Puppet::Node::Environment.create(:testing, [], filename) krt = env.known_resource_types expect(krt.find_definition('from_manifest')).to be_kind_of(Puppet::Resource::Type) end it "prefers Puppet[:code] over manifest files" do Puppet[:code] = "define from_code_setting {}" filename = tmpfile('a_manifest.pp') File.open(filename, 'w') do |f| f.puts("define from_manifest {}") end env = Puppet::Node::Environment.create(:testing, [], filename) krt = env.known_resource_types expect(krt.find_definition('from_code_setting')).to be_kind_of(Puppet::Resource::Type) end it "initial import proceeds even if manifest file does not exist on disk" do filename = tmpfile('a_manifest.pp') env = Puppet::Node::Environment.create(:testing, [], filename) expect(env.known_resource_types).to be_kind_of(Puppet::Resource::TypeCollection) end it "returns an empty TypeCollection if neither code nor manifests is present" do expect(env.known_resource_types).to be_kind_of(Puppet::Resource::TypeCollection) end it "fails helpfully if there is an error importing" do Puppet[:code] = "oops {" expect do env.known_resource_types end.to raise_error(Puppet::Error, /Could not parse for environment #{env.name}/) end it "should mark the type collection as needing a reparse when there is an error parsing" do Puppet[:code] = "oops {" expect do env.known_resource_types end.to raise_error(Puppet::Error, /Syntax error at .../) expect(env.known_resource_types.parse_failed?).to be_truthy end end end describe "managing module translations" do it "creates a new text domain the first time we try to use the text domain" do Puppet::GettextConfig.expects(:reset_text_domain).with(env.name) Puppet::ModuleTranslations.expects(:load_from_modulepath) Puppet::GettextConfig.expects(:clear_text_domain) env.with_text_domain do; end end it "uses the existing text domain once it has been created" do env.with_text_domain do; end Puppet::GettextConfig.expects(:use_text_domain).with(env.name) env.with_text_domain do; end end it "yields block results" do ran = false expect(env.with_text_domain { ran = true; :result }).to eq(:result) expect(ran).to eq(true) end it "yields block results when i18n is disabled" do Puppet[:disable_i18n] = true ran = false expect(env.with_text_domain { ran = true; :result }).to eq(:result) expect(ran).to eq(true) end end end puppet-5.5.10/spec/unit/node/facts_spec.rb0000644005276200011600000001614413417161722020316 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/node/facts' require 'matchers/json' describe Puppet::Node::Facts, "when indirecting" do include JSONMatchers before do @facts = Puppet::Node::Facts.new("me") end describe "adding local facts" do it "should add the node's certificate name as the 'clientcert' fact" do @facts.add_local_facts expect(@facts.values["clientcert"]).to eq(Puppet.settings[:certname]) end it "adds the Puppet version as a 'clientversion' fact" do @facts.add_local_facts expect(@facts.values["clientversion"]).to eq(Puppet.version.to_s) end it "adds the agent side noop setting as 'clientnoop'" do @facts.add_local_facts expect(@facts.values["clientnoop"]).to eq(Puppet.settings[:noop]) end it "doesn't add the current environment" do @facts.add_local_facts expect(@facts.values).not_to include("environment") end it "doesn't replace any existing environment fact when adding local facts" do @facts.values["environment"] = "foo" @facts.add_local_facts expect(@facts.values["environment"]).to eq("foo") end end describe "when sanitizing facts" do it "should convert fact values if needed" do @facts.values["test"] = /foo/ @facts.sanitize expect(@facts.values["test"]).to eq("(?-mix:foo)") end it "should convert hash keys if needed" do @facts.values["test"] = {/foo/ => "bar"} @facts.sanitize expect(@facts.values["test"]).to eq({"(?-mix:foo)" => "bar"}) end it "should convert hash values if needed" do @facts.values["test"] = {"foo" => /bar/} @facts.sanitize expect(@facts.values["test"]).to eq({"foo" => "(?-mix:bar)"}) end it "should convert array elements if needed" do @facts.values["test"] = [1, "foo", /bar/] @facts.sanitize expect(@facts.values["test"]).to eq([1, "foo", "(?-mix:bar)"]) end it "should handle nested arrays" do @facts.values["test"] = [1, "foo", [/bar/]] @facts.sanitize expect(@facts.values["test"]).to eq([1, "foo", ["(?-mix:bar)"]]) end it "should handle nested hashes" do @facts.values["test"] = {/foo/ => {"bar" => /baz/}} @facts.sanitize expect(@facts.values["test"]).to eq({"(?-mix:foo)" => {"bar" => "(?-mix:baz)"}}) end it "should handle nester arrays and hashes" do @facts.values["test"] = {/foo/ => ["bar", /baz/]} @facts.sanitize expect(@facts.values["test"]).to eq({"(?-mix:foo)" => ["bar", "(?-mix:baz)"]}) end end describe "when indirecting" do before do @indirection = stub 'indirection', :request => mock('request'), :name => :facts @facts = Puppet::Node::Facts.new("me", "one" => "two") end it "should redirect to the specified fact store for storage" do Puppet::Node::Facts.stubs(:indirection).returns(@indirection) @indirection.expects(:save) Puppet::Node::Facts.indirection.save(@facts) end describe "when the Puppet application is 'master'" do it "should default to the 'yaml' terminus" do pending "Cannot test the behavior of defaults in defaults.rb" expect(Puppet::Node::Facts.indirection.terminus_class).to eq(:yaml) end end describe "when the Puppet application is not 'master'" do it "should default to the 'facter' terminus" do pending "Cannot test the behavior of defaults in defaults.rb" expect(Puppet::Node::Facts.indirection.terminus_class).to eq(:facter) end end end describe "when storing and retrieving" do it "doesn't manufacture a `_timestamp` fact value" do values = {"one" => "two", "three" => "four"} facts = Puppet::Node::Facts.new("mynode", values) expect(facts.values).to eq(values) end describe "when deserializing from yaml" do let(:timestamp) { Time.parse("Thu Oct 28 11:16:31 -0700 2010") } let(:expiration) { Time.parse("Thu Oct 28 11:21:31 -0700 2010") } def create_facts(values = {}) Puppet::Node::Facts.new('mynode', values) end def deserialize_yaml_facts(facts) format = Puppet::Network::FormatHandler.format('yaml') format.intern(Puppet::Node::Facts, facts.to_yaml) end it 'preserves `_timestamp` value' do facts = deserialize_yaml_facts(create_facts('_timestamp' => timestamp)) expect(facts.timestamp).to eq(timestamp) end it "doesn't preserve the `_timestamp` fact" do facts = deserialize_yaml_facts(create_facts('_timestamp' => timestamp)) expect(facts.values['_timestamp']).to be_nil end it 'preserves expiration time if present' do old_facts = create_facts old_facts.expiration = expiration facts = deserialize_yaml_facts(old_facts) expect(facts.expiration).to eq(expiration) end it 'ignores expiration time if absent' do facts = deserialize_yaml_facts(create_facts) expect(facts.expiration).to be_nil end end describe "using json" do before :each do @timestamp = Time.parse("Thu Oct 28 11:16:31 -0700 2010") @expiration = Time.parse("Thu Oct 28 11:21:31 -0700 2010") end it "should accept properly formatted json" do json = %Q({"name": "foo", "expiration": "#{@expiration}", "timestamp": "#{@timestamp}", "values": {"a": "1", "b": "2", "c": "3"}}) format = Puppet::Network::FormatHandler.format('json') facts = format.intern(Puppet::Node::Facts, json) expect(facts.name).to eq('foo') expect(facts.expiration).to eq(@expiration) expect(facts.timestamp).to eq(@timestamp) expect(facts.values).to eq({'a' => '1', 'b' => '2', 'c' => '3'}) end it "should generate properly formatted json" do Time.stubs(:now).returns(@timestamp) facts = Puppet::Node::Facts.new("foo", {'a' => 1, 'b' => 2, 'c' => 3}) facts.expiration = @expiration result = JSON.parse(facts.to_json) expect(result['name']).to eq(facts.name) expect(result['values']).to eq(facts.values) expect(result['timestamp']).to eq(facts.timestamp.iso8601(9)) expect(result['expiration']).to eq(facts.expiration.iso8601(9)) end it "should generate valid facts data against the facts schema" do Time.stubs(:now).returns(@timestamp) facts = Puppet::Node::Facts.new("foo", {'a' => 1, 'b' => 2, 'c' => 3}) facts.expiration = @expiration expect(facts.to_json).to validate_against('api/schemas/facts.json') end it "should not include nil values" do facts = Puppet::Node::Facts.new("foo", {'a' => 1, 'b' => 2, 'c' => 3}) json= JSON.parse(facts.to_json) expect(json).not_to be_include("expiration") end it "should be able to handle nil values" do json = %Q({"name": "foo", "values": {"a": "1", "b": "2", "c": "3"}}) format = Puppet::Network::FormatHandler.format('json') facts = format.intern(Puppet::Node::Facts, json) expect(facts.name).to eq('foo') expect(facts.expiration).to be_nil end end end end puppet-5.5.10/spec/unit/other/0000755005276200011600000000000013417162176016051 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/other/selinux_spec.rb0000644005276200011600000000527713417161722021106 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/type/selboolean' require 'puppet/type/selmodule' describe Puppet::Type.type(:file), " when manipulating file contexts" do include PuppetSpec::Files before :each do @file = Puppet::Type::File.new( :name => make_absolute("/tmp/foo"), :ensure => "file", :seluser => "user_u", :selrole => "role_r", :seltype => "type_t") end it "should use :seluser to get/set an SELinux user file context attribute" do expect(@file[:seluser]).to eq("user_u") end it "should use :selrole to get/set an SELinux role file context attribute" do expect(@file[:selrole]).to eq("role_r") end it "should use :seltype to get/set an SELinux user file context attribute" do expect(@file[:seltype]).to eq("type_t") end end describe Puppet::Type.type(:selboolean), " when manipulating booleans" do before :each do provider_class = Puppet::Type::Selboolean.provider(Puppet::Type::Selboolean.providers[0]) Puppet::Type::Selboolean.stubs(:defaultprovider).returns provider_class @bool = Puppet::Type::Selboolean.new( :name => "foo", :value => "on", :persistent => true ) end it "should be able to access :name" do expect(@bool[:name]).to eq("foo") end it "should be able to access :value" do expect(@bool.property(:value).should).to eq(:on) end it "should set :value to off" do @bool[:value] = :off expect(@bool.property(:value).should).to eq(:off) end it "should be able to access :persistent" do expect(@bool[:persistent]).to eq(:true) end it "should set :persistent to false" do @bool[:persistent] = false expect(@bool[:persistent]).to eq(:false) end end describe Puppet::Type.type(:selmodule), " when checking policy modules" do before :each do provider_class = Puppet::Type::Selmodule.provider(Puppet::Type::Selmodule.providers[0]) Puppet::Type::Selmodule.stubs(:defaultprovider).returns provider_class @module = Puppet::Type::Selmodule.new( :name => "foo", :selmoduledir => "/some/path", :selmodulepath => "/some/path/foo.pp", :syncversion => true) end it "should be able to access :name" do expect(@module[:name]).to eq("foo") end it "should be able to access :selmoduledir" do expect(@module[:selmoduledir]).to eq("/some/path") end it "should be able to access :selmodulepath" do expect(@module[:selmodulepath]).to eq("/some/path/foo.pp") end it "should be able to access :syncversion" do expect(@module[:syncversion]).to eq(:true) end it "should set the syncversion value to false" do @module[:syncversion] = :false expect(@module[:syncversion]).to eq(:false) end end puppet-5.5.10/spec/unit/parameter/0000755005276200011600000000000013417162176016710 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/parameter/boolean_spec.rb0000644005276200011600000000211213417161721021655 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet' require 'puppet/parameter/boolean' describe Puppet::Parameter::Boolean do let (:resource) { mock('resource') } describe "after initvars" do before { described_class.initvars } it "should have the correct value_collection" do expect(described_class.value_collection.values.sort).to eq( [:true, :false, :yes, :no].sort ) end end describe "instances" do subject { described_class.new(:resource => resource) } [ true, :true, 'true', :yes, 'yes', 'TrUe', 'yEs' ].each do |arg| it "should munge #{arg.inspect} as true" do expect(subject.munge(arg)).to eq(true) end end [ false, :false, 'false', :no, 'no', 'FaLSE', 'nO' ].each do |arg| it "should munge #{arg.inspect} as false" do expect(subject.munge(arg)).to eq(false) end end [ nil, :undef, 'undef', '0', 0, '1', 1, 9284 ].each do |arg| it "should fail to munge #{arg.inspect}" do expect { subject.munge(arg) }.to raise_error Puppet::Error end end end end puppet-5.5.10/spec/unit/parameter/package_options_spec.rb0000644005276200011600000000257113417161721023415 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/parameter/package_options' describe Puppet::Parameter::PackageOptions do let (:resource) { mock('resource') } let (:param) { described_class.new(:resource => resource) } let (:arg) { '/S' } let (:key) { 'INSTALLDIR' } let (:value) { 'C:/mydir' } context '#munge' do # The parser automatically converts single element arrays to just # a single element, why it does this is beyond me. See 46252b5bb8 it 'should accept a string' do expect(param.munge(arg)).to eq([arg]) end it 'should accept a hash' do expect(param.munge({key => value})).to eq([{key => value}]) end it 'should accept an array of strings and hashes' do munged = param.munge([arg, {key => value}, '/NCRC', {'CONF' => 'C:\datadir'}]) expect(munged).to eq([arg, {key => value}, '/NCRC', {'CONF' => 'C:\datadir'}]) end it 'should quote strings' do expect(param.munge('arg one')).to eq(["\"arg one\""]) end it 'should quote hash pairs' do munged = param.munge({'INSTALL DIR' => 'C:\Program Files'}) expect(munged).to eq([{"\"INSTALL DIR\"" => "\"C:\\Program Files\""}]) end it 'should reject symbols' do expect { param.munge([:symbol]) }.to raise_error(Puppet::Error, /Expected either a string or hash of options/) end end end puppet-5.5.10/spec/unit/parameter/path_spec.rb0000644005276200011600000000140413417161721021175 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/parameter/path' [false, true].each do |arrays| describe "Puppet::Parameter::Path with arrays #{arrays}" do it_should_behave_like "all path parameters", :path, :array => arrays do # The new type allows us a test that is guaranteed to go direct to our # validation code, without passing through any "real type" overrides or # whatever on the way. Puppet::Type.newtype(:test_puppet_parameter_path) do newparam(:path, :parent => Puppet::Parameter::Path, :arrays => arrays) do isnamevar accept_arrays arrays end end def instance(path) Puppet::Type.type(:test_puppet_parameter_path).new(:path => path) end end end end puppet-5.5.10/spec/unit/parameter/value_collection_spec.rb0000644005276200011600000001231013417161721023566 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/parameter' describe Puppet::Parameter::ValueCollection do before do @collection = Puppet::Parameter::ValueCollection.new end it "should have a method for defining new values" do expect(@collection).to respond_to(:newvalues) end it "should have a method for adding individual values" do expect(@collection).to respond_to(:newvalue) end it "should be able to retrieve individual values" do value = @collection.newvalue(:foo) expect(@collection.value(:foo)).to equal(value) end it "should be able to add an individual value with a block" do @collection.newvalue(:foo) { raise "testing" } expect(@collection.value(:foo).block).to be_instance_of(Proc) end it "should be able to add values that are empty strings" do expect { @collection.newvalue('') }.to_not raise_error end it "should be able to add values that are empty strings" do value = @collection.newvalue('') expect(@collection.match?('')).to equal(value) end describe "when adding a value with a block" do it "should set the method name to 'set_' plus the value name" do value = @collection.newvalue(:myval) { raise "testing" } expect(value.method).to eq("set_myval") end end it "should be able to add an individual value with options" do value = @collection.newvalue(:foo, :method => 'set_myval') expect(value.method).to eq('set_myval') end it "should have a method for validating a value" do expect(@collection).to respond_to(:validate) end it "should have a method for munging a value" do expect(@collection).to respond_to(:munge) end it "should be able to generate documentation when it has both values and regexes" do @collection.newvalues :foo, "bar", %r{test} expect(@collection.doc).to be_instance_of(String) end it "should correctly generate documentation for values" do @collection.newvalues :foo expect(@collection.doc).to be_include("Valid values are `foo`") end it "should correctly generate documentation for regexes" do @collection.newvalues %r{\w+} expect(@collection.doc).to be_include("Values can match `/\\w+/`") end it "should be able to find the first matching value" do @collection.newvalues :foo, :bar expect(@collection.match?("foo")).to be_instance_of(Puppet::Parameter::Value) end it "should be able to match symbols" do @collection.newvalues :foo, :bar expect(@collection.match?(:foo)).to be_instance_of(Puppet::Parameter::Value) end it "should be able to match symbols when a regex is provided" do @collection.newvalues %r{.} expect(@collection.match?(:foo)).to be_instance_of(Puppet::Parameter::Value) end it "should be able to match values using regexes" do @collection.newvalues %r{.} expect(@collection.match?("foo")).not_to be_nil end it "should prefer value matches to regex matches" do @collection.newvalues %r{.}, :foo expect(@collection.match?("foo").name).to eq(:foo) end describe "when validating values" do it "should do nothing if no values or regexes have been defined" do @collection.validate("foo") end it "should fail if the value is not a defined value or alias and does not match a regex" do @collection.newvalues :foo expect { @collection.validate("bar") }.to raise_error(ArgumentError) end it "should succeed if the value is one of the defined values" do @collection.newvalues :foo expect { @collection.validate(:foo) }.to_not raise_error end it "should succeed if the value is one of the defined values even if the definition uses a symbol and the validation uses a string" do @collection.newvalues :foo expect { @collection.validate("foo") }.to_not raise_error end it "should succeed if the value is one of the defined values even if the definition uses a string and the validation uses a symbol" do @collection.newvalues "foo" expect { @collection.validate(:foo) }.to_not raise_error end it "should succeed if the value is one of the defined aliases" do @collection.newvalues :foo @collection.aliasvalue :bar, :foo expect { @collection.validate("bar") }.to_not raise_error end it "should succeed if the value matches one of the regexes" do @collection.newvalues %r{\d} expect { @collection.validate("10") }.to_not raise_error end end describe "when munging values" do it "should do nothing if no values or regexes have been defined" do expect(@collection.munge("foo")).to eq("foo") end it "should return return any matching defined values" do @collection.newvalues :foo, :bar expect(@collection.munge("foo")).to eq(:foo) end it "should return any matching aliases" do @collection.newvalues :foo @collection.aliasvalue :bar, :foo expect(@collection.munge("bar")).to eq(:foo) end it "should return the value if it matches a regex" do @collection.newvalues %r{\w} expect(@collection.munge("bar")).to eq("bar") end it "should return the value if no other option is matched" do @collection.newvalues :foo expect(@collection.munge("bar")).to eq("bar") end end end puppet-5.5.10/spec/unit/parameter/value_spec.rb0000644005276200011600000000521413417161721021360 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/parameter' describe Puppet::Parameter::Value do it "should require a name" do expect { Puppet::Parameter::Value.new }.to raise_error(ArgumentError) end it "should set its name" do expect(Puppet::Parameter::Value.new(:foo).name).to eq(:foo) end it "should support regexes as names" do expect { Puppet::Parameter::Value.new(%r{foo}) }.not_to raise_error end it "should mark itself as a regex if its name is a regex" do expect(Puppet::Parameter::Value.new(%r{foo})).to be_regex end it "should always convert its name to a symbol if it is not a regex" do expect(Puppet::Parameter::Value.new("foo").name).to eq(:foo) expect(Puppet::Parameter::Value.new(true).name).to eq(:true) end it "should support adding aliases" do expect(Puppet::Parameter::Value.new("foo")).to respond_to(:alias) end it "should be able to return its aliases" do value = Puppet::Parameter::Value.new("foo") value.alias("bar") value.alias("baz") expect(value.aliases).to eq([:bar, :baz]) end [:block, :method, :event, :required_features].each do |attr| it "should support a #{attr} attribute" do value = Puppet::Parameter::Value.new("foo") expect(value).to respond_to(attr.to_s + "=") expect(value).to respond_to(attr) end end it "should always return events as symbols" do value = Puppet::Parameter::Value.new("foo") value.event = "foo_test" expect(value.event).to eq(:foo_test) end describe "when matching" do describe "a regex" do it "should return true if the regex matches the value" do expect(Puppet::Parameter::Value.new(/\w/)).to be_match("foo") end it "should return false if the regex does not match the value" do expect(Puppet::Parameter::Value.new(/\d/)).not_to be_match("foo") end end describe "a non-regex" do it "should return true if the value, converted to a symbol, matches the name" do expect(Puppet::Parameter::Value.new("foo")).to be_match("foo") expect(Puppet::Parameter::Value.new(:foo)).to be_match(:foo) expect(Puppet::Parameter::Value.new(:foo)).to be_match("foo") expect(Puppet::Parameter::Value.new("foo")).to be_match(:foo) end it "should return false if the value, converted to a symbol, does not match the name" do expect(Puppet::Parameter::Value.new(:foo)).not_to be_match(:bar) end it "should return true if any of its aliases match" do value = Puppet::Parameter::Value.new("foo") value.alias("bar") expect(value).to be_match("bar") end end end end puppet-5.5.10/spec/unit/parameter_spec.rb0000644005276200011600000002101313417161721020237 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/parameter' describe Puppet::Parameter do before do @class = Class.new(Puppet::Parameter) do @name = :foo end @class.initvars @resource = mock 'resource' @resource.stub_everything @parameter = @class.new :resource => @resource end it "should create a value collection" do @class = Class.new(Puppet::Parameter) expect(@class.value_collection).to be_nil @class.initvars expect(@class.value_collection).to be_instance_of(Puppet::Parameter::ValueCollection) end it "should return its name as a string when converted to a string" do expect(@parameter.to_s).to eq(@parameter.name.to_s) end [:line, :file, :version].each do |data| it "should return its resource's #{data} as its #{data}" do @resource.expects(data).returns "foo" expect(@parameter.send(data)).to eq("foo") end end it "should return the resource's tags plus its name as its tags" do @resource.expects(:tags).returns %w{one two} expect(@parameter.tags).to eq(%w{one two foo}) end it "should have a path" do expect(@parameter.path).to eq("//foo") end describe "when returning the value" do it "should return nil if no value is set" do expect(@parameter.value).to be_nil end it "should validate the value" do @parameter.expects(:validate).with("foo") @parameter.value = "foo" end it "should munge the value and use any result as the actual value" do @parameter.expects(:munge).with("foo").returns "bar" @parameter.value = "foo" expect(@parameter.value).to eq("bar") end it "should unmunge the value when accessing the actual value" do @parameter.class.unmunge do |value| value.to_sym end @parameter.value = "foo" expect(@parameter.value).to eq(:foo) end it "should return the actual value by default when unmunging" do expect(@parameter.unmunge("bar")).to eq("bar") end it "should return any set value" do @parameter.value = "foo" expect(@parameter.value).to eq("foo") end end describe "when validating values" do it "should do nothing if no values or regexes have been defined" do @parameter.validate("foo") end it "should catch abnormal failures thrown during validation" do @class.validate { |v| raise "This is broken" } expect { @parameter.validate("eh") }.to raise_error(Puppet::DevError) end it "should fail if the value is not a defined value or alias and does not match a regex" do @class.newvalues :foo expect { @parameter.validate("bar") }.to raise_error(Puppet::Error) end it "should succeed if the value is one of the defined values" do @class.newvalues :foo expect { @parameter.validate(:foo) }.to_not raise_error end it "should succeed if the value is one of the defined values even if the definition uses a symbol and the validation uses a string" do @class.newvalues :foo expect { @parameter.validate("foo") }.to_not raise_error end it "should succeed if the value is one of the defined values even if the definition uses a string and the validation uses a symbol" do @class.newvalues "foo" expect { @parameter.validate(:foo) }.to_not raise_error end it "should succeed if the value is one of the defined aliases" do @class.newvalues :foo @class.aliasvalue :bar, :foo expect { @parameter.validate("bar") }.to_not raise_error end it "should succeed if the value matches one of the regexes" do @class.newvalues %r{\d} expect { @parameter.validate("10") }.to_not raise_error end end describe "when munging values" do it "should do nothing if no values or regexes have been defined" do expect(@parameter.munge("foo")).to eq("foo") end it "should catch abnormal failures thrown during munging" do @class.munge { |v| raise "This is broken" } expect { @parameter.munge("eh") }.to raise_error(Puppet::DevError) end it "should return return any matching defined values" do @class.newvalues :foo, :bar expect(@parameter.munge("foo")).to eq(:foo) end it "should return any matching aliases" do @class.newvalues :foo @class.aliasvalue :bar, :foo expect(@parameter.munge("bar")).to eq(:foo) end it "should return the value if it matches a regex" do @class.newvalues %r{\w} expect(@parameter.munge("bar")).to eq("bar") end it "should return the value if no other option is matched" do @class.newvalues :foo expect(@parameter.munge("bar")).to eq("bar") end end describe "when logging" do it "should use its resource's log level and the provided message" do @resource.expects(:[]).with(:loglevel).returns :notice @parameter.expects(:send_log).with(:notice, "mymessage") @parameter.log "mymessage" end end describe ".format_value_for_display" do it 'should format strings appropriately' do expect(described_class.format_value_for_display('foo')).to eq("'foo'") end it 'should format numbers appropriately' do expect(described_class.format_value_for_display(1)).to eq('1') end it 'should format symbols appropriately' do expect(described_class.format_value_for_display(:bar)).to eq("'bar'") end it 'should format arrays appropriately' do expect(described_class.format_value_for_display([1, 'foo', :bar])).to eq("[1, 'foo', 'bar']") end it 'should format hashes appropriately' do expect(described_class.format_value_for_display( {1 => 'foo', :bar => 2, 'baz' => :qux} )).to eq(<<-RUBY.unindent.sub(/\n$/, '')) { 1 => 'foo', 'bar' => 2, 'baz' => 'qux' } RUBY end it 'should format arrays with nested data appropriately' do expect(described_class.format_value_for_display( [1, 'foo', :bar, [1, 2, 3], {1 => 2, 3 => 4}] )).to eq(<<-RUBY.unindent.sub(/\n$/, '')) [1, 'foo', 'bar', [1, 2, 3], { 1 => 2, 3 => 4 }] RUBY end it 'should format hashes with nested data appropriately' do expect(described_class.format_value_for_display( {1 => 'foo', :bar => [2, 3, 4], 'baz' => {:qux => 1, :quux => 'two'}} )).to eq(<<-RUBY.unindent.sub(/\n$/, '')) { 1 => 'foo', 'bar' => [2, 3, 4], 'baz' => { 'qux' => 1, 'quux' => 'two' } } RUBY end it 'should format hashes with nested Objects appropriately' do tf = Puppet::Pops::Types::TypeFactory type = tf.object({'name' => 'MyType', 'attributes' => { 'qux' => tf.integer, 'quux' => tf.string }}) expect(described_class.format_value_for_display( {1 => 'foo', 'bar' => type.create(1, 'one'), 'baz' => type.create(2, 'two')} )).to eq(<<-RUBY.unindent.sub(/\n$/, '')) { 1 => 'foo', 'bar' => MyType({ 'qux' => 1, 'quux' => 'one' }), 'baz' => MyType({ 'qux' => 2, 'quux' => 'two' }) } RUBY end it 'should format Objects with nested Objects appropriately' do tf = Puppet::Pops::Types::TypeFactory inner_type = tf.object({'name' => 'MyInnerType', 'attributes' => { 'qux' => tf.integer, 'quux' => tf.string }}) outer_type = tf.object({'name' => 'MyOuterType', 'attributes' => { 'x' => tf.string, 'inner' => inner_type }}) expect(described_class.format_value_for_display( {'bar' => outer_type.create('a', inner_type.create(1, 'one')), 'baz' => outer_type.create('b', inner_type.create(2, 'two'))} )).to eq(<<-RUBY.unindent.sub(/\n$/, '')) { 'bar' => MyOuterType({ 'x' => 'a', 'inner' => MyInnerType({ 'qux' => 1, 'quux' => 'one' }) }), 'baz' => MyOuterType({ 'x' => 'b', 'inner' => MyInnerType({ 'qux' => 2, 'quux' => 'two' }) }) } RUBY end end describe 'formatting messages' do it "formats messages as-is when the parameter is not sensitive" do expect(@parameter.format("hello %s", "world")).to eq("hello world") end it "formats messages with redacted values when the parameter is not sensitive" do @parameter.sensitive = true expect(@parameter.format("hello %s", "world")).to eq("hello [redacted]") end end end puppet-5.5.10/spec/unit/parser/0000755005276200011600000000000013417162176016224 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/parser/ast/0000755005276200011600000000000013417162176017013 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/parser/ast/block_expression_spec.rb0000644005276200011600000000332313417161721023717 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/parser/ast/block_expression' describe 'Puppet::Parser::AST::BlockExpression' do class StackDepthAST < Puppet::Parser::AST attr_reader :call_depth def evaluate(*options) @call_depth = caller.length end end NO_SCOPE = nil def depth_probe StackDepthAST.new({}) end def sequence_probe(name, sequence) probe = mock("Sequence Probe #{name}") probe.expects(:safeevaluate).in_sequence(sequence) probe end def block_of(children) Puppet::Parser::AST::BlockExpression.new(:children => children) end def assert_all_at_same_depth(*probes) depth0 = probes[0].call_depth probes.drop(1).each do |p| expect(p.call_depth).to eq(depth0) end end it "evaluates all its children at the same stack depth" do depth_probes = [depth_probe, depth_probe] expr = block_of(depth_probes) expr.evaluate(NO_SCOPE) assert_all_at_same_depth(*depth_probes) end it "evaluates sequenced children at the same stack depth" do depth1 = depth_probe depth2 = depth_probe depth3 = depth_probe expr1 = block_of([depth1]) expr2 = block_of([depth2]) expr3 = block_of([depth3]) expr1.sequence_with(expr2).sequence_with(expr3).evaluate(NO_SCOPE) assert_all_at_same_depth(depth1, depth2, depth3) end it "evaluates sequenced children in order" do evaluation_order = sequence("Child evaluation order") expr1 = block_of([sequence_probe("Step 1", evaluation_order)]) expr2 = block_of([sequence_probe("Step 2", evaluation_order)]) expr3 = block_of([sequence_probe("Step 3", evaluation_order)]) expr1.sequence_with(expr2).sequence_with(expr3).evaluate(NO_SCOPE) end end puppet-5.5.10/spec/unit/parser/ast/leaf_spec.rb0000644005276200011600000000750113417161721021257 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Parser::AST::Leaf do before :each do node = Puppet::Node.new('localhost') compiler = Puppet::Parser::Compiler.new(node) @scope = Puppet::Parser::Scope.new(compiler) @value = stub 'value' @leaf = Puppet::Parser::AST::Leaf.new(:value => @value) end describe "when converting to string" do it "should transform its value to string" do value = stub 'value', :is_a? => true value.expects(:to_s) Puppet::Parser::AST::Leaf.new( :value => value ).to_s end end it "should have a match method" do expect(@leaf).to respond_to(:match) end it "should delegate match to ==" do @value.expects(:==).with("value") @leaf.match("value") end end describe Puppet::Parser::AST::Regex do before :each do node = Puppet::Node.new('localhost') compiler = Puppet::Parser::Compiler.new(node) @scope = Puppet::Parser::Scope.new(compiler) end describe "when initializing" do it "should create a Regexp with its content when value is not a Regexp" do Regexp.expects(:new).with("/ab/") Puppet::Parser::AST::Regex.new :value => "/ab/" end it "should not create a Regexp with its content when value is a Regexp" do value = Regexp.new("/ab/") Regexp.expects(:new).with("/ab/").never Puppet::Parser::AST::Regex.new :value => value end end describe "when evaluating" do it "should return self" do val = Puppet::Parser::AST::Regex.new :value => "/ab/" expect(val.evaluate(@scope)).to be === val end end it 'should return the PRegexpType#regexp_to_s_with_delimiters with to_s' do regex = stub 'regex' Regexp.stubs(:new).returns(regex) val = Puppet::Parser::AST::Regex.new :value => '/ab/' Puppet::Pops::Types::PRegexpType.expects(:regexp_to_s_with_delimiters) val.to_s end it "should delegate match to the underlying regexp match method" do regex = Regexp.new("/ab/") val = Puppet::Parser::AST::Regex.new :value => regex regex.expects(:match).with("value") val.match("value") end end describe Puppet::Parser::AST::HostName do before :each do node = Puppet::Node.new('localhost') compiler = Puppet::Parser::Compiler.new(node) @scope = Puppet::Parser::Scope.new(compiler) @value = 'value' @value.stubs(:to_s).returns(@value) @value.stubs(:downcase).returns(@value) @host = Puppet::Parser::AST::HostName.new(:value => @value) end it "should raise an error if hostname is not valid" do expect { Puppet::Parser::AST::HostName.new( :value => "not a hostname!" ) }.to raise_error(Puppet::DevError, /'not a hostname!' is not a valid hostname/) end it "should not raise an error if hostname is a regex" do expect { Puppet::Parser::AST::HostName.new( :value => Puppet::Parser::AST::Regex.new(:value => "/test/") ) }.not_to raise_error end it "should stringify the value" do value = stub 'value', :=~ => false value.expects(:to_s).returns("test") Puppet::Parser::AST::HostName.new(:value => value) end it "should downcase the value" do value = stub 'value', :=~ => false value.stubs(:to_s).returns("UPCASED") host = Puppet::Parser::AST::HostName.new(:value => value) host.value == "upcased" end it "should evaluate to its value" do expect(@host.evaluate(@scope)).to eq(@value) end it "should delegate eql? to the underlying value if it is an HostName" do @value.expects(:eql?).with("value") @host.eql?("value") end it "should delegate eql? to the underlying value if it is not an HostName" do value = stub 'compared', :is_a? => true, :value => "value" @value.expects(:eql?).with("value") @host.eql?(value) end it "should delegate hash to the underlying value" do @value.expects(:hash) @host.hash end end puppet-5.5.10/spec/unit/parser/compiler_spec.rb0000644005276200011600000010402713417161721021374 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' class CompilerTestResource attr_accessor :builtin, :virtual, :evaluated, :type, :title def initialize(type, title) @type = type @title = title end def [](attr) return nil if (attr == :stage || attr == :alias) :main end def ref "#{type.to_s.capitalize}[#{title}]" end def evaluated? @evaluated end def builtin_type? @builtin end def virtual? @virtual end def class? false end def stage? false end def evaluate end def file "/fake/file/goes/here" end def line "42" end def resource_type self.class end end describe Puppet::Parser::Compiler do include PuppetSpec::Files include Matchers::Resource def resource(type, title) Puppet::Parser::Resource.new(type, title, :scope => @scope) end let(:environment) { Puppet::Node::Environment.create(:testing, []) } before :each do # Push me faster, I wanna go back in time! (Specifically, freeze time # across the test since we have a bunch of version == timestamp code # hidden away in the implementation and we keep losing the race.) # --daniel 2011-04-21 now = Time.now Time.stubs(:now).returns(now) @node = Puppet::Node.new("testnode", :facts => Puppet::Node::Facts.new("facts", {}), :environment => environment) @known_resource_types = environment.known_resource_types @compiler = Puppet::Parser::Compiler.new(@node) @scope = Puppet::Parser::Scope.new(@compiler, :source => stub('source')) @scope_resource = Puppet::Parser::Resource.new(:file, "/my/file", :scope => @scope) @scope.resource = @scope_resource end it "should fail intelligently when a class-level compile fails" do Puppet::Parser::Compiler.expects(:new).raises ArgumentError expect { Puppet::Parser::Compiler.compile(@node) }.to raise_error(Puppet::Error) end it "should use the node's environment as its environment" do expect(@compiler.environment).to equal(@node.environment) end it "fails if the node's environment has validation errors" do conflicted_environment = Puppet::Node::Environment.create(:testing, [], '/some/environment.conf/manifest.pp') conflicted_environment.stubs(:validation_errors).returns(['bad environment']) @node.environment = conflicted_environment expect { Puppet::Parser::Compiler.compile(@node) }.to raise_error(Puppet::Error, /Compilation has been halted because.*bad environment/) end it "should be able to return a class list containing all added classes" do @compiler.add_class "" @compiler.add_class "one" @compiler.add_class "two" expect(@compiler.classlist.sort).to eq(%w{one two}.sort) end describe "when initializing" do it 'should not create the settings class more than once' do logs = [] Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do Puppet[:code] = 'undef' @compiler.compile @compiler = Puppet::Parser::Compiler.new(@node) Puppet[:code] = 'undef' @compiler.compile end warnings = logs.select { |log| log.level == :warning }.map { |log| log.message } expect(warnings).not_to include(/Class 'settings' is already defined/) end it "should set its node attribute" do expect(@compiler.node).to equal(@node) end it "the set of ast_nodes should be empty" do expect(@compiler.environment.known_resource_types.nodes?).to be_falsey end it "should copy the known_resource_types version to the catalog" do expect(@compiler.catalog.version).to eq(@known_resource_types.version) end it "should copy any node classes into the class list" do node = Puppet::Node.new("mynode") node.classes = %w{foo bar} compiler = Puppet::Parser::Compiler.new(node) expect(compiler.classlist).to match_array(['foo', 'bar']) end it "should transform node class hashes into a class list" do node = Puppet::Node.new("mynode") node.classes = {'foo'=>{'one'=>'p1'}, 'bar'=>{'two'=>'p2'}} compiler = Puppet::Parser::Compiler.new(node) expect(compiler.classlist).to match_array(['foo', 'bar']) end it "should return a catalog with the specified code_id" do node = Puppet::Node.new("mynode") code_id = 'b59e5df0578ef411f773ee6c33d8073c50e7b8fe' compiler = Puppet::Parser::Compiler.new(node, :code_id => code_id) expect(compiler.catalog.code_id).to eq(code_id) end it "should add a 'main' stage to the catalog" do expect(@compiler.catalog.resource(:stage, :main)).to be_instance_of(Puppet::Parser::Resource) end end describe "sanitize_node" do it "should delete trusted from parameters" do node = Puppet::Node.new("mynode") node.parameters['trusted'] = { :a => 42 } node.parameters['preserve_me'] = 'other stuff' compiler = Puppet::Parser::Compiler.new(node) sanitized = compiler.node expect(sanitized.parameters['trusted']).to eq(nil) expect(sanitized.parameters['preserve_me']).to eq('other stuff') end it "should not report trusted_data if trusted is false" do node = Puppet::Node.new("mynode") node.parameters['trusted'] = false compiler = Puppet::Parser::Compiler.new(node) sanitized = compiler.node expect(sanitized.trusted_data).to_not eq(false) end it "should not report trusted_data if trusted is not a hash" do node = Puppet::Node.new("mynode") node.parameters['trusted'] = 'not a hash' compiler = Puppet::Parser::Compiler.new(node) sanitized = compiler.node expect(sanitized.trusted_data).to_not eq('not a hash') end it "should not report trusted_data if trusted hash doesn't include known keys" do node = Puppet::Node.new("mynode") node.parameters['trusted'] = { :a => 42 } compiler = Puppet::Parser::Compiler.new(node) sanitized = compiler.node expect(sanitized.trusted_data).to_not eq({ :a => 42 }) end it "should prefer trusted_data in the node above other plausible sources" do node = Puppet::Node.new("mynode") node.trusted_data = { 'authenticated' => true, 'certname' => 'the real deal', 'extensions' => 'things' } node.parameters['trusted'] = { 'authenticated' => true, 'certname' => 'not me', 'extensions' => 'things' } compiler = Puppet::Parser::Compiler.new(node) sanitized = compiler.node expect(sanitized.trusted_data).to eq({ 'authenticated' => true, 'certname' => 'the real deal', 'extensions' => 'things' }) end end describe "when managing scopes" do it "should create a top scope" do expect(@compiler.topscope).to be_instance_of(Puppet::Parser::Scope) end it "should be able to create new scopes" do expect(@compiler.newscope(@compiler.topscope)).to be_instance_of(Puppet::Parser::Scope) end it "should set the parent scope of the new scope to be the passed-in parent" do scope = mock 'scope' newscope = @compiler.newscope(scope) expect(newscope.parent).to equal(scope) end it "should set the parent scope of the new scope to its topscope if the parent passed in is nil" do newscope = @compiler.newscope(nil) expect(newscope.parent).to equal(@compiler.topscope) end end describe "when compiling" do def compile_methods [:set_node_parameters, :evaluate_main, :evaluate_ast_node, :evaluate_node_classes, :evaluate_generators, :fail_on_unevaluated, :finish, :store, :extract, :evaluate_relationships] end # Stub all of the main compile methods except the ones we're specifically interested in. def compile_stub(*except) (compile_methods - except).each { |m| @compiler.stubs(m) } end it "should set node parameters as variables in the top scope" do params = {"a" => "b", "c" => "d"} @node.stubs(:parameters).returns(params) compile_stub(:set_node_parameters) @compiler.compile expect(@compiler.topscope['a']).to eq("b") expect(@compiler.topscope['c']).to eq("d") end it "should set node parameters that are of Symbol type as String variables in the top scope" do params = {"a" => :b} @node.stubs(:parameters).returns(params) compile_stub(:set_node_parameters) @compiler.compile expect(@compiler.topscope['a']).to eq("b") end it "should set the node's environment as a string variable in top scope" do compile_stub(:set_node_parameters) @node.merge({'wat' => 'this is how the sausage is made'}) @compiler.compile expect(@compiler.topscope['environment']).to eq("testing") expect(@compiler.topscope['wat']).to eq('this is how the sausage is made') end it "sets the environment based on node.environment instead of the parameters" do compile_stub(:set_node_parameters) @node.parameters['environment'] = "Not actually #{@node.environment.name}" @compiler.compile expect(@compiler.topscope['environment']).to eq('testing') end it "should set the client and server versions on the catalog" do params = {"clientversion" => "2", "serverversion" => "3"} @node.stubs(:parameters).returns(params) compile_stub(:set_node_parameters) @compiler.compile expect(@compiler.catalog.client_version).to eq("2") expect(@compiler.catalog.server_version).to eq("3") end it "should evaluate the main class if it exists" do compile_stub(:evaluate_main) main_class = @known_resource_types.add Puppet::Resource::Type.new(:hostclass, "") main_class.expects(:evaluate_code).with { |r| r.is_a?(Puppet::Parser::Resource) } @compiler.topscope.expects(:source=).with(main_class) @compiler.compile end it "should create a new, empty 'main' if no main class exists" do compile_stub(:evaluate_main) @compiler.compile expect(@known_resource_types.find_hostclass("")).to be_instance_of(Puppet::Resource::Type) end it "should add an edge between the main stage and main class" do @compiler.compile expect(stage = @compiler.catalog.resource(:stage, "main")).to be_instance_of(Puppet::Parser::Resource) expect(klass = @compiler.catalog.resource(:class, "")).to be_instance_of(Puppet::Parser::Resource) expect(@compiler.catalog.edge?(stage, klass)).to be_truthy end it "should evaluate all added collections" do colls = [] # And when the collections fail to evaluate. colls << mock("coll1-false") colls << mock("coll2-false") colls.each { |c| c.expects(:evaluate).returns(false) } @compiler.add_collection(colls[0]) @compiler.add_collection(colls[1]) compile_stub(:evaluate_generators) @compiler.compile end it "should ignore builtin resources" do resource = resource(:file, "testing") @compiler.add_resource(@scope, resource) resource.expects(:evaluate).never @compiler.compile end it "should evaluate unevaluated resources" do resource = CompilerTestResource.new(:file, "testing") @compiler.add_resource(@scope, resource) # We have to now mark the resource as evaluated resource.expects(:evaluate).with { |*whatever| resource.evaluated = true } @compiler.compile end it "should not evaluate already-evaluated resources" do resource = resource(:file, "testing") resource.stubs(:evaluated?).returns true @compiler.add_resource(@scope, resource) resource.expects(:evaluate).never @compiler.compile end it "should evaluate unevaluated resources created by evaluating other resources" do resource = CompilerTestResource.new(:file, "testing") @compiler.add_resource(@scope, resource) resource2 = CompilerTestResource.new(:file, "other") # We have to now mark the resource as evaluated resource.expects(:evaluate).with { |*whatever| resource.evaluated = true; @compiler.add_resource(@scope, resource2) } resource2.expects(:evaluate).with { |*whatever| resource2.evaluated = true } @compiler.compile end describe "when finishing" do before do @compiler.send(:evaluate_main) @catalog = @compiler.catalog end def add_resource(name, parent = nil) resource = Puppet::Parser::Resource.new "file", name, :scope => @scope @compiler.add_resource(@scope, resource) @catalog.add_edge(parent, resource) if parent resource end it "should call finish() on all resources" do # Add a resource that does respond to :finish resource = Puppet::Parser::Resource.new "file", "finish", :scope => @scope resource.expects(:finish) @compiler.add_resource(@scope, resource) # And one that does not dnf_resource = stub_everything "dnf", :ref => "File[dnf]", :type => "file" @compiler.add_resource(@scope, dnf_resource) @compiler.send(:finish) end it "should call finish() in add_resource order" do resources = sequence('resources') resource1 = add_resource("finish1") resource1.expects(:finish).in_sequence(resources) resource2 = add_resource("finish2") resource2.expects(:finish).in_sequence(resources) @compiler.send(:finish) end it "should add each container's metaparams to its contained resources" do main = @catalog.resource(:class, :main) main[:noop] = true resource1 = add_resource("meh", main) @compiler.send(:finish) expect(resource1[:noop]).to be_truthy end it "should add metaparams recursively" do main = @catalog.resource(:class, :main) main[:noop] = true resource1 = add_resource("meh", main) resource2 = add_resource("foo", resource1) @compiler.send(:finish) expect(resource2[:noop]).to be_truthy end it "should prefer metaparams from immediate parents" do main = @catalog.resource(:class, :main) main[:noop] = true resource1 = add_resource("meh", main) resource2 = add_resource("foo", resource1) resource1[:noop] = false @compiler.send(:finish) expect(resource2[:noop]).to be_falsey end it "should merge tags downward" do main = @catalog.resource(:class, :main) main.tag("one") resource1 = add_resource("meh", main) resource1.tag "two" resource2 = add_resource("foo", resource1) @compiler.send(:finish) expect(resource2.tags).to be_include("one") expect(resource2.tags).to be_include("two") end it "should work if only middle resources have metaparams set" do main = @catalog.resource(:class, :main) resource1 = add_resource("meh", main) resource1[:noop] = true resource2 = add_resource("foo", resource1) @compiler.send(:finish) expect(resource2[:noop]).to be_truthy end end it "should return added resources in add order" do resource1 = resource(:file, "yay") @compiler.add_resource(@scope, resource1) resource2 = resource(:file, "youpi") @compiler.add_resource(@scope, resource2) expect(@compiler.resources).to eq([resource1, resource2]) end it "should add resources that do not conflict with existing resources" do resource = resource(:file, "yay") @compiler.add_resource(@scope, resource) expect(@compiler.catalog).to be_vertex(resource) end it "should fail to add resources that conflict with existing resources" do path = make_absolute("/foo") file1 = resource(:file, path) file2 = resource(:file, path) @compiler.add_resource(@scope, file1) expect { @compiler.add_resource(@scope, file2) }.to raise_error(Puppet::Resource::Catalog::DuplicateResourceError) end it "should add an edge from the scope resource to the added resource" do resource = resource(:file, "yay") @compiler.add_resource(@scope, resource) expect(@compiler.catalog).to be_edge(@scope.resource, resource) end it "should not add non-class resources that don't specify a stage to the 'main' stage" do main = @compiler.catalog.resource(:stage, :main) resource = resource(:file, "foo") @compiler.add_resource(@scope, resource) expect(@compiler.catalog).not_to be_edge(main, resource) end it "should not add any parent-edges to stages" do stage = resource(:stage, "other") @compiler.add_resource(@scope, stage) @scope.resource = resource(:class, "foo") expect(@compiler.catalog.edge?(@scope.resource, stage)).to be_falsey end it "should not attempt to add stages to other stages" do other_stage = resource(:stage, "other") second_stage = resource(:stage, "second") @compiler.add_resource(@scope, other_stage) @compiler.add_resource(@scope, second_stage) second_stage[:stage] = "other" expect(@compiler.catalog.edge?(other_stage, second_stage)).to be_falsey end it "should have a method for looking up resources" do resource = resource(:yay, "foo") @compiler.add_resource(@scope, resource) expect(@compiler.findresource("Yay[foo]")).to equal(resource) end it "should be able to look resources up by type and title" do resource = resource(:yay, "foo") @compiler.add_resource(@scope, resource) expect(@compiler.findresource("Yay", "foo")).to equal(resource) end it "should not evaluate virtual defined resources" do resource = resource(:file, "testing") resource.virtual = true @compiler.add_resource(@scope, resource) resource.expects(:evaluate).never @compiler.compile end end describe "when evaluating collections" do it "should evaluate each collection" do 2.times { |i| coll = mock 'coll%s' % i @compiler.add_collection(coll) # This is the hard part -- we have to emulate the fact that # collections delete themselves if they are done evaluating. coll.expects(:evaluate).with do @compiler.delete_collection(coll) end } @compiler.compile end it "should not fail when there are unevaluated resource collections that do not refer to specific resources" do coll = stub 'coll', :evaluate => false coll.expects(:unresolved_resources).returns(nil) @compiler.add_collection(coll) expect { @compiler.compile }.not_to raise_error end it "should fail when there are unevaluated resource collections that refer to a specific resource" do coll = stub 'coll', :evaluate => false coll.expects(:unresolved_resources).returns(:something) @compiler.add_collection(coll) expect { @compiler.compile }.to raise_error(Puppet::ParseError, 'Failed to realize virtual resources something') end it "should fail when there are unevaluated resource collections that refer to multiple specific resources" do coll = stub 'coll', :evaluate => false coll.expects(:unresolved_resources).returns([:one, :two]) @compiler.add_collection(coll) expect { @compiler.compile }.to raise_error(Puppet::ParseError, 'Failed to realize virtual resources one, two') end end describe "when evaluating relationships" do it "should evaluate each relationship with its catalog" do dep = stub 'dep' dep.expects(:evaluate).with(@compiler.catalog) @compiler.add_relationship dep @compiler.evaluate_relationships end end describe "when told to evaluate missing classes" do it "should fail if there's no source listed for the scope" do scope = stub 'scope', :source => nil expect { @compiler.evaluate_classes(%w{one two}, scope) }.to raise_error(Puppet::DevError) end it "should raise an error if a class is not found" do @scope.environment.known_resource_types.expects(:find_hostclass).with("notfound").returns(nil) expect{ @compiler.evaluate_classes(%w{notfound}, @scope) }.to raise_error(Puppet::Error, /Could not find class/) end it "should raise an error when it can't find class" do klasses = {'foo'=>nil} @node.classes = klasses expect{ @compiler.compile }.to raise_error(Puppet::Error, /Could not find class foo for testnode/) end end describe "when evaluating found classes" do before do Puppet.settings[:data_binding_terminus] = "none" @class = @known_resource_types.add Puppet::Resource::Type.new(:hostclass, "myclass") @resource = stub 'resource', :ref => "Class[myclass]", :type => "file" end around do |example| Puppet.override( :environments => Puppet::Environments::Static.new(environment), :description => "Static loader for specs" ) do example.run end end it "should evaluate each class" do @compiler.catalog.stubs(:tag) @class.expects(:ensure_in_catalog).with(@scope) @scope.stubs(:class_scope).with(@class) @compiler.evaluate_classes(%w{myclass}, @scope) end describe "and the classes are specified as a hash with parameters" do before do @node.classes = {} @ast_obj = Puppet::Parser::AST::Leaf.new(:value => 'foo') end # Define the given class with default parameters def define_class(name, parameters) @node.classes[name] = parameters klass = Puppet::Resource::Type.new(:hostclass, name, :arguments => {'p1' => @ast_obj, 'p2' => @ast_obj}) @compiler.environment.known_resource_types.add klass end def compile @catalog = @compiler.compile end it "should record which classes are evaluated" do classes = {'foo'=>{}, 'bar::foo'=>{}, 'bar'=>{}} classes.each { |c, params| define_class(c, params) } compile() classes.each { |name, p| expect(@catalog.classes).to include(name) } end it "should provide default values for parameters that have no values specified" do define_class('foo', {}) compile() expect(@catalog.resource(:class, 'foo')['p1']).to eq("foo") end it "should use any provided values" do define_class('foo', {'p1' => 'real_value'}) compile() expect(@catalog.resource(:class, 'foo')['p1']).to eq("real_value") end it "should support providing some but not all values" do define_class('foo', {'p1' => 'real_value'}) compile() expect(@catalog.resource(:class, 'Foo')['p1']).to eq("real_value") expect(@catalog.resource(:class, 'Foo')['p2']).to eq("foo") end it "should ensure each node class is in catalog and has appropriate tags" do klasses = ['bar::foo'] @node.classes = klasses ast_obj = Puppet::Parser::AST::Leaf.new(:value => 'foo') klasses.each do |name| klass = Puppet::Resource::Type.new(:hostclass, name, :arguments => {'p1' => ast_obj, 'p2' => ast_obj}) @compiler.environment.known_resource_types.add klass end catalog = @compiler.compile r2 = catalog.resources.detect {|r| r.title == 'Bar::Foo' } expect(r2.tags).to eq(Puppet::Util::TagSet.new(['bar::foo', 'class', 'bar', 'foo'])) end end it "should fail if required parameters are missing" do klass = {'foo'=>{'a'=>'one'}} @node.classes = klass klass = Puppet::Resource::Type.new(:hostclass, 'foo', :arguments => {'a' => nil, 'b' => nil}) @compiler.environment.known_resource_types.add klass expect { @compiler.compile }.to raise_error(Puppet::PreformattedError, /Class\[Foo\]: expects a value for parameter 'b'/) end it "should fail if invalid parameters are passed" do klass = {'foo'=>{'3'=>'one'}} @node.classes = klass klass = Puppet::Resource::Type.new(:hostclass, 'foo', :arguments => {}) @compiler.environment.known_resource_types.add klass expect { @compiler.compile }.to raise_error(Puppet::PreformattedError, /Class\[Foo\]: has no parameter named '3'/) end it "should ensure class is in catalog without params" do @node.classes = {'foo'=>nil} foo = Puppet::Resource::Type.new(:hostclass, 'foo') @compiler.environment.known_resource_types.add foo catalog = @compiler.compile expect(catalog.classes).to include 'foo' end it "should not evaluate the resources created for found classes unless asked" do @compiler.catalog.stubs(:tag) @resource.expects(:evaluate).never @class.expects(:ensure_in_catalog).returns(@resource) @scope.stubs(:class_scope).with(@class) @compiler.evaluate_classes(%w{myclass}, @scope) end it "should immediately evaluate the resources created for found classes when asked" do @compiler.catalog.stubs(:tag) @resource.expects(:evaluate) @class.expects(:ensure_in_catalog).returns(@resource) @scope.stubs(:class_scope).with(@class) @compiler.evaluate_classes(%w{myclass}, @scope, false) end it "should skip classes that have already been evaluated" do @compiler.catalog.stubs(:tag) @scope.stubs(:class_scope).with(@class).returns(@scope) @compiler.expects(:add_resource).never @resource.expects(:evaluate).never Puppet::Parser::Resource.expects(:new).never @compiler.evaluate_classes(%w{myclass}, @scope, false) end it "should skip classes previously evaluated with different capitalization" do @compiler.catalog.stubs(:tag) @scope.environment.known_resource_types.stubs(:find_hostclass).with("MyClass").returns(@class) @scope.stubs(:class_scope).with(@class).returns(@scope) @compiler.expects(:add_resource).never @resource.expects(:evaluate).never Puppet::Parser::Resource.expects(:new).never @compiler.evaluate_classes(%w{MyClass}, @scope, false) end end describe "when evaluating AST nodes with no AST nodes present" do it "should do nothing" do @compiler.environment.known_resource_types.stubs(:nodes).returns(false) Puppet::Parser::Resource.expects(:new).never @compiler.send(:evaluate_ast_node) end end describe "when evaluating AST nodes with AST nodes present" do before do @compiler.environment.known_resource_types.stubs(:nodes?).returns true # Set some names for our test @node.stubs(:names).returns(%w{a b c}) @compiler.environment.known_resource_types.stubs(:node).with("a").returns(nil) @compiler.environment.known_resource_types.stubs(:node).with("b").returns(nil) @compiler.environment.known_resource_types.stubs(:node).with("c").returns(nil) # It should check this last, of course. @compiler.environment.known_resource_types.stubs(:node).with("default").returns(nil) end it "should fail if the named node cannot be found" do expect { @compiler.send(:evaluate_ast_node) }.to raise_error(Puppet::ParseError) end it "should evaluate the first node class matching the node name" do node_class = stub 'node', :name => "c", :evaluate_code => nil @compiler.environment.known_resource_types.stubs(:node).with("c").returns(node_class) node_resource = stub 'node resource', :ref => "Node[c]", :evaluate => nil, :type => "node" node_class.expects(:ensure_in_catalog).returns(node_resource) @compiler.compile end it "should match the default node if no matching node can be found" do node_class = stub 'node', :name => "default", :evaluate_code => nil @compiler.environment.known_resource_types.stubs(:node).with("default").returns(node_class) node_resource = stub 'node resource', :ref => "Node[default]", :evaluate => nil, :type => "node" node_class.expects(:ensure_in_catalog).returns(node_resource) @compiler.compile end it "should evaluate the node resource immediately rather than using lazy evaluation" do node_class = stub 'node', :name => "c" @compiler.environment.known_resource_types.stubs(:node).with("c").returns(node_class) node_resource = stub 'node resource', :ref => "Node[c]", :type => "node" node_class.expects(:ensure_in_catalog).returns(node_resource) node_resource.expects(:evaluate) @compiler.send(:evaluate_ast_node) end end describe 'when using meta parameters to form relationships' do include PuppetSpec::Compiler [:before, :subscribe, :notify, :require].each do | meta_p | it "an entry consisting of nested empty arrays is flattened for parameter #{meta_p}" do expect { node = Puppet::Node.new('someone') manifest = <<-"MANIFEST" notify{hello_kitty: message => meow, #{meta_p} => [[],[]]} notify{hello_kitty2: message => meow, #{meta_p} => [[],[[]],[]]} MANIFEST catalog = compile_to_catalog(manifest, node) catalog.to_ral }.not_to raise_error end end end describe "when evaluating node classes" do include PuppetSpec::Compiler describe "when provided classes in array format" do let(:node) { Puppet::Node.new('someone', :classes => ['something']) } describe "when the class exists" do it "should succeed if the class is already included" do manifest = <<-MANIFEST class something {} include something MANIFEST catalog = compile_to_catalog(manifest, node) expect(catalog.resource('Class', 'Something')).not_to be_nil end it "should evaluate the class without parameters if it's not already included" do manifest = "class something {}" catalog = compile_to_catalog(manifest, node) expect(catalog.resource('Class', 'Something')).not_to be_nil end end it "should fail if the class doesn't exist" do expect { compile_to_catalog('', node) }.to raise_error(Puppet::Error, /Could not find class something/) end end describe "when provided classes in hash format" do describe "for classes without parameters" do let(:node) { Puppet::Node.new('someone', :classes => {'something' => {}}) } describe "when the class exists" do it "should succeed if the class is already included" do manifest = <<-MANIFEST class something {} include something MANIFEST catalog = compile_to_catalog(manifest, node) expect(catalog.resource('Class', 'Something')).not_to be_nil end it "should evaluate the class if it's not already included" do manifest = <<-MANIFEST class something {} MANIFEST catalog = compile_to_catalog(manifest, node) expect(catalog.resource('Class', 'Something')).not_to be_nil end end it "should fail if the class doesn't exist" do expect { compile_to_catalog('', node) }.to raise_error(Puppet::Error, /Could not find class something/) end end describe "for classes with parameters" do let(:node) { Puppet::Node.new('someone', :classes => {'something' => {'configuron' => 'defrabulated'}}) } describe "when the class exists" do it "should fail if the class is already included" do manifest = <<-MANIFEST class something($configuron=frabulated) {} include something MANIFEST expect { compile_to_catalog(manifest, node) }.to raise_error(Puppet::Error, /Class\[Something\] is already declared/) end it "should evaluate the class if it's not already included" do manifest = <<-MANIFEST class something($configuron=frabulated) {} MANIFEST catalog = compile_to_catalog(manifest, node) resource = catalog.resource('Class', 'Something') expect(resource['configuron']).to eq('defrabulated') end end it "should fail if the class doesn't exist" do expect { compile_to_catalog('', node) }.to raise_error(Puppet::Error, /Could not find class something/) end it 'evaluates classes declared with parameters before unparameterized classes' do node = Puppet::Node.new('someone', :classes => { 'app::web' => {}, 'app' => { 'port' => 8080 } }) manifest = <<-MANIFEST class app($port = 80) { } class app::web($port = $app::port) inherits app { notify { expected: message => "$port" } } MANIFEST catalog = compile_to_catalog(manifest, node) expect(catalog).to have_resource("Class[App]").with_parameter(:port, 8080) expect(catalog).to have_resource("Class[App::Web]") expect(catalog).to have_resource("Notify[expected]").with_parameter(:message, "8080") end end end end describe "when managing resource overrides" do before do @override = stub 'override', :ref => "File[/foo]", :type => "my" @resource = resource(:file, "/foo") end it "should be able to store overrides" do expect { @compiler.add_override(@override) }.not_to raise_error end it "should apply overrides to the appropriate resources" do @compiler.add_resource(@scope, @resource) @resource.expects(:merge).with(@override) @compiler.add_override(@override) @compiler.compile end it "should accept overrides before the related resource has been created" do @resource.expects(:merge).with(@override) # First store the override @compiler.add_override(@override) # Then the resource @compiler.add_resource(@scope, @resource) # And compile, so they get resolved @compiler.compile end it "should fail if the compile is finished and resource overrides have not been applied" do @compiler.add_override(@override) expect { @compiler.compile }.to raise_error Puppet::ParseError, 'Could not find resource(s) File[/foo] for overriding' end end end puppet-5.5.10/spec/unit/parser/files_spec.rb0000644005276200011600000001013213417161721020655 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/parser/files' describe Puppet::Parser::Files do include PuppetSpec::Files let(:modulepath) { tmpdir("modulepath") } let(:environment) { Puppet::Node::Environment.create(:testing, [modulepath]) } let(:mymod) { File.join(modulepath, "mymod") } let(:mymod_files) { File.join(mymod, "files") } let(:mymod_a_file) { File.join(mymod_files, "some.txt") } let(:mymod_templates) { File.join(mymod, "templates") } let(:mymod_a_template) { File.join(mymod_templates, "some.erb") } let(:mymod_manifests) { File.join(mymod, "manifests") } let(:mymod_init_manifest) { File.join(mymod_manifests, "init.pp") } let(:mymod_another_manifest) { File.join(mymod_manifests, "another.pp") } let(:an_absolute_file_path_outside_of_module) { make_absolute("afilenamesomewhere") } before do FileUtils.mkdir_p(mymod_files) File.open(mymod_a_file, 'w') do |f| f.puts('something') end FileUtils.mkdir_p(mymod_templates) File.open(mymod_a_template, 'w') do |f| f.puts('<%= "something" %>') end FileUtils.mkdir_p(mymod_manifests) File.open(mymod_init_manifest, 'w') do |f| f.puts('class mymod { }') end File.open(mymod_another_manifest, 'w') do |f| f.puts('class mymod::another { }') end end describe "when searching for files" do it "returns fully-qualified file names directly" do expect(Puppet::Parser::Files.find_file(an_absolute_file_path_outside_of_module, environment)).to eq(an_absolute_file_path_outside_of_module) end it "returns the full path to the file if given a modulename/relative_filepath selector " do expect(Puppet::Parser::Files.find_file("mymod/some.txt", environment)).to eq(mymod_a_file) end it "returns nil if the module is not found" do expect(Puppet::Parser::Files.find_file("mod_does_not_exist/myfile", environment)).to be_nil end it "also returns nil if the module is found, but the file is not" do expect(Puppet::Parser::Files.find_file("mymod/file_does_not_exist", environment)).to be_nil end end describe "when searching for templates" do it "returns fully-qualified templates directly" do expect(Puppet::Parser::Files.find_template(an_absolute_file_path_outside_of_module, environment)).to eq(an_absolute_file_path_outside_of_module) end it "returns the full path to the template if given a modulename/relative_templatepath selector" do expect(Puppet::Parser::Files.find_template("mymod/some.erb", environment)).to eq(mymod_a_template) end it "returns nil if the module is not found" do expect(Puppet::Parser::Files.find_template("module_does_not_exist/mytemplate", environment)).to be_nil end it "returns nil if the module is found, but the template is not " do expect(Puppet::Parser::Files.find_template("mymod/template_does_not_exist", environment)).to be_nil end end describe "when searching for manifests in a module" do let(:no_manifests_found) { [nil, []] } it "ignores invalid module names" do expect(Puppet::Parser::Files.find_manifests_in_modules("mod.has.invalid.name/init.pp", environment)).to eq(no_manifests_found) end it "returns no files when no module is found" do expect(Puppet::Parser::Files.find_manifests_in_modules("not_here_module/init.pp", environment)).to eq(no_manifests_found) end it "returns the name of the module and the manifests from the first found module" do expect(Puppet::Parser::Files.find_manifests_in_modules("mymod/init.pp", environment) ).to eq(["mymod", [mymod_init_manifest]]) end it "always includes init.pp if present" do expect(Puppet::Parser::Files.find_manifests_in_modules("mymod/another.pp", environment) ).to eq(["mymod", [mymod_init_manifest, mymod_another_manifest]]) end it "does not find the module when it is a different environment" do different_env = Puppet::Node::Environment.create(:different, []) expect(Puppet::Parser::Files.find_manifests_in_modules("mymod/init.pp", different_env)).to eq(no_manifests_found) end end end puppet-5.5.10/spec/unit/parser/functions/0000755005276200011600000000000013417162176020234 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/parser/functions/create_resources_spec.rb0000644005276200011600000003206113417161721025125 0ustar jenkinsjenkinsrequire 'puppet' require 'spec_helper' require 'puppet_spec/compiler' require 'puppet_spec/files' describe 'function for dynamically creating resources' do include PuppetSpec::Compiler include PuppetSpec::Files before :each do node = Puppet::Node.new("floppy", :environment => 'production') @compiler = Puppet::Parser::Compiler.new(node) @scope = Puppet::Parser::Scope.new(@compiler) @topscope = @scope.compiler.topscope @scope.parent = @topscope Puppet::Parser::Functions.function(:create_resources) end it "should exist" do expect(Puppet::Parser::Functions.function(:create_resources)).to eq("function_create_resources") end it 'should require two or three arguments' do expect { @scope.function_create_resources(['foo']) }.to raise_error(ArgumentError, 'create_resources(): Wrong number of arguments given (1 for minimum 2)') expect { @scope.function_create_resources(['foo', 'bar', 'blah', 'baz']) }.to raise_error(ArgumentError, 'create_resources(): wrong number of arguments (4; must be 2 or 3)') end it 'should require second argument to be a hash' do expect { @scope.function_create_resources(['foo','bar']) }.to raise_error(ArgumentError, 'create_resources(): second argument must be a hash') end it 'should require optional third argument to be a hash' do expect { @scope.function_create_resources(['foo',{},'foo']) }.to raise_error(ArgumentError, 'create_resources(): third argument, if provided, must be a hash') end context 'when being called from a manifest in a file' do let(:dir) do dir_containing('manifests', { 'site.pp' => <<-EOF # comment here to make the call be on a particular # source line (3) create_resources('notify', { 'a' => { 'message'=>'message a'}, 'b' => { 'message'=>'message b'}, } ) EOF } ) end it 'file and line information where call originates is written to all resources created in one call' do node = Puppet::Node.new('test') file = File.join(dir, 'site.pp') Puppet[:manifest] = file catalog = Puppet::Parser::Compiler.compile(node).filter { |r| r.virtual? } expect(catalog.resource(:notify, 'a').file).to eq(file) expect(catalog.resource(:notify, 'a').line).to eq(3) expect(catalog.resource(:notify, 'b').file).to eq(file) expect(catalog.resource(:notify, 'b').line).to eq(3) end end describe 'when creating native types' do it 'empty hash should not cause resources to be added' do noop_catalog = compile_to_catalog("create_resources('file', {})") empty_catalog = compile_to_catalog("") expect(noop_catalog.resources.size).to eq(empty_catalog.resources.size) end it 'should be able to add' do catalog = compile_to_catalog("create_resources('file', {'/etc/foo'=>{'ensure'=>'present'}})") expect(catalog.resource(:file, "/etc/foo")['ensure']).to eq('present') end it 'should pick up and pass on file and line information' do # mock location as the compile_to_catalog sets Puppet[:code} which does not # have file/line support. Puppet::Pops::PuppetStack.expects(:top_of_stack).once.returns(['test.pp', 1234]) catalog = compile_to_catalog("create_resources('file', {'/etc/foo'=>{'ensure'=>'present'}})") r = catalog.resource(:file, "/etc/foo") expect(r.file).to eq('test.pp') expect(r.line).to eq(1234) end it 'should be able to add virtual resources' do catalog = compile_to_catalog("create_resources('@file', {'/etc/foo'=>{'ensure'=>'present'}})\nrealize(File['/etc/foo'])") expect(catalog.resource(:file, "/etc/foo")['ensure']).to eq('present') end it 'unrealized exported resources should not be added' do # a compiled catalog is normally filtered on virtual resources # here the compilation is performed unfiltered to be able to find the exported resource # it is then asserted that the exported resource is also virtual (and therefore filtered out by a real compilation). catalog = compile_to_catalog_unfiltered("create_resources('@@file', {'/etc/foo'=>{'ensure'=>'present'}})") expect(catalog.resource(:file, "/etc/foo").exported).to eq(true) expect(catalog.resource(:file, "/etc/foo").virtual).to eq(true) end it 'should be able to add exported resources' do catalog = compile_to_catalog("create_resources('@@file', {'/etc/foo'=>{'ensure'=>'present'}}) realize(File['/etc/foo'])") expect(catalog.resource(:file, "/etc/foo")['ensure']).to eq('present') expect(catalog.resource(:file, "/etc/foo").exported).to eq(true) end it 'should accept multiple resources' do catalog = compile_to_catalog("create_resources('notify', {'foo'=>{'message'=>'one'}, 'bar'=>{'message'=>'two'}})") expect(catalog.resource(:notify, "foo")['message']).to eq('one') expect(catalog.resource(:notify, "bar")['message']).to eq('two') end it 'should fail to add non-existing resource type' do expect do @scope.function_create_resources(['create-resource-foo', { 'foo' => {} }]) end.to raise_error(/Unknown resource type: 'create-resource-foo'/) end it 'should be able to add edges' do rg = compile_to_relationship_graph("notify { test: }\n create_resources('notify', {'foo'=>{'require'=>'Notify[test]'}})") test = rg.vertices.find { |v| v.title == 'test' } foo = rg.vertices.find { |v| v.title == 'foo' } expect(test).to be expect(foo).to be expect(rg.path_between(test,foo)).to be end it 'should filter out undefined edges as they cause errors' do rg = compile_to_relationship_graph("notify { test: }\n create_resources('notify', {'foo'=>{'require'=>undef}})") test = rg.vertices.find { |v| v.title == 'test' } foo = rg.vertices.find { |v| v.title == 'foo' } expect(test).to be expect(foo).to be expect(rg.path_between(foo,nil)).to_not be end it 'should filter out undefined edges in an array as they cause errors' do rg = compile_to_relationship_graph("notify { test: }\n create_resources('notify', {'foo'=>{'require'=>[undef]}})") test = rg.vertices.find { |v| v.title == 'test' } foo = rg.vertices.find { |v| v.title == 'foo' } expect(test).to be expect(foo).to be expect(rg.path_between(foo,nil)).to_not be end it 'should account for default values' do catalog = compile_to_catalog("create_resources('file', {'/etc/foo'=>{'ensure'=>'present'}, '/etc/baz'=>{'group'=>'food'}}, {'group' => 'bar'})") expect(catalog.resource(:file, "/etc/foo")['group']).to eq('bar') expect(catalog.resource(:file, "/etc/baz")['group']).to eq('food') end end describe 'when dynamically creating resource types' do it 'should be able to create defined resource types' do catalog = compile_to_catalog(<<-MANIFEST) define foocreateresource($one) { notify { $name: message => $one } } create_resources('foocreateresource', {'blah'=>{'one'=>'two'}}) MANIFEST expect(catalog.resource(:notify, "blah")['message']).to eq('two') end it 'should fail if defines are missing params' do expect { compile_to_catalog(<<-MANIFEST) define foocreateresource($one) { notify { $name: message => $one } } create_resources('foocreateresource', {'blah'=>{}}) MANIFEST }.to raise_error(Puppet::Error, /Foocreateresource\[blah\]: expects a value for parameter 'one'/) end it 'should accept undef as explicit value when parameter has no default value' do catalog = compile_to_catalog(<<-MANIFEST) define foocreateresource($one) { notify { $name: message => "aaa${one}bbb" } } create_resources('foocreateresource', {'blah'=>{ one => undef}}) MANIFEST expect(catalog.resource(:notify, "blah")['message']).to eq('aaabbb') end it 'should use default value expression if given value is undef' do catalog = compile_to_catalog(<<-MANIFEST) define foocreateresource($one = 'xx') { notify { $name: message => "aaa${one}bbb" } } create_resources('foocreateresource', {'blah'=>{ one => undef}}) MANIFEST expect(catalog.resource(:notify, "blah")['message']).to eq('aaaxxbbb') end it 'should be able to add multiple defines' do catalog = compile_to_catalog(<<-MANIFEST) define foocreateresource($one) { notify { $name: message => $one } } create_resources('foocreateresource', {'blah'=>{'one'=>'two'}, 'blaz'=>{'one'=>'three'}}) MANIFEST expect(catalog.resource(:notify, "blah")['message']).to eq('two') expect(catalog.resource(:notify, "blaz")['message']).to eq('three') end it 'should be able to add edges' do rg = compile_to_relationship_graph(<<-MANIFEST) define foocreateresource($one) { notify { $name: message => $one } } notify { test: } create_resources('foocreateresource', {'blah'=>{'one'=>'two', 'require' => 'Notify[test]'}}) MANIFEST test = rg.vertices.find { |v| v.title == 'test' } blah = rg.vertices.find { |v| v.title == 'blah' } expect(test).to be expect(blah).to be expect(rg.path_between(test,blah)).to be end it 'should account for default values' do catalog = compile_to_catalog(<<-MANIFEST) define foocreateresource($one) { notify { $name: message => $one } } create_resources('foocreateresource', {'blah'=>{}}, {'one' => 'two'}) MANIFEST expect(catalog.resource(:notify, "blah")['message']).to eq('two') end end describe 'when creating classes' do let(:logs) { [] } let(:warnings) { logs.select { |log| log.level == :warning }.map { |log| log.message } } it 'should be able to create classes' do catalog = compile_to_catalog(<<-MANIFEST) class bar($one) { notify { test: message => $one } } create_resources('class', {'bar'=>{'one'=>'two'}}) MANIFEST expect(catalog.resource(:notify, "test")['message']).to eq('two') expect(catalog.resource(:class, "bar")).not_to be_nil end [:off, :warning].each do | strictness | it "should warn if strict = #{strictness} and class is exported" do Puppet[:strict] = strictness collect_notices('class test{} create_resources("@@class", {test => {}})') expect(warnings).to include(/Classes are not virtualizable/) end end it 'should error if strict = error and class is exported' do Puppet[:strict] = :error expect{ compile_to_catalog('class test{} create_resources("@@class", {test => {}})') }.to raise_error(/Classes are not virtualizable/) end [:off, :warning].each do | strictness | it "should warn if strict = #{strictness} and class is virtual" do Puppet[:strict] = strictness collect_notices('class test{} create_resources("@class", {test => {}})') expect(warnings).to include(/Classes are not virtualizable/) end end it 'should error if strict = error and class is virtual' do Puppet[:strict] = :error expect{ compile_to_catalog('class test{} create_resources("@class", {test => {}})') }.to raise_error(/Classes are not virtualizable/) end it 'should be able to add edges' do rg = compile_to_relationship_graph(<<-MANIFEST) class bar($one) { notify { test: message => $one } } notify { tester: } create_resources('class', {'bar'=>{'one'=>'two', 'require' => 'Notify[tester]'}}) MANIFEST test = rg.vertices.find { |v| v.title == 'test' } tester = rg.vertices.find { |v| v.title == 'tester' } expect(test).to be expect(tester).to be expect(rg.path_between(tester,test)).to be end it 'should account for default values' do catalog = compile_to_catalog(<<-MANIFEST) class bar($one) { notify { test: message => $one } } create_resources('class', {'bar'=>{}}, {'one' => 'two'}) MANIFEST expect(catalog.resource(:notify, "test")['message']).to eq('two') expect(catalog.resource(:class, "bar")).not_to be_nil end it 'should fail with a correct error message if the syntax of an imported file is incorrect' do expect{ Puppet[:modulepath] = my_fixture_dir compile_to_catalog('include foo') }.to raise_error(Puppet::Error, /Syntax error at.*/) end it 'is not available when --tasks is on' do Puppet[:tasks] = true expect do compile_to_catalog(<<-MANIFEST) create_resources('class', {'bar'=>{}}, {'one' => 'two'}) MANIFEST end.to raise_error(Puppet::ParseError, /is only available when compiling a catalog/) end end def collect_notices(code) Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do compile_to_catalog(code) end end end puppet-5.5.10/spec/unit/parser/functions/fqdn_rand_spec.rb0000644005276200011600000000473713417161721023535 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/scope' describe "the fqdn_rand function" do include PuppetSpec::Scope it "returns an integer" do expect(fqdn_rand(3)).to be_an(Integer) end it "provides a random number strictly less than the given max" do expect(fqdn_rand(3)).to satisfy {|n| n < 3 } end it "provides the same 'random' value on subsequent calls for the same host" do expect(fqdn_rand(3)).to eql(fqdn_rand(3)) end it "considers the same host and same extra arguments to have the same random sequence" do first_random = fqdn_rand(3, :extra_identifier => [1, "same", "host"]) second_random = fqdn_rand(3, :extra_identifier => [1, "same", "host"]) expect(first_random).to eql(second_random) end it "allows extra arguments to control the random value on a single host" do first_random = fqdn_rand(10000, :extra_identifier => [1, "different", "host"]) second_different_random = fqdn_rand(10000, :extra_identifier => [2, "different", "host"]) expect(first_random).not_to eql(second_different_random) end it "should return different sequences of value for different hosts" do val1 = fqdn_rand(1000000000, :host => "first.host.com") val2 = fqdn_rand(1000000000, :host => "second.host.com") expect(val1).not_to eql(val2) end it "should return a specific value with given set of inputs on non-fips enabled host" do Puppet::Util::Platform.stubs(:fips_enabled?).returns false expect(fqdn_rand(3000, :host => 'dummy.fqdn.net')).to eql(338) end it "should return a specific value with given set of inputs on fips enabled host" do Puppet::Util::Platform.stubs(:fips_enabled?).returns true expect(fqdn_rand(3000, :host => 'dummy.fqdn.net')).to eql(278) end it "should return a specific value with given seed on a non-fips enabled host" do Puppet::Util::Platform.stubs(:fips_enabled?).returns false expect(fqdn_rand(5000, :extra_identifier => ['expensive job 33'])).to eql(3374) end it "should return a specific value with given seed on a fips enabled host" do Puppet::Util::Platform.stubs(:fips_enabled?).returns true expect(fqdn_rand(5000, :extra_identifier => ['expensive job 33'])).to eql(2389) end def fqdn_rand(max, args = {}) host = args[:host] || '127.0.0.1' extra = args[:extra_identifier] || [] scope = create_test_scope_for_node('localhost') scope.stubs(:[]).with("::fqdn").returns(host) scope.function_fqdn_rand([max] + extra) end end puppet-5.5.10/spec/unit/parser/functions/hiera_array_spec.rb0000644005276200011600000000063013417161721024053 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/scope' describe 'Puppet::Parser::Functions#hiera_array' do include PuppetSpec::Scope let :scope do create_test_scope_for_node('foo') end it 'should raise an error since this function is converted to 4x API)' do expect { scope.function_hiera_array(['key']) }.to raise_error(Puppet::ParseError, /can only be called using the 4.x function API/) end end puppet-5.5.10/spec/unit/parser/functions/hiera_hash_spec.rb0000644005276200011600000000062613417161721023665 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/scope' describe 'Puppet::Parser::Functions#hiera_hash' do include PuppetSpec::Scope let :scope do create_test_scope_for_node('foo') end it 'should raise an error since this function is converted to 4x API)' do expect { scope.function_hiera_hash(['key']) }.to raise_error(Puppet::ParseError, /can only be called using the 4.x function API/) end end puppet-5.5.10/spec/unit/parser/functions/hiera_include_spec.rb0000644005276200011600000000063413417161721024364 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/scope' describe 'Puppet::Parser::Functions#hiera_include' do include PuppetSpec::Scope let :scope do create_test_scope_for_node('foo') end it 'should raise an error since this function is converted to 4x API)' do expect { scope.function_hiera_include(['key']) }.to raise_error(Puppet::ParseError, /can only be called using the 4.x function API/) end end puppet-5.5.10/spec/unit/parser/functions/hiera_spec.rb0000644005276200011600000000061413417161721022657 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/scope' describe 'Puppet::Parser::Functions#hiera' do include PuppetSpec::Scope let :scope do create_test_scope_for_node('foo') end it 'should raise an error since this function is converted to 4x API)' do expect { scope.function_hiera(['key']) }.to raise_error(Puppet::ParseError, /can only be called using the 4.x function API/) end end puppet-5.5.10/spec/unit/parser/functions/lookup_spec.rb0000644005276200011600000000064613417161721023105 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' require 'stringio' require 'puppet_spec/scope' describe "lookup function" do include PuppetSpec::Scope let :scope do create_test_scope_for_node('foo') end it 'should raise an error since this function is converted to 4x API)' do expect { scope.function_lookup(['key']) }.to raise_error(Puppet::ParseError, /can only be called using the 4.x function API/) end end puppet-5.5.10/spec/unit/parser/functions/realize_spec.rb0000644005276200011600000000342113417161721023221 0ustar jenkinsjenkinsrequire 'spec_helper' require 'matchers/resource' require 'puppet_spec/compiler' describe "the realize function" do include Matchers::Resource include PuppetSpec::Compiler it "realizes a single, referenced resource" do catalog = compile_to_catalog(<<-EOM) @notify { testing: } realize(Notify[testing]) EOM expect(catalog).to have_resource("Notify[testing]") end it "realizes multiple resources" do catalog = compile_to_catalog(<<-EOM) @notify { testing: } @notify { other: } realize(Notify[testing], Notify[other]) EOM expect(catalog).to have_resource("Notify[testing]") expect(catalog).to have_resource("Notify[other]") end it "realizes resources provided in arrays" do catalog = compile_to_catalog(<<-EOM) @notify { testing: } @notify { other: } realize([Notify[testing], [Notify[other]]]) EOM expect(catalog).to have_resource("Notify[testing]") expect(catalog).to have_resource("Notify[other]") end it "fails when the resource does not exist" do expect do compile_to_catalog(<<-EOM) realize(Notify[missing]) EOM end.to raise_error(Puppet::Error, /Failed to realize/) end it "fails when no parameters given" do expect do compile_to_catalog(<<-EOM) realize() EOM end.to raise_error(Puppet::Error, /Wrong number of arguments/) end it "silently does nothing when an empty array of resources is given" do compile_to_catalog(<<-EOM) realize([]) EOM end it 'is not available when --tasks is on' do Puppet[:tasks] = true expect do compile_to_catalog(<<-MANIFEST) realize([]) MANIFEST end.to raise_error(Puppet::ParseError, /is only available when compiling a catalog/) end end puppet-5.5.10/spec/unit/parser/functions/shellquote_spec.rb0000644005276200011600000000437613417161721023765 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe "the shellquote function" do let :node do Puppet::Node.new('localhost') end let :compiler do Puppet::Parser::Compiler.new(node) end let :scope do Puppet::Parser::Scope.new(compiler) end it "should exist" do expect(Puppet::Parser::Functions.function("shellquote")).to eq("function_shellquote") end it "should handle no arguments" do expect(scope.function_shellquote([])).to eq("") end it "should handle several simple arguments" do expect(scope.function_shellquote( ['foo', 'bar@example.com', 'localhost:/dev/null', 'xyzzy+-4711,23'] )).to eq('foo bar@example.com localhost:/dev/null xyzzy+-4711,23') end it "should handle array arguments" do expect(scope.function_shellquote( ['foo', ['bar@example.com', 'localhost:/dev/null'], 'xyzzy+-4711,23'] )).to eq('foo bar@example.com localhost:/dev/null xyzzy+-4711,23') end it "should quote unsafe characters" do expect(scope.function_shellquote(['/etc/passwd ', '(ls)', '*', '[?]', "'&'"])). to eq('"/etc/passwd " "(ls)" "*" "[?]" "\'&\'"') end it "should deal with double quotes" do expect(scope.function_shellquote(['"foo"bar"'])).to eq('\'"foo"bar"\'') end it "should cope with dollar signs" do expect(scope.function_shellquote(['$PATH', 'foo$bar', '"x$"'])). to eq("'$PATH' 'foo$bar' '\"x$\"'") end it "should deal with apostrophes (single quotes)" do expect(scope.function_shellquote(["'foo'bar'", "`$'EDITOR'`"])). to eq('"\'foo\'bar\'" "\\`\\$\'EDITOR\'\\`"') end it "should cope with grave accents (backquotes)" do expect(scope.function_shellquote(['`echo *`', '`ls "$MAILPATH"`'])). to eq("'`echo *`' '`ls \"$MAILPATH\"`'") end it "should deal with both single and double quotes" do expect(scope.function_shellquote(['\'foo"bar"xyzzy\'', '"foo\'bar\'xyzzy"'])). to eq('"\'foo\\"bar\\"xyzzy\'" "\\"foo\'bar\'xyzzy\\""') end it "should handle multiple quotes *and* dollars and backquotes" do expect(scope.function_shellquote(['\'foo"$x`bar`"xyzzy\''])). to eq('"\'foo\\"\\$x\\`bar\\`\\"xyzzy\'"') end it "should handle linefeeds" do expect(scope.function_shellquote(["foo \n bar"])).to eq("\"foo \n bar\"") end end puppet-5.5.10/spec/unit/parser/functions/digest_spec.rb0000644005276200011600000000141013417161722023042 0ustar jenkinsjenkins#!/usr/bin/env rspec require 'spec_helper' describe "the digest function", :uses_checksums => true do before :all do Puppet::Parser::Functions.autoloader.loadall end before :each do n = Puppet::Node.new('unnamed') c = Puppet::Parser::Compiler.new(n) @scope = Puppet::Parser::Scope.new(c) end it "should exist" do expect(Puppet::Parser::Functions.function("digest")).to eq("function_digest") end with_digest_algorithms do it "should use the proper digest function" do result = @scope.function_digest([plaintext]) expect(result).to(eql( checksum )) end it "should only accept one parameter" do expect do @scope.function_digest(['foo', 'bar']) end.to raise_error(ArgumentError) end end end puppet-5.5.10/spec/unit/parser/functions/fail_spec.rb0000644005276200011600000000140113417161722022476 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe "the 'fail' parser function" do before :all do Puppet::Parser::Functions.autoloader.loadall end let :scope do node = Puppet::Node.new('localhost') compiler = Puppet::Parser::Compiler.new(node) scope = Puppet::Parser::Scope.new(compiler) scope.stubs(:environment).returns(nil) scope end it "should exist" do expect(Puppet::Parser::Functions.function(:fail)).to eq("function_fail") end it "should raise a parse error if invoked" do expect { scope.function_fail([]) }.to raise_error Puppet::ParseError end it "should join arguments into a string in the error" do expect { scope.function_fail(["hello", "world"]) }.to raise_error(/hello world/) end end puppet-5.5.10/spec/unit/parser/functions/file_spec.rb0000644005276200011600000000616113417161722022512 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/files' describe "the 'file' function" do include PuppetSpec::Files before :all do Puppet::Parser::Functions.autoloader.loadall end let :node do Puppet::Node.new('localhost') end let :compiler do Puppet::Parser::Compiler.new(node) end let :scope do Puppet::Parser::Scope.new(compiler) end def with_file_content(content) path = tmpfile('file-function') file = File.new(path, 'wb') file.sync = true file.print content yield path end it "should read a file" do with_file_content('file content') do |name| expect(scope.function_file([name])).to eq("file content") end end it "should read a file keeping line endings intact" do with_file_content("file content\r\n") do |name| expect(scope.function_file([name])).to eq("file content\r\n") end end it "should read a file from a module path" do with_file_content('file content') do |name| mod = mock 'module' mod.stubs(:file).with('myfile').returns(name) compiler.environment.stubs(:module).with('mymod').returns(mod) expect(scope.function_file(['mymod/myfile'])).to eq('file content') end end it "should return the first file if given two files with absolute paths" do with_file_content('one') do |one| with_file_content('two') do |two| expect(scope.function_file([one, two])).to eq("one") end end end it "should return the first file if given two files with module paths" do with_file_content('one') do |one| with_file_content('two') do |two| mod = mock 'module' compiler.environment.expects(:module).with('mymod').returns(mod) mod.expects(:file).with('one').returns(one) mod.stubs(:file).with('two').returns(two) expect(scope.function_file(['mymod/one','mymod/two'])).to eq('one') end end end it "should return the first file if given two files with mixed paths, absolute first" do with_file_content('one') do |one| with_file_content('two') do |two| mod = mock 'module' compiler.environment.stubs(:module).with('mymod').returns(mod) mod.stubs(:file).with('two').returns(two) expect(scope.function_file([one,'mymod/two'])).to eq('one') end end end it "should return the first file if given two files with mixed paths, module first" do with_file_content('one') do |one| with_file_content('two') do |two| mod = mock 'module' compiler.environment.expects(:module).with('mymod').returns(mod) mod.stubs(:file).with('two').returns(two) expect(scope.function_file(['mymod/two',one])).to eq('two') end end end it "should not fail when some files are absent" do expect { with_file_content('one') do |one| expect(scope.function_file([make_absolute("/should-not-exist"), one])).to eq('one') end }.to_not raise_error end it "should fail when all files are absent" do expect { scope.function_file([File.expand_path('one')]) }.to raise_error(Puppet::ParseError, /Could not find any files/) end end puppet-5.5.10/spec/unit/parser/functions/generate_spec.rb0000644005276200011600000001055713417161722023371 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe "the generate function" do include PuppetSpec::Files before :all do Puppet::Parser::Functions.autoloader.loadall end let :node do Puppet::Node.new('localhost') end let :compiler do Puppet::Parser::Compiler.new(node) end let :scope do Puppet::Parser::Scope.new(compiler) end it "should exist" do expect(Puppet::Parser::Functions.function("generate")).to eq("function_generate") end it "accept a fully-qualified path as a command" do command = File.expand_path('/command/foo') Dir.expects(:chdir).with(File.dirname(command)).returns("yay") expect(scope.function_generate([command])).to eq("yay") end it "should not accept a relative path as a command" do expect { scope.function_generate(["command"]) }.to raise_error(Puppet::ParseError) end it "should not accept a command containing illegal characters" do expect { scope.function_generate([File.expand_path('/##/command')]) }.to raise_error(Puppet::ParseError) end it "should not accept a command containing spaces" do expect { scope.function_generate([File.expand_path('/com mand')]) }.to raise_error(Puppet::ParseError) end it "should not accept a command containing '..'" do command = File.expand_path("/command/../") expect { scope.function_generate([command]) }.to raise_error(Puppet::ParseError) end it "should execute the generate script with the correct working directory" do command = File.expand_path("/command") Dir.expects(:chdir).with(File.dirname(command)).returns("yay") expect(scope.function_generate([command])).to eq('yay') end describe "on Windows", :if => Puppet.features.microsoft_windows? do it "should accept the tilde in the path" do command = "C:/DOCUME~1/ADMINI~1/foo.bat" Dir.expects(:chdir).with(File.dirname(command)).returns("yay") expect(scope.function_generate([command])).to eq('yay') end it "should accept lower-case drive letters" do command = 'd:/command/foo' Dir.expects(:chdir).with(File.dirname(command)).returns("yay") expect(scope.function_generate([command])).to eq('yay') end it "should accept upper-case drive letters" do command = 'D:/command/foo' Dir.expects(:chdir).with(File.dirname(command)).returns("yay") expect(scope.function_generate([command])).to eq('yay') end it "should accept forward and backslashes in the path" do command = 'D:\command/foo\bar' Dir.expects(:chdir).with(File.dirname(command)).returns("yay") expect(scope.function_generate([command])).to eq('yay') end it "should reject colons when not part of the drive letter" do expect { scope.function_generate(['C:/com:mand']) }.to raise_error(Puppet::ParseError) end it "should reject root drives" do expect { scope.function_generate(['C:/']) }.to raise_error(Puppet::ParseError) end end describe "on POSIX", :if => Puppet.features.posix? do it "should reject backslashes" do expect { scope.function_generate(['/com\\mand']) }.to raise_error(Puppet::ParseError) end it "should accept plus and dash" do command = "/var/folders/9z/9zXImgchH8CZJh6SgiqS2U+++TM/-Tmp-/foo" Dir.expects(:chdir).with(File.dirname(command)).returns("yay") expect(scope.function_generate([command])).to eq('yay') end end let :command do script_containing('function_generate', :windows => '@echo off' + "\n" + 'echo a-%1 b-%2', :posix => '#!/bin/sh' + "\n" + 'echo a-$1 b-$2') end after :each do File.delete(command) if Puppet::FileSystem.exist?(command) end it "returns the output as a String" do expect(scope.function_generate([command]).class).to eq(String) end it "should call generator with no arguments" do expect(scope.function_generate([command])).to eq("a- b-\n") end it "should call generator with one argument" do expect(scope.function_generate([command, 'one'])).to eq("a-one b-\n") end it "should call generator with wo arguments" do expect(scope.function_generate([command, 'one', 'two'])).to eq("a-one b-two\n") end it "should fail if generator is not absolute" do expect { scope.function_generate(['boo']) }.to raise_error(Puppet::ParseError) end it "should fail if generator fails" do expect { scope.function_generate(['/boo']) }.to raise_error(Puppet::ParseError) end end puppet-5.5.10/spec/unit/parser/functions/inline_template_spec.rb0000644005276200011600000000253413417161722024744 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe "the inline_template function" do before :all do Puppet::Parser::Functions.autoloader.loadall end let(:node) { Puppet::Node.new('localhost') } let(:compiler) { Puppet::Parser::Compiler.new(node) } let(:scope) { Puppet::Parser::Scope.new(compiler) } it "should concatenate template wrapper outputs for multiple templates" do expect(inline_template("template1", "template2")).to eq("template1template2") end it "should raise an error if the template raises an error" do expect { inline_template("<% raise 'error' %>") }.to raise_error(Puppet::ParseError) end it "is not interfered with by a variable called 'string' (#14093)" do scope['string'] = "this is a variable" expect(inline_template("this is a template")).to eq("this is a template") end it "has access to a variable called 'string' (#14093)" do scope['string'] = "this is a variable" expect(inline_template("string was: <%= @string %>")).to eq("string was: this is a variable") end it 'is not available when --tasks is on' do Puppet[:tasks] = true expect { inline_template("<%= lookupvar('myvar') %>") }.to raise_error(Puppet::ParseError, /is only available when compiling a catalog/) end def inline_template(*templates) scope.function_inline_template(templates) end end puppet-5.5.10/spec/unit/parser/functions/regsubst_spec.rb0000644005276200011600000000112513417161722023424 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe "the regsubst function" do before :all do Puppet::Parser::Functions.autoloader.loadall end before :each do node = Puppet::Node.new('localhost') compiler = Puppet::Parser::Compiler.new(node) @scope = Puppet::Parser::Scope.new(compiler) end it 'should raise an ParseError' do expect do @scope.function_regsubst( [ 'the monkey breaks banana trees', 'b[an]*a', 'coconut' ]) end.to raise_error(Puppet::ParseError, /can only be called using the 4.x function API/) end end puppet-5.5.10/spec/unit/parser/functions/scanf_spec.rb0000644005276200011600000000147713417161722022672 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe "the scanf function" do before :all do Puppet::Parser::Functions.autoloader.loadall end let(:node) { Puppet::Node.new('localhost') } let(:compiler) { Puppet::Parser::Compiler.new(node) } let(:scope) { Puppet::Parser::Scope.new(compiler) } it 'scans a value and returns an array' do expect(scope.function_scanf(['42', '%i'])[0] == 42) end it 'returns empty array if nothing was scanned' do expect(scope.function_scanf(['no', '%i']) == []) end it 'produces result up to first unsuccessful scan' do expect(scope.function_scanf(['42 no', '%i'])[0] == 42) end it 'errors when not given enough arguments' do expect do scope.function_scanf(['42']) end.to raise_error(/.*scanf\(\): Wrong number of arguments given/m) end end puppet-5.5.10/spec/unit/parser/functions/split_spec.rb0000644005276200011600000000100213417161722022713 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe "the split function" do before :all do Puppet::Parser::Functions.autoloader.loadall end before :each do node = Puppet::Node.new('localhost') compiler = Puppet::Parser::Compiler.new(node) @scope = Puppet::Parser::Scope.new(compiler) end it 'should raise a ParseError' do expect { @scope.function_split([ '130;236;254;10', ';']) }.to raise_error(Puppet::ParseError, /can only be called using the 4.x function API/) end end puppet-5.5.10/spec/unit/parser/functions/sprintf_spec.rb0000644005276200011600000000436613417161722023265 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe "the sprintf function" do before :all do Puppet::Parser::Functions.autoloader.loadall end before :each do node = Puppet::Node.new('localhost') compiler = Puppet::Parser::Compiler.new(node) @scope = Puppet::Parser::Scope.new(compiler) end it "should exist" do expect(Puppet::Parser::Functions.function("sprintf")).to eq("function_sprintf") end it "should raise an ArgumentError if there is less than 1 argument" do expect { @scope.function_sprintf([]) }.to( raise_error(ArgumentError)) end it "should format integers" do result = @scope.function_sprintf(["%+05d", "23"]) expect(result).to(eql("+0023")) end it "should format floats" do result = @scope.function_sprintf(["%+.2f", "2.7182818284590451"]) expect(result).to(eql("+2.72")) end it "should format large floats" do result = @scope.function_sprintf(["%+.2e", "27182818284590451"]) str = "+2.72e+16" expect(result).to(eql(str)) end it "should perform more complex formatting" do result = @scope.function_sprintf( [ "<%.8s:%#5o %#8X (%-8s)>", "overlongstring", "23", "48879", "foo" ]) expect(result).to(eql("")) end it 'does not attempt to mutate its arguments' do args = ['%d', 1].freeze expect { @scope.function_sprintf(args) }.to_not raise_error end it 'support named arguments in a hash with string keys' do result = @scope.function_sprintf(["%d : %f", {'foo' => 1, 'bar' => 2}]) expect(result).to eq("1 : 2.000000") end it 'raises a key error if a key is not present' do expect do @scope.function_sprintf(["%d : %f", {'foo' => 1, 'bar' => 2}]) end.to raise_error(KeyError, /key not found/) end it 'a hash with string keys that is output formats as strings' do result = @scope.function_sprintf(["%s", {'foo' => 1, 'bar' => 2}]) expect(result).to eq("{\"foo\"=>1, \"bar\"=>2}") end it 'named arguments hash with non string keys are tolerated' do result = @scope.function_sprintf(["%d : %f", {'foo' => 1, 'bar' => 2, 1 => 2, [1] => 2, false => true, {} => {}}]) expect(result).to eq("1 : 2.000000") end end puppet-5.5.10/spec/unit/parser/functions/tag_spec.rb0000644005276200011600000000170513417161722022345 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe "the 'tag' function" do before :all do Puppet::Parser::Functions.autoloader.loadall end before :each do node = Puppet::Node.new('localhost') compiler = Puppet::Parser::Compiler.new(node) @scope = Puppet::Parser::Scope.new(compiler) end it "should exist" do expect(Puppet::Parser::Functions.function(:tag)).to eq("function_tag") end it "should tag the resource with any provided tags" do resource = Puppet::Parser::Resource.new(:file, "/file", :scope => @scope) @scope.expects(:resource).returns resource @scope.function_tag ["one", "two"] expect(resource).to be_tagged("one") expect(resource).to be_tagged("two") end it 'is not available when --tasks is on' do Puppet[:tasks] = true expect do @scope.function_tag(['one', 'two']) end.to raise_error(Puppet::ParseError, /is only available when compiling a catalog/) end end puppet-5.5.10/spec/unit/parser/functions/tagged_spec.rb0000644005276200011600000000226613417161722023030 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe "the 'tagged' function" do before :all do Puppet::Parser::Functions.autoloader.loadall end before :each do node = Puppet::Node.new('localhost') compiler = Puppet::Parser::Compiler.new(node) @scope = Puppet::Parser::Scope.new(compiler) end it "should exist" do expect(Puppet::Parser::Functions.function(:tagged)).to eq("function_tagged") end it 'is not available when --tasks is on' do Puppet[:tasks] = true expect do @scope.function_tagged(['one', 'two']) end.to raise_error(Puppet::ParseError, /is only available when compiling a catalog/) end it 'should be case-insensitive' do resource = Puppet::Parser::Resource.new(:file, "/file", :scope => @scope) @scope.stubs(:resource).returns resource @scope.function_tag ["one"] expect(@scope.function_tagged(['One'])).to eq(true) end it 'should check if all specified tags are included' do resource = Puppet::Parser::Resource.new(:file, "/file", :scope => @scope) @scope.stubs(:resource).returns resource @scope.function_tag ["one"] expect(@scope.function_tagged(['one', 'two'])).to eq(false) end end puppet-5.5.10/spec/unit/parser/functions/template_spec.rb0000644005276200011600000000645013417161722023407 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe "the template function" do before :all do Puppet::Parser::Functions.autoloader.loadall end let :node do Puppet::Node.new('localhost') end let :compiler do Puppet::Parser::Compiler.new(node) end let :scope do Puppet::Parser::Scope.new(compiler) end it "concatenates outputs for multiple templates" do tw1 = stub_everything "template_wrapper1" tw2 = stub_everything "template_wrapper2" Puppet::Parser::TemplateWrapper.stubs(:new).returns(tw1,tw2) tw1.stubs(:file=).with("1") tw2.stubs(:file=).with("2") tw1.stubs(:result).returns("result1") tw2.stubs(:result).returns("result2") expect(scope.function_template(["1","2"])).to eq("result1result2") end it "raises an error if the template raises an error" do tw = stub_everything 'template_wrapper' Puppet::Parser::TemplateWrapper.stubs(:new).returns(tw) tw.stubs(:result).raises expect { scope.function_template(["1"]) }.to raise_error(Puppet::ParseError, /Failed to parse template/) end context "when accessing scope variables via method calls (deprecated)" do it "raises an error when accessing an undefined variable" do expect { eval_template("template <%= deprecated %>") }.to raise_error(Puppet::ParseError, /undefined local variable or method `deprecated'/) end it "looks up the value from the scope" do scope["deprecated"] = "deprecated value" expect { eval_template("template <%= deprecated %>")}.to raise_error(/undefined local variable or method `deprecated'/) end it "still has access to Kernel methods" do expect { eval_template("<%= binding %>") }.to_not raise_error end end context "when accessing scope variables as instance variables" do it "has access to values" do scope['scope_var'] = "value" expect(eval_template("<%= @scope_var %>")).to eq("value") end it "get nil accessing a variable that does not exist" do expect(eval_template("<%= @not_defined.nil? %>")).to eq("true") end it "get nil accessing a variable that is undef" do scope['undef_var'] = :undef expect(eval_template("<%= @undef_var.nil? %>")).to eq("true") end end it "is not interfered with by having a variable named 'string' (#14093)" do scope['string'] = "this output should not be seen" expect(eval_template("some text that is static")).to eq("some text that is static") end it "has access to a variable named 'string' (#14093)" do scope['string'] = "the string value" expect(eval_template("string was: <%= @string %>")).to eq("string was: the string value") end it "does not have direct access to Scope#lookupvar" do expect { eval_template("<%= lookupvar('myvar') %>") }.to raise_error(Puppet::ParseError, /undefined method `lookupvar'/) end it 'is not available when --tasks is on' do Puppet[:tasks] = true expect { eval_template("<%= lookupvar('myvar') %>") }.to raise_error(Puppet::ParseError, /is only available when compiling a catalog/) end def eval_template(content) Puppet::FileSystem.stubs(:read_preserve_line_endings).with("template").returns(content) Puppet::Parser::Files.stubs(:find_template).returns("template") scope.function_template(['template']) end end puppet-5.5.10/spec/unit/parser/functions/versioncmp_spec.rb0000644005276200011600000000175013417161722023757 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe "the versioncmp function" do before :all do Puppet::Parser::Functions.autoloader.loadall end before :each do node = Puppet::Node.new('localhost') compiler = Puppet::Parser::Compiler.new(node) @scope = Puppet::Parser::Scope.new(compiler) end it "should exist" do expect(Puppet::Parser::Functions.function("versioncmp")).to eq("function_versioncmp") end it "should raise an ArgumentError if there is less than 2 arguments" do expect { @scope.function_versioncmp(["1.2"]) }.to raise_error(ArgumentError) end it "should raise an ArgumentError if there is more than 2 arguments" do expect { @scope.function_versioncmp(["1.2", "2.4.5", "3.5.6"]) }.to raise_error(ArgumentError) end it "should call Puppet::Util::Package.versioncmp (included in scope)" do Puppet::Util::Package.expects(:versioncmp).with("1.2", "1.3").returns(-1) @scope.function_versioncmp(["1.2", "1.3"]) end end puppet-5.5.10/spec/unit/parser/relationship_spec.rb0000644005276200011600000000542413417161721022264 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/parser/relationship' describe Puppet::Parser::Relationship do before do @source = Puppet::Resource.new(:mytype, "source") @target = Puppet::Resource.new(:mytype, "target") @extra_resource = Puppet::Resource.new(:mytype, "extra") @extra_resource2 = Puppet::Resource.new(:mytype, "extra2") @dep = Puppet::Parser::Relationship.new(@source, @target, :relationship) end describe "when evaluating" do before do @catalog = Puppet::Resource::Catalog.new @catalog.add_resource(@source) @catalog.add_resource(@target) @catalog.add_resource(@extra_resource) @catalog.add_resource(@extra_resource2) end it "should fail if the source resource cannot be found" do @catalog = Puppet::Resource::Catalog.new @catalog.add_resource @target expect { @dep.evaluate(@catalog) }.to raise_error(ArgumentError) end it "should fail if the target resource cannot be found" do @catalog = Puppet::Resource::Catalog.new @catalog.add_resource @source expect { @dep.evaluate(@catalog) }.to raise_error(ArgumentError) end it "should add the target as a 'before' value if the type is 'relationship'" do @dep.type = :relationship @dep.evaluate(@catalog) expect(@source[:before]).to be_include("Mytype[target]") end it "should add the target as a 'notify' value if the type is 'subscription'" do @dep.type = :subscription @dep.evaluate(@catalog) expect(@source[:notify]).to be_include("Mytype[target]") end it "should supplement rather than clobber existing relationship values" do @source[:before] = "File[/bar]" @dep.evaluate(@catalog) # this test did not work before. It was appending the resources # together as a string expect(@source[:before].class == Array).to be_truthy expect(@source[:before]).to be_include("Mytype[target]") expect(@source[:before]).to be_include("File[/bar]") end it "should supplement rather than clobber existing resource relationships" do @source[:before] = @extra_resource @dep.evaluate(@catalog) expect(@source[:before].class == Array).to be_truthy expect(@source[:before]).to be_include("Mytype[target]") expect(@source[:before]).to be_include(@extra_resource) end it "should supplement rather than clobber multiple existing resource relationships" do @source[:before] = [@extra_resource, @extra_resource2] @dep.evaluate(@catalog) expect(@source[:before].class == Array).to be_truthy expect(@source[:before]).to be_include("Mytype[target]") expect(@source[:before]).to be_include(@extra_resource) expect(@source[:before]).to be_include(@extra_resource2) end end end puppet-5.5.10/spec/unit/parser/resource/0000755005276200011600000000000013417162176020053 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/parser/resource/param_spec.rb0000644005276200011600000000177013417161722022513 0ustar jenkinsjenkinsrequire 'spec_helper' describe Puppet::Parser::Resource::Param do it "has readers for all of the attributes" do param = Puppet::Parser::Resource::Param.new(:name => 'myparam', :value => 'foo', :file => 'foo.pp', :line => 42) expect(param.name).to eq(:myparam) expect(param.value).to eq('foo') expect(param.file).to eq('foo.pp') expect(param.line).to eq(42) end context "parameter validation" do it "throws an error when instantiated without a name" do expect { Puppet::Parser::Resource::Param.new(:value => 'foo') }.to raise_error(Puppet::Error, /name is a required option/) end it "does not require a value" do param = Puppet::Parser::Resource::Param.new(:name => 'myparam') expect(param.value).to be_nil end it "includes file/line context in errors" do expect { Puppet::Parser::Resource::Param.new(:file => 'foo.pp', :line => 42) }.to raise_error(Puppet::Error, /\(file: foo.pp, line: 42\)/) end end end puppet-5.5.10/spec/unit/parser/resource_spec.rb0000644005276200011600000005655513417161721021425 0ustar jenkinsjenkinsrequire 'spec_helper' describe Puppet::Parser::Resource do before do environment = Puppet::Node::Environment.create(:testing, []) @node = Puppet::Node.new("yaynode", :environment => environment) @known_resource_types = environment.known_resource_types @compiler = Puppet::Parser::Compiler.new(@node) @source = newclass "" @scope = @compiler.topscope end def mkresource(args = {}) args[:source] ||= @source args[:scope] ||= @scope params = args[:parameters] || {:one => "yay", :three => "rah"} if args[:parameters] == :none args.delete(:parameters) elsif not args[:parameters].is_a? Array args[:parameters] = paramify(args[:source], params) end Puppet::Parser::Resource.new("resource", "testing", args) end def param(name, value, source) Puppet::Parser::Resource::Param.new(:name => name, :value => value, :source => source) end def paramify(source, hash) hash.collect do |name, value| Puppet::Parser::Resource::Param.new( :name => name, :value => value, :source => source ) end end def newclass(name) @known_resource_types.add Puppet::Resource::Type.new(:hostclass, name) end def newdefine(name) @known_resource_types.add Puppet::Resource::Type.new(:definition, name) end def newnode(name) @known_resource_types.add Puppet::Resource::Type.new(:node, name) end it "should get its environment from its scope" do scope = stub 'scope', :source => stub("source") scope.expects(:environment).returns("foo").at_least_once scope.expects(:lookupdefaults).returns({}) expect(Puppet::Parser::Resource.new("file", "whatever", :scope => scope).environment).to eq("foo") end it "should use the scope's environment as its environment" do @scope.expects(:environment).returns("myenv").at_least_once expect(Puppet::Parser::Resource.new("file", "whatever", :scope => @scope).environment).to eq("myenv") end it "should be isomorphic if it is builtin and models an isomorphic type" do Puppet::Type.type(:file).expects(:isomorphic?).returns(true) @resource = expect(Puppet::Parser::Resource.new("file", "whatever", :scope => @scope, :source => @source).isomorphic?).to be_truthy end it "should not be isomorphic if it is builtin and models a non-isomorphic type" do Puppet::Type.type(:file).expects(:isomorphic?).returns(false) @resource = expect(Puppet::Parser::Resource.new("file", "whatever", :scope => @scope, :source => @source).isomorphic?).to be_falsey end it "should be isomorphic if it is not builtin" do newdefine "whatever" @resource = expect(Puppet::Parser::Resource.new("whatever", "whatever", :scope => @scope, :source => @source).isomorphic?).to be_truthy end it "should have an array-indexing method for retrieving parameter values" do @resource = mkresource expect(@resource[:one]).to eq("yay") end it "should use a Puppet::Resource for converting to a ral resource" do trans = mock 'resource', :to_ral => "yay" @resource = mkresource @resource.expects(:copy_as_resource).returns trans expect(@resource.to_ral).to eq("yay") end it "should be able to use the indexing operator to access parameters" do resource = Puppet::Parser::Resource.new("resource", "testing", :source => "source", :scope => @scope) resource["foo"] = "bar" expect(resource["foo"]).to eq("bar") end it "should return the title when asked for a parameter named 'title'" do expect(Puppet::Parser::Resource.new("resource", "testing", :source => @source, :scope => @scope)[:title]).to eq("testing") end describe "when initializing" do before do @arguments = {:scope => @scope} end it "should fail unless hash is specified" do expect { Puppet::Parser::Resource.new('file', '/my/file', nil) }.to raise_error(ArgumentError, /Resources require a hash as last argument/) end it "should set the reference correctly" do res = Puppet::Parser::Resource.new("resource", "testing", @arguments) expect(res.ref).to eq("Resource[testing]") end it "should be tagged with user tags" do tags = [ "tag1", "tag2" ] @arguments[:parameters] = [ param(:tag, tags , :source) ] res = Puppet::Parser::Resource.new("resource", "testing", @arguments) expect(res).to be_tagged("tag1") expect(res).to be_tagged("tag2") end end describe "when evaluating" do before do @catalog = Puppet::Resource::Catalog.new source = stub('source') source.stubs(:module_name) @scope = Puppet::Parser::Scope.new(@compiler, :source => source) @catalog.add_resource(Puppet::Parser::Resource.new("stage", :main, :scope => @scope)) end it "should evaluate the associated AST definition" do definition = newdefine "mydefine" res = Puppet::Parser::Resource.new("mydefine", "whatever", :scope => @scope, :source => @source, :catalog => @catalog) definition.expects(:evaluate_code).with(res) res.evaluate end it "should evaluate the associated AST class" do @class = newclass "myclass" res = Puppet::Parser::Resource.new("class", "myclass", :scope => @scope, :source => @source, :catalog => @catalog) @class.expects(:evaluate_code).with(res) res.evaluate end it "should evaluate the associated AST node" do nodedef = newnode("mynode") res = Puppet::Parser::Resource.new("node", "mynode", :scope => @scope, :source => @source, :catalog => @catalog) nodedef.expects(:evaluate_code).with(res) res.evaluate end it "should add an edge to any specified stage for class resources" do @compiler.environment.known_resource_types.add Puppet::Resource::Type.new(:hostclass, "foo", {}) other_stage = Puppet::Parser::Resource.new(:stage, "other", :scope => @scope, :catalog => @catalog) @compiler.add_resource(@scope, other_stage) resource = Puppet::Parser::Resource.new(:class, "foo", :scope => @scope, :catalog => @catalog) resource[:stage] = 'other' @compiler.add_resource(@scope, resource) resource.evaluate expect(@compiler.catalog.edge?(other_stage, resource)).to be_truthy end it "should fail if an unknown stage is specified" do @compiler.environment.known_resource_types.add Puppet::Resource::Type.new(:hostclass, "foo", {}) resource = Puppet::Parser::Resource.new(:class, "foo", :scope => @scope, :catalog => @catalog) resource[:stage] = 'other' expect { resource.evaluate }.to raise_error(ArgumentError, /Could not find stage other specified by/) end it "should add edges from the class resources to the parent's stage if no stage is specified" do foo_stage = Puppet::Parser::Resource.new(:stage, :foo_stage, :scope => @scope, :catalog => @catalog) @compiler.add_resource(@scope, foo_stage) @compiler.environment.known_resource_types.add Puppet::Resource::Type.new(:hostclass, "foo", {}) resource = Puppet::Parser::Resource.new(:class, "foo", :scope => @scope, :catalog => @catalog) resource[:stage] = 'foo_stage' @compiler.add_resource(@scope, resource) resource.evaluate expect(@compiler.catalog).to be_edge(foo_stage, resource) end it 'should allow a resource reference to be undef' do Puppet[:code] = "notify { 'hello': message=>'yo', notify => undef }" catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new 'anyone') edges = catalog.edges.map {|e| [e.source.ref, e.target.ref]} expect(edges).to include(['Class[main]', 'Notify[hello]']) end it 'should evaluate class in the same file without include' do Puppet[:code] = <<-MANIFEST class a($myvar = 'hello') {} class { 'a': myvar => 'goodbye' } notify { $a::myvar: } MANIFEST catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new 'anyone') expect(catalog.resource('Notify[goodbye]')).to be_a(Puppet::Resource) end it "should allow edges to propagate multiple levels down the scope hierarchy" do Puppet[:code] = <<-MANIFEST stage { before: before => Stage[main] } class alpha { include beta } class beta { include gamma } class gamma { } class { alpha: stage => before } MANIFEST catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new 'anyone') # Stringify them to make for easier lookup edges = catalog.edges.map {|e| [e.source.ref, e.target.ref]} expect(edges).to include(["Stage[before]", "Class[Alpha]"]) expect(edges).to include(["Stage[before]", "Class[Beta]"]) expect(edges).to include(["Stage[before]", "Class[Gamma]"]) end it "should use the specified stage even if the parent scope specifies one" do Puppet[:code] = <<-MANIFEST stage { before: before => Stage[main], } stage { after: require => Stage[main], } class alpha { class { beta: stage => after } } class beta { } class { alpha: stage => before } MANIFEST catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new 'anyone') edges = catalog.edges.map {|e| [e.source.ref, e.target.ref]} expect(edges).to include(["Stage[before]", "Class[Alpha]"]) expect(edges).to include(["Stage[after]", "Class[Beta]"]) end it "should add edges from top-level class resources to the main stage if no stage is specified" do main = @compiler.catalog.resource(:stage, :main) @compiler.environment.known_resource_types.add Puppet::Resource::Type.new(:hostclass, "foo", {}) resource = Puppet::Parser::Resource.new(:class, "foo", :scope => @scope, :catalog => @catalog) @compiler.add_resource(@scope, resource) resource.evaluate expect(@compiler.catalog).to be_edge(main, resource) end it 'should assign default value to generated resource' do Puppet[:code] = <<-PUPPET define one($var) { notify { "${var} says hello": } } define two($x = $title) { One { var => $x } one { a: } one { b: var => 'bill'} } two { 'bob': } PUPPET catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new 'anyone') edges = catalog.edges.map {|e| [e.source.ref, e.target.ref]} expect(edges).to include(['One[a]', 'Notify[bob says hello]']) expect(edges).to include(['One[b]', 'Notify[bill says hello]']) end it 'should override default value with new value' do Puppet[:code] = <<-PUPPET.unindent class foo { File { ensure => file, mode => '644', owner => 'root', group => 'root', } file { '/tmp/foo': ensure => directory } File['/tmp/foo'] { mode => '0755' } } include foo PUPPET catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new 'anyone') file = catalog.resource('File[/tmp/foo]') expect(file).to be_a(Puppet::Resource) expect(file['mode']).to eql('0755') end end describe 'when evaluating resource defaults' do let(:resource) { Puppet::Parser::Resource.new('file', 'whatever', :scope => @scope, :source => @source) } it 'should add all defaults available from the scope' do @scope.expects(:lookupdefaults).with('File').returns(:owner => param(:owner, 'default', @source)) expect(resource[:owner]).to eq('default') end it 'should not replace existing parameters with defaults' do @scope.expects(:lookupdefaults).with('File').returns(:owner => param(:owner, 'replaced', @source)) r = Puppet::Parser::Resource.new('file', 'whatever', :scope => @scope, :source => @source, :parameters => [ param(:owner, 'oldvalue', @source) ]) expect(r[:owner]).to eq('oldvalue') end it 'should override defaults with new parameters' do @scope.expects(:lookupdefaults).with('File').returns(:owner => param(:owner, 'replaced', @source)) resource.set_parameter(:owner, 'newvalue') expect(resource[:owner]).to eq('newvalue') end it 'should add a copy of each default, rather than the actual default parameter instance' do newparam = param(:owner, 'default', @source) other = newparam.dup other.value = "other" newparam.expects(:dup).returns(other) @scope.expects(:lookupdefaults).with('File').returns(:owner => newparam) expect(resource[:owner]).to eq('other') end it "should tag with value of default parameter named 'tag'" do @scope.expects(:lookupdefaults).with('File').returns(:tag => param(:tag, 'the_tag', @source)) expect(resource.tags).to include('the_tag') end end describe "when finishing" do before do @resource = Puppet::Parser::Resource.new("file", "whatever", :scope => @scope, :source => @source) end it "should do nothing if it has already been finished" do @resource.finish @resource.expects(:add_scope_tags).never @resource.finish end it "converts parameters with Sensitive values to unwrapped values and metadata" do @resource[:content] = Puppet::Pops::Types::PSensitiveType::Sensitive.new("hunter2") @resource.finish expect(@resource[:content]).to eq "hunter2" expect(@resource.sensitive_parameters).to eq [:content] end end describe "when being tagged" do before do @scope_resource = stub 'scope_resource', :tags => %w{srone srtwo} @scope.stubs(:resource).returns @scope_resource @resource = Puppet::Parser::Resource.new("file", "yay", :scope => @scope, :source => mock('source')) end it "should get tagged with the resource type" do expect(@resource.tags).to be_include("file") end it "should get tagged with the title" do expect(@resource.tags).to be_include("yay") end it "should get tagged with each name in the title if the title is a qualified class name" do resource = Puppet::Parser::Resource.new("file", "one::two", :scope => @scope, :source => mock('source')) expect(resource.tags).to be_include("one") expect(resource.tags).to be_include("two") end it "should get tagged with each name in the type if the type is a qualified class name" do resource = Puppet::Parser::Resource.new("one::two", "whatever", :scope => @scope, :source => mock('source')) expect(resource.tags).to be_include("one") expect(resource.tags).to be_include("two") end it "should not get tagged with non-alphanumeric titles" do resource = Puppet::Parser::Resource.new("file", "this is a test", :scope => @scope, :source => mock('source')) expect(resource.tags).not_to be_include("this is a test") end it "should fail on tags containing '*' characters" do expect { @resource.tag("bad*tag") }.to raise_error(Puppet::ParseError) end it "should fail on tags starting with '-' characters" do expect { @resource.tag("-badtag") }.to raise_error(Puppet::ParseError) end it "should fail on tags containing ' ' characters" do expect { @resource.tag("bad tag") }.to raise_error(Puppet::ParseError) end it "should allow alpha tags" do expect { @resource.tag("good_tag") }.to_not raise_error end end describe "when merging overrides" do before do @source = "source1" @resource = mkresource :source => @source @override = mkresource :source => @source end it "should fail when the override was not created by a parent class" do @override.source = "source2" @override.source.expects(:child_of?).with("source1").returns(false) expect { @resource.merge(@override) }.to raise_error(Puppet::ParseError) end it "should succeed when the override was created in the current scope" do @resource.source = "source3" @override.source = @resource.source @override.source.expects(:child_of?).with("source3").never params = {:a => :b, :c => :d} @override.expects(:parameters).returns(params) @resource.expects(:override_parameter).with(:b) @resource.expects(:override_parameter).with(:d) @resource.merge(@override) end it "should succeed when a parent class created the override" do @resource.source = "source3" @override.source = "source4" @override.source.expects(:child_of?).with("source3").returns(true) params = {:a => :b, :c => :d} @override.expects(:parameters).returns(params) @resource.expects(:override_parameter).with(:b) @resource.expects(:override_parameter).with(:d) @resource.merge(@override) end it "should add new parameters when the parameter is not set" do @source.stubs(:child_of?).returns true @override.set_parameter(:testing, "value") @resource.merge(@override) expect(@resource[:testing]).to eq("value") end it "should replace existing parameter values" do @source.stubs(:child_of?).returns true @resource.set_parameter(:testing, "old") @override.set_parameter(:testing, "value") @resource.merge(@override) expect(@resource[:testing]).to eq("value") end it "should add values to the parameter when the override was created with the '+>' syntax" do @source.stubs(:child_of?).returns true param = Puppet::Parser::Resource::Param.new(:name => :testing, :value => "testing", :source => @resource.source) param.add = true @override.set_parameter(param) @resource.set_parameter(:testing, "other") @resource.merge(@override) expect(@resource[:testing]).to eq(%w{other testing}) end it "should not merge parameter values when multiple resources are overriden with '+>' at once " do @resource_2 = mkresource :source => @source @resource. set_parameter(:testing, "old_val_1") @resource_2.set_parameter(:testing, "old_val_2") @source.stubs(:child_of?).returns true param = Puppet::Parser::Resource::Param.new(:name => :testing, :value => "new_val", :source => @resource.source) param.add = true @override.set_parameter(param) @resource. merge(@override) @resource_2.merge(@override) expect(@resource [:testing]).to eq(%w{old_val_1 new_val}) expect(@resource_2[:testing]).to eq(%w{old_val_2 new_val}) end it "should promote tag overrides to real tags" do @source.stubs(:child_of?).returns true param = Puppet::Parser::Resource::Param.new(:name => :tag, :value => "testing", :source => @resource.source) @override.set_parameter(param) @resource.merge(@override) expect(@resource.tagged?("testing")).to be_truthy end end it "should be able to be converted to a normal resource" do @source = stub 'scope', :name => "myscope" @resource = mkresource :source => @source expect(@resource).to respond_to(:copy_as_resource) end describe "when being converted to a resource" do before do @parser_resource = mkresource :scope => @scope, :parameters => {:foo => "bar", :fee => "fum"} end it "should create an instance of Puppet::Resource" do expect(@parser_resource.copy_as_resource).to be_instance_of(Puppet::Resource) end it "should set the type correctly on the Puppet::Resource" do expect(@parser_resource.copy_as_resource.type).to eq(@parser_resource.type) end it "should set the title correctly on the Puppet::Resource" do expect(@parser_resource.copy_as_resource.title).to eq(@parser_resource.title) end it "should copy over all of the parameters" do result = @parser_resource.copy_as_resource.to_hash # The name will be in here, also. expect(result[:foo]).to eq("bar") expect(result[:fee]).to eq("fum") end it "should copy over the tags" do @parser_resource.tag "foo" @parser_resource.tag "bar" expect(@parser_resource.copy_as_resource.tags).to eq(@parser_resource.tags) end it "should copy over the line" do @parser_resource.line = 40 expect(@parser_resource.copy_as_resource.line).to eq(40) end it "should copy over the file" do @parser_resource.file = "/my/file" expect(@parser_resource.copy_as_resource.file).to eq("/my/file") end it "should copy over the 'exported' value" do @parser_resource.exported = true expect(@parser_resource.copy_as_resource.exported).to be_truthy end it "should copy over the 'virtual' value" do @parser_resource.virtual = true expect(@parser_resource.copy_as_resource.virtual).to be_truthy end it "should convert any parser resource references to Puppet::Resource instances" do ref = Puppet::Resource.new("file", "/my/file") @parser_resource = mkresource :source => @source, :parameters => {:foo => "bar", :fee => ref} result = @parser_resource.copy_as_resource expect(result[:fee]).to eq(Puppet::Resource.new(:file, "/my/file")) end it "should convert any parser resource references to Puppet::Resource instances even if they are in an array" do ref = Puppet::Resource.new("file", "/my/file") @parser_resource = mkresource :source => @source, :parameters => {:foo => "bar", :fee => ["a", ref]} result = @parser_resource.copy_as_resource expect(result[:fee]).to eq(["a", Puppet::Resource.new(:file, "/my/file")]) end it "should convert any parser resource references to Puppet::Resource instances even if they are in an array of array, and even deeper" do ref1 = Puppet::Resource.new("file", "/my/file1") ref2 = Puppet::Resource.new("file", "/my/file2") @parser_resource = mkresource :source => @source, :parameters => {:foo => "bar", :fee => ["a", [ref1,ref2]]} result = @parser_resource.copy_as_resource expect(result[:fee]).to eq(["a", Puppet::Resource.new(:file, "/my/file1"), Puppet::Resource.new(:file, "/my/file2")]) end it "should fail if the same param is declared twice" do expect do @parser_resource = mkresource :source => @source, :parameters => [ Puppet::Parser::Resource::Param.new( :name => :foo, :value => "bar", :source => @source ), Puppet::Parser::Resource::Param.new( :name => :foo, :value => "baz", :source => @source ) ] end.to raise_error(Puppet::ParseError) end end describe "when setting parameters" do before do @source = newclass "foobar" @resource = Puppet::Parser::Resource.new :foo, "bar", :scope => @scope, :source => @source end it "should accept Param instances and add them to the parameter list" do param = Puppet::Parser::Resource::Param.new :name => "foo", :value => "bar", :source => @source @resource.set_parameter(param) expect(@resource["foo"]).to eq("bar") end it "should allow parameters to be set to 'false'" do @resource.set_parameter("myparam", false) expect(@resource["myparam"]).to be_falsey end it "should use its source when provided a parameter name and value" do @resource.set_parameter("myparam", "myvalue") expect(@resource["myparam"]).to eq("myvalue") end end # part of #629 -- the undef keyword. Make sure 'undef' params get skipped. it "should not include 'undef' parameters when converting itself to a hash" do resource = Puppet::Parser::Resource.new "file", "/tmp/testing", :source => mock("source"), :scope => @scope resource[:owner] = :undef resource[:mode] = "755" expect(resource.to_hash[:owner]).to be_nil end end puppet-5.5.10/spec/unit/parser/scope_spec.rb0000644005276200011600000005745113417161721020703 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/compiler' require 'puppet_spec/scope' describe Puppet::Parser::Scope do include PuppetSpec::Scope before :each do @scope = Puppet::Parser::Scope.new( Puppet::Parser::Compiler.new(Puppet::Node.new("foo")) ) @scope.source = Puppet::Resource::Type.new(:node, :foo) @topscope = @scope.compiler.topscope @scope.parent = @topscope end describe "create_test_scope_for_node" do let(:node_name) { "node_name_foo" } let(:scope) { create_test_scope_for_node(node_name) } it "should be a kind of Scope" do expect(scope).to be_a_kind_of(Puppet::Parser::Scope) end it "should set the source to a node resource" do expect(scope.source).to be_a_kind_of(Puppet::Resource::Type) end it "should have a compiler" do expect(scope.compiler).to be_a_kind_of(Puppet::Parser::Compiler) end it "should set the parent to the compiler topscope" do expect(scope.parent).to be(scope.compiler.topscope) end end it "should generate a simple string when inspecting a scope" do expect(@scope.inspect).to eq("Scope()") end it "should generate a simple string when inspecting a scope with a resource" do @scope.resource="foo::bar" expect(@scope.inspect).to eq("Scope(foo::bar)") end it "should generate a path if there is one on the puppet stack" do result = Puppet::Pops::PuppetStack.stack('/tmp/kansas.pp', 42, @scope, 'inspect', []) expect(result).to eq("Scope(/tmp/kansas.pp, 42)") end it "should generate an shortened path if path points into the environment" do env_path = @scope.environment.configuration.path_to_env mocked_path = File.join(env_path, 'oz.pp') result = Puppet::Pops::PuppetStack.stack(mocked_path, 42, @scope, 'inspect', []) expect(result).to eq("Scope(/oz.pp, 42)") end it "should generate a shortened path if path points into a module" do mocked_path = File.join(@scope.environment.full_modulepath[0], 'mymodule', 'oz.pp') result = Puppet::Pops::PuppetStack.stack(mocked_path, 42, @scope, 'inspect', []) expect(result).to eq("Scope(/mymodule/oz.pp, 42)") end it "should return a scope for use in a test harness" do expect(create_test_scope_for_node("node_name_foo")).to be_a_kind_of(Puppet::Parser::Scope) end it "should be able to retrieve class scopes by name" do @scope.class_set "myname", "myscope" expect(@scope.class_scope("myname")).to eq("myscope") end it "should be able to retrieve class scopes by object" do klass = mock 'ast_class' klass.expects(:name).returns("myname") @scope.class_set "myname", "myscope" expect(@scope.class_scope(klass)).to eq("myscope") end it "should be able to retrieve its parent module name from the source of its parent type" do @topscope.source = Puppet::Resource::Type.new(:hostclass, :foo, :module_name => "foo") expect(@scope.parent_module_name).to eq("foo") end it "should return a nil parent module name if it has no parent" do expect(@topscope.parent_module_name).to be_nil end it "should return a nil parent module name if its parent has no source" do expect(@scope.parent_module_name).to be_nil end it "should get its environment from its compiler" do env = Puppet::Node::Environment.create(:testing, []) compiler = stub 'compiler', :environment => env, :is_a? => true scope = Puppet::Parser::Scope.new(compiler) expect(scope.environment).to equal(env) end it "should fail if no compiler is supplied" do expect { Puppet::Parser::Scope.new }.to raise_error(ArgumentError, /wrong number of arguments/) end it "should fail if something that isn't a compiler is supplied" do expect { Puppet::Parser::Scope.new(:compiler => true) }.to raise_error(Puppet::DevError, /you must pass a compiler instance/) end describe "when custom functions are called" do let(:env) { Puppet::Node::Environment.create(:testing, []) } let(:compiler) { Puppet::Parser::Compiler.new(Puppet::Node.new('foo', :environment => env)) } let(:scope) { Puppet::Parser::Scope.new(compiler) } it "calls methods prefixed with function_ as custom functions" do expect(scope.function_sprintf(["%b", 123])).to eq("1111011") end it "raises an error when arguments are not passed in an Array" do expect do scope.function_sprintf("%b", 123) end.to raise_error ArgumentError, /custom functions must be called with a single array that contains the arguments/ end it "raises an error on subsequent calls when arguments are not passed in an Array" do scope.function_sprintf(["first call"]) expect do scope.function_sprintf("%b", 123) end.to raise_error ArgumentError, /custom functions must be called with a single array that contains the arguments/ end it "raises NoMethodError when the not prefixed" do expect { scope.sprintf(["%b", 123]) }.to raise_error(NoMethodError) end it "raises NoMethodError when prefixed with function_ but it doesn't exist" do expect { scope.function_fake_bs(['cows']) }.to raise_error(NoMethodError) end end describe "when initializing" do it "should extend itself with its environment's Functions module as well as the default" do env = Puppet::Node::Environment.create(:myenv, []) root = Puppet.lookup(:root_environment) compiler = stub 'compiler', :environment => env, :is_a? => true scope = Puppet::Parser::Scope.new(compiler) expect(scope.singleton_class.ancestors).to be_include(Puppet::Parser::Functions.environment_module(env)) expect(scope.singleton_class.ancestors).to be_include(Puppet::Parser::Functions.environment_module(root)) end it "should extend itself with the default Functions module if its environment is the default" do root = Puppet.lookup(:root_environment) node = Puppet::Node.new('localhost') compiler = Puppet::Parser::Compiler.new(node) scope = Puppet::Parser::Scope.new(compiler) expect(scope.singleton_class.ancestors).to be_include(Puppet::Parser::Functions.environment_module(root)) end end describe "when looking up a variable" do it "should support :lookupvar and :setvar for backward compatibility" do @scope.setvar("var", "yep") expect(@scope.lookupvar("var")).to eq("yep") end it "should fail if invoked with a non-string name" do expect { @scope[:foo] }.to raise_error(Puppet::ParseError, /Scope variable name .* not a string/) expect { @scope[:foo] = 12 }.to raise_error(Puppet::ParseError, /Scope variable name .* not a string/) end it "should return nil for unset variables when --strict variables is not in effect" do expect(@scope["var"]).to be_nil end it "answers exist? with boolean false for non existing variables" do expect(@scope.exist?("var")).to be(false) end it "answers exist? with boolean false for non existing variables" do @scope["var"] = "yep" expect(@scope.exist?("var")).to be(true) end it "should be able to look up values" do @scope["var"] = "yep" expect(@scope["var"]).to eq("yep") end it "should be able to look up hashes" do @scope["var"] = {"a" => "b"} expect(@scope["var"]).to eq({"a" => "b"}) end it "should be able to look up variables in parent scopes" do @topscope["var"] = "parentval" expect(@scope["var"]).to eq("parentval") end it "should prefer its own values to parent values" do @topscope["var"] = "parentval" @scope["var"] = "childval" expect(@scope["var"]).to eq("childval") end it "should be able to detect when variables are set" do @scope["var"] = "childval" expect(@scope).to be_include("var") end it "does not allow changing a set value" do @scope["var"] = "childval" expect { @scope["var"] = "change" }.to raise_error(Puppet::Error, "Cannot reassign variable '$var'") end it "should be able to detect when variables are not set" do expect(@scope).not_to be_include("var") end it "warns and return nil for non found unqualified variable" do Puppet.expects(:warn_once) expect(@scope["santa_clause"]).to be_nil end it "warns once for a non found variable" do Puppet.expects(:send_log).with(:warning, is_a(String)).once expect([@scope["santa_claus"],@scope["santa_claus"]]).to eq([nil, nil]) end it "warns and return nil for non found qualified variable" do Puppet.expects(:warn_once) expect(@scope["north_pole::santa_clause"]).to be_nil end it "does not warn when a numeric variable is missing - they always exist" do Puppet.expects(:warn_once).never expect(@scope["1"]).to be_nil end describe "and the variable is qualified" do before :each do @known_resource_types = @scope.environment.known_resource_types node = Puppet::Node.new('localhost') @compiler = Puppet::Parser::Compiler.new(node) end def newclass(name) @known_resource_types.add Puppet::Resource::Type.new(:hostclass, name) end def create_class_scope(name) klass = newclass(name) catalog = Puppet::Resource::Catalog.new catalog.add_resource(Puppet::Parser::Resource.new("stage", :main, :scope => Puppet::Parser::Scope.new(@compiler))) Puppet::Parser::Resource.new("class", name, :scope => @scope, :source => mock('source'), :catalog => catalog).evaluate @scope.class_scope(klass) end it "should be able to look up explicitly fully qualified variables from compiler's top scope" do Puppet.expects(:deprecation_warning).never other_scope = @scope.compiler.topscope other_scope["othervar"] = "otherval" expect(@scope["::othervar"]).to eq("otherval") end it "should be able to look up explicitly fully qualified variables from other scopes" do Puppet.expects(:deprecation_warning).never other_scope = create_class_scope("other") other_scope["var"] = "otherval" expect(@scope["::other::var"]).to eq("otherval") end it "should be able to look up deeply qualified variables" do Puppet.expects(:deprecation_warning).never other_scope = create_class_scope("other::deep::klass") other_scope["var"] = "otherval" expect(@scope["other::deep::klass::var"]).to eq("otherval") end it "should return nil for qualified variables that cannot be found in other classes" do create_class_scope("other::deep::klass") expect(@scope["other::deep::klass::var"]).to be_nil end it "should warn and return nil for qualified variables whose classes have not been evaluated" do newclass("other::deep::klass") Puppet.expects(:warn_once) expect(@scope["other::deep::klass::var"]).to be_nil end it "should warn and return nil for qualified variables whose classes do not exist" do Puppet.expects(:warn_once) expect(@scope["other::deep::klass::var"]).to be_nil end it "should return nil when asked for a non-string qualified variable from a class that does not exist" do expect(@scope["other::deep::klass::var"]).to be_nil end it "should return nil when asked for a non-string qualified variable from a class that has not been evaluated" do @scope.stubs(:warning) newclass("other::deep::klass") expect(@scope["other::deep::klass::var"]).to be_nil end end context "and strict_variables is true" do before(:each) do Puppet[:strict_variables] = true end it "should throw a symbol when unknown variable is looked up" do expect { @scope['john_doe'] }.to throw_symbol(:undefined_variable) end it "should throw a symbol when unknown qualified variable is looked up" do expect { @scope['nowhere::john_doe'] }.to throw_symbol(:undefined_variable) end it "should not raise an error when built in variable is looked up" do expect { @scope['caller_module_name'] }.to_not raise_error expect { @scope['module_name'] }.to_not raise_error end end context "and strict_variables is false and --strict=off" do before(:each) do Puppet[:strict_variables] = false Puppet[:strict] = :off end it "should not error when unknown variable is looked up and produce nil" do expect(@scope['john_doe']).to be_nil end it "should not error when unknown qualified variable is looked up and produce nil" do expect(@scope['nowhere::john_doe']).to be_nil end end context "and strict_variables is false and --strict=warning" do before(:each) do Puppet[:strict_variables] = false Puppet[:strict] = :warning end it "should not error when unknown variable is looked up" do expect(@scope['john_doe']).to be_nil end it "should not error when unknown qualified variable is looked up" do expect(@scope['nowhere::john_doe']).to be_nil end end context "and strict_variables is false and --strict=error" do before(:each) do Puppet[:strict_variables] = false Puppet[:strict] = :error end it "should raise error when unknown variable is looked up" do expect { @scope['john_doe'] }.to raise_error(/Undefined variable/) end it "should not throw a symbol when unknown qualified variable is looked up" do expect { @scope['nowhere::john_doe'] }.to raise_error(/Undefined variable/) end end end describe "when calling number?" do it "should return nil if called with anything not a number" do expect(Puppet::Parser::Scope.number?([2])).to be_nil end it "should return a Integer for an Integer" do expect(Puppet::Parser::Scope.number?(2)).to be_a(Integer) end it "should return a Float for a Float" do expect(Puppet::Parser::Scope.number?(2.34)).to be_an_instance_of(Float) end it "should return 234 for '234'" do expect(Puppet::Parser::Scope.number?("234")).to eq(234) end it "should return nil for 'not a number'" do expect(Puppet::Parser::Scope.number?("not a number")).to be_nil end it "should return 23.4 for '23.4'" do expect(Puppet::Parser::Scope.number?("23.4")).to eq(23.4) end it "should return 23.4e13 for '23.4e13'" do expect(Puppet::Parser::Scope.number?("23.4e13")).to eq(23.4e13) end it "should understand negative numbers" do expect(Puppet::Parser::Scope.number?("-234")).to eq(-234) end it "should know how to convert exponential float numbers ala '23e13'" do expect(Puppet::Parser::Scope.number?("23e13")).to eq(23e13) end it "should understand hexadecimal numbers" do expect(Puppet::Parser::Scope.number?("0x234")).to eq(0x234) end it "should understand octal numbers" do expect(Puppet::Parser::Scope.number?("0755")).to eq(0755) end it "should return nil on malformed integers" do expect(Puppet::Parser::Scope.number?("0.24.5")).to be_nil end it "should convert strings with leading 0 to integer if they are not octal" do expect(Puppet::Parser::Scope.number?("0788")).to eq(788) end it "should convert strings of negative integers" do expect(Puppet::Parser::Scope.number?("-0788")).to eq(-788) end it "should return nil on malformed hexadecimal numbers" do expect(Puppet::Parser::Scope.number?("0x89g")).to be_nil end end describe "when using ephemeral variables" do it "should store the variable value" do @scope.set_match_data({1 => :value}) expect(@scope["1"]).to eq(:value) end it "should raise an error when setting numerical variable" do expect { @scope.setvar("1", :value3, :ephemeral => true) }.to raise_error(Puppet::ParseError, /Cannot assign to a numeric match result variable/) end describe "with more than one level" do it "should prefer latest ephemeral scopes" do @scope.set_match_data({0 => :earliest}) @scope.new_ephemeral @scope.set_match_data({0 => :latest}) expect(@scope["0"]).to eq(:latest) end it "should be able to report the current level" do expect(@scope.ephemeral_level).to eq(1) @scope.new_ephemeral expect(@scope.ephemeral_level).to eq(2) end it "should not check presence of an ephemeral variable across multiple levels" do @scope.new_ephemeral @scope.set_match_data({1 => :value1}) @scope.new_ephemeral @scope.set_match_data({0 => :value2}) @scope.new_ephemeral expect(@scope.include?("1")).to be_falsey end it "should return false when an ephemeral variable doesn't exist in any ephemeral scope" do @scope.new_ephemeral @scope.set_match_data({1 => :value1}) @scope.new_ephemeral @scope.set_match_data({0 => :value2}) @scope.new_ephemeral expect(@scope.include?("2")).to be_falsey end it "should not get ephemeral values from earlier scope when not in later" do @scope.set_match_data({1 => :value1}) @scope.new_ephemeral @scope.set_match_data({0 => :value2}) expect(@scope.include?("1")).to be_falsey end describe "when using a guarded scope" do it "should remove ephemeral scopes up to this level" do @scope.set_match_data({1 => :value1}) @scope.new_ephemeral @scope.set_match_data({1 => :value2}) @scope.with_guarded_scope do @scope.new_ephemeral @scope.set_match_data({1 => :value3}) end expect(@scope["1"]).to eq(:value2) end end end end context "when using ephemeral as local scope" do it "should store all variables in local scope" do @scope.new_ephemeral true @scope.setvar("apple", :fruit) expect(@scope["apple"]).to eq(:fruit) end it 'should store an undef in local scope and let it override parent scope' do @scope['cloaked'] = 'Cloak me please' @scope.new_ephemeral(true) @scope['cloaked'] = nil expect(@scope['cloaked']).to eq(nil) end it "should be created from a hash" do @scope.ephemeral_from({ "apple" => :fruit, "strawberry" => :berry}) expect(@scope["apple"]).to eq(:fruit) expect(@scope["strawberry"]).to eq(:berry) end end describe "when setting ephemeral vars from matches" do before :each do @match = stub 'match', :is_a? => true @match.stubs(:[]).with(0).returns("this is a string") @match.stubs(:captures).returns([]) @scope.stubs(:setvar) end it "should accept only MatchData" do expect { @scope.ephemeral_from("match") }.to raise_error(ArgumentError, /Invalid regex match data/) end it "should set $0 with the full match" do # This is an internal impl detail test @scope.expects(:new_match_scope).with { |*arg| arg[0][0] == "this is a string" } @scope.ephemeral_from(@match) end it "should set every capture as ephemeral var" do # This is an internal impl detail test @match.stubs(:[]).with(1).returns(:capture1) @match.stubs(:[]).with(2).returns(:capture2) @scope.expects(:new_match_scope).with { |*arg| arg[0][1] == :capture1 && arg[0][2] == :capture2 } @scope.ephemeral_from(@match) end it "should shadow previous match variables" do # This is an internal impl detail test @match.stubs(:[]).with(1).returns(:capture1) @match.stubs(:[]).with(2).returns(:capture2) @match2 = stub 'match', :is_a? => true @match2.stubs(:[]).with(1).returns(:capture2_1) @match2.stubs(:[]).with(2).returns(nil) @scope.ephemeral_from(@match) @scope.ephemeral_from(@match2) expect(@scope.lookupvar('2')).to eq(nil) end it "should create a new ephemeral level" do level_before = @scope.ephemeral_level @scope.ephemeral_from(@match) expect(level_before < @scope.ephemeral_level) end end describe "when managing defaults" do it "should be able to set and lookup defaults" do param = Puppet::Parser::Resource::Param.new(:name => :myparam, :value => "myvalue", :source => stub("source")) @scope.define_settings(:mytype, param) expect(@scope.lookupdefaults(:mytype)).to eq({:myparam => param}) end it "should fail if a default is already defined and a new default is being defined" do param = Puppet::Parser::Resource::Param.new(:name => :myparam, :value => "myvalue", :source => stub("source")) @scope.define_settings(:mytype, param) expect { @scope.define_settings(:mytype, param) }.to raise_error(Puppet::ParseError, /Default already defined .* cannot redefine/) end it "should return multiple defaults at once" do param1 = Puppet::Parser::Resource::Param.new(:name => :myparam, :value => "myvalue", :source => stub("source")) @scope.define_settings(:mytype, param1) param2 = Puppet::Parser::Resource::Param.new(:name => :other, :value => "myvalue", :source => stub("source")) @scope.define_settings(:mytype, param2) expect(@scope.lookupdefaults(:mytype)).to eq({:myparam => param1, :other => param2}) end it "should look up defaults defined in parent scopes" do param1 = Puppet::Parser::Resource::Param.new(:name => :myparam, :value => "myvalue", :source => stub("source")) @scope.define_settings(:mytype, param1) child_scope = @scope.newscope param2 = Puppet::Parser::Resource::Param.new(:name => :other, :value => "myvalue", :source => stub("source")) child_scope.define_settings(:mytype, param2) expect(child_scope.lookupdefaults(:mytype)).to eq({:myparam => param1, :other => param2}) end end context "#true?" do { "a string" => true, "true" => true, "false" => true, true => true, "" => false, :undef => false, nil => false }.each do |input, output| it "should treat #{input.inspect} as #{output}" do expect(Puppet::Parser::Scope.true?(input)).to eq(output) end end end context "when producing a hash of all variables (as used in templates)" do it "should contain all defined variables in the scope" do @scope.setvar("orange", :tangerine) @scope.setvar("pear", :green) expect(@scope.to_hash).to eq({'orange' => :tangerine, 'pear' => :green }) end it "should contain variables in all local scopes (#21508)" do @scope.new_ephemeral true @scope.setvar("orange", :tangerine) @scope.setvar("pear", :green) @scope.new_ephemeral true @scope.setvar("apple", :red) expect(@scope.to_hash).to eq({'orange' => :tangerine, 'pear' => :green, 'apple' => :red }) end it "should contain all defined variables in the scope and all local scopes" do @scope.setvar("orange", :tangerine) @scope.setvar("pear", :green) @scope.new_ephemeral true @scope.setvar("apple", :red) expect(@scope.to_hash).to eq({'orange' => :tangerine, 'pear' => :green, 'apple' => :red }) end it "should not contain varaibles in match scopes (non local emphemeral)" do @scope.new_ephemeral true @scope.setvar("orange", :tangerine) @scope.setvar("pear", :green) @scope.ephemeral_from(/(f)(o)(o)/.match('foo')) expect(@scope.to_hash).to eq({'orange' => :tangerine, 'pear' => :green }) end it "should delete values that are :undef in inner scope" do @scope.new_ephemeral true @scope.setvar("orange", :tangerine) @scope.setvar("pear", :green) @scope.new_ephemeral true @scope.setvar("apple", :red) @scope.setvar("orange", :undef) expect(@scope.to_hash).to eq({'pear' => :green, 'apple' => :red }) end end end puppet-5.5.10/spec/unit/parser/templatewrapper_spec.rb0000644005276200011600000000661513417161721023002 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/parser/templatewrapper' describe Puppet::Parser::TemplateWrapper do let(:known_resource_types) { Puppet::Resource::TypeCollection.new("env") } let(:scope) do compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("mynode")) compiler.environment.stubs(:known_resource_types).returns known_resource_types Puppet::Parser::Scope.new compiler end let(:tw) { Puppet::Parser::TemplateWrapper.new(scope) } it "fails if a template cannot be found" do Puppet::Parser::Files.expects(:find_template).returns nil expect { tw.file = "fake_template" }.to raise_error(Puppet::ParseError) end it "stringifies as template[] for a file based template" do Puppet::Parser::Files.stubs(:find_template).returns("/tmp/fake_template") tw.file = "fake_template" expect(tw.to_s).to eql("template[/tmp/fake_template]") end it "stringifies as template[inline] for a string-based template" do expect(tw.to_s).to eql("template[inline]") end it "reads and evaluates a file-based template" do given_a_template_file("fake_template", "template contents") tw.file = "fake_template" expect(tw.result).to eql("template contents") end it "provides access to the name of the template via #file" do full_file_name = given_a_template_file("fake_template", "<%= file %>") tw.file = "fake_template" expect(tw.result).to eq(full_file_name) end it "evaluates a given string as a template" do expect(tw.result("template contents")).to eql("template contents") end it "provides the defined classes with #classes" do catalog = mock 'catalog', :classes => ["class1", "class2"] scope.expects(:catalog).returns( catalog ) expect(tw.classes).to eq(["class1", "class2"]) end it "provides all the tags with #all_tags" do catalog = mock 'catalog', :tags => ["tag1", "tag2"] scope.expects(:catalog).returns( catalog ) expect(tw.all_tags).to eq(["tag1","tag2"]) end it "provides the tags defined in the current scope with #tags" do scope.expects(:tags).returns( ["tag1", "tag2"] ) expect(tw.tags).to eq(["tag1","tag2"]) end it "raises error on access to removed in-scope variables via method calls" do scope["in_scope_variable"] = "is good" expect { tw.result("<%= in_scope_variable %>") }.to raise_error(/undefined local variable or method `in_scope_variable'/ ) end it "reports that variable is available when it is in scope" do scope["in_scope_variable"] = "is good" expect(tw.result("<%= has_variable?('in_scope_variable') %>")).to eq("true") end it "reports that a variable is not available when it is not in scope" do expect(tw.result("<%= has_variable?('not_in_scope_variable') %>")).to eq("false") end it "provides access to in-scope variables via instance variables" do scope["one"] = "foo" expect(tw.result("<%= @one %>")).to eq("foo") end %w{! . ; :}.each do |badchar| it "translates #{badchar} to _ in instance variables" do scope["one#{badchar}"] = "foo" expect(tw.result("<%= @one_ %>")).to eq("foo") end end def given_a_template_file(name, contents) full_name = "/full/path/to/#{name}" Puppet::Parser::Files.stubs(:find_template). with(name, anything()). returns(full_name) Puppet::FileSystem.stubs(:read_preserve_line_endings).with(full_name).returns(contents) full_name end end puppet-5.5.10/spec/unit/parser/type_loader_spec.rb0000644005276200011600000001654113417161721022074 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/parser/type_loader' require 'puppet/parser/parser_factory' require 'puppet_spec/modules' require 'puppet_spec/files' describe Puppet::Parser::TypeLoader do include PuppetSpec::Modules include PuppetSpec::Files let(:empty_hostclass) { Puppet::Parser::AST::Hostclass.new('') } let(:loader) { Puppet::Parser::TypeLoader.new(:myenv) } let(:my_env) { Puppet::Node::Environment.create(:myenv, []) } around do |example| envs = Puppet::Environments::Static.new(my_env) Puppet.override(:environments => envs) do example.run end end it "should support an environment" do loader = Puppet::Parser::TypeLoader.new(:myenv) expect(loader.environment.name).to eq(:myenv) end it "should delegate its known resource types to its environment" do expect(loader.known_resource_types).to be_instance_of(Puppet::Resource::TypeCollection) end describe "when loading names from namespaces" do it "should do nothing if the name to import is an empty string" do expect(loader.try_load_fqname(:hostclass, "")).to be_nil end it "should attempt to import each generated name" do loader.expects(:import_from_modules).with("foo/bar").returns([]) loader.expects(:import_from_modules).with("foo").returns([]) loader.try_load_fqname(:hostclass, "foo::bar") end it "should attempt to load each possible name going from most to least specific" do path_order = sequence('path') ['foo/bar/baz', 'foo/bar', 'foo'].each do |path| Puppet::Parser::Files.expects(:find_manifests_in_modules).with(path, anything).returns([nil, []]).in_sequence(path_order) end loader.try_load_fqname(:hostclass, 'foo::bar::baz') end end describe "when importing" do let(:stub_parser) { stub 'Parser', :file= => nil, :parse => empty_hostclass } before(:each) do Puppet::Parser::ParserFactory.stubs(:parser).with(anything).returns(stub_parser) end it "should find all manifests matching the file or pattern" do Puppet::Parser::Files.expects(:find_manifests_in_modules).with("myfile", anything).returns ["modname", %w{one}] loader.import("myfile", "/path") end it "should pass the environment when looking for files" do Puppet::Parser::Files.expects(:find_manifests_in_modules).with(anything, loader.environment).returns ["modname", %w{one}] loader.import("myfile", "/path") end it "should fail if no files are found" do Puppet::Parser::Files.expects(:find_manifests_in_modules).returns [nil, []] expect { loader.import("myfile", "/path") }.to raise_error(/No file\(s\) found for import/) end it "should parse each found file" do Puppet::Parser::Files.expects(:find_manifests_in_modules).returns ["modname", [make_absolute("/one")]] loader.expects(:parse_file).with(make_absolute("/one")).returns(Puppet::Parser::AST::Hostclass.new('')) loader.import("myfile", "/path") end it "should not attempt to import files that have already been imported" do loader = Puppet::Parser::TypeLoader.new(:myenv) Puppet::Parser::Files.expects(:find_manifests_in_modules).twice.returns ["modname", %w{/one}] expect(loader.import("myfile", "/path")).not_to be_empty expect(loader.import("myfile", "/path")).to be_empty end end describe "when importing all" do let(:base) { tmpdir("base") } let(:modulebase1) { File.join(base, "first") } let(:modulebase2) { File.join(base, "second") } let(:my_env) { Puppet::Node::Environment.create(:myenv, [modulebase1, modulebase2]) } before do # Create two module path directories FileUtils.mkdir_p(modulebase1) FileUtils.mkdir_p(modulebase2) end def mk_module(basedir, name) PuppetSpec::Modules.create(name, basedir) end # We have to pass the base path so that we can # write to modules that are in the second search path def mk_manifests(base, mod, files) files.collect do |file| name = mod.name + "::" + file.gsub("/", "::") path = File.join(base, mod.name, "manifests", file + ".pp") FileUtils.mkdir_p(File.split(path)[0]) # write out the class File.open(path, "w") { |f| f.print "class #{name} {}" } name end end it "should load all puppet manifests from all modules in the specified environment" do module1 = mk_module(modulebase1, "one") module2 = mk_module(modulebase2, "two") mk_manifests(modulebase1, module1, %w{a b}) mk_manifests(modulebase2, module2, %w{c d}) loader.import_all expect(loader.environment.known_resource_types.hostclass("one::a")).to be_instance_of(Puppet::Resource::Type) expect(loader.environment.known_resource_types.hostclass("one::b")).to be_instance_of(Puppet::Resource::Type) expect(loader.environment.known_resource_types.hostclass("two::c")).to be_instance_of(Puppet::Resource::Type) expect(loader.environment.known_resource_types.hostclass("two::d")).to be_instance_of(Puppet::Resource::Type) end it "should not load manifests from duplicate modules later in the module path" do module1 = mk_module(modulebase1, "one") # duplicate module2 = mk_module(modulebase2, "one") mk_manifests(modulebase1, module1, %w{a}) mk_manifests(modulebase2, module2, %w{c}) loader.import_all expect(loader.environment.known_resource_types.hostclass("one::c")).to be_nil end it "should load manifests from subdirectories" do module1 = mk_module(modulebase1, "one") mk_manifests(modulebase1, module1, %w{a a/b a/b/c}) loader.import_all expect(loader.environment.known_resource_types.hostclass("one::a::b")).to be_instance_of(Puppet::Resource::Type) expect(loader.environment.known_resource_types.hostclass("one::a::b::c")).to be_instance_of(Puppet::Resource::Type) end it "should skip modules that don't have manifests" do mk_module(modulebase1, "one") module2 = mk_module(modulebase2, "two") mk_manifests(modulebase2, module2, %w{c d}) loader.import_all expect(loader.environment.known_resource_types.hostclass("one::a")).to be_nil expect(loader.environment.known_resource_types.hostclass("two::c")).to be_instance_of(Puppet::Resource::Type) expect(loader.environment.known_resource_types.hostclass("two::d")).to be_instance_of(Puppet::Resource::Type) end end describe "when parsing a file" do it "requests a new parser instance for each file" do parser = stub 'Parser', :file= => nil, :parse => empty_hostclass Puppet::Parser::ParserFactory.expects(:parser).twice.returns(parser) loader.parse_file("/my/file") loader.parse_file("/my/other_file") end it "assigns the parser its file and then parses" do parser = mock 'parser' Puppet::Parser::ParserFactory.expects(:parser).returns(parser) parser.expects(:file=).with("/my/file") parser.expects(:parse).returns(empty_hostclass) loader.parse_file("/my/file") end end it "should be able to add classes to the current resource type collection" do file = tmpfile("simple_file.pp") File.open(file, "w") { |f| f.puts "class foo {}" } loader.import(File.basename(file), File.dirname(file)) expect(loader.known_resource_types.hostclass("foo")).to be_instance_of(Puppet::Resource::Type) end end puppet-5.5.10/spec/unit/parser/environment_compiler_spec.rb0000644005276200011600000004757513417161722024037 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/compiler' require 'puppet/parser/environment_compiler' describe "Application instantiation" do include PuppetSpec::Compiler let(:env) { Puppet::Node::Environment.create(:testing, []) } let(:node) { Puppet::Node.new('test', :environment => env) } let(:loaders) { Puppet::Pops::Loaders.new(env) } let(:logs) { [] } let(:notices) { logs.select { |log| log.level == :notice }.map { |log| log.message } } let(:warnings) { logs.select { |log| log.level == :warning }.map { |log| log.message } } def compile_collect_log(string) Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do compile_to_catalog(string, Puppet::Node.new('other', :environment => env)) end end def compile_to_env_catalog(string, code_id=nil) Puppet[:code] = string Puppet::Parser::EnvironmentCompiler.compile(env, code_id).filter { |r| r.virtual? } end around :each do |example| Puppet::Parser::Compiler.any_instance.stubs(:loaders).returns(loaders) Puppet::Parser::EnvironmentCompiler.any_instance.stubs(:loaders).returns(loaders) Puppet.override(:loaders => loaders, :current_environment => env) do Puppet::Type.newtype :cap, :is_capability => true do newparam :name newparam :host end example.run Puppet::Type.rmtype(:cap) end end MANIFEST = <<-EOS define prod($host) { notify { "host ${host}":} } Prod produces Cap { } define cons($host) { notify { "host ${host}": } } Cons consumes Cap { } application app { prod { one: host => ahost, export => Cap[cap] } cons { two: host => ahost, consume => Cap[cap] } cons { three: consume => Cap[cap] } } site { app { anapp: nodes => { Node[first] => Prod[one], Node[second] => Cons[two] } } } EOS MANIFEST_WO_EXPORT = <<-EOS define prod($host) { notify { "host ${host}":} } Prod produces Cap { } define cons($host) { notify { "host ${host}": } } Cons consumes Cap { } application app { cons { two: host => ahost, consume => Cap[cap] } } site { app { anapp: nodes => { Node[first] => Prod[one], Node[second] => Cons[two] } } } EOS MANIFEST_WO_NODE = <<-EOS define prod($host) { notify { "host ${host}":} } Prod produces Cap { } define cons($host) { notify { "host ${host}": } } Cons consumes Cap { } application app { prod { one: host => ahost, export => Cap[cap] } cons { two: host => ahost, consume => Cap[cap] } } site { app { anapp: } } EOS MANIFEST_WITH_STRING_NODES = <<-EOS application app { } site { app { anapp: nodes => "foobar", } } EOS MANIFEST_WITH_FALSE_NODES = <<-EOS application app { } site { app { anapp: nodes => false, } } EOS MANIFEST_REQ_WO_EXPORT = <<-EOS define prod($host) { notify { "host ${host}":} } Prod produces Cap { } define cons($host) { notify { "host ${host}": } } Cons consumes Cap { } application app { cons { two: host => ahost, require => Cap[cap] } } site { app { anapp: nodes => { Node[first] => Prod[one], Node[second] => Cons[two] } } } EOS MANIFEST_WITH_DOUBLE_EXPORT = <<-EOS define prod($host) { notify { "host ${host}":} } Prod produces Cap { } define cons($host) { notify { "host ${host}": } } Cons consumes Cap { } application app { prod { one: host => ahost, export => Cap[cap] } prod { two: host => anotherhost, export => Cap[cap] } cons { two: host => ahost, consume => Cap[cap] } } site { app { anapp: nodes => { Node[first] => Prod[one], Node[second] => Cons[two] } } } EOS FAULTY_MANIFEST = <<-EOS define prod($host) { notify { "host ${host}":} } Prod produces Cap { } define cons($host) { notify { "host ${host}": } } Cons consumes Cap { } application app { prod { one: host => ahost, export => Cap[cap] } cons { two: host => ahost, consume => Cap[cap] } } # app is not in site => error app { anapp: nodes => { Node[first] => Prod[one], Node[second] => Cons[two] } } EOS MANIFEST_WITH_SITE = <<-EOS define prod($host) { notify { "host ${host}":} } Prod produces Cap { } define cons($host) { notify { "host ${host}": } } Cons consumes Cap { } application app { prod { one: host => ahost, export => Cap[cap] } cons { two: host => ahost, consume => Cap[cap] } } $one = not_the_value_one $two = two node default { notify { "on a node": } } notify { 'ignore me': } site { $one = one app { anapp: nodes => { Node[first] => Prod[$one], Node[second] => Cons[$two] } } } EOS MANIFEST_WITH_ILLEGAL_RESOURCE = <<-EOS define prod($host) { notify { "host ${host}":} } Prod produces Cap { } define cons($host) { notify { "host ${host}": } } Cons consumes Cap { } application app { prod { one: host => ahost, export => Cap[cap] } cons { two: consume => Cap[cap] } } site { # The rouge expression is here notify { 'fail me': } $one = one app { anapp: nodes => { Node[first] => Prod[one], Node[second] => Cons[two] } } } EOS MANIFEST_WITH_CLASS = <<-EOS define test($host) { notify { "c $host": } } class prod($host) { notify { "p $host": } } class cons($host) { test { c: host => $host } } Class[prod] produces Cap {} Class[cons] consumes Cap {} application app { class { prod: host => 'ahost', export => Cap[cap]} class { cons: consume => Cap[cap]} } site { app { anapp: nodes => { Node[first] => Class[prod], Node[second] => Class[cons] } } } EOS context 'a node catalog' do it "is unaffected for a non-participating node" do catalog = compile_to_catalog(MANIFEST, Puppet::Node.new('other', :environment => env)) types = catalog.resource_keys.map { |type, _| type }.uniq.sort expect(types).to eq(["Class", "Stage"]) end it "an application instance must be contained in a site" do expect { compile_to_catalog(FAULTY_MANIFEST, Puppet::Node.new('first', :environment => env)) }.to raise_error(/Application instances .* can only be contained within a Site/) end it "does not raise an error when node mappings are not provided" do expect { compile_to_catalog(MANIFEST_WO_NODE, node) }.to_not raise_error end it "raises an error if node mapping is a string" do expect { compile_to_catalog(MANIFEST_WITH_STRING_NODES, node) }.to raise_error(/Invalid node mapping in .*: Mapping must be a hash/) end it "raises an error if node mapping is false" do expect { compile_to_catalog(MANIFEST_WITH_FALSE_NODES, node) }.to raise_error(/Invalid node mapping in .*: Mapping must be a hash/) end it "detects that consumed capability is never exported" do expect { compile_to_env_catalog(MANIFEST_WO_EXPORT) }.to raise_error(/Capability 'Cap\[cap\]' referenced by 'consume' is never exported/) end it "detects that required capability is never exported" do expect { compile_to_env_catalog(MANIFEST_REQ_WO_EXPORT) }.to raise_error(/Capability 'Cap\[cap\]' referenced by 'require' is never exported/) end it "detects that a capability is exported more than once" do expect { compile_to_env_catalog(MANIFEST_WITH_DOUBLE_EXPORT) }.to raise_error(/'Cap\[cap\]' is exported by both 'Prod\[one\]' and 'Prod\[two\]'/) end context "for producing node" do let(:compiled_node) { Puppet::Node.new('first', :environment => env) } let(:compiled_catalog) { compile_to_catalog(MANIFEST, compiled_node)} { "App[anapp]" => 'application instance', "Cap[cap]" => 'capability resource', "Prod[one]" => 'component', "Notify[host ahost]" => 'node resource' }.each do |k,v| it "contains the #{v} (#{k})" do expect(compiled_catalog.resource(k)).not_to be_nil end end it "does not contain the consumed resource (Cons[two])" do expect(compiled_catalog.resource("Cons[two]")).to be_nil end end context "for consuming node" do let(:compiled_node) { Puppet::Node.new('second', :environment => env) } let(:compiled_catalog) { compile_to_catalog(MANIFEST, compiled_node)} let(:cap) { the_cap = Puppet::Resource.new("Cap", "cap") the_cap["host"] = "ahost" the_cap } { "App[anapp]" => 'application instance', "Cap[cap]" => 'capability resource', "Cons[two]" => 'component', "Notify[host ahost]" => 'node resource' }.each do |k,v| it "contains the #{v} (#{k})" do # Mock the connection to Puppet DB Puppet::Resource::CapabilityFinder.expects(:find).returns(cap) expect(compiled_catalog.resource(k)).not_to be_nil end end it "does not contain the produced resource (Prod[one])" do # Mock the connection to Puppet DB Puppet::Resource::CapabilityFinder.expects(:find).returns(cap) expect(compiled_catalog.resource("Prod[one]")).to be_nil end end context "for node with class producer" do let(:compiled_node) { Puppet::Node.new('first', :environment => env) } let(:compiled_catalog) { compile_to_catalog(MANIFEST_WITH_CLASS, compiled_node)} { "App[anapp]" => 'application instance', "Cap[cap]" => 'capability resource', "Class[prod]" => 'class', "Notify[p ahost]" => 'node resource' }.each do |k,v| it "contains the #{v} (#{k})" do cat = compiled_catalog expect(cat.resource(k)).not_to be_nil end end it "does not contain the consumed resource (Class[cons])" do expect(compiled_catalog.resource("Class[cons]")).to be_nil end end context "for node with class consumer" do let(:compiled_node) { Puppet::Node.new('second', :environment => env) } let(:compiled_catalog) { compile_to_catalog(MANIFEST_WITH_CLASS, compiled_node)} let(:cap) { the_cap = Puppet::Resource.new("Cap", "cap") the_cap["host"] = "ahost" the_cap } { "App[anapp]" => 'application instance', "Cap[cap]" => 'capability resource', "Class[cons]" => 'class', "Notify[c ahost]" => 'node resource' }.each do |k,v| it "contains the #{v} (#{k})" do # Mock the connection to Puppet DB Puppet::Resource::CapabilityFinder.expects(:find).returns(cap) expect(compiled_catalog.resource(k)).not_to be_nil end end it "does not contain the produced resource (Class[prod])" do # Mock the connection to Puppet DB Puppet::Resource::CapabilityFinder.expects(:find).returns(cap) expect(compiled_catalog.resource("Class[prod]")).to be_nil end end context "when using a site expression" do # The site expression must be evaluated in a node catalog compilation because # the application instantiations inside it may contain other logic (local variables) # that are used to instantiate an application. The application instances are needed. # it "the node expressions is evaluated" do catalog = compile_to_catalog(MANIFEST_WITH_SITE, Puppet::Node.new('other', :environment => env)) types = catalog.resource_keys.map { |type, _| type }.uniq.sort expect(types).to eq(["Class", "Node", "Notify", "Stage"]) expect(catalog.resource("Notify[on a node]")).to_not be_nil expect(catalog.resource("Notify[on the site]")).to be_nil end end context "when using a site expression" do it "the site expression is not evaluated in a node compilation" do catalog = compile_to_catalog(MANIFEST_WITH_SITE, Puppet::Node.new('other', :environment => env)) types = catalog.resource_keys.map { |type, _| type }.uniq.sort expect(types).to eq(["Class", "Node", "Notify", "Stage"]) expect(catalog.resource("Notify[on a node]")).to_not be_nil expect(catalog.resource("Notify[on the site]")).to be_nil end end end describe "in the environment catalog" do it "does not fail if there is no site expression" do expect { compile_to_env_catalog(<<-EOC).to_resource notify { 'ignore me':} EOC }.to_not raise_error() end it "ignores usage of hiera_include() at topscope for classification" do Puppet.expects(:debug).with(regexp_matches(/Ignoring hiera_include/)) expect { compile_to_env_catalog(<<-EOC).to_resource hiera_include('classes') site { } EOC }.to_not raise_error() end it 'removes overriden functions after compile' do expect { compile_to_env_catalog(<<-EOC) hiera_include('classes') site { } EOC }.to_not raise_error() func = Puppet::Pops::Loaders.loaders.puppet_system_loader.load(:function, 'hiera_include') expect(func).to be_a(Puppet::Functions::Function) end it "includes components and capability resources" do catalog = compile_to_env_catalog(MANIFEST).to_resource apps = catalog.resources.select do |res| res.resource_type && res.resource_type.application? end expect(apps.size).to eq(1) app = apps.first expect(app["nodes"]).not_to be_nil comps = catalog.direct_dependents_of(app).map(&:ref).sort expect(comps).to eq(["Cons[three]", "Cons[two]", "Prod[one]"]) prod = catalog.resource("Prod[one]") expect(prod).not_to be_nil expect(prod.export.map(&:ref)).to eq(["Cap[cap]"]) cons = catalog.resource("Cons[two]") expect(cons).not_to be_nil expect(cons[:consume].ref).to eq("Cap[cap]") end it "includes class components" do catalog = compile_to_env_catalog(MANIFEST_WITH_CLASS).to_resource classes = catalog.resources.select do |res| res.type == 'Class' && (res.title == 'Prod' || res.title == 'Cons') end expect(classes.size).to eq(2) expect(classes.map(&:ref).sort).to eq(["Class[Cons]", "Class[Prod]"]) prod = catalog.resource("Class[prod]") expect(prod).not_to be_nil expect(prod.export.map(&:ref)).to eq(["Cap[cap]"]) cons = catalog.resource("Class[cons]") expect(cons).not_to be_nil expect(cons[:consume].ref).to eq("Cap[cap]") end it "an application instance must be contained in a site" do expect { compile_to_env_catalog(FAULTY_MANIFEST) }.to raise_error(/Application instances .* can only be contained within a Site/) end context "when using a site expression" do it "includes components and capability resources" do catalog = compile_to_env_catalog(MANIFEST_WITH_SITE).to_resource apps = catalog.resources.select do |res| res.resource_type && res.resource_type.application? end expect(apps.size).to eq(1) app = apps.first expect(app["nodes"]).not_to be_nil comps = catalog.direct_dependents_of(app).map(&:ref).sort expect(comps).to eq(["Cons[two]", "Prod[one]"]) prod = catalog.resource("Prod[one]") expect(prod).not_to be_nil expect(prod.export.map(&:ref)).to eq(["Cap[cap]"]) cons = catalog.resource("Cons[two]") expect(cons).not_to be_nil expect(cons[:consume].ref).to eq("Cap[cap]") end it "the site expression is evaluated in an environment compilation" do catalog = compile_to_env_catalog(MANIFEST_WITH_SITE).to_resource types = catalog.resource_keys.map { |type, _| type }.uniq.sort expect(types).to eq(["App", "Class", "Cons", "Prod", "Site", "Stage"]) expect(catalog.resource("Notify[on a node]")).to be_nil apps = catalog.resources.select do |res| res.resource_type && res.resource_type.application? end expect(apps.size).to eq(1) app = apps.first comps = catalog.direct_dependents_of(app).map(&:ref).sort expect(comps).to eq(["Cons[two]", "Prod[one]"]) end it "fails if there are non component resources in the site" do expect { compile_to_env_catalog(MANIFEST_WITH_ILLEGAL_RESOURCE).to_resource }.to raise_error(/Only application components can appear inside a site - Notify\[fail me\] is not allowed \(line: 20\)/) end end it "includes code_id if specified" do catalog = compile_to_env_catalog(MANIFEST_WITH_SITE, "12345") expect(catalog.code_id).to eq("12345") end it "omits code_id if unspecified" do catalog = compile_to_env_catalog(MANIFEST_WITH_SITE) expect(catalog.code_id).to be_nil end end describe "when validation of nodes" do it 'validates that the key of a node mapping is a Node' do expect { compile_to_catalog(<<-EOS, Puppet::Node.new('other', :environment => env)) application app { } site { app { anapp: nodes => { 'hello' => Node[other], } } } EOS }.to raise_error(Puppet::Error, /hello is not a Node/) end it 'validates that the value of a node mapping is a resource' do expect { compile_to_catalog(<<-EOS, Puppet::Node.new('other', :environment => env)) application app { } site { app { anapp: nodes => { Node[other] => 'hello' } } } EOS }.to raise_error(Puppet::Error, /hello is not a resource/) end it 'validates that the value can be an array or resources' do expect { compile_to_catalog(<<-EOS, Puppet::Node.new('other', :environment => env)) define p { notify {$title:} } application app { p{one:} p{two:} } site { app { anapp: nodes => { Node[other] => [P[one],P[two]] } } } EOS }.not_to raise_error end it 'validates that the is bound to exactly one node' do expect { compile_to_catalog(<<-EOS, Puppet::Node.new('first', :environment => env)) define p { notify {$title:} } application app { p{one:} } site { app { anapp: nodes => { Node[first] => P[one], Node[second] => P[one], } } } EOS }.to raise_error(Puppet::Error, /maps component P\[one\] to multiple nodes/) end end describe "site containing a resource named 'plan'" do it 'finds an application named plan' do expect {compile_collect_log(<<-PUPPET)}.not_to raise_error define plan::node_file() { file { "/tmp/plans/${name}.txt": content => "this is ${name}.txt", } } Plan::Node_file produces Node_file {} application plan() { plan::node_file { "node_file_${name}": export => Node_file["node_file_${name}"] } } site { plan { "test": nodes => { Node["test.example.com"] => Plan::Node_file["node_file_plan_test"], } } } PUPPET expect(warnings).to include(/Use of future reserved word: 'plan'/) end end end puppet-5.5.10/spec/unit/parser/functions_spec.rb0000644005276200011600000001254213417161722021573 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Parser::Functions do def callable_functions_from(mod) Class.new { include mod }.new end let(:function_module) { Puppet::Parser::Functions.environment_module(Puppet.lookup(:current_environment)) } let(:environment) { Puppet::Node::Environment.create(:myenv, []) } before do Puppet::Parser::Functions.reset end it "should have a method for returning an environment-specific module" do expect(Puppet::Parser::Functions.environment_module(environment)).to be_instance_of(Module) end describe "when calling newfunction" do it "should create the function in the environment module" do Puppet::Parser::Functions.newfunction("name", :type => :rvalue) { |args| } expect(function_module).to be_method_defined :function_name end it "should warn if the function already exists" do Puppet::Parser::Functions.newfunction("name", :type => :rvalue) { |args| } Puppet.expects(:warning) Puppet::Parser::Functions.newfunction("name", :type => :rvalue) { |args| } end it "should raise an error if the function type is not correct" do expect { Puppet::Parser::Functions.newfunction("name", :type => :unknown) { |args| } }.to raise_error Puppet::DevError, "Invalid statement type :unknown" end it "instruments the function to profile the execution" do messages = [] Puppet::Util::Profiler.add_profiler(Puppet::Util::Profiler::WallClock.new(proc { |msg| messages << msg }, "id")) Puppet::Parser::Functions.newfunction("name", :type => :rvalue) { |args| } callable_functions_from(function_module).function_name([]) expect(messages.first).to match(/Called name/) end end describe "when calling function to test function existence" do it "should return false if the function doesn't exist" do Puppet::Parser::Functions.autoloader.stubs(:load) expect(Puppet::Parser::Functions.function("name")).to be_falsey end it "should return its name if the function exists" do Puppet::Parser::Functions.newfunction("name", :type => :rvalue) { |args| } expect(Puppet::Parser::Functions.function("name")).to eq("function_name") end it "should try to autoload the function if it doesn't exist yet" do Puppet::Parser::Functions.autoloader.expects(:load) Puppet::Parser::Functions.function("name") end it "combines functions from the root with those from the current environment" do Puppet.override(:current_environment => Puppet.lookup(:root_environment)) do Puppet::Parser::Functions.newfunction("onlyroot", :type => :rvalue) do |args| end end Puppet.override(:current_environment => Puppet::Node::Environment.create(:other, [])) do Puppet::Parser::Functions.newfunction("other_env", :type => :rvalue) do |args| end expect(Puppet::Parser::Functions.function("onlyroot")).to eq("function_onlyroot") expect(Puppet::Parser::Functions.function("other_env")).to eq("function_other_env") end expect(Puppet::Parser::Functions.function("other_env")).to be_falsey end end describe "when calling function to test arity" do let(:function_module) { Puppet::Parser::Functions.environment_module(Puppet.lookup(:current_environment)) } it "should raise an error if the function is called with too many arguments" do Puppet::Parser::Functions.newfunction("name", :arity => 2) { |args| } expect { callable_functions_from(function_module).function_name([1,2,3]) }.to raise_error ArgumentError end it "should raise an error if the function is called with too few arguments" do Puppet::Parser::Functions.newfunction("name", :arity => 2) { |args| } expect { callable_functions_from(function_module).function_name([1]) }.to raise_error ArgumentError end it "should not raise an error if the function is called with correct number of arguments" do Puppet::Parser::Functions.newfunction("name", :arity => 2) { |args| } expect { callable_functions_from(function_module).function_name([1,2]) }.to_not raise_error end it "should raise an error if the variable arg function is called with too few arguments" do Puppet::Parser::Functions.newfunction("name", :arity => -3) { |args| } expect { callable_functions_from(function_module).function_name([1]) }.to raise_error ArgumentError end it "should not raise an error if the variable arg function is called with correct number of arguments" do Puppet::Parser::Functions.newfunction("name", :arity => -3) { |args| } expect { callable_functions_from(function_module).function_name([1,2]) }.to_not raise_error end it "should not raise an error if the variable arg function is called with more number of arguments" do Puppet::Parser::Functions.newfunction("name", :arity => -3) { |args| } expect { callable_functions_from(function_module).function_name([1,2,3]) }.to_not raise_error end end describe "::arity" do it "returns the given arity of a function" do Puppet::Parser::Functions.newfunction("name", :arity => 4) { |args| } expect(Puppet::Parser::Functions.arity(:name)).to eq(4) end it "returns -1 if no arity is given" do Puppet::Parser::Functions.newfunction("name") { |args| } expect(Puppet::Parser::Functions.arity(:name)).to eq(-1) end end end puppet-5.5.10/spec/unit/pops/0000755005276200011600000000000013417162177015712 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/pops/adaptable_spec.rb0000644005276200011600000000731713417161721021170 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' describe Puppet::Pops::Adaptable::Adapter do class ValueAdapter < Puppet::Pops::Adaptable::Adapter attr_accessor :value end class OtherAdapter < Puppet::Pops::Adaptable::Adapter attr_accessor :value def OtherAdapter.create_adapter(o) x = new x.value="I am calling you Daffy." x end end module Farm class FarmAdapter < Puppet::Pops::Adaptable::Adapter attr_accessor :value end end class Duck include Puppet::Pops::Adaptable end it "should create specialized adapter instance on call to adapt" do d = Duck.new a = ValueAdapter.adapt(d) expect(a.class).to eq(ValueAdapter) end it "should produce the same instance on multiple adaptations" do d = Duck.new a = ValueAdapter.adapt(d) a.value = 10 b = ValueAdapter.adapt(d) expect(b.value).to eq(10) end it "should return the correct adapter if there are several" do d = Duck.new a = ValueAdapter.adapt(d) a.value = 10 b = ValueAdapter.adapt(d) expect(b.value).to eq(10) end it "should allow specialization to override creating" do d = Duck.new a = OtherAdapter.adapt(d) expect(a.value).to eq("I am calling you Daffy.") end it "should create a new adapter overriding existing" do d = Duck.new a = OtherAdapter.adapt(d) expect(a.value).to eq("I am calling you Daffy.") a.value = "Something different" expect(a.value).to eq("Something different") b = OtherAdapter.adapt(d) expect(b.value).to eq("Something different") b = OtherAdapter.adapt_new(d) expect(b.value).to eq("I am calling you Daffy.") end it "should not create adapter on get" do d = Duck.new a = OtherAdapter.get(d) expect(a).to eq(nil) end it "should return same adapter from get after adapt" do d = Duck.new a = OtherAdapter.get(d) expect(a).to eq(nil) a = OtherAdapter.adapt(d) expect(a.value).to eq("I am calling you Daffy.") b = OtherAdapter.get(d) expect(b.value).to eq("I am calling you Daffy.") expect(a).to eq(b) end it "should handle adapters in nested namespaces" do d = Duck.new a = Farm::FarmAdapter.get(d) expect(a).to eq(nil) a = Farm::FarmAdapter.adapt(d) a.value = 10 b = Farm::FarmAdapter.get(d) expect(b.value).to eq(10) end it "should be able to clear the adapter" do d = Duck.new a = OtherAdapter.adapt(d) expect(a.value).to eq("I am calling you Daffy.") # The adapter cleared should be returned expect(OtherAdapter.clear(d).value).to eq("I am calling you Daffy.") expect(OtherAdapter.get(d)).to eq(nil) end context "When adapting with #adapt it" do it "should be possible to pass a block to configure the adapter" do d = Duck.new a = OtherAdapter.adapt(d) do |x| x.value = "Donald" end expect(a.value).to eq("Donald") end it "should be possible to pass a block to configure the adapter and get the adapted" do d = Duck.new a = OtherAdapter.adapt(d) do |x, o| x.value = "Donald, the #{o.class.name}" end expect(a.value).to eq("Donald, the Duck") end end context "When adapting with #adapt_new it" do it "should be possible to pass a block to configure the adapter" do d = Duck.new a = OtherAdapter.adapt_new(d) do |x| x.value = "Donald" end expect(a.value).to eq("Donald") end it "should be possible to pass a block to configure the adapter and get the adapted" do d = Duck.new a = OtherAdapter.adapt_new(d) do |x, o| x.value = "Donald, the #{o.class.name}" end expect(a.value).to eq("Donald, the Duck") end end end puppet-5.5.10/spec/unit/pops/benchmark_spec.rb0000644005276200011600000000703213417161721021177 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' require 'puppet_spec/pops' require 'puppet_spec/scope' describe "Benchmark", :benchmark => true do include PuppetSpec::Pops include PuppetSpec::Scope def code 'if true { $a = 10 + 10 } else { $a = "interpolate ${foo} and stuff" } ' end class StringWriter < String alias write concat end def json_dump(model) output = StringWriter.new ser = Puppet::Pops::Serialization::Serializer.new(Puppet::Pops::Serialization::JSON.writer.new(output)) ser.write(model) ser.finish output end it "transformer", :profile => true do parser = Puppet::Pops::Parser::Parser.new() model = parser.parse_string(code).model transformer = Puppet::Pops::Model::AstTransformer.new() m = Benchmark.measure { 10000.times { transformer.transform(model) }} puts "Transformer: #{m}" end it "validator", :profile => true do parser = Puppet::Pops::Parser::EvaluatingParser.new() model = parser.parse_string(code) m = Benchmark.measure { 100000.times { parser.assert_and_report(model) }} puts "Validator: #{m}" end it "parse transform", :profile => true do parser = Puppet::Pops::Parser::Parser.new() transformer = Puppet::Pops::Model::AstTransformer.new() m = Benchmark.measure { 10000.times { transformer.transform(parser.parse_string(code).model) }} puts "Parse and transform: #{m}" end it "parser0", :profile => true do parser = Puppet::Parser::Parser.new('test') m = Benchmark.measure { 10000.times { parser.parse(code) }} puts "Parser 0: #{m}" end it "parser1", :profile => true do parser = Puppet::Pops::Parser::EvaluatingParser.new() m = Benchmark.measure { 10000.times { parser.parse_string(code) }} puts "Parser1: #{m}" end it "marshal1", :profile => true do parser = Puppet::Pops::Parser::EvaluatingParser.new() model = parser.parse_string(code).model dumped = Marshal.dump(model) m = Benchmark.measure { 10000.times { Marshal.load(dumped) }} puts "Marshal1: #{m}" end it "rgenjson", :profile => true do parser = Puppet::Pops::Parser::EvaluatingParser.new() model = parser.parse_string(code).model dumped = json_dump(model) m = Benchmark.measure { 10000.times { json_load(dumped) }} puts "Pcore Json: #{m}" end it "lexer2", :profile => true do lexer = Puppet::Pops::Parser::Lexer2.new m = Benchmark.measure {10000.times {lexer.string = code; lexer.fullscan }} puts "Lexer2: #{m}" end it "lexer1", :profile => true do lexer = Puppet::Pops::Parser::Lexer.new m = Benchmark.measure {10000.times {lexer.string = code; lexer.fullscan }} puts "Pops Lexer: #{m}" end it "lexer0", :profile => true do lexer = Puppet::Parser::Lexer.new m = Benchmark.measure {10000.times {lexer.string = code; lexer.fullscan }} puts "Original Lexer: #{m}" end context "Measure Evaluator" do let(:parser) { Puppet::Pops::Parser::EvaluatingParser.new } let(:node) { 'node.example.com' } let(:scope) { s = create_test_scope_for_node(node); s } it "evaluator", :profile => true do # Do the loop in puppet code since it otherwise drowns in setup puppet_loop = 'Integer[0, 1000].each |$i| { if true { $a = 10 + 10 } else { $a = "interpolate ${foo} and stuff" }} ' # parse once, only measure the evaluation model = parser.parse_string(puppet_loop, __FILE__) m = Benchmark.measure { parser.evaluate(create_test_scope_for_node(node), model) } puts("Evaluator: #{m}") end end end puppet-5.5.10/spec/unit/pops/containment_spec.rb0000644005276200011600000000000013417161721021550 0ustar jenkinsjenkinspuppet-5.5.10/spec/unit/pops/evaluator/0000755005276200011600000000000013417162176017713 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/pops/evaluator/access_ops_spec.rb0000644005276200011600000005234713417161721023402 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' require 'puppet/pops/evaluator/evaluator_impl' require 'puppet/pops/types/type_factory' require 'base64' # relative to this spec file (./) does not work as this file is loaded by rspec require File.join(File.dirname(__FILE__), '/evaluator_rspec_helper') describe 'Puppet::Pops::Evaluator::EvaluatorImpl/AccessOperator' do include EvaluatorRspecHelper def range(from, to) Puppet::Pops::Types::TypeFactory.range(from, to) end def float_range(from, to) Puppet::Pops::Types::TypeFactory.float_range(from, to) end def binary(s) # Note that the factory is not aware of Binary and cannot operate on a # literal binary. Instead, it must create a call to Binary.new() with the base64 encoded # string as an argument CALL_NAMED(QREF("Binary"), true, [infer(Base64.strict_encode64(s))]) end context 'The evaluator when operating on a String' do it 'can get a single character using a single key index to []' do expect(evaluate(literal('abc').access_at(1))).to eql('b') end it 'can get the last character using the key -1 in []' do expect(evaluate(literal('abc').access_at(-1))).to eql('c') end it 'can get a substring by giving two keys' do expect(evaluate(literal('abcd').access_at(1,2))).to eql('bc') # flattens keys expect(evaluate(literal('abcd').access_at([1,2]))).to eql('bc') end it 'produces empty string for a substring out of range' do expect(evaluate(literal('abc').access_at(100))).to eql('') end it 'raises an error if arity is wrong for []' do expect{evaluate(literal('abc').access_at)}.to raise_error(/String supports \[\] with one or two arguments\. Got 0/) expect{evaluate(literal('abc').access_at(1,2,3))}.to raise_error(/String supports \[\] with one or two arguments\. Got 3/) end end context 'The evaluator when operating on a Binary' do it 'can get a single character using a single key index to []' do expect(evaluate(binary('abc').access_at(1)).binary_buffer).to eql('b') end it 'can get the last character using the key -1 in []' do expect(evaluate(binary('abc').access_at(-1)).binary_buffer).to eql('c') end it 'can get a substring by giving two keys' do expect(evaluate(binary('abcd').access_at(1,2)).binary_buffer).to eql('bc') # flattens keys expect(evaluate(binary('abcd').access_at([1,2])).binary_buffer).to eql('bc') end it 'produces empty string for a substring out of range' do expect(evaluate(binary('abc').access_at(100)).binary_buffer).to eql('') end it 'raises an error if arity is wrong for []' do expect{evaluate(binary('abc').access_at)}.to raise_error(/String supports \[\] with one or two arguments\. Got 0/) expect{evaluate(binary('abc').access_at(1,2,3))}.to raise_error(/String supports \[\] with one or two arguments\. Got 3/) end end context 'The evaluator when operating on an Array' do it 'is tested with the correct assumptions' do expect(literal([1,2,3]).access_at(1).model_class <= Puppet::Pops::Model::AccessExpression).to eql(true) end it 'can get an element using a single key index to []' do expect(evaluate(literal([1,2,3]).access_at(1))).to eql(2) end it 'can get the last element using the key -1 in []' do expect(evaluate(literal([1,2,3]).access_at(-1))).to eql(3) end it 'can get a slice of elements using two keys' do expect(evaluate(literal([1,2,3,4]).access_at(1,2))).to eql([2,3]) # flattens keys expect(evaluate(literal([1,2,3,4]).access_at([1,2]))).to eql([2,3]) end it 'produces nil for a missing entry' do expect(evaluate(literal([1,2,3]).access_at(100))).to eql(nil) end it 'raises an error if arity is wrong for []' do expect{evaluate(literal([1,2,3,4]).access_at)}.to raise_error(/Array supports \[\] with one or two arguments\. Got 0/) expect{evaluate(literal([1,2,3,4]).access_at(1,2,3))}.to raise_error(/Array supports \[\] with one or two arguments\. Got 3/) end end context 'The evaluator when operating on a Hash' do it 'can get a single element giving a single key to []' do expect(evaluate(literal({'a'=>1,'b'=>2,'c'=>3}).access_at('b'))).to eql(2) end it 'can lookup an array' do expect(evaluate(literal({[1]=>10,[2]=>20}).access_at([2]))).to eql(20) end it 'produces nil for a missing key' do expect(evaluate(literal({'a'=>1,'b'=>2,'c'=>3}).access_at('x'))).to eql(nil) end it 'can get multiple elements by giving multiple keys to []' do expect(evaluate(literal({'a'=>1,'b'=>2,'c'=>3, 'd'=>4}).access_at('b', 'd'))).to eql([2, 4]) end it 'compacts the result when using multiple keys' do expect(evaluate(literal({'a'=>1,'b'=>2,'c'=>3, 'd'=>4}).access_at('b', 'x'))).to eql([2]) end it 'produces an empty array if none of multiple given keys were missing' do expect(evaluate(literal({'a'=>1,'b'=>2,'c'=>3, 'd'=>4}).access_at('x', 'y'))).to eql([]) end it 'raises an error if arity is wrong for []' do expect{evaluate(literal({'a'=>1,'b'=>2,'c'=>3}).access_at)}.to raise_error(/Hash supports \[\] with one or more arguments\. Got 0/) end end context "When applied to a type it" do let(:types) { Puppet::Pops::Types::TypeFactory } # Integer # it 'produces an Integer[from, to]' do expr = fqr('Integer').access_at(1, 3) expect(evaluate(expr)).to eql(range(1,3)) # arguments are flattened expr = fqr('Integer').access_at([1, 3]) expect(evaluate(expr)).to eql(range(1,3)) end it 'produces an Integer[1]' do expr = fqr('Integer').access_at(1) expect(evaluate(expr)).to eql(range(1,:default)) end it 'gives an error for Integer[from, literal(3)}) expect(evaluate(expr)).to be_the_type(types.timespan({'hours' => 3})) end it 'produdes a Timespan type with an upper bound' do expr = fqr('Timespan').access_at(literal(:default), {fqn('hours') => literal(9)}) expect(evaluate(expr)).to be_the_type(types.timespan(nil, {'hours' => 9})) end it 'produdes a Timespan type with both lower and upper bounds' do expr = fqr('Timespan').access_at({fqn('hours') => literal(3)}, {fqn('hours') => literal(9)}) expect(evaluate(expr)).to be_the_type(types.timespan({'hours' => 3}, {'hours' => 9})) end # Timestamp Type # it 'produdes a Timestamp type with a lower bound' do expr = fqr('Timestamp').access_at(literal('2014-12-12T13:14:15 CET')) expect(evaluate(expr)).to be_the_type(types.timestamp('2014-12-12T13:14:15 CET')) end it 'produdes a Timestamp type with an upper bound' do expr = fqr('Timestamp').access_at(literal(:default), literal('2016-08-23T17:50:00 CET')) expect(evaluate(expr)).to be_the_type(types.timestamp(nil, '2016-08-23T17:50:00 CET')) end it 'produdes a Timestamp type with both lower and upper bounds' do expr = fqr('Timestamp').access_at(literal('2014-12-12T13:14:15 CET'), literal('2016-08-23T17:50:00 CET')) expect(evaluate(expr)).to be_the_type(types.timestamp('2014-12-12T13:14:15 CET', '2016-08-23T17:50:00 CET')) end # Tuple Type # it 'produces a Tuple[String] from the expression Tuple[String]' do expr = fqr('Tuple').access_at(fqr('String')) expect(evaluate(expr)).to be_the_type(types.tuple([String])) # arguments are flattened expr = fqr('Tuple').access_at([fqr('String')]) expect(evaluate(expr)).to be_the_type(types.tuple([String])) end it "Tuple parameterization gives an error if parameter is not a type" do expr = fqr('Tuple').access_at('String') expect { evaluate(expr)}.to raise_error(/Tuple-Type, Cannot use String where Any-Type is expected/) end it 'produces a varargs Tuple when the last two arguments specify size constraint' do expr = fqr('Tuple').access_at(fqr('String'), 1) expected_t = types.tuple([String], types.range(1, :default)) expect(evaluate(expr)).to be_the_type(expected_t) expr = fqr('Tuple').access_at(fqr('String'), 1, 2) expected_t = types.tuple([String], types.range(1, 2)) expect(evaluate(expr)).to be_the_type(expected_t) end # Pattern Type # it 'creates a PPatternType instance when applied to a Pattern' do regexp_expr = fqr('Pattern').access_at('foo') expect(evaluate(regexp_expr)).to eql(Puppet::Pops::Types::TypeFactory.pattern('foo')) end # Regexp Type # it 'creates a Regexp instance when applied to a Pattern' do regexp_expr = fqr('Regexp').access_at('foo') expect(evaluate(regexp_expr)).to eql(Puppet::Pops::Types::TypeFactory.regexp('foo')) # arguments are flattened regexp_expr = fqr('Regexp').access_at(['foo']) expect(evaluate(regexp_expr)).to eql(Puppet::Pops::Types::TypeFactory.regexp('foo')) end # Class # it 'produces a specific class from Class[classname]' do expr = fqr('Class').access_at(fqn('apache')) expect(evaluate(expr)).to be_the_type(types.host_class('apache')) expr = fqr('Class').access_at(literal('apache')) expect(evaluate(expr)).to be_the_type(types.host_class('apache')) end it 'produces an array of Class when args are in an array' do # arguments are flattened expr = fqr('Class').access_at([fqn('apache')]) expect(evaluate(expr)[0]).to be_the_type(types.host_class('apache')) end it 'produces undef for Class if arg is undef' do # arguments are flattened expr = fqr('Class').access_at(nil) expect(evaluate(expr)).to be_nil end it 'produces empty array for Class if arg is [undef]' do # arguments are flattened expr = fqr('Class').access_at([]) expect(evaluate(expr)).to be_eql([]) expr = fqr('Class').access_at([nil]) expect(evaluate(expr)).to be_eql([]) end it 'raises error if access is to no keys' do expr = fqr('Class').access_at(fqn('apache')).access_at expect { evaluate(expr) }.to raise_error(/Evaluation Error: Class\[apache\]\[\] accepts 1 or more arguments\. Got 0/) end it 'produces a collection of classes when multiple class names are given' do expr = fqr('Class').access_at(fqn('apache'), literal('nginx')) result = evaluate(expr) expect(result[0]).to be_the_type(types.host_class('apache')) expect(result[1]).to be_the_type(types.host_class('nginx')) end it 'removes leading :: in class name' do expr = fqr('Class').access_at('::evoe') expect(evaluate(expr)).to be_the_type(types.host_class('evoe')) end it 'raises error if the name is not a valid name' do expr = fqr('Class').access_at('fail-whale') expect { evaluate(expr) }.to raise_error(/Illegal name/) end it 'downcases capitalized class names' do expr = fqr('Class').access_at('My::Class') expect(evaluate(expr)).to be_the_type(types.host_class('my::class')) end it 'gives an error if no keys are given as argument' do expr = fqr('Class').access_at expect {evaluate(expr)}.to raise_error(/Evaluation Error: Class\[\] accepts 1 or more arguments. Got 0/) end it 'produces an empty array if the keys reduce to empty array' do expr = fqr('Class').access_at(literal([[],[]])) expect(evaluate(expr)).to be_eql([]) end # Resource it 'produces a specific resource type from Resource[type]' do expr = fqr('Resource').access_at(fqr('File')) expect(evaluate(expr)).to be_the_type(types.resource('File')) expr = fqr('Resource').access_at(literal('File')) expect(evaluate(expr)).to be_the_type(types.resource('File')) end it 'does not allow the type to be specified in an array' do # arguments are flattened expr = fqr('Resource').access_at([fqr('File')]) expect{evaluate(expr)}.to raise_error(Puppet::ParseError, /must be a resource type or a String/) end it 'produces a specific resource reference type from File[title]' do expr = fqr('File').access_at(literal('/tmp/x')) expect(evaluate(expr)).to be_the_type(types.resource('File', '/tmp/x')) end it 'produces a collection of specific resource references when multiple titles are used' do # Using a resource type expr = fqr('File').access_at(literal('x'),literal('y')) result = evaluate(expr) expect(result[0]).to be_the_type(types.resource('File', 'x')) expect(result[1]).to be_the_type(types.resource('File', 'y')) # Using generic resource expr = fqr('Resource').access_at(fqr('File'), literal('x'),literal('y')) result = evaluate(expr) expect(result[0]).to be_the_type(types.resource('File', 'x')) expect(result[1]).to be_the_type(types.resource('File', 'y')) end it 'produces undef for Resource if arg is undef' do # arguments are flattened expr = fqr('File').access_at(nil) expect(evaluate(expr)).to be_nil end it 'gives an error if no keys are given as argument to Resource' do expr = fqr('Resource').access_at expect {evaluate(expr)}.to raise_error(/Evaluation Error: Resource\[\] accepts 1 or more arguments. Got 0/) end it 'produces an empty array if the type is given, and keys reduce to empty array for Resource' do expr = fqr('Resource').access_at(fqr('File'),literal([[],[]])) expect(evaluate(expr)).to be_eql([]) end it 'gives an error i no keys are given as argument to a specific Resource type' do expr = fqr('File').access_at expect {evaluate(expr)}.to raise_error(/Evaluation Error: File\[\] accepts 1 or more arguments. Got 0/) end it 'produces an empty array if the keys reduce to empty array for a specific Resource tyoe' do expr = fqr('File').access_at(literal([[],[]])) expect(evaluate(expr)).to be_eql([]) end it 'gives an error if resource is not found' do expr = fqr('File').access_at(fqn('x')).access_at(fqn('y')) expect {evaluate(expr)}.to raise_error(/Resource not found: File\['x'\]/) end # NotUndef Type # it 'produces a NotUndef instance' do type_expr = fqr('NotUndef') expect(evaluate(type_expr)).to eql(Puppet::Pops::Types::TypeFactory.not_undef()) end it 'produces a NotUndef instance with contained type' do type_expr = fqr('NotUndef').access_at(fqr('Integer')) tf = Puppet::Pops::Types::TypeFactory expect(evaluate(type_expr)).to eql(tf.not_undef(tf.integer)) end it 'produces a NotUndef instance with String type when given a literal String' do type_expr = fqr('NotUndef').access_at(literal('hey')) tf = Puppet::Pops::Types::TypeFactory expect(evaluate(type_expr)).to be_the_type(tf.not_undef(tf.string('hey'))) end it 'Produces Optional instance with String type when using a String argument' do type_expr = fqr('Optional').access_at(literal('hey')) tf = Puppet::Pops::Types::TypeFactory expect(evaluate(type_expr)).to be_the_type(tf.optional(tf.string('hey'))) end # Type Type # it 'creates a Type instance when applied to a Type' do type_expr = fqr('Type').access_at(fqr('Integer')) tf = Puppet::Pops::Types::TypeFactory expect(evaluate(type_expr)).to eql(tf.type_type(tf.integer)) # arguments are flattened type_expr = fqr('Type').access_at([fqr('Integer')]) expect(evaluate(type_expr)).to eql(tf.type_type(tf.integer)) end # Ruby Type # it 'creates a Ruby Type instance when applied to a Ruby Type' do type_expr = fqr('Runtime').access_at('ruby','String') tf = Puppet::Pops::Types::TypeFactory expect(evaluate(type_expr)).to eql(tf.ruby_type('String')) # arguments are flattened type_expr = fqr('Runtime').access_at(['ruby', 'String']) expect(evaluate(type_expr)).to eql(tf.ruby_type('String')) end # Callable Type # it 'produces Callable instance without return type' do type_expr = fqr('Callable').access_at(fqr('String')) tf = Puppet::Pops::Types::TypeFactory expect(evaluate(type_expr)).to eql(tf.callable(String)) end it 'produces Callable instance with parameters and return type' do type_expr = fqr('Callable').access_at([fqr('String')], fqr('Integer')) tf = Puppet::Pops::Types::TypeFactory expect(evaluate(type_expr)).to eql(tf.callable([String], Integer)) end # Variant Type it 'does not allow Variant declarations with non-type arguments' do type_expr = fqr('Variant').access_at(fqr('Integer'), 'not a type') expect { evaluate(type_expr) }.to raise_error(/Cannot use String where Any-Type is expected/) end end matcher :be_the_type do |type| calc = Puppet::Pops::Types::TypeCalculator.new match do |actual| calc.assignable?(actual, type) && calc.assignable?(type, actual) end failure_message do |actual| "expected #{type.to_s}, but was #{actual.to_s}" end end end puppet-5.5.10/spec/unit/pops/evaluator/basic_expressions_spec.rb0000644005276200011600000000637513417161721025003 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' require 'puppet/pops/evaluator/evaluator_impl' # relative to this spec file (./) does not work as this file is loaded by rspec require File.join(File.dirname(__FILE__), '/evaluator_rspec_helper') describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do include EvaluatorRspecHelper context "When the evaluator evaluates literals" do it 'should evaluator numbers to numbers' do expect(evaluate(literal(1))).to eq(1) expect(evaluate(literal(3.14))).to eq(3.14) end it 'should evaluate strings to string' do expect(evaluate(literal('banana'))).to eq('banana') end it 'should evaluate booleans to booleans' do expect(evaluate(literal(false))).to eq(false) expect(evaluate(literal(true))).to eq(true) end it 'should evaluate names to strings' do expect(evaluate(fqn('banana'))).to eq('banana') end it 'should evaluator types to types' do array_type = Puppet::Pops::Types::PArrayType::DEFAULT expect(evaluate(fqr('Array'))).to eq(array_type) end end context "When the evaluator evaluates Lists" do it "should create an Array when evaluating a LiteralList" do expect(evaluate(literal([1,2,3]))).to eq([1,2,3]) end it "[...[...[]]] should create nested arrays without trouble" do expect(evaluate(literal([1,[2.0, 2.1, [2.2]],[3.0, 3.1]]))).to eq([1,[2.0, 2.1, [2.2]],[3.0, 3.1]]) end it "[2 + 2] should evaluate expressions in entries" do x = literal([literal(2) + literal(2)]); expect(Puppet::Pops::Model::ModelTreeDumper.new.dump(x)).to eq("([] (+ 2 2))") expect(evaluate(x)[0]).to eq(4) end it "[1,2,3] == [1,2,3] == true" do expect(evaluate(literal([1,2,3]).eq(literal([1,2,3])))).to eq(true); end it "[1,2,3] != [2,3,4] == true" do expect(evaluate(literal([1,2,3]).ne(literal([2,3,4])))).to eq(true); end it "[1, 2, 3][2] == 3" do expect(evaluate(literal([1,2,3]))[2]).to eq(3) end end context "When the evaluator evaluates Hashes" do it "should create a Hash when evaluating a LiteralHash" do expect(evaluate(literal({'a'=>1,'b'=>2}))).to eq({'a'=>1,'b'=>2}) end it "{...{...{}}} should create nested hashes without trouble" do expect(evaluate(literal({'a'=>1,'b'=>{'x'=>2.1,'y'=>2.2}}))).to eq({'a'=>1,'b'=>{'x'=>2.1,'y'=>2.2}}) end it "{'a'=> 2 + 2} should evaluate values in entries" do expect(evaluate(literal({'a'=> literal(2) + literal(2)}))['a']).to eq(4) end it "{'a'=> 1, 'b'=>2} == {'a'=> 1, 'b'=>2} == true" do expect(evaluate(literal({'a'=> 1, 'b'=>2}).eq(literal({'a'=> 1, 'b'=>2})))).to eq(true); end it "{'a'=> 1, 'b'=>2} != {'x'=> 1, 'y'=>3} == true" do expect(evaluate(literal({'a'=> 1, 'b'=>2}).ne(literal({'x'=> 1, 'y'=>3})))).to eq(true); end it "{'a' => 1, 'b' => 2}['b'] == 2" do expect(evaluate(literal({:a => 1, :b => 2}).access_at(:b))).to eq(2) end end context 'When the evaluator evaluates a Block' do it 'an empty block evaluates to nil' do expect(evaluate(block())).to eq(nil) end it 'a block evaluates to its last expression' do expect(evaluate(block(literal(1), literal(2)))).to eq(2) end end end puppet-5.5.10/spec/unit/pops/evaluator/collections_ops_spec.rb0000644005276200011600000001000713417161721024442 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' require 'puppet/pops/evaluator/evaluator_impl' require 'puppet/pops/types/type_factory' # relative to this spec file (./) does not work as this file is loaded by rspec require File.join(File.dirname(__FILE__), '/evaluator_rspec_helper') describe 'Puppet::Pops::Evaluator::EvaluatorImpl/Concat/Delete' do include EvaluatorRspecHelper context 'The evaluator when operating on an Array' do it 'concatenates another array using +' do expect(evaluate(literal([1,2,3]) + literal([4,5]))).to eql([1,2,3,4,5]) end it 'concatenates another nested array using +' do expect(evaluate(literal([1,2,3]) + literal([[4,5]]))).to eql([1,2,3,[4,5]]) end it 'concatenates a hash by converting it to array' do expect(evaluate(literal([1,2,3]) + literal({'a' => 1, 'b'=>2}))).to eql([1,2,3,['a',1],['b',2]]) end it 'concatenates a non array value with +' do expect(evaluate(literal([1,2,3]) + literal(4))).to eql([1,2,3,4]) end it 'appends another array using <<' do expect(evaluate(literal([1,2,3]) << literal([4,5]))).to eql([1,2,3,[4,5]]) end it 'appends a hash without conversion when << operator is used' do expect(evaluate(literal([1,2,3]) << literal({'a' => 1, 'b'=>2}))).to eql([1,2,3,{'a' => 1, 'b'=>2}]) end it 'appends another non array using <<' do expect(evaluate(literal([1,2,3]) << literal(4))).to eql([1,2,3,4]) end it 'computes the difference with another array using -' do expect(evaluate(literal([1,2,3,4]) - literal([2,3]))).to eql([1,4]) end it 'computes the difference with a non array using -' do expect(evaluate(literal([1,2,3,4]) - literal(2))).to eql([1,3,4]) end it 'does not recurse into nested arrays when computing diff' do expect(evaluate(literal([1,2,3,[2],4]) - literal(2))).to eql([1,3,[2],4]) end it 'can compute diff with sub arrays' do expect(evaluate(literal([1,2,3,[2,3],4]) - literal([[2,3]]))).to eql([1,2,3,4]) end it 'computes difference by removing all matching instances' do expect(evaluate(literal([1,2,3,3,2,4,2,3]) - literal([2,3]))).to eql([1,4]) end it 'computes difference with a hash by converting it to an array' do expect(evaluate(literal([1,2,3,['a',1],['b',2]]) - literal({'a' => 1, 'b'=>2}))).to eql([1,2,3]) end it 'diffs hashes when given in an array' do expect(evaluate(literal([1,2,3,{'a'=>1,'b'=>2}]) - literal([{'a' => 1, 'b'=>2}]))).to eql([1,2,3]) end it 'raises and error when LHS of << is a hash' do expect { evaluate(literal({'a' => 1, 'b'=>2}) << literal(1)) }.to raise_error(/Operator '<<' is not applicable to a Hash/) end end context 'The evaluator when operating on a Hash' do it 'merges with another Hash using +' do expect(evaluate(literal({'a' => 1, 'b'=>2}) + literal({'c' => 3}))).to eql({'a' => 1, 'b'=>2, 'c' => 3}) end it 'merges RHS on top of LHS ' do expect(evaluate(literal({'a' => 1, 'b'=>2}) + literal({'c' => 3, 'b'=>3}))).to eql({'a' => 1, 'b'=>3, 'c' => 3}) end it 'merges a flat array of pairs converted to a hash' do expect(evaluate(literal({'a' => 1, 'b'=>2}) + literal(['c', 3, 'b', 3]))).to eql({'a' => 1, 'b'=>3, 'c' => 3}) end it 'merges an array converted to a hash' do expect(evaluate(literal({'a' => 1, 'b'=>2}) + literal([['c', 3], ['b', 3]]))).to eql({'a' => 1, 'b'=>3, 'c' => 3}) end it 'computes difference with another hash using the - operator' do expect(evaluate(literal({'a' => 1, 'b'=>2}) - literal({'b' => 3}))).to eql({'a' => 1 }) end it 'computes difference with an array by treating array as array of keys' do expect(evaluate(literal({'a' => 1, 'b'=>2,'c'=>3}) - literal(['b', 'c']))).to eql({'a' => 1 }) end it 'computes difference with a non array/hash by treating it as a key' do expect(evaluate(literal({'a' => 1, 'b'=>2,'c'=>3}) - literal('c'))).to eql({'a' => 1, 'b' => 2 }) end end end puppet-5.5.10/spec/unit/pops/evaluator/comparison_ops_spec.rb0000644005276200011600000003442713417161721024312 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' require 'puppet/pops/evaluator/evaluator_impl' # relative to this spec file (./) does not work as this file is loaded by rspec require File.join(File.dirname(__FILE__), '/evaluator_rspec_helper') describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do include EvaluatorRspecHelper context "When the evaluator performs comparisons" do context "of string values" do it "'a' == 'a' == true" do; expect(evaluate(literal('a').eq(literal('a')))).to eq(true) ; end it "'a' == 'b' == false" do; expect(evaluate(literal('a').eq(literal('b')))).to eq(false) ; end it "'a' != 'a' == false" do; expect(evaluate(literal('a').ne(literal('a')))).to eq(false) ; end it "'a' != 'b' == true" do; expect(evaluate(literal('a').ne(literal('b')))).to eq(true) ; end it "'a' < 'b' == true" do; expect(evaluate(literal('a') < literal('b'))).to eq(true) ; end it "'a' < 'a' == false" do; expect(evaluate(literal('a') < literal('a'))).to eq(false) ; end it "'b' < 'a' == false" do; expect(evaluate(literal('b') < literal('a'))).to eq(false) ; end it "'a' <= 'b' == true" do; expect(evaluate(literal('a') <= literal('b'))).to eq(true) ; end it "'a' <= 'a' == true" do; expect(evaluate(literal('a') <= literal('a'))).to eq(true) ; end it "'b' <= 'a' == false" do; expect(evaluate(literal('b') <= literal('a'))).to eq(false) ; end it "'a' > 'b' == false" do; expect(evaluate(literal('a') > literal('b'))).to eq(false) ; end it "'a' > 'a' == false" do; expect(evaluate(literal('a') > literal('a'))).to eq(false) ; end it "'b' > 'a' == true" do; expect(evaluate(literal('b') > literal('a'))).to eq(true) ; end it "'a' >= 'b' == false" do; expect(evaluate(literal('a') >= literal('b'))).to eq(false) ; end it "'a' >= 'a' == true" do; expect(evaluate(literal('a') >= literal('a'))).to eq(true) ; end it "'b' >= 'a' == true" do; expect(evaluate(literal('b') >= literal('a'))).to eq(true) ; end context "with mixed case" do it "'a' == 'A' == true" do; expect(evaluate(literal('a').eq(literal('A')))).to eq(true) ; end it "'a' != 'A' == false" do; expect(evaluate(literal('a').ne(literal('A')))).to eq(false) ; end it "'a' > 'A' == false" do; expect(evaluate(literal('a') > literal('A'))).to eq(false) ; end it "'a' >= 'A' == true" do; expect(evaluate(literal('a') >= literal('A'))).to eq(true) ; end it "'A' < 'a' == false" do; expect(evaluate(literal('A') < literal('a'))).to eq(false) ; end it "'A' <= 'a' == true" do; expect(evaluate(literal('A') <= literal('a'))).to eq(true) ; end end end context "of integer values" do it "1 == 1 == true" do; expect(evaluate(literal(1).eq(literal(1)))).to eq(true) ; end it "1 == 2 == false" do; expect(evaluate(literal(1).eq(literal(2)))).to eq(false) ; end it "1 != 1 == false" do; expect(evaluate(literal(1).ne(literal(1)))).to eq(false) ; end it "1 != 2 == true" do; expect(evaluate(literal(1).ne(literal(2)))).to eq(true) ; end it "1 < 2 == true" do; expect(evaluate(literal(1) < literal(2))).to eq(true) ; end it "1 < 1 == false" do; expect(evaluate(literal(1) < literal(1))).to eq(false) ; end it "2 < 1 == false" do; expect(evaluate(literal(2) < literal(1))).to eq(false) ; end it "1 <= 2 == true" do; expect(evaluate(literal(1) <= literal(2))).to eq(true) ; end it "1 <= 1 == true" do; expect(evaluate(literal(1) <= literal(1))).to eq(true) ; end it "2 <= 1 == false" do; expect(evaluate(literal(2) <= literal(1))).to eq(false) ; end it "1 > 2 == false" do; expect(evaluate(literal(1) > literal(2))).to eq(false) ; end it "1 > 1 == false" do; expect(evaluate(literal(1) > literal(1))).to eq(false) ; end it "2 > 1 == true" do; expect(evaluate(literal(2) > literal(1))).to eq(true) ; end it "1 >= 2 == false" do; expect(evaluate(literal(1) >= literal(2))).to eq(false) ; end it "1 >= 1 == true" do; expect(evaluate(literal(1) >= literal(1))).to eq(true) ; end it "2 >= 1 == true" do; expect(evaluate(literal(2) >= literal(1))).to eq(true) ; end end context "of mixed value types" do it "1 == 1.0 == true" do; expect(evaluate(literal(1).eq( literal(1.0)))).to eq(true) ; end it "1 < 1.1 == true" do; expect(evaluate(literal(1) < literal(1.1))).to eq(true) ; end it "1.0 == 1 == true" do; expect(evaluate(literal(1.0).eq( literal(1)))).to eq(true) ; end it "1.0 < 2 == true" do; expect(evaluate(literal(1.0) < literal(2))).to eq(true) ; end it "'1.0' < 'a' == true" do; expect(evaluate(literal('1.0') < literal('a'))).to eq(true) ; end it "'1.0' < '' == true" do; expect(evaluate(literal('1.0') < literal(''))).to eq(false) ; end it "'1.0' < ' ' == true" do; expect(evaluate(literal('1.0') < literal(' '))).to eq(false) ; end it "'a' > '1.0' == true" do; expect(evaluate(literal('a') > literal('1.0'))).to eq(true) ; end end context "of unsupported mixed value types" do it "'1' < 1.1 == true" do expect{ evaluate(literal('1') < literal(1.1))}.to raise_error(/String < Float/) end it "'1.0' < 1.1 == true" do expect{evaluate(literal('1.0') < literal(1.1))}.to raise_error(/String < Float/) end it "undef < 1.1 == true" do expect{evaluate(literal(nil) < literal(1.1))}.to raise_error(/Undef Value < Float/) end end context "of regular expressions" do it "/.*/ == /.*/ == true" do; expect(evaluate(literal(/.*/).eq(literal(/.*/)))).to eq(true) ; end it "/.*/ != /a.*/ == true" do; expect(evaluate(literal(/.*/).ne(literal(/a.*/)))).to eq(true) ; end end context "of booleans" do it "true == true == true" do; expect(evaluate(literal(true).eq(literal(true)))).to eq(true) ; end; it "false == false == true" do; expect(evaluate(literal(false).eq(literal(false)))).to eq(true) ; end; it "true == false != true" do; expect(evaluate(literal(true).eq(literal(false)))).to eq(false) ; end; it "false == '' == false" do; expect(evaluate(literal(false).eq(literal('')))).to eq(false) ; end; it "undef == '' == false" do; expect(evaluate(literal(:undef).eq(literal('')))).to eq(false) ; end; it "undef == undef == true" do; expect(evaluate(literal(:undef).eq(literal(:undef)))).to eq(true) ; end; it "nil == undef == true" do; expect(evaluate(literal(nil).eq(literal(:undef)))).to eq(true) ; end; end context "of collections" do it "[1,2,3] == [1,2,3] == true" do expect(evaluate(literal([1,2,3]).eq(literal([1,2,3])))).to eq(true) expect(evaluate(literal([1,2,3]).ne(literal([1,2,3])))).to eq(false) expect(evaluate(literal([1,2,4]).eq(literal([1,2,3])))).to eq(false) expect(evaluate(literal([1,2,4]).ne(literal([1,2,3])))).to eq(true) end it "{'a'=>1, 'b'=>2} == {'a'=>1, 'b'=>2} == true" do expect(evaluate(literal({'a'=>1, 'b'=>2}).eq(literal({'a'=>1, 'b'=>2})))).to eq(true) expect(evaluate(literal({'a'=>1, 'b'=>2}).ne(literal({'a'=>1, 'b'=>2})))).to eq(false) expect(evaluate(literal({'a'=>1, 'b'=>2}).eq(literal({'x'=>1, 'b'=>2})))).to eq(false) expect(evaluate(literal({'a'=>1, 'b'=>2}).ne(literal({'x'=>1, 'b'=>2})))).to eq(true) end end context "of non comparable types" do # TODO: Change the exception type it "false < true == error" do; expect { evaluate(literal(true) < literal(false))}.to raise_error(Puppet::ParseError); end it "false <= true == error" do; expect { evaluate(literal(true) <= literal(false))}.to raise_error(Puppet::ParseError); end it "false > true == error" do; expect { evaluate(literal(true) > literal(false))}.to raise_error(Puppet::ParseError); end it "false >= true == error" do; expect { evaluate(literal(true) >= literal(false))}.to raise_error(Puppet::ParseError); end it "/a/ < /b/ == error" do; expect { evaluate(literal(/a/) < literal(/b/))}.to raise_error(Puppet::ParseError); end it "/a/ <= /b/ == error" do; expect { evaluate(literal(/a/) <= literal(/b/))}.to raise_error(Puppet::ParseError); end it "/a/ > /b/ == error" do; expect { evaluate(literal(/a/) > literal(/b/))}.to raise_error(Puppet::ParseError); end it "/a/ >= /b/ == error" do; expect { evaluate(literal(/a/) >= literal(/b/))}.to raise_error(Puppet::ParseError); end it "[1,2,3] < [1,2,3] == error" do expect{ evaluate(literal([1,2,3]) < literal([1,2,3]))}.to raise_error(Puppet::ParseError) end it "[1,2,3] > [1,2,3] == error" do expect{ evaluate(literal([1,2,3]) > literal([1,2,3]))}.to raise_error(Puppet::ParseError) end it "[1,2,3] >= [1,2,3] == error" do expect{ evaluate(literal([1,2,3]) >= literal([1,2,3]))}.to raise_error(Puppet::ParseError) end it "[1,2,3] <= [1,2,3] == error" do expect{ evaluate(literal([1,2,3]) <= literal([1,2,3]))}.to raise_error(Puppet::ParseError) end it "{'a'=>1, 'b'=>2} < {'a'=>1, 'b'=>2} == error" do expect{ evaluate(literal({'a'=>1, 'b'=>2}) < literal({'a'=>1, 'b'=>2}))}.to raise_error(Puppet::ParseError) end it "{'a'=>1, 'b'=>2} > {'a'=>1, 'b'=>2} == error" do expect{ evaluate(literal({'a'=>1, 'b'=>2}) > literal({'a'=>1, 'b'=>2}))}.to raise_error(Puppet::ParseError) end it "{'a'=>1, 'b'=>2} <= {'a'=>1, 'b'=>2} == error" do expect{ evaluate(literal({'a'=>1, 'b'=>2}) <= literal({'a'=>1, 'b'=>2}))}.to raise_error(Puppet::ParseError) end it "{'a'=>1, 'b'=>2} >= {'a'=>1, 'b'=>2} == error" do expect{ evaluate(literal({'a'=>1, 'b'=>2}) >= literal({'a'=>1, 'b'=>2}))}.to raise_error(Puppet::ParseError) end end end context "When the evaluator performs Regular Expression matching" do it "'a' =~ /.*/ == true" do; expect(evaluate(literal('a') =~ literal(/.*/))).to eq(true) ; end it "'a' =~ '.*' == true" do; expect(evaluate(literal('a') =~ literal(".*"))).to eq(true) ; end it "'a' !~ /b.*/ == true" do; expect(evaluate(literal('a').mne(literal(/b.*/)))).to eq(true) ; end it "'a' !~ 'b.*' == true" do; expect(evaluate(literal('a').mne(literal("b.*")))).to eq(true) ; end it "'a' =~ Pattern['.*'] == true" do expect(evaluate(literal('a') =~ fqr('Pattern').access_at(literal(".*")))).to eq(true) end it "$a = Pattern['.*']; 'a' =~ $a == true" do expr = block(var('a').set(fqr('Pattern').access_at('.*')), literal('foo') =~ var('a')) expect(evaluate(expr)).to eq(true) end it 'should fail if LHS is not a string' do expect { evaluate(literal(666) =~ literal(/6/))}.to raise_error(Puppet::ParseError) end end context "When evaluator evaluates the 'in' operator" do it "should find elements in an array" do expect(evaluate(literal(1).in(literal([1,2,3])))).to eq(true) expect(evaluate(literal(4).in(literal([1,2,3])))).to eq(false) end it "should find keys in a hash" do expect(evaluate(literal('a').in(literal({'x'=>1, 'a'=>2, 'y'=> 3})))).to eq(true) expect(evaluate(literal('z').in(literal({'x'=>1, 'a'=>2, 'y'=> 3})))).to eq(false) end it "should find substrings in a string" do expect(evaluate(literal('ana').in(literal('bananas')))).to eq(true) expect(evaluate(literal('xxx').in(literal('bananas')))).to eq(false) end it "should find substrings in a string (regexp)" do expect(evaluate(literal(/ana/).in(literal('bananas')))).to eq(true) expect(evaluate(literal(/xxx/).in(literal('bananas')))).to eq(false) end it "should find substrings in a string (ignoring case)" do expect(evaluate(literal('ANA').in(literal('bananas')))).to eq(true) expect(evaluate(literal('ana').in(literal('BANANAS')))).to eq(true) expect(evaluate(literal('xxx').in(literal('BANANAS')))).to eq(false) end it "should find sublists in a list" do expect(evaluate(literal([2,3]).in(literal([1,[2,3],4])))).to eq(true) expect(evaluate(literal([2,4]).in(literal([1,[2,3],4])))).to eq(false) end it "should find sublists in a list (case insensitive)" do expect(evaluate(literal(['a','b']).in(literal(['A',['A','B'],'C'])))).to eq(true) expect(evaluate(literal(['x','y']).in(literal(['A',['A','B'],'C'])))).to eq(false) end it "should find keys in a hash" do expect(evaluate(literal('a').in(literal({'a' => 10, 'b' => 20})))).to eq(true) expect(evaluate(literal('x').in(literal({'a' => 10, 'b' => 20})))).to eq(false) end it "should find keys in a hash (case insensitive)" do expect(evaluate(literal('A').in(literal({'a' => 10, 'b' => 20})))).to eq(true) expect(evaluate(literal('X').in(literal({'a' => 10, 'b' => 20})))).to eq(false) end it "should find keys in a hash (regexp)" do expect(evaluate(literal(/xxx/).in(literal({'abcxxxabc' => 10, 'xyz' => 20})))).to eq(true) expect(evaluate(literal(/yyy/).in(literal({'abcxxxabc' => 10, 'xyz' => 20})))).to eq(false) end it "should find numbers as numbers" do expect(evaluate(literal(15).in(literal([1,0xf,2])))).to eq(true) end it "should not find numbers as strings" do expect(evaluate(literal(15).in(literal([1, '0xf',2])))).to eq(false) expect(evaluate(literal('15').in(literal([1, 0xf,2])))).to eq(false) end it "should not find numbers embedded in strings, nor digits in numbers" do expect(evaluate(literal(15).in(literal([1, '115', 2])))).to eq(false) expect(evaluate(literal(1).in(literal([11, 111, 2])))).to eq(false) expect(evaluate(literal('1').in(literal([11, 111, 2])))).to eq(false) end it 'should find an entry with compatible type in an Array' do expect(evaluate(fqr('Array').access_at(fqr('Integer')).in(literal(['a', [1,2,3], 'b'])))).to eq(true) expect(evaluate(fqr('Array').access_at(fqr('Integer')).in(literal(['a', [1,2,'not integer'], 'b'])))).to eq(false) end it 'should find an entry with compatible type in a Hash' do expect(evaluate(fqr('Integer').in(literal({1 => 'a', 'a' => 'b'})))).to eq(true) expect(evaluate(fqr('Integer').in(literal({'a' => 'a', 'b' => 'b'})))).to eq(false) end end end puppet-5.5.10/spec/unit/pops/evaluator/conditionals_spec.rb0000644005276200011600000001467713417161721023752 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' require 'puppet/pops/evaluator/evaluator_impl' # relative to this spec file (./) does not work as this file is loaded by rspec require File.join(File.dirname(__FILE__), '/evaluator_rspec_helper') # This file contains testing of Conditionals, if, case, unless, selector # describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do include EvaluatorRspecHelper context "When the evaluator evaluates" do context "an if expression" do it 'should output the expected result when dumped' do expect(dump(IF(literal(true), literal(2), literal(5)))).to eq unindent(<<-TEXT (if true (then 2) (else 5)) TEXT ) end it 'if true {5} == 5' do expect(evaluate(IF(literal(true), literal(5)))).to eq(5) end it 'if false {5} == nil' do expect(evaluate(IF(literal(false), literal(5)))).to eq(nil) end it 'if false {2} else {5} == 5' do expect(evaluate(IF(literal(false), literal(2), literal(5)))).to eq(5) end it 'if false {2} elsif true {5} == 5' do expect(evaluate(IF(literal(false), literal(2), IF(literal(true), literal(5))))).to eq(5) end it 'if false {2} elsif false {5} == nil' do expect(evaluate(IF(literal(false), literal(2), IF(literal(false), literal(5))))).to eq(nil) end end context "an unless expression" do it 'should output the expected result when dumped' do expect(dump(UNLESS(literal(true), literal(2), literal(5)))).to eq unindent(<<-TEXT (unless true (then 2) (else 5)) TEXT ) end it 'unless false {5} == 5' do expect(evaluate(UNLESS(literal(false), literal(5)))).to eq(5) end it 'unless true {5} == nil' do expect(evaluate(UNLESS(literal(true), literal(5)))).to eq(nil) end it 'unless true {2} else {5} == 5' do expect(evaluate(UNLESS(literal(true), literal(2), literal(5)))).to eq(5) end it 'unless true {2} elsif true {5} == 5' do # not supported by concrete syntax expect(evaluate(UNLESS(literal(true), literal(2), IF(literal(true), literal(5))))).to eq(5) end it 'unless true {2} elsif false {5} == nil' do # not supported by concrete syntax expect(evaluate(UNLESS(literal(true), literal(2), IF(literal(false), literal(5))))).to eq(nil) end end context "a case expression" do it 'should output the expected result when dumped' do expect(dump(CASE(literal(2), WHEN(literal(1), literal('wat')), WHEN([literal(2), literal(3)], literal('w00t')) ))).to eq unindent(<<-TEXT (case 2 (when (1) (then 'wat')) (when (2 3) (then 'w00t'))) TEXT ) expect(dump(CASE(literal(2), WHEN(literal(1), literal('wat')), WHEN([literal(2), literal(3)], literal('w00t')) ).default(literal(4)))).to eq unindent(<<-TEXT (case 2 (when (1) (then 'wat')) (when (2 3) (then 'w00t')) (when (:default) (then 4))) TEXT ) end it "case 1 { 1 : { 'w00t'} } == 'w00t'" do expect(evaluate(CASE(literal(1), WHEN(literal(1), literal('w00t'))))).to eq('w00t') end it "case 2 { 1,2,3 : { 'w00t'} } == 'w00t'" do expect(evaluate(CASE(literal(2), WHEN([literal(1), literal(2), literal(3)], literal('w00t'))))).to eq('w00t') end it "case 2 { 1,3 : {'wat'} 2: { 'w00t'} } == 'w00t'" do expect(evaluate(CASE(literal(2), WHEN([literal(1), literal(3)], literal('wat')), WHEN(literal(2), literal('w00t'))))).to eq('w00t') end it "case 2 { 1,3 : {'wat'} 5: { 'wat'} default: {'w00t'}} == 'w00t'" do expect(evaluate(CASE(literal(2), WHEN([literal(1), literal(3)], literal('wat')), WHEN(literal(5), literal('wat'))).default(literal('w00t')) )).to eq('w00t') end it "case 2 { 1,3 : {'wat'} 5: { 'wat'} } == nil" do expect(evaluate(CASE(literal(2), WHEN([literal(1), literal(3)], literal('wat')), WHEN(literal(5), literal('wat'))) )).to eq(nil) end it "case 'banana' { 1,3 : {'wat'} /.*ana.*/: { 'w00t'} } == w00t" do expect(evaluate(CASE(literal('banana'), WHEN([literal(1), literal(3)], literal('wat')), WHEN(literal(/.*ana.*/), literal('w00t'))) )).to eq('w00t') end context "with regular expressions" do it "should set numeric variables from the match" do expect(evaluate(CASE(literal('banana'), WHEN([literal(1), literal(3)], literal('wat')), WHEN(literal(/.*(ana).*/), var(1))) )).to eq('ana') end end end context "select expressions" do it 'should output the expected result when dumped' do expect(dump(literal(2).select( MAP(literal(1), literal('wat')), MAP(literal(2), literal('w00t')) ))).to eq("(? 2 (1 => 'wat') (2 => 'w00t'))") end it "1 ? {1 => 'w00t'} == 'w00t'" do expect(evaluate(literal(1).select(MAP(literal(1), literal('w00t'))))).to eq('w00t') end it "2 ? {1 => 'wat', 2 => 'w00t'} == 'w00t'" do expect(evaluate(literal(2).select( MAP(literal(1), literal('wat')), MAP(literal(2), literal('w00t')) ))).to eq('w00t') end it "3 ? {1 => 'wat', 2 => 'wat', default => 'w00t'} == 'w00t'" do expect(evaluate(literal(3).select( MAP(literal(1), literal('wat')), MAP(literal(2), literal('wat')), MAP(literal(:default), literal('w00t')) ))).to eq('w00t') end it "3 ? {1 => 'wat', default => 'w00t', 2 => 'wat'} == 'w00t'" do expect(evaluate(literal(3).select( MAP(literal(1), literal('wat')), MAP(literal(:default), literal('w00t')), MAP(literal(2), literal('wat')) ))).to eq('w00t') end it "should set numerical variables from match" do expect(evaluate(literal('banana').select( MAP(literal(1), literal('wat')), MAP(literal(/.*(ana).*/), var(1)) ))).to eq('ana') end end end end puppet-5.5.10/spec/unit/pops/evaluator/evaluator_rspec_helper.rb0000644005276200011600000000565413417161721025002 0ustar jenkinsjenkinsrequire 'puppet/pops' require 'puppet/pops/evaluator/evaluator_impl' require File.join(File.dirname(__FILE__), '/../factory_rspec_helper') module EvaluatorRspecHelper include FactoryRspecHelper # Evaluate a Factory wrapper round a model object in top scope + named scope # Optionally pass two or three model objects (typically blocks) to be executed # in top scope, named scope, and then top scope again. If a named_scope is used, it must # be preceded by the name of the scope. # The optional block is executed before the result of the last specified model object # is evaluated. This block gets the top scope as an argument. The intent is to pass # a block that asserts the state of the top scope after the operations. # def evaluate in_top_scope, scopename="x", in_named_scope = nil, in_top_scope_again = nil, &block node = Puppet::Node.new('localhost') compiler = Puppet::Parser::Compiler.new(node) # compiler creates the top scope if one is not present top_scope = compiler.topscope() # top_scope = Puppet::Parser::Scope.new(compiler) evaluator = Puppet::Pops::Evaluator::EvaluatorImpl.new Puppet.override(:loaders => compiler.loaders) do result = evaluator.evaluate(in_top_scope.model, top_scope) if in_named_scope other_scope = Puppet::Parser::Scope.new(compiler) result = evaluator.evaluate(in_named_scope.model, other_scope) end if in_top_scope_again result = evaluator.evaluate(in_top_scope_again.model, top_scope) end if block_given? block.call(top_scope) end result end end # Evaluate a Factory wrapper round a model object in top scope + local scope # Optionally pass two or three model objects (typically blocks) to be executed # in top scope, local scope, and then top scope again # The optional block is executed before the result of the last specified model object # is evaluated. This block gets the top scope as an argument. The intent is to pass # a block that asserts the state of the top scope after the operations. # def evaluate_l in_top_scope, in_local_scope = nil, in_top_scope_again = nil, &block node = Puppet::Node.new('localhost') compiler = Puppet::Parser::Compiler.new(node) # compiler creates the top scope if one is not present top_scope = compiler.topscope() evaluator = Puppet::Pops::Evaluator::EvaluatorImpl.new Puppet.override(:loaders => compiler.loaders) do result = evaluator.evaluate(in_top_scope.model, top_scope) if in_local_scope # This is really bad in 3.x scope top_scope.with_guarded_scope do top_scope.new_ephemeral(true) result = evaluator.evaluate(in_local_scope.model, top_scope) end end if in_top_scope_again result = evaluator.evaluate(in_top_scope_again.model, top_scope) end if block_given? block.call(top_scope) end result end end end puppet-5.5.10/spec/unit/pops/evaluator/json_strict_literal_evaluator_spec.rb0000644005276200011600000000307113417161721027405 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' require 'puppet/pops/evaluator/json_strict_literal_evaluator' describe "Puppet::Pops::Evaluator::JsonStrictLiteralEvaluator" do let(:parser) { Puppet::Pops::Parser::EvaluatingParser.new } let(:leval) { Puppet::Pops::Evaluator::JsonStrictLiteralEvaluator.new } { "1" => 1, "3.14" => 3.14, "true" => true, "false" => false, "'1'" => '1', "'a'" => 'a', '"a"' => 'a', 'a' => 'a', 'a::b' => 'a::b', 'undef' => nil, # collections '[1,2,3]' => [1,2,3], '{a=>1,b=>2}' => {'a' => 1, 'b' => 2}, '[undef]' => [nil], '{a=>undef}' => { 'a' => nil } }.each do |source, result| it "evaluates '#{source}' to #{result}" do expect(leval.literal(parser.parse_string(source))).to eq(result) end end [ '1+1', 'File', '[1,2, 1+2]', '{a=>1+1}', 'Integer[1]', '"x$y"', '"x${y}z"' ].each do |source| it "throws :not_literal for non literal expression '#{source}'" do expect{leval.literal(parser.parse_string('1+1'))}.to throw_symbol(:not_literal) end end [ 'default', '/.*/', '{1=>100}', '{undef => 10}', '{default => 10}', '{/.*/ => 10}', '{"ok" => {1 => 100}}', '[default]', '[[default]]', '[/.*/]', '[[/.*/]]', '[{1 => 100}]', ].each do |source| it "throws :not_literal for values not representable as pure JSON '#{source}'" do expect{leval.literal(parser.parse_string('1+1'))}.to throw_symbol(:not_literal) end end end puppet-5.5.10/spec/unit/pops/evaluator/literal_evaluator_spec.rb0000644005276200011600000000224513417161721024766 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' require 'puppet/pops/evaluator/literal_evaluator' describe "Puppet::Pops::Evaluator::LiteralEvaluator" do let(:parser) { Puppet::Pops::Parser::EvaluatingParser.new } let(:leval) { Puppet::Pops::Evaluator::LiteralEvaluator.new } { "1" => 1, "3.14" => 3.14, "true" => true, "false" => false, "'1'" => '1', "'a'" => 'a', '"a"' => 'a', 'a' => 'a', 'a::b' => 'a::b', # special values 'default' => :default, '/.*/' => /.*/, # collections '[1,2,3]' => [1,2,3], '{a=>1,b=>2}' => {'a' => 1, 'b' => 2}, }.each do |source, result| it "evaluates '#{source}' to #{result}" do expect(leval.literal(parser.parse_string(source))).to eq(result) end end it "evaluates undef to nil" do expect(leval.literal(parser.parse_string('undef'))).to be_nil end ['1+1', 'File', '[1,2, 1+2]', '{a=>1+1}', 'Integer[1]', '"x$y"', '"x${y}z"'].each do |source| it "throws :not_literal for non literal expression '#{source}'" do expect{leval.literal(parser.parse_string('1+1'))}.to throw_symbol(:not_literal) end end end puppet-5.5.10/spec/unit/pops/evaluator/logical_ops_spec.rb0000644005276200011600000000536013417161721023544 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' require 'puppet/pops/evaluator/evaluator_impl' # relative to this spec file (./) does not work as this file is loaded by rspec require File.join(File.dirname(__FILE__), '/evaluator_rspec_helper') describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do include EvaluatorRspecHelper context "When the evaluator performs boolean operations" do context "using operator AND" do it "true && true == true" do expect(evaluate(literal(true).and(literal(true)))).to eq(true) end it "false && true == false" do expect(evaluate(literal(false).and(literal(true)))).to eq(false) end it "true && false == false" do expect(evaluate(literal(true).and(literal(false)))).to eq(false) end it "false && false == false" do expect(evaluate(literal(false).and(literal(false)))).to eq(false) end end context "using operator OR" do it "true || true == true" do expect(evaluate(literal(true).or(literal(true)))).to eq(true) end it "false || true == true" do expect(evaluate(literal(false).or(literal(true)))).to eq(true) end it "true || false == true" do expect(evaluate(literal(true).or(literal(false)))).to eq(true) end it "false || false == false" do expect(evaluate(literal(false).or(literal(false)))).to eq(false) end end context "using operator NOT" do it "!false == true" do expect(evaluate(literal(false).not())).to eq(true) end it "!true == false" do expect(evaluate(literal(true).not())).to eq(false) end end context "on values requiring boxing to Boolean" do it "'x' == true" do expect(evaluate(literal('x').not())).to eq(false) end it "'' == true" do expect(evaluate(literal('').not())).to eq(false) end it ":undef == false" do expect(evaluate(literal(:undef).not())).to eq(true) end end context "connectives should stop when truth is obtained" do it "true && false && error == false (and no failure)" do expect(evaluate(literal(false).and(literal('0xwtf') + literal(1)).and(literal(true)))).to eq(false) end it "false || true || error == true (and no failure)" do expect(evaluate(literal(true).or(literal('0xwtf') + literal(1)).or(literal(false)))).to eq(true) end it "false || false || error == error (false positive test)" do # TODO: Change the exception type expect {evaluate(literal(true).and(literal('0xwtf') + literal(1)).or(literal(false)))}.to raise_error(Puppet::ParseError) end end end end puppet-5.5.10/spec/unit/pops/evaluator/string_interpolation_spec.rb0000644005276200011600000000351513417161721025526 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' require 'puppet/pops/evaluator/evaluator_impl' # relative to this spec file (./) does not work as this file is loaded by rspec require File.join(File.dirname(__FILE__), '/evaluator_rspec_helper') describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do include EvaluatorRspecHelper context "When evaluator performs string interpolation" do it "should interpolate a bare word as a variable name, \"${var}\"" do a_block = block(var('a').set(literal(10)), string('value is ', text(fqn('a')), ' yo')) expect(evaluate(a_block)).to eq('value is 10 yo') end it "should interpolate a variable in a text expression, \"${$var}\"" do a_block = block(var('a').set(literal(10)), string('value is ', text(var(fqn('a'))), ' yo')) expect(evaluate(a_block)).to eq('value is 10 yo') end it "should interpolate a variable, \"$var\"" do a_block = block(var('a').set(literal(10)), string('value is ', var(fqn('a')), ' yo')) expect(evaluate(a_block)).to eq('value is 10 yo') end it "should interpolate any expression in a text expression, \"${$var*2}\"" do a_block = block(var('a').set(literal(5)), string('value is ', text(var(fqn('a')) * literal(2)) , ' yo')) expect(evaluate(a_block)).to eq('value is 10 yo') end it "should interpolate any expression without a text expression, \"${$var*2}\"" do # there is no concrete syntax for this, but the parser can generate this simpler # equivalent form where the expression is not wrapped in a TextExpression a_block = block(var('a').set(literal(5)), string('value is ', var(fqn('a')) * literal(2) , ' yo')) expect(evaluate(a_block)).to eq('value is 10 yo') end # TODO: Add function call tests - Pending implementation of calls in the evaluator end end puppet-5.5.10/spec/unit/pops/evaluator/variables_spec.rb0000644005276200011600000000704613417161721023224 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' require 'puppet/pops/evaluator/evaluator_impl' # This file contains basic testing of variable references and assignments # using a top scope and a local scope. # It does not test variables and named scopes. # # relative to this spec file (./) does not work as this file is loaded by rspec require File.join(File.dirname(__FILE__), '/evaluator_rspec_helper') describe 'Puppet::Pops::Impl::EvaluatorImpl' do include EvaluatorRspecHelper context "When the evaluator deals with variables" do context "it should handle" do it "simple assignment and dereference" do expect(evaluate_l(block( var('a').set(literal(2)+literal(2)), var('a')))).to eq(4) end it "local scope shadows top scope" do top_scope_block = block( var('a').set(literal(2)+literal(2)), var('a')) local_scope_block = block( var('a').set(var('a') + literal(2)), var('a')) expect(evaluate_l(top_scope_block, local_scope_block)).to eq(6) end it "shadowed in local does not affect parent scope" do top_scope_block = block( var('a').set(literal(2)+literal(2)), var('a')) local_scope_block = block( var('a').set(var('a') + literal(2)), var('a')) top_scope_again = var('a') expect(evaluate_l(top_scope_block, local_scope_block, top_scope_again)).to eq(4) end it "access to global names works in top scope" do top_scope_block = block( var('a').set(literal(2)+literal(2)), var('::a')) expect(evaluate_l(top_scope_block)).to eq(4) end it "access to global names works in local scope" do top_scope_block = block( var('a').set(literal(2)+literal(2))) local_scope_block = block( var('a').set(literal(100)), var('b').set(var('::a')+literal(2)), var('b')) expect(evaluate_l(top_scope_block, local_scope_block)).to eq(6) end it "can not change a variable value in same scope" do expect { evaluate_l(block(var('a').set(literal(10)), var('a').set(literal(20)))) }.to raise_error(/Cannot reassign variable '\$a'/) end context "access to numeric variables" do it "without a match" do expect(evaluate_l(block(literal(2) + literal(2), [var(0), var(1), var(2), var(3)]))).to eq([nil, nil, nil, nil]) end it "after a match" do expect(evaluate_l(block(literal('abc') =~ literal(/(a)(b)(c)/), [var(0), var(1), var(2), var(3)]))).to eq(['abc', 'a', 'b', 'c']) end it "after a failed match" do expect(evaluate_l(block(literal('abc') =~ literal(/(x)(y)(z)/), [var(0), var(1), var(2), var(3)]))).to eq([nil, nil, nil, nil]) end it "a failed match does not alter previous match" do expect(evaluate_l(block( literal('abc') =~ literal(/(a)(b)(c)/), literal('abc') =~ literal(/(x)(y)(z)/), [var(0), var(1), var(2), var(3)]))).to eq(['abc', 'a', 'b', 'c']) end it "a new match completely shadows previous match" do expect(evaluate_l(block( literal('abc') =~ literal(/(a)(b)(c)/), literal('abc') =~ literal(/(a)bc/), [var(0), var(1), var(2), var(3)]))).to eq(['abc', 'a', nil, nil]) end it "after a match with variable referencing a non existing group" do expect(evaluate_l(block(literal('abc') =~ literal(/(a)(b)(c)/), [var(0), var(1), var(2), var(3), var(4)]))).to eq(['abc', 'a', 'b', 'c', nil]) end end end end end puppet-5.5.10/spec/unit/pops/evaluator/arithmetic_ops_spec.rb0000644005276200011600000003241313417161722024263 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/compiler' require 'puppet/pops' require 'puppet/pops/evaluator/evaluator_impl' # relative to this spec file (./) does not work as this file is loaded by rspec require File.join(File.dirname(__FILE__), '/evaluator_rspec_helper') describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do include EvaluatorRspecHelper context "When the evaluator performs arithmetic" do context "on Integers" do it "2 + 2 == 4" do; expect(evaluate(literal(2) + literal(2))).to eq(4) ; end it "7 - 3 == 4" do; expect(evaluate(literal(7) - literal(3))).to eq(4) ; end it "6 * 3 == 18" do; expect(evaluate(literal(6) * literal(3))).to eq(18); end it "6 / 3 == 2" do; expect(evaluate(literal(6) / literal(3))).to eq(2) ; end it "6 % 3 == 0" do; expect(evaluate(literal(6) % literal(3))).to eq(0) ; end it "10 % 3 == 1" do; expect(evaluate(literal(10) % literal(3))).to eq(1); end it "-(6/3) == -2" do; expect(evaluate(minus(literal(6) / literal(3)))).to eq(-2) ; end it "-6/3 == -2" do; expect(evaluate(minus(literal(6)) / literal(3))).to eq(-2) ; end it "8 >> 1 == 4" do; expect(evaluate(literal(8) >> literal(1))).to eq(4) ; end it "8 << 1 == 16" do; expect(evaluate(literal(8) << literal(1))).to eq(16); end it "8 >> -1 == 16" do; expect(evaluate(literal(8) >> literal(-1))).to eq(16) ; end it "8 << -1 == 4" do; expect(evaluate(literal(8) << literal(-1))).to eq(4); end end # 64 bit signed integer max and min MAX_INTEGER = 0x7fffffffffffffff MIN_INTEGER = -0x8000000000000000 context "on integer values that cause 64 bit overflow" do it "MAX + 1 => error" do expect{ evaluate(literal(MAX_INTEGER) + literal(1)) }.to raise_error(/resulted in a value outside of Puppet Integer max range/) end it "MAX - -1 => error" do expect{ evaluate(literal(MAX_INTEGER) - literal(-1)) }.to raise_error(/resulted in a value outside of Puppet Integer max range/) end it "MAX * 2 => error" do expect{ evaluate(literal(MAX_INTEGER) * literal(2)) }.to raise_error(/resulted in a value outside of Puppet Integer max range/) end it "(MAX+1)*2 / 2 => error" do expect{ evaluate(literal((MAX_INTEGER+1)*2) / literal(2)) }.to raise_error(/resulted in a value outside of Puppet Integer max range/) end it "MAX << 1 => error" do expect{ evaluate(literal(MAX_INTEGER) << literal(1)) }.to raise_error(/resulted in a value outside of Puppet Integer max range/) end it "((MAX+1)*2) << 1 => error" do expect{ evaluate(literal((MAX_INTEGER+1)*2) >> literal(1)) }.to raise_error(/resulted in a value outside of Puppet Integer max range/) end it "MIN - 1 => error" do expect{ evaluate(literal(MIN_INTEGER) - literal(1)) }.to raise_error(/resulted in a value outside of Puppet Integer min range/) end it "does not error on the border values" do expect(evaluate(literal(MAX_INTEGER) + literal(MIN_INTEGER))).to eq(MAX_INTEGER+MIN_INTEGER) end end context "on Floats" do it "2.2 + 2.2 == 4.4" do; expect(evaluate(literal(2.2) + literal(2.2))).to eq(4.4) ; end it "7.7 - 3.3 == 4.4" do; expect(evaluate(literal(7.7) - literal(3.3))).to eq(4.4) ; end it "6.1 * 3.1 == 18.91" do; expect(evaluate(literal(6.1) * literal(3.1))).to eq(18.91); end it "6.6 / 3.3 == 2.0" do; expect(evaluate(literal(6.6) / literal(3.3))).to eq(2.0) ; end it "-(6.0/3.0) == -2.0" do; expect(evaluate(minus(literal(6.0) / literal(3.0)))).to eq(-2.0); end it "-6.0/3.0 == -2.0" do; expect(evaluate(minus(literal(6.0)) / literal(3.0))).to eq(-2.0); end it "6.6 % 3.3 == 0.0" do; expect { evaluate(literal(6.6) % literal(3.3))}.to raise_error(Puppet::ParseError); end it "10.0 % 3.0 == 1.0" do; expect { evaluate(literal(10.0) % literal(3.0))}.to raise_error(Puppet::ParseError); end it "3.14 << 2 == error" do; expect { evaluate(literal(3.14) << literal(2))}.to raise_error(Puppet::ParseError); end it "3.14 >> 2 == error" do; expect { evaluate(literal(3.14) >> literal(2))}.to raise_error(Puppet::ParseError); end end context "on strings requiring boxing to Numeric" do it "'2' + '2' == 4" do expect(evaluate(literal('2') + literal('2'))).to eq(4) end it "'2.2' + '2.2' == 4.4" do expect(evaluate(literal('2.2') + literal('2.2'))).to eq(4.4) end it "'0xF7' + '0x8' == 0xFF" do expect(evaluate(literal('0xF7') + literal('0x8'))).to eq(0xFF) end it "'0367' + '010' == 0xFF" do expect(evaluate(literal('0367') + literal('010'))).to eq(0xFF) end it "'0888' + '010' == error" do expect { evaluate(literal('0888') + literal('010'))}.to raise_error(Puppet::ParseError) end it "'0xWTF' + '010' == error" do expect { evaluate(literal('0xWTF') + literal('010'))}.to raise_error(Puppet::ParseError) end it "'0x12.3' + '010' == error" do expect { evaluate(literal('0x12.3') + literal('010'))}.to raise_error(Puppet::ParseError) end it "'012.3' + '010' == 20.3 (not error, floats can start with 0)" do expect(evaluate(literal('012.3') + literal('010'))).to eq(20.3) end end context 'on timespans' do include PuppetSpec::Compiler it 'Timespan + Timespan = Timespan' do code = 'notice(assert_type(Timespan, Timespan({days => 3}) + Timespan({hours => 12})))' expect(eval_and_collect_notices(code)).to eql(['3-12:00:00.0']) end it 'Timespan - Timespan = Timespan' do code = 'notice(assert_type(Timespan, Timespan({days => 3}) - Timespan({hours => 12})))' expect(eval_and_collect_notices(code)).to eql(['2-12:00:00.0']) end it 'Timespan + -Timespan = Timespan' do code = 'notice(assert_type(Timespan, Timespan({days => 3}) + -Timespan({hours => 12})))' expect(eval_and_collect_notices(code)).to eql(['2-12:00:00.0']) end it 'Timespan - -Timespan = Timespan' do code = 'notice(assert_type(Timespan, Timespan({days => 3}) - -Timespan({hours => 12})))' expect(eval_and_collect_notices(code)).to eql(['3-12:00:00.0']) end it 'Timespan / Timespan = Float' do code = "notice(assert_type(Float, Timespan({days => 3}) / Timespan('0-12:00:00')))" expect(eval_and_collect_notices(code)).to eql(['6.0']) end it 'Timespan * Timespan is an error' do code = 'notice(Timespan({days => 3}) * Timespan({hours => 12}))' expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /A Timestamp cannot be multiplied by a Timespan/) end it 'Timespan + Numeric = Timespan (numeric treated as seconds)' do code = 'notice(assert_type(Timespan, Timespan({days => 3}) + 7300.0))' expect(eval_and_collect_notices(code)).to eql(['3-02:01:40.0']) end it 'Timespan - Numeric = Timespan (numeric treated as seconds)' do code = "notice(assert_type(Timespan, Timespan({days => 3}) - 7300.123))" expect(eval_and_collect_notices(code)).to eql(['2-21:58:19.877']) end it 'Timespan * Numeric = Timespan (numeric treated as seconds)' do code = "notice(strftime(assert_type(Timespan, Timespan({days => 3}) * 2), '%D'))" expect(eval_and_collect_notices(code)).to eql(['6']) end it 'Numeric + Timespan = Timespan (numeric treated as seconds)' do code = 'notice(assert_type(Timespan, 7300.0 + Timespan({days => 3})))' expect(eval_and_collect_notices(code)).to eql(['3-02:01:40.0']) end it 'Numeric - Timespan = Timespan (numeric treated as seconds)' do code = "notice(strftime(assert_type(Timespan, 300000 - Timespan({days => 3})), '%H:%M'))" expect(eval_and_collect_notices(code)).to eql(['11:20']) end it 'Numeric * Timespan = Timespan (numeric treated as seconds)' do code = "notice(strftime(assert_type(Timespan, 2 * Timespan({days => 3})), '%D'))" expect(eval_and_collect_notices(code)).to eql(['6']) end it 'Timespan + Timestamp = Timestamp' do code = "notice(assert_type(Timestamp, Timespan({days => 3}) + Timestamp('2016-08-27T16:44:49.999 UTC')))" expect(eval_and_collect_notices(code)).to eql(['2016-08-30T16:44:49.999000000 UTC']) end it 'Timespan - Timestamp is an error' do code = 'notice(Timespan({days => 3}) - Timestamp())' expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /A Timestamp cannot be subtracted from a Timespan/) end it 'Timespan * Timestamp is an error' do code = 'notice(Timespan({days => 3}) * Timestamp())' expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /A Timestamp cannot be multiplied by a Timestamp/) end it 'Timespan / Timestamp is an error' do code = 'notice(Timespan({days => 3}) / Timestamp())' expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /A Timespan cannot be divided by a Timestamp/) end end context 'on timestamps' do include PuppetSpec::Compiler it 'Timestamp + Timestamp is an error' do code = 'notice(Timestamp() + Timestamp())' expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /A Timestamp cannot be added to a Timestamp/) end it 'Timestamp + Timespan = Timestamp' do code = "notice(assert_type(Timestamp, Timestamp('2016-10-10') + Timespan('0-12:00:00')))" expect(eval_and_collect_notices(code)).to eql(['2016-10-10T12:00:00.000000000 UTC']) end it 'Timestamp + Numeric = Timestamp' do code = "notice(assert_type(Timestamp, Timestamp('2016-10-10T12:00:00.000') + 3600.123))" expect(eval_and_collect_notices(code)).to eql(['2016-10-10T13:00:00.123000000 UTC']) end it 'Numeric + Timestamp = Timestamp' do code = "notice(assert_type(Timestamp, 3600.123 + Timestamp('2016-10-10T12:00:00.000')))" expect(eval_and_collect_notices(code)).to eql(['2016-10-10T13:00:00.123000000 UTC']) end it 'Timestamp - Timestamp = Timespan' do code = "notice(assert_type(Timespan, Timestamp('2016-10-10') - Timestamp('2015-10-10')))" expect(eval_and_collect_notices(code)).to eql(['366-00:00:00.0']) end it 'Timestamp - Timespan = Timestamp' do code = "notice(assert_type(Timestamp, Timestamp('2016-10-10') - Timespan('0-12:00:00')))" expect(eval_and_collect_notices(code)).to eql(['2016-10-09T12:00:00.000000000 UTC']) end it 'Timestamp - Numeric = Timestamp' do code = "notice(assert_type(Timestamp, Timestamp('2016-10-10') - 3600.123))" expect(eval_and_collect_notices(code)).to eql(['2016-10-09T22:59:59.877000000 UTC']) end it 'Numeric - Timestamp = Timestamp' do code = "notice(assert_type(Timestamp, 123 - Timestamp('2016-10-10')))" expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /Operator '-' is not applicable.*when right side is a Timestamp/) end it 'Timestamp / Timestamp is an error' do code = "notice(Timestamp('2016-10-10') / Timestamp())" expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /Operator '\/' is not applicable to a Timestamp/) end it 'Timestamp / Timespan is an error' do code = "notice(Timestamp('2016-10-10') / Timespan('0-12:00:00'))" expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /Operator '\/' is not applicable to a Timestamp/) end it 'Timestamp / Numeric is an error' do code = "notice(Timestamp('2016-10-10') / 3600.123)" expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /Operator '\/' is not applicable to a Timestamp/) end it 'Numeric / Timestamp is an error' do code = "notice(3600.123 / Timestamp('2016-10-10'))" expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /Operator '\/' is not applicable.*when right side is a Timestamp/) end it 'Timestamp * Timestamp is an error' do code = "notice(Timestamp('2016-10-10') * Timestamp())" expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /Operator '\*' is not applicable to a Timestamp/) end it 'Timestamp * Timespan is an error' do code = "notice(Timestamp('2016-10-10') * Timespan('0-12:00:00'))" expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /Operator '\*' is not applicable to a Timestamp/) end it 'Timestamp * Numeric is an error' do code = "notice(Timestamp('2016-10-10') * 3600.123)" expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /Operator '\*' is not applicable to a Timestamp/) end it 'Numeric * Timestamp is an error' do code = "notice(3600.123 * Timestamp('2016-10-10'))" expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /Operator '\*' is not applicable.*when right side is a Timestamp/) end end end end puppet-5.5.10/spec/unit/pops/evaluator/evaluating_parser_spec.rb0000644005276200011600000017726413417161722025002 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' require 'puppet/pops/evaluator/evaluator_impl' require 'puppet/loaders' require 'puppet_spec/pops' require 'puppet_spec/scope' require 'puppet/parser/e4_parser_adapter' # relative to this spec file (./) does not work as this file is loaded by rspec #require File.join(File.dirname(__FILE__), '/evaluator_rspec_helper') describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do include PuppetSpec::Pops include PuppetSpec::Scope before(:each) do Puppet[:strict_variables] = true Puppet[:data_binding_terminus] = 'none' # Tests needs a known configuration of node/scope/compiler since it parses and evaluates # snippets as the compiler will evaluate them, butwithout the overhead of compiling a complete # catalog for each tested expression. # @parser = Puppet::Pops::Parser::EvaluatingParser.new @node = Puppet::Node.new('node.example.com') @node.environment = environment @compiler = Puppet::Parser::Compiler.new(@node) @scope = Puppet::Parser::Scope.new(@compiler) @scope.source = Puppet::Resource::Type.new(:node, 'node.example.com') @scope.parent = @compiler.topscope Puppet.push_context(:loaders => @compiler.loaders) end after(:each) do Puppet.pop_context end let(:environment) { Puppet::Node::Environment.create(:testing, []) } let(:parser) { @parser } let(:scope) { @scope } types = Puppet::Pops::Types::TypeFactory context "When evaluator evaluates literals" do { "1" => 1, "010" => 8, "0x10" => 16, "3.14" => 3.14, "0.314e1" => 3.14, "31.4e-1" => 3.14, "'1'" => '1', "'banana'" => 'banana', '"banana"' => 'banana', "banana" => 'banana', "banana::split" => 'banana::split', "false" => false, "true" => true, "Array" => types.array_of_any, "/.*/" => /.*/ }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) end end it 'should error when it encounters an unknown resource' do expect {parser.evaluate_string(scope, '$a = SantaClause', __FILE__)}.to raise_error(/Resource type not found: SantaClause/) end it 'should error when it encounters an unknown resource with a parameter' do expect {parser.evaluate_string(scope, '$b = ToothFairy[emea]', __FILE__)}.to raise_error(/Resource type not found: ToothFairy/) end end context "When the evaluator evaluates Lists and Hashes" do { "[]" => [], "[1,2,3]" => [1,2,3], "[1,[2.0, 2.1, [2.2]],[3.0, 3.1]]" => [1,[2.0, 2.1, [2.2]],[3.0, 3.1]], "[2 + 2]" => [4], "[1,2,3] == [1,2,3]" => true, "[1,2,3] != [2,3,4]" => true, "[1,2,3] == [2,2,3]" => false, "[1,2,3] != [1,2,3]" => false, "[1,2,3][2]" => 3, "[1,2,3] + [4,5]" => [1,2,3,4,5], "[1,2,3, *[4,5]]" => [1,2,3,4,5], "[1,2,3, (*[4,5])]" => [1,2,3,4,5], "[1,2,3, ((*[4,5]))]" => [1,2,3,4,5], "[1,2,3] + [[4,5]]" => [1,2,3,[4,5]], "[1,2,3] + 4" => [1,2,3,4], "[1,2,3] << [4,5]" => [1,2,3,[4,5]], "[1,2,3] << {'a' => 1, 'b'=>2}" => [1,2,3,{'a' => 1, 'b'=>2}], "[1,2,3] << 4" => [1,2,3,4], "[1,2,3,4] - [2,3]" => [1,4], "[1,2,3,4] - [2,5]" => [1,3,4], "[1,2,3,4] - 2" => [1,3,4], "[1,2,3,[2],4] - 2" => [1,3,[2],4], "[1,2,3,[2,3],4] - [[2,3]]" => [1,2,3,4], "[1,2,3,3,2,4,2,3] - [2,3]" => [1,4], "[1,2,3,['a',1],['b',2]] - {'a' => 1, 'b'=>2}" => [1,2,3], "[1,2,3,{'a'=>1,'b'=>2}] - [{'a' => 1, 'b'=>2}]" => [1,2,3], "[1,2,3] + {'a' => 1, 'b'=>2}" => [1,2,3,['a',1],['b',2]], }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) end end { "[1,2,3][a]" => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end { "{}" => {}, "{'a'=>1,'b'=>2}" => {'a'=>1,'b'=>2}, "{'a'=>1,'b'=>{'x'=>2.1,'y'=>2.2}}" => {'a'=>1,'b'=>{'x'=>2.1,'y'=>2.2}}, "{'a'=> 2 + 2}" => {'a'=> 4}, "{'a'=> 1, 'b'=>2} == {'a'=> 1, 'b'=>2}" => true, "{'a'=> 1, 'b'=>2} != {'x'=> 1, 'b'=>2}" => true, "{'a'=> 1, 'b'=>2} == {'a'=> 2, 'b'=>3}" => false, "{'a'=> 1, 'b'=>2} != {'a'=> 1, 'b'=>2}" => false, "{a => 1, b => 2}[b]" => 2, "{2+2 => sum, b => 2}[4]" => 'sum', "{'a'=>1, 'b'=>2} + {'c'=>3}" => {'a'=>1,'b'=>2,'c'=>3}, "{'a'=>1, 'b'=>2} + {'b'=>3}" => {'a'=>1,'b'=>3}, "{'a'=>1, 'b'=>2} + ['c', 3, 'b', 3]" => {'a'=>1,'b'=>3, 'c'=>3}, "{'a'=>1, 'b'=>2} + [['c', 3], ['b', 3]]" => {'a'=>1,'b'=>3, 'c'=>3}, "{'a'=>1, 'b'=>2} - {'b' => 3}" => {'a'=>1}, "{'a'=>1, 'b'=>2, 'c'=>3} - ['b', 'c']" => {'a'=>1}, "{'a'=>1, 'b'=>2, 'c'=>3} - 'c'" => {'a'=>1, 'b'=>2}, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) end end { "{'a' => 1, 'b'=>2} << 1" => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end end context "When the evaluator perform comparisons" do { "'a' == 'a'" => true, "'a' == 'b'" => false, "'a' != 'a'" => false, "'a' != 'b'" => true, "'a' < 'b' " => true, "'a' < 'a' " => false, "'b' < 'a' " => false, "'a' <= 'b'" => true, "'a' <= 'a'" => true, "'b' <= 'a'" => false, "'a' > 'b' " => false, "'a' > 'a' " => false, "'b' > 'a' " => true, "'a' >= 'b'" => false, "'a' >= 'a'" => true, "'b' >= 'a'" => true, "'a' == 'A'" => true, "'a' != 'A'" => false, "'a' > 'A'" => false, "'a' >= 'A'" => true, "'A' < 'a'" => false, "'A' <= 'a'" => true, "1 == 1" => true, "1 == 2" => false, "1 != 1" => false, "1 != 2" => true, "1 < 2 " => true, "1 < 1 " => false, "2 < 1 " => false, "1 <= 2" => true, "1 <= 1" => true, "2 <= 1" => false, "1 > 2 " => false, "1 > 1 " => false, "2 > 1 " => true, "1 >= 2" => false, "1 >= 1" => true, "2 >= 1" => true, "1 == 1.0 " => true, "1 < 1.1 " => true, "1.0 == 1 " => true, "1.0 < 2 " => true, "'1.0' < 'a'" => true, "'1.0' < '' " => false, "'1.0' < ' '" => false, "'a' > '1.0'" => true, "/.*/ == /.*/ " => true, "/.*/ != /a.*/" => true, "true == true " => true, "false == false" => true, "true == false" => false, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) end end { "a > 1" => /String > Integer/, "a >= 1" => /String >= Integer/, "a < 1" => /String < Integer/, "a <= 1" => /String <= Integer/, "1 > a" => /Integer > String/, "1 >= a" => /Integer >= String/, "1 < a" => /Integer < String/, "1 <= a" => /Integer <= String/, }.each do | source, error| it "should not allow comparison of String and Number '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__)}.to raise_error(error) end end { "'a' =~ /.*/" => true, "'a' =~ '.*'" => true, "/.*/ != /a.*/" => true, "'a' !~ /b.*/" => true, "'a' !~ 'b.*'" => true, '$x = a; a =~ "$x.*"' => true, "a =~ Pattern['a.*']" => true, "a =~ Regexp['a.*']" => false, # String is not subtype of Regexp. PUP-957 "$x = /a.*/ a =~ $x" => true, "$x = Pattern['a.*'] a =~ $x" => true, "1 =~ Integer" => true, "1 !~ Integer" => false, "undef =~ NotUndef" => false, "undef !~ NotUndef" => true, "[1,2,3] =~ Array[Integer[1,10]]" => true, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) end end { "666 =~ /6/" => :error, "[a] =~ /a/" => :error, "{a=>1} =~ /a/" => :error, "/a/ =~ /a/" => :error, "Array =~ /A/" => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end { "1 in [1,2,3]" => true, "4 in [1,2,3]" => false, "a in {x=>1, a=>2}" => true, "z in {x=>1, a=>2}" => false, "ana in bananas" => true, "xxx in bananas" => false, "/ana/ in bananas" => true, "/xxx/ in bananas" => false, "FILE in profiler" => false, # FILE is a type, not a String "'FILE' in profiler" => true, "String[1] in bananas" => false, # Philosophically true though :-) "ana in 'BANANAS'" => true, "/ana/ in 'BANANAS'" => false, "/ANA/ in 'BANANAS'" => true, "xxx in 'BANANAS'" => false, "[2,3] in [1,[2,3],4]" => true, "[2,4] in [1,[2,3],4]" => false, "[a,b] in ['A',['A','B'],'C']" => true, "[x,y] in ['A',['A','B'],'C']" => false, "a in {a=>1}" => true, "x in {a=>1}" => false, "'A' in {a=>1}" => true, "'X' in {a=>1}" => false, "a in {'A'=>1}" => true, "x in {'A'=>1}" => false, "/xxx/ in {'aaaxxxbbb'=>1}" => true, "/yyy/ in {'aaaxxxbbb'=>1}" => false, "15 in [1, 0xf]" => true, "15 in [1, '0xf']" => false, "'15' in [1, 0xf]" => false, "15 in [1, 115]" => false, "1 in [11, '111']" => false, "'1' in [11, '111']" => false, "Array[Integer] in [2, 3]" => false, "Array[Integer] in [2, [3, 4]]" => true, "Array[Integer] in [2, [a, 4]]" => false, "Integer in { 2 =>'a'}" => true, "Integer[5,10] in [1,5,3]" => true, "Integer[5,10] in [1,2,3]" => false, "Integer in {'a'=>'a'}" => false, "Integer in {'a'=>1}" => false, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) end end { "if /(ana)/ in bananas {$1}" => 'ana', "if /(xyz)/ in bananas {$1} else {$1}" => nil, "$a = bananas =~ /(ana)/; $b = /(xyz)/ in bananas; $1" => 'ana', "$a = xyz =~ /(xyz)/; $b = /(ana)/ in bananas; $1" => 'ana', "if /p/ in [pineapple, bananas] {$0}" => 'p', "if /b/ in [pineapple, bananas] {$0}" => 'b', }.each do |source, result| it "sets match variables for a regexp search using in such that '#{source}' produces '#{result}'" do expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) end end { 'Any' => ['NotUndef', 'Data', 'Scalar', 'Numeric', 'Integer', 'Float', 'Boolean', 'String', 'Pattern', 'Collection', 'Array', 'Hash', 'CatalogEntry', 'Resource', 'Class', 'Undef', 'File' ], # Note, Data > Collection is false (so not included) 'Data' => ['ScalarData', 'Numeric', 'Integer', 'Float', 'Boolean', 'String', 'Pattern', 'Array[Data]', 'Hash[String,Data]',], 'Scalar' => ['Numeric', 'Integer', 'Float', 'Boolean', 'String', 'Pattern'], 'Numeric' => ['Integer', 'Float'], 'CatalogEntry' => ['Class', 'Resource', 'File'], 'Integer[1,10]' => ['Integer[2,3]'], }.each do |general, specials| specials.each do |special | it "should compute that #{general} > #{special}" do expect(parser.evaluate_string(scope, "#{general} > #{special}", __FILE__)).to eq(true) end it "should compute that #{special} < #{general}" do expect(parser.evaluate_string(scope, "#{special} < #{general}", __FILE__)).to eq(true) end it "should compute that #{general} != #{special}" do expect(parser.evaluate_string(scope, "#{special} != #{general}", __FILE__)).to eq(true) end end end { 'Integer[1,10] > Integer[2,3]' => true, 'Integer[1,10] == Integer[2,3]' => false, 'Integer[1,10] > Integer[0,5]' => false, 'Integer[1,10] > Integer[1,10]' => false, 'Integer[1,10] >= Integer[1,10]' => true, 'Integer[1,10] == Integer[1,10]' => true, }.each do |source, result| it "should parse and evaluate the integer range comparison expression '#{source}' to #{result}" do expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) end end end context "When the evaluator performs arithmetic" do context "on Integers" do { "2+2" => 4, "2 + 2" => 4, "7 - 3" => 4, "6 * 3" => 18, "6 / 3" => 2, "6 % 3" => 0, "10 % 3" => 1, "-(6/3)" => -2, "-6/3 " => -2, "8 >> 1" => 4, "8 << 1" => 16, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) end end context "on Floats" do { "2.2 + 2.2" => 4.4, "7.7 - 3.3" => 4.4, "6.1 * 3.1" => 18.91, "6.6 / 3.3" => 2.0, "-(6.0/3.0)" => -2.0, "-6.0/3.0 " => -2.0, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) end end { "3.14 << 2" => :error, "3.14 >> 2" => :error, "6.6 % 3.3" => 0.0, "10.0 % 3.0" => 1.0, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end end context "on strings requiring boxing to Numeric" do let(:logs) { [] } let(:notices) { logs.select { |log| log.level == :notice }.map { |log| log.message } } let(:warnings) { logs.select { |log| log.level == :warning }.map { |log| log.message } } let(:debugs) { logs.select { |log| log.level == :debug }.map { |log| log.message } } { "'2' + '2'" => 4, "'-2' + '2'" => 0, "'- 2' + '2'" => 0, '"-\t 2" + "2"' => 0, "'+2' + '2'" => 4, "'+ 2' + '2'" => 4, "'2.2' + '2.2'" => 4.4, "'-2.2' + '2.2'" => 0.0, "'0xF7' + '010'" => 0xFF, "'0xF7' + '0x8'" => 0xFF, "'0367' + '010'" => 0xFF, "'012.3' + '010'" => 20.3, "'-0x2' + '0x4'" => 2, "'+0x2' + '0x4'" => 6, "'-02' + '04'" => 2, "'+02' + '04'" => 6, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) end end { "'2' + 2" => 2, "'4' - 2" => 4, "'2' * 2" => 2, "'2' / 1" => 2, "'8' >> 1" => 8, "'4' << 1" => 4, "'10' % 3" => 10, }.each do |source, coerced_val| it "should warn about numeric coercion in '#{source}' when strict = warning" do Puppet[:strict] = :warning collect_notices(source) expect(warnings).to include(/The string '#{coerced_val}' was automatically coerced to the numerical value #{coerced_val}/) end it "should not warn about numeric coercion in '#{source}' if strict = off" do Puppet[:strict] = :off collect_notices(source) expect(warnings).to_not include(/The string '#{coerced_val}' was automatically coerced to the numerical value #{coerced_val}/) end it "should error when finding numeric coercion in '#{source}' if strict = error" do Puppet[:strict] = :error expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error( /The string '#{coerced_val}' was automatically coerced to the numerical value #{coerced_val}/ ) end end { "'0888' + '010'" => :error, "'0xWTF' + '010'" => :error, "'0x12.3' + '010'" => :error, '"-\n 2" + "2"' => :error, '"-\v 2" + "2"' => :error, '"-2\n" + "2"' => :error, '"-2\n " + "2"' => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end end end end # arithmetic context "When the evaluator evaluates assignment" do { "$a = 5" => 5, "$a = 5; $a" => 5, "$a = 5; $b = 6; $a" => 5, "$a = $b = 5; $a == $b" => true, "[$a] = 1 $a" => 1, "[$a] = [1] $a" => 1, "[$a, $b] = [1,2] $a+$b" => 3, "[$a, [$b, $c]] = [1,[2, 3]] $a+$b+$c" => 6, "[$a] = {a => 1} $a" => 1, "[$a, $b] = {a=>1,b=>2} $a+$b" => 3, "[$a, [$b, $c]] = {a=>1,[b,c] =>{b=>2, c=>3}} $a+$b+$c" => 6, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) end end [ "[a,b,c] = [1,2,3]", "[a,b,c] = {b=>2,c=>3,a=>1}", "[$a, $b] = 1", "[$a, $b] = [1,2,3]", "[$a, [$b,$c]] = [1,[2]]", "[$a, [$b,$c]] = [1,[2,3,4]]", "[$a, $b] = {a=>1}", "[$a, [$b, $c]] = {a=>1, b =>{b=>2, c=>3}}", ].each do |source| it "should parse and evaluate the expression '#{source}' to error" do expect { parser.evaluate_string(scope, source, __FILE__)}.to raise_error(Puppet::ParseError) end end end context "When the evaluator evaluates conditionals" do { "if true {5}" => 5, "if false {5}" => nil, "if false {2} else {5}" => 5, "if false {2} elsif true {5}" => 5, "if false {2} elsif false {5}" => nil, "unless false {5}" => 5, "unless true {5}" => nil, "unless true {2} else {5}" => 5, "unless true {} else {5}" => 5, "$a = if true {5} $a" => 5, "$a = if false {5} $a" => nil, "$a = if false {2} else {5} $a" => 5, "$a = if false {2} elsif true {5} $a" => 5, "$a = if false {2} elsif false {5} $a" => nil, "$a = unless false {5} $a" => 5, "$a = unless true {5} $a" => nil, "$a = unless true {2} else {5} $a" => 5, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) end end { "case 1 { 1 : { yes } }" => 'yes', "case 2 { 1,2,3 : { yes} }" => 'yes', "case 2 { 1,3 : { no } 2: { yes} }" => 'yes', "case 2 { 1,3 : { no } 5: { no } default: { yes }}" => 'yes', "case 2 { 1,3 : { no } 5: { no } }" => nil, "case 'banana' { 1,3 : { no } /.*ana.*/: { yes } }" => 'yes', "case 'banana' { /.*(ana).*/: { $1 } }" => 'ana', "case [1] { Array : { yes } }" => 'yes', "case [1] { Array[String] : { no } Array[Integer]: { yes } }" => 'yes', "case 1 { Integer : { yes } Type[Integer] : { no } }" => 'yes', "case Integer { Integer : { no } Type[Integer] : { yes } }" => 'yes', # supports unfold "case ringo { *[paul, john, ringo, george] : { 'beatle' } }" => 'beatle', "case ringo { (*[paul, john, ringo, george]) : { 'beatle' } }" => 'beatle', "case undef { undef : { 'yes' } }" => 'yes', "case undef { *undef : { 'no' } default :{ 'yes' }}" => 'yes', "case [green, 2, whatever] { [/ee/, Integer[0,10], default] : { 'yes' } default :{ 'no' }}" => 'yes', "case [green, 2, whatever] { default :{ 'no' } [/ee/, Integer[0,10], default] : { 'yes' }}" => 'yes', "case {a=>1, b=>2, whatever=>3, extra => ignored} { { a => Integer[0,5], b => Integer[0,5], whatever => default } : { 'yes' } default : { 'no' }}" => 'yes', }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) end end { "2 ? { 1 => no, 2 => yes}" => 'yes', "3 ? { 1 => no, 2 => no, default => yes }" => 'yes', "3 ? { 1 => no, default => yes, 3 => no }" => 'no', "3 ? { 1 => no, 3 => no, default => yes }" => 'no', "4 ? { 1 => no, default => yes, 3 => no }" => 'yes', "4 ? { 1 => no, 3 => no, default => yes }" => 'yes', "'banana' ? { /.*(ana).*/ => $1 }" => 'ana', "[2] ? { Array[String] => yes, Array => yes}" => 'yes', "ringo ? *[paul, john, ringo, george] => 'beatle'" => 'beatle', "ringo ? (*[paul, john, ringo, george]) => 'beatle'"=> 'beatle', "undef ? undef => 'yes'" => 'yes', "undef ? {*undef => 'no', default => 'yes'}" => 'yes', "[green, 2, whatever] ? { [/ee/, Integer[0,10], default ] => 'yes', default => 'no'}" => 'yes', "{a=>1, b=>2, whatever=>3, extra => ignored} ? {{ a => Integer[0,5], b => Integer[0,5], whatever => default } => 'yes', default => 'no' }" => 'yes', }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) end end it 'fails if a selector does not match' do expect{parser.evaluate_string(scope, "2 ? 3 => 4")}.to raise_error(/No matching entry for selector parameter with value '2'/) end end context "When evaluator evaluated unfold" do { "*[1,2,3]" => [1,2,3], "*1" => [1], "*'a'" => ['a'] }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) end end it "should parse and evaluate the expression '*{a=>10, b=>20} to [['a',10],['b',20]]" do result = parser.evaluate_string(scope, '*{a=>10, b=>20}', __FILE__) expect(result).to include(['a', 10]) expect(result).to include(['b', 20]) end it "should create an array from an Iterator" do expect(parser.evaluate_string(scope, '[1,2,3].reverse_each', __FILE__).is_a?(Array)).to be(false) result = parser.evaluate_string(scope, '*[1,2,3].reverse_each', __FILE__) expect(result).to eql([3,2,1]) end end context "When evaluator performs [] operations" do { "[1,2,3][0]" => 1, "[1,2,3][2]" => 3, "[1,2,3][3]" => nil, "[1,2,3][-1]" => 3, "[1,2,3][-2]" => 2, "[1,2,3][-4]" => nil, "[1,2,3,4][0,2]" => [1,2], "[1,2,3,4][1,3]" => [2,3,4], "[1,2,3,4][-2,2]" => [3,4], "[1,2,3,4][-3,2]" => [2,3], "[1,2,3,4][3,5]" => [4], "[1,2,3,4][5,2]" => [], "[1,2,3,4][0,-1]" => [1,2,3,4], "[1,2,3,4][0,-2]" => [1,2,3], "[1,2,3,4][0,-4]" => [1], "[1,2,3,4][0,-5]" => [], "[1,2,3,4][-5,2]" => [1], "[1,2,3,4][-5,-3]" => [1,2], "[1,2,3,4][-6,-3]" => [1,2], "[1,2,3,4][2,-3]" => [], "[1,*[2,3],4]" => [1,2,3,4], "[1,*[2,3],4][1]" => 2, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) end end { "{a=>1, b=>2, c=>3}[a]" => 1, "{a=>1, b=>2, c=>3}[c]" => 3, "{a=>1, b=>2, c=>3}[x]" => nil, "{a=>1, b=>2, c=>3}[c,b]" => [3,2], "{a=>1, b=>2, c=>3}[a,b,c]" => [1,2,3], "{a=>{b=>{c=>'it works'}}}[a][b][c]" => 'it works', "$a = {undef => 10} $a[free_lunch]" => nil, "$a = {undef => 10} $a[undef]" => 10, "$a = {undef => 10} $a[$a[free_lunch]]" => 10, "$a = {} $a[free_lunch] == undef" => true, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) end end { "'abc'[0]" => 'a', "'abc'[2]" => 'c', "'abc'[-1]" => 'c', "'abc'[-2]" => 'b', "'abc'[-3]" => 'a', "'abc'[-4]" => '', "'abc'[3]" => '', "abc[0]" => 'a', "abc[2]" => 'c', "abc[-1]" => 'c', "abc[-2]" => 'b', "abc[-3]" => 'a', "abc[-4]" => '', "abc[3]" => '', "'abcd'[0,2]" => 'ab', "'abcd'[1,3]" => 'bcd', "'abcd'[-2,2]" => 'cd', "'abcd'[-3,2]" => 'bc', "'abcd'[3,5]" => 'd', "'abcd'[5,2]" => '', "'abcd'[0,-1]" => 'abcd', "'abcd'[0,-2]" => 'abc', "'abcd'[0,-4]" => 'a', "'abcd'[0,-5]" => '', "'abcd'[-5,2]" => 'a', "'abcd'[-5,-3]" => 'ab', "'abcd'[-6,-3]" => 'ab', "'abcd'[2,-3]" => '', }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) end end # Type operations (full set tested by tests covering type calculator) { "Array[Integer]" => types.array_of(types.integer), "Array[Integer,1]" => types.array_of(types.integer, types.range(1, :default)), "Array[Integer,1,2]" => types.array_of(types.integer, types.range(1, 2)), "Array[Integer,Integer[1,2]]" => types.array_of(types.integer, types.range(1, 2)), "Array[Integer,Integer[1]]" => types.array_of(types.integer, types.range(1, :default)), "Hash[Integer,Integer]" => types.hash_of(types.integer, types.integer), "Hash[Integer,Integer,1]" => types.hash_of(types.integer, types.integer, types.range(1, :default)), "Hash[Integer,Integer,1,2]" => types.hash_of(types.integer, types.integer, types.range(1, 2)), "Hash[Integer,Integer,Integer[1,2]]" => types.hash_of(types.integer, types.integer, types.range(1, 2)), "Hash[Integer,Integer,Integer[1]]" => types.hash_of(types.integer, types.integer, types.range(1, :default)), "Resource[File]" => types.resource('File'), "Resource['File']" => types.resource(types.resource('File')), "File[foo]" => types.resource('file', 'foo'), "File[foo, bar]" => [types.resource('file', 'foo'), types.resource('file', 'bar')], "Pattern[a, /b/, Pattern[c], Regexp[d]]" => types.pattern('a', 'b', 'c', 'd'), "String[1,2]" => types.string(types.range(1, 2)), "String[Integer[1,2]]" => types.string(types.range(1, 2)), "String[Integer[1]]" => types.string(types.range(1, :default)), }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) end end # LHS where [] not supported, and missing key(s) { "Array[]" => :error, "'abc'[]" => :error, "Resource[]" => :error, "File[]" => :error, "String[]" => :error, "1[]" => :error, "3.14[]" => :error, "/.*/[]" => :error, "$a=[1] $a[]" => :error, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do expect { parser.evaluate_string(scope, source, __FILE__)}.to raise_error(/Syntax error/) end end # Integer for >= 2.4.0, otherwise Fixnum int_class_name = 0.class.name # Errors when wrong number/type of keys are used { "Array[0]" => "Array-Type[] arguments must be types. Got #{int_class_name}", "Hash[0]" => "Hash-Type[] arguments must be types. Got #{int_class_name}", "Hash[Integer, 0]" => "Hash-Type[] arguments must be types. Got #{int_class_name}", "Array[Integer,1,2,3]" => 'Array-Type[] accepts 1 to 3 arguments. Got 4', "Array[Integer,String]" => "A Type's size constraint arguments must be a single Integer type, or 1-2 integers (or default). Got a String-Type", "Hash[Integer,String, 1,2,3]" => 'Hash-Type[] accepts 2 to 4 arguments. Got 5', "'abc'[x]" => "A substring operation does not accept a String as a character index. Expected an Integer", "'abc'[1.0]" => "A substring operation does not accept a Float as a character index. Expected an Integer", "'abc'[1, x]" => "A substring operation does not accept a String as a character index. Expected an Integer", "'abc'[1,2,3]" => "String supports [] with one or two arguments. Got 3", "NotUndef[0]" => "NotUndef-Type[] argument must be a Type or a String. Got #{int_class_name}", "NotUndef[a,b]" => 'NotUndef-Type[] accepts 0 to 1 arguments. Got 2', "Resource[0]" => 'First argument to Resource[] must be a resource type or a String. Got Integer', "Resource[a, 0]" => 'Error creating type specialization of a Resource-Type, Cannot use Integer where a resource title String is expected', "File[0]" => 'Error creating type specialization of a File-Type, Cannot use Integer where a resource title String is expected', "String[a]" => "A Type's size constraint arguments must be a single Integer type, or 1-2 integers (or default). Got a String", "Pattern[0]" => 'Error creating type specialization of a Pattern-Type, Cannot use Integer where String or Regexp or Pattern-Type or Regexp-Type is expected', "Regexp[0]" => 'Error creating type specialization of a Regexp-Type, Cannot use Integer where String or Regexp is expected', "Regexp[a,b]" => 'A Regexp-Type[] accepts 1 argument. Got 2', "true[0]" => "Operator '[]' is not applicable to a Boolean", "1[0]" => "Operator '[]' is not applicable to an Integer", "3.14[0]" => "Operator '[]' is not applicable to a Float", "/.*/[0]" => "Operator '[]' is not applicable to a Regexp", "[1][a]" => "The value 'a' cannot be converted to Numeric", "[1][0.0]" => "An Array[] cannot use Float where Integer is expected", "[1]['0.0']" => "An Array[] cannot use Float where Integer is expected", "[1,2][1, 0.0]" => "An Array[] cannot use Float where Integer is expected", "[1,2][1.0, -1]" => "An Array[] cannot use Float where Integer is expected", "[1,2][1, -1.0]" => "An Array[] cannot use Float where Integer is expected", }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do expect { parser.evaluate_string(scope, source, __FILE__)}.to raise_error(Regexp.new(Regexp.quote(result))) end end context "on catalog types" do it "[n] gets resource parameter [n]" do source = "notify { 'hello': message=>'yo'} Notify[hello][message]" expect(parser.evaluate_string(scope, source, __FILE__)).to eq('yo') end it "[n] gets class parameter [n]" do source = "class wonka($produces='chocolate'){ } include wonka Class[wonka]['produces']" # This is more complicated since it needs to run like 3.x and do an import_ast adapted_parser = Puppet::Parser::E4ParserAdapter.new adapted_parser.file = __FILE__ ast = adapted_parser.parse(source) Puppet.override({:global_scope => scope, :environments => Puppet::Environments::Static.new(@node.environment) }, "gets class parameter test") do scope.environment.known_resource_types.import_ast(ast, '') expect(ast.code.safeevaluate(scope)).to eq('chocolate') end end # Resource default and override expressions and resource parameter access with [] { # Properties "notify { id: message=>explicit} Notify[id][message]" => "explicit", "Notify { message=>by_default} notify {foo:} Notify[foo][message]" => "by_default", "notify {foo:} Notify[foo]{message =>by_override} Notify[foo][message]" => "by_override", # Parameters "notify { id: withpath=>explicit} Notify[id][withpath]" => "explicit", "Notify { withpath=>by_default } notify { foo: } Notify[foo][withpath]" => "by_default", "notify {foo:} Notify[foo]{withpath=>by_override} Notify[foo][withpath]" => "by_override", # Metaparameters "notify { foo: tag => evoe} Notify[foo][tag]" => "evoe", # Does not produce the defaults for tag parameter (title, type or names of scopes) "notify { foo: } Notify[foo][tag]" => nil, # But a default may be specified on the type "Notify { tag=>by_default } notify { foo: } Notify[foo][tag]" => "by_default", "Notify { tag=>by_default } notify { foo: } Notify[foo]{ tag=>by_override } Notify[foo][tag]" => "by_override", }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) end end # Virtual and realized resource default and overridden resource parameter access with [] { # Properties "@notify { id: message=>explicit } Notify[id][message]" => "explicit", "@notify { id: message=>explicit } realize Notify[id] Notify[id][message]" => "explicit", "Notify { message=>by_default } @notify { id: } Notify[id][message]" => "by_default", "Notify { message=>by_default } @notify { id: tag=>thisone } Notify <| tag == thisone |>; Notify[id][message]" => "by_default", "@notify { id: } Notify[id]{message=>by_override} Notify[id][message]" => "by_override", # Parameters "@notify { id: withpath=>explicit } Notify[id][withpath]" => "explicit", "Notify { withpath=>by_default } @notify { id: } Notify[id][withpath]" => "by_default", "@notify { id: } realize Notify[id] Notify[id]{withpath=>by_override} Notify[id][withpath]" => "by_override", # Metaparameters "@notify { id: tag=>explicit } Notify[id][tag]" => "explicit", }.each do |source, result| it "parses and evaluates virtual and realized resources in the expression '#{source}' to #{result}" do expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) end end # Exported resource attributes { "@@notify { id: message=>explicit } Notify[id][message]" => "explicit", "@@notify { id: message=>explicit, tag=>thisone } Notify <<| tag == thisone |>> Notify[id][message]" => "explicit", }.each do |source, result| it "parses and evaluates exported resources in the expression '#{source}' to #{result}" do expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) end end # Resource default and override expressions and resource parameter access error conditions { "notify { xid: message=>explicit} Notify[id][message]" => /Resource not found/, "notify { id: message=>explicit} Notify[id][mustard]" => /does not have a parameter called 'mustard'/, # NOTE: these meta-esque parameters are not recognized as such "notify { id: message=>explicit} Notify[id][title]" => /does not have a parameter called 'title'/, "notify { id: message=>explicit} Notify[id]['type']" => /does not have a parameter called 'type'/, "notify { id: message=>explicit } Notify[id]{message=>override}" => /'message' is already set on Notify\[id\]/, "notify { id: message => 'once', message => 'twice' }" => /'message' has already been set/ }.each do |source, result| it "should parse '#{source}' and raise error matching #{result}" do expect { parser.evaluate_string(scope, source, __FILE__)}.to raise_error(result) end end context 'with errors' do { "Class['fail-whale']" => /Illegal name/, "Class[0]" => /An Integer cannot be used where a String is expected/, "Class[/.*/]" => /A Regexp cannot be used where a String is expected/, "Class[4.1415]" => /A Float cannot be used where a String is expected/, "Class[Integer]" => /An Integer-Type cannot be used where a String is expected/, "Class[File['tmp']]" => /A File\['tmp'\] Resource-Reference cannot be used where a String is expected/, }.each do | source, error_pattern| it "an error is flagged for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__)}.to raise_error(error_pattern) end end end end # end [] operations end context "When the evaluator performs boolean operations" do { "true and true" => true, "false and true" => false, "true and false" => false, "false and false" => false, "true or true" => true, "false or true" => true, "true or false" => true, "false or false" => false, "! true" => false, "!! true" => true, "!! false" => false, "! 'x'" => false, "! ''" => false, "! undef" => true, "! [a]" => false, "! []" => false, "! {a=>1}" => false, "! {}" => false, "true and false and '0xwtf' + 1" => false, "false or true or '0xwtf' + 1" => true, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) end end { "false || false || '0xwtf' + 1" => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end end context "When evaluator performs operations on literal undef" do it "computes non existing hash lookup as undef" do expect(parser.evaluate_string(scope, "{a => 1}[b] == undef", __FILE__)).to eq(true) expect(parser.evaluate_string(scope, "undef == {a => 1}[b]", __FILE__)).to eq(true) end end context "When evaluator performs calls" do let(:populate) do parser.evaluate_string(scope, "$a = 10 $b = [1,2,3]") end { 'sprintf( "x%iy", $a )' => "x10y", # unfolds 'sprintf( *["x%iy", $a] )' => "x10y", '( *["x%iy", $a] ).sprintf' => "x10y", '((*["x%iy", $a])).sprintf' => "x10y", '"x%iy".sprintf( $a )' => "x10y", '$b.reduce |$memo,$x| { $memo + $x }' => 6, 'reduce($b) |$memo,$x| { $memo + $x }' => 6, }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do populate expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) end end { '"value is ${a*2} yo"' => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end it "provides location information on error in unparenthesized call logic" do expect{parser.evaluate_string(scope, "include non_existing_class", __FILE__)}.to raise_error(Puppet::ParseError, /line: 1, column: 1/) end it 'defaults can be given in a lambda and used only when arg is missing' do env_loader = @compiler.loaders.public_environment_loader fc = Puppet::Functions.create_function(:test) do dispatch :test do param 'Integer', :count required_block_param end def test(count) yield(*[].fill(10, 0, count)) end end the_func = fc.new({}, env_loader) env_loader.add_entry(:function, 'test', the_func, __FILE__) expect(parser.evaluate_string(scope, "test(1) |$x, $y=20| { $x + $y}")).to eql(30) expect(parser.evaluate_string(scope, "test(2) |$x, $y=20| { $x + $y}")).to eql(20) end it 'a given undef does not select the default value' do env_loader = @compiler.loaders.public_environment_loader fc = Puppet::Functions.create_function(:test) do dispatch :test do param 'Any', :lambda_arg required_block_param end def test(lambda_arg) yield(lambda_arg) end end the_func = fc.new({}, env_loader) env_loader.add_entry(:function, 'test', the_func, __FILE__) expect(parser.evaluate_string(scope, "test(undef) |$x=20| { $x == undef}")).to eql(true) end it 'a given undef is given as nil' do env_loader = @compiler.loaders.public_environment_loader fc = Puppet::Functions.create_function(:assert_no_undef) do dispatch :assert_no_undef do param 'Any', :x end def assert_no_undef(x) case x when Array return unless x.include?(:undef) when Hash return unless x.keys.include?(:undef) || x.values.include?(:undef) else return unless x == :undef end raise "contains :undef" end end the_func = fc.new({}, env_loader) env_loader.add_entry(:function, 'assert_no_undef', the_func, __FILE__) expect{parser.evaluate_string(scope, "assert_no_undef(undef)")}.to_not raise_error() expect{parser.evaluate_string(scope, "assert_no_undef([undef])")}.to_not raise_error() expect{parser.evaluate_string(scope, "assert_no_undef({undef => 1})")}.to_not raise_error() expect{parser.evaluate_string(scope, "assert_no_undef({1 => undef})")}.to_not raise_error() end it 'a lambda return value is checked using the return type' do expect(parser.evaluate_string(scope, "[1,2].map |Integer $x| >> Integer { $x }")).to eql([1,2]) expect { parser.evaluate_string(scope, "[1,2].map |Integer $x| >> String { $x }") }.to raise_error( /value returned from lambda has wrong type, expects a String value, got Integer/) end context 'using the 3x function api' do it 'can call a 3x function' do Puppet::Parser::Functions.newfunction("bazinga", :type => :rvalue) { |args| args[0] } expect(parser.evaluate_string(scope, "bazinga(42)", __FILE__)).to eq(42) end it 'maps :undef to empty string' do Puppet::Parser::Functions.newfunction("bazinga", :type => :rvalue) { |args| args[0] } expect(parser.evaluate_string(scope, "$a = {} bazinga($a[nope])", __FILE__)).to eq('') expect(parser.evaluate_string(scope, "bazinga(undef)", __FILE__)).to eq('') end it 'does not map :undef to empty string in arrays' do Puppet::Parser::Functions.newfunction("bazinga", :type => :rvalue) { |args| args[0][0] } expect(parser.evaluate_string(scope, "$a = {} $b = [$a[nope]] bazinga($b)", __FILE__)).to eq(:undef) expect(parser.evaluate_string(scope, "bazinga([undef])", __FILE__)).to eq(:undef) end it 'does not map :undef to empty string in hashes' do Puppet::Parser::Functions.newfunction("bazinga", :type => :rvalue) { |args| args[0]['a'] } expect(parser.evaluate_string(scope, "$a = {} $b = {a => $a[nope]} bazinga($b)", __FILE__)).to eq(:undef) expect(parser.evaluate_string(scope, "bazinga({a => undef})", __FILE__)).to eq(:undef) end end end context "When evaluator performs string interpolation" do let(:populate) do parser.evaluate_string(scope, "$a = 10 $b = [1,2,3]") end { '"value is $a yo"' => "value is 10 yo", '"value is \$a yo"' => "value is $a yo", '"value is ${a} yo"' => "value is 10 yo", '"value is \${a} yo"' => "value is ${a} yo", '"value is ${$a} yo"' => "value is 10 yo", '"value is ${$a*2} yo"' => "value is 20 yo", '"value is ${sprintf("x%iy",$a)} yo"' => "value is x10y yo", '"value is ${"x%iy".sprintf($a)} yo"' => "value is x10y yo", '"value is ${[1,2,3]} yo"' => "value is [1, 2, 3] yo", '"value is ${/.*/} yo"' => "value is /.*/ yo", '$x = undef "value is $x yo"' => "value is yo", '$x = default "value is $x yo"' => "value is default yo", '$x = Array[Integer] "value is $x yo"' => "value is Array[Integer] yo", '"value is ${Array[Integer]} yo"' => "value is Array[Integer] yo", }.each do |source, result| it "should parse and evaluate the expression '#{source}' to #{result}" do populate expect(parser.evaluate_string(scope, source, __FILE__)).to eq(result) end end it "should parse and evaluate an interpolation of a hash" do source = '"value is ${{a=>1,b=>2}} yo"' # This test requires testing against two options because a hash to string # produces a result that is unordered alt_results = ["value is {a => 1, b => 2} yo", "value is {b => 2, a => 1} yo" ] populate parse_result = parser.evaluate_string(scope, source, __FILE__) expect(alt_results.include?(parse_result)).to eq(true) end it 'should accept a variable with leading underscore when used directly' do source = '$_x = 10; "$_x"' expect(parser.evaluate_string(scope, source, __FILE__)).to eql('10') end it 'should accept a variable with leading underscore when used as an expression' do source = '$_x = 10; "${_x}"' expect(parser.evaluate_string(scope, source, __FILE__)).to eql('10') end it 'should accept a numeric variable expressed as $n' do source = '$x = "abc123def" =~ /(abc)(123)(def)/; "${$2}"' expect(parser.evaluate_string(scope, source, __FILE__)).to eql('123') end it 'should accept a numeric variable expressed as just an integer' do source = '$x = "abc123def" =~ /(abc)(123)(def)/; "${2}"' expect(parser.evaluate_string(scope, source, __FILE__)).to eql('123') end it 'should accept a numeric variable expressed as $n in an access operation' do source = '$x = "abc123def" =~ /(abc)(123)(def)/; "${$0[4,3]}"' expect(parser.evaluate_string(scope, source, __FILE__)).to eql('23d') end it 'should accept a numeric variable expressed as just an integer in an access operation' do source = '$x = "abc123def" =~ /(abc)(123)(def)/; "${0[4,3]}"' expect(parser.evaluate_string(scope, source, __FILE__)).to eql('23d') end { '"value is ${a*2} yo"' => :error, }.each do |source, result| it "should parse and raise error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(Puppet::ParseError) end end end context "When evaluating variables" do context "that are non existing an error is raised for" do it "unqualified variable" do expect { parser.evaluate_string(scope, "$quantum_gravity", __FILE__) }.to raise_error(/Unknown variable/) end it "qualified variable" do expect { parser.evaluate_string(scope, "$quantum_gravity::graviton", __FILE__) }.to raise_error(/Unknown variable/) end end it "a lex error should be raised for '$foo::::bar'" do expect { parser.evaluate_string(scope, "$foo::::bar") }.to raise_error(Puppet::ParseErrorWithIssue, /Illegal fully qualified name \(line: 1, column: 7\)/) end { '$a = $0' => nil, '$a = $1' => nil, }.each do |source, value| it "it is ok to reference numeric unassigned variables '#{source}'" do expect(parser.evaluate_string(scope, source, __FILE__)).to eq(value) end end { '$00 = 0' => /must be a decimal value/, '$0xf = 0' => /must be a decimal value/, '$0777 = 0' => /must be a decimal value/, '$123a = 0' => /must be a decimal value/, }.each do |source, error_pattern| it "should raise an error for '#{source}'" do expect { parser.evaluate_string(scope, source, __FILE__) }.to raise_error(error_pattern) end end context "an initial underscore in the last segment of a var name is allowed" do { '$_a = 1' => 1, '$__a = 1' => 1, }.each do |source, value| it "as in this example '#{source}'" do expect(parser.evaluate_string(scope, source, __FILE__)).to eq(value) end end end end context "When evaluating relationships" do it 'should form a relation with File[a] -> File[b]' do source = "File[a] -> File[b]" parser.evaluate_string(scope, source, __FILE__) expect(scope.compiler).to have_relationship(['File', 'a', '->', 'File', 'b']) end it 'should form a relation with resource -> resource' do source = "notify{a:} -> notify{b:}" parser.evaluate_string(scope, source, __FILE__) expect(scope.compiler).to have_relationship(['Notify', 'a', '->', 'Notify', 'b']) end it 'should form a relation with [File[a], File[b]] -> [File[x], File[y]]' do source = "[File[a], File[b]] -> [File[x], File[y]]" parser.evaluate_string(scope, source, __FILE__) expect(scope.compiler).to have_relationship(['File', 'a', '->', 'File', 'x']) expect(scope.compiler).to have_relationship(['File', 'b', '->', 'File', 'x']) expect(scope.compiler).to have_relationship(['File', 'a', '->', 'File', 'y']) expect(scope.compiler).to have_relationship(['File', 'b', '->', 'File', 'y']) end it 'should form a relation with 3.x resource -> resource' do # Create a 3.x resource since this is the value given as arguments to defined type scope['a_3x_resource']= Puppet::Parser::Resource.new('notify', 'a', {:scope => scope, :file => __FILE__, :line => 1}) source = "$a_3x_resource -> notify{b:}" parser.evaluate_string(scope, source, __FILE__) expect(scope.compiler).to have_relationship(['Notify', 'a', '->', 'Notify', 'b']) end it 'should tolerate (eliminate) duplicates in operands' do source = "[File[a], File[a]] -> File[x]" parser.evaluate_string(scope, source, __FILE__) expect(scope.compiler).to have_relationship(['File', 'a', '->', 'File', 'x']) expect(scope.compiler.relationships.size).to eq(1) end it 'should form a relation with <-' do source = "File[a] <- File[b]" parser.evaluate_string(scope, source, __FILE__) expect(scope.compiler).to have_relationship(['File', 'b', '->', 'File', 'a']) end it 'should form a relation with <-' do source = "File[a] <~ File[b]" parser.evaluate_string(scope, source, __FILE__) expect(scope.compiler).to have_relationship(['File', 'b', '~>', 'File', 'a']) end it 'should close the gap created by an intermediate empty set' do source = "[File[a], File[aa]] -> [] ~> [File[b], File[bb]]" parser.evaluate_string(scope, source, __FILE__) expect(scope.compiler).to have_relationship(['File', 'a', '~>', 'File', 'b']) expect(scope.compiler).to have_relationship(['File', 'aa', '~>', 'File', 'b']) expect(scope.compiler).to have_relationship(['File', 'a', '~>', 'File', 'bb']) expect(scope.compiler).to have_relationship(['File', 'aa', '~>', 'File', 'bb']) end end context "When evaluating heredoc" do it "evaluates plain heredoc" do src = "@(END)\nThis is\nheredoc text\nEND\n" expect(parser.evaluate_string(scope, src)).to eq("This is\nheredoc text\n") end it "parses heredoc with margin" do src = [ "@(END)", " This is", " heredoc text", " | END", "" ].join("\n") expect(parser.evaluate_string(scope, src)).to eq("This is\nheredoc text\n") end it "parses heredoc with margin and right newline trim" do src = [ "@(END)", " This is", " heredoc text", " |- END", "" ].join("\n") expect(parser.evaluate_string(scope, src)).to eq("This is\nheredoc text") end it "parses escape specification" do src = <<-CODE @(END/t) Tex\\tt\\n |- END CODE expect(parser.evaluate_string(scope, src)).to eq("Tex\tt\\n") end it "parses json syntax checked specification" do src = <<-CODE @(END:json) ["foo", "bar"] |- END CODE expect(parser.evaluate_string(scope, src)).to eq('["foo", "bar"]') end it "parses base64 syntax checked specification" do src = <<-CODE @(END:base64) dGhlIHF1aWNrIHJlZCBmb3g= |- END CODE expect(parser.evaluate_string(scope, src)).to eq('dGhlIHF1aWNrIHJlZCBmb3g=') end it "parses json syntax checked specification with error and reports it" do src = <<-CODE @(END:json) ['foo', "bar"] |- END CODE expect { parser.evaluate_string(scope, src)}.to raise_error(/Cannot parse invalid JSON string/) end it "parses base syntax checked specification with error and reports it" do src = <<-CODE @(END:base64) dGhlIHF1aWNrIHJlZCBmb3g |- END CODE expect { parser.evaluate_string(scope, src)}.to raise_error(/Cannot parse invalid Base64 string/) end it "parses interpolated heredoc expression" do src = <<-CODE $pname = 'Fjodor' @("END") Hello $pname |- END CODE expect(parser.evaluate_string(scope, src)).to eq("Hello Fjodor") end it "parses interpolated heredoc expression with escapes" do src = <<-CODE $name = 'Fjodor' @("END") Hello\\ \\$name |- END CODE expect(parser.evaluate_string(scope, src)).to eq("Hello\\ \\Fjodor") end end context "Handles Deprecations and Discontinuations" do it 'of import statements' do source = "\nimport foo" # Error references position 5 at the opening '{' # Set file to nil to make it easier to match with line number (no file name in output) expect { parser.evaluate_string(scope, source) }.to raise_error(/'import' has been discontinued.* \(line: 2, column: 1\)/) end end context "Detailed Error messages are reported" do it 'for illegal type references' do source = '1+1 { "title": }' # Error references position 5 at the opening '{' # Set file to nil to make it easier to match with line number (no file name in output) expect { parser.evaluate_string(scope, source) }.to raise_error( /Illegal Resource Type expression, expected result to be a type name, or untitled Resource.* \(line: 1, column: 2\)/) end it 'for non r-value producing <| |>' do expect { parser.parse_string("$a = File <| |>", nil) }.to raise_error(/A Virtual Query does not produce a value \(line: 1, column: 6\)/) end it 'for non r-value producing <<| |>>' do expect { parser.parse_string("$a = File <<| |>>", nil) }.to raise_error(/An Exported Query does not produce a value \(line: 1, column: 6\)/) end it 'for non r-value producing define' do Puppet::Util::Log.expects(:create).with(has_entries(:level => :err, :message => "Invalid use of expression. A 'define' expression does not produce a value", :line => 1, :pos => 6)) Puppet::Util::Log.expects(:create).with(has_entries(:level => :err, :message => 'Classes, definitions, and nodes may only appear at toplevel or inside other classes', :line => 1, :pos => 6)) expect { parser.parse_string("$a = define foo { }", nil) }.to raise_error(/2 errors/) end it 'for non r-value producing class' do Puppet::Util::Log.expects(:create).with(has_entries(:level => :err, :message => 'Invalid use of expression. A Host Class Definition does not produce a value', :line => 1, :pos => 6)) Puppet::Util::Log.expects(:create).with(has_entries(:level => :err, :message => 'Classes, definitions, and nodes may only appear at toplevel or inside other classes', :line => 1, :pos => 6)) expect { parser.parse_string("$a = class foo { }", nil) }.to raise_error(/2 errors/) end it 'for unclosed quote with indication of start position of string' do source = <<-SOURCE.gsub(/^ {6}/,'') $a = "xx yyy SOURCE # first char after opening " reported as being in error. expect { parser.parse_string(source) }.to raise_error(/Unclosed quote after '"' followed by 'xx\\nyy\.\.\.' \(line: 1, column: 7\)/) end it 'for multiple errors with a summary exception' do Puppet::Util::Log.expects(:create).with(has_entries(:level => :err, :message => 'Invalid use of expression. A Node Definition does not produce a value', :line => 1, :pos => 6)) Puppet::Util::Log.expects(:create).with(has_entries(:level => :err, :message => 'Classes, definitions, and nodes may only appear at toplevel or inside other classes', :line => 1, :pos => 6)) expect { parser.parse_string("$a = node x { }",nil) }.to raise_error(/2 errors/) end it 'for a bad hostname' do expect { parser.parse_string("node 'macbook+owned+by+name' { }", nil) }.to raise_error(/The hostname 'macbook\+owned\+by\+name' contains illegal characters.* \(line: 1, column: 6\)/) end it 'for a hostname with interpolation' do source = <<-SOURCE.gsub(/^ {6}/,'') $pname = 'fred' node "macbook-owned-by$pname" { } SOURCE expect { parser.parse_string(source, nil) }.to raise_error(/An interpolated expression is not allowed in a hostname of a node \(line: 2, column: 23\)/) end end context 'does not leak variables' do it 'local variables are gone when lambda ends' do source = <<-SOURCE [1,2,3].each |$x| { $y = $x} $a = $y SOURCE expect do parser.evaluate_string(scope, source) end.to raise_error(/Unknown variable: 'y'/) end it 'lambda parameters are gone when lambda ends' do source = <<-SOURCE [1,2,3].each |$x| { $y = $x} $a = $x SOURCE expect do parser.evaluate_string(scope, source) end.to raise_error(/Unknown variable: 'x'/) end it 'does not leak match variables' do source = <<-SOURCE if 'xyz' =~ /(x)(y)(z)/ { notice $2 } case 'abc' { /(a)(b)(c)/ : { $x = $2 } } "-$x-$2-" SOURCE expect(parser.evaluate_string(scope, source)).to eq('-b--') end end matcher :have_relationship do |expected| match do |compiler| op_name = {'->' => :relationship, '~>' => :subscription} compiler.relationships.any? do | relation | relation.source.type == expected[0] && relation.source.title == expected[1] && relation.type == op_name[expected[2]] && relation.target.type == expected[3] && relation.target.title == expected[4] end end failure_message do |actual| "Relationship #{expected[0]}[#{expected[1]}] #{expected[2]} #{expected[3]}[#{expected[4]}] but was unknown to compiler" end end def collect_notices(code) Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do parser.evaluate_string(scope, code, __FILE__) end end end puppet-5.5.10/spec/unit/pops/evaluator/runtime3_converter_spec.rb0000644005276200011600000001335413417161722025111 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' require 'puppet/pops/types/type_factory' describe 'when converting to 3.x' do let(:converter) { Puppet::Pops::Evaluator::Runtime3Converter.instance } it "converts a resource type starting with Class without confusing it with exact match on 'class'" do t = Puppet::Pops::Types::TypeFactory.resource('classroom', 'kermit') converted = converter.catalog_type_to_split_type_title(t) expect(converted).to eql(['classroom', 'kermit']) end it "converts a resource type of exactly 'Class'" do t = Puppet::Pops::Types::TypeFactory.resource('class', 'kermit') converted = converter.catalog_type_to_split_type_title(t) expect(converted).to eql(['class', 'kermit']) end it "errors on attempts to convert an 'Iterator'" do expect { converter.convert(Puppet::Pops::Types::Iterable.on((1..3)), {}, nil) }.to raise_error(Puppet::Error, /Use of an Iterator is not supported here/) end it 'does not convert a SemVer instance to string' do v = SemanticPuppet::Version.parse('1.0.0') expect(converter.convert(v, {}, nil)).to equal(v) end it 'converts the symbol :undef to the undef value' do expect(converter.convert(:undef, {}, 'undef value')).to eql('undef value') end it 'converts the nil to the undef value' do expect(converter.convert(nil, {}, 'undef value')).to eql('undef value') end it 'does not convert a symbol nested in an array' do expect(converter.convert({'foo' => :undef}, {}, 'undef value')).to eql({'foo' => :undef}) end it 'converts nil to :undef when nested in an array' do expect(converter.convert({'foo' => nil}, {}, 'undef value')).to eql({'foo' => :undef}) end it 'does not convert a Regex instance to string' do v = /^[A-Z]$/ expect(converter.convert(v, {}, nil)).to equal(v) end it 'does not convert a Version instance to string' do v = SemanticPuppet::Version.parse('1.0.0') expect(converter.convert(v, {}, nil)).to equal(v) end it 'does not convert a VersionRange instance to string' do v = SemanticPuppet::VersionRange.parse('>=1.0.0') expect(converter.convert(v, {}, nil)).to equal(v) end it 'does not convert a Timespan instance to string' do v = Puppet::Pops::Time::Timespan.new(1234) expect(converter.convert(v, {}, nil)).to equal(v) end it 'does not convert a Timestamp instance to string' do v = Puppet::Pops::Time::Timestamp.now expect(converter.convert(v, {}, nil)).to equal(v) end it 'does not convert a Sensitive instance to string' do v = Puppet::Pops::Types::PSensitiveType::Sensitive.new("don't reveal this") expect(converter.convert(v, {}, nil)).to equal(v) end it 'does not convert a Binary instance to string' do v = Puppet::Pops::Types::PBinaryType::Binary.from_base64('w5ZzdGVuIG1lZCByw7ZzdGVuCg==') expect(converter.convert(v, {}, nil)).to equal(v) end context 'the Runtime3FunctionArgumentConverter' do let(:converter) { Puppet::Pops::Evaluator::Runtime3FunctionArgumentConverter.instance } it 'converts a Regex instance to string' do c = converter.convert(/^[A-Z]$/, {}, nil) expect(c).to be_a(String) expect(c).to eql('/^[A-Z]$/') end it 'converts a Version instance to string' do c = converter.convert(SemanticPuppet::Version.parse('1.0.0'), {}, nil) expect(c).to be_a(String) expect(c).to eql('1.0.0') end it 'converts a VersionRange instance to string' do c = converter.convert(SemanticPuppet::VersionRange.parse('>=1.0.0'), {}, nil) expect(c).to be_a(String) expect(c).to eql('>=1.0.0') end it 'converts a Timespan instance to string' do c = converter.convert(Puppet::Pops::Time::Timespan.new(1234), {}, nil) expect(c).to be_a(String) expect(c).to eql('0-00:00:00.1234') end it 'converts a Timestamp instance to string' do c = converter.convert(Puppet::Pops::Time::Timestamp.parse('2016-09-15T12:24:47.193 UTC'), {}, nil) expect(c).to be_a(String) expect(c).to eql('2016-09-15T12:24:47.193000000 UTC') end it 'converts a Binary instance to string' do b64 = 'w5ZzdGVuIG1lZCByw7ZzdGVuCg==' c = converter.convert(Puppet::Pops::Types::PBinaryType::Binary.from_base64(b64), {}, nil) expect(c).to be_a(String) expect(c).to eql(b64) end it 'does not convert a Sensitive instance to string' do v = Puppet::Pops::Types::PSensitiveType::Sensitive.new("don't reveal this") expect(converter.convert(v, {}, nil)).to equal(v) end it 'errors if an Integer is too big' do too_big = 0x7fffffffffffffff + 1 expect do converter.convert(too_big, {}, nil) end.to raise_error(/Use of a Ruby Integer outside of Puppet Integer max range, got/) end it 'errors if an Integer is too small' do too_small = -0x8000000000000000-1 expect do converter.convert(too_small, {}, nil) end.to raise_error(/Use of a Ruby Integer outside of Puppet Integer min range, got/) end it 'errors if a BigDecimal is out of range for Float' do big_dec = BigDecimal("123456789123456789.1415") expect do converter.convert(big_dec, {}, nil) end.to raise_error(/Use of a Ruby BigDecimal value outside Puppet Float range, got/) end it 'BigDecimal values in Float range are converted' do big_dec = BigDecimal("3.1415") f = converter.convert(big_dec, {}, nil) expect(f.class).to be(Float) end it 'errors when Integer is out of range in a structure' do structure = {'key' => [{ 'key' => [0x7fffffffffffffff + 1]}]} expect do converter.convert(structure, {}, nil) end.to raise_error(/Use of a Ruby Integer outside of Puppet Integer max range, got/) end end end puppet-5.5.10/spec/unit/pops/factory_rspec_helper.rb0000644005276200011600000000251113417161721022432 0ustar jenkinsjenkinsrequire 'puppet/pops' module FactoryRspecHelper def literal(x) Puppet::Pops::Model::Factory.literal(x) end def block(*args) Puppet::Pops::Model::Factory.block(*args) end def var(x) Puppet::Pops::Model::Factory.var(x) end def fqn(x) Puppet::Pops::Model::Factory.fqn(x) end def string(*args) Puppet::Pops::Model::Factory.string(*args) end def minus(x) Puppet::Pops::Model::Factory.minus(x) end def IF(test, then_expr, else_expr=nil) Puppet::Pops::Model::Factory.IF(test, then_expr, else_expr) end def UNLESS(test, then_expr, else_expr=nil) Puppet::Pops::Model::Factory.UNLESS(test, then_expr, else_expr) end def CASE(test, *options) Puppet::Pops::Model::Factory.CASE(test, *options) end def WHEN(values, block) Puppet::Pops::Model::Factory.WHEN(values, block) end def method_missing(method, *args, &block) if Puppet::Pops::Model::Factory.respond_to? method Puppet::Pops::Model::Factory.send(method, *args, &block) else super end end # i.e. Selector Entry 1 => 'hello' def MAP(match, value) Puppet::Pops::Model::Factory.MAP(match, value) end def dump(x) Puppet::Pops::Model::ModelTreeDumper.new.dump(x) end def unindent x x.gsub(/^#{x[/\A\s*/]}/, '').chomp end factory ||= Puppet::Pops::Model::Factory end puppet-5.5.10/spec/unit/pops/factory_spec.rb0000644005276200011600000003230713417161721020717 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' require File.join(File.dirname(__FILE__), '/factory_rspec_helper') # This file contains testing of the pops model factory # describe Puppet::Pops::Model::Factory do include FactoryRspecHelper context "When factory methods are invoked they should produce expected results" do it "tests #var should create a VariableExpression" do expect(var('a').model.class).to eq(Puppet::Pops::Model::VariableExpression) end it "tests #fqn should create a QualifiedName" do expect(fqn('a').model.class).to eq(Puppet::Pops::Model::QualifiedName) end it "tests #QNAME should create a QualifiedName" do expect(QNAME('a').model.class).to eq(Puppet::Pops::Model::QualifiedName) end it "tests #QREF should create a QualifiedReference" do expect(QREF('a').model.class).to eq(Puppet::Pops::Model::QualifiedReference) end it "tests #block should create a BlockExpression" do expect(block().model.is_a?(Puppet::Pops::Model::BlockExpression)).to eq(true) end it "should create a literal undef on :undef" do expect(literal(:undef).model.class).to eq(Puppet::Pops::Model::LiteralUndef) end it "should create a literal default on :default" do expect(literal(:default).model.class).to eq(Puppet::Pops::Model::LiteralDefault) end end context "When calling block_or_expression" do it "A single expression should produce identical output" do expect(block_or_expression([literal(1) + literal(2)]).model.is_a?(Puppet::Pops::Model::ArithmeticExpression)).to eq(true) end it "Multiple expressions should produce a block expression" do braces = mock 'braces' braces.stubs(:offset).returns(0) braces.stubs(:length).returns(0) model = block_or_expression([literal(1) + literal(2), literal(2) + literal(3)], braces, braces).model expect(model.is_a?(Puppet::Pops::Model::BlockExpression)).to eq(true) expect(model.statements.size).to eq(2) end end context "When processing calls with CALL_NAMED" do it "Should be possible to state that r-value is required" do built = call_named('foo', true).model expect(built.is_a?(Puppet::Pops::Model::CallNamedFunctionExpression)).to eq(true) expect(built.rval_required).to eq(true) end it "Should produce a call expression without arguments" do built = call_named('foo', false).model expect(built.is_a?(Puppet::Pops::Model::CallNamedFunctionExpression)).to eq(true) expect(built.functor_expr.is_a?(Puppet::Pops::Model::QualifiedName)).to eq(true) expect(built.functor_expr.value).to eq("foo") expect(built.rval_required).to eq(false) expect(built.arguments.size).to eq(0) end it "Should produce a call expression with one argument" do built = call_named('foo', false, literal(1) + literal(2)).model expect(built.is_a?(Puppet::Pops::Model::CallNamedFunctionExpression)).to eq(true) expect(built.functor_expr.is_a?(Puppet::Pops::Model::QualifiedName)).to eq(true) expect(built.functor_expr.value).to eq("foo") expect(built.rval_required).to eq(false) expect(built.arguments.size).to eq(1) expect(built.arguments[0].is_a?(Puppet::Pops::Model::ArithmeticExpression)).to eq(true) end it "Should produce a call expression with two arguments" do built = call_named('foo', false, literal(1) + literal(2), literal(1) + literal(2)).model expect(built.is_a?(Puppet::Pops::Model::CallNamedFunctionExpression)).to eq(true) expect(built.functor_expr.is_a?(Puppet::Pops::Model::QualifiedName)).to eq(true) expect(built.functor_expr.value).to eq("foo") expect(built.rval_required).to eq(false) expect(built.arguments.size).to eq(2) expect(built.arguments[0].is_a?(Puppet::Pops::Model::ArithmeticExpression)).to eq(true) expect(built.arguments[1].is_a?(Puppet::Pops::Model::ArithmeticExpression)).to eq(true) end end context "When creating attribute operations" do it "Should produce an attribute operation for =>" do built = ATTRIBUTE_OP('aname', '=>', literal('x')).model built.is_a?(Puppet::Pops::Model::AttributeOperation) expect(built.operator).to eq('=>') expect(built.attribute_name).to eq("aname") expect(built.value_expr.is_a?(Puppet::Pops::Model::LiteralString)).to eq(true) end it "Should produce an attribute operation for +>" do built = ATTRIBUTE_OP('aname', '+>', literal('x')).model built.is_a?(Puppet::Pops::Model::AttributeOperation) expect(built.operator).to eq('+>') expect(built.attribute_name).to eq("aname") expect(built.value_expr.is_a?(Puppet::Pops::Model::LiteralString)).to eq(true) end end context "When processing RESOURCE" do it "Should create a Resource body" do built = RESOURCE_BODY(literal('title'), [ATTRIBUTE_OP('aname', '=>', literal('x'))]).model expect(built.is_a?(Puppet::Pops::Model::ResourceBody)).to eq(true) expect(built.title.is_a?(Puppet::Pops::Model::LiteralString)).to eq(true) expect(built.operations.size).to eq(1) expect(built.operations[0].class).to eq(Puppet::Pops::Model::AttributeOperation) expect(built.operations[0].attribute_name).to eq('aname') end it "Should create a RESOURCE without a resource body" do bodies = [] built = RESOURCE(literal('rtype'), bodies).model expect(built.class).to eq(Puppet::Pops::Model::ResourceExpression) expect(built.bodies.size).to eq(0) end it "Should create a RESOURCE with 1 resource body" do bodies = [] << RESOURCE_BODY(literal('title'), []) built = RESOURCE(literal('rtype'), bodies).model expect(built.class).to eq(Puppet::Pops::Model::ResourceExpression) expect(built.bodies.size).to eq(1) expect(built.bodies[0].title.value).to eq('title') end it "Should create a RESOURCE with 2 resource bodies" do bodies = [] << RESOURCE_BODY(literal('title'), []) << RESOURCE_BODY(literal('title2'), []) built = RESOURCE(literal('rtype'), bodies).model expect(built.class).to eq(Puppet::Pops::Model::ResourceExpression) expect(built.bodies.size).to eq(2) expect(built.bodies[0].title.value).to eq('title') expect(built.bodies[1].title.value).to eq('title2') end end context "When processing simple literals" do it "Should produce a literal boolean from a boolean" do model = literal(true).model expect(model.class).to eq(Puppet::Pops::Model::LiteralBoolean) expect(model.value).to eq(true) model = literal(false).model expect(model.class).to eq(Puppet::Pops::Model::LiteralBoolean) expect(model.value).to eq(false) end end context "When processing COLLECT" do it "should produce a virtual query" do model = VIRTUAL_QUERY(fqn('a').eq(literal(1))).model expect(model.class).to eq(Puppet::Pops::Model::VirtualQuery) expect(model.expr.class).to eq(Puppet::Pops::Model::ComparisonExpression) expect(model.expr.operator).to eq('==') end it "should produce an export query" do model = EXPORTED_QUERY(fqn('a').eq(literal(1))).model expect(model.class).to eq(Puppet::Pops::Model::ExportedQuery) expect(model.expr.class).to eq(Puppet::Pops::Model::ComparisonExpression) expect(model.expr.operator).to eq('==') end it "should produce a collect expression" do q = VIRTUAL_QUERY(fqn('a').eq(literal(1))) built = COLLECT(literal('t'), q, [ATTRIBUTE_OP('name', '=>', literal(3))]).model expect(built.class).to eq(Puppet::Pops::Model::CollectExpression) expect(built.operations.size).to eq(1) end it "should produce a collect expression without attribute operations" do q = VIRTUAL_QUERY(fqn('a').eq(literal(1))) built = COLLECT(literal('t'), q, []).model expect(built.class).to eq(Puppet::Pops::Model::CollectExpression) expect(built.operations.size).to eq(0) end end context "When processing concatenated string(iterpolation)" do it "should handle 'just a string'" do model = string('blah blah').model expect(model.class).to eq(Puppet::Pops::Model::ConcatenatedString) expect(model.segments.size).to eq(1) expect(model.segments[0].class).to eq(Puppet::Pops::Model::LiteralString) expect(model.segments[0].value).to eq("blah blah") end it "should handle one expression in the middle" do model = string('blah blah', TEXT(literal(1)+literal(2)), 'blah blah').model expect(model.class).to eq(Puppet::Pops::Model::ConcatenatedString) expect(model.segments.size).to eq(3) expect(model.segments[0].class).to eq(Puppet::Pops::Model::LiteralString) expect(model.segments[0].value).to eq("blah blah") expect(model.segments[1].class).to eq(Puppet::Pops::Model::TextExpression) expect(model.segments[1].expr.class).to eq(Puppet::Pops::Model::ArithmeticExpression) expect(model.segments[2].class).to eq(Puppet::Pops::Model::LiteralString) expect(model.segments[2].value).to eq("blah blah") end it "should handle one expression at the end" do model = string('blah blah', TEXT(literal(1)+literal(2))).model expect(model.class).to eq(Puppet::Pops::Model::ConcatenatedString) expect(model.segments.size).to eq(2) expect(model.segments[0].class).to eq(Puppet::Pops::Model::LiteralString) expect(model.segments[0].value).to eq("blah blah") expect(model.segments[1].class).to eq(Puppet::Pops::Model::TextExpression) expect(model.segments[1].expr.class).to eq(Puppet::Pops::Model::ArithmeticExpression) end it "should handle only one expression" do model = string(TEXT(literal(1)+literal(2))).model expect(model.class).to eq(Puppet::Pops::Model::ConcatenatedString) expect(model.segments.size).to eq(1) expect(model.segments[0].class).to eq(Puppet::Pops::Model::TextExpression) expect(model.segments[0].expr.class).to eq(Puppet::Pops::Model::ArithmeticExpression) end it "should handle several expressions" do model = string(TEXT(literal(1)+literal(2)), TEXT(literal(1)+literal(2))).model expect(model.class).to eq(Puppet::Pops::Model::ConcatenatedString) expect(model.segments.size).to eq(2) expect(model.segments[0].class).to eq(Puppet::Pops::Model::TextExpression) expect(model.segments[0].expr.class).to eq(Puppet::Pops::Model::ArithmeticExpression) expect(model.segments[1].class).to eq(Puppet::Pops::Model::TextExpression) expect(model.segments[1].expr.class).to eq(Puppet::Pops::Model::ArithmeticExpression) end it "should handle no expression" do model = string().model expect(model.class).to eq(Puppet::Pops::Model::ConcatenatedString) model.segments.size == 0 end end context "When processing UNLESS" do it "should create an UNLESS expression with then part" do built = UNLESS(literal(true), literal(1), literal(nil)).model expect(built.class).to eq(Puppet::Pops::Model::UnlessExpression) expect(built.test.class).to eq(Puppet::Pops::Model::LiteralBoolean) expect(built.then_expr.class).to eq(Puppet::Pops::Model::LiteralInteger) expect(built.else_expr.class).to eq(Puppet::Pops::Model::Nop) end it "should create an UNLESS expression with then and else parts" do built = UNLESS(literal(true), literal(1), literal(2)).model expect(built.class).to eq(Puppet::Pops::Model::UnlessExpression) expect(built.test.class).to eq(Puppet::Pops::Model::LiteralBoolean) expect(built.then_expr.class).to eq(Puppet::Pops::Model::LiteralInteger) expect(built.else_expr.class).to eq(Puppet::Pops::Model::LiteralInteger) end end context "When processing IF" do it "should create an IF expression with then part" do built = IF(literal(true), literal(1), literal(nil)).model expect(built.class).to eq(Puppet::Pops::Model::IfExpression) expect(built.test.class).to eq(Puppet::Pops::Model::LiteralBoolean) expect(built.then_expr.class).to eq(Puppet::Pops::Model::LiteralInteger) expect(built.else_expr.class).to eq(Puppet::Pops::Model::Nop) end it "should create an IF expression with then and else parts" do built = IF(literal(true), literal(1), literal(2)).model expect(built.class).to eq(Puppet::Pops::Model::IfExpression) expect(built.test.class).to eq(Puppet::Pops::Model::LiteralBoolean) expect(built.then_expr.class).to eq(Puppet::Pops::Model::LiteralInteger) expect(built.else_expr.class).to eq(Puppet::Pops::Model::LiteralInteger) end end context "When processing a Parameter" do it "should create a Parameter" do # PARAM(name, expr) # PARAM(name) # end end # LIST, HASH, KEY_ENTRY context "When processing Definition" do # DEFINITION(classname, arguments, statements) # should accept empty arguments, and no statements end context "When processing Hostclass" do # HOSTCLASS(classname, arguments, parent, statements) # parent may be passed as a nop /nil - check this works, should accept empty statements (nil) # should accept empty arguments end context "When processing Node" do end # Tested in the evaluator test already, but should be here to test factory assumptions # # TODO: CASE / WHEN # TODO: MAP end puppet-5.5.10/spec/unit/pops/issues_spec.rb0000644005276200011600000002066313417161721020565 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' describe "Puppet::Pops::Issues" do include Puppet::Pops::Issues it "should have an issue called NAME_WITH_HYPHEN" do x = Puppet::Pops::Issues::NAME_WITH_HYPHEN expect(x.class).to eq(Puppet::Pops::Issues::Issue) expect(x.issue_code).to eq(:NAME_WITH_HYPHEN) end it "should should format a message that requires an argument" do x = Puppet::Pops::Issues::NAME_WITH_HYPHEN expect(x.format(:name => 'Boo-Hoo', :label => Puppet::Pops::Model::ModelLabelProvider.new, :semantic => "dummy" )).to eq("A String may not have a name containing a hyphen. The name 'Boo-Hoo' is not legal") end it "should should format a message that does not require an argument" do x = Puppet::Pops::Issues::NOT_TOP_LEVEL expect(x.format()).to eq("Classes, definitions, and nodes may only appear at toplevel or inside other classes") end end describe "Puppet::Pops::IssueReporter" do let(:acceptor) { Puppet::Pops::Validation::Acceptor.new } def fake_positioned(number) stub("positioned_#{number}", :line => number, :pos => number) end def diagnostic(severity, number, args) Puppet::Pops::Validation::Diagnostic.new( severity, Puppet::Pops::Issues::Issue.new(number) { "#{severity}#{number}" }, "#{severity}file", fake_positioned(number), args) end def warning(number, args = {}) diagnostic(:warning, number, args) end def deprecation(number, args = {}) diagnostic(:deprecation, number, args) end def error(number, args = {}) diagnostic(:error, number, args) end context "given warnings" do before(:each) do acceptor.accept( warning(1) ) acceptor.accept( deprecation(1) ) end it "emits warnings if told to emit them" do Puppet::Log.expects(:create).twice.with(has_entries(:level => :warning, :message => regexp_matches(/warning1|deprecation1/))) Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :emit_warnings => true }) end it "does not emit warnings if not told to emit them" do Puppet::Log.expects(:create).never Puppet::Pops::IssueReporter.assert_and_report(acceptor, {}) end it "emits no warnings if :max_warnings is 0" do acceptor.accept( warning(2) ) Puppet[:max_warnings] = 0 Puppet::Log.expects(:create).once.with(has_entries(:level => :warning, :message => regexp_matches(/deprecation1/))) Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :emit_warnings => true }) end it "emits no more than 1 warning if :max_warnings is 1" do acceptor.accept( warning(2) ) acceptor.accept( warning(3) ) Puppet[:max_warnings] = 1 Puppet::Log.expects(:create).twice.with(has_entries(:level => :warning, :message => regexp_matches(/warning1|deprecation1/))) Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :emit_warnings => true }) end it "does not emit more deprecations warnings than the max deprecation warnings" do acceptor.accept( deprecation(2) ) Puppet[:max_deprecations] = 0 Puppet::Log.expects(:create).once.with(has_entries(:level => :warning, :message => regexp_matches(/warning1/))) Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :emit_warnings => true }) end it "does not emit deprecation warnings, but does emit regular warnings if disable_warnings includes deprecations" do Puppet[:disable_warnings] = 'deprecations' Puppet::Log.expects(:create).once.with(has_entries(:level => :warning, :message => regexp_matches(/warning1/))) Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :emit_warnings => true }) end it "includes diagnostic arguments in logged entry" do acceptor.accept( warning(2, :n => 'a') ) Puppet::Log.expects(:create).twice.with(has_entries(:level => :warning, :message => regexp_matches(/warning1|deprecation1/))) Puppet::Log.expects(:create).once.with(has_entries(:level => :warning, :message => regexp_matches(/warning2/), :arguments => {:n => 'a'})) Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :emit_warnings => true }) end end context "given errors" do it "logs nothing, but raises the given :message if :emit_errors is repressing error logging" do acceptor.accept( error(1) ) Puppet::Log.expects(:create).never expect do Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :emit_errors => false, :message => 'special'}) end.to raise_error(Puppet::ParseError, 'special') end it "prefixes :message if a single error is raised" do acceptor.accept( error(1) ) Puppet::Log.expects(:create).never expect do Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :message => 'special'}) end.to raise_error(Puppet::ParseError, /special error1/) end it "logs nothing and raises immediately if there is only one error" do acceptor.accept( error(1) ) Puppet::Log.expects(:create).never expect do Puppet::Pops::IssueReporter.assert_and_report(acceptor, { }) end.to raise_error(Puppet::ParseError, /error1/) end it "logs nothing and raises immediately if there are multiple errors but max_errors is 0" do acceptor.accept( error(1) ) acceptor.accept( error(2) ) Puppet[:max_errors] = 0 Puppet::Log.expects(:create).never expect do Puppet::Pops::IssueReporter.assert_and_report(acceptor, { }) end.to raise_error(Puppet::ParseError, /error1/) end it "logs the :message if there is more than one allowed error" do acceptor.accept( error(1) ) acceptor.accept( error(2) ) Puppet::Log.expects(:create).times(3).with(has_entries(:level => :err, :message => regexp_matches(/error1|error2|special/))) expect do Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :message => 'special'}) end.to raise_error(Puppet::ParseError, /Giving up/) end it "emits accumulated errors before raising a 'giving up' message if there are more errors than allowed" do acceptor.accept( error(1) ) acceptor.accept( error(2) ) acceptor.accept( error(3) ) Puppet[:max_errors] = 2 Puppet::Log.expects(:create).times(2).with(has_entries(:level => :err, :message => regexp_matches(/error1|error2/))) expect do Puppet::Pops::IssueReporter.assert_and_report(acceptor, { }) end.to raise_error(Puppet::ParseError, /3 errors.*Giving up/) end it "emits accumulated errors before raising a 'giving up' message if there are multiple errors but fewer than limits" do acceptor.accept( error(1) ) acceptor.accept( error(2) ) acceptor.accept( error(3) ) Puppet[:max_errors] = 4 Puppet::Log.expects(:create).times(3).with(has_entries(:level => :err, :message => regexp_matches(/error[123]/))) expect do Puppet::Pops::IssueReporter.assert_and_report(acceptor, { }) end.to raise_error(Puppet::ParseError, /3 errors.*Giving up/) end it "emits errors regardless of disable_warnings setting" do acceptor.accept( error(1) ) acceptor.accept( error(2) ) Puppet[:disable_warnings] = 'deprecations' Puppet::Log.expects(:create).times(2).with(has_entries(:level => :err, :message => regexp_matches(/error1|error2/))) expect do Puppet::Pops::IssueReporter.assert_and_report(acceptor, { }) end.to raise_error(Puppet::ParseError, /Giving up/) end it "includes diagnostic arguments in raised error" do acceptor.accept( error(1, :n => 'a') ) expect do Puppet::Pops::IssueReporter.assert_and_report(acceptor, { }) end.to raise_error(Puppet::ParseErrorWithIssue, /error1/) { |ex| expect(ex.arguments).to eq(:n => 'a')} end end context "given both" do it "logs warnings and errors" do acceptor.accept( warning(1) ) acceptor.accept( error(1) ) acceptor.accept( error(2) ) acceptor.accept( error(3) ) acceptor.accept( deprecation(1) ) Puppet[:max_errors] = 2 Puppet::Log.expects(:create).twice.with(has_entries(:level => :warning, :message => regexp_matches(/warning1|deprecation1/))) Puppet::Log.expects(:create).twice.with(has_entries(:level => :err, :message => regexp_matches(/error[123]/))) expect do Puppet::Pops::IssueReporter.assert_and_report(acceptor, { :emit_warnings => true }) end.to raise_error(Puppet::ParseError, /3 errors.*2 warnings.*Giving up/) end end end puppet-5.5.10/spec/unit/pops/label_provider_spec.rb0000644005276200011600000000237513417161721022243 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' describe Puppet::Pops::LabelProvider do class TestLabelProvider include Puppet::Pops::LabelProvider end let(:labeler) { TestLabelProvider.new } it "prefixes words that start with a vowel with an 'an'" do expect(labeler.a_an('owl')).to eq('an owl') end it "prefixes words that start with a consonant with an 'a'" do expect(labeler.a_an('bear')).to eq('a bear') end it "prefixes non-word characters with an 'a'" do expect(labeler.a_an('[] expression')).to eq('a [] expression') end it "ignores a single quote leading the word" do expect(labeler.a_an("'owl'")).to eq("an 'owl'") end it "ignores a double quote leading the word" do expect(labeler.a_an('"owl"')).to eq('an "owl"') end it "capitalizes the indefinite article for a word when requested" do expect(labeler.a_an_uc('owl')).to eq('An owl') end it "raises an error when missing a character to work with" do expect { labeler.a_an('"') }.to raise_error(Puppet::DevError, /<"> does not appear to contain a word/) end it "raises an error when given an empty string" do expect { labeler.a_an('') }.to raise_error(Puppet::DevError, /<> does not appear to contain a word/) end end puppet-5.5.10/spec/unit/pops/loaders/0000755005276200011600000000000013417162176017342 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/pops/loaders/environment_loader_spec.rb0000644005276200011600000001202013417161721024561 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/files' require 'puppet/pops' require 'puppet/loaders' describe 'Environment loader' do include PuppetSpec::Files let(:env_name) { 'spec' } let(:code_dir) { Puppet[:environmentpath] } let(:env_dir) { File.join(code_dir, env_name) } let(:env) { Puppet::Node::Environment.create(env_name.to_sym, [File.join(populated_code_dir, env_name, 'modules')]) } let(:populated_code_dir) do dir_contained_in(code_dir, env_name => env_content) PuppetSpec::Files.record_tmp(env_dir) code_dir end let(:env_content) { { 'lib' => { 'puppet' => { 'functions' => { 'ruby_foo.rb' => <<-RUBY.unindent, Puppet::Functions.create_function(:ruby_foo) do def ruby_foo() 'ruby_foo' end end RUBY 'environment' => { 'ruby_foo.rb' => <<-RUBY.unindent Puppet::Functions.create_function(:'environment::ruby_foo') do def ruby_foo() 'environment::ruby_foo' end end RUBY }, 'someother' => { 'ruby_foo.rb' => <<-RUBY.unindent Puppet::Functions.create_function(:'someother::ruby_foo') do def ruby_foo() 'someother::ruby_foo' end end RUBY }, } } }, 'functions' => { 'puppet_foo.pp' => <<-PUPPET.unindent, function puppet_foo() { 'puppet_foo' } PUPPET 'environment' => { 'puppet_foo.pp' => <<-PUPPET.unindent, function environment::puppet_foo() { 'environment::puppet_foo' } PUPPET }, 'someother' => { 'puppet_foo.pp' => <<-PUPPET.unindent, function somether::puppet_foo() { 'someother::puppet_foo' } PUPPET } }, 'types' => { 'footype.pp' => <<-PUPPET.unindent, type FooType = Enum['foo', 'bar', 'baz'] PUPPET 'environment' => { 'footype.pp' => <<-PUPPET.unindent, type Environment::FooType = Integer[0,9] PUPPET }, 'someother' => { 'footype.pp' => <<-PUPPET.unindent, type SomeOther::FooType = Float[0.0,9.0] PUPPET } } } } before(:each) do Puppet.push_context(:loaders => Puppet::Pops::Loaders.new(env)) end after(:each) do Puppet.pop_context end def load_or_nil(type, name) found = Puppet::Pops::Loaders.find_loader(nil).load_typed(Puppet::Pops::Loader::TypedName.new(type, name)) found.nil? ? nil : found.value end context 'loading a Ruby function' do it 'loads from global name space' do function = load_or_nil(:function, 'ruby_foo') expect(function).not_to be_nil expect(function.class.name).to eq('ruby_foo') expect(function).to be_a(Puppet::Functions::Function) end it 'loads from environment name space' do function = load_or_nil(:function, 'environment::ruby_foo') expect(function).not_to be_nil expect(function.class.name).to eq('environment::ruby_foo') expect(function).to be_a(Puppet::Functions::Function) end it 'fails to load from namespaces other than global or environment' do function = load_or_nil(:function, 'someother::ruby_foo') expect(function).to be_nil end end context 'loading a Puppet function' do it 'loads from global name space' do function = load_or_nil(:function, 'puppet_foo') expect(function).not_to be_nil expect(function.class.name).to eq('puppet_foo') expect(function).to be_a(Puppet::Functions::PuppetFunction) end it 'loads from environment name space' do function = load_or_nil(:function, 'environment::puppet_foo') expect(function).not_to be_nil expect(function.class.name).to eq('environment::puppet_foo') expect(function).to be_a(Puppet::Functions::PuppetFunction) end it 'fails to load from namespaces other than global or environment' do function = load_or_nil(:function, 'someother::puppet_foo') expect(function).to be_nil end end context 'loading a Puppet type' do it 'loads from global name space' do type = load_or_nil(:type, 'footype') expect(type).not_to be_nil expect(type).to be_a(Puppet::Pops::Types::PTypeAliasType) expect(type.name).to eq('FooType') end it 'loads from environment name space' do type = load_or_nil(:type, 'environment::footype') expect(type).not_to be_nil expect(type).to be_a(Puppet::Pops::Types::PTypeAliasType) expect(type.name).to eq('Environment::FooType') end it 'fails to load from namespaces other than global or environment' do type = load_or_nil(:type, 'someother::footype') expect(type).to be_nil end end end puppet-5.5.10/spec/unit/pops/loaders/loader_paths_spec.rb0000644005276200011600000000320313417161721023337 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/files' require 'puppet/pops' require 'puppet/loaders' describe 'loader paths' do include PuppetSpec::Files let(:static_loader) { Puppet::Pops::Loader::StaticLoader.new() } let(:unused_loaders) { Puppet::Pops::Loaders.new(Puppet::Node::Environment.create(:'*test*', [])) } it 'module loader has smart-paths that prunes unavailable paths' do module_dir = dir_containing('testmodule', {'lib' => {'puppet' => {'functions' => {'foo.rb' => 'Puppet::Functions.create_function("testmodule::foo") { def foo; end; }' } }}}) module_loader = Puppet::Pops::Loader::ModuleLoaders.module_loader_from(static_loader, unused_loaders, 'testmodule', module_dir) effective_paths = module_loader.smart_paths.effective_paths(:function) expect(effective_paths.size).to be_eql(1) expect(effective_paths[0].generic_path).to be_eql(File.join(module_dir, 'lib', 'puppet', 'functions')) end it 'all function smart-paths produces entries if they exist' do module_dir = dir_containing('testmodule', { 'lib' => { 'puppet' => { 'functions' => {'foo4x.rb' => 'ignored in this test'}, }}}) module_loader = Puppet::Pops::Loader::ModuleLoaders.module_loader_from(static_loader, unused_loaders, 'testmodule', module_dir) effective_paths = module_loader.smart_paths.effective_paths(:function) expect(effective_paths.size).to eq(1) expect(module_loader.path_index.size).to eq(1) path_index = module_loader.path_index expect(path_index).to include(File.join(module_dir, 'lib', 'puppet', 'functions', 'foo4x.rb')) end end puppet-5.5.10/spec/unit/pops/loaders/dependency_loader_spec.rb0000644005276200011600000001202413417161722024340 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/files' require 'puppet/pops' require 'puppet/loaders' describe 'dependency loader' do include PuppetSpec::Files let(:static_loader) { Puppet::Pops::Loader::StaticLoader.new() } let(:loaders) { Puppet::Pops::Loaders.new(Puppet::Node::Environment.create(:testing, [])) } describe 'FileBased module loader' do it 'prints a pretty name for itself when inspected' do module_dir = dir_containing('testmodule', { 'lib' => { 'puppet' => { 'functions' => { 'testmodule' => { 'foo.rb' => 'Puppet::Functions.create_function("foo") { def foo; end; }' }}}}}) loader = loader_for('testmodule', module_dir) expect(loader.inspect).to eq("(DependencyLoader 'test-dep' [(ModuleLoader::FileBased 'testmodule' 'testmodule')])") expect(loader.to_s).to eq("(DependencyLoader 'test-dep' [(ModuleLoader::FileBased 'testmodule' 'testmodule')])") end it 'load something in global name space raises an error' do module_dir = dir_containing('testmodule', { 'lib' => { 'puppet' => { 'functions' => { 'testmodule' => { 'foo.rb' => 'Puppet::Functions.create_function("foo") { def foo; end; }' }}}}}) loader = loader_for('testmodule', module_dir) expect do loader.load_typed(typed_name(:function, 'testmodule::foo')).value end.to raise_error(ArgumentError, /produced mis-matched name, expected 'testmodule::foo', got foo/) end it 'can load something in a qualified name space' do module_dir = dir_containing('testmodule', { 'lib' => { 'puppet' => { 'functions' => { 'testmodule' => { 'foo.rb' => 'Puppet::Functions.create_function("testmodule::foo") { def foo; end; }' }}}}}) loader = loader_for('testmodule', module_dir) function = loader.load_typed(typed_name(:function, 'testmodule::foo')).value expect(function.class.name).to eq('testmodule::foo') expect(function.is_a?(Puppet::Functions::Function)).to eq(true) end it 'can load something in a qualified name space more than once' do module_dir = dir_containing('testmodule', { 'lib' => { 'puppet' => { 'functions' => { 'testmodule' => { 'foo.rb' => 'Puppet::Functions.create_function("testmodule::foo") { def foo; end; }' }}}}}) loader = loader_for('testmodule', module_dir) function = loader.load_typed(typed_name(:function, 'testmodule::foo')).value expect(function.class.name).to eq('testmodule::foo') expect(function.is_a?(Puppet::Functions::Function)).to eq(true) function = loader.load_typed(typed_name(:function, 'testmodule::foo')).value expect(function.class.name).to eq('testmodule::foo') expect(function.is_a?(Puppet::Functions::Function)).to eq(true) end describe "when parsing files from disk" do # First line of Rune version of Rune poem at http://www.columbia.edu/~fdc/utf8/ # characters chosen since they will not parse on Windows with codepage 437 or 1252 # Section 3.2.1.3 of Ruby spec guarantees that \u strings are encoded as UTF-8 let (:node) { Puppet::Node.new('node') } let (:rune_utf8) { "\u16A0\u16C7\u16BB" } # ᚠᛇᚻ let (:code_utf8) do <<-CODE Puppet::Functions.create_function('testmodule::foo') { def foo return \"#{rune_utf8}\" end } CODE end context 'when loading files from disk' do it 'should always read files as UTF-8' do if Puppet.features.microsoft_windows? && Encoding.default_external == Encoding::UTF_8 raise 'This test must be run in a codepage other than 65001 to validate behavior' end module_dir = dir_containing('testmodule', { 'lib' => { 'puppet' => { 'functions' => { 'testmodule' => { 'foo.rb' => code_utf8 }}}}}) loader = loader_for('testmodule', module_dir) function = loader.load_typed(typed_name(:function, 'testmodule::foo')).value expect(function.call({})).to eq(rune_utf8) end it 'currently ignores the UTF-8 BOM (Byte Order Mark) when loading module files' do bom = "\uFEFF" if Puppet.features.microsoft_windows? && Encoding.default_external == Encoding::UTF_8 raise 'This test must be run in a codepage other than 65001 to validate behavior' end module_dir = dir_containing('testmodule', { 'lib' => { 'puppet' => { 'functions' => { 'testmodule' => { 'foo.rb' => "#{bom}#{code_utf8}" }}}}}) loader = loader_for('testmodule', module_dir) function = loader.load_typed(typed_name(:function, 'testmodule::foo')).value expect(function.call({})).to eq(rune_utf8) end end end end def loader_for(name, dir) module_loader = Puppet::Pops::Loader::ModuleLoaders.module_loader_from(static_loader, loaders, name, dir) Puppet::Pops::Loader::DependencyLoader.new(static_loader, 'test-dep', [module_loader]) end def typed_name(type, name) Puppet::Pops::Loader::TypedName.new(type, name) end end puppet-5.5.10/spec/unit/pops/loaders/loader_spec.rb0000644005276200011600000004327413417161722022155 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/files' require 'puppet_spec/compiler' require 'puppet/pops' require 'puppet/loaders' module Puppet::Pops module Loader describe 'The Loader' do include PuppetSpec::Compiler include PuppetSpec::Files before(:each) do Puppet[:tasks] = true end let(:testing_env) do { 'testing' => { 'functions' => functions, 'lib' => { 'puppet' => lib_puppet }, 'manifests' => manifests, 'modules' => modules, 'plans' => plans, 'tasks' => tasks, 'types' => types, } } end let(:functions) { {} } let(:manifests) { {} } let(:modules) { {} } let(:plans) { {} } let(:lib_puppet) { {} } let(:tasks) { {} } let(:types) { {} } let(:environments_dir) { Puppet[:environmentpath] } let(:testing_env_dir) do dir_contained_in(environments_dir, testing_env) env_dir = File.join(environments_dir, 'testing') PuppetSpec::Files.record_tmp(env_dir) env_dir end let(:modules_dir) { File.join(testing_env_dir, 'modules') } let(:env) { Puppet::Node::Environment.create(:testing, [modules_dir]) } let(:node) { Puppet::Node.new('test', :environment => env) } let(:loader) { Loaders.find_loader(nil) } let(:tasks_feature) { false } before(:each) do Puppet[:tasks] = tasks_feature loaders = Loaders.new(env) Puppet.push_context(:loaders => loaders) loaders.pre_load end after(:each) { Puppet.pop_context } context 'when doing discovery' do context 'of things' do it 'finds statically basic types' do expect(loader.discover(:type)).to include(tn(:type, 'integer')) end it 'finds statically loaded types' do expect(loader.discover(:type)).to include(tn(:type, 'file')) end it 'finds statically loaded Object types' do expect(loader.discover(:type)).to include(tn(:type, 'puppet::ast::accessexpression')) end context 'in environment' do let(:types) { { 'global.pp' => <<-PUPPET.unindent, type Global = Integer PUPPET 'environment' => { 'env.pp' => <<-PUPPET.unindent, type Environment::Env = String PUPPET } } } let(:tasks) { { 'globtask' => '', 'environment' => { 'envtask' => '' } } } let(:functions) { { 'globfunc.pp' => 'function globfunc() {}', 'environment' => { 'envfunc.pp' => 'function environment::envfunc() {}' } } } let(:lib_puppet) { { 'functions' => { 'globrubyfunc.rb' => 'Puppet::Functions.create_function(:globrubyfunc) { def globrubyfunc; end }', 'environment' => { 'envrubyfunc.rb' => "Puppet::Functions.create_function(:'environment::envrubyfunc') { def envrubyfunc; end }", } } } } it 'finds global types in environment' do expect(loader.discover(:type)).to include(tn(:type, 'global')) end it 'finds global functions in environment' do expect(loader.discover(:function)).to include(tn(:function, 'lookup')) end it 'finds types prefixed with Environment in environment' do expect(loader.discover(:type)).to include(tn(:type, 'environment::env')) end it 'finds global functions in environment' do expect(loader.discover(:function)).to include(tn(:function, 'globfunc')) end it 'finds functions prefixed with Environment in environment' do expect(loader.discover(:function)).to include(tn(:function, 'environment::envfunc')) end it 'finds global ruby functions in environment' do expect(loader.discover(:function)).to include(tn(:function, 'globrubyfunc')) end it 'finds ruby functions prefixed with Environment in environment' do expect(loader.discover(:function)).to include(tn(:function, 'environment::envrubyfunc')) end it 'can filter the list of discovered entries using a block' do expect(loader.discover(:function) { |t| t.name =~ /rubyfunc\z/ }).to contain_exactly( tn(:function, 'environment::envrubyfunc'), tn(:function, 'globrubyfunc') ) end context 'with tasks feature enabled' do let(:tasks_feature) { true } it 'finds global tasks in environment' do expect(loader.discover(:task)).to include(tn(:task, 'globtask')) end it 'finds tasks prefixed with Environment in environment' do expect(loader.discover(:task)).to include(tn(:task, 'environment::envtask')) end end context 'with multiple modules' do let(:metadata_json_a) { { 'name' => 'example/a', 'version' => '0.1.0', 'source' => 'git@github.com/example/example-a.git', 'dependencies' => [{'name' => 'c', 'version_range' => '>=0.1.0'}], 'author' => 'Bob the Builder', 'license' => 'Apache-2.0' } } let(:metadata_json_b) { { 'name' => 'example/b', 'version' => '0.1.0', 'source' => 'git@github.com/example/example-b.git', 'dependencies' => [{'name' => 'c', 'version_range' => '>=0.1.0'}], 'author' => 'Bob the Builder', 'license' => 'Apache-2.0' } } let(:metadata_json_c) { { 'name' => 'example/c', 'version' => '0.1.0', 'source' => 'git@github.com/example/example-c.git', 'dependencies' => [], 'author' => 'Bob the Builder', 'license' => 'Apache-2.0' } } let(:modules) { { 'a' => { 'functions' => a_functions, 'lib' => { 'puppet' => a_lib_puppet }, 'plans' => a_plans, 'tasks' => a_tasks, 'types' => a_types, 'metadata.json' => metadata_json_a.to_json }, 'b' => { 'functions' => b_functions, 'lib' => { 'puppet' => b_lib_puppet }, 'plans' => b_plans, 'tasks' => b_tasks, 'types' => b_types, 'metadata.json' => metadata_json_b.to_json }, 'c' => { 'types' => c_types, 'tasks' => c_tasks, 'metadata.json' => metadata_json_c.to_json }, } } let(:a_plans) { { 'aplan.pp' => <<-PUPPET.unindent, plan a::aplan() {} PUPPET } } let(:a_types) { { 'atype.pp' => <<-PUPPET.unindent, type A::Atype = Integer PUPPET } } let(:a_tasks) { { 'atask' => '', } } let(:a_functions) { { 'afunc.pp' => 'function a::afunc() {}', } } let(:a_lib_puppet) { { 'functions' => { 'a' => { 'arubyfunc.rb' => "Puppet::Functions.create_function(:'a::arubyfunc') { def arubyfunc; end }", } } } } let(:b_plans) { { 'init.pp' => <<-PUPPET.unindent, plan b() {} PUPPET 'aplan.pp' => <<-PUPPET.unindent, plan b::aplan() {} PUPPET } } let(:b_types) { { 'atype.pp' => <<-PUPPET.unindent, type B::Atype = Integer PUPPET } } let(:b_tasks) { { 'init.json' => <<-JSON.unindent, { "description": "test task b", "parameters": {} } JSON 'init.sh' => "# doing exactly nothing\n", 'atask' => "# doing exactly nothing\n", 'atask.json' => <<-JSON.unindent, { "description": "test task b::atask", "input_method": "stdin", "parameters": { "string_param": { "description": "A string parameter", "type": "String[1]" }, "int_param": { "description": "An integer parameter", "type": "Integer" } } } JSON } } let(:b_functions) { { 'afunc.pp' => 'function b::afunc() {}', } } let(:b_lib_puppet) { { 'functions' => { 'b' => { 'arubyfunc.rb' => "Puppet::Functions.create_function(:'b::arubyfunc') { def arubyfunc; end }", } } } } let(:c_types) { { 'atype.pp' => <<-PUPPET.unindent, type C::Atype = Integer PUPPET } } let(:c_tasks) { { 'foo.sh' => <<-SH.unindent, # This is a task that does nothing SH 'fee.md' => <<-MD.unindent, This is not a task because it has .md extension MD 'fum.conf' => <<-CONF.unindent, text=This is not a task because it has .conf extension CONF 'bad_syntax.sh' => '', 'bad_syntax.json' => <<-TXT.unindent, text => This is not a task because JSON is unparsable TXT 'bad_content.sh' => '', 'bad_content.json' => <<-JSON.unindent, { "description": "This is not a task because parameters is misspelled", "paramters": { "string_param": { "type": "String[1]" } } } JSON 'missing_adjacent.json' => <<-JSON.unindent, { "description": "This is not a task because there is no adjacent file with the same base name", "parameters": { "string_param": { "type": "String[1]" } } } JSON } } it 'private loader finds types in all modules' do expect(loader.private_loader.discover(:type) { |t| t.name =~ /^.::.*\z/ }).to( contain_exactly(tn(:type, 'a::atype'), tn(:type, 'b::atype'), tn(:type, 'c::atype'))) end it 'module loader finds types only in itself' do expect(Loaders.find_loader('a').discover(:type) { |t| t.name =~ /^.::.*\z/ }).to( contain_exactly(tn(:type, 'a::atype'))) end it 'private loader finds functions in all modules' do expect(loader.private_loader.discover(:function) { |t| t.name =~ /^.::.*\z/ }).to( contain_exactly(tn(:function, 'a::afunc'), tn(:function, 'b::afunc'), tn(:function, 'a::arubyfunc'), tn(:function, 'b::arubyfunc'))) end it 'module loader finds functions only in itself' do expect(Loaders.find_loader('a').discover(:function) { |t| t.name =~ /^.::.*\z/ }).to( contain_exactly(tn(:function, 'a::afunc'), tn(:function, 'a::arubyfunc'))) end it 'discover is only called once on dependent loader' do ModuleLoaders::FileBased.any_instance.expects(:discover).times(4).with(:type, nil, Pcore::RUNTIME_NAME_AUTHORITY).returns([]) expect(loader.private_loader.discover(:type) { |t| t.name =~ /^.::.*\z/ }).to(contain_exactly()) end context 'with tasks enabled' do let(:tasks_feature) { true } it 'private loader finds plans in all modules' do expect(loader.private_loader.discover(:plan) { |t| t.name =~ /^.(?:::.*)?\z/ }).to( contain_exactly(tn(:plan, 'b'), tn(:plan, 'a::aplan'), tn(:plan, 'b::aplan'))) end it 'module loader finds plans only in itself' do expect(Loaders.find_loader('a').discover(:plan)).to( contain_exactly(tn(:plan, 'a::aplan'))) end it 'private loader finds types in all modules' do expect(loader.private_loader.discover(:type) { |t| t.name =~ /^.::.*\z/ }).to( contain_exactly(tn(:type, 'a::atype'), tn(:type, 'b::atype'), tn(:type, 'c::atype'))) end it 'private loader finds tasks in all modules' do expect(loader.private_loader.discover(:task) { |t| t.name =~ /^.(?:::.*)?\z/ }).to( contain_exactly(tn(:task, 'a::atask'), tn(:task, 'b::atask'), tn(:task, 'b'), tn(:task, 'c::foo'))) end it 'module loader finds types only in itself' do expect(Loaders.find_loader('a').discover(:type) { |t| t.name =~ /^.::.*\z/ }).to( contain_exactly(tn(:type, 'a::atype'))) end it 'module loader finds tasks only in itself' do expect(Loaders.find_loader('a').discover(:task) { |t| t.name =~ /^.::.*\z/ }).to( contain_exactly(tn(:task, 'a::atask'))) end it 'module loader does not consider files with .md and .conf extension to be tasks' do expect(Loaders.find_loader('c').discover(:task) { |t| t.name =~ /(?:foo|fee|fum)\z/ }).to( contain_exactly(tn(:task, 'c::foo'))) end it 'without error_collector, invalid task metadata results in warnings' do logs = [] Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do expect(Loaders.find_loader('c').discover(:task)).to( contain_exactly(tn(:task, 'environment::envtask'), tn(:task, 'globtask'), tn(:task, 'c::foo'))) end expect(logs.select { |log| log.level == :warning }.map { |log| log.message }).to( contain_exactly(/unexpected token/, /unrecognized key/, /No source besides task metadata was found/) ) end it 'with error_collector, errors are collected and no warnings are logged' do logs = [] error_collector = [] Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do expect(Loaders.find_loader('c').discover(:task, error_collector)).to( contain_exactly(tn(:task, 'environment::envtask'), tn(:task, 'globtask'), tn(:task, 'c::foo'))) end expect(logs.select { |log| log.level == :warning }.map { |log| log.message }).to be_empty expect(error_collector.size).to eql(3) expect(error_collector.all? { |e| e.is_a?(Puppet::DataTypes::Error) }) expect(error_collector.all? { |e| e.issue_code == Puppet::Pops::Issues::LOADER_FAILURE.issue_code }) expect(error_collector.map { |e| e.details['original_error'] }).to( contain_exactly(/unexpected token/, /unrecognized key/, /No source besides task metadata was found/) ) end context 'and an environment without directory' do let(:environments_dir) { tmpdir('loader_spec') } let(:env) { Puppet::Node::Environment.create(:none_such, [modules_dir]) } it 'an EmptyLoader is used and module loader finds types' do Puppet::Pops::Loader::ModuleLoaders::EmptyLoader.any_instance.expects(:find).at_least_once.returns(nil) expect(Loaders.find_loader('a').discover(:type) { |t| t.name =~ /^.::.*\z/ }).to( contain_exactly(tn(:type, 'a::atype'))) end it 'an EmptyLoader is used and module loader finds tasks' do Puppet::Pops::Loader::ModuleLoaders::EmptyLoader.any_instance.expects(:find).at_least_once.returns(nil) expect(Loaders.find_loader('a').discover(:task) { |t| t.name =~ /^.::.*\z/ }).to( contain_exactly(tn(:task, 'a::atask'))) end end end context 'with no explicit dependencies' do let(:modules) do { 'a' => { 'functions' => a_functions, 'lib' => { 'puppet' => a_lib_puppet }, 'plans' => a_plans, 'tasks' => a_tasks, 'types' => a_types, }, 'b' => { 'functions' => b_functions, 'lib' => { 'puppet' => b_lib_puppet }, 'plans' => b_plans, 'tasks' => b_tasks, 'types' => b_types, }, 'c' => { 'types' => c_types, }, } end it 'discover is only called once on dependent loader' do ModuleLoaders::FileBased.any_instance.expects(:discover).times(4).with(:type, nil, Pcore::RUNTIME_NAME_AUTHORITY).returns([]) expect(loader.private_loader.discover(:type) { |t| t.name =~ /^.::.*\z/ }).to(contain_exactly()) end end end end end end def tn(type, name) TypedName.new(type, name) end end end end puppet-5.5.10/spec/unit/pops/loaders/loaders_spec.rb0000644005276200011600000007261613417161722022342 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/files' require 'puppet_spec/compiler' require 'puppet/pops' require 'puppet/loaders' describe 'loader helper classes' do it 'NamedEntry holds values and is frozen' do ne = Puppet::Pops::Loader::Loader::NamedEntry.new('name', 'value', 'origin') expect(ne.frozen?).to be_truthy expect(ne.typed_name).to eql('name') expect(ne.origin).to eq('origin') expect(ne.value).to eq('value') end it 'TypedName holds values and is frozen' do tn = Puppet::Pops::Loader::TypedName.new(:function, '::foo::bar') expect(tn.frozen?).to be_truthy expect(tn.type).to eq(:function) expect(tn.name_parts).to eq(['foo', 'bar']) expect(tn.name).to eq('foo::bar') expect(tn.qualified?).to be_truthy end it 'TypedName converts name to lower case' do tn = Puppet::Pops::Loader::TypedName.new(:type, '::Foo::Bar') expect(tn.name_parts).to eq(['foo', 'bar']) expect(tn.name).to eq('foo::bar') end it 'TypedName is case insensitive' do expect(Puppet::Pops::Loader::TypedName.new(:type, '::Foo::Bar')).to eq(Puppet::Pops::Loader::TypedName.new(:type, '::foo::bar')) end end describe 'loaders' do include PuppetSpec::Files include PuppetSpec::Compiler let(:module_without_metadata) { File.join(config_dir('wo_metadata_module'), 'modules') } let(:module_without_lib) { File.join(config_dir('module_no_lib'), 'modules') } let(:mix_4x_and_3x_functions) { config_dir('mix_4x_and_3x_functions') } let(:module_with_metadata) { File.join(config_dir('single_module'), 'modules') } let(:dependent_modules_with_metadata) { config_dir('dependent_modules_with_metadata') } let(:no_modules) { config_dir('no_modules') } let(:user_metadata_path) { File.join(dependent_modules_with_metadata, 'modules/user/metadata.json') } let(:usee_metadata_path) { File.join(dependent_modules_with_metadata, 'modules/usee/metadata.json') } let(:usee2_metadata_path) { File.join(dependent_modules_with_metadata, 'modules/usee2/metadata.json') } let(:empty_test_env) { environment_for() } # Loaders caches the puppet_system_loader, must reset between tests before(:each) { Puppet::Pops::Loaders.clear() } context 'when loading pp resource types using auto loading' do let(:pp_resources) { config_dir('pp_resources') } let(:environments) { Puppet::Environments::Directories.new(my_fixture_dir, []) } let(:env) { Puppet::Node::Environment.create(:'pp_resources', [File.join(pp_resources, 'modules')]) } let(:compiler) { Puppet::Parser::Compiler.new(Puppet::Node.new("test", :environment => env)) } let(:loader) { Puppet::Pops::Loaders.loaders.find_loader(nil) } around(:each) do |example| Puppet.override(:environments => environments) do Puppet.override(:loaders => compiler.loaders) do example.run end end end it 'finds a resource type that resides under /.resource_types' do rt = loader.load(:resource_type_pp, 'myresource') expect(rt).to be_a(Puppet::Pops::Resource::ResourceTypeImpl) end it 'does not allow additional logic in the file' do expect{loader.load(:resource_type_pp, 'addlogic')}.to raise_error(ArgumentError, /it has additional logic/) end it 'does not allow creation of classes other than Puppet::Resource::ResourceType3' do expect{loader.load(:resource_type_pp, 'badcall')}.to raise_error(ArgumentError, /no call to Puppet::Resource::ResourceType3.new found/) end it 'does not allow creation of other types' do expect{loader.load(:resource_type_pp, 'wrongname')}.to raise_error(ArgumentError, /produced resource type with the wrong name, expected 'wrongname', actual 'notwrongname'/) end it 'errors with message about empty file for files that contain no logic' do expect{loader.load(:resource_type_pp, 'empty')}.to raise_error(ArgumentError, /it is empty/) end end it 'creates a puppet_system loader' do loaders = Puppet::Pops::Loaders.new(empty_test_env) expect(loaders.puppet_system_loader()).to be_a(Puppet::Pops::Loader::ModuleLoaders::FileBased) end it 'creates an environment loader' do loaders = Puppet::Pops::Loaders.new(empty_test_env) expect(loaders.public_environment_loader()).to be_a(Puppet::Pops::Loader::SimpleEnvironmentLoader) expect(loaders.public_environment_loader().to_s).to eql("(SimpleEnvironmentLoader 'environment')") expect(loaders.private_environment_loader()).to be_a(Puppet::Pops::Loader::DependencyLoader) expect(loaders.private_environment_loader().to_s).to eql("(DependencyLoader 'environment private' [])") end context 'when loading from a module' do it 'loads a ruby function using a qualified or unqualified name' do loaders = Puppet::Pops::Loaders.new(environment_for(module_with_metadata)) modulea_loader = loaders.public_loader_for_module('modulea') unqualified_function = modulea_loader.load_typed(typed_name(:function, 'rb_func_a')).value qualified_function = modulea_loader.load_typed(typed_name(:function, 'modulea::rb_func_a')).value expect(unqualified_function).to be_a(Puppet::Functions::Function) expect(qualified_function).to be_a(Puppet::Functions::Function) expect(unqualified_function.class.name).to eq('rb_func_a') expect(qualified_function.class.name).to eq('modulea::rb_func_a') end it 'loads a puppet function using a qualified name in module' do loaders = Puppet::Pops::Loaders.new(environment_for(module_with_metadata)) modulea_loader = loaders.public_loader_for_module('modulea') qualified_function = modulea_loader.load_typed(typed_name(:function, 'modulea::hello')).value expect(qualified_function).to be_a(Puppet::Functions::Function) expect(qualified_function.class.name).to eq('modulea::hello') end it 'loads a puppet function from a module without a lib directory' do loaders = Puppet::Pops::Loaders.new(environment_for(module_without_lib)) modulea_loader = loaders.public_loader_for_module('modulea') qualified_function = modulea_loader.load_typed(typed_name(:function, 'modulea::hello')).value expect(qualified_function).to be_a(Puppet::Functions::Function) expect(qualified_function.class.name).to eq('modulea::hello') end it 'loads a puppet function in a sub namespace of module' do loaders = Puppet::Pops::Loaders.new(environment_for(module_with_metadata)) modulea_loader = loaders.public_loader_for_module('modulea') qualified_function = modulea_loader.load_typed(typed_name(:function, 'modulea::subspace::hello')).value expect(qualified_function).to be_a(Puppet::Functions::Function) expect(qualified_function.class.name).to eq('modulea::subspace::hello') end it 'loader does not add namespace if not given' do loaders = Puppet::Pops::Loaders.new(environment_for(module_without_metadata)) moduleb_loader = loaders.public_loader_for_module('moduleb') expect(moduleb_loader.load_typed(typed_name(:function, 'rb_func_b'))).to be_nil end it 'loader allows loading a function more than once' do File.stubs(:read).with(user_metadata_path, {:encoding => 'utf-8'}).returns '' File.stubs(:read).with(usee_metadata_path, {:encoding => 'utf-8'}).raises Errno::ENOENT File.stubs(:read).with(usee2_metadata_path, {:encoding => 'utf-8'}).raises Errno::ENOENT env = environment_for(File.join(dependent_modules_with_metadata, 'modules')) loaders = Puppet::Pops::Loaders.new(env) moduleb_loader = loaders.private_loader_for_module('user') function = moduleb_loader.load_typed(typed_name(:function, 'user::caller')).value expect(function.call({})).to eql("usee::callee() was told 'passed value' + I am user::caller()") function = moduleb_loader.load_typed(typed_name(:function, 'user::caller')).value expect(function.call({})).to eql("usee::callee() was told 'passed value' + I am user::caller()") end end context 'when loading from a module with metadata' do let(:env) { environment_for(File.join(dependent_modules_with_metadata, 'modules')) } let(:scope) { Puppet::Parser::Compiler.new(Puppet::Node.new("test", :environment => env)).newscope(nil) } let(:environmentpath) { my_fixture_dir } let(:node) { Puppet::Node.new('test', :facts => Puppet::Node::Facts.new('facts', {}), :environment => 'dependent_modules_with_metadata') } let(:compiler) { Puppet::Parser::Compiler.new(node) } let(:user_metadata) { { 'name' => 'test-user', 'author' => 'test', 'description' => '', 'license' => '', 'source' => '', 'version' => '1.0.0', 'dependencies' => [] } } def compile_and_get_notifications(code) Puppet[:code] = code catalog = block_given? ? compiler.compile { |c| yield(compiler.topscope); c } : compiler.compile catalog.resources.map(&:ref).select { |r| r.start_with?('Notify[') }.map { |r| r[7..-2] } end around(:each) do |example| # Initialize settings to get a full compile as close as possible to a real # environment load Puppet.settings.initialize_global_settings # Initialize loaders based on the environmentpath. It does not work to # just set the setting environmentpath for some reason - this achieves the same: # - first a loader is created, loading directory environments from the fixture (there is # one environment, 'sample', which will be loaded since the node references this # environment by name). # - secondly, the created env loader is set as 'environments' in the puppet context. # environments = Puppet::Environments::Directories.new(environmentpath, []) Puppet.override(:environments => environments) do example.run end end it 'all dependent modules are visible' do File.stubs(:read).with(user_metadata_path, {:encoding => 'utf-8'}).returns user_metadata.merge('dependencies' => [ { 'name' => 'test-usee'}, { 'name' => 'test-usee2'} ]).to_pson File.stubs(:read).with(usee_metadata_path, {:encoding => 'utf-8'}).raises Errno::ENOENT File.stubs(:read).with(usee2_metadata_path, {:encoding => 'utf-8'}).raises Errno::ENOENT loaders = Puppet::Pops::Loaders.new(env) moduleb_loader = loaders.private_loader_for_module('user') function = moduleb_loader.load_typed(typed_name(:function, 'user::caller')).value expect(function.call({})).to eql("usee::callee() was told 'passed value' + I am user::caller()") function = moduleb_loader.load_typed(typed_name(:function, 'user::caller2')).value expect(function.call({})).to eql("usee2::callee() was told 'passed value' + I am user::caller2()") end it 'all other modules are visible when tasks are enabled' do Puppet[:tasks] = true env = environment_for(File.join(dependent_modules_with_metadata, 'modules')) loaders = Puppet::Pops::Loaders.new(env) moduleb_loader = loaders.private_loader_for_module('user') function = moduleb_loader.load_typed(typed_name(:function, 'user::caller')).value expect(function.call({})).to eql("usee::callee() was told 'passed value' + I am user::caller()") end [ 'outside a function', 'a puppet function declared under functions', 'a puppet function declared in init.pp', 'a ruby function'].each_with_index do |from, from_idx| [ {:from => from, :called => 'a puppet function declared under functions', :expects => "I'm the function usee::usee_puppet()"}, {:from => from, :called => 'a puppet function declared in init.pp', :expects => "I'm the function usee::usee_puppet_init()"}, {:from => from, :called => 'a ruby function', :expects => "I'm the function usee::usee_ruby()"} ].each_with_index do |desc, called_idx| case_number = from_idx * 3 + called_idx + 1 it "can call #{desc[:called]} from #{desc[:from]} when dependency is present in metadata.json" do File.stubs(:read).with(user_metadata_path, {:encoding => 'utf-8'}).returns user_metadata.merge('dependencies' => [ { 'name' => 'test-usee'} ]).to_pson File.stubs(:read).with(usee_metadata_path, {:encoding => 'utf-8'}).raises Errno::ENOENT File.stubs(:read).with(usee2_metadata_path, {:encoding => 'utf-8'}).raises Errno::ENOENT Puppet[:code] = "$case_number = #{case_number}\ninclude ::user" catalog = compiler.compile resource = catalog.resource('Notify', "case_#{case_number}") expect(resource).not_to be_nil expect(resource['message']).to eq(desc[:expects]) end it "can call #{desc[:called]} from #{desc[:from]} when no metadata is present" do Puppet::Module.any_instance.expects('has_metadata?').at_least_once.returns(false) Puppet[:code] = "$case_number = #{case_number}\ninclude ::user" catalog = compiler.compile resource = catalog.resource('Notify', "case_#{case_number}") expect(resource).not_to be_nil expect(resource['message']).to eq(desc[:expects]) end it "can not call #{desc[:called]} from #{desc[:from]} if dependency is missing in existing metadata.json" do File.stubs(:read).with(user_metadata_path, {:encoding => 'utf-8'}).returns user_metadata.merge('dependencies' => []).to_pson File.stubs(:read).with(usee_metadata_path, {:encoding => 'utf-8'}).raises Errno::ENOENT File.stubs(:read).with(usee2_metadata_path, {:encoding => 'utf-8'}).raises Errno::ENOENT Puppet[:code] = "$case_number = #{case_number}\ninclude ::user" expect { compiler.compile }.to raise_error(Puppet::Error, /Unknown function/) end end end it "a type can reference an autoloaded type alias from another module when dependency is present in metadata.json" do File.stubs(:read).with(user_metadata_path, {:encoding => 'utf-8'}).returns user_metadata.merge('dependencies' => [ { 'name' => 'test-usee'} ]).to_pson File.stubs(:read).with(usee_metadata_path, {:encoding => 'utf-8'}).raises Errno::ENOENT File.stubs(:read).with(usee2_metadata_path, {:encoding => 'utf-8'}).raises Errno::ENOENT expect(eval_and_collect_notices(<<-CODE, node)).to eq(['ok']) assert_type(Usee::Zero, 0) notice(ok) CODE end it "a type can reference an autoloaded type alias from another module when no metadata is present" do Puppet::Module.any_instance.expects('has_metadata?').at_least_once.returns(false) expect(eval_and_collect_notices(<<-CODE, node)).to eq(['ok']) assert_type(Usee::Zero, 0) notice(ok) CODE end it "a type can reference a type alias from another module when other module has it declared in init.pp" do File.stubs(:read).with(user_metadata_path, {:encoding => 'utf-8'}).returns user_metadata.merge('dependencies' => [ { 'name' => 'test-usee'} ]).to_pson File.stubs(:read).with(usee_metadata_path, {:encoding => 'utf-8'}).raises Errno::ENOENT File.stubs(:read).with(usee2_metadata_path, {:encoding => 'utf-8'}).raises Errno::ENOENT expect(eval_and_collect_notices(<<-CODE, node)).to eq(['ok']) include 'usee' assert_type(Usee::One, 1) notice(ok) CODE end it "an autoloaded type can reference an autoloaded type alias from another module when dependency is present in metadata.json" do File.stubs(:read).with(user_metadata_path, {:encoding => 'utf-8'}).returns user_metadata.merge('dependencies' => [ { 'name' => 'test-usee'} ]).to_pson File.stubs(:read).with(usee_metadata_path, {:encoding => 'utf-8'}).raises Errno::ENOENT File.stubs(:read).with(usee2_metadata_path, {:encoding => 'utf-8'}).raises Errno::ENOENT expect(eval_and_collect_notices(<<-CODE, node)).to eq(['ok']) assert_type(User::WithUseeZero, [0]) notice(ok) CODE end it "an autoloaded type can reference an autoloaded type alias from another module when other module has it declared in init.pp" do File.stubs(:read).with(user_metadata_path, {:encoding => 'utf-8'}).returns user_metadata.merge('dependencies' => [ { 'name' => 'test-usee'} ]).to_pson File.stubs(:read).with(usee_metadata_path, {:encoding => 'utf-8'}).raises Errno::ENOENT File.stubs(:read).with(usee2_metadata_path, {:encoding => 'utf-8'}).raises Errno::ENOENT expect(eval_and_collect_notices(<<-CODE, node)).to eq(['ok']) include 'usee' assert_type(User::WithUseeOne, [1]) notice(ok) CODE end end context 'when loading from a module without metadata' do it 'loads a ruby function with a qualified name' do loaders = Puppet::Pops::Loaders.new(environment_for(module_without_metadata)) moduleb_loader = loaders.public_loader_for_module('moduleb') function = moduleb_loader.load_typed(typed_name(:function, 'moduleb::rb_func_b')).value expect(function).to be_a(Puppet::Functions::Function) expect(function.class.name).to eq('moduleb::rb_func_b') end it 'all other modules are visible' do env = environment_for(module_with_metadata, module_without_metadata) loaders = Puppet::Pops::Loaders.new(env) moduleb_loader = loaders.private_loader_for_module('moduleb') function = moduleb_loader.load_typed(typed_name(:function, 'moduleb::rb_func_b')).value expect(function.call({})).to eql("I am modulea::rb_func_a() + I am moduleb::rb_func_b()") end end context 'when loading from an environment without modules' do let(:node) { Puppet::Node.new('test', :facts => Puppet::Node::Facts.new('facts', {}), :environment => 'no_modules') } it 'can load the same function twice with two different compilations and produce different values' do Puppet.settings.initialize_global_settings environments = Puppet::Environments::Directories.new(my_fixture_dir, []) Puppet.override(:environments => environments) do compiler = Puppet::Parser::Compiler.new(node) compiler.topscope['value_from_scope'] = 'first' catalog = compiler.compile expect(catalog.resource('Notify[first]')).to be_a(Puppet::Resource) Puppet::Pops::Loader::RubyFunctionInstantiator.expects(:create).never compiler = Puppet::Parser::Compiler.new(node) compiler.topscope['value_from_scope'] = 'second' catalog = compiler.compile expect(catalog.resource('Notify[first]')).to be_nil expect(catalog.resource('Notify[second]')).to be_a(Puppet::Resource) end end end context 'when calling' do let(:env) { environment_for(mix_4x_and_3x_functions) } let(:compiler) { Puppet::Parser::Compiler.new(Puppet::Node.new("test", :environment => env)) } let(:scope) { compiler.topscope } let(:loader) { compiler.loaders.private_loader_for_module('user') } around(:each) do |example| Puppet.override(:current_environment => scope.environment, :global_scope => scope, :loaders => compiler.loaders) do example.run end end it 'a 3x function in dependent module can be called from a 4x function' do function = loader.load_typed(typed_name(:function, 'user::caller')).value expect(function.call(scope)).to eql("usee::callee() got 'first' - usee::callee() got 'second'") end it 'a 3x function in dependent module can be called from a puppet function' do function = loader.load_typed(typed_name(:function, 'user::puppetcaller')).value expect(function.call(scope)).to eql("usee::callee() got 'first' - usee::callee() got 'second'") end it 'a 4x function can be called from a puppet function' do function = loader.load_typed(typed_name(:function, 'user::puppetcaller4')).value expect(function.call(scope)).to eql("usee::callee() got 'first' - usee::callee() got 'second'") end it 'a puppet function can be called from a 4x function' do function = loader.load_typed(typed_name(:function, 'user::callingpuppet')).value expect(function.call(scope)).to eql("Did you call to say you love me?") end it 'a 3x function can be called with caller scope propagated from a 4x function' do function = loader.load_typed(typed_name(:function, 'user::caller_ws')).value expect(function.call(scope, 'passed in scope')).to eql("usee::callee_ws() got 'passed in scope'") end end context 'loading' do let(:env_name) { 'testenv' } let(:environments_dir) { Puppet[:environmentpath] } let(:env_dir) { File.join(environments_dir, env_name) } let(:env) { Puppet::Node::Environment.create(env_name.to_sym, [File.join(populated_env_dir, 'modules')]) } let(:node) { Puppet::Node.new("test", :environment => env) } let(:env_dir_files) {} let(:populated_env_dir) do dir_contained_in(environments_dir, env_name => env_dir_files) PuppetSpec::Files.record_tmp(env_dir) env_dir end context 'non autoloaded types and functions' do let(:env_dir_files) { { 'modules' => { 'tstf' => { 'manifests' => { 'init.pp' => <<-PUPPET.unindent class tstf { notice(testfunc()) } PUPPET } }, 'tstt' => { 'manifests' => { 'init.pp' => <<-PUPPET.unindent class tstt { notice(assert_type(GlobalType, 23)) } PUPPET } } } } } it 'finds the function from a module' do expect(eval_and_collect_notices(<<-PUPPET.unindent, node)).to eq(['hello from testfunc']) function testfunc() { 'hello from testfunc' } include 'tstf' PUPPET end it 'finds the type from a module' do expect(eval_and_collect_notices(<<-PUPPET.unindent, node)).to eq(['23']) type GlobalType = Integer include 'tstt' PUPPET end end context 'types' do let(:env_name) { 'testenv' } let(:environments_dir) { Puppet[:environmentpath] } let(:env_dir) { File.join(environments_dir, env_name) } let(:env) { Puppet::Node::Environment.create(env_name.to_sym, [File.join(populated_env_dir, 'modules')]) } let(:metadata_json) { <<-JSON { "name": "example/%1$s", "version": "0.0.2", "source": "git@github.com/example/example-%1$s.git", "dependencies": [], "author": "Bob the Builder", "license": "Apache-2.0"%2$s } JSON } let(:env_dir_files) do { 'types' => { 'c.pp' => 'type C = Integer' }, 'modules' => { 'a' => { 'manifests' => { 'init.pp' => 'class a { notice(A::A) }' }, 'types' => { 'a.pp' => 'type A::A = Variant[B::B, String]', 'n.pp' => 'type A::N = C::C' }, 'metadata.json' => sprintf(metadata_json, 'a', ', "dependencies": [{ "name": "example/b" }]') }, 'b' => { 'types' => { 'b.pp' => 'type B::B = Variant[C::C, Float]', 'x.pp' => 'type B::X = A::A' }, 'metadata.json' => sprintf(metadata_json, 'b', ', "dependencies": [{ "name": "example/c" }]') }, 'c' => { 'types' => { 'init_typeset.pp' => <<-PUPPET.unindent, type C = TypeSet[{ pcore_version => '1.0.0', types => { C => Integer, D => Float } }] PUPPET 'd.pp' => <<-PUPPET.unindent, type C::D = TypeSet[{ pcore_version => '1.0.0', types => { X => String, Y => Float } }] PUPPET 'd' => { 'y.pp' => 'type C::D::Y = Integer' } }, 'metadata.json' => sprintf(metadata_json, 'c', '') }, 'd' => { 'types' => { 'init_typeset.pp' => <<-PUPPET.unindent, type D = TypeSet[{ pcore_version => '1.0.0', types => { P => Object[{}], O => Object[{ parent => P }] } }] PUPPET }, 'metadata.json' => sprintf(metadata_json, 'd', '') } } } end before(:each) do Puppet.push_context(:loaders => Puppet::Pops::Loaders.new(env)) end after(:each) do Puppet.pop_context end it 'resolves types using the loader that loaded the type a -> b -> c' do type = Puppet::Pops::Types::TypeParser.singleton.parse('A::A', Puppet::Pops::Loaders.find_loader('a')) expect(type).to be_a(Puppet::Pops::Types::PTypeAliasType) expect(type.name).to eql('A::A') type = type.resolved_type expect(type).to be_a(Puppet::Pops::Types::PVariantType) type = type.types[0] expect(type.name).to eql('B::B') type = type.resolved_type expect(type).to be_a(Puppet::Pops::Types::PVariantType) type = type.types[0] expect(type.name).to eql('C::C') type = type.resolved_type expect(type).to be_a(Puppet::Pops::Types::PIntegerType) end it 'will not resolve implicit transitive dependencies, a -> c' do type = Puppet::Pops::Types::TypeParser.singleton.parse('A::N', Puppet::Pops::Loaders.find_loader('a')) expect(type).to be_a(Puppet::Pops::Types::PTypeAliasType) expect(type.name).to eql('A::N') type = type.resolved_type expect(type).to be_a(Puppet::Pops::Types::PTypeReferenceType) expect(type.type_string).to eql('C::C') end it 'will not resolve reverse dependencies, b -> a' do type = Puppet::Pops::Types::TypeParser.singleton.parse('B::X', Puppet::Pops::Loaders.find_loader('b')) expect(type).to be_a(Puppet::Pops::Types::PTypeAliasType) expect(type.name).to eql('B::X') type = type.resolved_type expect(type).to be_a(Puppet::Pops::Types::PTypeReferenceType) expect(type.type_string).to eql('A::A') end it 'does not resolve init_typeset when more qualified type is found in typeset' do type = Puppet::Pops::Types::TypeParser.singleton.parse('C::D::X', Puppet::Pops::Loaders.find_loader('c')) expect(type).to be_a(Puppet::Pops::Types::PTypeAliasType) expect(type.resolved_type).to be_a(Puppet::Pops::Types::PStringType) end it 'defined TypeSet type shadows type defined inside of TypeSet' do type = Puppet::Pops::Types::TypeParser.singleton.parse('C::D', Puppet::Pops::Loaders.find_loader('c')) expect(type).to be_a(Puppet::Pops::Types::PTypeSetType) end it 'parent name search does not traverse parent loaders' do type = Puppet::Pops::Types::TypeParser.singleton.parse('C::C', Puppet::Pops::Loaders.find_loader('c')) expect(type).to be_a(Puppet::Pops::Types::PTypeAliasType) expect(type.resolved_type).to be_a(Puppet::Pops::Types::PIntegerType) end it 'global type defined in environment trumps modules init_typeset type' do type = Puppet::Pops::Types::TypeParser.singleton.parse('C', Puppet::Pops::Loaders.find_loader('c')) expect(type).to be_a(Puppet::Pops::Types::PTypeAliasType) expect(type.resolved_type).to be_a(Puppet::Pops::Types::PIntegerType) end it 'hit on qualified name trumps hit on typeset using parent name + traversal' do type = Puppet::Pops::Types::TypeParser.singleton.parse('C::D::Y', Puppet::Pops::Loaders.find_loader('c')) expect(type).to be_a(Puppet::Pops::Types::PTypeAliasType) expect(type.resolved_type).to be_a(Puppet::Pops::Types::PIntegerType) end it 'hit on qualified name and subsequent hit in typeset when searching for other name causes collision' do l = Puppet::Pops::Loaders.find_loader('c') p = Puppet::Pops::Types::TypeParser.singleton p.parse('C::D::Y', l) expect { p.parse('C::D::X', l) }.to raise_error(/Attempt to redefine entity 'http:\/\/puppet.com\/2016.1\/runtime\/type\/c::d::y'/) end it 'hit in typeset using parent name and subsequent search that would cause hit on fqn does not cause collision (fqn already loaded from typeset)' do l = Puppet::Pops::Loaders.find_loader('c') p = Puppet::Pops::Types::TypeParser.singleton p.parse('C::D::X', l) type = p.parse('C::D::Y', l) expect(type).to be_a(Puppet::Pops::Types::PTypeAliasType) expect(type.resolved_type).to be_a(Puppet::Pops::Types::PFloatType) end it 'loads an object type from a typeset that references another type defined in the same typeset' do l = Puppet::Pops::Loaders.find_loader('d').private_loader p = Puppet::Pops::Types::TypeParser.singleton type = p.parse('D::O', l) expect(type).to be_a(Puppet::Pops::Types::PObjectType) expect(type.resolved_parent).to be_a(Puppet::Pops::Types::PObjectType) end end end def environment_for(*module_paths) Puppet::Node::Environment.create(:'*test*', module_paths) end def typed_name(type, name) Puppet::Pops::Loader::TypedName.new(type, name) end def config_dir(config_name) my_fixture(config_name) end end puppet-5.5.10/spec/unit/pops/loaders/module_loaders_spec.rb0000644005276200011600000001555013417161722023701 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/files' require 'puppet/pops' require 'puppet/loaders' require 'puppet_spec/compiler' describe 'FileBased module loader' do include PuppetSpec::Files let(:static_loader) { Puppet::Pops::Loader::StaticLoader.new() } let(:loaders) { Puppet::Pops::Loaders.new(Puppet::Node::Environment.create(:testing, [])) } it 'can load a 4x function API ruby function in global name space' do module_dir = dir_containing('testmodule', { 'lib' => { 'puppet' => { 'functions' => { 'foo4x.rb' => <<-CODE Puppet::Functions.create_function(:foo4x) do def foo4x() 'yay' end end CODE } } } }) module_loader = Puppet::Pops::Loader::ModuleLoaders.module_loader_from(static_loader, loaders, 'testmodule', module_dir) function = module_loader.load_typed(typed_name(:function, 'foo4x')).value expect(function.class.name).to eq('foo4x') expect(function.is_a?(Puppet::Functions::Function)).to eq(true) end it 'can load a 4x function API ruby function in qualified name space' do module_dir = dir_containing('testmodule', { 'lib' => { 'puppet' => { 'functions' => { 'testmodule' => { 'foo4x.rb' => <<-CODE Puppet::Functions.create_function('testmodule::foo4x') do def foo4x() 'yay' end end CODE } } } }}) module_loader = Puppet::Pops::Loader::ModuleLoaders.module_loader_from(static_loader, loaders, 'testmodule', module_dir) function = module_loader.load_typed(typed_name(:function, 'testmodule::foo4x')).value expect(function.class.name).to eq('testmodule::foo4x') expect(function.is_a?(Puppet::Functions::Function)).to eq(true) end it 'system loader has itself as private loader' do module_loader = loaders.puppet_system_loader expect(module_loader.private_loader).to be(module_loader) end it 'makes parent loader win over entries in child' do module_dir = dir_containing('testmodule', { 'lib' => { 'puppet' => { 'functions' => { 'testmodule' => { 'foo.rb' => <<-CODE Puppet::Functions.create_function('testmodule::foo') do def foo() 'yay' end end CODE }}}}}) module_loader = Puppet::Pops::Loader::ModuleLoaders.module_loader_from(static_loader, loaders, 'testmodule', module_dir) module_dir2 = dir_containing('testmodule2', { 'lib' => { 'puppet' => { 'functions' => { 'testmodule2' => { 'foo.rb' => <<-CODE raise "should not get here" CODE }}}}}) module_loader2 = Puppet::Pops::Loader::ModuleLoaders::FileBased.new(module_loader, loaders, 'testmodule2', module_dir2, 'test2') function = module_loader2.load_typed(typed_name(:function, 'testmodule::foo')).value expect(function.class.name).to eq('testmodule::foo') expect(function.is_a?(Puppet::Functions::Function)).to eq(true) end context 'loading tasks' do before(:each) do Puppet[:tasks] = true Puppet.push_context(:loaders => loaders) end after(:each) { Puppet.pop_context } it 'can load tasks with multiple files' do module_dir = dir_containing('testmodule', 'tasks' => {'foo.py' => '', 'foo.json' => '{}'}) module_loader = Puppet::Pops::Loader::ModuleLoaders.module_loader_from(static_loader, loaders, 'testmodule', module_dir) task = module_loader.load_typed(typed_name(:task, 'testmodule::foo')).value expect(task.name).to eq('testmodule::foo') expect(File.basename(task.executable)).to eq('foo.py') end it 'can load multiple tasks with multiple files' do module_dir = dir_containing('testmodule', 'tasks' => {'foo.py' => '', 'foo.json' => '{}', 'foobar.py' => '', 'foobar.json' => '{}'}) module_loader = Puppet::Pops::Loader::ModuleLoaders.module_loader_from(static_loader, loaders, 'testmodule', module_dir) foo_task = module_loader.load_typed(typed_name(:task, 'testmodule::foo')).value foobar_task = module_loader.load_typed(typed_name(:task, 'testmodule::foobar')).value expect(foo_task.name).to eq('testmodule::foo') expect(File.basename(foo_task.executable)).to eq('foo.py') expect(foobar_task.name).to eq('testmodule::foobar') expect(File.basename(foobar_task.executable)).to eq('foobar.py') end it "won't load tasks with invalid names" do module_dir = dir_containing('testmodule', 'tasks' => {'a-b.py' => '', 'foo.tar.gz' => ''}) module_loader = Puppet::Pops::Loader::ModuleLoaders.module_loader_from(static_loader, loaders, 'testmodule', module_dir) tasks = module_loader.discover(:task) expect(tasks).to be_empty expect(module_loader.load_typed(typed_name(:task, 'testmodule::foo'))).to be_nil end end def typed_name(type, name) Puppet::Pops::Loader::TypedName.new(type, name) end context 'module function and class using a module type alias' do include PuppetSpec::Compiler let(:modules) do { 'mod' => { 'functions' => { 'afunc.pp' => <<-PUPPET.unindent function mod::afunc(Mod::Analias $v) { notice($v) } PUPPET }, 'types' => { 'analias.pp' => <<-PUPPET.unindent type Mod::Analias = Enum[a,b] PUPPET }, 'manifests' => { 'init.pp' => <<-PUPPET.unindent class mod(Mod::Analias $v) { notify { $v: } } PUPPET } } } end let(:testing_env) do { 'testing' => { 'modules' => modules } } end let(:environments_dir) { Puppet[:environmentpath] } let(:testing_env_dir) do dir_contained_in(environments_dir, testing_env) env_dir = File.join(environments_dir, 'testing') PuppetSpec::Files.record_tmp(env_dir) env_dir end let(:env) { Puppet::Node::Environment.create(:testing, [File.join(testing_env_dir, 'modules')]) } let(:node) { Puppet::Node.new('test', :environment => env) } # The call to mod:afunc will load the function, and as a consequence, make an attempt to load # the parameter type Mod::Analias. That load in turn, will trigger the Runtime3TypeLoader which # will load the manifests in Mod. The init.pp manifest also references the Mod::Analias parameter # which results in a recursive call to the same loader. This test asserts that this recursive # call is handled OK. # See PUP-7391 for more info. it 'should handle a recursive load' do expect(eval_and_collect_notices("mod::afunc('b')", node)).to eql(['b']) end end end puppet-5.5.10/spec/unit/pops/loaders/static_loader_spec.rb0000644005276200011600000000534713417161722023523 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' require 'puppet/loaders' describe 'the static loader' do let(:loader) do loader = Puppet::Pops::Loader::StaticLoader.new() loader.runtime_3_init loader end it 'has no parent' do expect(loader.parent).to be(nil) end it 'identifies itself in string form' do expect(loader.to_s).to be_eql('(StaticLoader)') end it 'support the Loader API' do # it may produce things later, this is just to test that calls work as they should - now all lookups are nil. a_typed_name = typed_name(:function, 'foo') expect(loader[a_typed_name]).to be(nil) expect(loader.load_typed(a_typed_name)).to be(nil) expect(loader.find(a_typed_name)).to be(nil) end context 'provides access to resource types built into puppet' do %w{ Augeas Component Computer Cron Exec File Filebucket Group Host Interface K5login Macauthorization Mailalias Maillist Mcx Mount Nagios_command Nagios_contact Nagios_contactgroup Nagios_host Nagios_hostdependency Nagios_hostescalation Nagios_hostescalation Nagios_hostgroup Nagios_service Nagios_servicedependency Nagios_serviceescalation Nagios_serviceextinfo Nagios_servicegroup Nagios_timeperiod Notify Package Resources Router Schedule Scheduled_task Selboolean Selmodule Service Ssh_authorized_key Sshkey Stage Tidy User Vlan Whit Yumrepo Zfs Zone Zpool }.each do |name | it "such that #{name} is available" do expect(loader.load(:type, name.downcase)).to be_the_type(resource_type(name)) end end end context 'provides access to app-management specific resource types built into puppet' do it "such that Node is available" do expect(loader.load(:type, 'node')).to be_the_type(resource_type('Node')) end end context 'without init_runtime3 initialization' do let(:loader) { Puppet::Pops::Loader::StaticLoader.new() } it 'does not provide access to resource types built into puppet' do expect(loader.load(:type, 'file')).to be_nil end end def typed_name(type, name) Puppet::Pops::Loader::TypedName.new(type, name) end def resource_type(name) Puppet::Pops::Types::TypeFactory.resource(name) end matcher :be_the_type do |type| calc = Puppet::Pops::Types::TypeCalculator.new match do |actual| calc.assignable?(actual, type) && calc.assignable?(type, actual) end failure_message do |actual| "expected #{type.to_s}, but was #{actual.to_s}" end end end puppet-5.5.10/spec/unit/pops/lookup/0000755005276200011600000000000013417162176017222 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/pops/lookup/context_spec.rb0000755005276200011600000003022713417161721022247 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/files' require 'puppet_spec/compiler' module Puppet::Pops module Lookup describe 'Puppet::Pops::Lookup::Context' do context 'an instance' do include PuppetSpec::Compiler it 'can be created' do code = "notice(type(Puppet::LookupContext.new('m')))" expect(eval_and_collect_notices(code)[0]).to match(/Object\[\{name => 'Puppet::LookupContext'/) end it 'returns its environment_name' do code = "notice(Puppet::LookupContext.new('m').environment_name)" expect(eval_and_collect_notices(code)[0]).to eql('production') end it 'returns its module_name' do code = "notice(Puppet::LookupContext.new('m').module_name)" expect(eval_and_collect_notices(code)[0]).to eql('m') end it 'can use an undef module_name' do code = "notice(type(Puppet::LookupContext.new(undef).module_name))" expect(eval_and_collect_notices(code)[0]).to eql('Undef') end it 'can store and retrieve a value using the cache' do code = <<-PUPPET.unindent $ctx = Puppet::LookupContext.new('m') $ctx.cache('ze_key', 'ze_value') notice($ctx.cached_value('ze_key')) PUPPET expect(eval_and_collect_notices(code)[0]).to eql('ze_value') end it 'the cache method returns the value that is cached' do code = <<-PUPPET.unindent $ctx = Puppet::LookupContext.new('m') notice($ctx.cache('ze_key', 'ze_value')) PUPPET expect(eval_and_collect_notices(code)[0]).to eql('ze_value') end it 'can store and retrieve a hash using the cache' do code = <<-PUPPET.unindent $ctx = Puppet::LookupContext.new('m') $ctx.cache_all({ 'v1' => 'first', 'v2' => 'second' }) $ctx.cached_entries |$key, $value| { notice($key); notice($value) } PUPPET expect(eval_and_collect_notices(code)).to eql(%w(v1 first v2 second)) end it 'can use the cache to merge hashes' do code = <<-PUPPET.unindent $ctx = Puppet::LookupContext.new('m') $ctx.cache_all({ 'v1' => 'first', 'v2' => 'second' }) $ctx.cache_all({ 'v3' => 'third', 'v4' => 'fourth' }) $ctx.cached_entries |$key, $value| { notice($key); notice($value) } PUPPET expect(eval_and_collect_notices(code)).to eql(%w(v1 first v2 second v3 third v4 fourth)) end it 'can use the cache to merge hashes and individual entries' do code = <<-PUPPET.unindent $ctx = Puppet::LookupContext.new('m') $ctx.cache_all({ 'v1' => 'first', 'v2' => 'second' }) $ctx.cache('v3', 'third') $ctx.cached_entries |$key, $value| { notice($key); notice($value) } PUPPET expect(eval_and_collect_notices(code)).to eql(%w(v1 first v2 second v3 third)) end it 'can iterate the cache using one argument block' do code = <<-PUPPET.unindent $ctx = Puppet::LookupContext.new('m') $ctx.cache_all({ 'v1' => 'first', 'v2' => 'second' }) $ctx.cached_entries |$entry| { notice($entry[0]); notice($entry[1]) } PUPPET expect(eval_and_collect_notices(code)).to eql(%w(v1 first v2 second)) end it 'can replace individual cached entries' do code = <<-PUPPET.unindent $ctx = Puppet::LookupContext.new('m') $ctx.cache_all({ 'v1' => 'first', 'v2' => 'second' }) $ctx.cache('v2', 'changed') $ctx.cached_entries |$key, $value| { notice($key); notice($value) } PUPPET expect(eval_and_collect_notices(code)).to eql(%w(v1 first v2 changed)) end it 'can replace multiple cached entries' do code = <<-PUPPET.unindent $ctx = Puppet::LookupContext.new('m') $ctx.cache_all({ 'v1' => 'first', 'v2' => 'second', 'v3' => 'third' }) $ctx.cache_all({ 'v1' => 'one', 'v3' => 'three' }) $ctx.cached_entries |$key, $value| { notice($key); notice($value) } PUPPET expect(eval_and_collect_notices(code)).to eql(%w(v1 one v2 second v3 three)) end it 'cached_entries returns an Iterable when called without a block' do code = <<-PUPPET.unindent $ctx = Puppet::LookupContext.new('m') $ctx.cache_all({ 'v1' => 'first', 'v2' => 'second' }) $iter = $ctx.cached_entries notice(type($iter, generalized)) $iter.each |$entry| { notice($entry[0]); notice($entry[1]) } PUPPET expect(eval_and_collect_notices(code)).to eql(['Iterator[Tuple[String, String, 2, 2]]', 'v1', 'first', 'v2', 'second']) end it 'will throw :no_such_key when not_found is called' do code = <<-PUPPET.unindent $ctx = Puppet::LookupContext.new('m') $ctx.not_found PUPPET expect { eval_and_collect_notices(code) }.to throw_symbol(:no_such_key) end context 'with cached_file_data' do include PuppetSpec::Files let(:code_dir) { Puppet[:environmentpath] } let(:env_name) { 'testing' } let(:env_dir) { File.join(code_dir, env_name) } let(:env) { Puppet::Node::Environment.create(env_name.to_sym, [File.join(populated_env_dir, 'modules')]) } let(:node) { Puppet::Node.new('test', :environment => env) } let(:data_yaml) { 'data.yaml' } let(:data_path) { File.join(populated_env_dir, 'data', data_yaml) } let(:populated_env_dir) do dir_contained_in(code_dir, { env_name => { 'data' => { data_yaml => <<-YAML.unindent a: value a YAML } } } ) PuppetSpec::Files.record_tmp(File.join(env_dir)) env_dir end it 'can use cached_file_data without a block' do code = <<-PUPPET.unindent $ctx = Puppet::LookupContext.new(nil) $yaml_data = $ctx.cached_file_data('#{data_path}') notice($yaml_data) PUPPET expect(eval_and_collect_notices(code, node)).to eql(["a: value a\n"]) end it 'can use cached_file_data with a block' do code = <<-PUPPET.unindent $ctx = Puppet::LookupContext.new(nil) $yaml_data = $ctx.cached_file_data('#{data_path}') |$content| { { 'parsed' => $content } } notice($yaml_data) PUPPET expect(eval_and_collect_notices(code, node)).to eql(["{parsed => a: value a\n}"]) end context 'and multiple compilations' do before(:each) { Puppet.settings[:environment_timeout] = 'unlimited' } it 'will reuse cached_file_data and not call block again' do code1 = <<-PUPPET.unindent $ctx = Puppet::LookupContext.new(nil) $yaml_data = $ctx.cached_file_data('#{data_path}') |$content| { { 'parsed' => $content } } notice($yaml_data) PUPPET code2 = <<-PUPPET.unindent $ctx = Puppet::LookupContext.new(nil) $yaml_data = $ctx.cached_file_data('#{data_path}') |$content| { { 'parsed' => 'should not be called' } } notice($yaml_data) PUPPET logs = [] Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do Puppet[:code] = code1 Puppet::Parser::Compiler.compile(node) Puppet[:code] = code2 Puppet::Parser::Compiler.compile(node) end logs = logs.select { |log| log.level == :notice }.map { |log| log.message } expect(logs.uniq.size).to eql(1) end it 'will invalidate cache if file changes size' do code = <<-PUPPET.unindent $ctx = Puppet::LookupContext.new(nil) $yaml_data = $ctx.cached_file_data('#{data_path}') |$content| { { 'parsed' => $content } } notice($yaml_data) PUPPET logs = [] Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do Puppet[:code] = code Puppet::Parser::Compiler.compile(node) # Change content size! File.write(data_path, "a: value is now A\n") Puppet::Parser::Compiler.compile(node) end logs = logs.select { |log| log.level == :notice }.map { |log| log.message } expect(logs).to eql(["{parsed => a: value a\n}", "{parsed => a: value is now A\n}"]) end it 'will invalidate cache if file changes mtime' do old_mtime = Puppet::FileSystem.stat(data_path).mtime code = <<-PUPPET.unindent $ctx = Puppet::LookupContext.new(nil) $yaml_data = $ctx.cached_file_data('#{data_path}') |$content| { { 'parsed' => $content } } notice($yaml_data) PUPPET logs = [] Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do Puppet[:code] = code Puppet::Parser::Compiler.compile(node) # Write content with the same size File.write(data_path, "a: value b\n") # Ensure mtime is at least 1 second ahead FileUtils.touch(data_path, :mtime => old_mtime + 1) Puppet::Parser::Compiler.compile(node) end logs = logs.select { |log| log.level == :notice }.map { |log| log.message } expect(logs).to eql(["{parsed => a: value a\n}", "{parsed => a: value b\n}"]) end it 'will invalidate cache if file changes inode' do code = <<-PUPPET.unindent $ctx = Puppet::LookupContext.new(nil) $yaml_data = $ctx.cached_file_data('#{data_path}') |$content| { { 'parsed' => $content } } notice($yaml_data) PUPPET logs = [] Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do Puppet[:code] = code Puppet::Parser::Compiler.compile(node) # Change inode! File.delete(data_path); # Write content with the same size File.write(data_path, "a: value b\n") Puppet::Parser::Compiler.compile(node) end logs = logs.select { |log| log.level == :notice }.map { |log| log.message } expect(logs).to eql(["{parsed => a: value a\n}", "{parsed => a: value b\n}"]) end end end context 'when used in an Invocation' do let(:node) { Puppet::Node.new('test') } let(:compiler) { Puppet::Parser::Compiler.new(node) } let(:invocation) { Invocation.new(compiler.topscope) } let(:invocation_with_explain) { Invocation.new(compiler.topscope, {}, {}, true) } def compile_and_get_notices(code, scope_vars = {}) Puppet[:code] = code scope = compiler.topscope scope_vars.each_pair { |k,v| scope.setvar(k, v) } node.environment.check_for_reparse logs = [] Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do compiler.compile end logs = logs.select { |log| log.level == :notice }.map { |log| log.message } logs end it 'will not call explain unless explanations are active' do invocation.lookup('dummy', nil) do code = <<-PUPPET.unindent $ctx = Puppet::LookupContext.new('m') $ctx.explain || { notice('stop calling'); 'bad' } PUPPET expect(compile_and_get_notices(code)).to be_empty end end it 'will call explain when explanations are active' do invocation_with_explain.lookup('dummy', nil) do code = <<-PUPPET.unindent $ctx = Puppet::LookupContext.new('m') $ctx.explain || { notice('called'); 'good' } PUPPET expect(compile_and_get_notices(code)).to eql(['called']) end expect(invocation_with_explain.explainer.explain).to eql("good\n") end it 'will call interpolate to resolve interpolation' do invocation.lookup('dummy', nil) do code = <<-PUPPET.unindent $ctx = Puppet::LookupContext.new('m') notice($ctx.interpolate('-- %{testing} --')) PUPPET expect(compile_and_get_notices(code, { 'testing' => 'called' })).to eql(['-- called --']) end end end end end end end puppet-5.5.10/spec/unit/pops/lookup/interpolation_spec.rb0000644005276200011600000003716613417161721023460 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet' module Puppet::Pops describe 'Puppet::Pops::Lookup::Interpolation' do include Lookup::SubLookup class InterpolationTestAdapter < Lookup::LookupAdapter include Lookup::SubLookup def initialize(data, interpolator) @data = data @interpolator = interpolator end def track(name) end def lookup(name, lookup_invocation, merge) track(name) segments = split_key(name) root_key = segments.shift found = @data[root_key] found = sub_lookup(name, lookup_invocation, segments, found) unless segments.empty? @interpolator.interpolate(found, lookup_invocation, true) end # ignore requests for lookup options when testing interpolation def lookup_lookup_options(_, _) nil end end let(:interpolator) { Class.new { include Lookup::Interpolation }.new } let(:scope) { {} } let(:data) { {} } let(:adapter) { InterpolationTestAdapter.new(data, interpolator) } let(:lookup_invocation) { Lookup::Invocation.new(scope, {}, {}, nil) } before(:each) do Lookup::Invocation.any_instance.stubs(:lookup_adapter).returns(adapter) end def expect_lookup(*keys) keys.each { |key| adapter.expects(:track).with(key) } end context 'when interpolating nested data' do let(:nested_hash) { {'a' => {'aa' => "%{alias('aaa')}"}} } let(:scope) { { 'ds1' => 'a', 'ds2' => 'b' } } let(:data) { { 'aaa' => {'b' => {'bb' => "%{alias('bbb')}"}}, 'bbb' => ["%{alias('ccc')}"], 'ccc' => 'text', 'ddd' => "%{literal('%')}{ds1}_%{literal('%')}{ds2}", } } it 'produces a nested hash with arrays from nested aliases with hashes and arrays' do expect_lookup('aaa', 'bbb', 'ccc') expect(interpolator.interpolate(nested_hash, lookup_invocation, true)).to eq('a' => {'aa' => {'b' => {'bb' => ['text']}}}) end it "'%{lookup('key')} will interpolate the returned value'" do expect_lookup('ddd') expect(interpolator.interpolate("%{lookup('ddd')}", lookup_invocation, true)).to eq('a_b') end it "'%{alias('key')} will not interpolate the returned value'" do expect_lookup('ddd') expect(interpolator.interpolate("%{alias('ddd')}", lookup_invocation, true)).to eq('%{ds1}_%{ds2}') end end context 'when interpolating boolean scope values' do let(:scope) { { 'yes' => true, 'no' => false } } it 'produces the string true' do expect(interpolator.interpolate('should yield %{yes}', lookup_invocation, true)).to eq('should yield true') end it 'produces the string false' do expect(interpolator.interpolate('should yield %{no}', lookup_invocation, true)).to eq('should yield false') end end context 'when there are empty interpolations %{} in data' do let(:empty_interpolation) { 'clown%{}shoe' } let(:empty_interpolation_as_escape) { 'clown%%{}{shoe}s' } let(:only_empty_interpolation) { '%{}' } let(:empty_namespace) { '%{::}' } let(:whitespace1) { '%{ :: }' } let(:whitespace2) { '%{ }' } it 'should produce an empty string for the interpolation' do expect(interpolator.interpolate(empty_interpolation, lookup_invocation, true)).to eq('clownshoe') end it 'the empty interpolation can be used as an escape mechanism' do expect(interpolator.interpolate(empty_interpolation_as_escape, lookup_invocation, true)).to eq('clown%{shoe}s') end it 'the value can consist of only an empty escape' do expect(interpolator.interpolate(only_empty_interpolation, lookup_invocation, true)).to eq('') end it 'the value can consist of an empty namespace %{::}' do expect(interpolator.interpolate(empty_namespace, lookup_invocation, true)).to eq('') end it 'the value can consist of whitespace %{ :: }' do expect(interpolator.interpolate(whitespace1, lookup_invocation, true)).to eq('') end it 'the value can consist of whitespace %{ }' do expect(interpolator.interpolate(whitespace2, lookup_invocation, true)).to eq('') end end context 'when there are quoted empty interpolations %{} in data' do let(:empty_interpolation) { 'clown%{""}shoe' } let(:empty_interpolation_as_escape) { 'clown%%{""}{shoe}s' } let(:only_empty_interpolation) { '%{""}' } let(:empty_namespace) { '%{"::"}' } let(:whitespace1) { '%{ "::" }' } let(:whitespace2) { '%{ "" }' } it 'should produce an empty string for the interpolation' do expect(interpolator.interpolate(empty_interpolation, lookup_invocation, true)).to eq('clownshoe') end it 'the empty interpolation can be used as an escape mechanism' do expect(interpolator.interpolate(empty_interpolation_as_escape, lookup_invocation, true)).to eq('clown%{shoe}s') end it 'the value can consist of only an empty escape' do expect(interpolator.interpolate(only_empty_interpolation, lookup_invocation, true)).to eq('') end it 'the value can consist of an empty namespace %{"::"}' do expect(interpolator.interpolate(empty_namespace, lookup_invocation, true)).to eq('') end it 'the value can consist of whitespace %{ "::" }' do expect(interpolator.interpolate(whitespace1, lookup_invocation, true)).to eq('') end it 'the value can consist of whitespace %{ "" }' do expect(interpolator.interpolate(whitespace2, lookup_invocation, true)).to eq('') end end context 'when using dotted keys' do let(:data) { { 'a.b' => '(lookup) a dot b', 'a' => { 'd' => '(lookup) a dot d is a hash entry', 'd.x' => '(lookup) a dot d.x is a hash entry', 'd.z' => { 'g' => '(lookup) a dot d.z dot g is a hash entry'} }, 'a.x' => { 'd' => '(lookup) a.x dot d is a hash entry', 'd.x' => '(lookup) a.x dot d.x is a hash entry', 'd.z' => { 'g' => '(lookup) a.x dot d.z dot g is a hash entry' } }, 'x.1' => '(lookup) x dot 1', 'key' => 'subkey' } } let(:scope) { { 'a.b' => '(scope) a dot b', 'a' => { 'd' => '(scope) a dot d is a hash entry', 'd.x' => '(scope) a dot d.x is a hash entry', 'd.z' => { 'g' => '(scope) a dot d.z dot g is a hash entry'} }, 'a.x' => { 'd' => '(scope) a.x dot d is a hash entry', 'd.x' => '(scope) a.x dot d.x is a hash entry', 'd.z' => { 'g' => '(scope) a.x dot d.z dot g is a hash entry' } }, 'x.1' => '(scope) x dot 1', } } it 'should find an entry using a quoted interpolation' do expect(interpolator.interpolate("a dot c: %{'a.b'}", lookup_invocation, true)).to eq('a dot c: (scope) a dot b') end it 'should find an entry using a quoted interpolation with method lookup' do expect_lookup("'a.b'") expect(interpolator.interpolate("a dot c: %{lookup(\"'a.b'\")}", lookup_invocation, true)).to eq('a dot c: (lookup) a dot b') end it 'should find an entry using a quoted interpolation with method alias' do expect_lookup("'a.b'") expect(interpolator.interpolate("%{alias(\"'a.b'\")}", lookup_invocation, true)).to eq('(lookup) a dot b') end it 'should use a dotted key to navigate into a structure when it is not quoted' do expect(interpolator.interpolate('a dot e: %{a.d}', lookup_invocation, true)).to eq('a dot e: (scope) a dot d is a hash entry') end it 'should report a key missing and replace with empty string when a dotted key is used to navigate into a structure and then not found' do expect(interpolator.interpolate('a dot n: %{a.n}', lookup_invocation, true)).to eq('a dot n: ') end it 'should use a dotted key to navigate into a structure when it is not quoted with method lookup' do expect_lookup('a.d') expect(interpolator.interpolate("a dot e: %{lookup('a.d')}", lookup_invocation, true)).to eq('a dot e: (lookup) a dot d is a hash entry') end it 'should use a mix of quoted and dotted keys to navigate into a structure containing dotted keys and quoted key is last' do expect(interpolator.interpolate("a dot ex: %{a.'d.x'}", lookup_invocation, true)).to eq('a dot ex: (scope) a dot d.x is a hash entry') end it 'should use a mix of quoted and dotted keys to navigate into a structure containing dotted keys and quoted key is last and method is lookup' do expect_lookup("a.'d.x'") expect(interpolator.interpolate("a dot ex: %{lookup(\"a.'d.x'\")}", lookup_invocation, true)).to eq('a dot ex: (lookup) a dot d.x is a hash entry') end it 'should use a mix of quoted and dotted keys to navigate into a structure containing dotted keys and quoted key is first' do expect(interpolator.interpolate("a dot xe: %{'a.x'.d}", lookup_invocation, true)).to eq('a dot xe: (scope) a.x dot d is a hash entry') end it 'should use a mix of quoted and dotted keys to navigate into a structure containing dotted keys and quoted key is first and method is lookup' do expect_lookup("'a.x'.d") expect(interpolator.interpolate("a dot xe: %{lookup(\"'a.x'.d\")}", lookup_invocation, true)).to eq('a dot xe: (lookup) a.x dot d is a hash entry') end it 'should use a mix of quoted and dotted keys to navigate into a structure containing dotted keys and quoted key is in the middle' do expect(interpolator.interpolate("a dot xm: %{a.'d.z'.g}", lookup_invocation, true)).to eq('a dot xm: (scope) a dot d.z dot g is a hash entry') end it 'should use a mix of quoted and dotted keys to navigate into a structure containing dotted keys and quoted key is in the middle and method is lookup' do expect_lookup("a.'d.z'.g") expect(interpolator.interpolate("a dot xm: %{lookup(\"a.'d.z'.g\")}", lookup_invocation, true)).to eq('a dot xm: (lookup) a dot d.z dot g is a hash entry') end it 'should use a mix of several quoted and dotted keys to navigate into a structure containing dotted keys and quoted key is in the middle' do expect(interpolator.interpolate("a dot xx: %{'a.x'.'d.z'.g}", lookup_invocation, true)).to eq('a dot xx: (scope) a.x dot d.z dot g is a hash entry') end it 'should use a mix of several quoted and dotted keys to navigate into a structure containing dotted keys and quoted key is in the middle and method is lookup' do expect_lookup("'a.x'.'d.z'.g") expect(interpolator.interpolate("a dot xx: %{lookup(\"'a.x'.'d.z'.g\")}", lookup_invocation, true)).to eq('a dot xx: (lookup) a.x dot d.z dot g is a hash entry') end it 'should find an entry using using a quoted interpolation on dotted key containing numbers' do expect(interpolator.interpolate("x dot 2: %{'x.1'}", lookup_invocation, true)).to eq('x dot 2: (scope) x dot 1') end it 'should find an entry using using a quoted interpolation on dotted key containing numbers using method lookup' do expect_lookup("'x.1'") expect(interpolator.interpolate("x dot 2: %{lookup(\"'x.1'\")}", lookup_invocation, true)).to eq('x dot 2: (lookup) x dot 1') end it 'should not find a subkey when the dotted key is quoted' do expect(interpolator.interpolate("a dot f: %{'a.d'}", lookup_invocation, true)).to eq('a dot f: ') end it 'should not find a subkey when the dotted key is quoted with method lookup' do expect_lookup("'a.d'") expect(interpolator.interpolate("a dot f: %{lookup(\"'a.d'\")}", lookup_invocation, true)).to eq('a dot f: ') end it 'should not find a subkey that is matched within a string' do expect{ interpolator.interpolate("%{lookup('key.subkey')}", lookup_invocation, true) }.to raise_error( /Got String when a hash-like object was expected to access value using 'subkey' from key 'key.subkey'/) end end context 'when dealing with non alphanumeric characters' do let(:data) { { 'a key with whitespace' => 'value for a ws key', 'ws_key' => '%{alias("a key with whitespace")}', '\#@!&%|' => 'not happy', 'angry' => '%{alias("\#@!&%|")}', '!$\%!' => { '\#@!&%|' => 'not happy at all' }, 'very_angry' => '%{alias("!$\%!.\#@!&%|")}', 'a key with' => { 'nested whitespace' => 'value for nested ws key', ' untrimmed whitespace ' => 'value for untrimmed ws key' } } } it 'allows keys with white space' do expect_lookup('ws_key', 'a key with whitespace') expect(interpolator.interpolate("%{lookup('ws_key')}", lookup_invocation, true)).to eq('value for a ws key') end it 'allows keys with non alphanumeric characters' do expect_lookup('angry', '\#@!&%|') expect(interpolator.interpolate("%{lookup('angry')}", lookup_invocation, true)).to eq('not happy') end it 'allows dotted keys with non alphanumeric characters' do expect_lookup('very_angry', '!$\%!.\#@!&%|') expect(interpolator.interpolate("%{lookup('very_angry')}", lookup_invocation, true)).to eq('not happy at all') end it 'allows dotted keys with nested white space' do expect_lookup('a key with.nested whitespace') expect(interpolator.interpolate("%{lookup('a key with.nested whitespace')}", lookup_invocation, true)).to eq('value for nested ws key') end it 'will trim each key element' do expect_lookup(' a key with . nested whitespace ') expect(interpolator.interpolate("%{lookup(' a key with . nested whitespace ')}", lookup_invocation, true)).to eq('value for nested ws key') end it 'will not trim quoted key element' do expect_lookup(' a key with ." untrimmed whitespace "') expect(interpolator.interpolate("%{lookup(' a key with .\" untrimmed whitespace \"')}", lookup_invocation, true)).to eq('value for untrimmed ws key') end it 'will not trim spaces outside of quoted key element' do expect_lookup(' a key with . " untrimmed whitespace " ') expect(interpolator.interpolate("%{lookup(' a key with . \" untrimmed whitespace \" ')}", lookup_invocation, true)).to eq('value for untrimmed ws key') end end context 'when dealing with bad keys' do it 'should produce an error when different quotes are used on either side' do expect { interpolator.interpolate("%{'the.key\"}", lookup_invocation, true)}.to raise_error("Syntax error in string: %{'the.key\"}") end it 'should produce an if there is only one quote' do expect { interpolator.interpolate("%{the.'key}", lookup_invocation, true)}.to raise_error("Syntax error in string: %{the.'key}") end it 'should produce an error for an empty segment' do expect { interpolator.interpolate('%{the..key}', lookup_invocation, true)}.to raise_error("Syntax error in string: %{the..key}") end it 'should produce an error for an empty quoted segment' do expect { interpolator.interpolate("%{the.''.key}", lookup_invocation, true)}.to raise_error("Syntax error in string: %{the.''.key}") end it 'should produce an error for an partly quoted segment' do expect { interpolator.interpolate("%{the.'pa'key}", lookup_invocation, true)}.to raise_error("Syntax error in string: %{the.'pa'key}") end it 'should produce an error when different quotes are used on either side in a method argument' do expect { interpolator.interpolate("%{lookup('the.key\")}", lookup_invocation, true)}.to raise_error("Syntax error in string: %{lookup('the.key\")}") end it 'should produce an error unless a known interpolation method is used' do expect { interpolator.interpolate("%{flubber(\"hello\")}", lookup_invocation, true)}.to raise_error("Unknown interpolation method 'flubber'") end end end end puppet-5.5.10/spec/unit/pops/lookup/lookup_spec.rb0000644005276200011600000002614413417161721022074 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/files' require 'puppet/pops' require 'deep_merge/core' module Puppet::Pops module Lookup describe 'The lookup API' do include PuppetSpec::Files let(:env_name) { 'spec' } let(:code_dir) { Puppet[:environmentpath] } let(:env_dir) { File.join(code_dir, env_name) } let(:env) { Puppet::Node::Environment.create(env_name.to_sym, [File.join(populated_env_dir, 'modules')]) } let(:node) { Puppet::Node.new('test', :environment => env) } let(:compiler) { Puppet::Parser::Compiler.new(node) } let(:scope) { compiler.topscope } let(:invocation) { Invocation.new(scope) } let(:code_dir_content) do { 'hiera.yaml' => <<-YAML.unindent, version: 5 YAML 'data' => { 'common.yaml' => <<-YAML.unindent a: a (from global) d: d (from global) mod::e: mod::e (from global) YAML } } end let(:env_content) do { 'hiera.yaml' => <<-YAML.unindent, version: 5 YAML 'data' => { 'common.yaml' => <<-YAML.unindent b: b (from environment) d: d (from environment) mod::f: mod::f (from environment) YAML } } end let(:mod_content) do { 'hiera.yaml' => <<-YAML.unindent, version: 5 YAML 'data' => { 'common.yaml' => <<-YAML.unindent mod::c: mod::c (from module) mod::e: mod::e (from module) mod::f: mod::f (from module) mod::g: :symbol: symbol key value key: string key value 6: integer key value -4: negative integer key value 2.7: float key value '6': string integer key value YAML } } end let(:populated_env_dir) do all_content = code_dir_content.merge(env_name => env_content.merge('modules' => { 'mod' => mod_content })) dir_contained_in(code_dir, all_content) all_content.keys.each { |key| PuppetSpec::Files.record_tmp(File.join(code_dir, key)) } env_dir end before(:each) do Puppet[:hiera_config] = File.join(code_dir, 'hiera.yaml') Puppet.push_context(:loaders => Puppet::Pops::Loaders.new(env)) end after(:each) do Puppet.pop_context end context 'when doing automatic parameter lookup' do let(:mod_content) do { 'hiera.yaml' => <<-YAML.unindent, version: 5 YAML 'data' => { 'common.yaml' => <<-YAML.unindent mod::x: mod::x (from module) YAML }, 'manifests' => { 'init.pp' => <<-PUPPET.unindent class mod($x) { notify { $x: } } PUPPET } } end let(:logs) { [] } let(:debugs) { logs.select { |log| log.level == :debug }.map { |log| log.message } } it 'includes APL in explain output when debug is enabled' do Puppet[:log_level] = 'debug' Puppet[:code] = 'include mod' Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do compiler.compile end expect(debugs).to include(/Found key: "mod::x" value: "mod::x \(from module\)"/) end end context 'when hiera YAML data is corrupt' do let(:mod_content) do { 'hiera.yaml' => 'version: 5', 'data' => { 'common.yaml' => <<-YAML.unindent --- #mod::classes: - cls1 - cls2 mod::somevar: 1 YAML }, } end let(:msg) { /file does not contain a valid yaml hash/ } %w(off warning).each do |strict| it "logs a warning when --strict is '#{strict}'" do Puppet[:strict] = strict logs = [] Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do expect(Lookup.lookup('mod::somevar', nil, nil, true, nil, invocation)).to be_nil end expect(logs.map(&:message)).to contain_exactly(msg) end end it 'fails when --strict is "error"' do Puppet[:strict] = 'error' expect { Lookup.lookup('mod::somevar', nil, nil, true, nil, invocation) }.to raise_error(msg) end end context 'when hiera YAML data is empty' do let(:mod_content) do { 'hiera.yaml' => 'version: 5', 'data' => { 'common.yaml' => '' }, } end let(:msg) { /file does not contain a valid yaml hash/ } %w(off warning error).each do |strict| it "logs a warning when --strict is '#{strict}'" do Puppet[:strict] = strict logs = [] Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do expect(Lookup.lookup('mod::somevar', nil, nil, true, nil, invocation)).to be_nil end expect(logs.map(&:message)).to contain_exactly(msg) end end end context 'when doing lookup' do it 'finds data in global layer' do expect(Lookup.lookup('a', nil, nil, false, nil, invocation)).to eql('a (from global)') end it 'finds data in environment layer' do expect(Lookup.lookup('b', nil, 'not found', true, nil, invocation)).to eql('b (from environment)') end it 'global layer wins over environment layer' do expect(Lookup.lookup('d', nil, 'not found', true, nil, invocation)).to eql('d (from global)') end it 'finds data in module layer' do expect(Lookup.lookup('mod::c', nil, 'not found', true, nil, invocation)).to eql('mod::c (from module)') end it 'global layer wins over module layer' do expect(Lookup.lookup('mod::e', nil, 'not found', true, nil, invocation)).to eql('mod::e (from global)') end it 'environment layer wins over module layer' do expect(Lookup.lookup('mod::f', nil, 'not found', true, nil, invocation)).to eql('mod::f (from environment)') end it 'returns the correct types for hash keys' do expect(Lookup.lookup('mod::g', nil, 'not found', true, nil, invocation)).to eql( { 'symbol' => 'symbol key value', 'key' => 'string key value', 6 => 'integer key value', -4 => 'negative integer key value', 2.7 => 'float key value', '6' => 'string integer key value' } ) end it 'can navigate a hash with an integer key using a dotted key' do expect(Lookup.lookup('mod::g.6', nil, 'not found', true, nil, invocation)).to eql('integer key value') end it 'can navigate a hash with a negative integer key using a dotted key' do expect(Lookup.lookup('mod::g.-4', nil, 'not found', true, nil, invocation)).to eql('negative integer key value') end it 'can navigate a hash with an string integer key using a dotted key with quoted integer' do expect(Lookup.lookup("mod::g.'6'", nil, 'not found', true, nil, invocation)).to eql('string integer key value') end context "with 'global_only' set to true in the invocation" do let(:invocation) { Invocation.new(scope).set_global_only } it 'finds data in global layer' do expect(Lookup.lookup('a', nil, nil, false, nil, invocation)).to eql('a (from global)') end it 'does not find data in environment layer' do expect(Lookup.lookup('b', nil, 'not found', true, nil, invocation)).to eql('not found') end it 'does not find data in module layer' do expect(Lookup.lookup('mod::c', nil, 'not found', true, nil, invocation)).to eql('not found') end end context "with 'global_only' set to true in the lookup adapter" do it 'finds data in global layer' do invocation.lookup_adapter.set_global_only expect(Lookup.lookup('a', nil, nil, false, nil, invocation)).to eql('a (from global)') end it 'does not find data in environment layer' do invocation.lookup_adapter.set_global_only expect(Lookup.lookup('b', nil, 'not found', true, nil, invocation)).to eql('not found') end it 'does not find data in module layer' do invocation.lookup_adapter.set_global_only expect(Lookup.lookup('mod::c', nil, 'not found', true, nil, invocation)).to eql('not found') end end context 'with subclassed lookup adpater' do let(:other_dir) { tmpdir('other') } let(:other_dir_content) do { 'hiera.yaml' => <<-YAML.unindent, version: 5 hierarchy: - name: Common path: common.yaml - name: More path: more.yaml YAML 'data' => { 'common.yaml' => <<-YAML.unindent, a: a (from other global) d: d (from other global) mixed_adapter_hash: a: ab: value a.ab (from other common global) ad: value a.ad (from other common global) mod::e: mod::e (from other global) lookup_options: mixed_adapter_hash: merge: deep YAML 'more.yaml' => <<-YAML.unindent mixed_adapter_hash: a: aa: value a.aa (from other more global) ac: value a.ac (from other more global) YAML } } end let(:populated_other_dir) do dir_contained_in(other_dir, other_dir_content) other_dir end before(:each) do eval(<<-RUBY.unindent) class SpecialLookupAdapter < LookupAdapter def initialize(compiler) super set_global_only set_global_hiera_config_path(File.join('#{populated_other_dir}', 'hiera.yaml')) end end RUBY end after(:each) do Puppet::Pops::Lookup.send(:remove_const, :SpecialLookupAdapter) end let(:other_invocation) { Invocation.new(scope, EMPTY_HASH, EMPTY_HASH, nil, SpecialLookupAdapter) } it 'finds different data in global layer' do expect(Lookup.lookup('a', nil, nil, false, nil, other_invocation)).to eql('a (from other global)') expect(Lookup.lookup('a', nil, nil, false, nil, invocation)).to eql('a (from global)') end it 'does not find data in environment layer' do expect(Lookup.lookup('b', nil, 'not found', true, nil, other_invocation)).to eql('not found') expect(Lookup.lookup('b', nil, 'not found', true, nil, invocation)).to eql('b (from environment)') end it 'does not find data in module layer' do expect(Lookup.lookup('mod::c', nil, 'not found', true, nil, other_invocation)).to eql('not found') expect(Lookup.lookup('mod::c', nil, 'not found', true, nil, invocation)).to eql('mod::c (from module)') end it 'resolves lookup options using the custom adapter' do expect(Lookup.lookup('mixed_adapter_hash', nil, 'not found', true, nil, other_invocation)).to eql( { 'a' => { 'aa' => 'value a.aa (from other more global)', 'ab' => 'value a.ab (from other common global)', 'ac' => 'value a.ac (from other more global)', 'ad' => 'value a.ad (from other common global)' } } ) end end end end end end puppet-5.5.10/spec/unit/pops/merge_strategy_spec.rb0000644005276200011600000000104713417161721022266 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' module Puppet::Pops describe 'MergeStrategy' do context 'deep merge' do it 'does not mutate the source of a merge' do a = { 'a' => { 'b' => 'va' }, 'c' => 2 } b = { 'a' => { 'b' => 'vb' }, 'b' => 3} c = MergeStrategy.strategy(:deep).merge(a, b); expect(a).to eql({ 'a' => { 'b' => 'va' }, 'c' => 2 }) expect(b).to eql({ 'a' => { 'b' => 'vb' }, 'b' => 3 }) expect(c).to eql({ 'a' => { 'b' => 'va' }, 'b' => 3, 'c' => 2 }) end end end end puppet-5.5.10/spec/unit/pops/migration_spec.rb0000644005276200011600000000426513417161721021243 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' require 'puppet/pops/evaluator/evaluator_impl' require 'puppet/loaders' require 'puppet_spec/pops' require 'puppet_spec/scope' require 'puppet/parser/e4_parser_adapter' describe 'Puppet::Pops::MigrationMigrationChecker' do include PuppetSpec::Pops include PuppetSpec::Scope before(:each) do Puppet[:strict_variables] = true # Puppetx cannot be loaded until the correct parser has been set (injector is turned off otherwise) require 'puppet_x' # Tests needs a known configuration of node/scope/compiler since it parses and evaluates # snippets as the compiler will evaluate them, butwithout the overhead of compiling a complete # catalog for each tested expression. # @parser = Puppet::Pops::Parser::EvaluatingParser.new @node = Puppet::Node.new('node.example.com') @node.environment = Puppet::Node::Environment.create(:testing, []) @compiler = Puppet::Parser::Compiler.new(@node) @scope = Puppet::Parser::Scope.new(@compiler) @scope.source = Puppet::Resource::Type.new(:node, 'node.example.com') @scope.parent = @compiler.topscope end let(:scope) { @scope } describe "when there is no MigrationChecker in the PuppetContext" do it "a null implementation of the MigrationChecker gets created (once per impl that needs one)" do migration_checker = Puppet::Pops::Migration::MigrationChecker.new() Puppet::Pops::Migration::MigrationChecker.expects(:new).at_least_once.returns(migration_checker) expect(Puppet::Pops::Parser::EvaluatingParser.new.evaluate_string(scope, "1", __FILE__)).to eq(1) Puppet::Pops::Migration::MigrationChecker.unstub(:new) end end describe "when there is a MigrationChecker in the Puppet Context" do it "does not create any MigrationChecker instances when parsing and evaluating" do migration_checker = mock() Puppet::Pops::Migration::MigrationChecker.expects(:new).never Puppet.override({:migration_checker => migration_checker}, "test-context") do Puppet::Pops::Parser::EvaluatingParser.new.evaluate_string(scope, "true", __FILE__) end Puppet::Pops::Migration::MigrationChecker.unstub(:new) end end end puppet-5.5.10/spec/unit/pops/model/0000755005276200011600000000000013417162176017011 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/pops/model/model_spec.rb0000644005276200011600000000304713417161721021447 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' describe Puppet::Pops::Model do it "should be possible to create an instance of a model object" do nop = Puppet::Pops::Model::Nop.new(Puppet::Pops::Parser::Locator.locator('code', 'file'), 0, 0) expect(nop.class).to eq(Puppet::Pops::Model::Nop) end end describe Puppet::Pops::Model::Factory do Factory = Puppet::Pops::Model::Factory Model = Puppet::Pops::Model it "construct an arithmetic expression" do x = Factory.literal(10) + Factory.literal(20) expect(x.is_a?(Factory)).to eq(true) model = x.model expect(model.is_a?(Model::ArithmeticExpression)).to eq(true) expect(model.operator).to eq('+') expect(model.left_expr.class).to eq(Model::LiteralInteger) expect(model.right_expr.class).to eq(Model::LiteralInteger) expect(model.left_expr.value).to eq(10) expect(model.right_expr.value).to eq(20) end it "should be easy to compare using a model tree dumper" do x = Factory.literal(10) + Factory.literal(20) expect(Puppet::Pops::Model::ModelTreeDumper.new.dump(x.model)).to eq("(+ 10 20)") end it "builder should apply precedence" do x = Factory.literal(2) * Factory.literal(10) + Factory.literal(20) expect(Puppet::Pops::Model::ModelTreeDumper.new.dump(x.model)).to eq("(+ (* 2 10) 20)") end describe "should be describable with labels" it 'describes a PlanDefinition as "Plan Definition"' do expect(Puppet::Pops::Model::ModelLabelProvider.new.label(Factory.PLAN('test', [], nil))).to eq("Plan Definition") end end puppet-5.5.10/spec/unit/pops/model/pn_transformer_spec.rb0000644005276200011600000000323013417161722023401 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' require 'puppet/pops/pn' module Puppet::Pops module Model describe 'Puppet::Pops::Model::PNTransformer' do def call(name, *elements) PN::Call.new(name, *elements.map { |e| lit(e) }) end context 'transforms the expression' do it '"\'hello\'" into the corresponding literal' do x = Factory.literal('hello') expect(Puppet::Pops::Model::PNTransformer.transform(x.model)).to eq(lit('hello')) end it '"32" into into the corresponding literal' do x = Factory.literal(32) expect(Puppet::Pops::Model::PNTransformer.transform(x.model)).to eq(lit(32)) end it '"true" into into the corresponding literal' do x = Factory.literal(true) expect(Puppet::Pops::Model::PNTransformer.transform(x.model)).to eq(lit(true)) end it '"10 + 20" into (+ 10 20)' do x = Factory.literal(10) + Factory.literal(20) expect(Puppet::Pops::Model::PNTransformer.transform(x.model)).to eq(call('+', 10, 20)) end it '"[10, 20]" into into (array 10 20)' do x = Factory.literal([10, 20]) expect(Puppet::Pops::Model::PNTransformer.transform(x.model)).to eq(call('array', 10, 20)) end it '"{a => 1, b => 2}" into into (hash (=> ("a" 1)) (=> ("b" 2)))' do x = Factory.HASH([Factory.KEY_ENTRY(Factory.literal('a'), Factory.literal(1)), Factory.KEY_ENTRY(Factory.literal('b'), Factory.literal(2))]) expect(Puppet::Pops::Model::PNTransformer.transform(x.model)).to eq( call('hash', call('=>', 'a', 1), call('=>', 'b', 2))) end end def lit(value) value.is_a?(PN) ? value : PN::Literal.new(value) end end end end puppet-5.5.10/spec/unit/pops/parser/0000755005276200011600000000000013417162176017205 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/pops/parser/epp_parser_spec.rb0000644005276200011600000000670013417161721022702 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' require File.join(File.dirname(__FILE__), '/../factory_rspec_helper') module EppParserRspecHelper include FactoryRspecHelper def parse(code) parser = Puppet::Pops::Parser::EppParser.new() parser.parse_string(code) end end describe "epp parser" do include EppParserRspecHelper it "should instantiate an epp parser" do parser = Puppet::Pops::Parser::EppParser.new() expect(parser.class).to eq(Puppet::Pops::Parser::EppParser) end it "should parse a code string and return a program with epp" do parser = Puppet::Pops::Parser::EppParser.new() model = parser.parse_string("Nothing to see here, move along...").model expect(model.class).to eq(Puppet::Pops::Model::Program) expect(model.body.class).to eq(Puppet::Pops::Model::LambdaExpression) expect(model.body.body.class).to eq(Puppet::Pops::Model::EppExpression) end context "when facing bad input it reports" do it "unbalanced tags" do expect { dump(parse("<% missing end tag")) }.to raise_error(/Unbalanced/) end it "abrupt end" do expect { dump(parse("dum di dum di dum <%")) }.to raise_error(/Unbalanced/) end it "nested epp tags" do expect { dump(parse("<% $a = 10 <% $b = 20 %>%>")) }.to raise_error(/Syntax error/) end it "nested epp expression tags" do expect { dump(parse("<%= 1+1 <%= 2+2 %>%>")) }.to raise_error(/Syntax error/) end it "rendering sequence of expressions" do expect { dump(parse("<%= 1 2 3 %>")) }.to raise_error(/Syntax error/) end end context "handles parsing of" do it "text (and nothing else)" do expect(dump(parse("Hello World"))).to eq([ "(lambda (epp (block", " (render-s 'Hello World')", ")))"].join("\n")) end it "template parameters" do expect(dump(parse("<%|$x|%>Hello World"))).to eq([ "(lambda (parameters x) (epp (block", " (render-s 'Hello World')", ")))"].join("\n")) end it "template parameters with default" do expect(dump(parse("<%|$x='cigar'|%>Hello World"))).to eq([ "(lambda (parameters (= x 'cigar')) (epp (block", " (render-s 'Hello World')", ")))"].join("\n")) end it "template parameters with and without default" do expect(dump(parse("<%|$x='cigar', $y|%>Hello World"))).to eq([ "(lambda (parameters (= x 'cigar') y) (epp (block", " (render-s 'Hello World')", ")))"].join("\n")) end it "template parameters + additional setup" do expect(dump(parse("<%|$x| $y = 10 %>Hello World"))).to eq([ "(lambda (parameters x) (epp (block", " (= $y 10)", " (render-s 'Hello World')", ")))"].join("\n")) end it "comments" do expect(dump(parse("<%#($x='cigar', $y)%>Hello World"))).to eq([ "(lambda (epp (block", " (render-s 'Hello World')", ")))" ].join("\n")) end it "verbatim epp tags" do expect(dump(parse("<%% contemplating %%>Hello World"))).to eq([ "(lambda (epp (block", " (render-s '<% contemplating %>Hello World')", ")))" ].join("\n")) end it "expressions" do expect(dump(parse("We all live in <%= 3.14 - 2.14 %> world"))).to eq([ "(lambda (epp (block", " (render-s 'We all live in ')", " (render (- 3.14 2.14))", " (render-s ' world')", ")))" ].join("\n")) end end end puppet-5.5.10/spec/unit/pops/parser/evaluating_parser_spec.rb0000644005276200011600000000450313417161721024254 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' require 'puppet_spec/pops' require 'puppet_spec/scope' describe 'The Evaluating Parser' do include PuppetSpec::Pops include PuppetSpec::Scope let(:acceptor) { Puppet::Pops::Validation::Acceptor.new() } let(:scope) { s = create_test_scope_for_node(node); s } let(:node) { 'node.example.com' } def quote(x) Puppet::Pops::Parser::EvaluatingParser.quote(x) end def evaluator() Puppet::Pops::Parser::EvaluatingParser.new() end def evaluate(s) evaluator.evaluate(scope, quote(s)) end def test(x) expect(evaluator.evaluate_string(scope, quote(x))).to eq(x) end def test_interpolate(x, y) scope['a'] = 'expansion' expect(evaluator.evaluate_string(scope, quote(x))).to eq(y) end context 'when evaluating' do it 'should produce an empty string with no change' do test('') end it 'should produce a normal string with no change' do test('A normal string') end it 'should produce a string with newlines with no change' do test("A\nnormal\nstring") end it 'should produce a string with escaped newlines with no change' do test("A\\nnormal\\nstring") end it 'should produce a string containing quotes without change' do test('This " should remain untouched') end it 'should produce a string containing escaped quotes without change' do test('This \" should remain untouched') end it 'should expand ${a} variables' do test_interpolate('This ${a} was expanded', 'This expansion was expanded') end it 'should expand quoted ${a} variables' do test_interpolate('This "${a}" was expanded', 'This "expansion" was expanded') end it 'should not expand escaped ${a}' do test_interpolate('This \${a} was not expanded', 'This ${a} was not expanded') end it 'should expand $a variables' do test_interpolate('This $a was expanded', 'This expansion was expanded') end it 'should expand quoted $a variables' do test_interpolate('This "$a" was expanded', 'This "expansion" was expanded') end it 'should not expand escaped $a' do test_interpolate('This \$a was not expanded', 'This $a was not expanded') end it 'should produce an single space from a \s' do test_interpolate("\\s", ' ') end end end puppet-5.5.10/spec/unit/pops/parser/locator_spec.rb0000644005276200011600000000424013417161721022202 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' describe Puppet::Pops::Parser::Locator do it "multi byte characters in a comment does not interfere with AST node text extraction" do parser = Puppet::Pops::Parser::Parser.new() model = parser.parse_string("# \u{0400}comment\nabcdef#XXXXXXXXXX").model expect(model.class).to eq(Puppet::Pops::Model::Program) expect(model.body.offset).to eq(12) expect(model.body.length).to eq(6) expect(model.body.locator.extract_text(model.body.offset, model.body.length)).to eq('abcdef') end it "multi byte characters in a comment does not interfere with AST node text extraction" do parser = Puppet::Pops::Parser::Parser.new() model = parser.parse_string("# \u{0400}comment\n1 + 2#XXXXXXXXXX").model expect(model.class).to eq(Puppet::Pops::Model::Program) expect(model.body.offset).to eq(14) # The '+' expect(model.body.length).to eq(1) expect(model.body.locator.extract_tree_text(model.body)).to eq('1 + 2') end it 'Locator caches last offset / line' do #Puppet::Pops::Parser::Locator::AbstractLocator.expects(:ary_bsearch_i).once parser = Puppet::Pops::Parser::Parser.new() model = parser.parse_string("$a\n = 1\n + 1\n").model model.body.locator.expects(:ary_bsearch_i).with(anything, 2).once.returns(:special_value) expect(model.body.locator.line_for_offset(2)).to eq(:special_value) expect(model.body.locator.line_for_offset(2)).to eq(:special_value) end it 'Locator invalidates last offset / line cache if asked for different offset' do #Puppet::Pops::Parser::Locator::AbstractLocator.expects(:ary_bsearch_i).once parser = Puppet::Pops::Parser::Parser.new() model = parser.parse_string("$a\n = 1\n + 1\n").model model.body.locator.expects(:ary_bsearch_i).with(anything, 2).twice.returns(:first_value, :third_value) model.body.locator.expects(:ary_bsearch_i).with(anything, 3).once.returns(:second_value) expect(model.body.locator.line_for_offset(2)).to eq(:first_value) expect(model.body.locator.line_for_offset(3)).to eq(:second_value) # invalidates cache as side effect expect(model.body.locator.line_for_offset(2)).to eq(:third_value) end end puppet-5.5.10/spec/unit/pops/parser/parse_application_spec.rb0000644005276200011600000000132313417161721024233 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' require_relative 'parser_rspec_helper' describe "egrammar parsing of 'application'" do include ParserRspecHelper it "an empty body" do expect(dump(parse("application foo { }"))).to eq("(application foo () ())") end it "an empty body" do prog = <<-EPROG application foo { db { one: password => 'secret' } } EPROG expect(dump(parse(prog))).to eq( [ "(application foo () (block", " (resource db", " (one", " (password => 'secret')))", "))" ].join("\n")) end it "accepts parameters" do s = "application foo($p1 = 'yo', $p2) { }" expect(dump(parse(s))).to eq("(application foo ((= p1 'yo') p2) ())") end end puppet-5.5.10/spec/unit/pops/parser/parse_basic_expressions_spec.rb0000644005276200011600000004021113417161721025452 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' # relative to this spec file (./) does not work as this file is loaded by rspec require File.join(File.dirname(__FILE__), '/parser_rspec_helper') describe "egrammar parsing basic expressions" do include ParserRspecHelper context "When the parser parses arithmetic" do context "with Integers" do it "$a = 2 + 2" do; expect(dump(parse("$a = 2 + 2"))).to eq("(= $a (+ 2 2))") ; end it "$a = 7 - 3" do; expect(dump(parse("$a = 7 - 3"))).to eq("(= $a (- 7 3))") ; end it "$a = 6 * 3" do; expect(dump(parse("$a = 6 * 3"))).to eq("(= $a (* 6 3))") ; end it "$a = 6 / 3" do; expect(dump(parse("$a = 6 / 3"))).to eq("(= $a (/ 6 3))") ; end it "$a = 6 % 3" do; expect(dump(parse("$a = 6 % 3"))).to eq("(= $a (% 6 3))") ; end it "$a = -(6/3)" do; expect(dump(parse("$a = -(6/3)"))).to eq("(= $a (- (/ 6 3)))") ; end it "$a = -6/3" do; expect(dump(parse("$a = -6/3"))).to eq("(= $a (/ (- 6) 3))") ; end it "$a = 8 >> 1 " do; expect(dump(parse("$a = 8 >> 1"))).to eq("(= $a (>> 8 1))") ; end it "$a = 8 << 1 " do; expect(dump(parse("$a = 8 << 1"))).to eq("(= $a (<< 8 1))") ; end end context "with Floats" do it "$a = 2.2 + 2.2" do; expect(dump(parse("$a = 2.2 + 2.2"))).to eq("(= $a (+ 2.2 2.2))") ; end it "$a = 7.7 - 3.3" do; expect(dump(parse("$a = 7.7 - 3.3"))).to eq("(= $a (- 7.7 3.3))") ; end it "$a = 6.1 * 3.1" do; expect(dump(parse("$a = 6.1 - 3.1"))).to eq("(= $a (- 6.1 3.1))") ; end it "$a = 6.6 / 3.3" do; expect(dump(parse("$a = 6.6 / 3.3"))).to eq("(= $a (/ 6.6 3.3))") ; end it "$a = -(6.0/3.0)" do; expect(dump(parse("$a = -(6.0/3.0)"))).to eq("(= $a (- (/ 6.0 3.0)))") ; end it "$a = -6.0/3.0" do; expect(dump(parse("$a = -6.0/3.0"))).to eq("(= $a (/ (- 6.0) 3.0))") ; end it "$a = 3.14 << 2" do; expect(dump(parse("$a = 3.14 << 2"))).to eq("(= $a (<< 3.14 2))") ; end it "$a = 3.14 >> 2" do; expect(dump(parse("$a = 3.14 >> 2"))).to eq("(= $a (>> 3.14 2))") ; end end context "with hex and octal Integer values" do it "$a = 0xAB + 0xCD" do; expect(dump(parse("$a = 0xAB + 0xCD"))).to eq("(= $a (+ 0xAB 0xCD))") ; end it "$a = 0777 - 0333" do; expect(dump(parse("$a = 0777 - 0333"))).to eq("(= $a (- 0777 0333))") ; end end context "with strings requiring boxing to Numeric" do # Test that numbers in string form does not turn into numbers it "$a = '2' + '2'" do; expect(dump(parse("$a = '2' + '2'"))).to eq("(= $a (+ '2' '2'))") ; end it "$a = '2.2' + '0.2'" do; expect(dump(parse("$a = '2.2' + '0.2'"))).to eq("(= $a (+ '2.2' '0.2'))") ; end it "$a = '0xab' + '0xcd'" do; expect(dump(parse("$a = '0xab' + '0xcd'"))).to eq("(= $a (+ '0xab' '0xcd'))") ; end it "$a = '0777' + '0333'" do; expect(dump(parse("$a = '0777' + '0333'"))).to eq("(= $a (+ '0777' '0333'))") ; end end context "precedence should be correct" do it "$a = 1 + 2 * 3" do; expect(dump(parse("$a = 1 + 2 * 3"))).to eq("(= $a (+ 1 (* 2 3)))"); end it "$a = 1 + 2 % 3" do; expect(dump(parse("$a = 1 + 2 % 3"))).to eq("(= $a (+ 1 (% 2 3)))"); end it "$a = 1 + 2 / 3" do; expect(dump(parse("$a = 1 + 2 / 3"))).to eq("(= $a (+ 1 (/ 2 3)))"); end it "$a = 1 + 2 << 3" do; expect(dump(parse("$a = 1 + 2 << 3"))).to eq("(= $a (<< (+ 1 2) 3))"); end it "$a = 1 + 2 >> 3" do; expect(dump(parse("$a = 1 + 2 >> 3"))).to eq("(= $a (>> (+ 1 2) 3))"); end end context "parentheses alter precedence" do it "$a = (1 + 2) * 3" do; expect(dump(parse("$a = (1 + 2) * 3"))).to eq("(= $a (* (+ 1 2) 3))"); end it "$a = (1 + 2) / 3" do; expect(dump(parse("$a = (1 + 2) / 3"))).to eq("(= $a (/ (+ 1 2) 3))"); end end end context "When the evaluator performs boolean operations" do context "using operators AND OR NOT" do it "$a = true and true" do; expect(dump(parse("$a = true and true"))).to eq("(= $a (&& true true))"); end it "$a = true or true" do; expect(dump(parse("$a = true or true"))).to eq("(= $a (|| true true))") ; end it "$a = !true" do; expect(dump(parse("$a = !true"))).to eq("(= $a (! true))") ; end end context "precedence should be correct" do it "$a = false or true and true" do expect(dump(parse("$a = false or true and true"))).to eq("(= $a (|| false (&& true true)))") end it "$a = (false or true) and true" do expect(dump(parse("$a = (false or true) and true"))).to eq("(= $a (&& (|| false true) true))") end it "$a = !true or true and true" do expect(dump(parse("$a = !false or true and true"))).to eq("(= $a (|| (! false) (&& true true)))") end end # Possibly change to check of literal expressions context "on values requiring boxing to Boolean" do it "'x' == true" do expect(dump(parse("! 'x'"))).to eq("(! 'x')") end it "'' == false" do expect(dump(parse("! ''"))).to eq("(! '')") end it ":undef == false" do expect(dump(parse("! undef"))).to eq("(! :undef)") end end end context "When parsing comparisons" do context "of string values" do it "$a = 'a' == 'a'" do; expect(dump(parse("$a = 'a' == 'a'"))).to eq("(= $a (== 'a' 'a'))") ; end it "$a = 'a' != 'a'" do; expect(dump(parse("$a = 'a' != 'a'"))).to eq("(= $a (!= 'a' 'a'))") ; end it "$a = 'a' < 'b'" do; expect(dump(parse("$a = 'a' < 'b'"))).to eq("(= $a (< 'a' 'b'))") ; end it "$a = 'a' > 'b'" do; expect(dump(parse("$a = 'a' > 'b'"))).to eq("(= $a (> 'a' 'b'))") ; end it "$a = 'a' <= 'b'" do; expect(dump(parse("$a = 'a' <= 'b'"))).to eq("(= $a (<= 'a' 'b'))") ; end it "$a = 'a' >= 'b'" do; expect(dump(parse("$a = 'a' >= 'b'"))).to eq("(= $a (>= 'a' 'b'))") ; end end context "of integer values" do it "$a = 1 == 1" do; expect(dump(parse("$a = 1 == 1"))).to eq("(= $a (== 1 1))") ; end it "$a = 1 != 1" do; expect(dump(parse("$a = 1 != 1"))).to eq("(= $a (!= 1 1))") ; end it "$a = 1 < 2" do; expect(dump(parse("$a = 1 < 2"))).to eq("(= $a (< 1 2))") ; end it "$a = 1 > 2" do; expect(dump(parse("$a = 1 > 2"))).to eq("(= $a (> 1 2))") ; end it "$a = 1 <= 2" do; expect(dump(parse("$a = 1 <= 2"))).to eq("(= $a (<= 1 2))") ; end it "$a = 1 >= 2" do; expect(dump(parse("$a = 1 >= 2"))).to eq("(= $a (>= 1 2))") ; end end context "of regular expressions (parse errors)" do # Not supported in concrete syntax it "$a = /.*/ == /.*/" do expect(dump(parse("$a = /.*/ == /.*/"))).to eq("(= $a (== /.*/ /.*/))") end it "$a = /.*/ != /a.*/" do expect(dump(parse("$a = /.*/ != /.*/"))).to eq("(= $a (!= /.*/ /.*/))") end end end context "When parsing Regular Expression matching" do it "$a = 'a' =~ /.*/" do; expect(dump(parse("$a = 'a' =~ /.*/"))).to eq("(= $a (=~ 'a' /.*/))") ; end it "$a = 'a' =~ '.*'" do; expect(dump(parse("$a = 'a' =~ '.*'"))).to eq("(= $a (=~ 'a' '.*'))") ; end it "$a = 'a' !~ /b.*/" do; expect(dump(parse("$a = 'a' !~ /b.*/"))).to eq("(= $a (!~ 'a' /b.*/))") ; end it "$a = 'a' !~ 'b.*'" do; expect(dump(parse("$a = 'a' !~ 'b.*'"))).to eq("(= $a (!~ 'a' 'b.*'))") ; end end context "When parsing unfold" do it "$a = *[1,2]" do; expect(dump(parse("$a = *[1,2]"))).to eq("(= $a (unfold ([] 1 2)))") ; end it "$a = *1" do; expect(dump(parse("$a = *1"))).to eq("(= $a (unfold 1))") ; end it "$a = *[1,a => 2]" do; expect(dump(parse("$a = *[1,a => 2]"))).to eq("(= $a (unfold ([] 1 ({} (a 2)))))") ; end end context "When parsing Lists" do it "$a = []" do expect(dump(parse("$a = []"))).to eq("(= $a ([]))") end it "$a = [1]" do expect(dump(parse("$a = [1]"))).to eq("(= $a ([] 1))") end it "$a = [1,2,3]" do expect(dump(parse("$a = [1,2,3]"))).to eq("(= $a ([] 1 2 3))") end it "$a = [1,a => 2]" do expect(dump(parse("$a = [1,a => 2]"))).to eq('(= $a ([] 1 ({} (a 2))))') end it "$a = [1,a => 2, 3]" do expect(dump(parse("$a = [1,a => 2, 3]"))).to eq('(= $a ([] 1 ({} (a 2)) 3))') end it "$a = [1,a => 2, b => 3]" do expect(dump(parse("$a = [1,a => 2, b => 3]"))).to eq('(= $a ([] 1 ({} (a 2) (b 3))))') end it "$a = [1,a => 2, b => 3, 4]" do expect(dump(parse("$a = [1,a => 2, b => 3, 4]"))).to eq('(= $a ([] 1 ({} (a 2) (b 3)) 4))') end it "$a = [{ x => y }, a => 2, b => 3, { z => p }]" do expect(dump(parse("$a = [{ x => y }, a => 2, b => 3, { z => p }]"))).to eq('(= $a ([] ({} (x y)) ({} (a 2) (b 3)) ({} (z p))))') end it "[...[...[]]] should create nested arrays without trouble" do expect(dump(parse("$a = [1,[2.0, 2.1, [2.2]],[3.0, 3.1]]"))).to eq("(= $a ([] 1 ([] 2.0 2.1 ([] 2.2)) ([] 3.0 3.1)))") end it "$a = [2 + 2]" do expect(dump(parse("$a = [2+2]"))).to eq("(= $a ([] (+ 2 2)))") end it "$a [1,2,3] == [1,2,3]" do expect(dump(parse("$a = [1,2,3] == [1,2,3]"))).to eq("(= $a (== ([] 1 2 3) ([] 1 2 3)))") end it "calculates the text length of an empty array" do expect(parse("[]").model.body.length).to eq(2) expect(parse("[ ]").model.body.length).to eq(3) end { 'keyword' => %w(type function), 'reserved word' => %w(application site produces consumes) }.each_pair do |word_type, words| words.each do |word| it "allows the #{word_type} '#{word}' in a list" do expect(dump(parse("$a = [#{word}]"))).to(eq("(= $a ([] '#{word}'))")) end it "allows the #{word_type} '#{word}' as a key in a hash" do expect(dump(parse("$a = {#{word}=>'x'}"))).to(eq("(= $a ({} ('#{word}' 'x')))")) end it "allows the #{word_type} '#{word}' as a value in a hash" do expect(dump(parse("$a = {'x'=>#{word}}"))).to(eq("(= $a ({} ('x' '#{word}')))")) end end end end context "When parsing indexed access" do it "$a = $b[2]" do expect(dump(parse("$a = $b[2]"))).to eq("(= $a (slice $b 2))") end it "$a = $b[2,]" do expect(dump(parse("$a = $b[2,]"))).to eq("(= $a (slice $b 2))") end it "$a = [1, 2, 3][2]" do expect(dump(parse("$a = [1,2,3][2]"))).to eq("(= $a (slice ([] 1 2 3) 2))") end it '$a = [1, 2, 3][a => 2]' do expect(dump(parse('$a = [1,2,3][a => 2]'))).to eq('(= $a (slice ([] 1 2 3) ({} (a 2))))') end it "$a = {'a' => 1, 'b' => 2}['b']" do expect(dump(parse("$a = {'a'=>1,'b' =>2}[b]"))).to eq("(= $a (slice ({} ('a' 1) ('b' 2)) b))") end end context 'When parsing type aliases' do it 'type A = B' do expect(dump(parse('type A = B'))).to eq('(type-alias A B)') end it 'type A = B[]' do expect{parse('type A = B[]')}.to raise_error(/Syntax error at '\]'/) end it 'type A = B[,]' do expect{parse('type A = B[,]')}.to raise_error(/Syntax error at ','/) end it 'type A = B[C]' do expect(dump(parse('type A = B[C]'))).to eq('(type-alias A (slice B C))') end it 'type A = B[C,]' do expect(dump(parse('type A = B[C,]'))).to eq('(type-alias A (slice B C))') end it 'type A = B[C,D]' do expect(dump(parse('type A = B[C,D]'))).to eq('(type-alias A (slice B (C D)))') end it 'type A = B[C,D,]' do expect(dump(parse('type A = B[C,D,]'))).to eq('(type-alias A (slice B (C D)))') end end context "When parsing assignments" do it "Should allow simple assignment" do expect(dump(parse("$a = 10"))).to eq("(= $a 10)") end it "Should allow append assignment" do expect(dump(parse("$a += 10"))).to eq("(+= $a 10)") end it "Should allow without assignment" do expect(dump(parse("$a -= 10"))).to eq("(-= $a 10)") end it "Should allow chained assignment" do expect(dump(parse("$a = $b = 10"))).to eq("(= $a (= $b 10))") end it "Should allow chained assignment with expressions" do expect(dump(parse("$a = 1 + ($b = 10)"))).to eq("(= $a (+ 1 (= $b 10)))") end end context "When parsing Hashes" do it "should create a Hash when evaluating a LiteralHash" do expect(dump(parse("$a = {'a'=>1,'b'=>2}"))).to eq("(= $a ({} ('a' 1) ('b' 2)))") end it "$a = {...{...{}}} should create nested hashes without trouble" do expect(dump(parse("$a = {'a'=>1,'b'=>{'x'=>2.1,'y'=>2.2}}"))).to eq("(= $a ({} ('a' 1) ('b' ({} ('x' 2.1) ('y' 2.2)))))") end it "$a = {'a'=> 2 + 2} should evaluate values in entries" do expect(dump(parse("$a = {'a'=>2+2}"))).to eq("(= $a ({} ('a' (+ 2 2))))") end it "$a = {'a'=> 1, 'b'=>2} == {'a'=> 1, 'b'=>2}" do expect(dump(parse("$a = {'a'=>1,'b'=>2} == {'a'=>1,'b'=>2}"))).to eq("(= $a (== ({} ('a' 1) ('b' 2)) ({} ('a' 1) ('b' 2))))") end it "$a = {'a'=> 1, 'b'=>2} != {'x'=> 1, 'y'=>3}" do expect(dump(parse("$a = {'a'=>1,'b'=>2} != {'a'=>1,'b'=>2}"))).to eq("(= $a (!= ({} ('a' 1) ('b' 2)) ({} ('a' 1) ('b' 2))))") end it "$a = 'a' => 1" do expect{parse("$a = 'a' => 1")}.to raise_error(/Syntax error at '=>'/) end it "$a = { 'a' => 'b' => 1 }" do expect{parse("$a = { 'a' => 'b' => 1 }")}.to raise_error(/Syntax error at '=>'/) end it "calculates the text length of an empty hash" do expect(parse("{}").model.body.length).to eq(2) expect(parse("{ }").model.body.length).to eq(3) end end context "When parsing the 'in' operator" do it "with integer in a list" do expect(dump(parse("$a = 1 in [1,2,3]"))).to eq("(= $a (in 1 ([] 1 2 3)))") end it "with string key in a hash" do expect(dump(parse("$a = 'a' in {'x'=>1, 'a'=>2, 'y'=> 3}"))).to eq("(= $a (in 'a' ({} ('x' 1) ('a' 2) ('y' 3))))") end it "with substrings of a string" do expect(dump(parse("$a = 'ana' in 'bananas'"))).to eq("(= $a (in 'ana' 'bananas'))") end it "with sublist in a list" do expect(dump(parse("$a = [2,3] in [1,2,3]"))).to eq("(= $a (in ([] 2 3) ([] 1 2 3)))") end end context "When parsing string interpolation" do it "should interpolate a bare word as a variable name, \"${var}\"" do expect(dump(parse("$a = \"$var\""))).to eq("(= $a (cat (str $var)))") end it "should interpolate a variable in a text expression, \"${$var}\"" do expect(dump(parse("$a = \"${$var}\""))).to eq("(= $a (cat (str $var)))") end it "should interpolate a variable, \"yo${var}yo\"" do expect(dump(parse("$a = \"yo${var}yo\""))).to eq("(= $a (cat 'yo' (str $var) 'yo'))") end it "should interpolate any expression in a text expression, \"${$var*2}\"" do expect(dump(parse("$a = \"yo${$var+2}yo\""))).to eq("(= $a (cat 'yo' (str (+ $var 2)) 'yo'))") end it "should not interpolate names as variable in expression, \"${notvar*2}\"" do expect(dump(parse("$a = \"yo${notvar+2}yo\""))).to eq("(= $a (cat 'yo' (str (+ notvar 2)) 'yo'))") end it "should interpolate name as variable in access expression, \"${var[0]}\"" do expect(dump(parse("$a = \"yo${var[0]}yo\""))).to eq("(= $a (cat 'yo' (str (slice $var 0)) 'yo'))") end it "should interpolate name as variable in method call, \"${var.foo}\"" do expect(dump(parse("$a = \"yo${$var.foo}yo\""))).to eq("(= $a (cat 'yo' (str (call-method (. $var foo))) 'yo'))") end it "should interpolate name as variable in method call, \"${var.foo}\"" do expect(dump(parse("$a = \"yo${var.foo}yo\""))).to eq("(= $a (cat 'yo' (str (call-method (. $var foo))) 'yo'))") expect(dump(parse("$a = \"yo${var.foo.bar}yo\""))).to eq("(= $a (cat 'yo' (str (call-method (. (call-method (. $var foo)) bar))) 'yo'))") end it "should interpolate interpolated expressions with a variable, \"yo${\"$var\"}yo\"" do expect(dump(parse("$a = \"yo${\"$var\"}yo\""))).to eq("(= $a (cat 'yo' (str (cat (str $var))) 'yo'))") end it "should interpolate interpolated expressions with an expression, \"yo${\"${$var+2}\"}yo\"" do expect(dump(parse("$a = \"yo${\"${$var+2}\"}yo\""))).to eq("(= $a (cat 'yo' (str (cat (str (+ $var 2)))) 'yo'))") end end end puppet-5.5.10/spec/unit/pops/parser/parse_calls_spec.rb0000644005276200011600000001266113417161721023035 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' # relative to this spec file (./) does not work as this file is loaded by rspec require File.join(File.dirname(__FILE__), '/parser_rspec_helper') describe "egrammar parsing function calls" do include ParserRspecHelper context "When parsing calls as statements" do context "in top level scope" do it "foo()" do expect(dump(parse("foo()"))).to eq("(invoke foo)") end it "notice bar" do expect(dump(parse("notice bar"))).to eq("(invoke notice bar)") end it "notice(bar)" do expect(dump(parse("notice bar"))).to eq("(invoke notice bar)") end it "foo(bar)" do expect(dump(parse("foo(bar)"))).to eq("(invoke foo bar)") end it "notice {a=>2}" do expect(dump(parse("notice {a => 2}"))).to eq("(invoke notice ({} ('a' 2)))") end it 'notice a=>2' do expect{parse('notice a => 2')}.to raise_error(/Syntax error at '=>'/) end it "foo(bar,)" do expect(dump(parse("foo(bar,)"))).to eq("(invoke foo bar)") end it "foo(bar, fum,)" do expect(dump(parse("foo(bar,fum,)"))).to eq("(invoke foo bar fum)") end it 'foo(a=>1)' do expect(dump(parse('foo(a=>1)'))).to eq('(invoke foo ({} (a 1)))') end it 'foo(a=>1, b=>2)' do expect(dump(parse('foo(a=>1, b=>2)'))).to eq('(invoke foo ({} (a 1) (b 2)))') end it 'foo({a=>1, b=>2})' do expect(dump(parse('foo({a=>1, b=>2})'))).to eq('(invoke foo ({} (a 1) (b 2)))') end it 'foo({a=>1}, b=>2)' do expect(dump(parse('foo({a=>1}, b=>2)'))).to eq('(invoke foo ({} (a 1)) ({} (b 2)))') end it 'foo(a=>1, {b=>2})' do expect(dump(parse('foo(a=>1, {b=>2})'))).to eq('(invoke foo ({} (a 1)) ({} (b 2)))') end it "notice fqdn_rand(30)" do expect(dump(parse("notice fqdn_rand(30)"))).to eq('(invoke notice (call fqdn_rand 30))') end it "notice type(42)" do expect(dump(parse("notice type(42)"))).to eq('(invoke notice (call type 42))') end it "is required to place the '(' on the same line as the name of the function to recognize it as arguments in call" do expect(dump(parse("notice foo\n(42)"))).to eq("(block\n (invoke notice foo)\n 42\n)") end end context "in nested scopes" do it "if true { foo() }" do expect(dump(parse("if true {foo()}"))).to eq("(if true\n (then (invoke foo)))") end it "if true { notice bar}" do expect(dump(parse("if true {notice bar}"))).to eq("(if true\n (then (invoke notice bar)))") end end end context "When parsing calls as expressions" do it "$a = foo()" do expect(dump(parse("$a = foo()"))).to eq("(= $a (call foo))") end it "$a = foo(bar)" do expect(dump(parse("$a = foo()"))).to eq("(= $a (call foo))") end # For egrammar where a bare word can be a "statement" it "$a = foo bar # illegal, must have parentheses" do expect(dump(parse("$a = foo bar"))).to eq("(block\n (= $a foo)\n bar\n)") end context "in nested scopes" do it "if true { $a = foo() }" do expect(dump(parse("if true { $a = foo()}"))).to eq("(if true\n (then (= $a (call foo))))") end it "if true { $a= foo(bar)}" do expect(dump(parse("if true {$a = foo(bar)}"))).to eq("(if true\n (then (= $a (call foo bar))))") end end end context "When parsing method calls" do it "$a.foo" do expect(dump(parse("$a.foo"))).to eq("(call-method (. $a foo))") end it "$a.foo || { }" do expect(dump(parse("$a.foo || { }"))).to eq("(call-method (. $a foo) (lambda ()))") end it "$a.foo |$x| { }" do expect(dump(parse("$a.foo |$x|{ }"))).to eq("(call-method (. $a foo) (lambda (parameters x) ()))") end it "$a.foo |$x|{ }" do expect(dump(parse("$a.foo |$x|{ $b = $x}"))).to eq([ "(call-method (. $a foo) (lambda (parameters x) (block", " (= $b $x)", ")))" ].join("\n")) end it "notice 42.type()" do expect(dump(parse("notice 42.type()"))).to eq('(invoke notice (call-method (. 42 type)))') end it 'notice Hash.assert_type(a => 42)' do expect(dump(parse('notice Hash.assert_type(a => 42)'))).to eq('(invoke notice (call-method (. Hash assert_type) ({} (a 42))))') end it "notice 42.type(detailed)" do expect(dump(parse("notice 42.type(detailed)"))).to eq('(invoke notice (call-method (. 42 type) detailed))') end it "is required to place the '(' on the same line as the name of the method to recognize it as arguments in call" do expect(dump(parse("notice 42.type\n(detailed)"))).to eq("(block\n (invoke notice (call-method (. 42 type)))\n detailed\n)") end end context "When parsing an illegal argument list" do it "raises an error if argument list is not for a call" do expect do parse("$a = 10, 3") end.to raise_error(/illegal comma/) end it "raises an error if argument list is for a potential call not allowed without parentheses" do expect do parse("foo 10, 3") end.to raise_error(/attempt to pass argument list to the function 'foo' which cannot be called without parentheses/) end it "does no raise an error for an argument list to an allowed call" do expect do parse("notice 10, 3") end.to_not raise_error() end end end puppet-5.5.10/spec/unit/pops/parser/parse_capabilities_spec.rb0000644005276200011600000000207613417161721024367 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' require_relative 'parser_rspec_helper' describe "egrammar parsing of capability mappings" do include ParserRspecHelper context "when parsing 'produces'" do it "the ast contains produces and attributes" do prog = "Foo produces Sql { name => value }" ast = "(produces Foo Sql ((name => value)))" expect(dump(parse(prog))).to eq(ast) end it "optional end comma is allowed" do prog = "Foo produces Sql { name => value, }" ast = "(produces Foo Sql ((name => value)))" expect(dump(parse(prog))).to eq(ast) end end context "when parsing 'consumes'" do it "the ast contains consumes and attributes" do prog = "Foo consumes Sql { name => value }" ast = "(consumes Foo Sql ((name => value)))" expect(dump(parse(prog))).to eq(ast) end it "optional end comma is allowed" do prog = "Foo consumes Sql { name => value, }" ast = "(consumes Foo Sql ((name => value)))" expect(dump(parse(prog))).to eq(ast) end end end puppet-5.5.10/spec/unit/pops/parser/parse_conditionals_spec.rb0000644005276200011600000001246513417161721024427 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' # relative to this spec file (./) does not work as this file is loaded by rspec require File.join(File.dirname(__FILE__), '/parser_rspec_helper') describe "egrammar parsing conditionals" do include ParserRspecHelper context "When parsing if statements" do it "if true { $a = 10 }" do expect(dump(parse("if true { $a = 10 }"))).to eq("(if true\n (then (= $a 10)))") end it "if true { $a = 10 } else {$a = 20}" do expect(dump(parse("if true { $a = 10 } else {$a = 20}"))).to eq( ["(if true", " (then (= $a 10))", " (else (= $a 20)))"].join("\n") ) end it "if true { $a = 10 } elsif false { $a = 15} else {$a = 20}" do expect(dump(parse("if true { $a = 10 } elsif false { $a = 15} else {$a = 20}"))).to eq( ["(if true", " (then (= $a 10))", " (else (if false", " (then (= $a 15))", " (else (= $a 20)))))"].join("\n") ) end it "if true { $a = 10 $b = 10 } else {$a = 20}" do expect(dump(parse("if true { $a = 10 $b = 20} else {$a = 20}"))).to eq([ "(if true", " (then (block", " (= $a 10)", " (= $b 20)", " ))", " (else (= $a 20)))" ].join("\n")) end it "allows a parenthesized conditional expression" do expect(dump(parse("if (true) { 10 }"))).to eq("(if true\n (then 10))") end it "allows a parenthesized elsif conditional expression" do expect(dump(parse("if true { 10 } elsif (false) { 20 }"))).to eq( ["(if true", " (then 10)", " (else (if false", " (then 20))))"].join("\n") ) end end context "When parsing unless statements" do it "unless true { $a = 10 }" do expect(dump(parse("unless true { $a = 10 }"))).to eq("(unless true\n (then (= $a 10)))") end it "unless true { $a = 10 } else {$a = 20}" do expect(dump(parse("unless true { $a = 10 } else {$a = 20}"))).to eq( ["(unless true", " (then (= $a 10))", " (else (= $a 20)))"].join("\n") ) end it "allows a parenthesized conditional expression" do expect(dump(parse("unless (true) { 10 }"))).to eq("(unless true\n (then 10))") end it "unless true { $a = 10 } elsif false { $a = 15} else {$a = 20} # is illegal" do expect { parse("unless true { $a = 10 } elsif false { $a = 15} else {$a = 20}")}.to raise_error(Puppet::ParseError) end end context "When parsing selector expressions" do it "$a = $b ? banana => fruit " do expect(dump(parse("$a = $b ? banana => fruit"))).to eq( "(= $a (? $b (banana => fruit)))" ) end it "$a = $b ? { banana => fruit}" do expect(dump(parse("$a = $b ? { banana => fruit }"))).to eq( "(= $a (? $b (banana => fruit)))" ) end it "does not fail on a trailing blank line" do expect(dump(parse("$a = $b ? { banana => fruit }\n\n"))).to eq( "(= $a (? $b (banana => fruit)))" ) end it "$a = $b ? { banana => fruit, grape => berry }" do expect(dump(parse("$a = $b ? {banana => fruit, grape => berry}"))).to eq( "(= $a (? $b (banana => fruit) (grape => berry)))" ) end it "$a = $b ? { banana => fruit, grape => berry, default => wat }" do expect(dump(parse("$a = $b ? {banana => fruit, grape => berry, default => wat}"))).to eq( "(= $a (? $b (banana => fruit) (grape => berry) (:default => wat)))" ) end it "$a = $b ? { default => wat, banana => fruit, grape => berry, }" do expect(dump(parse("$a = $b ? {default => wat, banana => fruit, grape => berry}"))).to eq( "(= $a (? $b (:default => wat) (banana => fruit) (grape => berry)))" ) end it '1+2 ? 3 => yes' do expect(dump(parse("1+2 ? 3 => yes"))).to eq( "(? (+ 1 2) (3 => yes))" ) end it 'true and 1+2 ? 3 => yes' do expect(dump(parse("true and 1+2 ? 3 => yes"))).to eq( "(&& true (? (+ 1 2) (3 => yes)))" ) end end context "When parsing case statements" do it "case $a { a : {}}" do expect(dump(parse("case $a { a : {}}"))).to eq( ["(case $a", " (when (a) (then ())))" ].join("\n") ) end it "allows a parenthesized value expression" do expect(dump(parse("case ($a) { a : {}}"))).to eq( ["(case $a", " (when (a) (then ())))" ].join("\n") ) end it "case $a { /.*/ : {}}" do expect(dump(parse("case $a { /.*/ : {}}"))).to eq( ["(case $a", " (when (/.*/) (then ())))" ].join("\n") ) end it "case $a { a, b : {}}" do expect(dump(parse("case $a { a, b : {}}"))).to eq( ["(case $a", " (when (a b) (then ())))" ].join("\n") ) end it "case $a { a, b : {} default : {}}" do expect(dump(parse("case $a { a, b : {} default : {}}"))).to eq( ["(case $a", " (when (a b) (then ()))", " (when (:default) (then ())))" ].join("\n") ) end it "case $a { a : {$b = 10 $c = 20}}" do expect(dump(parse("case $a { a : {$b = 10 $c = 20}}"))).to eq( ["(case $a", " (when (a) (then (block", " (= $b 10)", " (= $c 20)", " ))))" ].join("\n") ) end end end puppet-5.5.10/spec/unit/pops/parser/parse_containers_spec.rb0000644005276200011600000002222013417161721024074 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' # relative to this spec file (./) does not work as this file is loaded by rspec require File.join(File.dirname(__FILE__), '/parser_rspec_helper') describe "egrammar parsing containers" do include ParserRspecHelper context "When parsing file scope" do it "$a = 10 $b = 20" do expect(dump(parse("$a = 10 $b = 20"))).to eq([ "(block", " (= $a 10)", " (= $b 20)", ")" ].join("\n")) end it "$a = 10" do expect(dump(parse("$a = 10"))).to eq("(= $a 10)") end end context "When parsing class" do it "class foo {}" do expect(dump(parse("class foo {}"))).to eq("(class foo ())") end it "class foo { class bar {} }" do expect(dump(parse("class foo { class bar {}}"))).to eq([ "(class foo (block", " (class foo::bar ())", "))" ].join("\n")) end it "class foo::bar {}" do expect(dump(parse("class foo::bar {}"))).to eq("(class foo::bar ())") end it "class foo inherits bar {}" do expect(dump(parse("class foo inherits bar {}"))).to eq("(class foo (inherits bar) ())") end it "class foo($a) {}" do expect(dump(parse("class foo($a) {}"))).to eq("(class foo (parameters a) ())") end it "class foo($a, $b) {}" do expect(dump(parse("class foo($a, $b) {}"))).to eq("(class foo (parameters a b) ())") end it "class foo($a, $b=10) {}" do expect(dump(parse("class foo($a, $b=10) {}"))).to eq("(class foo (parameters a (= b 10)) ())") end it "class foo($a, $b) inherits belgo::bar {}" do expect(dump(parse("class foo($a, $b) inherits belgo::bar{}"))).to eq("(class foo (inherits belgo::bar) (parameters a b) ())") end it "class foo {$a = 10 $b = 20}" do expect(dump(parse("class foo {$a = 10 $b = 20}"))).to eq([ "(class foo (block", " (= $a 10)", " (= $b 20)", "))" ].join("\n")) end context "it should handle '3x weirdness'" do it "class class {} # a class named 'class'" do # Not as much weird as confusing that it is possible to name a class 'class'. Can have # a very confusing effect when resolving relative names, getting the global hardwired "Class" # instead of some foo::class etc. # This was allowed in 3.x. expect { parse("class class {}") }.to raise_error(/'class' keyword not allowed at this location/) end it "class default {} # a class named 'default'" do # The weirdness here is that a class can inherit 'default' but not declare a class called default. # (It will work with relative names i.e. foo::default though). The whole idea with keywords as # names is flawed to begin with - it generally just a very bad idea. expect { parse("class default {}") }.to raise_error(Puppet::ParseError) end it "class 'foo' {} # a string as class name" do # A common error is to put quotes around the class name, the parser should provide the error message at the right location # See PUP-7471 expect { parse("class 'foo' {}") }.to raise_error(/A quoted string is not valid as a name here \(line: 1, column: 7\)/) end it "class foo::default {} # a nested name 'default'" do expect(dump(parse("class foo::default {}"))).to eq("(class foo::default ())") end it "class class inherits default {} # inherits default", :broken => true do expect { parse("class class inherits default {}") }.to raise_error(/not a valid classname/) end it "class class inherits default {} # inherits default" do # TODO: See previous test marked as :broken=>true, it is actually this test (result) that is wacky, # this because a class is named at parse time (since class evaluation is lazy, the model must have the # full class name for nested classes - only, it gets this wrong when a class is named "class" - or at least # I think it is wrong.) # expect { parse("class class inherits default {}") }.to raise_error(/'class' keyword not allowed at this location/) end it "class foo inherits class" do expect { parse("class foo inherits class {}") }.to raise_error(/'class' keyword not allowed at this location/) end end context 'it should allow keywords as attribute names' do ['and', 'case', 'class', 'default', 'define', 'else', 'elsif', 'if', 'in', 'inherits', 'node', 'or', 'undef', 'unless', 'type', 'attr', 'function', 'private'].each do |keyword| it "such as #{keyword}" do expect { parse("class x ($#{keyword}){} class { x: #{keyword} => 1 }") }.to_not raise_error end end end end context "When the parser parses define" do it "define foo {}" do expect(dump(parse("define foo {}"))).to eq("(define foo ())") end it "class foo { define bar {}}" do expect(dump(parse("class foo {define bar {}}"))).to eq([ "(class foo (block", " (define foo::bar ())", "))" ].join("\n")) end it "define foo { define bar {}}" do # This is illegal, but handled as part of validation expect(dump(parse("define foo { define bar {}}"))).to eq([ "(define foo (block", " (define bar ())", "))" ].join("\n")) end it "define foo::bar {}" do expect(dump(parse("define foo::bar {}"))).to eq("(define foo::bar ())") end it "define foo($a) {}" do expect(dump(parse("define foo($a) {}"))).to eq("(define foo (parameters a) ())") end it "define foo($a, $b) {}" do expect(dump(parse("define foo($a, $b) {}"))).to eq("(define foo (parameters a b) ())") end it "define foo($a, $b=10) {}" do expect(dump(parse("define foo($a, $b=10) {}"))).to eq("(define foo (parameters a (= b 10)) ())") end it "define foo {$a = 10 $b = 20}" do expect(dump(parse("define foo {$a = 10 $b = 20}"))).to eq([ "(define foo (block", " (= $a 10)", " (= $b 20)", "))" ].join("\n")) end context "it should handle '3x weirdness'" do it "define class {} # a define named 'class'" do # This is weird because Class already exists, and instantiating this define will probably not # work expect { parse("define class {}") }.to raise_error(/'class' keyword not allowed at this location/) end it "define default {} # a define named 'default'" do # Check unwanted ability to define 'default'. # The expression below is not allowed (which is good). expect { parse("define default {}") }.to raise_error(Puppet::ParseError) end end context 'it should allow keywords as attribute names' do ['and', 'case', 'class', 'default', 'define', 'else', 'elsif', 'if', 'in', 'inherits', 'node', 'or', 'undef', 'unless', 'type', 'attr', 'function', 'private'].each do |keyword| it "such as #{keyword}" do expect {parse("define x ($#{keyword}){} x { y: #{keyword} => 1 }")}.to_not raise_error end end end end context "When parsing node" do it "node foo {}" do expect(dump(parse("node foo {}"))).to eq("(node (matches 'foo') ())") end it "node foo, {} # trailing comma" do expect(dump(parse("node foo, {}"))).to eq("(node (matches 'foo') ())") end it "node kermit.example.com {}" do expect(dump(parse("node kermit.example.com {}"))).to eq("(node (matches 'kermit.example.com') ())") end it "node kermit . example . com {}" do expect(dump(parse("node kermit . example . com {}"))).to eq("(node (matches 'kermit.example.com') ())") end it "node foo, x::bar, default {}" do expect(dump(parse("node foo, x::bar, default {}"))).to eq("(node (matches 'foo' 'x::bar' :default) ())") end it "node 'foo' {}" do expect(dump(parse("node 'foo' {}"))).to eq("(node (matches 'foo') ())") end it "node foo inherits x::bar {}" do expect(dump(parse("node foo inherits x::bar {}"))).to eq("(node (matches 'foo') (parent 'x::bar') ())") end it "node foo inherits 'bar' {}" do expect(dump(parse("node foo inherits 'bar' {}"))).to eq("(node (matches 'foo') (parent 'bar') ())") end it "node foo inherits default {}" do expect(dump(parse("node foo inherits default {}"))).to eq("(node (matches 'foo') (parent :default) ())") end it "node /web.*/ {}" do expect(dump(parse("node /web.*/ {}"))).to eq("(node (matches /web.*/) ())") end it "node /web.*/, /do\.wop.*/, and.so.on {}" do expect(dump(parse("node /web.*/, /do\.wop.*/, 'and.so.on' {}"))).to eq("(node (matches /web.*/ /do\.wop.*/ 'and.so.on') ())") end it "node wat inherits /apache.*/ {}" do expect(dump(parse("node wat inherits /apache.*/ {}"))).to eq("(node (matches 'wat') (parent /apache.*/) ())") end it "node foo inherits bar {$a = 10 $b = 20}" do expect(dump(parse("node foo inherits bar {$a = 10 $b = 20}"))).to eq([ "(node (matches 'foo') (parent 'bar') (block", " (= $a 10)", " (= $b 20)", "))" ].join("\n")) end end end puppet-5.5.10/spec/unit/pops/parser/parse_functions_spec.rb0000644005276200011600000000105113417161721023736 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' require_relative 'parser_rspec_helper' describe 'egrammar parsing function definitions' do include ParserRspecHelper context 'without return type' do it 'function foo() { 1 }' do expect(dump(parse('function foo() { 1 }'))).to eq("(function foo (block\n 1\n))") end end context 'with return type' do it 'function foo() >> Integer { 1 }' do expect(dump(parse('function foo() >> Integer { 1 }'))).to eq("(function foo (return_type Integer) (block\n 1\n))") end end end puppet-5.5.10/spec/unit/pops/parser/parse_heredoc_spec.rb0000644005276200011600000000747513417161721023357 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' # relative to this spec file (./) does not work as this file is loaded by rspec require File.join(File.dirname(__FILE__), '/parser_rspec_helper') describe "egrammar parsing heredoc" do include ParserRspecHelper it "parses plain heredoc" do expect(dump(parse("@(END)\nThis is\nheredoc text\nEND\n"))).to eq([ "(@()", " (sublocated 'This is\nheredoc text\n')", ")" ].join("\n")) end it "parses heredoc with margin" do src = [ "@(END)", " This is", " heredoc text", " | END", "" ].join("\n") expect(dump(parse(src))).to eq([ "(@()", " (sublocated 'This is\nheredoc text\n')", ")" ].join("\n")) end it "parses heredoc with margin and right newline trim" do src = [ "@(END)", " This is", " heredoc text", " |- END", "" ].join("\n") expect(dump(parse(src))).to eq([ "(@()", " (sublocated 'This is\nheredoc text')", ")" ].join("\n")) end it "parses syntax and escape specification" do src = <<-CODE @(END:syntax/t) Tex\\tt\\n |- END CODE expect(dump(parse(src))).to eq([ "(@(syntax)", " (sublocated 'Tex\tt\\n')", ")" ].join("\n")) end it "parses interpolated heredoc expression" do src = <<-CODE @("END") Hello $name |- END CODE expect(dump(parse(src))).to eq([ "(@()", " (sublocated (cat 'Hello ' (str $name)))", ")" ].join("\n")) end it "parses interpolated heredoc expression containing escapes" do src = <<-CODE @("END") Hello \\$name |- END CODE expect(dump(parse(src))).to eq([ "(@()", " (sublocated (cat 'Hello \\' (str $name)))", ")" ].join("\n")) end it "parses interpolated heredoc expression containing escapes when escaping other things than $" do src = <<-CODE @("END"/t) Hello \\$name |- END CODE expect(dump(parse(src))).to eq([ "(@()", " (sublocated (cat 'Hello \\' (str $name)))", ")" ].join("\n")) end it "parses with escaped newlines without preceding whitespace" do src = <<-CODE @(END/L) First Line\\ Second Line |- END CODE parse(src) expect(dump(parse(src))).to eq([ "(@()", " (sublocated 'First Line Second Line')", ")" ].join("\n")) end it "parses with escaped newlines with proper margin" do src = <<-CODE @(END/L) First Line\\ Second Line |- END CODE parse(src) expect(dump(parse(src))).to eq([ "(@()", " (sublocated ' First Line Second Line')", ")" ].join("\n")) end it "parses interpolated heredoc expression with false start on $" do src = <<-CODE @("END") Hello $name$%a |- END CODE expect(dump(parse(src))).to eq([ "(@()", " (sublocated (cat 'Hello ' (str $name) '$%a'))", ")" ].join("\n")) end it "parses interpolated [] expression by looking at the correct preceding char for space" do # NOTE: Important not to use the left margin feature here src = <<-CODE $xxxxxxx = @("END") ${facts['os']['family']} XXXXXXX XXX END CODE expect(dump(parse(src))).to eq([ "(= $xxxxxxx (@()", " (sublocated (cat (str (slice (slice $facts 'os') 'family')) '", "XXXXXXX XXX", "'))", "))"].join("\n")) end it 'parses multiple heredocs on the same line' do src = <<-CODE notice({ @(foo) => @(bar) }) hello -foo world -bar notice '!' CODE expect(dump(parse(src))).to eq([ '(block', ' (invoke notice ({} ((@()', ' (sublocated \' hello\')', ' ) (@()', ' (sublocated \' world\')', ' ))))', ' (invoke notice \'!\')', ')' ].join("\n")) end end puppet-5.5.10/spec/unit/pops/parser/parse_lambda_spec.rb0000644005276200011600000000106713417161721023155 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' require_relative 'parser_rspec_helper' describe 'egrammar parsing lambda definitions' do include ParserRspecHelper context 'without return type' do it 'f() |$x| { 1 }' do expect(dump(parse('f() |$x| { 1 }'))).to eq("(invoke f (lambda (parameters x) (block\n 1\n)))") end end context 'with return type' do it 'f() |$x| >> Integer { 1 }' do expect(dump(parse('f() |$x| >> Integer { 1 }'))).to eq("(invoke f (lambda (parameters x) (return_type Integer) (block\n 1\n)))") end end end puppet-5.5.10/spec/unit/pops/parser/parse_plan_spec.rb0000644005276200011600000000171013417161721022662 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' require_relative 'parser_rspec_helper' describe "egrammar parsing of 'plan'" do include ParserRspecHelper context 'with --tasks' do before(:each) do Puppet[:tasks] = true end it "an empty body" do expect(dump(parse("plan foo { }"))).to eq("(plan foo ())") end it "a non empty body" do prog = <<-EPROG plan foo { $a = 10 $b = 20 } EPROG expect(dump(parse(prog))).to eq( [ "(plan foo (block", " (= $a 10)", " (= $b 20)", "))", ].join("\n")) end it "accepts parameters" do s = "plan foo($p1 = 'yo', $p2) { }" expect(dump(parse(s))).to eq("(plan foo (parameters (= p1 'yo') p2) ())") end end context 'with --no-tasks' do before(:each) do Puppet[:tasks] = false end it "the keyword 'plan' is a name" do expect(dump(parse("$a = plan"))).to eq("(= $a plan)") end end end puppet-5.5.10/spec/unit/pops/parser/parse_resource_spec.rb0000644005276200011600000002442713417161721023571 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' # relative to this spec file (./) does not work as this file is loaded by rspec require File.join(File.dirname(__FILE__), '/parser_rspec_helper') describe "egrammar parsing resource declarations" do include ParserRspecHelper context "When parsing regular resource" do ["File", "file"].each do |word| it "#{word} { 'title': }" do expect(dump(parse("#{word} { 'title': }"))).to eq([ "(resource #{word}", " ('title'))" ].join("\n")) end it "#{word} { 'title': path => '/somewhere', mode => '0777'}" do expect(dump(parse("#{word} { 'title': path => '/somewhere', mode => '0777'}"))).to eq([ "(resource #{word}", " ('title'", " (path => '/somewhere')", " (mode => '0777')))" ].join("\n")) end it "#{word} { 'title': path => '/somewhere', }" do expect(dump(parse("#{word} { 'title': path => '/somewhere', }"))).to eq([ "(resource #{word}", " ('title'", " (path => '/somewhere')))" ].join("\n")) end it "#{word} { 'title': , }" do expect(dump(parse("#{word} { 'title': , }"))).to eq([ "(resource #{word}", " ('title'))" ].join("\n")) end it "#{word} { 'title': ; }" do expect(dump(parse("#{word} { 'title': ; }"))).to eq([ "(resource #{word}", " ('title'))" ].join("\n")) end it "#{word} { 'title': ; 'other_title': }" do expect(dump(parse("#{word} { 'title': ; 'other_title': }"))).to eq([ "(resource #{word}", " ('title')", " ('other_title'))" ].join("\n")) end # PUP-2898, trailing ';' it "#{word} { 'title': ; 'other_title': ; }" do expect(dump(parse("#{word} { 'title': ; 'other_title': ; }"))).to eq([ "(resource #{word}", " ('title')", " ('other_title'))" ].join("\n")) end it "#{word} { 'title1': path => 'x'; 'title2': path => 'y'}" do expect(dump(parse("#{word} { 'title1': path => 'x'; 'title2': path => 'y'}"))).to eq([ "(resource #{word}", " ('title1'", " (path => 'x'))", " ('title2'", " (path => 'y')))", ].join("\n")) end it "#{word} { title: * => {mode => '0777'} }" do expect(dump(parse("#{word} { title: * => {mode => '0777'}}"))).to eq([ "(resource #{word}", " (title", " (* => ({} (mode '0777')))))" ].join("\n")) end end end context "When parsing (type based) resource defaults" do it "File { }" do expect(dump(parse("File { }"))).to eq("(resource-defaults File)") end it "File { mode => '0777' }" do expect(dump(parse("File { mode => '0777'}"))).to eq([ "(resource-defaults File", " (mode => '0777'))" ].join("\n")) end it "File { * => {mode => '0777'} } (even if validated to be illegal)" do expect(dump(parse("File { * => {mode => '0777'}}"))).to eq([ "(resource-defaults File", " (* => ({} (mode '0777'))))" ].join("\n")) end end context "When parsing resource override" do it "File['x'] { }" do expect(dump(parse("File['x'] { }"))).to eq("(override (slice File 'x'))") end it "File['x'] { x => 1 }" do expect(dump(parse("File['x'] { x => 1}"))).to eq([ "(override (slice File 'x')", " (x => 1))" ].join("\n")) end it "File['x', 'y'] { x => 1 }" do expect(dump(parse("File['x', 'y'] { x => 1}"))).to eq([ "(override (slice File ('x' 'y'))", " (x => 1))" ].join("\n")) end it "File['x'] { x => 1, y => 2 }" do expect(dump(parse("File['x'] { x => 1, y=> 2}"))).to eq([ "(override (slice File 'x')", " (x => 1)", " (y => 2))" ].join("\n")) end it "File['x'] { x +> 1 }" do expect(dump(parse("File['x'] { x +> 1}"))).to eq([ "(override (slice File 'x')", " (x +> 1))" ].join("\n")) end it "File['x'] { * => {mode => '0777'} } (even if validated to be illegal)" do expect(dump(parse("File['x'] { * => {mode => '0777'}}"))).to eq([ "(override (slice File 'x')", " (* => ({} (mode '0777'))))" ].join("\n")) end end context "When parsing virtual and exported resources" do it "parses exported @@file { 'title': }" do expect(dump(parse("@@file { 'title': }"))).to eq("(exported-resource file\n ('title'))") end it "parses virtual @file { 'title': }" do expect(dump(parse("@file { 'title': }"))).to eq("(virtual-resource file\n ('title'))") end it "nothing before the title colon is a syntax error" do expect do parse("@file {: mode => '0777' }") end.to raise_error(/Syntax error/) end it "raises error for user error; not a resource" do # The expression results in VIRTUAL, CALL FUNCTION('file', HASH) since the resource body has # no title. expect do parse("@file { mode => '0777' }") end.to raise_error(/Virtual \(@\) can only be applied to a Resource Expression/) end it "parses global defaults with @ (even if validated to be illegal)" do expect(dump(parse("@File { mode => '0777' }"))).to eq([ "(virtual-resource-defaults File", " (mode => '0777'))" ].join("\n")) end it "parses global defaults with @@ (even if validated to be illegal)" do expect(dump(parse("@@File { mode => '0777' }"))).to eq([ "(exported-resource-defaults File", " (mode => '0777'))" ].join("\n")) end it "parses override with @ (even if validated to be illegal)" do expect(dump(parse("@File[foo] { mode => '0777' }"))).to eq([ "(virtual-override (slice File foo)", " (mode => '0777'))" ].join("\n")) end it "parses override combined with @@ (even if validated to be illegal)" do expect(dump(parse("@@File[foo] { mode => '0777' }"))).to eq([ "(exported-override (slice File foo)", " (mode => '0777'))" ].join("\n")) end end context "When parsing class resource" do it "class { 'cname': }" do expect(dump(parse("class { 'cname': }"))).to eq([ "(resource class", " ('cname'))" ].join("\n")) end it "@class { 'cname': }" do expect(dump(parse("@class { 'cname': }"))).to eq([ "(virtual-resource class", " ('cname'))" ].join("\n")) end it "@@class { 'cname': }" do expect(dump(parse("@@class { 'cname': }"))).to eq([ "(exported-resource class", " ('cname'))" ].join("\n")) end it "class { 'cname': x => 1, y => 2}" do expect(dump(parse("class { 'cname': x => 1, y => 2}"))).to eq([ "(resource class", " ('cname'", " (x => 1)", " (y => 2)))" ].join("\n")) end it "class { 'cname1': x => 1; 'cname2': y => 2}" do expect(dump(parse("class { 'cname1': x => 1; 'cname2': y => 2}"))).to eq([ "(resource class", " ('cname1'", " (x => 1))", " ('cname2'", " (y => 2)))", ].join("\n")) end end context "reported issues in 3.x" do it "should not screw up on brackets in title of resource #19632" do expect(dump(parse('notify { "thisisa[bug]": }'))).to eq([ "(resource notify", " ('thisisa[bug]'))", ].join("\n")) end end context "When parsing Relationships" do it "File[a] -> File[b]" do expect(dump(parse("File[a] -> File[b]"))).to eq("(-> (slice File a) (slice File b))") end it "File[a] <- File[b]" do expect(dump(parse("File[a] <- File[b]"))).to eq("(<- (slice File a) (slice File b))") end it "File[a] ~> File[b]" do expect(dump(parse("File[a] ~> File[b]"))).to eq("(~> (slice File a) (slice File b))") end it "File[a] <~ File[b]" do expect(dump(parse("File[a] <~ File[b]"))).to eq("(<~ (slice File a) (slice File b))") end it "Should chain relationships" do expect(dump(parse("a -> b -> c"))).to eq( "(-> (-> a b) c)" ) end it "Should chain relationships" do expect(dump(parse("File[a] -> File[b] ~> File[c] <- File[d] <~ File[e]"))).to eq( "(<~ (<- (~> (-> (slice File a) (slice File b)) (slice File c)) (slice File d)) (slice File e))" ) end it "should create relationships between collects" do expect(dump(parse("File <| mode == 0644 |> -> File <| mode == 0755 |>"))).to eq( "(-> (collect File\n (<| |> (== mode 0644))) (collect File\n (<| |> (== mode 0755))))" ) end end context "When parsing collection" do context "of virtual resources" do it "File <| |>" do expect(dump(parse("File <| |>"))).to eq("(collect File\n (<| |>))") end end context "of exported resources" do it "File <<| |>>" do expect(dump(parse("File <<| |>>"))).to eq("(collect File\n (<<| |>>))") end end context "queries are parsed with correct precedence" do it "File <| tag == 'foo' |>" do expect(dump(parse("File <| tag == 'foo' |>"))).to eq("(collect File\n (<| |> (== tag 'foo')))") end it "File <| tag == 'foo' and mode != '0777' |>" do expect(dump(parse("File <| tag == 'foo' and mode != '0777' |>"))).to eq("(collect File\n (<| |> (&& (== tag 'foo') (!= mode '0777'))))") end it "File <| tag == 'foo' or mode != '0777' |>" do expect(dump(parse("File <| tag == 'foo' or mode != '0777' |>"))).to eq("(collect File\n (<| |> (|| (== tag 'foo') (!= mode '0777'))))") end it "File <| tag == 'foo' or tag == 'bar' and mode != '0777' |>" do expect(dump(parse("File <| tag == 'foo' or tag == 'bar' and mode != '0777' |>"))).to eq( "(collect File\n (<| |> (|| (== tag 'foo') (&& (== tag 'bar') (!= mode '0777')))))" ) end it "File <| (tag == 'foo' or tag == 'bar') and mode != '0777' |>" do expect(dump(parse("File <| (tag == 'foo' or tag == 'bar') and mode != '0777' |>"))).to eq( "(collect File\n (<| |> (&& (|| (== tag 'foo') (== tag 'bar')) (!= mode '0777'))))" ) end end end end puppet-5.5.10/spec/unit/pops/parser/parse_site_spec.rb0000644005276200011600000000311713417161721022677 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' require_relative 'parser_rspec_helper' describe "egrammar parsing of site expression" do include ParserRspecHelper context "when parsing 'site'" do it "an empty body is allowed" do prog = "site { }" ast = "(site ())" expect(dump(parse(prog))).to eq(ast) end it "a body with one expression is allowed" do prog = "site { $x = 1 }" ast = "(site (block\n (= $x 1)\n))" expect(dump(parse(prog))).to eq(ast) end it "a body with more than one expression is allowed" do prog = "site { $x = 1 $y = 2}" ast = "(site (block\n (= $x 1)\n (= $y 2)\n))" expect(dump(parse(prog))).to eq(ast) end end context 'When parsing collections containing application management specific keywords' do %w(application site produces consumes).each do |keyword| it "allows the keyword '#{keyword}' in a list" do expect(dump(parse("$a = [#{keyword}]"))).to(eq("(= $a ([] '#{keyword}'))")) end it "allows the keyword '#{keyword}' as a key in a hash" do expect(dump(parse("$a = {#{keyword}=>'x'}"))).to(eq("(= $a ({} ('#{keyword}' 'x')))")) end it "allows the keyword '#{keyword}' as a value in a hash" do expect(dump(parse("$a = {'x'=>#{keyword}}"))).to(eq("(= $a ({} ('x' '#{keyword}')))")) end it "allows the keyword '#{keyword}' as an attribute name" do expect(dump(parse("foo { 'x': #{keyword} => 'value' } "))).to eql("(resource foo\n ('x'\n (#{keyword} => 'value')))") end end end end puppet-5.5.10/spec/unit/pops/parser/parser_rspec_helper.rb0000644005276200011600000000055513417161721023561 0ustar jenkinsjenkinsrequire 'puppet/pops' require File.join(File.dirname(__FILE__), '/../factory_rspec_helper') module ParserRspecHelper include FactoryRspecHelper def parse(code) parser = Puppet::Pops::Parser::Parser.new() parser.parse_string(code) end def parse_epp(code) parser = Puppet::Pops::Parser::EppParser.new() parser.parse_string(code) end end puppet-5.5.10/spec/unit/pops/parser/parser_spec.rb0000644005276200011600000001620013417161721022032 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' describe Puppet::Pops::Parser::Parser do it "should instantiate a parser" do parser = Puppet::Pops::Parser::Parser.new() expect(parser.class).to eq(Puppet::Pops::Parser::Parser) end it "should parse a code string and return a model" do parser = Puppet::Pops::Parser::Parser.new() model = parser.parse_string("$a = 10").model expect(model.class).to eq(Puppet::Pops::Model::Program) expect(model.body.class).to eq(Puppet::Pops::Model::AssignmentExpression) end it "should accept empty input and return a model" do parser = Puppet::Pops::Parser::Parser.new() model = parser.parse_string("").model expect(model.class).to eq(Puppet::Pops::Model::Program) expect(model.body.class).to eq(Puppet::Pops::Model::Nop) end it "should accept empty input containing only comments and report location at end of input" do parser = Puppet::Pops::Parser::Parser.new() model = parser.parse_string("# comment\n").model expect(model.class).to eq(Puppet::Pops::Model::Program) expect(model.body.class).to eq(Puppet::Pops::Model::Nop) expect(model.body.offset).to eq(10) expect(model.body.length).to eq(0) end it "should give single resource expressions the correct offset inside an if/else statement" do parser = Puppet::Pops::Parser::Parser.new() model = parser.parse_string(<<-EOF).model class firewall { if(true) { service { 'if service': ensure => stopped } } else { service { 'else service': ensure => running } } } EOF then_service = model.body.body.statements[0].then_expr expect(then_service.class).to eq(Puppet::Pops::Model::ResourceExpression) expect(then_service.offset).to eq(34) else_service = model.body.body.statements[0].else_expr expect(else_service.class).to eq(Puppet::Pops::Model::ResourceExpression) expect(else_service.offset).to eq(106) end it "should give block expressions and their contained resources the correct offset inside an if/else statement" do parser = Puppet::Pops::Parser::Parser.new() model = parser.parse_string(<<-EOF).model class firewall { if(true) { service { 'if service 1': ensure => running } service { 'if service 2': ensure => stopped } } else { service { 'else service 1': ensure => running } service { 'else service 2': ensure => stopped } } } EOF if_expr = model.body.body.statements[0] block_expr = model.body.body.statements[0].then_expr expect(if_expr.class).to eq(Puppet::Pops::Model::IfExpression) expect(if_expr.offset).to eq(19) expect(block_expr.class).to eq(Puppet::Pops::Model::BlockExpression) expect(block_expr.offset).to eq(28) expect(block_expr.statements[0].class).to eq(Puppet::Pops::Model::ResourceExpression) expect(block_expr.statements[0].offset).to eq(34) expect(block_expr.statements[1].class).to eq(Puppet::Pops::Model::ResourceExpression) expect(block_expr.statements[1].offset).to eq(98) block_expr = model.body.body.statements[0].else_expr expect(block_expr.class).to eq(Puppet::Pops::Model::BlockExpression) expect(block_expr.offset).to eq(166) expect(block_expr.statements[0].class).to eq(Puppet::Pops::Model::ResourceExpression) expect(block_expr.statements[0].offset).to eq(172) expect(block_expr.statements[1].class).to eq(Puppet::Pops::Model::ResourceExpression) expect(block_expr.statements[1].offset).to eq(238) end it "should give single resource expressions the correct offset inside an unless/else statement" do parser = Puppet::Pops::Parser::Parser.new() model = parser.parse_string(<<-EOF).model class firewall { unless(true) { service { 'if service': ensure => stopped } } else { service { 'else service': ensure => running } } } EOF then_service = model.body.body.statements[0].then_expr expect(then_service.class).to eq(Puppet::Pops::Model::ResourceExpression) expect(then_service.offset).to eq(38) else_service = model.body.body.statements[0].else_expr expect(else_service.class).to eq(Puppet::Pops::Model::ResourceExpression) expect(else_service.offset).to eq(110) end it "should give block expressions and their contained resources the correct offset inside an unless/else statement" do parser = Puppet::Pops::Parser::Parser.new() model = parser.parse_string(<<-EOF).model class firewall { unless(true) { service { 'if service 1': ensure => running } service { 'if service 2': ensure => stopped } } else { service { 'else service 1': ensure => running } service { 'else service 2': ensure => stopped } } } EOF if_expr = model.body.body.statements[0] block_expr = model.body.body.statements[0].then_expr expect(if_expr.class).to eq(Puppet::Pops::Model::UnlessExpression) expect(if_expr.offset).to eq(19) expect(block_expr.class).to eq(Puppet::Pops::Model::BlockExpression) expect(block_expr.offset).to eq(32) expect(block_expr.statements[0].class).to eq(Puppet::Pops::Model::ResourceExpression) expect(block_expr.statements[0].offset).to eq(38) expect(block_expr.statements[1].class).to eq(Puppet::Pops::Model::ResourceExpression) expect(block_expr.statements[1].offset).to eq(102) block_expr = model.body.body.statements[0].else_expr expect(block_expr.class).to eq(Puppet::Pops::Model::BlockExpression) expect(block_expr.offset).to eq(170) expect(block_expr.statements[0].class).to eq(Puppet::Pops::Model::ResourceExpression) expect(block_expr.statements[0].offset).to eq(176) expect(block_expr.statements[1].class).to eq(Puppet::Pops::Model::ResourceExpression) expect(block_expr.statements[1].offset).to eq(242) end it "multi byte characters in a comment are counted as individual bytes" do parser = Puppet::Pops::Parser::Parser.new() model = parser.parse_string("# \u{0400}comment\n").model expect(model.class).to eq(Puppet::Pops::Model::Program) expect(model.body.class).to eq(Puppet::Pops::Model::Nop) expect(model.body.offset).to eq(12) expect(model.body.length).to eq(0) end it "should raise an error with position information when error is raised from within parser" do parser = Puppet::Pops::Parser::Parser.new() the_error = nil begin parser.parse_string("File [1] { }", 'fakefile.pp') rescue Puppet::ParseError => e the_error = e end expect(the_error).to be_a(Puppet::ParseError) expect(the_error.file).to eq('fakefile.pp') expect(the_error.line).to eq(1) expect(the_error.pos).to eq(6) end it "should raise an error with position information when error is raised on token" do parser = Puppet::Pops::Parser::Parser.new() the_error = nil begin parser.parse_string(<<-EOF, 'fakefile.pp') class whoops($a,$b,$c) { $d = "oh noes", "b" } EOF rescue Puppet::ParseError => e the_error = e end expect(the_error).to be_a(Puppet::ParseError) expect(the_error.file).to eq('fakefile.pp') expect(the_error.line).to eq(2) expect(the_error.pos).to eq(17) end end puppet-5.5.10/spec/unit/pops/parser/parsing_typed_parameters_spec.rb0000644005276200011600000000352313417161721025635 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' require 'puppet/pops/evaluator/evaluator_impl' require 'puppet_spec/pops' require 'puppet_spec/scope' require 'puppet/parser/e4_parser_adapter' # relative to this spec file (./) does not work as this file is loaded by rspec #require File.join(File.dirname(__FILE__), '/evaluator_rspec_helper') describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do include PuppetSpec::Pops include PuppetSpec::Scope let(:parser) { Puppet::Pops::Parser::EvaluatingParser.new } context "captures-rest parameter" do it 'is allowed in lambda when placed last' do source = <<-CODE foo() |$a, *$b| { $a + $b[0] } CODE expect do parser.parse_string(source, __FILE__) end.to_not raise_error() end it 'allows a type annotation' do source = <<-CODE foo() |$a, Integer *$b| { $a + $b[0] } CODE expect do parser.parse_string(source, __FILE__) end.to_not raise_error() end it 'is not allowed in lambda except last' do source = <<-CODE foo() |*$a, $b| { $a + $b[0] } CODE expect do parser.parse_string(source, __FILE__) end.to raise_error(Puppet::ParseError, /Parameter \$a is not last, and has 'captures rest'/) end it 'is not allowed in define' do source = <<-CODE define foo(*$a) { } CODE expect do parser.parse_string(source, __FILE__) end.to raise_error(Puppet::ParseError, /Parameter \$a has 'captures rest' - not supported in a 'define'/) end it 'is not allowed in class' do source = <<-CODE class foo(*$a) { } CODE expect do parser.parse_string(source, __FILE__) end.to raise_error(Puppet::ParseError, /Parameter \$a has 'captures rest' - not supported in a Host Class Definition/) end end end puppet-5.5.10/spec/unit/pops/parser/pn_parser_spec.rb0000644005276200011600000000536313417161721022537 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' require 'puppet/pops/pn' module Puppet::Pops module Parser describe 'Puppet::Pops::Parser::PNParser' do context 'parses the text' do it '"true" to PN::Literal(true)' do expect(PNParser.new.parse('true')).to eql(lit(true)) end it '"false" to PN::Literal(false)' do expect(PNParser.new.parse('false')).to eql(lit(false)) end it '"nil" to PN::Literal(nil)' do expect(PNParser.new.parse('nil')).to eql(lit(nil)) end it '"123" to PN::Literal(123)' do expect(PNParser.new.parse('123')).to eql(lit(123)) end it '"-123" to PN::Literal(-123)' do expect(PNParser.new.parse('-123')).to eql(lit(-123)) end it '"123.45" to PN::Literal(123.45)' do expect(PNParser.new.parse('123.45')).to eql(lit(123.45)) end it '"-123.45" to PN::Literal(-123.45)' do expect(PNParser.new.parse('-123.45')).to eql(lit(-123.45)) end it '"123.45e12" to PN::Literal(123.45e12)' do expect(PNParser.new.parse('123.45e12')).to eql(lit(123.45e12)) end it '"123.45e+12" to PN::Literal(123.45e+12)' do expect(PNParser.new.parse('123.45e+12')).to eql(lit(123.45e+12)) end it '"123.45e-12" to PN::Literal(123.45e-12)' do expect(PNParser.new.parse('123.45e-12')).to eql(lit(123.45e-12)) end it '"hello" to PN::Literal("hello")' do expect(PNParser.new.parse('"hello"')).to eql(lit('hello')) end it '"\t" to PN::Literal("\t")' do expect(PNParser.new.parse('"\t"')).to eql(lit("\t")) end it '"\r" to PN::Literal("\r")' do expect(PNParser.new.parse('"\r"')).to eql(lit("\r")) end it '"\n" to PN::Literal("\n")' do expect(PNParser.new.parse('"\n"')).to eql(lit("\n")) end it '"\"" to PN::Literal("\"")' do expect(PNParser.new.parse('"\""')).to eql(lit('"')) end it '"\\\\" to PN::Literal("\\")' do expect(PNParser.new.parse('"\\\\"')).to eql(lit("\\")) end it '"\o024" to PN::Literal("\u{14}")' do expect(PNParser.new.parse('"\o024"')).to eql(lit("\u{14}")) end end it 'parses elements enclosed in brackets to a PN::List' do expect(PNParser.new.parse('[1 "2" true false nil]')).to eql(PN::List.new([lit(1), lit('2'), lit(true), lit(false), lit(nil)])) end it 'parses elements enclosed in parenthesis to a PN::Call' do expect(PNParser.new.parse('(+ 1 2)')).to eql(PN::Call.new('+', lit(1), lit(2))) end it 'parses entries enclosed in curly braces to a PN::Map' do expect(PNParser.new.parse('{:a 1 :b "2" :c true}')).to eql(PN::Map.new([entry('a', 1), entry('b', '2'), entry('c', true)])) end def entry(k, v) PN::Entry.new(k, lit(v)) end def lit(v) PN::Literal.new(v) end end end end puppet-5.5.10/spec/unit/pops/parser/lexer2_spec.rb0000644005276200011600000007502213417161722021747 0ustar jenkinsjenkinsrequire 'spec_helper' require 'matchers/match_tokens2' require 'puppet/pops' require 'puppet/pops/parser/lexer2' module EgrammarLexer2Spec def tokens_scanned_from(s) lexer = Puppet::Pops::Parser::Lexer2.new lexer.string = s lexer.fullscan[0..-2] end def epp_tokens_scanned_from(s) lexer = Puppet::Pops::Parser::Lexer2.new lexer.string = s lexer.fullscan_epp[0..-2] end end describe 'Lexer2' do include EgrammarLexer2Spec { :LISTSTART => '[', :RBRACK => ']', :LBRACE => '{', :RBRACE => '}', :WSLPAREN => '(', # since it is first on a line it is special (LPAREN handled separately) :RPAREN => ')', :EQUALS => '=', :ISEQUAL => '==', :GREATEREQUAL => '>=', :GREATERTHAN => '>', :LESSTHAN => '<', :LESSEQUAL => '<=', :NOTEQUAL => '!=', :NOT => '!', :COMMA => ',', :DOT => '.', :COLON => ':', :AT => '@', :LLCOLLECT => '<<|', :RRCOLLECT => '|>>', :LCOLLECT => '<|', :RCOLLECT => '|>', :SEMIC => ';', :QMARK => '?', :OTHER => '\\', :FARROW => '=>', :PARROW => '+>', :APPENDS => '+=', :DELETES => '-=', :PLUS => '+', :MINUS => '-', :DIV => '/', :TIMES => '*', :LSHIFT => '<<', :RSHIFT => '>>', :MATCH => '=~', :NOMATCH => '!~', :IN_EDGE => '->', :OUT_EDGE => '<-', :IN_EDGE_SUB => '~>', :OUT_EDGE_SUB => '<~', :PIPE => '|', }.each do |name, string| it "should lex a token named #{name.to_s}" do expect(tokens_scanned_from(string)).to match_tokens2(name) end end it "should lex [ in position after non whitespace as LBRACK" do expect(tokens_scanned_from("a[")).to match_tokens2(:NAME, :LBRACK) end { "case" => :CASE, "class" => :CLASS, "default" => :DEFAULT, "define" => :DEFINE, # "import" => :IMPORT, # done as a function in egrammar "if" => :IF, "elsif" => :ELSIF, "else" => :ELSE, "inherits" => :INHERITS, "node" => :NODE, "and" => :AND, "or" => :OR, "undef" => :UNDEF, "false" => :BOOLEAN, "true" => :BOOLEAN, "in" => :IN, "unless" => :UNLESS, "private" => :PRIVATE, "type" => :TYPE, "attr" => :ATTR, "application" => :APPLICATION, "consumes" => :CONSUMES, "produces" => :PRODUCES, "site" => :SITE, }.each do |string, name| it "should lex a keyword from '#{string}'" do expect(tokens_scanned_from(string)).to match_tokens2(name) end end context 'when --no-tasks (the default)' do it "should lex a NAME from 'plan'" do expect(tokens_scanned_from('plan')).to match_tokens2(:NAME) end end context 'when --tasks' do before(:each) { Puppet[:tasks] = true } after(:each) { Puppet[:tasks] = false } it "should lex a keyword from 'plan'" do expect(tokens_scanned_from('plan')).to match_tokens2(:PLAN) end end # TODO: Complete with all edge cases [ 'A', 'A::B', '::A', '::A::B',].each do |string| it "should lex a CLASSREF on the form '#{string}'" do expect(tokens_scanned_from(string)).to match_tokens2([:CLASSREF, string]) end end # TODO: Complete with all edge cases [ 'a', 'a::b', '::a', '::a::b',].each do |string| it "should lex a NAME on the form '#{string}'" do expect(tokens_scanned_from(string)).to match_tokens2([:NAME, string]) end end [ 'a-b', 'a--b', 'a-b-c', '_x'].each do |string| it "should lex a BARE WORD STRING on the form '#{string}'" do expect(tokens_scanned_from(string)).to match_tokens2([:WORD, string]) end end [ '_x::y', 'x::_y'].each do |string| it "should consider the bare word '#{string}' to be a WORD" do expect(tokens_scanned_from(string)).to match_tokens2(:WORD) end end { '-a' => [:MINUS, :NAME], '--a' => [:MINUS, :MINUS, :NAME], 'a-' => [:NAME, :MINUS], 'a- b' => [:NAME, :MINUS, :NAME], 'a--' => [:NAME, :MINUS, :MINUS], 'a-$3' => [:NAME, :MINUS, :VARIABLE], }.each do |source, expected| it "should lex leading and trailing hyphens from #{source}" do expect(tokens_scanned_from(source)).to match_tokens2(*expected) end end { 'false'=>false, 'true'=>true}.each do |string, value| it "should lex a BOOLEAN on the form '#{string}'" do expect(tokens_scanned_from(string)).to match_tokens2([:BOOLEAN, value]) end end [ '0', '1', '2982383139'].each do |string| it "should lex a decimal integer NUMBER on the form '#{string}'" do expect(tokens_scanned_from(string)).to match_tokens2([:NUMBER, string]) end end { ' 1' => '1', '1 ' => '1', ' 1 ' => '1'}.each do |string, value| it "should lex a NUMBER with surrounding space '#{string}'" do expect(tokens_scanned_from(string)).to match_tokens2([:NUMBER, value]) end end [ '0.0', '0.1', '0.2982383139', '29823.235', '10e23', '10e-23', '1.234e23'].each do |string| it "should lex a decimal floating point NUMBER on the form '#{string}'" do expect(tokens_scanned_from(string)).to match_tokens2([:NUMBER, string]) end end [ '00', '01', '0123', '0777'].each do |string| it "should lex an octal integer NUMBER on the form '#{string}'" do expect(tokens_scanned_from(string)).to match_tokens2([:NUMBER, string]) end end [ '0x0', '0x1', '0xa', '0xA', '0xabcdef', '0xABCDEF'].each do |string| it "should lex an hex integer NUMBER on the form '#{string}'" do expect(tokens_scanned_from(string)).to match_tokens2([:NUMBER, string]) end end { "''" => '', "'a'" => 'a', "'a\\'b'" =>"a'b", "'a\\rb'" =>"a\\rb", "'a\\nb'" =>"a\\nb", "'a\\tb'" =>"a\\tb", "'a\\sb'" =>"a\\sb", "'a\\$b'" =>"a\\$b", "'a\\\"b'" =>"a\\\"b", "'a\\\\b'" =>"a\\b", "'a\\\\'" =>"a\\", }.each do |source, expected| it "should lex a single quoted STRING on the form #{source}" do expect(tokens_scanned_from(source)).to match_tokens2([:STRING, expected]) end end { "''" => [2, ""], "'a'" => [3, "a"], "'a\\'b'" => [6, "a'b"], }.each do |source, expected| it "should lex a single quoted STRING on the form #{source} as having length #{expected[0]}" do length, value = expected expect(tokens_scanned_from(source)).to match_tokens2([:STRING, value, {:line => 1, :pos=>1, :length=> length}]) end end { '""' => '', '"a"' => 'a', '"a\'b"' => "a'b", }.each do |source, expected| it "should lex a double quoted STRING on the form #{source}" do expect(tokens_scanned_from(source)).to match_tokens2([:STRING, expected]) end end { '"a$x b"' => [[:DQPRE, 'a', {:line => 1, :pos=>1, :length=>2 }], [:VARIABLE, 'x', {:line => 1, :pos=>3, :length=>2 }], [:DQPOST, ' b', {:line => 1, :pos=>5, :length=>3 }]], '"a$x.b"' => [[:DQPRE, 'a', {:line => 1, :pos=>1, :length=>2 }], [:VARIABLE, 'x', {:line => 1, :pos=>3, :length=>2 }], [:DQPOST, '.b', {:line => 1, :pos=>5, :length=>3 }]], '"$x.b"' => [[:DQPRE, '', {:line => 1, :pos=>1, :length=>1 }], [:VARIABLE, 'x', {:line => 1, :pos=>2, :length=>2 }], [:DQPOST, '.b', {:line => 1, :pos=>4, :length=>3 }]], '"a$x"' => [[:DQPRE, 'a', {:line => 1, :pos=>1, :length=>2 }], [:VARIABLE, 'x', {:line => 1, :pos=>3, :length=>2 }], [:DQPOST, '', {:line => 1, :pos=>5, :length=>1 }]], '"a${x}"' => [[:DQPRE, 'a', {:line => 1, :pos=>1, :length=>4 }], [:VARIABLE, 'x', {:line => 1, :pos=>5, :length=>1 }], [:DQPOST, '', {:line => 1, :pos=>7, :length=>1 }]], '"a${_x}"' => [[:DQPRE, 'a', {:line => 1, :pos=>1, :length=>4 }], [:VARIABLE, '_x', {:line => 1, :pos=>5, :length=>2 }], [:DQPOST, '', {:line => 1, :pos=>8, :length=>1 }]], '"a${y::_x}"' => [[:DQPRE, 'a', {:line => 1, :pos=>1, :length=>4 }], [:VARIABLE, 'y::_x', {:line => 1, :pos=>5, :length=>5 }], [:DQPOST, '', {:line => 1, :pos=>11, :length=>1 }]], '"a${_x[1]}"' => [[:DQPRE, 'a', {:line => 1, :pos=>1, :length=>4 }], [:VARIABLE, '_x', {:line => 1, :pos=>5, :length=>2 }], [:LBRACK, '[', {:line => 1, :pos=>7, :length=>1 }], [:NUMBER, '1', {:line => 1, :pos=>8, :length=>1 }], [:RBRACK, ']', {:line => 1, :pos=>9, :length=>1 }], [:DQPOST, '', {:line => 1, :pos=>11, :length=>1 }]], '"a${_x.foo}"'=> [[:DQPRE, 'a', {:line => 1, :pos=>1, :length=>4 }], [:VARIABLE, '_x', {:line => 1, :pos=>5, :length=>2 }], [:DOT, '.', {:line => 1, :pos=>7, :length=>1 }], [:NAME, 'foo', {:line => 1, :pos=>8, :length=>3 }], [:DQPOST, '', {:line => 1, :pos=>12, :length=>1 }]], }.each do |source, expected| it "should lex an interpolated variable 'x' from #{source}" do expect(tokens_scanned_from(source)).to match_tokens2(*expected) end end { '"$"' => '$', '"a$"' => 'a$', '"a$%b"' => "a$%b", '"a$$"' => "a$$", '"a$$%"' => "a$$%", }.each do |source, expected| it "should lex interpolation including false starts #{source}" do expect(tokens_scanned_from(source)).to match_tokens2([:STRING, expected]) end end it "differentiates between foo[x] and foo [x] (whitespace)" do expect(tokens_scanned_from("$a[1]")).to match_tokens2(:VARIABLE, :LBRACK, :NUMBER, :RBRACK) expect(tokens_scanned_from("$a [1]")).to match_tokens2(:VARIABLE, :LISTSTART, :NUMBER, :RBRACK) expect(tokens_scanned_from("a[1]")).to match_tokens2(:NAME, :LBRACK, :NUMBER, :RBRACK) expect(tokens_scanned_from("a [1]")).to match_tokens2(:NAME, :LISTSTART, :NUMBER, :RBRACK) end it "differentiates between '(' first on line, and not first on line" do expect(tokens_scanned_from("(")).to match_tokens2(:WSLPAREN) expect(tokens_scanned_from("\n(")).to match_tokens2(:WSLPAREN) expect(tokens_scanned_from("\n\r(")).to match_tokens2(:WSLPAREN) expect(tokens_scanned_from("\n\t(")).to match_tokens2(:WSLPAREN) expect(tokens_scanned_from("\n\r\t(")).to match_tokens2(:WSLPAREN) expect(tokens_scanned_from("\n\u00a0(")).to match_tokens2(:WSLPAREN) expect(tokens_scanned_from("x(")).to match_tokens2(:NAME, :LPAREN) expect(tokens_scanned_from("\nx(")).to match_tokens2(:NAME, :LPAREN) expect(tokens_scanned_from("\n\rx(")).to match_tokens2(:NAME, :LPAREN) expect(tokens_scanned_from("\n\tx(")).to match_tokens2(:NAME, :LPAREN) expect(tokens_scanned_from("\n\r\tx(")).to match_tokens2(:NAME, :LPAREN) expect(tokens_scanned_from("\n\u00a0x(")).to match_tokens2(:NAME, :LPAREN) expect(tokens_scanned_from("x (")).to match_tokens2(:NAME, :LPAREN) expect(tokens_scanned_from("x\t(")).to match_tokens2(:NAME, :LPAREN) expect(tokens_scanned_from("x\u00a0(")).to match_tokens2(:NAME, :LPAREN) end it "skips whitepsace" do expect(tokens_scanned_from(" if if if ")).to match_tokens2(:IF, :IF, :IF) expect(tokens_scanned_from(" if \n\r\t\nif if ")).to match_tokens2(:IF, :IF, :IF) expect(tokens_scanned_from(" if \n\r\t\n\u00a0if\u00a0 if ")).to match_tokens2(:IF, :IF, :IF) end it "skips single line comments" do expect(tokens_scanned_from("if # comment\nif")).to match_tokens2(:IF, :IF) end ["if /* comment */\nif", "if /* comment\n */\nif", "if /*\n comment\n */\nif", ].each do |source| it "skips multi line comments" do expect(tokens_scanned_from(source)).to match_tokens2(:IF, :IF) end end it 'detects unterminated multiline comment' do expect { tokens_scanned_from("/* not terminated\nmultiline\ncomment") }.to raise_error(Puppet::ParseErrorWithIssue) { |e| expect(e.issue_code).to be(Puppet::Pops::Issues::UNCLOSED_MLCOMMENT.issue_code) } end { "=~" => [:MATCH, "=~ /./"], "!~" => [:NOMATCH, "!~ /./"], "," => [:COMMA, ", /./"], "(" => [:WSLPAREN, "( /./"], "x (" => [[:NAME, :LPAREN], "x ( /./"], "x\\t (" => [[:NAME, :LPAREN], "x\t ( /./"], "[ (liststart)" => [:LISTSTART, "[ /./"], "[ (LBRACK)" => [[:NAME, :LBRACK], "a[ /./"], "[ (liststart after name)" => [[:NAME, :LISTSTART], "a [ /./"], "{" => [:LBRACE, "{ /./"], "+" => [:PLUS, "+ /./"], "-" => [:MINUS, "- /./"], "*" => [:TIMES, "* /./"], ";" => [:SEMIC, "; /./"], }.each do |token, entry| it "should lex regexp after '#{token}'" do expected = [entry[0], :REGEX].flatten expect(tokens_scanned_from(entry[1])).to match_tokens2(*expected) end end it "should lex a simple expression" do expect(tokens_scanned_from('1 + 1')).to match_tokens2([:NUMBER, '1'], :PLUS, [:NUMBER, '1']) end { "1" => ["1 /./", [:NUMBER, :DIV, :DOT, :DIV]], "'a'" => ["'a' /./", [:STRING, :DIV, :DOT, :DIV]], "true" => ["true /./", [:BOOLEAN, :DIV, :DOT, :DIV]], "false" => ["false /./", [:BOOLEAN, :DIV, :DOT, :DIV]], "/./" => ["/./ /./", [:REGEX, :DIV, :DOT, :DIV]], "a" => ["a /./", [:NAME, :DIV, :DOT, :DIV]], "A" => ["A /./", [:CLASSREF, :DIV, :DOT, :DIV]], ")" => [") /./", [:RPAREN, :DIV, :DOT, :DIV]], "]" => ["] /./", [:RBRACK, :DIV, :DOT, :DIV]], "|>" => ["|> /./", [:RCOLLECT, :DIV, :DOT, :DIV]], "|>>" => ["|>> /./", [:RRCOLLECT, :DIV, :DOT, :DIV]], "$x" => ["$x /1/", [:VARIABLE, :DIV, :NUMBER, :DIV]], "a-b" => ["a-b /1/", [:WORD, :DIV, :NUMBER, :DIV]], '"a$a"' => ['"a$a" /./', [:DQPRE, :VARIABLE, :DQPOST, :DIV, :DOT, :DIV]], }.each do |token, entry| it "should not lex regexp after '#{token}'" do expect(tokens_scanned_from(entry[ 0 ])).to match_tokens2(*entry[ 1 ]) end end it 'should lex assignment' do expect(tokens_scanned_from("$a = 10")).to match_tokens2([:VARIABLE, "a"], :EQUALS, [:NUMBER, '10']) end # TODO: Tricky, and heredoc not supported yet # it "should not lex regexp after heredoc" do # tokens_scanned_from("1 / /./").should match_tokens2(:NUMBER, :DIV, :REGEX) # end it "should lex regexp at beginning of input" do expect(tokens_scanned_from(" /./")).to match_tokens2(:REGEX) end it "should lex regexp right of div" do expect(tokens_scanned_from("1 / /./")).to match_tokens2(:NUMBER, :DIV, :REGEX) end it 'should lex regexp with escaped slash' do scanned = tokens_scanned_from('/\//') expect(scanned).to match_tokens2(:REGEX) expect(scanned[0][1][:value]).to eql(Regexp.new('/')) end it 'should lex regexp with escaped backslash' do scanned = tokens_scanned_from('/\\\\/') expect(scanned).to match_tokens2(:REGEX) expect(scanned[0][1][:value]).to eql(Regexp.new('\\\\')) end it 'should lex regexp with escaped backslash followed escaped slash ' do scanned = tokens_scanned_from('/\\\\\\//') expect(scanned).to match_tokens2(:REGEX) expect(scanned[0][1][:value]).to eql(Regexp.new('\\\\/')) end it 'should lex regexp with escaped slash followed escaped backslash ' do scanned = tokens_scanned_from('/\\/\\\\/') expect(scanned).to match_tokens2(:REGEX) expect(scanned[0][1][:value]).to eql(Regexp.new('/\\\\')) end it 'should not lex regexp with escaped ending slash' do expect(tokens_scanned_from('/\\/')).to match_tokens2(:DIV, :OTHER, :DIV) end it "should accept newline in a regular expression" do scanned = tokens_scanned_from("/\n.\n/") # Note that strange formatting here is important expect(scanned[0][1][:value]).to eql(/ . /) end context 'when lexer lexes heredoc' do it 'lexes tag, syntax and escapes, margin and right trim' do code = <<-CODE @(END:syntax/t) Tex\\tt\\n |- END CODE expect(tokens_scanned_from(code)).to match_tokens2([:HEREDOC, 'syntax'], :SUBLOCATE, [:STRING, "Tex\tt\\n"]) end it 'lexes "tag", syntax and escapes, margin, right trim and interpolation' do code = <<-CODE @("END":syntax/t) Tex\\tt\\n$var After |- END CODE expect(tokens_scanned_from(code)).to match_tokens2( [:HEREDOC, 'syntax'], :SUBLOCATE, [:DQPRE, "Tex\tt\\n"], [:VARIABLE, "var"], [:DQPOST, " After"] ) end it 'strips only last newline when using trim option' do code = <<-CODE.unindent @(END) Line 1 Line 2 -END CODE expect(tokens_scanned_from(code)).to match_tokens2( [:HEREDOC, ''], [:SUBLOCATE, ["Line 1\n", "\n", "Line 2\n"]], [:STRING, "Line 1\n\nLine 2"], ) end it 'strips only one newline at the end when using trim option' do code = <<-CODE.unindent @(END) Line 1 Line 2 -END CODE expect(tokens_scanned_from(code)).to match_tokens2( [:HEREDOC, ''], [:SUBLOCATE, ["Line 1\n", "Line 2\n", "\n"]], [:STRING, "Line 1\nLine 2\n"], ) end context 'with bad syntax' do def expect_issue(code, issue) expect { tokens_scanned_from(code) }.to raise_error(Puppet::ParseErrorWithIssue) { |e| expect(e.issue_code).to be(issue.issue_code) } end it 'detects and reports HEREDOC_UNCLOSED_PARENTHESIS' do code = <<-CODE @(END:syntax/t Text |- END CODE expect_issue(code, Puppet::Pops::Issues::HEREDOC_UNCLOSED_PARENTHESIS) end it 'detects and reports HEREDOC_WITHOUT_END_TAGGED_LINE' do code = <<-CODE @(END:syntax/t) Text CODE expect_issue(code, Puppet::Pops::Issues::HEREDOC_WITHOUT_END_TAGGED_LINE) end it 'detects and reports HEREDOC_INVALID_ESCAPE' do code = <<-CODE @(END:syntax/x) Text |- END CODE expect_issue(code, Puppet::Pops::Issues::HEREDOC_INVALID_ESCAPE) end it 'detects and reports HEREDOC_INVALID_SYNTAX' do code = <<-CODE @(END:syntax/t/p) Text |- END CODE expect_issue(code, Puppet::Pops::Issues::HEREDOC_INVALID_SYNTAX) end it 'detects and reports HEREDOC_WITHOUT_TEXT' do code = '@(END:syntax/t)' expect_issue(code, Puppet::Pops::Issues::HEREDOC_WITHOUT_TEXT) end it 'detects and reports HEREDOC_EMPTY_ENDTAG' do code = <<-CODE @("") Text |-END CODE expect_issue(code, Puppet::Pops::Issues::HEREDOC_EMPTY_ENDTAG) end it 'detects and reports HEREDOC_MULTIPLE_AT_ESCAPES' do code = <<-CODE @(END:syntax/tst) Tex\\tt\\n |- END CODE expect_issue(code, Puppet::Pops::Issues::HEREDOC_MULTIPLE_AT_ESCAPES) end end end context 'when not given multi byte characters' do it 'produces byte offsets for tokens' do code = <<-"CODE" 1 2\n3 CODE expect(tokens_scanned_from(code)).to match_tokens2( [:NUMBER, '1', {:line => 1, :offset => 0, :length=>1}], [:NUMBER, '2', {:line => 1, :offset => 2, :length=>1}], [:NUMBER, '3', {:line => 2, :offset => 4, :length=>1}] ) end end context 'when dealing with multi byte characters' do it 'should support unicode characters' do code = <<-CODE "x\\u2713y" CODE # >= Ruby 1.9.3 reports \u expect(tokens_scanned_from(code)).to match_tokens2([:STRING, "x\u2713y"]) end it 'should support adjacent short form unicode characters' do code = <<-CODE "x\\u2713\\u2713y" CODE # >= Ruby 1.9.3 reports \u expect(tokens_scanned_from(code)).to match_tokens2([:STRING, "x\u2713\u2713y"]) end it 'should support unicode characters in long form' do code = <<-CODE "x\\u{1f452}y" CODE expect(tokens_scanned_from(code)).to match_tokens2([:STRING, "x\u{1f452}y"]) end it 'can escape the unicode escape' do code = <<-"CODE" "x\\\\u{1f452}y" CODE expect(tokens_scanned_from(code)).to match_tokens2([:STRING, "x\\u{1f452}y"]) end it 'produces byte offsets that counts each byte in a comment' do code = <<-"CODE" # \u{0400}\na CODE expect(tokens_scanned_from(code.strip)).to match_tokens2([:NAME, 'a', {:line => 2, :offset => 5, :length=>1}]) end it 'produces byte offsets that counts each byte in value token' do code = <<-"CODE" '\u{0400}'\na CODE expect(tokens_scanned_from(code.strip)).to match_tokens2( [:STRING, "\u{400}", {:line => 1, :offset => 0, :length=>4}], [:NAME, 'a', {:line => 2, :offset => 5, :length=>1}] ) end it 'should not select LISTSTART token when preceded by multibyte chars' do # This test is sensitive to the number of multibyte characters and position of the expressions # within the string - it is designed to fail if the position is calculated on the byte offset of the '[' # instead of the char offset. # code = "$a = '\u00f6\u00fc\u00fc\u00fc\u00fc\u00e4\u00e4\u00f6\u00e4'\nnotify {'x': message => B['dkda'] }\n" expect(tokens_scanned_from(code)).to match_tokens2( :VARIABLE, :EQUALS, :STRING, [:NAME, 'notify'], :LBRACE, [:STRING, 'x'], :COLON, :NAME, :FARROW, :CLASSREF, :LBRACK, :STRING, :RBRACK, :RBRACE) end end context 'when lexing epp' do it 'epp can contain just text' do code = <<-CODE This is just text CODE expect(epp_tokens_scanned_from(code)).to match_tokens2(:EPP_START, [:RENDER_STRING, " This is just text\n"]) end it 'epp can contain text with interpolated rendered expressions' do code = <<-CODE This is <%= $x %> just text CODE expect(epp_tokens_scanned_from(code)).to match_tokens2( :EPP_START, [:RENDER_STRING, " This is "], [:RENDER_EXPR, nil], [:VARIABLE, "x"], [:EPP_END, "%>"], [:RENDER_STRING, " just text\n"] ) end it 'epp can contain text with trimmed interpolated rendered expressions' do code = <<-CODE This is <%= $x -%> just text CODE expect(epp_tokens_scanned_from(code)).to match_tokens2( :EPP_START, [:RENDER_STRING, " This is "], [:RENDER_EXPR, nil], [:VARIABLE, "x"], [:EPP_END_TRIM, "-%>"], [:RENDER_STRING, "just text\n"] ) end it 'epp can contain text with expressions that are not rendered' do code = <<-CODE This is <% $x=10 %> just text CODE expect(epp_tokens_scanned_from(code)).to match_tokens2( :EPP_START, [:RENDER_STRING, " This is "], [:VARIABLE, "x"], :EQUALS, [:NUMBER, "10"], [:RENDER_STRING, " just text\n"] ) end it 'epp can skip leading space in tail text' do code = <<-CODE This is <% $x=10 -%> just text CODE expect(epp_tokens_scanned_from(code)).to match_tokens2( :EPP_START, [:RENDER_STRING, " This is "], [:VARIABLE, "x"], :EQUALS, [:NUMBER, "10"], [:RENDER_STRING, " just text\n"] ) end it 'epp can skip comments' do code = <<-CODE This is <% $x=10 -%> <%# This is an epp comment -%> just text CODE expect(epp_tokens_scanned_from(code)).to match_tokens2( :EPP_START, [:RENDER_STRING, " This is "], [:VARIABLE, "x"], :EQUALS, [:NUMBER, "10"], [:RENDER_STRING, " just text\n"] ) end it 'epp comments strips left whitespace when preceding is right trim' do code = <<-CODE This is <% $x=10 -%> space-before-me-but-not-after <%# This is an epp comment %> just text CODE expect(epp_tokens_scanned_from(code)).to match_tokens2( :EPP_START, [:RENDER_STRING, " This is "], [:VARIABLE, "x"], :EQUALS, [:NUMBER, "10"], [:RENDER_STRING, " space-before-me-but-not-after\n just text\n"] ) end it 'epp comments strips left whitespace on same line when preceding is not right trim' do code = <<-CODE This is <% $x=10 %> <%# This is an epp comment -%> just text CODE expect(epp_tokens_scanned_from(code)).to match_tokens2( :EPP_START, [:RENDER_STRING, " This is "], [:VARIABLE, "x"], :EQUALS, [:NUMBER, "10"], [:RENDER_STRING, "\n just text\n"] ) end it 'epp can escape epp tags' do code = <<-CODE This is <% $x=10 -%> <%% this is escaped epp %%> CODE expect(epp_tokens_scanned_from(code)).to match_tokens2( :EPP_START, [:RENDER_STRING, " This is "], [:VARIABLE, "x"], :EQUALS, [:NUMBER, "10"], [:RENDER_STRING, " <% this is escaped epp %>\n"] ) end context 'with bad epp syntax' do def expect_issue(code, issue) expect { epp_tokens_scanned_from(code) }.to raise_error(Puppet::ParseErrorWithIssue) { |e| expect(e.issue_code).to be(issue.issue_code) } end it 'detects and reports EPP_UNBALANCED_TAG' do expect_issue('<% asf', Puppet::Pops::Issues::EPP_UNBALANCED_TAG) end it 'detects and reports EPP_UNBALANCED_COMMENT' do expect_issue('<%# asf', Puppet::Pops::Issues::EPP_UNBALANCED_COMMENT) end it 'detects and reports EPP_UNBALANCED_EXPRESSION' do expect_issue('asf <%', Puppet::Pops::Issues::EPP_UNBALANCED_EXPRESSION) end end end context 'when parsing bad code' do def expect_issue(code, issue) expect { tokens_scanned_from(code) }.to raise_error(Puppet::ParseErrorWithIssue) do |e| expect(e.issue_code).to be(issue.issue_code) end end it 'detects and reports issue ILLEGAL_CLASS_REFERENCE' do expect_issue('A::3', Puppet::Pops::Issues::ILLEGAL_CLASS_REFERENCE) end it 'detects and reports issue ILLEGAL_FULLY_QUALIFIED_CLASS_REFERENCE' do expect_issue('::A::3', Puppet::Pops::Issues::ILLEGAL_FULLY_QUALIFIED_CLASS_REFERENCE) end it 'detects and reports issue ILLEGAL_FULLY_QUALIFIED_NAME' do expect_issue('::a::3', Puppet::Pops::Issues::ILLEGAL_FULLY_QUALIFIED_NAME) end it 'detects and reports issue ILLEGAL_NUMBER' do expect_issue('3g', Puppet::Pops::Issues::ILLEGAL_NUMBER) end it 'detects and reports issue INVALID_HEX_NUMBER' do expect_issue('0x3g', Puppet::Pops::Issues::INVALID_HEX_NUMBER) end it 'detects and reports issue INVALID_OCTAL_NUMBER' do expect_issue('038', Puppet::Pops::Issues::INVALID_OCTAL_NUMBER) end it 'detects and reports issue INVALID_DECIMAL_NUMBER' do expect_issue('4.3g', Puppet::Pops::Issues::INVALID_DECIMAL_NUMBER) end it 'detects and reports issue NO_INPUT_TO_LEXER' do expect { Puppet::Pops::Parser::Lexer2.new.fullscan }.to raise_error(Puppet::ParseErrorWithIssue) { |e| expect(e.issue_code).to be(Puppet::Pops::Issues::NO_INPUT_TO_LEXER.issue_code) } end it 'detects and reports issue UNCLOSED_QUOTE' do expect_issue('"asd', Puppet::Pops::Issues::UNCLOSED_QUOTE) end end context 'when dealing with non UTF-8 and Byte Order Marks (BOMs)' do { 'UTF_8' => [0xEF, 0xBB, 0xBF], 'UTF_16_1' => [0xFE, 0xFF], 'UTF_16_2' => [0xFF, 0xFE], 'UTF_32_1' => [0x00, 0x00, 0xFE, 0xFF], 'UTF_32_2' => [0xFF, 0xFE, 0x00, 0x00], 'UTF_1' => [0xF7, 0x64, 0x4C], 'UTF_EBCDIC' => [0xDD, 0x73, 0x66, 0x73], 'SCSU' => [0x0E, 0xFE, 0xFF], 'BOCU' => [0xFB, 0xEE, 0x28], 'GB_18030' => [0x84, 0x31, 0x95, 0x33] }.each do |key, bytes| it "errors on the byte order mark for #{key} '[#{bytes.map() {|b| '%X' % b}.join(' ')}]'" do format_name = key.split('_')[0,2].join('-') bytes_str = "\\[#{bytes.map {|b| '%X' % b}.join(' ')}\\]" fix = " - remove these from the puppet source" expect { tokens_scanned_from(bytes.pack('C*')) }.to raise_error(Puppet::ParseErrorWithIssue, /Illegal #{format_name} .* at beginning of input: #{bytes_str}#{fix}/) end it "can use a possibly 'broken' UTF-16 string without problems for #{key}" do format_name = key.split('_')[0,2].join('-') string = bytes.pack('C*').force_encoding('UTF-16') bytes_str = "\\[#{string.bytes.map {|b| '%X' % b}.join(' ')}\\]" fix = " - remove these from the puppet source" expect { tokens_scanned_from(string) }.to raise_error(Puppet::ParseErrorWithIssue, /Illegal #{format_name} .* at beginning of input: #{bytes_str}#{fix}/) end end end end describe Puppet::Pops::Parser::Lexer2 do include PuppetSpec::Files # First line of Rune version of Rune poem at http://www.columbia.edu/~fdc/utf8/ # characters chosen since they will not parse on Windows with codepage 437 or 1252 # Section 3.2.1.3 of Ruby spec guarantees that \u strings are encoded as UTF-8 # Runes (may show up as garbage if font is not available): ᚠᛇᚻ᛫ᛒᛦᚦ᛫ᚠᚱᚩᚠᚢᚱ᛫ᚠá›áš±ášŞá›«áš·á›–ᚻᚹᛦᛚᚳᚢᛗ let (:rune_utf8) { "\u16A0\u16C7\u16BB\u16EB\u16D2\u16E6\u16A6\u16EB\u16A0\u16B1\u16A9\u16A0\u16A2" + "\u16B1\u16EB\u16A0\u16C1\u16B1\u16AA\u16EB\u16B7\u16D6\u16BB\u16B9\u16E6\u16DA" + "\u16B3\u16A2\u16D7" } context 'when lexing files from disk' do it 'should always read files as UTF-8' do if Puppet.features.microsoft_windows? && Encoding.default_external == Encoding::UTF_8 raise 'This test must be run in a codepage other than 65001 to validate behavior' end manifest_code = "notify { '#{rune_utf8}': }" manifest = file_containing('manifest.pp', manifest_code) lexed_file = described_class.new.lex_file(manifest) expect(lexed_file.string.encoding).to eq(Encoding::UTF_8) expect(lexed_file.string).to eq(manifest_code) end it 'currently errors when the UTF-8 BOM (Byte Order Mark) is present when lexing files' do bom = "\uFEFF" manifest_code = "#{bom}notify { '#{rune_utf8}': }" manifest = file_containing('manifest.pp', manifest_code) expect { described_class.new.lex_file(manifest) }.to raise_error(Puppet::ParseErrorWithIssue, 'Illegal UTF-8 Byte Order mark at beginning of input: [EF BB BF] - remove these from the puppet source') end end end puppet-5.5.10/spec/unit/pops/pn_spec.rb0000644005276200011600000001027213417161721017662 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' require 'puppet/pops/pn' module Puppet::Pops module PN describe 'Puppet::Pops::PN' do context 'Literal' do context 'containing the value' do it 'true produces the text "true"' do expect(lit(true).to_s).to eql('true') end it 'false produces the text "false"' do expect(lit(false).to_s).to eql('false') end it 'nil produces the text "nil"' do expect(lit(nil).to_s).to eql('nil') end it '34 produces the text "34"' do expect(lit(34).to_s).to eql('34') end it '3.0 produces the text "3.0"' do expect(lit(3.0).to_s).to eql('3.0') end end context 'produces a double quoted text from a string such that' do it '"plain" produces "plain"' do expect(lit('plain').to_s).to eql('"plain"') end it '"\t" produces a text containing a tab character' do expect(lit("\t").to_s).to eql('"\t"') end it '"\r" produces a text containing a return character' do expect(lit("\r").to_s).to eql('"\r"') end it '"\n" produces a text containing a newline character' do expect(lit("\n").to_s).to eql('"\n"') end it '"\"" produces a text containing a double quote' do expect(lit("\"").to_s).to eql('"\""') end it '"\\" produces a text containing a backslash' do expect(lit("\\").to_s).to eql('"\\\\"') end it '"\u{14}" produces "\o024"' do expect(lit("\u{14}").to_s).to eql('"\o024"') end end end context 'List' do it 'produces a text where its elements are enclosed in brackets' do expect(List.new([lit(3), lit('a'), lit(true)]).to_s).to eql('[3 "a" true]') end it 'produces a text where the elements of nested lists are enclosed in brackets' do expect(List.new([lit(3), lit('a'), List.new([lit(true), lit(false)])]).to_s).to eql('[3 "a" [true false]]') end context 'with indent' do it 'produces a text where the each element is on an indented line ' do s = '' List.new([lit(3), lit('a'), List.new([lit(true), lit(false)])]).format(Indent.new(' '), s) expect(s).to eql(<<-RESULT.unindent[0..-2]) # unindent and strip last newline [ 3 "a" [ true false]] RESULT end end end context 'Map' do it 'raises error when illegal keys are used' do expect { Map.new([Entry.new('123', lit(3))]) }.to raise_error(ArgumentError, /key 123 does not conform to pattern/) end it 'produces a text where entries are enclosed in curly braces' do expect(Map.new([Entry.new('a', lit(3))]).to_s).to eql('{:a 3}') end it 'produces a text where the entries of nested maps are enclosed in curly braces' do expect(Map.new([ Map.new([Entry.new('a', lit(3))]).with_name('o')]).to_s).to eql('{:o {:a 3}}') end context 'with indent' do it 'produces a text where the each element is on an indented line ' do s = '' Map.new([ Map.new([Entry.new('a', lit(3)), Entry.new('b', lit(5))]).with_name('o')]).format(Indent.new(' '), s) expect(s).to eql(<<-RESULT.unindent[0..-2]) # unindent and strip last newline { :o { :a 3 :b 5}} RESULT end end end context 'Call' do it 'produces a text where elements are enclosed in parenthesis' do expect(Call.new('+', lit(3), lit(5)).to_s).to eql('(+ 3 5)') end it 'produces a text where the elements of nested calls are enclosed in parenthesis' do expect(Map.new([ Call.new('+', lit(3), lit(5)).with_name('o')]).to_s).to eql('{:o (+ 3 5)}') end context 'with indent' do it 'produces a text where the each element is on an indented line ' do s = '' Call.new('+', lit(3), lit(Call.new('-', lit(10), lit(5)))).format(Indent.new(' '), s) expect(s).to eql(<<-RESULT.unindent[0..-2]) # unindent and strip last newline (+ 3 (- 10 5)) RESULT end end end def lit(v) v.is_a?(PN) ? v : Literal.new(v) end end end end puppet-5.5.10/spec/unit/pops/puppet_stack_spec.rb0000644005276200011600000000630213417161721021746 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' describe 'Puppet::Pops::PuppetStack' do class StackTraceTest def get_stacktrace Puppet::Pops::PuppetStack.stacktrace end def get_top_of_stack Puppet::Pops::PuppetStack.top_of_stack end def one_level Puppet::Pops::PuppetStack.stack("one_level.pp", 1234, self, :get_stacktrace, []) end def one_level_top Puppet::Pops::PuppetStack.stack("one_level.pp", 1234, self, :get_top_of_stack, []) end def two_levels Puppet::Pops::PuppetStack.stack("two_levels.pp", 1237, self, :level2, []) end def two_levels_top Puppet::Pops::PuppetStack.stack("two_levels.pp", 1237, self, :level2_top, []) end def level2 Puppet::Pops::PuppetStack.stack("level2.pp", 1240, self, :get_stacktrace, []) end def level2_top Puppet::Pops::PuppetStack.stack("level2.pp", 1240, self, :get_top_of_stack, []) end def gets_block(a, &block) block.call(a) end def gets_args_and_block(a, b, &block) block.call(a, b) end def with_nil_file Puppet::Pops::PuppetStack.stack(nil, 1250, self, :get_stacktrace, []) end def with_empty_string_file Puppet::Pops::PuppetStack.stack('', 1251, self, :get_stacktrace, []) end end it 'returns an empty array from stacktrace when there is nothing on the stack' do expect(Puppet::Pops::PuppetStack.stacktrace).to eql([]) end context 'full stacktrace' do it 'returns a one element array with file, line from stacktrace when there is one entry on the stack' do expect(StackTraceTest.new.one_level).to eql([['one_level.pp', 1234]]) end it 'returns an array from stacktrace with information about each level with oldest frame last' do expect(StackTraceTest.new.two_levels).to eql([['level2.pp', 1240], ['two_levels.pp', 1237]]) end it 'returns an empty array from top_of_stack when there is nothing on the stack' do expect(Puppet::Pops::PuppetStack.top_of_stack).to eql([]) end end context 'top of stack' do it 'returns a one element array with file, line from top_of_stack when there is one entry on the stack' do expect(StackTraceTest.new.one_level_top).to eql(['one_level.pp', 1234]) end it 'returns newest entry from top_of_stack' do expect(StackTraceTest.new.two_levels_top).to eql(['level2.pp', 1240]) end it 'accepts file to be nil' do expect(StackTraceTest.new.with_nil_file).to eql([['unknown', 1250]]) end end it 'accepts file to be empty_string' do expect(StackTraceTest.new.with_empty_string_file).to eql([['unknown', 1251]]) end it 'stacktrace is empty when call has returned' do StackTraceTest.new.two_levels expect(Puppet::Pops::PuppetStack.stacktrace).to eql([]) end it 'allows passing a block to the stack call' do expect(Puppet::Pops::PuppetStack.stack("test.pp", 1, StackTraceTest.new, :gets_block, ['got_it']) {|x| x }).to eql('got_it') end it 'allows passing multiple variables and a block' do expect( Puppet::Pops::PuppetStack.stack("test.pp", 1, StackTraceTest.new, :gets_args_and_block, ['got_it', 'again']) {|x, y| [x,y].join(' ')} ).to eql('got_it again') end endpuppet-5.5.10/spec/unit/pops/resource/0000755005276200011600000000000013417162176017540 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/pops/resource/resource_type_impl_spec.rb0000644005276200011600000000630413417161721025006 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' require 'puppet_spec/files' require 'puppet_spec/compiler' module Puppet::Pops module Resource describe "Puppet::Pops::Resource" do include PuppetSpec::Compiler let!(:pp_parser) { Parser::EvaluatingParser.new } let(:loader) { Loader::BaseLoader.new(nil, 'type_parser_unit_test_loader') } let(:factory) { TypeFactory } context 'when creating resources' do let!(:resource_type) { ResourceTypeImpl._pcore_type } it 'can create an instance of a ResourceType' do code = <<-CODE $rt = Puppet::Resource::ResourceType3.new('notify', [], [Puppet::Resource::Param.new(String, 'message')]) assert_type(Puppet::Resource::ResourceType3, $rt) notice('looks like we made it') CODE rt = nil notices = eval_and_collect_notices(code) do |scope, _| rt = scope['rt'] end expect(notices).to eq(['looks like we made it']) expect(rt).to be_a(ResourceTypeImpl) expect(rt.valid_parameter?(:nonesuch)).to be_falsey expect(rt.valid_parameter?(:message)).to be_truthy expect(rt.valid_parameter?(:loglevel)).to be_truthy end end context 'when used with capability resource with producers/consumers' do include PuppetSpec::Files let!(:env_name) { 'spec' } let!(:env_dir) { tmpdir('environments') } let!(:populated_env_dir) do dir_contained_in(env_dir, env_name => { '.resource_types' => { 'capability.pp' => <<-PUPPET Puppet::Resource::ResourceType3.new( 'capability', [], [Puppet::Resource::Param(Any, 'name', true)], { /(.*)/ => ['name'] }, true, true) PUPPET }, 'modules' => { 'test' => { 'lib' => { 'puppet' => { 'type' => { 'capability.rb' => <<-RUBY Puppet::Type.newtype(:capability, :is_capability => true) do newparam :name, :namevar => true raise Puppet::Error, 'Ruby resource was loaded' end RUBY } } } } } }) end let!(:code) { <<-PUPPET } define producer() { notify { "producer":} } define consumer() { notify { $title:} } Producer produces Capability {} Consumer consumes Capability {} producer {x: export => Capability[cap]} consumer {x: consume => Capability[cap]} consumer {y: require => Capability[cap]} PUPPET let(:environments) { Puppet::Environments::Directories.new(populated_env_dir, []) } let(:env) { Puppet::Node::Environment.create(:'spec', [File.join(env_dir, 'spec', 'modules')]) } let(:node) { Puppet::Node.new('test', :environment => env) } around(:each) do |example| Puppet[:environment] = env_name Puppet.override(:environments => environments, :current_environment => env) do example.run end Puppet::Type.rmtype(:capability) end it 'does not load the Ruby resource' do expect { compile_to_catalog(code, node) }.not_to raise_error end end end end end puppet-5.5.10/spec/unit/pops/serialization/0000755005276200011600000000000013417162176020566 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/pops/serialization/packer_spec.rb0000644005276200011600000000744413417161721023376 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops/serialization' module Puppet::Pops module Serialization [JSON].each do |packer_module| describe "the Puppet::Pops::Serialization when using #{packer_module.name}" do let(:io) { StringIO.new } let(:reader_class) { packer_module::Reader } let(:writer_class) { packer_module::Writer } def write(*values) io.reopen serializer = writer_class.new(io) values.each { |val| serializer.write(val) } serializer.finish io.rewind end def read(count = nil) @deserializer = reader_class.new(io) count.nil? ? @deserializer.read : Array.new(count) { @deserializer.read } end context 'can write and read a' do it 'String' do val = 'the value' write(val) val2 = read expect(val2).to be_a(String) expect(val2).to eql(val) end it 'positive Integer' do val = 2**63-1 write(val) val2 = read expect(val2).to be_a(Integer) expect(val2).to eql(val) end it 'negative Integer' do val = -2**63 write(val) val2 = read expect(val2).to be_a(Integer) expect(val2).to eql(val) end it 'Float' do val = 32.45 write(val) val2 = read expect(val2).to be_a(Float) expect(val2).to eql(val) end it 'true' do val = true write(val) val2 = read expect(val2).to be_a(TrueClass) expect(val2).to eql(val) end it 'false' do val = false write(val) val2 = read expect(val2).to be_a(FalseClass) expect(val2).to eql(val) end it 'nil' do val = nil write(val) val2 = read expect(val2).to be_a(NilClass) expect(val2).to eql(val) end it 'Regexp' do val = /match me/ write(val) val2 = read expect(val2).to be_a(Regexp) expect(val2).to eql(val) end it 'Timespan' do val = Time::Timespan.from_fields(false, 3, 12, 40, 31, 123) write(val) val2 = read expect(val2).to be_a(Time::Timespan) expect(val2).to eql(val) end it 'Timestamp' do val = Time::Timestamp.now write(val) val2 = read expect(val2).to be_a(Time::Timestamp) expect(val2).to eql(val) end it 'Version' do val = SemanticPuppet::Version.parse('1.2.3-alpha2') write(val) val2 = read expect(val2).to be_a(SemanticPuppet::Version) expect(val2).to eql(val) end it 'VersionRange' do val = SemanticPuppet::VersionRange.parse('>=1.2.3-alpha2 <1.2.4') write(val) val2 = read expect(val2).to be_a(SemanticPuppet::VersionRange) expect(val2).to eql(val) end it 'Binary' do val = Types::PBinaryType::Binary.from_base64('w5ZzdGVuIG1lZCByw7ZzdGVuCg==') write(val) val2 = read expect(val2).to be_a(Types::PBinaryType::Binary) expect(val2).to eql(val) end it 'URI' do val = URI('http://bob:ewing@dallas.example.com:8080/oil/baron?crude=cash#leftovers') write(val) val2 = read expect(val2).to be_a(URI) expect(val2).to eql(val) end end context 'will fail on attempts to write' do it 'Integer larger than 2**63-1' do expect { write(2**63) }.to raise_error(SerializationError, 'Integer out of bounds') end it 'Integer smaller than -2**63' do expect { write(-2**63-1) }.to raise_error(SerializationError, 'Integer out of bounds') end it 'objects unknown to Puppet serialization' do expect { write("".class) }.to raise_error(SerializationError, 'Unable to serialize a Class') end end it 'should be able to write and read primitives using tabulation' do val = 'the value' write(val, val) expect(read(2)).to eql([val, val]) expect(@deserializer.primitive_count).to eql(1) end end end end end puppet-5.5.10/spec/unit/pops/serialization/serialization_spec.rb0000644005276200011600000003103313417161722024776 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' module Puppet::Pops module Serialization [JSON].each do |packer_module| describe "the Puppet::Pops::Serialization over #{packer_module.name}" do let!(:dumper) { Model::ModelTreeDumper.new } let(:io) { StringIO.new } let(:parser) { Parser::EvaluatingParser.new } let(:env) { Puppet::Node::Environment.create(:testing, []) } let(:loaders) { Puppet::Pops::Loaders.new(env) } let(:loader) { loaders.find_loader(nil) } let(:reader_class) { packer_module::Reader } let(:writer_class) { packer_module::Writer } let(:serializer) { Serializer.new(writer_class.new(io)) } let(:deserializer) { Deserializer.new(reader_class.new(io), loaders.find_loader(nil)) } around :each do |example| Puppet.override(:loaders => loaders, :current_environment => env) do example.run end end def write(*values) io.reopen values.each { |val| serializer.write(val) } serializer.finish io.rewind end def read deserializer.read end def parse(string) parser.parse_string(string, '/home/tester/experiments/manifests/init.pp') end context 'can write and read a' do it 'String' do val = 'the value' write(val) val2 = read expect(val2).to be_a(String) expect(val2).to eql(val) end it 'Integer' do val = 32 write(val) val2 = read expect(val2).to be_a(Integer) expect(val2).to eql(val) end it 'Float' do val = 32.45 write(val) val2 = read expect(val2).to be_a(Float) expect(val2).to eql(val) end it 'true' do val = true write(val) val2 = read expect(val2).to be_a(TrueClass) expect(val2).to eql(val) end it 'false' do val = false write(val) val2 = read expect(val2).to be_a(FalseClass) expect(val2).to eql(val) end it 'nil' do val = nil write(val) val2 = read expect(val2).to be_a(NilClass) expect(val2).to eql(val) end it 'Regexp' do val = /match me/ write(val) val2 = read expect(val2).to be_a(Regexp) expect(val2).to eql(val) end it 'Sensitive' do sval = 'the sensitive value' val = Types::PSensitiveType::Sensitive.new(sval) write(val) val2 = read expect(val2).to be_a(Types::PSensitiveType::Sensitive) expect(val2.unwrap).to eql(sval) end it 'Timespan' do val = Time::Timespan.from_fields(false, 3, 12, 40, 31, 123) write(val) val2 = read expect(val2).to be_a(Time::Timespan) expect(val2).to eql(val) end it 'Timestamp' do val = Time::Timestamp.now write(val) val2 = read expect(val2).to be_a(Time::Timestamp) expect(val2).to eql(val) end it 'Version' do # It does succeed on rare occasions, so we need to repeat val = SemanticPuppet::Version.parse('1.2.3-alpha2') write(val) val2 = read expect(val2).to be_a(SemanticPuppet::Version) expect(val2).to eql(val) end it 'VersionRange' do # It does succeed on rare occasions, so we need to repeat val = SemanticPuppet::VersionRange.parse('>=1.2.3-alpha2 <1.2.4') write(val) val2 = read expect(val2).to be_a(SemanticPuppet::VersionRange) expect(val2).to eql(val) end it 'Binary' do val = Types::PBinaryType::Binary.from_base64('w5ZzdGVuIG1lZCByw7ZzdGVuCg==') write(val) val2 = read expect(val2).to be_a(Types::PBinaryType::Binary) expect(val2).to eql(val) end it 'URI' do val = URI('http://bob:ewing@dallas.example.com:8080/oil/baron?crude=cash#leftovers') write(val) val2 = read expect(val2).to be_a(URI) expect(val2).to eql(val) end it 'Sensitive with rich data' do sval = Time::Timestamp.now val = Types::PSensitiveType::Sensitive.new(sval) write(val) val2 = read expect(val2).to be_a(Types::PSensitiveType::Sensitive) expect(val2.unwrap).to be_a(Time::Timestamp) expect(val2.unwrap).to eql(sval) end end context 'can write and read' do include_context 'types_setup' all_types.each do |t| it "the default for type #{t.name}" do val = t::DEFAULT write(val) val2 = read expect(val2).to be_a(t) expect(val2).to eql(val) end end context 'a parameterized' do it 'String' do val = Types::TypeFactory.string(Types::TypeFactory.range(1, :default)) write(val) val2 = read expect(val2).to be_a(Types::PStringType) expect(val2).to eql(val) end it 'Regex' do val = Types::TypeFactory.regexp(/foo/) write(val) val2 = read expect(val2).to be_a(Types::PRegexpType) expect(val2).to eql(val) end it 'Collection' do val = Types::TypeFactory.collection(Types::TypeFactory.range(0, 20)) write(val) val2 = read expect(val2).to be_a(Types::PCollectionType) expect(val2).to eql(val) end it 'Array' do val = Types::TypeFactory.array_of(Types::TypeFactory.integer, Types::TypeFactory.range(0, 20)) write(val) val2 = read expect(val2).to be_a(Types::PArrayType) expect(val2).to eql(val) end it 'Hash' do val = Types::TypeFactory.hash_kv(Types::TypeFactory.string, Types::TypeFactory.integer, Types::TypeFactory.range(0, 20)) write(val) val2 = read expect(val2).to be_a(Types::PHashType) expect(val2).to eql(val) end it 'Variant' do val = Types::TypeFactory.variant(Types::TypeFactory.string, Types::TypeFactory.range(1, :default)) write(val) val2 = read expect(val2).to be_a(Types::PVariantType) expect(val2).to eql(val) end it 'Object' do val = Types::TypeParser.singleton.parse('Pcore::StringType', loader) write(val) val2 = read expect(val2).to be_a(Types::PObjectType) expect(val2).to eql(val) end context 'ObjectType' do let(:type) do Types::PObjectType.new({ 'name' => 'MyType', 'type_parameters' => { 'x' => Types::PIntegerType::DEFAULT }, 'attributes' => { 'x' => Types::PIntegerType::DEFAULT } }) end it 'with preserved parameters' do val = type.create(34)._pcore_type write(val) val2 = read expect(val2).to be_a(Types::PObjectTypeExtension) expect(val2).to eql(val) end end end it 'Array of rich data' do # Sensitive omitted because it doesn't respond to == val = [ Time::Timespan.from_fields(false, 3, 12, 40, 31, 123), Time::Timestamp.now, SemanticPuppet::Version.parse('1.2.3-alpha2'), SemanticPuppet::VersionRange.parse('>=1.2.3-alpha2 <1.2.4'), Types::PBinaryType::Binary.from_base64('w5ZzdGVuIG1lZCByw7ZzdGVuCg==') ] write(val) val2 = read expect(val2).to eql(val) end it 'Hash of rich data' do # Sensitive omitted because it doesn't respond to == val = { 'duration' => Time::Timespan.from_fields(false, 3, 12, 40, 31, 123), 'time' => Time::Timestamp.now, 'version' => SemanticPuppet::Version.parse('1.2.3-alpha2'), 'range' => SemanticPuppet::VersionRange.parse('>=1.2.3-alpha2 <1.2.4'), 'binary' => Types::PBinaryType::Binary.from_base64('w5ZzdGVuIG1lZCByw7ZzdGVuCg==') } write(val) val2 = read expect(val2).to eql(val) end context 'an AST model' do it "Locator" do val = Parser::Locator::Locator19.new('here is some text', '/tmp/foo', [5]) write(val) val2 = read expect(val2).to be_a(Parser::Locator::Locator19) expect(val2).to eql(val) end it 'nested Expression' do expr = parse(<<-CODE) $rootgroup = $osfamily ? { 'Solaris' => 'wheel', /(Darwin|FreeBSD)/ => 'wheel', default => 'root', } file { '/etc/passwd': ensure => file, owner => 'root', group => $rootgroup, } CODE write(expr) expr2 = read expect(dumper.dump(expr)).to eq(dumper.dump(expr2)) end end context 'PuppetObject' do before(:each) do class DerivedArray < Array include Types::PuppetObject def self._pcore_type @type end def self.register_ptype(loader, ir) @type = Pcore.create_object_type(loader, ir, DerivedArray, 'DerivedArray', nil, 'values' => Types::PArrayType::DEFAULT) .resolve(loader) end def initialize(values) concat(values) end def values Array.new(self) end end class DerivedHash < Hash include Types::PuppetObject def self._pcore_type @type end def self.register_ptype(loader, ir) @type = Pcore.create_object_type(loader, ir, DerivedHash, 'DerivedHash', nil, '_pcore_init_hash' => Types::PHashType::DEFAULT) .resolve(loader) end def initialize(_pcore_init_hash) merge!(_pcore_init_hash) end def _pcore_init_hash result = {} result.merge!(self) result end end end after(:each) do x = Puppet::Pops::Serialization x.send(:remove_const, :DerivedArray) if x.const_defined?(:DerivedArray) x.send(:remove_const, :DerivedHash) if x.const_defined?(:DerivedHash) end it 'derived from Array' do DerivedArray.register_ptype(loader, loaders.implementation_registry) # Sensitive omitted because it doesn't respond to == val = DerivedArray.new([ Time::Timespan.from_fields(false, 3, 12, 40, 31, 123), Time::Timestamp.now, SemanticPuppet::Version.parse('1.2.3-alpha2'), SemanticPuppet::VersionRange.parse('>=1.2.3-alpha2 <1.2.4'), Types::PBinaryType::Binary.from_base64('w5ZzdGVuIG1lZCByw7ZzdGVuCg==') ]) write(val) val2 = read expect(val2).to eql(val) end it 'derived from Hash' do DerivedHash.register_ptype(loader, loaders.implementation_registry) # Sensitive omitted because it doesn't respond to == val = DerivedHash.new({ 'duration' => Time::Timespan.from_fields(false, 3, 12, 40, 31, 123), 'time' => Time::Timestamp.now, 'version' => SemanticPuppet::Version.parse('1.2.3-alpha2'), 'range' => SemanticPuppet::VersionRange.parse('>=1.2.3-alpha2 <1.2.4'), 'binary' => Types::PBinaryType::Binary.from_base64('w5ZzdGVuIG1lZCByw7ZzdGVuCg==') }) write(val) val2 = read expect(val2).to eql(val) end end end context 'deserializing an instance whose Object type was serialized by reference' do let(:serializer) { Serializer.new(writer_class.new(io), :type_by_reference => true) } let(:type) do Types::PObjectType.new({ 'name' => 'MyType', 'attributes' => { 'x' => Types::PIntegerType::DEFAULT } }) end it 'fails when deserializer is unaware of the referenced type' do write(type.create(32)) # Should fail since no loader knows about 'MyType' expect{ read }.to raise_error(Puppet::Error, 'No implementation mapping found for Puppet Type MyType') end it 'succeeds when deserializer is aware of the referenced type' do obj = type.create(32) write(obj) loaders.find_loader(nil).expects(:load).with(:type, 'mytype').returns(type) expect(read).to eql(obj) end end context 'When debugging' do let(:debug_io) { StringIO.new } let(:writer) { reader_class.new(io, { :debug_io => debug_io, :tabulate => false, :verbose => true }) } it 'can write and read an AST expression' do expr = parse(<<-CODE) $rootgroup = $osfamily ? { 'Solaris' => 'wheel', /(Darwin|FreeBSD)/ => 'wheel', default => 'root', } file { '/etc/passwd': ensure => file, owner => 'root', group => $rootgroup, } CODE write(expr) expr2 = read expect(dumper.dump(expr)).to eq(dumper.dump(expr2)) end end end end end end puppet-5.5.10/spec/unit/pops/serialization/to_from_hr_spec.rb0000644005276200011600000004424313417161722024266 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' module Puppet::Pops module Serialization describe 'Passing values through ToDataConverter/FromDataConverter' do let(:dumper) { Model::ModelTreeDumper.new } let(:io) { StringIO.new } let(:parser) { Parser::EvaluatingParser.new } let(:env) { Puppet::Node::Environment.create(:testing, []) } let(:loaders) { Puppet::Pops::Loaders.new(env) } let(:loader) { loaders.find_loader(nil) } let(:to_converter) { ToDataConverter.new(:rich_data => true) } let(:from_converter) { FromDataConverter.new(:loader => loader) } before(:each) do Puppet.lookup(:environments).clear_all Puppet.push_context(:loaders => loaders, :current_environment => env) end after(:each) do Puppet.pop_context end def write(value) io.reopen value = to_converter.convert(value) expect(Types::TypeFactory.data).to be_instance(value) io << [value].to_json io.rewind end def read from_converter.convert(::JSON.parse(io.read)[0]) end def parse(string) parser.parse_string(string, '/home/tester/experiments/manifests/init.pp') end context 'can write and read a' do it 'String' do val = 'the value' write(val) val2 = read expect(val2).to be_a(String) expect(val2).to eql(val) end it 'Integer' do val = 32 write(val) val2 = read expect(val2).to be_a(Integer) expect(val2).to eql(val) end it 'Float' do val = 32.45 write(val) val2 = read expect(val2).to be_a(Float) expect(val2).to eql(val) end it 'true' do val = true write(val) val2 = read expect(val2).to be_a(TrueClass) expect(val2).to eql(val) end it 'false' do val = false write(val) val2 = read expect(val2).to be_a(FalseClass) expect(val2).to eql(val) end it 'nil' do val = nil write(val) val2 = read expect(val2).to be_a(NilClass) expect(val2).to eql(val) end it 'Regexp' do val = /match me/ write(val) val2 = read expect(val2).to be_a(Regexp) expect(val2).to eql(val) end it 'Sensitive' do sval = 'the sensitive value' val = Types::PSensitiveType::Sensitive.new(sval) write(val) val2 = read expect(val2).to be_a(Types::PSensitiveType::Sensitive) expect(val2.unwrap).to eql(sval) end it 'Timespan' do val = Time::Timespan.from_fields(false, 3, 12, 40, 31, 123) write(val) val2 = read expect(val2).to be_a(Time::Timespan) expect(val2).to eql(val) end it 'Timestamp' do val = Time::Timestamp.now write(val) val2 = read expect(val2).to be_a(Time::Timestamp) expect(val2).to eql(val) end it 'Version' do # It does succeed on rare occasions, so we need to repeat val = SemanticPuppet::Version.parse('1.2.3-alpha2') write(val) val2 = read expect(val2).to be_a(SemanticPuppet::Version) expect(val2).to eql(val) end it 'VersionRange' do # It does succeed on rare occasions, so we need to repeat val = SemanticPuppet::VersionRange.parse('>=1.2.3-alpha2 <1.2.4') write(val) val2 = read expect(val2).to be_a(SemanticPuppet::VersionRange) expect(val2).to eql(val) end it 'Binary' do val = Types::PBinaryType::Binary.from_base64('w5ZzdGVuIG1lZCByw7ZzdGVuCg==') write(val) val2 = read expect(val2).to be_a(Types::PBinaryType::Binary) expect(val2).to eql(val) end it 'URI' do val = URI('http://bob:ewing@dallas.example.com:8080/oil/baron?crude=cash#leftovers') write(val) val2 = read expect(val2).to be_a(URI) expect(val2).to eql(val) end it 'Sensitive with rich data' do sval = Time::Timestamp.now val = Types::PSensitiveType::Sensitive.new(sval) write(val) val2 = read expect(val2).to be_a(Types::PSensitiveType::Sensitive) expect(val2.unwrap).to be_a(Time::Timestamp) expect(val2.unwrap).to eql(sval) end it 'Hash with Symbol keys' do val = { :one => 'one', :two => 'two' } write(val) val2 = read expect(val2).to be_a(Hash) expect(val2).to eql(val) end it 'Hash with Integer keys' do val = { 1 => 'one', 2 => 'two' } write(val) val2 = read expect(val2).to be_a(Hash) expect(val2).to eql(val) end it 'A Hash that references itself' do val = {} val['myself'] = val write(val) val2 = read expect(val2).to be_a(Hash) expect(val2['myself']).to equal(val2) end end context 'can write and read' do include_context 'types_setup' all_types.each do |t| it "the default for type #{t.name}" do val = t::DEFAULT write(val) val2 = read expect(val2).to be_a(t) expect(val2).to eql(val) end end context 'a parameterized' do it 'String' do val = Types::TypeFactory.string(Types::TypeFactory.range(1, :default)) write(val) val2 = read expect(val2).to be_a(Types::PStringType) expect(val2).to eql(val) end it 'Regex' do val = Types::TypeFactory.regexp(/foo/) write(val) val2 = read expect(val2).to be_a(Types::PRegexpType) expect(val2).to eql(val) end it 'Collection' do val = Types::TypeFactory.collection(Types::TypeFactory.range(0, 20)) write(val) val2 = read expect(val2).to be_a(Types::PCollectionType) expect(val2).to eql(val) end it 'Array' do val = Types::TypeFactory.array_of(Types::TypeFactory.integer, Types::TypeFactory.range(0, 20)) write(val) val2 = read expect(val2).to be_a(Types::PArrayType) expect(val2).to eql(val) end it 'Hash' do val = Types::TypeFactory.hash_kv(Types::TypeFactory.string, Types::TypeFactory.integer, Types::TypeFactory.range(0, 20)) write(val) val2 = read expect(val2).to be_a(Types::PHashType) expect(val2).to eql(val) end it 'Variant' do val = Types::TypeFactory.variant(Types::TypeFactory.string, Types::TypeFactory.range(1, :default)) write(val) val2 = read expect(val2).to be_a(Types::PVariantType) expect(val2).to eql(val) end it 'Object' do val = Types::TypeParser.singleton.parse('Pcore::StringType', loader) write(val) val2 = read expect(val2).to be_a(Types::PObjectType) expect(val2).to eql(val) end context 'ObjectType' do let(:type) do Types::PObjectType.new({ 'name' => 'MyType', 'type_parameters' => { 'x' => Types::PIntegerType::DEFAULT }, 'attributes' => { 'x' => Types::PIntegerType::DEFAULT } }) end it 'with preserved parameters' do val = type.create(34)._pcore_type write(val) val2 = read expect(val2).to be_a(Types::PObjectTypeExtension) expect(val2).to eql(val) end end end it 'Array of rich data' do # Sensitive omitted because it doesn't respond to == val = [ Time::Timespan.from_fields(false, 3, 12, 40, 31, 123), Time::Timestamp.now, SemanticPuppet::Version.parse('1.2.3-alpha2'), SemanticPuppet::VersionRange.parse('>=1.2.3-alpha2 <1.2.4'), Types::PBinaryType::Binary.from_base64('w5ZzdGVuIG1lZCByw7ZzdGVuCg==') ] write(val) val2 = read expect(val2).to eql(val) end it 'Hash of rich data' do # Sensitive omitted because it doesn't respond to == val = { 'duration' => Time::Timespan.from_fields(false, 3, 12, 40, 31, 123), 'time' => Time::Timestamp.now, 'version' => SemanticPuppet::Version.parse('1.2.3-alpha2'), 'range' => SemanticPuppet::VersionRange.parse('>=1.2.3-alpha2 <1.2.4'), 'binary' => Types::PBinaryType::Binary.from_base64('w5ZzdGVuIG1lZCByw7ZzdGVuCg==') } write(val) val2 = read expect(val2).to eql(val) end context 'an AST model' do it "Locator" do val = Parser::Locator::Locator19.new('here is some text', '/tmp/foo', [5]) write(val) val2 = read expect(val2).to be_a(Parser::Locator::Locator19) expect(val2).to eql(val) end it 'nested Expression' do expr = parse(<<-CODE) $rootgroup = $osfamily ? { 'Solaris' => 'wheel', /(Darwin|FreeBSD)/ => 'wheel', default => 'root', } file { '/etc/passwd': ensure => file, owner => 'root', group => $rootgroup, } CODE write(expr) expr2 = read expect(dumper.dump(expr)).to eq(dumper.dump(expr2)) end end context 'PuppetObject' do before(:each) do class DerivedArray < Array include Types::PuppetObject def self._pcore_type @type end def self.register_ptype(loader, ir) @type = Pcore.create_object_type(loader, ir, DerivedArray, 'DerivedArray', nil, 'values' => Types::PArrayType::DEFAULT) .resolve(loader) end def initialize(values) concat(values) end def values Array.new(self) end end class DerivedHash < Hash include Types::PuppetObject def self._pcore_type @type end def self.register_ptype(loader, ir) @type = Pcore.create_object_type(loader, ir, DerivedHash, 'DerivedHash', nil, '_pcore_init_hash' => Types::PHashType::DEFAULT) .resolve(loader) end def initialize(_pcore_init_hash) merge!(_pcore_init_hash) end def _pcore_init_hash result = {} result.merge!(self) result end end end after(:each) do x = Puppet::Pops::Serialization x.send(:remove_const, :DerivedArray) if x.const_defined?(:DerivedArray) x.send(:remove_const, :DerivedHash) if x.const_defined?(:DerivedHash) end it 'derived from Array' do DerivedArray.register_ptype(loader, loaders.implementation_registry) # Sensitive omitted because it doesn't respond to == val = DerivedArray.new([ Time::Timespan.from_fields(false, 3, 12, 40, 31, 123), Time::Timestamp.now, SemanticPuppet::Version.parse('1.2.3-alpha2'), SemanticPuppet::VersionRange.parse('>=1.2.3-alpha2 <1.2.4'), Types::PBinaryType::Binary.from_base64('w5ZzdGVuIG1lZCByw7ZzdGVuCg==') ]) write(val) val2 = read expect(val2).to eql(val) end it 'derived from Hash' do DerivedHash.register_ptype(loader, loaders.implementation_registry) # Sensitive omitted because it doesn't respond to == val = DerivedHash.new({ 'duration' => Time::Timespan.from_fields(false, 3, 12, 40, 31, 123), 'time' => Time::Timestamp.now, 'version' => SemanticPuppet::Version.parse('1.2.3-alpha2'), 'range' => SemanticPuppet::VersionRange.parse('>=1.2.3-alpha2 <1.2.4'), 'binary' => Types::PBinaryType::Binary.from_base64('w5ZzdGVuIG1lZCByw7ZzdGVuCg==') }) write(val) val2 = read expect(val2).to eql(val) end end end context 'deserializing an instance whose Object type was serialized by reference' do let(:to_converter) { ToDataConverter.new(:type_by_reference => true, :rich_data => true) } let(:type) do Types::PObjectType.new({ 'name' => 'MyType', 'attributes' => { 'x' => Types::PIntegerType::DEFAULT } }) end context 'fails when deserializer is unaware of the referenced type' do it 'fails by default' do write(type.create(32)) # Should fail since no loader knows about 'MyType' expect{ read }.to raise_error(Puppet::Error, 'No implementation mapping found for Puppet Type MyType') end context "succeds but produces an rich_type hash when deserializer has 'allow_unresolved' set to true" do let(:from_converter) { FromDataConverter.new(:allow_unresolved => true) } it do write(type.create(32)) expect(read).to eql({'__pcore_type__'=>'MyType', 'x'=>32}) end end end it 'succeeds when deserializer is aware of the referenced type' do obj = type.create(32) write(obj) loaders.find_loader(nil).expects(:load).with(:type, 'mytype').returns(type) expect(read).to eql(obj) end end context 'with rich_data set to false' do let(:to_converter) { ToDataConverter.new(:message_prefix => 'Test Hash', :rich_data => false) } let(:logs) { [] } let(:warnings) { logs.select { |log| log.level == :warning }.map { |log| log.message } } it 'A Hash with Symbol keys is converted to hash with String keys with warning' do val = { :one => 'one', :two => 'two' } Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do write(val) val2 = read expect(val2).to be_a(Hash) expect(val2).to eql({ 'one' => 'one', 'two' => 'two' }) end expect(warnings).to eql([ "Test Hash contains a hash with a Symbol key. It will be converted to the String 'one'", "Test Hash contains a hash with a Symbol key. It will be converted to the String 'two'"]) end it 'A Hash with Version keys is converted to hash with String keys with warning' do val = { SemanticPuppet::Version.parse('1.0.0') => 'one', SemanticPuppet::Version.parse('2.0.0') => 'two' } Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do write(val) val2 = read expect(val2).to be_a(Hash) expect(val2).to eql({ '1.0.0' => 'one', '2.0.0' => 'two' }) end expect(warnings).to eql([ "Test Hash contains a hash with a SemanticPuppet::Version key. It will be converted to the String '1.0.0'", "Test Hash contains a hash with a SemanticPuppet::Version key. It will be converted to the String '2.0.0'"]) end context 'and symbol_as_string is set to true' do let(:to_converter) { ToDataConverter.new(:rich_data => false, :symbol_as_string => true) } it 'A Hash with Symbol keys is silently converted to hash with String keys' do val = { :one => 'one', :two => 'two' } Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do write(val) val2 = read expect(val2).to be_a(Hash) expect(val2).to eql({ 'one' => 'one', 'two' => 'two' }) end expect(warnings).to be_empty end it 'A Hash with Symbol values is silently converted to hash with String values' do val = { 'one' => :one, 'two' => :two } Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do write(val) val2 = read expect(val2).to be_a(Hash) expect(val2).to eql({ 'one' => 'one', 'two' => 'two' }) end expect(warnings).to be_empty end it 'A Hash with default values will have the values converted to string with a warning' do val = { 'key' => :default } Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do write(val) val2 = read expect(val2).to be_a(Hash) expect(val2).to eql({ 'key' => 'default' }) end expect(warnings).to eql(["['key'] contains the special value default. It will be converted to the String 'default'"]) end end end context 'with rich_data is set to true' do let(:to_converter) { ToDataConverter.new(:message_prefix => 'Test Hash', :rich_data => true) } let(:logs) { [] } let(:warnings) { logs.select { |log| log.level == :warning }.map { |log| log.message } } context 'and symbol_as_string is set to true' do let(:to_converter) { ToDataConverter.new(:rich_data => true, :symbol_as_string => true) } it 'A Hash with Symbol keys is silently converted to hash with String keys' do val = { :one => 'one', :two => 'two' } Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do write(val) val2 = read expect(val2).to be_a(Hash) expect(val2).to eql({ 'one' => 'one', 'two' => 'two' }) end expect(warnings).to be_empty end it 'A Hash with Symbol values is silently converted to hash with String values' do val = { 'one' => :one, 'two' => :two } Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do write(val) val2 = read expect(val2).to be_a(Hash) expect(val2).to eql({ 'one' => 'one', 'two' => 'two' }) end expect(warnings).to be_empty end it 'A Hash with default values will not loose type information' do val = { 'key' => :default } Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do write(val) val2 = read expect(val2).to be_a(Hash) expect(val2).to eql({ 'key' => :default }) end expect(warnings).to be_empty end end end context 'with local_reference set to false' do let(:to_converter) { ToDataConverter.new(:local_reference => false) } it 'A self referencing value will trigger an endless recursion error' do val = {} val['myself'] = val expect { write(val) }.to raise_error(/Endless recursion detected when attempting to serialize value of class Hash/) end end context 'will fail when' do it 'the value of a type description is something other than a String or a Hash' do expect do from_converter.convert({ '__pcore_type__' => { '__pcore_type__' => 'Pcore::TimestampType', '__pcore_value__' => 12345 }}) end.to raise_error(/Cannot create a Pcore::TimestampType from a (Fixnum|Integer)/) end end end end end puppet-5.5.10/spec/unit/pops/time/0000755005276200011600000000000013417162176016647 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/pops/time/timespan_spec.rb0000644005276200011600000001023213417161721022017 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' require 'puppet_spec/compiler' module Puppet::Pops module Time describe 'Timespan' do include PuppetSpec::Compiler let! (:simple) { Timespan.from_fields(false, 1, 3, 10, 11) } let! (:all_fields_hash) { {'days' => 1, 'hours' => 7, 'minutes' => 10, 'seconds' => 11, 'milliseconds' => 123, 'microseconds' => 456, 'nanoseconds' => 789} } let! (:complex) { Timespan.from_fields_hash(all_fields_hash) } context 'can be created from a String' do it 'using default format' do expect(Timespan.parse('1-03:10:11')).to eql(simple) end it 'using explicit format' do expect(Timespan.parse('1-7:10:11.123456789', '%D-%H:%M:%S.%N')).to eql(complex) end it 'using leading minus and explicit format' do expect(Timespan.parse('-1-7:10:11.123456789', '%D-%H:%M:%S.%N')).to eql(-complex) end it 'using %H as the biggest quantity' do expect(Timespan.parse('27:10:11', '%H:%M:%S')).to eql(simple) end it 'using %M as the biggest quantity' do expect(Timespan.parse('1630:11', '%M:%S')).to eql(simple) end it 'using %S as the biggest quantity' do expect(Timespan.parse('97811', '%S')).to eql(simple) end it 'where biggest quantity is not frist' do expect(Timespan.parse('11:1630', '%S:%M')).to eql(simple) end it 'raises an error when using %L as the biggest quantity' do expect { Timespan.parse('123', '%L') }.to raise_error(ArgumentError, /denotes fractions and must be used together with a specifier of higher magnitude/) end it 'raises an error when using %N as the biggest quantity' do expect { Timespan.parse('123', '%N') }.to raise_error(ArgumentError, /denotes fractions and must be used together with a specifier of higher magnitude/) end it 'where %L is treated as fractions of a second' do expect(Timespan.parse('0.4', '%S.%L')).to eql(Timespan.from_fields(false, 0, 0, 0, 0, 400)) end it 'where %N is treated as fractions of a second' do expect(Timespan.parse('0.4', '%S.%N')).to eql(Timespan.from_fields(false, 0, 0, 0, 0, 400)) end end context 'when presented as a String' do it 'uses default format for #to_s' do expect(simple.to_s).to eql('1-03:10:11.0') end context 'using a format' do it 'produces a string containing all components' do expect(complex.format('%D-%H:%M:%S.%N')).to eql('1-07:10:11.123456789') end it 'produces a literal % for %%' do expect(complex.format('%D%%%H:%M:%S')).to eql('1%07:10:11') end it 'produces a leading dash for negative instance' do expect((-complex).format('%D-%H:%M:%S')).to eql('-1-07:10:11') end it 'produces a string without trailing zeros for %-N' do expect(Timespan.parse('2.345', '%S.%N').format('%-S.%-N')).to eql('2.345') end it 'produces a string with trailing zeros for %N' do expect(Timespan.parse('2.345', '%S.%N').format('%-S.%N')).to eql('2.345000000') end it 'produces a string with trailing zeros for %0N' do expect(Timespan.parse('2.345', '%S.%N').format('%-S.%0N')).to eql('2.345000000') end it 'produces a string with trailing spaces for %_N' do expect(Timespan.parse('2.345', '%S.%N').format('%-S.%_N')).to eql('2.345 ') end end end context 'when converted to a hash' do it 'produces a hash with all numeric keys' do hash = complex.to_hash expect(hash).to eql(all_fields_hash) end it 'produces a compact hash with seconds and nanoseconds for #to_hash(true)' do hash = complex.to_hash(true) expect(hash).to eql({'seconds' => 112211, 'nanoseconds' => 123456789}) end context 'from a negative value' do it 'produces a hash with all numeric keys and negative = true' do hash = (-complex).to_hash expect(hash).to eql(all_fields_hash.merge('negative' => true)) end it 'produces a compact hash with negative seconds and negative nanoseconds for #to_hash(true)' do hash = (-complex).to_hash(true) expect(hash).to eql({'seconds' => -112211, 'nanoseconds' => -123456789}) end end end end end end puppet-5.5.10/spec/unit/pops/time/timestamp_spec.rb0000644005276200011600000000047413417161721022211 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' module Puppet::Pops module Time describe 'Timestamp' do it 'Does not loose microsecond precision when converted to/from String' do ts = Timestamp.new(1495789430910161286) expect(Timestamp.parse(ts.to_s)).to eql(ts) end end end end puppet-5.5.10/spec/unit/pops/types/0000755005276200011600000000000013417162177017056 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/pops/types/class_loader_spec.rb0000644005276200011600000000061413417161721023043 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' describe 'the Puppet::Pops::Types::ClassLoader' do it 'should produce path alternatives for CamelCase classes' do expected_paths = ['puppet_x/some_thing', 'puppetx/something'] # path_for_name method is private expect(Puppet::Pops::Types::ClassLoader.send(:paths_for_name, ['PuppetX', 'SomeThing'])).to include(*expected_paths) end end puppet-5.5.10/spec/unit/pops/types/enumeration_spec.rb0000644005276200011600000000275213417161721022743 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' module Puppet::Pops::Types describe 'The enumeration support' do it 'produces an enumerator for Array' do expect(Enumeration.enumerator([1,2,3]).respond_to?(:next)).to eql(true) end it 'produces an enumerator for Hash' do expect(Enumeration.enumerator({:a=>1}).respond_to?(:next)).to eql(true) end it 'produces a char enumerator for String' do enum = Enumeration.enumerator("abc") expect(enum.respond_to?(:next)).to eql(true) expect(enum.next).to eql('a') end it 'produces an enumerator for integer times' do enum = Enumeration.enumerator(2) expect(enum.next).to eql(0) expect(enum.next).to eql(1) expect{enum.next}.to raise_error(StopIteration) end it 'produces an enumerator for Integer range' do range = TypeFactory.range(1,2) enum = Enumeration.enumerator(range) expect(enum.next).to eql(1) expect(enum.next).to eql(2) expect{enum.next}.to raise_error(StopIteration) end it 'does not produce an enumerator for infinite Integer range' do range = TypeFactory.range(1,:default) enum = Enumeration.enumerator(range) expect(enum).to be_nil range = TypeFactory.range(:default,2) enum = Enumeration.enumerator(range) expect(enum).to be_nil end [3.14, /.*/, true, false, nil, :something].each do |x| it "does not produce an enumerator for object of type #{x.class}" do enum = Enumeration.enumerator(x) expect(enum).to be_nil end end end end puppet-5.5.10/spec/unit/pops/types/error_spec.rb0000644005276200011600000002020613417161721021540 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' require 'puppet_spec/compiler' module Puppet::Pops module Types describe 'Error type' do context 'when used in Puppet expressions' do include PuppetSpec::Compiler it 'is equal to itself only' do expect(eval_and_collect_notices(<<-CODE)).to eq(%w(true true false false)) $t = Error notice(Error =~ Type[Error]) notice(Error == Error) notice(Error < Error) notice(Error > Error) CODE end context "when parameterized" do it 'is equal other types with the same parameterization' do code = <<-CODE notice(Error['puppet/error'] == Error['puppet/error', default]) notice(Error['puppet/error', 'ouch'] == Error['puppet/error', 'ouch']) notice(Error['puppet/error', 'ouch'] != Error['puppet/error', 'ouch!']) CODE expect(eval_and_collect_notices(code)).to eq(%w(true true true)) end it 'is assignable from more qualified types' do expect(eval_and_collect_notices(<<-CODE)).to eq(%w(true true true)) notice(Error > Error['puppet/error']) notice(Error['puppet/error'] > Error['puppet/error', 'ouch']) notice(Error['puppet/error', default] > Error['puppet/error', 'ouch']) CODE end it 'is not assignable unless kind is assignable' do expect(eval_and_collect_notices(<<-CODE)).to eq(%w(true false true false true false true)) notice(Error[/a/] > Error['hah']) notice(Error[/a/] > Error['hbh']) notice(Error[Enum[a,b,c]] > Error[a]) notice(Error[Enum[a,b,c]] > Error[d]) notice(Error[Pattern[/a/, /b/]] > Error[a]) notice(Error[Pattern[/a/, /b/]] > Error[c]) notice(Error[Pattern[/a/, /b/]] > Error[Enum[a, b]]) CODE end it 'presents parsable string form' do code = <<-CODE notice(Error['a']) notice(Error[/a/]) notice(Error[Enum['a', 'b']]) notice(Error[Pattern[/a/, /b/]]) notice(Error['a', default]) notice(Error[/a/, default]) notice(Error[Enum['a', 'b'], default]) notice(Error[Pattern[/a/, /b/], default]) notice(Error[default,'a']) notice(Error[default,/a/]) notice(Error[default,Enum['a', 'b']]) notice(Error[default,Pattern[/a/, /b/]]) notice(Error['a','a']) notice(Error[/a/,/a/]) notice(Error[Enum['a', 'b'],Enum['a', 'b']]) notice(Error[Pattern[/a/, /b/],Pattern[/a/, /b/]]) CODE expect(eval_and_collect_notices(code)).to eq([ "Error['a']", 'Error[/a/]', "Error[Enum['a', 'b']]", "Error[Pattern[/a/, /b/]]", "Error['a']", 'Error[/a/]', "Error[Enum['a', 'b']]", "Error[Pattern[/a/, /b/]]", "Error[default, 'a']", 'Error[default, /a/]', "Error[default, Enum['a', 'b']]", "Error[default, Pattern[/a/, /b/]]", "Error['a', 'a']", 'Error[/a/, /a/]', "Error[Enum['a', 'b'], Enum['a', 'b']]", "Error[Pattern[/a/, /b/], Pattern[/a/, /b/]]", ]) end end context 'an Error instance' do it 'can be created using positional arguments' do code = <<-CODE $o = Error('bad things happened', 'puppet/error', {'detail' => 'val'}, 'OOPS') notice($o) notice(type($o)) CODE expect(eval_and_collect_notices(code)).to eq([ "Error({'msg' => 'bad things happened', 'kind' => 'puppet/error', 'details' => {'detail' => 'val'}, 'issue_code' => 'OOPS'})", "Error['puppet/error', 'OOPS']" ]) end it 'can be created using named arguments' do code = <<-CODE $o = Error(msg => 'Sorry, not implemented', kind => 'puppet/error', issue_code => 'NOT_IMPLEMENTED') notice($o) notice(type($o)) CODE expect(eval_and_collect_notices(code)).to eq([ "Error({'msg' => 'Sorry, not implemented', 'kind' => 'puppet/error', 'issue_code' => 'NOT_IMPLEMENTED'})", "Error['puppet/error', 'NOT_IMPLEMENTED']" ]) end it 'exposes message' do code = <<-CODE $o = Error(msg => 'Sorry, not implemented', kind => 'puppet/error', issue_code => 'NOT_IMPLEMENTED') notice($o.message) CODE expect(eval_and_collect_notices(code)).to eq(["Sorry, not implemented"]) end it 'exposes kind' do code = <<-CODE $o = Error(msg => 'Sorry, not implemented', kind => 'puppet/error', issue_code => 'NOT_IMPLEMENTED') notice($o.kind) CODE expect(eval_and_collect_notices(code)).to eq(["puppet/error"]) end it 'exposes issue_code' do code = <<-CODE $o = Error(msg => 'Sorry, not implemented', kind => 'puppet/error', issue_code => 'NOT_IMPLEMENTED') notice($o.issue_code) CODE expect(eval_and_collect_notices(code)).to eq(["NOT_IMPLEMENTED"]) end it 'exposes details' do code = <<-CODE $o = Error(msg => 'Sorry, not implemented', kind => 'puppet/error', details => { 'detailk' => 'detailv' }) notice($o.details) CODE expect(eval_and_collect_notices(code)).to eq(["{detailk => detailv}"]) end it 'is an instance of its inferred type' do code = <<-CODE $o = Error('bad things happened', 'puppet/error') notice($o =~ type($o)) CODE expect(eval_and_collect_notices(code)).to eq(['true']) end it 'is an instance of Error with matching kind' do code = <<-CODE $o = Error('bad things happened', 'puppet/error') notice($o =~ Error[/puppet\\/error/]) CODE expect(eval_and_collect_notices(code)).to eq(['true']) end it 'is an instance of Error with matching issue_code' do code = <<-CODE $o = Error('bad things happened', 'puppet/error', {}, 'FEE') notice($o =~ Error[default, 'FEE']) CODE expect(eval_and_collect_notices(code)).to eq(['true']) end it 'is an instance of Error with matching kind and issue_code' do code = <<-CODE $o = Error('bad things happened', 'puppet/error', {}, 'FEE') notice($o =~ Error['puppet/error', 'FEE']) CODE expect(eval_and_collect_notices(code)).to eq(['true']) end it 'is not an instance of Error unless kind matches' do code = <<-CODE $o = Error('bad things happened', 'puppetlabs/error') notice($o =~ Error[/puppet\\/error/]) CODE expect(eval_and_collect_notices(code)).to eq(['false']) end it 'is not an instance of Error unless issue_code matches' do code = <<-CODE $o = Error('bad things happened', 'puppetlabs/error', {}, 'BAR') notice($o =~ Error[default, 'FOO']) CODE expect(eval_and_collect_notices(code)).to eq(['false']) end it 'is not an instance of Error unless both kind and issue is a match' do code = <<-CODE $o = Error('bad things happened', 'puppet/error', {}, 'FEE') notice($o =~ Error['puppetlabs/error', 'FEE']) notice($o =~ Error['puppet/error', 'FUM']) CODE expect(eval_and_collect_notices(code)).to eq(['false', 'false']) end end end end end end puppet-5.5.10/spec/unit/pops/types/iterable_spec.rb0000644005276200011600000002233313417161721022201 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' module Puppet::Pops::Types describe 'The iterable support' do [ 0, 5, (3..10), %w(a b c), {'a'=>2}, 'hello', PIntegerType.new(1, 4), PEnumType.new(%w(yes no)) ].each do |obj| it "should consider instances of #{obj.class.name} to be Iterable" do expect(PIterableType::DEFAULT.instance?(obj)).to eq(true) end it "should yield an Iterable instance when Iterable.on is called with a #{obj.class.name}" do expect(Iterable.on(obj)).to be_a(Iterable) end end { -1 => 'a negative Integer', 5.times => 'an Enumerable', PIntegerType.new(nil, nil) => 'an unbounded Integer type' }.each_pair do |obj, desc| it "does not consider #{desc} to be Iterable" do expect(PIterableType::DEFAULT.instance?(obj)).to eq(false) end it "does not yield an Iterable when Iterable.on is called with #{desc}" do expect(Iterable.on(obj)).to be_nil end end context 'when testing assignability' do iterable_types = [ PIntegerType::DEFAULT, PStringType::DEFAULT, PIterableType::DEFAULT, PIteratorType::DEFAULT, PCollectionType::DEFAULT, PArrayType::DEFAULT, PHashType::DEFAULT, PTupleType::DEFAULT, PStructType::DEFAULT, PUnitType::DEFAULT ] iterable_types << PTypeType.new(PIntegerType.new(0, 10)) iterable_types << PTypeType.new(PEnumType.new(%w(yes no))) iterable_types << PRuntimeType.new(:ruby, 'Puppet::Pops::Types::Iterator') iterable_types << PVariantType.new(iterable_types.clone) not_iterable_types = [ PAnyType::DEFAULT, PBooleanType::DEFAULT, PCallableType::DEFAULT, PCatalogEntryType::DEFAULT, PDefaultType::DEFAULT, PFloatType::DEFAULT, PClassType::DEFAULT, PNotUndefType::DEFAULT, PNumericType::DEFAULT, POptionalType::DEFAULT, PPatternType::DEFAULT, PRegexpType::DEFAULT, PResourceType::DEFAULT, PRuntimeType::DEFAULT, PScalarType::DEFAULT, PScalarDataType::DEFAULT, PTypeType::DEFAULT, PUndefType::DEFAULT ] not_iterable_types << PTypeType.new(PIntegerType::DEFAULT) not_iterable_types << PVariantType.new([iterable_types[0], not_iterable_types[0]]) iterable_types.each do |type| it "should consider #{type} to be assignable to Iterable type" do expect(PIterableType::DEFAULT.assignable?(type)).to eq(true) end end not_iterable_types.each do |type| it "should not consider #{type} to be assignable to Iterable type" do expect(PIterableType::DEFAULT.assignable?(type)).to eq(false) end end it "should consider Type[Integer[0,5]] to be assignable to Iterable[Integer[0,5]]" do expect(PIterableType.new(PIntegerType.new(0,5)).assignable?(PTypeType.new(PIntegerType.new(0,5)))).to eq(true) end it "should consider Type[Enum[yes,no]] to be assignable to Iterable[Enum[yes,no]]" do expect(PIterableType.new(PEnumType.new(%w(yes no))).assignable?(PTypeType.new(PEnumType.new(%w(yes no))))).to eq(true) end it "should not consider Type[Enum[ok,fail]] to be assignable to Iterable[Enum[yes,no]]" do expect(PIterableType.new(PEnumType.new(%w(ok fail))).assignable?(PTypeType.new(PEnumType.new(%w(yes no))))).to eq(false) end it "should not consider Type[String] to be assignable to Iterable[String]" do expect(PIterableType.new(PStringType::DEFAULT).assignable?(PTypeType.new(PStringType::DEFAULT))).to eq(false) end end it 'does not wrap an Iterable in another Iterable' do x = Iterable.on(5) expect(Iterable.on(x)).to equal(x) end it 'produces a "times" iterable on integer' do expect{ |b| Iterable.on(3).each(&b) }.to yield_successive_args(0,1,2) end it 'produces an iterable with element type Integer[0,X-1] for an iterable on an integer X' do expect(Iterable.on(3).element_type).to eq(PIntegerType.new(0,2)) end it 'produces a step iterable on an integer' do expect{ |b| Iterable.on(8).step(3, &b) }.to yield_successive_args(0, 3, 6) end it 'produces a reverse iterable on an integer' do expect{ |b| Iterable.on(5).reverse_each(&b) }.to yield_successive_args(4,3,2,1,0) end it 'produces an iterable on a integer range' do expect{ |b| Iterable.on(2..7).each(&b) }.to yield_successive_args(2,3,4,5,6,7) end it 'produces an iterable with element type Integer[X,Y] for an iterable on an integer range (X..Y)' do expect(Iterable.on(2..7).element_type).to eq(PIntegerType.new(2,7)) end it 'produces an iterable on a character range' do expect{ |b| Iterable.on('a'..'f').each(&b) }.to yield_successive_args('a', 'b', 'c', 'd', 'e', 'f') end it 'produces a step iterable on a range' do expect{ |b| Iterable.on(1..5).step(2, &b) }.to yield_successive_args(1,3,5) end it 'produces a reverse iterable on a range' do expect{ |b| Iterable.on(2..7).reverse_each(&b) }.to yield_successive_args(7,6,5,4,3,2) end it 'produces an iterable with element type String with a size constraint for an iterable on a character range' do expect(Iterable.on('a'..'fe').element_type).to eq(PStringType.new(PIntegerType.new(1,2))) end it 'produces an iterable on a bounded Integer type' do expect{ |b| Iterable.on(PIntegerType.new(2,7)).each(&b) }.to yield_successive_args(2,3,4,5,6,7) end it 'produces an iterable with element type Integer[X,Y] for an iterable on Integer[X,Y]' do expect(Iterable.on(PIntegerType.new(2,7)).element_type).to eq(PIntegerType.new(2,7)) end it 'produces an iterable on String' do expect{ |b| Iterable.on('eat this').each(&b) }.to yield_successive_args('e', 'a', 't', ' ', 't', 'h', 'i', 's') end it 'produces an iterable with element type String[1,1] for an iterable created on a String' do expect(Iterable.on('eat this').element_type).to eq(PStringType.new(PIntegerType.new(1,1))) end it 'produces an iterable on Array' do expect{ |b| Iterable.on([1,5,9]).each(&b) }.to yield_successive_args(1,5,9) end it 'produces an iterable with element type inferred from the array elements for an iterable on Array' do expect(Iterable.on([1,5,5,9,9,9]).element_type).to eq(PVariantType.new([PIntegerType.new(1,1), PIntegerType.new(5,5), PIntegerType.new(9,9)])) end it 'can chain reverse_each after step on Iterable' do expect{ |b| Iterable.on(6).step(2).reverse_each(&b) }.to yield_successive_args(4,2,0) end it 'can chain reverse_each after step on Integer range' do expect{ |b| Iterable.on(PIntegerType.new(0, 5)).step(2).reverse_each(&b) }.to yield_successive_args(4,2,0) end it 'can chain step after reverse_each on Iterable' do expect{ |b| Iterable.on(6).reverse_each.step(2, &b) }.to yield_successive_args(5,3,1) end it 'can chain step after reverse_each on Integer range' do expect{ |b| Iterable.on(PIntegerType.new(0, 5)).reverse_each.step(2, &b) }.to yield_successive_args(5,3,1) end it 'will produce the same result for each as for reverse_each.reverse_each' do x1 = Iterable.on(5) x2 = Iterable.on(5) expect(x1.reduce([]) { |a,i| a << i; a}).to eq(x2.reverse_each.reverse_each.reduce([]) { |a,i| a << i; a}) end it 'can chain many nested step/reverse_each calls' do # x = Iterable.on(18).step(3) (0, 3, 6, 9, 12, 15) # x = x.reverse_each (15, 12, 9, 6, 3, 0) # x = x.step(2) (15, 9, 3) # x = x.reverse_each(3, 9, 15) expect{ |b| Iterable.on(18).step(3).reverse_each.step(2).reverse_each(&b) }.to yield_successive_args(3, 9, 15) end it 'can chain many nested step/reverse_each calls on Array iterable' do expect{ |b| Iterable.on(18.times.to_a).step(3).reverse_each.step(2).reverse_each(&b) }.to yield_successive_args(3, 9, 15) end it 'produces an steppable iterable for Array' do expect{ |b| Iterable.on(%w(a b c d e f g h i)).step(3, &b) }.to yield_successive_args('a', 'd', 'g') end it 'produces an reverse steppable iterable for Array' do expect{ |b| Iterable.on(%w(a b c d e f g h i)).reverse_each.step(3, &b) }.to yield_successive_args('i', 'f', 'c') end it 'responds false when a bounded Iterable is passed to Iterable.unbounded?' do expect(Iterable.unbounded?(Iterable.on(%w(a b c d e f g h i)))).to eq(false) end it 'can create an Array from a bounded Iterable' do expect(Iterable.on(%w(a b c d e f g h i)).to_a).to eq(%w(a b c d e f g h i)) end class TestUnboundedIterator include Enumerable include Iterable def step(step_size) if block_given? begin current = 0 loop do yield(@current) current = current + step_size end rescue StopIteration end end self end end it 'responds true when an unbounded Iterable is passed to Iterable.unbounded?' do ubi = TestUnboundedIterator.new expect(Iterable.unbounded?(Iterable.on(ubi))).to eq(true) end it 'can not create an Array from an unbounded Iterable' do ubi = TestUnboundedIterator.new expect{ Iterable.on(ubi).to_a }.to raise_error(Puppet::Error, /Attempt to create an Array from an unbounded Iterable/) end it 'will produce the string Iterator[T] on to_s on an iterator instance with element type T' do expect(Iterable.on(18).to_s).to eq('Iterator[Integer]-Value') end end end puppet-5.5.10/spec/unit/pops/types/p_binary_type_spec.rb0000644005276200011600000001762013417161721023261 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' require 'puppet_spec/compiler' module Puppet::Pops module Types describe 'Binary Type' do include PuppetSpec::Compiler context 'as a type' do it 'can be created with the type factory' do t = TypeFactory.binary() expect(t).to be_a(PBinaryType) expect(t).to eql(PBinaryType::DEFAULT) end end context 'a Binary instance' do it 'can be created from a raw String using %r, raw string mode' do str = [0xF1].pack("C*") code = <<-CODE $x = Binary($testing, '%r') notice(assert_type(Binary, $x)) CODE expect(eval_and_collect_notices(code, Puppet::Node.new('foonode'), { 'testing' => str })).to eql(['8Q==']) end it 'can be created from a String using %s, string mode' do # the text 'binar' needs padding with '=' code = <<-CODE $x = Binary('binary', '%s') notice(assert_type(Binary, $x)) CODE expect(eval_and_collect_notices(code)).to eql(['YmluYXJ5']) end it 'format %s errors if encoding is bad in given string when using format %s and a broken UTF-8 string' do str = [0xF1].pack("C*") str.force_encoding('UTF-8') code = <<-CODE $x = Binary($testing, '%s') notice(assert_type(Binary, $x)) CODE expect { eval_and_collect_notices(code, Puppet::Node.new('foonode'), { 'testing' => str }) }.to raise_error(/.*The given string in encoding 'UTF-8' is invalid\. Cannot create a Binary UTF-8 representation.*/) end it 'format %s errors if encoding is correct but string cannot be transcoded to UTF-8' do str = [0xF1].pack("C*") code = <<-CODE $x = Binary($testing, '%s') notice(assert_type(Binary, $x)) CODE expect { eval_and_collect_notices(code, Puppet::Node.new('foonode'), { 'testing' => str }) }.to raise_error(/.*"\\xF1" from ASCII-8BIT to UTF-8.*/) end it 'can be created from a strict Base64 encoded String using default format' do code = <<-CODE $x = Binary('YmluYXI=') notice(assert_type(Binary, $x)) CODE expect(eval_and_collect_notices(code)).to eql(['YmluYXI=']) end it 'will error creation in strict mode if padding is missing when using default format' do # the text 'binar' needs padding with '=' (missing here to trigger error code = <<-CODE $x = Binary('YmluYXI') notice(assert_type(Binary, $x)) CODE expect{ eval_and_collect_notices(code) }.to raise_error(/invalid base64/) end it 'can be created from a Base64 encoded String using %B, strict mode' do # the text 'binar' needs padding with '=' code = <<-CODE $x = Binary('YmluYXI=', '%B') notice(assert_type(Binary, $x)) CODE expect(eval_and_collect_notices(code)).to eql(['YmluYXI=']) end it 'will error creation in strict mode if padding is missing' do # the text 'binar' needs padding with '=' (missing here to trigger error code = <<-CODE $x = Binary('YmluYXI', '%B') notice(assert_type(Binary, $x)) CODE expect{ eval_and_collect_notices(code) }.to raise_error(/invalid base64/) end it 'will not error creation in base mode if padding is missing' do # the text 'binar' needs padding with '=' (missing here to trigger possible error) code = <<-CODE $x = Binary('YmluYXI', '%b') notice(assert_type(Binary, $x)) CODE expect(eval_and_collect_notices(code)).to eql(['YmluYXI=']) end it 'will not error creation in base mode if padding is not required' do # the text 'binary' does not need padding with '=' code = <<-CODE $x = Binary('YmluYXJ5', '%b') notice(assert_type(Binary, $x)) CODE expect(eval_and_collect_notices(code)).to eql(['YmluYXJ5']) end it 'can be compared to another instance for equality' do code = <<-CODE $x = Binary('YmluYXJ5') $y = Binary('YmluYXJ5') notice($x == $y) notice($x != $y) CODE expect(eval_and_collect_notices(code)).to eql(['true', 'false']) end it 'can be created from an array of byte values' do # the text 'binar' needs padding with '=' code = <<-CODE $x = Binary([251, 239, 255]) notice(assert_type(Binary, $x)) CODE expect(eval_and_collect_notices(code)).to eql(['++//']) end it "can be created from an hash with value and format" do # the text 'binar' needs padding with '=' code = <<-CODE $x = Binary({value => '--__', format => '%u'}) notice(assert_type(Binary, $x)) CODE expect(eval_and_collect_notices(code)).to eql(['++//']) end it "can be created from an hash with value and default format" do code = <<-CODE $x = Binary({value => 'YmluYXI='}) notice(assert_type(Binary, $x)) CODE expect(eval_and_collect_notices(code)).to eql(['YmluYXI=']) end it 'can be created from a hash with value being an array' do # the text 'binar' needs padding with '=' code = <<-CODE $x = Binary({value => [251, 239, 255]}) notice(assert_type(Binary, $x)) CODE expect(eval_and_collect_notices(code)).to eql(['++//']) end it "can be created from an Base64 using URL safe encoding by specifying '%u' format'" do # the text 'binar' needs padding with '=' code = <<-CODE $x = Binary('--__', '%u') notice(assert_type(Binary, $x)) CODE expect(eval_and_collect_notices(code)).to eql(['++//']) end it "when created with URL safe encoding chars in '%b' format, these are skipped" do code = <<-CODE $x = Binary('--__YmluYXJ5', '%b') notice(assert_type(Binary, $x)) CODE expect(eval_and_collect_notices(code)).to eql(['YmluYXJ5']) end it "will error in strict format if string contains URL safe encoded chars" do code = <<-CODE $x = Binary('--__YmluYXJ5', '%B') notice(assert_type(Binary, $x)) CODE expect { eval_and_collect_notices(code) }.to raise_error(/invalid base64/) end [ '<', '<=', '>', '>=' ].each do |op| it "cannot be compared to another instance for magnitude using #{op}" do code = <<-"CODE" $x = Binary('YmluYXJ5') $y = Binary('YmluYXJ5') $x #{op} $y CODE expect { eval_and_collect_notices(code)}.to raise_error(/Comparison of: Binary #{op} Binary, is not possible/) end end it 'can be matched against a Binary in case expression' do code = <<-CODE case Binary('YmluYXJ5') { Binary('YWxpZW4='): { notice('nope') } Binary('YmluYXJ5'): { notice('yay') } default: { notice('nope') } } CODE expect(eval_and_collect_notices(code)).to eql(['yay']) end it "can be matched against a Binary subsequence using 'in' expression" do # finding 'one' in 'one two three' code = <<-CODE notice(Binary("b25l") in Binary("b25lIHR3byB0aHJlZQ==")) notice(Binary("c25l") in Binary("b25lIHR3byB0aHJlZQ==")) CODE expect(eval_and_collect_notices(code)).to eql(['true', 'false']) end it "can be matched against a byte value using 'in' expression" do # finding 'e' (ascii 101) in 'one two three' code = <<-CODE notice(101 in Binary("b25lIHR3byB0aHJlZQ==")) notice(101.0 in Binary("b25lIHR3byB0aHJlZQ==")) notice(102 in Binary("b25lIHR3byB0aHJlZQ==")) CODE expect(eval_and_collect_notices(code)).to eql(['true', 'true', 'false']) end it "has a length method in ruby returning the length measured in bytes" do # \u{1f452} is "woman's hat emoji - 4 bytes in UTF-8" a_binary = PBinaryType::Binary.new("\u{1f452}") expect(a_binary.length).to be(4) end end end end end puppet-5.5.10/spec/unit/pops/types/p_init_type_spec.rb0000644005276200011600000003220613417161721022735 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' require 'puppet_spec/compiler' module Puppet::Pops module Types describe 'The Init Type' do include PuppetSpec::Compiler include_context 'types_setup' context 'when used in Puppet expressions' do it 'an unparameterized type can be used' do code = <<-CODE notice(type(Init)) CODE expect(eval_and_collect_notices(code)).to eql(['Type[Init]']) end it 'a parameterized type can be used' do code = <<-CODE notice(type(Init[Integer])) CODE expect(eval_and_collect_notices(code)).to eql(['Type[Init[Integer]]']) end it 'a parameterized type can have additional arguments' do code = <<-CODE notice(type(Init[Integer, 16])) CODE expect(eval_and_collect_notices(code)).to eql(['Type[Init[Integer, 16]]']) end (all_types - abstract_types - internal_types).map { |tc| tc::DEFAULT.name }.each do |type_name| it "can be created on a #{type_name}" do code = <<-CODE notice(type(Init[#{type_name}])) CODE expect(eval_and_collect_notices(code)).to eql(["Type[Init[#{type_name}]]"]) end end (abstract_types - internal_types).map { |tc| tc::DEFAULT.name }.each do |type_name| it "cannot be created on a #{type_name}" do code = <<-CODE type(Init[#{type_name}]('x')) CODE expect { eval_and_collect_notices(code) }.to raise_error(/Creation of new instance of type '#{type_name}' is not supported/) end end it 'an Init[Integer, 16] can create an instance from a String' do code = <<-CODE notice(Init[Integer, 16]('100')) CODE expect(eval_and_collect_notices(code)).to eql(['256']) end it "an Init[String,'%x'] can create an instance from an Integer" do code = <<-CODE notice(Init[String,'%x'](128)) CODE expect(eval_and_collect_notices(code)).to eql(['80']) end it "an Init[String,23] raises an error because no available dispatcher exists" do code = <<-CODE notice(Init[String,23](128)) CODE expect { eval_and_collect_notices(code) }.to raise_error( /The type 'Init\[String, 23\]' does not represent a valid set of parameters for String\.new\(\)/) end it 'an Init[String] can create an instance from an Integer' do code = <<-CODE notice(Init[String](128)) CODE expect(eval_and_collect_notices(code)).to eql(['128']) end it 'an Init[String] can create an instance from an array with an Integer' do code = <<-CODE notice(Init[String]([128])) CODE expect(eval_and_collect_notices(code)).to eql(['128']) end it 'an Init[String] can create an instance from an array with an Integer and a format' do code = <<-CODE notice(Init[String]([128, '%x'])) CODE expect(eval_and_collect_notices(code)).to eql(['80']) end it 'an Init[String] can create an instance from an array with an array' do code = <<-CODE notice(Init[String]([[128, '%x']])) CODE expect(eval_and_collect_notices(code)).to eql(["[128, '%x']"]) end it 'an Init[Binary] can create an instance from a string' do code = <<-CODE notice(Init[Binary]('b25lIHR3byB0aHJlZQ==')) CODE expect(eval_and_collect_notices(code)).to eql(['b25lIHR3byB0aHJlZQ==']) end it 'an Init[String] can not create an instance from an Integer and a format unless given as an array argument' do code = <<-CODE notice(Init[String](128, '%x')) CODE expect { eval_and_collect_notices(code) }.to raise_error(/'new_Init' expects 1 argument, got 2/) end it 'the array [128] is an instance of Init[String]' do code = <<-CODE notice(assert_type(Init[String], [128])) CODE expect(eval_and_collect_notices(code)).to eql(['[128]']) end it 'the value 128 is an instance of Init[String]' do code = <<-CODE notice(assert_type(Init[String], 128)) CODE expect(eval_and_collect_notices(code)).to eql(['128']) end it "the array [128] is an instance of Init[String]" do code = <<-CODE notice(assert_type(Init[String], [128])) CODE expect(eval_and_collect_notices(code)).to eql(['[128]']) end it "the array [128, '%x'] is an instance of Init[String]" do code = <<-CODE notice(assert_type(Init[String], [128, '%x'])) CODE expect(eval_and_collect_notices(code)).to eql(['[128, %x]']) end it "the array [[128, '%x']] is an instance of Init[String]" do code = <<-CODE notice(assert_type(Init[String], [[128, '%x']])) CODE expect(eval_and_collect_notices(code)).to eql(['[[128, %x]]']) end it 'RichData is assignable to Init' do code = <<-CODE notice(Init > RichData) CODE expect(eval_and_collect_notices(code)).to eql(['true']) end it 'Runtime is not assignable to Init' do code = <<-CODE notice(Init > Runtime['ruby', 'Time']) CODE expect(eval_and_collect_notices(code)).to eql(['false']) end it 'Init is not assignable to RichData' do code = <<-CODE notice(Init < RichData) CODE expect(eval_and_collect_notices(code)).to eql(['false']) end it 'Init[T1] is assignable to Init[T2] when T1 is assignable to T2' do code = <<-CODE notice(Init[Integer] < Init[Numeric]) notice(Init[Tuple[Integer]] < Init[Array[Integer]]) CODE expect(eval_and_collect_notices(code)).to eql(['true', 'true']) end it 'Init[T1] is not assignable to Init[T2] unless T1 is assignable to T2' do code = <<-CODE notice(Init[Integer] > Init[Numeric]) notice(Init[Tuple[Integer]] > Init[Array[Integer]]) CODE expect(eval_and_collect_notices(code)).to eql(['false', 'false']) end it 'T is assignable to Init[T]' do code = <<-CODE notice(Integer < Init[Integer]) CODE expect(eval_and_collect_notices(code)).to eql(['true']) end it 'T1 is assignable to Init[T2] if T2 can be created from instance of T1' do code = <<-CODE notice(Integer < Init[String]) CODE expect(eval_and_collect_notices(code)).to eql(['true']) end it 'a RichData value is an instance of Init' do code = <<-CODE notice( String(assert_type(Init, { 'a' => [ 1, 2, Sensitive(3) ], 2 => Timestamp('2014-12-01T13:15:00') }), { Any => { format => '%s', string_formats => { Any => '%s' }}})) CODE expect(eval_and_collect_notices(code)).to eql(['{a => [1, 2, Sensitive [value redacted]], 2 => 2014-12-01T13:15:00.000000000 UTC}']) end it 'an Init[Sensitive[String]] can create an instance from a String' do code = <<-CODE notice(Init[Sensitive[String]]('256')) CODE expect(eval_and_collect_notices(code)).to eql(['Sensitive [value redacted]']) end it 'an Init[Type] can create an instance from a String' do code = <<-CODE notice(Init[Type]('Integer[2,3]')) CODE expect(eval_and_collect_notices(code)).to eql(['Integer[2, 3]']) end it 'an Init[Type[Numeric]] can not create an unassignable type from a String' do code = <<-CODE notice(Init[Type[Numeric]]('String')) CODE expect { eval_and_collect_notices(code) }.to raise_error( /Converted value from Type\[Numeric\]\.new\(\) has wrong type, expects a Type\[Numeric\] value, got Type\[String\]/) end it 'an Init with a custom object type can create an instance from a Hash' do code = <<-CODE type MyType = Object[{ attributes => { granularity => String, price => String, category => Integer } }] notice(Init[MyType]({ granularity => 'fine', price => '$10', category => 23 })) CODE expect(eval_and_collect_notices(code)).to eql(["MyType({'granularity' => 'fine', 'price' => '$10', 'category' => 23})"]) end it 'an Init with a custom object type and additional parameters can create an instance from a value' do code = <<-CODE type MyType = Object[{ attributes => { granularity => String, price => String, category => Integer } }] notice(Init[MyType,'$5',20]('coarse')) CODE expect(eval_and_collect_notices(code)).to eql(["MyType({'granularity' => 'coarse', 'price' => '$5', 'category' => 20})"]) end it 'an Init with a custom object with one Array parameter, can be created from an Array' do code = <<-CODE type MyType = Object[{ attributes => { array => Array[String] } }] notice(Init[MyType](['a'])) CODE expect(eval_and_collect_notices(code)).to eql(["MyType({'array' => ['a']})"]) end it 'an Init type can be used recursively' do # Even if it doesn't make sense, it should not crash code = <<-CODE type One = Object[{ attributes => { init_one => Variant[Init[One],String] } }] notice(Init[One]('w')) CODE expect(eval_and_collect_notices(code)).to eql(["One({'init_one' => 'w'})"]) end context 'computes if x is an instance such that' do %w(true false True False TRUE FALSE tRuE FaLsE Yes No yes no YES NO YeS nO y n Y N).each do |str| it "string '#{str}' is an instance of Init[Boolean]" do expect(eval_and_collect_notices("notice('#{str}' =~ Init[Boolean])")).to eql(['true']) end end it 'arbitrary string is not an instance of Init[Boolean]' do expect(eval_and_collect_notices("notice('blue' =~ Init[Boolean])")).to eql(['false']) end it 'empty string is not an instance of Init[Boolean]' do expect(eval_and_collect_notices("notice('' =~ Init[Boolean])")).to eql(['false']) end it 'undef string is not an instance of Init[Boolean]' do expect(eval_and_collect_notices("notice(undef =~ Init[Boolean])")).to eql(['false']) end %w(0 1 0634 0x3b -0xba 0b1001 +0b1111 23.14 -2.3 2e-21 1.23e18 -0.23e18).each do |str| it "string '#{str}' is an instance of Init[Numeric]" do expect(eval_and_collect_notices("notice('#{str}' =~ Init[Numeric])")).to eql(['true']) end end it 'non numeric string is not an instance of Init[Numeric]' do expect(eval_and_collect_notices("notice('blue' =~ Init[Numeric])")).to eql(['false']) end it 'empty string is not an instance of Init[Numeric]' do expect(eval_and_collect_notices("notice('' =~ Init[Numeric])")).to eql(['false']) end it 'undef is not an instance of Init[Numeric]' do expect(eval_and_collect_notices("notice(undef =~ Init[Numeric])")).to eql(['false']) end %w(0 1 0634 0x3b -0xba 0b1001 +0b1111 23.14 -2.3 2e-21 1.23e18 -0.23e18).each do |str| it "string '#{str}' is an instance of Init[Float]" do expect(eval_and_collect_notices("notice('#{str}' =~ Init[Float])")).to eql(['true']) end end it 'non numeric string is not an instance of Init[Float]' do expect(eval_and_collect_notices("notice('blue' =~ Init[Float])")).to eql(['false']) end it 'empty string is not an instance of Init[Float]' do expect(eval_and_collect_notices("notice('' =~ Init[Float])")).to eql(['false']) end it 'undef is not an instance of Init[Float]' do expect(eval_and_collect_notices("notice(undef =~ Init[Float])")).to eql(['false']) end %w(0 1 0634 0x3b -0xba 0b1001 0b1111).each do |str| it "string '#{str}' is an instance of Init[Integer]" do expect(eval_and_collect_notices("notice('#{str}' =~ Init[Integer])")).to eql(['true']) end end %w(23.14 -2.3 2e-21 1.23e18 -0.23e18).each do |str| it "valid float string '#{str}' is not an instance of Init[Integer]" do expect(eval_and_collect_notices("notice('#{str}' =~ Init[Integer])")).to eql(['false']) end end it 'non numeric string is not an instance of Init[Integer]' do expect(eval_and_collect_notices("notice('blue' =~ Init[Integer])")).to eql(['false']) end it 'empty string is not an instance of Init[Integer]' do expect(eval_and_collect_notices("notice('' =~ Init[Integer])")).to eql(['false']) end it 'undef is not an instance of Init[Integer]' do expect(eval_and_collect_notices("notice(undef =~ Init[Integer])")).to eql(['false']) end %w(1.2.3 1.1.1-a3 1.2.3+b3 1.2.3-a3+b3).each do |str| it "string '#{str}' is an instance of Init[SemVer]" do expect(eval_and_collect_notices("notice('#{str}' =~ Init[SemVer])")).to eql(['true']) end end it 'non SemVer compliant string is not an instance of Init[SemVer]' do expect(eval_and_collect_notices("notice('blue' =~ Init[SemVer])")).to eql(['false']) end it 'empty string is not an instance of Init[SemVer]' do expect(eval_and_collect_notices("notice('' =~ Init[SemVer])")).to eql(['false']) end it 'undef is not an instance of Init[SemVer]' do expect(eval_and_collect_notices("notice(undef =~ Init[SemVer])")).to eql(['false']) end end end end end end puppet-5.5.10/spec/unit/pops/types/p_sem_ver_type_spec.rb0000644005276200011600000002502113417161721023427 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' require 'puppet_spec/compiler' module Puppet::Pops module Types describe 'Semantic Versions' do include PuppetSpec::Compiler context 'the SemVer type' do it 'is normalized in a Variant' do t = TypeFactory.variant(TypeFactory.sem_ver('>=1.0.0 <2.0.0'), TypeFactory.sem_ver('>=1.5.0 <4.0.0')).normalize expect(t).to be_a(PSemVerType) expect(t).to eql(TypeFactory.sem_ver('>=1.0.0 <4.0.0')) end context 'convert method' do it 'returns nil on a nil argument' do expect(PSemVerType.convert(nil)).to be_nil end it 'returns its argument when the argument is a version' do v = SemanticPuppet::Version.new(1,0,0) expect(PSemVerType.convert(v)).to equal(v) end it 'converts a valid version string argument to a version' do v = SemanticPuppet::Version.new(1,0,0) expect(PSemVerType.convert('1.0.0')).to eq(v) end it 'raises an error string that does not represent a valid version' do expect{PSemVerType.convert('1-3')}.to raise_error(ArgumentError) end end end context 'the SemVerRange type' do context 'convert method' do it 'returns nil on a nil argument' do expect(PSemVerRangeType.convert(nil)).to be_nil end it 'returns its argument when the argument is a version range' do vr = SemanticPuppet::VersionRange.parse('1.x') expect(PSemVerRangeType.convert(vr)).to equal(vr) end it 'converts a valid version string argument to a version range' do vr = SemanticPuppet::VersionRange.parse('1.x') expect(PSemVerRangeType.convert('1.x')).to eq(vr) end it 'raises an error string that does not represent a valid version range' do expect{PSemVerRangeType.convert('x3')}.to raise_error(ArgumentError) end end end context 'when used in Puppet expressions' do context 'the SemVer type' do it 'can have multiple range arguments' do code = <<-CODE $t = SemVer[SemVerRange('>=1.0.0 <2.0.0'), SemVerRange('>=3.0.0 <4.0.0')] notice(SemVer('1.2.3') =~ $t) notice(SemVer('2.3.4') =~ $t) notice(SemVer('3.4.5') =~ $t) CODE expect(eval_and_collect_notices(code)).to eql(['true', 'false', 'true']) end it 'can have multiple range arguments in string form' do code = <<-CODE $t = SemVer['>=1.0.0 <2.0.0', '>=3.0.0 <4.0.0'] notice(SemVer('1.2.3') =~ $t) notice(SemVer('2.3.4') =~ $t) notice(SemVer('3.4.5') =~ $t) CODE expect(eval_and_collect_notices(code)).to eql(['true', 'false', 'true']) end it 'range arguments are normalized' do code = <<-CODE notice(SemVer['>=1.0.0 <2.0.0', '>=1.5.0 <4.0.0'] == SemVer['>=1.0.0 <4.0.0']) CODE expect(eval_and_collect_notices(code)).to eql(['true']) end it 'is assignable to a type containing ranges with a merged range that is assignable but individual ranges are not' do code = <<-CODE $x = SemVer['>=1.0.0 <2.0.0', '>=1.5.0 <3.0.0'] $y = SemVer['>=1.2.0 <2.8.0'] notice($y < $x) CODE expect(eval_and_collect_notices(code)).to eql(['true']) end end context 'the SemVerRange type' do it 'a range is an instance of the type' do code = <<-CODE notice(SemVerRange('3.0.0 - 4.0.0') =~ SemVerRange) CODE expect(eval_and_collect_notices(code)).to eql(['true']) end end context 'a SemVer instance' do it 'can be created from a String' do code = <<-CODE $x = SemVer('1.2.3') notice(assert_type(SemVer, $x)) CODE expect(eval_and_collect_notices(code)).to eql(['1.2.3']) end it 'can be compared to another instance for equality' do code = <<-CODE $x = SemVer('1.2.3') $y = SemVer('1.2.3') notice($x == $y) notice($x != $y) CODE expect(eval_and_collect_notices(code)).to eql(['true', 'false']) end it 'can be compared to another instance for magnitude' do code = <<-CODE $x = SemVer('1.1.1') $y = SemVer('1.2.3') notice($x < $y) notice($x <= $y) notice($x > $y) notice($x >= $y) CODE expect(eval_and_collect_notices(code)).to eql(['true', 'true', 'false', 'false']) end it 'can be matched against a version range' do code = <<-CODE $v = SemVer('1.1.1') notice($v =~ SemVerRange('>1.0.0')) notice($v =~ SemVerRange('>1.1.1')) notice($v =~ SemVerRange('>=1.1.1')) CODE expect(eval_and_collect_notices(code)).to eql(['true', 'false', 'true']) end it 'can be matched against a SemVerRange in case expression' do code = <<-CODE case SemVer('1.1.1') { SemVerRange('>1.1.1'): { notice('high') } SemVerRange('>1.0.0'): { notice('mid') } default: { notice('low') } } CODE expect(eval_and_collect_notices(code)).to eql(['mid']) end it 'can be matched against a SemVer in case expression' do code = <<-CODE case SemVer('1.1.1') { SemVer('1.1.0'): { notice('high') } SemVer('1.1.1'): { notice('mid') } default: { notice('low') } } CODE expect(eval_and_collect_notices(code)).to eql(['mid']) end it "can be matched against a versions in 'in' expression" do code = <<-CODE notice(SemVer('1.1.1') in [SemVer('1.0.0'), SemVer('1.1.1'), SemVer('2.3.4')]) CODE expect(eval_and_collect_notices(code)).to eql(['true']) end it "can be matched against a VersionRange using an 'in' expression" do code = <<-CODE notice(SemVer('1.1.1') in SemVerRange('>1.0.0')) CODE expect(eval_and_collect_notices(code)).to eql(['true']) end it "can be matched against multiple VersionRanges using an 'in' expression" do code = <<-CODE notice(SemVer('1.1.1') in [SemVerRange('>=1.0.0 <1.0.2'), SemVerRange('>=1.1.0 <1.1.2')]) CODE expect(eval_and_collect_notices(code)).to eql(['true']) end end context 'a String representing a SemVer' do it 'can be matched against a version range' do code = <<-CODE $v = '1.1.1' notice($v =~ SemVerRange('>1.0.0')) notice($v =~ SemVerRange('>1.1.1')) notice($v =~ SemVerRange('>=1.1.1')) CODE expect(eval_and_collect_notices(code)).to eql(['true', 'false', 'true']) end it 'can be matched against a SemVerRange in case expression' do code = <<-CODE case '1.1.1' { SemVerRange('>1.1.1'): { notice('high') } SemVerRange('>1.0.0'): { notice('mid') } default: { notice('low') } } CODE expect(eval_and_collect_notices(code)).to eql(['mid']) end it 'can be matched against a SemVer in case expression' do code = <<-CODE case '1.1.1' { SemVer('1.1.0'): { notice('high') } SemVer('1.1.1'): { notice('mid') } default: { notice('low') } } CODE expect(eval_and_collect_notices(code)).to eql(['mid']) end it "can be matched against a VersionRange using an 'in' expression" do code = <<-CODE notice('1.1.1' in SemVerRange('>1.0.0')) CODE expect(eval_and_collect_notices(code)).to eql(['true']) end it "can be matched against multiple VersionRanges using an 'in' expression" do code = <<-CODE notice('1.1.1' in [SemVerRange('>=1.0.0 <1.0.2'), SemVerRange('>=1.1.0 <1.1.2')]) CODE expect(eval_and_collect_notices(code)).to eql(['true']) end end context 'matching SemVer' do suitability = { [ '1.2.3', '1.2.2' ] => false, [ '>=1.2.3', '1.2.2' ] => false, [ '<=1.2.3', '1.2.2' ] => true, [ '1.2.3 - 1.2.4', '1.2.2' ] => false, [ '~1.2.3', '1.2.2' ] => false, [ '~1.2', '1.2.2' ] => true, [ '~1', '1.2.2' ] => true, [ '1.2.x', '1.2.2' ] => true, [ '1.x', '1.2.2' ] => true, [ '1.2.3-alpha', '1.2.3-alpha' ] => true, [ '>=1.2.3-alpha', '1.2.3-alpha' ] => true, [ '<=1.2.3-alpha', '1.2.3-alpha' ] => true, [ '<=1.2.3-alpha', '1.2.3-a' ] => true, [ '>1.2.3-alpha', '1.2.3-alpha' ] => false, [ '>1.2.3-a', '1.2.3-alpha' ] => true, [ '<1.2.3-alpha', '1.2.3-alpha' ] => false, [ '<1.2.3-alpha', '1.2.3-a' ] => true, [ '1.2.3-alpha - 1.2.4', '1.2.3-alpha' ] => true, [ '1.2.3 - 1.2.4-alpha', '1.2.4-alpha' ] => true, [ '1.2.3 - 1.2.4', '1.2.5-alpha' ] => false, [ '~1.2.3-alhpa', '1.2.3-alpha' ] => true, [ '~1.2.3-alpha', '1.3.0-alpha' ] => false, [ '1.2.3', '1.2.3' ] => true, [ '>=1.2.3', '1.2.3' ] => true, [ '<=1.2.3', '1.2.3' ] => true, [ '1.2.3 - 1.2.4', '1.2.3' ] => true, [ '~1.2.3', '1.2.3' ] => true, [ '~1.2', '1.2.3' ] => true, [ '~1', '1.2.3' ] => true, [ '1.2.x', '1.2.3' ] => true, [ '1.x', '1.2.3' ] => true, [ '1.2.3', '1.2.4' ] => false, [ '>=1.2.3', '1.2.4' ] => true, [ '<=1.2.3', '1.2.4' ] => false, [ '1.2.3 - 1.2.4', '1.2.4' ] => true, # [ '~1.2.3', '1.2.4' ] => true, Awaits fix for PUP-6242 [ '~1.2', '1.2.4' ] => true, [ '~1', '1.2.4' ] => true, [ '1.2.x', '1.2.4' ] => true, [ '1.x', '1.2.4' ] => true, } suitability.each do |arguments, expected| it "'#{arguments[1]}' against SemVerRange '#{arguments[0]}', yields #{expected}" do code = "notice(SemVer('#{arguments[1]}') =~ SemVerRange('#{arguments[0]}'))" expect(eval_and_collect_notices(code)).to eql([expected.to_s]) end end end end end end end puppet-5.5.10/spec/unit/pops/types/p_sensitive_type_spec.rb0000644005276200011600000001332413417161721024003 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' require 'puppet_spec/compiler' module Puppet::Pops module Types describe 'Sensitive Type' do include PuppetSpec::Compiler context 'as a type' do it 'can be created without a parameter with the type factory' do t = TypeFactory.sensitive expect(t).to be_a(PSensitiveType) expect(t).to eql(PSensitiveType::DEFAULT) end it 'can be created with a parameter with the type factory' do t = TypeFactory.sensitive(PIntegerType::DEFAULT) expect(t).to be_a(PSensitiveType) expect(t.type).to eql(PIntegerType::DEFAULT) end it 'string representation of unparameterized instance is "Sensitive"' do expect(PSensitiveType::DEFAULT.to_s).to eql('Sensitive') end context 'when used in Puppet expressions' do it 'is equal to itself only' do code = <<-CODE $t = Sensitive notice(Sensitive =~ Type[ Sensitive ]) notice(Sensitive == Sensitive) notice(Sensitive < Sensitive) notice(Sensitive > Sensitive) CODE expect(eval_and_collect_notices(code)).to eq(['true', 'true', 'false', 'false']) end context "when parameterized" do it 'is equal other types with the same parameterization' do code = <<-CODE notice(Sensitive[String] == Sensitive[String]) notice(Sensitive[Numeric] != Sensitive[Integer]) CODE expect(eval_and_collect_notices(code)).to eq(['true', 'true']) end it 'orders parameterized types based on the type system hierarchy' do code = <<-CODE notice(Sensitive[Numeric] > Sensitive[Integer]) notice(Sensitive[Numeric] < Sensitive[Integer]) CODE expect(eval_and_collect_notices(code)).to eq(['true', 'false']) end it 'does not order incomparable parameterized types' do code = <<-CODE notice(Sensitive[String] < Sensitive[Integer]) notice(Sensitive[String] > Sensitive[Integer]) CODE expect(eval_and_collect_notices(code)).to eq(['false', 'false']) end it 'generalizes passed types to prevent information leakage' do code =<<-CODE $it = String[7, 7] $st = Sensitive[$it] notice(type($st)) CODE expect(eval_and_collect_notices(code)).to eq(['Type[Sensitive[String]]']) end end end end context 'a Sensitive instance' do it 'can be created from a string and does not leak its contents' do code =<<-CODE $o = Sensitive("hunter2") notice($o) notice(type($o)) CODE expect(eval_and_collect_notices(code)).to eq(['Sensitive [value redacted]', 'Sensitive[String]']) end it 'matches the appropriate parameterized type' do code =<<-CODE $o = Sensitive("hunter2") notice(assert_type(Sensitive[String], $o)) notice(assert_type(Sensitive[String[7, 7]], $o)) CODE expect(eval_and_collect_notices(code)).to eq(['Sensitive [value redacted]', 'Sensitive [value redacted]']) end it 'verifies the constrains of the parameterized type' do pending "the ability to enforce constraints without leaking information" code =<<-CODE $o = Sensitive("hunter2") notice(assert_type(Sensitive[String[10, 20]], $o)) CODE expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /expects a Sensitive\[String\[10, 20\]\] value, got Sensitive\[String\[7, 7\]\]/) end it 'does not match an inappropriate parameterized type' do code =<<-CODE $o = Sensitive("hunter2") notice(assert_type(Sensitive[Integer], $o) |$expected, $actual| { "$expected != $actual" }) CODE expect(eval_and_collect_notices(code)).to eq(['Sensitive[Integer] != Sensitive[String]']) end it 'can be created from another sensitive instance ' do code =<<-CODE $o = Sensitive("hunter2") $x = Sensitive($o) notice(assert_type(Sensitive[String], $x)) CODE expect(eval_and_collect_notices(code)).to eq(['Sensitive [value redacted]']) end it 'can be given to a user defined resource as a parameter' do code =<<-CODE define keeper_of_secrets(Sensitive $x) { notice(assert_type(Sensitive[String], $x)) } keeper_of_secrets { 'test': x => Sensitive("long toe") } CODE expect(eval_and_collect_notices(code)).to eq(['Sensitive [value redacted]']) end it 'can be given to a class as a parameter' do code =<<-CODE class keeper_of_secrets(Sensitive $x) { notice(assert_type(Sensitive[String], $x)) } class { 'keeper_of_secrets': x => Sensitive("long toe") } CODE expect(eval_and_collect_notices(code)).to eq(['Sensitive [value redacted]']) end it 'can be given to a function as a parameter' do code =<<-CODE function keeper_of_secrets(Sensitive $x) { notice(assert_type(Sensitive[String], $x)) } keeper_of_secrets(Sensitive("long toe")) CODE expect(eval_and_collect_notices(code)).to eq(['Sensitive [value redacted]']) end end it "enforces wrapped type constraints" do pending "the ability to enforce constraints without leaking information" code =<<-CODE class secrets_handler(Sensitive[Array[String[4, 8]]] $pwlist) { notice($pwlist) } class { "secrets_handler": pwlist => Sensitive(['hi', 'longlonglong']) } CODE expect { expect(eval_and_collect_notices(code)) }.to raise_error(Puppet::Error, /expects a String\[4, 8\], got String/) end end end end puppet-5.5.10/spec/unit/pops/types/p_timespan_type_spec.rb0000644005276200011600000002646013417161721023617 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' require 'puppet_spec/compiler' module Puppet::Pops module Types describe 'Timespan type' do it 'is normalized in a Variant' do t = TypeFactory.variant(TypeFactory.timespan('10:00:00', '15:00:00'), TypeFactory.timespan('14:00:00', '17:00:00')).normalize expect(t).to be_a(PTimespanType) expect(t).to eql(TypeFactory.timespan('10:00:00', '17:00:00')) end context 'when used in Puppet expressions' do include PuppetSpec::Compiler it 'is equal to itself only' do code = <<-CODE $t = Timespan notice(Timespan =~ Type[Timespan]) notice(Timespan == Timespan) notice(Timespan < Timespan) notice(Timespan > Timespan) CODE expect(eval_and_collect_notices(code)).to eq(%w(true true false false)) end it 'does not consider an Integer to be an instance' do code = <<-CODE notice(assert_type(Timespan, 1234)) CODE expect { eval_and_collect_notices(code) }.to raise_error(/expects a Timespan value, got Integer/) end it 'does not consider a Float to be an instance' do code = <<-CODE notice(assert_type(Timespan, 1.234)) CODE expect { eval_and_collect_notices(code) }.to raise_error(/expects a Timespan value, got Float/) end context "when parameterized" do it 'is equal other types with the same parameterization' do code = <<-CODE notice(Timespan['01:00:00', '13:00:00'] == Timespan['01:00:00', '13:00:00']) notice(Timespan['01:00:00', '13:00:00'] != Timespan['01:12:20', '13:00:00']) CODE expect(eval_and_collect_notices(code)).to eq(%w(true true)) end it 'using just one parameter is the same as using default for the second parameter' do code = <<-CODE notice(Timespan['01:00:00'] == Timespan['01:00:00', default]) CODE expect(eval_and_collect_notices(code)).to eq(%w(true)) end it 'if the second parameter is default, it is unlimited' do code = <<-CODE notice(Timespan('12345-23:59:59') =~ Timespan['01:00:00', default]) CODE expect(eval_and_collect_notices(code)).to eq(%w(true)) end it 'orders parameterized types based on range inclusion' do code = <<-CODE notice(Timespan['01:00:00', '13:00:00'] < Timespan['00:00:00', '14:00:00']) notice(Timespan['01:00:00', '13:00:00'] > Timespan['00:00:00', '14:00:00']) CODE expect(eval_and_collect_notices(code)).to eq(%w(true false)) end it 'accepts integer values when specifying the range' do code = <<-CODE notice(Timespan(1) =~ Timespan[1, 2]) notice(Timespan(3) =~ Timespan[1]) notice(Timespan(0) =~ Timespan[default, 2]) notice(Timespan(0) =~ Timespan[1, 2]) notice(Timespan(3) =~ Timespan[1, 2]) CODE expect(eval_and_collect_notices(code)).to eq(%w(true true true false false)) end it 'accepts float values when specifying the range' do code = <<-CODE notice(Timespan(1.0) =~ Timespan[1.0, 2.0]) notice(Timespan(3.0) =~ Timespan[1.0]) notice(Timespan(0.0) =~ Timespan[default, 2.0]) notice(Timespan(0.0) =~ Timespan[1.0, 2.0]) notice(Timespan(3.0) =~ Timespan[1.0, 2.0]) CODE expect(eval_and_collect_notices(code)).to eq(%w(true true true false false)) end end context 'a Timespan instance' do it 'can be created from a string' do code = <<-CODE $o = Timespan('3-11:00') notice($o) notice(type($o)) CODE expect(eval_and_collect_notices(code)).to eq(%w(3-11:00:00.0 Timespan['3-11:00:00.0'])) end it 'can be created from a string and format' do code = <<-CODE $o = Timespan('1d11h23m', '%Dd%Hh%Mm') notice($o) CODE expect(eval_and_collect_notices(code)).to eq(%w(1-11:23:00.0)) end it 'can be created from a hash with string and format' do code = <<-CODE $o = Timespan({string => '1d11h23m', format => '%Dd%Hh%Mm'}) notice($o) CODE expect(eval_and_collect_notices(code)).to eq(%w(1-11:23:00.0)) end it 'can be created from a string and array of formats' do code = <<-CODE $fmts = ['%Dd%Hh%Mm%Ss', '%Hh%Mm%Ss', '%Dd%Hh%Mm', '%Dd%Hh', '%Hh%Mm', '%Mm%Ss', '%Dd', '%Hh', '%Mm', '%Ss' ] notice(Timespan('1d11h23m13s', $fmts)) notice(Timespan('11h23m13s', $fmts)) notice(Timespan('1d11h23m', $fmts)) notice(Timespan('1d11h', $fmts)) notice(Timespan('11h23m', $fmts)) notice(Timespan('23m13s', $fmts)) notice(Timespan('1d', $fmts)) notice(Timespan('11h', $fmts)) notice(Timespan('23m', $fmts)) notice(Timespan('13s', $fmts)) CODE expect(eval_and_collect_notices(code)).to eq( %w(1-11:23:13.0 0-11:23:13.0 1-11:23:00.0 1-11:00:00.0 0-11:23:00.0 0-00:23:13.0 1-00:00:00.0 0-11:00:00.0 0-00:23:00.0 0-00:00:13.0)) end it 'it cannot be created using an empty formats array' do code = <<-CODE notice(Timespan('1d11h23m13s', [])) CODE expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /parameter 'format' variant 1 expects size to be at least 1, got 0/) end it 'can be created from a integer that represents seconds' do code = <<-CODE $o = Timespan(6800) notice(Integer($o) == 6800) notice($o == Timespan('01:53:20')) CODE expect(eval_and_collect_notices(code)).to eq(%w(true true)) end it 'can be created from a float that represents seconds with fraction' do code = <<-CODE $o = Timespan(6800.123456789) notice(Float($o) == 6800.123456789) notice($o == Timespan('01:53:20.123456789', '%H:%M:%S.%N')) CODE expect(eval_and_collect_notices(code)).to eq(%w(true true)) end it 'matches the appropriate parameterized type' do code = <<-CODE $o = Timespan('3-11:12:13') notice(assert_type(Timespan['3-00:00:00', '4-00:00:00'], $o)) CODE expect(eval_and_collect_notices(code)).to eq(['3-11:12:13.0']) end it 'does not match an inappropriate parameterized type' do code = <<-CODE $o = Timespan('1-03:04:05') notice(assert_type(Timespan['2-00:00:00', '3-00:00:00'], $o) |$e, $a| { 'nope' }) CODE expect(eval_and_collect_notices(code)).to eq(['nope']) end it 'can be compared to other instances' do code = <<-CODE $o1 = Timespan('00:00:01') $o2 = Timespan('00:00:02') $o3 = Timespan('00:00:02') notice($o1 > $o3) notice($o1 >= $o3) notice($o1 < $o3) notice($o1 <= $o3) notice($o1 == $o3) notice($o1 != $o3) notice($o2 > $o3) notice($o2 < $o3) notice($o2 >= $o3) notice($o2 <= $o3) notice($o2 == $o3) notice($o2 != $o3) CODE expect(eval_and_collect_notices(code)).to eq(%w(false false true true false true false false true true true false)) end it 'can be compared to integer that represents seconds' do code = <<-CODE $o1 = Timespan('00:00:01') $o2 = Timespan('00:00:02') $o3 = 2 notice($o1 > $o3) notice($o1 >= $o3) notice($o1 < $o3) notice($o1 <= $o3) notice($o2 > $o3) notice($o2 < $o3) notice($o2 >= $o3) notice($o2 <= $o3) CODE expect(eval_and_collect_notices(code)).to eq(%w(false false true true false false true true)) end it 'integer that represents seconds can be compared to it' do code = <<-CODE $o1 = 1 $o2 = 2 $o3 = Timespan('00:00:02') notice($o1 > $o3) notice($o1 >= $o3) notice($o1 < $o3) notice($o1 <= $o3) notice($o2 > $o3) notice($o2 < $o3) notice($o2 >= $o3) notice($o2 <= $o3) CODE expect(eval_and_collect_notices(code)).to eq(%w(false false true true false false true true)) end it 'is equal to integer that represents seconds' do code = <<-CODE $o1 = Timespan('02', '%S') $o2 = 2 notice($o1 == $o2) notice($o1 != $o2) notice(Integer($o1) == $o2) CODE expect(eval_and_collect_notices(code)).to eq(%w(true false true)) end it 'integer that represents seconds is equal to it' do code = <<-CODE $o1 = 2 $o2 = Timespan('02', '%S') notice($o1 == $o2) notice($o1 != $o2) notice($o1 == Integer($o2)) CODE expect(eval_and_collect_notices(code)).to eq(%w(true false true)) end it 'can be compared to float that represents seconds with fraction' do code = <<-CODE $o1 = Timespan('01.123456789', '%S.%N') $o2 = Timespan('02.123456789', '%S.%N') $o3 = 2.123456789 notice($o1 > $o3) notice($o1 >= $o3) notice($o1 < $o3) notice($o1 <= $o3) notice($o2 > $o3) notice($o2 < $o3) notice($o2 >= $o3) notice($o2 <= $o3) CODE expect(eval_and_collect_notices(code)).to eq(%w(false false true true false false true true)) end it 'float that represents seconds with fraction can be compared to it' do code = <<-CODE $o1 = 1.123456789 $o2 = 2.123456789 $o3 = Timespan('02.123456789', '%S.%N') notice($o1 > $o3) notice($o1 >= $o3) notice($o1 < $o3) notice($o1 <= $o3) notice($o2 > $o3) notice($o2 < $o3) notice($o2 >= $o3) notice($o2 <= $o3) CODE expect(eval_and_collect_notices(code)).to eq(%w(false false true true false false true true)) end it 'is equal to float that represents seconds with fraction' do code = <<-CODE $o1 = Timespan('02.123456789', '%S.%N') $o2 = 2.123456789 notice($o1 == $o2) notice($o1 != $o2) notice(Float($o1) == $o2) CODE expect(eval_and_collect_notices(code)).to eq(%w(true false true)) end it 'float that represents seconds with fraction is equal to it' do code = <<-CODE $o1 = 2.123456789 $o2 = Timespan('02.123456789', '%S.%N') notice($o1 == $o2) notice($o1 != $o2) notice($o1 == Float($o2)) CODE expect(eval_and_collect_notices(code)).to eq(%w(true false true)) end it 'it cannot be compared to a Timestamp' do code = <<-CODE notice(Timespan(3) < Timestamp()) CODE expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /Timespans are only comparable to Timespans, Integers, and Floats/) end end end end end end puppet-5.5.10/spec/unit/pops/types/p_timestamp_type_spec.rb0000644005276200011600000003467713417161721024013 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' require 'puppet_spec/compiler' module Puppet::Pops module Types describe 'Timestamp type' do it 'is normalized in a Variant' do t = TypeFactory.variant(TypeFactory.timestamp('2015-03-01', '2016-01-01'), TypeFactory.timestamp('2015-11-03', '2016-12-24')).normalize expect(t).to be_a(PTimestampType) expect(t).to eql(TypeFactory.timestamp('2015-03-01', '2016-12-24')) end it 'DateTime#_strptime creates hash with :leftover field' do expect(DateTime._strptime('2015-05-04 and bogus', '%F')).to include(:leftover) expect(DateTime._strptime('2015-05-04T10:34:11.003 UTC and bogus', '%FT%T.%N %Z')).to include(:leftover) end context 'when used in Puppet expressions' do include PuppetSpec::Compiler it 'is equal to itself only' do code = <<-CODE $t = Timestamp notice(Timestamp =~ Type[Timestamp]) notice(Timestamp == Timestamp) notice(Timestamp < Timestamp) notice(Timestamp > Timestamp) CODE expect(eval_and_collect_notices(code)).to eq(%w(true true false false)) end it 'does not consider an Integer to be an instance' do code = <<-CODE notice(assert_type(Timestamp, 1234)) CODE expect { eval_and_collect_notices(code) }.to raise_error(/expects a Timestamp value, got Integer/) end it 'does not consider a Float to be an instance' do code = <<-CODE notice(assert_type(Timestamp, 1.234)) CODE expect { eval_and_collect_notices(code) }.to raise_error(/expects a Timestamp value, got Float/) end context "when parameterized" do it 'is equal other types with the same parameterization' do code = <<-CODE notice(Timestamp['2015-03-01', '2016-01-01'] == Timestamp['2015-03-01', '2016-01-01']) notice(Timestamp['2015-03-01', '2016-01-01'] != Timestamp['2015-11-03', '2016-12-24']) CODE expect(eval_and_collect_notices(code)).to eq(%w(true true)) end it 'using just one parameter is the same as using default for the second parameter' do code = <<-CODE notice(Timestamp['2015-03-01'] == Timestamp['2015-03-01', default]) CODE expect(eval_and_collect_notices(code)).to eq(%w(true)) end it 'if the second parameter is default, it is unlimited' do code = <<-CODE notice(Timestamp('5553-12-31') =~ Timestamp['2015-03-01', default]) CODE expect(eval_and_collect_notices(code)).to eq(%w(true)) end it 'orders parameterized types based on range inclusion' do code = <<-CODE notice(Timestamp['2015-03-01', '2015-09-30'] < Timestamp['2015-02-01', '2015-10-30']) notice(Timestamp['2015-03-01', '2015-09-30'] > Timestamp['2015-02-01', '2015-10-30']) CODE expect(eval_and_collect_notices(code)).to eq(%w(true false)) end it 'accepts integer values when specifying the range' do code = <<-CODE notice(Timestamp(1) =~ Timestamp[1, 2]) notice(Timestamp(3) =~ Timestamp[1]) notice(Timestamp(0) =~ Timestamp[default, 2]) CODE expect(eval_and_collect_notices(code)).to eq(%w(true true true)) end it 'accepts float values when specifying the range' do code = <<-CODE notice(Timestamp(1.0) =~ Timestamp[1.0, 2.0]) notice(Timestamp(3.0) =~ Timestamp[1.0]) notice(Timestamp(0.0) =~ Timestamp[default, 2.0]) CODE expect(eval_and_collect_notices(code)).to eq(%w(true true true)) end end context 'a Timestamp instance' do it 'can be created from a string with just a date' do code = <<-CODE $o = Timestamp('2015-03-01') notice($o) notice(type($o)) CODE expect(eval_and_collect_notices(code)).to eq(['2015-03-01T00:00:00.000000000 UTC', "Timestamp['2015-03-01T00:00:00.000000000 UTC']"]) end it 'can be created from a string and time separated by "T"' do code = <<-CODE notice(Timestamp('2015-03-01T11:12:13')) CODE expect(eval_and_collect_notices(code)).to eq(['2015-03-01T11:12:13.000000000 UTC']) end it 'can be created from a string and time separated by space' do code = <<-CODE notice(Timestamp('2015-03-01 11:12:13')) CODE expect(eval_and_collect_notices(code)).to eq(['2015-03-01T11:12:13.000000000 UTC']) end it 'should error when none of the default formats can parse the string' do code = <<-CODE notice(Timestamp('2015#03#01 11:12:13')) CODE expect { eval_and_collect_notices(code) }.to raise_error(/Unable to parse/) end it 'should error when only part of the string is parsed' do code = <<-CODE notice(Timestamp('2015-03-01T11:12:13 bogus after')) CODE expect { eval_and_collect_notices(code) }.to raise_error(/Unable to parse/) end it 'can be created from a string and format' do code = <<-CODE $o = Timestamp('Sunday, 28 August, 2016', '%A, %d %B, %Y') notice($o) CODE expect(eval_and_collect_notices(code)).to eq(['2016-08-28T00:00:00.000000000 UTC']) end it 'can be created from a string, format, and a timezone' do code = <<-CODE $o = Timestamp('Sunday, 28 August, 2016', '%A, %d %B, %Y', 'EST') notice($o) CODE expect(eval_and_collect_notices(code)).to eq(['2016-08-28T05:00:00.000000000 UTC']) end it 'can be not be created from a string, format with timezone designator, and a timezone' do code = <<-CODE $o = Timestamp('Sunday, 28 August, 2016 UTC', '%A, %d %B, %Y %z', 'EST') notice($o) CODE expect { eval_and_collect_notices(code) }.to raise_error( /Using a Timezone designator in format specification is mutually exclusive to providing an explicit timezone argument/) end it 'can be created from a hash with string and format' do code = <<-CODE $o = Timestamp({ string => 'Sunday, 28 August, 2016', format => '%A, %d %B, %Y' }) notice($o) CODE expect(eval_and_collect_notices(code)).to eq(['2016-08-28T00:00:00.000000000 UTC']) end it 'can be created from a hash with string, format, and a timezone' do code = <<-CODE $o = Timestamp({ string => 'Sunday, 28 August, 2016', format => '%A, %d %B, %Y', timezone => 'EST' }) notice($o) CODE expect(eval_and_collect_notices(code)).to eq(['2016-08-28T05:00:00.000000000 UTC']) end it 'can be created from a string and array of formats' do code = <<-CODE $fmts = [ '%A, %d %B, %Y at %r', '%b %d, %Y, %l:%M %P', '%y-%m-%d %H:%M:%S %z' ] notice(Timestamp('Sunday, 28 August, 2016 at 12:15:00 PM', $fmts)) notice(Timestamp('Jul 24, 2016, 1:20 am', $fmts)) notice(Timestamp('16-06-21 18:23:15 UTC', $fmts)) CODE expect(eval_and_collect_notices(code)).to eq( ['2016-08-28T12:15:00.000000000 UTC', '2016-07-24T01:20:00.000000000 UTC', '2016-06-21T18:23:15.000000000 UTC']) end it 'it cannot be created using an empty formats array' do code = <<-CODE notice(Timestamp('2015-03-01T11:12:13', [])) CODE expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /parameter 'format' variant 1 expects size to be at least 1, got 0/) end it 'can be created from a string, array of formats, and a timezone' do code = <<-CODE $fmts = [ '%A, %d %B, %Y at %r', '%b %d, %Y, %l:%M %P', '%y-%m-%d %H:%M:%S' ] notice(Timestamp('Sunday, 28 August, 2016 at 12:15:00 PM', $fmts, 'CET')) notice(Timestamp('Jul 24, 2016, 1:20 am', $fmts, 'CET')) notice(Timestamp('16-06-21 18:23:15', $fmts, 'CET')) CODE expect(eval_and_collect_notices(code)).to eq( ['2016-08-28T11:15:00.000000000 UTC', '2016-07-24T00:20:00.000000000 UTC', '2016-06-21T17:23:15.000000000 UTC']) end it 'can be created from a integer that represents seconds since epoch' do code = <<-CODE $o = Timestamp(1433116800) notice(Integer($o) == 1433116800) notice($o == Timestamp('2015-06-01T00:00:00 UTC')) CODE expect(eval_and_collect_notices(code)).to eq(%w(true true)) end it 'can be created from a float that represents seconds with fraction since epoch' do code = <<-CODE $o = Timestamp(1433116800.123456) notice(Float($o) == 1433116800.123456) notice($o == Timestamp('2015-06-01T00:00:00.123456 UTC')) CODE expect(eval_and_collect_notices(code)).to eq(%w(true true)) end it 'matches the appropriate parameterized type' do code = <<-CODE $o = Timestamp('2015-05-01') notice(assert_type(Timestamp['2015-03-01', '2015-09-30'], $o)) CODE expect(eval_and_collect_notices(code)).to eq(['2015-05-01T00:00:00.000000000 UTC']) end it 'does not match an inappropriate parameterized type' do code = <<-CODE $o = Timestamp('2015-05-01') notice(assert_type(Timestamp['2016-03-01', '2016-09-30'], $o) |$e, $a| { 'nope' }) CODE expect(eval_and_collect_notices(code)).to eq(['nope']) end it 'can be compared to other instances' do code = <<-CODE $o1 = Timestamp('2015-05-01') $o2 = Timestamp('2015-06-01') $o3 = Timestamp('2015-06-01') notice($o1 > $o3) notice($o1 >= $o3) notice($o1 < $o3) notice($o1 <= $o3) notice($o1 == $o3) notice($o1 != $o3) notice($o2 > $o3) notice($o2 < $o3) notice($o2 >= $o3) notice($o2 <= $o3) notice($o2 == $o3) notice($o2 != $o3) CODE expect(eval_and_collect_notices(code)).to eq(%w(false false true true false true false false true true true false)) end it 'can be compared to integer that represents seconds since epoch' do code = <<-CODE $o1 = Timestamp('2015-05-01') $o2 = Timestamp('2015-06-01') $o3 = 1433116800 notice($o1 > $o3) notice($o1 >= $o3) notice($o1 < $o3) notice($o1 <= $o3) notice($o2 > $o3) notice($o2 < $o3) notice($o2 >= $o3) notice($o2 <= $o3) CODE expect(eval_and_collect_notices(code)).to eq(%w(false false true true false false true true)) end it 'integer that represents seconds since epoch can be compared to it' do code = <<-CODE $o1 = 1430438400 $o2 = 1433116800 $o3 = Timestamp('2015-06-01') notice($o1 > $o3) notice($o1 >= $o3) notice($o1 < $o3) notice($o1 <= $o3) notice($o2 > $o3) notice($o2 < $o3) notice($o2 >= $o3) notice($o2 <= $o3) CODE expect(eval_and_collect_notices(code)).to eq(%w(false false true true false false true true)) end it 'is equal to integer that represents seconds since epoch' do code = <<-CODE $o1 = Timestamp('2015-06-01T00:00:00 UTC') $o2 = 1433116800 notice($o1 == $o2) notice($o1 != $o2) notice(Integer($o1) == $o2) CODE expect(eval_and_collect_notices(code)).to eq(%w(true false true)) end it 'integer that represents seconds is equal to it' do code = <<-CODE $o1 = 1433116800 $o2 = Timestamp('2015-06-01T00:00:00 UTC') notice($o1 == $o2) notice($o1 != $o2) notice($o1 == Integer($o2)) CODE expect(eval_and_collect_notices(code)).to eq(%w(true false true)) end it 'can be compared to float that represents seconds with fraction since epoch' do code = <<-CODE $o1 = Timestamp('2015-05-01T00:00:00.123456789 UTC') $o2 = Timestamp('2015-06-01T00:00:00.123456789 UTC') $o3 = 1433116800.123456789 notice($o1 > $o3) notice($o1 >= $o3) notice($o1 < $o3) notice($o1 <= $o3) notice($o2 > $o3) notice($o2 < $o3) notice($o2 >= $o3) notice($o2 <= $o3) CODE expect(eval_and_collect_notices(code)).to eq(%w(false false true true false false true true)) end it 'float that represents seconds with fraction since epoch can be compared to it' do code = <<-CODE $o1 = 1430438400.123456789 $o2 = 1433116800.123456789 $o3 = Timestamp('2015-06-01T00:00:00.123456789 UTC') notice($o1 > $o3) notice($o1 >= $o3) notice($o1 < $o3) notice($o1 <= $o3) notice($o2 > $o3) notice($o2 < $o3) notice($o2 >= $o3) notice($o2 <= $o3) CODE expect(eval_and_collect_notices(code)).to eq(%w(false false true true false false true true)) end it 'is equal to float that represents seconds with fraction since epoch' do code = <<-CODE $o1 = Timestamp('2015-06-01T00:00:00.123456789 UTC') $o2 = 1433116800.123456789 notice($o1 == $o2) notice($o1 != $o2) notice(Float($o1) == $o2) CODE expect(eval_and_collect_notices(code)).to eq(%w(true false true)) end it 'float that represents seconds with fraction is equal to it' do code = <<-CODE $o1 = 1433116800.123456789 $o2 = Timestamp('2015-06-01T00:00:00.123456789 UTC') notice($o1 == $o2) notice($o1 != $o2) notice($o1 == Float($o2)) CODE expect(eval_and_collect_notices(code)).to eq(%w(true false true)) end it 'it cannot be compared to a Timespan' do code = <<-CODE notice(Timestamp() > Timespan(3)) CODE expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /Timestamps are only comparable to Timestamps, Integers, and Floats/) end end end end end end puppet-5.5.10/spec/unit/pops/types/p_type_set_type_spec.rb0000644005276200011600000005032413417161721023627 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' require 'puppet_spec/compiler' module Puppet::Pops module Types describe 'The TypeSet Type' do include PuppetSpec::Compiler let(:parser) { TypeParser.singleton } let(:pp_parser) { Parser::EvaluatingParser.new } let(:env) { Puppet::Node::Environment.create('test', []) } let(:loaders) { Loaders.new(env) } let(:loader) { loaders.find_loader(nil) } def type_set_t(name, body_string, name_authority) init_literal_hash = pp_parser.parse_string("{#{body_string}}").body typeset = PTypeSetType.new(name, init_literal_hash, name_authority) loader.set_entry(Loader::TypedName.new(:type, name, name_authority), typeset) typeset end # Creates and parses an alias type declaration of a TypeSet, e.g. # ``` # type = TypeSet[{}] # ``` # The declaration implies the name authority {Pcore::RUNTIME_NAME_AUTHORITY} # # @param name [String] the name of the type set # @param body [String] the body (initialization hash) of the type-set # @return [PTypeSetType] the created type set def parse_type_set(name, body, name_authority = Pcore::RUNTIME_NAME_AUTHORITY) type_set_t(name, body, name_authority) parser.parse(name, loader) end context 'when validating the initialization hash' do context 'it will allow that it' do it 'has no types and no references' do ts = <<-OBJECT version => '1.0.0', pcore_version => '1.0.0', OBJECT expect { parse_type_set('MySet', ts) }.not_to raise_error end it 'has only references' do parse_type_set('FirstSet', <<-OBJECT) version => '1.0.0', pcore_version => '1.0.0', types => { Car => Object[{}] } OBJECT expect { parse_type_set('SecondSet', <<-OBJECT) }.not_to raise_error version => '1.0.0', pcore_version => '1.0.0', references => { First => { name => 'FirstSet', version_range => '1.x' } } OBJECT end it 'has multiple references to equally named TypeSets using different name authorities' do parse_type_set('FirstSet', <<-OBJECT, 'http://example.com/ns1') version => '1.0.0', pcore_version => '1.0.0', types => { Car => Object[{}] } OBJECT parse_type_set('FirstSet', <<-OBJECT, 'http://example.com/ns2') version => '1.0.0', pcore_version => '1.0.0', types => { Car => Object[{}] } OBJECT expect { parse_type_set('SecondSet', <<-OBJECT) }.not_to raise_error version => '1.0.0', pcore_version => '1.0.0', references => { First_1 => { name_authority => 'http://example.com/ns1', name => 'FirstSet', version_range => '1.x' }, First_2 => { name => 'FirstSet', name_authority => 'http://example.com/ns2', version_range => '1.x' } } OBJECT end end context 'it raises an error when' do it 'pcore_version is missing' do ts = <<-OBJECT version => '1.0.0', OBJECT expect { parse_type_set('MySet', ts) }.to raise_error(TypeAssertionError, /expects a value for key 'pcore_version'/) end it 'the version is an invalid semantic version' do ts = <<-OBJECT version => '1.x', pcore_version => '1.0.0', OBJECT expect { parse_type_set('MySet', ts) }.to raise_error(SemanticPuppet::Version::ValidationFailure) end it 'the pcore_version is an invalid semantic version' do ts = <<-OBJECT version => '1.0.0', pcore_version => '1.x', OBJECT expect { parse_type_set('MySet', ts) }.to raise_error(SemanticPuppet::Version::ValidationFailure) end it 'the pcore_version is outside of the range of that is parsable by this runtime' do ts = <<-OBJECT version => '1.0.0', pcore_version => '2.0.0', OBJECT expect { parse_type_set('MySet', ts) }.to raise_error(ArgumentError, /The pcore version for TypeSet 'MySet' is not understood by this runtime. Expected range 1\.x, got 2\.0\.0/) end it 'the name authority is an invalid URI' do ts = <<-OBJECT version => '1.0.0', pcore_version => '1.0.0', name_authority => 'not a valid URI' OBJECT expect { parse_type_set('MySet', ts) }.to raise_error(TypeAssertionError, /entry 'name_authority' expects a match for Pattern\[.*\], got 'not a valid URI'/m) end context 'the types map' do it 'is empty' do ts = <<-OBJECT pcore_version => '1.0.0', version => '1.0.0', types => {} OBJECT expect { parse_type_set('MySet', ts) }.to raise_error(TypeAssertionError, /entry 'types' expects size to be at least 1, got 0/) end it 'is not a map' do ts = <<-OBJECT pcore_version => '1.0.0', version => '1.0.0', types => [] OBJECT expect { parse_type_set('MySet', ts) }.to raise_error(Puppet::Error, /entry 'types' expects a Hash value, got Array/) end it 'contains values that are not types' do ts = <<-OBJECT pcore_version => '1.0.0', version => '1.0.0', types => { Car => 'brum' } OBJECT expect { parse_type_set('MySet', ts) }.to raise_error(Puppet::Error, /The expression <'brum'> is not a valid type specification/) end it 'contains keys that are not SimpleNames' do ts = <<-OBJECT pcore_version => '1.0.0', version => '1.0.0', types => { car => Integer } OBJECT expect { parse_type_set('MySet', ts) }.to raise_error(TypeAssertionError, /key of entry 'car' expects a match for Pattern\[\/\\A\[A-Z\]\\w\*\\z\/\], got 'car'/) end end context 'the references hash' do it 'is empty' do ts = <<-OBJECT pcore_version => '1.0.0', version => '1.0.0', references => {} OBJECT expect { parse_type_set('MySet', ts) }.to raise_error(TypeAssertionError, /entry 'references' expects size to be at least 1, got 0/) end it 'is not a hash' do ts = <<-OBJECT pcore_version => '1.0.0', version => '1.0.0', references => [] OBJECT expect { parse_type_set('MySet', ts) }.to raise_error(TypeAssertionError, /entry 'references' expects a Hash value, got Array/) end it 'contains something other than reference initialization maps' do ts = <<-OBJECT pcore_version => '1.0.0', version => '1.0.0', references => {Ref => 2} OBJECT expect { parse_type_set('MySet', ts) }.to raise_error(TypeAssertionError, /entry 'references' entry 'Ref' expects a Struct value, got Integer/) end it 'contains several initialization that refers to the same TypeSet' do ts = <<-OBJECT pcore_version => '1.0.0', version => '1.0.0', references => { A => { name => 'Vehicle::Cars', version_range => '1.x' }, V => { name => 'Vehicle::Cars', version_range => '1.x' }, } OBJECT expect { parse_type_set('MySet', ts) }.to raise_error(ArgumentError, /references TypeSet 'http:\/\/puppet\.com\/2016\.1\/runtime\/Vehicle::Cars' more than once using overlapping version ranges/) end it 'contains an initialization maps with an alias that collides with a type name' do ts = <<-OBJECT pcore_version => '1.0.0', version => '1.0.0', types => { Car => Object[{}] }, references => { Car => { name => 'Vehicle::Car', version_range => '1.x' } } OBJECT expect { parse_type_set('MySet', ts) }.to raise_error(ArgumentError, /references a TypeSet using alias 'Car'. The alias collides with the name of a declared type/) end context 'contains an initialization map that' do it 'has no version range' do ts = <<-OBJECT pcore_version => '1.0.0', version => '1.0.0', references => { Ref => { name => 'X' } } OBJECT expect { parse_type_set('MySet', ts) }.to raise_error(TypeAssertionError, /entry 'references' entry 'Ref' expects a value for key 'version_range'/) end it 'has no name' do ts = <<-OBJECT pcore_version => '1.0.0', version => '1.0.0', references => { Ref => { version_range => '1.x' } } OBJECT expect { parse_type_set('MySet', ts) }.to raise_error(TypeAssertionError, /entry 'references' entry 'Ref' expects a value for key 'name'/) end it 'has a name that is not a QRef' do ts = <<-OBJECT pcore_version => '1.0.0', version => '1.0.0', references => { Ref => { name => 'cars', version_range => '1.x' } } OBJECT expect { parse_type_set('MySet', ts) }.to raise_error(TypeAssertionError, /entry 'references' entry 'Ref' entry 'name' expects a match for Pattern\[\/\\A\[A-Z\]\[\\w\]\*\(\?:::\[A-Z\]\[\\w\]\*\)\*\\z\/\], got 'cars'/) end it 'has a version_range that is not a valid SemVer range' do ts = <<-OBJECT pcore_version => '1.0.0', version => '1.0.0', references => { Ref => { name => 'Cars', version_range => 'N' } } OBJECT expect { parse_type_set('MySet', ts) }.to raise_error(ArgumentError, /Unparsable version range: "N"/) end it 'has an alias that is not a SimpleName' do ts = <<-OBJECT pcore_version => '1.0.0', version => '1.0.0', references => { 'cars' => { name => 'X', version_range => '1.x' } } OBJECT expect { parse_type_set('MySet', ts) }.to raise_error(TypeAssertionError, /entry 'references' key of entry 'cars' expects a match for Pattern\[\/\\A\[A-Z\]\\w\*\\z\/\], got 'cars'/) end end end end end context 'when declaring types' do it 'can declare a type Alias' do expect { parse_type_set('TheSet', <<-OBJECT) }.not_to raise_error version => '1.0.0', pcore_version => '1.0.0', types => { PositiveInt => Integer[0, default] } OBJECT end it 'can declare an Object type using Object[{}]' do expect { parse_type_set('TheSet', <<-OBJECT) }.not_to raise_error version => '1.0.0', pcore_version => '1.0.0', types => { Complex => Object[{}] } OBJECT end it 'can declare an Object type that references other types in the same set' do expect { parse_type_set('TheSet', <<-OBJECT) }.not_to raise_error version => '1.0.0', pcore_version => '1.0.0', types => { Real => Float, Complex => Object[{ attributes => { real => Real, imaginary => Real } }] } OBJECT end it 'can declare an alias that references itself' do expect { parse_type_set('TheSet', <<-OBJECT) }.not_to raise_error version => '1.0.0', pcore_version => '1.0.0', types => { Tree => Hash[String,Variant[String,Tree]] } OBJECT end it 'can declare a type that references types in another type set' do parse_type_set('Vehicles', <<-OBJECT) version => '1.0.0', pcore_version => '1.0.0', types => { Car => Object[{}], Bicycle => Object[{}] } OBJECT expect { parse_type_set('TheSet', <<-OBJECT) }.not_to raise_error version => '1.0.0', pcore_version => '1.0.0', types => { Transports => Variant[Vecs::Car,Vecs::Bicycle] }, references => { Vecs => { name => 'Vehicles', version_range => '1.x' } } OBJECT end it 'can declare a type that references types in a type set referenced by another type set' do parse_type_set('Vehicles', <<-OBJECT) version => '1.0.0', pcore_version => '1.0.0', types => { Car => Object[{}], Bicycle => Object[{}] } OBJECT parse_type_set('Transports', <<-OBJECT) version => '1.0.0', pcore_version => '1.0.0', types => { Transports => Variant[Vecs::Car,Vecs::Bicycle] }, references => { Vecs => { name => 'Vehicles', version_range => '1.x' } } OBJECT expect { parse_type_set('TheSet', <<-OBJECT) }.not_to raise_error version => '1.0.0', pcore_version => '1.0.0', types => { MotorPowered => Variant[T::Vecs::Car], Pedaled => Variant[T::Vecs::Bicycle], All => T::Transports }, references => { T => { name => 'Transports', version_range => '1.x' } } OBJECT end context 'allows bracket-less form' do let(:logs) { [] } let(:notices) { logs.select { |log| log.level == :notice }.map { |log| log.message } } let(:warnings) { logs.select { |log| log.level == :warning }.map { |log| log.message } } let(:node) { Puppet::Node.new('example.com') } let(:compiler) { Puppet::Parser::Compiler.new(node) } def compile(code) Puppet[:code] = code Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) { compiler.compile } end it 'on the TypeSet declaration itself' do compile(<<-PUPPET) type TS = TypeSet { pcore_version => '1.0.0' } notice(TS =~ Type[TypeSet]) PUPPET expect(warnings).to be_empty expect(notices).to eql(['true']) end it 'without prefix on declared types (implies Object)' do compile(<<-PUPPET) type TS = TypeSet { pcore_version => '1.0.0', types => { MyObject => { attributes => { a => Integer} } } } notice(TS =~ Type[TypeSet]) notice(TS::MyObject =~ Type) notice(TS::MyObject(3)) PUPPET expect(warnings).to be_empty expect(notices).to eql(['true', 'true', "TS::MyObject({'a' => 3})"]) end it "prefixed with QREF 'Object' on declared types" do compile(<<-PUPPET) type TS = TypeSet { pcore_version => '1.0.0', types => { MyObject => Object { attributes => { a => Integer} } } } notice(TS =~ Type[TypeSet]) notice(TS::MyObject =~ Type) notice(TS::MyObject(3)) PUPPET expect(warnings).to be_empty expect(notices).to eql(['true', 'true', "TS::MyObject({'a' => 3})"]) end it 'prefixed with QREF to declare parent on declared types' do compile(<<-PUPPET) type TS = TypeSet { pcore_version => '1.0.0', types => { MyObject => { attributes => { a => String }}, MySecondObject => MyObject { attributes => { b => String }} } } notice(TS =~ Type[TypeSet]) notice(TS::MySecondObject =~ Type) notice(TS::MySecondObject < TS::MyObject) notice(TS::MyObject('hi')) notice(TS::MySecondObject('hello', 'world')) PUPPET expect(warnings).to be_empty expect(notices).to eql( ['true', 'true', 'true', "TS::MyObject({'a' => 'hi'})", "TS::MySecondObject({'a' => 'hello', 'b' => 'world'})"]) end it 'and warns when parent is specified both before and inside the hash if strict == warning' do Puppet[:strict] = 'warning' compile(<<-PUPPET) type TS = TypeSet { pcore_version => '1.0.0', types => { MyObject => { attributes => { a => String }}, MySecondObject => MyObject { parent => MyObject, attributes => { b => String }} } } notice(TS =~ Type[TypeSet]) PUPPET expect(warnings).to eql(["The key 'parent' is declared more than once"]) expect(notices).to eql(['true']) end it 'and errors when parent is specified both before and inside the hash if strict == error' do Puppet[:strict] = 'error' expect{ compile(<<-PUPPET) }.to raise_error(/The key 'parent' is declared more than once/) type TS = TypeSet { pcore_version => '1.0.0', types => { MyObject => { attributes => { a => String }}, MySecondObject => MyObject { parent => MyObject, attributes => { b => String }} } } notice(TS =~ Type[TypeSet]) PUPPET end end end it '#name_for method reports the name of deeply nested type correctly' do tv = parse_type_set('Vehicles', <<-OBJECT) version => '1.0.0', pcore_version => '1.0.0', types => { Car => Object[{}] } OBJECT parse_type_set('Transports', <<-OBJECT) version => '1.0.0', pcore_version => '1.0.0', references => { Vecs => { name => 'Vehicles', version_range => '1.x' } } OBJECT ts = parse_type_set('TheSet', <<-OBJECT) version => '1.0.0', pcore_version => '1.0.0', references => { T => { name => 'Transports', version_range => '1.x' } } OBJECT expect(ts.name_for(tv['Car'], nil)).to eql('T::Vecs::Car') end end end end puppet-5.5.10/spec/unit/pops/types/p_uri_type_spec.rb0000644005276200011600000001445013417161721022572 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' require 'puppet_spec/compiler' module Puppet::Pops module Types describe 'URI type' do context 'when used in Puppet expressions' do include PuppetSpec::Compiler it 'is equal to itself only' do expect(eval_and_collect_notices(<<-CODE)).to eq(%w(true true false false)) $t = URI notice(URI =~ Type[URI]) notice(URI == URI) notice(URI < URI) notice(URI > URI) CODE end context "when parameterized" do it 'is equal other types with the same parameterization' do code = <<-CODE notice(URI == URI[{}]) notice(URI['http://example.com'] == URI[scheme => http, host => 'example.com']) notice(URI['urn:a:b:c'] == URI[scheme => urn, opaque => 'a:b:c']) CODE expect(eval_and_collect_notices(code)).to eq(%w(true true true)) end it 'is assignable from more qualified types' do expect(eval_and_collect_notices(<<-CODE)).to eq(%w(true true true)) notice(URI > URI['http://example.com']) notice(URI['http://example.com'] > URI['http://example.com/path']) notice(URI[scheme => Enum[http, https]] > URI['http://example.com']) CODE end it 'is not assignable unless scheme is assignable' do expect(eval_and_collect_notices(<<-CODE)).to eq(%w(false)) notice(URI[scheme => Enum[http, https]] > URI[scheme => 'ftp']) CODE end it 'presents parsable string form' do code = <<-CODE notice(URI['https://user:password@www.example.com:3000/some/path?x=y#frag']) CODE expect(eval_and_collect_notices(code)).to eq([ "URI[{'scheme' => 'https', 'userinfo' => 'user:password', 'host' => 'www.example.com', 'port' => '3000', 'path' => '/some/path', 'query' => 'x=y', 'fragment' => 'frag'}]", ]) end end context 'a URI instance' do it 'can be created from a string' do code = <<-CODE $o = URI('https://example.com/a/b') notice(String($o, '%p')) notice(type($o)) CODE expect(eval_and_collect_notices(code)).to eq([ "URI('https://example.com/a/b')", "URI[{'scheme' => 'https', 'host' => 'example.com', 'path' => '/a/b'}]" ]) end it 'which is opaque, can be created from a string' do code = <<-CODE $o = URI('urn:a:b:c') notice(String($o, '%p')) notice(type($o)) CODE expect(eval_and_collect_notices(code)).to eq([ "URI('urn:a:b:c')", "URI[{'scheme' => 'urn', 'opaque' => 'a:b:c'}]" ]) end it 'can be created from a hash' do code = <<-CODE $o = URI(scheme => 'https', host => 'example.com', path => '/a/b') notice(String($o, '%p')) notice(type($o)) CODE expect(eval_and_collect_notices(code)).to eq([ "URI('https://example.com/a/b')", "URI[{'scheme' => 'https', 'host' => 'example.com', 'path' => '/a/b'}]" ]) end it 'which is opaque, can be created from a hash' do code = <<-CODE $o = URI(scheme => 'urn', opaque => 'a:b:c') notice(String($o, '%p')) notice(type($o)) CODE expect(eval_and_collect_notices(code)).to eq([ "URI('urn:a:b:c')", "URI[{'scheme' => 'urn', 'opaque' => 'a:b:c'}]" ]) end it 'is an instance of its type' do code = <<-CODE $o = URI('https://example.com/a/b') notice($o =~ type($o)) CODE expect(eval_and_collect_notices(code)).to eq(['true']) end it 'is an instance of matching parameterized URI' do code = <<-CODE $o = URI('https://example.com/a/b') notice($o =~ URI[scheme => https, host => 'example.com']) CODE expect(eval_and_collect_notices(code)).to eq(['true']) end it 'is an instance of matching default URI' do code = <<-CODE $o = URI('https://example.com/a/b') notice($o =~ URI) CODE expect(eval_and_collect_notices(code)).to eq(['true']) end it 'path is not matched by opaque' do code = <<-CODE $o = URI('urn:a:b:c') notice($o =~ URI[path => 'a:b:c']) CODE expect(eval_and_collect_notices(code)).to eq(['false']) end it 'opaque is not matched by path' do code = <<-CODE $o = URI('https://example.com/a/b') notice($o =~ URI[opaque => '/a/b']) CODE expect(eval_and_collect_notices(code)).to eq(['false']) end it 'is not an instance unless parameters matches' do code = <<-CODE $o = URI('https://example.com/a/b') notice($o =~ URI[scheme => http]) CODE expect(eval_and_collect_notices(code)).to eq(['false']) end it 'individual parts of URI can be accessed using accessor methods' do code = <<-CODE $o = URI('https://bob:pw@example.com:8080/a/b?a=b#frag') notice($o.scheme) notice($o.userinfo) notice($o.host) notice($o.port) notice($o.path) notice($o.query) notice($o.fragment) CODE expect(eval_and_collect_notices(code)).to eq(['https', 'bob:pw', 'example.com', '8080', '/a/b', 'a=b', 'frag']) end it 'individual parts of opaque URI can be accessed using accessor methods' do code = <<-CODE $o = URI('urn:a:b:c') notice($o.scheme) notice($o.opaque) CODE expect(eval_and_collect_notices(code)).to eq(['urn', 'a:b:c']) end it 'An URI can be merged with a String using the + operator' do code = <<-CODE notice(String(URI('https://example.com') + '/a/b', '%p')) CODE expect(eval_and_collect_notices(code)).to eq(["URI('https://example.com/a/b')"]) end it 'An URI can be merged with another URI using the + operator' do code = <<-CODE notice(String(URI('https://example.com') + URI('/a/b'), '%p')) CODE expect(eval_and_collect_notices(code)).to eq(["URI('https://example.com/a/b')"]) end end end end end end puppet-5.5.10/spec/unit/pops/types/recursion_guard_spec.rb0000644005276200011600000000563613417161721023614 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops/types/recursion_guard' module Puppet::Pops::Types describe 'the RecursionGuard' do let(:guard) { RecursionGuard.new } it "should detect recursion in 'this' context" do x = Object.new expect(guard.add_this(x)).to eq(RecursionGuard::NO_SELF_RECURSION) expect(guard.add_this(x)).to eq(RecursionGuard::SELF_RECURSION_IN_THIS) end it "should detect recursion in 'that' context" do x = Object.new expect(guard.add_that(x)).to eq(RecursionGuard::NO_SELF_RECURSION) expect(guard.add_that(x)).to eq(RecursionGuard::SELF_RECURSION_IN_THAT) end it "should keep 'this' and 'that' context separate" do x = Object.new expect(guard.add_this(x)).to eq(RecursionGuard::NO_SELF_RECURSION) expect(guard.add_that(x)).to eq(RecursionGuard::NO_SELF_RECURSION) end it "should detect when there's a recursion in both 'this' and 'that' context" do x = Object.new y = Object.new expect(guard.add_this(x)).to eq(RecursionGuard::NO_SELF_RECURSION) expect(guard.add_that(y)).to eq(RecursionGuard::NO_SELF_RECURSION) expect(guard.add_this(x)).to eq(RecursionGuard::SELF_RECURSION_IN_THIS) expect(guard.add_that(y)).to eq(RecursionGuard::SELF_RECURSION_IN_BOTH) end it "should report that 'this' is recursive after a recursion has been detected" do x = Object.new guard.add_this(x) guard.add_this(x) expect(guard.recursive_this?(x)).to be_truthy end it "should report that 'that' is recursive after a recursion has been detected" do x = Object.new guard.add_that(x) guard.add_that(x) expect(guard.recursive_that?(x)).to be_truthy end it "should not report that 'this' is recursive after a recursion of 'that' has been detected" do x = Object.new guard.add_that(x) guard.add_that(x) expect(guard.recursive_this?(x)).to be_falsey end it "should not report that 'that' is recursive after a recursion of 'this' has been detected" do x = Object.new guard.add_that(x) guard.add_that(x) expect(guard.recursive_this?(x)).to be_falsey end it "should not call 'hash' on an added instance" do x = mock x.expects(:hash).never expect(guard.add_that(x)).to eq(RecursionGuard::NO_SELF_RECURSION) end it "should not call '==' on an added instance" do x = mock x.expects(:==).never expect(guard.add_that(x)).to eq(RecursionGuard::NO_SELF_RECURSION) end it "should not call 'eq?' on an added instance" do x = mock x.expects(:eq?).never expect(guard.add_that(x)).to eq(RecursionGuard::NO_SELF_RECURSION) end it "should not call 'eql?' on an added instance" do x = mock x.expects(:eql?).never expect(guard.add_that(x)).to eq(RecursionGuard::NO_SELF_RECURSION) end it "should not call 'equal?' on an added instance" do x = mock x.expects(:equal?).never expect(guard.add_that(x)).to eq(RecursionGuard::NO_SELF_RECURSION) end end endpuppet-5.5.10/spec/unit/pops/types/ruby_generator_spec.rb0000644005276200011600000010431013417161721023435 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' require 'puppet_spec/compiler' require 'puppet/pops/types/ruby_generator' def root_binding return binding end module Puppet::Pops module Types describe 'Puppet Ruby Generator' do include PuppetSpec::Compiler let!(:parser) { TypeParser.singleton } let(:generator) { RubyGenerator.new } context 'when generating classes for Objects having attribute names that are Ruby reserved words' do let (:source) { <<-PUPPET } type MyObject = Object[{ attributes => { alias => String, begin => String, break => String, def => String, do => String, end => String, ensure => String, for => String, module => String, next => String, nil => String, not => String, redo => String, rescue => String, retry => String, return => String, self => String, super => String, then => String, until => String, when => String, while => String, yield => String, }, }] $x = MyObject({ alias => 'value of alias', begin => 'value of begin', break => 'value of break', def => 'value of def', do => 'value of do', end => 'value of end', ensure => 'value of ensure', for => 'value of for', module => 'value of module', next => 'value of next', nil => 'value of nil', not => 'value of not', redo => 'value of redo', rescue => 'value of rescue', retry => 'value of retry', return => 'value of return', self => 'value of self', super => 'value of super', then => 'value of then', until => 'value of until', when => 'value of when', while => 'value of while', yield => 'value of yield', }) notice($x.alias) notice($x.begin) notice($x.break) notice($x.def) notice($x.do) notice($x.end) notice($x.ensure) notice($x.for) notice($x.module) notice($x.next) notice($x.nil) notice($x.not) notice($x.redo) notice($x.rescue) notice($x.retry) notice($x.return) notice($x.self) notice($x.super) notice($x.then) notice($x.until) notice($x.when) notice($x.while) notice($x.yield) PUPPET it 'can create an instance and access all attributes' do expect(eval_and_collect_notices(source)).to eql([ 'value of alias', 'value of begin', 'value of break', 'value of def', 'value of do', 'value of end', 'value of ensure', 'value of for', 'value of module', 'value of next', 'value of nil', 'value of not', 'value of redo', 'value of rescue', 'value of retry', 'value of return', 'value of self', 'value of super', 'value of then', 'value of until', 'value of when', 'value of while', 'value of yield', ]) end end context 'when generating classes for Objects having function names that are Ruby reserved words' do let (:source) { <<-PUPPET } type MyObject = Object[{ functions => { alias => { type => Callable[[0,0],String], annotations => {RubyMethod => { 'body' => "'value of alias'" }}}, begin => { type => Callable[[0,0],String], annotations => {RubyMethod => { 'body' => "'value of begin'" }}}, break => { type => Callable[[0,0],String], annotations => {RubyMethod => { 'body' => "'value of break'" }}}, def => { type => Callable[[0,0],String], annotations => {RubyMethod => { 'body' => "'value of def'" }}}, do => { type => Callable[[0,0],String], annotations => {RubyMethod => { 'body' => "'value of do'" }}}, end => { type => Callable[[0,0],String], annotations => {RubyMethod => { 'body' => "'value of end'" }}}, ensure => { type => Callable[[0,0],String], annotations => {RubyMethod => { 'body' => "'value of ensure'" }}}, for => { type => Callable[[0,0],String], annotations => {RubyMethod => { 'body' => "'value of for'" }}}, module => { type => Callable[[0,0],String], annotations => {RubyMethod => { 'body' => "'value of module'" }}}, next => { type => Callable[[0,0],String], annotations => {RubyMethod => { 'body' => "'value of next'" }}}, nil => { type => Callable[[0,0],String], annotations => {RubyMethod => { 'body' => "'value of nil'" }}}, not => { type => Callable[[0,0],String], annotations => {RubyMethod => { 'body' => "'value of not'" }}}, redo => { type => Callable[[0,0],String], annotations => {RubyMethod => { 'body' => "'value of redo'" }}}, rescue => { type => Callable[[0,0],String], annotations => {RubyMethod => { 'body' => "'value of rescue'" }}}, retry => { type => Callable[[0,0],String], annotations => {RubyMethod => { 'body' => "'value of retry'" }}}, return => { type => Callable[[0,0],String], annotations => {RubyMethod => { 'body' => "'value of return'" }}}, self => { type => Callable[[0,0],String], annotations => {RubyMethod => { 'body' => "'value of self'" }}}, super => { type => Callable[[0,0],String], annotations => {RubyMethod => { 'body' => "'value of super'" }}}, then => { type => Callable[[0,0],String], annotations => {RubyMethod => { 'body' => "'value of then'" }}}, until => { type => Callable[[0,0],String], annotations => {RubyMethod => { 'body' => "'value of until'" }}}, when => { type => Callable[[0,0],String], annotations => {RubyMethod => { 'body' => "'value of when'" }}}, while => { type => Callable[[0,0],String], annotations => {RubyMethod => { 'body' => "'value of while'" }}}, yield => { type => Callable[[0,0],String], annotations => {RubyMethod => { 'body' => "'value of yield'" }}}, }, }] $x = MyObject() notice($x.alias) notice($x.begin) notice($x.break) notice($x.def) notice($x.do) notice($x.end) notice($x.ensure) notice($x.for) notice($x.module) notice($x.next) notice($x.nil) notice($x.not) notice($x.redo) notice($x.rescue) notice($x.retry) notice($x.return) notice($x.self) notice($x.super) notice($x.then) notice($x.until) notice($x.when) notice($x.while) notice($x.yield) PUPPET it 'can create an instance and call all functions' do expect(eval_and_collect_notices(source)).to eql([ 'value of alias', 'value of begin', 'value of break', 'value of def', 'value of do', 'value of end', 'value of ensure', 'value of for', 'value of module', 'value of next', 'value of nil', 'value of not', 'value of redo', 'value of rescue', 'value of retry', 'value of return', 'value of self', 'value of super', 'value of then', 'value of until', 'value of when', 'value of while', 'value of yield', ]) end end context 'when generating from Object types' do let (:type_decls) { <<-CODE.unindent } type MyModule::FirstGenerated = Object[{ attributes => { name => String, age => { type => Integer, value => 30 }, what => { type => String, value => 'what is this', kind => constant }, uc_name => { type => String, kind => derived, annotations => { RubyMethod => { body => '@name.upcase' } } }, other_name => { type => String, kind => derived }, }, functions => { some_other => { type => Callable[1,1] }, name_and_age => { type => Callable[1,1], annotations => { RubyMethod => { parameters => 'joiner', body => '"\#{@name}\#{joiner}\#{@age}"' } } }, '[]' => { type => Callable[1,1], annotations => { RubyMethod => { parameters => 'key', body => @(EOF) case key when 'name' name when 'age' age else nil end |-EOF } } } } }] type MyModule::SecondGenerated = Object[{ parent => MyModule::FirstGenerated, attributes => { address => String, zipcode => String, email => String, another => { type => Optional[MyModule::FirstGenerated], value => undef }, number => Integer, aref => { type => Optional[MyModule::FirstGenerated], value => undef, kind => reference } } }] CODE let(:type_usage) { '' } let(:source) { type_decls + type_usage } context 'when generating anonymous classes' do loader = nil let(:first_type) { parser.parse('MyModule::FirstGenerated', loader) } let(:second_type) { parser.parse('MyModule::SecondGenerated', loader) } let(:first) { generator.create_class(first_type) } let(:second) { generator.create_class(second_type) } let(:notices) { [] } before(:each) do notices.concat(eval_and_collect_notices(source) do |topscope| loader = topscope.compiler.loaders.find_loader(nil) end) end context 'the generated class' do it 'inherits the PuppetObject module' do expect(first < PuppetObject).to be_truthy end it 'is the superclass of a generated subclass' do expect(second < first).to be_truthy end end context 'the #create class method' do it 'has an arity that reflects optional arguments' do expect(first.method(:create).arity).to eql(-2) expect(second.method(:create).arity).to eql(-6) end it 'creates an instance of the class' do inst = first.create('Bob Builder', 52) expect(inst).to be_a(first) expect(inst.name).to eq('Bob Builder') expect(inst.age).to eq(52) end it 'created instance has a [] method' do inst = first.create('Bob Builder', 52) expect(inst['name']).to eq('Bob Builder') expect(inst['age']).to eq(52) end it 'will perform type assertion of the arguments' do expect { first.create('Bob Builder', '52') }.to( raise_error(TypeAssertionError, 'MyModule::FirstGenerated[age] has wrong type, expects an Integer value, got String') ) end it 'will not accept nil as given value for an optional parameter that does not accept nil' do expect { first.create('Bob Builder', nil) }.to( raise_error(TypeAssertionError, 'MyModule::FirstGenerated[age] has wrong type, expects an Integer value, got Undef') ) end it 'reorders parameters to but the optional parameters last' do inst = second.create('Bob Builder', '42 Cool Street', '12345', 'bob@example.com', 23) expect(inst.name).to eq('Bob Builder') expect(inst.address).to eq('42 Cool Street') expect(inst.zipcode).to eq('12345') expect(inst.email).to eq('bob@example.com') expect(inst.number).to eq(23) expect(inst.what).to eql('what is this') expect(inst.age).to eql(30) expect(inst.another).to be_nil end it 'generates a code body for derived attribute from a RubyMethod body attribute' do inst = first.create('Bob Builder', 52) expect(inst.uc_name).to eq('BOB BUILDER') end it "generates a code body with 'not implemented' in the absense of a RubyMethod body attribute" do inst = first.create('Bob Builder', 52) expect { inst.other_name }.to raise_error(/no method is implemented for derived attribute MyModule::FirstGenerated\[other_name\]/) end it 'generates parameter list and a code body for derived function from a RubyMethod body attribute' do inst = first.create('Bob Builder', 52) expect(inst.name_and_age(' of age ')).to eq('Bob Builder of age 52') end end context 'the #from_hash class method' do it 'has an arity of one' do expect(first.method(:from_hash).arity).to eql(1) expect(second.method(:from_hash).arity).to eql(1) end it 'creates an instance of the class' do inst = first.from_hash('name' => 'Bob Builder', 'age' => 52) expect(inst).to be_a(first) expect(inst.name).to eq('Bob Builder') expect(inst.age).to eq(52) end it 'accepts an initializer where optional keys are missing' do inst = first.from_hash('name' => 'Bob Builder') expect(inst).to be_a(first) expect(inst.name).to eq('Bob Builder') expect(inst.age).to eq(30) end it 'does not accept an initializer where optional values are nil and type does not accept nil' do expect { first.from_hash('name' => 'Bob Builder', 'age' => nil) }.to( raise_error(TypeAssertionError, "MyModule::FirstGenerated initializer has wrong type, entry 'age' expects an Integer value, got Undef") ) end end context 'creates an instance' do it 'that the TypeCalculator infers to the Object type' do expect(TypeCalculator.infer(first.from_hash('name' => 'Bob Builder'))).to eq(first_type) end it "where attributes of kind 'reference' are not considered part of #_pcore_all_contents" do inst = first.from_hash('name' => 'Bob Builder') wrinst = second.create('Bob Builder', '42 Cool Street', '12345', 'bob@example.com', 23, 40, inst, inst) results = [] wrinst._pcore_all_contents([]) { |v| results << v } expect(results).to eq([inst]) end end context 'when used from Puppet' do let(:type_usage) { <<-PUPPET.unindent } $i = MyModule::FirstGenerated('Bob Builder', 52) notice($i['name']) notice($i['age']) PUPPET it 'The [] method is present on a created instance' do expect(notices).to eql(['Bob Builder', '52']) end end end context 'when generating static code' do module_def = nil before(:each) do # Ideally, this would be in a before(:all) but that is impossible since lots of Puppet # environment specific settings are configured by the spec_helper in before(:each) if module_def.nil? first_type = nil second_type = nil eval_and_collect_notices(source) do first_type = parser.parse('MyModule::FirstGenerated') second_type = parser.parse('MyModule::SecondGenerated') Loaders.implementation_registry.register_type_mapping( PRuntimeType.new(:ruby, [/^PuppetSpec::RubyGenerator::(\w+)$/, 'MyModule::\1']), [/^MyModule::(\w+)$/, 'PuppetSpec::RubyGenerator::\1']) module_def = generator.module_definition([first_type, second_type], 'Generated stuff') end Loaders.clear Puppet[:code] = nil # Create the actual classes in the PuppetSpec::RubyGenerator module Puppet.override(:loaders => Puppet::Pops::Loaders.new(Puppet::Node::Environment.create(:testing, []))) do eval(module_def, root_binding) end end end after(:all) do # Don't want generated module to leak outside this test PuppetSpec.send(:remove_const, :RubyGenerator) end it 'the #_pcore_type class method returns a resolved Type' do first_type = PuppetSpec::RubyGenerator::FirstGenerated._pcore_type expect(first_type).to be_a(PObjectType) second_type = PuppetSpec::RubyGenerator::SecondGenerated._pcore_type expect(second_type).to be_a(PObjectType) expect(second_type.parent).to eql(first_type) end context 'the #create class method' do it 'has an arity that reflects optional arguments' do expect(PuppetSpec::RubyGenerator::FirstGenerated.method(:create).arity).to eql(-2) expect(PuppetSpec::RubyGenerator::SecondGenerated.method(:create).arity).to eql(-6) end it 'creates an instance of the class' do inst = PuppetSpec::RubyGenerator::FirstGenerated.create('Bob Builder', 52) expect(inst).to be_a(PuppetSpec::RubyGenerator::FirstGenerated) expect(inst.name).to eq('Bob Builder') expect(inst.age).to eq(52) end it 'will perform type assertion of the arguments' do expect { PuppetSpec::RubyGenerator::FirstGenerated.create('Bob Builder', '52') }.to( raise_error(TypeAssertionError, 'MyModule::FirstGenerated[age] has wrong type, expects an Integer value, got String') ) end it 'will not accept nil as given value for an optional parameter that does not accept nil' do expect { PuppetSpec::RubyGenerator::FirstGenerated.create('Bob Builder', nil) }.to( raise_error(TypeAssertionError, 'MyModule::FirstGenerated[age] has wrong type, expects an Integer value, got Undef') ) end it 'reorders parameters to but the optional parameters last' do inst = PuppetSpec::RubyGenerator::SecondGenerated.create('Bob Builder', '42 Cool Street', '12345', 'bob@example.com', 23) expect(inst.name).to eq('Bob Builder') expect(inst.address).to eq('42 Cool Street') expect(inst.zipcode).to eq('12345') expect(inst.email).to eq('bob@example.com') expect(inst.number).to eq(23) expect(inst.what).to eql('what is this') expect(inst.age).to eql(30) expect(inst.another).to be_nil end end context 'the #from_hash class method' do it 'has an arity of one' do expect(PuppetSpec::RubyGenerator::FirstGenerated.method(:from_hash).arity).to eql(1) expect(PuppetSpec::RubyGenerator::SecondGenerated.method(:from_hash).arity).to eql(1) end it 'creates an instance of the class' do inst = PuppetSpec::RubyGenerator::FirstGenerated.from_hash('name' => 'Bob Builder', 'age' => 52) expect(inst).to be_a(PuppetSpec::RubyGenerator::FirstGenerated) expect(inst.name).to eq('Bob Builder') expect(inst.age).to eq(52) end it 'accepts an initializer where optional keys are missing' do inst = PuppetSpec::RubyGenerator::FirstGenerated.from_hash('name' => 'Bob Builder') expect(inst).to be_a(PuppetSpec::RubyGenerator::FirstGenerated) expect(inst.name).to eq('Bob Builder') expect(inst.age).to eq(30) end it 'does not accept an initializer where optional values are nil and type does not accept nil' do expect { PuppetSpec::RubyGenerator::FirstGenerated.from_hash('name' => 'Bob Builder', 'age' => nil) }.to( raise_error(TypeAssertionError, "MyModule::FirstGenerated initializer has wrong type, entry 'age' expects an Integer value, got Undef") ) end end end end context 'when generating from TypeSets' do def source <<-CODE type MyModule = TypeSet[{ pcore_version => '1.0.0', version => '1.0.0', types => { MyInteger => Integer, FirstGenerated => Object[{ attributes => { name => String, age => { type => Integer, value => 30 }, what => { type => String, value => 'what is this', kind => constant } } }], SecondGenerated => Object[{ parent => FirstGenerated, attributes => { address => String, zipcode => String, email => String, another => { type => Optional[FirstGenerated], value => undef }, number => MyInteger } }] }, }] type OtherModule = TypeSet[{ pcore_version => '1.0.0', version => '1.0.0', types => { MyFloat => Float, ThirdGenerated => Object[{ attributes => { first => My::FirstGenerated } }], FourthGenerated => Object[{ parent => My::SecondGenerated, attributes => { complex => { type => Optional[ThirdGenerated], value => undef }, n1 => My::MyInteger, n2 => MyFloat } }] }, references => { My => { name => 'MyModule', version_range => '1.x' } } }] CODE end context 'when generating anonymous classes' do typeset = nil let(:first_type) { typeset['My::FirstGenerated'] } let(:second_type) { typeset['My::SecondGenerated'] } let(:third_type) { typeset['ThirdGenerated'] } let(:fourth_type) { typeset['FourthGenerated'] } let(:first) { generator.create_class(first_type) } let(:second) { generator.create_class(second_type) } let(:third) { generator.create_class(third_type) } let(:fourth) { generator.create_class(fourth_type) } before(:each) do eval_and_collect_notices(source) do typeset = parser.parse('OtherModule') end end after(:each) { typeset = nil } context 'the typeset' do it 'produces expected string representation' do expect(typeset.to_s).to eq( "TypeSet[{pcore_version => '1.0.0', name_authority => 'http://puppet.com/2016.1/runtime', name => 'OtherModule', version => '1.0.0', types => {"+ "MyFloat => Float, "+ "ThirdGenerated => Object[{attributes => {'first' => My::FirstGenerated}}], "+ "FourthGenerated => Object[{parent => My::SecondGenerated, attributes => {"+ "'complex' => {type => Optional[ThirdGenerated], value => undef}, "+ "'n1' => My::MyInteger, "+ "'n2' => MyFloat"+ "}}]}, references => {My => {'name' => 'MyModule', 'version_range' => '1.x'}}}]") end end context 'the generated class' do it 'inherits the PuppetObject module' do expect(first < PuppetObject).to be_truthy end it 'is the superclass of a generated subclass' do expect(second < first).to be_truthy end end context 'the #create class method' do it 'has an arity that reflects optional arguments' do expect(first.method(:create).arity).to eql(-2) expect(second.method(:create).arity).to eql(-6) expect(third.method(:create).arity).to eql(1) expect(fourth.method(:create).arity).to eql(-8) end it 'creates an instance of the class' do inst = first.create('Bob Builder', 52) expect(inst).to be_a(first) expect(inst.name).to eq('Bob Builder') expect(inst.age).to eq(52) end it 'will perform type assertion of the arguments' do expect { first.create('Bob Builder', '52') }.to( raise_error(TypeAssertionError, 'MyModule::FirstGenerated[age] has wrong type, expects an Integer value, got String') ) end it 'will not accept nil as given value for an optional parameter that does not accept nil' do expect { first.create('Bob Builder', nil) }.to( raise_error(TypeAssertionError, 'MyModule::FirstGenerated[age] has wrong type, expects an Integer value, got Undef') ) end it 'reorders parameters to but the optional parameters last' do inst = second.create('Bob Builder', '42 Cool Street', '12345', 'bob@example.com', 23) expect(inst.name).to eq('Bob Builder') expect(inst.address).to eq('42 Cool Street') expect(inst.zipcode).to eq('12345') expect(inst.email).to eq('bob@example.com') expect(inst.number).to eq(23) expect(inst.what).to eql('what is this') expect(inst.age).to eql(30) expect(inst.another).to be_nil end it 'two instances with the same attribute values are equal using #eql?' do inst1 = second.create('Bob Builder', '42 Cool Street', '12345', 'bob@example.com', 23) inst2 = second.create('Bob Builder', '42 Cool Street', '12345', 'bob@example.com', 23) expect(inst1.eql?(inst2)).to be_truthy end it 'two instances with the same attribute values are equal using #==' do inst1 = second.create('Bob Builder', '42 Cool Street', '12345', 'bob@example.com', 23) inst2 = second.create('Bob Builder', '42 Cool Street', '12345', 'bob@example.com', 23) expect(inst1 == inst2).to be_truthy end it 'two instances with the different attribute in super class values are different' do inst1 = second.create('Bob Builder', '42 Cool Street', '12345', 'bob@example.com', 23) inst2 = second.create('Bob Engineer', '42 Cool Street', '12345', 'bob@example.com', 23) expect(inst1 == inst2).to be_falsey end it 'two instances with the different attribute in sub class values are different' do inst1 = second.create('Bob Builder', '42 Cool Street', '12345', 'bob@example.com', 23) inst2 = second.create('Bob Builder', '42 Cool Street', '12345', 'bob@other.com', 23) expect(inst1 == inst2).to be_falsey end end context 'the #from_hash class method' do it 'has an arity of one' do expect(first.method(:from_hash).arity).to eql(1) expect(second.method(:from_hash).arity).to eql(1) end it 'creates an instance of the class' do inst = first.from_hash('name' => 'Bob Builder', 'age' => 52) expect(inst).to be_a(first) expect(inst.name).to eq('Bob Builder') expect(inst.age).to eq(52) end it 'accepts an initializer where optional keys are missing' do inst = first.from_hash('name' => 'Bob Builder') expect(inst).to be_a(first) expect(inst.name).to eq('Bob Builder') expect(inst.age).to eq(30) end it 'does not accept an initializer where optional values are nil and type does not accept nil' do expect { first.from_hash('name' => 'Bob Builder', 'age' => nil) }.to( raise_error(TypeAssertionError, "MyModule::FirstGenerated initializer has wrong type, entry 'age' expects an Integer value, got Undef") ) end end context 'creates an instance' do it 'that the TypeCalculator infers to the Object type' do expect(TypeCalculator.infer(first.from_hash('name' => 'Bob Builder'))).to eq(first_type) end end end context 'when generating static code' do module_def = nil module_def2 = nil before(:each) do # Ideally, this would be in a before(:all) but that is impossible since lots of Puppet # environment specific settings are configured by the spec_helper in before(:each) if module_def.nil? eval_and_collect_notices(source) do typeset1 = parser.parse('MyModule') typeset2 = parser.parse('OtherModule') Loaders.implementation_registry.register_type_mapping( PRuntimeType.new(:ruby, [/^PuppetSpec::RubyGenerator::My::(\w+)$/, 'MyModule::\1']), [/^MyModule::(\w+)$/, 'PuppetSpec::RubyGenerator::My::\1']) Loaders.implementation_registry.register_type_mapping( PRuntimeType.new(:ruby, [/^PuppetSpec::RubyGenerator::Other::(\w+)$/, 'OtherModule::\1']), [/^OtherModule::(\w+)$/, 'PuppetSpec::RubyGenerator::Other::\1']) module_def = generator.module_definition_from_typeset(typeset1) module_def2 = generator.module_definition_from_typeset(typeset2) end Loaders.clear Puppet[:code] = nil # Create the actual classes in the PuppetSpec::RubyGenerator module Puppet.override(:loaders => Puppet::Pops::Loaders.new(Puppet::Node::Environment.create(:testing, []))) do eval(module_def, root_binding) eval(module_def2, root_binding) end end end after(:all) do # Don't want generated module to leak outside this test PuppetSpec.send(:remove_const, :RubyGenerator) end it 'the #_pcore_type class method returns a resolved Type' do first_type = PuppetSpec::RubyGenerator::My::FirstGenerated._pcore_type expect(first_type).to be_a(PObjectType) second_type = PuppetSpec::RubyGenerator::My::SecondGenerated._pcore_type expect(second_type).to be_a(PObjectType) expect(second_type.parent).to eql(first_type) end context 'the #create class method' do it 'has an arity that reflects optional arguments' do expect(PuppetSpec::RubyGenerator::My::FirstGenerated.method(:create).arity).to eql(-2) expect(PuppetSpec::RubyGenerator::My::SecondGenerated.method(:create).arity).to eql(-6) end it 'creates an instance of the class' do inst = PuppetSpec::RubyGenerator::My::FirstGenerated.create('Bob Builder', 52) expect(inst).to be_a(PuppetSpec::RubyGenerator::My::FirstGenerated) expect(inst.name).to eq('Bob Builder') expect(inst.age).to eq(52) end it 'will perform type assertion of the arguments' do expect { PuppetSpec::RubyGenerator::My::FirstGenerated.create('Bob Builder', '52') }.to( raise_error(TypeAssertionError, 'MyModule::FirstGenerated[age] has wrong type, expects an Integer value, got String') ) end it 'will not accept nil as given value for an optional parameter that does not accept nil' do expect { PuppetSpec::RubyGenerator::My::FirstGenerated.create('Bob Builder', nil) }.to( raise_error(TypeAssertionError, 'MyModule::FirstGenerated[age] has wrong type, expects an Integer value, got Undef') ) end it 'reorders parameters to but the optional parameters last' do inst = PuppetSpec::RubyGenerator::My::SecondGenerated.create('Bob Builder', '42 Cool Street', '12345', 'bob@example.com', 23) expect(inst.name).to eq('Bob Builder') expect(inst.address).to eq('42 Cool Street') expect(inst.zipcode).to eq('12345') expect(inst.email).to eq('bob@example.com') expect(inst.number).to eq(23) expect(inst.what).to eql('what is this') expect(inst.age).to eql(30) expect(inst.another).to be_nil end it 'two instances with the same attribute values are equal using #eql?' do inst1 = PuppetSpec::RubyGenerator::My::SecondGenerated.create('Bob Builder', '42 Cool Street', '12345', 'bob@example.com', 23) inst2 = PuppetSpec::RubyGenerator::My::SecondGenerated.create('Bob Builder', '42 Cool Street', '12345', 'bob@example.com', 23) expect(inst1.eql?(inst2)).to be_truthy end it 'two instances with the same attribute values are equal using #==' do inst1 = PuppetSpec::RubyGenerator::My::SecondGenerated.create('Bob Builder', '42 Cool Street', '12345', 'bob@example.com', 23) inst2 = PuppetSpec::RubyGenerator::My::SecondGenerated.create('Bob Builder', '42 Cool Street', '12345', 'bob@example.com', 23) expect(inst1 == inst2).to be_truthy end it 'two instances with the different attribute in super class values are different' do inst1 = PuppetSpec::RubyGenerator::My::SecondGenerated.create('Bob Builder', '42 Cool Street', '12345', 'bob@example.com', 23) inst2 = PuppetSpec::RubyGenerator::My::SecondGenerated.create('Bob Engineer', '42 Cool Street', '12345', 'bob@example.com', 23) expect(inst1 == inst2).to be_falsey end it 'two instances with the different attribute in sub class values are different' do inst1 = PuppetSpec::RubyGenerator::My::SecondGenerated.create('Bob Builder', '42 Cool Street', '12345', 'bob@example.com', 23) inst2 = PuppetSpec::RubyGenerator::My::SecondGenerated.create('Bob Builder', '42 Cool Street', '12345', 'bob@other.com', 23) expect(inst1 == inst2).to be_falsey end end context 'the #from_hash class method' do it 'has an arity of one' do expect(PuppetSpec::RubyGenerator::My::FirstGenerated.method(:from_hash).arity).to eql(1) expect(PuppetSpec::RubyGenerator::My::SecondGenerated.method(:from_hash).arity).to eql(1) end it 'creates an instance of the class' do inst = PuppetSpec::RubyGenerator::My::FirstGenerated.from_hash('name' => 'Bob Builder', 'age' => 52) expect(inst).to be_a(PuppetSpec::RubyGenerator::My::FirstGenerated) expect(inst.name).to eq('Bob Builder') expect(inst.age).to eq(52) end it 'accepts an initializer where optional keys are missing' do inst = PuppetSpec::RubyGenerator::My::FirstGenerated.from_hash('name' => 'Bob Builder') expect(inst).to be_a(PuppetSpec::RubyGenerator::My::FirstGenerated) expect(inst.name).to eq('Bob Builder') expect(inst.age).to eq(30) end it 'does not accept an initializer where optional values are nil and type does not accept nil' do expect { PuppetSpec::RubyGenerator::My::FirstGenerated.from_hash('name' => 'Bob Builder', 'age' => nil) }.to( raise_error(TypeAssertionError, "MyModule::FirstGenerated initializer has wrong type, entry 'age' expects an Integer value, got Undef") ) end end end end end end end puppet-5.5.10/spec/unit/pops/types/type_acceptor_spec.rb0000644005276200011600000000671113417161721023255 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops/types/type_acceptor' class PuppetSpec::TestTypeAcceptor include Puppet::Pops::Types::TypeAcceptor attr_reader :visitors, :guard def initialize @visitors = [] @guard = nil end def visit(type, guard) @visitors << type @guard = guard end end module Puppet::Pops::Types describe 'the Puppet::Pops::Types::TypeAcceptor' do let!(:acceptor_class) { PuppetSpec::TestTypeAcceptor } let(:acceptor) { acceptor_class.new } let(:guard) { RecursionGuard.new } it "should get a visit from the type that accepts it" do PAnyType::DEFAULT.accept(acceptor, nil) expect(acceptor.visitors).to include(PAnyType::DEFAULT) end it "should receive the guard as an argument" do PAnyType::DEFAULT.accept(acceptor, guard) expect(acceptor.guard).to equal(guard) end it "should get a visit from the type of a Type that accepts it" do t = PTypeType.new(PAnyType::DEFAULT) t.accept(acceptor, nil) expect(acceptor.visitors).to include(t, PAnyType::DEFAULT) end [ PTypeType, PNotUndefType, PIterableType, PIteratorType, POptionalType ].each do |tc| it "should get a visit from the contained type of an #{tc.class.name} that accepts it" do t = tc.new(PStringType::DEFAULT) t.accept(acceptor, nil) expect(acceptor.visitors).to include(t, PStringType::DEFAULT) end end it "should get a visit from the size type of String type that accepts it" do sz = PIntegerType.new(0,4) t = PStringType.new(sz) t.accept(acceptor, nil) expect(acceptor.visitors).to include(t, sz) end it "should get a visit from all contained types of an Array type that accepts it" do sz = PIntegerType.new(0,4) t = PArrayType.new(PAnyType::DEFAULT, sz) t.accept(acceptor, nil) expect(acceptor.visitors).to include(t, PAnyType::DEFAULT, sz) end it "should get a visit from all contained types of a Hash type that accepts it" do sz = PIntegerType.new(0,4) t = PHashType.new(PStringType::DEFAULT, PAnyType::DEFAULT, sz) t.accept(acceptor, nil) expect(acceptor.visitors).to include(t, PStringType::DEFAULT, PAnyType::DEFAULT, sz) end it "should get a visit from all contained types of a Tuple type that accepts it" do sz = PIntegerType.new(0,4) t = PTupleType.new([PStringType::DEFAULT, PIntegerType::DEFAULT], sz) t.accept(acceptor, nil) expect(acceptor.visitors).to include(t, PStringType::DEFAULT, PIntegerType::DEFAULT, sz) end it "should get a visit from all contained types of a Struct type that accepts it" do t = PStructType.new([PStructElement.new(PStringType::DEFAULT, PIntegerType::DEFAULT)]) t.accept(acceptor, nil) expect(acceptor.visitors).to include(t, PStringType::DEFAULT, PIntegerType::DEFAULT) end it "should get a visit from all contained types of a Callable type that accepts it" do sz = PIntegerType.new(0,4) args = PTupleType.new([PStringType::DEFAULT, PIntegerType::DEFAULT], sz) block = PCallableType::DEFAULT t = PCallableType.new(args, block) t.accept(acceptor, nil) expect(acceptor.visitors).to include(t, PStringType::DEFAULT, PIntegerType::DEFAULT, sz, args, block) end it "should get a visit from all contained types of a Variant type that accepts it" do t = PVariantType.new([PStringType::DEFAULT, PIntegerType::DEFAULT]) t.accept(acceptor, nil) expect(acceptor.visitors).to include(t, PStringType::DEFAULT, PIntegerType::DEFAULT) end end end puppet-5.5.10/spec/unit/pops/types/type_asserter_spec.rb0000644005276200011600000000322713417161721023304 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' module Puppet::Pops::Types describe 'the type asserter' do let!(:asserter) { TypeAsserter } context 'when deferring formatting of subject' it 'can use an array' do expect{ asserter.assert_instance_of(['The %s in the %s', 'gizmo', 'gadget'], PIntegerType::DEFAULT, 'lens') }.to( raise_error(TypeAssertionError, 'The gizmo in the gadget has wrong type, expects an Integer value, got String')) end it 'can use an array obtained from block' do expect do asserter.assert_instance_of('gizmo', PIntegerType::DEFAULT, 'lens') { |s| ['The %s in the %s', s, 'gadget'] } end.to(raise_error(TypeAssertionError, 'The gizmo in the gadget has wrong type, expects an Integer value, got String')) end it 'can use an subject obtained from zero argument block' do expect do asserter.assert_instance_of(nil, PIntegerType::DEFAULT, 'lens') { 'The gizmo in the gadget' } end.to(raise_error(TypeAssertionError, 'The gizmo in the gadget has wrong type, expects an Integer value, got String')) end it 'does not produce a string unless the assertion fails' do TypeAsserter.expects(:report_type_mismatch).never asserter.assert_instance_of(nil, PIntegerType::DEFAULT, 1) end it 'does not format string unless the assertion fails' do fmt_string = 'The %s in the %s' fmt_string.expects(:'%').never asserter.assert_instance_of([fmt_string, 'gizmo', 'gadget'], PIntegerType::DEFAULT, 1) end it 'does not call block unless the assertion fails' do expect do asserter.assert_instance_of(nil, PIntegerType::DEFAULT, 1) { |s| raise Error } end.not_to raise_error end end end puppet-5.5.10/spec/unit/pops/types/type_factory_spec.rb0000644005276200011600000002600013417161721023115 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' module Puppet::Pops module Types describe 'The type factory' do context 'when creating' do it 'integer() returns PIntegerType' do expect(TypeFactory.integer().class()).to eq(PIntegerType) end it 'float() returns PFloatType' do expect(TypeFactory.float().class()).to eq(PFloatType) end it 'string() returns PStringType' do expect(TypeFactory.string().class()).to eq(PStringType) end it 'boolean() returns PBooleanType' do expect(TypeFactory.boolean().class()).to eq(PBooleanType) end it 'pattern() returns PPatternType' do expect(TypeFactory.pattern().class()).to eq(PPatternType) end it 'regexp() returns PRegexpType' do expect(TypeFactory.regexp().class()).to eq(PRegexpType) end it 'enum() returns PEnumType' do expect(TypeFactory.enum().class()).to eq(PEnumType) end it 'variant() returns PVariantType' do expect(TypeFactory.variant().class()).to eq(PVariantType) end it 'scalar() returns PScalarType' do expect(TypeFactory.scalar().class()).to eq(PScalarType) end it 'data() returns Data' do expect(TypeFactory.data.name).to eq('Data') end it 'optional() returns POptionalType' do expect(TypeFactory.optional().class()).to eq(POptionalType) end it 'collection() returns PCollectionType' do expect(TypeFactory.collection().class()).to eq(PCollectionType) end it 'catalog_entry() returns PCatalogEntryType' do expect(TypeFactory.catalog_entry().class()).to eq(PCatalogEntryType) end it 'struct() returns PStructType' do expect(TypeFactory.struct().class()).to eq(PStructType) end it "object() returns PObjectType" do expect(TypeFactory.object.class).to eq(PObjectType) end it 'tuple() returns PTupleType' do expect(TypeFactory.tuple.class()).to eq(PTupleType) end it 'undef() returns PUndefType' do expect(TypeFactory.undef().class()).to eq(PUndefType) end it 'type_alias() returns PTypeAliasType' do expect(TypeFactory.type_alias().class()).to eq(PTypeAliasType) end it 'sem_ver() returns PSemVerType' do expect(TypeFactory.sem_ver.class).to eq(PSemVerType) end it 'sem_ver(r1, r2) returns constrained PSemVerType' do expect(TypeFactory.sem_ver('1.x', '3.x').ranges).to include(SemanticPuppet::VersionRange.parse('1.x'), SemanticPuppet::VersionRange.parse('3.x')) end it 'sem_ver_range() returns PSemVerRangeType' do expect(TypeFactory.sem_ver_range.class).to eq(PSemVerRangeType) end it 'default() returns PDefaultType' do expect(TypeFactory.default().class()).to eq(PDefaultType) end it 'range(to, from) returns PIntegerType' do t = TypeFactory.range(1,2) expect(t.class()).to eq(PIntegerType) expect(t.from).to eq(1) expect(t.to).to eq(2) end it 'range(default, default) returns PIntegerType' do t = TypeFactory.range(:default,:default) expect(t.class()).to eq(PIntegerType) expect(t.from).to eq(nil) expect(t.to).to eq(nil) end it 'float_range(to, from) returns PFloatType' do t = TypeFactory.float_range(1.0, 2.0) expect(t.class()).to eq(PFloatType) expect(t.from).to eq(1.0) expect(t.to).to eq(2.0) end it 'float_range(default, default) returns PFloatType' do t = TypeFactory.float_range(:default, :default) expect(t.class()).to eq(PFloatType) expect(t.from).to eq(nil) expect(t.to).to eq(nil) end it 'resource() creates a generic PResourceType' do pr = TypeFactory.resource() expect(pr.class()).to eq(PResourceType) expect(pr.type_name).to eq(nil) end it 'resource(x) creates a PResourceType[x]' do pr = TypeFactory.resource('x') expect(pr.class()).to eq(PResourceType) expect(pr.type_name).to eq('X') end it 'host_class() creates a generic PClassType' do hc = TypeFactory.host_class() expect(hc.class()).to eq(PClassType) expect(hc.class_name).to eq(nil) end it 'host_class(x) creates a PClassType[x]' do hc = TypeFactory.host_class('x') expect(hc.class()).to eq(PClassType) expect(hc.class_name).to eq('x') end it 'host_class(::x) creates a PClassType[x]' do hc = TypeFactory.host_class('::x') expect(hc.class()).to eq(PClassType) expect(hc.class_name).to eq('x') end it 'array_of(fixnum) returns PArrayType[PIntegerType]' do at = TypeFactory.array_of(1) expect(at.class()).to eq(PArrayType) expect(at.element_type.class).to eq(PIntegerType) end it 'array_of(PIntegerType) returns PArrayType[PIntegerType]' do at = TypeFactory.array_of(PIntegerType::DEFAULT) expect(at.class()).to eq(PArrayType) expect(at.element_type.class).to eq(PIntegerType) end it 'array_of_data returns Array[Data]' do at = TypeFactory.array_of_data expect(at.class()).to eq(PArrayType) expect(at.element_type.name).to eq('Data') end it 'hash_of_data returns Hash[String,Data]' do ht = TypeFactory.hash_of_data expect(ht.class()).to eq(PHashType) expect(ht.key_type.class).to eq(PStringType) expect(ht.value_type.name).to eq('Data') end it 'ruby(1) returns PRuntimeType[ruby, \'Integer\']' do ht = TypeFactory.ruby(1) expect(ht.class()).to eq(PRuntimeType) expect(ht.runtime).to eq(:ruby) expect(ht.runtime_type_name).to eq(1.class.name) end it 'a size constrained collection can be created from array' do t = TypeFactory.array_of(TypeFactory.data, TypeFactory.range(1,2)) expect(t.size_type.class).to eq(PIntegerType) expect(t.size_type.from).to eq(1) expect(t.size_type.to).to eq(2) end it 'a size constrained collection can be created from hash' do t = TypeFactory.hash_of(TypeFactory.scalar, TypeFactory.data, TypeFactory.range(1,2)) expect(t.size_type.class).to eq(PIntegerType) expect(t.size_type.from).to eq(1) expect(t.size_type.to).to eq(2) end it 'a typed empty array, the resulting array erases the type' do t = Puppet::Pops::Types::TypeFactory.array_of(Puppet::Pops::Types::TypeFactory.data, Puppet::Pops::Types::TypeFactory.range(0,0)) expect(t.size_type.class).to eq(Puppet::Pops::Types::PIntegerType) expect(t.size_type.from).to eq(0) expect(t.size_type.to).to eq(0) expect(t.element_type).to eq(Puppet::Pops::Types::PUnitType::DEFAULT) end it 'a typed empty hash, the resulting hash erases the key and value type' do t = Puppet::Pops::Types::TypeFactory.hash_of(Puppet::Pops::Types::TypeFactory.scalar, Puppet::Pops::Types::TypeFactory.data, Puppet::Pops::Types::TypeFactory.range(0,0)) expect(t.size_type.class).to eq(Puppet::Pops::Types::PIntegerType) expect(t.size_type.from).to eq(0) expect(t.size_type.to).to eq(0) expect(t.key_type).to eq(Puppet::Pops::Types::PUnitType::DEFAULT) expect(t.value_type).to eq(Puppet::Pops::Types::PUnitType::DEFAULT) end context 'callable types' do it 'the callable methods produces a Callable' do t = TypeFactory.callable() expect(t.class).to be(PCallableType) expect(t.param_types.class).to be(PTupleType) expect(t.param_types.types).to be_empty expect(t.block_type).to be_nil end it 'callable method with types produces the corresponding Tuple for parameters and generated names' do tf = TypeFactory t = tf.callable(tf.integer, tf.string) expect(t.class).to be(PCallableType) expect(t.param_types.class).to be(PTupleType) expect(t.param_types.types).to eql([tf.integer, tf.string]) expect(t.block_type).to be_nil end it 'callable accepts min range to be given' do tf = TypeFactory t = tf.callable(tf.integer, tf.string, 1) expect(t.class).to be(PCallableType) expect(t.param_types.class).to be(PTupleType) expect(t.param_types.size_type.from).to eql(1) expect(t.param_types.size_type.to).to be_nil end it 'callable accepts max range to be given' do tf = TypeFactory t = tf.callable(tf.integer, tf.string, 1, 3) expect(t.class).to be(PCallableType) expect(t.param_types.class).to be(PTupleType) expect(t.param_types.size_type.from).to eql(1) expect(t.param_types.size_type.to).to eql(3) end it 'callable accepts max range to be given as :default' do tf = TypeFactory t = tf.callable(tf.integer, tf.string, 1, :default) expect(t.class).to be(PCallableType) expect(t.param_types.class).to be(PTupleType) expect(t.param_types.size_type.from).to eql(1) expect(t.param_types.size_type.to).to be_nil end it 'the all_callables method produces a Callable matching any Callable' do t = TypeFactory.all_callables() expect(t.class).to be(PCallableType) expect(t.param_types).to be_nil expect(t.block_type).to be_nil end it 'with block are created by placing a Callable last' do block_t = TypeFactory.callable(String) t = TypeFactory.callable(String, block_t) expect(t.block_type).to be(block_t) end it 'min size constraint can be used with a block last' do block_t = TypeFactory.callable(String) t = TypeFactory.callable(String, 1, block_t) expect(t.block_type).to be(block_t) expect(t.param_types.size_type.from).to eql(1) expect(t.param_types.size_type.to).to be_nil end it 'min, max size constraint can be used with a block last' do block_t = TypeFactory.callable(String) t = TypeFactory.callable(String, 1, 3, block_t) expect(t.block_type).to be(block_t) expect(t.param_types.size_type.from).to eql(1) expect(t.param_types.size_type.to).to eql(3) end it 'the with_block methods decorates a Callable with a block_type' do t = TypeFactory.callable t2 = TypeFactory.callable(t) block_t = t2.block_type # given t is returned after mutation expect(block_t).to be(t) expect(block_t.class).to be(PCallableType) expect(block_t.param_types.class).to be(PTupleType) expect(block_t.param_types.types).to be_empty expect(block_t.block_type).to be_nil end it 'the with_optional_block methods decorates a Callable with an optional block_type' do b = TypeFactory.callable t = TypeFactory.optional(b) t2 = TypeFactory.callable(t) opt_t = t2.block_type expect(opt_t.class).to be(POptionalType) block_t = opt_t.optional_type # given t is returned after mutation expect(opt_t).to be(t) expect(block_t).to be(b) expect(block_t.class).to be(PCallableType) expect(block_t.param_types.class).to be(PTupleType) expect(block_t.param_types.types).to be_empty expect(block_t.block_type).to be_nil end end end end end end puppet-5.5.10/spec/unit/pops/types/type_formatter_spec.rb0000644005276200011600000003615113417161721023461 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' module Puppet::Pops::Types describe 'The type formatter' do let(:s) { TypeFormatter.new } let(:f) { TypeFactory } def drop_indent(str) ld = str.index("\n") if ld.nil? str else str.gsub(Regexp.compile("^ {#{ld - 1}}"), '') end end context 'when representing a literal as a string' do { 'true' => true, 'false' => false, 'undef' => nil, '23.4' => 23.4, '145' => 145, "'string'" => 'string', '/expr/' => /expr/, '[1, 2, 3]' => [1, 2, 3], "{'a' => 32, 'b' => [1, 2, 3]}" => {'a' => 32,'b' => [1, 2, 3]} }.each_pair do |str, value| it "should yield '#{str}' for a value of #{str}" do expect(s.string(value)).to eq(str) end end end context 'when using indent' do it 'should put hash entries on new indented lines' do expect(s.indented_string({'a' => 32,'b' => [1, 2, {'c' => 'd'}]})).to eq(<<-FORMATTED) { 'a' => 32, 'b' => [ 1, 2, { 'c' => 'd' } ] } FORMATTED end it 'should start on given indent level' do expect(s.indented_string({'a' => 32,'b' => [1, 2, {'c' => 'd'}]}, 3)).to eq(<<-FORMATTED) { 'a' => 32, 'b' => [ 1, 2, { 'c' => 'd' } ] } FORMATTED end it 'should use given indent width' do expect(s.indented_string({'a' => 32,'b' => [1, 2, {'c' => 'd'}]}, 2, 4)).to eq(<<-FORMATTED) { 'a' => 32, 'b' => [ 1, 2, { 'c' => 'd' } ] } FORMATTED end end context 'when representing the type as string' do include_context 'types_setup' it "should yield 'Type' for PTypeType" do expect(s.string(f.type_type)).to eq('Type') end it "should yield 'Any' for PAnyType" do expect(s.string(f.any)).to eq('Any') end it "should yield 'Scalar' for PScalarType" do expect(s.string(f.scalar)).to eq('Scalar') end it "should yield 'Boolean' for PBooleanType" do expect(s.string(f.boolean)).to eq('Boolean') end it "should yield 'Boolean[true]' for PBooleanType parameterized with true" do expect(s.string(f.boolean(true))).to eq('Boolean[true]') end it "should yield 'Boolean[false]' for PBooleanType parameterized with false" do expect(s.string(f.boolean(false))).to eq('Boolean[false]') end it "should yield 'Data' for the Data type" do expect(s.string(f.data)).to eq('Data') end it "should yield 'Numeric' for PNumericType" do expect(s.string(f.numeric)).to eq('Numeric') end it "should yield 'Integer' and from/to for PIntegerType" do expect(s.string(f.integer)).to eq('Integer') expect(s.string(f.range(1,1))).to eq('Integer[1, 1]') expect(s.string(f.range(1,2))).to eq('Integer[1, 2]') expect(s.string(f.range(:default, 2))).to eq('Integer[default, 2]') expect(s.string(f.range(2, :default))).to eq('Integer[2]') end it "should yield 'Float' for PFloatType" do expect(s.string(f.float)).to eq('Float') end it "should yield 'Regexp' for PRegexpType" do expect(s.string(f.regexp)).to eq('Regexp') end it "should yield 'Regexp[/pat/]' for parameterized PRegexpType" do expect(s.string(f.regexp('a/b'))).to eq('Regexp[/a\/b/]') end it "should yield 'String' for PStringType" do expect(s.string(f.string)).to eq('String') end it "should yield 'String' for PStringType with value" do expect(s.string(f.string('a'))).to eq('String') end it "should yield 'String['a']' for PStringType with value if printed with debug_string" do expect(s.debug_string(f.string('a'))).to eq("String['a']") end it "should yield 'String' and from/to for PStringType" do expect(s.string(f.string(f.range(1,1)))).to eq('String[1, 1]') expect(s.string(f.string(f.range(1,2)))).to eq('String[1, 2]') expect(s.string(f.string(f.range(:default, 2)))).to eq('String[0, 2]') expect(s.string(f.string(f.range(2, :default)))).to eq('String[2]') end it "should yield 'Array[Integer]' for PArrayType[PIntegerType]" do expect(s.string(f.array_of(f.integer))).to eq('Array[Integer]') end it "should yield 'Array' for PArrayType::DEFAULT" do expect(s.string(f.array_of_any)).to eq('Array') end it "should yield 'Array[0, 0]' for an empty array" do t = f.array_of(PUnitType::DEFAULT, f.range(0,0)) expect(s.string(t)).to eq('Array[0, 0]') end it "should yield 'Hash[0, 0]' for an empty hash" do t = f.hash_of(PUnitType::DEFAULT, PUnitType::DEFAULT, f.range(0,0)) expect(s.string(t)).to eq('Hash[0, 0]') end it "should yield 'Collection' and from/to for PCollectionType" do expect(s.string(f.collection(f.range(1,1)))).to eq('Collection[1, 1]') expect(s.string(f.collection(f.range(1,2)))).to eq('Collection[1, 2]') expect(s.string(f.collection(f.range(:default, 2)))).to eq('Collection[0, 2]') expect(s.string(f.collection(f.range(2, :default)))).to eq('Collection[2]') end it "should yield 'Array' and from/to for PArrayType" do expect(s.string(f.array_of(f.string, f.range(1,1)))).to eq('Array[String, 1, 1]') expect(s.string(f.array_of(f.string, f.range(1,2)))).to eq('Array[String, 1, 2]') expect(s.string(f.array_of(f.string, f.range(:default, 2)))).to eq('Array[String, 0, 2]') expect(s.string(f.array_of(f.string, f.range(2, :default)))).to eq('Array[String, 2]') end it "should yield 'Iterable' for PIterableType" do expect(s.string(f.iterable)).to eq('Iterable') end it "should yield 'Iterable[Integer]' for PIterableType[PIntegerType]" do expect(s.string(f.iterable(f.integer))).to eq('Iterable[Integer]') end it "should yield 'Iterator' for PIteratorType" do expect(s.string(f.iterator)).to eq('Iterator') end it "should yield 'Iterator[Integer]' for PIteratorType[PIntegerType]" do expect(s.string(f.iterator(f.integer))).to eq('Iterator[Integer]') end it "should yield 'Timespan' for PTimespanType" do expect(s.string(f.timespan())).to eq('Timespan') end it "should yield 'Timespan[{hours => 1}] for PTimespanType[Timespan]" do expect(s.string(f.timespan({'hours' => 1}))).to eq("Timespan['0-01:00:00.0']") end it "should yield 'Timespan[default, {hours => 2}] for PTimespanType[nil, Timespan]" do expect(s.string(f.timespan(nil, {'hours' => 2}))).to eq("Timespan[default, '0-02:00:00.0']") end it "should yield 'Timespan[{hours => 1}, {hours => 2}] for PTimespanType[Timespan, Timespan]" do expect(s.string(f.timespan({'hours' => 1}, {'hours' => 2}))).to eq("Timespan['0-01:00:00.0', '0-02:00:00.0']") end it "should yield 'Timestamp' for PTimestampType" do expect(s.string(f.timestamp())).to eq('Timestamp') end it "should yield 'Timestamp['2016-09-05T13:00:00.000 UTC'] for PTimestampType[Timestamp]" do expect(s.string(f.timestamp('2016-09-05T13:00:00.000 UTC'))).to eq("Timestamp['2016-09-05T13:00:00.000000000 UTC']") end it "should yield 'Timestamp[default, '2016-09-05T13:00:00.000 UTC'] for PTimestampType[nil, Timestamp]" do expect(s.string(f.timestamp(nil, '2016-09-05T13:00:00.000 UTC'))).to eq("Timestamp[default, '2016-09-05T13:00:00.000000000 UTC']") end it "should yield 'Timestamp['2016-09-05T13:00:00.000 UTC', '2016-12-01T00:00:00.000 UTC'] for PTimestampType[Timestamp, Timestamp]" do expect(s.string(f.timestamp('2016-09-05T13:00:00.000 UTC', '2016-12-01T00:00:00.000 UTC'))).to( eq("Timestamp['2016-09-05T13:00:00.000000000 UTC', '2016-12-01T00:00:00.000000000 UTC']")) end it "should yield 'Tuple[Integer]' for PTupleType[PIntegerType]" do expect(s.string(f.tuple([f.integer]))).to eq('Tuple[Integer]') end it "should yield 'Tuple[T, T,..]' for PTupleType[T, T, ...]" do expect(s.string(f.tuple([f.integer, f.integer, f.string]))).to eq('Tuple[Integer, Integer, String]') end it "should yield 'Tuple' and from/to for PTupleType" do types = [f.string] expect(s.string(f.tuple(types, f.range(1,1)))).to eq('Tuple[String, 1, 1]') expect(s.string(f.tuple(types, f.range(1,2)))).to eq('Tuple[String, 1, 2]') expect(s.string(f.tuple(types, f.range(:default, 2)))).to eq('Tuple[String, 0, 2]') expect(s.string(f.tuple(types, f.range(2, :default)))).to eq('Tuple[String, 2]') end it "should yield 'Struct' and details for PStructType" do struct_t = f.struct({'a'=>Integer, 'b'=>String}) expect(s.string(struct_t)).to eq("Struct[{'a' => Integer, 'b' => String}]") struct_t = f.struct({}) expect(s.string(struct_t)).to eq('Struct') end it "should yield 'Hash[String, Integer]' for PHashType[PStringType, PIntegerType]" do expect(s.string(f.hash_of(f.integer, f.string))).to eq('Hash[String, Integer]') end it "should yield 'Hash' and from/to for PHashType" do expect(s.string(f.hash_of(f.string, f.string, f.range(1,1)))).to eq('Hash[String, String, 1, 1]') expect(s.string(f.hash_of(f.string, f.string, f.range(1,2)))).to eq('Hash[String, String, 1, 2]') expect(s.string(f.hash_of(f.string, f.string, f.range(:default, 2)))).to eq('Hash[String, String, 0, 2]') expect(s.string(f.hash_of(f.string, f.string, f.range(2, :default)))).to eq('Hash[String, String, 2]') end it "should yield 'Hash' for PHashType::DEFAULT" do expect(s.string(f.hash_of_any)).to eq('Hash') end it "should yield 'Class' for a PClassType" do expect(s.string(f.host_class)).to eq('Class') end it "should yield 'Class[x]' for a PClassType[x]" do expect(s.string(f.host_class('x'))).to eq('Class[x]') end it "should yield 'Resource' for a PResourceType" do expect(s.string(f.resource)).to eq('Resource') end it "should yield 'File' for a PResourceType['File']" do expect(s.string(f.resource('File'))).to eq('File') end it "should yield 'File['/tmp/foo']' for a PResourceType['File', '/tmp/foo']" do expect(s.string(f.resource('File', '/tmp/foo'))).to eq("File['/tmp/foo']") end it "should yield 'Enum[s,...]' for a PEnumType[s,...]" do t = f.enum('a', 'b', 'c') expect(s.string(t)).to eq("Enum['a', 'b', 'c']") end it "should yield 'Enum[s,...]' for a PEnumType[s,...,false]" do t = f.enum('a', 'b', 'c', false) expect(s.string(t)).to eq("Enum['a', 'b', 'c']") end it "should yield 'Enum[s,...,true]' for a PEnumType[s,...,true]" do t = f.enum('a', 'b', 'c', true) expect(s.string(t)).to eq("Enum['a', 'b', 'c', true]") end it "should yield 'Pattern[/pat/,...]' for a PPatternType['pat',...]" do t = f.pattern('a') t2 = f.pattern('a', 'b', 'c') expect(s.string(t)).to eq('Pattern[/a/]') expect(s.string(t2)).to eq('Pattern[/a/, /b/, /c/]') end it "should escape special characters in the string for a PPatternType['pat',...]" do t = f.pattern('a/b') expect(s.string(t)).to eq("Pattern[/a\\/b/]") end it "should yield 'Variant[t1,t2,...]' for a PVariantType[t1, t2,...]" do t1 = f.string t2 = f.integer t3 = f.pattern('a') t = f.variant(t1, t2, t3) expect(s.string(t)).to eq('Variant[String, Integer, Pattern[/a/]]') end it "should yield 'Callable' for generic callable" do expect(s.string(f.all_callables)).to eql('Callable') end it "should yield 'Callable[0,0]' for callable without params" do expect(s.string(f.callable)).to eql('Callable[0, 0]') end it "should yield 'Callable[[0,0],rt]' for callable without params but with return type" do expect(s.string(f.callable([], Float))).to eql('Callable[[0, 0], Float]') end it "should yield 'Callable[t,t]' for callable with typed parameters" do expect(s.string(f.callable(String, Integer))).to eql('Callable[String, Integer]') end it "should yield 'Callable[[t,t],rt]' for callable with typed parameters and return type" do expect(s.string(f.callable([String, Integer], Float))).to eql('Callable[[String, Integer], Float]') end it "should yield 'Callable[t,min,max]' for callable with size constraint (infinite max)" do expect(s.string(f.callable(String, 0))).to eql('Callable[String, 0]') end it "should yield 'Callable[t,min,max]' for callable with size constraint (capped max)" do expect(s.string(f.callable(String, 0, 3))).to eql('Callable[String, 0, 3]') end it "should yield 'Callable[min,max]' callable with size > 0" do expect(s.string(f.callable(0, 0))).to eql('Callable[0, 0]') expect(s.string(f.callable(0, 1))).to eql('Callable[0, 1]') expect(s.string(f.callable(0, :default))).to eql('Callable[0]') end it "should yield 'Callable[Callable]' for callable with block" do expect(s.string(f.callable(f.all_callables))).to eql('Callable[0, 0, Callable]') expect(s.string(f.callable(f.string, f.all_callables))).to eql('Callable[String, Callable]') expect(s.string(f.callable(f.string, 1,1, f.all_callables))).to eql('Callable[String, 1, 1, Callable]') end it 'should yield Unit for a Unit type' do expect(s.string(PUnitType::DEFAULT)).to eql('Unit') end it "should yield 'NotUndef' for a PNotUndefType" do t = f.not_undef expect(s.string(t)).to eq('NotUndef') end it "should yield 'NotUndef[T]' for a PNotUndefType[T]" do t = f.not_undef(f.data) expect(s.string(t)).to eq('NotUndef[Data]') end it "should yield 'NotUndef['string']' for a PNotUndefType['string']" do t = f.not_undef('hey') expect(s.string(t)).to eq("NotUndef['hey']") end it "should yield the name of an unparameterized type reference" do t = f.type_reference('What') expect(s.string(t)).to eq("TypeReference['What']") end it "should yield the name and arguments of an parameterized type reference" do t = f.type_reference('What[Undef, String]') expect(s.string(t)).to eq("TypeReference['What[Undef, String]']") end it "should yield the name of a type alias" do t = f.type_alias('Alias', 'Integer') expect(s.string(t)).to eq('Alias') end it "should yield 'Type[Runtime[ruby, Puppet]]' for the Puppet module" do expect(s.string(Puppet)).to eq("Runtime[ruby, 'Puppet']") end it "should yield 'Type[Runtime[ruby, Puppet::Pops]]' for the Puppet::Resource class" do expect(s.string(Puppet::Resource)).to eq("Runtime[ruby, 'Puppet::Resource']") end it "should yield \"SemVer['1.x', '3.x']\" for the PSemVerType['1.x', '3.x']" do expect(s.string(PSemVerType.new(['1.x', '3.x']))).to eq("SemVer['1.x', '3.x']") end it 'should present a valid simple name' do (all_types - [PTypeType, PClassType]).each do |t| name = t::DEFAULT.simple_name expect(t.name).to match("^Puppet::Pops::Types::P#{name}Type$") end expect(PTypeType::DEFAULT.simple_name).to eql('Type') expect(PClassType::DEFAULT.simple_name).to eql('Class') end end end end puppet-5.5.10/spec/unit/pops/types/type_mismatch_describer_spec.rb0000644005276200011600000002323113417161721025300 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' require 'puppet_spec/compiler' module Puppet::Pops module Types describe 'the type mismatch describer' do include PuppetSpec::Compiler it 'will report a mismatch between a hash and a struct with details' do code = <<-CODE function f(Hash[String,String] $h) { $h['a'] } f({'a' => 'a', 'b' => 23}) CODE expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /'f' parameter 'h' entry 'b' expects a String value, got Integer/) end it 'will report a mismatch between a array and tuple with details' do code = <<-CODE function f(Array[String] $h) { $h[0] } f(['a', 23]) CODE expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /'f' parameter 'h' index 1 expects a String value, got Integer/) end it 'will not report details for a mismatch between an array and a struct' do code = <<-CODE function f(Array[String] $h) { $h[0] } f({'a' => 'a string', 'b' => 23}) CODE expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /expects an Array value, got Struct/) end it 'will not report details for a mismatch between a hash and a tuple' do code = <<-CODE function f(Hash[String,String] $h) { $h['a'] } f(['a', 23]) CODE expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /expects a Hash value, got Tuple/) end it 'will report an array size mismatch' do code = <<-CODE function f(Array[String,1,default] $h) { $h[0] } f([]) CODE expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /expects size to be at least 1, got 0/) end it 'will report a hash size mismatch' do code = <<-CODE function f(Hash[String,String,1,default] $h) { $h['a'] } f({}) CODE expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /expects size to be at least 1, got 0/) end it 'will include the aliased type when reporting a mismatch that involves an alias' do code = <<-CODE type UnprivilegedPort = Integer[1024,65537] function check_port(UnprivilegedPort $port) {} check_port(34) CODE expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /parameter 'port' expects an UnprivilegedPort = Integer\[1024, 65537\] value, got Integer\[34, 34\]/) end it 'will include the aliased type when reporting a mismatch that involves an alias nested in another type' do code = <<-CODE type UnprivilegedPort = Integer[1024,65537] type PortMap = Hash[UnprivilegedPort,String] function check_port(PortMap $ports) {} check_port({ 34 => 'some service'}) CODE expect { eval_and_collect_notices(code) }.to(raise_error(Puppet::Error, /parameter 'ports' expects a PortMap = Hash\[UnprivilegedPort = Integer\[1024, 65537\], String\] value, got Hash\[Integer\[34, 34\], String\]/)) end it 'will not include the aliased type more than once when reporting a mismatch that involves an alias that is self recursive' do code = <<-CODE type Tree = Hash[String,Tree] function check_tree(Tree $tree) {} check_tree({ 'x' => {'y' => {32 => 'n'}}}) CODE expect { eval_and_collect_notices(code) }.to(raise_error(Puppet::Error, /parameter 'tree' entry 'x' entry 'y' expects a Tree = Hash\[String, Tree\] value, got Hash\[Integer\[32, 32\], String\]/)) end it 'will use type normalization' do code = <<-CODE type EVariants = Variant[Enum[a,b],Enum[b,c],Enum[c,d]] function check_enums(EVariants $evars) {} check_enums('n') CODE expect { eval_and_collect_notices(code) }.to(raise_error(Puppet::Error, /parameter 'evars' expects a match for EVariants = Enum\['a', 'b', 'c', 'd'\], got 'n'/)) end it "will not generalize a string that doesn't match an enum in a function call" do code = <<-CODE function check_enums(Enum[a,b] $arg) {} check_enums('c') CODE expect { eval_and_collect_notices(code) }.to(raise_error(Puppet::Error, /parameter 'arg' expects a match for Enum\['a', 'b'\], got 'c'/)) end it "will not disclose a Sensitive that doesn't match an enum in a function call" do code = <<-CODE function check_enums(Enum[a,b] $arg) {} check_enums(Sensitive('c')) CODE expect { eval_and_collect_notices(code) }.to(raise_error(Puppet::Error, /parameter 'arg' expects a match for Enum\['a', 'b'\], got Sensitive/)) end it "reports errors on the first failing parameter when that parameter is not the first in order" do code = <<-CODE type Abc = Enum['a', 'b', 'c'] type Cde = Enum['c', 'd', 'e'] function two_params(Abc $a, Cde $b) {} two_params('a', 'x') CODE expect { eval_and_collect_notices(code) }.to(raise_error(Puppet::Error, /parameter 'b' expects a match for Cde = Enum\['c', 'd', 'e'\], got 'x'/)) end it "will not generalize a string that doesn't match an enum in a define call" do code = <<-CODE define check_enums(Enum[a,b] $arg) {} check_enums { x: arg => 'c' } CODE expect { eval_and_collect_notices(code) }.to(raise_error(Puppet::Error, /parameter 'arg' expects a match for Enum\['a', 'b'\], got 'c'/)) end it "will include Undef when describing a mismatch against a Variant where one of the types is Undef" do code = <<-CODE define check(Variant[Undef,String,Integer,Hash,Array] $arg) {} check{ x: arg => 2.4 } CODE expect { eval_and_collect_notices(code) }.to(raise_error(Puppet::Error, /parameter 'arg' expects a value of type Undef, String, Integer, Hash, or Array/)) end it "will not disclose a Sensitive that doesn't match an enum in a define call" do code = <<-CODE define check_enums(Enum[a,b] $arg) {} check_enums { x: arg => Sensitive('c') } CODE expect { eval_and_collect_notices(code) }.to(raise_error(Puppet::Error, /parameter 'arg' expects a match for Enum\['a', 'b'\], got Sensitive/)) end it "will report the parameter of Type[] using the alias name" do code = <<-CODE type Custom = String[1] Custom.each |$x| { notice($x) } CODE expect { eval_and_collect_notices(code) }.to(raise_error(Puppet::Error, /expects an Iterable value, got Type\[Custom\]/)) end context 'when reporting a mismatch between' do let(:parser) { TypeParser.singleton } let(:subject) { TypeMismatchDescriber.singleton } context 'hash and struct' do it 'reports a size mismatch when hash has unlimited size' do expected = parser.parse('Struct[{a=>Integer,b=>Integer}]') actual = parser.parse('Hash[String,Integer]') expect(subject.describe_mismatch('', expected, actual)).to eq('expects size to be 2, got unlimited') end it 'reports a size mismatch when hash has specified but incorrect size' do expected = parser.parse('Struct[{a=>Integer,b=>Integer}]') actual = parser.parse('Hash[String,Integer,1,1]') expect(subject.describe_mismatch('', expected, actual)).to eq('expects size to be 2, got 1') end it 'reports a full type mismatch when size is correct but hash value type is incorrect' do expected = parser.parse('Struct[{a=>Integer,b=>String}]') actual = parser.parse('Hash[String,Integer,2,2]') expect(subject.describe_mismatch('', expected, actual)).to eq("expects a Struct[{'a' => Integer, 'b' => String}] value, got Hash[String, Integer]") end end it 'reports a missing parameter as "has no parameter"' do t = parser.parse('Struct[{a=>String}]') expect { subject.validate_parameters('v', t, {'a'=>'a','b'=>'b'}, false) }.to raise_error(Puppet::Error, "v: has no parameter named 'b'") end it 'reports a missing value as "expects a value"' do t = parser.parse('Struct[{a=>String,b=>String}]') expect { subject.validate_parameters('v', t, {'a'=>'a'}, false) }.to raise_error(Puppet::Error, "v: expects a value for parameter 'b'") end it 'reports a missing block as "expects a block"' do callable = parser.parse('Callable[String,String,Callable]') args_tuple = parser.parse('Tuple[String,String]') dispatch = Functions::Dispatch.new(callable, 'foo', ['a','b'], false, 'block') expect(subject.describe_signatures('function', [dispatch], args_tuple)).to eq("'function' expects a block") end it 'reports an unexpected block as "does not expect a block"' do callable = parser.parse('Callable[String,String]') args_tuple = parser.parse('Tuple[String,String,Callable]') dispatch = Functions::Dispatch.new(callable, 'foo', ['a','b']) expect(subject.describe_signatures('function', [dispatch], args_tuple)).to eq("'function' does not expect a block") end it 'reports a block return type mismatch' do callable = parser.parse('Callable[[0,0,Callable[ [0,0],String]],Undef]') args_tuple = parser.parse('Tuple[Callable[[0,0],Integer]]') dispatch = Functions::Dispatch.new(callable, 'foo', [], false, 'block') expect(subject.describe_signatures('function', [dispatch], args_tuple)).to eq("'function' block return expects a String value, got Integer") end end it "reports struct mismatch correctly when hash doesn't contain required keys" do code = <<-PUPPET type Test::Options = Struct[{ var => String }] class test(String $var, Test::Options $opts) {} class { 'test': var => 'hello', opts => {} } PUPPET expect { eval_and_collect_notices(code) }.to(raise_error(Puppet::Error, /Class\[Test\]: parameter 'opts' expects size to be 1, got 0/)) end end end end puppet-5.5.10/spec/unit/pops/types/p_object_type_spec.rb0000644005276200011600000015644613417161722023256 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' require 'puppet_spec/compiler' module Puppet::Pops module Types describe 'The Object Type' do include PuppetSpec::Compiler let(:parser) { TypeParser.singleton } let(:pp_parser) { Parser::EvaluatingParser.new } let(:env) { Puppet::Node::Environment.create(:testing, []) } let(:node) { Puppet::Node.new('testnode', :environment => env) } let(:loader) { Loaders.find_loader(nil) } let(:factory) { TypeFactory } before(:each) do Puppet.push_context(:loaders => Loaders.new(env)) end after(:each) do Puppet.pop_context() end def type_object_t(name, body_string) object = PObjectType.new(name, pp_parser.parse_string("{#{body_string}}").body) loader.set_entry(Loader::TypedName.new(:type, name), object) object end def parse_object(name, body_string) type_object_t(name, body_string) parser.parse(name, loader).resolve(loader) end context 'when dealing with attributes' do it 'raises an error when the attribute type is not a type' do obj = <<-OBJECT attributes => { a => 23 } OBJECT expect { parse_object('MyObject', obj) }.to raise_error(TypeAssertionError, /attribute MyObject\[a\] has wrong type, expects a Type value, got Integer/) end it 'raises an error if the type is missing' do obj = <<-OBJECT attributes => { a => { kind => derived } } OBJECT expect { parse_object('MyObject', obj) }.to raise_error(TypeAssertionError, /expects a value for key 'type'/) end it 'raises an error when value is of incompatible type' do obj = <<-OBJECT attributes => { a => { type => Integer, value => 'three' } } OBJECT expect { parse_object('MyObject', obj) }.to raise_error(TypeAssertionError, /attribute MyObject\[a\] value has wrong type, expects an Integer value, got String/) end it 'raises an error if the kind is invalid' do obj = <<-OBJECT attributes => { a => { type => String, kind => derivd } } OBJECT expect { parse_object('MyObject', obj) }.to raise_error(TypeAssertionError, /expects a match for Enum\['constant', 'derived', 'given_or_derived', 'reference'\], got 'derivd'/) end it 'stores value in attribute' do tp = parse_object('MyObject', <<-OBJECT) attributes => { a => { type => Integer, value => 3 } } OBJECT attr = tp['a'] expect(attr).to be_a(PObjectType::PAttribute) expect(attr.value).to eql(3) end it 'attribute with defined value responds true to value?' do tp = parse_object('MyObject', <<-OBJECT) attributes => { a => { type => Integer, value => 3 } } OBJECT attr = tp['a'] expect(attr.value?).to be_truthy end it 'attribute value can be defined using heredoc?' do tp = parse_object('MyObject', <<-OBJECT.unindent) attributes => { a => { type => String, value => @(END) } The value is some multiline text |-END } OBJECT attr = tp['a'] expect(attr.value).to eql("The value is some\nmultiline text") end it 'attribute without defined value responds false to value?' do tp = parse_object('MyObject', <<-OBJECT) attributes => { a => Integer } OBJECT attr = tp['a'] expect(attr.value?).to be_falsey end it 'attribute without defined value but optional type responds true to value?' do tp = parse_object('MyObject', <<-OBJECT) attributes => { a => Optional[Integer] } OBJECT attr = tp['a'] expect(attr.value?).to be_truthy expect(attr.value).to be_nil end it 'raises an error when value is requested from an attribute that has no value' do tp = parse_object('MyObject', <<-OBJECT) attributes => { a => Integer } OBJECT expect { tp['a'].value }.to raise_error(Puppet::Error, 'attribute MyObject[a] has no value') end context 'that are constants' do context 'and declared under key "constants"' do it 'sets final => true' do tp = parse_object('MyObject', <<-OBJECT) constants => { a => 3 } OBJECT expect(tp['a'].final?).to be_truthy end it 'sets kind => constant' do tp = parse_object('MyObject', <<-OBJECT) constants => { a => 3 } OBJECT expect(tp['a'].constant?).to be_truthy end it 'infers generic type from value' do tp = parse_object('MyObject', <<-OBJECT) constants => { a => 3 } OBJECT expect(tp['a'].type.to_s).to eql('Integer') end it 'cannot have the same name as an attribute' do obj = <<-OBJECT constants => { a => 3 }, attributes => { a => Integer } OBJECT expect { parse_object('MyObject', obj) }.to raise_error(Puppet::ParseError, 'attribute MyObject[a] is defined as both a constant and an attribute') end end context 'and declared under key "attributes"' do it 'sets final => true when declard in attributes' do tp = parse_object('MyObject', <<-OBJECT) attributes => { a => { type => Integer, kind => constant, value => 3 } } OBJECT expect(tp['a'].final?).to be_truthy end it 'raises an error when no value is declared' do obj = <<-OBJECT attributes => { a => { type => Integer, kind => constant } } OBJECT expect { parse_object('MyObject', obj) }.to raise_error(Puppet::ParseError, "attribute MyObject[a] of kind 'constant' requires a value") end it 'raises an error when final => false' do obj = <<-OBJECT attributes => { a => { type => Integer, kind => constant, final => false } } OBJECT expect { parse_object('MyObject', obj) }.to raise_error(Puppet::ParseError, "attribute MyObject[a] of kind 'constant' cannot be combined with final => false") end end end end context 'when dealing with functions' do it 'raises an error unless the function type is a Type[Callable]' do obj = <<-OBJECT functions => { a => String } OBJECT expect { parse_object('MyObject', obj) }.to raise_error(TypeAssertionError, /function MyObject\[a\] has wrong type, expects a Type\[Callable\] value, got Type\[String\]/) end it 'raises an error when a function has the same name as an attribute' do obj = <<-OBJECT attributes => { a => Integer }, functions => { a => Callable } OBJECT expect { parse_object('MyObject', obj) }.to raise_error(Puppet::ParseError, 'function MyObject[a] conflicts with attribute with the same name') end end context 'when dealing with overrides' do it 'can redefine inherited member to assignable type' do parent = <<-OBJECT attributes => { a => Integer } OBJECT obj = <<-OBJECT parent => MyObject, attributes => { a => { type => Integer[0,10], override => true } } OBJECT parse_object('MyObject', parent) tp = parse_object('MyDerivedObject', obj) expect(tp['a'].type).to eql(PIntegerType.new(0,10)) end it 'can redefine inherited constant to assignable type' do parent = <<-OBJECT constants => { a => 23 } OBJECT obj = <<-OBJECT parent => MyObject, constants => { a => 46 } OBJECT tp = parse_object('MyObject', parent) td = parse_object('MyDerivedObject', obj) expect(tp['a'].value).to eql(23) expect(td['a'].value).to eql(46) end it 'raises an error when an attribute overrides a function' do parent = <<-OBJECT attributes => { a => Integer } OBJECT obj = <<-OBJECT parent => MyObject, functions => { a => { type => Callable, override => true } } OBJECT parse_object('MyObject', parent) expect { parse_object('MyDerivedObject', obj) }.to raise_error(Puppet::ParseError, 'function MyDerivedObject[a] attempts to override attribute MyObject[a]') end it 'raises an error when the a function overrides an attribute' do parent = <<-OBJECT functions => { a => Callable } OBJECT obj = <<-OBJECT parent => MyObject, attributes => { a => { type => Integer, override => true } } OBJECT parse_object('MyObject', parent) expect { parse_object('MyDerivedObject', obj) }.to raise_error(Puppet::ParseError, 'attribute MyDerivedObject[a] attempts to override function MyObject[a]') end it 'raises an error on attempts to redefine inherited member to unassignable type' do parent = <<-OBJECT attributes => { a => Integer } OBJECT obj = <<-OBJECT parent => MyObject, attributes => { a => { type => String, override => true } } OBJECT parse_object('MyObject', parent) expect { parse_object('MyDerivedObject', obj) }.to raise_error(Puppet::ParseError, 'attribute MyDerivedObject[a] attempts to override attribute MyObject[a] with a type that does not match') end it 'raises an error when an attribute overrides a final attribute' do parent = <<-OBJECT attributes => { a => { type => Integer, final => true } } OBJECT obj = <<-OBJECT parent => MyObject, attributes => { a => { type => Integer, override => true } } OBJECT parse_object('MyObject', parent) expect { parse_object('MyDerivedObject', obj) }.to raise_error(Puppet::ParseError, 'attribute MyDerivedObject[a] attempts to override final attribute MyObject[a]') end it 'raises an error when an overriding attribute is not declared with override => true' do parent = <<-OBJECT attributes => { a => Integer } OBJECT obj = <<-OBJECT parent => MyObject, attributes => { a => Integer } OBJECT parse_object('MyObject', parent) expect { parse_object('MyDerivedObject', obj) }.to raise_error(Puppet::ParseError, 'attribute MyDerivedObject[a] attempts to override attribute MyObject[a] without having override => true') end it 'raises an error when an attribute declared with override => true does not override' do parent = <<-OBJECT attributes => { a => Integer } OBJECT obj = <<-OBJECT parent => MyObject, attributes => { b => { type => Integer, override => true } } OBJECT parse_object('MyObject', parent) expect { parse_object('MyDerivedObject', obj) }.to raise_error(Puppet::ParseError, "expected attribute MyDerivedObject[b] to override an inherited attribute, but no such attribute was found") end end context 'when dealing with equality' do it 'the attributes can be declared as an array of names' do obj = <<-OBJECT attributes => { a => Integer, b => Integer }, equality => [a,b] OBJECT tp = parse_object('MyObject', obj) expect(tp.equality).to eq(['a','b']) expect(tp.equality_attributes.keys).to eq(['a','b']) end it 'a single [] can be declared as ' do obj = <<-OBJECT attributes => { a => Integer, b => Integer }, equality => a OBJECT tp = parse_object('MyObject', obj) expect(tp.equality).to eq(['a']) end it 'includes all non-constant attributes by default' do obj = <<-OBJECT attributes => { a => Integer, b => { type => Integer, kind => constant, value => 3 }, c => Integer } OBJECT tp = parse_object('MyObject', obj) expect(tp.equality).to be_nil expect(tp.equality_attributes.keys).to eq(['a','c']) end it 'equality_include_type is true by default' do obj = <<-OBJECT attributes => { a => Integer }, equality => a OBJECT expect(parse_object('MyObject', obj).equality_include_type?).to be_truthy end it 'will allow an empty list of attributes' do obj = <<-OBJECT attributes => { a => Integer, b => Integer }, equality => [] OBJECT tp = parse_object('MyObject', obj) expect(tp.equality).to be_empty expect(tp.equality_attributes).to be_empty end it 'will extend default equality in parent' do parent = <<-OBJECT attributes => { a => Integer, b => Integer } OBJECT obj = <<-OBJECT parent => MyObject, attributes => { c => Integer, d => Integer } OBJECT parse_object('MyObject', parent) tp = parse_object('MyDerivedObject', obj) expect(tp.equality).to be_nil expect(tp.equality_attributes.keys).to eq(['a','b','c','d']) end it 'extends equality declared in parent' do parent = <<-OBJECT attributes => { a => Integer, b => Integer }, equality => a OBJECT obj = <<-OBJECT parent => MyObject, attributes => { c => Integer, d => Integer } OBJECT parse_object('MyObject', parent) tp = parse_object('MyDerivedObject', obj) expect(tp.equality).to be_nil expect(tp.equality_attributes.keys).to eq(['a','c','d']) end it 'parent defined attributes can be included in equality if not already included by a parent' do parent = <<-OBJECT attributes => { a => Integer, b => Integer }, equality => a OBJECT obj = <<-OBJECT parent => MyObject, attributes => { c => Integer, d => Integer }, equality => [b,c] OBJECT parse_object('MyObject', parent) tp = parse_object('MyDerivedObject', obj) expect(tp.equality).to eq(['b','c']) expect(tp.equality_attributes.keys).to eq(['a','b','c']) end it 'raises an error when attempting to extend default equality in parent' do parent = <<-OBJECT attributes => { a => Integer, b => Integer } OBJECT obj = <<-OBJECT parent => MyObject, attributes => { c => Integer, d => Integer }, equality => a OBJECT parse_object('MyObject', parent) expect { parse_object('MyDerivedObject', obj) }.to raise_error(Puppet::ParseError, "MyDerivedObject equality is referencing attribute MyObject[a] which is included in equality of MyObject") end it 'raises an error when equality references a constant attribute' do obj = <<-OBJECT attributes => { a => Integer, b => { type => Integer, kind => constant, value => 3 } }, equality => [a,b] OBJECT expect { parse_object('MyObject', obj) }.to raise_error(Puppet::ParseError, 'MyObject equality is referencing constant attribute MyObject[b]. Reference to constant is not allowed in equality') end it 'raises an error when equality references a function' do obj = <<-OBJECT attributes => { a => Integer, }, functions => { b => Callable }, equality => [a,b] OBJECT expect { parse_object('MyObject', obj) }.to raise_error(Puppet::ParseError, 'MyObject equality is referencing function MyObject[b]. Only attribute references are allowed') end it 'raises an error when equality references a non existent attributes' do obj = <<-OBJECT attributes => { a => Integer }, equality => [a,b] OBJECT expect { parse_object('MyObject', obj) }.to raise_error(Puppet::ParseError, "MyObject equality is referencing non existent attribute 'b'") end it 'raises an error when equality_include_type = false and attributes are provided' do obj = <<-OBJECT attributes => { a => Integer }, equality => a, equality_include_type => false OBJECT expect { parse_object('MyObject', obj) }.to raise_error(Puppet::ParseError, 'equality_include_type = false cannot be combined with non empty equality specification') end end it 'raises an error when initialization hash contains invalid keys' do obj = <<-OBJECT attribrutes => { a => Integer } OBJECT expect { parse_object('MyObject', obj) }.to raise_error(TypeAssertionError, /object initializer has wrong type, unrecognized key 'attribrutes'/) end it 'raises an error when attribute contains invalid keys' do obj = <<-OBJECT attributes => { a => { type => Integer, knid => constant } } OBJECT expect { parse_object('MyObject', obj) }.to raise_error(TypeAssertionError, /initializer for attribute MyObject\[a\] has wrong type, unrecognized key 'knid'/) end context 'when inheriting from a another Object type' do let(:parent) { <<-OBJECT } attributes => { a => Integer }, functions => { b => Callable } OBJECT let(:derived) { <<-OBJECT } parent => MyObject, attributes => { c => String, d => Boolean } OBJECT it 'includes the inherited type and its members' do parse_object('MyObject', parent) t = parse_object('MyDerivedObject', derived) members = t.members.values expect{ |b| members.each {|m| m.name.tap(&b) }}.to yield_successive_args('c', 'd') expect{ |b| members.each {|m| m.type.simple_name.tap(&b) }}.to yield_successive_args('String', 'Boolean') members = t.members(true).values expect{ |b| members.each {|m| m.name.tap(&b) }}.to yield_successive_args('a', 'b', 'c', 'd') expect{ |b| members.each {|m| m.type.simple_name.tap(&b) }}.to(yield_successive_args('Integer', 'Callable', 'String', 'Boolean')) end it 'is assignable to its inherited type' do p = parse_object('MyObject', parent) t = parse_object('MyDerivedObject', derived) expect(p).to be_assignable(t) end it 'does not consider inherited type to be assignable' do p = parse_object('MyObject', parent) d = parse_object('MyDerivedObject', derived) expect(d).not_to be_assignable(p) end it 'ruby access operator can retrieve parent member' do p = parse_object('MyObject', parent) d = parse_object('MyDerivedObject', derived) expect(d['b'].container).to equal(p) end context 'that in turn inherits another Object type' do let(:derived2) { <<-OBJECT } parent => MyDerivedObject, attributes => { e => String, f => Boolean } OBJECT it 'is assignable to all inherited types' do p = parse_object('MyObject', parent) d1 = parse_object('MyDerivedObject', derived) d2 = parse_object('MyDerivedObject2', derived2) expect(p).to be_assignable(d2) expect(d1).to be_assignable(d2) end it 'does not consider any of the inherited types to be assignable' do p = parse_object('MyObject', parent) d1 = parse_object('MyDerivedObject', derived) d2 = parse_object('MyDerivedObject2', derived2) expect(d2).not_to be_assignable(p) expect(d2).not_to be_assignable(d1) end end end context 'when producing an init_hash_type' do it 'produces a struct of all attributes that are not derived or constant' do t = parse_object('MyObject', <<-OBJECT) attributes => { a => { type => Integer }, b => { type => Integer, kind => given_or_derived }, c => { type => Integer, kind => derived }, d => { type => Integer, kind => constant, value => 4 } } OBJECT expect(t.init_hash_type).to eql(factory.struct({ 'a' => factory.integer, 'b' => factory.integer })) end it 'produces a struct where optional entires are denoted with an optional key' do t = parse_object('MyObject', <<-OBJECT) attributes => { a => { type => Integer }, b => { type => Integer, value => 4 } } OBJECT expect(t.init_hash_type).to eql(factory.struct({ 'a' => factory.integer, factory.optional('b') => factory.integer })) end it 'produces a struct that includes parameters from parent type' do t1 = parse_object('MyObject', <<-OBJECT) attributes => { a => { type => Integer } } OBJECT t2 = parse_object('MyDerivedObject', <<-OBJECT) parent => MyObject, attributes => { b => { type => Integer } } OBJECT expect(t1.init_hash_type).to eql(factory.struct({ 'a' => factory.integer })) expect(t2.init_hash_type).to eql(factory.struct({ 'a' => factory.integer, 'b' => factory.integer })) end it 'produces a struct that reflects overrides made in derived type' do t1 = parse_object('MyObject', <<-OBJECT) attributes => { a => { type => Integer }, b => { type => Integer } } OBJECT t2 = parse_object('MyDerivedObject', <<-OBJECT) parent => MyObject, attributes => { b => { type => Integer, override => true, value => 5 } } OBJECT expect(t1.init_hash_type).to eql(factory.struct({ 'a' => factory.integer, 'b' => factory.integer })) expect(t2.init_hash_type).to eql(factory.struct({ 'a' => factory.integer, factory.optional('b') => factory.integer })) end end context 'with attributes and parameters of its own type' do it 'resolves an attribute type' do t = parse_object('MyObject', <<-OBJECT) attributes => { a => MyObject } OBJECT expect(t['a'].type).to equal(t) end it 'resolves a parameter type' do t = parse_object('MyObject', <<-OBJECT) functions => { a => Callable[MyObject] } OBJECT expect(t['a'].type).to eql(PCallableType.new(PTupleType.new([t]))) end end context 'when using the initialization hash' do it 'produced hash that contains features using short form (type instead of detailed hash when only type is declared)' do obj = parse_object('MyObject', <<-OBJECT) attributes => { a => { type => Integer } } OBJECT expect(obj.to_s).to eql("Object[{name => 'MyObject', attributes => {'a' => Integer}}]") end it 'produced hash that does not include default for equality_include_type' do obj = parse_object('MyObject', <<-OBJECT) attributes => { a => Integer }, equality_include_type => true OBJECT expect(obj.to_s).to eql("Object[{name => 'MyObject', attributes => {'a' => Integer}}]") end it 'constants are presented in a separate hash if they use a generic type' do obj = parse_object('MyObject', <<-OBJECT) attributes => { a => { type => Integer, value => 23, kind => constant }, }, OBJECT expect(obj.to_s).to eql("Object[{name => 'MyObject', constants => {'a' => 23}}]") end it 'constants are not presented in a separate hash unless they use a generic type' do obj = parse_object('MyObject', <<-OBJECT) attributes => { a => { type => Integer[0, 30], value => 23, kind => constant }, }, OBJECT expect(obj.to_s).to eql("Object[{name => 'MyObject', attributes => {'a' => {type => Integer[0, 30], kind => constant, value => 23}}}]") end it 'can create an equal copy from produced hash' do obj = parse_object('MyObject', <<-OBJECT) attributes => { a => { type => Struct[{x => Integer, y => Integer}], value => {x => 4, y => 9}, kind => constant }, b => Integer }, functions => { x => Callable[MyObject,Integer] }, equality => [b] OBJECT obj2 = PObjectType.new(obj._pcore_init_hash) expect(obj).to eql(obj2) end end context 'when stringifying created instances' do it 'outputs a Puppet constructor using the initializer hash' do code = <<-CODE type Spec::MyObject = Object[{attributes => { a => Integer }}] type Spec::MySecondObject = Object[{parent => Spec::MyObject, attributes => { b => String }}] notice(Spec::MySecondObject(42, 'Meaning of life')) CODE expect(eval_and_collect_notices(code)).to eql(["Spec::MySecondObject({'a' => 42, 'b' => 'Meaning of life'})"]) end end context 'when used from Ruby' do it 'can create an instance without scope using positional arguments' do parse_object('MyObject', <<-OBJECT) attributes => { a => { type => Integer } } OBJECT t = Puppet::Pops::Types::TypeParser.singleton.parse('MyObject', Puppet::Pops::Loaders.find_loader(nil)) instance = t.create(32) expect(instance.a).to eql(32) end it 'can create an instance without scope using initialization hash' do parse_object('MyObject', <<-OBJECT) attributes => { a => { type => Integer } } OBJECT t = Puppet::Pops::Types::TypeParser.singleton.parse('MyObject', Puppet::Pops::Loaders.find_loader(nil)) instance = t.from_hash('a' => 32) expect(instance.a).to eql(32) end end context 'when used in Puppet expressions' do it 'two anonymous empty objects are equal' do code = <<-CODE $x = Object[{}] $y = Object[{}] notice($x == $y) CODE expect(eval_and_collect_notices(code)).to eql(['true']) end it 'two objects where one object inherits another object are different' do code = <<-CODE type MyFirstObject = Object[{}] type MySecondObject = Object[{ parent => MyFirstObject }] notice(MyFirstObject == MySecondObject) CODE expect(eval_and_collect_notices(code)).to eql(['false']) end it 'two anonymous objects that inherits the same parent are equal' do code = <<-CODE type MyFirstObject = Object[{}] $x = Object[{ parent => MyFirstObject }] $y = Object[{ parent => MyFirstObject }] notice($x == $y) CODE expect(eval_and_collect_notices(code)).to eql(['true']) end it 'declared Object type is assignable to default Object type' do code = <<-CODE type MyObject = Object[{ attributes => { a => Integer }}] notice(MyObject < Object) notice(MyObject <= Object) CODE expect(eval_and_collect_notices(code)).to eql(['true', 'true']) end it 'default Object type not is assignable to declared Object type' do code = <<-CODE type MyObject = Object[{ attributes => { a => Integer }}] notice(Object < MyObject) notice(Object <= MyObject) CODE expect(eval_and_collect_notices(code)).to eql(['false', 'false']) end it 'default Object type is assignable to itself' do code = <<-CODE notice(Object < Object) notice(Object <= Object) notice(Object > Object) notice(Object >= Object) CODE expect(eval_and_collect_notices(code)).to eql(['false', 'true', 'false', 'true']) end it 'an object type is an instance of an object type type' do code = <<-CODE type MyObject = Object[{ attributes => { a => Integer }}] notice(MyObject =~ Type[MyObject]) CODE expect(eval_and_collect_notices(code)).to eql(['true']) end it 'an object that inherits another object is an instance of the type of its parent' do code = <<-CODE type MyFirstObject = Object[{}] type MySecondObject = Object[{ parent => MyFirstObject }] notice(MySecondObject =~ Type[MyFirstObject]) CODE expect(eval_and_collect_notices(code)).to eql(['true']) end it 'a named object is not added to the loader unless a type = is made' do code = <<-CODE $x = Object[{ name => 'MyFirstObject' }] notice($x == MyFirstObject) CODE expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /Resource type not found: MyFirstObject/) end it 'a type alias on a named object overrides the name' do code = <<-CODE type MyObject = Object[{ name => 'MyFirstObject', attributes => { a => { type => Integer, final => true }}}] type MySecondObject = Object[{ parent => MyObject, attributes => { a => { type => Integer[10], override => true }}}] notice(MySecondObject =~ Type) CODE expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /attribute MySecondObject\[a\] attempts to override final attribute MyObject\[a\]/) end it 'a type cannot be created using an unresolved parent' do code = <<-CODE notice(Object[{ name => 'MyObject', parent => Type('NoneSuch'), attributes => { a => String}}].new('hello')) CODE expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /reference to unresolved type 'NoneSuch'/) end context 'type alias using bracket-less (implicit Object) form' do let(:logs) { [] } let(:notices) { logs.select { |log| log.level == :notice }.map { |log| log.message } } let(:warnings) { logs.select { |log| log.level == :warning }.map { |log| log.message } } let(:node) { Puppet::Node.new('example.com') } let(:compiler) { Puppet::Parser::Compiler.new(node) } def compile(code) Puppet[:code] = code Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) { compiler.compile } end it 'Object is implicit' do compile(<<-CODE) type MyObject = { name => 'MyFirstObject', attributes => { a => Integer}} notice(MyObject =~ Type) notice(MyObject(3)) CODE expect(warnings).to be_empty expect(notices).to eql(['true', "MyObject({'a' => 3})"]) end it 'Object can be specified' do compile(<<-CODE) type MyObject = Object { name => 'MyFirstObject', attributes => { a =>Integer }} notice(MyObject =~ Type) notice(MyObject(3)) CODE expect(warnings).to be_empty expect(notices).to eql(['true', "MyObject({'a' => 3})"]) end it 'parent can be specified before the hash' do compile(<<-CODE) type MyObject = { name => 'MyFirstObject', attributes => { a => String }} type MySecondObject = MyObject { attributes => { b => String }} notice(MySecondObject =~ Type) notice(MySecondObject < MyObject) notice(MyObject('hi')) notice(MySecondObject('hello', 'world')) CODE expect(warnings).to be_empty expect(notices).to eql( ['true', 'true', "MyObject({'a' => 'hi'})", "MySecondObject({'a' => 'hello', 'b' => 'world'})"]) end it 'parent can be specified in the hash' do Puppet[:strict] = 'warning' compile(<<-CODE) type MyObject = { name => 'MyFirstObject', attributes => { a => String }} type MySecondObject = { parent => MyObject, attributes => { b => String }} notice(MySecondObject =~ Type) CODE expect(warnings).to be_empty expect(notices).to eql(['true']) end it 'Object before the hash and parent inside the hash can be combined' do Puppet[:strict] = 'warning' compile(<<-CODE) type MyObject = { name => 'MyFirstObject', attributes => { a => String }} type MySecondObject = Object { parent => MyObject, attributes => { b => String }} notice(MySecondObject =~ Type) CODE expect(warnings).to be_empty expect(notices).to eql(['true']) end it 'if strict == warning, a warning is issued when the same is parent specified both before and inside the hash' do Puppet[:strict] = 'warning' compile(<<-CODE) type MyObject = { name => 'MyFirstObject', attributes => { a => String }} type MySecondObject = MyObject { parent => MyObject, attributes => { b => String }} notice(MySecondObject =~ Type) CODE expect(notices).to eql(['true']) expect(warnings).to eql(["The key 'parent' is declared more than once"]) end it 'if strict == warning, a warning is issued when different parents are specified before and inside the hash. The former overrides the latter' do Puppet[:strict] = 'warning' compile(<<-CODE) type MyObject = { name => 'MyFirstObject', attributes => { a => String }} type MySecondObject = MyObject { parent => MyObject, attributes => { b => String }} notice(MySecondObject =~ Type) CODE expect(notices).to eql(['true']) expect(warnings).to eql(["The key 'parent' is declared more than once"]) end it 'if strict == error, an error is raised when the same parent is specified both before and inside the hash' do Puppet[:strict] = 'error' expect { compile(<<-CODE) }.to raise_error(/The key 'parent' is declared more than once/) type MyObject = { name => 'MyFirstObject', attributes => { a => String }} type MySecondObject = MyObject { parent => MyObject, attributes => { b => String }} notice(MySecondObject =~ Type) CODE end it 'if strict == error, an error is raised when different parents are specified before and inside the hash' do Puppet[:strict] = 'error' expect { compile(<<-CODE) }.to raise_error(/The key 'parent' is declared more than once/) type MyObject = { name => 'MyFirstObject', attributes => { a => String }} type MySecondObject = MyObject { parent => MyOtherType, attributes => { b => String }} notice(MySecondObject =~ Type) CODE end end it 'can inherit from an aliased type' do code = <<-CODE type MyObject = Object[{ name => 'MyFirstObject', attributes => { a => Integer }}] type MyObjectAlias = MyObject type MySecondObject = Object[{ parent => MyObjectAlias, attributes => { b => String }}] notice(MySecondObject < MyObjectAlias) notice(MySecondObject < MyObject) CODE expect(eval_and_collect_notices(code)).to eql(['true', 'true']) end it 'detects equality duplication when inherited from an aliased type' do code = <<-CODE type MyObject = Object[{ name => 'MyFirstObject', attributes => { a => Integer }}] type MyObjectAlias = MyObject type MySecondObject = Object[{ parent => MyObjectAlias, attributes => { b => String }, equality => a}] notice(MySecondObject < MyObject) CODE expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /MySecondObject equality is referencing attribute MyObject\[a\] which is included in equality of MyObject/) end it 'raises an error when object when circular inheritance is detected' do code = <<-CODE type MyFirstObject = Object[{ parent => MySecondObject }] type MySecondObject = Object[{ parent => MyFirstObject }] notice(MySecondObject =~ Type[MyFirstObject]) CODE expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /inherits from itself/) end it 'notices the expanded string form expected content' do code = <<-CODE type MyFirstObject = Object[{ attributes => { first_a => Integer, first_b => { type => String, kind => constant, value => 'the first constant' }, first_c => { type => String, final => true, kind => derived }, first_d => { type => String, kind => given_or_derived }, first_e => { type => String } }, functions => { first_x => Callable[Integer], first_y => Callable[String] }, equality => first_a }] type MySecondObject = Object[{ parent => MyFirstObject, attributes => { second_a => Integer, second_b => { type => String, kind => constant, value => 'the second constant' }, first_e => { type => Enum[foo,fee,fum], final => true, override => true, value => 'fee' } }, functions => { second_x => Callable[Integer], second_y => Callable[String] }, equality => second_a }] notice(MyFirstObject) notice(MySecondObject) CODE expect(eval_and_collect_notices(code)).to eql([ "Object[{"+ "name => 'MyFirstObject', "+ "attributes => {"+ "'first_a' => Integer, "+ "'first_c' => {type => String, final => true, kind => derived}, "+ "'first_d' => {type => String, kind => given_or_derived}, "+ "'first_e' => String"+ "}, "+ "constants => {"+ "'first_b' => 'the first constant'"+ "}, "+ "functions => {"+ "'first_x' => Callable[Integer], "+ "'first_y' => Callable[String]"+ "}, "+ "equality => ['first_a']"+ "}]", "Object[{"+ "name => 'MySecondObject', "+ "parent => MyFirstObject, "+ "attributes => {"+ "'second_a' => Integer, "+ "'first_e' => {type => Enum['fee', 'foo', 'fum'], final => true, override => true, value => 'fee'}"+ "}, "+ "constants => {"+ "'second_b' => 'the second constant'"+ "}, "+ "functions => {"+ "'second_x' => Callable[Integer], "+ "'second_y' => Callable[String]"+ "}, "+ "equality => ['second_a']"+ "}]" ]) end context 'object with type parameters' do it 'can be declared' do expect(eval_and_collect_notices(<<-PUPPET, node)).to eql(['ok']) type MyType = Object[ type_parameters => { p1 => String }] notice('ok') PUPPET end it 'can be referenced' do expect(eval_and_collect_notices(<<-PUPPET, node)).to eql(["MyType['hello']"]) type MyType = Object[ type_parameters => { p1 => String }] notice(MyType['hello']) PUPPET end it 'leading unset parameters are represented as default in string representation' do expect(eval_and_collect_notices(<<-PUPPET, node)).to eql(["MyType[default, 'world']"]) type MyType = Object[ type_parameters => { p1 => String, p2 => String, }] notice(MyType[default, 'world']) PUPPET end it 'trailing unset parameters are skipped in string representation' do expect(eval_and_collect_notices(<<-PUPPET, node)).to eql(["MyType['my']"]) type MyType = Object[ type_parameters => { p1 => String, p2 => String, }] notice(MyType['my']) PUPPET end it 'a type with more than 2 type parameters uses named arguments in string representation' do expect(eval_and_collect_notices(<<-PUPPET, node)).to eql(["MyType[{'p1' => 'my'}]"]) type MyType = Object[ type_parameters => { p1 => String, p2 => String, p3 => String, }] notice(MyType['my']) PUPPET end it 'can be used without parameters' do expect(eval_and_collect_notices(<<-PUPPET, node)).to eql(["Object[{name => 'MyType', type_parameters => {'p1' => String}}]"]) type MyType = Object[ type_parameters => { p1 => String }] notice(MyType) PUPPET end it 'involves type parameter values when testing instance of' do expect(eval_and_collect_notices(<<-PUPPET, node)).to eql(['true', 'false', 'true']) type MyType = Object[ type_parameters => { p1 => String }, attributes => { p1 => String }] $x = MyType('world') notice($x =~ MyType) notice($x =~ MyType['hello']) notice($x =~ MyType['world']) PUPPET end it 'involves type parameter values when testing assignability' do expect(eval_and_collect_notices(<<-PUPPET, node)).to eql(['true', 'false', 'true', 'true', 'false', 'true']) type MyType = Object[ type_parameters => { p1 => String }, attributes => { p1 => String }] $x = MyType['world'] notice($x <= MyType) notice($x <= MyType['hello']) notice($x <= MyType['world']) notice(MyType >= $x) notice(MyType['hello'] >= $x) notice(MyType['world'] >= $x) PUPPET end it 'parameters can be types' do expect(eval_and_collect_notices(<<-PUPPET, node)).to eql(['true', 'true', 'true', 'true', 'false']) type MyType = Object[ type_parameters => { p1 => Variant[String,Regexp,Type[Enum],Type[Pattern],Type[NotUndef]], p2 => Variant[String,Regexp,Type[Enum],Type[Pattern],Type[NotUndef]], }, attributes => { p1 => String, p2 => String }] $x = MyType('good bye', 'cruel world') notice($x =~ MyType) notice($x =~ MyType[Enum['hello', 'good bye']]) notice($x =~ MyType[Enum['hello', 'good bye'], Pattern[/world/, /universe/]]) notice($x =~ MyType[NotUndef, NotUndef]) notice($x =~ MyType[Enum['hello', 'yo']]) PUPPET end it 'parameters can be provided using named arguments' do expect(eval_and_collect_notices(<<-PUPPET, node)).to eql(['true', 'false', 'true']) type MyType = Object[ type_parameters => { p1 => String, p2 => String }, attributes => { p1 => String, p2 => String }] $x = MyType('good bye', 'cruel world') notice($x =~ MyType) notice($x =~ MyType[p1 => 'hello', p2 => 'cruel world']) notice($x =~ MyType[p1 => 'good bye', p2 => 'cruel world']) PUPPET end it 'at least one parameter must be given' do expect{eval_and_collect_notices(<<-PUPPET, node)}.to raise_error(/The MyType-Type cannot be parameterized using an empty parameter list/) type MyType = Object[ type_parameters => { p1 => Variant[String,Regexp,Type[Enum],Type[Pattern]], }, attributes => { p1 => String, }] notice(MyType[default]) PUPPET end it 'undef is a valid value for a type parameter' do expect(eval_and_collect_notices(<<-PUPPET, node)).to eql(['true', 'false']) type MyType = Object[ type_parameters => { p1 => Optional[String], }, attributes => { p1 => Optional[String], }] notice(MyType() =~ MyType[undef]) notice(MyType('hello') =~ MyType[undef]) PUPPET end it 'Type parameters does not mean that type must be parameterized' do expect(eval_and_collect_notices(<<-PUPPET, node)).to eql(['true']) type MyType = Object[ type_parameters => { p1 => Variant[Undef,String,Regexp,Type[Enum],Type[Pattern]], p2 => Variant[Undef,String,Regexp,Type[Enum],Type[Pattern]], }, attributes => { p1 => String, p2 => String }] notice(MyType('hello', 'world') =~ MyType) PUPPET end it 'A parameterized type is assignable to another parameterized type if base type and parameters are assignable' do expect(eval_and_collect_notices(<<-PUPPET, node)).to eql(['true']) type MyType = Object[ type_parameters => { p1 => Variant[Undef,String,Regexp,Type[Enum],Type[Pattern]], p2 => Variant[Undef,String,Regexp,Type[Enum],Type[Pattern]], }, attributes => { p1 => String, p2 => String }] notice(MyType[Pattern[/a/,/b/]] > MyType[Enum['a','b']]) PUPPET end it 'Instance is inferred to parameterized type' do expect(eval_and_collect_notices(<<-PUPPET, node)).to eql(['true', 'true', 'true', 'true', 'true']) type MyType = Object[ type_parameters => { p1 => Variant[Undef,String,Regexp,Type[Enum],Type[Pattern]], p2 => Variant[Undef,String,Regexp,Type[Enum],Type[Pattern]], }, attributes => { p1 => String, p2 => String }] $x = MyType('hello', 'world') notice(type($x, generalized) == MyType) notice(type($x) < MyType) notice(type($x) < MyType['hello']) notice(type($x) < MyType[/hello/, /world/]) notice(type($x) == MyType['hello', 'world']) PUPPET end it 'Attributes of instance of parameterized type can be accessed using function calls' do expect(eval_and_collect_notices(<<-PUPPET, node)).to eql(['hello', 'world']) type MyType = Object[ type_parameters => { p1 => Variant[Undef,String,Regexp,Type[Enum],Type[Pattern]], p2 => Variant[Undef,String,Regexp,Type[Enum],Type[Pattern]], }, attributes => { p1 => String, p2 => String }] $x = MyType('hello', 'world') notice($x.p1) notice($x.p2) PUPPET end end end context "when used with function 'new'" do context 'with ordered parameters' do it 'creates an instance with initialized attributes' do code = <<-CODE type MyFirstObject = Object[{ attributes => { a => Integer, b => String } }] $obj = MyFirstObject.new(3, 'hi') notice($obj.a) notice($obj.b) CODE expect(eval_and_collect_notices(code)).to eql(['3', 'hi']) end it 'creates an instance with default attribute values' do code = <<-CODE type MyFirstObject = Object[{ attributes => { a => { type => String, value => 'the default' } } }] $obj = MyFirstObject.new notice($obj.a) CODE expect(eval_and_collect_notices(code)).to eql(['the default']) end it 'creates an instance with constant attributes' do code = <<-CODE type MyFirstObject = Object[{ attributes => { a => { type => String, kind => constant, value => 'the constant' } } }] $obj = MyFirstObject.new notice($obj.a) CODE expect(eval_and_collect_notices(code)).to eql(['the constant']) end it 'creates an instance with overridden attribute defaults' do code = <<-CODE type MyFirstObject = Object[{ attributes => { a => { type => String, value => 'the default' } } }] $obj = MyFirstObject.new('not default') notice($obj.a) CODE expect(eval_and_collect_notices(code)).to eql(['not default']) end it 'fails on an attempt to provide a constant attribute value' do code = <<-CODE type MyFirstObject = Object[{ attributes => { a => { type => String, kind => constant, value => 'the constant' } } }] $obj = MyFirstObject.new('not constant') notice($obj.a) CODE expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /expects no arguments/) end it 'fails when a required key is missing' do code = <<-CODE type MyFirstObject = Object[{ attributes => { a => String } }] $obj = MyFirstObject.new notice($obj.a) CODE expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /expects 1 argument, got none/) end it 'creates a derived instance with initialized attributes' do code = <<-CODE type MyFirstObject = Object[{ attributes => { a => Integer, b => { type => String, kind => constant, value => 'the first constant' }, c => String } }] type MySecondObject = Object[{ parent => MyFirstObject, attributes => { d => { type => Integer, value => 34 } } }] $obj = MySecondObject.new(3, 'hi') notice($obj.a) notice($obj.b) notice($obj.c) notice($obj.d) CODE expect(eval_and_collect_notices(code)).to eql(['3', 'the first constant', 'hi', '34']) end end context 'with named parameters' do it 'creates an instance with initialized attributes' do code = <<-CODE type MyFirstObject = Object[{ attributes => { a => Integer, b => String } }] $obj = MyFirstObject.new({b => 'hi', a => 3}) notice($obj.a) notice($obj.b) CODE expect(eval_and_collect_notices(code)).to eql(['3', 'hi']) end it 'creates an instance with default attribute values' do code = <<-CODE type MyFirstObject = Object[{ attributes => { a => { type => String, value => 'the default' } } }] $obj = MyFirstObject.new({}) notice($obj.a) CODE expect(eval_and_collect_notices(code)).to eql(['the default']) end it 'creates an instance with constant attributes' do code = <<-CODE type MyFirstObject = Object[{ attributes => { a => { type => String, kind => constant, value => 'the constant' } } }] $obj = MyFirstObject.new({}) notice($obj.a) CODE expect(eval_and_collect_notices(code)).to eql(['the constant']) end it 'creates an instance with overridden attribute defaults' do code = <<-CODE type MyFirstObject = Object[{ attributes => { a => { type => String, value => 'the default' } } }] $obj = MyFirstObject.new({a => 'not default'}) notice($obj.a) CODE expect(eval_and_collect_notices(code)).to eql(['not default']) end it 'fails on an attempt to provide a constant attribute value' do code = <<-CODE type MyFirstObject = Object[{ attributes => { a => { type => String, kind => constant, value => 'the constant' } } }] $obj = MyFirstObject.new({a => 'not constant'}) notice($obj.a) CODE expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /unrecognized key 'a'/) end it 'fails when a required key is missing' do code = <<-CODE type MyFirstObject = Object[{ attributes => { a => String } }] $obj = MyFirstObject.new({}) notice($obj.a) CODE expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /expects size to be 1, got 0/) end it 'creates a derived instance with initialized attributes' do code = <<-CODE type MyFirstObject = Object[{ attributes => { a => Integer, b => { type => String, kind => constant, value => 'the first constant' }, c => String } }] type MySecondObject = Object[{ parent => MyFirstObject, attributes => { d => { type => Integer, value => 34 } } }] $obj = MySecondObject.new({c => 'hi', a => 3}) notice($obj.a) notice($obj.b) notice($obj.c) notice($obj.d) CODE expect(eval_and_collect_notices(code)).to eql(['3', 'the first constant', 'hi', '34']) end end end context 'is assigned to all PAnyType classes such that' do include_context 'types_setup' def find_parent(tc, parent_name) p = tc._pcore_type while p.is_a?(PObjectType) && p.name != parent_name p = p.parent end expect(p).to be_a(PObjectType), "did not find #{parent_name} in parent chain of #{tc.name}" p end it 'the class has a _pcore_type method' do all_types.each do |tc| expect(tc).to respond_to(:_pcore_type).with(0).arguments end end it 'the _pcore_type method returns a PObjectType instance' do all_types.each do |tc| expect(tc._pcore_type).to be_a(PObjectType) end end it 'the instance returned by _pcore_type is a descendant from Pcore::AnyType' do all_types.each { |tc| expect(find_parent(tc, 'Pcore::AnyType').name).to eq('Pcore::AnyType') } end it 'PScalarType classes _pcore_type returns a descendant from Pcore::ScalarType' do scalar_types.each { |tc| expect(find_parent(tc, 'Pcore::ScalarType').name).to eq('Pcore::ScalarType') } end it 'PNumericType classes _pcore_type returns a descendant from Pcore::NumberType' do numeric_types.each { |tc| expect(find_parent(tc, 'Pcore::NumericType').name).to eq('Pcore::NumericType') } end it 'PCollectionType classes _pcore_type returns a descendant from Pcore::CollectionType' do coll_descendants = collection_types - [PTupleType, PStructType] coll_descendants.each { |tc| expect(find_parent(tc, 'Pcore::CollectionType').name).to eq('Pcore::CollectionType') } end end context 'when dealing with annotations' do let(:annotation) { <<-PUPPET } type MyAdapter = Object[{ parent => Annotation, attributes => { id => Integer, value => String[1] } }] PUPPET it 'the Annotation type can be used as parent' do code = <<-PUPPET #{annotation} notice(MyAdapter < Annotation) PUPPET expect(eval_and_collect_notices(code)).to eql(['true']) end it 'an annotation can be added to an Object type' do code = <<-PUPPET #{annotation} type MyObject = Object[{ annotations => { MyAdapter => { 'id' => 2, 'value' => 'annotation value' } } }] notice(MyObject) PUPPET expect(eval_and_collect_notices(code)).to eql([ "Object[{annotations => {MyAdapter => {'id' => 2, 'value' => 'annotation value'}}, name => 'MyObject'}]"]) end it 'other types can not be used as annotations' do code = <<-PUPPET type NotAnAnnotation = Object[{}] type MyObject = Object[{ annotations => { NotAnAnnotation => {} } }] notice(MyObject) PUPPET expect{eval_and_collect_notices(code)}.to raise_error(/entry 'annotations' expects a value of type Undef or Hash/) end end end end end puppet-5.5.10/spec/unit/pops/types/string_converter_spec.rb0000644005276200011600000011676313417161722024023 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' describe 'The string converter' do let(:converter) { Puppet::Pops::Types::StringConverter.singleton } let(:factory) { Puppet::Pops::Types::TypeFactory } let(:format) { Puppet::Pops::Types::StringConverter::Format } let(:binary) { Puppet::Pops::Types::PBinaryType::Binary } describe 'helper Format' do it 'parses a single character like "%d" as a format' do fmt = format.new("%d") expect(fmt.format()).to be(:d) expect(fmt.alt()).to be(false) expect(fmt.left()).to be(false) expect(fmt.width()).to be_nil expect(fmt.prec()).to be_nil expect(fmt.plus()).to eq(:ignore) end it 'alternative form can be given with "%#d"' do fmt = format.new("%#d") expect(fmt.format()).to be(:d) expect(fmt.alt()).to be(true) end it 'left adjust can be given with "%-d"' do fmt = format.new("%-d") expect(fmt.format()).to be(:d) expect(fmt.left()).to be(true) end it 'plus sign can be used to indicate how positive numbers are displayed' do fmt = format.new("%+d") expect(fmt.format()).to be(:d) expect(fmt.plus()).to eq(:plus) end it 'a space can be used to output " " instead of "+" for positive numbers' do fmt = format.new("% d") expect(fmt.format()).to be(:d) expect(fmt.plus()).to eq(:space) end it 'padding with zero can be specified with a "0" flag' do fmt = format.new("%0d") expect(fmt.format()).to be(:d) expect(fmt.zero_pad()).to be(true) end it 'width can be specified as an integer >= 1' do fmt = format.new("%1d") expect(fmt.format()).to be(:d) expect(fmt.width()).to be(1) fmt = format.new("%10d") expect(fmt.width()).to be(10) end it 'precision can be specified as an integer >= 0' do fmt = format.new("%.0d") expect(fmt.format()).to be(:d) expect(fmt.prec()).to be(0) fmt = format.new("%.10d") expect(fmt.prec()).to be(10) end it 'width and precision can both be specified' do fmt = format.new("%2.3d") expect(fmt.format()).to be(:d) expect(fmt.width()).to be(2) expect(fmt.prec()).to be(3) end [ '[', '{', '(', '<', '|', ].each do | delim | it "a container delimiter pair can be set with '#{delim}'" do fmt = format.new("%#{delim}d") expect(fmt.format()).to be(:d) expect(fmt.delimiters()).to eql(delim) end end it "Is an error to specify different delimiters at the same time" do expect do format.new("%[{d") end.to raise_error(/Only one of the delimiters/) end it "Is an error to have trailing characters after the format" do expect do format.new("%dv") end.to raise_error(/The format '%dv' is not a valid format/) end it "Is an error to specify the same flag more than once" do expect do format.new("%[[d") end.to raise_error(/The same flag can only be used once/) end end context 'when converting to string' do { 42 => "42", 3.14 => "3.140000", "hello world" => "hello world", "hello\tworld" => "hello\tworld", }.each do |from, to| it "the string value of #{from} is '#{to}'" do expect(converter.convert(from, :default)).to eq(to) end end it 'float point value decimal digits can be specified' do string_formats = { Puppet::Pops::Types::PFloatType::DEFAULT => '%.2f'} expect(converter.convert(3.14, string_formats)).to eq('3.14') end it 'when specifying format for one type, other formats are not affected' do string_formats = { Puppet::Pops::Types::PFloatType::DEFAULT => '%.2f'} expect(converter.convert('3.1415', string_formats)).to eq('3.1415') end context 'The %p format for string produces' do let!(:string_formats) { { Puppet::Pops::Types::PStringType::DEFAULT => '%p'} } it 'double quoted result for string that contains control characters' do expect(converter.convert("hello\tworld.\r\nSun is brigth today.", string_formats)).to eq('"hello\\tworld.\\r\\nSun is brigth today."') end it 'singe quoted result for string that is plain ascii without \\, $ or control characters' do expect(converter.convert('hello world', string_formats)).to eq("'hello world'") end it 'quoted 5-byte unicode chars' do expect(converter.convert("smile \u{1f603}.", string_formats)).to eq("'smile \u{1F603}.'") end it 'quoted 2-byte unicode chars' do expect(converter.convert("esc \u{1b}.", string_formats)).to eq('"esc \\u{1B}."') end it 'escape for $ in double quoted string' do # Use \n in string to force double quotes expect(converter.convert("escape the $ sign\n", string_formats)).to eq('"escape the \$ sign\n"') end it 'no escape for $ in single quoted string' do expect(converter.convert('don\'t escape the $ sign', string_formats)).to eq("'don\\'t escape the $ sign'") end it 'escape for double quote but not for single quote in double quoted string' do # Use \n in string to force double quotes expect(converter.convert("the ' single and \" double quote\n", string_formats)).to eq('"the \' single and \\" double quote\n"') end it 'escape for single quote but not for double quote in single quoted string' do expect(converter.convert('the \' single and " double quote', string_formats)).to eq("'the \\' single and \" double quote'") end it 'no escape for #' do expect(converter.convert('do not escape #{x}', string_formats)).to eq('\'do not escape #{x}\'') end it 'escape for last \\' do expect(converter.convert('escape the last \\', string_formats)).to eq("'escape the last \\'") end end { '%s' => 'hello::world', '%p' => "'hello::world'", '%c' => 'Hello::world', '%#c' => "'Hello::world'", '%u' => 'HELLO::WORLD', '%#u' => "'HELLO::WORLD'", }.each do |fmt, result | it "the format #{fmt} produces #{result}" do string_formats = { Puppet::Pops::Types::PStringType::DEFAULT => fmt} expect(converter.convert('hello::world', string_formats)).to eq(result) end end { '%c' => 'Hello::world', '%#c' => "'Hello::world'", '%d' => 'hello::world', '%#d' => "'hello::world'", }.each do |fmt, result | it "the format #{fmt} produces #{result}" do string_formats = { Puppet::Pops::Types::PStringType::DEFAULT => fmt} expect(converter.convert('HELLO::WORLD', string_formats)).to eq(result) end end { [nil, '%.1p'] => 'u', [nil, '%#.2p'] => '"u', [:default, '%.1p'] => 'd', [true, '%.2s'] => 'tr', [true, '%.2y'] => 'ye', }.each do |args, result | it "the format #{args[1]} produces #{result} for value #{args[0]}" do expect(converter.convert(*args)).to eq(result) end end { ' a b ' => 'a b', 'a b ' => 'a b', ' a b' => 'a b', }.each do |input, result | it "the input #{input} is trimmed to #{result} by using format '%t'" do string_formats = { Puppet::Pops::Types::PStringType::DEFAULT => '%t'} expect(converter.convert(input, string_formats)).to eq(result) end end { ' a b ' => "'a b'", 'a b ' => "'a b'", ' a b' => "'a b'", }.each do |input, result | it "the input #{input} is trimmed to #{result} by using format '%#t'" do string_formats = { Puppet::Pops::Types::PStringType::DEFAULT => '%#t'} expect(converter.convert(input, string_formats)).to eq(result) end end it 'errors when format is not recognized' do expect do string_formats = { Puppet::Pops::Types::PStringType::DEFAULT => "%k"} converter.convert('wat', string_formats) end.to raise_error(/Illegal format 'k' specified for value of String type - expected one of the characters 'cCudspt'/) end it 'Width pads a string left with spaces to given width' do string_formats = { Puppet::Pops::Types::PStringType::DEFAULT => '%6s'} expect(converter.convert("abcd", string_formats)).to eq(' abcd') end it 'Width pads a string right with spaces to given width and - flag' do string_formats = { Puppet::Pops::Types::PStringType::DEFAULT => '%-6s'} expect(converter.convert("abcd", string_formats)).to eq('abcd ') end it 'Precision truncates the string if precision < length' do string_formats = { Puppet::Pops::Types::PStringType::DEFAULT => '%-6.2s'} expect(converter.convert("abcd", string_formats)).to eq('ab ') end { '%4.2s' => ' he', '%4.2p' => " 'h", '%4.2c' => ' He', '%#4.2c' => " 'H", '%4.2u' => ' HE', '%#4.2u' => " 'H", '%4.2d' => ' he', '%#4.2d' => " 'h" }.each do |fmt, result | it "width and precision can be combined with #{fmt}" do string_formats = { Puppet::Pops::Types::PStringType::DEFAULT => fmt} expect(converter.convert('hello::world', string_formats)).to eq(result) end end end context 'when converting integer' do it 'the default string representation is decimal' do expect(converter.convert(42, :default)).to eq('42') end { '%s' => '18', '%4.1s' => ' 1', '%p' => '18', '%4.2p' => ' 18', '%4.1p' => ' 1', '%#s' => '"18"', '%#6.4s' => ' "18"', '%#p' => '18', '%#6.4p' => ' 18', '%d' => '18', '%4.1d' => ' 18', '%4.3d' => ' 018', '%x' => '12', '%4.3x' => ' 012', '%#x' => '0x12', '%#6.4x' => '0x0012', '%X' => '12', '%4.3X' => ' 012', '%#X' => '0X12', '%#6.4X' => '0X0012', '%o' => '22', '%4.2o' => ' 22', '%#o' => '022', '%#6.4o' => ' 0022', '%b' => '10010', '%7.6b' => ' 010010', '%#b' => '0b10010', '%#9.6b' => ' 0b010010', '%#B' => '0B10010', '%#9.6B' => ' 0B010010', # Integer to float then a float format - fully tested for float '%e' => '1.800000e+01', '%E' => '1.800000E+01', '%f' => '18.000000', '%g' => '18', '%a' => '0x1.2p+4', '%A' => '0X1.2P+4', '%.1f' => '18.0', }.each do |fmt, result | it "the format #{fmt} produces #{result}" do string_formats = { Puppet::Pops::Types::PIntegerType::DEFAULT => fmt} expect(converter.convert(18, string_formats)).to eq(result) end end it 'produces a unicode char string by using format %c' do string_formats = { Puppet::Pops::Types::PIntegerType::DEFAULT => '%c'} expect(converter.convert(0x1F603, string_formats)).to eq("\u{1F603}") end it 'produces a quoted unicode char string by using format %#c' do string_formats = { Puppet::Pops::Types::PIntegerType::DEFAULT => '%#c'} expect(converter.convert(0x1F603, string_formats)).to eq("\"\u{1F603}\"") end it 'errors when format is not recognized' do expect do string_formats = { Puppet::Pops::Types::PIntegerType::DEFAULT => "%k"} converter.convert(18, string_formats) end.to raise_error(/Illegal format 'k' specified for value of Integer type - expected one of the characters 'dxXobBeEfgGaAspc'/) end end context 'when converting float' do it 'the default string representation is decimal' do expect(converter.convert(42.0, :default)).to eq('42.000000') end { '%s' => '18.0', '%#s' => '"18.0"', '%5s' => ' 18.0', '%#8s' => ' "18.0"', '%05.1s' => ' 1', '%p' => '18.0', '%7.2p' => ' 18', '%e' => '1.800000e+01', '%+e' => '+1.800000e+01', '% e' => ' 1.800000e+01', '%.2e' => '1.80e+01', '%10.2e' => ' 1.80e+01', '%-10.2e' => '1.80e+01 ', '%010.2e' => '001.80e+01', '%E' => '1.800000E+01', '%+E' => '+1.800000E+01', '% E' => ' 1.800000E+01', '%.2E' => '1.80E+01', '%10.2E' => ' 1.80E+01', '%-10.2E' => '1.80E+01 ', '%010.2E' => '001.80E+01', '%f' => '18.000000', '%+f' => '+18.000000', '% f' => ' 18.000000', '%.2f' => '18.00', '%10.2f' => ' 18.00', '%-10.2f' => '18.00 ', '%010.2f' => '0000018.00', '%g' => '18', '%5g' => ' 18', '%05g' => '00018', '%-5g' => '18 ', '%5.4g' => ' 18', # precision has no effect '%a' => '0x1.2p+4', '%.4a' => '0x1.2000p+4', '%10a' => ' 0x1.2p+4', '%010a' => '0x001.2p+4', '%-10a' => '0x1.2p+4 ', '%A' => '0X1.2P+4', '%.4A' => '0X1.2000P+4', '%10A' => ' 0X1.2P+4', '%-10A' => '0X1.2P+4 ', '%010A' => '0X001.2P+4', # integer formats fully tested for integer '%d' => '18', '%x' => '12', '%X' => '12', '%o' => '22', '%b' => '10010', '%#B' => '0B10010', }.each do |fmt, result | it "the format #{fmt} produces #{result}" do string_formats = { Puppet::Pops::Types::PFloatType::DEFAULT => fmt} expect(converter.convert(18.0, string_formats)).to eq(result) end end it 'errors when format is not recognized' do expect do string_formats = { Puppet::Pops::Types::PFloatType::DEFAULT => "%k"} converter.convert(18.0, string_formats) end.to raise_error(/Illegal format 'k' specified for value of Float type - expected one of the characters 'dxXobBeEfgGaAsp'/) end end context 'when converting undef' do it 'the default string representation is empty string' do expect(converter.convert(nil, :default)).to eq('') end { "%u" => "undef", "%#u" => "undefined", "%s" => "", "%#s" => '""', "%p" => 'undef', "%#p" => '"undef"', "%n" => 'nil', "%#n" => 'null', "%v" => 'n/a', "%V" => 'N/A', "%d" => 'NaN', "%x" => 'NaN', "%o" => 'NaN', "%b" => 'NaN', "%B" => 'NaN', "%e" => 'NaN', "%E" => 'NaN', "%f" => 'NaN', "%g" => 'NaN', "%G" => 'NaN', "%a" => 'NaN', "%A" => 'NaN', }.each do |fmt, result | it "the format #{fmt} produces #{result}" do string_formats = { Puppet::Pops::Types::PUndefType::DEFAULT => fmt} expect(converter.convert(nil, string_formats)).to eq(result) end end { "%7u" => " undef", "%-7u" => "undef ", "%#10u" => " undefined", "%#-10u" => "undefined ", "%7.2u" => " un", "%4s" => " ", "%#4s" => ' ""', "%7p" => ' undef', "%7.1p" => ' u', "%#8p" => ' "undef"', "%5n" => ' nil', "%.1n" => 'n', "%-5n" => 'nil ', "%#5n" => ' null', "%#-5n" => 'null ', "%5v" => ' n/a', "%5.2v" => ' n/', "%-5v" => 'n/a ', "%5V" => ' N/A', "%5.1V" => ' N', "%-5V" => 'N/A ', "%5d" => ' NaN', "%5.2d" => ' Na', "%-5d" => 'NaN ', "%5x" => ' NaN', "%5.2x" => ' Na', "%-5x" => 'NaN ', "%5o" => ' NaN', "%5.2o" => ' Na', "%-5o" => 'NaN ', "%5b" => ' NaN', "%5.2b" => ' Na', "%-5b" => 'NaN ', "%5B" => ' NaN', "%5.2B" => ' Na', "%-5B" => 'NaN ', "%5e" => ' NaN', "%5.2e" => ' Na', "%-5e" => 'NaN ', "%5E" => ' NaN', "%5.2E" => ' Na', "%-5E" => 'NaN ', "%5f" => ' NaN', "%5.2f" => ' Na', "%-5f" => 'NaN ', "%5g" => ' NaN', "%5.2g" => ' Na', "%-5g" => 'NaN ', "%5G" => ' NaN', "%5.2G" => ' Na', "%-5G" => 'NaN ', "%5a" => ' NaN', "%5.2a" => ' Na', "%-5a" => 'NaN ', "%5A" => ' NaN', "%5.2A" => ' Na', "%-5A" => 'NaN ', }.each do |fmt, result | it "the format #{fmt} produces #{result}" do string_formats = { Puppet::Pops::Types::PUndefType::DEFAULT => fmt} expect(converter.convert(nil, string_formats)).to eq(result) end end it 'errors when format is not recognized' do expect do string_formats = { Puppet::Pops::Types::PUndefType::DEFAULT => "%k"} converter.convert(nil, string_formats) end.to raise_error(/Illegal format 'k' specified for value of Undef type - expected one of the characters 'nudxXobBeEfgGaAvVsp'/) end end context 'when converting default' do it 'the default string representation is unquoted "default"' do expect(converter.convert(:default, :default)).to eq('default') end { "%d" => 'default', "%D" => 'Default', "%#d" => '"default"', "%#D" => '"Default"', "%s" => 'default', "%p" => 'default', }.each do |fmt, result | it "the format #{fmt} produces #{result}" do string_formats = { Puppet::Pops::Types::PDefaultType::DEFAULT => fmt} expect(converter.convert(:default, string_formats)).to eq(result) end end it 'errors when format is not recognized' do expect do string_formats = { Puppet::Pops::Types::PDefaultType::DEFAULT => "%k"} converter.convert(:default, string_formats) end.to raise_error(/Illegal format 'k' specified for value of Default type - expected one of the characters 'dDsp'/) end end context 'when converting boolean true' do it 'the default string representation is unquoted "true"' do expect(converter.convert(true, :default)).to eq('true') end { "%t" => 'true', "%#t" => 't', "%T" => 'True', "%#T" => 'T', "%s" => 'true', "%p" => 'true', "%d" => '1', "%x" => '1', "%#x" => '0x1', "%o" => '1', "%#o" => '01', "%b" => '1', "%#b" => '0b1', "%#B" => '0B1', "%e" => '1.000000e+00', "%f" => '1.000000', "%g" => '1', "%a" => '0x1p+0', "%A" => '0X1P+0', "%.1f" => '1.0', "%y" => 'yes', "%Y" => 'Yes', "%#y" => 'y', "%#Y" => 'Y', }.each do |fmt, result | it "the format #{fmt} produces #{result}" do string_formats = { Puppet::Pops::Types::PBooleanType::DEFAULT => fmt} expect(converter.convert(true, string_formats)).to eq(result) end end it 'errors when format is not recognized' do expect do string_formats = { Puppet::Pops::Types::PBooleanType::DEFAULT => "%k"} converter.convert(true, string_formats) end.to raise_error(/Illegal format 'k' specified for value of Boolean type - expected one of the characters 'tTyYdxXobBeEfgGaAsp'/) end end context 'when converting boolean false' do it 'the default string representation is unquoted "false"' do expect(converter.convert(false, :default)).to eq('false') end { "%t" => 'false', "%#t" => 'f', "%T" => 'False', "%#T" => 'F', "%s" => 'false', "%p" => 'false', "%d" => '0', "%x" => '0', "%#x" => '0', "%o" => '0', "%#o" => '0', "%b" => '0', "%#b" => '0', "%#B" => '0', "%e" => '0.000000e+00', "%E" => '0.000000E+00', "%f" => '0.000000', "%g" => '0', "%a" => '0x0p+0', "%A" => '0X0P+0', "%.1f" => '0.0', "%y" => 'no', "%Y" => 'No', "%#y" => 'n', "%#Y" => 'N', }.each do |fmt, result | it "the format #{fmt} produces #{result}" do string_formats = { Puppet::Pops::Types::PBooleanType::DEFAULT => fmt} expect(converter.convert(false, string_formats)).to eq(result) end end it 'errors when format is not recognized' do expect do string_formats = { Puppet::Pops::Types::PBooleanType::DEFAULT => "%k"} converter.convert(false, string_formats) end.to raise_error(/Illegal format 'k' specified for value of Boolean type - expected one of the characters 'tTyYdxXobBeEfgGaAsp'/) end end context 'when converting array' do it 'the default string representation is using [] delimiters, joins with ',' and uses %p for values' do expect(converter.convert(["hello", "world"], :default)).to eq("['hello', 'world']") end { "%s" => "[1, 'hello']", "%p" => "[1, 'hello']", "%a" => "[1, 'hello']", "% "<1, 'hello'>", "%[a" => "[1, 'hello']", "%(a" => "(1, 'hello')", "%{a" => "{1, 'hello'}", "% a" => "1, 'hello'", {'format' => '%(a', 'separator' => ' ' } => "(1 'hello')", {'format' => '%(a', 'separator' => '' } => "(1'hello')", {'format' => '%|a', 'separator' => ' ' } => "|1 'hello'|", {'format' => '%(a', 'separator' => ' ', 'string_formats' => {Puppet::Pops::Types::PIntegerType::DEFAULT => '%#x'} } => "(0x1 'hello')", }.each do |fmt, result | it "the format #{fmt} produces #{result}" do string_formats = { Puppet::Pops::Types::PArrayType::DEFAULT => fmt} expect(converter.convert([1, "hello"], string_formats)).to eq(result) end end it "multiple rules selects most specific" do short_array_t = factory.array_of(factory.integer, factory.range(1,2)) long_array_t = factory.array_of(factory.integer, factory.range(3,100)) string_formats = { short_array_t => "%(a", long_array_t => "%[a", } expect(converter.convert([1, 2], string_formats)).to eq('(1, 2)') expect(converter.convert([1, 2, 3], string_formats)).to eq('[1, 2, 3]') end it 'indents elements in alternate mode' do string_formats = { Puppet::Pops::Types::PArrayType::DEFAULT => { 'format' => '%#a', 'separator' =>", " } } # formatting matters here result = [ "[1, 2, 9, 9,", " [3, 4],", " [5,", " [6, 7]],", " 8, 9]" ].join("\n") formatted = converter.convert([1, 2, 9, 9, [3, 4], [5, [6, 7]], 8, 9], string_formats) expect(formatted).to eq(result) end it 'applies a short form format' do result = [ "[1,", " [2, 3],", " 4]" ].join("\n") expect(converter.convert([1, [2,3], 4], '%#a')).to eq(result) end it 'treats hashes as nested arrays wrt indentation' do string_formats = { Puppet::Pops::Types::PArrayType::DEFAULT => { 'format' => '%#a', 'separator' =>", " } } # formatting matters here result = [ "[1, 2, 9, 9,", " {3 => 4, 5 => 6},", " [5,", " [6, 7]],", " 8, 9]" ].join("\n") formatted = converter.convert([1, 2, 9, 9, {3 => 4, 5 => 6}, [5, [6, 7]], 8, 9], string_formats) expect(formatted).to eq(result) end it 'indents and breaks when a sequence > given width, in alternate mode' do string_formats = { Puppet::Pops::Types::PArrayType::DEFAULT => { 'format' => '%#3a', 'separator' =>", " } } # formatting matters here result = [ "[ 1,", " 2,", " 90,", # this sequence has length 4 (1,2,90) which is > 3 " [3, 4],", " [5,", " [6, 7]],", " 8,", " 9]", ].join("\n") formatted = converter.convert([1, 2, 90, [3, 4], [5, [6, 7]], 8, 9], string_formats) expect(formatted).to eq(result) end it 'indents and breaks when a sequence (placed last) > given width, in alternate mode' do string_formats = { Puppet::Pops::Types::PArrayType::DEFAULT => { 'format' => '%#3a', 'separator' =>", " } } # formatting matters here result = [ "[ 1,", " 2,", " 9,", # this sequence has length 3 (1,2,9) which does not cause breaking on each " [3, 4],", " [5,", " [6, 7]],", " 8,", " 900]", # this sequence has length 4 (8, 900) which causes breaking on each ].join("\n") formatted = converter.convert([1, 2, 9, [3, 4], [5, [6, 7]], 8, 900], string_formats) expect(formatted).to eq(result) end it 'indents and breaks nested sequences when one is placed first' do string_formats = { Puppet::Pops::Types::PArrayType::DEFAULT => { 'format' => '%#a', 'separator' =>", " } } # formatting matters here result = [ "[", " [", " [1, 2,", " [3, 4]]],", " [5,", " [6, 7]],", " 8, 900]", ].join("\n") formatted = converter.convert([[[1, 2, [3, 4]]], [5, [6, 7]], 8, 900], string_formats) expect(formatted).to eq(result) end it 'errors when format is not recognized' do expect do string_formats = { Puppet::Pops::Types::PArrayType::DEFAULT => "%k"} converter.convert([1,2], string_formats) end.to raise_error(/Illegal format 'k' specified for value of Array type - expected one of the characters 'asp'/) end end context 'when converting hash' do it 'the default string representation is using {} delimiters, joins with '=>' and uses %p for values' do expect(converter.convert({"hello" => "world"}, :default)).to eq("{'hello' => 'world'}") end { "%s" => "{1 => 'world'}", "%p" => "{1 => 'world'}", "%h" => "{1 => 'world'}", "%a" => "[[1, 'world']]", "% "<1 => 'world'>", "%[h" => "[1 => 'world']", "%(h" => "(1 => 'world')", "%{h" => "{1 => 'world'}", "% h" => "1 => 'world'", {'format' => '%(h', 'separator2' => ' ' } => "(1 'world')", {'format' => '%(h', 'separator2' => ' ', 'string_formats' => {Puppet::Pops::Types::PIntegerType::DEFAULT => '%#x'} } => "(0x1 'world')", }.each do |fmt, result | it "the format #{fmt} produces #{result}" do string_formats = { Puppet::Pops::Types::PHashType::DEFAULT => fmt} expect(converter.convert({1 => "world"}, string_formats)).to eq(result) end end { "%s" => "{1 => 'hello', 2 => 'world'}", {'format' => '%(h', 'separator2' => ' ' } => "(1 'hello', 2 'world')", {'format' => '%(h', 'separator' => '', 'separator2' => '' } => "(1'hello'2'world')", {'format' => '%(h', 'separator' => ' >> ', 'separator2' => ' <=> ', 'string_formats' => {Puppet::Pops::Types::PIntegerType::DEFAULT => '%#x'} } => "(0x1 <=> 'hello' >> 0x2 <=> 'world')", }.each do |fmt, result | it "the format #{fmt} produces #{result}" do string_formats = { Puppet::Pops::Types::PHashType::DEFAULT => fmt} expect(converter.convert({1 => "hello", 2 => "world"}, string_formats)).to eq(result) end end it 'indents elements in alternative mode #' do string_formats = { Puppet::Pops::Types::PHashType::DEFAULT => { 'format' => '%#h', } } # formatting matters here result = [ "{", " 1 => 'hello',", " 2 => {", " 3 => 'world'", " }", "}" ].join("\n") expect(converter.convert({1 => "hello", 2 => {3=> "world"}}, string_formats)).to eq(result) end it 'applies a short form format' do result = [ "{", " 1 => {", " 2 => 3", " },", " 4 => 5", "}"].join("\n") expect(converter.convert({1 => {2 => 3}, 4 => 5}, '%#h')).to eq(result) end context "containing an array" do it 'the hash and array renders without breaks and indentation by default' do result = "{1 => [1, 2, 3]}" formatted = converter.convert({ 1 => [1, 2, 3] }, :default) expect(formatted).to eq(result) end it 'the array renders with breaks if so specified' do string_formats = { Puppet::Pops::Types::PArrayType::DEFAULT => { 'format' => '%#1a', 'separator' =>"," } } result = [ "{1 => [ 1,", " 2,", " 3]}" ].join("\n") formatted = converter.convert({ 1 => [1, 2, 3] }, string_formats) expect(formatted).to eq(result) end it 'both hash and array renders with breaks and indentation if so specified for both' do string_formats = { Puppet::Pops::Types::PArrayType::DEFAULT => { 'format' => '%#1a', 'separator' =>", " }, Puppet::Pops::Types::PHashType::DEFAULT => { 'format' => '%#h', 'separator' =>"," } } result = [ "{", " 1 => [ 1,", " 2,", " 3]", "}" ].join("\n") formatted = converter.convert({ 1 => [1, 2, 3] }, string_formats) expect(formatted).to eq(result) end it 'hash, but not array is rendered with breaks and indentation if so specified only for the hash' do string_formats = { Puppet::Pops::Types::PArrayType::DEFAULT => { 'format' => '%a', 'separator' =>", " }, Puppet::Pops::Types::PHashType::DEFAULT => { 'format' => '%#h', 'separator' =>"," } } result = [ "{", " 1 => [1, 2, 3]", "}" ].join("\n") formatted = converter.convert({ 1 => [1, 2, 3] }, string_formats) expect(formatted).to eq(result) end end context 'that is subclassed' do let(:array) { ['a', 2] } let(:derived_array) do Class.new(Array) do def to_a self # Dead wrong! Should return a plain Array copy end end.new(array) end let(:derived_with_to_a) do Class.new(Array) do def to_a super end end.new(array) end let(:hash) { {'first' => 1, 'second' => 2} } let(:derived_hash) do Class.new(Hash)[hash] end let(:derived_with_to_hash) do Class.new(Hash) do def to_hash {}.merge(self) end end[hash] end it 'formats a derived array as a Runtime' do expect(converter.convert(array)).to eq('[\'a\', 2]') expect(converter.convert(derived_array)).to eq('["a", 2]') end it 'formats a derived array with #to_a retuning plain Array as an Array' do expect(converter.convert(derived_with_to_a)).to eq('[\'a\', 2]') end it 'formats a derived hash as a Runtime' do expect(converter.convert(hash)).to eq('{\'first\' => 1, \'second\' => 2}') expect(converter.convert(derived_hash)).to eq('{"first"=>1, "second"=>2}') end it 'formats a derived hash with #to_hash retuning plain Hash as a Hash' do expect(converter.convert(derived_with_to_hash, '%p')).to eq('{\'first\' => 1, \'second\' => 2}') end end it 'errors when format is not recognized' do expect do string_formats = { Puppet::Pops::Types::PHashType::DEFAULT => "%k"} converter.convert({1 => 2}, string_formats) end.to raise_error(/Illegal format 'k' specified for value of Hash type - expected one of the characters 'hasp'/) end end context 'when converting a runtime type' do [ :sym, (1..3), Time.now ].each do |value| it "the default string representation for #{value} is #to_s" do expect(converter.convert(value, :default)).to eq(value.to_s) end it "the '%q' string representation for #{value} is #inspect" do expect(converter.convert(value, '%q')).to eq(value.inspect) end it "the '%p' string representation for #{value} is quoted #to_s" do expect(converter.convert(value, '%p')).to eq("'#{value}'") end end it 'an unknown format raises an error' do expect { converter.convert(:sym, '%b') }.to raise_error("Illegal format 'b' specified for value of Runtime type - expected one of the characters 'spq'") end end context 'when converting regexp' do it 'the default string representation is "regexp"' do expect(converter.convert(/.*/, :default)).to eq('.*') end { "%s" => '.*', "%6s" => ' .*', "%.1s" => '.', "%-6s" => '.* ', "%p" => '/.*/', "%6p" => ' /.*/', "%-6p" => '/.*/ ', "%.2p" => '/.', "%#s" => "'.*'", "%#p" => '/.*/' }.each do |fmt, result | it "the format #{fmt} produces #{result}" do string_formats = { Puppet::Pops::Types::PRegexpType::DEFAULT => fmt} expect(converter.convert(/.*/, string_formats)).to eq(result) end end context 'that contains flags' do it 'the format %s produces \'(?m-ix:[a-z]\s*)\' for expression /[a-z]\s*/m' do string_formats = { Puppet::Pops::Types::PRegexpType::DEFAULT => '%s'} expect(converter.convert(/[a-z]\s*/m, string_formats)).to eq('(?m-ix:[a-z]\s*)') end it 'the format %p produces \'/(?m-ix:[a-z]\s*)/\' for expression /[a-z]\s*/m' do string_formats = { Puppet::Pops::Types::PRegexpType::DEFAULT => '%p'} expect(converter.convert(/[a-z]\s*/m, string_formats)).to eq('/(?m-ix:[a-z]\s*)/') end it 'the format %p produces \'/foo\/bar/\' for expression /foo\/bar/' do string_formats = { Puppet::Pops::Types::PRegexpType::DEFAULT => '%p'} expect(converter.convert(/foo\/bar/, string_formats)).to eq('/foo\/bar/') end context 'and slashes' do it 'the format %s produces \'(?m-ix:foo/bar)\' for expression /foo\/bar/m' do string_formats = { Puppet::Pops::Types::PRegexpType::DEFAULT => '%s'} expect(converter.convert(/foo\/bar/m, string_formats)).to eq('(?m-ix:foo/bar)') end it 'the format %s produces \'(?m-ix:foo\/bar)\' for expression /foo\\\/bar/m' do string_formats = { Puppet::Pops::Types::PRegexpType::DEFAULT => '%s'} expect(converter.convert(/foo\\\/bar/m, string_formats)).to eq('(?m-ix:foo\\\\/bar)') end it 'the format %p produces \'(?m-ix:foo\/bar)\' for expression /foo\/bar/m' do string_formats = { Puppet::Pops::Types::PRegexpType::DEFAULT => '%p'} expect(converter.convert(/foo\/bar/m, string_formats)).to eq('/(?m-ix:foo\/bar)/') end it 'the format %p produces \'(?m-ix:foo\/bar)\' for expression /(?m-ix:foo\/bar)/' do string_formats = { Puppet::Pops::Types::PRegexpType::DEFAULT => '%p'} expect(converter.convert(/(?m-ix:foo\/bar)/, string_formats)).to eq('/(?m-ix:foo\/bar)/') end end end it 'errors when format is not recognized' do expect do string_formats = { Puppet::Pops::Types::PRegexpType::DEFAULT => "%k"} converter.convert(/.*/, string_formats) end.to raise_error(/Illegal format 'k' specified for value of Regexp type - expected one of the characters 'sp'/) end end context 'when converting binary' do let(:sample) { binary.from_binary_string('binary') } it 'the binary is converted to strict base64 string unquoted by default (same as %B)' do expect(converter.convert(sample, :default)).to eq("YmluYXJ5") end it 'the binary is converted using %p by default when contained in an array' do expect(converter.convert([sample], :default)).to eq("[Binary(\"YmluYXJ5\")]") end it '%B formats in base64 strict mode (same as default)' do string_formats = { Puppet::Pops::Types::PBinaryType::DEFAULT => '%B'} expect(converter.convert(sample, string_formats)).to eq("YmluYXJ5") end it '%b formats in base64 relaxed mode, and adds newline' do string_formats = { Puppet::Pops::Types::PBinaryType::DEFAULT => '%b'} expect(converter.convert(sample, string_formats)).to eq("YmluYXJ5\n") end it '%u formats in base64 urlsafe mode' do string_formats = { Puppet::Pops::Types::PBinaryType::DEFAULT => '%u'} expect(converter.convert(binary.from_base64("++//"), string_formats)).to eq("--__") end it '%p formats with type name' do string_formats = { Puppet::Pops::Types::PBinaryType::DEFAULT => '%p'} expect(converter.convert(sample, string_formats)).to eq("Binary(\"YmluYXJ5\")") end it '%#s formats as quoted string with escaped non printable bytes' do string_formats = { Puppet::Pops::Types::PBinaryType::DEFAULT => '%#s'} expect(converter.convert(binary.from_base64("apa="), string_formats)).to eq("\"j\\x96\"") end it '%s formats as unquoted string with valid UTF-8 chars' do string_formats = { Puppet::Pops::Types::PBinaryType::DEFAULT => '%s'} # womans hat emoji is E318, a three byte UTF-8 char EE 8C 98 expect(converter.convert(binary.from_binary_string("\xEE\x8C\x98"), string_formats)).to eq("\uE318") end it '%s errors if given non UTF-8 bytes' do string_formats = { Puppet::Pops::Types::PBinaryType::DEFAULT => '%s'} expect { converter.convert(binary.from_base64("apa="), string_formats) }.to raise_error(Encoding::UndefinedConversionError) end { "%s" => 'binary', "%#s" => '"binary"', "%8s" => ' binary', "%.2s" => 'bi', "%-8s" => 'binary ', "%p" => 'Binary("YmluYXJ5")', "%10p" => 'Binary(" YmluYXJ5")', "%-10p" => 'Binary("YmluYXJ5 ")', "%.2p" => 'Binary("Ym")', "%b" => "YmluYXJ5\n", "%11b" => " YmluYXJ5\n", "%-11b" => "YmluYXJ5\n ", "%.2b" => "Ym", "%B" => "YmluYXJ5", "%11B" => " YmluYXJ5", "%-11B" => "YmluYXJ5 ", "%.2B" => "Ym", "%u" => "YmluYXJ5", "%11u" => " YmluYXJ5", "%-11u" => "YmluYXJ5 ", "%.2u" => "Ym", "%t" => 'Binary', "%#t" => '"Binary"', "%8t" => ' Binary', "%-8t" => 'Binary ', "%.3t" => 'Bin', "%T" => 'BINARY', "%#T" => '"BINARY"', "%8T" => ' BINARY', "%-8T" => 'BINARY ', "%.3T" => 'BIN', }.each do |fmt, result | it "the format #{fmt} produces #{result}" do string_formats = { Puppet::Pops::Types::PBinaryType::DEFAULT => fmt} expect(converter.convert(sample, string_formats)).to eq(result) end end end context 'when converting iterator' do it 'the iterator is transformed to an array and formatted using array rules' do itor = Puppet::Pops::Types::Iterator.new(Puppet::Pops::Types::PIntegerType::DEFAULT, [1,2,3]).reverse_each expect(converter.convert(itor, :default)).to eq('[3, 2, 1]') end end context 'when converting type' do it 'the default string representation of a type is its programmatic string form' do expect(converter.convert(factory.integer, :default)).to eq('Integer') end { "%s" => 'Integer', "%p" => 'Integer', "%#s" => '"Integer"', "%#p" => 'Integer', }.each do |fmt, result | it "the format #{fmt} produces #{result}" do string_formats = { Puppet::Pops::Types::PTypeType::DEFAULT => fmt} expect(converter.convert(factory.integer, string_formats)).to eq(result) end end it 'errors when format is not recognized' do expect do string_formats = { Puppet::Pops::Types::PTypeType::DEFAULT => "%k"} converter.convert(factory.integer, string_formats) end.to raise_error(/Illegal format 'k' specified for value of Type type - expected one of the characters 'sp'/) end end it "allows format to be directly given (instead of as a type=> format hash)" do expect(converter.convert('hello', '%5.1s')).to eq(' h') end it 'an explicit format for a type will override more specific defaults' do expect(converter.convert({ 'x' => 'X' }, { Puppet::Pops::Types::PCollectionType::DEFAULT => '%#p' })).to eq("{\n 'x' => 'X'\n}") end end puppet-5.5.10/spec/unit/pops/types/task_spec.rb0000644005276200011600000002632213417161722021357 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' require 'puppet_spec/compiler' require 'puppet/parser/script_compiler' module Puppet::Pops module Types describe 'The Task Type' do include PuppetSpec::Compiler include PuppetSpec::Files context 'when loading' do let(:testing_env) do { 'testing' => { 'modules' => modules, 'manifests' => manifests } } end let(:manifests) { {} } let(:environments_dir) { Puppet[:environmentpath] } let(:testing_env_dir) do dir_contained_in(environments_dir, testing_env) env_dir = File.join(environments_dir, 'testing') PuppetSpec::Files.record_tmp(env_dir) env_dir end let(:modules_dir) { File.join(testing_env_dir, 'modules') } let(:env) { Puppet::Node::Environment.create(:testing, [modules_dir]) } let(:node) { Puppet::Node.new('test', :environment => env) } let(:logs) { [] } let(:warnings) { logs.select { |log| log.level == :warning }.map { |log| log.message } } let(:notices) { logs.select { |log| log.level == :notice }.map { |log| log.message } } let(:task_t) { TypeFactory.task } before(:each) { Puppet[:tasks] = true } context 'tasks' do let(:compiler) { Puppet::Parser::ScriptCompiler.new(env, node.name) } let(:modules) do { 'testmodule' => testmodule } end def compile(code = nil) Puppet[:code] = code Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do compiler.compile do |catalog| yield if block_given? catalog end end end context 'without metadata' do let(:testmodule) { { 'tasks' => { 'hello' => <<-RUBY require 'json' args = JSON.parse(STDIN.read) puts({message: args['message']}.to_json) exit 0 RUBY } } } it 'loads task without metadata as a generic Task' do compile do module_loader = Puppet.lookup(:loaders).find_loader('testmodule') task = module_loader.load(:task, 'testmodule::hello') expect(task_t.instance?(task)).to be_truthy expect(task.name).to eq('testmodule::hello') expect(task._pcore_type).to eq(task_t) end end context 'without --tasks' do before(:each) { Puppet[:tasks] = false } it 'evaluator does not recognize generic tasks' do compile do module_loader = Puppet.lookup(:loaders).find_loader('testmodule') expect(module_loader.load(:task, 'testmodule::hello')).to be_nil end end end end context 'with metadata' do let(:testmodule) { { 'tasks' => { 'hello.rb' => <<-RUBY, require 'json' args = JSON.parse(STDIN.read) puts({message: args['message']}.to_json) exit 0 RUBY 'hello.json' => <<-JSON, { "puppet_task_version": 1, "supports_noop": true, "parameters": { "message": { "type": "String", "description": "the message", "sensitive": false }, "font": { "type": "Optional[String]" } }} JSON 'non_data.rb' => <<-RUBY, require 'json' args = JSON.parse(STDIN.read) puts({message: args['message']}.to_json) exit 0 RUBY 'non_data.json' => <<-JSON { "puppet_task_version": 1, "supports_noop": true, "parameters": { "arg": { "type": "Hash", "description": "the non data param" } }} JSON } } } it 'loads a task with parameters' do compile do module_loader = Puppet.lookup(:loaders).find_loader('testmodule') task = module_loader.load(:task, 'testmodule::hello') expect(task_t.instance?(task)).to be_truthy expect(task.name).to eq('testmodule::hello') expect(task._pcore_type).to eq(task_t) expect(task.supports_noop).to eql(true) expect(task.puppet_task_version).to eql(1) expect(task.executable).to eql("#{modules_dir}/testmodule/tasks/hello.rb") tp = task.parameters expect(tp['message']['description']).to eql('the message') expect(tp['message']['type']).to be_a(Puppet::Pops::Types::PStringType) end end it 'loads a task with non-Data parameter' do compile do module_loader = Puppet.lookup(:loaders).find_loader('testmodule') task = module_loader.load(:task, 'testmodule::non_data') expect(task_t.instance?(task)).to be_truthy tp = task.parameters expect(tp['arg']['type']).to be_a(Puppet::Pops::Types::PHashType) end end context 'with adjacent directory for init task' do let(:testmodule) { { 'tasks' => { 'init' => { 'foo.sh' => 'echo hello' }, 'init.sh' => 'echo hello', 'init.json' => <<-JSON { "supports_noop": true, "parameters": { "message": { "type": "String" } } } JSON } } } it 'loads the init task with parameters and executable' do compile do module_loader = Puppet.lookup(:loaders).find_loader('testmodule') task = module_loader.load(:task, 'testmodule') expect(task_t.instance?(task)).to be_truthy expect(task.executable).to eql("#{modules_dir}/testmodule/tasks/init.sh") expect(task.parameters).to be_a(Hash) expect(task.parameters['message']['type']).to be_a(Puppet::Pops::Types::PStringType) end end end context 'with adjacent directory for named task' do let(:testmodule) { { 'tasks' => { 'hello' => { 'foo.sh' => 'echo hello' }, 'hello.sh' => 'echo hello', 'hello.json' => <<-JSON { "supports_noop": true, "parameters": { "message": { "type": "String" } } } JSON } } } it 'loads a named task with parameters and executable' do compile do module_loader = Puppet.lookup(:loaders).find_loader('testmodule') task = module_loader.load(:task, 'testmodule::hello') expect(task_t.instance?(task)).to be_truthy expect(task.executable).to eql("#{modules_dir}/testmodule/tasks/hello.sh") expect(task.parameters).to be_a(Hash) expect(task.parameters['message']['type']).to be_a(Puppet::Pops::Types::PStringType) end end end context 'using more than two segments in the name' do let(:testmodule) { { 'tasks' => { 'hello' => { 'foo.sh' => 'echo hello' } } } } it 'task is not found' do compile do module_loader = Puppet.lookup(:loaders).find_loader('testmodule') expect(module_loader.load(:task, 'testmodule::hello::foo')).to be_nil end end end context 'that has a malformed top-level entry' do let(:testmodule) { { 'tasks' => { 'hello' => 'echo hello', 'hello.json' => <<-JSON { "supports_nop": true, "parameters": { "message": { "type": "String" } } } JSON } } } it 'fails with unrecognized key error' do compile do module_loader = Puppet.lookup(:loaders).find_loader('testmodule') expect{module_loader.load(:task, 'testmodule::hello')}.to raise_error( /Failed to load metadata for task testmodule::hello:.*unrecognized key 'supports_nop'/) end end end context 'that has no parameters' do let(:testmodule) { { 'tasks' => { 'hello' => 'echo hello', 'hello.json' => '{ "supports_noop": false }' } } } it 'loads the task with parameters set to undef' do compile do module_loader = Puppet.lookup(:loaders).find_loader('testmodule') task = module_loader.load(:task, 'testmodule::hello') expect(task_t.instance?(task)).to be_truthy expect(task.parameters).to be_nil end end end context 'that has a malformed parameter name' do let(:testmodule) { { 'tasks' => { 'hello' => 'echo hello', 'hello.json' => <<-JSON { "supports_noop": true, "parameters": { "Message": { "type": "String" } } } JSON } } } it 'fails with pattern mismatch error' do compile do module_loader = Puppet.lookup(:loaders).find_loader('testmodule') expect{module_loader.load(:task, 'testmodule::hello')}.to raise_error( /entry 'parameters' key of entry 'Message' expects a match for Pattern\[\/\\A\[a-z\]\[a-z0-9_\]\*\\z\/\], got 'Message'/) end end end context 'that has a puppet_task_version that is a string' do let(:testmodule) { { 'tasks' => { 'hello' => 'echo hello', 'hello.json' => <<-JSON { "puppet_task_version": "1", "supports_noop": true, "parameters": { "message": { "type": "String" } } } JSON } } } it 'fails with type mismatch error' do compile do module_loader = Puppet.lookup(:loaders).find_loader('testmodule') expect{module_loader.load(:task, 'testmodule::hello')}.to raise_error( /entry 'puppet_task_version' expects an Integer value, got String/) end end end end end end end end end puppet-5.5.10/spec/unit/pops/types/type_calculator_spec.rb0000644005276200011600000027541613417161722023621 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' module Puppet::Pops module Types describe 'The type calculator' do let(:calculator) { TypeCalculator.new } def range_t(from, to) PIntegerType.new(from, to) end def pattern_t(*patterns) TypeFactory.pattern(*patterns) end def regexp_t(pattern) TypeFactory.regexp(pattern) end def string_t(string = nil) TypeFactory.string(string) end def constrained_string_t(size_type) TypeFactory.string(size_type) end def callable_t(*params) TypeFactory.callable(*params) end def all_callables_t TypeFactory.all_callables end def enum_t(*strings) TypeFactory.enum(*strings) end def variant_t(*types) TypeFactory.variant(*types) end def empty_variant_t() t = TypeFactory.variant() # assert this to ensure we did get an empty variant (or tests may pass when not being empty) raise 'typefactory did not return empty variant' unless t.types.empty? t end def type_alias_t(name, type_string) type_expr = Parser::EvaluatingParser.new.parse_string(type_string) TypeFactory.type_alias(name, type_expr) end def type_reference_t(type_string) TypeFactory.type_reference(type_string) end def integer_t TypeFactory.integer end def array_t(t, s = nil) TypeFactory.array_of(t, s) end def empty_array_t array_t(unit_t, range_t(0,0)) end def hash_t(k,v,s = nil) TypeFactory.hash_of(v, k, s) end def data_t TypeFactory.data end def factory TypeFactory end def collection_t(size_type = nil) TypeFactory.collection(size_type) end def tuple_t(*types) TypeFactory.tuple(types) end def constrained_tuple_t(size_type, *types) TypeFactory.tuple(types, size_type) end def struct_t(type_hash) TypeFactory.struct(type_hash) end def any_t TypeFactory.any end def optional_t(t) TypeFactory.optional(t) end def type_t(t) TypeFactory.type_type(t) end def not_undef_t(t = nil) TypeFactory.not_undef(t) end def undef_t TypeFactory.undef end def unit_t # Cannot be created via factory, the type is private to the type system PUnitType::DEFAULT end def runtime_t(t, c) TypeFactory.runtime(t, c) end def object_t(hash) TypeFactory.object(hash) end def iterable_t(t = nil) TypeFactory.iterable(t) end def types Types end context 'when inferring ruby' do it 'fixnum translates to PIntegerType' do expect(calculator.infer(1).class).to eq(PIntegerType) end it 'large fixnum (or bignum depending on architecture) translates to PIntegerType' do expect(calculator.infer(2**33).class).to eq(PIntegerType) end it 'float translates to PFloatType' do expect(calculator.infer(1.3).class).to eq(PFloatType) end it 'string translates to PStringType' do expect(calculator.infer('foo').class).to eq(PStringType) end it 'inferred string type knows the string value' do t = calculator.infer('foo') expect(t.class).to eq(PStringType) expect(t.value).to eq('foo') end it 'boolean true translates to PBooleanType::TRUE' do expect(calculator.infer(true)).to eq(PBooleanType::TRUE) end it 'boolean false translates to PBooleanType::FALSE' do expect(calculator.infer(false)).to eq(PBooleanType::FALSE) end it 'regexp translates to PRegexpType' do expect(calculator.infer(/^a regular expression$/).class).to eq(PRegexpType) end it 'iterable translates to PIteratorType' do expect(calculator.infer(Iterable.on(1))).to be_a(PIteratorType) end it 'nil translates to PUndefType' do expect(calculator.infer(nil).class).to eq(PUndefType) end it ':undef translates to PUndefType' do expect(calculator.infer(:undef).class).to eq(PUndefType) end it 'an instance of class Foo translates to PRuntimeType[ruby, Foo]' do ::Foo = Class.new begin t = calculator.infer(::Foo.new) expect(t.class).to eq(PRuntimeType) expect(t.runtime).to eq(:ruby) expect(t.runtime_type_name).to eq('Foo') ensure Object.send(:remove_const, :Foo) end end it 'Class Foo translates to PTypeType[PRuntimeType[ruby, Foo]]' do ::Foo = Class.new begin t = calculator.infer(::Foo) expect(t.class).to eq(PTypeType) tt = t.type expect(tt.class).to eq(PRuntimeType) expect(tt.runtime).to eq(:ruby) expect(tt.runtime_type_name).to eq('Foo') ensure Object.send(:remove_const, :Foo) end end it 'Module FooModule translates to PTypeType[PRuntimeType[ruby, FooModule]]' do ::FooModule = Module.new begin t = calculator.infer(::FooModule) expect(t.class).to eq(PTypeType) tt = t.type expect(tt.class).to eq(PRuntimeType) expect(tt.runtime).to eq(:ruby) expect(tt.runtime_type_name).to eq('FooModule') ensure Object.send(:remove_const, :FooModule) end end context 'sensitive' do it 'translates to PSensitiveType' do expect(calculator.infer(PSensitiveType::Sensitive.new("hunter2")).class).to eq(PSensitiveType) end end context 'binary' do it 'translates to PBinaryType' do expect(calculator.infer(PBinaryType::Binary.from_binary_string("binary")).class).to eq(PBinaryType) end end context 'version' do it 'translates to PVersionType' do expect(calculator.infer(SemanticPuppet::Version.new(1,0,0)).class).to eq(PSemVerType) end it 'range translates to PVersionRangeType' do expect(calculator.infer(SemanticPuppet::VersionRange.parse('1.x')).class).to eq(PSemVerRangeType) end it 'translates to a limited PVersionType by infer_set' do v = SemanticPuppet::Version.new(1,0,0) t = calculator.infer_set(v) expect(t.class).to eq(PSemVerType) expect(t.ranges.size).to eq(1) expect(t.ranges[0].begin).to eq(v) expect(t.ranges[0].end).to eq(v) end end context 'timespan' do it 'translates to PTimespanType' do expect(calculator.infer(Time::Timespan.from_fields_hash('days' => 2))).to be_a(PTimespanType) end it 'translates to a limited PTimespanType by infer_set' do ts = Time::Timespan.from_fields_hash('days' => 2) t = calculator.infer_set(ts) expect(t.class).to eq(PTimespanType) expect(t.from).to be(ts) expect(t.to).to be(ts) end end context 'timestamp' do it 'translates to PTimespanType' do expect(calculator.infer(Time::Timestamp.now)).to be_a(PTimestampType) end it 'translates to a limited PTimespanType by infer_set' do ts = Time::Timestamp.now t = calculator.infer_set(ts) expect(t.class).to eq(PTimestampType) expect(t.from).to be(ts) expect(t.to).to be(ts) end end context 'array' do let(:derived) do Class.new(Array).new([1,2]) end let(:derived_object) do Class.new(Array) do include PuppetObject def self._pcore_type @type ||= TypeFactory.object('name' => 'DerivedObjectArray') end end.new([1,2]) end it 'translates to PArrayType' do expect(calculator.infer([1,2]).class).to eq(PArrayType) end it 'translates derived Array to PRuntimeType' do expect(calculator.infer(derived).class).to eq(PRuntimeType) end it 'translates derived Puppet Object Array to PObjectType' do expect(calculator.infer(derived_object).class).to eq(PObjectType) end it 'Instance of derived Array class is not instance of Array type' do expect(PArrayType::DEFAULT).not_to be_instance(derived) end it 'Instance of derived Array class is instance of Runtime type' do expect(runtime_t('ruby', nil)).to be_instance(derived) end it 'Instance of derived Puppet Object Array class is not instance of Array type' do expect(PArrayType::DEFAULT).not_to be_instance(derived_object) end it 'Instance of derived Puppet Object Array class is instance of Object type' do expect(object_t('name' => 'DerivedObjectArray')).to be_instance(derived_object) end it 'with fixnum values translates to PArrayType[PIntegerType]' do expect(calculator.infer([1,2]).element_type.class).to eq(PIntegerType) end it 'with 32 and 64 bit integer values translates to PArrayType[PIntegerType]' do expect(calculator.infer([1,2**33]).element_type.class).to eq(PIntegerType) end it 'Range of integer values are computed' do t = calculator.infer([-3,0,42]).element_type expect(t.class).to eq(PIntegerType) expect(t.from).to eq(-3) expect(t.to).to eq(42) end it 'Compound string values are converted to enums' do t = calculator.infer(['a','b', 'c']).element_type expect(t.class).to eq(PEnumType) expect(t.values).to eq(['a', 'b', 'c']) end it 'with integer and float values translates to PArrayType[PNumericType]' do expect(calculator.infer([1,2.0]).element_type.class).to eq(PNumericType) end it 'with integer and string values translates to PArrayType[PScalarDataType]' do expect(calculator.infer([1,'two']).element_type.class).to eq(PScalarDataType) end it 'with float and string values translates to PArrayType[PScalarDataType]' do expect(calculator.infer([1.0,'two']).element_type.class).to eq(PScalarDataType) end it 'with integer, float, and string values translates to PArrayType[PScalarDataType]' do expect(calculator.infer([1, 2.0,'two']).element_type.class).to eq(PScalarDataType) end it 'with integer and regexp values translates to PArrayType[PScalarType]' do expect(calculator.infer([1, /two/]).element_type.class).to eq(PScalarType) end it 'with string and regexp values translates to PArrayType[PScalarType]' do expect(calculator.infer(['one', /two/]).element_type.class).to eq(PScalarType) end it 'with string and symbol values translates to PArrayType[PAnyType]' do expect(calculator.infer(['one', :two]).element_type.class).to eq(PAnyType) end it 'with integer and nil values translates to PArrayType[PIntegerType]' do expect(calculator.infer([1, nil]).element_type.class).to eq(PIntegerType) end it 'with integer value, and array of string values, translates to Array[Data]' do expect(calculator.infer([1, ['two']]).element_type.name).to eq('Data') end it 'with integer value, and hash of string => string values, translates to Array[Data]' do expect(calculator.infer([1, {'two' => 'three'} ]).element_type.name).to eq('Data') end it 'with integer value, and hash of integer => string values, translates to Array[RichData]' do expect(calculator.infer([1, {2 => 'three'} ]).element_type.name).to eq('RichData') end it 'with integer, regexp, and binary values translates to Array[RichData]' do expect(calculator.infer([1, /two/, PBinaryType::Binary.from_string('three')]).element_type.name).to eq('RichData') end it 'with arrays of string values translates to PArrayType[PArrayType[PStringType]]' do et = calculator.infer([['first', 'array'], ['second','array']]) expect(et.class).to eq(PArrayType) et = et.element_type expect(et.class).to eq(PArrayType) et = et.element_type expect(et.class).to eq(PEnumType) end it 'with array of string values and array of fixnums translates to PArrayType[PArrayType[PScalarDataType]]' do et = calculator.infer([['first', 'array'], [1,2]]) expect(et.class).to eq(PArrayType) et = et.element_type expect(et.class).to eq(PArrayType) et = et.element_type expect(et.class).to eq(PScalarDataType) end it 'with hashes of string values translates to PArrayType[PHashType[PEnumType]]' do et = calculator.infer([{:first => 'first', :second => 'second' }, {:first => 'first', :second => 'second' }]) expect(et.class).to eq(PArrayType) et = et.element_type expect(et.class).to eq(PHashType) et = et.value_type expect(et.class).to eq(PEnumType) end it 'with hash of string values and hash of fixnums translates to PArrayType[PHashType[PScalarDataType]]' do et = calculator.infer([{:first => 'first', :second => 'second' }, {:first => 1, :second => 2 }]) expect(et.class).to eq(PArrayType) et = et.element_type expect(et.class).to eq(PHashType) et = et.value_type expect(et.class).to eq(PScalarDataType) end end context 'hash' do let(:derived) do Class.new(Hash)[:first => 1, :second => 2] end let(:derived_object) do Class.new(Hash) do include PuppetObject def self._pcore_type @type ||= TypeFactory.object('name' => 'DerivedObjectHash') end end[:first => 1, :second => 2] end it 'translates to PHashType' do expect(calculator.infer({:first => 1, :second => 2}).class).to eq(PHashType) end it 'translates derived Hash to PRuntimeType' do expect(calculator.infer(derived).class).to eq(PRuntimeType) end it 'translates derived Puppet Object Hash to PObjectType' do expect(calculator.infer(derived_object).class).to eq(PObjectType) end it 'Instance of derived Hash class is not instance of Hash type' do expect(PHashType::DEFAULT).not_to be_instance(derived) end it 'Instance of derived Hash class is instance of Runtime type' do expect(runtime_t('ruby', nil)).to be_instance(derived) end it 'Instance of derived Puppet Object Hash class is not instance of Hash type' do expect(PHashType::DEFAULT).not_to be_instance(derived_object) end it 'Instance of derived Puppet Object Hash class is instance of Object type' do expect(object_t('name' => 'DerivedObjectHash')).to be_instance(derived_object) end it 'with symbolic keys translates to PHashType[PRuntimeType[ruby, Symbol], value]' do k = calculator.infer({:first => 1, :second => 2}).key_type expect(k.class).to eq(PRuntimeType) expect(k.runtime).to eq(:ruby) expect(k.runtime_type_name).to eq('Symbol') end it 'with string keys translates to PHashType[PEnumType, value]' do expect(calculator.infer({'first' => 1, 'second' => 2}).key_type.class).to eq(PEnumType) end it 'with fixnum values translates to PHashType[key, PIntegerType]' do expect(calculator.infer({:first => 1, :second => 2}).value_type.class).to eq(PIntegerType) end it 'when empty infers a type that answers true to is_the_empty_hash?' do expect(calculator.infer({}).is_the_empty_hash?).to eq(true) expect(calculator.infer_set({}).is_the_empty_hash?).to eq(true) end it 'when empty is assignable to any PHashType' do expect(calculator.assignable?(hash_t(string_t, string_t), calculator.infer({}))).to eq(true) end it 'when empty is not assignable to a PHashType with from size > 0' do expect(calculator.assignable?(hash_t(string_t,string_t,range_t(1, 1)), calculator.infer({}))).to eq(false) end context 'using infer_set' do it "with 'first' and 'second' keys translates to PStructType[{first=>value,second=>value}]" do t = calculator.infer_set({'first' => 1, 'second' => 2}) expect(t.class).to eq(PStructType) expect(t.elements.size).to eq(2) expect(t.elements.map { |e| e.name }.sort).to eq(['first', 'second']) end it 'with string keys and string and array values translates to PStructType[{key1=>PStringType,key2=>PTupleType}]' do t = calculator.infer_set({ 'mode' => 'read', 'path' => ['foo', 'fee' ] }) expect(t.class).to eq(PStructType) expect(t.elements.size).to eq(2) els = t.elements.map { |e| e.value_type }.sort {|a,b| a.to_s <=> b.to_s } expect(els[0].class).to eq(PStringType) expect(els[1].class).to eq(PTupleType) end it 'with mixed string and non-string keys translates to PHashType' do t = calculator.infer_set({ 1 => 'first', 'second' => 'second' }) expect(t.class).to eq(PHashType) end it 'with empty string keys translates to PHashType' do t = calculator.infer_set({ '' => 'first', 'second' => 'second' }) expect(t.class).to eq(PHashType) end end end it 'infers an instance of an anonymous class to Runtime[ruby]' do cls = Class.new do attr_reader :name def initialize(name) @name = name end end t = calculator.infer(cls.new('test')) expect(t.class).to eql(PRuntimeType) expect(t.runtime).to eql(:ruby) expect(t.name_or_pattern).to eql(nil) end end context 'patterns' do it 'constructs a PPatternType' do t = pattern_t('a(b)c') expect(t.class).to eq(PPatternType) expect(t.patterns.size).to eq(1) expect(t.patterns[0].class).to eq(PRegexpType) expect(t.patterns[0].pattern).to eq('a(b)c') expect(t.patterns[0].regexp.match('abc')[1]).to eq('b') end it 'constructs a PEnumType with multiple strings' do t = enum_t('a', 'b', 'c', 'abc') expect(t.values).to eq(['a', 'b', 'c', 'abc'].sort) end end # Deal with cases not covered by computing common type context 'when computing common type' do it 'computes given resource type commonality' do r1 = PResourceType.new('File', nil) r2 = PResourceType.new('File', nil) expect(calculator.common_type(r1, r2).to_s).to eq('File') r2 = PResourceType.new('File', '/tmp/foo') expect(calculator.common_type(r1, r2).to_s).to eq('File') r1 = PResourceType.new('File', '/tmp/foo') expect(calculator.common_type(r1, r2).to_s).to eq("File['/tmp/foo']") r1 = PResourceType.new('File', '/tmp/bar') expect(calculator.common_type(r1, r2).to_s).to eq('File') r2 = PResourceType.new('Package', 'apache') expect(calculator.common_type(r1, r2).to_s).to eq('Resource') end it 'computes given hostclass type commonality' do r1 = PClassType.new('foo') r2 = PClassType.new('foo') expect(calculator.common_type(r1, r2).to_s).to eq('Class[foo]') r2 = PClassType.new('bar') expect(calculator.common_type(r1, r2).to_s).to eq('Class') r2 = PClassType.new(nil) expect(calculator.common_type(r1, r2).to_s).to eq('Class') r1 = PClassType.new(nil) expect(calculator.common_type(r1, r2).to_s).to eq('Class') end context 'of strings' do it 'computes commonality' do t1 = string_t('abc') t2 = string_t('xyz') common_t = calculator.common_type(t1,t2) expect(common_t.class).to eq(PEnumType) expect(common_t.values).to eq(['abc', 'xyz']) end it 'computes common size_type' do t1 = constrained_string_t(range_t(3,6)) t2 = constrained_string_t(range_t(2,4)) common_t = calculator.common_type(t1,t2) expect(common_t.class).to eq(PStringType) expect(common_t.size_type).to eq(range_t(2,6)) end it 'computes common size_type to be undef when one of the types has no size_type' do t1 = string_t t2 = constrained_string_t(range_t(2,4)) common_t = calculator.common_type(t1,t2) expect(common_t.class).to eq(PStringType) expect(common_t.size_type).to be_nil end it 'computes values to be empty if the one has empty values' do t1 = string_t('apa') t2 = constrained_string_t(range_t(2,4)) common_t = calculator.common_type(t1,t2) expect(common_t.class).to eq(PStringType) expect(common_t.value).to be_nil end end it 'computes pattern commonality' do t1 = pattern_t('abc') t2 = pattern_t('xyz') common_t = calculator.common_type(t1,t2) expect(common_t.class).to eq(PPatternType) expect(common_t.patterns.map { |pr| pr.pattern }).to eq(['abc', 'xyz']) expect(common_t.to_s).to eq('Pattern[/abc/, /xyz/]') end it 'computes enum commonality to value set sum' do t1 = enum_t('a', 'b', 'c') t2 = enum_t('x', 'y', 'z') common_t = calculator.common_type(t1, t2) expect(common_t).to eq(enum_t('a', 'b', 'c', 'x', 'y', 'z')) end it 'computed variant commonality to type union where added types are not sub-types' do a_t1 = integer_t a_t2 = enum_t('b') v_a = variant_t(a_t1, a_t2) b_t1 = integer_t b_t2 = enum_t('a') v_b = variant_t(b_t1, b_t2) common_t = calculator.common_type(v_a, v_b) expect(common_t.class).to eq(PVariantType) expect(Set.new(common_t.types)).to eq(Set.new([a_t1, a_t2, b_t1, b_t2])) end it 'computed variant commonality to type union where added types are sub-types' do a_t1 = integer_t a_t2 = string_t v_a = variant_t(a_t1, a_t2) b_t1 = integer_t b_t2 = enum_t('a') v_b = variant_t(b_t1, b_t2) common_t = calculator.common_type(v_a, v_b) expect(common_t.class).to eq(PVariantType) expect(Set.new(common_t.types)).to eq(Set.new([a_t1, a_t2])) end context 'commonality of scalar data types' do it 'Numeric and String == ScalarData' do expect(calculator.common_type(PNumericType::DEFAULT, PStringType::DEFAULT).class).to eq(PScalarDataType) end it 'Numeric and Boolean == ScalarData' do expect(calculator.common_type(PNumericType::DEFAULT, PBooleanType::DEFAULT).class).to eq(PScalarDataType) end it 'String and Boolean == ScalarData' do expect(calculator.common_type(PStringType::DEFAULT, PBooleanType::DEFAULT).class).to eq(PScalarDataType) end end context 'commonality of scalar types' do it 'Regexp and Integer == Scalar' do expect(calculator.common_type(PRegexpType::DEFAULT, PScalarDataType::DEFAULT).class).to eq(PScalarType) end it 'Regexp and SemVer == ScalarData' do expect(calculator.common_type(PRegexpType::DEFAULT, PSemVerType::DEFAULT).class).to eq(PScalarType) end it 'Timestamp and Timespan == ScalarData' do expect(calculator.common_type(PTimestampType::DEFAULT, PTimespanType::DEFAULT).class).to eq(PScalarType) end it 'Timestamp and Boolean == ScalarData' do expect(calculator.common_type(PTimestampType::DEFAULT, PBooleanType::DEFAULT).class).to eq(PScalarType) end end context 'of callables' do it 'incompatible instances => generic callable' do t1 = callable_t(String) t2 = callable_t(Integer) common_t = calculator.common_type(t1, t2) expect(common_t.class).to be(PCallableType) expect(common_t.param_types).to be_nil expect(common_t.block_type).to be_nil end it 'compatible instances => the most specific' do t1 = callable_t(String) scalar_t = PScalarType.new t2 = callable_t(scalar_t) common_t = calculator.common_type(t1, t2) expect(common_t.class).to be(PCallableType) expect(common_t.param_types.class).to be(PTupleType) expect(common_t.param_types.types).to eql([string_t]) expect(common_t.block_type).to be_nil end it 'block_type is included in the check (incompatible block)' do b1 = callable_t(String) b2 = callable_t(Integer) t1 = callable_t(String, b1) t2 = callable_t(String, b2) common_t = calculator.common_type(t1, t2) expect(common_t.class).to be(PCallableType) expect(common_t.param_types).to be_nil expect(common_t.block_type).to be_nil end it 'block_type is included in the check (compatible block)' do b1 = callable_t(String) t1 = callable_t(String, b1) scalar_t = PScalarType::DEFAULT b2 = callable_t(scalar_t) t2 = callable_t(String, b2) common_t = calculator.common_type(t1, t2) expect(common_t.param_types.class).to be(PTupleType) expect(common_t.block_type).to eql(callable_t(scalar_t)) end it 'return_type is included in the check (incompatible return_type)' do t1 = callable_t([String], String) t2 = callable_t([String], Integer) common_t = calculator.common_type(t1, t2) expect(common_t.class).to be(PCallableType) expect(common_t.param_types).to be_nil expect(common_t.return_type).to be_nil end it 'return_type is included in the check (compatible return_type)' do t1 = callable_t([String], Numeric) t2 = callable_t([String], Integer) common_t = calculator.common_type(t1, t2) expect(common_t.class).to be(PCallableType) expect(common_t.param_types).to be_a(PTupleType) expect(common_t.return_type).to eql(PNumericType::DEFAULT) end end end context 'computes assignability' do include_context 'types_setup' it 'such that all types are assignable to themselves' do all_types.each do |tc| t = tc::DEFAULT expect(t).to be_assignable_to(t) end end context 'for Unit, such that' do it 'all types are assignable to Unit' do t = PUnitType::DEFAULT all_types.each { |t2| expect(t2::DEFAULT).to be_assignable_to(t) } end it 'Unit is assignable to all other types' do t = PUnitType::DEFAULT all_types.each { |t2| expect(t).to be_assignable_to(t2::DEFAULT) } end it 'Unit is assignable to Unit' do t = PUnitType::DEFAULT t2 = PUnitType::DEFAULT expect(t).to be_assignable_to(t2) end end context 'for Any, such that' do it 'all types are assignable to Any' do t = PAnyType::DEFAULT all_types.each { |t2| expect(t2::DEFAULT).to be_assignable_to(t) } end it 'Any is not assignable to anything but Any and Optional (implied Optional[Any])' do tested_types = all_types() - [PAnyType, POptionalType] t = PAnyType::DEFAULT tested_types.each { |t2| expect(t).not_to be_assignable_to(t2::DEFAULT) } end end context "for NotUndef, such that" do it 'all types except types assignable from Undef are assignable to NotUndef' do t = not_undef_t tc = TypeCalculator.singleton undef_t = PUndefType::DEFAULT all_types.each do |c| t2 = c::DEFAULT if tc.assignable?(t2, undef_t) expect(t2).not_to be_assignable_to(t) else expect(t2).to be_assignable_to(t) end end end it 'type NotUndef[T] is assignable from T unless T is assignable from Undef ' do tc = TypeCalculator.singleton undef_t = PUndefType::DEFAULT all_types().select do |c| t2 = c::DEFAULT not_undef_t = not_undef_t(t2) if tc.assignable?(t2, undef_t) expect(t2).not_to be_assignable_to(not_undef_t) else expect(t2).to be_assignable_to(not_undef_t) end end end it 'type T is assignable from NotUndef[T] unless T is assignable from Undef' do tc = TypeCalculator.singleton undef_t = PUndefType::DEFAULT all_types().select do |c| t2 = c::DEFAULT not_undef_t = not_undef_t(t2) unless tc.assignable?(t2, undef_t) expect(not_undef_t).to be_assignable_to(t2) end end end end context "for TypeReference, such that" do it 'no other type is assignable' do t = PTypeReferenceType::DEFAULT all_instances = (all_types - [ PTypeReferenceType, # Avoid comparison with t PTypeAliasType # DEFAULT resolves to PTypeReferenceType::DEFAULT, i.e. t ]).map {|c| c::DEFAULT } # Add a non-empty variant all_instances << variant_t(PAnyType::DEFAULT, PUnitType::DEFAULT) # Add a type alias that doesn't resolve to 't' all_instances << type_alias_t('MyInt', 'Integer').resolve(nil) all_instances.each { |i| expect(i).not_to be_assignable_to(t) } end it 'a TypeReference to the exact same type is assignable' do expect(type_reference_t('Integer[0,10]')).to be_assignable_to(type_reference_t('Integer[0,10]')) end it 'a TypeReference to the different type is not assignable' do expect(type_reference_t('String')).not_to be_assignable_to(type_reference_t('Integer')) end it 'a TypeReference to the different type is not assignable even if the referenced type is' do expect(type_reference_t('Integer[1,2]')).not_to be_assignable_to(type_reference_t('Integer[0,3]')) end end context 'for Data, such that' do let(:data) { TypeFactory.data } data_compatible_types.map { |t2| type_from_class(t2) }.each do |tc| it "it is assignable from #{tc.name}" do expect(tc).to be_assignable_to(data) end end data_compatible_types.map { |t2| type_from_class(t2) }.each do |tc| it "it is assignable from Optional[#{tc.name}]" do expect(optional_t(tc)).to be_assignable_to(data) end end it 'it is not assignable to any of its subtypes' do types_to_test = data_compatible_types types_to_test.each {|t2| expect(data).not_to be_assignable_to(type_from_class(t2)) } end it 'it is not assignable to any disjunct type' do tested_types = all_types - [PAnyType, POptionalType, PInitType] - scalar_data_types tested_types.each {|t2| expect(data).not_to be_assignable_to(t2::DEFAULT) } end end context 'for Rich Data, such that' do let(:rich_data) { TypeFactory.rich_data } rich_data_compatible_types.map { |t2| type_from_class(t2) }.each do |tc| it "it is assignable from #{tc.name}" do expect(tc).to be_assignable_to(rich_data) end end rich_data_compatible_types.map { |t2| type_from_class(t2) }.each do |tc| it "it is assignable from Optional[#{tc.name}]" do expect(optional_t(tc)).to be_assignable_to(rich_data) end end it 'it is not assignable to any of its subtypes' do types_to_test = rich_data_compatible_types types_to_test.each {|t2| expect(rich_data).not_to be_assignable_to(type_from_class(t2)) } end it 'it is not assignable to any disjunct type' do tested_types = all_types - [PAnyType, POptionalType, PInitType] - scalar_types tested_types.each {|t2| expect(rich_data).not_to be_assignable_to(t2::DEFAULT) } end end context 'for Variant, such that' do it 'it is assignable to a type if all contained types are assignable to that type' do v = variant_t(range_t(10, 12),range_t(14, 20)) expect(v).to be_assignable_to(integer_t) expect(v).to be_assignable_to(range_t(10, 20)) # test that both types are assignable to one of the variants OK expect(v).to be_assignable_to(variant_t(range_t(10, 20), range_t(30, 40))) # test where each type is assignable to different types in a variant is OK expect(v).to be_assignable_to(variant_t(range_t(10, 13), range_t(14, 40))) # not acceptable expect(v).not_to be_assignable_to(range_t(0, 4)) expect(v).not_to be_assignable_to(string_t) end it 'an empty Variant is assignable to another empty Variant' do expect(empty_variant_t).to be_assignable_to(empty_variant_t) end it 'an empty Variant is assignable to Any' do expect(empty_variant_t).to be_assignable_to(PAnyType::DEFAULT) end it 'an empty Variant is assignable to Unit' do expect(empty_variant_t).to be_assignable_to(PUnitType::DEFAULT) end it 'an empty Variant is not assignable to any type except empty Variant, Any, NotUndef, and Unit' do assignables = [PUnitType, PAnyType, PNotUndefType] unassignables = all_types - assignables unassignables.each {|t2| expect(empty_variant_t).not_to be_assignable_to(t2::DEFAULT) } assignables.each {|t2| expect(empty_variant_t).to be_assignable_to(t2::DEFAULT) } end it 'an empty Variant is not assignable to Optional[Any] since it is not assignable to Undef' do opt_any = optional_t(any_t) expect(empty_variant_t).not_to be_assignable_to(opt_any) end it 'an Optional[Any] is not assignable to empty Variant' do opt_any = optional_t(any_t) expect(opt_any).not_to be_assignable_to(empty_variant_t) end it 'an empty Variant is assignable to NotUndef[Variant] since Variant is not Undef' do not_undef_variant = not_undef_t(empty_variant_t) expect(empty_variant_t).to be_assignable_to(not_undef_variant) end it 'a NotUndef[Variant] is assignable to empty Variant' do not_undef_variant = not_undef_t(empty_variant_t) expect(not_undef_variant).to be_assignable_to(empty_variant_t) end end context 'for Scalar, such that' do it 'all scalars are assignable to Scalar' do t = PScalarType::DEFAULT scalar_types.each {|t2| expect(t2::DEFAULT).to be_assignable_to(t) } end it 'Scalar is not assignable to any of its subtypes' do t = PScalarType::DEFAULT types_to_test = scalar_types - [PScalarType] types_to_test.each {|t2| expect(t).not_to be_assignable_to(t2::DEFAULT) } end it 'Scalar is not assignable to any disjunct type' do tested_types = all_types - [PAnyType, POptionalType, PInitType, PNotUndefType] - scalar_types t = PScalarType::DEFAULT tested_types.each {|t2| expect(t).not_to be_assignable_to(t2::DEFAULT) } end end context 'for Numeric, such that' do it 'all numerics are assignable to Numeric' do t = PNumericType::DEFAULT numeric_types.each {|t2| expect(t2::DEFAULT).to be_assignable_to(t) } end it 'Numeric is not assignable to any of its subtypes' do t = PNumericType::DEFAULT types_to_test = numeric_types - [PNumericType] types_to_test.each {|t2| expect(t).not_to be_assignable_to(t2::DEFAULT) } end it 'Numeric does not consider ruby Rational to be an instance' do t = PNumericType::DEFAULT expect(t).not_to be_instance(Rational(2,3)) end it 'Numeric is not assignable to any disjunct type' do tested_types = all_types - [ PAnyType, POptionalType, PInitType, PNotUndefType, PScalarType, PScalarDataType, ] - numeric_types t = PNumericType::DEFAULT tested_types.each {|t2| expect(t).not_to be_assignable_to(t2::DEFAULT) } end end context 'for Collection, such that' do it 'all collections are assignable to Collection' do t = PCollectionType::DEFAULT collection_types.each {|t2| expect(t2::DEFAULT).to be_assignable_to(t) } end it 'Collection is not assignable to any of its subtypes' do t = PCollectionType::DEFAULT types_to_test = collection_types - [PCollectionType] types_to_test.each {|t2| expect(t).not_to be_assignable_to(t2::DEFAULT) } end it 'Collection is not assignable to any disjunct type' do tested_types = all_types - [ PAnyType, POptionalType, PNotUndefType, PIterableType] - collection_types t = PCollectionType::DEFAULT tested_types.each {|t2| expect(t).not_to be_assignable_to(t2::DEFAULT) } end end context 'for Array, such that' do it 'Array is not assignable to non Array based Collection type' do t = PArrayType::DEFAULT tested_types = collection_types - [ PCollectionType, PNotUndefType, PArrayType, PTupleType] tested_types.each {|t2| expect(t).not_to be_assignable_to(t2::DEFAULT) } end it 'Array is not assignable to any disjunct type' do tested_types = all_types - [ PAnyType, POptionalType, PNotUndefType, PIterableType] - collection_types t = PArrayType::DEFAULT tested_types.each {|t2| expect(t).not_to be_assignable_to(t2::DEFAULT) } end it 'Empty Array is assignable to an array that accepts 0 entries' do expect(empty_array_t).to be_assignable_to(array_t(string_t)) expect(empty_array_t).to be_assignable_to(array_t(integer_t)) end it 'A Tuple is assignable to an array' do expect(tuple_t(String)).to be_assignable_to(array_t(String)) end it 'A Tuple with elements is assignable to an array with min size ' do expect(tuple_t(String,String)).to be_assignable_to(array_t(String, range_t(2, :default))) end it 'A Tuple with elements where the last 2 are optional is assignable to an array with size - 2' do expect(constrained_tuple_t(range_t(2, :default), String,String,String,String)).to be_assignable_to(array_t(String, range_t(2, :default))) end end context 'for Enum, such that' do it 'Enum is assignable to an Enum with all contained options' do expect(enum_t('a', 'b')).to be_assignable_to(enum_t('a', 'b', 'c')) end it 'Enum is not assignable to an Enum with fewer contained options' do expect(enum_t('a', 'b')).not_to be_assignable_to(enum_t('a')) end it 'case insensitive Enum is not assignable to case sensitive Enum' do expect(enum_t('a', 'b', true)).not_to be_assignable_to(enum_t('a', 'b')) end it 'case sensitive Enum is assignable to case insensitive Enum' do expect(enum_t('a', 'b')).to be_assignable_to(enum_t('a', 'b', true)) end it 'case sensitive Enum is not assignable to case sensitive Enum using different case' do expect(enum_t('a', 'b')).not_to be_assignable_to(enum_t('A', 'B')) end it 'case sensitive Enum is assignable to case insensitive Enum using different case' do expect(enum_t('a', 'b')).to be_assignable_to(enum_t('A', 'B', true)) end end context 'for Hash, such that' do it 'Hash is not assignable to any other Collection type' do t = PHashType::DEFAULT tested_types = collection_types - [ PCollectionType, PStructType, PHashType] tested_types.each {|t2| expect(t).not_to be_assignable_to(t2::DEFAULT) } end it 'Hash is not assignable to any disjunct type' do tested_types = all_types - [ PAnyType, POptionalType, PNotUndefType, PIterableType] - collection_types t = PHashType::DEFAULT tested_types.each {|t2| expect(t).not_to be_assignable_to(t2::DEFAULT) } end it 'Struct is assignable to Hash with Pattern that matches all keys' do expect(struct_t({'x' => integer_t, 'y' => integer_t})).to be_assignable_to(hash_t(pattern_t(/^\w+$/), factory.any)) end it 'Struct is assignable to Hash with Enum that matches all keys' do expect(struct_t({'x' => integer_t, 'y' => integer_t})).to be_assignable_to(hash_t(enum_t('x', 'y', 'z'), factory.any)) end it 'Struct is not assignable to Hash with Pattern unless all keys match' do expect(struct_t({'a' => integer_t, 'A' => integer_t})).not_to be_assignable_to(hash_t(pattern_t(/^[A-Z]+$/), factory.any)) end it 'Struct is not assignable to Hash with Enum unless all keys match' do expect(struct_t({'a' => integer_t, 'y' => integer_t})).not_to be_assignable_to(hash_t(enum_t('x', 'y', 'z'), factory.any)) end end context 'for Timespan such that' do it 'Timespan is assignable to less constrained Timespan' do t1 = PTimespanType.new('00:00:10', '00:00:20') t2 = PTimespanType.new('00:00:11', '00:00:19') expect(t2).to be_assignable_to(t1) end it 'Timespan is not assignable to more constrained Timespan' do t1 = PTimespanType.new('00:00:10', '00:00:20') t2 = PTimespanType.new('00:00:11', '00:00:19') expect(t1).not_to be_assignable_to(t2) end end context 'for Timestamp such that' do it 'Timestamp is assignable to less constrained Timestamp' do t1 = PTimestampType.new('2016-01-01', '2016-12-31') t2 = PTimestampType.new('2016-02-01', '2016-11-30') expect(t2).to be_assignable_to(t1) end it 'Timestamp is not assignable to more constrained Timestamp' do t1 = PTimestampType.new('2016-01-01', '2016-12-31') t2 = PTimestampType.new('2016-02-01', '2016-11-30') expect(t1).not_to be_assignable_to(t2) end end context 'for Tuple, such that' do it 'Tuple is not assignable to any other non Array based Collection type' do t = PTupleType::DEFAULT tested_types = collection_types - [ PCollectionType, PTupleType, PArrayType] tested_types.each {|t2| expect(t).not_to be_assignable_to(t2::DEFAULT) } end it 'A tuple with parameters is assignable to the default Tuple' do t = PTupleType::DEFAULT t2 = PTupleType.new([PStringType::DEFAULT]) expect(t2).to be_assignable_to(t) end it 'Tuple is not assignable to any disjunct type' do tested_types = all_types - [ PAnyType, POptionalType, PInitType, PNotUndefType, PIterableType] - collection_types t = PTupleType::DEFAULT tested_types.each {|t2| expect(t).not_to be_assignable_to(t2::DEFAULT) } end end context 'for Struct, such that' do it 'Struct is not assignable to any other non Hashed based Collection type' do t = PStructType::DEFAULT tested_types = collection_types - [ PCollectionType, PStructType, PHashType, PInitType] tested_types.each {|t2| expect(t).not_to be_assignable_to(t2::DEFAULT) } end it 'Struct is not assignable to any disjunct type' do tested_types = all_types - [ PAnyType, POptionalType, PNotUndefType, PIterableType, PInitType] - collection_types t = PStructType::DEFAULT tested_types.each {|t2| expect(t).not_to be_assignable_to(t2::DEFAULT) } end it 'Default key optionality is controlled by value assignability to undef' do t1 = struct_t({'member' => string_t}) expect(t1.elements[0].key_type).to eq(string_t('member')) t1 = struct_t({'member' => any_t}) expect(t1.elements[0].key_type).to eq(optional_t(string_t('member'))) end it "NotUndef['key'] becomes String['key'] (since its implied that String is required)" do t1 = struct_t({not_undef_t('member') => string_t}) expect(t1.elements[0].key_type).to eq(string_t('member')) end it "Optional['key'] becomes Optional[String['key']]" do t1 = struct_t({optional_t('member') => string_t}) expect(t1.elements[0].key_type).to eq(optional_t(string_t('member'))) end it 'Optional members are not required' do t1 = struct_t({optional_t('optional_member') => string_t, not_undef_t('other_member') => string_t}) t2 = struct_t({not_undef_t('other_member') => string_t}) expect(t2).to be_assignable_to(t1) end it 'Required members not optional even when value is' do t1 = struct_t({not_undef_t('required_member') => any_t, not_undef_t('other_member') => string_t}) t2 = struct_t({not_undef_t('other_member') => string_t}) expect(t2).not_to be_assignable_to(t1) end it 'A hash of string is not assignable to struct with integer value' do t1 = struct_t({'foo' => integer_t, 'bar' => string_t}) t2 = hash_t(string_t, string_t, range_t(2, 2)) expect(t1.assignable?(t2)).to eql(false) end it 'A hash of with integer key is not assignable to struct with string key' do t1 = struct_t({'foo' => string_t, 'bar' => string_t}) t2 = hash_t(integer_t, string_t, range_t(2, 2)) expect(t1.assignable?(t2)).to eql(false) end end context 'for Callable, such that' do it 'Callable is not assignable to any disjunct type' do t = PCallableType::DEFAULT tested_types = all_types - [ PCallableType, PAnyType, POptionalType, PNotUndefType] tested_types.each {|t2| expect(t).to_not be_assignable_to(t2::DEFAULT) } end it 'a callable with parameter is assignable to the default callable' do expect(callable_t(string_t)).to be_assignable_to(PCallableType::DEFAULT) end it 'the default callable is not assignable to a callable with parameter' do expect(PCallableType::DEFAULT).not_to be_assignable_to(callable_t(string_t)) end it 'a callable with a return type is assignable to the default callable' do expect(callable_t([], string_t)).to be_assignable_to(PCallableType::DEFAULT) end it 'the default callable is not assignable to a callable with a return type' do expect(PCallableType::DEFAULT).not_to be_assignable_to(callable_t([],string_t)) end it 'a callable with a return type Any is assignable to the default callable' do expect(callable_t([], any_t)).to be_assignable_to(PCallableType::DEFAULT) end it 'a callable with a return type Any is equal to a callable with the same parameters and no return type' do expect(callable_t([string_t], any_t)).to eql(callable_t(string_t)) end it 'a callable with a return type different than Any is not equal to a callable with the same parameters and no return type' do expect(callable_t([string_t], string_t)).not_to eql(callable_t(string_t)) end it 'a callable with a return type is assignable from another callable with an assignable return type' do expect(callable_t([], string_t)).to be_assignable_to(callable_t([], PScalarType::DEFAULT)) end it 'a callable with a return type is not assignable from another callable unless the return type is assignable' do expect(callable_t([], string_t)).not_to be_assignable_to(callable_t([], integer_t)) end end it 'should recognize mapped ruby types' do { Integer => PIntegerType::DEFAULT, Fixnum => PIntegerType::DEFAULT, Bignum => PIntegerType::DEFAULT, Float => PFloatType::DEFAULT, Numeric => PNumericType::DEFAULT, NilClass => PUndefType::DEFAULT, TrueClass => PBooleanType::DEFAULT, FalseClass => PBooleanType::DEFAULT, String => PStringType::DEFAULT, Regexp => PRegexpType::DEFAULT, Regexp => PRegexpType::DEFAULT, Array => TypeFactory.array_of_any, Hash => TypeFactory.hash_of_any }.each do |ruby_type, puppet_type | expect(ruby_type).to be_assignable_to(puppet_type) end end context 'when dealing with integer ranges' do it 'should accept an equal range' do expect(calculator.assignable?(range_t(2,5), range_t(2,5))).to eq(true) end it 'should accept a narrower range' do expect(calculator.assignable?(range_t(2,10), range_t(3,5))).to eq(true) end it 'should reject a wider range' do expect(calculator.assignable?(range_t(3,5), range_t(2,10))).to eq(false) end it 'should reject a partially overlapping range' do expect(calculator.assignable?(range_t(3,5), range_t(2,4))).to eq(false) expect(calculator.assignable?(range_t(3,5), range_t(4,6))).to eq(false) end end context 'when dealing with patterns' do it 'should accept a string matching a pattern' do p_t = pattern_t('abc') p_s = string_t('XabcY') expect(calculator.assignable?(p_t, p_s)).to eq(true) end it 'should accept a regexp matching a pattern' do p_t = pattern_t(/abc/) p_s = string_t('XabcY') expect(calculator.assignable?(p_t, p_s)).to eq(true) end it 'should accept a pattern matching a pattern' do p_t = pattern_t(pattern_t('abc')) p_s = string_t('XabcY') expect(calculator.assignable?(p_t, p_s)).to eq(true) end it 'should accept a regexp matching a pattern' do p_t = pattern_t(regexp_t('abc')) p_s = string_t('XabcY') expect(calculator.assignable?(p_t, p_s)).to eq(true) end it 'should accept a string matching all patterns' do p_t = pattern_t('abc', 'ab', 'c') p_s = string_t('XabcY') expect(calculator.assignable?(p_t, p_s)).to eq(true) end it 'should accept multiple strings if they all match any patterns' do p_t = pattern_t('X', 'Y', 'abc') p_s = enum_t('Xa', 'aY', 'abc') expect(calculator.assignable?(p_t, p_s)).to eq(true) end it 'should reject a string not matching any patterns' do p_t = pattern_t('abc', 'ab', 'c') p_s = string_t('XqqqY') expect(calculator.assignable?(p_t, p_s)).to eq(false) end it 'should reject multiple strings if not all match any patterns' do p_t = pattern_t('abc', 'ab', 'c', 'q') p_s = enum_t('X', 'Y', 'Z') expect(calculator.assignable?(p_t, p_s)).to eq(false) end it 'should accept enum matching patterns as instanceof' do enum = enum_t('XS', 'S', 'M', 'L' 'XL', 'XXL') pattern = pattern_t('S', 'M', 'L') expect(calculator.assignable?(pattern, enum)).to eq(true) end it 'pattern should accept a variant where all variants are acceptable' do pattern = pattern_t(/^\w+$/) expect(calculator.assignable?(pattern, variant_t(string_t('a'), string_t('b')))).to eq(true) end it 'pattern representing all patterns should accept any pattern' do expect(calculator.assignable?(pattern_t, pattern_t('a'))).to eq(true) expect(calculator.assignable?(pattern_t, pattern_t)).to eq(true) end it 'pattern representing all patterns should accept any enum' do expect(calculator.assignable?(pattern_t, enum_t('a'))).to eq(true) expect(calculator.assignable?(pattern_t, enum_t)).to eq(true) end it 'pattern representing all patterns should accept any string' do expect(calculator.assignable?(pattern_t, string_t('a'))).to eq(true) expect(calculator.assignable?(pattern_t, string_t)).to eq(true) end end context 'when dealing with enums' do it 'should accept a string with matching content' do expect(calculator.assignable?(enum_t('a', 'b'), string_t('a'))).to eq(true) expect(calculator.assignable?(enum_t('a', 'b'), string_t('b'))).to eq(true) expect(calculator.assignable?(enum_t('a', 'b'), string_t('c'))).to eq(false) end it 'should accept an enum with matching enum' do expect(calculator.assignable?(enum_t('a', 'b'), enum_t('a', 'b'))).to eq(true) expect(calculator.assignable?(enum_t('a', 'b'), enum_t('a'))).to eq(true) expect(calculator.assignable?(enum_t('a', 'b'), enum_t('c'))).to eq(false) end it 'non parameterized enum accepts any other enum but not the reverse' do expect(calculator.assignable?(enum_t, enum_t('a'))).to eq(true) expect(calculator.assignable?(enum_t('a'), enum_t)).to eq(false) end it 'enum should accept a variant where all variants are acceptable' do enum = enum_t('a', 'b') expect(calculator.assignable?(enum, variant_t(string_t('a'), string_t('b')))).to eq(true) end end context 'when dealing with string and enum combinations' do it 'should accept assigning any enum to unrestricted string' do expect(calculator.assignable?(string_t, enum_t('blue'))).to eq(true) expect(calculator.assignable?(string_t, enum_t('blue', 'red'))).to eq(true) end it 'should not accept assigning longer enum value to size restricted string' do expect(calculator.assignable?(constrained_string_t(range_t(2,2)), enum_t('a','blue'))).to eq(false) end it 'should accept assigning any string to empty enum' do expect(calculator.assignable?(enum_t, string_t)).to eq(true) end it 'should accept assigning empty enum to any string' do expect(calculator.assignable?(string_t, enum_t)).to eq(true) end it 'should not accept assigning empty enum to size constrained string' do expect(calculator.assignable?(constrained_string_t(range_t(2,2)), enum_t)).to eq(false) end end context 'when dealing with string/pattern/enum combinations' do it 'any string is equal to any enum is equal to any pattern' do expect(calculator.assignable?(string_t, enum_t)).to eq(true) expect(calculator.assignable?(string_t, pattern_t)).to eq(true) expect(calculator.assignable?(enum_t, string_t)).to eq(true) expect(calculator.assignable?(enum_t, pattern_t)).to eq(true) expect(calculator.assignable?(pattern_t, string_t)).to eq(true) expect(calculator.assignable?(pattern_t, enum_t)).to eq(true) end end context 'when dealing with tuples' do it 'matches empty tuples' do tuple1 = tuple_t tuple2 = tuple_t expect(calculator.assignable?(tuple1, tuple2)).to eq(true) expect(calculator.assignable?(tuple2, tuple1)).to eq(true) end it 'accepts an empty tuple as assignable to a tuple with a min size of 0' do tuple1 = constrained_tuple_t(range_t(0, :default)) tuple2 = tuple_t() expect(calculator.assignable?(tuple1, tuple2)).to eq(true) expect(calculator.assignable?(tuple2, tuple1)).to eq(true) end it 'should accept matching tuples' do tuple1 = tuple_t(1,2) tuple2 = tuple_t(Integer,Integer) expect(calculator.assignable?(tuple1, tuple2)).to eq(true) expect(calculator.assignable?(tuple2, tuple1)).to eq(true) end it 'should accept matching tuples where one is more general than the other' do tuple1 = tuple_t(1,2) tuple2 = tuple_t(Numeric,Numeric) expect(calculator.assignable?(tuple1, tuple2)).to eq(false) expect(calculator.assignable?(tuple2, tuple1)).to eq(true) end it 'should accept ranged tuples' do tuple1 = constrained_tuple_t(range_t(5,5), 1) tuple2 = tuple_t(Integer,Integer, Integer, Integer, Integer) expect(calculator.assignable?(tuple1, tuple2)).to eq(true) expect(calculator.assignable?(tuple2, tuple1)).to eq(true) end it 'should reject ranged tuples when ranges does not match' do tuple1 = constrained_tuple_t(range_t(4, 5), 1) tuple2 = tuple_t(Integer,Integer, Integer, Integer, Integer) expect(calculator.assignable?(tuple1, tuple2)).to eq(true) expect(calculator.assignable?(tuple2, tuple1)).to eq(false) end it 'should reject ranged tuples when ranges does not match (using infinite upper bound)' do tuple1 = constrained_tuple_t(range_t(4, :default), 1) tuple2 = tuple_t(Integer,Integer, Integer, Integer, Integer) expect(calculator.assignable?(tuple1, tuple2)).to eq(true) expect(calculator.assignable?(tuple2, tuple1)).to eq(false) end it 'should accept matching tuples with optional entries by repeating last' do tuple1 = constrained_tuple_t(range_t(0, :default), 1,2) tuple2 = constrained_tuple_t(range_t(0, :default), Numeric,Numeric) expect(calculator.assignable?(tuple1, tuple2)).to eq(false) expect(calculator.assignable?(tuple2, tuple1)).to eq(true) end it 'should accept matching tuples with optional entries' do tuple1 = constrained_tuple_t(range_t(1, 3), Integer, Integer, String) array2 = array_t(Integer, range_t(2,2)) expect(calculator.assignable?(tuple1, array2)).to eq(true) tuple1 = constrained_tuple_t(range_t(3, 3), tuple1.types) expect(calculator.assignable?(tuple1, array2)).to eq(false) end it 'should accept matching array' do tuple1 = tuple_t(1,2) array = array_t(Integer, range_t(2, 2)) expect(calculator.assignable?(tuple1, array)).to eq(true) expect(calculator.assignable?(array, tuple1)).to eq(true) end it 'should accept empty array when tuple allows min of 0' do tuple1 = constrained_tuple_t(range_t(0, 1), Integer) array = array_t(unit_t, range_t(0, 0)) expect(calculator.assignable?(tuple1, array)).to eq(true) expect(calculator.assignable?(array, tuple1)).to eq(false) end end context 'when dealing with structs' do it 'should accept matching structs' do struct1 = struct_t({'a'=>Integer, 'b'=>Integer}) struct2 = struct_t({'a'=>Integer, 'b'=>Integer}) expect(calculator.assignable?(struct1, struct2)).to eq(true) expect(calculator.assignable?(struct2, struct1)).to eq(true) end it 'should accept matching structs with less elements when unmatched elements are optional' do struct1 = struct_t({'a'=>Integer, 'b'=>Integer, 'c'=>optional_t(Integer)}) struct2 = struct_t({'a'=>Integer, 'b'=>Integer}) expect(calculator.assignable?(struct1, struct2)).to eq(true) end it 'should reject matching structs with more elements even if excess elements are optional' do struct1 = struct_t({'a'=>Integer, 'b'=>Integer}) struct2 = struct_t({'a'=>Integer, 'b'=>Integer, 'c'=>optional_t(Integer)}) expect(calculator.assignable?(struct1, struct2)).to eq(false) end it 'should accept matching structs where one is more general than the other with respect to optional' do struct1 = struct_t({'a'=>Integer, 'b'=>Integer, 'c'=>optional_t(Integer)}) struct2 = struct_t({'a'=>Integer, 'b'=>Integer, 'c'=>Integer}) expect(calculator.assignable?(struct1, struct2)).to eq(true) end it 'should reject matching structs where one is more special than the other with respect to optional' do struct1 = struct_t({'a'=>Integer, 'b'=>Integer, 'c'=>Integer}) struct2 = struct_t({'a'=>Integer, 'b'=>Integer, 'c'=>optional_t(Integer)}) expect(calculator.assignable?(struct1, struct2)).to eq(false) end it 'should accept matching structs where one is more general than the other' do struct1 = struct_t({'a'=>Integer, 'b'=>Integer}) struct2 = struct_t({'a'=>Numeric, 'b'=>Numeric}) expect(calculator.assignable?(struct1, struct2)).to eq(false) expect(calculator.assignable?(struct2, struct1)).to eq(true) end it 'should accept matching hash' do struct1 = struct_t({'a'=>Integer, 'b'=>Integer}) non_empty_string = constrained_string_t(range_t(1, nil)) hsh = hash_t(non_empty_string, Integer, range_t(2,2)) expect(calculator.assignable?(struct1, hsh)).to eq(true) expect(calculator.assignable?(hsh, struct1)).to eq(true) end it 'should accept empty hash with key_type unit' do struct1 = struct_t({'a'=>optional_t(Integer)}) hsh = hash_t(unit_t, unit_t, range_t(0, 0)) expect(calculator.assignable?(struct1, hsh)).to eq(true) end end it 'should recognize ruby type inheritance' do class Foo end class Bar < Foo end fooType = calculator.infer(Foo.new) barType = calculator.infer(Bar.new) expect(calculator.assignable?(fooType, fooType)).to eq(true) expect(calculator.assignable?(Foo, fooType)).to eq(true) expect(calculator.assignable?(fooType, barType)).to eq(true) expect(calculator.assignable?(Foo, barType)).to eq(true) expect(calculator.assignable?(barType, fooType)).to eq(false) expect(calculator.assignable?(Bar, fooType)).to eq(false) end it 'should allow host class with same name' do hc1 = TypeFactory.host_class('the_name') hc2 = TypeFactory.host_class('the_name') expect(calculator.assignable?(hc1, hc2)).to eq(true) end it 'should allow host class with name assigned to hostclass without name' do hc1 = TypeFactory.host_class hc2 = TypeFactory.host_class('the_name') expect(calculator.assignable?(hc1, hc2)).to eq(true) end it 'should reject host classes with different names' do hc1 = TypeFactory.host_class('the_name') hc2 = TypeFactory.host_class('another_name') expect(calculator.assignable?(hc1, hc2)).to eq(false) end it 'should reject host classes without name assigned to host class with name' do hc1 = TypeFactory.host_class('the_name') hc2 = TypeFactory.host_class expect(calculator.assignable?(hc1, hc2)).to eq(false) end it 'should allow resource with same type_name and title' do r1 = TypeFactory.resource('file', 'foo') r2 = TypeFactory.resource('file', 'foo') expect(calculator.assignable?(r1, r2)).to eq(true) end it 'should allow more specific resource assignment' do r1 = TypeFactory.resource r2 = TypeFactory.resource('file') expect(calculator.assignable?(r1, r2)).to eq(true) r2 = TypeFactory.resource('file', '/tmp/foo') expect(calculator.assignable?(r1, r2)).to eq(true) r1 = TypeFactory.resource('file') expect(calculator.assignable?(r1, r2)).to eq(true) end it 'should reject less specific resource assignment' do r1 = TypeFactory.resource('file', '/tmp/foo') r2 = TypeFactory.resource('file') expect(calculator.assignable?(r1, r2)).to eq(false) r2 = TypeFactory.resource expect(calculator.assignable?(r1, r2)).to eq(false) end context 'for TypeAlias, such that' do let!(:parser) { TypeParser.singleton } it 'it is assignable to the type that it is an alias for' do t = type_alias_t('Alias', 'Integer').resolve(nil) expect(calculator.assignable?(integer_t, t)).to be_truthy end it 'the type that it is an alias for is assignable to it' do t = type_alias_t('Alias', 'Integer').resolve(nil) expect(calculator.assignable?(t, integer_t)).to be_truthy end it 'a recursive alias can be assignable from a conformant type with any depth' do scope = Object.new t = type_alias_t('Tree', 'Hash[String,Variant[String,Tree]]') loader = Object.new loader.expects(:load).with(:type, 'tree').returns t Adapters::LoaderAdapter.expects(:loader_for_model_object).at_least_once.returns loader t.resolve(scope) expect(calculator.assignable?(t, parser.parse('Hash[String,Variant[String,Hash[String,Variant[String,String]]]]'))).to be_truthy end it 'similar recursive aliases are assignable' do scope = Object.new t1 = type_alias_t('Tree1', 'Hash[String,Variant[String,Tree1]]') t2 = type_alias_t('Tree2', 'Hash[String,Variant[String,Tree2]]') loader = Object.new loader.expects(:load).with(:type, 'tree1').returns t1 loader.expects(:load).with(:type, 'tree2').returns t2 Adapters::LoaderAdapter.expects(:loader_for_model_object).at_least_once.returns loader t1.resolve(scope) t2.resolve(scope) expect(calculator.assignable?(t1, t2)).to be_truthy end it 'crossing recursive aliases are assignable' do t1 = type_alias_t('Tree1', 'Hash[String,Variant[String,Tree2]]') t2 = type_alias_t('Tree2', 'Hash[String,Variant[String,Tree1]]') loader = Object.new loader.expects(:load).with(:type, 'tree1').returns t1 loader.expects(:load).with(:type, 'tree2').returns t2 Adapters::LoaderAdapter.expects(:loader_for_model_object).at_least_once.returns loader t1.resolve(loader) t2.resolve(loader) expect(calculator.assignable?(t1, t2)).to be_truthy end it 'Type[T] is assignable to Type[AT] when AT is an alias for T' do scope = Object.new ta = type_alias_t('PositiveInteger', 'Integer[0,default]') loader = Object.new loader.expects(:load).with(:type, 'positiveinteger').returns ta Adapters::LoaderAdapter.expects(:loader_for_model_object).at_least_once.returns loader t1 = type_t(range_t(0, :default)) t2 = parser.parse('Type[PositiveInteger]', scope) expect(calculator.assignable?(t2, t1)).to be_truthy end it 'Type[T] is assignable to AT when AT is an alias for Type[T]' do scope = Object.new ta = type_alias_t('PositiveIntegerType', 'Type[Integer[0,default]]') loader = Object.new loader.expects(:load).with(:type, 'positiveintegertype').returns ta Adapters::LoaderAdapter.expects(:loader_for_model_object).at_least_once.returns loader t1 = type_t(range_t(0, :default)) t2 = parser.parse('PositiveIntegerType', scope) expect(calculator.assignable?(t2, t1)).to be_truthy end it 'Type[Type[T]] is assignable to Type[Type[AT]] when AT is an alias for T' do scope = Object.new ta = type_alias_t('PositiveInteger', 'Integer[0,default]') loader = Object.new loader.expects(:load).with(:type, 'positiveinteger').returns ta Adapters::LoaderAdapter.expects(:loader_for_model_object).at_least_once.returns loader t1 = type_t(type_t(range_t(0, :default))) t2 = parser.parse('Type[Type[PositiveInteger]]', scope) expect(calculator.assignable?(t2, t1)).to be_truthy end it 'Type[Type[T]] is assignable to Type[AT] when AT is an alias for Type[T]' do scope = Object.new ta = type_alias_t('PositiveIntegerType', 'Type[Integer[0,default]]') loader = Object.new loader.expects(:load).with(:type, 'positiveintegertype').returns ta Adapters::LoaderAdapter.expects(:loader_for_model_object).at_least_once.returns loader t1 = type_t(type_t(range_t(0, :default))) t2 = parser.parse('Type[PositiveIntegerType]', scope) expect(calculator.assignable?(t2, t1)).to be_truthy end it 'An alias for a Type that describes an Iterable instance is assignable to Iterable' do t = type_alias_t('MyType', 'Enum[a,b]').resolve(nil) # True because String is iterable and an instance of Enum is a String expect(calculator.assignable?(iterable_t, t)).to be_truthy end end end context 'when testing if x is instance of type t' do include_context 'types_setup' it 'should consider undef to be instance of Any, NilType, and optional' do expect(calculator.instance?(PUndefType::DEFAULT, nil)).to eq(true) expect(calculator.instance?(PAnyType::DEFAULT, nil)).to eq(true) expect(calculator.instance?(POptionalType::DEFAULT, nil)).to eq(true) end it 'all types should be (ruby) instance of PAnyType' do all_types.each do |t| expect(t::DEFAULT.is_a?(PAnyType)).to eq(true) end end it "should infer :undef to be Undef" do expect(calculator.infer(:undef)).to be_assignable_to(undef_t) end it "should not consider :default to be instance of Runtime['ruby', 'Symbol]" do expect(calculator.instance?(PRuntimeType.new(:ruby, 'Symbol'), :default)).to eq(false) end it "should not consider :undef to be instance of Runtime['ruby', 'Symbol]" do expect(calculator.instance?(PRuntimeType.new(:ruby, 'Symbol'), :undef)).to eq(false) end it 'should consider :undef to be instance of an Optional type' do expect(calculator.instance?(POptionalType::DEFAULT, :undef)).to eq(true) end it 'should not consider undef to be an instance of any other type than Any, Undef, Optional, and Init' do types_to_test = all_types - [ PAnyType, PUndefType, POptionalType, PInitType ] types_to_test.each {|t| expect(calculator.instance?(t::DEFAULT, nil)).to eq(false) } types_to_test.each {|t| expect(calculator.instance?(t::DEFAULT, :undef)).to eq(false) } end it 'should consider default to be instance of Default and Any' do expect(calculator.instance?(PDefaultType::DEFAULT, :default)).to eq(true) expect(calculator.instance?(PAnyType::DEFAULT, :default)).to eq(true) end it 'should not consider "default" to be an instance of anything but Default, Init, NotUndef, and Any' do types_to_test = all_types - [ PAnyType, PNotUndefType, PDefaultType, PInitType, ] types_to_test.each {|t| expect(calculator.instance?(t::DEFAULT, :default)).to eq(false) } end it 'should consider integer instanceof PIntegerType' do expect(calculator.instance?(PIntegerType::DEFAULT, 1)).to eq(true) end it 'should consider integer instanceof Integer' do expect(calculator.instance?(Integer, 1)).to eq(true) end it 'should consider integer in range' do range = range_t(0,10) expect(calculator.instance?(range, 1)).to eq(true) expect(calculator.instance?(range, 10)).to eq(true) expect(calculator.instance?(range, -1)).to eq(false) expect(calculator.instance?(range, 11)).to eq(false) end it 'should consider string in length range' do range = constrained_string_t(range_t(1,3)) expect(calculator.instance?(range, 'a')).to eq(true) expect(calculator.instance?(range, 'abc')).to eq(true) expect(calculator.instance?(range, '')).to eq(false) expect(calculator.instance?(range, 'abcd')).to eq(false) end it 'should consider string values' do string = string_t('a') expect(calculator.instance?(string, 'a')).to eq(true) expect(calculator.instance?(string, 'c')).to eq(false) end it 'should consider array in length range' do range = array_t(integer_t, range_t(1,3)) expect(calculator.instance?(range, [1])).to eq(true) expect(calculator.instance?(range, [1,2,3])).to eq(true) expect(calculator.instance?(range, [])).to eq(false) expect(calculator.instance?(range, [1,2,3,4])).to eq(false) end it 'should consider hash in length range' do range = hash_t(integer_t, integer_t, range_t(1,2)) expect(calculator.instance?(range, {1=>1})).to eq(true) expect(calculator.instance?(range, {1=>1, 2=>2})).to eq(true) expect(calculator.instance?(range, {})).to eq(false) expect(calculator.instance?(range, {1=>1, 2=>2, 3=>3})).to eq(false) end it 'should consider collection in length range for array ' do range = collection_t(range_t(1,3)) expect(calculator.instance?(range, [1])).to eq(true) expect(calculator.instance?(range, [1,2,3])).to eq(true) expect(calculator.instance?(range, [])).to eq(false) expect(calculator.instance?(range, [1,2,3,4])).to eq(false) end it 'should consider collection in length range for hash' do range = collection_t(range_t(1,2)) expect(calculator.instance?(range, {1=>1})).to eq(true) expect(calculator.instance?(range, {1=>1, 2=>2})).to eq(true) expect(calculator.instance?(range, {})).to eq(false) expect(calculator.instance?(range, {1=>1, 2=>2, 3=>3})).to eq(false) end it 'should consider string matching enum as instanceof' do enum = enum_t('XS', 'S', 'M', 'L', 'XL', '0') expect(calculator.instance?(enum, 'XS')).to eq(true) expect(calculator.instance?(enum, 'S')).to eq(true) expect(calculator.instance?(enum, 'XXL')).to eq(false) expect(calculator.instance?(enum, '')).to eq(false) expect(calculator.instance?(enum, '0')).to eq(true) expect(calculator.instance?(enum, 0)).to eq(false) end it 'should consider array[string] as instance of Array[Enum] when strings are instance of Enum' do enum = enum_t('XS', 'S', 'M', 'L', 'XL', '0') array = array_t(enum) expect(calculator.instance?(array, ['XS', 'S', 'XL'])).to eq(true) expect(calculator.instance?(array, ['XS', 'S', 'XXL'])).to eq(false) end it 'should consider array[mixed] as instance of Variant[mixed] when mixed types are listed in Variant' do enum = enum_t('XS', 'S', 'M', 'L', 'XL') sizes = range_t(30, 50) array = array_t(variant_t(enum, sizes)) expect(calculator.instance?(array, ['XS', 'S', 30, 50])).to eq(true) expect(calculator.instance?(array, ['XS', 'S', 'XXL'])).to eq(false) expect(calculator.instance?(array, ['XS', 'S', 29])).to eq(false) end it 'should consider array[seq] as instance of Tuple[seq] when elements of seq are instance of' do tuple = tuple_t(Integer, String, Float) expect(calculator.instance?(tuple, [1, 'a', 3.14])).to eq(true) expect(calculator.instance?(tuple, [1.2, 'a', 3.14])).to eq(false) expect(calculator.instance?(tuple, [1, 1, 3.14])).to eq(false) expect(calculator.instance?(tuple, [1, 'a', 1])).to eq(false) end context 'and t is Struct' do it 'should consider hash[cont] as instance of Struct[cont-t]' do struct = struct_t({'a'=>Integer, 'b'=>String, 'c'=>Float}) expect(calculator.instance?(struct, {'a'=>1, 'b'=>'a', 'c'=>3.14})).to eq(true) expect(calculator.instance?(struct, {'a'=>1.2, 'b'=>'a', 'c'=>3.14})).to eq(false) expect(calculator.instance?(struct, {'a'=>1, 'b'=>1, 'c'=>3.14})).to eq(false) expect(calculator.instance?(struct, {'a'=>1, 'b'=>'a', 'c'=>1})).to eq(false) end it 'should consider empty hash as instance of Struct[x=>Optional[String]]' do struct = struct_t({'a'=>optional_t(String)}) expect(calculator.instance?(struct, {})).to eq(true) end it 'should consider hash[cont] as instance of Struct[cont-t,optionals]' do struct = struct_t({'a'=>Integer, 'b'=>String, 'c'=>optional_t(Float)}) expect(calculator.instance?(struct, {'a'=>1, 'b'=>'a'})).to eq(true) end it 'should consider hash[cont] as instance of Struct[cont-t,variants with optionals]' do struct = struct_t({'a'=>Integer, 'b'=>String, 'c'=>variant_t(String, optional_t(Float))}) expect(calculator.instance?(struct, {'a'=>1, 'b'=>'a'})).to eq(true) end it 'should not consider hash[cont,cont2] as instance of Struct[cont-t]' do struct = struct_t({'a'=>Integer, 'b'=>String}) expect(calculator.instance?(struct, {'a'=>1, 'b'=>'a', 'c'=>'x'})).to eq(false) end it 'should not consider hash[cont,cont2] as instance of Struct[cont-t,optional[cont3-t]' do struct = struct_t({'a'=>Integer, 'b'=>String, 'c'=>optional_t(Float)}) expect(calculator.instance?(struct, {'a'=>1, 'b'=>'a', 'c'=>'x'})).to eq(false) end it 'should consider nil to be a valid element value' do struct = struct_t({not_undef_t('a') => any_t, 'b'=>String}) expect(calculator.instance?(struct, {'a'=>nil , 'b'=>'a'})).to eq(true) end it 'should consider nil to be a valid element value but subject to value type' do struct = struct_t({not_undef_t('a') => String, 'b'=>String}) expect(calculator.instance?(struct, {'a'=>nil , 'b'=>'a'})).to eq(false) end it 'should consider nil to be a valid element value but subject to value type even when key is optional' do struct = struct_t({optional_t('a') => String, 'b'=>String}) expect(calculator.instance?(struct, {'a'=>nil , 'b'=>'a'})).to eq(false) end it 'should consider a hash where optional key is missing as assignable even if value of optional key is required' do struct = struct_t({optional_t('a') => String, 'b'=>String}) expect(calculator.instance?(struct, {'b'=>'a'})).to eq(true) end end context 'and t is Data' do it 'undef should be considered instance of Data' do expect(calculator.instance?(data_t, nil)).to eq(true) end it 'other symbols should not be considered instance of Data' do expect(calculator.instance?(data_t, :love)).to eq(false) end it 'an empty array should be considered instance of Data' do expect(calculator.instance?(data_t, [])).to eq(true) end it 'an empty hash should be considered instance of Data' do expect(calculator.instance?(data_t, {})).to eq(true) end it 'a hash with nil/undef data should be considered instance of Data' do expect(calculator.instance?(data_t, {'a' => nil})).to eq(true) end it 'a hash with nil/default key should not considered instance of Data' do expect(calculator.instance?(data_t, {nil => 10})).to eq(false) expect(calculator.instance?(data_t, {:default => 10})).to eq(false) end it 'an array with nil entries should be considered instance of Data' do expect(calculator.instance?(data_t, [nil])).to eq(true) end it 'an array with nil + data entries should be considered instance of Data' do expect(calculator.instance?(data_t, [1, nil, 'a'])).to eq(true) end end context 'and t is something Callable' do it 'a Closure should be considered a Callable' do factory = Model::Factory params = [factory.PARAM('a')] the_block = factory.LAMBDA(params,factory.literal(42), nil).model the_closure = Evaluator::Closure::Dynamic.new(:fake_evaluator, the_block, :fake_scope) expect(calculator.instance?(all_callables_t, the_closure)).to be_truthy expect(calculator.instance?(callable_t(any_t), the_closure)).to be_truthy expect(calculator.instance?(callable_t(any_t, any_t), the_closure)).to be_falsey end it 'a Function instance should be considered a Callable' do fc = Puppet::Functions.create_function(:foo) do dispatch :foo do param 'String', :a end def foo(a) a end end f = fc.new(:closure_scope, :loader) # Any callable expect(calculator.instance?(all_callables_t, f)).to be_truthy # Callable[String] expect(calculator.instance?(callable_t(String), f)).to be_truthy end end context 'and t is a TypeAlias' do let!(:parser) { TypeParser.singleton } it 'should consider x an instance of the aliased simple type' do t = type_alias_t('Alias', 'Integer').resolve(nil) expect(calculator.instance?(t, 15)).to be_truthy end it 'should consider x an instance of the aliased parameterized type' do t = type_alias_t('Alias', 'Integer[0,20]').resolve(nil) expect(calculator.instance?(t, 15)).to be_truthy end it 'should consider t an instance of Iterable when aliased type is Iterable' do t = type_alias_t('Alias', 'Enum[a, b]').resolve(nil) expect(calculator.instance?(iterable_t, t)).to be_truthy end it 'should consider x an instance of the aliased type that uses self recursion' do t = type_alias_t('Tree', 'Hash[String,Variant[String,Tree]]') loader = Object.new loader.expects(:load).with(:type, 'tree').returns t Adapters::LoaderAdapter.expects(:loader_for_model_object).at_least_once.returns loader t.resolve(loader) expect(calculator.instance?(t, {'a'=>{'aa'=>{'aaa'=>'aaaa'}}, 'b'=>'bb'})).to be_truthy end it 'should consider x an instance of the aliased type that uses contains an alias that causes self recursion' do t1 = type_alias_t('Tree', 'Hash[String,Variant[String,OtherTree]]') t2 = type_alias_t('OtherTree', 'Hash[String,Tree]') loader = Object.new loader.expects(:load).with(:type, 'tree').returns t1 loader.expects(:load).with(:type, 'othertree').returns t2 Adapters::LoaderAdapter.expects(:loader_for_model_object).at_least_once.returns loader t1.resolve(loader) expect(calculator.instance?(t1, {'a'=>{'aa'=>{'aaa'=>'aaaa'}}, 'b'=>'bb'})).to be_truthy end end end context 'when converting a ruby class' do it 'should yield \'PIntegerType\' for Fixnum' do expect(calculator.type(Fixnum).class).to eq(PIntegerType) end it 'should yield \'PIntegerType\' for Bignum' do expect(calculator.type(Bignum).class).to eq(PIntegerType) end it 'should yield \'PIntegerType\' for Integer' do expect(calculator.type(Integer).class).to eq(PIntegerType) end it 'should yield \'PFloatType\' for Float' do expect(calculator.type(Float).class).to eq(PFloatType) end it 'should yield \'PBooleanType\' for FalseClass and TrueClass' do [FalseClass,TrueClass].each do |c| expect(calculator.type(c).class).to eq(PBooleanType) end end it 'should yield \'PUndefType\' for NilClass' do expect(calculator.type(NilClass).class).to eq(PUndefType) end it 'should yield \'PStringType\' for String' do expect(calculator.type(String).class).to eq(PStringType) end it 'should yield \'PRegexpType\' for Regexp' do expect(calculator.type(Regexp).class).to eq(PRegexpType) end it 'should yield \'PArrayType[PAnyType]\' for Array' do t = calculator.type(Array) expect(t.class).to eq(PArrayType) expect(t.element_type.class).to eq(PAnyType) end it 'should yield \'PHashType[PAnyType,PAnyType]\' for Hash' do t = calculator.type(Hash) expect(t.class).to eq(PHashType) expect(t.key_type.class).to eq(PAnyType) expect(t.value_type.class).to eq(PAnyType) end it 'type should yield \'PRuntimeType[ruby,Rational]\' for Rational' do t = calculator.type(Rational) expect(t.class).to eq(PRuntimeType) expect(t.runtime).to eq(:ruby) expect(t.runtime_type_name).to eq('Rational') end it 'infer should yield \'PRuntimeType[ruby,Rational]\' for Rational instance' do t = calculator.infer(Rational(2, 3)) expect(t.class).to eq(PRuntimeType) expect(t.runtime).to eq(:ruby) expect(t.runtime_type_name).to eq('Rational') end end context 'when processing meta type' do it 'should infer PTypeType as the type of all other types' do ptype = PTypeType expect(calculator.infer(PUndefType::DEFAULT ).is_a?(ptype)).to eq(true) expect(calculator.infer(PScalarType::DEFAULT ).is_a?(ptype)).to eq(true) expect(calculator.infer(PScalarDataType::DEFAULT).is_a?(ptype)).to eq(true) expect(calculator.infer(PStringType::DEFAULT ).is_a?(ptype)).to eq(true) expect(calculator.infer(PNumericType::DEFAULT ).is_a?(ptype)).to eq(true) expect(calculator.infer(PIntegerType::DEFAULT ).is_a?(ptype)).to eq(true) expect(calculator.infer(PFloatType::DEFAULT ).is_a?(ptype)).to eq(true) expect(calculator.infer(PRegexpType::DEFAULT ).is_a?(ptype)).to eq(true) expect(calculator.infer(PBooleanType::DEFAULT ).is_a?(ptype)).to eq(true) expect(calculator.infer(PCollectionType::DEFAULT).is_a?(ptype)).to eq(true) expect(calculator.infer(PArrayType::DEFAULT ).is_a?(ptype)).to eq(true) expect(calculator.infer(PHashType::DEFAULT ).is_a?(ptype)).to eq(true) expect(calculator.infer(PIterableType::DEFAULT ).is_a?(ptype)).to eq(true) expect(calculator.infer(PRuntimeType::DEFAULT ).is_a?(ptype)).to eq(true) expect(calculator.infer(PClassType::DEFAULT ).is_a?(ptype)).to eq(true) expect(calculator.infer(PResourceType::DEFAULT ).is_a?(ptype)).to eq(true) expect(calculator.infer(PEnumType::DEFAULT ).is_a?(ptype)).to eq(true) expect(calculator.infer(PPatternType::DEFAULT ).is_a?(ptype)).to eq(true) expect(calculator.infer(PVariantType::DEFAULT ).is_a?(ptype)).to eq(true) expect(calculator.infer(PTupleType::DEFAULT ).is_a?(ptype)).to eq(true) expect(calculator.infer(POptionalType::DEFAULT ).is_a?(ptype)).to eq(true) expect(calculator.infer(PCallableType::DEFAULT ).is_a?(ptype)).to eq(true) end it 'should infer PTypeType as the type of all other types' do expect(calculator.infer(PUndefType::DEFAULT ).to_s).to eq('Type[Undef]') expect(calculator.infer(PScalarType::DEFAULT ).to_s).to eq('Type[Scalar]') expect(calculator.infer(PScalarDataType::DEFAULT).to_s).to eq('Type[ScalarData]') expect(calculator.infer(PStringType::DEFAULT ).to_s).to eq('Type[String]') expect(calculator.infer(PNumericType::DEFAULT ).to_s).to eq('Type[Numeric]') expect(calculator.infer(PIntegerType::DEFAULT ).to_s).to eq('Type[Integer]') expect(calculator.infer(PFloatType::DEFAULT ).to_s).to eq('Type[Float]') expect(calculator.infer(PRegexpType::DEFAULT ).to_s).to eq('Type[Regexp]') expect(calculator.infer(PBooleanType::DEFAULT ).to_s).to eq('Type[Boolean]') expect(calculator.infer(PCollectionType::DEFAULT).to_s).to eq('Type[Collection]') expect(calculator.infer(PArrayType::DEFAULT ).to_s).to eq('Type[Array]') expect(calculator.infer(PHashType::DEFAULT ).to_s).to eq('Type[Hash]') expect(calculator.infer(PIterableType::DEFAULT ).to_s).to eq('Type[Iterable]') expect(calculator.infer(PRuntimeType::DEFAULT ).to_s).to eq('Type[Runtime]') expect(calculator.infer(PClassType::DEFAULT ).to_s).to eq('Type[Class]') expect(calculator.infer(PResourceType::DEFAULT ).to_s).to eq('Type[Resource]') expect(calculator.infer(PEnumType::DEFAULT ).to_s).to eq('Type[Enum]') expect(calculator.infer(PVariantType::DEFAULT ).to_s).to eq('Type[Variant]') expect(calculator.infer(PPatternType::DEFAULT ).to_s).to eq('Type[Pattern]') expect(calculator.infer(PTupleType::DEFAULT ).to_s).to eq('Type[Tuple]') expect(calculator.infer(POptionalType::DEFAULT ).to_s).to eq('Type[Optional]') expect(calculator.infer(PCallableType::DEFAULT ).to_s).to eq('Type[Callable]') expect(calculator.infer(PResourceType.new('foo::fee::fum')).to_s).to eq('Type[Foo::Fee::Fum]') expect(calculator.infer(PResourceType.new('foo::fee::fum')).to_s).to eq('Type[Foo::Fee::Fum]') expect(calculator.infer(PResourceType.new('Foo::Fee::Fum')).to_s).to eq('Type[Foo::Fee::Fum]') end it "computes the common type of PTypeType's type parameter" do int_t = PIntegerType::DEFAULT string_t = PStringType::DEFAULT expect(calculator.infer([int_t]).to_s).to eq('Array[Type[Integer], 1, 1]') expect(calculator.infer([int_t, string_t]).to_s).to eq('Array[Type[ScalarData], 2, 2]') end it 'should infer PTypeType as the type of ruby classes' do class Foo end [Object, Numeric, Integer, Fixnum, Bignum, Float, String, Regexp, Array, Hash, Foo].each do |c| expect(calculator.infer(c).is_a?(PTypeType)).to eq(true) end end it 'should infer PTypeType as the type of PTypeType (meta regression short-circuit)' do expect(calculator.infer(PTypeType::DEFAULT).is_a?(PTypeType)).to eq(true) end it 'computes instance? to be true if parameterized and type match' do int_t = PIntegerType::DEFAULT type_t = TypeFactory.type_type(int_t) type_type_t = TypeFactory.type_type(type_t) expect(calculator.instance?(type_type_t, type_t)).to eq(true) end it 'computes instance? to be false if parameterized and type do not match' do int_t = PIntegerType::DEFAULT string_t = PStringType::DEFAULT type_t = TypeFactory.type_type(int_t) type_t2 = TypeFactory.type_type(string_t) type_type_t = TypeFactory.type_type(type_t) # i.e. Type[Integer] =~ Type[Type[Integer]] # false expect(calculator.instance?(type_type_t, type_t2)).to eq(false) end it 'computes instance? to be true if unparameterized and matched against a type[?]' do int_t = PIntegerType::DEFAULT type_t = TypeFactory.type_type(int_t) expect(calculator.instance?(PTypeType::DEFAULT, type_t)).to eq(true) end end context 'when asking for an iterable ' do it 'should produce an iterable for an Integer range that is finite' do t = PIntegerType.new(1, 10) expect(calculator.iterable(t).respond_to?(:each)).to eq(true) end it 'should not produce an iterable for an Integer range that has an infinite side' do t = PIntegerType.new(nil, 10) expect(calculator.iterable(t)).to eq(nil) t = PIntegerType.new(1, nil) expect(calculator.iterable(t)).to eq(nil) end it 'all but Integer range are not iterable' do [Object, Numeric, Float, String, Regexp, Array, Hash].each do |t| expect(calculator.iterable(calculator.type(t))).to eq(nil) end end it 'should produce an iterable for a type alias of an Iterable type' do t = PTypeAliasType.new('MyAlias', nil, PIntegerType.new(1, 10)) expect(calculator.iterable(t).respond_to?(:each)).to eq(true) end end context 'when dealing with different types of inference' do it 'an instance specific inference is produced by infer' do expect(calculator.infer(['a','b']).element_type.values).to eq(['a', 'b']) end it 'a generic inference is produced using infer_generic' do expect(calculator.infer_generic(['a','b']).element_type).to eql(string_t(range_t(1,1))) end it 'a generic result is created by generalize given an instance specific result for an Array' do generic = calculator.infer(['a','b']) expect(generic.element_type.values).to eq(['a','b']) generic = generic.generalize expect(generic.element_type).to eql(string_t(range_t(1,1))) end it 'a generic result is created by generalize given an instance specific result for a Hash' do generic = calculator.infer({'a' =>1,'bcd' => 2}) expect(generic.key_type.values.sort).to eq(['a', 'bcd']) expect(generic.value_type.from).to eq(1) expect(generic.value_type.to).to eq(2) generic = generic.generalize expect(generic.key_type.size_type).to eq(range_t(1,3)) expect(generic.value_type.from).to eq(nil) expect(generic.value_type.to).to eq(nil) end it 'ensures that Struct key types are not generalized' do generic = struct_t({'a' => any_t}).generalize expect(generic.to_s).to eq("Struct[{'a' => Any}]") generic = struct_t({not_undef_t('a') => any_t}).generalize expect(generic.to_s).to eq("Struct[{NotUndef['a'] => Any}]") generic = struct_t({optional_t('a') => string_t}).generalize expect(generic.to_s).to eq("Struct[{Optional['a'] => String}]") end it 'ensures that Struct value types are generalized' do generic = struct_t({'a' => range_t(1, 3)}).generalize expect(generic.to_s).to eq("Struct[{'a' => Integer}]") end it "does not reduce by combining types when using infer_set" do element_type = calculator.infer(['a','b',1,2]).element_type expect(element_type.class).to eq(PScalarDataType) inferred_type = calculator.infer_set(['a','b',1,2]) expect(inferred_type.class).to eq(PTupleType) element_types = inferred_type.types expect(element_types[0].class).to eq(PStringType) expect(element_types[1].class).to eq(PStringType) expect(element_types[2].class).to eq(PIntegerType) expect(element_types[3].class).to eq(PIntegerType) end it 'does not reduce by combining types when using infer_set and values are undef' do element_type = calculator.infer(['a',nil]).element_type expect(element_type.class).to eq(PStringType) inferred_type = calculator.infer_set(['a',nil]) expect(inferred_type.class).to eq(PTupleType) element_types = inferred_type.types expect(element_types[0].class).to eq(PStringType) expect(element_types[1].class).to eq(PUndefType) end it 'infers on an empty Array produces Array[Unit,0,0]' do inferred_type = calculator.infer([]) expect(inferred_type.element_type.class).to eq(PUnitType) expect(inferred_type.size_range).to eq([0, 0]) end it 'infer_set on an empty Array produces Array[Unit,0,0]' do inferred_type = calculator.infer_set([]) expect(inferred_type.element_type.class).to eq(PUnitType) expect(inferred_type.size_range).to eq([0, 0]) end end context 'when determening callability' do context 'and given is exact' do it 'with callable' do required = callable_t(string_t) given = callable_t(string_t) expect(calculator.callable?(required, given)).to eq(true) end it 'with args tuple' do required = callable_t(string_t) given = tuple_t(string_t) expect(calculator.callable?(required, given)).to eq(true) end it 'with args tuple having a block' do required = callable_t(string_t, callable_t(string_t)) given = tuple_t(string_t, callable_t(string_t)) expect(calculator.callable?(required, given)).to eq(true) end it 'with args array' do required = callable_t(string_t) given = array_t(string_t, range_t(1, 1)) expect(calculator.callable?(required, given)).to eq(true) end end context 'and given is more generic' do it 'with callable' do required = callable_t(string_t) given = callable_t(any_t) expect(calculator.callable?(required, given)).to eq(true) end it 'with args tuple' do required = callable_t(string_t) given = tuple_t(any_t) expect(calculator.callable?(required, given)).to eq(false) end it 'with args tuple having a block' do required = callable_t(string_t, callable_t(string_t)) given = tuple_t(string_t, callable_t(any_t)) expect(calculator.callable?(required, given)).to eq(true) end it 'with args tuple having a block with captures rest' do required = callable_t(string_t, callable_t(string_t)) given = tuple_t(string_t, callable_t(any_t, 0, :default)) expect(calculator.callable?(required, given)).to eq(true) end end context 'and given is more specific' do it 'with callable' do required = callable_t(any_t) given = callable_t(string_t) expect(calculator.callable?(required, given)).to eq(false) end it 'with args tuple' do required = callable_t(any_t) given = tuple_t(string_t) expect(calculator.callable?(required, given)).to eq(true) end it 'with args tuple having a block' do required = callable_t(string_t, callable_t(any_t)) given = tuple_t(string_t, callable_t(string_t)) expect(calculator.callable?(required, given)).to eq(false) end it 'with args tuple having a block with captures rest' do required = callable_t(string_t, callable_t(any_t)) given = tuple_t(string_t, callable_t(string_t, 0, :default)) expect(calculator.callable?(required, given)).to eq(false) end end end matcher :be_assignable_to do |type| match do |actual| type.is_a?(PAnyType) && type.assignable?(actual) end failure_message do |actual| "#{TypeFormatter.string(actual)} should be assignable to #{TypeFormatter.string(type)}" end failure_message_when_negated do |actual| "#{TypeFormatter.string(actual)} is assignable to #{TypeFormatter.string(type)} when it should not" end end end end end puppet-5.5.10/spec/unit/pops/types/type_parser_spec.rb0000644005276200011600000004345113417161722022754 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' module Puppet::Pops module Types describe TypeParser do extend RSpec::Matchers::DSL let(:parser) { TypeParser.singleton } let(:types) { TypeFactory } it "rejects a puppet expression" do expect { parser.parse("1 + 1") }.to raise_error(Puppet::ParseError, /The expression <1 \+ 1> is not a valid type specification/) end it "rejects a empty type specification" do expect { parser.parse("") }.to raise_error(Puppet::ParseError, /The expression <> is not a valid type specification/) end it "rejects an invalid type simple type" do expect { parser.parse("notAType") }.to raise_error(Puppet::ParseError, /The expression is not a valid type specification/) end it "rejects an unknown parameterized type" do expect { parser.parse("notAType[Integer]") }.to raise_error(Puppet::ParseError, /The expression is not a valid type specification/) end it "rejects an unknown type parameter" do expect { parser.parse("Array[notAType]") }.to raise_error(Puppet::ParseError, /The expression is not a valid type specification/) end it "rejects an unknown type parameter in a variant" do expect { parser.parse("Variant[Integer,'not a type']") }.to raise_error(Puppet::ParseError, /The expression is not a valid type specification/) end [ 'Any', 'Data', 'CatalogEntry', 'Scalar', 'Undef', 'Numeric', 'Default' ].each do |name| it "does not support parameterizing unparameterized type <#{name}>" do expect { parser.parse("#{name}[Integer]") }.to raise_unparameterized_error_for(name) end end it "parses a simple, unparameterized type into the type object" do expect(the_type_parsed_from(types.any)).to be_the_type(types.any) expect(the_type_parsed_from(types.integer)).to be_the_type(types.integer) expect(the_type_parsed_from(types.float)).to be_the_type(types.float) expect(the_type_parsed_from(types.string)).to be_the_type(types.string) expect(the_type_parsed_from(types.boolean)).to be_the_type(types.boolean) expect(the_type_parsed_from(types.pattern)).to be_the_type(types.pattern) expect(the_type_parsed_from(types.data)).to be_the_type(types.data) expect(the_type_parsed_from(types.catalog_entry)).to be_the_type(types.catalog_entry) expect(the_type_parsed_from(types.collection)).to be_the_type(types.collection) expect(the_type_parsed_from(types.tuple)).to be_the_type(types.tuple) expect(the_type_parsed_from(types.struct)).to be_the_type(types.struct) expect(the_type_parsed_from(types.optional)).to be_the_type(types.optional) expect(the_type_parsed_from(types.default)).to be_the_type(types.default) end it "interprets an unparameterized Array as an Array of Any" do expect(parser.parse("Array")).to be_the_type(types.array_of_any) end it "interprets an unparameterized Hash as a Hash of Any, Any" do expect(parser.parse("Hash")).to be_the_type(types.hash_of_any) end it "interprets a parameterized Array[0, 0] as an empty hash with no key and value type" do expect(parser.parse("Array[0, 0]")).to be_the_type(types.array_of(types.default, types.range(0, 0))) end it "interprets a parameterized Hash[0, 0] as an empty hash with no key and value type" do expect(parser.parse("Hash[0, 0]")).to be_the_type(types.hash_of(types.default, types.default, types.range(0, 0))) end it "interprets a parameterized Hash[t] as a Hash of Scalar to t" do expect(parser.parse("Hash[Scalar, Integer]")).to be_the_type(types.hash_of(types.integer)) end it 'interprets an Boolean with a true parameter to represent boolean true' do expect(parser.parse('Boolean[true]')).to be_the_type(types.boolean(true)) end it 'interprets an Boolean with a false parameter to represent boolean false' do expect(parser.parse('Boolean[false]')).to be_the_type(types.boolean(false)) end it 'does not accept non-boolean parameters' do expect{parser.parse('Boolean["false"]')}.to raise_error(/Boolean parameter must be true or false/) end it 'interprets an Integer with one parameter to have unbounded upper range' do expect(parser.parse('Integer[0]')).to eq(parser.parse('Integer[0,default]')) end it 'interprets a Float with one parameter to have unbounded upper range' do expect(parser.parse('Float[0]')).to eq(parser.parse('Float[0,default]')) end it "parses a parameterized type into the type object" do parameterized_array = types.array_of(types.integer) parameterized_hash = types.hash_of(types.integer, types.boolean) expect(the_type_parsed_from(parameterized_array)).to be_the_type(parameterized_array) expect(the_type_parsed_from(parameterized_hash)).to be_the_type(parameterized_hash) end it "parses a size constrained collection using capped range" do parameterized_array = types.array_of(types.integer, types.range(1,2)) parameterized_hash = types.hash_of(types.integer, types.boolean, types.range(1,2)) expect(the_type_parsed_from(parameterized_array)).to be_the_type(parameterized_array) expect(the_type_parsed_from(parameterized_hash)).to be_the_type(parameterized_hash) end it "parses a size constrained collection with open range" do parameterized_array = types.array_of(types.integer, types.range(1, :default)) parameterized_hash = types.hash_of(types.integer, types.boolean, types.range(1, :default)) expect(the_type_parsed_from(parameterized_array)).to be_the_type(parameterized_array) expect(the_type_parsed_from(parameterized_hash)).to be_the_type(parameterized_hash) end it "parses optional type" do opt_t = types.optional(Integer) expect(the_type_parsed_from(opt_t)).to be_the_type(opt_t) end it "parses timespan type" do timespan_t = types.timespan expect(the_type_parsed_from(timespan_t)).to be_the_type(timespan_t) end it "parses timestamp type" do timestamp_t = types.timestamp expect(the_type_parsed_from(timestamp_t)).to be_the_type(timestamp_t) end it "parses tuple type" do tuple_t = types.tuple([Integer, String]) expect(the_type_parsed_from(tuple_t)).to be_the_type(tuple_t) end it "parses tuple type with occurrence constraint" do tuple_t = types.tuple([Integer, String], types.range(2, 5)) expect(the_type_parsed_from(tuple_t)).to be_the_type(tuple_t) end it "parses struct type" do struct_t = types.struct({'a'=>Integer, 'b'=>String}) expect(the_type_parsed_from(struct_t)).to be_the_type(struct_t) end describe "handles parsing of patterns and regexp" do { 'Pattern[/([a-z]+)([1-9]+)/]' => [:pattern, [/([a-z]+)([1-9]+)/]], 'Pattern["([a-z]+)([1-9]+)"]' => [:pattern, [/([a-z]+)([1-9]+)/]], 'Regexp[/([a-z]+)([1-9]+)/]' => [:regexp, [/([a-z]+)([1-9]+)/]], 'Pattern[/x9/, /([a-z]+)([1-9]+)/]' => [:pattern, [/x9/, /([a-z]+)([1-9]+)/]], }.each do |source, type| it "such that the source '#{source}' yields the type #{type.to_s}" do expect(parser.parse(source)).to be_the_type(TypeFactory.send(type[0], *type[1])) end end end it "rejects an collection spec with the wrong number of parameters" do expect { parser.parse("Array[Integer, 1,2,3]") }.to raise_the_parameter_error("Array", "1 to 3", 4) expect { parser.parse("Hash[Integer, Integer, 1,2,3]") }.to raise_the_parameter_error("Hash", "2 to 4", 5) end context 'with scope context and loader' do let!(:scope) { {} } let(:loader) { Object.new } before :each do Adapters::LoaderAdapter.expects(:loader_for_model_object).returns loader end it 'interprets anything that is not found by the loader to be a type reference' do loader.expects(:load).with(:type, 'nonesuch').returns nil expect(parser.parse('Nonesuch', scope)).to be_the_type(types.type_reference('Nonesuch')) end it 'interprets anything that is found by the loader to be what the loader found' do loader.expects(:load).with(:type, 'file').returns types.resource('File') expect(parser.parse('File', scope)).to be_the_type(types.resource('File')) end it "parses a resource type with title" do loader.expects(:load).with(:type, 'file').returns types.resource('File') expect(parser.parse("File['/tmp/foo']", scope)).to be_the_type(types.resource('file', '/tmp/foo')) end it "parses a resource type using 'Resource[type]' form" do loader.expects(:load).with(:type, 'file').returns types.resource('File') expect(parser.parse("Resource[File]", scope)).to be_the_type(types.resource('file')) end it "parses a resource type with title using 'Resource[type, title]'" do loader.expects(:load).with(:type, 'file').returns nil expect(parser.parse("Resource[File, '/tmp/foo']", scope)).to be_the_type(types.resource('file', '/tmp/foo')) end it "parses a resource type with title using 'Resource[Type[title]]'" do loader.expects(:load).with(:type, 'nonesuch').returns nil expect(parser.parse("Resource[Nonesuch['fife']]", scope)).to be_the_type(types.resource('nonesuch', 'fife')) end end context 'with loader context' do let(:loader) { Puppet::Pops::Loader::BaseLoader.new(nil, "type_parser_unit_test_loader") } it 'interprets anything that is not found by the loader to be a type reference' do loader.expects(:load).with(:type, 'nonesuch').returns nil expect(parser.parse('Nonesuch', loader)).to be_the_type(types.type_reference('Nonesuch')) end it 'interprets anything that is found by the loader to be what the loader found' do loader.expects(:load).with(:type, 'file').returns types.resource('File') expect(parser.parse('File', loader)).to be_the_type(types.resource('file')) end it "parses a resource type with title" do loader.expects(:load).with(:type, 'file').returns types.resource('File') expect(parser.parse("File['/tmp/foo']", loader)).to be_the_type(types.resource('file', '/tmp/foo')) end it "parses a resource type using 'Resource[type]' form" do loader.expects(:load).with(:type, 'file').returns types.resource('File') expect(parser.parse("Resource[File]", loader)).to be_the_type(types.resource('file')) end it "parses a resource type with title using 'Resource[type, title]'" do loader.expects(:load).with(:type, 'file').returns types.resource('File') expect(parser.parse("Resource[File, '/tmp/foo']", loader)).to be_the_type(types.resource('file', '/tmp/foo')) end end context 'without a scope' do it "interprets anything that is not a built in type to be a type reference" do expect(parser.parse('TestType')).to eq(types.type_reference('TestType')) end it "interprets anything that is not a built in type with parameterers to be type reference with parameters" do expect(parser.parse("TestType['/tmp/foo']")).to eq(types.type_reference("TestType['/tmp/foo']")) end end it "parses a host class type" do expect(parser.parse("Class")).to be_the_type(types.host_class()) end it "parses a parameterized host class type" do expect(parser.parse("Class[foo::bar]")).to be_the_type(types.host_class('foo::bar')) end it 'parses an integer range' do expect(parser.parse("Integer[1,2]")).to be_the_type(types.range(1,2)) end it 'parses a negative integer range' do expect(parser.parse("Integer[-3,-1]")).to be_the_type(types.range(-3,-1)) end it 'parses a float range' do expect(parser.parse("Float[1.0,2.0]")).to be_the_type(types.float_range(1.0,2.0)) end it 'parses a collection size range' do expect(parser.parse("Collection[1,2]")).to be_the_type(types.collection(types.range(1,2))) end it 'parses a timespan type' do expect(parser.parse("Timespan")).to be_the_type(types.timespan) end it 'parses a timespan type with a lower bound' do expect(parser.parse("Timespan[{hours => 3}]")).to be_the_type(types.timespan({'hours' => 3})) end it 'parses a timespan type with an upper bound' do expect(parser.parse("Timespan[default, {hours => 9}]")).to be_the_type(types.timespan(nil, {'hours' => 9})) end it 'parses a timespan type with both lower and upper bounds' do expect(parser.parse("Timespan[{hours => 3}, {hours => 9}]")).to be_the_type(types.timespan({'hours' => 3}, {'hours' => 9})) end it 'parses a timestamp type' do expect(parser.parse("Timestamp")).to be_the_type(types.timestamp) end it 'parses a timestamp type with a lower bound' do expect(parser.parse("Timestamp['2014-12-12T13:14:15 CET']")).to be_the_type(types.timestamp('2014-12-12T13:14:15 CET')) end it 'parses a timestamp type with an upper bound' do expect(parser.parse("Timestamp[default, '2014-12-12T13:14:15 CET']")).to be_the_type(types.timestamp(nil, '2014-12-12T13:14:15 CET')) end it 'parses a timestamp type with both lower and upper bounds' do expect(parser.parse("Timestamp['2014-12-12T13:14:15 CET', '2016-08-23T17:50:00 CET']")).to be_the_type(types.timestamp('2014-12-12T13:14:15 CET', '2016-08-23T17:50:00 CET')) end it 'parses a type type' do expect(parser.parse("Type[Integer]")).to be_the_type(types.type_type(types.integer)) end it 'parses a ruby type' do expect(parser.parse("Runtime[ruby, 'Integer']")).to be_the_type(types.ruby_type('Integer')) end it 'parses a callable type' do t = parser.parse("Callable") expect(t).to be_the_type(types.all_callables()) expect(t.return_type).to be_nil end it 'parses a parameterized callable type' do t = parser.parse("Callable[String, Integer]") expect(t).to be_the_type(types.callable(String, Integer)) expect(t.return_type).to be_nil end it 'parses a parameterized callable type with min/max' do t = parser.parse("Callable[String, Integer, 1, default]") expect(t).to be_the_type(types.callable(String, Integer, 1, :default)) expect(t.return_type).to be_nil end it 'parses a parameterized callable type with block' do t = parser.parse("Callable[String, Callable[Boolean]]") expect(t).to be_the_type(types.callable(String, types.callable(true))) expect(t.return_type).to be_nil end it 'parses a callable with no parameters and return type' do expect(parser.parse("Callable[[],Float]")).to be_the_type(types.callable([],Float)) end it 'parses a parameterized callable type with return type' do expect(parser.parse("Callable[[String, Integer],Float]")).to be_the_type(types.callable([String, Integer],Float)) end it 'parses a parameterized callable type with min/max and return type' do expect(parser.parse("Callable[[String, Integer, 1, default],Float]")).to be_the_type(types.callable([String, Integer, 1, :default], Float)) end it 'parses a parameterized callable type with block and return type' do expect(parser.parse("Callable[[String, Callable[Boolean]],Float]")).to be_the_type(types.callable([String, types.callable(true)], Float)) end it 'parses a parameterized callable type with 0 min/max' do t = parser.parse("Callable[0,0]") expect(t).to be_the_type(types.callable(0,0)) expect(t.param_types.types).to be_empty expect(t.return_type).to be_nil end it 'parses a parameterized callable type with 0 min/max and return_type' do t = parser.parse("Callable[[0,0],Float]") expect(t).to be_the_type(types.callable([0,0],Float)) expect(t.param_types.types).to be_empty expect(t.return_type).to be_the_type(types.float) end it 'parses a parameterized callable type with >0 min/max' do t = parser.parse("Callable[0,1]") expect(t).to be_the_type(types.callable(0,1)) # Contains a Unit type to indicate "called with what you accept" expect(t.param_types.types[0]).to be_the_type(PUnitType.new()) expect(t.return_type).to be_nil end it 'parses a parameterized callable type with >0 min/max and a return type' do t = parser.parse("Callable[[0,1],Float]") expect(t).to be_the_type(types.callable([0,1], Float)) # Contains a Unit type to indicate "called with what you accept" expect(t.param_types.types[0]).to be_the_type(PUnitType.new()) expect(t.return_type).to be_the_type(types.float) end it 'parses all known literals' do t = parser.parse('Nonesuch[{a=>undef,b=>true,c=>false,d=>default,e=>"string",f=>0,g=>1.0,h=>[1,2,3]}]') expect(t).to be_a(PTypeReferenceType) expect(t.type_string).to eql('Nonesuch[{a=>undef,b=>true,c=>false,d=>default,e=>"string",f=>0,g=>1.0,h=>[1,2,3]}]') end it 'parses a parameterized Enum using identifiers' do t = parser.parse('Enum[a, b]') expect(t).to be_a(PEnumType) expect(t.to_s).to eql("Enum['a', 'b']") end it 'parses a parameterized Enum using strings' do t = parser.parse("Enum['a', 'b']") expect(t).to be_a(PEnumType) expect(t.to_s).to eql("Enum['a', 'b']") end it 'rejects a parameterized Enum using type refs' do expect { parser.parse('Enum[A, B]') }.to raise_error(/Enum parameters must be identifiers or strings/) end it 'rejects a parameterized Enum using integers' do expect { parser.parse('Enum[1, 2]') }.to raise_error(/Enum parameters must be identifiers or strings/) end matcher :be_the_type do |type| calc = TypeCalculator.new match do |actual| calc.assignable?(actual, type) && calc.assignable?(type, actual) end failure_message do |actual| "expected #{calc.string(type)}, but was #{calc.string(actual)}" end end def raise_the_parameter_error(type, required, given) raise_error(Puppet::ParseError, /#{type} requires #{required}, #{given} provided/) end def raise_type_error_for(type_name) raise_error(Puppet::ParseError, /Unknown type <#{type_name}>/) end def raise_unparameterized_error_for(type_name) raise_error(Puppet::ParseError, /Not a parameterized type <#{type_name}>/) end def the_type_parsed_from(type) parser.parse(the_type_spec_for(type)) end def the_type_spec_for(type) TypeFormatter.string(type) end end end end puppet-5.5.10/spec/unit/pops/types/types_spec.rb0000644005276200011600000005674713417161722021577 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' require 'puppet_spec/compiler' module Puppet::Pops module Types describe 'Puppet Type System' do include PuppetSpec::Compiler let(:tf) { TypeFactory } context 'Integer type' do let!(:a) { tf.range(10, 20) } let!(:b) { tf.range(18, 28) } let!(:c) { tf.range( 2, 12) } let!(:d) { tf.range(12, 18) } let!(:e) { tf.range( 8, 22) } let!(:f) { tf.range( 8, 9) } let!(:g) { tf.range(21, 22) } let!(:h) { tf.range(30, 31) } let!(:i) { tf.float_range(1.0, 30.0) } let!(:j) { tf.float_range(1.0, 9.0) } context 'when testing if ranges intersect' do it 'detects an intersection when self is before its argument' do expect(a.intersect?(b)).to be_truthy end it 'detects an intersection when self is after its argument' do expect(a.intersect?(c)).to be_truthy end it 'detects an intersection when self covers its argument' do expect(a.intersect?(d)).to be_truthy end it 'detects an intersection when self equals its argument' do expect(a.intersect?(a)).to be_truthy end it 'detects an intersection when self is covered by its argument' do expect(a.intersect?(e)).to be_truthy end it 'does not consider an adjacent range to be intersecting' do [f, g].each {|x| expect(a.intersect?(x)).to be_falsey } end it 'does not consider an range that is apart to be intersecting' do expect(a.intersect?(h)).to be_falsey end it 'does not consider an overlapping float range to be intersecting' do expect(a.intersect?(i)).to be_falsey end end context 'when testing if ranges are adjacent' do it 'detects an adjacent type when self is after its argument' do expect(a.adjacent?(f)).to be_truthy end it 'detects an adjacent type when self is before its argument' do expect(a.adjacent?(g)).to be_truthy end it 'does not consider overlapping types to be adjacent' do [a, b, c, d, e].each { |x| expect(a.adjacent?(x)).to be_falsey } end it 'does not consider an range that is apart to be adjacent' do expect(a.adjacent?(h)).to be_falsey end it 'does not consider an adjacent float range to be adjancent' do expect(a.adjacent?(j)).to be_falsey end end context 'when merging ranges' do it 'will merge intersecting ranges' do expect(a.merge(b)).to eq(tf.range(10, 28)) end it 'will merge adjacent ranges' do expect(a.merge(g)).to eq(tf.range(10, 22)) end it 'will not merge ranges that are apart' do expect(a.merge(h)).to be_nil end it 'will not merge overlapping float ranges' do expect(a.merge(i)).to be_nil end it 'will not merge adjacent float ranges' do expect(a.merge(j)).to be_nil end end end context 'Float type' do let!(:a) { tf.float_range(10.0, 20.0) } let!(:b) { tf.float_range(18.0, 28.0) } let!(:c) { tf.float_range( 2.0, 12.0) } let!(:d) { tf.float_range(12.0, 18.0) } let!(:e) { tf.float_range( 8.0, 22.0) } let!(:f) { tf.float_range(30.0, 31.0) } let!(:g) { tf.range(1, 30) } context 'when testing if ranges intersect' do it 'detects an intersection when self is before its argument' do expect(a.intersect?(b)).to be_truthy end it 'detects an intersection when self is after its argument' do expect(a.intersect?(c)).to be_truthy end it 'detects an intersection when self covers its argument' do expect(a.intersect?(d)).to be_truthy end it 'detects an intersection when self equals its argument' do expect(a.intersect?(a)).to be_truthy end it 'detects an intersection when self is covered by its argument' do expect(a.intersect?(e)).to be_truthy end it 'does not consider an range that is apart to be intersecting' do expect(a.intersect?(f)).to be_falsey end it 'does not consider an overlapping integer range to be intersecting' do expect(a.intersect?(g)).to be_falsey end end context 'when merging ranges' do it 'will merge intersecting ranges' do expect(a.merge(b)).to eq(tf.float_range(10.0, 28.0)) end it 'will not merge ranges that are apart' do expect(a.merge(f)).to be_nil end it 'will not merge overlapping integer ranges' do expect(a.merge(g)).to be_nil end end end context 'Boolean type' do it 'parameterized type is assignable to base type' do code = <<-CODE notice(Boolean[true] < Boolean) notice(Boolean[false] < Boolean) CODE expect(eval_and_collect_notices(code)).to eq(['true', 'true']) end it 'boolean literals are instances of the base type' do code = <<-CODE notice(true =~ Boolean) notice(false =~ Boolean) CODE expect(eval_and_collect_notices(code)).to eq(['true', 'true']) end it 'boolean literals are instances of type parameterized with the same literal' do code = <<-CODE notice(true =~ Boolean[true]) notice(false =~ Boolean[false]) CODE expect(eval_and_collect_notices(code)).to eq(['true', 'true']) end it 'boolean literals are not instances of type parameterized with a different literal' do code = <<-CODE notice(true =~ Boolean[false]) notice(false =~ Boolean[true]) CODE expect(eval_and_collect_notices(code)).to eq(['false', 'false']) end end context 'Enum type' do it 'sorts its entries' do code = <<-CODE Enum[c,b,a].each |$e| { notice $e } CODE expect(eval_and_collect_notices(code)).to eq(['a', 'b', 'c']) end it 'makes entries unique' do code = <<-CODE Enum[a,b,c,b,a].each |$e| { notice $e } CODE expect(eval_and_collect_notices(code)).to eq(['a', 'b', 'c']) end it 'is case sensitive by default' do code = <<-CODE notice('A' =~ Enum[a,b]) notice('a' =~ Enum[a,b]) CODE expect(eval_and_collect_notices(code)).to eq(['false', 'true']) end it 'is case insensitive when last parameter argument is true' do code = <<-CODE notice('A' =~ Enum[a,b,true]) notice('a' =~ Enum[a,b,true]) CODE expect(eval_and_collect_notices(code)).to eq(['true', 'true']) end end context 'Iterable type' do it 'can be parameterized with element type' do code = <<-CODE function foo(Iterable[String] $x) { $x.each |$e| { notice $e } } foo([bar, baz, cake]) CODE expect(eval_and_collect_notices(code)).to eq(['bar', 'baz', 'cake']) end end context 'Iterator type' do let!(:iterint) { tf.iterator(tf.integer) } context 'when testing instance?' do it 'will consider an iterable on an integer is an instance of Iterator[Integer]' do expect(iterint.instance?(Iterable.on(3))).to be_truthy end it 'will consider an iterable on string to be an instance of Iterator[Integer]' do expect(iterint.instance?(Iterable.on('string'))).to be_falsey end end context 'when testing assignable?' do it 'will consider an iterator with an assignable type as assignable' do expect(tf.iterator(tf.numeric).assignable?(iterint)).to be_truthy end it 'will not consider an iterator with a non assignable type as assignable' do expect(tf.iterator(tf.string).assignable?(iterint)).to be_falsey end end context 'when asked for an iterable type' do it 'the default iterator type returns the default iterable type' do expect(PIteratorType::DEFAULT.iterable_type).to be(PIterableType::DEFAULT) end it 'a typed iterator type returns the an equally typed iterable type' do expect(iterint.iterable_type).to eq(tf.iterable(tf.integer)) end end it 'can be parameterized with an element type' do code = <<-CODE function foo(Iterator[String] $x) { $x.each |$e| { notice $e } } foo([bar, baz, cake].reverse_each) CODE expect(eval_and_collect_notices(code)).to eq(['cake', 'baz', 'bar']) end end context 'Collection type' do it 'can be parameterized with a range' do code = <<-CODE notice(Collection[5, default] == Collection[5]) notice(Collection[5, 5] > Tuple[Integer, 0, 10]) CODE expect(eval_and_collect_notices(code)).to eq(['true', 'false']) end end context 'Struct type' do context 'can be used as key in hash' do it 'compacts optional in optional in optional to just optional' do key1 = tf.struct({'foo' => tf.string}) key2 = tf.struct({'foo' => tf.string}) expect({key1 => 'hi'}[key2]).to eq('hi') end end end context 'Optional type' do let!(:overlapping_ints) { tf.variant(tf.range(10, 20), tf.range(18, 28)) } let!(:optoptopt) { tf.optional(tf.optional(tf.optional(overlapping_ints))) } let!(:optnu) { tf.optional(tf.not_undef(overlapping_ints)) } context 'when normalizing' do it 'compacts optional in optional in optional to just optional' do expect(optoptopt.normalize).to eq(tf.optional(tf.range(10, 28))) end end it 'compacts NotUndef in Optional to just Optional' do expect(optnu.normalize).to eq(tf.optional(tf.range(10, 28))) end end context 'NotUndef type' do let!(:nununu) { tf.not_undef(tf.not_undef(tf.not_undef(tf.any))) } let!(:nuopt) { tf.not_undef(tf.optional(tf.any)) } let!(:nuoptint) { tf.not_undef(tf.optional(tf.integer)) } context 'when normalizing' do it 'compacts NotUndef in NotUndef in NotUndef to just NotUndef' do expect(nununu.normalize).to eq(tf.not_undef(tf.any)) end it 'compacts Optional in NotUndef to just NotUndef' do expect(nuopt.normalize).to eq(tf.not_undef(tf.any)) end it 'compacts NotUndef[Optional[Integer]] in NotUndef to just Integer' do expect(nuoptint.normalize).to eq(tf.integer) end end end context 'Variant type' do let!(:overlapping_ints) { tf.variant(tf.range(10, 20), tf.range(18, 28)) } let!(:adjacent_ints) { tf.variant(tf.range(10, 20), tf.range(8, 9)) } let!(:mix_ints) { tf.variant(overlapping_ints, adjacent_ints) } let!(:overlapping_floats) { tf.variant(tf.float_range(10.0, 20.0), tf.float_range(18.0, 28.0)) } let!(:enums) { tf.variant(tf.enum('a', 'b'), tf.enum('b', 'c')) } let!(:enums_s_is) { tf.variant(tf.enum('a', 'b'), tf.enum('b', 'c', true)) } let!(:enums_is_is) { tf.variant(tf.enum('A', 'b', true), tf.enum('B', 'c', true)) } let!(:patterns) { tf.variant(tf.pattern('a', 'b'), tf.pattern('b', 'c')) } let!(:with_undef) { tf.variant(tf.undef, tf.range(1,10)) } let!(:all_optional) { tf.variant(tf.optional(tf.range(1,10)), tf.optional(tf.range(11,20))) } let!(:groups) { tf.variant(mix_ints, overlapping_floats, enums, patterns, with_undef, all_optional) } context 'when normalizing contained types that' do it 'are overlapping ints, the result is a range' do expect(overlapping_ints.normalize).to eq(tf.range(10, 28)) end it 'are adjacent ints, the result is a range' do expect(adjacent_ints.normalize).to eq(tf.range(8, 20)) end it 'are mixed variants with adjacent and overlapping ints, the result is a range' do expect(mix_ints.normalize).to eq(tf.range(8, 28)) end it 'are overlapping floats, the result is a float range' do expect(overlapping_floats.normalize).to eq(tf.float_range(10.0, 28.0)) end it 'are enums, the result is an enum' do expect(enums.normalize).to eq(tf.enum('a', 'b', 'c')) end it 'are case sensitive versus case insensitive enums, does not merge the enums' do expect(enums_s_is.normalize).to eq(enums_s_is) end it 'are case insensitive enums, result is case insensitive and unique irrespective of case' do expect(enums_is_is.normalize).to eq(tf.enum('a', 'b', 'c', true)) end it 'are patterns, the result is a pattern' do expect(patterns.normalize).to eq(tf.pattern('a', 'b', 'c')) end it 'contains an Undef, the result is Optional' do expect(with_undef.normalize).to eq(tf.optional(tf.range(1,10))) end it 'are all Optional, the result is an Optional with normalized type' do expect(all_optional.normalize).to eq(tf.optional(tf.range(1,20))) end it 'can be normalized in groups, the result is a Variant containing the resulting normalizations' do expect(groups.normalize).to eq(tf.optional( tf.variant( tf.range(1, 28), tf.float_range(10.0, 28.0), tf.enum('a', 'b', 'c'), tf.pattern('a', 'b', 'c')))) end end context 'when generalizing' do it 'will generalize and compact contained types' do expect(tf.variant(tf.string(tf.range(3,3)), tf.string(tf.range(5,5))).generalize).to eq(tf.variant(tf.string)) end end end context 'Runtime type' do it 'can be created with a runtime and a runtime type name' do expect(tf.runtime('ruby', 'Hash').to_s).to eq("Runtime[ruby, 'Hash']") end it 'can be created with a runtime and, puppet name pattern, and runtime replacement' do expect(tf.runtime('ruby', [/^MyPackage::(.*)$/, 'MyModule::\1']).to_s).to eq("Runtime[ruby, [/^MyPackage::(.*)$/, 'MyModule::\\1']]") end it 'will map a Puppet name to a runtime type' do t = tf.runtime('ruby', [/^MyPackage::(.*)$/, 'MyModule::\1']) expect(t.from_puppet_name('MyPackage::MyType').to_s).to eq("Runtime[ruby, 'MyModule::MyType']") end it 'with parameters is assignable to the default Runtime type' do code = <<-CODE notice(Runtime[ruby, 'Symbol'] < Runtime) CODE expect(eval_and_collect_notices(code)).to eq(['true']) end it 'with parameters is not assignable from the default Runtime type' do code = <<-CODE notice(Runtime < Runtime[ruby, 'Symbol']) CODE expect(eval_and_collect_notices(code)).to eq(['false']) end it 'default is assignable to itself' do code = <<-CODE notice(Runtime < Runtime) notice(Runtime <= Runtime) CODE expect(eval_and_collect_notices(code)).to eq(['false', 'true']) end end context 'Type aliases' do it 'will resolve nested objects using self recursion' do code = <<-CODE type Tree = Hash[String,Variant[String,Tree]] notice({a => {b => {c => d}}} =~ Tree) CODE expect(eval_and_collect_notices(code)).to eq(['true']) end it 'will find mismatches using self recursion' do code = <<-CODE type Tree = Hash[String,Variant[String,Tree]] notice({a => {b => {c => 1}}} =~ Tree) CODE expect(eval_and_collect_notices(code)).to eq(['false']) end it 'will not allow an alias chain to only contain aliases' do code = <<-CODE type Foo = Bar type Fee = Foo type Bar = Fee notice(0 =~ Bar) CODE expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /Type alias 'Bar' cannot be resolved to a real type/) end it 'will not allow an alias chain that contains nothing but aliases and variants' do code = <<-CODE type Foo = Bar type Fee = Foo type Bar = Variant[Fee,Foo] notice(0 =~ Bar) CODE expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /Type alias 'Bar' cannot be resolved to a real type/) end it 'will not allow an alias to directly reference itself' do code = <<-CODE type Foo = Foo notice(0 =~ Foo) CODE expect { eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /Type alias 'Foo' cannot be resolved to a real type/) end it 'will allow an alias to directly reference itself in a variant with other types' do code = <<-CODE type Foo = Variant[Foo,String] notice(a =~ Foo) CODE expect(eval_and_collect_notices(code)).to eq(['true']) end it 'will allow an alias where a variant references an alias with a variant that references itself' do code = <<-CODE type X = Variant[Y, Integer] type Y = Variant[X, String] notice(X >= X) notice(X >= Y) notice(Y >= X) CODE expect(eval_and_collect_notices(code)).to eq(['true','true','true']) end it 'will detect a mismatch in an alias that directly references itself in a variant with other types' do code = <<-CODE type Foo = Variant[Foo,String] notice(3 =~ Foo) CODE expect(eval_and_collect_notices(code)).to eq(['false']) end it 'will normalize a Variant containing a self reference so that the self reference is removed' do code = <<-CODE type Foo = Variant[Foo,String,Integer] assert_type(Foo, /x/) CODE expect { eval_and_collect_notices(code) }.to raise_error(/expects a Foo = Variant\[String, Integer\] value, got Regexp/) end it 'will handle a scalar correctly in combinations of nested aliased variants' do code = <<-CODE type Bar = Variant[Foo,Integer] type Foo = Variant[Bar,String] notice(a =~ Foo) notice(1 =~ Foo) notice(/x/ =~ Foo) CODE expect(eval_and_collect_notices(code)).to eq(['true', 'true', 'false']) end it 'will handle a non scalar correctly in combinations of nested aliased array with nested variants' do code = <<-CODE type Bar = Variant[Foo,Integer] type Foo = Array[Variant[Bar,String]] notice([a] =~ Foo) notice([1] =~ Foo) notice([/x/] =~ Foo) CODE expect(eval_and_collect_notices(code)).to eq(['true', 'true', 'false']) end it 'will handle a non scalar correctly in combinations of nested aliased variants with array' do code = <<-CODE type Bar = Variant[Foo,Array[Integer]] type Foo = Variant[Bar,Array[String]] notice([a] =~ Foo) notice([1] =~ Foo) notice([/x/] =~ Foo) CODE expect(eval_and_collect_notices(code)).to eq(['true', 'true', 'false']) end it 'will not allow dynamic constructs in type definition' do code = <<-CODE type Foo = Enum[$facts[os][family]] notice(Foo) CODE expect{ eval_and_collect_notices(code) }.to raise_error(Puppet::Error, /The expression <\$facts\[os\]\[family\]> is not a valid type specification/) end end context 'Type mappings' do it 'can register a singe type mapping' do source = <<-CODE type MyModule::ImplementationRegistry = Object[{}] type Runtime[ruby, 'Puppet::Pops::Types::ImplementationRegistry'] = MyModule::ImplementationRegistry notice(true) CODE collect_notices(source) do |compiler| compiler.compile do |catalog| type = Loaders.implementation_registry.type_for_module(ImplementationRegistry) expect(type).to be_a(PObjectType) expect(type.name).to eql('MyModule::ImplementationRegistry') catalog end end end it 'can register a regexp based mapping' do source = <<-CODE type MyModule::TypeMismatchDescriber = Object[{}] type Runtime[ruby, [/^Puppet::Pops::Types::(\\w+)$/, 'MyModule::\\1']] = [/^MyModule::(\\w+)$/, 'Puppet::Pops::Types::\\1'] notice(true) CODE collect_notices(source) do |compiler| compiler.compile do |catalog| type = Loaders.implementation_registry.type_for_module(TypeMismatchDescriber) expect(type).to be_a(PObjectType) expect(type.name).to eql('MyModule::TypeMismatchDescriber') catalog end end end it 'a type mapping affects type inference' do source = <<-CODE type MyModule::ImplementationRegistry = Object[{}] type Runtime[ruby, 'Puppet::Pops::Types::ImplementationRegistry'] = MyModule::ImplementationRegistry notice(true) CODE collect_notices(source) do |compiler| compiler.compile do |catalog| type = TypeCalculator.singleton.infer(Loaders.implementation_registry) expect(type).to be_a(PObjectType) expect(type.name).to eql('MyModule::ImplementationRegistry') catalog end end end end context 'When attempting to redefine a built in type' do it 'such as Integer, an error is raised' do code = <<-CODE type Integer = String notice 'hello' =~ Integer CODE expect{ eval_and_collect_notices(code) }.to raise_error(/Attempt to redefine entity 'http:\/\/puppet\.com\/2016\.1\/runtime\/type\/integer'. Originally set by Puppet-Type-System\/Static-Loader/) end end context 'instantiation via new_function is supported by' do let(:loader) { Loader::BaseLoader.new(nil, "types_unit_test_loader") } it 'Integer' do func_class = tf.integer.new_function expect(func_class).to be_a(Class) expect(func_class.superclass).to be(Puppet::Functions::Function) end it 'Optional[Integer]' do func_class = tf.optional(tf.integer).new_function expect(func_class).to be_a(Class) expect(func_class.superclass).to be(Puppet::Functions::Function) end it 'Regexp' do func_class = tf.regexp.new_function expect(func_class).to be_a(Class) expect(func_class.superclass).to be(Puppet::Functions::Function) end end context 'instantiation via new_function is not supported by' do let(:loader) { Loader::BaseLoader.new(nil, "types_unit_test_loader") } it 'Any, Scalar, Collection' do [tf.any, tf.scalar, tf.collection ].each do |t| expect { t.new_function }.to raise_error(ArgumentError, /Creation of new instance of type '#{t.to_s}' is not supported/) end end end context 'instantiation via ruby create function' do around(:each) do |example| Puppet.override(:loaders => Loaders.new(Puppet::Node::Environment.create(:testing, []))) do example.run end end it 'is supported by Integer' do int = tf.integer.create('32') expect(int).to eq(32) end it 'is supported by Regexp' do rx = tf.regexp.create('[a-z]+') expect(rx).to eq(/[a-z]+/) end it 'is supported by Optional[Integer]' do int = tf.optional(tf.integer).create('32') expect(int).to eq(32) end it 'is not supported by Any, Scalar, Collection' do [tf.any, tf.scalar, tf.collection ].each do |t| expect { t.create }.to raise_error(ArgumentError, /Creation of new instance of type '#{t.to_s}' is not supported/) end end end context 'creation of parameterized type via ruby create function on class' do around(:each) do |example| Puppet.override(:loaders => Loaders.new(Puppet::Node::Environment.create(:testing, []))) do example.run end end it 'is supported by Integer' do int_type = tf.integer.class.create(0, 32) expect(int_type).to eq(tf.range(0, 32)) end it 'is supported by Regexp' do rx_type = tf.regexp.class.create('[a-z]+') expect(rx_type).to eq(tf.regexp(/[a-z]+/)) end end context 'backward compatibility' do it 'PTypeType can be accessed from PType' do # should appoint the exact same instance expect(PType).to equal(PTypeType) end it 'PClassType can be accessed from PHostClassType' do # should appoint the exact same instance expect(PHostClassType).to equal(PClassType) end end end end end puppet-5.5.10/spec/unit/pops/utils_spec.rb0000644005276200011600000000472013417161721020406 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' describe 'pops utils' do context 'when converting strings to numbers' do it 'should convert "0" to 0' do expect(Puppet::Pops::Utils.to_n("0")).to eq(0) end it 'should convert "0" to 0 with radix' do expect(Puppet::Pops::Utils.to_n_with_radix("0")).to eq([0, 10]) end it 'should convert "0.0" to 0.0' do expect(Puppet::Pops::Utils.to_n("0.0")).to eq(0.0) end it 'should convert "0.0" to 0.0 with radix' do expect(Puppet::Pops::Utils.to_n_with_radix("0.0")).to eq([0.0, 10]) end it 'should convert "0.01e1" to 0.01e1' do expect(Puppet::Pops::Utils.to_n("0.01e1")).to eq(0.01e1) expect(Puppet::Pops::Utils.to_n("0.01E1")).to eq(0.01e1) end it 'should convert "0.01e1" to 0.01e1 with radix' do expect(Puppet::Pops::Utils.to_n_with_radix("0.01e1")).to eq([0.01e1, 10]) expect(Puppet::Pops::Utils.to_n_with_radix("0.01E1")).to eq([0.01e1, 10]) end it 'should not convert "0e1" to floating point' do expect(Puppet::Pops::Utils.to_n("0e1")).to be_nil expect(Puppet::Pops::Utils.to_n("0E1")).to be_nil end it 'should not convert "0e1" to floating point with radix' do expect(Puppet::Pops::Utils.to_n_with_radix("0e1")).to be_nil expect(Puppet::Pops::Utils.to_n_with_radix("0E1")).to be_nil end it 'should not convert "0.0e1" to floating point' do expect(Puppet::Pops::Utils.to_n("0.0e1")).to be_nil expect(Puppet::Pops::Utils.to_n("0.0E1")).to be_nil end it 'should not convert "0.0e1" to floating point with radix' do expect(Puppet::Pops::Utils.to_n_with_radix("0.0e1")).to be_nil expect(Puppet::Pops::Utils.to_n_with_radix("0.0E1")).to be_nil end it 'should not convert "000000.0000e1" to floating point' do expect(Puppet::Pops::Utils.to_n("000000.0000e1")).to be_nil expect(Puppet::Pops::Utils.to_n("000000.0000E1")).to be_nil end it 'should not convert "000000.0000e1" to floating point with radix' do expect(Puppet::Pops::Utils.to_n_with_radix("000000.0000e1")).to be_nil expect(Puppet::Pops::Utils.to_n_with_radix("000000.0000E1")).to be_nil end it 'should not convert infinite values to floating point' do expect(Puppet::Pops::Utils.to_n("4e999")).to be_nil end it 'should not convert infinite values to floating point with_radix' do expect(Puppet::Pops::Utils.to_n_with_radix("4e999")).to be_nil end end endpuppet-5.5.10/spec/unit/pops/validation_spec.rb0000644005276200011600000000472513417161721021405 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' describe 'Puppet::Pops::Validation::Diagnostic' do # Mocks a SourcePosAdapter as it is used in these use cases # of a Diagnostic # class MockSourcePos attr_reader :offset def initialize(offset) @offset = offset end end it "computes equal hash value ignoring arguments" do issue = Puppet::Pops::Issues::DIV_BY_ZERO source_pos = MockSourcePos.new(10) d1 = Puppet::Pops::Validation::Diagnostic.new(:warning, issue, "foo", source_pos, {:foo => 10}) d2 = Puppet::Pops::Validation::Diagnostic.new(:warning, issue, "foo", source_pos.clone, {:bar => 20}) expect(d1.hash).to eql(d2.hash) end it "computes non equal hash value for different severities" do issue = Puppet::Pops::Issues::DIV_BY_ZERO source_pos = MockSourcePos.new(10) d1 = Puppet::Pops::Validation::Diagnostic.new(:warning, issue, "foo", source_pos, {}) d2 = Puppet::Pops::Validation::Diagnostic.new(:error, issue, "foo", source_pos.clone, {}) expect(d1.hash).to_not eql(d2.hash) end it "computes non equal hash value for different offsets" do issue = Puppet::Pops::Issues::DIV_BY_ZERO source_pos1 = MockSourcePos.new(10) source_pos2 = MockSourcePos.new(11) d1 = Puppet::Pops::Validation::Diagnostic.new(:warning, issue, "foo", source_pos1, {}) d2 = Puppet::Pops::Validation::Diagnostic.new(:warning, issue, "foo", source_pos2, {}) expect(d1.hash).to_not eql(d2.hash) end it "can be used in a set" do the_set = Set.new() issue = Puppet::Pops::Issues::DIV_BY_ZERO source_pos = MockSourcePos.new(10) d1 = Puppet::Pops::Validation::Diagnostic.new(:warning, issue, "foo", source_pos, {}) d2 = Puppet::Pops::Validation::Diagnostic.new(:warning, issue, "foo", source_pos.clone, {}) d3 = Puppet::Pops::Validation::Diagnostic.new(:error, issue, "foo", source_pos.clone, {}) expect(the_set.add?(d1)).to_not be_nil expect(the_set.add?(d2)).to be_nil expect(the_set.add?(d3)).to_not be_nil end end describe "Puppet::Pops::Validation::SeverityProducer" do it 'sets default severity given in initializer' do producer = Puppet::Pops::Validation::SeverityProducer.new(:warning) expect(producer.severity(Puppet::Pops::Issues::DIV_BY_ZERO)).to be(:warning) end it 'sets default severity to :error if not given' do producer = Puppet::Pops::Validation::SeverityProducer.new() expect(producer.severity(Puppet::Pops::Issues::DIV_BY_ZERO)).to be(:error) end end puppet-5.5.10/spec/unit/pops/validator/0000755005276200011600000000000013417162177017677 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/pops/validator/validator_spec.rb0000644005276200011600000011343513417161722023225 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' require 'puppet_spec/pops' require_relative '../parser/parser_rspec_helper' describe "validating 4x" do include ParserRspecHelper include PuppetSpec::Pops let(:acceptor) { Puppet::Pops::Validation::Acceptor.new() } let(:validator) { Puppet::Pops::Validation::ValidatorFactory_4_0.new().validator(acceptor) } let(:environment) { Puppet::Node::Environment.create(:bar, ['path']) } def validate(factory) validator.validate(factory.model) acceptor end def deprecation_count(acceptor) acceptor.diagnostics.select {|d| d.severity == :deprecation }.count end def with_environment(environment, env_params = {}) override_env = environment override_env = environment.override_with({ modulepath: env_params[:modulepath] || environment.full_modulepath, manifest: env_params[:manifest] || environment.manifest, config_version: env_params[:config_version] || environment.config_version }) if env_params.count > 0 Puppet.override(current_environment: override_env) do yield end end it 'should raise error for illegal class names' do expect(validate(parse('class aaa::_bbb {}'))).to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_NAME) expect(validate(parse('class Aaa {}'))).to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_NAME) expect(validate(parse('class ::aaa {}'))).to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_NAME) end it 'should raise error for illegal define names' do expect(validate(parse('define aaa::_bbb {}'))).to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_NAME) expect(validate(parse('define Aaa {}'))).to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_NAME) expect(validate(parse('define ::aaa {}'))).to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_NAME) end it 'should raise error for illegal function names' do expect(validate(parse('function aaa::_bbb() {}'))).to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_NAME) expect(validate(parse('function Aaa() {}'))).to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_NAME) expect(validate(parse('function ::aaa() {}'))).to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_NAME) end it 'should raise error for illegal definition locations' do with_environment(environment) do expect(validate(parse('function aaa::ccc() {}', 'path/aaa/manifests/bbb.pp'))).to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION) expect(validate(parse('class bbb() {}', 'path/aaa/manifests/init.pp'))).to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION) expect(validate(parse('define aaa::bbb::ccc::eee() {}', 'path/aaa/manifests/bbb/ddd.pp'))).to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION) end end it 'should not raise error for legal definition locations' do with_environment(environment) do expect(validate(parse('function aaa::bbb() {}', 'path/aaa/manifests/bbb.pp'))).not_to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION) expect(validate(parse('class aaa() {}', 'path/aaa/manifests/init.pp'))).not_to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION) expect(validate(parse('function aaa::bbB::ccc() {}', 'path/aaa/manifests/bBb.pp'))).not_to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION) expect(validate(parse('function aaa::bbb::ccc() {}', 'path/aaa/manifests/bbb/CCC.pp'))).not_to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION) end end it 'should not raise error for class locations when not parsing a file' do #nil/'' file means eval or some other way to get puppet language source code into the catalog with_environment(environment) do expect(validate(parse('function aaa::ccc() {}', nil))).not_to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION) expect(validate(parse('function aaa::ccc() {}', ''))).not_to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION) end end it 'should not raise error for definitions inside initial --manifest file' do with_environment(environment, :manifest => 'a/manifest/file.pp') do expect(validate(parse('class aaa() {}', 'a/manifest/file.pp'))).not_to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION) end end it 'should not raise error for definitions inside initial --manifest directory' do with_environment(environment, :manifest => 'a/manifest/dir') do expect(validate(parse('class aaa() {}', 'a/manifest/dir/file1.pp'))).not_to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION) expect(validate(parse('class bbb::ccc::ddd() {}', 'a/manifest/dir/and/more/stuff.pp'))).not_to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION) end end it 'should not raise error for definitions not inside initial --manifest but also not in modulepath' do with_environment(environment, :manifest => 'a/manifest/somewhere/else') do expect(validate(parse('class aaa() {}', 'a/random/dir/file1.pp'))).not_to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION) end end it 'should not raise error for empty files in modulepath' do with_environment(environment) do expect(validate(parse('', 'path/aaa/manifests/init.pp'))).not_to have_issue(Puppet::Pops::Issues::ILLEGAL_TOP_CONSTRUCT_LOCATION) expect(validate(parse('#this is a comment', 'path/aaa/manifests/init.pp'))).not_to have_issue(Puppet::Pops::Issues::ILLEGAL_TOP_CONSTRUCT_LOCATION) end end it 'should raise error if the file is in the modulepath but is not well formed' do with_environment(environment) do expect(validate(parse('class aaa::bbb::ccc() {}', 'path/manifest/aaa/bbb.pp'))).to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION) expect(validate(parse('class aaa::bbb::ccc() {}', 'path/aaa/bbb/manifest/ccc.pp'))).to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION) end end it 'should not raise error for definitions not inside initial --manifest but also not in modulepath because of only a case difference' do with_environment(environment) do expect(validate(parse('class aaa::bb() {}', 'Path/aaa/manifests/ccc.pp'))).not_to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION) end end it 'should not raise error when one modulepath is a substring of another' do with_environment(environment, modulepath: ['path', 'pathplus']) do expect(validate(parse('class aaa::ccc() {}', 'pathplus/aaa/manifests/ccc.pp'))).not_to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION) end end it 'should not raise error when a modulepath ends with a file separator' do with_environment(environment, modulepath: ['path/']) do expect(validate(parse('class aaa::ccc() {}', 'pathplus/aaa/manifests/ccc.pp'))).not_to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION) end end it 'should raise error for illegal type names' do expect(validate(parse('type ::Aaa = Any'))).to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_NAME) end it 'should raise error for illegal variable names' do expect(validate(fqn('Aaa').var())).to have_issue(Puppet::Pops::Issues::ILLEGAL_VAR_NAME) expect(validate(fqn('AAA').var())).to have_issue(Puppet::Pops::Issues::ILLEGAL_VAR_NAME) expect(validate(fqn('aaa::_aaa').var())).to have_issue(Puppet::Pops::Issues::ILLEGAL_VAR_NAME) end it 'should not raise error for variable name with underscore first in first name segment' do expect(validate(fqn('_aa').var())).to_not have_issue(Puppet::Pops::Issues::ILLEGAL_VAR_NAME) expect(validate(fqn('::_aa').var())).to_not have_issue(Puppet::Pops::Issues::ILLEGAL_VAR_NAME) end context 'with the default settings for --strict' do it 'produces a warning for duplicate keys in a literal hash' do acceptor = validate(parse('{ a => 1, a => 2 }')) expect(acceptor.warning_count).to eql(1) expect(acceptor.error_count).to eql(0) expect(acceptor).to have_issue(Puppet::Pops::Issues::DUPLICATE_KEY) end it 'produces a deprecation for illegal function locations' do with_environment(environment) do acceptor = validate(parse('function aaa::ccc() {}', 'path/aaa/manifests/bbb.pp')) expect(deprecation_count(acceptor)).to eql(1) expect(acceptor.warning_count).to eql(1) expect(acceptor.error_count).to eql(0) expect(acceptor).to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION) end end it 'produces a deprecation for illegal top level constructs' do with_environment(environment) do acceptor = validate(parse('$foo = 1', 'path/aaa/manifests/bbb.pp')) expect(deprecation_count(acceptor)).to eql(1) expect(acceptor.warning_count).to eql(1) expect(acceptor.error_count).to eql(0) expect(acceptor).to have_issue(Puppet::Pops::Issues::ILLEGAL_TOP_CONSTRUCT_LOCATION) end end end context 'with --strict set to warning' do before(:each) { Puppet[:strict] = :warning } it 'produces a warning for duplicate keys in a literal hash' do acceptor = validate(parse('{ a => 1, a => 2 }')) expect(acceptor.warning_count).to eql(1) expect(acceptor.error_count).to eql(0) expect(acceptor).to have_issue(Puppet::Pops::Issues::DUPLICATE_KEY) end it 'produces a warning for virtual class resource' do acceptor = validate(parse('@class { test: }')) expect(acceptor.warning_count).to eql(1) expect(acceptor.error_count).to eql(0) expect(acceptor).to have_issue(Puppet::Pops::Issues::CLASS_NOT_VIRTUALIZABLE) end it 'produces a warning for exported class resource' do acceptor = validate(parse('@@class { test: }')) expect(acceptor.warning_count).to eql(1) expect(acceptor.error_count).to eql(0) expect(acceptor).to have_issue(Puppet::Pops::Issues::CLASS_NOT_VIRTUALIZABLE) end it 'produces a deprecation for illegal function locations' do with_environment(environment) do acceptor = validate(parse('function aaa::ccc() {}', 'path/aaa/manifests/bbb.pp')) expect(deprecation_count(acceptor)).to eql(1) expect(acceptor.warning_count).to eql(1) expect(acceptor.error_count).to eql(0) expect(acceptor).to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION) end end it 'produces a deprecation for illegal top level constructs' do with_environment(environment) do acceptor = validate(parse('$foo = 1', 'path/aaa/manifests/bbb.pp')) expect(deprecation_count(acceptor)).to eql(1) expect(acceptor.warning_count).to eql(1) expect(acceptor.error_count).to eql(0) expect(acceptor).to have_issue(Puppet::Pops::Issues::ILLEGAL_TOP_CONSTRUCT_LOCATION) end end end context 'with --strict set to error' do before(:each) { Puppet[:strict] = :error } it 'produces an error for duplicate keys in a literal hash' do acceptor = validate(parse('{ a => 1, a => 2 }')) expect(acceptor.warning_count).to eql(0) expect(acceptor.error_count).to eql(1) expect(acceptor).to have_issue(Puppet::Pops::Issues::DUPLICATE_KEY) end it 'produces an error for virtual class resource' do acceptor = validate(parse('@class { test: }')) expect(acceptor.warning_count).to eql(0) expect(acceptor.error_count).to eql(1) expect(acceptor).to have_issue(Puppet::Pops::Issues::CLASS_NOT_VIRTUALIZABLE) end it 'does not produce an error for regular class resource' do acceptor = validate(parse('class { test: }')) expect(acceptor.warning_count).to eql(0) expect(acceptor.error_count).to eql(0) expect(acceptor).not_to have_issue(Puppet::Pops::Issues::CLASS_NOT_VIRTUALIZABLE) end it 'produces an error for exported class resource' do acceptor = validate(parse('@@class { test: }')) expect(acceptor.warning_count).to eql(0) expect(acceptor.error_count).to eql(1) expect(acceptor).to have_issue(Puppet::Pops::Issues::CLASS_NOT_VIRTUALIZABLE) end it 'produces a deprecation for illegal function locations' do with_environment(environment) do acceptor = validate(parse('function aaa::ccc() {}', 'path/aaa/manifests/bbb.pp')) expect(deprecation_count(acceptor)).to eql(1) expect(acceptor.warning_count).to eql(1) expect(acceptor.error_count).to eql(0) expect(acceptor).to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION) end end it 'produces a deprecation for illegal top level constructs' do with_environment(environment) do acceptor = validate(parse('$foo = 1', 'path/aaa/manifests/bbb.pp')) expect(deprecation_count(acceptor)).to eql(1) expect(acceptor.warning_count).to eql(1) expect(acceptor.error_count).to eql(0) expect(acceptor).to have_issue(Puppet::Pops::Issues::ILLEGAL_TOP_CONSTRUCT_LOCATION) end end end context 'with --strict set to off' do before(:each) { Puppet[:strict] = :off } it 'does not produce an error or warning for duplicate keys in a literal hash' do acceptor = validate(parse('{ a => 1, a => 2 }')) expect(acceptor.warning_count).to eql(0) expect(acceptor.error_count).to eql(0) expect(acceptor).to_not have_issue(Puppet::Pops::Issues::DUPLICATE_KEY) end it 'produces a deprecation for illegal function locations' do with_environment(environment) do acceptor = validate(parse('function aaa::ccc() {}', 'path/aaa/manifests/bbb.pp')) expect(deprecation_count(acceptor)).to eql(1) expect(acceptor.warning_count).to eql(1) expect(acceptor.error_count).to eql(0) expect(acceptor).to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION) end end it 'produces a deprecation for illegal top level constructs' do with_environment(environment) do acceptor = validate(parse('$foo = 1', 'path/aaa/manifests/bbb.pp')) expect(deprecation_count(acceptor)).to eql(1) expect(acceptor.warning_count).to eql(1) expect(acceptor.error_count).to eql(0) expect(acceptor).to have_issue(Puppet::Pops::Issues::ILLEGAL_TOP_CONSTRUCT_LOCATION) end end end context 'irrespective of --strict' do it 'produces an error for duplicate default in a case expression' do acceptor = validate(parse('case 1 { default: {1} default : {2} }')) expect(acceptor.warning_count).to eql(0) expect(acceptor.error_count).to eql(1) expect(acceptor).to have_issue(Puppet::Pops::Issues::DUPLICATE_DEFAULT) end it 'produces an error for duplicate default in a selector expression' do acceptor = validate(parse(' 1 ? { default => 1, default => 2 }')) expect(acceptor.warning_count).to eql(0) expect(acceptor.error_count).to eql(1) expect(acceptor).to have_issue(Puppet::Pops::Issues::DUPLICATE_DEFAULT) end it 'produces a warning for virtual class resource' do acceptor = validate(parse('@class { test: }')) expect(acceptor.warning_count).to eql(1) expect(acceptor.error_count).to eql(0) expect(acceptor).to have_issue(Puppet::Pops::Issues::CLASS_NOT_VIRTUALIZABLE) end it 'produces a warning for exported class resource' do acceptor = validate(parse('@@class { test: }')) expect(acceptor.warning_count).to eql(1) expect(acceptor.error_count).to eql(0) expect(acceptor).to have_issue(Puppet::Pops::Issues::CLASS_NOT_VIRTUALIZABLE) end end context 'with --tasks set' do before(:each) { Puppet[:tasks] = true } it 'raises an error for illegal plan names' do with_environment(environment) do expect(validate(parse('plan aaa::ccc::eee() {}', 'path/aaa/plans/bbb/ccc/eee.pp'))).to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION) expect(validate(parse('plan aaa() {}', 'path/aaa/plans/aaa.pp'))).to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION) expect(validate(parse('plan aaa::bbb() {}', 'path/aaa/plans/bbb/bbb.pp'))).to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION) end end it 'accepts legal plan names' do with_environment(environment) do expect(validate(parse('plan aaa::ccc::eee() {}', 'path/aaa/plans/ccc/eee.pp'))).not_to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION) expect(validate(parse('plan aaa() {}', 'path/aaa/plans/init.pp'))).not_to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION) expect(validate(parse('plan aaa::bbb() {}', 'path/aaa/plans/bbb.pp'))).not_to have_issue(Puppet::Pops::Issues::ILLEGAL_DEFINITION_LOCATION) end end it 'produces an error for application' do acceptor = validate(parse('application test {}')) expect(acceptor.error_count).to eql(1) expect(acceptor).to have_issue(Puppet::Pops::Issues::CATALOG_OPERATION_NOT_SUPPORTED_WHEN_SCRIPTING) end it 'produces an error for capability mapping' do acceptor = validate(parse('Foo produces Sql {}')) expect(acceptor.error_count).to eql(1) expect(acceptor).to have_issue(Puppet::Pops::Issues::CATALOG_OPERATION_NOT_SUPPORTED_WHEN_SCRIPTING) end it 'produces an error for collect expressions with virtual query' do acceptor = validate(parse("User <| title == 'admin' |>")) expect(acceptor.error_count).to eql(1) expect(acceptor).to have_issue(Puppet::Pops::Issues::CATALOG_OPERATION_NOT_SUPPORTED_WHEN_SCRIPTING) end it 'produces an error for collect expressions with exported query' do acceptor = validate(parse("User <<| title == 'admin' |>>")) expect(acceptor.error_count).to eql(1) expect(acceptor).to have_issue(Puppet::Pops::Issues::CATALOG_OPERATION_NOT_SUPPORTED_WHEN_SCRIPTING) end it 'produces an error for class expressions' do acceptor = validate(parse('class test {}')) expect(acceptor.error_count).to eql(1) expect(acceptor).to have_issue(Puppet::Pops::Issues::CATALOG_OPERATION_NOT_SUPPORTED_WHEN_SCRIPTING) end it 'produces an error for node expressions' do acceptor = validate(parse('node default {}')) expect(acceptor.error_count).to eql(1) expect(acceptor).to have_issue(Puppet::Pops::Issues::CATALOG_OPERATION_NOT_SUPPORTED_WHEN_SCRIPTING) end it 'produces an error for relationship expressions' do acceptor = validate(parse('$x -> $y')) expect(acceptor.error_count).to eql(1) expect(acceptor).to have_issue(Puppet::Pops::Issues::CATALOG_OPERATION_NOT_SUPPORTED_WHEN_SCRIPTING) end it 'produces an error for resource expressions' do acceptor = validate(parse('notify { nope: }')) expect(acceptor.error_count).to eql(1) expect(acceptor).to have_issue(Puppet::Pops::Issues::CATALOG_OPERATION_NOT_SUPPORTED_WHEN_SCRIPTING) end it 'produces an error for resource default expressions' do acceptor = validate(parse("File { mode => '0644' }")) expect(acceptor.error_count).to eql(1) expect(acceptor).to have_issue(Puppet::Pops::Issues::CATALOG_OPERATION_NOT_SUPPORTED_WHEN_SCRIPTING) end it 'produces an error for resource override expressions' do acceptor = validate(parse("File['/tmp/foo'] { mode => '0644' }")) expect(acceptor.error_count).to eql(1) expect(acceptor).to have_issue(Puppet::Pops::Issues::CATALOG_OPERATION_NOT_SUPPORTED_WHEN_SCRIPTING) end it 'produces an error for resource definitions' do acceptor = validate(parse('define foo($a) {}')) expect(acceptor.error_count).to eql(1) expect(acceptor).to have_issue(Puppet::Pops::Issues::CATALOG_OPERATION_NOT_SUPPORTED_WHEN_SCRIPTING) end it 'produces an error for site definitions' do acceptor = validate(parse('site {}')) expect(acceptor.error_count).to eql(1) expect(acceptor).to have_issue(Puppet::Pops::Issues::CATALOG_OPERATION_NOT_SUPPORTED_WHEN_SCRIPTING) end end context 'for non productive expressions' do [ '1', '3.14', "'a'", '"a"', '"${$a=10}"', # interpolation with side effect 'false', 'true', 'default', 'undef', '[1,2,3]', '{a=>10}', 'if 1 {2}', 'if 1 {2} else {3}', 'if 1 {2} elsif 3 {4}', 'unless 1 {2}', 'unless 1 {2} else {3}', '1 ? 2 => 3', '1 ? { 2 => 3}', '-1', '-foo()', # unary minus on productive '1+2', '1<2', '(1<2)', '!true', '!foo()', # not on productive '$a', '$a[1]', 'name', 'Type', 'Type[foo]' ].each do |expr| it "produces error for non productive: #{expr}" do source = "#{expr}; $a = 10" expect(validate(parse(source))).to have_issue(Puppet::Pops::Issues::IDEM_EXPRESSION_NOT_LAST) end it "does not produce error when last for non productive: #{expr}" do source = " $a = 10; #{expr}" expect(validate(parse(source))).to_not have_issue(Puppet::Pops::Issues::IDEM_EXPRESSION_NOT_LAST) end end [ 'if 1 {$a = 1}', 'if 1 {2} else {$a=1}', 'if 1 {2} elsif 3 {$a=1}', 'unless 1 {$a=1}', 'unless 1 {2} else {$a=1}', '$a = 1 ? 2 => 3', '$a = 1 ? { 2 => 3}', 'Foo[a] -> Foo[b]', '($a=1)', 'foo()', '$a.foo()', '"foo" =~ /foo/', # may produce or modify $n vars '"foo" !~ /foo/', # may produce or modify $n vars ].each do |expr| it "does not produce error when for productive: #{expr}" do source = "#{expr}; $x = 1" expect(validate(parse(source))).to_not have_issue(Puppet::Pops::Issues::IDEM_EXPRESSION_NOT_LAST) end end ['class', 'define', 'node'].each do |type| it "flags non productive expression last in #{type}" do source = <<-SOURCE #{type} nope { 1 } end SOURCE expect(validate(parse(source))).to have_issue(Puppet::Pops::Issues::IDEM_NOT_ALLOWED_LAST) end it "detects a resource declared without title in #{type} when it is the only declaration present" do source = <<-SOURCE #{type} nope { notify { message => 'Nope' } } SOURCE expect(validate(parse(source))).to have_issue(Puppet::Pops::Issues::RESOURCE_WITHOUT_TITLE) end it "detects a resource declared without title in #{type} when it is in between other declarations" do source = <<-SOURCE #{type} nope { notify { succ: message => 'Nope' } notify { message => 'Nope' } notify { pred: message => 'Nope' } } SOURCE expect(validate(parse(source))).to have_issue(Puppet::Pops::Issues::RESOURCE_WITHOUT_TITLE) end it "detects a resource declared without title in #{type} when it is declarated first" do source = <<-SOURCE #{type} nope { notify { message => 'Nope' } notify { pred: message => 'Nope' } } SOURCE expect(validate(parse(source))).to have_issue(Puppet::Pops::Issues::RESOURCE_WITHOUT_TITLE) end it "detects a resource declared without title in #{type} when it is declarated last" do source = <<-SOURCE #{type} nope { notify { succ: message => 'Nope' } notify { message => 'Nope' } } SOURCE expect(validate(parse(source))).to have_issue(Puppet::Pops::Issues::RESOURCE_WITHOUT_TITLE) end end end context 'for reserved words' do ['private', 'attr'].each do |word| it "produces an error for the word '#{word}'" do source = "$a = #{word}" expect(validate(parse(source))).to have_issue(Puppet::Pops::Issues::RESERVED_WORD) end end end context 'for reserved type names' do [# type/Type, is a reserved name but results in syntax error because it is a keyword in lower case form 'any', 'unit', 'scalar', 'boolean', 'numeric', 'integer', 'float', 'collection', 'array', 'hash', 'tuple', 'struct', 'variant', 'optional', 'enum', 'regexp', 'pattern', 'runtime', ].each do |name| it "produces an error for 'class #{name}'" do source = "class #{name} {}" expect(validate(parse(source))).to have_issue(Puppet::Pops::Issues::RESERVED_TYPE_NAME) end it "produces an error for 'define #{name}'" do source = "define #{name} {}" expect(validate(parse(source))).to have_issue(Puppet::Pops::Issues::RESERVED_TYPE_NAME) end end end context 'for keywords' do it "should allow using the 'type' as the name of a function with no parameters" do source = "type()" expect(validate(parse(source))).not_to have_any_issues end it "should allow using the keyword 'type' as the name of a function with parameters" do source = "type('a', 'b')" expect(validate(parse(source))).not_to have_any_issues end it "should allow using the 'type' as the name of a function with no parameters and a block" do source = "type() |$x| { $x }" expect(validate(parse(source))).not_to have_any_issues end it "should allow using the keyword 'type' as the name of a function with parameters and a block" do source = "type('a', 'b') |$x| { $x }" expect(validate(parse(source))).not_to have_any_issues end end context 'for parameter names' do ['class', 'define'].each do |word| it "should require that #{word} parameter names are unique" do expect(validate(parse("#{word} foo($a = 10, $a = 20) {}"))).to have_issue(Puppet::Pops::Issues::DUPLICATE_PARAMETER) end end it "should require that template parameter names are unique" do expect(validate(parse_epp("<%-| $a, $a |-%><%= $a == doh %>"))).to have_issue(Puppet::Pops::Issues::DUPLICATE_PARAMETER) end end context 'for parameter defaults' do ['class', 'define'].each do |word| it "should not permit assignments in #{word} parameter default expressions" do expect { parse("#{word} foo($a = $x = 10) {}") }.to raise_error(Puppet::ParseErrorWithIssue, /Syntax error at '='/) end end ['class', 'define'].each do |word| it "should not permit assignments in #{word} parameter default nested expressions" do expect(validate(parse("#{word} foo($a = [$x = 10]) {}"))).to have_issue(Puppet::Pops::Issues::ILLEGAL_ASSIGNMENT_CONTEXT) end it "should not permit assignments to subsequently declared parameters in #{word} parameter default nested expressions" do expect(validate(parse("#{word} foo($a = ($b = 3), $b = 5) {}"))).to have_issue(Puppet::Pops::Issues::ILLEGAL_ASSIGNMENT_CONTEXT) end it "should not permit assignments to previously declared parameters in #{word} parameter default nested expressions" do expect(validate(parse("#{word} foo($a = 10, $b = ($a = 10)) {}"))).to have_issue(Puppet::Pops::Issues::ILLEGAL_ASSIGNMENT_CONTEXT) end it "should permit assignments in #{word} parameter default inside nested lambda expressions" do expect(validate(parse( "#{word} foo($a = [1,2,3], $b = 0, $c = $a.map |$x| { $b = $x; $b * $a.reduce |$x, $y| {$x + $y}}) {}"))).not_to( have_issue(Puppet::Pops::Issues::ILLEGAL_ASSIGNMENT_CONTEXT)) end end end context 'for reserved parameter names' do ['name', 'title'].each do |word| it "produces an error when $#{word} is used as a parameter in a class" do source = "class x ($#{word}) {}" expect(validate(parse(source))).to have_issue(Puppet::Pops::Issues::RESERVED_PARAMETER) end it "produces an error when $#{word} is used as a parameter in a define" do source = "define x ($#{word}) {}" expect(validate(parse(source))).to have_issue(Puppet::Pops::Issues::RESERVED_PARAMETER) end end end context 'for numeric parameter names' do ['1', '0x2', '03'].each do |word| it "produces an error when $#{word} is used as a parameter in a class" do source = "class x ($#{word}) {}" expect(validate(parse(source))).to have_issue(Puppet::Pops::Issues::ILLEGAL_NUMERIC_PARAMETER) end end end context 'for badly formed non-numeric parameter names' do ['Ateam', 'a::team'].each do |word| it "produces an error when $#{word} is used as a parameter in a class" do source = "class x ($#{word}) {}" expect(validate(parse(source))).to have_issue(Puppet::Pops::Issues::ILLEGAL_PARAM_NAME) end it "produces an error when $#{word} is used as a parameter in a define" do source = "define x ($#{word}) {}" expect(validate(parse(source))).to have_issue(Puppet::Pops::Issues::ILLEGAL_PARAM_NAME) end it "produces an error when $#{word} is used as a parameter in a lambda" do source = "with() |$#{word}| {}" expect(validate(parse(source))).to have_issue(Puppet::Pops::Issues::ILLEGAL_PARAM_NAME) end end end context 'top level constructs' do def issue(at_top) at_top ? Puppet::Pops::Issues::NOT_ABSOLUTE_TOP_LEVEL : Puppet::Pops::Issues::NOT_TOP_LEVEL end # Top level. Defines the expressions that are tested inside of other things { 'a class' => ['class x{}', false], 'a define' => ['define x{}', false], 'a node' => ['node x{}', false], 'a function' => ['function x() {}', true], 'a type alias' => ['type A = Data', true], 'a type alias for a complex type' => ['type C = Hash[String[1],Integer]', true], 'a type definition' => ['type A {}', true] }.each_pair do |word, (decl, at_top)| # Nesting level. Defines how each of the top level expressions are nested in # another expression { 'a define' => ["define y{ #{decl} }", at_top], 'a function' => ["function y() { #{decl} }", at_top], 'a type definition' => ["type A { #{decl} }", at_top], 'an if expression' => ["if true { #{decl} }", false], 'an if-else expression' => ["if false {} else { #{decl} }", false], 'an unless' => ["unless false { #{decl} }", false] }.each_pair do |nester, (source, abs_top)| # Tests each top level expression in each nested expression it "produces an error when #{word} is nested in #{nester}" do expect(validate(parse(source))).to have_issue(issue(abs_top)) end end # Test that the expression can exist anywhere in a top level block it "will allow #{word} as the only statement in a top level block" do expect(validate(parse(decl))).not_to have_issue(issue(at_top)) end it "will allow #{word} as the last statement in a top level block" do source = "$a = 10\n#{decl}" expect(validate(parse(source))).not_to have_issue(issue(at_top)) end it "will allow #{word} as the first statement in a top level block" do source = "#{decl}\n$a = 10" expect(validate(parse(source))).not_to have_issue(issue(at_top)) end it "will allow #{word} in between other statements in a top level block" do source = "$a = 10\n#{decl}\n$b = 20" expect(validate(parse(source))).not_to have_issue(issue(at_top)) end end context 'that are type aliases' do it 'raises errors when RHS is a name that is an invalid reference' do source = 'type MyInt = integer' expect { parse(source) }.to raise_error(/Syntax error at 'integer'/) end it 'raises errors when RHS is an AccessExpression with a name that is an invalid reference on LHS' do source = 'type IntegerArray = array[Integer]' expect { parse(source) }.to raise_error(/Syntax error at 'array'/) end end context 'that are functions' do it 'accepts typed parameters' do source = <<-CODE function f(Integer $a) { $a } CODE expect(validate(parse(source))).not_to have_any_issues end it 'accepts return types' do source = <<-CODE function f() >> Integer { 42 } CODE expect(validate(parse(source))).not_to have_any_issues end it 'accepts block with return types' do source = <<-CODE map([1,2]) |Integer $x| >> Integer { $x + 3 } CODE expect(validate(parse(source))).not_to have_any_issues end end context 'that are type mappings' do it 'accepts a valid type mapping expression' do source = <<-CODE type Runtime[ruby, 'MyModule::MyObject'] = MyPackage::MyObject notice(true) CODE expect(validate(parse(source))).not_to have_any_issues end it 'accepts a valid regexp based type mapping expression' do source = <<-CODE type Runtime[ruby, [/^MyPackage::(\w+)$/, 'MyModule::\1']] = [/^MyModule::(\w+)$/, 'MyPackage::\1'] notice(true) CODE expect(validate(parse(source))).not_to have_any_issues end it 'raises an error when a regexp based Runtime type is paired with a Puppet Type' do source = <<-CODE type Runtime[ruby, [/^MyPackage::(\w+)$/, 'MyModule::\1']] = MyPackage::MyObject notice(true) CODE expect(validate(parse(source))).to have_issue(Puppet::Pops::Issues::ILLEGAL_REGEXP_TYPE_MAPPING) end it 'raises an error when a singleton Runtime type is paired with replacement pattern' do source = <<-CODE type Runtime[ruby, 'MyModule::MyObject'] = [/^MyModule::(\w+)$/, 'MyPackage::\1'] notice(true) CODE expect(validate(parse(source))).to have_issue(Puppet::Pops::Issues::ILLEGAL_SINGLE_TYPE_MAPPING) end it 'raises errors unless LHS is Runtime type' do source = <<-CODE type Pattern[/^MyPackage::(\w+)$/, 'MyModule::\1'] = [/^MyModule::(\w+)$/, 'MyPackage::\1'] notice(true) CODE expect(validate(parse(source))).to have_issue(Puppet::Pops::Issues::UNSUPPORTED_EXPRESSION) end end end context 'top level constructs' do { 'a class' => 'class x{}', 'a define' => 'define x{}', 'a function' => 'function x() {}', 'a type alias' => 'type A = Data', 'a type alias for a complex type' => 'type C = Hash[String[1],Integer]', 'a type definition' => 'type A {}', }.each_pair do |word, source| it "will not have an issue with #{word} at the top level in a module" do with_environment(environment) do expect(validate(parse(source, 'path/x/manifests/init.pp'))).not_to have_issue(Puppet::Pops::Issues::ILLEGAL_TOP_CONSTRUCT_LOCATION) end end end end context 'non-top level constructs' do { 'an assignment' => '$foo = 1', 'a resource' => 'notify { nope: }', 'a resource default' => "Notify { message => 'yo' }", 'a function call' => "include 'foo'", 'a node definition' => 'node default {}', 'an expression' => '1+1', 'a conditional' => 'if true {42}', 'a literal value' => '42', 'a virtual collector' => 'User <| tag == web |>', 'an exported collector' => 'Sshkey <<| |>>', }.each_pair do |word, source| it "will have an issue with #{word} at the top level in a module" do with_environment(environment) do expect(validate(parse(source, 'path/x/manifests/init.pp'))).to have_issue(Puppet::Pops::Issues::ILLEGAL_TOP_CONSTRUCT_LOCATION) end end it "will not have an issue with #{word} at top level not in a module" do with_environment(environment) do expect(validate(parse(source))).not_to have_issue(Puppet::Pops::Issues::ILLEGAL_TOP_CONSTRUCT_LOCATION) end end end it "will give multiple errors in one file with multiple issues" do source = <<-SOURCE class foo {} notify { nope: } node bar {} $a = 7 SOURCE with_environment(environment) do acceptor = validate(parse(source, 'path/foo/manifests/init.pp')) expect(deprecation_count(acceptor)).to eql(3) expect(acceptor.warning_count).to eql(3) expect(acceptor.error_count).to eql(0) expect(acceptor.warnings[0].source_pos.line).to eql(2) expect(acceptor.warnings[1].source_pos.line).to eql(3) expect(acceptor.warnings[2].source_pos.line).to eql(5) end end end context "capability annotations" do ['produces', 'consumes'].each do |word| it "rejects illegal resource types in #{word} clauses" do expect(validate(parse("foo produces Bar {}"))).to have_issue(Puppet::Pops::Issues::ILLEGAL_CLASSREF) end it "accepts legal resource and capability types in #{word} clauses" do expect(validate(parse("Foo produces Bar {}"))).to_not have_issue(Puppet::Pops::Issues::ILLEGAL_CLASSREF) expect(validate(parse("Mod::Foo produces ::Mod2::Bar {}"))).to_not have_issue(Puppet::Pops::Issues::ILLEGAL_CLASSREF) end it "rejects illegal capability types in #{word} clauses" do expect(validate(parse("Foo produces bar {}"))).to have_issue(Puppet::Pops::Issues::ILLEGAL_CLASSREF) end end end context 'literal values' do it 'rejects a literal integer outside of max signed 64 bit range' do expect(validate(parse("0x8000000000000000"))).to have_issue(Puppet::Pops::Issues::NUMERIC_OVERFLOW) end it 'rejects a literal integer outside of min signed 64 bit range' do expect(validate(parse("-0x8000000000000001"))).to have_issue(Puppet::Pops::Issues::NUMERIC_OVERFLOW) end end context 'uses a var pattern that is performant' do it 'such that illegal VAR_NAME is not too slow' do t = Time.now.nsec result = '$hg_oais::archivematica::requirements::automation_tools::USER' =~ Puppet::Pops::Patterns::VAR_NAME t2 = Time.now.nsec expect(result).to be(nil) expect(t2-t).to be < 1000000 # one ms as a check for very slow operation, is in fact at ~< 10 microsecond end end def parse(source, path=nil) Puppet::Pops::Parser::Parser.new.parse_string(source, path) end end puppet-5.5.10/spec/unit/pops/visitor_spec.rb0000644005276200011600000000525413417161721020750 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/pops' describe Puppet::Pops::Visitor do describe "A visitor and a visitable in a configuration with min and max args set to 0" do class DuckProcessor def initialize @friend_visitor = Puppet::Pops::Visitor.new(self, "friend", 0, 0) end def hi(o, *args) @friend_visitor.visit(o, *args) end def friend_Duck(o) "Hi #{o.class}" end def friend_Numeric(o) "Howdy #{o.class}" end end class Duck include Puppet::Pops::Visitable end it "should select the expected method when there are no arguments" do duck = Duck.new duck_processor = DuckProcessor.new expect(duck_processor.hi(duck)).to eq("Hi Duck") end it "should fail if there are too many arguments" do duck = Duck.new duck_processor = DuckProcessor.new expect { duck_processor.hi(duck, "how are you?") }.to raise_error(/^Visitor Error: Too many.*/) end it "should select method for superclass" do duck_processor = DuckProcessor.new expect(duck_processor.hi(42)).to match(/Howdy (?:Fixnum|Integer)/) end it "should select method for superclass" do duck_processor = DuckProcessor.new expect(duck_processor.hi(42.0)).to eq("Howdy Float") end it "should fail if class not handled" do duck_processor = DuckProcessor.new expect { duck_processor.hi("wassup?") }.to raise_error(/Visitor Error: the configured.*/) end end describe "A visitor and a visitable in a configuration with min =1, and max args set to 2" do class DuckProcessor2 def initialize @friend_visitor = Puppet::Pops::Visitor.new(self, "friend", 1, 2) end def hi(o, *args) @friend_visitor.visit(o, *args) end def friend_Duck(o, drink, eat="grain") "Hi #{o.class}, drink=#{drink}, eat=#{eat}" end end class Duck include Puppet::Pops::Visitable end it "should select the expected method when there are is one arguments" do duck = Duck.new duck_processor = DuckProcessor2.new expect(duck_processor.hi(duck, "water")).to eq("Hi Duck, drink=water, eat=grain") end it "should fail if there are too many arguments" do duck = Duck.new duck_processor = DuckProcessor2.new expect { duck_processor.hi(duck, "scotch", "soda", "peanuts") }.to raise_error(/^Visitor Error: Too many.*/) end it "should fail if there are too few arguments" do duck = Duck.new duck_processor = DuckProcessor2.new expect { duck_processor.hi(duck) }.to raise_error(/^Visitor Error: Too few.*/) end end end puppet-5.5.10/spec/unit/property/0000755005276200011600000000000013417162177016615 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/property/boolean_spec.rb0000644005276200011600000000134413417161721021567 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/property/boolean' describe Puppet::Property::Boolean do let (:resource) { mock('resource') } subject { described_class.new(:resource => resource) } [ true, :true, 'true', :yes, 'yes', 'TrUe', 'yEs' ].each do |arg| it "should munge #{arg.inspect} as true" do expect(subject.munge(arg)).to eq(true) end end [ false, :false, 'false', :no, 'no', 'FaLSE', 'nO' ].each do |arg| it "should munge #{arg.inspect} as false" do expect(subject.munge(arg)).to eq(false) end end [ nil, :undef, 'undef', '0', 0, '1', 1, 9284 ].each do |arg| it "should fail to munge #{arg.inspect}" do expect { subject.munge(arg) }.to raise_error Puppet::Error end end end puppet-5.5.10/spec/unit/property/ensure_spec.rb0000644005276200011600000000035413417161721021451 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/property/ensure' klass = Puppet::Property::Ensure describe klass do it "should be a subclass of Property" do expect(klass.superclass).to eq(Puppet::Property) end end puppet-5.5.10/spec/unit/property/keyvalue_spec.rb0000644005276200011600000002201513417161721021773 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/property/keyvalue' describe 'Puppet::Property::KeyValue' do let(:klass) { Puppet::Property::KeyValue } it "should be a subclass of Property" do expect(klass.superclass).to eq(Puppet::Property) end describe "as an instance" do before do # Wow that's a messy interface to the resource. klass.initvars @resource = stub 'resource', :[]= => nil, :property => nil @property = klass.new(:resource => @resource) klass.log_only_changed_or_new_keys = false end it "should have a , as default delimiter" do expect(@property.delimiter).to eq(";") end it "should have a = as default separator" do expect(@property.separator).to eq("=") end it "should have a :membership as default membership" do expect(@property.membership).to eq(:key_value_membership) end it "should return the same value passed into should_to_s" do @property.should_to_s({:foo => "baz", :bar => "boo"}) == "foo=baz;bar=boo" end it "should return the passed in hash values joined with the delimiter from is_to_s" do s = @property.is_to_s({"foo" => "baz" , "bar" => "boo"}) # We can't predict the order the hash is processed in... expect(["foo=baz;bar=boo", "bar=boo;foo=baz"]).to be_include s end describe "when calling hash_to_key_value_s" do let(:input) do { :key1 => "value1", :key2 => "value2", :key3 => "value3" } end before(:each) do @property.instance_variable_set(:@changed_or_new_keys, [:key1, :key2]) end it "returns only the changed or new keys if log_only_changed_or_new_keys is set" do klass.log_only_changed_or_new_keys = true expect(@property.hash_to_key_value_s(input)).to eql("key1=value1;key2=value2") end end describe "when calling inclusive?" do it "should use the membership method to look up on the @resource" do @property.expects(:membership).returns(:key_value_membership) @resource.expects(:[]).with(:key_value_membership) @property.inclusive? end it "should return true when @resource[membership] == inclusive" do @property.stubs(:membership).returns(:key_value_membership) @resource.stubs(:[]).with(:key_value_membership).returns(:inclusive) expect(@property.inclusive?).to eq(true) end it "should return false when @resource[membership] != inclusive" do @property.stubs(:membership).returns(:key_value_membership) @resource.stubs(:[]).with(:key_value_membership).returns(:minimum) expect(@property.inclusive?).to eq(false) end end describe "when calling process_current_hash" do it "should return {} if hash is :absent" do expect(@property.process_current_hash(:absent)).to eq({}) end it "should set every key to nil if inclusive?" do @property.stubs(:inclusive?).returns(true) expect(@property.process_current_hash({:foo => "bar", :do => "re"})).to eq({ :foo => nil, :do => nil }) end it "should return the hash if !inclusive?" do @property.stubs(:inclusive?).returns(false) expect(@property.process_current_hash({:foo => "bar", :do => "re"})).to eq({:foo => "bar", :do => "re"}) end end describe "when calling should" do it "should return nil if @should is nil" do expect(@property.should).to eq(nil) end it "should call process_current_hash" do @property.should = ["foo=baz", "bar=boo"] @property.stubs(:retrieve).returns({:do => "re", :mi => "fa" }) @property.expects(:process_current_hash).returns({}) @property.should end it "should return the hashed values of @should and the nilled values of retrieve if inclusive" do @property.should = ["foo=baz", "bar=boo"] @property.expects(:retrieve).returns({:do => "re", :mi => "fa" }) @property.expects(:inclusive?).returns(true) expect(@property.should).to eq({ :foo => "baz", :bar => "boo", :do => nil, :mi => nil }) end it "should return the hashed @should + the unique values of retrieve if !inclusive" do @property.should = ["foo=baz", "bar=boo"] @property.expects(:retrieve).returns({:foo => "diff", :do => "re", :mi => "fa"}) @property.expects(:inclusive?).returns(false) expect(@property.should).to eq({ :foo => "baz", :bar => "boo", :do => "re", :mi => "fa" }) end it "should mark the keys that will change or be added as a result of our Puppet run" do @property.should = { :key1 => "new_value1", :key2 => "value2", :key3 => "new_value3", :key4 => "value4" } @property.stubs(:retrieve).returns( { :key1 => "value1", :key2 => "value2", :key3 => "value3" } ) @property.stubs(:inclusive?).returns(false) @property.should expect(@property.instance_variable_get(:@changed_or_new_keys)).to eql([:key1, :key3, :key4]) end end describe "when calling retrieve" do before do @provider = mock("provider") @property.stubs(:provider).returns(@provider) end it "should send 'name' to the provider" do @provider.expects(:send).with(:keys) @property.expects(:name).returns(:keys) @property.retrieve end it "should return a hash with the provider returned info" do @provider.stubs(:send).with(:keys).returns({"do" => "re", "mi" => "fa" }) @property.stubs(:name).returns(:keys) @property.retrieve == {"do" => "re", "mi" => "fa" } end it "should return :absent when the provider returns :absent" do @provider.stubs(:send).with(:keys).returns(:absent) @property.stubs(:name).returns(:keys) @property.retrieve == :absent end end describe "when calling hashify_should" do it "should return the underlying hash if the user passed in a hash" do @property.should = { "foo" => "bar" } expect(@property.hashify_should).to eql({ :foo => "bar" }) end it "should hashify the array of key/value pairs if that is what our user passed in" do @property.should = [ "foo=baz", "bar=boo" ] expect(@property.hashify_should).to eq({ :foo => "baz", :bar => "boo" }) end end describe "when calling safe_insync?" do before do @provider = mock("provider") @property.stubs(:provider).returns(@provider) @property.stubs(:name).returns(:prop_name) end it "should return true unless @should is defined and not nil" do @property.safe_insync?("foo") == true end it "should return true if the passed in values is nil" do @property.safe_insync?(nil) == true end it "should return true if hashified should value == (retrieved) value passed in" do @provider.stubs(:prop_name).returns({ :foo => "baz", :bar => "boo" }) @property.should = ["foo=baz", "bar=boo"] @property.expects(:inclusive?).returns(true) expect(@property.safe_insync?({ :foo => "baz", :bar => "boo" })).to eq(true) end it "should return false if prepared value != should value" do @provider.stubs(:prop_name).returns({ "foo" => "bee", "bar" => "boo" }) @property.should = ["foo=baz", "bar=boo"] @property.expects(:inclusive?).returns(true) expect(@property.safe_insync?({ "foo" => "bee", "bar" => "boo" })).to eq(false) end end describe 'when validating a passed-in property value' do it 'should raise a Puppet::Error if the property value is anything but a Hash or a String' do expect { @property.validate(5) }.to raise_error do |error| expect(error).to be_a(Puppet::Error) expect(error.message).to match("specified as a hash or an array") end end it 'should accept a Hash property value' do @property.validate({ 'foo' => 'bar' }) end it "should raise a Puppet::Error if the property value isn't a key/value pair" do expect { @property.validate('foo') }.to raise_error do |error| expect(error).to be_a(Puppet::Error) expect(error.message).to match("separated by '='") end end it 'should accept a valid key/value pair property value' do @property.validate('foo=bar') end end describe 'when munging a passed-in property value' do it 'should return the value as-is if it is a string' do expect(@property.munge('foo=bar')).to eql('foo=bar') end it 'should stringify + symbolize the keys and stringify the values if it is a hash' do input = { 1 => 2, true => false, ' foo ' => 'bar' } expected_output = { :'1' => '2', :true => 'false', :foo => 'bar' } expect(@property.munge(input)).to eql(expected_output) end end end end puppet-5.5.10/spec/unit/property/list_spec.rb0000644005276200011600000001334213417161721021124 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/property/list' list_class = Puppet::Property::List describe list_class do it "should be a subclass of Property" do expect(list_class.superclass).to eq(Puppet::Property) end describe "as an instance" do before do # Wow that's a messy interface to the resource. list_class.initvars @resource = stub 'resource', :[]= => nil, :property => nil @property = list_class.new(:resource => @resource) end it "should have a , as default delimiter" do expect(@property.delimiter).to eq(",") end it "should have a :membership as default membership" do expect(@property.membership).to eq(:membership) end it "should return the same value passed into should_to_s" do @property.should_to_s("foo") == "foo" end it "should return the passed in array values joined with the delimiter from is_to_s" do expect(@property.is_to_s(["foo","bar"])).to eq("foo,bar") end it "should be able to correctly convert ':absent' to a quoted string" do expect(@property.is_to_s(:absent)).to eq("'absent'") end describe "when adding should to current" do it "should add the arrays when current is an array" do expect(@property.add_should_with_current(["foo"], ["bar"])).to eq(["foo", "bar"]) end it "should return should if current is not an array" do expect(@property.add_should_with_current(["foo"], :absent)).to eq(["foo"]) end it "should return only the uniq elements" do expect(@property.add_should_with_current(["foo", "bar"], ["foo", "baz"])).to eq(["foo", "bar", "baz"]) end end describe "when calling inclusive?" do it "should use the membership method to look up on the @resource" do @property.expects(:membership).returns(:membership) @resource.expects(:[]).with(:membership) @property.inclusive? end it "should return true when @resource[membership] == inclusive" do @property.stubs(:membership).returns(:membership) @resource.stubs(:[]).with(:membership).returns(:inclusive) expect(@property.inclusive?).to eq(true) end it "should return false when @resource[membership] != inclusive" do @property.stubs(:membership).returns(:membership) @resource.stubs(:[]).with(:membership).returns(:minimum) expect(@property.inclusive?).to eq(false) end end describe "when calling should" do it "should return nil if @should is nil" do expect(@property.should).to eq(nil) end it "should return the sorted values of @should as a string if inclusive" do @property.should = ["foo", "bar"] @property.expects(:inclusive?).returns(true) expect(@property.should).to eq("bar,foo") end it "should return the uniq sorted values of @should + retrieve as a string if !inclusive" do @property.should = ["foo", "bar"] @property.expects(:inclusive?).returns(false) @property.expects(:retrieve).returns(["foo","baz"]) expect(@property.should).to eq("bar,baz,foo") end end describe "when calling retrieve" do let(:provider) { @provider } before do @provider = mock("provider") @property.stubs(:provider).returns(provider) end it "should send 'name' to the provider" do @provider.expects(:send).with(:group) @property.expects(:name).returns(:group) @property.retrieve end it "should return an array with the provider returned info" do @provider.stubs(:send).with(:group).returns("foo,bar,baz") @property.stubs(:name).returns(:group) @property.retrieve == ["foo", "bar", "baz"] end it "should return :absent when the provider returns :absent" do @provider.stubs(:send).with(:group).returns(:absent) @property.stubs(:name).returns(:group) @property.retrieve == :absent end context "and provider is nil" do let(:provider) { nil } it "should return :absent" do @property.retrieve == :absent end end end describe "when calling safe_insync?" do it "should return true unless @should is defined and not nil" do expect(@property).to be_safe_insync("foo") end it "should return true unless the passed in values is not nil" do @property.should = "foo" expect(@property).to be_safe_insync(nil) end it "should call prepare_is_for_comparison with value passed in and should" do @property.should = "foo" @property.expects(:prepare_is_for_comparison).with("bar") @property.expects(:should) @property.safe_insync?("bar") end it "should return true if 'is' value is array of comma delimited should values" do @property.should = "bar,foo" @property.expects(:inclusive?).returns(true) expect(@property).to be_safe_insync(["bar","foo"]) end it "should return true if 'is' value is :absent and should value is empty string" do @property.should = "" @property.expects(:inclusive?).returns(true) expect(@property).to be_safe_insync([]) end it "should return false if prepared value != should value" do @property.should = "bar,baz,foo" @property.expects(:inclusive?).returns(true) expect(@property).to_not be_safe_insync(["bar","foo"]) end end describe "when calling dearrayify" do it "should sort and join the array with 'delimiter'" do array = mock "array" array.expects(:sort).returns(array) array.expects(:join).with(@property.delimiter) @property.dearrayify(array) end end end end puppet-5.5.10/spec/unit/property/ordered_list_spec.rb0000644005276200011600000000420513417161721022626 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/property/ordered_list' ordered_list_class = Puppet::Property::OrderedList describe ordered_list_class do it "should be a subclass of List" do expect(ordered_list_class.superclass).to eq(Puppet::Property::List) end describe "as an instance" do before do # Wow that's a messy interface to the resource. ordered_list_class.initvars @resource = stub 'resource', :[]= => nil, :property => nil @property = ordered_list_class.new(:resource => @resource) end describe "when adding should to current" do it "should add the arrays when current is an array" do expect(@property.add_should_with_current(["should"], ["current"])).to eq(["should", "current"]) end it "should return 'should' if current is not an array" do expect(@property.add_should_with_current(["should"], :absent)).to eq(["should"]) end it "should return only the uniq elements leading with the order of 'should'" do expect(@property.add_should_with_current(["this", "is", "should"], ["is", "this", "current"])).to eq(["this", "is", "should", "current"]) end end describe "when calling should" do it "should return nil if @should is nil" do expect(@property.should).to eq(nil) end it "should return the values of @should (without sorting) as a string if inclusive" do @property.should = ["foo", "bar"] @property.expects(:inclusive?).returns(true) expect(@property.should).to eq("foo,bar") end it "should return the uniq values of @should + retrieve as a string if !inclusive with the @ values leading" do @property.should = ["foo", "bar"] @property.expects(:inclusive?).returns(false) @property.expects(:retrieve).returns(["foo","baz"]) expect(@property.should).to eq("foo,bar,baz") end end describe "when calling dearrayify" do it "should join the array with the delimiter" do array = mock "array" array.expects(:join).with(@property.delimiter) @property.dearrayify(array) end end end end puppet-5.5.10/spec/unit/provider/0000755005276200011600000000000013417162177016563 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/provider/README.markdown0000644005276200011600000000012513417161721021254 0ustar jenkinsjenkinsProvider Specs ============== Define specs for your providers under this directory. puppet-5.5.10/spec/unit/provider/aix_object_spec.rb0000644005276200011600000005667113417161721022242 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/provider/aix_object' describe 'Puppet::Provider::AixObject' do let(:resource) do Puppet::Type.type(:user).new( :name => 'test_aix_user', :ensure => :present ) end let(:klass) { Puppet::Provider::AixObject } let(:provider) do Puppet::Provider::AixObject.new(resource) end # Clear out the class-level + instance-level mappings def clear_attributes klass.instance_variable_set(:@mappings, nil) end before(:each) do clear_attributes end describe '.mapping' do let(:puppet_property) { :uid } let(:aix_attribute) { :id } let(:info) do { :puppet_property => puppet_property, :aix_attribute => aix_attribute } end shared_examples 'a mapping' do |from, to| context "<#{from}> => <#{to}>" do let(:from_suffix) { from.to_s.split("_")[-1] } let(:to_suffix) { to.to_s.split("_")[-1] } let(:conversion_fn) do "convert_#{from_suffix}_value".to_sym end it 'creates the mapping for a pure conversion function and defines it' do conversion_fn_lambda = "#{from_suffix}_to_#{to_suffix}".to_sym info[conversion_fn_lambda] = lambda { |x| x.to_s } provider.class.mapping(info) mappings = provider.class.mappings[to] expect(mappings).to include(info[from]) mapping = mappings[info[from]] expect(mapping.public_methods).to include(conversion_fn) expect(mapping.send(conversion_fn, 3)).to eql('3') end it 'creates the mapping for an impure conversion function without defining it' do conversion_fn_lambda = "#{from_suffix}_to_#{to_suffix}".to_sym info[conversion_fn_lambda] = lambda { |provider, x| x.to_s } provider.class.mapping(info) mappings = provider.class.mappings[to] expect(mappings).to include(info[from]) mapping = mappings[info[from]] expect(mapping.public_methods).not_to include(conversion_fn) end it 'uses the identity function as the conversion function if none is provided' do provider.class.mapping(info) mappings = provider.class.mappings[to] expect(mappings).to include(info[from]) mapping = mappings[info[from]] expect(mapping.public_methods).to include(conversion_fn) expect(mapping.send(conversion_fn, 3)).to eql(3) end end end include_examples 'a mapping', :puppet_property, :aix_attribute include_examples 'a mapping', :aix_attribute, :puppet_property it 'sets the AIX attribute to the Puppet property if it is not provided' do info[:aix_attribute] = nil provider.class.mapping(info) mappings = provider.class.mappings[:puppet_property] expect(mappings).to include(info[:puppet_property]) end end describe '.numeric_mapping' do let(:info) do info_hash = { :puppet_property => :uid, :aix_attribute => :id } provider.class.numeric_mapping(info_hash) info_hash end let(:aix_attribute) do provider.class.mappings[:aix_attribute][info[:puppet_property]] end let(:puppet_property) do provider.class.mappings[:puppet_property][info[:aix_attribute]] end it 'raises an ArgumentError for a non-numeric Puppet property value' do value = 'foo' expect do aix_attribute.convert_property_value(value) end.to raise_error do |error| expect(error).to be_a(ArgumentError) expect(error.message).to match(value) expect(error.message).to match(info[:puppet_property].to_s) end end it 'converts the numeric Puppet property to a numeric AIX attribute' do expect(aix_attribute.convert_property_value(10)).to eql('10') end it 'converts the numeric AIX attribute to a numeric Puppet property' do expect(puppet_property.convert_attribute_value('10')).to eql(10) end end describe '.mk_resource_methods' do before(:each) do # Add some Puppet properties provider.class.mapping( puppet_property: :foo, aix_attribute: :foo ) provider.class.mapping( puppet_property: :bar, aix_attribute: :bar ) provider.class.mk_resource_methods end it 'defines the property getters' do provider = Puppet::Provider::AixObject.new(resource) provider.instance_variable_set(:@object_info, { :foo => 'foo', :baz => 'baz' }) (provider.class.mappings[:aix_attribute].keys + [:attributes]).each do |property| provider.expects(:get).with(property).returns('value') expect(provider.send(property)).to eql('value') end end it 'defines the property setters' do provider = Puppet::Provider::AixObject.new(resource) value = '15' provider.class.mappings[:aix_attribute].keys.each do |property| provider.expects(:set).with(property, value) provider.send("#{property}=".to_sym, value) end end end describe '.parse_colon_separated_list' do it 'parses a single empty item' do input = '' output = [''] expect(provider.class.parse_colon_separated_list(input)).to eql(output) end it 'parses a single nonempty item' do input = 'item' output = ['item'] expect(provider.class.parse_colon_separated_list(input)).to eql(output) end it "parses an escaped ':'" do input = '#!:' output = [':'] expect(provider.class.parse_colon_separated_list(input)).to eql(output) end it "parses a single item with an escaped ':'" do input = 'fd8c#!:215d#!:178#!:' output = ['fd8c:215d:178:'] expect(provider.class.parse_colon_separated_list(input)).to eql(output) end it "parses multiple items that do not have an escaped ':'" do input = "foo:bar baz:buu:1234" output = ["foo", "bar baz", "buu", "1234"] expect(provider.class.parse_colon_separated_list(input)).to eql(output) end it "parses multiple items some of which have escaped ':'" do input = "1234#!:567:foo bar#!:baz:buu#!bob:sally:fd8c#!:215d#!:178" output = ["1234:567", "foo bar:baz", "buu#!bob", "sally", 'fd8c:215d:178'] expect(provider.class.parse_colon_separated_list(input)).to eql(output) end it "parses a list with several empty items" do input = "foo:::bar:baz:boo:" output = ["foo", "", "", "bar", "baz", "boo", ""] expect(provider.class.parse_colon_separated_list(input)).to eql(output) end it "parses a list with an escaped ':' and empty item at the end" do input = "foo:bar#!::" output = ["foo", "bar:", ""] expect(provider.class.parse_colon_separated_list(input)).to eql(output) end it 'parses a real world example' do input = File.read(my_fixture('aix_colon_list_real_world_input.out')).chomp output = Object.instance_eval(File.read(my_fixture('aix_colon_list_real_world_output.out'))) expect(provider.class.parse_colon_separated_list(input)).to eql(output) end end describe '.parse_aix_objects' do # parse_colon_separated_list is well tested, so we don't need to be # as strict on the formatting of the output here. Main point of these # tests is to capture the 'wholemeal' parsing that's going on, i.e. # that we can parse a bunch of objects together. let(:output) do <<-AIX_OBJECTS #name:id:pgrp:groups root:0:system:system,bin,sys,security,cron,audit,lp #name:id:pgrp:groups:home:gecos user:10000:staff:staff:/home/user3:Some User AIX_OBJECTS end let(:expected_aix_attributes) do [ { :name => 'root', :attributes => { :id => '0', :pgrp => 'system', :groups => 'system,bin,sys,security,cron,audit,lp', } }, { :name => 'user', :attributes => { :id => '10000', :pgrp => 'staff', :groups => 'staff', :home => '/home/user3', :gecos => 'Some User' } } ] end it 'parses the AIX attributes from the command output' do expect(provider.class.parse_aix_objects(output)).to eql(expected_aix_attributes) end end describe 'list_all' do let(:output) do <<-OUTPUT #name:id system:0 #name:id staff:1 #name:id bin:2 OUTPUT end it 'lists all of the objects' do lscmd = 'lsgroups' provider.class.stubs(:command).with(:list).returns(lscmd) provider.class.stubs(:execute).with([lscmd, '-c', '-a', 'id', 'ALL']).returns(output) expected_objects = [ { :name => 'system', :id => '0' }, { :name => 'staff', :id => '1' }, { :name => 'bin', :id => '2' } ] expect(provider.class.list_all).to eql(expected_objects) end end describe '.instances' do let(:objects) do [ { :name => 'group1', :id => '1' }, { :name => 'group2', :id => '2' } ] end it 'returns all of the available instances' do provider.class.stubs(:list_all).returns(objects) expect(provider.class.instances.map(&:name)).to eql(['group1', 'group2']) end end describe '#mappings' do # Returns a pair [ instance_level_mapped_object, class_level_mapped_object ] def mapped_objects(type, input) [ provider.mappings[type][input], provider.class.mappings[type][input] ] end before(:each) do # Create a pure mapping provider.class.numeric_mapping( puppet_property: :pure_puppet_property, aix_attribute: :pure_aix_attribute ) # Create an impure mapping impure_conversion_fn = lambda do |provider, value| "Provider instance's name is #{provider.name}" end provider.class.mapping( puppet_property: :impure_puppet_property, aix_attribute: :impure_aix_attribute, property_to_attribute: impure_conversion_fn, attribute_to_property: impure_conversion_fn ) end it 'memoizes the result' do provider.instance_variable_set(:@mappings, 'memoized') expect(provider.mappings).to eql('memoized') end it 'creates the instance-level mappings with the same structure as the class-level one' do expect(provider.mappings.keys).to eql(provider.class.mappings.keys) provider.mappings.keys.each do |type| expect(provider.mappings[type].keys).to eql(provider.class.mappings[type].keys) end end shared_examples 'uses the right mapped object for a given mapping' do |from_type, to_type| context "<#{from_type}> => <#{to_type}>" do it 'shares the class-level mapped object for pure mappings' do input = "pure_#{from_type}".to_sym instance_level_mapped_object, class_level_mapped_object = mapped_objects(to_type, input) expect(instance_level_mapped_object.object_id).to eql(class_level_mapped_object.object_id) end it 'dups the class-level mapped object for impure mappings' do input = "impure_#{from_type}".to_sym instance_level_mapped_object, class_level_mapped_object = mapped_objects(to_type, input) expect(instance_level_mapped_object.object_id).to_not eql( class_level_mapped_object.object_id ) end it 'defines the conversion function for impure mappings' do from_type_suffix = from_type.to_s.split("_")[-1] conversion_fn = "convert_#{from_type_suffix}_value".to_sym input = "impure_#{from_type}".to_sym mapped_object, _ = mapped_objects(to_type, input) expect(mapped_object.public_methods).to include(conversion_fn) expect(mapped_object.send(conversion_fn, 3)).to match(provider.name) end end end include_examples 'uses the right mapped object for a given mapping', :puppet_property, :aix_attribute include_examples 'uses the right mapped object for a given mapping', :aix_attribute, :puppet_property end describe '#attributes_to_args' do let(:attributes) do { :attribute1 => 'value1', :attribute2 => 'value2' } end it 'converts the attributes hash to CLI arguments' do expect(provider.attributes_to_args(attributes)).to eql( ["attribute1=value1", "attribute2=value2"] ) end end describe '#ia_module_args' do it 'returns no arguments if the ia_load_module parameter is not specified' do provider.resource.stubs(:[]).with(:ia_load_module).returns(nil) expect(provider.ia_module_args).to eql([]) end it 'returns the ia_load_module as a CLI argument' do provider.resource.stubs(:[]).with(:ia_load_module).returns('module') expect(provider.ia_module_args).to eql(['-R', 'module']) end end describe '#lscmd' do it 'returns the lscmd' do provider.class.stubs(:command).with(:list).returns('list') provider.stubs(:ia_module_args).returns(['ia_module_args']) expect(provider.lscmd).to eql( ['list', '-c', 'ia_module_args', provider.resource.name] ) end end describe '#addcmd' do let(:attributes) do { :attribute1 => 'value1', :attribute2 => 'value2' } end it 'returns the addcmd passing in the attributes as CLI arguments' do provider.class.stubs(:command).with(:add).returns('add') provider.stubs(:ia_module_args).returns(['ia_module_args']) expect(provider.addcmd(attributes)).to eql( ['add', 'ia_module_args', 'attribute1=value1', 'attribute2=value2', provider.resource.name] ) end end describe '#deletecmd' do it 'returns the lscmd' do provider.class.stubs(:command).with(:delete).returns('delete') provider.stubs(:ia_module_args).returns(['ia_module_args']) expect(provider.deletecmd).to eql( ['delete', 'ia_module_args', provider.resource.name] ) end end describe '#modifycmd' do let(:attributes) do { :attribute1 => 'value1', :attribute2 => 'value2' } end it 'returns the addcmd passing in the attributes as CLI arguments' do provider.class.stubs(:command).with(:modify).returns('modify') provider.stubs(:ia_module_args).returns(['ia_module_args']) expect(provider.modifycmd(attributes)).to eql( ['modify', 'ia_module_args', 'attribute1=value1', 'attribute2=value2', provider.resource.name] ) end end describe '#modify_object' do let(:new_attributes) do { :nofiles => 10000, :fsize => 30000 } end it 'modifies the AIX object with the new attributes' do provider.stubs(:modifycmd).with(new_attributes).returns('modify_cmd') provider.expects(:execute).with('modify_cmd') provider.expects(:object_info).with(true) provider.modify_object(new_attributes) end end describe '#get' do # Input let(:property) { :uid } let!(:object_info) do hash = {} provider.instance_variable_set(:@object_info, hash) hash end it 'returns :absent if the AIX object does not exist' do provider.stubs(:exists?).returns(false) object_info[property] = 15 expect(provider.get(property)).to eql(:absent) end it 'returns :absent if the property is not present on the system' do provider.stubs(:exists?).returns(true) expect(provider.get(property)).to eql(:absent) end it "returns the property's value" do provider.stubs(:exists?).returns(true) object_info[property] = 15 expect(provider.get(property)).to eql(15) end end describe '#set' do # Input let(:property) { :uid } let(:value) { 10 } # AIX attribute params let(:aix_attribute) { :id } let(:property_to_attribute) do lambda { |x| x.to_s } end before(:each) do # Add an attribute provider.class.mapping( puppet_property: property, aix_attribute: aix_attribute, property_to_attribute: property_to_attribute ) end it "raises a Puppet::Error if it fails to set the property's value" do provider.stubs(:modify_object) .with({ :id => value.to_s }) .raises(Puppet::ExecutionFailure, 'failed to modify the AIX object!') expect { provider.set(property, value) }.to raise_error do |error| expect(error).to be_a(Puppet::Error) end end it "sets the given property's value to the passed-in value" do provider.expects(:modify_object).with({ :id => value.to_s }) provider.set(property, value) end end describe '#validate_new_attributes' do let(:new_attributes) do { :nofiles => 10000, :fsize => 100000 } end it 'raises a Puppet::Error if a specified attributes corresponds to a Puppet property, reporting all of the attribute-property conflicts' do provider.class.mapping(puppet_property: :uid, aix_attribute: :id) provider.class.mapping(puppet_property: :groups, aix_attribute: :groups) new_attributes[:id] = '25' new_attributes[:groups] = 'groups' expect { provider.validate_new_attributes(new_attributes) }.to raise_error do |error| expect(error).to be_a(Puppet::Error) expect(error.message).to match("'uid', 'groups'") expect(error.message).to match("'id', 'groups'") end end end describe '#attributes=' do let(:new_attributes) do { :nofiles => 10000, :fsize => 100000 } end it 'raises a Puppet::Error if one of the specified attributes corresponds to a Puppet property' do provider.class.mapping(puppet_property: :uid, aix_attribute: :id) new_attributes[:id] = '25' expect { provider.attributes = new_attributes }.to raise_error do |error| expect(error).to be_a(Puppet::Error) expect(error.message).to match('uid') expect(error.message).to match('id') end end it 'raises a Puppet::Error if it fails to set the new AIX attributes' do provider.stubs(:modify_object) .with(new_attributes) .raises(Puppet::ExecutionFailure, 'failed to modify the AIX object!') expect { provider.attributes = new_attributes }.to raise_error do |error| expect(error).to be_a(Puppet::Error) expect(error.message).to match('failed to modify the AIX object!') end end it 'sets the new AIX attributes' do provider.expects(:modify_object).with(new_attributes) provider.attributes = new_attributes end end describe '#object_info' do before(:each) do # Add some Puppet properties provider.class.mapping( puppet_property: :uid, aix_attribute: :id, attribute_to_property: lambda { |x| x.to_i }, ) provider.class.mapping( puppet_property: :groups, aix_attribute: :groups ) # Mock out our lscmd provider.stubs(:lscmd).returns("lsuser #{resource[:name]}") end it 'memoizes the result' do provider.instance_variable_set(:@object_info, {}) expect(provider.object_info).to eql({}) end it 'returns nil if the AIX object does not exist' do provider.stubs(:execute).with(provider.lscmd).raises( Puppet::ExecutionFailure, 'lscmd failed!' ) expect(provider.object_info).to be_nil end it 'collects the Puppet properties' do output = 'mock_output' provider.stubs(:execute).with(provider.lscmd).returns(output) # Mock the AIX attributes on the system mock_attributes = { :id => '1', :groups => 'foo,bar,baz', :attribute1 => 'value1', :attribute2 => 'value2' } provider.class.stubs(:parse_aix_objects) .with(output) .returns([{ :name => resource.name, :attributes => mock_attributes }]) expected_property_values = { :uid => 1, :groups => 'foo,bar,baz', :attributes => { :attribute1 => 'value1', :attribute2 => 'value2' } } provider.object_info expect(provider.instance_variable_get(:@object_info)).to eql(expected_property_values) end end describe '#exists?' do it 'should return true if the AIX object exists' do provider.stubs(:object_info).returns({}) expect(provider.exists?).to be(true) end it 'should return false if the AIX object does not exist' do provider.stubs(:object_info).returns(nil) expect(provider.exists?).to be(false) end end describe "#create" do let(:property_attributes) do {} end def stub_attributes_property(attributes) provider.resource.stubs(:should).with(:attributes).returns(attributes) end def set_property(puppet_property, aix_attribute, property_to_attribute, should_value = nil) property_to_attribute ||= lambda { |x| x } provider.class.mapping( puppet_property: puppet_property, aix_attribute: aix_attribute, property_to_attribute: property_to_attribute ) provider.resource.stubs(:should).with(puppet_property).returns(should_value) if should_value property_attributes[aix_attribute] = property_to_attribute.call(should_value) end end before(:each) do clear_attributes # Clear out the :attributes property. We will be setting this later. stub_attributes_property(nil) # Add some properties set_property(:uid, :id, lambda { |x| x.to_s }, 10) set_property(:groups, :groups, nil, 'group1,group2,group3') set_property(:shell, :shell, nil) end it 'raises a Puppet::Error if one of the specified attributes corresponds to a Puppet property' do stub_attributes_property({ :id => 15 }) provider.class.mapping(puppet_property: :uid, aix_attribute: :id) expect { provider.create }.to raise_error do |error| expect(error).to be_a(Puppet::Error) expect(error.message).to match('uid') expect(error.message).to match('id') end end it "raises a Puppet::Error if it fails to create the AIX object" do provider.stubs(:addcmd) provider.stubs(:execute).raises( Puppet::ExecutionFailure, "addcmd failed!" ) expect { provider.create }.to raise_error do |error| expect(error).to be_a(Puppet::Error) expect(error.message).to match("not create") end end it "creates the AIX object with the given AIX attributes + Puppet properties" do attributes = { :fsize => 1000 } stub_attributes_property(attributes) provider.expects(:addcmd) .with(attributes.merge(property_attributes)) .returns('addcmd') provider.expects(:execute).with('addcmd') provider.create end end describe "#delete" do before(:each) do provider.stubs(:deletecmd).returns('deletecmd') end it "raises a Puppet::Error if it fails to delete the AIX object" do provider.stubs(:execute).with(provider.deletecmd).raises( Puppet::ExecutionFailure, "deletecmd failed!" ) expect { provider.delete }.to raise_error do |error| expect(error).to be_a(Puppet::Error) expect(error.message).to match("not delete") end end it "deletes the AIX object" do provider.expects(:execute).with(provider.deletecmd) provider.expects(:object_info).with(true) provider.delete end end end puppet-5.5.10/spec/unit/provider/command_spec.rb0000644005276200011600000000356513417161721021543 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/provider/command' describe Puppet::Provider::Command do let(:name) { "the name" } let(:the_options) { { :option => 1 } } let(:no_options) { {} } let(:executable) { "foo" } let(:executable_absolute_path) { "/foo/bar" } let(:executor) { mock('executor') } let(:resolver) { mock('resolver') } let(:path_resolves_to_itself) do resolves = Object.new class << resolves def which(path) path end end resolves end it "executes a simple command" do executor.expects(:execute).with([executable], no_options) command = Puppet::Provider::Command.new(name, executable, path_resolves_to_itself, executor) command.execute() end it "executes a command with extra options" do executor.expects(:execute).with([executable], the_options) command = Puppet::Provider::Command.new(name, executable, path_resolves_to_itself, executor, the_options) command.execute() end it "executes a command with arguments" do executor.expects(:execute).with([executable, "arg1", "arg2"], no_options) command = Puppet::Provider::Command.new(name, executable, path_resolves_to_itself, executor) command.execute("arg1", "arg2") end it "resolves to an absolute path for better execution" do resolver.expects(:which).with(executable).returns(executable_absolute_path) executor.expects(:execute).with([executable_absolute_path], no_options) command = Puppet::Provider::Command.new(name, executable, resolver, executor) command.execute() end it "errors when the executable resolves to nothing" do resolver.expects(:which).with(executable).returns(nil) executor.expects(:execute).never command = Puppet::Provider::Command.new(name, executable, resolver, executor) expect { command.execute() }.to raise_error(Puppet::Error, "Command #{name} is missing") end end puppet-5.5.10/spec/unit/provider/exec/0000755005276200011600000000000013417162177017507 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/provider/exec/posix_spec.rb0000644005276200011600000002024713417161722022210 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:exec).provider(:posix), :if => Puppet.features.posix? do include PuppetSpec::Files def make_exe cmdpath = tmpdir('cmdpath') exepath = tmpfile('my_command', cmdpath) FileUtils.touch(exepath) File.chmod(0755, exepath) exepath end let(:resource) { Puppet::Type.type(:exec).new(:title => '/foo', :provider => :posix) } let(:provider) { described_class.new(resource) } describe "#validatecmd" do it "should fail if no path is specified and the command is not fully qualified" do expect { provider.validatecmd("foo") }.to raise_error( Puppet::Error, "'foo' is not qualified and no path was specified. Please qualify the command or specify a path." ) end it "should pass if a path is given" do provider.resource[:path] = ['/bogus/bin'] provider.validatecmd("../foo") end it "should pass if command is fully qualifed" do provider.resource[:path] = ['/bogus/bin'] provider.validatecmd("/bin/blah/foo") end end describe "#run" do describe "when the command is an absolute path" do let(:command) { tmpfile('foo') } it "should fail if the command doesn't exist" do expect { provider.run(command) }.to raise_error(ArgumentError, "Could not find command '#{command}'") end it "should fail if the command isn't a file" do FileUtils.mkdir(command) FileUtils.chmod(0755, command) expect { provider.run(command) }.to raise_error(ArgumentError, "'#{command}' is a directory, not a file") end it "should fail if the command isn't executable" do FileUtils.touch(command) File.stubs(:executable?).with(command).returns(false) expect { provider.run(command) }.to raise_error(ArgumentError, "'#{command}' is not executable") end end describe "when the command is a relative path" do it "should execute the command if it finds it in the path and is executable" do command = make_exe provider.resource[:path] = [File.dirname(command)] filename = File.basename(command) Puppet::Util::Execution.expects(:execute).with(filename, instance_of(Hash)).returns(Puppet::Util::Execution::ProcessOutput.new('', 0)) provider.run(filename) end it "should fail if the command isn't in the path" do resource[:path] = ["/fake/path"] expect { provider.run('foo') }.to raise_error(ArgumentError, "Could not find command 'foo'") end it "should fail if the command is in the path but not executable" do command = make_exe File.chmod(0644, command) FileTest.stubs(:executable?).with(command).returns(false) resource[:path] = [File.dirname(command)] filename = File.basename(command) expect { provider.run(filename) }.to raise_error(ArgumentError, "Could not find command '#{filename}'") end end it "should not be able to execute shell builtins" do provider.resource[:path] = ['/bogus/bin'] expect { provider.run("cd ..") }.to raise_error(ArgumentError, "Could not find command 'cd'") end it "should execute the command if the command given includes arguments or subcommands" do provider.resource[:path] = ['/bogus/bin'] command = make_exe Puppet::Util::Execution.expects(:execute).with("#{command} bar --sillyarg=true --blah", instance_of(Hash)).returns(Puppet::Util::Execution::ProcessOutput.new('', 0)) provider.run("#{command} bar --sillyarg=true --blah") end it "should fail if quoted command doesn't exist" do provider.resource[:path] = ['/bogus/bin'] command = "/foo bar --sillyarg=true --blah" expect { provider.run(%Q["#{command}"]) }.to raise_error(ArgumentError, "Could not find command '#{command}'") end it "should warn if you're overriding something in environment" do provider.resource[:environment] = ['WHATEVER=/something/else', 'WHATEVER=/foo'] command = make_exe Puppet::Util::Execution.expects(:execute).with(command, instance_of(Hash)).returns(Puppet::Util::Execution::ProcessOutput.new('', 0)) provider.run(command) expect(@logs.map {|l| "#{l.level}: #{l.message}" }).to eq(["warning: Overriding environment setting 'WHATEVER' with '/foo'"]) end it "should set umask before execution if umask parameter is in use" do provider.resource[:umask] = '0027' Puppet::Util.expects(:withumask).with(0027) provider.run(provider.resource[:command]) end describe "posix locale settings" do # a sentinel value that we can use to emulate what locale environment variables might be set to on an international # system. lang_sentinel_value = "en_US.UTF-8" # a temporary hash that contains sentinel values for each of the locale environment variables that we override in # "exec" locale_sentinel_env = {} Puppet::Util::POSIX::LOCALE_ENV_VARS.each { |var| locale_sentinel_env[var] = lang_sentinel_value } command = "/bin/echo $%s" it "should not override user's locale during execution" do # we'll do this once without any sentinel values, to give us a little more test coverage orig_env = {} Puppet::Util::POSIX::LOCALE_ENV_VARS.each { |var| orig_env[var] = ENV[var] if ENV[var] } orig_env.keys.each do |var| output, _ = provider.run(command % var) expect(output.strip).to eq(orig_env[var]) end # now, once more... but with our sentinel values Puppet::Util.withenv(locale_sentinel_env) do Puppet::Util::POSIX::LOCALE_ENV_VARS.each do |var| output, _ = provider.run(command % var) expect(output.strip).to eq(locale_sentinel_env[var]) end end end it "should respect locale overrides in user's 'environment' configuration" do provider.resource[:environment] = ['LANG=C', 'LC_ALL=C'] output, _ = provider.run(command % 'LANG') expect(output.strip).to eq('C') output, _ = provider.run(command % 'LC_ALL') expect(output.strip).to eq('C') end end describe "posix user-related environment vars" do # a temporary hash that contains sentinel values for each of the user-related environment variables that we # are expected to unset during an "exec" user_sentinel_env = {} Puppet::Util::POSIX::USER_ENV_VARS.each { |var| user_sentinel_env[var] = "Abracadabra" } command = "/bin/echo $%s" it "should unset user-related environment vars during execution" do # first we set up a temporary execution environment with sentinel values for the user-related environment vars # that we care about. Puppet::Util.withenv(user_sentinel_env) do # with this environment, we loop over the vars in question Puppet::Util::POSIX::USER_ENV_VARS.each do |var| # ensure that our temporary environment is set up as we expect expect(ENV[var]).to eq(user_sentinel_env[var]) # run an "exec" via the provider and ensure that it unsets the vars output, _ = provider.run(command % var) expect(output.strip).to eq("") # ensure that after the exec, our temporary env is still intact expect(ENV[var]).to eq(user_sentinel_env[var]) end end end it "should respect overrides to user-related environment vars in caller's 'environment' configuration" do sentinel_value = "Abracadabra" # set the "environment" property of the resource, populating it with a hash containing sentinel values for # each of the user-related posix environment variables provider.resource[:environment] = Puppet::Util::POSIX::USER_ENV_VARS.collect { |var| "#{var}=#{sentinel_value}"} # loop over the posix user-related environment variables Puppet::Util::POSIX::USER_ENV_VARS.each do |var| # run an 'exec' to get the value of each variable output, _ = provider.run(command % var) # ensure that it matches our expected sentinel value expect(output.strip).to eq(sentinel_value) end end end end end puppet-5.5.10/spec/unit/provider/exec/shell_spec.rb0000644005276200011600000000362513417161722022156 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:exec).provider(:shell), :unless => Puppet.features.microsoft_windows? do let(:resource) { Puppet::Type.type(:exec).new(:title => 'foo', :provider => 'shell') } let(:provider) { described_class.new(resource) } describe "#run" do it "should be able to run builtin shell commands" do output, status = provider.run("if [ 1 = 1 ]; then echo 'blah'; fi") expect(status.exitstatus).to eq(0) expect(output).to eq("blah\n") end it "should be able to run commands with single quotes in them" do output, status = provider.run("echo 'foo bar'") expect(status.exitstatus).to eq(0) expect(output).to eq("foo bar\n") end it "should be able to run commands with double quotes in them" do output, status = provider.run('echo "foo bar"') expect(status.exitstatus).to eq(0) expect(output).to eq("foo bar\n") end it "should be able to run multiple commands separated by a semicolon" do output, status = provider.run("echo 'foo' ; echo 'bar'") expect(status.exitstatus).to eq(0) expect(output).to eq("foo\nbar\n") end it "should be able to read values from the environment parameter" do resource[:environment] = "FOO=bar" output, status = provider.run("echo $FOO") expect(status.exitstatus).to eq(0) expect(output).to eq("bar\n") end it "#14060: should interpolate inside the subshell, not outside it" do resource[:environment] = "foo=outer" output, status = provider.run("foo=inner; echo \"foo is $foo\"") expect(status.exitstatus).to eq(0) expect(output).to eq("foo is inner\n") end end describe "#validatecmd" do it "should always return true because builtins don't need path or to be fully qualified" do expect(provider.validatecmd('whateverdoesntmatter')).to eq(true) end end end puppet-5.5.10/spec/unit/provider/exec/windows_spec.rb0000644005276200011600000000713513417161722022541 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:exec).provider(:windows), :if => Puppet.features.microsoft_windows? do include PuppetSpec::Files let(:resource) { Puppet::Type.type(:exec).new(:title => 'C:\foo', :provider => :windows) } let(:provider) { described_class.new(resource) } after :all do # This provider may not be suitable on some machines, so we want to reset # the default so it isn't used by mistake in future specs. Puppet::Type.type(:exec).defaultprovider = nil end describe "#extractexe" do describe "when the command has no arguments" do it "should return the command if it's quoted" do expect(provider.extractexe('"foo"')).to eq('foo') end it "should return the command if it's quoted and contains spaces" do expect(provider.extractexe('"foo bar"')).to eq('foo bar') end it "should return the command if it's not quoted" do expect(provider.extractexe('foo')).to eq('foo') end end describe "when the command has arguments" do it "should return the command if it's quoted" do expect(provider.extractexe('"foo" bar baz')).to eq('foo') end it "should return the command if it's quoted and contains spaces" do expect(provider.extractexe('"foo bar" baz "quux quiz"')).to eq('foo bar') end it "should return the command if it's not quoted" do expect(provider.extractexe('foo bar baz')).to eq('foo') end end end describe "#checkexe" do describe "when the command is absolute", :if => Puppet.features.microsoft_windows? do it "should return if the command exists and is a file" do command = tmpfile('command') FileUtils.touch(command) expect(provider.checkexe(command)).to eq(nil) end it "should fail if the command doesn't exist" do command = tmpfile('command') expect { provider.checkexe(command) }.to raise_error(ArgumentError, "Could not find command '#{command}'") end it "should fail if the command isn't a file" do command = tmpfile('command') FileUtils.mkdir(command) expect { provider.checkexe(command) }.to raise_error(ArgumentError, "'#{command}' is a directory, not a file") end end describe "when the command is relative" do describe "and a path is specified" do before :each do provider.stubs(:which) end it "should search for executables with no extension" do provider.resource[:path] = [File.expand_path('/bogus/bin')] provider.expects(:which).with('foo').returns('foo') provider.checkexe('foo') end it "should fail if the command isn't in the path" do expect { provider.checkexe('foo') }.to raise_error(ArgumentError, "Could not find command 'foo'") end end it "should fail if no path is specified" do expect { provider.checkexe('foo') }.to raise_error(ArgumentError, "Could not find command 'foo'") end end end describe "#validatecmd" do it "should fail if the command isn't absolute and there is no path" do expect { provider.validatecmd('foo') }.to raise_error(Puppet::Error, /'foo' is not qualified and no path was specified/) end it "should not fail if the command is absolute and there is no path" do expect(provider.validatecmd('C:\foo')).to eq(nil) end it "should not fail if the command is not absolute and there is a path" do resource[:path] = 'C:\path;C:\another_path' expect(provider.validatecmd('foo')).to eq(nil) end end end puppet-5.5.10/spec/unit/provider/exec_spec.rb0000644005276200011600000000244313417161721021043 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/provider/exec' describe Puppet::Provider::Exec do describe "#extractexe" do it "should return the first element of an array" do expect(subject.extractexe(['one', 'two'])).to eq('one') end { # double-quoted commands %q{"/has whitespace"} => "/has whitespace", %q{"/no/whitespace"} => "/no/whitespace", # singe-quoted commands %q{'/has whitespace'} => "/has whitespace", %q{'/no/whitespace'} => "/no/whitespace", # combinations %q{"'/has whitespace'"} => "'/has whitespace'", %q{'"/has whitespace"'} => '"/has whitespace"', %q{"/has 'special' characters"} => "/has 'special' characters", %q{'/has "special" characters'} => '/has "special" characters', # whitespace split commands %q{/has whitespace} => "/has", %q{/no/whitespace} => "/no/whitespace", }.each do |base_command, exe| ['', ' and args', ' "and args"', " 'and args'"].each do |args| command = base_command + args it "should extract #{exe.inspect} from #{command.inspect}" do expect(subject.extractexe(command)).to eq(exe) end end end end end puppet-5.5.10/spec/unit/provider/file/0000755005276200011600000000000013417162177017502 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/provider/file/posix_spec.rb0000644005276200011600000001540713417161721022204 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:file).provider(:posix), :if => Puppet.features.posix? do include PuppetSpec::Files let(:path) { tmpfile('posix_file_spec') } let(:resource) { Puppet::Type.type(:file).new :path => path, :mode => '0777', :provider => described_class.name } let(:provider) { resource.provider } describe "#mode" do it "should return a string with the higher-order bits stripped away" do FileUtils.touch(path) File.chmod(0644, path) expect(provider.mode).to eq('0644') end it "should return absent if the file doesn't exist" do expect(provider.mode).to eq(:absent) end end describe "#mode=" do it "should chmod the file to the specified value" do FileUtils.touch(path) File.chmod(0644, path) provider.mode = '0755' expect(provider.mode).to eq('0755') end it "should pass along any errors encountered" do expect do provider.mode = '0644' end.to raise_error(Puppet::Error, /failed to set mode/) end end describe "#uid2name" do it "should return the name of the user identified by the id" do Etc.stubs(:getpwuid).with(501).returns(Struct::Passwd.new('jilluser', nil, 501)) expect(provider.uid2name(501)).to eq('jilluser') end it "should return the argument if it's already a name" do expect(provider.uid2name('jilluser')).to eq('jilluser') end it "should return nil if the argument is above the maximum uid" do expect(provider.uid2name(Puppet[:maximum_uid] + 1)).to eq(nil) end it "should return nil if the user doesn't exist" do Etc.expects(:getpwuid).raises(ArgumentError, "can't find user for 999") expect(provider.uid2name(999)).to eq(nil) end end describe "#name2uid" do it "should return the id of the user if it exists" do passwd = Struct::Passwd.new('bobbo', nil, 502) Etc.stubs(:getpwnam).with('bobbo').returns(passwd) Etc.stubs(:getpwuid).with(502).returns(passwd) expect(provider.name2uid('bobbo')).to eq(502) end it "should return the argument if it's already an id" do expect(provider.name2uid('503')).to eq(503) end it "should return false if the user doesn't exist" do Etc.stubs(:getpwnam).with('chuck').raises(ArgumentError, "can't find user for chuck") expect(provider.name2uid('chuck')).to eq(false) end end describe "#owner" do it "should return the uid of the file owner" do FileUtils.touch(path) owner = Puppet::FileSystem.stat(path).uid expect(provider.owner).to eq(owner) end it "should return absent if the file can't be statted" do expect(provider.owner).to eq(:absent) end it "should warn and return :silly if the value is beyond the maximum uid" do stat = stub('stat', :uid => Puppet[:maximum_uid] + 1) resource.stubs(:stat).returns(stat) expect(provider.owner).to eq(:silly) expect(@logs).to be_any {|log| log.level == :warning and log.message =~ /Apparently using negative UID/} end end describe "#owner=" do it "should set the owner but not the group of the file" do File.expects(:lchown).with(15, nil, resource[:path]) provider.owner = 15 end it "should chown a link if managing links" do resource[:links] = :manage File.expects(:lchown).with(20, nil, resource[:path]) provider.owner = 20 end it "should chown a link target if following links" do resource[:links] = :follow File.expects(:chown).with(20, nil, resource[:path]) provider.owner = 20 end it "should pass along any error encountered setting the owner" do File.expects(:lchown).raises(ArgumentError) expect { provider.owner = 25 }.to raise_error(Puppet::Error, /Failed to set owner to '25'/) end end describe "#gid2name" do it "should return the name of the group identified by the id" do Etc.stubs(:getgrgid).with(501).returns(Struct::Passwd.new('unicorns', nil, nil, 501)) expect(provider.gid2name(501)).to eq('unicorns') end it "should return the argument if it's already a name" do expect(provider.gid2name('leprechauns')).to eq('leprechauns') end it "should return nil if the argument is above the maximum gid" do expect(provider.gid2name(Puppet[:maximum_uid] + 1)).to eq(nil) end it "should return nil if the group doesn't exist" do Etc.expects(:getgrgid).raises(ArgumentError, "can't find group for 999") expect(provider.gid2name(999)).to eq(nil) end end describe "#name2gid" do it "should return the id of the group if it exists" do passwd = Struct::Passwd.new('penguins', nil, nil, 502) Etc.stubs(:getgrnam).with('penguins').returns(passwd) Etc.stubs(:getgrgid).with(502).returns(passwd) expect(provider.name2gid('penguins')).to eq(502) end it "should return the argument if it's already an id" do expect(provider.name2gid('503')).to eq(503) end it "should return false if the group doesn't exist" do Etc.stubs(:getgrnam).with('wombats').raises(ArgumentError, "can't find group for wombats") expect(provider.name2gid('wombats')).to eq(false) end end describe "#group" do it "should return the gid of the file group" do FileUtils.touch(path) group = Puppet::FileSystem.stat(path).gid expect(provider.group).to eq(group) end it "should return absent if the file can't be statted" do expect(provider.group).to eq(:absent) end it "should warn and return :silly if the value is beyond the maximum gid" do stat = stub('stat', :gid => Puppet[:maximum_uid] + 1) resource.stubs(:stat).returns(stat) expect(provider.group).to eq(:silly) expect(@logs).to be_any {|log| log.level == :warning and log.message =~ /Apparently using negative GID/} end end describe "#group=" do it "should set the group but not the owner of the file" do File.expects(:lchown).with(nil, 15, resource[:path]) provider.group = 15 end it "should change the group for a link if managing links" do resource[:links] = :manage File.expects(:lchown).with(nil, 20, resource[:path]) provider.group = 20 end it "should change the group for a link target if following links" do resource[:links] = :follow File.expects(:chown).with(nil, 20, resource[:path]) provider.group = 20 end it "should pass along any error encountered setting the group" do File.expects(:lchown).raises(ArgumentError) expect { provider.group = 25 }.to raise_error(Puppet::Error, /Failed to set group to '25'/) end end describe "when validating" do it "should not perform any validation" do resource.validate end end end puppet-5.5.10/spec/unit/provider/file/windows_spec.rb0000644005276200011600000001121613417161722022527 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' if Puppet.features.microsoft_windows? require 'puppet/util/windows' class WindowsSecurity extend Puppet::Util::Windows::Security end end describe Puppet::Type.type(:file).provider(:windows), :if => Puppet.features.microsoft_windows? do include PuppetSpec::Files let(:path) { tmpfile('windows_file_spec') } let(:resource) { Puppet::Type.type(:file).new :path => path, :mode => '0777', :provider => described_class.name } let(:provider) { resource.provider } let(:sid) { 'S-1-1-50' } let(:account) { 'quinn' } describe "#mode" do it "should return a string representing the mode in 4-digit octal notation" do FileUtils.touch(path) WindowsSecurity.set_mode(0644, path) expect(provider.mode).to eq('0644') end it "should return absent if the file doesn't exist" do expect(provider.mode).to eq(:absent) end end describe "#mode=" do it "should chmod the file to the specified value" do FileUtils.touch(path) WindowsSecurity.set_mode(0644, path) provider.mode = '0755' expect(provider.mode).to eq('0755') end it "should pass along any errors encountered" do expect do provider.mode = '0644' end.to raise_error(Puppet::Error, /failed to set mode/) end end describe "#id2name" do it "should return the name of the user identified by the sid" do Puppet::Util::Windows::SID.expects(:valid_sid?).with(sid).returns(true) Puppet::Util::Windows::SID.expects(:sid_to_name).with(sid).returns(account) expect(provider.id2name(sid)).to eq(account) end it "should return the argument if it's already a name" do Puppet::Util::Windows::SID.expects(:valid_sid?).with(account).returns(false) Puppet::Util::Windows::SID.expects(:sid_to_name).never expect(provider.id2name(account)).to eq(account) end it "should return nil if the user doesn't exist" do Puppet::Util::Windows::SID.expects(:valid_sid?).with(sid).returns(true) Puppet::Util::Windows::SID.expects(:sid_to_name).with(sid).returns(nil) expect(provider.id2name(sid)).to eq(nil) end end describe "#name2id" do it "should delegate to name_to_sid" do Puppet::Util::Windows::SID.expects(:name_to_sid).with(account).returns(sid) expect(provider.name2id(account)).to eq(sid) end end describe "#owner" do it "should return the sid of the owner if the file does exist" do FileUtils.touch(resource[:path]) provider.stubs(:get_owner).with(resource[:path]).returns(sid) expect(provider.owner).to eq(sid) end it "should return absent if the file doesn't exist" do expect(provider.owner).to eq(:absent) end end describe "#owner=" do it "should set the owner to the specified value" do provider.expects(:set_owner).with(sid, resource[:path]) provider.owner = sid end it "should propagate any errors encountered when setting the owner" do provider.stubs(:set_owner).raises(ArgumentError) expect { provider.owner = sid }.to raise_error(Puppet::Error, /Failed to set owner/) end end describe "#group" do it "should return the sid of the group if the file does exist" do FileUtils.touch(resource[:path]) provider.stubs(:get_group).with(resource[:path]).returns(sid) expect(provider.group).to eq(sid) end it "should return absent if the file doesn't exist" do expect(provider.group).to eq(:absent) end end describe "#group=" do it "should set the group to the specified value" do provider.expects(:set_group).with(sid, resource[:path]) provider.group = sid end it "should propagate any errors encountered when setting the group" do provider.stubs(:set_group).raises(ArgumentError) expect { provider.group = sid }.to raise_error(Puppet::Error, /Failed to set group/) end end describe "when validating" do {:owner => 'foo', :group => 'foo', :mode => '0777'}.each do |k,v| it "should fail if the filesystem doesn't support ACLs and we're managing #{k}" do described_class.any_instance.stubs(:supports_acl?).returns false expect { Puppet::Type.type(:file).new :path => path, k => v }.to raise_error(Puppet::Error, /Can only manage owner, group, and mode on filesystems that support Windows ACLs, such as NTFS/) end end it "should not fail if the filesystem doesn't support ACLs and we're not managing permissions" do described_class.any_instance.stubs(:supports_acl?).returns false Puppet::Type.type(:file).new :path => path end end end puppet-5.5.10/spec/unit/provider/group/0000755005276200011600000000000013417162177017717 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/provider/group/aix_spec.rb0000644005276200011600000000443513417161721022037 0ustar jenkinsjenkinsrequire 'spec_helper' describe 'Puppet::Type::Group::Provider::Aix' do let(:provider_class) { Puppet::Type.type(:group).provider(:aix) } let(:resource) do Puppet::Type.type(:group).new( :name => 'test_aix_user', :ensure => :present ) end let(:provider) do provider_class.new(resource) end describe '.find' do let(:groups) do objects = [ { :name => 'group1', :id => '1' }, { :name => 'group2', :id => '2' } ] objects end let(:ia_module_args) { [ '-R', 'module' ] } let(:expected_group) do { :name => 'group1', :gid => 1 } end before(:each) do provider_class.stubs(:list_all).with(ia_module_args).returns(groups) end it 'raises an ArgumentError if the group does not exist' do expect do provider_class.find('non_existent_group', ia_module_args) end.to raise_error do |error| expect(error).to be_a(ArgumentError) expect(error.message).to match('non_existent_group') end end it 'can find the group when passed-in a group name' do expect(provider_class.find('group1', ia_module_args)).to eql(expected_group) end it 'can find the group when passed-in the gid' do expect(provider_class.find(1, ia_module_args)).to eql(expected_group) end end describe '.users_to_members' do it 'converts the users attribute to the members property' do expect(provider_class.users_to_members('foo,bar')) .to eql(['foo', 'bar']) end end describe '.members_to_users' do context 'when auth_membership == true' do before(:each) do resource[:auth_membership] = true end it 'returns only the passed-in members' do expect(provider_class.members_to_users(provider, ['user1', 'user2'])) .to eql('user1,user2') end end context 'when auth_membership == false' do before(:each) do resource[:auth_membership] = false provider.stubs(:members).returns(['user3', 'user1']) end it 'adds the passed-in members to the current list of members, filtering out any duplicates' do expect(provider_class.members_to_users(provider, ['user1', 'user2'])) .to eql('user1,user2,user3') end end end end puppet-5.5.10/spec/unit/provider/group/directoryservice_spec.rb0000644005276200011600000000205613417161721024640 0ustar jenkinsjenkinsrequire 'spec_helper' describe Puppet::Type.type(:group).provider(:directoryservice) do let :resource do Puppet::Type.type(:group).new( :title => 'testgroup', :provider => :directoryservice, ) end let(:provider) { resource.provider } it 'should return true for same lists of unordered members' do expect(provider.members_insync?(['user1', 'user2'], ['user2', 'user1'])).to be_truthy end it 'should return false when the group currently has no members' do expect(provider.members_insync?([], ['user2', 'user1'])).to be_falsey end it 'should return true for the same lists of members irrespective of duplicates' do expect(provider.members_insync?(['user1', 'user2', 'user2'], ['user1', 'user2'])).to be_truthy end it "should return true when current and should members are empty lists" do expect(provider.members_insync?([], [])).to be_truthy end it "should return true when current is :absent and should members is empty list" do expect(provider.members_insync?(:absent, [])).to be_truthy end end puppet-5.5.10/spec/unit/provider/group/groupadd_spec.rb0000644005276200011600000001343413417161721023062 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:group).provider(:groupadd) do before do described_class.stubs(:command).with(:add).returns '/usr/sbin/groupadd' described_class.stubs(:command).with(:delete).returns '/usr/sbin/groupdel' described_class.stubs(:command).with(:modify).returns '/usr/sbin/groupmod' described_class.stubs(:command).with(:localadd).returns '/usr/sbin/lgroupadd' described_class.stubs(:command).with(:localdelete).returns '/usr/sbin/lgroupdel' described_class.stubs(:command).with(:localmodify).returns '/usr/sbin/lgroupmod' end let(:resource) { Puppet::Type.type(:group).new(:name => 'mygroup', :provider => provider) } let(:provider) { described_class.new(:name => 'mygroup') } describe "#create" do before do provider.stubs(:exists?).returns(false) end it "should add -o when allowdupe is enabled and the group is being created" do resource[:allowdupe] = :true provider.expects(:execute).with(['/usr/sbin/groupadd', '-o', 'mygroup'], kind_of(Hash)) provider.create end describe "on system that feature system_groups", :if => described_class.system_groups? do it "should add -r when system is enabled and the group is being created" do resource[:system] = :true provider.expects(:execute).with(['/usr/sbin/groupadd', '-r', 'mygroup'], kind_of(Hash)) provider.create end end describe "on system that do not feature system_groups", :unless => described_class.system_groups? do it "should not add -r when system is enabled and the group is being created" do resource[:system] = :true provider.expects(:execute).with(['/usr/sbin/groupadd', 'mygroup'], kind_of(Hash)) provider.create end end describe "on systems with the libuser and forcelocal=true" do before do described_class.has_feature(:libuser) resource[:forcelocal] = :true end it "should use lgroupadd instead of groupadd" do provider.expects(:execute).with(includes('/usr/sbin/lgroupadd'), has_entry(:custom_environment, has_key('LIBUSER_CONF'))) provider.create end it "should NOT pass -o to lgroupadd" do resource[:allowdupe] = :true provider.expects(:execute).with(Not(includes('-o')), has_entry(:custom_environment, has_key('LIBUSER_CONF'))) provider.create end it "should raise an exception for duplicate GID if allowdupe is not set and duplicate GIDs exist" do resource[:gid] = 505 provider.stubs(:findgroup).returns(true) expect { provider.create }.to raise_error(Puppet::Error, "GID 505 already exists, use allowdupe to force group creation") end end end describe "#modify" do before do provider.stubs(:exists?).returns(true) end describe "on systems with the libuser and forcelocal=false" do before do described_class.has_feature(:libuser) resource[:forcelocal] = :false end it "should use groupmod" do provider.expects(:execute).with(['/usr/sbin/groupmod', '-g', 150, 'mygroup'], has_entries({:failonfail => true, :combine => true, :custom_environment => {}})) provider.gid = 150 end it "should pass -o to groupmod" do resource[:allowdupe] = :true provider.expects(:execute).with(['/usr/sbin/groupmod', '-g', 150, '-o', 'mygroup'], has_entries({:failonfail => true, :combine => true, :custom_environment => {}})) provider.gid = 150 end end describe "on systems with the libuser and forcelocal=true" do before do described_class.has_feature(:libuser) resource[:forcelocal] = :true end it "should use lgroupmod instead of groupmod" do provider.expects(:execute).with(['/usr/sbin/lgroupmod', '-g', 150, 'mygroup'], has_entry(:custom_environment, has_key('LIBUSER_CONF'))) provider.gid = 150 end it "should NOT pass -o to lgroupmod" do resource[:allowdupe] = :true provider.expects(:execute).with(['/usr/sbin/lgroupmod', '-g', 150, 'mygroup'], has_entry(:custom_environment, has_key('LIBUSER_CONF'))) provider.gid = 150 end it "should raise an exception for duplicate GID if allowdupe is not set and duplicate GIDs exist" do resource[:gid] = 150 resource[:allowdupe] = :false provider.stubs(:findgroup).returns(true) expect { provider.gid = 150 }.to raise_error(Puppet::Error, "GID 150 already exists, use allowdupe to force group creation") end end end describe "#gid=" do it "should add -o when allowdupe is enabled and the gid is being modified" do resource[:allowdupe] = :true provider.expects(:execute).with(['/usr/sbin/groupmod', '-g', 150, '-o', 'mygroup'], has_entries({:failonfail => true, :combine => true, :custom_environment => {}})) provider.gid = 150 end end describe "#delete" do before do provider.stubs(:exists?).returns(true) end describe "on systems with the libuser and forcelocal=false" do before do described_class.has_feature(:libuser) resource[:forcelocal] = :false end it "should use groupdel" do provider.expects(:execute).with(['/usr/sbin/groupdel', 'mygroup'], has_entries({:failonfail => true, :combine => true, :custom_environment => {}})) provider.delete end end describe "on systems with the libuser and forcelocal=true" do before do described_class.has_feature(:libuser) resource[:forcelocal] = :true end it "should use lgroupdel instead of groupdel" do provider.expects(:execute).with(['/usr/sbin/lgroupdel', 'mygroup'], has_entry(:custom_environment, has_key('LIBUSER_CONF'))) provider.delete end end end end puppet-5.5.10/spec/unit/provider/group/ldap_spec.rb0000644005276200011600000000675013417161721022200 0ustar jenkinsjenkinsrequire 'spec_helper' describe Puppet::Type.type(:group).provider(:ldap) do it "should have the Ldap provider class as its baseclass" do expect(described_class.superclass).to equal(Puppet::Provider::Ldap) end it "should manage :posixGroup objectclass" do expect(described_class.manager.objectclasses).to eq([:posixGroup]) end it "should use 'ou=Groups' as its relative base" do expect(described_class.manager.location).to eq("ou=Groups") end it "should use :cn as its rdn" do expect(described_class.manager.rdn).to eq(:cn) end it "should map :name to 'cn'" do expect(described_class.manager.ldap_name(:name)).to eq('cn') end it "should map :gid to 'gidNumber'" do expect(described_class.manager.ldap_name(:gid)).to eq('gidNumber') end it "should map :members to 'memberUid', to be used by the user ldap provider" do expect(described_class.manager.ldap_name(:members)).to eq('memberUid') end describe "when being created" do before do # So we don't try to actually talk to ldap @connection = mock 'connection' described_class.manager.stubs(:connect).yields @connection end describe "with no gid specified" do it "should pick the first available GID after the largest existing GID" do low = {:name=>["luke"], :gid=>["600"]} high = {:name=>["testing"], :gid=>["640"]} described_class.manager.expects(:search).returns([low, high]) resource = stub 'resource', :should => %w{whatever} resource.stubs(:should).with(:gid).returns nil resource.stubs(:should).with(:ensure).returns :present instance = described_class.new(:name => "luke", :ensure => :absent) instance.stubs(:resource).returns resource @connection.expects(:add).with { |dn, attrs| attrs["gidNumber"] == ["641"] } instance.create instance.flush end it "should pick '501' as its GID if no groups are found" do described_class.manager.expects(:search).returns nil resource = stub 'resource', :should => %w{whatever} resource.stubs(:should).with(:gid).returns nil resource.stubs(:should).with(:ensure).returns :present instance = described_class.new(:name => "luke", :ensure => :absent) instance.stubs(:resource).returns resource @connection.expects(:add).with { |dn, attrs| attrs["gidNumber"] == ["501"] } instance.create instance.flush end end end it "should have a method for converting group names to GIDs" do expect(described_class).to respond_to(:name2id) end describe "when converting from a group name to GID" do it "should use the ldap manager to look up the GID" do described_class.manager.expects(:search).with("cn=foo") described_class.name2id("foo") end it "should return nil if no group is found" do described_class.manager.expects(:search).with("cn=foo").returns nil expect(described_class.name2id("foo")).to be_nil described_class.manager.expects(:search).with("cn=bar").returns [] expect(described_class.name2id("bar")).to be_nil end # We shouldn't ever actually have more than one gid, but it doesn't hurt # to test for the possibility. it "should return the first gid from the first returned group" do described_class.manager.expects(:search).with("cn=foo").returns [{:name => "foo", :gid => [10, 11]}, {:name => :bar, :gid => [20, 21]}] expect(described_class.name2id("foo")).to eq(10) end end end puppet-5.5.10/spec/unit/provider/group/pw_spec.rb0000644005276200011600000000574413417161721021710 0ustar jenkinsjenkinsrequire 'spec_helper' describe Puppet::Type.type(:group).provider(:pw) do let :resource do Puppet::Type.type(:group).new(:name => "testgroup", :provider => :pw) end let :provider do resource.provider end describe "when creating groups" do let :provider do prov = resource.provider prov.expects(:exists?).returns nil prov end it "should run pw with no additional flags when no properties are given" do expect(provider.addcmd).to eq([described_class.command(:pw), "groupadd", "testgroup"]) provider.expects(:execute).with([described_class.command(:pw), "groupadd", "testgroup"], kind_of(Hash)) provider.create end it "should use -o when allowdupe is enabled" do resource[:allowdupe] = true provider.expects(:execute).with(includes("-o"), kind_of(Hash)) provider.create end it "should use -g with the correct argument when the gid property is set" do resource[:gid] = 12345 provider.expects(:execute).with(all_of(includes("-g"), includes(12345)), kind_of(Hash)) provider.create end it "should use -M with the correct argument when the members property is set" do resource[:members] = "user1" provider.expects(:execute).with(all_of(includes("-M"), includes("user1")), kind_of(Hash)) provider.create end it "should use -M with all the given users when the members property is set to an array" do resource[:members] = ["user1", "user2"] provider.expects(:execute).with(all_of(includes("-M"), includes("user1,user2")), kind_of(Hash)) provider.create end end describe "when deleting groups" do it "should run pw with no additional flags" do provider.expects(:exists?).returns true expect(provider.deletecmd).to eq([described_class.command(:pw), "groupdel", "testgroup"]) provider.expects(:execute).with([described_class.command(:pw), "groupdel", "testgroup"], has_entry(:custom_environment, {})) provider.delete end end describe "when modifying groups" do it "should run pw with the correct arguments" do expect(provider.modifycmd("gid", 12345)).to eq([described_class.command(:pw), "groupmod", "testgroup", "-g", 12345]) provider.expects(:execute).with([described_class.command(:pw), "groupmod", "testgroup", "-g", 12345], has_entry(:custom_environment, {})) provider.gid = 12345 end it "should use -M with the correct argument when the members property is changed" do resource[:members] = "user1" provider.expects(:execute).with(all_of(includes("-M"), includes("user2")), has_entry(:custom_environment, {})) provider.members = "user2" end it "should use -M with all the given users when the members property is changed with an array" do resource[:members] = ["user1", "user2"] provider.expects(:execute).with(all_of(includes("-M"), includes("user3,user4")), has_entry(:custom_environment, {})) provider.members = ["user3", "user4"] end end end puppet-5.5.10/spec/unit/provider/group/windows_adsi_spec.rb0000644005276200011600000003143113417161722023745 0ustar jenkinsjenkins#!/usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:group).provider(:windows_adsi), :if => Puppet.features.microsoft_windows? do let(:resource) do Puppet::Type.type(:group).new( :title => 'testers', :provider => :windows_adsi ) end let(:provider) { resource.provider } let(:connection) { stub 'connection' } before :each do Puppet::Util::Windows::ADSI.stubs(:computer_name).returns('testcomputername') Puppet::Util::Windows::ADSI.stubs(:connect).returns connection # this would normally query the system, but not needed for these tests Puppet::Util::Windows::ADSI::Group.stubs(:localized_domains).returns([]) end describe ".instances" do it "should enumerate all groups" do names = ['group1', 'group2', 'group3'] stub_groups = names.map{|n| stub(:name => n)} connection.stubs(:execquery).with('select name from win32_group where localaccount = "TRUE"').returns stub_groups expect(described_class.instances.map(&:name)).to match(names) end end describe "group type :members property helpers" do let(:user1) { stub(:account => 'user1', :domain => '.', :sid => 'user1sid') } let(:user2) { stub(:account => 'user2', :domain => '.', :sid => 'user2sid') } let(:user3) { stub(:account => 'user3', :domain => '.', :sid => 'user3sid') } let(:invalid_user) { SecureRandom.uuid } before :each do Puppet::Util::Windows::SID.stubs(:name_to_principal).with('user1').returns(user1) Puppet::Util::Windows::SID.stubs(:name_to_principal).with('user2').returns(user2) Puppet::Util::Windows::SID.stubs(:name_to_principal).with('user3').returns(user3) Puppet::Util::Windows::SID.stubs(:name_to_principal).with(invalid_user).returns(nil) end describe "#members_insync?" do it "should return true for same lists of members" do current = [ 'user1', 'user2', ] expect(provider.members_insync?(current, ['user1', 'user2'])).to be_truthy end it "should return true for same lists of unordered members" do current = [ 'user1', 'user2', ] expect(provider.members_insync?(current, ['user2', 'user1'])).to be_truthy end it "should return true for same lists of members irrespective of duplicates" do current = [ 'user1', 'user2', 'user2', ] expect(provider.members_insync?(current, ['user2', 'user1', 'user1'])).to be_truthy end it "should return true when current and should members are empty lists" do expect(provider.members_insync?([], [])).to be_truthy end # invalid scenarios #it "should return true when current and should members are nil lists" do #it "should return true when current members is nil and should members is empty" do it "should return true when current members is empty and should members is nil" do expect(provider.members_insync?([], nil)).to be_truthy end context "when auth_membership => true" do before :each do resource[:auth_membership] = true end it "should return true when current and should contain the same users in a different order" do current = [ 'user1', 'user2', 'user3', ] expect(provider.members_insync?(current, ['user3', 'user1', 'user2'])).to be_truthy end it "should return false when current is nil" do expect(provider.members_insync?(nil, ['user2'])).to be_falsey end it "should return false when should is nil" do current = [ 'user1', ] expect(provider.members_insync?(current, nil)).to be_falsey end it "should return false when current contains different users than should" do current = [ 'user1', ] expect(provider.members_insync?(current, ['user2'])).to be_falsey end it "should return false when current contains members and should is empty" do current = [ 'user1', ] expect(provider.members_insync?(current, [])).to be_falsey end it "should return false when current is empty and should contains members" do expect(provider.members_insync?([], ['user2'])).to be_falsey end it "should return false when should user(s) are not the only items in the current" do current = [ 'user1', 'user2', ] expect(provider.members_insync?(current, ['user1'])).to be_falsey end it "should return false when current user(s) is not empty and should is an empty list" do current = [ 'user1', 'user2', ] expect(provider.members_insync?(current, [])).to be_falsey end end context "when auth_membership => false" do before :each do # this is also the default resource[:auth_membership] = false end it "should return false when current is nil" do expect(provider.members_insync?(nil, ['user2'])).to be_falsey end it "should return true when should is nil" do current = [ 'user1', ] expect(provider.members_insync?(current, nil)).to be_truthy end it "should return false when current contains different users than should" do current = [ 'user1', ] expect(provider.members_insync?(current, ['user2'])).to be_falsey end it "should return true when current contains members and should is empty" do current = [ 'user1', ] expect(provider.members_insync?(current, [])).to be_truthy end it "should return false when current is empty and should contains members" do expect(provider.members_insync?([], ['user2'])).to be_falsey end it "should return true when current user(s) contains at least the should list" do current = [ 'user1', 'user2', ] expect(provider.members_insync?(current, ['user1'])).to be_truthy end it "should return true when current user(s) is not empty and should is an empty list" do current = [ 'user1', 'user2', ] expect(provider.members_insync?(current, [])).to be_truthy end it "should return true when current user(s) contains at least the should list, even unordered" do current = [ 'user3', 'user1', 'user2', ] expect(provider.members_insync?(current, ['user2','user1'])).to be_truthy end end end describe "#members_to_s" do it "should return an empty string on non-array input" do [Object.new, {}, 1, :symbol, ''].each do |input| expect(provider.members_to_s(input)).to be_empty end end it "should return an empty string on empty or nil users" do expect(provider.members_to_s([])).to be_empty expect(provider.members_to_s(nil)).to be_empty end it "should return a user string like DOMAIN\\USER" do expect(provider.members_to_s(['user1'])).to eq('.\user1') end it "should return a user string like DOMAIN\\USER,DOMAIN2\\USER2" do expect(provider.members_to_s(['user1', 'user2'])).to eq('.\user1,.\user2') end it "should return the username when it cannot be resolved to a SID (for the sake of resource_harness error messages)" do expect(provider.members_to_s([invalid_user])).to eq("#{invalid_user}") end it "should return the username when it cannot be resolved to a SID (for the sake of resource_harness error messages)" do expect(provider.members_to_s([invalid_user])).to eq("#{invalid_user}") end end end describe "when managing members" do let(:user1) { stub(:account => 'user1', :domain => '.', :sid => 'user1sid') } let(:user2) { stub(:account => 'user2', :domain => '.', :sid => 'user2sid') } let(:user3) { stub(:account => 'user3', :domain => '.', :sid => 'user3sid') } before :each do Puppet::Util::Windows::SID.stubs(:name_to_principal).with('user1').returns(user1) Puppet::Util::Windows::SID.stubs(:name_to_principal).with('user2').returns(user2) Puppet::Util::Windows::SID.stubs(:name_to_principal).with('user3').returns(user3) resource[:auth_membership] = true end it "should be able to provide a list of members" do provider.group.stubs(:members).returns [ 'user1', 'user2', 'user3', ] expect(provider.members).to match_array([user1.sid, user2.sid, user3.sid]) end it "should be able to set group members" do provider.group.stubs(:members).returns ['user1', 'user2'] member_sids = [ stub(:account => 'user1', :domain => 'testcomputername', :sid => 1), stub(:account => 'user2', :domain => 'testcomputername', :sid => 2), stub(:account => 'user3', :domain => 'testcomputername', :sid => 3), ] provider.group.stubs(:member_sids).returns(member_sids[0..1]) Puppet::Util::Windows::SID.expects(:name_to_principal).with('user2').returns(member_sids[1]) Puppet::Util::Windows::SID.expects(:name_to_principal).with('user3').returns(member_sids[2]) provider.group.expects(:remove_member_sids).with(member_sids[0]) provider.group.expects(:add_member_sids).with(member_sids[2]) provider.members = ['user2', 'user3'] end end describe 'when creating groups' do it "should be able to create a group" do resource[:members] = ['user1', 'user2'] group = stub 'group' Puppet::Util::Windows::ADSI::Group.expects(:create).with('testers').returns group create = sequence('create') group.expects(:commit).in_sequence(create) # due to PUP-1967, defaultto false will set the default to nil group.expects(:set_members).with(['user1', 'user2'], nil).in_sequence(create) provider.create end it 'should not create a group if a user by the same name exists' do Puppet::Util::Windows::ADSI::Group.expects(:create).with('testers').raises( Puppet::Error.new("Cannot create group if user 'testers' exists.") ) expect{ provider.create }.to raise_error( Puppet::Error, /Cannot create group if user 'testers' exists./ ) end it "should fail with an actionable message when trying to create an active directory group" do resource[:name] = 'DOMAIN\testdomaingroup' Puppet::Util::Windows::ADSI::User.expects(:exists?).with(resource[:name]).returns(false) connection.expects(:Create) connection.expects(:SetInfo).raises( WIN32OLERuntimeError.new("(in OLE method `SetInfo': )\n OLE error code:8007089A in Active Directory\n The specified username is invalid.\r\n\n HRESULT error code:0x80020009\n Exception occurred.")) expect{ provider.create }.to raise_error(Puppet::Error) end it 'should commit a newly created group' do provider.group.expects( :commit ) provider.flush end end it "should be able to test whether a group exists" do Puppet::Util::Windows::SID.stubs(:name_to_principal).returns(nil) Puppet::Util::Windows::ADSI.stubs(:connect).returns stub('connection', :Class => 'Group') expect(provider).to be_exists Puppet::Util::Windows::ADSI.stubs(:connect).returns nil expect(provider).not_to be_exists end it "should be able to delete a group" do connection.expects(:Delete).with('group', 'testers') provider.delete end it 'should not run commit on a deleted group' do connection.expects(:Delete).with('group', 'testers') connection.expects(:SetInfo).never provider.delete provider.flush end it "should report the group's SID as gid" do Puppet::Util::Windows::SID.expects(:name_to_sid).with('testers').returns('S-1-5-32-547') expect(provider.gid).to eq('S-1-5-32-547') end it "should fail when trying to manage the gid property" do provider.expects(:fail).with { |msg| msg =~ /gid is read-only/ } provider.send(:gid=, 500) end it "should prefer the domain component from the resolved SID" do # must lookup well known S-1-5-32-544 as actual 'Administrators' name may be localized admins_sid_bytes = [1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0] admins_group = Puppet::Util::Windows::SID::Principal.lookup_account_sid(admins_sid_bytes) # prefix just the name like .\Administrators converted = provider.members_to_s([".\\#{admins_group.account}"]) # and ensure equivalent of BUILTIN\Administrators, without a leading . expect(converted).to eq(admins_group.domain_account) expect(converted[0]).to_not eq('.') end end puppet-5.5.10/spec/unit/provider/ldap_spec.rb0000644005276200011600000002015413417161721021036 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/provider/ldap' describe Puppet::Provider::Ldap do before do @class = Class.new(Puppet::Provider::Ldap) end it "should be able to define its manager" do manager = mock 'manager' Puppet::Util::Ldap::Manager.expects(:new).returns manager @class.stubs :mk_resource_methods manager.expects(:manages).with(:one) expect(@class.manages(:one)).to equal(manager) expect(@class.manager).to equal(manager) end it "should be able to prefetch instances from ldap" do expect(@class).to respond_to(:prefetch) end it "should create its resource getter/setter methods when the manager is defined" do manager = mock 'manager' Puppet::Util::Ldap::Manager.expects(:new).returns manager @class.expects :mk_resource_methods manager.stubs(:manages) expect(@class.manages(:one)).to equal(manager) end it "should have an instances method" do expect(@class).to respond_to(:instances) end describe "when providing a list of instances" do it "should convert all results returned from the manager's :search method into provider instances" do manager = mock 'manager' @class.stubs(:manager).returns manager manager.expects(:search).returns %w{one two three} @class.expects(:new).with("one").returns(1) @class.expects(:new).with("two").returns(2) @class.expects(:new).with("three").returns(3) expect(@class.instances).to eq([1,2,3]) end end it "should have a prefetch method" do expect(@class).to respond_to(:prefetch) end describe "when prefetching" do before do @manager = mock 'manager' @class.stubs(:manager).returns @manager @resource = mock 'resource' @resources = {"one" => @resource} end it "should find an entry for each passed resource" do @manager.expects(:find).with("one").returns nil @class.stubs(:new) @resource.stubs(:provider=) @class.prefetch(@resources) end describe "resources that do not exist" do it "should create a provider with :ensure => :absent" do @manager.expects(:find).with("one").returns nil @class.expects(:new).with(:ensure => :absent).returns "myprovider" @resource.expects(:provider=).with("myprovider") @class.prefetch(@resources) end end describe "resources that exist" do it "should create a provider with the results of the find" do @manager.expects(:find).with("one").returns("one" => "two") @class.expects(:new).with("one" => "two", :ensure => :present).returns "myprovider" @resource.expects(:provider=).with("myprovider") @class.prefetch(@resources) end it "should set :ensure to :present in the returned values" do @manager.expects(:find).with("one").returns("one" => "two") @class.expects(:new).with("one" => "two", :ensure => :present).returns "myprovider" @resource.expects(:provider=).with("myprovider") @class.prefetch(@resources) end end end describe "when being initialized" do it "should fail if no manager has been defined" do expect { @class.new }.to raise_error(Puppet::DevError) end it "should fail if the manager is invalid" do manager = stub "manager", :valid? => false @class.stubs(:manager).returns manager expect { @class.new }.to raise_error(Puppet::DevError) end describe "with a hash" do before do @manager = stub "manager", :valid? => true @class.stubs(:manager).returns @manager @resource_class = mock 'resource_class' @class.stubs(:resource_type).returns @resource_class @property_class = stub 'property_class', :array_matching => :all, :superclass => Puppet::Property @resource_class.stubs(:attrclass).with(:one).returns(@property_class) @resource_class.stubs(:valid_parameter?).returns true end it "should store a copy of the hash as its ldap_properties" do instance = @class.new(:one => :two) expect(instance.ldap_properties).to eq({:one => :two}) end it "should only store the first value of each value array for those attributes that do not match all values" do @property_class.expects(:array_matching).returns :first instance = @class.new(:one => %w{two three}) expect(instance.properties).to eq({:one => "two"}) end it "should store the whole value array for those attributes that match all values" do @property_class.expects(:array_matching).returns :all instance = @class.new(:one => %w{two three}) expect(instance.properties).to eq({:one => %w{two three}}) end it "should only use the first value for attributes that are not properties" do # Yay. hackish, but easier than mocking everything. @resource_class.expects(:attrclass).with(:a).returns Puppet::Type.type(:user).attrclass(:name) @property_class.stubs(:array_matching).returns :all instance = @class.new(:one => %w{two three}, :a => %w{b c}) expect(instance.properties).to eq({:one => %w{two three}, :a => "b"}) end it "should discard any properties not valid in the resource class" do @resource_class.expects(:valid_parameter?).with(:a).returns false @property_class.stubs(:array_matching).returns :all instance = @class.new(:one => %w{two three}, :a => %w{b}) expect(instance.properties).to eq({:one => %w{two three}}) end end end describe "when an instance" do before do @manager = stub "manager", :valid? => true @class.stubs(:manager).returns @manager @instance = @class.new @property_class = stub 'property_class', :array_matching => :all, :superclass => Puppet::Property @resource_class = stub 'resource_class', :attrclass => @property_class, :valid_parameter? => true, :validproperties => [:one, :two] @class.stubs(:resource_type).returns @resource_class end it "should have a method for creating the ldap entry" do expect(@instance).to respond_to(:create) end it "should have a method for removing the ldap entry" do expect(@instance).to respond_to(:delete) end it "should have a method for returning the class's manager" do expect(@instance.manager).to equal(@manager) end it "should indicate when the ldap entry already exists" do @instance = @class.new(:ensure => :present) expect(@instance.exists?).to be_truthy end it "should indicate when the ldap entry does not exist" do @instance = @class.new(:ensure => :absent) expect(@instance.exists?).to be_falsey end describe "is being flushed" do it "should call the manager's :update method with its name, current attributes, and desired attributes" do @instance.stubs(:name).returns "myname" @instance.stubs(:ldap_properties).returns(:one => :two) @instance.stubs(:properties).returns(:three => :four) @manager.expects(:update).with(@instance.name, {:one => :two}, {:three => :four}) @instance.flush end end describe "is being created" do before do @rclass = mock 'resource_class' @rclass.stubs(:validproperties).returns([:one, :two]) @resource = mock 'resource' @resource.stubs(:class).returns @rclass @resource.stubs(:should).returns nil @instance.stubs(:resource).returns @resource end it "should set its :ensure value to :present" do @instance.create expect(@instance.properties[:ensure]).to eq(:present) end it "should set all of the other attributes from the resource" do @resource.expects(:should).with(:one).returns "oneval" @resource.expects(:should).with(:two).returns "twoval" @instance.create expect(@instance.properties[:one]).to eq("oneval") expect(@instance.properties[:two]).to eq("twoval") end end describe "is being deleted" do it "should set its :ensure value to :absent" do @instance.delete expect(@instance.properties[:ensure]).to eq(:absent) end end end end puppet-5.5.10/spec/unit/provider/nameservice/0000755005276200011600000000000013417162177021064 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/provider/nameservice/directoryservice_spec.rb0000644005276200011600000002001613417161721026001 0ustar jenkinsjenkinsrequire 'spec_helper' module Puppet::Util::Plist end # We use this as a reasonable way to obtain all the support infrastructure. [:group].each do |type_for_this_round| describe Puppet::Type.type(type_for_this_round).provider(:directoryservice) do before do @resource = stub("resource") @resource.stubs(:[]).with(:name) @provider = described_class.new(@resource) end it "[#6009] should handle nested arrays of members" do current = ["foo", "bar", "baz"] desired = ["foo", ["quux"], "qorp"] group = 'example' @resource.stubs(:[]).with(:name).returns(group) @resource.stubs(:[]).with(:auth_membership).returns(true) @provider.instance_variable_set(:@property_value_cache_hash, { :members => current }) %w{bar baz}.each do |del| @provider.expects(:execute).once. with([:dseditgroup, '-o', 'edit', '-n', '.', '-d', del, group]) end %w{quux qorp}.each do |add| @provider.expects(:execute).once. with([:dseditgroup, '-o', 'edit', '-n', '.', '-a', add, group]) end expect { @provider.set(:members, desired) }.to_not raise_error end end end describe Puppet::Provider::NameService::DirectoryService do context '.single_report' do it 'should use plist data' do Puppet::Provider::NameService::DirectoryService.stubs(:get_ds_path).returns('Users') Puppet::Provider::NameService::DirectoryService.stubs(:list_all_present).returns( ['root', 'user1', 'user2', 'resource_name'] ) Puppet::Provider::NameService::DirectoryService.stubs(:generate_attribute_hash) Puppet::Provider::NameService::DirectoryService.stubs(:execute) Puppet::Provider::NameService::DirectoryService.expects(:parse_dscl_plist_data) Puppet::Provider::NameService::DirectoryService.single_report('resource_name') end end context '.get_exec_preamble' do it 'should use plist data' do Puppet::Provider::NameService::DirectoryService.stubs(:get_ds_path).returns('Users') expect(Puppet::Provider::NameService::DirectoryService.get_exec_preamble('-list')).to include("-plist") end end context 'password behavior' do # The below is a binary plist containing a ShadowHashData key which CONTAINS # another binary plist. The nested binary plist contains a 'SALTED-SHA512' # key that contains a base64 encoded salted-SHA512 password hash... let (:binary_plist) { "bplist00\324\001\002\003\004\005\006\a\bXCRAM-MD5RNT]SALTED-SHA512[RECOVERABLEO\020 \231k2\3360\200GI\201\355J\216\202\215y\243\001\206J\300\363\032\031\022\006\2359\024\257\217<\361O\020\020F\353\at\377\277\226\276c\306\254\031\037J(\235O\020D\335\006{\3744g@\377z\204\322\r\332t\021\330\n\003\246K\223\356\034!P\261\305t\035\346\352p\206\003n\247MMA\310\301Z<\366\246\023\0161W3\340\357\000\317T\t\301\311+\204\246L7\276\370\320*\245O\021\002\000k\024\221\270x\353\001\237\346D}\377?\265]\356+\243\v[\350\316a\340h\376<\322\266\327\016\306n\272r\t\212A\253L\216\214\205\016\241 [\360/\335\002#\\A\372\241a\261\346\346\\\251\330\312\365\016\n\341\017\016\225&;\322\\\004*\ru\316\372\a \362?8\031\247\231\030\030\267\315\023\v\343{@\227\301s\372h\212\000a\244&\231\366\nt\277\2036,\027bZ+\223W\212g\333`\264\331N\306\307\362\257(^~ b\262\247&\231\261t\341\231%\244\247\203eOt\365\271\201\273\330\350\363C^A\327F\214!\217hgf\e\320k\260n\315u~\336\371M\t\235k\230S\375\311\303\240\351\037d\273\321y\335=K\016`_\317\230\2612_\023K\036\350\v\232\323Y\310\317_\035\227%\237\v\340\023\016\243\233\025\306:\227\351\370\364x\234\231\266\367\016w\275\333-\351\210}\375x\034\262\272kRuHa\362T/F!\347B\231O`K\304\037'k$$\245h)e\363\365mT\b\317\\2\361\026\351\254\375Jl1~\r\371\267\352\2322I\341\272\376\243^Un\266E7\230[VocUJ\220N\2116D/\025f=\213\314\325\vG}\311\360\377DT\307m\261&\263\340\272\243_\020\271rG^BW\210\030l\344\0324\335\233\300\023\272\225Im\330\n\227*Yv[\006\315\330y'\a\321\373\273A\240\305F{S\246I#/\355\2425\031\031GGF\270y\n\331\004\023G@\331\000\361\343\350\264$\032\355_\210y\000\205\342\375\212q\024\004\026W:\205 \363v?\035\270L-\270=\022\323\2003\v\336\277\t\237\356\374\n\267n\003\367\342\330;\371S\326\016`B6@Njm>\240\021%\336\345\002(P\204Yn\3279l\0228\264\254\304\2528t\372h\217\347sA\314\345\245\337)]\000\b\000\021\000\032\000\035\000+\0007\000Z\000m\000\264\000\000\000\000\000\000\002\001\000\000\000\000\000\000\000\t\000\000\000\000\000\000\000\000\000\000\000\000\000\000\002\270" } # The below is a base64 encoded salted-SHA512 password hash. let (:pw_string) { "\335\006{\3744g@\377z\204\322\r\332t\021\330\n\003\246K\223\356\034!P\261\305t\035\346\352p\206\003n\247MMA\310\301Z<\366\246\023\0161W3\340\357\000\317T\t\301\311+\204\246L7\276\370\320*\245" } # The below is a salted-SHA512 password hash in hex. let (:sha512_hash) { 'dd067bfc346740ff7a84d20dda7411d80a03a64b93ee1c2150b1c5741de6ea7086036ea74d4d41c8c15a3cf6a6130e315733e0ef00cf5409c1c92b84a64c37bef8d02aa5' } let :plist_path do '/var/db/dslocal/nodes/Default/users/jeff.plist' end let :ds_provider do described_class end let :shadow_hash_data do {'ShadowHashData' => [binary_plist]} end it 'should execute convert_binary_to_hash once when getting the password' do described_class.expects(:convert_binary_to_hash).returns({'SALTED-SHA512' => pw_string}) Puppet::FileSystem.expects(:exist?).with(plist_path).once.returns(true) Puppet::Util::Plist.expects(:read_plist_file).returns(shadow_hash_data) described_class.get_password('uid', 'jeff') end it 'should fail if a salted-SHA512 password hash is not passed in' do expect { described_class.set_password('jeff', 'uid', 'badpassword') }.to raise_error(RuntimeError, /OS X 10.7 requires a Salted SHA512 hash password of 136 characters./) end it 'should convert xml-to-binary and binary-to-xml when setting the pw on >= 10.7' do described_class.expects(:convert_binary_to_hash).returns({'SALTED-SHA512' => pw_string}) described_class.expects(:convert_hash_to_binary).returns(binary_plist) Puppet::FileSystem.expects(:exist?).with(plist_path).once.returns(true) Puppet::Util::Plist.expects(:read_plist_file).returns(shadow_hash_data) Puppet::Util::Plist.expects(:write_plist_file).with(shadow_hash_data, plist_path, :binary) described_class.set_password('jeff', 'uid', sha512_hash) end it '[#13686] should handle an empty ShadowHashData field in the users plist' do described_class.expects(:convert_hash_to_binary).returns(binary_plist) Puppet::FileSystem.expects(:exist?).with(plist_path).once.returns(true) Puppet::Util::Plist.expects(:read_plist_file).returns({'ShadowHashData' => nil}) Puppet::Util::Plist.expects(:write_plist_file) described_class.set_password('jeff', 'uid', sha512_hash) end end context '(#4855) directoryservice group resource failure' do let :provider_class do Puppet::Type.type(:group).provider(:directoryservice) end let :group_members do ['root','jeff'] end let :user_account do ['root'] end let :stub_resource do stub('resource') end subject do provider_class.new(stub_resource) end before :each do @resource = stub("resource") @resource.stubs(:[]).with(:name) @provider = provider_class.new(@resource) end it 'should delete a group member if the user does not exist' do stub_resource.stubs(:[]).with(:name).returns('fake_group') stub_resource.stubs(:name).returns('fake_group') subject.expects(:execute).with([:dseditgroup, '-o', 'edit', '-n', '.', '-d', 'jeff', 'fake_group']).raises(Puppet::ExecutionFailure, 'it broke') subject.expects(:execute).with([:dscl, '.', '-delete', '/Groups/fake_group', 'GroupMembership', 'jeff']) subject.remove_unwanted_members(group_members, user_account) end end end puppet-5.5.10/spec/unit/provider/package/0000755005276200011600000000000013417162177020156 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/provider/package/aix_spec.rb0000644005276200011600000001150313417161721022270 0ustar jenkinsjenkinsrequire 'spec_helper' describe Puppet::Type.type(:package).provider(:aix) do before(:each) do # Create a mock resource @resource = Puppet::Type.type(:package).new(:name => 'mypackage', :ensure => :installed, :source => 'mysource', :provider => :aix) @provider = @resource.provider end [:install, :uninstall, :latest, :query, :update].each do |method| it "should have a #{method} method" do expect(@provider).to respond_to(method) end end it "should uninstall a package" do @provider.expects(:installp).with('-gu', 'mypackage') @provider.class.expects(:pkglist).with(:pkgname => 'mypackage').returns(nil) @provider.uninstall end context "when installing" do it "should install a package" do @provider.expects(:installp).with('-acgwXY', '-d', 'mysource', 'mypackage') @provider.install end it "should install a specific package version" do @resource.stubs(:should).with(:ensure).returns("1.2.3.4") @provider.expects(:installp).with('-acgwXY', '-d', 'mysource', 'mypackage 1.2.3.4') @provider.install end it "should fail if the specified version is superseded" do @resource[:ensure] = '1.2.3.3' @provider.stubs(:installp).returns <<-OUTPUT +-----------------------------------------------------------------------------+ Pre-installation Verification... +-----------------------------------------------------------------------------+ Verifying selections...done Verifying requisites...done Results... WARNINGS -------- Problems described in this section are not likely to be the source of any immediate or serious failures, but further actions may be necessary or desired. Already Installed ----------------- The number of selected filesets that are either already installed or effectively installed through superseding filesets is 1. See the summaries at the end of this installation for details. NOTE: Base level filesets may be reinstalled using the "Force" option (-F flag), or they may be removed, using the deinstall or "Remove Software Products" facility (-u flag), and then reinstalled. << End of Warning Section >> +-----------------------------------------------------------------------------+ BUILDDATE Verification ... +-----------------------------------------------------------------------------+ Verifying build dates...done FILESET STATISTICS ------------------ 1 Selected to be installed, of which: 1 Already installed (directly or via superseding filesets) ---- 0 Total to be installed Pre-installation Failure/Warning Summary ---------------------------------------- Name Level Pre-installation Failure/Warning ------------------------------------------------------------------------------- mypackage 1.2.3.3 Already superseded by 1.2.3.4 OUTPUT expect { @provider.install }.to raise_error(Puppet::Error, "aix package provider is unable to downgrade packages") end end context "when finding the latest version" do it "should return the current version when no later version is present" do @provider.stubs(:latest_info).returns(nil) @provider.stubs(:properties).returns( { :ensure => "1.2.3.4" } ) expect(@provider.latest).to eq("1.2.3.4") end it "should return the latest version of a package" do @provider.stubs(:latest_info).returns( { :version => "1.2.3.5" } ) expect(@provider.latest).to eq("1.2.3.5") end it "should prefetch the right values" do Process.stubs(:euid).returns(0) resource = Puppet::Type.type(:package). new(:name => 'sudo.rte', :ensure => :latest, :source => 'mysource', :provider => :aix) resource.stubs(:should).with(:ensure).returns(:latest) resource.should(:ensure) resource.provider.class.stubs(:execute).returns(<<-END.chomp) sudo:sudo.rte:1.7.10.4::I:C:::::N:Configurable super-user privileges runtime::::0:: sudo:sudo.rte:1.8.6.4::I:T:::::N:Configurable super-user privileges runtime::::0:: END resource.provider.class.prefetch('sudo.rte' => resource) expect(resource.provider.latest).to eq('1.8.6.4') end end it "update should install a package" do @provider.expects(:install).with(false) @provider.update end it "should prefetch when some packages lack sources" do latest = Puppet::Type.type(:package).new(:name => 'mypackage', :ensure => :latest, :source => 'mysource', :provider => :aix) absent = Puppet::Type.type(:package).new(:name => 'otherpackage', :ensure => :absent, :provider => :aix) Process.stubs(:euid).returns(0) described_class.expects(:execute).returns 'mypackage:mypackage.rte:1.8.6.4::I:T:::::N:A Super Cool Package::::0::\n' described_class.prefetch({ 'mypackage' => latest, 'otherpackage' => absent }) end end puppet-5.5.10/spec/unit/provider/package/appdmg_spec.rb0000644005276200011600000000262213417161721022761 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:package).provider(:appdmg) do let(:resource) { Puppet::Type.type(:package).new(:name => 'foo', :provider => :appdmg) } let(:provider) { described_class.new(resource) } describe "when installing an appdmg" do let(:fake_mountpoint) { "/tmp/dmg.foo" } let(:fake_hdiutil_plist) { {"system-entities" => [{"mount-point" => fake_mountpoint}]} } before do fh = mock 'filehandle' fh.stubs(:path).yields "/tmp/foo" resource[:source] = "foo.dmg" described_class.stubs(:open).yields fh Dir.stubs(:mktmpdir).returns "/tmp/testtmp123" FileUtils.stubs(:remove_entry_secure) end describe "from a remote source" do let(:tmpdir) { "/tmp/good123" } before :each do resource[:source] = "http://fake.puppetlabs.com/foo.dmg" end it "should call tmpdir and use the returned directory" do Dir.expects(:mktmpdir).returns tmpdir Dir.stubs(:entries).returns ["foo.app"] described_class.expects(:curl).with do |*args| args[0] == "-o" && args[1].include?(tmpdir) && ! args.include?("-k") end described_class.stubs(:hdiutil).returns 'a plist' Puppet::Util::Plist.expects(:parse_plist).with('a plist').returns fake_hdiutil_plist described_class.expects(:installapp) provider.install end end end end puppet-5.5.10/spec/unit/provider/package/apt_spec.rb0000644005276200011600000001071213417161721022274 0ustar jenkinsjenkinsrequire 'spec_helper' describe Puppet::Type.type(:package).provider(:apt) do let(:name) { 'asdf' } let(:resource) do Puppet::Type.type(:package).new( :name => name, :provider => 'apt' ) end let(:provider) do provider = subject() provider.resource = resource provider end it "should be the default provider on :osfamily => Debian" do Facter.expects(:value).with(:osfamily).returns("Debian") expect(described_class.default?).to be_truthy end it "should be versionable" do expect(described_class).to be_versionable end it "should use :install to update" do provider.expects(:install) provider.update end it "should use 'apt-get remove' to uninstall" do provider.expects(:aptget).with("-y", "-q", :remove, name) provider.uninstall end it "should use 'apt-get purge' and 'dpkg purge' to purge" do provider.expects(:aptget).with("-y", "-q", :remove, "--purge", name) provider.expects(:dpkg).with("--purge", name) provider.purge end it "should use 'apt-cache policy' to determine the latest version of a package" do provider.expects(:aptcache).with(:policy, name).returns "#{name}: Installed: 1:1.0 Candidate: 1:1.1 Version table: 1:1.0 650 http://ftp.osuosl.org testing/main Packages *** 1:1.1 100 /var/lib/dpkg/status" expect(provider.latest).to eq("1:1.1") end it "should print and error and return nil if no policy is found" do provider.expects(:aptcache).with(:policy, name).returns "#{name}:" provider.expects(:err) expect(provider.latest).to be_nil end it "should be able to preseed" do expect(provider).to respond_to(:run_preseed) end it "should preseed with the provided responsefile when preseeding is called for" do resource[:responsefile] = '/my/file' Puppet::FileSystem.expects(:exist?).with('/my/file').returns true provider.expects(:info) provider.expects(:preseed).with('/my/file') provider.run_preseed end it "should not preseed if no responsefile is provided" do provider.expects(:info) provider.expects(:preseed).never provider.run_preseed end describe "when installing" do it "should preseed if a responsefile is provided" do resource[:responsefile] = "/my/file" provider.expects(:run_preseed) provider.stubs(:aptget) provider.install end it "should check for a cdrom" do provider.expects(:checkforcdrom) provider.stubs(:aptget) provider.install end it "should use 'apt-get install' with the package name if no version is asked for" do resource[:ensure] = :installed provider.expects(:aptget).with { |*command| command[-1] == name and command[-2] == :install } provider.install end it "should specify the package version if one is asked for" do resource[:ensure] = '1.0' provider.expects(:aptget).with { |*command| command[-1] == "#{name}=1.0" } provider.install end it "should use --force-yes if a package version is specified" do resource[:ensure] = '1.0' provider.expects(:aptget).with { |*command| command.include?("--force-yes") } provider.install end it "should do a quiet install" do provider.expects(:aptget).with { |*command| command.include?("-q") } provider.install end it "should default to 'yes' for all questions" do provider.expects(:aptget).with { |*command| command.include?("-y") } provider.install end it "should keep config files if asked" do resource[:configfiles] = :keep provider.expects(:aptget).with { |*command| command.include?("DPkg::Options::=--force-confold") } provider.install end it "should replace config files if asked" do resource[:configfiles] = :replace provider.expects(:aptget).with { |*command| command.include?("DPkg::Options::=--force-confnew") } provider.install end it 'should support string install options' do resource[:install_options] = ['--foo', '--bar'] provider.expects(:aptget).with('-q', '-y', '-o', 'DPkg::Options::=--force-confold', '--foo', '--bar', :install, name) provider.install end it 'should support hash install options' do resource[:install_options] = ['--foo', { '--bar' => 'baz', '--baz' => 'foo' }] provider.expects(:aptget).with('-q', '-y', '-o', 'DPkg::Options::=--force-confold', '--foo', '--bar=baz', '--baz=foo', :install, name) provider.install end end end puppet-5.5.10/spec/unit/provider/package/aptitude_spec.rb0000644005276200011600000000257213417161721023334 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:package).provider(:aptitude) do let :type do Puppet::Type.type(:package) end let :pkg do type.new(:name => 'faff', :provider => :aptitude, :source => '/tmp/faff.deb') end it { is_expected.to be_versionable } context "when retrieving ensure" do let(:dpkgquery_path) { '/bin/dpkg-query' } before do Puppet::Util.stubs(:which).with('/usr/bin/dpkg-query').returns(dpkgquery_path) end { :absent => "deinstall ok config-files faff 1.2.3-1\n", "1.2.3-1" => "install ok installed faff 1.2.3-1\n", }.each do |expect, output| it "detects #{expect} packages" do Puppet::Util::Execution.expects(:execute).with( [dpkgquery_path, '-W', '--showformat', "'${Status} ${Package} ${Version}\\n'", 'faff'], {:failonfail => true, :combine => true, :custom_environment => {}} ).returns(Puppet::Util::Execution::ProcessOutput.new(output, 0)) expect(pkg.property(:ensure).retrieve).to eq(expect) end end end it "installs when asked" do pkg.provider.expects(:aptitude). with('-y', '-o', 'DPkg::Options::=--force-confold', :install, 'faff'). returns(0) pkg.provider.install end it "purges when asked" do pkg.provider.expects(:aptitude).with('-y', 'purge', 'faff').returns(0) pkg.provider.purge end end puppet-5.5.10/spec/unit/provider/package/aptrpm_spec.rb0000644005276200011600000000315013417161721023011 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:package).provider(:aptrpm) do let :type do Puppet::Type.type(:package) end let :pkg do type.new(:name => 'faff', :provider => :aptrpm, :source => '/tmp/faff.rpm') end it { is_expected.to be_versionable } context "when retrieving ensure" do before(:each) do Puppet::Util.stubs(:which).with("rpm").returns("/bin/rpm") pkg.provider.stubs(:which).with("rpm").returns("/bin/rpm") Puppet::Util::Execution.expects(:execute).with(["/bin/rpm", "--version"], {:combine => true, :custom_environment => {}, :failonfail => true}).returns(Puppet::Util::Execution::ProcessOutput.new("4.10.1\n", 0)).at_most_once end def rpm_args ['-q', 'faff', '--nosignature', '--nodigest', '--qf', "'%{NAME} %|EPOCH?{%{EPOCH}}:{0}| %{VERSION} %{RELEASE} %{ARCH}\\n'"] end def rpm(args = rpm_args) pkg.provider.expects(:rpm).with(*args) end it "should report purged packages" do rpm.raises(Puppet::ExecutionFailure, "couldn't find rpm") expect(pkg.property(:ensure).retrieve).to eq(:purged) end it "should report present packages correctly" do rpm.returns("faff-1.2.3-1 0 1.2.3-1 5 i686\n") expect(pkg.property(:ensure).retrieve).to eq("1.2.3-1-5") end end it "should try and install when asked" do pkg.provider.expects(:aptget). with('-q', '-y', 'install', 'faff'). returns(0) pkg.provider.install end it "should try and purge when asked" do pkg.provider.expects(:aptget).with('-y', '-q', 'remove', '--purge', 'faff').returns(0) pkg.provider.purge end end puppet-5.5.10/spec/unit/provider/package/base_spec.rb0000644005276200011600000000123313417161721022420 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/provider/package' describe Puppet::Provider::Package do it 'returns absent for uninstalled packages when not purgeable' do provider = Puppet::Provider::Package.new provider.expects(:query).returns nil provider.class.expects(:feature?).with(:purgeable).returns false expect(provider.properties[:ensure]).to eq(:absent) end it 'returns purged for uninstalled packages when purgeable' do provider = Puppet::Provider::Package.new provider.expects(:query).returns nil provider.class.expects(:feature?).with(:purgeable).returns true expect(provider.properties[:ensure]).to eq(:purged) end end puppet-5.5.10/spec/unit/provider/package/freebsd_spec.rb0000644005276200011600000000312413417161721023121 0ustar jenkinsjenkinsrequire 'spec_helper' describe Puppet::Type.type(:package).provider(:freebsd) do before :each do # Create a mock resource @resource = stub 'resource' # A catch all; no parameters set @resource.stubs(:[]).returns(nil) # But set name and source @resource.stubs(:[]).with(:name).returns "mypackage" @resource.stubs(:[]).with(:ensure).returns :installed @provider = subject() @provider.resource = @resource end it "should have an install method" do @provider = subject() expect(@provider).to respond_to(:install) end context "when installing" do before :each do @resource.stubs(:should).with(:ensure).returns(:installed) end it "should install a package from a path to a directory" do # For better or worse, trailing '/' is needed. --daniel 2011-01-26 path = '/path/to/directory/' @resource.stubs(:[]).with(:source).returns(path) Puppet::Util.expects(:withenv).once.with({:PKG_PATH => path}).yields @provider.expects(:pkgadd).once.with("mypackage") expect { @provider.install }.to_not raise_error end %w{http https ftp}.each do |protocol| it "should install a package via #{protocol}" do # For better or worse, trailing '/' is needed. --daniel 2011-01-26 path = "#{protocol}://localhost/" @resource.stubs(:[]).with(:source).returns(path) Puppet::Util.expects(:withenv).once.with({:PACKAGESITE => path}).yields @provider.expects(:pkgadd).once.with('-r', "mypackage") expect { @provider.install }.to_not raise_error end end end end puppet-5.5.10/spec/unit/provider/package/hpux_spec.rb0000644005276200011600000000263113417161721022475 0ustar jenkinsjenkinsrequire 'spec_helper' describe Puppet::Type.type(:package).provider(:hpux) do before(:each) do # Create a mock resource @resource = stub 'resource' # A catch all; no parameters set @resource.stubs(:[]).returns(nil) # But set name and source @resource.stubs(:[]).with(:name).returns "mypackage" @resource.stubs(:[]).with(:source).returns "mysource" @resource.stubs(:[]).with(:ensure).returns :installed @provider = subject() @provider.stubs(:resource).returns @resource end it "should have an install method" do @provider = subject() expect(@provider).to respond_to(:install) end it "should have an uninstall method" do @provider = subject() expect(@provider).to respond_to(:uninstall) end it "should have a swlist method" do @provider = subject() expect(@provider).to respond_to(:swlist) end context "when installing" do it "should use a command-line like 'swinstall -x mount_all_filesystems=false -s SOURCE PACKAGE-NAME'" do @provider.expects(:swinstall).with('-x', 'mount_all_filesystems=false', '-s', 'mysource', 'mypackage') @provider.install end end context "when uninstalling" do it "should use a command-line like 'swremove -x mount_all_filesystems=false PACKAGE-NAME'" do @provider.expects(:swremove).with('-x', 'mount_all_filesystems=false', 'mypackage') @provider.uninstall end end end puppet-5.5.10/spec/unit/provider/package/macports_spec.rb0000644005276200011600000001062313417161721023341 0ustar jenkinsjenkinsrequire 'spec_helper' describe Puppet::Type.type(:package).provider(:macports) do let :resource_name do "foo" end let :resource do Puppet::Type.type(:package).new(:name => resource_name, :provider => :macports) end let :provider do prov = resource.provider prov.expects(:execute).never prov end let :current_hash do {:name => resource_name, :ensure => "1.2.3", :revision => "1", :provider => :macports} end context "provider features" do subject { provider } it { is_expected.to be_installable } it { is_expected.to be_uninstallable } it { is_expected.to be_upgradeable } it { is_expected.to be_versionable } end context "when listing all instances" do it "should call port -q installed" do described_class.expects(:port).with("-q", :installed).returns("") described_class.instances end it "should create instances from active ports" do described_class.expects(:port).returns("foo @1.234.5_2 (active)") expect(described_class.instances.size).to eq(1) end it "should ignore ports that aren't activated" do described_class.expects(:port).returns("foo @1.234.5_2") expect(described_class.instances.size).to eq(0) end it "should ignore variants" do expect(described_class.parse_installed_query_line("bar @1.0beta2_38_1+x11+java (active)")). to eq({:provider=>:macports, :revision=>"1", :name=>"bar", :ensure=>"1.0beta2_38"}) end end context "when installing" do it "should not specify a version when ensure is set to latest" do resource[:ensure] = :latest provider.expects(:port).with { |flag, method, name, version| expect(version).to be_nil } provider.install end it "should not specify a version when ensure is set to present" do resource[:ensure] = :present provider.expects(:port).with { |flag, method, name, version| expect(version).to be_nil } provider.install end it "should specify a version when ensure is set to a version" do resource[:ensure] = "1.2.3" provider.expects(:port).with { |flag, method, name, version| expect(version).to be } provider.install end end context "when querying for the latest version" do let :new_info_line do "1.2.3 2" end let :infoargs do ["/opt/local/bin/port", "-q", :info, "--line", "--version", "--revision", resource_name] end let(:arguments) do {:failonfail => false, :combine => false} end before :each do provider.stubs(:command).with(:port).returns("/opt/local/bin/port") end it "should return nil when the package cannot be found" do resource[:name] = resource_name provider.expects(:execute).with(infoargs, arguments).returns("") expect(provider.latest).to eq(nil) end it "should return the current version if the installed port has the same revision" do current_hash[:revision] = "2" provider.expects(:execute).with(infoargs, arguments).returns(new_info_line) provider.expects(:query).returns(current_hash) expect(provider.latest).to eq(current_hash[:ensure]) end it "should return the new version_revision if the installed port has a lower revision" do current_hash[:revision] = "1" provider.expects(:execute).with(infoargs, arguments).returns(new_info_line) provider.expects(:query).returns(current_hash) expect(provider.latest).to eq("1.2.3_2") end it "should return the newest version if the port is not installed" do resource[:name] = resource_name provider.expects(:execute).with(infoargs, arguments).returns(new_info_line) provider.expects(:execute).with(["/opt/local/bin/port", "-q", :installed, resource[:name]], arguments).returns("") expect(provider.latest).to eq("1.2.3_2") end end context "when updating a port" do it "should execute port install if the port is installed" do resource[:name] = resource_name resource[:ensure] = :present provider.stubs(:query).returns(current_hash) provider.expects(:port).with("-q", :install, resource_name) provider.update end it "should execute port install if the port is not installed" do resource[:name] = resource_name resource[:ensure] = :present provider.stubs(:query).returns("") provider.expects(:port).with("-q", :install, resource_name) provider.update end end end puppet-5.5.10/spec/unit/provider/package/nim_spec.rb0000644005276200011600000002473613417161721022306 0ustar jenkinsjenkinsrequire 'spec_helper' describe Puppet::Type.type(:package).provider(:nim) do before(:each) do # Create a mock resource @resource = stub 'resource' # A catch all; no parameters set @resource.stubs(:[]).returns(nil) # But set name and source @resource.stubs(:[]).with(:name).returns "mypackage.foo" @resource.stubs(:[]).with(:source).returns "mysource" @resource.stubs(:[]).with(:ensure).returns :installed @provider = subject() @provider.resource = @resource end it "should have an install method" do @provider = subject() expect(@provider).to respond_to(:install) end let(:bff_showres_output) { Puppet::Util::Execution::ProcessOutput.new(<> +-----------------------------------------------------------------------------+ BUILDDATE Verification ... +-----------------------------------------------------------------------------+ Verifying build dates...done FILESET STATISTICS ------------------ 1 Selected to be installed, of which: 1 Already installed (directly or via superseding filesets) ---- 0 Total to be installed Pre-installation Failure/Warning Summary ---------------------------------------- Name Level Pre-installation Failure/Warning ------------------------------------------------------------------------------- mypackage.foo 1.2.3.1 Already superseded by 1.2.3.4 OUTPUT @resource.stubs(:should).with(:ensure).returns("1.2.3.1") Puppet::Util::Execution.expects(:execute).with("/usr/sbin/nimclient -o showres -a resource=mysource |/usr/bin/grep -p -E 'mypackage\\.foo( |-)1\\.2\\.3\\.1'").returns(bff_showres_output).in_sequence(nimclient_sequence) @provider.expects(:nimclient).with("-o", "cust", "-a", "installp_flags=acgwXY", "-a", "lpp_source=mysource", "-a", "filesets=mypackage.foo 1.2.3.1").in_sequence(nimclient_sequence).returns(install_output) expect { @provider.install }.to raise_error(Puppet::Error, "NIM package provider is unable to downgrade packages") end it "should succeed if an RPM package is available on the lpp source" do nimclient_sequence = sequence('nimclient') @resource.stubs(:should).with(:ensure).returns("1.2.3-4") Puppet::Util::Execution.expects(:execute).with("/usr/sbin/nimclient -o showres -a resource=mysource |/usr/bin/grep -p -E 'mypackage\\.foo( |-)1\\.2\\.3\\-4'").returns(rpm_showres_output).in_sequence(nimclient_sequence) @provider.expects(:nimclient).with("-o", "cust", "-a", "installp_flags=acgwXY", "-a", "lpp_source=mysource", "-a", "filesets=mypackage.foo-1.2.3-4").in_sequence(nimclient_sequence) @provider.install end end it "should fail if the specified version of a RPM package is superseded" do nimclient_sequence = sequence('nimclient') install_output = < 'mypackage.foo').returns(nil) @provider.uninstall end it "should call rpm to uninstall an rpm package" do @provider.expects(:lslpp).with("-qLc", "mypackage.foo").returns("cdrecord:cdrecord-1.9-6:1.9-6: : :C:R:A command line CD/DVD recording program.: :/bin/rpm -e cdrecord: : : : :0: :/opt/freeware:Wed Jun 29 09:41:32 PDT 2005") @provider.expects(:rpm).with("-e", "mypackage.foo") @provider.class.expects(:pkglist).with(:pkgname => 'mypackage.foo').returns(nil) @provider.uninstall end end context "when parsing nimclient showres output" do describe "#parse_showres_output" do it "should be able to parse installp/BFF package listings" do packages = subject.send(:parse_showres_output, bff_showres_output) expect(Set.new(packages.keys)).to eq(Set.new(['mypackage.foo'])) versions = packages['mypackage.foo'] ['1.2.3.1', '1.2.3.4', '1.2.3.8'].each do |version| expect(versions.has_key?(version)).to eq(true) expect(versions[version]).to eq(:installp) end end it "should be able to parse RPM package listings" do packages = subject.send(:parse_showres_output, rpm_showres_output) expect(Set.new(packages.keys)).to eq(Set.new(['mypackage.foo'])) versions = packages['mypackage.foo'] ['1.2.3-1', '1.2.3-4', '1.2.3-8'].each do |version| expect(versions.has_key?(version)).to eq(true) expect(versions[version]).to eq(:rpm) end end end describe "#determine_latest_version" do context "when there are multiple versions" do it "should return the latest version" do expect(subject.send(:determine_latest_version, rpm_showres_output, 'mypackage.foo')).to eq([:rpm, '1.2.3-8']) end end context "when there is only one version" do it "should return the type specifier and `nil` for the version number" do nimclient_showres_output = < 'bash', :provider => 'openbsd') } let(:provider) { described_class.new(package) } def expect_read_from_pkgconf(lines) pkgconf = stub(:readlines => lines) Puppet::FileSystem.expects(:exist?).with('/etc/pkg.conf').returns(true) File.expects(:open).with('/etc/pkg.conf', 'rb').returns(pkgconf) end def expect_pkgadd_with_source(source) provider.expects(:pkgadd).with do |fullname| expect(ENV).not_to be_key('PKG_PATH') expect(fullname).to eq([source]) end end def expect_pkgadd_with_env_and_name(source, &block) expect(ENV).not_to be_key('PKG_PATH') provider.expects(:pkgadd).with do |fullname| expect(ENV).to be_key('PKG_PATH') expect(ENV['PKG_PATH']).to eq(source) expect(fullname).to eq([provider.resource[:name]]) end provider.expects(:execpipe).with(['/bin/pkg_info', '-I', provider.resource[:name]]).yields('') yield expect(ENV).not_to be_key('PKG_PATH') end context 'provider features' do it { is_expected.to be_installable } it { is_expected.to be_install_options } it { is_expected.to be_uninstallable } it { is_expected.to be_uninstall_options } it { is_expected.to be_upgradeable } it { is_expected.to be_versionable } end before :each do # Stub some provider methods to avoid needing the actual software # installed, so we can test on whatever platform we want. described_class.stubs(:command).with(:pkginfo).returns('/bin/pkg_info') described_class.stubs(:command).with(:pkgadd).returns('/bin/pkg_add') described_class.stubs(:command).with(:pkgdelete).returns('/bin/pkg_delete') end context "#instances" do it "should return nil if execution failed" do described_class.expects(:execpipe).raises(Puppet::ExecutionFailure, 'wawawa') expect(described_class.instances).to be_nil end it "should return the empty set if no packages are listed" do described_class.expects(:execpipe).with(%w{/bin/pkg_info -a}).yields(StringIO.new('')) expect(described_class.instances).to be_empty end it "should return all packages when invoked" do fixture = File.read(my_fixture('pkginfo.list')) described_class.expects(:execpipe).with(%w{/bin/pkg_info -a}).yields(fixture) expect(described_class.instances.map(&:name).sort).to eq( %w{bash bzip2 expat gettext libiconv lzo openvpn python vim wget}.sort ) end it "should return all flavors if set" do fixture = File.read(my_fixture('pkginfo_flavors.list')) described_class.expects(:execpipe).with(%w{/bin/pkg_info -a}).yields(fixture) instances = described_class.instances.map {|p| {:name => p.get(:name), :ensure => p.get(:ensure), :flavor => p.get(:flavor)}} expect(instances.size).to eq(2) expect(instances[0]).to eq({:name => 'bash', :ensure => '3.1.17', :flavor => 'static'}) expect(instances[1]).to eq({:name => 'vim', :ensure => '7.0.42', :flavor => 'no_x11'}) end end context "#install" do it "should fail if the resource doesn't have a source" do Puppet::FileSystem.expects(:exist?).with('/etc/pkg.conf').returns(false) expect { provider.install }.to raise_error(Puppet::Error, /must specify a package source/) end it "should fail if /etc/pkg.conf exists, but is not readable" do Puppet::FileSystem.expects(:exist?).with('/etc/pkg.conf').returns(true) File.expects(:open).with('/etc/pkg.conf', 'rb').raises(Errno::EACCES) expect { provider.install }.to raise_error(Errno::EACCES, /Permission denied/) end it "should fail if /etc/pkg.conf exists, but there is no installpath" do expect_read_from_pkgconf([]) expect { provider.install }.to raise_error(Puppet::Error, /No valid installpath found in \/etc\/pkg\.conf and no source was set/) end it "should install correctly when given a directory-unlike source" do source = '/whatever.tgz' provider.resource[:source] = source expect_pkgadd_with_source(source) provider.install end it "should install correctly when given a directory-like source" do source = '/whatever/' provider.resource[:source] = source expect_pkgadd_with_env_and_name(source) do provider.install end end it "should install correctly when given a CDROM installpath" do dir = '/mnt/cdrom/5.2/packages/amd64/' expect_read_from_pkgconf(["installpath = #{dir}"]) expect_pkgadd_with_env_and_name(dir) do provider.install end end it "should install correctly when given a ftp mirror" do url = 'ftp://your.ftp.mirror/pub/OpenBSD/5.2/packages/amd64/' expect_read_from_pkgconf(["installpath = #{url}"]) expect_pkgadd_with_env_and_name(url) do provider.install end end it "should set the resource's source parameter" do url = 'ftp://your.ftp.mirror/pub/OpenBSD/5.2/packages/amd64/' expect_read_from_pkgconf(["installpath = #{url}"]) expect_pkgadd_with_env_and_name(url) do provider.install end expect(provider.resource[:source]).to eq(url) end it "should strip leading whitespace in installpath" do dir = '/one/' lines = ["# Notice the extra spaces after the ='s\n", "installpath = #{dir}\n", "# And notice how each line ends with a newline\n"] expect_read_from_pkgconf(lines) expect_pkgadd_with_env_and_name(dir) do provider.install end end it "should not require spaces around the equals" do dir = '/one/' lines = ["installpath=#{dir}"] expect_read_from_pkgconf(lines) expect_pkgadd_with_env_and_name(dir) do provider.install end end it "should be case-insensitive" do dir = '/one/' lines = ["INSTALLPATH = #{dir}"] expect_read_from_pkgconf(lines) expect_pkgadd_with_env_and_name(dir) do provider.install end end it "should ignore unknown keywords" do dir = '/one/' lines = ["foo = bar\n", "installpath = #{dir}\n"] expect_read_from_pkgconf(lines) expect_pkgadd_with_env_and_name(dir) do provider.install end end it "should preserve trailing spaces" do dir = '/one/ ' lines = ["installpath = #{dir}"] expect_read_from_pkgconf(lines) expect_pkgadd_with_source(dir) provider.install end it "should append installpath" do urls = ["ftp://your.ftp.mirror/pub/OpenBSD/5.2/packages/amd64/", "http://another.ftp.mirror/pub/OpenBSD/5.2/packages/amd64/"] lines = ["installpath = #{urls[0]}\n", "installpath += #{urls[1]}\n"] expect_read_from_pkgconf(lines) expect_pkgadd_with_env_and_name(urls.join(":")) do provider.install end end it "should handle append on first installpath" do url = "ftp://your.ftp.mirror/pub/OpenBSD/5.2/packages/amd64/" lines = ["installpath += #{url}\n"] expect_read_from_pkgconf(lines) expect_pkgadd_with_env_and_name(url) do provider.install end end %w{ installpath installpath= installpath+=}.each do |line| it "should reject '#{line}'" do expect_read_from_pkgconf([line]) expect { provider.install }.to raise_error(Puppet::Error, /No valid installpath found in \/etc\/pkg\.conf and no source was set/) end end it 'should use install_options as Array' do provider.resource[:source] = '/tma1/' provider.resource[:install_options] = ['-r', '-z'] provider.expects(:pkgadd).with(['-r', '-z', 'bash']) provider.install end end context "#latest" do before do provider.resource[:source] = '/tmp/tcsh.tgz' provider.resource[:name] = 'tcsh' provider.stubs(:pkginfo).with('tcsh') end it "should return the ensure value if the package is already installed" do provider.stubs(:properties).returns({:ensure => '4.2.45'}) provider.stubs(:pkginfo).with('-Q', 'tcsh') expect(provider.latest).to eq('4.2.45') end it "should recognize a new version" do pkginfo_query = 'tcsh-6.18.01p1' provider.stubs(:pkginfo).with('-Q', 'tcsh').returns(pkginfo_query) expect(provider.latest).to eq('6.18.01p1') end it "should recognize a newer version" do provider.stubs(:properties).returns({:ensure => '1.6.8'}) pkginfo_query = 'tcsh-1.6.10' provider.stubs(:pkginfo).with('-Q', 'tcsh').returns(pkginfo_query) expect(provider.latest).to eq('1.6.10') end it "should recognize a package that is already the newest" do pkginfo_query = 'tcsh-6.18.01p0 (installed)' provider.stubs(:pkginfo).with('-Q', 'tcsh').returns(pkginfo_query) expect(provider.latest).to eq('6.18.01p0') end end context "#get_full_name" do it "should return the full unversioned package name when updating with a flavor" do provider.resource[:ensure] = 'latest' provider.resource[:flavor] = 'static' expect(provider.get_full_name).to eq('bash--static') end it "should return the full unversioned package name when updating without a flavor" do provider.resource[:name] = 'puppet' provider.resource[:ensure] = 'latest' expect(provider.get_full_name).to eq('puppet') end it "should use the ensure parameter if it is numeric" do provider.resource[:name] = 'zsh' provider.resource[:ensure] = '1.0' expect(provider.get_full_name).to eq('zsh-1.0') end it "should lookup the correct version" do output = 'bash-3.1.17 GNU Bourne Again Shell' provider.expects(:execpipe).with(%w{/bin/pkg_info -I bash}).yields(output) expect(provider.get_full_name).to eq('bash-3.1.17') end it "should lookup the correction version with flavors" do provider.resource[:name] = 'fossil' provider.resource[:flavor] = 'static' output = 'fossil-1.29v0-static simple distributed software configuration management' provider.expects(:execpipe).with(%w{/bin/pkg_info -I fossil}).yields(output) expect(provider.get_full_name).to eq('fossil-1.29v0-static') end end context "#get_version" do it "should return nil if execution fails" do provider.expects(:execpipe).raises(Puppet::ExecutionFailure, 'wawawa') expect(provider.get_version).to be_nil end it "should return the package version if in the output" do output = 'bash-3.1.17 GNU Bourne Again Shell' provider.expects(:execpipe).with(%w{/bin/pkg_info -I bash}).yields(output) expect(provider.get_version).to eq('3.1.17') end it "should return the empty string if the package is not present" do provider.resource[:name] = 'zsh' provider.expects(:execpipe).with(%w{/bin/pkg_info -I zsh}).yields(StringIO.new('')) expect(provider.get_version).to eq('') end end context "#query" do it "should return the installed version if present" do fixture = File.read(my_fixture('pkginfo.detail')) provider.expects(:pkginfo).with('bash').returns(fixture) expect(provider.query).to eq({ :ensure => '3.1.17' }) end it "should return nothing if not present" do provider.resource[:name] = 'zsh' provider.expects(:pkginfo).with('zsh').returns('') expect(provider.query).to be_nil end end context "#install_options" do it "should return nill by default" do expect(provider.install_options).to be_nil end it "should return install_options when set" do provider.resource[:install_options] = ['-n'] expect(provider.resource[:install_options]).to eq(['-n']) end it "should return multiple install_options when set" do provider.resource[:install_options] = ['-L', '/opt/puppet'] expect(provider.resource[:install_options]).to eq(['-L', '/opt/puppet']) end it 'should return install_options when set as hash' do provider.resource[:install_options] = { '-Darch' => 'vax' } expect(provider.install_options).to eq(['-Darch=vax']) end end context "#uninstall_options" do it "should return nill by default" do expect(provider.uninstall_options).to be_nil end it "should return uninstall_options when set" do provider.resource[:uninstall_options] = ['-n'] expect(provider.resource[:uninstall_options]).to eq(['-n']) end it "should return multiple uninstall_options when set" do provider.resource[:uninstall_options] = ['-q', '-c'] expect(provider.resource[:uninstall_options]).to eq(['-q', '-c']) end it 'should return uninstall_options when set as hash' do provider.resource[:uninstall_options] = { '-Dbaddepend' => '1' } expect(provider.uninstall_options).to eq(['-Dbaddepend=1']) end end context "#uninstall" do describe 'when uninstalling' do it 'should use erase to purge' do provider.expects(:pkgdelete).with('-c', '-q', 'bash') provider.purge end end describe 'with uninstall_options' do it 'should use uninstall_options as Array' do provider.resource[:uninstall_options] = ['-q', '-c'] provider.expects(:pkgdelete).with(['-q', '-c'], 'bash') provider.uninstall end end end end puppet-5.5.10/spec/unit/provider/package/opkg_spec.rb0000644005276200011600000001340013417161721022445 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:package).provider(:opkg) do let(:resource) do Puppet::Type.type(:package).new(:name => 'package') end let(:provider) { described_class.new(resource) } before do Puppet::Util::Execution.stubs(:execute).never Puppet::Util.stubs(:which).with("opkg").returns("/bin/opkg") provider.stubs(:package_lists).returns ['.', '..', 'packages'] end describe "when installing" do before do provider.stubs(:query).returns({ :ensure => '1.0' }) end context "when the package list is absent" do before do provider.stubs(:package_lists).returns ['.', '..'] #empty, no package list end it "fetches the package list when installing" do provider.expects(:opkg).with('update') provider.expects(:opkg).with("--force-overwrite", "install", resource[:name]) provider.install end end context "when the package list is present" do before do provider.stubs(:package_lists).returns ['.', '..', 'lists'] # With a pre-downloaded package list end it "fetches the package list when installing" do provider.expects(:opkg).with('update').never provider.expects(:opkg).with("--force-overwrite", "install", resource[:name]) provider.install end end it "should call opkg install" do Puppet::Util::Execution.expects(:execute).with(["/bin/opkg", "--force-overwrite", "install", resource[:name]], {:failonfail => true, :combine => true, :custom_environment => {}}) provider.install end context "when :source is specified" do context "works on valid urls" do %w{ /some/package/file http://some.package.in/the/air ftp://some.package.in/the/air }.each do |source| it "should install #{source} directly" do resource[:source] = source Puppet::Util::Execution.expects(:execute).with(["/bin/opkg", "--force-overwrite", "install", resource[:source]], {:failonfail => true, :combine => true, :custom_environment => {}}) provider.install end end end context "as a file:// URL" do before do @package_file = "file:///some/package/file" @actual_file_path = "/some/package/file" resource[:source] = @package_file end it "should install from the path segment of the URL" do Puppet::Util::Execution.expects(:execute).returns(Puppet::Util::Execution::ProcessOutput.new("", 0)) provider.install end end context "with invalid URL for opkg" do before do # Emulate the `opkg` command returning a non-zero exit value Puppet::Util::Execution.stubs(:execute).raises Puppet::ExecutionFailure, 'oops' end context "puppet://server/whatever" do before do resource[:source] = "puppet://server/whatever" end it "should fail" do expect { provider.install }.to raise_error Puppet::ExecutionFailure end end context "as a malformed URL" do before do resource[:source] = "blah://" end it "should fail" do expect { provider.install }.to raise_error Puppet::ExecutionFailure end end end end # end when source is specified end # end when installing describe "when updating" do it "should call install" do provider.expects(:install).returns("install return value") expect(provider.update).to eq("install return value") end end describe "when uninstalling" do it "should run opkg remove bla" do Puppet::Util::Execution.expects(:execute).with(["/bin/opkg", "remove", resource[:name]], {:failonfail => true, :combine => true, :custom_environment => {}}) provider.uninstall end end describe "when querying" do describe "self.instances" do let (:packages) do <<-OPKG_OUTPUT dropbear - 2011.54-2 kernel - 3.3.8-1-ba5cdb2523b4fc7722698b4a7ece6702 uhttpd - 2012-10-30-e57bf6d8bfa465a50eea2c30269acdfe751a46fd OPKG_OUTPUT end it "returns an array of packages" do Puppet::Util.stubs(:which).with("opkg").returns("/bin/opkg") described_class.stubs(:which).with("opkg").returns("/bin/opkg") described_class.expects(:execpipe).with("/bin/opkg list-installed").yields(packages) installed_packages = described_class.instances expect(installed_packages.length).to eq(3) expect(installed_packages[0].properties).to eq( { :provider => :opkg, :name => "dropbear", :ensure => "2011.54-2" } ) expect(installed_packages[1].properties).to eq( { :provider => :opkg, :name => "kernel", :ensure => "3.3.8-1-ba5cdb2523b4fc7722698b4a7ece6702" } ) expect(installed_packages[2].properties).to eq( { :provider => :opkg, :name => "uhttpd", :ensure => "2012-10-30-e57bf6d8bfa465a50eea2c30269acdfe751a46fd" } ) end end it "should return a nil if the package isn't found" do Puppet::Util::Execution.expects(:execute).returns(Puppet::Util::Execution::ProcessOutput.new("", 0)) expect(provider.query).to be_nil end it "should return a hash indicating that the package is missing on error" do Puppet::Util::Execution.expects(:execute).raises(Puppet::ExecutionFailure.new("ERROR!")) expect(provider.query).to eq({ :ensure => :purged, :status => 'missing', :name => resource[:name], :error => 'ok', }) end end #end when querying end # end describe provider puppet-5.5.10/spec/unit/provider/package/pacman_spec.rb0000644005276200011600000003773713417161721022767 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'stringio' describe Puppet::Type.type(:package).provider(:pacman) do let(:no_extra_options) { { :failonfail => true, :combine => true, :custom_environment => {} } } let(:executor) { Puppet::Util::Execution } let(:resolver) { Puppet::Util } let(:resource) { Puppet::Type.type(:package).new(:name => 'package', :provider => 'pacman') } let(:provider) { described_class.new(resource) } before do resolver.stubs(:which).with('/usr/bin/pacman').returns('/usr/bin/pacman') described_class.stubs(:which).with('/usr/bin/pacman').returns('/usr/bin/pacman') resolver.stubs(:which).with('/usr/bin/yaourt').returns('/usr/bin/yaourt') described_class.stubs(:which).with('/usr/bin/yaourt').returns('/usr/bin/yaourt') described_class.stubs(:group?).returns(false) described_class.stubs(:yaourt?).returns(false) end describe "when installing" do before do provider.stubs(:query).returns({ :ensure => '1.0' }) end it "should call pacman to install the right package quietly when yaourt is not installed" do args = ['--noconfirm', '--needed', '--noprogressbar', '-Sy', resource[:name]] provider.expects(:pacman).at_least_once.with(*args).returns '' provider.install end it "should call yaourt to install the right package quietly when yaourt is installed" do described_class.stubs(:yaourt?).returns(true) args = ['--noconfirm', '--needed', '--noprogressbar', '-Sy', resource[:name]] provider.expects(:yaourt).at_least_once.with(*args).returns '' provider.install end it "should raise an Puppet::Error if the installation failed" do executor.stubs(:execute).returns("") provider.expects(:query).returns(nil) expect { provider.install }.to raise_exception(Puppet::Error, /Could not find package/) end it "should raise an Puppet::Error when trying to install a group and allow_virtual is false" do described_class.stubs(:group?).returns(true) resource[:allow_virtual] = false expect { provider.install }.to raise_error(Puppet::Error, /Refusing to install package group/) end it "should not raise an Puppet::Error when trying to install a group and allow_virtual is true" do described_class.stubs(:group?).returns(true) resource[:allow_virtual] = true executor.stubs(:execute).returns("") provider.install end describe "and install_options are given" do before do resource[:install_options] = ['-x', {'--arg' => 'value'}] end it "should call pacman to install the right package quietly when yaourt is not installed" do args = ['--noconfirm', '--needed', '--noprogressbar', '-x', '--arg=value', '-Sy', resource[:name]] provider.expects(:pacman).at_least_once.with(*args).returns '' provider.install end it "should call yaourt to install the right package quietly when yaourt is installed" do described_class.stubs(:yaourt?).returns(true) args = ['--noconfirm', '--needed', '--noprogressbar', '-x', '--arg=value', '-Sy', resource[:name]] provider.expects(:yaourt).at_least_once.with(*args).returns '' provider.install end end context "when :source is specified" do let(:install_seq) { sequence("install") } context "recognizable by pacman" do %w{ /some/package/file http://some.package.in/the/air ftp://some.package.in/the/air }.each do |source| it "should install #{source} directly" do resource[:source] = source executor.expects(:execute). with(all_of(includes("-Sy"), includes("--noprogressbar")), no_extra_options). in_sequence(install_seq). returns("") executor.expects(:execute). with(all_of(includes("-U"), includes(source)), no_extra_options). in_sequence(install_seq). returns("") provider.install end end end context "as a file:// URL" do let(:actual_file_path) { "/some/package/file" } before do resource[:source] = "file:///some/package/file" end it "should install from the path segment of the URL" do executor.expects(:execute). with(all_of(includes("-Sy"), includes("--noprogressbar"), includes("--noconfirm")), no_extra_options). in_sequence(install_seq). returns("") executor.expects(:execute). with(all_of(includes("-U"), includes(actual_file_path)), no_extra_options). in_sequence(install_seq). returns("") provider.install end end context "as a puppet URL" do before do resource[:source] = "puppet://server/whatever" end it "should fail" do expect { provider.install }.to raise_error(Puppet::Error, /puppet:\/\/ URL is not supported/) end end context "as an unsupported URL scheme" do before do resource[:source] = "blah://foo.com" end it "should fail" do expect { provider.install }.to raise_error(Puppet::Error, /Source blah:\/\/foo\.com is not supported/) end end end end describe "when updating" do it "should call install" do provider.expects(:install).returns("install return value") expect(provider.update).to eq("install return value") end end describe "when uninstalling" do it "should call pacman to remove the right package quietly" do args = ["/usr/bin/pacman", "--noconfirm", "--noprogressbar", "-R", resource[:name]] executor.expects(:execute).with(args, no_extra_options).returns "" provider.uninstall end it "should call yaourt to remove the right package quietly" do described_class.stubs(:yaourt?).returns(true) args = ["--noconfirm", "--noprogressbar", "-R", resource[:name]] provider.expects(:yaourt).with(*args) provider.uninstall end it "adds any uninstall_options" do resource[:uninstall_options] = ['-x', {'--arg' => 'value'}] args = ["/usr/bin/pacman", "--noconfirm", "--noprogressbar", "-x", "--arg=value", "-R", resource[:name]] executor.expects(:execute).with(args, no_extra_options).returns "" provider.uninstall end it "should recursively remove packages when given a package group" do described_class.stubs(:group?).returns(true) args = ["/usr/bin/pacman", "--noconfirm", "--noprogressbar", "-R", "-s", resource[:name]] executor.expects(:execute).with(args, no_extra_options).returns "" provider.uninstall end end describe "when querying" do it "should query pacman" do executor.expects(:execpipe).with(["/usr/bin/pacman", '-Q']) executor.expects(:execpipe).with(["/usr/bin/pacman", '-Sgg', 'package']) provider.query end it "should return the version" do executor. expects(:execpipe). with(["/usr/bin/pacman", "-Q"]).yields(< 'package', :ensure => '1.01.3-2', :provider => :pacman, }) end it "should return a hash indicating that the package is missing" do executor.expects(:execpipe).twice.yields("") expect(provider.query).to be_nil end it "should raise an error if execpipe fails" do executor.expects(:execpipe).raises(Puppet::ExecutionFailure.new("ERROR!")) expect { provider.query }.to raise_error(RuntimeError) end describe 'when querying a group' do before :each do executor.expects(:execpipe).with(['/usr/bin/pacman', '-Q']).yields('foo 1.2.3') executor.expects(:execpipe).with(['/usr/bin/pacman', '-Sgg', 'package']).yields('package foo') end it 'should warn when allow_virtual is false' do resource[:allow_virtual] = false provider.expects(:warning) provider.query end it 'should not warn allow_virtual is true' do resource[:allow_virtual] = true described_class.expects(:warning).never provider.query end end end describe "when determining instances" do it "should retrieve installed packages and groups" do described_class.expects(:execpipe).with(["/usr/bin/pacman", '-Q']) described_class.expects(:execpipe).with(["/usr/bin/pacman", '-Sgg']) described_class.instances end it "should return installed packages" do described_class.expects(:execpipe).with(["/usr/bin/pacman", '-Q']).yields(StringIO.new("package1 1.23-4\npackage2 2.00\n")) described_class.expects(:execpipe).with(["/usr/bin/pacman", '-Sgg']).yields("") instances = described_class.instances expect(instances.length).to eq(2) expect(instances[0].properties).to eq({ :provider => :pacman, :ensure => '1.23-4', :name => 'package1' }) expect(instances[1].properties).to eq({ :provider => :pacman, :ensure => '2.00', :name => 'package2' }) end it "should return completely installed groups with a virtual version together with packages" do described_class.expects(:execpipe).with(["/usr/bin/pacman", '-Q']).yields(< :pacman, :ensure => '1.00', :name => 'package1' }) expect(instances[1].properties).to eq({ :provider => :pacman, :ensure => '1.00', :name => 'package2' }) expect(instances[2].properties).to eq({ :provider => :pacman, :ensure => 'package1 1.00, package2 1.00', :name => 'group1' }) end it "should not return partially installed packages" do described_class.expects(:execpipe).with(["/usr/bin/pacman", '-Q']).yields(< :pacman, :ensure => '1.00', :name => 'package1' }) end it 'should sort package names for installed groups' do described_class.expects(:execpipe).with(['/usr/bin/pacman', '-Sgg', 'group1']).yields(< '1', 'aa' => '1', 'b' => '1', } virtual_group_version = described_class.get_installed_groups(package_versions, 'group1') expect(virtual_group_version).to eq({ 'group1' => 'a 1, aa 1, b 1' }) end it "should return nil on error" do described_class.expects(:execpipe).raises(Puppet::ExecutionFailure.new("ERROR!")) expect { described_class.instances }.to raise_error(RuntimeError) end it "should warn on invalid input" do described_class.expects(:execpipe).twice.yields(StringIO.new("blah")) described_class.expects(:warning).with("Failed to match line 'blah'") expect(described_class.instances).to eq([]) end end describe "when determining the latest version" do it "should refresh package list" do get_latest_version = sequence("get_latest_version") executor. expects(:execute). in_sequence(get_latest_version). with(['/usr/bin/pacman', '-Sy'], no_extra_options) executor. stubs(:execute). in_sequence(get_latest_version). returns("") provider.latest end it "should get query pacman for the latest version" do get_latest_version = sequence("get_latest_version") executor. stubs(:execute). in_sequence(get_latest_version) executor. expects(:execute). in_sequence(get_latest_version). with(['/usr/bin/pacman', '-Sp', '--print-format', '%v', resource[:name]], no_extra_options). returns("") provider.latest end it "should return the version number from pacman" do executor. expects(:execute). at_least_once(). returns("1.00.2-3\n") expect(provider.latest).to eq("1.00.2-3") end it "should return a virtual group version when resource is a package group" do described_class.stubs(:group?).returns(true) get_latest_version = sequence("get_latest_version") executor. stubs(:execute). with(['/usr/bin/pacman', '-Sy'], no_extra_options). in_sequence(get_latest_version) executor. expects(:execute). in_sequence(get_latest_version). with(['/usr/bin/pacman', '-Sp', '--print-format', '%n %v', resource[:name]], no_extra_options). returns(< true, :combine => true, :custom_environment => {}}).raises(Puppet::ExecutionFailure, 'error') expect(described_class.group?('git')).to eq(false) end it 'should return false on empty pacman output' do executor.stubs(:execute).with(['/usr/bin/pacman', '-Sg', 'git'], {:failonfail => true, :combine => true, :custom_environment => {}}).returns '' expect(described_class.group?('git')).to eq(false) end it 'should return true on non-empty pacman output' do executor.stubs(:execute).with(['/usr/bin/pacman', '-Sg', 'vim-plugins'], {:failonfail => true, :combine => true, :custom_environment => {}}).returns 'vim-plugins vim-a' expect(described_class.group?('vim-plugins')).to eq(true) end end describe 'when querying installed groups' do let(:installed_packages) { {'package1' => '1.0', 'package2' => '2.0', 'package3' => '3.0'} } let(:groups) { [['foo package1'], ['foo package2'], ['bar package3'], ['bar package4'], ['baz package5']] } it 'should raise an error on non-zero pacman exit without a filter' do executor.expects(:open).with('| /usr/bin/pacman -Sgg 2>&1').returns 'error!' Puppet::Util::Execution.expects(:exitstatus).returns 1 expect { described_class.get_installed_groups(installed_packages) }.to raise_error(Puppet::ExecutionFailure, 'error!') end it 'should return empty groups on non-zero pacman exit with a filter' do executor.expects(:open).with('| /usr/bin/pacman -Sgg git 2>&1').returns '' Puppet::Util::Execution.expects(:exitstatus).returns 1 expect(described_class.get_installed_groups(installed_packages, 'git')).to eq({}) end it 'should return empty groups on empty pacman output' do pipe = stub() pipe.expects(:each_line) executor.expects(:open).with('| /usr/bin/pacman -Sgg 2>&1').yields(pipe).returns '' Puppet::Util::Execution.expects(:exitstatus).returns 0 expect(described_class.get_installed_groups(installed_packages)).to eq({}) end it 'should return groups on non-empty pacman output' do pipe = stub() pipe.expects(:each_line).multiple_yields(*groups) executor.expects(:open).with('| /usr/bin/pacman -Sgg 2>&1').yields(pipe).returns '' Puppet::Util::Execution.expects(:exitstatus).returns 0 expect(described_class.get_installed_groups(installed_packages)).to eq({'foo' => 'package1 1.0, package2 2.0'}) end end end puppet-5.5.10/spec/unit/provider/package/pip3_spec.rb0000644005276200011600000000077613417161721022374 0ustar jenkinsjenkinsrequire 'spec_helper' describe Puppet::Type.type(:package).provider(:pip3) do it { is_expected.to be_installable } it { is_expected.to be_uninstallable } it { is_expected.to be_upgradeable } it { is_expected.to be_versionable } it { is_expected.to be_install_options } it "should inherit most things from pip provider" do expect(described_class < Puppet::Type.type(:package).provider(:pip)) end it "should use pip3 command" do expect(described_class.cmd).to eq(["pip3"]) end end puppet-5.5.10/spec/unit/provider/package/pkgdmg_spec.rb0000644005276200011600000001515713417161721022771 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:package).provider(:pkgdmg) do let(:resource) { Puppet::Type.type(:package).new(:name => 'foo', :provider => :pkgdmg) } let(:provider) { described_class.new(resource) } it { is_expected.not_to be_versionable } it { is_expected.not_to be_uninstallable } describe "when installing it should fail when" do before :each do Puppet::Util.expects(:execute).never end it "no source is specified" do expect { provider.install }.to raise_error(Puppet::Error, /must specify a package source/) end it "the source does not end in .dmg or .pkg" do resource[:source] = "bar" expect { provider.install }.to raise_error(Puppet::Error, /must specify a source string ending in .*dmg.*pkg/) end end # These tests shouldn't be this messy. The pkgdmg provider needs work... describe "when installing a pkgdmg" do let(:fake_mountpoint) { "/tmp/dmg.foo" } let(:fake_hdiutil_plist) { {"system-entities" => [{"mount-point" => fake_mountpoint}]} } before do fh = mock 'filehandle' fh.stubs(:path).yields "/tmp/foo" resource[:source] = "foo.dmg" File.stubs(:open).yields fh Dir.stubs(:mktmpdir).returns "/tmp/testtmp123" FileUtils.stubs(:remove_entry_secure) end it "should fail when a disk image with no system entities is mounted" do described_class.stubs(:hdiutil).returns 'empty plist' Puppet::Util::Plist.expects(:parse_plist).with('empty plist').returns({}) expect { provider.install }.to raise_error(Puppet::Error, /No disk entities/) end it "should call hdiutil to mount and eject the disk image" do Dir.stubs(:entries).returns [] provider.class.expects(:hdiutil).with("eject", fake_mountpoint).returns 0 provider.class.expects(:hdiutil).with("mount", "-plist", "-nobrowse", "-readonly", "-noidme", "-mountrandom", "/tmp", nil).returns 'a plist' Puppet::Util::Plist.expects(:parse_plist).with('a plist').returns fake_hdiutil_plist provider.install end it "should call installpkg if a pkg/mpkg is found on the dmg" do Dir.stubs(:entries).returns ["foo.pkg"] provider.class.stubs(:hdiutil).returns 'a plist' Puppet::Util::Plist.expects(:parse_plist).with('a plist').returns fake_hdiutil_plist provider.class.expects(:installpkg).with("#{fake_mountpoint}/foo.pkg", resource[:name], "foo.dmg").returns "" provider.install end describe "from a remote source" do let(:tmpdir) { "/tmp/good123" } before :each do resource[:source] = "http://fake.puppetlabs.com/foo.dmg" end it "should call tmpdir and then call curl with that directory" do Dir.expects(:mktmpdir).returns tmpdir Dir.stubs(:entries).returns ["foo.pkg"] described_class.expects(:curl).with do |*args| args[0] == "-o" && args[1].include?(tmpdir) && args.include?("--fail") && ! args.include?("-k") end described_class.stubs(:hdiutil).returns 'a plist' Puppet::Util::Plist.expects(:parse_plist).with('a plist').returns fake_hdiutil_plist described_class.expects(:installpkg) provider.install end it "should use an http proxy host and port if specified" do Puppet::Util::HttpProxy.expects(:no_proxy?).returns false Puppet::Util::HttpProxy.expects(:http_proxy_host).returns 'some_host' Puppet::Util::HttpProxy.expects(:http_proxy_port).returns 'some_port' Dir.expects(:mktmpdir).returns tmpdir Dir.stubs(:entries).returns ["foo.pkg"] described_class.expects(:curl).with do |*args| expect(args).to be_include 'some_host:some_port' expect(args).to be_include '--proxy' end described_class.stubs(:hdiutil).returns 'a plist' Puppet::Util::Plist.expects(:parse_plist).with('a plist').returns fake_hdiutil_plist described_class.expects(:installpkg) provider.install end it "should use an http proxy host only if specified" do Puppet::Util::HttpProxy.expects(:no_proxy?).returns false Puppet::Util::HttpProxy.expects(:http_proxy_host).returns 'some_host' Puppet::Util::HttpProxy.expects(:http_proxy_port).returns nil Dir.expects(:mktmpdir).returns tmpdir Dir.stubs(:entries).returns ["foo.pkg"] described_class.expects(:curl).with do |*args| expect(args).to be_include 'some_host' expect(args).to be_include '--proxy' end described_class.stubs(:hdiutil).returns 'a plist' Puppet::Util::Plist.expects(:parse_plist).with('a plist').returns fake_hdiutil_plist described_class.expects(:installpkg) provider.install end it "should not use the configured proxy if no_proxy contains a match for the destination" do Puppet::Util::HttpProxy.expects(:no_proxy?).returns true Puppet::Util::HttpProxy.expects(:http_proxy_host).never Puppet::Util::HttpProxy.expects(:http_proxy_port).never Dir.expects(:mktmpdir).returns tmpdir Dir.stubs(:entries).returns ["foo.pkg"] described_class.expects(:curl).with do |*args| expect(args).not_to be_include 'some_host:some_port' expect(args).not_to be_include '--proxy' true end described_class.stubs(:hdiutil).returns 'a plist' Puppet::Util::Plist.expects(:parse_plist).with('a plist').returns fake_hdiutil_plist described_class.expects(:installpkg) provider.install end end end describe "when installing flat pkg file" do describe "with a local source" do it "should call installpkg if a flat pkg file is found instead of a .dmg image" do resource[:source] = "/tmp/test.pkg" resource[:name] = "testpkg" provider.class.expects(:installpkgdmg).with("/tmp/test.pkg", "testpkg").returns "" provider.install end end describe "with a remote source" do let(:remote_source) { 'http://fake.puppetlabs.com/test.pkg' } let(:tmpdir) { '/path/to/tmpdir' } let(:tmpfile) { File.join(tmpdir, 'testpkg.pkg') } before do resource[:name] = 'testpkg' resource[:source] = remote_source Dir.stubs(:mktmpdir).returns tmpdir end it "should call installpkg if a flat pkg file is found instead of a .dmg image" do described_class.expects(:curl).with do |*args| expect(args).to be_include tmpfile expect(args).to be_include remote_source end provider.class.expects(:installpkg).with(tmpfile, 'testpkg', remote_source) provider.install end end end end puppet-5.5.10/spec/unit/provider/package/pkgin_spec.rb0000644005276200011600000001535413417161721022627 0ustar jenkinsjenkinsrequire "spec_helper" describe Puppet::Type.type(:package).provider(:pkgin) do let(:resource) { Puppet::Type.type(:package).new(:name => "vim", :provider => :pkgin) } subject { resource.provider } describe "Puppet provider interface" do it "can return the list of all packages" do expect(described_class).to respond_to(:instances) end end describe "#install" do describe "a package not installed" do before { resource[:ensure] = :absent } it "uses pkgin install to install" do subject.expects(:pkgin).with("-y", :install, "vim").once() subject.install end end describe "a package with a fixed version" do before { resource[:ensure] = '7.2.446' } it "uses pkgin install to install a fixed version" do subject.expects(:pkgin).with("-y", :install, "vim-7.2.446").once() subject.install end end end describe "#uninstall" do it "uses pkgin remove to uninstall" do subject.expects(:pkgin).with("-y", :remove, "vim").once() subject.uninstall end end describe "#instances" do let(:pkgin_ls_output) do "zlib-1.2.3;General purpose data compression library\nzziplib-0.13.59;Library for ZIP archive handling\n" end before do described_class.stubs(:pkgin).with(:list).returns(pkgin_ls_output) end it "returns an array of providers for each package" do instances = described_class.instances expect(instances).to have(2).items instances.each do |instance| expect(instance).to be_a(described_class) end end it "populates each provider with an installed package" do zlib_provider, zziplib_provider = described_class.instances expect(zlib_provider.get(:name)).to eq("zlib") expect(zlib_provider.get(:ensure)).to eq("1.2.3") expect(zziplib_provider.get(:name)).to eq("zziplib") expect(zziplib_provider.get(:ensure)).to eq("0.13.59") end end describe "#latest" do before do described_class.stubs(:pkgin).with(:search, "vim").returns(pkgin_search_output) end context "when the package is installed" do let(:pkgin_search_output) do "vim-7.2.446;=;Vim editor (vi clone) without GUI\nvim-share-7.2.446;=;Data files for the vim editor (vi clone)\n\n=: package is installed and up-to-date\n<: package is installed but newer version is available\n>: installed package has a greater version than available package\n" end it "returns installed version" do subject.expects(:properties).returns( { :ensure => "7.2.446" } ) expect(subject.latest).to eq("7.2.446") end end context "when the package is out of date" do let(:pkgin_search_output) do "vim-7.2.447;<;Vim editor (vi clone) without GUI\nvim-share-7.2.447;<;Data files for the vim editor (vi clone)\n\n=: package is installed and up-to-date\n<: package is installed but newer version is available\n>: installed package has a greater version than available package\n" end it "returns the version to be installed" do expect(subject.latest).to eq("7.2.447") end end context "when the package is ahead of date" do let(:pkgin_search_output) do "vim-7.2.446;>;Vim editor (vi clone) without GUI\nvim-share-7.2.446;>;Data files for the vim editor (vi clone)\n\n=: package is installed and up-to-date\n<: package is installed but newer version is available\n>: installed package has a greater version than available package\n" end it "returns current version" do subject.expects(:properties).returns( { :ensure => "7.2.446" } ) expect(subject.latest).to eq("7.2.446") end end context "when multiple candidates do exists" do let(:pkgin_search_output) do <<-SEARCH vim-7.1;>;Vim editor (vi clone) without GUI vim-share-7.1;>;Data files for the vim editor (vi clone) vim-7.2.446;=;Vim editor (vi clone) without GUI vim-share-7.2.446;=;Data files for the vim editor (vi clone) vim-7.3;<;Vim editor (vi clone) without GUI vim-share-7.3;<;Data files for the vim editor (vi clone) =: package is installed and up-to-date <: package is installed but newer version is available >: installed package has a greater version than available package SEARCH end it "returns the newest available version" do described_class.stubs(:pkgin).with(:search, "vim").returns(pkgin_search_output) expect(subject.latest).to eq("7.3") end end context "when the package cannot be found" do let(:pkgin_search_output) do "No results found for is-puppet" end it "returns nil" do expect { subject.latest }.to raise_error(Puppet::Error, "No candidate to be installed") end end end describe "#parse_pkgin_line" do context "with an installed package" do let(:package) { "vim-7.2.446;=;Vim editor (vi clone) without GUI" } it "extracts the name and status" do expect(described_class.parse_pkgin_line(package)).to eq({ :name => "vim" , :status => "=" , :ensure => "7.2.446" }) end end context "with an installed package with a hyphen in the name" do let(:package) { "ruby18-puppet-0.25.5nb1;>;Configuration management framework written in Ruby" } it "extracts the name and status" do expect(described_class.parse_pkgin_line(package)).to eq({ :name => "ruby18-puppet", :status => ">" , :ensure => "0.25.5nb1" }) end end context "with an installed package with a hyphen in the name and package description" do let(:package) { "ruby200-facter-2.4.3nb1;=;Cross-platform Ruby library for retrieving facts from OS" } it "extracts the name and status" do expect(described_class.parse_pkgin_line(package)).to eq({ :name => "ruby200-facter", :status => "=" , :ensure => "2.4.3nb1" }) end end context "with a package not yet installed" do let(:package) { "vim-7.2.446;Vim editor (vi clone) without GUI" } it "extracts the name and status" do expect(described_class.parse_pkgin_line(package)).to eq({ :name => "vim" , :status => nil , :ensure => "7.2.446" }) end end context "with an invalid package" do let(:package) { "" } it "returns nil" do expect(described_class.parse_pkgin_line(package)).to be_nil end end end end puppet-5.5.10/spec/unit/provider/package/pkgutil_spec.rb0000644005276200011600000002177213417161721023177 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' provider = Puppet::Type.type(:package).provider(:pkgutil) describe provider do before(:each) do @resource = Puppet::Type.type(:package).new( :name => "TESTpkg", :ensure => :present, :provider => :pkgutil ) @provider = provider.new(@resource) # Stub all file and config tests provider.stubs(:healthcheck) end it "should have an install method" do expect(@provider).to respond_to(:install) end it "should have a latest method" do expect(@provider).to respond_to(:uninstall) end it "should have an update method" do expect(@provider).to respond_to(:update) end it "should have a latest method" do expect(@provider).to respond_to(:latest) end describe "when installing" do it "should use a command without versioned package" do @resource[:ensure] = :latest @provider.expects(:pkguti).with('-y', '-i', 'TESTpkg') @provider.install end it "should support a single temp repo URL" do @resource[:ensure] = :latest @resource[:source] = "http://example.net/repo" @provider.expects(:pkguti).with('-t', 'http://example.net/repo', '-y', '-i', 'TESTpkg') @provider.install end it "should support multiple temp repo URLs as array" do @resource[:ensure] = :latest @resource[:source] = [ 'http://example.net/repo', 'http://example.net/foo' ] @provider.expects(:pkguti).with('-t', 'http://example.net/repo', '-t', 'http://example.net/foo', '-y', '-i', 'TESTpkg') @provider.install end end describe "when updating" do it "should use a command without versioned package" do @provider.expects(:pkguti).with('-y', '-u', 'TESTpkg') @provider.update end it "should support a single temp repo URL" do @resource[:source] = "http://example.net/repo" @provider.expects(:pkguti).with('-t', 'http://example.net/repo', '-y', '-u', 'TESTpkg') @provider.update end it "should support multiple temp repo URLs as array" do @resource[:source] = [ 'http://example.net/repo', 'http://example.net/foo' ] @provider.expects(:pkguti).with('-t', 'http://example.net/repo', '-t', 'http://example.net/foo', '-y', '-u', 'TESTpkg') @provider.update end end describe "when uninstalling" do it "should call the remove operation" do @provider.expects(:pkguti).with('-y', '-r', 'TESTpkg') @provider.uninstall end it "should support a single temp repo URL" do @resource[:source] = "http://example.net/repo" @provider.expects(:pkguti).with('-t', 'http://example.net/repo', '-y', '-r', 'TESTpkg') @provider.uninstall end it "should support multiple temp repo URLs as array" do @resource[:source] = [ 'http://example.net/repo', 'http://example.net/foo' ] @provider.expects(:pkguti).with('-t', 'http://example.net/repo', '-t', 'http://example.net/foo', '-y', '-r', 'TESTpkg') @provider.uninstall end end describe "when getting latest version" do it "should return TESTpkg's version string" do fake_data = " noisy output here TESTpkg 1.4.5,REV=2007.11.18 1.4.5,REV=2007.11.20" provider.expects(:pkguti).with('-c', '--single', 'TESTpkg').returns fake_data expect(@provider.latest).to eq("1.4.5,REV=2007.11.20") end it "should support a temp repo URL" do @resource[:source] = "http://example.net/repo" fake_data = " noisy output here TESTpkg 1.4.5,REV=2007.11.18 1.4.5,REV=2007.11.20" provider.expects(:pkguti).with('-t', 'http://example.net/repo', '-c', '--single', 'TESTpkg').returns fake_data expect(@provider.latest).to eq("1.4.5,REV=2007.11.20") end it "should handle TESTpkg's 'SAME' version string" do fake_data = " noisy output here TESTpkg 1.4.5,REV=2007.11.18 SAME" provider.expects(:pkguti).with('-c', '--single', 'TESTpkg').returns fake_data expect(@provider.latest).to eq("1.4.5,REV=2007.11.18") end it "should handle a non-existent package" do fake_data = "noisy output here Not in catalog" provider.expects(:pkguti).with('-c', '--single', 'TESTpkg').returns fake_data expect(@provider.latest).to eq(nil) end it "should warn on unknown pkgutil noise" do provider.expects(:pkguti).with('-c', '--single', 'TESTpkg').returns("testingnoise") expect(@provider.latest).to eq(nil) end it "should ignore pkgutil noise/headers to find TESTpkg" do fake_data = "# stuff => Fetching new catalog and descriptions (http://mirror.opencsw.org/opencsw/unstable/i386/5.11) if available ... 2011-02-19 23:05:46 URL:http://mirror.opencsw.org/opencsw/unstable/i386/5.11/catalog [534635/534635] -> \"/var/opt/csw/pkgutil/catalog.mirror.opencsw.org_opencsw_unstable_i386_5.11.tmp\" [1] Checking integrity of /var/opt/csw/pkgutil/catalog.mirror.opencsw.org_opencsw_unstable_i386_5.11 with gpg. gpg: Signature made February 17, 2011 05:27:53 PM GMT using DSA key ID E12E9D2F gpg: Good signature from \"Distribution Manager \" ==> 2770 packages loaded from /var/opt/csw/pkgutil/catalog.mirror.opencsw.org_opencsw_unstable_i386_5.11 package installed catalog TESTpkg 1.4.5,REV=2007.11.18 1.4.5,REV=2007.11.20" provider.expects(:pkguti).with('-c', '--single', 'TESTpkg').returns fake_data expect(@provider.latest).to eq("1.4.5,REV=2007.11.20") end it "should find REALpkg via an alias (TESTpkg)" do fake_data = " noisy output here REALpkg 1.4.5,REV=2007.11.18 1.4.5,REV=2007.11.20" provider.expects(:pkguti).with('-c', '--single', 'TESTpkg').returns fake_data expect(@provider.query[:name]).to eq("TESTpkg") end end describe "when querying current version" do it "should return TESTpkg's version string" do fake_data = "TESTpkg 1.4.5,REV=2007.11.18 1.4.5,REV=2007.11.20" provider.expects(:pkguti).with('-c', '--single', 'TESTpkg').returns fake_data expect(@provider.query[:ensure]).to eq("1.4.5,REV=2007.11.18") end it "should handle a package that isn't installed" do fake_data = "TESTpkg notinst 1.4.5,REV=2007.11.20" provider.expects(:pkguti).with('-c', '--single', 'TESTpkg').returns fake_data expect(@provider.query[:ensure]).to eq(:absent) end it "should handle a non-existent package" do fake_data = "noisy output here Not in catalog" provider.expects(:pkguti).with('-c', '--single', 'TESTpkg').returns fake_data expect(@provider.query[:ensure]).to eq(:absent) end it "should support a temp repo URL" do @resource[:source] = "http://example.net/repo" fake_data = "TESTpkg 1.4.5,REV=2007.11.18 1.4.5,REV=2007.11.20" provider.expects(:pkguti).with('-t', 'http://example.net/repo', '-c', '--single', 'TESTpkg').returns fake_data expect(@provider.query[:ensure]).to eq("1.4.5,REV=2007.11.18") end end describe "when querying current instances" do it "should warn on unknown pkgutil noise" do provider.expects(:pkguti).with(['-a']).returns("testingnoise") provider.expects(:pkguti).with(['-c']).returns("testingnoise") Puppet.expects(:warning).times(2) provider.expects(:new).never expect(provider.instances).to eq([]) end it "should return TESTpkg's version string" do fake_data = "TESTpkg TESTpkg 1.4.5,REV=2007.11.20" provider.expects(:pkguti).with(['-a']).returns fake_data fake_data = "TESTpkg 1.4.5,REV=2007.11.18 1.4.5,REV=2007.11.20" provider.expects(:pkguti).with(['-c']).returns fake_data testpkg = mock 'pkg1' provider.expects(:new).with(:ensure => "1.4.5,REV=2007.11.18", :name => "TESTpkg", :provider => :pkgutil).returns testpkg expect(provider.instances).to eq([testpkg]) end it "should also return both TESTpkg and mypkg alias instances" do fake_data = "mypkg TESTpkg 1.4.5,REV=2007.11.20" provider.expects(:pkguti).with(['-a']).returns fake_data fake_data = "TESTpkg 1.4.5,REV=2007.11.18 1.4.5,REV=2007.11.20" provider.expects(:pkguti).with(['-c']).returns fake_data testpkg = mock 'pkg1' provider.expects(:new).with(:ensure => "1.4.5,REV=2007.11.18", :name => "TESTpkg", :provider => :pkgutil).returns testpkg aliaspkg = mock 'pkg2' provider.expects(:new).with(:ensure => "1.4.5,REV=2007.11.18", :name => "mypkg", :provider => :pkgutil).returns aliaspkg expect(provider.instances).to eq([testpkg,aliaspkg]) end it "shouldn't mind noise in the -a output" do fake_data = "noisy output here" provider.expects(:pkguti).with(['-a']).returns fake_data fake_data = "TESTpkg 1.4.5,REV=2007.11.18 1.4.5,REV=2007.11.20" provider.expects(:pkguti).with(['-c']).returns fake_data testpkg = mock 'pkg1' provider.expects(:new).with(:ensure => "1.4.5,REV=2007.11.18", :name => "TESTpkg", :provider => :pkgutil).returns testpkg expect(provider.instances).to eq([testpkg]) end end end puppet-5.5.10/spec/unit/provider/package/rpm_spec.rb0000644005276200011600000006164613417161721022322 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' provider_class = Puppet::Type.type(:package).provider(:rpm) describe provider_class do let (:packages) do <<-RPM_OUTPUT 'cracklib-dicts 0 2.8.9 3.3 x86_64 basesystem 0 8.0 5.1.1.el5.centos noarch chkconfig 0 1.3.30.2 2.el5 x86_64 myresource 0 1.2.3.4 5.el4 noarch mysummaryless 0 1.2.3.4 5.el4 noarch tomcat 1 1.2.3.4 5.el4 x86_64 ' RPM_OUTPUT end let(:resource_name) { 'myresource' } let(:resource) do Puppet::Type.type(:package).new( :name => resource_name, :ensure => :installed, :provider => 'rpm' ) end let(:provider) do provider = provider_class.new provider.resource = resource provider end let(:nevra_format) { %Q{%{NAME} %|EPOCH?{%{EPOCH}}:{0}| %{VERSION} %{RELEASE} %{ARCH}\\n} } let(:execute_options) do {:failonfail => true, :combine => true, :custom_environment => {}} end let(:rpm_version) { "RPM version 5.0.0\n" } before(:each) do Puppet::Util.stubs(:which).with("rpm").returns("/bin/rpm") provider_class.stubs(:which).with("rpm").returns("/bin/rpm") provider_class.instance_variable_set("@current_version", nil) Puppet::Type::Package::ProviderRpm.expects(:execute) .with(["/bin/rpm", "--version"]) .returns(rpm_version).at_most_once Puppet::Util::Execution.expects(:execute) .with(["/bin/rpm", "--version"], execute_options) .returns(Puppet::Util::Execution::ProcessOutput.new(rpm_version, 0)).at_most_once end describe 'provider features' do it { is_expected.to be_versionable } it { is_expected.to be_install_options } it { is_expected.to be_uninstall_options } it { is_expected.to be_virtual_packages } end describe "self.instances" do describe "with a modern version of RPM" do it "includes all the modern flags" do Puppet::Util::Execution.expects(:execpipe).with("/bin/rpm -qa --nosignature --nodigest --qf '#{nevra_format}'").yields(packages) provider_class.instances end end describe "with a version of RPM < 4.1" do let(:rpm_version) { "RPM version 4.0.2\n" } it "excludes the --nosignature flag" do Puppet::Util::Execution.expects(:execpipe).with("/bin/rpm -qa --nodigest --qf '#{nevra_format}'").yields(packages) provider_class.instances end end describe "with a version of RPM < 4.0.2" do let(:rpm_version) { "RPM version 3.0.5\n" } it "excludes the --nodigest flag" do Puppet::Util::Execution.expects(:execpipe).with("/bin/rpm -qa --qf '#{nevra_format}'").yields(packages) provider_class.instances end end it "returns an array of packages" do Puppet::Util::Execution.expects(:execpipe).with("/bin/rpm -qa --nosignature --nodigest --qf '#{nevra_format}'").yields(packages) installed_packages = provider_class.instances expect(installed_packages[0].properties).to eq( { :provider => :rpm, :name => "cracklib-dicts", :epoch => "0", :version => "2.8.9", :release => "3.3", :arch => "x86_64", :ensure => "2.8.9-3.3", } ) expect(installed_packages[1].properties).to eq( { :provider => :rpm, :name => "basesystem", :epoch => "0", :version => "8.0", :release => "5.1.1.el5.centos", :arch => "noarch", :ensure => "8.0-5.1.1.el5.centos", } ) expect(installed_packages[2].properties).to eq( { :provider => :rpm, :name => "chkconfig", :epoch => "0", :version => "1.3.30.2", :release => "2.el5", :arch => "x86_64", :ensure => "1.3.30.2-2.el5", } ) expect(installed_packages[3].properties).to eq( { :provider => :rpm, :name => "myresource", :epoch => "0", :version => "1.2.3.4", :release => "5.el4", :arch => "noarch", :ensure => "1.2.3.4-5.el4", } ) expect(installed_packages[4].properties).to eq( { :provider => :rpm, :name => "mysummaryless", :epoch => "0", :version => "1.2.3.4", :release => "5.el4", :arch => "noarch", :ensure => "1.2.3.4-5.el4", } ) expect(installed_packages[5].properties).to eq( { :provider => :rpm, :name => "tomcat", :epoch => "1", :version => "1.2.3.4", :release => "5.el4", :arch => "x86_64", :ensure => "1:1.2.3.4-5.el4", } ) end end describe "#install" do let(:resource) do Puppet::Type.type(:package).new( :name => 'myresource', :ensure => :installed, :source => '/path/to/package' ) end describe "when not already installed" do it "only includes the '-i' flag" do Puppet::Util::Execution.expects(:execute).with(["/bin/rpm", ["-i"], '/path/to/package'], execute_options) provider.install end end describe "when installed with options" do let(:resource) do Puppet::Type.type(:package).new( :name => resource_name, :ensure => :installed, :provider => 'rpm', :source => '/path/to/package', :install_options => ['-D', {'--test' => 'value'}, '-Q'] ) end it "includes the options" do Puppet::Util::Execution.expects(:execute).with(["/bin/rpm", ["-i", "-D", "--test=value", "-Q"], '/path/to/package'], execute_options) provider.install end end describe "when an older version is installed" do before(:each) do # Force the provider to think a version of the package is already installed # This is real hacky. I'm sorry. --jeffweiss 25 Jan 2013 provider.instance_variable_get('@property_hash')[:ensure] = '1.2.3.3' end it "includes the '-U --oldpackage' flags" do Puppet::Util::Execution.expects(:execute).with(["/bin/rpm", ["-U", "--oldpackage"], '/path/to/package'], execute_options) provider.install end end end describe "#latest" do it "retrieves version string after querying rpm for version from source file" do resource.expects(:[]).with(:source).returns('source-string') Puppet::Util::Execution.expects(:execute) .with(["/bin/rpm", "-q", "--qf", "'#{nevra_format}'", "-p", "source-string"]) .returns(Puppet::Util::Execution::ProcessOutput.new("myresource 0 1.2.3.4 5.el4 noarch\n", 0)) expect(provider.latest).to eq("1.2.3.4-5.el4") end it "raises an error if the rpm command fails" do resource.expects(:[]).with(:source).returns('source-string') Puppet::Util::Execution.expects(:execute).with(["/bin/rpm", "-q", "--qf", "'#{nevra_format}'", "-p", "source-string"]).raises(Puppet::ExecutionFailure, 'rpm command failed') expect { provider.latest }.to raise_error(Puppet::Error, 'rpm command failed') end end describe "#uninstall" do let(:resource) do Puppet::Type.type(:package).new( :name => 'myresource', :ensure => :installed ) end describe "on a modern RPM" do before(:each) do Puppet::Util::Execution.expects(:execute) .with(["/bin/rpm", "-q", "myresource", '--nosignature', '--nodigest', "--qf", "'#{nevra_format}'"], execute_options) .returns(Puppet::Util::Execution::ProcessOutput.new("myresource 0 1.2.3.4 5.el4 noarch\n", 0)) end let(:rpm_version) { "RPM version 4.10.0\n" } it "includes the architecture in the package name" do Puppet::Util::Execution.expects(:execute) .with(["/bin/rpm", ["-e"], 'myresource-1.2.3.4-5.el4.noarch'], execute_options) .returns(Puppet::Util::Execution::ProcessOutput.new('', 0)).at_most_once provider.uninstall end end describe "on an ancient RPM" do before(:each) do Puppet::Util::Execution.expects(:execute) .with(["/bin/rpm", "-q", "myresource", '', '', '--qf', "'#{nevra_format}'"], execute_options) .returns(Puppet::Util::Execution::ProcessOutput.new("myresource 0 1.2.3.4 5.el4 noarch\n", 0)) end let(:rpm_version) { "RPM version 3.0.6\n" } it "excludes the architecture from the package name" do Puppet::Util::Execution.expects(:execute) .with(["/bin/rpm", ["-e"], 'myresource-1.2.3.4-5.el4'], execute_options) .returns(Puppet::Util::Execution::ProcessOutput.new('', 0)).at_most_once provider.uninstall end end describe "when uninstalled with options" do before(:each) do Puppet::Util::Execution.expects(:execute) .with(["/bin/rpm", "-q", "myresource", '--nosignature', '--nodigest', "--qf", "'#{nevra_format}'"], execute_options) .returns(Puppet::Util::Execution::ProcessOutput.new("myresource 0 1.2.3.4 5.el4 noarch\n", 0)) end let(:resource) do Puppet::Type.type(:package).new( :name => resource_name, :ensure => :absent, :provider => 'rpm', :uninstall_options => ['--nodeps'] ) end it "includes the options" do Puppet::Util::Execution.expects(:execute).with(["/bin/rpm", ["-e", "--nodeps"], 'myresource-1.2.3.4-5.el4.noarch'], execute_options) provider.uninstall end end end describe "parsing" do def parser_test(rpm_output_string, gold_hash, number_of_debug_logs = 0) Puppet.expects(:debug).times(number_of_debug_logs) Puppet::Util::Execution.expects(:execute) .with(["/bin/rpm", "-q", resource_name, "--nosignature", "--nodigest", "--qf", "'#{nevra_format}'"], execute_options) .returns(Puppet::Util::Execution::ProcessOutput.new(rpm_output_string, 0)) expect(provider.query).to eq(gold_hash) end let(:resource_name) { 'name' } let('delimiter') { ':DESC:' } let(:package_hash) do { :name => 'name', :epoch => 'epoch', :version => 'version', :release => 'release', :arch => 'arch', :provider => :rpm, :ensure => 'epoch:version-release', } end let(:line) { 'name epoch version release arch' } ['name', 'epoch', 'version', 'release', 'arch'].each do |field| it "still parses if #{field} is replaced by delimiter" do parser_test( line.gsub(field, delimiter), package_hash.merge( field.to_sym => delimiter, :ensure => 'epoch:version-release'.gsub(field, delimiter) ) ) end end it "does not fail if line is unparseable, but issues a debug log" do parser_test('bad data', {}, 1) end describe "when the package is not found" do before do Puppet.expects(:debug).never expected_args = ["/bin/rpm", "-q", resource_name, "--nosignature", "--nodigest", "--qf", "'#{nevra_format}'"] Puppet::Util::Execution.expects(:execute).with(expected_args, execute_options).raises Puppet::ExecutionFailure.new("package #{resource_name} is not installed") end it "does not log or fail if allow_virtual is false" do resource[:allow_virtual] = false expect(provider.query).to be_nil end it "does not log or fail if allow_virtual is true" do resource[:allow_virtual] = true expected_args = ['/bin/rpm', '-q', resource_name, '--nosignature', '--nodigest', '--qf', "'#{nevra_format}'", '--whatprovides'] Puppet::Util::Execution.expects(:execute).with(expected_args, execute_options).raises Puppet::ExecutionFailure.new("package #{resource_name} is not provided") expect(provider.query).to be_nil end end it "parses virtual package" do provider.resource[:allow_virtual] = true expected_args = ["/bin/rpm", "-q", resource_name, "--nosignature", "--nodigest", "--qf", "'#{nevra_format}'"] Puppet::Util::Execution.expects(:execute).with(expected_args, execute_options).raises Puppet::ExecutionFailure.new("package #{resource_name} is not installed") Puppet::Util::Execution.expects(:execute) .with(expected_args + ["--whatprovides"], execute_options) .returns(Puppet::Util::Execution::ProcessOutput.new("myresource 0 1.2.3.4 5.el4 noarch\n", 0)) expect(provider.query).to eq({ :name => "myresource", :epoch => "0", :version => "1.2.3.4", :release => "5.el4", :arch => "noarch", :provider => :rpm, :ensure => "1.2.3.4-5.el4" }) end end describe "#install_options" do it "returns nil by default" do expect(provider.install_options).to eq(nil) end it "returns install_options when set" do provider.resource[:install_options] = ['-n'] expect(provider.install_options).to eq(['-n']) end it "returns multiple install_options when set" do provider.resource[:install_options] = ['-L', '/opt/puppet'] expect(provider.install_options).to eq(['-L', '/opt/puppet']) end it 'returns install_options when set as hash' do provider.resource[:install_options] = [{ '-Darch' => 'vax' }] expect(provider.install_options).to eq(['-Darch=vax']) end it 'returns install_options when an array with hashes' do provider.resource[:install_options] = [ '-L', { '-Darch' => 'vax' }] expect(provider.install_options).to eq(['-L', '-Darch=vax']) end end describe "#uninstall_options" do it "returns nil by default" do expect(provider.uninstall_options).to eq(nil) end it "returns uninstall_options when set" do provider.resource[:uninstall_options] = ['-n'] expect(provider.uninstall_options).to eq(['-n']) end it "returns multiple uninstall_options when set" do provider.resource[:uninstall_options] = ['-L', '/opt/puppet'] expect(provider.uninstall_options).to eq(['-L', '/opt/puppet']) end it 'returns uninstall_options when set as hash' do provider.resource[:uninstall_options] = [{ '-Darch' => 'vax' }] expect(provider.uninstall_options).to eq(['-Darch=vax']) end it 'returns uninstall_options when an array with hashes' do provider.resource[:uninstall_options] = [ '-L', { '-Darch' => 'vax' }] expect(provider.uninstall_options).to eq(['-L', '-Darch=vax']) end end describe ".nodigest" do { '4.0' => nil, '4.0.1' => nil, '4.0.2' => '--nodigest', '4.0.3' => '--nodigest', '4.1' => '--nodigest', '5' => '--nodigest', }.each do |version, expected| describe "when current version is #{version}" do it "returns #{expected.inspect}" do provider_class.stubs(:current_version).returns(version) expect(provider_class.nodigest).to eq(expected) end end end end describe ".nosignature" do { '4.0.3' => nil, '4.1' => '--nosignature', '4.1.1' => '--nosignature', '4.2' => '--nosignature', '5' => '--nosignature', }.each do |version, expected| describe "when current version is #{version}" do it "returns #{expected.inspect}" do provider_class.stubs(:current_version).returns(version) expect(provider_class.nosignature).to eq(expected) end end end end describe 'version comparison' do # test cases munged directly from rpm's own # tests/rpmvercmp.at it { expect(provider.rpmvercmp("1.0", "1.0")).to eq(0) } it { expect(provider.rpmvercmp("1.0", "2.0")).to eq(-1) } it { expect(provider.rpmvercmp("2.0", "1.0")).to eq(1) } it { expect(provider.rpmvercmp("2.0.1", "2.0.1")).to eq(0) } it { expect(provider.rpmvercmp("2.0", "2.0.1")).to eq(-1) } it { expect(provider.rpmvercmp("2.0.1", "2.0")).to eq(1) } it { expect(provider.rpmvercmp("2.0.1a", "2.0.1a")).to eq(0) } it { expect(provider.rpmvercmp("2.0.1a", "2.0.1")).to eq(1) } it { expect(provider.rpmvercmp("2.0.1", "2.0.1a")).to eq(-1) } it { expect(provider.rpmvercmp("5.5p1", "5.5p1")).to eq(0) } it { expect(provider.rpmvercmp("5.5p1", "5.5p2")).to eq(-1) } it { expect(provider.rpmvercmp("5.5p2", "5.5p1")).to eq(1) } it { expect(provider.rpmvercmp("5.5p10", "5.5p10")).to eq(0) } it { expect(provider.rpmvercmp("5.5p1", "5.5p10")).to eq(-1) } it { expect(provider.rpmvercmp("5.5p10", "5.5p1")).to eq(1) } it { expect(provider.rpmvercmp("10xyz", "10.1xyz")).to eq(-1) } it { expect(provider.rpmvercmp("10.1xyz", "10xyz")).to eq(1) } it { expect(provider.rpmvercmp("xyz10", "xyz10")).to eq(0) } it { expect(provider.rpmvercmp("xyz10", "xyz10.1")).to eq(-1) } it { expect(provider.rpmvercmp("xyz10.1", "xyz10")).to eq(1) } it { expect(provider.rpmvercmp("xyz.4", "xyz.4")).to eq(0) } it { expect(provider.rpmvercmp("xyz.4", "8")).to eq(-1) } it { expect(provider.rpmvercmp("8", "xyz.4")).to eq(1) } it { expect(provider.rpmvercmp("xyz.4", "2")).to eq(-1) } it { expect(provider.rpmvercmp("2", "xyz.4")).to eq(1) } it { expect(provider.rpmvercmp("5.5p2", "5.6p1")).to eq(-1) } it { expect(provider.rpmvercmp("5.6p1", "5.5p2")).to eq(1) } it { expect(provider.rpmvercmp("5.6p1", "6.5p1")).to eq(-1) } it { expect(provider.rpmvercmp("6.5p1", "5.6p1")).to eq(1) } it { expect(provider.rpmvercmp("6.0.rc1", "6.0")).to eq(1) } it { expect(provider.rpmvercmp("6.0", "6.0.rc1")).to eq(-1) } it { expect(provider.rpmvercmp("10b2", "10a1")).to eq(1) } it { expect(provider.rpmvercmp("10a2", "10b2")).to eq(-1) } it { expect(provider.rpmvercmp("1.0aa", "1.0aa")).to eq(0) } it { expect(provider.rpmvercmp("1.0a", "1.0aa")).to eq(-1) } it { expect(provider.rpmvercmp("1.0aa", "1.0a")).to eq(1) } it { expect(provider.rpmvercmp("10.0001", "10.0001")).to eq(0) } it { expect(provider.rpmvercmp("10.0001", "10.1")).to eq(0) } it { expect(provider.rpmvercmp("10.1", "10.0001")).to eq(0) } it { expect(provider.rpmvercmp("10.0001", "10.0039")).to eq(-1) } it { expect(provider.rpmvercmp("10.0039", "10.0001")).to eq(1) } it { expect(provider.rpmvercmp("4.999.9", "5.0")).to eq(-1) } it { expect(provider.rpmvercmp("5.0", "4.999.9")).to eq(1) } it { expect(provider.rpmvercmp("20101121", "20101121")).to eq(0) } it { expect(provider.rpmvercmp("20101121", "20101122")).to eq(-1) } it { expect(provider.rpmvercmp("20101122", "20101121")).to eq(1) } it { expect(provider.rpmvercmp("2_0", "2_0")).to eq(0) } it { expect(provider.rpmvercmp("2.0", "2_0")).to eq(0) } it { expect(provider.rpmvercmp("2_0", "2.0")).to eq(0) } it { expect(provider.rpmvercmp("a", "a")).to eq(0) } it { expect(provider.rpmvercmp("a+", "a+")).to eq(0) } it { expect(provider.rpmvercmp("a+", "a_")).to eq(0) } it { expect(provider.rpmvercmp("a_", "a+")).to eq(0) } it { expect(provider.rpmvercmp("+a", "+a")).to eq(0) } it { expect(provider.rpmvercmp("+a", "_a")).to eq(0) } it { expect(provider.rpmvercmp("_a", "+a")).to eq(0) } it { expect(provider.rpmvercmp("+_", "+_")).to eq(0) } it { expect(provider.rpmvercmp("_+", "+_")).to eq(0) } it { expect(provider.rpmvercmp("_+", "_+")).to eq(0) } it { expect(provider.rpmvercmp("+", "_")).to eq(0) } it { expect(provider.rpmvercmp("_", "+")).to eq(0) } it { expect(provider.rpmvercmp("1.0~rc1", "1.0~rc1")).to eq(0) } it { expect(provider.rpmvercmp("1.0~rc1", "1.0")).to eq(-1) } it { expect(provider.rpmvercmp("1.0", "1.0~rc1")).to eq(1) } it { expect(provider.rpmvercmp("1.0~rc1", "1.0~rc2")).to eq(-1) } it { expect(provider.rpmvercmp("1.0~rc2", "1.0~rc1")).to eq(1) } it { expect(provider.rpmvercmp("1.0~rc1~git123", "1.0~rc1~git123")).to eq(0) } it { expect(provider.rpmvercmp("1.0~rc1~git123", "1.0~rc1")).to eq(-1) } it { expect(provider.rpmvercmp("1.0~rc1", "1.0~rc1~git123")).to eq(1) } it { expect(provider.rpmvercmp("1.0~rc1", "1.0arc1")).to eq(-1) } it { expect(provider.rpmvercmp("", "~")).to eq(1) } it { expect(provider.rpmvercmp("~", "~~")).to eq(1) } it { expect(provider.rpmvercmp("~", "~+~")).to eq(1) } it { expect(provider.rpmvercmp("~", "~a")).to eq(-1) } # non-upstream test cases it { expect(provider.rpmvercmp("405", "406")).to eq(-1) } it { expect(provider.rpmvercmp("1", "0")).to eq(1) } end describe 'package evr parsing' do it 'should parse full simple evr' do v = provider.rpm_parse_evr('0:1.2.3-4.el5') expect(v[:epoch]).to eq('0') expect(v[:version]).to eq('1.2.3') expect(v[:release]).to eq('4.el5') end it 'should parse version only' do v = provider.rpm_parse_evr('1.2.3') expect(v[:epoch]).to eq(nil) expect(v[:version]).to eq('1.2.3') expect(v[:release]).to eq(nil) end it 'should parse version-release' do v = provider.rpm_parse_evr('1.2.3-4.5.el6') expect(v[:epoch]).to eq(nil) expect(v[:version]).to eq('1.2.3') expect(v[:release]).to eq('4.5.el6') end it 'should parse release with git hash' do v = provider.rpm_parse_evr('1.2.3-4.1234aefd') expect(v[:epoch]).to eq(nil) expect(v[:version]).to eq('1.2.3') expect(v[:release]).to eq('4.1234aefd') end it 'should parse single integer versions' do v = provider.rpm_parse_evr('12345') expect(v[:epoch]).to eq(nil) expect(v[:version]).to eq('12345') expect(v[:release]).to eq(nil) end it 'should parse text in the epoch to 0' do v = provider.rpm_parse_evr('foo0:1.2.3-4') expect(v[:epoch]).to eq(nil) expect(v[:version]).to eq('1.2.3') expect(v[:release]).to eq('4') end it 'should parse revisions with text' do v = provider.rpm_parse_evr('1.2.3-SNAPSHOT20140107') expect(v[:epoch]).to eq(nil) expect(v[:version]).to eq('1.2.3') expect(v[:release]).to eq('SNAPSHOT20140107') end # test cases for PUP-682 it 'should parse revisions with text and numbers' do v = provider.rpm_parse_evr('2.2-SNAPSHOT20121119105647') expect(v[:epoch]).to eq(nil) expect(v[:version]).to eq('2.2') expect(v[:release]).to eq('SNAPSHOT20121119105647') end end describe 'rpm evr comparison' do # currently passing tests it 'should evaluate identical version-release as equal' do v = provider.rpm_compareEVR({:epoch => '0', :version => '1.2.3', :release => '1.el5'}, {:epoch => '0', :version => '1.2.3', :release => '1.el5'}) expect(v).to eq(0) end it 'should evaluate identical version as equal' do v = provider.rpm_compareEVR({:epoch => '0', :version => '1.2.3', :release => nil}, {:epoch => '0', :version => '1.2.3', :release => nil}) expect(v).to eq(0) end it 'should evaluate identical version but older release as less' do v = provider.rpm_compareEVR({:epoch => '0', :version => '1.2.3', :release => '1.el5'}, {:epoch => '0', :version => '1.2.3', :release => '2.el5'}) expect(v).to eq(-1) end it 'should evaluate identical version but newer release as greater' do v = provider.rpm_compareEVR({:epoch => '0', :version => '1.2.3', :release => '3.el5'}, {:epoch => '0', :version => '1.2.3', :release => '2.el5'}) expect(v).to eq(1) end it 'should evaluate a newer epoch as greater' do v = provider.rpm_compareEVR({:epoch => '1', :version => '1.2.3', :release => '4.5'}, {:epoch => '0', :version => '1.2.3', :release => '4.5'}) expect(v).to eq(1) end # these tests describe PUP-1244 logic yet to be implemented it 'should evaluate any version as equal to the same version followed by release' do v = provider.rpm_compareEVR({:epoch => '0', :version => '1.2.3', :release => nil}, {:epoch => '0', :version => '1.2.3', :release => '2.el5'}) expect(v).to eq(0) end # test cases for PUP-682 it 'should evaluate same-length numeric revisions numerically' do expect(provider.rpm_compareEVR({:epoch => '0', :version => '2.2', :release => '405'}, {:epoch => '0', :version => '2.2', :release => '406'})).to eq(-1) end end describe 'version segment comparison' do it 'should treat two nil values as equal' do v = provider.compare_values(nil, nil) expect(v).to eq(0) end it 'should treat a nil value as less than a non-nil value' do v = provider.compare_values(nil, '0') expect(v).to eq(-1) end it 'should treat a non-nil value as greater than a nil value' do v = provider.compare_values('0', nil) expect(v).to eq(1) end it 'should pass two non-nil values on to rpmvercmp' do provider.stubs(:rpmvercmp) { 0 } provider.expects(:rpmvercmp).with('s1', 's2') provider.compare_values('s1', 's2') end end end puppet-5.5.10/spec/unit/provider/package/sun_spec.rb0000644005276200011600000000751513417161721022324 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:package).provider(:sun) do let(:resource) { Puppet::Type.type(:package).new(:name => 'dummy', :ensure => :installed, :provider => :sun) } let(:provider) { resource.provider } describe 'provider features' do it { is_expected.to be_installable } it { is_expected.to be_uninstallable } it { is_expected.to be_upgradeable } it { is_expected.not_to be_versionable } end [:install, :uninstall, :latest, :query, :update].each do |method| it "should have a #{method} method" do expect(provider).to respond_to(method) end end context '#install' do it "should install a package" do resource[:ensure] = :installed resource[:source] = '/cdrom' provider.expects(:pkgadd).with(['-d', '/cdrom', '-n', 'dummy']) provider.install end it "should install a package if it is not present on update" do provider.expects(:pkginfo).with('-l', 'dummy').returns File.read(my_fixture('dummy.server')) provider.expects(:pkgrm).with(['-n', 'dummy']) provider.expects(:install) provider.update end it "should install a package on global zone if -G specified" do resource[:ensure] = :installed resource[:source] = '/cdrom' resource[:install_options] = '-G' provider.expects(:pkgadd).with(['-d', '/cdrom', '-G', '-n', 'dummy']) provider.install end end context '#uninstall' do it "should uninstall a package" do provider.expects(:pkgrm).with(['-n','dummy']) provider.uninstall end end context '#update' do it "should call uninstall if not :absent on info2hash" do provider.stubs(:info2hash).returns({:name => 'SUNWdummy', :ensure => "11.11.0,REV=2010.10.12.04.23"}) provider.expects(:uninstall) provider.expects(:install) provider.update end it "should not call uninstall if :absent on info2hash" do provider.stubs(:info2hash).returns({:name => 'SUNWdummy', :ensure => :absent}) provider.expects(:install) provider.update end end context '#query' do it "should find the package on query" do provider.expects(:pkginfo).with('-l', 'dummy').returns File.read(my_fixture('dummy.server')) expect(provider.query).to eq({ :name => 'SUNWdummy', :category=>"system", :platform=>"i386", :ensure => "11.11.0,REV=2010.10.12.04.23", :root=>"/", :description=>"Dummy server (9.6.1-P3)", :vendor => "Oracle Corporation", }) end it "shouldn't find the package on query if it is not present" do provider.expects(:pkginfo).with('-l', 'dummy').raises Puppet::ExecutionFailure, "Execution of 'pkginfo -l dummy' returned 3: ERROR: information for \"dummy\" not found." expect(provider.query).to eq({:ensure => :absent}) end it "unknown message should raise error." do provider.expects(:pkginfo).with('-l', 'dummy').returns 'RANDOM' expect { provider.query }.to raise_error Puppet::Error end end context '#instance' do it "should list instances when there are packages in the system" do described_class.expects(:pkginfo).with('-l').returns File.read(my_fixture('simple')) instances = provider.class.instances.map { |p| {:name => p.get(:name), :ensure => p.get(:ensure)} } expect(instances.size).to eq(2) expect(instances[0]).to eq({ :name => 'SUNWdummy', :ensure => "11.11.0,REV=2010.10.12.04.23", }) expect(instances[1]).to eq({ :name => 'SUNWdummyc', :ensure => "11.11.0,REV=2010.10.12.04.24", }) end it "should return empty if there were no packages" do described_class.expects(:pkginfo).with('-l').returns '' instances = provider.class.instances expect(instances.size).to eq(0) end end end puppet-5.5.10/spec/unit/provider/package/tdnf_spec.rb0000644005276200011600000000106013417161721022437 0ustar jenkinsjenkinsrequire 'spec_helper' # Note that much of the functionality of the tdnf provider is already tested with yum provider tests, # as yum is the parent provider, via dnf describe Puppet::Type.type(:package).provider(:tdnf) do it_behaves_like 'RHEL package provider', described_class, 'tdnf' context 'default' do it 'should be the default provider on PhotonOS' do Facter.stubs(:value).with(:osfamily).returns(:redhat) Facter.stubs(:value).with(:operatingsystem).returns("PhotonOS") expect(described_class).to be_default end end end puppet-5.5.10/spec/unit/provider/package/up2date_spec.rb0000644005276200011600000000124013417161721023050 0ustar jenkinsjenkins# spec/unit/provider/package/up2date_spec.rb require 'spec_helper' describe 'up2date package provider' do # This sets the class itself as the subject rather than # an instance of the class. subject do Puppet::Type.type(:package).provider(:up2date) end osfamilies = [ 'redhat' ] releases = [ '2.1', '3', '4' ] osfamilies.each do |osfamily| releases.each do |release| it "should be the default provider on #{osfamily} #{release}" do Facter.expects(:value).with(:osfamily).returns(osfamily) Facter.expects(:value).with(:lsbdistrelease).returns(release) expect(subject.default?).to be_truthy end end end end puppet-5.5.10/spec/unit/provider/package/urpmi_spec.rb0000644005276200011600000000532013417161721022643 0ustar jenkinsjenkinsrequire 'spec_helper' describe Puppet::Type.type(:package).provider(:urpmi) do before do Puppet::Util::Execution.expects(:execute).never %w[rpm urpmi urpme urpmq].each do |executable| Puppet::Util.stubs(:which).with(executable).returns(executable) end Puppet::Util::Execution.stubs(:execute) .with(['rpm', '--version'], anything) .returns(Puppet::Util::Execution::ProcessOutput.new('RPM version 4.9.1.3', 0)) end let(:resource) do Puppet::Type.type(:package).new(:name => 'foopkg', :provider => :urpmi) end before do subject.resource = resource Puppet::Type.type(:package).stubs(:defaultprovider).returns described_class end describe '#install' do before do subject.stubs(:rpm).with('-q', 'foopkg', any_parameters).returns "foopkg 0 1.2.3.4 5 noarch :DESC:\n" end describe 'without a version' do it 'installs the unversioned package' do resource[:ensure] = :present Puppet::Util::Execution.expects(:execute).with(['urpmi', '--auto', 'foopkg'], anything) subject.install end end describe 'with a version' do it 'installs the versioned package' do resource[:ensure] = '4.5.6' Puppet::Util::Execution.expects(:execute).with(['urpmi', '--auto', 'foopkg-4.5.6'], anything) subject.install end end describe "and the package install fails" do it "raises an error" do Puppet::Util::Execution.stubs(:execute).with(['urpmi', '--auto', 'foopkg'], anything) subject.stubs(:query) expect { subject.install }.to raise_error Puppet::Error, /Package \S+ was not present after trying to install it/ end end end describe '#latest' do let(:urpmq_output) { 'foopkg : Lorem ipsum dolor sit amet, consectetur adipisicing elit ( 7.8.9-1.mga2 )' } it "uses urpmq to determine the latest package" do Puppet::Util::Execution.expects(:execute) .with(['urpmq', '-S', 'foopkg'], anything) .returns(Puppet::Util::Execution::ProcessOutput.new(urpmq_output, 0)) expect(subject.latest).to eq('7.8.9-1.mga2') end it "falls back to the current version" do resource[:ensure] = '5.4.3' Puppet::Util::Execution.expects(:execute) .with(['urpmq', '-S', 'foopkg'], anything) .returns(Puppet::Util::Execution::ProcessOutput.new('', 0)) expect(subject.latest).to eq('5.4.3') end end describe '#update' do it 'delegates to #install' do subject.expects(:install) subject.update end end describe '#purge' do it 'uses urpme to purge packages' do Puppet::Util::Execution.expects(:execute).with(['urpme', '--auto', 'foopkg'], anything) subject.purge end end end puppet-5.5.10/spec/unit/provider/package/windows/0000755005276200011600000000000013417162177021650 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/provider/package/windows/exe_package_spec.rb0000644005276200011600000000605313417161721025441 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/provider/package/windows/exe_package' describe Puppet::Provider::Package::Windows::ExePackage do subject { described_class } let (:name) { 'Git version 1.7.11' } let (:version) { '1.7.11' } let (:source) { 'E:\Git-1.7.11.exe' } let (:uninstall) { '"C:\Program Files (x86)\Git\unins000.exe" /SP-' } context '::from_registry' do it 'should return an instance of ExePackage' do subject.expects(:valid?).returns(true) pkg = subject.from_registry('', {'DisplayName' => name, 'DisplayVersion' => version, 'UninstallString' => uninstall}) expect(pkg.name).to eq(name) expect(pkg.version).to eq(version) expect(pkg.uninstall_string).to eq(uninstall) end it 'should return nil if it is not a valid executable' do subject.expects(:valid?).returns(false) expect(subject.from_registry('', {})).to be_nil end end context '::valid?' do let(:name) { 'myproduct' } let(:values) do { 'DisplayName' => name, 'UninstallString' => uninstall } end { 'DisplayName' => ['My App', ''], 'UninstallString' => ['E:\uninstall.exe', ''], 'WindowsInstaller' => [nil, 1], 'ParentKeyName' => [nil, 'Uber Product'], 'Security Update' => [nil, 'KB890830'], 'Update Rollup' => [nil, 'Service Pack 42'], 'Hotfix' => [nil, 'QFE 42'] }.each_pair do |k, arr| it "should accept '#{k}' with value '#{arr[0]}'" do values[k] = arr[0] expect(subject.valid?(name, values)).to be_truthy end it "should reject '#{k}' with value '#{arr[1]}'" do values[k] = arr[1] expect(subject.valid?(name, values)).to be_falsey end end it 'should reject packages whose name starts with "KBXXXXXX"' do expect(subject.valid?('KB890830', values)).to be_falsey end it 'should accept packages whose name does not start with "KBXXXXXX"' do expect(subject.valid?('My Update (KB890830)', values)).to be_truthy end end context '#match?' do let(:pkg) { subject.new(name, version, uninstall) } it 'should match product name' do expect(pkg.match?({:name => name})).to be_truthy end it 'should return false otherwise' do expect(pkg.match?({:name => 'not going to find it'})).to be_falsey end end context '#install_command' do it 'should install using the source' do cmd = subject.install_command({:source => source}) expect(cmd).to eq(source) end end context '#uninstall_command' do ['C:\uninstall.exe', 'C:\Program Files\uninstall.exe'].each do |exe| it "should quote #{exe}" do expect(subject.new(name, version, exe).uninstall_command).to eq( "\"#{exe}\"" ) end end ['"C:\Program Files\uninstall.exe"', '"C:\Program Files (x86)\Git\unins000.exe" /SILENT"'].each do |exe| it "should not quote #{exe}" do expect(subject.new(name, version, exe).uninstall_command).to eq( exe ) end end end end puppet-5.5.10/spec/unit/provider/package/windows/msi_package_spec.rb0000644005276200011600000000706613417161722025456 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/provider/package/windows/msi_package' describe Puppet::Provider::Package::Windows::MsiPackage do subject { described_class } let (:name) { 'mysql-5.1.58-win-x64' } let (:version) { '5.1.58' } let (:source) { 'E:\mysql-5.1.58-win-x64.msi' } let (:productcode) { '{E437FFB6-5C49-4DAC-ABAE-33FF065FE7CC}' } let (:packagecode) { '{5A6FD560-763A-4BC1-9E03-B18DFFB7C72C}' } def expect_installer inst = mock inst.expects(:ProductState).returns(5) inst.expects(:ProductInfo).with(productcode, 'PackageCode').returns(packagecode) subject.expects(:installer).returns(inst) end context '::installer', :if => Puppet.features.microsoft_windows? do it 'should return an instance of the COM interface' do expect(subject.installer).not_to be_nil end end context '::from_registry' do it 'should return an instance of MsiPackage' do subject.expects(:valid?).returns(true) expect_installer pkg = subject.from_registry(productcode, {'DisplayName' => name, 'DisplayVersion' => version}) expect(pkg.name).to eq(name) expect(pkg.version).to eq(version) expect(pkg.productcode).to eq(productcode) expect(pkg.packagecode).to eq(packagecode) end it 'should return nil if it is not a valid MSI' do subject.expects(:valid?).returns(false) expect(subject.from_registry(productcode, {})).to be_nil end end context '::valid?' do let(:values) do { 'DisplayName' => name, 'DisplayVersion' => version, 'WindowsInstaller' => 1 } end { 'DisplayName' => ['My App', ''], 'WindowsInstaller' => [1, nil], }.each_pair do |k, arr| it "should accept '#{k}' with value '#{arr[0]}'" do values[k] = arr[0] expect(subject.valid?(productcode, values)).to be_truthy end it "should reject '#{k}' with value '#{arr[1]}'" do values[k] = arr[1] expect(subject.valid?(productcode, values)).to be_falsey end end it 'should reject packages whose name is not a productcode' do expect(subject.valid?('AddressBook', values)).to be_falsey end it 'should accept packages whose name is a productcode' do expect(subject.valid?(productcode, values)).to be_truthy end end context '#match?' do it 'should match package codes case-insensitively' do pkg = subject.new(name, version, productcode, packagecode.upcase) expect(pkg.match?({:name => packagecode.downcase})).to be_truthy end it 'should match product codes case-insensitively' do pkg = subject.new(name, version, productcode.upcase, packagecode) expect(pkg.match?({:name => productcode.downcase})).to be_truthy end it 'should match product name' do pkg = subject.new(name, version, productcode, packagecode) expect(pkg.match?({:name => name})).to be_truthy end it 'should return false otherwise' do pkg = subject.new(name, version, productcode, packagecode) expect(pkg.match?({:name => 'not going to find it'})).to be_falsey end end context '#install_command' do it 'should install using the source' do cmd = subject.install_command({:source => source}) expect(cmd).to eq(['msiexec.exe', '/qn', '/norestart', '/i', source]) end end context '#uninstall_command' do it 'should uninstall using the productcode' do pkg = subject.new(name, version, productcode, packagecode) expect(pkg.uninstall_command).to eq(['msiexec.exe', '/qn', '/norestart', '/x', productcode]) end end end puppet-5.5.10/spec/unit/provider/package/windows/package_spec.rb0000644005276200011600000001443013417161722024577 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/provider/package/windows/package' describe Puppet::Provider::Package::Windows::Package do subject { described_class } let(:hklm) { 'HKEY_LOCAL_MACHINE' } let(:hkcu) { 'HKEY_CURRENT_USER' } let(:path) { 'Software\Microsoft\Windows\CurrentVersion\Uninstall' } let(:key) { mock('key', :name => "#{hklm}\\#{path}\\Google") } let(:package) { mock('package') } context '::each' do it 'should generate an empty enumeration' do subject.expects(:with_key) expect(subject.to_a).to be_empty end it 'should yield each package it finds' do subject.expects(:with_key).yields(key, {}) Puppet::Provider::Package::Windows::MsiPackage.expects(:from_registry).with('Google', {}).returns(package) yielded = nil subject.each do |pkg| yielded = pkg end expect(yielded).to eq(package) end end context '::with_key', :if => Puppet.features.microsoft_windows? do it 'should search HKLM (64 & 32) and HKCU (64 & 32)' do seq = sequence('reg') subject.expects(:open).with(hklm, path, subject::KEY64 | subject::KEY_READ).in_sequence(seq) subject.expects(:open).with(hklm, path, subject::KEY32 | subject::KEY_READ).in_sequence(seq) subject.expects(:open).with(hkcu, path, subject::KEY64 | subject::KEY_READ).in_sequence(seq) subject.expects(:open).with(hkcu, path, subject::KEY32 | subject::KEY_READ).in_sequence(seq) subject.with_key { |key, values| } end it 'should ignore file not found exceptions' do ex = Puppet::Util::Windows::Error.new('Failed to open registry key', Puppet::Util::Windows::Error::ERROR_FILE_NOT_FOUND) # make sure we don't stop after the first exception subject.expects(:open).times(4).raises(ex) keys = [] subject.with_key { |key, values| keys << key } expect(keys).to be_empty end it 'should raise other types of exceptions' do ex = Puppet::Util::Windows::Error.new('Failed to open registry key', Puppet::Util::Windows::Error::ERROR_ACCESS_DENIED) subject.expects(:open).raises(ex) expect { subject.with_key{ |key, values| } }.to raise_error do |error| expect(error).to be_a(Puppet::Util::Windows::Error) expect(error.code).to eq(5) # ERROR_ACCESS_DENIED end end end context '::installer_class' do it 'should require the source parameter' do expect { subject.installer_class({}) }.to raise_error(Puppet::Error, /The source parameter is required when using the Windows provider./) end context 'MSI' do let (:klass) { Puppet::Provider::Package::Windows::MsiPackage } it 'should accept source ending in .msi' do expect(subject.installer_class({:source => 'foo.msi'})).to eq(klass) end it 'should accept quoted source ending in .msi' do expect(subject.installer_class({:source => '"foo.msi"'})).to eq(klass) end it 'should accept source case insensitively' do expect(subject.installer_class({:source => '"foo.MSI"'})).to eq(klass) end it 'should reject source containing msi in the name' do expect { subject.installer_class({:source => 'mymsi.txt'}) }.to raise_error(Puppet::Error, /Don't know how to install 'mymsi.txt'/) end end context 'Unknown' do it 'should reject packages it does not know about' do expect { subject.installer_class({:source => 'basram'}) }.to raise_error(Puppet::Error, /Don't know how to install 'basram'/) end end end context '::munge' do it 'should shell quote strings with spaces and fix forward slashes' do expect(subject.munge('c:/windows/the thing')).to eq('"c:\windows\the thing"') end it 'should leave properly formatted paths alone' do expect(subject.munge('c:\windows\thething')).to eq('c:\windows\thething') end end context '::replace_forward_slashes' do it 'should replace forward with back slashes' do expect(subject.replace_forward_slashes('c:/windows/thing/stuff')).to eq('c:\windows\thing\stuff') end end context '::quote' do it 'should shell quote strings with spaces' do expect(subject.quote('foo bar')).to eq('"foo bar"') end it 'should shell quote strings with spaces and quotes' do expect(subject.quote('"foo bar" baz')).to eq('"\"foo bar\" baz"') end it 'should not shell quote strings without spaces' do expect(subject.quote('"foobar"')).to eq('"foobar"') end end context '::get_display_name' do it 'should return nil if values is nil' do expect(subject.get_display_name(nil)).to be_nil end it 'should return empty if values is empty' do reg_values = {} expect(subject.get_display_name(reg_values)).to eq('') end it 'should return DisplayName when available' do reg_values = { 'DisplayName' => 'Google' } expect(subject.get_display_name(reg_values)).to eq('Google') end it 'should return DisplayName when available, even when QuietDisplayName is also available' do reg_values = { 'DisplayName' => 'Google', 'QuietDisplayName' => 'Google Quiet' } expect(subject.get_display_name(reg_values)).to eq('Google') end it 'should return QuietDisplayName when available if DisplayName is empty' do reg_values = { 'DisplayName' => '', 'QuietDisplayName' =>'Google Quiet' } expect(subject.get_display_name(reg_values)).to eq('Google Quiet') end it 'should return QuietDisplayName when DisplayName is not available' do reg_values = { 'QuietDisplayName' =>'Google Quiet' } expect(subject.get_display_name(reg_values)).to eq('Google Quiet') end it 'should return empty when DisplayName is empty and QuietDisplay name is not available' do reg_values = { 'DisplayName' => '' } expect(subject.get_display_name(reg_values)).to eq('') end it 'should return empty when DisplayName is empty and QuietDisplay name is empty' do reg_values = { 'DisplayName' => '', 'QuietDisplayName' =>'' } expect(subject.get_display_name(reg_values)).to eq('') end end it 'should implement instance methods' do pkg = subject.new('orca', '5.0') expect(pkg.name).to eq('orca') expect(pkg.version).to eq('5.0') end end puppet-5.5.10/spec/unit/provider/package/zypper_spec.rb0000644005276200011600000002270513417161721023046 0ustar jenkinsjenkinsrequire 'spec_helper' describe Puppet::Type.type(:package).provider(:zypper) do before(:each) do # Create a mock resource @resource = stub 'resource' # A catch all; no parameters set @resource.stubs(:[]).returns(nil) # But set name and source @resource.stubs(:[]).with(:name).returns "mypackage" @resource.stubs(:[]).with(:ensure).returns :installed @resource.stubs(:command).with(:zypper).returns "/usr/bin/zypper" @provider = described_class.new(@resource) end it "should have an install method" do @provider = described_class.new expect(@provider).to respond_to(:install) end it "should have an uninstall method" do @provider = described_class.new expect(@provider).to respond_to(:uninstall) end it "should have an update method" do @provider = described_class.new expect(@provider).to respond_to(:update) end it "should have a latest method" do @provider = described_class.new expect(@provider).to respond_to(:latest) end it "should have a install_options method" do @provider = described_class.new expect(@provider).to respond_to(:install_options) end context "when installing with zypper version >= 1.0" do it "should use a command-line with versioned package'" do @resource.stubs(:should).with(:ensure).returns "1.2.3-4.5.6" @resource.stubs(:allow_virtual?).returns false @provider.stubs(:zypper_version).returns "1.2.8" @provider.expects(:zypper).with('--quiet', :install, '--auto-agree-with-licenses', '--no-confirm', 'mypackage-1.2.3-4.5.6') @provider.expects(:query).returns "mypackage 0 1.2.3 4.5.6 x86_64" @provider.install end it "should use a command-line without versioned package" do @resource.stubs(:should).with(:ensure).returns :latest @resource.stubs(:allow_virtual?).returns false @provider.stubs(:zypper_version).returns "1.2.8" @provider.expects(:zypper).with('--quiet', :install, '--auto-agree-with-licenses', '--no-confirm', '--name', 'mypackage') @provider.expects(:query).returns "mypackage 0 1.2.3 4.5.6 x86_64" @provider.install end end context "when installing with zypper version = 0.6.104" do it "should use a command-line with versioned package'" do @resource.stubs(:should).with(:ensure).returns "1.2.3-4.5.6" @resource.stubs(:allow_virtual?).returns false @provider.stubs(:zypper_version).returns "0.6.104" @provider.expects(:zypper).with('--terse', :install, '--auto-agree-with-licenses', '--no-confirm', 'mypackage-1.2.3-4.5.6') @provider.expects(:query).returns "mypackage 0 1.2.3 4.5.6 x86_64" @provider.install end it "should use a command-line without versioned package" do @resource.stubs(:should).with(:ensure).returns :latest @resource.stubs(:allow_virtual?).returns false @provider.stubs(:zypper_version).returns "0.6.104" @provider.expects(:zypper).with('--terse', :install, '--auto-agree-with-licenses', '--no-confirm', 'mypackage') @provider.expects(:query).returns "mypackage 0 1.2.3 4.5.6 x86_64" @provider.install end end context "when installing with zypper version = 0.6.13" do it "should use a command-line with versioned package'" do @resource.stubs(:should).with(:ensure).returns "1.2.3-4.5.6" @resource.stubs(:allow_virtual?).returns false @provider.stubs(:zypper_version).returns "0.6.13" @provider.expects(:zypper).with('--terse', :install, '--no-confirm', 'mypackage-1.2.3-4.5.6') @provider.expects(:query).returns "mypackage 0 1.2.3 4.5.6 x86_64" @provider.install end it "should use a command-line without versioned package" do @resource.stubs(:should).with(:ensure).returns :latest @resource.stubs(:allow_virtual?).returns false @provider.stubs(:zypper_version).returns "0.6.13" @provider.expects(:zypper).with('--terse', :install, '--no-confirm', 'mypackage') @provider.expects(:query).returns "mypackage 0 1.2.3 4.5.6 x86_64" @provider.install end end context "when updating" do it "should call install method of instance" do @provider.expects(:install) @provider.update end end context "when getting latest version" do after { described_class.reset! } context "when the package has available update" do it "should return a version string with valid list-updates data from SLES11sp1" do fake_data = File.read(my_fixture('zypper-list-updates-SLES11sp1.out')) @resource.stubs(:[]).with(:name).returns "at" described_class.expects(:zypper).with("list-updates").returns fake_data expect(@provider.latest).to eq("3.1.8-1069.18.2") end end context "when the package is in the latest version" do it "should return nil with valid list-updates data from SLES11sp1" do fake_data = File.read(my_fixture('zypper-list-updates-SLES11sp1.out')) @resource.stubs(:[]).with(:name).returns "zypper-log" described_class.expects(:zypper).with("list-updates").returns fake_data expect(@provider.latest).to eq(nil) end end context "when there are no updates available" do it "should return nil" do fake_data_empty = File.read(my_fixture('zypper-list-updates-empty.out')) @resource.stubs(:[]).with(:name).returns "at" described_class.expects(:zypper).with("list-updates").returns fake_data_empty expect(@provider.latest).to eq(nil) end end end context "should install a virtual package" do it "when zypper version = 0.6.13" do @resource.stubs(:should).with(:ensure).returns :installed @resource.stubs(:allow_virtual?).returns true @provider.stubs(:zypper_version).returns "0.6.13" @provider.expects(:zypper).with('--terse', :install, '--no-confirm', 'mypackage') @provider.expects(:query).returns "mypackage 0 1.2.3 4.5.6 x86_64" @provider.install end it "when zypper version >= 1.0.0" do @resource.stubs(:should).with(:ensure).returns :installed @resource.stubs(:allow_virtual?).returns true @provider.stubs(:zypper_version).returns "1.2.8" @provider.expects(:zypper).with('--quiet', :install, '--auto-agree-with-licenses', '--no-confirm', 'mypackage') @provider.expects(:query).returns "mypackage 0 1.2.3 4.5.6 x86_64" @provider.install end end context "when installing with zypper install options" do it "should install the package without checking keys" do @resource.stubs(:[]).with(:name).returns "php5" @resource.stubs(:[]).with(:install_options).returns ['--no-gpg-check', {'-p' => '/vagrant/files/localrepo/'}] @resource.stubs(:should).with(:ensure).returns "5.4.10-4.5.6" @resource.stubs(:allow_virtual?).returns false @provider.stubs(:zypper_version).returns "1.2.8" @provider.expects(:zypper).with('--quiet', '--no-gpg-check', :install, '--auto-agree-with-licenses', '--no-confirm', '-p=/vagrant/files/localrepo/', 'php5-5.4.10-4.5.6') @provider.expects(:query).returns "php5 0 5.4.10 4.5.6 x86_64" @provider.install end it "should install package with hash install options" do @resource.stubs(:[]).with(:name).returns 'vim' @resource.stubs(:[]).with(:install_options).returns([{ '--a' => 'foo', '--b' => '"quoted bar"' }]) @resource.stubs(:should).with(:ensure).returns :present @resource.stubs(:allow_virtual?).returns false @provider.stubs(:zypper_version).returns '1.2.8' @provider.expects(:zypper).with('--quiet', :install, '--auto-agree-with-licenses', '--no-confirm', '--a=foo', '--b="quoted bar"', '--name', 'vim') @provider.expects(:query).returns 'package vim is not installed' @provider.install end it "should install package with array install options" do @resource.stubs(:[]).with(:name).returns 'vim' @resource.stubs(:[]).with(:install_options).returns([['--a', '--b', '--c']]) @resource.stubs(:should).with(:ensure).returns :present @resource.stubs(:allow_virtual?).returns false @provider.stubs(:zypper_version).returns '1.2.8' @provider.expects(:zypper).with('--quiet', :install, '--auto-agree-with-licenses', '--no-confirm', '--a', '--b', '--c', '--name', 'vim') @provider.expects(:query).returns 'package vim is not installed' @provider.install end it "should install package with string install options" do @resource.stubs(:[]).with(:name).returns 'vim' @resource.stubs(:[]).with(:install_options).returns(['--a --b --c']) @resource.stubs(:should).with(:ensure).returns :present @resource.stubs(:allow_virtual?).returns false @provider.stubs(:zypper_version).returns '1.2.8' @provider.expects(:zypper).with('--quiet', :install, '--auto-agree-with-licenses', '--no-confirm', '--a --b --c', '--name', 'vim') @provider.expects(:query).returns 'package vim is not installed' @provider.install end end context 'when uninstalling' do it 'should use remove to uninstall on zypper version 1.6 and above' do @provider.stubs(:zypper_version).returns '1.6.308' @provider.expects(:zypper).with(:remove, '--no-confirm', 'mypackage') @provider.uninstall end it 'should use remove --force-solution to uninstall on zypper versions between 1.0 and 1.6' do @provider.stubs(:zypper_version).returns '1.0.2' @provider.expects(:zypper).with(:remove, '--no-confirm', '--force-resolution', 'mypackage') @provider.uninstall end end end puppet-5.5.10/spec/unit/provider/package/dnf_spec.rb0000644005276200011600000000322313417161722022257 0ustar jenkinsjenkinsrequire 'spec_helper' # Note that much of the functionality of the dnf provider is already tested with yum provider tests, # as yum is the parent provider. describe Puppet::Type.type(:package).provider(:dnf) do context 'default' do (19..21).each do |ver| it "should not be the default provider on fedora#{ver}" do Facter.stubs(:value).with(:osfamily).returns(:redhat) Facter.stubs(:value).with(:operatingsystem).returns(:fedora) Facter.stubs(:value).with(:operatingsystemmajrelease).returns("#{ver}") expect(described_class).to_not be_default end end (22..26).each do |ver| it "should be the default provider on fedora#{ver}" do Facter.stubs(:value).with(:osfamily).returns(:redhat) Facter.stubs(:value).with(:operatingsystem).returns(:fedora) Facter.stubs(:value).with(:operatingsystemmajrelease).returns("#{ver}") expect(described_class).to be_default end end it "should not be the default provider on rhel7" do Facter.stubs(:value).with(:osfamily).returns(:redhat) Facter.stubs(:value).with(:operatingsystem).returns(:redhat) Facter.stubs(:value).with(:operatingsystemmajrelease).returns("7") expect(described_class).to_not be_default end it "should be the default provider on rhel8" do Facter.stubs(:value).with(:osfamily).returns(:redhat) Facter.stubs(:value).with(:operatingsystem).returns(:redhat) Facter.stubs(:value).with(:operatingsystemmajrelease).returns("8") expect(described_class).to be_default end end it_behaves_like 'RHEL package provider', described_class, 'dnf' end puppet-5.5.10/spec/unit/provider/package/dpkg_spec.rb0000644005276200011600000002376513417161722022452 0ustar jenkinsjenkinsrequire 'spec_helper' require 'stringio' describe Puppet::Type.type(:package).provider(:dpkg) do let(:bash_version) { '4.2-5ubuntu3' } let(:bash_installed_output) { "install ok installed bash #{bash_version}\n" } let(:bash_installed_io) { StringIO.new(bash_installed_output) } let(:vim_installed_output) { "install ok installed vim 2:7.3.547-6ubuntu5\n" } let(:all_installed_io) { StringIO.new([bash_installed_output, vim_installed_output].join) } let(:args) { ['-W', '--showformat', %Q{'${Status} ${Package} ${Version}\\n'}] } let(:execute_options) do {:failonfail => true, :combine => true, :custom_environment => {}} end let(:resource_name) { 'package' } let(:resource) { stub 'resource', :[] => resource_name } let(:provider) { described_class.new(resource) } it "has documentation" do expect(described_class.doc).to be_instance_of(String) end context "when listing all instances" do let(:execpipe_args) { args.unshift('myquery') } before do described_class.stubs(:command).with(:dpkgquery).returns 'myquery' end it "creates and return an instance for a single dpkg-query entry" do Puppet::Util::Execution.expects(:execpipe).with(execpipe_args).yields bash_installed_io installed = mock 'bash' described_class.expects(:new).with(:ensure => "4.2-5ubuntu3", :error => "ok", :desired => "install", :name => "bash", :status => "installed", :provider => :dpkg).returns installed expect(described_class.instances).to eq([installed]) end it "parses multiple dpkg-query multi-line entries in the output" do Puppet::Util::Execution.expects(:execpipe).with(execpipe_args).yields all_installed_io bash = mock 'bash' described_class.expects(:new).with(:ensure => "4.2-5ubuntu3", :error => "ok", :desired => "install", :name => "bash", :status => "installed", :provider => :dpkg).returns bash vim = mock 'vim' described_class.expects(:new).with(:ensure => "2:7.3.547-6ubuntu5", :error => "ok", :desired => "install", :name => "vim", :status => "installed", :provider => :dpkg).returns vim expect(described_class.instances).to eq([bash, vim]) end it "continues without failing if it encounters bad lines between good entries" do Puppet::Util::Execution.expects(:execpipe).with(execpipe_args).yields StringIO.new([bash_installed_output, "foobar\n", vim_installed_output].join) bash = mock 'bash' vim = mock 'vim' described_class.expects(:new).twice.returns(bash, vim) expect(described_class.instances).to eq([bash, vim]) end end context "when querying the current state" do let(:dpkgquery_path) { '/bin/dpkg-query' } let(:query_args) do args.unshift(dpkgquery_path) args.push(resource_name) end def dpkg_query_execution_returns(output) Puppet::Util::Execution.expects(:execute).with(query_args, execute_options).returns(Puppet::Util::Execution::ProcessOutput.new(output, 0)) end before do Puppet::Util.stubs(:which).with('/usr/bin/dpkg-query').returns(dpkgquery_path) end it "considers the package purged if dpkg-query fails" do Puppet::Util::Execution.expects(:execute).with(query_args, execute_options).raises Puppet::ExecutionFailure.new("eh") expect(provider.query[:ensure]).to eq(:purged) end it "returns a hash of the found package status for an installed package" do dpkg_query_execution_returns(bash_installed_output) expect(provider.query).to eq({:ensure => "4.2-5ubuntu3", :error => "ok", :desired => "install", :name => "bash", :status => "installed", :provider => :dpkg}) end it "considers the package absent if the dpkg-query result cannot be interpreted" do dpkg_query_execution_returns('some-bad-data') expect(provider.query[:ensure]).to eq(:absent) end it "fails if an error is discovered" do dpkg_query_execution_returns(bash_installed_output.gsub("ok","error")) expect { provider.query }.to raise_error(Puppet::Error) end it "considers the package purged if it is marked 'not-installed'" do not_installed_bash = bash_installed_output.gsub("installed", "not-installed") not_installed_bash.gsub!(bash_version, "") dpkg_query_execution_returns(not_installed_bash) expect(provider.query[:ensure]).to eq(:purged) end it "considers the package absent if it is marked 'config-files'" do dpkg_query_execution_returns(bash_installed_output.gsub("installed","config-files")) expect(provider.query[:ensure]).to eq(:absent) end it "considers the package absent if it is marked 'half-installed'" do dpkg_query_execution_returns(bash_installed_output.gsub("installed","half-installed")) expect(provider.query[:ensure]).to eq(:absent) end it "considers the package absent if it is marked 'unpacked'" do dpkg_query_execution_returns(bash_installed_output.gsub("installed","unpacked")) expect(provider.query[:ensure]).to eq(:absent) end it "considers the package absent if it is marked 'half-configured'" do dpkg_query_execution_returns(bash_installed_output.gsub("installed","half-configured")) expect(provider.query[:ensure]).to eq(:absent) end it "considers the package held if its state is 'hold'" do dpkg_query_execution_returns(bash_installed_output.gsub("install","hold")) expect(provider.query[:ensure]).to eq(:held) end context "parsing tests" do let(:resource_name) { 'name' } let(:package_hash) do { :desired => 'desired', :error => 'ok', :status => 'status', :name => resource_name, :ensure => 'ensure', :provider => :dpkg, } end let(:package_not_found_hash) do {:ensure => :purged, :status => 'missing', :name => resource_name, :error => 'ok'} end def parser_test(dpkg_output_string, gold_hash, number_of_debug_logs = 0) dpkg_query_execution_returns(dpkg_output_string) Puppet.expects(:warning).never Puppet.expects(:debug).times(number_of_debug_logs) expect(provider.query).to eq(gold_hash) end it "parses properly even if optional ensure field is missing" do no_ensure = 'desired ok status name ' parser_test(no_ensure, package_hash.merge(:ensure => '')) end it "provides debug logging of unparsable lines" do parser_test('an unexpected dpkg msg with an exit code of 0', package_not_found_hash.merge(:ensure => :absent), 1) end it "does not log if execution returns with non-zero exit code" do Puppet::Util::Execution.expects(:execute).with(query_args, execute_options).raises Puppet::ExecutionFailure.new("failed") Puppet::expects(:debug).never expect(provider.query).to eq(package_not_found_hash) end end end context "when installing" do before do resource.stubs(:[]).with(:source).returns "mypkg" end it "fails to install if no source is specified in the resource" do resource.expects(:[]).with(:source).returns nil expect { provider.install }.to raise_error(ArgumentError) end it "uses 'dpkg -i' to install the package" do resource.expects(:[]).with(:source).returns "mypackagefile" provider.expects(:unhold) provider.expects(:dpkg).with { |*command| command[-1] == "mypackagefile" and command[-2] == "-i" } provider.install end it "keeps old config files if told to do so" do resource.expects(:[]).with(:configfiles).returns :keep provider.expects(:unhold) provider.expects(:dpkg).with { |*command| command[0] == "--force-confold" } provider.install end it "replaces old config files if told to do so" do resource.expects(:[]).with(:configfiles).returns :replace provider.expects(:unhold) provider.expects(:dpkg).with { |*command| command[0] == "--force-confnew" } provider.install end it "ensures any hold is removed" do provider.expects(:unhold).once provider.expects(:dpkg) provider.install end end context "when holding or unholding" do let(:tempfile) { stub 'tempfile', :print => nil, :close => nil, :flush => nil, :path => "/other/file" } before do tempfile.stubs(:write) Tempfile.stubs(:new).returns tempfile end it "installs first if holding" do provider.stubs(:execute) provider.expects(:install).once provider.hold end it "executes dpkg --set-selections when holding" do provider.stubs(:install) provider.expects(:execute).with([:dpkg, '--set-selections'], {:failonfail => false, :combine => false, :stdinfile => tempfile.path}).once provider.hold end it "executes dpkg --set-selections when unholding" do provider.stubs(:install) provider.expects(:execute).with([:dpkg, '--set-selections'], {:failonfail => false, :combine => false, :stdinfile => tempfile.path}).once provider.hold end end it "uses :install to update" do provider.expects(:install) provider.update end context "when determining latest available version" do it "returns the version found by dpkg-deb" do resource.expects(:[]).with(:source).returns "myfile" provider.expects(:dpkg_deb).with { |*command| command[-1] == "myfile" }.returns "package\t1.0" expect(provider.latest).to eq("1.0") end it "warns if the package file contains a different package" do provider.expects(:dpkg_deb).returns("foo\tversion") provider.expects(:warning) provider.latest end it "copes with names containing ++" do resource = stub 'resource', :[] => "package++" provider = described_class.new(resource) provider.expects(:dpkg_deb).returns "package++\t1.0" expect(provider.latest).to eq("1.0") end end it "uses 'dpkg -r' to uninstall" do provider.expects(:dpkg).with("-r", resource_name) provider.uninstall end it "uses 'dpkg --purge' to purge" do provider.expects(:dpkg).with("--purge", resource_name) provider.purge end end puppet-5.5.10/spec/unit/provider/package/gem_spec.rb0000644005276200011600000003021613417161722022262 0ustar jenkinsjenkinsrequire 'spec_helper' context Puppet::Type.type(:package).provider(:gem) do context 'installing myresource' do let(:resource) do Puppet::Type.type(:package).new( :name => 'myresource', :ensure => :installed ) end let(:provider) do provider = described_class.new provider.resource = resource provider end before :each do resource.provider = provider end context "when installing" do it "should use the path to the gem" do described_class.stubs(:command).with(:gemcmd).returns "/my/gem" provider.expects(:execute).with { |args| args[0] == "/my/gem" }.returns "" provider.install end it "should specify that the gem is being installed" do provider.expects(:execute).with { |args| args[1] == "install" }.returns "" provider.install end it "should specify that documentation should not be included" do provider.expects(:execute).with { |args| args[2] == "--no-rdoc" }.returns "" provider.install end it "should specify that RI should not be included" do provider.expects(:execute).with { |args| args[3] == "--no-ri" }.returns "" provider.install end it "should specify the package name" do provider.expects(:execute).with { |args| args[4] == "myresource" }.returns "" provider.install end it "should not append install_options by default" do provider.expects(:execute).with { |args| args.length == 5 }.returns "" provider.install end it "should allow setting an install_options parameter" do resource[:install_options] = [ '--force', {'--bindir' => '/usr/bin' } ] provider.expects(:execute).with { |args| args[2] == '--force' && args[3] == '--bindir=/usr/bin' }.returns '' provider.install end context "when a source is specified" do context "as a normal file" do it "should use the file name instead of the gem name" do resource[:source] = "/my/file" provider.expects(:execute).with { |args| args[2] == "/my/file" }.returns "" provider.install end end context "as a file url" do it "should use the file name instead of the gem name" do resource[:source] = "file:///my/file" provider.expects(:execute).with { |args| args[2] == "/my/file" }.returns "" provider.install end end context "as a puppet url" do it "should fail" do resource[:source] = "puppet://my/file" expect { provider.install }.to raise_error(Puppet::Error) end end context "as a non-file and non-puppet url" do it "should treat the source as a gem repository" do resource[:source] = "http://host/my/file" provider.expects(:execute).with { |args| args[2..4] == ["--source", "http://host/my/file", "myresource"] }.returns "" provider.install end end context "as a windows path on windows", :if => Puppet.features.microsoft_windows? do it "should treat the source as a local path" do resource[:source] = "c:/this/is/a/path/to/a/gem.gem" provider.expects(:execute).with { |args| args[2] == "c:/this/is/a/path/to/a/gem.gem" }.returns "" provider.install end end context "with an invalid uri" do it "should fail" do URI.expects(:parse).raises(ArgumentError) resource[:source] = "http:::::uppet:/:/my/file" expect { provider.install }.to raise_error(Puppet::Error) end end end end context "#latest" do it "should return a single value for 'latest'" do #gemlist is used for retrieving both local and remote version numbers, and there are cases # (particularly local) where it makes sense for it to return an array. That doesn't make # sense for '#latest', though. provider.class.expects(:gemlist).with({ :justme => 'myresource'}).returns({ :name => 'myresource', :ensure => ["3.0"], :provider => :gem, }) expect(provider.latest).to eq("3.0") end it "should list from the specified source repository" do resource[:source] = "http://foo.bar.baz/gems" provider.class.expects(:gemlist). with({:justme => 'myresource', :source => "http://foo.bar.baz/gems"}). returns({ :name => 'myresource', :ensure => ["3.0"], :provider => :gem, }) expect(provider.latest).to eq("3.0") end end context "#instances" do before do described_class.stubs(:command).with(:gemcmd).returns "/my/gem" end it "should return an empty array when no gems installed" do described_class.expects(:execute).with(%w{/my/gem list --local}, {:failonfail => true, :combine => true, :custom_environment => {"HOME"=>ENV["HOME"]}}).returns("\n") expect(described_class.instances).to eq([]) end it "should return ensure values as an array of installed versions" do described_class.expects(:execute).with(%w{/my/gem list --local}, {:failonfail => true, :combine => true, :custom_environment => {"HOME"=>ENV["HOME"]}}).returns <<-HEREDOC.gsub(/ /, '') systemu (1.2.0) vagrant (0.8.7, 0.6.9) HEREDOC expect(described_class.instances.map {|p| p.properties}).to eq([ {:ensure => ["1.2.0"], :provider => :gem, :name => 'systemu'}, {:ensure => ["0.8.7", "0.6.9"], :provider => :gem, :name => 'vagrant'} ]) end it "should ignore platform specifications" do described_class.expects(:execute).with(%w{/my/gem list --local}, {:failonfail => true, :combine => true, :custom_environment => {"HOME"=>ENV["HOME"]}}).returns <<-HEREDOC.gsub(/ /, '') systemu (1.2.0) nokogiri (1.6.1 ruby java x86-mingw32 x86-mswin32-60, 1.4.4.1 x86-mswin32) HEREDOC expect(described_class.instances.map {|p| p.properties}).to eq([ {:ensure => ["1.2.0"], :provider => :gem, :name => 'systemu'}, {:ensure => ["1.6.1", "1.4.4.1"], :provider => :gem, :name => 'nokogiri'} ]) end it "should not list 'default: ' text from rubygems''" do described_class.expects(:execute).with(%w{/my/gem list --local}, anything).returns <<-HEREDOC.gsub(/ /, '') bundler (1.16.1, default: 1.16.0, 1.15.1) HEREDOC expect(described_class.instances.map {|p| p.properties}).to eq([ {:name => 'bundler', :ensure => ["1.16.1", "1.16.0", "1.15.1"], :provider => :gem} ]) end it "should not fail when an unmatched line is returned" do described_class.expects(:execute).with(%w{/my/gem list --local}, {:failonfail => true, :combine => true, :custom_environment => {"HOME"=>ENV["HOME"]}}). returns(File.read(my_fixture('line-with-1.8.5-warning'))) expect(described_class.instances.map {|p| p.properties}). to eq([{:provider=>:gem, :ensure=>["0.3.2"], :name=>"columnize"}, {:provider=>:gem, :ensure=>["1.1.3"], :name=>"diff-lcs"}, {:provider=>:gem, :ensure=>["0.0.1"], :name=>"metaclass"}, {:provider=>:gem, :ensure=>["0.10.5"], :name=>"mocha"}, {:provider=>:gem, :ensure=>["0.8.7"], :name=>"rake"}, {:provider=>:gem, :ensure=>["2.9.0"], :name=>"rspec-core"}, {:provider=>:gem, :ensure=>["2.9.1"], :name=>"rspec-expectations"}, {:provider=>:gem, :ensure=>["2.9.0"], :name=>"rspec-mocks"}, {:provider=>:gem, :ensure=>["0.9.0"], :name=>"rubygems-bundler"}, {:provider=>:gem, :ensure=>["1.11.3.3"], :name=>"rvm"}]) end end context "listing gems" do context "searching for a single package" do it "searches for an exact match" do described_class.expects(:execute).with(includes('\Abundler\z'), {:failonfail => true, :combine => true, :custom_environment => {"HOME"=>ENV["HOME"]}}).returns(File.read(my_fixture('gem-list-single-package'))) expected = {:name => 'bundler', :ensure => %w[1.6.2], :provider => :gem} expect(described_class.gemlist({:justme => 'bundler'})).to eq(expected) end end end context 'insync?' do context 'for array of versions' do let(:is) { ['1.3.4', '3.6.1', '5.1.2'] } it 'returns true for ~> 1.3' do resource[:ensure] = '~> 1.3' expect(provider).to be_insync(is) end it 'returns false for ~> 2' do resource[:ensure] = '~> 2' expect(provider).to_not be_insync(is) end it 'returns true for > 4' do resource[:ensure] = '> 4' expect(provider).to be_insync(is) end it 'returns true for 3.6.1' do resource[:ensure] = '3.6.1' expect(provider).to be_insync(is) end it 'returns false for 3.6.2' do resource[:ensure] = '3.6.2' expect(provider).to_not be_insync(is) end end context 'for string version' do let(:is) { '1.3.4' } it 'returns true for ~> 1.3' do resource[:ensure] = '~> 1.3' expect(provider).to be_insync(is) end it 'returns false for ~> 2' do resource[:ensure] = '~> 2' expect(provider).to_not be_insync(is) end it 'returns false for > 4' do resource[:ensure] = '> 4' expect(provider).to_not be_insync(is) end it 'returns true for 1.3.4' do resource[:ensure] = '1.3.4' expect(provider).to be_insync(is) end it 'returns false for 3.6.1' do resource[:ensure] = '3.6.1' expect(provider).to_not be_insync(is) end end it 'should return false for bad version specifiers' do resource[:ensure] = 'not a valid gem specifier' expect(provider).to_not be_insync('1.0') end it 'should return false for :absent' do resource[:ensure] = '~> 1.0' expect(provider).to_not be_insync(:absent) end end end context 'uninstalling myresource' do let(:resource) do Puppet::Type.type(:package).new( :name => 'myresource', :ensure => :absent ) end let(:provider) do provider = described_class.new provider.resource = resource provider end before :each do resource.provider = provider end context "when uninstalling" do it "should use the path to the gem" do described_class.stubs(:command).with(:gemcmd).returns "/my/gem" provider.expects(:execute).with { |args| args[0] == "/my/gem" }.returns "" provider.uninstall end it "should specify that the gem is being uninstalled" do provider.expects(:execute).with { |args| args[1] == "uninstall" }.returns "" provider.uninstall end it "should specify that the relevant executables should be removed without confirmation" do provider.expects(:execute).with { |args| args[2] == "--executables" }.returns "" provider.uninstall end it "should specify that all the matching versions should be removed" do provider.expects(:execute).with { |args| args[3] == "--all" }.returns "" provider.uninstall end it "should specify the package name" do provider.expects(:execute).with { |args| args[4] == "myresource" }.returns "" provider.uninstall end it "should not append uninstall_options by default" do provider.expects(:execute).with { |args| args.length == 5 }.returns "" provider.uninstall end it "should allow setting an uninstall_options parameter" do resource[:uninstall_options] = [ '--ignore-dependencies', {'--version' => '0.1.1' } ] provider.expects(:execute).with { |args| args[5] == '--ignore-dependencies' && args[6] == '--version=0.1.1' }.returns '' provider.uninstall end end end end puppet-5.5.10/spec/unit/provider/package/pip_spec.rb0000644005276200011600000003611413417161722022305 0ustar jenkinsjenkinsrequire 'spec_helper' osfamilies = { 'windows' => ['pip.exe'], 'other' => ['pip', 'pip-python'] } describe Puppet::Type.type(:package).provider(:pip) do before do @resource = Puppet::Resource.new(:package, "fake_package") @provider = described_class.new(@resource) @client = stub_everything('client') @client.stubs(:call).with('package_releases', 'real_package').returns(["1.3", "1.2.5", "1.2.4"]) @client.stubs(:call).with('package_releases', 'fake_package').returns([]) end context "parse" do it "should return a hash on valid input" do expect(described_class.parse("real_package==1.2.5")).to eq({ :ensure => "1.2.5", :name => "real_package", :provider => :pip, }) end it "should return nil on invalid input" do expect(described_class.parse("foo")).to eq(nil) end end context "cmd" do it "should return 'pip.exe' by default on Windows systems" do Puppet.features.stubs(:microsoft_windows?).returns true expect(described_class.cmd[0]).to eq('pip.exe') end it "could return pip-python on legacy redhat systems which rename pip" do Puppet.features.stubs(:microsoft_windows?).returns false expect(described_class.cmd[1]).to eq('pip-python') end it "should return pip by default on other systems" do Puppet.features.stubs(:microsoft_windows?).returns false expect(described_class.cmd[0]).to eq('pip') end end context "instances" do osfamilies.each do |osfamily, pip_cmds| it "should return an array on #{osfamily} systems when #{pip_cmds.join(' or ')} is present" do Puppet.features.stubs(:microsoft_windows?).returns (osfamily == 'windows') pip_cmds.each do |pip_cmd| pip_cmds.each do |cmd| unless cmd == pip_cmd described_class.expects(:which).with(cmd).returns(nil) end end described_class.stubs(:pip_version).returns('8.0.1') described_class.expects(:which).with(pip_cmd).returns("/fake/bin/#{pip_cmd}") p = stub("process") p.expects(:collect).yields("real_package==1.2.5") described_class.expects(:execpipe).with(["/fake/bin/#{pip_cmd}", "freeze"]).yields(p) described_class.instances end end context "with pip version >= 8.1.0" do versions = ['8.1.0', '9.0.1'] versions.each do |version| it "should use the --all option when version is '#{version}'" do Puppet.features.stubs(:microsoft_windows?).returns (osfamily == 'windows') described_class.stubs(:pip_cmd).returns('/fake/bin/pip') described_class.stubs(:pip_version).returns(version) p = stub("process") p.expects(:collect).yields("real_package==1.2.5") described_class.expects(:execpipe).with(["/fake/bin/pip", "freeze", "--all"]).yields(p) described_class.instances end end end it "should return an empty array on #{osfamily} systems when #{pip_cmds.join(' and ')} are missing" do Puppet.features.stubs(:microsoft_windows?).returns (osfamily == 'windows') pip_cmds.each do |cmd| described_class.expects(:which).with(cmd).returns nil end expect(described_class.instances).to eq([]) end end end context "query" do before do @resource[:name] = "real_package" end it "should return a hash when pip and the package are present" do described_class.expects(:instances).returns [described_class.new({ :ensure => "1.2.5", :name => "real_package", :provider => :pip, })] expect(@provider.query).to eq({ :ensure => "1.2.5", :name => "real_package", :provider => :pip, }) end it "should return nil when the package is missing" do described_class.expects(:instances).returns [] expect(@provider.query).to eq(nil) end it "should be case insensitive" do @resource[:name] = "Real_Package" described_class.expects(:instances).returns [described_class.new({ :ensure => "1.2.5", :name => "real_package", :provider => :pip, })] expect(@provider.query).to eq({ :ensure => "1.2.5", :name => "real_package", :provider => :pip, }) end end context "latest" do context "with pip version < 1.5.4" do before :each do described_class.stubs(:pip_version).returns('1.0.1') described_class.stubs(:which).with('pip').returns("/fake/bin/pip") described_class.stubs(:which).with('pip-python').returns("/fake/bin/pip") described_class.stubs(:which).with('pip.exe').returns("/fake/bin/pip") end it "should find a version number for new_pip_package" do p = StringIO.new( <<-EOS Downloading/unpacking fake-package Using version 0.10.1 (newest of versions: 0.10.1, 0.10, 0.9, 0.8.1, 0.8, 0.7.2, 0.7.1, 0.7, 0.6.1, 0.6, 0.5.2, 0.5.1, 0.5, 0.4, 0.3.1, 0.3, 0.2, 0.1) Downloading real-package-0.10.1.tar.gz (544Kb): 544Kb downloaded Saved ./foo/real-package-0.10.1.tar.gz Successfully downloaded real-package EOS ) Puppet::Util::Execution.expects(:execpipe).yields(p).once @resource[:name] = "real_package" expect(@provider.latest).to eq('0.10.1') end it "should not find a version number for fake_package" do p = StringIO.new( <<-EOS Downloading/unpacking fake-package Could not fetch URL http://pypi.python.org/simple/fake_package: HTTP Error 404: Not Found Will skip URL http://pypi.python.org/simple/fake_package when looking for download links for fake-package Could not fetch URL http://pypi.python.org/simple/fake_package/: HTTP Error 404: Not Found Will skip URL http://pypi.python.org/simple/fake_package/ when looking for download links for fake-package Could not find any downloads that satisfy the requirement fake-package No distributions at all found for fake-package Exception information: Traceback (most recent call last): File "/usr/lib/python2.7/dist-packages/pip/basecommand.py", line 126, in main self.run(options, args) File "/usr/lib/python2.7/dist-packages/pip/commands/install.py", line 223, in run requirement_set.prepare_files(finder, force_root_egg_info=self.bundle, bundle=self.bundle) File "/usr/lib/python2.7/dist-packages/pip/req.py", line 948, in prepare_files url = finder.find_requirement(req_to_install, upgrade=self.upgrade) File "/usr/lib/python2.7/dist-packages/pip/index.py", line 152, in find_requirement raise DistributionNotFound('No distributions at all found for %s' % req) DistributionNotFound: No distributions at all found for fake-package Storing complete log in /root/.pip/pip.log EOS ) Puppet::Util::Execution.expects(:execpipe).yields(p).once @resource[:name] = "fake_package" expect(@provider.latest).to eq(nil) end end context "with pip version >= 1.5.4" do # For Pip 1.5.4 and above, you can get a version list from CLI - which allows for native pip behavior # with regards to custom repositories, proxies and the like before :each do described_class.stubs(:pip_version).returns('1.5.4') described_class.stubs(:which).with('pip').returns("/fake/bin/pip") described_class.stubs(:which).with('pip-python').returns("/fake/bin/pip") described_class.stubs(:which).with('pip.exe').returns("/fake/bin/pip") end it "should find a version number for real_package" do p = StringIO.new( <<-EOS Collecting real-package==versionplease Could not find a version that satisfies the requirement real-package==versionplease (from versions: 1.1.3, 1.2, 1.9b1) No matching distribution found for real-package==versionplease EOS ) Puppet::Util::Execution.expects(:execpipe).with(["/fake/bin/pip", "install", "real_package==versionplease"]).yields(p).once @resource[:name] = "real_package" latest = @provider.latest expect(latest).to eq('1.9b1') end it "should not find a version number for fake_package" do p = StringIO.new( <<-EOS Collecting fake-package==versionplease Could not find a version that satisfies the requirement fake-package==versionplease (from versions: ) No matching distribution found for fake-package==versionplease EOS ) Puppet::Util::Execution.expects(:execpipe).with(["/fake/bin/pip", "install", "fake_package==versionplease"]).yields(p).once @resource[:name] = "fake_package" expect(@provider.latest).to eq(nil) end it "should handle out-of-order version numbers for real_package" do p = StringIO.new( <<-EOS Collecting real-package==versionplease Could not find a version that satisfies the requirement real-package==versionplease (from versions: 1.11, 13.0.3, 1.6, 1.9, 1.3.2, 14.0.1, 12.0.7, 13.0.3, 1.7.2, 1.8.4, 1.6.1, 0.9.2, 1.3, 1.8.3, 12.1.1, 1.1, 1.11.6, 1.4.8, 1.6.3, 1.10.1, 14.0.2, 1.11.3, 14.0.3, 1.4rc1, 0.8.4, 1.0, 12.0.5, 14.0.6, 1.11.5, 1.7.1.1, 1.11.4, 13.0.1, 13.1.2, 1.3.3, 0.8.2, 14.0.0, 12.0, 1.8, 1.3.4, 12.0, 1.2, 12.0.6, 0.9.1, 13.1.1, 14.0.5, 15.0.2, 15.0.0, 1.4.5, 1.4.3, 13.1.1, 1.11.2, 13.1.2, 1.3.1, 13.1.0, 12.0.2, 1.11.1, 12.0.1, 12.1.0, 0.9, 1.4.4, 13.0.0, 1.4.9, 12.1.0, 1.7.1, 1.4.2, 14.0.5, 0.8.1, 1.4.6, 0.8.3, 1.11.3, 1.5.1, 1.4.7, 13.0.2, 12.0.7, 13.0.0, 1.9.1, 1.8.2, 14.0.1, 14.0.0, 14.0.4, 1.6.2, 15.0.1, 13.1.0, 0.8, 1.7, 15.0.2, 12.0.5, 13.0.1, 1.8.1, 1.11.6, 15.0.1, 12.0.4, 12.1.1, 13.0.2, 1.11.4, 1.10, 14.0.4, 14.0.6, 1.4.1, 1.4, 1.5.2, 12.0.2, 12.0.1, 14.0.3, 14.0.2, 1.11.1, 1.7.1.2, 15.0.0, 12.0.4, 1.6.4, 1.11.2, 1.5) No distributions matching the version for real-package==versionplease EOS ) Puppet::Util::Execution.expects(:execpipe).with(["/fake/bin/pip", "install", "real_package==versionplease"]).yields(p).once @resource[:name] = "real_package" latest = @provider.latest expect(latest).to eq('15.0.2') end end end context "install" do before do @resource[:name] = "fake_package" @url = "git+https://example.com/fake_package.git" end it "should install" do @resource[:ensure] = :installed @resource[:source] = nil @provider.expects(:lazy_pip). with("install", '-q', "fake_package") @provider.install end it "omits the -e flag (GH-1256)" do # The -e flag makes the provider non-idempotent @resource[:ensure] = :installed @resource[:source] = @url @provider.expects(:lazy_pip).with() do |*args| not args.include?("-e") end @provider.install end it "should install from SCM" do @resource[:ensure] = :installed @resource[:source] = @url @provider.expects(:lazy_pip). with("install", '-q', "#{@url}#egg=fake_package") @provider.install end it "should install a particular SCM revision" do @resource[:ensure] = "0123456" @resource[:source] = @url @provider.expects(:lazy_pip). with("install", "-q", "#{@url}@0123456#egg=fake_package") @provider.install end it "should install a particular version" do @resource[:ensure] = "0.0.0" @resource[:source] = nil @provider.expects(:lazy_pip).with("install", "-q", "fake_package==0.0.0") @provider.install end it "should upgrade" do @resource[:ensure] = :latest @resource[:source] = nil @provider.expects(:lazy_pip). with("install", "-q", "--upgrade", "fake_package") @provider.install end it "should handle install options" do @resource[:ensure] = :installed @resource[:source] = nil @resource[:install_options] = [{"--timeout" => "10"}, "--no-index"] @provider.expects(:lazy_pip). with("install", "-q", "--timeout=10", "--no-index", "fake_package") @provider.install end end context "uninstall" do it "should uninstall" do @resource[:name] = "fake_package" @provider.expects(:lazy_pip). with('uninstall', '-y', '-q', 'fake_package') @provider.uninstall end end context "update" do it "should just call install" do @provider.expects(:install).returns(nil) @provider.update end end context "pip_version" do it "should return nil on missing pip" do described_class.stubs(:pip_cmd).returns(nil) expect(described_class.pip_version).to eq(nil) end it "should look up version if pip is present" do described_class.stubs(:pip_cmd).returns('/fake/bin/pip') p = stub("process") p.expects(:collect).yields('pip 8.0.2 from /usr/local/lib/python2.7/dist-packages (python 2.7)') described_class.expects(:execpipe).with(['/fake/bin/pip', '--version']).yields(p) expect(described_class.pip_version).to eq('8.0.2') end end context "lazy_pip" do after(:each) do Puppet::Type::Package::ProviderPip.instance_variable_set(:@confine_collection, nil) end it "should succeed if pip is present" do @provider.stubs(:pip).returns(nil) @provider.method(:lazy_pip).call "freeze" end osfamilies.each do |osfamily, pip_cmds| pip_cmds.each do |pip_cmd| it "should retry on #{osfamily} systems if #{pip_cmd} has not yet been found" do Puppet.features.stubs(:microsoft_windows?).returns (osfamily == 'windows') @provider.expects(:pip).twice.with('freeze').raises(NoMethodError).then.returns(nil) pip_cmds.each do |cmd| unless cmd == pip_cmd @provider.expects(:which).with(cmd).returns(nil) end end @provider.expects(:which).with(pip_cmd).returns("/fake/bin/#{pip_cmd}") @provider.method(:lazy_pip).call "freeze" end end it "should fail on #{osfamily} systems if #{pip_cmds.join(' and ')} are missing" do Puppet.features.stubs(:microsoft_windows?).returns (osfamily == 'windows') @provider.expects(:pip).with('freeze').raises(NoMethodError) pip_cmds.each do |pip_cmd| @provider.expects(:which).with(pip_cmd).returns(nil) end expect { @provider.method(:lazy_pip).call("freeze") }.to raise_error(NoMethodError) end it "should output a useful error message on #{osfamily} systems if #{pip_cmds.join(' and ')} are missing" do Puppet.features.stubs(:microsoft_windows?).returns (osfamily == 'windows') @provider.expects(:pip).with('freeze').raises(NoMethodError) pip_cmds.each do |pip_cmd| @provider.expects(:which).with(pip_cmd).returns(nil) end expect { @provider.method(:lazy_pip).call("freeze") }. to raise_error(NoMethodError, "Could not locate command #{pip_cmds.join(' and ')}.") end end end end puppet-5.5.10/spec/unit/provider/package/pkg_spec.rb0000644005276200011600000005005313417161722022274 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:package).provider(:pkg) do let (:resource) { Puppet::Resource.new(:package, 'dummy', :parameters => {:name => 'dummy', :ensure => :latest}) } let (:provider) { described_class.new(resource) } if Puppet.features.microsoft_windows? # Get a pid for $CHILD_STATUS to latch on to command = "cmd.exe /c \"exit 0\"" Puppet::Util::Execution.execute(command, {:failonfail => false}) else Puppet::Util::Execution.execute('exit 0', {:failonfail => false}) end before :each do described_class.stubs(:command).with(:pkg).returns('/bin/pkg') end def self.it_should_respond_to(*actions) actions.each do |action| it "should respond to :#{action}" do expect(provider).to respond_to(action) end end end it_should_respond_to :install, :uninstall, :update, :query, :latest context 'default' do [ 10 ].each do |ver| it "should not be the default provider on Solaris #{ver}" do Facter.stubs(:value).with(:osfamily).returns(:Solaris) Facter.stubs(:value).with(:kernelrelease).returns("5.#{ver}") Facter.stubs(:value).with(:operatingsystem).returns(:Solaris) Facter.stubs(:value).with(:operatingsystemmajrelease).returns("#{ver}") expect(described_class).to_not be_default end end [ 11, 12 ].each do |ver| it "should be the default provider on Solaris #{ver}" do Facter.stubs(:value).with(:osfamily).returns(:Solaris) Facter.stubs(:value).with(:kernelrelease).returns("5.#{ver}") Facter.stubs(:value).with(:operatingsystem).returns(:Solaris) Facter.stubs(:value).with(:operatingsystemmajrelease).returns("#{ver}") expect(described_class).to be_default end end end it "should be versionable" do expect(described_class).to be_versionable end describe "#methods" do context ":pkg_state" do it "should raise error on unknown values" do expect { expect(described_class.pkg_state('extra')).to }.to raise_error(ArgumentError, /Unknown format/) end ['known', 'installed'].each do |k| it "should return known values" do expect(described_class.pkg_state(k)).to eq({:status => k}) end end end context ":ifo_flag" do it "should raise error on unknown values" do expect { expect(described_class.ifo_flag('x--')).to }.to raise_error(ArgumentError, /Unknown format/) end {'i--' => 'installed', '---'=> 'known'}.each do |k, v| it "should return known values" do expect(described_class.ifo_flag(k)).to eq({:status => v}) end end end context ":parse_line" do it "should raise error on unknown values" do expect { expect(described_class.parse_line('pkg (mypkg) 1.2.3.4 i-- zzz')).to }.to raise_error(ArgumentError, /Unknown line format/) end { 'pkg://omnios/SUNWcs@0.5.11,5.11-0.151006:20130506T161045Z i--' => {:name => 'SUNWcs', :ensure => '0.5.11,5.11-0.151006:20130506T161045Z', :status => 'installed', :provider => :pkg, :publisher => 'omnios'}, 'pkg://omnios/incorporation/jeos/illumos-gate@11,5.11-0.151006:20130506T183443Z if-' => {:name => 'incorporation/jeos/illumos-gate', :ensure => 'held', :status => 'installed', :provider => :pkg, :publisher => 'omnios'}, 'pkg://solaris/SUNWcs@0.5.11,5.11-0.151.0.1:20101105T001108Z installed -----' => {:name => 'SUNWcs', :ensure => '0.5.11,5.11-0.151.0.1:20101105T001108Z', :status => 'installed', :provider => :pkg, :publisher => 'solaris'}, }.each do |k, v| it "[#{k}] should correctly parse" do expect(described_class.parse_line(k)).to eq(v) end end end context ":latest" do before do described_class.expects(:pkg).with(:refresh) end it "should work correctly for ensure latest on solaris 11 (UFOXI) when there are no further packages to install" do described_class.expects(:pkg).with(:list,'-Hvn','dummy').returns File.read(my_fixture('dummy_solaris11.installed')) expect(provider.latest).to eq('1.0.6,5.11-0.175.0.0.0.2.537:20131230T130000Z') end it "should work correctly for ensure latest on solaris 11 in the presence of a certificate expiration warning" do described_class.expects(:pkg).with(:list,'-Hvn','dummy').returns File.read(my_fixture('dummy_solaris11.certificate_warning')) expect(provider.latest).to eq("1.0.6-0.175.0.0.0.2.537") end it "should work correctly for ensure latest on solaris 11(known UFOXI)" do Puppet::Util::Execution.expects(:execute) .with(['/bin/pkg', 'update', '-n', 'dummy'], {:failonfail => false, :combine => true}) .returns(Puppet::Util::Execution::ProcessOutput.new('', 0)) $CHILD_STATUS.stubs(:exitstatus).returns 0 described_class.expects(:pkg).with(:list,'-Hvn','dummy').returns File.read(my_fixture('dummy_solaris11.known')) expect(provider.latest).to eq('1.0.6,5.11-0.175.0.0.0.2.537:20131230T130000Z') end it "should work correctly for ensure latest on solaris 11 (IFO)" do described_class.expects(:pkg).with(:list,'-Hvn','dummy').returns File.read(my_fixture('dummy_solaris11.ifo.installed')) expect(provider.latest).to eq('1.0.6,5.11-0.175.0.0.0.2.537:20131230T130000Z') end it "should work correctly for ensure latest on solaris 11(known IFO)" do Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'update', '-n', 'dummy'], {:failonfail => false, :combine => true}).returns '' $CHILD_STATUS.stubs(:exitstatus).returns 0 described_class.expects(:pkg).with(:list,'-Hvn','dummy').returns File.read(my_fixture('dummy_solaris11.ifo.known')) expect(provider.latest).to eq('1.0.6,5.11-0.175.0.0.0.2.537:20131230T130000Z') end it "issues a warning when the certificate has expired" do warning = "Certificate '/var/pkg/ssl/871b4ed0ade09926e6adf95f86bf17535f987684' for publisher 'solarisstudio', needed to access 'https://pkg.oracle.com/solarisstudio/release/', will expire in '29' days." Puppet.expects(:warning).with("pkg warning: #{warning}") described_class.expects(:pkg).with(:list,'-Hvn','dummy').returns File.read(my_fixture('dummy_solaris11.certificate_warning')) provider.latest end it "doesn't issue a warning when the certificate hasn't expired" do Puppet.expects(:warning).with(/pkg warning/).never described_class.expects(:pkg).with(:list,'-Hvn','dummy').returns File.read(my_fixture('dummy_solaris11.installed')) provider.latest end end context ":instances" do it "should correctly parse lines on solaris 11" do described_class.expects(:pkg).with(:list, '-Hv').returns File.read(my_fixture('solaris11')) described_class.expects(:warning).never instances = described_class.instances.map { |p| {:name => p.get(:name), :ensure => p.get(:ensure) }} expect(instances.size).to eq(2) expect(instances[0]).to eq({:name => 'dummy/dummy', :ensure => '3.0,5.11-0.175.0.0.0.2.537:20131230T130000Z'}) expect(instances[1]).to eq({:name => 'dummy/dummy2', :ensure => '1.8.1.2-0.175.0.0.0.2.537:20131230T130000Z'}) end it "should fail on incorrect lines" do fake_output = File.read(my_fixture('incomplete')) described_class.expects(:pkg).with(:list,'-Hv').returns fake_output expect { described_class.instances }.to raise_error(ArgumentError, /Unknown line format pkg/) end it "should fail on unknown package status" do described_class.expects(:pkg).with(:list,'-Hv').returns File.read(my_fixture('unknown_status')) expect { described_class.instances }.to raise_error(ArgumentError, /Unknown format pkg/) end end context ":query" do context "on solaris 10" do it "should find the package" do Puppet::Util::Execution.expects(:execute) .with(['/bin/pkg', 'list', '-Hv', 'dummy'], {:failonfail => false, :combine => true}) .returns(Puppet::Util::Execution::ProcessOutput.new(File.read(my_fixture('dummy_solaris10')), 0)) $CHILD_STATUS.stubs(:exitstatus).returns 0 expect(provider.query).to eq({ :name => 'dummy', :ensure => '2.5.5,5.10-0.111:20131230T130000Z', :publisher => 'solaris', :status => 'installed', :provider => :pkg, }) end it "should return :absent when the package is not found" do Puppet::Util::Execution.expects(:execute) .with(['/bin/pkg', 'list', '-Hv', 'dummy'], {:failonfail => false, :combine => true}) .returns(Puppet::Util::Execution::ProcessOutput.new('', 0)) $CHILD_STATUS.stubs(:exitstatus).returns 1 expect(provider.query).to eq({:ensure => :absent, :name => "dummy"}) end end context "on solaris 11" do it "should find the package" do $CHILD_STATUS.stubs(:exitstatus).returns 0 Puppet::Util::Execution.expects(:execute) .with(['/bin/pkg', 'list', '-Hv', 'dummy'], {:failonfail => false, :combine => true}) .returns(Puppet::Util::Execution::ProcessOutput.new(File.read(my_fixture('dummy_solaris11.installed')), 0)) expect(provider.query).to eq({ :name => 'dummy', :status => 'installed', :ensure => '1.0.6,5.11-0.175.0.0.0.2.537:20131230T130000Z', :publisher => 'solaris', :provider => :pkg, }) end it "should return :absent when the package is not found" do Puppet::Util::Execution .expects(:execute) .with(['/bin/pkg', 'list', '-Hv', 'dummy'], {:failonfail => false, :combine => true}) .returns(Puppet::Util::Execution::ProcessOutput.new('', 0)) $CHILD_STATUS.stubs(:exitstatus).returns 1 expect(provider.query).to eq({:ensure => :absent, :name => "dummy"}) end end it "should return fail when the packageline cannot be parsed" do Puppet::Util::Execution.expects(:execute) .with(['/bin/pkg', 'list', '-Hv', 'dummy'], {:failonfail => false, :combine => true}) .returns(Puppet::Util::Execution::ProcessOutput.new(File.read(my_fixture('incomplete')), 0)) $CHILD_STATUS.stubs(:exitstatus).returns 0 expect { provider.query }.to raise_error(ArgumentError, /Unknown line format/) end end context ":install" do [ { :osrel => '11.0', :flags => ['--accept'] }, { :osrel => '11.2', :flags => ['--accept', '--sync-actuators-timeout', '900'] }, ].each do |hash| context "with :operatingsystemrelease #{hash[:osrel]}" do before :each do Facter.stubs(:value).with(:operatingsystemrelease).returns hash[:osrel] end it "should accept all licenses" do provider.expects(:query).with().returns({:ensure => :absent}) Puppet::Util::Execution.expects(:execute) .with(['/bin/pkg', 'install', *hash[:flags], 'dummy'], {:failonfail => false, :combine => true}) .returns(Puppet::Util::Execution::ProcessOutput.new('', 0)) Puppet::Util::Execution.expects(:execute) .with(['/bin/pkg', 'unfreeze', 'dummy'], {:failonfail => false, :combine => true}) .returns(Puppet::Util::Execution::ProcessOutput.new('', 0)) $CHILD_STATUS.stubs(:exitstatus).returns 0 provider.install end it "should install specific version(1)" do # Should install also check if the version installed is the same version we are asked to install? or should we rely on puppet for that? resource[:ensure] = '0.0.7,5.11-0.151006:20131230T130000Z' $CHILD_STATUS.stubs(:exitstatus).returns 0 Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'unfreeze', 'dummy'], {:failonfail => false, :combine => true}) Puppet::Util::Execution.expects(:execute) .with(['/bin/pkg', 'list', '-Hv', 'dummy'], {:failonfail => false, :combine => true}) .returns(Puppet::Util::Execution::ProcessOutput.new('pkg://foo/dummy@0.0.6,5.11-0.151006:20131230T130000Z installed -----', 0)) Puppet::Util::Execution.expects(:execute) .with(['/bin/pkg', 'update', *hash[:flags], 'dummy@0.0.7,5.11-0.151006:20131230T130000Z'], {:failonfail => false, :combine => true}) .returns(Puppet::Util::Execution::ProcessOutput.new('', 0)) provider.install end it "should install specific version(2)" do resource[:ensure] = '0.0.8' Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'unfreeze', 'dummy'], {:failonfail => false, :combine => true}) Puppet::Util::Execution.expects(:execute) .with(['/bin/pkg', 'list', '-Hv', 'dummy'], {:failonfail => false, :combine => true}) .returns(Puppet::Util::Execution::ProcessOutput.new('pkg://foo/dummy@0.0.7,5.11-0.151006:20131230T130000Z installed -----', 0)) Puppet::Util::Execution.expects(:execute) .with(['/bin/pkg', 'update', *hash[:flags], 'dummy@0.0.8'], {:failonfail => false, :combine => true}) .returns(Puppet::Util::Execution::ProcessOutput.new('', 0)) $CHILD_STATUS.stubs(:exitstatus).returns 0 provider.install end it "should downgrade to specific version" do resource[:ensure] = '0.0.7' provider.expects(:query).with().returns({:ensure => '0.0.8,5.11-0.151106:20131230T130000Z'}) $CHILD_STATUS.stubs(:exitstatus).returns 0 Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'unfreeze', 'dummy'], {:failonfail => false, :combine => true}) Puppet::Util::Execution.expects(:execute) .with(['/bin/pkg', 'update', *hash[:flags], 'dummy@0.0.7'], {:failonfail => false, :combine => true}) .returns(Puppet::Util::Execution::ProcessOutput.new('', 0)) provider.install end it "should install any if version is not specified" do resource[:ensure] = :present provider.expects(:query).with().returns({:ensure => :absent}) Puppet::Util::Execution.expects(:execute) .with(['/bin/pkg', 'install', *hash[:flags], 'dummy'], {:failonfail => false, :combine => true}) .returns(Puppet::Util::Execution::ProcessOutput.new('', 0)) Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'unfreeze', 'dummy'], {:failonfail => false, :combine => true}) $CHILD_STATUS.stubs(:exitstatus).returns 0 provider.install end it "should install if no version was previously installed, and a specific version was requested" do resource[:ensure] = '0.0.7' provider.expects(:query).with().returns({:ensure => :absent}) Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'unfreeze', 'dummy'], {:failonfail => false, :combine => true}) Puppet::Util::Execution.expects(:execute) .with(['/bin/pkg', 'install', *hash[:flags], 'dummy@0.0.7'], {:failonfail => false, :combine => true}) .returns(Puppet::Util::Execution::ProcessOutput.new('', 0)) $CHILD_STATUS.stubs(:exitstatus).returns 0 provider.install end it "installs the latest matching version when given implicit version, and none are installed" do resource[:ensure] = '1.0-0.151006' is = :absent provider.expects(:query).with().returns({:ensure => is}) described_class.expects(:pkg) .with(:list, '-Hvfa', 'dummy@1.0-0.151006') .returns(Puppet::Util::Execution::ProcessOutput.new(File.read(my_fixture('dummy_implicit_version')), 0)) Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'install', '-n', 'dummy@1.0,5.11-0.151006:20140220T084443Z'], {:failonfail => false, :combine => true}) provider.expects(:unhold).with() Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'install', *hash[:flags], 'dummy@1.0,5.11-0.151006:20140220T084443Z'], {:failonfail => false, :combine => true}) $CHILD_STATUS.stubs(:exitstatus).returns 0 provider.insync?(is) provider.install end it "updates to the latest matching version when given implicit version" do resource[:ensure] = '1.0-0.151006' is = '1.0,5.11-0.151006:20140219T191204Z' provider.expects(:query).with().returns({:ensure => is}) described_class.expects(:pkg).with(:list, '-Hvfa', 'dummy@1.0-0.151006').returns File.read(my_fixture('dummy_implicit_version')) Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'update', '-n', 'dummy@1.0,5.11-0.151006:20140220T084443Z'], {:failonfail => false, :combine => true}) provider.expects(:unhold).with() Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'update', *hash[:flags], 'dummy@1.0,5.11-0.151006:20140220T084443Z'], {:failonfail => false, :combine => true}) $CHILD_STATUS.stubs(:exitstatus).returns 0 provider.insync?(is) provider.install end it "issues a warning when an implicit version number is used, and in sync" do resource[:ensure] = '1.0-0.151006' is = '1.0,5.11-0.151006:20140220T084443Z' provider.expects(:warning).with("Implicit version 1.0-0.151006 has 3 possible matches") described_class.expects(:pkg) .with(:list, '-Hvfa', 'dummy@1.0-0.151006') .returns(Puppet::Util::Execution::ProcessOutput.new(File.read(my_fixture('dummy_implicit_version')), 0)) Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'update', '-n', 'dummy@1.0,5.11-0.151006:20140220T084443Z'], {:failonfail => false, :combine => true}) $CHILD_STATUS.stubs(:exitstatus).returns 4 provider.insync?(is) end it "issues a warning when choosing a version number for an implicit match" do resource[:ensure] = '1.0-0.151006' is = :absent provider.expects(:warning).with("Implicit version 1.0-0.151006 has 3 possible matches") provider.expects(:warning).with("Selecting version '1.0,5.11-0.151006:20140220T084443Z' for implicit '1.0-0.151006'") described_class.expects(:pkg).with(:list, '-Hvfa', 'dummy@1.0-0.151006').returns File.read(my_fixture('dummy_implicit_version')) Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'install', '-n', 'dummy@1.0,5.11-0.151006:20140220T084443Z'], {:failonfail => false, :combine => true}) $CHILD_STATUS.stubs(:exitstatus).returns 0 provider.insync?(is) end end end end context ":update" do it "should not raise error if not necessary" do provider.expects(:install).with(true).returns({:exit => 0}) provider.update end it "should not raise error if not necessary (2)" do provider.expects(:install).with(true).returns({:exit => 4}) provider.update end it "should raise error if necessary" do provider.expects(:install).with(true).returns({:exit => 1}) expect { provider.update }.to raise_error(Puppet::Error, /Unable to update/) end end context ":uninstall" do it "should support current pkg version" do described_class.expects(:pkg).with(:version).returns('630e1ffc7a19') described_class.expects(:pkg).with([:uninstall, resource[:name]]) provider.uninstall end it "should support original pkg commands" do described_class.expects(:pkg).with(:version).returns('052adf36c3f4') described_class.expects(:pkg).with([:uninstall, '-r', resource[:name]]) provider.uninstall end end end end puppet-5.5.10/spec/unit/provider/package/pkgng_spec.rb0000644005276200011600000001350213417161722022617 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/provider/package/pkgng' describe Puppet::Type.type(:package).provider(:pkgng) do let(:name) { 'bash' } let(:installed_name) { 'zsh' } let(:pkgng) { 'pkgng' } let(:resource) do # When bash is not present Puppet::Type.type(:package).new(:name => name, :provider => pkgng) end let(:installed_resource) do # When zsh is present Puppet::Type.type(:package).new(:name => installed_name, :provider => pkgng) end let(:latest_resource) do # When curl is installed but not the latest Puppet::Type.type(:package).new(:name => 'ftp/curl', :provider => pkgng, :ensure => latest) end let (:provider) { resource.provider } let (:installed_provider) { installed_resource.provider } def run_in_catalog(*resources) catalog = Puppet::Resource::Catalog.new catalog.host_config = false resources.each do |resource| #resource.expects(:err).never catalog.add_resource(resource) end catalog.apply end before do described_class.stubs(:command).with(:pkg) { '/usr/local/sbin/pkg' } info = File.read(my_fixture('pkg.info')) described_class.stubs(:get_query).returns(info) version_list = File.read(my_fixture('pkg.version')) described_class.stubs(:get_version_list).returns(version_list) end context "#instances" do it "should return the empty set if no packages are listed" do described_class.stubs(:get_query).returns('') described_class.stubs(:get_version_list).returns('') expect(described_class.instances).to be_empty end it "should return all packages when invoked" do expect(described_class.instances.map(&:name).sort).to eq( %w{ca_root_nss curl nmap pkg gnupg mcollective zsh tac_plus}.sort) end it "should set latest to current version when no upgrade available" do nmap = described_class.instances.find {|i| i.properties[:origin] == 'security/nmap' } expect(nmap.properties[:version]).to eq(nmap.properties[:latest]) end it "should return an empty array when pkg calls raise an exception" do described_class.stubs(:get_query).raises(Puppet::ExecutionFailure, 'An error occurred.') expect(described_class.instances).to eq([]) end describe "version" do it "should retrieve the correct version of the current package" do zsh = described_class.instances.find {|i| i.properties[:origin] == 'shells/zsh' } expect( zsh.properties[:version]).to eq('5.0.2_1') end end end context "#install" do it "should call pkg with the specified package version given an origin for package name" do resource = Puppet::Type.type(:package).new( :name => 'ftp/curl', :provider => :pkgng, :ensure => '7.33.1' ) resource.provider.expects(:pkg) do |arg| arg.should include('curl-7.33.1') end resource.provider.install end it "should call pkg with the specified package version" do resource = Puppet::Type.type(:package).new( :name => 'curl', :provider => :pkgng, :ensure => '7.33.1' ) resource.provider.expects(:pkg) do |arg| arg.should include('curl-7.33.1') end resource.provider.install end it "should call pkg with the specified package repo" do resource = Puppet::Type.type(:package).new( :name => 'curl', :provider => :pkgng, :source => 'urn:freebsd:repo:FreeBSD' ) resource.provider.expects(:pkg) do |arg| arg.should include('FreeBSD') end resource.provider.install end end context "#prefetch" do it "should fail gracefully when " do described_class.stubs(:instances).returns([]) expect{ described_class.prefetch({}) }.to_not raise_error end end context "#query" do it "should return the installed version if present" do described_class.prefetch({installed_name => installed_resource}) expect(installed_provider.query).to eq({:version=>'5.0.2_1'}) end it "should return nil if not present" do fixture = File.read(my_fixture('pkg.query_absent')) described_class.stubs(:get_resource_info).with('bash').returns(fixture) expect(provider.query).to equal(nil) end end describe "latest" do it "should retrieve the correct version of the latest package" do described_class.prefetch( { installed_name => installed_resource }) expect(installed_provider.latest).not_to be_nil end it "should set latest to newer package version when available" do instances = described_class.instances curl = instances.find {|i| i.properties[:origin] == 'ftp/curl' } expect(curl.properties[:latest]).to eq('7.33.0_2') end it "should call update to upgrade the version" do resource = Puppet::Type.type(:package).new( :name => 'ftp/curl', :provider => pkgng, :ensure => :latest ) resource.provider.expects(:update) resource.property(:ensure).sync end end describe "get_latest_version" do it "should rereturn nil when the current package is the latest" do version_list = File.read(my_fixture('pkg.version')) nmap_latest_version = described_class.get_latest_version('security/nmap', version_list) expect(nmap_latest_version).to be_nil end it "should match the package name exactly" do version_list = File.read(my_fixture('pkg.version')) bash_comp_latest_version = described_class.get_latest_version('shells/bash-completion', version_list) expect(bash_comp_latest_version).to eq('2.1_3') end end describe "confine" do context "on FreeBSD" do it "should be the default provider" do Facter.expects(:value).with(:operatingsystem).at_least_once.returns :freebsd expect(described_class).to be_default end end end end puppet-5.5.10/spec/unit/provider/package/portage_spec.rb0000644005276200011600000002042313417161722023152 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' provider = Puppet::Type.type(:package).provider(:portage) describe provider do before do packagename="sl" @resource = stub('resource', :should => true) @resource.stubs(:[]).with(:name).returns(packagename) @resource.stubs(:[]).with(:install_options).returns(['--foo', '--bar']) @resource.stubs(:[]).with(:uninstall_options).returns(['--foo', { '--bar' => 'baz', '--baz' => 'foo' }]) unslotted_packagename = "dev-lang/ruby" @unslotted_resource = stub('resource', :should => true) @unslotted_resource.stubs(:should).with(:ensure).returns :latest @unslotted_resource.stubs(:[]).with(:name).returns(unslotted_packagename) @unslotted_resource.stubs(:[]).with(:install_options).returns([]) slotted_packagename = "dev-lang/ruby:2.1" @slotted_resource = stub('resource', :should => true) @slotted_resource.stubs(:[]).with(:name).returns(slotted_packagename) @slotted_resource.stubs(:[]).with(:install_options).returns(['--foo', { '--bar' => 'baz', '--baz' => 'foo' }]) versioned_packagename = "dev-lang/ruby-1.9.3" @versioned_resource = stub('resource', :should => true) @versioned_resource.stubs(:[]).with(:name).returns(versioned_packagename) @versioned_resource.stubs(:[]).with(:uninstall_options).returns([]) versioned_slotted_packagename = "=dev-lang/ruby-1.9.3:1.9" @versioned_slotted_resource = stub('resource', :should => true) @versioned_slotted_resource.stubs(:[]).with(:name).returns(versioned_slotted_packagename) @versioned_slotted_resource.stubs(:[]).with(:uninstall_options).returns([]) set_packagename = "@system" @set_resource = stub('resource', :should => true) @set_resource.stubs(:[]).with(:name).returns(set_packagename) @set_resource.stubs(:[]).with(:install_options).returns([]) package_sets = "system\nworld\n" @provider = provider.new(@resource) @provider.stubs(:qatom).returns({:category=>nil, :pn=>"sl", :pv=>nil, :pr=>nil, :slot=>nil, :pfx=>nil, :sfx=>nil}) @provider.class.stubs(:emerge).with('--list-sets').returns(package_sets) @unslotted_provider = provider.new(@unslotted_resource) @unslotted_provider.stubs(:qatom).returns({:category=>"dev-lang", :pn=>"ruby", :pv=>nil, :pr=>nil, :slot=>nil, :pfx=>nil, :sfx=>nil}) @unslotted_provider.class.stubs(:emerge).with('--list-sets').returns(package_sets) @slotted_provider = provider.new(@slotted_resource) @slotted_provider.stubs(:qatom).returns({:category=>"dev-lang", :pn=>"ruby", :pv=>nil, :pr=>nil, :slot=>":2.1", :pfx=>nil, :sfx=>nil}) @slotted_provider.class.stubs(:emerge).with('--list-sets').returns(package_sets) @versioned_provider = provider.new(@versioned_resource) @versioned_provider.stubs(:qatom).returns({:category=>"dev-lang", :pn=>"ruby", :pv=>"1.9.3", :pr=>nil, :slot=>nil, :pfx=>nil, :sfx=>nil}) @versioned_provider.class.stubs(:emerge).with('--list-sets').returns(package_sets) @versioned_slotted_provider = provider.new(@versioned_slotted_resource) @versioned_slotted_provider.stubs(:qatom).returns({:category=>"dev-lang", :pn=>"ruby", :pv=>"1.9.3", :pr=>nil, :slot=>":1.9", :pfx=>"=", :sfx=>nil}) @versioned_slotted_provider.class.stubs(:emerge).with('--list-sets').returns(package_sets) @set_provider = provider.new(@set_resource) @set_provider.stubs(:qatom).returns({:category=>nil, :pn=>"@system", :pv=>nil, :pr=>nil, :slot=>nil, :pfx=>nil, :sfx=>nil}) @set_provider.class.stubs(:emerge).with('--list-sets').returns(package_sets) portage = stub(:executable => "foo",:execute => true) Puppet::Provider::CommandDefiner.stubs(:define).returns(portage) @nomatch_result = "" @match_result = "app-misc sl [] [5.02] [] [] [5.02] [5.02:0] http://www.tkl.iis.u-tokyo.ac.jp/~toyoda/index_e.html https://github.com/mtoyoda/sl/ sophisticated graphical program which corrects your miss typing\n" @slot_match_result = "dev-lang ruby [2.1.8] [2.1.9] [2.1.8:2.1] [2.1.8] [2.1.9,,,,,,,] [2.1.9:2.1] http://www.ruby-lang.org/ An object-oriented scripting language\n" end it "is versionable" do expect(provider).to be_versionable end it "is reinstallable" do expect(provider).to be_reinstallable end it 'should support string install options' do @provider.expects(:emerge).with('--foo', '--bar', @resource[:name]) @provider.install end it 'should support updating' do @unslotted_provider.expects(:emerge).with('--update', @unslotted_resource[:name]) @unslotted_provider.install end it 'should support hash install options' do @slotted_provider.expects(:emerge).with('--foo', '--bar=baz', '--baz=foo', @slotted_resource[:name]) @slotted_provider.install end it 'should support hash uninstall options' do @provider.expects(:emerge).with('--rage-clean', '--foo', '--bar=baz', '--baz=foo', @resource[:name]) @provider.uninstall end it 'should support uninstall of specific version' do @versioned_provider.expects(:emerge).with('--rage-clean', @versioned_resource[:name]) @versioned_provider.uninstall end it 'should support uninstall of specific version and slot' do @versioned_slotted_provider.expects(:emerge).with('--rage-clean', @versioned_slotted_resource[:name]) @versioned_slotted_provider.uninstall end it "uses :emerge to install packages" do @provider.expects(:emerge) @provider.install end it "uses query to find the latest package" do @provider.expects(:query).returns({:versions_available => "myversion"}) @provider.latest end it "uses eix to search the lastest version of a package" do @provider.stubs(:update_eix) @provider.expects(:eix).returns(StringIO.new(@match_result)) @provider.query end it "allows to emerge package sets" do @set_provider.expects(:emerge).with(@set_resource[:name]) @set_provider.install end it "allows to emerge and update package sets" do @set_resource.stubs(:should).with(:ensure).returns :latest @set_provider.expects(:emerge).with('--update', @set_resource[:name]) @set_provider.install end it "eix arguments must not include --stable" do expect(@provider.class.eix_search_arguments).not_to include("--stable") end it "eix arguments must not include --exact" do expect(@provider.class.eix_search_arguments).not_to include("--exact") end it "query uses default arguments" do @provider.stubs(:update_eix) @provider.expects(:eix).returns(StringIO.new(@match_result)) @provider.class.expects(:eix_search_arguments).returns([]) @provider.query end it "can handle search output with empty square brackets" do @provider.stubs(:update_eix) @provider.expects(:eix).returns(StringIO.new(@match_result)) expect(@provider.query[:name]).to eq("sl") end it "can provide the package name without slot" do expect(@unslotted_provider.qatom[:slot]).to be_nil end it "can extract the slot from the package name" do expect(@slotted_provider.qatom[:slot]).to eq(':2.1') end it "returns nil for as the slot when no slot is specified" do expect(@provider.qatom[:slot]).to be_nil end it "provides correct package atoms for unslotted packages" do expect(@versioned_provider.qatom[:pv]).to eq('1.9.3') end it "provides correct package atoms for slotted packages" do expect(@versioned_slotted_provider.qatom[:pfx]).to eq('=') expect(@versioned_slotted_provider.qatom[:category]).to eq('dev-lang') expect(@versioned_slotted_provider.qatom[:pn]).to eq('ruby') expect(@versioned_slotted_provider.qatom[:pv]).to eq('1.9.3') expect(@versioned_slotted_provider.qatom[:slot]).to eq(':1.9') end it "can handle search output with slots for unslotted packages" do @unslotted_provider.stubs(:update_eix) @unslotted_provider.expects(:eix).returns(StringIO.new(@slot_match_result)) result = @unslotted_provider.query expect(result[:name]).to eq('ruby') expect(result[:ensure]).to eq('2.1.8') expect(result[:version_available]).to eq('2.1.9') end it "can handle search output with slots" do @slotted_provider.stubs(:update_eix) @slotted_provider.expects(:eix).returns(StringIO.new(@slot_match_result)) result = @slotted_provider.query expect(result[:name]).to eq('ruby') expect(result[:ensure]).to eq('2.1.8') expect(result[:version_available]).to eq('2.1.9') end end puppet-5.5.10/spec/unit/provider/package/puppet_gem_spec.rb0000644005276200011600000000370213417161722023657 0ustar jenkinsjenkinsrequire 'spec_helper' describe Puppet::Type.type(:package).provider(:puppet_gem) do let(:resource) do Puppet::Type.type(:package).new( :name => 'myresource', :ensure => :installed ) end let(:provider) do provider = described_class.new provider.resource = resource provider end if Puppet.features.microsoft_windows? let(:puppet_gem) { 'gem' } else let(:puppet_gem) { '/opt/puppetlabs/puppet/bin/gem' } end before :each do resource.provider = provider end context "when installing" do it "should use the path to the gem" do described_class.expects(:which).with(puppet_gem).returns(puppet_gem) provider.expects(:execute).with { |args| args[0] == puppet_gem }.returns '' provider.install end it "should not append install_options by default" do provider.expects(:execute).with { |args| args.length == 5 }.returns '' provider.install end it "should allow setting an install_options parameter" do resource[:install_options] = [ '--force', {'--bindir' => '/usr/bin' } ] provider.expects(:execute).with { |args| args[2] == '--force' && args[3] == '--bindir=/usr/bin' }.returns '' provider.install end end context "when uninstalling" do it "should use the path to the gem" do described_class.expects(:which).with(puppet_gem).returns(puppet_gem) provider.expects(:execute).with { |args| args[0] == puppet_gem }.returns '' provider.install end it "should not append uninstall_options by default" do provider.expects(:execute).with { |args| args.length == 5 }.returns '' provider.uninstall end it "should allow setting an uninstall_options parameter" do resource[:uninstall_options] = [ '--force', {'--bindir' => '/usr/bin' } ] provider.expects(:execute).with { |args| args[5] == '--force' && args[6] == '--bindir=/usr/bin' }.returns '' provider.uninstall end end end puppet-5.5.10/spec/unit/provider/package/windows_spec.rb0000644005276200011600000002004713417161722023205 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:package).provider(:windows), :if => Puppet.features.microsoft_windows? do let (:name) { 'mysql-5.1.58-win-x64' } let (:source) { 'E:\Rando\Directory\mysql-5.1.58-win-x64.msi' } let (:resource) { Puppet::Type.type(:package).new(:name => name, :provider => :windows, :source => source) } let (:provider) { resource.provider } let (:execute_options) do {:failonfail => false, :combine => true, :suppress_window => true} end before :each do # make sure we never try to execute anything provider.expects(:execute).never end def expect_execute(command, status) provider.expects(:execute).with(command, execute_options).returns(Puppet::Util::Execution::ProcessOutput.new('',status)) end describe 'provider features' do it { is_expected.to be_installable } it { is_expected.to be_uninstallable } it { is_expected.to be_install_options } it { is_expected.to be_uninstall_options } it { is_expected.to be_versionable } end describe 'on Windows', :if => Puppet.features.microsoft_windows? do it 'should be the default provider' do expect(Puppet::Type.type(:package).defaultprovider).to eq(subject.class) end end context '::instances' do it 'should return an array of provider instances' do pkg1 = stub('pkg1') pkg2 = stub('pkg2') prov1 = stub('prov1', :name => 'pkg1', :version => '1.0.0', :package => pkg1) prov2 = stub('prov2', :name => 'pkg2', :version => nil, :package => pkg2) Puppet::Provider::Package::Windows::Package.expects(:map).multiple_yields([prov1], [prov2]).returns([prov1, prov2]) providers = provider.class.instances expect(providers.count).to eq(2) expect(providers[0].name).to eq('pkg1') expect(providers[0].version).to eq('1.0.0') expect(providers[0].package).to eq(pkg1) expect(providers[1].name).to eq('pkg2') expect(providers[1].version).to be_nil expect(providers[1].package).to eq(pkg2) end it 'should return an empty array if none found' do Puppet::Provider::Package::Windows::Package.expects(:map).returns([]) expect(provider.class.instances).to eq([]) end end context '#query' do it 'should return the hash of the matched packaged' do pkg = mock(:name => 'pkg1', :version => nil) pkg.expects(:match?).returns(true) Puppet::Provider::Package::Windows::Package.expects(:find).yields(pkg) expect(provider.query).to eq({ :name => 'pkg1', :ensure => :installed, :provider => :windows }) end it 'should include the version string when present' do pkg = mock(:name => 'pkg1', :version => '1.0.0') pkg.expects(:match?).returns(true) Puppet::Provider::Package::Windows::Package.expects(:find).yields(pkg) expect(provider.query).to eq({ :name => 'pkg1', :ensure => '1.0.0', :provider => :windows }) end it 'should return nil if no package was found' do Puppet::Provider::Package::Windows::Package.expects(:find) expect(provider.query).to be_nil end end context '#install' do let(:command) { 'blarg.exe /S' } let(:klass) { mock('installer', :install_command => ['blarg.exe', '/S'] ) } let(:execute_options) do {:failonfail => false, :combine => true, :cwd => 'E:\Rando\Directory', :suppress_window => true} end before :each do Puppet::Provider::Package::Windows::Package.expects(:installer_class).returns(klass) end it 'should join the install command and options' do resource[:install_options] = { 'INSTALLDIR' => 'C:\mysql-5.1' } expect_execute("#{command} INSTALLDIR=C:\\mysql-5.1", 0) provider.install end it 'should compact nil install options' do expect_execute(command, 0) provider.install end it 'should not warn if the package install succeeds' do expect_execute(command, 0) provider.expects(:warning).never provider.install end it 'should warn if reboot initiated' do expect_execute(command, 1641) provider.expects(:warning).with('The package installed successfully and the system is rebooting now.') provider.install end it 'should warn if reboot required' do expect_execute(command, 3010) provider.expects(:warning).with('The package installed successfully, but the system must be rebooted.') provider.install end it 'should fail otherwise', :if => Puppet.features.microsoft_windows? do expect_execute(command, 5) expect do provider.install end.to raise_error do |error| expect(error).to be_a(Puppet::Util::Windows::Error) expect(error.code).to eq(5) # ERROR_ACCESS_DENIED end end end context '#uninstall' do let(:command) { 'unblarg.exe /Q' } let(:package) { mock('package', :uninstall_command => ['unblarg.exe', '/Q'] ) } before :each do resource[:ensure] = :absent provider.package = package end it 'should join the uninstall command and options' do resource[:uninstall_options] = { 'INSTALLDIR' => 'C:\mysql-5.1' } expect_execute("#{command} INSTALLDIR=C:\\mysql-5.1", 0) provider.uninstall end it 'should compact nil install options' do expect_execute(command, 0) provider.uninstall end it 'should not warn if the package install succeeds' do expect_execute(command, 0) provider.expects(:warning).never provider.uninstall end it 'should warn if reboot initiated' do expect_execute(command, 1641) provider.expects(:warning).with('The package uninstalled successfully and the system is rebooting now.') provider.uninstall end it 'should warn if reboot required' do expect_execute(command, 3010) provider.expects(:warning).with('The package uninstalled successfully, but the system must be rebooted.') provider.uninstall end it 'should fail otherwise', :if => Puppet.features.microsoft_windows? do expect_execute(command, 5) expect do provider.uninstall end.to raise_error do |error| expect(error).to be_a(Puppet::Util::Windows::Error) expect(error.code).to eq(5) # ERROR_ACCESS_DENIED end end end context '#validate_source' do it 'should fail if the source parameter is empty' do expect do resource[:source] = '' end.to raise_error(Puppet::Error, /The source parameter cannot be empty when using the Windows provider/) end it 'should accept a source' do resource[:source] = source end end context '#install_options' do it 'should return nil by default' do expect(provider.install_options).to be_nil end it 'should return the options' do resource[:install_options] = { 'INSTALLDIR' => 'C:\mysql-here' } expect(provider.install_options).to eq(['INSTALLDIR=C:\mysql-here']) end it 'should only quote if needed' do resource[:install_options] = { 'INSTALLDIR' => 'C:\mysql here' } expect(provider.install_options).to eq(['INSTALLDIR="C:\mysql here"']) end it 'should escape embedded quotes in install_options values with spaces' do resource[:install_options] = { 'INSTALLDIR' => 'C:\mysql "here"' } expect(provider.install_options).to eq(['INSTALLDIR="C:\mysql \"here\""']) end end context '#uninstall_options' do it 'should return nil by default' do expect(provider.uninstall_options).to be_nil end it 'should return the options' do resource[:uninstall_options] = { 'INSTALLDIR' => 'C:\mysql-here' } expect(provider.uninstall_options).to eq(['INSTALLDIR=C:\mysql-here']) end end context '#join_options' do it 'should return nil if there are no options' do expect(provider.join_options(nil)).to be_nil end it 'should sort hash keys' do expect(provider.join_options([{'b' => '2', 'a' => '1', 'c' => '3'}])).to eq(['a=1', 'b=2', 'c=3']) end it 'should return strings and hashes' do expect(provider.join_options([{'a' => '1'}, 'b'])).to eq(['a=1', 'b']) end end end puppet-5.5.10/spec/unit/provider/package/yum_spec.rb0000644005276200011600000001427113417161722022327 0ustar jenkinsjenkinsrequire 'spec_helper' describe Puppet::Type.type(:package).provider(:yum) do include PuppetSpec::Fixtures it_behaves_like 'RHEL package provider', described_class, 'yum' describe "when supplied the source param" do let(:name) { 'baz' } let(:resource) do Puppet::Type.type(:package).new( :name => name, :provider => 'yum', ) end let(:provider) do provider = described_class.new provider.resource = resource provider end before { described_class.stubs(:command).with(:cmd).returns("/usr/bin/yum") } context "when installing" do it "should use the supplied source as the explicit path to a package to install" do resource[:ensure] = :present resource[:source] = "/foo/bar/baz-1.1.0.rpm" provider.expects(:execute).with do |arr| expect(arr[-2..-1]).to eq([:install, "/foo/bar/baz-1.1.0.rpm"]) end provider.install end end context "when ensuring a specific version" do it "should use the suppplied source as the explicit path to the package to update" do # The first query response informs yum provider that package 1.1.0 is # already installed, and the second that it's been upgraded provider.expects(:query).twice.returns({:ensure => "1.1.0"}, {:ensure => "1.2.0"}) resource[:ensure] = "1.2.0" resource[:source] = "http://foo.repo.com/baz-1.2.0.rpm" provider.expects(:execute).with do |arr| expect(arr[-2..-1]).to eq(['update', "http://foo.repo.com/baz-1.2.0.rpm"]) end provider.install end end end context "parsing the output of check-update" do context "with no multiline entries" do let(:check_update) { File.read(my_fixture("yum-check-update-simple.txt")) } let(:output) { described_class.parse_updates(check_update) } it 'creates an entry for each package keyed on the package name' do expect(output['curl']).to eq([{:name => 'curl', :epoch => '0', :version => '7.32.0', :release => '10.fc20', :arch => 'i686'}, {:name => 'curl', :epoch => '0', :version => '7.32.0', :release => '10.fc20', :arch => 'x86_64'}]) expect(output['gawk']).to eq([{:name => 'gawk', :epoch => '0', :version => '4.1.0', :release => '3.fc20', :arch => 'i686'}]) expect(output['dhclient']).to eq([{:name => 'dhclient', :epoch => '12', :version => '4.1.1', :release => '38.P1.fc20', :arch => 'i686'}]) expect(output['selinux-policy']).to eq([{:name => 'selinux-policy', :epoch => '0', :version => '3.12.1', :release => '163.fc20', :arch => 'noarch'}]) end it 'creates an entry for each package keyed on the package name and package architecture' do expect(output['curl.i686']).to eq([{:name => 'curl', :epoch => '0', :version => '7.32.0', :release => '10.fc20', :arch => 'i686'}]) expect(output['curl.x86_64']).to eq([{:name => 'curl', :epoch => '0', :version => '7.32.0', :release => '10.fc20', :arch => 'x86_64'}]) expect(output['gawk.i686']).to eq([{:name => 'gawk', :epoch => '0', :version => '4.1.0', :release => '3.fc20', :arch => 'i686'}]) expect(output['dhclient.i686']).to eq([{:name => 'dhclient', :epoch => '12', :version => '4.1.1', :release => '38.P1.fc20', :arch => 'i686'}]) expect(output['selinux-policy.noarch']).to eq([{:name => 'selinux-policy', :epoch => '0', :version => '3.12.1', :release => '163.fc20', :arch => 'noarch'}]) expect(output['java-1.8.0-openjdk.x86_64']).to eq([{:name => 'java-1.8.0-openjdk', :epoch => '1', :version => '1.8.0.131', :release => '2.b11.el7_3', :arch => 'x86_64'}]) end end context "with multiline entries" do let(:check_update) { File.read(my_fixture("yum-check-update-multiline.txt")) } let(:output) { described_class.parse_updates(check_update) } it "parses multi-line values as a single package tuple" do expect(output['libpcap']).to eq([{:name => 'libpcap', :epoch => '14', :version => '1.4.0', :release => '1.20130826git2dbcaa1.el6', :arch => 'x86_64'}]) end end context "with obsoleted packages" do let(:check_update) { File.read(my_fixture("yum-check-update-obsoletes.txt")) } let(:output) { described_class.parse_updates(check_update) } it "ignores all entries including and after 'Obsoleting Packages'" do expect(output).not_to include("Obsoleting") expect(output).not_to include("NetworkManager-bluetooth.x86_64") expect(output).not_to include("1:1.0.0-14.git20150121.b4ea599c.el7") end end context "with security notifications" do let(:check_update) { File.read(my_fixture("yum-check-update-security.txt")) } let(:output) { described_class.parse_updates(check_update) } it "ignores all entries including and after 'Security'" do expect(output).not_to include("Security") end it "includes updates before 'Security'" do expect(output).to include("yum-plugin-fastestmirror.noarch") end end context "with broken update notices" do let(:check_update) { File.read(my_fixture("yum-check-update-broken-notices.txt")) } let(:output) { described_class.parse_updates(check_update) } it "ignores all entries including and after 'Update'" do expect(output).not_to include("Update") end it "includes updates before 'Update'" do expect(output).to include("yum-plugin-fastestmirror.noarch") end end context "with improper package names in output" do it "raises an exception parsing package name" do expect { described_class.update_to_hash('badpackagename', '1') }.to raise_exception(Exception, /Failed to parse/) end end context "with trailing plugin output" do let(:check_update) { File.read(my_fixture("yum-check-update-plugin-output.txt")) } let(:output) { described_class.parse_updates(check_update) } it "parses correctly formatted entries" do expect(output['bash']).to eq([{:name => 'bash', :epoch => '0', :version => '4.2.46', :release => '12.el7', :arch => 'x86_64'}]) end it "ignores all mentions of plugin output" do expect(output).not_to include("Random plugin") end end end end puppet-5.5.10/spec/unit/provider/parsedfile_spec.rb0000644005276200011600000001763013417161721022241 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/files' require 'puppet' require 'puppet/provider/parsedfile' Puppet::Type.newtype(:parsedfile_type) do newparam(:name) newproperty(:target) end # Most of the tests for this are still in test/ral/provider/parsedfile.rb. describe Puppet::Provider::ParsedFile do # The ParsedFile provider class is meant to be used as an abstract base class # but also stores a lot of state within the singleton class. To avoid # sharing data between classes we construct an anonymous class that inherits # the ParsedFile provider instead of directly working with the ParsedFile # provider itself. let(:parsed_type) do Puppet::Type.type(:parsedfile_type) end let!(:provider) { parsed_type.provide(:parsedfile_provider, :parent => described_class) } describe "when looking up records loaded from disk" do it "should return nil if no records have been loaded" do expect(provider.record?("foo")).to be_nil end end describe "when generating a list of instances" do it "should return an instance for each record parsed from all of the registered targets" do provider.expects(:targets).returns %w{/one /two} provider.stubs(:skip_record?).returns false one = [:uno1, :uno2] two = [:dos1, :dos2] provider.expects(:prefetch_target).with("/one").returns one provider.expects(:prefetch_target).with("/two").returns two results = [] (one + two).each do |inst| results << inst.to_s + "_instance" provider.expects(:new).with(inst).returns(results[-1]) end expect(provider.instances).to eq(results) end it "should ignore target when retrieve fails" do provider.expects(:targets).returns %w{/one /two /three} provider.stubs(:skip_record?).returns false provider.expects(:retrieve).with("/one").returns [ {:name => 'target1_record1'}, {:name => 'target1_record2'} ] provider.expects(:retrieve).with("/two").raises Puppet::Util::FileType::FileReadError, "some error" provider.expects(:retrieve).with("/three").returns [ {:name => 'target3_record1'}, {:name => 'target3_record2'} ] Puppet.expects(:err).with('Could not prefetch parsedfile_type provider \'parsedfile_provider\' target \'/two\': some error. Treating as empty') provider.expects(:new).with(:name => 'target1_record1', :on_disk => true, :target => '/one', :ensure => :present).returns 'r1' provider.expects(:new).with(:name => 'target1_record2', :on_disk => true, :target => '/one', :ensure => :present).returns 'r2' provider.expects(:new).with(:name => 'target3_record1', :on_disk => true, :target => '/three', :ensure => :present).returns 'r3' provider.expects(:new).with(:name => 'target3_record2', :on_disk => true, :target => '/three', :ensure => :present).returns 'r4' expect(provider.instances).to eq(%w{r1 r2 r3 r4}) end it "should skip specified records" do provider.expects(:targets).returns %w{/one} provider.expects(:skip_record?).with(:uno).returns false provider.expects(:skip_record?).with(:dos).returns true one = [:uno, :dos] provider.expects(:prefetch_target).returns one provider.expects(:new).with(:uno).returns "eh" provider.expects(:new).with(:dos).never provider.instances end end describe "when matching resources to existing records" do let(:first_resource) { stub(:one, :name => :one) } let(:second_resource) { stub(:two, :name => :two) } let(:resources) {{:one => first_resource, :two => second_resource}} it "returns a resource if the record name matches the resource name" do record = {:name => :one} expect(provider.resource_for_record(record, resources)).to be first_resource end it "doesn't return a resource if the record name doesn't match any resource names" do record = {:name => :three} expect(provider.resource_for_record(record, resources)).to be_nil end end describe "when flushing a file's records to disk" do before do # This way we start with some @records, like we would in real life. provider.stubs(:retrieve).returns [] provider.default_target = "/foo/bar" provider.initvars provider.prefetch @filetype = Puppet::Util::FileType.filetype(:flat).new("/my/file") Puppet::Util::FileType.filetype(:flat).stubs(:new).with("/my/file",nil).returns @filetype @filetype.stubs(:write) end it "should back up the file being written if the filetype can be backed up" do @filetype.expects(:backup) provider.flush_target("/my/file") end it "should not try to back up the file if the filetype cannot be backed up" do @filetype = Puppet::Util::FileType.filetype(:ram).new("/my/file") Puppet::Util::FileType.filetype(:flat).expects(:new).returns @filetype @filetype.stubs(:write) provider.flush_target("/my/file") end it "should not back up the file more than once between calls to 'prefetch'" do @filetype.expects(:backup).once provider.flush_target("/my/file") provider.flush_target("/my/file") end it "should back the file up again once the file has been reread" do @filetype.expects(:backup).times(2) provider.flush_target("/my/file") provider.prefetch provider.flush_target("/my/file") end end describe "when flushing multiple files" do describe "and an error is encountered" do it "the other file does not fail" do provider.stubs(:backup_target) bad_file = 'broken' good_file = 'writable' bad_writer = mock 'bad' bad_writer.expects(:write).raises(Exception, "Failed to write to bad file") good_writer = mock 'good' good_writer.expects(:write).returns(nil) provider.stubs(:target_object).with(bad_file).returns(bad_writer) provider.stubs(:target_object).with(good_file).returns(good_writer) bad_resource = parsed_type.new(:name => 'one', :target => bad_file) good_resource = parsed_type.new(:name => 'two', :target => good_file) expect { bad_resource.flush }.to raise_error(Exception, "Failed to write to bad file") good_resource.flush end end end end describe "A very basic provider based on ParsedFile" do include PuppetSpec::Files let(:input_text) { File.read(my_fixture('simple.txt')) } let(:target) { tmpfile('parsedfile_spec') } let(:provider) do example_provider_class = Class.new(Puppet::Provider::ParsedFile) example_provider_class.default_target = target # Setup some record rules example_provider_class.instance_eval do text_line :text, :match => %r{.} end example_provider_class.initvars example_provider_class.prefetch # evade a race between multiple invocations of the header method example_provider_class.stubs(:header). returns("# HEADER As added by puppet.\n") example_provider_class end context "writing file contents back to disk" do it "should not change anything except from adding a header" do input_records = provider.parse(input_text) expect(provider.to_file(input_records)). to match provider.header + input_text end end context "rewriting a file containing a native header" do let(:regex) { %r/^# HEADER.*third party\.\n/ } let(:input_records) { provider.parse(input_text) } before :each do provider.stubs(:native_header_regex).returns(regex) end it "should move the native header to the top" do expect(provider.to_file(input_records)).not_to match(/\A#{provider.header}/) end context "and dropping native headers found in input" do before :each do provider.stubs(:drop_native_header).returns(true) end it "should not include the native header in the output" do expect(provider.to_file(input_records)).not_to match regex end end end end puppet-5.5.10/spec/unit/provider/service/0000755005276200011600000000000013417162177020223 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/provider/service/base_spec.rb0000644005276200011600000000630313417161722022471 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'rbconfig' require 'fileutils' describe "base service provider" do include PuppetSpec::Files let :type do Puppet::Type.type(:service) end let :provider do type.provider(:base) end let(:executor) { Puppet::Util::Execution } let(:start_command) { 'start' } let(:status_command) { 'status' } let(:stop_command) { 'stop' } subject { provider } if Puppet.features.microsoft_windows? # Get a pid for $CHILD_STATUS to latch on to cmd = "cmd.exe /c \"exit 0\"" Puppet::Util::Execution.execute(cmd, {:failonfail => false}) end context "basic operations" do subject do type.new( :name => "test", :provider => :base, :start => start_command, :status => status_command, :stop => stop_command ).provider end def execute_command(command, options) case command.shift when start_command expect(options[:failonfail]).to eq(true) raise(Puppet::ExecutionFailure, 'failed to start') if @running @running = true return 'started' when status_command expect(options[:failonfail]).to eq(false) $CHILD_STATUS.expects(:exitstatus).at_least(1).returns(@running ? 0 : 1) return @running ? 'running' : 'not running' when stop_command expect(options[:failonfail]).to eq(true) raise(Puppet::ExecutionFailure, 'failed to stop') unless @running @running = false return 'stopped' else raise "unexpected command execution: #{command}" end end before :each do @running = false executor.expects(:execute).at_least(1).with { |command, options| execute_command(command, options) } end it "should invoke the start command if not running" do subject.start end it "should be stopped before being started" do expect(subject.status).to eq(:stopped) end it "should be running after being started" do subject.start expect(subject.status).to eq(:running) end it "should invoke the stop command when asked" do subject.start expect(subject.status).to eq(:running) subject.stop expect(subject.status).to eq(:stopped) end it "should raise an error if started twice" do subject.start expect {subject.start }.to raise_error(Puppet::Error, 'Could not start Service[test]: failed to start') end it "should raise an error if stopped twice" do subject.start subject.stop expect {subject.stop }.to raise_error(Puppet::Error, 'Could not stop Service[test]: failed to stop') end end context "when hasstatus is false" do subject do type.new( :name => "status test", :provider => :base, :hasstatus => false, :pattern => "majestik m\u00f8\u00f8se", ).provider end it "retrieves a PID from the process table" do Facter.stubs(:value).with(:operatingsystem).returns("CentOS") ps_output = File.binread(my_fixture("ps_ef.mixed_encoding")).force_encoding(Encoding::UTF_8) executor.expects(:execute).with("ps -ef").returns(ps_output) expect(subject.status).to eq(:running) end end end puppet-5.5.10/spec/unit/provider/service/bsd_spec.rb0000644005276200011600000001221613417161722022327 0ustar jenkinsjenkinsrequire 'spec_helper' describe Puppet::Type.type(:service).provider(:bsd), :unless => Puppet.features.microsoft_windows? do before :each do Puppet::Type.type(:service).stubs(:defaultprovider).returns described_class Facter.stubs(:value).with(:operatingsystem).returns :netbsd Facter.stubs(:value).with(:osfamily).returns 'NetBSD' described_class.stubs(:defpath).returns('/etc/rc.conf.d') @provider = subject() @provider.stubs(:initscript) end context "#instances" do it "should have an instances method" do expect(described_class).to respond_to :instances end it "should use defpath" do expect(described_class.instances).to be_all { |provider| provider.get(:path) == described_class.defpath } end end context "#disable" do it "should have a disable method" do expect(@provider).to respond_to(:disable) end it "should remove a service file to disable" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) Puppet::FileSystem.stubs(:exist?).with('/etc/rc.conf.d/sshd').returns(true) Puppet::FileSystem.expects(:exist?).with('/etc/rc.conf.d/sshd').returns(true) File.stubs(:delete).with('/etc/rc.conf.d/sshd') provider.disable end it "should not remove a service file if it doesn't exist" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) File.stubs(:exist?).with('/etc/rc.conf.d/sshd').returns(false) Puppet::FileSystem.expects(:exist?).with('/etc/rc.conf.d/sshd').returns(false) provider.disable end end context "#enable" do it "should have an enable method" do expect(@provider).to respond_to(:enable) end it "should set the proper contents to enable" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) Dir.stubs(:mkdir).with('/etc/rc.conf.d') fh = stub 'fh' File.stubs(:open).with('/etc/rc.conf.d/sshd', File::WRONLY | File::APPEND | File::CREAT, 0644).yields(fh) fh.expects(:<<).with("sshd_enable=\"YES\"\n") provider.enable end it "should set the proper contents to enable when disabled" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) Dir.stubs(:mkdir).with('/etc/rc.conf.d') File.stubs(:read).with('/etc/rc.conf.d/sshd').returns("sshd_enable=\"NO\"\n") fh = stub 'fh' File.stubs(:open).with('/etc/rc.conf.d/sshd', File::WRONLY | File::APPEND | File::CREAT, 0644).yields(fh) fh.expects(:<<).with("sshd_enable=\"YES\"\n") provider.enable end end context "#enabled?" do it "should have an enabled? method" do expect(@provider).to respond_to(:enabled?) end it "should return false if the service file does not exist" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) Puppet::FileSystem.stubs(:exist?).with('/etc/rc.conf.d/sshd').returns(false) # File.stubs(:read).with('/etc/rc.conf.d/sshd').returns("sshd_enable=\"NO\"\n") expect(provider.enabled?).to eq(:false) end it "should return true if the service file exists" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) Puppet::FileSystem.stubs(:exist?).with('/etc/rc.conf.d/sshd').returns(true) # File.stubs(:read).with('/etc/rc.conf.d/sshd').returns("sshd_enable=\"YES\"\n") expect(provider.enabled?).to eq(:true) end end context "#startcmd" do it "should have a startcmd method" do expect(@provider).to respond_to(:startcmd) end it "should use the supplied start command if specified" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :start => '/bin/foo')) provider.expects(:execute).with(['/bin/foo'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.start end it "should start the serviced directly otherwise" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) provider.expects(:execute).with(['/etc/rc.d/sshd', :onestart], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.expects(:search).with('sshd').returns('/etc/rc.d/sshd') provider.start end end context "#stopcmd" do it "should have a stopcmd method" do expect(@provider).to respond_to(:stopcmd) end it "should use the supplied stop command if specified" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :stop => '/bin/foo')) provider.expects(:execute).with(['/bin/foo'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.stop end it "should stop the serviced directly otherwise" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) provider.expects(:execute).with(['/etc/rc.d/sshd', :onestop], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.expects(:search).with('sshd').returns('/etc/rc.d/sshd') provider.stop end end end puppet-5.5.10/spec/unit/provider/service/daemontools_spec.rb0000644005276200011600000001176413417161722024112 0ustar jenkinsjenkinsrequire 'spec_helper' describe Puppet::Type.type(:service).provider(:daemontools) do before(:each) do # Create a mock resource @resource = stub 'resource' @provider = subject() @servicedir = "/etc/service" @provider.servicedir=@servicedir @daemondir = "/var/lib/service" @provider.class.defpath=@daemondir # A catch all; no parameters set @resource.stubs(:[]).returns(nil) # But set name, source and path (because we won't run # the thing that will fetch the resource path from the provider) @resource.stubs(:[]).with(:name).returns "myservice" @resource.stubs(:[]).with(:ensure).returns :enabled @resource.stubs(:[]).with(:path).returns @daemondir @resource.stubs(:ref).returns "Service[myservice]" @provider.resource = @resource @provider.stubs(:command).with(:svc).returns "svc" @provider.stubs(:command).with(:svstat).returns "svstat" @provider.stubs(:svc) @provider.stubs(:svstat) end it "should have a restart method" do expect(@provider).to respond_to(:restart) end it "should have a start method" do expect(@provider).to respond_to(:start) end it "should have a stop method" do expect(@provider).to respond_to(:stop) end it "should have an enabled? method" do expect(@provider).to respond_to(:enabled?) end it "should have an enable method" do expect(@provider).to respond_to(:enable) end it "should have a disable method" do expect(@provider).to respond_to(:disable) end context "when starting" do it "should use 'svc' to start the service" do @provider.stubs(:enabled?).returns :true @provider.expects(:svc).with("-u", "/etc/service/myservice") @provider.start end it "should enable the service if it is not enabled" do @provider.stubs(:svc) @provider.expects(:enabled?).returns :false @provider.expects(:enable) @provider.start end end context "when stopping" do it "should use 'svc' to stop the service" do @provider.stubs(:disable) @provider.expects(:svc).with("-d", "/etc/service/myservice") @provider.stop end end context "when restarting" do it "should use 'svc' to restart the service" do @provider.expects(:svc).with("-t", "/etc/service/myservice") @provider.restart end end context "when enabling" do it "should create a symlink between daemon dir and service dir", :if => Puppet.features.manages_symlinks? do daemon_path = File.join(@daemondir, "myservice") service_path = File.join(@servicedir, "myservice") Puppet::FileSystem.expects(:symlink?).with(service_path).returns(false) Puppet::FileSystem.expects(:symlink).with(daemon_path, service_path).returns(0) @provider.enable end end context "when disabling" do it "should remove the symlink between daemon dir and service dir" do FileTest.stubs(:directory?).returns(false) path = File.join(@servicedir,"myservice") Puppet::FileSystem.expects(:symlink?).with(path).returns(true) Puppet::FileSystem.expects(:unlink).with(path) @provider.stubs(:texecute).returns("") @provider.disable end it "should stop the service" do FileTest.stubs(:directory?).returns(false) Puppet::FileSystem.expects(:symlink?).returns(true) Puppet::FileSystem.stubs(:unlink) @provider.expects(:stop) @provider.disable end end context "when checking if the service is enabled?" do it "should return true if it is running" do @provider.stubs(:status).returns(:running) expect(@provider.enabled?).to eq(:true) end [true, false].each do |t| it "should return #{t} if the symlink exists" do @provider.stubs(:status).returns(:stopped) path = File.join(@servicedir,"myservice") Puppet::FileSystem.expects(:symlink?).with(path).returns(t) expect(@provider.enabled?).to eq("#{t}".to_sym) end end end context "when checking status" do it "should call the external command 'svstat /etc/service/myservice'" do @provider.expects(:svstat).with(File.join(@servicedir,"myservice")) @provider.status end end context "when checking status" do it "and svstat fails, properly raise a Puppet::Error" do @provider.expects(:svstat).with(File.join(@servicedir,"myservice")).raises(Puppet::ExecutionFailure, "failure") expect { @provider.status }.to raise_error(Puppet::Error, 'Could not get status for service Service[myservice]: failure') end it "and svstat returns up, then return :running" do @provider.expects(:svstat).with(File.join(@servicedir,"myservice")).returns("/etc/service/myservice: up (pid 454) 954326 seconds") expect(@provider.status).to eq(:running) end it "and svstat returns not running, then return :stopped" do @provider.expects(:svstat).with(File.join(@servicedir,"myservice")).returns("/etc/service/myservice: supervise not running") expect(@provider.status).to eq(:stopped) end end end puppet-5.5.10/spec/unit/provider/service/debian_spec.rb0000644005276200011600000001244213417161722023002 0ustar jenkinsjenkinsrequire 'spec_helper' describe Puppet::Type.type(:service).provider(:debian) do if Puppet.features.microsoft_windows? # Get a pid for $CHILD_STATUS to latch on to command = "cmd.exe /c \"exit 0\"" Puppet::Util::Execution.execute(command, {:failonfail => false}) end before(:each) do # Create a mock resource @resource = stub 'resource' @provider = subject() # A catch all; no parameters set @resource.stubs(:[]).returns(nil) # But set name, source and path @resource.stubs(:[]).with(:name).returns "myservice" @resource.stubs(:[]).with(:ensure).returns :enabled @resource.stubs(:ref).returns "Service[myservice]" @provider.resource = @resource @provider.stubs(:command).with(:update_rc).returns "update_rc" @provider.stubs(:command).with(:invoke_rc).returns "invoke_rc" @provider.stubs(:command).with(:service).returns "service" @provider.stubs(:update_rc) @provider.stubs(:invoke_rc) end ['1','2'].each do |version| it "should be the default provider on CumulusLinux #{version}" do Facter.expects(:value).with(:operatingsystem).at_least_once.returns('CumulusLinux') Facter.expects(:value).with(:operatingsystemmajrelease).returns(version) expect(described_class.default?).to be_truthy end end it "should be the default provider on Debian" do Facter.expects(:value).with(:operatingsystem).at_least_once.returns('Debian') Facter.expects(:value).with(:operatingsystemmajrelease).returns('7') expect(described_class.default?).to be_truthy end it "should have an enabled? method" do expect(@provider).to respond_to(:enabled?) end it "should have an enable method" do expect(@provider).to respond_to(:enable) end it "should have a disable method" do expect(@provider).to respond_to(:disable) end context "when enabling" do it "should call update-rc.d twice" do @provider.expects(:update_rc).twice @provider.enable end end context "when disabling" do it "should be able to disable services with newer sysv-rc versions" do @provider.stubs(:`).with("dpkg --compare-versions $(dpkg-query -W --showformat '${Version}' sysv-rc) ge 2.88 ; echo $?").returns "0" @provider.expects(:update_rc).with(@resource[:name], "disable") @provider.disable end it "should be able to enable services with older sysv-rc versions" do @provider.stubs(:`).with("dpkg --compare-versions $(dpkg-query -W --showformat '${Version}' sysv-rc) ge 2.88 ; echo $?").returns "1" @provider.expects(:update_rc).with("-f", @resource[:name], "remove") @provider.expects(:update_rc).with(@resource[:name], "stop", "00", "1", "2", "3", "4", "5", "6", ".") @provider.disable end end context "when checking whether it is enabled" do it "should call Kernel.system() with the appropriate parameters" do @provider.expects(:system).with("/usr/sbin/invoke-rc.d", "--quiet", "--query", @resource[:name], "start").once $CHILD_STATUS.stubs(:exitstatus).returns(0) @provider.enabled? end it "should return true when invoke-rc.d exits with 104 status" do @provider.stubs(:system) $CHILD_STATUS.stubs(:exitstatus).returns(104) expect(@provider.enabled?).to eq(:true) end it "should return true when invoke-rc.d exits with 106 status" do @provider.stubs(:system) $CHILD_STATUS.stubs(:exitstatus).returns(106) expect(@provider.enabled?).to eq(:true) end shared_examples "manually queries service status" do |status| it "links count is 4" do @provider.stubs(:system) $CHILD_STATUS.stubs(:exitstatus).returns(status) @provider.stubs(:get_start_link_count).returns(4) expect(@provider.enabled?).to eq(:true) end it "links count is less than 4" do @provider.stubs(:system) $CHILD_STATUS.stubs(:exitstatus).returns(status) @provider.stubs(:get_start_link_count).returns(3) expect(@provider.enabled?).to eq(:false) end end context "when invoke-rc.d exits with 101 status" do it_should_behave_like "manually queries service status", 101 end context "when invoke-rc.d exits with 105 status" do it_should_behave_like "manually queries service status", 105 end context "when invoke-rc.d exits with 101 status" do it_should_behave_like "manually queries service status", 101 end context "when invoke-rc.d exits with 105 status" do it_should_behave_like "manually queries service status", 105 end # pick a range of non-[104.106] numbers, strings and booleans to test with. [-100, -1, 0, 1, 100, "foo", "", :true, :false].each do |exitstatus| it "should return false when invoke-rc.d exits with #{exitstatus} status" do @provider.stubs(:system) $CHILD_STATUS.stubs(:exitstatus).returns(exitstatus) expect(@provider.enabled?).to eq(:false) end end end context "when checking service status" do it "should use the service command" do Facter.stubs(:value).with(:operatingsystem).returns('Debian') Facter.stubs(:value).with(:operatingsystemmajrelease).returns('8') @resource.stubs(:[]).with(:hasstatus).returns(:true) expect(@provider.statuscmd).to eq(["service", @resource[:name], "status"]) end end end puppet-5.5.10/spec/unit/provider/service/freebsd_spec.rb0000644005276200011600000000522413417161722023172 0ustar jenkinsjenkinsrequire 'spec_helper' describe Puppet::Type.type(:service).provider(:freebsd) do before :each do @provider = subject() @provider.stubs(:initscript) Facter.stubs(:value).with(:osfamily).returns 'FreeBSD' end it "should correctly parse rcvar for FreeBSD < 7" do @provider.stubs(:execute).returns <= 8.1" do @provider.stubs(:execute).returns <= 7" do @provider.stubs(:rcvar).returns(['# ntpd', 'ntpd_enable="YES"', '# (default: "")']) expect(@provider.rcvar_value).to eq("YES") end it "should find the right rcvar_name" do @provider.stubs(:rcvar).returns(['# ntpd', 'ntpd_enable="YES"']) expect(@provider.rcvar_name).to eq("ntpd") end it "should enable only the selected service" do Puppet::FileSystem.stubs(:exist?).with('/etc/rc.conf').returns(true) File.stubs(:read).with('/etc/rc.conf').returns("openntpd_enable=\"NO\"\nntpd_enable=\"NO\"\n") fh = stub 'fh' File.stubs(:open).with('/etc/rc.conf', File::WRONLY).yields(fh) fh.expects(:<<).with("openntpd_enable=\"NO\"\nntpd_enable=\"YES\"\n") Puppet::FileSystem.stubs(:exist?).with('/etc/rc.conf.local').returns(false) Puppet::FileSystem.stubs(:exist?).with('/etc/rc.conf.d/ntpd').returns(false) @provider.rc_replace('ntpd', 'ntpd', 'YES') end end puppet-5.5.10/spec/unit/provider/service/gentoo_spec.rb0000644005276200011600000002623513417161722023060 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:service).provider(:gentoo) do if Puppet.features.microsoft_windows? # Get a pid for $CHILD_STATUS to latch on to command = "cmd.exe /c \"exit 0\"" Puppet::Util::Execution.execute(command, {:failonfail => false}) end before :each do Puppet::Type.type(:service).stubs(:defaultprovider).returns described_class FileTest.stubs(:file?).with('/sbin/rc-update').returns true FileTest.stubs(:executable?).with('/sbin/rc-update').returns true Facter.stubs(:value).with(:operatingsystem).returns 'Gentoo' Facter.stubs(:value).with(:osfamily).returns 'Gentoo' # The initprovider (parent of the gentoo provider) does a stat call # before it even tries to execute an initscript. We use sshd in all the # tests so make sure it is considered present. sshd_path = '/etc/init.d/sshd' # stub_file = stub(sshd_path, :stat => stub('stat')) Puppet::FileSystem.stubs(:stat).with(sshd_path).returns stub('stat') end let :initscripts do [ 'alsasound', 'bootmisc', 'functions.sh', 'hwclock', 'reboot.sh', 'rsyncd', 'shutdown.sh', 'sshd', 'vixie-cron', 'wpa_supplicant', 'xdm-setup' ] end let :helperscripts do [ 'functions.sh', 'reboot.sh', 'shutdown.sh' ] end describe ".instances" do it "should have an instances method" do expect(described_class).to respond_to(:instances) end it "should get a list of services from /etc/init.d but exclude helper scripts" do FileTest.expects(:directory?).with('/etc/init.d').returns true Dir.expects(:entries).with('/etc/init.d').returns initscripts (initscripts - helperscripts).each do |script| FileTest.expects(:executable?).with("/etc/init.d/#{script}").returns true end helperscripts.each do |script| FileTest.expects(:executable?).with("/etc/init.d/#{script}").never end Puppet::FileSystem.stubs(:symlink?).returns false # stub('file', :symlink? => false) expect(described_class.instances.map(&:name)).to eq([ 'alsasound', 'bootmisc', 'hwclock', 'rsyncd', 'sshd', 'vixie-cron', 'wpa_supplicant', 'xdm-setup' ]) end end describe "#start" do it "should use the supplied start command if specified" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :start => '/bin/foo')) provider.expects(:execute).with(['/bin/foo'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.start end it "should start the service with start otherwise" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) provider.expects(:execute).with(['/etc/init.d/sshd',:start], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.expects(:search).with('sshd').returns('/etc/init.d/sshd') provider.start end end describe "#stop" do it "should use the supplied stop command if specified" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :stop => '/bin/foo')) provider.expects(:execute).with(['/bin/foo'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.stop end it "should stop the service with stop otherwise" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) provider.expects(:execute).with(['/etc/init.d/sshd',:stop], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.expects(:search).with('sshd').returns('/etc/init.d/sshd') provider.stop end end describe "#enabled?" do before :each do described_class.any_instance.stubs(:update).with(:show).returns File.read(my_fixture('rc_update_show')) end it "should run rc-update show to get a list of enabled services" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) provider.expects(:update).with(:show).returns "\n" provider.enabled? end ['hostname', 'net.lo', 'procfs'].each do |service| it "should consider service #{service} in runlevel boot as enabled" do provider = described_class.new(Puppet::Type.type(:service).new(:name => service)) expect(provider.enabled?).to eq(:true) end end ['alsasound', 'xdm', 'netmount'].each do |service| it "should consider service #{service} in runlevel default as enabled" do provider = described_class.new(Puppet::Type.type(:service).new(:name => service)) expect(provider.enabled?).to eq(:true) end end ['rsyncd', 'lighttpd', 'mysql'].each do |service| it "should consider unused service #{service} as disabled" do provider = described_class.new(Puppet::Type.type(:service).new(:name => service)) expect(provider.enabled?).to eq(:false) end end end describe "#enable" do it "should run rc-update add to enable a service" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) provider.expects(:update).with(:add, 'sshd', :default) provider.enable end end describe "#disable" do it "should run rc-update del to disable a service" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) provider.expects(:update).with(:del, 'sshd', :default) provider.disable end end describe "#status" do describe "when a special status command is specified" do it "should use the status command from the resource" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :status => '/bin/foo')) provider.expects(:execute).with(['/etc/init.d/sshd',:status], :failonfail => false, :override_locale => false, :squelch => false, :combine => true).never provider.expects(:execute).with(['/bin/foo'], :failonfail => false, :override_locale => false, :squelch => false, :combine => true) $CHILD_STATUS.stubs(:exitstatus).returns 0 provider.status end it "should return :stopped when the status command returns with a non-zero exitcode" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :status => '/bin/foo')) provider.expects(:execute).with(['/etc/init.d/sshd',:status], :failonfail => false, :override_locale => false, :squelch => false, :combine => true).never provider.expects(:execute).with(['/bin/foo'], :failonfail => false, :override_locale => false, :squelch => false, :combine => true) $CHILD_STATUS.stubs(:exitstatus).returns 3 expect(provider.status).to eq(:stopped) end it "should return :running when the status command returns with a zero exitcode" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :status => '/bin/foo')) provider.expects(:execute).with(['/etc/init.d/sshd',:status], :failonfail => false, :override_locale => false, :squelch => false, :combine => true).never provider.expects(:execute).with(['/bin/foo'], :failonfail => false, :override_locale => false, :squelch => false, :combine => true) $CHILD_STATUS.stubs(:exitstatus).returns 0 expect(provider.status).to eq(:running) end end describe "when hasstatus is false" do it "should return running if a pid can be found" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :hasstatus => false)) provider.expects(:execute).with(['/etc/init.d/sshd',:status], :failonfail => false, :override_locale => false, :squelch => false, :combine => true).never provider.expects(:getpid).returns 1000 expect(provider.status).to eq(:running) end it "should return stopped if no pid can be found" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :hasstatus => false)) provider.expects(:execute).with(['/etc/init.d/sshd',:status], :failonfail => false, :override_locale => false, :squelch => false, :combine => true).never provider.expects(:getpid).returns nil expect(provider.status).to eq(:stopped) end end describe "when hasstatus is true" do it "should return running if status exits with a zero exitcode" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :hasstatus => true)) provider.expects(:search).with('sshd').returns('/etc/init.d/sshd') provider.expects(:execute).with(['/etc/init.d/sshd',:status], :failonfail => false, :override_locale => false, :squelch => false, :combine => true) $CHILD_STATUS.stubs(:exitstatus).returns 0 expect(provider.status).to eq(:running) end it "should return stopped if status exits with a non-zero exitcode" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :hasstatus => true)) provider.expects(:search).with('sshd').returns('/etc/init.d/sshd') provider.expects(:execute).with(['/etc/init.d/sshd',:status], :failonfail => false, :override_locale => false, :squelch => false, :combine => true) $CHILD_STATUS.stubs(:exitstatus).returns 3 expect(provider.status).to eq(:stopped) end end end describe "#restart" do it "should use the supplied restart command if specified" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :restart => '/bin/foo')) provider.expects(:execute).with(['/etc/init.d/sshd',:restart], :failonfail => true, :override_locale => false, :squelch => false, :combine => true).never provider.expects(:execute).with(['/bin/foo'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.restart end it "should restart the service with restart if hasrestart is true" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :hasrestart => true)) provider.expects(:search).with('sshd').returns('/etc/init.d/sshd') provider.expects(:execute).with(['/etc/init.d/sshd',:restart], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.restart end it "should restart the service with stop/start if hasrestart is false" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :hasrestart => false)) provider.expects(:search).with('sshd').returns('/etc/init.d/sshd') provider.expects(:execute).with(['/etc/init.d/sshd',:restart], :failonfail => true, :override_locale => false, :squelch => false, :combine => true).never provider.expects(:execute).with(['/etc/init.d/sshd',:stop], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.expects(:execute).with(['/etc/init.d/sshd',:start], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.restart end end end puppet-5.5.10/spec/unit/provider/service/init_spec.rb0000644005276200011600000002321313417161722022521 0ustar jenkinsjenkins#! /usr/bin/env ruby # # Unit testing for the Init service Provider # require 'spec_helper' describe Puppet::Type.type(:service).provider(:init) do if Puppet.features.microsoft_windows? # Get a pid for $CHILD_STATUS to latch on to cmd = "cmd.exe /c \"exit 0\"" Puppet::Util::Execution.execute(cmd, {:failonfail => false}) end before do Puppet::Type.type(:service).defaultprovider = described_class end after do Puppet::Type.type(:service).defaultprovider = nil end let :provider do resource.provider end let :resource do Puppet::Type.type(:service).new( :name => 'myservice', :ensure => :running, :path => paths ) end let :paths do ["/service/path","/alt/service/path"] end let :excludes do # Taken from redhat, gentoo, and debian %w{functions.sh reboot.sh shutdown.sh functions halt killall single linuxconf reboot boot wait-for-state rcS module-init-tools} end describe "when getting all service instances" do before :each do described_class.stubs(:defpath).returns('tmp') @services = ['one', 'two', 'three', 'four', 'umountfs'] Dir.stubs(:entries).with('tmp').returns @services FileTest.expects(:directory?).with('tmp').returns(true) FileTest.stubs(:executable?).returns(true) end it "should return instances for all services" do expect(described_class.instances.map(&:name)).to eq(@services) end it "should omit an array of services from exclude list" do exclude = ['two', 'four'] expect(described_class.get_services(described_class.defpath, exclude).map(&:name)).to eq(@services - exclude) end it "should omit a single service from the exclude list" do exclude = 'two' expect(described_class.get_services(described_class.defpath, exclude).map(&:name)).to eq(@services - [exclude]) end it "should omit Yocto services on cisco-wrlinux" do Facter.stubs(:value).with(:osfamily).returns 'cisco-wrlinux' exclude = 'umountfs' expect(described_class.get_services(described_class.defpath).map(&:name)).to eq(@services - [exclude]) end it "should not omit Yocto services on non cisco-wrlinux platforms" do expect(described_class.get_services(described_class.defpath).map(&:name)).to eq(@services) end it "should use defpath" do expect(described_class.instances).to be_all { |provider| provider.get(:path) == described_class.defpath } end it "should set hasstatus to true for providers" do expect(described_class.instances).to be_all { |provider| provider.get(:hasstatus) == true } end it "should discard upstart jobs", :if => Puppet.features.manages_symlinks? do not_init_service, *valid_services = @services path = "tmp/#{not_init_service}" Puppet::FileSystem.expects(:symlink?).at_least_once.returns false Puppet::FileSystem.expects(:symlink?).with(Puppet::FileSystem.pathname(path)).returns(true) Puppet::FileSystem.expects(:readlink).with(Puppet::FileSystem.pathname(path)).returns("/lib/init/upstart-job") expect(described_class.instances.map(&:name)).to eq(valid_services) end it "should discard non-initscript scripts" do valid_services = @services all_services = valid_services + excludes Dir.expects(:entries).with('tmp').returns all_services expect(described_class.instances.map(&:name)).to match_array(valid_services) end end describe "when checking valid paths" do it "should discard paths that do not exist" do File.expects(:directory?).with(paths[0]).returns false Puppet::FileSystem.expects(:exist?).with(paths[0]).returns false File.expects(:directory?).with(paths[1]).returns true expect(provider.paths).to eq([paths[1]]) end it "should discard paths that are not directories" do paths.each do |path| Puppet::FileSystem.expects(:exist?).with(path).returns true File.expects(:directory?).with(path).returns false end expect(provider.paths).to be_empty end end describe "when searching for the init script" do before :each do paths.each {|path| File.expects(:directory?).with(path).returns true } end it "should be able to find the init script in the service path" do Puppet::FileSystem.expects(:exist?).with("#{paths[0]}/myservice").returns true Puppet::FileSystem.expects(:exist?).with("#{paths[1]}/myservice").never # first one wins expect(provider.initscript).to eq("/service/path/myservice") end it "should be able to find the init script in an alternate service path" do Puppet::FileSystem.expects(:exist?).with("#{paths[0]}/myservice").returns false Puppet::FileSystem.expects(:exist?).with("#{paths[1]}/myservice").returns true expect(provider.initscript).to eq("/alt/service/path/myservice") end it "should be able to find the init script if it ends with .sh" do Puppet::FileSystem.expects(:exist?).with("#{paths[0]}/myservice").returns false Puppet::FileSystem.expects(:exist?).with("#{paths[1]}/myservice").returns false Puppet::FileSystem.expects(:exist?).with("#{paths[0]}/myservice.sh").returns true expect(provider.initscript).to eq("/service/path/myservice.sh") end it "should fail if the service isn't there" do paths.each do |path| Puppet::FileSystem.expects(:exist?).with("#{path}/myservice").returns false Puppet::FileSystem.expects(:exist?).with("#{path}/myservice.sh").returns false end expect { provider.initscript }.to raise_error(Puppet::Error, "Could not find init script for 'myservice'") end end describe "if the init script is present" do before :each do File.stubs(:directory?).with("/service/path").returns true File.stubs(:directory?).with("/alt/service/path").returns true Puppet::FileSystem.stubs(:exist?).with("/service/path/myservice").returns true end [:start, :stop, :status, :restart].each do |method| it "should have a #{method} method" do expect(provider).to respond_to(method) end describe "when running #{method}" do before :each do $CHILD_STATUS.stubs(:exitstatus).returns(0) end it "should use any provided explicit command" do resource[method] = "/user/specified/command" provider.expects(:execute).with { |command, *args| command == ["/user/specified/command"] } provider.send(method) end it "should pass #{method} to the init script when no explicit command is provided" do resource[:hasrestart] = :true resource[:hasstatus] = :true provider.expects(:execute).with { |command, *args| command == ["/service/path/myservice",method]} provider.send(method) end end end describe "when checking status" do describe "when hasstatus is :true" do before :each do resource[:hasstatus] = :true end it "should execute the command" do provider.expects(:texecute).with(:status, ['/service/path/myservice', :status], false).returns("") $CHILD_STATUS.stubs(:exitstatus).returns(0) provider.status end it "should consider the process running if the command returns 0" do provider.expects(:texecute).with(:status, ['/service/path/myservice', :status], false).returns("") $CHILD_STATUS.stubs(:exitstatus).returns(0) expect(provider.status).to eq(:running) end [-10,-1,1,10].each { |ec| it "should consider the process stopped if the command returns something non-0" do provider.expects(:texecute).with(:status, ['/service/path/myservice', :status], false).returns("") $CHILD_STATUS.stubs(:exitstatus).returns(ec) expect(provider.status).to eq(:stopped) end } end describe "when hasstatus is not :true" do before :each do resource[:hasstatus] = :false end it "should consider the service :running if it has a pid" do provider.expects(:getpid).returns "1234" expect(provider.status).to eq(:running) end it "should consider the service :stopped if it doesn't have a pid" do provider.expects(:getpid).returns nil expect(provider.status).to eq(:stopped) end end end describe "when restarting and hasrestart is not :true" do before :each do resource[:hasrestart] = :false end it "should stop and restart the process" do provider.expects(:texecute).with(:stop, ['/service/path/myservice', :stop ], true).returns("") provider.expects(:texecute).with(:start,['/service/path/myservice', :start], true).returns("") $CHILD_STATUS.stubs(:exitstatus).returns(0) provider.restart end end describe "when starting a service on Solaris" do it "should use ctrun" do Facter.stubs(:value).with(:osfamily).returns 'Solaris' provider.expects(:execute).with('/usr/bin/ctrun -l child /service/path/myservice start', {:failonfail => true, :override_locale => false, :squelch => false, :combine => true}).returns("") $CHILD_STATUS.stubs(:exitstatus).returns(0) provider.start end end describe "when starting a service on RedHat" do it "should not use ctrun" do Facter.stubs(:value).with(:osfamily).returns 'RedHat' provider.expects(:execute).with(['/service/path/myservice', :start], {:failonfail => true, :override_locale => false, :squelch => false, :combine => true}).returns("") $CHILD_STATUS.stubs(:exitstatus).returns(0) provider.start end end end end puppet-5.5.10/spec/unit/provider/service/launchd_spec.rb0000644005276200011600000004073513417161722023204 0ustar jenkinsjenkins# Spec Tests for the Launchd provider # require 'spec_helper' describe Puppet::Type.type(:service).provider(:launchd) do let (:plistlib) { Puppet::Util::Plist } let (:joblabel) { "com.foo.food" } let (:provider) { subject.class } let (:resource) { Puppet::Type.type(:service).new(:name => joblabel, :provider => :launchd) } let (:launchd_overrides_6_9) { '/var/db/launchd.db/com.apple.launchd/overrides.plist' } let (:launchd_overrides_10_) { '/var/db/com.apple.xpc.launchd/disabled.plist' } subject { resource.provider } if Puppet.features.microsoft_windows? # Get a pid for $CHILD_STATUS to latch on to command = "cmd.exe /c \"exit 0\"" Puppet::Util::Execution.execute(command, {:failonfail => false}) end describe "the type interface" do %w{ start stop enabled? enable disable status}.each do |method| it { is_expected.to respond_to method.to_sym } end end describe 'the status of the services' do it "should call the external command 'launchctl list' once" do provider.expects(:launchctl).with(:list).returns(joblabel) provider.expects(:jobsearch).with(nil).returns({joblabel => "/Library/LaunchDaemons/#{joblabel}"}) provider.prefetch({}) end it "should return stopped if not listed in launchctl list output" do provider.expects(:launchctl).with(:list).returns('com.bar.is_running') provider.expects(:jobsearch).with(nil).returns({'com.bar.is_not_running' => "/Library/LaunchDaemons/com.bar.is_not_running"}) expect(provider.prefetch({}).last.status).to eq :stopped end it "should return running if listed in launchctl list output" do provider.expects(:launchctl).with(:list).returns('com.bar.is_running') provider.expects(:jobsearch).with(nil).returns({'com.bar.is_running' => "/Library/LaunchDaemons/com.bar.is_running"}) expect(provider.prefetch({}).last.status).to eq :running end after :each do provider.instance_variable_set(:@job_list, nil) end describe "when hasstatus is set to false" do before :each do resource[:hasstatus] = :false end it "should use the user-provided status command if present and return running if true" do resource[:status] = '/bin/true' subject.expects(:texecute).with(:status, ["/bin/true"], false).returns(0) $CHILD_STATUS.stubs(:exitstatus).returns(0) expect(subject.status).to eq(:running) end it "should use the user-provided status command if present and return stopped if false" do resource[:status] = '/bin/false' subject.expects(:texecute).with(:status, ["/bin/false"], false).returns(nil) $CHILD_STATUS.stubs(:exitstatus).returns(1) expect(subject.status).to eq(:stopped) end it "should fall back to getpid if no status command is provided" do subject.expects(:getpid).returns(123) expect(subject.status).to eq(:running) end end end [[10, '10.6'], [13, '10.9']].each do |kernel, version| describe "when checking whether the service is enabled on OS X #{version}" do it "should return true if the job plist says disabled is true and the global overrides says disabled is false" do provider.expects(:get_os_version).returns(kernel).at_least_once subject.expects(:plist_from_label).returns([joblabel, {"Disabled" => true}]) plistlib.expects(:read_plist_file).with(launchd_overrides_6_9).returns({joblabel => {"Disabled" => false}}) FileTest.expects(:file?).with(launchd_overrides_6_9).returns(true) expect(subject.enabled?).to eq(:true) end it "should return false if the job plist says disabled is false and the global overrides says disabled is true" do provider.expects(:get_os_version).returns(kernel).at_least_once subject.expects(:plist_from_label).returns([joblabel, {"Disabled" => false}]) plistlib.expects(:read_plist_file).with(launchd_overrides_6_9).returns({joblabel => {"Disabled" => true}}) FileTest.expects(:file?).with(launchd_overrides_6_9).returns(true) expect(subject.enabled?).to eq(:false) end it "should return true if the job plist and the global overrides have no disabled keys" do provider.expects(:get_os_version).returns(kernel).at_least_once subject.expects(:plist_from_label).returns([joblabel, {}]) plistlib.expects(:read_plist_file).with(launchd_overrides_6_9).returns({}) FileTest.expects(:file?).with(launchd_overrides_6_9).returns(true) expect(subject.enabled?).to eq(:true) end end end describe "when checking whether the service is enabled on OS X 10.10" do it "should return true if the job plist says disabled is true and the global overrides says disabled is false" do provider.expects(:get_os_version).returns(14).at_least_once subject.expects(:plist_from_label).returns([joblabel, {"Disabled" => true}]) plistlib.expects(:read_plist_file).with(launchd_overrides_10_).returns({joblabel => false}) FileTest.expects(:file?).with(launchd_overrides_10_).returns(true) expect(subject.enabled?).to eq(:true) end it "should return false if the job plist says disabled is false and the global overrides says disabled is true" do provider.expects(:get_os_version).returns(14).at_least_once subject.expects(:plist_from_label).returns([joblabel, {"Disabled" => false}]) plistlib.expects(:read_plist_file).with(launchd_overrides_10_).returns({joblabel => true}) FileTest.expects(:file?).with(launchd_overrides_10_).returns(true) expect(subject.enabled?).to eq(:false) end it "should return true if the job plist and the global overrides have no disabled keys" do provider.expects(:get_os_version).returns(14).at_least_once subject.expects(:plist_from_label).returns([joblabel, {}]) plistlib.expects(:read_plist_file).with(launchd_overrides_10_).returns({}) FileTest.expects(:file?).with(launchd_overrides_10_).returns(true) expect(subject.enabled?).to eq(:true) end end describe "when starting the service" do it "should call any explicit 'start' command" do resource[:start] = "/bin/false" subject.expects(:texecute).with(:start, ["/bin/false"], true) subject.start end it "should look for the relevant plist once" do subject.expects(:plist_from_label).returns([joblabel, {}]).once subject.expects(:enabled?).returns :true subject.expects(:execute).with([:launchctl, :load, "-w", joblabel]) subject.start end it "should execute 'launchctl load' once without writing to the plist if the job is enabled" do subject.expects(:plist_from_label).returns([joblabel, {}]) subject.expects(:enabled?).returns :true subject.expects(:execute).with([:launchctl, :load, "-w", joblabel]).once subject.start end it "should execute 'launchctl load' with writing to the plist once if the job is disabled" do subject.expects(:plist_from_label).returns([joblabel, {}]) subject.expects(:enabled?).returns(:false) subject.expects(:execute).with([:launchctl, :load, "-w", joblabel]).once subject.start end it "should disable the job once if the job is disabled and should be disabled at boot" do resource[:enable] = false subject.expects(:plist_from_label).returns([joblabel, {"Disabled" => true}]) subject.expects(:enabled?).returns :false subject.expects(:execute).with([:launchctl, :load, "-w", joblabel]) subject.expects(:disable).once subject.start end it "(#2773) should execute 'launchctl load -w' if the job is enabled but stopped" do subject.expects(:plist_from_label).returns([joblabel, {}]) subject.expects(:enabled?).returns(:true) subject.expects(:status).returns(:stopped) subject.expects(:execute).with([:launchctl, :load, '-w', joblabel]) subject.start end it "(#16271) Should stop and start the service when a restart is called" do subject.expects(:stop) subject.expects(:start) subject.restart end end describe "when stopping the service" do it "should call any explicit 'stop' command" do resource[:stop] = "/bin/false" subject.expects(:texecute).with(:stop, ["/bin/false"], true) subject.stop end it "should look for the relevant plist once" do subject.expects(:plist_from_label).returns([joblabel, {}]).once subject.expects(:enabled?).returns :true subject.expects(:execute).with([:launchctl, :unload, '-w', joblabel]) subject.stop end it "should execute 'launchctl unload' once without writing to the plist if the job is disabled" do subject.expects(:plist_from_label).returns([joblabel, {}]) subject.expects(:enabled?).returns :false subject.expects(:execute).with([:launchctl, :unload, joblabel]).once subject.stop end it "should execute 'launchctl unload' with writing to the plist once if the job is enabled" do subject.expects(:plist_from_label).returns([joblabel, {}]) subject.expects(:enabled?).returns :true subject.expects(:execute).with([:launchctl, :unload, '-w', joblabel]).once subject.stop end it "should enable the job once if the job is enabled and should be enabled at boot" do resource[:enable] = true subject.expects(:plist_from_label).returns([joblabel, {"Disabled" => false}]) subject.expects(:enabled?).returns :true subject.expects(:execute).with([:launchctl, :unload, "-w", joblabel]) subject.expects(:enable).once subject.stop end end describe "when enabling the service" do it "should look for the relevant plist once" do ### Do we need this test? Differentiating it? resource[:enable] = true subject.expects(:plist_from_label).returns([joblabel, {}]).once subject.expects(:enabled?).returns :false subject.expects(:execute).with([:launchctl, :unload, joblabel]) subject.stop end it "should check if the job is enabled once" do resource[:enable] = true subject.expects(:plist_from_label).returns([joblabel, {}]).once subject.expects(:enabled?).once subject.expects(:execute).with([:launchctl, :unload, joblabel]) subject.stop end end describe "when disabling the service" do it "should look for the relevant plist once" do resource[:enable] = false subject.expects(:plist_from_label).returns([joblabel, {}]).once subject.expects(:enabled?).returns :true subject.expects(:execute).with([:launchctl, :unload, '-w', joblabel]) subject.stop end end [[10, "10.6"], [13, "10.9"]].each do |kernel, version| describe "when enabling the service on OS X #{version}" do it "should write to the global launchd overrides file once" do resource[:enable] = true provider.expects(:get_os_version).returns(kernel).at_least_once plistlib.expects(:read_plist_file).with(launchd_overrides_6_9).returns({}) plistlib.expects(:write_plist_file).with(has_entry(resource[:name], {'Disabled' => false}), launchd_overrides_6_9).once subject.enable end end describe "when disabling the service on OS X #{version}" do it "should write to the global launchd overrides file once" do resource[:enable] = false provider.expects(:get_os_version).returns(kernel).at_least_once plistlib.expects(:read_plist_file).with(launchd_overrides_6_9).returns({}) plistlib.expects(:write_plist_file).with(has_entry(resource[:name], {'Disabled' => true}), launchd_overrides_6_9).once subject.disable end end end describe "when enabling the service on OS X 10.10" do it "should write to the global launchd overrides file once" do resource[:enable] = true provider.expects(:get_os_version).returns(14).at_least_once plistlib.expects(:read_plist_file).with(launchd_overrides_10_).returns({}) plistlib.expects(:write_plist_file).with(has_entry(resource[:name], false), launchd_overrides_10_).once subject.enable end end describe "when disabling the service on OS X 10.10" do it "should write to the global launchd overrides file once" do resource[:enable] = false provider.expects(:get_os_version).returns(14).at_least_once plistlib.expects(:read_plist_file).with(launchd_overrides_10_).returns({}) plistlib.expects(:write_plist_file).with(has_entry(resource[:name], true), launchd_overrides_10_).once subject.disable end end describe "make_label_to_path_map" do before do # clear out this class variable between runs if provider.instance_variable_defined? :@label_to_path_map provider.send(:remove_instance_variable, :@label_to_path_map) end end describe "when encountering malformed plists" do let(:plist_without_label) do { 'LimitLoadToSessionType' => 'Aqua' } end let(:busted_plist_path) { '/Library/LaunchAgents/org.busted.plist' } let(:binary_plist_path) { '/Library/LaunchAgents/org.binary.plist' } it "[17624] should warn that the plist in question is being skipped" do provider.expects(:launchd_paths).returns(['/Library/LaunchAgents']) provider.expects(:return_globbed_list_of_file_paths).with('/Library/LaunchAgents').returns([busted_plist_path]) plistlib.expects(:read_plist_file).with(busted_plist_path).returns(plist_without_label) Puppet.expects(:debug).with("Reading launchd plist #{busted_plist_path}") Puppet.expects(:debug).with("The #{busted_plist_path} plist does not contain a 'label' key; Puppet is skipping it") provider.make_label_to_path_map end end it "should return the cached value when available" do provider.instance_variable_set(:@label_to_path_map, {'xx'=>'yy'}) expect(provider.make_label_to_path_map).to eq({'xx'=>'yy'}) end describe "when successful" do let(:launchd_dir) { '/Library/LaunchAgents' } let(:plist) { launchd_dir + '/foo.bar.service.plist' } let(:label) { 'foo.bar.service' } before do provider.instance_variable_set(:@label_to_path_map, nil) provider.expects(:launchd_paths).returns([launchd_dir]) provider.expects(:return_globbed_list_of_file_paths).with(launchd_dir).returns([plist]) plistlib.expects(:read_plist_file).with(plist).returns({'Label'=>'foo.bar.service'}) end it "should read the plists and return their contents" do expect(provider.make_label_to_path_map).to eq({label=>plist}) end it "should re-read the plists and return their contents when refreshed" do provider.instance_variable_set(:@label_to_path_map, {'xx'=>'yy'}) expect(provider.make_label_to_path_map(true)).to eq({label=>plist}) end end end describe "jobsearch" do let(:map) { {"org.mozilla.puppet" => "/path/to/puppet.plist", "org.mozilla.python" => "/path/to/python.plist"} } it "returns the entire map with no args" do provider.expects(:make_label_to_path_map).returns(map) expect(provider.jobsearch).to eq(map) end it "returns a singleton hash when given a label" do provider.expects(:make_label_to_path_map).returns(map) expect(provider.jobsearch("org.mozilla.puppet")).to eq({ "org.mozilla.puppet" => "/path/to/puppet.plist" }) end it "refreshes the label_to_path_map when label is not found" do provider.expects(:make_label_to_path_map).with().returns({}) provider.expects(:make_label_to_path_map).with(true).returns(map) expect(provider.jobsearch("org.mozilla.puppet")).to eq({ "org.mozilla.puppet" => "/path/to/puppet.plist" }) end it "raises Puppet::Error when the label is still not found" do provider.expects(:make_label_to_path_map).with().returns(map) provider.expects(:make_label_to_path_map).with(true).returns(map) expect { provider.jobsearch("NOSUCH") }.to raise_error(Puppet::Error) end end describe "read_overrides" do before do Kernel.stubs(:sleep) end it "should read overrides" do provider.expects(:read_plist).once.returns({}) expect(provider.read_overrides).to eq({}) end it "should retry if read_plist fails" do provider.expects(:read_plist).once.returns({}) provider.expects(:read_plist).once.returns(nil) expect(provider.read_overrides).to eq({}) end it "raises Puppet::Error after 20 attempts" do provider.expects(:read_plist).times(20).returns(nil) expect { provider.read_overrides }.to raise_error(Puppet::Error) end end end puppet-5.5.10/spec/unit/provider/service/openbsd_spec.rb0000644005276200011600000002405313417161722023213 0ustar jenkinsjenkinsrequire 'spec_helper' describe Puppet::Type.type(:service).provider(:openbsd), :unless => Puppet.features.microsoft_windows? do before :each do Puppet::Type.type(:service).stubs(:defaultprovider).returns described_class Facter.stubs(:value).with(:operatingsystem).returns :openbsd Facter.stubs(:value).with(:osfamily).returns 'OpenBSD' FileTest.stubs(:file?).with('/usr/sbin/rcctl').returns true FileTest.stubs(:executable?).with('/usr/sbin/rcctl').returns true end context "#instances" do it "should have an instances method" do expect(described_class).to respond_to :instances end it "should list all available services" do described_class.stubs(:execpipe).with(['/usr/sbin/rcctl', :getall]).yields File.read(my_fixture('rcctl_getall')) expect(described_class.instances.map(&:name)).to eq([ 'accounting', 'pf', 'postgresql', 'tftpd', 'wsmoused', 'xdm', ]) end end context "#start" do it "should use the supplied start command if specified" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :start => '/bin/foo')) provider.expects(:execute).with(['/bin/foo'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.start end it "should start the service otherwise" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) provider.expects(:texecute).with(:start, ['/usr/sbin/rcctl', '-f', :start, 'sshd'], true) provider.start end end context "#stop" do it "should use the supplied stop command if specified" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :stop => '/bin/foo')) provider.expects(:execute).with(['/bin/foo'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.stop end it "should stop the service otherwise" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) provider.expects(:texecute).with(:stop, ['/usr/sbin/rcctl', :stop, 'sshd'], true) provider.stop end end context "#status" do it "should use the status command from the resource" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :status => '/bin/foo')) provider.expects(:execute).with(['/usr/sbin/rcctl', :get, 'sshd', :status], :failonfail => true, :override_locale => false, :squelch => false, :combine => true).never provider.expects(:execute).with(['/bin/foo'], :failonfail => false, :override_locale => false, :squelch => false, :combine => true) provider.status end it "should return :stopped when status command returns with a non-zero exitcode" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :status => '/bin/foo')) provider.expects(:execute).with(['/usr/sbin/rcctl', :get, 'sshd', :status], :failonfail => true, :override_locale => false, :squelch => false, :combine => true).never provider.expects(:execute).with(['/bin/foo'], :failonfail => false, :override_locale => false, :squelch => false, :combine => true) $CHILD_STATUS.stubs(:exitstatus).returns 3 expect(provider.status).to eq(:stopped) end it "should return :running when status command returns with a zero exitcode" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :status => '/bin/foo')) provider.expects(:execute).with(['/usr/sbin/rcctl', :get, 'sshd', :status], :failonfail => true, :override_locale => false, :squelch => false, :combine => true).never provider.expects(:execute).with(['/bin/foo'], :failonfail => false, :override_locale => false, :squelch => false, :combine => true) $CHILD_STATUS.stubs(:exitstatus).returns 0 expect(provider.status).to eq(:running) end end context "#restart" do it "should use the supplied restart command if specified" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :restart => '/bin/foo')) provider.expects(:execute).with(['/usr/sbin/rcctl', '-f', :restart, 'sshd'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true).never provider.expects(:execute).with(['/bin/foo'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.restart end it "should restart the service with rcctl restart if hasrestart is true" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :hasrestart => true)) provider.expects(:texecute).with(:restart, ['/usr/sbin/rcctl', '-f', :restart, 'sshd'], true) provider.restart end it "should restart the service with rcctl stop/start if hasrestart is false" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :hasrestart => false)) provider.expects(:texecute).with(:restart, ['/usr/sbin/rcctl', '-f', :restart, 'sshd'], true).never provider.expects(:texecute).with(:stop, ['/usr/sbin/rcctl', :stop, 'sshd'], true) provider.expects(:texecute).with(:start, ['/usr/sbin/rcctl', '-f', :start, 'sshd'], true) provider.restart end end context "#enabled?" do it "should return :true if the service is enabled" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) described_class.stubs(:rcctl).with(:get, 'sshd', :status) provider.expects(:execute).with(['/usr/sbin/rcctl', 'get', 'sshd', 'status'], :failonfail => false, :combine => false, :squelch => false).returns(stub(:exitstatus => 0)) expect(provider.enabled?).to eq(:true) end it "should return :false if the service is disabled" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) described_class.stubs(:rcctl).with(:get, 'sshd', :status).returns('NO') provider.expects(:execute).with(['/usr/sbin/rcctl', 'get', 'sshd', 'status'], :failonfail => false, :combine => false, :squelch => false).returns(stub(:exitstatus => 1)) expect(provider.enabled?).to eq(:false) end end context "#enable" do it "should run rcctl enable to enable the service" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) described_class.stubs(:rcctl).with(:enable, 'sshd').returns('') provider.expects(:rcctl).with(:enable, 'sshd') provider.enable end it "should run rcctl enable with flags if provided" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :flags => '-6')) described_class.stubs(:rcctl).with(:enable, 'sshd').returns('') described_class.stubs(:rcctl).with(:set, 'sshd', :flags, '-6').returns('') provider.expects(:rcctl).with(:enable, 'sshd') provider.expects(:rcctl).with(:set, 'sshd', :flags, '-6') provider.enable end end context "#disable" do it "should run rcctl disable to disable the service" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) described_class.stubs(:rcctl).with(:disable, 'sshd').returns('') provider.expects(:rcctl).with(:disable, 'sshd') provider.disable end end context "#running?" do it "should run rcctl check to check the service" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) described_class.stubs(:rcctl).with(:check, 'sshd').returns('sshd(ok)') provider.expects(:execute).with(['/usr/sbin/rcctl', 'check', 'sshd'], :failonfail => false, :combine => false, :squelch => false).returns('sshd(ok)') expect(provider.running?).to be_truthy end it "should return true if the service is running" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) described_class.stubs(:rcctl).with(:check, 'sshd').returns('sshd(ok)') provider.expects(:execute).with(['/usr/sbin/rcctl', 'check', 'sshd'], :failonfail => false, :combine => false, :squelch => false).returns('sshd(ok)') expect(provider.running?).to be_truthy end it "should return nil if the service is not running" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) described_class.stubs(:rcctl).with(:check, 'sshd').returns('sshd(failed)') provider.expects(:execute).with(['/usr/sbin/rcctl', 'check', 'sshd'], :failonfail => false, :combine => false, :squelch => false).returns('sshd(failed)') expect(provider.running?).to be_nil end end context "#flags" do it "should return flags when set" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :flags => '-6')) described_class.stubs(:rcctl).with('get', 'sshd', 'flags').returns('-6') provider.expects(:execute).with(['/usr/sbin/rcctl', 'get', 'sshd', 'flags'], :failonfail => false, :combine => false, :squelch => false).returns('-6') provider.flags end it "should return empty flags" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) described_class.stubs(:rcctl).with('get', 'sshd', 'flags').returns('') provider.expects(:execute).with(['/usr/sbin/rcctl', 'get', 'sshd', 'flags'], :failonfail => false, :combine => false, :squelch => false).returns('') provider.flags end it "should return flags for special services" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'pf')) described_class.stubs(:rcctl).with('get', 'pf', 'flags').returns('YES') provider.expects(:execute).with(['/usr/sbin/rcctl', 'get', 'pf', 'flags'], :failonfail => false, :combine => false, :squelch => false).returns('YES') provider.flags end end context "#flags=" do it "should run rcctl to set flags" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) described_class.stubs(:rcctl).with(:set, 'sshd', :flags, '-4').returns('') provider.expects(:rcctl).with(:set, 'sshd', :flags, '-4') provider.flags = '-4' end end end puppet-5.5.10/spec/unit/provider/service/openrc_spec.rb0000644005276200011600000002600013417161722023041 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:service).provider(:openrc) do if Puppet.features.microsoft_windows? # Get a pid for $CHILD_STATUS to latch on to cmd = "cmd.exe /c \"exit 0\"" Puppet::Util::Execution.execute(cmd, {:failonfail => false}) end before :each do Puppet::Type.type(:service).stubs(:defaultprovider).returns described_class ['/sbin/rc-service', '/bin/rc-status', '/sbin/rc-update'].each do |command| # Puppet::Util is both mixed in to providers and is also invoked directly # by Puppet::Provider::CommandDefiner, so we have to stub both out. described_class.stubs(:which).with(command).returns(command) Puppet::Util.stubs(:which).with(command).returns(command) end end describe ".instances" do it "should have an instances method" do expect(described_class).to respond_to :instances end it "should get a list of services from rc-service --list" do described_class.expects(:rcservice).with('-C','--list').returns File.read(my_fixture('rcservice_list')) expect(described_class.instances.map(&:name)).to eq([ 'alsasound', 'consolefont', 'lvm-monitoring', 'pydoc-2.7', 'pydoc-3.2', 'wpa_supplicant', 'xdm', 'xdm-setup' ]) end end describe "#start" do it "should use the supplied start command if specified" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :start => '/bin/foo')) provider.expects(:execute).with(['/bin/foo'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.start end it "should start the service with rc-service start otherwise" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) provider.expects(:execute).with(['/sbin/rc-service','sshd',:start], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.start end end describe "#stop" do it "should use the supplied stop command if specified" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :stop => '/bin/foo')) provider.expects(:execute).with(['/bin/foo'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.stop end it "should stop the service with rc-service stop otherwise" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) provider.expects(:execute).with(['/sbin/rc-service','sshd',:stop], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.stop end end describe 'when invoking `rc-status`' do subject { described_class.new(Puppet::Type.type(:service).new(:name => 'urandom')) } it "clears the RC_SVCNAME environment variable" do Puppet::Util.withenv(:RC_SVCNAME => 'puppet') do Puppet::Util::Execution.expects(:execute).with( includes('/bin/rc-status'), has_entry(:custom_environment, {:RC_SVCNAME => nil}) ).returns(Puppet::Util::Execution::ProcessOutput.new('', 0)) subject.enabled? end end end describe "#enabled?" do before :each do described_class.any_instance.stubs(:rcstatus).with('-C','-a').returns File.read(my_fixture('rcstatus')) end it "should run rc-status to get a list of enabled services" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) provider.expects(:rcstatus).with('-C','-a').returns "\n" provider.enabled? end ['hwclock', 'modules', 'urandom'].each do |service| it "should consider service #{service} in runlevel boot as enabled" do provider = described_class.new(Puppet::Type.type(:service).new(:name => service)) expect(provider.enabled?).to eq(:true) end end ['netmount', 'xdm', 'local', 'foo_with_very_very_long_servicename_no_still_not_the_end_wait_for_it_almost_there_almost_there_now_finally_the_end'].each do |service| it "should consider service #{service} in runlevel default as enabled" do provider = described_class.new(Puppet::Type.type(:service).new(:name => service)) expect(provider.enabled?).to eq(:true) end end ['net.eth0', 'pcscd'].each do |service| it "should consider service #{service} in dynamic runlevel: hotplugged as disabled" do provider = described_class.new(Puppet::Type.type(:service).new(:name => service)) expect(provider.enabled?).to eq(:false) end end ['sysfs', 'udev-mount'].each do |service| it "should consider service #{service} in dynamic runlevel: needed as disabled" do provider = described_class.new(Puppet::Type.type(:service).new(:name => service)) expect(provider.enabled?).to eq(:false) end end ['sshd'].each do |service| it "should consider service #{service} in dynamic runlevel: manual as disabled" do provider = described_class.new(Puppet::Type.type(:service).new(:name => service)) expect(provider.enabled?).to eq(:false) end end end describe "#enable" do it "should run rc-update add to enable a service" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) provider.expects(:rcupdate).with('-C', :add, 'sshd') provider.enable end end describe "#disable" do it "should run rc-update del to disable a service" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) provider.expects(:rcupdate).with('-C', :del, 'sshd') provider.disable end end describe "#status" do describe "when a special status command if specified" do it "should use the status command from the resource" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :status => '/bin/foo')) provider.expects(:execute).with(['/sbin/rc-service','sshd',:status], :failonfail => false, :override_locale => false, :squelch => false, :combine => true).never provider.expects(:execute).with(['/bin/foo'], :failonfail => false, :override_locale => false, :squelch => false, :combine => true) $CHILD_STATUS.stubs(:exitstatus).returns 0 provider.status end it "should return :stopped when status command returns with a non-zero exitcode" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :status => '/bin/foo')) provider.expects(:execute).with(['/sbin/rc-service','sshd',:status], :failonfail => false, :override_locale => false, :squelch => false, :combine => true).never provider.expects(:execute).with(['/bin/foo'], :failonfail => false, :override_locale => false, :squelch => false, :combine => true) $CHILD_STATUS.stubs(:exitstatus).returns 3 expect(provider.status).to eq(:stopped) end it "should return :running when status command returns with a zero exitcode" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :status => '/bin/foo')) provider.expects(:execute).with(['/sbin/rc-service','sshd',:status], :failonfail => false, :override_locale => false, :squelch => false, :combine => true).never provider.expects(:execute).with(['/bin/foo'], :failonfail => false, :override_locale => false, :squelch => false, :combine => true) $CHILD_STATUS.stubs(:exitstatus).returns 0 expect(provider.status).to eq(:running) end end describe "when hasstatus is false" do it "should return running if a pid can be found" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :hasstatus => false)) provider.expects(:execute).with(['/sbin/rc-service','sshd',:status], :failonfail => false, :override_locale => false, :squelch => false, :combine => true).never provider.expects(:getpid).returns 1000 expect(provider.status).to eq(:running) end it "should return stopped if no pid can be found" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :hasstatus => false)) provider.expects(:execute).with(['/sbin/rc-service','sshd',:status], :failonfail => false, :override_locale => false, :squelch => false, :combine => true).never provider.expects(:getpid).returns nil expect(provider.status).to eq(:stopped) end end describe "when hasstatus is true" do it "should return running if rc-service status exits with a zero exitcode" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :hasstatus => true)) provider.expects(:execute).with(['/sbin/rc-service','sshd',:status], :failonfail => false, :override_locale => false, :squelch => false, :combine => true) $CHILD_STATUS.stubs(:exitstatus).returns 0 expect(provider.status).to eq(:running) end it "should return stopped if rc-service status exits with a non-zero exitcode" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :hasstatus => true)) provider.expects(:execute).with(['/sbin/rc-service','sshd',:status], :failonfail => false, :override_locale => false, :squelch => false, :combine => true) $CHILD_STATUS.stubs(:exitstatus).returns 3 expect(provider.status).to eq(:stopped) end end end describe "#restart" do it "should use the supplied restart command if specified" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :restart => '/bin/foo')) provider.expects(:execute).with(['/sbin/rc-service','sshd',:restart], :failonfail => true, :override_locale => false, :squelch => false, :combine => true).never provider.expects(:execute).with(['/bin/foo'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.restart end it "should restart the service with rc-service restart if hasrestart is true" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :hasrestart => true)) provider.expects(:execute).with(['/sbin/rc-service','sshd',:restart], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.restart end it "should restart the service with rc-service stop/start if hasrestart is false" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :hasrestart => false)) provider.expects(:execute).with(['/sbin/rc-service','sshd',:restart], :failonfail => true, :override_locale => false, :squelch => false, :combine => true).never provider.expects(:execute).with(['/sbin/rc-service','sshd',:stop], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.expects(:execute).with(['/sbin/rc-service','sshd',:start], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.restart end end end puppet-5.5.10/spec/unit/provider/service/openwrt_spec.rb0000644005276200011600000000665113417161722023263 0ustar jenkinsjenkins#! /usr/bin/env ruby # # Unit testing for the OpenWrt service Provider # require 'spec_helper' describe Puppet::Type.type(:service).provider(:openwrt), :if => Puppet.features.posix? do let(:resource) do resource = stub 'resource' resource.stubs(:[]).returns(nil) resource.stubs(:[]).with(:name).returns "myservice" resource.stubs(:[]).with(:path).returns ["/etc/init.d"] resource end let(:provider) do provider = described_class.new provider.stubs(:get).with(:hasstatus).returns false provider end before :each do resource.stubs(:provider).returns provider provider.resource = resource FileTest.stubs(:file?).with('/etc/rc.common').returns true FileTest.stubs(:executable?).with('/etc/rc.common').returns true # All OpenWrt tests operate on the init script directly. It must exist. File.stubs(:directory?).with('/etc/init.d').returns true Puppet::FileSystem.stubs(:exist?).with('/etc/init.d/myservice').returns true FileTest.stubs(:file?).with('/etc/init.d/myservice').returns true FileTest.stubs(:executable?).with('/etc/init.d/myservice').returns true end operatingsystem = 'openwrt' it "should be the default provider on #{operatingsystem}" do Facter.expects(:value).with(:operatingsystem).returns(operatingsystem) expect(described_class.default?).to be_truthy end # test self.instances describe "when getting all service instances" do let(:services) {['dnsmasq', 'dropbear', 'firewall', 'led', 'puppet', 'uhttpd' ]} before :each do Dir.stubs(:entries).returns services FileTest.stubs(:directory?).returns(true) FileTest.stubs(:executable?).returns(true) end it "should return instances for all services" do services.each do |inst| described_class.expects(:new).with{|hash| hash[:name] == inst && hash[:path] == '/etc/init.d'}.returns("#{inst}_instance") end results = services.collect {|x| "#{x}_instance"} expect(described_class.instances).to eq(results) end end it "should have an enabled? method" do expect(provider).to respond_to(:enabled?) end it "should have an enable method" do expect(provider).to respond_to(:enable) end it "should have a disable method" do expect(provider).to respond_to(:disable) end [:start, :stop, :restart].each do |method| it "should have a #{method} method" do expect(provider).to respond_to(method) end describe "when running #{method}" do it "should use any provided explicit command" do resource.stubs(:[]).with(method).returns "/user/specified/command" provider.expects(:execute).with { |command, *args| command == ["/user/specified/command"] } provider.send(method) end it "should execute the init script with #{method} when no explicit command is provided" do resource.stubs(:[]).with("has#{method}".intern).returns :true provider.expects(:execute).with { |command, *args| command == ['/etc/init.d/myservice', method ]} provider.send(method) end end end describe "when checking status" do it "should consider the service :running if it has a pid" do provider.expects(:getpid).returns "1234" expect(provider.status).to eq(:running) end it "should consider the service :stopped if it doesn't have a pid" do provider.expects(:getpid).returns nil expect(provider.status).to eq(:stopped) end end end puppet-5.5.10/spec/unit/provider/service/rcng_spec.rb0000644005276200011600000000265413417161722022515 0ustar jenkinsjenkinsrequire 'spec_helper' describe Puppet::Type.type(:service).provider(:rcng), :unless => Puppet.features.microsoft_windows? do before :each do Puppet::Type.type(:service).stubs(:defaultprovider).returns described_class Facter.stubs(:value).with(:operatingsystem).returns :netbsd Facter.stubs(:value).with(:osfamily).returns 'NetBSD' described_class.stubs(:defpath).returns('/etc/rc.d') @provider = subject() @provider.stubs(:initscript) end context "#enable" do it "should have an enable method" do expect(@provider).to respond_to(:enable) end it "should set the proper contents to enable" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) Dir.stubs(:mkdir).with('/etc/rc.conf.d') fh = stub 'fh' Puppet::Util.expects(:replace_file).with('/etc/rc.conf.d/sshd', 0644).yields(fh) fh.expects(:puts).with("sshd=${sshd:=YES}\n") provider.enable end it "should set the proper contents to enable when disabled" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) Dir.stubs(:mkdir).with('/etc/rc.conf.d') File.stubs(:read).with('/etc/rc.conf.d/sshd').returns("sshd_enable=\"NO\"\n") fh = stub 'fh' Puppet::Util.expects(:replace_file).with('/etc/rc.conf.d/sshd', 0644).yields(fh) fh.expects(:puts).with("sshd=${sshd:=YES}\n") provider.enable end end end puppet-5.5.10/spec/unit/provider/service/redhat_spec.rb0000644005276200011600000001503113417161722023024 0ustar jenkinsjenkinsrequire 'spec_helper' describe Puppet::Type.type(:service).provider(:redhat), :if => Puppet.features.posix? do before :each do @class = Puppet::Type.type(:service).provider(:redhat) @resource = stub 'resource' @resource.stubs(:[]).returns(nil) @resource.stubs(:[]).with(:name).returns "myservice" @provider = subject() @resource.stubs(:provider).returns @provider @provider.resource = @resource @provider.stubs(:get).with(:hasstatus).returns false FileTest.stubs(:file?).with('/sbin/service').returns true FileTest.stubs(:executable?).with('/sbin/service').returns true Facter.stubs(:value).with(:operatingsystem).returns('CentOS') Facter.stubs(:value).with(:osfamily).returns 'RedHat' end osfamilies = [ 'RedHat' ] osfamilies.each do |osfamily| it "should be the default provider on #{osfamily}" do Facter.expects(:value).with(:osfamily).returns(osfamily) expect(described_class.default?).to be_truthy end end it "should be the default provider on sles11" do Facter.stubs(:value).with(:osfamily).returns(:suse) Facter.stubs(:value).with(:operatingsystem).returns(:suse) Facter.stubs(:value).with(:operatingsystemmajrelease).returns("11") expect(described_class.default?).to be_truthy end # test self.instances context "when getting all service instances" do before :each do @services = ['one', 'two', 'three', 'four', 'kudzu', 'functions', 'halt', 'killall', 'single', 'linuxconf', 'boot', 'reboot'] @not_services = ['functions', 'halt', 'killall', 'single', 'linuxconf', 'reboot', 'boot'] Dir.stubs(:entries).returns @services FileTest.stubs(:directory?).returns(true) FileTest.stubs(:executable?).returns(true) end it "should return instances for all services" do (@services-@not_services).each do |inst| @class.expects(:new).with{|hash| hash[:name] == inst && hash[:path] == '/etc/init.d'}.returns("#{inst}_instance") end results = (@services-@not_services).collect {|x| "#{x}_instance"} expect(@class.instances).to eq(results) end it "should call service status when initialized from provider" do @resource.stubs(:[]).with(:status).returns nil @provider.stubs(:get).with(:hasstatus).returns true @provider.expects(:execute).with{|command, *args| command == ['/sbin/service', 'myservice', 'status']} @provider.send(:status) end end it "should use '--add' and 'on' when calling enable" do described_class.expects(:chkconfig).with("--add", @resource[:name]) described_class.expects(:chkconfig).with(@resource[:name], :on) @provider.enable end it "(#15797) should explicitly turn off the service in all run levels" do described_class.expects(:chkconfig).with("--level", "0123456", @resource[:name], :off) @provider.disable end it "should have an enabled? method" do expect(@provider).to respond_to(:enabled?) end context "when checking enabled? on Suse" do before :each do Facter.expects(:value).with(:osfamily).returns 'Suse' end it "should check for on" do described_class.stubs(:chkconfig).with(@resource[:name]).returns "#{@resource[:name]} on" expect(@provider.enabled?).to eq(:true) end it "should check for B" do described_class.stubs(:chkconfig).with(@resource[:name]).returns "#{@resource[:name]} B" expect(@provider.enabled?).to eq(:true) end it "should check for off" do described_class.stubs(:chkconfig).with(@resource[:name]).returns "#{@resource[:name]} off" expect(@provider.enabled?).to eq(:false) end it "should check for unknown service" do described_class.stubs(:chkconfig).with(@resource[:name]).returns "#{@resource[:name]}: unknown service" expect(@provider.enabled?).to eq(:false) end end it "should have an enable method" do expect(@provider).to respond_to(:enable) end it "should have a disable method" do expect(@provider).to respond_to(:disable) end [:start, :stop, :status, :restart].each do |method| it "should have a #{method} method" do expect(@provider).to respond_to(method) end describe "when running #{method}" do it "should use any provided explicit command" do @resource.stubs(:[]).with(method).returns "/user/specified/command" @provider.expects(:execute).with { |command, *args| command == ["/user/specified/command"] } @provider.send(method) end it "should execute the service script with #{method} when no explicit command is provided" do @resource.stubs(:[]).with("has#{method}".intern).returns :true @provider.expects(:execute).with { |command, *args| command == ['/sbin/service', 'myservice', method.to_s]} @provider.send(method) end end end context "when checking status" do context "when hasstatus is :true" do before :each do @resource.stubs(:[]).with(:hasstatus).returns :true end it "should execute the service script with fail_on_failure false" do @provider.expects(:texecute).with(:status, ['/sbin/service', 'myservice', 'status'], false) @provider.status end it "should consider the process running if the command returns 0" do @provider.expects(:texecute).with(:status, ['/sbin/service', 'myservice', 'status'], false) $CHILD_STATUS.stubs(:exitstatus).returns(0) expect(@provider.status).to eq(:running) end [-10,-1,1,10].each { |ec| it "should consider the process stopped if the command returns something non-0" do @provider.expects(:texecute).with(:status, ['/sbin/service', 'myservice', 'status'], false) $CHILD_STATUS.stubs(:exitstatus).returns(ec) expect(@provider.status).to eq(:stopped) end } end context "when hasstatus is not :true" do it "should consider the service :running if it has a pid" do @provider.expects(:getpid).returns "1234" expect(@provider.status).to eq(:running) end it "should consider the service :stopped if it doesn't have a pid" do @provider.expects(:getpid).returns nil expect(@provider.status).to eq(:stopped) end end end context "when restarting and hasrestart is not :true" do it "should stop and restart the process with the server script" do @provider.expects(:texecute).with(:stop, ['/sbin/service', 'myservice', 'stop'], true) @provider.expects(:texecute).with(:start, ['/sbin/service', 'myservice', 'start'], true) @provider.restart end end end puppet-5.5.10/spec/unit/provider/service/runit_spec.rb0000644005276200011600000001116413417161722022721 0ustar jenkinsjenkinsrequire 'spec_helper' describe Puppet::Type.type(:service).provider(:runit) do before(:each) do # Create a mock resource @resource = stub 'resource' @provider = subject() @servicedir = "/etc/service" @provider.servicedir=@servicedir @daemondir = "/etc/sv" @provider.class.defpath=@daemondir # A catch all; no parameters set @resource.stubs(:[]).returns(nil) # But set name, source and path (because we won't run # the thing that will fetch the resource path from the provider) @resource.stubs(:[]).with(:name).returns "myservice" @resource.stubs(:[]).with(:ensure).returns :enabled @resource.stubs(:[]).with(:path).returns @daemondir @resource.stubs(:ref).returns "Service[myservice]" @provider.stubs(:sv) @provider.stubs(:resource).returns @resource end it "should have a restart method" do expect(@provider).to respond_to(:restart) end it "should have a restartcmd method" do expect(@provider).to respond_to(:restartcmd) end it "should have a start method" do expect(@provider).to respond_to(:start) end it "should have a stop method" do expect(@provider).to respond_to(:stop) end it "should have an enabled? method" do expect(@provider).to respond_to(:enabled?) end it "should have an enable method" do expect(@provider).to respond_to(:enable) end it "should have a disable method" do expect(@provider).to respond_to(:disable) end context "when starting" do it "should enable the service if it is not enabled" do @provider.stubs(:sv) @provider.expects(:enabled?).returns :false @provider.expects(:enable) @provider.stubs(:sleep) @provider.start end it "should execute external command 'sv start /etc/service/myservice'" do @provider.stubs(:enabled?).returns :true @provider.expects(:sv).with("start", "/etc/service/myservice") @provider.start end end context "when stopping" do it "should execute external command 'sv stop /etc/service/myservice'" do @provider.expects(:sv).with("stop", "/etc/service/myservice") @provider.stop end end context "when restarting" do it "should call 'sv restart /etc/service/myservice'" do @provider.expects(:sv).with("restart","/etc/service/myservice") @provider.restart end end context "when enabling" do it "should create a symlink between daemon dir and service dir", :if => Puppet.features.manages_symlinks? do daemon_path = File.join(@daemondir,"myservice") service_path = File.join(@servicedir,"myservice") Puppet::FileSystem.expects(:symlink?).with(service_path).returns(false) Puppet::FileSystem.expects(:symlink).with(daemon_path, File.join(@servicedir,"myservice")).returns(0) @provider.enable end end context "when disabling" do it "should remove the '/etc/service/myservice' symlink" do path = File.join(@servicedir,"myservice") # mocked_file = mock(path, :symlink? => true) FileTest.stubs(:directory?).returns(false) Puppet::FileSystem.expects(:symlink?).with(path).returns(true) # mocked_file) Puppet::FileSystem.expects(:unlink).with(path).returns(0) @provider.disable end end context "when checking status" do it "should call the external command 'sv status /etc/sv/myservice'" do @provider.expects(:sv).with('status',File.join(@daemondir,"myservice")) @provider.status end end context "when checking status" do it "and sv status fails, properly raise a Puppet::Error" do @provider.expects(:sv).with('status',File.join(@daemondir,"myservice")).raises(Puppet::ExecutionFailure, "fail: /etc/sv/myservice: file not found") expect { @provider.status }.to raise_error(Puppet::Error, 'Could not get status for service Service[myservice]: fail: /etc/sv/myservice: file not found') end it "and sv status returns up, then return :running" do @provider.expects(:sv).with('status',File.join(@daemondir,"myservice")).returns("run: /etc/sv/myservice: (pid 9029) 6s") expect(@provider.status).to eq(:running) end it "and sv status returns not running, then return :stopped" do @provider.expects(:sv).with('status',File.join(@daemondir,"myservice")).returns("fail: /etc/sv/myservice: runsv not running") expect(@provider.status).to eq(:stopped) end it "and sv status returns a warning, then return :stopped" do @provider.expects(:sv).with('status',File.join(@daemondir,"myservice")).returns("warning: /etc/sv/myservice: unable to open supervise/ok: file does not exist") expect(@provider.status).to eq(:stopped) end end end puppet-5.5.10/spec/unit/provider/service/smf_spec.rb0000644005276200011600000002713013417161722022345 0ustar jenkinsjenkinsrequire 'spec_helper' describe Puppet::Type.type(:service).provider(:smf), :if => Puppet.features.posix? do before(:each) do # Create a mock resource @resource = Puppet::Type.type(:service).new( :name => "/system/myservice", :ensure => :running, :enable => :true) @provider = described_class.new(@resource) FileTest.stubs(:file?).with('/usr/sbin/svcadm').returns true FileTest.stubs(:executable?).with('/usr/sbin/svcadm').returns true FileTest.stubs(:file?).with('/usr/bin/svcs').returns true FileTest.stubs(:executable?).with('/usr/bin/svcs').returns true Facter.stubs(:value).with(:operatingsystem).returns('Solaris') Facter.stubs(:value).with(:osfamily).returns('Solaris') Facter.stubs(:value).with(:operatingsystemrelease).returns '11.2' end context ".instances" do it "should have an instances method" do expect(described_class).to respond_to :instances end it "should get a list of services (excluding legacy)" do described_class.expects(:svcs).with('-H', '-o', 'state,fmri').returns File.read(my_fixture('svcs.out')) instances = described_class.instances.map { |p| {:name => p.get(:name), :ensure => p.get(:ensure)} } # we dont manage legacy expect(instances.size).to eq(3) expect(instances[0]).to eq({:name => 'svc:/system/svc/restarter:default', :ensure => :running }) expect(instances[1]).to eq({:name => 'svc:/network/cswrsyncd:default', :ensure => :maintenance }) expect(instances[2]).to eq({:name => 'svc:/network/dns/client:default', :ensure => :degraded }) end end it "should have a restart method" do expect(@provider).to respond_to(:restart) end it "should have a restartcmd method" do expect(@provider).to respond_to(:restartcmd) end it "should have a start method" do expect(@provider).to respond_to(:start) end it "should have a stop method" do expect(@provider).to respond_to(:stop) end it "should have an enabled? method" do expect(@provider).to respond_to(:enabled?) end it "should have an enable method" do expect(@provider).to respond_to(:enable) end it "should have a disable method" do expect(@provider).to respond_to(:disable) end describe "when checking status" do before(:each) do @provider.stubs(:complete_service?).returns(true) end it "should call the external command 'svcs /system/myservice' once" do @provider.expects(:svcs).with('-H', '-o', 'state,nstate', "/system/myservice").returns("online\t-") @provider.status end it "should return stopped if svcs can't find the service" do @provider.stubs(:svcs).raises(Puppet::ExecutionFailure.new("no svc found")) expect(@provider.status).to eq(:stopped) end it "should return stopped for an incomplete service on Solaris 11" do Facter.stubs(:value).with(:operatingsystemrelease).returns('11.3') @provider.stubs(:complete_service?).returns(false) expect(@provider.status).to eq(:stopped) end it "should return running if online in svcs output" do @provider.stubs(:svcs).returns("online\t-") expect(@provider.status).to eq(:running) end it "should return stopped if disabled in svcs output" do @provider.stubs(:svcs).returns("disabled\t-") expect(@provider.status).to eq(:stopped) end it "should return maintenance if in maintenance in svcs output" do @provider.stubs(:svcs).returns("maintenance\t-") expect(@provider.status).to eq(:maintenance) end it "should return degraded if in degraded in svcs output" do @provider.stubs(:svcs).returns("degraded\t-") expect(@provider.status).to eq(:degraded) end it "should return target state if transitioning in svcs output" do @provider.stubs(:svcs).returns("online\tdisabled") expect(@provider.status).to eq(:stopped) end it "should throw error if it's a legacy service in svcs output" do @provider.stubs(:svcs).returns("legacy_run\t-") expect { @provider.status }.to raise_error(Puppet::Error, "Cannot manage legacy services through SMF") end end context "when starting" do it "should enable the service if it is not enabled" do @provider.expects(:status).returns :stopped @provider.expects(:texecute).with(:start, ['/usr/sbin/svcadm', :enable, '-rs', '/system/myservice'], true) @provider.expects(:wait).with('online') @provider.start end it "should always execute external command 'svcadm enable /system/myservice'" do @provider.expects(:status).returns :running @provider.expects(:texecute).with(:start, ['/usr/sbin/svcadm', :enable, '-rs', '/system/myservice'], true) @provider.expects(:wait).with('online') @provider.start end it "should execute external command 'svcadm clear /system/myservice' if in maintenance" do @provider.stubs(:status).returns :maintenance @provider.expects(:texecute).with(:start, ["/usr/sbin/svcadm", :clear, "/system/myservice"], true) @provider.expects(:wait).with('online') @provider.start end it "should execute external command 'svcadm clear /system/myservice' if in degraded" do @provider.stubs(:status).returns :degraded @provider.expects(:texecute).with(:start, ["/usr/sbin/svcadm", :clear, "/system/myservice"], true) @provider.expects(:wait).with('online') @provider.start end it "should error if timeout occurs while starting the service" do @provider.expects(:status).returns :stopped @provider.expects(:texecute).with(:start, ["/usr/sbin/svcadm", :enable, '-rs', "/system/myservice"], true) Timeout.expects(:timeout).with(60).raises(Timeout::Error) expect { @provider.start }.to raise_error Puppet::Error, ('Timed out waiting for /system/myservice to transition states') end end context "when starting a service with a manifest" do before(:each) do @resource = Puppet::Type.type(:service).new(:name => "/system/myservice", :ensure => :running, :enable => :true, :manifest => "/tmp/myservice.xml") @provider = described_class.new(@resource) $CHILD_STATUS.stubs(:exitstatus).returns(1) end it "should import the manifest if service is missing" do @provider.stubs(:complete_service?).returns(true) @provider.expects(:svcs).with('-l', '/system/myservice').raises(Puppet::ExecutionFailure, "Exited 1") @provider.expects(:svccfg).with(:import, "/tmp/myservice.xml") @provider.expects(:texecute).with(:start, ["/usr/sbin/svcadm", :enable, '-rs', "/system/myservice"], true) @provider.expects(:wait).with('online') @provider.expects(:svcs).with('-H', '-o', 'state,nstate', "/system/myservice").returns("online\t-") @provider.start end it "should handle failures if importing a manifest" do @provider.expects(:svcs).with('-l', '/system/myservice').raises(Puppet::ExecutionFailure, "Exited 1") @provider.expects(:svccfg).raises(Puppet::ExecutionFailure.new("can't svccfg import")) expect { @provider.start }.to raise_error(Puppet::Error, "Cannot config /system/myservice to enable it: can't svccfg import") end end context "when stopping" do it "should execute external command 'svcadm disable /system/myservice'" do @provider.expects(:texecute).with(:stop, ["/usr/sbin/svcadm", :disable, '-s', "/system/myservice"], true) @provider.expects(:wait).with('offline', 'disabled', 'uninitialized') @provider.stop end it "should error if timeout occurs while stopping the service" do @provider.expects(:texecute).with(:stop, ["/usr/sbin/svcadm", :disable, '-s', "/system/myservice"], true) Timeout.expects(:timeout).with(60).raises(Timeout::Error) expect { @provider.stop }.to raise_error Puppet::Error, ('Timed out waiting for /system/myservice to transition states') end end context "when restarting" do it "should error if timeout occurs while restarting the service" do @provider.expects(:texecute).with(:restart, ["/usr/sbin/svcadm", :restart, '-s', "/system/myservice"], true) Timeout.expects(:timeout).with(60).raises(Timeout::Error) expect { @provider.restart }.to raise_error Puppet::Error, ('Timed out waiting for /system/myservice to transition states') end context 'with :operatingsystemrelease == 10_u10' do it "should call 'svcadm restart /system/myservice'" do Facter.stubs(:value).with(:operatingsystemrelease).returns '10_u10' @provider.expects(:texecute).with(:restart, ["/usr/sbin/svcadm", :restart, "/system/myservice"], true) @provider.expects(:wait).with('online') @provider.restart end end context 'with :operatingsystemrelease == 11.2' do it "should call 'svcadm restart -s /system/myservice'" do Facter.stubs(:value).with(:operatingsystemrelease).returns '11.2' @provider.expects(:texecute).with(:restart, ["/usr/sbin/svcadm", :restart, '-s', "/system/myservice"], true) @provider.expects(:wait).with('online') @provider.restart end end context 'with :operatingsystemrelease > 11.2' do it "should call 'svcadm restart -s /system/myservice'" do Facter.stubs(:value).with(:operatingsystemrelease).returns '11.3' @provider.expects(:texecute).with(:restart, ["/usr/sbin/svcadm", :restart, '-s', "/system/myservice"], true) @provider.expects(:wait).with('online') @provider.restart end end end describe '#service_fmri' do it 'raises a Puppet::Error if the service resource matches multiple FMRIs' do @provider.stubs(:svcs).with('-l', @provider.resource[:name]).returns(File.read(my_fixture('svcs_multiple_fmris.out'))) expect { @provider.service_fmri }.to raise_error do |error| expect(error).to be_a(Puppet::Error) expect(error.message).to match(@provider.resource[:name]) expect(error.message).to match('multiple') matched_fmris = ["svc:/application/tstapp:one", "svc:/application/tstapp:two"] expect(error.message).to match(matched_fmris.join(', ')) end end it 'raises a Puppet:ExecutionFailure if svcs fails' do @provider.stubs(:svcs).with('-l', @provider.resource[:name]).raises( Puppet::ExecutionFailure, 'svcs failed!' ) expect { @provider.service_fmri }.to raise_error do |error| expect(error).to be_a(Puppet::ExecutionFailure) expect(error.message).to match('svcs failed!') end end it "returns the service resource's fmri and memoizes it" do @provider.stubs(:svcs).with('-l', @provider.resource[:name]).returns(File.read(my_fixture('svcs_fmri.out'))) expected_fmri = 'svc:/application/tstapp:default' expect(@provider.service_fmri).to eql(expected_fmri) expect(@provider.instance_variable_get(:@fmri)).to eql(expected_fmri) end end describe '#complete_service?' do let(:fmri) { 'service_fmri' } before(:each) do @provider.stubs(:service_fmri).returns(fmri) end it 'should raise a Puppet::Error if it is called on an older Solaris machine' do Facter.stubs(:value).with(:operatingsystemrelease).returns('10.0') expect { @provider.complete_service? }.to raise_error(Puppet::Error) end it 'should return false for an incomplete service' do @provider.stubs(:svccfg).with('-s', fmri, 'listprop', 'general/complete').returns("") expect(@provider.complete_service?).to be false end it 'should return true for a complete service' do @provider.stubs(:svccfg) .with('-s', fmri, 'listprop', 'general/complete') .returns("general/complete astring") expect(@provider.complete_service?).to be true end end end puppet-5.5.10/spec/unit/provider/service/src_spec.rb0000644005276200011600000001730313417161722022350 0ustar jenkinsjenkinsrequire 'spec_helper' describe Puppet::Type.type(:service).provider(:src) do if Puppet.features.microsoft_windows? # Get a pid for $CHILD_STATUS to latch on to command = "cmd.exe /c \"exit 0\"" Puppet::Util::Execution.execute(command, {:failonfail => false}) end before :each do @resource = stub 'resource' @resource.stubs(:[]).returns(nil) @resource.stubs(:[]).with(:name).returns "myservice" @provider = subject() @provider.resource = @resource @provider.stubs(:command).with(:stopsrc).returns "/usr/bin/stopsrc" @provider.stubs(:command).with(:startsrc).returns "/usr/bin/startsrc" @provider.stubs(:command).with(:lssrc).returns "/usr/bin/lssrc" @provider.stubs(:command).with(:refresh).returns "/usr/bin/refresh" @provider.stubs(:command).with(:lsitab).returns "/usr/sbin/lsitab" @provider.stubs(:command).with(:mkitab).returns "/usr/sbin/mkitab" @provider.stubs(:command).with(:rmitab).returns "/usr/sbin/rmitab" @provider.stubs(:command).with(:chitab).returns "/usr/sbin/chitab" @provider.stubs(:stopsrc) @provider.stubs(:startsrc) @provider.stubs(:lssrc) @provider.stubs(:refresh) @provider.stubs(:lsitab) @provider.stubs(:mkitab) @provider.stubs(:rmitab) @provider.stubs(:chitab) end context ".instances" do it "should have a .instances method" do expect(described_class).to respond_to :instances end it "should get a list of running services" do sample_output = <<_EOF_ #subsysname:synonym:cmdargs:path:uid:auditid:standin:standout:standerr:action:multi:contact:svrkey:svrmtype:priority:signorm:sigforce:display:waittime:grpname: myservice.1:::/usr/sbin/inetd:0:0:/dev/console:/dev/console:/dev/console:-O:-Q:-K:0:0:20:0:0:-d:20:tcpip: myservice.2:::/usr/sbin/inetd:0:0:/dev/console:/dev/console:/dev/console:-O:-Q:-K:0:0:20:0:0:-d:20:tcpip: myservice.3:::/usr/sbin/inetd:0:0:/dev/console:/dev/console:/dev/console:-O:-Q:-K:0:0:20:0:0:-d:20:tcpip: myservice.4:::/usr/sbin/inetd:0:0:/dev/console:/dev/console:/dev/console:-O:-Q:-K:0:0:20:0:0:-d:20:tcpip: _EOF_ described_class.stubs(:lssrc).returns sample_output expect(described_class.instances.map(&:name)).to eq([ 'myservice.1', 'myservice.2', 'myservice.3', 'myservice.4' ]) end end context "when starting a service" do it "should execute the startsrc command" do @provider.expects(:execute).with(['/usr/bin/startsrc', '-s', "myservice"], {:override_locale => false, :squelch => false, :combine => true, :failonfail => true}) @provider.expects(:status).returns :running @provider.start end it "should error if timeout occurs while stopping the service" do @provider.expects(:execute).with(['/usr/bin/startsrc', '-s', "myservice"], {:override_locale => false, :squelch => false, :combine => true, :failonfail => true}) Timeout.expects(:timeout).with(60).raises(Timeout::Error) expect { @provider.start }.to raise_error Puppet::Error, ('Timed out waiting for myservice to transition states') end end context "when stopping a service" do it "should execute the stopsrc command" do @provider.expects(:execute).with(['/usr/bin/stopsrc', '-s', "myservice"], {:override_locale => false, :squelch => false, :combine => true, :failonfail => true}) @provider.expects(:status).returns :stopped @provider.stop end it "should error if timeout occurs while stopping the service" do @provider.expects(:execute).with(['/usr/bin/stopsrc', '-s', "myservice"], {:override_locale => false, :squelch => false, :combine => true, :failonfail => true}) Timeout.expects(:timeout).with(60).raises(Timeout::Error) expect { @provider.stop }.to raise_error Puppet::Error, ('Timed out waiting for myservice to transition states') end end context "should have a set of methods" do [:enabled?, :enable, :disable, :start, :stop, :status, :restart].each do |method| it "should have a #{method} method" do expect(@provider).to respond_to(method) end end end context "when enabling" do it "should execute the mkitab command" do @provider.expects(:mkitab).with("myservice:2:once:/usr/bin/startsrc -s myservice").once @provider.enable end end context "when disabling" do it "should execute the rmitab command" do @provider.expects(:rmitab).with("myservice") @provider.disable end end context "when checking if it is enabled" do it "should execute the lsitab command" do @provider.expects(:execute).with(['/usr/sbin/lsitab', 'myservice'], {:combine => true, :failonfail => false}) $CHILD_STATUS.stubs(:exitstatus).returns(0) @provider.enabled? end it "should return false when lsitab returns non-zero" do @provider.stubs(:execute) $CHILD_STATUS.stubs(:exitstatus).returns(1) expect(@provider.enabled?).to eq(:false) end it "should return true when lsitab returns zero" do @provider.stubs(:execute) $CHILD_STATUS.stubs(:exitstatus).returns(0) expect(@provider.enabled?).to eq(:true) end end context "when checking a subsystem's status" do it "should execute status and return running if the subsystem is active" do sample_output = <<_EOF_ Subsystem Group PID Status myservice tcpip 1234 active _EOF_ @provider.expects(:execute).with(['/usr/bin/lssrc', '-s', "myservice"]).returns sample_output expect(@provider.status).to eq(:running) end it "should execute status and return stopped if the subsystem is inoperative" do sample_output = <<_EOF_ Subsystem Group PID Status myservice tcpip inoperative _EOF_ @provider.expects(:execute).with(['/usr/bin/lssrc', '-s', "myservice"]).returns sample_output expect(@provider.status).to eq(:stopped) end it "should execute status and return nil if the status is not known" do sample_output = <<_EOF_ Subsystem Group PID Status myservice tcpip randomdata _EOF_ @provider.expects(:execute).with(['/usr/bin/lssrc', '-s', "myservice"]).returns sample_output expect(@provider.status).to eq(nil) end it "should consider a non-existing service to be have a status of :stopped" do @provider.expects(:execute).with(['/usr/bin/lssrc', '-s', 'myservice']).raises(Puppet::ExecutionFailure, "fail") expect(@provider.status).to eq(:stopped) end end context "when restarting a service" do it "should execute restart which runs refresh" do sample_output = <<_EOF_ #subsysname:synonym:cmdargs:path:uid:auditid:standin:standout:standerr:action:multi:contact:svrkey:svrmtype:priority:signorm:sigforce:display:waittime:grpname: myservice:::/usr/sbin/inetd:0:0:/dev/console:/dev/console:/dev/console:-O:-Q:-K:0:0:20:0:0:-d:20:tcpip: _EOF_ @provider.expects(:execute).with(['/usr/bin/lssrc', '-Ss', "myservice"]).returns sample_output @provider.expects(:execute).with(['/usr/bin/refresh', '-s', "myservice"]) @provider.restart end it "should execute restart which runs stop then start" do sample_output = <<_EOF_ #subsysname:synonym:cmdargs:path:uid:auditid:standin:standout:standerr:action:multi:contact:svrkey:svrmtype:priority:signorm:sigforce:display:waittime:grpname: myservice::--no-daemonize:/usr/sbin/puppetd:0:0:/dev/null:/var/log/puppet.log:/var/log/puppet.log:-O:-Q:-S:0:0:20:15:9:-d:20::" _EOF_ @provider.expects(:execute).with(['/usr/bin/lssrc', '-Ss', "myservice"]).returns sample_output @provider.expects(:stop) @provider.expects(:start) @provider.restart end end end puppet-5.5.10/spec/unit/provider/service/systemd_spec.rb0000644005276200011600000004633613417161722023261 0ustar jenkinsjenkins#! /usr/bin/env ruby # # Unit testing for the systemd service Provider # require 'spec_helper' describe Puppet::Type.type(:service).provider(:systemd) do if Puppet.features.microsoft_windows? # Get a pid for $CHILD_STATUS to latch on to command = "cmd.exe /c \"exit 0\"" Puppet::Util::Execution.execute(command, {:failonfail => false}) end before :each do Puppet::Type.type(:service).stubs(:defaultprovider).returns described_class described_class.stubs(:which).with('systemctl').returns '/bin/systemctl' end let :provider do described_class.new(:name => 'sshd.service') end osfamilies = [ 'archlinux', 'coreos' ] osfamilies.each do |osfamily| it "should be the default provider on #{osfamily}" do Facter.stubs(:value).with(:osfamily).returns(osfamily) expect(described_class).to be_default end end it "should be the default provider on rhel7" do Facter.stubs(:value).with(:osfamily).returns(:redhat) Facter.stubs(:value).with(:operatingsystemmajrelease).returns("7") expect(described_class).to be_default end [ 4, 5, 6 ].each do |ver| it "should not be the default provider on rhel#{ver}" do # In Ruby 1.8.7, the order of hash elements differs from 1.9+ and # caused short-circuiting of the logic used by default.all? in the # provider. As a workaround we need to use stubs() instead of # expects() here. Facter.stubs(:value).with(:osfamily).returns(:redhat) Facter.stubs(:value).with(:operatingsystem).returns(:redhat) Facter.stubs(:value).with(:operatingsystemmajrelease).returns("#{ver}") expect(described_class).not_to be_default end end [ 17, 18, 19, 20, 21, 22, 23 ].each do |ver| it "should be the default provider on fedora#{ver}" do Facter.stubs(:value).with(:osfamily).returns(:redhat) Facter.stubs(:value).with(:operatingsystem).returns(:fedora) Facter.stubs(:value).with(:operatingsystemmajrelease).returns("#{ver}") expect(described_class).to be_default end end it "should be the default provider on Amazon Linux 2.0" do Facter.stubs(:value).with(:osfamily).returns(:redhat) Facter.stubs(:value).with(:operatingsystem).returns(:amazon) Facter.stubs(:value).with(:operatingsystemmajrelease).returns("2") expect(described_class).to be_default end it "should not be the default provider on Amazon Linux 2017.09" do Facter.stubs(:value).with(:osfamily).returns(:redhat) Facter.stubs(:value).with(:operatingsystem).returns(:amazon) Facter.stubs(:value).with(:operatingsystemmajrelease).returns("2017") expect(described_class).not_to be_default end it "should be the default provider on cumulus3" do Facter.stubs(:value).with(:osfamily).returns(:debian) Facter.stubs(:value).with(:operatingsystem).returns('CumulusLinux') Facter.stubs(:value).with(:operatingsystemmajrelease).returns("3") expect(described_class).to be_default end it "should be the default provider on sles12" do Facter.stubs(:value).with(:osfamily).returns(:suse) Facter.stubs(:value).with(:operatingsystemmajrelease).returns("12") expect(described_class).to be_default end it "should be the default provider on opensuse13" do Facter.stubs(:value).with(:osfamily).returns(:suse) Facter.stubs(:value).with(:operatingsystemmajrelease).returns("13") expect(described_class).to be_default end # tumbleweed is a rolling release with date-based major version numbers it "should be the default provider on tumbleweed" do Facter.stubs(:value).with(:osfamily).returns(:suse) Facter.stubs(:value).with(:operatingsystemmajrelease).returns("20150829") expect(described_class).to be_default end # leap is the next generation suse release it "should be the default provider on leap" do Facter.stubs(:value).with(:osfamily).returns(:suse) Facter.stubs(:value).with(:operatingsystemmajrelease).returns("42") expect(described_class).to be_default end it "should not be the default provider on debian7" do Facter.stubs(:value).with(:osfamily).returns(:debian) Facter.stubs(:value).with(:operatingsystem).returns(:debian) Facter.stubs(:value).with(:operatingsystemmajrelease).returns("7") expect(described_class).not_to be_default end it "should be the default provider on debian8" do Facter.stubs(:value).with(:osfamily).returns(:debian) Facter.stubs(:value).with(:operatingsystem).returns(:debian) Facter.stubs(:value).with(:operatingsystemmajrelease).returns("8") expect(described_class).to be_default end it "should not be the default provider on ubuntu14.04" do Facter.stubs(:value).with(:osfamily).returns(:debian) Facter.stubs(:value).with(:operatingsystem).returns(:ubuntu) Facter.stubs(:value).with(:operatingsystemmajrelease).returns("14.04") expect(described_class).not_to be_default end [ '15.04', '15.10', '16.04', '16.10', '17.04', '17.10', '18.04' ].each do |ver| it "should be the default provider on ubuntu#{ver}" do Facter.stubs(:value).with(:osfamily).returns(:debian) Facter.stubs(:value).with(:operatingsystem).returns(:ubuntu) Facter.stubs(:value).with(:operatingsystemmajrelease).returns("#{ver}") expect(described_class).to be_default end end [:enabled?, :enable, :disable, :start, :stop, :status, :restart].each do |method| it "should have a #{method} method" do expect(provider).to respond_to(method) end end describe ".instances" do it "should have an instances method" do expect(described_class).to respond_to :instances end it "should return only services" do described_class.expects(:systemctl).with('list-unit-files', '--type', 'service', '--full', '--all', '--no-pager').returns File.read(my_fixture('list_unit_files_services')) expect(described_class.instances.map(&:name)).to match_array(%w{ arp-ethers.service auditd.service autovt@.service avahi-daemon.service blk-availability.service }) end end describe "#start" do it "should use the supplied start command if specified" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service', :start => '/bin/foo')) provider.expects(:execute).with(['/bin/foo'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.start end it "should start the service with systemctl start otherwise" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) provider.expects(:systemctl).with(:unmask, 'sshd.service') provider.expects(:execute).with(['/bin/systemctl','start','sshd.service'], {:failonfail => true, :override_locale => false, :squelch => false, :combine => true}) provider.start end it "should show journald logs on failure" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) provider.expects(:systemctl).with(:unmask, 'sshd.service') provider.expects(:execute).with(['/bin/systemctl','start','sshd.service'],{:failonfail => true, :override_locale => false, :squelch => false, :combine => true}) .raises(Puppet::ExecutionFailure, "Failed to start sshd.service: Unit sshd.service failed to load: Invalid argument. See system logs and 'systemctl status sshd.service' for details.") journalctl_logs = <<-EOS -- Logs begin at Tue 2016-06-14 11:59:21 UTC, end at Tue 2016-06-14 21:45:02 UTC. -- Jun 14 21:41:34 foo.example.com systemd[1]: Stopping sshd Service... Jun 14 21:41:35 foo.example.com systemd[1]: Starting sshd Service... Jun 14 21:43:23 foo.example.com systemd[1]: sshd.service lacks both ExecStart= and ExecStop= setting. Refusing. EOS provider.expects(:execute).with("journalctl -n 50 --since '5 minutes ago' -u sshd.service --no-pager").returns(journalctl_logs) expect { provider.start }.to raise_error(Puppet::Error, /Systemd start for sshd.service failed![\n]+journalctl log for sshd.service:[\n]+-- Logs begin at Tue 2016-06-14 11:59:21 UTC, end at Tue 2016-06-14 21:45:02 UTC. --/m) end end describe "#stop" do it "should use the supplied stop command if specified" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service', :stop => '/bin/foo')) provider.expects(:execute).with(['/bin/foo'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.stop end it "should stop the service with systemctl stop otherwise" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) provider.expects(:execute).with(['/bin/systemctl','stop','sshd.service'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.stop end it "should show journald logs on failure" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) provider.expects(:execute).with(['/bin/systemctl','stop','sshd.service'],{:failonfail => true, :override_locale => false, :squelch => false, :combine => true}) .raises(Puppet::ExecutionFailure, "Failed to stop sshd.service: Unit sshd.service failed to load: Invalid argument. See system logs and 'systemctl status sshd.service' for details.") journalctl_logs = <<-EOS -- Logs begin at Tue 2016-06-14 11:59:21 UTC, end at Tue 2016-06-14 21:45:02 UTC. -- Jun 14 21:41:34 foo.example.com systemd[1]: Stopping sshd Service... Jun 14 21:41:35 foo.example.com systemd[1]: Starting sshd Service... Jun 14 21:43:23 foo.example.com systemd[1]: sshd.service lacks both ExecStart= and ExecStop= setting. Refusing. EOS provider.expects(:execute).with("journalctl -n 50 --since '5 minutes ago' -u sshd.service --no-pager").returns(journalctl_logs) expect { provider.stop }.to raise_error(Puppet::Error, /Systemd stop for sshd.service failed![\n]+journalctl log for sshd.service:[\n]-- Logs begin at Tue 2016-06-14 11:59:21 UTC, end at Tue 2016-06-14 21:45:02 UTC. --/m) end end describe "#enabled?" do it "should return :true if the service is enabled" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) provider.expects(:execute).with(['/bin/systemctl','is-enabled','sshd.service'], :failonfail => false).returns "enabled\n" $CHILD_STATUS.stubs(:exitstatus).returns(0) expect(provider.enabled?).to eq(:true) end it "should return :true if the service is static" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) provider.expects(:execute).with(['/bin/systemctl','is-enabled','sshd.service'], :failonfail => false).returns "static\n" $CHILD_STATUS.stubs(:exitstatus).returns(0) expect(provider.enabled?).to eq(:true) end it "should return :false if the service is disabled" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) provider.expects(:execute).with(['/bin/systemctl','is-enabled','sshd.service'], :failonfail => false).returns "disabled\n" $CHILD_STATUS.stubs(:exitstatus).returns(1) expect(provider.enabled?).to eq(:false) end it "should return :false if the service is indirect" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) provider.expects(:execute).with(['/bin/systemctl','is-enabled','sshd.service'], :failonfail => false).returns "indirect\n" $CHILD_STATUS.stubs(:exitstatus).returns(0) expect(provider.enabled?).to eq(:false) end it "should return :false if the service is masked and the resource is attempting to be disabled" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service', :enable => false)) provider.expects(:execute).with(['/bin/systemctl','is-enabled','sshd.service'], :failonfail => false).returns "masked\n" $CHILD_STATUS.stubs(:exitstatus).returns(1) expect(provider.enabled?).to eq(:false) end it "should return :mask if the service is masked and the resource is attempting to be masked" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service', :enable => 'mask')) provider.expects(:execute).with(['/bin/systemctl','is-enabled','sshd.service'], :failonfail => false).returns "masked\n" $CHILD_STATUS.stubs(:exitstatus).returns(1) expect(provider.enabled?).to eq(:mask) end end describe "#enable" do it "should run systemctl enable to enable a service" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) provider.expects(:systemctl).with(:unmask, 'sshd.service') provider.expects(:systemctl).with(:enable, 'sshd.service') provider.enable end end describe "#disable" do it "should run systemctl disable to disable a service" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) provider.expects(:systemctl).with(:disable, 'sshd.service') provider.disable end end describe "#mask" do it "should run systemctl to disable and mask a service" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) # :disable is the only call in the provider that uses a symbol instead of # a string. # This should be made consistent in the future and all tests updated. provider.expects(:systemctl).with(:disable, 'sshd.service') provider.expects(:systemctl).with(:mask, 'sshd.service') provider.mask end end # Note: systemd provider does not care about hasstatus or a custom status # command. I just assume that it does not make sense for systemd. describe "#status" do it "should return running if if the command returns 0" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) provider.expects(:execute).with(['/bin/systemctl','is-active','sshd.service'], :failonfail => false, :override_locale => false, :squelch => false, :combine => true).returns "active\n" $CHILD_STATUS.stubs(:exitstatus).returns(0) expect(provider.status).to eq(:running) end [-10,-1,3,10].each { |ec| it "should return stopped if the command returns something non-0" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) provider.expects(:execute).with(['/bin/systemctl','is-active','sshd.service'], :failonfail => false, :override_locale => false, :squelch => false, :combine => true).returns "inactive\n" $CHILD_STATUS.stubs(:exitstatus).returns(ec) expect(provider.status).to eq(:stopped) end } it "should use the supplied status command if specified" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service', :status => '/bin/foo')) provider.expects(:execute).with(['/bin/foo'], :failonfail => false, :override_locale => false, :squelch => false, :combine => true) provider.status end end # Note: systemd provider does not care about hasrestart. I just assume it # does not make sense for systemd describe "#restart" do it "should use the supplied restart command if specified" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd', :restart => '/bin/foo')) provider.expects(:execute).with(['/bin/systemctl','restart','sshd.service'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true).never provider.expects(:execute).with(['/bin/foo'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.restart end it "should restart the service with systemctl restart" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) provider.expects(:execute).with(['/bin/systemctl','restart','sshd.service'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.restart end it "should show journald logs on failure" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) provider.expects(:execute).with(['/bin/systemctl','restart','sshd.service'],{:failonfail => true, :override_locale => false, :squelch => false, :combine => true}) .raises(Puppet::ExecutionFailure, "Failed to restart sshd.service: Unit sshd.service failed to load: Invalid argument. See system logs and 'systemctl status sshd.service' for details.") journalctl_logs = <<-EOS -- Logs begin at Tue 2016-06-14 11:59:21 UTC, end at Tue 2016-06-14 21:45:02 UTC. -- Jun 14 21:41:34 foo.example.com systemd[1]: Stopping sshd Service... Jun 14 21:41:35 foo.example.com systemd[1]: Starting sshd Service... Jun 14 21:43:23 foo.example.com systemd[1]: sshd.service lacks both ExecStart= and ExecStop= setting. Refusing. EOS provider.expects(:execute).with("journalctl -n 50 --since '5 minutes ago' -u sshd.service --no-pager").returns(journalctl_logs) expect { provider.restart }.to raise_error(Puppet::Error, /Systemd restart for sshd.service failed![\n]+journalctl log for sshd.service:[\n]+-- Logs begin at Tue 2016-06-14 11:59:21 UTC, end at Tue 2016-06-14 21:45:02 UTC. --/m) end end describe "#debian_enabled?" do [104, 106].each do |status| it "should return true when invoke-rc.d returns #{status}" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) provider.stubs(:system) $CHILD_STATUS.expects(:exitstatus).returns(status) expect(provider.debian_enabled?).to eq(:true) end end [101, 105].each do |status| it "should return true when status is #{status} and there are at least 4 start links" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) provider.stubs(:system) provider.expects(:get_start_link_count).returns(4) $CHILD_STATUS.expects(:exitstatus).twice.returns(status) expect(provider.debian_enabled?).to eq(:true) end it "should return false when status is #{status} and there are less than 4 start links" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) provider.stubs(:system) provider.expects(:get_start_link_count).returns(1) $CHILD_STATUS.expects(:exitstatus).twice.returns(status) expect(provider.debian_enabled?).to eq(:false) end end end describe "#get_start_link_count" do it "should strip the '.service' from the search if present in the resource name" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd.service')) Dir.expects(:glob).with("/etc/rc*.d/S??sshd").returns(['files']) provider.get_start_link_count end it "should use the full service name if it does not include '.service'" do provider = described_class.new(Puppet::Type.type(:service).new(:name => 'sshd')) Dir.expects(:glob).with("/etc/rc*.d/S??sshd").returns(['files']) provider.get_start_link_count end end it "(#16451) has command systemctl without being fully qualified" do expect(described_class.instance_variable_get(:@commands)).to include(:systemctl => 'systemctl') end end puppet-5.5.10/spec/unit/provider/service/upstart_spec.rb0000644005276200011600000006107513417161722023270 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:service).provider(:upstart) do let(:manual) { "\nmanual" } let(:start_on_default_runlevels) { "\nstart on runlevel [2,3,4,5]" } let(:provider_class) { Puppet::Type.type(:service).provider(:upstart) } if Puppet.features.microsoft_windows? # Get a pid for $CHILD_STATUS to latch on to command = "cmd.exe /c \"exit 0\"" Puppet::Util::Execution.execute(command, {:failonfail => false}) end def given_contents_of(file, content) File.open(file, 'w') do |f| f.write(content) end end def then_contents_of(file) File.open(file).read end def lists_processes_as(output) Puppet::Util::Execution.stubs(:execpipe).with("/sbin/initctl list").yields(output) provider_class.stubs(:which).with("/sbin/initctl").returns("/sbin/initctl") end it "should be the default provider on Ubuntu" do Facter.expects(:value).with(:operatingsystem).returns("Ubuntu") Facter.expects(:value).with(:operatingsystemmajrelease).returns("12.04") expect(described_class.default?).to be_truthy end context "upstart daemon existence confine" do # We have a separate method here because our search for the upstart daemon # confine expects it to be the last confine declared in the upstart provider. # If in the future we add other confines below it or change its order, these # unit tests will fail. Placing knowledge of where this confine is located # in one place makes updating it less painful in case we ever need to do this. def assert_upstart_daemon_existence_confine_is(expected_value) upstart_daemon_existence_confine = provider_class.confine_collection.instance_variable_get(:@confines)[-1] expect(upstart_daemon_existence_confine.valid?).to be(expected_value) end let(:initctl_version) { ['/sbin/initctl', 'version', '--quiet'] } before(:each) do # Stub out /sbin/initctl Puppet::Util.stubs(:which).with('/sbin/initctl').returns('/sbin/initctl') # Both of our tests are asserting the confine :true block that shells out to # `initctl version --quiet`. Its expression is evaluated at provider load-time. # Hence before each test, we want to reload the upstart provider so that the # confine is re-evaluated. Puppet::Type.type(:service).unprovide(:upstart) end it "should return true when the daemon is running" do Puppet::Util::Execution.expects(:execute).with(initctl_version, instance_of(Hash)) assert_upstart_daemon_existence_confine_is(true) end it "should return false when the daemon is not running" do Puppet::Util::Execution.expects(:execute) .with(initctl_version, instance_of(Hash)) .raises(Puppet::ExecutionFailure, "initctl failed!") assert_upstart_daemon_existence_confine_is(false) end end describe "excluding services" do it "ignores tty and serial on Redhat systems" do Facter.stubs(:value).with(:osfamily).returns('RedHat') expect(described_class.excludes).to include 'serial' expect(described_class.excludes).to include 'tty' end end describe "#instances" do it "should be able to find all instances" do lists_processes_as("rc stop/waiting\nssh start/running, process 712") expect(provider_class.instances.map {|provider| provider.name}).to match_array(["rc","ssh"]) end it "should attach the interface name for network interfaces" do lists_processes_as("network-interface (eth0)") expect(provider_class.instances.first.name).to eq("network-interface INTERFACE=eth0") end it "should attach the job name for network interface security" do processes = "network-interface-security (network-interface/eth0)" provider_class.stubs(:execpipe).yields(processes) expect(provider_class.instances.first.name).to eq("network-interface-security JOB=network-interface/eth0") end it "should not find excluded services" do processes = "wait-for-state stop/waiting" processes += "\nportmap-wait start/running" processes += "\nidmapd-mounting stop/waiting" processes += "\nstartpar-bridge start/running" processes += "\ncryptdisks-udev stop/waiting" processes += "\nstatd-mounting stop/waiting" processes += "\ngssd-mounting stop/waiting" provider_class.stubs(:execpipe).yields(processes) expect(provider_class.instances).to be_empty end end describe "#search" do it "searches through paths to find a matching conf file" do File.stubs(:directory?).returns(true) Puppet::FileSystem.stubs(:exist?).returns(false) Puppet::FileSystem.expects(:exist?).with("/etc/init/foo-bar.conf").returns(true) resource = Puppet::Type.type(:service).new(:name => "foo-bar", :provider => :upstart) provider = provider_class.new(resource) expect(provider.initscript).to eq("/etc/init/foo-bar.conf") end it "searches for just the name of a compound named service" do File.stubs(:directory?).returns(true) Puppet::FileSystem.stubs(:exist?).returns(false) Puppet::FileSystem.expects(:exist?).with("/etc/init/network-interface.conf").returns(true) resource = Puppet::Type.type(:service).new(:name => "network-interface INTERFACE=lo", :provider => :upstart) provider = provider_class.new(resource) expect(provider.initscript).to eq("/etc/init/network-interface.conf") end end describe "#status" do it "should use the default status command if none is specified" do resource = Puppet::Type.type(:service).new(:name => "foo", :provider => :upstart) provider = provider_class.new(resource) provider.stubs(:is_upstart?).returns(true) provider.expects(:status_exec).with(["foo"]).returns("foo start/running, process 1000") Process::Status.any_instance.stubs(:exitstatus).returns(0) expect(provider.status).to eq(:running) end describe "when a special status command is specifed" do it "should use the provided status command" do resource = Puppet::Type.type(:service).new(:name => 'foo', :provider => :upstart, :status => '/bin/foo') provider = provider_class.new(resource) provider.stubs(:is_upstart?).returns(true) provider.expects(:status_exec).with(['foo']).never provider.expects(:execute).with(['/bin/foo'], :failonfail => false, :override_locale => false, :squelch => false, :combine => true) Process::Status.any_instance.stubs(:exitstatus).returns(0) provider.status end it "should return :stopped when the provided status command return non-zero" do resource = Puppet::Type.type(:service).new(:name => 'foo', :provider => :upstart, :status => '/bin/foo') provider = provider_class.new(resource) provider.stubs(:is_upstart?).returns(true) provider.expects(:status_exec).with(['foo']).never provider.expects(:execute).with(['/bin/foo'], :failonfail => false, :override_locale => false, :squelch => false, :combine => true) $CHILD_STATUS.stubs(:exitstatus).returns 1 expect(provider.status).to eq(:stopped) end it "should return :running when the provided status command return zero" do resource = Puppet::Type.type(:service).new(:name => 'foo', :provider => :upstart, :status => '/bin/foo') provider = provider_class.new(resource) provider.stubs(:is_upstart?).returns(true) provider.expects(:status_exec).with(['foo']).never provider.expects(:execute).with(['/bin/foo'], :failonfail => false, :override_locale => false, :squelch => false, :combine => true) $CHILD_STATUS.stubs(:exitstatus).returns 0 expect(provider.status).to eq(:running) end end describe "when :hasstatus is set to false" do it "should return :stopped if the pid can not be found" do resource = Puppet::Type.type(:service).new(:name => 'foo', :hasstatus => false, :provider => :upstart) provider = provider_class.new(resource) provider.stubs(:is_upstart?).returns(true) provider.expects(:status_exec).with(['foo']).never provider.expects(:getpid).returns nil expect(provider.status).to eq(:stopped) end it "should return :running if the pid can be found" do resource = Puppet::Type.type(:service).new(:name => 'foo', :hasstatus => false, :provider => :upstart) provider = provider_class.new(resource) provider.stubs(:is_upstart?).returns(true) provider.expects(:status_exec).with(['foo']).never provider.expects(:getpid).returns 2706 expect(provider.status).to eq(:running) end end describe "when a special status command is specifed" do it "should use the provided status command" do resource = Puppet::Type.type(:service).new(:name => 'foo', :provider => :upstart, :status => '/bin/foo') provider = provider_class.new(resource) provider.stubs(:is_upstart?).returns(true) provider.expects(:status_exec).with(['foo']).never provider.expects(:execute).with(['/bin/foo'], :failonfail => false, :override_locale => false, :squelch => false, :combine => true) Process::Status.any_instance.stubs(:exitstatus).returns(0) provider.status end it "should return :stopped when the provided status command return non-zero" do resource = Puppet::Type.type(:service).new(:name => 'foo', :provider => :upstart, :status => '/bin/foo') provider = provider_class.new(resource) provider.stubs(:is_upstart?).returns(true) provider.expects(:status_exec).with(['foo']).never provider.expects(:execute).with(['/bin/foo'], :failonfail => false, :override_locale => false, :squelch => false, :combine => true) $CHILD_STATUS.stubs(:exitstatus).returns 1 expect(provider.status).to eq(:stopped) end it "should return :running when the provided status command return zero" do resource = Puppet::Type.type(:service).new(:name => 'foo', :provider => :upstart, :status => '/bin/foo') provider = provider_class.new(resource) provider.stubs(:is_upstart?).returns(true) provider.expects(:status_exec).with(['foo']).never provider.expects(:execute).with(['/bin/foo'], :failonfail => false, :override_locale => false, :squelch => false, :combine => true) $CHILD_STATUS.stubs(:exitstatus).returns 0 expect(provider.status).to eq(:running) end end describe "when :hasstatus is set to false" do it "should return :stopped if the pid can not be found" do resource = Puppet::Type.type(:service).new(:name => 'foo', :hasstatus => false, :provider => :upstart) provider = provider_class.new(resource) provider.stubs(:is_upstart?).returns(true) provider.expects(:status_exec).with(['foo']).never provider.expects(:getpid).returns nil expect(provider.status).to eq(:stopped) end it "should return :running if the pid can be found" do resource = Puppet::Type.type(:service).new(:name => 'foo', :hasstatus => false, :provider => :upstart) provider = provider_class.new(resource) provider.stubs(:is_upstart?).returns(true) provider.expects(:status_exec).with(['foo']).never provider.expects(:getpid).returns 2706 expect(provider.status).to eq(:running) end end it "should properly handle services with 'start' in their name" do resource = Puppet::Type.type(:service).new(:name => "foostartbar", :provider => :upstart) provider = provider_class.new(resource) provider.stubs(:is_upstart?).returns(true) provider.expects(:status_exec).with(["foostartbar"]).returns("foostartbar stop/waiting") Process::Status.any_instance.stubs(:exitstatus).returns(0) expect(provider.status).to eq(:stopped) end end describe "inheritance" do let :resource do Puppet::Type.type(:service).new(:name => "foo", :provider => :upstart) end let :provider do provider_class.new(resource) end describe "when upstart job" do before(:each) do provider.stubs(:is_upstart?).returns(true) end ["start", "stop"].each do |action| it "should return the #{action}cmd of its parent provider" do expect(provider.send("#{action}cmd".to_sym)).to eq([provider.command(action.to_sym), resource.name]) end end it "should return nil for the statuscmd" do expect(provider.statuscmd).to be_nil end end end describe "should be enableable" do let :resource do Puppet::Type.type(:service).new(:name => "foo", :provider => :upstart) end let :provider do provider_class.new(resource) end let :init_script do PuppetSpec::Files.tmpfile("foo.conf") end let :over_script do PuppetSpec::Files.tmpfile("foo.override") end let :disabled_content do "\t # \t start on\nother file stuff" end let :multiline_disabled do "# \t start on other file stuff (\n" + "# more stuff ( # )))))inline comment\n" + "# finishing up )\n" + "# and done )\n" + "this line shouldn't be touched\n" end let :multiline_disabled_bad do "# \t start on other file stuff (\n" + "# more stuff ( # )))))inline comment\n" + "# finishing up )\n" + "# and done )\n" + "# this is a comment i want to be a comment\n" + "this line shouldn't be touched\n" end let :multiline_enabled_bad do " \t start on other file stuff (\n" + " more stuff ( # )))))inline comment\n" + " finishing up )\n" + " and done )\n" + "# this is a comment i want to be a comment\n" + "this line shouldn't be touched\n" end let :multiline_enabled do " \t start on other file stuff (\n" + " more stuff ( # )))))inline comment\n" + " finishing up )\n" + " and done )\n" + "this line shouldn't be touched\n" end let :multiline_enabled_standalone do " \t start on other file stuff (\n" + " more stuff ( # )))))inline comment\n" + " finishing up )\n" + " and done )\n" end let :enabled_content do "\t \t start on\nother file stuff" end let :content do "just some text" end describe "Upstart version < 0.6.7" do before(:each) do provider.stubs(:is_upstart?).returns(true) provider.stubs(:upstart_version).returns("0.6.5") provider.stubs(:search).returns(init_script) end [:enabled?,:enable,:disable].each do |enableable| it "should respond to #{enableable}" do expect(provider).to respond_to(enableable) end end describe "when enabling" do it "should open and uncomment the '#start on' line" do given_contents_of(init_script, disabled_content) provider.enable expect(then_contents_of(init_script)).to eq(enabled_content) end it "should add a 'start on' line if none exists" do given_contents_of(init_script, "this is a file") provider.enable expect(then_contents_of(init_script)).to eq("this is a file" + start_on_default_runlevels) end it "should handle multiline 'start on' stanzas" do given_contents_of(init_script, multiline_disabled) provider.enable expect(then_contents_of(init_script)).to eq(multiline_enabled) end it "should leave not 'start on' comments alone" do given_contents_of(init_script, multiline_disabled_bad) provider.enable expect(then_contents_of(init_script)).to eq(multiline_enabled_bad) end end describe "when disabling" do it "should open and comment the 'start on' line" do given_contents_of(init_script, enabled_content) provider.disable expect(then_contents_of(init_script)).to eq("#" + enabled_content) end it "should handle multiline 'start on' stanzas" do given_contents_of(init_script, multiline_enabled) provider.disable expect(then_contents_of(init_script)).to eq(multiline_disabled) end end describe "when checking whether it is enabled" do it "should consider 'start on ...' to be enabled" do given_contents_of(init_script, enabled_content) expect(provider.enabled?).to eq(:true) end it "should consider '#start on ...' to be disabled" do given_contents_of(init_script, disabled_content) expect(provider.enabled?).to eq(:false) end it "should consider no start on line to be disabled" do given_contents_of(init_script, content) expect(provider.enabled?).to eq(:false) end end end describe "Upstart version < 0.9.0" do before(:each) do provider.stubs(:is_upstart?).returns(true) provider.stubs(:upstart_version).returns("0.7.0") provider.stubs(:search).returns(init_script) end [:enabled?,:enable,:disable].each do |enableable| it "should respond to #{enableable}" do expect(provider).to respond_to(enableable) end end describe "when enabling" do it "should open and uncomment the '#start on' line" do given_contents_of(init_script, disabled_content) provider.enable expect(then_contents_of(init_script)).to eq(enabled_content) end it "should add a 'start on' line if none exists" do given_contents_of(init_script, "this is a file") provider.enable expect(then_contents_of(init_script)).to eq("this is a file" + start_on_default_runlevels) end it "should handle multiline 'start on' stanzas" do given_contents_of(init_script, multiline_disabled) provider.enable expect(then_contents_of(init_script)).to eq(multiline_enabled) end it "should remove manual stanzas" do given_contents_of(init_script, multiline_enabled + manual) provider.enable expect(then_contents_of(init_script)).to eq(multiline_enabled) end it "should leave not 'start on' comments alone" do given_contents_of(init_script, multiline_disabled_bad) provider.enable expect(then_contents_of(init_script)).to eq(multiline_enabled_bad) end end describe "when disabling" do it "should add a manual stanza" do given_contents_of(init_script, enabled_content) provider.disable expect(then_contents_of(init_script)).to eq(enabled_content + manual) end it "should remove manual stanzas before adding new ones" do given_contents_of(init_script, multiline_enabled + manual + "\n" + multiline_enabled) provider.disable expect(then_contents_of(init_script)).to eq(multiline_enabled + "\n" + multiline_enabled + manual) end it "should handle multiline 'start on' stanzas" do given_contents_of(init_script, multiline_enabled) provider.disable expect(then_contents_of(init_script)).to eq(multiline_enabled + manual) end end describe "when checking whether it is enabled" do describe "with no manual stanza" do it "should consider 'start on ...' to be enabled" do given_contents_of(init_script, enabled_content) expect(provider.enabled?).to eq(:true) end it "should consider '#start on ...' to be disabled" do given_contents_of(init_script, disabled_content) expect(provider.enabled?).to eq(:false) end it "should consider no start on line to be disabled" do given_contents_of(init_script, content) expect(provider.enabled?).to eq(:false) end end describe "with manual stanza" do it "should consider 'start on ...' to be disabled if there is a trailing manual stanza" do given_contents_of(init_script, enabled_content + manual + "\nother stuff") expect(provider.enabled?).to eq(:false) end it "should consider two start on lines with a manual in the middle to be enabled" do given_contents_of(init_script, enabled_content + manual + "\n" + enabled_content) expect(provider.enabled?).to eq(:true) end end end end describe "Upstart version > 0.9.0" do before(:each) do provider.stubs(:is_upstart?).returns(true) provider.stubs(:upstart_version).returns("0.9.5") provider.stubs(:search).returns(init_script) provider.stubs(:overscript).returns(over_script) end [:enabled?,:enable,:disable].each do |enableable| it "should respond to #{enableable}" do expect(provider).to respond_to(enableable) end end describe "when enabling" do it "should add a 'start on' line if none exists" do given_contents_of(init_script, "this is a file") provider.enable expect(then_contents_of(init_script)).to eq("this is a file") expect(then_contents_of(over_script)).to eq(start_on_default_runlevels) end it "should handle multiline 'start on' stanzas" do given_contents_of(init_script, multiline_disabled) provider.enable expect(then_contents_of(init_script)).to eq(multiline_disabled) expect(then_contents_of(over_script)).to eq(start_on_default_runlevels) end it "should remove any manual stanzas from the override file" do given_contents_of(over_script, manual) given_contents_of(init_script, enabled_content) provider.enable expect(then_contents_of(init_script)).to eq(enabled_content) expect(then_contents_of(over_script)).to eq("") end it "should copy existing start on from conf file if conf file is disabled" do given_contents_of(init_script, multiline_enabled_standalone + manual) provider.enable expect(then_contents_of(init_script)).to eq(multiline_enabled_standalone + manual) expect(then_contents_of(over_script)).to eq(multiline_enabled_standalone) end it "should leave not 'start on' comments alone" do given_contents_of(init_script, multiline_disabled_bad) given_contents_of(over_script, "") provider.enable expect(then_contents_of(init_script)).to eq(multiline_disabled_bad) expect(then_contents_of(over_script)).to eq(start_on_default_runlevels) end end describe "when disabling" do it "should add a manual stanza to the override file" do given_contents_of(init_script, enabled_content) provider.disable expect(then_contents_of(init_script)).to eq(enabled_content) expect(then_contents_of(over_script)).to eq(manual) end it "should handle multiline 'start on' stanzas" do given_contents_of(init_script, multiline_enabled) provider.disable expect(then_contents_of(init_script)).to eq(multiline_enabled) expect(then_contents_of(over_script)).to eq(manual) end end describe "when checking whether it is enabled" do describe "with no override file" do it "should consider 'start on ...' to be enabled" do given_contents_of(init_script, enabled_content) expect(provider.enabled?).to eq(:true) end it "should consider '#start on ...' to be disabled" do given_contents_of(init_script, disabled_content) expect(provider.enabled?).to eq(:false) end it "should consider no start on line to be disabled" do given_contents_of(init_script, content) expect(provider.enabled?).to eq(:false) end end describe "with override file" do it "should consider 'start on ...' to be disabled if there is manual in override file" do given_contents_of(init_script, enabled_content) given_contents_of(over_script, manual + "\nother stuff") expect(provider.enabled?).to eq(:false) end it "should consider '#start on ...' to be enabled if there is a start on in the override file" do given_contents_of(init_script, disabled_content) given_contents_of(over_script, "start on stuff") expect(provider.enabled?).to eq(:true) end end end end end end puppet-5.5.10/spec/unit/provider/service/windows_spec.rb0000644005276200011600000001563613417161722023262 0ustar jenkinsjenkins#! /usr/bin/env ruby # # Unit testing for the Windows service Provider # require 'spec_helper' require 'win32/service' if Puppet.features.microsoft_windows? describe Puppet::Type.type(:service).provider(:windows), :if => Puppet.features.microsoft_windows? do let(:name) { 'nonexistentservice' } let(:resource) { Puppet::Type.type(:service).new(:name => name, :provider => :windows) } let(:provider) { resource.provider } let(:config) { Struct::ServiceConfigInfo.new } let(:status) { Struct::ServiceStatus.new } let(:service_util) { Puppet::Util::Windows::Service } let(:service_handle) { mock() } before :each do # make sure we never actually execute anything (there are two execute methods) provider.class.expects(:execute).never provider.expects(:execute).never service_util.stubs(:exists?).with(resource[:name]).returns(true) end describe ".instances" do it "should enumerate all services" do list_of_services = {'snmptrap' => {}, 'svchost' => {}, 'sshd' => {}} service_util.expects(:services).returns(list_of_services) expect(described_class.instances.map(&:name)).to match_array(['snmptrap', 'svchost', 'sshd']) end end describe "#start" do before(:each) do provider.stubs(:status).returns(:stopped) end it "should resume a paused service" do provider.stubs(:status).returns(:paused) service_util.expects(:resume).with(name) provider.start end it "should start the service" do service_util.expects(:service_start_type).with(name).returns(:SERVICE_AUTO_START) service_util.expects(:start).with(name) provider.start end context "when the service is disabled" do before :each do service_util.expects(:service_start_type).with(name).returns(:SERVICE_DISABLED) end it "should refuse to start if not managing enable" do expect { provider.start }.to raise_error(Puppet::Error, /Will not start disabled service/) end it "should enable if managing enable and enable is true" do resource[:enable] = :true service_util.expects(:start).with(name) service_util.expects(:set_startup_mode).with(name, :SERVICE_AUTO_START) provider.start end it "should manual start if managing enable and enable is false" do resource[:enable] = :false service_util.expects(:start).with(name) service_util.expects(:set_startup_mode).with(name, :SERVICE_DEMAND_START) provider.start end end end describe "#stop" do it "should stop a running service" do service_util.expects(:stop).with(name) provider.stop end end describe "#status" do it "should report a nonexistent service as stopped" do service_util.stubs(:exists?).with(resource[:name]).returns(false) expect(provider.status).to eql(:stopped) end [ :SERVICE_PAUSED, :SERVICE_PAUSE_PENDING ].each do |state| it "should report a #{state} service as paused" do service_util.expects(:service_state).with(name).returns(state) expect(provider.status).to eq(:paused) end end [ :SERVICE_STOPPED, :SERVICE_STOP_PENDING ].each do |state| it "should report a #{state} service as stopped" do service_util.expects(:service_state).with(name).returns(state) expect(provider.status).to eq(:stopped) end end [ :SERVICE_RUNNING, :SERVICE_CONTINUE_PENDING, :SERVICE_START_PENDING, ].each do |state| it "should report a #{state} service as running" do service_util.expects(:service_state).with(name).returns(state) expect(provider.status).to eq(:running) end end end describe "#restart" do it "should use the supplied restart command if specified" do resource[:restart] = 'c:/bin/foo' provider.expects(:execute).never provider.expects(:execute).with(['c:/bin/foo'], :failonfail => true, :override_locale => false, :squelch => false, :combine => true) provider.restart end it "should restart the service" do seq = sequence("restarting") provider.expects(:stop).in_sequence(seq) provider.expects(:start).in_sequence(seq) provider.restart end end describe "#enabled?" do it "should report a nonexistent service as false" do service_util.stubs(:exists?).with(resource[:name]).returns(false) expect(provider.enabled?).to eql(:false) end it "should report a service with a startup type of manual as manual" do service_util.expects(:service_start_type).with(name).returns(:SERVICE_DEMAND_START) expect(provider.enabled?).to eq(:manual) end it "should report a service with a startup type of disabled as false" do service_util.expects(:service_start_type).with(name).returns(:SERVICE_DISABLED) expect(provider.enabled?).to eq(:false) end # We need to guard this section explicitly since rspec will always # construct all examples, even if it isn't going to run them. if Puppet.features.microsoft_windows? [ :SERVICE_AUTO_START, :SERVICE_BOOT_START, :SERVICE_SYSTEM_START ].each do |start_type| it "should report a service with a startup type of '#{start_type}' as true" do service_util.expects(:service_start_type).with(name).returns(start_type) expect(provider.enabled?).to eq(:true) end end end end describe "#enable" do it "should set service start type to Service_Auto_Start when enabled" do service_util.expects(:set_startup_mode).with(name, :SERVICE_AUTO_START) provider.enable end it "raises an error if set_startup_mode fails" do service_util.expects(:set_startup_mode).with(name, :SERVICE_AUTO_START).raises(Puppet::Error.new('foobar')) expect { provider.enable }.to raise_error(Puppet::Error, /Cannot enable #{name}/) end end describe "#disable" do it "should set service start type to Service_Disabled when disabled" do service_util.expects(:set_startup_mode).with(name, :SERVICE_DISABLED) provider.disable end it "raises an error if set_startup_mode fails" do service_util.expects(:set_startup_mode).with(name, :SERVICE_DISABLED).raises(Puppet::Error.new('foobar')) expect { provider.disable }.to raise_error(Puppet::Error, /Cannot disable #{name}/) end end describe "#manual_start" do it "should set service start type to Service_Demand_Start (manual) when manual" do service_util.expects(:set_startup_mode).with(name, :SERVICE_DEMAND_START) provider.manual_start end it "raises an error if set_startup_mode fails" do service_util.expects(:set_startup_mode).with(name, :SERVICE_DEMAND_START).raises(Puppet::Error.new('foobar')) expect { provider.manual_start }.to raise_error(Puppet::Error, /Cannot enable #{name}/) end end end puppet-5.5.10/spec/unit/provider/user/0000755005276200011600000000000013417162177017541 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/provider/user/aix_spec.rb0000644005276200011600000001462613417161721021664 0ustar jenkinsjenkinsrequire 'spec_helper' describe 'Puppet::Type::User::Provider::Aix' do let(:provider_class) { Puppet::Type.type(:user).provider(:aix) } let(:group_provider_class) { Puppet::Type.type(:group).provider(:aix) } let(:resource) do Puppet::Type.type(:user).new( :name => 'test_aix_user', :ensure => :present ) end let(:provider) do provider_class.new(resource) end describe '.pgrp_to_gid' do it "finds the primary group's gid" do provider.stubs(:ia_module_args).returns(['-R', 'module']) group_provider_class.expects(:list_all) .with(provider.ia_module_args) .returns([{ :name => 'group', :id => 1}]) expect(provider_class.pgrp_to_gid(provider, 'group')).to eql(1) end end describe '.gid_to_pgrp' do it "finds the gid's primary group" do provider.stubs(:ia_module_args).returns(['-R', 'module']) group_provider_class.expects(:list_all) .with(provider.ia_module_args) .returns([{ :name => 'group', :id => 1}]) expect(provider_class.gid_to_pgrp(provider, 1)).to eql('group') end end describe '.expires_to_expiry' do it 'returns absent if expires is 0' do expect(provider_class.expires_to_expiry(provider, '0')).to eql(:absent) end it 'returns absent if the expiry attribute is not formatted properly' do expect(provider_class.expires_to_expiry(provider, 'bad_format')).to eql(:absent) end it 'returns the password expiration date' do expect(provider_class.expires_to_expiry(provider, '0910122314')).to eql('2014-09-10') end end describe '.expiry_to_expires' do it 'returns 0 if the expiry date is 0000-00-00' do expect(provider_class.expiry_to_expires('0000-00-00')).to eql('0') end it 'returns 0 if the expiry date is "absent"' do expect(provider_class.expiry_to_expires('absent')).to eql('0') end it 'returns 0 if the expiry date is :absent' do expect(provider_class.expiry_to_expires(:absent)).to eql('0') end it 'returns the expires attribute value' do expect(provider_class.expiry_to_expires('2014-09-10')).to eql('0910000014') end end describe '.groups_attribute_to_property' do it "reads the user's groups from the etc/groups file" do groups = ['system', 'adm'] Puppet::Util::POSIX.stubs(:groups_of).with(resource[:name]).returns(groups) actual_groups = provider_class.groups_attribute_to_property(provider, 'unused_value') expected_groups = groups.join(',') expect(actual_groups).to eql(expected_groups) end end describe '.groups_property_to_attribute' do it 'raises an ArgumentError if the groups are space-separated' do groups = "foo bar baz" expect do provider_class.groups_property_to_attribute(groups) end.to raise_error do |error| expect(error).to be_a(ArgumentError) expect(error.message).to match(groups) expect(error.message).to match("Groups") end end end describe '#gid=' do let(:value) { 'new_pgrp' } let(:old_pgrp) { 'old_pgrp' } let(:cur_groups) { 'system,adm' } before(:each) do provider.stubs(:gid).returns(old_pgrp) provider.stubs(:groups).returns(cur_groups) provider.stubs(:set) end it 'raises a Puppet::Error if it fails to set the groups property' do provider.stubs(:set) .with(:groups, cur_groups) .raises(Puppet::ExecutionFailure, 'failed to reset the groups!') expect { provider.gid = value }.to raise_error do |error| expect(error).to be_a(Puppet::Error) expect(error.message).to match('groups') expect(error.message).to match(cur_groups) expect(error.message).to match(old_pgrp) expect(error.message).to match(value) end end end describe '#parse_password' do def call_parse_password File.open(my_fixture('aix_passwd_file.out')) do |f| provider.parse_password(f) end end it "returns :absent if the user stanza doesn't exist" do resource[:name] = 'nonexistent_user' expect(call_parse_password).to eql(:absent) end it "returns absent if the user does not have a password" do resource[:name] = 'no_password_user' expect(call_parse_password).to eql(:absent) end it "returns the user's password" do expect(call_parse_password).to eql('some_password') end end # TODO: If we move from using Mocha to rspec's mocks, # or a better and more robust mocking library, we should # remove #parse_password and copy over its tests to here. describe '#password' do end describe '#password=' do let(:mock_tempfile) do mock_tempfile_obj = mock() mock_tempfile_obj.stubs(:<<) mock_tempfile_obj.stubs(:close) mock_tempfile_obj.stubs(:delete) mock_tempfile_obj.stubs(:path).returns('tempfile_path') Tempfile.stubs(:new) .with("puppet_#{provider.name}_pw", :encoding => Encoding::ASCII) .returns(mock_tempfile_obj) mock_tempfile_obj end let(:cmd) do [provider.class.command(:chpasswd), *provider.ia_module_args, '-e', '-c'] end let(:execute_options) do { :failonfail => false, :combine => true, :stdinfile => mock_tempfile.path } end it 'raises a Puppet::Error if chpasswd fails' do provider.stubs(:execute).with(cmd, execute_options).returns("failed to change passwd!") expect { provider.password = 'foo' }.to raise_error do |error| expect(error).to be_a(Puppet::Error) expect(error.message).to match("failed to change passwd!") end end it "changes the user's password" do provider.expects(:execute).with(cmd, execute_options).returns("") provider.password = 'foo' end it "closes and deletes the tempfile" do provider.stubs(:execute).with(cmd, execute_options).returns("") mock_tempfile.expects(:close).times(2) mock_tempfile.expects(:delete) provider.password = 'foo' end end describe '#create' do it 'should create the user' do provider.resource.stubs(:should).with(anything).returns(nil) provider.resource.stubs(:should).with(:groups).returns('g1,g2') provider.resource.stubs(:should).with(:password).returns('password') provider.expects(:execute) provider.expects(:groups=).with('g1,g2') provider.expects(:password=).with('password') provider.create end end end puppet-5.5.10/spec/unit/provider/user/directoryservice_spec.rb0000644005276200011600000016537413417161721024477 0ustar jenkinsjenkins#! /usr/bin/env ruby -S rspec # encoding: ASCII-8BIT require 'spec_helper' module Puppet::Util::Plist end describe Puppet::Type.type(:user).provider(:directoryservice) do let(:username) { 'nonexistent_user' } let(:user_path) { "/Users/#{username}" } let(:resource) do Puppet::Type.type(:user).new( :name => username, :provider => :directoryservice ) end let(:provider) { resource.provider } let(:users_plist_dir) { '/var/db/dslocal/nodes/Default/users' } # This is the output of doing `dscl -plist . read /Users/` which # will return a hash of keys whose values are all arrays. let(:user_plist_xml) do ' dsAttrTypeStandard:NFSHomeDirectory /Users/nonexistent_user dsAttrTypeStandard:RealName nonexistent_user dsAttrTypeStandard:PrimaryGroupID 22 dsAttrTypeStandard:UniqueID 1000 dsAttrTypeStandard:RecordName nonexistent_user ' end # This is the same as above, however in a native Ruby hash instead # of XML let(:user_plist_hash) do { "dsAttrTypeStandard:RealName" => [username], "dsAttrTypeStandard:NFSHomeDirectory" => [user_path], "dsAttrTypeStandard:PrimaryGroupID" => ["22"], "dsAttrTypeStandard:UniqueID" => ["1000"], "dsAttrTypeStandard:RecordName" => [username] } end let(:sha512_shadowhashdata_array) do ['62706c69 73743030 d101025d 53414c54 45442d53 48413531 324f1044 7ea7d592 131f57b2 c8f8bdbc '\ 'ec8d9df1 2128a386 393a4f00 c7619bac 2622a44d 451419d1 1da512d5 915ab98e 39718ac9 4083fe2e '\ 'fd6bf710 a54d477f 8ff735b1 2587192d 080b1900 00000000 00010100 00000000 00000300 00000000 '\ '00000000 00000000 000060'] end # The below value is the result of executing # `dscl -plist . read /Users/ ShadowHashData` on a 10.7 # system and converting it to a native Ruby Hash with Plist.parse_xml let(:sha512_shadowhashdata_hash) do { 'dsAttrTypeNative:ShadowHashData' => sha512_shadowhashdata_array } end # The below is a binary plist that is stored in the ShadowHashData key # on a 10.7 system. let(:sha512_embedded_bplist) do "bplist00\321\001\002]SALTED-SHA512O\020D~\247\325\222\023\037W\262\310\370\275\274\354\215\235\361!(\243\2069:O\000\307a\233\254&\"\244ME\024\031\321\035\245\022\325\221Z\271\2169q\212\311@\203\376.\375k\367\020\245MG\177\217\3675\261%\207\031-\b\v\031\000\000\000\000\000\000\001\001\000\000\000\000\000\000\000\003\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000`" end # The below is a Base64 encoded string representing a salted-SHA512 password # hash. let(:sha512_pw_string) do "~\247\325\222\023\037W\262\310\370\275\274\354\215\235\361!(\243\2069:O\000\307a\233\254&\"\244ME\024\031\321\035\245\022\325\221Z\271\2169q\212\311@\203\376.\375k\367\020\245MG\177\217\3675\261%\207\031-" end # The below is the result of converting sha512_embedded_bplist to XML and # parsing it with Plist.parse_xml. It is a Ruby Hash whose value is a # Base64 encoded salted-SHA512 password hash. let(:sha512_embedded_bplist_hash) do { 'SALTED-SHA512' => sha512_pw_string } end # The value below is the result of converting sha512_pw_string to Hex. let(:sha512_password_hash) do '7ea7d592131f57b2c8f8bdbcec8d9df12128a386393a4f00c7619bac2622a44d451419d11da512d5915ab98e39718ac94083fe2efd6bf710a54d477f8ff735b12587192d' end let(:pbkdf2_shadowhashdata_array) do ['62706c69 73743030 d101025f 10145341 4c544544 2d534841 3531322d 50424b44 4632d303 04050607 '\ '0857656e 74726f70 79547361 6c745a69 74657261 74696f6e 734f1080 0590ade1 9e6953c1 35ae872a '\ 'e7761823 5df7d46c 63de7f9a 0fcdf2cd 9e7d85e4 b7ca8681 01235b61 58e05a30 9805ee48 14b027a4 '\ 'be9c23ec 2926bc81 72269aff ba5c9a59 85e81091 fa689807 6d297f1f aa75fa61 7551ef16 71d75200 '\ '55c4a0d9 7b9b9c58 05aa322b aedbcd8e e9c52381 1653ac2e a9e9c8d8 f1ac519a 0f2b595e 4f102093 '\ '77c46908 a1c8ac2c 3e45c0d4 4da8ad0f cd85ec5c 14d9a59f fc40c9da 31f0ec11 60b0080b 22293136 '\ '41c4e700 00000000 00010100 00000000 00000900 00000000 00000000 00000000 0000ea'] end # The below value is the result of executing # `dscl -plist . read /Users/ ShadowHashData` on a 10.8 # system and converting it to a native Ruby Hash with Plist.parse_xml let(:pbkdf2_shadowhashdata_hash) do { "dsAttrTypeNative:ShadowHashData"=> pbkdf2_shadowhashdata_array } end # The below value is the result of converting pbkdf2_embedded_bplist to XML and # parsing it with Plist.parse_xml. let(:pbkdf2_embedded_bplist_hash) do { 'SALTED-SHA512-PBKDF2' => { 'entropy' => pbkdf2_pw_string, 'salt' => pbkdf2_salt_string, 'iterations' => pbkdf2_iterations_value } } end # The value below is the result of converting pbkdf2_pw_string to Hex. let(:pbkdf2_password_hash) do '0590ade19e6953c135ae872ae77618235df7d46c63de7f9a0fcdf2cd9e7d85e4b7ca868101235b6158e05a309805ee4814b027a4be9c23ec2926bc8172269affba5c9a5985e81091fa6898076d297f1faa75fa617551ef1671d7520055c4a0d97b9b9c5805aa322baedbcd8ee9c523811653ac2ea9e9c8d8f1ac519a0f2b595e' end # The below is a binary plist that is stored in the ShadowHashData key # of a 10.8 system. let(:pbkdf2_embedded_plist) do "bplist00\321\001\002_\020\024SALTED-SHA512-PBKDF2\323\003\004\005\006\a\bWentropyTsaltZiterationsO\020\200\005\220\255\341\236iS\3015\256\207*\347v\030#]\367\324lc\336\177\232\017\315\362\315\236}\205\344\267\312\206\201\001#[aX\340Z0\230\005\356H\024\260'\244\276\234#\354)&\274\201r&\232\377\272\\\232Y\205\350\020\221\372h\230\am)\177\037\252u\372auQ\357\026q\327R\000U\304\240\331{\233\234X\005\2522+\256\333\315\216\351\305#\201\026S\254.\251\351\310\330\361\254Q\232\017+Y^O\020 \223w\304i\b\241\310\254,>E\300\324M\250\255\017\315\205\354\\\024\331\245\237\374@\311\3321\360\354\021`\260\b\v\")16A\304\347\000\000\000\000\000\000\001\001\000\000\000\000\000\000\000\t\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\352" end # The below value is a Base64 encoded string representing a PBKDF2 password # hash. let(:pbkdf2_pw_string) do "\005\220\255\341\236iS\3015\256\207*\347v\030#]\367\324lc\336\177\232\017\315\362\315\236}\205\344\267\312\206\201\001#[aX\340Z0\230\005\356H\024\260'\244\276\234#\354)&\274\201r&\232\377\272\\\232Y\205\350\020\221\372h\230\am)\177\037\252u\372auQ\357\026q\327R\000U\304\240\331{\233\234X\005\2522+\256\333\315\216\351\305#\201\026S\254.\251\351\310\330\361\254Q\232\017+Y^" end # The below value is a Base64 encoded string representing a PBKDF2 salt # string. let(:pbkdf2_salt_string) do "\223w\304i\b\241\310\254,>E\300\324M\250\255\017\315\205\354\\\024\331\245\237\374@\311\3321\360\354" end # The below value represents the Hex value of a PBKDF2 salt string let(:pbkdf2_salt_value) do "9377c46908a1c8ac2c3e45c0d44da8ad0fcd85ec5c14d9a59ffc40c9da31f0ec" end # The below value is an Integer iterations value used in the PBKDF2 # key stretching algorithm let(:pbkdf2_iterations_value) do 24752 end let(:pbkdf2_and_ssha512_shadowhashdata_array) do ['62706c69 73743030 d2010203 0a5f1014 53414c54 45442d53 48413531 322d5042 4b444632 5d53414c '\ '5445442d 53484135 3132d304 05060708 0957656e 74726f70 79547361 6c745a69 74657261 74696f6e '\ '734f1080 0590ade1 9e6953c1 35ae872a e7761823 5df7d46c 63de7f9a 0fcdf2cd 9e7d85e4 b7ca8681 '\ '01235b61 58e05a30 9805ee48 14b027a4 be9c23ec 2926bc81 72269aff ba5c9a59 85e81091 fa689807 '\ '6d297f1f aa75fa61 7551ef16 71d75200 55c4a0d9 7b9b9c58 05aa322b aedbcd8e e9c52381 1653ac2e '\ 'a9e9c8d8 f1ac519a 0f2b595e 4f102093 77c46908 a1c8ac2c 3e45c0d4 4da8ad0f cd85ec5c 14d9a59f '\ 'fc40c9da 31f0ec11 60b04f10 447ea7d5 92131f57 b2c8f8bd bcec8d9d f12128a3 86393a4f 00c7619b '\ 'ac2622a4 4d451419 d11da512 d5915ab9 8e39718a c94083fe 2efd6bf7 10a54d47 7f8ff735 b1258719 '\ '2d000800 0d002400 32003900 41004600 5100d400 f700fa00 00000000 00020100 00000000 00000b00 '\ '00000000 00000000 00000000 000141'] end let(:pbkdf2_and_ssha512_shadowhashdata_hash) do { 'dsAttrTypeNative:ShadowHashData' => pbkdf2_and_ssha512_shadowhashdata_array } end let (:pbkdf2_and_ssha512_embedded_plist) do "bplist00\xD2\x01\x02\x03\n_\x10\x14SALTED-SHA512-PBKDF2]SALTED-SHA512\xD3\x04\x05\x06\a\b\tWentropyTsaltZiterationsO\x10\x80\x05\x90\xAD\xE1\x9EiS\xC15\xAE\x87*\xE7v\x18#]\xF7\xD4lc\xDE\x7F\x9A\x0F\xCD\xF2\xCD\x9E}\x85\xE4\xB7\xCA\x86\x81\x01#[aX\xE0Z0\x98\x05\xEEH\x14\xB0'\xA4\xBE\x9C#\xEC)&\xBC\x81r&\x9A\xFF\xBA\\\x9AY\x85\xE8\x10\x91\xFAh\x98\am)\x7F\x1F\xAAu\xFAauQ\xEF\x16q\xD7R\x00U\xC4\xA0\xD9{\x9B\x9CX\x05\xAA2+\xAE\xDB\xCD\x8E\xE9\xC5#\x81\x16S\xAC.\xA9\xE9\xC8\xD8\xF1\xACQ\x9A\x0F+Y^O\x10 \x93w\xC4i\b\xA1\xC8\xAC,>E\xC0\xD4M\xA8\xAD\x0F\xCD\x85\xEC\\\x14\xD9\xA5\x9F\xFC@\xC9\xDA1\xF0\xEC\x11`\xB0O\x10D~\xA7\xD5\x92\x13\x1FW\xB2\xC8\xF8\xBD\xBC\xEC\x8D\x9D\xF1!(\xA3\x869:O\x00\xC7a\x9B\xAC&\"\xA4ME\x14\x19\xD1\x1D\xA5\x12\xD5\x91Z\xB9\x8E9q\x8A\xC9@\x83\xFE.\xFDk\xF7\x10\xA5MG\x7F\x8F\xF75\xB1%\x87\x19-\x00\b\x00\r\x00$\x002\x009\x00A\x00F\x00Q\x00\xD4\x00\xF7\x00\xFA\x00\x00\x00\x00\x00\x00\x02\x01\x00\x00\x00\x00\x00\x00\x00\v\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01A" end let (:pbkdf2_and_ssha512_embedded_bplist_hash) do { "SALTED-SHA512-PBKDF2" => { "entropy" => pbkdf2_pw_string, "salt" => pbkdf2_salt_value, "iterations" => pbkdf2_iterations_value, }, "SALTED-SHA512" => sha512_password_hash } end # The below represents output of 'dscl -plist . readall /Users' converted to # a native Ruby hash if only one user were installed on the system. # This lets us check the behavior of all the methods necessary to return a # user's groups property by controlling the data provided by dscl let(:testuser_base) do { "dsAttrTypeStandard:RecordName" =>["nonexistent_user"], "dsAttrTypeStandard:UniqueID" =>["1000"], "dsAttrTypeStandard:AuthenticationAuthority"=> [";Kerberosv5;;testuser@LKDC:SHA1.4383E152D9D394AA32D13AE98F6F6E1FE8D00F81;LKDC:SHA1.4383E152D9D394AA32D13AE98F6F6E1FE8D00F81", ";ShadowHash;HASHLIST:"], "dsAttrTypeStandard:AppleMetaNodeLocation" =>["/Local/Default"], "dsAttrTypeStandard:NFSHomeDirectory" =>["/Users/nonexistent_user"], "dsAttrTypeStandard:RecordType" =>["dsRecTypeStandard:Users"], "dsAttrTypeStandard:RealName" =>["nonexistent_user"], "dsAttrTypeStandard:Password" =>["********"], "dsAttrTypeStandard:PrimaryGroupID" =>["22"], "dsAttrTypeStandard:GeneratedUID" =>["0A7D5B63-3AD4-4CA7-B03E-85876F1D1FB3"], "dsAttrTypeStandard:AuthenticationHint" =>[""], "dsAttrTypeNative:KerberosKeys" => ["30820157 a1030201 02a08201 4e308201 4a3074a1 2b3029a0 03020112 a1220420 54af3992 1c198bf8 94585a6b 2fba445b c8482228 0dcad666 ea62e038 99e59c45 a2453043 a0030201 03a13c04 3a4c4b44 433a5348 41312e34 33383345 31353244 39443339 34414133 32443133 41453938 46364636 45314645 38443030 46383174 65737475 73657230 64a11b30 19a00302 0111a112 04106375 7d97b2ce ca8343a6 3b0f73d5 1001a245 3043a003 020103a1 3c043a4c 4b44433a 53484131 2e343338 33453135 32443944 33393441 41333244 31334145 39384636 46364531 46453844 30304638 31746573 74757365 72306ca1 233021a0 03020110 a11a0418 67b09be3 5131b670 f8e9265e 62459b4c 19435419 fe918519 a2453043 a0030201 03a13c04 3a4c4b44 433a5348 41312e34 33383345 31353244 39443339 34414133 32443133 41453938 46364636 45314645 38443030 46383174 65737475 736572"], "dsAttrTypeStandard:PasswordPolicyOptions" => ["\n \n \n \n failedLoginCount\n 0\n failedLoginTimestamp\n 2001-01-01T00:00:00Z\n lastLoginTimestamp\n 2001-01-01T00:00:00Z\n passwordTimestamp\n 2012-08-10T23:53:50Z\n \n \n "], "dsAttrTypeStandard:UserShell" =>["/bin/bash"], "dsAttrTypeNative:ShadowHashData" => ["62706c69 73743030 d101025d 53414c54 45442d53 48413531 324f1044 7ea7d592 131f57b2 c8f8bdbc ec8d9df1 2128a386 393a4f00 c7619bac 2622a44d 451419d1 1da512d5 915ab98e 39718ac9 4083fe2e fd6bf710 a54d477f 8ff735b1 2587192d 080b1900 00000000 00010100 00000000 00000300 00000000 00000000 00000000 000060"] } end let(:testuser_hash) do [ testuser_base.merge(sha512_shadowhashdata_hash), testuser_base.merge(pbkdf2_shadowhashdata_hash), ] end # The below represents the result of running Plist.parse_xml on XML # data returned from the `dscl -plist . readall /Groups` command. # (AKA: What the get_list_of_groups method returns) let(:group_plist_hash_guid) do [{ 'dsAttrTypeStandard:RecordName' => ['testgroup'], 'dsAttrTypeStandard:GroupMembership' => [ username, 'jeff', 'zack' ], 'dsAttrTypeStandard:GroupMembers' => [ "guid#{username}", 'guidtestuser', 'guidjeff', 'guidzack' ], }, { 'dsAttrTypeStandard:RecordName' => ['second'], 'dsAttrTypeStandard:GroupMembership' => [ 'jeff', 'zack' ], 'dsAttrTypeStandard:GroupMembers' => [ "guid#{username}", 'guidjeff', 'guidzack' ], }, { 'dsAttrTypeStandard:RecordName' => ['third'], 'dsAttrTypeStandard:GroupMembership' => [ username, 'jeff', 'zack' ], 'dsAttrTypeStandard:GroupMembers' => [ "guid#{username}", 'guidtestuser', 'guidjeff', 'guidzack' ], }] end describe 'Creating a user that does not exist' do # These are the defaults that the provider will use if a user does # not provide a value let(:defaults) do { 'UniqueID' => '1000', 'RealName' => resource[:name], 'PrimaryGroupID' => 20, 'UserShell' => '/bin/bash', 'NFSHomeDirectory' => "/Users/#{resource[:name]}" } end before :each do # Stub out all calls to dscl with default values from above defaults.each do |key, val| provider.stubs(:merge_attribute_with_dscl).with('Users', username, key, val) end # Mock the rest of the dscl calls. We can't assume that our Linux # build system will have the dscl binary provider.stubs(:create_new_user).with(username) provider.class.stubs(:get_attribute_from_dscl).with('Users', username, 'GeneratedUID').returns({'dsAttrTypeStandard:GeneratedUID' => ['GUID']}) provider.stubs(:next_system_id).returns('1000') end it 'should not raise any errors when creating a user with default values' do provider.create end %w{password iterations salt}.each do |value| it "should call ##{value}= if a #{value} attribute is specified" do resource[value.intern] = 'somevalue' setter = (value << '=').intern provider.expects(setter).with('somevalue') provider.create end end it 'should merge the GroupMembership and GroupMembers dscl values if a groups attribute is specified' do resource[:groups] = 'somegroup' provider.expects(:merge_attribute_with_dscl).with('Groups', 'somegroup', 'GroupMembership', username) provider.expects(:merge_attribute_with_dscl).with('Groups', 'somegroup', 'GroupMembers', 'GUID') provider.create end it 'should convert group names into integers' do resource[:gid] = 'somegroup' Puppet::Util.expects(:gid).with('somegroup').returns(21) provider.expects(:merge_attribute_with_dscl).with('Users', username, 'PrimaryGroupID', 21) provider.create end end describe 'self#instances' do it 'should create an array of provider instances' do provider.class.expects(:get_all_users).returns(['foo', 'bar']) ['foo', 'bar'].each do |user| provider.class.expects(:generate_attribute_hash).with(user).returns({}) end instances = provider.class.instances expect(instances).to be_a_kind_of Array instances.each do |instance| expect(instance).to be_a_kind_of Puppet::Provider end end end describe 'self#get_all_users', :if => Puppet.features.cfpropertylist? do let(:empty_plist) do ' ' end it 'should return a hash of user attributes' do provider.class.expects(:dscl).with('-plist', '.', 'readall', '/Users').returns(user_plist_xml) expect(provider.class.get_all_users).to eq(user_plist_hash) end it 'should return a hash when passed an empty plist' do provider.class.expects(:dscl).with('-plist', '.', 'readall', '/Users').returns(empty_plist) expect(provider.class.get_all_users).to eq({}) end end describe 'self#generate_attribute_hash' do let(:user_plist_resource) do { :ensure => :present, :provider => :directoryservice, :groups => 'testgroup,third', :comment => username, :password => sha512_password_hash, :shadowhashdata => sha512_shadowhashdata_array, :name => username, :uid => 1000, :gid => 22, :home => user_path } end before :each do provider.class.stubs(:get_os_version).returns('10.7') provider.class.stubs(:get_all_users).returns(testuser_hash) provider.class.stubs(:get_list_of_groups).returns(group_plist_hash_guid) provider.class.stubs(:convert_binary_to_hash).with(sha512_embedded_bplist).returns(sha512_embedded_bplist_hash) provider.class.stubs(:convert_binary_to_hash).with(pbkdf2_embedded_plist).returns(pbkdf2_embedded_bplist_hash) provider.class.prefetch({}) end it 'should return :uid values as an Integer' do expect(provider.class.generate_attribute_hash(user_plist_hash)[:uid]).to be_a Integer end it 'should return :gid values as an Integer' do expect(provider.class.generate_attribute_hash(user_plist_hash)[:gid]).to be_a Integer end it 'should return a hash of resource attributes' do expect(provider.class.generate_attribute_hash(user_plist_hash.merge(sha512_shadowhashdata_hash))).to eq(user_plist_resource) end end describe 'self#generate_attribute_hash with pbkdf2 and ssha512' do let(:user_plist_resource) do { :ensure => :present, :provider => :directoryservice, :groups => 'testgroup,third', :comment => username, :password => pbkdf2_password_hash, :iterations => pbkdf2_iterations_value, :salt => pbkdf2_salt_value, :shadowhashdata => pbkdf2_and_ssha512_shadowhashdata_array, :name => username, :uid => 1000, :gid => 22, :home => user_path } end before :each do provider.class.stubs(:get_os_version).returns('10.7') provider.class.stubs(:get_all_users).returns(testuser_hash) provider.class.stubs(:get_list_of_groups).returns(group_plist_hash_guid) provider.class.prefetch({}) end it 'should return a hash of resource attributes' do expect(provider.class.generate_attribute_hash(user_plist_hash.merge(pbkdf2_and_ssha512_shadowhashdata_hash))).to eq(user_plist_resource) end end describe 'self#generate_attribute_hash empty shadowhashdata' do let(:user_plist_resource) do { :ensure => :present, :provider => :directoryservice, :groups => 'testgroup,third', :comment => username, :password => '*', :shadowhashdata => nil, :name => username, :uid => 1000, :gid => 22, :home => user_path } end it 'should handle empty shadowhashdata' do provider.class.stubs(:get_os_version).returns('10.7') provider.class.stubs(:get_all_users).returns([testuser_base]) provider.class.stubs(:get_list_of_groups).returns(group_plist_hash_guid) provider.class.prefetch({}) expect(provider.class.generate_attribute_hash(user_plist_hash)).to eq(user_plist_resource) end end describe '#delete' do it 'should call dscl when destroying/deleting a resource' do provider.expects(:dscl).with('.', '-delete', user_path) provider.delete end end describe 'the groups property' do # The below represents the result of running Plist.parse_xml on XML # data returned from the `dscl -plist . readall /Groups` command. # (AKA: What the get_list_of_groups method returns) let(:group_plist_hash) do [{ 'dsAttrTypeStandard:RecordName' => ['testgroup'], 'dsAttrTypeStandard:GroupMembership' => [ 'testuser', username, 'jeff', 'zack' ], 'dsAttrTypeStandard:GroupMembers' => [ 'guidtestuser', 'guidjeff', 'guidzack' ], }, { 'dsAttrTypeStandard:RecordName' => ['second'], 'dsAttrTypeStandard:GroupMembership' => [ username, 'testuser', 'jeff', ], 'dsAttrTypeStandard:GroupMembers' => [ 'guidtestuser', 'guidjeff', ], }, { 'dsAttrTypeStandard:RecordName' => ['third'], 'dsAttrTypeStandard:GroupMembership' => [ 'jeff', 'zack' ], 'dsAttrTypeStandard:GroupMembers' => [ 'guidjeff', 'guidzack' ], }] end before :each do provider.class.stubs(:get_all_users).returns(testuser_hash) provider.class.stubs(:get_os_version).returns('10.7') end it "should return a list of groups if the user's name matches GroupMembership" do provider.class.expects(:get_list_of_groups).returns(group_plist_hash) provider.class.expects(:get_list_of_groups).returns(group_plist_hash) expect(provider.class.prefetch({}).first.groups).to eq('second,testgroup') end it "should return a list of groups if the user's GUID matches GroupMembers" do provider.class.expects(:get_list_of_groups).returns(group_plist_hash_guid) provider.class.expects(:get_list_of_groups).returns(group_plist_hash_guid) expect(provider.class.prefetch({}).first.groups).to eq('testgroup,third') end end describe '#groups=' do let(:group_plist_one_two_three) do [{ 'dsAttrTypeStandard:RecordName' => ['one'], 'dsAttrTypeStandard:GroupMembership' => [ 'jeff', 'zack' ], 'dsAttrTypeStandard:GroupMembers' => [ 'guidjeff', 'guidzack' ], }, { 'dsAttrTypeStandard:RecordName' => ['two'], 'dsAttrTypeStandard:GroupMembership' => [ 'jeff', 'zack', username ], 'dsAttrTypeStandard:GroupMembers' => [ 'guidjeff', 'guidzack' ], }, { 'dsAttrTypeStandard:RecordName' => ['three'], 'dsAttrTypeStandard:GroupMembership' => [ 'jeff', 'zack', username ], 'dsAttrTypeStandard:GroupMembers' => [ 'guidjeff', 'guidzack' ], }] end before :each do provider.class.stubs(:get_all_users).returns(testuser_hash) provider.class.stubs(:get_list_of_groups).returns(group_plist_one_two_three) end it 'should call dscl to add necessary groups' do provider.class.expects(:get_attribute_from_dscl).with('Users', username, 'GeneratedUID').returns({'dsAttrTypeStandard:GeneratedUID' => ['guidnonexistent_user']}) provider.expects(:groups).returns('two,three') provider.expects(:dscl).with('.', '-merge', '/Groups/one', 'GroupMembership', 'nonexistent_user') provider.expects(:dscl).with('.', '-merge', '/Groups/one', 'GroupMembers', 'guidnonexistent_user') provider.class.prefetch({}) provider.groups= 'one,two,three' end it 'should call the get_salted_sha512 method on 10.7 and return the correct hash' do provider.class.expects(:convert_binary_to_hash).with(sha512_embedded_bplist).returns(sha512_embedded_bplist_hash) provider.class.expects(:convert_binary_to_hash).with(pbkdf2_embedded_plist).returns(pbkdf2_embedded_bplist_hash) expect(provider.class.prefetch({}).first.password).to eq(sha512_password_hash) end it 'should call the get_salted_sha512_pbkdf2 method on 10.8 and return the correct hash' do provider.class.expects(:convert_binary_to_hash).with(sha512_embedded_bplist).returns(sha512_embedded_bplist_hash) provider.class.expects(:convert_binary_to_hash).with(pbkdf2_embedded_plist).returns(pbkdf2_embedded_bplist_hash) expect(provider.class.prefetch({}).last.password).to eq(pbkdf2_password_hash) end end describe '#password=' do before :each do provider.stubs(:sleep) provider.stubs(:flush_dscl_cache) end it 'should call write_password_to_users_plist when setting the password' do provider.class.stubs(:get_os_version).returns('10.7') provider.expects(:write_password_to_users_plist).with(sha512_password_hash) provider.password = sha512_password_hash end it 'should call write_password_to_users_plist when setting the password' do provider.class.stubs(:get_os_version).returns('10.8') resource[:salt] = pbkdf2_salt_value resource[:iterations] = pbkdf2_iterations_value resource[:password] = pbkdf2_password_hash provider.expects(:write_password_to_users_plist).with(pbkdf2_password_hash) provider.password = resource[:password] end it "should raise an error on 10.7 if a password hash that doesn't contain 136 characters is passed" do provider.class.stubs(:get_os_version).returns('10.7') expect { provider.password = 'password' }.to raise_error Puppet::Error, /OS X 10\.7 requires a Salted SHA512 hash password of 136 characters\. Please check your password and try again/ end end describe "passwords on 10.8" do before :each do provider.class.stubs(:get_os_version).returns('10.8') end it "should raise an error on 10.8 if a password hash that doesn't contain 256 characters is passed" do expect do provider.password = 'password' end.to raise_error(Puppet::Error, /OS X versions > 10\.7 require a Salted SHA512 PBKDF2 password hash of 256 characters\. Please check your password and try again\./) end it "fails if a password is given but not salt and iterations" do resource[:password] = pbkdf2_password_hash expect do provider.password = resource[:password] end.to raise_error(Puppet::Error, /OS X versions > 10\.7 use PBKDF2 password hashes, which requires all three of salt, iterations, and password hash\. This resource is missing: salt, iterations\./) end it "fails if salt is given but not password and iterations" do resource[:salt] = pbkdf2_salt_value expect do provider.salt = resource[:salt] end.to raise_error(Puppet::Error, /OS X versions > 10\.7 use PBKDF2 password hashes, which requires all three of salt, iterations, and password hash\. This resource is missing: password, iterations\./) end it "fails if iterations is given but not password and salt" do resource[:iterations] = pbkdf2_iterations_value expect do provider.iterations = resource[:iterations] end.to raise_error(Puppet::Error, /OS X versions > 10\.7 use PBKDF2 password hashes, which requires all three of salt, iterations, and password hash\. This resource is missing: password, salt\./) end end describe '#get_list_of_groups', :if => Puppet.features.cfpropertylist? do # The below value is the result of running `dscl -plist . readall /Groups` # on an OS X system. let(:groups_xml) do ' dsAttrTypeStandard:AppleMetaNodeLocation /Local/Default dsAttrTypeStandard:GeneratedUID ABCDEFAB-CDEF-ABCD-EFAB-CDEF00000053 dsAttrTypeStandard:Password * dsAttrTypeStandard:PrimaryGroupID 83 dsAttrTypeStandard:RealName SPAM Assassin Group 2 dsAttrTypeStandard:RecordName _amavisd amavisd dsAttrTypeStandard:RecordType dsRecTypeStandard:Groups ' end # The below value is the result of executing Plist.parse_xml on # groups_xml let(:groups_hash) do [{ 'dsAttrTypeStandard:AppleMetaNodeLocation' => ['/Local/Default'], 'dsAttrTypeStandard:GeneratedUID' => ['ABCDEFAB-CDEF-ABCD-EFAB-CDEF00000053'], 'dsAttrTypeStandard:Password' => ['*'], 'dsAttrTypeStandard:PrimaryGroupID' => ['83'], 'dsAttrTypeStandard:RealName' => ['SPAM Assassin Group 2'], 'dsAttrTypeStandard:RecordName' => ['_amavisd', 'amavisd'], 'dsAttrTypeStandard:RecordType' => ['dsRecTypeStandard:Groups'] }] end before :each do # Ensure we don't have a value cached from another spec provider.class.instance_variable_set(:@groups, nil) if provider.class.instance_variable_defined? :@groups end it 'should return an array of hashes containing group data' do provider.class.expects(:dscl).with('-plist', '.', 'readall', '/Groups').returns(groups_xml) expect(provider.class.get_list_of_groups).to eq(groups_hash) end end describe '#get_attribute_from_dscl', :if => Puppet.features.cfpropertylist? do # The below value is the result of executing # `dscl -plist . read /Users/ dsAttrTypeStandard:GeneratedUID DCC660C6-F5A9-446D-B9FF-3C0258AB5BA0 ' end # The below value is the result of parsing user_guid_xml with # Plist.parse_xml let(:user_guid_hash) do { 'dsAttrTypeStandard:GeneratedUID' => ['DCC660C6-F5A9-446D-B9FF-3C0258AB5BA0'] } end it 'should return a hash containing a user\'s dscl attribute data' do provider.class.expects(:dscl).with('-plist', '.', 'read', user_path, 'GeneratedUID').returns(user_guid_xml) expect(provider.class.get_attribute_from_dscl('Users', username, 'GeneratedUID')).to eq(user_guid_hash) end end describe '#convert_hash_to_binary' do it 'should use plutil to successfully convert an xml plist to a binary plist' do Puppet::Util::Plist.expects(:dump_plist).with('ruby_hash', :binary).returns('binary_plist_data') expect(provider.class.convert_hash_to_binary('ruby_hash')).to eq('binary_plist_data') end end describe '#convert_binary_to_hash' do it 'should accept a binary plist and return a ruby hash containing the plist data' do Puppet::Util::Plist.expects(:parse_plist).with('binary_plist_data').returns(user_plist_hash) expect(provider.class.convert_binary_to_hash('binary_plist_data')).to eq(user_plist_hash) end end describe '#next_system_id' do it 'should return the next available UID number that is not in the list obtained from dscl and is greater than the passed integer value' do provider.expects(:dscl).with('.', '-list', '/Users', 'uid').returns("kathee 312\ngary 11\ntanny 33\njohn 9\nzach 5") expect(provider.next_system_id(30)).to eq(34) end end describe '#get_salted_sha512' do it "should accept a hash whose 'SALTED-SHA512' key contains a base64 encoded salted-SHA512 password hash and return the hex value of that password hash" do expect(provider.class.get_salted_sha512(sha512_embedded_bplist_hash)).to eq(sha512_password_hash) end end describe '#get_salted_sha512_pbkdf2' do it "should accept a hash containing a PBKDF2 password hash, salt, and iterations value and return the correct password hash" do expect(provider.class.get_salted_sha512_pbkdf2('entropy', pbkdf2_embedded_bplist_hash)).to eq(pbkdf2_password_hash) end it "should accept a hash containing a PBKDF2 password hash, salt, and iterations value and return the correct salt value" do expect(provider.class.get_salted_sha512_pbkdf2('salt', pbkdf2_embedded_bplist_hash)).to eq(pbkdf2_salt_value) end it "should accept a hash containing a PBKDF2 password hash, salt, and iterations value and return the correct iterations value" do expect(provider.class.get_salted_sha512_pbkdf2('iterations', pbkdf2_embedded_bplist_hash)).to eq(pbkdf2_iterations_value) end it "should return an Integer value when looking up the PBKDF2 iterations value" do expect(provider.class.get_salted_sha512_pbkdf2('iterations', pbkdf2_embedded_bplist_hash)).to be_a(Integer) end it "should raise an error if a field other than 'entropy', 'salt', or 'iterations' is passed" do expect { provider.class.get_salted_sha512_pbkdf2('othervalue', pbkdf2_embedded_bplist_hash) }.to raise_error(Puppet::Error, /Puppet has tried to read an incorrect value from the SALTED-SHA512-PBKDF2 hash. Acceptable fields are 'salt', 'entropy', or 'iterations'/) end end describe '#get_sha1' do let(:password_hash_file) { '/var/db/shadow/hash/user_guid' } let(:stub_password_file) { stub('connection') } it 'should return a sha1 hash read from disk' do Puppet::FileSystem.expects(:exist?).with(password_hash_file).returns(true) File.expects(:file?).with(password_hash_file).returns(true) File.expects(:readable?).with(password_hash_file).returns(true) File.expects(:new).with(password_hash_file).returns(stub_password_file) stub_password_file.expects(:read).returns('sha1_password_hash') stub_password_file.expects(:close) expect(provider.class.get_sha1('user_guid')).to eq('sha1_password_hash') end it 'should return nil if the password_hash_file does not exist' do Puppet::FileSystem.expects(:exist?).with(password_hash_file).returns(false) expect(provider.class.get_sha1('user_guid')).to eq(nil) end it 'should return nil if the password_hash_file is not a file' do Puppet::FileSystem.expects(:exist?).with(password_hash_file).returns(true) File.expects(:file?).with(password_hash_file).returns(false) expect(provider.class.get_sha1('user_guid')).to eq(nil) end it 'should raise an error if the password_hash_file is not readable' do Puppet::FileSystem.expects(:exist?).with(password_hash_file).returns(true) File.expects(:file?).with(password_hash_file).returns(true) File.expects(:readable?).with(password_hash_file).returns(false) expect { expect(provider.class.get_sha1('user_guid')).to eq(nil) }.to raise_error(Puppet::Error, /Could not read password hash file at #{password_hash_file}/) end end describe '#write_password_to_users_plist' do let(:sha512_plist_xml) do "\n\n\n\n\tKerberosKeys\n\t\n\t\t\n\t\tMIIBS6EDAgEBoIIBQjCCAT4wcKErMCmgAwIBEqEiBCCS/0Im7BAps/YhX/ED\n\t\tKOpDeSMFkUsu3UzEa6gqDu35BKJBMD+gAwIBA6E4BDZMS0RDOlNIQTEuNDM4\n\t\tM0UxNTJEOUQzOTRBQTMyRDEzQUU5OEY2RjZFMUZFOEQwMEY4MWplZmYwYKEb\n\t\tMBmgAwIBEaESBBAk8a3rrFk5mHAdEU5nRgFwokEwP6ADAgEDoTgENkxLREM6\n\t\tU0hBMS40MzgzRTE1MkQ5RDM5NEFBMzJEMTNBRTk4RjZGNkUxRkU4RDAwRjgx\n\t\tamVmZjBooSMwIaADAgEQoRoEGFg71irsV+9ddRNPSn9houo3Q6jZuj55XaJB\n\t\tMD+gAwIBA6E4BDZMS0RDOlNIQTEuNDM4M0UxNTJEOUQzOTRBQTMyRDEzQUU5\n\t\tOEY2RjZFMUZFOEQwMEY4MWplZmY=\n\t\t\n\t\n\tShadowHashData\n\t\n\t\t\n\t\tYnBsaXN0MDDRAQJdU0FMVEVELVNIQTUxMk8QRFNL0iuruijP6becUWe43GTX\n\t\t5WTgOTi2emx41DMnwnB4vbKieVOE4eNHiyocX5c0GX1LWJ6VlZqZ9EnDLsuA\n\t\tNC5Ga9qlCAsZAAAAAAAAAQEAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAGA=\n\t\t\n\t\n\tauthentication_authority\n\t\n\t\t;Kerberosv5;;jeff@LKDC:SHA1.4383E152D9D394AA32D13AE98F6F6E1FE8D00F81;LKDC:SHA1.4383E152D9D394AA32D13AE98F6F6E1FE8D00F81\n\t\t;ShadowHash;HASHLIST:<SALTED-SHA512>\n\t\n\tdsAttrTypeStandard:ShadowHashData\n\t\n\t\t\n\t\tYnBsaXN0MDDRAQJdU0FMVEVELVNIQTUxMk8QRH6n1ZITH1eyyPi9vOyNnfEh\n\t\tKKOGOTpPAMdhm6wmIqRNRRQZ0R2lEtWRWrmOOXGKyUCD/i79a/cQpU1Hf4/3\n\t\tNbElhxktCAsZAAAAAAAAAQEAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAGA=\n\t\t\n\t\n\tgenerateduid\n\t\n\t\t3AC74939-C14F-45DD-B6A9-D1A82373F0B0\n\t\n\tname\n\t\n\t\tjeff\n\t\n\tpasswd\n\t\n\t\t********\n\t\n\tpasswordpolicyoptions\n\t\n\t\t\n\t\tPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NU\n\t\tWVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VO\n\t\tIiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4w\n\t\tLmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KCTxrZXk+ZmFp\n\t\tbGVkTG9naW5Db3VudDwva2V5PgoJPGludGVnZXI+MDwvaW50ZWdlcj4KCTxr\n\t\tZXk+ZmFpbGVkTG9naW5UaW1lc3RhbXA8L2tleT4KCTxkYXRlPjIwMDEtMDEt\n\t\tMDFUMDA6MDA6MDBaPC9kYXRlPgoJPGtleT5sYXN0TG9naW5UaW1lc3RhbXA8\n\t\tL2tleT4KCTxkYXRlPjIwMDEtMDEtMDFUMDA6MDA6MDBaPC9kYXRlPgoJPGtl\n\t\teT5wYXNzd29yZFRpbWVzdGFtcDwva2V5PgoJPGRhdGU+MjAxMi0wOC0xMVQw\n\t\tMDozNTo1MFo8L2RhdGU+CjwvZGljdD4KPC9wbGlzdD4K\n\t\t\n\t\n\tuid\n\t\n\t\t28\n\t\n\n" end let(:pbkdf2_plist_xml) do "\n\n\n\n\tKerberosKeys\n\t\n\t\t\n\t\tMIIBS6EDAgEBoIIBQjCCAT4wcKErMCmgAwIBEqEiBCDrboPy0gxu7oTZR/Pc\n\t\tYdCBC9ivXo1k05gt036/aNe5VqJBMD+gAwIBA6E4BDZMS0RDOlNIQTEuNDEz\n\t\tQTMwRjU5MEVFREM3ODdENTMyOTgxODUwQTk3NTI0NUIwQTcyM2plZmYwYKEb\n\t\tMBmgAwIBEaESBBCm02SYYdsxo2fiDP4KuPtmokEwP6ADAgEDoTgENkxLREM6\n\t\tU0hBMS40MTNBMzBGNTkwRUVEQzc4N0Q1MzI5ODE4NTBBOTc1MjQ1QjBBNzIz\n\t\tamVmZjBooSMwIaADAgEQoRoEGHPBc7Dg7zjaE8g+YXObwupiBLMIlCrN5aJB\n\t\tMD+gAwIBA6E4BDZMS0RDOlNIQTEuNDEzQTMwRjU5MEVFREM3ODdENTMyOTgx\n\t\tODUwQTk3NTI0NUIwQTcyM2plZmY=\n\t\t\n\t\n\tShadowHashData\n\t\n\t\t\n\t\tYnBsaXN0MDDRAQJfEBRTQUxURUQtU0hBNTEyLVBCS0RGMtMDBAUGBwhXZW50\n\t\tcm9weVRzYWx0Wml0ZXJhdGlvbnNPEIAFkK3hnmlTwTWuhyrndhgjXffUbGPe\n\t\tf5oPzfLNnn2F5LfKhoEBI1thWOBaMJgF7kgUsCekvpwj7CkmvIFyJpr/ulya\n\t\tWYXoEJH6aJgHbSl/H6p1+mF1Ue8WcddSAFXEoNl7m5xYBaoyK67bzY7pxSOB\n\t\tFlOsLqnpyNjxrFGaDytZXk8QIJN3xGkIocisLD5FwNRNqK0PzYXsXBTZpZ/8\n\t\tQMnaMfDsEWCwCAsiKTE2QcTnAAAAAAAAAQEAAAAAAAAACQAAAAAAAAAAAAAA\n\t\tAAAAAOo=\n\t\t\n\t\n\tauthentication_authority\n\t\n\t\t;Kerberosv5;;jeff@LKDC:SHA1.413A30F590EEDC787D532981850A975245B0A723;LKDC:SHA1.413A30F590EEDC787D532981850A975245B0A723\n\t\t;ShadowHash;HASHLIST:<SALTED-SHA512-PBKDF2>\n\t\n\tgenerateduid\n\t\n\t\t1CB825D1-2DF7-43CC-B874-DB6BBB76C402\n\t\n\tgid\n\t\n\t\t21\n\t\n\tname\n\t\n\t\tjeff\n\t\n\tpasswd\n\t\n\t\t********\n\t\n\tpasswordpolicyoptions\n\t\n\t\t\n\t\tPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NU\n\t\tWVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VO\n\t\tIiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4w\n\t\tLmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KCTxrZXk+ZmFp\n\t\tbGVkTG9naW5Db3VudDwva2V5PgoJPGludGVnZXI+MDwvaW50ZWdlcj4KCTxr\n\t\tZXk+ZmFpbGVkTG9naW5UaW1lc3RhbXA8L2tleT4KCTxkYXRlPjIwMDEtMDEt\n\t\tMDFUMDA6MDA6MDBaPC9kYXRlPgoJPGtleT5sYXN0TG9naW5UaW1lc3RhbXA8\n\t\tL2tleT4KCTxkYXRlPjIwMDEtMDEtMDFUMDA6MDA6MDBaPC9kYXRlPgoJPGtl\n\t\teT5wYXNzd29yZExhc3RTZXRUaW1lPC9rZXk+Cgk8ZGF0ZT4yMDEyLTA3LTI1\n\t\tVDE4OjQ3OjU5WjwvZGF0ZT4KPC9kaWN0Pgo8L3BsaXN0Pgo=\n\t\t\n\t\n\tuid\n\t\n\t\t28\n\t\n\n" end let(:sha512_shadowhashdata) do { 'SALTED-SHA512' => 'blankvalue' } end let(:pbkdf2_shadowhashdata) do { 'SALTED-SHA512-PBKDF2' => { 'entropy' => 'blank_entropy', 'salt' => 'blank_salt', 'iterations' => 100 } } end let(:sample_users_plist) do { "shell" => ["/bin/zsh"], "passwd" => ["********"], "picture" => ["/Library/User Pictures/Animals/Eagle.tif"], "_writers_LinkedIdentity" => ["puppet"], "name"=>["puppet"], "home" => ["/Users/puppet"], "_writers_UserCertificate" => ["puppet"], "_writers_passwd" => ["puppet"], "gid" => ["20"], "generateduid" => ["DA8A0E67-E9BE-4B4F-B34E-8977BAE0D3D4"], "realname" => ["Puppet"], "_writers_picture" => ["puppet"], "uid" => ["501"], "hint" => [""], "authentication_authority" => [";ShadowHash;HASHLIST:", ";Kerberosv5;;puppet@LKDC:S HA1.35580B1D6366D2890A35D430373FF653297F377D;LKDC:SHA1.35580B1D6366D2890A35D430373FF653297F377D"], "_writers_realname" => ["puppet"], "_writers_hint" => ["puppet"], "ShadowHashData" => ['blank'] } end it 'should call set_salted_sha512 on 10.7 when given a salted-SHA512 password hash' do provider.expects(:get_users_plist).returns(sample_users_plist) provider.expects(:get_shadow_hash_data).with(sample_users_plist).returns(sha512_shadowhashdata) provider.class.expects(:get_os_version).returns('10.7') provider.expects(:set_salted_sha512).with(sample_users_plist, sha512_shadowhashdata, sha512_password_hash) provider.write_password_to_users_plist(sha512_password_hash) end it 'should call set_salted_pbkdf2 on 10.8 when given a PBKDF2 password hash' do provider.expects(:get_users_plist).returns(sample_users_plist) provider.expects(:get_shadow_hash_data).with(sample_users_plist).returns(pbkdf2_shadowhashdata) provider.class.expects(:get_os_version).returns('10.8') provider.expects(:set_salted_pbkdf2).with(sample_users_plist, pbkdf2_shadowhashdata, 'entropy', pbkdf2_password_hash) provider.write_password_to_users_plist(pbkdf2_password_hash) end it "should delete the SALTED-SHA512 key in the shadow_hash_data hash if it exists on a 10.8 system and write_password_to_users_plist has been called to set the user's password" do provider.expects(:get_users_plist).returns('users_plist') provider.expects(:get_shadow_hash_data).with('users_plist').returns(sha512_shadowhashdata) provider.class.expects(:get_os_version).returns('10.8') provider.expects(:set_salted_pbkdf2).with('users_plist', {}, 'entropy', pbkdf2_password_hash) provider.write_password_to_users_plist(pbkdf2_password_hash) end end describe '#set_salted_sha512' do let(:users_plist) { {'ShadowHashData' => ['string_data'] } } let(:sha512_shadow_hash_data) do { 'SALTED-SHA512' => sha512_pw_string } end it 'should set the SALTED-SHA512 password hash for a user in 10.7 and call the set_shadow_hash_data method to write the plist to disk' do provider.class.expects(:convert_hash_to_binary).with(sha512_embedded_bplist_hash).returns(sha512_embedded_bplist) provider.expects(:set_shadow_hash_data).with(users_plist, sha512_embedded_bplist) provider.set_salted_sha512(users_plist, sha512_embedded_bplist_hash, sha512_password_hash) end it 'should set the salted-SHA512 password, even if a blank shadow_hash_data hash is passed' do provider.class.expects(:convert_hash_to_binary).with(sha512_shadow_hash_data).returns(sha512_embedded_bplist) provider.expects(:set_shadow_hash_data).with(users_plist, sha512_embedded_bplist) provider.set_salted_sha512(users_plist, false, sha512_password_hash) end end describe '#set_salted_pbkdf2' do let(:users_plist) { {'ShadowHashData' => ['string_data'] } } let(:entropy_shadow_hash_data) do { 'SALTED-SHA512-PBKDF2' => { 'entropy' => 'binary_string' } } end # This will also catch the edge-case where a 10.6-style user exists on # a 10.8 system and Puppet attempts to set a password it 'should not fail if shadow_hash_data is not a Hash' do Puppet::Util::Plist.expects(:string_to_blob).with(provider.base64_decode_string(pbkdf2_password_hash)).returns('binary_string') provider.class.expects(:convert_hash_to_binary).with(entropy_shadow_hash_data).returns('binary_plist') provider.expects(:set_shadow_hash_data).with({'passwd' => '********'}, 'binary_plist') provider.set_salted_pbkdf2({}, false, 'entropy', pbkdf2_password_hash) end it "should set the PBKDF2 password hash when the 'entropy' field is passed with a valid password hash" do Puppet::Util::Plist.expects(:string_to_blob).with(provider.base64_decode_string(pbkdf2_password_hash)) provider.class.expects(:convert_hash_to_binary).with(pbkdf2_embedded_bplist_hash).returns(pbkdf2_embedded_plist) provider.expects(:set_shadow_hash_data).with(users_plist, pbkdf2_embedded_plist) users_plist.expects(:[]=).with('passwd', '********') provider.set_salted_pbkdf2(users_plist, pbkdf2_embedded_bplist_hash, 'entropy', pbkdf2_password_hash) end it "should set the PBKDF2 password hash when the 'salt' field is passed with a valid password hash" do Puppet::Util::Plist.expects(:string_to_blob).with(provider.base64_decode_string(pbkdf2_salt_value)) provider.class.expects(:convert_hash_to_binary).with(pbkdf2_embedded_bplist_hash).returns(pbkdf2_embedded_plist) provider.expects(:set_shadow_hash_data).with(users_plist, pbkdf2_embedded_plist) users_plist.expects(:[]=).with('passwd', '********') provider.set_salted_pbkdf2(users_plist, pbkdf2_embedded_bplist_hash, 'salt', pbkdf2_salt_value) end it "should set the PBKDF2 password hash when the 'iterations' field is passed with a valid password hash" do provider.class.expects(:convert_hash_to_binary).with(pbkdf2_embedded_bplist_hash).returns(pbkdf2_embedded_plist) provider.expects(:set_shadow_hash_data).with(users_plist, pbkdf2_embedded_plist) users_plist.expects(:[]=).with('passwd', '********') provider.set_salted_pbkdf2(users_plist, pbkdf2_embedded_bplist_hash, 'iterations', pbkdf2_iterations_value) end end describe '#write_users_plist_to_disk' do it 'should save the passed plist to disk and convert it to a binary plist' do Puppet::Util::Plist.expects(:write_plist_file).with(user_plist_xml, "#{users_plist_dir}/nonexistent_user.plist", :binary) provider.write_users_plist_to_disk(user_plist_xml) end end describe '#merge_attribute_with_dscl' do it 'should raise an error if a dscl command raises an error' do provider.expects(:dscl).with('.', '-merge', user_path, 'GeneratedUID', 'GUID').raises(Puppet::ExecutionFailure, 'boom') expect { provider.merge_attribute_with_dscl('Users', username, 'GeneratedUID', 'GUID') }.to raise_error Puppet::Error, /Could not set the dscl GeneratedUID key with value: GUID/ end end describe '#get_users_plist' do let(:test_hash) do { 'user' => 'puppet', 'shell' => '/bin/bash' } end it 'should convert a plist to a valid Ruby hash' do Puppet::Util::Plist.expects(:read_plist_file).with("#{users_plist_dir}/#{username}.plist").returns(test_hash) expect(provider.get_users_plist(username)).to eq(test_hash, ) end end describe '#get_shadow_hash_data' do let(:shadow_hash) do { 'ShadowHashData' => ['test'] } end let(:no_shadow_hash) do { 'no' => 'Shadow Hash Data' } end it 'should return false if the passed users_plist does NOT have a ShadowHashData key' do expect(provider.get_shadow_hash_data(no_shadow_hash)).to eq(false) end it 'should call convert_binary_to_hash() with the string ' + 'located in the first element of the array of the ShadowHashData key if the ' + 'passed users_plist contains a ShadowHashData key' do provider.class.expects(:convert_binary_to_hash).with('test').returns('returnvalue') expect(provider.get_shadow_hash_data(shadow_hash)).to eq('returnvalue') end end describe 'self#get_os_version' do before :each do # Ensure we don't have a value cached from another spec provider.class.instance_variable_set(:@os_version, nil) if provider.class.instance_variable_defined? :@os_version end it 'should call Facter.value(:macosx_productversion_major) ONLY ONCE no matter how ' + 'many times get_os_version() is called' do Facter.expects(:value).with(:macosx_productversion_major).once.returns('10.8') expect(provider.class.get_os_version).to eq('10.8') expect(provider.class.get_os_version).to eq('10.8') expect(provider.class.get_os_version).to eq('10.8') expect(provider.class.get_os_version).to eq('10.8') end end describe '#base64_decode_string' do it 'should return a Base64-decoded string appropriate for use in a user\'s plist' do expect(provider.base64_decode_string(sha512_password_hash)).to eq(sha512_pw_string) end end describe '(#12833) 10.6-style users on 10.8' do # The below represents output of 'dscl -plist . readall /Users' # converted to a Ruby hash if only one user were installed on the system. # This lets us check the behavior of all the methods necessary to return # a user's groups property by controlling the data provided by dscl. The # differentiating aspect about this plist is that it's from a 10.6-style # user. There's an edge case whereby a user that was created in 10.6, but # who hasn't attempted to login to the system until after it's been # upgraded to 10.8, will experience errors due to assumptions in Puppet # based solely on operatingsystem. let(:all_users_hash) do [ { "dsAttrTypeNative:_writers_UserCertificate" => ["testuser"], "dsAttrTypeStandard:RealName" => ["testuser"], "dsAttrTypeStandard:NFSHomeDirectory" => ["/Users/testuser"], "dsAttrTypeNative:_writers_realname" => ["testuser"], "dsAttrTypeNative:_writers_picture" => ["testuser"], "dsAttrTypeStandard:AppleMetaNodeLocation" => ["/Local/Default"], "dsAttrTypeStandard:PrimaryGroupID" => ["20"], "dsAttrTypeNative:_writers_LinkedIdentity" => ["testuser"], "dsAttrTypeStandard:UserShell" => ["/bin/bash"], "dsAttrTypeStandard:UniqueID" => ["1234"], "dsAttrTypeStandard:RecordName" => ["testuser"], "dsAttrTypeStandard:Password" => ["********"], "dsAttrTypeNative:_writers_jpegphoto" => ["testuser"], "dsAttrTypeNative:_writers_hint" => ["testuser"], "dsAttrTypeNative:_writers_passwd" => ["testuser"], "dsAttrTypeStandard:RecordType" => ["dsRecTypeStandard:Users"], "dsAttrTypeStandard:AuthenticationAuthority" => [ ";ShadowHash;", ";Kerberosv5;;testuser@LKDC:SHA1.48AC4BCFEFE9 D66847B5E7D813BC4B12C5513A07;LKDC:SHA1.48AC4BCFEFE9D66847B5E7D813BC4B12C5513A07;" ], "dsAttrTypeStandard:GeneratedUID" => ["D1AC2ECC-F177-4B45-8B18-59CF002F97FF"] } ] end let(:username) { 'testuser' } let(:user_path) { "/Users/#{username}" } let(:resource) do Puppet::Type.type(:user).new( :name => username, :provider => :directoryservice ) end let(:provider) { resource.provider } # The below represents the result of get_users_plist on the testuser # account from the 'all_users_hash' helper method. The get_users_plist # method calls the `plutil` binary to do its work, so we want to stub # that out let(:user_plist_hash) do { 'realname' => ['testuser'], 'authentication_authority' => [';ShadowHash;', ';Kerberosv5;;testuser@LKDC:SHA1.48AC4BCFEFE9D66847B5E7D813BC4B12C5513A07;LKDC:SHA1.48AC4BCFEFE9D66847B5E7D813BC4B12C5513A07;'], 'home' => ['/Users/testuser'], '_writers_realname' => ['testuser'], 'passwd' => '********', '_writers_LinkedIdentity' => ['testuser'], '_writers_picture' => ['testuser'], 'gid' => ['20'], '_writers_passwd' => ['testuser'], '_writers_hint' => ['testuser'], '_writers_UserCertificate' => ['testuser'], '_writers_jpegphoto' => ['testuser'], 'shell' => ['/bin/bash'], 'uid' => ['1234'], 'generateduid' => ['D1AC2ECC-F177-4B45-8B18-59CF002F97FF'], 'name' => ['testuser'] } end before :each do provider.class.stubs(:get_all_users).returns(all_users_hash) provider.class.stubs(:get_list_of_groups).returns(group_plist_hash_guid) provider.class.prefetch({}) end it 'should not raise an error if the password=() method is called on ' + 'a user without a ShadowHashData key in their user\'s plist on OS X ' + 'version 10.8' do resource[:salt] = pbkdf2_salt_value resource[:iterations] = pbkdf2_iterations_value resource[:password] = pbkdf2_password_hash provider.class.stubs(:get_os_version).returns('10.8') provider.stubs(:sleep) provider.stubs(:flush_dscl_cache) provider.expects(:get_users_plist).with('testuser').returns(user_plist_hash) provider.expects(:set_salted_pbkdf2).with(user_plist_hash, false, 'entropy', pbkdf2_password_hash) provider.password = resource[:password] end end end puppet-5.5.10/spec/unit/provider/user/ldap_spec.rb0000644005276200011600000002306213417161721022015 0ustar jenkinsjenkinsrequire 'spec_helper' describe Puppet::Type.type(:user).provider(:ldap) do it "should have the Ldap provider class as its baseclass" do expect(described_class.superclass).to equal(Puppet::Provider::Ldap) end it "should manage :posixAccount and :person objectclasses" do expect(described_class.manager.objectclasses).to eq([:posixAccount, :person]) end it "should use 'ou=People' as its relative base" do expect(described_class.manager.location).to eq("ou=People") end it "should use :uid as its rdn" do expect(described_class.manager.rdn).to eq(:uid) end it "should be able to manage passwords" do expect(described_class).to be_manages_passwords end {:name => "uid", :password => "userPassword", :comment => "cn", :uid => "uidNumber", :gid => "gidNumber", :home => "homeDirectory", :shell => "loginShell" }.each do |puppet, ldap| it "should map :#{puppet.to_s} to '#{ldap}'" do expect(described_class.manager.ldap_name(puppet)).to eq(ldap) end end context "when being created" do before do # So we don't try to actually talk to ldap @connection = mock 'connection' described_class.manager.stubs(:connect).yields @connection end it "should generate the sn as the last field of the cn" do Puppet::Type.type(:group).provider(:ldap).expects(:name2id).with(["whatever"]).returns [123] resource = stub 'resource', :should => %w{whatever} resource.stubs(:should).with(:comment).returns ["Luke Kanies"] resource.stubs(:should).with(:ensure).returns :present instance = described_class.new(:name => "luke", :ensure => :absent) instance.stubs(:resource).returns resource @connection.expects(:add).with { |dn, attrs| attrs["sn"] == ["Kanies"] } instance.create instance.flush end it "should translate a group name to the numeric id" do Puppet::Type.type(:group).provider(:ldap).expects(:name2id).with("bar").returns 101 resource = stub 'resource', :should => %w{whatever} resource.stubs(:should).with(:gid).returns 'bar' resource.stubs(:should).with(:ensure).returns :present instance = described_class.new(:name => "luke", :ensure => :absent) instance.stubs(:resource).returns resource @connection.expects(:add).with { |dn, attrs| attrs["gidNumber"] == ["101"] } instance.create instance.flush end context "with no uid specified" do it "should pick the first available UID after the largest existing UID" do Puppet::Type.type(:group).provider(:ldap).expects(:name2id).with(["whatever"]).returns [123] low = {:name=>["luke"], :shell=>:absent, :uid=>["600"], :home=>["/h"], :gid=>["1000"], :password=>["blah"], :comment=>["l k"]} high = {:name=>["testing"], :shell=>:absent, :uid=>["640"], :home=>["/h"], :gid=>["1000"], :password=>["blah"], :comment=>["t u"]} described_class.manager.expects(:search).returns([low, high]) resource = stub 'resource', :should => %w{whatever} resource.stubs(:should).with(:uid).returns nil resource.stubs(:should).with(:ensure).returns :present instance = described_class.new(:name => "luke", :ensure => :absent) instance.stubs(:resource).returns resource @connection.expects(:add).with { |dn, attrs| attrs["uidNumber"] == ["641"] } instance.create instance.flush end it "should pick 501 of no users exist" do Puppet::Type.type(:group).provider(:ldap).expects(:name2id).with(["whatever"]).returns [123] described_class.manager.expects(:search).returns nil resource = stub 'resource', :should => %w{whatever} resource.stubs(:should).with(:uid).returns nil resource.stubs(:should).with(:ensure).returns :present instance = described_class.new(:name => "luke", :ensure => :absent) instance.stubs(:resource).returns resource @connection.expects(:add).with { |dn, attrs| attrs["uidNumber"] == ["501"] } instance.create instance.flush end end end context "when flushing" do before do described_class.stubs(:suitable?).returns true @instance = described_class.new(:name => "myname", :groups => %w{whatever}, :uid => "400") end it "should remove the :groups value before updating" do @instance.class.manager.expects(:update).with { |name, ldap, puppet| puppet[:groups].nil? } @instance.flush end it "should empty the property hash" do @instance.class.manager.stubs(:update) @instance.flush expect(@instance.uid).to eq(:absent) end it "should empty the ldap property hash" do @instance.class.manager.stubs(:update) @instance.flush expect(@instance.ldap_properties[:uid]).to be_nil end end context "when checking group membership" do before do @groups = Puppet::Type.type(:group).provider(:ldap) @group_manager = @groups.manager described_class.stubs(:suitable?).returns true @instance = described_class.new(:name => "myname") end it "should show its group membership as the sorted list of all groups returned by an ldap query of group memberships" do one = {:name => "one"} two = {:name => "two"} @group_manager.expects(:search).with("memberUid=myname").returns([two, one]) expect(@instance.groups).to eq("one,two") end it "should show its group membership as :absent if no matching groups are found in ldap" do @group_manager.expects(:search).with("memberUid=myname").returns(nil) expect(@instance.groups).to eq(:absent) end it "should cache the group value" do @group_manager.expects(:search).with("memberUid=myname").once.returns nil @instance.groups expect(@instance.groups).to eq(:absent) end end context "when modifying group membership" do before do @groups = Puppet::Type.type(:group).provider(:ldap) @group_manager = @groups.manager described_class.stubs(:suitable?).returns true @one = {:name => "one", :gid => "500"} @group_manager.stubs(:find).with("one").returns(@one) @two = {:name => "one", :gid => "600"} @group_manager.stubs(:find).with("two").returns(@two) @instance = described_class.new(:name => "myname") @instance.stubs(:groups).returns :absent end it "should fail if the group does not exist" do @group_manager.expects(:find).with("mygroup").returns nil expect { @instance.groups = "mygroup" }.to raise_error(Puppet::Error) end it "should only pass the attributes it cares about to the group manager" do @group_manager.expects(:update).with { |name, attrs| attrs[:gid].nil? } @instance.groups = "one" end it "should always include :ensure => :present in the current values" do @group_manager.expects(:update).with { |name, is, should| is[:ensure] == :present } @instance.groups = "one" end it "should always include :ensure => :present in the desired values" do @group_manager.expects(:update).with { |name, is, should| should[:ensure] == :present } @instance.groups = "one" end it "should always pass the group's original member list" do @one[:members] = %w{yay ness} @group_manager.expects(:update).with { |name, is, should| is[:members] == %w{yay ness} } @instance.groups = "one" end it "should find the group again when resetting its member list, so it has the full member list" do @group_manager.expects(:find).with("one").returns(@one) @group_manager.stubs(:update) @instance.groups = "one" end context "for groups that have no members" do it "should create a new members attribute with its value being the user's name" do @group_manager.expects(:update).with { |name, is, should| should[:members] == %w{myname} } @instance.groups = "one" end end context "for groups it is being removed from" do it "should replace the group's member list with one missing the user's name" do @one[:members] = %w{myname a} @two[:members] = %w{myname b} @group_manager.expects(:update).with { |name, is, should| name == "two" and should[:members] == %w{b} } @instance.stubs(:groups).returns "one,two" @instance.groups = "one" end it "should mark the member list as empty if there are no remaining members" do @one[:members] = %w{myname} @two[:members] = %w{myname b} @group_manager.expects(:update).with { |name, is, should| name == "one" and should[:members] == :absent } @instance.stubs(:groups).returns "one,two" @instance.groups = "two" end end context "for groups that already have members" do it "should replace each group's member list with a new list including the user's name" do @one[:members] = %w{a b} @group_manager.expects(:update).with { |name, is, should| should[:members] == %w{a b myname} } @two[:members] = %w{b c} @group_manager.expects(:update).with { |name, is, should| should[:members] == %w{b c myname} } @instance.groups = "one,two" end end context "for groups of which it is a member" do it "should do nothing" do @one[:members] = %w{a b} @group_manager.expects(:update).with { |name, is, should| should[:members] == %w{a b myname} } @two[:members] = %w{c myname} @group_manager.expects(:update).with { |name, *other| name == "two" }.never @instance.stubs(:groups).returns "two" @instance.groups = "one,two" end end end end puppet-5.5.10/spec/unit/provider/user/openbsd_spec.rb0000644005276200011600000000514613417161721022532 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:user).provider(:openbsd) do before :each do described_class.stubs(:command).with(:password).returns '/usr/sbin/passwd' described_class.stubs(:command).with(:add).returns '/usr/sbin/useradd' described_class.stubs(:command).with(:modify).returns '/usr/sbin/usermod' described_class.stubs(:command).with(:delete).returns '/usr/sbin/userdel' end let(:resource) do Puppet::Type.type(:user).new( :name => 'myuser', :managehome => :false, :system => :false, :loginclass => 'staff', :provider => provider ) end let(:provider) { described_class.new(:name => 'myuser') } let(:shadow_entry) { return unless Puppet.features.libshadow? entry = Struct::PasswdEntry.new entry[:sp_namp] = 'myuser' # login name entry[:sp_loginclass] = 'staff' # login class entry } describe "#expiry=" do it "should pass expiry to usermod as MM/DD/YY" do resource[:expiry] = '2014-11-05' provider.expects(:execute).with(['/usr/sbin/usermod', '-e', 'November 05 2014', 'myuser'], has_entry(:custom_environment, {})) provider.expiry = '2014-11-05' end it "should use -e with an empty string when the expiry property is removed" do resource[:expiry] = :absent provider.expects(:execute).with(['/usr/sbin/usermod', '-e', '', 'myuser'], has_entry(:custom_environment, {})) provider.expiry = :absent end end describe "#addcmd" do it "should return an array with the full command and expiry as MM/DD/YY" do Facter.stubs(:value).with(:osfamily).returns('OpenBSD') resource[:expiry] = "1997-06-01" expect(provider.addcmd).to eq(['/usr/sbin/useradd', '-e', 'June 01 1997', 'myuser']) end end describe "#loginclass" do before :each do resource end it "should return the loginclass if set", :if => Puppet.features.libshadow? do Shadow::Passwd.expects(:getspnam).with('myuser').returns shadow_entry provider.send(:loginclass).should == 'staff' end it "should return the empty string when loginclass isn't set", :if => Puppet.features.libshadow? do shadow_entry[:sp_loginclass] = '' Shadow::Passwd.expects(:getspnam).with('myuser').returns shadow_entry provider.send(:loginclass).should == '' end it "should return nil when loginclass isn't available", :if => Puppet.features.libshadow? do shadow_entry[:sp_loginclass] = nil Shadow::Passwd.expects(:getspnam).with('myuser').returns shadow_entry provider.send(:loginclass).should be_nil end end end puppet-5.5.10/spec/unit/provider/user/pw_spec.rb0000644005276200011600000002110113417161721021513 0ustar jenkinsjenkinsrequire 'spec_helper' describe Puppet::Type.type(:user).provider(:pw) do let :resource do Puppet::Type.type(:user).new(:name => "testuser", :provider => :pw) end context "when creating users" do let :provider do prov = resource.provider prov.expects(:exists?).returns nil prov end it "should run pw with no additional flags when no properties are given" do expect(provider.addcmd).to eq([described_class.command(:pw), "useradd", "testuser"]) provider.expects(:execute).with([described_class.command(:pw), "useradd", "testuser"], kind_of(Hash)) provider.create end it "should use -o when allowdupe is enabled" do resource[:allowdupe] = true provider.expects(:execute).with(includes("-o"), kind_of(Hash)) provider.create end it "should use -c with the correct argument when the comment property is set" do resource[:comment] = "Testuser Name" provider.expects(:execute).with(all_of(includes("-c"), includes("Testuser Name")), kind_of(Hash)) provider.create end it "should use -e with the correct argument when the expiry property is set" do resource[:expiry] = "2010-02-19" provider.expects(:execute).with(all_of(includes("-e"), includes("19-02-2010")), kind_of(Hash)) provider.create end it "should use -e 00-00-0000 if the expiry property has to be removed" do resource[:expiry] = :absent provider.expects(:execute).with(all_of(includes("-e"), includes("00-00-0000")), kind_of(Hash)) provider.create end it "should use -g with the correct argument when the gid property is set" do resource[:gid] = 12345 provider.expects(:execute).with(all_of(includes("-g"), includes(12345)), kind_of(Hash)) provider.create end it "should use -G with the correct argument when the groups property is set" do resource[:groups] = "group1" provider.expects(:execute).with(all_of(includes("-G"), includes("group1")), kind_of(Hash)) provider.create end it "should use -G with all the given groups when the groups property is set to an array" do resource[:groups] = ["group1", "group2"] provider.expects(:execute).with(all_of(includes("-G"), includes("group1,group2")), kind_of(Hash)) provider.create end it "should use -d with the correct argument when the home property is set" do resource[:home] = "/home/testuser" provider.expects(:execute).with(all_of(includes("-d"), includes("/home/testuser")), kind_of(Hash)) provider.create end it "should use -m when the managehome property is enabled" do resource[:managehome] = true provider.expects(:execute).with(includes("-m"), kind_of(Hash)) provider.create end it "should call the password set function with the correct argument when the password property is set" do resource[:password] = "*" provider.expects(:execute) provider.expects(:password=).with("*") provider.create end it "should use -s with the correct argument when the shell property is set" do resource[:shell] = "/bin/sh" provider.expects(:execute).with(all_of(includes("-s"), includes("/bin/sh")), kind_of(Hash)) provider.create end it "should use -u with the correct argument when the uid property is set" do resource[:uid] = 12345 provider.expects(:execute).with(all_of(includes("-u"), includes(12345)), kind_of(Hash)) provider.create end # (#7500) -p should not be used to set a password (it means something else) it "should not use -p when a password is given" do resource[:password] = "*" expect(provider.addcmd).not_to include("-p") provider.expects(:password=) provider.expects(:execute).with(Not(includes("-p")), kind_of(Hash)) provider.create end end context "when deleting users" do it "should run pw with no additional flags" do provider = resource.provider provider.expects(:exists?).returns true expect(provider.deletecmd).to eq([described_class.command(:pw), "userdel", "testuser"]) provider.expects(:execute).with([described_class.command(:pw), "userdel", "testuser"], has_entry(:custom_environment, {})) provider.delete end # The above test covers this, but given the consequences of # accidentally deleting a user's home directory it seems better to # have an explicit test. it "should not use -r when managehome is not set" do provider = resource.provider provider.expects(:exists?).returns true resource[:managehome] = false provider.expects(:execute).with(Not(includes("-r")), has_entry(:custom_environment, {})) provider.delete end it "should use -r when managehome is set" do provider = resource.provider provider.expects(:exists?).returns true resource[:managehome] = true provider.expects(:execute).with(includes("-r"), has_entry(:custom_environment, {})) provider.delete end end context "when modifying users" do let :provider do resource.provider end it "should run pw with the correct arguments" do expect(provider.modifycmd("uid", 12345)).to eq([described_class.command(:pw), "usermod", "testuser", "-u", 12345]) provider.expects(:execute).with([described_class.command(:pw), "usermod", "testuser", "-u", 12345], has_entry(:custom_environment, {})) provider.uid = 12345 end it "should use -c with the correct argument when the comment property is changed" do resource[:comment] = "Testuser Name" provider.expects(:execute).with(all_of(includes("-c"), includes("Testuser New Name")), has_entry(:custom_environment, {})) provider.comment = "Testuser New Name" end it "should use -e with the correct argument when the expiry property is changed" do resource[:expiry] = "2010-02-19" provider.expects(:execute).with(all_of(includes("-e"), includes("19-02-2011")), has_entry(:custom_environment, {})) provider.expiry = "2011-02-19" end it "should use -e with the correct argument when the expiry property is removed" do resource[:expiry] = :absent provider.expects(:execute).with(all_of(includes("-e"), includes("00-00-0000")), has_entry(:custom_environment, {})) provider.expiry = :absent end it "should use -g with the correct argument when the gid property is changed" do resource[:gid] = 12345 provider.expects(:execute).with(all_of(includes("-g"), includes(54321)), has_entry(:custom_environment, {})) provider.gid = 54321 end it "should use -G with the correct argument when the groups property is changed" do resource[:groups] = "group1" provider.expects(:execute).with(all_of(includes("-G"), includes("group2")), has_entry(:custom_environment, {})) provider.groups = "group2" end it "should use -G with all the given groups when the groups property is changed with an array" do resource[:groups] = ["group1", "group2"] provider.expects(:execute).with(all_of(includes("-G"), includes("group3,group4")), has_entry(:custom_environment, {})) provider.groups = "group3,group4" end it "should use -d with the correct argument when the home property is changed" do resource[:home] = "/home/testuser" provider.expects(:execute).with(all_of(includes("-d"), includes("/newhome/testuser")), has_entry(:custom_environment, {})) provider.home = "/newhome/testuser" end it "should use -m and -d with the correct argument when the home property is changed and managehome is enabled" do resource[:home] = "/home/testuser" resource[:managehome] = true provider.expects(:execute).with(all_of(includes("-d"), includes("/newhome/testuser"), includes("-m")), has_entry(:custom_environment, {})) provider.home = "/newhome/testuser" end it "should call the password set function with the correct argument when the password property is changed" do resource[:password] = "*" provider.expects(:password=).with("!") provider.password = "!" end it "should use -s with the correct argument when the shell property is changed" do resource[:shell] = "/bin/sh" provider.expects(:execute).with(all_of(includes("-s"), includes("/bin/tcsh")), has_entry(:custom_environment, {})) provider.shell = "/bin/tcsh" end it "should use -u with the correct argument when the uid property is changed" do resource[:uid] = 12345 provider.expects(:execute).with(all_of(includes("-u"), includes(54321)), has_entry(:custom_environment, {})) provider.uid = 54321 end end end puppet-5.5.10/spec/unit/provider/user/useradd_spec.rb0000755005276200011600000006132613417161721022534 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:user).provider(:useradd) do before :each do described_class.stubs(:command).with(:password).returns '/usr/bin/chage' described_class.stubs(:command).with(:localpassword).returns '/usr/sbin/lchage' described_class.stubs(:command).with(:add).returns '/usr/sbin/useradd' described_class.stubs(:command).with(:localadd).returns '/usr/sbin/luseradd' described_class.stubs(:command).with(:modify).returns '/usr/sbin/usermod' described_class.stubs(:command).with(:localmodify).returns '/usr/sbin/lusermod' described_class.stubs(:command).with(:delete).returns '/usr/sbin/userdel' described_class.stubs(:command).with(:localdelete).returns '/usr/sbin/luserdel' end let(:resource) do Puppet::Type.type(:user).new( :name => 'myuser', :managehome => :false, :system => :false, :provider => provider ) end let(:provider) { described_class.new(:name => 'myuser') } let(:shadow_entry) { return unless Puppet.features.libshadow? entry = Struct::PasswdEntry.new entry[:sp_namp] = 'myuser' # login name entry[:sp_pwdp] = '$6$FvW8Ib8h$qQMI/CR9m.QzIicZKutLpBgCBBdrch1IX0rTnxuI32K1pD9.RXZrmeKQlaC.RzODNuoUtPPIyQDufunvLOQWF0' # encrypted password entry[:sp_lstchg] = 15573 # date of last password change entry[:sp_min] = 10 # minimum password age entry[:sp_max] = 20 # maximum password age entry[:sp_warn] = 7 # password warning period entry[:sp_inact] = -1 # password inactivity period entry[:sp_expire] = 15706 # account expiration date entry } describe "#create" do before do provider.stubs(:exists?).returns(false) end it "should add -g when no gid is specified and group already exists" do Puppet::Util.stubs(:gid).returns(true) resource[:ensure] = :present provider.expects(:execute).with(includes('-g'), kind_of(Hash)) provider.create end it "should use -G to set groups" do Facter.stubs(:value).with(:osfamily).returns('Not RedHat') resource[:ensure] = :present resource[:groups] = ['group1', 'group2'] provider.expects(:execute).with(['/usr/sbin/useradd', '-G', 'group1,group2', 'myuser'], kind_of(Hash)) provider.create end it "should use -G to set groups without -M on RedHat" do Facter.stubs(:value).with(:osfamily).returns('RedHat') resource[:ensure] = :present resource[:groups] = ['group1', 'group2'] provider.expects(:execute).with(['/usr/sbin/useradd', '-G', 'group1,group2', '-M', 'myuser'], kind_of(Hash)) provider.create end it "should add -o when allowdupe is enabled and the user is being created" do resource[:allowdupe] = true provider.expects(:execute).with(includes('-o'), kind_of(Hash)) provider.create end describe "on systems that support has_system", :if => described_class.system_users? do it "should add -r when system is enabled" do resource[:system] = :true expect(provider).to be_system_users provider.expects(:execute).with(includes('-r'), kind_of(Hash)) provider.create end end describe "on systems that do not support has_system", :unless => described_class.system_users? do it "should not add -r when system is enabled" do resource[:system] = :true expect(provider).not_to be_system_users provider.expects(:execute).with(['/usr/sbin/useradd', 'myuser'], kind_of(Hash)) provider.create end end it "should set password age rules" do described_class.has_feature :manages_password_age resource[:password_min_age] = 5 resource[:password_max_age] = 10 resource[:password_warn_days] = 15 provider.expects(:execute).with(includes('/usr/sbin/useradd'), kind_of(Hash)) provider.expects(:execute).with(['/usr/bin/chage', '-m', 5, '-M', 10, '-W', 15, 'myuser'], has_entries({:failonfail => true, :combine => true, :custom_environment => {}})) provider.create end describe "on systems with the libuser and forcelocal=true" do before do described_class.has_feature :libuser resource[:forcelocal] = true end it "should use luseradd instead of useradd" do provider.expects(:execute).with(includes('/usr/sbin/luseradd'), has_entry(:custom_environment, has_key('LIBUSER_CONF'))) provider.create end it "should NOT use -o when allowdupe=true" do resource[:allowdupe] = :true provider.expects(:execute).with(Not(includes('-o')), has_entry(:custom_environment, has_key('LIBUSER_CONF'))) provider.create end it "should raise an exception for duplicate UIDs" do resource[:uid] = 505 provider.stubs(:finduser).returns(true) expect { provider.create }.to raise_error(Puppet::Error, "UID 505 already exists, use allowdupe to force user creation") end it "should not use -G for luseradd and should call usermod with -G after luseradd when groups property is set" do resource[:groups] = ['group1', 'group2'] provider.expects(:execute).with(all_of(includes('/usr/sbin/luseradd'), Not(includes('-G'))), has_entry(:custom_environment, has_key('LIBUSER_CONF'))) provider.expects(:execute).with(all_of(includes('/usr/sbin/usermod'), includes('-G')), has_entry(:custom_environment, has_key('LIBUSER_CONF'))) provider.create end it "should not use -m when managehome set" do resource[:managehome] = :true provider.expects(:execute).with(Not(includes('-m')), has_entry(:custom_environment, has_key('LIBUSER_CONF'))) provider.create end it "should not use -e with luseradd, should call usermod with -e after luseradd when expiry is set" do resource[:expiry] = '2038-01-24' provider.expects(:execute).with(all_of(includes('/usr/sbin/luseradd'), Not(includes('-e'))), has_entry(:custom_environment, has_key('LIBUSER_CONF'))) provider.expects(:execute).with(all_of(includes('/usr/sbin/usermod'), includes('-e')), has_entry(:custom_environment, has_key('LIBUSER_CONF'))) provider.create end it 'should set password age rules locally' do described_class.has_feature :manages_password_age resource[:password_min_age] = 5 resource[:password_max_age] = 10 resource[:password_warn_days] = 15 provider.expects(:execute).with(includes('/usr/sbin/luseradd'), has_entry(:custom_environment, has_key('LIBUSER_CONF'))) provider.expects(:execute).with(['/usr/sbin/lchage', '-m', 5, '-M', 10, '-W', 15, 'myuser'], has_entry(:custom_environment, has_key('LIBUSER_CONF'))) provider.create end end describe "on systems that allow to set shell" do it "should trigger shell validation" do resource[:shell] = '/bin/bash' provider.expects(:check_valid_shell) provider.expects(:execute).with(includes('-s'), kind_of(Hash)) provider.create end end end describe '#modify' do describe "on systems with the libuser and forcelocal=false" do before do described_class.has_feature :libuser resource[:forcelocal] = false end it "should use usermod" do provider.expects(:execute).with(['/usr/sbin/usermod', '-u', 150, 'myuser'], has_entries({:failonfail => true, :combine => true, :custom_environment => {}})) provider.uid = 150 end it "should use -o when allowdupe=true" do resource[:allowdupe] = :true provider.expects(:execute).with(includes('-o'), has_entries({:failonfail => true, :combine => true, :custom_environment => {}})) provider.uid = 505 end it 'should use chage for password_min_age' do provider.expects(:execute).with(['/usr/bin/chage', '-m', 100, 'myuser'], has_entries({:failonfail => true, :combine => true, :custom_environment => {}})) provider.password_min_age = 100 end it 'should use chage for password_max_age' do provider.expects(:execute).with(['/usr/bin/chage', '-M', 101, 'myuser'], has_entries({:failonfail => true, :combine => true, :custom_environment => {}})) provider.password_max_age = 101 end it 'should use chage for password_warn_days' do provider.expects(:execute).with(['/usr/bin/chage', '-W', 99, 'myuser'], has_entries({:failonfail => true, :combine => true, :custom_environment => {}})) provider.password_warn_days = 99 end it 'should not call check_allow_dup if not modifying the uid' do provider.expects(:check_allow_dup).never provider.expects(:execute) provider.home = 'foo/bar' end end describe "on systems with the libuser and forcelocal=true" do before do described_class.has_feature :libuser resource[:forcelocal] = true end it "should use lusermod and not usermod" do provider.expects(:execute).with(['/usr/sbin/lusermod', '-u', 150, 'myuser'], has_entry(:custom_environment, has_key('LIBUSER_CONF'))) provider.uid = 150 end it "should NOT use -o when allowdupe=true" do resource[:allowdupe] = :true provider.expects(:execute).with(Not(includes('-o')), has_entry(:custom_environment, has_key('LIBUSER_CONF'))) provider.uid = 505 end it "should raise an exception for duplicate UIDs" do resource[:uid] = 505 provider.stubs(:finduser).returns(true) expect { provider.uid = 505 }.to raise_error(Puppet::Error, "UID 505 already exists, use allowdupe to force user creation") end it 'should use lchage for password_warn_days' do provider.expects(:execute).with(['/usr/sbin/lchage', '-W', 99, 'myuser'], has_entry(:custom_environment, has_key('LIBUSER_CONF'))) provider.password_warn_days = 99 end it 'should use lchage for password_min_age' do provider.expects(:execute).with(['/usr/sbin/lchage', '-m', 100, 'myuser'], has_entry(:custom_environment, has_key('LIBUSER_CONF'))) provider.password_min_age = 100 end it 'should use lchage for password_max_age' do provider.expects(:execute).with(['/usr/sbin/lchage', '-M', 101, 'myuser'], has_entry(:custom_environment, has_key('LIBUSER_CONF'))) provider.password_max_age = 101 end end end describe "#uid=" do it "should add -o when allowdupe is enabled and the uid is being modified" do resource[:allowdupe] = :true provider.expects(:execute).with(['/usr/sbin/usermod', '-u', 150, '-o', 'myuser'], has_entry(:custom_environment, {})) provider.uid = 150 end end describe "#expiry=" do it "should pass expiry to usermod as MM/DD/YY when on Solaris" do Facter.expects(:value).with(:operatingsystem).returns 'Solaris' resource[:expiry] = '2012-10-31' provider.expects(:execute).with(['/usr/sbin/usermod', '-e', '10/31/2012', 'myuser'], has_entry(:custom_environment, {})) provider.expiry = '2012-10-31' end it "should pass expiry to usermod as YYYY-MM-DD when not on Solaris" do Facter.expects(:value).with(:operatingsystem).returns 'not_solaris' resource[:expiry] = '2012-10-31' provider.expects(:execute).with(['/usr/sbin/usermod', '-e', '2012-10-31', 'myuser'], has_entry(:custom_environment, {})) provider.expiry = '2012-10-31' end it "should use -e with an empty string when the expiry property is removed" do resource[:expiry] = :absent provider.expects(:execute).with(['/usr/sbin/usermod', '-e', '', 'myuser'], has_entry(:custom_environment, {})) provider.expiry = :absent end end describe "#check_allow_dup" do it "should return an array with a flag if dup is allowed" do resource[:allowdupe] = :true expect(provider.check_allow_dup).to eq(["-o"]) end it "should return an empty array if no dup is allowed" do resource[:allowdupe] = :false expect(provider.check_allow_dup).to eq([]) end end describe "#check_system_users" do it "should check system users" do described_class.expects(:system_users?).returns true resource.expects(:system?) provider.check_system_users end it "should return an array with a flag if it's a system user" do described_class.expects(:system_users?).returns true resource[:system] = :true expect(provider.check_system_users).to eq(["-r"]) end it "should return an empty array if it's not a system user" do described_class.expects(:system_users?).returns true resource[:system] = :false expect(provider.check_system_users).to eq([]) end it "should return an empty array if system user is not featured" do described_class.expects(:system_users?).returns false resource[:system] = :true expect(provider.check_system_users).to eq([]) end end describe "#check_manage_home" do it "should return an array with -m flag if home is managed" do resource[:managehome] = :true provider.expects(:execute).with(includes('-m'), has_entry(:custom_environment, {})) provider.create end it "should return an array with -r flag if home is managed" do resource[:managehome] = :true resource[:ensure] = :absent provider.stubs(:exists?).returns(true) provider.expects(:execute).with(includes('-r'), has_entry(:custom_environment, {})) provider.delete end it "should use -M flag if home is not managed and on Redhat" do Facter.stubs(:value).with(:osfamily).returns("RedHat") resource[:managehome] = :false provider.expects(:execute).with(includes('-M'), kind_of(Hash)) provider.create end it "should not use -M flag if home is not managed and not on Redhat" do Facter.stubs(:value).with(:osfamily).returns("not RedHat") resource[:managehome] = :false provider.expects(:execute).with(Not(includes('-M')), kind_of(Hash)) provider.create end end describe "#addcmd" do before do resource[:allowdupe] = :true resource[:managehome] = :true resource[:system] = :true resource[:groups] = [ 'somegroup' ] end it "should call command with :add" do provider.expects(:command).with(:add) provider.addcmd end it "should add properties" do provider.expects(:add_properties).returns(['-foo_add_properties']) expect(provider.addcmd).to include '-foo_add_properties' end it "should check and add if dup allowed" do provider.expects(:check_allow_dup).returns(['-allow_dup_flag']) expect(provider.addcmd).to include '-allow_dup_flag' end it "should check and add if home is managed" do provider.expects(:check_manage_home).returns(['-manage_home_flag']) expect(provider.addcmd).to include '-manage_home_flag' end it "should add the resource :name" do expect(provider.addcmd).to include 'myuser' end describe "on systems featuring system_users", :if => described_class.system_users? do it "should return an array with -r if system? is true" do resource[:system] = :true expect(provider.addcmd).to include("-r") end it "should return an array without -r if system? is false" do resource[:system] = :false expect(provider.addcmd).not_to include("-r") end end describe "on systems not featuring system_users", :unless => described_class.system_users? do [:false, :true].each do |system| it "should return an array without -r if system? is #{system}" do resource[:system] = system expect(provider.addcmd).not_to include("-r") end end end it "should return an array with the full command and expiry as MM/DD/YY when on Solaris" do Facter.stubs(:value).with(:operatingsystem).returns 'Solaris' described_class.expects(:system_users?).returns true resource[:expiry] = "2012-08-18" expect(provider.addcmd).to eq(['/usr/sbin/useradd', '-e', '08/18/2012', '-G', 'somegroup', '-o', '-m', '-r', 'myuser']) end it "should return an array with the full command and expiry as YYYY-MM-DD when not on Solaris" do Facter.stubs(:value).with(:operatingsystem).returns 'not_solaris' described_class.expects(:system_users?).returns true resource[:expiry] = "2012-08-18" expect(provider.addcmd).to eq(['/usr/sbin/useradd', '-e', '2012-08-18', '-G', 'somegroup', '-o', '-m', '-r', 'myuser']) end it "should return an array without -e if expiry is undefined full command" do described_class.expects(:system_users?).returns true expect(provider.addcmd).to eq(["/usr/sbin/useradd", "-G", "somegroup", "-o", "-m", "-r", "myuser"]) end it "should pass -e \"\" if the expiry has to be removed" do described_class.expects(:system_users?).returns true resource[:expiry] = :absent expect(provider.addcmd).to eq(['/usr/sbin/useradd', '-e', '', '-G', 'somegroup', '-o', '-m', '-r', 'myuser']) end it "should use lgroupadd with forcelocal=true" do resource[:forcelocal] = :true expect(provider.addcmd[0]).to eq('/usr/sbin/luseradd') end it "should not pass -o with forcelocal=true and allowdupe=true" do resource[:forcelocal] = :true resource[:allowdupe] = :true expect(provider.addcmd).not_to include("-o") end context 'when forcelocal=true' do before do resource[:forcelocal] = :true end it 'does not pass lchage options to luseradd for password_max_age' do resource[:password_max_age] = 100 expect(provider.addcmd).not_to include('-M') end it 'does not pass lchage options to luseradd for password_min_age' do resource[:managehome] = false # This needs to be set so that we don't pass in -m to create the home resource[:password_min_age] = 100 expect(provider.addcmd).not_to include('-m') end it 'does not pass lchage options to luseradd for password_warn_days' do resource[:password_warn_days] = 100 expect(provider.addcmd).not_to include('-W') end end end { :password_min_age => 10, :password_max_age => 20, :password_warn_days => 30, :password => '$6$FvW8Ib8h$qQMI/CR9m.QzIicZKutLpBgCBBdrch1IX0rTnxuI32K1pD9.RXZrmeKQlaC.RzODNuoUtPPIyQDufunvLOQWF0' }.each_pair do |property, expected_value| describe "##{property}" do before :each do resource # just to link the resource to the provider end it "should return absent if libshadow feature is not present" do Puppet.features.stubs(:libshadow?).returns false # Shadow::Passwd.expects(:getspnam).never # if we really don't have libshadow we dont have Shadow::Passwd either expect(provider.send(property)).to eq(:absent) end it "should return absent if user cannot be found", :if => Puppet.features.libshadow? do Shadow::Passwd.expects(:getspnam).with('myuser').returns nil expect(provider.send(property)).to eq(:absent) end it "should return the correct value if libshadow is present", :if => Puppet.features.libshadow? do Shadow::Passwd.expects(:getspnam).with('myuser').returns shadow_entry expect(provider.send(property)).to eq(expected_value) end # nameservice provider instances are initialized with a @canonical_name # instance variable to track the original name of the instance on disk # before converting it to UTF-8 if appropriate. When re-querying the # system for attributes of this user such as password info, we need to # supply the pre-UTF8-converted value. it "should query using the canonical_name attribute of the user", :if => Puppet.features.libshadow? do canonical_name = [253, 241].pack('C*').force_encoding(Encoding::EUC_KR) provider = described_class.new(:name => '??', :canonical_name => canonical_name) Shadow::Passwd.expects(:getspnam).with(canonical_name).returns shadow_entry provider.password end end end describe '#expiry' do before :each do resource # just to link the resource to the provider end it "should return absent if libshadow feature is not present" do Puppet.features.stubs(:libshadow?).returns false expect(provider.expiry).to eq(:absent) end it "should return absent if user cannot be found", :if => Puppet.features.libshadow? do Shadow::Passwd.expects(:getspnam).with('myuser').returns nil expect(provider.expiry).to eq(:absent) end it "should return absent if expiry is -1", :if => Puppet.features.libshadow? do shadow_entry.sp_expire = -1 Shadow::Passwd.expects(:getspnam).with('myuser').returns shadow_entry expect(provider.expiry).to eq(:absent) end it "should convert to YYYY-MM-DD", :if => Puppet.features.libshadow? do Shadow::Passwd.expects(:getspnam).with('myuser').returns shadow_entry expect(provider.expiry).to eq('2013-01-01') end end describe "#passcmd" do before do resource[:allowdupe] = :true resource[:managehome] = :true resource[:system] = :true described_class.has_feature :manages_password_age end it "should call command with :pass" do # command(:password) is only called inside passcmd if # password_min_age or password_max_age is set resource[:password_min_age] = 123 provider.expects(:command).with(:password) provider.passcmd end it "should return nil if neither min nor max is set" do expect(provider.passcmd).to be_nil end it "should return a chage command array with -m and the user name if password_min_age is set" do resource[:password_min_age] = 123 expect(provider.passcmd).to eq(['/usr/bin/chage', '-m', 123, 'myuser']) end it "should return a chage command array with -M if password_max_age is set" do resource[:password_max_age] = 999 expect(provider.passcmd).to eq(['/usr/bin/chage', '-M', 999, 'myuser']) end it "should return a chage command array with -W if password_warn_days is set" do resource[:password_warn_days] = 999 expect(provider.passcmd).to eq(['/usr/bin/chage', '-W', 999, 'myuser']) end it "should return a chage command array with -M -m if both password_min_age and password_max_age are set" do resource[:password_min_age] = 123 resource[:password_max_age] = 999 expect(provider.passcmd).to eq(['/usr/bin/chage', '-m', 123, '-M', 999, 'myuser']) end it "should return a chage command array with -M -m -W if password_min_age, password_max_age and password_warn_days are set" do resource[:password_min_age] = 123 resource[:password_max_age] = 999 resource[:password_warn_days] = 555 expect(provider.passcmd).to eq(['/usr/bin/chage', '-m', 123, '-M', 999, '-W', 555, 'myuser']) end context 'with forcelocal=true' do before do resource[:forcelocal] = true end it 'should return a lchage command array with -M -m -W if password_min_age, password_max_age and password_warn_days are set' do resource[:password_min_age] = 123 resource[:password_max_age] = 999 resource[:password_warn_days] = 555 expect(provider.passcmd).to eq(['/usr/sbin/lchage', '-m', 123, '-M', 999, '-W', 555, 'myuser']) end end end describe "#check_valid_shell" do it "should raise an error if shell does not exist" do resource[:shell] = 'foo/bin/bash' expect { provider.check_valid_shell }.to raise_error(Puppet::Error, /Shell foo\/bin\/bash must exist/) end it "should raise an error if the shell is not executable" do FileTest.stubs(:executable?).with('LICENSE').returns false resource[:shell] = 'LICENSE' expect { provider.check_valid_shell }.to raise_error(Puppet::Error, /Shell LICENSE must be executable/) end end describe "#delete" do before do provider.stubs(:exists?).returns(true) resource[:ensure] = :absent end describe "on systems with the libuser and forcelocal=false" do before do described_class.has_feature :libuser resource[:forcelocal] = false end it "should use userdel to delete users" do provider.expects(:execute).with(includes('/usr/sbin/userdel'), has_entry(:custom_environment, {})) provider.delete end end describe "on systems with the libuser and forcelocal=true" do before do described_class.has_feature :libuser resource[:forcelocal] = true end it "should use luserdel to delete users" do provider.expects(:execute).with(includes('/usr/sbin/luserdel'), has_entry(:custom_environment, has_key('LIBUSER_CONF'))) provider.delete end end end end puppet-5.5.10/spec/unit/provider/user/hpux_spec.rb0000644005276200011600000000445013417161722022062 0ustar jenkinsjenkinsrequire 'spec_helper' require 'etc' describe Puppet::Type.type(:user).provider(:hpuxuseradd), :unless => Puppet.features.microsoft_windows? do let :resource do Puppet::Type.type(:user).new( :title => 'testuser', :comment => 'Test J. User', :provider => :hpuxuseradd ) end let(:provider) { resource.provider } it "should add -F when modifying a user" do resource.stubs(:allowdupe?).returns true provider.expects(:execute).with { |args| args.include?("-F") } provider.uid = 1000 end it "should add -F when deleting a user" do provider.stubs(:exists?).returns(true) provider.expects(:execute).with { |args| args.include?("-F") } provider.delete end context "managing passwords" do let :pwent do Struct::Passwd.new("testuser", "foopassword") end before :each do Etc.stubs(:getpwent).returns(pwent) Etc.stubs(:getpwnam).returns(pwent) resource.stubs(:command).with(:modify).returns '/usr/sam/lbin/usermod.sam' end it "should have feature manages_passwords" do expect(described_class).to be_manages_passwords end it "should return nil if user does not exist" do Etc.stubs(:getpwent).returns(nil) expect(provider.password).to be_nil end it "should return password entry if exists" do expect(provider.password).to eq("foopassword") end end context "check for trusted computing" do before :each do provider.stubs(:command).with(:modify).returns '/usr/sam/lbin/usermod.sam' end it "should add modprpw to modifycmd if Trusted System" do resource.stubs(:allowdupe?).returns true provider.expects(:exec_getprpw).with('root','-m uid').returns('uid=0') provider.expects(:execute).with(['/usr/sam/lbin/usermod.sam', '-u', 1000, '-o', 'testuser', '-F', ';', '/usr/lbin/modprpw', '-v', '-l', 'testuser'], has_entry(:custom_environment, {})) provider.uid = 1000 end it "should not add modprpw if not Trusted System" do resource.stubs(:allowdupe?).returns true provider.expects(:exec_getprpw).with('root','-m uid').returns('System is not trusted') provider.expects(:execute).with(['/usr/sam/lbin/usermod.sam', '-u', 1000, '-o', 'testuser', '-F'], has_entry(:custom_environment, {})) provider.uid = 1000 end end end puppet-5.5.10/spec/unit/provider/user/user_role_add_spec.rb0000644005276200011600000002766313417161722023720 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/files' require 'tempfile' describe Puppet::Type.type(:user).provider(:user_role_add), :unless => Puppet.features.microsoft_windows? do include PuppetSpec::Files let(:resource) { Puppet::Type.type(:user).new(:name => 'myuser', :managehome => false, :allowdupe => false) } let(:provider) { described_class.new(resource) } before do resource.stubs(:should).returns "fakeval" resource.stubs(:should).with(:keys).returns Hash.new resource.stubs(:[]).returns "fakeval" end describe "#command" do before do klass = stub("provider") klass.stubs(:command).with(:foo).returns("userfoo") klass.stubs(:command).with(:role_foo).returns("rolefoo") provider.stubs(:class).returns(klass) end it "should use the command if not a role and ensure!=role" do provider.stubs(:is_role?).returns(false) provider.stubs(:exists?).returns(false) resource.stubs(:[]).with(:ensure).returns(:present) provider.class.stubs(:foo) expect(provider.command(:foo)).to eq("userfoo") end it "should use the role command when a role" do provider.stubs(:is_role?).returns(true) expect(provider.command(:foo)).to eq("rolefoo") end it "should use the role command when !exists and ensure=role" do provider.stubs(:is_role?).returns(false) provider.stubs(:exists?).returns(false) resource.stubs(:[]).with(:ensure).returns(:role) expect(provider.command(:foo)).to eq("rolefoo") end end describe "#transition" do it "should return the type set to whatever is passed in" do provider.expects(:command).with(:modify).returns("foomod") provider.transition("bar").include?("type=bar") end end describe "#create" do before do provider.stubs(:password=) end it "should use the add command when the user is not a role" do provider.stubs(:is_role?).returns(false) provider.expects(:addcmd).returns("useradd") provider.expects(:run).at_least_once provider.create end it "should use transition(normal) when the user is a role" do provider.stubs(:is_role?).returns(true) provider.expects(:transition).with("normal") provider.expects(:run) provider.create end it "should set password age rules" do resource = Puppet::Type.type(:user).new :name => "myuser", :password_min_age => 5, :password_max_age => 10, :password_warn_days => 15, :provider => :user_role_add provider = described_class.new(resource) provider.stubs(:user_attributes) provider.stubs(:execute) provider.expects(:execute).with { |cmd, *args| args == ["-n", 5, "-x", 10, '-w', 15, "myuser"] } provider.create end end describe "#destroy" do it "should use the delete command if the user exists and is not a role" do provider.stubs(:exists?).returns(true) provider.stubs(:is_role?).returns(false) provider.expects(:deletecmd) provider.expects(:run) provider.destroy end it "should use the delete command if the user is a role" do provider.stubs(:exists?).returns(true) provider.stubs(:is_role?).returns(true) provider.expects(:deletecmd) provider.expects(:run) provider.destroy end end describe "#create_role" do it "should use the transition(role) if the user exists" do provider.stubs(:exists?).returns(true) provider.stubs(:is_role?).returns(false) provider.expects(:transition).with("role") provider.expects(:run) provider.create_role end it "should use the add command when role doesn't exists" do provider.stubs(:exists?).returns(false) provider.expects(:addcmd) provider.expects(:run) provider.create_role end end describe "with :allow_duplicates" do before do resource.stubs(:allowdupe?).returns true provider.stubs(:is_role?).returns(false) provider.stubs(:execute) resource.stubs(:system?).returns false provider.expects(:execute).with { |args| args.include?("-o") } end it "should add -o when the user is being created" do provider.stubs(:password=) provider.create end it "should add -o when the uid is being modified" do provider.uid = 150 end end [:roles, :auths, :profiles].each do |val| context "#send" do describe "when getting #{val}" do it "should get the user_attributes" do provider.expects(:user_attributes) provider.send(val) end it "should get the #{val} attribute" do attributes = mock("attributes") attributes.expects(:[]).with(val) provider.stubs(:user_attributes).returns(attributes) provider.send(val) end end end end describe "#keys" do it "should get the user_attributes" do provider.expects(:user_attributes) provider.keys end it "should call removed_managed_attributes" do provider.stubs(:user_attributes).returns({ :type => "normal", :foo => "something" }) provider.expects(:remove_managed_attributes) provider.keys end it "should removed managed attribute (type, auths, roles, etc)" do provider.stubs(:user_attributes).returns({ :type => "normal", :foo => "something" }) expect(provider.keys).to eq({ :foo => "something" }) end end describe "#add_properties" do it "should call build_keys_cmd" do resource.stubs(:should).returns "" resource.expects(:should).with(:keys).returns({ :foo => "bar" }) provider.expects(:build_keys_cmd).returns([]) provider.add_properties end it "should add the elements of the keys hash to an array" do resource.stubs(:should).returns "" resource.expects(:should).with(:keys).returns({ :foo => "bar"}) expect(provider.add_properties).to eq(["-K", "foo=bar"]) end end describe "#build_keys_cmd" do it "should build cmd array with keypairs separated by -K ending with user" do expect(provider.build_keys_cmd({"foo" => "bar", "baz" => "boo"})).to eq(["-K", "foo=bar", "-K", "baz=boo"]) end end describe "#keys=" do before do provider.stubs(:is_role?).returns(false) end it "should run a command" do provider.expects(:run) provider.keys=({}) end it "should build the command" do resource.stubs(:[]).with(:name).returns("someuser") provider.stubs(:command).returns("usermod") provider.expects(:build_keys_cmd).returns(["-K", "foo=bar"]) provider.expects(:run).with(["usermod", "-K", "foo=bar", "someuser"], "modify attribute key pairs") provider.keys=({}) end end describe "#password" do before do @array = mock "array" end it "should readlines of /etc/shadow" do File.expects(:readlines).with("/etc/shadow").returns([]) provider.password end it "should reject anything that doesn't start with alpha numerics" do @array.expects(:reject).returns([]) File.stubs(:readlines).with("/etc/shadow").returns(@array) provider.password end it "should collect splitting on ':'" do @array.stubs(:reject).returns(@array) @array.expects(:collect).returns([]) File.stubs(:readlines).with("/etc/shadow").returns(@array) provider.password end it "should find the matching user" do resource.stubs(:[]).with(:name).returns("username") @array.stubs(:reject).returns(@array) @array.stubs(:collect).returns([["username", "hashedpassword"], ["someoneelse", "theirpassword"]]) File.stubs(:readlines).with("/etc/shadow").returns(@array) expect(provider.password).to eq("hashedpassword") end it "should get the right password" do resource.stubs(:[]).with(:name).returns("username") File.stubs(:readlines).with("/etc/shadow").returns(["#comment", " nonsense", " ", "username:hashedpassword:stuff:foo:bar:::", "other:pword:yay:::"]) expect(provider.password).to eq("hashedpassword") end end describe "#password=" do let(:path) { tmpfile('etc-shadow') } before :each do provider.stubs(:target_file_path).returns(path) end def write_fixture(content) File.open(path, 'w') { |f| f.print(content) } end it "should update the target user" do write_fixture < Puppet.features.microsoft_windows? do let(:resource) do Puppet::Type.type(:user).new( :title => 'testuser', :comment => 'Test J. User', :provider => :windows_adsi ) end let(:provider) { resource.provider } let(:connection) { stub 'connection' } before :each do Puppet::Util::Windows::ADSI.stubs(:computer_name).returns('testcomputername') Puppet::Util::Windows::ADSI.stubs(:connect).returns connection # this would normally query the system, but not needed for these tests Puppet::Util::Windows::ADSI::User.stubs(:localized_domains).returns([]) end describe ".instances" do it "should enumerate all users" do names = ['user1', 'user2', 'user3'] stub_users = names.map{|n| stub(:name => n)} connection.stubs(:execquery).with('select name from win32_useraccount where localaccount = "TRUE"').returns(stub_users) expect(described_class.instances.map(&:name)).to match(names) end end it "should provide access to a Puppet::Util::Windows::ADSI::User object" do expect(provider.user).to be_a(Puppet::Util::Windows::ADSI::User) end describe "when retrieving the password property" do context "when the resource has a nil password" do it "should never issue a logon attempt" do resource.stubs(:[]).with(any_of(:name, :password)).returns(nil) Puppet::Util::Windows::User.expects(:logon_user).never provider.password end end end describe "when managing groups" do it 'should return the list of groups as an array of strings' do provider.user.stubs(:groups).returns nil groups = {'group1' => nil, 'group2' => nil, 'group3' => nil} Puppet::Util::Windows::ADSI::Group.expects(:name_sid_hash).returns(groups) expect(provider.groups).to eq(groups.keys) end it "should return an empty array if there are no groups" do provider.user.stubs(:groups).returns [] expect(provider.groups).to eq([]) end it 'should be able to add a user to a set of groups' do resource[:membership] = :minimum provider.user.expects(:set_groups).with('group1,group2', true) provider.groups = 'group1,group2' resource[:membership] = :inclusive provider.user.expects(:set_groups).with('group1,group2', false) provider.groups = 'group1,group2' end end describe "#groups_insync?" do let(:group1) { stub(:account => 'group1', :domain => '.', :sid => 'group1sid') } let(:group2) { stub(:account => 'group2', :domain => '.', :sid => 'group2sid') } let(:group3) { stub(:account => 'group3', :domain => '.', :sid => 'group3sid') } before :each do Puppet::Util::Windows::SID.stubs(:name_to_principal).with('group1').returns(group1) Puppet::Util::Windows::SID.stubs(:name_to_principal).with('group2').returns(group2) Puppet::Util::Windows::SID.stubs(:name_to_principal).with('group3').returns(group3) end it "should return true for same lists of members" do expect(provider.groups_insync?(['group1', 'group2'], ['group1', 'group2'])).to be_truthy end it "should return true for same lists of unordered members" do expect(provider.groups_insync?(['group1', 'group2'], ['group2', 'group1'])).to be_truthy end it "should return true for same lists of members irrespective of duplicates" do expect(provider.groups_insync?(['group1', 'group2', 'group2'], ['group2', 'group1', 'group1'])).to be_truthy end it "should return true when current group(s) and should group(s) are empty lists" do expect(provider.groups_insync?([], [])).to be_truthy end it "should return true when current groups is empty and should groups is nil" do expect(provider.groups_insync?([], nil)).to be_truthy end context "when membership => inclusive" do before :each do resource[:membership] = :inclusive end it "should return true when current and should contain the same groups in a different order" do expect(provider.groups_insync?(['group1', 'group2', 'group3'], ['group3', 'group1', 'group2'])).to be_truthy end it "should return false when current contains different groups than should" do expect(provider.groups_insync?(['group1'], ['group2'])).to be_falsey end it "should return false when current is nil" do expect(provider.groups_insync?(nil, ['group2'])).to be_falsey end it "should return false when should is nil" do expect(provider.groups_insync?(['group1'], nil)).to be_falsey end it "should return false when current contains members and should is empty" do expect(provider.groups_insync?(['group1'], [])).to be_falsey end it "should return false when current is empty and should contains members" do expect(provider.groups_insync?([], ['group2'])).to be_falsey end it "should return false when should groups(s) are not the only items in the current" do expect(provider.groups_insync?(['group1', 'group2'], ['group1'])).to be_falsey end it "should return false when current group(s) is not empty and should is an empty list" do expect(provider.groups_insync?(['group1','group2'], [])).to be_falsey end end context "when membership => minimum" do before :each do # this is also the default resource[:membership] = :minimum end it "should return false when current contains different groups than should" do expect(provider.groups_insync?(['group1'], ['group2'])).to be_falsey end it "should return false when current is nil" do expect(provider.groups_insync?(nil, ['group2'])).to be_falsey end it "should return true when should is nil" do expect(provider.groups_insync?(['group1'], nil)).to be_truthy end it "should return true when current contains members and should is empty" do expect(provider.groups_insync?(['group1'], [])).to be_truthy end it "should return false when current is empty and should contains members" do expect(provider.groups_insync?([], ['group2'])).to be_falsey end it "should return true when current group(s) contains at least the should list" do expect(provider.groups_insync?(['group1','group2'], ['group1'])).to be_truthy end it "should return true when current group(s) is not empty and should is an empty list" do expect(provider.groups_insync?(['group1','group2'], [])).to be_truthy end it "should return true when current group(s) contains at least the should list, even unordered" do expect(provider.groups_insync?(['group3','group1','group2'], ['group2','group1'])).to be_truthy end end end describe "when creating a user" do it "should create the user on the system and set its other properties" do resource[:groups] = ['group1', 'group2'] resource[:membership] = :inclusive resource[:comment] = 'a test user' resource[:home] = 'C:\Users\testuser' user = stub 'user' Puppet::Util::Windows::ADSI::User.expects(:create).with('testuser').returns user user.stubs(:groups).returns(['group2', 'group3']) create = sequence('create') user.expects(:password=).in_sequence(create) user.expects(:commit).in_sequence(create) user.expects(:set_groups).with('group1,group2', false).in_sequence(create) user.expects(:[]=).with('Description', 'a test user') user.expects(:[]=).with('HomeDirectory', 'C:\Users\testuser') provider.create end it "should load the profile if managehome is set" do resource[:password] = '0xDeadBeef' resource[:managehome] = true user = stub_everything 'user' Puppet::Util::Windows::ADSI::User.expects(:create).with('testuser').returns user Puppet::Util::Windows::User.expects(:load_profile).with('testuser', '0xDeadBeef') provider.create end it "should set a user's password" do provider.user.expects(:disabled?).returns(false) provider.user.expects(:locked_out?).returns(false) provider.user.expects(:expired?).returns(false) provider.user.expects(:password=).with('plaintextbad') provider.password = "plaintextbad" end it "should test a valid user password" do resource[:password] = 'plaintext' provider.user.expects(:password_is?).with('plaintext').returns true expect(provider.password).to eq('plaintext') end it "should test a bad user password" do resource[:password] = 'plaintext' provider.user.expects(:password_is?).with('plaintext').returns false expect(provider.password).to be_nil end it "should test a blank user password" do resource[:password] = '' provider.user.expects(:password_is?).with('').returns true expect(provider.password).to eq('') end it 'should not create a user if a group by the same name exists' do Puppet::Util::Windows::ADSI::User.expects(:create).with('testuser').raises( Puppet::Error.new("Cannot create user if group 'testuser' exists.") ) expect{ provider.create }.to raise_error( Puppet::Error, /Cannot create user if group 'testuser' exists./ ) end it "should fail with an actionable message when trying to create an active directory user" do resource[:name] = 'DOMAIN\testdomainuser' Puppet::Util::Windows::ADSI::Group.expects(:exists?).with(resource[:name]).returns(false) connection.expects(:Create) connection.expects(:Get).with('UserFlags') connection.expects(:Put).with('UserFlags', true) connection.expects(:SetInfo).raises( WIN32OLERuntimeError.new("(in OLE method `SetInfo': )\n OLE error code:8007089A in Active Directory\n The specified username is invalid.\r\n\n HRESULT error code:0x80020009\n Exception occurred.")) expect{ provider.create }.to raise_error(Puppet::Error) end end it 'should be able to test whether a user exists' do Puppet::Util::Windows::SID.stubs(:name_to_principal).returns(nil) Puppet::Util::Windows::ADSI.stubs(:connect).returns stub('connection', :Class => 'User') expect(provider).to be_exists Puppet::Util::Windows::ADSI.stubs(:connect).returns nil expect(provider).not_to be_exists end it 'should be able to delete a user' do connection.expects(:Delete).with('user', 'testuser') provider.delete end it 'should not run commit on a deleted user' do connection.expects(:Delete).with('user', 'testuser') connection.expects(:SetInfo).never provider.delete provider.flush end it 'should delete the profile if managehome is set' do resource[:managehome] = true sid = 'S-A-B-C' Puppet::Util::Windows::SID.expects(:name_to_sid).with('testuser').returns(sid) Puppet::Util::Windows::ADSI::UserProfile.expects(:delete).with(sid) connection.expects(:Delete).with('user', 'testuser') provider.delete end it "should commit the user when flushed" do provider.user.expects(:commit) provider.flush end it "should return the user's SID as uid" do Puppet::Util::Windows::SID.expects(:name_to_sid).with('testuser').returns('S-1-5-21-1362942247-2130103807-3279964888-1111') expect(provider.uid).to eq('S-1-5-21-1362942247-2130103807-3279964888-1111') end it "should fail when trying to manage the uid property" do provider.expects(:fail).with { |msg| msg =~ /uid is read-only/ } provider.send(:uid=, 500) end [:gid, :shell].each do |prop| it "should fail when trying to manage the #{prop} property" do provider.expects(:fail).with { |msg| msg =~ /No support for managing property #{prop}/ } provider.send("#{prop}=", 'foo') end end end puppet-5.5.10/spec/unit/provider/augeas/0000755005276200011600000000000013417162177020030 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/provider/augeas/augeas_spec.rb0000644005276200011600000012412113417161722022630 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/util/package' provider_class = Puppet::Type.type(:augeas).provider(:augeas) describe provider_class do before(:each) do @resource = Puppet::Type.type(:augeas).new( :name => "test", :root => my_fixture_dir, :provider => :augeas ) @provider = provider_class.new(@resource) end after(:each) do @provider.close_augeas end describe "command parsing" do it "should break apart a single line into three tokens and clean up the context" do @resource[:context] = "/context" tokens = @provider.parse_commands("set Jar/Jar Binks") expect(tokens.size).to eq(1) expect(tokens[0].size).to eq(3) expect(tokens[0][0]).to eq("set") expect(tokens[0][1]).to eq("/context/Jar/Jar") expect(tokens[0][2]).to eq("Binks") end it "should break apart a multiple line into six tokens" do tokens = @provider.parse_commands("set /Jar/Jar Binks\nrm anakin") expect(tokens.size).to eq(2) expect(tokens[0].size).to eq(3) expect(tokens[1].size).to eq(2) expect(tokens[0][0]).to eq("set") expect(tokens[0][1]).to eq("/Jar/Jar") expect(tokens[0][2]).to eq("Binks") expect(tokens[1][0]).to eq("rm") expect(tokens[1][1]).to eq("anakin") end it "should strip whitespace and ignore blank lines" do tokens = @provider.parse_commands(" set /Jar/Jar Binks \t\n \n\n rm anakin ") expect(tokens.size).to eq(2) expect(tokens[0].size).to eq(3) expect(tokens[1].size).to eq(2) expect(tokens[0][0]).to eq("set") expect(tokens[0][1]).to eq("/Jar/Jar") expect(tokens[0][2]).to eq("Binks") expect(tokens[1][0]).to eq("rm") expect(tokens[1][1]).to eq("anakin") end it "should handle arrays" do @resource[:context] = "/foo/" commands = ["set /Jar/Jar Binks", "rm anakin"] tokens = @provider.parse_commands(commands) expect(tokens.size).to eq(2) expect(tokens[0].size).to eq(3) expect(tokens[1].size).to eq(2) expect(tokens[0][0]).to eq("set") expect(tokens[0][1]).to eq("/Jar/Jar") expect(tokens[0][2]).to eq("Binks") expect(tokens[1][0]).to eq("rm") expect(tokens[1][1]).to eq("/foo/anakin") end # This is not supported in the new parsing class #it "should concat the last values" do # provider = provider_class.new # tokens = provider.parse_commands("set /Jar/Jar Binks is my copilot") # tokens.size.should == 1 # tokens[0].size.should == 3 # tokens[0][0].should == "set" # tokens[0][1].should == "/Jar/Jar" # tokens[0][2].should == "Binks is my copilot" #end it "should accept spaces in the value and single ticks" do @resource[:context] = "/foo/" tokens = @provider.parse_commands("set JarJar 'Binks is my copilot'") expect(tokens.size).to eq(1) expect(tokens[0].size).to eq(3) expect(tokens[0][0]).to eq("set") expect(tokens[0][1]).to eq("/foo/JarJar") expect(tokens[0][2]).to eq("Binks is my copilot") end it "should accept spaces in the value and double ticks" do @resource[:context] = "/foo/" tokens = @provider.parse_commands('set /JarJar "Binks is my copilot"') expect(tokens.size).to eq(1) expect(tokens[0].size).to eq(3) expect(tokens[0][0]).to eq("set") expect(tokens[0][1]).to eq('/JarJar') expect(tokens[0][2]).to eq('Binks is my copilot') end it "should accept mixed ticks" do @resource[:context] = "/foo/" tokens = @provider.parse_commands('set JarJar "Some \'Test\'"') expect(tokens.size).to eq(1) expect(tokens[0].size).to eq(3) expect(tokens[0][0]).to eq("set") expect(tokens[0][1]).to eq('/foo/JarJar') expect(tokens[0][2]).to eq("Some \'Test\'") end it "should handle predicates with literals" do @resource[:context] = "/foo/" tokens = @provider.parse_commands("rm */*[module='pam_console.so']") expect(tokens).to eq([["rm", "/foo/*/*[module='pam_console.so']"]]) end it "should handle whitespace in predicates" do @resource[:context] = "/foo/" tokens = @provider.parse_commands("ins 42 before /files/etc/hosts/*/ipaddr[ . = '127.0.0.1' ]") expect(tokens).to eq([["ins", "42", "before","/files/etc/hosts/*/ipaddr[ . = '127.0.0.1' ]"]]) end it "should handle multiple predicates" do @resource[:context] = "/foo/" tokens = @provider.parse_commands("clear pam.d/*/*[module = 'system-auth'][type = 'account']") expect(tokens).to eq([["clear", "/foo/pam.d/*/*[module = 'system-auth'][type = 'account']"]]) end it "should handle nested predicates" do @resource[:context] = "/foo/" args = ["clear", "/foo/pam.d/*/*[module[ ../type = 'type] = 'system-auth'][type[last()] = 'account']"] tokens = @provider.parse_commands(args.join(" ")) expect(tokens).to eq([ args ]) end it "should handle escaped doublequotes in doublequoted string" do @resource[:context] = "/foo/" tokens = @provider.parse_commands("set /foo \"''\\\"''\"") expect(tokens).to eq([[ "set", "/foo", "''\"''" ]]) end it "should preserve escaped single quotes in double quoted strings" do @resource[:context] = "/foo/" tokens = @provider.parse_commands("set /foo \"\\'\"") expect(tokens).to eq([[ "set", "/foo", "\\'" ]]) end it "should allow escaped spaces and brackets in paths" do @resource[:context] = "/foo/" args = [ "set", "/white\\ space/\\[section", "value" ] tokens = @provider.parse_commands(args.join(" \t ")) expect(tokens).to eq([ args ]) end it "should allow single quoted escaped spaces in paths" do @resource[:context] = "/foo/" args = [ "set", "'/white\\ space/key'", "value" ] tokens = @provider.parse_commands(args.join(" \t ")) expect(tokens).to eq([[ "set", "/white\\ space/key", "value" ]]) end it "should allow double quoted escaped spaces in paths" do @resource[:context] = "/foo/" args = [ "set", '"/white\\ space/key"', "value" ] tokens = @provider.parse_commands(args.join(" \t ")) expect(tokens).to eq([[ "set", "/white\\ space/key", "value" ]]) end it "should remove trailing slashes" do @resource[:context] = "/foo/" tokens = @provider.parse_commands("set foo/ bar") expect(tokens).to eq([[ "set", "/foo/foo", "bar" ]]) end end describe "get filters" do before do augeas = stub("augeas", :get => "value") augeas.stubs("close") @provider.aug = augeas end it "should return false for a = nonmatch" do command = ["get", "fake value", "==", "value"] expect(@provider.process_get(command)).to eq(true) end it "should return true for a != match" do command = ["get", "fake value", "!=", "value"] expect(@provider.process_get(command)).to eq(false) end it "should return true for a =~ match" do command = ["get", "fake value", "=~", "val*"] expect(@provider.process_get(command)).to eq(true) end it "should return false for a == nonmatch" do command = ["get", "fake value", "=~", "num*"] expect(@provider.process_get(command)).to eq(false) end end describe "values filters" do before do augeas = stub("augeas", :match => ["set", "of", "values"]) augeas.stubs(:get).returns('set').then.returns('of').then.returns('values') augeas.stubs("close") @provider = provider_class.new(@resource) @provider.aug = augeas end it "should return true for includes match" do command = ["values", "fake value", "include values"] expect(@provider.process_values(command)).to eq(true) end it "should return false for includes non match" do command = ["values", "fake value", "include JarJar"] expect(@provider.process_values(command)).to eq(false) end it "should return true for includes match" do command = ["values", "fake value", "not_include JarJar"] expect(@provider.process_values(command)).to eq(true) end it "should return false for includes non match" do command = ["values", "fake value", "not_include values"] expect(@provider.process_values(command)).to eq(false) end it "should return true for an array match" do command = ["values", "fake value", "== ['set', 'of', 'values']"] expect(@provider.process_values(command)).to eq(true) end it "should return true for an array match with double quotes and spaces" do command = ["values", "fake value", "== [ \"set\" , \"of\" , \"values\" ] "] expect(@provider.process_values(command)).to eq(true) end it "should return true for an array match with internally escaped single quotes" do @provider.aug.stubs(:match).returns(["set", "o'values", "here"]) @provider.aug.stubs(:get).returns('set').then.returns("o'values").then.returns('here') command = ["values", "fake value", "== [ 'set', 'o\\'values', 'here']"] expect(@provider.process_values(command)).to eq(true) end it "should return true for an array match with octal character sequences" do command = ["values", "fake value", "== [\"\\x73et\", \"of\", \"values\"]"] expect(@provider.process_values(command)).to eq(true) end it "should return true for an array match with hex character sequences" do command = ["values", "fake value", "== [\"\\163et\", \"of\", \"values\"]"] expect(@provider.process_values(command)).to eq(true) end it "should return true for an array match with short unicode escape sequences" do command = ["values", "fake value", "== [\"\\u0073et\", \"of\", \"values\"]"] expect(@provider.process_values(command)).to eq(true) end it "should return true for an array match with single character long unicode escape sequences" do command = ["values", "fake value", "== [\"\\u{0073}et\", \"of\", \"values\"]"] expect(@provider.process_values(command)).to eq(true) end it "should return true for an array match with multi-character long unicode escape sequences" do command = ["values", "fake value", "== [\"\\u{0073 0065 0074}\", \"of\", \"values\"]"] expect(@provider.process_values(command)).to eq(true) end it "should return true for an array match with literal backslashes" do @provider.aug.stubs(:match).returns(["set", "o\\values", "here"]) @provider.aug.stubs(:get).returns('set').then.returns("o\\values").then.returns('here') command = ["values", "fake value", "== [ 'set', 'o\\\\values', 'here']"] expect(@provider.process_values(command)).to eq(true) end it "should return false for an array non match" do command = ["values", "fake value", "== ['this', 'should', 'not', 'match']"] expect(@provider.process_values(command)).to eq(false) end it "should return false for an array match with noteq" do command = ["values", "fake value", "!= ['set', 'of', 'values']"] expect(@provider.process_values(command)).to eq(false) end it "should return true for an array non match with noteq" do command = ["values", "fake value", "!= ['this', 'should', 'not', 'match']"] expect(@provider.process_values(command)).to eq(true) end it "should return true for an array non match with double quotes and spaces" do command = ["values", "fake value", "!= [ \"this\" , \"should\" ,\"not\", \"match\" ] "] expect(@provider.process_values(command)).to eq(true) end it "should return true for an empty array match" do @provider.aug.stubs(:match).returns([]) @provider.aug.stubs(:get) command = ["values", "fake value", "== []"] expect(@provider.process_values(command)).to eq(true) end end describe "match filters" do before do augeas = stub("augeas", :match => ["set", "of", "values"]) augeas.stubs("close") @provider = provider_class.new(@resource) @provider.aug = augeas end it "should return true for size match" do command = ["match", "fake value", "size == 3"] expect(@provider.process_match(command)).to eq(true) end it "should return false for a size non match" do command = ["match", "fake value", "size < 3"] expect(@provider.process_match(command)).to eq(false) end it "should return true for includes match" do command = ["match", "fake value", "include values"] expect(@provider.process_match(command)).to eq(true) end it "should return false for includes non match" do command = ["match", "fake value", "include JarJar"] expect(@provider.process_match(command)).to eq(false) end it "should return true for includes match" do command = ["match", "fake value", "not_include JarJar"] expect(@provider.process_match(command)).to eq(true) end it "should return false for includes non match" do command = ["match", "fake value", "not_include values"] expect(@provider.process_match(command)).to eq(false) end it "should return true for an array match" do command = ["match", "fake value", "== ['set', 'of', 'values']"] expect(@provider.process_match(command)).to eq(true) end it "should return true for an array match with double quotes and spaces" do command = ["match", "fake value", "== [ \"set\" , \"of\" , \"values\" ] "] expect(@provider.process_match(command)).to eq(true) end it "should return false for an array non match" do command = ["match", "fake value", "== ['this', 'should', 'not', 'match']"] expect(@provider.process_match(command)).to eq(false) end it "should return false for an array match with noteq" do command = ["match", "fake value", "!= ['set', 'of', 'values']"] expect(@provider.process_match(command)).to eq(false) end it "should return true for an array non match with noteq" do command = ["match", "fake value", "!= ['this', 'should', 'not', 'match']"] expect(@provider.process_match(command)).to eq(true) end it "should return true for an array non match with double quotes and spaces" do command = ["match", "fake value", "!= [ \"this\" , \"should\" ,\"not\", \"match\" ] "] expect(@provider.process_match(command)).to eq(true) end end describe "need to run" do before(:each) do @augeas = stub("augeas") @augeas.stubs("close") @provider.aug = @augeas # These tests pretend to be an earlier version so the provider doesn't # attempt to make the change in the need_to_run? method @provider.stubs(:get_augeas_version).returns("0.3.5") end it "should handle no filters" do @augeas.stubs("match").returns(["set", "of", "values"]) expect(@provider.need_to_run?).to eq(true) end it "should return true when a get filter matches" do @resource[:onlyif] = "get path == value" @augeas.stubs("get").returns("value") expect(@provider.need_to_run?).to eq(true) end describe "performing numeric comparisons (#22617)" do it "should return true when a get string compare is true" do @resource[:onlyif] = "get bpath > a" @augeas.stubs("get").returns("b") expect(@provider.need_to_run?).to eq(true) end it "should return false when a get string compare is false" do @resource[:onlyif] = "get a19path > a2" @augeas.stubs("get").returns("a19") expect(@provider.need_to_run?).to eq(false) end it "should return true when a get int gt compare is true" do @resource[:onlyif] = "get path19 > 2" @augeas.stubs("get").returns("19") expect(@provider.need_to_run?).to eq(true) end it "should return true when a get int ge compare is true" do @resource[:onlyif] = "get path19 >= 2" @augeas.stubs("get").returns("19") expect(@provider.need_to_run?).to eq(true) end it "should return true when a get int lt compare is true" do @resource[:onlyif] = "get path2 < 19" @augeas.stubs("get").returns("2") expect(@provider.need_to_run?).to eq(true) end it "should return false when a get int le compare is false" do @resource[:onlyif] = "get path39 <= 4" @augeas.stubs("get").returns("39") expect(@provider.need_to_run?).to eq(false) end end describe "performing is_numeric checks (#22617)" do it "should return false for nil" do expect(@provider.is_numeric?(nil)).to eq(false) end it "should return true for Integers" do expect(@provider.is_numeric?(9)).to eq(true) end it "should return true for numbers in Strings" do expect(@provider.is_numeric?('9')).to eq(true) end it "should return false for non-number Strings" do expect(@provider.is_numeric?('x9')).to eq(false) end it "should return false for other types" do expect(@provider.is_numeric?([true])).to eq(false) end end it "should return false when a get filter does not match" do @resource[:onlyif] = "get path == another value" @augeas.stubs("get").returns("value") expect(@provider.need_to_run?).to eq(false) end it "should return true when a match filter matches" do @resource[:onlyif] = "match path size == 3" @augeas.stubs("match").returns(["set", "of", "values"]) expect(@provider.need_to_run?).to eq(true) end it "should return false when a match filter does not match" do @resource[:onlyif] = "match path size == 2" @augeas.stubs("match").returns(["set", "of", "values"]) expect(@provider.need_to_run?).to eq(false) end # Now setting force to true it "setting force should not change the above logic" do @resource[:force] = true @resource[:onlyif] = "match path size == 2" @augeas.stubs("match").returns(["set", "of", "values"]) expect(@provider.need_to_run?).to eq(false) end #Ticket 5211 testing it "should return true when a size != the provided value" do @resource[:onlyif] = "match path size != 17" @augeas.stubs("match").returns(["set", "of", "values"]) expect(@provider.need_to_run?).to eq(true) end #Ticket 5211 testing it "should return false when a size does equal the provided value" do @resource[:onlyif] = "match path size != 3" @augeas.stubs("match").returns(["set", "of", "values"]) expect(@provider.need_to_run?).to eq(false) end [true, false].product([true, false]) do |cfg, param| describe "and Puppet[:show_diff] is #{cfg} and show_diff => #{param}" do let(:file) { "/some/random/file" } before(:each) do Puppet[:show_diff] = cfg @resource[:show_diff] = param @resource[:root] = "" @resource[:context] = "/files" @resource[:changes] = ["set #{file}/foo bar"] File.stubs(:delete) @provider.stubs(:get_augeas_version).returns("0.10.0") @provider.stubs("diff").with("#{file}", "#{file}.augnew").returns("diff") @augeas.stubs(:set).returns(true) @augeas.stubs(:save).returns(true) @augeas.stubs(:match).with("/augeas/events/saved").returns(["/augeas/events/saved"]) @augeas.stubs(:get).with("/augeas/events/saved").returns("/files#{file}") @augeas.stubs(:set).with("/augeas/save", "newfile") end if cfg && param it "should display a diff" do expect(@provider).to be_need_to_run expect(@logs[0].message).to eq("\ndiff") end else it "should not display a diff" do expect(@provider).to be_need_to_run expect(@logs).to be_empty end end end end # Ticket 2728 (diff files) describe "and configured to show diffs" do before(:each) do Puppet[:show_diff] = true @resource[:show_diff] = true @resource[:root] = "" @provider.stubs(:get_augeas_version).returns("0.10.0") @augeas.stubs(:set).returns(true) @augeas.stubs(:save).returns(true) end it "should display a diff when a single file is shown to have been changed" do file = "/etc/hosts" File.stubs(:delete) @resource[:loglevel] = "crit" @resource[:context] = "/files" @resource[:changes] = ["set #{file}/foo bar"] @augeas.stubs(:match).with("/augeas/events/saved").returns(["/augeas/events/saved"]) @augeas.stubs(:get).with("/augeas/events/saved").returns("/files#{file}") @augeas.expects(:set).with("/augeas/save", "newfile") @provider.expects("diff").with("#{file}", "#{file}.augnew").returns("diff") expect(@provider).to be_need_to_run expect(@logs[0].message).to eq("\ndiff") expect(@logs[0].level).to eq(:crit) end it "should display a diff for each file that is changed when changing many files" do file1 = "/etc/hosts" file2 = "/etc/resolv.conf" File.stubs(:delete) @resource[:context] = "/files" @resource[:changes] = ["set #{file1}/foo bar", "set #{file2}/baz biz"] @augeas.stubs(:match).with("/augeas/events/saved").returns(["/augeas/events/saved[1]", "/augeas/events/saved[2]"]) @augeas.stubs(:get).with("/augeas/events/saved[1]").returns("/files#{file1}") @augeas.stubs(:get).with("/augeas/events/saved[2]").returns("/files#{file2}") @augeas.expects(:set).with("/augeas/save", "newfile") @provider.expects(:diff).with("#{file1}", "#{file1}.augnew").returns("diff #{file1}") @provider.expects(:diff).with("#{file2}", "#{file2}.augnew").returns("diff #{file2}") expect(@provider).to be_need_to_run expect(@logs.collect(&:message)).to include("\ndiff #{file1}", "\ndiff #{file2}") expect(@logs.collect(&:level)).to eq([:notice, :notice]) end describe "and resource[:root] is set" do it "should call diff when a file is shown to have been changed" do root = "/tmp/foo" file = "/etc/hosts" File.stubs(:delete) @resource[:context] = "/files" @resource[:changes] = ["set #{file}/foo bar"] @resource[:root] = root @augeas.stubs(:match).with("/augeas/events/saved").returns(["/augeas/events/saved"]) @augeas.stubs(:get).with("/augeas/events/saved").returns("/files#{file}") @augeas.expects(:set).with("/augeas/save", "newfile") @provider.expects(:diff).with("#{root}#{file}", "#{root}#{file}.augnew").returns("diff") expect(@provider).to be_need_to_run expect(@logs[0].message).to eq("\ndiff") expect(@logs[0].level).to eq(:notice) end end it "should not call diff if no files change" do file = "/etc/hosts" @resource[:context] = "/files" @resource[:changes] = ["set #{file}/foo bar"] @augeas.stubs(:match).with("/augeas/events/saved").returns([]) @augeas.expects(:set).with("/augeas/save", "newfile") @augeas.expects(:get).with("/augeas/events/saved").never() @augeas.expects(:close) @provider.expects(:diff).never() expect(@provider).not_to be_need_to_run end it "should cleanup the .augnew file" do file = "/etc/hosts" @resource[:context] = "/files" @resource[:changes] = ["set #{file}/foo bar"] @augeas.stubs(:match).with("/augeas/events/saved").returns(["/augeas/events/saved"]) @augeas.stubs(:get).with("/augeas/events/saved").returns("/files#{file}") @augeas.expects(:set).with("/augeas/save", "newfile") @augeas.expects(:close) File.expects(:delete).with(file + ".augnew") @provider.expects(:diff).with("#{file}", "#{file}.augnew").returns("") expect(@provider).to be_need_to_run end # Workaround for Augeas bug #264 which reports filenames twice it "should handle duplicate /augeas/events/saved filenames" do file = "/etc/hosts" @resource[:context] = "/files" @resource[:changes] = ["set #{file}/foo bar"] @augeas.stubs(:match).with("/augeas/events/saved").returns(["/augeas/events/saved[1]", "/augeas/events/saved[2]"]) @augeas.stubs(:get).with("/augeas/events/saved[1]").returns("/files#{file}") @augeas.stubs(:get).with("/augeas/events/saved[2]").returns("/files#{file}") @augeas.expects(:set).with("/augeas/save", "newfile") @augeas.expects(:close) File.expects(:delete).with(file + ".augnew").once() @provider.expects(:diff).with("#{file}", "#{file}.augnew").returns("").once() expect(@provider).to be_need_to_run end it "should fail with an error if saving fails" do file = "/etc/hosts" @resource[:context] = "/files" @resource[:changes] = ["set #{file}/foo bar"] @augeas.stubs(:save).returns(false) @augeas.stubs(:match).with("/augeas/events/saved").returns([]) @augeas.expects(:close) @provider.expects(:diff).never() @provider.expects(:print_put_errors) expect { @provider.need_to_run? }.to raise_error(Puppet::Error) end end end describe "augeas execution integration" do before do @augeas = stub("augeas", :load) @augeas.stubs("close") @augeas.stubs(:match).with("/augeas/events/saved").returns([]) @provider.aug = @augeas @provider.stubs(:get_augeas_version).returns("0.3.5") end it "should handle set commands" do @resource[:changes] = "set JarJar Binks" @resource[:context] = "/some/path/" @augeas.expects(:set).with("/some/path/JarJar", "Binks").returns(true) @augeas.expects(:save).returns(true) @augeas.expects(:close) expect(@provider.execute_changes).to eq(:executed) end it "should handle rm commands" do @resource[:changes] = "rm /Jar/Jar" @augeas.expects(:rm).with("/Jar/Jar") @augeas.expects(:save).returns(true) @augeas.expects(:close) expect(@provider.execute_changes).to eq(:executed) end it "should handle remove commands" do @resource[:changes] = "remove /Jar/Jar" @augeas.expects(:rm).with("/Jar/Jar") @augeas.expects(:save).returns(true) @augeas.expects(:close) expect(@provider.execute_changes).to eq(:executed) end it "should handle clear commands" do @resource[:changes] = "clear Jar/Jar" @resource[:context] = "/foo/" @augeas.expects(:clear).with("/foo/Jar/Jar").returns(true) @augeas.expects(:save).returns(true) @augeas.expects(:close) expect(@provider.execute_changes).to eq(:executed) end describe "touch command" do it "should clear missing path" do @resource[:changes] = "touch Jar/Jar" @resource[:context] = "/foo/" @augeas.expects(:match).with("/foo/Jar/Jar").returns([]) @augeas.expects(:clear).with("/foo/Jar/Jar").returns(true) @augeas.expects(:save).returns(true) @augeas.expects(:close) expect(@provider.execute_changes).to eq(:executed) end it "should not change on existing path" do @resource[:changes] = "touch Jar/Jar" @resource[:context] = "/foo/" @augeas.expects(:match).with("/foo/Jar/Jar").returns(["/foo/Jar/Jar"]) @augeas.expects(:clear).never @augeas.expects(:save).returns(true) @augeas.expects(:close) expect(@provider.execute_changes).to eq(:executed) end end it "should handle ins commands with before" do @resource[:changes] = "ins Binks before Jar/Jar" @resource[:context] = "/foo" @augeas.expects(:insert).with("/foo/Jar/Jar", "Binks", true) @augeas.expects(:save).returns(true) @augeas.expects(:close) expect(@provider.execute_changes).to eq(:executed) end it "should handle ins commands with after" do @resource[:changes] = "ins Binks after /Jar/Jar" @resource[:context] = "/foo" @augeas.expects(:insert).with("/Jar/Jar", "Binks", false) @augeas.expects(:save).returns(true) @augeas.expects(:close) expect(@provider.execute_changes).to eq(:executed) end it "should handle ins with no context" do @resource[:changes] = "ins Binks after /Jar/Jar" @augeas.expects(:insert).with("/Jar/Jar", "Binks", false) @augeas.expects(:save).returns(true) @augeas.expects(:close) expect(@provider.execute_changes).to eq(:executed) end it "should handle multiple commands" do @resource[:changes] = ["ins Binks after /Jar/Jar", "clear Jar/Jar"] @resource[:context] = "/foo/" @augeas.expects(:insert).with("/Jar/Jar", "Binks", false) @augeas.expects(:clear).with("/foo/Jar/Jar").returns(true) @augeas.expects(:save).returns(true) @augeas.expects(:close) expect(@provider.execute_changes).to eq(:executed) end it "should handle defvar commands" do @resource[:changes] = "defvar myjar Jar/Jar" @resource[:context] = "/foo/" @augeas.expects(:defvar).with("myjar", "/foo/Jar/Jar").returns(true) @augeas.expects(:save).returns(true) @augeas.expects(:close) expect(@provider.execute_changes).to eq(:executed) end it "should pass through augeas variables without context" do @resource[:changes] = ["defvar myjar Jar/Jar","set $myjar/Binks 1"] @resource[:context] = "/foo/" @augeas.expects(:defvar).with("myjar", "/foo/Jar/Jar").returns(true) # this is the important bit, shouldn't be /foo/$myjar/Binks @augeas.expects(:set).with("$myjar/Binks", "1").returns(true) @augeas.expects(:save).returns(true) @augeas.expects(:close) expect(@provider.execute_changes).to eq(:executed) end it "should handle defnode commands" do @resource[:changes] = "defnode newjar Jar/Jar[last()+1] Binks" @resource[:context] = "/foo/" @augeas.expects(:defnode).with("newjar", "/foo/Jar/Jar[last()+1]", "Binks").returns(true) @augeas.expects(:save).returns(true) @augeas.expects(:close) expect(@provider.execute_changes).to eq(:executed) end it "should handle mv commands" do @resource[:changes] = "mv Jar/Jar Binks" @resource[:context] = "/foo/" @augeas.expects(:mv).with("/foo/Jar/Jar", "/foo/Binks").returns(true) @augeas.expects(:save).returns(true) @augeas.expects(:close) expect(@provider.execute_changes).to eq(:executed) end it "should handle rename commands" do @resource[:changes] = "rename Jar/Jar Binks" @resource[:context] = "/foo/" @augeas.expects(:rename).with("/foo/Jar/Jar", "Binks").returns(true) @augeas.expects(:save).returns(true) @augeas.expects(:close) expect(@provider.execute_changes).to eq(:executed) end it "should handle setm commands" do @resource[:changes] = ["set test[1]/Jar/Jar Foo","set test[2]/Jar/Jar Bar","setm test Jar/Jar Binks"] @resource[:context] = "/foo/" @augeas.expects(:respond_to?).with("setm").returns(true) @augeas.expects(:set).with("/foo/test[1]/Jar/Jar", "Foo").returns(true) @augeas.expects(:set).with("/foo/test[2]/Jar/Jar", "Bar").returns(true) @augeas.expects(:setm).with("/foo/test", "Jar/Jar", "Binks").returns(true) @augeas.expects(:save).returns(true) @augeas.expects(:close) expect(@provider.execute_changes).to eq(:executed) end it "should throw error if setm command not supported" do @resource[:changes] = ["set test[1]/Jar/Jar Foo","set test[2]/Jar/Jar Bar","setm test Jar/Jar Binks"] @resource[:context] = "/foo/" @augeas.expects(:respond_to?).with("setm").returns(false) @augeas.expects(:set).with("/foo/test[1]/Jar/Jar", "Foo").returns(true) @augeas.expects(:set).with("/foo/test[2]/Jar/Jar", "Bar").returns(true) expect { @provider.execute_changes }.to raise_error RuntimeError, /command 'setm' not supported/ end it "should handle clearm commands" do @resource[:changes] = ["set test[1]/Jar/Jar Foo","set test[2]/Jar/Jar Bar","clearm test Jar/Jar"] @resource[:context] = "/foo/" @augeas.expects(:respond_to?).with("clearm").returns(true) @augeas.expects(:set).with("/foo/test[1]/Jar/Jar", "Foo").returns(true) @augeas.expects(:set).with("/foo/test[2]/Jar/Jar", "Bar").returns(true) @augeas.expects(:clearm).with("/foo/test", "Jar/Jar").returns(true) @augeas.expects(:save).returns(true) @augeas.expects(:close) expect(@provider.execute_changes).to eq(:executed) end it "should throw error if clearm command not supported" do @resource[:changes] = ["set test[1]/Jar/Jar Foo","set test[2]/Jar/Jar Bar","clearm test Jar/Jar"] @resource[:context] = "/foo/" @augeas.expects(:respond_to?).with("clearm").returns(false) @augeas.expects(:set).with("/foo/test[1]/Jar/Jar", "Foo").returns(true) @augeas.expects(:set).with("/foo/test[2]/Jar/Jar", "Bar").returns(true) expect { @provider.execute_changes }.to raise_error(RuntimeError, /command 'clearm' not supported/) end it "should throw error if saving failed" do @resource[:changes] = ["set test[1]/Jar/Jar Foo","set test[2]/Jar/Jar Bar","clearm test Jar/Jar"] @resource[:context] = "/foo/" @augeas.expects(:respond_to?).with("clearm").returns(true) @augeas.expects(:set).with("/foo/test[1]/Jar/Jar", "Foo").returns(true) @augeas.expects(:set).with("/foo/test[2]/Jar/Jar", "Bar").returns(true) @augeas.expects(:clearm).with("/foo/test", "Jar/Jar").returns(true) @augeas.expects(:save).returns(false) @provider.expects(:print_put_errors) @augeas.expects(:match).returns([]) expect { @provider.execute_changes }.to raise_error(Puppet::Error) end end describe "when making changes", :if => Puppet.features.augeas? do include PuppetSpec::Files it "should not clobber the file if it's a symlink" do Puppet::Util::Storage.stubs(:store) link = tmpfile('link') target = tmpfile('target') FileUtils.touch(target) Puppet::FileSystem.symlink(target, link) resource = Puppet::Type.type(:augeas).new( :name => 'test', :incl => link, :lens => 'Sshd.lns', :changes => "set PermitRootLogin no" ) catalog = Puppet::Resource::Catalog.new catalog.add_resource resource catalog.apply expect(File.ftype(link)).to eq('link') expect(Puppet::FileSystem.readlink(link)).to eq(target) expect(File.read(target)).to match(/PermitRootLogin no/) end end describe "load/save failure reporting" do before do @augeas = stub("augeas") @augeas.stubs("close") @provider.aug = @augeas end describe "should find load errors" do before do @augeas.expects(:match).with("/augeas//error").returns(["/augeas/files/foo/error"]) @augeas.expects(:match).with("/augeas/files/foo/error/*").returns(["/augeas/files/foo/error/path", "/augeas/files/foo/error/message"]) @augeas.expects(:get).with("/augeas/files/foo/error").returns("some_failure") @augeas.expects(:get).with("/augeas/files/foo/error/path").returns("/foo") @augeas.expects(:get).with("/augeas/files/foo/error/message").returns("Failed to...") end it "and output only to debug when no path supplied" do @provider.expects(:debug).times(5) @provider.expects(:warning).never() @provider.print_load_errors(nil) end it "and output a warning and to debug when path supplied" do @augeas.expects(:match).with("/augeas/files/foo//error").returns(["/augeas/files/foo/error"]) @provider.expects(:warning).once() @provider.expects(:debug).times(4) @provider.print_load_errors('/augeas/files/foo//error') end it "and output only to debug when path doesn't match" do @augeas.expects(:match).with("/augeas/files/foo//error").returns([]) @provider.expects(:warning).never() @provider.expects(:debug).times(5) @provider.print_load_errors('/augeas/files/foo//error') end end it "should find load errors from lenses" do @augeas.expects(:match).with("/augeas//error").twice.returns(["/augeas/load/Xfm/error"]) @augeas.expects(:match).with("/augeas/load/Xfm/error/*").returns([]) @augeas.expects(:get).with("/augeas/load/Xfm/error").returns(["Could not find lens php.aug"]) @provider.expects(:warning).once() @provider.expects(:debug).twice() @provider.print_load_errors('/augeas//error') end it "should find save errors and output to debug" do @augeas.expects(:match).with("/augeas//error[. = 'put_failed']").returns(["/augeas/files/foo/error"]) @augeas.expects(:match).with("/augeas/files/foo/error/*").returns(["/augeas/files/foo/error/path", "/augeas/files/foo/error/message"]) @augeas.expects(:get).with("/augeas/files/foo/error").returns("some_failure") @augeas.expects(:get).with("/augeas/files/foo/error/path").returns("/foo") @augeas.expects(:get).with("/augeas/files/foo/error/message").returns("Failed to...") @provider.expects(:debug).times(5) @provider.print_put_errors end end # Run initialisation tests of the real Augeas library to test our open_augeas # method. This relies on Augeas and ruby-augeas on the host to be # functioning. describe "augeas lib initialisation", :if => Puppet.features.augeas? do # Expect lenses for fstab and hosts it "should have loaded standard files by default" do aug = @provider.open_augeas expect(aug).not_to eq(nil) expect(aug.match("/files/etc/fstab")).to eq(["/files/etc/fstab"]) expect(aug.match("/files/etc/hosts")).to eq(["/files/etc/hosts"]) expect(aug.match("/files/etc/test")).to eq([]) end it "should report load errors to debug only" do @provider.expects(:print_load_errors).with(nil) aug = @provider.open_augeas expect(aug).not_to eq(nil) end # Only the file specified should be loaded it "should load one file if incl/lens used" do @resource[:incl] = "/etc/hosts" @resource[:lens] = "Hosts.lns" @provider.expects(:print_load_errors).with('/augeas//error') aug = @provider.open_augeas expect(aug).not_to eq(nil) expect(aug.match("/files/etc/fstab")).to eq([]) expect(aug.match("/files/etc/hosts")).to eq(["/files/etc/hosts"]) expect(aug.match("/files/etc/test")).to eq([]) end it "should also load lenses from load_path" do @resource[:load_path] = my_fixture_dir aug = @provider.open_augeas expect(aug).not_to eq(nil) expect(aug.match("/files/etc/fstab")).to eq(["/files/etc/fstab"]) expect(aug.match("/files/etc/hosts")).to eq(["/files/etc/hosts"]) expect(aug.match("/files/etc/test")).to eq(["/files/etc/test"]) end it "should also load lenses from pluginsync'd path" do Puppet[:libdir] = my_fixture_dir aug = @provider.open_augeas expect(aug).not_to eq(nil) expect(aug.match("/files/etc/fstab")).to eq(["/files/etc/fstab"]) expect(aug.match("/files/etc/hosts")).to eq(["/files/etc/hosts"]) expect(aug.match("/files/etc/test")).to eq(["/files/etc/test"]) end # Optimisations added for Augeas 0.8.2 or higher is available, see #7285 describe ">= 0.8.2 optimisations", :if => Puppet.features.augeas? && Facter.value(:augeasversion) && Puppet::Util::Package.versioncmp(Facter.value(:augeasversion), "0.8.2") >= 0 do it "should only load one file if relevant context given" do @resource[:context] = "/files/etc/fstab" @provider.expects(:print_load_errors).with('/augeas/files/etc/fstab//error') aug = @provider.open_augeas expect(aug).not_to eq(nil) expect(aug.match("/files/etc/fstab")).to eq(["/files/etc/fstab"]) expect(aug.match("/files/etc/hosts")).to eq([]) end it "should only load one lens from load_path if context given" do @resource[:context] = "/files/etc/test" @resource[:load_path] = my_fixture_dir @provider.expects(:print_load_errors).with('/augeas/files/etc/test//error') aug = @provider.open_augeas expect(aug).not_to eq(nil) expect(aug.match("/files/etc/fstab")).to eq([]) expect(aug.match("/files/etc/hosts")).to eq([]) expect(aug.match("/files/etc/test")).to eq(["/files/etc/test"]) end it "should load standard files if context isn't specific" do @resource[:context] = "/files/etc" @provider.expects(:print_load_errors).with(nil) aug = @provider.open_augeas expect(aug).not_to eq(nil) expect(aug.match("/files/etc/fstab")).to eq(["/files/etc/fstab"]) expect(aug.match("/files/etc/hosts")).to eq(["/files/etc/hosts"]) end it "should not optimise if the context is a complex path" do @resource[:context] = "/files/*[label()='etc']" @provider.expects(:print_load_errors).with(nil) aug = @provider.open_augeas expect(aug).not_to eq(nil) expect(aug.match("/files/etc/fstab")).to eq(["/files/etc/fstab"]) expect(aug.match("/files/etc/hosts")).to eq(["/files/etc/hosts"]) end end end describe "get_load_path" do it "should offer no load_path by default" do expect(@provider.get_load_path(@resource)).to eq("") end it "should offer one path from load_path" do @resource[:load_path] = "/foo" expect(@provider.get_load_path(@resource)).to eq("/foo") end it "should offer multiple colon-separated paths from load_path" do @resource[:load_path] = "/foo:/bar:/baz" expect(@provider.get_load_path(@resource)).to eq("/foo:/bar:/baz") end it "should offer multiple paths in array from load_path" do @resource[:load_path] = ["/foo", "/bar", "/baz"] expect(@provider.get_load_path(@resource)).to eq("/foo:/bar:/baz") end it "should offer pluginsync augeas/lenses subdir" do Puppet[:libdir] = my_fixture_dir expect(@provider.get_load_path(@resource)).to eq("#{my_fixture_dir}/augeas/lenses") end it "should offer both pluginsync and load_path paths" do Puppet[:libdir] = my_fixture_dir @resource[:load_path] = ["/foo", "/bar", "/baz"] expect(@provider.get_load_path(@resource)).to eq("/foo:/bar:/baz:#{my_fixture_dir}/augeas/lenses") end end end puppet-5.5.10/spec/unit/provider/cisco_spec.rb0000644005276200011600000000065313417161722021221 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/provider/cisco' describe Puppet::Provider::Cisco do it "should implement a device class method" do expect(Puppet::Provider::Cisco).to respond_to(:device) end it "should create a cisco device instance" do Puppet::Util::NetworkDevice::Cisco::Device.expects(:new).returns :device expect(Puppet::Provider::Cisco.device(:url)).to eq(:device) end end puppet-5.5.10/spec/unit/provider/cron/0000755005276200011600000000000013417162177017524 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/provider/cron/crontab_spec.rb0000644005276200011600000001613413417161722022513 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:cron).provider(:crontab) do subject do provider = Puppet::Type.type(:cron).provider(:crontab) provider.initvars provider end def compare_crontab_text(have, want) # We should have four header lines, and then the text... expect(have.lines.to_a[0..3]).to be_all {|x| x =~ /^# / } expect(have.lines.to_a[4..-1].join('')).to eq(want) end context "with the simple samples" do FIELDS = { :crontab => %w{command minute hour month monthday weekday}.collect { |o| o.intern }, :environment => [:line], :blank => [:line], :comment => [:line], } def compare_crontab_record(have, want) want.each do |param, value| expect(have).to be_key param expect(have[param]).to eq(value) end (FIELDS[have[:record_type]] - want.keys).each do |name| expect(have[name]).to eq(:absent) end end ######################################################################## # Simple input fixtures for testing. samples = YAML.load(File.read(my_fixture('single_line.yaml'))) samples.each do |name, data| it "should parse crontab line #{name} correctly" do compare_crontab_record subject.parse_line(data[:text]), data[:record] end it "should reconstruct the crontab line #{name} from the record" do expect(subject.to_line(data[:record])).to eq(data[:text]) end end records = [] text = "" # Sorting is from the original, and avoids :empty being the last line, # since the provider will ignore that and cause this to fail. samples.sort_by {|x| x.first.to_s }.each do |name, data| records << data[:record] text << data[:text] + "\n" end it "should parse all sample records at once" do subject.parse(text).zip(records).each do |round| compare_crontab_record(*round) end end it "should reconstitute the file from the records" do compare_crontab_text subject.to_file(records), text end context "multi-line crontabs" do tests = { :simple => [:spaces_in_command_with_times], :with_name => [:name, :spaces_in_command_with_times], :with_env => [:environment, :spaces_in_command_with_times], :with_multiple_envs => [:environment, :lowercase_environment, :spaces_in_command_with_times], :with_name_and_env => [:name_with_spaces, :another_env, :spaces_in_command_with_times], :with_name_and_multiple_envs => [:long_name, :another_env, :fourth_env, :spaces_in_command_with_times] } all_records = [] all_text = '' tests.each do |name, content| data = content.map {|x| samples[x] or raise "missing sample data #{x}" } text = data.map {|x| x[:text] }.join("\n") + "\n" records = data.map {|x| x[:record] } # Capture the whole thing for later, too... all_records += records all_text += text context name.to_s.gsub('_', ' ') do it "should regenerate the text from the record" do compare_crontab_text subject.to_file(records), text end it "should parse the records from the text" do subject.parse(text).zip(records).each do |round| compare_crontab_record(*round) end end end end it "should parse the whole set of records from the text" do subject.parse(all_text).zip(all_records).each do |round| compare_crontab_record(*round) end end it "should regenerate the whole text from the set of all records" do compare_crontab_text subject.to_file(all_records), all_text end end end context "when receiving a vixie cron header from the cron interface" do it "should not write that header back to disk" do vixie_header = File.read(my_fixture('vixie_header.txt')) vixie_records = subject.parse(vixie_header) compare_crontab_text subject.to_file(vixie_records), "" end end context "when adding a cronjob with the same command as an existing job" do let(:record) { {:name => "existing", :user => "root", :command => "/bin/true", :record_type => :crontab} } let(:resource) { Puppet::Type::Cron.new(:name => "test", :user => "root", :command => "/bin/true") } let(:resources) { { "test" => resource } } before :each do subject.stubs(:prefetch_all_targets).returns([record]) end # this would be a more fitting test, but I haven't yet # figured out how to get it working # it "should include both jobs in the output" do # subject.prefetch(resources) # class Puppet::Provider::ParsedFile # def self.records # @records # end # end # subject.to_file(subject.records).should match /Puppet name: test/ # end it "should not base the new resource's provider on the existing record" do subject.expects(:new).with(record).never subject.stubs(:new) subject.prefetch(resources) end end context "when prefetching an entry now managed for another user" do let(:resource) do s = stub(:resource) s.stubs(:[]).with(:user).returns 'root' s.stubs(:[]).with(:target).returns 'root' s end let(:record) { {:name => "test", :user => "nobody", :command => "/bin/true", :record_type => :crontab} } let(:resources) { { "test" => resource } } before :each do subject.stubs(:prefetch_all_targets).returns([record]) end it "should try and use the match method to find a more fitting record" do subject.expects(:match).with(record, resources) subject.prefetch(resources) end it "should not match a provider to the resource" do resource.expects(:provider=).never subject.prefetch(resources) end it "should not find the resource when looking up the on-disk record" do subject.prefetch(resources) expect(subject.resource_for_record(record, resources)).to be_nil end end context "when matching resources to existing crontab entries" do let(:first_resource) { Puppet::Type::Cron.new(:name => :one, :user => 'root', :command => '/bin/true') } let(:second_resource) { Puppet::Type::Cron.new(:name => :two, :user => 'nobody', :command => '/bin/false') } let(:resources) {{:one => first_resource, :two => second_resource}} describe "with a record with a matching name and mismatching user (#2251)" do # Puppet::Resource objects have #should defined on them, so in these # examples we have to use the monkey patched `must` alias for the rspec # `should` method. it "doesn't match the record to the resource" do record = {:name => :one, :user => 'notroot', :record_type => :crontab} expect(subject.resource_for_record(record, resources)).to be_nil end end describe "with a record with a matching name and matching user" do it "matches the record to the resource" do record = {:name => :two, :target => 'nobody', :command => '/bin/false'} expect(subject.resource_for_record(record, resources)).to eq(second_resource) end end end end puppet-5.5.10/spec/unit/provider/cron/parsed_spec.rb0000644005276200011600000003055313417161722022342 0ustar jenkinsjenkins#!/usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:cron).provider(:crontab) do let :provider do described_class.new(:command => '/bin/true') end let :resource do Puppet::Type.type(:cron).new( :minute => %w{0 15 30 45}, :hour => %w{8-18 20-22}, :monthday => %w{31}, :month => %w{12}, :weekday => %w{7}, :name => 'basic', :command => '/bin/true', :target => 'root', :provider => provider ) end let :resource_special do Puppet::Type.type(:cron).new( :special => 'reboot', :name => 'special', :command => '/bin/true', :target => 'nobody' ) end let :resource_sparse do Puppet::Type.type(:cron).new( :minute => %w{42}, :target => 'root', :name => 'sparse' ) end let :record_special do { :record_type => :crontab, :special => 'reboot', :command => '/bin/true', :on_disk => true, :target => 'nobody' } end let :record do { :record_type => :crontab, :minute => %w{0 15 30 45}, :hour => %w{8-18 20-22}, :monthday => %w{31}, :month => %w{12}, :weekday => %w{7}, :special => :absent, :command => '/bin/true', :on_disk => true, :target => 'root' } end describe "when determining the correct filetype" do it "should use the suntab filetype on Solaris" do Facter.stubs(:value).with(:osfamily).returns 'Solaris' expect(described_class.filetype).to eq(Puppet::Util::FileType::FileTypeSuntab) end it "should use the aixtab filetype on AIX" do Facter.stubs(:value).with(:osfamily).returns 'AIX' expect(described_class.filetype).to eq(Puppet::Util::FileType::FileTypeAixtab) end it "should use the crontab filetype on other platforms" do Facter.stubs(:value).with(:osfamily).returns 'Not a real operating system family' expect(described_class.filetype).to eq(Puppet::Util::FileType::FileTypeCrontab) end end # I'd use ENV.expects(:[]).with('USER') but this does not work because # ENV["USER"] is evaluated at load time. describe "when determining the default target" do it "should use the current user #{ENV['USER']}", :if => ENV['USER'] do expect(described_class.default_target).to eq(ENV['USER']) end it "should fallback to root", :unless => ENV['USER'] do expect(described_class.default_target).to eq("root") end end describe ".targets" do let(:tabs) { [ described_class.default_target ] + %w{foo bar} } before do File.expects(:readable?).returns true File.stubs(:file?).returns true File.stubs(:writable?).returns true end after do File.unstub :readable?, :file?, :writable? Dir.unstub :foreach end it "should add all crontabs as targets" do Dir.expects(:foreach).multiple_yields(*tabs) expect(described_class.targets).to eq(tabs) end end describe "when parsing a record" do it "should parse a comment" do expect(described_class.parse_line("# This is a test")).to eq({ :record_type => :comment, :line => "# This is a test", }) end it "should get the resource name of a PUPPET NAME comment" do expect(described_class.parse_line('# Puppet Name: My Fancy Cronjob')).to eq({ :record_type => :comment, :name => 'My Fancy Cronjob', :line => '# Puppet Name: My Fancy Cronjob', }) end it "should ignore blank lines" do expect(described_class.parse_line('')).to eq({:record_type => :blank, :line => ''}) expect(described_class.parse_line(' ')).to eq({:record_type => :blank, :line => ' '}) expect(described_class.parse_line("\t")).to eq({:record_type => :blank, :line => "\t"}) expect(described_class.parse_line(" \t ")).to eq({:record_type => :blank, :line => " \t "}) end it "should extract environment assignments" do # man 5 crontab: MAILTO="" with no value can be used to surpress sending # mails at all expect(described_class.parse_line('MAILTO=""')).to eq({:record_type => :environment, :line => 'MAILTO=""'}) expect(described_class.parse_line('FOO=BAR')).to eq({:record_type => :environment, :line => 'FOO=BAR'}) expect(described_class.parse_line('FOO_BAR=BAR')).to eq({:record_type => :environment, :line => 'FOO_BAR=BAR'}) expect(described_class.parse_line('SPACE = BAR')).to eq({:record_type => :environment, :line => 'SPACE = BAR'}) end it "should extract a cron entry" do expect(described_class.parse_line('* * * * * /bin/true')).to eq({ :record_type => :crontab, :hour => :absent, :minute => :absent, :month => :absent, :weekday => :absent, :monthday => :absent, :special => :absent, :command => '/bin/true' }) expect(described_class.parse_line('0,15,30,45 8-18,20-22 31 12 7 /bin/true')).to eq({ :record_type => :crontab, :minute => %w{0 15 30 45}, :hour => %w{8-18 20-22}, :monthday => %w{31}, :month => %w{12}, :weekday => %w{7}, :special => :absent, :command => '/bin/true' }) # A percent sign will cause the rest of the string to be passed as # standard input and will also act as a newline character. Not sure # if puppet should convert % to a \n as the command property so the # test covers the current behaviour: Do not do any conversions expect(described_class.parse_line('0 22 * * 1-5 mail -s "It\'s 10pm" joe%Joe,%%Where are your kids?%')).to eq({ :record_type => :crontab, :minute => %w{0}, :hour => %w{22}, :monthday => :absent, :month => :absent, :weekday => %w{1-5}, :special => :absent, :command => 'mail -s "It\'s 10pm" joe%Joe,%%Where are your kids?%' }) end describe "it should support special strings" do ['reboot','yearly','anually','monthly', 'weekly', 'daily', 'midnight', 'hourly'].each do |special| it "should support @#{special}" do expect(described_class.parse_line("@#{special} /bin/true")).to eq({ :record_type => :crontab, :hour => :absent, :minute => :absent, :month => :absent, :weekday => :absent, :monthday => :absent, :special => special, :command => '/bin/true' }) end end end end describe ".instances" do before :each do described_class.stubs(:default_target).returns 'foobar' end describe "on linux" do before do Facter.stubs(:value).with(:osfamily).returns 'Linux' Facter.stubs(:value).with(:operatingsystem) end it "should contain no resources for a user who has no crontab" do Puppet::Util.stubs(:uid).returns(10) Puppet::Util::Execution.expects(:execute) .with('crontab -u foobar -l', { failonfail: true, combine: true }) .returns("") expect(described_class.instances.select { |resource| resource.get('target') == 'foobar' }).to be_empty end it "should contain no resources for a user who is absent" do Puppet::Util.stubs(:uid).returns(nil) expect(described_class.instances.select { |resource| resource.get('target') == 'foobar' }).to be_empty end it "should be able to create records from not-managed records" do described_class.stubs(:target_object).returns File.new(my_fixture('simple')) parameters = described_class.instances.map do |p| h = {:name => p.get(:name)} Puppet::Type.type(:cron).validproperties.each do |property| h[property] = p.get(property) end h end expect(parameters[0][:name]).to match(%r{unmanaged:\$HOME/bin/daily.job_>>_\$HOME/tmp/out_2>&1-\d+}) expect(parameters[0][:minute]).to eq(['5']) expect(parameters[0][:hour]).to eq(['0']) expect(parameters[0][:weekday]).to eq(:absent) expect(parameters[0][:month]).to eq(:absent) expect(parameters[0][:monthday]).to eq(:absent) expect(parameters[0][:special]).to eq(:absent) expect(parameters[0][:command]).to match(%r{\$HOME/bin/daily.job >> \$HOME/tmp/out 2>&1}) expect(parameters[0][:ensure]).to eq(:present) expect(parameters[0][:environment]).to eq(:absent) expect(parameters[0][:user]).to eq(:absent) expect(parameters[1][:name]).to match(%r{unmanaged:\$HOME/bin/monthly-\d+}) expect(parameters[1][:minute]).to eq(['15']) expect(parameters[1][:hour]).to eq(['14']) expect(parameters[1][:weekday]).to eq(:absent) expect(parameters[1][:month]).to eq(:absent) expect(parameters[1][:monthday]).to eq(['1']) expect(parameters[1][:special]).to eq(:absent) expect(parameters[1][:command]).to match(%r{\$HOME/bin/monthly}) expect(parameters[1][:ensure]).to eq(:present) expect(parameters[1][:environment]).to eq(:absent) expect(parameters[1][:user]).to eq(:absent) expect(parameters[1][:target]).to eq('foobar') end it "should be able to parse puppet managed cronjobs" do described_class.stubs(:target_object).returns File.new(my_fixture('managed')) expect(described_class.instances.map do |p| h = {:name => p.get(:name)} Puppet::Type.type(:cron).validproperties.each do |property| h[property] = p.get(property) end h end).to eq([ { :name => 'real_job', :minute => :absent, :hour => :absent, :weekday => :absent, :month => :absent, :monthday => :absent, :special => :absent, :command => '/bin/true', :ensure => :present, :environment => :absent, :user => :absent, :target => 'foobar' }, { :name => 'complex_job', :minute => :absent, :hour => :absent, :weekday => :absent, :month => :absent, :monthday => :absent, :special => 'reboot', :command => '/bin/true >> /dev/null 2>&1', :ensure => :present, :environment => [ 'MAILTO=foo@example.com', 'SHELL=/bin/sh' ], :user => :absent, :target => 'foobar' } ]) end end end describe ".match" do describe "normal records" do it "should match when all fields are the same" do expect(described_class.match(record,{resource[:name] => resource})).to eq(resource) end { :minute => %w{0 15 31 45}, :hour => %w{8-18}, :monthday => %w{30 31}, :month => %w{12 23}, :weekday => %w{4}, :command => '/bin/false', :target => 'nobody' }.each_pair do |field, new_value| it "should not match a record when #{field} does not match" do record[field] = new_value expect(described_class.match(record,{resource[:name] => resource})).to be_falsey end end end describe "special records" do it "should match when all fields are the same" do expect(described_class.match(record_special,{resource_special[:name] => resource_special})).to eq(resource_special) end { :special => 'monthly', :command => '/bin/false', :target => 'root' }.each_pair do |field, new_value| it "should not match a record when #{field} does not match" do record_special[field] = new_value expect(described_class.match(record_special,{resource_special[:name] => resource_special})).to be_falsey end end end describe "with a resource without a command" do it "should not raise an error" do expect { described_class.match(record,{resource_sparse[:name] => resource_sparse}) }.to_not raise_error end end end end puppet-5.5.10/spec/unit/provider/host/0000755005276200011600000000000013417162177017540 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/provider/host/parsed_spec.rb0000644005276200011600000001660013417161722022353 0ustar jenkinsjenkinsrequire 'spec_helper' require 'shared_behaviours/all_parsedfile_providers' require 'puppet_spec/files' describe Puppet::Type.type(:host).provider(:parsed) do include PuppetSpec::Files before do @host_class = Puppet::Type.type(:host) @provider = @host_class.provider(:parsed) @hostfile = tmpfile('hosts') @provider.any_instance.stubs(:target).returns @hostfile end after :each do @provider.initvars end def mkhost(args) hostresource = Puppet::Type::Host.new(:name => args[:name]) hostresource.stubs(:should).with(:target).returns @hostfile # Using setters of provider to build our testobject # Note: We already proved, that in case of host_aliases # the provider setter "host_aliases=(value)" will be # called with the joined array, so we just simulate that host = @provider.new(hostresource) args.each do |property,value| value = value.join(" ") if property == :host_aliases and value.is_a?(Array) host.send("#{property}=", value) end host end def genhost(host) @provider.stubs(:filetype).returns(Puppet::Util::FileType::FileTypeRam) File.stubs(:chown) File.stubs(:chmod) Puppet::Util::SUIDManager.stubs(:asuser).yields host.flush @provider.target_object(@hostfile).read end describe "when parsing on incomplete line" do it "should work for only ip" do expect(@provider.parse_line("127.0.0.1")[:line]).to eq("127.0.0.1") end it "should work for only hostname" do expect(@provider.parse_line("www.example.com")[:line]).to eq("www.example.com") end it "should work for ip and space" do expect(@provider.parse_line("127.0.0.1 ")[:line]).to eq("127.0.0.1 ") end it "should work for hostname and space" do expect(@provider.parse_line("www.example.com ")[:line]).to eq("www.example.com ") end it "should work for hostname and host_aliases" do expect(@provider.parse_line("www.example.com www xyz")[:line]).to eq("www.example.com www xyz") end it "should work for ip and comment" do expect(@provider.parse_line("127.0.0.1 #www xyz")[:line]).to eq("127.0.0.1 #www xyz") end it "should work for hostname and comment" do expect(@provider.parse_line("xyz #www test123")[:line]).to eq("xyz #www test123") end it "should work for crazy incomplete lines" do expect(@provider.parse_line("%th1s is a\t cr$zy !incompl1t line")[:line]).to eq("%th1s is a\t cr$zy !incompl1t line") end end describe "when parsing a line with ip and hostname" do it "should parse an ipv4 from the first field" do expect(@provider.parse_line("127.0.0.1 localhost")[:ip]).to eq("127.0.0.1") end it "should parse an ipv6 from the first field" do expect(@provider.parse_line("::1 localhost")[:ip]).to eq("::1") end it "should parse the name from the second field" do expect(@provider.parse_line("::1 localhost")[:name]).to eq("localhost") end it "should set an empty comment" do expect(@provider.parse_line("::1 localhost")[:comment]).to eq("") end it "should set host_aliases to :absent" do expect(@provider.parse_line("::1 localhost")[:host_aliases]).to eq(:absent) end end describe "when parsing a line with ip, hostname and comment" do before do @testline = "127.0.0.1 localhost # A comment with a #-char" end it "should parse the ip from the first field" do expect(@provider.parse_line(@testline)[:ip]).to eq("127.0.0.1") end it "should parse the hostname from the second field" do expect(@provider.parse_line(@testline)[:name]).to eq("localhost") end it "should parse the comment after the first '#' character" do expect(@provider.parse_line(@testline)[:comment]).to eq('A comment with a #-char') end end describe "when parsing a line with ip, hostname and aliases" do it "should parse alias from the third field" do expect(@provider.parse_line("127.0.0.1 localhost localhost.localdomain")[:host_aliases]).to eq("localhost.localdomain") end it "should parse multiple aliases" do expect(@provider.parse_line("127.0.0.1 host alias1 alias2")[:host_aliases]).to eq('alias1 alias2') expect(@provider.parse_line("127.0.0.1 host alias1\talias2")[:host_aliases]).to eq('alias1 alias2') expect(@provider.parse_line("127.0.0.1 host alias1\talias2 alias3")[:host_aliases]).to eq('alias1 alias2 alias3') end end describe "when parsing a line with ip, hostname, aliases and comment" do before do # Just playing with a few different delimiters @testline = "127.0.0.1\t host alias1\talias2 alias3 # A comment with a #-char" end it "should parse the ip from the first field" do expect(@provider.parse_line(@testline)[:ip]).to eq("127.0.0.1") end it "should parse the hostname from the second field" do expect(@provider.parse_line(@testline)[:name]).to eq("host") end it "should parse all host_aliases from the third field" do expect(@provider.parse_line(@testline)[:host_aliases]).to eq('alias1 alias2 alias3') end it "should parse the comment after the first '#' character" do expect(@provider.parse_line(@testline)[:comment]).to eq('A comment with a #-char') end end describe "when operating on /etc/hosts like files" do it_should_behave_like "all parsedfile providers", described_class, my_fixtures('valid*') it "should be able to generate a simple hostfile entry" do host = mkhost( :name => 'localhost', :ip => '127.0.0.1', :ensure => :present ) expect(genhost(host)).to eq("127.0.0.1\tlocalhost\n") end it "should be able to generate an entry with one alias" do host = mkhost( :name => 'localhost.localdomain', :ip => '127.0.0.1', :host_aliases => 'localhost', :ensure => :present ) expect(genhost(host)).to eq("127.0.0.1\tlocalhost.localdomain\tlocalhost\n") end it "should be able to generate an entry with more than one alias" do host = mkhost( :name => 'host', :ip => '192.0.0.1', :host_aliases => [ 'a1','a2','a3','a4' ], :ensure => :present ) expect(genhost(host)).to eq("192.0.0.1\thost\ta1 a2 a3 a4\n") end it "should be able to generate a simple hostfile entry with comments" do host = mkhost( :name => 'localhost', :ip => '127.0.0.1', :comment => 'Bazinga!', :ensure => :present ) expect(genhost(host)).to eq("127.0.0.1\tlocalhost\t# Bazinga!\n") end it "should be able to generate an entry with one alias and a comment" do host = mkhost( :name => 'localhost.localdomain', :ip => '127.0.0.1', :host_aliases => 'localhost', :comment => 'Bazinga!', :ensure => :present ) expect(genhost(host)).to eq("127.0.0.1\tlocalhost.localdomain\tlocalhost\t# Bazinga!\n") end it "should be able to generate an entry with more than one alias and a comment" do host = mkhost( :name => 'host', :ip => '192.0.0.1', :host_aliases => [ 'a1','a2','a3','a4' ], :comment => 'Bazinga!', :ensure => :present ) expect(genhost(host)).to eq("192.0.0.1\thost\ta1 a2 a3 a4\t# Bazinga!\n") end end end puppet-5.5.10/spec/unit/provider/interface/0000755005276200011600000000000013417162177020523 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/provider/interface/cisco_spec.rb0000644005276200011600000000371013417161722023156 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/provider/interface/cisco' provider_class = Puppet::Type.type(:interface).provider(:cisco) describe provider_class do before do @device = stub_everything 'device' @resource = stub("resource", :name => "Fa0/1") @provider = provider_class.new(@device, @resource) end it "should have a parent of Puppet::Provider::Cisco" do expect(provider_class).to be < Puppet::Provider::Cisco end it "should have an instances method" do expect(provider_class).to respond_to(:instances) end describe "when looking up instances at prefetch" do before do @device.stubs(:command).yields(@device) end it "should delegate to the device interface fetcher" do @device.expects(:interface) provider_class.lookup(@device, "Fa0/1") end it "should return the given interface data" do @device.expects(:interface).returns({ :description => "thisone", :mode => :access}) expect(provider_class.lookup(@device, "Fa0")).to eq({:description => "thisone", :mode => :access }) end end describe "when an instance is being flushed" do it "should call the device interface update method with current and past properties" do @instance = provider_class.new(@device, :ensure => :present, :name => "Fa0/1", :description => "myinterface") @instance.description = "newdesc" @instance.resource = @resource @resource.stubs(:[]).with(:name).returns("Fa0/1") device = stub_everything 'device' @instance.stubs(:device).returns(device) device.expects(:command).yields(device) interface = stub 'interface' device.expects(:new_interface).with("Fa0/1").returns(interface) interface.expects(:update).with( {:ensure => :present, :name => "Fa0/1", :description => "myinterface"}, {:ensure => :present, :name => "Fa0/1", :description => "newdesc"}) @instance.flush end end end puppet-5.5.10/spec/unit/provider/macauthorization_spec.rb0000644005276200011600000000761013417161722023502 0ustar jenkinsjenkins#! /usr/bin/env ruby # # Unit testing for the macauthorization provider # require 'spec_helper' require 'puppet' module Puppet::Util::Plist end provider_class = Puppet::Type.type(:macauthorization).provider(:macauthorization) describe provider_class do before :each do # Create a mock resource @resource = stub 'resource' @authname = "foo.spam.eggs.puppettest" @authplist = {} @rules = {@authname => @authplist} authdb = {} authdb["rules"] = { "foorule" => "foo" } authdb["rights"] = { "fooright" => "foo" } # Stub out Plist::parse_xml Puppet::Util::Plist.stubs(:parse_plist).returns(authdb) Puppet::Util::Plist.stubs(:write_plist_file) # A catch all; no parameters set @resource.stubs(:[]).returns(nil) # But set name, ensure @resource.stubs(:[]).with(:name).returns @authname @resource.stubs(:[]).with(:ensure).returns :present @resource.stubs(:ref).returns "MacAuthorization[#{@authname}]" @provider = provider_class.new(@resource) end it "should have a create method" do expect(@provider).to respond_to(:create) end it "should have a destroy method" do expect(@provider).to respond_to(:destroy) end it "should have an exists? method" do expect(@provider).to respond_to(:exists?) end it "should have a flush method" do expect(@provider).to respond_to(:flush) end properties = [ :allow_root, :authenticate_user, :auth_class, :comment, :group, :k_of_n, :mechanisms, :rule, :session_owner, :shared, :timeout, :tries, :auth_type ] properties.each do |prop| it "should have a #{prop.to_s} method" do expect(@provider).to respond_to(prop.to_s) end it "should have a #{prop.to_s}= method" do expect(@provider).to respond_to(prop.to_s + "=") end end describe "when destroying a right" do before :each do @resource.stubs(:[]).with(:auth_type).returns(:right) end it "should call the internal method destroy_right" do @provider.expects(:destroy_right) @provider.destroy end it "should call the external command 'security authorizationdb remove @authname" do @provider.expects(:security).with("authorizationdb", :remove, @authname) @provider.destroy end end describe "when destroying a rule" do before :each do @resource.stubs(:[]).with(:auth_type).returns(:rule) end it "should call the internal method destroy_rule" do @provider.expects(:destroy_rule) @provider.destroy end end describe "when flushing a right" do before :each do @resource.stubs(:[]).with(:auth_type).returns(:right) end it "should call the internal method flush_right" do @provider.expects(:flush_right) @provider.flush end it "should call the internal method set_right" do @provider.expects(:execute).with { |cmds, args| cmds.include?("read") and cmds.include?(@authname) and args[:combine] == false }.once @provider.expects(:set_right) @provider.flush end it "should read and write to the auth database with the right arguments" do @provider.expects(:execute).with { |cmds, args| cmds.include?("read") and cmds.include?(@authname) and args[:combine] == false }.once @provider.expects(:execute).with { |cmds, args| cmds.include?("write") and cmds.include?(@authname) and args[:combine] == false and args[:stdinfile] != nil }.once @provider.flush end end describe "when flushing a rule" do before :each do @resource.stubs(:[]).with(:auth_type).returns(:rule) end it "should call the internal method flush_rule" do @provider.expects(:flush_rule) @provider.flush end it "should call the internal method set_rule" do @provider.expects(:set_rule) @provider.flush end end end puppet-5.5.10/spec/unit/provider/mcx/0000755005276200011600000000000013417162177017352 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/provider/mcx/mcxcontent_spec.rb0000644005276200011600000001540513417161722023073 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' provider_class = Puppet::Type.type(:mcx).provider(:mcxcontent) # describe creates a new ExampleGroup object. describe provider_class do # :each executes before each test. # :all executes once for the test group and before :each. before :each do # Create a mock resource @resource = stub 'resource' @provider = provider_class.new @attached_to = "/Users/foobar" @ds_path = "/Local/Default/Users/foobar" # A catch all; no parameters set @resource.stubs(:[]).returns(nil) # But set name, ensure and enable @resource.stubs(:[]).with(:name).returns @attached_to @resource.stubs(:[]).with(:ensure).returns :present @resource.stubs(:ref).returns "Mcx[#{@attached_to}]" # stub out the provider methods that actually touch the filesystem # or execute commands @provider.class.stubs(:execute).returns('') @provider.stubs(:execute).returns('') @provider.stubs(:resource).returns @resource end it "should have a create method." do expect(@provider).to respond_to(:create) end it "should have a destroy method." do expect(@provider).to respond_to(:destroy) end it "should have an exists? method." do expect(@provider).to respond_to(:exists?) end it "should have a content method." do expect(@provider).to respond_to(:content) end it "should have a content= method." do expect(@provider).to respond_to(:content=) end describe "when managing the resource" do it "should execute external command dscl from :create" do @provider.stubs(:has_mcx?).returns(false) @provider.class.expects(:dscl).returns('').once @provider.create end it "deletes existing mcx prior to import from :create" do @provider.stubs(:has_mcx?).returns(true) @provider.class.expects(:dscl).with('localhost', '-mcxdelete', @ds_path, anything()).once @provider.class.expects(:dscl).with('localhost', '-mcximport', @ds_path, anything()).once @provider.create end it "should execute external command dscl from :destroy" do @provider.class.expects(:dscl).with('localhost', '-mcxdelete', @ds_path).returns('').once @provider.destroy end it "should execute external command dscl from :exists?" do @provider.class.expects(:dscl).with('localhost', '-mcxexport', @ds_path).returns('').once @provider.exists? end it "should execute external command dscl from :content" do @provider.class.expects(:dscl).with('localhost', '-mcxexport', @ds_path).returns('') @provider.content end it "should execute external command dscl from :content=" do @provider.stubs(:has_mcx?).returns(false) @provider.class.expects(:dscl).returns('').once @provider.content='' end it "deletes existing mcx prior to import from :content=" do @provider.stubs(:has_mcx?).returns(true) @provider.class.expects(:dscl).with('localhost', '-mcxdelete', @ds_path, anything()).once @provider.class.expects(:dscl).with('localhost', '-mcximport', @ds_path, anything()).once @provider.content='' end end describe "when creating and parsing the name for ds_type" do before :each do @provider.class.stubs(:dscl).returns('') @resource.stubs(:[]).with(:name).returns "/Foo/bar" end it "should not accept /Foo/bar" do expect { @provider.create }.to raise_error(MCXContentProviderException) end it "should accept /Foo/bar with ds_type => user" do @resource.stubs(:[]).with(:ds_type).returns "user" expect { @provider.create }.to_not raise_error end it "should accept /Foo/bar with ds_type => group" do @resource.stubs(:[]).with(:ds_type).returns "group" expect { @provider.create }.to_not raise_error end it "should accept /Foo/bar with ds_type => computer" do @resource.stubs(:[]).with(:ds_type).returns "computer" expect { @provider.create }.to_not raise_error end it "should accept :name => /Foo/bar with ds_type => computerlist" do @resource.stubs(:[]).with(:ds_type).returns "computerlist" expect { @provider.create }.to_not raise_error end end describe "when creating and :name => foobar" do before :each do @provider.class.stubs(:dscl).returns('') @resource.stubs(:[]).with(:name).returns "foobar" end it "should not accept unspecified :ds_type and :ds_name" do expect { @provider.create }.to raise_error(MCXContentProviderException) end it "should not accept unspecified :ds_type" do @resource.stubs(:[]).with(:ds_type).returns "user" expect { @provider.create }.to raise_error(MCXContentProviderException) end it "should not accept unspecified :ds_name" do @resource.stubs(:[]).with(:ds_name).returns "foo" expect { @provider.create }.to raise_error(MCXContentProviderException) end it "should accept :ds_type => user, ds_name => foo" do @resource.stubs(:[]).with(:ds_type).returns "user" @resource.stubs(:[]).with(:ds_name).returns "foo" expect { @provider.create }.to_not raise_error end it "should accept :ds_type => group, ds_name => foo" do @resource.stubs(:[]).with(:ds_type).returns "group" @resource.stubs(:[]).with(:ds_name).returns "foo" expect { @provider.create }.to_not raise_error end it "should accept :ds_type => computer, ds_name => foo" do @resource.stubs(:[]).with(:ds_type).returns "computer" @resource.stubs(:[]).with(:ds_name).returns "foo" expect { @provider.create }.to_not raise_error end it "should accept :ds_type => computerlist, ds_name => foo" do @resource.stubs(:[]).with(:ds_type).returns "computerlist" @resource.stubs(:[]).with(:ds_name).returns "foo" expect { @provider.create }.to_not raise_error end it "should not accept :ds_type => bogustype, ds_name => foo" do @resource.stubs(:[]).with(:ds_type).returns "bogustype" @resource.stubs(:[]).with(:ds_name).returns "foo" expect { @provider.create }.to raise_error(MCXContentProviderException) end end describe "when gathering existing instances" do it "should define an instances class method." do expect(@provider.class).to respond_to(:instances) end it "should call external command dscl -list /Local/Default/ on each known ds_type" do @provider.class.expects(:dscl).with('localhost', '-list', "/Local/Default/Users").returns('') @provider.class.expects(:dscl).with('localhost', '-list', "/Local/Default/Groups").returns('') @provider.class.expects(:dscl).with('localhost', '-list', "/Local/Default/Computers").returns('') @provider.class.expects(:dscl).with('localhost', '-list', "/Local/Default/ComputerLists").returns('') @provider.class.instances end end end puppet-5.5.10/spec/unit/provider/mount/0000755005276200011600000000000013417162177017725 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/provider/mount/parsed_spec.rb0000644005276200011600000003271713417161722022547 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'shared_behaviours/all_parsedfile_providers' # TODO: We've recently dropped running specs on Solaris because it was poor ROI. # This file has a ton of tiptoeing around Solaris that we should ultimately # remove, but I don't want to do so just yet, in case we get pushback to # restore Solaris spec tests. describe Puppet::Type.type(:mount).provider(:parsed), :unless => Puppet.features.microsoft_windows? do before :each do Facter.clear end let :vfstab_sample do "/dev/dsk/c0d0s0 /dev/rdsk/c0d0s0 \t\t / \t ufs 1 no\t-" end let :fstab_sample do "/dev/vg00/lv01\t/spare \t \t ext3 defaults\t1 2" end # LAK:FIXME I can't mock Facter because this test happens at parse-time. it "should default to /etc/vfstab on Solaris" do if Facter.value(:osfamily) != 'Solaris' skip("This test only works on Solaris") else expect(described_class.default_target).to eq('/etc/vfstab') end end it "should default to /etc/vfstab on Solaris" do pending "This test only works on AIX" unless Facter.value(:osfamily) == 'AIX' expect(described_class.default_target).to eq('/etc/filesystems') end it "should default to /etc/fstab on anything else" do if Facter.value(:osfamily) == 'Solaris' skip("This test only does not work on Solaris") else expect(described_class.default_target).to eq('/etc/fstab') end end describe "when parsing a line" do it "should not crash on incomplete lines in fstab" do parse = described_class.parse <<-FSTAB /dev/incomplete /dev/device name FSTAB expect { described_class.to_line(parse[0]) }.to_not raise_error end # it_should_behave_like "all parsedfile providers", # provider_class, my_fixtures('*.fstab') describe "on Solaris", :if => Facter.value(:osfamily) == 'Solaris' do it "should extract device from the first field" do expect(described_class.parse_line(vfstab_sample)[:device]).to eq('/dev/dsk/c0d0s0') end it "should extract blockdevice from second field" do expect(described_class.parse_line(vfstab_sample)[:blockdevice]).to eq("/dev/rdsk/c0d0s0") end it "should extract name from third field" do expect(described_class.parse_line(vfstab_sample)[:name]).to eq("/") end it "should extract fstype from fourth field" do expect(described_class.parse_line(vfstab_sample)[:fstype]).to eq("ufs") end it "should extract pass from fifth field" do expect(described_class.parse_line(vfstab_sample)[:pass]).to eq("1") end it "should extract atboot from sixth field" do expect(described_class.parse_line(vfstab_sample)[:atboot]).to eq("no") end it "should extract options from seventh field" do expect(described_class.parse_line(vfstab_sample)[:options]).to eq("-") end end describe "on other platforms than Solaris", :if => Facter.value(:osfamily) != 'Solaris' do it "should extract device from the first field" do expect(described_class.parse_line(fstab_sample)[:device]).to eq('/dev/vg00/lv01') end it "should extract name from second field" do expect(described_class.parse_line(fstab_sample)[:name]).to eq("/spare") end it "should extract fstype from third field" do expect(described_class.parse_line(fstab_sample)[:fstype]).to eq("ext3") end it "should extract options from fourth field" do expect(described_class.parse_line(fstab_sample)[:options]).to eq("defaults") end it "should extract dump from fifth field" do expect(described_class.parse_line(fstab_sample)[:dump]).to eq("1") end it "should extract options from sixth field" do expect(described_class.parse_line(fstab_sample)[:pass]).to eq("2") end end end describe "mountinstances" do it "should get name from mountoutput found on Solaris" do Facter.stubs(:value).with(:osfamily).returns 'Solaris' described_class.stubs(:mountcmd).returns(File.read(my_fixture('solaris.mount'))) mounts = described_class.mountinstances expect(mounts.size).to eq(6) expect(mounts[0]).to eq({ :name => '/', :mounted => :yes }) expect(mounts[1]).to eq({ :name => '/proc', :mounted => :yes }) expect(mounts[2]).to eq({ :name => '/etc/mnttab', :mounted => :yes }) expect(mounts[3]).to eq({ :name => '/tmp', :mounted => :yes }) expect(mounts[4]).to eq({ :name => '/export/home', :mounted => :yes }) expect(mounts[5]).to eq({ :name => '/ghost', :mounted => :yes }) end it "should get name from mountoutput found on HP-UX" do Facter.stubs(:value).with(:osfamily).returns 'HP-UX' described_class.stubs(:mountcmd).returns(File.read(my_fixture('hpux.mount'))) mounts = described_class.mountinstances expect(mounts.size).to eq(17) expect(mounts[0]).to eq({ :name => '/', :mounted => :yes }) expect(mounts[1]).to eq({ :name => '/devices', :mounted => :yes }) expect(mounts[2]).to eq({ :name => '/dev', :mounted => :yes }) expect(mounts[3]).to eq({ :name => '/system/contract', :mounted => :yes }) expect(mounts[4]).to eq({ :name => '/proc', :mounted => :yes }) expect(mounts[5]).to eq({ :name => '/etc/mnttab', :mounted => :yes }) expect(mounts[6]).to eq({ :name => '/etc/svc/volatile', :mounted => :yes }) expect(mounts[7]).to eq({ :name => '/system/object', :mounted => :yes }) expect(mounts[8]).to eq({ :name => '/etc/dfs/sharetab', :mounted => :yes }) expect(mounts[9]).to eq({ :name => '/lib/libc.so.1', :mounted => :yes }) expect(mounts[10]).to eq({ :name => '/dev/fd', :mounted => :yes }) expect(mounts[11]).to eq({ :name => '/tmp', :mounted => :yes }) expect(mounts[12]).to eq({ :name => '/var/run', :mounted => :yes }) expect(mounts[13]).to eq({ :name => '/export', :mounted => :yes }) expect(mounts[14]).to eq({ :name => '/export/home', :mounted => :yes }) expect(mounts[15]).to eq({ :name => '/rpool', :mounted => :yes }) expect(mounts[16]).to eq({ :name => '/ghost', :mounted => :yes }) end it "should get name from mountoutput found on Darwin" do Facter.stubs(:value).with(:osfamily).returns 'Darwin' described_class.stubs(:mountcmd).returns(File.read(my_fixture('darwin.mount'))) mounts = described_class.mountinstances expect(mounts.size).to eq(6) expect(mounts[0]).to eq({ :name => '/', :mounted => :yes }) expect(mounts[1]).to eq({ :name => '/dev', :mounted => :yes }) expect(mounts[2]).to eq({ :name => '/net', :mounted => :yes }) expect(mounts[3]).to eq({ :name => '/home', :mounted => :yes }) expect(mounts[4]).to eq({ :name => '/usr', :mounted => :yes }) expect(mounts[5]).to eq({ :name => '/ghost', :mounted => :yes }) end it "should get name from mountoutput found on Linux" do Facter.stubs(:value).with(:osfamily).returns 'Gentoo' described_class.stubs(:mountcmd).returns(File.read(my_fixture('linux.mount'))) mounts = described_class.mountinstances expect(mounts[0]).to eq({ :name => '/', :mounted => :yes }) expect(mounts[1]).to eq({ :name => '/lib64/rc/init.d', :mounted => :yes }) expect(mounts[2]).to eq({ :name => '/sys', :mounted => :yes }) expect(mounts[3]).to eq({ :name => '/usr/portage', :mounted => :yes }) expect(mounts[4]).to eq({ :name => '/ghost', :mounted => :yes }) end it "should get name from mountoutput found on AIX" do Facter.stubs(:value).with(:osfamily).returns 'AIX' described_class.stubs(:mountcmd).returns(File.read(my_fixture('aix.mount'))) mounts = described_class.mountinstances expect(mounts[0]).to eq({ :name => '/', :mounted => :yes }) expect(mounts[1]).to eq({ :name => '/usr', :mounted => :yes }) expect(mounts[2]).to eq({ :name => '/var', :mounted => :yes }) expect(mounts[3]).to eq({ :name => '/tmp', :mounted => :yes }) expect(mounts[4]).to eq({ :name => '/home', :mounted => :yes }) expect(mounts[5]).to eq({ :name => '/admin', :mounted => :yes }) expect(mounts[6]).to eq({ :name => '/proc', :mounted => :yes }) expect(mounts[7]).to eq({ :name => '/opt', :mounted => :yes }) expect(mounts[8]).to eq({ :name => '/srv/aix', :mounted => :yes }) end it "should raise an error if a line is not understandable" do described_class.stubs(:mountcmd).returns("bazinga!") expect { described_class.mountinstances }.to raise_error Puppet::Error, 'Could not understand line bazinga! from mount output' end end it "should support AIX's paragraph based /etc/filesystems" do pending "This test only works on AIX" unless Facter.value(:osfamily) == 'AIX' Facter.stubs(:value).with(:osfamily).returns 'AIX' described_class.stubs(:default_target).returns my_fixture('aix.filesystems') described_class.stubs(:mountcmd).returns File.read(my_fixture('aix.mount')) instances = described_class.instances expect(instances[0].name).to eq("/") expect(instances[0].device).to eq("/dev/hd4") expect(instances[0].fstype).to eq("jfs2") expect(instances[0].options).to eq("check=false,free=true,log=NULL,mount=automatic,quota=no,type=bootfs,vol=root") expect(instances[11].name).to eq("/srv/aix") expect(instances[11].device).to eq("mynode") expect(instances[11].fstype).to eq("nfs") expect(instances[11].options).to eq("vers=2,account=false,log=NULL,mount=true") end my_fixtures('*.fstab').each do |fstab| platform = File.basename(fstab, '.fstab') describe "when calling instances on #{platform}" do before :each do if Facter[:osfamily] == "Solaris" then platform == 'solaris' or skip "We need to stub the operatingsystem fact at load time, but can't" else platform != 'solaris' or skip "We need to stub the operatingsystem fact at load time, but can't" end # Stub the mount output to our fixture. begin mount = my_fixture(platform + '.mount') described_class.stubs(:mountcmd).returns File.read(mount) rescue skip "is #{platform}.mount missing at this point?" end # Note: we have to stub default_target before creating resources # because it is used by Puppet::Type::Mount.new to populate the # :target property. described_class.stubs(:default_target).returns fstab @retrieve = described_class.instances.collect { |prov| {:name => prov.get(:name), :ensure => prov.get(:ensure)}} end # Following mountpoint are present in all fstabs/mountoutputs describe "on other platforms than Solaris", :if => Facter.value(:osfamily) != 'Solaris' do it "should include unmounted resources" do expect(@retrieve).to include(:name => '/', :ensure => :mounted) end it "should include mounted resources" do expect(@retrieve).to include(:name => '/boot', :ensure => :unmounted) end it "should include ghost resources" do expect(@retrieve).to include(:name => '/ghost', :ensure => :ghost) end end end describe "when prefetching on #{platform}" do before :each do if Facter[:osfamily] == "Solaris" then platform == 'solaris' or skip "We need to stub the operatingsystem fact at load time, but can't" else platform != 'solaris' or skip "We need to stub the operatingsystem fact at load time, but can't" end # Stub the mount output to our fixture. begin mount = my_fixture(platform + '.mount') described_class.stubs(:mountcmd).returns File.read(mount) rescue skip "is #{platform}.mount missing at this point?" end # Note: we have to stub default_target before creating resources # because it is used by Puppet::Type::Mount.new to populate the # :target property. described_class.stubs(:default_target).returns fstab @res_ghost = Puppet::Type::Mount.new(:name => '/ghost') # in no fake fstab @res_mounted = Puppet::Type::Mount.new(:name => '/') # in every fake fstab @res_unmounted = Puppet::Type::Mount.new(:name => '/boot') # in every fake fstab @res_absent = Puppet::Type::Mount.new(:name => '/absent') # in no fake fstab # Simulate transaction.rb:prefetch @resource_hash = {} [@res_ghost, @res_mounted, @res_unmounted, @res_absent].each do |resource| @resource_hash[resource.name] = resource end end describe "on other platforms than Solaris", :if => Facter.value(:osfamily) != 'Solaris' do it "should set :ensure to :unmounted if found in fstab but not mounted" do described_class.prefetch(@resource_hash) expect(@res_unmounted.provider.get(:ensure)).to eq(:unmounted) end it "should set :ensure to :ghost if not found in fstab but mounted" do described_class.prefetch(@resource_hash) expect(@res_ghost.provider.get(:ensure)).to eq(:ghost) end it "should set :ensure to :mounted if found in fstab and mounted" do described_class.prefetch(@resource_hash) expect(@res_mounted.provider.get(:ensure)).to eq(:mounted) end end it "should set :ensure to :absent if not found in fstab and not mounted" do described_class.prefetch(@resource_hash) expect(@res_absent.provider.get(:ensure)).to eq(:absent) end end end end puppet-5.5.10/spec/unit/provider/mount_spec.rb0000644005276200011600000001261113417161722021260 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/provider/mount' describe Puppet::Provider::Mount do before :each do @mounter = Object.new @mounter.extend(Puppet::Provider::Mount) @name = "/" @resource = stub 'resource' @resource.stubs(:[]).with(:name).returns(@name) @mounter.stubs(:resource).returns(@resource) end describe Puppet::Provider::Mount, " when mounting" do before :each do @mounter.stubs(:get).with(:ensure).returns(:mounted) end it "should use the 'mountcmd' method to mount" do @mounter.stubs(:options).returns(nil) @mounter.expects(:mountcmd) @mounter.mount end it "should add the options following '-o' on MacOS if they exist and are not set to :absent" do Facter.expects(:value).with(:kernel).returns 'Darwin' @mounter.stubs(:options).returns("ro") @mounter.expects(:mountcmd).with '-o', 'ro', '/' @mounter.mount end it "should not explicitly pass mount options on systems other than MacOS" do Facter.expects(:value).with(:kernel).returns 'HP-UX' @mounter.stubs(:options).returns("ro") @mounter.expects(:mountcmd).with '/' @mounter.mount end it "should specify the filesystem name to the mount command" do @mounter.stubs(:options).returns(nil) @mounter.expects(:mountcmd).with { |*ary| ary[-1] == @name } @mounter.mount end it "should update the :ensure state to :mounted if it was :unmounted before" do @mounter.expects(:mountcmd) @mounter.stubs(:options).returns(nil) @mounter.expects(:get).with(:ensure).returns(:unmounted) @mounter.expects(:set).with(:ensure => :mounted) @mounter.mount end it "should update the :ensure state to :ghost if it was :absent before" do @mounter.expects(:mountcmd) @mounter.stubs(:options).returns(nil) @mounter.expects(:get).with(:ensure).returns(:absent) @mounter.expects(:set).with(:ensure => :ghost) @mounter.mount end end describe Puppet::Provider::Mount, " when remounting" do context "if the resource supports remounting" do context "given explicit options on AIX" do it "should combine the options with 'remount'" do @mounter.stubs(:info) @mounter.stubs(:options).returns('ro') @resource.stubs(:[]).with(:remounts).returns(:true) Facter.expects(:value).with(:operatingsystem).returns 'AIX' @mounter.expects(:mountcmd).with("-o", "ro,remount", @name) @mounter.remount end end it "should use '-o remount'" do @mounter.stubs(:info) @resource.stubs(:[]).with(:remounts).returns(:true) @mounter.expects(:mountcmd).with("-o", "remount", @name) @mounter.remount end end it "should mount with '-o update' on OpenBSD" do @mounter.stubs(:info) @mounter.stubs(:options) @resource.stubs(:[]).with(:remounts).returns(false) Facter.expects(:value).with(:operatingsystem).returns 'OpenBSD' @mounter.expects(:mountcmd).with("-o", "update", @name) @mounter.remount end it "should unmount and mount if the resource does not specify it supports remounting" do @mounter.stubs(:info) @mounter.stubs(:options) @resource.stubs(:[]).with(:remounts).returns(false) Facter.expects(:value).with(:operatingsystem).returns 'AIX' @mounter.expects(:mount) @mounter.expects(:unmount) @mounter.remount end it "should log that it is remounting" do @resource.stubs(:[]).with(:remounts).returns(:true) @mounter.stubs(:mountcmd) @mounter.expects(:info).with("Remounting") @mounter.remount end end describe Puppet::Provider::Mount, " when unmounting" do before :each do @mounter.stubs(:get).with(:ensure).returns(:unmounted) end it "should call the :umount command with the resource name" do @mounter.expects(:umount).with(@name) @mounter.unmount end it "should update the :ensure state to :absent if it was :ghost before" do @mounter.expects(:umount).with(@name).returns true @mounter.expects(:get).with(:ensure).returns(:ghost) @mounter.expects(:set).with(:ensure => :absent) @mounter.unmount end it "should update the :ensure state to :unmounted if it was :mounted before" do @mounter.expects(:umount).with(@name).returns true @mounter.expects(:get).with(:ensure).returns(:mounted) @mounter.expects(:set).with(:ensure => :unmounted) @mounter.unmount end end describe Puppet::Provider::Mount, " when determining if it is mounted" do it "should query the property_hash" do @mounter.expects(:get).with(:ensure).returns(:mounted) @mounter.mounted? end it "should return true if prefetched value is :mounted" do @mounter.stubs(:get).with(:ensure).returns(:mounted) @mounter.mounted? == true end it "should return true if prefetched value is :ghost" do @mounter.stubs(:get).with(:ensure).returns(:ghost) @mounter.mounted? == true end it "should return false if prefetched value is :absent" do @mounter.stubs(:get).with(:ensure).returns(:absent) @mounter.mounted? == false end it "should return false if prefetched value is :unmounted" do @mounter.stubs(:get).with(:ensure).returns(:unmounted) @mounter.mounted? == false end end end puppet-5.5.10/spec/unit/provider/naginator_spec.rb0000644005276200011600000000426413417161722022105 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/provider/naginator' describe Puppet::Provider::Naginator do before do @resource_type = stub 'resource_type', :name => :nagios_test @class = Class.new(Puppet::Provider::Naginator) @class.stubs(:resource_type).returns @resource_type end it "should be able to look up the associated Nagios type" do nagios_type = mock "nagios_type" nagios_type.stubs :attr_accessor Nagios::Base.expects(:type).with(:test).returns nagios_type expect(@class.nagios_type).to equal(nagios_type) end it "should use the Nagios type to determine whether an attribute is valid" do nagios_type = mock "nagios_type" nagios_type.stubs :attr_accessor Nagios::Base.expects(:type).with(:test).returns nagios_type nagios_type.expects(:parameters).returns [:foo, :bar] expect(@class.valid_attr?(:test, :foo)).to be_truthy end it "should use Naginator to parse configuration snippets" do parser = mock 'parser' parser.expects(:parse).with("my text").returns "my instances" Nagios::Parser.expects(:new).returns(parser) expect(@class.parse("my text")).to eq("my instances") end it "should join Nagios::Base records with '\\n' when asked to convert them to text" do @class.expects(:header).returns "myheader\n" expect(@class.to_file([:one, :two])).to eq("myheader\none\ntwo") end it "should be able to prefetch instance from configuration files" do expect(@class).to respond_to(:prefetch) end it "should be able to generate a list of instances" do expect(@class).to respond_to(:instances) end it "should never skip records" do expect(@class).not_to be_skip_record("foo") end end describe Nagios::Base do it "should not turn set parameters into arrays #17871" do obj = Nagios::Base.create('host') obj.host_name = "my_hostname" expect(obj.host_name).to eq("my_hostname") end end describe Nagios::Parser do include PuppetSpec::Files subject do described_class.new end let(:config) { File.new( my_fixture('define_empty_param') ).read } it "should handle empty parameter values" do expect { subject.parse(config) }.to_not raise_error end end puppet-5.5.10/spec/unit/provider/nameservice_spec.rb0000644005276200011600000004445213417161722022427 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/provider/nameservice' require 'puppet/etc' require 'puppet_spec/character_encoding' describe Puppet::Provider::NameService do before :each do described_class.initvars described_class.resource_type = faketype end # These are values getpwent might give you let :users do [ Struct::Passwd.new('root', 'x', 0, 0), Struct::Passwd.new('foo', 'x', 1000, 2000), nil ] end # These are values getgrent might give you let :groups do [ Struct::Group.new('root', 'x', 0, %w{root}), Struct::Group.new('bin', 'x', 1, %w{root bin daemon}), nil ] end # A fake struct besides Struct::Group and Struct::Passwd let :fakestruct do Struct.new(:foo, :bar) end # A fake value getent might return let :fakeetcobject do fakestruct.new('fooval', 'barval') end # The provider sometimes relies on @resource for valid properties so let's # create a fake type with properties that match our fake struct. let :faketype do Puppet::Type.newtype(:nameservice_dummytype) do newparam(:name) ensurable newproperty(:foo) newproperty(:bar) end end let :provider do described_class.new(:name => 'bob', :foo => 'fooval', :bar => 'barval') end let :resource do resource = faketype.new(:name => 'bob', :ensure => :present) resource.provider = provider resource end # These values simulate what Ruby Etc would return from a host with the "same" # user represented in different encodings on disk. let(:utf_8_jose) { "Jos\u00E9"} let(:utf_8_labeled_as_latin_1_jose) { utf_8_jose.dup.force_encoding(Encoding::ISO_8859_1) } let(:valid_latin1_jose) { utf_8_jose.encode(Encoding::ISO_8859_1)} let(:invalid_utf_8_jose) { valid_latin1_jose.dup.force_encoding(Encoding::UTF_8) } let(:escaped_utf_8_jose) { "Jos\uFFFD".force_encoding(Encoding::UTF_8) } let(:utf_8_mixed_users) { [ Struct::Passwd.new('root', 'x', 0, 0), Struct::Passwd.new('foo', 'x', 1000, 2000), Struct::Passwd.new(utf_8_jose, utf_8_jose, 1001, 2000), # UTF-8 character # In a UTF-8 environment, ruby will return strings labeled as UTF-8 even if they're not valid in UTF-8 Struct::Passwd.new(invalid_utf_8_jose, invalid_utf_8_jose, 1002, 2000), nil ] } let(:latin_1_mixed_users) { [ # In a LATIN-1 environment, ruby will return *all* strings labeled as LATIN-1 Struct::Passwd.new('root'.force_encoding(Encoding::ISO_8859_1), 'x', 0, 0), Struct::Passwd.new('foo'.force_encoding(Encoding::ISO_8859_1), 'x', 1000, 2000), Struct::Passwd.new(utf_8_labeled_as_latin_1_jose, utf_8_labeled_as_latin_1_jose, 1002, 2000), Struct::Passwd.new(valid_latin1_jose, valid_latin1_jose, 1001, 2000), # UTF-8 character nil ] } describe "#options" do it "should add options for a valid property" do described_class.options :foo, :key1 => 'val1', :key2 => 'val2' described_class.options :bar, :key3 => 'val3' expect(described_class.option(:foo, :key1)).to eq('val1') expect(described_class.option(:foo, :key2)).to eq('val2') expect(described_class.option(:bar, :key3)).to eq('val3') end it "should raise an error for an invalid property" do expect { described_class.options :baz, :key1 => 'val1' }.to raise_error( Puppet::Error, 'baz is not a valid attribute for nameservice_dummytype') end end describe "#option" do it "should return the correct value" do described_class.options :foo, :key1 => 'val1', :key2 => 'val2' expect(described_class.option(:foo, :key2)).to eq('val2') end it "should symbolize the name first" do described_class.options :foo, :key1 => 'val1', :key2 => 'val2' expect(described_class.option('foo', :key2)).to eq('val2') end it "should return nil if no option has been specified earlier" do expect(described_class.option(:foo, :key2)).to be_nil end it "should return nil if no option for that property has been specified earlier" do described_class.options :bar, :key2 => 'val2' expect(described_class.option(:foo, :key2)).to be_nil end it "should return nil if no matching key can be found for that property" do described_class.options :foo, :key3 => 'val2' expect(described_class.option(:foo, :key2)).to be_nil end end describe "#section" do it "should raise an error if resource_type has not been set" do described_class.expects(:resource_type).returns nil expect { described_class.section }.to raise_error Puppet::Error, 'Cannot determine Etc section without a resource type' end # the return values are hard coded so I am using types that actually make # use of the nameservice provider it "should return pw for users" do described_class.resource_type = Puppet::Type.type(:user) expect(described_class.section).to eq('pw') end it "should return gr for groups" do described_class.resource_type = Puppet::Type.type(:group) expect(described_class.section).to eq('gr') end end describe "#listbyname" do it "should be deprecated" do Puppet.expects(:deprecation_warning).with(regexp_matches(/listbyname is deprecated/)) described_class.listbyname end it "should return a list of users if resource_type is user" do described_class.resource_type = Puppet::Type.type(:user) Puppet::Etc.expects(:setpwent) Puppet::Etc.stubs(:getpwent).returns(*users) Puppet::Etc.expects(:endpwent) expect(described_class.listbyname).to eq(%w{root foo}) end context "encoding handling" do described_class.resource_type = Puppet::Type.type(:user) # These two tests simulate an environment where there are two users with # the same name on disk, but each name is stored on disk in a different # encoding it "should return names with invalid byte sequences replaced with '?'" do Etc.stubs(:getpwent).returns(*utf_8_mixed_users) expect(invalid_utf_8_jose).to_not be_valid_encoding result = PuppetSpec::CharacterEncoding.with_external_encoding(Encoding::UTF_8) do described_class.listbyname end expect(result).to eq(['root', 'foo', utf_8_jose, escaped_utf_8_jose]) end it "should return names in their original encoding/bytes if they would not be valid UTF-8" do Etc.stubs(:getpwent).returns(*latin_1_mixed_users) result = PuppetSpec::CharacterEncoding.with_external_encoding(Encoding::ISO_8859_1) do described_class.listbyname end expect(result).to eq(['root'.force_encoding(Encoding::UTF_8), 'foo'.force_encoding(Encoding::UTF_8), utf_8_jose, valid_latin1_jose]) end end it "should return a list of groups if resource_type is group", :unless => Puppet.features.microsoft_windows? do described_class.resource_type = Puppet::Type.type(:group) Puppet::Etc.expects(:setgrent) Puppet::Etc.stubs(:getgrent).returns(*groups) Puppet::Etc.expects(:endgrent) expect(described_class.listbyname).to eq(%w{root bin}) end it "should yield if a block given" do yield_results = [] described_class.resource_type = Puppet::Type.type(:user) Puppet::Etc.expects(:setpwent) Puppet::Etc.stubs(:getpwent).returns(*users) Puppet::Etc.expects(:endpwent) described_class.listbyname {|x| yield_results << x } expect(yield_results).to eq(%w{root foo}) end end describe "instances" do it "should return a list of objects in UTF-8 with any invalid characters replaced with '?'" do # These two tests simulate an environment where there are two users with # the same name on disk, but each name is stored on disk in a different # encoding Etc.stubs(:getpwent).returns(*utf_8_mixed_users) result = PuppetSpec::CharacterEncoding.with_external_encoding(Encoding::UTF_8) do described_class.instances end expect(result.map(&:name)).to eq( [ 'root'.force_encoding(Encoding::UTF_8), # started as UTF-8 on disk, returned unaltered as UTF-8 'foo'.force_encoding(Encoding::UTF_8), # started as UTF-8 on disk, returned unaltered as UTF-8 utf_8_jose, # started as UTF-8 on disk, returned unaltered as UTF-8 escaped_utf_8_jose # started as LATIN-1 on disk, but Etc returned as UTF-8 and we escaped invalid chars ] ) end it "should have object names in their original encoding/bytes if they would not be valid UTF-8" do Etc.stubs(:getpwent).returns(*latin_1_mixed_users) result = PuppetSpec::CharacterEncoding.with_external_encoding(Encoding::ISO_8859_1) do described_class.instances end expect(result.map(&:name)).to eq( [ 'root'.force_encoding(Encoding::UTF_8), # started as LATIN-1 on disk, we overrode to UTF-8 'foo'.force_encoding(Encoding::UTF_8), # started as LATIN-1 on disk, we overrode to UTF-8 utf_8_jose, # started as UTF-8 on disk, returned by Etc as LATIN-1, and we overrode to UTF-8 valid_latin1_jose # started as LATIN-1 on disk, returned by Etc as valid LATIN-1, and we leave as LATIN-1 ] ) end it "should pass the Puppet::Etc :canonical_name Struct member to the constructor" do users = [ Struct::Passwd.new(invalid_utf_8_jose, invalid_utf_8_jose, 1002, 2000), nil ] Etc.stubs(:getpwent).returns(*users) described_class.expects(:new).with(:name => escaped_utf_8_jose, :canonical_name => invalid_utf_8_jose, :ensure => :present) described_class.instances end end describe "validate" do it "should pass if no check is registered at all" do expect { described_class.validate(:foo, 300) }.to_not raise_error expect { described_class.validate('foo', 300) }.to_not raise_error end it "should pass if no check for that property is registered" do described_class.verify(:bar, 'Must be 100') { |val| val == 100 } expect { described_class.validate(:foo, 300) }.to_not raise_error expect { described_class.validate('foo', 300) }.to_not raise_error end it "should pass if the value is valid" do described_class.verify(:foo, 'Must be 100') { |val| val == 100 } expect { described_class.validate(:foo, 100) }.to_not raise_error expect { described_class.validate('foo', 100) }.to_not raise_error end it "should raise an error if the value is invalid" do described_class.verify(:foo, 'Must be 100') { |val| val == 100 } expect { described_class.validate(:foo, 200) }.to raise_error(ArgumentError, 'Invalid value 200: Must be 100') expect { described_class.validate('foo', 200) }.to raise_error(ArgumentError, 'Invalid value 200: Must be 100') end end describe "getinfo" do before :each do # with section=foo we'll call Etc.getfoonam instead of getpwnam or getgrnam described_class.stubs(:section).returns 'foo' resource # initialize the resource so our provider has a @resource instance variable end it "should return a hash if we can retrieve something" do Puppet::Etc.expects(:send).with(:getfoonam, 'bob').returns fakeetcobject provider.expects(:info2hash).with(fakeetcobject).returns(:foo => 'fooval', :bar => 'barval') expect(provider.getinfo(true)).to eq({:foo => 'fooval', :bar => 'barval'}) end it "should return nil if we cannot retrieve anything" do Puppet::Etc.expects(:send).with(:getfoonam, 'bob').raises(ArgumentError, "can't find bob") provider.expects(:info2hash).never expect(provider.getinfo(true)).to be_nil end # Nameservice instances track the original resource name on disk, before # overriding to UTF-8, in @canonical_name for querying that state on disk # again if needed it "should use the instance's @canonical_name to query the system" do provider_instance = described_class.new(:name => 'foo', :canonical_name => 'original_foo', :ensure => :present) Puppet::Etc.expects(:send).with(:getfoonam, 'original_foo') provider_instance.getinfo(true) end it "should use the instance's name instead of canonical_name if not supplied during instantiation" do provider_instance = described_class.new(:name => 'foo', :ensure => :present) Puppet::Etc.expects(:send).with(:getfoonam, 'foo') provider_instance.getinfo(true) end end describe "info2hash" do it "should return a hash with all properties" do # we have to have an implementation of posixmethod which has to # convert a propertyname (e.g. comment) into a fieldname of our # Struct (e.g. gecos). I do not want to test posixmethod here so # let's fake an implementation which does not do any translation. We # expect two method invocations because info2hash calls the method # twice if the Struct responds to the propertyname (our fake Struct # provides values for :foo and :bar) TODO: Fix that provider.expects(:posixmethod).with(:foo).returns(:foo).twice provider.expects(:posixmethod).with(:bar).returns(:bar).twice provider.expects(:posixmethod).with(:ensure).returns :ensure expect(provider.info2hash(fakeetcobject)).to eq({ :foo => 'fooval', :bar => 'barval' }) end end describe "munge" do it "should return the input value if no munge method has be defined" do expect(provider.munge(:foo, 100)).to eq(100) end it "should return the munged value otherwise" do described_class.options(:foo, :munge => proc { |x| x*2 }) expect(provider.munge(:foo, 100)).to eq(200) end end describe "unmunge" do it "should return the input value if no unmunge method has been defined" do expect(provider.unmunge(:foo, 200)).to eq(200) end it "should return the unmunged value otherwise" do described_class.options(:foo, :unmunge => proc { |x| x/2 }) expect(provider.unmunge(:foo, 200)).to eq(100) end end describe "exists?" do it "should return true if we can retrieve anything" do provider.expects(:getinfo).with(true).returns(:foo => 'fooval', :bar => 'barval') expect(provider).to be_exists end it "should return false if we cannot retrieve anything" do provider.expects(:getinfo).with(true).returns nil expect(provider).not_to be_exists end end describe "get" do before(:each) {described_class.resource_type = faketype } it "should return the correct getinfo value" do provider.expects(:getinfo).with(false).returns(:foo => 'fooval', :bar => 'barval') expect(provider.get(:bar)).to eq('barval') end it "should unmunge the value first" do described_class.options(:bar, :munge => proc { |x| x*2}, :unmunge => proc {|x| x/2}) provider.expects(:getinfo).with(false).returns(:foo => 200, :bar => 500) expect(provider.get(:bar)).to eq(250) end it "should return nil if getinfo cannot retrieve the value" do provider.expects(:getinfo).with(false).returns(:foo => 'fooval', :bar => 'barval') expect(provider.get(:no_such_key)).to be_nil end end describe "set" do before :each do resource # initialize resource so our provider has a @resource object described_class.verify(:foo, 'Must be 100') { |val| val == 100 } end it "should raise an error on invalid values" do expect { provider.set(:foo, 200) }.to raise_error(ArgumentError, 'Invalid value 200: Must be 100') end it "should execute the modify command on valid values" do provider.expects(:modifycmd).with(:foo, 100).returns ['/bin/modify', '-f', '100' ] provider.expects(:execute).with(['/bin/modify', '-f', '100'], has_entry(:custom_environment, {})) provider.set(:foo, 100) end it "should munge the value first" do described_class.options(:foo, :munge => proc { |x| x*2}, :unmunge => proc {|x| x/2}) provider.expects(:modifycmd).with(:foo, 200).returns(['/bin/modify', '-f', '200' ]) provider.expects(:execute).with(['/bin/modify', '-f', '200'], has_entry(:custom_environment, {})) provider.set(:foo, 100) end it "should fail if the modify command fails" do provider.expects(:modifycmd).with(:foo, 100).returns(['/bin/modify', '-f', '100' ]) provider.expects(:execute).with(['/bin/modify', '-f', '100'], kind_of(Hash)).raises(Puppet::ExecutionFailure, "Execution of '/bin/modify' returned 1: some_failure") expect { provider.set(:foo, 100) }.to raise_error Puppet::Error, /Could not set foo/ end end describe "comments_insync?" do # comments_insync? overrides Puppet::Property#insync? and will act on an # array containing a should value (the expected value of Puppet::Property # @should) context "given strings with compatible encodings" do it "should return false if the is-value and should-value are not equal" do is_value = "foo" should_value = ["bar"] expect(provider.comments_insync?(is_value, should_value)).to be_falsey end it "should return true if the is-value and should-value are equal" do is_value = "foo" should_value = ["foo"] expect(provider.comments_insync?(is_value, should_value)).to be_truthy end end context "given strings with incompatible encodings" do let(:snowman_iso) { "\u2603".force_encoding(Encoding::ISO_8859_1) } let(:snowman_utf8) { "\u2603".force_encoding(Encoding::UTF_8) } let(:snowman_binary) { "\u2603".force_encoding(Encoding::ASCII_8BIT) } let(:arabic_heh_utf8) { "\u06FF".force_encoding(Encoding::UTF_8) } it "should be able to compare unequal strings and return false" do expect(Encoding.compatible?(snowman_iso, arabic_heh_utf8)).to be_falsey expect(provider.comments_insync?(snowman_iso, [arabic_heh_utf8])).to be_falsey end it "should be able to compare equal strings and return true" do expect(Encoding.compatible?(snowman_binary, snowman_utf8)).to be_falsey expect(provider.comments_insync?(snowman_binary, [snowman_utf8])).to be_truthy end it "should not manipulate the actual encoding of either string" do expect(Encoding.compatible?(snowman_binary, snowman_utf8)).to be_falsey provider.comments_insync?(snowman_binary, [snowman_utf8]) expect(snowman_binary.encoding).to eq(Encoding::ASCII_8BIT) expect(snowman_utf8.encoding).to eq(Encoding::UTF_8) end end end end puppet-5.5.10/spec/unit/provider/network_device_spec.rb0000644005276200011600000001152513417161722023131 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/provider/network_device' require 'ostruct' Puppet::Type.type(:vlan).provide :test, :parent => Puppet::Provider::NetworkDevice do mk_resource_methods def self.lookup(device, name) end def self.device(url) :device end end provider_class = Puppet::Type.type(:vlan).provider(:test) describe provider_class do before do @resource = stub("resource", :name => "test") @provider = provider_class.new(@resource) end it "should be able to prefetch instances from the device" do expect(provider_class).to respond_to(:prefetch) end it "should have an instances method" do expect(provider_class).to respond_to(:instances) end describe "when prefetching" do before do @resource = stub_everything 'resource' @resources = {"200" => @resource} provider_class.stubs(:lookup) end it "should lookup an entry for each passed resource" do provider_class.expects(:lookup).with{ |device,value| value == "200" }.returns nil provider_class.stubs(:new) @resource.stubs(:provider=) provider_class.prefetch(@resources) end describe "resources that do not exist" do it "should create a provider with :ensure => :absent" do provider_class.stubs(:lookup).returns(nil) provider_class.expects(:new).with(:device, :ensure => :absent).returns "myprovider" @resource.expects(:provider=).with("myprovider") provider_class.prefetch(@resources) end end describe "resources that exist" do it "should create a provider with the results of the find and ensure at present" do provider_class.stubs(:lookup).returns({ :name => "200", :description => "myvlan"}) provider_class.expects(:new).with(:device, :name => "200", :description => "myvlan", :ensure => :present).returns "myprovider" @resource.expects(:provider=).with("myprovider") provider_class.prefetch(@resources) end end end describe "when being initialized" do describe "with a hash" do before do @resource_class = mock 'resource_class' provider_class.stubs(:resource_type).returns @resource_class @property_class = stub 'property_class', :array_matching => :all, :superclass => Puppet::Property @resource_class.stubs(:attrclass).with(:one).returns(@property_class) @resource_class.stubs(:valid_parameter?).returns true end it "should store a copy of the hash as its vlan_properties" do instance = provider_class.new(:device, :one => :two) expect(instance.former_properties).to eq({:one => :two}) end end end describe "when an instance" do before do @instance = provider_class.new(:device) @property_class = stub 'property_class', :array_matching => :all, :superclass => Puppet::Property @resource_class = stub 'resource_class', :attrclass => @property_class, :valid_parameter? => true, :validproperties => [:description] provider_class.stubs(:resource_type).returns @resource_class end it "should have a method for creating the instance" do expect(@instance).to respond_to(:create) end it "should have a method for removing the instance" do expect(@instance).to respond_to(:destroy) end it "should indicate when the instance already exists" do @instance = provider_class.new(:device, :ensure => :present) expect(@instance.exists?).to be_truthy end it "should indicate when the instance does not exist" do @instance = provider_class.new(:device, :ensure => :absent) expect(@instance.exists?).to be_falsey end describe "is being flushed" do it "should flush properties" do @instance = provider_class.new(:ensure => :present, :name => "200", :description => "myvlan") @instance.flush expect(@instance.properties).to be_empty end end describe "is being created" do before do @rclass = mock 'resource_class' @rclass.stubs(:validproperties).returns([:description]) @resource = stub_everything 'resource' @resource.stubs(:class).returns @rclass @resource.stubs(:should).returns nil @instance.stubs(:resource).returns @resource end it "should set its :ensure value to :present" do @instance.create expect(@instance.properties[:ensure]).to eq(:present) end it "should set all of the other attributes from the resource" do @resource.expects(:should).with(:description).returns "myvlan" @instance.create expect(@instance.properties[:description]).to eq("myvlan") end end describe "is being destroyed" do it "should set its :ensure value to :absent" do @instance.destroy expect(@instance.properties[:ensure]).to eq(:absent) end end end end puppet-5.5.10/spec/unit/provider/scheduled_task/0000755005276200011600000000000013417162177021545 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/provider/scheduled_task/win32_taskscheduler_spec.rb0000644005276200011600000022671413417161722026776 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/windows/taskscheduler' if Puppet.features.microsoft_windows? shared_examples_for "a trigger that handles start_date and start_time" do let(:trigger) do described_class.new( :name => 'Shared Test Task', :command => 'C:\Windows\System32\notepad.exe' ).translate_hash_to_trigger(trigger_hash) end before :each do Win32::TaskScheduler.any_instance.stubs(:save) end describe 'the given start_date' do before :each do trigger_hash['start_time'] = '00:00' end def date_component { 'start_year' => trigger['start_year'], 'start_month' => trigger['start_month'], 'start_day' => trigger['start_day'] } end it 'should be able to be specified in ISO 8601 calendar date format' do trigger_hash['start_date'] = '2011-12-31' expect(date_component).to eq({ 'start_year' => 2011, 'start_month' => 12, 'start_day' => 31 }) end it 'should fail if before 1753-01-01' do trigger_hash['start_date'] = '1752-12-31' expect { date_component }.to raise_error( Puppet::Error, 'start_date must be on or after 1753-01-01' ) end it 'should succeed if on 1753-01-01' do trigger_hash['start_date'] = '1753-01-01' expect(date_component).to eq({ 'start_year' => 1753, 'start_month' => 1, 'start_day' => 1 }) end it 'should succeed if after 1753-01-01' do trigger_hash['start_date'] = '1753-01-02' expect(date_component).to eq({ 'start_year' => 1753, 'start_month' => 1, 'start_day' => 2 }) end end describe 'the given start_time' do before :each do trigger_hash['start_date'] = '2011-12-31' end def time_component { 'start_hour' => trigger['start_hour'], 'start_minute' => trigger['start_minute'] } end it 'should be able to be specified as a 24-hour "hh:mm"' do trigger_hash['start_time'] = '17:13' expect(time_component).to eq({ 'start_hour' => 17, 'start_minute' => 13 }) end it 'should be able to be specified as a 12-hour "hh:mm am"' do trigger_hash['start_time'] = '3:13 am' expect(time_component).to eq({ 'start_hour' => 3, 'start_minute' => 13 }) end it 'should be able to be specified as a 12-hour "hh:mm pm"' do trigger_hash['start_time'] = '3:13 pm' expect(time_component).to eq({ 'start_hour' => 15, 'start_minute' => 13 }) end end end describe Puppet::Type.type(:scheduled_task).provider(:win32_taskscheduler), :if => Puppet.features.microsoft_windows? do before :each do Puppet::Type.type(:scheduled_task).stubs(:defaultprovider).returns(described_class) end describe 'when retrieving' do before :each do @mock_task = stub @mock_task.responds_like(Win32::TaskScheduler.new) described_class.any_instance.stubs(:task).returns(@mock_task) Win32::TaskScheduler.stubs(:new).returns(@mock_task) end let(:resource) { Puppet::Type.type(:scheduled_task).new(:name => 'Test Task', :command => 'C:\Windows\System32\notepad.exe') } describe 'the triggers for a task' do describe 'with only one trigger' do before :each do @mock_task.expects(:trigger_count).returns(1) end it 'should handle a single daily trigger' do @mock_task.expects(:trigger).with(0).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_DAILY, 'start_year' => 2011, 'start_month' => 9, 'start_day' => 12, 'start_hour' => 13, 'start_minute' => 20, 'flags' => 0, 'type' => { 'days_interval' => 2 }, }) expect(resource.provider.trigger).to eq([{ 'start_date' => '2011-9-12', 'start_time' => '13:20', 'schedule' => 'daily', 'every' => '2', 'minutes_interval' => 0, 'minutes_duration' => 0, 'enabled' => true, 'index' => 0, }]) end it 'should handle a single daily with repeat trigger' do @mock_task.expects(:trigger).with(0).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_DAILY, 'start_year' => 2011, 'start_month' => 9, 'start_day' => 12, 'start_hour' => 13, 'start_minute' => 20, 'minutes_interval' => 60, 'minutes_duration' => 180, 'flags' => 0, 'type' => { 'days_interval' => 2 }, }) expect(resource.provider.trigger).to eq([{ 'start_date' => '2011-9-12', 'start_time' => '13:20', 'schedule' => 'daily', 'every' => '2', 'minutes_interval' => 60, 'minutes_duration' => 180, 'enabled' => true, 'index' => 0, }]) end it 'should handle a single weekly trigger' do scheduled_days_of_week = Win32::TaskScheduler::MONDAY | Win32::TaskScheduler::WEDNESDAY | Win32::TaskScheduler::FRIDAY | Win32::TaskScheduler::SUNDAY @mock_task.expects(:trigger).with(0).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_WEEKLY, 'start_year' => 2011, 'start_month' => 9, 'start_day' => 12, 'start_hour' => 13, 'start_minute' => 20, 'flags' => 0, 'type' => { 'weeks_interval' => 2, 'days_of_week' => scheduled_days_of_week } }) expect(resource.provider.trigger).to eq([{ 'start_date' => '2011-9-12', 'start_time' => '13:20', 'schedule' => 'weekly', 'every' => '2', 'day_of_week' => ['sun', 'mon', 'wed', 'fri'], 'minutes_interval' => 0, 'minutes_duration' => 0, 'enabled' => true, 'index' => 0, }]) end it 'should handle a single monthly date-based trigger' do scheduled_months = Win32::TaskScheduler::JANUARY | Win32::TaskScheduler::FEBRUARY | Win32::TaskScheduler::AUGUST | Win32::TaskScheduler::SEPTEMBER | Win32::TaskScheduler::DECEMBER # 1 3 5 15 'last' scheduled_days = 1 | 1 << 2 | 1 << 4 | 1 << 14 | 1 << 31 @mock_task.expects(:trigger).with(0).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_MONTHLYDATE, 'start_year' => 2011, 'start_month' => 9, 'start_day' => 12, 'start_hour' => 13, 'start_minute' => 20, 'flags' => 0, 'type' => { 'months' => scheduled_months, 'days' => scheduled_days } }) expect(resource.provider.trigger).to eq([{ 'start_date' => '2011-9-12', 'start_time' => '13:20', 'schedule' => 'monthly', 'months' => [1, 2, 8, 9, 12], 'on' => [1, 3, 5, 15, 'last'], 'minutes_interval' => 0, 'minutes_duration' => 0, 'enabled' => true, 'index' => 0, }]) end it 'should handle a single monthly day-of-week-based trigger' do scheduled_months = Win32::TaskScheduler::JANUARY | Win32::TaskScheduler::FEBRUARY | Win32::TaskScheduler::AUGUST | Win32::TaskScheduler::SEPTEMBER | Win32::TaskScheduler::DECEMBER scheduled_days_of_week = Win32::TaskScheduler::MONDAY | Win32::TaskScheduler::WEDNESDAY | Win32::TaskScheduler::FRIDAY | Win32::TaskScheduler::SUNDAY @mock_task.expects(:trigger).with(0).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_MONTHLYDOW, 'start_year' => 2011, 'start_month' => 9, 'start_day' => 12, 'start_hour' => 13, 'start_minute' => 20, 'flags' => 0, 'type' => { 'months' => scheduled_months, 'weeks' => Win32::TaskScheduler::FIRST_WEEK, 'days_of_week' => scheduled_days_of_week } }) expect(resource.provider.trigger).to eq([{ 'start_date' => '2011-9-12', 'start_time' => '13:20', 'schedule' => 'monthly', 'months' => [1, 2, 8, 9, 12], 'which_occurrence' => 'first', 'day_of_week' => ['sun', 'mon', 'wed', 'fri'], 'minutes_interval' => 0, 'minutes_duration' => 0, 'enabled' => true, 'index' => 0, }]) end it 'should handle a single one-time trigger' do @mock_task.expects(:trigger).with(0).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2011, 'start_month' => 9, 'start_day' => 12, 'start_hour' => 13, 'start_minute' => 20, 'flags' => 0, }) expect(resource.provider.trigger).to eq([{ 'start_date' => '2011-9-12', 'start_time' => '13:20', 'schedule' => 'once', 'minutes_interval' => 0, 'minutes_duration' => 0, 'enabled' => true, 'index' => 0, }]) end end it 'should handle multiple triggers' do @mock_task.expects(:trigger_count).returns(3) @mock_task.expects(:trigger).with(0).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2011, 'start_month' => 10, 'start_day' => 13, 'start_hour' => 14, 'start_minute' => 21, 'flags' => 0, }) @mock_task.expects(:trigger).with(1).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2012, 'start_month' => 11, 'start_day' => 14, 'start_hour' => 15, 'start_minute' => 22, 'flags' => 0, }) @mock_task.expects(:trigger).with(2).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2013, 'start_month' => 12, 'start_day' => 15, 'start_hour' => 16, 'start_minute' => 23, 'flags' => 0, }) expect(resource.provider.trigger).to match_array([ { 'start_date' => '2011-10-13', 'start_time' => '14:21', 'schedule' => 'once', 'minutes_interval' => 0, 'minutes_duration' => 0, 'enabled' => true, 'index' => 0, }, { 'start_date' => '2012-11-14', 'start_time' => '15:22', 'schedule' => 'once', 'minutes_interval' => 0, 'minutes_duration' => 0, 'enabled' => true, 'index' => 1, }, { 'start_date' => '2013-12-15', 'start_time' => '16:23', 'schedule' => 'once', 'minutes_interval' => 0, 'minutes_duration' => 0, 'enabled' => true, 'index' => 2, } ]) end it 'should handle multiple triggers with repeat triggers' do @mock_task.expects(:trigger_count).returns(3) @mock_task.expects(:trigger).with(0).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2011, 'start_month' => 10, 'start_day' => 13, 'start_hour' => 14, 'start_minute' => 21, 'minutes_interval' => 15, 'minutes_duration' => 60, 'flags' => 0, }) @mock_task.expects(:trigger).with(1).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2012, 'start_month' => 11, 'start_day' => 14, 'start_hour' => 15, 'start_minute' => 22, 'minutes_interval' => 30, 'minutes_duration' => 120, 'flags' => 0, }) @mock_task.expects(:trigger).with(2).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2013, 'start_month' => 12, 'start_day' => 15, 'start_hour' => 16, 'start_minute' => 23, 'minutes_interval' => 60, 'minutes_duration' => 240, 'flags' => 0, }) expect(resource.provider.trigger).to match_array([ { 'start_date' => '2011-10-13', 'start_time' => '14:21', 'schedule' => 'once', 'minutes_interval' => 15, 'minutes_duration' => 60, 'enabled' => true, 'index' => 0, }, { 'start_date' => '2012-11-14', 'start_time' => '15:22', 'schedule' => 'once', 'minutes_interval' => 30, 'minutes_duration' => 120, 'enabled' => true, 'index' => 1, }, { 'start_date' => '2013-12-15', 'start_time' => '16:23', 'schedule' => 'once', 'minutes_interval' => 60, 'minutes_duration' => 240, 'enabled' => true, 'index' => 2, } ]) end it 'should skip triggers Win32::TaskScheduler cannot handle' do @mock_task.expects(:trigger_count).returns(3) @mock_task.expects(:trigger).with(0).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2011, 'start_month' => 10, 'start_day' => 13, 'start_hour' => 14, 'start_minute' => 21, 'flags' => 0, }) @mock_task.expects(:trigger).with(1).raises( Win32::TaskScheduler::Error.new('Unhandled trigger type!') ) @mock_task.expects(:trigger).with(2).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2013, 'start_month' => 12, 'start_day' => 15, 'start_hour' => 16, 'start_minute' => 23, 'flags' => 0, }) expect(resource.provider.trigger).to match_array([ { 'start_date' => '2011-10-13', 'start_time' => '14:21', 'schedule' => 'once', 'minutes_interval' => 0, 'minutes_duration' => 0, 'enabled' => true, 'index' => 0, }, { 'start_date' => '2013-12-15', 'start_time' => '16:23', 'schedule' => 'once', 'minutes_interval' => 0, 'minutes_duration' => 0, 'enabled' => true, 'index' => 2, } ]) end it 'should skip trigger types Puppet does not handle' do @mock_task.expects(:trigger_count).returns(3) @mock_task.expects(:trigger).with(0).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2011, 'start_month' => 10, 'start_day' => 13, 'start_hour' => 14, 'start_minute' => 21, 'flags' => 0, }) @mock_task.expects(:trigger).with(1).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_EVENT_TRIGGER_AT_LOGON, }) @mock_task.expects(:trigger).with(2).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2013, 'start_month' => 12, 'start_day' => 15, 'start_hour' => 16, 'start_minute' => 23, 'flags' => 0, }) expect(resource.provider.trigger).to match_array([ { 'start_date' => '2011-10-13', 'start_time' => '14:21', 'schedule' => 'once', 'minutes_interval' => 0, 'minutes_duration' => 0, 'enabled' => true, 'index' => 0, }, { 'start_date' => '2013-12-15', 'start_time' => '16:23', 'schedule' => 'once', 'minutes_interval' => 0, 'minutes_duration' => 0, 'enabled' => true, 'index' => 2, } ]) end end it 'should get the working directory from the working_directory on the task' do @mock_task.expects(:working_directory).returns('C:\Windows\System32') expect(resource.provider.working_dir).to eq('C:\Windows\System32') end it 'should get the command from the application_name on the task' do @mock_task.expects(:application_name).returns('C:\Windows\System32\notepad.exe') expect(resource.provider.command).to eq('C:\Windows\System32\notepad.exe') end it 'should get the command arguments from the parameters on the task' do @mock_task.expects(:parameters).returns('these are my arguments') expect(resource.provider.arguments).to eq('these are my arguments') end it 'should get the user from the account_information on the task' do @mock_task.expects(:account_information).returns('this is my user') expect(resource.provider.user).to eq('this is my user') end describe 'whether the task is enabled' do it 'should report tasks with the disabled bit set as disabled' do @mock_task.stubs(:flags).returns(Win32::TaskScheduler::DISABLED) expect(resource.provider.enabled).to eq(:false) end it 'should report tasks without the disabled bit set as enabled' do @mock_task.stubs(:flags).returns(~Win32::TaskScheduler::DISABLED) expect(resource.provider.enabled).to eq(:true) end it 'should not consider triggers for determining if the task is enabled' do @mock_task.stubs(:flags).returns(~Win32::TaskScheduler::DISABLED) @mock_task.stubs(:trigger_count).returns(1) @mock_task.stubs(:trigger).with(0).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2011, 'start_month' => 10, 'start_day' => 13, 'start_hour' => 14, 'start_minute' => 21, 'flags' => Win32::TaskScheduler::TASK_TRIGGER_FLAG_DISABLED, }) expect(resource.provider.enabled).to eq(:true) end end end describe '#exists?' do before :each do @mock_task = stub @mock_task.responds_like(Win32::TaskScheduler.new) described_class.any_instance.stubs(:task).returns(@mock_task) Win32::TaskScheduler.stubs(:new).returns(@mock_task) end let(:resource) { Puppet::Type.type(:scheduled_task).new(:name => 'Test Task', :command => 'C:\Windows\System32\notepad.exe') } it "should delegate to Win32::TaskScheduler using the resource's name" do @mock_task.expects(:exists?).with('Test Task').returns(true) expect(resource.provider.exists?).to eq(true) end end describe '#clear_task' do before :each do @mock_task = stub @new_mock_task = stub @mock_task.responds_like(Win32::TaskScheduler.new) @new_mock_task.responds_like(Win32::TaskScheduler.new) Win32::TaskScheduler.stubs(:new).returns(@mock_task, @new_mock_task) described_class.any_instance.stubs(:exists?).returns(false) end let(:resource) { Puppet::Type.type(:scheduled_task).new(:name => 'Test Task', :command => 'C:\Windows\System32\notepad.exe') } it 'should clear the cached task object' do expect(resource.provider.task).to eq(@mock_task) expect(resource.provider.task).to eq(@mock_task) resource.provider.clear_task expect(resource.provider.task).to eq(@new_mock_task) end it 'should clear the cached list of triggers for the task' do @mock_task.stubs(:trigger_count).returns(1) @mock_task.stubs(:trigger).with(0).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2011, 'start_month' => 10, 'start_day' => 13, 'start_hour' => 14, 'start_minute' => 21, 'flags' => 0, }) @new_mock_task.stubs(:trigger_count).returns(1) @new_mock_task.stubs(:trigger).with(0).returns({ 'trigger_type' => Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE, 'start_year' => 2012, 'start_month' => 11, 'start_day' => 14, 'start_hour' => 15, 'start_minute' => 22, 'flags' => 0, }) mock_task_trigger = { 'start_date' => '2011-10-13', 'start_time' => '14:21', 'schedule' => 'once', 'minutes_interval' => 0, 'minutes_duration' => 0, 'enabled' => true, 'index' => 0, } expect(resource.provider.trigger).to eq([mock_task_trigger]) expect(resource.provider.trigger).to eq([mock_task_trigger]) resource.provider.clear_task expect(resource.provider.trigger).to eq([{ 'start_date' => '2012-11-14', 'start_time' => '15:22', 'schedule' => 'once', 'minutes_interval' => 0, 'minutes_duration' => 0, 'enabled' => true, 'index' => 0, }]) end end describe '.instances' do it 'should use the list of .job files to construct the list of scheduled_tasks' do job_files = ['foo.job', 'bar.job', 'baz.job'] Win32::TaskScheduler.any_instance.stubs(:tasks).returns(job_files) job_files.each do |job| job = File.basename(job, '.job') described_class.expects(:new).with(:provider => :win32_taskscheduler, :name => job) end described_class.instances end end describe '#user_insync?', :if => Puppet.features.microsoft_windows? do let(:resource) { described_class.new(:name => 'foobar', :command => 'C:\Windows\System32\notepad.exe') } it 'should consider the user as in sync if the name matches' do Puppet::Util::Windows::SID.expects(:name_to_sid).with('joe').twice.returns('SID A') expect(resource).to be_user_insync('joe', ['joe']) end it 'should consider the user as in sync if the current user is fully qualified' do Puppet::Util::Windows::SID.expects(:name_to_sid).with('joe').returns('SID A') Puppet::Util::Windows::SID.expects(:name_to_sid).with('MACHINE\joe').returns('SID A') expect(resource).to be_user_insync('MACHINE\joe', ['joe']) end it 'should consider a current user of the empty string to be the same as the system user' do Puppet::Util::Windows::SID.expects(:name_to_sid).with('system').twice.returns('SYSTEM SID') expect(resource).to be_user_insync('', ['system']) end it 'should consider different users as being different' do Puppet::Util::Windows::SID.expects(:name_to_sid).with('joe').returns('SID A') Puppet::Util::Windows::SID.expects(:name_to_sid).with('bob').returns('SID B') expect(resource).not_to be_user_insync('joe', ['bob']) end end describe '#trigger_insync?' do let(:resource) { described_class.new(:name => 'foobar', :command => 'C:\Windows\System32\notepad.exe') } it 'should not consider any extra current triggers as in sync' do current = [ {'start_date' => '2011-09-12', 'start_time' => '15:15', 'schedule' => 'once'}, {'start_date' => '2012-10-13', 'start_time' => '16:16', 'schedule' => 'once'} ] desired = {'start_date' => '2011-09-12', 'start_time' => '15:15', 'schedule' => 'once'} expect(resource).not_to be_trigger_insync(current, desired) end it 'should not consider any extra desired triggers as in sync' do current = {'start_date' => '2011-09-12', 'start_time' => '15:15', 'schedule' => 'once'} desired = [ {'start_date' => '2011-09-12', 'start_time' => '15:15', 'schedule' => 'once'}, {'start_date' => '2012-10-13', 'start_time' => '16:16', 'schedule' => 'once'} ] expect(resource).not_to be_trigger_insync(current, desired) end it 'should consider triggers to be in sync if the sets of current and desired triggers are equal' do current = [ {'start_date' => '2011-09-12', 'start_time' => '15:15', 'schedule' => 'once'}, {'start_date' => '2012-10-13', 'start_time' => '16:16', 'schedule' => 'once'} ] desired = [ {'start_date' => '2011-09-12', 'start_time' => '15:15', 'schedule' => 'once'}, {'start_date' => '2012-10-13', 'start_time' => '16:16', 'schedule' => 'once'} ] expect(resource).to be_trigger_insync(current, desired) end end describe '#triggers_same?' do let(:provider) { described_class.new(:name => 'foobar', :command => 'C:\Windows\System32\notepad.exe') } it "should not mutate triggers" do current = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} current.freeze desired = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30'} desired.freeze expect(provider).to be_triggers_same(current, desired) end it "ignores 'index' in current trigger" do current = {'index' => 0, 'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} desired = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} expect(provider).to be_triggers_same(current, desired) end it "ignores 'enabled' in current triggger" do current = {'enabled' => true, 'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} desired = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} expect(provider).to be_triggers_same(current, desired) end it "should not consider a disabled 'current' trigger to be the same" do current = {'schedule' => 'once', 'enabled' => false} desired = {'schedule' => 'once'} expect(provider).not_to be_triggers_same(current, desired) end it 'should not consider triggers with different schedules to be the same' do current = {'schedule' => 'once'} desired = {'schedule' => 'weekly'} expect(provider).not_to be_triggers_same(current, desired) end describe 'start_date' do it "considers triggers to be equal when start_date is not specified in the 'desired' trigger" do current = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} desired = {'schedule' => 'daily', 'start_time' => '15:30', 'every' => 3} expect(provider).to be_triggers_same(current, desired) end end describe 'comparing daily triggers' do it "should consider 'desired' triggers not specifying 'every' to have the same value as the 'current' trigger" do current = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} desired = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30'} expect(provider).to be_triggers_same(current, desired) end it "should consider different 'start_dates' as different triggers" do current = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} desired = {'schedule' => 'daily', 'start_date' => '2012-09-12', 'start_time' => '15:30', 'every' => 3} expect(provider).not_to be_triggers_same(current, desired) end it "should consider different 'start_times' as different triggers" do current = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} desired = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:31', 'every' => 3} expect(provider).not_to be_triggers_same(current, desired) end it 'should not consider differences in date formatting to be different triggers' do current = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} desired = {'schedule' => 'weekly', 'start_date' => '2011-9-12', 'start_time' => '15:30', 'every' => 3} expect(provider).to be_triggers_same(current, desired) end it 'should not consider differences in time formatting to be different triggers' do current = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '5:30', 'every' => 3} desired = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '05:30', 'every' => 3} expect(provider).to be_triggers_same(current, desired) end it "should consider different 'every' as different triggers" do current = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} desired = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 1} expect(provider).not_to be_triggers_same(current, desired) end it 'should consider triggers that are the same as being the same' do trigger = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '01:30', 'every' => 1} expect(provider).to be_triggers_same(trigger, trigger) end end describe 'comparing one-time triggers' do it "should consider different 'start_dates' as different triggers" do current = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30'} desired = {'schedule' => 'daily', 'start_date' => '2012-09-12', 'start_time' => '15:30'} expect(provider).not_to be_triggers_same(current, desired) end it "should consider different 'start_times' as different triggers" do current = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30'} desired = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:31'} expect(provider).not_to be_triggers_same(current, desired) end it 'should not consider differences in date formatting to be different triggers' do current = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30'} desired = {'schedule' => 'weekly', 'start_date' => '2011-9-12', 'start_time' => '15:30'} expect(provider).to be_triggers_same(current, desired) end it 'should not consider differences in time formatting to be different triggers' do current = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '1:30'} desired = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '01:30'} expect(provider).to be_triggers_same(current, desired) end it 'should consider triggers that are the same as being the same' do trigger = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '01:30'} expect(provider).to be_triggers_same(trigger, trigger) end end describe 'comparing monthly date-based triggers' do it "should consider 'desired' triggers not specifying 'months' to have the same value as the 'current' trigger" do current = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [3], 'on' => [1,'last']} desired = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'on' => [1, 'last']} expect(provider).to be_triggers_same(current, desired) end it "should consider different 'start_dates' as different triggers" do current = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [1, 2], 'on' => [1, 3, 5, 7]} desired = {'schedule' => 'monthly', 'start_date' => '2011-10-12', 'start_time' => '15:30', 'months' => [1, 2], 'on' => [1, 3, 5, 7]} expect(provider).not_to be_triggers_same(current, desired) end it "should consider different 'start_times' as different triggers" do current = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [1, 2], 'on' => [1, 3, 5, 7]} desired = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '22:30', 'months' => [1, 2], 'on' => [1, 3, 5, 7]} expect(provider).not_to be_triggers_same(current, desired) end it 'should not consider differences in date formatting to be different triggers' do current = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [1, 2], 'on' => [1, 3, 5, 7]} desired = {'schedule' => 'monthly', 'start_date' => '2011-9-12', 'start_time' => '15:30', 'months' => [1, 2], 'on' => [1, 3, 5, 7]} expect(provider).to be_triggers_same(current, desired) end it 'should not consider differences in time formatting to be different triggers' do current = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '5:30', 'months' => [1, 2], 'on' => [1, 3, 5, 7]} desired = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '05:30', 'months' => [1, 2], 'on' => [1, 3, 5, 7]} expect(provider).to be_triggers_same(current, desired) end it "should consider different 'months' as different triggers" do current = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [1, 2], 'on' => [1, 3, 5, 7]} desired = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [1], 'on' => [1, 3, 5, 7]} expect(provider).not_to be_triggers_same(current, desired) end it "should consider different 'on' as different triggers" do current = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [1, 2], 'on' => [1, 3, 5, 7]} desired = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [1, 2], 'on' => [1, 5, 7]} expect(provider).not_to be_triggers_same(current, desired) end it 'should consider triggers that are the same as being the same' do trigger = {'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [1, 2], 'on' => [1, 3, 5, 7]} expect(provider).to be_triggers_same(trigger, trigger) end end describe 'comparing monthly day-of-week-based triggers' do it "should consider 'desired' triggers not specifying 'months' to have the same value as the 'current' trigger" do current = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [3], 'which_occurrence' => 'first', 'day_of_week' => ['mon', 'tues', 'sat'] } desired = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'which_occurrence' => 'first', 'day_of_week' => ['mon', 'tues', 'sat'] } expect(provider).to be_triggers_same(current, desired) end it "should consider different 'start_dates' as different triggers" do current = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [3], 'which_occurrence' => 'first', 'day_of_week' => ['mon', 'tues', 'sat'] } desired = { 'schedule' => 'monthly', 'start_date' => '2011-10-12', 'start_time' => '15:30', 'months' => [3], 'which_occurrence' => 'first', 'day_of_week' => ['mon', 'tues', 'sat'] } expect(provider).not_to be_triggers_same(current, desired) end it "should consider different 'start_times' as different triggers" do current = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [3], 'which_occurrence' => 'first', 'day_of_week' => ['mon', 'tues', 'sat'] } desired = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '22:30', 'months' => [3], 'which_occurrence' => 'first', 'day_of_week' => ['mon', 'tues', 'sat'] } expect(provider).not_to be_triggers_same(current, desired) end it "should consider different 'months' as different triggers" do current = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [3], 'which_occurrence' => 'first', 'day_of_week' => ['mon', 'tues', 'sat'] } desired = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [3, 5, 7, 9], 'which_occurrence' => 'first', 'day_of_week' => ['mon', 'tues', 'sat'] } expect(provider).not_to be_triggers_same(current, desired) end it "should consider different 'which_occurrence' as different triggers" do current = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [3], 'which_occurrence' => 'first', 'day_of_week' => ['mon', 'tues', 'sat'] } desired = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [3], 'which_occurrence' => 'last', 'day_of_week' => ['mon', 'tues', 'sat'] } expect(provider).not_to be_triggers_same(current, desired) end it "should consider different 'day_of_week' as different triggers" do current = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [3], 'which_occurrence' => 'first', 'day_of_week' => ['mon', 'tues', 'sat'] } desired = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [3], 'which_occurrence' => 'first', 'day_of_week' => ['fri'] } expect(provider).not_to be_triggers_same(current, desired) end it 'should consider triggers that are the same as being the same' do trigger = { 'schedule' => 'monthly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'months' => [3], 'which_occurrence' => 'first', 'day_of_week' => ['mon', 'tues', 'sat'] } expect(provider).to be_triggers_same(trigger, trigger) end end describe 'comparing weekly triggers' do it "should consider 'desired' triggers not specifying 'day_of_week' to have the same value as the 'current' trigger" do current = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} desired = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} expect(provider).to be_triggers_same(current, desired) end it "should consider different 'start_dates' as different triggers" do current = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} desired = {'schedule' => 'weekly', 'start_date' => '2011-10-12', 'start_time' => '15:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} expect(provider).not_to be_triggers_same(current, desired) end it "should consider different 'start_times' as different triggers" do current = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} desired = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '22:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} expect(provider).not_to be_triggers_same(current, desired) end it 'should not consider differences in date formatting to be different triggers' do current = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} desired = {'schedule' => 'weekly', 'start_date' => '2011-9-12', 'start_time' => '15:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} expect(provider).to be_triggers_same(current, desired) end it 'should not consider differences in time formatting to be different triggers' do current = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '1:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} desired = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '01:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} expect(provider).to be_triggers_same(current, desired) end it "should consider different 'every' as different triggers" do current = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 1, 'day_of_week' => ['mon', 'wed', 'fri']} desired = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} expect(provider).not_to be_triggers_same(current, desired) end it "should consider different 'day_of_week' as different triggers" do current = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} desired = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3, 'day_of_week' => ['fri']} expect(provider).not_to be_triggers_same(current, desired) end it 'should consider triggers that are the same as being the same' do trigger = {'schedule' => 'weekly', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3, 'day_of_week' => ['mon', 'wed', 'fri']} expect(provider).to be_triggers_same(trigger, trigger) end end end describe '#normalized_date' do it 'should format the date without leading zeros' do expect(described_class.normalized_date('2011-01-01')).to eq('2011-1-1') end end describe '#normalized_time' do it 'should format the time as {24h}:{minutes}' do expect(described_class.normalized_time('8:37 PM')).to eq('20:37') end end describe '#translate_hash_to_trigger' do before :each do @puppet_trigger = { 'start_date' => '2011-1-1', 'start_time' => '01:10' } end let(:provider) { described_class.new(:name => 'Test Task', :command => 'C:\Windows\System32\notepad.exe') } let(:trigger) { provider.translate_hash_to_trigger(@puppet_trigger) } context "working with repeat every x triggers" do before :each do @puppet_trigger['schedule'] = 'once' end it 'should succeed if minutes_interval is equal to 0' do @puppet_trigger['minutes_interval'] = '0' expect(trigger['minutes_interval']).to eq(0) end it 'should default minutes_duration to a full day when minutes_interval is greater than 0 without setting minutes_duration' do @puppet_trigger['minutes_interval'] = '1' expect(trigger['minutes_duration']).to eq(1440) end it 'should succeed if minutes_interval is greater than 0 and minutes_duration is also set' do @puppet_trigger['minutes_interval'] = '1' @puppet_trigger['minutes_duration'] = '2' expect(trigger['minutes_interval']).to eq(1) end it 'should fail if minutes_interval is less than 0' do @puppet_trigger['minutes_interval'] = '-1' expect { trigger }.to raise_error( Puppet::Error, 'minutes_interval must be an integer greater or equal to 0' ) end it 'should fail if minutes_interval is not an integer' do @puppet_trigger['minutes_interval'] = 'abc' expect { trigger }.to raise_error(ArgumentError) end it 'should succeed if minutes_duration is equal to 0' do @puppet_trigger['minutes_duration'] = '0' expect(trigger['minutes_duration']).to eq(0) end it 'should succeed if minutes_duration is greater than 0' do @puppet_trigger['minutes_duration'] = '1' expect(trigger['minutes_duration']).to eq(1) end it 'should fail if minutes_duration is less than 0' do @puppet_trigger['minutes_duration'] = '-1' expect { trigger }.to raise_error( Puppet::Error, 'minutes_duration must be an integer greater than minutes_interval and equal to or greater than 0' ) end it 'should fail if minutes_duration is not an integer' do @puppet_trigger['minutes_duration'] = 'abc' expect { trigger }.to raise_error(ArgumentError) end it 'should succeed if minutes_duration is equal to a full day' do @puppet_trigger['minutes_duration'] = '1440' expect(trigger['minutes_duration']).to eq(1440) end it 'should succeed if minutes_duration is equal to three days' do @puppet_trigger['minutes_duration'] = '4320' expect(trigger['minutes_duration']).to eq(4320) end it 'should succeed if minutes_duration is greater than minutes_duration' do @puppet_trigger['minutes_interval'] = '10' @puppet_trigger['minutes_duration'] = '11' expect(trigger['minutes_interval']).to eq(10) expect(trigger['minutes_duration']).to eq(11) end it 'should fail if minutes_duration is equal to minutes_interval' do # On Windows 2003, the duration must be greater than the interval # on other platforms the values can be equal. @puppet_trigger['minutes_interval'] = '10' @puppet_trigger['minutes_duration'] = '10' expect { trigger }.to raise_error( Puppet::Error, 'minutes_duration must be an integer greater than minutes_interval and equal to or greater than 0' ) end it 'should succeed if minutes_duration and minutes_interval are both set to 0' do @puppet_trigger['minutes_interval'] = '0' @puppet_trigger['minutes_duration'] = '0' expect(trigger['minutes_interval']).to eq(0) expect(trigger['minutes_duration']).to eq(0) end it 'should fail if minutes_duration is less than minutes_interval' do @puppet_trigger['minutes_interval'] = '10' @puppet_trigger['minutes_duration'] = '9' expect { trigger }.to raise_error( Puppet::Error, 'minutes_duration must be an integer greater than minutes_interval and equal to or greater than 0' ) end it 'should fail if minutes_duration is less than minutes_interval and set to 0' do @puppet_trigger['minutes_interval'] = '10' @puppet_trigger['minutes_duration'] = '0' expect { trigger }.to raise_error( Puppet::Error, 'minutes_interval cannot be set without minutes_duration also being set to a number greater than 0' ) end end describe 'when given a one-time trigger' do before :each do @puppet_trigger['schedule'] = 'once' end it 'should set the trigger_type to Win32::TaskScheduler::ONCE' do expect(trigger['trigger_type']).to eq(Win32::TaskScheduler::ONCE) end it 'should not set a type' do expect(trigger).not_to be_has_key('type') end it "should require 'start_date'" do @puppet_trigger.delete('start_date') expect { trigger }.to raise_error( Puppet::Error, /Must specify 'start_date' when defining a one-time trigger/ ) end it "should require 'start_time'" do @puppet_trigger.delete('start_time') expect { trigger }.to raise_error( Puppet::Error, /Must specify 'start_time' when defining a trigger/ ) end it_behaves_like "a trigger that handles start_date and start_time" do let(:trigger_hash) {{'schedule' => 'once' }} end end describe 'when given a daily trigger' do before :each do @puppet_trigger['schedule'] = 'daily' end it "should default 'every' to 1" do expect(trigger['type']['days_interval']).to eq(1) end it "should use the specified value for 'every'" do @puppet_trigger['every'] = 5 expect(trigger['type']['days_interval']).to eq(5) end it "should default 'start_date' to 'today'" do @puppet_trigger.delete('start_date') today = Time.now expect(trigger['start_year']).to eq(today.year) expect(trigger['start_month']).to eq(today.month) expect(trigger['start_day']).to eq(today.day) end it_behaves_like "a trigger that handles start_date and start_time" do let(:trigger_hash) {{'schedule' => 'daily', 'every' => 1}} end end describe 'when given a weekly trigger' do before :each do @puppet_trigger['schedule'] = 'weekly' end it "should default 'every' to 1" do expect(trigger['type']['weeks_interval']).to eq(1) end it "should use the specified value for 'every'" do @puppet_trigger['every'] = 4 expect(trigger['type']['weeks_interval']).to eq(4) end it "should default 'day_of_week' to be every day of the week" do expect(trigger['type']['days_of_week']).to eq(Win32::TaskScheduler::MONDAY | Win32::TaskScheduler::TUESDAY | Win32::TaskScheduler::WEDNESDAY | Win32::TaskScheduler::THURSDAY | Win32::TaskScheduler::FRIDAY | Win32::TaskScheduler::SATURDAY | Win32::TaskScheduler::SUNDAY) end it "should use the specified value for 'day_of_week'" do @puppet_trigger['day_of_week'] = ['mon', 'wed', 'fri'] expect(trigger['type']['days_of_week']).to eq(Win32::TaskScheduler::MONDAY | Win32::TaskScheduler::WEDNESDAY | Win32::TaskScheduler::FRIDAY) end it "should default 'start_date' to 'today'" do @puppet_trigger.delete('start_date') today = Time.now expect(trigger['start_year']).to eq(today.year) expect(trigger['start_month']).to eq(today.month) expect(trigger['start_day']).to eq(today.day) end it_behaves_like "a trigger that handles start_date and start_time" do let(:trigger_hash) {{'schedule' => 'weekly', 'every' => 1, 'day_of_week' => 'mon'}} end end shared_examples_for 'a monthly schedule' do it "should default 'months' to be every month" do expect(trigger['type']['months']).to eq(Win32::TaskScheduler::JANUARY | Win32::TaskScheduler::FEBRUARY | Win32::TaskScheduler::MARCH | Win32::TaskScheduler::APRIL | Win32::TaskScheduler::MAY | Win32::TaskScheduler::JUNE | Win32::TaskScheduler::JULY | Win32::TaskScheduler::AUGUST | Win32::TaskScheduler::SEPTEMBER | Win32::TaskScheduler::OCTOBER | Win32::TaskScheduler::NOVEMBER | Win32::TaskScheduler::DECEMBER) end it "should use the specified value for 'months'" do @puppet_trigger['months'] = [2, 8] expect(trigger['type']['months']).to eq(Win32::TaskScheduler::FEBRUARY | Win32::TaskScheduler::AUGUST) end end describe 'when given a monthly date-based trigger' do before :each do @puppet_trigger['schedule'] = 'monthly' @puppet_trigger['on'] = [7, 14] end it_behaves_like 'a monthly schedule' it "should not allow 'which_occurrence' to be specified" do @puppet_trigger['which_occurrence'] = 'first' expect {trigger}.to raise_error( Puppet::Error, /Neither 'day_of_week' nor 'which_occurrence' can be specified when creating a monthly date-based trigger/ ) end it "should not allow 'day_of_week' to be specified" do @puppet_trigger['day_of_week'] = 'mon' expect {trigger}.to raise_error( Puppet::Error, /Neither 'day_of_week' nor 'which_occurrence' can be specified when creating a monthly date-based trigger/ ) end it "should require 'on'" do @puppet_trigger.delete('on') expect {trigger}.to raise_error( Puppet::Error, /Don't know how to create a 'monthly' schedule with the options: schedule, start_date, start_time/ ) end it "should default 'start_date' to 'today'" do @puppet_trigger.delete('start_date') today = Time.now expect(trigger['start_year']).to eq(today.year) expect(trigger['start_month']).to eq(today.month) expect(trigger['start_day']).to eq(today.day) end it_behaves_like "a trigger that handles start_date and start_time" do let(:trigger_hash) {{'schedule' => 'monthly', 'months' => 1, 'on' => 1}} end end describe 'when given a monthly day-of-week-based trigger' do before :each do @puppet_trigger['schedule'] = 'monthly' @puppet_trigger['which_occurrence'] = 'first' @puppet_trigger['day_of_week'] = 'mon' end it_behaves_like 'a monthly schedule' it "should not allow 'on' to be specified" do @puppet_trigger['on'] = 15 expect {trigger}.to raise_error( Puppet::Error, /Neither 'day_of_week' nor 'which_occurrence' can be specified when creating a monthly date-based trigger/ ) end it "should require 'which_occurrence'" do @puppet_trigger.delete('which_occurrence') expect {trigger}.to raise_error( Puppet::Error, /which_occurrence must be specified when creating a monthly day-of-week based trigger/ ) end it "should require 'day_of_week'" do @puppet_trigger.delete('day_of_week') expect {trigger}.to raise_error( Puppet::Error, /day_of_week must be specified when creating a monthly day-of-week based trigger/ ) end it "should default 'start_date' to 'today'" do @puppet_trigger.delete('start_date') today = Time.now expect(trigger['start_year']).to eq(today.year) expect(trigger['start_month']).to eq(today.month) expect(trigger['start_day']).to eq(today.day) end it_behaves_like "a trigger that handles start_date and start_time" do let(:trigger_hash) {{'schedule' => 'monthly', 'months' => 1, 'which_occurrence' => 'first', 'day_of_week' => 'mon'}} end end end describe '#validate_trigger' do let(:provider) { described_class.new(:name => 'Test Task', :command => 'C:\Windows\System32\notepad.exe') } it 'should succeed if all passed triggers translate from hashes to triggers' do triggers_to_validate = [ {'schedule' => 'once', 'start_date' => '2011-09-13', 'start_time' => '13:50'}, {'schedule' => 'weekly', 'start_date' => '2011-09-13', 'start_time' => '13:50', 'day_of_week' => 'mon'} ] expect(provider.validate_trigger(triggers_to_validate)).to eq(true) end it 'should use the exception from translate_hash_to_trigger when it fails' do triggers_to_validate = [ {'schedule' => 'once', 'start_date' => '2011-09-13', 'start_time' => '13:50'}, {'schedule' => 'monthly', 'this is invalid' => true} ] expect {provider.validate_trigger(triggers_to_validate)}.to raise_error( Puppet::Error, /#{Regexp.escape("Unknown trigger option(s): ['this is invalid']")}/ ) end end describe '#flush' do let(:resource) do Puppet::Type.type(:scheduled_task).new( :name => 'Test Task', :command => 'C:\Windows\System32\notepad.exe', :ensure => @ensure ) end before :each do @mock_task = stub @mock_task.responds_like(Win32::TaskScheduler.new) @mock_task.stubs(:exists?).returns(true) @mock_task.stubs(:activate) Win32::TaskScheduler.stubs(:new).returns(@mock_task) @command = 'C:\Windows\System32\notepad.exe' end describe 'when :ensure is :present' do before :each do @ensure = :present end it 'should save the task' do @mock_task.expects(:set_account_information).with(nil, nil) @mock_task.expects(:save) resource.provider.flush end it 'should fail if the command is not specified' do resource = Puppet::Type.type(:scheduled_task).new( :name => 'Test Task', :ensure => @ensure ) expect { resource.provider.flush }.to raise_error( Puppet::Error, 'Parameter command is required.' ) end end describe 'when :ensure is :absent' do before :each do @ensure = :absent @mock_task.stubs(:activate) end it 'should not save the task if :ensure is :absent' do @mock_task.expects(:save).never resource.provider.flush end it 'should not fail if the command is not specified' do @mock_task.stubs(:save) resource = Puppet::Type.type(:scheduled_task).new( :name => 'Test Task', :ensure => @ensure ) resource.provider.flush end end end describe 'property setter methods' do let(:resource) do Puppet::Type.type(:scheduled_task).new( :name => 'Test Task', :command => 'C:\dummy_task.exe' ) end before :each do @mock_task = stub @mock_task.responds_like(Win32::TaskScheduler.new) @mock_task.stubs(:exists?).returns(true) @mock_task.stubs(:activate) Win32::TaskScheduler.stubs(:new).returns(@mock_task) end describe '#command=' do it 'should set the application_name on the task' do @mock_task.expects(:application_name=).with('C:\Windows\System32\notepad.exe') resource.provider.command = 'C:\Windows\System32\notepad.exe' end end describe '#arguments=' do it 'should set the parameters on the task' do @mock_task.expects(:parameters=).with(['/some /arguments /here']) resource.provider.arguments = ['/some /arguments /here'] end end describe '#working_dir=' do it 'should set the working_directory on the task' do @mock_task.expects(:working_directory=).with('C:\Windows\System32') resource.provider.working_dir = 'C:\Windows\System32' end end describe '#enabled=' do it 'should set the disabled flag if the task should be disabled' do @mock_task.stubs(:flags).returns(0) @mock_task.expects(:flags=).with(Win32::TaskScheduler::DISABLED) resource.provider.enabled = :false end it 'should clear the disabled flag if the task should be enabled' do @mock_task.stubs(:flags).returns(Win32::TaskScheduler::DISABLED) @mock_task.expects(:flags=).with(0) resource.provider.enabled = :true end end describe '#trigger=' do let(:resource) do Puppet::Type.type(:scheduled_task).new( :name => 'Test Task', :command => 'C:\Windows\System32\notepad.exe', :trigger => @trigger ) end before :each do @mock_task = stub @mock_task.responds_like(Win32::TaskScheduler.new) @mock_task.stubs(:exists?).returns(true) @mock_task.stubs(:activate) Win32::TaskScheduler.stubs(:new).returns(@mock_task) end it 'should not consider all duplicate current triggers in sync with a single desired trigger' do @trigger = {'schedule' => 'once', 'start_date' => '2011-09-15', 'start_time' => '15:10'} current_triggers = [ {'schedule' => 'once', 'start_date' => '2011-09-15', 'start_time' => '15:10', 'index' => 0}, {'schedule' => 'once', 'start_date' => '2011-09-15', 'start_time' => '15:10', 'index' => 1}, {'schedule' => 'once', 'start_date' => '2011-09-15', 'start_time' => '15:10', 'index' => 2}, ] resource.provider.stubs(:trigger).returns(current_triggers) @mock_task.expects(:delete_trigger).with(1) @mock_task.expects(:delete_trigger).with(2) resource.provider.trigger = @trigger end it 'should remove triggers not defined in the resource' do @trigger = {'schedule' => 'once', 'start_date' => '2011-09-15', 'start_time' => '15:10'} current_triggers = [ {'schedule' => 'once', 'start_date' => '2011-09-15', 'start_time' => '15:10', 'index' => 0}, {'schedule' => 'once', 'start_date' => '2012-09-15', 'start_time' => '15:10', 'index' => 1}, {'schedule' => 'once', 'start_date' => '2013-09-15', 'start_time' => '15:10', 'index' => 2}, ] resource.provider.stubs(:trigger).returns(current_triggers) @mock_task.expects(:delete_trigger).with(1) @mock_task.expects(:delete_trigger).with(2) resource.provider.trigger = @trigger end it 'should add triggers defined in the resource, but not found on the system' do @trigger = [ {'schedule' => 'once', 'start_date' => '2011-09-15', 'start_time' => '15:10'}, {'schedule' => 'once', 'start_date' => '2012-09-15', 'start_time' => '15:10'}, {'schedule' => 'once', 'start_date' => '2013-09-15', 'start_time' => '15:10'}, ] current_triggers = [ {'schedule' => 'once', 'start_date' => '2011-09-15', 'start_time' => '15:10', 'index' => 0}, ] resource.provider.stubs(:trigger).returns(current_triggers) @mock_task.expects(:trigger=).with(resource.provider.translate_hash_to_trigger(@trigger[1])) @mock_task.expects(:trigger=).with(resource.provider.translate_hash_to_trigger(@trigger[2])) resource.provider.trigger = @trigger end end describe '#user=', :if => Puppet.features.microsoft_windows? do before :each do @mock_task = stub @mock_task.responds_like(Win32::TaskScheduler.new) @mock_task.stubs(:exists?).returns(true) @mock_task.stubs(:activate) Win32::TaskScheduler.stubs(:new).returns(@mock_task) end it 'should use nil for user and password when setting the user to the SYSTEM account' do Puppet::Util::Windows::SID.stubs(:name_to_sid).with('system').returns('SYSTEM SID') resource = Puppet::Type.type(:scheduled_task).new( :name => 'Test Task', :command => 'C:\dummy_task.exe', :user => 'system' ) @mock_task.expects(:set_account_information).with(nil, nil) resource.provider.user = 'system' end it 'should use the specified user and password when setting the user to anything other than SYSTEM' do Puppet::Util::Windows::SID.stubs(:name_to_sid).with('my_user_name').returns('SID A') resource = Puppet::Type.type(:scheduled_task).new( :name => 'Test Task', :command => 'C:\dummy_task.exe', :user => 'my_user_name', :password => 'my password' ) @mock_task.expects(:set_account_information).with('my_user_name', 'my password') resource.provider.user = 'my_user_name' end end end describe '#create' do let(:resource) do Puppet::Type.type(:scheduled_task).new( :name => 'Test Task', :enabled => @enabled, :command => @command, :arguments => @arguments, :working_dir => @working_dir, :trigger => { 'schedule' => 'once', 'start_date' => '2011-09-27', 'start_time' => '17:00' } ) end before :each do @enabled = :true @command = 'C:\Windows\System32\notepad.exe' @arguments = '/a /list /of /arguments' @working_dir = 'C:\Windows\Some\Directory' @mock_task = stub @mock_task.responds_like(Win32::TaskScheduler.new) @mock_task.stubs(:exists?).returns(true) @mock_task.stubs(:activate) @mock_task.stubs(:application_name=) @mock_task.stubs(:parameters=) @mock_task.stubs(:working_directory=) @mock_task.stubs(:set_account_information) @mock_task.stubs(:flags) @mock_task.stubs(:flags=) @mock_task.stubs(:trigger_count).returns(0) @mock_task.stubs(:trigger=) @mock_task.stubs(:save) Win32::TaskScheduler.stubs(:new).returns(@mock_task) described_class.any_instance.stubs(:sync_triggers) end it 'should set the command' do resource.provider.expects(:command=).with(@command) resource.provider.create end it 'should set the arguments' do resource.provider.expects(:arguments=).with(@arguments) resource.provider.create end it 'should set the working_dir' do resource.provider.expects(:working_dir=).with(@working_dir) resource.provider.create end it "should set the user" do resource.provider.expects(:user=).with(:system) resource.provider.create end it 'should set the enabled property' do resource.provider.expects(:enabled=) resource.provider.create end it 'should sync triggers' do resource.provider.expects(:trigger=) resource.provider.create end end describe "Win32::TaskScheduler", :if => Puppet.features.microsoft_windows? do let(:name) { SecureRandom.uuid } describe 'sets appropriate generic trigger defaults' do before(:each) do @now = Time.now Time.stubs(:now).returns(@now) end it 'for a ONCE schedule' do task = Win32::TaskScheduler.new(name, { 'trigger_type' => Win32::TaskScheduler::ONCE }) expect(task.trigger(0)['start_year']).to eq(@now.year) expect(task.trigger(0)['start_month']).to eq(@now.month) expect(task.trigger(0)['start_day']).to eq(@now.day) end it 'for a DAILY schedule' do trigger = { 'trigger_type' => Win32::TaskScheduler::DAILY, 'type' => { 'days_interval' => 1 } } task = Win32::TaskScheduler.new(name, trigger) expect(task.trigger(0)['start_year']).to eq(@now.year) expect(task.trigger(0)['start_month']).to eq(@now.month) expect(task.trigger(0)['start_day']).to eq(@now.day) end it 'for a WEEKLY schedule' do trigger = { 'trigger_type' => Win32::TaskScheduler::WEEKLY, 'type' => { 'weeks_interval' => 1, 'days_of_week' => 1 } } task = Win32::TaskScheduler.new(name, trigger) expect(task.trigger(0)['start_year']).to eq(@now.year) expect(task.trigger(0)['start_month']).to eq(@now.month) expect(task.trigger(0)['start_day']).to eq(@now.day) end it 'for a MONTHLYDATE schedule' do trigger = { 'trigger_type' => Win32::TaskScheduler::MONTHLYDATE, 'type' => { 'days' => 1, 'months' => 1 } } task = Win32::TaskScheduler.new(name, trigger) expect(task.trigger(0)['start_year']).to eq(@now.year) expect(task.trigger(0)['start_month']).to eq(@now.month) expect(task.trigger(0)['start_day']).to eq(@now.day) end it 'for a MONTHLYDOW schedule' do trigger = { 'trigger_type' => Win32::TaskScheduler::MONTHLYDOW, 'type' => { 'weeks' => 1, 'days_of_week' => 1, 'months' => 1 } } task = Win32::TaskScheduler.new(name, trigger) expect(task.trigger(0)['start_year']).to eq(@now.year) expect(task.trigger(0)['start_month']).to eq(@now.month) expect(task.trigger(0)['start_day']).to eq(@now.day) end end describe 'enforces maximum lengths' do let(:task) { Win32::TaskScheduler.new(name, { 'trigger_type' => Win32::TaskScheduler::ONCE }) } it 'on account user name' do expect { task.set_account_information('a' * (Win32::TaskScheduler::MAX_ACCOUNT_LENGTH + 1), 'pass') }.to raise_error(Puppet::Error) end it 'on application name' do expect { task.application_name = 'a' * (Win32::TaskScheduler::MAX_PATH + 1) }.to raise_error(Puppet::Error) end it 'on parameters' do expect { task.parameters = 'a' * (Win32::TaskScheduler::MAX_PARAMETERS_LENGTH + 1) }.to raise_error(Puppet::Error) end it 'on working directory' do expect { task.working_directory = 'a' * (Win32::TaskScheduler::MAX_PATH + 1) }.to raise_error(Puppet::Error) end it 'on comment' do expect { task.comment = 'a' * (Win32::TaskScheduler::MAX_COMMENT_LENGTH + 1) }.to raise_error(Puppet::Error) end it 'on creator' do expect { task.creator = 'a' * (Win32::TaskScheduler::MAX_ACCOUNT_LENGTH + 1) }.to raise_error(Puppet::Error) end end def delete_task_with_retry(task, name, attempts = 3) failed = false attempts.times do begin task.delete(name) if Win32::TaskScheduler.new.exists?(name) rescue Puppet::Util::Windows::Error failed = true end if failed then sleep 1 else break end end end describe '#exists?' do it 'works with Unicode task names' do task_name = name + "\u16A0\u16C7\u16BB" # ᚠᛇᚻ begin task = Win32::TaskScheduler.new(task_name, { 'trigger_type' => Win32::TaskScheduler::ONCE }) task.save() expect(Puppet::FileSystem.exist?("C:\\Windows\\Tasks\\#{task_name}.job")).to be_truthy expect(task.exists?(task_name)).to be_truthy ensure delete_task_with_retry(task, task_name) end end it 'is case insensitive' do task_name = name + 'abc' # name is a guid, but might not have alpha chars begin task = Win32::TaskScheduler.new(task_name.upcase, { 'trigger_type' => Win32::TaskScheduler::ONCE }) task.save() expect(task.exists?(task_name.downcase)).to be_truthy ensure delete_task_with_retry(task, task_name) end end end describe 'does not corrupt tasks' do it 'when setting maximum length values for all settings' do begin task = Win32::TaskScheduler.new(name, { 'trigger_type' => Win32::TaskScheduler::ONCE }) application_name = 'a' * Win32::TaskScheduler::MAX_PATH parameters = 'b' * Win32::TaskScheduler::MAX_PARAMETERS_LENGTH working_directory = 'c' * Win32::TaskScheduler::MAX_PATH comment = 'd' * Win32::TaskScheduler::MAX_COMMENT_LENGTH creator = 'e' * Win32::TaskScheduler::MAX_ACCOUNT_LENGTH task.application_name = application_name task.parameters = parameters task.working_directory = working_directory task.comment = comment task.creator = creator # saving and reloading (activating) can induce COM load errors when # file is corrupted, which can happen when the upper bounds of these lengths are set too high task.save() task.activate(name) # furthermore, corrupted values may not necessarily be read back properly # note that SYSTEM is always returned as an empty string in account_information expect(task.account_information).to eq('') expect(task.application_name).to eq(application_name) expect(task.parameters).to eq(parameters) expect(task.working_directory).to eq(working_directory) expect(task.comment).to eq(comment) expect(task.creator).to eq(creator) ensure delete_task_with_retry(task, name) end end it 'by preventing a save() not preceded by a set_account_information()' do begin # creates a default new task with SYSTEM user task = Win32::TaskScheduler.new(name, { 'trigger_type' => Win32::TaskScheduler::ONCE }) # save automatically resets the current task task.save() # re-activate named task, try to modify, and save task.activate(name) task.application_name = 'c:/windows/system32/notepad.exe' expect { task.save() }.to raise_error(Puppet::Error, /Account information must be set on the current task to save it properly/) # on a failed save, the current task is still active - add SYSTEM task.set_account_information('', nil) expect(task.save()).to be_instance_of(Win32::TaskScheduler::COM::Task) # the most appropriate additional validation here would be to confirm settings with schtasks.exe # but that test can live inside a system-level acceptance test ensure delete_task_with_retry(task, name) end end end end end puppet-5.5.10/spec/unit/provider/selboolean_spec.rb0000644005276200011600000000227613417161722022247 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' provider_class = Puppet::Type.type(:selboolean).provider(:getsetsebool) describe provider_class do before :each do @resource = stub("resource", :name => "foo") @resource.stubs(:[]).returns "foo" @provider = provider_class.new(@resource) end it "should return :on when getsebool returns on" do @provider.expects(:getsebool).with("foo").returns "foo --> on\n" expect(@provider.value).to eq(:on) end it "should return :off when getsebool returns on" do @provider.expects(:getsebool).with("foo").returns "foo --> off\n" expect(@provider.value).to eq(:off) end it "should call execpipe when updating boolean setting" do @provider.expects(:command).with(:setsebool).returns "/usr/sbin/setsebool" @provider.expects(:execpipe).with("/usr/sbin/setsebool foo off") @provider.value = :off end it "should call execpipe with -P when updating persistent boolean setting" do @resource.stubs(:[]).with(:persistent).returns :true @provider.expects(:command).with(:setsebool).returns "/usr/sbin/setsebool" @provider.expects(:execpipe).with("/usr/sbin/setsebool -P foo off") @provider.value = :off end end puppet-5.5.10/spec/unit/provider/selmodule-example.pp0000644005276200011600000000040113417161722022534 0ustar jenkinsjenkinsŹ˙|ů›NŤ˙|ůSE Linux Modulenagios1.5.0@00$netlink_audit_socket nlmsg_relay append bind connectcreatewrite relabel puppet-5.5.10/spec/unit/provider/selmodule_spec.rb0000644005276200011600000000717713417161722022122 0ustar jenkinsjenkins#! /usr/bin/env ruby # Note: This unit test depends on having a sample SELinux policy file # in the same directory as this test called selmodule-example.pp # with version 1.5.0. The provided selmodule-example.pp is the first # 256 bytes taken from /usr/share/selinux/targeted/nagios.pp on Fedora 9 require 'spec_helper' require 'stringio' provider_class = Puppet::Type.type(:selmodule).provider(:semodule) describe provider_class do before :each do @resource = stub("resource", :name => "foo") @resource.stubs(:[]).returns "foo" @provider = provider_class.new(@resource) end describe "exists? method" do it "should find a module if it is already loaded" do @provider.expects(:command).with(:semodule).returns "/usr/sbin/semodule" @provider.expects(:execpipe).with("/usr/sbin/semodule --list").yields StringIO.new("bar\t1.2.3\nfoo\t4.4.4\nbang\t1.0.0\n") expect(@provider.exists?).to eq(:true) end it "should return nil if not loaded" do @provider.expects(:command).with(:semodule).returns "/usr/sbin/semodule" @provider.expects(:execpipe).with("/usr/sbin/semodule --list").yields StringIO.new("bar\t1.2.3\nbang\t1.0.0\n") expect(@provider.exists?).to be_nil end it "should return nil if module with same suffix is loaded" do @provider.expects(:command).with(:semodule).returns "/usr/sbin/semodule" @provider.expects(:execpipe).with("/usr/sbin/semodule --list").yields StringIO.new("bar\t1.2.3\nmyfoo\t1.0.0\n") expect(@provider.exists?).to be_nil end it "should return nil if no modules are loaded" do @provider.expects(:command).with(:semodule).returns "/usr/sbin/semodule" @provider.expects(:execpipe).with("/usr/sbin/semodule --list").yields StringIO.new("") expect(@provider.exists?).to be_nil end end describe "selmodversion_file" do it "should return 1.5.0 for the example policy file" do @provider.expects(:selmod_name_to_filename).returns "#{File.dirname(__FILE__)}/selmodule-example.pp" expect(@provider.selmodversion_file).to eq("1.5.0") end end describe "syncversion" do it "should return :true if loaded and file modules are in sync" do @provider.expects(:selmodversion_loaded).returns "1.5.0" @provider.expects(:selmodversion_file).returns "1.5.0" expect(@provider.syncversion).to eq(:true) end it "should return :false if loaded and file modules are not in sync" do @provider.expects(:selmodversion_loaded).returns "1.4.0" @provider.expects(:selmodversion_file).returns "1.5.0" expect(@provider.syncversion).to eq(:false) end it "should return before checking file version if no loaded policy" do @provider.expects(:selmodversion_loaded).returns nil expect(@provider.syncversion).to eq(:false) end end describe "selmodversion_loaded" do it "should return the version of a loaded module" do @provider.expects(:command).with(:semodule).returns "/usr/sbin/semodule" @provider.expects(:execpipe).with("/usr/sbin/semodule --list").yields StringIO.new("bar\t1.2.3\nfoo\t4.4.4\nbang\t1.0.0\n") expect(@provider.selmodversion_loaded).to eq("4.4.4") end it 'should return raise an exception when running selmodule raises an exception' do @provider.expects(:command).with(:semodule).returns "/usr/sbin/semodule" @provider.expects(:execpipe).with("/usr/sbin/semodule --list").yields("this is\nan error").raises(Puppet::ExecutionFailure, 'it failed') expect {@provider.selmodversion_loaded}.to raise_error(Puppet::ExecutionFailure, /Could not list policy modules: ".*" failed with "this is an error"/) end end end puppet-5.5.10/spec/unit/provider/ssh_authorized_key/0000755005276200011600000000000013417162177022466 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/provider/ssh_authorized_key/parsed_spec.rb0000644005276200011600000002725213417161722025306 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'shared_behaviours/all_parsedfile_providers' require 'puppet_spec/files' provider_class = Puppet::Type.type(:ssh_authorized_key).provider(:parsed) describe provider_class, :unless => Puppet.features.microsoft_windows? do include PuppetSpec::Files before :each do @keyfile = tmpfile('authorized_keys') @provider_class = provider_class @provider_class.initvars @provider_class.any_instance.stubs(:target).returns @keyfile @user = 'random_bob' Puppet::Util.stubs(:uid).with(@user).returns 12345 end def mkkey(args) args[:target] = @keyfile args[:user] = @user resource = Puppet::Type.type(:ssh_authorized_key).new(args) key = @provider_class.new(resource) args.each do |p,v| key.send(p.to_s + "=", v) end key end def genkey(key) @provider_class.stubs(:filetype).returns(Puppet::Util::FileType::FileTypeRam) File.stubs(:chown) File.stubs(:chmod) Puppet::Util::SUIDManager.stubs(:asuser).yields key.flush @provider_class.target_object(@keyfile).read end it_should_behave_like "all parsedfile providers", provider_class it "should be able to generate a basic authorized_keys file" do key = mkkey(:name => "Just_Testing", :key => "AAAAfsfddsjldjgksdflgkjsfdlgkj", :type => "ssh-dss", :ensure => :present, :options => [:absent] ) expect(genkey(key)).to eq("ssh-dss AAAAfsfddsjldjgksdflgkjsfdlgkj Just_Testing\n") end it "should be able to generate an authorized_keys file with options" do key = mkkey(:name => "root@localhost", :key => "AAAAfsfddsjldjgksdflgkjsfdlgkj", :type => "ssh-rsa", :ensure => :present, :options => ['from="192.168.1.1"', "no-pty", "no-X11-forwarding"] ) expect(genkey(key)).to eq("from=\"192.168.1.1\",no-pty,no-X11-forwarding ssh-rsa AAAAfsfddsjldjgksdflgkjsfdlgkj root@localhost\n") end it "should parse comments" do result = [{ :record_type => :comment, :line => "# hello" }] expect(@provider_class.parse("# hello\n")).to eq(result) end it "should parse comments with leading whitespace" do result = [{ :record_type => :comment, :line => " # hello" }] expect(@provider_class.parse(" # hello\n")).to eq(result) end it "should skip over lines with only whitespace" do result = [{ :record_type => :comment, :line => "#before" }, { :record_type => :blank, :line => " " }, { :record_type => :comment, :line => "#after" }] expect(@provider_class.parse("#before\n \n#after\n")).to eq(result) end it "should skip over completely empty lines" do result = [{ :record_type => :comment, :line => "#before"}, { :record_type => :blank, :line => ""}, { :record_type => :comment, :line => "#after"}] expect(@provider_class.parse("#before\n\n#after\n")).to eq(result) end it "should be able to parse name if it includes whitespace" do expect(@provider_class.parse_line('ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC7pHZ1XRj3tXbFpPFhMGU1bVwz7jr13zt/wuE+pVIJA8GlmHYuYtIxHPfDHlkixdwLachCpSQUL9NbYkkRFRn9m6PZ7125ohE4E4m96QS6SGSQowTiRn4Lzd9LV38g93EMHjPmEkdSq7MY4uJEd6DUYsLvaDYdIgBiLBIWPA3OrQ== fancy user')[:name]).to eq('fancy user') expect(@provider_class.parse_line('from="host1.reductlivelabs.com,host.reductivelabs.com",command="/usr/local/bin/run",ssh-pty ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC7pHZ1XRj3tXbFpPFhMGU1bVwz7jr13zt/wuE+pVIJA8GlmHYuYtIxHPfDHlkixdwLachCpSQUL9NbYkkRFRn9m6PZ7125ohE4E4m96QS6SGSQowTiRn4Lzd9LV38g93EMHjPmEkdSq7MY4uJEd6DUYsLvaDYdIgBiLBIWPA3OrQ== fancy user')[:name]).to eq('fancy user') expect(@provider_class.parse_line('ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC7pHZ1XRj3tXbFpPFhMGU1bVwz7jr13zt/wuE+pVIJA8GlmHYuYtIxHPfDHlkixdwLachCpSQUL9NbYkkRFRn9m6PZ7125ohE4E4m96QS6SGSQowTiRn4Lzd9LV38g93EMHjPmEkdSq7MY4uJEd6DUYsLvaDYdIgBiLBIWPA3OrQ== whitespace fan')[:name]).to eq('whitespace fan') end it "should be able to parse options containing commas via its parse_options method" do options = %w{from="host1.reductlivelabs.com,host.reductivelabs.com" command="/usr/local/bin/run" ssh-pty} optionstr = options.join(", ") expect(@provider_class.parse_options(optionstr)).to eq(options) end it "should parse quoted options" do line = 'command="/usr/local/bin/mybin \"$SSH_ORIGINAL_COMMAND\"" ssh-rsa xxx mykey' expect(@provider_class.parse(line)[0][:options][0]).to eq('command="/usr/local/bin/mybin \"$SSH_ORIGINAL_COMMAND\""') end it "should use '' as name for entries that lack a comment" do line = "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAut8aOSxenjOqF527dlsdHWV4MNoAsX14l9M297+SQXaQ5Z3BedIxZaoQthkDALlV/25A1COELrg9J2MqJNQc8Xe9XQOIkBQWWinUlD/BXwoOTWEy8C8zSZPHZ3getMMNhGTBO+q/O+qiJx3y5cA4MTbw2zSxukfWC87qWwcZ64UUlegIM056vPsdZWFclS9hsROVEa57YUMrehQ1EGxT4Z5j6zIopufGFiAPjZigq/vqgcAqhAKP6yu4/gwO6S9tatBeEjZ8fafvj1pmvvIplZeMr96gHE7xS3pEEQqnB3nd4RY7AF6j9kFixnsytAUO7STPh/M3pLiVQBN89TvWPQ==" expect(@provider_class.parse(line)[0][:name]).to eq("") end { # ssh-keygen -t dsa -b 1024 'ssh-dss' => 'AAAAB3NzaC1kc3MAAACBANGTefWMXS780qLMMgysq3GNMKzg55LXZODif6Tqv1vtTh4Wuk3J5X5u644jTyNdAIn1RiBI9MnwnZMZ6nXKvucMcMQWMibYS9W2MhkRj3oqsLWMMsdGXJL18SWM5A6oC3oIRC4JHJZtkm0OctR2trKxmX+MGhdCd+Xpsh9CNK8XAAAAFQD4olFiwv+QQUFdaZbWUy1CLEG9xQAAAIByCkXKgoriZF8bQ0OX1sKuR69M/6n5ngmQGVBKB7BQkpUjbK/OggB6iJgst5utKkDcaqYRnrTYG9q3jJ/flv7yYePuoSreS0nCMMx9gpEYuq+7Sljg9IecmN/IHrNd9qdYoASy5iuROQMvEZM7KFHA8vBv0tWdBOsp4hZKyiL1DAAAAIEAjkZlOps9L+cD/MTzxDj7toYYypdLOvjlcPBaglkPZoFZ0MAKTI0zXlVX1cWAnkd0Yfo4EpP+6XAjlZkod+QXKXM4Tb4PnR34ASMeU6sEjM61Na24S7JD3gpPKataFU/oH3hzXsBdK2ttKYmoqvf61h32IA/3Z5PjCCD9pPLPpAY', # ssh-keygen -t rsa -b 2048 'ssh-rsa' => 'AAAAB3NzaC1yc2EAAAADAQABAAABAQDYtEaWa1mlxaAh9vtiz6RCVKDiJHDY15nsqqWU7F7A1+U1498+sWDyRDkZ8vXWQpzyOMBzBSHIxhsprlKhkjomy8BuJP+bHDBIKx4zgSFDrklrPIf467Iuug8J0qqDLxO4rOOjeAiLEyC0t2ZGnsTEea+rmat0bJ2cv3g5L4gH/OFz2pI4ZLp1HGN83ipl5UH8CjXQKwo3Db1E3WJCqKgszVX0Z4/qjnBRxFMoqky/1mGb/mX1eoT9JyQ8OhU9uENZOShkksSpgUqjlrjpj0Yd14hBlnE3M18pE4ivxjzectA/XRKNZaxOL1YREtU8sXusAwmlEY4aJ64aR0JrXfgx', # ssh-keygen -t ecdsa -b 256 'ecdsa-sha2-nistp256' => 'AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBBO5PfBf0c2jAuqD+Lj3j+SuXOXNT2uqESLVOn5jVQfEF9GzllOw+CMOpUvV1CiOOn+F1ET15vcsfmD7z05WUTA=', # ssh-keygen -t ecdsa -b 384 'ecdsa-sha2-nistp384' => 'AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBJIfxNoVK4FX3RuMlkHOwwxXwAh6Fqx5uAp4ftXrJ+64qYuIzb+/zSAkJV698Sre1b1lb0G4LyDdVAvXwaYK9kN25vy8umV3WdfZeHKXJGCcrplMCbbOERWARlpiPNEblg==', # ssh-keygen -t ecdsa -b 521 'ecdsa-sha2-nistp521' => 'AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBADLK+u12xwB0JOwpmaxYXv8KnPK4p+SE2405qoo+vpAQ569fMwPMgKzltd770amdeuFogw/MJu17PN9LDdrD3o0uwHMjWee6TpHQDkuEetaxiou6K0WAzgbxx9QsY0MsJgXf1BuMLqdK+xT183wOSXwwumv99G7T32dOJZ5tYrH0y4XMw==', # ssh-keygen -t ed25519 'ssh-ed25519' => 'AAAAC3NzaC1lZDI1NTE5AAAAIBWvu7D1KHBPaNXQcEuBsp48+JyPelXAq8ds6K5Du9gd', }.each_pair do |keytype, keydata| it "should be able to parse a #{keytype} key entry" do comment = 'sample_key' record = @provider_class.parse_line("#{keytype} #{keydata} #{comment}") expect(record).not_to be_nil expect(record[:name]).to eq(comment) expect(record[:key]).to eq(keydata) expect(record[:type]).to eq(keytype) end end describe "prefetch_hook" do let(:path) { '/path/to/keyfile' } let(:input) do { :type => 'rsa', :key => 'KEYDATA', :name => '', :record_type => :parsed, :target => path, } end it "adds an indexed name to unnamed resources" do expect(@provider_class.prefetch_hook([input])[0][:name]).to match(/^#{path}:unnamed-\d+/) end end end describe provider_class, :unless => Puppet.features.microsoft_windows? do before :each do @resource = Puppet::Type.type(:ssh_authorized_key).new(:name => "foo", :user => "random_bob") @provider = provider_class.new(@resource) provider_class.stubs(:filetype).returns(Puppet::Util::FileType::FileTypeRam) Puppet::Util::SUIDManager.stubs(:asuser).yields provider_class.initvars end describe "when flushing" do before :each do # Stub file and directory operations Dir.stubs(:mkdir) File.stubs(:chmod) File.stubs(:chown) end describe "and both a user and a target have been specified" do before :each do Puppet::Util.stubs(:uid).with("random_bob").returns 12345 @resource[:user] = "random_bob" target = "/tmp/.ssh_dir/place_to_put_authorized_keys" @resource[:target] = target end it "should create the directory" do Puppet::FileSystem.stubs(:exist?).with("/tmp/.ssh_dir").returns false Dir.expects(:mkdir).with("/tmp/.ssh_dir", 0700) @provider.flush end it "should absolutely not chown the directory to the user" do File.expects(:chown).never @provider.flush end it "should absolutely not chown the key file to the user" do File.expects(:chown).never @provider.flush end it "should chmod the key file to 0600" do File.expects(:chmod).with(0600, "/tmp/.ssh_dir/place_to_put_authorized_keys") @provider.flush end end describe "and a user has been specified with no target" do before :each do @resource[:user] = "nobody" # # I'd like to use random_bob here and something like # # File.stubs(:expand_path).with("~random_bob/.ssh").returns "/users/r/random_bob/.ssh" # # but mocha objects strenuously to stubbing File.expand_path # so I'm left with using nobody. @dir = File.expand_path("~nobody/.ssh") end it "should create the directory if it doesn't exist" do Puppet::FileSystem.stubs(:exist?).with(@dir).returns false Dir.expects(:mkdir).with(@dir,0700) @provider.flush end it "should not create or chown the directory if it already exist" do Puppet::FileSystem.stubs(:exist?).with(@dir).returns false Dir.expects(:mkdir).never @provider.flush end it "should absolutely not chown the directory to the user if it creates it" do Puppet::FileSystem.stubs(:exist?).with(@dir).returns false Dir.stubs(:mkdir).with(@dir,0700) File.expects(:chown).never @provider.flush end it "should not create or chown the directory if it already exist" do Puppet::FileSystem.stubs(:exist?).with(@dir).returns false Dir.expects(:mkdir).never File.expects(:chown).never @provider.flush end it "should absolutely not chown the key file to the user" do File.expects(:chown).never @provider.flush end it "should chmod the key file to 0600" do File.expects(:chmod).with(0600, File.expand_path("~nobody/.ssh/authorized_keys")) @provider.flush end end describe "and a target has been specified with no user" do it "should raise an error" do @resource = Puppet::Type.type(:ssh_authorized_key).new(:name => "foo", :target => "/tmp/.ssh_dir/place_to_put_authorized_keys") @provider = provider_class.new(@resource) expect { @provider.flush }.to raise_error(Puppet::Error, /Cannot write SSH authorized keys without user/) end end describe "and an invalid user has been specified with no target" do it "should catch an exception and raise a Puppet error" do @resource[:user] = "thisusershouldnotexist" expect { @provider.flush }.to raise_error(Puppet::Error) end end end end puppet-5.5.10/spec/unit/provider/sshkey/0000755005276200011600000000000013417162177020071 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/provider/sshkey/parsed_spec.rb0000644005276200011600000000710313417161722022702 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe "sshkey parsed provider" do let :type do Puppet::Type.type(:sshkey) end let :provider do type.provider(:parsed) end subject { provider } after :each do subject.clear end def key 'AAAAB3NzaC1yc2EAAAABIwAAAQEAzwHhxXvIrtfIwrudFqc8yQcIfMudrgpnuh1F3AV6d2BrLgu/yQE7W5UyJMUjfj427sQudRwKW45O0Jsnr33F4mUw+GIMlAAmp9g24/OcrTiB8ZUKIjoPy/cO4coxGi8/NECtRzpD/ZUPFh6OEpyOwJPMb7/EC2Az6Otw4StHdXUYw22zHazBcPFnv6zCgPx1hA7QlQDWTu4YcL0WmTYQCtMUb3FUqrcFtzGDD0ytosgwSd+JyN5vj5UwIABjnNOHPZ62EY1OFixnfqX/+dUwrFSs5tPgBF/KkC6R7tmbUfnBON6RrGEmu+ajOTOLy23qUZB4CQ53V7nyAWhzqSK+hw==' end it "should parse the name from the first field" do expect(subject.parse_line('test ssh-rsa '+key)[:name]).to eq("test") end it "should parse the first component of the first field as the name" do expect(subject.parse_line('test,alias ssh-rsa '+key)[:name]).to eq("test") end it "should parse host_aliases from the remaining components of the first field" do expect(subject.parse_line('test,alias ssh-rsa '+key)[:host_aliases]).to eq(["alias"]) end it "should parse multiple host_aliases" do expect(subject.parse_line('test,alias1,alias2,alias3 ssh-rsa '+key)[:host_aliases]).to eq(["alias1","alias2","alias3"]) end it "should not drop an empty host_alias" do expect(subject.parse_line('test,alias, ssh-rsa '+key)[:host_aliases]).to eq(["alias",""]) end it "should recognise when there are no host aliases" do expect(subject.parse_line('test ssh-rsa '+key)[:host_aliases]).to eq([]) end context "with the sample file" do ['sample', 'sample_with_blank_lines'].each do |sample_file| let :fixture do my_fixture(sample_file) end before :each do subject.stubs(:default_target).returns(fixture) end it "should parse to records on prefetch" do expect(subject.target_records(fixture)).to be_empty subject.prefetch records = subject.target_records(fixture) expect(records).to be_an Array expect(records).to be_all {|x| expect(x).to be_an Hash } end it "should reconstitute the file from records" do subject.prefetch records = subject.target_records(fixture) text = subject.to_file(records).gsub(/^# HEADER.+\n/, '') oldlines = File.readlines(fixture).map(&:chomp) newlines = text.chomp.split("\n") expect(oldlines.length).to eq(newlines.length) oldlines.zip(newlines).each do |old, new| expect(old.gsub(/\s+/, '')).to eq(new.gsub(/\s+/, '')) end end end end context 'default ssh_known_hosts target path' do ['9.10', '9.11', '10.10'].each do |version| it 'should be `/etc/ssh_known_hosts` when OSX version 10.10 or older`' do Facter.expects(:value).with(:operatingsystem).returns('Darwin') Facter.expects(:value).with(:macosx_productversion_major).returns(version) expect(subject.default_target).to eq('/etc/ssh_known_hosts') end end ['10.11', '10.13', '11.0', '11.11'].each do |version| it 'should be `/etc/ssh/ssh_known_hosts` when OSX version 10.11 or newer`' do Facter.expects(:value).with(:operatingsystem).returns('Darwin') Facter.expects(:value).with(:macosx_productversion_major).returns(version) expect(subject.default_target).to eq('/etc/ssh/ssh_known_hosts') end end it 'should be `/etc/ssh/ssh_known_hosts` on other operating systems' do Facter.expects(:value).with(:operatingsystem).returns('RedHat') expect(subject.default_target).to eq('/etc/ssh/ssh_known_hosts') end end end puppet-5.5.10/spec/unit/provider/vlan/0000755005276200011600000000000013417162177017523 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/provider/vlan/cisco_spec.rb0000644005276200011600000000356613417161722022167 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/provider/vlan/cisco' provider_class = Puppet::Type.type(:vlan).provider(:cisco) describe provider_class do before do @device = stub_everything 'device' @resource = stub("resource", :name => "200") @provider = provider_class.new(@device, @resource) end it "should have a parent of Puppet::Provider::Cisco" do expect(provider_class).to be < Puppet::Provider::Cisco end it "should have an instances method" do expect(provider_class).to respond_to(:instances) end describe "when looking up instances at prefetch" do before do @device.stubs(:command).yields(@device) end it "should delegate to the device vlans" do @device.expects(:parse_vlans) provider_class.lookup(@device, "200") end it "should return only the given vlan" do @device.expects(:parse_vlans).returns({"200" => { :description => "thisone" }, "1" => { :description => "nothisone" }}) expect(provider_class.lookup(@device, "200")).to eq({:description => "thisone" }) end end describe "when an instance is being flushed" do it "should call the device update_vlan method with its vlan id, current attributes, and desired attributes" do @instance = provider_class.new(@device, :ensure => :present, :name => "200", :description => "myvlan") @instance.description = "myvlan2" @instance.resource = @resource @resource.stubs(:[]).with(:name).returns("200") device = stub_everything 'device' @instance.stubs(:device).returns(device) device.expects(:command).yields(device) device.expects(:update_vlan).with(@instance.name, {:ensure => :present, :name => "200", :description => "myvlan"}, {:ensure => :present, :name => "200", :description => "myvlan2"}) @instance.flush end end end puppet-5.5.10/spec/unit/provider/yumrepo/0000755005276200011600000000000013417162177020263 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/provider/yumrepo/inifile_spec.rb0000644005276200011600000003550513417161722023244 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/files' describe Puppet::Type.type(:yumrepo).provider(:inifile) do include PuppetSpec::Files after(:each) do described_class.clear end describe "enumerating all yum repo files" do it "reads all files in the directories specified by reposdir" do described_class.expects(:reposdir).returns ['/etc/yum.repos.d'] Dir.expects(:glob).with("/etc/yum.repos.d/*.repo").returns(['/etc/yum.repos.d/first.repo', '/etc/yum.repos.d/second.repo']) actual = described_class.repofiles expect(actual).to include("/etc/yum.repos.d/first.repo") expect(actual).to include("/etc/yum.repos.d/second.repo") end it "includes '/etc/yum.conf' as the first element" do described_class.expects(:reposdir).returns [] actual = described_class.repofiles expect(actual[0]).to eq "/etc/yum.conf" end end describe "generating the virtual inifile" do let(:files) { ['/etc/yum.repos.d/first.repo', '/etc/yum.repos.d/second.repo', '/etc/yum.conf'] } let(:collection) { mock('virtual inifile') } before do described_class.clear Puppet::Util::IniConfig::FileCollection.expects(:new).returns collection end it "reads all files in the directories specified by self.repofiles" do described_class.expects(:repofiles).returns(files) files.each do |file| Puppet::FileSystem.stubs(:file?).with(file).returns true collection.expects(:read).with(file) end described_class.virtual_inifile end it "ignores repofile entries that are not files" do described_class.expects(:repofiles).returns(files) Puppet::FileSystem.stubs(:file?).with('/etc/yum.repos.d/first.repo').returns true Puppet::FileSystem.stubs(:file?).with('/etc/yum.repos.d/second.repo').returns false Puppet::FileSystem.stubs(:file?).with('/etc/yum.conf').returns true collection.expects(:read).with('/etc/yum.repos.d/first.repo').once collection.expects(:read).with('/etc/yum.repos.d/second.repo').never collection.expects(:read).with('/etc/yum.conf').once described_class.virtual_inifile end end describe 'creating provider instances' do let(:virtual_inifile) { stub('virtual inifile') } before :each do described_class.stubs(:virtual_inifile).returns(virtual_inifile) end let(:main_section) do sect = Puppet::Util::IniConfig::Section.new('main', '/some/imaginary/file') sect.entries << ['distroverpkg', 'centos-release'] sect.entries << ['plugins', '1'] sect end let(:updates_section) do sect = Puppet::Util::IniConfig::Section.new('updates', '/some/imaginary/file') sect.entries << ['name', 'Some long description of the repo'] sect.entries << ['enabled', '1'] sect end it "ignores the main section" do virtual_inifile.expects(:each_section).multiple_yields(main_section, updates_section) instances = described_class.instances expect(instances).to have(1).items expect(instances[0].name).to eq 'updates' end it "creates provider instances for every non-main section that was found" do virtual_inifile.expects(:each_section).multiple_yields(main_section, updates_section) sect = described_class.instances.first expect(sect.name).to eq 'updates' expect(sect.descr).to eq 'Some long description of the repo' expect(sect.enabled).to eq '1' end end describe "retrieving a section from the inifile" do let(:collection) { stub('ini file collection') } let(:ini_section) { stub('ini file section') } before do described_class.stubs(:virtual_inifile).returns(collection) end describe "and the requested section exists" do before do collection.stubs(:[]).with('updates').returns ini_section end it "returns the existing section" do expect(described_class.section('updates')).to eq ini_section end it "doesn't create a new section" do collection.expects(:add_section).never described_class.section('updates') end end describe "and the requested section doesn't exist" do it "creates a section in the preferred repodir" do described_class.stubs(:reposdir).returns ['/etc/yum.repos.d', '/etc/alternate.repos.d'] collection.expects(:[]).with('updates') collection.expects(:add_section).with('updates', '/etc/alternate.repos.d/updates.repo') described_class.section('updates') end it "creates a section in yum.conf if no repodirs exist" do described_class.stubs(:reposdir).returns [] collection.expects(:[]).with('updates') collection.expects(:add_section).with('updates', '/etc/yum.conf') described_class.section('updates') end end end describe "setting and getting properties" do let(:type_instance) do Puppet::Type.type(:yumrepo).new( :name => 'puppetlabs-products', :ensure => :present, :baseurl => 'http://yum.puppetlabs.com/el/6/products/$basearch', :descr => 'Puppet Labs Products El 6 - $basearch', :enabled => '1', :gpgcheck => '1', :gpgkey => 'file:///etc/pki/rpm-gpg/RPM-GPG-KEY-puppetlabs' ) end let(:provider) do described_class.new(type_instance) end let(:section) do stub('inifile puppetlabs section', :name => 'puppetlabs-products') end before do type_instance.provider = provider described_class.stubs(:section).with('puppetlabs-products').returns(section) end describe "methods used by ensurable" do it "#create sets the yumrepo properties on the according section" do section.expects(:[]=).with('baseurl', 'http://yum.puppetlabs.com/el/6/products/$basearch') section.expects(:[]=).with('name', 'Puppet Labs Products El 6 - $basearch') section.expects(:[]=).with('enabled', '1') section.expects(:[]=).with('gpgcheck', '1') section.expects(:[]=).with('gpgkey', 'file:///etc/pki/rpm-gpg/RPM-GPG-KEY-puppetlabs') provider.create end it "#exists? checks if the repo has been marked as present" do described_class.stubs(:section).returns(stub(:[]= => nil)) provider.create expect(provider).to be_exist end it "#destroy deletes the associated ini file section" do described_class.expects(:section).returns(section) section.expects(:destroy=).with(true) provider.destroy end end describe "getting properties" do it "maps the 'descr' property to the 'name' INI property" do section.expects(:[]).with('name').returns 'Some rather long description of the repository' expect(provider.descr).to eq 'Some rather long description of the repository' end it "gets the property from the INI section" do section.expects(:[]).with('enabled').returns '1' expect(provider.enabled).to eq '1' end it "sets the property as :absent if the INI property is nil" do section.expects(:[]).with('exclude').returns nil expect(provider.exclude).to eq :absent end end describe "setting properties" do it "maps the 'descr' property to the 'name' INI property" do section.expects(:[]=).with('name', 'Some rather long description of the repository') provider.descr = 'Some rather long description of the repository' end it "sets the property on the INI section" do section.expects(:[]=).with('enabled', '0') provider.enabled = '0' end it "sets the section field to nil when the specified value is absent" do section.expects(:[]=).with('exclude', nil) provider.exclude = :absent end end end describe 'reposdir' do let(:defaults) { ['/etc/yum.repos.d', '/etc/yum/repos.d'] } before do Puppet::FileSystem.stubs(:exist?).with('/etc/yum.repos.d').returns(true) Puppet::FileSystem.stubs(:exist?).with('/etc/yum/repos.d').returns(true) end it "returns the default directories if yum.conf doesn't contain a `reposdir` entry" do described_class.stubs(:find_conf_value).with('reposdir', '/etc/yum.conf') expect(described_class.reposdir('/etc/yum.conf')).to eq(defaults) end it "includes the directory specified by the yum.conf 'reposdir' entry when the directory is present" do Puppet::FileSystem.expects(:exist?).with("/etc/yum/extra.repos.d").returns(true) described_class.expects(:find_conf_value).with('reposdir', '/etc/yum.conf').returns "/etc/yum/extra.repos.d" expect(described_class.reposdir('/etc/yum.conf')).to include("/etc/yum/extra.repos.d") end it "includes the directory if the value is split by whitespace" do Puppet::FileSystem.expects(:exist?).with("/etc/yum/extra.repos.d").returns(true) Puppet::FileSystem.expects(:exist?).with("/etc/yum/misc.repos.d").returns(true) described_class.expects(:find_conf_value).with('reposdir', '/etc/yum.conf').returns "/etc/yum/extra.repos.d /etc/yum/misc.repos.d" expect(described_class.reposdir('/etc/yum.conf')).to include("/etc/yum/extra.repos.d", "/etc/yum/misc.repos.d") end it "includes the directory if the value is split by new lines" do Puppet::FileSystem.expects(:exist?).with("/etc/yum/extra.repos.d").returns(true) Puppet::FileSystem.expects(:exist?).with("/etc/yum/misc.repos.d").returns(true) described_class.expects(:find_conf_value).with('reposdir', '/etc/yum.conf').returns "/etc/yum/extra.repos.d\n/etc/yum/misc.repos.d" expect(described_class.reposdir('/etc/yum.conf')).to include("/etc/yum/extra.repos.d", "/etc/yum/misc.repos.d") end it "doesn't include the directory specified by the yum.conf 'reposdir' entry when the directory is absent" do Puppet::FileSystem.expects(:exist?).with("/etc/yum/extra.repos.d").returns(false) described_class.expects(:find_conf_value).with('reposdir', '/etc/yum.conf').returns "/etc/yum/extra.repos.d" expect(described_class.reposdir('/etc/yum.conf')).not_to include("/etc/yum/extra.repos.d") end it "logs a warning and returns an empty array if none of the specified repo directories exist" do Puppet::FileSystem.unstub(:exist?) Puppet::FileSystem.stubs(:exist?).returns false described_class.stubs(:find_conf_value).with('reposdir', '/etc/yum.conf') Puppet.expects(:debug).with('No yum directories were found on the local filesystem') expect(described_class.reposdir('/etc/yum.conf')).to be_empty end end describe "looking up a conf value" do describe "and the file doesn't exist" do it "returns nil" do Puppet::FileSystem.stubs(:exist?).returns false expect(described_class.find_conf_value('reposdir')).to be_nil end end describe "and the file exists" do let(:pfile) { stub('yum.conf physical file') } let(:sect) { stub('ini section') } before do Puppet::FileSystem.stubs(:exist?).with('/etc/yum.conf').returns true Puppet::Util::IniConfig::PhysicalFile.stubs(:new).with('/etc/yum.conf').returns pfile pfile.expects(:read) end it "creates a PhysicalFile to parse the given file" do pfile.expects(:get_section) described_class.find_conf_value('reposdir') end it "returns nil if the file exists but the 'main' section doesn't exist" do pfile.expects(:get_section).with('main') expect(described_class.find_conf_value('reposdir')).to be_nil end it "returns nil if the file exists but the INI property doesn't exist" do pfile.expects(:get_section).with('main').returns sect sect.expects(:[]).with('reposdir') expect(described_class.find_conf_value('reposdir')).to be_nil end it "returns the value if the value is defined in the PhysicalFile" do pfile.expects(:get_section).with('main').returns sect sect.expects(:[]).with('reposdir').returns '/etc/alternate.repos.d' expect(described_class.find_conf_value('reposdir')).to eq '/etc/alternate.repos.d' end end end describe "resource application after prefetch" do let(:type_instance) do Puppet::Type.type(:yumrepo).new( :name => 'puppetlabs-products', :ensure => :present, :baseurl => 'http://yum.puppetlabs.com/el/6/products/$basearch', :descr => 'Puppet Labs Products El 6 - $basearch', :enabled => '1', :gpgcheck => '1', :gpgkey => 'file:///etc/pki/rpm-gpg/RPM-GPG-KEY-puppetlabs' ) end let(:provider) do described_class.new(type_instance) end before :each do @yumrepo_dir = tmpdir('yumrepo_integration_specs') @yumrepo_conf_file = tmpfile('yumrepo_conf_file', @yumrepo_dir) described_class.stubs(:reposdir).returns [@yumrepo_dir] type_instance.provider = provider end it "preserves repo file contents that were created after prefetch" do provider.class.prefetch({}) # we specifically want to create a file after prefetch has happened so that # none of the sections in the file exist in the prefetch cache repo_file = File.join(@yumrepo_dir, 'puppetlabs-products.repo') contents = <<-HEREDOC [puppetlabs-products] name=created_by_package_after_prefetch enabled=1 failovermethod=priority gpgcheck=0 [additional_section] name=Extra Packages for Enterprise Linux 6 - $basearch - Debug #baseurl=http://download.fedoraproject.org/pub/epel/6/$basearch/debug mirrorlist=https://mirrors.fedoraproject.org/metalink?repo=epel-debug-6&arch=$basearch failovermethod=priority enabled=0 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-6 gpgcheck=1 HEREDOC File.open(repo_file, 'wb') { |f| f.write(contents)} provider.create provider.flush expected_contents = <<-HEREDOC [puppetlabs-products] name=Puppet Labs Products El 6 - $basearch enabled=1 failovermethod=priority gpgcheck=1 baseurl=http://yum.puppetlabs.com/el/6/products/$basearch gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-puppetlabs [additional_section] name=Extra Packages for Enterprise Linux 6 - $basearch - Debug #baseurl=http://download.fedoraproject.org/pub/epel/6/$basearch/debug mirrorlist=https://mirrors.fedoraproject.org/metalink?repo=epel-debug-6&arch=$basearch failovermethod=priority enabled=0 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-6 gpgcheck=1 HEREDOC expect(File.read(repo_file)).to eq(expected_contents) end it "does not error becuase of repo files that have been removed from disk" do repo_file = File.join(@yumrepo_dir, 'epel.repo') contents = <<-HEREDOC [epel] name=created_by_package_after_prefetch enabled=1 failovermethod=priority gpgcheck=0 HEREDOC File.open(repo_file, 'wb') { |f| f.write(contents)} provider.class.prefetch({}) File.delete(repo_file) provider.create provider.flush end end end puppet-5.5.10/spec/unit/provider/zfs/0000755005276200011600000000000013417162177017365 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/provider/zfs/zfs_spec.rb0000644005276200011600000001213413417161722021522 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:zfs).provider(:zfs) do let(:name) { 'myzfs' } let(:zfs) { '/usr/sbin/zfs' } let(:resource) do Puppet::Type.type(:zfs).new(:name => name, :provider => :zfs) end let(:provider) { resource.provider } before do provider.class.stubs(:which).with('zfs').returns(zfs) end context ".instances" do it "should have an instances method" do expect(provider.class).to respond_to(:instances) end it "should list instances" do provider.class.expects(:zfs).with(:list,'-H').returns File.read(my_fixture('zfs-list.out')) instances = provider.class.instances.map { |p| {:name => p.get(:name), :ensure => p.get(:ensure)} } expect(instances.size).to eq(2) expect(instances[0]).to eq({:name => 'rpool', :ensure => :present}) expect(instances[1]).to eq({:name => 'rpool/ROOT', :ensure => :present}) end end context '#add_properties' do it 'should return an array of properties' do resource[:mountpoint] = '/foo' expect(provider.add_properties).to eq(['-o', "mountpoint=/foo"]) end it 'should return an empty array' do expect(provider.add_properties).to eq([]) end end context "#create" do it "should execute zfs create" do provider.expects(:zfs).with(:create, name) provider.create end Puppet::Type.type(:zfs).validproperties.each do |prop| next if [:ensure, :volsize].include?(prop) it "should include property #{prop}" do resource[prop] = prop provider.expects(:zfs).with(:create, '-o', "#{prop}=#{prop}", name) provider.create end end it "should use -V for the volsize property" do resource[:volsize] = "10" provider.expects(:zfs).with(:create, '-V', "10", name) provider.create end end context "#destroy" do it "should execute zfs destroy" do provider.expects(:zfs).with(:destroy, name) provider.destroy end end context "#exists?" do it "should return true if the resource exists" do #return stuff because we have to slice and dice it provider.expects(:zfs).with(:list, name) expect(provider).to be_exists end it "should return false if returned values don't match the name" do provider.expects(:zfs).with(:list, name).raises(Puppet::ExecutionFailure, "Failed") expect(provider).not_to be_exists end end describe "zfs properties" do [:aclinherit, :aclmode, :atime, :canmount, :checksum, :compression, :copies, :dedup, :devices, :exec, :logbias, :mountpoint, :nbmand, :primarycache, :quota, :readonly, :recordsize, :refquota, :refreservation, :reservation, :secondarycache, :setuid, :shareiscsi, :sharenfs, :sharesmb, :snapdir, :version, :volsize, :vscan, :xattr].each do |prop| it "should get #{prop}" do provider.expects(:zfs).with(:get, '-H', '-o', 'value', prop, name).returns("value\n") expect(provider.send(prop)).to eq('value') end it "should set #{prop}=value" do provider.expects(:zfs).with(:set, "#{prop}=value", name) provider.send("#{prop}=", "value") end end end describe "zoned" do context "on FreeBSD" do before do Facter.stubs(:value).with(:operatingsystem).returns("FreeBSD") end it "should get 'jailed' property" do provider.expects(:zfs).with(:get, '-H', '-o', 'value', :jailed, name).returns("value\n") expect(provider.send("zoned")).to eq('value') end it "should set jalied=value" do provider.expects(:zfs).with(:set, "jailed=value", name) provider.send("zoned=", "value") end end context "when not running FreeBSD" do before do Facter.stubs(:value).with(:operatingsystem).returns("Solaris") end it "should get 'zoned' property" do provider.expects(:zfs).with(:get, '-H', '-o', 'value', :zoned, name).returns("value\n") expect(provider.send("zoned")).to eq('value') end it "should set zoned=value" do provider.expects(:zfs).with(:set, "zoned=value", name) provider.send("zoned=", "value") end end end describe "acltype" do context "when available" do it "should get 'acltype' property" do provider.expects(:zfs).with(:get, '-H', '-o', 'value', :acltype, name).returns("value\n") expect(provider.send("acltype")).to eq('value') end it "should set acltype=value" do provider.expects(:zfs).with(:set, "acltype=value", name) provider.send("acltype=", "value") end end context "when not available" do it "should get '-' for the acltype property" do provider.expects(:zfs).with(:get, '-H', '-o', 'value', :acltype, name).raises(RuntimeError, 'not valid') expect(provider.send("acltype")).to eq('-') end it "should not error out when trying to set acltype" do provider.expects(:zfs).with(:set, "acltype=value", name).raises(RuntimeError, 'not valid') expect{provider.send("acltype=", "value")}.to_not raise_error end end end end puppet-5.5.10/spec/unit/provider/zone/0000755005276200011600000000000013417162177017536 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/provider/zone/solaris_spec.rb0000644005276200011600000002210413417161722022543 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:zone).provider(:solaris) do let(:resource) { Puppet::Type.type(:zone).new(:name => 'dummy', :path => '/', :provider => :solaris) } let(:provider) { described_class.new(resource) } context "#configure" do it "should add the create args to the create str" do resource.stubs(:properties).returns([]) resource[:create_args] = "create_args" provider.expects(:setconfig).with("create -b create_args") provider.configure end it "should add the create args to the create str" do iptype = stub "property" iptype.stubs(:name).with().returns(:iptype) iptype.stubs(:safe_insync?).with(iptype).returns(false) provider.stubs(:properties).returns({:iptype => iptype}) resource.stubs(:properties).with().returns([iptype]) resource[:create_args] = "create_args" provider.expects(:setconfig).with("create -b create_args\nset ip-type=shared") provider.configure end end context "#install" do context "clone" do it "should call zoneadm" do provider.expects(:zoneadm).with(:install) provider.install end it "with the resource's clone attribute" do resource[:clone] = :clone_argument provider.expects(:zoneadm).with(:clone, :clone_argument) provider.install end end context "not clone" do it "should just install if there are no install args" do # there is a nil check in type.rb:[]= so we cannot directly set nil. resource.stubs(:[]).with(:clone).returns(nil) resource.stubs(:[]).with(:install_args).returns(nil) provider.expects(:zoneadm).with(:install) provider.install end it "should add the install args to the command if they exist" do # there is a nil check in type.rb:[]= so we cannot directly set nil. resource.stubs(:[]).with(:clone).returns(nil) resource.stubs(:[]).with(:install_args).returns('install args') provider.expects(:zoneadm).with(:install, ["install", "args"]) provider.install end end end context "#instances" do it "should list the instances correctly" do described_class.expects(:adm).with(:list, "-cp").returns("0:dummy:running:/::native:shared") instances = described_class.instances.map { |p| {:name => p.get(:name), :ensure => p.get(:ensure)} } expect(instances.size).to eq(1) expect(instances[0]).to eq({ :name=>"dummy", :ensure=>:running, }) end end context "#setconfig" do it "should correctly set configuration" do provider.expects(:command).with(:cfg).returns('/usr/sbin/zonecfg') provider.expects(:exec_cmd).with(:input => "set zonepath=/\ncommit\nexit", :cmd => '/usr/sbin/zonecfg -z dummy -f -').returns({:out=>'', :exit => 0}) provider.setconfig("set zonepath=\/") provider.flush end it "should correctly warn on 'not allowed'" do provider.expects(:command).with(:cfg).returns('/usr/sbin/zonecfg') provider.expects(:exec_cmd).with(:input => "set zonepath=/\ncommit\nexit", :cmd => '/usr/sbin/zonecfg -z dummy -f -').returns({:out=>"Zone z2 already installed; set zonepath not allowed.\n", :exit => 0}) provider.setconfig("set zonepath=\/") expect { provider.flush }.to raise_error(ArgumentError, /Failed to apply configuration/) end end context "#getconfig" do describe "with a shared iptype zone" do zone_info =<<-EOF zonename: dummy zonepath: /dummy/z brand: native autoboot: true bootargs: pool: limitpriv: scheduling-class: ip-type: shared hostid: net: address: 1.1.1.1 physical: ex0001 defrouter not specified net: address: 1.1.1.2 physical: ex0002 defrouter not specified EOF it "should correctly parse zone info" do provider.expects(:zonecfg).with(:info).returns(zone_info) expect(provider.getconfig).to eq({ :brand=>"native", :autoboot=>"true", :"ip-type"=>"shared", :zonename=>"dummy", "net"=>[{:physical=>"ex0001", :address=>"1.1.1.1"}, {:physical=>"ex0002", :address=>"1.1.1.2"}], :zonepath=>"/dummy/z" }) end end describe "with an exclusive iptype zone" do zone_info =<<-EOF zonename: dummy zonepath: /dummy/z brand: native autoboot: true bootargs: pool: limitpriv: scheduling-class: ip-type: exclusive hostid: net: address not specified allowed-address not specified configure-allowed-address: true physical: net1 defrouter not specified EOF it "should correctly parse zone info" do provider.expects(:zonecfg).with(:info).returns(zone_info) expect(provider.getconfig).to eq({ :brand=>"native", :autoboot=>"true", :"ip-type"=>"exclusive", :zonename=>"dummy", "net"=>[{:physical=>"net1",:'configure-allowed-address'=>"true"}], :zonepath=>"/dummy/z" }) end end describe "with an invalid or unrecognized config" do it "should produce an error message with provider context when given an invalid config" do erroneous_zone_info =<<-EOF physical: net1' EOF provider.expects(:zonecfg).with(:info).returns(erroneous_zone_info) provider.expects('err').with("Ignoring ' physical: net1''") provider.getconfig end it "should produce a debugging message with provider context when given an unrecognized config" do unrecognized_zone_info = "dummy" provider.expects(:zonecfg).with(:info).returns(unrecognized_zone_info) provider.expects('debug').with("Ignoring zone output 'dummy'") provider.getconfig end end end context "#flush" do it "should correctly execute pending commands" do provider.expects(:command).with(:cfg).returns('/usr/sbin/zonecfg') provider.expects(:exec_cmd).with(:input => "set iptype=shared\ncommit\nexit", :cmd => '/usr/sbin/zonecfg -z dummy -f -').returns({:out=>'', :exit => 0}) provider.setconfig("set iptype=shared") provider.flush end it "should correctly raise error on failure" do provider.expects(:command).with(:cfg).returns('/usr/sbin/zonecfg') provider.expects(:exec_cmd).with(:input => "set iptype=shared\ncommit\nexit", :cmd => '/usr/sbin/zonecfg -z dummy -f -').returns({:out=>'', :exit => 1}) provider.setconfig("set iptype=shared") expect { provider.flush }.to raise_error(ArgumentError, /Failed to apply/) end end context "#start" do it "should not require path if sysidcfg is specified" do resource[:path] = '/mypath' resource[:sysidcfg] = 'dummy' Puppet::FileSystem.stubs(:exist?).with('/mypath/root/etc/sysidcfg').returns true File.stubs(:directory?).with('/mypath/root/etc').returns true provider.expects(:zoneadm).with(:boot) provider.start end it "should require path if sysidcfg is specified" do resource.stubs(:[]).with(:path).returns nil resource.stubs(:[]).with(:sysidcfg).returns 'dummy' expect { provider.start }.to raise_error(Puppet::Error, /Path is required/) end end context "#line2hash" do it "should parse lines correctly" do expect(described_class.line2hash('0:dummy:running:/z::native:shared')).to eq({:ensure=>:running, :iptype=>"shared", :path=>"/z", :name=>"dummy", :id=>"0"}) end it "should parse lines correctly(2)" do expect(described_class.line2hash('0:dummy:running:/z:ipkg:native:shared')).to eq({:ensure=>:running, :iptype=>"shared", :path=>"/z", :name=>"dummy", :id=>"0"}) end it "should parse lines correctly(3)" do expect(described_class.line2hash('-:dummy:running:/z:ipkg:native:shared')).to eq({:ensure=>:running, :iptype=>"shared", :path=>"/z", :name=>"dummy"}) end it "should parse lines correctly(3)" do expect(described_class.line2hash('-:dummy:running:/z:ipkg:native:exclusive')).to eq({:ensure=>:running, :iptype=>"exclusive", :path=>"/z", :name=>"dummy"}) end end context "#multi_conf" do it "should correctly add and remove properties" do provider.stubs(:properties).with().returns({:ip => ['1.1.1.1', '2.2.2.2']}) should = ['1.1.1.1', '3.3.3.3'] p = Proc.new do |a, str| case a when :add; 'add:' + str when :rm; 'rm:' + str end end expect(provider.multi_conf(:ip, should, &p)).to eq("rm:2.2.2.2\nadd:3.3.3.3") end end context "single props" do {:iptype => /set ip-type/, :autoboot => /set autoboot/, :path => /set zonepath/, :pool => /set pool/, :shares => /add rctl/}.each do |p, v| it "#{p.to_s}: should correctly return conf string" do expect(provider.send(p.to_s + '_conf', 'dummy')).to match(v) end it "#{p.to_s}: should correctly set property string" do provider.expects((p.to_s + '_conf').intern).returns('dummy') provider.expects(:setconfig).with('dummy').returns('dummy2') expect(provider.send(p.to_s + '=', 'dummy')).to eq('dummy2') end end end end puppet-5.5.10/spec/unit/provider/zpool/0000755005276200011600000000000013417162177017726 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/provider/zpool/zpool_spec.rb0000644005276200011600000002135713417161722022433 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:zpool).provider(:zpool) do let(:name) { 'mypool' } let(:zpool) { '/usr/sbin/zpool' } let(:resource) do Puppet::Type.type(:zpool).new(:name => name, :provider => :zpool) end let(:provider) { resource.provider } before do provider.class.stubs(:which).with('zpool').returns(zpool) end context '#current_pool' do it "should call process_zpool_data with the result of get_pool_data only once" do provider.stubs(:get_pool_data).returns(["foo", "disk"]) provider.expects(:process_zpool_data).with(["foo", "disk"]).returns("stuff").once provider.current_pool provider.current_pool end end describe "self.instances" do it "should have an instances method" do expect(provider.class).to respond_to(:instances) end it "should list instances" do provider.class.expects(:zpool).with(:list,'-H').returns File.read(my_fixture('zpool-list.out')) instances = provider.class.instances.map { |p| {:name => p.get(:name), :ensure => p.get(:ensure)} } expect(instances.size).to eq(2) expect(instances[0]).to eq({:name => 'rpool', :ensure => :present}) expect(instances[1]).to eq({:name => 'mypool', :ensure => :present}) end end context '#flush' do it "should reload the pool" do provider.stubs(:get_pool_data) provider.expects(:process_zpool_data).returns("stuff").times(2) provider.current_pool provider.flush provider.current_pool end end context '#process_zpool_data' do let(:zpool_data) { ["foo", "disk"] } describe "when there is no data" do it "should return a hash with ensure=>:absent" do expect(provider.process_zpool_data([])[:ensure]).to eq(:absent) end end describe "when there is a spare" do it "should add the spare disk to the hash" do zpool_data.concat ["spares", "spare_disk"] expect(provider.process_zpool_data(zpool_data)[:spare]).to eq(["spare_disk"]) end end describe "when there are two spares" do it "should add the spare disk to the hash as a single string" do zpool_data.concat ["spares", "spare_disk", "spare_disk2"] expect(provider.process_zpool_data(zpool_data)[:spare]).to eq(["spare_disk spare_disk2"]) end end describe "when there is a log" do it "should add the log disk to the hash" do zpool_data.concat ["logs", "log_disk"] expect(provider.process_zpool_data(zpool_data)[:log]).to eq(["log_disk"]) end end describe "when there are two logs" do it "should add the log disks to the hash as a single string" do zpool_data.concat ["spares", "spare_disk", "spare_disk2"] expect(provider.process_zpool_data(zpool_data)[:spare]).to eq(["spare_disk spare_disk2"]) end end describe "when the vdev is a single mirror" do it "should call create_multi_array with mirror" do zpool_data = ["mirrorpool", "mirror", "disk1", "disk2"] expect(provider.process_zpool_data(zpool_data)[:mirror]).to eq(["disk1 disk2"]) end end describe "when the vdev is a single mirror on solaris 10u9 or later" do it "should call create_multi_array with mirror" do zpool_data = ["mirrorpool", "mirror-0", "disk1", "disk2"] expect(provider.process_zpool_data(zpool_data)[:mirror]).to eq(["disk1 disk2"]) end end describe "when the vdev is a double mirror" do it "should call create_multi_array with mirror" do zpool_data = ["mirrorpool", "mirror", "disk1", "disk2", "mirror", "disk3", "disk4"] expect(provider.process_zpool_data(zpool_data)[:mirror]).to eq(["disk1 disk2", "disk3 disk4"]) end end describe "when the vdev is a double mirror on solaris 10u9 or later" do it "should call create_multi_array with mirror" do zpool_data = ["mirrorpool", "mirror-0", "disk1", "disk2", "mirror-1", "disk3", "disk4"] expect(provider.process_zpool_data(zpool_data)[:mirror]).to eq(["disk1 disk2", "disk3 disk4"]) end end describe "when the vdev is a raidz1" do it "should call create_multi_array with raidz1" do zpool_data = ["mirrorpool", "raidz1", "disk1", "disk2"] expect(provider.process_zpool_data(zpool_data)[:raidz]).to eq(["disk1 disk2"]) end end describe "when the vdev is a raidz1 on solaris 10u9 or later" do it "should call create_multi_array with raidz1" do zpool_data = ["mirrorpool", "raidz1-0", "disk1", "disk2"] expect(provider.process_zpool_data(zpool_data)[:raidz]).to eq(["disk1 disk2"]) end end describe "when the vdev is a raidz2" do it "should call create_multi_array with raidz2 and set the raid_parity" do zpool_data = ["mirrorpool", "raidz2", "disk1", "disk2"] pool = provider.process_zpool_data(zpool_data) expect(pool[:raidz]).to eq(["disk1 disk2"]) expect(pool[:raid_parity]).to eq("raidz2") end end describe "when the vdev is a raidz2 on solaris 10u9 or later" do it "should call create_multi_array with raidz2 and set the raid_parity" do zpool_data = ["mirrorpool", "raidz2-0", "disk1", "disk2"] pool = provider.process_zpool_data(zpool_data) expect(pool[:raidz]).to eq(["disk1 disk2"]) expect(pool[:raid_parity]).to eq("raidz2") end end end describe "when calling the getters and setters" do [:disk, :mirror, :raidz, :log, :spare].each do |field| describe "when calling #{field}" do it "should get the #{field} value from the current_pool hash" do pool_hash = {} pool_hash[field] = 'value' provider.stubs(:current_pool).returns(pool_hash) expect(provider.send(field)).to eq('value') end end describe "when setting the #{field}" do it "should fail if readonly #{field} values change" do provider.stubs(:current_pool).returns(Hash.new("currentvalue")) expect { provider.send((field.to_s + "=").intern, "shouldvalue") }.to raise_error(Puppet::Error, /can\'t be changed/) end end end end context '#create' do context "when creating disks for a zpool" do before do resource[:disk] = "disk1" end it "should call create with the build_vdevs value" do provider.expects(:zpool).with(:create, name, 'disk1') provider.create end it "should call create with the 'spares' and 'log' values" do resource[:spare] = ['value1'] resource[:log] = ['value2'] provider.expects(:zpool).with(:create, name, 'disk1', 'spare', 'value1', 'log', 'value2') provider.create end end context "when creating mirrors for a zpool" do it "executes 'create' for a single group of mirrored devices" do resource[:mirror] = ["disk1 disk2"] provider.expects(:zpool).with(:create, name, 'mirror', 'disk1', 'disk2') provider.create end it "repeats the 'mirror' keyword between groups of mirrored devices" do resource[:mirror] = ["disk1 disk2", "disk3 disk4"] provider.expects(:zpool).with(:create, name, 'mirror', 'disk1', 'disk2', 'mirror', 'disk3', 'disk4') provider.create end end describe "when creating raidz for a zpool" do it "executes 'create' for a single raidz group" do resource[:raidz] = ["disk1 disk2"] provider.expects(:zpool).with(:create, name, 'raidz1', 'disk1', 'disk2') provider.create end it "execute 'create' for a single raidz2 group" do resource[:raidz] = ["disk1 disk2"] resource[:raid_parity] = 'raidz2' provider.expects(:zpool).with(:create, name, 'raidz2', 'disk1', 'disk2') provider.create end it "repeats the 'raidz1' keyword between each group of raidz devices" do resource[:raidz] = ["disk1 disk2", "disk3 disk4"] provider.expects(:zpool).with(:create, name, 'raidz1', 'disk1', 'disk2', 'raidz1', 'disk3', 'disk4') provider.create end end end context '#delete' do it "should call zpool with destroy and the pool name" do provider.expects(:zpool).with(:destroy, name) provider.destroy end end context '#exists?' do it "should get the current pool" do provider.expects(:current_pool).returns({:pool => 'somepool'}) provider.exists? end it "should return false if the current_pool is absent" do provider.expects(:current_pool).returns({:pool => :absent}) expect(provider).not_to be_exists end it "should return true if the current_pool has values" do provider.expects(:current_pool).returns({:pool => name}) expect(provider).to be_exists end end end puppet-5.5.10/spec/unit/relationship_spec.rb0000644005276200011600000001342613417161721020771 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/relationship' describe Puppet::Relationship do before do @edge = Puppet::Relationship.new(:a, :b) end it "should have a :source attribute" do expect(@edge).to respond_to(:source) end it "should have a :target attribute" do expect(@edge).to respond_to(:target) end it "should have a :callback attribute" do @edge.callback = :foo expect(@edge.callback).to eq(:foo) end it "should have an :event attribute" do @edge.event = :NONE expect(@edge.event).to eq(:NONE) end it "should require a callback if a non-NONE event is specified" do expect { @edge.event = :something }.to raise_error(ArgumentError) end it "should have a :label attribute" do expect(@edge).to respond_to(:label) end it "should provide a :ref method that describes the edge" do @edge = Puppet::Relationship.new("a", "b") expect(@edge.ref).to eq("a => b") end it "should be able to produce a label as a hash with its event and callback" do @edge.callback = :foo @edge.event = :bar expect(@edge.label).to eq({:callback => :foo, :event => :bar}) end it "should work if nil options are provided" do expect { Puppet::Relationship.new("a", "b", nil) }.not_to raise_error end end describe Puppet::Relationship, " when initializing" do before do @edge = Puppet::Relationship.new(:a, :b) end it "should use the first argument as the source" do expect(@edge.source).to eq(:a) end it "should use the second argument as the target" do expect(@edge.target).to eq(:b) end it "should set the rest of the arguments as the event and callback" do @edge = Puppet::Relationship.new(:a, :b, :callback => :foo, :event => :bar) expect(@edge.callback).to eq(:foo) expect(@edge.event).to eq(:bar) end it "should accept events specified as strings" do @edge = Puppet::Relationship.new(:a, :b, "event" => :NONE) expect(@edge.event).to eq(:NONE) end it "should accept callbacks specified as strings" do @edge = Puppet::Relationship.new(:a, :b, "callback" => :foo) expect(@edge.callback).to eq(:foo) end end describe Puppet::Relationship, " when matching edges with no specified event" do before do @edge = Puppet::Relationship.new(:a, :b) end it "should not match :NONE" do expect(@edge).not_to be_match(:NONE) end it "should not match :ALL_EVENTS" do expect(@edge).not_to be_match(:ALL_EVENTS) end it "should not match any other events" do expect(@edge).not_to be_match(:whatever) end end describe Puppet::Relationship, " when matching edges with :NONE as the event" do before do @edge = Puppet::Relationship.new(:a, :b, :event => :NONE) end it "should not match :NONE" do expect(@edge).not_to be_match(:NONE) end it "should not match :ALL_EVENTS" do expect(@edge).not_to be_match(:ALL_EVENTS) end it "should not match other events" do expect(@edge).not_to be_match(:yayness) end end describe Puppet::Relationship, " when matching edges with :ALL as the event" do before do @edge = Puppet::Relationship.new(:a, :b, :event => :ALL_EVENTS, :callback => :whatever) end it "should not match :NONE" do expect(@edge).not_to be_match(:NONE) end it "should match :ALL_EVENTS" do expect(@edge).to be_match(:ALL_EVENTS) end it "should match all other events" do expect(@edge).to be_match(:foo) end end describe Puppet::Relationship, " when matching edges with a non-standard event" do before do @edge = Puppet::Relationship.new(:a, :b, :event => :random, :callback => :whatever) end it "should not match :NONE" do expect(@edge).not_to be_match(:NONE) end it "should not match :ALL_EVENTS" do expect(@edge).not_to be_match(:ALL_EVENTS) end it "should match events with the same name" do expect(@edge).to be_match(:random) end end describe Puppet::Relationship, "when converting to json" do before do @edge = Puppet::Relationship.new('a', 'b', :event => :random, :callback => :whatever) end it "should store the stringified source as the source in the data" do expect(JSON.parse(@edge.to_json)["source"]).to eq("a") end it "should store the stringified target as the target in the data" do expect(JSON.parse(@edge.to_json)['target']).to eq("b") end it "should store the jsonified event as the event in the data" do expect(JSON.parse(@edge.to_json)["event"]).to eq("random") end it "should not store an event when none is set" do @edge.event = nil expect(JSON.parse(@edge.to_json)).not_to include('event') end it "should store the jsonified callback as the callback in the data" do @edge.callback = "whatever" expect(JSON.parse(@edge.to_json)["callback"]).to eq("whatever") end it "should not store a callback when none is set in the edge" do @edge.callback = nil expect(JSON.parse(@edge.to_json)).not_to include('callback') end end describe Puppet::Relationship, "when converting from json" do it "should pass the source in as the first argument" do expect(Puppet::Relationship.from_data_hash("source" => "mysource", "target" => "mytarget").source).to eq('mysource') end it "should pass the target in as the second argument" do expect(Puppet::Relationship.from_data_hash("source" => "mysource", "target" => "mytarget").target).to eq('mytarget') end it "should pass the event as an argument if it's provided" do expect(Puppet::Relationship.from_data_hash("source" => "mysource", "target" => "mytarget", "event" => "myevent", "callback" => "eh").event).to eq(:myevent) end it "should pass the callback as an argument if it's provided" do expect(Puppet::Relationship.from_data_hash("source" => "mysource", "target" => "mytarget", "callback" => "mycallback").callback).to eq(:mycallback) end end puppet-5.5.10/spec/unit/reports/0000755005276200011600000000000013417162177016427 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/reports/http_spec.rb0000644005276200011600000000615513417161721020746 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/reports' processor = Puppet::Reports.report(:http) describe processor do subject { Puppet::Transaction::Report.new.extend(processor) } describe "when setting up the connection" do let(:http) { stub_everything "http" } let(:httpok) { Net::HTTPOK.new('1.1', 200, '') } before :each do http.expects(:post).returns(httpok) end it "configures the connection for ssl when using https" do Puppet[:reporturl] = 'https://testing:8080/the/path' Puppet::Network::HttpPool.expects(:http_instance).with( 'testing', 8080, true ).returns http subject.process end it "does not configure the connectino for ssl when using http" do Puppet[:reporturl] = "http://testing:8080/the/path" Puppet::Network::HttpPool.expects(:http_instance).with( 'testing', 8080, false ).returns http subject.process end end describe "when making a request" do let(:connection) { stub_everything "connection" } let(:httpok) { Net::HTTPOK.new('1.1', 200, '') } let(:options) { {:metric_id => [:puppet, :report, :http]} } before :each do Puppet::Network::HttpPool.expects(:http_instance).returns(connection) end it "should use the path specified by the 'reporturl' setting" do report_path = URI.parse(Puppet[:reporturl]).path connection.expects(:post).with(report_path, anything, anything, options).returns(httpok) subject.process end it "should use the username and password specified by the 'reporturl' setting" do Puppet[:reporturl] = "https://user:pass@myhost.mydomain:1234/report/upload" connection.expects(:post).with(anything, anything, anything, {:metric_id => [:puppet, :report, :http], :basic_auth => { :user => 'user', :password => 'pass' }}).returns(httpok) subject.process end it "should give the body as the report as YAML" do connection.expects(:post).with(anything, subject.to_yaml, anything, options).returns(httpok) subject.process end it "should set content-type to 'application/x-yaml'" do connection.expects(:post).with(anything, anything, has_entry("Content-Type" => "application/x-yaml"), options).returns(httpok) subject.process end Net::HTTPResponse::CODE_TO_OBJ.each do |code, klass| if code.to_i >= 200 and code.to_i < 300 it "should succeed on http code #{code}" do response = klass.new('1.1', code, '') connection.expects(:post).returns(response) Puppet.expects(:err).never subject.process end end if code.to_i >= 300 && ![301, 302, 307].include?(code.to_i) it "should log error on http code #{code}" do response = klass.new('1.1', code, '') connection.expects(:post).returns(response) Puppet.expects(:err) subject.process end end end end end puppet-5.5.10/spec/unit/reports/store_spec.rb0000644005276200011600000000334213417161721021116 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/reports' require 'time' require 'pathname' require 'tempfile' require 'fileutils' processor = Puppet::Reports.report(:store) describe processor do describe "#process" do include PuppetSpec::Files before :each do Puppet[:reportdir] = File.join(tmpdir('reports'), 'reports') @report = YAML.load_file(File.join(PuppetSpec::FIXTURE_DIR, 'yaml/report2.6.x.yaml')).extend processor end it "should create a report directory for the client if one doesn't exist" do @report.process expect(File).to be_directory(File.join(Puppet[:reportdir], @report.host)) end it "should write the report to the file in YAML" do Time.stubs(:now).returns(Time.utc(2011,01,06,12,00,00)) @report.process expect(File.read(File.join(Puppet[:reportdir], @report.host, "201101061200.yaml"))).to eq(@report.to_yaml) end it "rejects invalid hostnames" do @report.host = ".." Puppet::FileSystem.expects(:exist?).never expect { @report.process }.to raise_error(ArgumentError, /Invalid node/) end end describe "::destroy" do it "rejects invalid hostnames" do Puppet::FileSystem.expects(:unlink).never expect { processor.destroy("..") }.to raise_error(ArgumentError, /Invalid node/) end end describe "::validate_host" do ['..', 'hello/', '/hello', 'he/llo', 'hello/..', '.'].each do |node| it "rejects #{node.inspect}" do expect { processor.validate_host(node) }.to raise_error(ArgumentError, /Invalid node/) end end ['.hello', 'hello.', '..hi', 'hi..'].each do |node| it "accepts #{node.inspect}" do processor.validate_host(node) end end end end puppet-5.5.10/spec/unit/reports_spec.rb0000644005276200011600000000504613417161721017765 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/reports' describe Puppet::Reports do it "should instance-load report types" do expect(Puppet::Reports.instance_loader(:report)).to be_instance_of(Puppet::Util::Autoload) end it "should have a method for registering report types" do expect(Puppet::Reports).to respond_to(:register_report) end it "should have a method for retrieving report types by name" do expect(Puppet::Reports).to respond_to(:report) end it "should provide a method for returning documentation for all reports" do Puppet::Reports.expects(:loaded_instances).with(:report).returns([:one, :two]) one = mock 'one', :doc => "onedoc" two = mock 'two', :doc => "twodoc" Puppet::Reports.expects(:report).with(:one).returns(one) Puppet::Reports.expects(:report).with(:two).returns(two) doc = Puppet::Reports.reportdocs expect(doc.include?("onedoc")).to be_truthy expect(doc.include?("twodoc")).to be_truthy end end describe Puppet::Reports, " when loading report types" do it "should use the instance loader to retrieve report types" do Puppet::Reports.expects(:loaded_instance).with(:report, :myreporttype) Puppet::Reports.report(:myreporttype) end end describe Puppet::Reports, " when registering report types" do it "should evaluate the supplied block as code for a module" do Puppet::Reports.expects(:genmodule).returns(Module.new) Puppet::Reports.register_report(:testing) { } end it "should allow a successful report to be reloaded" do Puppet::Reports.register_report(:testing) { } Puppet::Reports.register_report(:testing) { } end it "should allow a failed report to be reloaded and show the correct exception both times" do expect { Puppet::Reports.register_report(:testing) { raise TypeError, 'failed report' } }.to raise_error(TypeError) expect { Puppet::Reports.register_report(:testing) { raise TypeError, 'failed report' } }.to raise_error(TypeError) end it "should extend the report type with the Puppet::Util::Docs module" do mod = stub 'module', :define_method => true Puppet::Reports.expects(:genmodule).with { |name, options, block| options[:extend] == Puppet::Util::Docs }.returns(mod) Puppet::Reports.register_report(:testing) { } end it "should define a :report_name method in the module that returns the name of the report" do mod = mock 'module' mod.expects(:define_method).with(:report_name) Puppet::Reports.expects(:genmodule).returns(mod) Puppet::Reports.register_report(:testing) { } end end puppet-5.5.10/spec/unit/resource/0000755005276200011600000000000013417162177016560 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/resource/status_spec.rb0000644005276200011600000001626413417161721021445 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/resource/status' describe Puppet::Resource::Status do include PuppetSpec::Files let(:resource) { Puppet::Type.type(:file).new(:path => make_absolute("/my/file")) } let(:containment_path) { ["foo", "bar", "baz"] } let(:status) { Puppet::Resource::Status.new(resource) } before do resource.stubs(:pathbuilder).returns(containment_path) end it "should compute type and title correctly" do expect(status.resource_type).to eq("File") expect(status.title).to eq(make_absolute("/my/file")) end [:file, :line, :evaluation_time].each do |attr| it "should support #{attr}" do status.send(attr.to_s + "=", "foo") expect(status.send(attr)).to eq("foo") end end [:skipped, :failed, :restarted, :failed_to_restart, :changed, :out_of_sync, :scheduled].each do |attr| it "should support #{attr}" do status.send(attr.to_s + "=", "foo") expect(status.send(attr)).to eq("foo") end it "should have a boolean method for determining whether it was #{attr}" do status.send(attr.to_s + "=", "foo") expect(status).to send("be_#{attr}") end end it "should accept a resource at initialization" do expect(Puppet::Resource::Status.new(resource).resource).not_to be_nil end it "should set its source description to the resource's path" do resource.expects(:path).returns "/my/path" expect(Puppet::Resource::Status.new(resource).source_description).to eq("/my/path") end it "should set its containment path" do expect(Puppet::Resource::Status.new(resource).containment_path).to eq(containment_path) end [:file, :line].each do |attr| it "should copy the resource's #{attr}" do resource.expects(attr).returns "foo" expect(Puppet::Resource::Status.new(resource).send(attr)).to eq("foo") end end it "should copy the resource's tags" do resource.tag('foo', 'bar') status = Puppet::Resource::Status.new(resource) expect(status).to be_tagged("foo") expect(status).to be_tagged("bar") end it "should always convert the resource to a string" do resource.expects(:to_s).returns "foo" expect(Puppet::Resource::Status.new(resource).resource).to eq("foo") end it 'should set the provider_used correctly' do expected_name = if Puppet::Util::Platform.windows? 'windows' else 'posix' end expect(status.provider_used).to eq(expected_name) end it "should support tags" do expect(Puppet::Resource::Status.ancestors).to include(Puppet::Util::Tagging) end it "should create a timestamp at its creation time" do expect(status.time).to be_instance_of(Time) end it "should support adding events" do event = Puppet::Transaction::Event.new(:name => :foobar) status.add_event(event) expect(status.events).to eq([event]) end it "should use '<<' to add events" do event = Puppet::Transaction::Event.new(:name => :foobar) expect(status << event).to equal(status) expect(status.events).to eq([event]) end it "fails and records a failure event with a given message" do status.fail_with_event("foo fail") event = status.events[0] expect(event.message).to eq("foo fail") expect(event.status).to eq("failure") expect(event.name).to eq(:resource_error) expect(status.failed?).to be_truthy end it "fails and records a failure event with a given exception" do error = StandardError.new("the message") resource.expects(:log_exception).with(error, "Could not evaluate: the message") status.expects(:fail_with_event).with("the message") status.failed_because(error) end it "should count the number of successful events and set changed" do 3.times{ status << Puppet::Transaction::Event.new(:status => 'success') } expect(status.change_count).to eq(3) expect(status.changed).to eq(true) expect(status.out_of_sync).to eq(true) end it "should not start with any changes" do expect(status.change_count).to eq(0) expect(status.changed).to eq(false) expect(status.out_of_sync).to eq(false) end it "should not treat failure, audit, or noop events as changed" do ['failure', 'audit', 'noop'].each do |s| status << Puppet::Transaction::Event.new(:status => s) end expect(status.change_count).to eq(0) expect(status.changed).to eq(false) end it "should not treat audit events as out of sync" do status << Puppet::Transaction::Event.new(:status => 'audit') expect(status.out_of_sync_count).to eq(0) expect(status.out_of_sync).to eq(false) end ['failure', 'noop', 'success'].each do |event_status| it "should treat #{event_status} events as out of sync" do 3.times do status << Puppet::Transaction::Event.new(:status => event_status) end expect(status.out_of_sync_count).to eq(3) expect(status.out_of_sync).to eq(true) end end context 'when serializing' do let(:status) do s = Puppet::Resource::Status.new(resource) s.file = '/foo.rb' s.line = 27 s.evaluation_time = 2.7 s.tags = %w{one two} s << Puppet::Transaction::Event.new(:name => :mode_changed, :status => 'audit') s.failed = false s.changed = true s.out_of_sync = true s.skipped = false s.provider_used = 'provider_used_class_name' s.failed_to_restart = false s end it 'should round trip through json' do expect(status.containment_path).to eq(containment_path) tripped = Puppet::Resource::Status.from_data_hash(JSON.parse(status.to_json)) expect(tripped.title).to eq(status.title) expect(tripped.containment_path).to eq(status.containment_path) expect(tripped.file).to eq(status.file) expect(tripped.line).to eq(status.line) expect(tripped.resource).to eq(status.resource) expect(tripped.resource_type).to eq(status.resource_type) expect(tripped.provider_used).to eq(status.provider_used) expect(tripped.evaluation_time).to eq(status.evaluation_time) expect(tripped.tags).to eq(status.tags) expect(tripped.time).to eq(status.time) expect(tripped.failed).to eq(status.failed) expect(tripped.changed).to eq(status.changed) expect(tripped.out_of_sync).to eq(status.out_of_sync) expect(tripped.skipped).to eq(status.skipped) expect(tripped.failed_to_restart).to eq(status.failed_to_restart) expect(tripped.change_count).to eq(status.change_count) expect(tripped.out_of_sync_count).to eq(status.out_of_sync_count) expect(events_as_hashes(tripped)).to eq(events_as_hashes(status)) end it 'to_data_hash returns value that is instance of to Data' do expect(Puppet::Pops::Types::TypeFactory.data.instance?(status.to_data_hash)).to be_truthy end def events_as_hashes(report) report.events.collect do |e| { :audited => e.audited, :property => e.property, :previous_value => e.previous_value, :desired_value => e.desired_value, :historical_value => e.historical_value, :message => e.message, :name => e.name, :status => e.status, :time => e.time, } end end end end puppet-5.5.10/spec/unit/resource/type_collection_spec.rb0000644005276200011600000003013713417161721023311 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/resource/type_collection' require 'puppet/resource/type' describe Puppet::Resource::TypeCollection do include PuppetSpec::Files let(:environment) { Puppet::Node::Environment.create(:testing, []) } before do @instance = Puppet::Resource::Type.new(:hostclass, "foo") @code = Puppet::Resource::TypeCollection.new(environment) end it "should consider '<<' to be an alias to 'add' but should return self" do @code.expects(:add).with "foo" @code.expects(:add).with "bar" @code << "foo" << "bar" end it "should set itself as the code collection for added resource types" do node = Puppet::Resource::Type.new(:node, "foo") @code.add(node) expect(@code.node("foo")).to equal(node) expect(node.resource_type_collection).to equal(@code) end it "should store node resource types as nodes" do node = Puppet::Resource::Type.new(:node, "foo") @code.add(node) expect(@code.node("foo")).to equal(node) end it "should fail if a duplicate node is added" do @code.add(Puppet::Resource::Type.new(:node, "foo")) expect do @code.add(Puppet::Resource::Type.new(:node, "foo")) end.to raise_error(Puppet::ParseError, /cannot redefine/) end it "should store hostclasses as hostclasses" do klass = Puppet::Resource::Type.new(:hostclass, "foo") @code.add(klass) expect(@code.hostclass("foo")).to equal(klass) end it "errors if an attempt is made to merge hostclasses of the same name" do klass1 = Puppet::Resource::Type.new(:hostclass, "foo", :doc => "first") klass2 = Puppet::Resource::Type.new(:hostclass, "foo", :doc => "second") expect { @code.add(klass1) @code.add(klass2) }.to raise_error(/.*is already defined; cannot redefine/) end it "should store definitions as definitions" do define = Puppet::Resource::Type.new(:definition, "foo") @code.add(define) expect(@code.definition("foo")).to equal(define) end it "should fail if a duplicate definition is added" do @code.add(Puppet::Resource::Type.new(:definition, "foo")) expect do @code.add(Puppet::Resource::Type.new(:definition, "foo")) end.to raise_error(Puppet::ParseError, /cannot be redefined/) end it "should remove all nodes, classes, definitions, and applications when cleared" do loader = Puppet::Resource::TypeCollection.new(environment) loader.add Puppet::Resource::Type.new(:hostclass, "class") loader.add Puppet::Resource::Type.new(:definition, "define") loader.add Puppet::Resource::Type.new(:node, "node") loader.add Puppet::Resource::Type.new(:application, "application") loader.clear expect(loader.hostclass("class")).to be_nil expect(loader.definition("define")).to be_nil expect(loader.node("node")).to be_nil expect(loader.node("application")).to be_nil end describe "when looking up names" do before do @type = Puppet::Resource::Type.new(:hostclass, "ns::klass") end it "should not attempt to import anything when the type is already defined" do @code.add @type @code.loader.expects(:import).never expect(@code.find_hostclass("ns::klass")).to equal(@type) end describe "that need to be loaded" do it "should use the loader to load the files" do @code.loader.expects(:try_load_fqname).with(:hostclass, "klass") @code.find_hostclass("klass") end it "should use the loader to load the files" do @code.loader.expects(:try_load_fqname).with(:hostclass, "ns::klass") @code.find_hostclass("ns::klass") end it "should downcase the name and downcase and array-fy the namespaces before passing to the loader" do @code.loader.expects(:try_load_fqname).with(:hostclass, "ns::klass") @code.find_hostclass("ns::klass") end it "should use the class returned by the loader" do @code.loader.expects(:try_load_fqname).returns(:klass) @code.expects(:hostclass).with("ns::klass").returns(false) expect(@code.find_hostclass("ns::klass")).to eq(:klass) end it "should return nil if the name isn't found" do @code.loader.stubs(:try_load_fqname).returns(nil) expect(@code.find_hostclass("Ns::Klass")).to be_nil end it "already-loaded names at broader scopes should not shadow autoloaded names" do @code.add Puppet::Resource::Type.new(:hostclass, "bar") @code.loader.expects(:try_load_fqname).with(:hostclass, "foo::bar").returns(:foobar) expect(@code.find_hostclass("foo::bar")).to eq(:foobar) end context 'when debugging' do # This test requires that debugging is on, it will otherwise not make a call to debug, # which is the easiest way to detect that that a certain path has been taken. before(:each) do Puppet.debug = true end after (:each) do Puppet.debug = false end it "should not try to autoload names that we couldn't autoload in a previous step if ignoremissingtypes is enabled" do Puppet[:ignoremissingtypes] = true @code.loader.expects(:try_load_fqname).with(:hostclass, "ns::klass").returns(nil) expect(@code.find_hostclass("ns::klass")).to be_nil Puppet.expects(:debug).at_least_once.with {|msg| msg =~ /Not attempting to load hostclass/} expect(@code.find_hostclass("ns::klass")).to be_nil end end end end KINDS = %w{hostclass node definition application} KINDS.each do |data| describe "behavior of add for #{data}" do it "should return the added #{data}" do loader = Puppet::Resource::TypeCollection.new(environment) instance = Puppet::Resource::Type.new(data, "foo") expect(loader.add(instance)).to equal(instance) end it "should retrieve #{data} insensitive to case" do loader = Puppet::Resource::TypeCollection.new(environment) instance = Puppet::Resource::Type.new(data, "Bar") loader.add instance expect(loader.send(data, "bAr")).to equal(instance) end it "should return nil when asked for a #{data} that has not been added" do expect(Puppet::Resource::TypeCollection.new(environment).send(data, "foo")).to be_nil end if data != "node" it "should fail if an application with the same name is added" do loader = Puppet::Resource::TypeCollection.new(environment) instance = Puppet::Resource::Type.new(data, "foo") application = Puppet::Resource::Type.new(:application, "foo") loader.add(instance) expect { loader.add(application) }.to raise_error(Puppet::ParseError, /redefine/) end it "should fail if there is an application with the same name" do loader = Puppet::Resource::TypeCollection.new(environment) application = Puppet::Resource::Type.new(:application, "foo") instance = Puppet::Resource::Type.new(data, "foo") loader.add(instance) expect { loader.add(application) }.to raise_error(Puppet::ParseError, /redefine/) end end end end describe "when finding a qualified instance" do it "should return any found instance if the instance name is fully qualified" do loader = Puppet::Resource::TypeCollection.new(environment) instance = Puppet::Resource::Type.new(:hostclass, "foo::bar") loader.add instance expect(loader.find_hostclass("::foo::bar")).to equal(instance) end it "should return nil if the instance name is fully qualified and no such instance exists" do loader = Puppet::Resource::TypeCollection.new(environment) expect(loader.find_hostclass("::foo::bar")).to be_nil end it "should be able to find classes in the base namespace" do loader = Puppet::Resource::TypeCollection.new(environment) instance = Puppet::Resource::Type.new(:hostclass, "foo") loader.add instance expect(loader.find_hostclass("foo")).to equal(instance) end it "should return the unqualified object if it exists in a provided namespace" do loader = Puppet::Resource::TypeCollection.new(environment) instance = Puppet::Resource::Type.new(:hostclass, "foo::bar") loader.add instance expect(loader.find_hostclass("foo::bar")).to equal(instance) end it "should return nil if the object cannot be found" do loader = Puppet::Resource::TypeCollection.new(environment) instance = Puppet::Resource::Type.new(:hostclass, "foo::bar::baz") loader.add instance expect(loader.find_hostclass("foo::bar::eh")).to be_nil end describe "when topscope has a class that has the same name as a local class" do before do @loader = Puppet::Resource::TypeCollection.new(environment) [ "foo::bar", "bar" ].each do |name| @loader.add Puppet::Resource::Type.new(:hostclass, name) end end it "looks up the given name, no more, no less" do expect(@loader.find_hostclass("bar").name).to eq('bar') expect(@loader.find_hostclass("::bar").name).to eq('bar') expect(@loader.find_hostclass("foo::bar").name).to eq('foo::bar') expect(@loader.find_hostclass("::foo::bar").name).to eq('foo::bar') end end it "should not look in the local scope for classes when the name is qualified" do @loader = Puppet::Resource::TypeCollection.new(environment) @loader.add Puppet::Resource::Type.new(:hostclass, "foo::bar") expect(@loader.find_hostclass("::bar")).to eq(nil) end end it "should be able to find nodes" do node = Puppet::Resource::Type.new(:node, "bar") loader = Puppet::Resource::TypeCollection.new(environment) loader.add(node) expect(loader.find_node("bar")).to eq(node) end it "should indicate whether any nodes are defined" do loader = Puppet::Resource::TypeCollection.new(environment) loader.add_node(Puppet::Resource::Type.new(:node, "foo")) expect(loader).to be_nodes end it "should indicate whether no nodes are defined" do expect(Puppet::Resource::TypeCollection.new(environment)).not_to be_nodes end describe "when finding nodes" do before :each do @loader = Puppet::Resource::TypeCollection.new(environment) end it "should return any node whose name exactly matches the provided node name" do node = Puppet::Resource::Type.new(:node, "foo") @loader << node expect(@loader.node("foo")).to equal(node) end it "should return the first regex node whose regex matches the provided node name" do node1 = Puppet::Resource::Type.new(:node, /\w/) node2 = Puppet::Resource::Type.new(:node, /\d/) @loader << node1 << node2 expect(@loader.node("foo10")).to equal(node1) end it "should preferentially return a node whose name is string-equal over returning a node whose regex matches a provided name" do node1 = Puppet::Resource::Type.new(:node, /\w/) node2 = Puppet::Resource::Type.new(:node, "foo") @loader << node1 << node2 expect(@loader.node("foo")).to equal(node2) end end describe "when determining the configuration version" do before do @code = Puppet::Resource::TypeCollection.new(environment) end it "should default to the current time" do time = Time.now Time.stubs(:now).returns time expect(@code.version).to eq(time.to_i) end context "when config_version script is specified" do let(:environment) { Puppet::Node::Environment.create(:testing, [], '', '/my/foo') } it "should use the output of the environment's config_version setting if one is provided" do Puppet::Util::Execution.expects(:execute) .with(["/my/foo"]) .returns Puppet::Util::Execution::ProcessOutput.new("output\n", 0) expect(@code.version).to be_instance_of(String) expect(@code.version).to eq("output") end it "should raise a puppet parser error if executing config_version fails" do Puppet::Util::Execution.expects(:execute).raises(Puppet::ExecutionFailure.new("msg")) expect { @code.version }.to raise_error(Puppet::ParseError) end end end end puppet-5.5.10/spec/unit/resource/type_spec.rb0000644005276200011600000007673013417161721021107 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/resource/type' require 'puppet/pops' require 'matchers/json' describe Puppet::Resource::Type do include JSONMatchers it "should have a 'name' attribute" do expect(Puppet::Resource::Type.new(:hostclass, "foo").name).to eq("foo") end [:code, :doc, :line, :file, :resource_type_collection].each do |attr| it "should have a '#{attr}' attribute" do type = Puppet::Resource::Type.new(:hostclass, "foo") type.send(attr.to_s + "=", "yay") expect(type.send(attr)).to eq("yay") end end [:hostclass, :node, :definition].each do |type| it "should know when it is a #{type}" do expect(Puppet::Resource::Type.new(type, "foo").send("#{type}?")).to be_truthy end end describe "when a node" do it "should allow a regex as its name" do expect { Puppet::Resource::Type.new(:node, /foo/) }.not_to raise_error end it "should allow an AST::HostName instance as its name" do regex = Puppet::Parser::AST::Regex.new(:value => /foo/) name = Puppet::Parser::AST::HostName.new(:value => regex) expect { Puppet::Resource::Type.new(:node, name) }.not_to raise_error end it "should match against the regexp in the AST::HostName when a HostName instance is provided" do regex = Puppet::Parser::AST::Regex.new(:value => /\w/) name = Puppet::Parser::AST::HostName.new(:value => regex) node = Puppet::Resource::Type.new(:node, name) expect(node.match("foo")).to be_truthy end it "should return the value of the hostname if provided a string-form AST::HostName instance as the name" do name = Puppet::Parser::AST::HostName.new(:value => "foo") node = Puppet::Resource::Type.new(:node, name) expect(node.name).to eq("foo") end describe "and the name is a regex" do it "should have a method that indicates that this is the case" do expect(Puppet::Resource::Type.new(:node, /w/)).to be_name_is_regex end it "should set its namespace to ''" do expect(Puppet::Resource::Type.new(:node, /w/).namespace).to eq("") end it "should return the regex converted to a string when asked for its name" do expect(Puppet::Resource::Type.new(:node, /ww/).name).to eq("__node_regexp__ww") end it "should downcase the regex when returning the name as a string" do expect(Puppet::Resource::Type.new(:node, /W/).name).to eq("__node_regexp__w") end it "should remove non-alpha characters when returning the name as a string" do expect(Puppet::Resource::Type.new(:node, /w*w/).name).not_to include("*") end it "should remove leading dots when returning the name as a string" do expect(Puppet::Resource::Type.new(:node, /.ww/).name).not_to match(/^\./) end it "should have a method for matching its regex name against a provided name" do expect(Puppet::Resource::Type.new(:node, /.ww/)).to respond_to(:match) end it "should return true when its regex matches the provided name" do expect(Puppet::Resource::Type.new(:node, /\w/).match("foo")).to be_truthy end it "should return true when its regex matches the provided name" do expect(Puppet::Resource::Type.new(:node, /\w/).match("foo")).to be_truthy end it "should return false when its regex does not match the provided name" do expect(!!Puppet::Resource::Type.new(:node, /\d/).match("foo")).to be_falsey end it "should return true when its name, as a string, is matched against an equal string" do expect(Puppet::Resource::Type.new(:node, "foo").match("foo")).to be_truthy end it "should return false when its name is matched against an unequal string" do expect(Puppet::Resource::Type.new(:node, "foo").match("bar")).to be_falsey end it "should match names insensitive to case" do expect(Puppet::Resource::Type.new(:node, "fOo").match("foO")).to be_truthy end end end describe "when initializing" do it "should require a resource super type" do expect(Puppet::Resource::Type.new(:hostclass, "foo").type).to eq(:hostclass) end it "should fail if provided an invalid resource super type" do expect { Puppet::Resource::Type.new(:nope, "foo") }.to raise_error(ArgumentError) end it "should set its name to the downcased, stringified provided name" do expect(Puppet::Resource::Type.new(:hostclass, "Foo::Bar".intern).name).to eq("foo::bar") end it "should set its namespace to the downcased, stringified qualified name for classes" do expect(Puppet::Resource::Type.new(:hostclass, "Foo::Bar::Baz".intern).namespace).to eq("foo::bar::baz") end [:definition, :node].each do |type| it "should set its namespace to the downcased, stringified qualified portion of the name for #{type}s" do expect(Puppet::Resource::Type.new(type, "Foo::Bar::Baz".intern).namespace).to eq("foo::bar") end end %w{code line file doc}.each do |arg| it "should set #{arg} if provided" do type = Puppet::Resource::Type.new(:hostclass, "foo", arg.to_sym => "something") expect(type.send(arg)).to eq("something") end end it "should set any provided arguments with the keys as symbols" do type = Puppet::Resource::Type.new(:hostclass, "foo", :arguments => {:foo => "bar", :baz => "biz"}) expect(type).to be_valid_parameter("foo") expect(type).to be_valid_parameter("baz") end it "should set any provided arguments with they keys as strings" do type = Puppet::Resource::Type.new(:hostclass, "foo", :arguments => {"foo" => "bar", "baz" => "biz"}) expect(type).to be_valid_parameter(:foo) expect(type).to be_valid_parameter(:baz) end it "should function if provided no arguments" do type = Puppet::Resource::Type.new(:hostclass, "foo") expect(type).not_to be_valid_parameter(:foo) end end describe "when testing the validity of an attribute" do it "should return true if the parameter was typed at initialization" do expect(Puppet::Resource::Type.new(:hostclass, "foo", :arguments => {"foo" => "bar"})).to be_valid_parameter("foo") end it "should return true if it is a metaparam" do expect(Puppet::Resource::Type.new(:hostclass, "foo")).to be_valid_parameter("require") end it "should return true if the parameter is named 'name'" do expect(Puppet::Resource::Type.new(:hostclass, "foo")).to be_valid_parameter("name") end it "should return false if it is not a metaparam and was not provided at initialization" do expect(Puppet::Resource::Type.new(:hostclass, "foo")).not_to be_valid_parameter("yayness") end end describe "when setting its parameters in the scope" do let(:parser) { Puppet::Pops::Parser::Parser.new() } def wrap3x(expression) Puppet::Parser::AST::PopsBridge::Expression.new(:value => expression.model) end def parse_expression(expr_string) wrap3x(parser.parse_string(expr_string)) end def number_expression(number) wrap3x(Puppet::Pops::Model::Factory.NUMBER(number)) end def variable_expression(name) wrap3x(Puppet::Pops::Model::Factory.QNAME(name).var()) end def matchref_expression(number) wrap3x(Puppet::Pops::Model::Factory.NUMBER(number).var()) end before(:each) do compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("foo")) @scope = Puppet::Parser::Scope.new(compiler, :source => stub("source")) @resource = Puppet::Parser::Resource.new(:foo, "bar", :scope => @scope) @type = Puppet::Resource::Type.new(:definition, "foo") @resource.environment.known_resource_types.add @type Puppet.push_context(:loaders => compiler.loaders) end after(:each) do Puppet.pop_context end ['module_name', 'name', 'title'].each do |variable| it "should allow #{variable} to be evaluated as param default" do @type.instance_eval { @module_name = "bar" } @type.set_arguments :foo => variable_expression(variable) @type.set_resource_parameters(@resource, @scope) expect(@scope['foo']).to eq('bar') end end # this test is to clarify a crazy edge case # if you specify these special names as params, the resource # will override the special variables it "should allow the resource to override defaults" do @type.set_arguments :name => nil @resource[:name] = 'foobar' @type.set_arguments :foo => variable_expression('name') @type.set_resource_parameters(@resource, @scope) expect(@scope['foo']).to eq('foobar') end context 'referencing a variable to the left of the default expression' do it 'is possible when the referenced variable uses a default' do @type.set_arguments({ :first => number_expression(10), :second => variable_expression('first'), }) @type.set_resource_parameters(@resource, @scope) expect(@scope['first']).to eq(10) expect(@scope['second']).to eq(10) end it 'is possible when the referenced variable is given a value' do @type.set_arguments({ :first => number_expression(10), :second => variable_expression('first'), }) @resource[:first] = 2 @type.set_resource_parameters(@resource, @scope) expect(@scope['first']).to eq(2) expect(@scope['second']).to eq(2) end it 'is possible when the referenced variable is an array produced by match function' do @type.set_arguments({ :first => parse_expression("'hello'.match(/(h)(.*)/)"), :second => parse_expression('$first[0]'), :third => parse_expression('$first[1]') }) @type.set_resource_parameters(@resource, @scope) expect(@scope['first']).to eq(['hello', 'h', 'ello']) expect(@scope['second']).to eq('hello') expect(@scope['third']).to eq('h') end it 'fails when the referenced variable is unassigned' do @type.set_arguments({ :first => nil, :second => variable_expression('first'), }) expect { @type.set_resource_parameters(@resource, @scope) }.to raise_error( Puppet::Error, 'Foo[bar]: expects a value for parameter $first') end it 'does not clobber a given value' do @type.set_arguments({ :first => number_expression(10), :second => variable_expression('first'), }) @resource[:first] = 2 @resource[:second] = 5 @type.set_resource_parameters(@resource, @scope) expect(@scope['first']).to eq(2) expect(@scope['second']).to eq(5) end end context 'referencing a variable to the right of the default expression' do before :each do @type.set_arguments({ :first => number_expression(10), :second => variable_expression('third'), :third => number_expression(20) }) end it 'no error is raised when no defaults are evaluated' do @resource[:first] = 1 @resource[:second] = 2 @resource[:third] = 3 @type.set_resource_parameters(@resource, @scope) expect(@scope['first']).to eq(1) expect(@scope['second']).to eq(2) expect(@scope['third']).to eq(3) end it 'no error is raised unless the referencing default expression is evaluated' do @resource[:second] = 2 @type.set_resource_parameters(@resource, @scope) expect(@scope['first']).to eq(10) expect(@scope['second']).to eq(2) expect(@scope['third']).to eq(20) end it 'fails when the default expression is evaluated' do @resource[:first] = 1 expect { @type.set_resource_parameters(@resource, @scope) }.to raise_error(Puppet::Error, 'Foo[bar]: default expression for $second tries to illegally access not yet evaluated $third') end end it 'does not allow a variable to be referenced from its own default expression' do @type.set_arguments({ :first => variable_expression('first') }) expect { @type.set_resource_parameters(@resource, @scope) }.to raise_error(Puppet::Error, 'Foo[bar]: default expression for $first tries to illegally access not yet evaluated $first') end context 'when using match scope' do it '$n evaluates to undef at the top level' do @type.set_arguments({ :first => matchref_expression('0'), :second => matchref_expression('1'), }) @type.set_resource_parameters(@resource, @scope) expect(@scope).not_to include('first') expect(@scope).not_to include('second') end it 'a match scope to the left of a parameter is not visible to it' do @type.set_arguments({ :first => parse_expression("['hello' =~ /(h)(.*)/, $1, $2]"), :second => matchref_expression('1'), }) @type.set_resource_parameters(@resource, @scope) expect(@scope['first']).to eq([true, 'h', 'ello']) expect(@scope['second']).to be_nil end it 'match scopes nests per parameter' do @type.set_arguments({ :first => parse_expression("['hi' =~ /(h)(.*)/, $1, if 'foo' =~ /f(oo)/ { $1 }, $1, $2]"), :second => matchref_expression('0'), }) @type.set_resource_parameters(@resource, @scope) expect(@scope['first']).to eq([true, 'h', 'oo', 'h', 'i']) expect(@scope['second']).to be_nil end end it "should set each of the resource's parameters as variables in the scope" do @type.set_arguments :foo => nil, :boo => nil @resource[:foo] = "bar" @resource[:boo] = "baz" @type.set_resource_parameters(@resource, @scope) expect(@scope['foo']).to eq("bar") expect(@scope['boo']).to eq("baz") end it "should set the variables as strings" do @type.set_arguments :foo => nil @resource[:foo] = "bar" @type.set_resource_parameters(@resource, @scope) expect(@scope['foo']).to eq("bar") end it "should fail if any of the resource's parameters are not valid attributes" do @type.set_arguments :foo => nil @resource[:boo] = "baz" expect { @type.set_resource_parameters(@resource, @scope) }.to raise_error(Puppet::ParseError) end it "should evaluate and set its default values as variables for parameters not provided by the resource" do @type.set_arguments :foo => Puppet::Parser::AST::Leaf.new(:value => "something") @type.set_resource_parameters(@resource, @scope) expect(@scope['foo']).to eq("something") end it "should set all default values as parameters in the resource" do @type.set_arguments :foo => Puppet::Parser::AST::Leaf.new(:value => "something") @type.set_resource_parameters(@resource, @scope) expect(@resource[:foo]).to eq("something") end it "should fail if the resource does not provide a value for a required argument" do @type.set_arguments :foo => nil expect { @type.set_resource_parameters(@resource, @scope) }.to raise_error(Puppet::ParseError) end it "should set the resource's title as a variable if not otherwise provided" do @type.set_resource_parameters(@resource, @scope) expect(@scope['title']).to eq("bar") end it "should set the resource's name as a variable if not otherwise provided" do @type.set_resource_parameters(@resource, @scope) expect(@scope['name']).to eq("bar") end it "should set its module name in the scope if available" do @type.instance_eval { @module_name = "mymod" } @type.set_resource_parameters(@resource, @scope) expect(@scope["module_name"]).to eq("mymod") end it "should set its caller module name in the scope if available" do @scope.expects(:parent_module_name).returns "mycaller" @type.set_resource_parameters(@resource, @scope) expect(@scope["caller_module_name"]).to eq("mycaller") end end describe "when describing and managing parent classes" do before do environment = Puppet::Node::Environment.create(:testing, []) @krt = environment.known_resource_types @parent = Puppet::Resource::Type.new(:hostclass, "bar") @krt.add @parent @child = Puppet::Resource::Type.new(:hostclass, "foo", :parent => "bar") @krt.add @child @scope = Puppet::Parser::Scope.new(Puppet::Parser::Compiler.new(Puppet::Node.new("foo", :environment => environment))) end it "should be able to define a parent" do Puppet::Resource::Type.new(:hostclass, "foo", :parent => "bar") end it "should use the code collection to find the parent resource type" do expect(@child.parent_type(@scope)).to equal(@parent) end it "should be able to find parent nodes" do parent = Puppet::Resource::Type.new(:node, "bar") @krt.add parent child = Puppet::Resource::Type.new(:node, "foo", :parent => "bar") @krt.add child expect(child.parent_type(@scope)).to equal(parent) end it "should cache a reference to the parent type" do @krt.stubs(:hostclass).with("foo::bar").returns nil @krt.expects(:hostclass).with("bar").once.returns @parent @child.parent_type(@scope) @child.parent_type end it "should correctly state when it is another type's child" do @child.parent_type(@scope) expect(@child).to be_child_of(@parent) end it "should be considered the child of a parent's parent" do @grandchild = Puppet::Resource::Type.new(:hostclass, "baz", :parent => "foo") @krt.add @grandchild @child.parent_type(@scope) @grandchild.parent_type(@scope) expect(@grandchild).to be_child_of(@parent) end it "should correctly state when it is not another type's child" do @notchild = Puppet::Resource::Type.new(:hostclass, "baz") @krt.add @notchild expect(@notchild).not_to be_child_of(@parent) end end describe "when evaluating its code" do before do @compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("mynode")) @scope = Puppet::Parser::Scope.new @compiler @resource = Puppet::Parser::Resource.new(:class, "foo", :scope => @scope) # This is so the internal resource lookup works, yo. @compiler.catalog.add_resource @resource @type = Puppet::Resource::Type.new(:hostclass, "foo") @resource.environment.known_resource_types.add @type end it "should add node regex captures to its scope" do @type = Puppet::Resource::Type.new(:node, /f(\w)o(.*)$/) match = @type.match('foo') code = stub 'code' @type.stubs(:code).returns code subscope = stub 'subscope', :compiler => @compiler @scope.expects(:newscope).with(:source => @type, :resource => @resource).returns subscope subscope.expects(:with_guarded_scope).yields subscope.expects(:ephemeral_from).with(match, nil, nil).returns subscope code.expects(:safeevaluate).with(subscope) # Just to keep the stub quiet about intermediate calls @type.expects(:set_resource_parameters).with(@resource, subscope) @type.evaluate_code(@resource) end it "should add hostclass names to the classes list" do @type.evaluate_code(@resource) expect(@compiler.catalog.classes).to be_include("foo") end it "should not add defined resource names to the classes list" do @type = Puppet::Resource::Type.new(:definition, "foo") @type.evaluate_code(@resource) expect(@compiler.catalog.classes).not_to be_include("foo") end it "should set all of its parameters in a subscope" do subscope = stub 'subscope', :compiler => @compiler @scope.expects(:newscope).with(:source => @type, :resource => @resource).returns subscope @type.expects(:set_resource_parameters).with(@resource, subscope) @type.evaluate_code(@resource) end it "should not create a subscope for the :main class" do @resource.stubs(:title).returns(:main) @type.expects(:subscope).never @type.expects(:set_resource_parameters).with(@resource, @scope) @type.evaluate_code(@resource) end it "should store the class scope" do @type.evaluate_code(@resource) expect(@scope.class_scope(@type)).to be_instance_of(@scope.class) end it "should still create a scope but not store it if the type is a definition" do @type = Puppet::Resource::Type.new(:definition, "foo") @type.evaluate_code(@resource) expect(@scope.class_scope(@type)).to be_nil end it "should evaluate the AST code if any is provided" do code = stub 'code' @type.stubs(:code).returns code code.expects(:safeevaluate).with kind_of(Puppet::Parser::Scope) @type.evaluate_code(@resource) end it "should noop if there is no code" do @type.expects(:code).returns nil @type.evaluate_code(@resource) end describe "and it has a parent class" do before do @parent_type = Puppet::Resource::Type.new(:hostclass, "parent") @type.parent = "parent" @parent_resource = Puppet::Parser::Resource.new(:class, "parent", :scope => @scope) @compiler.add_resource @scope, @parent_resource @type.resource_type_collection = @scope.environment.known_resource_types @type.resource_type_collection.add @parent_type end it "should evaluate the parent's resource" do @type.parent_type(@scope) @type.evaluate_code(@resource) expect(@scope.class_scope(@parent_type)).not_to be_nil end it "should not evaluate the parent's resource if it has already been evaluated" do @parent_resource.evaluate @type.parent_type(@scope) @parent_resource.expects(:evaluate).never @type.evaluate_code(@resource) end it "should use the parent's scope as its base scope" do @type.parent_type(@scope) @type.evaluate_code(@resource) expect(@scope.class_scope(@type).parent.object_id).to eq(@scope.class_scope(@parent_type).object_id) end end describe "and it has a parent node" do before do @type = Puppet::Resource::Type.new(:node, "foo") @parent_type = Puppet::Resource::Type.new(:node, "parent") @type.parent = "parent" @parent_resource = Puppet::Parser::Resource.new(:node, "parent", :scope => @scope) @compiler.add_resource @scope, @parent_resource @type.resource_type_collection = @scope.environment.known_resource_types @type.resource_type_collection.add(@parent_type) end it "should evaluate the parent's resource" do @type.parent_type(@scope) @type.evaluate_code(@resource) expect(@scope.class_scope(@parent_type)).not_to be_nil end it "should not evaluate the parent's resource if it has already been evaluated" do @parent_resource.evaluate @type.parent_type(@scope) @parent_resource.expects(:evaluate).never @type.evaluate_code(@resource) end it "should use the parent's scope as its base scope" do @type.parent_type(@scope) @type.evaluate_code(@resource) expect(@scope.class_scope(@type).parent.object_id).to eq(@scope.class_scope(@parent_type).object_id) end end end describe "when creating a resource" do before do env = Puppet::Node::Environment.create('env', []) @node = Puppet::Node.new("foo", :environment => env) @compiler = Puppet::Parser::Compiler.new(@node) @scope = Puppet::Parser::Scope.new(@compiler) @top = Puppet::Resource::Type.new :hostclass, "top" @middle = Puppet::Resource::Type.new :hostclass, "middle", :parent => "top" @code = env.known_resource_types @code.add @top @code.add @middle end it "should create a resource instance" do expect(@top.ensure_in_catalog(@scope)).to be_instance_of(Puppet::Parser::Resource) end it "should set its resource type to 'class' when it is a hostclass" do expect(Puppet::Resource::Type.new(:hostclass, "top").ensure_in_catalog(@scope).type).to eq("Class") end it "should set its resource type to 'node' when it is a node" do expect(Puppet::Resource::Type.new(:node, "top").ensure_in_catalog(@scope).type).to eq("Node") end it "should fail when it is a definition" do expect { Puppet::Resource::Type.new(:definition, "top").ensure_in_catalog(@scope) }.to raise_error(ArgumentError) end it "should add the created resource to the scope's catalog" do @top.ensure_in_catalog(@scope) expect(@compiler.catalog.resource(:class, "top")).to be_instance_of(Puppet::Parser::Resource) end it "should add specified parameters to the resource" do @top.ensure_in_catalog(@scope, {'one'=>'1', 'two'=>'2'}) expect(@compiler.catalog.resource(:class, "top")['one']).to eq('1') expect(@compiler.catalog.resource(:class, "top")['two']).to eq('2') end it "should not require params for a param class" do @top.ensure_in_catalog(@scope, {}) expect(@compiler.catalog.resource(:class, "top")).to be_instance_of(Puppet::Parser::Resource) end it "should evaluate the parent class if one exists" do @middle.ensure_in_catalog(@scope) expect(@compiler.catalog.resource(:class, "top")).to be_instance_of(Puppet::Parser::Resource) end it "should evaluate the parent class if one exists" do @middle.ensure_in_catalog(@scope, {}) expect(@compiler.catalog.resource(:class, "top")).to be_instance_of(Puppet::Parser::Resource) end it "should fail if you try to create duplicate class resources" do othertop = Puppet::Parser::Resource.new(:class, 'top',:source => @source, :scope => @scope ) # add the same class resource to the catalog @compiler.catalog.add_resource(othertop) expect { @top.ensure_in_catalog(@scope, {}) }.to raise_error(Puppet::Resource::Catalog::DuplicateResourceError) end it "should fail to evaluate if a parent class is defined but cannot be found" do othertop = Puppet::Resource::Type.new :hostclass, "something", :parent => "yay" @code.add othertop expect { othertop.ensure_in_catalog(@scope) }.to raise_error(Puppet::ParseError) end it "should not create a new resource if one already exists" do @compiler.catalog.expects(:resource).with(:class, "top").returns("something") @compiler.catalog.expects(:add_resource).never @top.ensure_in_catalog(@scope) end it "should return the existing resource when not creating a new one" do @compiler.catalog.expects(:resource).with(:class, "top").returns("something") @compiler.catalog.expects(:add_resource).never expect(@top.ensure_in_catalog(@scope)).to eq("something") end it "should not create a new parent resource if one already exists and it has a parent class" do @top.ensure_in_catalog(@scope) top_resource = @compiler.catalog.resource(:class, "top") @middle.ensure_in_catalog(@scope) expect(@compiler.catalog.resource(:class, "top")).to equal(top_resource) end # #795 - tag before evaluation. it "should tag the catalog with the resource tags when it is evaluated" do @middle.ensure_in_catalog(@scope) expect(@compiler.catalog).to be_tagged("middle") end it "should tag the catalog with the parent class tags when it is evaluated" do @middle.ensure_in_catalog(@scope) expect(@compiler.catalog).to be_tagged("top") end end describe "when merging code from another instance" do def code(str) Puppet::Pops::Model::Factory.literal(str) end it "should fail unless it is a class" do expect { Puppet::Resource::Type.new(:node, "bar").merge("foo") }.to raise_error(Puppet::Error) end it "should fail unless the source instance is a class" do dest = Puppet::Resource::Type.new(:hostclass, "bar") source = Puppet::Resource::Type.new(:node, "foo") expect { dest.merge(source) }.to raise_error(Puppet::Error) end it "should fail if both classes have different parent classes" do code = Puppet::Resource::TypeCollection.new("env") {"a" => "b", "c" => "d"}.each do |parent, child| code.add Puppet::Resource::Type.new(:hostclass, parent) code.add Puppet::Resource::Type.new(:hostclass, child, :parent => parent) end expect { code.hostclass("b").merge(code.hostclass("d")) }.to raise_error(Puppet::Error) end context 'when "freeze_main" is enabled and a merge is done into the main class' do it "an error is raised if there is something other than definitions in the merged class" do Puppet.settings[:freeze_main] = true code = Puppet::Resource::TypeCollection.new("env") code.add Puppet::Resource::Type.new(:hostclass, "") other = Puppet::Resource::Type.new(:hostclass, "") mock = stub mock.expects(:is_definitions_only?).returns(false) other.expects(:code).returns(mock) expect { code.hostclass("").merge(other) }.to raise_error(Puppet::Error) end it "an error is not raised if the merged class contains nothing but definitions" do Puppet.settings[:freeze_main] = true code = Puppet::Resource::TypeCollection.new("env") code.add Puppet::Resource::Type.new(:hostclass, "") other = Puppet::Resource::Type.new(:hostclass, "") mock = stub mock.expects(:is_definitions_only?).returns(true) other.expects(:code).at_least_once.returns(mock) expect { code.hostclass("").merge(other) }.not_to raise_error end end it "should copy the other class's parent if it has not parent" do dest = Puppet::Resource::Type.new(:hostclass, "bar") Puppet::Resource::Type.new(:hostclass, "parent") source = Puppet::Resource::Type.new(:hostclass, "foo", :parent => "parent") dest.merge(source) expect(dest.parent).to eq("parent") end it "should copy the other class's documentation as its docs if it has no docs" do dest = Puppet::Resource::Type.new(:hostclass, "bar") source = Puppet::Resource::Type.new(:hostclass, "foo", :doc => "yayness") dest.merge(source) expect(dest.doc).to eq("yayness") end it "should append the other class's docs to its docs if it has any" do dest = Puppet::Resource::Type.new(:hostclass, "bar", :doc => "fooness") source = Puppet::Resource::Type.new(:hostclass, "foo", :doc => "yayness") dest.merge(source) expect(dest.doc).to eq("foonessyayness") end it "should set the other class's code as its code if it has none" do dest = Puppet::Resource::Type.new(:hostclass, "bar") source = Puppet::Resource::Type.new(:hostclass, "foo", :code => code("bar").model) dest.merge(source) expect(dest.code.value).to eq("bar") end it "should append the other class's code to its code if it has any" do # PUP-3274, the code merging at the top still uses AST::BlockExpression # But does not do mutating changes to code blocks, instead a new block is created # with references to the two original blocks. # TODO: fix this when the code merging is changed at the very top in 4x. # dcode = Puppet::Parser::AST::BlockExpression.new(:children => [code("dest")]) dest = Puppet::Resource::Type.new(:hostclass, "bar", :code => dcode) scode = Puppet::Parser::AST::BlockExpression.new(:children => [code("source")]) source = Puppet::Resource::Type.new(:hostclass, "foo", :code => scode) dest.merge(source) expect(dest.code.children.map { |c| c.value }).to eq(%w{dest source}) end end end puppet-5.5.10/spec/unit/resource/capability_finder_spec.rb0000644005276200011600000001416213417161722023566 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require_relative '../pops/parser/parser_rspec_helper' require 'puppet/resource/capability_finder' describe Puppet::Resource::CapabilityFinder do context 'when PuppetDB is not configured' do it 'should error' do Puppet::Util.expects(:const_defined?).with('Puppetdb').returns false expect { Puppet::Resource::CapabilityFinder.find('production', nil, nil) }.to raise_error(/PuppetDB is not available/) end end context 'when PuppetDB is configured' do around(:each) do |example| mock_pdb = !Puppet::Util.const_defined?('Puppetdb') if mock_pdb module Puppet::Util::Puppetdb class Http; end end end begin Puppet::Parser::Compiler.any_instance.stubs(:loaders).returns(loaders) Puppet.override(:loaders => loaders, :current_environment => env) do make_cap_type example.run end ensure Puppet::Util.send(:remove_const, 'Puppetdb') if mock_pdb Puppet::Type.rmtype(:cap) Puppet::Pops::Loaders.clear end end let(:env) { Puppet::Node::Environment.create(:testing, []) } let(:loaders) { Puppet::Pops::Loaders.new(env) } let(:response_body) { [{"type"=>"Cap", "title"=>"cap", "parameters"=>{"host"=>"ahost"}}] } let(:response) { stub('response', :body => response_body.to_json) } def make_cap_type Puppet::Type.newtype :cap, :is_capability => true do newparam :name newparam :host end end describe "when query_puppetdb method is available" do it 'should call use the query_puppetdb method if available' do Puppet::Util::Puppetdb.expects(:query_puppetdb).returns(response_body) Puppet::Util::Puppetdb::Http.expects(:action).never result = Puppet::Resource::CapabilityFinder.find('production', nil, Puppet::Resource.new('Cap', 'cap')) expect(result['host']).to eq('ahost') end end describe "when query_puppetdb method is unavailable" do before :each do Puppet::Util::Puppetdb.stubs(:respond_to?).with(:query_puppetdb).returns false end it 'should call Puppet::Util::PuppetDB::Http.action' do Puppet::Util::Puppetdb::Http.expects(:action).returns(response) result = Puppet::Resource::CapabilityFinder.find('production', nil, Puppet::Resource.new('Cap', 'cap')) expect(result['host']).to eq('ahost') end end describe '#find' do let(:capability) { Puppet::Resource.new('Cap', 'cap') } let(:code_id) { 'b59e5df0578ef411f773ee6c33d8073c50e7b8fe' } it 'should search for the resource without including code_id or environment' do resources = [{"type"=>"Cap", "title"=>"cap", "parameters"=>{"host"=>"ahost"}}] Puppet::Resource::CapabilityFinder.stubs(:search).with(nil, nil, capability).returns resources result = Puppet::Resource::CapabilityFinder.find('production', code_id, Puppet::Resource.new('Cap', 'cap')) expect(result['host']).to eq('ahost') end it 'should return nil if no resource is found' do Puppet::Resource::CapabilityFinder.stubs(:search).with(nil, nil, capability).returns [] result = Puppet::Resource::CapabilityFinder.find('production', code_id, capability) expect(result).to be_nil end describe 'when multiple results are returned for different environments' do let(:resources) do [{"type"=>"Cap", "title"=>"cap", "parameters"=>{"host"=>"ahost"}, "tags"=>["producer:production"]}, {"type"=>"Cap", "title"=>"cap", "parameters"=>{"host"=>"bhost"}, "tags"=>["producer:other_env"]}] end before :each do Puppet::Resource::CapabilityFinder.stubs(:search).with(nil, nil, capability).returns resources end it 'should return the resource matching environment' do result = Puppet::Resource::CapabilityFinder.find('production', code_id, capability) expect(result['host']).to eq('ahost') end it 'should return nil if no resource matches environment' do result = Puppet::Resource::CapabilityFinder.find('bad_env', code_id, capability) expect(result).to be_nil end end describe 'when multiple results are returned for the same environment' do let(:resources) do [{"type"=>"Cap", "title"=>"cap", "parameters"=>{"host"=>"ahost"}, "tags"=>["producer:production"]}, {"type"=>"Cap", "title"=>"cap", "parameters"=>{"host"=>"bhost"}, "tags"=>["producer:production"]}] end before :each do Puppet::Resource::CapabilityFinder.stubs(:search).with(nil, nil, capability).returns resources end it 'should return the resource matching code_id' do Puppet::Resource::CapabilityFinder.stubs(:search).with('production', code_id, capability).returns [{"type"=>"Cap", "title"=>"cap", "parameters"=>{"host"=>"chost"}}] result = Puppet::Resource::CapabilityFinder.find('production', code_id, capability) expect(result['host']).to eq('chost') end it 'should fail if no resource matches code_id' do Puppet::Resource::CapabilityFinder.stubs(:search).with('production', code_id, capability).returns [] expect { Puppet::Resource::CapabilityFinder.find('production', code_id, capability) }.to raise_error(Puppet::Error, /expected exactly one resource but got 2/) end it 'should fail if multiple resources match code_id' do Puppet::Resource::CapabilityFinder.stubs(:search).with('production', code_id, capability).returns resources expect { Puppet::Resource::CapabilityFinder.find('production', code_id, capability) }.to raise_error(Puppet::DevError, /expected exactly one resource but got 2/) end it 'should fail if no code_id was specified' do Puppet::Resource::CapabilityFinder.stubs(:search).with('production', nil, capability).returns resources expect { Puppet::Resource::CapabilityFinder.find('production', nil, capability) }.to raise_error(Puppet::DevError, /expected exactly one resource but got 2/) end end end end end puppet-5.5.10/spec/unit/resource/catalog_spec.rb0000644005276200011600000012412413417161722021530 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/compiler' require 'matchers/json' describe Puppet::Resource::Catalog, "when compiling" do include JSONMatchers include PuppetSpec::Files before do @basepath = make_absolute("/somepath") # stub this to not try to create state.yaml Puppet::Util::Storage.stubs(:store) end it "should support json, pson, dot, yaml" do # msgpack is optional, so using include instead of eq expect(Puppet::Resource::Catalog.supported_formats).to include(:json, :pson, :dot, :yaml) end # audit only resources are unmanaged # as are resources without properties with should values it "should write its managed resources' types, namevars" do catalog = Puppet::Resource::Catalog.new("host") resourcefile = tmpfile('resourcefile') Puppet[:resourcefile] = resourcefile res = Puppet::Type.type('file').new(:title => File.expand_path('/tmp/sam'), :ensure => 'present') res.file = 'site.pp' res.line = 21 res2 = Puppet::Type.type('exec').new(:title => 'bob', :command => "#{File.expand_path('/bin/rm')} -rf /") res2.file = File.expand_path('/modules/bob/manifests/bob.pp') res2.line = 42 res3 = Puppet::Type.type('file').new(:title => File.expand_path('/tmp/susan'), :audit => 'all') res3.file = 'site.pp' res3.line = 63 res4 = Puppet::Type.type('file').new(:title => File.expand_path('/tmp/lilly')) res4.file = 'site.pp' res4.line = 84 comp_res = Puppet::Type.type('component').new(:title => 'Class[Main]') catalog.add_resource(res, res2, res3, res4, comp_res) catalog.write_resource_file expect(File.readlines(resourcefile).map(&:chomp)).to match_array([ "file[#{res.title.downcase}]", "exec[#{res2.title.downcase}]" ]) end it "should log an error if unable to write to the resource file" do catalog = Puppet::Resource::Catalog.new("host") Puppet[:resourcefile] = File.expand_path('/not/writable/file') catalog.add_resource(Puppet::Type.type('file').new(:title => File.expand_path('/tmp/foo'))) catalog.write_resource_file expect(@logs.size).to eq(1) expect(@logs.first.message).to match(/Could not create resource file/) expect(@logs.first.level).to eq(:err) end it "should be able to write its list of classes to the class file" do @catalog = Puppet::Resource::Catalog.new("host") @catalog.add_class "foo", "bar" Puppet[:classfile] = File.expand_path("/class/file") fh = mock 'filehandle' classfile = Puppet.settings.setting(:classfile) Puppet::FileSystem.expects(:open).with(classfile.value, classfile.mode.to_i(8), "w:UTF-8").yields fh fh.expects(:puts).with "foo\nbar" @catalog.write_class_file end it "should have a client_version attribute" do @catalog = Puppet::Resource::Catalog.new("host") @catalog.client_version = 5 expect(@catalog.client_version).to eq(5) end it "should have a server_version attribute" do @catalog = Puppet::Resource::Catalog.new("host") @catalog.server_version = 5 expect(@catalog.server_version).to eq(5) end it "defaults code_id to nil" do catalog = Puppet::Resource::Catalog.new("host") expect(catalog.code_id).to be_nil end it "should include a catalog_uuid" do SecureRandom.stubs(:uuid).returns ("827a74c8-cf98-44da-9ff7-18c5e4bee41e") catalog = Puppet::Resource::Catalog.new("host") expect(catalog.catalog_uuid).to eq("827a74c8-cf98-44da-9ff7-18c5e4bee41e") end it "should include the current catalog_format" do catalog = Puppet::Resource::Catalog.new("host") expect(catalog.catalog_format).to eq(1) end describe "when compiling" do it "should accept tags" do config = Puppet::Resource::Catalog.new("mynode") config.tag("one") expect(config).to be_tagged("one") end it "should accept multiple tags at once" do config = Puppet::Resource::Catalog.new("mynode") config.tag("one", "two") expect(config).to be_tagged("one") expect(config).to be_tagged("two") end it "should convert all tags to strings" do config = Puppet::Resource::Catalog.new("mynode") config.tag("one", :two) expect(config).to be_tagged("one") expect(config).to be_tagged("two") end it "should tag with both the qualified name and the split name" do config = Puppet::Resource::Catalog.new("mynode") config.tag("one::two") expect(config).to be_tagged("one") expect(config).to be_tagged("one::two") end it "should accept classes" do config = Puppet::Resource::Catalog.new("mynode") config.add_class("one") expect(config.classes).to eq(%w{one}) config.add_class("two", "three") expect(config.classes).to eq(%w{one two three}) end it "should tag itself with passed class names" do config = Puppet::Resource::Catalog.new("mynode") config.add_class("one") expect(config).to be_tagged("one") end it "handles resource titles with brackets" do config = Puppet::Resource::Catalog.new("mynode") expect(config.title_key_for_ref("Notify[[foo]bar]")).to eql(["Notify", "[foo]bar"]) end end describe "when converting to a RAL catalog" do before do @original = Puppet::Resource::Catalog.new("mynode") @original.tag(*%w{one two three}) @original.add_class(*%w{four five six}) @top = Puppet::Resource.new :class, 'top' @topobject = Puppet::Resource.new :file, @basepath+'/topobject' @middle = Puppet::Resource.new :class, 'middle' @middleobject = Puppet::Resource.new :file, @basepath+'/middleobject' @bottom = Puppet::Resource.new :class, 'bottom' @bottomobject = Puppet::Resource.new :file, @basepath+'/bottomobject' @resources = [@top, @topobject, @middle, @middleobject, @bottom, @bottomobject] @original.add_resource(*@resources) @original.add_edge(@top, @topobject) @original.add_edge(@top, @middle) @original.add_edge(@middle, @middleobject) @original.add_edge(@middle, @bottom) @original.add_edge(@bottom, @bottomobject) @catalog = @original.to_ral end it "should add all resources as RAL instances" do @resources.each do |resource| # Warning: a failure here will result in "global resource iteration is # deprecated" being raised, because the rspec rendering to get the # result tries to call `each` on the resource, and that raises. expect(@catalog.resource(resource.ref)).to be_a_kind_of(Puppet::Type) end end it "should copy the tag list to the new catalog" do expect(@catalog.tags.sort).to eq(@original.tags.sort) end it "should copy the class list to the new catalog" do expect(@catalog.classes).to eq(@original.classes) end it "should duplicate the original edges" do @original.edges.each do |edge| expect(@catalog.edge?(@catalog.resource(edge.source.ref), @catalog.resource(edge.target.ref))).to be_truthy end end it "should set itself as the catalog for each converted resource" do @catalog.vertices.each { |v| expect(v.catalog.object_id).to equal(@catalog.object_id) } end # This tests #931. it "should not lose track of resources whose names vary" do changer = Puppet::Resource.new :file, @basepath+'/test/', :parameters => {:ensure => :directory} config = Puppet::Resource::Catalog.new('test') config.add_resource(changer) config.add_resource(@top) config.add_edge(@top, changer) catalog = config.to_ral expect(catalog.resource("File[#{@basepath}/test/]")).to equal(catalog.resource("File[#{@basepath}/test]")) end after do # Remove all resource instances. @catalog.clear(true) end end describe "when filtering" do before :each do @original = Puppet::Resource::Catalog.new("mynode") @original.tag(*%w{one two three}) @original.add_class(*%w{four five six}) @r1 = stub_everything 'r1', :ref => "File[/a]" @r1.stubs(:respond_to?).with(:ref).returns(true) @r1.stubs(:copy_as_resource).returns(@r1) @r1.stubs(:is_a?).with(Puppet::Resource).returns(true) @r2 = stub_everything 'r2', :ref => "File[/b]" @r2.stubs(:respond_to?).with(:ref).returns(true) @r2.stubs(:copy_as_resource).returns(@r2) @r2.stubs(:is_a?).with(Puppet::Resource).returns(true) @resources = [@r1,@r2] @original.add_resource(@r1,@r2) end it "should transform the catalog to a resource catalog" do @original.expects(:to_catalog).with { |h,b| h == :to_resource } @original.filter end it "should scan each catalog resource in turn and apply filtering block" do @resources.each { |r| r.expects(:test?) } @original.filter do |r| r.test? end end it "should filter out resources which produce true when the filter block is evaluated" do expect(@original.filter do |r| r == @r1 end.resource("File[/a]")).to be_nil end it "should not consider edges against resources that were filtered out" do @original.add_edge(@r1,@r2) expect(@original.filter do |r| r == @r1 end.edge?(@r1,@r2)).not_to be end it "copies the version" do @original.version = '123' expect(@original.filter.version).to eq(@original.version) end it 'copies the code_id' do @original.code_id = 'b59e5df0578ef411f773ee6c33d8073c50e7b8fe' expect(@original.filter.code_id).to eq(@original.code_id) end it 'copies the catalog_uuid' do @original.catalog_uuid = '827a74c8-cf98-44da-9ff7-18c5e4bee41e' expect(@original.filter.catalog_uuid).to eq(@original.catalog_uuid) end it 'copies the catalog_format' do @original.catalog_format = 42 expect(@original.filter.catalog_format).to eq(@original.catalog_format) end end describe "when functioning as a resource container" do before do @catalog = Puppet::Resource::Catalog.new("host") @one = Puppet::Type.type(:notify).new :name => "one" @two = Puppet::Type.type(:notify).new :name => "two" @three = Puppet::Type.type(:notify).new :name => "three" @dupe = Puppet::Type.type(:notify).new :name => "one" end it "should provide a method to add one or more resources" do @catalog.add_resource @one, @two expect(@catalog.resource(@one.ref)).to equal(@one) expect(@catalog.resource(@two.ref)).to equal(@two) end it "should add resources to the relationship graph if it exists" do relgraph = @catalog.relationship_graph @catalog.add_resource @one expect(relgraph).to be_vertex(@one) end it "should set itself as the resource's catalog if it is not a relationship graph" do @one.expects(:catalog=).with(@catalog) @catalog.add_resource @one end it "should make all vertices available by resource reference" do @catalog.add_resource(@one) expect(@catalog.resource(@one.ref)).to equal(@one) expect(@catalog.vertices.find { |r| r.ref == @one.ref }).to equal(@one) end it "tracks the container through edges" do @catalog.add_resource(@two) @catalog.add_resource(@one) @catalog.add_edge(@one, @two) expect(@catalog.container_of(@two)).to eq(@one) end it "a resource without a container is contained in nil" do @catalog.add_resource(@one) expect(@catalog.container_of(@one)).to be_nil end it "should canonize how resources are referred to during retrieval when both type and title are provided" do @catalog.add_resource(@one) expect(@catalog.resource("notify", "one")).to equal(@one) end it "should canonize how resources are referred to during retrieval when just the title is provided" do @catalog.add_resource(@one) expect(@catalog.resource("notify[one]", nil)).to equal(@one) end it "adds resources before an existing resource" do @catalog.add_resource(@one) @catalog.add_resource_before(@one, @two, @three) expect(@catalog.resources).to eq([@two, @three, @one]) end it "raises if adding a resource before a resource not in the catalog" do expect { @catalog.add_resource_before(@one, @two) }.to raise_error(ArgumentError, "Cannot add resource Notify[two] before Notify[one] because Notify[one] is not yet in the catalog") end it "adds resources after an existing resource in reverse order" do @catalog.add_resource(@one) @catalog.add_resource_after(@one, @two, @three) expect(@catalog.resources).to eq([@one, @three, @two]) end it "raises if adding a resource after a resource not in the catalog" do expect { @catalog.add_resource_after(@one, @two) }.to raise_error(ArgumentError, "Cannot add resource Notify[two] after Notify[one] because Notify[one] is not yet in the catalog") end describe 'with a duplicate resource' do def resource_at(type, name, file, line) resource = Puppet::Resource.new(type, name) resource.file = file resource.line = line Puppet::Type.type(type).new(resource) end let(:orig) { resource_at(:notify, 'duplicate-title', '/path/to/orig/file', 42) } let(:dupe) { resource_at(:notify, 'duplicate-title', '/path/to/dupe/file', 314) } it "should print the locations of the original duplicated resource" do @catalog.add_resource(orig) expect { @catalog.add_resource(dupe) }.to raise_error { |error| expect(error).to be_a Puppet::Resource::Catalog::DuplicateResourceError expect(error.message).to match %r[Duplicate declaration: Notify\[duplicate-title\] is already declared] expect(error.message).to match %r[at \(file: /path/to/orig/file, line: 42\)] expect(error.message).to match %r[cannot redeclare] expect(error.message).to match %r[\(file: /path/to/dupe/file, line: 314\)] } end end it "should remove all resources when asked" do @catalog.add_resource @one @catalog.add_resource @two @one.expects :remove @two.expects :remove @catalog.clear(true) end it "should support a mechanism for finishing resources" do @one.expects :finish @two.expects :finish @catalog.add_resource @one @catalog.add_resource @two @catalog.finalize end it "should make default resources when finalizing" do @catalog.expects(:make_default_resources) @catalog.finalize end it "should add default resources to the catalog upon creation" do @catalog.make_default_resources expect(@catalog.resource(:schedule, "daily")).not_to be_nil end it "should optionally support an initialization block and should finalize after such blocks" do @one.expects :finish @two.expects :finish Puppet::Resource::Catalog.new("host") do |conf| conf.add_resource @one conf.add_resource @two end end it "should inform the resource that it is the resource's catalog" do @one.expects(:catalog=).with(@catalog) @catalog.add_resource @one end it "should be able to find resources by reference" do @catalog.add_resource @one expect(@catalog.resource(@one.ref)).to equal(@one) end it "should be able to find resources by reference or by type/title tuple" do @catalog.add_resource @one expect(@catalog.resource("notify", "one")).to equal(@one) end it "should have a mechanism for removing resources" do @catalog.add_resource(@one) expect(@catalog.resource(@one.ref)).to be expect(@catalog.vertex?(@one)).to be_truthy @catalog.remove_resource(@one) expect(@catalog.resource(@one.ref)).to be_nil expect(@catalog.vertex?(@one)).to be_falsey end it "should have a method for creating aliases for resources" do @catalog.add_resource @one @catalog.alias(@one, "other") expect(@catalog.resource("notify", "other")).to equal(@one) end it "should ignore conflicting aliases that point to the aliased resource" do @catalog.alias(@one, "other") expect { @catalog.alias(@one, "other") }.not_to raise_error end it "should create aliases for isomorphic resources whose names do not match their titles" do resource = Puppet::Type::File.new(:title => "testing", :path => @basepath+"/something") @catalog.add_resource(resource) expect(@catalog.resource(:file, @basepath+"/something")).to equal(resource) end it "should not create aliases for non-isomorphic resources whose names do not match their titles" do resource = Puppet::Type.type(:exec).new(:title => "testing", :command => "echo", :path => %w{/bin /usr/bin /usr/local/bin}) @catalog.add_resource(resource) # Yay, I've already got a 'should' method expect(@catalog.resource(:exec, "echo").object_id).to eq(nil.object_id) end # This test is the same as the previous, but the behaviour should be explicit. it "should alias using the class name from the resource reference, not the resource class name" do @catalog.add_resource @one @catalog.alias(@one, "other") expect(@catalog.resource("notify", "other")).to equal(@one) end it "should fail to add an alias if the aliased name already exists" do @catalog.add_resource @one expect { @catalog.alias @two, "one" }.to raise_error(ArgumentError) end it "should not fail when a resource has duplicate aliases created" do @catalog.add_resource @one expect { @catalog.alias @one, "one" }.not_to raise_error end it "should not create aliases that point back to the resource" do @catalog.alias(@one, "one") expect(@catalog.resource(:notify, "one")).to be_nil end it "should be able to look resources up by their aliases" do @catalog.add_resource @one @catalog.alias @one, "two" expect(@catalog.resource(:notify, "two")).to equal(@one) end it "should remove resource aliases when the target resource is removed" do @catalog.add_resource @one @catalog.alias(@one, "other") @one.expects :remove @catalog.remove_resource(@one) expect(@catalog.resource("notify", "other")).to be_nil end it "should add an alias for the namevar when the title and name differ on isomorphic resource types" do resource = Puppet::Type.type(:file).new :path => @basepath+"/something", :title => "other", :content => "blah" resource.expects(:isomorphic?).returns(true) @catalog.add_resource(resource) expect(@catalog.resource(:file, "other")).to equal(resource) expect(@catalog.resource(:file, @basepath+"/something").ref).to eq(resource.ref) end it "should not add an alias for the namevar when the title and name differ on non-isomorphic resource types" do resource = Puppet::Type.type(:file).new :path => @basepath+"/something", :title => "other", :content => "blah" resource.expects(:isomorphic?).returns(false) @catalog.add_resource(resource) expect(@catalog.resource(:file, resource.title)).to equal(resource) # We can't use .should here, because the resources respond to that method. raise "Aliased non-isomorphic resource" if @catalog.resource(:file, resource.name) end it "should provide a method to create additional resources that also registers the resource" do args = {:name => "/yay", :ensure => :file} resource = stub 'file', :ref => "File[/yay]", :catalog= => @catalog, :title => "/yay", :[] => "/yay" Puppet::Type.type(:file).expects(:new).with(args).returns(resource) @catalog.create_resource :file, args expect(@catalog.resource("File[/yay]")).to equal(resource) end describe "when adding resources with multiple namevars" do before :each do Puppet::Type.newtype(:multiple) do newparam(:color, :namevar => true) newparam(:designation, :namevar => true) def self.title_patterns [ [ /^(\w+) (\w+)$/, [ [:color, lambda{|x| x}], [:designation, lambda{|x| x}] ] ] ] end end end it "should add an alias using the uniqueness key" do @resource = Puppet::Type.type(:multiple).new(:title => "some resource", :color => "red", :designation => "5") @catalog.add_resource(@resource) expect(@catalog.resource(:multiple, "some resource")).to eq(@resource) expect(@catalog.resource("Multiple[some resource]")).to eq(@resource) expect(@catalog.resource("Multiple[red 5]")).to eq(@resource) end it "should conflict with a resource with the same uniqueness key" do @resource = Puppet::Type.type(:multiple).new(:title => "some resource", :color => "red", :designation => "5") @other = Puppet::Type.type(:multiple).new(:title => "another resource", :color => "red", :designation => "5") @catalog.add_resource(@resource) expect { @catalog.add_resource(@other) }.to raise_error(ArgumentError, /Cannot alias Multiple\[another resource\] to \["red", "5"\].*resource \["Multiple", "red", "5"\] already declared/) end it "should conflict when its uniqueness key matches another resource's title" do path = make_absolute("/tmp/foo") @resource = Puppet::Type.type(:file).new(:title => path) @other = Puppet::Type.type(:file).new(:title => "another file", :path => path) @catalog.add_resource(@resource) expect { @catalog.add_resource(@other) }.to raise_error(ArgumentError, /Cannot alias File\[another file\] to \["#{Regexp.escape(path)}"\].*resource \["File", "#{Regexp.escape(path)}"\] already declared/) end it "should conflict when its uniqueness key matches the uniqueness key derived from another resource's title" do @resource = Puppet::Type.type(:multiple).new(:title => "red leader") @other = Puppet::Type.type(:multiple).new(:title => "another resource", :color => "red", :designation => "leader") @catalog.add_resource(@resource) expect { @catalog.add_resource(@other) }.to raise_error(ArgumentError, /Cannot alias Multiple\[another resource\] to \["red", "leader"\].*resource \["Multiple", "red", "leader"\] already declared/) end end end describe "when applying" do before :each do @catalog = Puppet::Resource::Catalog.new("host") @transaction = Puppet::Transaction.new(@catalog, nil, Puppet::Graph::RandomPrioritizer.new) Puppet::Transaction.stubs(:new).returns(@transaction) @transaction.stubs(:evaluate) @transaction.stubs(:for_network_device=) Puppet.settings.stubs(:use) end it "should create and evaluate a transaction" do @transaction.expects(:evaluate) @catalog.apply end it "should add a transaction evalution time to the report" do @transaction.report.expects(:add_times).with(:transaction_evaluation, kind_of(Numeric)) @catalog.apply end it "should return the transaction" do expect(@catalog.apply).to equal(@transaction) end it "should yield the transaction if a block is provided" do @catalog.apply do |trans| expect(trans).to equal(@transaction) end end it "should default to being a host catalog" do expect(@catalog.host_config).to be_truthy end it "should be able to be set to a non-host_config" do @catalog.host_config = false expect(@catalog.host_config).to be_falsey end it "should pass supplied tags on to the transaction" do @transaction.expects(:tags=).with(%w{one two}) @catalog.apply(:tags => %w{one two}) end it "should set ignoreschedules on the transaction if specified in apply()" do @transaction.expects(:ignoreschedules=).with(true) @catalog.apply(:ignoreschedules => true) end it "should detect transaction failure and report it" do @transaction.stubs(:evaluate).raises(RuntimeError, 'transaction failed.') report = Puppet::Transaction::Report.new('apply') expect { @catalog.apply(:report => report) }.to raise_error(RuntimeError) report.finalize_report expect(report.status).to eq('failed') end describe "host catalogs" do # super() doesn't work in the setup method for some reason before do @catalog.host_config = true Puppet::Util::Storage.stubs(:store) end it "should initialize the state database before applying a catalog" do Puppet::Util::Storage.expects(:load) # Short-circuit the apply, so we know we're loading before the transaction Puppet::Transaction.expects(:new).raises ArgumentError expect { @catalog.apply }.to raise_error(ArgumentError) end it "should sync the state database after applying" do Puppet::Util::Storage.expects(:store) @transaction.stubs :any_failed? => false @catalog.apply end end describe "non-host catalogs" do before do @catalog.host_config = false end it "should never send reports" do Puppet[:report] = true Puppet[:summarize] = true @catalog.apply end it "should never modify the state database" do Puppet::Util::Storage.expects(:load).never Puppet::Util::Storage.expects(:store).never @catalog.apply end end end describe "when creating a relationship graph" do before do @catalog = Puppet::Resource::Catalog.new("host") end it "should get removed when the catalog is cleaned up" do @catalog.relationship_graph.expects(:clear) @catalog.clear expect(@catalog.instance_variable_get("@relationship_graph")).to be_nil end end describe "when writing dot files" do before do @catalog = Puppet::Resource::Catalog.new("host") @name = :test @file = File.join(Puppet[:graphdir], @name.to_s + ".dot") end it "should only write when it is a host catalog" do Puppet::FileSystem.expects(:open).with(@file, 0640, "w:UTF-8").never @catalog.host_config = false Puppet[:graph] = true @catalog.write_graph(@name) end end describe "when indirecting" do before do @real_indirection = Puppet::Resource::Catalog.indirection @indirection = stub 'indirection', :name => :catalog end it "should use the value of the 'catalog_terminus' setting to determine its terminus class" do # Puppet only checks the terminus setting the first time you ask # so this returns the object to the clean state # at the expense of making this test less pure Puppet::Resource::Catalog.indirection.reset_terminus_class Puppet.settings[:catalog_terminus] = "rest" expect(Puppet::Resource::Catalog.indirection.terminus_class).to eq(:rest) end it "should allow the terminus class to be set manually" do Puppet::Resource::Catalog.indirection.terminus_class = :rest expect(Puppet::Resource::Catalog.indirection.terminus_class).to eq(:rest) end after do @real_indirection.reset_terminus_class end end describe "when converting to yaml" do before do @catalog = Puppet::Resource::Catalog.new("me") @catalog.add_edge("one", "two") end it "should be able to be dumped to yaml" do expect(YAML.dump(@catalog)).to be_instance_of(String) end end describe "when converting from yaml" do before do @catalog = Puppet::Resource::Catalog.new("me") @catalog.add_edge("one", "two") text = YAML.dump(@catalog) @newcatalog = YAML.load(text) end it "should get converted back to a catalog" do expect(@newcatalog).to be_instance_of(Puppet::Resource::Catalog) end it "should have all vertices" do expect(@newcatalog.vertex?("one")).to be_truthy expect(@newcatalog.vertex?("two")).to be_truthy end it "should have all edges" do expect(@newcatalog.edge?("one", "two")).to be_truthy end end end describe Puppet::Resource::Catalog, "when converting a resource catalog to json" do include JSONMatchers include PuppetSpec::Compiler it "should validate an empty catalog against the schema" do empty_catalog = compile_to_catalog("") expect(empty_catalog.to_json).to validate_against('api/schemas/catalog.json') end it "should validate a noop catalog against the schema" do noop_catalog = compile_to_catalog("create_resources('file', {})") expect(noop_catalog.to_json).to validate_against('api/schemas/catalog.json') end it "should validate a single resource catalog against the schema" do catalog = compile_to_catalog("create_resources('file', {'/etc/foo'=>{'ensure'=>'present'}})") expect(catalog.to_json).to validate_against('api/schemas/catalog.json') end it "should validate a virtual resource catalog against the schema" do catalog = compile_to_catalog("create_resources('@file', {'/etc/foo'=>{'ensure'=>'present'}})\nrealize(File['/etc/foo'])") expect(catalog.to_json).to validate_against('api/schemas/catalog.json') end it "should validate a single exported resource catalog against the schema" do catalog = compile_to_catalog("create_resources('@@file', {'/etc/foo'=>{'ensure'=>'present'}})") expect(catalog.to_json).to validate_against('api/schemas/catalog.json') end it "should validate a single sensitive parameter resource catalog against the schema" do catalog = compile_to_catalog("create_resources('file', {'/etc/foo'=>{'ensure'=>'present','content'=>Sensitive('hunter2')}})") expect(catalog.to_json).to validate_against('api/schemas/catalog.json') end it "should validate a two resource catalog against the schema" do catalog = compile_to_catalog("create_resources('notify', {'foo'=>{'message'=>'one'}, 'bar'=>{'message'=>'two'}})") expect(catalog.to_json).to validate_against('api/schemas/catalog.json') end it "should validate a two parameter class catalog against the schema" do catalog = compile_to_catalog(<<-MANIFEST) class multi_param_class ($one, $two) { notify {'foo': message => "One is $one, two is $two", } } class {'multi_param_class': one => 'hello', two => 'world', } MANIFEST expect(catalog.to_json).to validate_against('api/schemas/catalog.json') end context 'when dealing with parameters that have non-Data values' do context 'and rich_data is enabled' do before(:each) do Puppet.push_context(:loaders => Puppet::Pops::Loaders.new(Puppet.lookup(:environments).get(Puppet[:environment]))) Puppet[:rich_data] = true end after(:each) do Puppet[:rich_data] = false Puppet.pop_context end let(:catalog_w_regexp) { compile_to_catalog("notify {'foo': message => /[a-z]+/ }") } it 'should generate rich value hash for parameter values that are not Data' do s = catalog_w_regexp.to_json expect(s).to include('"parameters":{"message":{"__pcore_type__":"Regexp","__pcore_value__":"[a-z]+"}}') end it 'should read and convert rich value hash containing Regexp from json' do catalog2 = Puppet::Resource::Catalog.from_data_hash(JSON.parse(catalog_w_regexp.to_json)) message = catalog2.resource('notify', 'foo')['message'] expect(message).to be_a(Regexp) expect(message).to eql(/[a-z]+/) end it 'should read and convert rich value hash containing Version from json' do catalog = compile_to_catalog("notify {'foo': message => SemVer('1.0.0') }") catalog2 = Puppet::Resource::Catalog.from_data_hash(JSON.parse(catalog.to_json)) message = catalog2.resource('notify', 'foo')['message'] expect(message).to be_a(SemanticPuppet::Version) expect(message).to eql(SemanticPuppet::Version.parse('1.0.0')) end it 'should read and convert rich value hash containing VersionRange from json' do catalog = compile_to_catalog("notify {'foo': message => SemVerRange('>=1.0.0') }") catalog2 = Puppet::Resource::Catalog.from_data_hash(JSON.parse(catalog.to_json)) message = catalog2.resource('notify', 'foo')['message'] expect(message).to be_a(SemanticPuppet::VersionRange) expect(message).to eql(SemanticPuppet::VersionRange.parse('>=1.0.0')) end it 'should read and convert rich value hash containing Timespan from json' do catalog = compile_to_catalog("notify {'foo': message => Timespan(1234) }") catalog2 = Puppet::Resource::Catalog.from_data_hash(JSON.parse(catalog.to_json)) message = catalog2.resource('notify', 'foo')['message'] expect(message).to be_a(Puppet::Pops::Time::Timespan) expect(message).to eql(Puppet::Pops::Time::Timespan.parse('1234', '%S')) end it 'should read and convert rich value hash containing Timestamp from json' do catalog = compile_to_catalog("notify {'foo': message => Timestamp('2016-09-15T08:32:16.123 UTC') }") catalog2 = Puppet::Resource::Catalog.from_data_hash(JSON.parse(catalog.to_json)) message = catalog2.resource('notify', 'foo')['message'] expect(message).to be_a(Puppet::Pops::Time::Timestamp) expect(message).to eql(Puppet::Pops::Time::Timestamp.parse('2016-09-15T08:32:16.123 UTC')) end it 'should read and convert rich value hash containing hash with rich data from json' do catalog = compile_to_catalog("notify {'foo': message => { 'version' => SemVer('1.0.0'), 'time' => Timestamp('2016-09-15T08:32:16.123 UTC') }}") catalog2 = Puppet::Resource::Catalog.from_data_hash(JSON.parse(catalog.to_json)) message = catalog2.resource('notify', 'foo')['message'] expect(message).to be_a(Hash) expect(message['version']).to eql(SemanticPuppet::Version.parse('1.0.0')) expect(message['time']).to eql(Puppet::Pops::Time::Timestamp.parse('2016-09-15T08:32:16.123 UTC')) end it 'should read and convert rich value hash containing an array with rich data from json' do catalog = compile_to_catalog("notify {'foo': message => [ SemVer('1.0.0'), Timestamp('2016-09-15T08:32:16.123 UTC') ] }") catalog2 = Puppet::Resource::Catalog.from_data_hash(JSON.parse(catalog.to_json)) message = catalog2.resource('notify', 'foo')['message'] expect(message).to be_a(Array) expect(message[0]).to eql(SemanticPuppet::Version.parse('1.0.0')) expect(message[1]).to eql(Puppet::Pops::Time::Timestamp.parse('2016-09-15T08:32:16.123 UTC')) end end context 'and rich_data is disabled' do before(:each) do Puppet[:rich_data] = false end let(:catalog_w_regexp) { compile_to_catalog("notify {'foo': message => /[a-z]+/ }") } it 'should not generate rich value hash for parameter values that are not Data' do expect(catalog_w_regexp.to_json).not_to include('"__pcore_type__"') end it 'should convert parameter containing Regexp into strings' do catalog2 = Puppet::Resource::Catalog.from_data_hash(JSON.parse(catalog_w_regexp.to_json)) message = catalog2.resource('notify', 'foo')['message'] expect(message).to be_a(String) expect(message).to eql('/[a-z]+/') end it 'should convert parameter containing Version into string' do catalog = compile_to_catalog("notify {'foo': message => SemVer('1.0.0') }") catalog2 = Puppet::Resource::Catalog.from_data_hash(JSON.parse(catalog.to_json)) message = catalog2.resource('notify', 'foo')['message'] expect(message).to be_a(String) expect(message).to eql('1.0.0') end it 'should convert parameter containing VersionRange into string' do catalog = compile_to_catalog("notify {'foo': message => SemVerRange('>=1.0.0') }") catalog2 = Puppet::Resource::Catalog.from_data_hash(JSON.parse(catalog.to_json)) message = catalog2.resource('notify', 'foo')['message'] expect(message).to be_a(String) expect(message).to eql('>=1.0.0') end it 'should convert parameter containing Timespan into string' do catalog = compile_to_catalog("notify {'foo': message => Timespan(1234) }") catalog2 = Puppet::Resource::Catalog.from_data_hash(JSON.parse(catalog.to_json)) message = catalog2.resource('notify', 'foo')['message'] expect(message).to be_a(String) expect(message).to eql('0-00:20:34.0') end it 'should convert parameter containing Timestamp into string' do catalog = compile_to_catalog("notify {'foo': message => Timestamp('2016-09-15T08:32:16.123 UTC') }") catalog2 = Puppet::Resource::Catalog.from_data_hash(JSON.parse(catalog.to_json)) message = catalog2.resource('notify', 'foo')['message'] expect(message).to be_a(String) expect(message).to eql('2016-09-15T08:32:16.123000000 UTC') end it 'should convert param containing array with :undef entries' do catalog = compile_to_catalog("notify {'foo': message => [ 10, undef, 20 ] }") catalog2 = Puppet::Resource::Catalog.from_data_hash(JSON.parse(catalog.to_json)) message = catalog2.resource('notify', 'foo')['message'] expect(message).to be_a(Array) expect(message[0]).to eql(10) expect(message[1]).to eql(nil) expect(message[2]).to eql(20) end it 'should convert param containing hash with :undef entries' do catalog = compile_to_catalog("notify {'foo': message => {a => undef, b => 10}}") catalog2 = Puppet::Resource::Catalog.from_data_hash(JSON.parse(catalog.to_json)) message = catalog2.resource('notify', 'foo')['message'] expect(message).to be_a(Hash) expect(message.has_key?('a')).to eql(true) expect(message['a']).to eql(nil) expect(message['b']).to eql(10) end end end end describe Puppet::Resource::Catalog, "when converting to json" do before do @catalog = Puppet::Resource::Catalog.new("myhost") end { :name => 'myhost', :version => 42, :code_id => 'b59e5df0578ef411f773ee6c33d8073c50e7b8fe', :catalog_uuid => '827a74c8-cf98-44da-9ff7-18c5e4bee41e', :catalog_format => 42 }.each do |param, value| it "emits a #{param} equal to #{value.inspect}" do @catalog.send(param.to_s + "=", value) json = JSON.parse(@catalog.to_json) expect(json[param.to_s]).to eq(@catalog.send(param)) end end it "emits an array of classes" do @catalog.add_class('foo') json = JSON.parse(@catalog.to_json) expect(json['classes']).to eq(['foo']) end it "should convert its resources to a JSON-encoded array and store it as the 'resources' data" do one = stub 'one', :to_data_hash => "one_resource", :ref => "Foo[one]" two = stub 'two', :to_data_hash => "two_resource", :ref => "Foo[two]" one.expects(:'[]').with(:alias).returns nil two.expects(:'[]').with(:alias).returns nil @catalog.add_resource(one) @catalog.add_resource(two) # TODO this should really guarantee sort order expect(JSON.parse(@catalog.to_json,:create_additions => false)['resources'].sort).to eq(["one_resource", "two_resource"].sort) end it "should convert its edges to a JSON-encoded array and store it as the 'edges' data" do one = stub 'one', :to_data_hash => "one_resource", :ref => 'Foo[one]' two = stub 'two', :to_data_hash => "two_resource", :ref => 'Foo[two]' three = stub 'three', :to_data_hash => "three_resource", :ref => 'Foo[three]' @catalog.add_edge(one, two) @catalog.add_edge(two, three) @catalog.edges_between(one, two )[0].expects(:to_data_hash).returns "one_two_json" @catalog.edges_between(two, three)[0].expects(:to_data_hash).returns "two_three_json" expect(JSON.parse(@catalog.to_json,:create_additions => false)['edges'].sort).to eq(%w{one_two_json two_three_json}.sort) end end describe Puppet::Resource::Catalog, "when converting from json" do before do @data = { 'name' => "myhost" } end it 'should create it with the provided name' do @data['version'] = 50 @data['code_id'] = 'b59e5df0578ef411f773ee6c33d8073c50e7b8fe' @data['catalog_uuid'] = '827a74c8-cf98-44da-9ff7-18c5e4bee41e' @data['catalog_format'] = 42 @data['tags'] = %w{one two} @data['classes'] = %w{one two} @data['edges'] = [Puppet::Relationship.new('File[/foo]', 'File[/bar]', :event => :one, :callback => :refresh).to_data_hash] @data['resources'] = [Puppet::Resource.new(:file, '/foo').to_data_hash, Puppet::Resource.new(:file, '/bar').to_data_hash] catalog = Puppet::Resource::Catalog.from_data_hash JSON.parse @data.to_json expect(catalog.name).to eq('myhost') expect(catalog.version).to eq(@data['version']) expect(catalog.code_id).to eq(@data['code_id']) expect(catalog.catalog_uuid).to eq(@data['catalog_uuid']) expect(catalog.catalog_format).to eq(@data['catalog_format']) expect(catalog).to be_tagged('one') expect(catalog).to be_tagged('two') expect(catalog.classes).to eq(@data['classes']) expect(catalog.resources.collect(&:ref)).to eq(['File[/foo]', 'File[/bar]']) expect(catalog.edges.collect(&:event)).to eq([:one]) expect(catalog.edges[0].source).to eq(catalog.resource(:file, '/foo')) expect(catalog.edges[0].target).to eq(catalog.resource(:file, '/bar')) end it "defaults the catalog_format to 0" do catalog = Puppet::Resource::Catalog.from_data_hash JSON.parse @data.to_json expect(catalog.catalog_format).to eq(0) end it "should fail if the source resource cannot be found" do @data['edges'] = [Puppet::Relationship.new("File[/missing]", "File[/bar]").to_data_hash] @data['resources'] = [Puppet::Resource.new(:file, "/bar").to_data_hash] expect { Puppet::Resource::Catalog.from_data_hash JSON.parse @data.to_json }.to raise_error(ArgumentError, /Could not find relationship source/) end it "should fail if the target resource cannot be found" do @data['edges'] = [Puppet::Relationship.new("File[/bar]", "File[/missing]").to_data_hash] @data['resources'] = [Puppet::Resource.new(:file, "/bar").to_data_hash] expect { Puppet::Resource::Catalog.from_data_hash JSON.parse @data.to_json }.to raise_error(ArgumentError, /Could not find relationship target/) end end puppet-5.5.10/spec/unit/scheduler/0000755005276200011600000000000013417162177016707 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/scheduler/job_spec.rb0000644005276200011600000000414113417161721021012 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/scheduler' describe Puppet::Scheduler::Job do let(:run_interval) { 10 } let(:job) { described_class.new(run_interval) } it "has a minimum run interval of 0" do expect(Puppet::Scheduler::Job.new(-1).run_interval).to eq(0) end describe "when not run yet" do it "is ready" do expect(job.ready?(2)).to be end it "gives the time to next run as 0" do expect(job.interval_to_next_from(2)).to eq(0) end end describe "when run at least once" do let(:last_run) { 50 } before(:each) do job.run(last_run) end it "is ready when the time is greater than the last run plus the interval" do expect(job.ready?(last_run + run_interval + 1)).to be end it "is ready when the time is equal to the last run plus the interval" do expect(job.ready?(last_run + run_interval)).to be end it "is not ready when the time is less than the last run plus the interval" do expect(job.ready?(last_run + run_interval - 1)).not_to be end context "when calculating the next run" do it "returns the run interval if now == last run" do expect(job.interval_to_next_from(last_run)).to eq(run_interval) end it "when time is between the last and next runs gives the remaining portion of the run_interval" do time_since_last_run = 2 now = last_run + time_since_last_run expect(job.interval_to_next_from(now)).to eq(run_interval - time_since_last_run) end it "when time is later than last+interval returns 0" do time_since_last_run = run_interval + 5 now = last_run + time_since_last_run expect(job.interval_to_next_from(now)).to eq(0) end end end it "starts enabled" do expect(job.enabled?).to be end it "can be disabled" do job.disable expect(job.enabled?).not_to be end it "has the job instance as a parameter" do passed_job = nil job = Puppet::Scheduler::Job.new(run_interval) do |j| passed_job = j end job.run(5) expect(passed_job).to eql(job) end end puppet-5.5.10/spec/unit/scheduler/scheduler_spec.rb0000644005276200011600000000475613417161721022232 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/scheduler' describe Puppet::Scheduler::Scheduler do let(:now) { 183550 } let(:timer) { MockTimer.new(now) } class MockTimer attr_reader :wait_for_calls def initialize(start=1729) @now = start @wait_for_calls = [] end def wait_for(seconds) @wait_for_calls << seconds @now += seconds end def now @now end end def one_time_job(interval) Puppet::Scheduler::Job.new(interval) { |j| j.disable } end def disabled_job(interval) job = Puppet::Scheduler::Job.new(interval) { |j| j.disable } job.disable job end let(:scheduler) { Puppet::Scheduler::Scheduler.new(timer) } it "uses the minimum interval" do later_job = one_time_job(7) earlier_job = one_time_job(2) later_job.last_run = now earlier_job.last_run = now scheduler.run_loop([later_job, earlier_job]) expect(timer.wait_for_calls).to eq([2, 5]) end it "ignores disabled jobs when calculating intervals" do enabled = one_time_job(7) enabled.last_run = now disabled = disabled_job(2) scheduler.run_loop([enabled, disabled]) expect(timer.wait_for_calls).to eq([7]) end it "asks the timer to wait for the job interval" do job = one_time_job(5) job.last_run = now scheduler.run_loop([job]) expect(timer.wait_for_calls).to eq([5]) end it "does not run when there are no jobs" do scheduler.run_loop([]) expect(timer.wait_for_calls).to be_empty end it "does not run when there are only disabled jobs" do disabled_job = Puppet::Scheduler::Job.new(0) disabled_job.disable scheduler.run_loop([disabled_job]) expect(timer.wait_for_calls).to be_empty end it "stops running when there are no more enabled jobs" do disabling_job = Puppet::Scheduler::Job.new(0) do |j| j.disable end scheduler.run_loop([disabling_job]) expect(timer.wait_for_calls.size).to eq(1) end it "marks the start of the run loop" do disabled_job = Puppet::Scheduler::Job.new(0) disabled_job.disable scheduler.run_loop([disabled_job]) expect(disabled_job.start_time).to eq(now) end it "calculates the next interval from the start of a job" do countdown = 2 slow_job = Puppet::Scheduler::Job.new(10) do |job| timer.wait_for(3) countdown -= 1 job.disable if countdown == 0 end scheduler.run_loop([slow_job]) expect(timer.wait_for_calls).to eq([0, 3, 7, 3]) end end puppet-5.5.10/spec/unit/scheduler/splay_job_spec.rb0000644005276200011600000000173013417161721022223 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/scheduler' describe Puppet::Scheduler::SplayJob do let(:run_interval) { 10 } let(:last_run) { 50 } let(:splay_limit) { 5 } let(:start_time) { 23 } let(:job) { described_class.new(run_interval, splay_limit) } it "does not apply a splay after the first run" do job.run(last_run) expect(job.interval_to_next_from(last_run)).to eq(run_interval) end it "calculates the first run splayed from the start time" do job.start_time = start_time expect(job.interval_to_next_from(start_time)).to eq(job.splay) end it "interval to the next run decreases as time advances" do time_passed = 3 job.start_time = start_time expect(job.interval_to_next_from(start_time + time_passed)).to eq(job.splay - time_passed) end it "is not immediately ready if splayed" do job.start_time = start_time job.expects(:splay).returns(6) expect(job.ready?(start_time)).not_to be end end puppet-5.5.10/spec/unit/settings/0000755005276200011600000000000013417162177016571 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/settings/array_setting_spec.rb0000644005276200011600000000210413417161721022772 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/settings' require 'puppet/settings/array_setting' describe Puppet::Settings::ArraySetting do subject { described_class.new(:settings => stub('settings'), :desc => "test") } it "is of type :array" do expect(subject.type).to eq :array end describe "munging the value" do describe "when given a string" do it "splits multiple values into an array" do expect(subject.munge("foo,bar")).to eq %w[foo bar] end it "strips whitespace between elements" do expect(subject.munge("foo , bar")).to eq %w[foo bar] end it "creates an array when one item is given" do expect(subject.munge("foo")).to eq %w[foo] end end describe "when given an array" do it "returns the array" do expect(subject.munge(%w[foo])).to eq %w[foo] end end it "raises an error when given an unexpected object type" do expect { subject.munge({:foo => 'bar'}) }.to raise_error(ArgumentError, "Expected an Array or String, got a Hash") end end end puppet-5.5.10/spec/unit/settings/autosign_setting_spec.rb0000644005276200011600000000566413417161721023523 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/settings' require 'puppet/settings/autosign_setting' require 'puppet/type/file' describe Puppet::Settings::AutosignSetting do let(:settings) do s = stub('settings') s.stubs(:[]).with(:mkusers).returns true s.stubs(:[]).with(:user).returns 'puppet' s.stubs(:[]).with(:group).returns 'puppet' s.stubs(:[]).with(:manage_internal_file_permissions).returns true s end let(:setting) { described_class.new(:name => 'autosign', :section => 'section', :settings => settings, :desc => "test") } it "is of type :file" do expect(setting.type).to eq :file end describe "when munging the setting" do it "passes boolean values through" do expect(setting.munge(true)).to eq true expect(setting.munge(false)).to eq false end it "converts nil to false" do expect(setting.munge(nil)).to eq false end it "munges string 'true' to boolean true" do expect(setting.munge('true')).to eq true end it "munges string 'false' to boolean false" do expect(setting.munge('false')).to eq false end it "passes absolute paths through" do path = File.expand_path('/path/to/autosign.conf') expect(setting.munge(path)).to eq path end it "fails if given anything else" do cases = [1.0, 'sometimes', 'relative/autosign.conf'] cases.each do |invalid| expect { setting.munge(invalid) }.to raise_error Puppet::Settings::ValidationError, /Invalid autosign value/ end end end describe "setting additional setting values" do it "can set the file mode" do setting.mode = '0664' expect(setting.mode).to eq '0664' end it "can set the file owner" do setting.owner = 'service' expect(setting.owner).to eq 'puppet' end it "can set the file group" do setting.group = 'service' expect(setting.group).to eq 'puppet' end end describe "converting the setting to a resource" do it "converts the file path to a file resource", :if => !Puppet::Util::Platform.windows? do path = File.expand_path('/path/to/autosign.conf') settings.stubs(:value).with('autosign', nil, false).returns(path) Puppet::FileSystem.stubs(:exist?).with(path).returns true Puppet.features.expects(:root?).returns(true) setting.mode = '0664' setting.owner = 'service' setting.group = 'service' resource = setting.to_resource expect(resource.title).to eq path expect(resource[:ensure]).to eq :file expect(resource[:mode]).to eq '664' expect(resource[:owner]).to eq 'puppet' expect(resource[:group]).to eq 'puppet' end it "returns nil when the setting is a boolean" do settings.stubs(:value).with('autosign', nil, false).returns 'true' setting.mode = '0664' setting.owner = 'service' setting.group = 'service' expect(setting.to_resource).to be_nil end end end puppet-5.5.10/spec/unit/settings/certificate_revocation_setting_spec.rb0000644005276200011600000000210613417161721026371 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/settings' require 'puppet/settings/certificate_revocation_setting' describe Puppet::Settings::CertificateRevocationSetting do subject { described_class.new(:settings => stub('settings'), :desc => "test") } it "is of type :certificate_revocation" do expect(subject.type).to eq :certificate_revocation end describe "munging the value" do ['true', true, 'chain'].each do |setting| it "munges #{setting.inspect} to :chain" do expect(subject.munge(setting)).to eq :chain end end it "munges 'leaf' to :leaf" do expect(subject.munge("leaf")).to eq :leaf end ['false', false, nil].each do |setting| it "munges #{setting.inspect} to false" do expect(subject.munge(setting)).to eq false end end it "raises an error when given an unexpected object type" do expect { subject.munge(1) }.to raise_error(Puppet::Settings::ValidationError, "Invalid certificate revocation value 1: must be one of 'true', 'chain', 'leaf', or 'false'") end end end puppet-5.5.10/spec/unit/settings/directory_setting_spec.rb0000644005276200011600000000133513417161721023665 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/settings' require 'puppet/settings/directory_setting' describe Puppet::Settings::DirectorySetting do DirectorySetting = Puppet::Settings::DirectorySetting include PuppetSpec::Files before do @basepath = make_absolute("/somepath") end describe "when being converted to a resource" do before do @settings = mock 'settings' @dir = Puppet::Settings::DirectorySetting.new( :settings => @settings, :desc => "eh", :name => :mydir, :section => "mysect") @settings.stubs(:value).with(:mydir).returns @basepath end it "should return :directory as its type" do expect(@dir.type).to eq(:directory) end end end puppet-5.5.10/spec/unit/settings/duration_setting_spec.rb0000644005276200011600000000301413417161721023502 0ustar jenkinsjenkins#!/usr/bin/env ruby require 'spec_helper' require 'puppet/settings' require 'puppet/settings/duration_setting' describe Puppet::Settings::DurationSetting do subject { described_class.new(:settings => mock('settings'), :desc => "test") } describe "when munging the setting" do it "should return the same value if given an integer" do expect(subject.munge(5)).to eq(5) end it "should return the same value if given nil" do expect(subject.munge(nil)).to be_nil end it "should return an integer if given a decimal string" do expect(subject.munge("12")).to eq(12) end it "should fail if given anything but a well-formed string, integer, or nil" do [ '', 'foo', '2 d', '2d ', true, Time.now, 8.3, [] ].each do |value| expect { subject.munge(value) }.to raise_error(Puppet::Settings::ValidationError) end end it "should parse strings with units of 'y', 'd', 'h', 'm', or 's'" do # Note: the year value won't jive with most methods of calculating # year due to the Julian calandar having 365.25 days in a year { '3y' => 94608000, '3d' => 259200, '3h' => 10800, '3m' => 180, '3s' => 3 }.each do |value, converted_value| # subject.munge(value).should == converted_value expect(subject.munge(value)).to eq(converted_value) end end # This is to support the `filetimeout` setting it "should allow negative values" do expect(subject.munge(-1)).to eq(-1) end end end puppet-5.5.10/spec/unit/settings/enum_setting_spec.rb0000644005276200011600000000157213417161721022630 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/settings' describe Puppet::Settings::EnumSetting do it "allows a configured value" do setting = enum_setting_allowing("allowed") expect(setting.munge("allowed")).to eq("allowed") end it "disallows a value that is not configured" do setting = enum_setting_allowing("allowed", "also allowed") expect do setting.munge("disallowed") end.to raise_error(Puppet::Settings::ValidationError, "Invalid value 'disallowed' for parameter testing. Allowed values are 'allowed', 'also allowed'") end def enum_setting_allowing(*values) Puppet::Settings::EnumSetting.new(:settings => mock('settings'), :name => "testing", :desc => "description of testing", :values => values) end end puppet-5.5.10/spec/unit/settings/environment_conf_spec.rb0000644005276200011600000001060613417161721023476 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/settings/environment_conf.rb' describe Puppet::Settings::EnvironmentConf do def setup_environment_conf(config, conf_hash) conf_hash.each do |setting,value| config.expects(:setting).with(setting).returns( mock('setting', :value => value) ) end end context "with config" do let(:config) { stub('config') } let(:envconf) { Puppet::Settings::EnvironmentConf.new("/some/direnv", config, ["/global/modulepath"]) } it "reads a modulepath from config and does not include global_module_path" do setup_environment_conf(config, :modulepath => '/some/modulepath') expect(envconf.modulepath).to eq(File.expand_path('/some/modulepath')) end it "reads a manifest from config" do setup_environment_conf(config, :manifest => '/some/manifest') expect(envconf.manifest).to eq(File.expand_path('/some/manifest')) end it "reads a config_version from config" do setup_environment_conf(config, :config_version => '/some/version.sh') expect(envconf.config_version).to eq(File.expand_path('/some/version.sh')) end it "reads an environment_timeout from config" do setup_environment_conf(config, :environment_timeout => '3m') expect(envconf.environment_timeout).to eq(180) end it "reads a static_catalogs from config" do setup_environment_conf(config, :static_catalogs => true) expect(envconf.static_catalogs).to eq(true) end it "can retrieve untruthy settings" do Puppet[:static_catalogs] = true setup_environment_conf(config, :static_catalogs => false) expect(envconf.static_catalogs).to eq(false) end it "can retrieve raw settings" do setup_environment_conf(config, :manifest => 'manifest.pp') expect(envconf.raw_setting(:manifest)).to eq('manifest.pp') end end context "without config" do let(:envconf) { Puppet::Settings::EnvironmentConf.new("/some/direnv", nil, ["/global/modulepath"]) } it "returns a default modulepath when config has none, with global_module_path" do expect(envconf.modulepath).to eq( [File.expand_path('/some/direnv/modules'), File.expand_path('/global/modulepath')].join(File::PATH_SEPARATOR) ) end it "returns a default manifest when config has none" do expect(envconf.manifest).to eq(File.expand_path('/some/direnv/manifests')) end it "returns nothing for config_version when config has none" do expect(envconf.config_version).to be_nil end it "returns a default of 0 for environment_timeout when config has none" do expect(envconf.environment_timeout).to eq(0) end it "returns default of true for static_catalogs when config has none" do expect(envconf.static_catalogs).to eq(true) end it "can still retrieve raw setting" do expect(envconf.raw_setting(:manifest)).to be_nil end end describe "with disable_per_environment_manifest" do let(:config) { stub('config') } let(:envconf) { Puppet::Settings::EnvironmentConf.new("/some/direnv", config, ["/global/modulepath"]) } context "set true" do before(:each) do Puppet[:default_manifest] = File.expand_path('/default/manifest') Puppet[:disable_per_environment_manifest] = true end it "ignores environment.conf manifest" do setup_environment_conf(config, :manifest => '/some/manifest.pp') expect(envconf.manifest).to eq(File.expand_path('/default/manifest')) end it "logs error when environment.conf has manifest set" do setup_environment_conf(config, :manifest => '/some/manifest.pp') envconf.manifest expect(@logs.first.to_s).to match(/disable_per_environment_manifest.*true.*environment.conf.*does not match the default_manifest/) end it "does not log an error when environment.conf does not have a manifest set" do setup_environment_conf(config, :manifest => nil) expect(envconf.manifest).to eq(File.expand_path('/default/manifest')) expect(@logs).to be_empty end end it "uses environment.conf when false" do setup_environment_conf(config, :manifest => '/some/manifest.pp') Puppet[:default_manifest] = File.expand_path('/default/manifest') Puppet[:disable_per_environment_manifest] = false expect(envconf.manifest).to eq(File.expand_path('/some/manifest.pp')) end end end puppet-5.5.10/spec/unit/settings/priority_setting_spec.rb0000644005276200011600000000401513417161721023540 0ustar jenkinsjenkins#!/usr/bin/env ruby require 'spec_helper' require 'puppet/settings' require 'puppet/settings/priority_setting' require 'puppet/util/platform' describe Puppet::Settings::PrioritySetting do let(:setting) { described_class.new(:settings => mock('settings'), :desc => "test") } it "is of type :priority" do expect(setting.type).to eq(:priority) end describe "when munging the setting" do it "passes nil through" do expect(setting.munge(nil)).to be_nil end it "returns the same value if given an integer" do expect(setting.munge(5)).to eq(5) end it "returns an integer if given a decimal string" do expect(setting.munge('12')).to eq(12) end it "returns a negative integer if given a negative integer string" do expect(setting.munge('-5')).to eq(-5) end it "fails if given anything else" do [ 'foo', 'realtime', true, 8.3, [] ].each do |value| expect { setting.munge(value) }.to raise_error(Puppet::Settings::ValidationError) end end describe "on a Unix-like platform it", :unless => Puppet::Util::Platform.windows? do it "parses high, normal, low, and idle priorities" do { 'high' => -10, 'normal' => 0, 'low' => 10, 'idle' => 19 }.each do |value, converted_value| expect(setting.munge(value)).to eq(converted_value) end end end describe "on a Windows-like platform it", :if => Puppet::Util::Platform.windows? do it "parses high, normal, low, and idle priorities" do { 'high' => Puppet::Util::Windows::Process::HIGH_PRIORITY_CLASS, 'normal' => Puppet::Util::Windows::Process::NORMAL_PRIORITY_CLASS, 'low' => Puppet::Util::Windows::Process::BELOW_NORMAL_PRIORITY_CLASS, 'idle' => Puppet::Util::Windows::Process::IDLE_PRIORITY_CLASS }.each do |value, converted_value| expect(setting.munge(value)).to eq(converted_value) end end end end end puppet-5.5.10/spec/unit/settings/string_setting_spec.rb0000644005276200011600000000515213417161721023170 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/settings' require 'puppet/settings/string_setting' describe Puppet::Settings::StringSetting do StringSetting = Puppet::Settings::StringSetting before(:each) do @test_setting_name = :test_setting @test_setting_default = "my_crazy_default/$var" @application_setting = "application/$var" @application_defaults = { } Puppet::Settings::REQUIRED_APP_SETTINGS.each do |key| @application_defaults[key] = "foo" end @application_defaults[:run_mode] = :user @settings = Puppet::Settings.new @application_defaults.each { |k,v| @settings.define_settings :main, k => {:default=>"", :desc => "blah"} } @settings.define_settings :main, :var => { :default => "interpolate!", :type => :string, :desc => "my var desc" }, @test_setting_name => { :default => @test_setting_default, :type => :string, :desc => "my test desc" } @test_setting = @settings.setting(@test_setting_name) end describe "#default" do describe "with no arguments" do it "should return the setting default" do expect(@test_setting.default).to eq(@test_setting_default) end it "should be uninterpolated" do expect(@test_setting.default).not_to match(/interpolate/) end end describe "checking application defaults first" do describe "if application defaults set" do before(:each) do @settings.initialize_app_defaults @application_defaults.merge @test_setting_name => @application_setting end it "should return the application-set default" do expect(@test_setting.default(true)).to eq(@application_setting) end it "should be uninterpolated" do expect(@test_setting.default(true)).not_to match(/interpolate/) end end describe "if application defaults not set" do it "should return the regular default" do expect(@test_setting.default(true)).to eq(@test_setting_default) end it "should be uninterpolated" do expect(@test_setting.default(true)).not_to match(/interpolate/) end end end end describe "#value" do it "should be interpolated" do expect(@test_setting.value).to match(/interpolate/) end end end puppet-5.5.10/spec/unit/settings/terminus_setting_spec.rb0000644005276200011600000000132713417161721023530 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Settings::TerminusSetting do let(:setting) { described_class.new(:settings => mock('settings'), :desc => "test") } describe "#munge" do it "converts strings to symbols" do expect(setting.munge("string")).to eq(:string) end it "converts '' to nil" do expect(setting.munge('')).to be_nil end it "preserves symbols" do expect(setting.munge(:symbol)).to eq(:symbol) end it "preserves nil" do expect(setting.munge(nil)).to be_nil end it "does not allow unknown types through" do expect { setting.munge(["not a terminus type"]) }.to raise_error Puppet::Settings::ValidationError end end end puppet-5.5.10/spec/unit/settings/value_translator_spec.rb0000644005276200011600000000420613417161721023511 0ustar jenkinsjenkins#! /usr/bin/env ruby -S rspec require 'spec_helper' require 'puppet/settings/value_translator' describe Puppet::Settings::ValueTranslator do let(:translator) { Puppet::Settings::ValueTranslator.new } context "booleans" do it "translates strings representing booleans to booleans" do expect(translator['true']).to eq(true) expect(translator['false']).to eq(false) end it "translates boolean values into themselves" do expect(translator[true]).to eq(true) expect(translator[false]).to eq(false) end it "leaves a boolean string with whitespace as a string" do expect(translator[' true']).to eq(" true") expect(translator['true ']).to eq("true") expect(translator[' false']).to eq(" false") expect(translator['false ']).to eq("false") end end context "numbers" do it "translates integer strings to integers" do expect(translator["1"]).to eq(1) expect(translator["2"]).to eq(2) end it "translates numbers starting with a 0 as octal" do expect(translator["011"]).to eq(9) end it "leaves hex numbers as strings" do expect(translator["0x11"]).to eq("0x11") end end context "arbitrary strings" do it "translates an empty string as the empty string" do expect(translator[""]).to eq("") end it "strips double quotes" do expect(translator['"a string"']).to eq('a string') end it "strips single quotes" do expect(translator["'a string'"]).to eq("a string") end it "does not strip preceding whitespace" do expect(translator[" \ta string"]).to eq(" \ta string") end it "strips trailing whitespace" do expect(translator["a string\t "]).to eq("a string") end it "leaves leading quote that is preceded by whitespace" do expect(translator[" 'a string'"]).to eq(" 'a string") end it "leaves trailing quote that is succeeded by whitespace" do expect(translator["'a string' "]).to eq("a string'") end it "leaves quotes that are not at the beginning or end of the string" do expect(translator["a st'\"ring"]).to eq("a st'\"ring") end end end puppet-5.5.10/spec/unit/settings/config_file_spec.rb0000644005276200011600000001151413417161722022371 0ustar jenkinsjenkins#! /usr/bin/env ruby -S rspec require 'spec_helper' require 'puppet/settings/config_file' describe Puppet::Settings::ConfigFile do NOTHING = {} def the_parse_of(*lines) config.parse_file(filename, lines.join("\n")) end let(:identity_transformer) { Proc.new { |value| value } } let(:config) { Puppet::Settings::ConfigFile.new(identity_transformer) } let(:filename) { "a/fake/filename.conf" } Conf = Puppet::Settings::ConfigFile::Conf Section = Puppet::Settings::ConfigFile::Section Meta = Puppet::Settings::ConfigFile::Meta NO_META = Puppet::Settings::ConfigFile::NO_META it "interprets an empty file to contain a main section with no entries" do result = the_parse_of("") expect(result).to eq(Conf.new.with_section(Section.new(:main))) end it "interprets an empty main section the same as an empty file" do expect(the_parse_of("")).to eq(config.parse_file(filename, "[main]")) end it "places an entry in no section in main" do result = the_parse_of("var = value") expect(result).to eq(Conf.new.with_section(Section.new(:main).with_setting(:var, "value", NO_META))) end it "places an entry after a section header in that section" do result = the_parse_of("[agent]", "var = value") expect(result).to eq(Conf.new. with_section(Section.new(:main)). with_section(Section.new(:agent). with_setting(:var, "value", NO_META))) end it "does not include trailing whitespace in the value" do result = the_parse_of("var = value\t ") expect(result).to eq(Conf.new. with_section(Section.new(:main). with_setting(:var, "value", NO_META))) end it "does not include leading whitespace in the name" do result = the_parse_of(" \t var=value") expect(result).to eq(Conf.new. with_section(Section.new(:main). with_setting(:var, "value", NO_META))) end it "skips lines that are commented out" do result = the_parse_of("#var = value") expect(result).to eq(Conf.new.with_section(Section.new(:main))) end it "skips lines that are entirely whitespace" do result = the_parse_of(" \t ") expect(result).to eq(Conf.new.with_section(Section.new(:main))) end it "errors when a line is not a known form" do expect { the_parse_of("unknown") }.to raise_error Puppet::Settings::ParseError, /Could not match line/ end it "errors providing correct line number when line is not a known form" do multi_line_config = <<-EOF [main] foo=bar badline EOF expect { the_parse_of(multi_line_config) }.to( raise_error(Puppet::Settings::ParseError, /Could not match line/) do |exception| expect(exception.line).to eq(3) end ) end it "stores file meta information in the _meta section" do result = the_parse_of("var = value { owner = me, group = you, mode = 0666 }") expect(result).to eq(Conf.new.with_section(Section.new(:main). with_setting(:var, "value", Meta.new("me", "you", "0666")))) end it "errors when there is unknown meta information" do expect { the_parse_of("var = value { unknown = no }") }. to raise_error ArgumentError, /Invalid file option 'unknown'/ end it "errors when the mode is not numeric" do expect { the_parse_of("var = value { mode = no }") }. to raise_error ArgumentError, "File modes must be numbers" end it "errors when the options are not key-value pairs" do expect { the_parse_of("var = value { mode }") }. to raise_error ArgumentError, "Could not parse 'value { mode }'" end it "may specify legal sections" do text = <<-EOF [legal] a = 'b' [illegal] one = 'e' two = 'f' EOF expect { config.parse_file(filename, text, [:legal]) }. to raise_error Puppet::Error, /Illegal section 'legal' in config file at \(file: #{filename}, line: 1\)/ end it "transforms values with the given function" do config = Puppet::Settings::ConfigFile.new(Proc.new { |value| value + " changed" }) result = config.parse_file(filename, "var = value") expect(result).to eq(Conf.new. with_section(Section.new(:main). with_setting(:var, "value changed", NO_META))) end it "does not try to transform an entry named 'mode'" do config = Puppet::Settings::ConfigFile.new(Proc.new { raise "Should not transform" }) result = config.parse_file(filename, "mode = value") expect(result).to eq(Conf.new. with_section(Section.new(:main). with_setting(:mode, "value", NO_META))) end end puppet-5.5.10/spec/unit/settings/file_setting_spec.rb0000644005276200011600000002776513417161722022620 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/settings' require 'puppet/settings/file_setting' describe Puppet::Settings::FileSetting do FileSetting = Puppet::Settings::FileSetting include PuppetSpec::Files describe "when controlling permissions" do def settings(wanted_values = {}) real_values = { :user => 'root', :group => 'root', :mkusers => false, :service_user_available? => false, :service_group_available? => false }.merge(wanted_values) settings = mock("settings") settings.stubs(:[]).with(:user).returns real_values[:user] settings.stubs(:[]).with(:group).returns real_values[:group] settings.stubs(:[]).with(:mkusers).returns real_values[:mkusers] settings.stubs(:service_user_available?).returns real_values[:service_user_available?] settings.stubs(:service_group_available?).returns real_values[:service_group_available?] settings end context "owner" do it "can always be root" do settings = settings(:user => "the_service", :mkusers => true) setting = FileSetting.new(:settings => settings, :owner => "root", :desc => "a setting") expect(setting.owner).to eq("root") end it "is the service user if we are making users" do settings = settings(:user => "the_service", :mkusers => true, :service_user_available? => false) setting = FileSetting.new(:settings => settings, :owner => "service", :desc => "a setting") expect(setting.owner).to eq("the_service") end it "is the service user if the user is available on the system" do settings = settings(:user => "the_service", :mkusers => false, :service_user_available? => true) setting = FileSetting.new(:settings => settings, :owner => "service", :desc => "a setting") expect(setting.owner).to eq("the_service") end it "is root when the setting specifies service and the user is not available on the system" do settings = settings(:user => "the_service", :mkusers => false, :service_user_available? => false) setting = FileSetting.new(:settings => settings, :owner => "service", :desc => "a setting") expect(setting.owner).to eq("root") end it "is unspecified when no specific owner is wanted" do expect(FileSetting.new(:settings => settings(), :desc => "a setting").owner).to be_nil end it "does not allow other owners" do expect { FileSetting.new(:settings => settings(), :desc => "a setting", :name => "testing", :default => "the default", :owner => "invalid") }. to raise_error(FileSetting::SettingError, /The :owner parameter for the setting 'testing' must be either 'root' or 'service'/) end end context "group" do it "is unspecified when no specific group is wanted" do setting = FileSetting.new(:settings => settings(), :desc => "a setting") expect(setting.group).to be_nil end it "is root if root is requested" do settings = settings(:group => "the_group") setting = FileSetting.new(:settings => settings, :group => "root", :desc => "a setting") expect(setting.group).to eq("root") end it "is the service group if we are making users" do settings = settings(:group => "the_service", :mkusers => true) setting = FileSetting.new(:settings => settings, :group => "service", :desc => "a setting") expect(setting.group).to eq("the_service") end it "is the service user if the group is available on the system" do settings = settings(:group => "the_service", :mkusers => false, :service_group_available? => true) setting = FileSetting.new(:settings => settings, :group => "service", :desc => "a setting") expect(setting.group).to eq("the_service") end it "is unspecified when the setting specifies service and the group is not available on the system" do settings = settings(:group => "the_service", :mkusers => false, :service_group_available? => false) setting = FileSetting.new(:settings => settings, :group => "service", :desc => "a setting") expect(setting.group).to be_nil end it "does not allow other groups" do expect { FileSetting.new(:settings => settings(), :group => "invalid", :name => 'testing', :desc => "a setting") }. to raise_error(FileSetting::SettingError, /The :group parameter for the setting 'testing' must be either 'root' or 'service'/) end end end it "should be able to be converted into a resource" do expect(FileSetting.new(:settings => mock("settings"), :desc => "eh")).to respond_to(:to_resource) end describe "when being converted to a resource" do before do @basepath = make_absolute("/somepath") @settings = mock 'settings' @file = Puppet::Settings::FileSetting.new(:settings => @settings, :desc => "eh", :name => :myfile, :section => "mysect") @file.stubs(:create_files?).returns true @settings.stubs(:value).with(:myfile, nil, false).returns @basepath end it "should return :file as its type" do expect(@file.type).to eq(:file) end it "should skip non-existent files if 'create_files' is not enabled" do @file.expects(:create_files?).returns false @file.expects(:type).returns :file Puppet::FileSystem.expects(:exist?).with(@basepath).returns false expect(@file.to_resource).to be_nil end it "should manage existent files even if 'create_files' is not enabled" do @file.expects(:create_files?).returns false @file.expects(:type).returns :file Puppet::FileSystem.stubs(:exist?) Puppet::FileSystem.expects(:exist?).with(@basepath).returns true expect(@file.to_resource).to be_instance_of(Puppet::Resource) end describe "on POSIX systems", :if => Puppet.features.posix? do it "should skip files in /dev" do @settings.stubs(:value).with(:myfile, nil, false).returns "/dev/file" expect(@file.to_resource).to be_nil end end it "should skip files whose paths are not strings" do @settings.stubs(:value).with(:myfile, nil, false).returns :foo expect(@file.to_resource).to be_nil end it "should return a file resource with the path set appropriately" do resource = @file.to_resource expect(resource.type).to eq("File") expect(resource.title).to eq(@basepath) end it "should have a working directory with a root directory not called dev", :if => Puppet.features.microsoft_windows? do # Although C:\Dev\.... is a valid path on Windows, some other code may regard it as a path to be ignored. e.g. /dev/null resolves to C:\dev\null on Windows. path = File.expand_path('somefile') expect(path).to_not match(/^[A-Z]:\/dev/i) end it "should fully qualified returned files if necessary (#795)" do @settings.stubs(:value).with(:myfile, nil, false).returns "myfile" path = File.expand_path('myfile') expect(@file.to_resource.title).to eq(path) end it "should set the mode on the file if a mode is provided as an octal number" do Puppet[:manage_internal_file_permissions] = true @file.mode = 0755 expect(@file.to_resource[:mode]).to eq('755') end it "should set the mode on the file if a mode is provided as a string" do Puppet[:manage_internal_file_permissions] = true @file.mode = '0755' expect(@file.to_resource[:mode]).to eq('755') end it "should not set the mode on a the file if manage_internal_file_permissions is disabled" do Puppet[:manage_internal_file_permissions] = false @file.stubs(:mode).returns(0755) expect(@file.to_resource[:mode]).to eq(nil) end it "should set the owner if running as root and the owner is provided" do Puppet[:manage_internal_file_permissions] = true Puppet.features.expects(:root?).returns true Puppet.features.stubs(:microsoft_windows?).returns false @file.stubs(:owner).returns "foo" expect(@file.to_resource[:owner]).to eq("foo") end it "should not set the owner if manage_internal_file_permissions is disabled" do Puppet[:manage_internal_file_permissions] = false Puppet.features.stubs(:root?).returns true @file.stubs(:owner).returns "foo" expect(@file.to_resource[:owner]).to eq(nil) end it "should set the group if running as root and the group is provided" do Puppet[:manage_internal_file_permissions] = true Puppet.features.expects(:root?).returns true Puppet.features.stubs(:microsoft_windows?).returns false @file.stubs(:group).returns "foo" expect(@file.to_resource[:group]).to eq("foo") end it "should not set the group if manage_internal_file_permissions is disabled" do Puppet[:manage_internal_file_permissions] = false Puppet.features.stubs(:root?).returns true @file.stubs(:group).returns "foo" expect(@file.to_resource[:group]).to eq(nil) end it "should not set owner if not running as root" do Puppet[:manage_internal_file_permissions] = true Puppet.features.expects(:root?).returns false Puppet.features.stubs(:microsoft_windows?).returns false @file.stubs(:owner).returns "foo" expect(@file.to_resource[:owner]).to be_nil end it "should not set group if not running as root" do Puppet[:manage_internal_file_permissions] = true Puppet.features.expects(:root?).returns false Puppet.features.stubs(:microsoft_windows?).returns false @file.stubs(:group).returns "foo" expect(@file.to_resource[:group]).to be_nil end describe "on Microsoft Windows systems" do before :each do Puppet.features.stubs(:microsoft_windows?).returns true end it "should not set owner" do @file.stubs(:owner).returns "foo" expect(@file.to_resource[:owner]).to be_nil end it "should not set group" do @file.stubs(:group).returns "foo" expect(@file.to_resource[:group]).to be_nil end end it "should set :ensure to the file type" do @file.expects(:type).returns :directory expect(@file.to_resource[:ensure]).to eq(:directory) end it "should set the loglevel to :debug" do expect(@file.to_resource[:loglevel]).to eq(:debug) end it "should set the backup to false" do expect(@file.to_resource[:backup]).to be_falsey end it "should tag the resource with the settings section" do @file.expects(:section).returns "mysect" expect(@file.to_resource).to be_tagged("mysect") end it "should tag the resource with the setting name" do expect(@file.to_resource).to be_tagged("myfile") end it "should tag the resource with 'settings'" do expect(@file.to_resource).to be_tagged("settings") end it "should set links to 'follow'" do expect(@file.to_resource[:links]).to eq(:follow) end end describe "#munge" do it 'does not expand the path of the special value :memory: so we can set dblocation to an in-memory database' do filesetting = FileSetting.new(:settings => mock("settings"), :desc => "eh") expect(filesetting.munge(':memory:')).to eq(':memory:') end end context "when opening", :unless => Puppet.features.microsoft_windows? do let(:path) do tmpfile('file_setting_spec') end let(:setting) do settings = mock("settings", :value => path) FileSetting.new(:name => :mysetting, :desc => "creates a file", :settings => settings) end it "creates a file with mode 0640" do setting.mode = '0640' expect(File).to_not be_exist(path) setting.open('w') expect(File).to be_exist(path) expect(Puppet::FileSystem.stat(path).mode & 0777).to eq(0640) end it "preserves the mode of an existing file" do setting.mode = '0640' Puppet::FileSystem.touch(path) Puppet::FileSystem.chmod(0644, path) setting.open('w') expect(Puppet::FileSystem.stat(path).mode & 0777).to eq(0644) end end end puppet-5.5.10/spec/unit/settings/ini_file_spec.rb0000644005276200011600000003330613417161722021706 0ustar jenkinsjenkinsrequire 'spec_helper' require 'stringio' require 'puppet/settings/ini_file' describe Puppet::Settings::IniFile do # different UTF-8 widths # 1-byte A # 2-byte Űż - http://www.fileformat.info/info/unicode/char/06ff/index.htm - 0xDB 0xBF / 219 191 # 3-byte áš  - http://www.fileformat.info/info/unicode/char/16A0/index.htm - 0xE1 0x9A 0xA0 / 225 154 160 # 4-byte 𠜎 - http://www.fileformat.info/info/unicode/char/2070E/index.htm - 0xF0 0xA0 0x9C 0x8E / 240 160 156 142 let (:mixed_utf8) { "A\u06FF\u16A0\u{2070E}" } # Aۿᚠ𠜎 it "preserves the file when no changes are made" do original_config = <<-CONFIG # comment [section] name = value CONFIG config_fh = a_config_file_containing(original_config) Puppet::Settings::IniFile.update(config_fh) do; end expect(config_fh.string).to eq original_config end it "adds a set name and value to an empty file" do config_fh = a_config_file_containing("") Puppet::Settings::IniFile.update(config_fh) do |config| config.set("the_section", "name", "value") end expect(config_fh.string).to eq "[the_section]\nname = value\n" end it "adds a UTF-8 name and value to an empty file" do config_fh = a_config_file_containing("") Puppet::Settings::IniFile.update(config_fh) do |config| config.set("the_section", mixed_utf8, mixed_utf8.reverse) end expect(config_fh.string).to eq "[the_section]\n#{mixed_utf8} = #{mixed_utf8.reverse}\n" end it "adds a [main] section to a file when it's needed" do config_fh = a_config_file_containing(<<-CONF) [section] name = different value CONF Puppet::Settings::IniFile.update(config_fh) do |config| config.set("main", "name", "value") end expect(config_fh.string).to eq(<<-CONF) [main] name = value [section] name = different value CONF end it "can update values within a UTF-8 section of an existing file" do config_fh = a_config_file_containing(<<-CONF) [#{mixed_utf8}] foo = default CONF Puppet::Settings::IniFile.update(config_fh) do |config| config.set(mixed_utf8, 'foo', 'bar') end expect(config_fh.string).to eq(<<-CONF) [#{mixed_utf8}] foo = bar CONF end it "preserves comments when writing a new name and value" do config_fh = a_config_file_containing("# this is a comment") Puppet::Settings::IniFile.update(config_fh) do |config| config.set("the_section", "name", "value") end expect(config_fh.string).to eq "# this is a comment\n[the_section]\nname = value\n" end it "updates existing names and values in place" do config_fh = a_config_file_containing(<<-CONFIG) # this is the preceding comment [section] name = original value # this is the trailing comment CONFIG Puppet::Settings::IniFile.update(config_fh) do |config| config.set("section", "name", "changed value") end expect(config_fh.string).to eq <<-CONFIG # this is the preceding comment [section] name = changed value # this is the trailing comment CONFIG end it "updates existing empty settings" do config_fh = a_config_file_containing(<<-CONFIG) # this is the preceding comment [section] name = # this is the trailing comment CONFIG Puppet::Settings::IniFile.update(config_fh) do |config| config.set("section", "name", "changed value") end expect(config_fh.string).to eq <<-CONFIG # this is the preceding comment [section] name = changed value # this is the trailing comment CONFIG end it "can set empty settings" do config_fh = a_config_file_containing(<<-CONFIG) # this is the preceding comment [section] name = original value # this is the trailing comment CONFIG Puppet::Settings::IniFile.update(config_fh) do |config| config.set("section", "name", "") end expect(config_fh.string).to eq <<-CONFIG # this is the preceding comment [section] name = # this is the trailing comment CONFIG end it "updates existing UTF-8 name / values in place" do config_fh = a_config_file_containing(<<-CONFIG) # this is the preceding comment [section] ascii = foo #{mixed_utf8} = bar # this is the trailing comment CONFIG Puppet::Settings::IniFile.update(config_fh) do |config| config.set("section", "ascii", mixed_utf8) config.set("section", mixed_utf8, mixed_utf8.reverse) end expect(config_fh.string).to eq <<-CONFIG # this is the preceding comment [section] ascii = #{mixed_utf8} #{mixed_utf8} = #{mixed_utf8.reverse} # this is the trailing comment CONFIG end it "updates only the value in the selected section" do config_fh = a_config_file_containing(<<-CONFIG) [other_section] name = does not change [section] name = original value CONFIG Puppet::Settings::IniFile.update(config_fh) do |config| config.set("section", "name", "changed value") end expect(config_fh.string).to eq <<-CONFIG [other_section] name = does not change [section] name = changed value CONFIG end it "considers settings found outside a section to be in section 'main'" do config_fh = a_config_file_containing(<<-CONFIG) name = original value CONFIG Puppet::Settings::IniFile.update(config_fh) do |config| config.set("main", "name", "changed value") end expect(config_fh.string).to eq <<-CONFIG [main] name = changed value CONFIG end it "adds new settings to an existing section" do config_fh = a_config_file_containing(<<-CONFIG) [section] original = value # comment about 'other' section [other] dont = change CONFIG Puppet::Settings::IniFile.update(config_fh) do |config| config.set("section", "updated", "new") end expect(config_fh.string).to eq <<-CONFIG [section] original = value updated = new # comment about 'other' section [other] dont = change CONFIG end it "adds a new setting into an existing, yet empty section" do config_fh = a_config_file_containing(<<-CONFIG) [section] [other] dont = change CONFIG Puppet::Settings::IniFile.update(config_fh) do |config| config.set("section", "updated", "new") end expect(config_fh.string).to eq <<-CONFIG [section] updated = new [other] dont = change CONFIG end it "finds settings when the section is split up" do config_fh = a_config_file_containing(<<-CONFIG) [section] name = original value [different] name = other value [section] other_name = different original value CONFIG Puppet::Settings::IniFile.update(config_fh) do |config| config.set("section", "name", "changed value") config.set("section", "other_name", "other changed value") end expect(config_fh.string).to eq <<-CONFIG [section] name = changed value [different] name = other value [section] other_name = other changed value CONFIG end it "adds a new setting to the appropriate section, when it would be added behind a setting with an identical value in a preceeding section" do config_fh = a_config_file_containing(<<-CONFIG) [different] name = some value [section] name = some value CONFIG Puppet::Settings::IniFile.update(config_fh) do |config| config.set("section", "new", "new value") end expect(config_fh.string).to eq <<-CONFIG [different] name = some value [section] name = some value new = new value CONFIG end context 'config with no main section' do it 'file does not change when there are no sections or entries' do config_fh = a_config_file_containing(<<-CONFIG) CONFIG Puppet::Settings::IniFile.update(config_fh) do |config| config.delete('main', 'missing') end expect(config_fh.string).to eq <<-CONFIG CONFIG end it 'when there is only 1 entry we can delete it' do config_fh = a_config_file_containing(<<-CONFIG) base = value CONFIG Puppet::Settings::IniFile.update(config_fh) do |config| config.delete('main', 'base') end expect(config_fh.string).to eq <<-CONFIG CONFIG end it 'we delete 1 entry from the default section and add the [main] section header' do config_fh = a_config_file_containing(<<-CONFIG) base = value other = another value CONFIG Puppet::Settings::IniFile.update(config_fh) do |config| config.delete('main', 'base') end expect(config_fh.string).to eq <<-CONFIG [main] other = another value CONFIG end it 'we add [main] to the config file when attempting to delete a setting in another section' do config_fh = a_config_file_containing(<<-CONF) name = different value CONF Puppet::Settings::IniFile.update(config_fh) do |config| config.delete('section', 'name') end expect(config_fh.string).to eq(<<-CONF) [main] name = different value CONF end end context 'config with 1 section' do it 'file does not change when entry to delete does not exist' do config_fh = a_config_file_containing(<<-CONFIG) [main] base = value CONFIG Puppet::Settings::IniFile.update(config_fh) do |config| config.delete('main', 'missing') end expect(config_fh.string).to eq <<-CONFIG [main] base = value CONFIG end it 'deletes the 1 entry in the section' do config_fh = a_config_file_containing(<<-CONFIG) [main] base = DELETING CONFIG Puppet::Settings::IniFile.update(config_fh) do |config| config.delete('main', 'base') end expect(config_fh.string).to eq <<-CONFIG [main] CONFIG end it 'deletes the entry and leaves another entry' do config_fh = a_config_file_containing(<<-CONFIG) [main] base = DELETING after = value to keep CONFIG Puppet::Settings::IniFile.update(config_fh) do |config| config.delete('main', 'base') end expect(config_fh.string).to eq <<-CONFIG [main] after = value to keep CONFIG end it 'deletes the entry while leaving other entries' do config_fh = a_config_file_containing(<<-CONFIG) [main] before = value to keep before base = DELETING after = value to keep CONFIG Puppet::Settings::IniFile.update(config_fh) do |config| config.delete('main', 'base') end expect(config_fh.string).to eq <<-CONFIG [main] before = value to keep before after = value to keep CONFIG end it 'when there are two entries of the same setting name delete one of them' do config_fh = a_config_file_containing(<<-CONFIG) [main] base = value base = value CONFIG Puppet::Settings::IniFile.update(config_fh) do |config| config.delete('main', 'base') end expect(config_fh.string).to eq <<-CONFIG [main] base = value CONFIG end end context 'with 2 sections' do it 'file does not change when entry to delete does not exist' do config_fh = a_config_file_containing(<<-CONFIG) [main] base = value [section] CONFIG Puppet::Settings::IniFile.update(config_fh) do |config| config.delete('section', 'missing') end expect(config_fh.string).to eq <<-CONFIG [main] base = value [section] CONFIG end it 'deletes the 1 entry in the specified section' do config_fh = a_config_file_containing(<<-CONFIG) [main] base = value [section] base = value CONFIG Puppet::Settings::IniFile.update(config_fh) do |config| config.delete('section', 'base') end expect(config_fh.string).to eq <<-CONFIG [main] base = value [section] CONFIG end it 'deletes the entry while leaving other entries' do config_fh = a_config_file_containing(<<-CONFIG) [main] before = value also staying base = value staying after = value to keep [section] before value in section keeping base = DELETING after = value to keep CONFIG Puppet::Settings::IniFile.update(config_fh) do |config| config.delete('section', 'base') end expect(config_fh.string).to eq <<-CONFIG [main] before = value also staying base = value staying after = value to keep [section] before value in section keeping after = value to keep CONFIG end end context 'with 2 sections' do it 'deletes the entry while leaving other entries' do config_fh = a_config_file_containing(<<-CONFIG) [main] before = value also staying base = value staying after = value to keep [section] before value in section keeping base = DELETING after = value to keep [otherSection] before value in section keeping base = value to keep really after = value to keep CONFIG Puppet::Settings::IniFile.update(config_fh) do |config| config.delete('section', 'base') end expect(config_fh.string).to eq <<-CONFIG [main] before = value also staying base = value staying after = value to keep [section] before value in section keeping after = value to keep [otherSection] before value in section keeping base = value to keep really after = value to keep CONFIG end end def a_config_file_containing(text) # set_encoding required for Ruby 1.9.3 as ASCII is the default StringIO.new(text).set_encoding(Encoding::UTF_8) end end puppet-5.5.10/spec/unit/settings/path_setting_spec.rb0000644005276200011600000000173613417161722022623 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Settings::PathSetting do subject { described_class.new(:settings => mock('settings'), :desc => "test") } context "#munge" do it "should expand all path elements" do munged = subject.munge("hello#{File::PATH_SEPARATOR}good/morning#{File::PATH_SEPARATOR}goodbye") munged.split(File::PATH_SEPARATOR).each do |p| expect(Puppet::Util).to be_absolute_path(p) end end it "should leave nil as nil" do expect(subject.munge(nil)).to be_nil end context "on Windows", :if => Puppet.features.microsoft_windows? do it "should convert \\ to /" do expect(subject.munge('C:\test\directory')).to eq('C:/test/directory') end it "should work with UNC paths" do expect(subject.munge('//localhost/some/path')).to eq('//localhost/some/path') expect(subject.munge('\\\\localhost\some\path')).to eq('//localhost/some/path') end end end end puppet-5.5.10/spec/unit/ssl/0000755005276200011600000000000013417162177015532 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/ssl/base_spec.rb0000644005276200011600000000602513417161721020000 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/ssl/certificate' class TestCertificate < Puppet::SSL::Base wraps(Puppet::SSL::Certificate) end describe Puppet::SSL::Certificate do before :each do @base = TestCertificate.new("name") @class = TestCertificate end describe "when creating new instances" do it "should fail if given an object that is not an instance of the wrapped class" do obj = stub 'obj', :is_a? => false expect { @class.from_instance(obj) }.to raise_error(ArgumentError) end it "should fail if a name is not supplied and can't be determined from the object" do obj = stub 'obj', :is_a? => true expect { @class.from_instance(obj) }.to raise_error(ArgumentError) end it "should determine the name from the object if it has a subject" do obj = stub 'obj', :is_a? => true, :subject => '/CN=foo' inst = stub 'base' inst.expects(:content=).with(obj) @class.expects(:new).with('foo').returns inst @class.expects(:name_from_subject).with('/CN=foo').returns('foo') expect(@class.from_instance(obj)).to eq(inst) end end describe "when determining a name from a certificate subject" do it "should extract only the CN and not any other components" do subject = stub 'sub' Puppet::Util::SSL.expects(:cn_from_subject).with(subject).returns 'host.domain.com' expect(@class.name_from_subject(subject)).to eq('host.domain.com') end end describe "when initializing wrapped class from a file with #read" do it "should open the file with ASCII encoding" do path = '/foo/bar/cert' Puppet::SSL::Base.stubs(:valid_certname).returns(true) Puppet::FileSystem.expects(:read).with(path, :encoding => Encoding::ASCII).returns("bar") @base.read(path) end end describe "#digest_algorithm" do let(:content) { stub 'content' } let(:base) { b = Puppet::SSL::Base.new('base') b.content = content b } # Some known signature algorithms taken from RFC 3279, 5758, and browsing # objs_dat.h in openssl { 'md5WithRSAEncryption' => 'md5', 'sha1WithRSAEncryption' => 'sha1', 'md4WithRSAEncryption' => 'md4', 'sha256WithRSAEncryption' => 'sha256', 'ripemd160WithRSA' => 'ripemd160', 'ecdsa-with-SHA1' => 'sha1', 'ecdsa-with-SHA224' => 'sha224', 'ecdsa-with-SHA256' => 'sha256', 'ecdsa-with-SHA384' => 'sha384', 'ecdsa-with-SHA512' => 'sha512', 'dsa_with_SHA224' => 'sha224', 'dsaWithSHA1' => 'sha1', }.each do |signature, digest| it "returns '#{digest}' for signature algorithm '#{signature}'" do content.stubs(:signature_algorithm).returns(signature) expect(base.digest_algorithm).to eq(digest) end end it "raises an error on an unknown signature algorithm" do content.stubs(:signature_algorithm).returns("nonsense") expect { base.digest_algorithm }.to raise_error(Puppet::Error, "Unknown signature algorithm 'nonsense'") end end end puppet-5.5.10/spec/unit/ssl/digest_spec.rb0000644005276200011600000000202713417161721020343 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/ssl/digest' describe Puppet::SSL::Digest do it "defaults to sha256" do digest = described_class.new(nil, 'blah') expect(digest.name).to eq('SHA256') expect(digest.digest.hexdigest).to eq("8b7df143d91c716ecfa5fc1730022f6b421b05cedee8fd52b1fc65a96030ad52") end describe '#name' do it "prints the hashing algorithm used by the openssl digest" do expect(described_class.new('SHA224', 'blah').name).to eq('SHA224') end it "upcases the hashing algorithm" do expect(described_class.new('sha224', 'blah').name).to eq('SHA224') end end describe '#to_hex' do it "returns ':' separated upper case hex pairs" do described_class.new(nil, 'blah').to_hex =~ /\A([A-Z0-9]:)+[A-Z0-9]\Z/ end end describe '#to_s' do it "formats the digest algorithm and the digest as a string" do digest = described_class.new('sha512', 'some content') expect(digest.to_s).to eq("(#{digest.name}) #{digest.to_hex}") end end end puppet-5.5.10/spec/unit/ssl/oids_spec.rb0000644005276200011600000000620113417161721020020 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/ssl/oids' describe Puppet::SSL::Oids do describe "defining application OIDs" do { 'puppetlabs' => '1.3.6.1.4.1.34380', 'ppCertExt' => '1.3.6.1.4.1.34380.1', 'ppRegCertExt' => '1.3.6.1.4.1.34380.1.1', 'pp_uuid' => '1.3.6.1.4.1.34380.1.1.1', 'pp_instance_id' => '1.3.6.1.4.1.34380.1.1.2', 'pp_image_name' => '1.3.6.1.4.1.34380.1.1.3', 'pp_preshared_key' => '1.3.6.1.4.1.34380.1.1.4', 'pp_cost_center' => '1.3.6.1.4.1.34380.1.1.5', 'pp_product' => '1.3.6.1.4.1.34380.1.1.6', 'pp_project' => '1.3.6.1.4.1.34380.1.1.7', 'pp_application' => '1.3.6.1.4.1.34380.1.1.8', 'pp_service' => '1.3.6.1.4.1.34380.1.1.9', 'pp_employee' => '1.3.6.1.4.1.34380.1.1.10', 'pp_created_by' => '1.3.6.1.4.1.34380.1.1.11', 'pp_environment' => '1.3.6.1.4.1.34380.1.1.12', 'pp_role' => '1.3.6.1.4.1.34380.1.1.13', 'pp_software_version' => '1.3.6.1.4.1.34380.1.1.14', 'pp_department' => '1.3.6.1.4.1.34380.1.1.15', 'pp_cluster' => '1.3.6.1.4.1.34380.1.1.16', 'pp_provisioner' => '1.3.6.1.4.1.34380.1.1.17', 'pp_region' => '1.3.6.1.4.1.34380.1.1.18', 'pp_datacenter' => '1.3.6.1.4.1.34380.1.1.19', 'pp_zone' => '1.3.6.1.4.1.34380.1.1.20', 'pp_network' => '1.3.6.1.4.1.34380.1.1.21', 'pp_securitypolicy' => '1.3.6.1.4.1.34380.1.1.22', 'pp_cloudplatform' => '1.3.6.1.4.1.34380.1.1.23', 'pp_apptier' => '1.3.6.1.4.1.34380.1.1.24', 'pp_hostname' => '1.3.6.1.4.1.34380.1.1.25', 'ppPrivCertExt' => '1.3.6.1.4.1.34380.1.2', 'ppAuthCertExt' => '1.3.6.1.4.1.34380.1.3', 'pp_authorization' => '1.3.6.1.4.1.34380.1.3.1', 'pp_auth_role' => '1.3.6.1.4.1.34380.1.3.13', }.each_pair do |sn, oid| it "defines #{sn} as #{oid}" do object_id = OpenSSL::ASN1::ObjectId.new(sn) expect(object_id.oid).to eq oid end end end describe "checking if an OID is a subtree of another OID" do it "can determine if an OID is contained in another OID" do expect(described_class.subtree_of?('1.3.6.1', '1.3.6.1.4.1')).to be_truthy expect(described_class.subtree_of?('1.3.6.1.4.1', '1.3.6.1')).to be_falsey end it "returns true if an OID is compared against itself and exclusive is false" do expect(described_class.subtree_of?('1.3.6.1', '1.3.6.1', false)).to be_truthy end it "returns false if an OID is compared against itself and exclusive is true" do expect(described_class.subtree_of?('1.3.6.1', '1.3.6.1', true)).to be_falsey end it "can compare OIDs defined as short names" do expect(described_class.subtree_of?('IANA', '1.3.6.1.4.1')).to be_truthy expect(described_class.subtree_of?('1.3.6.1', 'enterprises')).to be_truthy end it "returns false when an invalid OID shortname is passed" do expect(described_class.subtree_of?('IANA', 'bananas')).to be_falsey end end end puppet-5.5.10/spec/unit/ssl/certificate_authority/0000755005276200011600000000000013417162177022124 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/ssl/certificate_authority/autosign_command_spec.rb0000644005276200011600000000170413417161722027007 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/ssl/certificate_authority/autosign_command' describe Puppet::SSL::CertificateAuthority::AutosignCommand do let(:csr) { stub 'csr', :name => 'host', :to_s => 'CSR PEM goes here' } let(:decider) { Puppet::SSL::CertificateAuthority::AutosignCommand.new('/autosign/command') } it "returns true if the command succeeded" do executes_the_command_resulting_in(0) expect(decider.allowed?(csr)).to eq(true) end it "returns false if the command failed" do executes_the_command_resulting_in(1) expect(decider.allowed?(csr)).to eq(false) end def executes_the_command_resulting_in(exitstatus) Puppet::Util::Execution.expects(:execute). with(['/autosign/command', 'host'], has_entries(:stdinfile => anything, :combine => true, :failonfail => false)). returns(Puppet::Util::Execution::ProcessOutput.new('', exitstatus)) end end puppet-5.5.10/spec/unit/ssl/certificate_authority/interface_spec.rb0000644005276200011600000004454513417161722025432 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/ssl/certificate_authority' shared_examples_for "a normal interface method" do it "should call the method on the CA for each host specified if an array was provided" do @ca.expects(@method).with("host1") @ca.expects(@method).with("host2") @applier = Puppet::SSL::CertificateAuthority::Interface.new(@method, :to => %w{host1 host2}) @applier.apply(@ca) end it "should call the method on the CA for all existing certificates if :all was provided" do @ca.expects(:list).returns %w{host1 host2} @ca.expects(@method).with("host1") @ca.expects(@method).with("host2") @applier = Puppet::SSL::CertificateAuthority::Interface.new(@method, :to => :all) @applier.apply(@ca) end end shared_examples_for "a destructive interface method" do it "calls the method on the CA for each host specified if an array was provided" do @ca.expects(@method).with("host1") @ca.expects(@method).with("host2") @applier = Puppet::SSL::CertificateAuthority::Interface.new(@method, :to => %w{host1 host2}) @applier.apply(@ca) end it "raises an error if :all was provided" do @applier = Puppet::SSL::CertificateAuthority::Interface.new(@method, :to => :all) expect { @applier.apply(@ca) }.to raise_error(ArgumentError, /Refusing to #{@method} all certs/) end it "raises an error if :signed was provided" do @applier = Puppet::SSL::CertificateAuthority::Interface.new(@method, :to => :signed) expect { @applier.apply(@ca) }.to raise_error(ArgumentError, /Refusing to #{@method} all signed certs/) end end describe Puppet::SSL::CertificateAuthority::Interface do before do @class = Puppet::SSL::CertificateAuthority::Interface end describe "when initializing" do it "should set its method using its settor" do instance = @class.new(:generate, :to => :all) expect(instance.method).to eq(:generate) end it "should set its subjects using the settor" do instance = @class.new(:generate, :to => :all) expect(instance.subjects).to eq(:all) end it "should set the digest if given" do interface = @class.new(:generate, :to => :all, :digest => :digest) expect(interface.digest).to eq(:digest) end end describe "when setting the method" do it "should set the method" do instance = @class.new(:generate, :to => :all) instance.method = :list expect(instance.method).to eq(:list) end it "should fail if the method isn't a member of the INTERFACE_METHODS array" do expect { @class.new(:thing, :to => :all) }.to raise_error(ArgumentError, /Invalid method thing to apply/) end end describe "when setting the subjects" do it "should set the subjects" do instance = @class.new(:generate, :to => :all) instance.subjects = :signed expect(instance.subjects).to eq(:signed) end it "should fail if the subjects setting isn't :all or an array" do expect { @class.new(:generate, :to => "other") }.to raise_error(ArgumentError, /Subjects must be an array or :all; not other/) end end it "should have a method for triggering the application" do expect(@class.new(:generate, :to => :all)).to respond_to(:apply) end describe "when applying" do before do # We use a real object here, because :verify can't be stubbed, apparently. @ca = Object.new end describe "with an empty array specified and the method is not list" do it "should fail" do @applier = @class.new(:sign, :to => []) expect { @applier.apply(@ca) }.to raise_error(ArgumentError) end end describe ":generate" do it "should fail if :all was specified" do @applier = @class.new(:generate, :to => :all) expect { @applier.apply(@ca) }.to raise_error(ArgumentError) end it "should call :generate on the CA for each host specified" do @applier = @class.new(:generate, :to => %w{host1 host2}) @ca.expects(:generate).with() {|*args| args.first == "host1" } @ca.expects(:generate).with() {|*args| args.first == "host2" } @applier.apply(@ca) end end describe ":verify" do before { @method = :verify } #it_should_behave_like "a normal interface method" it "should call the method on the CA for each host specified if an array was provided" do # LAK:NOTE Mocha apparently doesn't allow you to mock :verify, but I'm confident this works in real life. end it "should call the method on the CA for all existing certificates if :all was provided" do # LAK:NOTE Mocha apparently doesn't allow you to mock :verify, but I'm confident this works in real life. end end describe ":destroy" do before { @method = :destroy } it_should_behave_like "a destructive interface method" end describe ":revoke" do before { @method = :revoke } it_should_behave_like "a destructive interface method" end describe ":sign" do before do @csr1 = Puppet::SSL::CertificateRequest.new 'baz' end describe "when run in interactive mode" do before do Puppet::SSL::CertificateRequest.indirection.stubs(:find).with("csr1").returns @csr1 @ca.stubs(:waiting?).returns(%w{csr1}) @ca.stubs(:check_internal_signing_policies).returns(true) end it "should prompt before signing cert" do @applier = @class.new(:sign, :to => :all, :interactive => true) @applier.stubs(:format_host).returns("(host info)") @applier.expects(:puts). with("Signing Certificate Request for:\n(host info)") STDOUT.expects(:print).with("Sign Certificate Request? [y/N] ") STDIN.stubs(:gets).returns('y') @ca.expects(:sign).with("csr1", {}) @applier.apply(@ca) end it "a yes answer can be assumed via options" do @applier = @class.new(:sign, :to => :all, :interactive => true, :yes => true) @applier.stubs(:format_host).returns("(host info)") @applier.expects(:puts). with("Signing Certificate Request for:\n(host info)") STDOUT.expects(:print).with("Sign Certificate Request? [y/N] ") @applier.expects(:puts). with("Assuming YES from `-y' or `--assume-yes' flag") @ca.expects(:sign).with("csr1", {}) @applier.apply(@ca) end end describe "and an array of names was provided" do before do Puppet::SSL::CertificateRequest.indirection.stubs(:find).with("host1").returns @csr1 Puppet::SSL::CertificateRequest.indirection.stubs(:find).with("host2").returns @csr1 end let(:applier) { @class.new(:sign, @options.merge(:to => %w{host1 host2})) } it "should sign the specified waiting certificate requests" do @options = {:allow_dns_alt_names => false} applier.stubs(:format_host).returns("") applier.stubs(:puts) @ca.stubs(:check_internal_signing_policies).returns(true) @ca.expects(:sign).with("host1", @options) @ca.expects(:sign).with("host2", @options) applier.apply(@ca) end it "should sign the certificate requests with alt names if specified" do @options = {:allow_dns_alt_names => true} applier.stubs(:format_host).returns("") applier.stubs(:puts) @ca.stubs(:check_internal_signing_policies).returns(true) @ca.expects(:sign).with("host1", @options) @ca.expects(:sign).with("host2", @options) applier.apply(@ca) end end describe "and :all was provided" do it "should sign all waiting certificate requests" do @ca.stubs(:waiting?).returns(%w{cert1 cert2}) Puppet::SSL::CertificateRequest.indirection.stubs(:find).with("cert1").returns @csr1 Puppet::SSL::CertificateRequest.indirection.stubs(:find).with("cert2").returns @csr1 @ca.stubs(:check_internal_signing_policies).returns(true) @ca.expects(:sign).with("cert1", {}) @ca.expects(:sign).with("cert2", {}) @applier = @class.new(:sign, :to => :all) @applier.stubs(:format_host).returns("") @applier.stubs(:puts) @applier.apply(@ca) end it "should fail if there are no waiting certificate requests" do @ca.stubs(:waiting?).returns([]) @applier = @class.new(:sign, :to => :all) expect { @applier.apply(@ca) }.to raise_error(Puppet::SSL::CertificateAuthority::Interface::InterfaceError) end end end describe ":list" do let(:signed_alt_names) { [] } let(:request_alt_names) { [] } let(:custom_attrs) { [] } let(:ext_requests) { [] } let(:custom_exts) { [] } before :each do @cert = Puppet::SSL::Certificate.new 'foo' @csr = Puppet::SSL::CertificateRequest.new 'bar' @cert.stubs(:subject_alt_names).returns signed_alt_names @cert.stubs(:custom_extensions).returns custom_exts @csr.stubs(:subject_alt_names).returns request_alt_names @csr.stubs(:custom_attributes).returns custom_attrs @csr.stubs(:request_extensions).returns ext_requests Puppet::SSL::Certificate.indirection.stubs(:find).returns @cert Puppet::SSL::CertificateRequest.indirection.stubs(:find).returns @csr @digest = mock("digest") @digest.stubs(:to_s).returns("(fingerprint)") @expiration = mock('time') @expiration.stubs(:iso8601).returns("(expiration)") @cert.stubs(:expiration).returns(@expiration) @ca.expects(:waiting?).returns %w{host1 host2 host3} @ca.expects(:list).returns(%w{host4 host5 host6}).at_most(1) @csr.stubs(:digest).returns @digest @cert.stubs(:digest).returns @digest @ca.stubs(:verify) end describe "and an empty array was provided" do it "should print all certificate requests" do applier = @class.new(:list, :to => []) applier.expects(:puts).with(<<-OUTPUT.chomp) "host1" (fingerprint) "host2" (fingerprint) "host3" (fingerprint) OUTPUT applier.apply(@ca) end end describe "and :all was provided" do it "should print a string containing all certificate requests and certificates" do @ca.expects(:list).returns %w{host4 host5 host6} @ca.stubs(:verify).with("host4").raises(Puppet::SSL::CertificateAuthority::CertificateVerificationError.new(23), "certificate revoked") applier = @class.new(:list, :to => :all) applier.expects(:puts).with(<<-OUTPUT.chomp) "host1" (fingerprint) "host2" (fingerprint) "host3" (fingerprint) + "host5" (fingerprint) + "host6" (fingerprint) - "host4" (fingerprint) (certificate revoked) OUTPUT applier.apply(@ca) end end describe "and :signed was provided" do it "should print a string containing all signed certificate requests and certificates" do @ca.expects(:list).returns %w{host4 host5 host6} applier = @class.new(:list, :to => :signed) applier.expects(:puts).with(<<-OUTPUT.chomp) + "host4" (fingerprint) + "host5" (fingerprint) + "host6" (fingerprint) OUTPUT applier.apply(@ca) end it "should include subject alt names if they are on the certificate request" do @csr.stubs(:subject_alt_names).returns ["DNS:foo", "DNS:bar"] applier = @class.new(:list, :to => ['host1']) applier.expects(:puts).with(<<-OUTPUT.chomp) "host1" (fingerprint) (alt names: "DNS:foo", "DNS:bar") OUTPUT applier.apply(@ca) end end describe "and an array of names was provided" do it "should print all named hosts" do applier = @class.new(:list, :to => %w{host1 host2 host4 host5}) applier.expects(:puts).with(<<-OUTPUT.chomp) "host1" (fingerprint) "host2" (fingerprint) + "host4" (fingerprint) + "host5" (fingerprint) OUTPUT applier.apply(@ca) end end describe "with custom attrbutes and extensions" do let(:custom_attrs) { [{'oid' => 'customAttr', 'value' => 'attrValue'}] } let(:ext_requests) { [{'oid' => 'customExt', 'value' => 'reqExtValue'}] } let(:custom_exts) { [{'oid' => 'extName', 'value' => 'extValue'}] } let(:signed_alt_names) { ["DNS:puppet", "DNS:puppet.example.com"] } before do @ca.unstub(:waiting?) @ca.unstub(:list) @ca.expects(:waiting?).returns %w{ext3} @ca.expects(:list).returns(%w{ext1 ext2}).at_most(1) @ca.stubs(:verify).with("ext2"). raises(Puppet::SSL::CertificateAuthority::CertificateVerificationError.new(23), "certificate revoked") Puppet::SSL::Certificate.indirection.stubs(:find).returns @cert Puppet::SSL::CertificateRequest.indirection.stubs(:find).returns @csr end describe "using legacy format" do it "should append astrisks to end of line to denote additional information available" do applier = @class.new(:list, :to => %w{ext1 ext2 ext3}) applier.expects(:puts).with(<<-OUTPUT.chomp) "ext3" (fingerprint) ** + "ext1" (fingerprint) (alt names: "DNS:puppet", "DNS:puppet.example.com") ** - "ext2" (fingerprint) (certificate revoked) OUTPUT applier.apply(@ca) end it "should append attributes and extensions to end of line when running :verbose" do applier = @class.new(:list, :to => %w{ext1 ext2 ext3}, :verbose => true) applier.expects(:puts).with(<<-OUTPUT.chomp) "ext3" (fingerprint) (customAttr: "attrValue", customExt: "reqExtValue") + "ext1" (fingerprint) (expiration) (alt names: "DNS:puppet", "DNS:puppet.example.com", extName: "extValue") - "ext2" (fingerprint) (certificate revoked) OUTPUT applier.apply(@ca) end end describe "using line-wise format" do it "use the same format as :verbose legacy format" do applier = @class.new(:list, :to => %w{ext1 ext2 ext3}, :format => :machine) applier.expects(:puts).with(<<-OUTPUT.chomp) "ext3" (fingerprint) (customAttr: "attrValue", customExt: "reqExtValue") + "ext1" (fingerprint) (expiration) (alt names: "DNS:puppet", "DNS:puppet.example.com", extName: "extValue") - "ext2" (fingerprint) (certificate revoked) OUTPUT applier.apply(@ca) end end describe "using human friendly format" do it "should break attributes and extensions to separate lines" do applier = @class.new(:list, :to => %w{ext1 ext2 ext3}, :format => :human) applier.expects(:puts).with(<<-OUTPUT) "ext3" (fingerprint) Status: Request Pending Extensions: customAttr: "attrValue" customExt: "reqExtValue" + "ext1" (fingerprint) Status: Signed Expiration: (expiration) Extensions: alt names: "DNS:puppet", "DNS:puppet.example.com" extName: "extValue" - "ext2" (fingerprint) Status: Invalid - (certificate revoked) OUTPUT applier.apply(@ca) end end end end describe ":print" do describe "and :all was provided" do it "should print all certificates" do @ca.expects(:list).returns %w{host1 host2} @applier = @class.new(:print, :to => :all) @ca.expects(:print).with("host1").returns "h1" @applier.expects(:puts).with "h1" @ca.expects(:print).with("host2").returns "h2" @applier.expects(:puts).with "h2" @applier.apply(@ca) end end describe "and an array of names was provided" do it "should print each named certificate if found" do @applier = @class.new(:print, :to => %w{host1 host2}) @ca.expects(:print).with("host1").returns "h1" @applier.expects(:puts).with "h1" @ca.expects(:print).with("host2").returns "h2" @applier.expects(:puts).with "h2" @applier.apply(@ca) end it "should log any named but not found certificates" do @applier = @class.new(:print, :to => %w{host1 host2}) @ca.expects(:print).with("host1").returns "h1" @applier.expects(:puts).with "h1" @ca.expects(:print).with("host2").returns nil expect { @applier.apply(@ca) }.to raise_error(ArgumentError, /Could not find certificate for host2/) end end end describe ":fingerprint" do before(:each) do @cert = Puppet::SSL::Certificate.new 'foo' @csr = Puppet::SSL::CertificateRequest.new 'bar' Puppet::SSL::Certificate.indirection.stubs(:find) Puppet::SSL::CertificateRequest.indirection.stubs(:find) Puppet::SSL::Certificate.indirection.stubs(:find).with('host1').returns(@cert) Puppet::SSL::CertificateRequest.indirection.stubs(:find).with('host2').returns(@csr) end it "should fingerprint with the set digest algorithm" do @applier = @class.new(:fingerprint, :to => %w{host1}, :digest => :shaonemillion) @cert.expects(:digest).with(:shaonemillion).returns("fingerprint1") @applier.expects(:puts).with "host1 fingerprint1" @applier.apply(@ca) end describe "and :all was provided" do it "should fingerprint all certificates (including waiting ones)" do @ca.expects(:list).returns %w{host1} @ca.expects(:waiting?).returns %w{host2} @applier = @class.new(:fingerprint, :to => :all) @cert.expects(:digest).returns("fingerprint1") @applier.expects(:puts).with "host1 fingerprint1" @csr.expects(:digest).returns("fingerprint2") @applier.expects(:puts).with "host2 fingerprint2" @applier.apply(@ca) end end describe "and an array of names was provided" do it "should print each named certificate if found" do @applier = @class.new(:fingerprint, :to => %w{host1 host2}) @cert.expects(:digest).returns("fingerprint1") @applier.expects(:puts).with "host1 fingerprint1" @csr.expects(:digest).returns("fingerprint2") @applier.expects(:puts).with "host2 fingerprint2" @applier.apply(@ca) end end end end end puppet-5.5.10/spec/unit/ssl/certificate_authority_spec.rb0000644005276200011600000011765013417161722023470 0ustar jenkinsjenkins#! /usr/bin/env ruby # encoding: ASCII-8BIT require 'spec_helper' require 'puppet/ssl/certificate_authority' describe Puppet::SSL::CertificateAuthority do after do Puppet::SSL::CertificateAuthority.instance_variable_set(:@singleton_instance, nil) end def stub_ca_host @key = mock 'key' @key.stubs(:content).returns "cakey" @cacert = mock 'certificate' @cacert.stubs(:content).returns "cacertificate" @host = stub 'ssl_host', :key => @key, :certificate => @cacert, :name => Puppet::SSL::Host.ca_name end it "should have a class method for returning a singleton instance" do expect(Puppet::SSL::CertificateAuthority).to respond_to(:instance) end describe "when finding an existing instance" do describe "and the host is a CA host and the run_mode is master" do before do Puppet[:ca] = true Puppet.run_mode.stubs(:master?).returns true @ca = mock('ca') Puppet::SSL::CertificateAuthority.stubs(:new).returns @ca end it "should return an instance" do expect(Puppet::SSL::CertificateAuthority.instance).to equal(@ca) end it "should always return the same instance" do expect(Puppet::SSL::CertificateAuthority.instance).to equal(Puppet::SSL::CertificateAuthority.instance) end end describe "and the host is not a CA host" do it "should return nil" do Puppet[:ca] = false Puppet.run_mode.stubs(:master?).returns true Puppet::SSL::CertificateAuthority.expects(:new).never expect(Puppet::SSL::CertificateAuthority.instance).to be_nil end end describe "and the run_mode is not master" do it "should return nil" do Puppet[:ca] = true Puppet.run_mode.stubs(:master?).returns false Puppet::SSL::CertificateAuthority.expects(:new).never expect(Puppet::SSL::CertificateAuthority.instance).to be_nil end end end describe "when initializing" do before do Puppet.settings.stubs(:use) Puppet::SSL::CertificateAuthority.any_instance.stubs(:setup) end it "should always set its name to the value of :certname" do Puppet[:certname] = "ca_testing" expect(Puppet::SSL::CertificateAuthority.new.name).to eq("ca_testing") end it "should create an SSL::Host instance whose name is the 'ca_name'" do Puppet::SSL::Host.expects(:ca_name).returns "caname" host = stub 'host' Puppet::SSL::Host.expects(:new).with("caname").returns host Puppet::SSL::CertificateAuthority.new end it "should use the :main, :ca, and :ssl settings sections" do Puppet.settings.expects(:use).with(:main, :ssl, :ca) Puppet::SSL::CertificateAuthority.new end it "should make sure the CA is set up" do Puppet::SSL::CertificateAuthority.any_instance.expects(:setup) Puppet::SSL::CertificateAuthority.new end end describe "when setting itself up" do it "should generate the CA certificate if it does not have one" do Puppet.settings.stubs :use host = stub 'host' Puppet::SSL::Host.stubs(:new).returns host host.expects(:certificate).returns nil Puppet::SSL::CertificateAuthority.any_instance.expects(:generate_ca_certificate) Puppet::SSL::CertificateAuthority.new end end describe "when retrieving the certificate revocation list" do before do Puppet.settings.stubs(:use) Puppet[:cacrl] = "/my/crl" cert = stub("certificate", :content => "real_cert") key = stub("key", :content => "real_key") @host = stub 'host', :certificate => cert, :name => "hostname", :key => key Puppet::SSL::CertificateAuthority.any_instance.stubs(:setup) @ca = Puppet::SSL::CertificateAuthority.new @ca.stubs(:host).returns @host end it "should return any found CRL instance" do crl = mock 'crl' Puppet::SSL::CertificateRevocationList.indirection.expects(:find).returns crl expect(@ca.crl).to equal(crl) end it "should create, generate, and save a new CRL instance of no CRL can be found" do crl = Puppet::SSL::CertificateRevocationList.new("fakename") Puppet::SSL::CertificateRevocationList.indirection.expects(:find).returns nil Puppet::SSL::CertificateRevocationList.expects(:new).returns crl crl.expects(:generate).with(@ca.host.certificate.content, @ca.host.key.content) Puppet::SSL::CertificateRevocationList.indirection.expects(:save).with(crl) expect(@ca.crl).to equal(crl) end end describe "when generating a self-signed CA certificate" do before do Puppet.settings.stubs(:use) Puppet::SSL::CertificateAuthority.any_instance.stubs(:setup) Puppet::SSL::CertificateAuthority.any_instance.stubs(:crl) @ca = Puppet::SSL::CertificateAuthority.new @host = stub 'host', :key => mock("key"), :name => "hostname", :certificate => mock('certificate') Puppet::SSL::CertificateRequest.any_instance.stubs(:generate) @ca.stubs(:host).returns @host end it "should create and store a password at :capass" do Puppet[:capass] = File.expand_path("/path/to/pass") Puppet::FileSystem.expects(:exist?).with(Puppet[:capass]).returns false fh = StringIO.new Puppet.settings.setting(:capass).expects(:open).with('w:ASCII').yields fh @ca.stubs(:sign) @ca.generate_ca_certificate expect(fh.string.length).to be > 18 end it "should generate a key if one does not exist" do @ca.stubs :generate_password @ca.stubs :sign @ca.host.expects(:key).returns nil @ca.host.expects(:generate_key) @ca.generate_ca_certificate end it "should create and sign a self-signed cert using the CA name" do request = mock 'request' Puppet::SSL::CertificateRequest.expects(:new).with(@ca.host.name).returns request request.expects(:generate).with(@ca.host.key) request.stubs(:request_extensions => []) @ca.expects(:sign).with(@host.name, {allow_dns_alt_names: false, self_signing_csr: request}) @ca.stubs :generate_password @ca.generate_ca_certificate end it "should generate its CRL" do @ca.stubs :generate_password @ca.stubs :sign @ca.host.expects(:key).returns nil @ca.host.expects(:generate_key) @ca.expects(:crl) @ca.generate_ca_certificate end end describe "when signing" do before do Puppet.settings.stubs(:use) Puppet::SSL::CertificateAuthority.any_instance.stubs(:password?).returns true stub_ca_host Puppet::SSL::Host.expects(:new).with(Puppet::SSL::Host.ca_name).returns @host @ca = Puppet::SSL::CertificateAuthority.new @name = "myhost" @real_cert = stub 'realcert', :sign => nil @cert = Puppet::SSL::Certificate.new(@name) @cert.content = @real_cert Puppet::SSL::Certificate.stubs(:new).returns @cert Puppet::SSL::Certificate.indirection.stubs(:save) # Stub out the factory Puppet::SSL::CertificateFactory.stubs(:build).returns @cert.content @request_content = stub "request content stub", :subject => OpenSSL::X509::Name.new([['CN', @name]]), :public_key => stub('public_key') @request = stub 'request', :name => @name, :request_extensions => [], :subject_alt_names => [], :content => @request_content @request_content.stubs(:verify).returns(true) # And the inventory @inventory = stub 'inventory', :add => nil @ca.stubs(:inventory).returns @inventory Puppet::SSL::CertificateRequest.indirection.stubs(:destroy) end describe "its own certificate" do before do @serial = 10 @ca.stubs(:next_serial).returns @serial end it "should not look up a certificate request for the host" do Puppet::SSL::CertificateRequest.indirection.expects(:find).never @ca.sign(@name, {allow_dns_alt_names: true, self_signing_csr: @request}) end it "should use a certificate type of :ca" do Puppet::SSL::CertificateFactory.expects(:build).with do |*args| expect(args[0]).to eq(:ca) end.returns @cert.content @ca.sign(@name, {allow_dns_alt_names: true, self_signing_csr: @request}) end it "should pass the provided CSR as the CSR" do Puppet::SSL::CertificateFactory.expects(:build).with do |*args| expect(args[1]).to eq(@request) end.returns @cert.content @ca.sign(@name, {allow_dns_alt_names: true, self_signing_csr: @request}) end it "should use the provided CSR's content as the issuer" do Puppet::SSL::CertificateFactory.expects(:build).with do |*args| expect(args[2].subject.to_s).to eq("/CN=myhost") end.returns @cert.content @ca.sign(@name, {allow_dns_alt_names: true, self_signing_csr: @request}) end it "should pass the next serial as the serial number" do Puppet::SSL::CertificateFactory.expects(:build).with do |*args| expect(args[3]).to eq(@serial) end.returns @cert.content @ca.sign(@name, {allow_dns_alt_names: true, self_signing_csr: @request}) end it "should sign the certificate request even if it contains alt names" do @request.stubs(:subject_alt_names).returns %w[DNS:foo DNS:bar DNS:baz] expect do @ca.sign(@name, {allow_dns_alt_names: false, self_signing_csr: @request}) end.not_to raise_error end it "should save the resulting certificate" do Puppet::SSL::Certificate.indirection.expects(:save).with(@cert) @ca.sign(@name, {allow_dns_alt_names: true, self_signing_csr: @request}) end end describe "another host's certificate" do before do @serial = 10 @ca.stubs(:next_serial).returns @serial Puppet::SSL::CertificateRequest.indirection.stubs(:find).with(@name).returns @request Puppet::SSL::CertificateRequest.indirection.stubs :save end it "should use a certificate type of :server" do Puppet::SSL::CertificateFactory.expects(:build).with do |*args| args[0] == :server end.returns @cert.content @ca.sign(@name) end it "should use look up a CSR for the host in the :ca_file terminus" do Puppet::SSL::CertificateRequest.indirection.expects(:find).with(@name).returns @request @ca.sign(@name) end it "should fail if no CSR can be found for the host" do Puppet::SSL::CertificateRequest.indirection.expects(:find).with(@name).returns nil expect { @ca.sign(@name) }.to raise_error(ArgumentError) end it "should fail if an unknown request extension is present" do @request.stubs :request_extensions => [{ "oid" => "bananas", "value" => "delicious" }] expect { @ca.sign(@name) }.to raise_error(/CSR has request extensions that are not permitted/) end it "should reject auth extensions" do @request.stubs :request_extensions => [{"oid" => "1.3.6.1.4.1.34380.1.3.1", "value" => "true"}, {"oid" => "1.3.6.1.4.1.34380.1.3.13", "value" => "com"}] expect { @ca.sign(@name) }.to raise_error(Puppet::SSL::CertificateAuthority::CertificateSigningError, /CSR '#{@name}' contains authorization extensions (.*?, .*?).*/) end it "should not fail if the CSR contains auth extensions and they're allowed" do @request.stubs :request_extensions => [{"oid" => "1.3.6.1.4.1.34380.1.3.1", "value" => "true"}, {"oid" => "1.3.6.1.4.1.34380.1.3.13", "value" => "com"}] expect { @ca.sign(@name, {allow_authorization_extensions: true})}.to_not raise_error end it "should fail if the CSR contains alt names and they are not expected" do @request.stubs(:subject_alt_names).returns %w[DNS:foo DNS:bar DNS:baz] expect do @ca.sign(@name, {allow_dns_alt_names: false}) end.to raise_error(Puppet::SSL::CertificateAuthority::CertificateSigningError, /CSR '#{@name}' contains subject alternative names \(.*?\), which are disallowed. Use `puppet cert --allow-dns-alt-names sign #{@name}` to sign this request./) end it "should not fail if the CSR does not contain alt names and they are expected" do @request.stubs(:subject_alt_names).returns [] expect { @ca.sign(@name, {allow_dns_alt_names: true}) }.to_not raise_error end it "should reject alt names by default" do @request.stubs(:subject_alt_names).returns %w[DNS:foo DNS:bar DNS:baz] expect do @ca.sign(@name) end.to raise_error(Puppet::SSL::CertificateAuthority::CertificateSigningError, /CSR '#{@name}' contains subject alternative names \(.*?\), which are disallowed. Use `puppet cert --allow-dns-alt-names sign #{@name}` to sign this request./) end it "should use the CA certificate as the issuer" do Puppet::SSL::CertificateFactory.expects(:build).with do |*args| args[2] == @cacert.content end.returns @cert.content @ca.sign(@name) end it "should pass the next serial as the serial number" do Puppet::SSL::CertificateFactory.expects(:build).with do |*args| args[3] == @serial end.returns @cert.content @ca.sign(@name) end it "should sign the resulting certificate using its real key and a digest" do digest = mock 'digest' OpenSSL::Digest::SHA256.expects(:new).returns digest key = stub 'key', :content => "real_key" @ca.host.stubs(:key).returns key @cert.content.expects(:sign).with("real_key", digest) @ca.sign(@name) end it "should save the resulting certificate" do Puppet::SSL::Certificate.indirection.stubs(:save).with(@cert) @ca.sign(@name) end it "should remove the host's certificate request" do Puppet::SSL::CertificateRequest.indirection.expects(:destroy).with(@name) @ca.sign(@name) end it "should check the internal signing policies" do @ca.expects(:check_internal_signing_policies).returns true @ca.sign(@name) end end context "#check_internal_signing_policies" do before do @serial = 10 @ca.stubs(:next_serial).returns @serial Puppet::SSL::CertificateRequest.indirection.stubs(:find).with(@name).returns @request @cert.stubs :save end it "should reject CSRs whose CN doesn't match the name for which we're signing them" do # Shorten this so the test doesn't take too long Puppet[:keylength] = 1024 key = Puppet::SSL::Key.new('the_certname') key.generate csr = Puppet::SSL::CertificateRequest.new('the_certname') csr.generate(key) expect do @ca.check_internal_signing_policies('not_the_certname', csr) end.to raise_error( Puppet::SSL::CertificateAuthority::CertificateSigningError, /common name "the_certname" does not match expected certname "not_the_certname"/ ) end describe "when validating the CN" do before :all do Puppet[:keylength] = 1024 Puppet[:passfile] = '/f00' @signing_key = Puppet::SSL::Key.new('my_signing_key') @signing_key.generate end [ 'completely_okay', 'sure, why not? :)', 'so+many(things)-are=allowed.', 'this"is#just&madness%you[see]', 'and even a (an?) \\!', 'waltz, nymph, for quick jigs vex bud.', '{552c04ca-bb1b-11e1-874b-60334b04494e}' ].each do |name| it "should accept #{name.inspect}" do csr = Puppet::SSL::CertificateRequest.new(name) csr.generate(@signing_key) @ca.check_internal_signing_policies(name, csr) end end [ 'super/bad', "not\neven\tkind\rof", "ding\adong\a", "hidden\b\b\b\b\b\bmessage", "\xE2\x98\x83 :(" ].each do |name| it "should reject #{name.inspect}" do # We aren't even allowed to make objects with these names, so let's # stub that to simulate an invalid one coming from outside Puppet Puppet::SSL::CertificateRequest.stubs(:validate_certname) csr = Puppet::SSL::CertificateRequest.new(name) csr.generate(@signing_key) expect do @ca.check_internal_signing_policies(name, csr) end.to raise_error( Puppet::SSL::CertificateAuthority::CertificateSigningError, /subject contains unprintable or non-ASCII characters/ ) end end end it "accepts numeric OIDs under the ppRegCertExt subtree" do exts = [{ 'oid' => '1.3.6.1.4.1.34380.1.1.1', 'value' => '657e4780-4cf5-11e3-8f96-0800200c9a66'}] @request.stubs(:request_extensions).returns exts expect { @ca.check_internal_signing_policies(@name, @request) }.to_not raise_error end it "accepts short name OIDs under the ppRegCertExt subtree" do exts = [{ 'oid' => 'pp_uuid', 'value' => '657e4780-4cf5-11e3-8f96-0800200c9a66'}] @request.stubs(:request_extensions).returns exts expect { @ca.check_internal_signing_policies(@name, @request) }.to_not raise_error end it "accepts OIDs under the ppPrivCertAttrs subtree" do exts = [{ 'oid' => '1.3.6.1.4.1.34380.1.2.1', 'value' => 'private extension'}] @request.stubs(:request_extensions).returns exts expect { @ca.check_internal_signing_policies(@name, @request) }.to_not raise_error end it "should reject a critical extension that isn't on the whitelist" do @request.stubs(:request_extensions).returns [{ "oid" => "banana", "value" => "yumm", "critical" => true }] expect { @ca.check_internal_signing_policies(@name, @request) }.to raise_error( Puppet::SSL::CertificateAuthority::CertificateSigningError, /request extensions that are not permitted/ ) end it "should reject a non-critical extension that isn't on the whitelist" do @request.stubs(:request_extensions).returns [{ "oid" => "peach", "value" => "meh", "critical" => false }] expect { @ca.check_internal_signing_policies(@name, @request) }.to raise_error( Puppet::SSL::CertificateAuthority::CertificateSigningError, /request extensions that are not permitted/ ) end it "should reject non-whitelist extensions even if a valid extension is present" do @request.stubs(:request_extensions).returns [{ "oid" => "peach", "value" => "meh", "critical" => false }, { "oid" => "subjectAltName", "value" => "DNS:foo", "critical" => true }] expect { @ca.check_internal_signing_policies(@name, @request) }.to raise_error( Puppet::SSL::CertificateAuthority::CertificateSigningError, /request extensions that are not permitted/ ) end it "should reject a subjectAltName for a non-DNS value" do @request.stubs(:subject_alt_names).returns ['DNS:foo', 'email:bar@example.com'] expect { @ca.check_internal_signing_policies(@name, @request, {allow_dns_alt_names: true}) }.to raise_error( Puppet::SSL::CertificateAuthority::CertificateSigningError, /subjectAltName outside the DNS label space/ ) end it "should allow a subjectAltName if subject matches CA's certname" do @request.stubs(:subject_alt_names).returns ['DNS:foo'] Puppet[:certname] = @name expect { @ca.check_internal_signing_policies(@name, @request, {allow_dns_alt_names: false}) }.to_not raise_error end it "should reject a wildcard subject" do @request.content.stubs(:subject). returns(OpenSSL::X509::Name.new([["CN", "*.local"]])) expect { @ca.check_internal_signing_policies('*.local', @request) }.to raise_error( Puppet::SSL::CertificateAuthority::CertificateSigningError, /subject contains a wildcard/ ) end it "should reject a wildcard subjectAltName" do @request.stubs(:subject_alt_names).returns ['DNS:foo', 'DNS:*.bar'] expect { @ca.check_internal_signing_policies(@name, @request, {allow_dns_alt_names: true}) }.to raise_error( Puppet::SSL::CertificateAuthority::CertificateSigningError, /subjectAltName contains a wildcard/ ) end end it "should create a certificate instance with the content set to the newly signed x509 certificate" do @serial = 10 @ca.stubs(:next_serial).returns @serial Puppet::SSL::CertificateRequest.indirection.stubs(:find).with(@name).returns @request Puppet::SSL::Certificate.indirection.stubs :save Puppet::SSL::Certificate.expects(:new).with(@name).returns @cert @ca.sign(@name) end it "should return the certificate instance" do @ca.stubs(:next_serial).returns @serial Puppet::SSL::CertificateRequest.indirection.stubs(:find).with(@name).returns @request Puppet::SSL::Certificate.indirection.stubs :save expect(@ca.sign(@name)).to equal(@cert) end it "should add the certificate to its inventory" do @ca.stubs(:next_serial).returns @serial @inventory.expects(:add).with(@cert) Puppet::SSL::CertificateRequest.indirection.stubs(:find).with(@name).returns @request Puppet::SSL::Certificate.indirection.stubs :save @ca.sign(@name) end it "should have a method for triggering autosigning of available CSRs" do expect(@ca).to respond_to(:autosign) end describe "when autosigning certificates" do let(:csr) { Puppet::SSL::CertificateRequest.new("host") } describe "using the autosign setting" do let(:autosign) { File.expand_path("/auto/sign") } it "should do nothing if autosign is disabled" do Puppet[:autosign] = false @ca.expects(:sign).never @ca.autosign(csr) end it "should do nothing if no autosign.conf exists" do Puppet[:autosign] = autosign non_existent_file = Puppet::FileSystem::MemoryFile.a_missing_file(autosign) Puppet::FileSystem.overlay(non_existent_file) do @ca.expects(:sign).never @ca.autosign(csr) end end describe "and autosign is enabled and the autosign.conf file exists" do let(:store) { stub 'store', :allow => nil, :allowed? => false } before do Puppet[:autosign] = autosign end describe "when creating the AuthStore instance to verify autosigning" do it "should create an AuthStore with each line in the configuration file allowed to be autosigned" do Puppet::FileSystem.overlay(Puppet::FileSystem::MemoryFile.a_regular_file_containing(autosign, "one\ntwo\n")) do Puppet::Network::AuthStore.stubs(:new).returns store store.expects(:allow).with("one") store.expects(:allow).with("two") @ca.autosign(csr) end end it "should reparse the autosign configuration on each call" do Puppet::FileSystem.overlay(Puppet::FileSystem::MemoryFile.a_regular_file_containing(autosign, "one")) do Puppet::Network::AuthStore.stubs(:new).times(2).returns store @ca.autosign(csr) @ca.autosign(csr) end end it "should ignore comments" do Puppet::FileSystem.overlay(Puppet::FileSystem::MemoryFile.a_regular_file_containing(autosign, "one\n#two\n")) do Puppet::Network::AuthStore.stubs(:new).returns store store.expects(:allow).with("one") @ca.autosign(csr) end end it "should ignore blank lines" do Puppet::FileSystem.overlay(Puppet::FileSystem::MemoryFile.a_regular_file_containing(autosign, "one\n\n")) do Puppet::Network::AuthStore.stubs(:new).returns store store.expects(:allow).with("one") @ca.autosign(csr) end end end end end describe "using the autosign command setting" do let(:cmd) { File.expand_path('/autosign_cmd') } let(:autosign_cmd) { mock 'autosign_command' } let(:autosign_executable) { Puppet::FileSystem::MemoryFile.an_executable(cmd) } before do Puppet[:autosign] = cmd Puppet::SSL::CertificateAuthority::AutosignCommand.stubs(:new).returns autosign_cmd end it "autosigns the CSR if the autosign command returned true" do Puppet::FileSystem.overlay(autosign_executable) do autosign_cmd.expects(:allowed?).with(csr).returns true @ca.expects(:sign).with('host') @ca.autosign(csr) end end it "doesn't autosign the CSR if the autosign_command returned false" do Puppet::FileSystem.overlay(autosign_executable) do autosign_cmd.expects(:allowed?).with(csr).returns false @ca.expects(:sign).never @ca.autosign(csr) end end end end end describe "when managing certificate clients" do before do Puppet.settings.stubs(:use) Puppet::SSL::CertificateAuthority.any_instance.stubs(:password?).returns true stub_ca_host Puppet::SSL::Host.expects(:new).returns @host Puppet::SSL::CertificateAuthority.any_instance.stubs(:host).returns @host @cacert = mock 'certificate' @cacert.stubs(:content).returns "cacertificate" @ca = Puppet::SSL::CertificateAuthority.new end it "should be able to list waiting certificate requests" do req1 = stub 'req1', :name => "one" req2 = stub 'req2', :name => "two" Puppet::SSL::CertificateRequest.indirection.expects(:search).with("*").returns [req1, req2] expect(@ca.waiting?).to eq(%w{one two}) end it "should delegate removing hosts to the Host class" do Puppet::SSL::Host.expects(:destroy).with("myhost") @ca.destroy("myhost") end it "should be able to verify certificates" do expect(@ca).to respond_to(:verify) end it "should list certificates as the sorted list of all existing signed certificates" do cert1 = stub 'cert1', :name => "cert1" cert2 = stub 'cert2', :name => "cert2" Puppet::SSL::Certificate.indirection.expects(:search).with("*").returns [cert1, cert2] expect(@ca.list).to eq(%w{cert1 cert2}) end it "should list the full certificates" do cert1 = stub 'cert1', :name => "cert1" cert2 = stub 'cert2', :name => "cert2" Puppet::SSL::Certificate.indirection.expects(:search).with("*").returns [cert1, cert2] expect(@ca.list_certificates).to eq([cert1, cert2]) end it "should print a deprecation when using #list_certificates" do Puppet::SSL::Certificate.indirection.stubs(:search).with("*").returns [:foo, :bar] Puppet.expects(:deprecation_warning).with(regexp_matches(/list_certificates is deprecated/)) @ca.list_certificates end describe "and printing certificates" do it "should return nil if the certificate cannot be found" do Puppet::SSL::Certificate.indirection.expects(:find).with("myhost").returns nil expect(@ca.print("myhost")).to be_nil end it "should print certificates by calling :to_text on the host's certificate" do cert1 = stub 'cert1', :name => "cert1", :to_text => "mytext" Puppet::SSL::Certificate.indirection.expects(:find).with("myhost").returns cert1 expect(@ca.print("myhost")).to eq("mytext") end end describe "and fingerprinting certificates" do before :each do @cert = stub 'cert', :name => "cert", :fingerprint => "DIGEST" Puppet::SSL::Certificate.indirection.stubs(:find).with("myhost").returns @cert Puppet::SSL::CertificateRequest.indirection.stubs(:find).with("myhost") end it "should raise an error if the certificate or CSR cannot be found" do Puppet::SSL::Certificate.indirection.expects(:find).with("myhost").returns nil Puppet::SSL::CertificateRequest.indirection.expects(:find).with("myhost").returns nil expect { @ca.fingerprint("myhost") }.to raise_error(ArgumentError, /Could not find a certificate/) end it "should try to find a CSR if no certificate can be found" do Puppet::SSL::Certificate.indirection.expects(:find).with("myhost").returns nil Puppet::SSL::CertificateRequest.indirection.expects(:find).with("myhost").returns @cert @cert.expects(:fingerprint) @ca.fingerprint("myhost") end it "should delegate to the certificate fingerprinting" do @cert.expects(:fingerprint) @ca.fingerprint("myhost") end it "should propagate the digest algorithm to the certificate fingerprinting system" do @cert.expects(:fingerprint).with(:digest) @ca.fingerprint("myhost", :digest) end end describe "and verifying certificates" do let(:cacert) { File.expand_path("/ca/cert") } before do @store = stub 'store', :verify => true, :add_file => nil, :purpose= => nil, :add_crl => true, :flags= => nil OpenSSL::X509::Store.stubs(:new).returns @store @cert = stub 'cert', :content => "mycert" Puppet::SSL::Certificate.indirection.stubs(:find).returns @cert @crl = stub('crl', :content => "mycrl") @ca.stubs(:crl).returns @crl end it "should fail if the host's certificate cannot be found" do Puppet::SSL::Certificate.indirection.expects(:find).with("me").returns(nil) expect { @ca.verify("me") }.to raise_error(ArgumentError) end it "should create an SSL Store to verify" do OpenSSL::X509::Store.expects(:new).returns @store @ca.verify("me") end it "should add the CA Certificate to the store" do Puppet[:cacert] = cacert @store.expects(:add_file).with cacert @ca.verify("me") end it "should add the CRL to the store if the crl is enabled" do @store.expects(:add_crl).with "mycrl" @ca.verify("me") end it "should set the store purpose to OpenSSL::X509::PURPOSE_SSL_CLIENT" do Puppet[:cacert] = cacert @store.expects(:add_file).with cacert @ca.verify("me") end it "should set the store flags to check the crl" do @store.expects(:flags=).with OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK @ca.verify("me") end it "should use the store to verify the certificate" do @cert.expects(:content).returns "mycert" @store.expects(:verify).with("mycert").returns true @ca.verify("me") end it "should fail if the verification returns false" do @cert.expects(:content).returns "mycert" @store.expects(:verify).with("mycert").returns false @store.expects(:error) @store.expects(:error_string) expect { @ca.verify("me") }.to raise_error(Puppet::SSL::CertificateAuthority::CertificateVerificationError) end describe "certificate_is_alive?" do it "should return false if verification fails" do @cert.expects(:content).returns "mycert" @store.expects(:verify).with("mycert").returns false expect(@ca.certificate_is_alive?(@cert)).to be_falsey end it "should return true if verification passes" do @cert.expects(:content).returns "mycert" @store.expects(:verify).with("mycert").returns true expect(@ca.certificate_is_alive?(@cert)).to be_truthy end it "should use a cached instance of the x509 store" do OpenSSL::X509::Store.stubs(:new).returns(@store).once @cert.expects(:content).returns "mycert" @store.expects(:verify).with("mycert").returns true @ca.certificate_is_alive?(@cert) @ca.certificate_is_alive?(@cert) end it "should be deprecated" do Puppet.expects(:deprecation_warning).with(regexp_matches(/certificate_is_alive\? is deprecated/)) @ca.certificate_is_alive?(@cert) end end end describe "and revoking certificates" do before do @crl = mock 'crl' @ca.stubs(:crl).returns @crl @ca.stubs(:next_serial).returns 10 @real_cert = stub 'real_cert', :serial => 15 @cert = stub 'cert', :content => @real_cert Puppet::SSL::Certificate.indirection.stubs(:find).returns @cert end it "should fail if the certificate revocation list is disabled" do @ca.stubs(:crl).returns false expect { @ca.revoke('ca_testing') }.to raise_error(ArgumentError) end it "should delegate the revocation to its CRL" do @ca.crl.expects(:revoke) @ca.revoke('host') end it "should get the serial number from the local certificate if it exists" do @ca.crl.expects(:revoke).with { |serial, key| serial == 15 } Puppet::SSL::Certificate.indirection.expects(:find).with("host").returns @cert @ca.revoke('host') end it "should get the serial number from inventory if no local certificate exists" do Puppet::SSL::Certificate.indirection.expects(:find).with("host").returns nil @ca.inventory.expects(:serials).with("host").returns [16] @ca.crl.expects(:revoke).with { |serial, key| serial == 16 } @ca.revoke('host') end it "should revoke all serials matching a name" do Puppet::SSL::Certificate.indirection.expects(:find).with("host").returns nil @ca.inventory.expects(:serials).with("host").returns [16, 20, 25] @ca.crl.expects(:revoke).with { |serial, key| serial == 16 } @ca.crl.expects(:revoke).with { |serial, key| serial == 20 } @ca.crl.expects(:revoke).with { |serial, key| serial == 25 } @ca.revoke('host') end it "should raise an error if no certificate match" do Puppet::SSL::Certificate.indirection.expects(:find).with("host").returns nil @ca.inventory.expects(:serials).with("host").returns [] @ca.crl.expects(:revoke).never expect { @ca.revoke('host') }.to raise_error(ArgumentError, /Could not find a serial number for host/) end context "revocation by serial number (#16798)" do it "revokes when given a lower case hexadecimal formatted string" do @ca.crl.expects(:revoke).with { |serial, key| serial == 15 } Puppet::SSL::Certificate.indirection.expects(:find).with("0xf").returns nil @ca.revoke('0xf') end it "revokes when given an upper case hexadecimal formatted string" do @ca.crl.expects(:revoke).with { |serial, key| serial == 15 } Puppet::SSL::Certificate.indirection.expects(:find).with("0xF").returns nil @ca.revoke('0xF') end it "handles very large serial numbers" do bighex = '0x4000000000000000000000000000000000000000' bighex_int = 365375409332725729550921208179070754913983135744 @ca.crl.expects(:revoke).with(bighex_int, anything) Puppet::SSL::Certificate.indirection.expects(:find).with(bighex).returns nil @ca.revoke(bighex) end end end it "should be able to generate a complete new SSL host" do expect(@ca).to respond_to(:generate) end end end require 'puppet/indirector/memory' module CertificateAuthorityGenerateSpecs describe "CertificateAuthority.generate" do def expect_to_increment_serial_file Puppet.settings.setting(:serial).expects(:exclusive_open) end def expect_to_sign_a_cert expect_to_increment_serial_file end def expect_to_write_the_ca_password Puppet.settings.setting(:capass).expects(:open).with('w:ASCII') end def expect_ca_initialization expect_to_write_the_ca_password expect_to_sign_a_cert end INDIRECTED_CLASSES = [ Puppet::SSL::Certificate, Puppet::SSL::CertificateRequest, Puppet::SSL::CertificateRevocationList, Puppet::SSL::Key, ] INDIRECTED_CLASSES.each do |const| class const::Memory < Puppet::Indirector::Memory # @return Array of all the indirector's values # # This mirrors Puppet::Indirector::SslFile#search which returns all files # in the directory. def search(request) return @instances.values end end end before do Puppet::SSL::Inventory.stubs(:new).returns(stub("Inventory", :add => nil)) INDIRECTED_CLASSES.each { |const| const.indirection.terminus_class = :memory } end after do INDIRECTED_CLASSES.each do |const| const.indirection.terminus_class = :file const.indirection.termini.clear end end describe "when generating certificates" do let(:ca) { Puppet::SSL::CertificateAuthority.new } before do expect_ca_initialization end it "should fail if a certificate already exists for the host" do cert = Puppet::SSL::Certificate.new('pre.existing') Puppet::SSL::Certificate.indirection.save(cert) expect { ca.generate(cert.name) }.to raise_error(ArgumentError, /a certificate already exists/i) end describe "that do not yet exist" do let(:cn) { "new.host" } def expect_cert_does_not_exist(cn) expect( Puppet::SSL::Certificate.indirection.find(cn) ).to be_nil end before do expect_to_sign_a_cert expect_cert_does_not_exist(cn) end it "should return the created certificate" do cert = ca.generate(cn) expect( cert ).to be_kind_of(Puppet::SSL::Certificate) expect( cert.name ).to eq(cn) end it "should not have any subject_alt_names by default" do cert = ca.generate(cn) expect( cert.subject_alt_names ).to be_empty end it "should have subject_alt_names if passed dns_alt_names" do cert = ca.generate(cn, :dns_alt_names => 'foo,bar') expect( cert.subject_alt_names ).to match_array(["DNS:#{cn}",'DNS:foo','DNS:bar']) end context "if autosign is false" do before do Puppet[:autosign] = false end it "should still generate and explicitly sign the request" do cert = nil cert = ca.generate(cn) expect(cert.name).to eq(cn) end end context "if autosign is true (Redmine #6112)" do def run_mode_must_be_master_for_autosign_to_be_attempted Puppet.stubs(:run_mode).returns(Puppet::Util::RunMode[:master]) end before do Puppet[:autosign] = true run_mode_must_be_master_for_autosign_to_be_attempted Puppet::Util::Log.level = :info end it "should generate a cert without attempting to sign again" do cert = ca.generate(cn) expect(cert.name).to eq(cn) expect(@logs.map(&:message)).to include("Autosigning #{cn}") end end end end end end puppet-5.5.10/spec/unit/ssl/certificate_factory_spec.rb0000644005276200011600000001543213417161722023102 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/ssl/certificate_factory' describe Puppet::SSL::CertificateFactory do let :serial do OpenSSL::BN.new('12') end let :name do "example.local" end let :x509_name do OpenSSL::X509::Name.new([['CN', name]]) end let :key do Puppet::SSL::Key.new(name).generate end let :csr do csr = Puppet::SSL::CertificateRequest.new(name) csr.generate(key) csr end let :issuer do cert = Puppet::SSL::CertificateAuthority.new cert.generate_ca_certificate cert.host.certificate.content end describe "when generating the certificate" do it "should return a new X509 certificate" do expect(subject.build(:server, csr, issuer, serial)).not_to eq( subject.build(:server, csr, issuer, serial) ) end it "should set the certificate's version to 2" do expect(subject.build(:server, csr, issuer, serial).version).to eq(2) end it "should set the certificate's subject to the CSR's subject" do cert = subject.build(:server, csr, issuer, serial) expect(cert.subject).to eq x509_name end it "should set the certificate's issuer to the Issuer's subject" do cert = subject.build(:server, csr, issuer, serial) expect(cert.issuer).to eq issuer.subject end it "should set the certificate's public key to the CSR's public key" do cert = subject.build(:server, csr, issuer, serial) expect(cert.public_key).to be_public expect(cert.public_key.to_s).to eq(csr.content.public_key.to_s) end it "should set the certificate's serial number to the provided serial number" do cert = subject.build(:server, csr, issuer, serial) expect(cert.serial).to eq(serial) end it "should have 24 hours grace on the start of the cert" do cert = subject.build(:server, csr, issuer, serial) expect(cert.not_before).to be_within(30).of(Time.now - 24*60*60) end it "should set the default TTL of the certificate to the `ca_ttl` setting" do Puppet[:ca_ttl] = 12 now = Time.now.utc Time.expects(:now).at_least_once.returns(now) cert = subject.build(:server, csr, issuer, serial) expect(cert.not_after.to_i).to eq(now.to_i + 12) end it "should not allow a non-integer TTL" do [ 'foo', 1.2, Time.now, true ].each do |ttl| expect { subject.build(:server, csr, issuer, serial, ttl) }.to raise_error(ArgumentError) end end it "should respect a custom TTL for the CA" do now = Time.now.utc Time.expects(:now).at_least_once.returns(now) cert = subject.build(:server, csr, issuer, serial, 12) expect(cert.not_after.to_i).to eq(now.to_i + 12) end it "should adds an extension for the nsComment" do cert = subject.build(:server, csr, issuer, serial) expect(cert.extensions.map {|x| x.to_h }.find {|x| x["oid"] == "nsComment" }).to eq( { "oid" => "nsComment", # Note that this output is due to a bug in OpenSSL::X509::Extensions # where the values of some extensions are not properly decoded "value" => ".(Puppet Ruby/OpenSSL Internal Certificate", "critical" => false } ) end it "should add an extension for the subjectKeyIdentifer" do cert = subject.build(:server, csr, issuer, serial) ef = OpenSSL::X509::ExtensionFactory.new(issuer, cert) expect(cert.extensions.map { |x| x.to_h }.find {|x| x["oid"] == "subjectKeyIdentifier" }).to eq( ef.create_extension("subjectKeyIdentifier", "hash", false).to_h ) end it "should add an extension for the authorityKeyIdentifer" do cert = subject.build(:server, csr, issuer, serial) ef = OpenSSL::X509::ExtensionFactory.new(issuer, cert) expect(cert.extensions.map { |x| x.to_h }.find {|x| x["oid"] == "authorityKeyIdentifier" }).to eq( ef.create_extension("authorityKeyIdentifier", "keyid:always", false).to_h ) end # See #2848 for why we are doing this: we need to make sure that # subjectAltName is set if the CSR has it, but *not* if it is set when the # certificate is built! it "should not add subjectAltNames from dns_alt_names" do Puppet[:dns_alt_names] = 'one, two' # Verify the CSR still has no extReq, just in case... expect(csr.request_extensions).to eq([]) cert = subject.build(:server, csr, issuer, serial) expect(cert.extensions.find {|x| x.oid == 'subjectAltName' }).to be_nil end it "should add subjectAltName when the CSR requests them" do Puppet[:dns_alt_names] = '' expect = %w{one two} + [name] csr = Puppet::SSL::CertificateRequest.new(name) csr.generate(key, :dns_alt_names => expect.join(', ')) expect(csr.request_extensions).not_to be_nil expect(csr.subject_alt_names).to match_array(expect.map{|x| "DNS:#{x}"}) cert = subject.build(:server, csr, issuer, serial) san = cert.extensions.find {|x| x.oid == 'subjectAltName' } expect(san).not_to be_nil expect.each do |name| expect(san.value).to match(/DNS:#{name}\b/i) end end it "can add custom extension requests" do csr = Puppet::SSL::CertificateRequest.new(name) csr.generate(key) csr.stubs(:request_extensions).returns([ {'oid' => '1.3.6.1.4.1.34380.1.2.1', 'value' => 'some-value'}, {'oid' => 'pp_uuid', 'value' => 'some-uuid'}, ]) cert = subject.build(:client, csr, issuer, serial) # The cert must be signed before being later DER-decoding signer = Puppet::SSL::CertificateSigner.new signer.sign(cert, key) wrapped_cert = Puppet::SSL::Certificate.from_instance cert priv_ext = wrapped_cert.custom_extensions.find {|ext| ext['oid'] == '1.3.6.1.4.1.34380.1.2.1'} uuid_ext = wrapped_cert.custom_extensions.find {|ext| ext['oid'] == 'pp_uuid'} # The expected results should be DER encoded, the Puppet cert wrapper will turn # these into normal strings. expect(priv_ext['value']).to eq 'some-value' expect(uuid_ext['value']).to eq 'some-uuid' end # Can't check the CA here, since that requires way more infrastructure # that I want to build up at this time. We can verify the critical # values, though, which are non-CA certs. --daniel 2011-10-11 { :ca => 'CA:TRUE', :terminalsubca => ['CA:TRUE', 'pathlen:0'], :server => 'CA:FALSE', :ocsp => 'CA:FALSE', :client => 'CA:FALSE', }.each do |name, value| it "should set basicConstraints for #{name} #{value.inspect}" do cert = subject.build(name, csr, issuer, serial) bc = cert.extensions.find {|x| x.oid == 'basicConstraints' } expect(bc).to be expect(bc.value.split(/\s*,\s*/)).to match_array(Array(value)) end end end end puppet-5.5.10/spec/unit/ssl/certificate_request_attributes_spec.rb0000644005276200011600000000475213417161722025374 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/ssl/certificate_request_attributes' describe Puppet::SSL::CertificateRequestAttributes do let(:expected) do { "custom_attributes" => { "1.3.6.1.4.1.34380.2.2"=>[3232235521, 3232235777], # system IPs in hex "1.3.6.1.4.1.34380.2.0"=>"hostname.domain.com", # different UTF-8 widths # 1-byte A # 2-byte Űż - http://www.fileformat.info/info/unicode/char/06ff/index.htm - 0xDB 0xBF / 219 191 # 3-byte áš  - http://www.fileformat.info/info/unicode/char/16A0/index.htm - 0xE1 0x9A 0xA0 / 225 154 160 # 4-byte 𠜎 - http://www.fileformat.info/info/unicode/char/2070E/index.htm - 0xF0 0xA0 0x9C 0x8E / 240 160 156 142 "1.2.840.113549.1.9.7"=>"utf8passwordA\u06FF\u16A0\u{2070E}" } } end let(:csr_attributes_hash) { expected.dup } let(:csr_attributes_path) { '/some/where/csr_attributes.yaml' } let(:csr_attributes) { Puppet::SSL::CertificateRequestAttributes.new(csr_attributes_path) } it "initializes with a path" do expect(csr_attributes.path).to eq(csr_attributes_path) end describe "loading" do it "returns nil when loading from a non-existent file" do expect(csr_attributes.load).to be_falsey end context "with an available attributes file" do before do Puppet::FileSystem.expects(:exist?).with(csr_attributes_path).returns(true) Puppet::Util::Yaml.expects(:load_file).with(csr_attributes_path, {}).returns(csr_attributes_hash) end it "loads csr attributes from a file when the file is present" do expect(csr_attributes.load).to be_truthy end it "exposes custom_attributes" do csr_attributes.load expect(csr_attributes.custom_attributes).to eq(expected['custom_attributes']) end it "returns an empty hash if custom_attributes points to nil" do csr_attributes_hash["custom_attributes"] = nil csr_attributes.load expect(csr_attributes.custom_attributes).to eq({}) end it "returns an empty hash if custom_attributes key is not present" do csr_attributes_hash.delete("custom_attributes") csr_attributes.load expect(csr_attributes.custom_attributes).to eq({}) end it "raise a Puppet::Error if an unexpected root key is defined" do csr_attributes_hash['unintentional'] = 'data' expect { csr_attributes.load }.to raise_error(Puppet::Error, /unexpected attributes.*unintentional/) end end end end puppet-5.5.10/spec/unit/ssl/certificate_request_spec.rb0000644005276200011600000003723513417161722023130 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/ssl/certificate_request' require 'puppet/ssl/key' describe Puppet::SSL::CertificateRequest do let(:request) { described_class.new("myname") } let(:key) { k = Puppet::SSL::Key.new("myname") k.generate k } it "should be extended with the Indirector module" do expect(described_class.singleton_class).to be_include(Puppet::Indirector) end it "should indirect certificate_request" do expect(described_class.indirection.name).to eq(:certificate_request) end it "should use any provided name as its name" do expect(described_class.new("myname").name).to eq("myname") end it "should only support the text format" do expect(described_class.supported_formats).to eq([:s]) end describe "when converting from a string" do it "should create a CSR instance with its name set to the CSR subject and its content set to the extracted CSR" do csr = stub 'csr', :subject => OpenSSL::X509::Name.parse("/CN=Foo.madstop.com"), :is_a? => true OpenSSL::X509::Request.expects(:new).with("my csr").returns(csr) mycsr = stub 'sslcsr' mycsr.expects(:content=).with(csr) described_class.expects(:new).with("Foo.madstop.com").returns mycsr described_class.from_s("my csr") end end describe "when managing instances" do it "should have a name attribute" do expect(request.name).to eq("myname") end it "should downcase its name" do expect(described_class.new("MyName").name).to eq("myname") end it "should have a content attribute" do expect(request).to respond_to(:content) end it "should be able to read requests from disk" do path = "/my/path" Puppet::FileSystem.expects(:read).with(path, :encoding => Encoding::ASCII).returns("my request") my_req = mock 'request' OpenSSL::X509::Request.expects(:new).with("my request").returns(my_req) expect(request.read(path)).to equal(my_req) expect(request.content).to equal(my_req) end it "should return an empty string when converted to a string with no request" do expect(request.to_s).to eq("") end it "should convert the request to pem format when converted to a string" do request.generate(key) expect(request.to_s).to eq(request.content.to_pem) end it "should have a :to_text method that it delegates to the actual key" do real_request = mock 'request' real_request.expects(:to_text).returns "requesttext" request.content = real_request expect(request.to_text).to eq("requesttext") end end describe "when generating" do it "should use the content of the provided key if the key is a Puppet::SSL::Key instance" do request.generate(key) expect(request.content.verify(key.content.public_key)).to be_truthy end it "should set the subject to [CN, name]" do request.generate(key) expect(request.content.subject).to eq OpenSSL::X509::Name.new([['CN', key.name]]) end it "should set the CN to the :ca_name setting when the CSR is for a CA" do Puppet[:ca_name] = "mycertname" request = described_class.new(Puppet::SSL::CA_NAME).generate(key) expect(request.subject).to eq OpenSSL::X509::Name.new([['CN', Puppet[:ca_name]]]) end it "should set the version to 0" do request.generate(key) expect(request.content.version).to eq(0) end it "should set the public key to the provided key's public key" do request.generate(key) # The openssl bindings do not define equality on keys so we use to_s expect(request.content.public_key.to_s).to eq(key.content.public_key.to_s) end context "without subjectAltName / dns_alt_names" do before :each do Puppet[:dns_alt_names] = "" end ["extreq", "msExtReq"].each do |name| it "should not add any #{name} attribute" do request.generate(key) expect(request.content.attributes.find do |attr| attr.oid == name end).not_to be end it "should return no subjectAltNames" do request.generate(key) expect(request.subject_alt_names).to be_empty end end end context "with dns_alt_names" do before :each do Puppet[:dns_alt_names] = "one, two, three" end ["extreq", "msExtReq"].each do |name| it "should not add any #{name} attribute" do request.generate(key) expect(request.content.attributes.find do |attr| attr.oid == name end).not_to be end it "should return no subjectAltNames" do request.generate(key) expect(request.subject_alt_names).to be_empty end end end context "with subjectAltName to generate request" do before :each do Puppet[:dns_alt_names] = "" end it "should add an extreq attribute" do request.generate(key, :dns_alt_names => 'one, two') extReq = request.content.attributes.find do |attr| attr.oid == 'extReq' end expect(extReq).to be extReq.value.value.all? do |x| x.value.all? do |y| expect(y.value[0].value).to eq("subjectAltName") end end end it "should return the subjectAltName values" do request.generate(key, :dns_alt_names => 'one,two') expect(request.subject_alt_names).to match_array(["DNS:myname", "DNS:one", "DNS:two"]) end end context "with DNS and IP SAN specified" do before :each do Puppet[:dns_alt_names] = "" end it "should return the subjectAltName values" do request.generate(key, :dns_alt_names => 'DNS:foo, bar, IP:172.16.254.1') expect(request.subject_alt_names).to match_array(["DNS:bar", "DNS:foo", "DNS:myname", "IP Address:172.16.254.1"]) end end context "with custom CSR attributes" do it "adds attributes with single values" do csr_attributes = { '1.3.6.1.4.1.34380.1.2.1' => 'CSR specific info', '1.3.6.1.4.1.34380.1.2.2' => 'more CSR specific info', } request.generate(key, :csr_attributes => csr_attributes) attrs = request.custom_attributes expect(attrs).to include({'oid' => '1.3.6.1.4.1.34380.1.2.1', 'value' => 'CSR specific info'}) expect(attrs).to include({'oid' => '1.3.6.1.4.1.34380.1.2.2', 'value' => 'more CSR specific info'}) end ['extReq', '1.2.840.113549.1.9.14'].each do |oid| it "doesn't overwrite standard PKCS#9 CSR attribute '#{oid}'" do expect do request.generate(key, :csr_attributes => {oid => 'data'}) end.to raise_error ArgumentError, /Cannot specify.*#{oid}/ end end ['msExtReq', '1.3.6.1.4.1.311.2.1.14'].each do |oid| it "doesn't overwrite Microsoft extension request OID '#{oid}'" do expect do request.generate(key, :csr_attributes => {oid => 'data'}) end.to raise_error ArgumentError, /Cannot specify.*#{oid}/ end end it "raises an error if an attribute cannot be created" do csr_attributes = { "thats.no.moon" => "death star" } expect do request.generate(key, :csr_attributes => csr_attributes) end.to raise_error Puppet::Error, /Cannot create CSR with attribute thats\.no\.moon: first num too large/ end it "should support old non-DER encoded extensions" do csr = OpenSSL::X509::Request.new(File.read(my_fixture("old-style-cert-request.pem"))) wrapped_csr = Puppet::SSL::CertificateRequest.from_instance csr exts = wrapped_csr.request_extensions() expect(exts.find { |ext| ext['oid'] == 'pp_uuid' }['value']).to eq('I-AM-A-UUID') expect(exts.find { |ext| ext['oid'] == 'pp_instance_id' }['value']).to eq('i_am_an_id') expect(exts.find { |ext| ext['oid'] == 'pp_image_name' }['value']).to eq('i_am_an_image_name') end end context "with extension requests" do let(:extension_data) do { '1.3.6.1.4.1.34380.1.1.31415' => 'pi', '1.3.6.1.4.1.34380.1.1.2718' => 'e', } end it "adds an extreq attribute to the CSR" do request.generate(key, :extension_requests => extension_data) exts = request.content.attributes.select { |attr| attr.oid = 'extReq' } expect(exts.length).to eq(1) end it "adds an extension for each entry in the extension request structure" do request.generate(key, :extension_requests => extension_data) exts = request.request_extensions expect(exts).to include('oid' => '1.3.6.1.4.1.34380.1.1.31415', 'value' => 'pi') expect(exts).to include('oid' => '1.3.6.1.4.1.34380.1.1.2718', 'value' => 'e') end it "defines the extensions as non-critical" do request.generate(key, :extension_requests => extension_data) request.request_extensions.each do |ext| expect(ext['critical']).to be_falsey end end it "rejects the subjectAltNames extension" do san_names = ['subjectAltName', '2.5.29.17'] san_field = 'DNS:first.tld, DNS:second.tld' san_names.each do |name| expect do request.generate(key, :extension_requests => {name => san_field}) end.to raise_error Puppet::Error, /conflicts with internally used extension/ end end it "merges the extReq attribute with the subjectAltNames extension" do request.generate(key, :dns_alt_names => 'first.tld, second.tld', :extension_requests => extension_data) exts = request.request_extensions expect(exts).to include('oid' => '1.3.6.1.4.1.34380.1.1.31415', 'value' => 'pi') expect(exts).to include('oid' => '1.3.6.1.4.1.34380.1.1.2718', 'value' => 'e') expect(exts).to include('oid' => 'subjectAltName', 'value' => 'DNS:first.tld, DNS:myname, DNS:second.tld') expect(request.subject_alt_names).to eq ['DNS:first.tld', 'DNS:myname', 'DNS:second.tld'] end it "raises an error if the OID could not be created" do exts = {"thats.no.moon" => "death star"} expect do request.generate(key, :extension_requests => exts) end.to raise_error Puppet::Error, /Cannot create CSR with extension request thats\.no\.moon.*: first num too large/ end end it "should sign the csr with the provided key" do request.generate(key) expect(request.content.verify(key.content.public_key)).to be_truthy end it "should verify the generated request using the public key" do # Stupid keys don't have a competent == method. OpenSSL::X509::Request.any_instance.expects(:verify).with { |public_key| public_key.to_s == key.content.public_key.to_s }.returns true request.generate(key) end it "should fail if verification fails" do OpenSSL::X509::Request.any_instance.expects(:verify).with { |public_key| public_key.to_s == key.content.public_key.to_s }.returns false expect { request.generate(key) }.to raise_error(Puppet::Error, /CSR sign verification failed/) end it "should log the fingerprint" do Puppet::SSL::Digest.any_instance.stubs(:to_hex).returns("FINGERPRINT") Puppet.stubs(:info) Puppet.expects(:info).with { |s| s =~ /FINGERPRINT/ } request.generate(key) end it "should return the generated request" do generated = request.generate(key) expect(generated).to be_a(OpenSSL::X509::Request) expect(generated).to be(request.content) end it "should use SHA1 to sign the csr when SHA256 isn't available" do csr = OpenSSL::X509::Request.new OpenSSL::Digest.expects(:const_defined?).with("SHA256").returns(false) OpenSSL::Digest.expects(:const_defined?).with("SHA1").returns(true) signer = Puppet::SSL::CertificateSigner.new signer.sign(csr, key.content) expect(csr.verify(key.content)).to be_truthy end # Attempts to use SHA512 and SHA384 for signing certificates don't seem to work # So commenting it out till it is sorted out # The problem seems to be with the ability to sign a CSR when using either of # these hash algorithms # it "should use SHA512 to sign the csr when SHA256 and SHA1 aren't available" do # csr = OpenSSL::X509::Request.new # OpenSSL::Digest.expects(:const_defined?).with("SHA256").returns(false) # OpenSSL::Digest.expects(:const_defined?).with("SHA1").returns(false) # OpenSSL::Digest.expects(:const_defined?).with("SHA512").returns(true) # signer = Puppet::SSL::CertificateSigner.new # signer.sign(csr, key.content) # expect(csr.verify(key.content)).to be_truthy # end # it "should use SHA384 to sign the csr when SHA256/SHA1/SHA512 aren't available" do # csr = OpenSSL::X509::Request.new # OpenSSL::Digest.expects(:const_defined?).with("SHA256").returns(false) # OpenSSL::Digest.expects(:const_defined?).with("SHA1").returns(false) # OpenSSL::Digest.expects(:const_defined?).with("SHA512").returns(false) # OpenSSL::Digest.expects(:const_defined?).with("SHA384").returns(true) # signer = Puppet::SSL::CertificateSigner.new # signer.sign(csr, key.content) # expect(csr.verify(key.content)).to be_truthy # end it "should use SHA224 to sign the csr when SHA256/SHA1/SHA512/SHA384 aren't available" do csr = OpenSSL::X509::Request.new OpenSSL::Digest.expects(:const_defined?).with("SHA256").returns(false) OpenSSL::Digest.expects(:const_defined?).with("SHA1").returns(false) OpenSSL::Digest.expects(:const_defined?).with("SHA512").returns(false) OpenSSL::Digest.expects(:const_defined?).with("SHA384").returns(false) OpenSSL::Digest.expects(:const_defined?).with("SHA224").returns(true) signer = Puppet::SSL::CertificateSigner.new signer.sign(csr, key.content) expect(csr.verify(key.content)).to be_truthy end it "should raise an error if neither SHA256/SHA1/SHA512/SHA384/SHA224 are available" do OpenSSL::Digest.expects(:const_defined?).with("SHA256").returns(false) OpenSSL::Digest.expects(:const_defined?).with("SHA1").returns(false) OpenSSL::Digest.expects(:const_defined?).with("SHA512").returns(false) OpenSSL::Digest.expects(:const_defined?).with("SHA384").returns(false) OpenSSL::Digest.expects(:const_defined?).with("SHA224").returns(false) expect { Puppet::SSL::CertificateSigner.new }.to raise_error(Puppet::Error) end end describe "when a CSR is saved" do describe "and a CA is available" do it "should save the CSR and trigger autosigning" do ca = mock 'ca', :autosign Puppet::SSL::CertificateAuthority.expects(:instance).returns ca csr = Puppet::SSL::CertificateRequest.new("me") terminus = mock 'terminus' terminus.stubs(:validate) Puppet::SSL::CertificateRequest.indirection.expects(:prepare).returns(terminus) terminus.expects(:save).with { |request| request.instance == csr && request.key == "me" } Puppet::SSL::CertificateRequest.indirection.save(csr) end end describe "and a CA is not available" do it "should save the CSR" do Puppet::SSL::CertificateAuthority.expects(:instance).returns nil csr = Puppet::SSL::CertificateRequest.new("me") terminus = mock 'terminus' terminus.stubs(:validate) Puppet::SSL::CertificateRequest.indirection.expects(:prepare).returns(terminus) terminus.expects(:save).with { |request| request.instance == csr && request.key == "me" } Puppet::SSL::CertificateRequest.indirection.save(csr) end end end end puppet-5.5.10/spec/unit/ssl/certificate_revocation_list_spec.rb0000644005276200011600000001363013417161722024635 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/ssl/certificate_revocation_list' describe Puppet::SSL::CertificateRevocationList do before do ca = Puppet::SSL::CertificateAuthority.new ca.generate_ca_certificate @cert = ca.host.certificate.content @key = ca.host.key.content @class = Puppet::SSL::CertificateRevocationList end def expects_time_close_to_now(time) expect(time.to_i).to be_within(5*60).of(Time.now.to_i) end def expects_time_close_to_five_years(time) future = Time.now + Puppet::SSL::CertificateRevocationList::FIVE_YEARS expect(time.to_i).to be_within(5*60).of(future.to_i) end def expects_crlnumber_extension(crl, value) crlNumber = crl.content.extensions.find { |ext| ext.oid == "crlNumber" } expect(crlNumber.value).to eq(value.to_s) expect(crlNumber).to_not be_critical end def expects_authkeyid_extension(crl, cert) subjectKeyId = cert.extensions.find { |ext| ext.oid == 'subjectKeyIdentifier' }.value authKeyId = crl.content.extensions.find { |ext| ext.oid == "authorityKeyIdentifier" } expect(authKeyId.value.chomp).to eq("keyid:#{subjectKeyId}") expect(authKeyId).to_not be_critical end def expects_crlreason_extension(crl, reason) revoke = crl.content.revoked.first crlNumber = crl.content.extensions.find { |ext| ext.oid == "crlNumber" } expect(revoke.serial.to_s).to eq(crlNumber.value) crlReason = revoke.extensions.find { |ext| ext.oid = 'CRLReason' } expect(crlReason.value).to eq(reason) expect(crlReason).to_not be_critical end it "should only support the text format" do expect(@class.supported_formats).to eq([:s]) end describe "when converting from a string" do it "deserializes a CRL" do crl = @class.new('foo') crl.generate(@cert, @key) new_crl = @class.from_s(crl.to_s) expect(new_crl.content.to_text).to eq(crl.content.to_text) end end describe "when an instance" do before do @crl = @class.new("whatever") end it "should always use 'crl' for its name" do expect(@crl.name).to eq("crl") end it "should have a content attribute" do expect(@crl).to respond_to(:content) end end describe "when generating the crl" do before do @crl = @class.new("crl") end it "should set its issuer to the subject of the passed certificate" do expect(@crl.generate(@cert, @key).issuer.to_s).to eq(@cert.subject.to_s) end it "should set its version to 1" do expect(@crl.generate(@cert, @key).version).to eq(1) end it "should create an instance of OpenSSL::X509::CRL" do expect(@crl.generate(@cert, @key)).to be_an_instance_of(OpenSSL::X509::CRL) end it "should add an extension for the CRL number" do @crl.generate(@cert, @key) expects_crlnumber_extension(@crl, 0) end it "should add an extension for the authority key identifier" do @crl.generate(@cert, @key) expects_authkeyid_extension(@crl, @cert) end it "returns the last update time in UTC" do # https://tools.ietf.org/html/rfc5280#section-5.1.2.4 thisUpdate = @crl.generate(@cert, @key).last_update expect(thisUpdate).to be_utc expects_time_close_to_now(thisUpdate) end it "returns the next update time in UTC 5 years from now" do # https://tools.ietf.org/html/rfc5280#section-5.1.2.5 nextUpdate = @crl.generate(@cert, @key).next_update expect(nextUpdate).to be_utc expects_time_close_to_five_years(nextUpdate) end it "should verify using the CA public_key" do expect(@crl.generate(@cert, @key).verify(@key.public_key)).to be_truthy end it "should set the content to the generated crl" do # this test shouldn't be needed since we test the return of generate() which should be the content field @crl.generate(@cert, @key) expect(@crl.content).to be_an_instance_of(OpenSSL::X509::CRL) end end # This test suite isn't exactly complete, because the # SSL stuff is very complicated. It just hits the high points. describe "when revoking a certificate" do before do @crl = @class.new("crl") @crl.generate(@cert, @key) Puppet::SSL::CertificateRevocationList.indirection.stubs :save end it "should require a serial number and the CA's private key" do expect { @crl.revoke }.to raise_error(ArgumentError) end it "should mark the CRL as updated at a time that makes it valid now" do @crl.revoke(1, @key) expects_time_close_to_now(@crl.content.last_update) end it "should mark the CRL valid for five years" do @crl.revoke(1, @key) expects_time_close_to_five_years(@crl.content.next_update) end it "should sign the CRL with the CA's private key and a digest instance" do digest = Puppet::SSL::CertificateSigner.new.digest @crl.content.expects(:sign).with { |key, signer| key == @key and signer.is_a?(digest) } @crl.revoke(1, @key) end it "should save the CRL" do Puppet::SSL::CertificateRevocationList.indirection.expects(:save).with(@crl, nil) @crl.revoke(1, @key) end it "adds the crlNumber extension containing the serial number" do serial = 1 @crl.revoke(serial, @key) expects_crlnumber_extension(@crl, serial) end it "adds the CA cert's subjectKeyId as the authorityKeyIdentifier to the CRL" do @crl.revoke(1, @key) expects_authkeyid_extension(@crl, @cert) end it "adds a non-critical CRL reason specifying key compromise by default" do # https://tools.ietf.org/html/rfc5280#section-5.3.1 serial = 1 @crl.revoke(serial, @key) expects_crlreason_extension(@crl, 'Key Compromise') end it "allows alternate reasons to be specified" do serial = 1 @crl.revoke(serial, @key, OpenSSL::OCSP::REVOKED_STATUS_CACOMPROMISE) expects_crlreason_extension(@crl, 'CA Compromise') end end end puppet-5.5.10/spec/unit/ssl/certificate_spec.rb0000644005276200011600000001424613417161722021355 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/ssl/certificate' describe Puppet::SSL::Certificate do let :key do Puppet::SSL::Key.new("test.localdomain").generate end # Sign the provided cert so that it can be DER-decoded later def sign_wrapped_cert(cert) signer = Puppet::SSL::CertificateSigner.new signer.sign(cert.content, key) end before do @class = Puppet::SSL::Certificate end after do @class.instance_variable_set("@ca_location", nil) end it "should be extended with the Indirector module" do expect(@class.singleton_class).to be_include(Puppet::Indirector) end it "should indirect certificate" do expect(@class.indirection.name).to eq(:certificate) end it "should only support the text format" do expect(@class.supported_formats).to eq([:s]) end describe "when converting from a string" do it "should create a certificate instance with its name set to the certificate subject and its content set to the extracted certificate" do cert = stub 'certificate', :subject => OpenSSL::X509::Name.parse("/CN=Foo.madstop.com"), :is_a? => true OpenSSL::X509::Certificate.expects(:new).with("my certificate").returns(cert) mycert = stub 'sslcert' mycert.expects(:content=).with(cert) @class.expects(:new).with("Foo.madstop.com").returns mycert @class.from_s("my certificate") end it "should create multiple certificate instances when asked" do cert1 = stub 'cert1' @class.expects(:from_s).with("cert1").returns cert1 cert2 = stub 'cert2' @class.expects(:from_s).with("cert2").returns cert2 expect(@class.from_multiple_s("cert1\n---\ncert2")).to eq([cert1, cert2]) end end describe "when converting to a string" do before do @certificate = @class.new("myname") end it "should return an empty string when it has no certificate" do expect(@certificate.to_s).to eq("") end it "should convert the certificate to pem format" do certificate = mock 'certificate', :to_pem => "pem" @certificate.content = certificate expect(@certificate.to_s).to eq("pem") end it "should be able to convert multiple instances to a string" do cert2 = @class.new("foo") @certificate.expects(:to_s).returns "cert1" cert2.expects(:to_s).returns "cert2" expect(@class.to_multiple_s([@certificate, cert2])).to eq("cert1\n---\ncert2") end end describe "when managing instances" do def build_cert(opts) key = Puppet::SSL::Key.new('quux') key.generate csr = Puppet::SSL::CertificateRequest.new('quux') csr.generate(key, opts) raw_cert = Puppet::SSL::CertificateFactory.build('client', csr, csr.content, 14) @class.from_instance(raw_cert) end before do @certificate = @class.new("myname") end it "should have a name attribute" do expect(@certificate.name).to eq("myname") end it "should convert its name to a string and downcase it" do expect(@class.new(:MyName).name).to eq("myname") end it "should have a content attribute" do expect(@certificate).to respond_to(:content) end describe "#subject_alt_names" do it "should list all alternate names when the extension is present" do certificate = build_cert(:dns_alt_names => 'foo, bar,baz') expect(certificate.subject_alt_names). to match_array(['DNS:foo', 'DNS:bar', 'DNS:baz', 'DNS:quux']) end it "should return an empty list of names if the extension is absent" do certificate = build_cert({}) expect(certificate.subject_alt_names).to be_empty end end describe "custom extensions" do it "returns extensions under the ppRegCertExt" do exts = {'pp_uuid' => 'abcdfd'} cert = build_cert(:extension_requests => exts) sign_wrapped_cert(cert) expect(cert.custom_extensions).to include('oid' => 'pp_uuid', 'value' => 'abcdfd') end it "returns extensions under the ppPrivCertExt" do exts = {'1.3.6.1.4.1.34380.1.2.1' => 'x509 :('} cert = build_cert(:extension_requests => exts) sign_wrapped_cert(cert) expect(cert.custom_extensions).to include('oid' => '1.3.6.1.4.1.34380.1.2.1', 'value' => 'x509 :(') end it "doesn't return standard extensions" do cert = build_cert(:dns_alt_names => 'foo') expect(cert.custom_extensions).to be_empty end end it "should return a nil expiration if there is no actual certificate" do @certificate.stubs(:content).returns nil expect(@certificate.expiration).to be_nil end it "should use the expiration of the certificate as its expiration date" do cert = stub 'cert' @certificate.stubs(:content).returns cert cert.expects(:not_after).returns "sometime" expect(@certificate.expiration).to eq("sometime") end it "should be able to read certificates from disk" do path = "/my/path" Puppet::FileSystem.expects(:read).with(path, :encoding => Encoding::ASCII).returns("my certificate") certificate = mock 'certificate' OpenSSL::X509::Certificate.expects(:new).with("my certificate").returns(certificate) expect(@certificate.read(path)).to equal(certificate) expect(@certificate.content).to equal(certificate) end it "should have a :to_text method that it delegates to the actual key" do real_certificate = mock 'certificate' real_certificate.expects(:to_text).returns "certificatetext" @certificate.content = real_certificate expect(@certificate.to_text).to eq("certificatetext") end it "should parse the old non-DER encoded extension values" do cert = OpenSSL::X509::Certificate.new(File.read(my_fixture("old-style-cert-exts.pem"))) wrapped_cert = Puppet::SSL::Certificate.from_instance cert exts = wrapped_cert.custom_extensions expect(exts.find { |ext| ext['oid'] == 'pp_uuid'}['value']).to eq('I-AM-A-UUID') expect(exts.find { |ext| ext['oid'] == 'pp_instance_id'}['value']).to eq('i_am_an_id') expect(exts.find { |ext| ext['oid'] == 'pp_image_name'}['value']).to eq('i_am_an_image_name') end end end puppet-5.5.10/spec/unit/ssl/configuration_spec.rb0000644005276200011600000001264113417161722021737 0ustar jenkinsjenkins#! /usr/bin/env ruby # require 'spec_helper' require 'puppet/ssl/configuration' describe Puppet::SSL::Configuration do let(:localcacert) { "/path/to/certs/ca.pem" } let(:ssl_server_ca_auth) { "/path/to/certs/ssl_server_ca_auth.pem" } # different UTF-8 widths # 1-byte A # 2-byte Űż - http://www.fileformat.info/info/unicode/char/06ff/index.htm - 0xDB 0xBF / 219 191 # 3-byte áš  - http://www.fileformat.info/info/unicode/char/16A0/index.htm - 0xE1 0x9A 0xA0 / 225 154 160 # 4-byte 𠜎 - http://www.fileformat.info/info/unicode/char/2070E/index.htm - 0xF0 0xA0 0x9C 0x8E / 240 160 156 142 let (:mixed_utf8) { "A\u06FF\u16A0\u{2070E}" } # Aۿᚠ𠜎 it "should require the localcacert argument" do expect { subject }.to raise_error ArgumentError end context "Default configuration" do subject do described_class.new(localcacert) end it "#ca_chain_file == localcacert" do expect(subject.ca_chain_file).to eq(localcacert) end it "#ca_auth_file == localcacert" do expect(subject.ca_auth_file).to eq(localcacert) end end context "Explicitly configured" do subject do options = { :ca_auth_file => ssl_server_ca_auth, } Puppet::SSL::Configuration.new(localcacert, options) end it "#ca_chain_file == ssl_server_ca_chain" do expect(subject.ca_chain_file).to eq(ssl_server_ca_auth) end it "#ca_auth_file == ssl_server_ca_auth" do expect(subject.ca_auth_file).to eq(ssl_server_ca_auth) end it "#ca_auth_certificates returns an Array" do Puppet::FileSystem.expects(:read).with(subject.ca_auth_file, :encoding => Encoding::UTF_8).returns(master_ca_pem + root_ca_pem) certs = subject.ca_auth_certificates certs.each { |cert| expect(cert).to be_a_kind_of OpenSSL::X509::Certificate } end end context "Partially configured" do describe "#ca_chain_file" do subject do described_class.new(localcacert, { :ca_auth_file => ssl_server_ca_auth }) end it "should use ca_auth_file" do expect(subject.ca_chain_file).to eq(ssl_server_ca_auth) end end end # This is the Intermediate CA specifically designated for issuing master # certificates. It is signed by the Root CA. def master_ca_pem @master_ca_pem ||= <<-AUTH_BUNDLE # Master CA #{mixed_utf8} -----BEGIN CERTIFICATE----- MIICljCCAf+gAwIBAgIBAjANBgkqhkiG9w0BAQUFADBJMRAwDgYDVQQDDAdSb290 IENBMRowGAYDVQQLDBFTZXJ2ZXIgT3BlcmF0aW9uczEZMBcGA1UECgwQRXhhbXBs ZSBPcmcsIExMQzAeFw0xMzAzMzAwNTUwNDhaFw0zMzAzMjUwNTUwNDhaMH4xJDAi BgNVBAMTG0ludGVybWVkaWF0ZSBDQSAobWFzdGVyLWNhKTEfMB0GCSqGSIb3DQEJ ARYQdGVzdEBleGFtcGxlLm9yZzEZMBcGA1UEChMQRXhhbXBsZSBPcmcsIExMQzEa MBgGA1UECxMRU2VydmVyIE9wZXJhdGlvbnMwXDANBgkqhkiG9w0BAQEFAANLADBI AkEAvo/az3oR69SP92jGnUHMJLEyyD1Ui1BZ/rUABJcQTRQqn3RqtlfYePWZnUaZ srKbXRS4q0w5Vqf1kx5w3q5tIwIDAQABo4GcMIGZMHkGA1UdIwRyMHCAFDBN1mqO Nc4gUraE4zRtw6ueFDDaoU2kSzBJMRAwDgYDVQQDDAdSb290IENBMRowGAYDVQQL DBFTZXJ2ZXIgT3BlcmF0aW9uczEZMBcGA1UECgwQRXhhbXBsZSBPcmcsIExMQ4IJ ALf2Pk2HvtBzMA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMA0GCSqGSIb3 DQEBBQUAA4GBACRfa1YPS7RQUuhYovGgV0VYqxuATC7WwdIRihVh5FceSXKgSIbz BKmOBAy/KixEhpnHTbkpaJ0d9ITkvjMTmj3M5YMahKaQA5niVPckQPecMMd6jg9U l1k75xLLIcrlsDYo3999KOSSchH2K7bLT7TuQ2okdP6FHWmeWmudewlu -----END CERTIFICATE----- AUTH_BUNDLE end # This is the Root CA def root_ca_pem @root_ca_pem ||= <<-LOCALCACERT # Root CA #{mixed_utf8} -----BEGIN CERTIFICATE----- MIICYDCCAcmgAwIBAgIJALf2Pk2HvtBzMA0GCSqGSIb3DQEBBQUAMEkxEDAOBgNV BAMMB1Jvb3QgQ0ExGjAYBgNVBAsMEVNlcnZlciBPcGVyYXRpb25zMRkwFwYDVQQK DBBFeGFtcGxlIE9yZywgTExDMB4XDTEzMDMzMDA1NTA0OFoXDTMzMDMyNTA1NTA0 OFowSTEQMA4GA1UEAwwHUm9vdCBDQTEaMBgGA1UECwwRU2VydmVyIE9wZXJhdGlv bnMxGTAXBgNVBAoMEEV4YW1wbGUgT3JnLCBMTEMwgZ8wDQYJKoZIhvcNAQEBBQAD gY0AMIGJAoGBAMGSpafR4lboYOPfPJC1wVHHl0gD49ZVRjOlJ9jidEUjBdFXK6SA S1tecDv2G4tM1ANmfMKjZl0m+KaZ8O2oq0g6kxkq1Mg0eSNvlnEyehjmTLRzHC2i a0biH2wMtCLzfAoXDKy4GPlciBPE9mup5I8Kien5s91t92tc7K8AJ8oBAgMBAAGj UDBOMB0GA1UdDgQWBBQwTdZqjjXOIFK2hOM0bcOrnhQw2jAfBgNVHSMEGDAWgBQw TdZqjjXOIFK2hOM0bcOrnhQw2jAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUA A4GBACs8EZRrzgzAlcKC1Tz8GYlNHQg0XhpbEDm+p2mOV//PuDD190O+UBpWxo9Q rrkkx8En0wXQZJf6iH3hwewwHLOq5yXZKbJN+SmvJvRNL95Yhyy08Y9N65tJveE7 rPsNU/Tx19jHC87oXlmAePLI4IaUHXrWb7CRbY9TEcPdmj1R -----END CERTIFICATE----- LOCALCACERT end # This is the intermediate CA designated to issue Agent SSL certs. It is # signed by the Root CA. def agent_ca_pem @agent_ca_pem ||= <<-AGENT_CA # Agent CA #{mixed_utf8} -----BEGIN CERTIFICATE----- MIIClTCCAf6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBJMRAwDgYDVQQDDAdSb290 IENBMRowGAYDVQQLDBFTZXJ2ZXIgT3BlcmF0aW9uczEZMBcGA1UECgwQRXhhbXBs ZSBPcmcsIExMQzAeFw0xMzAzMzAwNTUwNDhaFw0zMzAzMjUwNTUwNDhaMH0xIzAh BgNVBAMTGkludGVybWVkaWF0ZSBDQSAoYWdlbnQtY2EpMR8wHQYJKoZIhvcNAQkB FhB0ZXN0QGV4YW1wbGUub3JnMRkwFwYDVQQKExBFeGFtcGxlIE9yZywgTExDMRow GAYDVQQLExFTZXJ2ZXIgT3BlcmF0aW9uczBcMA0GCSqGSIb3DQEBAQUAA0sAMEgC QQDkEj/Msmi4hJImxP5+ocixMTHuYC1M1E2p4QcuzOkZYrfHf+5hJMcahfYhLiXU jHBredOXhgSisHh6CLSb/rKzAgMBAAGjgZwwgZkweQYDVR0jBHIwcIAUME3Wao41 ziBStoTjNG3Dq54UMNqhTaRLMEkxEDAOBgNVBAMMB1Jvb3QgQ0ExGjAYBgNVBAsM EVNlcnZlciBPcGVyYXRpb25zMRkwFwYDVQQKDBBFeGFtcGxlIE9yZywgTExDggkA t/Y+TYe+0HMwDwYDVR0TAQH/BAUwAwEB/zALBgNVHQ8EBAMCAQYwDQYJKoZIhvcN AQEFBQADgYEAujSj9rxIxJHEuuYXb15L30yxs9Tdvy4OCLiKdjvs9Z7gG8Pbutls ooCwyYAkmzKVs/8cYjZJnvJrPEW1gFwqX7Xknp85Cfrl+/pQEPYq5sZVa5BIm9tI 0EvlDax/Hd28jI6Bgq5fsTECNl9GDGknCy7vwRZem0h+hI56lzR3pYE= -----END CERTIFICATE----- AGENT_CA end end puppet-5.5.10/spec/unit/ssl/host_spec.rb0000644005276200011600000010403213417161722020041 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/ssl/host' require 'matchers/json' def base_json_comparison(result, json_hash) expect(result["fingerprint"]).to eq(json_hash["fingerprint"]) expect(result["name"]).to eq(json_hash["name"]) expect(result["state"]).to eq(json_hash["desired_state"]) end describe Puppet::SSL::Host do include JSONMatchers include PuppetSpec::Files before do Puppet::SSL::Host.indirection.terminus_class = :file # Get a safe temporary file dir = tmpdir("ssl_host_testing") Puppet.settings[:confdir] = dir Puppet.settings[:vardir] = dir Puppet.settings.use :main, :ssl @host = Puppet::SSL::Host.new("myname") end after do # Cleaned out any cached localhost instance. Puppet::SSL::Host.reset Puppet::SSL::Host.ca_location = :none end it "should use any provided name as its name" do expect(@host.name).to eq("myname") end it "should retrieve its public key from its private key" do realkey = mock 'realkey' key = stub 'key', :content => realkey Puppet::SSL::Key.indirection.stubs(:find).returns(key) pubkey = mock 'public_key' realkey.expects(:public_key).returns pubkey expect(@host.public_key).to equal(pubkey) end it "should default to being a non-ca host" do expect(@host.ca?).to be_falsey end it "should be a ca host if its name matches the CA_NAME" do Puppet::SSL::Host.stubs(:ca_name).returns "yayca" expect(Puppet::SSL::Host.new("yayca")).to be_ca end it "should have a method for determining the CA location" do expect(Puppet::SSL::Host).to respond_to(:ca_location) end it "should have a method for specifying the CA location" do expect(Puppet::SSL::Host).to respond_to(:ca_location=) end it "should have a method for retrieving the default ssl host" do expect(Puppet::SSL::Host).to respond_to(:ca_location=) end it "should have a method for producing an instance to manage the local host's keys" do expect(Puppet::SSL::Host).to respond_to(:localhost) end it "should allow to reset localhost" do previous_host = Puppet::SSL::Host.localhost Puppet::SSL::Host.reset expect(Puppet::SSL::Host.localhost).not_to eq(previous_host) end it "should generate the certificate for the localhost instance if no certificate is available" do host = stub 'host', :key => nil Puppet::SSL::Host.expects(:new).returns host host.expects(:certificate).returns nil host.expects(:generate) expect(Puppet::SSL::Host.localhost).to equal(host) end it "should create a localhost cert if no cert is available and it is a CA with autosign and it is using DNS alt names", :unless => Puppet.features.microsoft_windows? do Puppet[:autosign] = true Puppet[:confdir] = tmpdir('conf') Puppet[:dns_alt_names] = "foo,bar,baz" ca = Puppet::SSL::CertificateAuthority.new Puppet::SSL::CertificateAuthority.stubs(:instance).returns ca localhost = Puppet::SSL::Host.localhost cert = localhost.certificate expect(cert).to be_a(Puppet::SSL::Certificate) expect(cert.subject_alt_names).to match_array(%W[DNS:#{Puppet[:certname]} DNS:foo DNS:bar DNS:baz]) end context "with dns_alt_names" do before :each do @key = stub('key content') key = stub('key', :generate => true, :content => @key) Puppet::SSL::Key.stubs(:new).returns key Puppet::SSL::Key.indirection.stubs(:save).with(key) @cr = stub('certificate request') Puppet::SSL::CertificateRequest.stubs(:new).returns @cr Puppet::SSL::CertificateRequest.indirection.stubs(:save).with(@cr) end describe "explicitly specified" do before :each do Puppet[:dns_alt_names] = 'one, two' end it "should not include subjectAltName if not the local node" do @cr.expects(:generate).with(@key, {}) Puppet::SSL::Host.new('not-the-' + Puppet[:certname]).generate end it "should include subjectAltName if I am a CA" do @cr.expects(:generate). with(@key, { :dns_alt_names => Puppet[:dns_alt_names] }) Puppet::SSL::Host.localhost end end describe "implicitly defaulted" do let(:ca) { stub('ca', :sign => nil) } before :each do Puppet[:dns_alt_names] = '' Puppet::SSL::CertificateAuthority.stubs(:instance).returns ca end it "should not include defaults if we're not the CA" do Puppet::SSL::CertificateAuthority.stubs(:ca?).returns false @cr.expects(:generate).with(@key, {}) Puppet::SSL::Host.localhost end it "should not include defaults if not the local node" do Puppet::SSL::CertificateAuthority.stubs(:ca?).returns true @cr.expects(:generate).with(@key, {}) Puppet::SSL::Host.new('not-the-' + Puppet[:certname]).generate end it "should not include defaults if we can't resolve our fqdn" do Puppet::SSL::CertificateAuthority.stubs(:ca?).returns true Facter.stubs(:value).with(:fqdn).returns nil @cr.expects(:generate).with(@key, {}) Puppet::SSL::Host.localhost end it "should provide defaults if we're bootstrapping the local master" do Puppet::SSL::CertificateAuthority.stubs(:ca?).returns true Facter.stubs(:value).with(:fqdn).returns 'web.foo.com' Facter.stubs(:value).with(:domain).returns 'foo.com' @cr.expects(:generate).with(@key, {:dns_alt_names => "puppet, web.foo.com, puppet.foo.com"}) Puppet::SSL::Host.localhost end end end it "should always read the key for the localhost instance in from disk" do host = stub 'host', :certificate => "eh" Puppet::SSL::Host.expects(:new).returns host host.expects(:key) Puppet::SSL::Host.localhost end it "should cache the localhost instance" do host = stub 'host', :certificate => "eh", :key => 'foo' Puppet::SSL::Host.expects(:new).once.returns host expect(Puppet::SSL::Host.localhost).to eq(Puppet::SSL::Host.localhost) end it "should be able to verify its certificate matches its key" do expect(Puppet::SSL::Host.new("foo")).to respond_to(:validate_certificate_with_key) end it "should consider the certificate invalid if it cannot find a key" do host = Puppet::SSL::Host.new("foo") certificate = mock('cert', :fingerprint => 'DEADBEEF') host.expects(:certificate).twice.returns certificate host.expects(:key).returns nil expect { host.validate_certificate_with_key }.to raise_error(Puppet::Error, "No private key with which to validate certificate with fingerprint: DEADBEEF") end it "should consider the certificate invalid if it cannot find a certificate" do host = Puppet::SSL::Host.new("foo") host.expects(:key).never host.expects(:certificate).returns nil expect { host.validate_certificate_with_key }.to raise_error(Puppet::Error, "No certificate to validate.") end it "should consider the certificate invalid if the SSL certificate's key verification fails" do host = Puppet::SSL::Host.new("foo") key = mock 'key', :content => "private_key" sslcert = mock 'sslcert' certificate = mock 'cert', {:content => sslcert, :fingerprint => 'DEADBEEF'} host.stubs(:key).returns key host.stubs(:certificate).returns certificate sslcert.expects(:check_private_key).with("private_key").returns false expect { host.validate_certificate_with_key }.to raise_error(Puppet::Error, /DEADBEEF/) end it "should consider the certificate valid if the SSL certificate's key verification succeeds" do host = Puppet::SSL::Host.new("foo") key = mock 'key', :content => "private_key" sslcert = mock 'sslcert' certificate = mock 'cert', :content => sslcert host.stubs(:key).returns key host.stubs(:certificate).returns certificate sslcert.expects(:check_private_key).with("private_key").returns true expect{ host.validate_certificate_with_key }.not_to raise_error end describe "when specifying the CA location" do it "should support the location ':local'" do expect { Puppet::SSL::Host.ca_location = :local }.not_to raise_error end it "should support the location ':remote'" do expect { Puppet::SSL::Host.ca_location = :remote }.not_to raise_error end it "should support the location ':none'" do expect { Puppet::SSL::Host.ca_location = :none }.not_to raise_error end it "should support the location ':only'" do expect { Puppet::SSL::Host.ca_location = :only }.not_to raise_error end it "should not support other modes" do expect { Puppet::SSL::Host.ca_location = :whatever }.to raise_error(ArgumentError) end describe "as 'local'" do before do Puppet::SSL::Host.ca_location = :local end it "should set the cache class for Certificate, CertificateRevocationList, and CertificateRequest as :file" do expect(Puppet::SSL::Certificate.indirection.cache_class).to eq(:file) expect(Puppet::SSL::CertificateRequest.indirection.cache_class).to eq(:file) expect(Puppet::SSL::CertificateRevocationList.indirection.cache_class).to eq(:file) end it "should set the terminus class for Key and Host as :file" do expect(Puppet::SSL::Key.indirection.terminus_class).to eq(:file) expect(Puppet::SSL::Host.indirection.terminus_class).to eq(:file) end it "should set the terminus class for Certificate, CertificateRevocationList, and CertificateRequest as :ca" do expect(Puppet::SSL::Certificate.indirection.terminus_class).to eq(:ca) expect(Puppet::SSL::CertificateRequest.indirection.terminus_class).to eq(:ca) expect(Puppet::SSL::CertificateRevocationList.indirection.terminus_class).to eq(:ca) end end describe "as 'remote'" do before do Puppet::SSL::Host.ca_location = :remote end it "should set the cache class for Certificate, CertificateRevocationList, and CertificateRequest as :file" do expect(Puppet::SSL::Certificate.indirection.cache_class).to eq(:file) expect(Puppet::SSL::CertificateRequest.indirection.cache_class).to eq(:file) expect(Puppet::SSL::CertificateRevocationList.indirection.cache_class).to eq(:file) end it "should set the terminus class for Key as :file" do expect(Puppet::SSL::Key.indirection.terminus_class).to eq(:file) end it "should set the terminus class for Host, Certificate, CertificateRevocationList, and CertificateRequest as :rest" do expect(Puppet::SSL::Host.indirection.terminus_class).to eq(:rest) expect(Puppet::SSL::Certificate.indirection.terminus_class).to eq(:rest) expect(Puppet::SSL::CertificateRequest.indirection.terminus_class).to eq(:rest) expect(Puppet::SSL::CertificateRevocationList.indirection.terminus_class).to eq(:rest) end end describe "as 'only'" do before do Puppet::SSL::Host.ca_location = :only end it "should set the terminus class for Key, Certificate, CertificateRevocationList, and CertificateRequest as :ca" do expect(Puppet::SSL::Key.indirection.terminus_class).to eq(:ca) expect(Puppet::SSL::Certificate.indirection.terminus_class).to eq(:ca) expect(Puppet::SSL::CertificateRequest.indirection.terminus_class).to eq(:ca) expect(Puppet::SSL::CertificateRevocationList.indirection.terminus_class).to eq(:ca) end it "should set the cache class for Certificate, CertificateRevocationList, and CertificateRequest to nil" do expect(Puppet::SSL::Certificate.indirection.cache_class).to be_nil expect(Puppet::SSL::CertificateRequest.indirection.cache_class).to be_nil expect(Puppet::SSL::CertificateRevocationList.indirection.cache_class).to be_nil end it "should set the terminus class for Host to :file" do expect(Puppet::SSL::Host.indirection.terminus_class).to eq(:file) end end describe "as 'none'" do before do Puppet::SSL::Host.ca_location = :none end it "should set the terminus class for Key, Certificate, CertificateRevocationList, and CertificateRequest as :file" do expect(Puppet::SSL::Key.indirection.terminus_class).to eq(:disabled_ca) expect(Puppet::SSL::Certificate.indirection.terminus_class).to eq(:disabled_ca) expect(Puppet::SSL::CertificateRequest.indirection.terminus_class).to eq(:disabled_ca) expect(Puppet::SSL::CertificateRevocationList.indirection.terminus_class).to eq(:disabled_ca) end it "should set the terminus class for Host to 'none'" do expect { Puppet::SSL::Host.indirection.terminus_class }.to raise_error(Puppet::DevError) end end end it "should have a class method for destroying all files related to a given host" do expect(Puppet::SSL::Host).to respond_to(:destroy) end describe "when destroying a host's SSL files" do before do Puppet::SSL::Key.indirection.stubs(:destroy).returns false Puppet::SSL::Certificate.indirection.stubs(:destroy).returns false Puppet::SSL::CertificateRequest.indirection.stubs(:destroy).returns false end it "should destroy its certificate, certificate request, and key" do Puppet::SSL::Key.indirection.expects(:destroy).with("myhost") Puppet::SSL::Certificate.indirection.expects(:destroy).with("myhost") Puppet::SSL::CertificateRequest.indirection.expects(:destroy).with("myhost") Puppet::SSL::Host.destroy("myhost") end it "should return true if any of the classes returned true" do Puppet::SSL::Certificate.indirection.expects(:destroy).with("myhost").returns true expect(Puppet::SSL::Host.destroy("myhost")).to be_truthy end it "should report that nothing was deleted if none of the classes returned true" do expect(Puppet::SSL::Host.destroy("myhost")).to eq("Nothing was deleted") end end describe "when initializing" do it "should default its name to the :certname setting" do Puppet[:certname] = "myname" expect(Puppet::SSL::Host.new.name).to eq("myname") end it "should downcase a passed in name" do expect(Puppet::SSL::Host.new("Host.Domain.Com").name).to eq("host.domain.com") end it "should indicate that it is a CA host if its name matches the ca_name constant" do Puppet::SSL::Host.stubs(:ca_name).returns "myca" expect(Puppet::SSL::Host.new("myca")).to be_ca end end describe "when managing its private key" do before do @realkey = "mykey" @key = Puppet::SSL::Key.new("mykey") @key.content = @realkey end it "should return nil if the key is not set and cannot be found" do Puppet::SSL::Key.indirection.expects(:find).with("myname").returns(nil) expect(@host.key).to be_nil end it "should find the key in the Key class and return the Puppet instance" do Puppet::SSL::Key.indirection.expects(:find).with("myname").returns(@key) expect(@host.key).to equal(@key) end it "should be able to generate and save a new key" do Puppet::SSL::Key.expects(:new).with("myname").returns(@key) @key.expects(:generate) Puppet::SSL::Key.indirection.expects(:save) expect(@host.generate_key).to be_truthy expect(@host.key).to equal(@key) end it "should not retain keys that could not be saved" do Puppet::SSL::Key.expects(:new).with("myname").returns(@key) @key.stubs(:generate) Puppet::SSL::Key.indirection.expects(:save).raises "eh" expect { @host.generate_key }.to raise_error(RuntimeError) expect(@host.key).to be_nil end it "should return any previously found key without requerying" do Puppet::SSL::Key.indirection.expects(:find).with("myname").returns(@key).once expect(@host.key).to equal(@key) expect(@host.key).to equal(@key) end end describe "when managing its certificate request" do before do @realrequest = "real request" @request = Puppet::SSL::CertificateRequest.new("myname") @request.content = @realrequest end it "should return nil if the key is not set and cannot be found" do Puppet::SSL::CertificateRequest.indirection.expects(:find).with("myname").returns(nil) expect(@host.certificate_request).to be_nil end it "should find the request in the Key class and return it and return the Puppet SSL request" do Puppet::SSL::CertificateRequest.indirection.expects(:find).with("myname").returns @request expect(@host.certificate_request).to equal(@request) end it "should generate a new key when generating the cert request if no key exists" do Puppet::SSL::CertificateRequest.expects(:new).with("myname").returns @request key = stub 'key', :public_key => mock("public_key"), :content => "mycontent" @host.expects(:key).times(2).returns(nil).then.returns(key) @host.expects(:generate_key).returns(key) @request.stubs(:generate) Puppet::SSL::CertificateRequest.indirection.stubs(:save) @host.generate_certificate_request end it "should be able to generate and save a new request using the private key" do Puppet::SSL::CertificateRequest.expects(:new).with("myname").returns @request key = stub 'key', :public_key => mock("public_key"), :content => "mycontent" @host.stubs(:key).returns(key) @request.expects(:generate).with("mycontent", {}) Puppet::SSL::CertificateRequest.indirection.expects(:save).with(@request) expect(@host.generate_certificate_request).to be_truthy expect(@host.certificate_request).to equal(@request) end it "should return any previously found request without requerying" do Puppet::SSL::CertificateRequest.indirection.expects(:find).with("myname").returns(@request).once expect(@host.certificate_request).to equal(@request) expect(@host.certificate_request).to equal(@request) end it "should not keep its certificate request in memory if the request cannot be saved" do Puppet::SSL::CertificateRequest.expects(:new).with("myname").returns @request key = stub 'key', :public_key => mock("public_key"), :content => "mycontent" @host.stubs(:key).returns(key) @request.stubs(:generate) @request.stubs(:name).returns("myname") terminus = stub 'terminus' terminus.stubs(:validate) Puppet::SSL::CertificateRequest.indirection.expects(:prepare).returns(terminus) terminus.expects(:save).with { |req| req.instance == @request && req.key == "myname" }.raises "eh" expect { @host.generate_certificate_request }.to raise_error(RuntimeError) expect(@host.instance_eval { @certificate_request }).to be_nil end end describe "when managing its certificate" do before do @realcert = mock 'certificate' @cert = stub 'cert', :content => @realcert @host.stubs(:key).returns mock("key") @host.stubs(:validate_certificate_with_key) end it "should find the CA certificate if it does not have a certificate" do Puppet::SSL::Certificate.indirection.expects(:find).with(Puppet::SSL::CA_NAME, :fail_on_404 => true).returns mock("cacert") Puppet::SSL::Certificate.indirection.stubs(:find).with("myname").returns @cert @host.certificate end it "should not find the CA certificate if it is the CA host" do @host.expects(:ca?).returns true Puppet::SSL::Certificate.indirection.stubs(:find) Puppet::SSL::Certificate.indirection.expects(:find).with(Puppet::SSL::CA_NAME, :fail_on_404 => true).never @host.certificate end it "should return nil if it cannot find a CA certificate" do Puppet::SSL::Certificate.indirection.expects(:find).with(Puppet::SSL::CA_NAME, :fail_on_404 => true).returns nil Puppet::SSL::Certificate.indirection.expects(:find).with("myname").never expect(@host.certificate).to be_nil end it "should find the key if it does not have one" do Puppet::SSL::Certificate.indirection.stubs(:find) @host.expects(:key).returns mock("key") @host.certificate end it "should generate the key if one cannot be found" do Puppet::SSL::Certificate.indirection.stubs(:find) @host.expects(:key).returns nil @host.expects(:generate_key) @host.certificate end it "should find the certificate in the Certificate class and return the Puppet certificate instance" do Puppet::SSL::Certificate.indirection.expects(:find).with(Puppet::SSL::CA_NAME, :fail_on_404 => true).returns mock("cacert") Puppet::SSL::Certificate.indirection.expects(:find).with("myname").returns @cert expect(@host.certificate).to equal(@cert) end it "should return any previously found certificate" do Puppet::SSL::Certificate.indirection.expects(:find).with(Puppet::SSL::CA_NAME, :fail_on_404 => true).returns mock("cacert") Puppet::SSL::Certificate.indirection.expects(:find).with("myname").returns(@cert).once expect(@host.certificate).to equal(@cert) expect(@host.certificate).to equal(@cert) end end it "should have a method for listing certificate hosts" do expect(Puppet::SSL::Host).to respond_to(:search) end describe "when listing certificate hosts" do it "should default to listing all clients with any file types" do Puppet::SSL::Key.indirection.expects(:search).returns [] Puppet::SSL::Certificate.indirection.expects(:search).returns [] Puppet::SSL::CertificateRequest.indirection.expects(:search).returns [] Puppet::SSL::Host.search end it "should be able to list only clients with a key" do Puppet::SSL::Key.indirection.expects(:search).returns [] Puppet::SSL::Certificate.indirection.expects(:search).never Puppet::SSL::CertificateRequest.indirection.expects(:search).never Puppet::SSL::Host.search :for => Puppet::SSL::Key end it "should be able to list only clients with a certificate" do Puppet::SSL::Key.indirection.expects(:search).never Puppet::SSL::Certificate.indirection.expects(:search).returns [] Puppet::SSL::CertificateRequest.indirection.expects(:search).never Puppet::SSL::Host.search :for => Puppet::SSL::Certificate end it "should be able to list only clients with a certificate request" do Puppet::SSL::Key.indirection.expects(:search).never Puppet::SSL::Certificate.indirection.expects(:search).never Puppet::SSL::CertificateRequest.indirection.expects(:search).returns [] Puppet::SSL::Host.search :for => Puppet::SSL::CertificateRequest end it "should return a Host instance created with the name of each found instance" do key = stub 'key', :name => "key", :to_ary => nil cert = stub 'cert', :name => "cert", :to_ary => nil csr = stub 'csr', :name => "csr", :to_ary => nil Puppet::SSL::Key.indirection.expects(:search).returns [key] Puppet::SSL::Certificate.indirection.expects(:search).returns [cert] Puppet::SSL::CertificateRequest.indirection.expects(:search).returns [csr] returned = [] %w{key cert csr}.each do |name| result = mock(name) returned << result Puppet::SSL::Host.expects(:new).with(name).returns result end result = Puppet::SSL::Host.search returned.each do |r| expect(result).to be_include(r) end end end it "should have a method for generating all necessary files" do expect(Puppet::SSL::Host.new("me")).to respond_to(:generate) end describe "when generating files" do before do @host = Puppet::SSL::Host.new("me") @host.stubs(:generate_key) @host.stubs(:generate_certificate_request) end it "should generate a key if one is not present" do @host.stubs(:key).returns nil @host.expects(:generate_key) @host.generate end it "should generate a certificate request if one is not present" do @host.expects(:certificate_request).returns nil @host.expects(:generate_certificate_request) @host.generate end describe "and it can create a certificate authority" do before do @ca = mock 'ca' Puppet::SSL::CertificateAuthority.stubs(:instance).returns @ca end it "should use the CA to sign its certificate request if it does not have a certificate" do @host.expects(:certificate).returns nil @ca.expects(:sign).with(@host.name, {allow_dns_alt_names: true}) @host.generate end end describe "and it cannot create a certificate authority" do before do Puppet::SSL::CertificateAuthority.stubs(:instance).returns nil end it "should seek its certificate" do @host.expects(:certificate) @host.generate end end end it "should have a method for creating an SSL store" do expect(Puppet::SSL::Host.new("me")).to respond_to(:ssl_store) end it "should always return the same store" do host = Puppet::SSL::Host.new("foo") store = mock 'store' store.stub_everything OpenSSL::X509::Store.expects(:new).returns store expect(host.ssl_store).to equal(host.ssl_store) end describe "when creating an SSL store" do before do @host = Puppet::SSL::Host.new("me") @store = mock 'store' @store.stub_everything OpenSSL::X509::Store.stubs(:new).returns @store Puppet[:localcacert] = "ssl_host_testing" Puppet::SSL::CertificateRevocationList.indirection.stubs(:find).returns(nil) end it "should accept a purpose" do @store.expects(:purpose=).with "my special purpose" @host.ssl_store("my special purpose") end it "should default to OpenSSL::X509::PURPOSE_ANY as the purpose" do @store.expects(:purpose=).with OpenSSL::X509::PURPOSE_ANY @host.ssl_store end it "should add the local CA cert file" do Puppet[:localcacert] = "/ca/cert/file" @store.expects(:add_file).with Puppet[:localcacert] @host.ssl_store end describe "and a CRL is available" do before do @crl = stub 'crl', :content => "real_crl" Puppet::SSL::CertificateRevocationList.indirection.stubs(:find).returns @crl end [true, 'chain'].each do |crl_setting| describe "and 'certificate_revocation' is #{crl_setting}" do before do Puppet[:certificate_revocation] = crl_setting end it "should add the CRL" do @store.expects(:add_crl).with "real_crl" @host.ssl_store end it "should set the flags to OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK" do @store.expects(:flags=).with(OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK) @host.ssl_store end end end describe "and 'certificate_revocation' is leaf" do before do Puppet[:certificate_revocation] = 'leaf' end it "should add the CRL" do @store.expects(:add_crl).with "real_crl" @host.ssl_store end it "should set the flags to OpenSSL::X509::V_FLAG_CRL_CHECK" do @store.expects(:flags=).with(OpenSSL::X509::V_FLAG_CRL_CHECK) @host.ssl_store end end describe "and 'certificate_revocation' is false" do before do Puppet[:certificate_revocation] = false end it "should not add the CRL" do @store.expects(:add_crl).never @host.ssl_store end it "should not set the flags" do @store.expects(:flags=).never @host.ssl_store end end end end describe "when waiting for a cert" do before do @host = Puppet::SSL::Host.new("me") end it "should generate its certificate request and attempt to read the certificate again if no certificate is found" do @host.expects(:certificate).times(2).returns(nil).then.returns "foo" @host.expects(:generate) @host.wait_for_cert(1) end it "should catch and log errors during CSR saving" do @host.expects(:certificate).times(2).returns(nil).then.returns "foo" @host.expects(:generate).raises(RuntimeError).then.returns nil @host.stubs(:sleep) @host.wait_for_cert(1) end it "should sleep and retry after failures saving the CSR if waitforcert is enabled" do @host.expects(:certificate).times(2).returns(nil).then.returns "foo" @host.expects(:generate).raises(RuntimeError).then.returns nil @host.expects(:sleep).with(1) @host.wait_for_cert(1) end it "should exit after failures saving the CSR of waitforcert is disabled" do @host.expects(:certificate).returns(nil) @host.expects(:generate).raises(RuntimeError) @host.expects(:puts) expect { @host.wait_for_cert(0) }.to exit_with 1 end it "should exit if the wait time is 0 and it can neither find nor retrieve a certificate" do @host.stubs(:certificate).returns nil @host.expects(:generate) @host.expects(:puts) expect { @host.wait_for_cert(0) }.to exit_with 1 end it "should sleep for the specified amount of time if no certificate is found after generating its certificate request" do @host.expects(:certificate).times(3).returns(nil).then.returns(nil).then.returns "foo" @host.expects(:generate) @host.expects(:sleep).with(1) @host.wait_for_cert(1) end it "should catch and log exceptions during certificate retrieval" do @host.expects(:certificate).times(3).returns(nil).then.raises(RuntimeError).then.returns("foo") @host.stubs(:generate) @host.stubs(:sleep) Puppet.expects(:err) @host.wait_for_cert(1) end end describe "when handling JSON", :unless => Puppet.features.microsoft_windows? do include PuppetSpec::Files before do Puppet[:vardir] = tmpdir("ssl_test_vardir") Puppet[:ssldir] = tmpdir("ssl_test_ssldir") # localcacert is where each client stores the CA certificate # cacert is where the master stores the CA certificate # Since we need to play the role of both for testing we need them to be the same and exist Puppet[:cacert] = Puppet[:localcacert] @ca=Puppet::SSL::CertificateAuthority.new end describe "when converting to JSON" do let(:host) do Puppet::SSL::Host.new("bazinga") end let(:json_hash) do { "fingerprint" => host.certificate_request.fingerprint, "desired_state" => 'requested', "name" => host.name } end it "should be able to identify a host with an unsigned certificate request" do host.generate_certificate_request result = JSON.parse(Puppet::SSL::Host.new(host.name).to_json) base_json_comparison result, json_hash end it "should validate against the schema" do host.generate_certificate_request expect(host.to_json).to validate_against('api/schemas/host.json') end describe "explicit fingerprints" do [:SHA1, :SHA256, :SHA512].each do |md| it "should include #{md}" do mds = md.to_s host.generate_certificate_request json_hash["fingerprints"] = {} json_hash["fingerprints"][mds] = host.certificate_request.fingerprint(md) result = JSON.parse(Puppet::SSL::Host.new(host.name).to_json) base_json_comparison result, json_hash expect(result["fingerprints"][mds]).to eq(json_hash["fingerprints"][mds]) end end end describe "dns_alt_names" do describe "when not specified" do it "should include the dns_alt_names associated with the certificate" do host.generate_certificate_request json_hash["desired_alt_names"] = host.certificate_request.subject_alt_names result = JSON.parse(Puppet::SSL::Host.new(host.name).to_json) base_json_comparison result, json_hash expect(result["dns_alt_names"]).to eq(json_hash["desired_alt_names"]) end end [ "", "test, alt, names" ].each do |alt_names| describe "when #{alt_names}" do before(:each) do host.generate_certificate_request :dns_alt_names => alt_names end it "should include the dns_alt_names associated with the certificate" do json_hash["desired_alt_names"] = host.certificate_request.subject_alt_names result = JSON.parse(Puppet::SSL::Host.new(host.name).to_json) base_json_comparison result, json_hash expect(result["dns_alt_names"]).to eq(json_hash["desired_alt_names"]) end it "should validate against the schema" do expect(host.to_json).to validate_against('api/schemas/host.json') end end end end it "should be able to identify a host with a signed certificate" do host.generate_certificate_request @ca.sign(host.name) json_hash = { "fingerprint" => Puppet::SSL::Certificate.indirection.find(host.name).fingerprint, "desired_state" => 'signed', "name" => host.name, } result = JSON.parse(Puppet::SSL::Host.new(host.name).to_json) base_json_comparison result, json_hash end it "should be able to identify a host with a revoked certificate" do host.generate_certificate_request @ca.sign(host.name) @ca.revoke(host.name) json_hash["fingerprint"] = Puppet::SSL::Certificate.indirection.find(host.name).fingerprint json_hash["desired_state"] = 'revoked' result = JSON.parse(Puppet::SSL::Host.new(host.name).to_json) base_json_comparison result, json_hash end end describe "when converting from JSON" do it "should return a Puppet::SSL::Host object with the specified desired state" do host = Puppet::SSL::Host.new("bazinga") host.desired_state="signed" json_hash = { "name" => host.name, "desired_state" => host.desired_state, } generated_host = Puppet::SSL::Host.from_data_hash(json_hash) expect(generated_host.desired_state).to eq(host.desired_state) expect(generated_host.name).to eq(host.name) end end end end puppet-5.5.10/spec/unit/ssl/inventory_spec.rb0000644005276200011600000001227713417161722021132 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/ssl/inventory' describe Puppet::SSL::Inventory, :unless => Puppet.features.microsoft_windows? do let(:cert_inventory) { File.expand_path("/inven/tory") } # different UTF-8 widths # 1-byte A # 2-byte Űż - http://www.fileformat.info/info/unicode/char/06ff/index.htm - 0xDB 0xBF / 219 191 # 3-byte áš  - http://www.fileformat.info/info/unicode/char/16A0/index.htm - 0xE1 0x9A 0xA0 / 225 154 160 # 4-byte ÜŽ - http://www.fileformat.info/info/unicode/char/2070E/index.htm - 0xF0 0xA0 0x9C 0x8E / 240 160 156 142 let (:mixed_utf8) { "A\u06FF\u16A0\u{2070E}" } # AŰżáš ÜŽ before do @class = Puppet::SSL::Inventory end describe "when initializing" do it "should set its path to the inventory file" do Puppet[:cert_inventory] = cert_inventory expect(@class.new.path).to eq(cert_inventory) end end describe "when managing an inventory" do before do Puppet[:cert_inventory] = cert_inventory Puppet::FileSystem.stubs(:exist?).with(cert_inventory).returns true @inventory = @class.new @cert = mock 'cert' end describe "and creating the inventory file" do it "re-adds all of the existing certificates" do inventory_file = StringIO.new Puppet.settings.setting(:cert_inventory).stubs(:open).yields(inventory_file) cert1 = Puppet::SSL::Certificate.new("cert1") cert1.content = stub 'cert1', :serial => 2, :not_before => Time.now, :not_after => Time.now, :subject => "/CN=smocking" cert2 = Puppet::SSL::Certificate.new("cert2") cert2.content = stub 'cert2', :serial => 3, :not_before => Time.now, :not_after => Time.now, :subject => "/CN=mocking bird" Puppet::SSL::Certificate.indirection.expects(:search).with("*").returns [cert1, cert2] @inventory.rebuild expect(inventory_file.string).to match(/\/CN=smocking/) expect(inventory_file.string).to match(/\/CN=mocking bird/) end end describe "and adding a certificate" do it "should use the Settings to write to the file" do Puppet.settings.setting(:cert_inventory).expects(:open).with('a:UTF-8') @inventory.add(@cert) end it "should add formatted certificate information to the end of the file" do cert = Puppet::SSL::Certificate.new("mycert") cert.content = @cert fh = StringIO.new Puppet.settings.setting(:cert_inventory).expects(:open).with('a:UTF-8').yields(fh) @inventory.expects(:format).with(@cert).returns "myformat" @inventory.add(@cert) expect(fh.string).to eq("myformat") end it "can use UTF-8 in the CN per RFC 5280" do cert = Puppet::SSL::Certificate.new("mycert") cert.content = stub 'cert', :serial => 1, :not_before => Time.now, :not_after => Time.now, :subject => "/CN=#{mixed_utf8}" fh = StringIO.new Puppet.settings.setting(:cert_inventory).expects(:open).with('a:UTF-8').yields(fh) @inventory.add(cert) expect(fh.string).to match(/\/CN=#{mixed_utf8}/) end end describe "and formatting a certificate" do before do @cert = stub 'cert', :not_before => Time.now, :not_after => Time.now, :subject => "mycert", :serial => 15 end it "should print the serial number as a 4 digit hex number in the first field" do expect(@inventory.format(@cert).split[0]).to eq("0x000f") # 15 in hex end it "should print the not_before date in '%Y-%m-%dT%H:%M:%S%Z' format in the second field" do @cert.not_before.expects(:strftime).with('%Y-%m-%dT%H:%M:%S%Z').returns "before_time" expect(@inventory.format(@cert).split[1]).to eq("before_time") end it "should print the not_after date in '%Y-%m-%dT%H:%M:%S%Z' format in the third field" do @cert.not_after.expects(:strftime).with('%Y-%m-%dT%H:%M:%S%Z').returns "after_time" expect(@inventory.format(@cert).split[2]).to eq("after_time") end it "should print the subject in the fourth field" do expect(@inventory.format(@cert).split[3]).to eq("mycert") end it "should add a carriage return" do expect(@inventory.format(@cert)).to match(/\n$/) end it "should produce a line consisting of the serial number, start date, expiration date, and subject" do # Just make sure our serial and subject bracket the lines. expect(@inventory.format(@cert)).to match(/^0x.+mycert$/) end end describe "and finding serial numbers" do it "should return an empty array if the inventory file is missing" do Puppet::FileSystem.expects(:exist?).with(cert_inventory).returns false expect(@inventory.serials(:whatever)).to be_empty end it "should return the all the serial numbers from the lines matching the provided name" do File.expects(:readlines).with(cert_inventory, :encoding => Encoding::UTF_8).returns ["0x00f blah blah /CN=me\n", "0x001 blah blah /CN=you\n", "0x002 blah blah /CN=me\n"] expect(@inventory.serials("me")).to eq([15, 2]) end end end end puppet-5.5.10/spec/unit/ssl/key_spec.rb0000644005276200011600000001375513417161722017667 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/ssl/key' describe Puppet::SSL::Key do before do @class = Puppet::SSL::Key end it "should be extended with the Indirector module" do expect(@class.singleton_class).to be_include(Puppet::Indirector) end it "should indirect key" do expect(@class.indirection.name).to eq(:key) end it "should only support the text format" do expect(@class.supported_formats).to eq([:s]) end it "should have a method for determining whether it's a CA key" do expect(@class.new("test")).to respond_to(:ca?) end it "should consider itself a ca key if its name matches the CA_NAME" do expect(@class.new(Puppet::SSL::Host.ca_name)).to be_ca end describe "when initializing" do it "should set its password file to the :capass if it's a CA key" do Puppet[:capass] = File.expand_path("/ca/pass") key = Puppet::SSL::Key.new(Puppet::SSL::Host.ca_name) expect(key.password_file).to eq(Puppet[:capass]) end it "should downcase its name" do expect(@class.new("MyName").name).to eq("myname") end it "should set its password file to the default password file if it is not the CA key" do Puppet[:passfile] = File.expand_path("/normal/pass") key = Puppet::SSL::Key.new("notca") expect(key.password_file).to eq(Puppet[:passfile]) end end describe "when managing instances" do before do @key = @class.new("myname") end it "should have a name attribute" do expect(@key.name).to eq("myname") end it "should have a content attribute" do expect(@key).to respond_to(:content) end it "should be able to read keys from disk" do path = "/my/path" Puppet::FileSystem.expects(:read).with(path, :encoding => Encoding::ASCII).returns("my key") key = mock 'key' OpenSSL::PKey::RSA.expects(:new).returns(key) expect(@key.read(path)).to equal(key) expect(@key.content).to equal(key) end it "should not try to use the provided password file if the file does not exist" do Puppet::FileSystem.stubs(:exist?).returns false @key.password_file = "/path/to/password" path = "/my/path" Puppet::FileSystem.stubs(:read).with(path, :encoding => Encoding::ASCII).returns("my key") OpenSSL::PKey::RSA.expects(:new).with("my key", nil).returns(mock('key')) Puppet::FileSystem.expects(:read).with("/path/to/password", :encoding => Encoding::BINARY).never @key.read(path) end it "should read the key with the password retrieved from the password file if one is provided" do Puppet::FileSystem.stubs(:exist?).returns true @key.password_file = "/path/to/password" path = "/my/path" Puppet::FileSystem.expects(:read).with(path, :encoding => Encoding::ASCII).returns("my key") Puppet::FileSystem.expects(:read).with("/path/to/password", :encoding => Encoding::BINARY).returns("my password") key = mock 'key' OpenSSL::PKey::RSA.expects(:new).with("my key", "my password").returns(key) expect(@key.read(path)).to equal(key) expect(@key.content).to equal(key) end it "should return an empty string when converted to a string with no key" do expect(@key.to_s).to eq("") end it "should convert the key to pem format when converted to a string" do key = mock 'key', :to_pem => "pem" @key.content = key expect(@key.to_s).to eq("pem") end it "should have a :to_text method that it delegates to the actual key" do real_key = mock 'key' real_key.expects(:to_text).returns "keytext" @key.content = real_key expect(@key.to_text).to eq("keytext") end end describe "when generating the private key" do before do @instance = @class.new("test") @key = mock 'key' end it "should create an instance of OpenSSL::PKey::RSA" do OpenSSL::PKey::RSA.expects(:new).returns(@key) @instance.generate end it "should create the private key with the keylength specified in the settings" do Puppet[:keylength] = 513 OpenSSL::PKey::RSA.expects(:new).with(513).returns(@key) @instance.generate end it "should set the content to the generated key" do OpenSSL::PKey::RSA.stubs(:new).returns(@key) @instance.generate expect(@instance.content).to equal(@key) end it "should return the generated key" do OpenSSL::PKey::RSA.stubs(:new).returns(@key) expect(@instance.generate).to equal(@key) end it "should return the key in pem format" do @instance.generate @instance.content.expects(:to_pem).returns "my normal key" expect(@instance.to_s).to eq("my normal key") end describe "with a password file set" do it "should return a nil password if the password file does not exist" do Puppet::FileSystem.expects(:exist?).with("/path/to/pass").returns false Puppet::FileSystem.expects(:read).with("/path/to/pass", :encoding => Encoding::BINARY).never @instance.password_file = "/path/to/pass" expect(@instance.password).to be_nil end it "should return the contents of the password file as its password" do Puppet::FileSystem.expects(:exist?).with("/path/to/pass").returns true Puppet::FileSystem.expects(:read).with("/path/to/pass", :encoding => Encoding::BINARY).returns "my password" @instance.password_file = "/path/to/pass" expect(@instance.password).to eq("my password") end it "should export the private key to text using the password" do @instance.password_file = "/path/to/pass" @instance.stubs(:password).returns "my password" OpenSSL::PKey::RSA.expects(:new).returns(@key) @instance.generate cipher = mock 'cipher' OpenSSL::Cipher::DES.expects(:new).with(:EDE3, :CBC).returns cipher @key.expects(:export).with(cipher, "my password").returns "my encrypted key" expect(@instance.to_s).to eq("my encrypted key") end end end end puppet-5.5.10/spec/unit/ssl/validator_spec.rb0000644005276200011600000003600013417161722021050 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/ssl' describe Puppet::SSL::Validator::DefaultValidator do let(:ssl_context) do mock('OpenSSL::X509::StoreContext') end let(:ssl_configuration) do Puppet::SSL::Configuration.new( Puppet[:localcacert], :ca_auth_file => Puppet[:ssl_client_ca_auth]) end let(:ssl_host) do stub('ssl_host', :ssl_store => nil, :certificate => stub('cert', :content => nil), :key => stub('key', :content => nil)) end subject do described_class.new(ssl_configuration, ssl_host) end before :each do ssl_configuration.stubs(:read_file). with(Puppet[:localcacert]). returns(root_ca) end describe '#call' do before :each do ssl_context.stubs(:current_cert).returns(*cert_chain_in_callback_order) ssl_context.stubs(:chain).returns(cert_chain) end context 'When pre-verification is not OK' do context 'and the ssl_context is in an error state' do let(:root_subject) { OpenSSL::X509::Certificate.new(root_ca).subject.to_s } let(:code) { OpenSSL::X509::V_ERR_INVALID_CA } it 'rejects the connection' do ssl_context.stubs(:error_string).returns("Something went wrong") ssl_context.stubs(:error).returns(code) expect(subject.call(false, ssl_context)).to eq(false) end it 'makes the error available via #verify_errors' do ssl_context.stubs(:error_string).returns("Something went wrong") ssl_context.stubs(:error).returns(code) subject.call(false, ssl_context) expect(subject.verify_errors).to eq(["Something went wrong for #{root_subject}"]) end it 'uses a generic message if error_string is nil' do ssl_context.stubs(:error_string).returns(nil) ssl_context.stubs(:error).returns(code) subject.call(false, ssl_context) expect(subject.verify_errors).to eq(["OpenSSL error #{code} for #{root_subject}"]) end it 'uses 0 for nil error codes' do ssl_context.stubs(:error_string).returns("Something went wrong") ssl_context.stubs(:error).returns(nil) subject.call(false, ssl_context) expect(subject.verify_errors).to eq(["Something went wrong for #{root_subject}"]) end context "when CRL is not yet valid" do before :each do ssl_context.stubs(:error_string).returns("CRL is not yet valid") ssl_context.stubs(:error).returns(OpenSSL::X509::V_ERR_CRL_NOT_YET_VALID) end it 'rejects nil CRL' do ssl_context.stubs(:current_crl).returns(nil) expect(subject.call(false, ssl_context)).to eq(false) expect(subject.verify_errors).to eq(["CRL is not yet valid"]) end it 'includes the CRL issuer in the verify error message' do crl = OpenSSL::X509::CRL.new crl.issuer = OpenSSL::X509::Name.new([['CN','Puppet CA: puppetmaster.example.com']]) crl.last_update = Time.now + 24 * 60 * 60 ssl_context.stubs(:current_crl).returns(crl) subject.call(false, ssl_context) expect(subject.verify_errors).to eq(["CRL is not yet valid for /CN=Puppet CA: puppetmaster.example.com"]) end it 'rejects CRLs whose last_update time is more than 5 minutes in the future' do crl = OpenSSL::X509::CRL.new crl.issuer = OpenSSL::X509::Name.new([['CN','Puppet CA: puppetmaster.example.com']]) crl.last_update = Time.now + 24 * 60 * 60 ssl_context.stubs(:current_crl).returns(crl) expect(subject.call(false, ssl_context)).to eq(false) end it 'accepts CRLs whose last_update time is 10 seconds in the future' do crl = OpenSSL::X509::CRL.new crl.issuer = OpenSSL::X509::Name.new([['CN','Puppet CA: puppetmaster.example.com']]) crl.last_update = Time.now + 10 ssl_context.stubs(:current_crl).returns(crl) expect(subject.call(false, ssl_context)).to eq(true) end end end end context 'When pre-verification is OK' do context 'and the ssl_context is in an error state' do before :each do ssl_context.stubs(:error_string).returns("Something went wrong") end it 'does not make the error available via #verify_errors' do subject.call(true, ssl_context) expect(subject.verify_errors).to eq([]) end end context 'and the chain is valid' do it 'is true for each CA certificate in the chain' do (cert_chain.length - 1).times do expect(subject.call(true, ssl_context)).to be_truthy end end it 'is true for the SSL certificate ending the chain' do (cert_chain.length - 1).times do subject.call(true, ssl_context) end expect(subject.call(true, ssl_context)).to be_truthy end end context 'and the chain is invalid' do before :each do ssl_configuration.stubs(:read_file). with(Puppet[:localcacert]). returns(agent_ca) end it 'is true for each CA certificate in the chain' do (cert_chain.length - 1).times do expect(subject.call(true, ssl_context)).to be_truthy end end it 'is false for the SSL certificate ending the chain' do (cert_chain.length - 1).times do subject.call(true, ssl_context) end expect(subject.call(true, ssl_context)).to be_falsey end end context 'an error is raised inside of #call' do before :each do ssl_context.expects(:current_cert).raises(StandardError, "BOOM!") end it 'is false' do expect(subject.call(true, ssl_context)).to be_falsey end it 'makes the error available through #verify_errors' do subject.call(true, ssl_context) expect(subject.verify_errors).to eq(["BOOM!"]) end end end end describe '#setup_connection' do it 'updates the connection for verification' do subject.stubs(:ssl_certificates_are_present?).returns(true) connection = mock('Net::HTTP') connection.expects(:cert_store=).with(ssl_host.ssl_store) connection.expects(:ca_file=).with(ssl_configuration.ca_auth_file) connection.expects(:cert=).with(ssl_host.certificate.content) connection.expects(:key=).with(ssl_host.key.content) connection.expects(:verify_callback=).with(subject) connection.expects(:verify_mode=).with(OpenSSL::SSL::VERIFY_PEER) subject.setup_connection(connection) end it 'does not perform verification if certificate files are missing' do subject.stubs(:ssl_certificates_are_present?).returns(false) connection = mock('Net::HTTP') connection.expects(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE) subject.setup_connection(connection) end end describe '#valid_peer?' do before :each do peer_certs = cert_chain_in_callback_order.map do |c| Puppet::SSL::Certificate.from_instance(c) end subject.instance_variable_set(:@peer_certs, peer_certs) end context 'when the peer presents a valid chain' do before :each do subject.stubs(:has_authz_peer_cert).returns(true) end it 'is true' do expect(subject.valid_peer?).to be_truthy end end context 'when the peer presents an invalid chain' do before :each do subject.stubs(:has_authz_peer_cert).returns(false) end it 'is false' do expect(subject.valid_peer?).to be_falsey end it 'makes a helpful error message available via #verify_errors' do subject.valid_peer? expect(subject.verify_errors).to eq([expected_authz_error_msg]) end end end describe '#has_authz_peer_cert' do context 'when the Root CA is listed as authorized' do it 'returns true when the SSL cert is issued by the Master CA' do expect(subject.has_authz_peer_cert(cert_chain, [root_ca_cert])).to be_truthy end it 'returns true when the SSL cert is issued by the Agent CA' do expect(subject.has_authz_peer_cert(cert_chain_agent_ca, [root_ca_cert])).to be_truthy end end context 'when the Master CA is listed as authorized' do it 'returns false when the SSL cert is issued by the Master CA' do expect(subject.has_authz_peer_cert(cert_chain, [master_ca_cert])).to be_truthy end it 'returns true when the SSL cert is issued by the Agent CA' do expect(subject.has_authz_peer_cert(cert_chain_agent_ca, [master_ca_cert])).to be_falsey end end context 'when the Agent CA is listed as authorized' do it 'returns true when the SSL cert is issued by the Master CA' do expect(subject.has_authz_peer_cert(cert_chain, [agent_ca_cert])).to be_falsey end it 'returns true when the SSL cert is issued by the Agent CA' do expect(subject.has_authz_peer_cert(cert_chain_agent_ca, [agent_ca_cert])).to be_truthy end end end def root_ca <<-ROOT_CA -----BEGIN CERTIFICATE----- MIICYDCCAcmgAwIBAgIJALf2Pk2HvtBzMA0GCSqGSIb3DQEBBQUAMEkxEDAOBgNV BAMMB1Jvb3QgQ0ExGjAYBgNVBAsMEVNlcnZlciBPcGVyYXRpb25zMRkwFwYDVQQK DBBFeGFtcGxlIE9yZywgTExDMB4XDTEzMDMzMDA1NTA0OFoXDTMzMDMyNTA1NTA0 OFowSTEQMA4GA1UEAwwHUm9vdCBDQTEaMBgGA1UECwwRU2VydmVyIE9wZXJhdGlv bnMxGTAXBgNVBAoMEEV4YW1wbGUgT3JnLCBMTEMwgZ8wDQYJKoZIhvcNAQEBBQAD gY0AMIGJAoGBAMGSpafR4lboYOPfPJC1wVHHl0gD49ZVRjOlJ9jidEUjBdFXK6SA S1tecDv2G4tM1ANmfMKjZl0m+KaZ8O2oq0g6kxkq1Mg0eSNvlnEyehjmTLRzHC2i a0biH2wMtCLzfAoXDKy4GPlciBPE9mup5I8Kien5s91t92tc7K8AJ8oBAgMBAAGj UDBOMB0GA1UdDgQWBBQwTdZqjjXOIFK2hOM0bcOrnhQw2jAfBgNVHSMEGDAWgBQw TdZqjjXOIFK2hOM0bcOrnhQw2jAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUA A4GBACs8EZRrzgzAlcKC1Tz8GYlNHQg0XhpbEDm+p2mOV//PuDD190O+UBpWxo9Q rrkkx8En0wXQZJf6iH3hwewwHLOq5yXZKbJN+SmvJvRNL95Yhyy08Y9N65tJveE7 rPsNU/Tx19jHC87oXlmAePLI4IaUHXrWb7CRbY9TEcPdmj1R -----END CERTIFICATE----- ROOT_CA end def master_ca <<-MASTER_CA -----BEGIN CERTIFICATE----- MIICljCCAf+gAwIBAgIBAjANBgkqhkiG9w0BAQUFADBJMRAwDgYDVQQDDAdSb290 IENBMRowGAYDVQQLDBFTZXJ2ZXIgT3BlcmF0aW9uczEZMBcGA1UECgwQRXhhbXBs ZSBPcmcsIExMQzAeFw0xMzAzMzAwNTUwNDhaFw0zMzAzMjUwNTUwNDhaMH4xJDAi BgNVBAMTG0ludGVybWVkaWF0ZSBDQSAobWFzdGVyLWNhKTEfMB0GCSqGSIb3DQEJ ARYQdGVzdEBleGFtcGxlLm9yZzEZMBcGA1UEChMQRXhhbXBsZSBPcmcsIExMQzEa MBgGA1UECxMRU2VydmVyIE9wZXJhdGlvbnMwXDANBgkqhkiG9w0BAQEFAANLADBI AkEAvo/az3oR69SP92jGnUHMJLEyyD1Ui1BZ/rUABJcQTRQqn3RqtlfYePWZnUaZ srKbXRS4q0w5Vqf1kx5w3q5tIwIDAQABo4GcMIGZMHkGA1UdIwRyMHCAFDBN1mqO Nc4gUraE4zRtw6ueFDDaoU2kSzBJMRAwDgYDVQQDDAdSb290IENBMRowGAYDVQQL DBFTZXJ2ZXIgT3BlcmF0aW9uczEZMBcGA1UECgwQRXhhbXBsZSBPcmcsIExMQ4IJ ALf2Pk2HvtBzMA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMA0GCSqGSIb3 DQEBBQUAA4GBACRfa1YPS7RQUuhYovGgV0VYqxuATC7WwdIRihVh5FceSXKgSIbz BKmOBAy/KixEhpnHTbkpaJ0d9ITkvjMTmj3M5YMahKaQA5niVPckQPecMMd6jg9U l1k75xLLIcrlsDYo3999KOSSchH2K7bLT7TuQ2okdP6FHWmeWmudewlu -----END CERTIFICATE----- MASTER_CA end def agent_ca <<-AGENT_CA -----BEGIN CERTIFICATE----- MIIClTCCAf6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBJMRAwDgYDVQQDDAdSb290 IENBMRowGAYDVQQLDBFTZXJ2ZXIgT3BlcmF0aW9uczEZMBcGA1UECgwQRXhhbXBs ZSBPcmcsIExMQzAeFw0xMzAzMzAwNTUwNDhaFw0zMzAzMjUwNTUwNDhaMH0xIzAh BgNVBAMTGkludGVybWVkaWF0ZSBDQSAoYWdlbnQtY2EpMR8wHQYJKoZIhvcNAQkB FhB0ZXN0QGV4YW1wbGUub3JnMRkwFwYDVQQKExBFeGFtcGxlIE9yZywgTExDMRow GAYDVQQLExFTZXJ2ZXIgT3BlcmF0aW9uczBcMA0GCSqGSIb3DQEBAQUAA0sAMEgC QQDkEj/Msmi4hJImxP5+ocixMTHuYC1M1E2p4QcuzOkZYrfHf+5hJMcahfYhLiXU jHBredOXhgSisHh6CLSb/rKzAgMBAAGjgZwwgZkweQYDVR0jBHIwcIAUME3Wao41 ziBStoTjNG3Dq54UMNqhTaRLMEkxEDAOBgNVBAMMB1Jvb3QgQ0ExGjAYBgNVBAsM EVNlcnZlciBPcGVyYXRpb25zMRkwFwYDVQQKDBBFeGFtcGxlIE9yZywgTExDggkA t/Y+TYe+0HMwDwYDVR0TAQH/BAUwAwEB/zALBgNVHQ8EBAMCAQYwDQYJKoZIhvcN AQEFBQADgYEAujSj9rxIxJHEuuYXb15L30yxs9Tdvy4OCLiKdjvs9Z7gG8Pbutls ooCwyYAkmzKVs/8cYjZJnvJrPEW1gFwqX7Xknp85Cfrl+/pQEPYq5sZVa5BIm9tI 0EvlDax/Hd28jI6Bgq5fsTECNl9GDGknCy7vwRZem0h+hI56lzR3pYE= -----END CERTIFICATE----- AGENT_CA end # Signed by the master CA (Good) def master_issued_by_master_ca <<-GOOD_SSL_CERT -----BEGIN CERTIFICATE----- MIICZzCCAhGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MSQwIgYDVQQDExtJbnRl cm1lZGlhdGUgQ0EgKG1hc3Rlci1jYSkxHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhh bXBsZS5vcmcxGTAXBgNVBAoTEEV4YW1wbGUgT3JnLCBMTEMxGjAYBgNVBAsTEVNl cnZlciBPcGVyYXRpb25zMB4XDTEzMDMzMDA1NTA0OFoXDTMzMDMyNTA1NTA0OFow HjEcMBoGA1UEAwwTbWFzdGVyMS5leGFtcGxlLm9yZzBcMA0GCSqGSIb3DQEBAQUA A0sAMEgCQQDACW8fryVZH0dC7vYUASonVBKYcILnKN2O9QX7RenZGN1TWek9LQxr yQFDyp7WJ8jUw6nENGniLU8J+QSSxryjAgMBAAGjgdkwgdYwWwYDVR0jBFQwUqFN pEswSTEQMA4GA1UEAwwHUm9vdCBDQTEaMBgGA1UECwwRU2VydmVyIE9wZXJhdGlv bnMxGTAXBgNVBAoMEEV4YW1wbGUgT3JnLCBMTEOCAQIwDAYDVR0TAQH/BAIwADAL BgNVHQ8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMD0GA1Ud EQQ2MDSCE21hc3RlcjEuZXhhbXBsZS5vcmeCB21hc3RlcjGCBnB1cHBldIIMcHVw cGV0bWFzdGVyMA0GCSqGSIb3DQEBBQUAA0EAo8PvgLrah6jQVs6YCBxOTn13PDip fVbcRsFd0dtIr00N61bCqr6Fa0aRwy424gh6bVJTNmk2zoaH7r025dZRhw== -----END CERTIFICATE----- GOOD_SSL_CERT end # Signed by the agent CA, not the master CA (Rogue) def master_issued_by_agent_ca <<-BAD_SSL_CERT -----BEGIN CERTIFICATE----- MIICZjCCAhCgAwIBAgIBBDANBgkqhkiG9w0BAQUFADB9MSMwIQYDVQQDExpJbnRl cm1lZGlhdGUgQ0EgKGFnZW50LWNhKTEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFt cGxlLm9yZzEZMBcGA1UEChMQRXhhbXBsZSBPcmcsIExMQzEaMBgGA1UECxMRU2Vy dmVyIE9wZXJhdGlvbnMwHhcNMTMwMzMwMDU1MDQ4WhcNMzMwMzI1MDU1MDQ4WjAe MRwwGgYDVQQDDBNtYXN0ZXIxLmV4YW1wbGUub3JnMFwwDQYJKoZIhvcNAQEBBQAD SwAwSAJBAPnCDnryLLXWepGLqsdBWlytfeakE/yijM8GlE/yT0SbpJInIhJR1N1A 0RskriHrxTU5qQEhd0RIja7K5o4NYksCAwEAAaOB2TCB1jBbBgNVHSMEVDBSoU2k SzBJMRAwDgYDVQQDDAdSb290IENBMRowGAYDVQQLDBFTZXJ2ZXIgT3BlcmF0aW9u czEZMBcGA1UECgwQRXhhbXBsZSBPcmcsIExMQ4IBATAMBgNVHRMBAf8EAjAAMAsG A1UdDwQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwPQYDVR0R BDYwNIITbWFzdGVyMS5leGFtcGxlLm9yZ4IHbWFzdGVyMYIGcHVwcGV0ggxwdXBw ZXRtYXN0ZXIwDQYJKoZIhvcNAQEFBQADQQA841IzHLlnn4RIJ0/BOZ/16iWC1dNr jV9bELC5OxeMNSsVXbFNeTHwbHEYjDg5dQ6eUkxPdBSMWBeQwe2Mw+xG -----END CERTIFICATE----- BAD_SSL_CERT end def cert_chain [ master_issued_by_master_ca, master_ca, root_ca ].map do |pem| OpenSSL::X509::Certificate.new(pem) end end def cert_chain_agent_ca [ master_issued_by_agent_ca, agent_ca, root_ca ].map do |pem| OpenSSL::X509::Certificate.new(pem) end end def cert_chain_in_callback_order cert_chain.reverse end let :authz_error_prefix do "The server presented a SSL certificate chain which does not include a CA listed in the ssl_client_ca_auth file. " end let :expected_authz_error_msg do authz_ca_certs = ssl_configuration.ca_auth_certificates msg = authz_error_prefix msg << "Authorized Issuers: #{authz_ca_certs.collect {|c| c.subject}.join(', ')} " msg << "Peer Chain: #{cert_chain.collect {|c| c.subject}.join(' => ')}" msg end let :root_ca_cert do OpenSSL::X509::Certificate.new(root_ca) end let :master_ca_cert do OpenSSL::X509::Certificate.new(master_ca) end let :agent_ca_cert do OpenSSL::X509::Certificate.new(agent_ca) end end puppet-5.5.10/spec/unit/status_spec.rb0000644005276200011600000000231413417161721017605 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'matchers/json' describe Puppet::Status do include JSONMatchers it "should implement find" do expect(Puppet::Status.indirection.find( :default )).to be_is_a(Puppet::Status) expect(Puppet::Status.indirection.find( :default ).status["is_alive"]).to eq(true) end it "should default to is_alive is true" do expect(Puppet::Status.new.status["is_alive"]).to eq(true) end it "should return a json hash" do expect(Puppet::Status.new.status.to_json).to eq('{"is_alive":true}') end it "should render to a json hash" do expect(JSON::pretty_generate(Puppet::Status.new)).to match(/"is_alive":\s*true/) end it "should accept a hash from json" do status = Puppet::Status.new( { "is_alive" => false } ) expect(status.status).to eq({ "is_alive" => false }) end it "should have a name" do Puppet::Status.new.name end it "should allow a name to be set" do Puppet::Status.new.name = "status" end it "serializes to JSON that conforms to the status schema" do status = Puppet::Status.new status.version = Puppet.version expect(status.render('json')).to validate_against('api/schemas/status.json') end end puppet-5.5.10/spec/unit/transaction/0000755005276200011600000000000013417162177017256 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/transaction/event_manager_spec.rb0000644005276200011600000003141113417161721023422 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/transaction/event_manager' describe Puppet::Transaction::EventManager do include PuppetSpec::Files describe "at initialization" do it "should require a transaction" do expect(Puppet::Transaction::EventManager.new("trans").transaction).to eq("trans") end end it "should delegate its relationship graph to the transaction" do transaction = stub 'transaction' manager = Puppet::Transaction::EventManager.new(transaction) transaction.expects(:relationship_graph).returns "mygraph" expect(manager.relationship_graph).to eq("mygraph") end describe "when queueing events" do before do @manager = Puppet::Transaction::EventManager.new(@transaction) @resource = Puppet::Type.type(:file).new :path => make_absolute("/my/file") @graph = stub 'graph', :matching_edges => [], :resource => @resource @manager.stubs(:relationship_graph).returns @graph @event = Puppet::Transaction::Event.new(:name => :foo, :resource => @resource) end it "should store all of the events in its event list" do @event2 = Puppet::Transaction::Event.new(:name => :bar, :resource => @resource) @manager.queue_events(@resource, [@event, @event2]) expect(@manager.events).to include(@event) expect(@manager.events).to include(@event2) end it "should queue events for the target and callback of any matching edges" do edge1 = stub("edge1", :callback => :c1, :source => stub("s1"), :target => stub("t1", :c1 => nil)) edge2 = stub("edge2", :callback => :c2, :source => stub("s2"), :target => stub("t2", :c2 => nil)) @graph.expects(:matching_edges).with { |event, resource| event == @event }.returns [edge1, edge2] @manager.expects(:queue_events_for_resource).with(@resource, edge1.target, edge1.callback, [@event]) @manager.expects(:queue_events_for_resource).with(@resource, edge2.target, edge2.callback, [@event]) @manager.queue_events(@resource, [@event]) end it "should queue events for the changed resource if the resource is self-refreshing and not being deleted" do @graph.stubs(:matching_edges).returns [] @resource.expects(:self_refresh?).returns true @resource.expects(:deleting?).returns false @manager.expects(:queue_events_for_resource).with(@resource, @resource, :refresh, [@event]) @manager.queue_events(@resource, [@event]) end it "should not queue events for the changed resource if the resource is not self-refreshing" do @graph.stubs(:matching_edges).returns [] @resource.expects(:self_refresh?).returns false @resource.stubs(:deleting?).returns false @manager.expects(:queue_events_for_resource).never @manager.queue_events(@resource, [@event]) end it "should not queue events for the changed resource if the resource is being deleted" do @graph.stubs(:matching_edges).returns [] @resource.expects(:self_refresh?).returns true @resource.expects(:deleting?).returns true @manager.expects(:queue_events_for_resource).never @manager.queue_events(@resource, [@event]) end it "should ignore edges that don't have a callback" do edge1 = stub("edge1", :callback => :nil, :source => stub("s1"), :target => stub("t1", :c1 => nil)) @graph.expects(:matching_edges).returns [edge1] @manager.expects(:queue_events_for_resource).never @manager.queue_events(@resource, [@event]) end it "should ignore targets that don't respond to the callback" do edge1 = stub("edge1", :callback => :c1, :source => stub("s1"), :target => stub("t1")) @graph.expects(:matching_edges).returns [edge1] @manager.expects(:queue_events_for_resource).never @manager.queue_events(@resource, [@event]) end it "should dequeue events for the changed resource if an event with invalidate_refreshes is processed" do @event2 = Puppet::Transaction::Event.new(:name => :foo, :resource => @resource, :invalidate_refreshes => true) @graph.stubs(:matching_edges).returns [] @manager.expects(:dequeue_events_for_resource).with(@resource, :refresh) @manager.queue_events(@resource, [@event, @event2]) end end describe "when queueing events for a resource" do before do @transaction = stub 'transaction' @manager = Puppet::Transaction::EventManager.new(@transaction) end it "should do nothing if no events are queued" do @manager.queued_events(stub("target")) { |callback, events| raise "should never reach this" } end it "should yield the callback and events for each callback" do target = stub("target") 2.times do |i| @manager.queue_events_for_resource(stub("source", :info => nil), target, "callback#{i}", ["event#{i}"]) end @manager.queued_events(target) { |callback, events| } end it "should use the source to log that it's scheduling a refresh of the target" do target = stub("target") source = stub 'source' source.expects(:info) @manager.queue_events_for_resource(source, target, "callback", ["event"]) @manager.queued_events(target) { |callback, events| } end end describe "when processing events for a given resource" do before do @transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new, nil, nil) @manager = Puppet::Transaction::EventManager.new(@transaction) @manager.stubs(:queue_events) @resource = Puppet::Type.type(:file).new :path => make_absolute("/my/file") @event = Puppet::Transaction::Event.new(:name => :event, :resource => @resource) end it "should call the required callback once for each set of associated events" do @manager.expects(:queued_events).with(@resource).multiple_yields([:callback1, [@event]], [:callback2, [@event]]) @resource.expects(:callback1) @resource.expects(:callback2) @manager.process_events(@resource) end it "should set the 'restarted' state on the resource status" do @manager.expects(:queued_events).with(@resource).yields(:callback1, [@event]) @resource.stubs(:callback1) @manager.process_events(@resource) expect(@transaction.resource_status(@resource)).to be_restarted end it "should have an event on the resource status" do @manager.expects(:queued_events).with(@resource).yields(:callback1, [@event]) @resource.stubs(:callback1) @manager.process_events(@resource) #x expect(@transaction.resource_status(@resource).events.length).to eq(1) end it "should queue a 'restarted' event generated by the resource" do @manager.expects(:queued_events).with(@resource).yields(:callback1, [@event]) @resource.stubs(:callback1) @resource.expects(:event).with(:message => "Triggered 'callback1' from 1 event", :status => 'success', :name => 'callback1') @resource.expects(:event).with(:name => :restarted, :status => "success").returns "myevent" @manager.expects(:queue_events).with(@resource, ["myevent"]) @manager.process_events(@resource) end it "should log that it restarted" do @manager.expects(:queued_events).with(@resource).yields(:callback1, [@event]) @resource.stubs(:callback1) @resource.expects(:notice).with { |msg| msg.include?("Triggered 'callback1'") } @manager.process_events(@resource) end describe "and the events include a noop event and at least one non-noop event" do before do @event.stubs(:status).returns "noop" @event2 = Puppet::Transaction::Event.new(:name => :event, :resource => @resource) @event2.status = "success" @manager.expects(:queued_events).with(@resource).yields(:callback1, [@event, @event2]) end it "should call the callback" do @resource.expects(:callback1) @manager.process_events(@resource) end end describe "and the events are all noop events" do before do @event.stubs(:status).returns "noop" @resource.stubs(:event).returns(Puppet::Transaction::Event.new) @manager.expects(:queued_events).with(@resource).yields(:callback1, [@event]) end it "should log" do @resource.expects(:notice).with { |msg| msg.include?("Would have triggered 'callback1'") } @manager.process_events(@resource) end it "should not call the callback" do @resource.expects(:callback1).never @manager.process_events(@resource) end it "should queue a new noop event generated from the resource" do event = Puppet::Transaction::Event.new @resource.expects(:event).with(:status => "noop", :name => :noop_restart).returns event @manager.expects(:queue_events).with(@resource, [event]) @manager.process_events(@resource) end end describe "and the resource has noop set to true" do before do @event.stubs(:status).returns "success" @resource.stubs(:event).returns(Puppet::Transaction::Event.new) @resource.stubs(:noop?).returns(true) @manager.expects(:queued_events).with(@resource).yields(:callback1, [@event]) end it "should log" do @resource.expects(:notice).with { |msg| msg.include?("Would have triggered 'callback1'") } @manager.process_events(@resource) end it "should not call the callback" do @resource.expects(:callback1).never @manager.process_events(@resource) end it "should queue a new noop event generated from the resource" do event = Puppet::Transaction::Event.new @resource.expects(:event).with(:status => "noop", :name => :noop_restart).returns event @manager.expects(:queue_events).with(@resource, [event]) @manager.process_events(@resource) end end describe "and the callback fails" do before do @resource.expects(:callback1).raises "a failure" @resource.stubs(:err) @manager.expects(:queued_events).yields(:callback1, [@event]) end it "should log but not fail" do @resource.expects(:err) expect { @manager.process_events(@resource) }.not_to raise_error end it "should set the 'failed_restarts' state on the resource status" do @manager.process_events(@resource) expect(@transaction.resource_status(@resource)).to be_failed_to_restart end it "should set the 'failed' state on the resource status" do @manager.process_events(@resource) expect(@transaction.resource_status(@resource)).to be_failed end it "should record a failed event on the resource status" do @manager.process_events(@resource) expect(@transaction.resource_status(@resource).events.length).to eq(2) expect(@transaction.resource_status(@resource).events[0].status).to eq('failure') end it "should not queue a 'restarted' event" do @manager.expects(:queue_events).never @manager.process_events(@resource) end it "should set the 'restarted' state on the resource status" do @manager.process_events(@resource) expect(@transaction.resource_status(@resource)).not_to be_restarted end end end describe "when queueing then processing events for a given resource" do before do @transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new, nil, nil) @manager = Puppet::Transaction::EventManager.new(@transaction) @resource = Puppet::Type.type(:file).new :path => make_absolute("/my/file") @target = Puppet::Type.type(:file).new :path => make_absolute("/your/file") @graph = stub 'graph' @graph.stubs(:matching_edges).returns [] @graph.stubs(:matching_edges).with(anything, @resource).returns [stub('edge', :target => @target, :callback => :refresh)] @manager.stubs(:relationship_graph).returns @graph @event = Puppet::Transaction::Event.new(:name => :notify, :resource => @target) @event2 = Puppet::Transaction::Event.new(:name => :service_start, :resource => @target, :invalidate_refreshes => true) end it "should succeed when there's no invalidated event" do @manager.queue_events(@target, [@event2]) end describe "and the events were dequeued/invalidated" do before do @resource.expects(:info).with { |msg| msg.include?("Scheduling refresh") } @target.expects(:info).with { |msg| msg.include?("Unscheduling") } end it "should not run an event or log" do @target.expects(:notice).with { |msg| msg.include?("Would have triggered 'refresh'") }.never @target.expects(:refresh).never @manager.queue_events(@resource, [@event]) @manager.queue_events(@target, [@event2]) @manager.process_events(@resource) @manager.process_events(@target) end end end end puppet-5.5.10/spec/unit/transaction/event_spec.rb0000644005276200011600000001643413417161721021740 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/transaction/event' class TestResource def to_s "Foo[bar]" end def [](v) nil end end describe Puppet::Transaction::Event do include PuppetSpec::Files it "should support resource" do event = Puppet::Transaction::Event.new event.resource = TestResource.new expect(event.resource).to eq("Foo[bar]") end it "should always convert the property to a string" do expect(Puppet::Transaction::Event.new(:property => :foo).property).to eq("foo") end it "should always convert the resource to a string" do expect(Puppet::Transaction::Event.new(:resource => TestResource.new).resource).to eq("Foo[bar]") end it "should produce the message when converted to a string" do event = Puppet::Transaction::Event.new event.expects(:message).returns "my message" expect(event.to_s).to eq("my message") end it "should support 'status'" do event = Puppet::Transaction::Event.new event.status = "success" expect(event.status).to eq("success") end it "should fail if the status is not to 'audit', 'noop', 'success', or 'failure" do event = Puppet::Transaction::Event.new expect { event.status = "foo" }.to raise_error(ArgumentError) end it "should support tags" do expect(Puppet::Transaction::Event.ancestors).to include(Puppet::Util::Tagging) end it "should create a timestamp at its creation time" do expect(Puppet::Transaction::Event.new.time).to be_instance_of(Time) end describe "audit property" do it "should default to false" do expect(Puppet::Transaction::Event.new.audited).to eq(false) end end describe "when sending logs" do before do Puppet::Util::Log.stubs(:new) end it "should set the level to the resources's log level if the event status is 'success' and a resource is available" do resource = stub 'resource' resource.expects(:[]).with(:loglevel).returns :myloglevel Puppet::Util::Log.expects(:create).with { |args| args[:level] == :myloglevel } Puppet::Transaction::Event.new(:status => "success", :resource => resource).send_log end it "should set the level to 'notice' if the event status is 'success' and no resource is available" do Puppet::Util::Log.expects(:new).with { |args| args[:level] == :notice } Puppet::Transaction::Event.new(:status => "success").send_log end it "should set the level to 'notice' if the event status is 'noop'" do Puppet::Util::Log.expects(:new).with { |args| args[:level] == :notice } Puppet::Transaction::Event.new(:status => "noop").send_log end it "should set the level to 'err' if the event status is 'failure'" do Puppet::Util::Log.expects(:new).with { |args| args[:level] == :err } Puppet::Transaction::Event.new(:status => "failure").send_log end it "should set the 'message' to the event log" do Puppet::Util::Log.expects(:new).with { |args| args[:message] == "my message" } Puppet::Transaction::Event.new(:message => "my message").send_log end it "should set the tags to the event tags" do Puppet::Util::Log.expects(:new).with { |args| expect(args[:tags].to_a).to match_array(%w{one two}) } Puppet::Transaction::Event.new(:tags => %w{one two}).send_log end [:file, :line].each do |attr| it "should pass the #{attr}" do Puppet::Util::Log.expects(:new).with { |args| args[attr] == "my val" } Puppet::Transaction::Event.new(attr => "my val").send_log end end it "should use the source description as the source if one is set" do Puppet::Util::Log.expects(:new).with { |args| args[:source] == "/my/param" } Puppet::Transaction::Event.new(:source_description => "/my/param", :resource => TestResource.new, :property => "foo").send_log end it "should use the property as the source if one is available and no source description is set" do Puppet::Util::Log.expects(:new).with { |args| args[:source] == "foo" } Puppet::Transaction::Event.new(:resource => TestResource.new, :property => "foo").send_log end it "should use the property as the source if one is available and no property or source description is set" do Puppet::Util::Log.expects(:new).with { |args| args[:source] == "Foo[bar]" } Puppet::Transaction::Event.new(:resource => TestResource.new).send_log end end describe "When converting to YAML" do let(:resource) { Puppet::Type.type(:file).new(:title => make_absolute('/tmp/foo')) } let(:event) do Puppet::Transaction::Event.new(:source_description => "/my/param", :resource => resource, :file => "/foo.rb", :line => 27, :tags => %w{one two}, :desired_value => 7, :historical_value => 'Brazil', :message => "Help I'm trapped in a spec test", :name => :mode_changed, :previous_value => 6, :property => :mode, :status => 'success', :redacted => false, :corrective_change => false) end it 'to_data_hash returns value that is instance of to Data' do expect(Puppet::Pops::Types::TypeFactory.data.instance?(event.to_data_hash)).to be_truthy end end it "should round trip through json" do resource = Puppet::Type.type(:file).new(:title => make_absolute("/tmp/foo")) event = Puppet::Transaction::Event.new( :source_description => "/my/param", :resource => resource, :file => "/foo.rb", :line => 27, :tags => %w{one two}, :desired_value => 7, :historical_value => 'Brazil', :message => "Help I'm trapped in a spec test", :name => :mode_changed, :previous_value => 6, :property => :mode, :status => 'success') tripped = Puppet::Transaction::Event.from_data_hash(JSON.parse(event.to_json)) expect(tripped.audited).to eq(event.audited) expect(tripped.property).to eq(event.property) expect(tripped.previous_value).to eq(event.previous_value) expect(tripped.desired_value).to eq(event.desired_value) expect(tripped.historical_value).to eq(event.historical_value) expect(tripped.message).to eq(event.message) expect(tripped.name).to eq(event.name) expect(tripped.status).to eq(event.status) expect(tripped.time).to eq(event.time) end it "should round trip an event for an inspect report through json" do resource = Puppet::Type.type(:file).new(:title => make_absolute("/tmp/foo")) event = Puppet::Transaction::Event.new( :audited => true, :source_description => "/my/param", :resource => resource, :file => "/foo.rb", :line => 27, :tags => %w{one two}, :message => "Help I'm trapped in a spec test", :previous_value => 6, :property => :mode, :status => 'success') tripped = Puppet::Transaction::Event.from_data_hash(JSON.parse(event.to_json)) expect(tripped.desired_value).to be_nil expect(tripped.historical_value).to be_nil expect(tripped.name).to be_nil expect(tripped.audited).to eq(event.audited) expect(tripped.property).to eq(event.property) expect(tripped.previous_value).to eq(event.previous_value) expect(tripped.message).to eq(event.message) expect(tripped.status).to eq(event.status) expect(tripped.time).to eq(event.time) end end puppet-5.5.10/spec/unit/transaction/additional_resource_generator_spec.rb0000644005276200011600000004254313417161722026705 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/transaction' require 'puppet_spec/compiler' require 'matchers/relationship_graph_matchers' require 'matchers/include_in_order' require 'matchers/resource' describe Puppet::Transaction::AdditionalResourceGenerator do include PuppetSpec::Compiler include PuppetSpec::Files include RelationshipGraphMatchers include Matchers::Resource let(:prioritizer) { Puppet::Graph::SequentialPrioritizer.new } let(:env) { Puppet::Node::Environment.create(:testing, []) } let(:node) { Puppet::Node.new('test', :environment => env) } let(:loaders) { Puppet::Pops::Loaders.new(env) } around :each do |example| Puppet::Parser::Compiler.any_instance.stubs(:loaders).returns(loaders) Puppet.override(:loaders => loaders, :current_environment => env) do Puppet::Type.newtype(:generator) do include PuppetSpec::Compiler newparam(:name) do isnamevar end newparam(:kind) do defaultto :eval_generate newvalues(:eval_generate, :generate) end newparam(:code) def respond_to?(method_name) method_name == self[:kind] || super end def eval_generate eval_code end def generate eval_code end def eval_code if self[:code] compile_to_ral(self[:code]).resources.select { |r| r.ref =~ /Notify/ } else [] end end end Puppet::Type.newtype(:autorequire) do newparam(:name) do isnamevar end autorequire(:notify) do self[:name] end end Puppet::Type.newtype(:gen_auto) do newparam(:name) do isnamevar end newparam(:eval_after) do end def generate() [ Puppet::Type.type(:autorequire).new(:name => self[:eval_after]) ] end end Puppet::Type.newtype(:empty) do newparam(:name) do isnamevar end end Puppet::Type.newtype(:gen_empty) do newparam(:name) do isnamevar end newparam(:eval_after) do end def generate() [ Puppet::Type.type(:empty).new(:name => self[:eval_after], :require => "Notify[#{self[:eval_after]}]") ] end end example.run Puppet::Type.rmtype(:gen_empty) Puppet::Type.rmtype(:eval_after) Puppet::Type.rmtype(:autorequire) Puppet::Type.rmtype(:generator) end end def find_vertex(graph, type, title) graph.vertices.find {|v| v.type == type and v.title == title} end context "when applying eval_generate" do it "should add the generated resources to the catalog" do catalog = compile_to_ral(<<-MANIFEST) generator { thing: code => 'notify { hello: }' } MANIFEST eval_generate_resources_in(catalog, relationship_graph_for(catalog), 'Generator[thing]') expect(catalog).to have_resource('Notify[hello]') end it "should add a sentinel whit for the resource" do graph = relationships_after_eval_generating(<<-MANIFEST, 'Generator[thing]') generator { thing: code => 'notify { hello: }' } MANIFEST expect(find_vertex(graph, :whit, "completed_thing")).to be_a(Puppet::Type.type(:whit)) end it "should replace dependencies on the resource with dependencies on the sentinel" do graph = relationships_after_eval_generating(<<-MANIFEST, 'Generator[thing]') generator { thing: code => 'notify { hello: }' } notify { last: require => Generator['thing'] } MANIFEST expect(graph).to enforce_order_with_edge( 'Whit[completed_thing]', 'Notify[last]') end it "should add an edge from the nearest ancestor to the generated resource" do graph = relationships_after_eval_generating(<<-MANIFEST, 'Generator[thing]') generator { thing: code => 'notify { hello: } notify { goodbye: }' } MANIFEST expect(graph).to enforce_order_with_edge( 'Generator[thing]', 'Notify[hello]') expect(graph).to enforce_order_with_edge( 'Generator[thing]', 'Notify[goodbye]') end it "should add an edge from each generated resource to the sentinel" do graph = relationships_after_eval_generating(<<-MANIFEST, 'Generator[thing]') generator { thing: code => 'notify { hello: } notify { goodbye: }' } MANIFEST expect(graph).to enforce_order_with_edge( 'Notify[hello]', 'Whit[completed_thing]') expect(graph).to enforce_order_with_edge( 'Notify[goodbye]', 'Whit[completed_thing]') end it "should add an edge from the resource to the sentinel" do graph = relationships_after_eval_generating(<<-MANIFEST, 'Generator[thing]') generator { thing: code => 'notify { hello: }' } MANIFEST expect(graph).to enforce_order_with_edge( 'Generator[thing]', 'Whit[completed_thing]') end it "should tag the sentinel with the tags of the resource" do graph = relationships_after_eval_generating(<<-MANIFEST, 'Generator[thing]') generator { thing: code => 'notify { hello: }', tag => 'foo', } MANIFEST whit = find_vertex(graph, :whit, "completed_thing") expect(whit.tags).to be_superset(['thing', 'foo', 'generator'].to_set) end it "should contain the generated resources in the same container as the generator" do catalog = compile_to_ral(<<-MANIFEST) class container { generator { thing: code => 'notify { hello: }' } } include container MANIFEST eval_generate_resources_in(catalog, relationship_graph_for(catalog), 'Generator[thing]') expect(catalog).to contain_resources_equally('Generator[thing]', 'Notify[hello]') end it "should return false if an error occurred when generating resources" do catalog = compile_to_ral(<<-MANIFEST) generator { thing: code => 'fail("not a good generation")' } MANIFEST generator = Puppet::Transaction::AdditionalResourceGenerator.new(catalog, relationship_graph_for(catalog), prioritizer) expect(generator.eval_generate(catalog.resource('Generator[thing]'))). to eq(false) end it "should return true if resources were generated" do catalog = compile_to_ral(<<-MANIFEST) generator { thing: code => 'notify { hello: }' } MANIFEST generator = Puppet::Transaction::AdditionalResourceGenerator.new(catalog, relationship_graph_for(catalog), prioritizer) expect(generator.eval_generate(catalog.resource('Generator[thing]'))). to eq(true) end it "should not add a sentinel if no resources are generated" do catalog = compile_to_ral(<<-MANIFEST) generator { thing: } MANIFEST relationship_graph = relationship_graph_for(catalog) generator = Puppet::Transaction::AdditionalResourceGenerator.new(catalog, relationship_graph, prioritizer) expect(generator.eval_generate(catalog.resource('Generator[thing]'))). to eq(false) expect(find_vertex(relationship_graph, :whit, "completed_thing")).to be_nil end it "orders generated resources with the generator" do graph = relationships_after_eval_generating(<<-MANIFEST, 'Generator[thing]') notify { before: } generator { thing: code => 'notify { hello: }' } notify { after: } MANIFEST expect(order_resources_traversed_in(graph)).to( include_in_order("Notify[before]", "Generator[thing]", "Notify[hello]", "Notify[after]")) end it "orders the generator in manifest order with dependencies" do graph = relationships_after_eval_generating(<<-MANIFEST, 'Generator[thing]') notify { before: } generator { thing: code => 'notify { hello: } notify { goodbye: }' } notify { third: require => Generator['thing'] } notify { after: } MANIFEST expect(order_resources_traversed_in(graph)).to( include_in_order("Notify[before]", "Generator[thing]", "Notify[hello]", "Notify[goodbye]", "Notify[third]", "Notify[after]")) end it "duplicate generated resources are made dependent on the generator" do graph = relationships_after_eval_generating(<<-MANIFEST, 'Generator[thing]') notify { before: } notify { hello: } generator { thing: code => 'notify { before: }' } notify { third: require => Generator['thing'] } notify { after: } MANIFEST expect(order_resources_traversed_in(graph)).to( include_in_order("Notify[hello]", "Generator[thing]", "Notify[before]", "Notify[third]", "Notify[after]")) end it "preserves dependencies on duplicate generated resources" do graph = relationships_after_eval_generating(<<-MANIFEST, 'Generator[thing]') notify { before: } generator { thing: code => 'notify { hello: } notify { before: }', require => 'Notify[before]' } notify { third: require => Generator['thing'] } notify { after: } MANIFEST expect(order_resources_traversed_in(graph)).to( include_in_order("Notify[before]", "Generator[thing]", "Notify[hello]", "Notify[third]", "Notify[after]")) end it "sets resources_failed_to_generate to true if resource#eval_generate raises an exception" do catalog = compile_to_ral(<<-MANIFEST) notify { 'hello': } MANIFEST catalog.resource("Notify[hello]").stubs(:eval_generate).raises(RuntimeError) relationship_graph = relationship_graph_for(catalog) generator = Puppet::Transaction::AdditionalResourceGenerator.new(catalog, relationship_graph, prioritizer) generator.eval_generate(catalog.resource("Notify[hello]")) expect(generator.resources_failed_to_generate).to be_truthy end def relationships_after_eval_generating(manifest, resource_to_generate) catalog = compile_to_ral(manifest) relationship_graph = relationship_graph_for(catalog) eval_generate_resources_in(catalog, relationship_graph, resource_to_generate) relationship_graph end def eval_generate_resources_in(catalog, relationship_graph, resource_to_generate) generator = Puppet::Transaction::AdditionalResourceGenerator.new(catalog, relationship_graph, prioritizer) generator.eval_generate(catalog.resource(resource_to_generate)) end end context "when applying generate" do it "should add the generated resources to the catalog" do catalog = compile_to_ral(<<-MANIFEST) generator { thing: kind => generate, code => 'notify { hello: }' } MANIFEST generate_resources_in(catalog, relationship_graph_for(catalog), 'Generator[thing]') expect(catalog).to have_resource('Notify[hello]') end it "should contain the generated resources in the same container as the generator" do catalog = compile_to_ral(<<-MANIFEST) class container { generator { thing: kind => generate, code => 'notify { hello: }' } } include container MANIFEST generate_resources_in(catalog, relationship_graph_for(catalog), 'Generator[thing]') expect(catalog).to contain_resources_equally('Generator[thing]', 'Notify[hello]') end it "should add an edge from the nearest ancestor to the generated resource" do graph = relationships_after_generating(<<-MANIFEST, 'Generator[thing]') generator { thing: kind => generate, code => 'notify { hello: } notify { goodbye: }' } MANIFEST expect(graph).to enforce_order_with_edge( 'Generator[thing]', 'Notify[hello]') expect(graph).to enforce_order_with_edge( 'Generator[thing]', 'Notify[goodbye]') end it "orders generated resources with the generator" do graph = relationships_after_generating(<<-MANIFEST, 'Generator[thing]') notify { before: } generator { thing: kind => generate, code => 'notify { hello: }' } notify { after: } MANIFEST expect(order_resources_traversed_in(graph)).to( include_in_order("Notify[before]", "Generator[thing]", "Notify[hello]", "Notify[after]")) end it "duplicate generated resources are made dependent on the generator" do graph = relationships_after_generating(<<-MANIFEST, 'Generator[thing]') notify { before: } notify { hello: } generator { thing: kind => generate, code => 'notify { before: }' } notify { third: require => Generator['thing'] } notify { after: } MANIFEST expect(order_resources_traversed_in(graph)).to( include_in_order("Notify[hello]", "Generator[thing]", "Notify[before]", "Notify[third]", "Notify[after]")) end it "preserves dependencies on duplicate generated resources" do graph = relationships_after_generating(<<-MANIFEST, 'Generator[thing]') notify { before: } generator { thing: kind => generate, code => 'notify { hello: } notify { before: }', require => 'Notify[before]' } notify { third: require => Generator['thing'] } notify { after: } MANIFEST expect(order_resources_traversed_in(graph)).to( include_in_order("Notify[before]", "Generator[thing]", "Notify[hello]", "Notify[third]", "Notify[after]")) end it "orders the generator in manifest order with dependencies" do graph = relationships_after_generating(<<-MANIFEST, 'Generator[thing]') notify { before: } generator { thing: kind => generate, code => 'notify { hello: } notify { goodbye: }' } notify { third: require => Generator['thing'] } notify { after: } MANIFEST expect(order_resources_traversed_in(graph)).to( include_in_order("Notify[before]", "Generator[thing]", "Notify[hello]", "Notify[goodbye]", "Notify[third]", "Notify[after]")) end it "runs autorequire on the generated resource" do graph = relationships_after_generating(<<-MANIFEST, 'Gen_auto[thing]') gen_auto { thing: eval_after => hello, } notify { hello: } notify { goodbye: } MANIFEST expect(order_resources_traversed_in(graph)).to( include_in_order("Gen_auto[thing]", "Notify[hello]", "Autorequire[hello]", "Notify[goodbye]")) end it "evaluates metaparameters on the generated resource" do graph = relationships_after_generating(<<-MANIFEST, 'Gen_empty[thing]') gen_empty { thing: eval_after => hello, } notify { hello: } notify { goodbye: } MANIFEST expect(order_resources_traversed_in(graph)).to( include_in_order("Gen_empty[thing]", "Notify[hello]", "Empty[hello]", "Notify[goodbye]")) end it "sets resources_failed_to_generate to true if resource#generate raises an exception" do catalog = compile_to_ral(<<-MANIFEST) user { 'foo': ensure => present, } MANIFEST catalog.resource("User[foo]").stubs(:generate).raises(RuntimeError) relationship_graph = relationship_graph_for(catalog) generator = Puppet::Transaction::AdditionalResourceGenerator.new(catalog, relationship_graph, prioritizer) generator.generate_additional_resources(catalog.resource("User[foo]")) expect(generator.resources_failed_to_generate).to be_truthy end def relationships_after_generating(manifest, resource_to_generate) catalog = compile_to_ral(manifest) generate_resources_in(catalog, nil, resource_to_generate) relationship_graph_for(catalog) end def generate_resources_in(catalog, relationship_graph, resource_to_generate) generator = Puppet::Transaction::AdditionalResourceGenerator.new(catalog, relationship_graph, prioritizer) generator.generate_additional_resources(catalog.resource(resource_to_generate)) end end def relationship_graph_for(catalog) relationship_graph = Puppet::Graph::RelationshipGraph.new(prioritizer) relationship_graph.populate_from(catalog) relationship_graph end def order_resources_traversed_in(relationships) order_seen = [] relationships.traverse { |resource| order_seen << resource.ref } order_seen end RSpec::Matchers.define :contain_resources_equally do |*resource_refs| match do |catalog| @containers = resource_refs.collect do |resource_ref| catalog.container_of(catalog.resource(resource_ref)).ref end @containers.all? { |resource_ref| resource_ref == @containers[0] } end def failure_message "expected #{@expected.join(', ')} to all be contained in the same resource but the containment was #{@expected.zip(@containers).collect { |(res, container)| res + ' => ' + container }.join(', ')}" end end end puppet-5.5.10/spec/unit/transaction/persistence_spec.rb0000644005276200011600000001524113417161722023137 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'yaml' require 'fileutils' require 'puppet/transaction/persistence' describe Puppet::Transaction::Persistence do include PuppetSpec::Files before(:each) do @basepath = File.expand_path("/somepath") end describe "when loading from file" do before do Puppet.settings.stubs(:use).returns(true) end describe "when the file/directory does not exist" do before(:each) do @path = tmpfile('storage_test') end it "should not fail to load" do expect(Puppet::FileSystem.exist?(@path)).to be_falsey Puppet[:statedir] = @path persistence = Puppet::Transaction::Persistence.new persistence.load Puppet[:transactionstorefile] = @path persistence = Puppet::Transaction::Persistence.new persistence.load end end describe "when the file/directory exists" do before(:each) do @tmpfile = tmpfile('storage_test') Puppet[:transactionstorefile] = @tmpfile end def write_state_file(contents) File.open(@tmpfile, 'w') { |f| f.write(contents) } end it "should overwrite its internal state if load() is called" do resource = "Foo[bar]" property = "my" value = "something" Puppet.expects(:err).never persistence = Puppet::Transaction::Persistence.new persistence.set_system_value(resource, property, value) persistence.load expect(persistence.get_system_value(resource, property)).to eq(nil) end it "should restore its internal state if the file contains valid YAML" do test_yaml = {"resources"=>{"a"=>"b"}} write_state_file(test_yaml.to_yaml) Puppet.expects(:err).never persistence = Puppet::Transaction::Persistence.new persistence.load expect(persistence.data).to eq(test_yaml) end it "should initialize with a clear internal state if the file does not contain valid YAML" do write_state_file('{ invalid') Puppet.expects(:err).with(regexp_matches(/Transaction store file .* is corrupt/)) persistence = Puppet::Transaction::Persistence.new persistence.load expect(persistence.data).to eq({}) end it "should initialize with a clear internal state if the file does not contain a hash of data" do write_state_file("not_a_hash") Puppet.expects(:err).with(regexp_matches(/Transaction store file .* is valid YAML but not returning a hash/)) persistence = Puppet::Transaction::Persistence.new persistence.load expect(persistence.data).to eq({}) end it "should raise an error if the file does not contain valid YAML and cannot be renamed" do write_state_file('{ invalid') File.expects(:rename).raises(SystemCallError) Puppet.expects(:err).with(regexp_matches(/Transaction store file .* is corrupt/)) Puppet.expects(:err).with(regexp_matches(/Unable to rename/)) persistence = Puppet::Transaction::Persistence.new expect { persistence.load }.to raise_error(Puppet::Error, /Could not rename/) end it "should attempt to rename the file if the file is corrupted" do write_state_file('{ invalid') File.expects(:rename).at_least_once Puppet.expects(:err).with(regexp_matches(/Transaction store file .* is corrupt/)) persistence = Puppet::Transaction::Persistence.new persistence.load end it "should fail gracefully on load() if the file is not a regular file" do FileUtils.rm_f(@tmpfile) Dir.mkdir(@tmpfile) Puppet.expects(:warning).with(regexp_matches(/Transaction store file .* is not a file/)) persistence = Puppet::Transaction::Persistence.new persistence.load end end end describe "when storing to the file" do before(:each) do @tmpfile = tmpfile('persistence_test') @saved = Puppet[:transactionstorefile] Puppet[:transactionstorefile] = @tmpfile end it "should create the file if it does not exist" do expect(Puppet::FileSystem.exist?(Puppet[:transactionstorefile])).to be_falsey persistence = Puppet::Transaction::Persistence.new persistence.save expect(Puppet::FileSystem.exist?(Puppet[:transactionstorefile])).to be_truthy end it "should raise an exception if the file is not a regular file" do Dir.mkdir(Puppet[:transactionstorefile]) persistence = Puppet::Transaction::Persistence.new if Puppet.features.microsoft_windows? expect do persistence.save end.to raise_error do |error| expect(error).to be_a(Puppet::Util::Windows::Error) expect(error.code).to eq(5) # ERROR_ACCESS_DENIED end else expect { persistence.save }.to raise_error(Errno::EISDIR, /Is a directory/) end Dir.rmdir(Puppet[:transactionstorefile]) end it "should load the same information that it saves" do resource = "File[/tmp/foo]" property = "content" value = "foo" persistence = Puppet::Transaction::Persistence.new persistence.set_system_value(resource, property, value) persistence.save persistence.load expect(persistence.get_system_value(resource, property)).to eq(value) end end describe "when checking if persistence is enabled" do let(:mock_catalog) do mock end let (:persistence) do Puppet::Transaction::Persistence.new end before :all do @preferred_run_mode = Puppet.settings.preferred_run_mode end after :all do Puppet.settings.preferred_run_mode = @preferred_run_mode end it "should not be enabled when not running in agent mode" do Puppet.settings.preferred_run_mode = :user mock_catalog.stubs(:host_config?).returns(true) expect(persistence.enabled?(mock_catalog)).to be false end it "should not be enabled when the catalog is not the host catalog" do Puppet.settings.preferred_run_mode = :agent mock_catalog.stubs(:host_config?).returns(false) expect(persistence.enabled?(mock_catalog)).to be false end it "should not be enabled outside of agent mode and the catalog is not the host catalog" do Puppet.settings.preferred_run_mode = :user mock_catalog.stubs(:host_config?).returns(false) expect(persistence.enabled?(mock_catalog)).to be false end it "should be enabled in agent mode and when the catalog is the host catalog" do Puppet.settings.preferred_run_mode = :agent mock_catalog.stubs(:host_config?).returns(true) expect(persistence.enabled?(mock_catalog)).to be true end end end puppet-5.5.10/spec/unit/transaction/report_spec.rb0000644005276200011600000005632113417161722022132 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet' require 'puppet/transaction/report' require 'matchers/json' describe Puppet::Transaction::Report do include JSONMatchers include PuppetSpec::Files before do Puppet::Util::Storage.stubs(:store) end it "should set its host name to the node_name_value" do Puppet[:node_name_value] = 'mynode' expect(Puppet::Transaction::Report.new.host).to eq("mynode") end it "should return its host name as its name" do r = Puppet::Transaction::Report.new expect(r.name).to eq(r.host) end it "should create an initialization timestamp" do Time.expects(:now).returns "mytime" expect(Puppet::Transaction::Report.new.time).to eq("mytime") end it "should take a 'configuration_version' as an argument" do expect(Puppet::Transaction::Report.new("some configuration version", "some environment").configuration_version).to eq("some configuration version") end it "should take a 'transaction_uuid' as an argument" do expect(Puppet::Transaction::Report.new("some configuration version", "some environment", "some transaction uuid").transaction_uuid).to eq("some transaction uuid") end it "should take a 'job_id' as an argument" do expect(Puppet::Transaction::Report.new('cv', 'env', 'tid', 'some job id').job_id).to eq('some job id') end it "should be able to set configuration_version" do report = Puppet::Transaction::Report.new report.configuration_version = "some version" expect(report.configuration_version).to eq("some version") end it "should be able to set transaction_uuid" do report = Puppet::Transaction::Report.new report.transaction_uuid = "some transaction uuid" expect(report.transaction_uuid).to eq("some transaction uuid") end it "should be able to set job_id" do report = Puppet::Transaction::Report.new report.job_id = "some job id" expect(report.job_id).to eq("some job id") end it "should be able to set code_id" do report = Puppet::Transaction::Report.new report.code_id = "some code id" expect(report.code_id).to eq("some code id") end it "should be able to set catalog_uuid" do report = Puppet::Transaction::Report.new report.catalog_uuid = "some catalog uuid" expect(report.catalog_uuid).to eq("some catalog uuid") end it "should be able to set cached_catalog_status" do report = Puppet::Transaction::Report.new report.cached_catalog_status = "explicitly_requested" expect(report.cached_catalog_status).to eq("explicitly_requested") end it "should set noop to true if Puppet[:noop] is true" do Puppet[:noop] = true report = Puppet::Transaction::Report.new expect(report.noop).to be_truthy end it "should set noop to false if Puppet[:noop] is false" do Puppet[:noop] = false report = Puppet::Transaction::Report.new expect(report.noop).to be_falsey end it "should set noop to false if Puppet[:noop] is unset" do Puppet[:noop] = nil report = Puppet::Transaction::Report.new expect(report.noop).to be_falsey end it "should take 'environment' as an argument" do expect(Puppet::Transaction::Report.new("some configuration version", "some environment").environment).to eq("some environment") end it "should be able to set environment" do report = Puppet::Transaction::Report.new report.environment = "some environment" expect(report.environment).to eq("some environment") end it "should be able to set resources_failed_to_generate" do report = Puppet::Transaction::Report.new report.resources_failed_to_generate = true expect(report.resources_failed_to_generate).to be_truthy end it "resources_failed_to_generate should not be true by default" do report = Puppet::Transaction::Report.new expect(report.resources_failed_to_generate).to be_falsey end it "should not include whits" do Puppet::FileBucket::File.indirection.stubs(:save) filename = tmpfile('whit_test') file = Puppet::Type.type(:file).new(:path => filename) catalog = Puppet::Resource::Catalog.new catalog.add_resource(file) report = Puppet::Transaction::Report.new catalog.apply(:report => report) report.finalize_report expect(report.resource_statuses.values.any? {|res| res.resource_type =~ /whit/i}).to be_falsey expect(report.metrics['time'].values.any? {|metric| metric.first =~ /whit/i}).to be_falsey end describe "when accepting logs" do before do @report = Puppet::Transaction::Report.new end it "should add new logs to the log list" do @report << "log" expect(@report.logs[-1]).to eq("log") end it "should return self" do r = @report << "log" expect(r).to equal(@report) end end describe "#as_logging_destination" do it "makes the report collect logs during the block " do log_string = 'Hello test report!' report = Puppet::Transaction::Report.new report.as_logging_destination do Puppet.err(log_string) end expect(report.logs.collect(&:message)).to include(log_string) end end describe "when accepting resource statuses" do before do @report = Puppet::Transaction::Report.new end it "should add each status to its status list" do status = stub 'status', :resource => "foo" @report.add_resource_status status expect(@report.resource_statuses["foo"]).to equal(status) end end describe "when using the indirector" do it "should redirect :save to the indirection" do Facter.stubs(:value).returns("eh") @indirection = stub 'indirection', :name => :report Puppet::Transaction::Report.stubs(:indirection).returns(@indirection) report = Puppet::Transaction::Report.new @indirection.expects(:save) Puppet::Transaction::Report.indirection.save(report) end it "should default to the 'processor' terminus" do expect(Puppet::Transaction::Report.indirection.terminus_class).to eq(:processor) end it "should delegate its name attribute to its host method" do report = Puppet::Transaction::Report.new report.expects(:host).returns "me" expect(report.name).to eq("me") end end describe "when computing exit status" do it "should produce -1 if no metrics are present" do report = Puppet::Transaction::Report.new("apply") expect(report.exit_status).to eq(-1) end it "should produce 2 if changes are present" do report = Puppet::Transaction::Report.new report.add_metric("changes", {"total" => 1}) report.add_metric("resources", {"failed" => 0}) expect(report.exit_status).to eq(2) end it "should produce 4 if failures are present" do report = Puppet::Transaction::Report.new report.add_metric("changes", {"total" => 0}) report.add_metric("resources", {"failed" => 1}) expect(report.exit_status).to eq(4) end it "should produce 4 if failures to restart are present" do report = Puppet::Transaction::Report.new report.add_metric("changes", {"total" => 0}) report.add_metric("resources", {"failed" => 0}) report.add_metric("resources", {"failed_to_restart" => 1}) expect(report.exit_status).to eq(4) end it "should produce 6 if both changes and failures are present" do report = Puppet::Transaction::Report.new report.add_metric("changes", {"total" => 1}) report.add_metric("resources", {"failed" => 1}) expect(report.exit_status).to eq(6) end end describe "before finalizing the report" do it "should have a status of 'failed'" do report = Puppet::Transaction::Report.new expect(report.status).to eq('failed') end end describe "when finalizing the report" do before do @report = Puppet::Transaction::Report.new end def metric(name, value) if metric = @report.metrics[name.to_s] metric[value] else nil end end def add_statuses(count, type = :file) count.times do |i| status = Puppet::Resource::Status.new(Puppet::Type.type(type).new(:title => make_absolute("/my/path#{i}"))) yield status if block_given? @report.add_resource_status status end end it "should be unchanged if there are no other failures or changes and the transaction completed" do @report.transaction_completed = true @report.finalize_report expect(@report.status).to eq("unchanged") end it "should be failed if there are no other failures or changes and the transaction did not complete" do @report.finalize_report expect(@report.status).to eq("failed") end [:time, :resources, :changes, :events].each do |type| it "should add #{type} metrics" do @report.finalize_report expect(@report.metrics[type.to_s]).to be_instance_of(Puppet::Transaction::Metric) end end describe "for resources" do it "should provide the total number of resources" do add_statuses(3) @report.finalize_report expect(metric(:resources, "total")).to eq(3) end Puppet::Resource::Status::STATES.each do |state| it "should provide the number of #{state} resources as determined by the status objects" do add_statuses(3) { |status| status.send(state.to_s + "=", true) } @report.finalize_report expect(metric(:resources, state.to_s)).to eq(3) end it "should provide 0 for states not in status" do @report.finalize_report expect(metric(:resources, state.to_s)).to eq(0) end end it "should mark the report as 'failed' if there are failing resources" do add_statuses(1) { |status| status.failed = true } @report.transaction_completed = true @report.finalize_report expect(@report.status).to eq('failed') end it "should mark the report as 'failed' if resources failed to restart" do add_statuses(1) { |status| status.failed_to_restart = true } @report.finalize_report expect(@report.status).to eq('failed') end it "should mark the report as 'failed' if resources_failed_to_generate" do @report.resources_failed_to_generate = true @report.transaction_completed = true @report.finalize_report expect(@report.status).to eq('failed') end end describe "for changes" do it "should provide the number of changes from the resource statuses and mark the report as 'changed'" do add_statuses(3) { |status| 3.times { status << Puppet::Transaction::Event.new(:status => 'success') } } @report.transaction_completed = true @report.finalize_report expect(metric(:changes, "total")).to eq(9) expect(@report.status).to eq('changed') end it "should provide a total even if there are no changes, and mark the report as 'unchanged'" do @report.transaction_completed = true @report.finalize_report expect(metric(:changes, "total")).to eq(0) expect(@report.status).to eq('unchanged') end end describe "for times" do it "should provide the total amount of time for each resource type" do add_statuses(3, :file) do |status| status.evaluation_time = 1 end add_statuses(3, :exec) do |status| status.evaluation_time = 2 end add_statuses(3, :tidy) do |status| status.evaluation_time = 3 end @report.finalize_report expect(metric(:time, "file")).to eq(3) expect(metric(:time, "exec")).to eq(6) expect(metric(:time, "tidy")).to eq(9) end it "should accrue times when called for one resource more than once" do @report.add_times :foobar, 50 @report.add_times :foobar, 30 @report.finalize_report expect(metric(:time, "foobar")).to eq(80) end it "should not accrue times when called for one resource more than once when set" do @report.add_times :foobar, 50, false @report.add_times :foobar, 30, false @report.finalize_report expect(metric(:time, "foobar")).to eq(30) end it "should add any provided times from external sources" do @report.add_times :foobar, 50 @report.finalize_report expect(metric(:time, "foobar")).to eq(50) end end describe "for events" do it "should provide the total number of events" do add_statuses(3) do |status| 3.times { |i| status.add_event(Puppet::Transaction::Event.new :status => 'success') } end @report.finalize_report expect(metric(:events, "total")).to eq(9) end it "should provide the total even if there are no events" do @report.finalize_report expect(metric(:events, "total")).to eq(0) end Puppet::Transaction::Event::EVENT_STATUSES.each do |status_name| it "should provide the number of #{status_name} events" do add_statuses(3) do |status| 3.times do |i| event = Puppet::Transaction::Event.new event.status = status_name status.add_event(event) end end @report.finalize_report expect(metric(:events, status_name)).to eq(9) end end end describe "for noop events" do it "should have 'noop_pending == false' when no events are available" do add_statuses(3) @report.finalize_report expect(@report.noop_pending).to be_falsey end it "should have 'noop_pending == false' when no 'noop' events are available" do add_statuses(3) do |status| ['success', 'audit'].each do |status_name| event = Puppet::Transaction::Event.new event.status = status_name status.add_event(event) end end @report.finalize_report expect(@report.noop_pending).to be_falsey end it "should have 'noop_pending == true' when 'noop' events are available" do add_statuses(3) do |status| ['success', 'audit', 'noop'].each do |status_name| event = Puppet::Transaction::Event.new event.status = status_name status.add_event(event) end end @report.finalize_report expect(@report.noop_pending).to be_truthy end it "should have 'noop_pending == true' when 'noop' and 'failure' events are available" do add_statuses(3) do |status| ['success', 'failure', 'audit', 'noop'].each do |status_name| event = Puppet::Transaction::Event.new event.status = status_name status.add_event(event) end end @report.finalize_report expect(@report.noop_pending).to be_truthy end end end describe "when producing a summary" do before do Benchmark.stubs(:realtime).returns(5.05683418) resource = Puppet::Type.type(:notify).new(:name => "testing") catalog = Puppet::Resource::Catalog.new catalog.add_resource resource catalog.version = 1234567 trans = catalog.apply @report = trans.report @report.add_times(:total, "8675") #Report total is now measured, not calculated. @report.finalize_report end %w{changes time resources events version}.each do |main| it "should include the key #{main} in the raw summary hash" do expect(@report.raw_summary).to be_key main end end it "should include the last run time in the raw summary hash" do Time.stubs(:now).returns(Time.utc(2010,11,10,12,0,24)) expect(@report.raw_summary["time"]["last_run"]).to eq(1289390424) end it "should include all resource statuses" do resources_report = @report.raw_summary["resources"] Puppet::Resource::Status::STATES.each do |state| expect(resources_report).to be_include(state.to_s) end end %w{total failure success}.each do |r| it "should include event #{r}" do events_report = @report.raw_summary["events"] expect(events_report).to be_include(r) end end it "should include config version" do expect(@report.raw_summary["version"]["config"]).to eq(1234567) end it "should include puppet version" do expect(@report.raw_summary["version"]["puppet"]).to eq(Puppet.version) end %w{Changes Total Resources Time Events}.each do |main| it "should include information on #{main} in the textual summary" do expect(@report.summary).to be_include(main) end end it 'should sort total at the very end of the time metrics' do expect(@report.summary).to match(/ Last run: \d+ Transaction evaluation: \d+.\d{2} Total: \d+.\d{2} Version: /) end end describe "when outputting yaml" do it "should not include @external_times" do report = Puppet::Transaction::Report.new report.add_times('config_retrieval', 1.0) expect(report.to_data_hash.keys).not_to include('external_times') end it "should not include @resources_failed_to_generate" do report = Puppet::Transaction::Report.new report.resources_failed_to_generate = true expect(report.to_data_hash.keys).not_to include('resources_failed_to_generate') end it 'to_data_hash returns value that is instance of to Data' do expect(Puppet::Pops::Types::TypeFactory.data.instance?(generate_report.to_data_hash)).to be_truthy end end it "defaults to serializing to json" do expect(Puppet::Transaction::Report.default_format).to eq(:json) end it "supports both json, pson and yaml" do # msgpack is optional, so using include instead of eq expect(Puppet::Transaction::Report.supported_formats).to include(:json, :pson, :yaml) end context 'can make a round trip through' do before(:each) do Puppet.push_context(:loaders => Puppet::Pops::Loaders.new(Puppet.lookup(:current_environment))) end after(:each) { Puppet.pop_context } it 'pson' do report = generate_report tripped = Puppet::Transaction::Report.convert_from(:pson, report.render) expect_equivalent_reports(tripped, report) end it 'json' do report = generate_report tripped = Puppet::Transaction::Report.convert_from(:json, report.render) expect_equivalent_reports(tripped, report) end it 'yaml' do report = generate_report yaml_output = report.render(:yaml) tripped = Puppet::Transaction::Report.convert_from(:yaml, yaml_output) expect(yaml_output).to match(/^--- /) expect_equivalent_reports(tripped, report) end end it "generates json which validates against the report schema" do report = generate_report expect(report.render).to validate_against('api/schemas/report.json') end it "generates json for error report which validates against the report schema" do error_report = generate_report_with_error expect(error_report.render).to validate_against('api/schemas/report.json') end def expect_equivalent_reports(tripped, report) expect(tripped.host).to eq(report.host) expect(tripped.time.to_i).to eq(report.time.to_i) expect(tripped.configuration_version).to eq(report.configuration_version) expect(tripped.transaction_uuid).to eq(report.transaction_uuid) expect(tripped.job_id).to eq(report.job_id) expect(tripped.code_id).to eq(report.code_id) expect(tripped.catalog_uuid).to eq(report.catalog_uuid) expect(tripped.cached_catalog_status).to eq(report.cached_catalog_status) expect(tripped.report_format).to eq(report.report_format) expect(tripped.puppet_version).to eq(report.puppet_version) expect(tripped.status).to eq(report.status) expect(tripped.transaction_completed).to eq(report.transaction_completed) expect(tripped.environment).to eq(report.environment) expect(tripped.corrective_change).to eq(report.corrective_change) expect(logs_as_strings(tripped)).to eq(logs_as_strings(report)) expect(metrics_as_hashes(tripped)).to eq(metrics_as_hashes(report)) expect_equivalent_resource_statuses(tripped.resource_statuses, report.resource_statuses) end def logs_as_strings(report) report.logs.map(&:to_report) end def metrics_as_hashes(report) Hash[*report.metrics.collect do |name, m| [name, { :name => m.name, :label => m.label, :value => m.value }] end.flatten] end def expect_equivalent_resource_statuses(tripped, report) expect(tripped.keys.sort).to eq(report.keys.sort) tripped.each_pair do |name, status| expected = report[name] expect(status.title).to eq(expected.title) expect(status.file).to eq(expected.file) expect(status.line).to eq(expected.line) expect(status.resource).to eq(expected.resource) expect(status.resource_type).to eq(expected.resource_type) expect(status.provider_used).to eq(expected.provider_used) expect(status.containment_path).to eq(expected.containment_path) expect(status.evaluation_time).to eq(expected.evaluation_time) expect(status.tags).to eq(expected.tags) expect(status.time.to_i).to eq(expected.time.to_i) expect(status.failed).to eq(expected.failed) expect(status.changed).to eq(expected.changed) expect(status.out_of_sync).to eq(expected.out_of_sync) expect(status.skipped).to eq(expected.skipped) expect(status.change_count).to eq(expected.change_count) expect(status.out_of_sync_count).to eq(expected.out_of_sync_count) expect(status.events).to eq(expected.events) end end def generate_report event_hash = { :audited => false, :property => 'message', :previous_value => SemanticPuppet::VersionRange.parse('>=1.0.0'), :desired_value => SemanticPuppet::VersionRange.parse('>=1.2.0'), :historical_value => nil, :message => "defined 'message' as 'a resource'", :name => :message_changed, :status => 'success', } event = Puppet::Transaction::Event.new(event_hash) status = Puppet::Resource::Status.new(Puppet::Type.type(:notify).new(:title => "a resource")) status.changed = true status.add_event(event) report = Puppet::Transaction::Report.new(1357986, 'test_environment', "df34516e-4050-402d-a166-05b03b940749", '42') report << Puppet::Util::Log.new(:level => :warning, :message => "log message") report.add_times("timing", 4) report.code_id = "some code id" report.catalog_uuid = "some catalog uuid" report.cached_catalog_status = "not_used" report.master_used = "test:000" report.add_resource_status(status) report.transaction_completed = true report.finalize_report report end def generate_report_with_error status = Puppet::Resource::Status.new(Puppet::Type.type(:notify).new(:title => "a resource")) status.changed = true status.failed_because("bad stuff happened") report = Puppet::Transaction::Report.new(1357986, 'test_environment', "df34516e-4050-402d-a166-05b03b940749", '42') report << Puppet::Util::Log.new(:level => :warning, :message => "log message") report.add_times("timing", 4) report.code_id = "some code id" report.catalog_uuid = "some catalog uuid" report.cached_catalog_status = "not_used" report.master_used = "test:000" report.add_resource_status(status) report.transaction_completed = true report.finalize_report report end end puppet-5.5.10/spec/unit/transaction/resource_harness_spec.rb0000644005276200011600000004762713417161722024202 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/transaction/resource_harness' describe Puppet::Transaction::ResourceHarness do include PuppetSpec::Files before do @mode_750 = Puppet.features.microsoft_windows? ? '644' : '750' @mode_755 = Puppet.features.microsoft_windows? ? '644' : '755' path = make_absolute("/my/file") @transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new, nil, nil) @resource = Puppet::Type.type(:file).new :path => path @harness = Puppet::Transaction::ResourceHarness.new(@transaction) @current_state = Puppet::Resource.new(:file, path) @resource.stubs(:retrieve).returns @current_state end it "should accept a transaction at initialization" do harness = Puppet::Transaction::ResourceHarness.new(@transaction) expect(harness.transaction).to equal(@transaction) end it "should delegate to the transaction for its relationship graph" do @transaction.expects(:relationship_graph).returns "relgraph" expect(Puppet::Transaction::ResourceHarness.new(@transaction).relationship_graph).to eq("relgraph") end describe "when evaluating a resource" do it "produces a resource state that describes what happened with the resource" do status = @harness.evaluate(@resource) expect(status.resource).to eq(@resource.ref) expect(status).not_to be_failed expect(status.events).to be_empty end it "retrieves the current state of the resource" do @resource.expects(:retrieve).returns @current_state @harness.evaluate(@resource) end it "produces a failure status for the resource when an error occurs" do the_message = "retrieve failed in testing" @resource.expects(:retrieve).raises(ArgumentError.new(the_message)) status = @harness.evaluate(@resource) expect(status).to be_failed expect(events_to_hash(status.events).collect do |event| { :@status => event[:@status], :@message => event[:@message] } end).to eq([{ :@status => "failure", :@message => the_message }]) end it "records the time it took to evaluate the resource" do before = Time.now status = @harness.evaluate(@resource) after = Time.now expect(status.evaluation_time).to be <= after - before end end def events_to_hash(events) events.map do |event| hash = {} event.instance_variables.each do |varname| hash[varname.to_sym] = event.instance_variable_get(varname) end hash end end def make_stub_provider stubProvider = Class.new(Puppet::Type) stubProvider.instance_eval do initvars newparam(:name) do desc "The name var" isnamevar end newproperty(:foo) do desc "A property that can be changed successfully" def sync end def retrieve :absent end def insync?(reference_value) false end end newproperty(:bar) do desc "A property that raises an exception when you try to change it" def sync raise ZeroDivisionError.new('bar') end def retrieve :absent end def insync?(reference_value) false end end newproperty(:baz) do desc "A property that raises an Exception (not StandardError) when you try to change it" def sync raise Exception.new('baz') end def retrieve :absent end def insync?(reference_value) false end end newproperty(:brillig) do desc "A property that raises a StandardError exception when you test if it's insync?" def sync end def retrieve :absent end def insync?(reference_value) raise ZeroDivisionError.new('brillig') end end newproperty(:slithy) do desc "A property that raises an Exception when you test if it's insync?" def sync end def retrieve :absent end def insync?(reference_value) raise Exception.new('slithy') end end end stubProvider end context "interaction of ensure with other properties" do def an_ensurable_resource_reacting_as(behaviors) stub_type = Class.new(Puppet::Type) stub_type.class_eval do initvars ensurable do def sync (@resource.behaviors[:on_ensure] || proc {}).call end def insync?(value) @resource.behaviors[:ensure_insync?] end def should_to_s(value) (@resource.behaviors[:on_should_to_s] || proc { "'#{value}'" }).call end end newparam(:name) do desc "The name var" isnamevar end newproperty(:prop) do newvalue("new") do #noop end def retrieve "old" end end attr_reader :behaviors def initialize(options) @behaviors = options.delete(:behaviors) super end def exists? @behaviors[:present?] end def present?(resource) @behaviors[:present?] end def self.name "Testing" end end stub_type.new(:behaviors => behaviors, :ensure => :present, :name => "testing", :prop => "new") end it "ensure errors means that the rest doesn't happen" do resource = an_ensurable_resource_reacting_as(:ensure_insync? => false, :on_ensure => proc { raise StandardError }, :present? => true) status = @harness.evaluate(resource) expect(status.events.length).to eq(1) expect(status.events[0].property).to eq('ensure') expect(status.events[0].name.to_s).to eq('Testing_created') expect(status.events[0].status).to eq('failure') end it "ensure fails completely means that the rest doesn't happen" do resource = an_ensurable_resource_reacting_as(:ensure_insync? => false, :on_ensure => proc { raise Exception }, :present? => false) expect do @harness.evaluate(resource) end.to raise_error(Exception) expect(@logs.first.message).to eq("change from 'absent' to 'present' failed: Exception") expect(@logs.first.level).to eq(:err) end it "ensure succeeds means that the rest doesn't happen" do resource = an_ensurable_resource_reacting_as(:ensure_insync? => false, :on_ensure => proc { }, :present? => true) status = @harness.evaluate(resource) expect(status.events.length).to eq(1) expect(status.events[0].property).to eq('ensure') expect(status.events[0].name.to_s).to eq('Testing_created') expect(status.events[0].status).to eq('success') end it "ensure is in sync means that the rest *does* happen" do resource = an_ensurable_resource_reacting_as(:ensure_insync? => true, :present? => true) status = @harness.evaluate(resource) expect(status.events.length).to eq(1) expect(status.events[0].property).to eq('prop') expect(status.events[0].name.to_s).to eq('prop_changed') expect(status.events[0].status).to eq('success') end it "ensure is in sync but resource not present, means that the rest doesn't happen" do resource = an_ensurable_resource_reacting_as(:ensure_insync? => true, :present? => false) status = @harness.evaluate(resource) expect(status.events).to be_empty end it "ensure errors in message still get a log entry" do resource = an_ensurable_resource_reacting_as(:ensure_insync? => false, :on_ensure => proc { raise StandardError }, :on_should_to_s => proc { raise StandardError }, :present? => true) status = @harness.evaluate(resource) expect(status.events.length).to eq(2) testing_errors = status.events.find_all { |x| x.name.to_s == "Testing_created" } resource_errors = status.events.find_all { |x| x.name.to_s == "resource_error" } expect(testing_errors.length).to eq(1) expect(resource_errors.length).to eq(1) expect(testing_errors[0].message).not_to be_nil expect(resource_errors[0].message).not_to eq("Puppet::Util::Log requires a message") end end describe "when a caught error occurs" do before :each do stub_provider = make_stub_provider resource = stub_provider.new :name => 'name', :foo => 1, :bar => 2 resource.expects(:err).never @status = @harness.evaluate(resource) end it "should record previous successful events" do expect(@status.events[0].property).to eq('foo') expect(@status.events[0].status).to eq('success') end it "should record a failure event" do expect(@status.events[1].property).to eq('bar') expect(@status.events[1].status).to eq('failure') end end describe "when an Exception occurs during sync" do before :each do stub_provider = make_stub_provider @resource = stub_provider.new :name => 'name', :baz => 1 @resource.expects(:err).never end it "should log and pass the exception through" do expect { @harness.evaluate(@resource) }.to raise_error(Exception, /baz/) expect(@logs.first.message).to eq("change from 'absent' to 1 failed: baz") expect(@logs.first.level).to eq(:err) end end describe "when a StandardError exception occurs during insync?" do before :each do stub_provider = make_stub_provider @resource = stub_provider.new :name => 'name', :brillig => 1 @resource.expects(:err).never end it "should record a failure event" do @status = @harness.evaluate(@resource) expect(@status.events[0].name.to_s).to eq('brillig_changed') expect(@status.events[0].property).to eq('brillig') expect(@status.events[0].status).to eq('failure') end end describe "when an Exception occurs during insync?" do before :each do stub_provider = make_stub_provider @resource = stub_provider.new :name => 'name', :slithy => 1 @resource.expects(:err).never end it "should log and pass the exception through" do expect { @harness.evaluate(@resource) }.to raise_error(Exception, /slithy/) expect(@logs.first.message).to eq("change from 'absent' to 1 failed: slithy") expect(@logs.first.level).to eq(:err) end end describe "when auditing" do it "should not call insync? on parameters that are merely audited" do stub_provider = make_stub_provider resource = stub_provider.new :name => 'name', :audit => ['foo'] resource.property(:foo).expects(:insync?).never status = @harness.evaluate(resource) expect(status.events).to be_empty end it "should be able to audit a file's group" do # see bug #5710 test_file = tmpfile('foo') File.open(test_file, 'w').close resource = Puppet::Type.type(:file).new :path => test_file, :audit => ['group'], :backup => false resource.expects(:err).never # make sure no exceptions get swallowed status = @harness.evaluate(resource) status.events.each do |event| expect(event.status).to != 'failure' end end it "should not ignore microseconds when auditing a file's mtime" do test_file = tmpfile('foo') File.open(test_file, 'w').close resource = Puppet::Type.type(:file).new :path => test_file, :audit => ['mtime'], :backup => false # construct a property hash with nanosecond resolution as would be # found on an ext4 file system time_with_nsec_resolution = Time.at(1000, 123456.999) current_from_filesystem = {:mtime => time_with_nsec_resolution} # construct a property hash with a 1 microsecond difference from above time_with_usec_resolution = Time.at(1000, 123457.000) historical_from_state_yaml = {:mtime => time_with_usec_resolution} # set up the sequence of stubs; yeah, this is pretty # brittle, so this might need to be adjusted if the # resource_harness logic changes resource.expects(:retrieve).returns(current_from_filesystem) Puppet::Util::Storage.stubs(:cache).with(resource). returns(historical_from_state_yaml).then. returns(current_from_filesystem).then. returns(current_from_filesystem) # there should be an audit change recorded, since the two # timestamps differ by at least 1 microsecond status = @harness.evaluate(resource) expect(status.events).not_to be_empty status.events.each do |event| expect(event.message).to match(/audit change: previously recorded/) end end it "should ignore nanoseconds when auditing a file's mtime" do test_file = tmpfile('foo') File.open(test_file, 'w').close resource = Puppet::Type.type(:file).new :path => test_file, :audit => ['mtime'], :backup => false # construct a property hash with nanosecond resolution as would be # found on an ext4 file system time_with_nsec_resolution = Time.at(1000, 123456.789) current_from_filesystem = {:mtime => time_with_nsec_resolution} # construct a property hash with the same timestamp as above, # truncated to microseconds, as would be read back from state.yaml time_with_usec_resolution = Time.at(1000, 123456.000) historical_from_state_yaml = {:mtime => time_with_usec_resolution} # set up the sequence of stubs; yeah, this is pretty # brittle, so this might need to be adjusted if the # resource_harness logic changes resource.expects(:retrieve).returns(current_from_filesystem) Puppet::Util::Storage.stubs(:cache).with(resource). returns(historical_from_state_yaml).then. returns(current_from_filesystem).then. returns(current_from_filesystem) # there should be no audit change recorded, despite the # slight difference in the two timestamps status = @harness.evaluate(resource) status.events.each do |event| expect(event.message).not_to match(/audit change: previously recorded/) end end end describe "handling sensitive properties" do describe 'when syncing' do let(:test_file) do tmpfile('foo').tap do |path| File.open(path, 'w') { |fh| fh.write("goodbye world") } end end let(:resource) do Puppet::Type.type(:file).new(:path => test_file, :backup => false, :content => "hello world").tap do |r| r.parameter(:content).sensitive = true end end it "redacts event messages for sensitive properties" do status = @harness.evaluate(resource) sync_event = status.events[0] expect(sync_event.message).to eq 'changed [redacted] to [redacted]' end it "redacts event contents for sensitive properties" do status = @harness.evaluate(resource) sync_event = status.events[0] expect(sync_event.previous_value).to eq '[redacted]' expect(sync_event.desired_value).to eq '[redacted]' end it "redacts event messages for sensitive properties when simulating noop changes" do resource[:noop] = true status = @harness.evaluate(resource) sync_event = status.events[0] expect(sync_event.message).to eq 'current_value [redacted], should be [redacted] (noop)' end describe 'auditing' do before do resource[:audit] = ['content'] end it "redacts notices when a parameter is newly audited" do resource.property(:content).expects(:notice).with("audit change: newly-recorded value [redacted]") @harness.evaluate(resource) end it "redacts event messages for sensitive properties" do Puppet::Util::Storage.stubs(:cache).with(resource).returns({:content => "historical world"}) status = @harness.evaluate(resource) sync_event = status.events[0] expect(sync_event.message).to eq 'changed [redacted] to [redacted] (previously recorded value was [redacted])' end it "redacts audit event messages for sensitive properties when simulating noop changes" do Puppet::Util::Storage.stubs(:cache).with(resource).returns({:content => "historical world"}) resource[:noop] = true status = @harness.evaluate(resource) sync_event = status.events[0] expect(sync_event.message).to eq 'current_value [redacted], should be [redacted] (noop) (previously recorded value was [redacted])' end it "redacts event contents for sensitive properties" do Puppet::Util::Storage.stubs(:cache).with(resource).returns({:content => "historical world"}) status = @harness.evaluate(resource) sync_event = status.events[0] expect(sync_event.historical_value).to eq '[redacted]' end end end describe 'handling errors' do it "redacts event messages generated when syncing a param raises a StandardError" do stub_provider = make_stub_provider resource = stub_provider.new :name => 'name', :bar => 1 resource.parameter(:bar).sensitive = true status = @harness.evaluate(resource) error_event = status.events[0] expect(error_event.message).to eq "change from [redacted] to [redacted] failed: bar" end it "redacts event messages generated when syncing a param raises an Exception" do stub_provider = make_stub_provider resource = stub_provider.new :name => 'name', :baz => 1 resource.parameter(:baz).sensitive = true expect { @harness.evaluate(resource) }.to raise_error(Exception, 'baz') expect(@logs.first.message).to eq "change from [redacted] to [redacted] failed: baz" end end end describe "when finding the schedule" do before do @catalog = Puppet::Resource::Catalog.new @resource.catalog = @catalog end it "should warn and return nil if the resource has no catalog" do @resource.catalog = nil @resource.expects(:warning) expect(@harness.schedule(@resource)).to be_nil end it "should return nil if the resource specifies no schedule" do expect(@harness.schedule(@resource)).to be_nil end it "should fail if the named schedule cannot be found" do @resource[:schedule] = "whatever" @resource.expects(:fail) @harness.schedule(@resource) end it "should return the named schedule if it exists" do sched = Puppet::Type.type(:schedule).new(:name => "sched") @catalog.add_resource(sched) @resource[:schedule] = "sched" expect(@harness.schedule(@resource).to_s).to eq(sched.to_s) end end describe "when determining if a resource is scheduled" do before do @catalog = Puppet::Resource::Catalog.new @resource.catalog = @catalog end it "should return true if 'ignoreschedules' is set" do Puppet[:ignoreschedules] = true @resource[:schedule] = "meh" expect(@harness).to be_scheduled(@resource) end it "should return true if the resource has no schedule set" do expect(@harness).to be_scheduled(@resource) end it "should return the result of matching the schedule with the cached 'checked' time if a schedule is set" do t = Time.now @harness.expects(:cached).with(@resource, :checked).returns(t) sched = Puppet::Type.type(:schedule).new(:name => "sched") @catalog.add_resource(sched) @resource[:schedule] = "sched" sched.expects(:match?).with(t.to_i).returns "feh" expect(@harness.scheduled?(@resource)).to eq("feh") end end it "should be able to cache data in the Storage module" do data = {} Puppet::Util::Storage.expects(:cache).with(@resource).returns data @harness.cache(@resource, :foo, "something") expect(data[:foo]).to eq("something") end it "should be able to retrieve data from the cache" do data = {:foo => "other"} Puppet::Util::Storage.expects(:cache).with(@resource).returns data expect(@harness.cached(@resource, :foo)).to eq("other") end end puppet-5.5.10/spec/unit/type/0000755005276200011600000000000013417162177015712 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/type/README.markdown0000644005276200011600000000014113417161721020401 0ustar jenkinsjenkinsResource Type Specs =================== Define specs for your resource types in this directory. puppet-5.5.10/spec/unit/type/component_spec.rb0000644005276200011600000000336313417161721021252 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' component = Puppet::Type.type(:component) describe component do it "should have a :name attribute" do expect(component.attrclass(:name)).not_to be_nil end it "should use Class as its type when a normal string is provided as the title" do expect(component.new(:name => "bar").ref).to eq("Class[Bar]") end it "should always produce a resource reference string as its title" do expect(component.new(:name => "bar").title).to eq("Class[Bar]") end it "should have a reference string equivalent to its title" do comp = component.new(:name => "Foo[bar]") expect(comp.title).to eq(comp.ref) end it "should not fail when provided an invalid value" do comp = component.new(:name => "Foo[bar]") expect { comp[:yayness] = "ey" }.not_to raise_error end it "should return previously provided invalid values" do comp = component.new(:name => "Foo[bar]") comp[:yayness] = "eh" expect(comp[:yayness]).to eq("eh") end it "should correctly support metaparameters" do comp = component.new(:name => "Foo[bar]", :require => "Foo[bar]") expect(comp.parameter(:require)).to be_instance_of(component.attrclass(:require)) end describe "when building up the path" do it "should produce the class name if the component models a class" do expect(component.new(:name => "Class[foo]").pathbuilder).to eq(["Foo"]) end it "should produce the class name even for the class named main" do expect(component.new(:name => "Class[main]").pathbuilder).to eq(["Main"]) end it "should produce a resource reference if the component does not model a class" do expect(component.new(:name => "Foo[bar]").pathbuilder).to eq(["Foo[bar]"]) end end end puppet-5.5.10/spec/unit/type/file/0000755005276200011600000000000013417162177016631 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/type/file/checksum_value_spec.rb0000644005276200011600000002335413417161721023167 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:file).attrclass(:checksum_value), :uses_checksums => true do include PuppetSpec::Files include_context 'with supported checksum types' let(:path) { tmpfile('foo_bar') } let(:source_file) { file_containing('temp_foo', 'nothing at all') } let(:environment) { Puppet::Node::Environment.create(:testing, []) } let(:catalog) { Puppet::Resource::Catalog.new(:test, environment) } let(:resource) { Puppet::Type.type(:file).new(:path => path, :catalog => catalog) } it "should be a property" do expect(described_class.superclass).to eq(Puppet::Property) end describe "when retrieving the current checksum_value" do let(:checksum_value) { described_class.new(:resource => resource) } it "should not compute a checksum if source is absent" do resource.expects(:stat).never expect(checksum_value.retrieve).to be_nil end describe "when using a source" do before do resource[:source] = source_file end it "should return :absent if the target does not exist" do resource.expects(:stat).returns nil expect(checksum_value.retrieve).to eq(:absent) end it "should not manage content on directories" do stat = mock 'stat', :ftype => "directory" resource.expects(:stat).returns stat expect(checksum_value.retrieve).to be_nil end it "should not manage content on links" do stat = mock 'stat', :ftype => "link" resource.expects(:stat).returns stat expect(checksum_value.retrieve).to be_nil end it "should always return the checksum as a string" do resource[:checksum] = :mtime stat = mock 'stat', :ftype => "file" resource.expects(:stat).returns stat time = Time.now resource.parameter(:checksum).expects(:mtime_file).with(resource[:path]).returns time expect(checksum_value.retrieve).to eq(time.to_s) end end with_digest_algorithms do it "should return the checksum of the target if it exists and is a normal file" do stat = mock 'stat', :ftype => "file" resource.expects(:stat).returns stat resource.parameter(:checksum).expects("#{digest_algorithm}_file".intern).with(resource[:path]).returns "mysum" resource[:source] = source_file expect(checksum_value.retrieve).to eq("mysum") end end end describe "when testing whether the checksum_value is in sync" do let(:checksum_value) { described_class.new(:resource => resource) } before do resource[:ensure] = :file end it "should return true if source is not specified" do checksum_value.should = "foo" expect(checksum_value).to be_safe_insync("whatever") end describe "when a source is provided" do before do resource[:source] = source_file end with_digest_algorithms do before(:each) do resource[:checksum] = digest_algorithm end it "should return true if the resource shouldn't be a regular file" do resource.expects(:should_be_file?).returns false checksum_value.should = "foo" expect(checksum_value).to be_safe_insync("whatever") end it "should return false if the current checksum_value is :absent" do checksum_value.should = "foo" expect(checksum_value).not_to be_safe_insync(:absent) end it "should return false if the file should be a file but is not present" do resource.expects(:should_be_file?).returns true checksum_value.should = "foo" expect(checksum_value).not_to be_safe_insync(:absent) end describe "and the file exists" do before do resource.stubs(:stat).returns mock("stat") checksum_value.should = "somechecksum" end it "should return false if the current checksum_value is different from the desired checksum_value" do expect(checksum_value).not_to be_safe_insync("otherchecksum") end it "should return true if the current checksum_value is the same as the desired checksum_value" do expect(checksum_value).to be_safe_insync("somechecksum") end it "should include the diff module" do expect(checksum_value.respond_to?("diff")).to eq(false) end [true, false].product([true, false]).each do |cfg, param| describe "and Puppet[:show_diff] is #{cfg} and show_diff => #{param}" do before do Puppet[:show_diff] = cfg resource.stubs(:show_diff?).returns param resource[:loglevel] = "debug" end if cfg and param it "should display a diff" do checksum_value.expects(:diff).returns("my diff").once checksum_value.expects(:debug).with("\nmy diff").once expect(checksum_value).not_to be_safe_insync("otherchecksum") end else it "should not display a diff" do checksum_value.expects(:diff).never expect(checksum_value).not_to be_safe_insync("otherchecksum") end end end end end end let(:saved_time) { Time.now } [:ctime, :mtime].each do |time_stat| [["older", -1, false], ["same", 0, true], ["newer", 1, true]].each do |compare, target_time, success| describe "with #{compare} target #{time_stat} compared to source" do before do resource[:checksum] = time_stat checksum_value.should = saved_time.to_s end it "should return #{success}" do if success expect(checksum_value).to be_safe_insync((saved_time+target_time).to_s) else expect(checksum_value).not_to be_safe_insync((saved_time+target_time).to_s) end end end end describe "with #{time_stat}" do before do resource[:checksum] = time_stat end it "should not be insync if trying to create it" do checksum_value.should = saved_time.to_s expect(checksum_value).not_to be_safe_insync(:absent) end it "should raise an error if checksum_value is not a checksum" do checksum_value.should = "some content" expect { checksum_value.safe_insync?(saved_time.to_s) }.to raise_error(/Resource with checksum_type #{time_stat} didn't contain a date in/) end it "should not be insync even if checksum_value is the absent symbol" do checksum_value.should = :absent expect(checksum_value).not_to be_safe_insync(:absent) end end end describe "and :replace is false" do before do resource.stubs(:replace?).returns false end it "should be insync if the file exists and the checksum_value is different" do resource.stubs(:stat).returns mock('stat') expect(checksum_value).to be_safe_insync("whatever") end it "should be insync if the file exists and the checksum_value is right" do resource.stubs(:stat).returns mock('stat') expect(checksum_value).to be_safe_insync("something") end it "should not be insync if the file does not exist" do checksum_value.should = "foo" expect(checksum_value).not_to be_safe_insync(:absent) end end end end describe "when testing whether the checksum_value is initialized in the resource and in sync" do CHECKSUM_TYPES_TO_TRY.each do |checksum_type, checksum| describe "sync with checksum type #{checksum_type} and the file exists" do before do @new_resource = Puppet::Type.type(:file).new :ensure => :file, :path => path, :catalog => catalog, :checksum_value => checksum, :checksum => checksum_type, :source => source_file @new_resource.stubs(:stat).returns mock('stat') end it "should return false if the current checksum_value is different from the desired checksum_value" do expect(@new_resource.parameters[:checksum_value]).not_to be_safe_insync("abcdef") end it "should return true if the current checksum_value is the same as the desired checksum_value" do expect(@new_resource.parameters[:checksum_value]).to be_safe_insync(checksum) end end end end describe "when changing the checksum_value" do let(:checksum_value) { described_class.new(:resource => resource) } before do resource.stubs(:[]).with(:path).returns "/boo" resource.stubs(:stat).returns "eh" end it "should raise if source is absent" do resource.expects(:write).never expect { checksum_value.sync }.to raise_error "checksum_value#sync should not be called without a source parameter" end describe "when using a source" do before do resource[:source] = source_file end it "should use the file's :write method to write the checksum_value" do resource.expects(:write).with(resource.parameter(:source)) checksum_value.sync end it "should return :file_changed if the file already existed" do resource.expects(:stat).returns "something" resource.stubs(:write) expect(checksum_value.sync).to eq(:file_changed) end it "should return :file_created if the file did not exist" do resource.expects(:stat).returns nil resource.stubs(:write) expect(checksum_value.sync).to eq(:file_created) end end end end puppet-5.5.10/spec/unit/type/file/ctime_spec.rb0000644005276200011600000000157013417161721021266 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:file).attrclass(:ctime) do require 'puppet_spec/files' include PuppetSpec::Files before do @filename = tmpfile('ctime') @resource = Puppet::Type.type(:file).new({:name => @filename}) end it "should be able to audit the file's ctime" do File.open(@filename, "w"){ } @resource[:audit] = [:ctime] # this .to_resource audit behavior is magical :-( expect(@resource.to_resource[:ctime]).to eq(Puppet::FileSystem.stat(@filename).ctime) end it "should return absent if auditing an absent file" do @resource[:audit] = [:ctime] expect(@resource.to_resource[:ctime]).to eq(:absent) end it "should prevent the user from trying to set the ctime" do expect { @resource[:ctime] = Time.now.to_s }.to raise_error(Puppet::Error, /ctime is read-only/) end end puppet-5.5.10/spec/unit/type/file/ensure_spec.rb0000644005276200011600000000673213417161721021473 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/type/file/ensure' describe Puppet::Type::File::Ensure do include PuppetSpec::Files let(:path) { tmpfile('file_ensure') } let(:resource) { Puppet::Type.type(:file).new(:ensure => 'file', :path => path, :replace => true) } let(:property) { resource.property(:ensure) } it "should be a subclass of Ensure" do expect(described_class.superclass).to eq(Puppet::Property::Ensure) end describe "when retrieving the current state" do it "should return :absent if the file does not exist" do resource.expects(:stat).returns nil expect(property.retrieve).to eq(:absent) end it "should return the current file type if the file exists" do stat = mock 'stat', :ftype => "directory" resource.expects(:stat).returns stat expect(property.retrieve).to eq(:directory) end end describe "when testing whether :ensure is in sync" do it "should always be in sync if replace is 'false' unless the file is missing" do property.should = :file resource.expects(:replace?).returns false expect(property.safe_insync?(:link)).to be_truthy end it "should be in sync if :ensure is set to :absent and the file does not exist" do property.should = :absent expect(property).to be_safe_insync(:absent) end it "should not be in sync if :ensure is set to :absent and the file exists" do property.should = :absent expect(property).not_to be_safe_insync(:file) end it "should be in sync if a normal file exists and :ensure is set to :present" do property.should = :present expect(property).to be_safe_insync(:file) end it "should be in sync if a directory exists and :ensure is set to :present" do property.should = :present expect(property).to be_safe_insync(:directory) end it "should be in sync if a symlink exists and :ensure is set to :present" do property.should = :present expect(property).to be_safe_insync(:link) end it "should not be in sync if :ensure is set to :file and a directory exists" do property.should = :file expect(property).not_to be_safe_insync(:directory) end end describe "#sync" do context "directory" do before :each do resource[:ensure] = :directory end it "should raise if the parent directory doesn't exist" do newpath = File.join(path, 'nonexistentparent', 'newdir') resource[:path] = newpath expect { property.sync }.to raise_error(Puppet::Error, /Cannot create #{newpath}; parent directory #{File.dirname(newpath)} does not exist/) end it "should accept octal mode as fixnum" do resource[:mode] = '0700' resource.expects(:property_fix) Dir.expects(:mkdir).with(path, 0700) property.sync end it "should accept octal mode as string" do resource[:mode] = "700" resource.expects(:property_fix) Dir.expects(:mkdir).with(path, 0700) property.sync end it "should accept octal mode as string with leading zero" do resource[:mode] = "0700" resource.expects(:property_fix) Dir.expects(:mkdir).with(path, 0700) property.sync end it "should accept symbolic mode" do resource[:mode] = "u=rwx,go=x" resource.expects(:property_fix) Dir.expects(:mkdir).with(path, 0711) property.sync end end end end puppet-5.5.10/spec/unit/type/file/group_spec.rb0000644005276200011600000000346713417161721021330 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:file).attrclass(:group) do include PuppetSpec::Files let(:path) { tmpfile('mode_spec') } let(:resource) { Puppet::Type.type(:file).new :path => path, :group => 'users' } let(:group) { resource.property(:group) } before :each do # If the provider was already loaded without root, it won't have the # feature, so we have to add it here to test. Puppet::Type.type(:file).defaultprovider.has_feature :manages_ownership end describe "#insync?" do before :each do resource[:group] = ['foos', 'bars'] resource.provider.stubs(:name2gid).with('foos').returns 1001 resource.provider.stubs(:name2gid).with('bars').returns 1002 end it "should fail if a group's id can't be found by name" do resource.provider.stubs(:name2gid).returns nil expect { group.insync?(5) }.to raise_error(/Could not find group foos/) end it "should use the id for comparisons, not the name" do expect(group.insync?('foos')).to be_falsey end it "should return true if the current group is one of the desired group" do expect(group.insync?(1001)).to be_truthy end it "should return false if the current group is not one of the desired group" do expect(group.insync?(1003)).to be_falsey end end %w[is_to_s should_to_s].each do |prop_to_s| describe "##{prop_to_s}" do it "should use the name of the user if it can find it" do resource.provider.stubs(:gid2name).with(1001).returns 'foos' expect(group.send(prop_to_s, 1001)).to eq("'foos'") end it "should use the id of the user if it can't" do resource.provider.stubs(:gid2name).with(1001).returns nil expect(group.send(prop_to_s, 1001)).to eq('1001') end end end end puppet-5.5.10/spec/unit/type/file/mode_spec.rb0000644005276200011600000001442413417161721021113 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:file).attrclass(:mode) do include PuppetSpec::Files let(:path) { tmpfile('mode_spec') } let(:resource) { Puppet::Type.type(:file).new :path => path, :mode => '0644' } let(:mode) { resource.property(:mode) } describe "#validate" do it "should reject non-string values" do expect { mode.value = 0755 }.to raise_error(Puppet::Error, /The file mode specification must be a string, not '(?:Fixnum|Integer)'/) end it "should accept values specified as octal numbers in strings" do expect { mode.value = '0755' }.not_to raise_error end it "should accept valid symbolic strings" do expect { mode.value = 'g+w,u-x' }.not_to raise_error end it "should not accept strings other than octal numbers" do expect do mode.value = 'readable please!' end.to raise_error(Puppet::Error, /The file mode specification is invalid/) end end describe "#munge" do # This is sort of a redundant test, but its spec is important. it "should return the value as a string" do expect(mode.munge('0644')).to be_a(String) end it "should accept strings as arguments" do expect(mode.munge('0644')).to eq('644') end it "should accept symbolic strings as arguments and return them intact" do expect(mode.munge('u=rw,go=r')).to eq('u=rw,go=r') end it "should accept integers are arguments" do expect(mode.munge(0644)).to eq('644') end end describe "#dirmask" do before :each do Dir.mkdir(path) end it "should add execute bits corresponding to read bits for directories" do expect(mode.dirmask('0644')).to eq('755') end it "should not add an execute bit when there is no read bit" do expect(mode.dirmask('0600')).to eq('700') end it "should not add execute bits for files that aren't directories" do resource[:path] = tmpfile('other_file') expect(mode.dirmask('0644')).to eq('0644') end end describe "#insync?" do it "should return true if the mode is correct" do FileUtils.touch(path) expect(mode).to be_insync('644') end it "should return false if the mode is incorrect" do FileUtils.touch(path) expect(mode).to_not be_insync('755') end it "should return true if the file is a link and we are managing links", :if => Puppet.features.manages_symlinks? do Puppet::FileSystem.symlink('anything', path) expect(mode).to be_insync('644') end describe "with a symbolic mode" do let(:resource_sym) { Puppet::Type.type(:file).new :path => path, :mode => 'u+w,g-w' } let(:mode_sym) { resource_sym.property(:mode) } it "should return true if the mode matches, regardless of other bits" do FileUtils.touch(path) expect(mode_sym).to be_insync('644') end it "should return false if the mode requires 0's where there are 1's" do FileUtils.touch(path) expect(mode_sym).to_not be_insync('624') end it "should return false if the mode requires 1's where there are 0's" do FileUtils.touch(path) expect(mode_sym).to_not be_insync('044') end end end describe "#retrieve" do it "should return absent if the resource doesn't exist" do resource[:path] = File.expand_path("/does/not/exist") expect(mode.retrieve).to eq(:absent) end it "should retrieve the directory mode from the provider" do Dir.mkdir(path) mode.expects(:dirmask).with('644').returns '755' resource.provider.expects(:mode).returns '755' expect(mode.retrieve).to eq('755') end it "should retrieve the file mode from the provider" do FileUtils.touch(path) mode.expects(:dirmask).with('644').returns '644' resource.provider.expects(:mode).returns '644' expect(mode.retrieve).to eq('644') end end describe '#should_to_s' do describe 'with a 3-digit mode' do it 'returns a 4-digit mode with a leading zero' do expect(mode.should_to_s('755')).to eq("'0755'") end end describe 'with a 4-digit mode' do it 'returns the 4-digit mode when the first digit is a zero' do expect(mode.should_to_s('0755')).to eq("'0755'") end it 'returns the 4-digit mode when the first digit is not a zero' do expect(mode.should_to_s('1755')).to eq("'1755'") end end end describe '#is_to_s' do describe 'with a 3-digit mode' do it 'returns a 4-digit mode with a leading zero' do expect(mode.is_to_s('755')).to eq("'0755'") end end describe 'with a 4-digit mode' do it 'returns the 4-digit mode when the first digit is a zero' do expect(mode.is_to_s('0755')).to eq("'0755'") end it 'returns the 4-digit mode when the first digit is not a zero' do expect(mode.is_to_s('1755')).to eq("'1755'") end end describe 'when passed :absent' do it "returns 'absent'" do expect(mode.is_to_s(:absent)).to eq("'absent'") end end end describe "#sync with a symbolic mode" do let(:resource_sym) { Puppet::Type.type(:file).new :path => path, :mode => 'u+w,g-w' } let(:mode_sym) { resource_sym.property(:mode) } before { FileUtils.touch(path) } it "changes only the requested bits" do # lower nibble must be set to 4 for the sake of passing on Windows Puppet::FileSystem.chmod(0464, path) mode_sym.sync stat = Puppet::FileSystem.stat(path) expect((stat.mode & 0777).to_s(8)).to eq("644") end end describe '#sync with a symbolic mode of +X for a file' do let(:resource_sym) { Puppet::Type.type(:file).new :path => path, :mode => 'g+wX' } let(:mode_sym) { resource_sym.property(:mode) } before { FileUtils.touch(path) } it 'does not change executable bit if no executable bit is set' do Puppet::FileSystem.chmod(0644, path) mode_sym.sync stat = Puppet::FileSystem.stat(path) expect((stat.mode & 0777).to_s(8)).to eq('664') end it 'does change executable bit if an executable bit is set' do Puppet::FileSystem.chmod(0744, path) mode_sym.sync stat = Puppet::FileSystem.stat(path) expect((stat.mode & 0777).to_s(8)).to eq('774') end end end puppet-5.5.10/spec/unit/type/file/mtime_spec.rb0000644005276200011600000000157013417161721021300 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:file).attrclass(:mtime) do require 'puppet_spec/files' include PuppetSpec::Files before do @filename = tmpfile('mtime') @resource = Puppet::Type.type(:file).new({:name => @filename}) end it "should be able to audit the file's mtime" do File.open(@filename, "w"){ } @resource[:audit] = [:mtime] # this .to_resource audit behavior is magical :-( expect(@resource.to_resource[:mtime]).to eq(Puppet::FileSystem.stat(@filename).mtime) end it "should return absent if auditing an absent file" do @resource[:audit] = [:mtime] expect(@resource.to_resource[:mtime]).to eq(:absent) end it "should prevent the user from trying to set the mtime" do expect { @resource[:mtime] = Time.now.to_s }.to raise_error(Puppet::Error, /mtime is read-only/) end end puppet-5.5.10/spec/unit/type/file/owner_spec.rb0000644005276200011600000000323413417161721021316 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:file).attrclass(:owner) do include PuppetSpec::Files let(:path) { tmpfile('mode_spec') } let(:resource) { Puppet::Type.type(:file).new :path => path, :owner => 'joeuser' } let(:owner) { resource.property(:owner) } before :each do Puppet.features.stubs(:root?).returns(true) end describe "#insync?" do before :each do resource[:owner] = ['foo', 'bar'] resource.provider.stubs(:name2uid).with('foo').returns 1001 resource.provider.stubs(:name2uid).with('bar').returns 1002 end it "should fail if an owner's id can't be found by name" do resource.provider.stubs(:name2uid).returns nil expect { owner.insync?(5) }.to raise_error(/Could not find user foo/) end it "should use the id for comparisons, not the name" do expect(owner.insync?('foo')).to be_falsey end it "should return true if the current owner is one of the desired owners" do expect(owner.insync?(1001)).to be_truthy end it "should return false if the current owner is not one of the desired owners" do expect(owner.insync?(1003)).to be_falsey end end %w[is_to_s should_to_s].each do |prop_to_s| describe "##{prop_to_s}" do it "should use the name of the user if it can find it" do resource.provider.stubs(:uid2name).with(1001).returns 'foo' expect(owner.send(prop_to_s, 1001)).to eq("'foo'") end it "should use the id of the user if it can't" do resource.provider.stubs(:uid2name).with(1001).returns nil expect(owner.send(prop_to_s, 1001)).to eq('1001') end end end end puppet-5.5.10/spec/unit/type/file/selinux_spec.rb0000644005276200011600000000605413417161721021656 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' [:seluser, :selrole, :seltype, :selrange].each do |param| property = Puppet::Type.type(:file).attrclass(param) describe property do include PuppetSpec::Files before do @path = make_absolute("/my/file") @resource = Puppet::Type.type(:file).new :path => @path @sel = property.new :resource => @resource @sel.stubs(:normalize_selinux_category).with("s0").returns("s0") @sel.stubs(:normalize_selinux_category).with(nil).returns(nil) end it "retrieve on #{param} should return :absent if the file isn't statable" do @resource.expects(:stat).returns nil expect(@sel.retrieve).to eq(:absent) end it "should retrieve nil for #{param} if there is no SELinux support" do stat = stub 'stat', :ftype => "foo" @resource.expects(:stat).returns stat @sel.expects(:get_selinux_current_context).with(@path).returns nil expect(@sel.retrieve).to be_nil end it "should retrieve #{param} if a SELinux context is found with a range" do stat = stub 'stat', :ftype => "foo" @resource.expects(:stat).returns stat @sel.expects(:get_selinux_current_context).with(@path).returns "user_u:role_r:type_t:s0" expectedresult = case param when :seluser; "user_u" when :selrole; "role_r" when :seltype; "type_t" when :selrange; "s0" end expect(@sel.retrieve).to eq(expectedresult) end it "should retrieve #{param} if a SELinux context is found without a range" do stat = stub 'stat', :ftype => "foo" @resource.expects(:stat).returns stat @sel.expects(:get_selinux_current_context).with(@path).returns "user_u:role_r:type_t" expectedresult = case param when :seluser; "user_u" when :selrole; "role_r" when :seltype; "type_t" when :selrange; nil end expect(@sel.retrieve).to eq(expectedresult) end it "should handle no default gracefully" do @sel.expects(:get_selinux_default_context).with(@path).returns nil expect(@sel.default).to be_nil end it "should be able to detect matchpathcon defaults" do @sel.stubs(:debug) @sel.expects(:get_selinux_default_context).with(@path).returns "user_u:role_r:type_t:s0" expectedresult = case param when :seluser; "user_u" when :selrole; "role_r" when :seltype; "type_t" when :selrange; "s0" end expect(@sel.default).to eq(expectedresult) end it "should return nil for defaults if selinux_ignore_defaults is true" do @resource[:selinux_ignore_defaults] = :true expect(@sel.default).to be_nil end it "should be able to set a new context" do @sel.should = %w{newone} @sel.expects(:set_selinux_context).with(@path, ["newone"], param) @sel.sync end it "should do nothing for safe_insync? if no SELinux support" do @sel.should = %{newcontext} @sel.expects(:selinux_support?).returns false expect(@sel.safe_insync?("oldcontext")).to eq(true) end end end puppet-5.5.10/spec/unit/type/file/type_spec.rb0000644005276200011600000000070213417161721021142 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:file).attrclass(:type) do require 'puppet_spec/files' include PuppetSpec::Files before do @filename = tmpfile('type') @resource = Puppet::Type.type(:file).new({:name => @filename}) end it "should prevent the user from trying to set the type" do expect { @resource[:type] = "fifo" }.to raise_error(Puppet::Error, /type is read-only/) end end puppet-5.5.10/spec/unit/type/file/checksum_spec.rb0000644005276200011600000000714513417161722021774 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' checksum = Puppet::Type.type(:file).attrclass(:checksum) describe checksum do before do @path = Puppet.features.microsoft_windows? ? "c:/foo/bar" : "/foo/bar" @resource = Puppet::Type.type(:file).new :path => @path @checksum = @resource.parameter(:checksum) end it "should be a parameter" do expect(checksum.superclass).to eq(Puppet::Parameter) end it "should use its current value when asked to sum content" do @checksum.value = :md5lite @checksum.expects(:md5lite).with("foobar").returns "yay" @checksum.sum("foobar") end it "should use :md5 to sum when no value is set" do @checksum.expects(:md5).with("foobar").returns "yay" @checksum.sum("foobar") end it "should return the summed contents with a checksum label" do sum = Digest::MD5.hexdigest("foobar") @resource[:checksum] = :md5 expect(@checksum.sum("foobar")).to eq("{md5}#{sum}") end it "when using digest_algorithm 'sha256' should return the summed contents with a checksum label" do sum = Digest::SHA256.hexdigest("foobar") @resource[:checksum] = :sha256 expect(@checksum.sum("foobar")).to eq("{sha256}#{sum}") end it "when using digest_algorithm 'sha512' should return the summed contents with a checksum label" do sum = Digest::SHA512.hexdigest("foobar") @resource[:checksum] = :sha512 expect(@checksum.sum("foobar")).to eq("{sha512}#{sum}") end it "when using digest_algorithm 'sha384' should return the summed contents with a checksum label" do sum = Digest::SHA384.hexdigest("foobar") @resource[:checksum] = :sha384 expect(@checksum.sum("foobar")).to eq("{sha384}#{sum}") end it "should use :md5 as its default type" do expect(@checksum.default).to eq(:md5) end it "should use its current value when asked to sum a file's content" do @checksum.value = :md5lite @checksum.expects(:md5lite_file).with(@path).returns "yay" @checksum.sum_file(@path) end it "should use :md5 to sum a file when no value is set" do @checksum.expects(:md5_file).with(@path).returns "yay" @checksum.sum_file(@path) end it "should convert all sums to strings when summing files" do @checksum.value = :mtime @checksum.expects(:mtime_file).with(@path).returns Time.now expect { @checksum.sum_file(@path) }.not_to raise_error end it "should return the summed contents of a file with a checksum label" do @resource[:checksum] = :md5 @checksum.expects(:md5_file).returns "mysum" expect(@checksum.sum_file(@path)).to eq("{md5}mysum") end it "should return the summed contents of a stream with a checksum label" do @resource[:checksum] = :md5 @checksum.expects(:md5_stream).returns "mysum" expect(@checksum.sum_stream).to eq("{md5}mysum") end it "should yield the sum_stream block to the underlying checksum" do @resource[:checksum] = :md5 @checksum.expects(:md5_stream).yields("something").returns("mysum") @checksum.sum_stream do |sum| expect(sum).to eq("something") end end it 'should use values allowed by the supported_checksum_types setting' do values = checksum.value_collection.values.reject {|v| v == :none}.map {|v| v.to_s} Puppet.settings[:supported_checksum_types] = values expect(Puppet.settings[:supported_checksum_types]).to eq(values) end it 'rejects md5 checksums in FIPS mode' do Puppet::Util::Platform.stubs(:fips_enabled?).returns true expect { @resource[:checksum] = :md5 }.to raise_error(Puppet::ResourceError, /Parameter checksum failed.* MD5 is not supported in FIPS mode/) end end puppet-5.5.10/spec/unit/type/file/content_spec.rb0000644005276200011600000003434713417161722021650 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:file).attrclass(:content), :uses_checksums => true do include PuppetSpec::Files include_context 'with supported checksum types' let(:filename) { tmpfile('testfile') } let(:environment) { Puppet::Node::Environment.create(:testing, []) } let(:catalog) { Puppet::Resource::Catalog.new(:test, environment) } let(:resource) { Puppet::Type.type(:file).new :path => filename, :catalog => catalog } before do File.open(filename, 'w') {|f| f.write "initial file content"} described_class.stubs(:standalone?).returns(false) end around do |example| Puppet.override(:environments => Puppet::Environments::Static.new(environment)) do example.run end end describe "when determining the actual content to write" do let(:content) { described_class.new(:resource => resource) } it "should use the set content if available" do content.should = "ehness" expect(content.actual_content).to eq("ehness") end it "should not use the content from the source if the source is set" do source = mock 'source' resource.expects(:parameter).never.with(:source).returns source expect(content.actual_content).to be_nil end end describe "when setting the desired content" do let(:content) { described_class.new(:resource => resource) } before do Puppet::Type.type(:file).any_instance.stubs(:file).returns('my/file.pp') Puppet::Type.type(:file).any_instance.stubs(:line).returns 5 end it "should make the actual content available via an attribute" do content.should = "this is some content" expect(content.actual_content).to eq("this is some content") end with_digest_algorithms do it "should store the checksum as the desired content" do d = digest("this is some content") content.should = "this is some content" expect(content.should).to eq("{#{digest_algorithm}}#{d}") end it "should not checksum 'absent'" do content.should = :absent expect(content.should).to eq(:absent) end it "should accept a checksum as the desired content" do d = digest("this is some content") string = "{#{digest_algorithm}}#{d}" content.should = string expect(content.should).to eq(string) end end it "should convert the value to ASCII-8BIT", :if => "".respond_to?(:encode) do content.should= "Let's make a \u{2603}" expect(content.actual_content).to eq("Let's make a \xE2\x98\x83".force_encoding(Encoding::ASCII_8BIT)) end end describe "when retrieving the current content" do let(:content) { described_class.new(:resource => resource) } it "should return :absent if the file does not exist" do resource.expects(:stat).returns nil expect(content.retrieve).to eq(:absent) end it "should not manage content on directories" do stat = mock 'stat', :ftype => "directory" resource.expects(:stat).returns stat expect(content.retrieve).to be_nil end it "should not manage content on links" do stat = mock 'stat', :ftype => "link" resource.expects(:stat).returns stat expect(content.retrieve).to be_nil end it "should always return the checksum as a string" do resource[:checksum] = :mtime stat = mock 'stat', :ftype => "file" resource.expects(:stat).returns stat time = Time.now resource.parameter(:checksum).expects(:mtime_file).with(resource[:path]).returns time expect(content.retrieve).to eq("{mtime}#{time}") end with_digest_algorithms do it "should return the checksum of the file if it exists and is a normal file" do stat = mock 'stat', :ftype => "file" resource.expects(:stat).returns stat resource.parameter(:checksum).expects("#{digest_algorithm}_file".intern).with(resource[:path]).returns "mysum" expect(content.retrieve).to eq("{#{digest_algorithm}}mysum") end end end describe "when testing whether the content is in sync" do let(:content) { described_class.new(:resource => resource) } before do resource[:ensure] = :file end with_digest_algorithms do before(:each) do resource[:checksum] = digest_algorithm end it "should return true if the resource shouldn't be a regular file" do resource.expects(:should_be_file?).returns false content.should = "foo" expect(content).to be_safe_insync("whatever") end it "should warn that no content will be synced to links when ensure is :present" do resource[:ensure] = :present resource[:content] = 'foo' resource.stubs(:should_be_file?).returns false resource.stubs(:stat).returns mock("stat", :ftype => "link") resource.expects(:warning).with {|msg| msg =~ /Ensure set to :present but file type is/} content.insync? :present end it "should return false if the current content is :absent" do content.should = "foo" expect(content).not_to be_safe_insync(:absent) end it "should return false if the file should be a file but is not present" do resource.expects(:should_be_file?).returns true content.should = "foo" expect(content).not_to be_safe_insync(:absent) end describe "and the file exists" do before do resource.stubs(:stat).returns mock("stat") content.should = "some content" end it "should return false if the current contents are different from the desired content" do expect(content).not_to be_safe_insync("other content") end it "should return true if the sum for the current contents is the same as the sum for the desired content" do expect(content).to be_safe_insync("{#{digest_algorithm}}" + digest("some content")) end it "should include the diff module" do expect(content.respond_to?("diff")).to eq(false) end describe "showing the diff" do it "doesn't show the diff when #show_diff? is false" do content.expects(:show_diff?).returns false content.expects(:diff).never expect(content).not_to be_safe_insync("other content") end describe "and #show_diff? is true" do before do content.expects(:show_diff?).returns true resource[:loglevel] = "debug" end it "prints the diff" do content.expects(:diff).returns("my diff").once content.expects(:debug).with("\nmy diff").once expect(content).not_to be_safe_insync("other content") end it "redacts the diff when the property is sensitive" do content.sensitive = true content.expects(:diff).returns("my diff").never content.expects(:debug).with("[diff redacted]").once expect(content).not_to be_safe_insync("other content") end end end end end let(:saved_time) { Time.now } [:ctime, :mtime].each do |time_stat| [["older", -1, false], ["same", 0, true], ["newer", 1, true]].each do |compare, target_time, success| describe "with #{compare} target #{time_stat} compared to source" do before do resource[:checksum] = time_stat resource[:source] = make_absolute('/temp/foo') content.should = "{#{time_stat}}#{saved_time}" end it "should return #{success}" do if success expect(content).to be_safe_insync("{#{time_stat}}#{saved_time+target_time}") else expect(content).not_to be_safe_insync("{#{time_stat}}#{saved_time+target_time}") end end end end describe "with #{time_stat}" do before do resource[:checksum] = time_stat resource[:source] = make_absolute('/temp/foo') end it "should not be insync if trying to create it" do content.should = "{#{time_stat}}#{saved_time}" expect(content).not_to be_safe_insync(:absent) end it "should raise an error if content is not a checksum" do content.should = "some content" expect { content.safe_insync?("{#{time_stat}}#{saved_time}") }.to raise_error(/Resource with checksum_type #{time_stat} didn't contain a date in/) end it "should not be insync even if content is the absent symbol" do content.should = :absent expect(content).not_to be_safe_insync(:absent) end it "should warn that no content will be synced to links when ensure is :present" do resource[:ensure] = :present resource[:content] = 'foo' resource.stubs(:should_be_file?).returns false resource.stubs(:stat).returns mock("stat", :ftype => "link") resource.expects(:warning).with {|msg| msg =~ /Ensure set to :present but file type is/} content.insync? :present end end end describe "and :replace is false" do before do resource.stubs(:replace?).returns false end it "should be insync if the file exists and the content is different" do resource.stubs(:stat).returns mock('stat') expect(content).to be_safe_insync("whatever") end it "should be insync if the file exists and the content is right" do resource.stubs(:stat).returns mock('stat') expect(content).to be_safe_insync("something") end it "should not be insync if the file does not exist" do content.should = "foo" expect(content).not_to be_safe_insync(:absent) end end end describe "when testing whether the content is initialized in the resource and in sync" do CHECKSUM_TYPES_TO_TRY.each do |checksum_type, checksum| describe "sync with checksum type #{checksum_type} and the file exists" do before do @new_resource = Puppet::Type.type(:file).new :ensure => :file, :path => filename, :catalog => catalog, :content => CHECKSUM_PLAINTEXT, :checksum => checksum_type @new_resource.stubs(:stat).returns mock('stat') end it "should return false if the sum for the current contents are different from the desired content" do expect(@new_resource.parameters[:content]).not_to be_safe_insync("other content") end it "should return true if the sum for the current contents is the same as the sum for the desired content" do expect(@new_resource.parameters[:content]).to be_safe_insync("{#{checksum_type}}#{checksum}") end end end end describe "determining if a diff should be shown" do let(:content) { described_class.new(:resource => resource) } before do Puppet[:show_diff] = true resource[:show_diff] = true end it "is true if there are changes and the global and per-resource show_diff settings are true" do expect(content.show_diff?(true)).to be_truthy end it "is false if there are no changes" do expect(content.show_diff?(false)).to be_falsey end it "is false if show_diff is globally disabled" do Puppet[:show_diff] = false expect(content.show_diff?(false)).to be_falsey end it "is false if show_diff is disabled on the resource" do resource[:show_diff] = false expect(content.show_diff?(false)).to be_falsey end end describe "when changing the content" do let(:content) { described_class.new(:resource => resource) } before do resource.stubs(:[]).with(:path).returns "/boo" resource.stubs(:stat).returns "eh" end it "should use the file's :write method to write the content" do resource.expects(:write).with(content) content.sync end it "should return :file_changed if the file already existed" do resource.expects(:stat).returns "something" resource.stubs(:write) expect(content.sync).to eq(:file_changed) end it "should return :file_created if the file did not exist" do resource.expects(:stat).returns nil resource.stubs(:write) expect(content.sync).to eq(:file_created) end end describe "when writing" do let(:content) { described_class.new(:resource => resource) } let(:fh) { File.open(filename, 'wb') } before do Puppet::Type.type(:file).any_instance.stubs(:file).returns('my/file.pp') Puppet::Type.type(:file).any_instance.stubs(:line).returns 5 end it "should attempt to read from the filebucket if no actual content nor source exists" do content.should = "{md5}foo" content.resource.bucket.class.any_instance.stubs(:getfile).returns "foo" content.write(fh) fh.close end describe "from actual content" do before(:each) do content.stubs(:actual_content).returns("this is content") end it "should write to the given file handle" do fh = mock 'filehandle' fh.expects(:print).with("this is content") content.write(fh) end it "should return the current checksum value" do resource.parameter(:checksum).expects(:sum_stream).returns "checksum" expect(content.write(fh)).to eq("checksum") end end describe "from a file bucket" do it "should fail if a file bucket cannot be retrieved" do content.should = "{md5}foo" content.resource.expects(:bucket).returns nil expect { content.write(fh) }.to raise_error(Puppet::Error) end it "should fail if the file bucket cannot find any content" do content.should = "{md5}foo" bucket = stub 'bucket' content.resource.expects(:bucket).returns bucket bucket.expects(:getfile).with("foo").raises "foobar" expect { content.write(fh) }.to raise_error(Puppet::Error) end it "should write the returned content to the file" do content.should = "{md5}foo" bucket = stub 'bucket' content.resource.expects(:bucket).returns bucket bucket.expects(:getfile).with("foo").returns "mycontent" fh = mock 'filehandle' fh.expects(:print).with("mycontent") content.write(fh) end end end end puppet-5.5.10/spec/unit/type/file/source_spec.rb0000644005276200011600000005730613417161722021476 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'uri' require 'puppet/network/http_pool' #require 'puppet/network/resolver' describe Puppet::Type.type(:file).attrclass(:source), :uses_checksums => true do include PuppetSpec::Files include_context 'with supported checksum types' around :each do |example| Puppet.override(:environments => Puppet::Environments::Static.new) do example.run end end let(:filename) { tmpfile('file_source_validate') } let(:environment) { Puppet::Node::Environment.remote("myenv") } let(:catalog) { Puppet::Resource::Catalog.new(:test, environment) } let(:resource) { Puppet::Type.type(:file).new :path => filename, :catalog => catalog } before do @foobar = make_absolute("/foo/bar baz") @feebooz = make_absolute("/fee/booz baz") @foobar_uri = URI.unescape(Puppet::Util.path_to_uri(@foobar).to_s) @feebooz_uri = URI.unescape(Puppet::Util.path_to_uri(@feebooz).to_s) end it "should be a subclass of Parameter" do expect(described_class.superclass).to eq(Puppet::Parameter) end describe "#validate" do it "should fail if the set values are not URLs" do URI.expects(:parse).with('foo').raises RuntimeError expect(lambda { resource[:source] = %w{foo} }).to raise_error(Puppet::Error) end it "should fail if the URI is not a local file, file URI, or puppet URI" do expect(lambda { resource[:source] = %w{ftp://foo/bar} }).to raise_error(Puppet::Error, /Cannot use URLs of type 'ftp' as source for fileserving/) end it "should strip trailing forward slashes", :unless => Puppet.features.microsoft_windows? do resource[:source] = "/foo/bar\\//" expect(resource[:source]).to eq(%w{file:/foo/bar\\}) end it "should strip trailing forward and backslashes", :if => Puppet.features.microsoft_windows? do resource[:source] = "X:/foo/bar\\//" expect(resource[:source]).to eq(%w{file:/X:/foo/bar}) end it "should accept an array of sources" do resource[:source] = %w{file:///foo/bar puppet://host:8140/foo/bar} expect(resource[:source]).to eq(%w{file:///foo/bar puppet://host:8140/foo/bar}) end it "should accept file path characters that are not valid in URI" do resource[:source] = 'file:///foo bar' end it "should reject relative URI sources" do expect(lambda { resource[:source] = 'foo/bar' }).to raise_error(Puppet::Error) end it "should reject opaque sources" do expect(lambda { resource[:source] = 'mailto:foo@com' }).to raise_error(Puppet::Error) end it "should accept URI authority component" do resource[:source] = 'file://host/foo' expect(resource[:source]).to eq(%w{file://host/foo}) end it "should accept when URI authority is absent" do resource[:source] = 'file:///foo/bar' expect(resource[:source]).to eq(%w{file:///foo/bar}) end end describe "#munge" do it "should prefix file scheme to absolute paths" do resource[:source] = filename expect(resource[:source]).to eq([URI.unescape(Puppet::Util.path_to_uri(filename).to_s)]) end %w[file puppet].each do |scheme| it "should not prefix valid #{scheme} URIs" do resource[:source] = "#{scheme}:///foo bar" expect(resource[:source]).to eq(["#{scheme}:///foo bar"]) end end end describe "when returning the metadata" do before do @metadata = stub 'metadata', :source= => nil resource.stubs(:[]).with(:links).returns :manage resource.stubs(:[]).with(:source_permissions).returns :use resource.stubs(:[]).with(:checksum).returns :checksum end it "should return already-available metadata" do @source = described_class.new(:resource => resource) @source.metadata = "foo" expect(@source.metadata).to eq("foo") end it "should return nil if no @should value is set and no metadata is available" do @source = described_class.new(:resource => resource) expect(@source.metadata).to be_nil end it "should collect its metadata using the Metadata class if it is not already set" do @source = described_class.new(:resource => resource, :value => @foobar) Puppet::FileServing::Metadata.indirection.expects(:find).with do |uri, options| expect(uri).to eq @foobar_uri expect(options[:environment]).to eq environment expect(options[:links]).to eq :manage expect(options[:checksum_type]).to eq :checksum end.returns @metadata @source.metadata end it "should use the metadata from the first found source" do metadata = stub 'metadata', :source= => nil @source = described_class.new(:resource => resource, :value => [@foobar, @feebooz]) options = { :environment => environment, :links => :manage, :source_permissions => :use, :checksum_type => :checksum } Puppet::FileServing::Metadata.indirection.expects(:find).with(@foobar_uri, options).returns nil Puppet::FileServing::Metadata.indirection.expects(:find).with(@feebooz_uri, options).returns metadata expect(@source.metadata).to equal(metadata) end it "should store the found source as the metadata's source" do metadata = mock 'metadata' @source = described_class.new(:resource => resource, :value => @foobar) Puppet::FileServing::Metadata.indirection.expects(:find).with do |uri, options| expect(uri).to eq @foobar_uri expect(options[:environment]).to eq environment expect(options[:links]).to eq :manage expect(options[:checksum_type]).to eq :checksum end.returns metadata metadata.expects(:source=).with(@foobar_uri) @source.metadata end it "should fail intelligently if an exception is encountered while querying for metadata" do @source = described_class.new(:resource => resource, :value => @foobar) Puppet::FileServing::Metadata.indirection.expects(:find).with do |uri, options| expect(uri).to eq @foobar_uri expect(options[:environment]).to eq environment expect(options[:links]).to eq :manage expect(options[:checksum_type]).to eq :checksum end.raises RuntimeError @source.expects(:fail).raises ArgumentError expect { @source.metadata }.to raise_error(ArgumentError) end it "should fail if no specified sources can be found" do @source = described_class.new(:resource => resource, :value => @foobar) Puppet::FileServing::Metadata.indirection.expects(:find).with do |uri, options| expect(uri).to eq @foobar_uri expect(options[:environment]).to eq environment expect(options[:links]).to eq :manage expect(options[:checksum_type]).to eq :checksum end.returns nil @source.expects(:fail).raises RuntimeError expect { @source.metadata }.to raise_error(RuntimeError) end end it "should have a method for setting the desired values on the resource" do expect(described_class.new(:resource => resource)).to respond_to(:copy_source_values) end describe "when copying the source values" do before do Puppet::Type.type(:file).any_instance.stubs(:file).returns('my/file.pp') Puppet::Type.type(:file).any_instance.stubs(:line).returns 5 end before :each do @resource = Puppet::Type.type(:file).new :path => @foobar @source = described_class.new(:resource => @resource) @metadata = stub 'metadata', :owner => 100, :group => 200, :mode => "173", :checksum => "{md5}asdfasdf", :checksum_type => "md5", :ftype => "file", :source => @foobar @source.stubs(:metadata).returns @metadata Puppet.features.stubs(:root?).returns true end it "should not issue an error - except on Windows - if the source mode value is a Numeric" do @metadata.stubs(:mode).returns 0173 @resource[:source_permissions] = :use if Puppet::Util::Platform.windows? expect { @source.copy_source_values }.to raise_error("Should not have tried to use source owner/mode/group on Windows (file: my/file.pp, line: 5)") else expect { @source.copy_source_values }.not_to raise_error end end it "should not issue an error - except on Windows - if the source mode value is a String" do @metadata.stubs(:mode).returns "173" @resource[:source_permissions] = :use if Puppet::Util::Platform.windows? expect { @source.copy_source_values }.to raise_error("Should not have tried to use source owner/mode/group on Windows (file: my/file.pp, line: 5)") else expect { @source.copy_source_values }.not_to raise_error end end it "should fail if there is no metadata" do @source.stubs(:metadata).returns nil @source.expects(:devfail).raises ArgumentError expect { @source.copy_source_values }.to raise_error(ArgumentError) end it "should set :ensure to the file type" do @metadata.stubs(:ftype).returns "file" @source.copy_source_values expect(@resource[:ensure]).to eq(:file) end it "should not set 'ensure' if it is already set to 'absent'" do @metadata.stubs(:ftype).returns "file" @resource[:ensure] = :absent @source.copy_source_values expect(@resource[:ensure]).to eq(:absent) end describe "and the source is a file" do before do @metadata.stubs(:ftype).returns "file" Puppet.features.stubs(:microsoft_windows?).returns false end context "when source_permissions is `use`" do before :each do @resource[:source_permissions] = "use" @resource[:checksum] = :sha256 end it "should copy the metadata's owner, group, checksum, checksum_type, and mode to the resource if they are not set on the resource" do @source.copy_source_values expect(@resource[:owner]).to eq(100) expect(@resource[:group]).to eq(200) expect(@resource[:mode]).to eq("173") # Metadata calls it checksum and checksum_type, we call it content and checksum. expect(@resource[:content]).to eq(@metadata.checksum) expect(@resource[:checksum]).to eq(@metadata.checksum_type.to_sym) end it "should not copy the metadata's owner, group, checksum, checksum_type, and mode to the resource if they are already set" do @resource[:owner] = 1 @resource[:group] = 2 @resource[:mode] = '173' @resource[:content] = "foobar" @source.copy_source_values expect(@resource[:owner]).to eq(1) expect(@resource[:group]).to eq(2) expect(@resource[:mode]).to eq('173') expect(@resource[:content]).not_to eq(@metadata.checksum) expect(@resource[:checksum]).not_to eq(@metadata.checksum_type.to_sym) end describe "and puppet is not running as root" do before do Puppet.features.stubs(:root?).returns false end it "should not try to set the owner" do @source.copy_source_values expect(@resource[:owner]).to be_nil end it "should not try to set the group" do @source.copy_source_values expect(@resource[:group]).to be_nil end end end context "when source_permissions is `use_when_creating`" do before :each do @resource[:source_permissions] = "use_when_creating" Puppet.features.expects(:root?).returns true @source.stubs(:local?).returns(false) end context "when managing a new file" do it "should copy owner and group from local sources" do @source.stubs(:local?).returns true @source.copy_source_values expect(@resource[:owner]).to eq(100) expect(@resource[:group]).to eq(200) expect(@resource[:mode]).to eq("173") end it "copies the remote owner" do @source.copy_source_values expect(@resource[:owner]).to eq(100) end it "copies the remote group" do @source.copy_source_values expect(@resource[:group]).to eq(200) end it "copies the remote mode" do @source.copy_source_values expect(@resource[:mode]).to eq("173") end end context "when managing an existing file" do before :each do Puppet::FileSystem.stubs(:exist?).with(@resource[:path]).returns(true) end it "should not copy owner, group or mode from local sources" do @source.stubs(:local?).returns true @source.copy_source_values expect(@resource[:owner]).to be_nil expect(@resource[:group]).to be_nil expect(@resource[:mode]).to be_nil end it "preserves the local owner" do @source.copy_source_values expect(@resource[:owner]).to be_nil end it "preserves the local group" do @source.copy_source_values expect(@resource[:group]).to be_nil end it "preserves the local mode" do @source.copy_source_values expect(@resource[:mode]).to be_nil end end end context "when source_permissions is default" do before :each do @source.stubs(:local?).returns(false) Puppet.features.expects(:root?).returns true end it "should not copy owner, group or mode from local sources" do @source.stubs(:local?).returns true @source.copy_source_values expect(@resource[:owner]).to be_nil expect(@resource[:group]).to be_nil expect(@resource[:mode]).to be_nil end it "preserves the local owner" do @source.copy_source_values expect(@resource[:owner]).to be_nil end it "preserves the local group" do @source.copy_source_values expect(@resource[:group]).to be_nil end it "preserves the local mode" do @source.copy_source_values expect(@resource[:mode]).to be_nil end end end describe "and the source is a link" do before do Puppet.features.stubs(:microsoft_windows?).returns false end it "should set the target to the link destination" do @metadata.stubs(:ftype).returns "link" @metadata.stubs(:links).returns "manage" @metadata.stubs(:checksum_type).returns nil @resource.stubs(:[]) @resource.stubs(:[]=) @metadata.expects(:destination).returns "/path/to/symlink" @resource.expects(:[]=).with(:target, "/path/to/symlink") @source.copy_source_values end end end it "should have a local? method" do expect(described_class.new(:resource => resource)).to be_respond_to(:local?) end context "when accessing source properties" do let(:catalog) { Puppet::Resource::Catalog.new } let(:path) { tmpfile('file_resource') } let(:resource) { Puppet::Type.type(:file).new(:path => path, :catalog => catalog) } let(:sourcepath) { tmpfile('file_source') } describe "for local sources" do before :each do FileUtils.touch(sourcepath) end describe "on POSIX systems", :if => Puppet.features.posix? do ['', "file:", "file://"].each do |prefix| it "with prefix '#{prefix}' should be local" do resource[:source] = "#{prefix}#{sourcepath}" expect(resource.parameter(:source)).to be_local end it "should be able to return the metadata source full path" do resource[:source] = "#{prefix}#{sourcepath}" expect(resource.parameter(:source).full_path).to eq(sourcepath) end end end describe "on Windows systems", :if => Puppet.features.microsoft_windows? do ['', "file:/", "file:///"].each do |prefix| it "should be local with prefix '#{prefix}'" do resource[:source] = "#{prefix}#{sourcepath}" expect(resource.parameter(:source)).to be_local end it "should be able to return the metadata source full path" do resource[:source] = "#{prefix}#{sourcepath}" expect(resource.parameter(:source).full_path).to eq(sourcepath) end it "should convert backslashes to forward slashes" do resource[:source] = "#{prefix}#{sourcepath.gsub(/\\/, '/')}" end end it "should be UNC with two slashes" end end %w{puppet http}.each do |scheme| describe "for remote (#{scheme}) sources" do let(:sourcepath) { "/path/to/source" } let(:uri) { URI::Generic.build(:scheme => scheme, :host => 'server', :port => 8192, :path => sourcepath).to_s } before(:each) do metadata = Puppet::FileServing::Metadata.new(path, :source => uri, 'type' => 'file') #metadata = stub('remote', :ftype => "file", :source => uri) Puppet::FileServing::Metadata.indirection.stubs(:find). with(uri,all_of(has_key(:environment), has_key(:links))).returns metadata Puppet::FileServing::Metadata.indirection.stubs(:find). with(uri,all_of(has_key(:environment), has_key(:links))).returns metadata resource[:source] = uri end it "should not be local" do expect(resource.parameter(:source)).not_to be_local end it "should be able to return the metadata source full path" do expect(resource.parameter(:source).full_path).to eq("/path/to/source") end it "should be able to return the source server" do expect(resource.parameter(:source).server).to eq("server") end it "should be able to return the source port" do expect(resource.parameter(:source).port).to eq(8192) end if scheme == 'puppet' describe "which don't specify server or port" do let(:uri) { "puppet:///path/to/source" } it "should return the default source server" do Puppet[:server] = "myserver" expect(resource.parameter(:source).server).to eq("myserver") end it "should return the default source port" do Puppet[:masterport] = 1234 expect(resource.parameter(:source).port).to eq(1234) end end end end end end describe "when writing" do describe "as puppet apply" do let(:source_content) { "source file content\r\n"*10 } before do Puppet[:default_file_terminus] = "file_server" resource[:source] = file_containing('apply', source_content) end it "should copy content from the source to the file" do source = resource.parameter(:source) resource.write(source) expect(Puppet::FileSystem.binread(filename)).to eq(source_content) end with_digest_algorithms do it "should return the checksum computed" do File.open(filename, 'wb') do |file| source = resource.parameter(:source) resource[:checksum] = digest_algorithm expect(source.write(file)).to eq("{#{digest_algorithm}}#{digest(source_content)}") end end end end describe "from local source" do let(:source_content) { "source file content\r\n"*10 } before do resource[:backup] = false resource[:source] = file_containing('source', source_content) end it "should copy content from the source to the file" do source = resource.parameter(:source) resource.write(source) expect(Puppet::FileSystem.binread(filename)).to eq(source_content) end with_digest_algorithms do it "should return the checksum computed" do File.open(filename, 'wb') do |file| source = resource.parameter(:source) resource[:checksum] = digest_algorithm expect(source.write(file)).to eq("{#{digest_algorithm}}#{digest(source_content)}") end end end end describe 'from remote source' do let(:source_content) { "source file content\n"*10 } let(:source) { resource.newattr(:source) } let(:response) { stub_everything('response') } let(:conn) { mock('connection') } before do resource[:backup] = false response.stubs(:read_body).multiple_yields(*source_content.lines) conn.stubs(:request_get).yields(response) end it 'should use an explicit fileserver if source starts with puppet://' do response.stubs(:code).returns('200') source.stubs(:metadata).returns stub_everything('metadata', :source => 'puppet://somehostname/test/foo', :ftype => 'file') Puppet::Network::HttpPool.expects(:http_instance).with('somehostname', anything).returns(conn) resource.write(source) end it 'should use the default fileserver if source starts with puppet:///' do response.stubs(:code).returns('200') source.stubs(:metadata).returns stub_everything('metadata', :source => 'puppet:///test/foo', :ftype => 'file') Puppet::Network::HttpPool.expects(:http_instance).with(Puppet.settings[:server], anything).returns(conn) resource.write(source) end it 'should percent encode reserved characters' do response.stubs(:code).returns('200') Puppet::Network::HttpPool.stubs(:http_instance).returns(conn) source.stubs(:metadata).returns stub_everything('metadata', :source => 'puppet:///test/foo bar', :ftype => 'file') conn.unstub(:request_get) conn.expects(:request_get).with("#{Puppet::Network::HTTP::MASTER_URL_PREFIX}/v3/file_content/test/foo%20bar?environment=myenv&", anything).yields(response) resource.write(source) end it 'should request binary content' do response.stubs(:code).returns('200') Puppet::Network::HttpPool.stubs(:http_instance).returns(conn) source.stubs(:metadata).returns stub_everything('metadata', :source => 'puppet:///test/foo bar', :ftype => 'file') conn.unstub(:request_get) conn.expects(:request_get).with do |_, options| expect(options).to include('Accept' => 'application/octet-stream') end.yields(response) resource.write(source) end describe 'when handling file_content responses' do before do File.open(filename, 'w') {|f| f.write "initial file content"} end before(:each) do Puppet::Network::HttpPool.stubs(:http_instance).returns(conn) source.stubs(:metadata).returns stub_everything('metadata', :source => 'puppet:///test/foo', :ftype => 'file') end it 'should not write anything if source is not found' do response.stubs(:code).returns('404') expect { resource.write(source) }.to raise_error(Net::HTTPError, /404/) expect(File.read(filename)).to eq('initial file content') end it 'should raise an HTTP error in case of server error' do response.stubs(:code).returns('500') expect { resource.write(source) }.to raise_error(Net::HTTPError, /500/) end context 'and the request was successful' do before(:each) { response.stubs(:code).returns '200' } it 'should write the contents to the file' do resource.write(source) expect(Puppet::FileSystem.binread(filename)).to eq(source_content) end with_digest_algorithms do it 'should return the checksum computed' do File.open(filename, 'w') do |file| resource[:checksum] = digest_algorithm expect(source.write(file)).to eq("{#{digest_algorithm}}#{digest(source_content)}") end end end end end end end end puppet-5.5.10/spec/unit/type/filebucket_spec.rb0000644005276200011600000000674013417161721021367 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:filebucket) do include PuppetSpec::Files describe "when validating attributes" do %w{name server port path}.each do |attr| it "should have a '#{attr}' parameter" do expect(Puppet::Type.type(:filebucket).attrtype(attr.intern)).to eq(:param) end end it "should have its 'name' attribute set as its namevar" do expect(Puppet::Type.type(:filebucket).key_attributes).to eq([:name]) end end it "should use the clientbucketdir as the path by default path" do Puppet.settings[:clientbucketdir] = "/my/bucket" expect(Puppet::Type.type(:filebucket).new(:name => "main")[:path]).to eq(Puppet[:clientbucketdir]) end it "should use the masterport as the path by default port" do Puppet.settings[:masterport] = 50 expect(Puppet::Type.type(:filebucket).new(:name => "main")[:port]).to eq(Puppet[:masterport]) end it "should use the server as the path by default server" do Puppet.settings[:server] = "myserver" expect(Puppet::Type.type(:filebucket).new(:name => "main")[:server]).to eq(Puppet[:server]) end it "be local by default" do bucket = Puppet::Type.type(:filebucket).new :name => "main" expect(bucket.bucket).to be_local end describe "path" do def bucket(hash) Puppet::Type.type(:filebucket).new({:name => 'main'}.merge(hash)) end it "should accept false as a value" do expect { bucket(:path => false) }.not_to raise_error end it "should accept true as a value" do expect { bucket(:path => true) }.not_to raise_error end it "should fail when given an array of values" do expect { bucket(:path => ['one', 'two']) }. to raise_error Puppet::Error, /only have one filebucket path/ end %w{one ../one one/two}.each do |path| it "should fail if given a relative path of #{path.inspect}" do expect { bucket(:path => path) }. to raise_error Puppet::Error, /Filebucket paths must be absolute/ end end it "should succeed if given an absolute path" do expect { bucket(:path => make_absolute('/tmp/bucket')) }.not_to raise_error end it "not be local if path is false" do expect(bucket(:path => false).bucket).not_to be_local end it "be local if both a path and a server are specified" do expect(bucket(:server => "puppet", :path => make_absolute("/my/path")).bucket).to be_local end end describe "when creating the filebucket" do before do @bucket = stub 'bucket', :name= => nil end it "should use any provided path" do path = make_absolute("/foo/bar") bucket = Puppet::Type.type(:filebucket).new :name => "main", :path => path Puppet::FileBucket::Dipper.expects(:new).with(:Path => path).returns @bucket bucket.bucket end it "should use any provided server and port" do bucket = Puppet::Type.type(:filebucket).new :name => "main", :server => "myserv", :port => "myport", :path => false Puppet::FileBucket::Dipper.expects(:new).with(:Server => "myserv", :Port => "myport").returns @bucket bucket.bucket end it "should use the default server if the path is unset and no server is provided" do Puppet.settings[:server] = "myserv" bucket = Puppet::Type.type(:filebucket).new :name => "main", :path => false Puppet::FileBucket::Dipper.expects(:new).with { |args| args[:Server] == "myserv" }.returns @bucket bucket.bucket end end end puppet-5.5.10/spec/unit/type/group_spec.rb0000644005276200011600000000613213417161721020401 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:group) do before do @class = Puppet::Type.type(:group) end it "should have a system_groups feature" do expect(@class.provider_feature(:system_groups)).not_to be_nil end it 'should default to `present`' do expect(@class.new(:name => "foo")[:ensure]).to eq(:present) end it 'should set ensure to whatever is passed in' do expect(@class.new(:name => "foo", :ensure => 'absent')[:ensure]).to eq(:absent) end describe "when validating attributes" do [:name, :allowdupe].each do |param| it "should have a #{param} parameter" do expect(@class.attrtype(param)).to eq(:param) end end [:ensure, :gid].each do |param| it "should have a #{param} property" do expect(@class.attrtype(param)).to eq(:property) end end it "should convert gids provided as strings into integers" do expect(@class.new(:name => "foo", :gid => "15")[:gid]).to eq(15) end it "should accepts gids provided as integers" do expect(@class.new(:name => "foo", :gid => 15)[:gid]).to eq(15) end end it "should have a boolean method for determining if duplicates are allowed" do expect(@class.new(:name => "foo")).to respond_to "allowdupe?" end it "should have a boolean method for determining if system groups are allowed" do expect(@class.new(:name => "foo")).to respond_to "system?" end it "should call 'create' to create the group" do group = @class.new(:name => "foo", :ensure => :present) group.provider.expects(:create) group.parameter(:ensure).sync end it "should call 'delete' to remove the group" do group = @class.new(:name => "foo", :ensure => :absent) group.provider.expects(:delete) group.parameter(:ensure).sync end it "delegates the existence check to its provider" do provider = @class.provide(:testing) {} provider_instance = provider.new provider_instance.expects(:exists?).returns true type = @class.new(:name => "group", :provider => provider_instance) expect(type.exists?).to eq(true) end describe "should delegate :members implementation to the provider:" do let (:provider) do @class.provide(:testing) do has_features :manages_members def members [] end end end let (:provider_instance) { provider.new } let (:type) { @class.new(:name => "group", :provider => provider_instance, :members => ['user1']) } it "insync? calls members_insync?" do provider_instance.expects(:members_insync?).with(['user1'], ['user1']).returns true expect(type.property(:members).insync?(['user1'])).to be_truthy end it "is_to_s and should_to_s call members_to_s" do provider_instance.expects(:members_to_s).with(['user1', 'user2']).returns "user1 (), user2 ()" provider_instance.expects(:members_to_s).with(['user1']).returns "user1 ()" expect(type.property(:members).is_to_s('user1')).to eq('user1 ()') expect(type.property(:members).should_to_s('user1,user2')).to eq('user1 (), user2 ()') end end end puppet-5.5.10/spec/unit/type/noop_metaparam_spec.rb0000644005276200011600000000161213417161721022245 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/type' describe Puppet::Type.type(:file).attrclass(:noop) do include PuppetSpec::Files before do Puppet.settings.stubs(:use) @file = Puppet::Type.newfile :path => make_absolute("/what/ever") end it "should accept true as a value" do expect { @file[:noop] = true }.not_to raise_error end it "should accept false as a value" do expect { @file[:noop] = false }.not_to raise_error end describe "when set on a resource" do it "should default to the :noop setting" do Puppet[:noop] = true expect(@file.noop).to eq(true) end it "should prefer true values from the attribute" do @file[:noop] = true expect(@file.noop).to be_truthy end it "should prefer false values from the attribute" do @file[:noop] = false expect(@file.noop).to be_falsey end end end puppet-5.5.10/spec/unit/type/package/0000755005276200011600000000000013417162177017305 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/type/package/package_settings_spec.rb0000644005276200011600000001253613417161721024160 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:package) do before do Puppet::Util::Storage.stubs(:store) end it "should have a :package_settings feature that requires :package_settings_insync?, :package_settings and :package_settings=" do expect(described_class.provider_feature(:package_settings).methods).to eq([:package_settings_insync?, :package_settings, :package_settings=]) end context "when validating attributes" do it "should have a package_settings property" do expect(described_class.attrtype(:package_settings)).to eq(:property) end end context "when validating attribute values" do let(:provider) do stub( 'provider', :class => described_class.defaultprovider, :clear => nil, :validate_source => false ) end before do provider.class.stubs(:supports_parameter?).returns(true) described_class.defaultprovider.stubs(:new).returns(provider) end describe 'package_settings' do context "with a minimalistic provider supporting package_settings" do context "and {:package_settings => :settings}" do let(:resource) do described_class.new :name => 'foo', :package_settings => :settings end it { expect { resource }.to_not raise_error } it "should set package_settings to :settings" do expect(resource.value(:package_settings)).to be :settings end end end context "with a provider that supports validation of the package_settings" do context "and {:package_settings => :valid_value}" do before do provider.expects(:package_settings_validate).once.with(:valid_value).returns(true) end let(:resource) do described_class.new :name => 'foo', :package_settings => :valid_value end it { expect { resource }.to_not raise_error } it "should set package_settings to :valid_value" do expect(resource.value(:package_settings)).to eq(:valid_value) end end context "and {:package_settings => :invalid_value}" do before do msg = "package_settings must be a Hash, not Symbol" provider.expects(:package_settings_validate).once. with(:invalid_value).raises(ArgumentError, msg) end let(:resource) do described_class.new :name => 'foo', :package_settings => :invalid_value end it do expect { resource }.to raise_error Puppet::Error, /package_settings must be a Hash, not Symbol/ end end end context "with a provider that supports munging of the package_settings" do context "and {:package_settings => 'A'}" do before do provider.expects(:package_settings_munge).once.with('A').returns(:a) end let(:resource) do described_class.new :name => 'foo', :package_settings => 'A' end it do expect { resource }.to_not raise_error end it "should set package_settings to :a" do expect(resource.value(:package_settings)).to be :a end end end end end describe "package_settings property" do let(:provider) do stub( 'provider', :class => described_class.defaultprovider, :clear => nil, :validate_source => false ) end before do provider.class.stubs(:supports_parameter?).returns(true) described_class.defaultprovider.stubs(:new).returns(provider) end context "with {package_settings => :should}" do let(:resource) do described_class.new :name => 'foo', :package_settings => :should end describe "#insync?(:is)" do it "returns the result of provider.package_settings_insync?(:should,:is)" do resource.provider.expects(:package_settings_insync?).once.with(:should,:is).returns :ok1 expect(resource.property(:package_settings).insync?(:is)).to be :ok1 end end describe "#should_to_s(:newvalue)" do it "returns the result of provider.package_settings_should_to_s(:should,:newvalue)" do resource.provider.expects(:package_settings_should_to_s).once.with(:should,:newvalue).returns :ok2 expect(resource.property(:package_settings).should_to_s(:newvalue)).to be :ok2 end end describe "#is_to_s(:currentvalue)" do it "returns the result of provider.package_settings_is_to_s(:should,:currentvalue)" do resource.provider.expects(:package_settings_is_to_s).once.with(:should,:currentvalue).returns :ok3 expect(resource.property(:package_settings).is_to_s(:currentvalue)).to be :ok3 end end end context "with any non-nil package_settings" do describe "#change_to_s(:currentvalue,:newvalue)" do let(:resource) do described_class.new :name => 'foo', :package_settings => {} end it "returns the result of provider.package_settings_change_to_s(:currentvalue,:newvalue)" do resource.provider.expects(:package_settings_change_to_s).once.with(:currentvalue,:newvalue).returns :ok4 expect(resource.property(:package_settings).change_to_s(:currentvalue,:newvalue)).to be :ok4 end end end end end puppet-5.5.10/spec/unit/type/stage_spec.rb0000644005276200011600000000032213417161721020343 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:stage) do it "should have a 'name' parameter'" do expect(Puppet::Type.type(:stage).new(:name => :foo)[:name]).to eq(:foo) end end puppet-5.5.10/spec/unit/type/whit_spec.rb0000644005276200011600000000034713417161721020222 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' whit = Puppet::Type.type(:whit) describe whit do it "should stringify in a way that users will regognise" do expect(whit.new(:name => "Foo::Bar").to_s).to eq("Foo::Bar") end end puppet-5.5.10/spec/unit/type/augeas_spec.rb0000644005276200011600000001003413417161722020507 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' augeas = Puppet::Type.type(:augeas) describe augeas do describe "when augeas is present", :if => Puppet.features.augeas? do it "should have a default provider inheriting from Puppet::Provider" do expect(augeas.defaultprovider.ancestors).to be_include(Puppet::Provider) end it "should have a valid provider" do expect(augeas.new(:name => "foo").provider.class.ancestors).to be_include(Puppet::Provider) end end describe "basic structure" do it "should be able to create an instance" do provider_class = Puppet::Type::Augeas.provider(Puppet::Type::Augeas.providers[0]) Puppet::Type::Augeas.expects(:defaultprovider).returns provider_class expect(augeas.new(:name => "bar")).not_to be_nil end it "should have a parse_commands feature" do expect(augeas.provider_feature(:parse_commands)).not_to be_nil end it "should have a need_to_run? feature" do expect(augeas.provider_feature(:need_to_run?)).not_to be_nil end it "should have an execute_changes feature" do expect(augeas.provider_feature(:execute_changes)).not_to be_nil end properties = [:returns] params = [:name, :context, :onlyif, :changes, :root, :load_path, :type_check, :show_diff] properties.each do |property| it "should have a #{property} property" do expect(augeas.attrclass(property).ancestors).to be_include(Puppet::Property) end it "should have documentation for its #{property} property" do expect(augeas.attrclass(property).doc).to be_instance_of(String) end end params.each do |param| it "should have a #{param} parameter" do expect(augeas.attrclass(param).ancestors).to be_include(Puppet::Parameter) end it "should have documentation for its #{param} parameter" do expect(augeas.attrclass(param).doc).to be_instance_of(String) end end end describe "default values" do before do provider_class = augeas.provider(augeas.providers[0]) augeas.expects(:defaultprovider).returns provider_class end it "should be blank for context" do expect(augeas.new(:name => :context)[:context]).to eq("") end it "should be blank for onlyif" do expect(augeas.new(:name => :onlyif)[:onlyif]).to eq("") end it "should be blank for load_path" do expect(augeas.new(:name => :load_path)[:load_path]).to eq("") end it "should be / for root" do expect(augeas.new(:name => :root)[:root]).to eq("/") end it "should be false for type_check" do expect(augeas.new(:name => :type_check)[:type_check]).to eq(:false) end end describe "provider interaction" do it "should return 0 if it does not need to run" do provider = stub("provider", :need_to_run? => false) resource = stub('resource', :resource => nil, :provider => provider, :line => nil, :file => nil) changes = augeas.attrclass(:returns).new(:resource => resource) expect(changes.retrieve).to eq(0) end it "should return :need_to_run if it needs to run" do provider = stub("provider", :need_to_run? => true) resource = stub('resource', :resource => nil, :provider => provider, :line => nil, :file => nil) changes = augeas.attrclass(:returns).new(:resource => resource) expect(changes.retrieve).to eq(:need_to_run) end end describe "loading specific files" do it "should require lens when incl is used" do expect { augeas.new(:name => :no_lens, :incl => "/etc/hosts")}.to raise_error(Puppet::Error) end it "should require incl when lens is used" do expect { augeas.new(:name => :no_incl, :lens => "Hosts.lns") }.to raise_error(Puppet::Error) end it "should set the context when a specific file is used" do fake_provider = stub_everything "fake_provider" augeas.stubs(:defaultprovider).returns fake_provider expect(augeas.new(:name => :no_incl, :lens => "Hosts.lns", :incl => "/etc/hosts")[:context]).to eq("/files/etc/hosts") end end end puppet-5.5.10/spec/unit/type/computer_spec.rb0000644005276200011600000000476513417161722021116 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' computer = Puppet::Type.type(:computer) describe Puppet::Type.type(:computer), " when checking computer objects" do before do provider_class = Puppet::Type::Computer.provider(Puppet::Type::Computer.providers[0]) Puppet::Type::Computer.expects(:defaultprovider).returns provider_class @resource = Puppet::Type::Computer.new( :name => "puppetcomputertest", :en_address => "aa:bb:cc:dd:ee:ff", :ip_address => "1.2.3.4") @properties = {} @ensure = Puppet::Type::Computer.attrclass(:ensure).new(:resource => @resource) end it "should be able to create an instance" do provider_class = Puppet::Type::Computer.provider(Puppet::Type::Computer.providers[0]) Puppet::Type::Computer.expects(:defaultprovider).returns provider_class expect(computer.new(:name => "bar")).not_to be_nil end properties = [:en_address, :ip_address] params = [:name] properties.each do |property| it "should have a #{property} property" do expect(computer.attrclass(property).ancestors).to be_include(Puppet::Property) end it "should have documentation for its #{property} property" do expect(computer.attrclass(property).doc).to be_instance_of(String) end it "should accept :absent as a value" do prop = computer.attrclass(property).new(:resource => @resource) prop.should = :absent expect(prop.should).to eq(:absent) end end params.each do |param| it "should have a #{param} parameter" do expect(computer.attrclass(param).ancestors).to be_include(Puppet::Parameter) end it "should have documentation for its #{param} parameter" do expect(computer.attrclass(param).doc).to be_instance_of(String) end end describe "default values" do before do provider_class = computer.provider(computer.providers[0]) computer.expects(:defaultprovider).returns provider_class end it "should be nil for en_address" do expect(computer.new(:name => :en_address)[:en_address]).to eq(nil) end it "should be nil for ip_address" do expect(computer.new(:name => :ip_address)[:ip_address]).to eq(nil) end end describe "when managing the ensure property" do it "should support a :present value" do expect { @ensure.should = :present }.not_to raise_error end it "should support an :absent value" do expect { @ensure.should = :absent }.not_to raise_error end end end puppet-5.5.10/spec/unit/type/cron_spec.rb0000644005276200011600000007162013417161722020213 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:cron), :unless => Puppet.features.microsoft_windows? do let(:simple_provider) do @provider_class = described_class.provide(:simple) { mk_resource_methods } @provider_class.stubs(:suitable?).returns true @provider_class end before :each do described_class.stubs(:defaultprovider).returns @provider_class end after :each do described_class.unprovide(:simple) end it "should have :name be its namevar" do expect(described_class.key_attributes).to eq([:name]) end describe "when validating attributes" do [:name, :provider].each do |param| it "should have a #{param} parameter" do expect(described_class.attrtype(param)).to eq(:param) end end [:command, :special, :minute, :hour, :weekday, :month, :monthday, :environment, :user, :target].each do |property| it "should have a #{property} property" do expect(described_class.attrtype(property)).to eq(:property) end end [:command, :minute, :hour, :weekday, :month, :monthday].each do |cronparam| it "should have #{cronparam} of type CronParam" do expect(described_class.attrclass(cronparam).ancestors).to include CronParam end end end describe "when validating values" do describe "ensure" do it "should support present as a value for ensure" do expect { described_class.new(:name => 'foo', :ensure => :present) }.to_not raise_error end it "should support absent as a value for ensure" do expect { described_class.new(:name => 'foo', :ensure => :present) }.to_not raise_error end it "should not support other values" do expect { described_class.new(:name => 'foo', :ensure => :foo) }.to raise_error(Puppet::Error, /Invalid value/) end end describe "command" do it "should discard leading spaces" do expect(described_class.new(:name => 'foo', :command => " /bin/true")[:command]).not_to match Regexp.new(" ") end it "should discard trailing spaces" do expect(described_class.new(:name => 'foo', :command => "/bin/true ")[:command]).not_to match Regexp.new(" ") end end describe "minute" do it "should support absent" do expect { described_class.new(:name => 'foo', :minute => 'absent') }.to_not raise_error end it "should support *" do expect { described_class.new(:name => 'foo', :minute => '*') }.to_not raise_error end it "should translate absent to :absent" do expect(described_class.new(:name => 'foo', :minute => 'absent')[:minute]).to eq(:absent) end it "should translate * to :absent" do expect(described_class.new(:name => 'foo', :minute => '*')[:minute]).to eq(:absent) end it "should support valid single values" do expect { described_class.new(:name => 'foo', :minute => '0') }.to_not raise_error expect { described_class.new(:name => 'foo', :minute => '1') }.to_not raise_error expect { described_class.new(:name => 'foo', :minute => '59') }.to_not raise_error end it "should not support non numeric characters" do expect { described_class.new(:name => 'foo', :minute => 'z59') }.to raise_error(Puppet::Error, /z59 is not a valid minute/) expect { described_class.new(:name => 'foo', :minute => '5z9') }.to raise_error(Puppet::Error, /5z9 is not a valid minute/) expect { described_class.new(:name => 'foo', :minute => '59z') }.to raise_error(Puppet::Error, /59z is not a valid minute/) end it "should not support single values out of range" do expect { described_class.new(:name => 'foo', :minute => '-1') }.to raise_error(Puppet::Error, /-1 is not a valid minute/) expect { described_class.new(:name => 'foo', :minute => '60') }.to raise_error(Puppet::Error, /60 is not a valid minute/) expect { described_class.new(:name => 'foo', :minute => '61') }.to raise_error(Puppet::Error, /61 is not a valid minute/) expect { described_class.new(:name => 'foo', :minute => '120') }.to raise_error(Puppet::Error, /120 is not a valid minute/) end it "should support valid multiple values" do expect { described_class.new(:name => 'foo', :minute => ['0','1','59'] ) }.to_not raise_error expect { described_class.new(:name => 'foo', :minute => ['40','30','20'] ) }.to_not raise_error expect { described_class.new(:name => 'foo', :minute => ['10','30','20'] ) }.to_not raise_error end it "should not support multiple values if at least one is invalid" do # one invalid expect { described_class.new(:name => 'foo', :minute => ['0','1','60'] ) }.to raise_error(Puppet::Error, /60 is not a valid minute/) expect { described_class.new(:name => 'foo', :minute => ['0','120','59'] ) }.to raise_error(Puppet::Error, /120 is not a valid minute/) expect { described_class.new(:name => 'foo', :minute => ['-1','1','59'] ) }.to raise_error(Puppet::Error, /-1 is not a valid minute/) # two invalid expect { described_class.new(:name => 'foo', :minute => ['0','61','62'] ) }.to raise_error(Puppet::Error, /(61|62) is not a valid minute/) # all invalid expect { described_class.new(:name => 'foo', :minute => ['-1','61','62'] ) }.to raise_error(Puppet::Error, /(-1|61|62) is not a valid minute/) end it "should support valid step syntax" do expect { described_class.new(:name => 'foo', :minute => '*/2' ) }.to_not raise_error expect { described_class.new(:name => 'foo', :minute => '10-16/2' ) }.to_not raise_error end it "should not support invalid steps" do expect { described_class.new(:name => 'foo', :minute => '*/A' ) }.to raise_error(Puppet::Error, /\*\/A is not a valid minute/) expect { described_class.new(:name => 'foo', :minute => '*/2A' ) }.to raise_error(Puppet::Error, /\*\/2A is not a valid minute/) # As it turns out cron does not complaining about steps that exceed the valid range # expect { described_class.new(:name => 'foo', :minute => '*/120' ) }.to raise_error(Puppet::Error, /is not a valid minute/) end end describe "hour" do it "should support absent" do expect { described_class.new(:name => 'foo', :hour => 'absent') }.to_not raise_error end it "should support *" do expect { described_class.new(:name => 'foo', :hour => '*') }.to_not raise_error end it "should translate absent to :absent" do expect(described_class.new(:name => 'foo', :hour => 'absent')[:hour]).to eq(:absent) end it "should translate * to :absent" do expect(described_class.new(:name => 'foo', :hour => '*')[:hour]).to eq(:absent) end it "should support valid single values" do expect { described_class.new(:name => 'foo', :hour => '0') }.to_not raise_error expect { described_class.new(:name => 'foo', :hour => '11') }.to_not raise_error expect { described_class.new(:name => 'foo', :hour => '12') }.to_not raise_error expect { described_class.new(:name => 'foo', :hour => '13') }.to_not raise_error expect { described_class.new(:name => 'foo', :hour => '23') }.to_not raise_error end it "should not support non numeric characters" do expect { described_class.new(:name => 'foo', :hour => 'z15') }.to raise_error(Puppet::Error, /z15 is not a valid hour/) expect { described_class.new(:name => 'foo', :hour => '1z5') }.to raise_error(Puppet::Error, /1z5 is not a valid hour/) expect { described_class.new(:name => 'foo', :hour => '15z') }.to raise_error(Puppet::Error, /15z is not a valid hour/) end it "should not support single values out of range" do expect { described_class.new(:name => 'foo', :hour => '-1') }.to raise_error(Puppet::Error, /-1 is not a valid hour/) expect { described_class.new(:name => 'foo', :hour => '24') }.to raise_error(Puppet::Error, /24 is not a valid hour/) expect { described_class.new(:name => 'foo', :hour => '120') }.to raise_error(Puppet::Error, /120 is not a valid hour/) end it "should support valid multiple values" do expect { described_class.new(:name => 'foo', :hour => ['0','1','23'] ) }.to_not raise_error expect { described_class.new(:name => 'foo', :hour => ['5','16','14'] ) }.to_not raise_error expect { described_class.new(:name => 'foo', :hour => ['16','13','9'] ) }.to_not raise_error end it "should not support multiple values if at least one is invalid" do # one invalid expect { described_class.new(:name => 'foo', :hour => ['0','1','24'] ) }.to raise_error(Puppet::Error, /24 is not a valid hour/) expect { described_class.new(:name => 'foo', :hour => ['0','-1','5'] ) }.to raise_error(Puppet::Error, /-1 is not a valid hour/) expect { described_class.new(:name => 'foo', :hour => ['-1','1','23'] ) }.to raise_error(Puppet::Error, /-1 is not a valid hour/) # two invalid expect { described_class.new(:name => 'foo', :hour => ['0','25','26'] ) }.to raise_error(Puppet::Error, /(25|26) is not a valid hour/) # all invalid expect { described_class.new(:name => 'foo', :hour => ['-1','24','120'] ) }.to raise_error(Puppet::Error, /(-1|24|120) is not a valid hour/) end it "should support valid step syntax" do expect { described_class.new(:name => 'foo', :hour => '*/2' ) }.to_not raise_error expect { described_class.new(:name => 'foo', :hour => '10-18/4' ) }.to_not raise_error end it "should not support invalid steps" do expect { described_class.new(:name => 'foo', :hour => '*/A' ) }.to raise_error(Puppet::Error, /\*\/A is not a valid hour/) expect { described_class.new(:name => 'foo', :hour => '*/2A' ) }.to raise_error(Puppet::Error, /\*\/2A is not a valid hour/) # As it turns out cron does not complaining about steps that exceed the valid range # expect { described_class.new(:name => 'foo', :hour => '*/26' ) }.to raise_error(Puppet::Error, /is not a valid hour/) end end describe "weekday" do it "should support absent" do expect { described_class.new(:name => 'foo', :weekday => 'absent') }.to_not raise_error end it "should support *" do expect { described_class.new(:name => 'foo', :weekday => '*') }.to_not raise_error end it "should translate absent to :absent" do expect(described_class.new(:name => 'foo', :weekday => 'absent')[:weekday]).to eq(:absent) end it "should translate * to :absent" do expect(described_class.new(:name => 'foo', :weekday => '*')[:weekday]).to eq(:absent) end it "should support valid numeric weekdays" do expect { described_class.new(:name => 'foo', :weekday => '0') }.to_not raise_error expect { described_class.new(:name => 'foo', :weekday => '1') }.to_not raise_error expect { described_class.new(:name => 'foo', :weekday => '6') }.to_not raise_error # According to http://www.manpagez.com/man/5/crontab 7 is also valid (Sunday) expect { described_class.new(:name => 'foo', :weekday => '7') }.to_not raise_error end it "should support valid weekdays as words (long version)" do expect { described_class.new(:name => 'foo', :weekday => 'Monday') }.to_not raise_error expect { described_class.new(:name => 'foo', :weekday => 'Tuesday') }.to_not raise_error expect { described_class.new(:name => 'foo', :weekday => 'Wednesday') }.to_not raise_error expect { described_class.new(:name => 'foo', :weekday => 'Thursday') }.to_not raise_error expect { described_class.new(:name => 'foo', :weekday => 'Friday') }.to_not raise_error expect { described_class.new(:name => 'foo', :weekday => 'Saturday') }.to_not raise_error expect { described_class.new(:name => 'foo', :weekday => 'Sunday') }.to_not raise_error end it "should support valid weekdays as words (3 character version)" do expect { described_class.new(:name => 'foo', :weekday => 'Mon') }.to_not raise_error expect { described_class.new(:name => 'foo', :weekday => 'Tue') }.to_not raise_error expect { described_class.new(:name => 'foo', :weekday => 'Wed') }.to_not raise_error expect { described_class.new(:name => 'foo', :weekday => 'Thu') }.to_not raise_error expect { described_class.new(:name => 'foo', :weekday => 'Fri') }.to_not raise_error expect { described_class.new(:name => 'foo', :weekday => 'Sat') }.to_not raise_error expect { described_class.new(:name => 'foo', :weekday => 'Sun') }.to_not raise_error end it "should not support numeric values out of range" do expect { described_class.new(:name => 'foo', :weekday => '-1') }.to raise_error(Puppet::Error, /-1 is not a valid weekday/) expect { described_class.new(:name => 'foo', :weekday => '8') }.to raise_error(Puppet::Error, /8 is not a valid weekday/) end it "should not support invalid weekday names" do expect { described_class.new(:name => 'foo', :weekday => 'Sar') }.to raise_error(Puppet::Error, /Sar is not a valid weekday/) end it "should support valid multiple values" do expect { described_class.new(:name => 'foo', :weekday => ['0','1','6'] ) }.to_not raise_error expect { described_class.new(:name => 'foo', :weekday => ['Mon','Wed','Friday'] ) }.to_not raise_error end it "should not support multiple values if at least one is invalid" do # one invalid expect { described_class.new(:name => 'foo', :weekday => ['0','1','8'] ) }.to raise_error(Puppet::Error, /8 is not a valid weekday/) expect { described_class.new(:name => 'foo', :weekday => ['Mon','Fii','Sat'] ) }.to raise_error(Puppet::Error, /Fii is not a valid weekday/) # two invalid expect { described_class.new(:name => 'foo', :weekday => ['Mos','Fii','Sat'] ) }.to raise_error(Puppet::Error, /(Mos|Fii) is not a valid weekday/) # all invalid expect { described_class.new(:name => 'foo', :weekday => ['Mos','Fii','Saa'] ) }.to raise_error(Puppet::Error, /(Mos|Fii|Saa) is not a valid weekday/) expect { described_class.new(:name => 'foo', :weekday => ['-1','8','11'] ) }.to raise_error(Puppet::Error, /(-1|8|11) is not a valid weekday/) end it "should support valid step syntax" do expect { described_class.new(:name => 'foo', :weekday => '*/2' ) }.to_not raise_error expect { described_class.new(:name => 'foo', :weekday => '0-4/2' ) }.to_not raise_error end it "should not support invalid steps" do expect { described_class.new(:name => 'foo', :weekday => '*/A' ) }.to raise_error(Puppet::Error, /\*\/A is not a valid weekday/) expect { described_class.new(:name => 'foo', :weekday => '*/2A' ) }.to raise_error(Puppet::Error, /\*\/2A is not a valid weekday/) # As it turns out cron does not complaining about steps that exceed the valid range # expect { described_class.new(:name => 'foo', :weekday => '*/9' ) }.to raise_error(Puppet::Error, /is not a valid weekday/) end end describe "month" do it "should support absent" do expect { described_class.new(:name => 'foo', :month => 'absent') }.to_not raise_error end it "should support *" do expect { described_class.new(:name => 'foo', :month => '*') }.to_not raise_error end it "should translate absent to :absent" do expect(described_class.new(:name => 'foo', :month => 'absent')[:month]).to eq(:absent) end it "should translate * to :absent" do expect(described_class.new(:name => 'foo', :month => '*')[:month]).to eq(:absent) end it "should support valid numeric values" do expect { described_class.new(:name => 'foo', :month => '1') }.to_not raise_error expect { described_class.new(:name => 'foo', :month => '12') }.to_not raise_error end it "should support valid months as words" do expect( described_class.new(:name => 'foo', :month => 'January')[:month] ).to eq(['1']) expect( described_class.new(:name => 'foo', :month => 'February')[:month] ).to eq(['2']) expect( described_class.new(:name => 'foo', :month => 'March')[:month] ).to eq(['3']) expect( described_class.new(:name => 'foo', :month => 'April')[:month] ).to eq(['4']) expect( described_class.new(:name => 'foo', :month => 'May')[:month] ).to eq(['5']) expect( described_class.new(:name => 'foo', :month => 'June')[:month] ).to eq(['6']) expect( described_class.new(:name => 'foo', :month => 'July')[:month] ).to eq(['7']) expect( described_class.new(:name => 'foo', :month => 'August')[:month] ).to eq(['8']) expect( described_class.new(:name => 'foo', :month => 'September')[:month] ).to eq(['9']) expect( described_class.new(:name => 'foo', :month => 'October')[:month] ).to eq(['10']) expect( described_class.new(:name => 'foo', :month => 'November')[:month] ).to eq(['11']) expect( described_class.new(:name => 'foo', :month => 'December')[:month] ).to eq(['12']) end it "should support valid months as words (3 character short version)" do expect( described_class.new(:name => 'foo', :month => 'Jan')[:month] ).to eq(['1']) expect( described_class.new(:name => 'foo', :month => 'Feb')[:month] ).to eq(['2']) expect( described_class.new(:name => 'foo', :month => 'Mar')[:month] ).to eq(['3']) expect( described_class.new(:name => 'foo', :month => 'Apr')[:month] ).to eq(['4']) expect( described_class.new(:name => 'foo', :month => 'May')[:month] ).to eq(['5']) expect( described_class.new(:name => 'foo', :month => 'Jun')[:month] ).to eq(['6']) expect( described_class.new(:name => 'foo', :month => 'Jul')[:month] ).to eq(['7']) expect( described_class.new(:name => 'foo', :month => 'Aug')[:month] ).to eq(['8']) expect( described_class.new(:name => 'foo', :month => 'Sep')[:month] ).to eq(['9']) expect( described_class.new(:name => 'foo', :month => 'Oct')[:month] ).to eq(['10']) expect( described_class.new(:name => 'foo', :month => 'Nov')[:month] ).to eq(['11']) expect( described_class.new(:name => 'foo', :month => 'Dec')[:month] ).to eq(['12']) end it "should not support numeric values out of range" do expect { described_class.new(:name => 'foo', :month => '-1') }.to raise_error(Puppet::Error, /-1 is not a valid month/) expect { described_class.new(:name => 'foo', :month => '0') }.to raise_error(Puppet::Error, /0 is not a valid month/) expect { described_class.new(:name => 'foo', :month => '13') }.to raise_error(Puppet::Error, /13 is not a valid month/) end it "should not support words that are not valid months" do expect { described_class.new(:name => 'foo', :month => 'Jal') }.to raise_error(Puppet::Error, /Jal is not a valid month/) end it "should not support single values out of range" do expect { described_class.new(:name => 'foo', :month => '-1') }.to raise_error(Puppet::Error, /-1 is not a valid month/) expect { described_class.new(:name => 'foo', :month => '60') }.to raise_error(Puppet::Error, /60 is not a valid month/) expect { described_class.new(:name => 'foo', :month => '61') }.to raise_error(Puppet::Error, /61 is not a valid month/) expect { described_class.new(:name => 'foo', :month => '120') }.to raise_error(Puppet::Error, /120 is not a valid month/) end it "should support valid multiple values" do expect { described_class.new(:name => 'foo', :month => ['1','9','12'] ) }.to_not raise_error expect { described_class.new(:name => 'foo', :month => ['Jan','March','Jul'] ) }.to_not raise_error end it "should not support multiple values if at least one is invalid" do # one invalid expect { described_class.new(:name => 'foo', :month => ['0','1','12'] ) }.to raise_error(Puppet::Error, /0 is not a valid month/) expect { described_class.new(:name => 'foo', :month => ['1','13','10'] ) }.to raise_error(Puppet::Error, /13 is not a valid month/) expect { described_class.new(:name => 'foo', :month => ['Jan','Feb','Jxx'] ) }.to raise_error(Puppet::Error, /Jxx is not a valid month/) # two invalid expect { described_class.new(:name => 'foo', :month => ['Jan','Fex','Jux'] ) }.to raise_error(Puppet::Error, /(Fex|Jux) is not a valid month/) # all invalid expect { described_class.new(:name => 'foo', :month => ['-1','0','13'] ) }.to raise_error(Puppet::Error, /(-1|0|13) is not a valid month/) expect { described_class.new(:name => 'foo', :month => ['Jax','Fex','Aux'] ) }.to raise_error(Puppet::Error, /(Jax|Fex|Aux) is not a valid month/) end it "should support valid step syntax" do expect { described_class.new(:name => 'foo', :month => '*/2' ) }.to_not raise_error expect { described_class.new(:name => 'foo', :month => '1-12/3' ) }.to_not raise_error end it "should not support invalid steps" do expect { described_class.new(:name => 'foo', :month => '*/A' ) }.to raise_error(Puppet::Error, /\*\/A is not a valid month/) expect { described_class.new(:name => 'foo', :month => '*/2A' ) }.to raise_error(Puppet::Error, /\*\/2A is not a valid month/) # As it turns out cron does not complaining about steps that exceed the valid range # expect { described_class.new(:name => 'foo', :month => '*/13' ) }.to raise_error(Puppet::Error, /is not a valid month/) end end describe "monthday" do it "should support absent" do expect { described_class.new(:name => 'foo', :monthday => 'absent') }.to_not raise_error end it "should support *" do expect { described_class.new(:name => 'foo', :monthday => '*') }.to_not raise_error end it "should translate absent to :absent" do expect(described_class.new(:name => 'foo', :monthday => 'absent')[:monthday]).to eq(:absent) end it "should translate * to :absent" do expect(described_class.new(:name => 'foo', :monthday => '*')[:monthday]).to eq(:absent) end it "should support valid single values" do expect { described_class.new(:name => 'foo', :monthday => '1') }.to_not raise_error expect { described_class.new(:name => 'foo', :monthday => '30') }.to_not raise_error expect { described_class.new(:name => 'foo', :monthday => '31') }.to_not raise_error end it "should not support non numeric characters" do expect { described_class.new(:name => 'foo', :monthday => 'z23') }.to raise_error(Puppet::Error, /z23 is not a valid monthday/) expect { described_class.new(:name => 'foo', :monthday => '2z3') }.to raise_error(Puppet::Error, /2z3 is not a valid monthday/) expect { described_class.new(:name => 'foo', :monthday => '23z') }.to raise_error(Puppet::Error, /23z is not a valid monthday/) end it "should not support single values out of range" do expect { described_class.new(:name => 'foo', :monthday => '-1') }.to raise_error(Puppet::Error, /-1 is not a valid monthday/) expect { described_class.new(:name => 'foo', :monthday => '0') }.to raise_error(Puppet::Error, /0 is not a valid monthday/) expect { described_class.new(:name => 'foo', :monthday => '32') }.to raise_error(Puppet::Error, /32 is not a valid monthday/) end it "should support valid multiple values" do expect { described_class.new(:name => 'foo', :monthday => ['1','23','31'] ) }.to_not raise_error expect { described_class.new(:name => 'foo', :monthday => ['31','23','1'] ) }.to_not raise_error expect { described_class.new(:name => 'foo', :monthday => ['1','31','23'] ) }.to_not raise_error end it "should not support multiple values if at least one is invalid" do # one invalid expect { described_class.new(:name => 'foo', :monthday => ['1','23','32'] ) }.to raise_error(Puppet::Error, /32 is not a valid monthday/) expect { described_class.new(:name => 'foo', :monthday => ['-1','12','23'] ) }.to raise_error(Puppet::Error, /-1 is not a valid monthday/) expect { described_class.new(:name => 'foo', :monthday => ['13','32','30'] ) }.to raise_error(Puppet::Error, /32 is not a valid monthday/) # two invalid expect { described_class.new(:name => 'foo', :monthday => ['-1','0','23'] ) }.to raise_error(Puppet::Error, /(-1|0) is not a valid monthday/) # all invalid expect { described_class.new(:name => 'foo', :monthday => ['-1','0','32'] ) }.to raise_error(Puppet::Error, /(-1|0|32) is not a valid monthday/) end it "should support valid step syntax" do expect { described_class.new(:name => 'foo', :monthday => '*/2' ) }.to_not raise_error expect { described_class.new(:name => 'foo', :monthday => '10-16/2' ) }.to_not raise_error end it "should not support invalid steps" do expect { described_class.new(:name => 'foo', :monthday => '*/A' ) }.to raise_error(Puppet::Error, /\*\/A is not a valid monthday/) expect { described_class.new(:name => 'foo', :monthday => '*/2A' ) }.to raise_error(Puppet::Error, /\*\/2A is not a valid monthday/) # As it turns out cron does not complaining about steps that exceed the valid range # expect { described_class.new(:name => 'foo', :monthday => '*/32' ) }.to raise_error(Puppet::Error, /is not a valid monthday/) end end describe "special" do %w(reboot yearly annually monthly weekly daily midnight hourly).each do |value| it "should support the value '#{value}'" do expect { described_class.new(:name => 'foo', :special => value ) }.to_not raise_error end end context "when combined with numeric schedule fields" do context "which are 'absent'" do [ %w(reboot yearly annually monthly weekly daily midnight hourly), :absent ].flatten.each { |value| it "should accept the value '#{value}' for special" do expect { described_class.new(:name => 'foo', :minute => :absent, :special => value ) }.to_not raise_error end } end context "which are not absent" do %w(reboot yearly annually monthly weekly daily midnight hourly).each { |value| it "should not accept the value '#{value}' for special" do expect { described_class.new(:name => 'foo', :minute => "1", :special => value ) }.to raise_error(Puppet::Error, /cannot specify both a special schedule and a value/) end } it "should accept the 'absent' value for special" do expect { described_class.new(:name => 'foo', :minute => "1", :special => :absent ) }.to_not raise_error end end end end describe "environment" do it "it should accept an :environment that looks like a path" do expect do described_class.new(:name => 'foo',:environment => 'PATH=/bin:/usr/bin:/usr/sbin') end.to_not raise_error end it "should not accept environment variables that do not contain '='" do expect do described_class.new(:name => 'foo',:environment => 'INVALID') end.to raise_error(Puppet::Error, /Invalid environment setting "INVALID"/) end it "should accept empty environment variables that do not contain '='" do expect do described_class.new(:name => 'foo',:environment => 'MAILTO=') end.to_not raise_error end it "should accept 'absent'" do expect do described_class.new(:name => 'foo',:environment => 'absent') end.to_not raise_error end end end describe "when autorequiring resources" do before :each do @user_bob = Puppet::Type.type(:user).new(:name => 'bob', :ensure => :present) @user_alice = Puppet::Type.type(:user).new(:name => 'alice', :ensure => :present) @catalog = Puppet::Resource::Catalog.new @catalog.add_resource @user_bob, @user_alice end it "should autorequire the user" do @resource = described_class.new(:name => 'dummy', :command => '/usr/bin/uptime', :user => 'alice') @catalog.add_resource @resource req = @resource.autorequire expect(req.size).to eq(1) expect(req[0].target).to eq(@resource) expect(req[0].source).to eq(@user_alice) end end it "should not require a command when removing an entry" do entry = described_class.new(:name => "test_entry", :ensure => :absent) expect(entry.value(:command)).to eq(nil) end it "should default to user => root if Etc.getpwuid(Process.uid) returns nil (#12357)" do Etc.expects(:getpwuid).returns(nil) entry = described_class.new(:name => "test_entry", :ensure => :present) expect(entry.value(:user)).to eql "root" end end puppet-5.5.10/spec/unit/type/exec_spec.rb0000644005276200011600000006753213417161722020205 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:exec) do include PuppetSpec::Files def exec_tester(command, exitstatus = 0, rest = {}) Puppet.features.stubs(:root?).returns(true) output = rest.delete(:output) || '' output = Puppet::Util::Execution::ProcessOutput.new(output, exitstatus) tries = rest[:tries] || 1 type_args = { :name => command, :path => @example_path, :logoutput => false, :loglevel => :err, :returns => 0 }.merge(rest) exec = Puppet::Type.type(:exec).new(type_args) Puppet::Util::Execution.expects(:execute).times(tries).with() do |*args| args[0] == command && args[1][:override_locale] == false && args[1].has_key?(:custom_environment) end.returns(output) return exec end before do @command = make_absolute('/bin/true whatever') @executable = make_absolute('/bin/true') @bogus_cmd = make_absolute('/bogus/cmd') end describe "when not stubbing the provider" do before do path = tmpdir('path') ext = Puppet.features.microsoft_windows? ? '.exe' : '' true_cmd = File.join(path, "true#{ext}") false_cmd = File.join(path, "false#{ext}") FileUtils.touch(true_cmd) FileUtils.touch(false_cmd) File.chmod(0755, true_cmd) File.chmod(0755, false_cmd) @example_path = [path] end it "should return :executed_command as its event" do resource = Puppet::Type.type(:exec).new :command => @command expect(resource.parameter(:returns).event.name).to eq(:executed_command) end describe "when execing" do it "should use the 'execute' method to exec" do expect(exec_tester("true").refresh).to eq(:executed_command) end it "should report a failure" do expect { exec_tester('false', 1).refresh }. to raise_error(Puppet::Error, /^'false' returned 1 instead of/) end it "should redact sensitive commands on failure" do expect { exec_tester('false', 1, :sensitive_parameters => [:command]).refresh }. to raise_error(Puppet::Error, /^\[command redacted\] returned 1 instead of/) end it "should not report a failure if the exit status is specified in a returns array" do expect { exec_tester("false", 1, :returns => [0, 1]).refresh }.to_not raise_error end it "should report a failure if the exit status is not specified in a returns array" do expect { exec_tester('false', 1, :returns => [0, 100]).refresh }. to raise_error(Puppet::Error, /^'false' returned 1 instead of/) end it "should report redact sensitive commands if the exit status is not specified in a returns array" do expect { exec_tester('false', 1, :returns => [0, 100], :sensitive_parameters => [:command]).refresh }. to raise_error(Puppet::Error, /^\[command redacted\] returned 1 instead of/) end it "should log the output on success" do output = "output1\noutput2\n" exec_tester('false', 0, :output => output, :logoutput => true).refresh output.split("\n").each do |line| log = @logs.shift expect(log.level).to eq(:err) expect(log.message).to eq(line) end end it "should log the output on failure" do output = "output1\noutput2\n" expect { exec_tester('false', 1, :output => output, :logoutput => true).refresh }. to raise_error(Puppet::Error) output.split("\n").each do |line| log = @logs.shift expect(log.level).to eq(:err) expect(log.message).to eq(line) end end end describe "when logoutput=>on_failure is set" do it "should log the output on failure" do output = "output1\noutput2\n" expect { exec_tester('false', 1, :output => output, :logoutput => :on_failure).refresh }. to raise_error(Puppet::Error, /^'false' returned 1 instead of/) output.split("\n").each do |line| log = @logs.shift expect(log.level).to eq(:err) expect(log.message).to eq(line) end end it "should redact the command on failure" do output = "output1\noutput2\n" expect { exec_tester('false', 1, :output => output, :logoutput => :on_failure, :sensitive_parameters => [:command]).refresh }. to raise_error(Puppet::Error, /^\[command redacted\] returned 1 instead of/) output.split("\n").each do |line| log = @logs.shift expect(log.level).to eq(:err) expect(log.message).to eq(line) end end it "should log the output on failure when returns is specified as an array" do output = "output1\noutput2\n" expect { exec_tester('false', 1, :output => output, :returns => [0, 100], :logoutput => :on_failure).refresh }.to raise_error(Puppet::Error, /^'false' returned 1 instead of/) output.split("\n").each do |line| log = @logs.shift expect(log.level).to eq(:err) expect(log.message).to eq(line) end end it "should redact the command on failure when returns is specified as an array" do output = "output1\noutput2\n" expect { exec_tester('false', 1, :output => output, :returns => [0, 100], :logoutput => :on_failure, :sensitive_parameters => [:command]).refresh }.to raise_error(Puppet::Error, /^\[command redacted\] returned 1 instead of/) output.split("\n").each do |line| log = @logs.shift expect(log.level).to eq(:err) expect(log.message).to eq(line) end end it "shouldn't log the output on success" do exec_tester('true', 0, :output => "a\nb\nc\n", :logoutput => :on_failure).refresh expect(@logs).to eq([]) end end it "shouldn't log the output on success when non-zero exit status is in a returns array" do exec_tester("true", 100, :output => "a\n", :logoutput => :on_failure, :returns => [1, 100]).refresh expect(@logs).to eq([]) end describe " when multiple tries are set," do it "should repeat the command attempt 'tries' times on failure and produce an error" do tries = 5 resource = exec_tester("false", 1, :tries => tries, :try_sleep => 0) expect { resource.refresh }.to raise_error(Puppet::Error) end end end it "should be able to autorequire files mentioned in the command" do foo = make_absolute('/bin/foo') catalog = Puppet::Resource::Catalog.new tmp = Puppet::Type.type(:file).new(:name => foo) execer = Puppet::Type.type(:exec).new(:name => foo) catalog.add_resource tmp catalog.add_resource execer dependencies = execer.autorequire(catalog) expect(dependencies.collect(&:to_s)).to eq([Puppet::Relationship.new(tmp, execer).to_s]) end describe "when handling the path parameter" do expect = %w{one two three four} { "an array" => expect, "a path-separator delimited list" => expect.join(File::PATH_SEPARATOR), "both array and path-separator delimited lists" => ["one", "two#{File::PATH_SEPARATOR}three", "four"], }.each do |test, input| it "should accept #{test}" do type = Puppet::Type.type(:exec).new(:name => @command, :path => input) expect(type[:path]).to eq(expect) end end describe "on platforms where path separator is not :" do before :each do @old_verbosity = $VERBOSE $VERBOSE = nil @old_separator = File::PATH_SEPARATOR File::PATH_SEPARATOR = 'q' end after :each do File::PATH_SEPARATOR = @old_separator $VERBOSE = @old_verbosity end it "should use the path separator of the current platform" do type = Puppet::Type.type(:exec).new(:name => @command, :path => "fooqbarqbaz") expect(type[:path]).to eq(%w[foo bar baz]) end end end describe "when setting user" do describe "on POSIX systems", :if => Puppet.features.posix? do it "should fail if we are not root" do Puppet.features.stubs(:root?).returns(false) expect { Puppet::Type.type(:exec).new(:name => '/bin/true whatever', :user => 'input') }.to raise_error Puppet::Error, /Parameter user failed/ end it "accepts the current user" do Puppet.features.stubs(:root?).returns(false) Etc.stubs(:getpwuid).returns(Struct::Passwd.new('input')) type = Puppet::Type.type(:exec).new(:name => '/bin/true whatever', :user => 'input') expect(type[:user]).to eq('input') end ['one', 2, 'root', 4294967295, 4294967296].each do |value| it "should accept '#{value}' as user if we are root" do Puppet.features.stubs(:root?).returns(true) type = Puppet::Type.type(:exec).new(:name => '/bin/true whatever', :user => value) expect(type[:user]).to eq(value) end end end describe "on Windows systems", :if => Puppet.features.microsoft_windows? do before :each do Puppet.features.stubs(:root?).returns(true) end it "should reject user parameter" do expect { Puppet::Type.type(:exec).new(:name => 'c:\windows\notepad.exe', :user => 'input') }.to raise_error Puppet::Error, /Unable to execute commands as other users on Windows/ end end end describe "when setting group" do shared_examples_for "exec[:group]" do ['one', 2, 'wheel', 4294967295, 4294967296].each do |value| it "should accept '#{value}' without error or judgement" do type = Puppet::Type.type(:exec).new(:name => @command, :group => value) expect(type[:group]).to eq(value) end end end describe "when running as root" do before :each do Puppet.features.stubs(:root?).returns(true) end it_behaves_like "exec[:group]" end describe "when not running as root" do before :each do Puppet.features.stubs(:root?).returns(false) end it_behaves_like "exec[:group]" end end describe "when setting cwd" do it_should_behave_like "all path parameters", :cwd, :array => false do def instance(path) # Specify shell provider so we don't have to care about command validation Puppet::Type.type(:exec).new(:name => @executable, :cwd => path, :provider => :shell) end end end shared_examples_for "all exec command parameters" do |param| { "relative" => "example", "absolute" => "/bin/example" }.sort.each do |name, command| describe "if command is #{name}" do before :each do @param = param end def test(command, valid) if @param == :name then instance = Puppet::Type.type(:exec).new() else instance = Puppet::Type.type(:exec).new(:name => @executable) end if valid then instance.provider.expects(:validatecmd).returns(true) else instance.provider.expects(:validatecmd).raises(Puppet::Error, "from a stub") end instance[@param] = command end it "should work if the provider calls the command valid" do expect { test(command, true) }.to_not raise_error end it "should fail if the provider calls the command invalid" do expect { test(command, false) }. to raise_error Puppet::Error, /Parameter #{@param} failed on Exec\[.*\]: from a stub/ end end end end shared_examples_for "all exec command parameters that take arrays" do |param| describe "when given an array of inputs" do before :each do @test = Puppet::Type.type(:exec).new(:name => @executable) end it "should accept the array when all commands return valid" do input = %w{one two three} @test.provider.expects(:validatecmd).times(input.length).returns(true) @test[param] = input expect(@test[param]).to eq(input) end it "should reject the array when any commands return invalid" do input = %w{one two three} @test.provider.expects(:validatecmd).with(input.first).returns(false) input[1..-1].each do |cmd| @test.provider.expects(:validatecmd).with(cmd).returns(true) end @test[param] = input expect(@test[param]).to eq(input) end it "should reject the array when all commands return invalid" do input = %w{one two three} @test.provider.expects(:validatecmd).times(input.length).returns(false) @test[param] = input expect(@test[param]).to eq(input) end end end describe "when setting command" do subject { described_class.new(:name => @command) } it "fails when passed an Array" do expect { subject[:command] = [] }.to raise_error Puppet::Error, /Command must be a String/ end it "fails when passed a Hash" do expect { subject[:command] = {} }.to raise_error Puppet::Error, /Command must be a String/ end end describe "when setting refresh" do it_should_behave_like "all exec command parameters", :refresh end describe "for simple parameters" do before :each do @exec = Puppet::Type.type(:exec).new(:name => @executable) end describe "when setting environment" do { "single values" => "foo=bar", "multiple values" => ["foo=bar", "baz=quux"], }.each do |name, data| it "should accept #{name}" do @exec[:environment] = data expect(@exec[:environment]).to eq(data) end end { "single values" => "foo", "only values" => ["foo", "bar"], "any values" => ["foo=bar", "baz"] }.each do |name, data| it "should reject #{name} without assignment" do expect { @exec[:environment] = data }. to raise_error Puppet::Error, /Invalid environment setting/ end end end describe "when setting timeout" do [0, 0.1, 1, 10, 4294967295].each do |valid| it "should accept '#{valid}' as valid" do @exec[:timeout] = valid expect(@exec[:timeout]).to eq(valid) end it "should accept '#{valid}' in an array as valid" do @exec[:timeout] = [valid] expect(@exec[:timeout]).to eq(valid) end end ['1/2', '', 'foo', '5foo'].each do |invalid| it "should reject '#{invalid}' as invalid" do expect { @exec[:timeout] = invalid }. to raise_error Puppet::Error, /The timeout must be a number/ end it "should reject '#{invalid}' in an array as invalid" do expect { @exec[:timeout] = [invalid] }. to raise_error Puppet::Error, /The timeout must be a number/ end end describe 'when timeout is exceeded' do subject do ruby_path = Puppet::Util::Execution.ruby_path() Puppet::Type.type(:exec).new(:name => "#{ruby_path} -e 'sleep 1'", :timeout => '0.1') end context 'on POSIX', :unless => Puppet.features.microsoft_windows? do it 'sends a SIGTERM and raises a Puppet::Error' do Process.expects(:kill).at_least_once expect { subject.refresh }.to raise_error Puppet::Error, "Command exceeded timeout" end end context 'on Windows', :if => Puppet.features.microsoft_windows? do it 'raises a Puppet::Error' do expect { subject.refresh }.to raise_error Puppet::Error, "Command exceeded timeout" end end end it "should convert timeout to a float" do command = make_absolute('/bin/false') resource = Puppet::Type.type(:exec).new :command => command, :timeout => "12" expect(resource[:timeout]).to be_a(Float) expect(resource[:timeout]).to eq(12.0) end it "should munge negative timeouts to 0.0" do command = make_absolute('/bin/false') resource = Puppet::Type.type(:exec).new :command => command, :timeout => "-12.0" expect(resource.parameter(:timeout).value).to be_a(Float) expect(resource.parameter(:timeout).value).to eq(0.0) end end describe "when setting tries" do [1, 10, 4294967295].each do |valid| it "should accept '#{valid}' as valid" do @exec[:tries] = valid expect(@exec[:tries]).to eq(valid) end if "REVISIT: too much test log spam" == "a good thing" then it "should accept '#{valid}' in an array as valid" do pending "inconsistent, but this is not supporting arrays, unlike timeout" @exec[:tries] = [valid] expect(@exec[:tries]).to eq(valid) end end end [-3.5, -1, 0, 0.2, '1/2', '1_000_000', '+12', '', 'foo'].each do |invalid| it "should reject '#{invalid}' as invalid" do expect { @exec[:tries] = invalid }. to raise_error Puppet::Error, /Tries must be an integer/ end if "REVISIT: too much test log spam" == "a good thing" then it "should reject '#{invalid}' in an array as invalid" do pending "inconsistent, but this is not supporting arrays, unlike timeout" expect { @exec[:tries] = [invalid] }. to raise_error Puppet::Error, /Tries must be an integer/ end end end end describe "when setting try_sleep" do [0, 0.2, 1, 10, 4294967295].each do |valid| it "should accept '#{valid}' as valid" do @exec[:try_sleep] = valid expect(@exec[:try_sleep]).to eq(valid) end if "REVISIT: too much test log spam" == "a good thing" then it "should accept '#{valid}' in an array as valid" do pending "inconsistent, but this is not supporting arrays, unlike timeout" @exec[:try_sleep] = [valid] expect(@exec[:try_sleep]).to eq(valid) end end end { -3.5 => "cannot be a negative number", -1 => "cannot be a negative number", '1/2' => 'must be a number', '1_000_000' => 'must be a number', '+12' => 'must be a number', '' => 'must be a number', 'foo' => 'must be a number', }.each do |invalid, error| it "should reject '#{invalid}' as invalid" do expect { @exec[:try_sleep] = invalid }. to raise_error Puppet::Error, /try_sleep #{error}/ end if "REVISIT: too much test log spam" == "a good thing" then it "should reject '#{invalid}' in an array as invalid" do pending "inconsistent, but this is not supporting arrays, unlike timeout" expect { @exec[:try_sleep] = [invalid] }. to raise_error Puppet::Error, /try_sleep #{error}/ end end end end describe "when setting refreshonly" do [:true, :false].each do |value| it "should accept '#{value}'" do @exec[:refreshonly] = value expect(@exec[:refreshonly]).to eq(value) end end [1, 0, "1", "0", "yes", "y", "no", "n"].each do |value| it "should reject '#{value}'" do expect { @exec[:refreshonly] = value }. to raise_error(Puppet::Error, /Invalid value #{value.inspect}\. Valid values are true, false/ ) end end end end describe "when setting creates" do it_should_behave_like "all path parameters", :creates, :array => true do def instance(path) # Specify shell provider so we don't have to care about command validation Puppet::Type.type(:exec).new(:name => @executable, :creates => path, :provider => :shell) end end end describe "when setting unless" do it_should_behave_like "all exec command parameters", :unless it_should_behave_like "all exec command parameters that take arrays", :unless end describe "when setting onlyif" do it_should_behave_like "all exec command parameters", :onlyif it_should_behave_like "all exec command parameters that take arrays", :onlyif end describe "#check" do before :each do @test = Puppet::Type.type(:exec).new(:name => @executable) end describe ":refreshonly" do { :true => false, :false => true }.each do |input, result| it "should return '#{result}' when given '#{input}'" do @test[:refreshonly] = input expect(@test.check_all_attributes).to eq(result) end end end describe ":creates" do before :each do @exist = tmpfile('exist') FileUtils.touch(@exist) @unexist = tmpfile('unexist') end context "with a single item" do it "should run when the item does not exist" do @test[:creates] = @unexist expect(@test.check_all_attributes).to eq(true) end it "should not run when the item exists" do @test[:creates] = @exist expect(@test.check_all_attributes).to eq(false) end end context "with an array with one item" do it "should run when the item does not exist" do @test[:creates] = [@unexist] expect(@test.check_all_attributes).to eq(true) end it "should not run when the item exists" do @test[:creates] = [@exist] expect(@test.check_all_attributes).to eq(false) end end context "with an array with multiple items" do it "should run when all items do not exist" do @test[:creates] = [@unexist] * 3 expect(@test.check_all_attributes).to eq(true) end it "should not run when one item exists" do @test[:creates] = [@unexist, @exist, @unexist] expect(@test.check_all_attributes).to eq(false) end it "should not run when all items exist" do @test[:creates] = [@exist] * 3 end end end { :onlyif => { :pass => false, :fail => true }, :unless => { :pass => true, :fail => false }, }.each do |param, sense| describe ":#{param}" do before :each do @pass = make_absolute("/magic/pass") @fail = make_absolute("/magic/fail") @pass_status = stub('status', :exitstatus => sense[:pass] ? 0 : 1) @fail_status = stub('status', :exitstatus => sense[:fail] ? 0 : 1) @test.provider.stubs(:checkexe).returns(true) [true, false].each do |check| @test.provider.stubs(:run).with(@pass, check). returns(['test output', @pass_status]) @test.provider.stubs(:run).with(@fail, check). returns(['test output', @fail_status]) end end context "with a single item" do it "should run if the command exits non-zero" do @test[param] = @fail expect(@test.check_all_attributes).to eq(true) end it "should not run if the command exits zero" do @test[param] = @pass expect(@test.check_all_attributes).to eq(false) end end context "with an array with a single item" do it "should run if the command exits non-zero" do @test[param] = [@fail] expect(@test.check_all_attributes).to eq(true) end it "should not run if the command exits zero" do @test[param] = [@pass] expect(@test.check_all_attributes).to eq(false) end end context "with an array with multiple items" do it "should run if all the commands exits non-zero" do @test[param] = [@fail] * 3 expect(@test.check_all_attributes).to eq(true) end it "should not run if one command exits zero" do @test[param] = [@pass, @fail, @pass] expect(@test.check_all_attributes).to eq(false) end it "should not run if all command exits zero" do @test[param] = [@pass] * 3 expect(@test.check_all_attributes).to eq(false) end end it "should emit output to debug" do Puppet::Util::Log.level = :debug @test[param] = @fail expect(@test.check_all_attributes).to eq(true) expect(@logs.shift.message).to eq("test output") end end end end describe "#retrieve" do before :each do @exec_resource = Puppet::Type.type(:exec).new(:name => @bogus_cmd) end it "should return :notrun when check_all_attributes returns true" do @exec_resource.stubs(:check_all_attributes).returns true expect(@exec_resource.retrieve[:returns]).to eq(:notrun) end it "should return default exit code 0 when check_all_attributes returns false" do @exec_resource.stubs(:check_all_attributes).returns false expect(@exec_resource.retrieve[:returns]).to eq(['0']) end it "should return the specified exit code when check_all_attributes returns false" do @exec_resource.stubs(:check_all_attributes).returns false @exec_resource[:returns] = 42 expect(@exec_resource.retrieve[:returns]).to eq(["42"]) end end describe "#output" do before :each do @exec_resource = Puppet::Type.type(:exec).new(:name => @bogus_cmd) end it "should return the provider's run output" do provider = stub 'provider' status = stubs "process_status" status.stubs(:exitstatus).returns("0") provider.expects(:run).returns(["silly output", status]) @exec_resource.stubs(:provider).returns(provider) @exec_resource.refresh expect(@exec_resource.output).to eq('silly output') end end describe "#refresh" do before :each do @exec_resource = Puppet::Type.type(:exec).new(:name => @bogus_cmd) end it "should call provider run with the refresh parameter if it is set" do myother_bogus_cmd = make_absolute('/myother/bogus/cmd') provider = stub 'provider' @exec_resource.stubs(:provider).returns(provider) @exec_resource.stubs(:[]).with(:refresh).returns(myother_bogus_cmd) provider.expects(:run).with(myother_bogus_cmd) @exec_resource.refresh end it "should call provider run with the specified command if the refresh parameter is not set" do provider = stub 'provider' status = stubs "process_status" status.stubs(:exitstatus).returns("0") provider.expects(:run).with(@bogus_cmd).returns(["silly output", status]) @exec_resource.stubs(:provider).returns(provider) @exec_resource.refresh end it "should not run the provider if check_all_attributes is false" do @exec_resource.stubs(:check_all_attributes).returns false provider = stub 'provider' provider.expects(:run).never @exec_resource.stubs(:provider).returns(provider) @exec_resource.refresh end end describe "relative and absolute commands vs path" do let :type do Puppet::Type.type(:exec) end let :rel do 'echo' end let :abs do make_absolute('/bin/echo') end let :path do make_absolute('/bin') end it "should fail with relative command and no path" do expect { type.new(:command => rel) }. to raise_error Puppet::Error, /no path was specified/ end it "should accept a relative command with a path" do expect(type.new(:command => rel, :path => path)).to be end it "should accept an absolute command with no path" do expect(type.new(:command => abs)).to be end it "should accept an absolute command with a path" do expect(type.new(:command => abs, :path => path)).to be end end describe "when providing a umask" do it "should fail if an invalid umask is used" do resource = Puppet::Type.type(:exec).new :command => @command expect { resource[:umask] = '0028'}.to raise_error(Puppet::ResourceError, /umask specification is invalid/) expect { resource[:umask] = '28' }.to raise_error(Puppet::ResourceError, /umask specification is invalid/) end end end puppet-5.5.10/spec/unit/type/file_spec.rb0000644005276200011600000016400113417161722020165 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:file) do include PuppetSpec::Files let(:path) { tmpfile('file_testing') } let(:file) { described_class.new(:path => path, :catalog => catalog) } let(:provider) { file.provider } let(:catalog) { Puppet::Resource::Catalog.new } before do Puppet.features.stubs("posix?").returns(true) end describe "the path parameter" do describe "on POSIX systems", :if => Puppet.features.posix? do it "should remove trailing slashes" do file[:path] = "/foo/bar/baz/" expect(file[:path]).to eq("/foo/bar/baz") end it "should remove double slashes" do file[:path] = "/foo/bar//baz" expect(file[:path]).to eq("/foo/bar/baz") end it "should remove triple slashes" do file[:path] = "/foo/bar///baz" expect(file[:path]).to eq("/foo/bar/baz") end it "should remove trailing double slashes" do file[:path] = "/foo/bar/baz//" expect(file[:path]).to eq("/foo/bar/baz") end it "should leave a single slash alone" do file[:path] = "/" expect(file[:path]).to eq("/") end it "should accept and collapse a double-slash at the start of the path" do file[:path] = "//tmp/xxx" expect(file[:path]).to eq('/tmp/xxx') end it "should accept and collapse a triple-slash at the start of the path" do file[:path] = "///tmp/xxx" expect(file[:path]).to eq('/tmp/xxx') end end describe "on Windows systems", :if => Puppet.features.microsoft_windows? do it "should remove trailing slashes" do file[:path] = "X:/foo/bar/baz/" expect(file[:path]).to eq("X:/foo/bar/baz") end it "should remove double slashes" do file[:path] = "X:/foo/bar//baz" expect(file[:path]).to eq("X:/foo/bar/baz") end it "should remove trailing double slashes" do file[:path] = "X:/foo/bar/baz//" expect(file[:path]).to eq("X:/foo/bar/baz") end it "should leave a drive letter with a slash alone" do file[:path] = "X:/" expect(file[:path]).to eq("X:/") end it "should not accept a drive letter without a slash" do expect { file[:path] = "X:" }.to raise_error(/File paths must be fully qualified/) end describe "when using UNC filenames", :if => Puppet.features.microsoft_windows? do it "should remove trailing slashes" do file[:path] = "//localhost/foo/bar/baz/" expect(file[:path]).to eq("//localhost/foo/bar/baz") end it "should remove double slashes" do file[:path] = "//localhost/foo/bar//baz" expect(file[:path]).to eq("//localhost/foo/bar/baz") end it "should remove trailing double slashes" do file[:path] = "//localhost/foo/bar/baz//" expect(file[:path]).to eq("//localhost/foo/bar/baz") end it "should remove a trailing slash from a sharename" do file[:path] = "//localhost/foo/" expect(file[:path]).to eq("//localhost/foo") end it "should not modify a sharename" do file[:path] = "//localhost/foo" expect(file[:path]).to eq("//localhost/foo") end end end end describe "the backup parameter" do [false, 'false', :false].each do |value| it "should disable backup if the value is #{value.inspect}" do file[:backup] = value expect(file[:backup]).to eq(false) end end [true, 'true', '.puppet-bak'].each do |value| it "should use .puppet-bak if the value is #{value.inspect}" do file[:backup] = value expect(file[:backup]).to eq('.puppet-bak') end end it "should use the provided value if it's any other string" do file[:backup] = "over there" expect(file[:backup]).to eq("over there") end it "should fail if backup is set to anything else" do expect do file[:backup] = 97 end.to raise_error(Puppet::Error, /Invalid backup type 97/) end end describe "the recurse parameter" do it "should default to recursion being disabled" do expect(file[:recurse]).to be_falsey end [true, "true", "remote"].each do |value| it "should consider #{value} to enable recursion" do file[:recurse] = value expect(file[:recurse]).to be_truthy end end it "should not allow numbers" do expect { file[:recurse] = 10 }.to raise_error( Puppet::Error, /Parameter recurse failed on File\[[^\]]+\]: Invalid recurse value 10/) end [false, "false"].each do |value| it "should consider #{value} to disable recursion" do file[:recurse] = value expect(file[:recurse]).to be_falsey end end end describe "the recurselimit parameter" do it "should accept integers" do file[:recurselimit] = 12 expect(file[:recurselimit]).to eq(12) end it "should munge string numbers to number numbers" do file[:recurselimit] = '12' expect(file[:recurselimit]).to eq(12) end it "should fail if given a non-number" do expect do file[:recurselimit] = 'twelve' end.to raise_error(Puppet::Error, /Invalid value "twelve"/) end end describe "the replace parameter" do [true, :true, :yes].each do |value| it "should consider #{value} to be true" do file[:replace] = value expect(file[:replace]).to be_truthy end end [false, :false, :no].each do |value| it "should consider #{value} to be false" do file[:replace] = value expect(file[:replace]).to be_falsey end end end describe ".instances" do it "should return an empty array" do expect(described_class.instances).to eq([]) end end describe "#bucket" do it "should return nil if backup is off" do file[:backup] = false expect(file.bucket).to eq(nil) end it "should not return a bucket if using a file extension for backup" do file[:backup] = '.backup' expect(file.bucket).to eq(nil) end it "should return the default filebucket if using the 'puppet' filebucket" do file[:backup] = 'puppet' bucket = stub('bucket') file.stubs(:default_bucket).returns bucket expect(file.bucket).to eq(bucket) end it "should fail if using a remote filebucket and no catalog exists" do file.catalog = nil file[:backup] = 'my_bucket' expect { file.bucket }.to raise_error(Puppet::Error, "Can not find filebucket for backups without a catalog") end it "should fail if the specified filebucket isn't in the catalog" do file[:backup] = 'my_bucket' expect { file.bucket }.to raise_error(Puppet::Error, "Could not find filebucket my_bucket specified in backup") end it "should use the specified filebucket if it is in the catalog" do file[:backup] = 'my_bucket' filebucket = Puppet::Type.type(:filebucket).new(:name => 'my_bucket') catalog.add_resource(filebucket) expect(file.bucket).to eq(filebucket.bucket) end end describe "#asuser" do before :each do # Mocha won't let me just stub SUIDManager.asuser to yield and return, # but it will do exactly that if we're not root. Puppet::Util::SUIDManager.stubs(:root?).returns false end it "should return the desired owner if they can write to the parent directory" do file[:owner] = 1001 FileTest.stubs(:writable?).with(File.dirname file[:path]).returns true expect(file.asuser).to eq(1001) end it "should return nil if the desired owner can't write to the parent directory" do file[:owner] = 1001 FileTest.stubs(:writable?).with(File.dirname file[:path]).returns false expect(file.asuser).to eq(nil) end it "should return nil if not managing owner" do expect(file.asuser).to eq(nil) end end describe "#exist?" do it "should be considered existent if it can be stat'ed" do file.expects(:stat).returns mock('stat') expect(file).to be_exist end it "should be considered nonexistent if it can not be stat'ed" do file.expects(:stat).returns nil expect(file).to_not be_exist end end describe "#eval_generate" do before do @graph = stub 'graph', :add_edge => nil catalog.stubs(:relationship_graph).returns @graph end it "should recurse if recursion is enabled" do resource = stub('resource', :[] => 'resource') file.expects(:recurse).returns [resource] file[:recurse] = true expect(file.eval_generate).to eq([resource]) end it "should not recurse if recursion is disabled" do file.expects(:recurse).never file[:recurse] = false expect(file.eval_generate).to eq([]) end end describe "#ancestors" do it "should return the ancestors of the file, in ascending order" do file = described_class.new(:path => make_absolute("/tmp/foo/bar/baz/qux")) pieces = %W[#{make_absolute('/')} tmp foo bar baz] ancestors = file.ancestors expect(ancestors).not_to be_empty ancestors.reverse.each_with_index do |path,i| expect(path).to eq(File.join(*pieces[0..i])) end end end describe "#flush" do it "should flush all properties that respond to :flush" do file[:source] = File.expand_path(__FILE__) file.parameter(:source).expects(:flush) file.flush end it "should reset its stat reference" do FileUtils.touch(path) stat1 = file.stat expect(file.stat).to equal(stat1) file.flush expect(file.stat).not_to equal(stat1) end end describe "#initialize" do it "should remove a trailing slash from the title to create the path" do title = File.expand_path("/abc/\n\tdef/") file = described_class.new(:title => title) expect(file[:path]).to eq(title) end it "should allow a single slash for a title and create the path" do title = File.expand_path("/") file = described_class.new(:title => title) expect(file[:path]).to eq(title) end it "should allow multiple slashes for a title and create the path" do title = File.expand_path("/") + "//" file = described_class.new(:title => title) expect(file[:path]).to eq(File.expand_path("/")) end it "should set a desired 'ensure' value if none is set and 'content' is set" do file = described_class.new(:path => path, :content => "/foo/bar") expect(file[:ensure]).to eq(:file) end it "should set a desired 'ensure' value if none is set and 'target' is set", :if => described_class.defaultprovider.feature?(:manages_symlinks) do file = described_class.new(:path => path, :target => File.expand_path(__FILE__)) expect(file[:ensure]).to eq(:link) end describe "marking parameters as sensitive" do it "marks sensitive, content, and ensure as sensitive when source is sensitive" do resource = Puppet::Resource.new(:file, make_absolute("/tmp/foo"), :parameters => {:source => make_absolute('/tmp/bar')}, :sensitive_parameters => [:source]) file = described_class.new(resource) expect(file.parameter(:source).sensitive).to eq true expect(file.property(:content).sensitive).to eq true expect(file.property(:ensure).sensitive).to eq true end it "marks ensure as sensitive when content is sensitive" do resource = Puppet::Resource.new(:file, make_absolute("/tmp/foo"), :parameters => {:content => 'hello world!'}, :sensitive_parameters => [:content]) file = described_class.new(resource) expect(file.property(:ensure).sensitive).to eq true end end end describe "#mark_children_for_purging" do it "should set each child's ensure to absent" do paths = %w[foo bar baz] children = {} paths.each do |child| children[child] = described_class.new(:path => File.join(path, child), :ensure => :present) end file.mark_children_for_purging(children) expect(children.length).to eq(3) children.values.each do |child| expect(child[:ensure]).to eq(:absent) end end it "should skip children which have a source" do child = described_class.new(:path => path, :ensure => :present, :source => File.expand_path(__FILE__)) file.mark_children_for_purging('foo' => child) expect(child[:ensure]).to eq(:present) end end describe "#newchild" do it "should create a new resource relative to the parent" do child = file.newchild('bar') expect(child).to be_a(described_class) expect(child[:path]).to eq(File.join(file[:path], 'bar')) end { :ensure => :present, :recurse => true, :recurselimit => 5, :target => "some_target", :source => File.expand_path("some_source"), }.each do |param, value| it "should omit the #{param} parameter", :if => described_class.defaultprovider.feature?(:manages_symlinks) do # Make a new file, because we have to set the param at initialization # or it wouldn't be copied regardless. file = described_class.new(:path => path, param => value) child = file.newchild('bar') expect(child[param]).not_to eq(value) end end it "should copy all of the parent resource's 'should' values that were set at initialization" do parent = described_class.new(:path => path, :owner => 'root', :group => 'wheel') child = parent.newchild("my/path") expect(child[:owner]).to eq('root') expect(child[:group]).to eq('wheel') end it "should not copy default values to the new child" do child = file.newchild("my/path") expect(child.original_parameters).not_to include(:backup) end it "should not copy values to the child which were set by the source" do source = File.expand_path(__FILE__) file[:source] = source metadata = stub 'metadata', :owner => "root", :group => "root", :mode => '0755', :ftype => "file", :checksum => "{md5}whatever", :checksum_type => "md5", :source => source file.parameter(:source).stubs(:metadata).returns metadata file.parameter(:source).copy_source_values file.class.expects(:new).with { |params| params[:group].nil? } file.newchild("my/path") end end describe "#purge?" do it "should return false if purge is not set" do expect(file).to_not be_purge end it "should return true if purge is set to true" do file[:purge] = true expect(file).to be_purge end it "should return false if purge is set to false" do file[:purge] = false expect(file).to_not be_purge end end describe "#recurse" do before do file[:recurse] = true @metadata = Puppet::FileServing::Metadata end describe "and a source is set" do it "should pass the already-discovered resources to recurse_remote" do file[:source] = File.expand_path(__FILE__) file.stubs(:recurse_local).returns(:foo => "bar") file.expects(:recurse_remote).with(:foo => "bar").returns [] file.recurse end end describe "and a target is set" do it "should use recurse_link" do file[:target] = File.expand_path(__FILE__) file.stubs(:recurse_local).returns(:foo => "bar") file.expects(:recurse_link).with(:foo => "bar").returns [] file.recurse end end it "should use recurse_local if recurse is not remote" do file.expects(:recurse_local).returns({}) file.recurse end it "should not use recurse_local if recurse is remote" do file[:recurse] = :remote file.expects(:recurse_local).never file.recurse end it "should return the generated resources as an array sorted by file path" do one = stub 'one', :[] => "/one" two = stub 'two', :[] => "/one/two" three = stub 'three', :[] => "/three" file.expects(:recurse_local).returns(:one => one, :two => two, :three => three) expect(file.recurse).to eq([one, two, three]) end describe "and purging is enabled" do before do file[:purge] = true end it "should mark each file for removal" do local = described_class.new(:path => path, :ensure => :present) file.expects(:recurse_local).returns("local" => local) file.recurse expect(local[:ensure]).to eq(:absent) end it "should not remove files that exist in the remote repository" do file[:source] = File.expand_path(__FILE__) file.expects(:recurse_local).returns({}) remote = described_class.new(:path => path, :source => File.expand_path(__FILE__), :ensure => :present) file.expects(:recurse_remote).with { |hash| hash["remote"] = remote } file.recurse expect(remote[:ensure]).not_to eq(:absent) end end end describe "#remove_less_specific_files" do it "should remove any nested files that are already in the catalog" do foo = described_class.new :path => File.join(file[:path], 'foo') bar = described_class.new :path => File.join(file[:path], 'bar') baz = described_class.new :path => File.join(file[:path], 'baz') catalog.add_resource(foo) catalog.add_resource(bar) expect(file.remove_less_specific_files([foo, bar, baz])).to eq([baz]) end end describe "#recurse?" do it "should be true if recurse is true" do file[:recurse] = true expect(file).to be_recurse end it "should be true if recurse is remote" do file[:recurse] = :remote expect(file).to be_recurse end it "should be false if recurse is false" do file[:recurse] = false expect(file).to_not be_recurse end end describe "#recurse_link" do before do @first = stub 'first', :relative_path => "first", :full_path => "/my/first", :ftype => "directory" @second = stub 'second', :relative_path => "second", :full_path => "/my/second", :ftype => "file" @resource = stub 'file', :[]= => nil end it "should pass its target to the :perform_recursion method" do file[:target] = "mylinks" file.expects(:perform_recursion).with("mylinks").returns [@first] file.stubs(:newchild).returns @resource file.recurse_link({}) end it "should ignore the recursively-found '.' file and configure the top-level file to create a directory" do @first.stubs(:relative_path).returns "." file[:target] = "mylinks" file.expects(:perform_recursion).with("mylinks").returns [@first] file.stubs(:newchild).never file.expects(:[]=).with(:ensure, :directory) file.recurse_link({}) end it "should create a new child resource for each generated metadata instance's relative path that doesn't already exist in the children hash" do file.expects(:perform_recursion).returns [@first, @second] file.expects(:newchild).with(@first.relative_path).returns @resource file.recurse_link("second" => @resource) end it "should not create a new child resource for paths that already exist in the children hash" do file.expects(:perform_recursion).returns [@first] file.expects(:newchild).never file.recurse_link("first" => @resource) end it "should set the target to the full path of discovered file and set :ensure to :link if the file is not a directory", :if => described_class.defaultprovider.feature?(:manages_symlinks) do file.stubs(:perform_recursion).returns [@first, @second] file.recurse_link("first" => @resource, "second" => file) expect(file[:ensure]).to eq(:link) expect(file[:target]).to eq("/my/second") end it "should :ensure to :directory if the file is a directory" do file.stubs(:perform_recursion).returns [@first, @second] file.recurse_link("first" => file, "second" => @resource) expect(file[:ensure]).to eq(:directory) end it "should return a hash with both created and existing resources with the relative paths as the hash keys" do file.expects(:perform_recursion).returns [@first, @second] file.stubs(:newchild).returns file expect(file.recurse_link("second" => @resource)).to eq({"second" => @resource, "first" => file}) end end describe "#recurse_local" do before do @metadata = stub 'metadata', :relative_path => "my/file" end it "should pass its path to the :perform_recursion method" do file.expects(:perform_recursion).with(file[:path]).returns [@metadata] file.stubs(:newchild) file.recurse_local end it "should return an empty hash if the recursion returns nothing" do file.expects(:perform_recursion).returns nil expect(file.recurse_local).to eq({}) end it "should create a new child resource with each generated metadata instance's relative path" do file.expects(:perform_recursion).returns [@metadata] file.expects(:newchild).with(@metadata.relative_path).returns "fiebar" file.recurse_local end it "should not create a new child resource for the '.' directory" do @metadata.stubs(:relative_path).returns "." file.expects(:perform_recursion).returns [@metadata] file.expects(:newchild).never file.recurse_local end it "should return a hash of the created resources with the relative paths as the hash keys" do file.expects(:perform_recursion).returns [@metadata] file.expects(:newchild).with("my/file").returns "fiebar" expect(file.recurse_local).to eq({"my/file" => "fiebar"}) end it "should set checksum_type to none if this file checksum is none" do file[:checksum] = :none Puppet::FileServing::Metadata.indirection.expects(:search).with { |path,params| params[:checksum_type] == :none }.returns [@metadata] file.expects(:newchild).with("my/file").returns "fiebar" file.recurse_local end end describe "#recurse_remote" do let(:my) { File.expand_path('/my') } before do file[:source] = "puppet://foo/bar" @first = Puppet::FileServing::Metadata.new(my, :relative_path => "first") @second = Puppet::FileServing::Metadata.new(my, :relative_path => "second") @first.stubs(:ftype).returns "directory" @second.stubs(:ftype).returns "directory" @parameter = stub 'property', :metadata= => nil @resource = stub 'file', :[]= => nil, :parameter => @parameter end it "should pass its source to the :perform_recursion method" do data = Puppet::FileServing::Metadata.new(File.expand_path("/whatever"), :relative_path => "foobar") file.expects(:perform_recursion).with("puppet://foo/bar").returns [data] file.stubs(:newchild).returns @resource file.recurse_remote({}) end it "should not recurse when the remote file is not a directory" do data = Puppet::FileServing::Metadata.new(File.expand_path("/whatever"), :relative_path => ".") data.stubs(:ftype).returns "file" file.expects(:perform_recursion).with("puppet://foo/bar").returns [data] file.expects(:newchild).never file.recurse_remote({}) end it "should set the source of each returned file to the searched-for URI plus the found relative path" do @first.expects(:source=).with File.join("puppet://foo/bar", @first.relative_path) file.expects(:perform_recursion).returns [@first] file.stubs(:newchild).returns @resource file.recurse_remote({}) end it "should create a new resource for any relative file paths that do not already have a resource" do file.stubs(:perform_recursion).returns [@first] file.expects(:newchild).with("first").returns @resource expect(file.recurse_remote({})).to eq({"first" => @resource}) end it "should not create a new resource for any relative file paths that do already have a resource" do file.stubs(:perform_recursion).returns [@first] file.expects(:newchild).never file.recurse_remote("first" => @resource) end it "should set the source of each resource to the source of the metadata" do file.stubs(:perform_recursion).returns [@first] @resource.stubs(:[]=) @resource.expects(:[]=).with(:source, File.join("puppet://foo/bar", @first.relative_path)) file.recurse_remote("first" => @resource) end it "should set the checksum parameter based on the metadata" do file.stubs(:perform_recursion).returns [@first] @resource.stubs(:[]=) @resource.expects(:[]=).with(:checksum, "md5") file.recurse_remote("first" => @resource) end it "should store the metadata in the source property for each resource so the source does not have to requery the metadata" do file.stubs(:perform_recursion).returns [@first] @resource.expects(:parameter).with(:source).returns @parameter @parameter.expects(:metadata=).with(@first) file.recurse_remote("first" => @resource) end it "should not create a new resource for the '.' file" do @first.stubs(:relative_path).returns "." file.stubs(:perform_recursion).returns [@first] file.expects(:newchild).never file.recurse_remote({}) end it "should store the metadata in the main file's source property if the relative path is '.'" do @first.stubs(:relative_path).returns "." file.stubs(:perform_recursion).returns [@first] file.parameter(:source).expects(:metadata=).with @first file.recurse_remote("first" => @resource) end it "should update the main file's checksum parameter if the relative path is '.'" do @first.stubs(:relative_path).returns "." file.stubs(:perform_recursion).returns [@first] file.stubs(:[]=) file.expects(:[]=).with(:checksum, "md5") file.recurse_remote("first" => @resource) end describe "and multiple sources are provided" do let(:sources) do h = {} %w{/a /b /c /d}.each do |key| h[key] = URI.unescape(Puppet::Util.path_to_uri(File.expand_path(key)).to_s) end h end describe "and :sourceselect is set to :first" do it "should create file instances for the results for the first source to return any values" do data = Puppet::FileServing::Metadata.new(File.expand_path("/whatever"), :relative_path => "foobar") file[:source] = sources.keys.sort.map { |key| File.expand_path(key) } file.expects(:perform_recursion).with(sources['/a']).returns nil file.expects(:perform_recursion).with(sources['/b']).returns [] file.expects(:perform_recursion).with(sources['/c']).returns [data] file.expects(:perform_recursion).with(sources['/d']).never file.expects(:newchild).with("foobar").returns @resource file.recurse_remote({}) end end describe "and :sourceselect is set to :all" do before do file[:sourceselect] = :all end it "should return every found file that is not in a previous source" do klass = Puppet::FileServing::Metadata file[:source] = abs_path = %w{/a /b /c /d}.map {|f| File.expand_path(f) } file.stubs(:newchild).returns @resource one = [klass.new(abs_path[0], :relative_path => "a")] file.expects(:perform_recursion).with(sources['/a']).returns one file.expects(:newchild).with("a").returns @resource two = [klass.new(abs_path[1], :relative_path => "a"), klass.new(abs_path[1], :relative_path => "b")] file.expects(:perform_recursion).with(sources['/b']).returns two file.expects(:newchild).with("b").returns @resource three = [klass.new(abs_path[2], :relative_path => "a"), klass.new(abs_path[2], :relative_path => "c")] file.expects(:perform_recursion).with(sources['/c']).returns three file.expects(:newchild).with("c").returns @resource file.expects(:perform_recursion).with(sources['/d']).returns [] file.recurse_remote({}) end end end end describe "#perform_recursion", :uses_checksums => true do it "should use Metadata to do its recursion" do Puppet::FileServing::Metadata.indirection.expects(:search) file.perform_recursion(file[:path]) end it "should use the provided path as the key to the search" do Puppet::FileServing::Metadata.indirection.expects(:search).with { |key, options| key == "/foo" } file.perform_recursion("/foo") end it "should return the results of the metadata search" do Puppet::FileServing::Metadata.indirection.expects(:search).returns "foobar" expect(file.perform_recursion(file[:path])).to eq("foobar") end it "should pass its recursion value to the search" do file[:recurse] = true Puppet::FileServing::Metadata.indirection.expects(:search).with { |key, options| options[:recurse] == true } file.perform_recursion(file[:path]) end it "should pass true if recursion is remote" do file[:recurse] = :remote Puppet::FileServing::Metadata.indirection.expects(:search).with { |key, options| options[:recurse] == true } file.perform_recursion(file[:path]) end it "should pass its recursion limit value to the search" do file[:recurselimit] = 10 Puppet::FileServing::Metadata.indirection.expects(:search).with { |key, options| options[:recurselimit] == 10 } file.perform_recursion(file[:path]) end it "should configure the search to ignore or manage links" do file[:links] = :manage Puppet::FileServing::Metadata.indirection.expects(:search).with { |key, options| options[:links] == :manage } file.perform_recursion(file[:path]) end it "should pass its 'ignore' setting to the search if it has one" do file[:ignore] = %w{.svn CVS} Puppet::FileServing::Metadata.indirection.expects(:search).with { |key, options| options[:ignore] == %w{.svn CVS} } file.perform_recursion(file[:path]) end with_digest_algorithms do it "it should pass its 'checksum' setting #{metadata[:digest_algorithm]} to the search" do file[:source] = File.expand_path('/foo') Puppet::FileServing::Metadata.indirection.expects(:search).with { |key, options| options[:checksum_type] == digest_algorithm.intern } file.perform_recursion(file[:path]) end end end describe "#remove_existing" do it "should do nothing if the file doesn't exist" do expect(file.remove_existing(:file)).to eq(false) end it "should fail if it can't backup the file" do # Default: file[:backup] = true file.stubs(:stat).returns stub('stat', :ftype => 'file') file.stubs(:perform_backup).returns false expect { file.remove_existing(:file) }.to raise_error(Puppet::Error, /Could not back up; will not remove/) end describe "backing up directories" do it "should not backup directories if backup is true and force is false" do # Default: file[:backup] = true file[:force] = false file.stubs(:stat).returns stub('stat', :ftype => 'directory') file.expects(:perform_backup).never file.expects(:warning).with("Could not back up file of type directory") expect(file.remove_existing(:file)).to eq(false) end it "should backup directories if backup is true and force is true" do # Default: file[:backup] = true file[:force] = true file.stubs(:stat).returns stub('stat', :ftype => 'directory') FileUtils.expects(:rmtree).with(file[:path]) file.expects(:perform_backup).returns(true) expect(file.remove_existing(:file)).to eq(true) end end it "should not do anything if the file is already the right type and not a link" do file.stubs(:stat).returns stub('stat', :ftype => 'file') expect(file.remove_existing(:file)).to eq(false) end it "should not remove directories and should not invalidate the stat unless force is true" do file[:force] = false # Actually call stat to set @needs_stat to nil file.stat file.stubs(:stat).returns stub('stat', :ftype => 'directory') expect(file.instance_variable_get(:@stat)).to eq(nil) end it "should remove a directory if backup is true and force is true" do # Default: file[:backup] = true file[:force] = true file.stubs(:stat).returns stub('stat', :ftype => 'directory') FileUtils.expects(:rmtree).with(file[:path]) expect(file.remove_existing(:file)).to eq(true) end it "should remove an existing file" do file.stubs(:perform_backup).returns true FileUtils.touch(path) expect(file.remove_existing(:directory)).to eq(true) expect(Puppet::FileSystem.exist?(file[:path])).to eq(false) end it "should remove an existing link", :if => described_class.defaultprovider.feature?(:manages_symlinks) do file.stubs(:perform_backup).returns true target = tmpfile('link_target') FileUtils.touch(target) Puppet::FileSystem.symlink(target, path) file[:target] = target expect(file.remove_existing(:directory)).to eq(true) expect(Puppet::FileSystem.exist?(file[:path])).to eq(false) end it "should fail if the file is not a directory, link, file, fifo, socket, or is unknown" do file.stubs(:stat).returns stub('stat', :ftype => 'blockSpecial') file.expects(:warning).with("Could not back up file of type blockSpecial") expect { file.remove_existing(:file) }.to raise_error(Puppet::Error, /Could not remove files of type blockSpecial/) end it "should invalidate the existing stat of the file" do # Actually call stat to set @needs_stat to nil file.stat file.stubs(:stat).returns stub('stat', :ftype => 'file') Puppet::FileSystem.stubs(:unlink) expect(file.remove_existing(:directory)).to eq(true) expect(file.instance_variable_get(:@stat)).to eq(:needs_stat) end end describe "#retrieve" do it "should copy the source values if the 'source' parameter is set" do file[:source] = File.expand_path('/foo/bar') file.parameter(:source).expects(:copy_source_values) file.retrieve end end describe "#should_be_file?" do it "should have a method for determining if the file should be a normal file" do expect(file).to respond_to(:should_be_file?) end it "should be a file if :ensure is set to :file" do file[:ensure] = :file expect(file).to be_should_be_file end it "should be a file if :ensure is set to :present and the file exists as a normal file" do file.stubs(:stat).returns(mock('stat', :ftype => "file")) file[:ensure] = :present expect(file).to be_should_be_file end it "should not be a file if :ensure is set to something other than :file" do file[:ensure] = :directory expect(file).to_not be_should_be_file end it "should not be a file if :ensure is set to :present and the file exists but is not a normal file" do file.stubs(:stat).returns(mock('stat', :ftype => "directory")) file[:ensure] = :present expect(file).to_not be_should_be_file end it "should be a file if :ensure is not set and :content is" do file[:content] = "foo" expect(file).to be_should_be_file end it "should be a file if neither :ensure nor :content is set but the file exists as a normal file" do file.stubs(:stat).returns(mock("stat", :ftype => "file")) expect(file).to be_should_be_file end it "should not be a file if neither :ensure nor :content is set but the file exists but not as a normal file" do file.stubs(:stat).returns(mock("stat", :ftype => "directory")) expect(file).to_not be_should_be_file end end describe "#stat", :if => described_class.defaultprovider.feature?(:manages_symlinks) do before do target = tmpfile('link_target') FileUtils.touch(target) Puppet::FileSystem.symlink(target, path) file[:target] = target file[:links] = :manage # so we always use :lstat end it "should stat the target if it is following links" do file[:links] = :follow expect(file.stat.ftype).to eq('file') end it "should stat the link if is it not following links" do file[:links] = :manage expect(file.stat.ftype).to eq('link') end it "should return nil if the file does not exist" do file[:path] = make_absolute('/foo/bar/baz/non-existent') expect(file.stat).to be_nil end it "should return nil if the file cannot be stat'ed" do dir = tmpfile('link_test_dir') child = File.join(dir, 'some_file') Dir.mkdir(dir) File.chmod(0, dir) file[:path] = child expect(file.stat).to be_nil # chmod it back so we can clean it up File.chmod(0777, dir) end it "should return nil if parts of path are no directories" do regular_file = tmpfile('ENOTDIR_test') FileUtils.touch(regular_file) impossible_child = File.join(regular_file, 'some_file') file[:path] = impossible_child expect(file.stat).to be_nil end it "should return the stat instance" do expect(file.stat).to be_a(File::Stat) end it "should cache the stat instance" do expect(file.stat.object_id).to eql(file.stat.object_id) end end describe "#write" do describe "when validating the checksum" do before { file.stubs(:validate_checksum?).returns(true) } it "should fail if the checksum parameter and content checksums do not match" do checksum = stub('checksum_parameter', :sum => 'checksum_b', :sum_file => 'checksum_b') file.stubs(:parameter).with(:checksum).returns(checksum) file.stubs(:parameter).with(:source).returns(nil) property = stub('content_property', :actual_content => "something", :length => "something".length, :write => 'checksum_a') file.stubs(:property).with(:content).returns(property) expect { file.write property }.to raise_error(Puppet::Error) end end describe "when not validating the checksum" do before { file.stubs(:validate_checksum?).returns(false) } it "should not fail if the checksum property and content checksums do not match" do checksum = stub('checksum_parameter', :sum => 'checksum_b') file.stubs(:parameter).with(:checksum).returns(checksum) file.stubs(:parameter).with(:source).returns(nil) property = stub('content_property', :actual_content => "something", :length => "something".length, :write => 'checksum_a') file.stubs(:property).with(:content).returns(property) expect { file.write property }.to_not raise_error end end describe "when resource mode is supplied" do before { file.stubs(:property_fix) } context "and writing temporary files" do before { file.stubs(:write_temporary_file?).returns(true) } it "should convert symbolic mode to int" do file[:mode] = 'oga=r' Puppet::Util.expects(:replace_file).with(file[:path], 0444) file.write end it "should support int modes" do file[:mode] = '0444' Puppet::Util.expects(:replace_file).with(file[:path], 0444) file.write end end context "and not writing temporary files" do before { file.stubs(:write_temporary_file?).returns(false) } it "should set a umask of 0" do file[:mode] = 'oga=r' Puppet::Util.expects(:withumask).with(0) file.write end it "should convert symbolic mode to int" do file[:mode] = 'oga=r' File.expects(:open).with(file[:path], anything, 0444) file.write end it "should support int modes" do file[:mode] = '0444' File.expects(:open).with(file[:path], anything, 0444) file.write end end end describe "when resource mode is not supplied" do context "and content is supplied" do it "should default to 0644 mode" do file = described_class.new(:path => path, :content => "file content") file.write file.parameter(:content) expect(File.stat(file[:path]).mode & 0777).to eq(0644) end end context "and no content is supplied" do it "should use puppet's default umask of 022" do file = described_class.new(:path => path) umask_from_the_user = 0777 Puppet::Util.withumask(umask_from_the_user) do file.write end expect(File.stat(file[:path]).mode & 0777).to eq(0644) end end end end describe "#fail_if_checksum_is_wrong" do it "should fail if the checksum of the file doesn't match the expected one" do expect do file.instance_eval do parameter(:checksum).stubs(:sum_file).returns('wrong!!') fail_if_checksum_is_wrong(self[:path], 'anything!') end end.to raise_error(Puppet::Error, /File written to disk did not match checksum/) end it "should not fail if the checksum is correct" do expect do file.instance_eval do parameter(:checksum).stubs(:sum_file).returns('anything!') fail_if_checksum_is_wrong(self[:path], 'anything!') end end.not_to raise_error end it "should not fail if the checksum is absent" do expect do file.instance_eval do parameter(:checksum).stubs(:sum_file).returns(nil) fail_if_checksum_is_wrong(self[:path], 'anything!') end end.not_to raise_error end end describe "#write_temporary_file?" do it "should be true if the file has specified content" do file[:content] = 'some content' expect(file.send(:write_temporary_file?)).to be_truthy end it "should be true if the file has specified source" do file[:source] = File.expand_path('/tmp/foo') expect(file.send(:write_temporary_file?)).to be_truthy end it "should be false if the file has neither content nor source" do expect(file.send(:write_temporary_file?)).to be_falsey end end describe "#property_fix" do { :mode => '0777', :owner => 'joeuser', :group => 'joeusers', :seluser => 'seluser', :selrole => 'selrole', :seltype => 'seltype', :selrange => 'selrange' }.each do |name,value| it "should sync the #{name} property if it's not in sync" do file[name] = value prop = file.property(name) prop.expects(:retrieve) prop.expects(:safe_insync?).returns false prop.expects(:sync) file.send(:property_fix) end end end describe "when autorequiring" do describe "target" do it "should require file resource when specified with the target property", :if => described_class.defaultprovider.feature?(:manages_symlinks) do file = described_class.new(:path => File.expand_path("/foo"), :ensure => :directory) link = described_class.new(:path => File.expand_path("/bar"), :ensure => :link, :target => File.expand_path("/foo")) catalog.add_resource file catalog.add_resource link reqs = link.autorequire expect(reqs.size).to eq(1) expect(reqs[0].source).to eq(file) expect(reqs[0].target).to eq(link) end it "should require file resource when specified with the ensure property" do file = described_class.new(:path => File.expand_path("/foo"), :ensure => :directory) link = described_class.new(:path => File.expand_path("/bar"), :ensure => File.expand_path("/foo")) catalog.add_resource file catalog.add_resource link reqs = link.autorequire expect(reqs.size).to eq(1) expect(reqs[0].source).to eq(file) expect(reqs[0].target).to eq(link) end it "should not require target if target is not managed", :if => described_class.defaultprovider.feature?(:manages_symlinks) do link = described_class.new(:path => File.expand_path('/foo'), :ensure => :link, :target => '/bar') catalog.add_resource link expect(link.autorequire.size).to eq(0) end end describe "directories" do it "should autorequire its parent directory" do dir = described_class.new(:path => File.dirname(path)) catalog.add_resource file catalog.add_resource dir reqs = file.autorequire expect(reqs[0].source).to eq(dir) expect(reqs[0].target).to eq(file) end it "should autorequire its nearest ancestor directory" do dir = described_class.new(:path => File.dirname(path)) grandparent = described_class.new(:path => File.dirname(File.dirname(path))) catalog.add_resource file catalog.add_resource dir catalog.add_resource grandparent reqs = file.autorequire expect(reqs.length).to eq(1) expect(reqs[0].source).to eq(dir) expect(reqs[0].target).to eq(file) end it "should not autorequire anything when there is no nearest ancestor directory" do catalog.add_resource file expect(file.autorequire).to be_empty end it "should not autorequire its parent dir if its parent dir is itself" do file[:path] = File.expand_path('/') catalog.add_resource file expect(file.autorequire).to be_empty end describe "on Windows systems", :if => Puppet.features.microsoft_windows? do describe "when using UNC filenames" do it "should autorequire its parent directory" do file[:path] = '//localhost/foo/bar/baz' dir = described_class.new(:path => "//localhost/foo/bar") catalog.add_resource file catalog.add_resource dir reqs = file.autorequire expect(reqs[0].source).to eq(dir) expect(reqs[0].target).to eq(file) end it "should autorequire its nearest ancestor directory" do file = described_class.new(:path => "//localhost/foo/bar/baz/qux") dir = described_class.new(:path => "//localhost/foo/bar/baz") grandparent = described_class.new(:path => "//localhost/foo/bar") catalog.add_resource file catalog.add_resource dir catalog.add_resource grandparent reqs = file.autorequire expect(reqs.length).to eq(1) expect(reqs[0].source).to eq(dir) expect(reqs[0].target).to eq(file) end it "should not autorequire anything when there is no nearest ancestor directory" do file = described_class.new(:path => "//localhost/foo/bar/baz/qux") catalog.add_resource file expect(file.autorequire).to be_empty end it "should not autorequire its parent dir if its parent dir is itself" do file = described_class.new(:path => "//localhost/foo") catalog.add_resource file puts file.autorequire expect(file.autorequire).to be_empty end end end end end describe "when managing links", :if => Puppet.features.manages_symlinks? do require 'tempfile' before :each do Dir.mkdir(path) @target = File.join(path, "target") @link = File.join(path, "link") target = described_class.new( :ensure => :file, :path => @target, :catalog => catalog, :content => 'yayness', :mode => '0644') catalog.add_resource target @link_resource = described_class.new( :ensure => :link, :path => @link, :target => @target, :catalog => catalog, :mode => '0755') catalog.add_resource @link_resource # to prevent the catalog from trying to write state.yaml Puppet::Util::Storage.stubs(:store) end it "should preserve the original file mode and ignore the one set by the link" do @link_resource[:links] = :manage # default catalog.apply # I convert them to strings so they display correctly if there's an error. expect((Puppet::FileSystem.stat(@target).mode & 007777).to_s(8)).to eq('644') end it "should manage the mode of the followed link" do if Puppet.features.microsoft_windows? skip "Windows cannot presently manage the mode when following symlinks" else @link_resource[:links] = :follow catalog.apply expect((Puppet::FileSystem.stat(@target).mode & 007777).to_s(8)).to eq('755') end end end describe 'when using source' do # different UTF-8 widths # 1-byte A # 2-byte Űż - http://www.fileformat.info/info/unicode/char/06ff/index.htm - 0xDB 0xBF / 219 191 # 3-byte áš  - http://www.fileformat.info/info/unicode/char/16A0/index.htm - 0xE1 0x9A 0xA0 / 225 154 160 # 4-byte - http://www.fileformat.info/info/unicode/char/2070E/index.htm - 0xF0 0xA0 0x9C 0x8E / 240 160 156 142 let (:mixed_utf8) { "A\u06FF\u16A0\u{2070E}" } # AŰżáš  it 'should allow UTF-8 characters and return a UTF-8 uri' do filename = "/bar #{mixed_utf8}" source = "puppet://foo#{filename}" file[:source] = source # intercept the indirector call to provide back mocked metadata for the given URI metadata = stub 'metadata', :source => source metadata.expects(:source=) Puppet::FileServing::Metadata.indirection.expects(:find).with do |path, opts| path == source end.returns metadata uri = file.parameters[:source].uri expect(URI.unescape(uri.path)).to eq(filename) expect(uri.path.encoding).to eq(Encoding::UTF_8) end it 'should allow UTF-8 characters inside the indirector / terminus code' do filename = "/bar #{mixed_utf8}" source = "puppet://foo#{filename}" file[:source] = source # for this test to properly trigger previously errant behavior, the code for # Puppet::FileServing::Metadata.indirection.find must run and produce an # instance of Puppet::Indirector::FileMetadata::Rest that can be amended metadata = stub 'metadata', :source => source metadata.expects(:source=) require 'puppet/indirector/file_metadata/rest' Puppet::Indirector::FileMetadata::Rest.any_instance.expects(:find).with do |req| req.key == filename[1..-1] end.returns(metadata) uri = file.parameters[:source].uri expect(URI.unescape(uri.path)).to eq(filename) expect(uri.path.encoding).to eq(Encoding::UTF_8) end end describe "when using source" do before do file[:source] = File.expand_path('/one') # Contents of an empty file generate the below hash values # in case you need to add support for additional algorithms in future @checksum_values = { :md5 => 'd41d8cd98f00b204e9800998ecf8427e', :md5lite => 'd41d8cd98f00b204e9800998ecf8427e', :sha256 => 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', :sha256lite => 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', :sha1 => 'da39a3ee5e6b4b0d3255bfef95601890afd80709', :sha1lite => 'da39a3ee5e6b4b0d3255bfef95601890afd80709', :sha224 => 'd14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f', :sha384 => '38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b', :sha512 => 'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e', :mtime => 'Jan 26 13:59:49 2016', :ctime => 'Jan 26 13:59:49 2016' } end Puppet::Type::File::ParameterChecksum.value_collection.values.reject {|v| v == :none}.each do |checksum_type| describe "with checksum '#{checksum_type}'" do before do file[:checksum] = checksum_type end it 'should validate' do expect { file.validate }.to_not raise_error end it 'should fail on an invalid checksum_value' do file[:checksum_value] = '' expect { file.validate }.to raise_error(Puppet::Error, "Checksum value '' is not a valid checksum type #{checksum_type}") end it 'should validate a valid checksum_value' do file[:checksum_value] = @checksum_values[checksum_type] expect { file.validate }.to_not raise_error end end end describe "on Windows when source_permissions is `use`" do before :each do Puppet.features.stubs(:microsoft_windows?).returns true file[:source_permissions] = "use" end let(:err_message) { "Copying owner/mode/group from the" << " source file on Windows is not supported;" << " use source_permissions => ignore." } it "should issue error when retrieving" do expect { file.retrieve }.to raise_error(err_message) end it "should issue error when retrieving if only user is unspecified" do file[:group] = 2 file[:mode] = "0003" expect { file.retrieve }.to raise_error(err_message) end it "should issue error when retrieving if only group is unspecified" do file[:owner] = 1 file[:mode] = "0003" expect { file.retrieve }.to raise_error(err_message) end it "should issue error when retrieving if only mode is unspecified" do file[:owner] = 1 file[:group] = 2 expect { file.retrieve }.to raise_error(err_message) end it "should issue warning when retrieve if group, owner, and mode are all specified" do file[:owner] = 1 file[:group] = 2 file[:mode] = "0003" file.parameter(:source).expects(:copy_source_values) file.expects(:warning).with(err_message) expect { file.retrieve }.not_to raise_error end end describe "with checksum 'none'" do before do file[:checksum] = :none end it 'should raise an exception when validating' do expect { file.validate }.to raise_error(/You cannot specify source when using checksum 'none'/) end end end describe "when using content" do before do file[:content] = 'file contents' @checksum_values = { :md5 => 'd41d8cd98f00b204e9800998ecf8427e', :md5lite => 'd41d8cd98f00b204e9800998ecf8427e', :sha256 => 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', :sha256lite => 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', :sha1 => 'da39a3ee5e6b4b0d3255bfef95601890afd80709', :sha1lite => 'da39a3ee5e6b4b0d3255bfef95601890afd80709', :sha224 => 'd14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f', :sha384 => '38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b', :sha512 => 'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e', } end (Puppet::Type::File::ParameterChecksum.value_collection.values - SOURCE_ONLY_CHECKSUMS).each do |checksum_type| describe "with checksum '#{checksum_type}'" do before do file[:checksum] = checksum_type end it 'should validate' do expect { file.validate }.to_not raise_error end it 'should fail on an invalid checksum_value' do file[:checksum_value] = '' expect { file.validate }.to raise_error(Puppet::Error, "Checksum value '' is not a valid checksum type #{checksum_type}") end it 'should validate a valid checksum_value' do file[:checksum_value] = @checksum_values[checksum_type] expect { file.validate }.to_not raise_error end end end SOURCE_ONLY_CHECKSUMS.each do |checksum_type| describe "with checksum '#{checksum_type}'" do it 'should raise an exception when validating' do file[:checksum] = checksum_type expect { file.validate }.to raise_error(/You cannot specify content when using checksum '#{checksum_type}'/) end end end end describe "when checksum is none" do before do file[:checksum] = :none end it 'should validate' do expect { file.validate }.to_not raise_error end it 'should fail on an invalid checksum_value' do file[:checksum_value] = 'boo' expect { file.validate }.to raise_error(Puppet::Error, "Checksum value 'boo' is not a valid checksum type none") end it 'should validate a valid checksum_value' do file[:checksum_value] = '' expect { file.validate }.to_not raise_error end end describe "when auditing" do before :each do # to prevent the catalog from trying to write state.yaml Puppet::Util::Storage.stubs(:store) end it "should not fail if creating a new file if group is not set" do file = described_class.new(:path => path, :audit => 'all', :content => 'content') catalog.add_resource(file) report = catalog.apply.report expect(report.resource_statuses["File[#{path}]"]).not_to be_failed expect(File.read(path)).to eq('content') end it "should not log errors if creating a new file with ensure present and no content" do file[:audit] = 'content' file[:ensure] = 'present' catalog.add_resource(file) catalog.apply expect(Puppet::FileSystem.exist?(path)).to be_truthy expect(@logs).not_to be_any { |l| # the audit metaparameter is deprecated and logs a warning l.level != :notice } end end describe "when specifying both source and checksum" do it 'should use the specified checksum when source is first' do file[:source] = File.expand_path('/foo') file[:checksum] = :md5lite expect(file[:checksum]).to eq(:md5lite) end it 'should use the specified checksum when source is last' do file[:checksum] = :md5lite file[:source] = File.expand_path('/foo') expect(file[:checksum]).to eq(:md5lite) end end describe "when validating" do [[:source, :target], [:source, :content], [:target, :content]].each do |prop1,prop2| it "should fail if both #{prop1} and #{prop2} are specified" do file[prop1] = prop1 == :source ? File.expand_path("prop1 value") : "prop1 value" file[prop2] = "prop2 value" expect do file.validate end.to raise_error(Puppet::Error, /You cannot specify more than one of/) end end end end puppet-5.5.10/spec/unit/type/host_spec.rb0000644005276200011600000005627513417161722020240 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' host = Puppet::Type.type(:host) describe host do FakeHostProvider = Struct.new(:ip, :host_aliases, :comment) before do @class = host @catalog = Puppet::Resource::Catalog.new @provider = FakeHostProvider.new @resource = stub 'resource', :resource => nil, :provider => @provider end it "should have :name be its namevar" do expect(@class.key_attributes).to eq([:name]) end describe "when validating attributes" do [:name, :provider ].each do |param| it "should have a #{param} parameter" do expect(@class.attrtype(param)).to eq(:param) end end [:ip, :target, :host_aliases, :comment, :ensure].each do |property| it "should have a #{property} property" do expect(@class.attrtype(property)).to eq(:property) end end it "should have a list host_aliases" do expect(@class.attrclass(:host_aliases).ancestors).to be_include(Puppet::Property::OrderedList) end end describe "when validating values" do it "should support present as a value for ensure" do expect { @class.new(:name => "foo", :ensure => :present) }.not_to raise_error end it "should support absent as a value for ensure" do expect { @class.new(:name => "foo", :ensure => :absent) }.not_to raise_error end it "should accept IPv4 addresses" do expect { @class.new(:name => "foo", :ip => '10.96.0.1') }.not_to raise_error end it "should accept long IPv6 addresses" do # Taken from wikipedia article about ipv6 expect { @class.new(:name => "foo", :ip => '2001:0db8:85a3:08d3:1319:8a2e:0370:7344') }.not_to raise_error end it "should accept one host_alias" do expect { @class.new(:name => "foo", :host_aliases => 'alias1') }.not_to raise_error end it "should accept multiple host_aliases" do expect { @class.new(:name => "foo", :host_aliases => [ 'alias1', 'alias2' ]) }.not_to raise_error end it "should accept shortened IPv6 addresses" do expect { @class.new(:name => "foo", :ip => '2001:db8:0:8d3:0:8a2e:70:7344') }.not_to raise_error expect { @class.new(:name => "foo", :ip => '::ffff:192.0.2.128') }.not_to raise_error expect { @class.new(:name => "foo", :ip => '::1') }.not_to raise_error end it "should not accept malformed IPv4 addresses like 192.168.0.300" do expect { @class.new(:name => "foo", :ip => '192.168.0.300') }.to raise_error(Puppet::ResourceError, /Parameter ip failed/) end it "should reject over-long IPv4 addresses" do expect { @class.new(:name => "foo", :ip => '10.10.10.10.10') }.to raise_error(Puppet::ResourceError, /Parameter ip failed/) end it "should not accept malformed IP addresses like 2001:0dg8:85a3:08d3:1319:8a2e:0370:7344" do expect { @class.new(:name => "foo", :ip => '2001:0dg8:85a3:08d3:1319:8a2e:0370:7344') }.to raise_error(Puppet::ResourceError, /Parameter ip failed/) end # Assorted, annotated IPv6 passes. ["::1", # loopback, compressed, non-routable "::", # unspecified, compressed, non-routable "0:0:0:0:0:0:0:1", # loopback, full "0:0:0:0:0:0:0:0", # unspecified, full "2001:DB8:0:0:8:800:200C:417A", # unicast, full "FF01:0:0:0:0:0:0:101", # multicast, full "2001:DB8::8:800:200C:417A", # unicast, compressed "FF01::101", # multicast, compressed # Some more test cases that should pass. "2001:0000:1234:0000:0000:C1C0:ABCD:0876", "3ffe:0b00:0000:0000:0001:0000:0000:000a", "FF02:0000:0000:0000:0000:0000:0000:0001", "0000:0000:0000:0000:0000:0000:0000:0001", "0000:0000:0000:0000:0000:0000:0000:0000", # Assorted valid, compressed IPv6 addresses. "2::10", "ff02::1", "fe80::", "2002::", "2001:db8::", "2001:0db8:1234::", "::ffff:0:0", "::1", "1:2:3:4:5:6:7:8", "1:2:3:4:5:6::8", "1:2:3:4:5::8", "1:2:3:4::8", "1:2:3::8", "1:2::8", "1::8", "1::2:3:4:5:6:7", "1::2:3:4:5:6", "1::2:3:4:5", "1::2:3:4", "1::2:3", "1::8", "::2:3:4:5:6:7:8", "::2:3:4:5:6:7", "::2:3:4:5:6", "::2:3:4:5", "::2:3:4", "::2:3", "::8", "1:2:3:4:5:6::", "1:2:3:4:5::", "1:2:3:4::", "1:2:3::", "1:2::", "1::", "1:2:3:4:5::7:8", "1:2:3:4::7:8", "1:2:3::7:8", "1:2::7:8", "1::7:8", # IPv4 addresses as dotted-quads "1:2:3:4:5:6:1.2.3.4", "1:2:3:4:5::1.2.3.4", "1:2:3:4::1.2.3.4", "1:2:3::1.2.3.4", "1:2::1.2.3.4", "1::1.2.3.4", "1:2:3:4::5:1.2.3.4", "1:2:3::5:1.2.3.4", "1:2::5:1.2.3.4", "1::5:1.2.3.4", "1::5:11.22.33.44", "fe80::217:f2ff:254.7.237.98", "::ffff:192.168.1.26", "::ffff:192.168.1.1", "0:0:0:0:0:0:13.1.68.3", # IPv4-compatible IPv6 address, full, deprecated "0:0:0:0:0:FFFF:129.144.52.38", # IPv4-mapped IPv6 address, full "::13.1.68.3", # IPv4-compatible IPv6 address, compressed, deprecated "::FFFF:129.144.52.38", # IPv4-mapped IPv6 address, compressed "fe80:0:0:0:204:61ff:254.157.241.86", "fe80::204:61ff:254.157.241.86", "::ffff:12.34.56.78", "::ffff:192.0.2.128", # this is OK, since there's a single zero digit in IPv4 "fe80:0000:0000:0000:0204:61ff:fe9d:f156", "fe80:0:0:0:204:61ff:fe9d:f156", "fe80::204:61ff:fe9d:f156", "::1", "fe80::", "fe80::1", "::ffff:c000:280", # Additional test cases from http://rt.cpan.org/Public/Bug/Display.html?id=50693 "2001:0db8:85a3:0000:0000:8a2e:0370:7334", "2001:db8:85a3:0:0:8a2e:370:7334", "2001:db8:85a3::8a2e:370:7334", "2001:0db8:0000:0000:0000:0000:1428:57ab", "2001:0db8:0000:0000:0000::1428:57ab", "2001:0db8:0:0:0:0:1428:57ab", "2001:0db8:0:0::1428:57ab", "2001:0db8::1428:57ab", "2001:db8::1428:57ab", "0000:0000:0000:0000:0000:0000:0000:0001", "::1", "::ffff:0c22:384e", "2001:0db8:1234:0000:0000:0000:0000:0000", "2001:0db8:1234:ffff:ffff:ffff:ffff:ffff", "2001:db8:a::123", "fe80::", "1111:2222:3333:4444:5555:6666:7777:8888", "1111:2222:3333:4444:5555:6666:7777::", "1111:2222:3333:4444:5555:6666::", "1111:2222:3333:4444:5555::", "1111:2222:3333:4444::", "1111:2222:3333::", "1111:2222::", "1111::", "1111:2222:3333:4444:5555:6666::8888", "1111:2222:3333:4444:5555::8888", "1111:2222:3333:4444::8888", "1111:2222:3333::8888", "1111:2222::8888", "1111::8888", "::8888", "1111:2222:3333:4444:5555::7777:8888", "1111:2222:3333:4444::7777:8888", "1111:2222:3333::7777:8888", "1111:2222::7777:8888", "1111::7777:8888", "::7777:8888", "1111:2222:3333:4444::6666:7777:8888", "1111:2222:3333::6666:7777:8888", "1111:2222::6666:7777:8888", "1111::6666:7777:8888", "::6666:7777:8888", "1111:2222:3333::5555:6666:7777:8888", "1111:2222::5555:6666:7777:8888", "1111::5555:6666:7777:8888", "::5555:6666:7777:8888", "1111:2222::4444:5555:6666:7777:8888", "1111::4444:5555:6666:7777:8888", "::4444:5555:6666:7777:8888", "1111::3333:4444:5555:6666:7777:8888", "::3333:4444:5555:6666:7777:8888", "::2222:3333:4444:5555:6666:7777:8888", "1111:2222:3333:4444:5555:6666:123.123.123.123", "1111:2222:3333:4444:5555::123.123.123.123", "1111:2222:3333:4444::123.123.123.123", "1111:2222:3333::123.123.123.123", "1111:2222::123.123.123.123", "1111::123.123.123.123", "::123.123.123.123", "1111:2222:3333:4444::6666:123.123.123.123", "1111:2222:3333::6666:123.123.123.123", "1111:2222::6666:123.123.123.123", "1111::6666:123.123.123.123", "::6666:123.123.123.123", "1111:2222:3333::5555:6666:123.123.123.123", "1111:2222::5555:6666:123.123.123.123", "1111::5555:6666:123.123.123.123", "::5555:6666:123.123.123.123", "1111:2222::4444:5555:6666:123.123.123.123", "1111::4444:5555:6666:123.123.123.123", "::4444:5555:6666:123.123.123.123", "1111::3333:4444:5555:6666:123.123.123.123", "::2222:3333:4444:5555:6666:123.123.123.123", # Playing with combinations of "0" and "::"; these are all sytactically # correct, but are bad form because "0" adjacent to "::" should be # combined into "::" "::0:0:0:0:0:0:0", "::0:0:0:0:0:0", "::0:0:0:0:0", "::0:0:0:0", "::0:0:0", "::0:0", "::0", "0:0:0:0:0:0:0::", "0:0:0:0:0:0::", "0:0:0:0:0::", "0:0:0:0::", "0:0:0::", "0:0::", "0::", # Additional cases: http://crisp.tweakblogs.net/blog/2031/ipv6-validation-%28and-caveats%29.html "0:a:b:c:d:e:f::", "::0:a:b:c:d:e:f", # syntactically correct, but bad form (::0:... could be combined) "a:b:c:d:e:f:0::", ].each do |ip| it "should accept #{ip.inspect} as an IPv6 address" do expect { @class.new(:name => "foo", :ip => ip) }.not_to raise_error end end # ...aaaand, some failure cases. [":", "02001:0000:1234:0000:0000:C1C0:ABCD:0876", # extra 0 not allowed! "2001:0000:1234:0000:00001:C1C0:ABCD:0876", # extra 0 not allowed! "2001:0000:1234:0000:0000:C1C0:ABCD:0876 0", # junk after valid address "2001:0000:1234: 0000:0000:C1C0:ABCD:0876", # internal space "3ffe:0b00:0000:0001:0000:0000:000a", # seven segments "FF02:0000:0000:0000:0000:0000:0000:0000:0001", # nine segments "3ffe:b00::1::a", # double "::" "::1111:2222:3333:4444:5555:6666::", # double "::" "1:2:3::4:5::7:8", # Double "::" "12345::6:7:8", # IPv4 embedded, but bad... "1::5:400.2.3.4", "1::5:260.2.3.4", "1::5:256.2.3.4", "1::5:1.256.3.4", "1::5:1.2.256.4", "1::5:1.2.3.256", "1::5:300.2.3.4", "1::5:1.300.3.4", "1::5:1.2.300.4", "1::5:1.2.3.300", "1::5:900.2.3.4", "1::5:1.900.3.4", "1::5:1.2.900.4", "1::5:1.2.3.900", "1::5:300.300.300.300", "1::5:3000.30.30.30", "1::400.2.3.4", "1::260.2.3.4", "1::256.2.3.4", "1::1.256.3.4", "1::1.2.256.4", "1::1.2.3.256", "1::300.2.3.4", "1::1.300.3.4", "1::1.2.300.4", "1::1.2.3.300", "1::900.2.3.4", "1::1.900.3.4", "1::1.2.900.4", "1::1.2.3.900", "1::300.300.300.300", "1::3000.30.30.30", "::400.2.3.4", "::260.2.3.4", "::256.2.3.4", "::1.256.3.4", "::1.2.256.4", "::1.2.3.256", "::300.2.3.4", "::1.300.3.4", "::1.2.300.4", "::1.2.3.300", "::900.2.3.4", "::1.900.3.4", "::1.2.900.4", "::1.2.3.900", "::300.300.300.300", "::3000.30.30.30", "2001:1:1:1:1:1:255Z255X255Y255", # garbage instead of "." in IPv4 "::ffff:192x168.1.26", # ditto "::ffff:2.3.4", "::ffff:257.1.2.3", "1.2.3.4:1111:2222:3333:4444::5555", "1.2.3.4:1111:2222:3333::5555", "1.2.3.4:1111:2222::5555", "1.2.3.4:1111::5555", "1.2.3.4::5555", "1.2.3.4::", # Testing IPv4 addresses represented as dotted-quads Leading zero's in # IPv4 addresses not allowed: some systems treat the leading "0" in # ".086" as the start of an octal number Update: The BNF in RFC-3986 # explicitly defines the dec-octet (for IPv4 addresses) not to have a # leading zero "fe80:0000:0000:0000:0204:61ff:254.157.241.086", "XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:1.2.3.4", "1111:2222:3333:4444:5555:6666:00.00.00.00", "1111:2222:3333:4444:5555:6666:000.000.000.000", "1111:2222:3333:4444:5555:6666:256.256.256.256", "1111:2222:3333:4444::5555:", "1111:2222:3333::5555:", "1111:2222::5555:", "1111::5555:", "::5555:", ":::", "1111:", ":", ":1111:2222:3333:4444::5555", ":1111:2222:3333::5555", ":1111:2222::5555", ":1111::5555", ":::5555", ":::", # Additional test cases from http://rt.cpan.org/Public/Bug/Display.html?id=50693 "123", "ldkfj", "2001::FFD3::57ab", "2001:db8:85a3::8a2e:37023:7334", "2001:db8:85a3::8a2e:370k:7334", "1:2:3:4:5:6:7:8:9", "1::2::3", "1:::3:4:5", "1:2:3::4:5:6:7:8:9", # Invalid data "XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX", # Too many components "1111:2222:3333:4444:5555:6666:7777:8888:9999", "1111:2222:3333:4444:5555:6666:7777:8888::", "::2222:3333:4444:5555:6666:7777:8888:9999", # Too few components "1111:2222:3333:4444:5555:6666:7777", "1111:2222:3333:4444:5555:6666", "1111:2222:3333:4444:5555", "1111:2222:3333:4444", "1111:2222:3333", "1111:2222", "1111", # Missing : "11112222:3333:4444:5555:6666:7777:8888", "1111:22223333:4444:5555:6666:7777:8888", "1111:2222:33334444:5555:6666:7777:8888", "1111:2222:3333:44445555:6666:7777:8888", "1111:2222:3333:4444:55556666:7777:8888", "1111:2222:3333:4444:5555:66667777:8888", "1111:2222:3333:4444:5555:6666:77778888", # Missing : intended for :: "1111:2222:3333:4444:5555:6666:7777:8888:", "1111:2222:3333:4444:5555:6666:7777:", "1111:2222:3333:4444:5555:6666:", "1111:2222:3333:4444:5555:", "1111:2222:3333:4444:", "1111:2222:3333:", "1111:2222:", "1111:", ":", ":8888", ":7777:8888", ":6666:7777:8888", ":5555:6666:7777:8888", ":4444:5555:6666:7777:8888", ":3333:4444:5555:6666:7777:8888", ":2222:3333:4444:5555:6666:7777:8888", ":1111:2222:3333:4444:5555:6666:7777:8888", # ::: ":::2222:3333:4444:5555:6666:7777:8888", "1111:::3333:4444:5555:6666:7777:8888", "1111:2222:::4444:5555:6666:7777:8888", "1111:2222:3333:::5555:6666:7777:8888", "1111:2222:3333:4444:::6666:7777:8888", "1111:2222:3333:4444:5555:::7777:8888", "1111:2222:3333:4444:5555:6666:::8888", "1111:2222:3333:4444:5555:6666:7777:::", # Double ::", "::2222::4444:5555:6666:7777:8888", "::2222:3333::5555:6666:7777:8888", "::2222:3333:4444::6666:7777:8888", "::2222:3333:4444:5555::7777:8888", "::2222:3333:4444:5555:7777::8888", "::2222:3333:4444:5555:7777:8888::", "1111::3333::5555:6666:7777:8888", "1111::3333:4444::6666:7777:8888", "1111::3333:4444:5555::7777:8888", "1111::3333:4444:5555:6666::8888", "1111::3333:4444:5555:6666:7777::", "1111:2222::4444::6666:7777:8888", "1111:2222::4444:5555::7777:8888", "1111:2222::4444:5555:6666::8888", "1111:2222::4444:5555:6666:7777::", "1111:2222:3333::5555::7777:8888", "1111:2222:3333::5555:6666::8888", "1111:2222:3333::5555:6666:7777::", "1111:2222:3333:4444::6666::8888", "1111:2222:3333:4444::6666:7777::", "1111:2222:3333:4444:5555::7777::", # Too many components" "1111:2222:3333:4444:5555:6666:7777:8888:1.2.3.4", "1111:2222:3333:4444:5555:6666:7777:1.2.3.4", "1111:2222:3333:4444:5555:6666::1.2.3.4", "::2222:3333:4444:5555:6666:7777:1.2.3.4", "1111:2222:3333:4444:5555:6666:1.2.3.4.5", # Too few components "1111:2222:3333:4444:5555:1.2.3.4", "1111:2222:3333:4444:1.2.3.4", "1111:2222:3333:1.2.3.4", "1111:2222:1.2.3.4", "1111:1.2.3.4", # Missing : "11112222:3333:4444:5555:6666:1.2.3.4", "1111:22223333:4444:5555:6666:1.2.3.4", "1111:2222:33334444:5555:6666:1.2.3.4", "1111:2222:3333:44445555:6666:1.2.3.4", "1111:2222:3333:4444:55556666:1.2.3.4", "1111:2222:3333:4444:5555:66661.2.3.4", # Missing . "1111:2222:3333:4444:5555:6666:255255.255.255", "1111:2222:3333:4444:5555:6666:255.255255.255", "1111:2222:3333:4444:5555:6666:255.255.255255", # Missing : intended for :: ":1.2.3.4", ":6666:1.2.3.4", ":5555:6666:1.2.3.4", ":4444:5555:6666:1.2.3.4", ":3333:4444:5555:6666:1.2.3.4", ":2222:3333:4444:5555:6666:1.2.3.4", ":1111:2222:3333:4444:5555:6666:1.2.3.4", # ::: ":::2222:3333:4444:5555:6666:1.2.3.4", "1111:::3333:4444:5555:6666:1.2.3.4", "1111:2222:::4444:5555:6666:1.2.3.4", "1111:2222:3333:::5555:6666:1.2.3.4", "1111:2222:3333:4444:::6666:1.2.3.4", "1111:2222:3333:4444:5555:::1.2.3.4", # Double :: "::2222::4444:5555:6666:1.2.3.4", "::2222:3333::5555:6666:1.2.3.4", "::2222:3333:4444::6666:1.2.3.4", "::2222:3333:4444:5555::1.2.3.4", "1111::3333::5555:6666:1.2.3.4", "1111::3333:4444::6666:1.2.3.4", "1111::3333:4444:5555::1.2.3.4", "1111:2222::4444::6666:1.2.3.4", "1111:2222::4444:5555::1.2.3.4", "1111:2222:3333::5555::1.2.3.4", # Missing parts "::.", "::..", "::...", "::1...", "::1.2..", "::1.2.3.", "::.2..", "::.2.3.", "::.2.3.4", "::..3.", "::..3.4", "::...4", # Extra : in front ":1111:2222:3333:4444:5555:6666:7777::", ":1111:2222:3333:4444:5555:6666::", ":1111:2222:3333:4444:5555::", ":1111:2222:3333:4444::", ":1111:2222:3333::", ":1111:2222::", ":1111::", ":::", ":1111:2222:3333:4444:5555:6666::8888", ":1111:2222:3333:4444:5555::8888", ":1111:2222:3333:4444::8888", ":1111:2222:3333::8888", ":1111:2222::8888", ":1111::8888", ":::8888", ":1111:2222:3333:4444:5555::7777:8888", ":1111:2222:3333:4444::7777:8888", ":1111:2222:3333::7777:8888", ":1111:2222::7777:8888", ":1111::7777:8888", ":::7777:8888", ":1111:2222:3333:4444::6666:7777:8888", ":1111:2222:3333::6666:7777:8888", ":1111:2222::6666:7777:8888", ":1111::6666:7777:8888", ":::6666:7777:8888", ":1111:2222:3333::5555:6666:7777:8888", ":1111:2222::5555:6666:7777:8888", ":1111::5555:6666:7777:8888", ":::5555:6666:7777:8888", ":1111:2222::4444:5555:6666:7777:8888", ":1111::4444:5555:6666:7777:8888", ":::4444:5555:6666:7777:8888", ":1111::3333:4444:5555:6666:7777:8888", ":::3333:4444:5555:6666:7777:8888", ":::2222:3333:4444:5555:6666:7777:8888", ":1111:2222:3333:4444:5555:6666:1.2.3.4", ":1111:2222:3333:4444:5555::1.2.3.4", ":1111:2222:3333:4444::1.2.3.4", ":1111:2222:3333::1.2.3.4", ":1111:2222::1.2.3.4", ":1111::1.2.3.4", ":::1.2.3.4", ":1111:2222:3333:4444::6666:1.2.3.4", ":1111:2222:3333::6666:1.2.3.4", ":1111:2222::6666:1.2.3.4", ":1111::6666:1.2.3.4", ":::6666:1.2.3.4", ":1111:2222:3333::5555:6666:1.2.3.4", ":1111:2222::5555:6666:1.2.3.4", ":1111::5555:6666:1.2.3.4", ":::5555:6666:1.2.3.4", ":1111:2222::4444:5555:6666:1.2.3.4", ":1111::4444:5555:6666:1.2.3.4", ":::4444:5555:6666:1.2.3.4", ":1111::3333:4444:5555:6666:1.2.3.4", ":::2222:3333:4444:5555:6666:1.2.3.4", # Extra : at end "1111:2222:3333:4444:5555:6666:7777:::", "1111:2222:3333:4444:5555:6666:::", "1111:2222:3333:4444:5555:::", "1111:2222:3333:4444:::", "1111:2222:3333:::", "1111:2222:::", "1111:::", ":::", "1111:2222:3333:4444:5555:6666::8888:", "1111:2222:3333:4444:5555::8888:", "1111:2222:3333:4444::8888:", "1111:2222:3333::8888:", "1111:2222::8888:", "1111::8888:", "::8888:", "1111:2222:3333:4444:5555::7777:8888:", "1111:2222:3333:4444::7777:8888:", "1111:2222:3333::7777:8888:", "1111:2222::7777:8888:", "1111::7777:8888:", "::7777:8888:", "1111:2222:3333:4444::6666:7777:8888:", "1111:2222:3333::6666:7777:8888:", "1111:2222::6666:7777:8888:", "1111::6666:7777:8888:", "::6666:7777:8888:", "1111:2222:3333::5555:6666:7777:8888:", "1111:2222::5555:6666:7777:8888:", "1111::5555:6666:7777:8888:", "::5555:6666:7777:8888:", "1111:2222::4444:5555:6666:7777:8888:", "1111::4444:5555:6666:7777:8888:", "::4444:5555:6666:7777:8888:", "1111::3333:4444:5555:6666:7777:8888:", "::3333:4444:5555:6666:7777:8888:", "::2222:3333:4444:5555:6666:7777:8888:", ].each do |ip| it "should reject #{ip.inspect} as an IPv6 address" do expect { @class.new(:name => "foo", :ip => ip) }.to raise_error(Puppet::ResourceError, /Parameter ip failed/) end end it "should not accept newlines in resourcename" do expect { @class.new(:name => "fo\no", :ip => '127.0.0.1' ) }.to raise_error(Puppet::ResourceError, /Hostname cannot include newline/) end it "should not accept newlines in ipaddress" do expect { @class.new(:name => "foo", :ip => "127.0.0.1\n") }.to raise_error(Puppet::ResourceError, /Invalid IP address/) end it "should not accept newlines in host_aliases" do expect { @class.new(:name => "foo", :ip => '127.0.0.1', :host_aliases => [ 'well_formed', "thisalias\nhavenewline" ] ) }.to raise_error(Puppet::ResourceError, /Host aliases cannot include whitespace/) end it "should not accept newlines in comment" do expect { @class.new(:name => "foo", :ip => '127.0.0.1', :comment => "Test of comment blah blah \n test 123" ) }.to raise_error(Puppet::ResourceError, /Comment cannot include newline/) end it "should not accept spaces in resourcename" do expect { @class.new(:name => "foo bar") }.to raise_error(Puppet::ResourceError, /Invalid host name/) end it "should not accept host_aliases with spaces" do expect { @class.new(:name => "foo", :host_aliases => [ 'well_formed', 'not wellformed' ]) }.to raise_error(Puppet::ResourceError, /Host aliases cannot include whitespace/) end it "should not accept empty host_aliases" do expect { @class.new(:name => "foo", :host_aliases => ['alias1','']) }.to raise_error(Puppet::ResourceError, /Host aliases cannot be an empty string/) end end describe "when syncing" do it "should send the first value to the provider for ip property" do @ip = @class.attrclass(:ip).new(:resource => @resource, :should => %w{192.168.0.1 192.168.0.2}) @ip.sync expect(@provider.ip).to eq('192.168.0.1') end it "should send the first value to the provider for comment property" do @comment = @class.attrclass(:comment).new(:resource => @resource, :should => %w{Bazinga Notme}) @comment.sync expect(@provider.comment).to eq('Bazinga') end it "should send the joined array to the provider for host_alias" do @host_aliases = @class.attrclass(:host_aliases).new(:resource => @resource, :should => %w{foo bar}) @host_aliases.sync expect(@provider.host_aliases).to eq('foo bar') end it "should also use the specified delimiter for joining" do @host_aliases = @class.attrclass(:host_aliases).new(:resource => @resource, :should => %w{foo bar}) @host_aliases.stubs(:delimiter).returns "\t" @host_aliases.sync expect(@provider.host_aliases).to eq("foo\tbar") end it "should care about the order of host_aliases" do @host_aliases = @class.attrclass(:host_aliases).new(:resource => @resource, :should => %w{foo bar}) expect(@host_aliases.insync?(%w{foo bar})).to eq(true) expect(@host_aliases.insync?(%w{bar foo})).to eq(false) end it "should not consider aliases to be in sync if should is a subset of current" do @host_aliases = @class.attrclass(:host_aliases).new(:resource => @resource, :should => %w{foo bar}) expect(@host_aliases.insync?(%w{foo bar anotherone})).to eq(false) end end end puppet-5.5.10/spec/unit/type/interface_spec.rb0000644005276200011600000001127213417161722021207 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:interface) do it "should have a 'name' parameter'" do expect(Puppet::Type.type(:interface).new(:name => "FastEthernet 0/1")[:name]).to eq("FastEthernet 0/1") end it "should have a 'device_url' parameter'" do expect(Puppet::Type.type(:interface).new(:name => "FastEthernet 0/1", :device_url => :device)[:device_url]).to eq(:device) end it "should have an ensure property" do expect(Puppet::Type.type(:interface).attrtype(:ensure)).to eq(:property) end it "should be applied on device" do expect(Puppet::Type.type(:interface).new(:name => "FastEthernet 0/1")).to be_appliable_to_device end [:description, :speed, :duplex, :native_vlan, :encapsulation, :mode, :allowed_trunk_vlans, :etherchannel, :ipaddress].each do |p| it "should have a #{p} property" do expect(Puppet::Type.type(:interface).attrtype(p)).to eq(:property) end end describe "when validating attribute values" do before do @provider = stub 'provider', :class => Puppet::Type.type(:interface).defaultprovider, :clear => nil Puppet::Type.type(:interface).defaultprovider.stubs(:new).returns(@provider) end it "should support :present as a value to :ensure" do Puppet::Type.type(:interface).new(:name => "FastEthernet 0/1", :ensure => :present) end it "should support :shutdown as a value to :ensure" do Puppet::Type.type(:interface).new(:name => "FastEthernet 0/1", :ensure => :shutdown) end it "should support :no_shutdown as a value to :ensure" do Puppet::Type.type(:interface).new(:name => "FastEthernet 0/1", :ensure => :no_shutdown) end describe "especially speed" do it "should allow a number" do Puppet::Type.type(:interface).new(:name => "FastEthernet 0/1", :speed => "100") end it "should allow :auto" do Puppet::Type.type(:interface).new(:name => "FastEthernet 0/1", :speed => :auto) end end describe "especially duplex" do it "should allow :half" do Puppet::Type.type(:interface).new(:name => "FastEthernet 0/1", :duplex => :half) end it "should allow :full" do Puppet::Type.type(:interface).new(:name => "FastEthernet 0/1", :duplex => :full) end it "should allow :auto" do Puppet::Type.type(:interface).new(:name => "FastEthernet 0/1", :duplex => :auto) end end describe "interface mode" do it "should allow :access" do Puppet::Type.type(:interface).new(:name => "FastEthernet 0/1", :mode => :access) end it "should allow :trunk" do Puppet::Type.type(:interface).new(:name => "FastEthernet 0/1", :mode => :trunk) end it "should allow 'dynamic auto'" do Puppet::Type.type(:interface).new(:name => "FastEthernet 0/1", :mode => 'dynamic auto') end it "should allow 'dynamic desirable'" do Puppet::Type.type(:interface).new(:name => "FastEthernet 0/1", :mode => 'dynamic desirable') end end describe "interface encapsulation" do it "should allow :dot1q" do Puppet::Type.type(:interface).new(:name => "FastEthernet 0/1", :encapsulation => :dot1q) end it "should allow :isl" do Puppet::Type.type(:interface).new(:name => "FastEthernet 0/1", :encapsulation => :isl) end it "should allow :negotiate" do Puppet::Type.type(:interface).new(:name => "FastEthernet 0/1", :encapsulation => :negotiate) end end describe "especially ipaddress" do it "should allow ipv4 addresses" do Puppet::Type.type(:interface).new(:name => "FastEthernet 0/1", :ipaddress => "192.168.0.1/24") end it "should allow arrays of ipv4 addresses" do Puppet::Type.type(:interface).new(:name => "FastEthernet 0/1", :ipaddress => ["192.168.0.1/24", "192.168.1.0/24"]) end it "should allow ipv6 addresses" do Puppet::Type.type(:interface).new(:name => "FastEthernet 0/1", :ipaddress => "f0e9::/64") end it "should allow ipv6 options" do Puppet::Type.type(:interface).new(:name => "FastEthernet 0/1", :ipaddress => "f0e9::/64 link-local") Puppet::Type.type(:interface).new(:name => "FastEthernet 0/1", :ipaddress => "f0e9::/64 eui-64") end it "should allow a mix of ipv4 and ipv6" do Puppet::Type.type(:interface).new(:name => "FastEthernet 0/1", :ipaddress => ["192.168.0.1/24", "f0e9::/64 link-local"]) end it "should munge ip addresses to a computer format" do expect(Puppet::Type.type(:interface).new(:name => "FastEthernet 0/1", :ipaddress => "192.168.0.1/24")[:ipaddress]).to eq([[24, IPAddr.new('192.168.0.1'), nil]]) end end end end puppet-5.5.10/spec/unit/type/k5login_spec.rb0000644005276200011600000001524313417161722020621 0ustar jenkinsjenkins#!/usr/bin/env ruby require 'spec_helper' require 'fileutils' require 'puppet/type' describe Puppet::Type.type(:k5login), :unless => Puppet.features.microsoft_windows? do include PuppetSpec::Files context "the type class" do subject { described_class } it { is_expected.to be_validattr :ensure } it { is_expected.to be_validattr :path } it { is_expected.to be_validattr :principals } it { is_expected.to be_validattr :mode } it { is_expected.to be_validattr :selrange } it { is_expected.to be_validattr :selrole } it { is_expected.to be_validattr :seltype } it { is_expected.to be_validattr :seluser } # We have one, inline provider implemented. it { is_expected.to be_validattr :provider } end let(:path) { tmpfile('k5login') } def resource(attrs = {}) attrs = { :ensure => 'present', :path => path, :principals => 'fred@EXAMPLE.COM', :seluser => 'user_u', :selrole => 'role_r', :seltype => 'type_t', :selrange => 's0', }.merge(attrs) if content = attrs.delete(:content) File.open(path, 'w') { |f| f.print(content) } end resource = described_class.new(attrs) resource end before :each do FileUtils.touch(path) end context "the provider" do context "when the file is missing" do it "should initially be absent" do File.delete(path) expect(resource.retrieve[:ensure]).to eq(:absent) end it "should create the file when synced" do resource(:ensure => 'present').parameter(:ensure).sync expect(Puppet::FileSystem.exist?(path)).to be_truthy end end context "when the file is present" do context "retrieved initial state" do subject { resource.retrieve } it "should retrieve its properties correctly with zero principals" do expect(subject[:ensure]).to eq(:present) expect(subject[:principals]).to eq([]) # We don't really care what the mode is, just that it got it expect(subject[:mode]).not_to be_nil end context "with one principal" do subject { resource(:content => "daniel@EXAMPLE.COM\n").retrieve } it "should retrieve its principals correctly" do expect(subject[:principals]).to eq(["daniel@EXAMPLE.COM"]) end end [:seluser, :selrole, :seltype, :selrange].each do |param| property = described_class.attrclass(param) context param.to_s do let(:sel_param) { property.new :resource => resource } context "with selinux" do it "should return correct values based on SELinux state" do sel_param.stubs(:debug) expectedresult = case param when :seluser; "user_u" when :selrole; "object_r" when :seltype; "krb5_home_t" when :selrange; "s0" end expect(sel_param.default).to eq(expectedresult) end end context 'without selinux' do it 'should not try to determine the initial state' do Puppet::Type::K5login::ProviderK5login.any_instance.stubs(:selinux_support?).returns false expect(subject[:selrole]).to be_nil end it "should do nothing for safe_insync? if no SELinux support" do sel_param.should = 'newcontext' sel_param.expects(:selinux_support?).returns false expect(sel_param.safe_insync?('oldcontext')).to eq(true) end end end end context "with two principals" do subject do content = ["daniel@EXAMPLE.COM", "george@EXAMPLE.COM"].join("\n") resource(:content => content).retrieve end it "should retrieve its principals correctly" do expect(subject[:principals]).to eq(["daniel@EXAMPLE.COM", "george@EXAMPLE.COM"]) end end end it "should remove the file ensure is absent" do resource(:ensure => 'absent').property(:ensure).sync expect(Puppet::FileSystem.exist?(path)).to be_falsey end it "should write one principal to the file" do expect(File.read(path)).to eq("") resource(:principals => ["daniel@EXAMPLE.COM"]).property(:principals).sync expect(File.read(path)).to eq("daniel@EXAMPLE.COM\n") end it "should write multiple principals to the file" do content = ["daniel@EXAMPLE.COM", "george@EXAMPLE.COM"] expect(File.read(path)).to eq("") resource(:principals => content).property(:principals).sync expect(File.read(path)).to eq(content.join("\n") + "\n") end describe "when setting the mode" do # The defined input type is "mode, as an octal string" ["400", "600", "700", "644", "664"].each do |mode| it "should update the mode to #{mode}" do resource(:mode => mode).property(:mode).sync expect((Puppet::FileSystem.stat(path).mode & 07777).to_s(8)).to eq(mode) end end end context "#stat" do let(:file) { described_class.new(:path => path) } it "should return nil if the file does not exist" do file[:path] = make_absolute('/foo/bar/baz/non-existent') expect(file.stat).to be_nil end it "should return nil if the file cannot be stat'ed" do dir = tmpfile('link_test_dir') child = File.join(dir, 'some_file') # Note: we aren't creating the file for this test. If the user is # running these tests as root, they will be able to access the # directory. In that case, this test will still succeed, not because # we cannot stat the file, but because the file does not exist. Dir.mkdir(dir) begin File.chmod(0, dir) file[:path] = child expect(file.stat).to be_nil ensure # chmod it back so we can clean it up File.chmod(0777, dir) end end it "should return nil if parts of path are not directories" do regular_file = tmpfile('ENOTDIR_test') FileUtils.touch(regular_file) impossible_child = File.join(regular_file, 'some_file') file[:path] = impossible_child expect(file.stat).to be_nil end it "should return the stat instance" do expect(file.stat).to be_a(File::Stat) end it "should cache the stat instance" do expect(file.stat.object_id).to eql(file.stat.object_id) end end end end end puppet-5.5.10/spec/unit/type/macauthorization_spec.rb0000644005276200011600000000747613417161722022643 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' module Puppet::Util::Plist end macauth_type = Puppet::Type.type(:macauthorization) describe Puppet::Type.type(:macauthorization), "when checking macauthorization objects" do before do authplist = {} authplist["rules"] = { "foorule" => "foo" } authplist["rights"] = { "fooright" => "foo" } provider_class = macauth_type.provider(macauth_type.providers[0]) Puppet::Util::Plist.stubs(:parse_plist).with("/etc/authorization").returns(authplist) macauth_type.stubs(:defaultprovider).returns provider_class @resource = macauth_type.new(:name => 'foo') end describe "when validating attributes" do parameters = [:name,] properties = [:auth_type, :allow_root, :authenticate_user, :auth_class, :comment, :group, :k_of_n, :mechanisms, :rule, :session_owner, :shared, :timeout, :tries] parameters.each do |parameter| it "should have a #{parameter} parameter" do expect(macauth_type.attrclass(parameter).ancestors).to be_include(Puppet::Parameter) end it "should have documentation for its #{parameter} parameter" do expect(macauth_type.attrclass(parameter).doc).to be_instance_of(String) end end properties.each do |property| it "should have a #{property} property" do expect(macauth_type.attrclass(property).ancestors).to be_include(Puppet::Property) end it "should have documentation for its #{property} property" do expect(macauth_type.attrclass(property).doc).to be_instance_of(String) end end end describe "when validating properties" do it "should have a default provider inheriting from Puppet::Provider" do expect(macauth_type.defaultprovider.ancestors).to be_include(Puppet::Provider) end it "should be able to create an instance" do expect { macauth_type.new(:name => 'foo') }.not_to raise_error end it "should support :present as a value to :ensure" do expect { macauth_type.new(:name => "foo", :ensure => :present) }.not_to raise_error end it "should support :absent as a value to :ensure" do expect { macauth_type.new(:name => "foo", :ensure => :absent) }.not_to raise_error end end [:k_of_n, :timeout, :tries].each do |property| describe "when managing the #{property} property" do it "should convert number-looking strings into actual numbers" do prop = macauth_type.attrclass(property).new(:resource => @resource) prop.should = "300" expect(prop.should).to eq(300) end it "should support integers as a value" do prop = macauth_type.attrclass(property).new(:resource => @resource) prop.should = 300 expect(prop.should).to eq(300) end it "should raise an error for non-integer values" do prop = macauth_type.attrclass(property).new(:resource => @resource) expect { prop.should = "foo" }.to raise_error(Puppet::Error) end end end [:allow_root, :authenticate_user, :session_owner, :shared].each do |property| describe "when managing the #{property} property" do it "should convert boolean-looking false strings into actual booleans" do prop = macauth_type.attrclass(property).new(:resource => @resource) prop.should = "false" expect(prop.should).to eq(:false) end it "should convert boolean-looking true strings into actual booleans" do prop = macauth_type.attrclass(property).new(:resource => @resource) prop.should = "true" expect(prop.should).to eq(:true) end it "should raise an error for non-boolean values" do prop = macauth_type.attrclass(property).new(:resource => @resource) expect { prop.should = "foo" }.to raise_error(Puppet::Error) end end end end puppet-5.5.10/spec/unit/type/mailalias_spec.rb0000644005276200011600000000327313417161722021205 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:mailalias) do include PuppetSpec::Files let :tmpfile_path do tmpfile('afile') end let :target do tmpfile('mailalias') end let :recipient_resource do described_class.new(:name => "luke", :recipient => "yay", :target => target) end let :file_resource do described_class.new(:name => "lukefile", :file => tmpfile_path, :target => target) end it "should be initially absent as a recipient" do expect(recipient_resource.retrieve_resource[:recipient]).to eq(:absent) end it "should be initially absent as an included file" do expect(file_resource.retrieve_resource[:file]).to eq(:absent) end it "should try and set the recipient when it does the sync" do expect(recipient_resource.retrieve_resource[:recipient]).to eq(:absent) recipient_resource.property(:recipient).expects(:set).with(["yay"]) recipient_resource.property(:recipient).sync end it "should try and set the included file when it does the sync" do expect(file_resource.retrieve_resource[:file]).to eq(:absent) file_resource.property(:file).expects(:set).with(tmpfile_path) file_resource.property(:file).sync end it "should fail when file is not an absolute path" do expect { Puppet::Type.type(:mailalias).new(:name => 'x', :file => 'afile') }.to raise_error Puppet::Error, /File paths must be fully qualified/ end it "should fail when both file and recipient are specified" do expect { Puppet::Type.type(:mailalias).new(:name => 'x', :file => tmpfile_path, :recipient => 'foo@example.com') }.to raise_error Puppet::Error, /cannot specify both a recipient and a file/ end end puppet-5.5.10/spec/unit/type/maillist_spec.rb0000644005276200011600000000264013417161722021064 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' maillist = Puppet::Type.type(:maillist) describe maillist do before do @provider_class = Puppet::Type.type(:maillist).provider(:mailman) @provider = stub 'provider', :class => @provider_class, :clear => nil @provider.stubs(:respond_to).with(:aliases).returns(true) @provider_class.stubs(:new).returns(@provider) Puppet::Type.type(:maillist).stubs(:defaultprovider).returns(@provider_class) @maillist = Puppet::Type.type(:maillist).new( :name => 'test' ) @catalog = Puppet::Resource::Catalog.new @maillist.catalog = @catalog end it "should generate aliases unless they already exist" do # Mail List aliases are careful not to stomp on managed Mail Alias aliases # test1 is an unmanaged alias from /etc/aliases Puppet::Type.type(:mailalias).provider(:aliases).stubs(:target_object).returns( StringIO.new("test1: root\n") ) # test2 is a managed alias from the manifest dupe = Puppet::Type.type(:mailalias).new( :name => 'test2' ) @catalog.add_resource dupe @provider.stubs(:aliases).returns({"test1" => 'this will get included', "test2" => 'this will dropped', "test3" => 'this will get included'}) generated = @maillist.generate expect(generated.map{ |x| x.name }.sort).to eq(['test1', 'test3']) expect(generated.map{ |x| x.class }).to eq([Puppet::Type::Mailalias, Puppet::Type::Mailalias]) end end puppet-5.5.10/spec/unit/type/mcx_spec.rb0000644005276200011600000000373113417161722020037 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/type/mcx' mcx_type = Puppet::Type.type(:mcx) describe mcx_type, "when validating attributes" do properties = [:ensure, :content] parameters = [:name, :ds_type, :ds_name] parameters.each do |p| it "should have a #{p} parameter" do expect(mcx_type.attrclass(p).ancestors).to be_include(Puppet::Parameter) end it "should have documentation for its #{p} parameter" do expect(mcx_type.attrclass(p).doc).to be_instance_of(String) end end properties.each do |p| it "should have a #{p} property" do expect(mcx_type.attrclass(p).ancestors).to be_include(Puppet::Property) end it "should have documentation for its #{p} property" do expect(mcx_type.attrclass(p).doc).to be_instance_of(String) end end end describe mcx_type, "default values" do before :each do provider_class = mcx_type.provider(mcx_type.providers[0]) mcx_type.stubs(:defaultprovider).returns provider_class end it "should be nil for :ds_type" do expect(mcx_type.new(:name => '/Foo/bar')[:ds_type]).to be_nil end it "should be nil for :ds_name" do expect(mcx_type.new(:name => '/Foo/bar')[:ds_name]).to be_nil end it "should be nil for :content" do expect(mcx_type.new(:name => '/Foo/bar')[:content]).to be_nil end end describe mcx_type, "when validating properties" do before :each do provider_class = mcx_type.provider(mcx_type.providers[0]) mcx_type.stubs(:defaultprovider).returns provider_class end it "should be able to create an instance" do expect { mcx_type.new(:name => '/Foo/bar') }.not_to raise_error end it "should support :present as a value to :ensure" do expect { mcx_type.new(:name => "/Foo/bar", :ensure => :present) }.not_to raise_error end it "should support :absent as a value to :ensure" do expect { mcx_type.new(:name => "/Foo/bar", :ensure => :absent) }.not_to raise_error end end puppet-5.5.10/spec/unit/type/mount_spec.rb0000644005276200011600000005637513417161722020426 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:mount), :unless => Puppet.features.microsoft_windows? do before :each do Puppet::Type.type(:mount).stubs(:defaultprovider).returns providerclass end let :providerclass do described_class.provide(:fake_mount_provider) do attr_accessor :property_hash def create; end def destroy; end def exists? get(:ensure) != :absent end def mount; end def umount; end def mounted? [:mounted, :ghost].include?(get(:ensure)) end mk_resource_methods end end let :provider do providerclass.new(:name => 'yay') end let :resource do described_class.new(:name => "yay", :audit => :ensure, :provider => provider) end let :ensureprop do resource.property(:ensure) end it "should have a :refreshable feature that requires the :remount method" do expect(described_class.provider_feature(:refreshable).methods).to eq([:remount]) end it "should have no default value for :ensure" do mount = described_class.new(:name => "yay") expect(mount.should(:ensure)).to be_nil end it "should have :name as the only keyattribut" do expect(described_class.key_attributes).to eq([:name]) end describe "when validating attributes" do [:name, :remounts, :provider].each do |param| it "should have a #{param} parameter" do expect(described_class.attrtype(param)).to eq(:param) end end [:ensure, :device, :blockdevice, :fstype, :options, :pass, :dump, :atboot, :target].each do |param| it "should have a #{param} property" do expect(described_class.attrtype(param)).to eq(:property) end end end describe "when validating values" do describe "for name" do it "should allow full qualified paths" do expect(described_class.new(:name => "/mnt/foo")[:name]).to eq('/mnt/foo') end it "should remove trailing slashes" do expect(described_class.new(:name => '/')[:name]).to eq('/') expect(described_class.new(:name => '//')[:name]).to eq('/') expect(described_class.new(:name => '/foo/')[:name]).to eq('/foo') expect(described_class.new(:name => '/foo/bar/')[:name]).to eq('/foo/bar') expect(described_class.new(:name => '/foo/bar/baz//')[:name]).to eq('/foo/bar/baz') end it "should not allow spaces" do expect { described_class.new(:name => "/mnt/foo bar") }.to raise_error Puppet::Error, /name.*whitespace/ end it "should allow pseudo mountpoints (e.g. swap)" do expect(described_class.new(:name => 'none')[:name]).to eq('none') end end describe "for ensure" do it "should alias :present to :defined as a value to :ensure" do mount = described_class.new(:name => "yay", :ensure => :present) expect(mount.should(:ensure)).to eq(:defined) end it "should support :present as a value to :ensure" do expect { described_class.new(:name => "yay", :ensure => :present) }.to_not raise_error end it "should support :defined as a value to :ensure" do expect { described_class.new(:name => "yay", :ensure => :defined) }.to_not raise_error end it "should support :unmounted as a value to :ensure" do expect { described_class.new(:name => "yay", :ensure => :unmounted) }.to_not raise_error end it "should support :absent as a value to :ensure" do expect { described_class.new(:name => "yay", :ensure => :absent) }.to_not raise_error end it "should support :mounted as a value to :ensure" do expect { described_class.new(:name => "yay", :ensure => :mounted) }.to_not raise_error end it "should not support other values for :ensure" do expect { described_class.new(:name => "yay", :ensure => :mount) }.to raise_error Puppet::Error, /Invalid value/ end end describe "for device" do it "should support normal /dev paths for device" do expect { described_class.new(:name => "/foo", :ensure => :present, :device => '/dev/hda1') }.to_not raise_error expect { described_class.new(:name => "/foo", :ensure => :present, :device => '/dev/dsk/c0d0s0') }.to_not raise_error end it "should support labels for device" do expect { described_class.new(:name => "/foo", :ensure => :present, :device => 'LABEL=/boot') }.to_not raise_error expect { described_class.new(:name => "/foo", :ensure => :present, :device => 'LABEL=SWAP-hda6') }.to_not raise_error end it "should support pseudo devices for device" do expect { described_class.new(:name => "/foo", :ensure => :present, :device => 'ctfs') }.to_not raise_error expect { described_class.new(:name => "/foo", :ensure => :present, :device => 'swap') }.to_not raise_error expect { described_class.new(:name => "/foo", :ensure => :present, :device => 'sysfs') }.to_not raise_error expect { described_class.new(:name => "/foo", :ensure => :present, :device => 'proc') }.to_not raise_error end it 'should not support whitespace in device' do expect { described_class.new(:name => "/foo", :ensure => :present, :device => '/dev/my dev/foo') }.to raise_error Puppet::Error, /device.*whitespace/ expect { described_class.new(:name => "/foo", :ensure => :present, :device => "/dev/my\tdev/foo") }.to raise_error Puppet::Error, /device.*whitespace/ end end describe "for blockdevice" do before :each do # blockdevice is only used on Solaris Facter.stubs(:value).with(:operatingsystem).returns 'Solaris' Facter.stubs(:value).with(:osfamily).returns 'Solaris' end it "should support normal /dev/rdsk paths for blockdevice" do expect { described_class.new(:name => "/foo", :ensure => :present, :blockdevice => '/dev/rdsk/c0d0s0') }.to_not raise_error end it "should support a dash for blockdevice" do expect { described_class.new(:name => "/foo", :ensure => :present, :blockdevice => '-') }.to_not raise_error end it "should not support whitespace in blockdevice" do expect { described_class.new(:name => "/foo", :ensure => :present, :blockdevice => '/dev/my dev/foo') }.to raise_error Puppet::Error, /blockdevice.*whitespace/ expect { described_class.new(:name => "/foo", :ensure => :present, :blockdevice => "/dev/my\tdev/foo") }.to raise_error Puppet::Error, /blockdevice.*whitespace/ end it "should default to /dev/rdsk/DEVICE if device is /dev/dsk/DEVICE" do obj = described_class.new(:name => "/foo", :device => '/dev/dsk/c0d0s0') expect(obj[:blockdevice]).to eq('/dev/rdsk/c0d0s0') end it "should default to - if it is an nfs-share" do obj = described_class.new(:name => "/foo", :device => "server://share", :fstype => 'nfs') expect(obj[:blockdevice]).to eq('-') end it "should have no default otherwise" do expect(described_class.new(:name => "/foo")[:blockdevice]).to eq(nil) expect(described_class.new(:name => "/foo", :device => "/foo")[:blockdevice]).to eq(nil) end it "should overwrite any default if blockdevice is explicitly set" do expect(described_class.new(:name => "/foo", :device => '/dev/dsk/c0d0s0', :blockdevice => '/foo')[:blockdevice]).to eq('/foo') expect(described_class.new(:name => "/foo", :device => "server://share", :fstype => 'nfs', :blockdevice => '/foo')[:blockdevice]).to eq('/foo') end end describe "for fstype" do it "should support valid fstypes" do expect { described_class.new(:name => "/foo", :ensure => :present, :fstype => 'ext3') }.to_not raise_error expect { described_class.new(:name => "/foo", :ensure => :present, :fstype => 'proc') }.to_not raise_error expect { described_class.new(:name => "/foo", :ensure => :present, :fstype => 'sysfs') }.to_not raise_error end it "should support auto as a special fstype" do expect { described_class.new(:name => "/foo", :ensure => :present, :fstype => 'auto') }.to_not raise_error end it "should not support whitespace in fstype" do expect { described_class.new(:name => "/foo", :ensure => :present, :fstype => 'ext 3') }.to raise_error Puppet::Error, /fstype.*whitespace/ end it "should not support an empty string in fstype" do expect { described_class.new(:name => "/foo", :ensure => :present, :fstype => "") }.to raise_error Puppet::Error, /fstype.*empty string/ end end describe "for options" do it "should support a single option" do expect { described_class.new(:name => "/foo", :ensure => :present, :options => 'ro') }.to_not raise_error end it "should support multiple options as a comma separated list" do expect { described_class.new(:name => "/foo", :ensure => :present, :options => 'ro,rsize=4096') }.to_not raise_error end it "should not support whitespace in options" do expect { described_class.new(:name => "/foo", :ensure => :present, :options => ['ro','foo bar','intr']) }.to raise_error Puppet::Error, /option.*whitespace/ end it "should not support an empty string in options" do expect { described_class.new(:name => "/foo", :ensure => :present, :options => "") }.to raise_error Puppet::Error, /option.*empty string/ end end describe "for pass" do it "should support numeric values" do expect { described_class.new(:name => "/foo", :ensure => :present, :pass => '0') }.to_not raise_error expect { described_class.new(:name => "/foo", :ensure => :present, :pass => '1') }.to_not raise_error expect { described_class.new(:name => "/foo", :ensure => :present, :pass => '2') }.to_not raise_error end it "should support - on Solaris" do Facter.stubs(:value).with(:operatingsystem).returns 'Solaris' Facter.stubs(:value).with(:osfamily).returns 'Solaris' expect { described_class.new(:name => "/foo", :ensure => :present, :pass => '-') }.to_not raise_error end it "should default to 0 on non Solaris" do Facter.stubs(:value).with(:osfamily).returns nil Facter.stubs(:value).with(:operatingsystem).returns 'HP-UX' expect(described_class.new(:name => "/foo", :ensure => :present)[:pass]).to eq(0) end it "should default to - on Solaris" do Facter.stubs(:value).with(:operatingsystem).returns 'Solaris' Facter.stubs(:value).with(:osfamily).returns 'Solaris' expect(described_class.new(:name => "/foo", :ensure => :present)[:pass]).to eq('-') end end describe "for dump" do it "should support 0 as a value for dump" do expect { described_class.new(:name => "/foo", :ensure => :present, :dump => '0') }.to_not raise_error end it "should support 1 as a value for dump" do expect { described_class.new(:name => "/foo", :ensure => :present, :dump => '1') }.to_not raise_error end # Unfortunately the operatingsystem is evaluatet at load time so I am unable to stub operatingsystem it "should support 2 as a value for dump on FreeBSD", :if => Facter.value(:operatingsystem) == 'FreeBSD' do expect { described_class.new(:name => "/foo", :ensure => :present, :dump => '2') }.to_not raise_error end it "should not support 2 as a value for dump when not on FreeBSD", :if => Facter.value(:operatingsystem) != 'FreeBSD' do expect { described_class.new(:name => "/foo", :ensure => :present, :dump => '2') }.to raise_error Puppet::Error, /Invalid value/ end it "should default to 0" do expect(described_class.new(:name => "/foo", :ensure => :present)[:dump]).to eq(0) end end describe "for atboot" do it "does not allow non-boolean values" do expect { described_class.new(:name => "/foo", :ensure => :present, :atboot => 'unknown') }.to raise_error Puppet::Error, /expected a boolean value/ end it "interprets yes as yes" do resource = described_class.new(:name => "/foo", :ensure => :present, :atboot => :yes) expect(resource[:atboot]).to eq(:yes) end it "interprets true as yes" do resource = described_class.new(:name => "/foo", :ensure => :present, :atboot => :true) expect(resource[:atboot]).to eq(:yes) end it "interprets no as no" do resource = described_class.new(:name => "/foo", :ensure => :present, :atboot => :no) expect(resource[:atboot]).to eq(:no) end it "interprets false as no" do resource = described_class.new(:name => "/foo", :ensure => :present, :atboot => false) expect(resource[:atboot]).to eq(:no) end end end describe "when changing the host" do def test_ensure_change(options) provider.set(:ensure => options[:from]) provider.expects(:create).times(options[:create] || 0) provider.expects(:destroy).times(options[:destroy] || 0) provider.expects(:mount).never provider.expects(:unmount).times(options[:unmount] || 0) ensureprop.stubs(:syncothers) ensureprop.should = options[:to] ensureprop.sync expect(!!provider.property_hash[:needs_mount]).to eq(!!options[:mount]) end it "should create itself when changing from :ghost to :present" do test_ensure_change(:from => :ghost, :to => :present, :create => 1) end it "should create itself when changing from :absent to :present" do test_ensure_change(:from => :absent, :to => :present, :create => 1) end it "should create itself and unmount when changing from :ghost to :unmounted" do test_ensure_change(:from => :ghost, :to => :unmounted, :create => 1, :unmount => 1) end it "should unmount resource when changing from :mounted to :unmounted" do test_ensure_change(:from => :mounted, :to => :unmounted, :unmount => 1) end it "should create itself when changing from :absent to :unmounted" do test_ensure_change(:from => :absent, :to => :unmounted, :create => 1) end it "should unmount resource when changing from :ghost to :absent" do test_ensure_change(:from => :ghost, :to => :absent, :unmount => 1) end it "should unmount and destroy itself when changing from :mounted to :absent" do test_ensure_change(:from => :mounted, :to => :absent, :destroy => 1, :unmount => 1) end it "should destroy itself when changing from :unmounted to :absent" do test_ensure_change(:from => :unmounted, :to => :absent, :destroy => 1) end it "should create itself when changing from :ghost to :mounted" do test_ensure_change(:from => :ghost, :to => :mounted, :create => 1) end it "should create itself and mount when changing from :absent to :mounted" do test_ensure_change(:from => :absent, :to => :mounted, :create => 1, :mount => 1) end it "should mount resource when changing from :unmounted to :mounted" do test_ensure_change(:from => :unmounted, :to => :mounted, :mount => 1) end it "should be in sync if it is :absent and should be :absent" do ensureprop.should = :absent expect(ensureprop.safe_insync?(:absent)).to eq(true) end it "should be out of sync if it is :absent and should be :defined" do ensureprop.should = :defined expect(ensureprop.safe_insync?(:absent)).to eq(false) end it "should be out of sync if it is :absent and should be :mounted" do ensureprop.should = :mounted expect(ensureprop.safe_insync?(:absent)).to eq(false) end it "should be out of sync if it is :absent and should be :unmounted" do ensureprop.should = :unmounted expect(ensureprop.safe_insync?(:absent)).to eq(false) end it "should be out of sync if it is :mounted and should be :absent" do ensureprop.should = :absent expect(ensureprop.safe_insync?(:mounted)).to eq(false) end it "should be in sync if it is :mounted and should be :defined" do ensureprop.should = :defined expect(ensureprop.safe_insync?(:mounted)).to eq(true) end it "should be in sync if it is :mounted and should be :mounted" do ensureprop.should = :mounted expect(ensureprop.safe_insync?(:mounted)).to eq(true) end it "should be out in sync if it is :mounted and should be :unmounted" do ensureprop.should = :unmounted expect(ensureprop.safe_insync?(:mounted)).to eq(false) end it "should be out of sync if it is :unmounted and should be :absent" do ensureprop.should = :absent expect(ensureprop.safe_insync?(:unmounted)).to eq(false) end it "should be in sync if it is :unmounted and should be :defined" do ensureprop.should = :defined expect(ensureprop.safe_insync?(:unmounted)).to eq(true) end it "should be out of sync if it is :unmounted and should be :mounted" do ensureprop.should = :mounted expect(ensureprop.safe_insync?(:unmounted)).to eq(false) end it "should be in sync if it is :unmounted and should be :unmounted" do ensureprop.should = :unmounted expect(ensureprop.safe_insync?(:unmounted)).to eq(true) end it "should be out of sync if it is :ghost and should be :absent" do ensureprop.should = :absent expect(ensureprop.safe_insync?(:ghost)).to eq(false) end it "should be out of sync if it is :ghost and should be :defined" do ensureprop.should = :defined expect(ensureprop.safe_insync?(:ghost)).to eq(false) end it "should be out of sync if it is :ghost and should be :mounted" do ensureprop.should = :mounted expect(ensureprop.safe_insync?(:ghost)).to eq(false) end it "should be out of sync if it is :ghost and should be :unmounted" do ensureprop.should = :unmounted expect(ensureprop.safe_insync?(:ghost)).to eq(false) end end describe "when responding to refresh" do pending "2.6.x specifies slightly different behavior and the desired behavior needs to be clarified and revisited. See ticket #4904" do it "should remount if it is supposed to be mounted" do resource[:ensure] = "mounted" provider.expects(:remount) resource.refresh end it "should not remount if it is supposed to be present" do resource[:ensure] = "present" provider.expects(:remount).never resource.refresh end it "should not remount if it is supposed to be absent" do resource[:ensure] = "absent" provider.expects(:remount).never resource.refresh end it "should not remount if it is supposed to be defined" do resource[:ensure] = "defined" provider.expects(:remount).never resource.refresh end it "should not remount if it is supposed to be unmounted" do resource[:ensure] = "unmounted" provider.expects(:remount).never resource.refresh end it "should not remount swap filesystems" do resource[:ensure] = "mounted" resource[:fstype] = "swap" provider.expects(:remount).never resource.refresh end end end describe "when modifying an existing mount entry" do let :initial_values do { :ensure => :mounted, :name => '/mnt/foo', :device => "/foo/bar", :blockdevice => "/other/bar", :target => "/what/ever", :options => "soft", :pass => 0, :dump => 0, :atboot => :no, } end let :resource do described_class.new(initial_values.merge(:provider => provider)) end let :provider do providerclass.new(initial_values) end def run_in_catalog(*resources) Puppet::Util::Storage.stubs(:store) catalog = Puppet::Resource::Catalog.new catalog.add_resource(*resources) catalog.apply end it "should use the provider to change the dump value" do provider.expects(:dump=).with(1) resource[:dump] = 1 run_in_catalog(resource) end it "should umount before flushing changes to disk" do syncorder = sequence('syncorder') provider.expects(:unmount).in_sequence(syncorder) provider.expects(:options=).in_sequence(syncorder).with 'hard' resource.expects(:flush).in_sequence(syncorder) # Call inside syncothers resource.expects(:flush).in_sequence(syncorder) # I guess transaction or anything calls flush again resource[:ensure] = :unmounted resource[:options] = 'hard' run_in_catalog(resource) end end describe "establishing autorequires and autobefores" do def create_mount_resource(path) described_class.new( :name => path, :provider => providerclass.new(path) ) end def create_file_resource(path) file_class = Puppet::Type.type(:file) file_class.new( :path => path, :provider => file_class.new(:path => path).provider ) end def create_catalog(*resources) catalog = Puppet::Resource::Catalog.new resources.each do |resource| catalog.add_resource resource end catalog end let(:root_mount) { create_mount_resource("/") } let(:var_mount) { create_mount_resource("/var") } let(:log_mount) { create_mount_resource("/var/log") } let(:var_file) { create_file_resource('/var') } let(:log_file) { create_file_resource('/var/log') } let(:puppet_file) { create_file_resource('/var/log/puppet') } let(:opt_file) { create_file_resource('/opt/var/puppet') } before do create_catalog(root_mount, var_mount, log_mount, var_file, log_file, puppet_file, opt_file) end it "adds no autorequires for the root mount" do expect(root_mount.autorequire).to be_empty end it "adds the parent autorequire and the file autorequire for a mount with one parent" do parent_relationship = var_mount.autorequire[0] expect(var_mount.autorequire).to have_exactly(1).item expect(parent_relationship.source).to eq root_mount expect(parent_relationship.target).to eq var_mount end it "adds both parent autorequires and the file autorequire for a mount with two parents" do grandparent_relationship = log_mount.autorequire[0] parent_relationship = log_mount.autorequire[1] expect(log_mount.autorequire).to have_exactly(2).items expect(grandparent_relationship.source).to eq root_mount expect(grandparent_relationship.target).to eq log_mount expect(parent_relationship.source).to eq var_mount expect(parent_relationship.target).to eq log_mount end it "adds the child autobefore for a mount with one file child" do child_relationship = log_mount.autobefore[0] expect(log_mount.autobefore).to have_exactly(1).item expect(child_relationship.source).to eq log_mount expect(child_relationship.target).to eq puppet_file end it "adds both child autobefores for a mount with two file children" do child_relationship = var_mount.autobefore[0] grandchild_relationship = var_mount.autobefore[1] expect(var_mount.autobefore).to have_exactly(2).items expect(child_relationship.source).to eq var_mount expect(child_relationship.target).to eq log_file expect(grandchild_relationship.source).to eq var_mount expect(grandchild_relationship.target).to eq puppet_file end end end puppet-5.5.10/spec/unit/type/nagios_spec.rb0000644005276200011600000002775013417161722020537 0ustar jenkinsjenkins#! /usr/bin/env ruby # encoding: utf-8 require 'spec_helper' require 'puppet/external/nagios' describe "Nagios parser" do NONESCAPED_SEMICOLON_COMMENT = <<-'EOL' define host{ use linux-server ; Name of host template to use host_name localhost alias localhost address 127.0.0.1 } define command{ command_name notify-host-by-email command_line /usr/bin/printf "%b" "***** Nagios *****\n\nNotification Type: $NOTIFICATIONTYPE$\nHost: $HOSTNAME$\nState: $HOSTSTATE$\nAddress: $HOSTADDRESS$\nInfo: $HOSTOUTPUT$\n\nDate/Time: $LONGDATETIME$\n" | /usr/bin/mail -s "** $NOTIFICATIONTYPE$ Host Alert: $HOSTNAME$ is $HOSTSTATE$ **" $CONTACTEMAIL$ } EOL LINE_COMMENT_SNIPPET = <<-'EOL' # This is a comment starting at the beginning of a line define command{ # This is a comment starting at the beginning of a line command_name command_name # This is a comment starting at the beginning of a line ## --PUPPET_NAME-- (called '_naginator_name' in the manifest) command_name command_line command_line # This is a comment starting at the beginning of a line } # This is a comment starting at the beginning of a line EOL LINE_COMMENT_SNIPPET2 = <<-'EOL' define host{ use linux-server ; Name of host template to use host_name localhost alias localhost address 127.0.0.1 } define command{ command_name command_name2 command_line command_line2 } EOL UNKNOWN_NAGIOS_OBJECT_DEFINITION = <<-'EOL' define command2{ command_name notify-host-by-email command_line /usr/bin/printf "%b" "***** Nagios *****\n\nNotification Type: $NOTIFICATIONTYPE$\nHost: $HOSTNAME$\nState: $HOSTSTATE$\nAddress: $HOSTADDRESS$\nInfo: $HOSTOUTPUT$\n\nDate/Time: $LONGDATETIME$\n" | /usr/bin/mail -s "** $NOTIFICATIONTYPE$ Host Alert: $HOSTNAME$ is $HOSTSTATE$ **" $CONTACTEMAIL$ } EOL MISSING_CLOSING_CURLY_BRACKET = <<-'EOL' define command{ command_name notify-host-by-email command_line /usr/bin/printf "%b" "***** Nagios *****\n\nNotification Type: $NOTIFICATIONTYPE$\nHost: $HOSTNAME$\nState: $HOSTSTATE$\nAddress: $HOSTADDRESS$\nInfo: $HOSTOUTPUT$\n\nDate/Time: $LONGDATETIME$\n" | /usr/bin/mail -s "** $NOTIFICATIONTYPE$ Host Alert: $HOSTNAME$ is $HOSTSTATE$ **" $CONTACTEMAIL$ EOL ESCAPED_SEMICOLON = <<-'EOL' define command { command_name nagios_table_size command_line $USER3$/check_mysql_health --hostname localhost --username nagioschecks --password nagiosCheckPWD --mode sql --name "SELECT ROUND(Data_length/1024) as Data_kBytes from INFORMATION_SCHEMA.TABLES where TABLE_NAME=\"$ARG1$\"\;" --name2 "table size" --units kBytes -w $ARG2$ -c $ARG3$ } EOL POUND_SIGN_HASH_SYMBOL_NOT_IN_FIRST_COLUMN = <<-'EOL' define command { command_name notify-by-irc command_line /usr/local/bin/riseup-nagios-client.pl "$HOSTNAME$ ($SERVICEDESC$) $NOTIFICATIONTYPE$ #$SERVICEATTEMPT$ $SERVICESTATETYPE$ $SERVICEEXECUTIONTIME$s $SERVICELATENCY$s $SERVICEOUTPUT$ $SERVICEPERFDATA$" } EOL ANOTHER_ESCAPED_SEMICOLON = <<-EOL define command { \tcommand_line LC_ALL=en_US.UTF-8 /usr/lib/nagios/plugins/check_haproxy -u 'http://blah:blah@$HOSTADDRESS$:8080/haproxy?stats\\;csv' \tcommand_name check_haproxy } EOL UNICODE_NAGIOS_CONTACT = <<-EOL define contact { \talias Paul Tötterman \tcontact_name ptman } EOL it "should parse without error" do parser = Nagios::Parser.new expect { parser.parse(NONESCAPED_SEMICOLON_COMMENT) }.to_not raise_error end describe "when parsing a statement" do parser = Nagios::Parser.new results = parser.parse(NONESCAPED_SEMICOLON_COMMENT) results.each do |obj| it "should have the proper base type" do expect(obj).to be_a_kind_of(Nagios::Base) end end end it "should raise an error when an incorrect object definition is present" do parser = Nagios::Parser.new expect { parser.parse(UNKNOWN_NAGIOS_OBJECT_DEFINITION) }.to raise_error Nagios::Base::UnknownNagiosType end it "should raise an error when syntax is not correct" do parser = Nagios::Parser.new expect { parser.parse(MISSING_CLOSING_CURLY_BRACKET) }.to raise_error Nagios::Parser::SyntaxError end describe "when encoutering ';'" do it "should not throw an exception" do parser = Nagios::Parser.new expect { parser.parse(ESCAPED_SEMICOLON) }.to_not raise_error end it "should ignore it if it is a comment" do parser = Nagios::Parser.new results = parser.parse(NONESCAPED_SEMICOLON_COMMENT) expect(results[0].use).to eql("linux-server") end it "should parse correctly if it is escaped" do parser = Nagios::Parser.new results = parser.parse(ESCAPED_SEMICOLON) expect(results[0].command_line).to eql("$USER3$/check_mysql_health --hostname localhost --username nagioschecks --password nagiosCheckPWD --mode sql --name \"SELECT ROUND(Data_length/1024) as Data_kBytes from INFORMATION_SCHEMA.TABLES where TABLE_NAME=\\\"$ARG1$\\\";\" --name2 \"table size\" --units kBytes -w $ARG2$ -c $ARG3$") end end describe "when encountering '#'" do it "should not throw an exception" do parser = Nagios::Parser.new expect { parser.parse(POUND_SIGN_HASH_SYMBOL_NOT_IN_FIRST_COLUMN) }.to_not raise_error end it "should ignore it at the beginning of a line" do parser = Nagios::Parser.new results = parser.parse(LINE_COMMENT_SNIPPET) expect(results[0].command_line).to eql("command_line") end it "should let it go anywhere else" do parser = Nagios::Parser.new results = parser.parse(POUND_SIGN_HASH_SYMBOL_NOT_IN_FIRST_COLUMN) expect(results[0].command_line).to eql("/usr/local/bin/riseup-nagios-client.pl \"$HOSTNAME$ ($SERVICEDESC$) $NOTIFICATIONTYPE$ \#$SERVICEATTEMPT$ $SERVICESTATETYPE$ $SERVICEEXECUTIONTIME$s $SERVICELATENCY$s $SERVICEOUTPUT$ $SERVICEPERFDATA$\"") end end describe "when encountering ';' again" do it "should not throw an exception" do parser = Nagios::Parser.new expect { parser.parse(ANOTHER_ESCAPED_SEMICOLON) }.to_not raise_error end it "should parse correctly" do parser = Nagios::Parser.new results = parser.parse(ANOTHER_ESCAPED_SEMICOLON) expect(results[0].command_line).to eql("LC_ALL=en_US.UTF-8 /usr/lib/nagios/plugins/check_haproxy -u 'http://blah:blah@$HOSTADDRESS$:8080/haproxy?stats;csv'") end end it "should be idempotent" do parser = Nagios::Parser.new src = ANOTHER_ESCAPED_SEMICOLON.dup results = parser.parse(src) nagios_type = Nagios::Base.create(:command) nagios_type.command_name = results[0].command_name nagios_type.command_line = results[0].command_line expect(nagios_type.to_s).to eql(ANOTHER_ESCAPED_SEMICOLON) end describe "when reading UTF8 values" do it "should be converted to ASCII_8BIT for ruby 1.9 / 2.0", :if => RUBY_VERSION < "2.1.0" && String.method_defined?(:encode) do parser = Nagios::Parser.new results = parser.parse(UNICODE_NAGIOS_CONTACT) expect(results[0].alias.encoding).to eq(Encoding::ASCII_8BIT) expect(results[0].alias).to eq('Paul Tötterman'.force_encoding(Encoding::ASCII_8BIT)) end it "must not be converted for ruby >= 2.1", :if => RUBY_VERSION >= "2.1.0" do parser = Nagios::Parser.new results = parser.parse(UNICODE_NAGIOS_CONTACT) expect(results[0].alias.encoding).to eq(Encoding::UTF_8) end end end describe "Nagios generator" do it "should escape ';'" do param = '$USER3$/check_mysql_health --hostname localhost --username nagioschecks --password nagiosCheckPWD --mode sql --name "SELECT ROUND(Data_length/1024) as Data_kBytes from INFORMATION_SCHEMA.TABLES where TABLE_NAME=\"$ARG1$\";" --name2 "table size" --units kBytes -w $ARG2$ -c $ARG3$' nagios_type = Nagios::Base.create(:command) nagios_type.command_line = param expect(nagios_type.to_s).to eql("define command {\n\tcommand_line $USER3$/check_mysql_health --hostname localhost --username nagioschecks --password nagiosCheckPWD --mode sql --name \"SELECT ROUND(Data_length/1024) as Data_kBytes from INFORMATION_SCHEMA.TABLES where TABLE_NAME=\\\"$ARG1$\\\"\\;\" --name2 \"table size\" --units kBytes -w $ARG2$ -c $ARG3$\n}\n") end it "should escape ';' if it is not already the case" do param = "LC_ALL=en_US.UTF-8 /usr/lib/nagios/plugins/check_haproxy -u 'http://blah:blah@$HOSTADDRESS$:8080/haproxy?stats;csv'" nagios_type = Nagios::Base.create(:command) nagios_type.command_line = param expect(nagios_type.to_s).to eql("define command {\n\tcommand_line LC_ALL=en_US.UTF-8 /usr/lib/nagios/plugins/check_haproxy -u 'http://blah:blah@$HOSTADDRESS$:8080/haproxy?stats\\;csv'\n}\n") end it "should be idempotent" do param = '$USER3$/check_mysql_health --hostname localhost --username nagioschecks --password nagiosCheckPWD --mode sql --name "SELECT ROUND(Data_length/1024) as Data_kBytes from INFORMATION_SCHEMA.TABLES where TABLE_NAME=\"$ARG1$\";" --name2 "table size" --units kBytes -w $ARG2$ -c $ARG3$' nagios_type = Nagios::Base.create(:command) nagios_type.command_line = param parser = Nagios::Parser.new results = parser.parse(nagios_type.to_s) expect(results[0].command_line).to eql(param) end it "should accept FixNum params and convert to string" do param = 1 nagios_type = Nagios::Base.create(:serviceescalation) nagios_type.first_notification = param parser = Nagios::Parser.new results = parser.parse(nagios_type.to_s) expect(results[0].first_notification).to eql(param.to_s) end end describe "Nagios resource types" do Nagios::Base.eachtype do |name, nagios_type| puppet_type = Puppet::Type.type("nagios_#{name}") it "should have a valid type for #{name}" do expect(puppet_type).not_to be_nil end next unless puppet_type describe puppet_type do it "should be defined as a Puppet resource type" do expect(puppet_type).not_to be_nil end it "should have documentation" do expect(puppet_type.instance_variable_get("@doc")).not_to eq("") end it "should have #{nagios_type.namevar} as its key attribute" do expect(puppet_type.key_attributes).to eq([nagios_type.namevar]) end it "should have documentation for its #{nagios_type.namevar} parameter" do expect(puppet_type.attrclass(nagios_type.namevar).instance_variable_get("@doc")).not_to be_nil end it "should have an ensure property" do expect(puppet_type).to be_validproperty(:ensure) end it "should have a target property" do expect(puppet_type).to be_validproperty(:target) end it "should have documentation for its target property" do expect(puppet_type.attrclass(:target).instance_variable_get("@doc")).not_to be_nil end [ :owner, :group, :mode ].each do |fileprop| it "should have a #{fileprop} parameter" do expect(puppet_type.parameters).to be_include(fileprop) end end nagios_type.parameters.reject { |param| param == nagios_type.namevar or param.to_s =~ /^[0-9]/ }.each do |param| it "should have a #{param} property" do expect(puppet_type).to be_validproperty(param) end it "should have documentation for its #{param} property" do expect(puppet_type.attrclass(param).instance_variable_get("@doc")).not_to be_nil end end nagios_type.parameters.find_all { |param| param.to_s =~ /^[0-9]/ }.each do |param| it "should have not have a #{param} property" do expect(puppet_type).not_to be_validproperty(:param) end end end end end puppet-5.5.10/spec/unit/type/package_spec.rb0000644005276200011600000003351713417161722020650 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:package) do before do Process.stubs(:euid).returns 0 Puppet::Util::Storage.stubs(:store) end it "should have a :reinstallable feature that requires the :reinstall method" do expect(Puppet::Type.type(:package).provider_feature(:reinstallable).methods).to eq([:reinstall]) end it "should have an :installable feature that requires the :install method" do expect(Puppet::Type.type(:package).provider_feature(:installable).methods).to eq([:install]) end it "should have an :uninstallable feature that requires the :uninstall method" do expect(Puppet::Type.type(:package).provider_feature(:uninstallable).methods).to eq([:uninstall]) end it "should have an :upgradeable feature that requires :update and :latest methods" do expect(Puppet::Type.type(:package).provider_feature(:upgradeable).methods).to eq([:update, :latest]) end it "should have a :purgeable feature that requires the :purge latest method" do expect(Puppet::Type.type(:package).provider_feature(:purgeable).methods).to eq([:purge]) end it "should have a :versionable feature" do expect(Puppet::Type.type(:package).provider_feature(:versionable)).not_to be_nil end it "should have a :package_settings feature that requires :package_settings_insync?, :package_settings and :package_settings=" do expect(Puppet::Type.type(:package).provider_feature(:package_settings).methods).to eq([:package_settings_insync?, :package_settings, :package_settings=]) end it "should default to being installed" do pkg = Puppet::Type.type(:package).new(:name => "yay", :provider => :apt) expect(pkg.should(:ensure)).to eq(:present) end describe "when validating attributes" do [:name, :source, :instance, :status, :adminfile, :responsefile, :configfiles, :category, :platform, :root, :vendor, :description, :allowcdrom, :allow_virtual, :reinstall_on_refresh].each do |param| it "should have a #{param} parameter" do expect(Puppet::Type.type(:package).attrtype(param)).to eq(:param) end end it "should have an ensure property" do expect(Puppet::Type.type(:package).attrtype(:ensure)).to eq(:property) end it "should have a package_settings property" do expect(Puppet::Type.type(:package).attrtype(:package_settings)).to eq(:property) end end describe "when validating attribute values" do before :each do @provider = stub( 'provider', :class => Puppet::Type.type(:package).defaultprovider, :clear => nil, :validate_source => nil ) Puppet::Type.type(:package).defaultprovider.stubs(:new).returns(@provider) end after :each do Puppet::Type.type(:package).defaultprovider = nil end it "should support :present as a value to :ensure" do Puppet::Type.type(:package).new(:name => "yay", :ensure => :present) end it "should alias :installed to :present as a value to :ensure" do pkg = Puppet::Type.type(:package).new(:name => "yay", :ensure => :installed) expect(pkg.should(:ensure)).to eq(:present) end it "should support :absent as a value to :ensure" do Puppet::Type.type(:package).new(:name => "yay", :ensure => :absent) end it "should support :purged as a value to :ensure if the provider has the :purgeable feature" do @provider.expects(:satisfies?).with([:purgeable]).returns(true) Puppet::Type.type(:package).new(:name => "yay", :ensure => :purged) end it "should not support :purged as a value to :ensure if the provider does not have the :purgeable feature" do @provider.expects(:satisfies?).with([:purgeable]).returns(false) expect { Puppet::Type.type(:package).new(:name => "yay", :ensure => :purged) }.to raise_error(Puppet::Error) end it "should support :latest as a value to :ensure if the provider has the :upgradeable feature" do @provider.expects(:satisfies?).with([:upgradeable]).returns(true) Puppet::Type.type(:package).new(:name => "yay", :ensure => :latest) end it "should not support :latest as a value to :ensure if the provider does not have the :upgradeable feature" do @provider.expects(:satisfies?).with([:upgradeable]).returns(false) expect { Puppet::Type.type(:package).new(:name => "yay", :ensure => :latest) }.to raise_error(Puppet::Error) end it "should support version numbers as a value to :ensure if the provider has the :versionable feature" do @provider.expects(:satisfies?).with([:versionable]).returns(true) Puppet::Type.type(:package).new(:name => "yay", :ensure => "1.0") end it "should not support version numbers as a value to :ensure if the provider does not have the :versionable feature" do @provider.expects(:satisfies?).with([:versionable]).returns(false) expect { Puppet::Type.type(:package).new(:name => "yay", :ensure => "1.0") }.to raise_error(Puppet::Error) end it "should accept any string as an argument to :source" do expect { Puppet::Type.type(:package).new(:name => "yay", :source => "stuff") }.to_not raise_error end it "should not accept a non-string name" do expect do Puppet::Type.type(:package).new(:name => ["error"]) end.to raise_error(Puppet::ResourceError, /Name must be a String/) end end module PackageEvaluationTesting def setprops(properties) @provider.stubs(:properties).returns(properties) end end describe Puppet::Type.type(:package) do before :each do @provider = stub( 'provider', :class => Puppet::Type.type(:package).defaultprovider, :clear => nil, :satisfies? => true, :name => :mock, :validate_source => nil ) Puppet::Type.type(:package).defaultprovider.stubs(:new).returns(@provider) Puppet::Type.type(:package).defaultprovider.stubs(:instances).returns([]) @package = Puppet::Type.type(:package).new(:name => "yay") @catalog = Puppet::Resource::Catalog.new @catalog.add_resource(@package) end describe Puppet::Type.type(:package), "when it should be purged" do include PackageEvaluationTesting before { @package[:ensure] = :purged } it "should do nothing if it is :purged" do @provider.expects(:properties).returns(:ensure => :purged).at_least_once @catalog.apply end [:absent, :installed, :present, :latest].each do |state| it "should purge if it is #{state.to_s}" do @provider.stubs(:properties).returns(:ensure => state) @provider.expects(:purge) @catalog.apply end end end describe Puppet::Type.type(:package), "when it should be absent" do include PackageEvaluationTesting before { @package[:ensure] = :absent } [:purged, :absent].each do |state| it "should do nothing if it is #{state.to_s}" do @provider.expects(:properties).returns(:ensure => state).at_least_once @catalog.apply end end [:installed, :present, :latest].each do |state| it "should uninstall if it is #{state.to_s}" do @provider.stubs(:properties).returns(:ensure => state) @provider.expects(:uninstall) @catalog.apply end end end describe Puppet::Type.type(:package), "when it should be present" do include PackageEvaluationTesting before { @package[:ensure] = :present } [:present, :latest, "1.0"].each do |state| it "should do nothing if it is #{state.to_s}" do @provider.expects(:properties).returns(:ensure => state).at_least_once @catalog.apply end end [:purged, :absent].each do |state| it "should install if it is #{state.to_s}" do @provider.stubs(:properties).returns(:ensure => state) @provider.expects(:install) @catalog.apply end end end describe Puppet::Type.type(:package), "when it should be latest" do include PackageEvaluationTesting before { @package[:ensure] = :latest } [:purged, :absent].each do |state| it "should upgrade if it is #{state.to_s}" do @provider.stubs(:properties).returns(:ensure => state) @provider.expects(:update) @catalog.apply end end it "should upgrade if the current version is not equal to the latest version" do @provider.stubs(:properties).returns(:ensure => "1.0") @provider.stubs(:latest).returns("2.0") @provider.expects(:update) @catalog.apply end it "should do nothing if it is equal to the latest version" do @provider.stubs(:properties).returns(:ensure => "1.0") @provider.stubs(:latest).returns("1.0") @provider.expects(:update).never @catalog.apply end it "should do nothing if the provider returns :present as the latest version" do @provider.stubs(:properties).returns(:ensure => :present) @provider.stubs(:latest).returns("1.0") @provider.expects(:update).never @catalog.apply end end describe Puppet::Type.type(:package), "when it should be a specific version" do include PackageEvaluationTesting before { @package[:ensure] = "1.0" } [:purged, :absent].each do |state| it "should install if it is #{state.to_s}" do @provider.stubs(:properties).returns(:ensure => state) expect(@package.property(:ensure).insync?(state)).to be_falsey @provider.expects(:install) @catalog.apply end end it "should do nothing if the current version is equal to the desired version" do @provider.stubs(:properties).returns(:ensure => "1.0") expect(@package.property(:ensure).insync?('1.0')).to be_truthy @provider.expects(:install).never @catalog.apply end it "should install if the current version is not equal to the specified version" do @provider.stubs(:properties).returns(:ensure => "2.0") expect(@package.property(:ensure).insync?('2.0')).to be_falsey @provider.expects(:install) @catalog.apply end describe "when current value is an array" do let(:installed_versions) { ["1.0", "2.0", "3.0"] } before (:each) do @provider.stubs(:properties).returns(:ensure => installed_versions) end it "should install if value not in the array" do @package[:ensure] = "1.5" expect(@package.property(:ensure).insync?(installed_versions)).to be_falsey @provider.expects(:install) @catalog.apply end it "should not install if value is in the array" do @package[:ensure] = "2.0" expect(@package.property(:ensure).insync?(installed_versions)).to be_truthy @provider.expects(:install).never @catalog.apply end describe "when ensure is set to 'latest'" do it "should not install if the value is in the array" do @provider.expects(:latest).returns("3.0") @package[:ensure] = "latest" expect(@package.property(:ensure).insync?(installed_versions)).to be_truthy @provider.expects(:install).never @catalog.apply end end end end describe Puppet::Type.type(:package), "when responding to refresh" do include PackageEvaluationTesting it "should support :true as a value to :reinstall_on_refresh" do srv = Puppet::Type.type(:package).new(:name => "yay", :reinstall_on_refresh => :true) expect(srv[:reinstall_on_refresh]).to eq(:true) end it "should support :false as a value to :reinstall_on_refresh" do srv = Puppet::Type.type(:package).new(:name => "yay", :reinstall_on_refresh => :false) expect(srv[:reinstall_on_refresh]).to eq(:false) end it "should specify :false as the default value of :reinstall_on_refresh" do srv = Puppet::Type.type(:package).new(:name => "yay") expect(srv[:reinstall_on_refresh]).to eq(:false) end [:latest, :present, :installed].each do |state| it "should reinstall if it should be #{state.to_s} and reinstall_on_refresh is true" do @package[:ensure] = state @package[:reinstall_on_refresh] = :true @provider.stubs(:reinstallable?).returns(true) @provider.expects(:reinstall).once @package.refresh end it "should reinstall if it should be #{state.to_s} and reinstall_on_refresh is false" do @package[:ensure] = state @package[:reinstall_on_refresh] = :false @provider.stubs(:reinstallable?).returns(true) @provider.expects(:reinstall).never @package.refresh end end [:purged, :absent, :held].each do |state| it "should not reinstall if it should be #{state.to_s} and reinstall_on_refresh is true" do @package[:ensure] = state @provider.stubs(:reinstallable?).returns(true) @provider.expects(:reinstall).never @package.refresh end it "should not reinstall if it should be #{state.to_s} and reinstall_on_refresh is false" do @package[:ensure] = state @provider.stubs(:reinstallable?).returns(true) @provider.expects(:reinstall).never @package.refresh end end end end describe "allow_virtual" do it "defaults to true on platforms that support virtual packages" do pkg = Puppet::Type.type(:package).new(:name => 'yay', :provider => :yum) expect(pkg[:allow_virtual]).to eq true end it "defaults to false on platforms that do not support virtual packages" do pkg = Puppet::Type.type(:package).new(:name => 'yay', :provider => :apple) expect(pkg[:allow_virtual]).to be_nil end end end puppet-5.5.10/spec/unit/type/resources_spec.rb0000644005276200011600000003524113417161722021263 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' resources = Puppet::Type.type(:resources) # There are still plenty of tests to port over from test/. describe resources do before :each do described_class.reset_system_users_max_uid! end context "when initializing" do it "should fail if the specified resource type does not exist" do Puppet::Type.stubs(:type).with { |x| x.to_s.downcase == "resources"}.returns resources Puppet::Type.expects(:type).with("nosuchtype").returns nil expect { resources.new :name => "nosuchtype" }.to raise_error(Puppet::Error) end it "should not fail when the specified resource type exists" do expect { resources.new :name => "file" }.not_to raise_error end it "should set its :resource_type attribute" do expect(resources.new(:name => "file").resource_type).to eq(Puppet::Type.type(:file)) end end context "purge" do let (:instance) { described_class.new(:name => 'file') } it "defaults to false" do expect(instance[:purge]).to be_falsey end it "can be set to false" do instance[:purge] = 'false' end it "cannot be set to true for a resource type that does not accept ensure" do instance.resource_type.stubs(:respond_to?).returns true instance.resource_type.stubs(:validproperty?).returns false expect { instance[:purge] = 'yes' }.to raise_error Puppet::Error end it "cannot be set to true for a resource type that does not have instances" do instance.resource_type.stubs(:respond_to?).returns false instance.resource_type.stubs(:validproperty?).returns true expect { instance[:purge] = 'yes' }.to raise_error Puppet::Error end it "can be set to true for a resource type that has instances and can accept ensure" do instance.resource_type.stubs(:respond_to?).returns true instance.resource_type.stubs(:validproperty?).returns true expect { instance[:purge] = 'yes' }.to_not raise_error end end context "#check_user purge behaviour" do context "with unless_system_user => true" do before do @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_system_user => true @res.catalog = Puppet::Resource::Catalog.new Puppet::FileSystem.stubs(:exist?).with('/etc/login.defs').returns false end it "should never purge hardcoded system users" do %w{root nobody bin noaccess daemon sys}.each do |sys_user| expect(@res.user_check(Puppet::Type.type(:user).new(:name => sys_user))).to be_falsey end end it "should not purge system users if unless_system_user => true" do user_hash = {:name => 'system_user', :uid => 125, :system => true} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) expect(@res.user_check(user)).to be_falsey end it "should purge non-system users if unless_system_user => true" do user_hash = {:name => 'system_user', :uid => described_class.system_users_max_uid + 1, :system => true} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) expect(@res.user_check(user)).to be_truthy end it "should not purge system users under 600 if unless_system_user => 600" do res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_system_user => 600 res.catalog = Puppet::Resource::Catalog.new user_hash = {:name => 'system_user', :uid => 500, :system => true} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) expect(res.user_check(user)).to be_falsey end it "should not purge Windows system users" do res = Puppet::Type.type(:resources).new :name => :user, :purge => true res.catalog = Puppet::Resource::Catalog.new user_hash = {:name => 'Administrator', :uid => 'S-1-5-21-12345-500'} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) expect(res.user_check(user)).to be_falsey end it "should not purge Windows system users" do res = Puppet::Type.type(:resources).new :name => :user, :purge => true res.catalog = Puppet::Resource::Catalog.new user_hash = {:name => 'other', :uid => 'S-1-5-21-12345-1001'} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) expect(res.user_check(user)).to be_truthy end end %w(FreeBSD OpenBSD).each do |os| context "on #{os}" do before :each do Facter.stubs(:value).with(:kernel).returns(os) Facter.stubs(:value).with(:operatingsystem).returns(os) Facter.stubs(:value).with(:osfamily).returns(os) Puppet::FileSystem.stubs(:exist?).with('/etc/login.defs').returns false @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_system_user => true @res.catalog = Puppet::Resource::Catalog.new end it "should not purge system users under 1000" do user_hash = {:name => 'system_user', :uid => 999} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) expect(@res.user_check(user)).to be_falsey end it "should purge users over 999" do user_hash = {:name => 'system_user', :uid => 1000} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) expect(@res.user_check(user)).to be_truthy end end end context 'with login.defs present' do before :each do Puppet::FileSystem.expects(:exist?).with('/etc/login.defs').returns true Puppet::FileSystem.expects(:each_line).with('/etc/login.defs').yields(' UID_MIN 1234 # UID_MIN comment ') @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_system_user => true @res.catalog = Puppet::Resource::Catalog.new end it 'should not purge a system user' do user_hash = {:name => 'system_user', :uid => 1233} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) expect(@res.user_check(user)).to be_falsey end it 'should purge a non-system user' do user_hash = {:name => 'system_user', :uid => 1234} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) expect(@res.user_check(user)).to be_truthy end end context "with unless_uid" do context "with a uid array" do before do @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_uid => [15_000, 15_001, 15_002] @res.catalog = Puppet::Resource::Catalog.new end it "should purge uids that are not in a specified array" do user_hash = {:name => 'special_user', :uid => 25_000} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) expect(@res.user_check(user)).to be_truthy end it "should not purge uids that are in a specified array" do user_hash = {:name => 'special_user', :uid => 15000} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) expect(@res.user_check(user)).to be_falsey end end context "with a single integer uid" do before do @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_uid => 15_000 @res.catalog = Puppet::Resource::Catalog.new end it "should purge uids that are not specified" do user_hash = {:name => 'special_user', :uid => 25_000} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) expect(@res.user_check(user)).to be_truthy end it "should not purge uids that are specified" do user_hash = {:name => 'special_user', :uid => 15_000} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) expect(@res.user_check(user)).to be_falsey end end context "with a single string uid" do before do @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_uid => '15000' @res.catalog = Puppet::Resource::Catalog.new end it "should purge uids that are not specified" do user_hash = {:name => 'special_user', :uid => 25_000} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) expect(@res.user_check(user)).to be_truthy end it "should not purge uids that are specified" do user_hash = {:name => 'special_user', :uid => 15_000} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) expect(@res.user_check(user)).to be_falsey end end context "with a mixed uid array" do before do @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_uid => ['15000', 16_666] @res.catalog = Puppet::Resource::Catalog.new end it "should not purge ids in the range" do user_hash = {:name => 'special_user', :uid => 15_000} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) expect(@res.user_check(user)).to be_falsey end it "should not purge specified ids" do user_hash = {:name => 'special_user', :uid => 16_666} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) expect(@res.user_check(user)).to be_falsey end it "should purge unspecified ids" do user_hash = {:name => 'special_user', :uid => 17_000} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) expect(@res.user_check(user)).to be_truthy end end end end context "#generate" do before do @host1 = Puppet::Type.type(:host).new(:name => 'localhost', :ip => '127.0.0.1') @catalog = Puppet::Resource::Catalog.new end context "when dealing with non-purging resources" do before do @resources = Puppet::Type.type(:resources).new(:name => 'host') end it "should not generate any resource" do expect(@resources.generate).to be_empty end end context "when the catalog contains a purging resource" do before do @resources = Puppet::Type.type(:resources).new(:name => 'host', :purge => true) @purgeable_resource = Puppet::Type.type(:host).new(:name => 'localhost', :ip => '127.0.0.1') @catalog.add_resource @resources end it "should not generate a duplicate of that resource" do Puppet::Type.type(:host).stubs(:instances).returns [@host1] @catalog.add_resource @host1 expect(@resources.generate.collect { |r| r.ref }).not_to include(@host1.ref) end it "should not include the skipped system users" do res = Puppet::Type.type(:resources).new :name => :user, :purge => true res.catalog = Puppet::Resource::Catalog.new root = Puppet::Type.type(:user).new(:name => "root") Puppet::Type.type(:user).expects(:instances).returns [ root ] list = res.generate names = list.collect { |r| r[:name] } expect(names).not_to be_include("root") end context "when generating a purgeable resource" do it "should be included in the generated resources" do Puppet::Type.type(:host).stubs(:instances).returns [@purgeable_resource] expect(@resources.generate.collect { |r| r.ref }).to include(@purgeable_resource.ref) end end context "when the instance's do not have an ensure property" do it "should not be included in the generated resources" do @no_ensure_resource = Puppet::Type.type(:exec).new(:name => "#{File.expand_path('/usr/bin/env')} echo") Puppet::Type.type(:host).stubs(:instances).returns [@no_ensure_resource] expect(@resources.generate.collect { |r| r.ref }).not_to include(@no_ensure_resource.ref) end end context "when the instance's ensure property does not accept absent" do it "should not be included in the generated resources" do @no_absent_resource = Puppet::Type.type(:service).new(:name => 'foobar') Puppet::Type.type(:host).stubs(:instances).returns [@no_absent_resource] expect(@resources.generate.collect { |r| r.ref }).not_to include(@no_absent_resource.ref) end end context "when checking the instance fails" do it "should not be included in the generated resources" do @purgeable_resource = Puppet::Type.type(:host).new(:name => 'foobar') Puppet::Type.type(:host).stubs(:instances).returns [@purgeable_resource] @resources.expects(:check).with(@purgeable_resource).returns(false) expect(@resources.generate.collect { |r| r.ref }).not_to include(@purgeable_resource.ref) end end end end end puppet-5.5.10/spec/unit/type/schedule_spec.rb0000644005276200011600000005024513417161722021046 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' module ScheduleTesting def diff(unit, incr, method, count) diff = Time.now.to_i.send(method, incr * count) Time.at(diff) end def day(method, count) diff(:hour, 3600 * 24, method, count) end def hour(method, count) diff(:hour, 3600, method, count) end def min(method, count) diff(:min, 60, method, count) end end describe Puppet::Type.type(:schedule) do include ScheduleTesting before :each do Puppet[:ignoreschedules] = false @schedule = Puppet::Type.type(:schedule).new(:name => "testing") end describe Puppet::Type.type(:schedule) do it "should apply to device" do expect(@schedule).to be_appliable_to_device end it "should apply to host" do expect(@schedule).to be_appliable_to_host end it "should default to :distance for period-matching" do expect(@schedule[:periodmatch]).to eq(:distance) end it "should default to a :repeat of 1" do expect(@schedule[:repeat]).to eq(1) end it "should never match when the period is :never" do @schedule[:period] = :never expect(@schedule).to_not be_match end end describe Puppet::Type.type(:schedule), "when producing default schedules" do %w{hourly daily weekly monthly never}.each do |period| period = period.to_sym it "should produce a #{period} schedule with the period set appropriately" do schedules = Puppet::Type.type(:schedule).mkdefaultschedules expect(schedules.find { |s| s[:name] == period.to_s and s[:period] == period }).to be_instance_of(Puppet::Type.type(:schedule)) end end it "should not produce default schedules when default_schedules is false" do Puppet[:default_schedules] = false schedules = Puppet::Type.type(:schedule).mkdefaultschedules expect(schedules).to have_exactly(0).items end it "should produce a schedule named puppet with a period of hourly and a repeat of 2" do schedules = Puppet::Type.type(:schedule).mkdefaultschedules expect(schedules.find { |s| s[:name] == "puppet" and s[:period] == :hourly and s[:repeat] == 2 }).to be_instance_of(Puppet::Type.type(:schedule)) end end describe Puppet::Type.type(:schedule), "when matching ranges" do before do Time.stubs(:now).returns(Time.local(2011, "may", 23, 11, 0, 0)) end it "should match when the start time is before the current time and the end time is after the current time" do @schedule[:range] = "10:59:50 - 11:00:10" expect(@schedule).to be_match end it "should not match when the start time is after the current time" do @schedule[:range] = "11:00:05 - 11:00:10" expect(@schedule).to_not be_match end it "should not match when the end time is previous to the current time" do @schedule[:range] = "10:59:50 - 10:59:55" expect(@schedule).to_not be_match end it "should not match the current time fails between an array of ranges" do @schedule[:range] = ["4-6", "20-23"] expect(@schedule).to_not be_match end it "should match the lower array of ranges" do @schedule[:range] = ["9-11", "14-16"] expect(@schedule).to be_match end it "should match the upper array of ranges" do @schedule[:range] = ["4-6", "11-12"] expect(@schedule).to be_match end end describe Puppet::Type.type(:schedule), "when matching ranges with abbreviated time specifications" do before do Time.stubs(:now).returns(Time.local(2011, "may", 23, 11, 45, 59)) end it "should match when just an hour is specified" do @schedule[:range] = "11-12" expect(@schedule).to be_match end it "should not match when the ending hour is the current hour" do @schedule[:range] = "10-11" expect(@schedule).to_not be_match end it "should not match when the ending minute is the current minute" do @schedule[:range] = "10:00 - 11:45" expect(@schedule).to_not be_match end end describe Puppet::Type.type(:schedule), "when matching ranges with abbreviated time specifications, edge cases part 1" do before do Time.stubs(:now).returns(Time.local(2011, "may", 23, 11, 00, 00)) end it "should match when the current time is the start of the range using hours" do @schedule[:range] = "11 - 12" expect(@schedule).to be_match end it "should match when the current time is the end of the range using hours" do @schedule[:range] = "10 - 11" expect(@schedule).to be_match end it "should match when the current time is the start of the range using hours and minutes" do @schedule[:range] = "11:00 - 12:00" expect(@schedule).to be_match end it "should match when the current time is the end of the range using hours and minutes" do @schedule[:range] = "10:00 - 11:00" expect(@schedule).to be_match end end describe Puppet::Type.type(:schedule), "when matching ranges with abbreviated time specifications, edge cases part 2" do before do Time.stubs(:now).returns(Time.local(2011, "may", 23, 11, 00, 01)) end it "should match when the current time is just past the start of the range using hours" do @schedule[:range] = "11 - 12" expect(@schedule).to be_match end it "should not match when the current time is just past the end of the range using hours" do @schedule[:range] = "10 - 11" expect(@schedule).to_not be_match end it "should match when the current time is just past the start of the range using hours and minutes" do @schedule[:range] = "11:00 - 12:00" expect(@schedule).to be_match end it "should not match when the current time is just past the end of the range using hours and minutes" do @schedule[:range] = "10:00 - 11:00" expect(@schedule).to_not be_match end end describe Puppet::Type.type(:schedule), "when matching ranges with abbreviated time specifications, edge cases part 3" do before do Time.stubs(:now).returns(Time.local(2011, "may", 23, 10, 59, 59)) end it "should not match when the current time is just before the start of the range using hours" do @schedule[:range] = "11 - 12" expect(@schedule).to_not be_match end it "should match when the current time is just before the end of the range using hours" do @schedule[:range] = "10 - 11" expect(@schedule).to be_match end it "should not match when the current time is just before the start of the range using hours and minutes" do @schedule[:range] = "11:00 - 12:00" expect(@schedule).to_not be_match end it "should match when the current time is just before the end of the range using hours and minutes" do @schedule[:range] = "10:00 - 11:00" expect(@schedule).to be_match end end describe Puppet::Type.type(:schedule), "when matching ranges spanning days, day 1" do before do # Test with the current time at a month's end boundary to ensure we are # advancing the day properly when we push the ending limit out a day. # For example, adding 1 to 31 would throw an error instead of advancing # the date. Time.stubs(:now).returns(Time.local(2011, "mar", 31, 22, 30, 0)) end it "should match when the start time is before current time and the end time is the following day" do @schedule[:range] = "22:00:00 - 02:00:00" expect(@schedule).to be_match end it "should not match when the current time is outside the range" do @schedule[:range] = "23:30:00 - 21:00:00" expect(@schedule).to_not be_match end end describe Puppet::Type.type(:schedule), "when matching ranges spanning days, day 2" do before do # Test with the current time at a month's end boundary to ensure we are # advancing the day properly when we push the ending limit out a day. # For example, adding 1 to 31 would throw an error instead of advancing # the date. Time.stubs(:now).returns(Time.local(2011, "mar", 31, 1, 30, 0)) end it "should match when the start time is the day before the current time and the end time is after the current time" do @schedule[:range] = "22:00:00 - 02:00:00" expect(@schedule).to be_match end it "should not match when the start time is after the current time" do @schedule[:range] = "02:00:00 - 00:30:00" expect(@schedule).to_not be_match end it "should not match when the end time is before the current time" do @schedule[:range] = "22:00:00 - 01:00:00" expect(@schedule).to_not be_match end end describe Puppet::Type.type(:schedule), "when matching hourly by distance" do before do @schedule[:period] = :hourly @schedule[:periodmatch] = :distance Time.stubs(:now).returns(Time.local(2011, "may", 23, 11, 0, 0)) end it "should match when the previous time was an hour ago" do expect(@schedule).to be_match(hour("-", 1)) end it "should not match when the previous time was now" do expect(@schedule).to_not be_match(Time.now) end it "should not match when the previous time was 59 minutes ago" do expect(@schedule).to_not be_match(min("-", 59)) end end describe Puppet::Type.type(:schedule), "when matching daily by distance" do before do @schedule[:period] = :daily @schedule[:periodmatch] = :distance Time.stubs(:now).returns(Time.local(2011, "may", 23, 11, 0, 0)) end it "should match when the previous time was one day ago" do expect(@schedule).to be_match(day("-", 1)) end it "should not match when the previous time is now" do expect(@schedule).to_not be_match(Time.now) end it "should not match when the previous time was 23 hours ago" do expect(@schedule).to_not be_match(hour("-", 23)) end end describe Puppet::Type.type(:schedule), "when matching weekly by distance" do before do @schedule[:period] = :weekly @schedule[:periodmatch] = :distance Time.stubs(:now).returns(Time.local(2011, "may", 23, 11, 0, 0)) end it "should match when the previous time was seven days ago" do expect(@schedule).to be_match(day("-", 7)) end it "should not match when the previous time was now" do expect(@schedule).to_not be_match(Time.now) end it "should not match when the previous time was six days ago" do expect(@schedule).to_not be_match(day("-", 6)) end end describe Puppet::Type.type(:schedule), "when matching monthly by distance" do before do @schedule[:period] = :monthly @schedule[:periodmatch] = :distance Time.stubs(:now).returns(Time.local(2011, "may", 23, 11, 0, 0)) end it "should match when the previous time was 32 days ago" do expect(@schedule).to be_match(day("-", 32)) end it "should not match when the previous time was now" do expect(@schedule).to_not be_match(Time.now) end it "should not match when the previous time was 27 days ago" do expect(@schedule).to_not be_match(day("-", 27)) end end describe Puppet::Type.type(:schedule), "when matching hourly by number" do before do @schedule[:period] = :hourly @schedule[:periodmatch] = :number end it "should match if the times are one minute apart and the current minute is 0" do current = Time.utc(2008, 1, 1, 0, 0, 0) previous = Time.utc(2007, 12, 31, 23, 59, 0) Time.stubs(:now).returns(current) expect(@schedule).to be_match(previous) end it "should not match if the times are 59 minutes apart and the current minute is 59" do current = Time.utc(2009, 2, 1, 12, 59, 0) previous = Time.utc(2009, 2, 1, 12, 0, 0) Time.stubs(:now).returns(current) expect(@schedule).to_not be_match(previous) end end describe Puppet::Type.type(:schedule), "when matching daily by number" do before do @schedule[:period] = :daily @schedule[:periodmatch] = :number end it "should match if the times are one minute apart and the current minute and hour are 0" do current = Time.utc(2010, "nov", 7, 0, 0, 0) # Now set the previous time to one minute before that previous = current - 60 Time.stubs(:now).returns(current) expect(@schedule).to be_match(previous) end it "should not match if the times are 23 hours and 58 minutes apart and the current hour is 23 and the current minute is 59" do # Reset the previous time to 00:00:00 previous = Time.utc(2010, "nov", 7, 0, 0, 0) # Set the current time to 23:59 now = previous + (23 * 3600) + (59 * 60) Time.stubs(:now).returns(now) expect(@schedule).to_not be_match(previous) end end describe Puppet::Type.type(:schedule), "when matching weekly by number" do before do @schedule[:period] = :weekly @schedule[:periodmatch] = :number end it "should match if the previous time is prior to the most recent Sunday" do now = Time.utc(2010, "nov", 11, 0, 0, 0) # Thursday Time.stubs(:now).returns(now) previous = Time.utc(2010, "nov", 6, 23, 59, 59) # Sat expect(@schedule).to be_match(previous) end it "should not match if the previous time is after the most recent Saturday" do now = Time.utc(2010, "nov", 11, 0, 0, 0) # Thursday Time.stubs(:now).returns(now) previous = Time.utc(2010, "nov", 7, 0, 0, 0) # Sunday expect(@schedule).to_not be_match(previous) end end describe Puppet::Type.type(:schedule), "when matching monthly by number" do before do @schedule[:period] = :monthly @schedule[:periodmatch] = :number end it "should match when the previous time is prior to the first day of this month" do now = Time.utc(2010, "nov", 8, 00, 59, 59) Time.stubs(:now).returns(now) previous = Time.utc(2010, "oct", 31, 23, 59, 59) expect(@schedule).to be_match(previous) end it "should not match when the previous time is after the last day of last month" do now = Time.utc(2010, "nov", 8, 00, 59, 59) Time.stubs(:now).returns(now) previous = Time.utc(2010, "nov", 1, 0, 0, 0) expect(@schedule).to_not be_match(previous) end end describe Puppet::Type.type(:schedule), "when matching with a repeat greater than one" do before do @schedule[:period] = :daily @schedule[:repeat] = 2 Time.stubs(:now).returns(Time.local(2011, "may", 23, 11, 0, 0)) end it "should fail if the periodmatch is 'number'" do @schedule[:periodmatch] = :number expect(proc { @schedule[:repeat] = 2 }).to raise_error(Puppet::Error) end it "should match if the previous run was further away than the distance divided by the repeat" do previous = Time.now - (3600 * 13) expect(@schedule).to be_match(previous) end it "should not match if the previous run was closer than the distance divided by the repeat" do previous = Time.now - (3600 * 11) expect(@schedule).to_not be_match(previous) end end describe Puppet::Type.type(:schedule), "when matching days of the week" do before do # 2011-05-23 is a Monday Time.stubs(:now).returns(Time.local(2011, "may", 23, 11, 0, 0)) end it "should raise an error if the weekday is 'Someday'" do expect { @schedule[:weekday] = "Someday" }.to raise_error(Puppet::Error) end it "should raise an error if the weekday is '7'" do expect { @schedule[:weekday] = "7" }.to raise_error(Puppet::Error) end it "should accept all full weekday names as valid values" do expect { @schedule[:weekday] = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'] }.not_to raise_error end it "should accept all short weekday names as valid values" do expect { @schedule[:weekday] = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] }.not_to raise_error end it "should accept all integers 0-6 as valid values" do expect {@schedule[:weekday] = [0, 1, 2, 3, 4, 5, 6] }.not_to raise_error end it "should match if the weekday is 'Monday'" do @schedule[:weekday] = "Monday" expect(@schedule.match?).to be_truthy end it "should match if the weekday is 'Mon'" do @schedule[:weekday] = "Mon" expect(@schedule.match?).to be_truthy end it "should match if the weekday is '1'" do @schedule[:weekday] = "1" expect(@schedule.match?).to be_truthy end it "should match if weekday is 1" do @schedule[:weekday] = 1 expect(@schedule).to be_match end it "should not match if the weekday is Tuesday" do @schedule[:weekday] = "Tuesday" expect(@schedule).not_to be_match end it "should match if weekday is ['Sun', 'Mon']" do @schedule[:weekday] = ["Sun", "Mon"] expect(@schedule.match?).to be_truthy end it "should not match if weekday is ['Sun', 'Tue']" do @schedule[:weekday] = ["Sun", "Tue"] expect(@schedule).not_to be_match end it "should match if the weekday is 'Monday'" do @schedule[:weekday] = "Monday" expect(@schedule.match?).to be_truthy end it "should match if the weekday is 'Mon'" do @schedule[:weekday] = "Mon" expect(@schedule.match?).to be_truthy end it "should match if the weekday is '1'" do @schedule[:weekday] = "1" expect(@schedule.match?).to be_truthy end it "should not match if the weekday is Tuesday" do @schedule[:weekday] = "Tuesday" expect(@schedule).not_to be_match end it "should match if weekday is ['Sun', 'Mon']" do @schedule[:weekday] = ["Sun", "Mon"] expect(@schedule.match?).to be_truthy end end it "should raise an error if the weekday is an int higher than 6" do expect { @schedule[:weekday] = 7 }.to raise_error(Puppet::ResourceError, /7 is not a valid day of the week/) end describe Puppet::Type.type(:schedule), "when matching days of week and ranges spanning days, day 1" do before do # Test with ranges and days-of-week both set. 2011-03-31 was a Thursday. Time.stubs(:now).returns(Time.local(2011, "mar", 31, 22, 30, 0)) end it "should match when the range and day of week matches" do @schedule[:range] = "22:00:00 - 02:00:00" @schedule[:weekday] = "Thursday" expect(@schedule).to be_match end it "should not match when the range doesn't match even if the day-of-week matches" do @schedule[:range] = "23:30:00 - 21:00:00" @schedule[:weekday] = "Thursday" expect(@schedule).to_not be_match end it "should not match when day-of-week doesn't match even if the range matches (1 day later)" do @schedule[:range] = "22:00:00 - 01:00:00" @schedule[:weekday] = "Friday" expect(@schedule).to_not be_match end it "should not match when day-of-week doesn't match even if the range matches (1 day earlier)" do @schedule[:range] = "22:00:00 - 01:00:00" @schedule[:weekday] = "Wednesday" expect(@schedule).to_not be_match end end describe Puppet::Type.type(:schedule), "when matching days of week and ranges spanning days, day 2" do before do # 2011-03-31 was a Thursday. As the end-time of a day spanning match, that means # we need to match on Wednesday. Time.stubs(:now).returns(Time.local(2011, "mar", 31, 1, 30, 0)) end it "should match when the range matches and the day of week should match" do @schedule[:range] = "22:00:00 - 02:00:00" @schedule[:weekday] = "Wednesday" expect(@schedule).to be_match end it "should not match when the range does not match and the day of week should match" do @schedule[:range] = "22:00:00 - 01:00:00" @schedule[:weekday] = "Thursday" expect(@schedule).to_not be_match end it "should not match when the range matches but the day-of-week does not (1 day later)" do @schedule[:range] = "22:00:00 - 02:00:00" @schedule[:weekday] = "Thursday" expect(@schedule).to_not be_match end it "should not match when the range matches but the day-of-week does not (1 day later)" do @schedule[:range] = "22:00:00 - 02:00:00" @schedule[:weekday] = "Tuesday" expect(@schedule).to_not be_match end end end puppet-5.5.10/spec/unit/type/scheduled_task_spec.rb0000644005276200011600000000760513417161722022236 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:scheduled_task), :if => Puppet.features.microsoft_windows? do it 'should use name as the namevar' do expect(described_class.new( :title => 'Foo', :command => 'C:\Windows\System32\notepad.exe' ).name).to eq('Foo') end describe 'when setting the command' do it 'should accept an absolute path to the command' do expect(described_class.new(:name => 'Test Task', :command => 'C:\Windows\System32\notepad.exe')[:command]).to eq('C:\Windows\System32\notepad.exe') end it 'should convert forward slashes to backslashes' do expect(described_class.new( :name => 'Test Task', :command => 'C:/Windows/System32/notepad.exe' )[:command]).to eq('C:\Windows\System32\notepad.exe') end it 'should normalize backslashes' do expect(described_class.new( :name => 'Test Task', :command => 'C:\Windows\\System32\\\\notepad.exe' )[:command]).to eq('C:\Windows\System32\notepad.exe') end it 'should fail if the path to the command is not absolute' do expect { described_class.new(:name => 'Test Task', :command => 'notepad.exe') }.to raise_error( Puppet::Error, /Parameter command failed on Scheduled_task\[Test Task\]: Must be specified using an absolute path\./ ) end end describe 'when setting the command arguments' do it 'should accept a string' do expect(described_class.new( :name => 'Test Task', :command => 'C:\Windows\System32\notepad.exe', :arguments => '/a /b /c' )[:arguments]).to eq('/a /b /c') end it 'should allow not specifying any command arguments' do expect(described_class.new( :name => 'Test Task', :command => 'C:\Windows\System32\notepad.exe' )[:arguments]).not_to be end end describe 'when setting whether the task is enabled or not' do it 'should return true when enabled is set to true' do expect(described_class.new( :title => 'Foo', :command => 'C:\Windows\System32\notepad.exe', :enabled => 'true', )[:enabled]).to eq(:true) end it 'should return false when enabled is set to false' do expect(described_class.new( :title => 'Foo', :command => 'C:\Windows\System32\notepad.exe', :enabled => 'false', )[:enabled]).to eq(:false) end end describe 'when setting the working directory' do it 'should accept an absolute path to the working directory' do expect(described_class.new( :name => 'Test Task', :command => 'C:\Windows\System32\notepad.exe', :working_dir => 'C:\Windows\System32' )[:working_dir]).to eq('C:\Windows\System32') end it 'should fail if the path to the working directory is not absolute' do expect { described_class.new( :name => 'Test Task', :command => 'C:\Windows\System32\notepad.exe', :working_dir => 'Windows\System32' ) }.to raise_error( Puppet::Error, /Parameter working_dir failed on Scheduled_task\[Test Task\]: Must be specified using an absolute path/ ) end it 'should allow not specifying any working directory' do expect(described_class.new( :name => 'Test Task', :command => 'C:\Windows\System32\notepad.exe' )[:working_dir]).not_to be end end describe 'when setting the trigger' do it 'should delegate to the provider to validate the trigger' do described_class.defaultprovider.any_instance.expects(:validate_trigger).returns(true) described_class.new( :name => 'Test Task', :command => 'C:\Windows\System32\notepad.exe', :trigger => {'schedule' => 'once', 'start_date' => '2011-09-16', 'start_time' => '13:20'} ) end end end puppet-5.5.10/spec/unit/type/selboolean_spec.rb0000644005276200011600000000270313417161722021371 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:selboolean), "when validating attributes" do [:name, :persistent].each do |param| it "should have a #{param} parameter" do expect(Puppet::Type.type(:selboolean).attrtype(param)).to eq(:param) end end it "should have a value property" do expect(Puppet::Type.type(:selboolean).attrtype(:value)).to eq(:property) end end describe Puppet::Type.type(:selboolean), "when validating values" do before do @class = Puppet::Type.type(:selboolean) @provider_class = stub 'provider_class', :name => "fake", :suitable? => true, :supports_parameter? => true @class.stubs(:defaultprovider).returns(@provider_class) @class.stubs(:provider).returns(@provider_class) @provider = stub 'provider', :class => @provider_class, :clear => nil @provider_class.stubs(:new).returns(@provider) end it "should support :on as a value to :value" do Puppet::Type.type(:selboolean).new(:name => "yay", :value => :on) end it "should support :off as a value to :value" do Puppet::Type.type(:selboolean).new(:name => "yay", :value => :off) end it "should support :true as a value to :persistent" do Puppet::Type.type(:selboolean).new(:name => "yay", :value => :on, :persistent => :true) end it "should support :false as a value to :persistent" do Puppet::Type.type(:selboolean).new(:name => "yay", :value => :on, :persistent => :false) end end puppet-5.5.10/spec/unit/type/selmodule_spec.rb0000644005276200011600000000075113417161722021240 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:selmodule), "when validating attributes" do [:name, :selmoduledir, :selmodulepath].each do |param| it "should have a #{param} parameter" do expect(Puppet::Type.type(:selmodule).attrtype(param)).to eq(:param) end end [:ensure, :syncversion].each do |param| it "should have a #{param} property" do expect(Puppet::Type.type(:selmodule).attrtype(param)).to eq(:property) end end end puppet-5.5.10/spec/unit/type/service_spec.rb0000644005276200011600000002562513417161722020716 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:service) do it "should have an :enableable feature that requires the :enable, :disable, and :enabled? methods" do expect(Puppet::Type.type(:service).provider_feature(:enableable).methods).to eq([:disable, :enable, :enabled?]) end it "should have a :refreshable feature that requires the :restart method" do expect(Puppet::Type.type(:service).provider_feature(:refreshable).methods).to eq([:restart]) end end describe Puppet::Type.type(:service), "when validating attributes" do [:name, :binary, :hasstatus, :path, :pattern, :start, :restart, :stop, :status, :hasrestart, :control].each do |param| it "should have a #{param} parameter" do expect(Puppet::Type.type(:service).attrtype(param)).to eq(:param) end end [:ensure, :enable].each do |param| it "should have an #{param} property" do expect(Puppet::Type.type(:service).attrtype(param)).to eq(:property) end end end describe Puppet::Type.type(:service), "when validating attribute values" do before do @provider = stub 'provider', :class => Puppet::Type.type(:service).defaultprovider, :clear => nil, :controllable? => false Puppet::Type.type(:service).defaultprovider.stubs(:new).returns(@provider) end it "should support :running as a value to :ensure" do Puppet::Type.type(:service).new(:name => "yay", :ensure => :running) end it "should support :stopped as a value to :ensure" do Puppet::Type.type(:service).new(:name => "yay", :ensure => :stopped) end it "should alias the value :true to :running in :ensure" do svc = Puppet::Type.type(:service).new(:name => "yay", :ensure => true) expect(svc.should(:ensure)).to eq(:running) end it "should alias the value :false to :stopped in :ensure" do svc = Puppet::Type.type(:service).new(:name => "yay", :ensure => false) expect(svc.should(:ensure)).to eq(:stopped) end describe "the enable property" do before :each do @provider.class.stubs(:supports_parameter?).returns true end it "should support :true as a value" do srv = Puppet::Type.type(:service).new(:name => "yay", :enable => :true) expect(srv.should(:enable)).to eq(:true) end it "should support :false as a value" do srv = Puppet::Type.type(:service).new(:name => "yay", :enable => :false) expect(srv.should(:enable)).to eq(:false) end it "should support :mask as a value" do srv = Puppet::Type.type(:service).new(:name => "yay", :enable => :mask) expect(srv.should(:enable)).to eq(:mask) end it "should support :manual as a value on Windows" do Puppet.features.stubs(:microsoft_windows?).returns true srv = Puppet::Type.type(:service).new(:name => "yay", :enable => :manual) expect(srv.should(:enable)).to eq(:manual) end it "should not support :manual as a value when not on Windows" do Puppet.features.stubs(:microsoft_windows?).returns false expect { Puppet::Type.type(:service).new(:name => "yay", :enable => :manual) }.to raise_error( Puppet::Error, /Setting enable to manual is only supported on Microsoft Windows\./ ) end end it "should support :true as a value to :hasstatus" do srv = Puppet::Type.type(:service).new(:name => "yay", :hasstatus => :true) expect(srv[:hasstatus]).to eq(:true) end it "should support :false as a value to :hasstatus" do srv = Puppet::Type.type(:service).new(:name => "yay", :hasstatus => :false) expect(srv[:hasstatus]).to eq(:false) end it "should specify :true as the default value of hasstatus" do srv = Puppet::Type.type(:service).new(:name => "yay") expect(srv[:hasstatus]).to eq(:true) end it "should support :true as a value to :hasrestart" do srv = Puppet::Type.type(:service).new(:name => "yay", :hasrestart => :true) expect(srv[:hasrestart]).to eq(:true) end it "should support :false as a value to :hasrestart" do srv = Puppet::Type.type(:service).new(:name => "yay", :hasrestart => :false) expect(srv[:hasrestart]).to eq(:false) end it "should allow setting the :enable parameter if the provider has the :enableable feature" do Puppet::Type.type(:service).defaultprovider.stubs(:supports_parameter?).returns(true) Puppet::Type.type(:service).defaultprovider.expects(:supports_parameter?).with(Puppet::Type.type(:service).attrclass(:enable)).returns(true) svc = Puppet::Type.type(:service).new(:name => "yay", :enable => true) expect(svc.should(:enable)).to eq(:true) end it "should not allow setting the :enable parameter if the provider is missing the :enableable feature" do Puppet::Type.type(:service).defaultprovider.stubs(:supports_parameter?).returns(true) Puppet::Type.type(:service).defaultprovider.expects(:supports_parameter?).with(Puppet::Type.type(:service).attrclass(:enable)).returns(false) svc = Puppet::Type.type(:service).new(:name => "yay", :enable => true) expect(svc.should(:enable)).to be_nil end it "should split paths on '#{File::PATH_SEPARATOR}'" do Puppet::FileSystem.stubs(:exist?).returns(true) FileTest.stubs(:directory?).returns(true) svc = Puppet::Type.type(:service).new(:name => "yay", :path => "/one/two#{File::PATH_SEPARATOR}/three/four") expect(svc[:path]).to eq(%w{/one/two /three/four}) end it "should accept arrays of paths joined by '#{File::PATH_SEPARATOR}'" do Puppet::FileSystem.stubs(:exist?).returns(true) FileTest.stubs(:directory?).returns(true) svc = Puppet::Type.type(:service).new(:name => "yay", :path => ["/one#{File::PATH_SEPARATOR}/two", "/three#{File::PATH_SEPARATOR}/four"]) expect(svc[:path]).to eq(%w{/one /two /three /four}) end end describe Puppet::Type.type(:service), "when setting default attribute values" do it "should default to the provider's default path if one is available" do FileTest.stubs(:directory?).returns(true) Puppet::FileSystem.stubs(:exist?).returns(true) Puppet::Type.type(:service).defaultprovider.stubs(:respond_to?).returns(true) Puppet::Type.type(:service).defaultprovider.stubs(:defpath).returns("testing") svc = Puppet::Type.type(:service).new(:name => "other") expect(svc[:path]).to eq(["testing"]) end it "should default 'pattern' to the binary if one is provided" do svc = Puppet::Type.type(:service).new(:name => "other", :binary => "/some/binary") expect(svc[:pattern]).to eq("/some/binary") end it "should default 'pattern' to the name if no pattern is provided" do svc = Puppet::Type.type(:service).new(:name => "other") expect(svc[:pattern]).to eq("other") end it "should default 'control' to the upcased service name with periods replaced by underscores if the provider supports the 'controllable' feature" do provider = stub 'provider', :controllable? => true, :class => Puppet::Type.type(:service).defaultprovider, :clear => nil Puppet::Type.type(:service).defaultprovider.stubs(:new).returns(provider) svc = Puppet::Type.type(:service).new(:name => "nfs.client") expect(svc[:control]).to eq("NFS_CLIENT_START") end end describe Puppet::Type.type(:service), "when retrieving the host's current state" do before do @service = Puppet::Type.type(:service).new(:name => "yay") end it "should use the provider's status to determine whether the service is running" do @service.provider.expects(:status).returns(:yepper) @service[:ensure] = :running expect(@service.property(:ensure).retrieve).to eq(:yepper) end it "should ask the provider whether it is enabled" do @service.provider.class.stubs(:supports_parameter?).returns(true) @service.provider.expects(:enabled?).returns(:yepper) @service[:enable] = true expect(@service.property(:enable).retrieve).to eq(:yepper) end end describe Puppet::Type.type(:service), "when changing the host" do before do @service = Puppet::Type.type(:service).new(:name => "yay") end it "should start the service if it is supposed to be running" do @service[:ensure] = :running @service.provider.expects(:start) @service.property(:ensure).sync end it "should stop the service if it is supposed to be stopped" do @service[:ensure] = :stopped @service.provider.expects(:stop) @service.property(:ensure).sync end it "should enable the service if it is supposed to be enabled" do @service.provider.class.stubs(:supports_parameter?).returns(true) @service[:enable] = true @service.provider.expects(:enable) @service.property(:enable).sync end it "should disable the service if it is supposed to be disabled" do @service.provider.class.stubs(:supports_parameter?).returns(true) @service[:enable] = false @service.provider.expects(:disable) @service.property(:enable).sync end it "should always consider the enable state of a static service to be in sync" do @service.provider.class.stubs(:supports_parameter?).returns(true) @service.provider.expects(:cached_enabled?).returns('static') @service[:enable] = false Puppet.expects(:debug).with("Unable to enable or disable static service yay") expect(@service.property(:enable).insync?(:true)).to eq(true) end it "should determine insyncness normally when the service is not static" do @service.provider.class.stubs(:supports_parameter?).returns(true) @service.provider.expects(:cached_enabled?).returns('true') @service[:enable] = true Puppet.expects(:debug).never expect(@service.property(:enable).insync?(:true)).to eq(true) end it "should sync the service's enable state when changing the state of :ensure if :enable is being managed" do @service.provider.class.stubs(:supports_parameter?).returns(true) @service[:enable] = false @service[:ensure] = :stopped @service.property(:enable).expects(:retrieve).returns("whatever") @service.property(:enable).expects(:insync?).returns(false) @service.property(:enable).expects(:sync) @service.provider.stubs(:stop) @service.property(:ensure).sync end end describe Puppet::Type.type(:service), "when refreshing the service" do before do @service = Puppet::Type.type(:service).new(:name => "yay") end it "should restart the service if it is running" do @service[:ensure] = :running @service.provider.expects(:status).returns(:running) @service.provider.expects(:restart) @service.refresh end it "should restart the service if it is running, even if it is supposed to stopped" do @service[:ensure] = :stopped @service.provider.expects(:status).returns(:running) @service.provider.expects(:restart) @service.refresh end it "should not restart the service if it is not running" do @service[:ensure] = :running @service.provider.expects(:status).returns(:stopped) @service.refresh end it "should add :ensure as a property if it is not being managed" do @service.provider.expects(:status).returns(:running) @service.provider.expects(:restart) @service.refresh end end puppet-5.5.10/spec/unit/type/ssh_authorized_key_spec.rb0000644005276200011600000002412713417161722023155 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:ssh_authorized_key), :unless => Puppet.features.microsoft_windows? do include PuppetSpec::Files before do provider_class = stub 'provider_class', :name => "fake", :suitable? => true, :supports_parameter? => true described_class.stubs(:defaultprovider).returns(provider_class) described_class.stubs(:provider).returns(provider_class) provider = stub 'provider', :class => provider_class, :file_path => make_absolute("/tmp/whatever"), :clear => nil provider_class.stubs(:new).returns(provider) end it "has :name as its namevar" do expect(described_class.key_attributes).to eq [:name] end describe "when validating attributes" do [:name, :provider].each do |param| it "has a #{param} parameter" do expect(described_class.attrtype(param)).to eq :param end end [:type, :key, :user, :target, :options, :ensure].each do |property| it "has a #{property} property" do expect(described_class.attrtype(property)).to eq :property end end end describe "when validating values" do describe "for name" do it "supports valid names" do described_class.new(:name => "username", :ensure => :present, :user => "nobody") described_class.new(:name => "username@hostname", :ensure => :present, :user => "nobody") end it "supports whitespace" do described_class.new(:name => "my test", :ensure => :present, :user => "nobody") end end describe "for ensure" do it "supports :present" do described_class.new(:name => "whev", :ensure => :present, :user => "nobody") end it "supports :absent" do described_class.new(:name => "whev", :ensure => :absent, :user => "nobody") end it "nots support other values" do expect { described_class.new(:name => "whev", :ensure => :foo, :user => "nobody") }.to raise_error(Puppet::Error, /Invalid value/) end end describe "for type" do [ :'ssh-dss', :dsa, :'ssh-rsa', :rsa, :'ecdsa-sha2-nistp256', :'ecdsa-sha2-nistp384', :'ecdsa-sha2-nistp521', :ed25519, :'ssh-ed25519', ].each do |keytype| it "supports #{keytype}" do described_class.new(:name => "whev", :type => keytype, :user => "nobody") end end it "aliases :rsa to :ssh-rsa" do key = described_class.new(:name => "whev", :type => :rsa, :user => "nobody") expect(key.should(:type)).to eq :'ssh-rsa' end it "aliases :dsa to :ssh-dss" do key = described_class.new(:name => "whev", :type => :dsa, :user => "nobody") expect(key.should(:type)).to eq :'ssh-dss' end it "doesn't support values other than ssh-dss, ssh-rsa, dsa, rsa" do expect { described_class.new(:name => "whev", :type => :something) }.to raise_error(Puppet::Error,/Invalid value/) end end describe "for key" do it "supports a valid key like a 1024 bit rsa key" do expect { described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :key => 'AAAAB3NzaC1yc2EAAAADAQABAAAAgQDCPfzW2ry7XvMc6E5Kj2e5fF/YofhKEvsNMUogR3PGL/HCIcBlsEjKisrY0aYgD8Ikp7ZidpXLbz5dBsmPy8hJiBWs5px9ZQrB/EOQAwXljvj69EyhEoGawmxQMtYw+OAIKHLJYRuk1QiHAMHLp5piqem8ZCV2mLb9AsJ6f7zUVw==')}.to_not raise_error end it "supports a valid key like a 4096 bit rsa key" do expect { described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :key => 'AAAAB3NzaC1yc2EAAAADAQABAAACAQDEY4pZFyzSfRc9wVWI3DfkgT/EL033UZm/7x1M+d+lBD00qcpkZ6CPT7lD3Z+vylQlJ5S8Wcw6C5Smt6okZWY2WXA9RCjNJMIHQbJAzwuQwgnwU/1VMy9YPp0tNVslg0sUUgpXb13WW4mYhwxyGmIVLJnUrjrQmIFhtfHsJAH8ZVqCWaxKgzUoC/YIu1u1ScH93lEdoBPLlwm6J0aiM7KWXRb7Oq1nEDZtug1zpX5lhgkQWrs0BwceqpUbY+n9sqeHU5e7DCyX/yEIzoPRW2fe2Gx1Iq6JKM/5NNlFfaW8rGxh3Z3S1NpzPHTRjw8js3IeGiV+OPFoaTtM1LsWgPDSBlzIdyTbSQR7gKh0qWYCNV/7qILEfa0yIFB5wIo4667iSPZw2pNgESVtenm8uXyoJdk8iWQ4mecdoposV/znknNb2GPgH+n/2vme4btZ0Sl1A6rev22GQjVgbWOn8zaDglJ2vgCN1UAwmq41RXprPxENGeLnWQppTnibhsngu0VFllZR5kvSIMlekLRSOFLFt92vfd+tk9hZIiKm9exxcbVCGGQPsf6dZ27rTOmg0xM2Sm4J6RRKuz79HQgA4Eg18+bqRP7j/itb89DmtXEtoZFAsEJw8IgIfeGGDtHTkfAlAC92mtK8byeaxGq57XCTKbO/r5gcOMElZHy1AcB8kw==')}.to_not raise_error end it "supports a valid key like a 1024 bit dsa key" do expect { described_class.new(:name => "whev", :type => :dsa, :user => "nobody", :key => 'AAAAB3NzaC1kc3MAAACBAI80iR78QCgpO4WabVqHHdEDigOjUEHwIjYHIubR/7u7DYrXY+e+TUmZ0CVGkiwB/0yLHK5dix3Y/bpj8ZiWCIhFeunnXccOdE4rq5sT2V3l1p6WP33RpyVYbLmeuHHl5VQ1CecMlca24nHhKpfh6TO/FIwkMjghHBfJIhXK+0w/AAAAFQDYzLupuMY5uz+GVrcP+Kgd8YqMmwAAAIB3SVN71whLWjFPNTqGyyIlMy50624UfNOaH4REwO+Of3wm/cE6eP8n75vzTwQGBpJX3BPaBGW1S1Zp/DpTOxhCSAwZzAwyf4WgW7YyAOdxN3EwTDJZeyiyjWMAOjW9/AOWt9gtKg0kqaylbMHD4kfiIhBzo31ZY81twUzAfN7angAAAIBfva8sTSDUGKsWWIXkdbVdvM4X14K4gFdy0ZJVzaVOtZ6alysW6UQypnsl6jfnbKvsZ0tFgvcX/CPyqNY/gMR9lyh/TCZ4XQcbqeqYPuceGehz+jL5vArfqsW2fJYFzgCcklmr/VxtP5h6J/T0c9YcDgc/xIfWdZAlznOnphI/FA==')}.to_not raise_error end it "doesn't support whitespaces" do expect { described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :key => 'AAA FA==')}.to raise_error(Puppet::Error,/Key must not contain whitespace/) end end describe "for options" do it "supports flags as options" do expect { described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => 'cert-authority')}.to_not raise_error expect { described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => 'no-port-forwarding')}.to_not raise_error end it "supports key-value pairs as options" do expect { described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => 'command="command"')}.to_not raise_error end it "supports key-value pairs where value consist of multiple items" do expect { described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => 'from="*.domain1,host1.domain2"')}.to_not raise_error end it "supports environments as options" do expect { described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => 'environment="NAME=value"')}.to_not raise_error end it "supports multiple options as an array" do expect { described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => ['cert-authority','environment="NAME=value"'])}.to_not raise_error end it "doesn't support a comma separated list" do expect { described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => 'cert-authority,no-port-forwarding')}.to raise_error(Puppet::Error, /must be provided as an array/) end it "uses :absent as a default value" do expect(described_class.new(:name => "whev", :type => :rsa, :user => "nobody").should(:options)).to eq [:absent] end it "property should return well formed string of arrays from is_to_s" do resource = described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => ["a","b","c"]) expect(resource.property(:options).is_to_s(["a","b","c"])).to eq "['a', 'b', 'c']" end it "property should return well formed string of arrays from should_to_s" do resource = described_class.new(:name => "whev", :type => :rsa, :user => "nobody", :options => ["a","b","c"]) expect(resource.property(:options).should_to_s(["a","b","c"])).to eq "['a', 'b', 'c']" end end describe "for user" do it "supports present users" do described_class.new(:name => "whev", :type => :rsa, :user => "root") end it "supports absent users" do described_class.new(:name => "whev", :type => :rsa, :user => "ihopeimabsent") end end describe "for target" do it "supports absolute paths" do described_class.new(:name => "whev", :type => :rsa, :target => "/tmp/here") end it "uses the user's path if not explicitly specified" do expect(described_class.new(:name => "whev", :user => 'root').should(:target)).to eq File.expand_path("~root/.ssh/authorized_keys") end it "doesn't consider the user's path if explicitly specified" do expect(described_class.new(:name => "whev", :user => 'root', :target => '/tmp/here').should(:target)).to eq '/tmp/here' end it "informs about an absent user" do Puppet::Log.level = :debug described_class.new(:name => "whev", :user => 'idontexist').should(:target) expect(@logs.map(&:message)).to include("The required user is not yet present on the system") end end end describe "when neither user nor target is specified" do it "raises an error" do expect do described_class.new( :name => "Test", :key => "AAA", :type => "ssh-rsa", :ensure => :present) end.to raise_error(Puppet::Error,/user.*or.*target.*mandatory/) end end describe "when both target and user are specified" do it "uses target" do resource = described_class.new( :name => "Test", :user => "root", :target => "/tmp/blah" ) expect(resource.should(:target)).to eq "/tmp/blah" end end describe "when user is specified" do it "determines target" do resource = described_class.new( :name => "Test", :user => "root" ) target = File.expand_path("~root/.ssh/authorized_keys") expect(resource.should(:target)).to eq target end # Bug #2124 - ssh_authorized_key always changes target if target is not defined it "doesn't raise spurious change events" do resource = described_class.new(:name => "Test", :user => "root") target = File.expand_path("~root/.ssh/authorized_keys") expect(resource.property(:target).safe_insync?(target)).to eq true end end describe "when calling validate" do it "doesn't crash on a non-existent user" do resource = described_class.new( :name => "Test", :user => "ihopesuchuserdoesnotexist" ) resource.validate end end end puppet-5.5.10/spec/unit/type/sshkey_spec.rb0000644005276200011600000000425113417161722020554 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:sshkey) do it "uses :name as its namevar" do expect(described_class.key_attributes).to eq [:name] end describe "when validating attributes" do [:name, :provider].each do |param| it "has a #{param} parameter" do expect(described_class.attrtype(param)).to eq :param end end [:host_aliases, :ensure, :key, :type].each do |property| it "has a #{property} property" do expect(described_class.attrtype(property)).to eq :property end end end describe "when validating values" do [ :'ssh-dss', :dsa, :'ssh-rsa', :rsa, :'ecdsa-sha2-nistp256', :'ecdsa-sha2-nistp384', :'ecdsa-sha2-nistp521', :'ssh-ed25519', :ed25519, ].each do |keytype| it "supports #{keytype} as a type value" do described_class.new(:name => "foo", :type => keytype) end end it "aliases :rsa to :ssh-rsa" do key = described_class.new(:name => "foo", :type => :rsa) expect(key.should(:type)).to eq :'ssh-rsa' end it "aliases :dsa to :ssh-dss" do key = described_class.new(:name => "foo", :type => :dsa) expect(key.should(:type)).to eq :'ssh-dss' end it "doesn't support values other than ssh-dss, ssh-rsa, dsa, rsa for type" do expect { described_class.new(:name => "whev", :type => :'ssh-dsa') }.to raise_error(Puppet::Error, /Invalid value.*ssh-dsa/) end it "accepts one host_alias" do described_class.new(:name => "foo", :host_aliases => 'foo.bar.tld') end it "accepts multiple host_aliases as an array" do described_class.new(:name => "foo", :host_aliases => ['foo.bar.tld','10.0.9.9']) end it "doesn't accept spaces in any host_alias" do expect { described_class.new(:name => "foo", :host_aliases => ['foo.bar.tld','foo bar']) }.to raise_error(Puppet::Error, /cannot include whitespace/) end it "doesn't accept aliases in the resourcename" do expect { described_class.new(:name => 'host,host.domain,ip') }.to raise_error(Puppet::Error, /No comma in resourcename/) end end end puppet-5.5.10/spec/unit/type/tidy_spec.rb0000644005276200011600000003655713417161722020235 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/file_bucket/dipper' tidy = Puppet::Type.type(:tidy) describe tidy do include PuppetSpec::Files before do @basepath = make_absolute("/what/ever") Puppet.settings.stubs(:use) end context "when normalizing 'path' on windows", :if => Puppet.features.microsoft_windows? do it "replaces backslashes with forward slashes" do resource = tidy.new(:path => 'c:\directory') expect(resource[:path]).to eq('c:/directory') end end it "should use :lstat when stating a file" do path = '/foo/bar' stat = mock 'stat' Puppet::FileSystem.expects(:lstat).with(path).returns stat resource = tidy.new :path => path, :age => "1d" expect(resource.stat(path)).to eq(stat) end [:age, :size, :path, :matches, :type, :recurse, :rmdirs].each do |param| it "should have a #{param} parameter" do expect(Puppet::Type.type(:tidy).attrclass(param).ancestors).to be_include(Puppet::Parameter) end it "should have documentation for its #{param} param" do expect(Puppet::Type.type(:tidy).attrclass(param).doc).to be_instance_of(String) end end describe "when validating parameter values" do describe "for 'recurse'" do before do @tidy = Puppet::Type.type(:tidy).new :path => "/tmp", :age => "100d" end it "should allow 'true'" do expect { @tidy[:recurse] = true }.not_to raise_error end it "should allow 'false'" do expect { @tidy[:recurse] = false }.not_to raise_error end it "should allow integers" do expect { @tidy[:recurse] = 10 }.not_to raise_error end it "should allow string representations of integers" do expect { @tidy[:recurse] = "10" }.not_to raise_error end it "should allow 'inf'" do expect { @tidy[:recurse] = "inf" }.not_to raise_error end it "should not allow arbitrary values" do expect { @tidy[:recurse] = "whatever" }.to raise_error(Puppet::ResourceError, /Parameter recurse failed/) end end describe "for 'matches'" do before do @tidy = Puppet::Type.type(:tidy).new :path => "/tmp", :age => "100d" end it "should object if matches is given with recurse is not specified" do expect { @tidy[:matches] = '*.doh' }.to raise_error(Puppet::ResourceError, /Parameter matches failed/) end it "should object if matches is given and recurse is 0" do expect { @tidy[:recurse] = 0; @tidy[:matches] = '*.doh' }.to raise_error(Puppet::ResourceError, /Parameter matches failed/) end it "should object if matches is given and recurse is false" do expect { @tidy[:recurse] = false; @tidy[:matches] = '*.doh' }.to raise_error(Puppet::ResourceError, /Parameter matches failed/) end it "should not object if matches is given and recurse is > 0" do expect { @tidy[:recurse] = 1; @tidy[:matches] = '*.doh' }.not_to raise_error end it "should not object if matches is given and recurse is true" do expect { @tidy[:recurse] = true; @tidy[:matches] = '*.doh' }.not_to raise_error end end end describe "when matching files by age" do convertors = { :second => 1, :minute => 60 } convertors[:hour] = convertors[:minute] * 60 convertors[:day] = convertors[:hour] * 24 convertors[:week] = convertors[:day] * 7 convertors.each do |unit, multiple| it "should consider a #{unit} to be #{multiple} seconds" do @tidy = Puppet::Type.type(:tidy).new :path => @basepath, :age => "5#{unit.to_s[0..0]}" expect(@tidy[:age]).to eq(5 * multiple) end end end describe "when matching files by size" do convertors = { :b => 0, :kb => 1, :mb => 2, :gb => 3, :tb => 4 } convertors.each do |unit, multiple| it "should consider a #{unit} to be 1024^#{multiple} bytes" do @tidy = Puppet::Type.type(:tidy).new :path => @basepath, :size => "5#{unit}" total = 5 multiple.times { total *= 1024 } expect(@tidy[:size]).to eq(total) end end end describe "when tidying" do before do @tidy = Puppet::Type.type(:tidy).new :path => @basepath @stat = stub 'stat', :ftype => "directory" lstat_is(@basepath, @stat) end describe "and generating files" do it "should set the backup on the file if backup is set on the tidy instance" do @tidy[:backup] = "whatever" Puppet::Type.type(:file).expects(:new).with { |args| args[:backup] == "whatever" } @tidy.mkfile(@basepath) end it "should set the file's path to the tidy's path" do Puppet::Type.type(:file).expects(:new).with { |args| args[:path] == @basepath } @tidy.mkfile(@basepath) end it "should configure the file for deletion" do Puppet::Type.type(:file).expects(:new).with { |args| args[:ensure] == :absent } @tidy.mkfile(@basepath) end it "should force deletion on the file" do Puppet::Type.type(:file).expects(:new).with { |args| args[:force] == true } @tidy.mkfile(@basepath) end it "should do nothing if the targeted file does not exist" do lstat_raises(@basepath, Errno::ENOENT) expect(@tidy.generate).to eq([]) end end describe "and recursion is not used" do it "should generate a file resource if the file should be tidied" do @tidy.expects(:tidy?).with(@basepath).returns true file = Puppet::Type.type(:file).new(:path => @basepath+"/eh") @tidy.expects(:mkfile).with(@basepath).returns file expect(@tidy.generate).to eq([file]) end it "should do nothing if the file should not be tidied" do @tidy.expects(:tidy?).with(@basepath).returns false @tidy.expects(:mkfile).never expect(@tidy.generate).to eq([]) end end describe "and recursion is used" do before do @tidy[:recurse] = true Puppet::FileServing::Fileset.any_instance.stubs(:stat).returns mock("stat") @fileset = Puppet::FileServing::Fileset.new(@basepath) Puppet::FileServing::Fileset.stubs(:new).returns @fileset end it "should use a Fileset for infinite recursion" do Puppet::FileServing::Fileset.expects(:new).with(@basepath, :recurse => true).returns @fileset @fileset.expects(:files).returns %w{. one two} @tidy.stubs(:tidy?).returns false @tidy.generate end it "should use a Fileset for limited recursion" do @tidy[:recurse] = 42 Puppet::FileServing::Fileset.expects(:new).with(@basepath, :recurse => true, :recurselimit => 42).returns @fileset @fileset.expects(:files).returns %w{. one two} @tidy.stubs(:tidy?).returns false @tidy.generate end it "should generate a file resource for every file that should be tidied but not for files that should not be tidied" do @fileset.expects(:files).returns %w{. one two} @tidy.expects(:tidy?).with(@basepath).returns true @tidy.expects(:tidy?).with(@basepath+"/one").returns true @tidy.expects(:tidy?).with(@basepath+"/two").returns false file = Puppet::Type.type(:file).new(:path => @basepath+"/eh") @tidy.expects(:mkfile).with(@basepath).returns file @tidy.expects(:mkfile).with(@basepath+"/one").returns file @tidy.generate end end describe "and determining whether a file matches provided glob patterns" do before do @tidy = Puppet::Type.type(:tidy).new :path => @basepath, :recurse => 1 @tidy[:matches] = %w{*foo* *bar*} @stat = mock 'stat' @matcher = @tidy.parameter(:matches) end it "should always convert the globs to an array" do @matcher.value = "*foo*" expect(@matcher.value).to eq(%w{*foo*}) end it "should return true if any pattern matches the last part of the file" do @matcher.value = %w{*foo* *bar*} expect(@matcher).to be_tidy("/file/yaybarness", @stat) end it "should return false if no pattern matches the last part of the file" do @matcher.value = %w{*foo* *bar*} expect(@matcher).not_to be_tidy("/file/yayness", @stat) end end describe "and determining whether a file is too old" do before do @tidy = Puppet::Type.type(:tidy).new :path => @basepath @stat = stub 'stat' @tidy[:age] = "1s" @tidy[:type] = "mtime" @ager = @tidy.parameter(:age) end it "should use the age type specified" do @tidy[:type] = :ctime @stat.expects(:ctime).returns(Time.now) @ager.tidy?(@basepath, @stat) end it "should return false if the file is more recent than the specified age" do @stat.expects(:mtime).returns(Time.now) expect(@ager).not_to be_tidy(@basepath, @stat) end it "should return true if the file is older than the specified age" do @stat.expects(:mtime).returns(Time.now - 10) expect(@ager).to be_tidy(@basepath, @stat) end end describe "and determining whether a file is too large" do before do @tidy = Puppet::Type.type(:tidy).new :path => @basepath @stat = stub 'stat', :ftype => "file" @tidy[:size] = "1kb" @sizer = @tidy.parameter(:size) end it "should return false if the file is smaller than the specified size" do @stat.expects(:size).returns(4) # smaller than a kilobyte expect(@sizer).not_to be_tidy(@basepath, @stat) end it "should return true if the file is larger than the specified size" do @stat.expects(:size).returns(1500) # larger than a kilobyte expect(@sizer).to be_tidy(@basepath, @stat) end it "should return true if the file is equal to the specified size" do @stat.expects(:size).returns(1024) expect(@sizer).to be_tidy(@basepath, @stat) end end describe "and determining whether a file should be tidied" do before do @tidy = Puppet::Type.type(:tidy).new :path => @basepath @catalog = Puppet::Resource::Catalog.new @tidy.catalog = @catalog @stat = stub 'stat', :ftype => "file" lstat_is(@basepath, @stat) end it "should not try to recurse if the file does not exist" do @tidy[:recurse] = true lstat_is(@basepath, nil) expect(@tidy.generate).to eq([]) end it "should not be tidied if the file does not exist" do lstat_raises(@basepath, Errno::ENOENT) expect(@tidy).not_to be_tidy(@basepath) end it "should not be tidied if the user has no access to the file" do lstat_raises(@basepath, Errno::EACCES) expect(@tidy).not_to be_tidy(@basepath) end it "should not be tidied if it is a directory and rmdirs is set to false" do stat = mock 'stat', :ftype => "directory" lstat_is(@basepath, stat) expect(@tidy).not_to be_tidy(@basepath) end it "should return false if it does not match any provided globs" do @tidy[:recurse] = 1 @tidy[:matches] = "globs" matches = @tidy.parameter(:matches) matches.expects(:tidy?).with(@basepath, @stat).returns false expect(@tidy).not_to be_tidy(@basepath) end it "should return false if it does not match aging requirements" do @tidy[:age] = "1d" ager = @tidy.parameter(:age) ager.expects(:tidy?).with(@basepath, @stat).returns false expect(@tidy).not_to be_tidy(@basepath) end it "should return false if it does not match size requirements" do @tidy[:size] = "1b" sizer = @tidy.parameter(:size) sizer.expects(:tidy?).with(@basepath, @stat).returns false expect(@tidy).not_to be_tidy(@basepath) end it "should tidy a file if age and size are set but only size matches" do @tidy[:size] = "1b" @tidy[:age] = "1d" @tidy.parameter(:size).stubs(:tidy?).returns true @tidy.parameter(:age).stubs(:tidy?).returns false expect(@tidy).to be_tidy(@basepath) end it "should tidy a file if age and size are set but only age matches" do @tidy[:size] = "1b" @tidy[:age] = "1d" @tidy.parameter(:size).stubs(:tidy?).returns false @tidy.parameter(:age).stubs(:tidy?).returns true expect(@tidy).to be_tidy(@basepath) end it "should tidy all files if neither age nor size is set" do expect(@tidy).to be_tidy(@basepath) end it "should sort the results inversely by path length, so files are added to the catalog before their directories" do @tidy[:recurse] = true @tidy[:rmdirs] = true fileset = Puppet::FileServing::Fileset.new(@basepath) Puppet::FileServing::Fileset.expects(:new).returns fileset fileset.expects(:files).returns %w{. one one/two} @tidy.stubs(:tidy?).returns true expect(@tidy.generate.collect { |r| r[:path] }).to eq([@basepath+"/one/two", @basepath+"/one", @basepath]) end end it "should configure directories to require their contained files if rmdirs is enabled, so the files will be deleted first" do @tidy[:recurse] = true @tidy[:rmdirs] = true fileset = mock 'fileset' Puppet::FileServing::Fileset.expects(:new).with(@basepath, :recurse => true).returns fileset fileset.expects(:files).returns %w{. one two one/subone two/subtwo one/subone/ssone} @tidy.stubs(:tidy?).returns true result = @tidy.generate.inject({}) { |hash, res| hash[res[:path]] = res; hash } { @basepath => [ @basepath+"/one", @basepath+"/two" ], @basepath+"/one" => [@basepath+"/one/subone"], @basepath+"/two" => [@basepath+"/two/subtwo"], @basepath+"/one/subone" => [@basepath+"/one/subone/ssone"] }.each do |parent, children| children.each do |child| ref = Puppet::Resource.new(:file, child) expect(result[parent][:require].find { |req| req.to_s == ref.to_s }).not_to be_nil end end end it "should configure directories to require their contained files in sorted order" do @tidy[:recurse] = true @tidy[:rmdirs] = true fileset = mock 'fileset' Puppet::FileServing::Fileset.expects(:new).with(@basepath, :recurse => true).returns fileset fileset.expects(:files).returns %w{. a a/2 a/1 a/3} @tidy.stubs(:tidy?).returns true result = @tidy.generate.inject({}) { |hash, res| hash[res[:path]] = res; hash } expect(result[@basepath + '/a'][:require].collect{|a| a.name[('File//a/' + @basepath).length..-1]}.join()).to eq('321') end it "generates resources whose noop parameter matches the managed resource's noop parameter" do @tidy[:recurse] = true @tidy[:noop] = true fileset = mock 'fileset' Puppet::FileServing::Fileset.expects(:new).with(@basepath, :recurse => true).returns fileset fileset.expects(:files).returns %w{. a a/2 a/1 a/3} @tidy.stubs(:tidy?).returns true result = @tidy.generate.inject({}) { |hash, res| hash[res[:path]] = res; hash } expect(result.values).to all(be_noop) end end def lstat_is(path, stat) Puppet::FileSystem.stubs(:lstat).with(path).returns(stat) end def lstat_raises(path, error_class) Puppet::FileSystem.expects(:lstat).with(path).raises Errno::ENOENT end end puppet-5.5.10/spec/unit/type/user_spec.rb0000644005276200011600000005363613417161722020237 0ustar jenkinsjenkins#! /usr/bin/env ruby # encoding: UTF-8 require 'spec_helper' describe Puppet::Type.type(:user) do before :each do @provider_class = described_class.provide(:simple) do has_features :manages_expiry, :manages_password_age, :manages_passwords, :manages_solaris_rbac, :manages_shell mk_resource_methods def create; end def delete; end def exists?; get(:ensure) != :absent; end def flush; end def self.instances; []; end end described_class.stubs(:defaultprovider).returns @provider_class end it "should be able to create an instance" do expect(described_class.new(:name => "foo")).not_to be_nil end it "should have an allows_duplicates feature" do expect(described_class.provider_feature(:allows_duplicates)).not_to be_nil end it "should have a manages_homedir feature" do expect(described_class.provider_feature(:manages_homedir)).not_to be_nil end it "should have a manages_passwords feature" do expect(described_class.provider_feature(:manages_passwords)).not_to be_nil end it "should have a manages_solaris_rbac feature" do expect(described_class.provider_feature(:manages_solaris_rbac)).not_to be_nil end it "should have a manages_expiry feature" do expect(described_class.provider_feature(:manages_expiry)).not_to be_nil end it "should have a manages_password_age feature" do expect(described_class.provider_feature(:manages_password_age)).not_to be_nil end it "should have a system_users feature" do expect(described_class.provider_feature(:system_users)).not_to be_nil end it "should have a manages_shell feature" do expect(described_class.provider_feature(:manages_shell)).not_to be_nil end context "managehome" do let (:provider) { @provider_class.new(:name => 'foo', :ensure => :absent) } let (:instance) { described_class.new(:name => 'foo', :provider => provider) } it "defaults to false" do expect(instance[:managehome]).to be_falsey end it "can be set to false" do instance[:managehome] = 'false' end it "cannot be set to true for a provider that does not manage homedirs" do provider.class.stubs(:manages_homedir?).returns false expect { instance[:managehome] = 'yes' }.to raise_error(Puppet::Error, /can not manage home directories/) end it "can be set to true for a provider that does manage homedirs" do provider.class.stubs(:manages_homedir?).returns true instance[:managehome] = 'yes' end end describe "instances" do it "should delegate existence questions to its provider" do @provider = @provider_class.new(:name => 'foo', :ensure => :absent) instance = described_class.new(:name => "foo", :provider => @provider) expect(instance.exists?).to eq(false) @provider.set(:ensure => :present) expect(instance.exists?).to eq(true) end end properties = [:ensure, :uid, :gid, :home, :comment, :shell, :password, :password_min_age, :password_max_age, :password_warn_days, :groups, :roles, :auths, :profiles, :project, :keys, :expiry] properties.each do |property| it "should have a #{property} property" do expect(described_class.attrclass(property).ancestors).to be_include(Puppet::Property) end it "should have documentation for its #{property} property" do expect(described_class.attrclass(property).doc).to be_instance_of(String) end end list_properties = [:groups, :roles, :auths] list_properties.each do |property| it "should have a list '#{property}'" do expect(described_class.attrclass(property).ancestors).to be_include(Puppet::Property::List) end end it "should have an ordered list 'profiles'" do expect(described_class.attrclass(:profiles).ancestors).to be_include(Puppet::Property::OrderedList) end it "should have key values 'keys'" do expect(described_class.attrclass(:keys).ancestors).to be_include(Puppet::Property::KeyValue) end describe "when retrieving all current values" do before do @provider = @provider_class.new(:name => 'foo', :ensure => :present, :uid => 15, :gid => 15) @user = described_class.new(:name => "foo", :uid => 10, :provider => @provider) end it "should return a hash containing values for all set properties" do @user[:gid] = 10 values = @user.retrieve [@user.property(:uid), @user.property(:gid)].each { |property| expect(values).to be_include(property) } end it "should set all values to :absent if the user is absent" do @user.property(:ensure).expects(:retrieve).returns :absent @user.property(:uid).expects(:retrieve).never expect(@user.retrieve[@user.property(:uid)]).to eq(:absent) end it "should include the result of retrieving each property's current value if the user is present" do expect(@user.retrieve[@user.property(:uid)]).to eq(15) end end describe "when managing the ensure property" do it "should support a :present value" do expect { described_class.new(:name => 'foo', :ensure => :present) }.to_not raise_error end it "should support an :absent value" do expect { described_class.new(:name => 'foo', :ensure => :absent) }.to_not raise_error end it "should call :create on the provider when asked to sync to the :present state" do @provider = @provider_class.new(:name => 'foo', :ensure => :absent) @provider.expects(:create) described_class.new(:name => 'foo', :ensure => :present, :provider => @provider).parameter(:ensure).sync end it "should call :delete on the provider when asked to sync to the :absent state" do @provider = @provider_class.new(:name => 'foo', :ensure => :present) @provider.expects(:delete) described_class.new(:name => 'foo', :ensure => :absent, :provider => @provider).parameter(:ensure).sync end describe "and determining the current state" do it "should return :present when the provider indicates the user exists" do @provider = @provider_class.new(:name => 'foo', :ensure => :present) expect(described_class.new(:name => 'foo', :ensure => :absent, :provider => @provider).parameter(:ensure).retrieve).to eq(:present) end it "should return :absent when the provider indicates the user does not exist" do @provider = @provider_class.new(:name => 'foo', :ensure => :absent) expect(described_class.new(:name => 'foo', :ensure => :present, :provider => @provider).parameter(:ensure).retrieve).to eq(:absent) end end end describe "when managing the uid property" do it "should convert number-looking strings into actual numbers" do expect(described_class.new(:name => 'foo', :uid => '50')[:uid]).to eq(50) end it "should support UIDs as numbers" do expect(described_class.new(:name => 'foo', :uid => 50)[:uid]).to eq(50) end it "should support :absent as a value" do expect(described_class.new(:name => 'foo', :uid => :absent)[:uid]).to eq(:absent) end end describe "when managing the gid" do it "should support :absent as a value" do expect(described_class.new(:name => 'foo', :gid => :absent)[:gid]).to eq(:absent) end it "should convert number-looking strings into actual numbers" do expect(described_class.new(:name => 'foo', :gid => '50')[:gid]).to eq(50) end it "should support GIDs specified as integers" do expect(described_class.new(:name => 'foo', :gid => 50)[:gid]).to eq(50) end it "should support groups specified by name" do expect(described_class.new(:name => 'foo', :gid => 'foo')[:gid]).to eq('foo') end describe "when testing whether in sync" do it "should return true if no 'should' values are set" do # this is currently not the case because gid has no default value, so we would never even # call insync? on that property if param = described_class.new(:name => 'foo').parameter(:gid) expect(param).to be_safe_insync(500) end end it "should return true if any of the specified groups are equal to the current integer" do Puppet::Util.expects(:gid).with("foo").returns 300 Puppet::Util.expects(:gid).with("bar").returns 500 expect(described_class.new(:name => 'baz', :gid => [ 'foo', 'bar' ]).parameter(:gid)).to be_safe_insync(500) end it "should return false if none of the specified groups are equal to the current integer" do Puppet::Util.expects(:gid).with("foo").returns 300 Puppet::Util.expects(:gid).with("bar").returns 500 expect(described_class.new(:name => 'baz', :gid => [ 'foo', 'bar' ]).parameter(:gid)).to_not be_safe_insync(700) end end describe "when syncing" do it "should use the first found, specified group as the desired value and send it to the provider" do Puppet::Util.expects(:gid).with("foo").returns nil Puppet::Util.expects(:gid).with("bar").returns 500 @provider = @provider_class.new(:name => 'foo') resource = described_class.new(:name => 'foo', :provider => @provider, :gid => [ 'foo', 'bar' ]) @provider.expects(:gid=).with 500 resource.parameter(:gid).sync end end end describe "when managing groups" do it "should support a singe group" do expect { described_class.new(:name => 'foo', :groups => 'bar') }.to_not raise_error end it "should support multiple groups as an array" do expect { described_class.new(:name => 'foo', :groups => [ 'bar' ]) }.to_not raise_error expect { described_class.new(:name => 'foo', :groups => [ 'bar', 'baz' ]) }.to_not raise_error end it "should not support a comma separated list" do expect { described_class.new(:name => 'foo', :groups => 'bar,baz') }.to raise_error(Puppet::Error, /Group names must be provided as an array/) end it "should not support an empty string" do expect { described_class.new(:name => 'foo', :groups => '') }.to raise_error(Puppet::Error, /Group names must not be empty/) end describe "when testing is in sync" do before :each do # the useradd provider uses a single string to represent groups and so does Puppet::Property::List when converting to should values @provider = @provider_class.new(:name => 'foo', :groups => 'a,b,e,f') end it "should not care about order" do @property = described_class.new(:name => 'foo', :groups => [ 'a', 'c', 'b' ]).property(:groups) expect(@property).to be_safe_insync([ 'a', 'b', 'c' ]) expect(@property).to be_safe_insync([ 'a', 'c', 'b' ]) expect(@property).to be_safe_insync([ 'b', 'a', 'c' ]) expect(@property).to be_safe_insync([ 'b', 'c', 'a' ]) expect(@property).to be_safe_insync([ 'c', 'a', 'b' ]) expect(@property).to be_safe_insync([ 'c', 'b', 'a' ]) end it "should merge current value and desired value if membership minimal" do @instance = described_class.new(:name => 'foo', :groups => [ 'a', 'c', 'b' ], :provider => @provider) @instance[:membership] = :minimum expect(@instance[:groups]).to eq('a,b,c,e,f') end it "should not treat a subset of groups insync if membership inclusive" do @instance = described_class.new(:name => 'foo', :groups => [ 'a', 'c', 'b' ], :provider => @provider) @instance[:membership] = :inclusive expect(@instance[:groups]).to eq('a,b,c') end end end describe "when managing expiry" do it "should fail if given an invalid date" do expect { described_class.new(:name => 'foo', :expiry => "200-20-20") }.to raise_error(Puppet::Error, /Expiry dates must be YYYY-MM-DD/) end end describe "when managing minimum password age" do it "should accept a negative minimum age" do expect { described_class.new(:name => 'foo', :password_min_age => '-1') }.to_not raise_error end it "should fail with an empty minimum age" do expect { described_class.new(:name => 'foo', :password_min_age => '') }.to raise_error(Puppet::Error, /minimum age must be provided as a number/) end end describe "when managing maximum password age" do it "should accept a negative maximum age" do expect { described_class.new(:name => 'foo', :password_max_age => '-1') }.to_not raise_error end it "should fail with an empty maximum age" do expect { described_class.new(:name => 'foo', :password_max_age => '') }.to raise_error(Puppet::Error, /maximum age must be provided as a number/) end end describe "when managing warning password days" do it "should accept a negative warning days" do expect { described_class.new(:name => 'foo', :password_warn_days => '-1') }.to_not raise_error end it "should fail with an empty warning days" do expect { described_class.new(:name => 'foo', :password_warn_days => '') }.to raise_error(Puppet::Error, /warning days must be provided as a number/) end end describe "when managing passwords" do before do @password = described_class.new(:name => 'foo', :password => 'mypass').parameter(:password) end it "should not include the password in the change log when adding the password" do expect(@password.change_to_s(:absent, "mypass")).not_to be_include("mypass") end it "should not include the password in the change log when changing the password" do expect(@password.change_to_s("other", "mypass")).not_to be_include("mypass") end it "should redact the password when displaying the old value" do expect(@password.is_to_s("currentpassword")).to match(/^\[old password hash redacted\]$/) end it "should redact the password when displaying the new value" do expect(@password.should_to_s("newpassword")).to match(/^\[new password hash redacted\]$/) end it "should fail if a ':' is included in the password" do expect { described_class.new(:name => 'foo', :password => "some:thing") }.to raise_error(Puppet::Error, /Passwords cannot include ':'/) end it "should allow the value to be set to :absent" do expect { described_class.new(:name => 'foo', :password => :absent) }.to_not raise_error end end describe "when managing comment" do before :each do @value = 'abcd™' expect(@value.encoding).to eq(Encoding::UTF_8) @user = described_class.new(:name => 'foo', :comment => @value) end describe "#insync" do it "should delegate to the provider's #comments_insync? method if defined" do # useradd subclasses nameservice and thus inherits #comments_insync? user = described_class.new(:name => 'foo', :comment => @value, :provider => :useradd) comment_property = user.properties.find {|p| p.name == :comment} user.provider.expects(:comments_insync?) comment_property.insync?('bar') end describe "#change_to_s" do let(:is) { "\u2603" } let(:should) { "\u06FF" } let(:comment_property) { @user.properties.find { |p| p.name == :comment } } context "given is and should strings with incompatible encoding" do it "should return a formatted string" do is.force_encoding(Encoding::ASCII_8BIT) should.force_encoding(Encoding::UTF_8) expect(Encoding.compatible?(is, should)).to be_falsey expect(comment_property.change_to_s(is,should)).to match(/changed '\u{E2}\u{98}\u{83}' to '\u{DB}\u{BF}'/) end end context "given is and should strings with compatible encoding" do it "should return a formatted string" do is.force_encoding(Encoding::UTF_8) should.force_encoding(Encoding::UTF_8) expect(Encoding.compatible?(is, should)).to be_truthy expect(comment_property.change_to_s(is,should)).to match(/changed '\u{2603}' to '\u{6FF}'/u) end end end end end describe "when manages_solaris_rbac is enabled" do it "should support a :role value for ensure" do expect { described_class.new(:name => 'foo', :ensure => :role) }.to_not raise_error end end describe "when user has roles" do it "should autorequire roles" do testuser = described_class.new(:name => "testuser", :roles => ['testrole'] ) testrole = described_class.new(:name => "testrole") Puppet::Resource::Catalog.new :testing do |conf| [testuser, testrole].each { |resource| conf.add_resource resource } end Puppet::Type::User::ProviderDirectoryservice.stubs(:get_macosx_version_major).returns "10.5" rel = testuser.autorequire[0] expect(rel.source.ref).to eq(testrole.ref) expect(rel.target.ref).to eq(testuser.ref) end end describe "when setting shell" do before :each do @shell_provider_class = described_class.provide(:shell_manager) do has_features :manages_shell mk_resource_methods def create; check_valid_shell;end def shell=(value); check_valid_shell; end def delete; end def exists?; get(:ensure) != :absent; end def flush; end def self.instances; []; end def check_valid_shell; end end described_class.stubs(:defaultprovider).returns @shell_provider_class end it "should call :check_valid_shell on the provider when changing shell value" do @provider = @shell_provider_class.new(:name => 'foo', :shell => '/bin/bash', :ensure => :present) @provider.expects(:check_valid_shell) resource = described_class.new(:name => 'foo', :shell => '/bin/zsh', :provider => @provider) Puppet::Util::Storage.stubs(:load) Puppet::Util::Storage.stubs(:store) catalog = Puppet::Resource::Catalog.new catalog.add_resource resource catalog.apply end it "should call :check_valid_shell on the provider when changing ensure from present to absent" do @provider = @shell_provider_class.new(:name => 'foo', :shell => '/bin/bash', :ensure => :absent) @provider.expects(:check_valid_shell) resource = described_class.new(:name => 'foo', :shell => '/bin/zsh', :provider => @provider) Puppet::Util::Storage.stubs(:load) Puppet::Util::Storage.stubs(:store) catalog = Puppet::Resource::Catalog.new catalog.add_resource resource catalog.apply end end describe "when purging ssh keys" do it "should not accept a keyfile with a relative path" do expect { described_class.new(:name => "a", :purge_ssh_keys => "keys") }.to raise_error(Puppet::Error, /Paths to keyfiles must be absolute, not keys/) end context "with a home directory specified" do it "should accept true" do described_class.new(:name => "a", :home => "/tmp", :purge_ssh_keys => true) end it "should accept the ~ wildcard" do described_class.new(:name => "a", :home => "/tmp", :purge_ssh_keys => "~/keys") end it "should accept the %h wildcard" do described_class.new(:name => "a", :home => "/tmp", :purge_ssh_keys => "%h/keys") end it "raises when given a relative path" do expect { described_class.new(:name => "a", :home => "/tmp", :purge_ssh_keys => "keys") }.to raise_error(Puppet::Error, /Paths to keyfiles must be absolute/) end end context "with no home directory specified" do it "should not accept true" do expect { described_class.new(:name => "a", :purge_ssh_keys => true) }.to raise_error(Puppet::Error, /purge_ssh_keys can only be true for users with a defined home directory/) end it "should not accept the ~ wildcard" do expect { described_class.new(:name => "a", :purge_ssh_keys => "~/keys") }.to raise_error(Puppet::Error, /meta character ~ or %h only allowed for users with a defined home directory/) end it "should not accept the %h wildcard" do expect { described_class.new(:name => "a", :purge_ssh_keys => "%h/keys") }.to raise_error(Puppet::Error, /meta character ~ or %h only allowed for users with a defined home directory/) end end context "with a valid parameter" do let(:paths) do [ "/dev/null", "/tmp/keyfile" ].map { |path| File.expand_path(path) } end subject do res = described_class.new(:name => "test", :purge_ssh_keys => paths) res.catalog = Puppet::Resource::Catalog.new res end it "should not just return from generate" do subject.expects :find_unmanaged_keys subject.generate end it "should check each keyfile for readability" do paths.each do |path| File.expects(:readable?).with(path) end subject.generate end end describe "generated keys" do subject do res = described_class.new(:name => "test_user_name", :purge_ssh_keys => purge_param) res.catalog = Puppet::Resource::Catalog.new res end context "when purging is disabled" do let(:purge_param) { false } its(:generate) { should be_empty } end context "when purging is enabled" do let(:purge_param) { my_fixture('authorized_keys') } let(:resources) { subject.generate } it "should contain a resource for each key" do names = resources.collect { |res| res.name } expect(names).to include("key1 name") expect(names).to include("keyname2") end it "should not include keys in comment lines" do names = resources.collect { |res| res.name } expect(names).not_to include("keyname3") end it "should generate names for unnamed keys" do names = resources.collect { |res| res.name } fixture_path = File.join(my_fixture_dir, 'authorized_keys') expect(names).to include("#{fixture_path}:unnamed-1") end it "should each have a value for the user property" do expect(resources.map { |res| res[:user] }.reject { |user_name| user_name == "test_user_name" }).to be_empty end end end end end puppet-5.5.10/spec/unit/type/vlan_spec.rb0000644005276200011600000000274413417161722020213 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:vlan) do it "should have a 'name' parameter'" do expect(Puppet::Type.type(:vlan).new(:name => "200")[:name]).to eq("200") end it "should have a 'device_url' parameter'" do expect(Puppet::Type.type(:vlan).new(:name => "200", :device_url => :device)[:device_url]).to eq(:device) end it "should be applied on device" do expect(Puppet::Type.type(:vlan).new(:name => "200")).to be_appliable_to_device end it "should have an ensure property" do expect(Puppet::Type.type(:vlan).attrtype(:ensure)).to eq(:property) end it "should have a description property" do expect(Puppet::Type.type(:vlan).attrtype(:description)).to eq(:property) end describe "when validating attribute values" do before do @provider = stub 'provider', :class => Puppet::Type.type(:vlan).defaultprovider, :clear => nil Puppet::Type.type(:vlan).defaultprovider.stubs(:new).returns(@provider) end it "should support :present as a value to :ensure" do Puppet::Type.type(:vlan).new(:name => "200", :ensure => :present) end it "should support :absent as a value to :ensure" do Puppet::Type.type(:vlan).new(:name => "200", :ensure => :absent) end it "should fail if vlan name is not a number" do expect { Puppet::Type.type(:vlan).new(:name => "notanumber", :ensure => :present) }.to raise_error(Puppet::ResourceError, /Parameter name failed on Vlan/) end end end puppet-5.5.10/spec/unit/type/yumrepo_spec.rb0000644005276200011600000003645313417161722020757 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet' shared_examples_for "a yumrepo parameter that can be absent" do |param| it "can be set as :absent" do described_class.new(:name => 'puppetlabs', param => :absent) end it "can be set as \"absent\"" do described_class.new(:name => 'puppetlabs', param => 'absent') end end shared_examples_for "a yumrepo parameter that can be an integer" do |param| it "accepts a valid positive integer" do instance = described_class.new(:name => 'puppetlabs', param => '12') expect(instance[param]).to eq '12' end it "accepts zero" do instance = described_class.new(:name => 'puppetlabs', param => '0') expect(instance[param]).to eq '0' end it "rejects invalid positive float" do expect { described_class.new( :name => 'puppetlabs', param => '12.5' ) }.to raise_error(Puppet::ResourceError, /Parameter #{param} failed/) end it "rejects invalid non-integer" do expect { described_class.new( :name => 'puppetlabs', param => 'I\'m a six' ) }.to raise_error(Puppet::ResourceError, /Parameter #{param} failed/) end it "rejects invalid string with integers inside" do expect { described_class.new( :name => 'puppetlabs', param => 'I\'m a 6' ) }.to raise_error(Puppet::ResourceError, /Parameter #{param} failed/) end end shared_examples_for "a yumrepo parameter that can't be a negative integer" do |param| it "rejects invalid negative integer" do expect { described_class.new( :name => 'puppetlabs', param => '-12' ) }.to raise_error(Puppet::ResourceError, /Parameter #{param} failed/) end end shared_examples_for "a yumrepo parameter that expects a boolean parameter" do |param| valid_values = %w[true false 0 1 no yes] valid_values.each do |value| it "accepts #{value} downcased to #{value.downcase} and capitalizes it" do instance = described_class.new(:name => 'puppetlabs', param => value.downcase) expect(instance[param]).to eq value.downcase.capitalize end it "fails on valid value #{value} contained in another value" do expect { described_class.new( :name => 'puppetlabs', param => "bla#{value}bla" ) }.to raise_error(Puppet::ResourceError, /Parameter #{param} failed/) end end it "rejects invalid boolean values" do expect { described_class.new(:name => 'puppetlabs', param => 'flase') }.to raise_error(Puppet::ResourceError, /Parameter #{param} failed/) end end shared_examples_for "a yumrepo parameter that accepts a single URL" do |param| it "can accept a single URL" do described_class.new( :name => 'puppetlabs', param => 'http://localhost/yumrepos' ) end it "fails if an invalid URL is provided" do expect { described_class.new( :name => 'puppetlabs', param => "that's no URL!" ) }.to raise_error(Puppet::ResourceError, /Parameter #{param} failed/) end it "fails if a valid URL uses an invalid URI scheme" do expect { described_class.new( :name => 'puppetlabs', param => 'ldap://localhost/yumrepos' ) }.to raise_error(Puppet::ResourceError, /Parameter #{param} failed/) end end shared_examples_for "a yumrepo parameter that accepts multiple URLs" do |param| it "can accept multiple URLs" do described_class.new( :name => 'puppetlabs', param => 'http://localhost/yumrepos http://localhost/more-yumrepos' ) end it "fails if multiple URLs are given and one is invalid" do expect { described_class.new( :name => 'puppetlabs', param => "http://localhost/yumrepos That's no URL!" ) }.to raise_error(Puppet::ResourceError, /Parameter #{param} failed/) end end shared_examples_for "a yumrepo parameter that accepts kMG units" do |param| %w[k M G].each do |unit| it "can accept an integer with #{unit} units" do described_class.new( :name => 'puppetlabs', param => "123#{unit}" ) end end it "fails if wrong unit passed" do expect { described_class.new( :name => 'puppetlabs', param => '123J' ) }.to raise_error(Puppet::ResourceError, /Parameter #{param} failed/) end end describe Puppet::Type.type(:yumrepo) do it "has :name as its namevar" do expect(described_class.key_attributes).to eq [:name] end describe "validating" do describe "name" do it "is a valid parameter" do instance = described_class.new(:name => 'puppetlabs') expect(instance.name).to eq 'puppetlabs' end end describe "target" do it_behaves_like "a yumrepo parameter that can be absent", :target end describe "descr" do it_behaves_like "a yumrepo parameter that can be absent", :descr end describe "mirrorlist" do it_behaves_like "a yumrepo parameter that accepts a single URL", :mirrorlist it_behaves_like "a yumrepo parameter that can be absent", :mirrorlist end describe "baseurl" do it_behaves_like "a yumrepo parameter that can be absent", :baseurl it_behaves_like "a yumrepo parameter that accepts a single URL", :baseurl it_behaves_like "a yumrepo parameter that accepts multiple URLs", :baseurl end describe "enabled" do it_behaves_like "a yumrepo parameter that expects a boolean parameter", :enabled it_behaves_like "a yumrepo parameter that can be absent", :enabled end describe "gpgcheck" do it_behaves_like "a yumrepo parameter that expects a boolean parameter", :gpgcheck it_behaves_like "a yumrepo parameter that can be absent", :gpgcheck end describe "payload_gpgcheck" do it_behaves_like "a yumrepo parameter that expects a boolean parameter", :payload_gpgcheck it_behaves_like "a yumrepo parameter that can be absent", :payload_gpgcheck end describe "repo_gpgcheck" do it_behaves_like "a yumrepo parameter that expects a boolean parameter", :repo_gpgcheck it_behaves_like "a yumrepo parameter that can be absent", :repo_gpgcheck end describe "gpgkey" do it_behaves_like "a yumrepo parameter that can be absent", :gpgkey it_behaves_like "a yumrepo parameter that accepts a single URL", :gpgkey it_behaves_like "a yumrepo parameter that accepts multiple URLs", :gpgkey end describe "include" do it_behaves_like "a yumrepo parameter that can be absent", :include it_behaves_like "a yumrepo parameter that accepts a single URL", :include end describe "exclude" do it_behaves_like "a yumrepo parameter that can be absent", :exclude end describe "includepkgs" do it_behaves_like "a yumrepo parameter that can be absent", :includepkgs end describe "enablegroups" do it_behaves_like "a yumrepo parameter that expects a boolean parameter", :enablegroups it_behaves_like "a yumrepo parameter that can be absent", :enablegroups end describe "failovermethod" do %w[roundrobin priority].each do |value| it "accepts a value of #{value}" do described_class.new(:name => "puppetlabs", :failovermethod => value) end it "fails on valid value #{value} contained in another value" do expect { described_class.new( :name => 'puppetlabs', :failovermethod => "bla#{value}bla" ) }.to raise_error(Puppet::ResourceError, /Parameter failovermethod failed/) end end it "raises an error if an invalid value is given" do expect { described_class.new(:name => "puppetlabs", :failovermethod => "notavalidvalue") }.to raise_error(Puppet::ResourceError, /Parameter failovermethod failed/) end it_behaves_like "a yumrepo parameter that can be absent", :failovermethod end describe "keepalive" do it_behaves_like "a yumrepo parameter that expects a boolean parameter", :keepalive it_behaves_like "a yumrepo parameter that can be absent", :keepalive end describe "http_caching" do %w[packages all none].each do |value| it "accepts a valid value of #{value}" do described_class.new(:name => 'puppetlabs', :http_caching => value) end it "fails on valid value #{value} contained in another value" do expect { described_class.new( :name => 'puppetlabs', :http_caching => "bla#{value}bla" ) }.to raise_error(Puppet::ResourceError, /Parameter http_caching failed/) end end it "rejects invalid values" do expect { described_class.new(:name => 'puppetlabs', :http_caching => 'yes') }.to raise_error(Puppet::ResourceError, /Parameter http_caching failed/) end it_behaves_like "a yumrepo parameter that can be absent", :http_caching end describe "timeout" do it_behaves_like "a yumrepo parameter that can be absent", :timeout it_behaves_like "a yumrepo parameter that can be an integer", :timeout it_behaves_like "a yumrepo parameter that can't be a negative integer", :timeout end describe "metadata_expire" do it_behaves_like "a yumrepo parameter that can be absent", :metadata_expire it_behaves_like "a yumrepo parameter that can be an integer", :metadata_expire it_behaves_like "a yumrepo parameter that can't be a negative integer", :metadata_expire it "accepts dhm units" do %W[d h m].each do |unit| described_class.new( :name => 'puppetlabs', :metadata_expire => "123#{unit}" ) end end it "accepts never as value" do described_class.new(:name => 'puppetlabs', :metadata_expire => 'never') end end describe "protect" do it_behaves_like "a yumrepo parameter that expects a boolean parameter", :protect it_behaves_like "a yumrepo parameter that can be absent", :protect end describe "priority" do it_behaves_like "a yumrepo parameter that can be absent", :priority it_behaves_like "a yumrepo parameter that can be an integer", :priority end describe "proxy" do it_behaves_like "a yumrepo parameter that can be absent", :proxy it "accepts _none_" do described_class.new( :name => 'puppetlabs', :proxy => "_none_" ) end it_behaves_like "a yumrepo parameter that accepts a single URL", :proxy end describe "proxy_username" do it_behaves_like "a yumrepo parameter that can be absent", :proxy_username end describe "proxy_password" do it_behaves_like "a yumrepo parameter that can be absent", :proxy_password end describe "s3_enabled" do it_behaves_like "a yumrepo parameter that expects a boolean parameter", :s3_enabled it_behaves_like "a yumrepo parameter that can be absent", :s3_enabled end describe "skip_if_unavailable" do it_behaves_like "a yumrepo parameter that expects a boolean parameter", :skip_if_unavailable it_behaves_like "a yumrepo parameter that can be absent", :skip_if_unavailable end describe "sslcacert" do it_behaves_like "a yumrepo parameter that can be absent", :sslcacert end describe "sslverify" do it_behaves_like "a yumrepo parameter that expects a boolean parameter", :sslverify it_behaves_like "a yumrepo parameter that can be absent", :sslverify end describe "sslclientcert" do it_behaves_like "a yumrepo parameter that can be absent", :sslclientcert end describe "sslclientkey" do it_behaves_like "a yumrepo parameter that can be absent", :sslclientkey end describe "metalink" do it_behaves_like "a yumrepo parameter that can be absent", :metalink it_behaves_like "a yumrepo parameter that accepts a single URL", :metalink end describe "assumeyes" do it_behaves_like "a yumrepo parameter that expects a boolean parameter", :assumeyes it_behaves_like "a yumrepo parameter that can be absent", :assumeyes end describe "cost" do it_behaves_like "a yumrepo parameter that can be absent", :cost it_behaves_like "a yumrepo parameter that can be an integer", :cost it_behaves_like "a yumrepo parameter that can't be a negative integer", :cost end describe "throttle" do it_behaves_like "a yumrepo parameter that can be absent", :throttle it_behaves_like "a yumrepo parameter that can be an integer", :throttle it_behaves_like "a yumrepo parameter that can't be a negative integer", :throttle it_behaves_like "a yumrepo parameter that accepts kMG units", :throttle it "accepts percentage as unit" do described_class.new( :name => 'puppetlabs', :throttle => '123%' ) end end describe "bandwidth" do it_behaves_like "a yumrepo parameter that can be absent", :bandwidth it_behaves_like "a yumrepo parameter that can be an integer", :bandwidth it_behaves_like "a yumrepo parameter that can't be a negative integer", :bandwidth it_behaves_like "a yumrepo parameter that accepts kMG units", :bandwidth end describe "gpgcakey" do it_behaves_like "a yumrepo parameter that can be absent", :gpgcakey it_behaves_like "a yumrepo parameter that accepts a single URL", :gpgcakey end describe "retries" do it_behaves_like "a yumrepo parameter that can be absent", :retries it_behaves_like "a yumrepo parameter that can be an integer", :retries it_behaves_like "a yumrepo parameter that can't be a negative integer", :retries end describe "mirrorlist_expire" do it_behaves_like "a yumrepo parameter that can be absent", :mirrorlist_expire it_behaves_like "a yumrepo parameter that can be an integer", :mirrorlist_expire it_behaves_like "a yumrepo parameter that can't be a negative integer", :mirrorlist_expire end describe "deltarpm_percentage" do it_behaves_like "a yumrepo parameter that can be absent", :deltarpm_percentage it_behaves_like "a yumrepo parameter that can be an integer", :deltarpm_percentage it_behaves_like "a yumrepo parameter that can't be a negative integer", :deltarpm_percentage end describe "deltarpm_metadata_percentage" do it_behaves_like "a yumrepo parameter that can be absent", :deltarpm_metadata_percentage it_behaves_like "a yumrepo parameter that can be an integer", :deltarpm_metadata_percentage it_behaves_like "a yumrepo parameter that can't be a negative integer", :deltarpm_metadata_percentage end describe "username" do it_behaves_like "a yumrepo parameter that can be absent", :username end describe "password" do it_behaves_like "a yumrepo parameter that can be absent", :password it "redacts password information from the logs" do resource = described_class.new(:name => 'puppetlabs', :password => 'top secret') harness = Puppet::Transaction::ResourceHarness.new(Puppet::Transaction.new(Puppet::Resource::Catalog.new, nil, nil)) harness.evaluate(resource) resource[:password] = 'super classified' status = harness.evaluate(resource) sync_event = status.events[0] expect(sync_event.message).to eq 'changed [redacted] to [redacted]' end end end end puppet-5.5.10/spec/unit/type/zfs_spec.rb0000644005276200011600000000307113417161722020047 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' zfs = Puppet::Type.type(:zfs) describe zfs do properties = [:ensure, :mountpoint, :compression, :copies, :quota, :reservation, :sharenfs, :snapdir] properties.each do |property| it "should have a #{property} property" do expect(zfs.attrclass(property).ancestors).to be_include(Puppet::Property) end end parameters = [:name] parameters.each do |parameter| it "should have a #{parameter} parameter" do expect(zfs.attrclass(parameter).ancestors).to be_include(Puppet::Parameter) end end it "should autorequire the containing zfs and the zpool" do zfs_provider = mock "provider" zfs_provider.stubs(:name).returns(:zfs) zfs.stubs(:defaultprovider).returns(zfs_provider) zpool_provider = mock "provider" zpool_provider.stubs(:name).returns(:zpool) Puppet::Type.type(:zpool).stubs(:defaultprovider).returns(zpool_provider) foo_pool = Puppet::Type.type(:zpool).new(:name => "foo") foo_bar_zfs = Puppet::Type.type(:zfs).new(:name => "foo/bar") foo_bar_baz_zfs = Puppet::Type.type(:zfs).new(:name => "foo/bar/baz") foo_bar_baz_buz_zfs = Puppet::Type.type(:zfs).new(:name => "foo/bar/baz/buz") Puppet::Resource::Catalog.new :testing do |conf| [foo_pool, foo_bar_zfs, foo_bar_baz_zfs, foo_bar_baz_buz_zfs].each { |resource| conf.add_resource resource } end req = foo_bar_baz_buz_zfs.autorequire.collect { |edge| edge.source.ref } [foo_pool.ref, foo_bar_zfs.ref, foo_bar_baz_zfs.ref].each { |ref| expect(req.include?(ref)).to eq(true) } end end puppet-5.5.10/spec/unit/type/zone_spec.rb0000644005276200011600000001466313417161722020231 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:zone) do let(:zone) { described_class.new(:name => 'dummy', :path => '/dummy', :provider => :solaris, :ip=>'if:1.2.3.4:2.3.4.5', :inherit=>'/', :dataset=>'tank') } let(:provider) { zone.provider } let(:ip) { zone.property(:ip) } let(:inherit) { zone.property(:inherit) } let(:dataset) { zone.property(:dataset) } parameters = [:create_args, :install_args, :sysidcfg, :realhostname] parameters.each do |parameter| it "should have a #{parameter} parameter" do expect(described_class.attrclass(parameter).ancestors).to be_include(Puppet::Parameter) end end properties = [:ip, :iptype, :autoboot, :pool, :shares, :inherit, :path] properties.each do |property| it "should have a #{property} property" do expect(described_class.attrclass(property).ancestors).to be_include(Puppet::Property) end end describe "when trying to set a property that is empty" do it "should verify that property.insync? of nil or :absent is true" do [inherit, ip, dataset].each do |prop| prop.stubs(:should).returns [] end [inherit, ip, dataset].each do |prop| expect(prop.insync?(nil)).to be_truthy end [inherit, ip, dataset].each do |prop| expect(prop.insync?(:absent)).to be_truthy end end end describe "when trying to set a property that is non empty" do it "should verify that property.insync? of nil or :absent is false" do [inherit, ip, dataset].each do |prop| prop.stubs(:should).returns ['a','b'] end [inherit, ip, dataset].each do |prop| expect(prop.insync?(nil)).to be_falsey end [inherit, ip, dataset].each do |prop| expect(prop.insync?(:absent)).to be_falsey end end end describe "when trying to set a property that is non empty" do it "insync? should return true or false depending on the current value, and new value" do [inherit, ip, dataset].each do |prop| prop.stubs(:should).returns ['a','b'] end [inherit, ip, dataset].each do |prop| expect(prop.insync?(['b', 'a'])).to be_truthy end [inherit, ip, dataset].each do |prop| expect(prop.insync?(['a'])).to be_falsey end end end it "should be valid when only :path is given" do described_class.new(:name => "dummy", :path => '/dummy', :provider => :solaris) end it "should be invalid when :ip is missing a \":\" and iptype is :shared" do expect { described_class.new(:name => "dummy", :ip => "if", :path => "/dummy", :provider => :solaris) }.to raise_error(Puppet::Error, /ip must contain interface name and ip address separated by a ":"/) end it "should be invalid when :ip has a \":\" and iptype is :exclusive" do expect { described_class.new(:name => "dummy", :ip => "if:1.2.3.4", :iptype => :exclusive, :provider => :solaris) }.to raise_error(Puppet::Error, /only interface may be specified when using exclusive IP stack/) end it "should be invalid when :ip has two \":\" and iptype is :exclusive" do expect { described_class.new(:name => "dummy", :ip => "if:1.2.3.4:2.3.4.5", :iptype => :exclusive, :provider => :solaris) }.to raise_error(Puppet::Error, /only interface may be specified when using exclusive IP stack/) end it "should be valid when :iptype is :shared and using interface and ip" do described_class.new(:name => "dummy", :path => "/dummy", :ip => "if:1.2.3.4", :provider => :solaris) end it "should be valid when :iptype is :shared and using interface, ip and default route" do described_class.new(:name => "dummy", :path => "/dummy", :ip => "if:1.2.3.4:2.3.4.5", :provider => :solaris) end it "should be valid when :iptype is :exclusive and using interface" do described_class.new(:name => "dummy", :path => "/dummy", :ip => "if", :iptype => :exclusive, :provider => :solaris) end it "should auto-require :dataset entries" do fs = 'random-pool/some-zfs' catalog = Puppet::Resource::Catalog.new relationship_graph = Puppet::Graph::RelationshipGraph.new(Puppet::Graph::RandomPrioritizer.new) zfs = Puppet::Type.type(:zfs).new(:name => fs) catalog.add_resource zfs zone = described_class.new(:name => "dummy", :path => "/foo", :ip => 'en1:1.0.0.0', :dataset => fs, :provider => :solaris) catalog.add_resource zone relationship_graph.populate_from(catalog) expect(relationship_graph.dependencies(zone)).to eq([zfs]) end describe Puppet::Zone::StateMachine do let (:sm) { Puppet::Zone::StateMachine.new } before :each do sm.insert_state :absent, :down => :destroy sm.insert_state :configured, :up => :configure, :down => :uninstall sm.insert_state :installed, :up => :install, :down => :stop sm.insert_state :running, :up => :start end context ":insert_state" do it "should insert state in correct order" do sm.insert_state :dummy, :left => :right expect(sm.index(:dummy)).to eq(4) end end context ":alias_state" do it "should alias state" do sm.alias_state :dummy, :running expect(sm.name(:dummy)).to eq(:running) end end context ":name" do it "should get an aliased state correctly" do sm.alias_state :dummy, :running expect(sm.name(:dummy)).to eq(:running) end it "should get an un aliased state correctly" do expect(sm.name(:dummy)).to eq(:dummy) end end context ":index" do it "should return the state index correctly" do sm.insert_state :dummy, :left => :right expect(sm.index(:dummy)).to eq(4) end end context ":sequence" do it "should correctly return the actions to reach state specified" do expect(sm.sequence(:absent, :running).map{|p|p[:up]}).to eq([:configure,:install,:start]) end it "should correctly return the actions to reach state specified(2)" do expect(sm.sequence(:running, :absent).map{|p|p[:down]}).to eq([:stop, :uninstall, :destroy]) end end context ":cmp" do it "should correctly compare state sequence values" do expect(sm.cmp?(:absent, :running)).to eq(true) expect(sm.cmp?(:running, :running)).to eq(false) expect(sm.cmp?(:running, :absent)).to eq(false) end end end end puppet-5.5.10/spec/unit/type/zpool_spec.rb0000644005276200011600000000662613417161722020421 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' zpool = Puppet::Type.type(:zpool) describe zpool do before do @provider = stub 'provider' @resource = stub 'resource', :resource => nil, :provider => @provider, :line => nil, :file => nil end properties = [:ensure, :disk, :mirror, :raidz, :spare, :log] properties.each do |property| it "should have a #{property} property" do expect(zpool.attrclass(property).ancestors).to be_include(Puppet::Property) end end parameters = [:pool, :raid_parity] parameters.each do |parameter| it "should have a #{parameter} parameter" do expect(zpool.attrclass(parameter).ancestors).to be_include(Puppet::Parameter) end end end vdev_property = Puppet::Property::VDev describe vdev_property do before do vdev_property.initvars @resource = stub 'resource', :[]= => nil, :property => nil @property = vdev_property.new(:resource => @resource) end it "should be insync if the devices are the same" do @property.should = ["dev1 dev2"] expect(@property.safe_insync?(["dev2 dev1"])).to be_truthy end it "should be out of sync if the devices are not the same" do @property.should = ["dev1 dev3"] expect(@property.safe_insync?(["dev2 dev1"])).to be_falsey end it "should be insync if the devices are the same and the should values are comma separated" do @property.should = ["dev1", "dev2"] expect(@property.safe_insync?(["dev2 dev1"])).to be_truthy end it "should be out of sync if the device is absent and should has a value" do @property.should = ["dev1", "dev2"] expect(@property.safe_insync?(:absent)).to be_falsey end it "should be insync if the device is absent and should is absent" do @property.should = [:absent] expect(@property.safe_insync?(:absent)).to be_truthy end end multi_vdev_property = Puppet::Property::MultiVDev describe multi_vdev_property do before do multi_vdev_property.initvars @resource = stub 'resource', :[]= => nil, :property => nil @property = multi_vdev_property.new(:resource => @resource) end it "should be insync if the devices are the same" do @property.should = ["dev1 dev2"] expect(@property.safe_insync?(["dev2 dev1"])).to be_truthy end it "should be out of sync if the devices are not the same" do @property.should = ["dev1 dev3"] expect(@property.safe_insync?(["dev2 dev1"])).to be_falsey end it "should be out of sync if the device is absent and should has a value" do @property.should = ["dev1", "dev2"] expect(@property.safe_insync?(:absent)).to be_falsey end it "should be insync if the device is absent and should is absent" do @property.should = [:absent] expect(@property.safe_insync?(:absent)).to be_truthy end describe "when there are multiple lists of devices" do it "should be in sync if each group has the same devices" do @property.should = ["dev1 dev2", "dev3 dev4"] expect(@property.safe_insync?(["dev2 dev1", "dev3 dev4"])).to be_truthy end it "should be out of sync if any group has the different devices" do @property.should = ["dev1 devX", "dev3 dev4"] expect(@property.safe_insync?(["dev2 dev1", "dev3 dev4"])).to be_falsey end it "should be out of sync if devices are in the wrong group" do @property.should = ["dev1 dev2", "dev3 dev4"] expect(@property.safe_insync?(["dev2 dev3", "dev1 dev4"])).to be_falsey end end end puppet-5.5.10/spec/unit/util/0000755005276200011600000000000013417162177015706 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/util/at_fork_spec.rb0000644005276200011600000001152113417161721020664 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe 'Puppet::Util::AtFork' do EXPECTED_HANDLER_METHODS = [:prepare, :parent, :child] before :each do Puppet::Util.class_exec do const_set(:AtFork, Module.new) end end after :each do Puppet::Util.class_exec do remove_const(:AtFork) end end describe '.get_handler' do context 'when on Solaris' do before :each do Facter.expects(:value).with(:operatingsystem).returns('Solaris') end after :each do Object.class_exec do remove_const(:Fiddle) if const_defined?(:Fiddle) end end def stub_solaris_handler(stub_noop_too = false) Puppet::Util::AtFork.stubs(:require).with() do |lib| if lib == 'puppet/util/at_fork/solaris' load lib + '.rb' true elsif stub_noop_too && lib == 'puppet/util/at_fork/noop' Puppet::Util::AtFork.class_exec do const_set(:Noop, Class.new) end true else false end end.returns(true) unless stub_noop_too Object.class_exec do const_set(:Fiddle, Module.new do const_set(:TYPE_VOIDP, nil) const_set(:TYPE_VOID, nil) const_set(:TYPE_INT, nil) const_set(:DLError, Class.new(StandardError)) const_set(:Handle, Class.new) const_set(:Function, Class.new) end) end end TOPLEVEL_BINDING.eval('self').stubs(:require).with() do |lib| if lib == 'fiddle' raise LoadError, 'no fiddle' if stub_noop_too else Kernel.require lib end true end.returns(true) end it %q(should return the Solaris specific AtFork handler) do Puppet::Util::AtFork.stubs(:require).with() do |lib| if lib == 'puppet/util/at_fork/solaris' Puppet::Util::AtFork.class_exec do const_set(:Solaris, Class.new) end true else false end end.returns(true) load 'puppet/util/at_fork.rb' expect(Puppet::Util::AtFork.get_handler.class).to eq(Puppet::Util::AtFork::Solaris) end it %q(should return the Noop handler when Fiddle could not be loaded) do stub_solaris_handler(true) load 'puppet/util/at_fork.rb' expect(Puppet::Util::AtFork.get_handler.class).to eq(Puppet::Util::AtFork::Noop) end it %q(should fail when libcontract cannot be loaded) do stub_solaris_handler Fiddle::Handle.expects(:new).with(regexp_matches(/^libcontract.so.*/)).raises(Fiddle::DLError, 'no such library') expect { load 'puppet/util/at_fork.rb' }.to raise_error(Fiddle::DLError, 'no such library') end it %q(should fail when libcontract doesn't define all the necessary functions) do stub_solaris_handler handle = stub('Fiddle::Handle') Fiddle::Handle.expects(:new).with(regexp_matches(/^libcontract.so.*/)).returns(handle) handle.expects(:[]).raises(Fiddle::DLError, 'no such method') expect { load 'puppet/util/at_fork.rb' }.to raise_error(Fiddle::DLError, 'no such method') end it %q(the returned Solaris specific handler should respond to the expected methods) do stub_solaris_handler handle = stub('Fiddle::Handle') Fiddle::Handle.expects(:new).with(regexp_matches(/^libcontract.so.*/)).returns(handle) handle.stubs(:[]).returns(nil) Fiddle::Function.stubs(:new).returns(Proc.new {}) load 'puppet/util/at_fork.rb' expect(Puppet::Util::AtFork.get_handler.public_methods).to include(*EXPECTED_HANDLER_METHODS) end end context 'when NOT on Solaris' do before :each do Facter.expects(:value).with(:operatingsystem).returns(nil) end def stub_noop_handler(namespace_only = false) Puppet::Util::AtFork.stubs(:require).with() do |lib| if lib == 'puppet/util/at_fork/noop' if namespace_only Puppet::Util::AtFork.class_exec do const_set(:Noop, Class.new) end else load lib + '.rb' end true else false end end.returns(true) end it %q(should return the Noop AtFork handler) do stub_noop_handler(true) load 'puppet/util/at_fork.rb' expect(Puppet::Util::AtFork.get_handler.class).to eq(Puppet::Util::AtFork::Noop) end it %q(the returned Noop handler should respond to the expected methods) do stub_noop_handler load 'puppet/util/at_fork.rb' expect(Puppet::Util::AtFork.get_handler.public_methods).to include(*EXPECTED_HANDLER_METHODS) end end end end puppet-5.5.10/spec/unit/util/backups_spec.rb0000644005276200011600000001034713417161721020674 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/backups' describe Puppet::Util::Backups do include PuppetSpec::Files let(:bucket) { stub('bucket', :name => "foo") } let!(:file) do f = Puppet::Type.type(:file).new(:name => path, :backup => 'foo') f.stubs(:bucket).returns(bucket) f end describe "when backing up a file" do let(:path) { make_absolute('/no/such/file') } it "should noop if the file does not exist" do file = Puppet::Type.type(:file).new(:name => path) file.expects(:bucket).never Puppet::FileSystem.expects(:exist?).with(path).returns false file.perform_backup end it "should succeed silently if self[:backup] is false" do file = Puppet::Type.type(:file).new(:name => path, :backup => false) file.expects(:bucket).never Puppet::FileSystem.expects(:exist?).never file.perform_backup end it "a bucket should be used when provided" do lstat_path_as(path, 'file') bucket.expects(:backup).with(path).returns("mysum") Puppet::FileSystem.expects(:exist?).with(path).returns(true) file.perform_backup end it "should propagate any exceptions encountered when backing up to a filebucket" do lstat_path_as(path, 'file') bucket.expects(:backup).raises ArgumentError Puppet::FileSystem.expects(:exist?).with(path).returns(true) expect { file.perform_backup }.to raise_error(ArgumentError) end describe "and local backup is configured" do let(:ext) { 'foobkp' } let(:backup) { path + '.' + ext } let(:file) { Puppet::Type.type(:file).new(:name => path, :backup => '.'+ext) } it "should remove any local backup if one exists" do lstat_path_as(backup, 'file') Puppet::FileSystem.expects(:unlink).with(backup) FileUtils.stubs(:cp_r) Puppet::FileSystem.expects(:exist?).with(path).returns(true) file.perform_backup end it "should fail when the old backup can't be removed" do lstat_path_as(backup, 'file') Puppet::FileSystem.expects(:unlink).with(backup).raises ArgumentError FileUtils.expects(:cp_r).never Puppet::FileSystem.expects(:exist?).with(path).returns(true) expect { file.perform_backup }.to raise_error(Puppet::Error) end it "should not try to remove backups that don't exist" do Puppet::FileSystem.expects(:lstat).with(backup).raises(Errno::ENOENT) Puppet::FileSystem.expects(:unlink).with(backup).never FileUtils.stubs(:cp_r) Puppet::FileSystem.expects(:exist?).with(path).returns(true) file.perform_backup end it "a copy should be created in the local directory" do FileUtils.expects(:cp_r).with(path, backup, :preserve => true) Puppet::FileSystem.stubs(:exist?).with(path).returns(true) expect(file.perform_backup).to be_truthy end it "should propagate exceptions if no backup can be created" do FileUtils.expects(:cp_r).raises ArgumentError Puppet::FileSystem.stubs(:exist?).with(path).returns(true) expect { file.perform_backup }.to raise_error(Puppet::Error) end end end describe "when backing up a directory" do let(:path) { make_absolute('/my/dir') } let(:filename) { File.join(path, 'file') } it "a bucket should work when provided" do File.stubs(:file?).with(filename).returns true Find.expects(:find).with(path).yields(filename) bucket.expects(:backup).with(filename).returns true lstat_path_as(path, 'directory') Puppet::FileSystem.stubs(:exist?).with(path).returns(true) Puppet::FileSystem.stubs(:exist?).with(filename).returns(true) file.perform_backup end it "should do nothing when recursing" do file = Puppet::Type.type(:file).new(:name => path, :backup => 'foo', :recurse => true) bucket.expects(:backup).never stub_file = stub('file', :stat => stub('stat', :ftype => 'directory')) Puppet::FileSystem.stubs(:new).with(path).returns stub_file Find.expects(:find).never file.perform_backup end end def lstat_path_as(path, ftype) Puppet::FileSystem.expects(:lstat).with(path).returns(stub('File::Stat', :ftype => ftype)) end end puppet-5.5.10/spec/unit/util/checksums_spec.rb0000644005276200011600000001606613417161721021235 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/checksums' describe Puppet::Util::Checksums do include PuppetSpec::Files before do @summer = Puppet::Util::Checksums end content_sums = [:md5, :md5lite, :sha1, :sha1lite, :sha256, :sha256lite, :sha512, :sha384, :sha224] file_only = [:ctime, :mtime, :none] content_sums.each do |sumtype| it "should be able to calculate #{sumtype} sums from strings" do expect(@summer).to be_respond_to(sumtype) end end content_sums.each do |sumtype| it "should know the expected length of #{sumtype} sums" do expect(@summer).to be_respond_to(sumtype.to_s + "_hex_length") end end [content_sums, file_only].flatten.each do |sumtype| it "should be able to calculate #{sumtype} sums from files" do expect(@summer).to be_respond_to(sumtype.to_s + "_file") end end [content_sums, file_only].flatten.each do |sumtype| it "should be able to calculate #{sumtype} sums from stream" do expect(@summer).to be_respond_to(sumtype.to_s + "_stream") end end it "should have a method for determining whether a given string is a checksum" do expect(@summer).to respond_to(:checksum?) end %w{{md5}asdfasdf {sha1}asdfasdf {ctime}asdasdf {mtime}asdfasdf {sha256}asdfasdf {sha256lite}asdfasdf {sha512}asdfasdf {sha384}asdfasdf {sha224}asdfasdf}.each do |sum| it "should consider #{sum} to be a checksum" do expect(@summer).to be_checksum(sum) end end %w{{nosuchsumthislong}asdfasdf {a}asdfasdf {ctime}}.each do |sum| it "should not consider #{sum} to be a checksum" do expect(@summer).not_to be_checksum(sum) end end it "should have a method for stripping a sum type from an existing checksum" do expect(@summer.sumtype("{md5}asdfasdfa")).to eq("md5") end it "should have a method for stripping the data from a checksum" do expect(@summer.sumdata("{md5}asdfasdfa")).to eq("asdfasdfa") end it "should return a nil sumtype if the checksum does not mention a checksum type" do expect(@summer.sumtype("asdfasdfa")).to be_nil end {:md5 => Digest::MD5, :sha1 => Digest::SHA1, :sha256 => Digest::SHA256, :sha512 => Digest::SHA512, :sha384 => Digest::SHA384}.each do |sum, klass| describe("when using #{sum}") do it "should use #{klass} to calculate string checksums" do klass.expects(:hexdigest).with("mycontent").returns "whatever" expect(@summer.send(sum, "mycontent")).to eq("whatever") end it "should use incremental #{klass} sums to calculate file checksums" do digest = mock 'digest' klass.expects(:new).returns digest file = "/path/to/my/file" fh = mock 'filehandle' fh.expects(:read).with(4096).times(3).returns("firstline").then.returns("secondline").then.returns(nil) File.expects(:open).with(file, "rb").yields(fh) digest.expects(:<<).with "firstline" digest.expects(:<<).with "secondline" digest.expects(:hexdigest).returns :mydigest expect(@summer.send(sum.to_s + "_file", file)).to eq(:mydigest) end it "should behave like #{klass} to calculate stream checksums" do digest = mock 'digest' klass.expects(:new).returns digest digest.expects(:<<).with "firstline" digest.expects(:<<).with "secondline" digest.expects(:hexdigest).returns :mydigest expect(@summer.send(sum.to_s + "_stream") do |checksum| checksum << "firstline" checksum << "secondline" end).to eq(:mydigest) end end end {:md5lite => Digest::MD5, :sha1lite => Digest::SHA1, :sha256lite => Digest::SHA256}.each do |sum, klass| describe("when using #{sum}") do it "should use #{klass} to calculate string checksums from the first 512 characters of the string" do content = "this is a test" * 100 klass.expects(:hexdigest).with(content[0..511]).returns "whatever" expect(@summer.send(sum, content)).to eq("whatever") end it "should use #{klass} to calculate a sum from the first 512 characters in the file" do digest = mock 'digest' klass.expects(:new).returns digest file = "/path/to/my/file" fh = mock 'filehandle' fh.expects(:read).with(512).returns('my content') File.expects(:open).with(file, "rb").yields(fh) digest.expects(:<<).with "my content" digest.expects(:hexdigest).returns :mydigest expect(@summer.send(sum.to_s + "_file", file)).to eq(:mydigest) end it "should use #{klass} to calculate a sum from the first 512 characters in a stream" do digest = mock 'digest' content = "this is a test" * 100 klass.expects(:new).returns digest digest.expects(:<<).with content[0..511] digest.expects(:hexdigest).returns :mydigest expect(@summer.send(sum.to_s + "_stream") do |checksum| checksum << content end).to eq(:mydigest) end it "should use #{klass} to calculate a sum from the first 512 characters in a multi-part stream" do digest = mock 'digest' content = "this is a test" * 100 klass.expects(:new).returns digest digest.expects(:<<).with content[0..5] digest.expects(:<<).with content[6..510] digest.expects(:<<).with content[511..511] digest.expects(:hexdigest).returns :mydigest expect(@summer.send(sum.to_s + "_stream") do |checksum| checksum << content[0..5] checksum << content[6..510] checksum << content[511..-1] end).to eq(:mydigest) end end end [:ctime, :mtime].each do |sum| describe("when using #{sum}") do it "should use the '#{sum}' on the file to determine the ctime" do file = "/my/file" stat = mock 'stat', sum => "mysum" Puppet::FileSystem.expects(:stat).with(file).returns(stat) expect(@summer.send(sum.to_s + "_file", file)).to eq("mysum") end it "should return nil for streams" do expectation = stub "expectation" expectation.expects(:do_something!).at_least_once expect(@summer.send(sum.to_s + "_stream"){ |checksum| checksum << "anything" ; expectation.do_something! }).to be_nil end end end describe "when using the none checksum" do it "should return an empty string" do expect(@summer.none_file("/my/file")).to eq("") end it "should return an empty string for streams" do expectation = stub "expectation" expectation.expects(:do_something!).at_least_once expect(@summer.none_stream{ |checksum| checksum << "anything" ; expectation.do_something! }).to eq("") end end {:md5 => Digest::MD5, :sha1 => Digest::SHA1}.each do |sum, klass| describe "when using #{sum}" do let(:content) { "hello\r\nworld" } let(:path) do path = tmpfile("checksum_#{sum}") File.open(path, 'wb') {|f| f.write(content)} path end it "should preserve nl/cr sequences" do expect(@summer.send(sum.to_s + "_file", path)).to eq(klass.hexdigest(content)) end end end end puppet-5.5.10/spec/unit/util/colors_spec.rb0000644005276200011600000000226013417161721020540 0ustar jenkinsjenkins#!/usr/bin/env ruby require 'spec_helper' describe Puppet::Util::Colors do include Puppet::Util::Colors let (:message) { 'a message' } let (:color) { :black } let (:subject) { self } describe ".console_color" do it { is_expected.to respond_to :console_color } it "should generate ANSI escape sequences" do expect(subject.console_color(color, message)).to eq("\e[0;30m#{message}\e[0m") end end describe ".html_color" do it { is_expected.to respond_to :html_color } it "should generate an HTML span element and style attribute" do expect(subject.html_color(color, message)).to match(/#{message}<\/span>/) end end describe ".colorize" do it { is_expected.to respond_to :colorize } context "ansicolor supported" do it "should colorize console output" do Puppet[:color] = true subject.expects(:console_color).with(color, message) subject.colorize(:black, message) end it "should not colorize unknown color schemes" do Puppet[:color] = :thisisanunknownscheme expect(subject.colorize(:black, message)).to eq(message) end end end end puppet-5.5.10/spec/unit/util/command_line_spec.rb0000755005276200011600000001375113417161721021676 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/face' require 'puppet/util/command_line' describe Puppet::Util::CommandLine do include PuppetSpec::Files context "#initialize" do it "should pull off the first argument if it looks like a subcommand" do command_line = Puppet::Util::CommandLine.new("puppet", %w{ client --help whatever.pp }) expect(command_line.subcommand_name).to eq("client") expect(command_line.args).to eq(%w{ --help whatever.pp }) end it "should return nil if the first argument looks like a .pp file" do command_line = Puppet::Util::CommandLine.new("puppet", %w{ whatever.pp }) expect(command_line.subcommand_name).to eq(nil) expect(command_line.args).to eq(%w{ whatever.pp }) end it "should return nil if the first argument looks like a flag" do command_line = Puppet::Util::CommandLine.new("puppet", %w{ --debug }) expect(command_line.subcommand_name).to eq(nil) expect(command_line.args).to eq(%w{ --debug }) end it "should return nil if the first argument is -" do command_line = Puppet::Util::CommandLine.new("puppet", %w{ - }) expect(command_line.subcommand_name).to eq(nil) expect(command_line.args).to eq(%w{ - }) end it "should return nil if the first argument is --help" do command_line = Puppet::Util::CommandLine.new("puppet", %w{ --help }) expect(command_line.subcommand_name).to eq(nil) end it "should return nil if there are no arguments" do command_line = Puppet::Util::CommandLine.new("puppet", []) expect(command_line.subcommand_name).to eq(nil) expect(command_line.args).to eq([]) end it "should pick up changes to the array of arguments" do args = %w{subcommand} command_line = Puppet::Util::CommandLine.new("puppet", args) args[0] = 'different_subcommand' expect(command_line.subcommand_name).to eq('different_subcommand') end end context "#execute" do %w{--version -V}.each do |arg| it "should print the version and exit if #{arg} is given" do expect do described_class.new("puppet", [arg]).execute end.to have_printed(/^#{Regexp.escape(Puppet.version)}$/) end end %w{--help -h}.each do|arg| it "should print help" do commandline = Puppet::Util::CommandLine.new("puppet", [arg]) commandline.expects(:exec).never expect { commandline.execute }.to have_printed(/Usage: puppet \[options\] \[options\]/).and_exit_with(0) end end end describe "when dealing with puppet commands" do it "should return the executable name if it is not puppet" do command_line = Puppet::Util::CommandLine.new("puppetmasterd", []) expect(command_line.subcommand_name).to eq("puppetmasterd") end describe "when the subcommand is not implemented" do it "should find and invoke an executable with a hyphenated name" do commandline = Puppet::Util::CommandLine.new("puppet", ['whatever', 'argument']) Puppet::Util.expects(:which).with('puppet-whatever'). returns('/dev/null/puppet-whatever') Kernel.expects(:exec).with('/dev/null/puppet-whatever', 'argument') commandline.execute end describe "and an external implementation cannot be found" do it "should abort and show the usage message" do Puppet::Util.expects(:which).with('puppet-whatever').returns(nil) commandline = Puppet::Util::CommandLine.new("puppet", ['whatever', 'argument']) commandline.expects(:exec).never expect { commandline.execute }.to have_printed(/Unknown Puppet subcommand 'whatever'/).and_exit_with(1) end it "should abort and show the help message" do Puppet::Util.expects(:which).with('puppet-whatever').returns(nil) commandline = Puppet::Util::CommandLine.new("puppet", ['whatever', 'argument']) commandline.expects(:exec).never expect { commandline.execute }.to have_printed(/See 'puppet help' for help on available puppet subcommands/).and_exit_with(1) end %w{--version -V}.each do |arg| it "should abort and display #{arg} information" do Puppet::Util.expects(:which).with('puppet-whatever').returns(nil) commandline = Puppet::Util::CommandLine.new("puppet", ['whatever', arg]) commandline.expects(:exec).never expect { commandline.execute }.to have_printed(%r[^#{Regexp.escape(Puppet.version)}$]).and_exit_with(1) end end end end describe 'when setting process priority' do let(:command_line) do Puppet::Util::CommandLine.new("puppet", %w{ agent }) end before :each do Puppet::Util::CommandLine::ApplicationSubcommand.any_instance.stubs(:run) end it 'should never set priority by default' do Process.expects(:setpriority).never command_line.execute end it 'should lower the process priority if one has been specified' do Puppet[:priority] = 10 Process.expects(:setpriority).with(0, Process.pid, 10) command_line.execute end it 'should warn if trying to raise priority, but not privileged user' do Puppet[:priority] = -10 Process.expects(:setpriority).raises(Errno::EACCES, 'Permission denied') Puppet.expects(:warning).with("Failed to set process priority to '-10'") command_line.execute end it "should warn if the platform doesn't support `Process.setpriority`" do Puppet[:priority] = 15 Process.expects(:setpriority).raises(NotImplementedError, 'NotImplementedError: setpriority() function is unimplemented on this machine') Puppet.expects(:warning).with("Failed to set process priority to '15'") command_line.execute end end end end puppet-5.5.10/spec/unit/util/command_line_utils/0000755005276200011600000000000013417162177021553 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/util/command_line_utils/puppet_option_parser_spec.rb0000644005276200011600000001011613417161721027364 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/util/command_line/puppet_option_parser' describe Puppet::Util::CommandLine::PuppetOptionParser do let(:option_parser) { described_class.new } describe "an option with a value" do it "parses a 'long' option with a value" do parses( :option => ["--angry", "Angry", :REQUIRED], :from_arguments => ["--angry", "foo"], :expects => "foo" ) end it "parses a 'short' option with a value" do parses( :option => ["--angry", "-a", "Angry", :REQUIRED], :from_arguments => ["-a", "foo"], :expects => "foo" ) end it "overrides a previous argument with a later one" do parses( :option => ["--later", "Later", :REQUIRED], :from_arguments => ["--later", "tomorrow", "--later", "morgen"], :expects => "morgen" ) end end describe "an option without a value" do it "parses a 'long' option" do parses( :option => ["--angry", "Angry", :NONE], :from_arguments => ["--angry"], :expects => true ) end it "parses a 'short' option" do parses( :option => ["--angry", "-a", "Angry", :NONE], :from_arguments => ["-a"], :expects => true ) end it "supports the '--no-blah' syntax" do parses( :option => ["--[no-]rage", "Rage", :NONE], :from_arguments => ["--no-rage"], :expects => false ) end it "overrides a previous argument with a later one" do parses( :option => ["--[no-]rage", "Rage", :NONE], :from_arguments => ["--rage", "--no-rage"], :expects => false ) end end it "does not accept an unknown option specification" do expect { option_parser.on("not", "enough") }.to raise_error(ArgumentError, /this method only takes 3 or 4 arguments/) end it "does not modify the original argument array" do option_parser.on("--foo", "Foo", :NONE) { |val| } args = ["--foo"] option_parser.parse(args) expect(args.length).to eq(1) end # The ruby stdlib OptionParser has an awesome "feature" that you cannot disable, whereby if # it sees a short option that you haven't specifically registered with it (e.g., "-r"), it # will automatically attempt to expand it out to whatever long options that you might have # registered. Since we need to do our option parsing in two passes (one pass against only # the global/puppet-wide settings definitions, and then a second pass that includes the # application or face settings--because we can't load the app/face until we've determined # the libdir), it is entirely possible that we intend to define our "short" option as part # of the second pass. Therefore, if the option parser attempts to expand it out into a # long option during the first pass, terrible things will happen. # # A long story short: we need to have the ability to control this kind of behavior in our # option parser, and this test simply affirms that we do. it "does not try to expand short options that weren't explicitly registered" do [ ["--ridiculous", "This is ridiculous", :REQUIRED], ["--rage-inducing", "This is rage-inducing", :REQUIRED] ].each do |option| option_parser.on(*option) {} end expect { option_parser.parse(["-r"]) }.to raise_error(Puppet::Util::CommandLine::PuppetOptionError) end it "respects :ignore_invalid_options" do option_parser.ignore_invalid_options = true expect { option_parser.parse(["--unknown-option"]) }.not_to raise_error end it "raises if there is an invalid option and :ignore_invalid_options is not set" do expect { option_parser.parse(["--unknown-option"]) }.to raise_error(Puppet::Util::CommandLine::PuppetOptionError) end def parses(option_case) option = option_case[:option] expected_value = option_case[:expects] arguments = option_case[:from_arguments] seen_value = nil option_parser.on(*option) do |val| seen_value = val end option_parser.parse(arguments) expect(seen_value).to eq(expected_value) end end puppet-5.5.10/spec/unit/util/constant_inflector_spec.rb0000644005276200011600000000377513417161721023151 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/constant_inflector' describe Puppet::Util::ConstantInflector, "when converting file names to constants" do it "should capitalize terms" do expect(subject.file2constant("file")).to eq("File") end it "should switch all '/' characters to double colons" do expect(subject.file2constant("file/other")).to eq("File::Other") end it "should remove underscores and capitalize the proceeding letter" do expect(subject.file2constant("file_other")).to eq("FileOther") end it "should correctly replace as many underscores as exist in the file name" do expect(subject.file2constant("two_under_scores/with_some_more_underscores")).to eq("TwoUnderScores::WithSomeMoreUnderscores") end it "should collapse multiple underscores" do expect(subject.file2constant("many___scores")).to eq("ManyScores") end it "should correctly handle file names deeper than two directories" do expect(subject.file2constant("one_two/three_four/five_six")).to eq("OneTwo::ThreeFour::FiveSix") end end describe Puppet::Util::ConstantInflector, "when converting constnats to file names" do it "should convert them to a string if necessary" do expect(subject.constant2file(Puppet::Util::ConstantInflector)).to be_instance_of(String) end it "should accept string inputs" do expect(subject.constant2file("Puppet::Util::ConstantInflector")).to be_instance_of(String) end it "should downcase all terms" do expect(subject.constant2file("Puppet")).to eq("puppet") end it "should convert '::' to '/'" do expect(subject.constant2file("Puppet::Util::Constant")).to eq("puppet/util/constant") end it "should convert mid-word capitalization to an underscore" do expect(subject.constant2file("OneTwo::ThreeFour")).to eq("one_two/three_four") end it "should correctly handle constants with more than two parts" do expect(subject.constant2file("OneTwoThree::FourFiveSixSeven")).to eq("one_two_three/four_five_six_seven") end end puppet-5.5.10/spec/unit/util/diff_spec.rb0000644005276200011600000000264513417161721020156 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/diff' require 'puppet/util/execution' describe Puppet::Util::Diff do let(:baz_output) { Puppet::Util::Execution::ProcessOutput.new('baz', 0) } describe ".diff" do it "should execute the diff command with arguments" do Puppet[:diff] = 'foo' Puppet[:diff_args] = 'bar' Puppet::Util::Execution.expects(:execute) .with(['foo', 'bar', 'a', 'b'], {:failonfail => false, :combine => false}) .returns(baz_output) expect(subject.diff('a', 'b')).to eq('baz') end it "should execute the diff command with multiple arguments" do Puppet[:diff] = 'foo' Puppet[:diff_args] = 'bar qux' Puppet::Util::Execution.expects(:execute) .with(['foo', 'bar', 'qux', 'a', 'b'], anything) .returns(baz_output) expect(subject.diff('a', 'b')).to eq('baz') end it "should omit diff arguments if none are specified" do Puppet[:diff] = 'foo' Puppet[:diff_args] = '' Puppet::Util::Execution.expects(:execute) .with(['foo', 'a', 'b'], {:failonfail => false, :combine => false}) .returns(baz_output) expect(subject.diff('a', 'b')).to eq('baz') end it "should return empty string if the diff command is empty" do Puppet[:diff] = '' Puppet::Util::Execution.expects(:execute).never expect(subject.diff('a', 'b')).to eq('') end end end puppet-5.5.10/spec/unit/util/docs_spec.rb0000644005276200011600000001023413417161721020167 0ustar jenkinsjenkinsrequire 'spec_helper' describe Puppet::Util::Docs do describe '.scrub' do let(:my_cleaned_output) do %q{This resource type uses the prescribed native tools for creating groups and generally uses POSIX APIs for retrieving information about them. It does not directly modify `/etc/passwd` or anything. * Just for fun, we'll add a list. * list item two, which has some add'l lines included in it. And here's a code block: this is the piece of code it does something cool **Autorequires:** I would be listing autorequired resources here.} end it "strips the least common indent from multi-line strings, without mangling indentation beyond the least common indent" do input = < host) do expect(subject.http_proxy_env).to eq(URI.parse(host)) end end it "should return a URI::HTTP object if HTTP_PROXY env variable is set" do Puppet::Util.withenv('HTTP_PROXY' => host) do expect(subject.http_proxy_env).to eq(URI.parse(host)) end end it "should return a URI::HTTP object with .host and .port if URI is given" do Puppet::Util.withenv('HTTP_PROXY' => "http://#{host}:#{port}") do expect(subject.http_proxy_env).to eq(URI.parse("http://#{host}:#{port}")) end end it "should return nil if proxy variable is malformed" do Puppet::Util.withenv('HTTP_PROXY' => 'this is not a valid URI') do expect(subject.http_proxy_env).to eq(nil) end end end describe ".http_proxy_host" do it "should return nil if no proxy host in config or env" do expect(subject.http_proxy_host).to eq(nil) end it "should return a proxy host if set in config" do Puppet.settings[:http_proxy_host] = host expect(subject.http_proxy_host).to eq(host) end it "should return nil if set to `none` in config" do Puppet.settings[:http_proxy_host] = 'none' expect(subject.http_proxy_host).to eq(nil) end it "uses environment variable before puppet settings" do Puppet::Util.withenv('HTTP_PROXY' => "http://#{host}:#{port}") do Puppet.settings[:http_proxy_host] = 'not.correct' expect(subject.http_proxy_host).to eq(host) end end end describe ".http_proxy_port" do it "should return a proxy port if set in environment" do Puppet::Util.withenv('HTTP_PROXY' => "http://#{host}:#{port}") do expect(subject.http_proxy_port).to eq(port) end end it "should return a proxy port if set in config" do Puppet.settings[:http_proxy_port] = port expect(subject.http_proxy_port).to eq(port) end it "uses environment variable before puppet settings" do Puppet::Util.withenv('HTTP_PROXY' => "http://#{host}:#{port}") do Puppet.settings[:http_proxy_port] = 7456 expect(subject.http_proxy_port).to eq(port) end end end describe ".http_proxy_user" do it "should return a proxy user if set in environment" do Puppet::Util.withenv('HTTP_PROXY' => "http://#{user}:#{password}@#{host}:#{port}") do expect(subject.http_proxy_user).to eq(user) end end it "should return a proxy user if set in config" do Puppet.settings[:http_proxy_user] = user expect(subject.http_proxy_user).to eq(user) end it "should use environment variable before puppet settings" do Puppet::Util.withenv('HTTP_PROXY' => "http://#{user}:#{password}@#{host}:#{port}") do Puppet.settings[:http_proxy_user] = 'clownpants' expect(subject.http_proxy_user).to eq(user) end end end describe ".http_proxy_password" do it "should return a proxy password if set in environment" do Puppet::Util.withenv('HTTP_PROXY' => "http://#{user}:#{password}@#{host}:#{port}") do expect(subject.http_proxy_password).to eq(password) end end it "should return a proxy password if set in config" do Puppet.settings[:http_proxy_user] = user Puppet.settings[:http_proxy_password] = password expect(subject.http_proxy_password).to eq(password) end it "should use environment variable before puppet settings" do Puppet::Util.withenv('HTTP_PROXY' => "http://#{user}:#{password}@#{host}:#{port}") do Puppet.settings[:http_proxy_password] = 'clownpants' expect(subject.http_proxy_password).to eq(password) end end end describe ".no_proxy?" do no_proxy = '127.0.0.1, localhost, mydomain.com, *.otherdomain.com, oddport.com:8080, *.otheroddport.com:8080, .anotherdomain.com, .anotheroddport.com:8080' it "should return false if no_proxy does not exist in env" do Puppet::Util.withenv('no_proxy' => nil) do dest = 'https://puppetlabs.com' expect(subject.no_proxy?(dest)).to be false end end it "should return false if the dest does not match any element in the no_proxy list" do Puppet::Util.withenv('no_proxy' => no_proxy) do dest = 'https://puppetlabs.com' expect(subject.no_proxy?(dest)).to be false end end it "should return true if the dest as an IP does match any element in the no_proxy list" do Puppet::Util.withenv('no_proxy' => no_proxy) do dest = 'http://127.0.0.1' expect(subject.no_proxy?(dest)).to be true end end it "should return true if the dest as single word does match any element in the no_proxy list" do Puppet::Util.withenv('no_proxy' => no_proxy) do dest = 'http://localhost' expect(subject.no_proxy?(dest)).to be true end end it "should return true if the dest as standard domain word does match any element in the no_proxy list" do Puppet::Util.withenv('no_proxy' => no_proxy) do dest = 'http://mydomain.com' expect(subject.no_proxy?(dest)).to be true end end it "should return true if the dest as standard domain with port does match any element in the no_proxy list" do Puppet::Util.withenv('no_proxy' => no_proxy) do dest = 'http://oddport.com:8080' expect(subject.no_proxy?(dest)).to be true end end it "should return false if the dest is standard domain not matching port" do Puppet::Util.withenv('no_proxy' => no_proxy) do dest = 'http://oddport.com' expect(subject.no_proxy?(dest)).to be false end end it "should return true if the dest does match any wildcarded element in the no_proxy list" do Puppet::Util.withenv('no_proxy' => no_proxy) do dest = 'http://sub.otherdomain.com' expect(subject.no_proxy?(dest)).to be true end end it "should return true if the dest does match any wildcarded element with port in the no_proxy list" do Puppet::Util.withenv('no_proxy' => no_proxy) do dest = 'http://sub.otheroddport.com:8080' expect(subject.no_proxy?(dest)).to be true end end it "should return true if the dest does match any domain level (no wildcard) element in the no_proxy list" do Puppet::Util.withenv('no_proxy' => no_proxy) do dest = 'http://sub.anotherdomain.com' expect(subject.no_proxy?(dest)).to be true end end it "should return true if the dest does match any domain level (no wildcard) element with port in the no_proxy list" do Puppet::Util.withenv('no_proxy' => no_proxy) do dest = 'http://sub.anotheroddport.com:8080' expect(subject.no_proxy?(dest)).to be true end end it "should work if passed a URI object" do Puppet::Util.withenv('no_proxy' => no_proxy) do dest = URI.parse('http://sub.otheroddport.com:8080') expect(subject.no_proxy?(dest)).to be true end end end describe '.request_with_redirects' do let(:dest) { URI.parse('http://mydomain.com/some/path') } let(:http_ok) { stub('http ok', :code => 200, :message => 'HTTP OK') } it 'generates accept and accept-encoding headers' do Net::HTTP.any_instance.stubs(:head).returns(http_ok) Net::HTTP.any_instance.expects(:get).with do |_, headers| expect(headers) .to match({'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent' => /Puppet/}) end.returns(http_ok) subject.request_with_redirects(dest, :get, 0) end it 'can return a compressed response body' do Net::HTTP.any_instance.stubs(:head).returns(http_ok) compressed_body = [ 0x1f, 0x8b, 0x08, 0x08, 0xe9, 0x08, 0x7a, 0x5a, 0x00, 0x03, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00, 0xcb, 0xc8, 0xe4, 0x02, 0x00, 0x7a, 0x7a, 0x6f, 0xed, 0x03, 0x00, 0x00, 0x00 ].pack('C*') response = stub('http ok', :code => 200, :message => 'HTTP OK', :body => compressed_body) response.stubs(:[]).with('content-encoding').returns('gzip') Net::HTTP.any_instance.expects(:get).returns(response) expect( uncompress_body(subject.request_with_redirects(dest, :get, 0)) ).to eq("hi\n") end it 'generates accept and accept-encoding headers when a block is provided' do Net::HTTP.any_instance.stubs(:head).returns(http_ok) Net::HTTP.any_instance.expects(:request_get).with do |_, headers, &block| expect(headers) .to match({'Accept' => '*/*', 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent' => /Puppet/}) end.returns(http_ok) subject.request_with_redirects(dest, :get, 0) do # unused end end it 'only makes a single HEAD request' do Net::HTTP.any_instance.expects(:head).with(anything, anything).returns(http_ok) subject.request_with_redirects(dest, :head, 0) end end end puppet-5.5.10/spec/unit/util/inifile_spec.rb0000644005276200011600000003451713417161721020670 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/util/inifile' describe Puppet::Util::IniConfig::Section do subject { described_class.new('testsection', '/some/imaginary/file') } describe "determining if the section is dirty" do it "is not dirty on creation" do expect(subject).to_not be_dirty end it "is dirty if a key is changed" do subject['hello'] = 'world' expect(subject).to be_dirty end it "is dirty if the section has been explicitly marked as dirty" do subject.mark_dirty expect(subject).to be_dirty end it "is dirty if the section is marked for deletion" do subject.destroy = true expect(subject).to be_dirty end it "is clean if the section has been explicitly marked as clean" do subject['hello'] = 'world' subject.mark_clean expect(subject).to_not be_dirty end end describe "reading an entry" do it "returns nil if the key is not present" do expect(subject['hello']).to be_nil end it "returns the value if the key is specified" do subject.entries << ['hello', 'world'] expect(subject['hello']).to eq 'world' end it "ignores comments when looking for a match" do subject.entries << '#this = comment' expect(subject['#this']).to be_nil end end describe "formatting the section" do it "prefixes the output with the section header" do expect(subject.format).to eq "[testsection]\n" end it "restores comments and blank lines" do subject.entries << "#comment\n" subject.entries << " " expect(subject.format).to eq( "[testsection]\n" + "#comment\n" + " " ) end it "adds all keys that have values" do subject.entries << ['somekey', 'somevalue'] expect(subject.format).to eq("[testsection]\nsomekey=somevalue\n") end it "excludes keys that have a value of nil" do subject.entries << ['empty', nil] expect(subject.format).to eq("[testsection]\n") end it "preserves the order of the section" do subject.entries << ['firstkey', 'firstval'] subject.entries << "# I am a comment, hear me roar\n" subject.entries << ['secondkey', 'secondval'] expect(subject.format).to eq( "[testsection]\n" + "firstkey=firstval\n" + "# I am a comment, hear me roar\n" + "secondkey=secondval\n" ) end it "is empty if the section is marked for deletion" do subject.entries << ['firstkey', 'firstval'] subject.destroy = true expect(subject.format).to eq('') end end end describe Puppet::Util::IniConfig::PhysicalFile do subject { described_class.new('/some/nonexistent/file') } let(:first_sect) do sect = Puppet::Util::IniConfig::Section.new('firstsection', '/some/imaginary/file') sect.entries << "# comment\n" << ['onefish', 'redfish'] << "\n" sect end let(:second_sect) do sect = Puppet::Util::IniConfig::Section.new('secondsection', '/some/imaginary/file') sect.entries << ['twofish', 'bluefish'] sect end describe "when reading a file" do it "raises an error if the file does not exist" do subject.filetype.stubs(:read) expect { subject.read }.to raise_error(%r[Cannot read nonexistent file .*/some/nonexistent/file]) end it "passes the contents of the file to #parse" do subject.filetype.stubs(:read).returns "[section]" subject.expects(:parse).with("[section]") subject.read end end describe "when parsing a file" do describe "parsing sections" do it "creates new sections the first time that the section is found" do text = "[mysect]\n" subject.parse(text) expect(subject.contents).to have(1).items sect = subject.contents[0] expect(sect.name).to eq "mysect" end it "raises an error if a section is redefined in the file" do text = "[mysect]\n[mysect]\n" expect { subject.parse(text) }.to raise_error(Puppet::Util::IniConfig::IniParseError, /Section "mysect" is already defined, cannot redefine/) end it "raises an error if a section is redefined in the file collection" do subject.file_collection = stub('file collection', :get_section => true) text = "[mysect]\n[mysect]\n" expect { subject.parse(text) }.to raise_error(Puppet::Util::IniConfig::IniParseError, /Section "mysect" is already defined, cannot redefine/) end end describe 'parsing properties' do it 'raises an error if the property is not within a section' do text = "key=val\n" expect { subject.parse(text) }.to raise_error(Puppet::Util::IniConfig::IniParseError, /Property with key "key" outside of a section/) end it 'adds the property to the current section' do text = "[main]\nkey=val\n" subject.parse(text) expect(subject.contents).to have(1).items sect = subject.contents[0] expect(sect['key']).to eq 'val' end context 'with white space' do let(:section) do text = <<-INIFILE [main] leading_white_space=value1 white_space_after_key =value2 white_space_after_equals= value3 white_space_after_value=value4\t INIFILE subject.parse(text) expect(subject.contents).to have(1).items subject.contents[0] end it 'allows and ignores white space before the key' do expect(section['leading_white_space']).to eq('value1') end it 'allows and ignores white space before the equals' do expect(section['white_space_after_key']).to eq('value2') end it 'allows and ignores white space after the equals' do expect(section['white_space_after_equals']).to eq('value3') end it 'allows and ignores white spaces after the value' do expect(section['white_space_after_value']).to eq('value4') end end end describe "parsing line continuations" do it "adds the continued line to the last parsed property" do text = "[main]\nkey=val\n moreval" subject.parse(text) expect(subject.contents).to have(1).items sect = subject.contents[0] expect(sect['key']).to eq "val\n moreval" end end describe "parsing comments and whitespace" do it "treats # as a comment leader" do text = "# octothorpe comment" subject.parse(text) expect(subject.contents).to eq ["# octothorpe comment"] end it "treats ; as a comment leader" do text = "; semicolon comment" subject.parse(text) expect(subject.contents).to eq ["; semicolon comment"] end it "treates 'rem' as a comment leader" do text = "rem rapid eye movement comment" subject.parse(text) expect(subject.contents).to eq ["rem rapid eye movement comment"] end it "stores comments and whitespace in a section in the correct section" do text = "[main]\n; main section comment" subject.parse(text) sect = subject.get_section("main") expect(sect.entries).to eq ["; main section comment"] end end end it "can return all sections" do text = "[first]\n" + "; comment\n" + "[second]\n" + "key=value" subject.parse(text) sections = subject.sections expect(sections).to have(2).items expect(sections[0].name).to eq "first" expect(sections[1].name).to eq "second" end it "can retrieve a specific section" do text = "[first]\n" + "; comment\n" + "[second]\n" + "key=value" subject.parse(text) section = subject.get_section("second") expect(section.name).to eq "second" expect(section["key"]).to eq "value" end describe "formatting" do it "concatenates each formatted section in order" do subject.contents << first_sect << second_sect expected = "[firstsection]\n" + "# comment\n" + "onefish=redfish\n" + "\n" + "[secondsection]\n" + "twofish=bluefish\n" expect(subject.format).to eq expected end it "includes comments that are not within a section" do subject.contents << "# This comment is not in a section\n" << first_sect << second_sect expected = "# This comment is not in a section\n" + "[firstsection]\n" + "# comment\n" + "onefish=redfish\n" + "\n" + "[secondsection]\n" + "twofish=bluefish\n" expect(subject.format).to eq expected end it "excludes sections that are marked to be destroyed" do subject.contents << first_sect << second_sect first_sect.destroy = true expected = "[secondsection]\n" + "twofish=bluefish\n" expect(subject.format).to eq expected end end describe "storing the file" do describe "with empty contents" do describe "and destroy_empty is true" do before { subject.destroy_empty = true } it "removes the file if there are no sections" do File.expects(:unlink) subject.store end it "removes the file if all sections are marked to be destroyed" do subject.contents << first_sect << second_sect first_sect.destroy = true second_sect.destroy = true File.expects(:unlink) subject.store end it "doesn't remove the file if not all sections are marked to be destroyed" do subject.contents << first_sect << second_sect first_sect.destroy = true second_sect.destroy = false File.expects(:unlink).never subject.filetype.stubs(:write) subject.store end end it "rewrites the file if destroy_empty is false" do subject.contents << first_sect << second_sect first_sect.destroy = true second_sect.destroy = true File.expects(:unlink).never subject.stubs(:format).returns "formatted" subject.filetype.expects(:write).with("formatted") subject.store end end it "rewrites the file if any section is dirty" do subject.contents << first_sect << second_sect first_sect.mark_dirty second_sect.mark_clean subject.stubs(:format).returns "formatted" subject.filetype.expects(:write).with("formatted") subject.store end it "doesn't modify the file if all sections are clean" do subject.contents << first_sect << second_sect first_sect.mark_clean second_sect.mark_clean subject.stubs(:format).returns "formatted" subject.filetype.expects(:write).never subject.store end end end describe Puppet::Util::IniConfig::FileCollection do let(:path_a) { '/some/nonexistent/file/a' } let(:path_b) { '/some/nonexistent/file/b' } let(:file_a) { Puppet::Util::IniConfig::PhysicalFile.new(path_a) } let(:file_b) { Puppet::Util::IniConfig::PhysicalFile.new(path_b) } let(:sect_a1) { Puppet::Util::IniConfig::Section.new('sect_a1', path_a) } let(:sect_a2) { Puppet::Util::IniConfig::Section.new('sect_a2', path_a) } let(:sect_b1) { Puppet::Util::IniConfig::Section.new('sect_b1', path_b) } let(:sect_b2) { Puppet::Util::IniConfig::Section.new('sect_b2', path_b) } before do file_a.contents << sect_a1 << sect_a2 file_b.contents << sect_b1 << sect_b2 end describe "reading a file" do let(:stub_file) { stub('Physical file') } it "creates a new PhysicalFile and uses that to read the file" do stub_file.expects(:read) stub_file.expects(:file_collection=) Puppet::Util::IniConfig::PhysicalFile.expects(:new).with(path_a).returns stub_file subject.read(path_a) end it "stores the PhysicalFile and the path to the file" do stub_file.stubs(:read) stub_file.stubs(:file_collection=) Puppet::Util::IniConfig::PhysicalFile.stubs(:new).with(path_a).returns stub_file subject.read(path_a) path, physical_file = subject.files.first expect(path).to eq(path_a) expect(physical_file).to eq stub_file end end describe "storing all files" do before do subject.files[path_a] = file_a subject.files[path_b] = file_b end it "stores all files in the collection" do file_a.expects(:store).once file_b.expects(:store).once subject.store end end describe "iterating over sections" do before do subject.files[path_a] = file_a subject.files[path_b] = file_b end it "yields every section from every file" do [sect_a1, sect_a2, sect_b1, sect_b2].each do |sect| sect.expects(:touch).once end subject.each_section do |sect| sect.touch end end end describe "iterating over files" do before do subject.files[path_a] = file_a subject.files[path_b] = file_b end it "yields the path to every file in the collection" do seen = [] subject.each_file do |file| seen << file end expect(seen).to include(path_a) expect(seen).to include(path_b) end end describe "retrieving a specific section" do before do subject.files[path_a] = file_a subject.files[path_b] = file_b end it "retrieves the first section defined" do expect(subject.get_section('sect_b1')).to eq sect_b1 end it "returns nil if there was no section with the given name" do expect(subject.get_section('nope')).to be_nil end it "allows #[] to be used as an alias to #get_section" do expect(subject['b2']).to eq subject.get_section('b2') end end describe "checking if a section has been defined" do before do subject.files[path_a] = file_a subject.files[path_b] = file_b end it "is true if a section with the given name is defined" do expect(subject.include?('sect_a1')).to be_truthy end it "is false if a section with the given name can't be found" do expect(subject.include?('nonexistent')).to be_falsey end end describe "adding a new section" do before do subject.files[path_a] = file_a subject.files[path_b] = file_b end it "adds the section to the appropriate file" do file_a.expects(:add_section).with('newsect') subject.add_section('newsect', path_a) end end end puppet-5.5.10/spec/unit/util/json_lockfile_spec.rb0000644005276200011600000000227313417161721022064 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/json_lockfile' describe Puppet::Util::JsonLockfile do require 'puppet_spec/files' include PuppetSpec::Files before(:each) do @lockfile = tmpfile("lock") @lock = Puppet::Util::JsonLockfile.new(@lockfile) end describe "#lock" do it "should create a lock file containing a json hash" do data = { "foo" => "foofoo", "bar" => "barbar" } @lock.lock(data) expect(PSON.parse(File.read(@lockfile))).to eq(data) end end describe "reading lock data" do it "returns deserialized JSON from the lockfile" do data = { "foo" => "foofoo", "bar" => "barbar" } @lock.lock(data) expect(@lock.lock_data).to eq data end it "returns nil if the file read returned nil" do @lock.lock File.stubs(:read).returns nil expect(@lock.lock_data).to be_nil end it "returns nil if the file was empty" do @lock.lock File.stubs(:read).returns '' expect(@lock.lock_data).to be_nil end it "returns nil if the file was not in PSON" do @lock.lock File.stubs(:read).returns '][' expect(@lock.lock_data).to be_nil end end end puppet-5.5.10/spec/unit/util/ldap/0000755005276200011600000000000013417162177016626 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/util/ldap/connection_spec.rb0000644005276200011600000001223113417161721022315 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/ldap/connection' # So our mocks and such all work, even when ldap isn't available. unless Puppet.features.ldap? class LDAP class Conn def initialize(*args) end end class SSLConn < Conn; end LDAP_OPT_PROTOCOL_VERSION = 1 LDAP_OPT_REFERRALS = 2 LDAP_OPT_ON = 3 end end describe Puppet::Util::Ldap::Connection do before do Puppet.features.stubs(:ldap?).returns true @ldapconn = mock 'ldap' LDAP::Conn.stubs(:new).returns(@ldapconn) LDAP::SSLConn.stubs(:new).returns(@ldapconn) @ldapconn.stub_everything @connection = Puppet::Util::Ldap::Connection.new("host", "port") end describe "when creating connections" do it "should require the host and port" do expect { Puppet::Util::Ldap::Connection.new("myhost") }.to raise_error(ArgumentError) end it "should allow specification of a user and password" do expect { Puppet::Util::Ldap::Connection.new("myhost", "myport", :user => "blah", :password => "boo") }.not_to raise_error end it "should allow specification of ssl" do expect { Puppet::Util::Ldap::Connection.new("myhost", "myport", :ssl => :tsl) }.not_to raise_error end it "should support requiring a new connection" do expect { Puppet::Util::Ldap::Connection.new("myhost", "myport", :reset => true) }.not_to raise_error end it "should fail if ldap is unavailable" do Puppet.features.expects(:ldap?).returns(false) expect { Puppet::Util::Ldap::Connection.new("host", "port") }.to raise_error(Puppet::Error) end it "should use neither ssl nor tls by default" do LDAP::Conn.expects(:new).with("host", "port").returns(@ldapconn) @connection.start end it "should use LDAP::SSLConn if ssl is requested" do LDAP::SSLConn.expects(:new).with("host", "port").returns(@ldapconn) @connection.ssl = true @connection.start end it "should use LDAP::SSLConn and tls if tls is requested" do LDAP::SSLConn.expects(:new).with("host", "port", true).returns(@ldapconn) @connection.ssl = :tls @connection.start end it "should set the protocol version to 3 and enable referrals" do @ldapconn.expects(:set_option).with(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3) @ldapconn.expects(:set_option).with(LDAP::LDAP_OPT_REFERRALS, LDAP::LDAP_OPT_ON) @connection.start end it "should bind with the provided user and password" do @connection.user = "myuser" @connection.password = "mypassword" @ldapconn.expects(:simple_bind).with("myuser", "mypassword") @connection.start end it "should bind with no user and password if none has been provided" do @ldapconn.expects(:simple_bind).with(nil, nil) @connection.start end end describe "when closing connections" do it "should not close connections that are not open" do @connection.stubs(:connection).returns(@ldapconn) @ldapconn.expects(:bound?).returns false @ldapconn.expects(:unbind).never @connection.close end end it "should have a class-level method for creating a default connection" do expect(Puppet::Util::Ldap::Connection).to respond_to(:instance) end describe "when creating a default connection" do it "should use the :ldapserver setting to determine the host" do Puppet[:ldapserver] = "myserv" Puppet::Util::Ldap::Connection.expects(:new).with { |host, port, options| host == "myserv" } Puppet::Util::Ldap::Connection.instance end it "should use the :ldapport setting to determine the port" do Puppet[:ldapport] = "456" Puppet::Util::Ldap::Connection.expects(:new).with { |host, port, options| port == "456" } Puppet::Util::Ldap::Connection.instance end it "should set ssl to :tls if tls is enabled" do Puppet[:ldaptls] = true Puppet::Util::Ldap::Connection.expects(:new).with { |host, port, options| options[:ssl] == :tls } Puppet::Util::Ldap::Connection.instance end it "should set ssl to 'true' if ssl is enabled and tls is not" do Puppet[:ldaptls] = false Puppet[:ldapssl] = true Puppet::Util::Ldap::Connection.expects(:new).with { |host, port, options| options[:ssl] == true } Puppet::Util::Ldap::Connection.instance end it "should set ssl to false if neither ssl nor tls are enabled" do Puppet[:ldaptls] = false Puppet[:ldapssl] = false Puppet::Util::Ldap::Connection.expects(:new).with { |host, port, options| options[:ssl] == false } Puppet::Util::Ldap::Connection.instance end it "should set the ldapuser if one is set" do Puppet[:ldapuser] = "foo" Puppet::Util::Ldap::Connection.expects(:new).with { |host, port, options| options[:user] == "foo" } Puppet::Util::Ldap::Connection.instance end it "should set the ldapuser and ldappassword if both is set" do Puppet[:ldapuser] = "foo" Puppet[:ldappassword] = "bar" Puppet::Util::Ldap::Connection.expects(:new).with { |host, port, options| options[:user] == "foo" and options[:password] == "bar" } Puppet::Util::Ldap::Connection.instance end end end puppet-5.5.10/spec/unit/util/ldap/generator_spec.rb0000644005276200011600000000267713417161721022161 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/ldap/generator' describe Puppet::Util::Ldap::Generator do before do @generator = Puppet::Util::Ldap::Generator.new(:uno) end it "should require a parameter name at initialization" do expect { Puppet::Util::Ldap::Generator.new }.to raise_error(ArgumentError, /wrong number of arguments/) end it "should always return its name as a string" do g = Puppet::Util::Ldap::Generator.new(:myname) expect(g.name).to eq("myname") end it "should provide a method for declaring the source parameter" do @generator.from(:dos) end it "should always return a set source as a string" do @generator.from(:dos) expect(@generator.source).to eq("dos") end it "should return the source as nil if there is no source" do expect(@generator.source).to be_nil end it "should return itself when declaring the source" do expect(@generator.from(:dos)).to equal(@generator) end it "should run the provided block when asked to generate the value" do @generator.with { "yayness" } expect(@generator.generate).to eq("yayness") end it "should pass in any provided value to the block" do @generator.with { |value| value.upcase } expect(@generator.generate("myval")).to eq("MYVAL") end it "should return itself when declaring the code used for generating" do expect(@generator.with { |value| value.upcase }).to equal(@generator) end end puppet-5.5.10/spec/unit/util/ldap/manager_spec.rb0000644005276200011600000005362513417161722021605 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/ldap/manager' # If the ldap classes aren't available, go ahead and # create some, so our tests will pass. unless defined?(LDAP::Mod) class LDAP LDAP_MOD_ADD = :adding LDAP_MOD_REPLACE = :replacing LDAP_MOD_DELETE = :deleting class ResultError < RuntimeError; end class Mod def initialize(*args) end end end end describe Puppet::Util::Ldap::Manager do before do @manager = Puppet::Util::Ldap::Manager.new end it "should return self when specifying objectclasses" do expect(@manager.manages(:one, :two)).to equal(@manager) end it "should allow specification of what objectclasses are managed" do expect(@manager.manages(:one, :two).objectclasses).to eq([:one, :two]) end it "should return self when specifying the relative base" do expect(@manager.at("yay")).to equal(@manager) end it "should allow specification of the relative base" do expect(@manager.at("yay").location).to eq("yay") end it "should return self when specifying the attribute map" do expect(@manager.maps(:one => :two)).to equal(@manager) end it "should allow specification of the rdn attribute" do expect(@manager.named_by(:uid).rdn).to eq(:uid) end it "should allow specification of the attribute map" do expect(@manager.maps(:one => :two).puppet2ldap).to eq({:one => :two}) end it "should have a no-op 'and' method that just returns self" do expect(@manager.and).to equal(@manager) end it "should allow specification of generated attributes" do expect(@manager.generates(:thing)).to be_instance_of(Puppet::Util::Ldap::Generator) end describe "when generating attributes" do before do @generator = stub 'generator', :source => "one", :name => "myparam" Puppet::Util::Ldap::Generator.stubs(:new).with(:myparam).returns @generator end it "should create a generator to do the parameter generation" do Puppet::Util::Ldap::Generator.expects(:new).with(:myparam).returns @generator @manager.generates(:myparam) end it "should return the generator from the :generates method" do expect(@manager.generates(:myparam)).to equal(@generator) end it "should not replace already present values" do @manager.generates(:myparam) attrs = {"myparam" => "testing"} @generator.expects(:generate).never @manager.generate attrs expect(attrs["myparam"]).to eq("testing") end it "should look for the parameter as a string, not a symbol" do @manager.generates(:myparam) @generator.expects(:generate).with("yay").returns %w{double yay} attrs = {"one" => "yay"} @manager.generate attrs expect(attrs["myparam"]).to eq(%w{double yay}) end it "should fail if a source is specified and no source value is not defined" do @manager.generates(:myparam) expect { @manager.generate "two" => "yay" }.to raise_error(ArgumentError) end it "should use the source value to generate the new value if a source attribute is specified" do @manager.generates(:myparam) @generator.expects(:generate).with("yay").returns %w{double yay} @manager.generate "one" => "yay" end it "should not pass in any value if no source attribute is specified" do @generator.stubs(:source).returns nil @manager.generates(:myparam) @generator.expects(:generate).with.returns %w{double yay} @manager.generate "one" => "yay" end it "should convert any results to arrays of strings if necessary" do @generator.expects(:generate).returns :test @manager.generates(:myparam) attrs = {"one" => "two"} @manager.generate(attrs) expect(attrs["myparam"]).to eq(["test"]) end it "should add the result to the passed-in attribute hash" do @generator.expects(:generate).returns %w{test} @manager.generates(:myparam) attrs = {"one" => "two"} @manager.generate(attrs) expect(attrs["myparam"]).to eq(%w{test}) end end it "should be considered invalid if it is missing a location" do @manager.manages :me @manager.maps :me => :you expect(@manager).not_to be_valid end it "should be considered invalid if it is missing an objectclass list" do @manager.maps :me => :you @manager.at "ou=yayness" expect(@manager).not_to be_valid end it "should be considered invalid if it is missing an attribute map" do @manager.manages :me @manager.at "ou=yayness" expect(@manager).not_to be_valid end it "should be considered valid if it has an attribute map, location, and objectclass list" do @manager.maps :me => :you @manager.manages :me @manager.at "ou=yayness" expect(@manager).to be_valid end it "should calculate an instance's dn using the :ldapbase setting and the relative base" do Puppet[:ldapbase] = "dc=testing" @manager.at "ou=mybase" expect(@manager.dn("me")).to eq("cn=me,ou=mybase,dc=testing") end it "should use the specified rdn when calculating an instance's dn" do Puppet[:ldapbase] = "dc=testing" @manager.named_by :uid @manager.at "ou=mybase" expect(@manager.dn("me")).to match(/^uid=me/) end it "should calculate its base using the :ldapbase setting and the relative base" do Puppet[:ldapbase] = "dc=testing" @manager.at "ou=mybase" expect(@manager.base).to eq("ou=mybase,dc=testing") end describe "when generating its search filter" do it "should using a single 'objectclass=' filter if a single objectclass is specified" do @manager.manages("testing") expect(@manager.filter).to eq("objectclass=testing") end it "should create an LDAP AND filter if multiple objectclasses are specified" do @manager.manages "testing", "okay", "done" expect(@manager.filter).to eq("(&(objectclass=testing)(objectclass=okay)(objectclass=done))") end end it "should have a method for converting a Puppet attribute name to an LDAP attribute name as a string" do @manager.maps :puppet_attr => :ldap_attr expect(@manager.ldap_name(:puppet_attr)).to eq("ldap_attr") end it "should have a method for converting an LDAP attribute name to a Puppet attribute name" do @manager.maps :puppet_attr => :ldap_attr expect(@manager.puppet_name(:ldap_attr)).to eq(:puppet_attr) end it "should have a :create method for creating ldap entries" do expect(@manager).to respond_to(:create) end it "should have a :delete method for deleting ldap entries" do expect(@manager).to respond_to(:delete) end it "should have a :modify method for modifying ldap entries" do expect(@manager).to respond_to(:modify) end it "should have a method for finding an entry by name in ldap" do expect(@manager).to respond_to(:find) end describe "when converting ldap entries to hashes for providers" do before do @manager.maps :uno => :one, :dos => :two @result = @manager.entry2provider("dn" => ["cn=one,ou=people,dc=madstop"], "one" => ["two"], "three" => %w{four}, "objectclass" => %w{yay ness}) end it "should set the name to the short portion of the dn" do expect(@result[:name]).to eq("one") end it "should remove the objectclasses" do expect(@result["objectclass"]).to be_nil end it "should remove any attributes that are not mentioned in the map" do expect(@result["three"]).to be_nil end it "should rename convert to symbols all attributes to their puppet names" do expect(@result[:uno]).to eq(%w{two}) end it "should set the value of all unset puppet attributes as :absent" do expect(@result[:dos]).to eq(:absent) end end describe "when using an ldap connection" do before do @ldapconn = mock 'ldapconn' @conn = stub 'connection', :connection => @ldapconn, :start => nil, :close => nil Puppet::Util::Ldap::Connection.stubs(:new).returns(@conn) end it "should fail unless a block is given" do expect { @manager.connect }.to raise_error(ArgumentError, /must pass a block/) end it "should open the connection with its server set to :ldapserver" do Puppet[:ldapserver] = "myserver" Puppet::Util::Ldap::Connection.expects(:new).with { |*args| args[0] == "myserver" }.returns @conn @manager.connect { |c| } end it "should open the connection with its port set to the :ldapport" do Puppet[:ldapport] = "28" Puppet::Util::Ldap::Connection.expects(:new).with { |*args| args[1] == "28" }.returns @conn @manager.connect { |c| } end it "should open the connection with no user if :ldapuser is not set" do Puppet[:ldapuser] = "" Puppet::Util::Ldap::Connection.expects(:new).with { |*args| args[2][:user].nil? }.returns @conn @manager.connect { |c| } end it "should open the connection with its user set to the :ldapuser if it is set" do Puppet[:ldapuser] = "mypass" Puppet::Util::Ldap::Connection.expects(:new).with { |*args| args[2][:user] == "mypass" }.returns @conn @manager.connect { |c| } end it "should open the connection with no password if :ldappassword is not set" do Puppet[:ldappassword] = "" Puppet::Util::Ldap::Connection.expects(:new).with { |*args| args[2][:password].nil? }.returns @conn @manager.connect { |c| } end it "should open the connection with its password set to the :ldappassword if it is set" do Puppet[:ldappassword] = "mypass" Puppet::Util::Ldap::Connection.expects(:new).with { |*args| args[2][:password] == "mypass" }.returns @conn @manager.connect { |c| } end it "should set ssl to :tls if ldaptls is enabled" do Puppet[:ldaptls] = true Puppet::Util::Ldap::Connection.expects(:new).with { |*args| args[2][:ssl] == :tls }.returns @conn @manager.connect { |c| } end it "should set ssl to true if ldapssl is enabled" do Puppet[:ldapssl] = true Puppet::Util::Ldap::Connection.expects(:new).with { |*args| args[2][:ssl] == true }.returns @conn @manager.connect { |c| } end it "should set ssl to false if neither ldaptls nor ldapssl is enabled" do Puppet[:ldapssl] = false Puppet::Util::Ldap::Connection.expects(:new).with { |*args| args[2][:ssl] == false }.returns @conn @manager.connect { |c| } end it "should open, yield, and then close the connection" do @conn.expects(:start) @conn.expects(:close) Puppet::Util::Ldap::Connection.expects(:new).returns(@conn) @ldapconn.expects(:test) @manager.connect { |c| c.test } end it "should close the connection even if there's an exception in the passed block" do @conn.expects(:close) expect { @manager.connect { |c| raise ArgumentError } }.to raise_error(ArgumentError) end end describe "when using ldap" do before do @conn = mock 'connection' @manager.stubs(:connect).yields @conn @manager.stubs(:objectclasses).returns [:oc1, :oc2] @manager.maps :one => :uno, :two => :dos, :three => :tres, :four => :quatro end describe "to create entries" do it "should convert the first argument to its :create method to a full dn and pass the resulting argument list to its connection" do @manager.expects(:dn).with("myname").returns "mydn" @conn.expects(:add).with { |name, attrs| name == "mydn" } @manager.create("myname", {"attr" => "myattrs"}) end it "should add the objectclasses to the attributes" do @manager.expects(:dn).with("myname").returns "mydn" @conn.expects(:add).with { |name, attrs| attrs["objectClass"].include?("oc1") and attrs["objectClass"].include?("oc2") } @manager.create("myname", {:one => :testing}) end it "should add the rdn to the attributes" do @manager.expects(:dn).with("myname").returns "mydn" @conn.expects(:add).with { |name, attrs| attrs["cn"] == %w{myname} } @manager.create("myname", {:one => :testing}) end it "should add 'top' to the objectclasses if it is not listed" do @manager.expects(:dn).with("myname").returns "mydn" @conn.expects(:add).with { |name, attrs| attrs["objectClass"].include?("top") } @manager.create("myname", {:one => :testing}) end it "should add any generated values that are defined" do generator = stub 'generator', :source => :one, :name => "myparam" Puppet::Util::Ldap::Generator.expects(:new).with(:myparam).returns generator @manager.generates(:myparam) @manager.stubs(:dn).with("myname").returns "mydn" generator.expects(:generate).with(:testing).returns ["generated value"] @conn.expects(:add).with { |name, attrs| attrs["myparam"] == ["generated value"] } @manager.create("myname", {:one => :testing}) end it "should convert any generated values to arrays of strings if necessary" do generator = stub 'generator', :source => :one, :name => "myparam" Puppet::Util::Ldap::Generator.expects(:new).with(:myparam).returns generator @manager.generates(:myparam) @manager.stubs(:dn).returns "mydn" generator.expects(:generate).returns :generated @conn.expects(:add).with { |name, attrs| attrs["myparam"] == ["generated"] } @manager.create("myname", {:one => :testing}) end end describe "do delete entries" do it "should convert the first argument to its :delete method to a full dn and pass the resulting argument list to its connection" do @manager.expects(:dn).with("myname").returns "mydn" @conn.expects(:delete).with("mydn") @manager.delete("myname") end end describe "to modify entries" do it "should convert the first argument to its :modify method to a full dn and pass the resulting argument list to its connection" do @manager.expects(:dn).with("myname").returns "mydn" @conn.expects(:modify).with("mydn", :mymods) @manager.modify("myname", :mymods) end end describe "to find a single entry" do it "should use the dn of the provided name as the search base, a scope of 0, and 'objectclass=*' as the filter for a search2 call" do @manager.expects(:dn).with("myname").returns "mydn" @conn.expects(:search2).with("mydn", 0, "objectclass=*") @manager.find("myname") end it "should return nil if an exception is thrown because no result is found" do @manager.expects(:dn).with("myname").returns "mydn" @conn.expects(:search2).raises LDAP::ResultError expect(@manager.find("myname")).to be_nil end it "should return a converted provider hash if the result is found" do @manager.expects(:dn).with("myname").returns "mydn" result = {"one" => "two"} @conn.expects(:search2).yields result @manager.expects(:entry2provider).with(result).returns "myprovider" expect(@manager.find("myname")).to eq("myprovider") end end describe "to search for multiple entries" do before do @manager.stubs(:filter).returns "myfilter" end it "should use the manager's search base as the dn of the provided name as the search base" do @manager.expects(:base).returns "mybase" @conn.expects(:search2).with { |base, scope, filter| base == "mybase" } @manager.search end it "should use a scope of 1" do @conn.expects(:search2).with { |base, scope, filter| scope == 1 } @manager.search end it "should use any specified search filter" do @manager.expects(:filter).never @conn.expects(:search2).with { |base, scope, filter| filter == "boo" } @manager.search("boo") end it "should turn its objectclass list into its search filter if one is not specified" do @manager.expects(:filter).returns "yay" @conn.expects(:search2).with { |base, scope, filter| filter == "yay" } @manager.search end it "should return nil if no result is found" do @conn.expects(:search2) expect(@manager.search).to be_nil end it "should return an array of the found results converted to provider hashes" do # LAK: AFAICT, it's impossible to yield multiple times in an expectation. one = {"dn" => "cn=one,dc=madstop,dc=com", "one" => "two"} @conn.expects(:search2).yields(one) @manager.expects(:entry2provider).with(one).returns "myprov" expect(@manager.search).to eq(["myprov"]) end end end describe "when an instance" do before do @name = "myname" @manager.maps :one => :uno, :two => :dos, :three => :tres, :four => :quatro end describe "is being updated" do it "should get created if the current attribute list is empty and the desired attribute list has :ensure == :present" do @manager.expects(:create) @manager.update(@name, {}, {:ensure => :present}) end it "should get created if the current attribute list has :ensure == :absent and the desired attribute list has :ensure == :present" do @manager.expects(:create) @manager.update(@name, {:ensure => :absent}, {:ensure => :present}) end it "should get deleted if the current attribute list has :ensure == :present and the desired attribute list has :ensure == :absent" do @manager.expects(:delete) @manager.update(@name, {:ensure => :present}, {:ensure => :absent}) end it "should get modified if both attribute lists have :ensure == :present" do @manager.expects(:modify) @manager.update(@name, {:ensure => :present, :one => :two}, {:ensure => :present, :one => :three}) end end describe "is being deleted" do it "should call the :delete method with its name and manager" do @manager.expects(:delete).with(@name) @manager.update(@name, {}, {:ensure => :absent}) end end describe "is being created" do before do @is = {} @should = {:ensure => :present, :one => :yay, :two => :absent} end it "should call the :create method with its name" do @manager.expects(:create).with { |name, attrs| name == @name } @manager.update(@name, @is, @should) end it "should call the :create method with its property hash converted to ldap attribute names" do @manager.expects(:create).with { |name, attrs| attrs["uno"] == ["yay"] } @manager.update(@name, @is, @should) end it "should convert the property names to strings" do @manager.expects(:create).with { |name, attrs| attrs["uno"] == ["yay"] } @manager.update(@name, @is, @should) end it "should convert the property values to arrays if necessary" do @manager.expects(:create).with { |name, attrs| attrs["uno"] == ["yay"] } @manager.update(@name, @is, @should) end it "should convert the property values to strings if necessary" do @manager.expects(:create).with { |name, attrs| attrs["uno"] == ["yay"] } @manager.update(@name, @is, @should) end it "should not include :ensure in the properties sent" do @manager.expects(:create).with { |*args| args[1][:ensure].nil? } @manager.update(@name, @is, @should) end it "should not include attributes set to :absent in the properties sent" do @manager.expects(:create).with { |*args| args[1][:dos].nil? } @manager.update(@name, @is, @should) end end describe "is being modified" do it "should call the :modify method with its name and an array of LDAP::Mod instances" do LDAP::Mod.stubs(:new).returns "whatever" @is = {:one => :yay} @should = {:one => :yay, :two => :foo} @manager.expects(:modify).with { |name, mods| name == @name } @manager.update(@name, @is, @should) end it "should create the LDAP::Mod with the property name converted to the ldap name as a string" do @is = {:one => :yay} @should = {:one => :yay, :two => :foo} mod = mock 'module' LDAP::Mod.expects(:new).with { |form, name, value| name == "dos" }.returns mod @manager.stubs(:modify) @manager.update(@name, @is, @should) end it "should create an LDAP::Mod instance of type LDAP_MOD_ADD for each attribute being added, with the attribute value converted to a string of arrays" do @is = {:one => :yay} @should = {:one => :yay, :two => :foo} mod = mock 'module' LDAP::Mod.expects(:new).with(LDAP::LDAP_MOD_ADD, "dos", ["foo"]).returns mod @manager.stubs(:modify) @manager.update(@name, @is, @should) end it "should create an LDAP::Mod instance of type LDAP_MOD_DELETE for each attribute being deleted" do @is = {:one => :yay, :two => :foo} @should = {:one => :yay, :two => :absent} mod = mock 'module' LDAP::Mod.expects(:new).with(LDAP::LDAP_MOD_DELETE, "dos", []).returns mod @manager.stubs(:modify) @manager.update(@name, @is, @should) end it "should create an LDAP::Mod instance of type LDAP_MOD_REPLACE for each attribute being modified, with the attribute converted to a string of arrays" do @is = {:one => :yay, :two => :four} @should = {:one => :yay, :two => :five} mod = mock 'module' LDAP::Mod.expects(:new).with(LDAP::LDAP_MOD_REPLACE, "dos", ["five"]).returns mod @manager.stubs(:modify) @manager.update(@name, @is, @should) end it "should pass all created Mod instances to the modify method" do @is = {:one => :yay, :two => :foo, :three => :absent} @should = {:one => :yay, :two => :foe, :three => :fee, :four => :fie} LDAP::Mod.expects(:new).times(3).returns("mod1").then.returns("mod2").then.returns("mod3") @manager.expects(:modify).with do |name, mods| mods.sort == %w{mod1 mod2 mod3}.sort end @manager.update(@name, @is, @should) end end end end puppet-5.5.10/spec/unit/util/log/0000755005276200011600000000000013417162177016467 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/util/log/destinations_spec.rb0000644005276200011600000002051313417161722022526 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/json' require 'puppet/util/log' describe Puppet::Util::Log.desttypes[:report] do before do @dest = Puppet::Util::Log.desttypes[:report] end it "should require a report at initialization" do expect(@dest.new("foo").report).to eq("foo") end it "should send new messages to the report" do report = mock 'report' dest = @dest.new(report) report.expects(:<<).with("my log") dest.handle "my log" end end describe Puppet::Util::Log.desttypes[:file] do include PuppetSpec::Files before do @class = Puppet::Util::Log.desttypes[:file] end it "should default to autoflush false" do expect(@class.new(tmpfile('log')).autoflush).to eq(true) end describe "when matching" do shared_examples_for "file destination" do it "should match an absolute path" do expect(@class.match?(abspath)).to be_truthy end it "should not match a relative path" do expect(@class.match?(relpath)).to be_falsey end end describe "on POSIX systems", :if => Puppet.features.posix? do describe "with a normal file" do let (:abspath) { '/tmp/log' } let (:relpath) { 'log' } it_behaves_like "file destination" it "logs an error if it can't chown the file owner & group" do File.expects(:exists?).with(abspath).returns(false) FileUtils.expects(:chown).with(Puppet[:user], Puppet[:group], abspath).raises(Errno::EPERM) Puppet.features.expects(:root?).returns(true) Puppet.expects(:err).with("Unable to set ownership to #{Puppet[:user]}:#{Puppet[:group]} for log file: #{abspath}") @class.new(abspath) end it "doesn't attempt to chown when running as non-root" do File.expects(:exists?).with(abspath).returns(false) FileUtils.expects(:chown).with(Puppet[:user], Puppet[:group], abspath).never Puppet.features.expects(:root?).returns(false) @class.new(abspath) end it "doesn't attempt to chown when file already exists" do File.expects(:exists?).with(abspath).returns(true) FileUtils.expects(:chown).with(Puppet[:user], Puppet[:group], abspath).never Puppet.features.expects(:root?).returns(true) @class.new(abspath) end end describe "with a JSON file" do let (:abspath) { '/tmp/log.json' } let (:relpath) { 'log.json' } it_behaves_like "file destination" it "should log messages as JSON" do msg = Puppet::Util::Log.new(:level => :info, :message => "don't panic") dest = @class.new(abspath) dest.handle(msg) expect(JSON.parse(File.read(abspath) + ']')).to include(a_hash_including({"message" => "don't panic"})) end end end describe "on Windows systems", :if => Puppet.features.microsoft_windows? do let (:abspath) { 'C:\\temp\\log.txt' } let (:relpath) { 'log.txt' } it_behaves_like "file destination" end end end describe Puppet::Util::Log.desttypes[:syslog] do let (:klass) { Puppet::Util::Log.desttypes[:syslog] } # these tests can only be run when syslog is present, because # we can't stub the top-level Syslog module describe "when syslog is available", :if => Puppet.features.syslog? do before :each do Syslog.stubs(:opened?).returns(false) Syslog.stubs(:const_get).returns("LOG_KERN").returns(0) Syslog.stubs(:open) end it "should open syslog" do Syslog.expects(:open) klass.new end it "should close syslog" do Syslog.expects(:close) dest = klass.new dest.close end it "should send messages to syslog" do syslog = mock 'syslog' syslog.expects(:info).with("don't panic") Syslog.stubs(:open).returns(syslog) msg = Puppet::Util::Log.new(:level => :info, :message => "don't panic") dest = klass.new dest.handle(msg) end end describe "when syslog is unavailable" do it "should not be a suitable log destination" do Puppet.features.stubs(:syslog?).returns(false) expect(klass.suitable?(:syslog)).to be_falsey end end end describe Puppet::Util::Log.desttypes[:logstash_event] do describe "when using structured log format with logstash_event schema" do before :each do @msg = Puppet::Util::Log.new(:level => :info, :message => "So long, and thanks for all the fish.", :source => "a dolphin") end it "format should fix the hash to have the correct structure" do dest = described_class.new result = dest.format(@msg) expect(result["version"]).to eq(1) expect(result["level"]).to eq('info') expect(result["message"]).to eq("So long, and thanks for all the fish.") expect(result["source"]).to eq("a dolphin") # timestamp should be within 10 seconds expect(Time.parse(result["@timestamp"])).to be >= ( Time.now - 10 ) end it "format returns a structure that can be converted to json" do dest = described_class.new hash = dest.format(@msg) Puppet::Util::Json.load(hash.to_json) end it "handle should send the output to stdout" do $stdout.expects(:puts).once dest = described_class.new dest.handle(@msg) end end end describe Puppet::Util::Log.desttypes[:console] do let (:klass) { Puppet::Util::Log.desttypes[:console] } it "should support color output" do Puppet[:color] = true expect(subject.colorize(:red, 'version')).to eq("\e[0;31mversion\e[0m") end it "should withhold color output when not appropriate" do Puppet[:color] = false expect(subject.colorize(:red, 'version')).to eq("version") end it "should handle multiple overlapping colors in a stack-like way" do Puppet[:color] = true vstring = subject.colorize(:red, 'version') expect(subject.colorize(:green, "(#{vstring})")).to eq("\e[0;32m(\e[0;31mversion\e[0;32m)\e[0m") end it "should handle resets in a stack-like way" do Puppet[:color] = true vstring = subject.colorize(:reset, 'version') expect(subject.colorize(:green, "(#{vstring})")).to eq("\e[0;32m(\e[mversion\e[0;32m)\e[0m") end it "should include the log message's source/context in the output when available" do Puppet[:color] = false $stdout.expects(:puts).with("Info: a hitchhiker: don't panic") msg = Puppet::Util::Log.new(:level => :info, :message => "don't panic", :source => "a hitchhiker") dest = klass.new dest.handle(msg) end end describe ":eventlog", :if => Puppet::Util::Platform.windows? do let(:klass) { Puppet::Util::Log.desttypes[:eventlog] } def expects_message_with_type(klass, level, eventlog_type, eventlog_id) eventlog = stub('eventlog') eventlog.expects(:report_event).with(has_entries(:event_type => eventlog_type, :event_id => eventlog_id, :data => "a hitchhiker: don't panic")) Puppet::Util::Windows::EventLog.stubs(:open).returns(eventlog) msg = Puppet::Util::Log.new(:level => level, :message => "don't panic", :source => "a hitchhiker") dest = klass.new dest.handle(msg) end it "supports the eventlog feature" do expect(Puppet.features.eventlog?).to be_truthy end it "should truncate extremely long log messages" do long_msg = "x" * 32000 expected_truncated_msg = "#{'x' * 31785}...Message exceeds character length limit, truncating." expected_data = "a vogon ship: " + expected_truncated_msg eventlog = stub('eventlog') eventlog.expects(:report_event).with(has_entries(:event_type => 2, :event_id => 2, :data => expected_data)) msg = Puppet::Util::Log.new(:level => :warning, :message => long_msg, :source => "a vogon ship") Puppet::Util::Windows::EventLog.stubs(:open).returns(eventlog) dest = klass.new dest.handle(msg) end it "logs to the Puppet Application event log" do Puppet::Util::Windows::EventLog.expects(:open).with('Puppet').returns(stub('eventlog')) klass.new end it "logs :debug level as an information type event" do expects_message_with_type(klass, :debug, klass::EVENTLOG_INFORMATION_TYPE, 0x1) end it "logs :warning level as an warning type event" do expects_message_with_type(klass, :warning, klass::EVENTLOG_WARNING_TYPE, 0x2) end it "logs :err level as an error type event" do expects_message_with_type(klass, :err, klass::EVENTLOG_ERROR_TYPE, 0x3) end end puppet-5.5.10/spec/unit/util/metric_spec.rb0000644005276200011600000000502513417161721020524 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/metric' describe Puppet::Util::Metric do before do @metric = Puppet::Util::Metric.new("foo") end [:type, :name, :value, :label].each do |name| it "should have a #{name} attribute" do expect(@metric).to respond_to(name) expect(@metric).to respond_to(name.to_s + "=") end end it "should require a name at initialization" do expect { Puppet::Util::Metric.new }.to raise_error(ArgumentError) end it "should always convert its name to a string" do expect(Puppet::Util::Metric.new(:foo).name).to eq("foo") end it "should support a label" do expect(Puppet::Util::Metric.new("foo", "mylabel").label).to eq("mylabel") end it "should autogenerate a label if none is provided" do expect(Puppet::Util::Metric.new("foo_bar").label).to eq("Foo bar") end it "should have a method for adding values" do expect(@metric).to respond_to(:newvalue) end it "should have a method for returning values" do expect(@metric).to respond_to(:values) end it "should require a name and value for its values" do expect { @metric.newvalue }.to raise_error(ArgumentError) end it "should support a label for values" do @metric.newvalue("foo", 10, "label") expect(@metric.values[0][1]).to eq("label") end it "should autogenerate value labels if none is provided" do @metric.newvalue("foo_bar", 10) expect(@metric.values[0][1]).to eq("Foo bar") end it "should return its values sorted by label" do @metric.newvalue("foo", 10, "b") @metric.newvalue("bar", 10, "a") expect(@metric.values).to eq([["bar", "a", 10], ["foo", "b", 10]]) end it "should use an array indexer method to retrieve individual values" do @metric.newvalue("foo", 10) expect(@metric["foo"]).to eq(10) end it "should return nil if the named value cannot be found" do expect(@metric["foo"]).to eq(0) end let(:metric) do metric = Puppet::Util::Metric.new("foo", "mylabel") metric.newvalue("v1", 10.1, "something") metric.newvalue("v2", 20, "something else") metric end it "should round trip through json" do tripped = Puppet::Util::Metric.from_data_hash(JSON.parse(metric.to_json)) expect(tripped.name).to eq(metric.name) expect(tripped.label).to eq(metric.label) expect(tripped.values).to eq(metric.values) end it 'to_data_hash returns value that is instance of to Data' do expect(Puppet::Pops::Types::TypeFactory.data.instance?(metric.to_data_hash)).to be_truthy end end puppet-5.5.10/spec/unit/util/multi_match_spec.rb0000644005276200011600000000215013417161721021543 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/multi_match' describe "The Puppet::Util::MultiMatch" do let(:not_nil) { Puppet::Util::MultiMatch::NOT_NIL } let(:mm) { Puppet::Util::MultiMatch } it "matches against not nil" do expect(not_nil === 3).to be(true) end it "matches against multiple values" do expect(mm.new(not_nil, not_nil) === [3, 3]).to be(true) end it "matches each value using ===" do expect(mm.new(3, 3.14) === [Integer, Float]).to be(true) end it "matches are commutative" do expect(mm.new(3, 3.14) === mm.new(Integer, Float)).to be(true) expect(mm.new(Integer, Float) === mm.new(3, 3.14)).to be(true) end it "has TUPLE constant for match of array of two non nil values" do expect(mm::TUPLE === [3, 3]).to be(true) end it "has TRIPLE constant for match of array of two non nil values" do expect(mm::TRIPLE === [3, 3, 3]).to be(true) end it "considers length of array of values when matching" do expect(mm.new(not_nil, not_nil) === [6, 6, 6]).to be(false) expect(mm.new(not_nil, not_nil) === [6]).to be(false) end end puppet-5.5.10/spec/unit/util/network_device/0000755005276200011600000000000013417162177020716 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/util/network_device/config_spec.rb0000644005276200011600000000533413417161721023521 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/network_device/config' describe Puppet::Util::NetworkDevice::Config do include PuppetSpec::Files before(:each) do Puppet[:deviceconfig] = tmpfile('deviceconfig') end describe "when parsing device" do let(:config) { Puppet::Util::NetworkDevice::Config.new } def write_device_config(*lines) File.open(Puppet[:deviceconfig], 'w') {|f| f.puts lines} end it "should skip comments" do write_device_config(' # comment') expect(config.devices).to be_empty end it "should increment line number even on commented lines" do write_device_config(' # comment','[router.puppetlabs.com]') expect(config.devices).to be_include('router.puppetlabs.com') end it "should skip blank lines" do write_device_config(' ') expect(config.devices).to be_empty end it "should produce the correct line number" do write_device_config(' ', '[router.puppetlabs.com]') expect(config.devices['router.puppetlabs.com'].line).to eq(2) end it "should throw an error if the current device already exists" do write_device_config('[router.puppetlabs.com]', '[router.puppetlabs.com]') end it "should accept device certname containing dashes" do write_device_config('[router-1.puppetlabs.com]') expect(config.devices).to include('router-1.puppetlabs.com') end it "should create a new device for each found device line" do write_device_config('[router.puppetlabs.com]', '[swith.puppetlabs.com]') expect(config.devices.size).to eq(2) end it "should parse the device type" do write_device_config('[router.puppetlabs.com]', 'type cisco') expect(config.devices['router.puppetlabs.com'].provider).to eq('cisco') end it "should parse the device url" do write_device_config('[router.puppetlabs.com]', 'type cisco', 'url ssh://test/') expect(config.devices['router.puppetlabs.com'].url).to eq('ssh://test/') end it "should error with a malformed device url" do write_device_config('[router.puppetlabs.com]', 'type cisco', 'url ssh://test node/') expect { config.devices['router.puppetlabs.com'] }.to raise_error Puppet::Error end it "should parse the debug mode" do write_device_config('[router.puppetlabs.com]', 'type cisco', 'url ssh://test/', 'debug') expect(config.devices['router.puppetlabs.com'].options).to eq({ :debug => true }) end it "should set the debug mode to false by default" do write_device_config('[router.puppetlabs.com]', 'type cisco', 'url ssh://test/') expect(config.devices['router.puppetlabs.com'].options).to eq({ :debug => false }) end end end puppet-5.5.10/spec/unit/util/network_device/transport/0000755005276200011600000000000013417162177022752 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/util/network_device/transport/base_spec.rb0000644005276200011600000000230613417161721025216 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/network_device/transport/base' describe Puppet::Util::NetworkDevice::Transport::Base do class TestTransport < Puppet::Util::NetworkDevice::Transport::Base end before(:each) do @transport = TestTransport.new end describe "when sending commands" do it "should send the command to the telnet session" do @transport.expects(:send).with("line") @transport.command("line") end it "should expect an output matching the given prompt" do @transport.expects(:expect).with(/prompt/) @transport.command("line", :prompt => /prompt/) end it "should expect an output matching the default prompt" do @transport.default_prompt = /defprompt/ @transport.expects(:expect).with(/defprompt/) @transport.command("line") end it "should yield telnet output to the given block" do @transport.expects(:expect).yields("output") @transport.command("line") { |out| expect(out).to eq("output") } end it "should return telnet output to the caller" do @transport.expects(:expect).returns("output") expect(@transport.command("line")).to eq("output") end end end puppet-5.5.10/spec/unit/util/network_device/transport/ssh_spec.rb0000644005276200011600000001473613417161722025114 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/network_device/transport/ssh' describe Puppet::Util::NetworkDevice::Transport::Ssh, :if => Puppet.features.ssh? do before(:each) do @transport = Puppet::Util::NetworkDevice::Transport::Ssh.new() end it "should handle login through the transport" do expect(@transport).to be_handles_login end it "should connect to the given host and port" do Net::SSH.expects(:start).with { |host, user, args| host == "localhost" && args[:port] == 22 }.returns stub_everything @transport.host = "localhost" @transport.port = 22 @transport.connect end it "should connect using the given username and password" do Net::SSH.expects(:start).with { |host, user, args| user == "user" && args[:password] == "pass" }.returns stub_everything @transport.user = "user" @transport.password = "pass" @transport.connect end it "should raise a Puppet::Error when encountering an authentication failure" do Net::SSH.expects(:start).raises Net::SSH::AuthenticationFailed @transport.host = "localhost" @transport.user = "user" expect { @transport.connect }.to raise_error Puppet::Error end describe "when connected" do before(:each) do @ssh = stub_everything 'ssh' @channel = stub_everything 'channel' Net::SSH.stubs(:start).returns @ssh @ssh.stubs(:open_channel).yields(@channel) @transport.stubs(:expect) end it "should open a channel" do @ssh.expects(:open_channel) @transport.connect end it "should request a pty" do @channel.expects(:request_pty) @transport.connect end it "should create a shell channel" do @channel.expects(:send_channel_request).with("shell") @transport.connect end it "should raise an error if shell channel creation fails" do @channel.expects(:send_channel_request).with("shell").yields(@channel, false) expect { @transport.connect }.to raise_error(RuntimeError, /failed to open ssh shell channel/) end it "should register an on_data and on_extended_data callback" do @channel.expects(:send_channel_request).with("shell").yields(@channel, true) @channel.expects(:on_data) @channel.expects(:on_extended_data) @transport.connect end it "should accumulate data to the buffer on data" do @channel.expects(:send_channel_request).with("shell").yields(@channel, true) @channel.expects(:on_data).yields(@channel, "data") @transport.connect expect(@transport.buf).to eq("data") end it "should accumulate data to the buffer on extended data" do @channel.expects(:send_channel_request).with("shell").yields(@channel, true) @channel.expects(:on_extended_data).yields(@channel, 1, "data") @transport.connect expect(@transport.buf).to eq("data") end it "should mark eof on close" do @channel.expects(:send_channel_request).with("shell").yields(@channel, true) @channel.expects(:on_close).yields(@channel) @transport.connect expect(@transport).to be_eof end it "should expect output to conform to the default prompt" do @channel.expects(:send_channel_request).with("shell").yields(@channel, true) @transport.expects(:default_prompt).returns("prompt") @transport.expects(:expect).with("prompt") @transport.connect end it "should start the ssh loop" do @ssh.expects(:loop) @transport.connect end end describe "when closing" do before(:each) do @ssh = stub_everything 'ssh' @channel = stub_everything 'channel' Net::SSH.stubs(:start).returns @ssh @ssh.stubs(:open_channel).yields(@channel) @channel.stubs(:send_channel_request).with("shell").yields(@channel, true) @transport.stubs(:expect) @transport.connect end it "should close the channel" do @channel.expects(:close) @transport.close end it "should close the ssh session" do @ssh.expects(:close) @transport.close end end describe "when sending commands" do before(:each) do @ssh = stub_everything 'ssh' @channel = stub_everything 'channel' Net::SSH.stubs(:start).returns @ssh @ssh.stubs(:open_channel).yields(@channel) @channel.stubs(:send_channel_request).with("shell").yields(@channel, true) @transport.stubs(:expect) @transport.connect end it "should send data to the ssh channel" do @channel.expects(:send_data).with("data\n") @transport.command("data") end it "should expect the default prompt afterward" do @transport.expects(:default_prompt).returns("prompt") @transport.expects(:expect).with("prompt") @transport.command("data") end it "should expect the given prompt" do @transport.expects(:expect).with("myprompt") @transport.command("data", :prompt => "myprompt") end it "should yield the buffer output to given block" do @transport.expects(:expect).yields("output") @transport.command("data") do |out| expect(out).to eq("output") end end it "should return buffer output" do @transport.expects(:expect).returns("output") expect(@transport.command("data")).to eq("output") end end describe "when expecting output" do before(:each) do @connection = stub_everything 'connection' @socket = stub_everything 'socket' transport = stub 'transport', :socket => @socket @ssh = stub_everything 'ssh', :transport => transport @channel = stub_everything 'channel', :connection => @connection @transport.ssh = @ssh @transport.channel = @channel end it "should process the ssh event loop" do IO.stubs(:select) @transport.buf = "output" @transport.expects(:process_ssh) @transport.expect(/output/) end it "should return the output" do IO.stubs(:select) @transport.buf = "output" @transport.stubs(:process_ssh) expect(@transport.expect(/output/)).to eq("output") end it "should return the output" do IO.stubs(:select) @transport.buf = "output" @transport.stubs(:process_ssh) expect(@transport.expect(/output/)).to eq("output") end describe "when processing the ssh loop" do it "should advance one tick in the ssh event loop and exit on eof" do @transport.buf = '' @connection.expects(:process).then.raises(EOFError) @transport.process_ssh end end end end puppet-5.5.10/spec/unit/util/network_device/transport/telnet_spec.rb0000644005276200011600000000463413417161722025606 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' if Puppet.features.telnet? require 'puppet/util/network_device/transport/telnet' describe Puppet::Util::NetworkDevice::Transport::Telnet do before(:each) do TCPSocket.stubs(:open).returns stub_everything('tcp') @transport = Puppet::Util::NetworkDevice::Transport::Telnet.new() end it "should not handle login through the transport" do expect(@transport).not_to be_handles_login end it "should not open any files" do File.expects(:open).never @transport.host = "localhost" @transport.port = 23 @transport.connect end it "should connect to the given host and port" do Net::Telnet.expects(:new).with { |args| args["Host"] == "localhost" && args["Port"] == 23 }.returns stub_everything @transport.host = "localhost" @transport.port = 23 @transport.connect end it "should connect and specify the default prompt" do @transport.default_prompt = "prompt" Net::Telnet.expects(:new).with { |args| args["Prompt"] == "prompt" }.returns stub_everything @transport.host = "localhost" @transport.port = 23 @transport.connect end describe "when connected" do before(:each) do @telnet = stub_everything 'telnet' Net::Telnet.stubs(:new).returns(@telnet) @transport.connect end it "should send line to the telnet session" do @telnet.expects(:puts).with("line") @transport.send("line") end describe "when expecting output" do it "should waitfor output on the telnet session" do @telnet.expects(:waitfor).with(/regex/) @transport.expect(/regex/) end it "should return telnet session output" do @telnet.expects(:waitfor).returns("output") expect(@transport.expect(/regex/)).to eq("output") end it "should yield telnet session output to the given block" do @telnet.expects(:waitfor).yields("output") @transport.expect(/regex/) { |out| expect(out).to eq("output") } end end end describe "when closing" do before(:each) do @telnet = stub_everything 'telnet' Net::Telnet.stubs(:new).returns(@telnet) @transport.connect end it "should close the telnet session" do @telnet.expects(:close) @transport.close end end end end puppet-5.5.10/spec/unit/util/network_device/cisco/0000755005276200011600000000000013417162177022016 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/util/network_device/cisco/device_spec.rb0000644005276200011600000004424513417161722024620 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/network_device/cisco/device' require 'puppet/util/network_device/transport/telnet' if Puppet.features.telnet? describe Puppet::Util::NetworkDevice::Cisco::Device do before(:each) do @transport = stub_everything 'transport', :is_a? => true, :command => "" @cisco = Puppet::Util::NetworkDevice::Cisco::Device.new("telnet://user:password@localhost:23/") @cisco.transport = @transport end describe "when creating the device" do it "should find the enable password from the url" do cisco = Puppet::Util::NetworkDevice::Cisco::Device.new("telnet://user:password@localhost:23/?enable=enable_password") expect(cisco.enable_password).to eq("enable_password") end describe "decoding the enable password" do it "should not parse a password if no query is given" do cisco = described_class.new("telnet://user:password@localhost:23") expect(cisco.enable_password).to be_nil end it "should not parse a password if no enable param is given" do cisco = described_class.new("telnet://user:password@localhost:23/?notenable=notapassword") expect(cisco.enable_password).to be_nil end it "should decode sharps" do cisco = described_class.new("telnet://user:password@localhost:23/?enable=enable_password%23with_a_sharp") expect(cisco.enable_password).to eq("enable_password#with_a_sharp") end it "should decode spaces" do cisco = described_class.new("telnet://user:password@localhost:23/?enable=enable_password%20with_a_space") expect(cisco.enable_password).to eq("enable_password with_a_space") end it "should only use the query parameter" do cisco = described_class.new("telnet://enable=:password@localhost:23/?enable=enable_password¬enable=notapassword") expect(cisco.enable_password).to eq("enable_password") end end it "should find the enable password from the options" do cisco = Puppet::Util::NetworkDevice::Cisco::Device.new("telnet://user:password@localhost:23/?enable=enable_password", :enable_password => "mypass") expect(cisco.enable_password).to eq("mypass") end it "should find the debug mode from the options" do Puppet::Util::NetworkDevice::Transport::Telnet.expects(:new).with(true).returns(@transport) Puppet::Util::NetworkDevice::Cisco::Device.new("telnet://user:password@localhost:23", :debug => true) end it "should set the debug mode to nil by default" do Puppet::Util::NetworkDevice::Transport::Telnet.expects(:new).with(nil).returns(@transport) Puppet::Util::NetworkDevice::Cisco::Device.new("telnet://user:password@localhost:23") end end describe "when connecting to the physical device" do it "should connect to the transport" do @transport.expects(:connect) @cisco.command end it "should attempt to login" do @cisco.expects(:login) @cisco.command end it "should tell the device to not page" do @transport.expects(:command).with("terminal length 0") @cisco.command end it "should enter the enable password if returned prompt is not privileged" do @transport.stubs(:command).yields("Switch>").returns("") @cisco.expects(:enable) @cisco.command end it "should find device capabilities" do @cisco.expects(:find_capabilities) @cisco.command end it "should execute given command" do @transport.expects(:command).with("mycommand") @cisco.command("mycommand") end it "should yield to the command block if one is provided" do @transport.expects(:command).with("mycommand") @cisco.command do |c| c.command("mycommand") end end it "should close the device transport" do @transport.expects(:close) @cisco.command end describe "when login in" do it "should not login if transport handles login" do @transport.expects(:handles_login?).returns(true) @transport.expects(:command).never @transport.expects(:expect).never @cisco.login end it "should send username if one has been provided" do @transport.expects(:command).with("user", :prompt => /^Password:/) @cisco.login end it "should send password after the username" do @transport.expects(:command).with("user", :prompt => /^Password:/) @transport.expects(:command).with("password") @cisco.login end it "should expect the Password: prompt if no user was sent" do @cisco.url.user = '' @transport.expects(:expect).with(/^Password:/) @transport.expects(:command).with("password") @cisco.login end end describe "when entering enable password" do it "should raise an error if no enable password has been set" do @cisco.enable_password = nil expect{ @cisco.enable }.to raise_error(RuntimeError, /Can't issue "enable" to enter privileged/) end it "should send the enable command and expect an enable prompt" do @cisco.enable_password = 'mypass' @transport.expects(:command).with("enable", :prompt => /^Password:/) @cisco.enable end it "should send the enable password" do @cisco.enable_password = 'mypass' @transport.stubs(:command).with("enable", :prompt => /^Password:/) @transport.expects(:command).with("mypass") @cisco.enable end end end describe "when finding network device capabilities" do it "should try to execute sh vlan brief" do @transport.expects(:command).with("sh vlan brief").returns("") @cisco.find_capabilities end it "should detect errors" do @transport.stubs(:command).with("sh vlan brief").returns(< "FastEthernet0/1", "Fa0/1" => "FastEthernet0/1", "FastEth 0/1" => "FastEthernet0/1", "Gi1" => "GigabitEthernet1", "Te2" => "TenGigabitEthernet2", "Di9" => "Dialer9", "Ethernet 0/0/1" => "Ethernet0/0/1", "E0" => "Ethernet0", "ATM 0/1.1" => "ATM0/1.1", "VLAN99" => "VLAN99" }.each do |input,expected| it "should canonicalize #{input} to #{expected}" do expect(@cisco.canonicalize_ifname(input)).to eq(expected) end end describe "when updating device vlans" do describe "when removing a vlan" do it "should issue the no vlan command" do @transport.expects(:command).with("no vlan 200") @cisco.update_vlan("200", {:ensure => :present, :name => "200"}, { :ensure=> :absent}) end end describe "when updating a vlan" do it "should issue the vlan command to enter global vlan modifications" do @transport.expects(:command).with("vlan 200") @cisco.update_vlan("200", {:ensure => :present, :name => "200"}, { :ensure=> :present, :name => "200"}) end it "should issue the name command to modify the vlan description" do @transport.expects(:command).with("name myvlan") @cisco.update_vlan("200", {:ensure => :present, :name => "200"}, { :ensure=> :present, :name => "200", :description => "myvlan"}) end end end describe "when parsing interface" do it "should parse interface output" do @cisco.expects(:parse_interface).returns({ :ensure => :present }) expect(@cisco.interface("FastEthernet0/1")).to eq({ :ensure => :present }) end it "should parse trunking and merge results" do @cisco.stubs(:parse_interface).returns({ :ensure => :present }) @cisco.expects(:parse_trunking).returns({ :native_vlan => "100" }) expect(@cisco.interface("FastEthernet0/1")).to eq({ :ensure => :present, :native_vlan => "100" }) end it "should return an absent interface if parse_interface returns nothing" do @cisco.stubs(:parse_interface).returns({}) expect(@cisco.interface("FastEthernet0/1")).to eq({ :ensure => :absent }) end it "should parse ip address information and merge results" do @cisco.stubs(:parse_interface).returns({ :ensure => :present }) @cisco.expects(:parse_interface_config).returns({ :ipaddress => [24,IPAddr.new('192.168.0.24'), nil] }) expect(@cisco.interface("FastEthernet0/1")).to eq({ :ensure => :present, :ipaddress => [24,IPAddr.new('192.168.0.24'), nil] }) end it "should parse the sh interface command" do @transport.stubs(:command).with("sh interface FastEthernet0/1").returns(< :absent, :duplex => :auto, :speed => :auto }) end it "should be able to parse the sh vlan brief command output" do @cisco.stubs(:support_vlan_brief?).returns(true) @transport.stubs(:command).with("sh vlan brief").returns(<{:status=>"active", :interfaces=>["FastEthernet0/1", "FastEthernet0/2"], :description=>"management", :name=>"100"}, "1"=>{:status=>"active", :interfaces=>["FastEthernet0/3", "FastEthernet0/4", "FastEthernet0/5", "FastEthernet0/6", "FastEthernet0/7", "FastEthernet0/8", "FastEthernet0/9", "FastEthernet0/10", "FastEthernet0/11", "FastEthernet0/12", "FastEthernet0/13", "FastEthernet0/14", "FastEthernet0/15", "FastEthernet0/16", "FastEthernet0/17", "FastEthernet0/18", "FastEthernet0/23", "FastEthernet0/24"], :description=>"default", :name=>"1"}, "10"=>{:status=>"active", :interfaces=>[], :description=>"VLAN0010", :name=>"10"}}) end it "should parse trunk switchport information" do @transport.stubs(:command).with("sh interface FastEthernet0/21 switchport").returns(< :trunk, :encapsulation => :dot1q, :native_vlan => "1", :allowed_trunk_vlans=>:all, }) end it "should parse dynamic desirable switchport information with native and allowed vlans" do @transport.stubs(:command).with("sh interface GigabitEthernet 0/1 switchport").returns(< "dynamic desirable", :encapsulation => :dot1q, :access_vlan => "100", :native_vlan => "1", :allowed_trunk_vlans=>"1,99", }) end it "should parse access switchport information" do @transport.stubs(:command).with("sh interface FastEthernet0/1 switchport").returns(< :access, :access_vlan => "100", :native_vlan => "1" }) end it "should parse auto/negotiate switchport information" do @transport.stubs(:command).with("sh interface FastEthernet0/24 switchport").returns(< "dynamic auto", :encapsulation => :negotiate, :allowed_trunk_vlans => :all, :access_vlan => "1", :native_vlan => "2" }) end it "should parse ip addresses" do @transport.stubs(:command).with("sh running-config interface Vlan 1 | begin interface").returns(<[[24, IPAddr.new('192.168.0.24'), 'secondary'], [24, IPAddr.new('192.168.0.1'), nil], [64, IPAddr.new('2001:07a8:71c1::'), "eui-64"]]}) end it "should parse etherchannel membership" do @transport.stubs(:command).with("sh running-config interface Gi0/17 | begin interface").returns(<"1"}) end end describe "when finding device facts" do it "should delegate to the cisco facts entity" do facts = stub 'facts' Puppet::Util::NetworkDevice::Cisco::Facts.expects(:new).returns(facts) facts.expects(:retrieve).returns(:facts) expect(@cisco.facts).to eq(:facts) end end end end puppet-5.5.10/spec/unit/util/network_device/cisco/facts_spec.rb0000644005276200011600000001044413417161722024453 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/network_device' require 'puppet/util/network_device/cisco/facts' describe Puppet::Util::NetworkDevice::Cisco::Facts do before(:each) do @transport = stub_everything 'transport' @facts = Puppet::Util::NetworkDevice::Cisco::Facts.new(@transport) end { "cisco WS-C2924C-XL (PowerPC403GA) processor (revision 0x11) with 8192K/1024K bytes of memory." => {:hardwaremodel => "WS-C2924C-XL", :memorysize => "8192K", :processor => "PowerPC403GA", :hardwarerevision => "0x11" }, "Cisco 1841 (revision 5.0) with 355328K/37888K bytes of memory." => {:hardwaremodel=>"1841", :memorysize => "355328K", :hardwarerevision => "5.0" }, "Cisco 877 (MPC8272) processor (revision 0x200) with 118784K/12288K bytes of memory." => {:hardwaremodel=>"877", :memorysize => "118784K", :processor => "MPC8272", :hardwarerevision => "0x200" }, "cisco WS-C2960G-48TC-L (PowerPC405) processor (revision C0) with 61440K/4088K bytes of memory." => {:hardwaremodel=>"WS-C2960G-48TC-L", :memorysize => "61440K", :processor => "PowerPC405", :hardwarerevision => "C0" }, "cisco WS-C2950T-24 (RC32300) processor (revision R0) with 19959K bytes of memory." => {:hardwaremodel=>"WS-C2950T-24", :memorysize => "19959K", :processor => "RC32300", :hardwarerevision => "R0" } }.each do |ver, expected| it "should parse show ver output for hardware device facts" do @transport.stubs(:command).with("sh ver").returns(<sh ver #{ver} Switch> eos expect(@facts.parse_show_ver).to eq(expected) end end { "Switch uptime is 1 year, 12 weeks, 6 days, 22 hours, 32 minutes" => { :hostname => "Switch", :uptime => "1 year, 12 weeks, 6 days, 22 hours, 32 minutes", :uptime_seconds => 39393120, :uptime_days => 455 }, "c2950 uptime is 3 weeks, 1 day, 23 hours, 36 minutes" => { :hostname => "c2950", :uptime => "3 weeks, 1 day, 23 hours, 36 minutes", :uptime_days => 22, :uptime_seconds => 1985760}, "router uptime is 5 weeks, 1 day, 3 hours, 30 minutes" => { :hostname => "router", :uptime => "5 weeks, 1 day, 3 hours, 30 minutes", :uptime_days => 36, :uptime_seconds => 3123000 }, "c2950 uptime is 1 minute" => { :hostname => "c2950", :uptime => "1 minute", :uptime_days => 0, :uptime_seconds => 60 }, "c2950 uptime is 20 weeks, 6 minutes" => { :hostname => "c2950", :uptime=>"20 weeks, 6 minutes", :uptime_seconds=>12096360, :uptime_days=>140 }, "c2950 uptime is 2 years, 20 weeks, 6 minutes" => { :hostname => "c2950", :uptime=>"2 years, 20 weeks, 6 minutes", :uptime_seconds=>75168360, :uptime_days=>870 } }.each do |ver, expected| it "should parse show ver output for device uptime facts" do @transport.stubs(:command).with("sh ver").returns(<sh ver #{ver} Switch> eos expect(@facts.parse_show_ver).to eq(expected) end end { "IOS (tm) C2900XL Software (C2900XL-C3H2S-M), Version 12.0(5)WC10, RELEASE SOFTWARE (fc1)"=> { :operatingsystem => "IOS", :operatingsystemrelease => "12.0(5)WC10", :operatingsystemmajrelease => "12.0WC", :operatingsystemfeature => "C3H2S"}, "IOS (tm) C2950 Software (C2950-I6K2L2Q4-M), Version 12.1(22)EA8a, RELEASE SOFTWARE (fc1)"=> { :operatingsystem => "IOS", :operatingsystemrelease => "12.1(22)EA8a", :operatingsystemmajrelease => "12.1EA", :operatingsystemfeature => "I6K2L2Q4"}, "Cisco IOS Software, C2960 Software (C2960-LANBASEK9-M), Version 12.2(44)SE, RELEASE SOFTWARE (fc1)"=>{ :operatingsystem => "IOS", :operatingsystemrelease => "12.2(44)SE", :operatingsystemmajrelease => "12.2SE", :operatingsystemfeature => "LANBASEK9"}, "Cisco IOS Software, C870 Software (C870-ADVIPSERVICESK9-M), Version 12.4(11)XJ4, RELEASE SOFTWARE (fc2)"=>{ :operatingsystem => "IOS", :operatingsystemrelease => "12.4(11)XJ4", :operatingsystemmajrelease => "12.4XJ", :operatingsystemfeature => "ADVIPSERVICESK9"}, "Cisco IOS Software, 1841 Software (C1841-ADVSECURITYK9-M), Version 12.4(24)T4, RELEASE SOFTWARE (fc2)" =>{ :operatingsystem => "IOS", :operatingsystemrelease => "12.4(24)T4", :operatingsystemmajrelease => "12.4T", :operatingsystemfeature => "ADVSECURITYK9"}, }.each do |ver, expected| it "should parse show ver output for device software version facts" do @transport.stubs(:command).with("sh ver").returns(<sh ver #{ver} Switch> eos expect(@facts.parse_show_ver).to eq(expected) end end end puppet-5.5.10/spec/unit/util/network_device/cisco/interface_spec.rb0000644005276200011600000000604413417161722025314 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/network_device' require 'puppet/util/network_device/cisco/interface' describe Puppet::Util::NetworkDevice::Cisco::Interface do before(:each) do @transport = stub_everything 'transport' @interface = Puppet::Util::NetworkDevice::Cisco::Interface.new("FastEthernet0/1",@transport) end it "should include IPCalc" do @interface.class.include?(Puppet::Util::NetworkDevice::IPCalc) end describe "when updating the physical device" do it "should enter global configuration mode" do @transport.expects(:command).with("conf t") @interface.update end it "should enter interface configuration mode" do @transport.expects(:command).with("interface FastEthernet0/1") @interface.update end it "should 'execute' all differing properties" do @interface.expects(:execute).with(:description, "b") @interface.expects(:execute).with(:mode, :access).never @interface.update({ :description => "a", :mode => :access }, { :description => "b", :mode => :access }) end it "should execute in cisco ios defined order" do speed = states('speed').starts_as('notset') @interface.expects(:execute).with(:speed, :auto).then(speed.is('set')) @interface.expects(:execute).with(:duplex, :auto).when(speed.is('set')) @interface.update({ :duplex => :half, :speed => "10" }, { :duplex => :auto, :speed => :auto }) end it "should execute absent properties with a no prefix" do @interface.expects(:execute).with(:description, "a", "no ") @interface.update({ :description => "a"}, { }) end it "should exit twice" do @transport.expects(:command).with("exit").twice @interface.update end end describe "when executing commands" do it "should execute string commands directly" do @transport.expects(:command).with("speed auto") @interface.execute(:speed, :auto) end it "should execute string commands with the given prefix" do @transport.expects(:command).with("no speed auto") @interface.execute(:speed, :auto, "no ") end it "should stop at executing the first command that works for array" do @transport.expects(:command).with("channel-group 1").yields("% Invalid command") @transport.expects(:command).with("port group 1") @interface.execute(:etherchannel, "1") end it "should execute the block for block commands" do @transport.expects(:command).with("ip address 192.168.0.1 255.255.255.0") @interface.execute(:ipaddress, [[24, IPAddr.new('192.168.0.1'), nil]]) end it "should execute the block for block commands" do @transport.expects(:command).with("ipv6 address fe08::/76 link-local") @interface.execute(:ipaddress, [[76, IPAddr.new('fe08::'), 'link-local']]) end end describe "when sending commands to the device" do it "should detect errors" do Puppet.expects(:err) @transport.stubs(:command).yields("% Invalid Command") @interface.command("sh ver") end end end puppet-5.5.10/spec/unit/util/network_device/ipcalc_spec.rb0000644005276200011600000000372513417161722023512 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/network_device/ipcalc' describe Puppet::Util::NetworkDevice::IPCalc do class TestIPCalc include Puppet::Util::NetworkDevice::IPCalc end before(:each) do @ipcalc = TestIPCalc.new end describe "when parsing ip/prefix" do it "should parse ipv4 without prefixes" do expect(@ipcalc.parse('127.0.0.1')).to eq([32,IPAddr.new('127.0.0.1')]) end it "should parse ipv4 with prefixes" do expect(@ipcalc.parse('127.0.1.2/8')).to eq([8,IPAddr.new('127.0.1.2')]) end it "should parse ipv6 without prefixes" do expect(@ipcalc.parse('FE80::21A:2FFF:FE30:ECF0')).to eq([128,IPAddr.new('FE80::21A:2FFF:FE30:ECF0')]) end it "should parse ipv6 with prefixes" do expect(@ipcalc.parse('FE80::21A:2FFF:FE30:ECF0/56')).to eq([56,IPAddr.new('FE80::21A:2FFF:FE30:ECF0')]) end end describe "when building netmask" do it "should produce the correct ipv4 netmask from prefix length" do expect(@ipcalc.netmask(Socket::AF_INET, 27)).to eq(IPAddr.new('255.255.255.224')) end it "should produce the correct ipv6 netmask from prefix length" do expect(@ipcalc.netmask(Socket::AF_INET6, 56)).to eq(IPAddr.new('ffff:ffff:ffff:ff00::0')) end end describe "when building wildmask" do it "should produce the correct ipv4 wildmask from prefix length" do expect(@ipcalc.wildmask(Socket::AF_INET, 27)).to eq(IPAddr.new('0.0.0.31')) end it "should produce the correct ipv6 wildmask from prefix length" do expect(@ipcalc.wildmask(Socket::AF_INET6, 126)).to eq(IPAddr.new('::3')) end end describe "when computing prefix length from netmask" do it "should produce the correct ipv4 prefix length" do expect(@ipcalc.prefix_length(IPAddr.new('255.255.255.224'))).to eq(27) end it "should produce the correct ipv6 prefix length" do expect(@ipcalc.prefix_length(IPAddr.new('fffe::0'))).to eq(15) end end end puppet-5.5.10/spec/unit/util/network_device_spec.rb0000644005276200011600000000313413417161721022250 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'ostruct' require 'puppet/util/network_device' describe Puppet::Util::NetworkDevice do before(:each) do @device = OpenStruct.new(:name => "name", :provider => "test", :url => "telnet://admin:password@127.0.0.1", :options => { :debug => false }) end after(:each) do Puppet::Util::NetworkDevice.teardown end class Puppet::Util::NetworkDevice::Test class Device def initialize(device, options) end end end describe "when initializing the remote network device singleton" do it "should load the network device code" do Puppet::Util::NetworkDevice.expects(:require) Puppet::Util::NetworkDevice.init(@device) end it "should create a network device instance" do Puppet::Util::NetworkDevice.stubs(:require) Puppet::Util::NetworkDevice::Test::Device.expects(:new).with("telnet://admin:password@127.0.0.1", :debug => false) Puppet::Util::NetworkDevice.init(@device) end it "should raise an error if the remote device instance can't be created" do Puppet::Util::NetworkDevice.stubs(:require).raises("error") expect { Puppet::Util::NetworkDevice.init(@device) }.to raise_error(RuntimeError, /Can't load test for name/) end it "should let caller to access the singleton device" do device = stub 'device' Puppet::Util::NetworkDevice.stubs(:require) Puppet::Util::NetworkDevice::Test::Device.expects(:new).returns(device) Puppet::Util::NetworkDevice.init(@device) expect(Puppet::Util::NetworkDevice.current).to eq(device) end end end puppet-5.5.10/spec/unit/util/package_spec.rb0000644005276200011600000000132513417161721020633 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/package' describe Puppet::Util::Package, " versioncmp" do it "should be able to be used as a module function" do expect(Puppet::Util::Package).to respond_to(:versioncmp) end it "should be able to sort a long set of various unordered versions" do ary = %w{ 1.1.6 2.3 1.1a 3.0 1.5 1 2.4 1.1-4 2.3.1 1.2 2.3.0 1.1-3 2.4b 2.4 2.40.2 2.3a.1 3.1 0002 1.1-5 1.1.a 1.06} newary = ary.sort { |a, b| Puppet::Util::Package.versioncmp(a,b) } expect(newary).to eq(["0002", "1", "1.06", "1.1-3", "1.1-4", "1.1-5", "1.1.6", "1.1.a", "1.1a", "1.2", "1.5", "2.3", "2.3.0", "2.3.1", "2.3a.1", "2.4", "2.4", "2.4b", "2.40.2", "3.0", "3.1"]) end end puppet-5.5.10/spec/unit/util/pidlock_spec.rb0000644005276200011600000001263313417161721020671 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/pidlock' describe Puppet::Util::Pidlock do require 'puppet_spec/files' include PuppetSpec::Files before(:each) do @lockfile = tmpfile("lock") @lock = Puppet::Util::Pidlock.new(@lockfile) end describe "#lock" do it "should not be locked at start" do expect(@lock).not_to be_locked end it "should not be mine at start" do expect(@lock).not_to be_mine end it "should become locked" do @lock.lock expect(@lock).to be_locked end it "should become mine" do @lock.lock expect(@lock).to be_mine end it "should be possible to lock multiple times" do @lock.lock expect { @lock.lock }.not_to raise_error end it "should return true when locking" do expect(@lock.lock).to be_truthy end it "should return true if locked by me" do @lock.lock expect(@lock.lock).to be_truthy end it "should create a lock file" do @lock.lock expect(Puppet::FileSystem.exist?(@lockfile)).to be_truthy end it 'should create an empty lock file even when pid is missing' do Process.stubs(:pid).returns('') @lock.lock expect(Puppet::FileSystem.exist?(@lock.file_path)).to be_truthy expect(Puppet::FileSystem.read(@lock.file_path)).to be_empty end it 'should replace an existing empty lockfile with a pid, given a subsequent lock call made against a valid pid' do # empty pid results in empty lockfile Process.stubs(:pid).returns('') @lock.lock expect(Puppet::FileSystem.exist?(@lock.file_path)).to be_truthy # next lock call with valid pid kills existing empty lockfile Process.stubs(:pid).returns(1234) @lock.lock expect(Puppet::FileSystem.exist?(@lock.file_path)).to be_truthy expect(Puppet::FileSystem.read(@lock.file_path)).to eq('1234') end it "should expose the lock file_path" do expect(@lock.file_path).to eq(@lockfile) end end describe "#unlock" do it "should not be locked anymore" do @lock.lock @lock.unlock expect(@lock).not_to be_locked end it "should return false if not locked" do expect(@lock.unlock).to be_falsey end it "should return true if properly unlocked" do @lock.lock expect(@lock.unlock).to be_truthy end it "should get rid of the lock file" do @lock.lock @lock.unlock expect(Puppet::FileSystem.exist?(@lockfile)).to be_falsey end end describe "#locked?" do it "should return true if locked" do @lock.lock expect(@lock).to be_locked end it "should remove the lockfile when pid is missing" do Process.stubs(:pid).returns('') @lock.lock expect(@lock.locked?).to be_falsey expect(Puppet::FileSystem.exist?(@lock.file_path)).to be_falsey end end describe '#lock_pid' do it 'should return nil if the pid is empty' do # fake pid to get empty lockfile Process.stubs(:pid).returns('') @lock.lock expect(@lock.lock_pid).to eq(nil) end end describe "with a stale lock" do before(:each) do # fake our pid to be 1234 Process.stubs(:pid).returns(1234) # lock the file @lock.lock # fake our pid to be a different pid, to simulate someone else # holding the lock Process.stubs(:pid).returns(6789) Process.stubs(:kill).with(0, 6789) Process.stubs(:kill).with(0, 1234).raises(Errno::ESRCH) end it "should not be locked" do expect(@lock).not_to be_locked end describe "#lock" do it "should clear stale locks" do expect(@lock.locked?).to be_falsey expect(Puppet::FileSystem.exist?(@lockfile)).to be_falsey end it "should replace with new locks" do @lock.lock expect(Puppet::FileSystem.exist?(@lockfile)).to be_truthy expect(@lock.lock_pid).to eq(6789) expect(@lock).to be_mine expect(@lock).to be_locked end end describe "#unlock" do it "should not be allowed" do expect(@lock.unlock).to be_falsey end it "should not remove the lock file" do @lock.unlock expect(Puppet::FileSystem.exist?(@lockfile)).to be_truthy end end end describe "with another process lock" do before(:each) do # fake our pid to be 1234 Process.stubs(:pid).returns(1234) # lock the file @lock.lock # fake our pid to be a different pid, to simulate someone else # holding the lock Process.stubs(:pid).returns(6789) Process.stubs(:kill).with(0, 6789) Process.stubs(:kill).with(0, 1234) end it "should be locked" do expect(@lock).to be_locked end it "should not be mine" do expect(@lock).not_to be_mine end describe "#lock" do it "should not be possible" do expect(@lock.lock).to be_falsey end it "should not overwrite the lock" do @lock.lock expect(@lock).not_to be_mine end end describe "#unlock" do it "should not be possible" do expect(@lock.unlock).to be_falsey end it "should not remove the lock file" do @lock.unlock expect(Puppet::FileSystem.exist?(@lockfile)).to be_truthy end it "should still not be our lock" do @lock.unlock expect(@lock).not_to be_mine end end end end puppet-5.5.10/spec/unit/util/plist_spec.rb0000644005276200011600000001612513417161721020377 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/util/plist' require 'puppet_spec/files' describe Puppet::Util::Plist, :if => Puppet.features.cfpropertylist? do include PuppetSpec::Files let(:valid_xml_plist) do ' LastUsedPrinters Network 10.85.132.1 PrinterID baskerville_corp_puppetlabs_net Network 10.14.96.1 PrinterID Statler ' end let(:invalid_xml_plist) do ' LastUsedPrinters Network 10.85.132.1 PrinterID baskerville_corp_puppetlabs_net Network 10.14.96.1 PrinterID Statler ' end let(:non_plist_data) do "Take my love, take my land Take me where I cannot stand I don't care, I'm still free You can't take the sky from me." end let(:binary_data) do "\xCF\xFA\xED\xFE\a\u0000\u0000\u0001\u0003\u0000\u0000\x80\u0002\u0000\u0000\u0000\u0012\u0000\u0000\u0000\b" end let(:valid_xml_plist_hash) { {"LastUsedPrinters"=>[{"Network"=>"10.85.132.1", "PrinterID"=>"baskerville_corp_puppetlabs_net"}, {"Network"=>"10.14.96.1", "PrinterID"=>"Statler"}]} } let(:plist_path) { file_containing('sample.plist', valid_xml_plist) } let(:binary_plist_magic_number) { 'bplist00' } let(:bad_xml_doctype) { '' } describe "#read_plist_file" do it "calls #convert_cfpropertylist_to_native_types on a plist object when a valid binary plist is read" do subject.stubs(:read_file_with_offset).with(plist_path, 8).returns(binary_plist_magic_number) subject.stubs(:new_cfpropertylist).with({:file => plist_path}).returns('plist_object') subject.expects(:convert_cfpropertylist_to_native_types).with('plist_object').returns('plist_hash') expect(subject.read_plist_file(plist_path)).to eq('plist_hash') end it "returns a valid hash when a valid XML plist is read" do subject.stubs(:read_file_with_offset).with(plist_path, 8).returns('notbinary') subject.stubs(:open_file_with_args).with(plist_path, 'r:UTF-8').returns(valid_xml_plist) expect(subject.read_plist_file(plist_path)).to eq(valid_xml_plist_hash) end it "raises a debug message and replaces a bad XML plist doctype should one be encountered" do subject.stubs(:read_file_with_offset).with(plist_path, 8).returns('notbinary') subject.stubs(:open_file_with_args).with(plist_path, 'r:UTF-8').returns(bad_xml_doctype) subject.expects(:new_cfpropertylist).with({:data => good_xml_doctype}).returns('plist_object') subject.stubs(:convert_cfpropertylist_to_native_types).with('plist_object').returns('plist_hash') Puppet.expects(:debug).with("Had to fix plist with incorrect DOCTYPE declaration: #{plist_path}") expect(subject.read_plist_file(plist_path)).to eq('plist_hash') end it "attempts to read pure xml using plutil when reading an improperly formatted service plist" do subject.stubs(:read_file_with_offset).with(plist_path, 8).returns('notbinary') subject.stubs(:open_file_with_args).with(plist_path, 'r:UTF-8').returns(invalid_xml_plist) Puppet.expects(:debug).with(regexp_matches(/^Failed with CFFormatError/)) Puppet.expects(:debug).with("Plist #{plist_path} ill-formatted, converting with plutil") Puppet::Util::Execution.expects(:execute) .with(['/usr/bin/plutil', '-convert', 'xml1', '-o', '-', plist_path], {:failonfail => true, :combine => true}) .returns(Puppet::Util::Execution::ProcessOutput.new(valid_xml_plist, 0)) expect(subject.read_plist_file(plist_path)).to eq(valid_xml_plist_hash) end it "returns nil when direct parsing and plutil conversion both fail" do subject.stubs(:read_file_with_offset).with(plist_path, 8).returns('notbinary') subject.stubs(:open_file_with_args).with(plist_path, 'r:UTF-8').returns(non_plist_data) Puppet.expects(:debug).with(regexp_matches(/^Failed with (CFFormatError|NoMethodError)/)) Puppet.expects(:debug).with("Plist #{plist_path} ill-formatted, converting with plutil") Puppet::Util::Execution.expects(:execute) .with(['/usr/bin/plutil', '-convert', 'xml1', '-o', '-', plist_path], {:failonfail => true, :combine => true}) .raises(Puppet::ExecutionFailure, 'boom') expect(subject.read_plist_file(plist_path)).to eq(nil) end it "returns nil when file is a non-plist binary blob" do subject.stubs(:read_file_with_offset).with(plist_path, 8).returns('notbinary') subject.stubs(:open_file_with_args).with(plist_path, 'r:UTF-8').returns(binary_data) Puppet.expects(:debug).with(regexp_matches(/^Failed with (CFFormatError|ArgumentError)/)) Puppet.expects(:debug).with("Plist #{plist_path} ill-formatted, converting with plutil") Puppet::Util::Execution.expects(:execute) .with(['/usr/bin/plutil', '-convert', 'xml1', '-o', '-', plist_path], {:failonfail => true, :combine => true}) .raises(Puppet::ExecutionFailure, 'boom') expect(subject.read_plist_file(plist_path)).to eq(nil) end end describe "#parse_plist" do it "returns a valid hash when a valid XML plist is provided" do expect(subject.parse_plist(valid_xml_plist)).to eq(valid_xml_plist_hash) end it "raises a debug message and replaces a bad XML plist doctype should one be encountered" do subject.expects(:new_cfpropertylist).with({:data => good_xml_doctype}).returns('plist_object') subject.stubs(:convert_cfpropertylist_to_native_types).with('plist_object') Puppet.expects(:debug).with("Had to fix plist with incorrect DOCTYPE declaration: #{plist_path}") subject.parse_plist(bad_xml_doctype, plist_path) end it "raises a debug message with malformed plist" do subject.stubs(:convert_cfpropertylist_to_native_types).with('plist_object') Puppet.expects(:debug).with(regexp_matches(/^Failed with CFFormatError/)) subject.parse_plist("Foo") end end end puppet-5.5.10/spec/unit/util/posix_spec.rb0000644005276200011600000002107213417161721020403 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/posix' class PosixTest include Puppet::Util::POSIX end describe Puppet::Util::POSIX do before do @posix = PosixTest.new end describe '.groups_of' do let(:mock_groups) do [ ['group1', ['user1', 'user2']], ['group2', ['user2']], ['group1', ['user1', 'user2']], ['group3', ['user1']], ['group4', ['user2']] ].map do |(name, members)| group_struct = stub("Group #{name}") group_struct.stubs(:name).returns(name) group_struct.stubs(:mem).returns(members) group_struct end end before(:each) do Puppet::Etc.stubs(:group).multiple_yields(*mock_groups) end it 'returns the groups of the given user' do expect(Puppet::Util::POSIX.groups_of('user1')).to eql( ['group1', 'group3'] ) end end [:group, :gr].each do |name| it "should return :gid as the field for #{name}" do expect(@posix.idfield(name)).to eq(:gid) end it "should return :getgrgid as the id method for #{name}" do expect(@posix.methodbyid(name)).to eq(:getgrgid) end it "should return :getgrnam as the name method for #{name}" do expect(@posix.methodbyname(name)).to eq(:getgrnam) end end [:user, :pw, :passwd].each do |name| it "should return :uid as the field for #{name}" do expect(@posix.idfield(name)).to eq(:uid) end it "should return :getpwuid as the id method for #{name}" do expect(@posix.methodbyid(name)).to eq(:getpwuid) end it "should return :getpwnam as the name method for #{name}" do expect(@posix.methodbyname(name)).to eq(:getpwnam) end end describe "when retrieving a posix field" do before do @thing = stub 'thing', :field => "asdf" end it "should fail if no id was passed" do expect { @posix.get_posix_field("asdf", "bar", nil) }.to raise_error(Puppet::DevError) end describe "and the id is an integer" do it "should log an error and return nil if the specified id is greater than the maximum allowed ID" do Puppet[:maximum_uid] = 100 Puppet.expects(:err) expect(@posix.get_posix_field("asdf", "bar", 200)).to be_nil end it "should use the method return by :methodbyid and return the specified field" do Etc.expects(:getgrgid).returns @thing @thing.expects(:field).returns "myval" expect(@posix.get_posix_field(:gr, :field, 200)).to eq("myval") end it "should return nil if the method throws an exception" do Etc.expects(:getgrgid).raises ArgumentError @thing.expects(:field).never expect(@posix.get_posix_field(:gr, :field, 200)).to be_nil end end describe "and the id is not an integer" do it "should use the method return by :methodbyid and return the specified field" do Etc.expects(:getgrnam).returns @thing @thing.expects(:field).returns "myval" expect(@posix.get_posix_field(:gr, :field, "asdf")).to eq("myval") end it "should return nil if the method throws an exception" do Etc.expects(:getgrnam).raises ArgumentError @thing.expects(:field).never expect(@posix.get_posix_field(:gr, :field, "asdf")).to be_nil end end end describe "when returning the gid" do before do @posix.stubs(:get_posix_field) end describe "and the group is an integer" do it "should convert integers specified as a string into an integer" do @posix.expects(:get_posix_field).with(:group, :name, 100) @posix.gid("100") end it "should look up the name for the group" do @posix.expects(:get_posix_field).with(:group, :name, 100) @posix.gid(100) end it "should return nil if the group cannot be found" do @posix.expects(:get_posix_field).once.returns nil @posix.expects(:search_posix_field).never expect(@posix.gid(100)).to be_nil end it "should use the found name to look up the id" do @posix.expects(:get_posix_field).with(:group, :name, 100).returns "asdf" @posix.expects(:get_posix_field).with(:group, :gid, "asdf").returns 100 expect(@posix.gid(100)).to eq(100) end # LAK: This is because some platforms have a broken Etc module that always return # the same group. it "should use :search_posix_field if the discovered id does not match the passed-in id" do @posix.expects(:get_posix_field).with(:group, :name, 100).returns "asdf" @posix.expects(:get_posix_field).with(:group, :gid, "asdf").returns 50 @posix.expects(:search_posix_field).with(:group, :gid, 100).returns "asdf" expect(@posix.gid(100)).to eq("asdf") end end describe "and the group is a string" do it "should look up the gid for the group" do @posix.expects(:get_posix_field).with(:group, :gid, "asdf") @posix.gid("asdf") end it "should return nil if the group cannot be found" do @posix.expects(:get_posix_field).once.returns nil @posix.expects(:search_posix_field).never expect(@posix.gid("asdf")).to be_nil end it "should use the found gid to look up the nam" do @posix.expects(:get_posix_field).with(:group, :gid, "asdf").returns 100 @posix.expects(:get_posix_field).with(:group, :name, 100).returns "asdf" expect(@posix.gid("asdf")).to eq(100) end it "should use :search_posix_field if the discovered name does not match the passed-in name" do @posix.expects(:get_posix_field).with(:group, :gid, "asdf").returns 100 @posix.expects(:get_posix_field).with(:group, :name, 100).returns "boo" @posix.expects(:search_posix_field).with(:group, :gid, "asdf").returns "asdf" expect(@posix.gid("asdf")).to eq("asdf") end end end describe "when returning the uid" do before do @posix.stubs(:get_posix_field) end describe "and the group is an integer" do it "should convert integers specified as a string into an integer" do @posix.expects(:get_posix_field).with(:passwd, :name, 100) @posix.uid("100") end it "should look up the name for the group" do @posix.expects(:get_posix_field).with(:passwd, :name, 100) @posix.uid(100) end it "should return nil if the group cannot be found" do @posix.expects(:get_posix_field).once.returns nil @posix.expects(:search_posix_field).never expect(@posix.uid(100)).to be_nil end it "should use the found name to look up the id" do @posix.expects(:get_posix_field).with(:passwd, :name, 100).returns "asdf" @posix.expects(:get_posix_field).with(:passwd, :uid, "asdf").returns 100 expect(@posix.uid(100)).to eq(100) end # LAK: This is because some platforms have a broken Etc module that always return # the same group. it "should use :search_posix_field if the discovered id does not match the passed-in id" do @posix.expects(:get_posix_field).with(:passwd, :name, 100).returns "asdf" @posix.expects(:get_posix_field).with(:passwd, :uid, "asdf").returns 50 @posix.expects(:search_posix_field).with(:passwd, :uid, 100).returns "asdf" expect(@posix.uid(100)).to eq("asdf") end end describe "and the group is a string" do it "should look up the uid for the group" do @posix.expects(:get_posix_field).with(:passwd, :uid, "asdf") @posix.uid("asdf") end it "should return nil if the group cannot be found" do @posix.expects(:get_posix_field).once.returns nil @posix.expects(:search_posix_field).never expect(@posix.uid("asdf")).to be_nil end it "should use the found uid to look up the nam" do @posix.expects(:get_posix_field).with(:passwd, :uid, "asdf").returns 100 @posix.expects(:get_posix_field).with(:passwd, :name, 100).returns "asdf" expect(@posix.uid("asdf")).to eq(100) end it "should use :search_posix_field if the discovered name does not match the passed-in name" do @posix.expects(:get_posix_field).with(:passwd, :uid, "asdf").returns 100 @posix.expects(:get_posix_field).with(:passwd, :name, 100).returns "boo" @posix.expects(:search_posix_field).with(:passwd, :uid, "asdf").returns "asdf" expect(@posix.uid("asdf")).to eq("asdf") end end end it "should be able to iteratively search for posix values" do expect(@posix).to respond_to(:search_posix_field) end end puppet-5.5.10/spec/unit/util/profiler/0000755005276200011600000000000013417162177017530 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/util/profiler/aggregate_spec.rb0000644005276200011600000000432213417161721023010 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/util/profiler' require 'puppet/util/profiler/around_profiler' require 'puppet/util/profiler/aggregate' describe Puppet::Util::Profiler::Aggregate do let(:logger) { AggregateSimpleLog.new } let(:profiler) { Puppet::Util::Profiler::Aggregate.new(logger, nil) } let(:profiler_mgr) do p = Puppet::Util::Profiler::AroundProfiler.new p.add_profiler(profiler) p end it "tracks the aggregate counts and time for the hierarchy of metrics" do profiler_mgr.profile("Looking up hiera data in production environment", ["function", "hiera_lookup", "production"]) { sleep 0.01 } profiler_mgr.profile("Looking up hiera data in test environment", ["function", "hiera_lookup", "test"]) {} profiler_mgr.profile("looking up stuff for compilation", ["compiler", "lookup"]) { sleep 0.01 } profiler_mgr.profile("COMPILING ALL OF THE THINGS!", ["compiler", "compiling"]) {} expect(profiler.values["function"].count).to eq(2) expect(profiler.values["function"].time).to be > 0 expect(profiler.values["function"]["hiera_lookup"].count).to eq(2) expect(profiler.values["function"]["hiera_lookup"]["production"].count).to eq(1) expect(profiler.values["function"]["hiera_lookup"]["test"].count).to eq(1) expect(profiler.values["function"].time).to be >= profiler.values["function"]["hiera_lookup"]["test"].time expect(profiler.values["compiler"].count).to eq(2) expect(profiler.values["compiler"].time).to be > 0 expect(profiler.values["compiler"]["lookup"].count).to eq(1) expect(profiler.values["compiler"]["compiling"].count).to eq(1) expect(profiler.values["compiler"].time).to be >= profiler.values["compiler"]["lookup"].time profiler.shutdown expect(logger.output).to match(/function -> hiera_lookup: .*\(2 calls\)\nfunction -> hiera_lookup ->.*\(1 calls\)/) expect(logger.output).to match(/compiler: .*\(2 calls\)\ncompiler ->.*\(1 calls\)/) end it "supports both symbols and strings as components of a metric id" do profiler_mgr.profile("yo", [:foo, "bar"]) {} end class AggregateSimpleLog attr_reader :output def initialize @output = "" end def call(msg) @output << msg << "\n" end end end puppet-5.5.10/spec/unit/util/profiler/around_profiler_spec.rb0000644005276200011600000000310413417161721024251 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/util/profiler' describe Puppet::Util::Profiler::AroundProfiler do let(:child) { TestAroundProfiler.new() } let(:profiler) { Puppet::Util::Profiler::AroundProfiler.new } before :each do profiler.add_profiler(child) end it "returns the value of the profiled segment" do retval = profiler.profile("Testing", ["testing"]) { "the return value" } expect(retval).to eq("the return value") end it "propagates any errors raised in the profiled segment" do expect do profiler.profile("Testing", ["testing"]) { raise "a problem" } end.to raise_error("a problem") end it "makes the description and the context available to the `start` and `finish` methods" do profiler.profile("Testing", ["testing"]) { } expect(child.context).to eq("Testing") expect(child.description).to eq("Testing") end it "calls finish even when an error is raised" do begin profiler.profile("Testing", ["testing"]) { raise "a problem" } rescue expect(child.context).to eq("Testing") end end it "supports multiple profilers" do profiler2 = TestAroundProfiler.new profiler.add_profiler(profiler2) profiler.profile("Testing", ["testing"]) {} expect(child.context).to eq("Testing") expect(profiler2.context).to eq("Testing") end class TestAroundProfiler attr_accessor :context, :description def start(description, metric_id) description end def finish(context, description, metric_id) @context = context @description = description end end end puppet-5.5.10/spec/unit/util/profiler/logging_spec.rb0000644005276200011600000000377113417161721022517 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/util/profiler' describe Puppet::Util::Profiler::Logging do let(:logger) { SimpleLog.new } let(:identifier) { "Profiling ID" } let(:logging_profiler) { TestLoggingProfiler.new(logger, identifier) } let(:profiler) do p = Puppet::Util::Profiler::AroundProfiler.new p.add_profiler(logging_profiler) p end it "logs the explanation of the profile results" do profiler.profile("Testing", ["test"]) { } expect(logger.messages.first).to match(/the explanation/) end it "describes the profiled segment" do profiler.profile("Tested measurement", ["test"]) { } expect(logger.messages.first).to match(/PROFILE \[#{identifier}\] \d Tested measurement/) end it "indicates the order in which segments are profiled" do profiler.profile("Measurement", ["measurement"]) { } profiler.profile("Another measurement", ["measurement"]) { } expect(logger.messages[0]).to match(/1 Measurement/) expect(logger.messages[1]).to match(/2 Another measurement/) end it "indicates the nesting of profiled segments" do profiler.profile("Measurement", ["measurement1"]) do profiler.profile("Nested measurement", ["measurement2"]) { } end profiler.profile("Another measurement", ["measurement1"]) do profiler.profile("Another nested measurement", ["measurement2"]) { } end expect(logger.messages[0]).to match(/1.1 Nested measurement/) expect(logger.messages[1]).to match(/1 Measurement/) expect(logger.messages[2]).to match(/2.1 Another nested measurement/) expect(logger.messages[3]).to match(/2 Another measurement/) end class TestLoggingProfiler < Puppet::Util::Profiler::Logging def do_start(metric, description) "the start" end def do_finish(context, metric, description) {:msg => "the explanation of #{context}"} end end class SimpleLog attr_reader :messages def initialize @messages = [] end def call(msg) @messages << msg end end end puppet-5.5.10/spec/unit/util/profiler/wall_clock_spec.rb0000644005276200011600000000063013417161721023172 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/util/profiler' describe Puppet::Util::Profiler::WallClock do it "logs the number of seconds it took to execute the segment" do profiler = Puppet::Util::Profiler::WallClock.new(nil, nil) message = profiler.do_finish(profiler.start(["foo", "bar"], "Testing"), ["foo", "bar"], "Testing")[:msg] expect(message).to match(/took \d\.\d{4} seconds/) end end puppet-5.5.10/spec/unit/util/profiler/object_counts_spec.rb0000644005276200011600000000051613417161722023725 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/util/profiler' describe Puppet::Util::Profiler::ObjectCounts do it "reports the changes in the system object counts" do profiler = Puppet::Util::Profiler::ObjectCounts.new(nil, nil) message = profiler.finish(profiler.start) expect(message).to match(/ T_STRING: \d+, /) end end puppet-5.5.10/spec/unit/util/profiler_spec.rb0000644005276200011600000000223313417161721021061 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/util/profiler' describe Puppet::Util::Profiler do let(:profiler) { TestProfiler.new() } it "supports adding profilers" do subject.add_profiler(profiler) expect(subject.current[0]).to eq(profiler) end it "supports removing profilers" do subject.add_profiler(profiler) subject.remove_profiler(profiler) expect(subject.current.length).to eq(0) end it "supports clearing profiler list" do subject.add_profiler(profiler) subject.clear expect(subject.current.length).to eq(0) end it "supports profiling" do subject.add_profiler(profiler) subject.profile("hi", ["mymetric"]) {} expect(profiler.context[:metric_id]).to eq(["mymetric"]) expect(profiler.context[:description]).to eq("hi") expect(profiler.description).to eq("hi") end class TestProfiler attr_accessor :context, :metric, :description def start(description, metric_id) {:metric_id => metric_id, :description => description} end def finish(context, description, metric_id) @context = context @metric_id = metric_id @description = description end end end puppet-5.5.10/spec/unit/util/rdoc_spec.rb0000644005276200011600000000350413417161721020170 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/rdoc' require 'rdoc/rdoc' describe Puppet::Util::RDoc do describe "when generating RDoc HTML documentation" do before :each do @rdoc = stub_everything 'rdoc' RDoc::RDoc.stubs(:new).returns(@rdoc) end it "should tell RDoc to generate documentation using the Puppet generator" do @rdoc.expects(:document).with { |args| args.include?("--fmt") and args.include?("puppet") } Puppet::Util::RDoc.rdoc("output", []) end it "should tell RDoc to be quiet" do @rdoc.expects(:document).with { |args| args.include?("--quiet") } Puppet::Util::RDoc.rdoc("output", []) end it "should pass charset to RDoc" do @rdoc.expects(:document).with { |args| args.include?("--charset") and args.include?("utf-8") } Puppet::Util::RDoc.rdoc("output", [], "utf-8") end it "should tell RDoc to use the given outputdir" do @rdoc.expects(:document).with { |args| args.include?("--op") and args.include?("myoutputdir") } Puppet::Util::RDoc.rdoc("myoutputdir", []) end it "should tell RDoc to exclude all files under any modules//files section" do @rdoc.expects(:document).with { |args| args.include?("--exclude") and args.include?("/modules/[^/]*/files/.*$") } Puppet::Util::RDoc.rdoc("myoutputdir", []) end it "should tell RDoc to exclude all files under any modules//templates section" do @rdoc.expects(:document).with { |args| args.include?("--exclude") and args.include?("/modules/[^/]*/templates/.*$") } Puppet::Util::RDoc.rdoc("myoutputdir", []) end it "should give all the source directories to RDoc" do @rdoc.expects(:document).with { |args| args.include?("sourcedir") } Puppet::Util::RDoc.rdoc("output", ["sourcedir"]) end end end puppet-5.5.10/spec/unit/util/reference_spec.rb0000644005276200011600000000277313417161721021206 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/reference' describe Puppet::Util::Reference do it "should create valid Markdown extension definition lists" do my_fragment = nil Puppet::Util::Reference.newreference :testreference, :doc => "A peer of the type and configuration references, but with no useful information" do my_term = "A term" my_definition = <<-EOT The definition of this term, marked by a colon and a space. We should be able to handle multi-line definitions. Each subsequent line should left-align with the first word character after the colon used as the definition marker. We should be able to handle multi-paragraph definitions. Leading indentation should be stripped from the definition, which allows us to indent the source string for cosmetic purposes. EOT my_fragment = markdown_definitionlist(my_term, my_definition) end Puppet::Util::Reference.reference(:testreference).send(:to_markdown, true) expect(my_fragment).to eq <<-EOT A term : The definition of this term, marked by a colon and a space. We should be able to handle multi-line definitions. Each subsequent line should left-align with the first word character after the colon used as the definition marker. We should be able to handle multi-paragraph definitions. Leading indentation should be stripped from the definition, which allows us to indent the source string for cosmetic purposes. EOT end end puppet-5.5.10/spec/unit/util/resource_template_spec.rb0000644005276200011600000000362513417161721022767 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/resource_template' describe Puppet::Util::ResourceTemplate do describe "when initializing" do it "should fail if the template does not exist" do Puppet::FileSystem.expects(:exist?).with("/my/template").returns false expect { Puppet::Util::ResourceTemplate.new("/my/template", mock('resource')) }.to raise_error(ArgumentError) end it "should not create the ERB template" do ERB.expects(:new).never Puppet::FileSystem.expects(:exist?).with("/my/template").returns true Puppet::Util::ResourceTemplate.new("/my/template", mock('resource')) end end describe "when evaluating" do before do Puppet::FileSystem.stubs(:exist?).returns true Puppet::FileSystem.stubs(:read).returns "eh" @template = stub 'template', :result => nil ERB.stubs(:new).returns @template @resource = mock 'resource' @wrapper = Puppet::Util::ResourceTemplate.new("/my/template", @resource) end it "should set all of the resource's parameters as instance variables" do @resource.expects(:to_hash).returns(:one => "uno", :two => "dos") @template.expects(:result).with do |bind| eval("@one", bind) == "uno" and eval("@two", bind) == "dos" end @wrapper.evaluate end it "should create a template instance with the contents of the file" do Puppet::FileSystem.expects(:read).with("/my/template", :encoding => 'utf-8').returns "yay" ERB.expects(:new).with("yay", 0, "-").returns(@template) @wrapper.stubs :set_resource_variables @wrapper.evaluate end it "should return the result of the template" do @wrapper.stubs :set_resource_variables @wrapper.expects(:binding).returns "mybinding" @template.expects(:result).with("mybinding").returns "myresult" expect(@wrapper.evaluate).to eq("myresult") end end end puppet-5.5.10/spec/unit/util/retry_action_spec.rb0000644005276200011600000000473113417161721021746 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/retry_action' describe Puppet::Util::RetryAction do let (:exceptions) { [ Puppet::Error, NameError ] } it "doesn't retry SystemExit" do expect do Puppet::Util::RetryAction.retry_action( :retries => 0 ) do raise SystemExit end end.to exit_with(0) end it "doesn't retry NoMemoryError" do expect do Puppet::Util::RetryAction.retry_action( :retries => 0 ) do raise NoMemoryError, "OOM" end end.to raise_error(NoMemoryError, /OOM/) end it 'should retry on any exception if no acceptable exceptions given' do Puppet::Util::RetryAction.expects(:sleep).with( (((2 ** 1) -1) * 0.1) ) Puppet::Util::RetryAction.expects(:sleep).with( (((2 ** 2) -1) * 0.1) ) expect do Puppet::Util::RetryAction.retry_action( :retries => 2 ) do raise ArgumentError, 'Fake Failure' end end.to raise_exception(Puppet::Util::RetryAction::RetryException::RetriesExceeded) end it 'should retry on acceptable exceptions' do Puppet::Util::RetryAction.expects(:sleep).with( (((2 ** 1) -1) * 0.1) ) Puppet::Util::RetryAction.expects(:sleep).with( (((2 ** 2) -1) * 0.1) ) expect do Puppet::Util::RetryAction.retry_action( :retries => 2, :retry_exceptions => exceptions) do raise Puppet::Error, 'Fake Failure' end end.to raise_exception(Puppet::Util::RetryAction::RetryException::RetriesExceeded) end it 'should not retry on unacceptable exceptions' do Puppet::Util::RetryAction.expects(:sleep).never expect do Puppet::Util::RetryAction.retry_action( :retries => 2, :retry_exceptions => exceptions) do raise ArgumentError end end.to raise_exception(ArgumentError) end it 'should succeed if nothing is raised' do Puppet::Util::RetryAction.expects(:sleep).never Puppet::Util::RetryAction.retry_action( :retries => 2) do true end end it 'should succeed if an expected exception is raised retried and succeeds' do should_retry = nil Puppet::Util::RetryAction.expects(:sleep).once Puppet::Util::RetryAction.retry_action( :retries => 2, :retry_exceptions => exceptions) do if should_retry true else should_retry = true raise Puppet::Error, 'Fake error' end end end it "doesn't mutate caller's arguments" do options = { :retries => 1 }.freeze Puppet::Util::RetryAction.retry_action(options) do end end end puppet-5.5.10/spec/unit/util/splayer_spec.rb0000644005276200011600000000173713417161721020726 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/splayer' describe Puppet::Util::Splayer do include Puppet::Util::Splayer let (:subject) { self } before do Puppet[:splay] = true Puppet[:splaylimit] = "10" end it "should do nothing if splay is disabled" do Puppet[:splay] = false subject.expects(:sleep).never subject.splay end it "should do nothing if it has already splayed" do subject.expects(:splayed?).returns true subject.expects(:sleep).never subject.splay end it "should log that it is splaying" do subject.stubs :sleep Puppet.expects :info subject.splay end it "should sleep for a random portion of the splaylimit plus 1" do Puppet[:splaylimit] = "50" subject.expects(:rand).with(51).returns 10 subject.expects(:sleep).with(10) subject.splay end it "should mark that it has splayed" do subject.stubs(:sleep) subject.splay expect(subject).to be_splayed end end puppet-5.5.10/spec/unit/util/ssl_spec.rb0000644005276200011600000000510713417161721020043 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'openssl' require 'puppet/util/ssl' describe Puppet::Util::SSL do def parse(dn) Puppet::Util::SSL.subject_from_dn(dn) end describe "when getting a subject from a DN" do RSpec::Matchers.define :be_a_subject_with do |expected| match do |actual| parts = actual.to_a.map { |part| part[0..1] }.flatten Hash[*parts] == expected end end NO_PARTS = {} it "parses a DN with a single part" do expect(parse('CN=client.example.org')).to be_a_subject_with({ 'CN' => 'client.example.org' }) end it "parses a DN with parts separated by slashes" do expect(parse('/CN=Root CA/OU=Server Operations/O=Example Org')).to be_a_subject_with({ 'CN' => 'Root CA', 'OU' => 'Server Operations', 'O' => 'Example Org' }) end it "parses a DN with a single part preceded by a slash" do expect(parse('/CN=client.example.org')).to be_a_subject_with({ 'CN' => 'client.example.org' }) end it "parses a DN with parts separated by commas" do expect(parse('O=Foo\, Inc,CN=client2a.example.org')).to be_a_subject_with({ 'O' => 'Foo, Inc', 'CN' => 'client2a.example.org' }) end it "finds no parts in something that is not a DN" do expect(parse('(no)')).to be_a_subject_with(NO_PARTS) end it "finds no parts in a DN with an invalid part" do expect(parse('no=yes,CN=Root CA')).to be_a_subject_with(NO_PARTS) end it "finds no parts in an empty DN" do expect(parse('')).to be_a_subject_with(NO_PARTS) end end describe "when getting a CN from a subject" do def cn_from(subject) Puppet::Util::SSL.cn_from_subject(subject) end it "should correctly parse a subject containing only a CN" do subj = parse('/CN=foo') expect(cn_from(subj)).to eq('foo') end it "should correctly parse a subject containing other components" do subj = parse('/CN=Root CA/OU=Server Operations/O=Example Org') expect(cn_from(subj)).to eq('Root CA') end it "should correctly parse a subject containing other components with CN not first" do subj = parse('/emailAddress=foo@bar.com/CN=foo.bar.com/O=Example Org') expect(cn_from(subj)).to eq('foo.bar.com') end it "should return nil for a subject with no CN" do subj = parse('/OU=Server Operations/O=Example Org') expect(cn_from(subj)).to eq(nil) end it "should return nil for a bare string" do expect(cn_from("/CN=foo")).to eq(nil) end end end puppet-5.5.10/spec/unit/util/symbolic_file_mode_spec.rb0000644005276200011600000001415313417161721023067 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/symbolic_file_mode' describe Puppet::Util::SymbolicFileMode do include Puppet::Util::SymbolicFileMode describe "#valid_symbolic_mode?" do %w{ 0 0000 1 1 7 11 77 111 777 11 0 00000 01 01 07 011 077 0111 0777 011 = - + u= g= o= a= u+ g+ o+ a+ u- g- o- a- ugo= ugoa= ugugug= a=,u=,g= a=,g+ =rwx +rwx -rwx 644 go-w =rw,+X +X 755 u=rwx,go=rx u=rwx,go=u-w go= g=u-w 755 0755 }.each do |input| it "should treat #{input.inspect} as valid" do expect(valid_symbolic_mode?(input)).to be_truthy end end [0000, 0111, 0640, 0755, 0777].each do |input| it "should treat the int #{input.to_s(8)} as value" do expect(valid_symbolic_mode?(input)).to be_truthy end end %w{ -1 -8 8 9 18 19 91 81 000000 11111 77777 0-1 0-8 08 09 018 019 091 081 0000000 011111 077777 u g o a ug uo ua ag }.each do |input| it "should treat #{input.inspect} as invalid" do expect(valid_symbolic_mode?(input)).to be_falsey end end end describe "#normalize_symbolic_mode" do it "should turn an int into a string" do expect(normalize_symbolic_mode(12)).to be_an_instance_of String end it "should not add a leading zero to an int" do expect(normalize_symbolic_mode(12)).not_to match(/^0/) end it "should not add a leading zero to a string with a number" do expect(normalize_symbolic_mode("12")).not_to match(/^0/) end it "should string a leading zero from a number" do expect(normalize_symbolic_mode("012")).to eq('12') end it "should pass through any other string" do expect(normalize_symbolic_mode("u=rwx")).to eq('u=rwx') end end describe "#symbolic_mode_to_int" do { "0654" => 00654, "u+r" => 00400, "g+r" => 00040, "a+r" => 00444, "a+x" => 00111, "o+t" => 01000, ["o-t", 07777] => 06777, ["a-x", 07777] => 07666, ["a-rwx", 07777] => 07000, ["ug-rwx", 07777] => 07007, "a+x,ug-rwx" => 00001, # My experimentation on debian suggests that +g ignores the sgid flag ["a+g", 02060] => 02666, # My experimentation on debian suggests that -g ignores the sgid flag ["a-g", 02666] => 02000, "g+x,a+g" => 00111, # +X without exec set in the original should not set anything "u+x,g+X" => 00100, "g+X" => 00000, # +X only refers to the original, *unmodified* file mode! ["u+x,a+X", 0600] => 00700, # Examples from the MacOS chmod(1) manpage "0644" => 00644, ["go-w", 07777] => 07755, ["=rw,+X", 07777] => 07777, ["=rw,+X", 07766] => 07777, ["=rw,+X", 07676] => 07777, ["=rw,+X", 07667] => 07777, ["=rw,+X", 07666] => 07666, "0755" => 00755, "u=rwx,go=rx" => 00755, "u=rwx,go=u-w" => 00755, ["go=", 07777] => 07700, ["g=u-w", 07777] => 07757, ["g=u-w", 00700] => 00750, ["g=u-w", 00600] => 00640, ["g=u-w", 00500] => 00550, ["g=u-w", 00400] => 00440, ["g=u-w", 00300] => 00310, ["g=u-w", 00200] => 00200, ["g=u-w", 00100] => 00110, ["g=u-w", 00000] => 00000, # Cruel, but legal, use of the action set. ["g=u+r-w", 0300] => 00350, # Empty assignments. ["u=", 00000] => 00000, ["u=", 00600] => 00000, ["ug=", 00000] => 00000, ["ug=", 00600] => 00000, ["ug=", 00660] => 00000, ["ug=", 00666] => 00006, ["=", 00000] => 00000, ["=", 00666] => 00000, ["+", 00000] => 00000, ["+", 00124] => 00124, ["-", 00000] => 00000, ["-", 00124] => 00124, }.each do |input, result| from = input.is_a?(Array) ? "#{input[0]}, 0#{input[1].to_s(8)}" : input it "should map #{from.inspect} to #{result.inspect}" do expect(symbolic_mode_to_int(*input)).to eq(result) end end # Now, test some failure modes. it "should fail if no mode is given" do expect { symbolic_mode_to_int('') }. to raise_error Puppet::Error, /empty mode string/ end %w{u g o ug uo go ugo a uu u/x u!x u=r,,g=r}.each do |input| it "should fail if no (valid) action is given: #{input.inspect}" do expect { symbolic_mode_to_int(input) }. to raise_error Puppet::Error, /Missing action/ end end %w{u+q u-rwF u+rw,g+rw,o+RW}.each do |input| it "should fail with unknown op #{input.inspect}" do expect { symbolic_mode_to_int(input) }. to raise_error Puppet::Error, /Unknown operation/ end end it "should refuse to subtract the conditional execute op" do expect { symbolic_mode_to_int("o-rwX") }. to raise_error Puppet::Error, /only works with/ end it "should refuse to set to the conditional execute op" do expect { symbolic_mode_to_int("o=rwX") }. to raise_error Puppet::Error, /only works with/ end %w{8 08 9 09 118 119}.each do |input| it "should fail for decimal modes: #{input.inspect}" do expect { symbolic_mode_to_int(input) }. to raise_error Puppet::Error, /octal/ end end it "should set the execute bit on a directory, without exec in original" do expect(symbolic_mode_to_int("u+X", 0444, true).to_s(8)).to eq("544") expect(symbolic_mode_to_int("g+X", 0444, true).to_s(8)).to eq("454") expect(symbolic_mode_to_int("o+X", 0444, true).to_s(8)).to eq("445") expect(symbolic_mode_to_int("+X", 0444, true).to_s(8)).to eq("555") end it "should set the execute bit on a file with exec in the original" do expect(symbolic_mode_to_int("+X", 0544).to_s(8)).to eq("555") end it "should not set the execute bit on a file without exec on the original even if set by earlier DSL" do expect(symbolic_mode_to_int("u+x,go+X", 0444).to_s(8)).to eq("544") end end end puppet-5.5.10/spec/unit/util/terminal_spec.rb0000644005276200011600000000267113417161721021060 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/terminal' describe Puppet::Util::Terminal do describe '.width' do before { Puppet.features.stubs(:posix?).returns(true) } it 'should invoke `stty` and return the width' do height, width = 100, 200 subject.expects(:`).with('stty size 2>/dev/null').returns("#{height} #{width}\n") expect(subject.width).to eq(width) end it 'should use `tput` if `stty` is unavailable' do width = 200 subject.expects(:`).with('stty size 2>/dev/null').returns("\n") subject.expects(:`).with('tput cols 2>/dev/null').returns("#{width}\n") expect(subject.width).to eq(width) end it 'should default to 80 columns if `tput` and `stty` are unavailable' do width = 80 subject.expects(:`).with('stty size 2>/dev/null').returns("\n") subject.expects(:`).with('tput cols 2>/dev/null').returns("\n") expect(subject.width).to eq(width) end it 'should default to 80 columns if `tput` or `stty` raise exceptions' do width = 80 subject.expects(:`).with('stty size 2>/dev/null').raises() subject.stubs(:`).with('tput cols 2>/dev/null').returns("#{width + 1000}\n") expect(subject.width).to eq(width) end it 'should default to 80 columns if not in a POSIX environment' do width = 80 Puppet.features.stubs(:posix?).returns(false) expect(subject.width).to eq(width) end end end puppet-5.5.10/spec/unit/util/user_attr_spec.rb0000644005276200011600000000266713417161721021262 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/user_attr' describe UserAttr do before do user_attr = ["foo::::type=role", "bar::::type=normal;profile=foobar"] File.stubs(:readlines).returns(user_attr) end describe "when getting attributes by name" do it "should return nil if there is no entry for that name" do expect(UserAttr.get_attributes_by_name('baz')).to eq(nil) end it "should return a hash if there is an entry in /etc/user_attr" do expect(UserAttr.get_attributes_by_name('foo').class).to eq(Hash) end it "should return a hash with the name value from /etc/user_attr" do expect(UserAttr.get_attributes_by_name('foo')[:name]).to eq('foo') end #this test is contrived #there are a bunch of possible parameters that could be in the hash #the role/normal is just a the convention of the file describe "when the name is a role" do it "should contain :type = role" do expect(UserAttr.get_attributes_by_name('foo')[:type]).to eq('role') end end describe "when the name is not a role" do it "should contain :type = normal" do expect(UserAttr.get_attributes_by_name('bar')[:type]).to eq('normal') end end describe "when the name has more attributes" do it "should contain all the attributes" do expect(UserAttr.get_attributes_by_name('bar')[:profile]).to eq('foobar') end end end end puppet-5.5.10/spec/unit/util/warnings_spec.rb0000644005276200011600000000225713417161721021075 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Util::Warnings do before(:all) do @msg1 = "booness" @msg2 = "more booness" end before(:each) do Puppet.debug = true end after (:each) do Puppet.debug = false end {:notice => "notice_once", :warning => "warnonce", :debug => "debug_once"}.each do |log, method| describe "when registring '#{log}' messages" do it "should always return nil" do expect(Puppet::Util::Warnings.send(method, @msg1)).to be(nil) end it "should issue a warning" do Puppet.expects(log).with(@msg1) Puppet::Util::Warnings.send(method, @msg1) end it "should issue a warning exactly once per unique message" do Puppet.expects(log).with(@msg1).once Puppet::Util::Warnings.send(method, @msg1) Puppet::Util::Warnings.send(method, @msg1) end it "should issue multiple warnings for multiple unique messages" do Puppet.expects(log).times(2) Puppet::Util::Warnings.send(method, @msg1) Puppet::Util::Warnings.send(method, @msg2) end end end after(:each) do Puppet::Util::Warnings.clear_warnings end end puppet-5.5.10/spec/unit/util/watched_file_spec.rb0000644005276200011600000000310313417161721021652 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/util/watched_file' require 'puppet/util/watcher' describe Puppet::Util::WatchedFile do let(:an_absurdly_long_timeout) { Puppet::Util::Watcher::Timer.new(100000) } let(:an_immediate_timeout) { Puppet::Util::Watcher::Timer.new(0) } it "acts like a string so that it can be used as a filename" do watched = Puppet::Util::WatchedFile.new("foo") expect(watched.to_str).to eq("foo") end it "considers the file to be unchanged before the timeout expires" do watched = Puppet::Util::WatchedFile.new(a_file_that_doesnt_exist, an_absurdly_long_timeout) expect(watched).to_not be_changed end it "considers a file that is created to be changed" do watched_filename = a_file_that_doesnt_exist watched = Puppet::Util::WatchedFile.new(watched_filename, an_immediate_timeout) create_file(watched_filename) expect(watched).to be_changed end it "considers a missing file to remain unchanged" do watched = Puppet::Util::WatchedFile.new(a_file_that_doesnt_exist, an_immediate_timeout) expect(watched).to_not be_changed end it "considers a file that has changed but the timeout is not expired to still be unchanged" do watched_filename = a_file_that_doesnt_exist watched = Puppet::Util::WatchedFile.new(watched_filename, an_absurdly_long_timeout) create_file(watched_filename) expect(watched).to_not be_changed end def create_file(name) File.open(name, "wb") { |file| file.puts("contents") } end def a_file_that_doesnt_exist PuppetSpec::Files.tmpfile("watched_file") end end puppet-5.5.10/spec/unit/util/watcher/0000755005276200011600000000000013417162177017343 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/util/watcher/periodic_watcher_spec.rb0000644005276200011600000000315313417161721024211 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/util/watcher' describe Puppet::Util::Watcher::PeriodicWatcher do let(:enabled_timeout) { 1 } let(:disabled_timeout) { -1 } let(:a_value) { 15 } let(:a_different_value) { 16 } let(:unused_watcher) { mock('unused watcher') } let(:unchanged_watcher) { a_watcher_reporting(a_value) } let(:changed_watcher) { a_watcher_reporting(a_value, a_different_value) } it 'reads only the initial change state when the timeout has not yet expired' do watcher = Puppet::Util::Watcher::PeriodicWatcher.new(unchanged_watcher, an_unexpired_timer(enabled_timeout)) expect(watcher).to_not be_changed end it 'reads enough values to determine change when the timeout has expired' do watcher = Puppet::Util::Watcher::PeriodicWatcher.new(changed_watcher, an_expired_timer(enabled_timeout)) expect(watcher).to be_changed end it 'is always marked as changed when the timeout is disabled' do watcher = Puppet::Util::Watcher::PeriodicWatcher.new(unused_watcher, an_expired_timer(disabled_timeout)) expect(watcher).to be_changed end def a_watcher_reporting(*observed_values) Puppet::Util::Watcher::ChangeWatcher.watch(proc do observed_values.shift or raise "No more observed values to report!" end) end def an_expired_timer(timeout) a_time_that_reports_expired_as(true, timeout) end def an_unexpired_timer(timeout) a_time_that_reports_expired_as(false, timeout) end def a_time_that_reports_expired_as(expired, timeout) timer = Puppet::Util::Watcher::Timer.new(timeout) timer.stubs(:expired?).returns(expired) timer end end puppet-5.5.10/spec/unit/util/watcher_spec.rb0000644005276200011600000000327513417161721020703 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/watcher' describe Puppet::Util::Watcher do describe "the common file ctime watcher" do FakeStat = Struct.new(:ctime) def ctime(time) FakeStat.new(time) end let(:filename) { "fake" } def after_reading_the_sequence(initial, *results) expectation = Puppet::FileSystem.expects(:stat).with(filename).at_least(1) ([initial] + results).each do |result| expectation = if result.is_a? Class expectation.raises(result) else expectation.returns(result) end.then end watcher = Puppet::Util::Watcher::Common.file_ctime_change_watcher(filename) results.size.times { watcher = watcher.next_reading } watcher end it "is initially unchanged" do expect(after_reading_the_sequence(ctime(20))).to_not be_changed end it "has not changed if a section of the file path continues to not exist" do expect(after_reading_the_sequence(Errno::ENOTDIR, Errno::ENOTDIR)).to_not be_changed end it "has not changed if the file continues to not exist" do expect(after_reading_the_sequence(Errno::ENOENT, Errno::ENOENT)).to_not be_changed end it "has changed if the file is created" do expect(after_reading_the_sequence(Errno::ENOENT, ctime(20))).to be_changed end it "is marked as changed if the file is deleted" do expect(after_reading_the_sequence(ctime(20), Errno::ENOENT)).to be_changed end it "is marked as changed if the file modified" do expect(after_reading_the_sequence(ctime(20), ctime(21))).to be_changed end end end puppet-5.5.10/spec/unit/util/windows/0000755005276200011600000000000013417162177017400 5ustar jenkinsjenkinspuppet-5.5.10/spec/unit/util/windows/file_spec.rb0000644005276200011600000000664713417161721021665 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/windows' describe Puppet::Util::Windows::File, :if => Puppet::Util::Platform.windows? do include PuppetSpec::Files let(:nonexist_file) { 'C:\foo.bar' } let(:nonexist_path) { 'C:\somefile\that\wont\ever\exist' } let(:invalid_file_attributes) { 0xFFFFFFFF } #define INVALID_FILE_ATTRIBUTES (DWORD (-1)) describe "get_attributes" do it "should raise an error for files that do not exist by default" do expect { described_class.get_attributes(nonexist_file) }.to raise_error(Puppet::Error, /GetFileAttributes/) end it "should raise an error for files that do not exist when specified" do expect { described_class.get_attributes(nonexist_file, true) }.to raise_error(Puppet::Error, /GetFileAttributes/) end it "should not raise an error for files that do not exist when specified" do expect { described_class.get_attributes(nonexist_file, false) }.not_to raise_error end it "should return INVALID_FILE_ATTRIBUTES for files that do not exist when specified" do expect(described_class.get_attributes(nonexist_file, false)).to eq(invalid_file_attributes) end end describe "get_long_pathname" do it "should raise an ERROR_FILE_NOT_FOUND for a file that does not exist in a valid path" do expect { described_class.get_long_pathname(nonexist_file) }.to raise_error do |error| expect(error).to be_a(Puppet::Util::Windows::Error) expect(error.code).to eq(Puppet::Util::Windows::File::ERROR_FILE_NOT_FOUND) end end it "should raise an ERROR_PATH_NOT_FOUND for a path that does not exist" do expect { described_class.get_long_pathname(nonexist_path) }.to raise_error do |error| expect(error).to be_a(Puppet::Util::Windows::Error) expect(error.code).to eq(Puppet::Util::Windows::File::ERROR_PATH_NOT_FOUND) end end it "should return the fully expanded path 'Program Files' given 'Progra~1'" do # this test could be resolve some of these values at runtime rather than hard-coding shortened = ENV['SystemDrive'] + '\\Progra~1' expanded = ENV['SystemDrive'] + '\\Program Files' expect(described_class.get_long_pathname(shortened)).to eq (expanded) end end describe "get_short_pathname" do it "should raise an ERROR_FILE_NOT_FOUND for a file that does not exist in a valid path" do expect { described_class.get_short_pathname(nonexist_file) }.to raise_error do |error| expect(error).to be_a(Puppet::Util::Windows::Error) expect(error.code).to eq(Puppet::Util::Windows::File::ERROR_FILE_NOT_FOUND) end end it "should raise an ERROR_PATH_NOT_FOUND for a path that does not exist" do expect { described_class.get_short_pathname(nonexist_path) }.to raise_error do |error| expect(error).to be_a(Puppet::Util::Windows::Error) expect(error.code).to eq(Puppet::Util::Windows::File::ERROR_PATH_NOT_FOUND) end end it "should return the shortened 'PROGRA~1' given fully expanded path 'Program Files'" do # this test could be resolve some of these values at runtime rather than hard-coding expanded = ENV['SystemDrive'] + '\\Program Files' shortened = ENV['SystemDrive'] + '\\PROGRA~1' expect(described_class.get_short_pathname(expanded)).to eq (shortened) end end end puppet-5.5.10/spec/unit/util/windows/root_certs_spec.rb0000644005276200011600000000074313417161721023120 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/windows' describe "Puppet::Util::Windows::RootCerts", :if => Puppet::Util::Platform.windows? do let(:x509_store) { Puppet::Util::Windows::RootCerts.instance.to_a } it "should return at least one X509 certificate" do expect(x509_store.to_a.size).to be >= 1 end it "should return an X509 certificate with a subject" do x509 = x509_store.first expect(x509.subject.to_s).to match(/CN=.*/) end end puppet-5.5.10/spec/unit/util/windows/access_control_entry_spec.rb0000644005276200011600000000347313417161722025163 0ustar jenkinsjenkins#!/usr/bin/env ruby require 'spec_helper' require 'puppet/util/windows' describe "Puppet::Util::Windows::AccessControlEntry", :if => Puppet.features.microsoft_windows? do let(:klass) { Puppet::Util::Windows::AccessControlEntry } let(:sid) { 'S-1-5-18' } let(:mask) { Puppet::Util::Windows::File::FILE_ALL_ACCESS } it "creates an access allowed ace" do ace = klass.new(sid, mask) expect(ace.type).to eq(klass::ACCESS_ALLOWED_ACE_TYPE) end it "creates an access denied ace" do ace = klass.new(sid, mask, 0, klass::ACCESS_DENIED_ACE_TYPE) expect(ace.type).to eq(klass::ACCESS_DENIED_ACE_TYPE) end it "creates a non-inherited ace by default" do ace = klass.new(sid, mask) expect(ace).not_to be_inherited end it "creates an inherited ace" do ace = klass.new(sid, mask, klass::INHERITED_ACE) expect(ace).to be_inherited end it "creates a non-inherit-only ace by default" do ace = klass.new(sid, mask) expect(ace).not_to be_inherit_only end it "creates an inherit-only ace" do ace = klass.new(sid, mask, klass::INHERIT_ONLY_ACE) expect(ace).to be_inherit_only end context "when comparing aces" do let(:ace1) { klass.new(sid, mask, klass::INHERIT_ONLY_ACE, klass::ACCESS_DENIED_ACE_TYPE) } let(:ace2) { klass.new(sid, mask, klass::INHERIT_ONLY_ACE, klass::ACCESS_DENIED_ACE_TYPE) } it "returns true if different objects have the same set of values" do expect(ace1).to eq(ace2) end it "returns false if different objects have different sets of values" do ace = klass.new(sid, mask) expect(ace).not_to eq(ace1) end it "returns true when testing if two objects are eql?" do ace1.eql?(ace2) end it "returns false when comparing object identity" do expect(ace1).not_to be_equal(ace2) end end end puppet-5.5.10/spec/unit/util/windows/access_control_list_spec.rb0000644005276200011600000000774613417161722025004 0ustar jenkinsjenkins#!/usr/bin/env ruby require 'spec_helper' require 'puppet/util/windows' describe "Puppet::Util::Windows::AccessControlList", :if => Puppet.features.microsoft_windows? do let(:klass) { Puppet::Util::Windows::AccessControlList } let(:system_sid) { 'S-1-5-18' } let(:admins_sid) { 'S-1-5-544' } let(:none_sid) { 'S-1-0-0' } let(:system_ace) do Puppet::Util::Windows::AccessControlEntry.new(system_sid, 0x1) end let(:admins_ace) do Puppet::Util::Windows::AccessControlEntry.new(admins_sid, 0x2) end let(:none_ace) do Puppet::Util::Windows::AccessControlEntry.new(none_sid, 0x3) end it "constructs an empty list" do acl = klass.new expect(acl.to_a).to be_empty end it "supports copy constructor" do aces = klass.new([system_ace]).to_a expect(aces.to_a).to eq([system_ace]) end context "appending" do it "appends an allow ace" do acl = klass.new acl.allow(system_sid, 0x1, 0x2) expect(acl.first.type).to eq(klass::ACCESS_ALLOWED_ACE_TYPE) end it "appends a deny ace" do acl = klass.new acl.deny(system_sid, 0x1, 0x2) expect(acl.first.type).to eq(klass::ACCESS_DENIED_ACE_TYPE) end it "always appends, never overwrites an ACE" do acl = klass.new([system_ace]) acl.allow(admins_sid, admins_ace.mask, admins_ace.flags) aces = acl.to_a expect(aces.size).to eq(2) expect(aces[0]).to eq(system_ace) expect(aces[1].sid).to eq(admins_sid) expect(aces[1].mask).to eq(admins_ace.mask) expect(aces[1].flags).to eq(admins_ace.flags) end end context "reassigning" do it "preserves the mask from the old sid when reassigning to the new sid" do dacl = klass.new([system_ace]) dacl.reassign!(system_ace.sid, admins_ace.sid) # we removed system, so ignore prepended ace ace = dacl.to_a[1] expect(ace.sid).to eq(admins_sid) expect(ace.mask).to eq(system_ace.mask) end it "matches multiple sids" do dacl = klass.new([system_ace, system_ace]) dacl.reassign!(system_ace.sid, admins_ace.sid) # we removed system, so ignore prepended ace aces = dacl.to_a expect(aces.size).to eq(3) aces.to_a[1,2].each do |ace| expect(ace.sid).to eq(admins_ace.sid) end end it "preserves aces for sids that don't match, in their original order" do dacl = klass.new([system_ace, admins_ace]) dacl.reassign!(system_sid, none_sid) aces = dacl.to_a aces[1].sid == admins_ace.sid end it "preserves inherited aces, even if the sids match" do flags = Puppet::Util::Windows::AccessControlEntry::INHERITED_ACE inherited_ace = Puppet::Util::Windows::AccessControlEntry.new(system_sid, 0x1, flags) dacl = klass.new([inherited_ace, system_ace]) dacl.reassign!(system_sid, none_sid) aces = dacl.to_a expect(aces[0].sid).to eq(system_sid) end it "prepends an explicit ace for the new sid with the same mask and basic inheritance as the inherited ace" do expected_flags = Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE | Puppet::Util::Windows::AccessControlEntry::CONTAINER_INHERIT_ACE | Puppet::Util::Windows::AccessControlEntry::INHERIT_ONLY_ACE flags = Puppet::Util::Windows::AccessControlEntry::INHERITED_ACE | expected_flags inherited_ace = Puppet::Util::Windows::AccessControlEntry.new(system_sid, 0x1, flags) dacl = klass.new([inherited_ace]) dacl.reassign!(system_sid, none_sid) aces = dacl.to_a expect(aces.size).to eq(2) expect(aces[0].sid).to eq(none_sid) expect(aces[0]).not_to be_inherited expect(aces[0].flags).to eq(expected_flags) expect(aces[1].sid).to eq(system_sid) expect(aces[1]).to be_inherited end it "makes a copy of the ace prior to modifying it" do arr = [system_ace] acl = klass.new(arr) acl.reassign!(system_sid, none_sid) expect(arr[0].sid).to eq(system_sid) end end end puppet-5.5.10/spec/unit/util/windows/adsi_spec.rb0000644005276200011600000006303413417161722021660 0ustar jenkinsjenkins#!/usr/bin/env ruby require 'spec_helper' require 'puppet/util/windows' describe Puppet::Util::Windows::ADSI, :if => Puppet.features.microsoft_windows? do let(:connection) { stub 'connection' } let(:builtin_localized) { Puppet::Util::Windows::SID.sid_to_name('S-1-5-32') } # SYSTEM is special as English can retrieve it via Windows API # but will return localized names let(:ntauthority_localized) { Puppet::Util::Windows::SID::Principal.lookup_account_name('SYSTEM').domain } before(:each) do Puppet::Util::Windows::ADSI.instance_variable_set(:@computer_name, 'testcomputername') Puppet::Util::Windows::ADSI.stubs(:connect).returns connection end after(:each) do Puppet::Util::Windows::ADSI.instance_variable_set(:@computer_name, nil) end it "should generate the correct URI for a resource" do expect(Puppet::Util::Windows::ADSI.uri('test', 'user')).to eq("WinNT://./test,user") end it "should be able to get the name of the computer" do expect(Puppet::Util::Windows::ADSI.computer_name).to eq('testcomputername') end it "should be able to provide the correct WinNT base URI for the computer" do expect(Puppet::Util::Windows::ADSI.computer_uri).to eq("WinNT://.") end it "should generate a fully qualified WinNT URI" do expect(Puppet::Util::Windows::ADSI.computer_uri('testcomputername')).to eq("WinNT://testcomputername") end describe ".computer_name" do it "should return a non-empty ComputerName string" do Puppet::Util::Windows::ADSI.instance_variable_set(:@computer_name, nil) expect(Puppet::Util::Windows::ADSI.computer_name).not_to be_empty end end describe ".sid_uri" do it "should raise an error when the input is not a SID Principal" do [Object.new, {}, 1, :symbol, '', nil].each do |input| expect { Puppet::Util::Windows::ADSI.sid_uri(input) }.to raise_error(Puppet::Error, /Must use a valid SID::Principal/) end end it "should return a SID uri for a well-known SID (SYSTEM)" do sid = Puppet::Util::Windows::SID::Principal.lookup_account_name('SYSTEM') expect(Puppet::Util::Windows::ADSI.sid_uri(sid)).to eq('WinNT://S-1-5-18') end end describe Puppet::Util::Windows::ADSI::User do let(:username) { 'testuser' } let(:domain) { 'DOMAIN' } let(:domain_username) { "#{domain}\\#{username}"} it "should generate the correct URI" do expect(Puppet::Util::Windows::ADSI::User.uri(username)).to eq("WinNT://./#{username},user") end it "should generate the correct URI for a user with a domain" do expect(Puppet::Util::Windows::ADSI::User.uri(username, domain)).to eq("WinNT://#{domain}/#{username},user") end it "should generate the correct URI for a BUILTIN user" do expect(Puppet::Util::Windows::ADSI::User.uri(username, builtin_localized)).to eq("WinNT://./#{username},user") end it "should generate the correct URI for a NT AUTHORITY user" do expect(Puppet::Util::Windows::ADSI::User.uri(username, ntauthority_localized)).to eq("WinNT://./#{username},user") end it "should be able to parse a username without a domain" do expect(Puppet::Util::Windows::ADSI::User.parse_name(username)).to eq([username, '.']) end it "should be able to parse a username with a domain" do expect(Puppet::Util::Windows::ADSI::User.parse_name(domain_username)).to eq([username, domain]) end it "should raise an error with a username that contains a /" do expect { Puppet::Util::Windows::ADSI::User.parse_name("#{domain}/#{username}") }.to raise_error(Puppet::Error, /Value must be in DOMAIN\\user style syntax/) end it "should be able to create a user" do adsi_user = stub('adsi') connection.expects(:Create).with('user', username).returns(adsi_user) Puppet::Util::Windows::ADSI::Group.expects(:exists?).with(username).returns(false) user = Puppet::Util::Windows::ADSI::User.create(username) expect(user).to be_a(Puppet::Util::Windows::ADSI::User) expect(user.native_object).to eq(adsi_user) end it "should be able to check the existence of a user" do Puppet::Util::Windows::SID.expects(:name_to_principal).with(username).returns nil Puppet::Util::Windows::ADSI.expects(:connect).with("WinNT://./#{username},user").returns connection connection.expects(:Class).returns('User') expect(Puppet::Util::Windows::ADSI::User.exists?(username)).to be_truthy end it "should be able to check the existence of a domain user" do Puppet::Util::Windows::SID.expects(:name_to_principal).with("#{domain}\\#{username}").returns nil Puppet::Util::Windows::ADSI.expects(:connect).with("WinNT://#{domain}/#{username},user").returns connection connection.expects(:Class).returns('User') expect(Puppet::Util::Windows::ADSI::User.exists?(domain_username)).to be_truthy end it "should be able to confirm the existence of a user with a well-known SID" do system_user = Puppet::Util::Windows::SID::LocalSystem # ensure that the underlying OS is queried here Puppet::Util::Windows::ADSI.unstub(:connect) expect(Puppet::Util::Windows::ADSI::User.exists?(system_user)).to be_truthy end it "should return false with a well-known Group SID" do group = Puppet::Util::Windows::SID::BuiltinAdministrators # ensure that the underlying OS is queried here Puppet::Util::Windows::ADSI.unstub(:connect) expect(Puppet::Util::Windows::ADSI::User.exists?(group)).to be_falsey end it "should return nil with an unknown SID" do bogus_sid = 'S-1-2-3-4' # ensure that the underlying OS is queried here Puppet::Util::Windows::ADSI.unstub(:connect) expect(Puppet::Util::Windows::ADSI::User.exists?(bogus_sid)).to be_falsey end it "should be able to delete a user" do connection.expects(:Delete).with('user', username) Puppet::Util::Windows::ADSI::User.delete(username) end it "should return an enumeration of IADsUser wrapped objects" do name = 'Administrator' wmi_users = [stub('WMI', :name => name)] Puppet::Util::Windows::ADSI.expects(:execquery).with('select name from win32_useraccount where localaccount = "TRUE"').returns(wmi_users) native_object = stub('IADsUser') homedir = "C:\\Users\\#{name}" native_object.expects(:Get).with('HomeDirectory').returns(homedir) Puppet::Util::Windows::ADSI.expects(:connect).with("WinNT://./#{name},user").returns(native_object) users = Puppet::Util::Windows::ADSI::User.to_a expect(users.length).to eq(1) expect(users[0].name).to eq(name) expect(users[0]['HomeDirectory']).to eq(homedir) end describe "an instance" do let(:adsi_user) { stub('user', :objectSID => []) } let(:sid) { stub(:account => username, :domain => 'testcomputername') } let(:user) { Puppet::Util::Windows::ADSI::User.new(username, adsi_user) } it "should provide its groups as a list of names" do names = ["group1", "group2"] groups = names.map { |name| stub('group', :Name => name) } adsi_user.expects(:Groups).returns(groups) expect(user.groups).to match(names) end it "should be able to test whether a given password is correct" do Puppet::Util::Windows::ADSI::User.expects(:logon).with(username, 'pwdwrong').returns(false) Puppet::Util::Windows::ADSI::User.expects(:logon).with(username, 'pwdright').returns(true) expect(user.password_is?('pwdwrong')).to be_falsey expect(user.password_is?('pwdright')).to be_truthy end it "should be able to set a password" do adsi_user.expects(:SetPassword).with('pwd') adsi_user.expects(:SetInfo).at_least_once flagname = "UserFlags" fADS_UF_DONT_EXPIRE_PASSWD = 0x10000 adsi_user.expects(:Get).with(flagname).returns(0) adsi_user.expects(:Put).with(flagname, fADS_UF_DONT_EXPIRE_PASSWD) user.password = 'pwd' end it "should be able manage a user without a password" do adsi_user.expects(:SetPassword).with('pwd').never adsi_user.expects(:SetInfo).at_least_once flagname = "UserFlags" fADS_UF_DONT_EXPIRE_PASSWD = 0x10000 adsi_user.expects(:Get).with(flagname).returns(0) adsi_user.expects(:Put).with(flagname, fADS_UF_DONT_EXPIRE_PASSWD) user.password = nil end it "should generate the correct URI" do Puppet::Util::Windows::SID.stubs(:octet_string_to_principal).returns(sid) expect(user.uri).to eq("WinNT://testcomputername/#{username},user") end describe "when given a set of groups to which to add the user" do let(:existing_groups) { ['group2','group3'] } let(:group_sids) { existing_groups.each_with_index.map{|n,i| stub(:Name => n, :objectSID => stub(:sid => i))} } let(:groups_to_set) { 'group1,group2' } let(:desired_sids) { groups_to_set.split(',').each_with_index.map{|n,i| stub(:Name => n, :objectSID => stub(:sid => i-1))} } before(:each) do user.expects(:group_sids).returns(group_sids.map {|s| s.objectSID }) end describe "if membership is specified as inclusive" do it "should add the user to those groups, and remove it from groups not in the list" do Puppet::Util::Windows::ADSI::User.expects(:name_sid_hash).returns(Hash[ desired_sids.map { |s| [s.objectSID.sid, s.objectSID] }]) user.expects(:add_group_sids).with { |value| value.sid == -1 } user.expects(:remove_group_sids).with { |value| value.sid == 1 } user.set_groups(groups_to_set, false) end it "should remove all users from a group if desired is empty" do Puppet::Util::Windows::ADSI::User.expects(:name_sid_hash).returns({}) user.expects(:add_group_sids).never user.expects(:remove_group_sids).with { |user1, user2| user1.sid == 0 && user2.sid == 1 } user.set_groups('', false) end end describe "if membership is specified as minimum" do it "should add the user to the specified groups without affecting its other memberships" do Puppet::Util::Windows::ADSI::User.expects(:name_sid_hash).returns(Hash[ desired_sids.map { |s| [s.objectSID.sid, s.objectSID] }]) user.expects(:add_group_sids).with { |value| value.sid == -1 } user.expects(:remove_group_sids).never user.set_groups(groups_to_set, true) end it "should do nothing if desired is empty" do Puppet::Util::Windows::ADSI::User.expects(:name_sid_hash).returns({}) user.expects(:remove_group_sids).never user.expects(:add_group_sids).never user.set_groups('', true) end end end describe 'userflags' do # Avoid having to type out the constant everytime we want to # retrieve a userflag's value. def ads_userflags(flag) Puppet::Util::Windows::ADSI::User::ADS_USERFLAGS[flag] end before(:each) do userflags = [ :ADS_UF_SCRIPT, :ADS_UF_ACCOUNTDISABLE, :ADS_UF_HOMEDIR_REQUIRED, :ADS_UF_LOCKOUT ].inject(0) do |flags, flag| flags | ads_userflags(flag) end user.stubs(:[]).with('UserFlags').returns(userflags) end describe '#userflag_set?' do it 'returns true if the specified userflag is set' do expect(user.userflag_set?(:ADS_UF_SCRIPT)).to be true end it 'returns false if the specified userflag is not set' do expect(user.userflag_set?(:ADS_UF_PASSWD_NOTREQD)).to be false end it 'returns false if the specified userflag is an unrecognized userflag' do expect(user.userflag_set?(:ADS_UF_UNRECOGNIZED_FLAG)).to be false end end shared_examples 'set/unset common tests' do |method| it 'raises an ArgumentError for any unrecognized userflags' do unrecognized_flags = [ :ADS_UF_UNRECOGNIZED_FLAG_ONE, :ADS_UF_UNRECOGNIZED_FLAG_TWO ] input_flags = unrecognized_flags + [ :ADS_UF_PASSWORD_EXPIRED, :ADS_UF_DONT_EXPIRE_PASSWD ] expect { user.send(method, *input_flags) }.to raise_error( ArgumentError, /#{unrecognized_flags.join(', ')}/ ) end it 'noops if no userflags are passed-in' do user.expects(:[]=).never user.expects(:commit).never user.send(method) end end describe '#set_userflags' do include_examples 'set/unset common tests', :set_userflags it 'should add the passed-in flags to the current set of userflags' do input_flags = [ :ADS_UF_PASSWORD_EXPIRED, :ADS_UF_DONT_EXPIRE_PASSWD ] userflags = user['UserFlags'] expected_userflags = userflags | ads_userflags(input_flags[0]) | ads_userflags(input_flags[1]) user.expects(:[]=).with('UserFlags', expected_userflags) user.set_userflags(*input_flags) end end describe '#unset_userflags' do include_examples 'set/unset common tests', :unset_userflags it 'should remove the passed-in flags from the current set of userflags' do input_flags = [ :ADS_UF_SCRIPT, :ADS_UF_ACCOUNTDISABLE ] # ADS_UF_HOMEDIR_REQUIRED and ADS_UF_LOCKOUT should be the only flags set. expected_userflags = 0 | ads_userflags(:ADS_UF_HOMEDIR_REQUIRED) | ads_userflags(:ADS_UF_LOCKOUT) user.expects(:[]=).with('UserFlags', expected_userflags) user.unset_userflags(*input_flags) end end end end end describe Puppet::Util::Windows::ADSI::Group do let(:groupname) { 'testgroup' } describe "an instance" do let(:adsi_group) { stub 'group' } let(:group) { Puppet::Util::Windows::ADSI::Group.new(groupname, adsi_group) } let(:someone_sid){ stub(:account => 'someone', :domain => 'testcomputername')} describe "should be able to use SID objects" do let(:system) { Puppet::Util::Windows::SID.name_to_principal('SYSTEM') } let(:invalid) { Puppet::Util::Windows::SID.name_to_principal('foobar') } it "to add a member" do adsi_group.expects(:Add).with("WinNT://S-1-5-18") group.add_member_sids(system) end it "and raise when passed a non-SID object to add" do expect{ group.add_member_sids(invalid)}.to raise_error(Puppet::Error, /Must use a valid SID::Principal/) end it "to remove a member" do adsi_group.expects(:Remove).with("WinNT://S-1-5-18") group.remove_member_sids(system) end it "and raise when passed a non-SID object to remove" do expect{ group.remove_member_sids(invalid)}.to raise_error(Puppet::Error, /Must use a valid SID::Principal/) end end it "should provide its groups as a list of names" do names = ['user1', 'user2'] users = names.map { |name| stub('user', :Name => name, :objectSID => name, :ole_respond_to? => true) } adsi_group.expects(:Members).returns(users) Puppet::Util::Windows::SID.expects(:octet_string_to_principal).with('user1').returns(stub(:domain_account => 'HOSTNAME\user1')) Puppet::Util::Windows::SID.expects(:octet_string_to_principal).with('user2').returns(stub(:domain_account => 'HOSTNAME\user2')) expect(group.members.map(&:domain_account)).to match(['HOSTNAME\user1', 'HOSTNAME\user2']) end context "calling .set_members" do it "should set the members of a group to only desired_members when inclusive" do names = ['DOMAIN\user1', 'user2'] sids = [ stub(:account => 'user1', :domain => 'DOMAIN', :sid => 1), stub(:account => 'user2', :domain => 'testcomputername', :sid => 2), stub(:account => 'user3', :domain => 'DOMAIN2', :sid => 3), ] # use stubbed objectSid on member to return stubbed SID Puppet::Util::Windows::SID.expects(:octet_string_to_principal).with([0]).returns(sids[0]) Puppet::Util::Windows::SID.expects(:octet_string_to_principal).with([1]).returns(sids[1]) Puppet::Util::Windows::SID.expects(:name_to_principal).with('user2').returns(sids[1]) Puppet::Util::Windows::SID.expects(:name_to_principal).with('DOMAIN2\user3').returns(sids[2]) Puppet::Util::Windows::ADSI.expects(:sid_uri).with(sids[0]).returns("WinNT://DOMAIN/user1,user") Puppet::Util::Windows::ADSI.expects(:sid_uri).with(sids[2]).returns("WinNT://DOMAIN2/user3,user") members = names.each_with_index.map{|n,i| stub(:Name => n, :objectSID => [i], :ole_respond_to? => true)} adsi_group.expects(:Members).returns members adsi_group.expects(:Remove).with('WinNT://DOMAIN/user1,user') adsi_group.expects(:Add).with('WinNT://DOMAIN2/user3,user') group.set_members(['user2', 'DOMAIN2\user3']) end it "should add the desired_members to an existing group when not inclusive" do names = ['DOMAIN\user1', 'user2'] sids = [ stub(:account => 'user1', :domain => 'DOMAIN', :sid => 1), stub(:account => 'user2', :domain => 'testcomputername', :sid => 2), stub(:account => 'user3', :domain => 'DOMAIN2', :sid => 3), ] # use stubbed objectSid on member to return stubbed SID Puppet::Util::Windows::SID.expects(:octet_string_to_principal).with([0]).returns(sids[0]) Puppet::Util::Windows::SID.expects(:octet_string_to_principal).with([1]).returns(sids[1]) Puppet::Util::Windows::SID.expects(:name_to_principal).with('user2').returns(sids[1]) Puppet::Util::Windows::SID.expects(:name_to_principal).with('DOMAIN2\user3').returns(sids[2]) Puppet::Util::Windows::ADSI.expects(:sid_uri).with(sids[2]).returns("WinNT://DOMAIN2/user3,user") members = names.each_with_index.map{|n,i| stub(:Name => n, :objectSID => [i], :ole_respond_to? => true)} adsi_group.expects(:Members).returns members adsi_group.expects(:Remove).with('WinNT://DOMAIN/user1,user').never adsi_group.expects(:Add).with('WinNT://DOMAIN2/user3,user') group.set_members(['user2', 'DOMAIN2\user3'],false) end it "should return immediately when desired_members is nil" do adsi_group.expects(:Members).never adsi_group.expects(:Remove).never adsi_group.expects(:Add).never group.set_members(nil) end it "should remove all members when desired_members is empty and inclusive" do names = ['DOMAIN\user1', 'user2'] sids = [ stub(:account => 'user1', :domain => 'DOMAIN', :sid => 1 ), stub(:account => 'user2', :domain => 'testcomputername', :sid => 2 ), ] # use stubbed objectSid on member to return stubbed SID Puppet::Util::Windows::SID.expects(:octet_string_to_principal).with([0]).returns(sids[0]) Puppet::Util::Windows::SID.expects(:octet_string_to_principal).with([1]).returns(sids[1]) Puppet::Util::Windows::ADSI.expects(:sid_uri).with(sids[0]).returns("WinNT://DOMAIN/user1,user") Puppet::Util::Windows::ADSI.expects(:sid_uri).with(sids[1]).returns("WinNT://testcomputername/user2,user") members = names.each_with_index.map{|n,i| stub(:Name => n, :objectSID => [i], :ole_respond_to? => true)} adsi_group.expects(:Members).returns members adsi_group.expects(:Remove).with('WinNT://DOMAIN/user1,user') adsi_group.expects(:Remove).with('WinNT://testcomputername/user2,user') group.set_members([]) end it "should do nothing when desired_members is empty and not inclusive" do names = ['DOMAIN\user1', 'user2'] sids = [ stub(:account => 'user1', :domain => 'DOMAIN', :sid => 1 ), stub(:account => 'user2', :domain => 'testcomputername', :sid => 2 ), ] # use stubbed objectSid on member to return stubbed SID Puppet::Util::Windows::SID.expects(:octet_string_to_principal).with([0]).returns(sids[0]) Puppet::Util::Windows::SID.expects(:octet_string_to_principal).with([1]).returns(sids[1]) members = names.each_with_index.map{|n,i| stub(:Name => n, :objectSID => [i], :ole_respond_to? => true)} adsi_group.expects(:Members).returns members adsi_group.expects(:Remove).never adsi_group.expects(:Add).never group.set_members([],false) end it "should raise an error when a username does not resolve to a SID" do expect { adsi_group.expects(:Members).returns [] group.set_members(['foobar']) }.to raise_error(Puppet::Error, /Could not resolve name: foobar/) end end it "should generate the correct URI" do adsi_group.expects(:objectSID).returns([0]) Socket.expects(:gethostname).returns('TESTcomputerNAME') computer_sid = stub(:account => groupname,:domain => 'testcomputername') Puppet::Util::Windows::SID.expects(:octet_string_to_principal).with([0]).returns(computer_sid) expect(group.uri).to eq("WinNT://./#{groupname},group") end end it "should generate the correct URI" do expect(Puppet::Util::Windows::ADSI::Group.uri("people")).to eq("WinNT://./people,group") end it "should generate the correct URI for a BUILTIN group" do expect(Puppet::Util::Windows::ADSI::Group.uri(groupname, builtin_localized)).to eq("WinNT://./#{groupname},group") end it "should generate the correct URI for a NT AUTHORITY group" do expect(Puppet::Util::Windows::ADSI::Group.uri(groupname, ntauthority_localized)).to eq("WinNT://./#{groupname},group") end it "should be able to create a group" do adsi_group = stub("adsi") connection.expects(:Create).with('group', groupname).returns(adsi_group) Puppet::Util::Windows::ADSI::User.expects(:exists?).with(groupname).returns(false) group = Puppet::Util::Windows::ADSI::Group.create(groupname) expect(group).to be_a(Puppet::Util::Windows::ADSI::Group) expect(group.native_object).to eq(adsi_group) end it "should be able to confirm the existence of a group" do Puppet::Util::Windows::SID.expects(:name_to_principal).with(groupname).returns nil Puppet::Util::Windows::ADSI.expects(:connect).with("WinNT://./#{groupname},group").returns connection connection.expects(:Class).returns('Group') expect(Puppet::Util::Windows::ADSI::Group.exists?(groupname)).to be_truthy end it "should be able to confirm the existence of a group with a well-known SID" do service_group = Puppet::Util::Windows::SID::Service # ensure that the underlying OS is queried here Puppet::Util::Windows::ADSI.unstub(:connect) expect(Puppet::Util::Windows::ADSI::Group.exists?(service_group)).to be_truthy end it "will return true with a well-known User SID, as there is no way to resolve it with a WinNT:// style moniker" do user = Puppet::Util::Windows::SID::NtLocal # ensure that the underlying OS is queried here Puppet::Util::Windows::ADSI.unstub(:connect) expect(Puppet::Util::Windows::ADSI::Group.exists?(user)).to be_truthy end it "should return nil with an unknown SID" do bogus_sid = 'S-1-2-3-4' # ensure that the underlying OS is queried here Puppet::Util::Windows::ADSI.unstub(:connect) expect(Puppet::Util::Windows::ADSI::Group.exists?(bogus_sid)).to be_falsey end it "should be able to delete a group" do connection.expects(:Delete).with('group', groupname) Puppet::Util::Windows::ADSI::Group.delete(groupname) end it "should return an enumeration of IADsGroup wrapped objects" do name = 'Administrators' wmi_groups = [stub('WMI', :name => name)] Puppet::Util::Windows::ADSI.expects(:execquery).with('select name from win32_group where localaccount = "TRUE"').returns(wmi_groups) native_object = stub('IADsGroup') Puppet::Util::Windows::SID.expects(:octet_string_to_principal).with([]).returns(stub(:domain_account => '.\Administrator')) native_object.expects(:Members).returns([stub(:Name => 'Administrator', :objectSID => [], :ole_respond_to? => true)]) Puppet::Util::Windows::ADSI.expects(:connect).with("WinNT://./#{name},group").returns(native_object) groups = Puppet::Util::Windows::ADSI::Group.to_a expect(groups.length).to eq(1) expect(groups[0].name).to eq(name) expect(groups[0].members.map(&:domain_account)).to eq(['.\Administrator']) end end describe Puppet::Util::Windows::ADSI::UserProfile do it "should be able to delete a user profile" do connection.expects(:Delete).with("Win32_UserProfile.SID='S-A-B-C'") Puppet::Util::Windows::ADSI::UserProfile.delete('S-A-B-C') end it "should warn on 2003" do connection.expects(:Delete).raises(WIN32OLERuntimeError, "Delete (WIN32OLERuntimeError) OLE error code:80041010 in SWbemServicesEx Invalid class HRESULT error code:0x80020009 Exception occurred.") Puppet.expects(:warning).with("Cannot delete user profile for 'S-A-B-C' prior to Vista SP1") Puppet::Util::Windows::ADSI::UserProfile.delete('S-A-B-C') end end end puppet-5.5.10/spec/unit/util/windows/api_types_spec.rb0000644005276200011600000001035413417161722022732 0ustar jenkinsjenkins# encoding: UTF-8 #!/usr/bin/env ruby require 'spec_helper' describe "FFI::MemoryPointer", :if => Puppet.features.microsoft_windows? do # use 2 bad bytes at end so we have even number of bytes / characters let (:bad_string) { "hello invalid world".encode(Encoding::UTF_16LE) + "\xDD\xDD".force_encoding(Encoding::UTF_16LE) } let (:bad_string_bytes) { bad_string.bytes.to_a } context "read_wide_string" do let (:string) { "foo_bar" } it "should properly roundtrip a given string" do read_string = nil FFI::MemoryPointer.from_string_to_wide_string(string) do |ptr| read_string = ptr.read_wide_string(string.length) end expect(read_string).to eq(string) end it "should return a given string in UTF-8" do read_string = nil FFI::MemoryPointer.from_string_to_wide_string(string) do |ptr| read_string = ptr.read_wide_string(string.length) end expect(read_string.encoding).to eq(Encoding::UTF_8) end it "should raise an error and emit a debug message when receiving a string containing invalid bytes in the destination encoding" do # enable a debug output sink to local string array Puppet.debug = true arraydest = [] Puppet::Util::Log.newdestination(Puppet::Test::LogCollector.new(arraydest)) read_string = nil expect { FFI::MemoryPointer.new(:byte, bad_string_bytes.count) do |ptr| # uchar here is synonymous with byte ptr.put_array_of_uchar(0, bad_string_bytes) read_string = ptr.read_wide_string(bad_string.length) end }.to raise_error(Encoding::InvalidByteSequenceError) expect(read_string).to be_nil expect(arraydest.last.message).to eq("Unable to convert value #{bad_string.dump} to encoding UTF-8 due to #") end it "should not raise an error when receiving a string containing invalid bytes in the destination encoding, when specifying :invalid => :replace" do read_string = nil FFI::MemoryPointer.new(:byte, bad_string_bytes.count) do |ptr| # uchar here is synonymous with byte ptr.put_array_of_uchar(0, bad_string_bytes) read_string = ptr.read_wide_string(bad_string.length, Encoding::UTF_8, :invalid => :replace) end expect(read_string).to eq("hello invalid world\uFFFD") end end context "read_arbitrary_wide_string_up_to" do let (:string) { "foo_bar" } let (:single_null_string) { string + "\x00" } let (:double_null_string) { string + "\x00\x00" } it "should read a short single null terminated string" do read_string = nil FFI::MemoryPointer.from_string_to_wide_string(single_null_string) do |ptr| read_string = ptr.read_arbitrary_wide_string_up_to() end expect(read_string).to eq(string) end it "should read a short double null terminated string" do read_string = nil FFI::MemoryPointer.from_string_to_wide_string(double_null_string) do |ptr| read_string = ptr.read_arbitrary_wide_string_up_to(512, :double_null) end expect(read_string).to eq(string) end it "should return a string of max_length characters when specified" do read_string = nil FFI::MemoryPointer.from_string_to_wide_string(single_null_string) do |ptr| read_string = ptr.read_arbitrary_wide_string_up_to(3) end expect(read_string).to eq(string[0..2]) end it "should return wide strings in UTF-8" do read_string = nil FFI::MemoryPointer.from_string_to_wide_string(string) do |ptr| read_string = ptr.read_arbitrary_wide_string_up_to() end expect(read_string.encoding).to eq(Encoding::UTF_8) end it "should not raise an error when receiving a string containing invalid bytes in the destination encoding, when specifying :invalid => :replace" do read_string = nil FFI::MemoryPointer.new(:byte, bad_string_bytes.count) do |ptr| # uchar here is synonymous with byte ptr.put_array_of_uchar(0, bad_string_bytes) read_string = ptr.read_arbitrary_wide_string_up_to(ptr.size / 2, :single_null, :invalid => :replace) end expect(read_string).to eq("hello invalid world\uFFFD") end end end puppet-5.5.10/spec/unit/util/windows/eventlog_spec.rb0000644005276200011600000001253613417161722022564 0ustar jenkinsjenkins#!/usr/bin/env ruby require 'spec_helper' require 'puppet/util/windows' describe Puppet::Util::Windows::EventLog, :if => Puppet.features.microsoft_windows? do before(:each) { @event_log = Puppet::Util::Windows::EventLog.new } after(:each) { @event_log.close } describe "class constants" do it "should define NULL_HANDLE as 0" do expect(Puppet::Util::Windows::EventLog::NULL_HANDLE).to eq(0) end it "should define WIN32_FALSE as 0" do expect(Puppet::Util::Windows::EventLog::WIN32_FALSE).to eq(0) end end describe "self.open" do it "sets a handle to the event log" do default_name = Puppet::Util::Windows::String.wide_string('Puppet') # return nil explicitly just to reinforce that we're not leaking eventlog handle Puppet::Util::Windows::EventLog.any_instance.expects(:RegisterEventSourceW).with(nil, default_name).returns(nil) Puppet::Util::Windows::EventLog.new end context "when it fails to open the event log" do before do # RegisterEventSourceW will return NULL on failure # Stubbing prevents leaking eventlog handle Puppet::Util::Windows::EventLog.any_instance.stubs(:RegisterEventSourceW).returns(Puppet::Util::Windows::EventLog::NULL_HANDLE) end it "raises an exception warning that the event log failed to open" do expect { Puppet::Util::Windows::EventLog.open('foo') }.to raise_error(Puppet::Util::Windows::EventLog::EventLogError, /failed to open Windows eventlog/) end it "passes the exit code to the exception constructor" do fake_error = Puppet::Util::Windows::EventLog::EventLogError.new('foo', 87) FFI.stubs(:errno).returns(87) # All we're testing here is that the constructor actually receives the exit code from FFI.errno (87) # We do so because `expect to...raise_error` doesn't support multiple parameter match arguments # We return fake_error just because `raise` expects an exception class Puppet::Util::Windows::EventLog::EventLogError.expects(:new).with(regexp_matches(/failed to open Windows eventlog/), 87).returns(fake_error) expect { Puppet::Util::Windows::EventLog.open('foo') }.to raise_error(Puppet::Util::Windows::EventLog::EventLogError) end end end describe "#close" do it "closes the handle to the event log" do @handle = "12345" Puppet::Util::Windows::EventLog.any_instance.stubs(:RegisterEventSourceW).returns(@handle) event_log = Puppet::Util::Windows::EventLog.new event_log.expects(:DeregisterEventSource).with(@handle).returns(1) event_log.close end end describe "#report_event" do it "raises an exception if the message passed is not a string" do expect { @event_log.report_event(:data => 123, :event_type => nil, :event_id => nil) }.to raise_error(ArgumentError, /data must be a string/) end context "when an event report fails" do before do # ReportEventW returns 0 on failure, which is mapped to WIN32_FALSE @event_log.stubs(:ReportEventW).returns(Puppet::Util::Windows::EventLog::WIN32_FALSE) end it "raises an exception warning that the event report failed" do expect { @event_log.report_event(:data => 'foo', :event_type => Puppet::Util::Windows::EventLog::EVENTLOG_ERROR_TYPE, :event_id => 0x03) }.to raise_error(Puppet::Util::Windows::EventLog::EventLogError, /failed to report event/) end it "passes the exit code to the exception constructor" do fake_error = Puppet::Util::Windows::EventLog::EventLogError.new('foo', 5) FFI.stubs(:errno).returns(5) # All we're testing here is that the constructor actually receives the exit code from FFI.errno (5) # We do so because `expect to...raise_error` doesn't support multiple parameter match arguments # We return fake_error just because `raise` expects an exception class Puppet::Util::Windows::EventLog::EventLogError.expects(:new).with(regexp_matches(/failed to report event/), 5).returns(fake_error) expect { @event_log.report_event(:data => 'foo', :event_type => Puppet::Util::Windows::EventLog::EVENTLOG_ERROR_TYPE, :event_id => 0x03) }.to raise_error(Puppet::Util::Windows::EventLog::EventLogError) end end end describe "self.to_native" do it "raises an exception if the log level is not supported" do expect { Puppet::Util::Windows::EventLog.to_native(:foo) }.to raise_error(ArgumentError) end # This is effectively duplicating the data assigned to the constants in # Puppet::Util::Windows::EventLog but since these are public constants we # ensure their values don't change lightly. log_levels_to_type_and_id = { :debug => [0x0004, 0x01], :info => [0x0004, 0x01], :notice => [0x0004, 0x01], :warning => [0x0002, 0x02], :err => [0x0001, 0x03], :alert => [0x0001, 0x03], :emerg => [0x0001, 0x03], :crit => [0x0001, 0x03], } shared_examples_for "#to_native" do |level| it "should return the correct INFORMATION_TYPE and ID" do result = Puppet::Util::Windows::EventLog.to_native(level) expect(result).to eq(log_levels_to_type_and_id[level]) end end log_levels_to_type_and_id.each_key do |level| describe "logging at #{level}" do it_should_behave_like "#to_native", level end end end end puppet-5.5.10/spec/unit/util/windows/security_descriptor_spec.rb0000644005276200011600000000747613417161722025055 0ustar jenkinsjenkins#!/usr/bin/env ruby require 'spec_helper' require 'puppet/util/windows' describe "Puppet::Util::Windows::SecurityDescriptor", :if => Puppet.features.microsoft_windows? do let(:system_sid) { Puppet::Util::Windows::SID::LocalSystem } let(:admins_sid) { Puppet::Util::Windows::SID::BuiltinAdministrators } let(:group_sid) { Puppet::Util::Windows::SID::Nobody } let(:new_sid) { 'S-1-5-32-500-1-2-3' } def empty_dacl Puppet::Util::Windows::AccessControlList.new end def system_ace_dacl dacl = Puppet::Util::Windows::AccessControlList.new dacl.allow(system_sid, 0x1) dacl end context "owner" do it "changes the owner" do sd = Puppet::Util::Windows::SecurityDescriptor.new(system_sid, group_sid, system_ace_dacl) sd.owner = new_sid expect(sd.owner).to eq(new_sid) end it "performs a noop if the new owner is the same as the old one" do dacl = system_ace_dacl sd = Puppet::Util::Windows::SecurityDescriptor.new(system_sid, group_sid, dacl) sd.owner = sd.owner expect(sd.dacl.object_id).to eq(dacl.object_id) end it "prepends SYSTEM when security descriptor owner is no longer SYSTEM" do sd = Puppet::Util::Windows::SecurityDescriptor.new(system_sid, group_sid, system_ace_dacl) sd.owner = new_sid aces = sd.dacl.to_a expect(aces.size).to eq(2) expect(aces[0].sid).to eq(system_sid) expect(aces[1].sid).to eq(new_sid) end it "does not prepend SYSTEM when DACL already contains inherited SYSTEM ace" do sd = Puppet::Util::Windows::SecurityDescriptor.new(admins_sid, system_sid, empty_dacl) sd.dacl.allow(admins_sid, 0x1) sd.dacl.allow(system_sid, 0x1, Puppet::Util::Windows::AccessControlEntry::INHERITED_ACE) sd.owner = new_sid aces = sd.dacl.to_a expect(aces.size).to eq(2) expect(aces[0].sid).to eq(new_sid) end it "does not prepend SYSTEM when security descriptor owner wasn't SYSTEM" do sd = Puppet::Util::Windows::SecurityDescriptor.new(group_sid, group_sid, empty_dacl) sd.dacl.allow(group_sid, 0x1) sd.owner = new_sid aces = sd.dacl.to_a expect(aces.size).to eq(1) expect(aces[0].sid).to eq(new_sid) end end context "group" do it "changes the group" do sd = Puppet::Util::Windows::SecurityDescriptor.new(system_sid, group_sid, system_ace_dacl) sd.group = new_sid expect(sd.group).to eq(new_sid) end it "performs a noop if the new group is the same as the old one" do dacl = system_ace_dacl sd = Puppet::Util::Windows::SecurityDescriptor.new(system_sid, group_sid, dacl) sd.group = sd.group expect(sd.dacl.object_id).to eq(dacl.object_id) end it "prepends SYSTEM when security descriptor group is no longer SYSTEM" do sd = Puppet::Util::Windows::SecurityDescriptor.new(new_sid, system_sid, system_ace_dacl) sd.group = new_sid aces = sd.dacl.to_a expect(aces.size).to eq(2) expect(aces[0].sid).to eq(system_sid) expect(aces[1].sid).to eq(new_sid) end it "does not prepend SYSTEM when DACL already contains inherited SYSTEM ace" do sd = Puppet::Util::Windows::SecurityDescriptor.new(admins_sid, admins_sid, empty_dacl) sd.dacl.allow(admins_sid, 0x1) sd.dacl.allow(system_sid, 0x1, Puppet::Util::Windows::AccessControlEntry::INHERITED_ACE) sd.group = new_sid aces = sd.dacl.to_a expect(aces.size).to eq(2) expect(aces[0].sid).to eq(new_sid) end it "does not prepend SYSTEM when security descriptor group wasn't SYSTEM" do sd = Puppet::Util::Windows::SecurityDescriptor.new(group_sid, group_sid, empty_dacl) sd.dacl.allow(group_sid, 0x1) sd.group = new_sid aces = sd.dacl.to_a expect(aces.size).to eq(1) expect(aces[0].sid).to eq(new_sid) end end end puppet-5.5.10/spec/unit/util/windows/service_spec.rb0000644005276200011600000006770513417161722022411 0ustar jenkinsjenkins#!/usr/bin/env ruby require 'spec_helper' describe "Puppet::Util::Windows::Service", :if => Puppet.features.microsoft_windows? do require 'puppet/util/windows' before(:each) do Puppet::Util::Windows::Error.stubs(:format_error_code) .with(anything) .returns("fake error!") end def service_state_str(state) Puppet::Util::Windows::Service::SERVICE_STATES[state].to_s end # The following should emulate a successful call to the private function # query_status that returns the value of query_return. This should give # us a way to mock changes in service status. # # Everything else is stubbed, the emulation of the successful call is really # just an expectation of subject::SERVICE_STATUS_PROCESS.new in sequence that # returns the value passed in as a param def expect_successful_status_query_and_return(query_return) subject::SERVICE_STATUS_PROCESS.expects(:new).in_sequence(status_checks).returns(query_return) end def expect_successful_status_queries_and_return(*query_returns) query_returns.each do |query_return| expect_successful_status_query_and_return(query_return) end end # The following should emulate a successful call to the private function # query_config that returns the value of query_return. This should give # us a way to mock changes in service configuration. # # Everything else is stubbed, the emulation of the successful call is really # just an expectation of subject::QUERY_SERVICE_CONFIGW.new in sequence that # returns the value passed in as a param def expect_successful_config_query_and_return(query_return) subject::QUERY_SERVICE_CONFIGW.expects(:new).in_sequence(status_checks).returns(query_return) end let(:subject) { Puppet::Util::Windows::Service } let(:pointer) { mock() } let(:status_checks) { sequence('status_checks') } let(:mock_service_name) { mock() } let(:service) { mock() } let(:scm) { mock() } before do subject.stubs(:QueryServiceStatusEx).returns(1) subject.stubs(:QueryServiceConfigW).returns(1) subject.stubs(:ChangeServiceConfigW).returns(1) subject.stubs(:OpenSCManagerW).returns(scm) subject.stubs(:OpenServiceW).returns(service) subject.stubs(:CloseServiceHandle) subject.stubs(:EnumServicesStatusExW).returns(1) subject.stubs(:wide_string) subject::SERVICE_STATUS_PROCESS.stubs(:new) subject::QUERY_SERVICE_CONFIGW.stubs(:new) subject::SERVICE_STATUS.stubs(:new).returns({:dwCurrentState => subject::SERVICE_RUNNING}) FFI.stubs(:errno).returns(0) FFI::MemoryPointer.stubs(:new).yields(pointer) pointer.stubs(:read_dword) pointer.stubs(:write_dword) pointer.stubs(:size) subject.stubs(:sleep) end describe "#exists?" do context "when the service control manager cannot be opened" do let(:scm) { FFI::Pointer::NULL_HANDLE } it "raises a puppet error" do expect{ subject.exists?(mock_service_name) }.to raise_error(Puppet::Error) end end context "when the service cannot be opened" do let(:service) { FFI::Pointer::NULL_HANDLE } it "returns false if it fails to open because the service does not exist" do FFI.stubs(:errno).returns(Puppet::Util::Windows::Service::ERROR_SERVICE_DOES_NOT_EXIST) expect(subject.exists?(mock_service_name)).to be false end it "raises a puppet error if it fails to open for some other reason" do expect{ subject.exists?(mock_service_name) }.to raise_error(Puppet::Error) end end context "when the service can be opened" do it "returns true" do expect(subject.exists?(mock_service_name)).to be true end end end # This shared example contains the unit tests for the wait_on_pending_state # helper as used by service actions like #start and #stop. Before including # this shared example, be sure to mock out any intermediate calls prior to # the pending transition, and make sure that the post-condition _after_ those # intermediate calls leaves the service in the pending state. Before including # this example in your tests, be sure to define the following variables in a `let` # context: # * action -- The service action shared_examples "a service action waiting on a pending transition" do |pending_state| pending_state_str = Puppet::Util::Windows::Service::SERVICE_STATES[pending_state].to_s final_state = Puppet::Util::Windows::Service::FINAL_STATES[pending_state] final_state_str = Puppet::Util::Windows::Service::SERVICE_STATES[final_state].to_s it "raises a Puppet::Error if the service query fails" do subject.expects(:QueryServiceStatusEx).in_sequence(status_checks).returns(FFI::WIN32_FALSE) expect { subject.send(action, mock_service_name) }.to raise_error(Puppet::Error) end it "raises a Puppet::Error if the service unexpectedly transitions to a state other than #{pending_state_str} or #{final_state_str}" do invalid_state = (subject::SERVICE_STATES.keys - [pending_state, final_state]).first expect_successful_status_query_and_return(dwCurrentState: invalid_state) expect { subject.send(action, mock_service_name) }.to raise_error(Puppet::Error) end it "waits for at least 1 second if the wait_hint/10 is < 1 second" do expect_successful_status_queries_and_return( { :dwCurrentState => pending_state, :dwWaitHint => 0, :dwCheckPoint => 1 }, { :dwCurrentState => final_state } ) subject.expects(:sleep).with(1) subject.send(action, mock_service_name) end it "waits for at most 10 seconds if wait_hint/10 is > 10 seconds" do expect_successful_status_queries_and_return( { :dwCurrentState => pending_state, :dwWaitHint => 1000000, :dwCheckPoint => 1 }, { :dwCurrentState => final_state } ) subject.expects(:sleep).with(10) subject.send(action, mock_service_name) end it "does not raise an error if the service makes any progress while transitioning to #{final_state_str}" do expect_successful_status_queries_and_return( # The three "pending_state" statuses simulate the scenario where the service # makes some progress during the transition right when Puppet's about to # time out. { :dwCurrentState => pending_state, :dwWaitHint => 100000, :dwCheckPoint => 1 }, { :dwCurrentState => pending_state, :dwWaitHint => 100000, :dwCheckPoint => 1 }, { :dwCurrentState => pending_state, :dwWaitHint => 100000, :dwCheckPoint => 2 }, { :dwCurrentState => final_state } ) expect { subject.send(action, mock_service_name) }.to_not raise_error end it "raises a Puppet::Error if it times out while waiting for the transition to #{final_state_str}" do 31.times do expect_successful_status_query_and_return( dwCurrentState: pending_state, dwWaitHint: 10000, dwCheckPoint: 1 ) end expect { subject.send(action, mock_service_name) }.to raise_error(Puppet::Error) end end # This shared example contains the unit tests for the transition_service_state # helper, which is the helper that all of our service actions like #start, #stop # delegate to. Including these tests under a shared example lets us include them in each of # those service action's unit tests. Before including this example in your tests, be # sure to define the following variables in a `let` context: # * initial_state -- The initial state of the service prior to performing the state # transition # # * mock_state_transition -- A lambda that mocks the state transition. This should mock # any code in the block that's passed to the # transition_service_state helper # # See the unit tests for the #start method to see how this shared example's # included. # shared_examples "a service action that transitions the service state" do |action, valid_initial_states, pending_state, final_state| valid_initial_states_str = valid_initial_states.map do |state| Puppet::Util::Windows::Service::SERVICE_STATES[state] end.join(', ') pending_state_str = Puppet::Util::Windows::Service::SERVICE_STATES[pending_state].to_s final_state_str = Puppet::Util::Windows::Service::SERVICE_STATES[final_state].to_s it "noops if the service is already in the #{final_state} state" do expect_successful_status_query_and_return(dwCurrentState: final_state) expect { subject.send(action, mock_service_name) }.to_not raise_error end # invalid_initial_states will be empty for the #stop action invalid_initial_states = Puppet::Util::Windows::Service::SERVICE_STATES.keys - valid_initial_states - [final_state] unless invalid_initial_states.empty? it "raises a Puppet::Error if the service's initial state is not one of #{valid_initial_states_str}" do invalid_initial_state = invalid_initial_states.first expect_successful_status_query_and_return(dwCurrentState: invalid_initial_state) expect{ subject.send(action, mock_service_name) }.to raise_error(Puppet::Error) end end context "when there's a pending transition to the #{final_state} state" do before(:each) do expect_successful_status_query_and_return(dwCurrentState: pending_state) end include_examples "a service action waiting on a pending transition", pending_state do let(:action) { action } end end # If the service action accepts an unsafe pending state as one of the service's # initial states, then we need to test that the action waits for the service to # transition from that unsafe pending state before doing anything else. unsafe_pending_states = valid_initial_states & Puppet::Util::Windows::Service::UNSAFE_PENDING_STATES unless unsafe_pending_states.empty? unsafe_pending_state = unsafe_pending_states.first unsafe_pending_state_str = Puppet::Util::Windows::Service::SERVICE_STATES[unsafe_pending_state] context "waiting for a service with #{unsafe_pending_state_str} as its initial state" do before(:each) do # This mocks the status query to return the 'final_state' by default. Otherwise, # we will fail the tests in the latter parts of the code where we wait for the # service to finish transitioning to the 'final_state'. subject::SERVICE_STATUS_PROCESS.stubs(:new).returns(dwCurrentState: final_state) # Set our service's initial state expect_successful_status_query_and_return(dwCurrentState: unsafe_pending_state) mock_state_transition.call end include_examples "a service action waiting on a pending transition", unsafe_pending_state do let(:action) { action } end end end # reads e.g. "waiting for the service to transition to the SERVICE_RUNNING state after executing the 'start' action" # # NOTE: This is really unit testing the wait_on_state_transition helper context "waiting for the service to transition to the #{final_state_str} state after executing the '#{action}' action" do before(:each) do # Set our service's initial state prior to performing the state transition expect_successful_status_query_and_return(dwCurrentState: initial_state) mock_state_transition.call end it "raises a Puppet::Error if the service query fails" do subject.expects(:QueryServiceStatusEx).in_sequence(status_checks).returns(FFI::WIN32_FALSE) expect { subject.send(action, mock_service_name) }.to raise_error(Puppet::Error) end it "waits, then queries again until it transitions to #{final_state_str}" do expect_successful_status_queries_and_return( { :dwCurrentState => initial_state }, { :dwCurrentState => initial_state }, { :dwCurrentState => final_state } ) subject.expects(:sleep).with(1).twice subject.send(action, mock_service_name) end context "when it transitions to the #{pending_state_str} state" do before(:each) do expect_successful_status_query_and_return(dwCurrentState: pending_state) end include_examples "a service action waiting on a pending transition", pending_state do let(:action) { action } end end it "raises a Puppet::Error if it times out while waiting for the transition to #{final_state_str}" do 31.times do expect_successful_status_query_and_return(dwCurrentState: initial_state) end expect { subject.send(action, mock_service_name) }.to raise_error(Puppet::Error) end end end describe "#start" do # rspec will still try to load the tests even though # the :if => Puppet.features.microsoft_windows? filter # is passed-in to the top-level describe block on # non-Windows platforms; it just won't run them. However # on these platforms, the loading will fail because this # test uses a shared example that references variables # from the Windows::Service module when building the unit # tests, which is only available on Windows platforms. # Thus, we add the next here to ensure that rspec does not # attempt to load our test code. This is OK for us to do # because we do not want to run these tests on non-Windows # platforms. next unless Puppet.features.microsoft_windows? context "when the service control manager cannot be opened" do let(:scm) { FFI::Pointer::NULL_HANDLE } it "raises a puppet error" do expect{ subject.start(mock_service_name) }.to raise_error(Puppet::Error) end end context "when the service cannot be opened" do let(:service) { FFI::Pointer::NULL_HANDLE } it "raises a puppet error" do expect{ subject.start(mock_service_name) }.to raise_error(Puppet::Error) end end context "when the service can be opened" do # Can't use rspec's subject here because that # can only be referenced inside an 'it' block. service = Puppet::Util::Windows::Service valid_initial_states = [ service::SERVICE_STOP_PENDING, service::SERVICE_STOPPED, service::SERVICE_START_PENDING ] final_state = service::SERVICE_RUNNING include_examples "a service action that transitions the service state", :start, valid_initial_states, service::SERVICE_START_PENDING, final_state do let(:initial_state) { subject::SERVICE_STOPPED } let(:mock_state_transition) do lambda do subject.stubs(:StartServiceW).returns(1) end end end it "raises a Puppet::Error if StartServiceW returns false" do expect_successful_status_query_and_return(dwCurrentState: subject::SERVICE_STOPPED) subject.expects(:StartServiceW).returns(FFI::WIN32_FALSE) expect { subject.start(mock_service_name) }.to raise_error(Puppet::Error) end it "starts the service" do expect_successful_status_queries_and_return( { dwCurrentState: subject::SERVICE_STOPPED }, { dwCurrentState: subject::SERVICE_RUNNING } ) subject.expects(:StartServiceW).returns(1) subject.start(mock_service_name) end end end describe "#stop" do next unless Puppet.features.microsoft_windows? context "when the service control manager cannot be opened" do let(:scm) { FFI::Pointer::NULL_HANDLE } it "raises a puppet error" do expect{ subject.start(mock_service_name) }.to raise_error(Puppet::Error) end end context "when the service cannot be opened" do let(:service) { FFI::Pointer::NULL_HANDLE } it "raises a puppet error" do expect{ subject.start(mock_service_name) }.to raise_error(Puppet::Error) end end context "when the service can be opened" do service = Puppet::Util::Windows::Service valid_initial_states = service::SERVICE_STATES.keys - [service::SERVICE_STOPPED] final_state = service::SERVICE_STOPPED include_examples "a service action that transitions the service state", :stop, valid_initial_states, service::SERVICE_STOP_PENDING, final_state do let(:initial_state) { subject::SERVICE_RUNNING } let(:mock_state_transition) do lambda do subject.stubs(:ControlService).returns(1) end end end it "raises a Puppet::Error if ControlService returns false" do expect_successful_status_query_and_return(dwCurrentState: subject::SERVICE_RUNNING) subject.stubs(:ControlService).returns(FFI::WIN32_FALSE) expect { subject.stop(mock_service_name) }.to raise_error(Puppet::Error) end it "stops the service" do expect_successful_status_queries_and_return( { dwCurrentState: subject::SERVICE_RUNNING }, { dwCurrentState: subject::SERVICE_STOPPED } ) subject.expects(:ControlService).returns(1) subject.stop(mock_service_name) end end end describe "#resume" do next unless Puppet.features.microsoft_windows? context "when the service control manager cannot be opened" do let(:scm) { FFI::Pointer::NULL_HANDLE } it "raises a puppet error" do expect{ subject.start(mock_service_name) }.to raise_error(Puppet::Error) end end context "when the service cannot be opened" do let(:service) { FFI::Pointer::NULL_HANDLE } it "raises a puppet error" do expect{ subject.start(mock_service_name) }.to raise_error(Puppet::Error) end end context "when the service can be opened" do service = Puppet::Util::Windows::Service valid_initial_states = [ service::SERVICE_PAUSE_PENDING, service::SERVICE_PAUSED, service::SERVICE_CONTINUE_PENDING ] final_state = service::SERVICE_RUNNING include_examples "a service action that transitions the service state", :resume, valid_initial_states, service::SERVICE_CONTINUE_PENDING, final_state do let(:initial_state) { service::SERVICE_PAUSED } let(:mock_state_transition) do lambda do # We need to mock the status query because in the block for #resume, we # wait for the service to enter the SERVICE_PAUSED state prior to # performing the transition (in case it is in SERVICE_PAUSE_PENDING). expect_successful_status_query_and_return(dwCurrentState: subject::SERVICE_PAUSED) subject.stubs(:ControlService).returns(1) end end end context "waiting for the SERVICE_PAUSE_PENDING => SERVICE_PAUSED transition to finish before resuming it" do before(:each) do # This mocks the status query to return the SERVICE_RUNNING state by default. # Otherwise, we will fail the tests in the latter parts of the code where we # wait for the service to finish transitioning to the 'SERVICE_RUNNING' state. subject::SERVICE_STATUS_PROCESS.stubs(:new).returns(dwCurrentState: subject::SERVICE_RUNNING) expect_successful_status_query_and_return(dwCurrentState: subject::SERVICE_PAUSE_PENDING) subject.stubs(:ControlService).returns(1) end include_examples "a service action waiting on a pending transition", service::SERVICE_PAUSE_PENDING do let(:action) { :resume } end end it "raises a Puppet::Error if ControlService returns false" do expect_successful_status_query_and_return(dwCurrentState: subject::SERVICE_PAUSED) expect_successful_status_query_and_return(dwCurrentState: subject::SERVICE_PAUSED) subject.stubs(:ControlService).returns(FFI::WIN32_FALSE) expect { subject.resume(mock_service_name) }.to raise_error(Puppet::Error) end it "resumes the service" do expect_successful_status_queries_and_return( { dwCurrentState: subject::SERVICE_PAUSED }, { dwCurrentState: subject::SERVICE_PAUSED }, { dwCurrentState: subject::SERVICE_RUNNING } ) subject.expects(:ControlService).returns(1) subject.resume(mock_service_name) end end end describe "#service_state" do context "when the service control manager cannot be opened" do let(:scm) { FFI::Pointer::NULL_HANDLE } it "raises a puppet error" do expect{ subject.service_state(mock_service_name) }.to raise_error(Puppet::Error) end end context "when the service cannot be opened" do let(:service) { FFI::Pointer::NULL_HANDLE } it "raises a puppet error" do expect{ subject.service_state(mock_service_name) }.to raise_error(Puppet::Error) end end context "when the service can be opened" do it "raises Puppet::Error if the result of the query is empty" do expect_successful_status_query_and_return({}) expect{subject.service_state(mock_service_name)}.to raise_error(Puppet::Error) end it "raises Puppet::Error if the result of the query is an unknown state" do expect_successful_status_query_and_return({:dwCurrentState => 999}) expect{subject.service_state(mock_service_name)}.to raise_error(Puppet::Error) end # We need to guard this section explicitly since rspec will always # construct all examples, even if it isn't going to run them. if Puppet.features.microsoft_windows? { :SERVICE_STOPPED => Puppet::Util::Windows::Service::SERVICE_STOPPED, :SERVICE_PAUSED => Puppet::Util::Windows::Service::SERVICE_PAUSED, :SERVICE_STOP_PENDING => Puppet::Util::Windows::Service::SERVICE_STOP_PENDING, :SERVICE_PAUSE_PENDING => Puppet::Util::Windows::Service::SERVICE_PAUSE_PENDING, :SERVICE_RUNNING => Puppet::Util::Windows::Service::SERVICE_RUNNING, :SERVICE_CONTINUE_PENDING => Puppet::Util::Windows::Service::SERVICE_CONTINUE_PENDING, :SERVICE_START_PENDING => Puppet::Util::Windows::Service::SERVICE_START_PENDING, }.each do |state_name, state| it "queries the service and returns #{state_name}" do expect_successful_status_query_and_return({:dwCurrentState => state}) expect(subject.service_state(mock_service_name)).to eq(state_name) end end end end end describe "#service_start_type" do context "when the service control manager cannot be opened" do let(:scm) { FFI::Pointer::NULL_HANDLE } it "raises a puppet error" do expect{ subject.service_start_type(mock_service_name) }.to raise_error(Puppet::Error) end end context "when the service cannot be opened" do let(:service) { FFI::Pointer::NULL_HANDLE } it "raises a puppet error" do expect{ subject.service_start_type(mock_service_name) }.to raise_error(Puppet::Error) end end context "when the service can be opened" do # We need to guard this section explicitly since rspec will always # construct all examples, even if it isn't going to run them. if Puppet.features.microsoft_windows? { :SERVICE_AUTO_START => Puppet::Util::Windows::Service::SERVICE_AUTO_START, :SERVICE_BOOT_START => Puppet::Util::Windows::Service::SERVICE_BOOT_START, :SERVICE_SYSTEM_START => Puppet::Util::Windows::Service::SERVICE_SYSTEM_START, :SERVICE_DEMAND_START => Puppet::Util::Windows::Service::SERVICE_DEMAND_START, :SERVICE_DISABLED => Puppet::Util::Windows::Service::SERVICE_DISABLED, }.each do |start_type_name, start_type| it "queries the service and returns the service start type #{start_type_name}" do expect_successful_config_query_and_return({:dwStartType => start_type}) expect(subject.service_start_type(mock_service_name)).to eq(start_type_name) end end end it "raises a puppet error if the service query fails" do subject.expects(:QueryServiceConfigW).in_sequence(status_checks) subject.expects(:QueryServiceConfigW).in_sequence(status_checks).returns(FFI::WIN32_FALSE) expect{ subject.service_start_type(mock_service_name) }.to raise_error(Puppet::Error) end end end describe "#set_startup_mode" do let(:status_checks) { sequence('status_checks') } context "when the service control manager cannot be opened" do let(:scm) { FFI::Pointer::NULL_HANDLE } it "raises a puppet error" do expect{ subject.set_startup_mode(mock_service_name, :SERVICE_DEMAND_START) }.to raise_error(Puppet::Error) end end context "when the service cannot be opened" do let(:service) { FFI::Pointer::NULL_HANDLE } it "raises a puppet error" do expect{ subject.set_startup_mode(mock_service_name, :SERVICE_DEMAND_START) }.to raise_error(Puppet::Error) end end context "when the service can be opened" do it "Raises an error on an unsuccessful change" do subject.expects(:ChangeServiceConfigW).returns(FFI::WIN32_FALSE) expect{ subject.set_startup_mode(mock_service_name, :SERVICE_DEMAND_START) }.to raise_error(Puppet::Error) end end end describe "#services" do let(:pointer_sequence) { sequence('pointer_sequence') } context "when the service control manager cannot be opened" do let(:scm) { FFI::Pointer::NULL_HANDLE } it "raises a puppet error" do expect{ subject.services }.to raise_error(Puppet::Error) end end context "when the service control manager is open" do let(:cursor) { [ 'svc1', 'svc2', 'svc3' ] } let(:svc1name_ptr) { mock() } let(:svc2name_ptr) { mock() } let(:svc3name_ptr) { mock() } let(:svc1displayname_ptr) { mock() } let(:svc2displayname_ptr) { mock() } let(:svc3displayname_ptr) { mock() } let(:svc1) { { :lpServiceName => svc1name_ptr, :lpDisplayName => svc1displayname_ptr, :ServiceStatusProcess => 'foo' } } let(:svc2) { { :lpServiceName => svc2name_ptr, :lpDisplayName => svc2displayname_ptr, :ServiceStatusProcess => 'foo' } } let(:svc3) { { :lpServiceName => svc3name_ptr, :lpDisplayName => svc3displayname_ptr, :ServiceStatusProcess => 'foo' } } it "Raises an error if EnumServicesStatusExW fails" do subject.expects(:EnumServicesStatusExW).in_sequence(pointer_sequence) subject.expects(:EnumServicesStatusExW).in_sequence(pointer_sequence).returns(FFI::WIN32_FALSE) expect{ subject.services }.to raise_error(Puppet::Error) end it "Reads the buffer using pointer arithmetic to create a hash of service entries" do # the first read_dword is for reading the bytes required, let that return 3 too. # the second read_dword will actually read the number of services returned pointer.expects(:read_dword).twice.returns(3) FFI::Pointer.expects(:new).with(subject::ENUM_SERVICE_STATUS_PROCESSW, pointer).returns(cursor) subject::ENUM_SERVICE_STATUS_PROCESSW.expects(:new).in_sequence(pointer_sequence).with('svc1').returns(svc1) subject::ENUM_SERVICE_STATUS_PROCESSW.expects(:new).in_sequence(pointer_sequence).with('svc2').returns(svc2) subject::ENUM_SERVICE_STATUS_PROCESSW.expects(:new).in_sequence(pointer_sequence).with('svc3').returns(svc3) svc1name_ptr.expects(:read_arbitrary_wide_string_up_to).returns('svc1') svc2name_ptr.expects(:read_arbitrary_wide_string_up_to).returns('svc2') svc3name_ptr.expects(:read_arbitrary_wide_string_up_to).returns('svc3') svc1displayname_ptr.expects(:read_arbitrary_wide_string_up_to).returns('service 1') svc2displayname_ptr.expects(:read_arbitrary_wide_string_up_to).returns('service 2') svc3displayname_ptr.expects(:read_arbitrary_wide_string_up_to).returns('service 3') expect(subject.services).to eq({ 'svc1' => { :display_name => 'service 1', :service_status_process => 'foo' }, 'svc2' => { :display_name => 'service 2', :service_status_process => 'foo' }, 'svc3' => { :display_name => 'service 3', :service_status_process => 'foo' } }) end end end end puppet-5.5.10/spec/unit/util/windows/sid_spec.rb0000644005276200011600000002756413417161722021527 0ustar jenkinsjenkins#!/usr/bin/env ruby require 'spec_helper' describe "Puppet::Util::Windows::SID", :if => Puppet.features.microsoft_windows? do if Puppet.features.microsoft_windows? require 'puppet/util/windows' end let(:subject) { Puppet::Util::Windows::SID } let(:sid) { Puppet::Util::Windows::SID::LocalSystem } let(:invalid_sid) { 'bogus' } let(:unknown_sid) { 'S-0-0-0' } let(:null_sid) { 'S-1-0-0' } let(:unknown_name) { 'chewbacca' } context "#octet_string_to_principal" do it "should properly convert an array of bytes for a well-known non-localized SID" do bytes = [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] converted = subject.octet_string_to_principal(bytes) expect(converted).to be_an_instance_of Puppet::Util::Windows::SID::Principal expect(converted.sid_bytes).to eq(bytes) expect(converted.sid).to eq(null_sid) # carefully select a SID here that is not localized on international Windows expect(converted.account).to eq('NULL SID') end it "should raise an error for non-array input" do expect { subject.octet_string_to_principal(invalid_sid) }.to raise_error(Puppet::Error, /Octet string must be an array of bytes/) end it "should raise an error for an empty byte array" do expect { subject.octet_string_to_principal([]) }.to raise_error(Puppet::Error, /Octet string must be an array of bytes/) end it "should raise an error for a valid byte array with no mapping to a user" do expect { # S-1-1-1 which is not a valid account valid_octet_invalid_user =[1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0] subject.octet_string_to_principal(valid_octet_invalid_user) }.to raise_error do |error| expect(error).to be_a(Puppet::Util::Windows::Error) expect(error.code).to eq(1332) # ERROR_NONE_MAPPED end end it "should raise an error for a malformed byte array" do expect { invalid_octet = [2] subject.octet_string_to_principal(invalid_octet) }.to raise_error do |error| expect(error).to be_a(Puppet::Util::Windows::Error) expect(error.code).to eq(87) # ERROR_INVALID_PARAMETER end end end context "#name_to_sid" do it "should return nil if the account does not exist" do expect(subject.name_to_sid(unknown_name)).to be_nil end it "should accept unqualified account name" do # NOTE: lookup by name works in localized environments only for a few instances # this works in French Windows, even though the account is really Syst\u00E8me expect(subject.name_to_sid('SYSTEM')).to eq(sid) end it "should return a SID for a passed user or group name" do subject.expects(:name_to_principal).with('testers').returns stub(:sid => 'S-1-5-32-547') expect(subject.name_to_sid('testers')).to eq('S-1-5-32-547') end it "should return a SID for a passed fully-qualified user or group name" do subject.expects(:name_to_principal).with('MACHINE\testers').returns stub(:sid => 'S-1-5-32-547') expect(subject.name_to_sid('MACHINE\testers')).to eq('S-1-5-32-547') end it "should be case-insensitive" do expect(subject.name_to_sid('SYSTEM')).to eq(subject.name_to_sid('system')) end it "should be leading and trailing whitespace-insensitive" do expect(subject.name_to_sid('SYSTEM')).to eq(subject.name_to_sid(' SYSTEM ')) end it "should accept domain qualified account names" do # NOTE: lookup by name works in localized environments only for a few instances # this works in French Windows, even though the account is really AUTORITE NT\\Syst\u00E8me expect(subject.name_to_sid('NT AUTHORITY\SYSTEM')).to eq(sid) end it "should be the identity function for any sid" do expect(subject.name_to_sid(sid)).to eq(sid) end describe "with non-US languages" do UMLAUT = [195, 164].pack('c*').force_encoding(Encoding::UTF_8) let(:username) { SecureRandom.uuid.to_s.gsub(/\-/, '')[0..13] + UMLAUT } after(:each) { Puppet::Util::Windows::ADSI::User.delete(username) } it "should properly resolve a username with an umlaut" do # Ruby seems to use the local codepage when making COM calls # if this fails, might want to use Windows API directly instead to ensure bytes user = Puppet::Util::Windows::ADSI.create(username, 'user') user.SetPassword('PUPPET_RULeZ_123!') user.SetInfo() # compare the new SID to the name_to_sid result sid_bytes = user.objectSID.to_a sid_string = '' FFI::MemoryPointer.new(:byte, sid_bytes.length) do |sid_byte_ptr| sid_byte_ptr.write_array_of_uchar(sid_bytes) sid_string = Puppet::Util::Windows::SID.sid_ptr_to_string(sid_byte_ptr) end expect(subject.name_to_sid(username)).to eq(sid_string) end end end context "#name_to_principal" do it "should return nil if the account does not exist" do expect(subject.name_to_principal(unknown_name)).to be_nil end it "should return a Puppet::Util::Windows::SID::Principal instance for any valid sid" do expect(subject.name_to_principal(sid)).to be_an_instance_of(Puppet::Util::Windows::SID::Principal) end it "should accept unqualified account name" do # NOTE: lookup by name works in localized environments only for a few instances # this works in French Windows, even though the account is really Syst\u00E8me expect(subject.name_to_principal('SYSTEM').sid).to eq(sid) end it "should be case-insensitive" do # NOTE: lookup by name works in localized environments only for a few instances # this works in French Windows, even though the account is really Syst\u00E8me expect(subject.name_to_principal('SYSTEM')).to eq(subject.name_to_principal('system')) end it "should be leading and trailing whitespace-insensitive" do # NOTE: lookup by name works in localized environments only for a few instances # this works in French Windows, even though the account is really Syst\u00E8me expect(subject.name_to_principal('SYSTEM')).to eq(subject.name_to_principal(' SYSTEM ')) end it "should accept domain qualified account names" do # NOTE: lookup by name works in localized environments only for a few instances # this works in French Windows, even though the account is really AUTORITE NT\\Syst\u00E8me expect(subject.name_to_principal('NT AUTHORITY\SYSTEM').sid).to eq(sid) end end context "#ads_to_principal" do it "should raise an error for non-WIN32OLE input" do expect { subject.ads_to_principal(stub('WIN32OLE', { :Name => 'foo' })) }.to raise_error(Puppet::Error, /ads_object must be an IAdsUser or IAdsGroup instance/) end it "should raise an error for an empty byte array in the objectSID property" do expect { subject.ads_to_principal(stub('WIN32OLE', { :objectSID => [], :Name => '', :ole_respond_to? => true })) }.to raise_error(Puppet::Error, /Octet string must be an array of bytes/) end it "should raise an error for a malformed byte array" do expect { invalid_octet = [2] subject.ads_to_principal(stub('WIN32OLE', { :objectSID => invalid_octet, :Name => '', :ole_respond_to? => true })) }.to raise_error do |error| expect(error).to be_a(Puppet::Util::Windows::Error) expect(error.code).to eq(87) # ERROR_INVALID_PARAMETER end end it "should raise an error when a valid byte array for SID is unresolvable and its Name does not match" do expect { # S-1-1-1 is a valid SID that will not resolve valid_octet_invalid_user = [1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0] subject.ads_to_principal(stub('WIN32OLE', { :objectSID => valid_octet_invalid_user, :Name => unknown_name, :ole_respond_to? => true })) }.to raise_error do |error| expect(error).to be_a(Puppet::Error) expect(error.cause.code).to eq(1332) # ERROR_NONE_MAPPED end end it "should return a Principal object even when the SID is unresolvable, as long as the Name matches" do # S-1-1-1 is a valid SID that will not resolve valid_octet_invalid_user = [1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0] unresolvable_user = stub('WIN32OLE', { :objectSID => valid_octet_invalid_user, :Name => 'S-1-1-1', :ole_respond_to? => true }) principal = subject.ads_to_principal(unresolvable_user) expect(principal).to be_an_instance_of(Puppet::Util::Windows::SID::Principal) expect(principal.account).to eq('S-1-1-1 (unresolvable)') expect(principal.domain).to eq(nil) expect(principal.domain_account).to eq('S-1-1-1 (unresolvable)') expect(principal.sid).to eq('S-1-1-1') expect(principal.sid_bytes).to eq(valid_octet_invalid_user) expect(principal.account_type).to eq(:SidTypeUnknown) end it "should return a Puppet::Util::Windows::SID::Principal instance for any valid sid" do system_bytes = [1, 1, 0, 0, 0, 0, 0, 5, 18, 0, 0, 0] adsuser = stub('WIN32OLE', { :objectSID => system_bytes, :Name => 'SYSTEM', :ole_respond_to? => true }) expect(subject.ads_to_principal(adsuser)).to be_an_instance_of(Puppet::Util::Windows::SID::Principal) end it "should properly convert an array of bytes for a well-known non-localized SID, ignoring the Name from the WIN32OLE object" do bytes = [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] adsuser = stub('WIN32OLE', { :objectSID => bytes, :Name => unknown_name, :ole_respond_to? => true }) converted = subject.ads_to_principal(adsuser) expect(converted).to be_an_instance_of Puppet::Util::Windows::SID::Principal expect(converted.sid_bytes).to eq(bytes) expect(converted.sid).to eq(null_sid) # carefully select a SID here that is not localized on international Windows expect(converted.account).to eq('NULL SID') # garbage name supplied does not carry forward as SID is looked up again expect(converted.account).to_not eq(adsuser.Name) end end context "#sid_to_name" do it "should return nil if given a sid for an account that doesn't exist" do expect(subject.sid_to_name(unknown_sid)).to be_nil end it "should accept a sid" do # choose a value that is not localized, for instance # S-1-5-18 can be NT AUTHORITY\\SYSTEM or AUTORITE NT\\Syst\u00E8me # but NULL SID appears universal expect(subject.sid_to_name(null_sid)).to eq('NULL SID') end end context "#sid_ptr_to_string" do it "should raise if given an invalid sid" do expect { subject.sid_ptr_to_string(nil) }.to raise_error(Puppet::Error, /Invalid SID/) end it "should yield a valid sid pointer" do string = nil subject.string_to_sid_ptr(sid) do |ptr| string = subject.sid_ptr_to_string(ptr) end expect(string).to eq(sid) end end context "#string_to_sid_ptr" do it "should yield sid_ptr" do ptr = nil subject.string_to_sid_ptr(sid) do |p| ptr = p end expect(ptr).not_to be_nil end it "should raise on an invalid sid" do expect { subject.string_to_sid_ptr(invalid_sid) }.to raise_error(Puppet::Error, /Failed to convert string SID/) end end context "#valid_sid?" do it "should return true for a valid SID" do expect(subject.valid_sid?(sid)).to be_truthy end it "should return false for an invalid SID" do expect(subject.valid_sid?(invalid_sid)).to be_falsey end it "should raise if the conversion fails" do subject.expects(:string_to_sid_ptr).with(sid). raises(Puppet::Util::Windows::Error.new("Failed to convert string SID: #{sid}", Puppet::Util::Windows::Error::ERROR_ACCESS_DENIED)) expect { subject.string_to_sid_ptr(sid) {|ptr| } }.to raise_error(Puppet::Util::Windows::Error, /Failed to convert string SID: #{sid}/) end end end puppet-5.5.10/spec/unit/util/windows/string_spec.rb0000644005276200011600000000311513417161722022240 0ustar jenkinsjenkins# encoding: UTF-8 #!/usr/bin/env ruby require 'spec_helper' require 'puppet/util/windows' describe "Puppet::Util::Windows::String", :if => Puppet.features.microsoft_windows? do UTF16_NULL = [0, 0] def wide_string(str) Puppet::Util::Windows::String.wide_string(str) end def converts_to_wide_string(string_value) expected = string_value.encode(Encoding::UTF_16LE) expected_bytes = expected.bytes.to_a + UTF16_NULL expect(wide_string(string_value).bytes.to_a).to eq(expected_bytes) end context "wide_string" do it "should return encoding of UTF-16LE" do expect(wide_string("bob").encoding).to eq(Encoding::UTF_16LE) end it "should return valid encoding" do expect(wide_string("bob").valid_encoding?).to be_truthy end it "should convert an ASCII string" do converts_to_wide_string("bob".encode(Encoding::US_ASCII)) end it "should convert a UTF-8 string" do converts_to_wide_string("bob".encode(Encoding::UTF_8)) end it "should convert a UTF-16LE string" do converts_to_wide_string("bob\u00E8".encode(Encoding::UTF_16LE)) end it "should convert a UTF-16BE string" do converts_to_wide_string("bob\u00E8".encode(Encoding::UTF_16BE)) end it "should convert an UTF-32LE string" do converts_to_wide_string("bob\u00E8".encode(Encoding::UTF_32LE)) end it "should convert an UTF-32BE string" do converts_to_wide_string("bob\u00E8".encode(Encoding::UTF_32BE)) end it "should return a nil when given a nil" do expect(wide_string(nil)).to eq(nil) end end end puppet-5.5.10/spec/unit/util/autoload_spec.rb0000644005276200011600000002276113417161722021060 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/autoload' describe Puppet::Util::Autoload do include PuppetSpec::Files before do @autoload = Puppet::Util::Autoload.new("foo", "tmp") @loaded = {} @autoload.class.stubs(:loaded).returns(@loaded) end describe "when building the search path" do before :each do ## modulepath/libdir can't be used until after app settings are initialized, so we need to simulate that: Puppet.settings.expects(:app_defaults_initialized?).returns(true).at_least_once end it "should collect all of the lib directories that exist in the current environment's module path" do dira = dir_containing('dir_a', { "one" => {}, "two" => { "lib" => {} } }) dirb = dir_containing('dir_a', { "one" => {}, "two" => { "lib" => {} } }) environment = Puppet::Node::Environment.create(:foo, [dira, dirb]) expect(@autoload.class.module_directories(environment)).to eq(["#{dira}/two/lib", "#{dirb}/two/lib"]) end it "ignores missing module directories" do environment = Puppet::Node::Environment.create(:foo, [File.expand_path('does/not/exist')]) expect(@autoload.class.module_directories(environment)).to be_empty end it "ignores the configured environment when it doesn't exist" do Puppet[:environment] = 'nonexistent' Puppet.override({ :environments => Puppet::Environments::Static.new() }) do expect(@autoload.class.module_directories(nil)).to be_empty end end it "uses the configured environment when no environment is given" do Puppet[:environment] = 'nonexistent' Puppet.override({ :environments => Puppet::Environments::Static.new() }) do expect(@autoload.class.module_directories(nil)).to be_empty end end it "should include the module directories, the Puppet libdir, and all of the Ruby load directories" do Puppet[:libdir] = '/libdir1' @autoload.class.expects(:gem_directories).returns %w{/one /two} @autoload.class.expects(:module_directories).returns %w{/three /four} expect(@autoload.class.search_directories(nil)).to eq(%w{/one /two /three /four} + [Puppet[:libdir]] + $LOAD_PATH) end it "does not split the Puppet[:libdir]" do Puppet[:libdir] = "/libdir1#{File::PATH_SEPARATOR}/libdir2" expect(@autoload.class.libdirs).to eq([Puppet[:libdir]]) end end describe "when loading a file" do before do @autoload.class.stubs(:search_directories).returns [make_absolute("/a")] FileTest.stubs(:directory?).returns true @time_a = Time.utc(2010, 'jan', 1, 6, 30) File.stubs(:mtime).returns @time_a end [RuntimeError, LoadError, SyntaxError].each do |error| it "should die with Puppet::Error if a #{error.to_s} exception is thrown" do Puppet::FileSystem.stubs(:exist?).returns true Kernel.expects(:load).raises error expect { @autoload.load("foo") }.to raise_error(Puppet::Error) end end it "should not raise an error if the file is missing" do expect(@autoload.load("foo")).to eq(false) end it "should register loaded files with the autoloader" do Puppet::FileSystem.stubs(:exist?).returns true Kernel.stubs(:load) @autoload.load("myfile") expect(@autoload.class.loaded?("tmp/myfile.rb")).to be $LOADED_FEATURES.delete("tmp/myfile.rb") end it "should be seen by loaded? on the instance using the short name" do Puppet::FileSystem.stubs(:exist?).returns true Kernel.stubs(:load) @autoload.load("myfile") expect(@autoload.loaded?("myfile.rb")).to be $LOADED_FEATURES.delete("tmp/myfile.rb") end it "should register loaded files with the main loaded file list so they are not reloaded by ruby" do Puppet::FileSystem.stubs(:exist?).returns true Kernel.stubs(:load) @autoload.load("myfile") expect($LOADED_FEATURES).to be_include("tmp/myfile.rb") $LOADED_FEATURES.delete("tmp/myfile.rb") end it "should load the first file in the searchpath" do @autoload.stubs(:search_directories).returns [make_absolute("/a"), make_absolute("/b")] FileTest.stubs(:directory?).returns true Puppet::FileSystem.stubs(:exist?).returns true Kernel.expects(:load).with(make_absolute("/a/tmp/myfile.rb"), optionally(anything)) @autoload.load("myfile") $LOADED_FEATURES.delete("tmp/myfile.rb") end it "should treat equivalent paths to a loaded file as loaded" do Puppet::FileSystem.stubs(:exist?).returns true Kernel.stubs(:load) @autoload.load("myfile") expect(@autoload.class.loaded?("tmp/myfile")).to be expect(@autoload.class.loaded?("tmp/./myfile.rb")).to be expect(@autoload.class.loaded?("./tmp/myfile.rb")).to be expect(@autoload.class.loaded?("tmp/../tmp/myfile.rb")).to be $LOADED_FEATURES.delete("tmp/myfile.rb") end end describe "when loading all files" do before do @autoload.class.stubs(:search_directories).returns [make_absolute("/a")] FileTest.stubs(:directory?).returns true Dir.stubs(:glob).returns [make_absolute("/a/foo/file.rb")] Puppet::FileSystem.stubs(:exist?).returns true @time_a = Time.utc(2010, 'jan', 1, 6, 30) File.stubs(:mtime).returns @time_a @autoload.class.stubs(:loaded?).returns(false) end [RuntimeError, LoadError, SyntaxError].each do |error| it "should die an if a #{error.to_s} exception is thrown" do Kernel.expects(:load).raises error expect { @autoload.loadall }.to raise_error(Puppet::Error) end end it "should require the full path to the file" do Kernel.expects(:load).with(make_absolute("/a/foo/file.rb"), optionally(anything)) @autoload.loadall end end describe "when reloading files" do before :each do @file_a = make_absolute("/a/file.rb") @file_b = make_absolute("/b/file.rb") @first_time = Time.utc(2010, 'jan', 1, 6, 30) @second_time = @first_time + 60 end after :each do $LOADED_FEATURES.delete("a/file.rb") $LOADED_FEATURES.delete("b/file.rb") end it "#changed? should return true for a file that was not loaded" do expect(@autoload.class.changed?(@file_a)).to be end it "changes should be seen by changed? on the instance using the short name" do File.stubs(:mtime).returns(@first_time) Puppet::FileSystem.stubs(:exist?).returns true Kernel.stubs(:load) @autoload.load("myfile") expect(@autoload.loaded?("myfile")).to be expect(@autoload.changed?("myfile")).not_to be File.stubs(:mtime).returns(@second_time) expect(@autoload.changed?("myfile")).to be $LOADED_FEATURES.delete("tmp/myfile.rb") end describe "in one directory" do before :each do @autoload.class.stubs(:search_directories).returns [make_absolute("/a")] File.expects(:mtime).with(@file_a).returns(@first_time) @autoload.class.mark_loaded("file", @file_a) end it "should reload if mtime changes" do File.stubs(:mtime).with(@file_a).returns(@first_time + 60) Puppet::FileSystem.stubs(:exist?).with(@file_a).returns true Kernel.expects(:load).with(@file_a, optionally(anything)) @autoload.class.reload_changed end it "should do nothing if the file is deleted" do File.stubs(:mtime).with(@file_a).raises(Errno::ENOENT) Puppet::FileSystem.stubs(:exist?).with(@file_a).returns false Kernel.expects(:load).never @autoload.class.reload_changed end end describe "in two directories" do before :each do @autoload.class.stubs(:search_directories).returns [make_absolute("/a"), make_absolute("/b")] end it "should load b/file when a/file is deleted" do File.expects(:mtime).with(@file_a).returns(@first_time) @autoload.class.mark_loaded("file", @file_a) File.stubs(:mtime).with(@file_a).raises(Errno::ENOENT) Puppet::FileSystem.stubs(:exist?).with(@file_a).returns false Puppet::FileSystem.stubs(:exist?).with(@file_b).returns true File.stubs(:mtime).with(@file_b).returns @first_time Kernel.expects(:load).with(@file_b, optionally(anything)) @autoload.class.reload_changed expect(@autoload.class.send(:loaded)["file"]).to eq([@file_b, @first_time]) end it "should load a/file when b/file is loaded and a/file is created" do File.stubs(:mtime).with(@file_b).returns @first_time Puppet::FileSystem.stubs(:exist?).with(@file_b).returns true @autoload.class.mark_loaded("file", @file_b) File.stubs(:mtime).with(@file_a).returns @first_time Puppet::FileSystem.stubs(:exist?).with(@file_a).returns true Kernel.expects(:load).with(@file_a, optionally(anything)) @autoload.class.reload_changed expect(@autoload.class.send(:loaded)["file"]).to eq([@file_a, @first_time]) end end end describe "#cleanpath" do it "should leave relative paths relative" do path = "hello/there" expect(Puppet::Util::Autoload.cleanpath(path)).to eq(path) end describe "on Windows", :if => Puppet.features.microsoft_windows? do it "should convert c:\ to c:/" do expect(Puppet::Util::Autoload.cleanpath('c:\\')).to eq('c:/') end end end describe "#expand" do it "should expand relative to the autoloader's prefix" do expect(@autoload.expand('bar')).to eq('tmp/bar') end end end puppet-5.5.10/spec/unit/util/character_encoding_spec.rb0000644005276200011600000002504013417161722023043 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/character_encoding' require 'puppet_spec/character_encoding' describe Puppet::Util::CharacterEncoding do describe "::convert_to_utf_8" do context "when passed a string that is already UTF-8" do context "with valid encoding" do let(:utf8_string) { "\u06FF\u2603".force_encoding(Encoding::UTF_8) } it "should return the string unmodified" do expect(Puppet::Util::CharacterEncoding.convert_to_utf_8(utf8_string)).to eq("\u06FF\u2603".force_encoding(Encoding::UTF_8)) end it "should not mutate the original string" do expect(utf8_string).to eq("\u06FF\u2603".force_encoding(Encoding::UTF_8)) end end context "with invalid encoding" do let(:invalid_utf8_string) { "\xfd\xf1".force_encoding(Encoding::UTF_8) } it "should issue a debug message" do Puppet.expects(:debug).with(regexp_matches(/encoding is invalid/)) Puppet::Util::CharacterEncoding.convert_to_utf_8(invalid_utf8_string) end it "should return the string unmodified" do expect(Puppet::Util::CharacterEncoding.convert_to_utf_8(invalid_utf8_string)).to eq("\xfd\xf1".force_encoding(Encoding::UTF_8)) end it "should not mutate the original string" do Puppet::Util::CharacterEncoding.convert_to_utf_8(invalid_utf8_string) expect(invalid_utf8_string).to eq("\xfd\xf1".force_encoding(Encoding::UTF_8)) end end end context "when passed a string in BINARY encoding" do context "that is valid in Encoding.default_external" do # When received as BINARY are not transcodable, but by "guessing" # Encoding.default_external can transcode to UTF-8 let(:win_31j) { [130, 187].pack('C*') } # pack('C*') returns string in BINARY it "should be able to convert to UTF-8 by labeling as Encoding.default_external" do # ăť - HIRAGANA LETTER SO # In Windows_31J: \x82 \xbb - 130 187 # In Unicode: \u305d - \xe3 \x81 \x9d - 227 129 157 result = PuppetSpec::CharacterEncoding.with_external_encoding(Encoding::Windows_31J) do Puppet::Util::CharacterEncoding.convert_to_utf_8(win_31j) end expect(result).to eq("\u305d") expect(result.bytes.to_a).to eq([227, 129, 157]) end it "should not mutate the original string" do PuppetSpec::CharacterEncoding.with_external_encoding(Encoding::Windows_31J) do Puppet::Util::CharacterEncoding.convert_to_utf_8(win_31j) end expect(win_31j).to eq([130, 187].pack('C*')) end end context "that is invalid in Encoding.default_external" do let(:invalid_win_31j) { [255, 254, 253].pack('C*') } # these bytes are not valid windows_31j it "should return the string umodified" do result = PuppetSpec::CharacterEncoding.with_external_encoding(Encoding::Windows_31J) do Puppet::Util::CharacterEncoding.convert_to_utf_8(invalid_win_31j) end expect(result.bytes.to_a).to eq([255, 254, 253]) expect(result.encoding).to eq(Encoding::BINARY) end it "should not mutate the original string" do PuppetSpec::CharacterEncoding.with_external_encoding(Encoding::Windows_31J) do Puppet::Util::CharacterEncoding.convert_to_utf_8(invalid_win_31j) end expect(invalid_win_31j).to eq([255, 254, 253].pack('C*')) end it "should issue a debug message that the string was not transcodable" do Puppet.expects(:debug).with(regexp_matches(/cannot be transcoded/)) PuppetSpec::CharacterEncoding.with_external_encoding(Encoding::Windows_31J) do Puppet::Util::CharacterEncoding.convert_to_utf_8(invalid_win_31j) end end end context "Given a string labeled as neither UTF-8 nor BINARY" do context "that is transcodable" do let (:shift_jis) { [130, 174].pack('C*').force_encoding(Encoding::Shift_JIS) } it "should return a copy of the string transcoded to UTF-8 if it is transcodable" do # http://www.fileformat.info/info/unicode/char/3050/index.htm # ă - HIRAGANA LETTER GU # In Shift_JIS: \x82 \xae - 130 174 # In Unicode: \u3050 - \xe3 \x81 \x90 - 227 129 144 # if we were only ruby > 2.3.0, we could do String.new("\x82\xae", :encoding => Encoding::Shift_JIS) result = Puppet::Util::CharacterEncoding.convert_to_utf_8(shift_jis) expect(result).to eq("\u3050".force_encoding(Encoding::UTF_8)) # largely redundant but reinforces the point - this was transcoded: expect(result.bytes.to_a).to eq([227, 129, 144]) end it "should not mutate the original string" do Puppet::Util::CharacterEncoding.convert_to_utf_8(shift_jis) expect(shift_jis).to eq([130, 174].pack('C*').force_encoding(Encoding::Shift_JIS)) end end context "when not transcodable" do # An admittedly contrived case, but perhaps not so improbable # http://www.fileformat.info/info/unicode/char/5e0c/index.htm # 希 Han Character 'rare; hope, expect, strive for' # In EUC_KR: \xfd \xf1 - 253 241 # In Unicode: \u5e0c - \xe5 \xb8 \x8c - 229 184 140 # In this case, this EUC_KR character has been read in as ASCII and is # invalid in that encoding. This would raise an EncodingError # exception on transcode but we catch this issue a debug message - # leaving the original string unaltered. let(:euc_kr) { [253, 241].pack('C*').force_encoding(Encoding::ASCII) } it "should issue a debug message" do Puppet.expects(:debug).with(regexp_matches(/cannot be transcoded/)) Puppet::Util::CharacterEncoding.convert_to_utf_8(euc_kr) end it "should return the original string unmodified" do result = Puppet::Util::CharacterEncoding.convert_to_utf_8(euc_kr) expect(result).to eq([253, 241].pack('C*').force_encoding(Encoding::ASCII)) end it "should not mutate the original string" do Puppet::Util::CharacterEncoding.convert_to_utf_8(euc_kr) expect(euc_kr).to eq([253, 241].pack('C*').force_encoding(Encoding::ASCII)) end end end end end describe "::override_encoding_to_utf_8" do context "given a string with bytes that represent valid UTF-8" do # â - unicode snowman # \u2603 - \xe2 \x98 \x83 - 226 152 131 let(:snowman) { [226, 152, 131].pack('C*') } it "should return a copy of the string with external encoding of the string to UTF-8" do result = Puppet::Util::CharacterEncoding.override_encoding_to_utf_8(snowman) expect(result).to eq("\u2603") expect(result.encoding).to eq(Encoding::UTF_8) end it "should not modify the original string" do Puppet::Util::CharacterEncoding.override_encoding_to_utf_8(snowman) expect(snowman).to eq([226, 152, 131].pack('C*')) end end context "given a string with bytes that do not represent valid UTF-8" do # Ă - Latin capital letter O with stroke # In ISO-8859-1: \xd8 - 216 # Invalid in UTF-8 without transcoding let(:oslash) { [216].pack('C*').force_encoding(Encoding::ISO_8859_1) } let(:foo) { 'foo' } it "should issue a debug message" do Puppet.expects(:debug).with(regexp_matches(/not valid UTF-8/)) Puppet::Util::CharacterEncoding.override_encoding_to_utf_8(oslash) end it "should return the original string unmodified" do result = Puppet::Util::CharacterEncoding.override_encoding_to_utf_8(oslash) expect(result).to eq([216].pack('C*').force_encoding(Encoding::ISO_8859_1)) end it "should not modify the string" do Puppet::Util::CharacterEncoding.override_encoding_to_utf_8(oslash) expect(oslash).to eq([216].pack('C*').force_encoding(Encoding::ISO_8859_1)) end end end describe "::scrub" do let(:utf_8_string_to_scrub) { "\xfdfoo".force_encoding(Encoding::UTF_8) } # invalid in UTF-8 # The invalid-ness of this string comes from unpaired surrogates, ie: # "any value in the range D80016 to DBFF16 not followed by a value in the # range DC0016 to DFFF16, or any value in the range DC0016 to DFFF16 not # preceded by a value in the range D80016 to DBFF16" # http://unicode.org/faq/utf_bom.html#utf16-7 # "a\ud800b" # We expect the "b" to be replaced as that is what makes the string invalid let(:utf_16LE_string_to_scrub) { [97, 237, 160, 128, 98].pack('C*').force_encoding(Encoding::UTF_16LE) } # invalid in UTF-16 let(:invalid_non_utf) { "foo\u2603".force_encoding(Encoding::EUC_KR) } # EUC_KR foosnowman! it "should defer to String#scrub if defined", :if => String.method_defined?(:scrub) do result = Puppet::Util::CharacterEncoding.scrub(utf_8_string_to_scrub) # The result should have the UTF-8 replacement character if we're using Ruby scrub expect(result).to eq("\uFFFDfoo".force_encoding(Encoding::UTF_8)) expect(result.bytes.to_a).to eq([239, 191, 189, 102, 111, 111]) end context "when String#scrub is not defined" do it "should still issue unicode replacement characters if the string is UTF-8" do utf_8_string_to_scrub.stubs(:respond_to?).with(:scrub).returns(false) result = Puppet::Util::CharacterEncoding.scrub(utf_8_string_to_scrub) expect(result).to eq("\uFFFDfoo".force_encoding(Encoding::UTF_8)) end it "should still issue unicode replacement characters if the string is UTF-16LE" do utf_16LE_string_to_scrub.stubs(:respond_to?).with(:scrub).returns(false) result = Puppet::Util::CharacterEncoding.scrub(utf_16LE_string_to_scrub) # Bytes of replacement character on UTF_16LE are [253, 255] # We just check for bytes because something (ruby?) interprets this array of bytes as: # (97) (237 160) (128 253 255) rather than (97) (237 160 128) (253 255) expect(result).to eq([97, 237, 160, 128, 253, 255].pack('C*').force_encoding(Encoding::UTF_16LE)) end it "should issue '?' characters if the string is not one of UTF_8 or UTF_16LE" do invalid_non_utf.stubs(:respond_to?).with(:scrub).returns(false) result = Puppet::Util::CharacterEncoding.scrub(invalid_non_utf) expect(result).to eq("foo???".force_encoding(Encoding::EUC_KR)) end end end end puppet-5.5.10/spec/unit/util/execution_spec.rb0000644005276200011600000011545613417161722021257 0ustar jenkinsjenkins#! /usr/bin/env ruby # encoding: UTF-8 require 'spec_helper' require 'puppet/file_system/uniquefile' require 'puppet_spec/character_encoding' describe Puppet::Util::Execution do include Puppet::Util::Execution # utility methods to help us test some private methods without being quite so verbose def call_exec_posix(command, arguments, stdin, stdout, stderr) Puppet::Util::Execution.send(:execute_posix, command, arguments, stdin, stdout, stderr) end def call_exec_windows(command, arguments, stdin, stdout, stderr) Puppet::Util::Execution.send(:execute_windows, command, arguments, stdin, stdout, stderr) end describe "execution methods" do let(:pid) { 5501 } let(:process_handle) { 0xDEADBEEF } let(:thread_handle) { 0xCAFEBEEF } let(:proc_info_stub) { stub 'processinfo', :process_handle => process_handle, :thread_handle => thread_handle, :process_id => pid} let(:null_file) { Puppet.features.microsoft_windows? ? 'NUL' : '/dev/null' } def stub_process_wait(exitstatus) if Puppet.features.microsoft_windows? Puppet::Util::Windows::Process.stubs(:wait_process).with(process_handle).returns(exitstatus) FFI::WIN32.stubs(:CloseHandle).with(process_handle) FFI::WIN32.stubs(:CloseHandle).with(thread_handle) else Process.stubs(:waitpid2).with(pid, Process::WNOHANG).returns(nil, [pid, stub('child_status', :exitstatus => exitstatus)]) Process.stubs(:waitpid2).with(pid).returns([pid, stub('child_status', :exitstatus => exitstatus)]) end end describe "#execute_posix (stubs)", :unless => Puppet.features.microsoft_windows? do before :each do # Most of the things this method does are bad to do during specs. :/ Kernel.stubs(:fork).returns(pid).yields Process.stubs(:setsid) Kernel.stubs(:exec) Puppet::Util::SUIDManager.stubs(:change_user) Puppet::Util::SUIDManager.stubs(:change_group) # ensure that we don't really close anything! (0..256).each {|n| IO.stubs(:new) } $stdin.stubs(:reopen) $stdout.stubs(:reopen) $stderr.stubs(:reopen) @stdin = File.open(null_file, 'r') @stdout = Puppet::FileSystem::Uniquefile.new('stdout') @stderr = File.open(null_file, 'w') # there is a danger here that ENV will be modified by exec_posix. Normally it would only affect the ENV # of a forked process, but here, we're stubbing Kernel.fork, so the method has the ability to override the # "real" ENV. To guard against this, we'll capture a snapshot of ENV before each test. @saved_env = ENV.to_hash # Now, we're going to effectively "mock" the magic ruby 'ENV' variable by creating a local definition of it # inside of the module we're testing. Puppet::Util::Execution::ENV = {} end after :each do # And here we remove our "mock" version of 'ENV', which will allow us to validate that the real ENV has been # left unharmed. Puppet::Util::Execution.send(:remove_const, :ENV) # capture the current environment and make sure it's the same as it was before the test cur_env = ENV.to_hash # we will get some fairly useless output if we just use the raw == operator on the hashes here, so we'll # be a bit more explicit and laborious in the name of making the error more useful... @saved_env.each_pair { |key,val| expect(cur_env[key]).to eq(val) } expect(cur_env.keys - @saved_env.keys).to eq([]) end it "should fork a child process to execute the command" do Kernel.expects(:fork).returns(pid).yields Kernel.expects(:exec).with('test command') call_exec_posix('test command', {}, @stdin, @stdout, @stderr) end it "should start a new session group" do Process.expects(:setsid) call_exec_posix('test command', {}, @stdin, @stdout, @stderr) end it "should permanently change to the correct user and group if specified" do Puppet::Util::SUIDManager.expects(:change_group).with(55, true) Puppet::Util::SUIDManager.expects(:change_user).with(50, true) call_exec_posix('test command', {:uid => 50, :gid => 55}, @stdin, @stdout, @stderr) end it "should exit failure if there is a problem execing the command" do Kernel.expects(:exec).with('test command').raises("failed to execute!") Puppet::Util::Execution.stubs(:puts) Puppet::Util::Execution.expects(:exit!).with(1) call_exec_posix('test command', {}, @stdin, @stdout, @stderr) end it "should properly execute commands specified as arrays" do Kernel.expects(:exec).with('test command', 'with', 'arguments') call_exec_posix(['test command', 'with', 'arguments'], {:uid => 50, :gid => 55}, @stdin, @stdout, @stderr) end it "should properly execute string commands with embedded newlines" do Kernel.expects(:exec).with("/bin/echo 'foo' ; \n /bin/echo 'bar' ;") call_exec_posix("/bin/echo 'foo' ; \n /bin/echo 'bar' ;", {:uid => 50, :gid => 55}, @stdin, @stdout, @stderr) end context 'cwd option' do let(:cwd) { 'cwd' } it 'should run the command in the specified working directory' do Dir.expects(:chdir).with(cwd) Kernel.expects(:exec).with('test command') call_exec_posix('test command', { :cwd => cwd }, @stdin, @stdout, @stderr) end end it "should return the pid of the child process" do expect(call_exec_posix('test command', {}, @stdin, @stdout, @stderr)).to eq(pid) end end describe "#execute_windows (stubs)", :if => Puppet.features.microsoft_windows? do before :each do Process.stubs(:create).returns(proc_info_stub) stub_process_wait(0) @stdin = File.open(null_file, 'r') @stdout = Puppet::FileSystem::Uniquefile.new('stdout') @stderr = File.open(null_file, 'w') end it "should create a new process for the command" do Process.expects(:create).with( :command_line => "test command", :startup_info => {:stdin => @stdin, :stdout => @stdout, :stderr => @stderr}, :close_handles => false ).returns(proc_info_stub) call_exec_windows('test command', {}, @stdin, @stdout, @stderr) end context 'cwd option' do let(:cwd) { 'cwd' } it "should execute the command in the specified working directory" do Dir.expects(:chdir).with(cwd).yields Process.expects(:create).with( :command_line => "test command", :startup_info => { :stdin => @stdin, :stdout => @stdout, :stderr => @stderr }, :close_handles => false ) call_exec_windows('test command', { :cwd => cwd }, @stdin, @stdout, @stderr) end end context 'suppress_window option' do let(:cwd) { 'cwd' } it "should execute the command in the specified working directory" do Process.expects(:create).with( :command_line => "test command", :startup_info => { :stdin => @stdin, :stdout => @stdout, :stderr => @stderr }, :close_handles => false, :creation_flags => Puppet::Util::Windows::Process::CREATE_NO_WINDOW ) call_exec_windows('test command', { :suppress_window => true }, @stdin, @stdout, @stderr) end end it "should return the process info of the child process" do expect(call_exec_windows('test command', {}, @stdin, @stdout, @stderr)).to eq(proc_info_stub) end it "should quote arguments containing spaces if command is specified as an array" do Process.expects(:create).with do |args| args[:command_line] == '"test command" with some "arguments \"with spaces"' end.returns(proc_info_stub) call_exec_windows(['test command', 'with', 'some', 'arguments "with spaces'], {}, @stdin, @stdout, @stderr) end end describe "#execute (stubs)" do before :each do stub_process_wait(0) end describe "when an execution stub is specified" do before :each do Puppet::Util::ExecutionStub.set do |command,args,stdin,stdout,stderr| "execution stub output" end end it "should call the block on the stub" do expect(Puppet::Util::Execution.execute("/usr/bin/run_my_execute_stub")).to eq("execution stub output") end it "should not actually execute anything" do Puppet::Util::Execution.expects(:execute_posix).never Puppet::Util::Execution.expects(:execute_windows).never Puppet::Util::Execution.execute("/usr/bin/run_my_execute_stub") end end describe "when setting up input and output files" do include PuppetSpec::Files let(:executor) { Puppet.features.microsoft_windows? ? 'execute_windows' : 'execute_posix' } let(:rval) { Puppet.features.microsoft_windows? ? proc_info_stub : pid } before :each do Puppet::Util::Execution.stubs(:wait_for_output) end it "should set stdin to the stdinfile if specified" do input = tmpfile('stdin') FileUtils.touch(input) Puppet::Util::Execution.expects(executor).with do |_,_,stdin,_,_| stdin.path == input end.returns(rval) Puppet::Util::Execution.execute('test command', :stdinfile => input) end it "should set stdin to the null file if not specified" do Puppet::Util::Execution.expects(executor).with do |_,_,stdin,_,_| stdin.path == null_file end.returns(rval) Puppet::Util::Execution.execute('test command') end describe "when squelch is set" do it "should set stdout and stderr to the null file" do Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr| stdout.path == null_file and stderr.path == null_file end.returns(rval) Puppet::Util::Execution.execute('test command', :squelch => true) end end describe "cwd option" do def expect_cwd_to_be(cwd) Puppet::Util::Execution.expects(executor).with( anything, has_entries(:cwd => cwd), anything, anything, anything ).returns(rval) end it 'should raise an ArgumentError if the specified working directory does not exist' do cwd = 'cwd' Puppet::FileSystem.stubs(:directory?).with(cwd).returns(false) expect { Puppet::Util::Execution.execute('test command', cwd: cwd) }.to raise_error do |error| expect(error).to be_a(ArgumentError) expect(error.message).to match(cwd) end end it "should set the cwd to the user-specified one" do Puppet::FileSystem.stubs(:directory?).with('cwd').returns(true) expect_cwd_to_be('cwd') Puppet::Util::Execution.execute('test command', cwd: 'cwd') end end describe "on POSIX", :if => Puppet.features.posix? do describe "when squelch is not set" do it "should set stdout to a pipe" do Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,_| stdout.class == IO end.returns(rval) Puppet::Util::Execution.execute('test command', :squelch => false) end it "should set stderr to the same file as stdout if combine is true" do Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr| stdout == stderr end.returns(rval) Puppet::Util::Execution.execute('test command', :squelch => false, :combine => true) end it "should set stderr to the null device if combine is false" do Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr| stdout.class == IO and stderr.path == null_file end.returns(rval) Puppet::Util::Execution.execute('test command', :squelch => false, :combine => false) end it "should default combine to true when no options are specified" do Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr| stdout == stderr end.returns(rval) Puppet::Util::Execution.execute('test command') end it "should default combine to false when options are specified, but combine is not" do Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr| stdout.class == IO and stderr.path == null_file end.returns(rval) Puppet::Util::Execution.execute('test command', :failonfail => false) end it "should default combine to false when an empty hash of options is specified" do Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr| stdout.class == IO and stderr.path == null_file end.returns(rval) Puppet::Util::Execution.execute('test command', {}) end end end describe "on Windows", :if => Puppet.features.microsoft_windows? do describe "when squelch is not set" do it "should set stdout to a temporary output file" do outfile = Puppet::FileSystem::Uniquefile.new('stdout') Puppet::FileSystem::Uniquefile.stubs(:new).returns(outfile) Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,_| stdout.path == outfile.path end.returns(rval) Puppet::Util::Execution.execute('test command', :squelch => false) end it "should set stderr to the same file as stdout if combine is true" do outfile = Puppet::FileSystem::Uniquefile.new('stdout') Puppet::FileSystem::Uniquefile.stubs(:new).returns(outfile) Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr| stdout.path == outfile.path and stderr.path == outfile.path end.returns(rval) Puppet::Util::Execution.execute('test command', :squelch => false, :combine => true) end it "should set stderr to the null device if combine is false" do outfile = Puppet::FileSystem::Uniquefile.new('stdout') Puppet::FileSystem::Uniquefile.stubs(:new).returns(outfile) Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr| stdout.path == outfile.path and stderr.path == null_file end.returns(rval) Puppet::Util::Execution.execute('test command', :squelch => false, :combine => false) end it "should combine stdout and stderr if combine is true" do outfile = Puppet::FileSystem::Uniquefile.new('stdout') Puppet::FileSystem::Uniquefile.stubs(:new).returns(outfile) Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr| stdout.path == outfile.path and stderr.path == outfile.path end.returns(rval) Puppet::Util::Execution.execute('test command', :combine => true) end it "should default combine to true when no options are specified" do outfile = Puppet::FileSystem::Uniquefile.new('stdout') Puppet::FileSystem::Uniquefile.stubs(:new).returns(outfile) Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr| stdout.path == outfile.path and stderr.path == outfile.path end.returns(rval) Puppet::Util::Execution.execute('test command') end it "should default combine to false when options are specified, but combine is not" do outfile = Puppet::FileSystem::Uniquefile.new('stdout') Puppet::FileSystem::Uniquefile.stubs(:new).returns(outfile) Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr| stdout.path == outfile.path and stderr.path == null_file end.returns(rval) Puppet::Util::Execution.execute('test command', :failonfail => false) end it "should default combine to false when an empty hash of options is specified" do outfile = Puppet::FileSystem::Uniquefile.new('stdout') Puppet::FileSystem::Uniquefile.stubs(:new).returns(outfile) Puppet::Util::Execution.expects(executor).with do |_,_,_,stdout,stderr| stdout.path == outfile.path and stderr.path == null_file end.returns(rval) Puppet::Util::Execution.execute('test command', {}) end end end end describe "on Windows", :if => Puppet.features.microsoft_windows? do it "should always close the process and thread handles" do Puppet::Util::Execution.stubs(:execute_windows).returns(proc_info_stub) Puppet::Util::Windows::Process.expects(:wait_process).with(process_handle).raises('whatever') FFI::WIN32.expects(:CloseHandle).with(thread_handle) FFI::WIN32.expects(:CloseHandle).with(process_handle) expect { Puppet::Util::Execution.execute('test command') }.to raise_error(RuntimeError) end it "should return the correct exit status even when exit status is greater than 256" do real_exit_status = 3010 Puppet::Util::Execution.stubs(:execute_windows).returns(proc_info_stub) stub_process_wait(real_exit_status) Puppet::Util::Execution.stubs(:exitstatus).returns(real_exit_status % 256) # The exitstatus is changed to be mod 256 so that ruby can fit it into 8 bits. expect(Puppet::Util::Execution.execute('test command', :failonfail => false).exitstatus).to eq(real_exit_status) end end end describe "#execute (posix locale)", :unless => Puppet.features.microsoft_windows? do before :each do # there is a danger here that ENV will be modified by exec_posix. Normally it would only affect the ENV # of a forked process, but, in some of the previous tests in this file we're stubbing Kernel.fork., which could # allow the method to override the "real" ENV. This shouldn't be a problem for these tests because they are # not stubbing Kernel.fork, but, better safe than sorry... so, to guard against this, we'll capture a snapshot # of ENV before each test. @saved_env = ENV.to_hash end after :each do # capture the current environment and make sure it's the same as it was before the test cur_env = ENV.to_hash # we will get some fairly useless output if we just use the raw == operator on the hashes here, so we'll # be a bit more explicit and laborious in the name of making the error more useful... @saved_env.each_pair { |key,val| expect(cur_env[key]).to eq(val) } expect(cur_env.keys - @saved_env.keys).to eq([]) end # build up a printf-style string that contains a command to get the value of an environment variable # from the operating system. We can substitute into this with the names of the desired environment variables later. get_env_var_cmd = 'echo $%s' # a sentinel value that we can use to emulate what locale environment variables might be set to on an international # system. lang_sentinel_value = "en_US.UTF-8" # a temporary hash that contains sentinel values for each of the locale environment variables that we override in # "execute" locale_sentinel_env = {} Puppet::Util::POSIX::LOCALE_ENV_VARS.each { |var| locale_sentinel_env[var] = lang_sentinel_value } it "should override the locale environment variables when :override_locale is not set (defaults to true)" do # temporarily override the locale environment vars with a sentinel value, so that we can confirm that # execute is actually setting them. Puppet::Util.withenv(locale_sentinel_env) do Puppet::Util::POSIX::LOCALE_ENV_VARS.each do |var| # we expect that all of the POSIX vars will have been cleared except for LANG and LC_ALL expected_value = (['LANG', 'LC_ALL'].include?(var)) ? "C" : "" expect(Puppet::Util::Execution.execute(get_env_var_cmd % var).strip).to eq(expected_value) end end end it "should override the LANG environment variable when :override_locale is set to true" do # temporarily override the locale environment vars with a sentinel value, so that we can confirm that # execute is actually setting them. Puppet::Util.withenv(locale_sentinel_env) do Puppet::Util::POSIX::LOCALE_ENV_VARS.each do |var| # we expect that all of the POSIX vars will have been cleared except for LANG and LC_ALL expected_value = (['LANG', 'LC_ALL'].include?(var)) ? "C" : "" expect(Puppet::Util::Execution.execute(get_env_var_cmd % var, {:override_locale => true}).strip).to eq(expected_value) end end end it "should *not* override the LANG environment variable when :override_locale is set to false" do # temporarily override the locale environment vars with a sentinel value, so that we can confirm that # execute is not setting them. Puppet::Util.withenv(locale_sentinel_env) do Puppet::Util::POSIX::LOCALE_ENV_VARS.each do |var| expect(Puppet::Util::Execution.execute(get_env_var_cmd % var, {:override_locale => false}).strip).to eq(lang_sentinel_value) end end end it "should have restored the LANG and locale environment variables after execution" do # we'll do this once without any sentinel values, to give us a little more test coverage orig_env_vals = {} Puppet::Util::POSIX::LOCALE_ENV_VARS.each do |var| orig_env_vals[var] = ENV[var] end # now we can really execute any command--doesn't matter what it is... Puppet::Util::Execution.execute(get_env_var_cmd % 'anything', {:override_locale => true}) # now we check and make sure the original environment was restored Puppet::Util::POSIX::LOCALE_ENV_VARS.each do |var| expect(ENV[var]).to eq(orig_env_vals[var]) end # now, once more... but with our sentinel values Puppet::Util.withenv(locale_sentinel_env) do # now we can really execute any command--doesn't matter what it is... Puppet::Util::Execution.execute(get_env_var_cmd % 'anything', {:override_locale => true}) # now we check and make sure the original environment was restored Puppet::Util::POSIX::LOCALE_ENV_VARS.each do |var| expect(ENV[var]).to eq(locale_sentinel_env[var]) end end end end describe "#execute (posix user env vars)", :unless => Puppet.features.microsoft_windows? do # build up a printf-style string that contains a command to get the value of an environment variable # from the operating system. We can substitute into this with the names of the desired environment variables later. get_env_var_cmd = 'echo $%s' # a sentinel value that we can use to emulate what locale environment variables might be set to on an international # system. user_sentinel_value = "Abracadabra" # a temporary hash that contains sentinel values for each of the locale environment variables that we override in # "execute" user_sentinel_env = {} Puppet::Util::POSIX::USER_ENV_VARS.each { |var| user_sentinel_env[var] = user_sentinel_value } it "should unset user-related environment vars during execution" do # first we set up a temporary execution environment with sentinel values for the user-related environment vars # that we care about. Puppet::Util.withenv(user_sentinel_env) do # with this environment, we loop over the vars in question Puppet::Util::POSIX::USER_ENV_VARS.each do |var| # ensure that our temporary environment is set up as we expect expect(ENV[var]).to eq(user_sentinel_env[var]) # run an "exec" via the provider and ensure that it unsets the vars expect(Puppet::Util::Execution.execute(get_env_var_cmd % var).strip).to eq("") # ensure that after the exec, our temporary env is still intact expect(ENV[var]).to eq(user_sentinel_env[var]) end end end it "should have restored the user-related environment variables after execution" do # we'll do this once without any sentinel values, to give us a little more test coverage orig_env_vals = {} Puppet::Util::POSIX::USER_ENV_VARS.each do |var| orig_env_vals[var] = ENV[var] end # now we can really execute any command--doesn't matter what it is... Puppet::Util::Execution.execute(get_env_var_cmd % 'anything') # now we check and make sure the original environment was restored Puppet::Util::POSIX::USER_ENV_VARS.each do |var| expect(ENV[var]).to eq(orig_env_vals[var]) end # now, once more... but with our sentinel values Puppet::Util.withenv(user_sentinel_env) do # now we can really execute any command--doesn't matter what it is... Puppet::Util::Execution.execute(get_env_var_cmd % 'anything') # now we check and make sure the original environment was restored Puppet::Util::POSIX::USER_ENV_VARS.each do |var| expect(ENV[var]).to eq(user_sentinel_env[var]) end end end end describe "#execute (debug logging)" do before :each do stub_process_wait(0) if Puppet.features.microsoft_windows? Puppet::Util::Execution.stubs(:execute_windows).returns(proc_info_stub) else Puppet::Util::Execution.stubs(:execute_posix).returns(pid) end end it "should log if no uid or gid specified" do Puppet::Util::Execution.expects(:debug).with("Executing: 'echo hello'") Puppet::Util::Execution.execute('echo hello') end it "should log numeric uid if specified" do Puppet::Util::Execution.expects(:debug).with("Executing with uid=100: 'echo hello'") Puppet::Util::Execution.execute('echo hello', {:uid => 100}) end it "should log numeric gid if specified" do Puppet::Util::Execution.expects(:debug).with("Executing with gid=500: 'echo hello'") Puppet::Util::Execution.execute('echo hello', {:gid => 500}) end it "should log numeric uid and gid if specified" do Puppet::Util::Execution.expects(:debug).with("Executing with uid=100 gid=500: 'echo hello'") Puppet::Util::Execution.execute('echo hello', {:uid => 100, :gid => 500}) end it "should log string uid if specified" do Puppet::Util::Execution.expects(:debug).with("Executing with uid=myuser: 'echo hello'") Puppet::Util::Execution.execute('echo hello', {:uid => 'myuser'}) end it "should log string gid if specified" do Puppet::Util::Execution.expects(:debug).with("Executing with gid=mygroup: 'echo hello'") Puppet::Util::Execution.execute('echo hello', {:gid => 'mygroup'}) end it "should log string uid and gid if specified" do Puppet::Util::Execution.expects(:debug).with("Executing with uid=myuser gid=mygroup: 'echo hello'") Puppet::Util::Execution.execute('echo hello', {:uid => 'myuser', :gid => 'mygroup'}) end it "should log numeric uid and string gid if specified" do Puppet::Util::Execution.expects(:debug).with("Executing with uid=100 gid=mygroup: 'echo hello'") Puppet::Util::Execution.execute('echo hello', {:uid => 100, :gid => 'mygroup'}) end it 'should redact commands in debug output when passed sensitive option' do Puppet::Util::Execution.expects(:debug).with("Executing: '[redacted]'") Puppet::Util::Execution.execute('echo hello', {:sensitive => true}) end end describe "after execution" do before :each do stub_process_wait(0) if Puppet.features.microsoft_windows? Puppet::Util::Execution.stubs(:execute_windows).returns(proc_info_stub) else Puppet::Util::Execution.stubs(:execute_posix).returns(pid) end end it "should wait for the child process to exit" do Puppet::Util::Execution.stubs(:wait_for_output) Puppet::Util::Execution.execute('test command') end it "should close the stdin/stdout/stderr files used by the child" do stdin = mock 'file' stdout = mock 'file' stderr = mock 'file' [stdin, stdout, stderr].each {|io| io.expects(:close).at_least_once} File.expects(:open). times(3). returns(stdin). then.returns(stdout). then.returns(stderr) Puppet::Util::Execution.execute('test command', {:squelch => true, :combine => false}) end describe "on POSIX", :if => Puppet.features.posix? do context "reading the output" do before :each do r, w = IO.pipe IO.expects(:pipe).returns([r, w]) w.write("My expected \u2744 command output") end it "should return output with external encoding ISO_8859_1" do result = PuppetSpec::CharacterEncoding.with_external_encoding(Encoding::ISO_8859_1) do Puppet::Util::Execution.execute('test command') end expect(result.encoding).to eq(Encoding::ISO_8859_1) expect(result).to eq("My expected \u2744 command output".force_encoding(Encoding::ISO_8859_1)) end it "should return output with external encoding UTF_8" do result = PuppetSpec::CharacterEncoding.with_external_encoding(Encoding::UTF_8) do Puppet::Util::Execution.execute('test command') end expect(result.encoding).to eq(Encoding::UTF_8) expect(result).to eq("My expected \u2744 command output") end end it "should not read the output if squelch is true" do IO.expects(:pipe).never expect(Puppet::Util::Execution.execute('test command', :squelch => true)).to eq('') end it "should close the pipe used for output if squelch is false" do r, w = IO.pipe IO.expects(:pipe).returns([r, w]) expect(Puppet::Util::Execution.execute('test command')).to eq("") expect(r.closed?) expect(w.closed?) end it "should close the pipe used for output if squelch is false and an error is raised" do r, w = IO.pipe IO.expects(:pipe).returns([r, w]) if Puppet.features.microsoft_windows? Puppet::Util::Execution.expects(:execute_windows).raises(Exception, 'execution failed') else Puppet::Util::Execution.expects(:execute_posix).raises(Exception, 'execution failed') end expect { subject.execute('fail command') }.to raise_error(Exception, 'execution failed') expect(r.closed?) expect(w.closed?) end end describe "on Windows", :if => Puppet.features.microsoft_windows? do context "reading the output" do before :each do stdout = Puppet::FileSystem::Uniquefile.new('test') Puppet::FileSystem::Uniquefile.stubs(:new).returns(stdout) stdout.write("My expected \u2744 command output") end it "should return output with external encoding ISO_8859_1" do result = PuppetSpec::CharacterEncoding.with_external_encoding(Encoding::ISO_8859_1) do Puppet::Util::Execution.execute('test command') end expect(result.encoding).to eq(Encoding::ISO_8859_1) expect(result).to eq("My expected \u2744 command output".force_encoding(Encoding::ISO_8859_1)) end it "should return output with external encoding UTF_8" do result = PuppetSpec::CharacterEncoding.with_external_encoding(Encoding::UTF_8) do Puppet::Util::Execution.execute('test command') end expect(result.encoding).to eq(Encoding::UTF_8) expect(result).to eq("My expected \u2744 command output") end end it "should not read the output if squelch is true" do stdout = Puppet::FileSystem::Uniquefile.new('test') Puppet::FileSystem::Uniquefile.stubs(:new).returns(stdout) stdout.write("My expected command output") expect(Puppet::Util::Execution.execute('test command', :squelch => true)).to eq('') end it "should delete the file used for output if squelch is false" do stdout = Puppet::FileSystem::Uniquefile.new('test') path = stdout.path Puppet::FileSystem::Uniquefile.stubs(:new).returns(stdout) Puppet::Util::Execution.execute('test command') expect(Puppet::FileSystem.exist?(path)).to be_falsey end it "should not raise an error if the file is open" do stdout = Puppet::FileSystem::Uniquefile.new('test') Puppet::FileSystem::Uniquefile.stubs(:new).returns(stdout) Puppet::Util::Execution.execute('test command') end end it "should raise an error if failonfail is true and the child failed" do stub_process_wait(1) expect { subject.execute('fail command', :failonfail => true) }.to raise_error(Puppet::ExecutionFailure, /Execution of 'fail command' returned 1/) end it "should raise an error with redacted sensitive command if failonfail is true and the child failed" do stub_process_wait(1) expect { subject.execute('fail command', :failonfail => true, :sensitive => true) }.to raise_error(Puppet::ExecutionFailure, /Execution of '\[redacted\]' returned 1/) end it "should not raise an error if failonfail is false and the child failed" do stub_process_wait(1) subject.execute('fail command', :failonfail => false) end it "should not raise an error if failonfail is true and the child succeeded" do stub_process_wait(0) subject.execute('fail command', :failonfail => true) end it "should not raise an error if failonfail is false and the child succeeded" do stub_process_wait(0) subject.execute('fail command', :failonfail => false) end it "should default failonfail to true when no options are specified" do stub_process_wait(1) expect { subject.execute('fail command') }.to raise_error(Puppet::ExecutionFailure, /Execution of 'fail command' returned 1/) end it "should default failonfail to false when options are specified, but failonfail is not" do stub_process_wait(1) subject.execute('fail command', { :combine => true }) end it "should default failonfail to false when an empty hash of options is specified" do stub_process_wait(1) subject.execute('fail command', {}) end it "should raise an error if a nil option is specified" do expect { Puppet::Util::Execution.execute('fail command', nil) }.to raise_error(TypeError, /(can\'t convert|no implicit conversion of) nil into Hash/) end end end describe "#execpipe" do it "should execute a string as a string" do Puppet::Util::Execution.expects(:open).with('| echo hello 2>&1').returns('hello') Puppet::Util::Execution.expects(:exitstatus).returns(0) expect(Puppet::Util::Execution.execpipe('echo hello')).to eq('hello') end it "should print meaningful debug message for string argument" do Puppet::Util::Execution.expects(:debug).with("Executing 'echo hello'") Puppet::Util::Execution.expects(:open).with('| echo hello 2>&1').returns('hello') Puppet::Util::Execution.expects(:exitstatus).returns(0) Puppet::Util::Execution.execpipe('echo hello') end it "should print meaningful debug message for array argument" do Puppet::Util::Execution.expects(:debug).with("Executing 'echo hello'") Puppet::Util::Execution.expects(:open).with('| echo hello 2>&1').returns('hello') Puppet::Util::Execution.expects(:exitstatus).returns(0) Puppet::Util::Execution.execpipe(['echo','hello']) end it "should execute an array by pasting together with spaces" do Puppet::Util::Execution.expects(:open).with('| echo hello 2>&1').returns('hello') Puppet::Util::Execution.expects(:exitstatus).returns(0) expect(Puppet::Util::Execution.execpipe(['echo', 'hello'])).to eq('hello') end it "should fail if asked to fail, and the child does" do Puppet::Util::Execution.stubs(:open).with('| echo hello 2>&1').returns('error message') Puppet::Util::Execution.expects(:exitstatus).returns(1) expect { Puppet::Util::Execution.execpipe('echo hello') }.to raise_error Puppet::ExecutionFailure, /error message/ end it "should not fail if asked not to fail, and the child does" do Puppet::Util::Execution.stubs(:open).returns('error message') expect(Puppet::Util::Execution.execpipe('echo hello', false)).to eq('error message') end end describe "execfail" do it "returns the executed command output" do Puppet::Util::Execution.stubs(:execute) .returns(Puppet::Util::Execution::ProcessOutput.new("process output", 0)) expect(Puppet::Util::Execution.execfail('echo hello', Puppet::Error)).to eq('process output') end it "raises a caller-specified exception on failure with the backtrace" do Puppet::Util::Execution.stubs(:execute).raises(Puppet::ExecutionFailure, "failed to execute") expect { Puppet::Util::Execution.execfail("this will fail", Puppet::Error) }.to raise_error(Puppet::Error, /failed to execute/) end it "raises exceptions that don't extend ExecutionFailure" do Puppet::Util::Execution.stubs(:execute).raises(ArgumentError, "failed to execute") expect { Puppet::Util::Execution.execfail("this will fail", Puppet::Error) }.to raise_error(ArgumentError, /failed to execute/) end it "raises a TypeError if the exception class is nil" do Puppet::Util::Execution.stubs(:execute).raises(Puppet::ExecutionFailure, "failed to execute") expect { Puppet::Util::Execution.execfail('echo hello', nil) }.to raise_error(TypeError, /exception class\/object expected/) end end end puppet-5.5.10/spec/unit/util/execution_stub_spec.rb0000644005276200011600000000274713417161722022312 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Util::ExecutionStub do it "should use the provided stub code when 'set' is called" do Puppet::Util::ExecutionStub.set do |command, options| expect(command).to eq(['/bin/foo', 'bar']) "stub output" end expect(Puppet::Util::ExecutionStub.current_value).not_to eq(nil) expect(Puppet::Util::Execution.execute(['/bin/foo', 'bar'])).to eq("stub output") end it "should automatically restore normal execution at the conclusion of each spec test" do # Note: this test relies on the previous test creating a stub. expect(Puppet::Util::ExecutionStub.current_value).to eq(nil) end it "should restore normal execution after 'reset' is called" do # Note: "true" exists at different paths in different OSes if Puppet.features.microsoft_windows? true_command = [Puppet::Util.which('cmd.exe').tr('/', '\\'), '/c', 'exit 0'] else true_command = [Puppet::Util.which('true')] end stub_call_count = 0 Puppet::Util::ExecutionStub.set do |command, options| expect(command).to eq(true_command) stub_call_count += 1 'stub called' end expect(Puppet::Util::Execution.execute(true_command)).to eq('stub called') expect(stub_call_count).to eq(1) Puppet::Util::ExecutionStub.reset expect(Puppet::Util::ExecutionStub.current_value).to eq(nil) expect(Puppet::Util::Execution.execute(true_command)).to eq('') expect(stub_call_count).to eq(1) end end puppet-5.5.10/spec/unit/util/feature_spec.rb0000644005276200011600000000646013417161722020701 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/feature' describe Puppet::Util::Feature do before do @features = Puppet::Util::Feature.new("features") @features.stubs(:warn) end it "should be able to add new features" do @features.add(:myfeature) {} expect(@features).to respond_to(:myfeature?) end it "should call associated code when loading a feature" do $loaded_feature = false @features.add(:myfeature) { $loaded_feature = true} expect($loaded_feature).to be_truthy end it "should consider a feature absent when the feature load fails" do @features.add(:failer) { raise "foo" } expect(@features).not_to be_failer end it "should consider a feature to be absent when the feature load returns false" do @features.add(:failer) { false } expect(@features).not_to be_failer end it "should consider a feature to be present when the feature load returns true" do @features.add(:available) { true } expect(@features).to be_available end it "should cache the results of a feature load via code block" do $loaded_feature = 0 @features.add(:myfeature) { $loaded_feature += 1 } @features.myfeature? @features.myfeature? expect($loaded_feature).to eq(1) end it "should invalidate the cache for the feature when loading" do # block defined features are evaluated at load time @features.add(:myfeature) { false } expect(@features).not_to be_myfeature # features with no block have deferred evaluation so an existing cached # value would take precedence @features.add(:myfeature) expect(@features).to be_myfeature end it "should support features with libraries" do expect { @features.add(:puppet, :libs => %w{puppet}) }.not_to raise_error end it "should consider a feature to be present if all of its libraries are present" do @features.add(:myfeature, :libs => %w{foo bar}) @features.expects(:require).with("foo") @features.expects(:require).with("bar") expect(@features).to be_myfeature end it "should log and consider a feature to be absent if any of its libraries are absent" do @features.add(:myfeature, :libs => %w{foo bar}) @features.expects(:require).with("foo").raises(LoadError) @features.stubs(:require).with("bar") Puppet.expects(:debug) expect(@features).not_to be_myfeature end it "should change the feature to be present when its libraries become available" do @features.add(:myfeature, :libs => %w{foo bar}) @features.expects(:require).twice().with("foo").raises(LoadError).then.returns(nil) @features.stubs(:require).with("bar") Puppet::Util::RubyGems::Source.stubs(:source).returns(Puppet::Util::RubyGems::OldGemsSource) Puppet::Util::RubyGems::OldGemsSource.any_instance.expects(:clear_paths).times(3) Puppet.expects(:debug) expect(@features).not_to be_myfeature expect(@features).to be_myfeature end it "should cache load failures when configured to do so" do Puppet[:always_retry_plugins] = false @features.add(:myfeature, :libs => %w{foo bar}) @features.expects(:require).with("foo").raises(LoadError) expect(@features).not_to be_myfeature # second call would cause an expectation exception if 'require' was # called a second time expect(@features).not_to be_myfeature end end puppet-5.5.10/spec/unit/util/filetype_spec.rb0000644005276200011600000001706413417161722021071 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/filetype' # XXX Import all of the tests into this file. describe Puppet::Util::FileType do describe "the flat filetype" do let(:path) { '/my/file' } let(:type) { Puppet::Util::FileType.filetype(:flat) } let(:file) { type.new(path) } it "should exist" do expect(type).not_to be_nil end describe "when the file already exists" do it "should return the file's contents when asked to read it" do Puppet::FileSystem.expects(:exist?).with(path).returns true Puppet::FileSystem.expects(:read).with(path, :encoding => Encoding.default_external).returns "my text" expect(file.read).to eq("my text") end it "should unlink the file when asked to remove it" do Puppet::FileSystem.expects(:exist?).with(path).returns true Puppet::FileSystem.expects(:unlink).with(path) file.remove end end describe "when the file does not exist" do it "should return an empty string when asked to read the file" do Puppet::FileSystem.expects(:exist?).with(path).returns false expect(file.read).to eq("") end end describe "when writing the file" do let(:tempfile) { stub 'tempfile', :print => nil, :close => nil, :flush => nil, :path => "/other/file" } before do FileUtils.stubs(:cp) Tempfile.stubs(:new).returns tempfile end it "should first create a temp file and copy its contents over to the file location" do Tempfile.expects(:new).with("puppet", :encoding => Encoding.default_external).returns tempfile tempfile.expects(:print).with("my text") tempfile.expects(:flush) tempfile.expects(:close) FileUtils.expects(:cp).with(tempfile.path, path) file.write "my text" end it "should set the selinux default context on the file" do file.expects(:set_selinux_default_context).with(path) file.write "eh" end end describe "when backing up a file" do it "should do nothing if the file does not exist" do Puppet::FileSystem.expects(:exist?).with(path).returns false file.expects(:bucket).never file.backup end it "should use its filebucket to backup the file if it exists" do Puppet::FileSystem.expects(:exist?).with(path).returns true bucket = mock 'bucket' bucket.expects(:backup).with(path) file.expects(:bucket).returns bucket file.backup end it "should use the default filebucket" do bucket = mock 'bucket' bucket.expects(:bucket).returns "mybucket" Puppet::Type.type(:filebucket).expects(:mkdefaultbucket).returns bucket expect(file.bucket).to eq("mybucket") end end end shared_examples_for "crontab provider" do let(:cron) { type.new('no_such_user') } let(:crontab) { File.read(my_fixture(crontab_output)) } let(:options) { { :failonfail => true, :combine => true } } let(:uid) { 'no_such_user' } let(:user_options) { options.merge({:uid => uid}) } it "should exist" do expect(type).not_to be_nil end # make Puppet::Util::SUIDManager return something deterministic, not the # uid of the user running the tests, except where overridden below. before :each do Puppet::Util::SUIDManager.stubs(:uid).returns 1234 end describe "#read" do before(:each) do Puppet::Util.stubs(:uid).with(uid).returns 9000 end it "should run crontab -l as the target user" do Puppet::Util::Execution.expects(:execute) .with(['crontab', '-l'], user_options) .returns(Puppet::Util::Execution::ProcessOutput.new(crontab, 0)) expect(cron.read).to eq(crontab) end it "should not switch user if current user is the target user" do Puppet::Util.expects(:uid).with(uid).twice.returns 9000 Puppet::Util::SUIDManager.expects(:uid).returns 9000 Puppet::Util::Execution .expects(:execute).with(['crontab', '-l'], options) .returns(Puppet::Util::Execution::ProcessOutput.new(crontab, 0)) expect(cron.read).to eq(crontab) end it "should treat an absent crontab as empty" do Puppet::Util::Execution.expects(:execute).with(['crontab', '-l'], user_options).raises(Puppet::ExecutionFailure, absent_crontab) expect(cron.read).to eq('') end it "should treat a nonexistent user's crontab as empty" do Puppet::Util.expects(:uid).with(uid).returns nil expect(cron.read).to eq('') end it "should return empty if the user is not authorized to use cron" do Puppet::Util::Execution.expects(:execute).with(['crontab', '-l'], user_options).raises(Puppet::ExecutionFailure, unauthorized_crontab) expect(cron.read).to eq('') end end describe "#remove" do it "should run crontab -r as the target user" do Puppet::Util::Execution.expects(:execute).with(['crontab', '-r'], user_options) cron.remove end it "should not switch user if current user is the target user" do Puppet::Util.expects(:uid).with(uid).returns 9000 Puppet::Util::SUIDManager.expects(:uid).returns 9000 Puppet::Util::Execution.expects(:execute).with(['crontab','-r'], options) cron.remove end end describe "#write" do before :each do @tmp_cron = Tempfile.new("puppet_crontab_spec") @tmp_cron_path = @tmp_cron.path Puppet::Util.stubs(:uid).with(uid).returns 9000 Tempfile.expects(:new).with("puppet_#{name}", :encoding => Encoding.default_external).returns @tmp_cron end after :each do expect(Puppet::FileSystem.exist?(@tmp_cron_path)).to be_falsey end it "should run crontab as the target user on a temporary file" do File.expects(:chown).with(9000, nil, @tmp_cron_path) Puppet::Util::Execution.expects(:execute).with(["crontab", @tmp_cron_path], user_options) @tmp_cron.expects(:print).with("foo\n") cron.write "foo\n" end it "should not switch user if current user is the target user" do Puppet::Util::SUIDManager.expects(:uid).returns 9000 File.expects(:chown).with(9000, nil, @tmp_cron_path) Puppet::Util::Execution.expects(:execute).with(["crontab", @tmp_cron_path], options) @tmp_cron.expects(:print).with("foo\n") cron.write "foo\n" end end end describe "the suntab filetype", :unless => Puppet.features.microsoft_windows? do let(:type) { Puppet::Util::FileType.filetype(:suntab) } let(:name) { type.name } let(:crontab_output) { 'suntab_output' } # possible crontab output was taken from here: # https://docs.oracle.com/cd/E19082-01/819-2380/sysrescron-60/index.html let(:absent_crontab) do 'crontab: can\'t open your crontab file' end let(:unauthorized_crontab) do 'crontab: you are not authorized to use cron. Sorry.' end it_should_behave_like "crontab provider" end describe "the aixtab filetype", :unless => Puppet.features.microsoft_windows? do let(:type) { Puppet::Util::FileType.filetype(:aixtab) } let(:name) { type.name } let(:crontab_output) { 'aixtab_output' } let(:absent_crontab) do '0481-103 Cannot open a file in the /var/spool/cron/crontabs directory.' end let(:unauthorized_crontab) do '0481-109 You are not authorized to use the cron command.' end it_should_behave_like "crontab provider" end end puppet-5.5.10/spec/unit/util/lockfile_spec.rb0000644005276200011600000000534513417161722021037 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/lockfile' module LockfileSpecHelper def self.run_in_forks(count, &blk) forks = {} results = [] count.times do |i| forks[i] = {} forks[i][:read], forks[i][:write] = IO.pipe forks[i][:pid] = fork do forks[i][:read].close res = yield Marshal.dump(res, forks[i][:write]) exit! end end count.times do |i| forks[i][:write].close result = forks[i][:read].read forks[i][:read].close Process.wait2(forks[i][:pid]) results << Marshal.load(result) end results end end describe Puppet::Util::Lockfile do require 'puppet_spec/files' include PuppetSpec::Files before(:each) do @lockfile = tmpfile("lock") @lock = Puppet::Util::Lockfile.new(@lockfile) end describe "#lock" do it "should return true if it successfully locked" do expect(@lock.lock).to be_truthy end it "should return false if already locked" do @lock.lock expect(@lock.lock).to be_falsey end it "should create a lock file" do @lock.lock expect(Puppet::FileSystem.exist?(@lockfile)).to be_truthy end # We test simultaneous locks using fork which isn't supported on Windows. it "should not be acquired by another process", :unless => Puppet.features.microsoft_windows? do 30.times do forks = 3 results = LockfileSpecHelper.run_in_forks(forks) do @lock.lock(Process.pid) end @lock.unlock # Confirm one fork returned true and everyone else false. expect((results - [true]).size).to eq(forks - 1) expect((results - [false]).size).to eq(1) end end it "should create a lock file containing a string" do data = "foofoo barbar" @lock.lock(data) expect(File.read(@lockfile)).to eq(data) end end describe "#unlock" do it "should return true when unlocking" do @lock.lock expect(@lock.unlock).to be_truthy end it "should return false when not locked" do expect(@lock.unlock).to be_falsey end it "should clear the lock file" do File.open(@lockfile, 'w') { |fd| fd.print("locked") } @lock.unlock expect(Puppet::FileSystem.exist?(@lockfile)).to be_falsey end end it "should be locked when locked" do @lock.lock expect(@lock).to be_locked end it "should not be locked when not locked" do expect(@lock).not_to be_locked end it "should not be locked when unlocked" do @lock.lock @lock.unlock expect(@lock).not_to be_locked end it "should return the lock data" do data = "foofoo barbar" @lock.lock(data) expect(@lock.lock_data).to eq(data) end end puppet-5.5.10/spec/unit/util/log_spec.rb0000644005276200011600000004770513417161722020036 0ustar jenkinsjenkins#! /usr/bin/env ruby # coding: utf-8 require 'spec_helper' require 'puppet/util/log' describe Puppet::Util::Log do include PuppetSpec::Files def log_notice(message) Puppet::Util::Log.new(:level => :notice, :message => message) end it "should write a given message to the specified destination" do arraydest = [] Puppet::Util::Log.newdestination(Puppet::Test::LogCollector.new(arraydest)) Puppet::Util::Log.new(:level => :notice, :message => "foo") message = arraydest.last.message expect(message).to eq("foo") end context "given a message with invalid encoding" do let(:logs) { [] } let(:invalid_message) { "\xFD\xFBfoo".force_encoding(Encoding::Shift_JIS) } before do Puppet::Util::Log.newdestination(Puppet::Test::LogCollector.new(logs)) Puppet::Util::Log.new(:level => :notice, :message => invalid_message) end it "does not raise an error" do expect { Puppet::Util::Log.new(:level => :notice, :message => invalid_message) }.not_to raise_error end it "includes a backtrace in the log" do expect(logs.last.message).to match(/Backtrace:\n.*in `newmessage'\n.*in `initialize'/ ) end it "warns that message included invalid encoding" do expect(logs.last.message).to match(/Received a Log attribute with invalid encoding/) end it "includes the 'dump' of the invalid message" do expect(logs.last.message).to match(/\"\\xFD\\xFBfoo\"/) end end # need a string that cannot be converted to US-ASCII or other encodings easily # different UTF-8 widths # 1-byte A # 2-byte Űż - http://www.fileformat.info/info/unicode/char/06ff/index.htm - 0xDB 0xBF / 219 191 # 3-byte áš  - http://www.fileformat.info/info/unicode/char/16A0/index.htm - 0xE1 0x9A 0xA0 / 225 154 160 # 4-byte ÜŽ - http://www.fileformat.info/info/unicode/char/2070E/index.htm - 0xF0 0xA0 0x9C 0x8E / 240 160 156 142 let (:mixed_utf8) { "A\u06FF\u16A0\u{2070E}" } # AŰżáš ÜŽ it "converts a given non-UTF-8 message to UTF-8" do logs = [] Puppet::Util::Log.newdestination(Puppet::Test::LogCollector.new(logs)) Puppet::Util::Log.newdestination(:console) # HIRAGANA LETTER SO # In Windows_31J: \x82 \xbb - 130 187 # In Unicode: \u305d - \xe3 \x81 \x9d - 227 129 157 win_31j_msg = [130, 187].pack('C*').force_encoding(Encoding::Windows_31J) utf_8_msg = "\u305d" $stdout.expects(:puts).with("\e[mNotice: #{mixed_utf8}: #{utf_8_msg}\e[0m") # most handlers do special things with a :source => 'Puppet', so use something else Puppet::Util::Log.new(:level => :notice, :message => win_31j_msg, :source => mixed_utf8) expect(logs.last.message).to eq(utf_8_msg) end it "converts a given non-UTF-8 source to UTF-8" do logs = [] Puppet::Util::Log.newdestination(Puppet::Test::LogCollector.new(logs)) Puppet::Util::Log.newdestination(:console) # HIRAGANA LETTER SO # In Windows_31J: \x82 \xbb - 130 187 # In Unicode: \u305d - \xe3 \x81 \x9d - 227 129 157 win_31j_msg = [130, 187].pack('C*').force_encoding(Encoding::Windows_31J) utf_8_msg = "\u305d" $stdout.expects(:puts).with("\e[mNotice: #{utf_8_msg}: #{mixed_utf8}\e[0m") Puppet::Util::Log.new(:level => :notice, :message => mixed_utf8, :source => win_31j_msg) expect(logs.last.source).to eq(utf_8_msg) end describe ".setup_default" do it "should default to :syslog" do Puppet.features.stubs(:syslog?).returns(true) Puppet::Util::Log.expects(:newdestination).with(:syslog) Puppet::Util::Log.setup_default end it "should fall back to :eventlog" do Puppet.features.stubs(:syslog?).returns(false) Puppet.features.stubs(:eventlog?).returns(true) Puppet::Util::Log.expects(:newdestination).with(:eventlog) Puppet::Util::Log.setup_default end it "should fall back to :file" do Puppet.features.stubs(:syslog?).returns(false) Puppet.features.stubs(:eventlog?).returns(false) Puppet::Util::Log.expects(:newdestination).with(Puppet[:puppetdlog]) Puppet::Util::Log.setup_default end end describe "#with_destination" do it "does nothing when nested" do logs = [] destination = Puppet::Test::LogCollector.new(logs) Puppet::Util::Log.with_destination(destination) do Puppet::Util::Log.with_destination(destination) do log_notice("Inner block") end log_notice("Outer block") end log_notice("Outside") expect(logs.collect(&:message)).to include("Inner block", "Outer block") expect(logs.collect(&:message)).not_to include("Outside") end it "logs when called a second time" do logs = [] destination = Puppet::Test::LogCollector.new(logs) Puppet::Util::Log.with_destination(destination) do log_notice("First block") end log_notice("Between blocks") Puppet::Util::Log.with_destination(destination) do log_notice("Second block") end expect(logs.collect(&:message)).to include("First block", "Second block") expect(logs.collect(&:message)).not_to include("Between blocks") end it "doesn't close the destination if already set manually" do logs = [] destination = Puppet::Test::LogCollector.new(logs) Puppet::Util::Log.newdestination(destination) Puppet::Util::Log.with_destination(destination) do log_notice "Inner block" end log_notice "Outer block" Puppet::Util::Log.close(destination) expect(logs.collect(&:message)).to include("Inner block", "Outer block") end it 'includes backtrace for RuntimeError in log message when trace is enabled' do logs = [] destination = Puppet::Test::LogCollector.new(logs) Puppet::Util::Log.newdestination(destination) Puppet::Util::Log.with_destination(destination) do begin raise RuntimeError, 'Oops' rescue RuntimeError => e Puppet.log_exception(e, :default, :trace => true) end end expect(logs.size).to eq(1) log = logs[0] expect(log.message).to match('/log_spec.rb') expect(log.backtrace).to be_nil end it 'excludes backtrace for RuntimeError in log message when trace is disabled' do logs = [] destination = Puppet::Test::LogCollector.new(logs) Puppet::Util::Log.newdestination(destination) Puppet::Util::Log.with_destination(destination) do begin raise RuntimeError, 'Oops' rescue RuntimeError => e Puppet.log_exception(e) end end expect(logs.size).to eq(1) log = logs[0] expect(log.message).to_not match('/log_spec.rb') expect(log.backtrace).to be_nil end it "backtrace is Array in 'backtrace' and excluded from 'message' when logging ParseErrorWithIssue with trace enabled" do logs = [] destination = Puppet::Test::LogCollector.new(logs) Puppet::Util::Log.newdestination(destination) Puppet::Util::Log.with_destination(destination) do begin raise Puppet::ParseErrorWithIssue.new('Oops', '/tmp/test.pp', 30, 15, nil, :SYNTAX_ERROR) rescue RuntimeError => e Puppet.log_exception(e, :default, :trace => true) end end expect(logs.size).to eq(1) log = logs[0] expect(log.message).to_not match('/log_spec.rb') expect(log.backtrace).to be_a(Array) end it "backtrace is excluded when logging ParseErrorWithIssue with trace disabled" do logs = [] destination = Puppet::Test::LogCollector.new(logs) Puppet::Util::Log.newdestination(destination) Puppet::Util::Log.with_destination(destination) do begin raise Puppet::ParseErrorWithIssue.new('Oops', '/tmp/test.pp', 30, 15, nil, :SYNTAX_ERROR) rescue RuntimeError => e Puppet.log_exception(e) end end expect(logs.size).to eq(1) log = logs[0] expect(log.message).to_not match('/log_spec.rb') expect(log.backtrace).to be_nil end it 'includes position details for ParseError in log message' do logs = [] destination = Puppet::Test::LogCollector.new(logs) Puppet::Util::Log.newdestination(destination) Puppet::Util::Log.with_destination(destination) do begin raise Puppet::ParseError.new('Oops', '/tmp/test.pp', 30, 15) rescue RuntimeError => e Puppet.log_exception(e) end end expect(logs.size).to eq(1) log = logs[0] expect(log.message).to match(/ \(file: \/tmp\/test\.pp, line: 30, column: 15\)/) expect(log.message).to be(log.to_s) end it 'excludes position details for ParseErrorWithIssue from log message' do logs = [] destination = Puppet::Test::LogCollector.new(logs) Puppet::Util::Log.newdestination(destination) Puppet::Util::Log.with_destination(destination) do begin raise Puppet::ParseErrorWithIssue.new('Oops', '/tmp/test.pp', 30, 15, nil, :SYNTAX_ERROR) rescue RuntimeError => e Puppet.log_exception(e) end end expect(logs.size).to eq(1) log = logs[0] expect(log.message).to_not match(/ \(file: \/tmp\/test\.pp, line: 30, column: 15\)/) expect(log.to_s).to match(/ \(file: \/tmp\/test\.pp, line: 30, column: 15\)/) expect(log.issue_code).to eq(:SYNTAX_ERROR) expect(log.file).to eq('/tmp/test.pp') expect(log.line).to eq(30) expect(log.pos).to eq(15) end end describe Puppet::Util::Log::DestConsole do before do @console = Puppet::Util::Log::DestConsole.new end it "should colorize if Puppet[:color] is :ansi" do Puppet[:color] = :ansi expect(@console.colorize(:alert, "abc")).to eq("\e[0;31mabc\e[0m") end it "should colorize if Puppet[:color] is 'yes'" do Puppet[:color] = "yes" expect(@console.colorize(:alert, "abc")).to eq("\e[0;31mabc\e[0m") end it "should htmlize if Puppet[:color] is :html" do Puppet[:color] = :html expect(@console.colorize(:alert, "abc")).to eq("abc") end it "should do nothing if Puppet[:color] is false" do Puppet[:color] = false expect(@console.colorize(:alert, "abc")).to eq("abc") end it "should do nothing if Puppet[:color] is invalid" do Puppet[:color] = "invalid option" expect(@console.colorize(:alert, "abc")).to eq("abc") end end describe Puppet::Util::Log::DestSyslog do before do @syslog = Puppet::Util::Log::DestSyslog.new end end describe Puppet::Util::Log::DestEventlog, :if => Puppet.features.eventlog? do before :each do Puppet::Util::Windows::EventLog.stubs(:open).returns(stub 'mylog') Puppet::Util::Windows::EventLog.stubs(:report_event) Puppet::Util::Windows::EventLog.stubs(:close) Puppet.features.stubs(:eventlog?).returns(true) end it "should restrict its suitability to Windows" do Puppet.features.expects(:microsoft_windows?).returns(false) expect(Puppet::Util::Log::DestEventlog.suitable?('whatever')).to eq(false) end it "should open the 'Puppet' event log" do Puppet::Util::Windows::EventLog.expects(:open).with('Puppet') Puppet::Util::Log.newdestination(:eventlog) end it "should close the event log" do log = stub('myeventlog') log.expects(:close) Puppet::Util::Windows::EventLog.expects(:open).returns(log) Puppet::Util::Log.newdestination(:eventlog) Puppet::Util::Log.close(:eventlog) end it "should handle each puppet log level" do log = Puppet::Util::Log::DestEventlog.new Puppet::Util::Log.eachlevel do |level| expect(log.to_native(level)).to be_is_a(Array) end end end describe "instances" do before do Puppet::Util::Log.stubs(:newmessage) end [:level, :message, :time, :remote].each do |attr| it "should have a #{attr} attribute" do log = Puppet::Util::Log.new :level => :notice, :message => "A test message" expect(log).to respond_to(attr) expect(log).to respond_to(attr.to_s + "=") end end it "should fail if created without a level" do expect { Puppet::Util::Log.new(:message => "A test message") }.to raise_error(ArgumentError) end it "should fail if created without a message" do expect { Puppet::Util::Log.new(:level => :notice) }.to raise_error(ArgumentError) end it "should make available the level passed in at initialization" do expect(Puppet::Util::Log.new(:level => :notice, :message => "A test message").level).to eq(:notice) end it "should make available the message passed in at initialization" do expect(Puppet::Util::Log.new(:level => :notice, :message => "A test message").message).to eq("A test message") end # LAK:NOTE I don't know why this behavior is here, I'm just testing what's in the code, # at least at first. it "should always convert messages to strings" do expect(Puppet::Util::Log.new(:level => :notice, :message => :foo).message).to eq("foo") end it "should flush the log queue when the first destination is specified" do Puppet::Util::Log.close_all Puppet::Util::Log.expects(:flushqueue) Puppet::Util::Log.newdestination(:console) end it "should convert the level to a symbol if it's passed in as a string" do expect(Puppet::Util::Log.new(:level => "notice", :message => :foo).level).to eq(:notice) end it "should fail if the level is not a symbol or string" do expect { Puppet::Util::Log.new(:level => 50, :message => :foo) }.to raise_error(ArgumentError) end it "should fail if the provided level is not valid" do Puppet::Util::Log.expects(:validlevel?).with(:notice).returns false expect { Puppet::Util::Log.new(:level => :notice, :message => :foo) }.to raise_error(ArgumentError) end it "should set its time to the initialization time" do time = mock 'time' Time.expects(:now).returns time expect(Puppet::Util::Log.new(:level => "notice", :message => :foo).time).to equal(time) end it "should make available any passed-in tags" do log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :tags => %w{foo bar}) expect(log.tags).to be_include("foo") expect(log.tags).to be_include("bar") end it "should use a passed-in source" do Puppet::Util::Log.any_instance.expects(:source=).with "foo" Puppet::Util::Log.new(:level => "notice", :message => :foo, :source => "foo") end [:file, :line].each do |attr| it "should use #{attr} if provided" do Puppet::Util::Log.any_instance.expects(attr.to_s + "=").with "foo" Puppet::Util::Log.new(:level => "notice", :message => :foo, attr => "foo") end end it "should default to 'Puppet' as its source" do expect(Puppet::Util::Log.new(:level => "notice", :message => :foo).source).to eq("Puppet") end it "should register itself with Log" do Puppet::Util::Log.expects(:newmessage) Puppet::Util::Log.new(:level => "notice", :message => :foo) end it "should update Log autoflush when Puppet[:autoflush] is set" do Puppet::Util::Log.expects(:autoflush=).once.with(true) Puppet[:autoflush] = true end it "should have a method for determining if a tag is present" do expect(Puppet::Util::Log.new(:level => "notice", :message => :foo)).to respond_to(:tagged?) end it "should match a tag if any of the tags are equivalent to the passed tag as a string" do expect(Puppet::Util::Log.new(:level => "notice", :message => :foo, :tags => %w{one two})).to be_tagged(:one) end it "should tag itself with its log level" do expect(Puppet::Util::Log.new(:level => "notice", :message => :foo)).to be_tagged(:notice) end it "should return its message when converted to a string" do expect(Puppet::Util::Log.new(:level => "notice", :message => :foo).to_s).to eq("foo") end it "should include its time, source, level, and message when prepared for reporting" do log = Puppet::Util::Log.new(:level => "notice", :message => :foo) report = log.to_report expect(report).to be_include("notice") expect(report).to be_include("foo") expect(report).to be_include(log.source) expect(report).to be_include(log.time.to_s) end it "should not create unsuitable log destinations" do Puppet.features.stubs(:syslog?).returns(false) Puppet::Util::Log::DestSyslog.expects(:suitable?) Puppet::Util::Log::DestSyslog.expects(:new).never Puppet::Util::Log.newdestination(:syslog) end describe "when setting the source as a RAL object" do let(:path) { File.expand_path('/foo/bar') } it "should tag itself with any tags the source has" do source = Puppet::Type.type(:file).new :path => path log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :source => source) source.tags.each do |tag| expect(log.tags).to be_include(tag) end end it "should set the source to a type's 'path', when available" do source = Puppet::Type.type(:file).new :path => path source.tags = ["tag", "tag2"] log = Puppet::Util::Log.new(:level => "notice", :message => :foo) log.source = source expect(log).to be_tagged('file') expect(log).to be_tagged('tag') expect(log).to be_tagged('tag2') expect(log.source).to eq("/File[#{path}]") end it "should set the source to a provider's type's 'path', when available" do source = Puppet::Type.type(:file).new :path => path source.tags = ["tag", "tag2"] log = Puppet::Util::Log.new(:level => "notice", :message => :foo) log.source = source.provider expect(log.source).to match Regexp.quote("File\[#{path}\]\(provider=") end it "should copy over any file and line information" do source = Puppet::Type.type(:file).new :path => path source.file = "/my/file" source.line = 50 log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :source => source) expect(log.line).to eq(50) expect(log.file).to eq("/my/file") end end describe "when setting the source as a non-RAL object" do it "should not try to copy over file, version, line, or tag information" do source = mock source.expects(:file).never Puppet::Util::Log.new(:level => "notice", :message => :foo, :source => source) end end end describe "to_yaml" do it "should not include the @version attribute" do log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :version => 100) expect(log.to_data_hash.keys).not_to include('version') end it "should include attributes 'file', 'line', 'level', 'message', 'source', 'tags', and 'time'" do log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :version => 100) expect(log.to_data_hash.keys).to match_array(%w(file line level message source tags time)) end it "should include attributes 'file' and 'line' if specified" do log = Puppet::Util::Log.new(:level => "notice", :message => :foo, :file => "foo", :line => 35) expect(log.to_data_hash.keys).to include('file') expect(log.to_data_hash.keys).to include('line') end end let(:log) { Puppet::Util::Log.new(:level => 'notice', :message => 'hooray', :file => 'thefile', :line => 1729, :source => 'specs', :tags => ['a', 'b', 'c']) } it "should round trip through json" do tripped = Puppet::Util::Log.from_data_hash(JSON.parse(log.to_json)) expect(tripped.file).to eq(log.file) expect(tripped.line).to eq(log.line) expect(tripped.level).to eq(log.level) expect(tripped.message).to eq(log.message) expect(tripped.source).to eq(log.source) expect(tripped.tags).to eq(log.tags) expect(tripped.time).to eq(log.time) end it 'to_data_hash returns value that is instance of to Data' do expect(Puppet::Pops::Types::TypeFactory.data.instance?(log.to_data_hash)).to be_truthy end end puppet-5.5.10/spec/unit/util/logging_spec.rb0000644005276200011600000002717713417161722020704 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/logging' class LoggingTester include Puppet::Util::Logging end describe Puppet::Util::Logging do before do @logger = LoggingTester.new end Puppet::Util::Log.eachlevel do |level| it "should have a method for sending '#{level}' logs" do expect(@logger).to respond_to(level) end end it "should have a method for sending a log with a specified log level" do @logger.expects(:to_s).returns "I'm a string!" Puppet::Util::Log.expects(:create).with { |args| args[:source] == "I'm a string!" and args[:level] == "loglevel" and args[:message] == "mymessage" } @logger.send_log "loglevel", "mymessage" end describe "when sending a log" do it "should use the Log's 'create' entrance method" do Puppet::Util::Log.expects(:create) @logger.notice "foo" end it "should send itself converted to a string as the log source" do @logger.expects(:to_s).returns "I'm a string!" Puppet::Util::Log.expects(:create).with { |args| args[:source] == "I'm a string!" } @logger.notice "foo" end it "should queue logs sent without a specified destination" do Puppet::Util::Log.close_all Puppet::Util::Log.expects(:queuemessage) @logger.notice "foo" end it "should use the path of any provided resource type" do resource = Puppet::Type.type(:host).new :name => "foo" resource.expects(:path).returns "/path/to/host".to_sym Puppet::Util::Log.expects(:create).with { |args| args[:source] == "/path/to/host" } resource.notice "foo" end it "should use the path of any provided resource parameter" do resource = Puppet::Type.type(:host).new :name => "foo" param = resource.parameter(:name) param.expects(:path).returns "/path/to/param".to_sym Puppet::Util::Log.expects(:create).with { |args| args[:source] == "/path/to/param" } param.notice "foo" end it "should send the provided argument as the log message" do Puppet::Util::Log.expects(:create).with { |args| args[:message] == "foo" } @logger.notice "foo" end it "should join any provided arguments into a single string for the message" do Puppet::Util::Log.expects(:create).with { |args| args[:message] == "foo bar baz" } @logger.notice ["foo", "bar", "baz"] end [:file, :line, :tags].each do |attr| it "should include #{attr} if available" do @logger.singleton_class.send(:attr_accessor, attr) @logger.send(attr.to_s + "=", "myval") Puppet::Util::Log.expects(:create).with { |args| args[attr] == "myval" } @logger.notice "foo" end end end describe "when sending a deprecation warning" do it "does not log a message when deprecation warnings are disabled" do Puppet.expects(:[]).with(:disable_warnings).returns %w[deprecations] @logger.expects(:warning).never @logger.deprecation_warning 'foo' end it "logs the message with warn" do @logger.expects(:warning).with do |msg| msg =~ /^foo\n/ end @logger.deprecation_warning 'foo' end it "only logs each offending line once" do @logger.expects(:warning).with do |msg| msg =~ /^foo\n/ end .once 5.times { @logger.deprecation_warning 'foo' } end it "ensures that deprecations from same origin are logged if their keys differ" do @logger.expects(:warning).with(regexp_matches(/deprecated foo/)).times(5) 5.times { |i| @logger.deprecation_warning('deprecated foo', :key => "foo#{i}") } end it "does not duplicate deprecations for a given key" do @logger.expects(:warning).with(regexp_matches(/deprecated foo/)).once 5.times { @logger.deprecation_warning('deprecated foo', :key => 'foo-msg') } end it "only logs the first 100 messages" do (1..100).each { |i| @logger.expects(:warning).with do |msg| msg =~ /^#{i}\n/ end .once # since the deprecation warning will only log each offending line once, we have to do some tomfoolery # here in order to make it think each of these calls is coming from a unique call stack; we're basically # mocking the method that it would normally use to find the call stack. @logger.expects(:get_deprecation_offender).returns(["deprecation log count test ##{i}"]) @logger.deprecation_warning i } @logger.expects(:warning).with(101).never @logger.deprecation_warning 101 end end describe "when sending a puppet_deprecation_warning" do it "requires file and line or key options" do expect do @logger.puppet_deprecation_warning("foo") end.to raise_error(Puppet::DevError, /Need either :file and :line, or :key/) expect do @logger.puppet_deprecation_warning("foo", :file => 'bar') end.to raise_error(Puppet::DevError, /Need either :file and :line, or :key/) expect do @logger.puppet_deprecation_warning("foo", :key => 'akey') @logger.puppet_deprecation_warning("foo", :file => 'afile', :line => 1) end.to_not raise_error end it "warns with file and line" do @logger.expects(:warning).with(regexp_matches(/deprecated foo.*\(file: afile, line: 5\)/m)) @logger.puppet_deprecation_warning("deprecated foo", :file => 'afile', :line => 5) end it "warns keyed from file and line" do @logger.expects(:warning).with(regexp_matches(/deprecated foo.*\(file: afile, line: 5\)/m)).once 5.times do @logger.puppet_deprecation_warning("deprecated foo", :file => 'afile', :line => 5) end end it "warns with separate key only once regardless of file and line" do @logger.expects(:warning).with(regexp_matches(/deprecated foo.*\(file: afile, line: 5\)/m)).once @logger.puppet_deprecation_warning("deprecated foo", :key => 'some_key', :file => 'afile', :line => 5) @logger.puppet_deprecation_warning("deprecated foo", :key => 'some_key', :file => 'bfile', :line => 3) end it "warns with key but no file and line" do @logger.expects(:warning).with(regexp_matches(/deprecated foo.*\(file: unknown, line: unknown\)/m)) @logger.puppet_deprecation_warning("deprecated foo", :key => 'some_key') end end describe "when sending a warn_once" do before(:each) { @logger.clear_deprecation_warnings } after(:each) { # this is required because of bugs in Mocha whe tearing down expectations for each test # why it works elsewhere is a mystery. @logger.unstub(:send_log) } it "warns with file when only file is given" do @logger.expects(:send_log).with(:warning, regexp_matches(/wet paint.*\(file: aFile\)/m)) @logger.warn_once('kind', 'wp', "wet paint", 'aFile') end it "warns with unknown file and line when only line is given" do @logger.expects(:send_log).with(:warning, regexp_matches(/wet paint.*\(line: 5\)/m)) @logger.warn_once('kind', 'wp', "wet paint", nil, 5) end it "warns with file and line when both are given" do @logger.expects(:send_log).with(:warning, regexp_matches(/wet paint.*\(file: aFile, line: 5\)/m)) @logger.warn_once('kind', 'wp', "wet paint",'aFile', 5) end it "warns once per key" do @logger.expects(:send_log).with(:warning, regexp_matches(/wet paint.*/m)).once 5.times do @logger.warn_once('kind', 'wp', "wet paint") end end Puppet::Util::Log.eachlevel do |level| it "can use log level #{level}" do @logger.expects(:send_log).with(level, regexp_matches(/wet paint.*/m)).once 5.times do @logger.warn_once('kind', 'wp', "wet paint", nil, nil, level) end end end end describe "does not warn about undefined variables when disabled_warnings says so" do let(:logger) { LoggingTester.new } around(:each) do |example| Puppet.settings.initialize_global_settings logger.clear_deprecation_warnings Puppet[:disable_warnings] = ['undefined_variables'] example.run Puppet[:disable_warnings] = [] logger.unstub(:send_log) end it "does not produce warning if kind is disabled" do logger.expects(:send_log).never logger.warn_once('undefined_variables', 'wp', "wet paint") end end describe "warns about undefined variables when deprecations are in disabled_warnings" do let(:logger) { LoggingTester.new } around(:each) do |example| Puppet.settings.initialize_global_settings logger.clear_deprecation_warnings Puppet[:disable_warnings] = ['deprecations'] example.run Puppet[:disable_warnings] = [] logger.unstub(:send_log) end it "produces warning even if deprecation warnings are disabled " do logger.expects(:send_log).with(:warning, regexp_matches(/wet paint/)).once logger.warn_once('undefined_variables', 'wp', "wet paint") end end describe "when formatting exceptions" do it "should be able to format a chain of exceptions" do exc3 = Puppet::Error.new("original") exc3.set_backtrace(["1.rb:4:in `a'","2.rb:2:in `b'","3.rb:1"]) exc2 = Puppet::Error.new("second", exc3) exc2.set_backtrace(["4.rb:8:in `c'","5.rb:1:in `d'","6.rb:3"]) exc1 = Puppet::Error.new("third", exc2) exc1.set_backtrace(["7.rb:31:in `e'","8.rb:22:in `f'","9.rb:9"]) # whoa ugly expect(@logger.format_exception(exc1)).to match(/third .*7\.rb:31:in `e' .*8\.rb:22:in `f' .*9\.rb:9 Wrapped exception: second .*4\.rb:8:in `c' .*5\.rb:1:in `d' .*6\.rb:3 Wrapped exception: original .*1\.rb:4:in `a' .*2\.rb:2:in `b' .*3\.rb:1/) end end describe 'when Facter' do after :each do # Unstub these calls as there is global code run after # each spec that may reset the log level to debug Facter.unstub(:respond_to?) Facter.unstub(:debugging) end describe 'does support debugging' do before :each do Facter.stubs(:respond_to?).with(:debugging).returns true end it 'enables Facter debugging when debug level' do Facter.stubs(:debugging).with(true) Puppet::Util::Log.level = :debug end it 'disables Facter debugging when not debug level' do Facter.stubs(:debugging).with(false) Puppet::Util::Log.level = :info end end describe 'does support trace' do before :each do Facter.stubs(:respond_to?).with(:trace).returns true end it 'enables Facter trace when enabled' do Facter.stubs(:trace).with(true) Puppet[:trace] = true end it 'disables Facter trace when disabled' do Facter.stubs(:trace).with(false) Puppet[:trace] = false end end describe 'does support on_message' do before :each do Facter.stubs(:respond_to?).with(:on_message).returns true end def setup(level, message) Facter.stubs(:on_message).yields level, message # Transform from Facter level to Puppet level case level when :trace level = :debug when :warn level = :warning when :error level = :err when :fatal level = :crit end Puppet::Util::Log.stubs(:create).with do |options| expect(options[:level]).to eq(level) expect(options[:message]).to eq(message) expect(options[:source]).to eq('Facter') end.once end [:trace, :debug, :info, :warn, :error, :fatal].each do |level| it "calls Facter.on_message and handles #{level} messages" do setup(level, "#{level} message") expect(Puppet::Util::Logging::setup_facter_logging!).to be_truthy end end end end end puppet-5.5.10/spec/unit/util/monkey_patches_spec.rb0000644005276200011600000001002313417161722022245 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/monkey_patches' describe Symbol do after :all do $unique_warnings.delete('symbol_comparison') if $unique_warnings end it 'should have an equal? that is not true for a string with same letters' do symbol = :undef expect(symbol).to_not equal('undef') end it "should have an eql? that is not true for a string with same letters" do symbol = :undef expect(symbol).to_not eql('undef') end it "should have an == that is not true for a string with same letters" do pending "JRuby is incompatible with MRI - Cannot test this on JRuby" if RUBY_PLATFORM == 'java' symbol = :undef expect(symbol == 'undef').to_not be(true) end it "should return self from #intern" do symbol = :foo expect(symbol).to equal symbol.intern end end describe OpenSSL::SSL::SSLContext do it 'disables SSLv2 via the SSLContext#options bitmask' do expect(subject.options & OpenSSL::SSL::OP_NO_SSLv2).to eq(OpenSSL::SSL::OP_NO_SSLv2) end it 'disables SSLv3 via the SSLContext#options bitmask' do expect(subject.options & OpenSSL::SSL::OP_NO_SSLv3).to eq(OpenSSL::SSL::OP_NO_SSLv3) end it 'explicitly disable SSLv2 ciphers using the ! prefix so they cannot be re-added' do cipher_str = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:ciphers] if cipher_str expect(cipher_str.split(':')).to include('!SSLv2') end end it 'does not exclude SSLv3 ciphers shared with TLSv1' do cipher_str = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:ciphers] if cipher_str expect(cipher_str.split(':')).not_to include('!SSLv3') end end it 'sets parameters on initialization' do described_class.any_instance.expects(:set_params) subject end it 'has no ciphers with version SSLv2 enabled' do ciphers = subject.ciphers.select do |name, version, bits, alg_bits| /SSLv2/.match(version) end expect(ciphers).to be_empty end end describe OpenSSL::X509::Store, :if => Puppet::Util::Platform.windows? do let(:store) { described_class.new } let(:cert) { OpenSSL::X509::Certificate.new(File.read(my_fixture('x509.pem'))) } let(:samecert) { cert.dup() } def with_root_certs(certs) Puppet::Util::Windows::RootCerts.expects(:instance).returns(certs) end it "adds a root cert to the store" do with_root_certs([cert]) store.set_default_paths end it "doesn't warn when calling set_default_paths multiple times" do with_root_certs([cert]) store.expects(:warn).never store.set_default_paths store.set_default_paths end it "ignores duplicate root certs" do # prove that even though certs have identical contents, their hashes differ expect(cert.hash).to_not eq(samecert.hash) with_root_certs([cert, samecert]) store.expects(:add_cert).with(cert).once store.expects(:add_cert).with(samecert).never store.set_default_paths end # openssl 1.1.1 ignores duplicate certs # https://github.com/openssl/openssl/commit/c0452248ea1a59a41023a4765ef7d9825e80a62b if OpenSSL::OPENSSL_VERSION_NUMBER < 0x10101000 it "warns when adding a certificate that already exists" do with_root_certs([cert]) store.add_cert(cert) store.expects(:warn).with('Failed to add /DC=com/DC=microsoft/CN=Microsoft Root Certificate Authority') store.set_default_paths end else it "doesn't warn when adding a duplicate cert" do with_root_certs([cert]) store.add_cert(cert) store.expects(:warn).never store.set_default_paths end end it "raises when adding an invalid certificate" do with_root_certs(['notacert']) expect { store.set_default_paths }.to raise_error(TypeError) end end describe SecureRandom do it 'generates a properly formatted uuid' do expect(SecureRandom.uuid).to match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i) end end describe 'Enumerable' do it 'expects uniq to work on an Enumerable' do expect(['c', 'c', 'C'].reverse_each.uniq).to eql(['C', 'c']) end end puppet-5.5.10/spec/unit/util/nagios_maker_spec.rb0000644005276200011600000000766013417161722021710 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/nagios_maker' describe Puppet::Util::NagiosMaker do before do @module = Puppet::Util::NagiosMaker @nagtype = stub 'nagios type', :parameters => [], :namevar => :name Nagios::Base.stubs(:type).with(:test).returns(@nagtype) @provider = stub 'provider', :nagios_type => nil @type = stub 'type', :newparam => nil, :newproperty => nil, :provide => @provider, :desc => nil, :ensurable => nil end it "should be able to create a new nagios type" do expect(@module).to respond_to(:create_nagios_type) end it "should fail if it cannot find the named Naginator type" do Nagios::Base.stubs(:type).returns(nil) expect { @module.create_nagios_type(:no_such_type) }.to raise_error(Puppet::DevError) end it "should create a new RAL type with the provided name prefixed with 'nagios_'" do Puppet::Type.expects(:newtype).with(:nagios_test).returns(@type) @module.create_nagios_type(:test) end it "should mark the created type as ensurable" do @type.expects(:ensurable) Puppet::Type.expects(:newtype).with(:nagios_test).returns(@type) @module.create_nagios_type(:test) end it "should create a namevar parameter for the nagios type's name parameter" do @type.expects(:newparam).with(:name, :namevar => true) Puppet::Type.expects(:newtype).with(:nagios_test).returns(@type) @module.create_nagios_type(:test) end it "should create a property for all non-namevar parameters" do @nagtype.stubs(:parameters).returns([:one, :two]) @type.expects(:newproperty).with(:one) @type.expects(:newproperty).with(:two) @type.expects(:newproperty).with(:target) Puppet::Type.expects(:newtype).with(:nagios_test).returns(@type) @module.create_nagios_type(:test) end it "should skip parameters that start with integers" do @nagtype.stubs(:parameters).returns(["2dcoords".to_sym, :other]) @type.expects(:newproperty).with(:other) @type.expects(:newproperty).with(:target) Puppet::Type.expects(:newtype).with(:nagios_test).returns(@type) @module.create_nagios_type(:test) end it "should deduplicate the parameter list" do @nagtype.stubs(:parameters).returns([:one, :one]) @type.expects(:newproperty).with(:one) @type.expects(:newproperty).with(:target) Puppet::Type.expects(:newtype).with(:nagios_test).returns(@type) @module.create_nagios_type(:test) end it "should create a target property" do @type.expects(:newproperty).with(:target) Puppet::Type.expects(:newtype).with(:nagios_test).returns(@type) @module.create_nagios_type(:test) end end describe Puppet::Util::NagiosMaker, " when creating the naginator provider" do before do @module = Puppet::Util::NagiosMaker @provider = stub 'provider', :nagios_type => nil @nagtype = stub 'nagios type', :parameters => [], :namevar => :name Nagios::Base.stubs(:type).with(:test).returns(@nagtype) @type = stub 'type', :newparam => nil, :ensurable => nil, :newproperty => nil, :desc => nil Puppet::Type.stubs(:newtype).with(:nagios_test).returns(@type) end it "should add a naginator provider" do @type.expects(:provide).with { |name, options| name == :naginator }.returns @provider @module.create_nagios_type(:test) end it "should set Puppet::Provider::Naginator as the parent class of the provider" do @type.expects(:provide).with { |name, options| options[:parent] == Puppet::Provider::Naginator }.returns @provider @module.create_nagios_type(:test) end it "should use /etc/nagios/$name.cfg as the default target" do @type.expects(:provide).with { |name, options| options[:default_target] == "/etc/nagios/nagios_test.cfg" }.returns @provider @module.create_nagios_type(:test) end it "should trigger the lookup of the Nagios class" do @type.expects(:provide).returns @provider @provider.expects(:nagios_type) @module.create_nagios_type(:test) end end puppet-5.5.10/spec/unit/util/rubygems_spec.rb0000644005276200011600000000564513417161722021107 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/util/rubygems' describe Puppet::Util::RubyGems::Source do let(:gem_path) { File.expand_path('/foo/gems') } let(:gem_lib) { File.join(gem_path, 'lib') } let(:fake_gem) { stub(:full_gem_path => gem_path) } describe "::new" do it "returns NoGemsSource if rubygems is not present" do described_class.expects(:has_rubygems?).returns(false) expect(described_class.new).to be_kind_of(Puppet::Util::RubyGems::NoGemsSource) end it "returns Gems18Source if Gem::Specification responds to latest_specs" do described_class.expects(:has_rubygems?).returns(true) Gem::Specification.expects(:respond_to?).with(:latest_specs).returns(true) expect(described_class.new).to be_kind_of(Puppet::Util::RubyGems::Gems18Source) end it "returns Gems18Source if Gem::Specification does not respond to latest_specs" do described_class.expects(:has_rubygems?).returns(true) Gem::Specification.expects(:respond_to?).with(:latest_specs).returns(false) expect(described_class.new).to be_kind_of(Puppet::Util::RubyGems::OldGemsSource) end end describe '::NoGemsSource' do before(:each) { described_class.stubs(:source).returns(Puppet::Util::RubyGems::NoGemsSource) } it "#directories returns an empty list" do expect(described_class.new.directories).to eq([]) end it "#clear_paths returns nil" do expect(described_class.new.clear_paths).to be_nil end end describe '::Gems18Source' do before(:each) { described_class.stubs(:source).returns(Puppet::Util::RubyGems::Gems18Source) } it "#directories returns the lib subdirs of Gem::Specification.latest_specs" do Gem::Specification.expects(:latest_specs).with(true).returns([fake_gem]) expect(described_class.new.directories).to eq([gem_lib]) end it "#clear_paths calls Gem.clear_paths" do Gem.expects(:clear_paths) described_class.new.clear_paths end end describe '::OldGemsSource' do before(:each) { described_class.stubs(:source).returns(Puppet::Util::RubyGems::OldGemsSource) } it "#directories returns the contents of Gem.latest_load_paths" do Gem.expects(:latest_load_paths).returns([gem_lib]) expect(described_class.new.directories).to eq([gem_lib]) end # Older rubygems seem to have a problem with rescanning the gem paths in which they # look for a file in the wrong place and expect it to be there. By caching the first # set of results we don't trigger this bug. This behavior was seen on ruby 1.8.7-p334 # using rubygems v1.6.2 it "caches the gem paths (works around a bug in older rubygems)" do Gem.expects(:latest_load_paths).returns([gem_lib]).once source = described_class.new expect(source.directories).to eq([gem_lib]) end it "#clear_paths calls Gem.clear_paths" do Gem.expects(:clear_paths) described_class.new.clear_paths end end end puppet-5.5.10/spec/unit/util/run_mode_spec.rb0000644005276200011600000002641413417161722021057 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Util::RunMode do # Discriminator for tests that attempts to unset HOME since that, for reasons currently unknown, # doesn't work in Ruby >= 2.4.0 def self.gte_ruby_2_4 @gte_ruby_2_4 ||= SemanticPuppet::Version.parse(RUBY_VERSION) >= SemanticPuppet::Version.parse('2.4.0') end before do @run_mode = Puppet::Util::RunMode.new('fake') end describe Puppet::Util::UnixRunMode, :unless => Puppet.features.microsoft_windows? do before do @run_mode = Puppet::Util::UnixRunMode.new('fake') end describe "#conf_dir" do it "has confdir /etc/puppetlabs/puppet when run as root" do as_root { expect(@run_mode.conf_dir).to eq(File.expand_path('/etc/puppetlabs/puppet')) } end it "has confdir ~/.puppetlabs/etc/puppet when run as non-root" do as_non_root { expect(@run_mode.conf_dir).to eq(File.expand_path('~/.puppetlabs/etc/puppet')) } end context "master run mode" do before do @run_mode = Puppet::Util::UnixRunMode.new('master') end it "has confdir ~/.puppetlabs/etc/puppet when run as non-root and master run mode" do as_non_root { expect(@run_mode.conf_dir).to eq(File.expand_path('~/.puppetlabs/etc/puppet')) } end end it "fails when asking for the conf_dir as non-root and there is no $HOME", :unless => gte_ruby_2_4 || Puppet.features.microsoft_windows? do as_non_root do without_home do expect { @run_mode.conf_dir }.to raise_error ArgumentError, /couldn't find HOME/ end end end end describe "#code_dir" do it "has codedir /etc/puppetlabs/code when run as root" do as_root { expect(@run_mode.code_dir).to eq(File.expand_path('/etc/puppetlabs/code')) } end it "has codedir ~/.puppetlabs/etc/code when run as non-root" do as_non_root { expect(@run_mode.code_dir).to eq(File.expand_path('~/.puppetlabs/etc/code')) } end context "master run mode" do before do @run_mode = Puppet::Util::UnixRunMode.new('master') end it "has codedir ~/.puppetlabs/etc/code when run as non-root and master run mode" do as_non_root { expect(@run_mode.code_dir).to eq(File.expand_path('~/.puppetlabs/etc/code')) } end end it "fails when asking for the code_dir as non-root and there is no $HOME", :unless => gte_ruby_2_4 || Puppet.features.microsoft_windows? do as_non_root do without_home do expect { @run_mode.code_dir }.to raise_error ArgumentError, /couldn't find HOME/ end end end end describe "#var_dir" do it "has vardir /opt/puppetlabs/puppet/cache when run as root" do as_root { expect(@run_mode.var_dir).to eq(File.expand_path('/opt/puppetlabs/puppet/cache')) } end it "has vardir ~/.puppetlabs/opt/puppet/cache when run as non-root" do as_non_root { expect(@run_mode.var_dir).to eq(File.expand_path('~/.puppetlabs/opt/puppet/cache')) } end it "fails when asking for the var_dir as non-root and there is no $HOME", :unless => gte_ruby_2_4 || Puppet.features.microsoft_windows? do as_non_root do without_home do expect { @run_mode.var_dir }.to raise_error ArgumentError, /couldn't find HOME/ end end end end describe "#log_dir" do describe "when run as root" do it "has logdir /var/log/puppetlabs/puppet" do as_root { expect(@run_mode.log_dir).to eq(File.expand_path('/var/log/puppetlabs/puppet')) } end end describe "when run as non-root" do it "has default logdir ~/.puppetlabs/var/log" do as_non_root { expect(@run_mode.log_dir).to eq(File.expand_path('~/.puppetlabs/var/log')) } end it "fails when asking for the log_dir and there is no $HOME", :unless => gte_ruby_2_4 || Puppet.features.microsoft_windows? do as_non_root do without_home do expect { @run_mode.log_dir }.to raise_error ArgumentError, /couldn't find HOME/ end end end end end describe "#run_dir" do describe "when run as root" do it "has rundir /var/run/puppetlabs" do as_root { expect(@run_mode.run_dir).to eq(File.expand_path('/var/run/puppetlabs')) } end end describe "when run as non-root" do it "has default rundir ~/.puppetlabs/var/run" do as_non_root { expect(@run_mode.run_dir).to eq(File.expand_path('~/.puppetlabs/var/run')) } end it "fails when asking for the run_dir and there is no $HOME", :unless => gte_ruby_2_4 || Puppet.features.microsoft_windows? do as_non_root do without_home do expect { @run_mode.run_dir }.to raise_error ArgumentError, /couldn't find HOME/ end end end end end end describe Puppet::Util::WindowsRunMode, :if => Puppet.features.microsoft_windows? do before do if not Dir.const_defined? :COMMON_APPDATA Dir.const_set :COMMON_APPDATA, "/CommonFakeBase" @remove_const = true end @run_mode = Puppet::Util::WindowsRunMode.new('fake') end after do if @remove_const Dir.send :remove_const, :COMMON_APPDATA end end describe "#conf_dir" do it "has confdir ending in Puppetlabs/puppet/etc when run as root" do as_root { expect(@run_mode.conf_dir).to eq(File.expand_path(File.join(Dir::COMMON_APPDATA, "PuppetLabs", "puppet", "etc"))) } end it "has confdir in ~/.puppetlabs/etc/puppet when run as non-root" do as_non_root { expect(@run_mode.conf_dir).to eq(File.expand_path("~/.puppetlabs/etc/puppet")) } end it "fails when asking for the conf_dir as non-root and there is no %HOME%, %HOMEDRIVE%, and %USERPROFILE%", :unless => gte_ruby_2_4 do as_non_root do without_env('HOME') do without_env('HOMEDRIVE') do without_env('USERPROFILE') do expect { @run_mode.conf_dir }.to raise_error ArgumentError, /couldn't find HOME/ end end end end end end describe "#code_dir" do it "has codedir ending in PuppetLabs/code when run as root" do as_root { expect(@run_mode.code_dir).to eq(File.expand_path(File.join(Dir::COMMON_APPDATA, "PuppetLabs", "code"))) } end it "has codedir in ~/.puppetlabs/etc/code when run as non-root" do as_non_root { expect(@run_mode.code_dir).to eq(File.expand_path("~/.puppetlabs/etc/code")) } end it "fails when asking for the code_dir as non-root and there is no %HOME%, %HOMEDRIVE%, and %USERPROFILE%", :unless => gte_ruby_2_4 do as_non_root do without_env('HOME') do without_env('HOMEDRIVE') do without_env('USERPROFILE') do expect { @run_mode.code_dir }.to raise_error ArgumentError, /couldn't find HOME/ end end end end end end describe "#var_dir" do it "has vardir ending in PuppetLabs/puppet/cache when run as root" do as_root { expect(@run_mode.var_dir).to eq(File.expand_path(File.join(Dir::COMMON_APPDATA, "PuppetLabs", "puppet", "cache"))) } end it "has vardir in ~/.puppetlabs/opt/puppet/cache when run as non-root" do as_non_root { expect(@run_mode.var_dir).to eq(File.expand_path("~/.puppetlabs/opt/puppet/cache")) } end it "fails when asking for the conf_dir as non-root and there is no %HOME%, %HOMEDRIVE%, and %USERPROFILE%", :unless => gte_ruby_2_4 do as_non_root do without_env('HOME') do without_env('HOMEDRIVE') do without_env('USERPROFILE') do expect { @run_mode.var_dir }.to raise_error ArgumentError, /couldn't find HOME/ end end end end end end describe "#log_dir" do describe "when run as root" do it "has logdir ending in PuppetLabs/puppet/var/log" do as_root { expect(@run_mode.log_dir).to eq(File.expand_path(File.join(Dir::COMMON_APPDATA, "PuppetLabs", "puppet", "var", "log"))) } end end describe "when run as non-root" do it "has default logdir ~/.puppetlabs/var/log" do as_non_root { expect(@run_mode.log_dir).to eq(File.expand_path('~/.puppetlabs/var/log')) } end it "fails when asking for the log_dir and there is no $HOME", :unless => gte_ruby_2_4 do as_non_root do without_env('HOME') do without_env('HOMEDRIVE') do without_env('USERPROFILE') do expect { @run_mode.log_dir }.to raise_error ArgumentError, /couldn't find HOME/ end end end end end end end describe "#run_dir" do describe "when run as root" do it "has rundir ending in PuppetLabs/puppet/var/run" do as_root { expect(@run_mode.run_dir).to eq(File.expand_path(File.join(Dir::COMMON_APPDATA, "PuppetLabs", "puppet", "var", "run"))) } end end describe "when run as non-root" do it "has default rundir ~/.puppetlabs/var/run" do as_non_root { expect(@run_mode.run_dir).to eq(File.expand_path('~/.puppetlabs/var/run')) } end it "fails when asking for the run_dir and there is no $HOME", :unless => gte_ruby_2_4 do as_non_root do without_env('HOME') do without_env('HOMEDRIVE') do without_env('USERPROFILE') do expect { @run_mode.run_dir }.to raise_error ArgumentError, /couldn't find HOME/ end end end end end end end describe "#without_env internal helper with UTF8 characters" do let(:varname) { "\u16A0\u16C7\u16BB\u16EB\u16D2\u16E6\u16A6\u16EB\u16A0\u16B1\u16A9\u16A0\u16A2\u16B1\u16EB\u16A0\u16C1\u16B1\u16AA\u16EB\u16B7\u16D6\u16BB\u16B9\u16E6\u16DA\u16B3\u16A2\u16D7" } let(:rune_utf8) { "\u16A0\u16C7\u16BB\u16EB\u16D2\u16E6\u16A6\u16EB\u16A0\u16B1\u16A9\u16A0\u16A2\u16B1\u16EB\u16A0\u16C1\u16B1\u16AA\u16EB\u16B7\u16D6\u16BB\u16B9\u16E6\u16DA\u16B3\u16A2\u16D7" } before do Puppet::Util::Windows::Process.set_environment_variable(varname, rune_utf8) end it "removes environment variables within the block with UTF8 name" do without_env(varname) do expect(ENV[varname]).to be(nil) end end it "restores UTF8 characters in environment variable values" do without_env(varname) do Puppet::Util::Windows::Process.set_environment_variable(varname, 'bad value') end envhash = Puppet::Util::Windows::Process.get_environment_strings expect(envhash[varname]).to eq(rune_utf8) end end end def as_root Puppet.features.stubs(:root?).returns(true) yield end def as_non_root Puppet.features.stubs(:root?).returns(false) yield end def without_env(name, &block) saved = Puppet::Util.get_env(name) Puppet::Util.set_env(name, nil) yield ensure Puppet::Util.set_env(name, saved) end def without_home(&block) without_env('HOME', &block) end end puppet-5.5.10/spec/unit/util/selinux_spec.rb0000644005276200011600000003102713417161722020732 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'pathname' require 'puppet/util/selinux' include Puppet::Util::SELinux unless defined?(Selinux) module Selinux def self.is_selinux_enabled false end end end describe Puppet::Util::SELinux do describe "selinux_support?" do before do end it "should return :true if this system has SELinux enabled" do Selinux.expects(:is_selinux_enabled).returns 1 expect(selinux_support?).to be_truthy end it "should return :false if this system lacks SELinux" do Selinux.expects(:is_selinux_enabled).returns 0 expect(selinux_support?).to be_falsey end it "should return nil if /proc/mounts does not exist" do File.stubs(:open).with("/proc/mounts").raises("No such file or directory - /proc/mounts") expect(read_mounts).to eq(nil) end end describe "read_mounts" do before :each do fh = stub 'fh', :close => nil File.stubs(:open).with("/proc/mounts").returns fh fh.expects(:read_nonblock).times(2).returns("rootfs / rootfs rw 0 0\n/dev/root / ext3 rw,relatime,errors=continue,user_xattr,acl,data=ordered 0 0\n/dev /dev tmpfs rw,relatime,mode=755 0 0\n/proc /proc proc rw,relatime 0 0\n/sys /sys sysfs rw,relatime 0 0\n192.168.1.1:/var/export /mnt/nfs nfs rw,relatime,vers=3,rsize=32768,wsize=32768,namlen=255,hard,nointr,proto=tcp,timeo=600,retrans=2,sec=sys,mountaddr=192.168.1.1,mountvers=3,mountproto=udp,addr=192.168.1.1 0 0\n").then.raises EOFError end it "should parse the contents of /proc/mounts" do expect(read_mounts).to eq({ '/' => 'ext3', '/sys' => 'sysfs', '/mnt/nfs' => 'nfs', '/proc' => 'proc', '/dev' => 'tmpfs' }) end end describe "filesystem detection" do before :each do self.stubs(:read_mounts).returns({ '/' => 'ext3', '/sys' => 'sysfs', '/mnt/nfs' => 'nfs', '/proc' => 'proc', '/dev' => 'tmpfs' }) end it "should match a path on / to ext3" do expect(find_fs('/etc/puppetlabs/puppet/testfile')).to eq("ext3") end it "should match a path on /mnt/nfs to nfs" do expect(find_fs('/mnt/nfs/testfile/foobar')).to eq("nfs") end it "should return true for a capable filesystem" do expect(selinux_label_support?('/etc/puppetlabs/puppet/testfile')).to be_truthy end it "should return true if tmpfs" do expect(selinux_label_support?('/dev/shm/testfile')).to be_truthy end it "should return false for a noncapable filesystem" do expect(selinux_label_support?('/mnt/nfs/testfile')).to be_falsey end it "(#8714) don't follow symlinks when determining file systems", :unless => Puppet.features.microsoft_windows? do scratch = Pathname(PuppetSpec::Files.tmpdir('selinux')) self.stubs(:read_mounts).returns({ '/' => 'ext3', scratch + 'nfs' => 'nfs', }) (scratch + 'foo').make_symlink('nfs/bar') expect(selinux_label_support?(scratch + 'foo')).to be_truthy end it "should handle files that don't exist" do scratch = Pathname(PuppetSpec::Files.tmpdir('selinux')) expect(selinux_label_support?(scratch + 'nonesuch')).to be_truthy end end describe "get_selinux_current_context" do it "should return nil if no SELinux support" do self.expects(:selinux_support?).returns false expect(get_selinux_current_context("/foo")).to be_nil end it "should return a context" do self.expects(:selinux_support?).returns true Selinux.expects(:lgetfilecon).with("/foo").returns [0, "user_u:role_r:type_t:s0"] expect(get_selinux_current_context("/foo")).to eq("user_u:role_r:type_t:s0") end it "should return nil if lgetfilecon fails" do self.expects(:selinux_support?).returns true Selinux.expects(:lgetfilecon).with("/foo").returns(-1) expect(get_selinux_current_context("/foo")).to be_nil end end describe "get_selinux_default_context" do it "should return nil if no SELinux support" do self.expects(:selinux_support?).returns false expect(get_selinux_default_context("/foo")).to be_nil end it "should return a context if a default context exists" do self.expects(:selinux_support?).returns true fstat = stub 'File::Stat', :mode => 0 Puppet::FileSystem.expects(:lstat).with('/foo').returns(fstat) self.expects(:find_fs).with("/foo").returns "ext3" Selinux.expects(:matchpathcon).with("/foo", 0).returns [0, "user_u:role_r:type_t:s0"] expect(get_selinux_default_context("/foo")).to eq("user_u:role_r:type_t:s0") end it "handles permission denied errors by issuing a warning" do self.stubs(:selinux_support?).returns true self.stubs(:selinux_label_support?).returns true Selinux.stubs(:matchpathcon).with("/root/chuj", 0).returns(-1) self.stubs(:file_lstat).with("/root/chuj").raises(Errno::EACCES, "/root/chuj") expect(get_selinux_default_context("/root/chuj")).to be_nil end it "handles no such file or directory errors by issuing a warning" do self.stubs(:selinux_support?).returns true self.stubs(:selinux_label_support?).returns true Selinux.stubs(:matchpathcon).with("/root/chuj", 0).returns(-1) self.stubs(:file_lstat).with("/root/chuj").raises(Errno::ENOENT, "/root/chuj") expect(get_selinux_default_context("/root/chuj")).to be_nil end it "should return nil if matchpathcon returns failure" do self.expects(:selinux_support?).returns true fstat = stub 'File::Stat', :mode => 0 Puppet::FileSystem.expects(:lstat).with('/foo').returns(fstat) self.expects(:find_fs).with("/foo").returns "ext3" Selinux.expects(:matchpathcon).with("/foo", 0).returns(-1) expect(get_selinux_default_context("/foo")).to be_nil end it "should return nil if selinux_label_support returns false" do self.expects(:selinux_support?).returns true self.expects(:find_fs).with("/foo").returns "nfs" expect(get_selinux_default_context("/foo")).to be_nil end end describe "parse_selinux_context" do it "should return nil if no context is passed" do expect(parse_selinux_context(:seluser, nil)).to be_nil end it "should return nil if the context is 'unlabeled'" do expect(parse_selinux_context(:seluser, "unlabeled")).to be_nil end it "should return the user type when called with :seluser" do expect(parse_selinux_context(:seluser, "user_u:role_r:type_t:s0")).to eq("user_u") expect(parse_selinux_context(:seluser, "user-withdash_u:role_r:type_t:s0")).to eq("user-withdash_u") end it "should return the role type when called with :selrole" do expect(parse_selinux_context(:selrole, "user_u:role_r:type_t:s0")).to eq("role_r") expect(parse_selinux_context(:selrole, "user_u:role-withdash_r:type_t:s0")).to eq("role-withdash_r") end it "should return the type type when called with :seltype" do expect(parse_selinux_context(:seltype, "user_u:role_r:type_t:s0")).to eq("type_t") expect(parse_selinux_context(:seltype, "user_u:role_r:type-withdash_t:s0")).to eq("type-withdash_t") end describe "with spaces in the components" do it "should raise when user contains a space" do expect{parse_selinux_context(:seluser, "user with space_u:role_r:type_t:s0")}.to raise_error Puppet::Error end it "should raise when role contains a space" do expect{parse_selinux_context(:selrole, "user_u:role with space_r:type_t:s0")}.to raise_error Puppet::Error end it "should raise when type contains a space" do expect{parse_selinux_context(:seltype, "user_u:role_r:type with space_t:s0")}.to raise_error Puppet::Error end it "should return the range when range contains a space" do expect(parse_selinux_context(:selrange, "user_u:role_r:type_t:s0 s1")).to eq("s0 s1") end end it "should return nil for :selrange when no range is returned" do expect(parse_selinux_context(:selrange, "user_u:role_r:type_t")).to be_nil end it "should return the range type when called with :selrange" do expect(parse_selinux_context(:selrange, "user_u:role_r:type_t:s0")).to eq("s0") expect(parse_selinux_context(:selrange, "user_u:role_r:type-withdash_t:s0")).to eq("s0") end describe "with a variety of SELinux range formats" do ['s0', 's0:c3', 's0:c3.c123', 's0:c3,c5,c8', 'TopSecret', 'TopSecret,Classified', 'Patient_Record'].each do |range| it "should parse range '#{range}'" do expect(parse_selinux_context(:selrange, "user_u:role_r:type_t:#{range}")).to eq(range) end end end end describe "set_selinux_context" do before :each do fh = stub 'fh', :close => nil File.stubs(:open).with("/proc/mounts").returns fh fh.stubs(:read_nonblock).returns( "rootfs / rootfs rw 0 0\n/dev/root / ext3 rw,relatime,errors=continue,user_xattr,acl,data=ordered 0 0\n"+ "/dev /dev tmpfs rw,relatime,mode=755 0 0\n/proc /proc proc rw,relatime 0 0\n"+ "/sys /sys sysfs rw,relatime 0 0\n" ).then.raises EOFError end it "should return nil if there is no SELinux support" do self.expects(:selinux_support?).returns false expect(set_selinux_context("/foo", "user_u:role_r:type_t:s0")).to be_nil end it "should return nil if selinux_label_support returns false" do self.expects(:selinux_support?).returns true self.expects(:selinux_label_support?).with("/foo").returns false expect(set_selinux_context("/foo", "user_u:role_r:type_t:s0")).to be_nil end it "should use lsetfilecon to set a context" do self.expects(:selinux_support?).returns true Selinux.expects(:lsetfilecon).with("/foo", "user_u:role_r:type_t:s0").returns 0 expect(set_selinux_context("/foo", "user_u:role_r:type_t:s0")).to be_truthy end it "should use lsetfilecon to set user_u user context" do self.expects(:selinux_support?).returns true Selinux.expects(:lgetfilecon).with("/foo").returns [0, "foo:role_r:type_t:s0"] Selinux.expects(:lsetfilecon).with("/foo", "user_u:role_r:type_t:s0").returns 0 expect(set_selinux_context("/foo", "user_u", :seluser)).to be_truthy end it "should use lsetfilecon to set role_r role context" do self.expects(:selinux_support?).returns true Selinux.expects(:lgetfilecon).with("/foo").returns [0, "user_u:foo:type_t:s0"] Selinux.expects(:lsetfilecon).with("/foo", "user_u:role_r:type_t:s0").returns 0 expect(set_selinux_context("/foo", "role_r", :selrole)).to be_truthy end it "should use lsetfilecon to set type_t type context" do self.expects(:selinux_support?).returns true Selinux.expects(:lgetfilecon).with("/foo").returns [0, "user_u:role_r:foo:s0"] Selinux.expects(:lsetfilecon).with("/foo", "user_u:role_r:type_t:s0").returns 0 expect(set_selinux_context("/foo", "type_t", :seltype)).to be_truthy end it "should use lsetfilecon to set s0:c3,c5 range context" do self.expects(:selinux_support?).returns true Selinux.expects(:lgetfilecon).with("/foo").returns [0, "user_u:role_r:type_t:s0"] Selinux.expects(:lsetfilecon).with("/foo", "user_u:role_r:type_t:s0:c3,c5").returns 0 expect(set_selinux_context("/foo", "s0:c3,c5", :selrange)).to be_truthy end end describe "set_selinux_default_context" do it "should return nil if there is no SELinux support" do self.expects(:selinux_support?).returns false expect(set_selinux_default_context("/foo")).to be_nil end it "should return nil if no default context exists" do self.expects(:get_selinux_default_context).with("/foo").returns nil expect(set_selinux_default_context("/foo")).to be_nil end it "should do nothing and return nil if the current context matches the default context" do self.expects(:get_selinux_default_context).with("/foo").returns "user_u:role_r:type_t" self.expects(:get_selinux_current_context).with("/foo").returns "user_u:role_r:type_t" expect(set_selinux_default_context("/foo")).to be_nil end it "should set and return the default context if current and default do not match" do self.expects(:get_selinux_default_context).with("/foo").returns "user_u:role_r:type_t" self.expects(:get_selinux_current_context).with("/foo").returns "olduser_u:role_r:type_t" self.expects(:set_selinux_context).with("/foo", "user_u:role_r:type_t").returns true expect(set_selinux_default_context("/foo")).to eq("user_u:role_r:type_t") end end end puppet-5.5.10/spec/unit/util/storage_spec.rb0000644005276200011600000002277213417161722020716 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'yaml' require 'fileutils' require 'puppet/util/storage' describe Puppet::Util::Storage do include PuppetSpec::Files before(:each) do @basepath = File.expand_path("/somepath") end describe "when caching a symbol" do it "should return an empty hash" do expect(Puppet::Util::Storage.cache(:yayness)).to eq({}) expect(Puppet::Util::Storage.cache(:more_yayness)).to eq({}) end it "should add the symbol to its internal state" do Puppet::Util::Storage.cache(:yayness) expect(Puppet::Util::Storage.state).to eq({:yayness=>{}}) end it "should not clobber existing state when caching additional objects" do Puppet::Util::Storage.cache(:yayness) expect(Puppet::Util::Storage.state).to eq({:yayness=>{}}) Puppet::Util::Storage.cache(:bubblyness) expect(Puppet::Util::Storage.state).to eq({:yayness=>{},:bubblyness=>{}}) end end describe "when caching a Puppet::Type" do before(:each) do @file_test = Puppet::Type.type(:file).new(:name => @basepath+"/yayness", :audit => %w{checksum type}) @exec_test = Puppet::Type.type(:exec).new(:name => @basepath+"/bin/ls /yayness") end it "should return an empty hash" do expect(Puppet::Util::Storage.cache(@file_test)).to eq({}) expect(Puppet::Util::Storage.cache(@exec_test)).to eq({}) end it "should add the resource ref to its internal state" do expect(Puppet::Util::Storage.state).to eq({}) Puppet::Util::Storage.cache(@file_test) expect(Puppet::Util::Storage.state).to eq({"File[#{@basepath}/yayness]"=>{}}) Puppet::Util::Storage.cache(@exec_test) expect(Puppet::Util::Storage.state).to eq({"File[#{@basepath}/yayness]"=>{}, "Exec[#{@basepath}/bin/ls /yayness]"=>{}}) end end describe "when caching something other than a resource or symbol" do it "should cache by converting to a string" do data = Puppet::Util::Storage.cache(42) data[:yay] = true expect(Puppet::Util::Storage.cache("42")[:yay]).to be_truthy end end it "should clear its internal state when clear() is called" do Puppet::Util::Storage.cache(:yayness) expect(Puppet::Util::Storage.state).to eq({:yayness=>{}}) Puppet::Util::Storage.clear expect(Puppet::Util::Storage.state).to eq({}) end describe "when loading from the state file" do before do Puppet.settings.stubs(:use).returns(true) end describe "when the state file/directory does not exist" do before(:each) do @path = tmpfile('storage_test') end it "should not fail to load" do expect(Puppet::FileSystem.exist?(@path)).to be_falsey Puppet[:statedir] = @path Puppet::Util::Storage.load Puppet[:statefile] = @path Puppet::Util::Storage.load end it "should not lose its internal state when load() is called" do expect(Puppet::FileSystem.exist?(@path)).to be_falsey Puppet::Util::Storage.cache(:yayness) expect(Puppet::Util::Storage.state).to eq({:yayness=>{}}) Puppet[:statefile] = @path Puppet::Util::Storage.load expect(Puppet::Util::Storage.state).to eq({:yayness=>{}}) end end describe "when the state file/directory exists" do before(:each) do @state_file = tmpfile('storage_test') FileUtils.touch(@state_file) Puppet[:statefile] = @state_file end def write_state_file(contents) File.open(@state_file, 'w') { |f| f.write(contents) } end it "should overwrite its internal state if load() is called" do # Should the state be overwritten even if Puppet[:statefile] is not valid YAML? Puppet::Util::Storage.cache(:yayness) expect(Puppet::Util::Storage.state).to eq({:yayness=>{}}) Puppet::Util::Storage.load expect(Puppet::Util::Storage.state).to eq({}) end it "should restore its internal state if the state file contains valid YAML" do test_yaml = {'File["/yayness"]'=>{"name"=>{:a=>:b,:c=>:d}}} write_state_file(test_yaml.to_yaml) Puppet::Util::Storage.load expect(Puppet::Util::Storage.state).to eq(test_yaml) end it "should initialize with a clear internal state if the state file does not contain valid YAML" do write_state_file('{ invalid') Puppet::Util::Storage.load expect(Puppet::Util::Storage.state).to eq({}) end it "should initialize with a clear internal state if the state file does not contain a hash of data" do write_state_file("not_a_hash") Puppet::Util::Storage.load expect(Puppet::Util::Storage.state).to eq({}) end it "should raise an error if the state file does not contain valid YAML and cannot be renamed" do write_state_file('{ invalid') File.expects(:rename).raises(SystemCallError) expect { Puppet::Util::Storage.load }.to raise_error(Puppet::Error, /Could not rename/) end it "should attempt to rename the state file if the file is corrupted" do write_state_file('{ invalid') File.expects(:rename).at_least_once Puppet::Util::Storage.load end it "should fail gracefully on load() if the state file is not a regular file" do FileUtils.rm_f(@state_file) Dir.mkdir(@state_file) Puppet::Util::Storage.load end end end describe "when storing to the state file" do before(:each) do @state_file = tmpfile('storage_test') @saved_statefile = Puppet[:statefile] Puppet[:statefile] = @state_file end it "should create the state file if it does not exist" do expect(Puppet::FileSystem.exist?(Puppet[:statefile])).to be_falsey Puppet::Util::Storage.cache(:yayness) Puppet::Util::Storage.store expect(Puppet::FileSystem.exist?(Puppet[:statefile])).to be_truthy end it "should raise an exception if the state file is not a regular file" do Dir.mkdir(Puppet[:statefile]) Puppet::Util::Storage.cache(:yayness) if Puppet.features.microsoft_windows? expect { Puppet::Util::Storage.store }.to raise_error do |error| expect(error).to be_a(Puppet::Util::Windows::Error) expect(error.code).to eq(5) # ERROR_ACCESS_DENIED end else expect { Puppet::Util::Storage.store }.to raise_error(Errno::EISDIR, /Is a directory/) end Dir.rmdir(Puppet[:statefile]) end it "should load() the same information that it store()s" do Puppet::Util::Storage.cache(:yayness) expect(Puppet::Util::Storage.state).to eq({:yayness=>{}}) Puppet::Util::Storage.store Puppet::Util::Storage.clear expect(Puppet::Util::Storage.state).to eq({}) Puppet::Util::Storage.load expect(Puppet::Util::Storage.state).to eq({:yayness=>{}}) end it "expires entries with a :checked older than statettl seconds ago" do Puppet[:statettl] = '1d' recent_checked = Time.now stale_checked = Time.now - (Puppet[:statettl] + 1) Puppet::Util::Storage.cache(:yayness)[:checked] = recent_checked Puppet::Util::Storage.cache(:stale)[:checked] = stale_checked expect(Puppet::Util::Storage.state).to eq( { :yayness => { :checked => recent_checked }, :stale => { :checked => stale_checked } } ) Puppet::Util::Storage.store Puppet::Util::Storage.clear expect(Puppet::Util::Storage.state).to eq({}) Puppet::Util::Storage.load expect(Puppet::Util::Storage.state).to eq( { :yayness => { :checked => recent_checked } } ) end it "does not expire entries when statettl is 0" do Puppet[:statettl] = '0' recent_checked = Time.now older_checked = Time.now - 10_000_000 Puppet::Util::Storage.cache(:yayness)[:checked] = recent_checked Puppet::Util::Storage.cache(:older)[:checked] = older_checked expect(Puppet::Util::Storage.state).to eq( { :yayness => { :checked => recent_checked }, :older => { :checked => older_checked } } ) Puppet::Util::Storage.store Puppet::Util::Storage.clear expect(Puppet::Util::Storage.state).to eq({}) Puppet::Util::Storage.load expect(Puppet::Util::Storage.state).to eq( { :yayness => { :checked => recent_checked }, :older => { :checked => older_checked } } ) end it "does not expire entries when statettl is 'unlimited'" do Puppet[:statettl] = 'unlimited' recent_checked = Time.now older_checked = Time.now - 10_000_000 Puppet::Util::Storage.cache(:yayness)[:checked] = recent_checked Puppet::Util::Storage.cache(:older)[:checked] = older_checked expect(Puppet::Util::Storage.state).to eq( { :yayness => { :checked => recent_checked }, :older => { :checked => older_checked } } ) Puppet::Util::Storage.store Puppet::Util::Storage.clear expect(Puppet::Util::Storage.state).to eq({}) Puppet::Util::Storage.load expect(Puppet::Util::Storage.state).to eq( { :yayness => { :checked => recent_checked }, :older => { :checked => older_checked } } ) end end end puppet-5.5.10/spec/unit/util/suidmanager_spec.rb0000644005276200011600000002125713417161722021546 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Util::SUIDManager do let :user do Puppet::Type.type(:user).new(:name => 'name', :uid => 42, :gid => 42) end let :xids do Hash.new {|h,k| 0} end before :each do Puppet::Util::SUIDManager.stubs(:convert_xid).returns(42) pwent = stub('pwent', :name => 'fred', :uid => 42, :gid => 42) Etc.stubs(:getpwuid).with(42).returns(pwent) [:euid, :egid, :uid, :gid, :groups].each do |id| Process.stubs("#{id}=").with {|value| xids[id] = value } end end describe "#initgroups" do it "should use the primary group of the user as the 'basegid'" do Process.expects(:initgroups).with('fred', 42) described_class.initgroups(42) end end describe "#uid" do it "should allow setting euid/egid" do Puppet::Util::SUIDManager.egid = user[:gid] Puppet::Util::SUIDManager.euid = user[:uid] expect(xids[:egid]).to eq(user[:gid]) expect(xids[:euid]).to eq(user[:uid]) end end describe "#asuser" do it "should not get or set euid/egid when not root" do Puppet.features.stubs(:microsoft_windows?).returns(false) Process.stubs(:uid).returns(1) Process.stubs(:egid).returns(51) Process.stubs(:euid).returns(50) Puppet::Util::SUIDManager.asuser(user[:uid], user[:gid]) {} expect(xids).to be_empty end context "when root and not windows" do before :each do Process.stubs(:uid).returns(0) Puppet.features.stubs(:microsoft_windows?).returns(false) end it "should set euid/egid" do Process.stubs(:egid).returns(51).then.returns(51).then.returns(user[:gid]) Process.stubs(:euid).returns(50).then.returns(50).then.returns(user[:uid]) Puppet::Util::SUIDManager.stubs(:convert_xid).with(:gid, 51).returns(51) Puppet::Util::SUIDManager.stubs(:convert_xid).with(:uid, 50).returns(50) Puppet::Util::SUIDManager.stubs(:initgroups).returns([]) yielded = false Puppet::Util::SUIDManager.asuser(user[:uid], user[:gid]) do expect(xids[:egid]).to eq(user[:gid]) expect(xids[:euid]).to eq(user[:uid]) yielded = true end expect(xids[:egid]).to eq(51) expect(xids[:euid]).to eq(50) # It's possible asuser could simply not yield, so the assertions in the # block wouldn't fail. So verify those actually got checked. expect(yielded).to be_truthy end it "should just yield if user and group are nil" do yielded = false Puppet::Util::SUIDManager.asuser(nil, nil) { yielded = true } expect(yielded).to be_truthy expect(xids).to eq({}) end it "should just change group if only group is given" do yielded = false Puppet::Util::SUIDManager.asuser(nil, 42) { yielded = true } expect(yielded).to be_truthy expect(xids).to eq({ :egid => 42 }) end it "should change gid to the primary group of uid by default" do Process.stubs(:initgroups) yielded = false Puppet::Util::SUIDManager.asuser(42) { yielded = true } expect(yielded).to be_truthy expect(xids).to eq({ :euid => 42, :egid => 42 }) end it "should change both uid and gid if given" do # I don't like the sequence, but it is the only way to assert on the # internal behaviour in a reliable fashion, given we need multiple # sequenced calls to the same methods. --daniel 2012-02-05 horror = sequence('of user and group changes') Puppet::Util::SUIDManager.expects(:change_group).with(43, false).in_sequence(horror) Puppet::Util::SUIDManager.expects(:change_user).with(42, false).in_sequence(horror) Puppet::Util::SUIDManager.expects(:change_group). with(Puppet::Util::SUIDManager.egid, false).in_sequence(horror) Puppet::Util::SUIDManager.expects(:change_user). with(Puppet::Util::SUIDManager.euid, false).in_sequence(horror) yielded = false Puppet::Util::SUIDManager.asuser(42, 43) { yielded = true } expect(yielded).to be_truthy end end it "should not get or set euid/egid on Windows", if: Puppet::Util::Platform.windows? do Puppet::Util::SUIDManager.asuser(user[:uid], user[:gid]) {} expect(xids).to be_empty end end describe "#change_group" do describe "when changing permanently" do it "should change_privilege" do Process::GID.expects(:change_privilege).with do |gid| Process.gid = gid Process.egid = gid end Puppet::Util::SUIDManager.change_group(42, true) expect(xids[:egid]).to eq(42) expect(xids[:gid]).to eq(42) end it "should not change_privilege when gid already matches" do Process::GID.expects(:change_privilege).with do |gid| Process.gid = 42 Process.egid = 42 end Puppet::Util::SUIDManager.change_group(42, true) expect(xids[:egid]).to eq(42) expect(xids[:gid]).to eq(42) end end describe "when changing temporarily" do it "should change only egid" do Puppet::Util::SUIDManager.change_group(42, false) expect(xids[:egid]).to eq(42) expect(xids[:gid]).to eq(0) end end end describe "#change_user" do describe "when changing permanently" do it "should change_privilege" do Process::UID.expects(:change_privilege).with do |uid| Process.uid = uid Process.euid = uid end Puppet::Util::SUIDManager.expects(:initgroups).with(42) Puppet::Util::SUIDManager.change_user(42, true) expect(xids[:euid]).to eq(42) expect(xids[:uid]).to eq(42) end it "should not change_privilege when uid already matches" do Process::UID.expects(:change_privilege).with do |uid| Process.uid = 42 Process.euid = 42 end Puppet::Util::SUIDManager.expects(:initgroups).with(42) Puppet::Util::SUIDManager.change_user(42, true) expect(xids[:euid]).to eq(42) expect(xids[:uid]).to eq(42) end end describe "when changing temporarily" do it "should change only euid and groups" do Puppet::Util::SUIDManager.stubs(:initgroups).returns([]) Puppet::Util::SUIDManager.change_user(42, false) expect(xids[:euid]).to eq(42) expect(xids[:uid]).to eq(0) end it "should set euid before groups if changing to root" do Process.stubs(:euid).returns 50 when_not_root = sequence 'when_not_root' Process.expects(:euid=).in_sequence(when_not_root) Puppet::Util::SUIDManager.expects(:initgroups).in_sequence(when_not_root) Puppet::Util::SUIDManager.change_user(0, false) end it "should set groups before euid if changing from root" do Process.stubs(:euid).returns 0 when_root = sequence 'when_root' Puppet::Util::SUIDManager.expects(:initgroups).in_sequence(when_root) Process.expects(:euid=).in_sequence(when_root) Puppet::Util::SUIDManager.change_user(50, false) end end end describe "#root?" do describe "on POSIX systems" do before :each do Puppet.features.stubs(:posix?).returns(true) Puppet.features.stubs(:microsoft_windows?).returns(false) end it "should be root if uid is 0" do Process.stubs(:uid).returns(0) expect(Puppet::Util::SUIDManager).to be_root end it "should not be root if uid is not 0" do Process.stubs(:uid).returns(1) expect(Puppet::Util::SUIDManager).not_to be_root end end describe "on Microsoft Windows", :if => Puppet.features.microsoft_windows? do it "should be root if user is privileged" do Puppet::Util::Windows::User.stubs(:admin?).returns true expect(Puppet::Util::SUIDManager).to be_root end it "should not be root if user is not privileged" do Puppet::Util::Windows::User.stubs(:admin?).returns false expect(Puppet::Util::SUIDManager).not_to be_root end end end end describe 'Puppet::Util::SUIDManager#groups=' do subject do Puppet::Util::SUIDManager end it "(#3419) should rescue Errno::EINVAL on OS X" do Process.expects(:groups=).raises(Errno::EINVAL, 'blew up') subject.expects(:osx_maj_ver).returns('10.7').twice subject.groups = ['list', 'of', 'groups'] end it "(#3419) should fail if an Errno::EINVAL is raised NOT on OS X" do Process.expects(:groups=).raises(Errno::EINVAL, 'blew up') subject.expects(:osx_maj_ver).returns(false) expect { subject.groups = ['list', 'of', 'groups'] }.to raise_error(Errno::EINVAL) end end puppet-5.5.10/spec/unit/util/tag_set_spec.rb0000644005276200011600000000207413417161722020671 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/tag_set' RSpec::Matchers.define :be_one_of do |*expected| match do |actual| expected.include? actual end failure_message do |actual| "expected #{actual.inspect} to be one of #{expected.map(&:inspect).join(' or ')}" end end describe Puppet::Util::TagSet do let(:set) { Puppet::Util::TagSet.new } it 'serializes to yaml as an array' do array = ['a', :b, 1, 5.4] set.merge(array) expect(Set.new(YAML.load(set.to_yaml))).to eq(Set.new(array)) end it 'deserializes from a yaml array' do array = ['a', :b, 1, 5.4] expect(Puppet::Util::TagSet.from_yaml(array.to_yaml)).to eq(Puppet::Util::TagSet.new(array)) end it 'round trips through json' do array = ['a', 'b', 1, 5.4] set.merge(array) tes = Puppet::Util::TagSet.from_data_hash(JSON.parse(set.to_json)) expect(tes).to eq(set) end it 'can join its elements with a string separator' do array = ['a', 'b'] set.merge(array) expect(set.join(', ')).to be_one_of('a, b', 'b, a') end end puppet-5.5.10/spec/unit/util/tagging_spec.rb0000644005276200011600000001456013417161722020666 0ustar jenkinsjenkins#! /usr/bin/env ruby # coding: utf-8 require 'spec_helper' require 'puppet/util/tagging' describe Puppet::Util::Tagging do let(:tagger) { Object.new.extend(Puppet::Util::Tagging) } it "should add tags to the returned tag list" do tagger.tag("one") expect(tagger.tags).to include("one") end it "should add all provided tags to the tag list" do tagger.tag("one", "two") expect(tagger.tags).to include("one") expect(tagger.tags).to include("two") end it "should fail on tags containing '*' characters" do expect { tagger.tag("bad*tag") }.to raise_error(Puppet::ParseError) end it "should fail on tags starting with '-' characters" do expect { tagger.tag("-badtag") }.to raise_error(Puppet::ParseError) end it "should fail on tags containing ' ' characters" do expect { tagger.tag("bad tag") }.to raise_error(Puppet::ParseError) end it "should fail on tags containing newline characters" do expect { tagger.tag("bad\ntag") }.to raise_error(Puppet::ParseError) end it "should allow alpha tags" do expect { tagger.tag("good_tag") }.not_to raise_error end it "should allow tags containing '.' characters" do expect { tagger.tag("good.tag") }.to_not raise_error end # different UTF-8 widths # 1-byte A # 2-byte Űż - http://www.fileformat.info/info/unicode/char/06ff/index.htm - 0xDB 0xBF / 219 191 # 3-byte áš  - http://www.fileformat.info/info/unicode/char/16A0/index.htm - 0xE1 0x9A 0xA0 / 225 154 160 # 4-byte ÜŽ - http://www.fileformat.info/info/unicode/char/2070E/index.htm - 0xF0 0xA0 0x9C 0x8E / 240 160 156 142 let (:mixed_utf8) { "A\u06FF\u16A0\u{2070E}" } # AŰżáš ÜŽ it "should allow UTF-8 alphanumeric characters" do expect { tagger.tag(mixed_utf8) }.not_to raise_error end # completely non-exhaustive list of a few UTF-8 punctuation characters # http://www.fileformat.info/info/unicode/block/general_punctuation/utf8test.htm [ "\u2020", # dagger † "\u203B", # reference mark ※ "\u204F", # reverse semicolon ⏠"!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "-", "+", "=", "{", "}", "[", "]", "|", "\\", "/", "?", "<", ">", ",", ".", "~", ",", ":", ";", "\"", "'", ].each do |char| it "should not allow UTF-8 punctuation characters, e.g. #{char}" do expect { tagger.tag(char) }.to raise_error(Puppet::ParseError) end end it "should allow encodings that can be coerced to UTF-8" do chinese = "標č¨ä˝ ćŻĺ®".force_encoding(Encoding::UTF_8) ascii = "tags--".force_encoding(Encoding::ASCII_8BIT) jose = "jos\xE9".force_encoding(Encoding::ISO_8859_1) [chinese, ascii, jose].each do |tag| expect(tagger.valid_tag?(tag)).to be_truthy end end it "should not allow strings that cannot be converted to UTF-8" do invalid = "\xA0".force_encoding(Encoding::ASCII_8BIT) expect(tagger.valid_tag?(invalid)).to be_falsey end it "should add qualified classes as tags" do tagger.tag("one::two") expect(tagger.tags).to include("one::two") end it "should add each part of qualified classes as tags" do tagger.tag("one::two::three") expect(tagger.tags).to include('one') expect(tagger.tags).to include("two") expect(tagger.tags).to include("three") end it "should indicate when the object is tagged with a provided tag" do tagger.tag("one") expect(tagger).to be_tagged("one") end it "should indicate when the object is not tagged with a provided tag" do expect(tagger).to_not be_tagged("one") end it "should indicate when the object is tagged with any tag in an array" do tagger.tag("one") expect(tagger).to be_tagged("one","two","three") end it "should indicate when the object is not tagged with any tag in an array" do tagger.tag("one") expect(tagger).to_not be_tagged("two","three") end context "when tagging" do it "converts symbols to strings" do tagger.tag(:hello) expect(tagger.tags).to include('hello') end it "downcases tags" do tagger.tag(:HEllO) tagger.tag("GooDByE") expect(tagger).to be_tagged("hello") expect(tagger).to be_tagged("goodbye") end it "downcases tag arguments" do tagger.tag("hello") tagger.tag("goodbye") expect(tagger).to be_tagged(:HEllO) expect(tagger).to be_tagged("GooDByE") end it "accepts hyphenated tags" do tagger.tag("my-tag") expect(tagger).to be_tagged("my-tag") end end context "when querying if tagged" do it "responds true if queried on the entire set" do tagger.tag("one", "two") expect(tagger).to be_tagged("one", "two") end it "responds true if queried on a subset" do tagger.tag("one", "two", "three") expect(tagger).to be_tagged("two", "one") end it "responds true if queried on an overlapping but not fully contained set" do tagger.tag("one", "two") expect(tagger).to be_tagged("zero", "one") end it "responds false if queried on a disjoint set" do tagger.tag("one", "two", "three") expect(tagger).to_not be_tagged("five") end it "responds false if queried on the empty set" do expect(tagger).to_not be_tagged end end context "when assigning tags" do it "splits a string on ','" do tagger.tags = "one, two, three" expect(tagger).to be_tagged("one") expect(tagger).to be_tagged("two") expect(tagger).to be_tagged("three") end it "protects against empty tags" do expect { tagger.tags = "one,,two"}.to raise_error(/Invalid tag ''/) end it "takes an array of tags" do tagger.tags = ["one", "two"] expect(tagger).to be_tagged("one") expect(tagger).to be_tagged("two") end it "removes any existing tags when reassigning" do tagger.tags = "one, two" tagger.tags = "three, four" expect(tagger).to_not be_tagged("one") expect(tagger).to_not be_tagged("two") expect(tagger).to be_tagged("three") expect(tagger).to be_tagged("four") end it "allows empty tags that are generated from :: separated tags" do tagger.tags = "one::::two::three" expect(tagger).to be_tagged("one") expect(tagger).to be_tagged("") expect(tagger).to be_tagged("two") expect(tagger).to be_tagged("three") end end end puppet-5.5.10/spec/unit/util/yaml_spec.rb0000644005276200011600000000410313417161722020200 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/util/yaml' describe Puppet::Util::Yaml do include PuppetSpec::Files let(:filename) { tmpfile("yaml") } it "reads a YAML file from disk" do write_file(filename, YAML.dump({ "my" => "data" })) expect(Puppet::Util::Yaml.load_file(filename)).to eq({ "my" => "data" }) end it "writes data formatted as YAML to disk" do Puppet::Util::Yaml.dump({ "my" => "data" }, filename) expect(Puppet::Util::Yaml.load_file(filename)).to eq({ "my" => "data" }) end it "raises an error when the file is invalid YAML" do write_file(filename, "{ invalid") expect { Puppet::Util::Yaml.load_file(filename) }.to raise_error(Puppet::Util::Yaml::YamlLoadError) end it "raises an error when the file does not exist" do expect { Puppet::Util::Yaml.load_file("no") }.to raise_error(Puppet::Util::Yaml::YamlLoadError, /No such file or directory/) end it "raises an error when the filename is illegal" do expect { Puppet::Util::Yaml.load_file("not\0allowed") }.to raise_error(Puppet::Util::Yaml::YamlLoadError, /null byte/) end context "when the file is empty" do it "returns false" do Puppet::FileSystem.touch(filename) expect(Puppet::Util::Yaml.load_file(filename)).to be_falsey end it "allows return value to be overridden" do Puppet::FileSystem.touch(filename) expect(Puppet::Util::Yaml.load_file(filename, {})).to eq({}) end end it "should allow one to strip ruby tags that would otherwise not parse" do write_file(filename, "---\nweirddata: !ruby/hash:Not::A::Valid::Class {}") expect(Puppet::Util::Yaml.load_file(filename, {}, true)).to eq({"weirddata" => {}}) end it "should not strip non-ruby tags" do write_file(filename, "---\nweirddata: !binary |-\n e21kNX04MTE4ZGY2NmM5MTc3OTg4ZWE4Y2JiOWEzMjMyNzFkYg==") expect(Puppet::Util::Yaml.load_file(filename, {}, true)).to eq({"weirddata" => "{md5}8118df66c9177988ea8cbb9a323271db"}) end def write_file(name, contents) File.open(name, "w:UTF-8") do |fh| fh.write(contents) end end end puppet-5.5.10/spec/unit/version_spec.rb0000644005276200011600000000302713417161721017751 0ustar jenkinsjenkinsrequire "spec_helper" require "puppet/version" require 'pathname' describe "Puppet.version Public API" do before :each do @current_ver = Puppet.version Puppet.instance_eval do if @puppet_version @puppet_version = nil end end end after :each do Puppet.version = @current_ver end context "without a VERSION file" do before :each do Puppet.stubs(:read_version_file).returns(nil) end it "is Puppet::PUPPETVERSION" do expect(Puppet.version).to eq(Puppet::PUPPETVERSION) end it "respects the version= setter" do Puppet.version = '1.2.3' expect(Puppet.version).to eq('1.2.3') expect(Puppet.minor_version).to eq('1.2') end end context "with a VERSION file" do it "is the content of the file" do Puppet.expects(:read_version_file).with() do |path| pathname = Pathname.new(path) pathname.basename.to_s == "VERSION" end.returns('3.0.1-260-g9ca4e54') expect(Puppet.version).to eq('3.0.1-260-g9ca4e54') expect(Puppet.minor_version).to eq('3.0') end it "respects the version= setter" do Puppet.version = '1.2.3' expect(Puppet.version).to eq('1.2.3') expect(Puppet.minor_version).to eq('1.2') end end context "Using version setter" do it "does not read VERSION file if using set version" do Puppet.expects(:read_version_file).never Puppet.version = '1.2.3' expect(Puppet.version).to eq('1.2.3') expect(Puppet.minor_version).to eq('1.2') end end end puppet-5.5.10/spec/unit/agent_spec.rb0000644005276200011600000002441713417161722017371 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/agent' require 'puppet/configurer' class AgentTestClient def run # no-op end def stop # no-op end end def without_warnings flag = $VERBOSE $VERBOSE = nil yield $VERBOSE = flag end describe Puppet::Agent do before do Puppet::Status.indirection.stubs(:find).returns Puppet::Status.new("version" => Puppet.version) @agent = Puppet::Agent.new(AgentTestClient, false) # So we don't actually try to hit the filesystem. @agent.stubs(:lock).yields # make Puppet::Application safe for stubbing; restore in an :after block; silence warnings for this. without_warnings { Puppet::Application = Class.new(Puppet::Application) } Puppet::Application.stubs(:clear?).returns(true) Puppet::Application.class_eval do class << self def controlled_run(&block) block.call end end end end after do # restore Puppet::Application from stub-safe subclass, and silence warnings without_warnings { Puppet::Application = Puppet::Application.superclass } end it "should set its client class at initialization" do expect(Puppet::Agent.new("foo", false).client_class).to eq("foo") end it "should include the Locker module" do expect(Puppet::Agent.ancestors).to be_include(Puppet::Agent::Locker) end it "should create an instance of its client class and run it when asked to run" do client = mock 'client' AgentTestClient.expects(:new).returns client client.expects(:run) @agent.stubs(:disabled?).returns false @agent.run end it "should initialize the client's transaction_uuid if passed as a client_option" do client = mock 'client' transaction_uuid = 'foo' AgentTestClient.expects(:new).with(transaction_uuid, nil).returns client client.expects(:run) @agent.stubs(:disabled?).returns false @agent.run(:transaction_uuid => transaction_uuid) end it "should initialize the client's job_id if passed as a client_option" do client = mock 'client' job_id = '289' AgentTestClient.expects(:new).with(anything, job_id).returns client client.expects(:run) @agent.stubs(:disabled?).returns false @agent.run(:job_id => job_id) end it "should be considered running if the lock file is locked" do lockfile = mock 'lockfile' @agent.expects(:lockfile).returns(lockfile) lockfile.expects(:locked?).returns true expect(@agent).to be_running end describe "when being run" do before do AgentTestClient.stubs(:lockfile_path).returns "/my/lock" @agent.stubs(:disabled?).returns false end it "should splay" do @agent.expects(:splay) @agent.run end it "should do nothing if disabled" do @agent.expects(:disabled?).returns(true) AgentTestClient.expects(:new).never @agent.run end it "(#11057) should notify the user about why a run is skipped" do Puppet::Application.stubs(:controlled_run).returns(false) Puppet::Application.stubs(:run_status).returns('MOCK_RUN_STATUS') # This is the actual test that we inform the user why the run is skipped. # We assume this information is contained in # Puppet::Application.run_status Puppet.expects(:notice).with(regexp_matches(/MOCK_RUN_STATUS/)) @agent.run end it "should display an informative message if the agent is administratively disabled" do @agent.expects(:disabled?).returns true @agent.expects(:disable_message).returns "foo" Puppet.expects(:notice).with(regexp_matches(/Skipping run of .*; administratively disabled.*\(Reason: 'foo'\)/)) @agent.run end it "should use Puppet::Application.controlled_run to manage process state behavior" do calls = sequence('calls') Puppet::Application.expects(:controlled_run).yields.in_sequence(calls) AgentTestClient.expects(:new).once.in_sequence(calls) @agent.run end it "should not fail if a client class instance cannot be created" do AgentTestClient.expects(:new).raises "eh" Puppet.expects(:err) @agent.run end it "should not fail if there is an exception while running its client" do client = AgentTestClient.new AgentTestClient.expects(:new).returns client client.expects(:run).raises "eh" Puppet.expects(:err) @agent.run end it "should use a filesystem lock to restrict multiple processes running the agent" do client = AgentTestClient.new AgentTestClient.expects(:new).returns client @agent.expects(:lock) client.expects(:run).never # if it doesn't run, then we know our yield is what triggers it @agent.run end it "should make its client instance available while running" do client = AgentTestClient.new AgentTestClient.expects(:new).returns client client.expects(:run).with { expect(@agent.client).to equal(client); true } @agent.run end it "should run the client instance with any arguments passed to it" do client = AgentTestClient.new AgentTestClient.expects(:new).returns client client.expects(:run).with(:pluginsync => true, :other => :options) @agent.run(:other => :options) end it "should return the agent result" do client = AgentTestClient.new AgentTestClient.expects(:new).returns client @agent.expects(:lock).returns(:result) expect(@agent.run).to eq(:result) end describe "when should_fork is true", :if => Puppet.features.posix? do before do @agent = Puppet::Agent.new(AgentTestClient, true) # So we don't actually try to hit the filesystem. @agent.stubs(:lock).yields Kernel.stubs(:fork) Process.stubs(:waitpid2).returns [123, (stub 'process::status', :exitstatus => 0)] @agent.stubs(:exit) end it "should run the agent in a forked process" do client = AgentTestClient.new AgentTestClient.expects(:new).returns client client.expects(:run) Kernel.expects(:fork).yields @agent.run end it "should exit child process if child exit" do client = AgentTestClient.new AgentTestClient.expects(:new).returns client client.expects(:run).raises(SystemExit) Kernel.expects(:fork).yields @agent.expects(:exit).with(-1) @agent.run end it 'should exit with 1 if an exception is raised' do client = AgentTestClient.new AgentTestClient.expects(:new).returns client client.expects(:run).raises(StandardError) Kernel.expects(:fork).yields @agent.expects(:exit).with(1) @agent.run end it "should re-raise exit happening in the child" do Process.stubs(:waitpid2).returns [123, (stub 'process::status', :exitstatus => -1)] expect { @agent.run }.to raise_error(SystemExit) end it "should re-raise NoMoreMemory happening in the child" do Process.stubs(:waitpid2).returns [123, (stub 'process::status', :exitstatus => -2)] expect { @agent.run }.to raise_error(NoMemoryError) end it "should return the child exit code" do Process.stubs(:waitpid2).returns [123, (stub 'process::status', :exitstatus => 777)] expect(@agent.run).to eq(777) end it "should return the block exit code as the child exit code" do Kernel.expects(:fork).yields @agent.expects(:exit).with(777) @agent.run_in_fork { 777 } end end describe "on Windows", :if => Puppet.features.microsoft_windows? do it "should never fork" do agent = Puppet::Agent.new(AgentTestClient, true) expect(agent.should_fork).to be_falsey end end describe 'when runtimeout is set' do before(:each) do Puppet[:runtimeout] = 1 end it 'times out when a run exceeds the set limit' do client = AgentTestClient.new client.instance_eval do # Stub methods used to set test expectations. def processing; end def handling; end def run(client_options = {}) # Simulate a hanging agent operation that also traps errors. begin ::Kernel.sleep(5) processing() rescue handling() end end end AgentTestClient.expects(:new).returns client client.expects(:processing).never client.expects(:handling).never Puppet.expects(:log_exception).with(instance_of(Puppet::Agent::RunTimeoutError), anything) expect(@agent.run).to eq(1) end end end describe "when checking execution state" do describe 'with regular run status' do before :each do Puppet::Application.stubs(:restart_requested?).returns(false) Puppet::Application.stubs(:stop_requested?).returns(false) Puppet::Application.stubs(:interrupted?).returns(false) Puppet::Application.stubs(:clear?).returns(true) end it 'should be false for :stopping?' do expect(@agent.stopping?).to be_falsey end it 'should be false for :needing_restart?' do expect(@agent.needing_restart?).to be_falsey end end describe 'with a stop requested' do before :each do Puppet::Application.stubs(:clear?).returns(false) Puppet::Application.stubs(:restart_requested?).returns(false) Puppet::Application.stubs(:stop_requested?).returns(true) Puppet::Application.stubs(:interrupted?).returns(true) end it 'should be true for :stopping?' do expect(@agent.stopping?).to be_truthy end it 'should be false for :needing_restart?' do expect(@agent.needing_restart?).to be_falsey end end describe 'with a restart requested' do before :each do Puppet::Application.stubs(:clear?).returns(false) Puppet::Application.stubs(:restart_requested?).returns(true) Puppet::Application.stubs(:stop_requested?).returns(false) Puppet::Application.stubs(:interrupted?).returns(true) end it 'should be false for :stopping?' do expect(@agent.stopping?).to be_falsey end it 'should be true for :needing_restart?' do expect(@agent.needing_restart?).to be_truthy end end end end puppet-5.5.10/spec/unit/application_spec.rb0000644005276200011600000004511613417161722020575 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/application' require 'puppet' require 'getoptlong' require 'timeout' describe Puppet::Application do before(:each) do @app = Class.new(Puppet::Application).new @appclass = @app.class @app.stubs(:name).returns("test_app") end describe "application commandline" do it "should not pick up changes to the array of arguments" do args = %w{subcommand --arg} command_line = Puppet::Util::CommandLine.new('puppet', args) app = Puppet::Application.new(command_line) args[0] = 'different_subcommand' args[1] = '--other-arg' expect(app.command_line.subcommand_name).to eq('subcommand') expect(app.command_line.args).to eq(['--arg']) end end describe "application defaults" do it "should fail if required app default values are missing" do @app.stubs(:app_defaults).returns({ :foo => 'bar' }) Puppet.expects(:err).with(regexp_matches(/missing required app default setting/)) expect { @app.run }.to exit_with(1) end end describe "finding" do before do @klass = Puppet::Application @klass.stubs(:puts) end it "should find classes in the namespace" do expect(@klass.find("Agent")).to eq(@klass::Agent) end it "should not find classes outside the namespace" do expect { @klass.find("String") }.to raise_error(LoadError) end it "should error if it can't find a class" do Puppet.expects(:err).with do |value| value =~ /Unable to find application 'ThisShallNeverEverEverExist'/ and value =~ /puppet\/application\/thisshallneverevereverexist/ and value =~ /no such file to load|cannot load such file/ end expect { @klass.find("ThisShallNeverEverEverExist") }.to raise_error(LoadError) end end describe "#available_application_names" do it 'should be able to find available application names' do apps = %w{describe filebucket kick queue resource agent cert apply doc master} Puppet::Util::Autoload.expects(:files_to_load).returns(apps) expect(Puppet::Application.available_application_names).to match_array(apps) end it 'should find applications from multiple paths' do Puppet::Util::Autoload.expects(:files_to_load).with('puppet/application').returns(%w{ /a/foo.rb /b/bar.rb }) expect(Puppet::Application.available_application_names).to match_array(%w{ foo bar }) end it 'should return unique application names' do Puppet::Util::Autoload.expects(:files_to_load).with('puppet/application').returns(%w{ /a/foo.rb /b/foo.rb }) expect(Puppet::Application.available_application_names).to eq(%w{ foo }) end end describe ".run_mode" do it "should default to user" do expect(@appclass.run_mode.name).to eq(:user) end it "should set and get a value" do @appclass.run_mode :agent expect(@appclass.run_mode.name).to eq(:agent) end end describe ".environment_mode" do it "should default to :local" do expect(@appclass.get_environment_mode).to eq(:local) end it "should set and get a value" do @appclass.environment_mode :remote expect(@appclass.get_environment_mode).to eq(:remote) end it "should error if given a random symbol" do expect{@appclass.environment_mode :foo}.to raise_error(/Invalid environment mode/) end it "should error if given a string" do expect{@appclass.environment_mode 'local'}.to raise_error(/Invalid environment mode/) end end # These tests may look a little weird and repetative in its current state; # it used to illustrate several ways that the run_mode could be changed # at run time; there are fewer ways now, but it would still be nice to # get to a point where it was entirely impossible. describe "when dealing with run_mode" do class TestApp < Puppet::Application run_mode :master def run_command # no-op end end it "should sadly and frighteningly allow run_mode to change at runtime via #initialize_app_defaults" do Puppet.features.stubs(:syslog?).returns(true) app = TestApp.new app.initialize_app_defaults expect(Puppet.run_mode).to be_master end it "should sadly and frighteningly allow run_mode to change at runtime via #run" do app = TestApp.new app.run expect(app.class.run_mode.name).to eq(:master) expect(Puppet.run_mode).to be_master end end it "should explode when an invalid run mode is set at runtime, for great victory" do expect { class InvalidRunModeTestApp < Puppet::Application run_mode :abracadabra def run_command # no-op end end }.to raise_error(Puppet::Settings::ValidationError, /Invalid run mode/) end it "should have a run entry-point" do expect(@app).to respond_to(:run) end it "should have a read accessor to options" do expect(@app).to respond_to(:options) end it "should include a default setup method" do expect(@app).to respond_to(:setup) end it "should include a default preinit method" do expect(@app).to respond_to(:preinit) end it "should include a default run_command method" do expect(@app).to respond_to(:run_command) end it "should invoke main as the default" do @app.expects( :main ) @app.run_command end describe 'when invoking clear!' do before :each do Puppet::Application.run_status = :stop_requested Puppet::Application.clear! end it 'should have nil run_status' do expect(Puppet::Application.run_status).to be_nil end it 'should return false for restart_requested?' do expect(Puppet::Application.restart_requested?).to be_falsey end it 'should return false for stop_requested?' do expect(Puppet::Application.stop_requested?).to be_falsey end it 'should return false for interrupted?' do expect(Puppet::Application.interrupted?).to be_falsey end it 'should return true for clear?' do expect(Puppet::Application.clear?).to be_truthy end end describe 'after invoking stop!' do before :each do Puppet::Application.run_status = nil Puppet::Application.stop! end after :each do Puppet::Application.run_status = nil end it 'should have run_status of :stop_requested' do expect(Puppet::Application.run_status).to eq(:stop_requested) end it 'should return true for stop_requested?' do expect(Puppet::Application.stop_requested?).to be_truthy end it 'should return false for restart_requested?' do expect(Puppet::Application.restart_requested?).to be_falsey end it 'should return true for interrupted?' do expect(Puppet::Application.interrupted?).to be_truthy end it 'should return false for clear?' do expect(Puppet::Application.clear?).to be_falsey end end describe 'when invoking restart!' do before :each do Puppet::Application.run_status = nil Puppet::Application.restart! end after :each do Puppet::Application.run_status = nil end it 'should have run_status of :restart_requested' do expect(Puppet::Application.run_status).to eq(:restart_requested) end it 'should return true for restart_requested?' do expect(Puppet::Application.restart_requested?).to be_truthy end it 'should return false for stop_requested?' do expect(Puppet::Application.stop_requested?).to be_falsey end it 'should return true for interrupted?' do expect(Puppet::Application.interrupted?).to be_truthy end it 'should return false for clear?' do expect(Puppet::Application.clear?).to be_falsey end end describe 'when performing a controlled_run' do it 'should not execute block if not :clear?' do Puppet::Application.run_status = :stop_requested target = mock 'target' target.expects(:some_method).never Puppet::Application.controlled_run do target.some_method end end it 'should execute block if :clear?' do Puppet::Application.run_status = nil target = mock 'target' target.expects(:some_method).once Puppet::Application.controlled_run do target.some_method end end describe 'on POSIX systems', :if => Puppet.features.posix? do it 'should signal process with HUP after block if restart requested during block execution' do Timeout::timeout(3) do # if the signal doesn't fire, this causes failure. has_run = false old_handler = trap('HUP') { has_run = true } begin Puppet::Application.controlled_run do Puppet::Application.run_status = :restart_requested end # Ruby 1.9 uses a separate OS level thread to run the signal # handler, so we have to poll - ideally, in a way that will kick # the OS into running other threads - for a while. # # You can't just use the Ruby Thread yield thing either, because # that is just an OS hint, and Linux ... doesn't take that # seriously. --daniel 2012-03-22 sleep 0.001 while not has_run ensure trap('HUP', old_handler) end end end end after :each do Puppet::Application.run_status = nil end end describe "when parsing command-line options" do before :each do @app.command_line.stubs(:args).returns([]) Puppet.settings.stubs(:optparse_addargs).returns([]) end it "should pass the banner to the option parser" do option_parser = stub "option parser" option_parser.stubs(:on) option_parser.stubs(:parse!) @app.class.instance_eval do banner "banner" end OptionParser.expects(:new).with("banner").returns(option_parser) @app.parse_options end it "should ask OptionParser to parse the command-line argument" do @app.command_line.stubs(:args).returns(%w{ fake args }) OptionParser.any_instance.expects(:parse!).with(%w{ fake args }) @app.parse_options end describe "when using --help" do it "should call exit" do @app.stubs(:puts) expect { @app.handle_help(nil) }.to exit_with 0 end end describe "when using --version" do it "should declare a version option" do expect(@app).to respond_to(:handle_version) end it "should exit after printing the version" do @app.stubs(:puts) expect { @app.handle_version(nil) }.to exit_with 0 end end describe "when dealing with an argument not declared directly by the application" do it "should pass it to handle_unknown if this method exists" do Puppet.settings.stubs(:optparse_addargs).returns([["--not-handled", :REQUIRED]]) @app.expects(:handle_unknown).with("--not-handled", "value").returns(true) @app.command_line.stubs(:args).returns(["--not-handled", "value"]) @app.parse_options end it "should transform boolean option to normal form for Puppet.settings" do @app.expects(:handle_unknown).with("--option", true) @app.send(:handlearg, "--[no-]option", true) end it "should transform boolean option to no- form for Puppet.settings" do @app.expects(:handle_unknown).with("--no-option", false) @app.send(:handlearg, "--[no-]option", false) end end end describe "when calling default setup" do before :each do @app.options.stubs(:[]) end [ :debug, :verbose ].each do |level| it "should honor option #{level}" do @app.options.stubs(:[]).with(level).returns(true) Puppet::Util::Log.stubs(:newdestination) @app.setup expect(Puppet::Util::Log.level).to eq(level == :verbose ? :info : :debug) end end it "should honor setdest option" do @app.options.stubs(:[]).with(:setdest).returns(false) Puppet::Util::Log.expects(:setup_default) @app.setup end it "sets the log destination if provided via settings" do @app.options.unstub(:[]) Puppet[:logdest] = "set_via_config" Puppet::Util::Log.expects(:newdestination).with("set_via_config") @app.setup end it "does not downgrade the loglevel when --verbose is specified" do Puppet[:log_level] = :debug @app.options.stubs(:[]).with(:verbose).returns(true) @app.setup_logs expect(Puppet::Util::Log.level).to eq(:debug) end it "allows the loglevel to be specified as an argument" do @app.set_log_level(:debug => true) expect(Puppet::Util::Log.level).to eq(:debug) end end describe "when configuring routes" do include PuppetSpec::Files before :each do Puppet::Node.indirection.reset_terminus_class end after :each do Puppet::Node.indirection.reset_terminus_class end it "should use the routes specified for only the active application" do Puppet[:route_file] = tmpfile('routes') File.open(Puppet[:route_file], 'w') do |f| f.print <<-ROUTES test_app: node: terminus: exec other_app: node: terminus: plain catalog: terminus: invalid ROUTES end @app.configure_indirector_routes expect(Puppet::Node.indirection.terminus_class).to eq('exec') end it "should not fail if the route file doesn't exist" do Puppet[:route_file] = "/dev/null/non-existent" expect { @app.configure_indirector_routes }.to_not raise_error end it "should raise an error if the routes file is invalid" do Puppet[:route_file] = tmpfile('routes') File.open(Puppet[:route_file], 'w') do |f| f.print <<-ROUTES invalid : : yaml ROUTES end expect { @app.configure_indirector_routes }.to raise_error(Psych::SyntaxError, /mapping values are not allowed in this context/) end end describe "when running" do before :each do @app.stubs(:preinit) @app.stubs(:setup) @app.stubs(:parse_options) end it "should call preinit" do @app.stubs(:run_command) @app.expects(:preinit) @app.run end it "should call parse_options" do @app.stubs(:run_command) @app.expects(:parse_options) @app.run end it "should call run_command" do @app.expects(:run_command) @app.run end it "should call run_command" do @app.expects(:run_command) @app.run end it "should call main as the default command" do @app.expects(:main) @app.run end it "should warn and exit if no command can be called" do Puppet.expects(:err) expect { @app.run }.to exit_with 1 end it "should raise an error if dispatch returns no command" do @app.stubs(:get_command).returns(nil) Puppet.expects(:err) expect { @app.run }.to exit_with 1 end it "should raise an error if dispatch returns an invalid command" do @app.stubs(:get_command).returns(:this_function_doesnt_exist) Puppet.expects(:err) expect { @app.run }.to exit_with 1 end end describe "when metaprogramming" do describe "when calling option" do it "should create a new method named after the option" do @app.class.option("--test1","-t") do end expect(@app).to respond_to(:handle_test1) end it "should transpose in option name any '-' into '_'" do @app.class.option("--test-dashes-again","-t") do end expect(@app).to respond_to(:handle_test_dashes_again) end it "should create a new method called handle_test2 with option(\"--[no-]test2\")" do @app.class.option("--[no-]test2","-t") do end expect(@app).to respond_to(:handle_test2) end describe "when a block is passed" do it "should create a new method with it" do @app.class.option("--[no-]test2","-t") do raise "I can't believe it, it works!" end expect { @app.handle_test2 }.to raise_error(RuntimeError, /I can't believe it, it works!/) end it "should declare the option to OptionParser" do OptionParser.any_instance.stubs(:on) OptionParser.any_instance.expects(:on).with { |*arg| arg[0] == "--[no-]test3" } @app.class.option("--[no-]test3","-t") do end @app.parse_options end it "should pass a block that calls our defined method" do OptionParser.any_instance.stubs(:on) OptionParser.any_instance.stubs(:on).with('--test4','-t').yields(nil) @app.expects(:send).with(:handle_test4, nil) @app.class.option("--test4","-t") do end @app.parse_options end end describe "when no block is given" do it "should declare the option to OptionParser" do OptionParser.any_instance.stubs(:on) OptionParser.any_instance.expects(:on).with("--test4","-t") @app.class.option("--test4","-t") @app.parse_options end it "should give to OptionParser a block that adds the value to the options array" do OptionParser.any_instance.stubs(:on) OptionParser.any_instance.stubs(:on).with("--test4","-t").yields(nil) @app.options.expects(:[]=).with(:test4,nil) @app.class.option("--test4","-t") @app.parse_options end end end end describe "#handle_logdest_arg" do let(:test_arg) { "arg_test_logdest" } it "should log an exception that is raised" do our_exception = Puppet::DevError.new("test exception") Puppet::Util::Log.expects(:newdestination).with(test_arg).raises(our_exception) Puppet.expects(:log_exception).with(our_exception) @app.handle_logdest_arg(test_arg) end it "should set the new log destination" do Puppet::Util::Log.expects(:newdestination).with(test_arg) @app.handle_logdest_arg(test_arg) end it "should set the flag that a destination is set in the options hash" do Puppet::Util::Log.stubs(:newdestination).with(test_arg) @app.handle_logdest_arg(test_arg) expect(@app.options[:setdest]).to be_truthy end it "does not set the log destination if setdest is true" do Puppet::Util::Log.expects(:newdestination).never @app.options[:setdest] = true @app.handle_logdest_arg(test_arg) end it "does not set the log destination if arg is nil" do Puppet::Util::Log.expects(:newdestination).never @app.handle_logdest_arg(nil) end end end puppet-5.5.10/spec/unit/capability_spec.rb0000644005276200011600000003137613417161722020416 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/compiler' describe 'Capability types' do include PuppetSpec::Compiler let(:env) { Puppet::Node::Environment.create(:testing, []) } let(:node) { Puppet::Node.new('test', :environment => env) } let(:loaders) { Puppet::Pops::Loaders.new(env) } around :each do |example| Puppet::Parser::Compiler.any_instance.stubs(:loaders).returns(loaders) Puppet.override(:loaders => loaders, :current_environment => env) do Puppet::Type.newtype :cap, :is_capability => true do newparam :name newparam :host end example.run Puppet::Type.rmtype(:cap) end end context 'annotations' do it "adds a blueprint for a produced resource" do catalog = compile_to_catalog(<<-MANIFEST, node) define test($hostname) { notify { "hostname ${hostname}":} } Test produces Cap { host => $hostname } MANIFEST krt = catalog.environment_instance.known_resource_types type = krt.definition(:test) expect(type.produces).to be_instance_of(Array) prd = type.produces.first expect(prd).to be_instance_of(Hash) expect(prd[:capability]).to eq("Cap") expect(prd[:mappings]).to be_instance_of(Hash) expect(prd[:mappings]["host"]).to be_instance_of(Puppet::Parser::AST::PopsBridge::Expression) end it "adds a blueprint for a consumed resource" do catalog = compile_to_catalog(<<-MANIFEST, node) define test($hostname) { notify { "hostname ${hostname}":} } Test consumes Cap { host => $hostname } MANIFEST krt = catalog.environment_instance.known_resource_types type = krt.definition(:test) expect(type.produces).to be_instance_of(Array) cns = type.consumes.first expect(cns).to be_instance_of(Hash) expect(cns[:capability]).to eq("Cap") expect(cns[:mappings]).to be_instance_of(Hash) expect(cns[:mappings]["host"]).to be_instance_of(Puppet::Parser::AST::PopsBridge::Expression) end it 'can place define and consumes/produces in separate manifests' do parse_results = [] parser = Puppet::Parser::ParserFactory.parser parser.string = <<-MANIFEST define test($hostname) { notify { "hostname ${hostname}":} } MANIFEST parse_results << parser.parse parser.string = <<-MANIFEST Test consumes Cap { host => $hostname } MANIFEST parse_results << parser.parse main = Puppet::Parser::AST::Hostclass.new('', :code => Puppet::Parser::ParserFactory.code_merger.concatenate(parse_results)) Puppet::Node::Environment.any_instance.stubs(:perform_initial_import).returns main type = compile_to_catalog(nil).environment_instance.known_resource_types.definition(:test) expect(type.produces).to be_instance_of(Array) cns = type.consumes.first expect(cns).to be_instance_of(Hash) expect(cns[:capability]).to eq('Cap') expect(cns[:mappings]).to be_instance_of(Hash) expect(cns[:mappings]['host']).to be_instance_of(Puppet::Parser::AST::PopsBridge::Expression) end it 'can place use a qualified name for defines that produces capabilities' do parse_results = [] parser = Puppet::Parser::ParserFactory.parser parser.string = <<-MANIFEST class mod { define test($hostname) { notify { "hostname ${hostname}":} } } include mod MANIFEST parse_results << parser.parse parser.string = <<-MANIFEST Mod::Test consumes Cap { host => $hostname } MANIFEST parse_results << parser.parse main = Puppet::Parser::AST::Hostclass.new('', :code => Puppet::Parser::ParserFactory.code_merger.concatenate(parse_results)) Puppet::Node::Environment.any_instance.stubs(:perform_initial_import).returns main type = compile_to_catalog(nil).environment_instance.known_resource_types.definition('Mod::Test') expect(type.produces).to be_instance_of(Array) cns = type.consumes.first expect(cns).to be_instance_of(Hash) expect(cns[:capability]).to eq('Cap') expect(cns[:mappings]).to be_instance_of(Hash) expect(cns[:mappings]['host']).to be_instance_of(Puppet::Parser::AST::PopsBridge::Expression) end it "does not allow operator '+>' in a mapping" do expect do compile_to_catalog(<<-MANIFEST, node) define test($hostname) { notify { "hostname ${hostname}":} } Test consumes Cap { host +> $hostname } MANIFEST end.to raise_error(Puppet::ParseErrorWithIssue, /Illegal \+> operation.*This operator can not be used in a Capability Mapping/) end it "does not allow operator '*=>' in a mapping" do expect do compile_to_catalog(<<-MANIFEST, node) define test($hostname) { notify { "hostname ${hostname}":} } Test consumes Cap { *=> { host => $hostname } } MANIFEST end.to raise_error(Puppet::ParseError, /The operator '\* =>' in a Capability Mapping is not supported/) end it "does not allow 'before' relationship to capability mapping" do expect do compile_to_catalog(<<-MANIFEST, node) define test() { notify { "hello":} } Test consumes Cap {} test { one: before => Cap[cap] } MANIFEST end.to raise_error(Puppet::Error, /'before' is not a valid relationship to a capability/) end ["produces", "consumes"].each do |kw| it "creates an error when #{kw} references nonexistent type" do manifest = <<-MANIFEST Test #{kw} Cap { host => $hostname } MANIFEST expect { compile_to_catalog(manifest, node) }.to raise_error(Puppet::Error, /#{kw} clause references nonexistent type Test/) end end end context 'exporting a capability' do it "does not add produced resources that are not exported" do manifest = <<-MANIFEST define test($hostname) { notify { "hostname ${hostname}":} } Test produces Cap { host => $hostname } test { one: hostname => "ahost" } MANIFEST catalog = compile_to_catalog(manifest, node) expect(catalog.resource("Test[one]")).to be_instance_of(Puppet::Resource) expect(catalog.resource_keys.find { |type, _| type == "Cap" }).to be_nil end it "adds produced resources that are exported" do manifest = <<-MANIFEST define test($hostname) { notify { "hostname ${hostname}":} } # The $hostname in the produces clause does not refer to this variable, # instead, it referes to the hostname property of the Test resource # that is producing the Cap $hostname = "other_host" Test produces Cap { host => $hostname } test { one: hostname => "ahost", export => Cap[two] } MANIFEST catalog = compile_to_catalog(manifest, node) expect(catalog.resource("Test[one]")).to be_instance_of(Puppet::Resource) caps = catalog.resource_keys.select { |type, _| type == "Cap" } expect(caps.size).to eq(1) cap = catalog.resource("Cap[two]") expect(cap).to be_instance_of(Puppet::Resource) expect(cap["require"]).to eq("Test[one]") expect(cap["host"]).to eq("ahost") expect(cap.resource_type).to eq(Puppet::Type::Cap) expect(cap.tags.any? { |t| t == 'producer:testing' }).to eq(true) end end context 'consuming a capability' do def make_catalog(instance) manifest = <<-MANIFEST define test($hostname = nohost) { notify { "hostname ${hostname}":} } Test consumes Cap { hostname => $host } MANIFEST compile_to_catalog(manifest + instance, node) end def mock_cap_finding cap = Puppet::Resource.new("Cap", "two") cap["host"] = "ahost" Puppet::Resource::CapabilityFinder.expects(:find).returns(cap) cap end it "does not fetch a consumed resource when consume metaparam not set" do Puppet::Resource::CapabilityFinder.expects(:find).never catalog = make_catalog("test { one: }") expect(catalog.resource_keys.find { |type, _| type == "Cap" }).to be_nil expect(catalog.resource("Test", "one")["hostname"]).to eq("nohost") end it "sets hostname from consumed capability" do cap = mock_cap_finding catalog = make_catalog("test { one: consume => Cap[two] }") expect(catalog.resource("Cap[two]")).to eq(cap) expect(catalog.resource("Cap[two]")["host"]).to eq("ahost") expect(catalog.resource("Test", "one")["hostname"]).to eq("ahost") end it "does not override explicit hostname property when consuming" do cap = mock_cap_finding catalog = make_catalog("test { one: hostname => other_host, consume => Cap[two] }") expect(catalog.resource("Cap[two]")).to eq(cap) expect(catalog.resource("Cap[two]")["host"]).to eq("ahost") expect(catalog.resource("Test", "one")["hostname"]).to eq("other_host") end it "fetches required capability" do cap = mock_cap_finding catalog = make_catalog("test { one: require => Cap[two] }") expect(catalog.resource("Cap[two]")).to eq(cap) expect(catalog.resource("Cap[two]")["host"]).to eq("ahost") expect(catalog.resource("Test", "one")["hostname"]).to eq("nohost") end ['export', 'consume'].each do |metaparam| it "validates that #{metaparam} metaparameter rejects values that are not resources" do expect { make_catalog("test { one: #{metaparam} => 'hello' }") }.to raise_error(Puppet::Error, /not a resource/) end it "validates that #{metaparam} metaparameter rejects resources that are not capability resources" do expect { make_catalog("notify{hello:} test { one: #{metaparam} => Notify[hello] }") }.to raise_error(Puppet::Error, /not a capability resource/) end end context 'producing/consuming resources' do let(:ral) do compile_to_ral(<<-MANIFEST, node) define producer() { notify { "producer":} } define consumer() { notify { $title:} } Producer produces Cap {} Consumer consumes Cap {} producer {x: export => Cap[cap]} consumer {x: consume => Cap[cap]} consumer {y: require => Cap[cap]} MANIFEST end let(:graph) do graph = Puppet::Graph::RelationshipGraph.new(Puppet::Graph::SequentialPrioritizer.new) graph.populate_from(ral) graph end let(:capability) { ral.resource('Cap[cap]') } it 'the produced resource depends on the producer' do expect(graph.dependencies(capability).map {|d| d.to_s }).to include('Producer[x]') end it 'the consumer depends on the consumed resource' do expect(graph.dependents(capability).map {|d| d.to_s }).to include('Consumer[x]') end it 'the consumer depends on the required resource' do expect(graph.dependents(capability).map {|d| d.to_s }).to include('Consumer[y]') end end context 'producing/consuming resources to/from classes' do let(:ral) do compile_to_ral(<<-MANIFEST, node) define test($hostname) { notify { $hostname:} } class producer($host) { notify { p: } } class consumer($host) { test { c: hostname => $host } } Class[producer] produces Cap {} Class[consumer] consumes Cap {} class { producer: host => 'produced.host', export => Cap[one]} class { consumer: consume => Cap[one]} MANIFEST end let(:graph) do graph = Puppet::Graph::RelationshipGraph.new(Puppet::Graph::SequentialPrioritizer.new) graph.populate_from(ral) graph end let(:capability) { ral.resource('Cap[one]') } it 'the produced resource depends on the producer' do expect(graph.dependencies(capability).map {|d| d.to_s }).to include('Class[Producer]') end it 'the consumer depends on the consumed resource' do expect(graph.dependents(capability).map {|d| d.to_s }).to include('Class[Consumer]') end it 'resource in the consumer class gets values from producer via the capability resource' do expect(graph.dependents(capability).map {|d| d.to_s }).to include('Notify[produced.host]') end end end context 'and aliased resources' do let(:drive) { Puppet.features.microsoft_windows? ? 'C:' : '' } let(:code) { <<-PUPPET } $dir='#{drive}/tmp/test' $same_dir='#{drive}/tmp/test/' file {$dir: ensure => directory } file { $same_dir: ensure => directory } PUPPET it 'fails if a resource is defined and then redefined using name that results in the same alias' do expect { compile_to_ral(code) }.to raise_error(/resource \["File", "#{drive}\/tmp\/test"\] already declared/) end end end puppet-5.5.10/spec/unit/configurer_spec.rb0000644005276200011600000011722213417161722020433 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/configurer' describe Puppet::Configurer do before do Puppet.settings.stubs(:use).returns(true) @agent = Puppet::Configurer.new @agent.stubs(:init_storage) Puppet::Util::Storage.stubs(:store) Puppet[:server] = "puppetmaster" Puppet[:report] = true end it "should include the Fact Handler module" do expect(Puppet::Configurer.ancestors).to be_include(Puppet::Configurer::FactHandler) end describe "when executing a pre-run hook" do it "should do nothing if the hook is set to an empty string" do Puppet.settings[:prerun_command] = "" Puppet::Util.expects(:exec).never @agent.execute_prerun_command end it "should execute any pre-run command provided via the 'prerun_command' setting" do Puppet.settings[:prerun_command] = "/my/command" Puppet::Util::Execution.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed") @agent.execute_prerun_command end it "should fail if the command fails" do Puppet.settings[:prerun_command] = "/my/command" Puppet::Util::Execution.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed") expect(@agent.execute_prerun_command).to be_falsey end end describe "when executing a post-run hook" do it "should do nothing if the hook is set to an empty string" do Puppet.settings[:postrun_command] = "" Puppet::Util.expects(:exec).never @agent.execute_postrun_command end it "should execute any post-run command provided via the 'postrun_command' setting" do Puppet.settings[:postrun_command] = "/my/command" Puppet::Util::Execution.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed") @agent.execute_postrun_command end it "should fail if the command fails" do Puppet.settings[:postrun_command] = "/my/command" Puppet::Util::Execution.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed") expect(@agent.execute_postrun_command).to be_falsey end end describe "when executing a catalog run" do before do Puppet.settings.stubs(:use).returns(true) @agent.stubs(:download_plugins) Puppet::Node::Facts.indirection.terminus_class = :memory @facts = Puppet::Node::Facts.new(Puppet[:node_name_value]) Puppet::Node::Facts.indirection.save(@facts) @catalog = Puppet::Resource::Catalog.new("tester", Puppet::Node::Environment.remote(Puppet[:environment].to_sym)) @catalog.stubs(:to_ral).returns(@catalog) Puppet::Resource::Catalog.indirection.terminus_class = :rest Puppet::Resource::Catalog.indirection.stubs(:find).returns(@catalog) @agent.stubs(:send_report) @agent.stubs(:save_last_run_summary) Puppet::Util::Log.stubs(:close_all) end after :all do Puppet::Node::Facts.indirection.reset_terminus_class Puppet::Resource::Catalog.indirection.reset_terminus_class end it "should initialize storage" do Puppet::Util::Storage.expects(:load) @agent.run end it "downloads plugins when told" do @agent.expects(:download_plugins) @agent.run(:pluginsync => true) end it "does not download plugins when told" do @agent.expects(:download_plugins).never @agent.run(:pluginsync => false) end it "should carry on when it can't fetch its node definition" do error = Net::HTTPError.new(400, 'dummy server communication error') Puppet::Node.indirection.expects(:find).raises(error) expect(@agent.run).to eq(0) end it "applies a cached catalog when it can't connect to the master" do error = Errno::ECONNREFUSED.new('Connection refused - connect(2)') Puppet::Node.indirection.expects(:find).raises(error) Puppet::Resource::Catalog.indirection.expects(:find).with(anything, has_entry(:ignore_cache => true)).raises(error) Puppet::Resource::Catalog.indirection.expects(:find).with(anything, has_entry(:ignore_terminus => true)).returns(@catalog) expect(@agent.run).to eq(0) end it "should initialize a transaction report if one is not provided" do report = Puppet::Transaction::Report.new Puppet::Transaction::Report.expects(:new).returns report @agent.run end it "should respect node_name_fact when setting the host on a report" do Puppet[:node_name_fact] = 'my_name_fact' @facts.values = {'my_name_fact' => 'node_name_from_fact'} report = Puppet::Transaction::Report.new @agent.run(:report => report) expect(report.host).to eq('node_name_from_fact') end it "should pass the new report to the catalog" do report = Puppet::Transaction::Report.new Puppet::Transaction::Report.stubs(:new).returns report @catalog.expects(:apply).with{|options| options[:report] == report} @agent.run end it "should use the provided report if it was passed one" do report = Puppet::Transaction::Report.new @catalog.expects(:apply).with {|options| options[:report] == report} @agent.run(:report => report) end it "should set the report as a log destination" do report = Puppet::Transaction::Report.new report.expects(:<<).with(instance_of(Puppet::Util::Log)).at_least_once @agent.run(:report => report) end it "should retrieve the catalog" do @agent.expects(:retrieve_catalog) @agent.run end it "should log a failure and do nothing if no catalog can be retrieved" do @agent.expects(:retrieve_catalog).returns nil Puppet.expects(:err).with "Could not retrieve catalog; skipping run" @agent.run end it "should apply the catalog with all options to :run" do @agent.expects(:retrieve_catalog).returns @catalog @catalog.expects(:apply).with { |args| args[:one] == true } @agent.run :one => true end it "should accept a catalog and use it instead of retrieving a different one" do @agent.expects(:retrieve_catalog).never @catalog.expects(:apply) @agent.run :one => true, :catalog => @catalog end it "should benchmark how long it takes to apply the catalog" do @agent.expects(:benchmark).with(:notice, instance_of(String)) @agent.expects(:retrieve_catalog).returns @catalog @catalog.expects(:apply).never # because we're not yielding @agent.run end it "should execute post-run hooks after the run" do @agent.expects(:execute_postrun_command) @agent.run end it "should create report with passed transaction_uuid and job_id" do @agent = Puppet::Configurer.new("test_tuuid", "test_jid") @agent.stubs(:init_storage) report = Puppet::Transaction::Report.new(nil, "test", "aaaa") Puppet::Transaction::Report.expects(:new).with(anything, anything, 'test_tuuid', 'test_jid').returns(report) @agent.expects(:send_report).with(report) @agent.run end it "should send the report" do report = Puppet::Transaction::Report.new(nil, "test", "aaaa") Puppet::Transaction::Report.expects(:new).returns(report) @agent.expects(:send_report).with(report) expect(report.environment).to eq("test") expect(report.transaction_uuid).to eq("aaaa") @agent.run end it "should send the transaction report even if the catalog could not be retrieved" do @agent.expects(:retrieve_catalog).returns nil report = Puppet::Transaction::Report.new(nil, "test", "aaaa") Puppet::Transaction::Report.expects(:new).returns(report) @agent.expects(:send_report).with(report) expect(report.environment).to eq("test") expect(report.transaction_uuid).to eq("aaaa") @agent.run end it "should send the transaction report even if there is a failure" do @agent.expects(:retrieve_catalog).raises "whatever" report = Puppet::Transaction::Report.new(nil, "test", "aaaa") Puppet::Transaction::Report.expects(:new).returns(report) @agent.expects(:send_report).with(report) expect(report.environment).to eq("test") expect(report.transaction_uuid).to eq("aaaa") expect(@agent.run).to be_nil end it "should remove the report as a log destination when the run is finished" do report = Puppet::Transaction::Report.new Puppet::Transaction::Report.expects(:new).returns(report) @agent.run expect(Puppet::Util::Log.destinations).not_to include(report) end it "should return the report exit_status as the result of the run" do report = Puppet::Transaction::Report.new Puppet::Transaction::Report.expects(:new).returns(report) report.expects(:exit_status).returns(1234) expect(@agent.run).to eq(1234) end it "should return nil if catalog application fails" do @catalog.expects(:apply).raises(Puppet::Error, 'One or more resource dependency cycles detected in graph') report = Puppet::Transaction::Report.new expect(@agent.run(catalog: @catalog, report: report)).to be_nil end it "should send the transaction report even if the pre-run command fails" do report = Puppet::Transaction::Report.new Puppet::Transaction::Report.expects(:new).returns(report) Puppet.settings[:prerun_command] = "/my/command" Puppet::Util::Execution.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed") @agent.expects(:send_report).with(report) expect(@agent.run).to be_nil end it "should include the pre-run command failure in the report" do report = Puppet::Transaction::Report.new Puppet::Transaction::Report.expects(:new).returns(report) Puppet.settings[:prerun_command] = "/my/command" Puppet::Util::Execution.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed") expect(@agent.run).to be_nil expect(report.logs.find { |x| x.message =~ /Could not run command from prerun_command/ }).to be end it "should send the transaction report even if the post-run command fails" do report = Puppet::Transaction::Report.new Puppet::Transaction::Report.expects(:new).returns(report) Puppet.settings[:postrun_command] = "/my/command" Puppet::Util::Execution.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed") @agent.expects(:send_report).with(report) expect(@agent.run).to be_nil end it "should include the post-run command failure in the report" do report = Puppet::Transaction::Report.new Puppet::Transaction::Report.expects(:new).returns(report) Puppet.settings[:postrun_command] = "/my/command" Puppet::Util::Execution.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed") report.expects(:<<).with { |log| log.message.include?("Could not run command from postrun_command") } expect(@agent.run).to be_nil end it "should execute post-run command even if the pre-run command fails" do Puppet.settings[:prerun_command] = "/my/precommand" Puppet.settings[:postrun_command] = "/my/postcommand" Puppet::Util::Execution.expects(:execute).with(["/my/precommand"]).raises(Puppet::ExecutionFailure, "Failed") Puppet::Util::Execution.expects(:execute).with(["/my/postcommand"]) expect(@agent.run).to be_nil end it "should finalize the report" do report = Puppet::Transaction::Report.new Puppet::Transaction::Report.expects(:new).returns(report) report.expects(:finalize_report) @agent.run end it "should not apply the catalog if the pre-run command fails" do report = Puppet::Transaction::Report.new Puppet::Transaction::Report.expects(:new).returns(report) Puppet.settings[:prerun_command] = "/my/command" Puppet::Util::Execution.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed") @catalog.expects(:apply).never() @agent.expects(:send_report) expect(@agent.run).to be_nil end it "should apply the catalog, send the report, and return nil if the post-run command fails" do report = Puppet::Transaction::Report.new Puppet::Transaction::Report.expects(:new).returns(report) Puppet.settings[:postrun_command] = "/my/command" Puppet::Util::Execution.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed") @catalog.expects(:apply) @agent.expects(:send_report) expect(@agent.run).to be_nil end it 'includes total time metrics in the report after successfully applying the catalog' do report = Puppet::Transaction::Report.new @catalog.stubs(:apply).with(:report => report) @agent.run(report: report) expect(report.metrics['time']).to be expect(report.metrics['time']['total']).to be_a_kind_of(Numeric) end it 'includes total time metrics in the report even if prerun fails' do Puppet.settings[:prerun_command] = "/my/command" Puppet::Util::Execution.expects(:execute).with(["/my/command"]).raises(Puppet::ExecutionFailure, "Failed") report = Puppet::Transaction::Report.new @agent.run(report: report) expect(report.metrics['time']).to be expect(report.metrics['time']['total']).to be_a_kind_of(Numeric) end it 'includes total time metrics in the report even if catalog retrieval fails' do report = Puppet::Transaction::Report.new @agent.stubs(:prepare_and_retrieve_catalog_from_cache).raises @agent.run(:report => report) expect(report.metrics['time']).to be expect(report.metrics['time']['total']).to be_a_kind_of(Numeric) end it "should refetch the catalog if the server specifies a new environment in the catalog" do catalog = Puppet::Resource::Catalog.new("tester", Puppet::Node::Environment.remote('second_env')) @agent.expects(:retrieve_catalog).returns(catalog).twice @agent.run end it "should change the environment setting if the server specifies a new environment in the catalog" do @catalog.stubs(:environment).returns("second_env") @agent.run expect(@agent.environment).to eq("second_env") end it "should fix the report if the server specifies a new environment in the catalog" do report = Puppet::Transaction::Report.new(nil, "test", "aaaa") Puppet::Transaction::Report.expects(:new).returns(report) @agent.expects(:send_report).with(report) @catalog.stubs(:environment).returns("second_env") @agent.stubs(:retrieve_catalog).returns(@catalog) @agent.run expect(report.environment).to eq("second_env") end it "sends the transaction uuid in a catalog request" do @agent.instance_variable_set(:@transaction_uuid, 'aaa') Puppet::Resource::Catalog.indirection.expects(:find).with(anything, has_entries(:transaction_uuid => 'aaa')) @agent.run end it "sends the transaction uuid in a catalog request" do @agent.instance_variable_set(:@job_id, 'aaa') Puppet::Resource::Catalog.indirection.expects(:find).with(anything, has_entries(:job_id => 'aaa')) @agent.run end it "sets the static_catalog query param to true in a catalog request" do Puppet::Resource::Catalog.indirection.expects(:find).with(anything, has_entries(:static_catalog => true)) @agent.run end it "sets the checksum_type query param to the default supported_checksum_types in a catalog request" do Puppet::Resource::Catalog.indirection.expects(:find).with(anything, has_entries(:checksum_type => 'md5.sha256.sha384.sha512.sha224')) @agent.run end it "sets the checksum_type query param to the supported_checksum_types setting in a catalog request" do # Regenerate the agent to pick up the new setting Puppet[:supported_checksum_types] = ['sha256'] @agent = Puppet::Configurer.new @agent.stubs(:init_storage) @agent.stubs(:download_plugins) @agent.stubs(:send_report) @agent.stubs(:save_last_run_summary) Puppet::Resource::Catalog.indirection.expects(:find).with(anything, has_entries(:checksum_type => 'sha256')) @agent.run end describe "when not using a REST terminus for catalogs" do it "should not pass any facts when retrieving the catalog" do Puppet::Resource::Catalog.indirection.terminus_class = :compiler @agent.expects(:facts_for_uploading).never Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:facts].nil? }.returns @catalog @agent.run end end describe "when using a REST terminus for catalogs" do it "should pass the prepared facts and the facts format as arguments when retrieving the catalog" do Puppet::Resource::Catalog.indirection.terminus_class = :rest @agent.expects(:facts_for_uploading).returns(:facts => "myfacts", :facts_format => :foo) Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:facts] == "myfacts" and options[:facts_format] == :foo }.returns @catalog @agent.run end end end describe "when initialized with a transaction_uuid" do it "stores it" do SecureRandom.expects(:uuid).never configurer = Puppet::Configurer.new('foo') expect(configurer.instance_variable_get(:@transaction_uuid) == 'foo') end end describe "when sending a report" do include PuppetSpec::Files before do Puppet.settings.stubs(:use).returns(true) @configurer = Puppet::Configurer.new Puppet[:lastrunfile] = tmpfile('last_run_file') @report = Puppet::Transaction::Report.new Puppet[:reports] = "none" end it "should print a report summary if configured to do so" do Puppet.settings[:summarize] = true @report.expects(:summary).returns "stuff" @configurer.expects(:puts).with("stuff") @configurer.send_report(@report) end it "should not print a report summary if not configured to do so" do Puppet.settings[:summarize] = false @configurer.expects(:puts).never @configurer.send_report(@report) end it "should save the report if reporting is enabled" do Puppet.settings[:report] = true Puppet::Transaction::Report.indirection.expects(:save).with(@report, nil, instance_of(Hash)) @configurer.send_report(@report) end it "should not save the report if reporting is disabled" do Puppet.settings[:report] = false Puppet::Transaction::Report.indirection.expects(:save).with(@report, nil, instance_of(Hash)).never @configurer.send_report(@report) end it "should save the last run summary if reporting is enabled" do Puppet.settings[:report] = true @configurer.expects(:save_last_run_summary).with(@report) @configurer.send_report(@report) end it "should save the last run summary if reporting is disabled" do Puppet.settings[:report] = false @configurer.expects(:save_last_run_summary).with(@report) @configurer.send_report(@report) end it "should log but not fail if saving the report fails" do Puppet.settings[:report] = true Puppet::Transaction::Report.indirection.expects(:save).raises("whatever") Puppet.expects(:err) expect { @configurer.send_report(@report) }.not_to raise_error end end describe "when saving the summary report file" do include PuppetSpec::Files before do Puppet.settings.stubs(:use).returns(true) @configurer = Puppet::Configurer.new @report = stub 'report', :raw_summary => {} Puppet[:lastrunfile] = tmpfile('last_run_file') end it "should write the last run file" do @configurer.save_last_run_summary(@report) expect(Puppet::FileSystem.exist?(Puppet[:lastrunfile])).to be_truthy end it "should write the raw summary as yaml" do @report.expects(:raw_summary).returns("summary") @configurer.save_last_run_summary(@report) expect(File.read(Puppet[:lastrunfile])).to eq(YAML.dump("summary")) end it "should log but not fail if saving the last run summary fails" do # The mock will raise an exception on any method used. This should # simulate a nice hard failure from the underlying OS for us. fh = Class.new(Object) do def method_missing(*args) raise "failed to do #{args[0]}" end end.new Puppet::Util.expects(:replace_file).yields(fh) Puppet.expects(:err) expect { @configurer.save_last_run_summary(@report) }.to_not raise_error end it "should create the last run file with the correct mode" do Puppet.settings.setting(:lastrunfile).expects(:mode).returns('664') @configurer.save_last_run_summary(@report) if Puppet::Util::Platform.windows? require 'puppet/util/windows/security' mode = Puppet::Util::Windows::Security.get_mode(Puppet[:lastrunfile]) else mode = Puppet::FileSystem.stat(Puppet[:lastrunfile]).mode end expect(mode & 0777).to eq(0664) end it "should report invalid last run file permissions" do Puppet.settings.setting(:lastrunfile).expects(:mode).returns('892') Puppet.expects(:err).with(regexp_matches(/Could not save last run local report.*892 is invalid/)) @configurer.save_last_run_summary(@report) end end describe "when requesting a node" do it "uses the transaction uuid in the request" do Puppet::Node.indirection.expects(:find).with(anything, has_entries(:transaction_uuid => anything)).twice @agent.run end it "sends an explicitly configured environment request" do Puppet.settings.expects(:set_by_config?).with(:environment).returns(true) Puppet::Node.indirection.expects(:find).with(anything, has_entries(:configured_environment => Puppet[:environment])).twice @agent.run end it "does not send a configured_environment when using the default" do Puppet::Node.indirection.expects(:find).with(anything, has_entries(:configured_environment => nil)).twice @agent.run end end def expects_new_catalog_only(catalog) Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns catalog Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_terminus] == true }.never end def expects_cached_catalog_only(catalog) Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_terminus] == true }.returns catalog Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_cache] == true }.never end def expects_fallback_to_cached_catalog(catalog) Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns nil Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_terminus] == true }.returns catalog end def expects_fallback_to_new_catalog(catalog) Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_terminus] == true }.returns nil Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns catalog end def expects_neither_new_or_cached_catalog Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns nil Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_terminus] == true }.returns nil end describe "when retrieving a catalog" do before do Puppet.settings.stubs(:use).returns(true) @agent.stubs(:facts_for_uploading).returns({}) @agent.stubs(:download_plugins) # retrieve a catalog in the current environment, so we don't try to converge unexpectedly @catalog = Puppet::Resource::Catalog.new("tester", Puppet::Node::Environment.remote(Puppet[:environment].to_sym)) # this is the default when using a Configurer instance Puppet::Resource::Catalog.indirection.stubs(:terminus_class).returns :rest end describe "and configured to only retrieve a catalog from the cache" do before do Puppet.settings[:use_cached_catalog] = true end it "should first look in the cache for a catalog" do expects_cached_catalog_only(@catalog) expect(@agent.retrieve_catalog({})).to eq(@catalog) end it "should not make a node request or pluginsync when a cached catalog is successfully retrieved" do Puppet::Node.indirection.expects(:find).never expects_cached_catalog_only(@catalog) @agent.expects(:download_plugins).never @agent.run end it "should make a node request and pluginsync when a cached catalog cannot be retrieved" do Puppet::Node.indirection.expects(:find).returns nil expects_fallback_to_new_catalog(@catalog) @agent.expects(:download_plugins) @agent.run end it "should set its cached_catalog_status to 'explicitly_requested'" do expects_cached_catalog_only(@catalog) @agent.retrieve_catalog({}) expect(@agent.instance_variable_get(:@cached_catalog_status)).to eq('explicitly_requested') end it "should set its cached_catalog_status to 'explicitly requested' if the cached catalog is from a different environment" do cached_catalog = Puppet::Resource::Catalog.new("tester", Puppet::Node::Environment.remote('second_env')) expects_cached_catalog_only(cached_catalog) @agent.retrieve_catalog({}) expect(@agent.instance_variable_get(:@cached_catalog_status)).to eq('explicitly_requested') end it "should compile a new catalog if none is found in the cache" do expects_fallback_to_new_catalog(@catalog) expect(@agent.retrieve_catalog({})).to eq(@catalog) end it "should set its cached_catalog_status to 'not_used' if no catalog is found in the cache" do expects_fallback_to_new_catalog(@catalog) @agent.retrieve_catalog({}) expect(@agent.instance_variable_get(:@cached_catalog_status)).to eq('not_used') end it "should not attempt to retrieve a cached catalog again if the first attempt failed" do Puppet::Node.indirection.expects(:find).returns(nil) expects_neither_new_or_cached_catalog @agent.run end it "should return the cached catalog when the environment doesn't match" do cached_catalog = Puppet::Resource::Catalog.new("tester", Puppet::Node::Environment.remote('second_env')) expects_cached_catalog_only(cached_catalog) Puppet.expects(:info).with("Using cached catalog from environment 'second_env'") expect(@agent.retrieve_catalog({})).to eq(cached_catalog) end end describe "and strict environment mode is set" do before do @catalog.stubs(:to_ral).returns(@catalog) @catalog.stubs(:write_class_file) @catalog.stubs(:write_resource_file) @agent.stubs(:send_report) @agent.stubs(:save_last_run_summary) Puppet.settings[:strict_environment_mode] = true end it "should not make a node request" do Puppet::Node.indirection.expects(:find).never @agent.run end it "should return nil when the catalog's environment doesn't match the agent specified environment" do @agent.instance_variable_set(:@environment, 'second_env') expects_new_catalog_only(@catalog) Puppet.expects(:err).with("Not using catalog because its environment 'production' does not match agent specified environment 'second_env' and strict_environment_mode is set") expect(@agent.run).to be_nil end it "should not return nil when the catalog's environment matches the agent specified environment" do @agent.instance_variable_set(:@environment, 'production') expects_new_catalog_only(@catalog) expect(@agent.run).to eq(0) end describe "and a cached catalog is explicitly requested" do before do Puppet.settings[:use_cached_catalog] = true end it "should return nil when the cached catalog's environment doesn't match the agent specified environment" do @agent.instance_variable_set(:@environment, 'second_env') expects_cached_catalog_only(@catalog) Puppet.expects(:err).with("Not using catalog because its environment 'production' does not match agent specified environment 'second_env' and strict_environment_mode is set") expect(@agent.run).to be_nil end it "should proceed with the cached catalog if its environment matchs the local environment" do Puppet.settings[:use_cached_catalog] = true @agent.instance_variable_set(:@environment, 'production') expects_cached_catalog_only(@catalog) expect(@agent.run).to eq(0) end end end it "should use the Catalog class to get its catalog" do Puppet::Resource::Catalog.indirection.expects(:find).returns @catalog @agent.retrieve_catalog({}) end it "should set its cached_catalog_status to 'not_used' when downloading a new catalog" do Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns @catalog @agent.retrieve_catalog({}) expect(@agent.instance_variable_get(:@cached_catalog_status)).to eq('not_used') end it "should use its node_name_value to retrieve the catalog" do Facter.stubs(:value).returns "eh" Puppet.settings[:node_name_value] = "myhost.domain.com" Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| name == "myhost.domain.com" }.returns @catalog @agent.retrieve_catalog({}) end it "should default to returning a catalog retrieved directly from the server, skipping the cache" do Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns @catalog expect(@agent.retrieve_catalog({})).to eq(@catalog) end it "should log and return the cached catalog when no catalog can be retrieved from the server" do expects_fallback_to_cached_catalog(@catalog) Puppet.expects(:info).with("Using cached catalog from environment 'production'") expect(@agent.retrieve_catalog({})).to eq(@catalog) end it "should set its cached_catalog_status to 'on_failure' when no catalog can be retrieved from the server" do expects_fallback_to_cached_catalog(@catalog) @agent.retrieve_catalog({}) expect(@agent.instance_variable_get(:@cached_catalog_status)).to eq('on_failure') end it "should not look in the cache for a catalog if one is returned from the server" do expects_new_catalog_only(@catalog) expect(@agent.retrieve_catalog({})).to eq(@catalog) end it "should return the cached catalog when retrieving the remote catalog throws an exception" do Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_cache] == true }.raises "eh" Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_terminus] == true }.returns @catalog expect(@agent.retrieve_catalog({})).to eq(@catalog) end it "should set its cached_catalog_status to 'on_failure' when retrieving the remote catalog throws an exception" do Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_cache] == true }.raises "eh" Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_terminus] == true }.returns @catalog @agent.retrieve_catalog({}) expect(@agent.instance_variable_get(:@cached_catalog_status)).to eq('on_failure') end it "should log and return nil if no catalog can be retrieved from the server and :usecacheonfailure is disabled" do Puppet[:usecacheonfailure] = false Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns nil Puppet.expects(:warning).with('Not using cache on failed catalog') expect(@agent.retrieve_catalog({})).to be_nil end it "should set its cached_catalog_status to 'not_used' if no catalog can be retrieved from the server and :usecacheonfailure is disabled or fails to retrieve a catalog" do Puppet[:usecacheonfailure] = false Puppet::Resource::Catalog.indirection.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns nil @agent.retrieve_catalog({}) expect(@agent.instance_variable_get(:@cached_catalog_status)).to eq('not_used') end it "should return nil if no cached catalog is available and no catalog can be retrieved from the server" do expects_neither_new_or_cached_catalog expect(@agent.retrieve_catalog({})).to be_nil end it "should return nil if its cached catalog environment doesn't match server-specified environment" do cached_catalog = Puppet::Resource::Catalog.new("tester", Puppet::Node::Environment.remote('second_env')) @agent.instance_variable_set(:@node_environment, 'production') expects_fallback_to_cached_catalog(cached_catalog) Puppet.expects(:err).with("Not using cached catalog because its environment 'second_env' does not match 'production'") expect(@agent.retrieve_catalog({})).to be_nil end it "should set its cached_catalog_status to 'not_used' if the cached catalog environment doesn't match server-specified environment" do cached_catalog = Puppet::Resource::Catalog.new("tester", Puppet::Node::Environment.remote('second_env')) @agent.instance_variable_set(:@node_environment, 'production') expects_fallback_to_cached_catalog(cached_catalog) @agent.retrieve_catalog({}) expect(@agent.instance_variable_get(:@cached_catalog_status)).to eq('not_used') end it "should return its cached catalog if the environment matches the server-specified environment" do cached_catalog = Puppet::Resource::Catalog.new("tester", Puppet::Node::Environment.remote(Puppet[:environment])) @agent.instance_variable_set(:@node_environment, cached_catalog.environment) expects_fallback_to_cached_catalog(cached_catalog) expect(@agent.retrieve_catalog({})).to eq(cached_catalog) end it "should set its cached_catalog_status to 'on_failure' if the cached catalog environment matches server-specified environment" do cached_catalog = Puppet::Resource::Catalog.new("tester", Puppet::Node::Environment.remote(Puppet[:environment])) @agent.instance_variable_set(:@node_environment, cached_catalog.environment) expects_fallback_to_cached_catalog(cached_catalog) @agent.retrieve_catalog({}) expect(@agent.instance_variable_get(:@cached_catalog_status)).to eq('on_failure') end it "should not update the cached catalog in noop mode" do Puppet[:noop] = true Puppet::Resource::Catalog.indirection.expects(:find).with(anything, has_entries(:ignore_cache => true, :ignore_cache_save => true)).returns(@catalog) @agent.retrieve_catalog({}) end it "should update the cached catalog when not in noop mode" do Puppet[:noop] = false Puppet::Resource::Catalog.indirection.expects(:find).with(anything, has_entries(:ignore_cache => true, :ignore_cache_save => false)).returns(@catalog) @agent.retrieve_catalog({}) end end describe "when converting the catalog" do before do Puppet.settings.stubs(:use).returns(true) catalog.stubs(:to_ral).returns ral_catalog end let (:catalog) { Puppet::Resource::Catalog.new('tester', Puppet::Node::Environment.remote(Puppet[:environment].to_sym)) } let (:ral_catalog) { Puppet::Resource::Catalog.new('tester', Puppet::Node::Environment.remote(Puppet[:environment].to_sym)) } it "should convert the catalog to a RAL-formed catalog" do expect(@agent.convert_catalog(catalog, 10)).to equal(ral_catalog) end it "should finalize the catalog" do ral_catalog.expects(:finalize) @agent.convert_catalog(catalog, 10) end it "should record the passed retrieval time with the RAL catalog" do ral_catalog.expects(:retrieval_duration=).with 10 @agent.convert_catalog(catalog, 10) end it "should write the RAL catalog's class file" do ral_catalog.expects(:write_class_file) @agent.convert_catalog(catalog, 10) end it "should write the RAL catalog's resource file" do ral_catalog.expects(:write_resource_file) @agent.convert_catalog(catalog, 10) end it "should set catalog conversion time on the report" do report = Puppet::Transaction::Report.new report.expects(:add_times).with(:convert_catalog, kind_of(Numeric)) @agent.convert_catalog(catalog, 10, {:report => report}) end end describe "when determining whether to pluginsync" do it "should default to Puppet[:pluginsync] when explicitly set by the commandline" do Puppet.settings[:pluginsync] = false Puppet.settings.expects(:set_by_cli?).returns(true) expect(described_class).not_to be_should_pluginsync end it "should default to Puppet[:pluginsync] when explicitly set by config" do Puppet.settings[:pluginsync] = false Puppet.settings.expects(:set_by_config?).returns(true) expect(described_class).not_to be_should_pluginsync end it "should be true if use_cached_catalog is false" do Puppet.settings[:use_cached_catalog] = false expect(described_class).to be_should_pluginsync end it "should be false if use_cached_catalog is true" do Puppet.settings[:use_cached_catalog] = true expect(described_class).not_to be_should_pluginsync end end describe "when attempting failover" do it "should not failover if server_list is not set" do Puppet.settings[:server_list] = [] @agent.expects(:find_functional_server).never @agent.run end it "should not failover during an apply run" do Puppet.settings[:server_list] = ["myserver:123"] @agent.expects(:find_functional_server).never catalog = Puppet::Resource::Catalog.new("tester", Puppet::Node::Environment.remote(Puppet[:environment].to_sym)) @agent.run :catalog => catalog end it "should select a server when provided" do Puppet.settings[:server_list] = ["myserver:123"] pool = Puppet::Network::HTTP::Pool.new(Puppet[:http_keepalive_timeout]) Puppet::Network::HTTP::Pool.expects(:new).returns(pool) Puppet.expects(:override).with({:http_pool => pool}).yields Puppet.expects(:override).with({:server => "myserver", :serverport => '123'}).twice.yields Puppet::Node.indirection.expects(:find).returns(nil) @agent.expects(:run_internal).returns(nil) @agent.run end it "should fallback to an empty server when failover fails" do Puppet.settings[:server_list] = ["myserver:123"] pool = Puppet::Network::HTTP::Pool.new(Puppet[:http_keepalive_timeout]) Puppet::Network::HTTP::Pool.expects(:new).returns(pool) Puppet.expects(:override).with({:http_pool => pool}).yields Puppet.expects(:override).with({:server => "myserver", :serverport => '123'}).yields Puppet.expects(:override).with({:server => nil, :serverport => nil}).yields error = Net::HTTPError.new(400, 'dummy server communication error') Puppet::Node.indirection.expects(:find).raises(error) @agent.expects(:run_internal).returns(nil) @agent.run end it "should not make multiple node requets when the server is found" do Puppet.settings[:server_list] = ["myserver:123"] Puppet::Node.indirection.expects(:find).returns("mynode").once @agent.expects(:prepare_and_retrieve_catalog).returns(nil) @agent.run end end end puppet-5.5.10/spec/unit/daemon_spec.rb0000644005276200011600000001446213417161722017535 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/daemon' require 'puppet/agent' def without_warnings flag = $VERBOSE $VERBOSE = nil yield $VERBOSE = flag end class TestClient def lockfile_path "/dev/null" end end describe Puppet::Daemon, :unless => Puppet.features.microsoft_windows? do include PuppetSpec::Files class RecordingScheduler attr_reader :jobs def run_loop(jobs) @jobs = jobs end end let(:agent) { Puppet::Agent.new(TestClient.new, false) } let(:server) { stub("Server", :start => nil, :wait_for_shutdown => nil) } let(:pidfile) { stub("PidFile", :lock => true, :unlock => true, :file_path => 'fake.pid') } let(:scheduler) { RecordingScheduler.new } let(:daemon) { Puppet::Daemon.new(pidfile, scheduler) } before do daemon.stubs(:close_streams).returns nil end it "should reopen the Log logs when told to reopen logs" do Puppet::Util::Log.expects(:reopen) daemon.reopen_logs end describe "when setting signal traps" do [:INT, :TERM].each do |signal| it "logs a notice and exits when sent #{signal}" do Signal.stubs(:trap).with(signal).yields Puppet.expects(:notice).with("Caught #{signal}; exiting") daemon.expects(:stop) daemon.set_signal_traps end end {:HUP => :restart, :USR1 => :reload, :USR2 => :reopen_logs}.each do |signal, method| it "logs a notice and remembers to call #{method} when it receives #{signal}" do Signal.stubs(:trap).with(signal).yields Puppet.expects(:notice).with("Caught #{signal}; storing #{method}") daemon.set_signal_traps expect(daemon.signals).to eq([method]) end end end describe "when starting" do before do daemon.stubs(:set_signal_traps) end it "should fail if it has neither agent nor server" do expect { daemon.start }.to raise_error(Puppet::DevError) end it "should create its pidfile" do pidfile.expects(:lock).returns(true) daemon.agent = agent daemon.start end it "should fail if it cannot lock" do pidfile.expects(:lock).returns(false) daemon.agent = agent expect { daemon.start }.to raise_error(RuntimeError, "Could not create PID file: #{pidfile.file_path}") end it "should start its server if one is configured" do daemon.server = server server.expects(:start) daemon.start end it "disables the reparse of configs if the filetimeout is 0" do Puppet[:filetimeout] = 0 daemon.agent = agent daemon.start expect(scheduler.jobs[0]).not_to be_enabled end it "disables the agent run when there is no agent" do Puppet[:filetimeout] = 0 daemon.server = server daemon.start expect(scheduler.jobs[1]).not_to be_enabled end it "waits for the server to shutdown when there is one" do daemon.server = server server.expects(:wait_for_shutdown) daemon.start end it "waits for the server to shutdown when there is one" do daemon.server = server server.expects(:wait_for_shutdown) daemon.start end end describe "when stopping" do before do Puppet::Util::Log.stubs(:close_all) # to make the global safe to mock, set it to a subclass of itself, # then restore it in an after pass without_warnings { Puppet::Application = Class.new(Puppet::Application) } end after do # restore from the superclass so we lose the stub garbage without_warnings { Puppet::Application = Puppet::Application.superclass } end it "should stop its server if one is configured" do server.expects(:stop) daemon.server = server expect { daemon.stop }.to exit_with 0 end it 'should request a stop from Puppet::Application' do Puppet::Application.expects(:stop!) expect { daemon.stop }.to exit_with 0 end it "should remove its pidfile" do pidfile.expects(:unlock) expect { daemon.stop }.to exit_with 0 end it "should close all logs" do Puppet::Util::Log.expects(:close_all) expect { daemon.stop }.to exit_with 0 end it "should exit unless called with ':exit => false'" do expect { daemon.stop }.to exit_with 0 end it "should not exit if called with ':exit => false'" do daemon.stop :exit => false end end describe "when reloading" do it "should do nothing if no agent is configured" do daemon.reload end it "should do nothing if the agent is running" do agent.expects(:run).with({:splay => false}).raises Puppet::LockError, 'Failed to aquire lock' Puppet.expects(:notice).with('Not triggering already-running agent') daemon.agent = agent daemon.reload end it "should run the agent if one is available and it is not running" do agent.expects(:run).with({:splay => false}) Puppet.expects(:notice).with('Not triggering already-running agent').never daemon.agent = agent daemon.reload end end describe "when restarting" do before do without_warnings { Puppet::Application = Class.new(Puppet::Application) } end after do without_warnings { Puppet::Application = Puppet::Application.superclass } end it 'should set Puppet::Application.restart!' do Puppet::Application.expects(:restart!) daemon.stubs(:reexec) daemon.restart end it "should reexec itself if no agent is available" do daemon.expects(:reexec) daemon.restart end it "should reexec itself if the agent is not running" do agent.expects(:running?).returns false daemon.agent = agent daemon.expects(:reexec) daemon.restart end end describe "when reexecing it self" do before do daemon.stubs(:exec) daemon.stubs(:stop) end it "should fail if no argv values are available" do daemon.expects(:argv).returns nil expect { daemon.reexec }.to raise_error(Puppet::DevError) end it "should shut down without exiting" do daemon.argv = %w{foo} daemon.expects(:stop).with(:exit => false) daemon.reexec end it "should call 'exec' with the original executable and arguments" do daemon.argv = %w{foo} daemon.expects(:exec).with($0 + " foo") daemon.reexec end end end puppet-5.5.10/spec/unit/defaults_spec.rb0000644005276200011600000001400013417161722020065 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/settings' describe "Defaults" do describe ".default_diffargs" do describe "on AIX" do before(:each) do Facter.stubs(:value).with(:kernel).returns("AIX") end describe "on 5.3" do before(:each) do Facter.stubs(:value).with(:kernelmajversion).returns("5300") end it "should be empty" do expect(Puppet.default_diffargs).to eq("") end end [ "", nil, "6300", "7300", ].each do |kernel_version| describe "on kernel version #{kernel_version.inspect}" do before(:each) do Facter.stubs(:value).with(:kernelmajversion).returns(kernel_version) end it "should be '-u'" do expect(Puppet.default_diffargs).to eq("-u") end end end end describe "on everything else" do before(:each) do Facter.stubs(:value).with(:kernel).returns("NOT_AIX") end it "should be '-u'" do expect(Puppet.default_diffargs).to eq("-u") end end end describe 'strict' do it 'should accept the valid value :off' do expect {Puppet.settings[:strict] = 'off'}.to_not raise_exception end it 'should accept the valid value :warning' do expect {Puppet.settings[:strict] = 'warning'}.to_not raise_exception end it 'should accept the valid value :error' do expect {Puppet.settings[:strict] = 'error'}.to_not raise_exception end it 'should fail if given an invalid value' do expect {Puppet.settings[:strict] = 'ignore'}.to raise_exception(/Invalid value 'ignore' for parameter strict\./) end end describe '.default_digest_algorithm' do it 'defaults to md5 when FIPS is not enabled' do Puppet::Util::Platform.stubs(:fips_enabled?).returns false expect(Puppet.default_digest_algorithm).to eq('md5') end it 'defaults to sha256 when FIPS is enabled' do Puppet::Util::Platform.stubs(:fips_enabled?).returns true expect(Puppet.default_digest_algorithm).to eq('sha256') end end describe '.supported_checksum_types' do it 'defaults to md5, sha256, sha384, sha512, sha224 when FIPS is not enabled' do Puppet::Util::Platform.stubs(:fips_enabled?).returns false expect(Puppet.default_file_checksum_types).to eq(%w[md5 sha256 sha384 sha512 sha224]) end it 'defaults to sha256, sha384, sha512, sha224 when FIPS is enabled' do Puppet::Util::Platform.stubs(:fips_enabled?).returns true expect(Puppet.default_file_checksum_types).to eq(%w[sha256 sha384 sha512 sha224]) end end describe 'Puppet[:supported_checksum_types]' do it 'defaults to md5, sha256, sha512, sha384, sha224' do expect(Puppet.settings[:supported_checksum_types]).to eq(%w[md5 sha256 sha384 sha512 sha224]) end it 'should raise an error on an unsupported checksum type' do expect { Puppet.settings[:supported_checksum_types] = %w[md5 foo] }.to raise_exception ArgumentError, /Invalid value 'foo' for parameter supported_checksum_types. Allowed values are/ end it 'should not raise an error on setting a valid list of checksum types' do Puppet.settings[:supported_checksum_types] = %w[sha256 md5lite mtime] expect(Puppet.settings[:supported_checksum_types]).to eq(%w[sha256 md5lite mtime]) end it 'raises when setting md5 in FIPS mode' do Puppet::Util::Platform.stubs(:fips_enabled?).returns true expect { Puppet.settings[:supported_checksum_types] = %w[md5] }.to raise_error(ArgumentError, /Invalid value 'md5' for parameter supported_checksum_types. Allowed values are 'sha256'/) end end describe 'server vs server_list' do it 'should warn when both settings are set in code' do Puppet.expects(:deprecation_warning).with('Attempted to set both server and server_list. Server setting will not be used.', :SERVER_DUPLICATION) Puppet.settings[:server] = 'test_server' Puppet.settings[:server_list] = ['one', 'two'] end it 'should warn when both settings are set by command line' do Puppet.expects(:deprecation_warning).with('Attempted to set both server and server_list. Server setting will not be used.', :SERVER_DUPLICATION) Puppet.settings.handlearg("--server_list", "one,two") Puppet.settings.handlearg("--server", "test_server") end end describe 'manage_internal_file_permissions' do describe 'on windows', :if => Puppet::Util::Platform.windows? do it 'should default to false' do expect(Puppet.settings[:manage_internal_file_permissions]).to be false end end describe 'on non-windows', :if => ! Puppet::Util::Platform.windows? do it 'should default to true' do expect(Puppet.settings[:manage_internal_file_permissions]).to be true end end end describe 'basemodulepath' do it 'includes the global and system modules on non-windows', :unless => Puppet::Util::Platform.windows? do expect( Puppet[:basemodulepath] ).to match(%r{.*/code/modules:/opt/puppetlabs/puppet/modules}) end it 'includes global modules on windows', :if => Puppet::Util::Platform.windows? do expect( Puppet[:basemodulepath] ).to match(%r{.*/code/modules}) end end describe 'ordering' do it 'issues a deprecation warning when set to title-hash' do Puppet.expects(:deprecation_warning).with('Setting ordering is deprecated.', 'setting-ordering') Puppet.settings[:ordering] = 'title-hash' end it 'issues a deprecation warning when set to random' do Puppet.expects(:deprecation_warning).with('Setting ordering is deprecated.', 'setting-ordering') Puppet.settings[:ordering] = 'random' end it 'does not issue a deprecation warning when set to manifest' do Puppet.expects(:deprecation_warning).with('Setting ordering is deprecated.', 'setting-ordering').never Puppet.settings[:ordering] = 'manifest' end end end puppet-5.5.10/spec/unit/etc_spec.rb0000644005276200011600000004113313417161722017040 0ustar jenkinsjenkinsrequire 'puppet' require 'spec_helper' require 'puppet_spec/character_encoding' # The Ruby::Etc module is largely non-functional on Windows - many methods # simply return nil regardless of input, the Etc::Group struct is not defined, # and Etc::Passwd is missing fields # We want to test that: # - We correctly set external encoding values IF they're valid UTF-8 bytes # - We do not modify non-UTF-8 values if they're NOT valid UTF-8 bytes describe Puppet::Etc, :if => !Puppet.features.microsoft_windows? do # http://www.fileformat.info/info/unicode/char/5e0c/index.htm # 希 Han Character 'rare; hope, expect, strive for' # In EUC_KR: \xfd \xf1 - 253 241 # In UTF-8: \u5e0c - \xe5 \xb8 \x8c - 229 184 140 let(:euc_kr) { [253, 241].pack('C*').force_encoding(Encoding::EUC_KR) } # valid_encoding? == true let(:euc_kr_as_binary) { [253, 241].pack('C*') } # valid_encoding? == true let(:euc_kr_as_utf_8) { [253, 241].pack('C*').force_encoding(Encoding::UTF_8) } # valid_encoding? == false # characters representing different UTF-8 widths # 1-byte A # 2-byte Űż - http://www.fileformat.info/info/unicode/char/06ff/index.htm - 0xDB 0xBF / 219 191 # 3-byte áš  - http://www.fileformat.info/info/unicode/char/16A0/index.htm - 0xE1 0x9A 0xA0 / 225 154 160 # 4-byte 𠜎 - http://www.fileformat.info/info/unicode/char/2070E/index.htm - 0xF0 0xA0 0x9C 0x8E / 240 160 156 142 let(:mixed_utf_8) { "A\u06FF\u16A0\u{2070E}".force_encoding(Encoding::UTF_8) } # Aۿᚠ𠜎 let(:mixed_utf_8_as_binary) { "A\u06FF\u16A0\u{2070E}".force_encoding(Encoding::BINARY) } let(:mixed_utf_8_as_euc_kr) { "A\u06FF\u16A0\u{2070E}".force_encoding(Encoding::EUC_KR) } # An uninteresting value that ruby might return in an Etc struct. let(:root) { 'root' } # Set up example Etc Group structs with values representative of what we would # get back in these encodings let(:utf_8_group_struct) do group = Etc::Group.new # In a UTF-8 environment, these values will come back as UTF-8, even if # they're not valid UTF-8. We do not modify anything about either the # valid or invalid UTF-8 strings. # Group member contains a mix of valid and invalid UTF-8-labeled strings group.mem = [mixed_utf_8, root.dup.force_encoding(Encoding::UTF_8), euc_kr_as_utf_8] # group name contains same EUC_KR bytes labeled as UTF-8 group.name = euc_kr_as_utf_8 # group passwd field is valid UTF-8 group.passwd = mixed_utf_8 group.gid = 12345 group end let(:euc_kr_group_struct) do # In an EUC_KR environment, values will come back as EUC_KR, even if they're # not valid in that encoding. For values that are valid in UTF-8 we expect # their external encoding to be set to UTF-8 by Puppet::Etc. For values that # are invalid in UTF-8, we expect the string to be kept intact, unmodified, # as we can't transcode it. group = Etc::Group.new group.mem = [euc_kr, root.dup.force_encoding(Encoding::EUC_KR), mixed_utf_8_as_euc_kr] group.name = euc_kr group.passwd = mixed_utf_8_as_euc_kr group.gid = 12345 group end let(:ascii_group_struct) do # In a POSIX environment, any strings containing only values under # code-point 128 will be returned as ASCII, whereas anything above that # point will be returned as BINARY. In either case we override the encoding # to UTF-8 if that would be valid. group = Etc::Group.new group.mem = [euc_kr_as_binary, root.dup.force_encoding(Encoding::ASCII), mixed_utf_8_as_binary] group.name = euc_kr_as_binary group.passwd = mixed_utf_8_as_binary group.gid = 12345 group end let(:utf_8_user_struct) do user = Etc::Passwd.new # user name contains same EUC_KR bytes labeled as UTF-8 user.name = euc_kr_as_utf_8 # group passwd field is valid UTF-8 user.passwd = mixed_utf_8 user.uid = 12345 user end let(:euc_kr_user_struct) do user = Etc::Passwd.new user.name = euc_kr user.passwd = mixed_utf_8_as_euc_kr user.uid = 12345 user end let(:ascii_user_struct) do user = Etc::Passwd.new user.name = euc_kr_as_binary user.passwd = mixed_utf_8_as_binary user.uid = 12345 user end shared_examples "methods that return an overridden group struct from Etc" do |params| it "should return a new Struct object with corresponding canonical_ members" do group = Etc::Group.new Etc.expects(subject).with(*params).returns(group) puppet_group = Puppet::Etc.send(subject, *params) expect(puppet_group.members).to include(*group.members) expect(puppet_group.members).to include(*group.members.map { |mem| "canonical_#{mem}".to_sym }) # Confirm we haven't just added the new members to the original struct object, ie this is really a new struct expect(group.members.any? { |elem| elem.match(/^canonical_/) }).to be_falsey end context "when Encoding.default_external is UTF-8" do before do Etc.expects(subject).with(*params).returns(utf_8_group_struct) end let(:overridden) { PuppetSpec::CharacterEncoding.with_external_encoding(Encoding::UTF_8) do Puppet::Etc.send(subject, *params) end } it "should leave the valid UTF-8 values in arrays unmodified" do expect(overridden.mem[0]).to eq(mixed_utf_8) expect(overridden.mem[1]).to eq(root) end it "should replace invalid characters with replacement characters in invalid UTF-8 values in arrays" do expect(overridden.mem[2]).to eq("\uFFFD\uFFFD") end it "should keep an unmodified version of the invalid UTF-8 values in arrays in the corresponding canonical_ member" do expect(overridden.canonical_mem[2]).to eq(euc_kr_as_utf_8) end it "should leave the valid UTF-8 values unmodified" do expect(overridden.passwd).to eq(mixed_utf_8) end it "should replace invalid characters with '?' characters in invalid UTF-8 values" do expect(overridden.name).to eq("\uFFFD\uFFFD") end it "should keep an unmodified version of the invalid UTF-8 values in the corresponding canonical_ member" do expect(overridden.canonical_name).to eq(euc_kr_as_utf_8) end it "should copy all values to the new struct object" do # Confirm we've actually copied all the values to the canonical_members utf_8_group_struct.each_pair do |member, value| expect(overridden["canonical_#{member}"]).to eq(value) # Confirm we've reassigned all non-string and array values if !value.is_a?(String) && !value.is_a?(Array) expect(overridden[member]).to eq(value) expect(overridden[member].object_id).to eq(value.object_id) end end end end context "when Encoding.default_external is EUC_KR (i.e., neither UTF-8 nor POSIX)" do before do Etc.expects(subject).with(*params).returns(euc_kr_group_struct) end let(:overridden) { PuppetSpec::CharacterEncoding.with_external_encoding(Encoding::EUC_KR) do Puppet::Etc.send(subject, *params) end } it "should override EUC_KR-labeled values in arrays to UTF-8 if that would result in valid UTF-8" do expect(overridden.mem[2]).to eq(mixed_utf_8) expect(overridden.mem[1]).to eq(root) end it "should leave valid EUC_KR-labeled values that would not be valid UTF-8 in arrays unmodified" do expect(overridden.mem[0]).to eq(euc_kr) end it "should override EUC_KR-labeled values to UTF-8 if that would result in valid UTF-8" do expect(overridden.passwd).to eq(mixed_utf_8) end it "should leave valid EUC_KR-labeled values that would not be valid UTF-8 unmodified" do expect(overridden.name).to eq(euc_kr) end it "should copy all values to the new struct object" do # Confirm we've actually copied all the values to the canonical_members euc_kr_group_struct.each_pair do |member, value| expect(overridden["canonical_#{member}"]).to eq(value) # Confirm we've reassigned all non-string and array values if !value.is_a?(String) && !value.is_a?(Array) expect(overridden[member]).to eq(value) expect(overridden[member].object_id).to eq(value.object_id) end end end end context "when Encoding.default_external is POSIX (ASCII-7bit)" do before do Etc.expects(subject).with(*params).returns(ascii_group_struct) end let(:overridden) { PuppetSpec::CharacterEncoding.with_external_encoding(Encoding::ASCII) do Puppet::Etc.send(subject, *params) end } it "should not modify binary values in arrays that would be invalid UTF-8" do expect(overridden.mem[0]).to eq(euc_kr_as_binary) end it "should set the encoding to UTF-8 on binary values in arrays that would be valid UTF-8" do expect(overridden.mem[1]).to eq(root.dup.force_encoding(Encoding::UTF_8)) expect(overridden.mem[2]).to eq(mixed_utf_8) end it "should not modify binary values that would be invalid UTF-8" do expect(overridden.name).to eq(euc_kr_as_binary) end it "should set the encoding to UTF-8 on binary values that would be valid UTF-8" do expect(overridden.passwd).to eq(mixed_utf_8) end it "should copy all values to the new struct object" do # Confirm we've actually copied all the values to the canonical_members ascii_group_struct.each_pair do |member, value| expect(overridden["canonical_#{member}"]).to eq(value) # Confirm we've reassigned all non-string and array values if !value.is_a?(String) && !value.is_a?(Array) expect(overridden[member]).to eq(value) expect(overridden[member].object_id).to eq(value.object_id) end end end end end shared_examples "methods that return an overridden user struct from Etc" do |params| it "should return a new Struct object with corresponding canonical_ members" do user = Etc::Passwd.new Etc.expects(subject).with(*params).returns(user) puppet_user = Puppet::Etc.send(subject, *params) expect(puppet_user.members).to include(*user.members) expect(puppet_user.members).to include(*user.members.map { |mem| "canonical_#{mem}".to_sym }) # Confirm we haven't just added the new members to the original struct object, ie this is really a new struct expect(user.members.any? { |elem| elem.match(/^canonical_/)}).to be_falsey end context "when Encoding.default_external is UTF-8" do before do Etc.expects(subject).with(*params).returns(utf_8_user_struct) end let(:overridden) { PuppetSpec::CharacterEncoding.with_external_encoding(Encoding::UTF_8) do Puppet::Etc.send(subject, *params) end } it "should leave the valid UTF-8 values unmodified" do expect(overridden.passwd).to eq(mixed_utf_8) end it "should replace invalid characters with unicode replacement characters in invalid UTF-8 values" do expect(overridden.name).to eq("\uFFFD\uFFFD") end it "should keep an unmodified version of the invalid UTF-8 values in the corresponding canonical_ member" do expect(overridden.canonical_name).to eq(euc_kr_as_utf_8) end it "should copy all values to the new struct object" do # Confirm we've actually copied all the values to the canonical_members utf_8_user_struct.each_pair do |member, value| expect(overridden["canonical_#{member}"]).to eq(value) # Confirm we've reassigned all non-string and array values if !value.is_a?(String) && !value.is_a?(Array) expect(overridden[member]).to eq(value) expect(overridden[member].object_id).to eq(value.object_id) end end end end context "when Encoding.default_external is EUC_KR (i.e., neither UTF-8 nor POSIX)" do before do Etc.expects(subject).with(*params).returns(euc_kr_user_struct) end let(:overridden) { PuppetSpec::CharacterEncoding.with_external_encoding(Encoding::EUC_KR) do Puppet::Etc.send(subject, *params) end } it "should override valid UTF-8 EUC_KR-labeled values to UTF-8" do expect(overridden.passwd).to eq(mixed_utf_8) end it "should leave invalid EUC_KR-labeled values unmodified" do expect(overridden.name).to eq(euc_kr) end it "should copy all values to the new struct object" do # Confirm we've actually copied all the values to the canonical_members euc_kr_user_struct.each_pair do |member, value| expect(overridden["canonical_#{member}"]).to eq(value) # Confirm we've reassigned all non-string and array values if !value.is_a?(String) && !value.is_a?(Array) expect(overridden[member]).to eq(value) expect(overridden[member].object_id).to eq(value.object_id) end end end end context "when Encoding.default_external is POSIX (ASCII-7bit)" do before do Etc.expects(subject).with(*params).returns(ascii_user_struct) end let(:overridden) { PuppetSpec::CharacterEncoding.with_external_encoding(Encoding::ASCII) do Puppet::Etc.send(subject, *params) end } it "should not modify binary values that would be invalid UTF-8" do expect(overridden.name).to eq(euc_kr_as_binary) end it "should set the encoding to UTF-8 on binary values that would be valid UTF-8" do expect(overridden.passwd).to eq(mixed_utf_8) end it "should copy all values to the new struct object" do # Confirm we've actually copied all the values to the canonical_members ascii_user_struct.each_pair do |member, value| expect(overridden["canonical_#{member}"]).to eq(value) # Confirm we've reassigned all non-string and array values if !value.is_a?(String) && !value.is_a?(Array) expect(overridden[member]).to eq(value) expect(overridden[member].object_id).to eq(value.object_id) end end end end end describe :getgrent do it_should_behave_like "methods that return an overridden group struct from Etc" end describe :getgrnam do it_should_behave_like "methods that return an overridden group struct from Etc", 'foo' it "should call Etc.getgrnam with the supplied group name" do Etc.expects(:getgrnam).with('foo') Puppet::Etc.getgrnam('foo') end end describe :getgrgid do it_should_behave_like "methods that return an overridden group struct from Etc", 0 it "should call Etc.getgrgid with supplied group id" do Etc.expects(:getgrgid).with(0) Puppet::Etc.getgrgid(0) end end describe :getpwent do it_should_behave_like "methods that return an overridden user struct from Etc" end describe :getpwnam do it_should_behave_like "methods that return an overridden user struct from Etc", 'foo' it "should call Etc.getpwnam with that username" do Etc.expects(:getpwnam).with('foo') Puppet::Etc.getpwnam('foo') end end describe :getpwuid do it_should_behave_like "methods that return an overridden user struct from Etc", 2 it "should call Etc.getpwuid with the id" do Etc.expects(:getpwuid).with(2) Puppet::Etc.getpwuid(2) end end describe :group do it 'should return the next group struct if a block is not provided' do Puppet::Etc.expects(:getgrent).returns(ascii_group_struct) expect(Puppet::Etc.group).to eql(ascii_group_struct) end it 'should iterate over the available groups if a block is provided' do expected_groups = [ utf_8_group_struct, euc_kr_group_struct, ascii_group_struct ] Puppet::Etc.stubs(:getgrent).returns(*(expected_groups + [nil])) Puppet::Etc.expects(:setgrent) Puppet::Etc.expects(:endgrent) actual_groups = [] Puppet::Etc.group { |group| actual_groups << group } expect(actual_groups).to eql(expected_groups) end end describe "endgrent" do it "should call Etc.getgrent" do Etc.expects(:getgrent) Puppet::Etc.getgrent end end describe "setgrent" do it "should call Etc.setgrent" do Etc.expects(:setgrent) Puppet::Etc.setgrent end end describe "endpwent" do it "should call Etc.endpwent" do Etc.expects(:endpwent) Puppet::Etc.endpwent end end describe "setpwent" do it "should call Etc.setpwent" do Etc.expects(:setpwent) Puppet::Etc.setpwent end end end puppet-5.5.10/spec/unit/file_system_spec.rb0000644005276200011600000010310213417161722020603 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/file_system' require 'puppet/util/platform' describe "Puppet::FileSystem" do include PuppetSpec::Files # different UTF-8 widths # 1-byte A # 2-byte Űż - http://www.fileformat.info/info/unicode/char/06ff/index.htm - 0xDB 0xBF / 219 191 # 3-byte áš  - http://www.fileformat.info/info/unicode/char/16A0/index.htm - 0xE1 0x9A 0xA0 / 225 154 160 # 4-byte 𠜎 - http://www.fileformat.info/info/unicode/char/2070E/index.htm - 0xF0 0xA0 0x9C 0x8E / 240 160 156 142 let (:mixed_utf8) { "A\u06FF\u16A0\u{2070E}" } # Aۿᚠ𠜎 def with_file_content(content) path = tmpfile('file-system') file = File.new(path, 'wb') file.sync = true file.print content yield path ensure file.close end SYSTEM_SID_BYTES = [1, 1, 0, 0, 0, 0, 0, 5, 18, 0, 0, 0] def is_current_user_system? SYSTEM_SID_BYTES == Puppet::Util::Windows::ADSI::User.current_user_sid.sid_bytes end context "#open" do it "uses the same default mode as File.open, when specifying a nil mode (umask used on non-Windows)" do file = tmpfile('file_to_update') expect(Puppet::FileSystem.exist?(file)).to be_falsey Puppet::FileSystem.open(file, nil, 'a') { |fh| fh.write('') } expected_perms = Puppet::Util::Platform.windows? ? # default Windows mode based on temp file storage for SYSTEM user or regular user # for Jenkins or other services running as SYSTEM writing to c:\windows\temp # the permissions will typically be SYSTEM(F) / Administrators(F) which is 770 # but sometimes there are extra users like IIS_IUSRS granted rights which adds the "extra ace" 2 # for local Administrators writing to their own temp folders under c:\users\USER # they will have (F) for themselves, and Users will not have a permission, hence 700 (is_current_user_system? ? ['770', '2000770'] : '2000700') : # or for *nix determine expected mode via bitwise AND complement of umask (0100000 | 0666 & ~File.umask).to_s(8) expect([expected_perms].flatten).to include(Puppet::FileSystem.stat(file).mode.to_s(8)) default_file = tmpfile('file_to_update2') expect(Puppet::FileSystem.exist?(default_file)).to be_falsey File.open(default_file, 'a') { |fh| fh.write('') } # which matches the behavior of File.open expect(Puppet::FileSystem.stat(file).mode).to eq(Puppet::FileSystem.stat(default_file).mode) end it "can accept an octal mode integer" do file = tmpfile('file_to_update') # NOTE: 777 here returns 755, but due to Ruby? Puppet::FileSystem.open(file, 0444, 'a') { |fh| fh.write('') } # Behavior may change in the future on Windows, to *actually* change perms # but for now, setting a mode doesn't touch them expected_perms = Puppet::Util::Platform.windows? ? (is_current_user_system? ? ['770', '2000770'] : '2000700') : '100444' expect([expected_perms].flatten).to include(Puppet::FileSystem.stat(file).mode.to_s(8)) expected_ruby_mode = Puppet::Util::Platform.windows? ? # The Windows behavior has been changed to ignore the mode specified by open # given it's unlikely a caller expects Windows file attributes to be set # therefore mode is explicitly not managed (until PUP-6959 is fixed) # # In default Ruby on Windows a mode controls file attribute setting # (like archive, read-only, etc) # The GetFileInformationByHandle API returns an attributes value that is # a bitmask of Windows File Attribute Constants at # https://msdn.microsoft.com/en-us/library/windows/desktop/gg258117(v=vs.85).aspx '100644' : # On other platforms, the mode should be what was set by octal 0444 '100444' expect(File.stat(file).mode.to_s(8)).to eq(expected_ruby_mode) end it "cannot accept a mode string" do file = tmpfile('file_to_update') expect { Puppet::FileSystem.open(file, "444", 'a') { |fh| fh.write('') } }.to raise_error(TypeError) end it "opens, creates ands allows updating of a new file, using by default, the external system encoding" do begin original_encoding = Encoding.default_external # this must be set through Ruby API and cannot be mocked - it sets internal state used by File.open # pick a bizarre encoding unlikely to be used in any real tests Encoding.default_external = Encoding::CP737 file = tmpfile('file_to_update') # test writing a UTF-8 string when Default external encoding is something different Puppet::FileSystem.open(file, 0660, 'w') do |fh| # note Ruby behavior which has no external_encoding, but implicitly uses Encoding.default_external expect(fh.external_encoding).to be_nil # write a UTF-8 string to this file fh.write(mixed_utf8) end # prove that Ruby implicitly converts read strings back to Encoding.default_external # and that it did that in the previous write written = Puppet::FileSystem.read(file) expect(written.encoding).to eq(Encoding.default_external) expect(written).to eq(mixed_utf8.force_encoding(Encoding.default_external)) ensure # carefully roll back to the previous Encoding.default_external = original_encoding end end end context "#exclusive_open" do it "opens ands allows updating of an existing file" do file = file_containing("file_to_update", "the contents") Puppet::FileSystem.exclusive_open(file, 0660, 'r+') do |fh| old = fh.read fh.truncate(0) fh.rewind fh.write("updated #{old}") end expect(Puppet::FileSystem.read(file)).to eq("updated the contents") end it "opens, creates ands allows updating of a new file, using by default, the external system encoding" do begin original_encoding = Encoding.default_external # this must be set through Ruby API and cannot be mocked - it sets internal state used by File.open # pick a bizarre encoding unlikely to be used in any real tests Encoding.default_external = Encoding::CP737 file = tmpfile('file_to_update') # test writing a UTF-8 string when Default external encoding is something different Puppet::FileSystem.exclusive_open(file, 0660, 'w') do |fh| # note Ruby behavior which has no external_encoding, but implicitly uses Encoding.default_external expect(fh.external_encoding).to be_nil # write a UTF-8 string to this file fh.write(mixed_utf8) end # prove that Ruby implicitly converts read strings back to Encoding.default_external # and that it did that in the previous write written = Puppet::FileSystem.read(file) expect(written.encoding).to eq(Encoding.default_external) expect(written).to eq(mixed_utf8.force_encoding(Encoding.default_external)) ensure # carefully roll back to the previous Encoding.default_external = original_encoding end end it "excludes other processes from updating at the same time", :unless => Puppet::Util::Platform.windows? do file = file_containing("file_to_update", "0") increment_counter_in_multiple_processes(file, 5, 'r+') expect(Puppet::FileSystem.read(file)).to eq("5") end it "excludes other processes from updating at the same time even when creating the file", :unless => Puppet::Util::Platform.windows? do file = tmpfile("file_to_update") increment_counter_in_multiple_processes(file, 5, 'a+') expect(Puppet::FileSystem.read(file)).to eq("5") end it "times out if the lock cannot be acquired in a specified amount of time", :unless => Puppet::Util::Platform.windows? do file = tmpfile("file_to_update") child = spawn_process_that_locks(file) expect do Puppet::FileSystem.exclusive_open(file, 0666, 'a', 0.1) do |f| end end.to raise_error(Timeout::Error) Process.kill(9, child) end def spawn_process_that_locks(file) read, write = IO.pipe child = Kernel.fork do read.close Puppet::FileSystem.exclusive_open(file, 0666, 'a') do |fh| write.write(true) write.close sleep 10 end end write.close read.read read.close child end def increment_counter_in_multiple_processes(file, num_procs, options) children = [] num_procs.times do children << Kernel.fork do Puppet::FileSystem.exclusive_open(file, 0660, options) do |fh| fh.rewind contents = (fh.read || 0).to_i fh.truncate(0) fh.rewind fh.write((contents + 1).to_s) end exit(0) end end children.each { |pid| Process.wait(pid) } end end context "read_preserve_line_endings" do it "should read a file with line feed" do with_file_content("file content \n") do |file| expect(Puppet::FileSystem.read_preserve_line_endings(file)).to eq("file content \n") end end it "should read a file with carriage return line feed" do with_file_content("file content \r\n") do |file| expect(Puppet::FileSystem.read_preserve_line_endings(file)).to eq("file content \r\n") end end it "should read a mixed file using only the first line newline when lf" do with_file_content("file content \nsecond line \r\n") do |file| expect(Puppet::FileSystem.read_preserve_line_endings(file)).to eq("file content \nsecond line \r\n") end end it "should read a mixed file using only the first line newline when crlf" do with_file_content("file content \r\nsecond line \n") do |file| expect(Puppet::FileSystem.read_preserve_line_endings(file)).to eq("file content \r\nsecond line \n") end end end context "read without an encoding specified" do it "returns strings as Encoding.default_external" do temp_file = file_containing('test.txt', 'hello world') contents = Puppet::FileSystem.read(temp_file) expect(contents.encoding).to eq(Encoding.default_external) expect(contents).to eq('hello world') end end context "read should allow an encoding to be specified" do # First line of Rune version of Rune poem at http://www.columbia.edu/~fdc/utf8/ # characters chosen since they will not parse on Windows with codepage 437 or 1252 # Section 3.2.1.3 of Ruby spec guarantees that \u strings are encoded as UTF-8 let (:rune_utf8) { "\u16A0\u16C7\u16BB" } # 'ᚠᛇᚻ' it "and should read a UTF8 file properly" do temp_file = file_containing('utf8.txt', rune_utf8) contents = Puppet::FileSystem.read(temp_file, :encoding => 'utf-8') expect(contents.encoding).to eq(Encoding::UTF_8) expect(contents).to eq(rune_utf8) end it "does not strip the UTF8 BOM (Byte Order Mark) if present in a file" do bom = "\uFEFF" temp_file = file_containing('utf8bom.txt', "#{bom}#{rune_utf8}") contents = Puppet::FileSystem.read(temp_file, :encoding => 'utf-8') expect(contents.encoding).to eq(Encoding::UTF_8) expect(contents).to eq("#{bom}#{rune_utf8}") end end describe "symlink", :if => ! Puppet.features.manages_symlinks? && Puppet.features.microsoft_windows? do let(:file) { tmpfile("somefile") } let(:missing_file) { tmpfile("missingfile") } let(:expected_msg) { "This version of Windows does not support symlinks. Windows Vista / 2008 or higher is required." } before :each do FileUtils.touch(file) end it "should raise an error when trying to create a symlink" do expect { Puppet::FileSystem.symlink(file, 'foo') }.to raise_error(Puppet::Util::Windows::Error) end it "should return false when trying to check if a path is a symlink" do expect(Puppet::FileSystem.symlink?(file)).to be_falsey end it "should raise an error when trying to read a symlink" do expect { Puppet::FileSystem.readlink(file) }.to raise_error(Puppet::Util::Windows::Error) end it "should return a File::Stat instance when calling stat on an existing file" do expect(Puppet::FileSystem.stat(file)).to be_instance_of(File::Stat) end it "should raise Errno::ENOENT when calling stat on a missing file" do expect { Puppet::FileSystem.stat(missing_file) }.to raise_error(Errno::ENOENT) end it "should fall back to stat when trying to lstat a file" do Puppet::Util::Windows::File.expects(:stat).with(Puppet::FileSystem.assert_path(file)) Puppet::FileSystem.lstat(file) end end describe "symlink", :if => Puppet.features.manages_symlinks? do let(:file) { tmpfile("somefile") } let(:missing_file) { tmpfile("missingfile") } let(:dir) { tmpdir("somedir") } before :each do FileUtils.touch(file) end it "should return true for exist? on a present file" do expect(Puppet::FileSystem.exist?(file)).to be_truthy end it "should return true for file? on a present file" do expect(Puppet::FileSystem.file?(file)).to be_truthy end it "should return false for exist? on a non-existent file" do expect(Puppet::FileSystem.exist?(missing_file)).to be_falsey end it "should return true for exist? on a present directory" do expect(Puppet::FileSystem.exist?(dir)).to be_truthy end it "should return false for exist? on a dangling symlink" do symlink = tmpfile("somefile_link") Puppet::FileSystem.symlink(missing_file, symlink) expect(Puppet::FileSystem.exist?(missing_file)).to be_falsey expect(Puppet::FileSystem.exist?(symlink)).to be_falsey end it "should return true for exist? on valid symlinks" do [file, dir].each do |target| symlink = tmpfile("#{Puppet::FileSystem.basename(target).to_s}_link") Puppet::FileSystem.symlink(target, symlink) expect(Puppet::FileSystem.exist?(target)).to be_truthy expect(Puppet::FileSystem.exist?(symlink)).to be_truthy end end it "should return false for exist? when resolving a cyclic symlink chain" do # point symlink -> file symlink = tmpfile("somefile_link") Puppet::FileSystem.symlink(file, symlink) # point symlink2 -> symlink symlink2 = tmpfile("somefile_link2") Puppet::FileSystem.symlink(symlink, symlink2) # point symlink3 -> symlink2 symlink3 = tmpfile("somefile_link3") Puppet::FileSystem.symlink(symlink2, symlink3) # yank file, temporarily dangle ::File.delete(file) # and trash it so that we can recreate it OK on windows Puppet::FileSystem.unlink(symlink) # point symlink -> symlink3 to create a cycle Puppet::FileSystem.symlink(symlink3, symlink) expect(Puppet::FileSystem.exist?(symlink3)).to be_falsey end it "should return true for exist? when resolving a symlink chain pointing to a file" do # point symlink -> file symlink = tmpfile("somefile_link") Puppet::FileSystem.symlink(file, symlink) # point symlink2 -> symlink symlink2 = tmpfile("somefile_link2") Puppet::FileSystem.symlink(symlink, symlink2) # point symlink3 -> symlink2 symlink3 = tmpfile("somefile_link3") Puppet::FileSystem.symlink(symlink2, symlink3) expect(Puppet::FileSystem.exist?(symlink3)).to be_truthy end it "should return false for exist? when resolving a symlink chain that dangles" do # point symlink -> file symlink = tmpfile("somefile_link") Puppet::FileSystem.symlink(file, symlink) # point symlink2 -> symlink symlink2 = tmpfile("somefile_link2") Puppet::FileSystem.symlink(symlink, symlink2) # point symlink3 -> symlink2 symlink3 = tmpfile("somefile_link3") Puppet::FileSystem.symlink(symlink2, symlink3) # yank file, and make symlink dangle ::File.delete(file) # symlink3 is now indirectly dangled expect(Puppet::FileSystem.exist?(symlink3)).to be_falsey end it "should not create a symlink when the :noop option is specified" do [file, dir].each do |target| symlink = tmpfile("#{Puppet::FileSystem.basename(target)}_link") Puppet::FileSystem.symlink(target, symlink, { :noop => true }) expect(Puppet::FileSystem.exist?(target)).to be_truthy expect(Puppet::FileSystem.exist?(symlink)).to be_falsey end end it "should raise Errno::EEXIST if trying to create a file / directory symlink when the symlink path already exists as a file" do existing_file = tmpfile("#{Puppet::FileSystem.basename(file)}_link") FileUtils.touch(existing_file) [file, dir].each do |target| expect { Puppet::FileSystem.symlink(target, existing_file) }.to raise_error(Errno::EEXIST) expect(Puppet::FileSystem.exist?(existing_file)).to be_truthy expect(Puppet::FileSystem.symlink?(existing_file)).to be_falsey end end it "should silently fail if trying to create a file / directory symlink when the symlink path already exists as a directory" do existing_dir = tmpdir("#{Puppet::FileSystem.basename(file)}_dir") [file, dir].each do |target| expect(Puppet::FileSystem.symlink(target, existing_dir)).to eq(0) expect(Puppet::FileSystem.exist?(existing_dir)).to be_truthy expect(File.directory?(existing_dir)).to be_truthy expect(Puppet::FileSystem.symlink?(existing_dir)).to be_falsey end end it "should silently fail to modify an existing directory symlink to reference a new file or directory" do [file, dir].each do |target| existing_dir = tmpdir("#{Puppet::FileSystem.basename(target)}_dir") symlink = tmpfile("#{Puppet::FileSystem.basename(existing_dir)}_link") Puppet::FileSystem.symlink(existing_dir, symlink) expect(Puppet::FileSystem.readlink(symlink)).to eq(Puppet::FileSystem.path_string(existing_dir)) # now try to point it at the new target, no error raised, but file system unchanged expect(Puppet::FileSystem.symlink(target, symlink)).to eq(0) expect(Puppet::FileSystem.readlink(symlink)).to eq(existing_dir.to_s) end end it "should raise Errno::EEXIST if trying to modify a file symlink to reference a new file or directory" do symlink = tmpfile("#{Puppet::FileSystem.basename(file)}_link") file_2 = tmpfile("#{Puppet::FileSystem.basename(file)}_2") FileUtils.touch(file_2) # symlink -> file_2 Puppet::FileSystem.symlink(file_2, symlink) [file, dir].each do |target| expect { Puppet::FileSystem.symlink(target, symlink) }.to raise_error(Errno::EEXIST) expect(Puppet::FileSystem.readlink(symlink)).to eq(file_2.to_s) end end it "should delete the existing file when creating a file / directory symlink with :force when the symlink path exists as a file" do [file, dir].each do |target| existing_file = tmpfile("#{Puppet::FileSystem.basename(target)}_existing") FileUtils.touch(existing_file) expect(Puppet::FileSystem.symlink?(existing_file)).to be_falsey Puppet::FileSystem.symlink(target, existing_file, { :force => true }) expect(Puppet::FileSystem.symlink?(existing_file)).to be_truthy expect(Puppet::FileSystem.readlink(existing_file)).to eq(target.to_s) end end it "should modify an existing file symlink when using :force to reference a new file or directory" do [file, dir].each do |target| existing_file = tmpfile("#{Puppet::FileSystem.basename(target)}_existing") FileUtils.touch(existing_file) existing_symlink = tmpfile("#{Puppet::FileSystem.basename(existing_file)}_link") Puppet::FileSystem.symlink(existing_file, existing_symlink) expect(Puppet::FileSystem.readlink(existing_symlink)).to eq(existing_file.to_s) Puppet::FileSystem.symlink(target, existing_symlink, { :force => true }) expect(Puppet::FileSystem.readlink(existing_symlink)).to eq(target.to_s) end end it "should silently fail if trying to overwrite an existing directory with a new symlink when using :force to reference a file or directory" do [file, dir].each do |target| existing_dir = tmpdir("#{Puppet::FileSystem.basename(target)}_existing") expect(Puppet::FileSystem.symlink(target, existing_dir, { :force => true })).to eq(0) expect(Puppet::FileSystem.symlink?(existing_dir)).to be_falsey end end it "should silently fail if trying to modify an existing directory symlink when using :force to reference a new file or directory" do [file, dir].each do |target| existing_dir = tmpdir("#{Puppet::FileSystem.basename(target)}_existing") existing_symlink = tmpfile("#{Puppet::FileSystem.basename(existing_dir)}_link") Puppet::FileSystem.symlink(existing_dir, existing_symlink) expect(Puppet::FileSystem.readlink(existing_symlink)).to eq(existing_dir.to_s) expect(Puppet::FileSystem.symlink(target, existing_symlink, { :force => true })).to eq(0) expect(Puppet::FileSystem.readlink(existing_symlink)).to eq(existing_dir.to_s) end end it "should accept a string, Pathname or object with to_str (Puppet::Util::WatchedFile) for exist?" do [ tmpfile('bogus1'), Pathname.new(tmpfile('bogus2')), Puppet::Util::WatchedFile.new(tmpfile('bogus3')) ].each { |f| expect(Puppet::FileSystem.exist?(f)).to be_falsey } end it "should return a File::Stat instance when calling stat on an existing file" do expect(Puppet::FileSystem.stat(file)).to be_instance_of(File::Stat) end it "should raise Errno::ENOENT when calling stat on a missing file" do expect { Puppet::FileSystem.stat(missing_file) }.to raise_error(Errno::ENOENT) end it "should be able to create a symlink, and verify it with symlink?" do symlink = tmpfile("somefile_link") Puppet::FileSystem.symlink(file, symlink) expect(Puppet::FileSystem.symlink?(symlink)).to be_truthy end it "should report symlink? as false on file, directory and missing files" do [file, dir, missing_file].each do |f| expect(Puppet::FileSystem.symlink?(f)).to be_falsey end end it "should return a File::Stat with ftype 'link' when calling lstat on a symlink pointing to existing file" do symlink = tmpfile("somefile_link") Puppet::FileSystem.symlink(file, symlink) stat = Puppet::FileSystem.lstat(symlink) expect(stat).to be_instance_of(File::Stat) expect(stat.ftype).to eq('link') end it "should return a File::Stat of ftype 'link' when calling lstat on a symlink pointing to missing file" do symlink = tmpfile("somefile_link") Puppet::FileSystem.symlink(missing_file, symlink) stat = Puppet::FileSystem.lstat(symlink) expect(stat).to be_instance_of(File::Stat) expect(stat.ftype).to eq('link') end it "should return a File::Stat of ftype 'file' when calling stat on a symlink pointing to existing file" do symlink = tmpfile("somefile_link") Puppet::FileSystem.symlink(file, symlink) stat = Puppet::FileSystem.stat(symlink) expect(stat).to be_instance_of(File::Stat) expect(stat.ftype).to eq('file') end it "should return a File::Stat of ftype 'directory' when calling stat on a symlink pointing to existing directory" do symlink = tmpfile("somefile_link") Puppet::FileSystem.symlink(dir, symlink) stat = Puppet::FileSystem.stat(symlink) expect(stat).to be_instance_of(File::Stat) expect(stat.ftype).to eq('directory') # on Windows, this won't get cleaned up if still linked Puppet::FileSystem.unlink(symlink) end it "should return a File::Stat of ftype 'file' when calling stat on a symlink pointing to another symlink" do # point symlink -> file symlink = tmpfile("somefile_link") Puppet::FileSystem.symlink(file, symlink) # point symlink2 -> symlink symlink2 = tmpfile("somefile_link2") Puppet::FileSystem.symlink(symlink, symlink2) expect(Puppet::FileSystem.stat(symlink2).ftype).to eq('file') end it "should raise Errno::ENOENT when calling stat on a dangling symlink" do symlink = tmpfile("somefile_link") Puppet::FileSystem.symlink(missing_file, symlink) expect { Puppet::FileSystem.stat(symlink) }.to raise_error(Errno::ENOENT) end it "should be able to readlink to resolve the physical path to a symlink" do symlink = tmpfile("somefile_link") Puppet::FileSystem.symlink(file, symlink) expect(Puppet::FileSystem.exist?(file)).to be_truthy expect(Puppet::FileSystem.readlink(symlink)).to eq(file.to_s) end it "should not resolve entire symlink chain with readlink on a symlink'd symlink" do # point symlink -> file symlink = tmpfile("somefile_link") Puppet::FileSystem.symlink(file, symlink) # point symlink2 -> symlink symlink2 = tmpfile("somefile_link2") Puppet::FileSystem.symlink(symlink, symlink2) expect(Puppet::FileSystem.exist?(file)).to be_truthy expect(Puppet::FileSystem.readlink(symlink2)).to eq(symlink.to_s) end it "should be able to readlink to resolve the physical path to a dangling symlink" do symlink = tmpfile("somefile_link") Puppet::FileSystem.symlink(missing_file, symlink) expect(Puppet::FileSystem.exist?(missing_file)).to be_falsey expect(Puppet::FileSystem.readlink(symlink)).to eq(missing_file.to_s) end it "should be able to unlink a dangling symlink pointed at a file" do symlink = tmpfile("somefile_link") Puppet::FileSystem.symlink(file, symlink) ::File.delete(file) Puppet::FileSystem.unlink(symlink) expect(Puppet::FileSystem).to_not be_exist(file) expect(Puppet::FileSystem).to_not be_exist(symlink) end it "should be able to unlink a dangling symlink pointed at a directory" do symlink = tmpfile("somedir_link") Puppet::FileSystem.symlink(dir, symlink) Dir.rmdir(dir) Puppet::FileSystem.unlink(symlink) expect(Puppet::FileSystem).to_not be_exist(dir) expect(Puppet::FileSystem).to_not be_exist(symlink) end it "should delete only the symlink and not the target when calling unlink instance method" do [file, dir].each do |target| symlink = tmpfile("#{Puppet::FileSystem.basename(target)}_link") Puppet::FileSystem.symlink(target, symlink) expect(Puppet::FileSystem.exist?(target)).to be_truthy expect(Puppet::FileSystem.readlink(symlink)).to eq(target.to_s) expect(Puppet::FileSystem.unlink(symlink)).to eq(1) # count of files expect(Puppet::FileSystem.exist?(target)).to be_truthy expect(Puppet::FileSystem.exist?(symlink)).to be_falsey end end it "should delete only the symlink and not the target when calling unlink class method" do [file, dir].each do |target| symlink = tmpfile("#{Puppet::FileSystem.basename(target)}_link") Puppet::FileSystem.symlink(target, symlink) expect(Puppet::FileSystem.exist?(target)).to be_truthy expect(Puppet::FileSystem.readlink(symlink)).to eq(target.to_s) expect(Puppet::FileSystem.unlink(symlink)).to eq(1) # count of files expect(Puppet::FileSystem.exist?(target)).to be_truthy expect(Puppet::FileSystem.exist?(symlink)).to be_falsey end end describe "unlink" do it "should delete files with unlink" do expect(Puppet::FileSystem.exist?(file)).to be_truthy expect(Puppet::FileSystem.unlink(file)).to eq(1) # count of files expect(Puppet::FileSystem.exist?(file)).to be_falsey end it "should delete files with unlink class method" do expect(Puppet::FileSystem.exist?(file)).to be_truthy expect(Puppet::FileSystem.unlink(file)).to eq(1) # count of files expect(Puppet::FileSystem.exist?(file)).to be_falsey end it "should delete multiple files with unlink class method" do paths = (1..3).collect do |i| f = tmpfile("somefile_#{i}") FileUtils.touch(f) expect(Puppet::FileSystem.exist?(f)).to be_truthy f.to_s end expect(Puppet::FileSystem.unlink(*paths)).to eq(3) # count of files paths.each { |p| expect(Puppet::FileSystem.exist?(p)).to be_falsey } end it "should raise Errno::EPERM or Errno::EISDIR when trying to delete a directory with the unlink class method" do expect(Puppet::FileSystem.exist?(dir)).to be_truthy ex = nil begin Puppet::FileSystem.unlink(dir) rescue Exception => e ex = e end expect([ Errno::EPERM, # Windows and OSX Errno::EISDIR # Linux ]).to include(ex.class) expect(Puppet::FileSystem.exist?(dir)).to be_truthy end end describe "exclusive_create" do it "should create a file that doesn't exist" do expect(Puppet::FileSystem.exist?(missing_file)).to be_falsey Puppet::FileSystem.exclusive_create(missing_file, nil) {} expect(Puppet::FileSystem.exist?(missing_file)).to be_truthy end it "should raise Errno::EEXIST creating a file that does exist" do expect(Puppet::FileSystem.exist?(file)).to be_truthy expect do Puppet::FileSystem.exclusive_create(file, nil) {} end.to raise_error(Errno::EEXIST) end end describe 'expand_path' do it 'should raise an error when given nil, like Ruby File.expand_path' do expect { File.expand_path(nil) }.to raise_error(TypeError) # match Ruby behavior expect { Puppet::FileSystem.expand_path(nil) }.to raise_error(TypeError) end it 'with an expanded path passed to Dir.glob, the same expanded path will be returned' do # this exists specifically for Puppet::Pops::Loader::ModuleLoaders::FileBased#add_to_index # which should receive an expanded path value from it's parent Environment # and will later compare values generated by Dir.glob tmp_long_file = tmpfile('foo.bar', tmpdir('super-long-thing-that-Windows-shortens')) Puppet::FileSystem.touch(tmp_long_file) expanded_path = Puppet::FileSystem.expand_path(tmp_long_file) expect(expanded_path).to eq(Dir.glob(expanded_path).first) end describe 'on non-Windows', :unless => Puppet::Util::Platform.windows? do it 'should produce the same results as the Ruby File.expand_path' do # on Windows this may be 8.3 style, but not so on other platforms # only done since ::File.expects(:expand_path).with(path).at_least_once # cannot be used since it will cause a stack overflow path = tmpdir('foobar') expect(Puppet::FileSystem.expand_path(path)).to eq(File.expand_path(path)) end end describe 'on Windows', :if => Puppet::Util::Platform.windows? do let(:nonexist_file) { 'C:\\file~1.ext' } let(:nonexist_path) { 'C:\\progra~1\\missing\\path\\file.ext' } ['/', '\\'].each do |slash| it "should return the absolute path including system drive letter when given #{slash}, like Ruby File.expand_path" do # regardless of slash direction, return value is drive letter expanded = Puppet::FileSystem.expand_path(slash) expect(expanded).to eq(ENV['SystemDrive'] + File::SEPARATOR) expect(expanded).to eq(File.expand_path(slash)) end end it 'should behave like Rubys File.expand_path for a file that doesnt exist' do expect(Puppet::FileSystem.exist?(nonexist_file)).to be_falsey # this will change c:\\file~1.ext to c:/file~1.ext (existing Ruby behavior), but not expand any ~ ruby_expanded = File.expand_path(nonexist_file) expect(ruby_expanded).to match(/~/) expect(Puppet::FileSystem.expand_path(nonexist_file)).to eq(ruby_expanded) end it 'should behave like Rubys File.expand_path for a file with a parent path that doesnt exist' do expect(Puppet::FileSystem.exist?(nonexist_path)).to be_falsey # this will change c:\\progra~1 to c:/progra~1 (existing Ruby behavior), but not expand any ~ ruby_expanded = File.expand_path(nonexist_path) expect(ruby_expanded).to match(/~/) expect(Puppet::FileSystem.expand_path(nonexist_path)).to eq(ruby_expanded) end it 'should expand a shortened path completely, unlike Ruby File.expand_path' do tmp_long_dir = tmpdir('super-long-thing-that-Windows-shortens') short_path = Puppet::Util::Windows::File.get_short_pathname(tmp_long_dir) # a shortened path to the temp dir will have a least 2 ~ # for instance, C:\\Users\\Administrator\\AppData\\Local\\Temp\\rspecrun2016####-####-#######\\super-long-thing-that-Windows-shortens\ # or C:\\Windows\\Temp\\rspecrun2016####-####-#######\\super-long-thing-that-Windows-shortens\ # will shorten to Temp\\rspecr~#\\super-~1 expect(short_path).to match(/~.*~/) # expand with Ruby, noting not all ~ have been expanded # which is the primary reason that a Puppet helper exists ruby_expanded = File.expand_path(short_path) expect(ruby_expanded).to match(/~/) # Puppet expansion uses the Windows API and has no ~ remaining puppet_expanded = Puppet::FileSystem.expand_path(short_path) expect(puppet_expanded).to_not match(/~/) # and the directories are one and the same expect(File.identical?(short_path, puppet_expanded)).to be_truthy end end end end end puppet-5.5.10/spec/unit/functions4_spec.rb0000644005276200011600000010203413417161722020357 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet/pops' require 'puppet/loaders' require 'puppet_spec/pops' require 'puppet_spec/scope' module FunctionAPISpecModule class TestDuck end class TestFunctionLoader < Puppet::Pops::Loader::StaticLoader def initialize @constants = {} end def add_function(name, function) set_entry(Puppet::Pops::Loader::TypedName.new(:function, name), function, __FILE__) end def add_type(name, type) set_entry(Puppet::Pops::Loader::TypedName.new(:type, name), type, __FILE__) end def set_entry(typed_name, value, origin = nil) @constants[typed_name] = Puppet::Pops::Loader::Loader::NamedEntry.new(typed_name, value, origin) end # override StaticLoader def load_constant(typed_name) @constants[typed_name] end end end describe 'the 4x function api' do include FunctionAPISpecModule include PuppetSpec::Pops include PuppetSpec::Scope let(:loader) { FunctionAPISpecModule::TestFunctionLoader.new } it 'allows a simple function to be created without dispatch declaration' do f = Puppet::Functions.create_function('min') do def min(x,y) x <= y ? x : y end end # the produced result is a Class inheriting from Function expect(f.class).to be(Class) expect(f.superclass).to be(Puppet::Functions::Function) # and this class had the given name (not a real Ruby class name) expect(f.name).to eql('min') end it 'refuses to create functions that are not based on the Function class' do expect do Puppet::Functions.create_function('testing', Object) {} end.to raise_error(ArgumentError, /function 'testing'.*Functions must be based on Puppet::Pops::Functions::Function. Got Object/) end it 'refuses to create functions with parameters that are not named with a symbol' do expect do Puppet::Functions.create_function('testing') do dispatch :test do param 'Integer', 'not_symbol' end def test(x) end end end.to raise_error(ArgumentError, /Parameter name argument must be a Symbol/) end it 'a function without arguments can be defined and called without dispatch declaration' do f = create_noargs_function_class() func = f.new(:closure_scope, :loader) expect(func.call({})).to eql(10) end it 'an error is raised when calling a no arguments function with arguments' do f = create_noargs_function_class() func = f.new(:closure_scope, :loader) expect{func.call({}, 'surprise')}.to raise_error(ArgumentError, "'test' expects no arguments, got 1") end it 'a simple function can be called' do f = create_min_function_class() # TODO: Bogus parameters, not yet used func = f.new(:closure_scope, :loader) expect(func.is_a?(Puppet::Functions::Function)).to be_truthy expect(func.call({}, 10,20)).to eql(10) end it 'an error is raised if called with too few arguments' do f = create_min_function_class() # TODO: Bogus parameters, not yet used func = f.new(:closure_scope, :loader) expect(func.is_a?(Puppet::Functions::Function)).to be_truthy expect do func.call({}, 10) end.to raise_error(ArgumentError, "'min' expects 2 arguments, got 1") end it 'an error is raised if called with too many arguments' do f = create_min_function_class() # TODO: Bogus parameters, not yet used func = f.new(:closure_scope, :loader) expect(func.is_a?(Puppet::Functions::Function)).to be_truthy expect do func.call({}, 10, 10, 10) end.to raise_error(ArgumentError, "'min' expects 2 arguments, got 3") end it 'correct dispatch is chosen when zero parameter dispatch exists' do f = create_function_with_no_parameter_dispatch func = f.new(:closure_scope, :loader) expect(func.is_a?(Puppet::Functions::Function)).to be_truthy expect(func.call({}, 1)).to eql(1) end it 'an error is raised if simple function-name and method are not matched' do expect do create_badly_named_method_function_class() end.to raise_error(ArgumentError, /Function Creation Error, cannot create a default dispatcher for function 'mix', no method with this name found/) end it 'the implementation separates dispatchers for different functions' do # this tests that meta programming / construction puts class attributes in the correct class f1 = create_min_function_class() f2 = create_max_function_class() d1 = f1.dispatcher d2 = f2.dispatcher expect(d1).to_not eql(d2) expect(d1.dispatchers[0]).to_not eql(d2.dispatchers[0]) end context 'when using regular dispatch' do it 'a function can be created using dispatch and called' do f = create_min_function_class_using_dispatch() func = f.new(:closure_scope, :loader) expect(func.call({}, 3,4)).to eql(3) end it 'an error is raised with reference to given parameter names when called with mis-matched arguments' do f = create_min_function_class_using_dispatch() # TODO: Bogus parameters, not yet used func = f.new(:closure_scope, :loader) expect(func.is_a?(Puppet::Functions::Function)).to be_truthy expect do func.call({}, 10, 'ten') end.to raise_error(ArgumentError, "'min' parameter 'b' expects a Numeric value, got String") end it 'an error includes optional indicators for last element' do f = create_function_with_optionals_and_repeated_via_multiple_dispatch() # TODO: Bogus parameters, not yet used func = f.new(:closure_scope, :loader) expect(func.is_a?(Puppet::Functions::Function)).to be_truthy expect do func.call({}, 3, 10, 3, "4") end.to raise_error(ArgumentError, "'min' expects one of: (Numeric x, Numeric y, Numeric a?, Numeric b?, Numeric c*) rejected: parameter 'b' expects a Numeric value, got String (String x, String y, String a+) rejected: parameter 'x' expects a String value, got Integer") end it 'can create optional repeated parameter' do f = create_function_with_repeated func = f.new(:closure_scope, :loader) expect(func.call({})).to eql(0) expect(func.call({}, 1)).to eql(1) expect(func.call({}, 1, 2)).to eql(2) f = create_function_with_optional_repeated func = f.new(:closure_scope, :loader) expect(func.call({})).to eql(0) expect(func.call({}, 1)).to eql(1) expect(func.call({}, 1, 2)).to eql(2) end it 'can create required repeated parameter' do f = create_function_with_required_repeated func = f.new(:closure_scope, :loader) expect(func.call({}, 1)).to eql(1) expect(func.call({}, 1, 2)).to eql(2) expect { func.call({}) }.to raise_error(ArgumentError, "'count_args' expects at least 1 argument, got none") end it 'can create scope_param followed by repeated parameter' do f = create_function_with_scope_param_required_repeat func = f.new(:closure_scope, :loader) expect(func.call({}, 'yay', 1,2,3)).to eql([{}, 'yay',1,2,3]) end it 'a function can use inexact argument mapping' do f = create_function_with_inexact_dispatch func = f.new(:closure_scope, :loader) expect(func.call({}, 3.0,4.0,5.0)).to eql([Float, Float, Float]) expect(func.call({}, 'Apple', 'Banana')).to eql([String, String]) end it 'a function can be created using dispatch and called' do f = create_min_function_class_disptaching_to_two_methods() func = f.new(:closure_scope, :loader) expect(func.call({}, 3,4)).to eql(3) expect(func.call({}, 'Apple', 'Banana')).to eql('Apple') end it 'a function can not be created with parameters declared after a repeated parameter' do expect { create_function_with_param_after_repeated }.to raise_error(ArgumentError, /function 't1'.*Parameters cannot be added after a repeated parameter/) end it 'a function can not be created with required parameters declared after optional ones' do expect { create_function_with_rq_after_opt }.to raise_error(ArgumentError, /function 't1'.*A required parameter cannot be added after an optional parameter/) end it 'a function can not be created with required repeated parameters declared after optional ones' do expect { create_function_with_rq_repeated_after_opt }.to raise_error(ArgumentError, /function 't1'.*A required repeated parameter cannot be added after an optional parameter/) end it 'an error is raised with reference to multiple methods when called with mis-matched arguments' do f = create_min_function_class_disptaching_to_two_methods() # TODO: Bogus parameters, not yet used func = f.new(:closure_scope, :loader) expect(func.is_a?(Puppet::Functions::Function)).to be_truthy expect do func.call({}, 10, '20') end.to raise_error(ArgumentError, "'min' expects one of: (Numeric a, Numeric b) rejected: parameter 'b' expects a Numeric value, got String (String s1, String s2) rejected: parameter 's1' expects a String value, got Integer") end context 'an argument_mismatch handler' do let(:func) { create_function_with_mismatch_handler.new(:closure_scope, :loader) } it 'is called on matching arguments' do expect { func.call({}, '1') }.to raise_error(ArgumentError, "'test' It's not OK to pass a string") end it 'is not called unless arguments are matching' do expect { func.call({}, '1', 3) }.to raise_error(ArgumentError, "'test' expects 1 argument, got 2") end it 'is not included in a signature mismatch description' do expect { func.call({}, 2.3) }.to raise_error { |e| expect(e.message).not_to match(/String/) } end end context 'when requesting a type' do it 'responds with a Callable for a single signature' do tf = Puppet::Pops::Types::TypeFactory fc = create_min_function_class_using_dispatch() t = fc.dispatcher.to_type expect(t.class).to be(Puppet::Pops::Types::PCallableType) expect(t.param_types.class).to be(Puppet::Pops::Types::PTupleType) expect(t.param_types.types).to eql([tf.numeric(), tf.numeric()]) expect(t.block_type).to be_nil end it 'responds with a Variant[Callable...] for multiple signatures' do tf = Puppet::Pops::Types::TypeFactory fc = create_min_function_class_disptaching_to_two_methods() t = fc.dispatcher.to_type expect(t.class).to be(Puppet::Pops::Types::PVariantType) expect(t.types.size).to eql(2) t1 = t.types[0] expect(t1.param_types.class).to be(Puppet::Pops::Types::PTupleType) expect(t1.param_types.types).to eql([tf.numeric(), tf.numeric()]) expect(t1.block_type).to be_nil t2 = t.types[1] expect(t2.param_types.class).to be(Puppet::Pops::Types::PTupleType) expect(t2.param_types.types).to eql([tf.string(), tf.string()]) expect(t2.block_type).to be_nil end end context 'supports lambdas' do it 'such that, a required block can be defined and given as an argument' do the_function = create_function_with_required_block_all_defaults().new(:closure_scope, :loader) result = the_function.call({}, 7) { |a,b| a < b ? a : b } expect(result).to eq(7) end it 'such that, a missing required block when called raises an error' do the_function = create_function_with_required_block_all_defaults().new(:closure_scope, :loader) expect do the_function.call({}, 10) end.to raise_error(ArgumentError, "'test' expects a block") end it 'such that, an optional block can be defined and given as an argument' do the_function = create_function_with_optional_block_all_defaults().new(:closure_scope, :loader) result = the_function.call({}, 4) { |a,b| a < b ? a : b } expect(result).to eql(4) end it 'such that, an optional block can be omitted when called and gets the value nil' do the_function = create_function_with_optional_block_all_defaults().new(:closure_scope, :loader) expect(the_function.call({}, 2)).to be_nil end it 'such that, a scope can be injected and a block can be used' do the_function = create_function_with_scope_required_block_all_defaults().new(:closure_scope, :loader) expect(the_function.call({}, 1) { |a,b| a < b ? a : b }).to eql(1) end end context 'provides signature information' do it 'about capture rest (varargs)' do fc = create_function_with_optionals_and_repeated signatures = fc.signatures expect(signatures.size).to eql(1) signature = signatures[0] expect(signature.last_captures_rest?).to be_truthy end it 'about optional and required parameters' do fc = create_function_with_optionals_and_repeated signature = fc.signatures[0] expect(signature.args_range).to eql( [2, Float::INFINITY ] ) expect(signature.infinity?(signature.args_range[1])).to be_truthy end it 'about block not being allowed' do fc = create_function_with_optionals_and_repeated signature = fc.signatures[0] expect(signature.block_range).to eql( [ 0, 0 ] ) expect(signature.block_type).to be_nil end it 'about required block' do fc = create_function_with_required_block_all_defaults signature = fc.signatures[0] expect(signature.block_range).to eql( [ 1, 1 ] ) expect(signature.block_type).to_not be_nil end it 'about optional block' do fc = create_function_with_optional_block_all_defaults signature = fc.signatures[0] expect(signature.block_range).to eql( [ 0, 1 ] ) expect(signature.block_type).to_not be_nil end it 'about the type' do fc = create_function_with_optional_block_all_defaults signature = fc.signatures[0] expect(signature.type.class).to be(Puppet::Pops::Types::PCallableType) end it 'about parameter names obtained from ruby introspection' do fc = create_min_function_class signature = fc.signatures[0] expect(signature.parameter_names).to eql(['x', 'y']) end it 'about parameter names specified with dispatch' do fc = create_min_function_class_using_dispatch signature = fc.signatures[0] expect(signature.parameter_names).to eql([:a, :b]) end it 'about block_name when it is *not* given in the definition' do # neither type, nor name fc = create_function_with_required_block_all_defaults signature = fc.signatures[0] expect(signature.block_name).to eql(:block) # no name given, only type fc = create_function_with_required_block_given_type signature = fc.signatures[0] expect(signature.block_name).to eql(:block) end it 'about block_name when it *is* given in the definition' do # neither type, nor name fc = create_function_with_required_block_default_type signature = fc.signatures[0] expect(signature.block_name).to eql(:the_block) # no name given, only type fc = create_function_with_required_block_fully_specified signature = fc.signatures[0] expect(signature.block_name).to eql(:the_block) end end context 'supports calling other functions' do before(:all) do Puppet.push_context( {:loaders => Puppet::Pops::Loaders.new(Puppet::Node::Environment.create(:testing, []))}) end after(:all) do Puppet.pop_context() end it 'such that, other functions are callable by name' do fc = Puppet::Functions.create_function('test') do def test() # Call a function available in the puppet system call_function('assert_type', 'Integer', 10) end end # initiate the function the same way the loader initiates it f = fc.new(:closure_scope, Puppet.lookup(:loaders).puppet_system_loader) expect(f.call({})).to eql(10) end it 'such that, calling a non existing function raises an error' do fc = Puppet::Functions.create_function('test') do def test() # Call a function not available in the puppet system call_function('no_such_function', 'Integer', 'hello') end end # initiate the function the same way the loader initiates it f = fc.new(:closure_scope, Puppet.lookup(:loaders).puppet_system_loader) expect{f.call({})}.to raise_error(ArgumentError, "Function test(): Unknown function: 'no_such_function'") end end context 'supports calling ruby functions with lambda from puppet' do before(:all) do Puppet.push_context( {:loaders => Puppet::Pops::Loaders.new(Puppet::Node::Environment.create(:testing, []))}) end after(:all) do Puppet.pop_context() end before(:each) do Puppet[:strict_variables] = true end let(:parser) { Puppet::Pops::Parser::EvaluatingParser.new } let(:node) { 'node.example.com' } let(:scope) { s = create_test_scope_for_node(node); s } let(:loader) { Puppet::Pops::Loaders.find_loader(nil) } it 'function with required block can be called' do # construct ruby function to call fc = Puppet::Functions.create_function('testing::test') do dispatch :test do param 'Integer', :x # block called 'the_block', and using "all_callables" required_block_param #(all_callables(), 'the_block') end def test(x) # call the block with x yield(x) end end # add the function to the loader (as if it had been loaded from somewhere) the_loader = loader f = fc.new({}, the_loader) loader.set_entry(Puppet::Pops::Loader::TypedName.new(:function, 'testing::test'), f) # evaluate a puppet call source = "testing::test(10) |$x| { $x+1 }" program = parser.parse_string(source, __FILE__) Puppet::Pops::Adapters::LoaderAdapter.expects(:loader_for_model_object).at_least_once.returns(the_loader) expect(parser.evaluate(scope, program)).to eql(11) end end context 'reports meaningful errors' do let(:parser) { Puppet::Pops::Parser::EvaluatingParser.new } it 'syntax error in local type is reported with puppet source, puppet location, and ruby file containing function' do the_loader = loader() here = get_binding(the_loader) expect do eval(<<-CODE, here) Puppet::Functions.create_function('testing::test') do local_types do type 'MyType += Array[Integer]' end dispatch :test do param 'MyType', :x end def test(x) x end end CODE end.to raise_error(/MyType \+\= Array.*/m) end end context 'can use a loader when parsing types in function dispatch, and' do let(:parser) { Puppet::Pops::Parser::EvaluatingParser.new } it 'uses return_type to validate returned value' do the_loader = loader() here = get_binding(the_loader) fc = eval(<<-CODE, here) Puppet::Functions.create_function('testing::test') do dispatch :test do param 'Integer', :x return_type 'String' end def test(x) x end end CODE the_loader.add_function('testing::test', fc.new({}, the_loader)) program = parser.parse_string('testing::test(10)', __FILE__) Puppet::Pops::Adapters::LoaderAdapter.expects(:loader_for_model_object).returns(the_loader) expect { parser.evaluate({}, program) }.to raise_error(Puppet::Error, /value returned from function 'test' has wrong type, expects a String value, got Integer/) end it 'resolve a referenced Type alias' do the_loader = loader() the_loader.add_type('myalias', type_alias_t('MyAlias', 'Integer')) here = get_binding(the_loader) fc = eval(<<-CODE, here) Puppet::Functions.create_function('testing::test') do dispatch :test do param 'MyAlias', :x return_type 'MyAlias' end def test(x) x end end CODE the_loader.add_function('testing::test', fc.new({}, the_loader)) program = parser.parse_string('testing::test(10)', __FILE__) Puppet::Pops::Adapters::LoaderAdapter.expects(:loader_for_model_object).returns(the_loader) expect(parser.evaluate({}, program)).to eql(10) end it 'reports a reference to an unresolved type' do the_loader = loader() here = get_binding(the_loader) fc = eval(<<-CODE, here) Puppet::Functions.create_function('testing::test') do dispatch :test do param 'MyAlias', :x end def test(x) x end end CODE the_loader.add_function('testing::test', fc.new({}, the_loader)) program = parser.parse_string('testing::test(10)', __FILE__) Puppet::Pops::Adapters::LoaderAdapter.expects(:loader_for_model_object).returns(the_loader) expect { parser.evaluate({}, program) }.to raise_error(Puppet::Error, /parameter 'x' references an unresolved type 'MyAlias'/) end it 'create local Type aliases' do the_loader = loader() here = get_binding(the_loader) fc = eval(<<-CODE, here) Puppet::Functions.create_function('testing::test') do local_types do type 'MyType = Array[Integer]' end dispatch :test do param 'MyType', :x end def test(x) x end end CODE the_loader.add_function('testing::test', fc.new({}, the_loader)) program = parser.parse_string('testing::test([10,20])', __FILE__) Puppet::Pops::Adapters::LoaderAdapter.expects(:loader_for_model_object).returns(the_loader) expect(parser.evaluate({}, program)).to eq([10,20]) end it 'create nested local Type aliases' do the_loader = loader() here = get_binding(the_loader) fc = eval(<<-CODE, here) Puppet::Functions.create_function('testing::test') do local_types do type 'InnerType = Array[Integer]' type 'OuterType = Hash[String,InnerType]' end dispatch :test do param 'OuterType', :x end def test(x) x end end CODE the_loader.add_function('testing::test', fc.new({}, the_loader)) program = parser.parse_string("testing::test({'x' => [10,20]})", __FILE__) Puppet::Pops::Adapters::LoaderAdapter.expects(:loader_for_model_object).returns(the_loader) expect(parser.evaluate({}, program)).to eq({'x' => [10,20]}) end it 'create self referencing local Type aliases' do the_loader = loader() here = get_binding(the_loader) fc = eval(<<-CODE, here) Puppet::Functions.create_function('testing::test') do local_types do type 'Tree = Hash[String,Variant[String,Tree]]' end dispatch :test do param 'Tree', :x end def test(x) x end end CODE the_loader.add_function('testing::test', fc.new({}, the_loader)) program = parser.parse_string("testing::test({'x' => {'y' => 'n'}})", __FILE__) Puppet::Pops::Adapters::LoaderAdapter.expects(:loader_for_model_object).returns(the_loader) expect(parser.evaluate({}, program)).to eq({'x' => {'y' => 'n'}}) end end end def create_noargs_function_class Puppet::Functions.create_function('test') do def test() 10 end end end def create_min_function_class Puppet::Functions.create_function('min') do def min(x,y) x <= y ? x : y end end end def create_max_function_class Puppet::Functions.create_function('max') do def max(x,y) x >= y ? x : y end end end def create_badly_named_method_function_class Puppet::Functions.create_function('mix') do def mix_up(x,y) x <= y ? x : y end end end def create_min_function_class_using_dispatch Puppet::Functions.create_function('min') do dispatch :min do param 'Numeric', :a param 'Numeric', :b end def min(x,y) x <= y ? x : y end end end def create_min_function_class_disptaching_to_two_methods Puppet::Functions.create_function('min') do dispatch :min do param 'Numeric', :a param 'Numeric', :b end dispatch :min_s do param 'String', :s1 param 'String', :s2 end def min(x,y) x <= y ? x : y end def min_s(x,y) cmp = (x.downcase <=> y.downcase) cmp <= 0 ? x : y end end end def create_function_with_optionals_and_repeated Puppet::Functions.create_function('min') do def min(x,y,a=1, b=1, *c) x <= y ? x : y end end end def create_function_with_optionals_and_repeated_via_dispatch Puppet::Functions.create_function('min') do dispatch :min do param 'Numeric', :x param 'Numeric', :y optional_param 'Numeric', :a optional_param 'Numeric', :b repeated_param 'Numeric', :c end def min(x,y,a=1, b=1, *c) x <= y ? x : y end end end def create_function_with_optionals_and_repeated_via_multiple_dispatch Puppet::Functions.create_function('min') do dispatch :min do param 'Numeric', :x param 'Numeric', :y optional_param 'Numeric', :a optional_param 'Numeric', :b repeated_param 'Numeric', :c end dispatch :min do param 'String', :x param 'String', :y required_repeated_param 'String', :a end def min(x,y,a=1, b=1, *c) x <= y ? x : y end end end def create_function_with_required_repeated_via_dispatch Puppet::Functions.create_function('min') do dispatch :min do param 'Numeric', :x param 'Numeric', :y required_repeated_param 'Numeric', :z end def min(x,y, *z) x <= y ? x : y end end end def create_function_with_repeated Puppet::Functions.create_function('count_args') do dispatch :count_args do repeated_param 'Any', :c end def count_args(*c) c.size end end end def create_function_with_optional_repeated Puppet::Functions.create_function('count_args') do dispatch :count_args do optional_repeated_param 'Any', :c end def count_args(*c) c.size end end end def create_function_with_required_repeated Puppet::Functions.create_function('count_args') do dispatch :count_args do required_repeated_param 'Any', :c end def count_args(*c) c.size end end end def create_function_with_inexact_dispatch Puppet::Functions.create_function('t1') do dispatch :t1 do param 'Numeric', :x param 'Numeric', :y repeated_param 'Numeric', :z end dispatch :t1 do param 'String', :x param 'String', :y repeated_param 'String', :z end def t1(first, *x) [first.class, *x.map {|e|e.class}] end end end def create_function_with_rq_after_opt Puppet::Functions.create_function('t1') do dispatch :t1 do optional_param 'Numeric', :x param 'Numeric', :y end def t1(*x) x end end end def create_function_with_rq_repeated_after_opt Puppet::Functions.create_function('t1') do dispatch :t1 do optional_param 'Numeric', :x required_repeated_param 'Numeric', :y end def t1(x, *y) x end end end def create_function_with_param_after_repeated Puppet::Functions.create_function('t1') do dispatch :t1 do repeated_param 'Numeric', :x param 'Numeric', :y end def t1(*x) x end end end def create_function_with_param_injection_regular Puppet::Functions.create_function('test', Puppet::Functions::InternalFunction) do attr_injected Puppet::Pops::Types::TypeFactory.type_of(FunctionAPISpecModule::TestDuck), :test_attr attr_injected Puppet::Pops::Types::TypeFactory.string(), :test_attr2, "a_string" attr_injected_producer Puppet::Pops::Types::TypeFactory.integer(), :serial, "an_int" dispatch :test do injected_param Puppet::Pops::Types::TypeFactory.string, :x, 'a_string' injected_producer_param Puppet::Pops::Types::TypeFactory.integer, :y, 'an_int' param 'Scalar', :a param 'Scalar', :b end def test(x,y,a,b) y_produced = y.produce(nil) "#{x}! #{a}, and #{b} < #{y_produced} = #{ !!(a < y_produced && b < y_produced)}" end end end def create_function_with_required_block_all_defaults Puppet::Functions.create_function('test') do dispatch :test do param 'Integer', :x # use defaults, any callable, name is 'block' block_param end def test(x) yield(8,x) end end end def create_function_with_scope_required_block_all_defaults Puppet::Functions.create_function('test', Puppet::Functions::InternalFunction) do dispatch :test do scope_param param 'Integer', :x # use defaults, any callable, name is 'block' required_block_param end def test(scope, x) yield(3,x) end end end def create_function_with_required_block_default_type Puppet::Functions.create_function('test') do dispatch :test do param 'Integer', :x # use defaults, any callable, name is 'block' required_block_param :the_block end def test(x) yield end end end def create_function_with_scope_param_required_repeat Puppet::Functions.create_function('test', Puppet::Functions::InternalFunction) do dispatch :test do scope_param param 'Any', :extra repeated_param 'Any', :the_block end def test(scope, *args) [scope, *args] end end end def create_function_with_required_block_given_type Puppet::Functions.create_function('test') do dispatch :test do param 'Integer', :x required_block_param end def test(x) yield end end end def create_function_with_required_block_fully_specified Puppet::Functions.create_function('test') do dispatch :test do param 'Integer', :x # use defaults, any callable, name is 'block' required_block_param('Callable', :the_block) end def test(x) yield end end end def create_function_with_optional_block_all_defaults Puppet::Functions.create_function('test') do dispatch :test do param 'Integer', :x # use defaults, any callable, name is 'block' optional_block_param end def test(x) yield(5,x) if block_given? end end end def create_function_with_no_parameter_dispatch Puppet::Functions.create_function('test') do dispatch :test_no_args do end dispatch :test_one_arg do param 'Integer', :x end def test_no_args 0 end def test_one_arg(x) x end end end def create_function_with_mismatch_handler Puppet::Functions.create_function('test') do dispatch :test do param 'Integer', :x end argument_mismatch :on_error do param 'String', :x end def test(x) yield(5,x) if block_given? end def on_error(x) "It's not OK to pass a string" end end end def type_alias_t(name, type_string) type_expr = Puppet::Pops::Parser::EvaluatingParser.new.parse_string(type_string) Puppet::Pops::Types::TypeFactory.type_alias(name, type_expr) end def get_binding(loader_injected_arg) binding end end puppet-5.5.10/spec/unit/info_service_spec.rb0000644005276200011600000003315313417161722020743 0ustar jenkinsjenkinsrequire 'spec_helper' require 'puppet_spec/files' require 'puppet_spec/modules' require 'puppet/pops' require 'puppet/info_service' require 'puppet/pops/evaluator/literal_evaluator' describe "Puppet::InfoService" do include PuppetSpec::Files context 'task information service' do let(:mod_name) { 'test1' } let(:task_name) { "#{mod_name}::thingtask" } let(:modpath) { tmpdir('modpath') } let(:env_name) { 'testing' } let(:env) { Puppet::Node::Environment.create(env_name.to_sym, [modpath]) } let(:env_loader) { Puppet::Environments::Static.new(env) } context 'tasks_per_environment method' do it "returns task data for the tasks in an environment" do Puppet.override(:environments => env_loader) do PuppetSpec::Modules.create(mod_name, modpath, {:environment => env, :tasks => [['thingtask']]}) expect(Puppet::InfoService.tasks_per_environment(env_name)).to eq([{:name => task_name, :module => {:name => mod_name}}]) end end it "should throw EnvironmentNotFound if given a nonexistent environment" do expect{ Puppet::InfoService.tasks_per_environment('utopia') }.to raise_error(Puppet::Environments::EnvironmentNotFound) end end context 'task_data method' do before do Puppet.override(:environments => env_loader) do @mod = PuppetSpec::Modules.create(mod_name, modpath, {:environment => env, :tasks => [['thingtask', 'thingtask.json']]}) @result = Puppet::InfoService.task_data(env_name, mod_name, task_name) end end describe 'in the happy case' do it 'returns the right set of keys' do expect(@result.keys.sort).to eq([:files, :metadata_file]) end it 'specifies the metadata_file correctly' do task = @mod.tasks[0] expect(@result[:metadata_file]).to eq(task.metadata_file) end it 'specifies the other files correctly' do task = @mod.tasks[0] expect(@result[:files]).to eq(task.files) end end it "should raise EnvironmentNotFound if given a nonexistent environment" do expect{ Puppet::InfoService.task_data('utopia', mod_name, task_name) }.to raise_error(Puppet::Environments::EnvironmentNotFound) end it "should raise MissingModule if the module does not exist" do Puppet.override(:environments => env_loader) do expect { Puppet::InfoService.task_data(env_name, 'notamodule', 'notamodule::thingtask') } .to raise_error(Puppet::Module::MissingModule) end end it "should raise TaskNotFound if the task does not exist" do Puppet.override(:environments => env_loader) do expect { Puppet::InfoService.task_data(env_name, mod_name, 'testing1::notatask') } .to raise_error(Puppet::Module::Task::TaskNotFound) end end end end context 'classes_per_environment service' do let(:code_dir) do dir_containing('manifests', { 'foo.pp' => <<-CODE, class foo($foo_a, Integer $foo_b, String $foo_c = 'c default value') { } class foo2($foo2_a, Integer $foo2_b, String $foo2_c = 'c default value') { } CODE 'bar.pp' => <<-CODE, class bar($bar_a, Integer $bar_b, String $bar_c = 'c default value') { } class bar2($bar2_a, Integer $bar2_b, String $bar2_c = 'c default value') { } CODE 'intp.pp' => <<-CODE, class intp(String $intp_a = "default with interpolated $::os_family") { } CODE 'fee.pp' => <<-CODE, class fee(Integer $fee_a = 1+1) { } CODE 'fum.pp' => <<-CODE, class fum($fum_a) { } CODE 'nothing.pp' => <<-CODE, # not much to see here, move along CODE 'borked.pp' => <<-CODE, class Borked($Herp+$Derp) {} CODE 'json_unsafe.pp' => <<-CODE, class json_unsafe($arg1 = /.*/, $arg2 = default, $arg3 = {1 => 1}) {} CODE }) end it "errors if not given a hash" do expect{ Puppet::InfoService.classes_per_environment("you wassup?")}.to raise_error(ArgumentError, 'Given argument must be a Hash') end it "returns empty hash if given nothing" do expect(Puppet::InfoService.classes_per_environment({})).to eq({}) end it "produces classes and parameters from a given file" do files = ['foo.pp'].map {|f| File.join(code_dir, f) } result = Puppet::InfoService.classes_per_environment({'production' => files }) expect(result).to eq({ "production"=>{ "#{code_dir}/foo.pp"=> {:classes => [ {:name=>"foo", :params=>[ {:name=>"foo_a"}, {:name=>"foo_b", :type=>"Integer"}, {:name=>"foo_c", :type=>"String", :default_literal=>"c default value", :default_source=>"'c default value'"} ]}, {:name=>"foo2", :params=>[ {:name=>"foo2_a"}, {:name=>"foo2_b", :type=>"Integer"}, {:name=>"foo2_c", :type=>"String", :default_literal=>"c default value", :default_source=>"'c default value'"} ] } ]}} # end production env }) end it "produces classes and parameters from multiple files in same environment" do files = ['foo.pp', 'bar.pp'].map {|f| File.join(code_dir, f) } result = Puppet::InfoService.classes_per_environment({'production' => files }) expect(result).to eq({ "production"=>{ "#{code_dir}/foo.pp"=>{:classes => [ {:name=>"foo", :params=>[ {:name=>"foo_a"}, {:name=>"foo_b", :type=>"Integer"}, {:name=>"foo_c", :type=>"String", :default_literal=>"c default value", :default_source=>"'c default value'"} ]}, {:name=>"foo2", :params=>[ {:name=>"foo2_a"}, {:name=>"foo2_b", :type=>"Integer"}, {:name=>"foo2_c", :type=>"String", :default_literal=>"c default value", :default_source=>"'c default value'"} ] } ]}, "#{code_dir}/bar.pp"=> {:classes =>[ {:name=>"bar", :params=>[ {:name=>"bar_a"}, {:name=>"bar_b", :type=>"Integer"}, {:name=>"bar_c", :type=>"String", :default_literal=>"c default value", :default_source=>"'c default value'"} ]}, {:name=>"bar2", :params=>[ {:name=>"bar2_a"}, {:name=>"bar2_b", :type=>"Integer"}, {:name=>"bar2_c", :type=>"String", :default_literal=>"c default value", :default_source=>"'c default value'"} ] } ]}, } # end production env } ) end it "produces classes and parameters from multiple files in multiple environments" do files_production = ['foo.pp', 'bar.pp'].map {|f| File.join(code_dir, f) } files_test = ['fee.pp', 'fum.pp'].map {|f| File.join(code_dir, f) } result = Puppet::InfoService.classes_per_environment({ 'production' => files_production, 'test' => files_test }) expect(result).to eq({ "production"=>{ "#{code_dir}/foo.pp"=>{:classes => [ {:name=>"foo", :params=>[ {:name=>"foo_a"}, {:name=>"foo_b", :type=>"Integer"}, {:name=>"foo_c", :type=>"String", :default_literal=>"c default value", :default_source=>"'c default value'"} ]}, {:name=>"foo2", :params=>[ {:name=>"foo2_a"}, {:name=>"foo2_b", :type=>"Integer"}, {:name=>"foo2_c", :type=>"String", :default_literal=>"c default value", :default_source=>"'c default value'"} ] } ]}, "#{code_dir}/bar.pp"=>{:classes => [ {:name=>"bar", :params=>[ {:name=>"bar_a"}, {:name=>"bar_b", :type=>"Integer"}, {:name=>"bar_c", :type=>"String", :default_literal=>"c default value", :default_source=>"'c default value'"} ]}, {:name=>"bar2", :params=>[ {:name=>"bar2_a"}, {:name=>"bar2_b", :type=>"Integer"}, {:name=>"bar2_c", :type=>"String", :default_literal=>"c default value", :default_source=>"'c default value'"} ] } ]}, }, # end production env "test"=>{ "#{code_dir}/fee.pp"=>{:classes => [ {:name=>"fee", :params=>[ {:name=>"fee_a", :type=>"Integer", :default_source=>"1+1"} ]}, ]}, "#{code_dir}/fum.pp"=>{:classes => [ {:name=>"fum", :params=>[ {:name=>"fum_a"} ]}, ]}, } # end test env } ) end it "avoids parsing file more than once when environments have same feature flag set" do # in this version of puppet, all environments are equal in this respect result = Puppet::Pops::Parser::EvaluatingParser.new.parse_file("#{code_dir}/fum.pp") Puppet::Pops::Parser::EvaluatingParser.any_instance.expects(:parse_file).with("#{code_dir}/fum.pp").returns(result).once files_production = ['fum.pp'].map {|f| File.join(code_dir, f) } files_test = files_production result = Puppet::InfoService.classes_per_environment({ 'production' => files_production, 'test' => files_test }) expect(result).to eq({ "production"=>{ "#{code_dir}/fum.pp"=>{:classes => [ {:name=>"fum", :params=>[ {:name=>"fum_a"}]}]}}, "test" =>{ "#{code_dir}/fum.pp"=>{:classes => [ {:name=>"fum", :params=>[ {:name=>"fum_a"}]}]}} } ) end it "produces expression string if a default value is not literal" do files = ['fee.pp'].map {|f| File.join(code_dir, f) } result = Puppet::InfoService.classes_per_environment({'production' => files }) expect(result).to eq({ "production"=>{ "#{code_dir}/fee.pp"=>{:classes => [ {:name=>"fee", :params=>[ {:name=>"fee_a", :type=>"Integer", :default_source=>"1+1"} ]}, ]}} # end production env }) end it "produces source string for literals that are not pure json" do files = ['json_unsafe.pp'].map {|f| File.join(code_dir, f) } result = Puppet::InfoService.classes_per_environment({'production' => files }) expect(result).to eq({ "production"=>{ "#{code_dir}/json_unsafe.pp" => {:classes => [ {:name=>"json_unsafe", :params => [ {:name => "arg1", :default_source => "/.*/" }, {:name => "arg2", :default_source => "default" }, {:name => "arg3", :default_source => "{1 => 1}" } ]} ]}} # end production env }) end it "produces no type entry if type is not given" do files = ['fum.pp'].map {|f| File.join(code_dir, f) } result = Puppet::InfoService.classes_per_environment({'production' => files }) expect(result).to eq({ "production"=>{ "#{code_dir}/fum.pp"=>{:classes => [ {:name=>"fum", :params=>[ {:name=>"fum_a" } ]}, ]}} # end production env }) end it 'does not evaluate default expressions' do files = ['intp.pp'].map {|f| File.join(code_dir, f) } result = Puppet::InfoService.classes_per_environment({'production' => files }) expect(result).to eq({ 'production' =>{ "#{code_dir}/intp.pp"=>{:classes => [ {:name=> 'intp', :params=>[ {:name=> 'intp_a', :type=> 'String', :default_source=>'"default with interpolated $::os_family"'} ]}, ]}} # end production env }) end it "produces error entry if file is broken" do files = ['borked.pp'].map {|f| File.join(code_dir, f) } result = Puppet::InfoService.classes_per_environment({'production' => files }) expect(result).to eq({ "production"=>{ "#{code_dir}/borked.pp"=> {:error=>"Syntax error at '+' (file: #{code_dir}/borked.pp, line: 1, column: 30)", }, } # end production env }) end it "produces empty {} if parsed result has no classes" do files = ['nothing.pp'].map {|f| File.join(code_dir, f) } result = Puppet::InfoService.classes_per_environment({'production' => files }) expect(result).to eq({ "production"=>{ "#{code_dir}/nothing.pp"=> {:classes => [] } }, }) end it "produces error when given a file that does not exist" do files = ['the_tooth_fairy_does_not_exist.pp'].map {|f| File.join(code_dir, f) } result = Puppet::InfoService.classes_per_environment({'production' => files }) expect(result).to eq({ "production"=>{ "#{code_dir}/the_tooth_fairy_does_not_exist.pp" => {:error => "The file #{code_dir}/the_tooth_fairy_does_not_exist.pp does not exist"} }, }) end end end puppet-5.5.10/spec/unit/module_spec.rb0000644005276200011600000010041613417161722017552 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/files' require 'puppet_spec/modules' require 'puppet/module_tool/checksums' describe Puppet::Module do include PuppetSpec::Files let(:env) { mock("environment") } let(:path) { "/path" } let(:name) { "mymod" } let(:mod) { Puppet::Module.new(name, path, env) } before do # This is necessary because of the extra checks we have for the deprecated # 'plugins' directory Puppet::FileSystem.stubs(:exist?).returns false end it "should have a class method that returns a named module from a given environment" do env = Puppet::Node::Environment.create(:myenv, []) env.expects(:module).with(name).returns "yep" Puppet.override(:environments => Puppet::Environments::Static.new(env)) do expect(Puppet::Module.find(name, "myenv")).to eq("yep") end end it "should return nil if asked for a named module that doesn't exist" do env = Puppet::Node::Environment.create(:myenv, []) env.expects(:module).with(name).returns nil Puppet.override(:environments => Puppet::Environments::Static.new(env)) do expect(Puppet::Module.find(name, "myenv")).to be_nil end end describe "is_module_directory?" do let(:first_modulepath) { tmpdir('firstmodules') } let(:not_a_module) { tmpfile('thereisnomodule', first_modulepath) } it "should return false for a non-directory" do expect(Puppet::Module.is_module_directory?('thereisnomodule', first_modulepath)).to be_falsey end it "should return true for a well named directories" do PuppetSpec::Modules.generate_files('foo', first_modulepath) PuppetSpec::Modules.generate_files('foo2', first_modulepath) PuppetSpec::Modules.generate_files('foo_bar', first_modulepath) expect(Puppet::Module.is_module_directory?('foo', first_modulepath)).to be_truthy expect(Puppet::Module.is_module_directory?('foo2', first_modulepath)).to be_truthy expect(Puppet::Module.is_module_directory?('foo_bar', first_modulepath)).to be_truthy end it "should return false for badly named directories" do PuppetSpec::Modules.generate_files('foo=bar', first_modulepath) PuppetSpec::Modules.generate_files('.foo', first_modulepath) expect(Puppet::Module.is_module_directory?('foo=bar', first_modulepath)).to be_falsey expect(Puppet::Module.is_module_directory?('.foo', first_modulepath)).to be_falsey end end describe "is_module_directory_name?" do it "should return true for a valid directory module name" do expect(Puppet::Module.is_module_directory_name?('foo')).to be_truthy expect(Puppet::Module.is_module_directory_name?('foo2')).to be_truthy expect(Puppet::Module.is_module_directory_name?('foo_bar')).to be_truthy end it "should return false for badly formed directory module names" do expect(Puppet::Module.is_module_directory_name?('foo-bar')).to be_falsey expect(Puppet::Module.is_module_directory_name?('foo=bar')).to be_falsey expect(Puppet::Module.is_module_directory_name?('foo bar')).to be_falsey expect(Puppet::Module.is_module_directory_name?('foo.bar')).to be_falsey expect(Puppet::Module.is_module_directory_name?('-foo')).to be_falsey expect(Puppet::Module.is_module_directory_name?('foo-')).to be_falsey expect(Puppet::Module.is_module_directory_name?('foo--bar')).to be_falsey expect(Puppet::Module.is_module_directory_name?('.foo')).to be_falsey end end describe "is_module_namespaced_name?" do it "should return true for a valid namespaced module name" do expect(Puppet::Module.is_module_namespaced_name?('foo-bar')).to be_truthy end it "should return false for badly formed namespaced module names" do expect(Puppet::Module.is_module_namespaced_name?('foo')).to be_falsey expect(Puppet::Module.is_module_namespaced_name?('.foo-bar')).to be_falsey expect(Puppet::Module.is_module_namespaced_name?('foo2')).to be_falsey expect(Puppet::Module.is_module_namespaced_name?('foo_bar')).to be_falsey expect(Puppet::Module.is_module_namespaced_name?('foo=bar')).to be_falsey expect(Puppet::Module.is_module_namespaced_name?('foo bar')).to be_falsey expect(Puppet::Module.is_module_namespaced_name?('foo.bar')).to be_falsey expect(Puppet::Module.is_module_namespaced_name?('-foo')).to be_falsey expect(Puppet::Module.is_module_namespaced_name?('foo-')).to be_falsey expect(Puppet::Module.is_module_namespaced_name?('foo--bar')).to be_falsey end end describe "attributes" do it "should support a 'version' attribute" do mod.version = 1.09 expect(mod.version).to eq(1.09) end it "should support a 'source' attribute" do mod.source = "http://foo/bar" expect(mod.source).to eq("http://foo/bar") end it "should support a 'project_page' attribute" do mod.project_page = "http://foo/bar" expect(mod.project_page).to eq("http://foo/bar") end it "should support an 'author' attribute" do mod.author = "Luke Kanies " expect(mod.author).to eq("Luke Kanies ") end it "should support a 'license' attribute" do mod.license = "GPL2" expect(mod.license).to eq("GPL2") end it "should support a 'summary' attribute" do mod.summary = "GPL2" expect(mod.summary).to eq("GPL2") end it "should support a 'description' attribute" do mod.description = "GPL2" expect(mod.description).to eq("GPL2") end end describe "when finding unmet dependencies" do before do Puppet::FileSystem.unstub(:exist?) @modpath = tmpdir('modpath') Puppet.settings[:modulepath] = @modpath end it "should resolve module dependencies using forge names" do parent = PuppetSpec::Modules.create( 'parent', @modpath, :metadata => { :author => 'foo', :dependencies => [{ "name" => "foo/child" }] }, :environment => env ) child = PuppetSpec::Modules.create( 'child', @modpath, :metadata => { :author => 'foo', :dependencies => [] }, :environment => env ) env.expects(:module_by_forge_name).with('foo/child').returns(child) expect(parent.unmet_dependencies).to eq([]) end it "should list modules that are missing" do mod = PuppetSpec::Modules.create( 'needy', @modpath, :metadata => { :dependencies => [{ "version_requirement" => ">= 2.2.0", "name" => "baz/foobar" }] }, :environment => env ) env.expects(:module_by_forge_name).with('baz/foobar').returns(nil) expect(mod.unmet_dependencies).to eq([{ :reason => :missing, :name => "baz/foobar", :version_constraint => ">= 2.2.0", :parent => { :name => 'puppetlabs/needy', :version => 'v9.9.9' }, :mod_details => { :installed_version => nil } }]) end it "should list modules that are missing and have invalid names" do mod = PuppetSpec::Modules.create( 'needy', @modpath, :metadata => { :dependencies => [{ "version_requirement" => ">= 2.2.0", "name" => "baz/foobar=bar" }] }, :environment => env ) env.expects(:module_by_forge_name).with('baz/foobar=bar').returns(nil) expect(mod.unmet_dependencies).to eq([{ :reason => :missing, :name => "baz/foobar=bar", :version_constraint => ">= 2.2.0", :parent => { :name => 'puppetlabs/needy', :version => 'v9.9.9' }, :mod_details => { :installed_version => nil } }]) end it "should list modules with unmet version requirement" do env = Puppet::Node::Environment.create(:testing, [@modpath]) ['test_gte_req', 'test_specific_req', 'foobar'].each do |mod_name| mod_dir = "#{@modpath}/#{mod_name}" metadata_file = "#{mod_dir}/metadata.json" Puppet::FileSystem.stubs(:exist?).with(metadata_file).returns true end mod = PuppetSpec::Modules.create( 'test_gte_req', @modpath, :metadata => { :dependencies => [{ "version_requirement" => ">= 2.2.0", "name" => "baz/foobar" }] }, :environment => env ) mod2 = PuppetSpec::Modules.create( 'test_specific_req', @modpath, :metadata => { :dependencies => [{ "version_requirement" => "1.0.0", "name" => "baz/foobar" }] }, :environment => env ) PuppetSpec::Modules.create( 'foobar', @modpath, :metadata => { :version => '2.0.0', :author => 'baz' }, :environment => env ) expect(mod.unmet_dependencies).to eq([{ :reason => :version_mismatch, :name => "baz/foobar", :version_constraint => ">= 2.2.0", :parent => { :version => "v9.9.9", :name => "puppetlabs/test_gte_req" }, :mod_details => { :installed_version => "2.0.0" } }]) expect(mod2.unmet_dependencies).to eq([{ :reason => :version_mismatch, :name => "baz/foobar", :version_constraint => "v1.0.0", :parent => { :version => "v9.9.9", :name => "puppetlabs/test_specific_req" }, :mod_details => { :installed_version => "2.0.0" } }]) end it "should consider a dependency without a version requirement to be satisfied" do env = Puppet::Node::Environment.create(:testing, [@modpath]) mod = PuppetSpec::Modules.create( 'foobar', @modpath, :metadata => { :dependencies => [{ "name" => "baz/foobar" }] }, :environment => env ) PuppetSpec::Modules.create( 'foobar', @modpath, :metadata => { :version => '2.0.0', :author => 'baz' }, :environment => env ) expect(mod.unmet_dependencies).to be_empty end it "should consider a dependency without a semantic version to be unmet" do env = Puppet::Node::Environment.create(:testing, [@modpath]) mod = PuppetSpec::Modules.create( 'foobar', @modpath, :metadata => { :dependencies => [{ "name" => "baz/foobar" }] }, :environment => env ) PuppetSpec::Modules.create( 'foobar', @modpath, :metadata => { :version => '5.1', :author => 'baz' }, :environment => env ) expect(mod.unmet_dependencies).to eq([{ :reason => :non_semantic_version, :parent => { :version => "v9.9.9", :name => "puppetlabs/foobar" }, :mod_details => { :installed_version => "5.1" }, :name => "baz/foobar", :version_constraint => ">= 0.0.0" }]) end it "should have valid dependencies when no dependencies have been specified" do mod = PuppetSpec::Modules.create( 'foobar', @modpath, :metadata => { :dependencies => [] } ) expect(mod.unmet_dependencies).to eq([]) end it "should throw an error if invalid dependencies are specified" do expect { PuppetSpec::Modules.create( 'foobar', @modpath, :metadata => { :dependencies => "" } ) }.to raise_error( Puppet::Module::MissingMetadata, /dependencies in the file metadata.json of the module foobar must be an array, not: ''/) end it "should only list unmet dependencies" do env = Puppet::Node::Environment.create(:testing, [@modpath]) mod = PuppetSpec::Modules.create( name, @modpath, :metadata => { :dependencies => [ { "version_requirement" => ">= 2.2.0", "name" => "baz/satisfied" }, { "version_requirement" => ">= 2.2.0", "name" => "baz/notsatisfied" } ] }, :environment => env ) PuppetSpec::Modules.create( 'satisfied', @modpath, :metadata => { :version => '3.3.0', :author => 'baz' }, :environment => env ) expect(mod.unmet_dependencies).to eq([{ :reason => :missing, :mod_details => { :installed_version => nil }, :parent => { :version => "v9.9.9", :name => "puppetlabs/#{name}" }, :name => "baz/notsatisfied", :version_constraint => ">= 2.2.0" }]) end it "should be empty when all dependencies are met" do env = Puppet::Node::Environment.create(:testing, [@modpath]) mod = PuppetSpec::Modules.create( 'mymod2', @modpath, :metadata => { :dependencies => [ { "version_requirement" => ">= 2.2.0", "name" => "baz/satisfied" }, { "version_requirement" => "< 2.2.0", "name" => "baz/alsosatisfied" } ] }, :environment => env ) PuppetSpec::Modules.create( 'satisfied', @modpath, :metadata => { :version => '3.3.0', :author => 'baz' }, :environment => env ) PuppetSpec::Modules.create( 'alsosatisfied', @modpath, :metadata => { :version => '2.1.0', :author => 'baz' }, :environment => env ) expect(mod.unmet_dependencies).to be_empty end end describe "when managing supported platforms" do it "should support specifying a supported platform" do mod.supports "solaris" end it "should support specifying a supported platform and version" do mod.supports "solaris", 1.0 end end it "should return nil if asked for a module whose name is 'nil'" do expect(Puppet::Module.find(nil, "myenv")).to be_nil end it "should provide support for logging" do expect(Puppet::Module.ancestors).to be_include(Puppet::Util::Logging) end it "should be able to be converted to a string" do expect(mod.to_s).to eq("Module #{name}(#{path})") end it "should fail if its name is not alphanumeric" do expect { Puppet::Module.new(".something", "/path", env) }.to raise_error(Puppet::Module::InvalidName) end it "should require a name at initialization" do expect { Puppet::Module.new }.to raise_error(ArgumentError) end it "should accept an environment at initialization" do expect(Puppet::Module.new("foo", "/path", env).environment).to eq(env) end describe '#modulepath' do it "should return the directory the module is installed in, if a path exists" do mod = Puppet::Module.new("foo", "/a/foo", env) expect(mod.modulepath).to eq('/a') end end [:plugins, :pluginfacts, :templates, :files, :manifests].each do |filetype| case filetype when :plugins dirname = "lib" when :pluginfacts dirname = "facts.d" else dirname = filetype.to_s end it "should be able to return individual #{filetype}" do module_file = File.join(path, dirname, "my/file") Puppet::FileSystem.expects(:exist?).with(module_file).returns true expect(mod.send(filetype.to_s.sub(/s$/, ''), "my/file")).to eq(module_file) end it "should consider #{filetype} to be present if their base directory exists" do module_file = File.join(path, dirname) Puppet::FileSystem.expects(:exist?).with(module_file).returns true expect(mod.send(filetype.to_s + "?")).to be_truthy end it "should consider #{filetype} to be absent if their base directory does not exist" do module_file = File.join(path, dirname) Puppet::FileSystem.expects(:exist?).with(module_file).returns false expect(mod.send(filetype.to_s + "?")).to be_falsey end it "should return nil if asked to return individual #{filetype} that don't exist" do module_file = File.join(path, dirname, "my/file") Puppet::FileSystem.expects(:exist?).with(module_file).returns false expect(mod.send(filetype.to_s.sub(/s$/, ''), "my/file")).to be_nil end it "should return the base directory if asked for a nil path" do base = File.join(path, dirname) Puppet::FileSystem.expects(:exist?).with(base).returns true expect(mod.send(filetype.to_s.sub(/s$/, ''), nil)).to eq(base) end end it "should return the path to the plugin directory" do expect(mod.plugin_directory).to eq(File.join(path, "lib")) end it "should return the path to the tasks directory" do expect(mod.tasks_directory).to eq(File.join(path, "tasks")) end describe "when finding tasks" do before do Puppet::FileSystem.unstub(:exist?) @modpath = tmpdir('modpath') Puppet.settings[:modulepath] = @modpath end it "should have an empty array for the tasks when the tasks directory does not exist" do mod = PuppetSpec::Modules.create('tasks_test_nodir', @modpath, :environment => env) expect(mod.tasks).to eq([]) end it "should have an empty array for the tasks when the tasks directory does exist and is empty" do mod = PuppetSpec::Modules.create('tasks_test_empty', @modpath, {:environment => env, :tasks => []}) expect(mod.tasks).to eq([]) end it "should list the expected tasks when the required files exist" do fake_tasks = [['task1'], ['task2.sh', 'task2.json']] mod = PuppetSpec::Modules.create('tasks_smoke', @modpath, {:environment => env, :tasks => fake_tasks}) expect(mod.tasks.count).to eq(2) expect(mod.tasks.map{|t| t.name}.sort).to eq(['tasks_smoke::task1', 'tasks_smoke::task2']) expect(mod.tasks.map{|t| t.class}).to eq([Puppet::Module::Task] * 2) end it "should be able to find individual task files when they exist" do task_exe = 'stateskatetask.stk' mod = PuppetSpec::Modules.create('task_file_smoke', @modpath, {:environment => env, :tasks => [[task_exe]]}) expect(mod.task_file(task_exe)).to eq("#{mod.path}/tasks/#{task_exe}") end it "should return nil when asked for an individual task file if it does not exist" do mod = PuppetSpec::Modules.create('task_file_neg', @modpath, {:environment => env, :tasks => []}) expect(mod.task_file('nosuchtask')).to be_nil end describe "does the task finding" do before :each do Puppet::FileSystem.unstub(:exist?) Puppet::Module::Task.unstub(:tasks_in_module) end let(:mod_name) { 'tasks_test_lazy' } let(:mod_tasks_dir) { File.join(@modpath, mod_name, 'tasks') } it "after the module is initialized" do Puppet::FileSystem.expects(:exist?).with(mod_tasks_dir).never Puppet::Module::Task.expects(:tasks_in_module).never Puppet::Module.new(mod_name, @modpath, env) end it "when the tasks method is called" do Puppet::Module::Task.expects(:tasks_in_module) mod = PuppetSpec::Modules.create(mod_name, @modpath, {:environment => env, :tasks => [['itascanstaccatotask']]}) mod.tasks end it "only once for the lifetime of the module object" do Dir.expects(:glob).with("#{mod_tasks_dir}/*").once.returns ['allalaskataskattacktactics'] mod = PuppetSpec::Modules.create(mod_name, @modpath, {:environment => env, :tasks => []}) mod.tasks mod.tasks end end end end describe Puppet::Module, "when finding matching manifests" do before do @mod = Puppet::Module.new("mymod", "/a", mock("environment")) @pq_glob_with_extension = "yay/*.xx" @fq_glob_with_extension = "/a/manifests/#{@pq_glob_with_extension}" end it "should return all manifests matching the glob pattern" do Dir.expects(:glob).with(@fq_glob_with_extension).returns(%w{foo bar}) FileTest.stubs(:directory?).returns false expect(@mod.match_manifests(@pq_glob_with_extension)).to eq(%w{foo bar}) end it "should not return directories" do Dir.expects(:glob).with(@fq_glob_with_extension).returns(%w{foo bar}) FileTest.expects(:directory?).with("foo").returns false FileTest.expects(:directory?).with("bar").returns true expect(@mod.match_manifests(@pq_glob_with_extension)).to eq(%w{foo}) end it "should default to the 'init' file if no glob pattern is specified" do Puppet::FileSystem.expects(:exist?).with("/a/manifests/init.pp").returns(true) expect(@mod.match_manifests(nil)).to eq(%w{/a/manifests/init.pp}) end it "should return all manifests matching the glob pattern in all existing paths" do Dir.expects(:glob).with(@fq_glob_with_extension).returns(%w{a b}) expect(@mod.match_manifests(@pq_glob_with_extension)).to eq(%w{a b}) end it "should match the glob pattern plus '.pp' if no extension is specified" do Dir.expects(:glob).with("/a/manifests/yay/foo.pp").returns(%w{yay}) expect(@mod.match_manifests("yay/foo")).to eq(%w{yay}) end it "should return an empty array if no manifests matched" do Dir.expects(:glob).with(@fq_glob_with_extension).returns([]) expect(@mod.match_manifests(@pq_glob_with_extension)).to eq([]) end it "should raise an error if the pattern tries to leave the manifest directory" do expect do @mod.match_manifests("something/../../*") end.to raise_error(Puppet::Module::InvalidFilePattern, 'The pattern "something/../../*" to find manifests in the module "mymod" is invalid and potentially unsafe.') end end describe Puppet::Module do include PuppetSpec::Files let!(:modpath) do path = tmpdir('modpath') PuppetSpec::Modules.create('mymod', path) path end let!(:mymodpath) { File.join(modpath, 'mymod') } let!(:mymod_metadata) { File.join(mymodpath, 'metadata.json') } let(:mymod) { Puppet::Module.new('mymod', mymodpath, nil) } it "should use 'License' in its current path as its metadata file" do expect(mymod.license_file).to eq("#{modpath}/mymod/License") end it "should cache the license file" do mymod.expects(:path).once.returns nil mymod.license_file mymod.license_file end it "should use 'metadata.json' in its current path as its metadata file" do expect(mymod_metadata).to eq("#{modpath}/mymod/metadata.json") end it "should not have metadata if it has a metadata file and its data is valid but empty json hash" do File.stubs(:read).with(mymod_metadata, {:encoding => 'utf-8'}).returns "{}" expect(mymod).not_to be_has_metadata end it "should not have metadata if it has a metadata file and its data is empty" do File.stubs(:read).with(mymod_metadata, {:encoding => 'utf-8'}).returns "" expect(mymod).not_to be_has_metadata end it "should not have metadata if has a metadata file and its data is invalid" do File.stubs(:read).with(mymod_metadata, {:encoding => 'utf-8'}).returns "This is some invalid json.\n" expect(mymod).not_to be_has_metadata end it "should know if it is missing a metadata file" do File.stubs(:read).with(mymod_metadata, {:encoding => 'utf-8'}).raises(Errno::ENOENT) expect(mymod).not_to be_has_metadata end it "should be able to parse its metadata file" do expect(mymod).to respond_to(:load_metadata) end it "should parse its metadata file on initialization if it is present" do Puppet::Module.any_instance.expects(:load_metadata) Puppet::Module.new("yay", "/path", mock("env")) end it "should tolerate failure to parse" do File.stubs(:read).with(mymod_metadata, {:encoding => 'utf-8'}).returns(my_fixture('trailing-comma.json')) expect(mymod.has_metadata?).to be_falsey end describe 'when --strict is warning' do before :each do Puppet[:strict] = :warning end it "should warn about a failure to parse" do File.stubs(:read).with(mymod_metadata, {:encoding => 'utf-8'}).returns(my_fixture('trailing-comma.json')) expect(mymod.has_metadata?).to be_falsey expect(@logs).to have_matching_log(/mymod has an invalid and unparsable metadata\.json file/) end end describe 'when --strict is off' do before :each do Puppet[:strict] = :off end it "should not warn about a failure to parse" do File.stubs(:read).with(mymod_metadata, {:encoding => 'utf-8'}).returns(my_fixture('trailing-comma.json')) expect(mymod.has_metadata?).to be_falsey expect(@logs).to_not have_matching_log(/mymod has an invalid and unparsable metadata\.json file.*/) end it "should log debug output about a failure to parse when --debug is on" do Puppet[:log_level] = :debug File.stubs(:read).with(mymod_metadata, {:encoding => 'utf-8'}).returns(my_fixture('trailing-comma.json')) expect(mymod.has_metadata?).to be_falsey expect(@logs).to have_matching_log(/mymod has an invalid and unparsable metadata\.json file.*/) end end describe 'when --strict is error' do before :each do Puppet[:strict] = :error end it "should fail on a failure to parse" do File.stubs(:read).with(mymod_metadata, {:encoding => 'utf-8'}).returns(my_fixture('trailing-comma.json')) expect do expect(mymod.has_metadata?).to be_falsey end.to raise_error(/mymod has an invalid and unparsable metadata\.json file/) end end def a_module_with_metadata(data) File.stubs(:read).with("/path/metadata.json", {:encoding => 'utf-8'}).returns data.to_json Puppet::Module.new("foo", "/path", mock("env")) end describe "when loading the metadata file" do let(:data) do { :license => "GPL2", :author => "luke", :version => "1.0", :source => "http://foo/", :dependencies => [] } end %w{source author version license}.each do |attr| it "should set #{attr} if present in the metadata file" do mod = a_module_with_metadata(data) expect(mod.send(attr)).to eq(data[attr.to_sym]) end it "should fail if #{attr} is not present in the metadata file" do data.delete(attr.to_sym) expect { a_module_with_metadata(data) }.to raise_error( Puppet::Module::MissingMetadata, "No #{attr} module metadata provided for foo" ) end end end describe "when loading the metadata file from disk" do it "should properly parse utf-8 contents" do rune_utf8 = "\u16A0\u16C7\u16BB" # ᚠᛇᚻ metadata_json = tmpfile('metadata.json') File.open(metadata_json, 'w:UTF-8') do |file| file.puts <<-EOF { "license" : "GPL2", "author" : "#{rune_utf8}", "version" : "1.0", "source" : "http://foo/", "dependencies" : [] } EOF end Puppet::Module.any_instance.stubs(:metadata_file).returns metadata_json mod = Puppet::Module.new('foo', '/path', mock('env')) mod.load_metadata expect(mod.author).to eq(rune_utf8) end end it "should be able to tell if there are local changes" do modpath = tmpdir('modpath') foo_checksum = 'acbd18db4cc2f85cedef654fccc4a4d8' checksummed_module = PuppetSpec::Modules.create( 'changed', modpath, :metadata => { :checksums => { "foo" => foo_checksum, } } ) foo_path = Pathname.new(File.join(checksummed_module.path, 'foo')) IO.binwrite(foo_path, 'notfoo') expect(Puppet::ModuleTool::Checksums.new(foo_path).checksum(foo_path)).not_to eq(foo_checksum) IO.binwrite(foo_path, 'foo') expect(Puppet::ModuleTool::Checksums.new(foo_path).checksum(foo_path)).to eq(foo_checksum) end it "should know what other modules require it" do env = Puppet::Node::Environment.create(:testing, [modpath]) dependable = PuppetSpec::Modules.create( 'dependable', modpath, :metadata => {:author => 'puppetlabs'}, :environment => env ) PuppetSpec::Modules.create( 'needy', modpath, :metadata => { :author => 'beggar', :dependencies => [{ "version_requirement" => ">= 2.2.0", "name" => "puppetlabs/dependable" }] }, :environment => env ) PuppetSpec::Modules.create( 'wantit', modpath, :metadata => { :author => 'spoiled', :dependencies => [{ "version_requirement" => "< 5.0.0", "name" => "puppetlabs/dependable" }] }, :environment => env ) expect(dependable.required_by).to match_array([ { "name" => "beggar/needy", "version" => "9.9.9", "version_requirement" => ">= 2.2.0" }, { "name" => "spoiled/wantit", "version" => "9.9.9", "version_requirement" => "< 5.0.0" } ]) end context 'when parsing VersionRange' do let(:logs) { [] } let(:notices) { logs.select { |log| log.level == :notice }.map { |log| log.message } } it 'can parse a strict range' do expect(Puppet::Module.parse_range('>=1.0.0', true).include?(SemanticPuppet::Version.parse('1.0.1-rc1'))).to be_falsey end it 'can parse a non-strict range' do expect(Puppet::Module.parse_range('>=1.0.0', false).include?(SemanticPuppet::Version.parse('1.0.1-rc1'))).to be_truthy end context 'using parse method with an arity of 1' do around(:each) do |example| begin example.run ensure Puppet::Module.instance_variable_set(:@semver_gem_version, nil) Puppet::Module.instance_variable_set(:@parse_range_method, nil) end end it 'will notify when non-strict ranges cannot be parsed' do Puppet::Module.instance_variable_set(:@semver_gem_version, SemanticPuppet::Version.parse('1.0.0')) Puppet::Module.instance_variable_set(:@parse_range_method, Proc.new { |str| SemanticPuppet::VersionRange.parse(str, true) }) Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do expect(Puppet::Module.parse_range('>=1.0.0', false).include?(SemanticPuppet::Version.parse('1.0.1-rc1'))).to be_falsey end expect(notices).to include(/VersionRanges will always be strict when using non-vendored SemanticPuppet gem, version 1\.0\.0/) end it 'will notify when strict ranges cannot be parsed' do Puppet::Module.instance_variable_set(:@semver_gem_version, SemanticPuppet::Version.parse('0.1.4')) Puppet::Module.instance_variable_set(:@parse_range_method, Proc.new { |str| SemanticPuppet::VersionRange.parse(str, false) }) Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do expect(Puppet::Module.parse_range('>=1.0.0', true).include?(SemanticPuppet::Version.parse('1.0.1-rc1'))).to be_truthy end expect(notices).to include(/VersionRanges will never be strict when using non-vendored SemanticPuppet gem, version 0\.1\.4/) end it 'will not notify when strict ranges can be parsed' do Puppet::Module.instance_variable_set(:@semver_gem_version, SemanticPuppet::Version.parse('1.0.0')) Puppet::Module.instance_variable_set(:@parse_range_method, Proc.new { |str| SemanticPuppet::VersionRange.parse(str, true) }) Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do expect(Puppet::Module.parse_range('>=1.0.0', true).include?(SemanticPuppet::Version.parse('1.0.1-rc1'))).to be_falsey end expect(notices).to be_empty end it 'will not notify when non-strict ranges can be parsed' do Puppet::Module.instance_variable_set(:@semver_gem_version, SemanticPuppet::Version.parse('0.1.4')) Puppet::Module.instance_variable_set(:@parse_range_method, Proc.new { |str| SemanticPuppet::VersionRange.parse(str, false) }) Puppet::Util::Log.with_destination(Puppet::Test::LogCollector.new(logs)) do expect(Puppet::Module.parse_range('>=1.0.0', false).include?(SemanticPuppet::Version.parse('1.0.1-rc1'))).to be_truthy end expect(notices).to be_empty end end end end puppet-5.5.10/spec/unit/module_tool_spec.rb0000644005276200011600000002374113417161722020614 0ustar jenkinsjenkins#! /usr/bin/env ruby # encoding: UTF-8 require 'spec_helper' require 'puppet/module_tool' describe Puppet::ModuleTool do describe '.is_module_root?' do it 'should return true if directory has a metadata.json file' do FileTest.expects(:file?).with(responds_with(:to_s, '/a/b/c/metadata.json')). returns(true) expect(subject.is_module_root?(Pathname.new('/a/b/c'))).to be_truthy end it 'should return false if directory does not have a metadata.json file' do FileTest.expects(:file?).with(responds_with(:to_s, '/a/b/c/metadata.json')). returns(false) expect(subject.is_module_root?(Pathname.new('/a/b/c'))).to be_falsey end end describe '.find_module_root' do let(:sample_path) { Pathname.new('/a/b/c').expand_path } it 'should return the first path as a pathname when it contains a module file' do Puppet::ModuleTool.expects(:is_module_root?).with(sample_path). returns(true) expect(subject.find_module_root(sample_path)).to eq(sample_path) end it 'should return a parent path as a pathname when it contains a module file' do Puppet::ModuleTool.expects(:is_module_root?). with(responds_with(:to_s, File.expand_path('/a/b/c'))).returns(false) Puppet::ModuleTool.expects(:is_module_root?). with(responds_with(:to_s, File.expand_path('/a/b'))).returns(true) expect(subject.find_module_root(sample_path)).to eq(Pathname.new('/a/b').expand_path) end it 'should return nil when no module root can be found' do Puppet::ModuleTool.expects(:is_module_root?).at_least_once.returns(false) expect(subject.find_module_root(sample_path)).to be_nil end end describe '.format_tree' do it 'should return an empty tree when given an empty list' do expect(subject.format_tree([])).to eq('') end it 'should return a shallow when given a list without dependencies' do list = [ { :text => 'first' }, { :text => 'second' }, { :text => 'third' } ] expect(subject.format_tree(list)).to eq <<-TREE ├── first ├── second └── third TREE end it 'should return a deeply nested tree when given a list with deep dependencies' do list = [ { :text => 'first', :dependencies => [ { :text => 'second', :dependencies => [ { :text => 'third' } ] } ] }, ] expect(subject.format_tree(list)).to eq <<-TREE └─┬ first └─┬ second └── third TREE end it 'should show connectors when deep dependencies are not on the last node of the top level' do list = [ { :text => 'first', :dependencies => [ { :text => 'second', :dependencies => [ { :text => 'third' } ] } ] }, { :text => 'fourth' } ] expect(subject.format_tree(list)).to eq <<-TREE ├─┬ first │ └─┬ second │ └── third └── fourth TREE end it 'should show connectors when deep dependencies are not on the last node of any level' do list = [ { :text => 'first', :dependencies => [ { :text => 'second', :dependencies => [ { :text => 'third' } ] }, { :text => 'fourth' } ] } ] expect(subject.format_tree(list)).to eq <<-TREE └─┬ first ├─┬ second │ └── third └── fourth TREE end it 'should show connectors in every case when deep dependencies are not on the last node' do list = [ { :text => 'first', :dependencies => [ { :text => 'second', :dependencies => [ { :text => 'third' } ] }, { :text => 'fourth' } ] }, { :text => 'fifth' } ] expect(subject.format_tree(list)).to eq <<-TREE ├─┬ first │ ├─┬ second │ │ └── third │ └── fourth └── fifth TREE end end describe '.set_option_defaults' do let(:options) { {} } let(:modulepath) { ['/env/module/path', '/global/module/path'] } let(:environment_name) { :current_environment } let(:environment) { Puppet::Node::Environment.create(environment_name, modulepath) } subject do described_class.set_option_defaults(options) options end around do |example| envs = Puppet::Environments::Static.new(environment) Puppet.override(:environments => envs) do example.run end end describe ':environment' do context 'as String' do let(:options) { { :environment => "#{environment_name}" } } it 'assigns the environment with the given name to :environment_instance' do expect(subject).to include :environment_instance => environment end end context 'as Symbol' do let(:options) { { :environment => :"#{environment_name}" } } it 'assigns the environment with the given name to :environment_instance' do expect(subject).to include :environment_instance => environment end end context 'as Puppet::Node::Environment' do let(:env) { Puppet::Node::Environment.create('anonymous', []) } let(:options) { { :environment => env } } it 'assigns the given environment to :environment_instance' do expect(subject).to include :environment_instance => env end end end describe ':modulepath' do let(:options) do { :modulepath => %w[bar foo baz].join(File::PATH_SEPARATOR) } end let(:paths) { options[:modulepath].split(File::PATH_SEPARATOR).map { |dir| File.expand_path(dir) } } it 'is expanded to an absolute path' do expect(subject[:environment_instance].full_modulepath).to eql paths end it 'is used to compute :target_dir' do expect(subject).to include :target_dir => paths.first end context 'conflicts with :environment' do let(:options) do { :modulepath => %w[bar foo baz].join(File::PATH_SEPARATOR), :environment => environment_name } end it 'replaces the modulepath of the :environment_instance' do expect(subject[:environment_instance].full_modulepath).to eql paths end it 'is used to compute :target_dir' do expect(subject).to include :target_dir => paths.first end end end describe ':strict_semver' do context 'when set' do let(:options) do { :strict_semver => true } end it 'is not overridden by default' do expect(subject).to include :strict_semver => true end end context 'when unset' do let(:options) do { } end it 'defaults to false' do expect(subject).to include :strict_semver => false end end end describe ':target_dir' do let(:options) do { :target_dir => 'foo' } end let(:target) { File.expand_path(options[:target_dir]) } it 'is expanded to an absolute path' do expect(subject).to include :target_dir => target end it 'is prepended to the modulepath of the :environment_instance' do expect(subject[:environment_instance].full_modulepath.first).to eql target end context 'conflicts with :modulepath' do let(:options) do { :target_dir => 'foo', :modulepath => %w[bar foo baz].join(File::PATH_SEPARATOR) } end it 'is prepended to the modulepath of the :environment_instance' do expect(subject[:environment_instance].full_modulepath.first).to eql target end it 'shares the provided :modulepath via the :environment_instance' do paths = %w[foo] + options[:modulepath].split(File::PATH_SEPARATOR) paths.map! { |dir| File.expand_path(dir) } expect(subject[:environment_instance].full_modulepath).to eql paths end end context 'conflicts with :environment' do let(:options) do { :target_dir => 'foo', :environment => environment_name } end it 'is prepended to the modulepath of the :environment_instance' do expect(subject[:environment_instance].full_modulepath.first).to eql target end it 'shares the provided :modulepath via the :environment_instance' do paths = %w[foo] + environment.full_modulepath paths.map! { |dir| File.expand_path(dir) } expect(subject[:environment_instance].full_modulepath).to eql paths end end context 'when not passed' do it 'is populated with the first component of the modulepath' do expect(subject).to include :target_dir => subject[:environment_instance].full_modulepath.first end end end end describe '.parse_module_dependency' do it 'parses a dependency without a version range expression' do name, range, expr = subject.parse_module_dependency('source', 'name' => 'foo-bar') expect(name).to eql('foo-bar') expect(range).to eql(SemanticPuppet::VersionRange.parse('>= 0.0.0')) expect(expr).to eql('>= 0.0.0') end it 'parses a dependency with a version range expression' do name, range, expr = subject.parse_module_dependency('source', 'name' => 'foo-bar', 'version_requirement' => '1.2.x') expect(name).to eql('foo-bar') expect(range).to eql(SemanticPuppet::VersionRange.parse('1.2.x')) expect(expr).to eql('1.2.x') end it 'does not raise an error on invalid version range expressions' do name, range, expr = subject.parse_module_dependency('source', 'name' => 'foo-bar', 'version_requirement' => 'nope') expect(name).to eql('foo-bar') expect(range).to eql(SemanticPuppet::VersionRange::EMPTY_RANGE) expect(expr).to eql('nope') end end end puppet-5.5.10/spec/unit/node_spec.rb0000644005276200011600000004206513417161722017217 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'matchers/json' require 'puppet_spec/files' describe Puppet::Node do include JSONMatchers include PuppetSpec::Files let(:environment) { Puppet::Node::Environment.create(:bar, []) } let(:env_loader) { Puppet::Environments::Static.new(environment) } describe "when managing its environment" do it "provides an environment instance" do expect(Puppet::Node.new("foo", :environment => environment).environment.name).to eq(:bar) end context "present in a loader" do around do |example| Puppet.override(:environments => env_loader) do example.run end end it "uses any set environment" do expect(Puppet::Node.new("foo", :environment => "bar").environment).to eq(environment) end it "determines its environment from its parameters if no environment is set" do expect(Puppet::Node.new("foo", :parameters => {"environment" => :bar}).environment).to eq(environment) end it "uses the configured environment if no environment is provided" do Puppet[:environment] = environment.name.to_s expect(Puppet::Node.new("foo").environment).to eq(environment) end it "allows the environment to be set after initialization" do node = Puppet::Node.new("foo") node.environment = :bar expect(node.environment.name).to eq(:bar) end it "allows its environment to be set by parameters after initialization" do node = Puppet::Node.new("foo") node.parameters["environment"] = :bar expect(node.environment.name).to eq(:bar) end end end describe "serialization" do around do |example| Puppet.override(:environments => env_loader) do example.run end end it "can round-trip through json" do Puppet::Node::Facts.new("hello", "one" => "c", "two" => "b") node = Puppet::Node.new("hello", :environment => 'bar', :classes => ['erth', 'aiu'], :parameters => {"hostname"=>"food"} ) new_node = Puppet::Node.convert_from('json', node.render('json')) expect(new_node.environment).to eq(node.environment) expect(new_node.parameters).to eq(node.parameters) expect(new_node.classes).to eq(node.classes) expect(new_node.name).to eq(node.name) end it "validates against the node json schema", :unless => Puppet.features.microsoft_windows? do Puppet::Node::Facts.new("hello", "one" => "c", "two" => "b") node = Puppet::Node.new("hello", :environment => 'bar', :classes => ['erth', 'aiu'], :parameters => {"hostname"=>"food"} ) expect(node.to_json).to validate_against('api/schemas/node.json') end it "when missing optional parameters validates against the node json schema", :unless => Puppet.features.microsoft_windows? do Puppet::Node::Facts.new("hello", "one" => "c", "two" => "b") node = Puppet::Node.new("hello", :environment => 'bar' ) expect(node.to_json).to validate_against('api/schemas/node.json') end it "should allow its environment parameter to be set by attribute after initialization" do node = Puppet::Node.new("foo", { :parameters => { 'environment' => :foo } }) node.environment_name = :foo node.environment = :bar expect(node.environment_name).to eq(:bar) expect(node.parameters['environment']).to eq('bar') end it 'to_data_hash returns value that is instance of to Data' do node = Puppet::Node.new("hello", :environment => 'bar', :classes => ['erth', 'aiu'], :parameters => {"hostname"=>"food"} ) expect(Puppet::Pops::Types::TypeFactory.data.instance?(node.to_data_hash)).to be_truthy end end describe "when serializing using yaml" do before do @node = Puppet::Node.new("mynode") end it "a node can roundtrip" do expect(YAML.load(@node.to_yaml).name).to eql("mynode") end it "limits the serialization of environment to be just the name" do yaml_file = file_containing("temp_yaml", @node.to_yaml) node_yaml = Puppet::Util::Yaml.load_file(yaml_file, false, true) expect(node_yaml['environment']).to eq('production') end end describe "when serializing using yaml and values classes and parameters are missing in deserialized hash" do it "a node can roundtrip" do @node = Puppet::Node.from_data_hash({'name' => "mynode"}) expect(YAML.load(@node.to_yaml).name).to eql("mynode") end it "errors if name is nil" do expect { Puppet::Node.from_data_hash({ })}.to raise_error(ArgumentError, /No name provided in serialized data/) end end describe "when converting to json" do before do @node = Puppet::Node.new("mynode") end it "provide its name" do expect(@node).to set_json_attribute('name').to("mynode") end it "includes the classes if set" do @node.classes = %w{a b c} expect(@node).to set_json_attribute("classes").to(%w{a b c}) end it "does not include the classes if there are none" do expect(@node).to_not set_json_attribute('classes') end it "includes parameters if set" do @node.parameters = {"a" => "b", "c" => "d"} expect(@node).to set_json_attribute('parameters').to({"a" => "b", "c" => "d"}) end it "does not include the environment parameter in the json" do @node.parameters = {"a" => "b", "c" => "d"} @node.environment = environment expect(@node.parameters).to eq({"a"=>"b", "c"=>"d", "environment"=>"bar"}) expect(@node).to set_json_attribute('parameters').to({"a" => "b", "c" => "d"}) end it "does not include the parameters if there are none" do expect(@node).to_not set_json_attribute('parameters') end it "includes the environment" do @node.environment = "production" expect(@node).to set_json_attribute('environment').to('production') end end describe "when converting from json" do before do @node = Puppet::Node.new("mynode") @format = Puppet::Network::FormatHandler.format('json') end def from_json(json) @format.intern(Puppet::Node, json) end it "sets its name" do expect(Puppet::Node).to read_json_attribute('name').from(@node.to_json).as("mynode") end it "includes the classes if set" do @node.classes = %w{a b c} expect(Puppet::Node).to read_json_attribute('classes').from(@node.to_json).as(%w{a b c}) end it "includes parameters if set" do @node.parameters = {"a" => "b", "c" => "d"} expect(Puppet::Node).to read_json_attribute('parameters').from(@node.to_json).as({"a" => "b", "c" => "d", "environment" => "production"}) end it "deserializes environment to environment_name as a symbol" do Puppet.override(:environments => env_loader) do @node.environment = environment expect(Puppet::Node).to read_json_attribute('environment_name').from(@node.to_json).as(:bar) end end it "does not immediately populate the environment instance" do node = described_class.from_data_hash("name" => "foo", "environment" => "production") expect(node.environment_name).to eq(:production) expect(node).not_to be_has_environment_instance node.environment expect(node).to be_has_environment_instance end end end describe Puppet::Node, "when initializing" do before do @node = Puppet::Node.new("testnode") end it "sets the node name" do expect(@node.name).to eq("testnode") end it "does not allow nil node names" do expect { Puppet::Node.new(nil) }.to raise_error(ArgumentError) end it "defaults to an empty parameter hash" do expect(@node.parameters).to eq({}) end it "defaults to an empty class array" do expect(@node.classes).to eq([]) end it "notes its creation time" do expect(@node.time).to be_instance_of(Time) end it "accepts parameters passed in during initialization" do params = {"a" => "b"} @node = Puppet::Node.new("testing", :parameters => params) expect(@node.parameters).to eq(params) end it "accepts classes passed in during initialization" do classes = %w{one two} @node = Puppet::Node.new("testing", :classes => classes) expect(@node.classes).to eq(classes) end it "always returns classes as an array" do @node = Puppet::Node.new("testing", :classes => "myclass") expect(@node.classes).to eq(["myclass"]) end end describe Puppet::Node, "when merging facts" do before do @node = Puppet::Node.new("testnode") Puppet[:facts_terminus] = :memory Puppet::Node::Facts.indirection.save(Puppet::Node::Facts.new(@node.name, "one" => "c", "two" => "b")) end context "when supplied facts as a parameter" do let(:facts) { Puppet::Node::Facts.new(@node.name, "foo" => "bar") } it "accepts facts to merge with the node" do @node.expects(:merge).with({ 'foo' => 'bar' }) @node.fact_merge(facts) end it "will not query the facts indirection if facts are supplied" do Puppet::Node::Facts.indirection.expects(:find).never @node.fact_merge(facts) end end it "recovers with a Puppet::Error if something is thrown from the facts indirection" do Puppet::Node::Facts.indirection.expects(:find).raises "something bad happened in the indirector" expect { @node.fact_merge }.to raise_error(Puppet::Error, /Could not retrieve facts for testnode: something bad happened in the indirector/) end it "prefers parameters already set on the node over facts from the node" do @node = Puppet::Node.new("testnode", :parameters => {"one" => "a"}) @node.fact_merge expect(@node.parameters["one"]).to eq("a") end it "adds passed parameters to the parameter list" do @node = Puppet::Node.new("testnode", :parameters => {"one" => "a"}) @node.fact_merge expect(@node.parameters["two"]).to eq("b") end it "warns when a parameter value is not updated" do @node = Puppet::Node.new("testnode", :parameters => {"one" => "a"}) Puppet.expects(:warning).with('The node parameter \'one\' for node \'testnode\' was already set to \'a\'. It could not be set to \'b\'') @node.merge "one" => "b" end it "accepts arbitrary parameters to merge into its parameters" do @node = Puppet::Node.new("testnode", :parameters => {"one" => "a"}) @node.merge "two" => "three" expect(@node.parameters["two"]).to eq("three") end context "with an env loader" do let(:environment) { Puppet::Node::Environment.create(:one, []) } let(:environment_two) { Puppet::Node::Environment.create(:two, []) } let(:environment_three) { Puppet::Node::Environment.create(:three, []) } let(:environment_prod) { Puppet::Node::Environment.create(:production, []) } let(:env_loader) { Puppet::Environments::Static.new(environment, environment_two, environment_three, environment_prod) } around do |example| Puppet.override(:environments => env_loader) do example.run end end context "when a node is initialized from a data hash" do context "when a node is initialzed with an environment" do it "uses 'environment' when provided" do my_node = Puppet::Node.from_data_hash("environment" => "one", "name" => "my_node") expect(my_node.environment.name).to eq(:one) end it "uses the environment parameter when provided" do my_node = Puppet::Node.from_data_hash("parameters" => {"environment" => "one"}, "name" => "my_node") expect(my_node.environment.name).to eq(:one) end it "uses the environment when also given an environment parameter" do my_node = Puppet::Node.from_data_hash("parameters" => {"environment" => "one"}, "name" => "my_node", "environment" => "two") expect(my_node.environment.name).to eq(:two) end it "uses 'environment' when an environment fact has been merged" do my_node = Puppet::Node.from_data_hash("environment" => "one", "name" => "my_node") my_node.fact_merge Puppet::Node::Facts.new "my_node", "environment" => "two" expect(my_node.environment.name).to eq(:one) end it "uses an environment parameter when an environment fact has been merged" do my_node = Puppet::Node.from_data_hash("parameters" => {"environment" => "one"}, "name" => "my_node") my_node.fact_merge Puppet::Node::Facts.new "my_node", "environment" => "two" expect(my_node.environment.name).to eq(:one) end it "uses an environment when an environment parameter has been given and an environment fact has been merged" do my_node = Puppet::Node.from_data_hash("parameters" => {"environment" => "two"}, "name" => "my_node", "environment" => "one") my_node.fact_merge Puppet::Node::Facts.new "my_node", "environment" => "three" expect(my_node.environment.name).to eq(:one) end end context "when a node is initialized without an environment" do it "should use the server's default environment" do my_node = Puppet::Node.from_data_hash("name" => "my_node") expect(my_node.environment.name).to eq(:production) end it "should use the server's default when an environment fact has been merged" do my_node = Puppet::Node.from_data_hash("name" => "my_node") my_node.fact_merge Puppet::Node::Facts.new "my_node", "environment" => "two" expect(my_node.environment.name).to eq(:production) end end end context "when a node is initialized from new" do context "when a node is initialzed with an environment" do it "adds the environment to the list of parameters" do Puppet[:environment] = "one" @node = Puppet::Node.new("testnode", :environment => "one") @node.merge "two" => "three" expect(@node.parameters["environment"]).to eq("one") end it "when merging, syncs the environment parameter to a node's existing environment" do @node = Puppet::Node.new("testnode", :environment => "one") @node.merge "environment" => "two" expect(@node.parameters["environment"]).to eq("one") end it "merging facts does not override that environment" do @node = Puppet::Node.new("testnode", :environment => "one") @node.fact_merge Puppet::Node::Facts.new "testnode", "environment" => "two" expect(@node.environment.name.to_s).to eq("one") end end context "when a node is initialized without an environment" do it "it perfers an environment name to an environment fact" do @node = Puppet::Node.new("testnode") @node.environment_name = "one" @node.fact_merge Puppet::Node::Facts.new "testnode", "environment" => "two" expect(@node.environment.name.to_s).to eq("one") end end end end end describe Puppet::Node, "when indirecting" do it "defaults to the 'plain' node terminus" do Puppet::Node.indirection.reset_terminus_class expect(Puppet::Node.indirection.terminus_class).to eq(:plain) end end describe Puppet::Node, "when generating the list of names to search through" do before do @node = Puppet::Node.new("foo.domain.com", :parameters => {"hostname" => "yay", "domain" => "domain.com"}) end it "returns an array of names" do expect(@node.names).to be_instance_of(Array) end describe "and the node name is fully qualified" do it "contains an entry for each part of the node name" do expect(@node.names).to include("foo.domain.com") expect(@node.names).to include("foo.domain") expect(@node.names).to include("foo") end end it "includes the node's fqdn" do expect(@node.names).to include("yay.domain.com") end it "combines and include the node's hostname and domain if no fqdn is available" do expect(@node.names).to include("yay.domain.com") end it "contains an entry for each name available by stripping a segment of the fqdn" do @node.parameters["fqdn"] = "foo.deep.sub.domain.com" expect(@node.names).to include("foo.deep.sub.domain") expect(@node.names).to include("foo.deep.sub") end describe "and :node_name is set to 'cert'" do before do Puppet[:strict_hostname_checking] = false Puppet[:node_name] = "cert" end it "uses the passed-in key as the first value" do expect(@node.names[0]).to eq("foo.domain.com") end describe "and strict hostname checking is enabled" do it "only uses the passed-in key" do Puppet[:strict_hostname_checking] = true expect(@node.names).to eq(["foo.domain.com"]) end end end describe "and :node_name is set to 'facter'" do before do Puppet[:strict_hostname_checking] = false Puppet[:node_name] = "facter" end it "uses the node's 'hostname' fact as the first value" do expect(@node.names[0]).to eq("yay") end end end puppet-5.5.10/spec/unit/property_spec.rb0000644005276200011600000004437513417161722020164 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/property' describe Puppet::Property do let :resource do Puppet::Type.type(:host).new :name => "foo" end let :subclass do # We need a completely fresh subclass every time, because we modify both # class and instance level things inside the tests. subclass = Class.new(Puppet::Property) do class << self attr_accessor :name end @name = :foo end subclass.initvars subclass end let :property do subclass.new :resource => resource end it "should be able to look up the modified name for a given value" do subclass.newvalue(:foo) expect(subclass.value_name("foo")).to eq(:foo) end it "should be able to look up the modified name for a given value matching a regex" do subclass.newvalue(%r{.}) expect(subclass.value_name("foo")).to eq(%r{.}) end it "should be able to look up a given value option" do subclass.newvalue(:foo, :event => :whatever) expect(subclass.value_option(:foo, :event)).to eq(:whatever) end it "should be able to specify required features" do expect(subclass).to respond_to(:required_features=) end {"one" => [:one],:one => [:one],%w{a} => [:a],[:b] => [:b],%w{one two} => [:one,:two],[:a,:b] => [:a,:b]}.each { |in_value,out_value| it "should always convert required features into an array of symbols (e.g. #{in_value.inspect} --> #{out_value.inspect})" do subclass.required_features = in_value expect(subclass.required_features).to eq(out_value) end } it "should return its name as a string when converted to a string" do expect(property.to_s).to eq(property.name.to_s) end describe "when returning the default event name" do it "should use the current 'should' value to pick the event name" do property.expects(:should).returns "myvalue" subclass.expects(:value_option).with('myvalue', :event).returns :event_name property.event_name end it "should return any event defined with the specified value" do property.expects(:should).returns :myval subclass.expects(:value_option).with(:myval, :event).returns :event_name expect(property.event_name).to eq(:event_name) end describe "and the property is 'ensure'" do before :each do property.stubs(:name).returns :ensure resource.expects(:type).returns :mytype end it "should use _created if the 'should' value is 'present'" do property.expects(:should).returns :present expect(property.event_name).to eq(:mytype_created) end it "should use _removed if the 'should' value is 'absent'" do property.expects(:should).returns :absent expect(property.event_name).to eq(:mytype_removed) end it "should use _changed if the 'should' value is not 'absent' or 'present'" do property.expects(:should).returns :foo expect(property.event_name).to eq(:mytype_changed) end it "should use _changed if the 'should value is nil" do property.expects(:should).returns nil expect(property.event_name).to eq(:mytype_changed) end end it "should use _changed if the property is not 'ensure'" do property.stubs(:name).returns :myparam property.expects(:should).returns :foo expect(property.event_name).to eq(:myparam_changed) end it "should use _changed if no 'should' value is set" do property.stubs(:name).returns :myparam property.expects(:should).returns nil expect(property.event_name).to eq(:myparam_changed) end end describe "when creating an event" do before :each do property.stubs(:should).returns "myval" end it "should use an event from the resource as the base event" do event = Puppet::Transaction::Event.new resource.expects(:event).returns event expect(property.event).to equal(event) end it "should have the default event name" do property.expects(:event_name).returns :my_event expect(property.event.name).to eq(:my_event) end it "should have the property's name" do expect(property.event.property).to eq(property.name.to_s) end it "should have the 'should' value set" do property.stubs(:should).returns "foo" expect(property.event.desired_value).to eq("foo") end it "should provide its path as the source description" do property.stubs(:path).returns "/my/param" expect(property.event.source_description).to eq("/my/param") end it "should have the 'invalidate_refreshes' value set if set on a value" do property.stubs(:event_name).returns :my_event property.stubs(:should).returns "foo" foo = mock() foo.expects(:invalidate_refreshes).returns(true) collection = mock() collection.expects(:match?).with("foo").returns(foo) property.class.stubs(:value_collection).returns(collection) expect(property.event.invalidate_refreshes).to be_truthy end it "sets the redacted field on the event when the property is sensitive" do property.sensitive = true expect(property.event.redacted).to eq true end end describe "when defining new values" do it "should define a method for each value created with a block that's not a regex" do subclass.newvalue(:foo) { } expect(property).to respond_to(:set_foo) end end describe "when assigning the value" do it "should just set the 'should' value" do property.value = "foo" expect(property.should).to eq("foo") end it "should validate each value separately" do property.expects(:validate).with("one") property.expects(:validate).with("two") property.value = %w{one two} end it "should munge each value separately and use any result as the actual value" do property.expects(:munge).with("one").returns :one property.expects(:munge).with("two").returns :two # Do this so we get the whole array back. subclass.array_matching = :all property.value = %w{one two} expect(property.should).to eq([:one, :two]) end it "should return any set value" do expect(property.value = :one).to eq(:one) end end describe "when returning the value" do it "should return nil if no value is set" do expect(property.should).to be_nil end it "should return the first set 'should' value if :array_matching is set to :first" do subclass.array_matching = :first property.should = %w{one two} expect(property.should).to eq("one") end it "should return all set 'should' values as an array if :array_matching is set to :all" do subclass.array_matching = :all property.should = %w{one two} expect(property.should).to eq(%w{one two}) end it "should default to :first array_matching" do expect(subclass.array_matching).to eq(:first) end it "should unmunge the returned value if :array_matching is set to :first" do property.class.unmunge do |v| v.to_sym end subclass.array_matching = :first property.should = %w{one two} expect(property.should).to eq(:one) end it "should unmunge all the returned values if :array_matching is set to :all" do property.class.unmunge do |v| v.to_sym end subclass.array_matching = :all property.should = %w{one two} expect(property.should).to eq([:one, :two]) end end describe "when validating values" do it "should do nothing if no values or regexes have been defined" do expect { property.should = "foo" }.not_to raise_error end it "should fail if the value is not a defined value or alias and does not match a regex" do subclass.newvalue(:foo) expect { property.should = "bar" }.to raise_error(Puppet::Error, /Invalid value "bar"./) end it "should succeeed if the value is one of the defined values" do subclass.newvalue(:foo) expect { property.should = :foo }.not_to raise_error end it "should succeeed if the value is one of the defined values even if the definition uses a symbol and the validation uses a string" do subclass.newvalue(:foo) expect { property.should = "foo" }.not_to raise_error end it "should succeeed if the value is one of the defined values even if the definition uses a string and the validation uses a symbol" do subclass.newvalue("foo") expect { property.should = :foo }.not_to raise_error end it "should succeed if the value is one of the defined aliases" do subclass.newvalue("foo") subclass.aliasvalue("bar", "foo") expect { property.should = :bar }.not_to raise_error end it "should succeed if the value matches one of the regexes" do subclass.newvalue(/./) expect { property.should = "bar" }.not_to raise_error end it "should validate that all required features are present" do subclass.newvalue(:foo, :required_features => [:a, :b]) resource.provider.expects(:satisfies?).with([:a, :b]).returns true property.should = :foo end it "should fail if required features are missing" do subclass.newvalue(:foo, :required_features => [:a, :b]) resource.provider.expects(:satisfies?).with([:a, :b]).returns false expect { property.should = :foo }.to raise_error(Puppet::Error) end it "should internally raise an ArgumentError if required features are missing" do subclass.newvalue(:foo, :required_features => [:a, :b]) resource.provider.expects(:satisfies?).with([:a, :b]).returns false expect { property.validate_features_per_value :foo }.to raise_error(ArgumentError) end it "should validate that all required features are present for regexes" do subclass.newvalue(/./, :required_features => [:a, :b]) resource.provider.expects(:satisfies?).with([:a, :b]).returns true property.should = "foo" end it "should support specifying an individual required feature" do subclass.newvalue(/./, :required_features => :a) resource.provider.expects(:satisfies?).returns true property.should = "foo" end end describe "when munging values" do it "should do nothing if no values or regexes have been defined" do expect(property.munge("foo")).to eq("foo") end it "should return return any matching defined values" do subclass.newvalue(:foo) expect(property.munge("foo")).to eq(:foo) end it "should return any matching aliases" do subclass.newvalue(:foo) subclass.aliasvalue(:bar, :foo) expect(property.munge("bar")).to eq(:foo) end it "should return the value if it matches a regex" do subclass.newvalue(/./) expect(property.munge("bar")).to eq("bar") end it "should return the value if no other option is matched" do subclass.newvalue(:foo) expect(property.munge("bar")).to eq("bar") end end describe "when syncing the 'should' value" do it "should set the value" do subclass.newvalue(:foo) property.should = :foo property.expects(:set).with(:foo) property.sync end end describe "when setting a value" do it "should catch exceptions and raise Puppet::Error" do subclass.newvalue(:foo) { raise "eh" } expect { property.set(:foo) }.to raise_error(Puppet::Error) end it "fails when the provider does not handle the attribute" do subclass.name = "unknown" expect { property.set(:a_value) }.to raise_error(Puppet::Error) end it "propogates the errors about missing methods from the provider" do provider = resource.provider def provider.bad_method=(value) value.this_method_does_not_exist end subclass.name = :bad_method expect { property.set(:a_value) }.to raise_error(NoMethodError, /this_method_does_not_exist/) end describe "that was defined without a block" do it "should call the settor on the provider" do subclass.newvalue(:bar) resource.provider.expects(:foo=).with :bar property.set(:bar) end it "should generate setter named from :method argument and propagate call to the provider" do subclass.newvalue(:bar, :method => 'set_vv') resource.provider.expects(:foo=).with :bar property.set_vv(:bar) end end describe "that was defined with a block" do it "should call the method created for the value if the value is not a regex" do subclass.newvalue(:bar) {} property.expects(:set_bar) property.set(:bar) end it "should call the provided block if the value is a regex" do subclass.newvalue(/./) { self.test } property.expects(:test) property.set("foo") end end end describe "when producing a change log" do it "should say 'defined' when the current value is 'absent'" do expect(property.change_to_s(:absent, "foo")).to match(/^defined/) end it "should say 'undefined' when the new value is 'absent'" do expect(property.change_to_s("foo", :absent)).to match(/^undefined/) end it "should say 'changed' when neither value is 'absent'" do expect(property.change_to_s("foo", "bar")).to match(/changed/) end end shared_examples_for "#insync?" do # We share a lot of behaviour between the all and first matching, so we # use a shared behaviour set to emulate that. The outside world makes # sure the class, etc, point to the right content. [[], [12], [12, 13]].each do |input| it "should return true if should is empty with is => #{input.inspect}" do property.should = [] expect(property).to be_insync(input) expect(property.insync_values?([], input)).to be true end end end describe "#insync?" do context "array_matching :all" do # `@should` is an array of scalar values, and `is` is an array of scalar values. before :each do property.class.array_matching = :all end it_should_behave_like "#insync?" context "if the should value is an array" do let(:input) { [1,2] } before :each do property.should = input end it "should match if is exactly matches" do val = [1, 2] expect(property).to be_insync val expect(property.insync_values?(input, val)).to be true end it "should match if it matches, but all stringified" do val = ["1", "2"] expect(property).to be_insync val expect(property.insync_values?(input, val)).to be true end it "should not match if some-but-not-all values are stringified" do val = ["1", 2] expect(property).to_not be_insync val expect(property.insync_values?(input, val)).to_not be true val = [1, "2"] expect(property).to_not be_insync val expect(property.insync_values?(input, val)).to_not be true end it "should not match if order is different but content the same" do val = [2, 1] expect(property).to_not be_insync val expect(property.insync_values?(input, val)).to_not be true end it "should not match if there are more items in should than is" do val = [1] expect(property).to_not be_insync val expect(property.insync_values?(input, val)).to_not be true end it "should not match if there are less items in should than is" do val = [1, 2, 3] expect(property).to_not be_insync val expect(property.insync_values?(input, val)).to_not be true end it "should not match if `is` is empty but `should` isn't" do val = [] expect(property).to_not be_insync val expect(property.insync_values?(input, val)).to_not be true end end end context "array_matching :first" do # `@should` is an array of scalar values, and `is` is a scalar value. before :each do property.class.array_matching = :first end it_should_behave_like "#insync?" [[1], # only the value [1, 2], # matching value first [2, 1], # matching value last [0, 1, 2], # matching value in the middle ].each do |input| it "should by true if one unmodified should value of #{input.inspect} matches what is" do val = 1 property.should = input expect(property).to be_insync val expect(property.insync_values?(input, val)).to be true end it "should be true if one stringified should value of #{input.inspect} matches what is" do val = "1" property.should = input expect(property).to be_insync val expect(property.insync_values?(input, val)).to be true end end it "should not match if we expect a string but get the non-stringified value" do property.should = ["1"] expect(property).to_not be_insync 1 expect(property.insync_values?(["1"], 1)).to_not be true end [[0], [0, 2]].each do |input| it "should not match if no should values match what is" do property.should = input expect(property).to_not be_insync 1 expect(property.insync_values?(input, 1)).to_not be true expect(property).to_not be_insync "1" # shouldn't match either. expect(property.insync_values?(input, "1")).to_not be true end end end end describe "#property_matches?" do [1, "1", [1], :one].each do |input| it "should treat two equal objects as equal (#{input.inspect})" do expect(property.property_matches?(input, input)).to be_truthy end end it "should treat two objects as equal if the first argument is the stringified version of the second" do expect(property.property_matches?("1", 1)).to be_truthy end it "should NOT treat two objects as equal if the first argument is not a string, and the second argument is a string, even if it stringifies to the first" do expect(property.property_matches?(1, "1")).to be_falsey end end describe "#insync_values?" do it "should log an exception when insync? throws one" do property.expects(:insync?).raises ArgumentError expect(property.insync_values?("foo","bar")).to be nil end end end puppet-5.5.10/spec/unit/provider_spec.rb0000644005276200011600000005712013417161722020122 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' def existing_command Puppet.features.microsoft_windows? ? "cmd" : "echo" end describe Puppet::Provider do before :each do Puppet::Type.newtype(:test) do newparam(:name) { isnamevar } end end after :each do Puppet::Type.rmtype(:test) end let :type do Puppet::Type.type(:test) end let :provider do type.provide(:default) {} end subject { provider } describe "has command" do it "installs a method to run the command specified by the path" do echo_command = expect_command_executed(:echo, "/bin/echo", "an argument") allow_creation_of(echo_command) provider = provider_of do has_command(:echo, "/bin/echo") end provider.echo("an argument") end it "installs a command that is run with a given environment" do echo_command = expect_command_executed(:echo, "/bin/echo", "an argument") allow_creation_of(echo_command, { :EV => "value", :OTHER => "different" }) provider = provider_of do has_command(:echo, "/bin/echo") do environment :EV => "value", :OTHER => "different" end end provider.echo("an argument") end it "is required by default" do provider = provider_of do has_command(:does_not_exist, "/does/not/exist") end expect(provider).not_to be_suitable end it "is required by default" do provider = provider_of do has_command(:does_exist, File.expand_path("/exists/somewhere")) end file_exists_and_is_executable(File.expand_path("/exists/somewhere")) expect(provider).to be_suitable end it "can be specified as optional" do provider = provider_of do has_command(:does_not_exist, "/does/not/exist") do is_optional end end expect(provider).to be_suitable end end describe "has required commands" do it "installs methods to run executables by path" do echo_command = expect_command_executed(:echo, "/bin/echo", "an argument") ls_command = expect_command_executed(:ls, "/bin/ls") allow_creation_of(echo_command) allow_creation_of(ls_command) provider = provider_of do commands :echo => "/bin/echo", :ls => "/bin/ls" end provider.echo("an argument") provider.ls end it "allows the provider to be suitable if the executable is present" do provider = provider_of do commands :always_exists => File.expand_path("/this/command/exists") end file_exists_and_is_executable(File.expand_path("/this/command/exists")) expect(provider).to be_suitable end it "does not allow the provider to be suitable if the executable is not present" do provider = provider_of do commands :does_not_exist => "/this/command/does/not/exist" end expect(provider).not_to be_suitable end end describe "has optional commands" do it "installs methods to run executables" do echo_command = expect_command_executed(:echo, "/bin/echo", "an argument") ls_command = expect_command_executed(:ls, "/bin/ls") allow_creation_of(echo_command) allow_creation_of(ls_command) provider = provider_of do optional_commands :echo => "/bin/echo", :ls => "/bin/ls" end provider.echo("an argument") provider.ls end it "allows the provider to be suitable even if the executable is not present" do provider = provider_of do optional_commands :does_not_exist => "/this/command/does/not/exist" end expect(provider).to be_suitable end end it "should have a specifity class method" do expect(Puppet::Provider).to respond_to(:specificity) end it "should be Comparable" do res = Puppet::Type.type(:notify).new(:name => "res") # Normally I wouldn't like the stubs, but the only way to name a class # otherwise is to assign it to a constant, and that hurts more here in # testing world. --daniel 2012-01-29 a = Class.new(Puppet::Provider).new(res) a.class.stubs(:name).returns "Puppet::Provider::Notify::A" b = Class.new(Puppet::Provider).new(res) b.class.stubs(:name).returns "Puppet::Provider::Notify::B" c = Class.new(Puppet::Provider).new(res) c.class.stubs(:name).returns "Puppet::Provider::Notify::C" [[a, b, c], [a, c, b], [b, a, c], [b, c, a], [c, a, b], [c, b, a]].each do |this| expect(this.sort).to eq([a, b, c]) end expect(a).to be < b expect(a).to be < c expect(b).to be > a expect(b).to be < c expect(c).to be > a expect(c).to be > b [a, b, c].each {|x| expect(a).to be <= x } [a, b, c].each {|x| expect(c).to be >= x } expect(b).to be_between(a, c) end context "when creating instances" do context "with a resource" do let :resource do type.new(:name => "fred") end subject { provider.new(resource) } it "should set the resource correctly" do expect(subject.resource).to equal resource end it "should set the name from the resource" do expect(subject.name).to eq(resource.name) end end context "with a hash" do subject { provider.new(:name => "fred") } it "should set the name" do expect(subject.name).to eq("fred") end it "should not have a resource" do expect(subject.resource).to be_nil end end context "with no arguments" do subject { provider.new } it "should raise an internal error if asked for the name" do expect { subject.name }.to raise_error Puppet::DevError end it "should not have a resource" do expect(subject.resource).to be_nil end end end context "when confining" do it "should be suitable by default" do expect(subject).to be_suitable end it "should not be default by default" do expect(subject).not_to be_default end { { :true => true } => true, { :true => false } => false, { :false => false } => true, { :false => true } => false, { :operatingsystem => Facter.value(:operatingsystem) } => true, { :operatingsystem => :yayness } => false, { :nothing => :yayness } => false, { :exists => Puppet::Util.which(existing_command) } => true, { :exists => "/this/file/does/not/exist" } => false, { :true => true, :exists => Puppet::Util.which(existing_command) } => true, { :true => true, :exists => "/this/file/does/not/exist" } => false, { :operatingsystem => Facter.value(:operatingsystem), :exists => Puppet::Util.which(existing_command) } => true, { :operatingsystem => :yayness, :exists => Puppet::Util.which(existing_command) } => false, { :operatingsystem => Facter.value(:operatingsystem), :exists => "/this/file/does/not/exist" } => false, { :operatingsystem => :yayness, :exists => "/this/file/does/not/exist" } => false, }.each do |confines, result| it "should confine #{confines.inspect} to #{result}" do confines.each {|test, value| subject.confine test => value } if result expect(subject).to be_suitable else expect(subject).to_not be_suitable end end end it "should not override a confine even if a second has the same type" do subject.confine :true => false expect(subject).not_to be_suitable subject.confine :true => true expect(subject).not_to be_suitable end it "should not be suitable if any confine fails" do subject.confine :true => false expect(subject).not_to be_suitable 10.times do subject.confine :true => true expect(subject).not_to be_suitable end end end context "default providers" do let :os do Facter.value(:operatingsystem) end it { is_expected.to respond_to :specificity } it "should find the default provider" do type.provide(:nondefault) {} subject.defaultfor :operatingsystem => os expect(subject.name).to eq(type.defaultprovider.name) end describe "regex matches" do it "should match a singular regex" do Facter.expects(:value).with(:osfamily).at_least_once.returns "solaris" one = type.provide(:one) do defaultfor :osfamily => /solaris/ end expect(one).to be_default end it "should not match a non-matching regex " do Facter.expects(:value).with(:osfamily).at_least_once.returns "redhat" one = type.provide(:one) do defaultfor :osfamily => /solaris/ end expect(one).to_not be_default end it "should allow a mix of regex and string" do Facter.expects(:value).with(:operatingsystem).at_least_once.returns "fedora" Facter.expects(:value).with(:operatingsystemmajrelease).at_least_once.returns "24" one = type.provide(:one) do defaultfor :operatingsystem => "fedora", :operatingsystemmajrelease => /^2[2-9]$/ end two = type.provide(:two) do defaultfor :operatingsystem => /fedora/, :operatingsystemmajrelease => '24' end expect(one).to be_default expect(two).to be_default end end describe "when there are multiple defaultfor's of equal specificity" do before :each do subject.defaultfor :operatingsystem => :os1 subject.defaultfor :operatingsystem => :os2 end let(:alternate) { type.provide(:alternate) {} } it "should be default for the first defaultfor" do Facter.expects(:value).with(:operatingsystem).at_least_once.returns :os1 expect(provider).to be_default expect(alternate).not_to be_default end it "should be default for the last defaultfor" do Facter.expects(:value).with(:operatingsystem).at_least_once.returns :os2 expect(provider).to be_default expect(alternate).not_to be_default end end describe "when there are multiple defaultfor's with different specificity" do before :each do subject.defaultfor :operatingsystem => :os1 subject.defaultfor :operatingsystem => :os2, :operatingsystemmajrelease => "42" subject.defaultfor :operatingsystem => :os3, :operatingsystemmajrelease => /^4[2-9]$/ end let(:alternate) { type.provide(:alternate) {} } it "should be default for a more specific, but matching, defaultfor" do Facter.expects(:value).with(:operatingsystem).at_least_once.returns :os2 Facter.expects(:value).with(:operatingsystemmajrelease).at_least_once.returns "42" expect(provider).to be_default expect(alternate).not_to be_default end it "should be default for a more specific, but matching, defaultfor with regex" do Facter.expects(:value).with(:operatingsystem).at_least_once.returns :os3 Facter.expects(:value).with(:operatingsystemmajrelease).at_least_once.returns "42" expect(provider).to be_default expect(alternate).not_to be_default end it "should be default for a less specific, but matching, defaultfor" do Facter.expects(:value).with(:operatingsystem).at_least_once.returns :os1 expect(provider).to be_default expect(alternate).not_to be_default end end it "should consider any true value enough to be default" do alternate = type.provide(:alternate) {} subject.defaultfor :operatingsystem => [:one, :two, :three, os] expect(subject.name).to eq(type.defaultprovider.name) expect(subject).to be_default expect(alternate).not_to be_default end it "should not be default if the defaultfor doesn't match" do expect(subject).not_to be_default subject.defaultfor :operatingsystem => :one expect(subject).not_to be_default end it "should consider two defaults to be higher specificity than one default" do Facter.expects(:value).with(:osfamily).at_least_once.returns "solaris" Facter.expects(:value).with(:operatingsystemrelease).at_least_once.returns "5.10" one = type.provide(:one) do defaultfor :osfamily => "solaris" end two = type.provide(:two) do defaultfor :osfamily => "solaris", :operatingsystemrelease => "5.10" end expect(two.specificity).to be > one.specificity end it "should consider a subclass more specific than its parent class" do parent = type.provide(:parent) child = type.provide(:child, :parent => parent) expect(child.specificity).to be > parent.specificity end describe "using a :feature key" do before :each do Puppet.features.add(:yay) do true end Puppet.features.add(:boo) do false end end it "is default for an available feature" do one = type.provide(:one) do defaultfor :feature => :yay end expect(one).to be_default end it "is not default for a missing feature" do two = type.provide(:two) do defaultfor :feature => :boo end expect(two).not_to be_default end end end context "provider commands" do it "should raise for unknown commands" do expect { subject.command(:something) }.to raise_error(Puppet::DevError) end it "should handle command inheritance" do parent = type.provide("parent") child = type.provide("child", :parent => parent.name) command = Puppet::Util.which('sh') || Puppet::Util.which('cmd.exe') parent.commands :sh => command expect(Puppet::FileSystem.exist?(parent.command(:sh))).to be_truthy expect(parent.command(:sh)).to match(/#{Regexp.escape(command)}$/) expect(Puppet::FileSystem.exist?(child.command(:sh))).to be_truthy expect(child.command(:sh)).to match(/#{Regexp.escape(command)}$/) end it "#1197: should find commands added in the same run" do subject.commands :testing => "puppet-bug-1197" expect(subject.command(:testing)).to be_nil subject.stubs(:which).with("puppet-bug-1197").returns("/puppet-bug-1197") expect(subject.command(:testing)).to eq("/puppet-bug-1197") # Ideally, we would also test that `suitable?` returned the right thing # here, but it is impossible to get access to the methods that do that # without digging way down into the implementation. --daniel 2012-03-20 end context "with optional commands" do before :each do subject.optional_commands :cmd => "/no/such/binary/exists" end it { is_expected.to be_suitable } it "should not be suitable if a mandatory command is also missing" do subject.commands :foo => "/no/such/binary/either" expect(subject).not_to be_suitable end it "should define a wrapper for the command" do expect(subject).to respond_to(:cmd) end it "should return nil if the command is requested" do expect(subject.command(:cmd)).to be_nil end it "should raise if the command is invoked" do expect { subject.cmd }.to raise_error(Puppet::Error, /Command cmd is missing/) end end end context "execution" do before :each do Puppet.expects(:deprecation_warning).never end it "delegates instance execute to Puppet::Util::Execution" do Puppet::Util::Execution.expects(:execute).with("a_command", { :option => "value" }) provider.new.send(:execute, "a_command", { :option => "value" }) end it "delegates class execute to Puppet::Util::Execution" do Puppet::Util::Execution.expects(:execute).with("a_command", { :option => "value" }) provider.send(:execute, "a_command", { :option => "value" }) end it "delegates instance execpipe to Puppet::Util::Execution" do block = Proc.new { } Puppet::Util::Execution.expects(:execpipe).with("a_command", true, block) provider.new.send(:execpipe, "a_command", true, block) end it "delegates class execpipe to Puppet::Util::Execution" do block = Proc.new { } Puppet::Util::Execution.expects(:execpipe).with("a_command", true, block) provider.send(:execpipe, "a_command", true, block) end it "delegates instance execfail to Puppet::Util::Execution" do Puppet::Util::Execution.expects(:execfail).with("a_command", "an exception to raise") provider.new.send(:execfail, "a_command", "an exception to raise") end it "delegates class execfail to Puppet::Util::Execution" do Puppet::Util::Execution.expects(:execfail).with("a_command", "an exception to raise") provider.send(:execfail, "a_command", "an exception to raise") end end context "mk_resource_methods" do before :each do type.newproperty(:prop) type.newparam(:param) provider.mk_resource_methods end let(:instance) { provider.new(nil) } it "defaults to :absent" do expect(instance.prop).to eq(:absent) expect(instance.param).to eq(:absent) end it "should update when set" do instance.prop = 'hello' instance.param = 'goodbye' expect(instance.prop).to eq('hello') expect(instance.param).to eq('goodbye') end it "treats nil the same as absent" do instance.prop = "value" instance.param = "value" instance.prop = nil instance.param = nil expect(instance.prop).to eq(:absent) expect(instance.param).to eq(:absent) end it "preserves false as false" do instance.prop = false instance.param = false expect(instance.prop).to eq(false) expect(instance.param).to eq(false) end end context "source" do it "should default to the provider name" do expect(subject.source).to eq(:default) end it "should default to the provider name for a child provider" do expect(type.provide(:sub, :parent => subject.name).source).to eq(:sub) end it "should override if requested" do provider = type.provide(:sub, :parent => subject.name, :source => subject.source) expect(provider.source).to eq(subject.source) end it "should override to anything you want" do expect { subject.source = :banana }.to change { subject.source }. from(:default).to(:banana) end end context "features" do before :each do type.feature :numeric, '', :methods => [:one, :two] type.feature :alpha, '', :methods => [:a, :b] type.feature :nomethods, '' end { :no => { :alpha => false, :numeric => false, :methods => [] }, :numeric => { :alpha => false, :numeric => true, :methods => [:one, :two] }, :alpha => { :alpha => true, :numeric => false, :methods => [:a, :b] }, :all => { :alpha => true, :numeric => true, :methods => [:a, :b, :one, :two] }, :alpha_and_partial => { :alpha => true, :numeric => false, :methods => [:a, :b, :one] }, :numeric_and_partial => { :alpha => false, :numeric => true, :methods => [:a, :one, :two] }, :all_partial => { :alpha => false, :numeric => false, :methods => [:a, :one] }, :other_and_none => { :alpha => false, :numeric => false, :methods => [:foo, :bar] }, :other_and_alpha => { :alpha => true, :numeric => false, :methods => [:foo, :bar, :a, :b] }, }.each do |name, setup| context "with #{name.to_s.gsub('_', ' ')} features" do let :provider do provider = type.provide(name) setup[:methods].map do |method| provider.send(:define_method, method) do true end end type.provider(name) end context "provider class" do subject { provider } it { is_expected.to respond_to(:has_features) } it { is_expected.to respond_to(:has_feature) } it { is_expected.to respond_to(:nomethods?) } it { is_expected.not_to be_nomethods } it { is_expected.to respond_to(:numeric?) } if setup[:numeric] it { is_expected.to be_numeric } it { is_expected.to be_satisfies(:numeric) } else it { is_expected.not_to be_numeric } it { is_expected.not_to be_satisfies(:numeric) } end it { is_expected.to respond_to(:alpha?) } if setup[:alpha] it { is_expected.to be_alpha } it { is_expected.to be_satisfies(:alpha) } else it { is_expected.not_to be_alpha } it { is_expected.not_to be_satisfies(:alpha) } end end context "provider instance" do subject { provider.new } it { is_expected.to respond_to(:numeric?) } if setup[:numeric] it { is_expected.to be_numeric } it { is_expected.to be_satisfies(:numeric) } else it { is_expected.not_to be_numeric } it { is_expected.not_to be_satisfies(:numeric) } end it { is_expected.to respond_to(:alpha?) } if setup[:alpha] it { is_expected.to be_alpha } it { is_expected.to be_satisfies(:alpha) } else it { is_expected.not_to be_alpha } it { is_expected.not_to be_satisfies(:alpha) } end end end end context "feature with no methods" do before :each do type.feature :undemanding, '' end it { is_expected.to respond_to(:undemanding?) } context "when the feature is not declared" do it { is_expected.not_to be_undemanding } it { is_expected.not_to be_satisfies(:undemanding) } end context "when the feature is declared" do before :each do subject.has_feature :undemanding end it { is_expected.to be_undemanding } it { is_expected.to be_satisfies(:undemanding) } end end context "supports_parameter?" do before :each do type.newparam(:no_feature) type.newparam(:one_feature, :required_features => :alpha) type.newparam(:two_features, :required_features => [:alpha, :numeric]) end let :providers do { :zero => type.provide(:zero), :one => type.provide(:one) do has_features :alpha end, :two => type.provide(:two) do has_features :alpha, :numeric end } end { :zero => { :yes => [:no_feature], :no => [:one_feature, :two_features] }, :one => { :yes => [:no_feature, :one_feature], :no => [:two_features] }, :two => { :yes => [:no_feature, :one_feature, :two_features], :no => [] } }.each do |name, data| data[:yes].each do |param| it "should support #{param} with provider #{name}" do expect(providers[name]).to be_supports_parameter(param) end end data[:no].each do |param| it "should not support #{param} with provider #{name}" do expect(providers[name]).not_to be_supports_parameter(param) end end end end end def provider_of(options = {}, &block) type = Puppet::Type.newtype(:dummy) do provide(:dummy, options, &block) end type.provider(:dummy) end def expect_command_executed(name, path, *args) command = Puppet::Provider::Command.new(name, path, Puppet::Util, Puppet::Util::Execution) command.expects(:execute).with(*args) command end def allow_creation_of(command, environment = {}) Puppet::Provider::Command.stubs(:new).with(command.name, command.executable, Puppet::Util, Puppet::Util::Execution, { :failonfail => true, :combine => true, :custom_environment => environment }).returns(command) end def file_exists_and_is_executable(path) FileTest.expects(:file?).with(path).returns(true) FileTest.expects(:executable?).with(path).returns(true) end end puppet-5.5.10/spec/unit/puppet_pal_2pec.rb0000644005276200011600000012331613417161722020341 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/files' describe 'Puppet Pal' do # before { skip("Puppet::Pal is not available on Ruby 1.9.3") if RUBY_VERSION == '1.9.3' } # Require here since it will not work on RUBY < 2.0.0 require 'puppet_pal' include PuppetSpec::Files let(:testing_env) do { 'pal_env' => { 'functions' => functions, 'lib' => { 'puppet' => lib_puppet }, 'manifests' => manifests, 'modules' => modules, 'plans' => plans, 'tasks' => tasks, 'types' => types, }, 'other_env1' => { 'modules' => {} }, 'other_env2' => { 'modules' => {} }, } end let(:functions) { {} } let(:manifests) { {} } let(:modules) { {} } let(:plans) { {} } let(:lib_puppet) { {} } let(:tasks) { {} } let(:types) { {} } let(:environments_dir) { Puppet[:environmentpath] } let(:testing_env_dir) do dir_contained_in(environments_dir, testing_env) env_dir = File.join(environments_dir, 'pal_env') PuppetSpec::Files.record_tmp(env_dir) PuppetSpec::Files.record_tmp(File.join(environments_dir, 'other_env1')) PuppetSpec::Files.record_tmp(File.join(environments_dir, 'other_env2')) env_dir end let(:modules_dir) { File.join(testing_env_dir, 'modules') } # Without any facts - this speeds up the tests that do not require $facts to have any values let(:node_facts) { Hash.new } # TODO: to be used in examples for running in an existing env # let(:env) { Puppet::Node::Environment.create(:testing, [modules_dir]) } context 'in general - without code in modules or env' do let(:modulepath) { [] } context 'deprecated PAL API methods work and' do it '"evaluate_script_string" evaluates a code string in a given tmp environment' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do |ctx| ctx.evaluate_script_string('1+2+3') end expect(result).to eq(6) end it '"evaluate_script_manifest" evaluates a manifest file in a given tmp environment' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do |ctx| manifest = file_containing('testing.pp', "1+2+3+4") ctx.evaluate_script_manifest(manifest) end expect(result).to eq(10) end end context "with a script compiler" do it 'errors if given both configured_by_env and manifest_file' do expect { Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do |ctx| ctx.with_script_compiler(configured_by_env: true, manifest_file: 'undef.pp') {|c| } end }.to raise_error(/manifest_file or code_string cannot be given when configured_by_env is true/) end it 'errors if given both configured_by_env and code_string' do expect { Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do |ctx| ctx.with_script_compiler(configured_by_env: true, code_string: 'undef') {|c| } end }.to raise_error(/manifest_file or code_string cannot be given when configured_by_env is true/) end context "evaluate_string method" do it 'evaluates code string in a given tmp environment' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do |ctx| ctx.with_script_compiler {|c| c.evaluate_string('1+2+3') } end expect(result).to eq(6) end it 'can be evaluated more than once in a given tmp environment - each in fresh compiler' do Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do |ctx| expect( ctx.with_script_compiler {|c| c.evaluate_string('$a = 1+2+3')}).to eq(6) expect { ctx.with_script_compiler {|c| c.evaluate_string('$a') }}.to raise_error(/Unknown variable: 'a'/) end end it 'instantiates definitions in the given code string' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do |pal| pal.with_script_compiler do |compiler| compiler.evaluate_string(<<-CODE) function run_me() { "worked1" } run_me() CODE end end expect(result).to eq('worked1') end end context "evaluate_file method" do it 'evaluates a manifest file in a given tmp environment' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do |ctx| manifest = file_containing('testing.pp', "1+2+3+4") ctx.with_script_compiler {|c| c.evaluate_file(manifest) } end expect(result).to eq(10) end it 'instantiates definitions in the given code string' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do |pal| pal.with_script_compiler do |compiler| manifest = file_containing('testing.pp', (<<-CODE)) function run_me() { "worked1" } run_me() CODE pal.with_script_compiler {|c| c.evaluate_file(manifest) } end end expect(result).to eq('worked1') end end context "variables are supported such that" do it 'they can be set in any scope' do vars = {'a'=> 10, 'x::y' => 20} result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts, variables: vars) do |ctx| ctx.with_script_compiler {|c| c.evaluate_string("1+2+3+4+$a+$x::y")} end expect(result).to eq(40) end it 'an error is raised if a variable name is illegal' do vars = {'_a::b'=> 10} expect do Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts, variables: vars) do |ctx| manifest = file_containing('testing.pp', "ok") ctx.with_script_compiler {|c| c.evaluate_file(manifest) } end end.to raise_error(/has illegal name/) end it 'an error is raised if variable value is not RichData compliant' do vars = {'a'=> ArgumentError.new("not rich data")} expect do Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts, variables: vars) do |ctx| ctx.with_script_compiler {|c| } end end.to raise_error(/has illegal type - got: ArgumentError/) end it 'variable given to script_compiler overrides those given for environment' do vars = {'a'=> 10, 'x::y' => 20} result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts, variables: vars) do |ctx| ctx.with_script_compiler(variables: {'x::y' => 40}) {|c| c.evaluate_string("1+2+3+4+$a+$x::y")} end expect(result).to eq(60) end end context "functions are supported such that" do it '"call_function" calls a function' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do |ctx| manifest = file_containing('afunc.pp', "function myfunc($a) { $a * 2 } ") ctx.with_script_compiler(manifest_file: manifest) {|c| c.call_function('myfunc', 6) } end expect(result).to eq(12) end it '"call_function" accepts a call with a ruby block' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do |ctx| ctx.with_script_compiler {|c| c.call_function('with', 6) {|x| x * 2} } end expect(result).to eq(12) end it '"function_signature" returns a signature of a function' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do |ctx| manifest = file_containing('afunc.pp', "function myfunc(Integer $a) { $a * 2 } ") ctx.with_script_compiler(manifest_file: manifest) do |c| c.function_signature('myfunc') end end expect(result.class).to eq(Puppet::Pal::FunctionSignature) end it '"FunctionSignature#callable_with?" returns boolean if function is callable with given argument values' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do |ctx| manifest = file_containing('afunc.pp', "function myfunc(Integer $a) { $a * 2 } ") ctx.with_script_compiler(manifest_file: manifest) do |c| signature = c.function_signature('myfunc') [ signature.callable_with?([10]), signature.callable_with?(['nope']) ] end end expect(result).to eq([true, false]) end it '"FunctionSignature#callable_with?" calls a given lambda if there is an error' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do |ctx| manifest = file_containing('afunc.pp', "function myfunc(Integer $a) { $a * 2 } ") ctx.with_script_compiler(manifest_file: manifest) do |c| signature = c.function_signature('myfunc') local_result = 'not yay' signature.callable_with?(['nope']) {|error| local_result = error } local_result end end expect(result).to match(/'myfunc' parameter 'a' expects an Integer value, got String/) end it '"FunctionSignature#callable_with?" does not call a given lambda when there is no error' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do |ctx| manifest = file_containing('afunc.pp', "function myfunc(Integer $a) { $a * 2 } ") ctx.with_script_compiler(manifest_file: manifest) do |c| signature = c.function_signature('myfunc') local_result = 'yay' signature.callable_with?([10]) {|error| local_result = 'not yay' } local_result end end expect(result).to eq('yay') end it '"function_signature" gets the signatures from a ruby function with multiple dispatch' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do |ctx| ctx.with_script_compiler {|c| c.function_signature('lookup') } end # check two different signatures of the lookup function expect(result.callable_with?(['key'])).to eq(true) expect(result.callable_with?(['key'], lambda() {|k| })).to eq(true) end it '"function_signature" returns nil if function is not found' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do |ctx| ctx.with_script_compiler {|c| c.function_signature('no_where_to_be_found') } end expect(result).to eq(nil) end it '"FunctionSignature#callables" returns an array of callables' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do |ctx| manifest = file_containing('afunc.pp', "function myfunc(Integer $a) { $a * 2 } ") ctx.with_script_compiler(manifest_file: manifest) do |c| c.function_signature('myfunc').callables end end expect(result.class).to eq(Array) expect(result.all? {|c| c.is_a?(Puppet::Pops::Types::PCallableType)}).to eq(true) end it '"list_functions" returns an array with all function names that can be loaded' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do |ctx| ctx.with_script_compiler {|c| c.list_functions() } end expect(result.is_a?(Array)).to eq(true) expect(result.all? {|s| s.is_a?(Puppet::Pops::Loader::TypedName) }).to eq(true) # there are certainly more than 30 functions in puppet - (56 when writing this, but some refactoring # may take place, so don't want an exact number here - jsut make sure it found "all of them" expect(result.count).to be > 30 end it '"list_functions" filters on name based on a given regexp' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do |ctx| ctx.with_script_compiler {|c| c.list_functions(/epp/) } end expect(result.is_a?(Array)).to eq(true) expect(result.all? {|s| s.is_a?(Puppet::Pops::Loader::TypedName) }).to eq(true) # there are two functions currently that have 'epp' in their name expect(result.count).to eq(2) end end context 'supports plans such that' do it '"plan_signature" returns the signatures of a plan' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do |ctx| manifest = file_containing('afunc.pp', "plan myplan(Integer $a) { } ") ctx.with_script_compiler(manifest_file: manifest) do |c| signature = c.plan_signature('myplan') [ signature.callable_with?({'a' => 10}), signature.callable_with?({'a' => 'nope'}) ] end end expect(result).to eq([true, false]) end it 'a PlanSignature.callable_with? calls a given lambda with any errors as a formatted string' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do |ctx| manifest = file_containing('afunc.pp', "plan myplan(Integer $a, Integer $b) { } ") ctx.with_script_compiler(manifest_file: manifest) do |c| signature = c.plan_signature('myplan') local_result = nil signature.callable_with?({'a' => 'nope'}) {|errors| local_result = errors } local_result end end # Note that errors are indented one space and on separate lines # expect(result).to eq(" parameter 'a' expects an Integer value, got String\n expects a value for parameter 'b'") end it 'a PlanSignature.callable_with? does not call a given lambda if there are no errors' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do |ctx| manifest = file_containing('afunc.pp', "plan myplan(Integer $a) { } ") ctx.with_script_compiler(manifest_file: manifest) do |c| signature = c.plan_signature('myplan') local_result = 'yay' signature.callable_with?({'a' => 1}) {|errors| local_result = 'not yay' } local_result end end expect(result).to eq('yay') end it '"plan_signature" returns nil if plan is not found' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do |ctx| ctx.with_script_compiler {|c| c.plan_signature('no_where_to_be_found') } end expect(result).to be(nil) end it '"PlanSignature#params_type" returns a map of all parameters and their types' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do |ctx| manifest = file_containing('afunc.pp', "plan myplan(Integer $a, String $b) { } ") ctx.with_script_compiler(manifest_file: manifest) do |c| c.plan_signature('myplan').params_type end end expect(result.class).to eq(Puppet::Pops::Types::PStructType) expect(result.to_s).to eq("Struct[{'a' => Integer, 'b' => String}]") end end context 'supports puppet data types such that' do it '"type" parses and returns a Type from a string specification' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do | ctx| manifest = file_containing('main.pp', "type MyType = Float") ctx.with_script_compiler(manifest_file: manifest) {|c| c.type('Variant[Integer, Boolean, MyType]') } end expect(result.is_a?(Puppet::Pops::Types::PVariantType)).to eq(true) expect(result.types.size).to eq(3) expect(result.instance?(3.14)).to eq(true) end it '"create" creates a new object from a puppet data type and args' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do | ctx| ctx.with_script_compiler { |c| c.create(Puppet::Pops::Types::PIntegerType::DEFAULT, '0x10') } end expect(result).to eq(16) end it '"create" creates a new object from puppet data type in string form and args' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do | ctx| ctx.with_script_compiler { |c| c.create('Integer', '010') } end expect(result).to eq(8) end end end context 'supports parsing such that' do it '"parse_string" parses a puppet language string' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do | ctx| ctx.with_script_compiler { |c| c.parse_string('$a = 10') } end expect(result.class).to eq(Puppet::Pops::Model::Program) end { nil => Puppet::Error, '0xWAT' => Puppet::ParseErrorWithIssue, '$0 = 1' => Puppet::ParseErrorWithIssue, 'else 32' => Puppet::ParseErrorWithIssue, }.each_pair do |input, error_class| it "'parse_string' raises an error for invalid input: '#{input}'" do expect { Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do | ctx| ctx.with_script_compiler { |c| c.parse_string(input) } end }.to raise_error(error_class) end end it '"parse_file" parses a puppet language string' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do | ctx| manifest = file_containing('main.pp', "$a = 10") ctx.with_script_compiler { |c| c.parse_file(manifest) } end expect(result.class).to eq(Puppet::Pops::Model::Program) end it "'parse_file' raises an error for invalid input: 'else 32'" do expect { Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do | ctx| manifest = file_containing('main.pp', "else 32") ctx.with_script_compiler { |c| c.parse_file(manifest) } end }.to raise_error(Puppet::ParseErrorWithIssue) end it "'parse_file' raises an error for invalid input, file is not a string" do expect { Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do | ctx| ctx.with_script_compiler { |c| c.parse_file(42) } end }.to raise_error(Puppet::Error) end it 'the "evaluate" method evaluates the parsed AST' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do | ctx| ctx.with_script_compiler { |c| c.evaluate(c.parse_string('10 + 20')) } end expect(result).to eq(30) end it 'the "evaluate" method instantiates definitions when given a Program' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do | ctx| ctx.with_script_compiler { |c| c.evaluate(c.parse_string('function foo() { "yay"}; foo()')) } end expect(result).to eq('yay') end it 'the "evaluate" method does not instantiates definitions when given ast other than Program' do expect do Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do | ctx| ctx.with_script_compiler do |c| program= c.parse_string('function foo() { "yay"}; foo()') c.evaluate(program.body) end end end.to raise_error(/Unknown function: 'foo'/) end it 'the "evaluate_literal" method evaluates AST being a representation of a literal value' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do | ctx| ctx.with_script_compiler { |c| c.evaluate_literal(c.parse_string('{10 => "hello"}')) } end expect(result).to eq({10 => 'hello'}) end it 'the "evaluate_literal" method errors if ast is not representing a literal value' do expect do Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do | ctx| ctx.with_script_compiler { |c| c.evaluate_literal(c.parse_string('{10+1 => "hello"}')) } end end.to raise_error(/does not represent a literal value/) end it 'the "evaluate_literal" method errors if ast contains definitions' do expect do Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do | ctx| ctx.with_script_compiler { |c| c.evaluate_literal(c.parse_string('function foo() { }; 42')) } end end.to raise_error(/does not represent a literal value/) end end end context 'with code in modules and env' do let(:modulepath) { [modules_dir] } let(:metadata_json_a) { { 'name' => 'example/a', 'version' => '0.1.0', 'source' => 'git@github.com/example/example-a.git', 'dependencies' => [{'name' => 'c', 'version_range' => '>=0.1.0'}], 'author' => 'Bob the Builder', 'license' => 'Apache-2.0' } } let(:metadata_json_b) { { 'name' => 'example/b', 'version' => '0.1.0', 'source' => 'git@github.com/example/example-b.git', 'dependencies' => [{'name' => 'c', 'version_range' => '>=0.1.0'}], 'author' => 'Bob the Builder', 'license' => 'Apache-2.0' } } let(:metadata_json_c) { { 'name' => 'example/c', 'version' => '0.1.0', 'source' => 'git@github.com/example/example-c.git', 'dependencies' => [], 'author' => 'Bob the Builder', 'license' => 'Apache-2.0' } } # TODO: there is something amiss with the metadata wrt dependencies - when metadata is present there is an error # that dependencies could not be resolved. Metadata is therefore commented out. # Dependency based visibility is probably something that we should remove... let(:modules) { { 'a' => { 'functions' => a_functions, 'lib' => { 'puppet' => a_lib_puppet }, 'plans' => a_plans, 'tasks' => a_tasks, 'types' => a_types, # 'metadata.json' => metadata_json_a.to_json }, 'b' => { 'functions' => b_functions, 'lib' => b_lib, 'plans' => b_plans, 'tasks' => b_tasks, 'types' => b_types, # 'metadata.json' => metadata_json_b.to_json }, 'c' => { 'types' => c_types, # 'metadata.json' => metadata_json_c.to_json }, } } let(:a_plans) { { 'aplan.pp' => <<-PUPPET.unindent, plan a::aplan() { 'a::aplan value' } PUPPET } } let(:a_types) { { 'atype.pp' => <<-PUPPET.unindent, type A::Atype = Integer PUPPET } } let(:a_tasks) { { 'atask' => '', } } let(:a_functions) { { 'afunc.pp' => 'function a::afunc() { "a::afunc value" }', } } let(:a_lib_puppet) { { 'functions' => { 'a' => { 'arubyfunc.rb' => <<-RUBY.unindent, require 'stuff/something' Puppet::Functions.create_function(:'a::arubyfunc') do def arubyfunc Stuff::SOMETHING end end RUBY 'myscriptcompilerfunc.rb' => <<-RUBY.unindent, Puppet::Functions.create_function(:'a::myscriptcompilerfunc', Puppet::Functions::InternalFunction) do dispatch :myscriptcompilerfunc do script_compiler_param param 'String',:name end def myscriptcompilerfunc(script_compiler, name) script_compiler.is_a?(Puppet::Pal::ScriptCompiler) ? name : 'no go' end end RUBY } } } } let(:b_plans) { { 'aplan.pp' => <<-PUPPET.unindent, plan b::aplan() {} PUPPET } } let(:b_types) { { 'atype.pp' => <<-PUPPET.unindent, type B::Atype = Integer PUPPET } } let(:b_tasks) { { 'atask' => "# doing exactly nothing\n", 'atask.json' => <<-JSONTEXT.unindent { "description": "test task b::atask", "input_method": "stdin", "parameters": { "string_param": { "description": "A string parameter", "type": "String[1]" }, "int_param": { "description": "An integer parameter", "type": "Integer" } } } JSONTEXT } } let(:b_functions) { { 'afunc.pp' => 'function b::afunc() {}', } } let(:b_lib) { { 'puppet' => b_lib_puppet, 'stuff' => { 'something.rb' => "module Stuff; SOMETHING = 'something'; end" } } } let(:b_lib_puppet) { { 'functions' => { 'b' => { 'arubyfunc.rb' => "Puppet::Functions.create_function(:'b::arubyfunc') { def arubyfunc; 'arubyfunc_value'; end }", } } } } let(:c_types) { { 'atype.pp' => <<-PUPPET.unindent, type C::Atype = Integer PUPPET } } context 'configured as temporary environment such that' do it 'modules are available' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do |ctx| ctx.with_script_compiler {|c| c.evaluate_string('a::afunc()') } end expect(result).to eq("a::afunc value") end it 'libs in a given "modulepath" are added to the Ruby $LOAD_PATH' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do |ctx| ctx.with_script_compiler {|c| c.evaluate_string('a::arubyfunc()') } end expect(result).to eql('something') end it 'errors if a block is not given to in_tmp_environment' do expect do Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) end.to raise_error(/A block must be given to 'in_tmp_environment/) end it 'errors if an env_name is given and is not a String[1]' do expect do Puppet::Pal.in_tmp_environment('', modulepath: modulepath, facts: node_facts) { |ctx| } end.to raise_error(/temporary environment name has wrong type/) expect do Puppet::Pal.in_tmp_environment(32, modulepath: modulepath, facts: node_facts) { |ctx| } end.to raise_error(/temporary environment name has wrong type/) end { 'a hash' => {'a' => 'hm'}, 'an integer' => 32, 'separated strings' => 'dir1;dir2', 'empty string in array' => [''] }.each_pair do |what, value| it "errors if modulepath is #{what}" do expect do Puppet::Pal.in_tmp_environment('pal_env', modulepath: value, facts: node_facts) { |ctx| } end.to raise_error(/modulepath has wrong type/) end end context 'facts are supported such that' do it 'they are obtained if they are not given' do testing_env_dir # creates the structure result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath ) do |ctx| ctx.with_script_compiler {|c| c.evaluate_string("$facts =~ Hash and $facts[puppetversion] == '#{Puppet.version}'") } end expect(result).to eq(true) end it 'can be given as a hash when creating the environment' do testing_env_dir # creates the structure result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: { 'myfact' => 42 }) do |ctx| ctx.with_script_compiler {|c| c.evaluate_string("$facts =~ Hash and $facts[myfact] == 42") } end expect(result).to eq(true) end it 'can be overridden with a hash when creating a script compiler' do testing_env_dir # creates the structure result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: { 'myfact' => 42 }) do |ctx| ctx.with_script_compiler(facts: { 'myfact' => 43 }) {|c| c.evaluate_string("$facts =~ Hash and $facts[myfact] == 43") } end expect(result).to eq(true) end end context 'supports tasks such that' do it '"task_signature" returns the signatures of a generic task' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do |ctx| ctx.with_script_compiler do |c| signature = c.task_signature('a::atask') [ signature.runnable_with?('whatever' => 10), signature.runnable_with?('anything_goes' => 'foo') ] end end expect(result).to eq([true, true]) end it '"TaskSignature#runnable_with?" calls a given lambda if there is an error in a generic task' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do |ctx| ctx.with_script_compiler do |c| signature = c.task_signature('a::atask') local_result = 'not yay' signature.runnable_with?('string_param' => /not data/) {|error| local_result = error } local_result end end expect(result).to match(/Task a::atask:\s+entry 'string_param' expects a Data value, got Regexp/m) end it '"task_signature" returns the signatures of a task defined with metadata' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do |ctx| ctx.with_script_compiler do |c| signature = c.task_signature('b::atask') [ signature.runnable_with?('string_param' => 'foo', 'int_param' => 10), signature.runnable_with?('anything_goes' => 'foo'), signature.task_hash['name'], signature.task_hash['parameters']['string_param']['description'], signature.task.description, signature.task.parameters['int_param']['type'], ] end end expect(result).to eq([true, false, 'b::atask', 'A string parameter', 'test task b::atask', Puppet::Pops::Types::PIntegerType::DEFAULT]) end it '"TaskSignature#runnable_with?" calls a given lambda if there is an error' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do |ctx| ctx.with_script_compiler do |c| signature = c.task_signature('b::atask') local_result = 'not yay' signature.runnable_with?('string_param' => 10) {|error| local_result = error } local_result end end expect(result).to match(/Task b::atask:\s+parameter 'string_param' expects a String value, got Integer/m) end it '"task_signature" returns nil if task is not found' do result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do |ctx| ctx.with_script_compiler {|c| c.task_signature('no_where_to_be_found') } end expect(result).to be(nil) end it '"list_tasks" returns an array with all tasks that can be loaded' do testing_env_dir # creates the structure result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do |ctx| ctx.with_script_compiler {|c| c.list_tasks() } end expect(result.is_a?(Array)).to eq(true) expect(result.all? {|s| s.is_a?(Puppet::Pops::Loader::TypedName) }).to eq(true) expect(result.map {|tn| tn.name}).to contain_exactly('a::atask', 'b::atask') end it '"list_tasks" filters on name based on a given regexp' do testing_env_dir # creates the structure result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: modulepath, facts: node_facts) do |ctx| ctx.with_script_compiler {|c| c.list_tasks(/^a::/) } end expect(result.is_a?(Array)).to eq(true) expect(result.all? {|s| s.is_a?(Puppet::Pops::Loader::TypedName) }).to eq(true) expect(result.map {|tn| tn.name}).to eq(['a::atask']) end end end context 'configured as an existing given environment directory such that' do it 'modules in it are available from its "modules" directory' do result = Puppet::Pal.in_environment('pal_env', env_dir: testing_env_dir, facts: node_facts) do |ctx| ctx.with_script_compiler {|c| c.evaluate_string('a::afunc()') } end expect(result).to eq("a::afunc value") end it 'libs in a given "modulepath" are added to the Ruby $LOAD_PATH' do result = Puppet::Pal.in_environment('pal_env', env_dir: testing_env_dir, facts: node_facts) do |ctx| ctx.with_script_compiler {|c| c.evaluate_string('a::arubyfunc()') } end expect(result).to eql('something') end it 'a given "modulepath" overrides the default' do expect do Puppet::Pal.in_environment('pal_env', env_dir: testing_env_dir, modulepath: [], facts: node_facts) do |ctx| ctx.with_script_compiler {|c| c.evaluate_string('a::afunc()') } end end.to raise_error(/Unknown function: 'a::afunc'/) end it 'a "pre_modulepath" is prepended and a "post_modulepath" is appended to the effective modulepath' do other_modules1 = File.join(environments_dir, 'other_env1/modules') other_modules2 = File.join(environments_dir, 'other_env2/modules') result = Puppet::Pal.in_environment('pal_env', env_dir: testing_env_dir, pre_modulepath: [other_modules1], post_modulepath: [other_modules2], facts: node_facts ) do |ctx| the_modulepath = Puppet.lookup(:environments).get('pal_env').modulepath the_modulepath[0] == other_modules1 && the_modulepath[-1] == other_modules2 end expect(result).to be(true) end it 'can set variables in any scope' do vars = {'a'=> 10, 'x::y' => 20} result = Puppet::Pal.in_environment('pal_env', env_dir: testing_env_dir, facts: node_facts, variables: vars) do |ctx| ctx.with_script_compiler { |c| c.evaluate_string("1+2+3+4+$a+$x::y") } end expect(result).to eq(40) end it 'errors in a meaningful way when a non existing env name is given' do testing_env_dir # creates the structure expect do Puppet::Pal.in_environment('blah_env', env_dir: testing_env_dir.chop, facts: node_facts) { |ctx| } end.to raise_error(/The environment directory '.*' does not exist/) end it 'errors if an env_name is given and is not a String[1]' do expect do Puppet::Pal.in_environment('', env_dir: testing_env_dir, facts: node_facts) { |ctx| } end.to raise_error(/env_name has wrong type/) expect do Puppet::Pal.in_environment(32, env_dir: testing_env_dir, facts: node_facts) { |ctx| } end.to raise_error(/env_name has wrong type/) end { 'a hash' => {'a' => 'hm'}, 'an integer' => 32, 'separated strings' => 'dir1;dir2', 'empty string in array' => [''] }.each_pair do |what, value| it "errors if modulepath is #{what}" do expect do Puppet::Pal.in_environment('pal_env', env_dir: testing_env_dir, modulepath: {'a' => 'hm'}, facts: node_facts) { |ctx| } Puppet::Pal.in_tmp_environment('pal_env', modulepath: value, facts: node_facts) { |ctx| } end.to raise_error(/modulepath has wrong type/) end end it 'errors if env_dir and envpath are both given' do testing_env_dir # creates the structure expect do Puppet::Pal.in_environment('blah_env', env_dir: testing_env_dir, envpath: environments_dir, facts: node_facts) { |ctx| } end.to raise_error(/Cannot use 'env_dir' and 'envpath' at the same time/) end end context 'configured as existing given envpath such that' do it 'modules in it are available from its "modules" directory' do testing_env_dir # creates the structure result = Puppet::Pal.in_environment('pal_env', envpath: environments_dir, facts: node_facts) do |ctx| ctx.with_script_compiler { |c| c.evaluate_string('a::afunc()') } end expect(result).to eq("a::afunc value") end it 'a given "modulepath" overrides the default' do testing_env_dir # creates the structure expect do Puppet::Pal.in_environment('pal_env', envpath: environments_dir, modulepath: [], facts: node_facts) do |ctx| ctx.with_script_compiler { |c| c.evaluate_string('a::afunc()') } end end.to raise_error(/Unknown function: 'a::afunc'/) end it 'a "pre_modulepath" is prepended and a "post_modulepath" is appended to the effective modulepath' do testing_env_dir # creates the structure other_modules1 = File.join(environments_dir, 'other_env1/modules') other_modules2 = File.join(environments_dir, 'other_env2/modules') result = Puppet::Pal.in_environment('pal_env', envpath: environments_dir, pre_modulepath: [other_modules1], post_modulepath: [other_modules2], facts: node_facts ) do |ctx| the_modulepath = Puppet.lookup(:environments).get('pal_env').modulepath the_modulepath[0] == other_modules1 && the_modulepath[-1] == other_modules2 end expect(result).to be(true) end it 'the envpath can have multiple entries - that are searched for the given env' do testing_env_dir # creates the structure result = Puppet::Pal.in_environment('pal_env', envpath: environments_dir, facts: node_facts) do |ctx| ctx.with_script_compiler {|c| c.evaluate_string('a::afunc()') } end expect(result).to eq("a::afunc value") end it 'errors in a meaningful way when a non existing env name is given' do testing_env_dir # creates the structure expect do Puppet::Pal.in_environment('blah_env', envpath: environments_dir, facts: node_facts) { |ctx| } end.to raise_error(/No directory found for the environment 'blah_env' on the path '.*'/) end it 'errors if a block is not given to in_environment' do expect do Puppet::Pal.in_environment('blah_env', envpath: environments_dir, facts: node_facts) end.to raise_error(/A block must be given to 'in_environment/) end it 'errors if envpath is something other than a string' do testing_env_dir # creates the structure expect do Puppet::Pal.in_environment('blah_env', envpath: '', facts: node_facts) { |ctx| } end.to raise_error(/envpath has wrong type/) expect do Puppet::Pal.in_environment('blah_env', envpath: [environments_dir], facts: node_facts) { |ctx| } end.to raise_error(/envpath has wrong type/) end context 'with a script compiler' do it 'uses configured manifest_file if configured_by_env is true and Puppet[:code] is unset' do testing_env_dir # creates the structure Puppet[:manifest] = file_containing('afunc.pp', "function myfunc(Integer $a) { $a * 2 } ") result = Puppet::Pal.in_environment('pal_env', envpath: environments_dir, facts: node_facts) do |ctx| ctx.with_script_compiler(configured_by_env: true) {|c| c.call_function('myfunc', 4)} end expect(result).to eql(8) end it 'uses Puppet[:code] if configured_by_env is true and Puppet[:code] is set' do testing_env_dir # creates the structure Puppet[:manifest] = file_containing('amanifest.pp', "$a = 20") Puppet[:code] = '$a = 40' result = Puppet::Pal.in_environment('pal_env', envpath: environments_dir, facts: node_facts) do |ctx| ctx.with_script_compiler(configured_by_env: true) {|c| c.evaluate_string('$a')} end expect(result).to eql(40) end it 'makes the pal ScriptCompiler available as script_compiler_param to Function dispatcher' do testing_env_dir # creates the structure Puppet[:manifest] = file_containing('noop.pp', "undef") result = Puppet::Pal.in_environment('pal_env', envpath: environments_dir, facts: node_facts) do |ctx| ctx.with_script_compiler(configured_by_env: true) {|c| c.call_function('a::myscriptcompilerfunc', 'go')} end expect(result).to eql('go') end end end end end puppet-5.5.10/spec/unit/puppet_pal_spec.rb0000644005276200011600000000060213417161722020432 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/files' require_relative('puppet_pal_2pec') unless RUBY_VERSION == '1.9.3' #describe 'Puppet Pal' do # pending("Puppet::Pal is not available on Ruby 1.9.3") if RUBY_VERSION == '1.9.3' ## before { skip("Puppet::Pal is not available on Ruby 1.9.3") if RUBY_VERSION == '1.9.3' } # require_relative 'puppet_pal_2pec' #end puppet-5.5.10/spec/unit/puppet_spec.rb0000644005276200011600000000532613417161722017606 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet' require 'puppet_spec/files' describe Puppet do include PuppetSpec::Files context "#version" do it "should be valid semver" do expect(SemanticPuppet::Version).to be_valid Puppet.version end end Puppet::Util::Log.eachlevel do |level| it "should have a method for sending '#{level}' logs" do expect(Puppet).to respond_to(level) end end it "should be able to change the path" do newpath = ENV["PATH"] + File::PATH_SEPARATOR + "/something/else" Puppet[:path] = newpath expect(ENV["PATH"]).to eq(newpath) end it "should change $LOAD_PATH when :libdir changes" do one = tmpdir('load-path-one') two = tmpdir('load-path-two') expect(one).not_to eq(two) Puppet[:libdir] = one expect($LOAD_PATH).to include one expect($LOAD_PATH).not_to include two Puppet[:libdir] = two expect($LOAD_PATH).not_to include one expect($LOAD_PATH).to include two end it 'should propagate --modulepath to base environment' do Puppet::Node::Environment.expects(:create).with( is_a(Symbol), ['/my/modules'], Puppet::Node::Environment::NO_MANIFEST) Puppet.base_context({ :environmentpath => '/envs', :basemodulepath => '/base/modules', :modulepath => '/my/modules' }) end it 'empty modulepath does not override basemodulepath' do Puppet::Node::Environment.expects(:create).with( is_a(Symbol), ['/base/modules'], Puppet::Node::Environment::NO_MANIFEST) Puppet.base_context({ :environmentpath => '/envs', :basemodulepath => '/base/modules', :modulepath => '' }) end it 'nil modulepath does not override basemodulepath' do Puppet::Node::Environment.expects(:create).with( is_a(Symbol), ['/base/modules'], Puppet::Node::Environment::NO_MANIFEST) Puppet.base_context({ :environmentpath => '/envs', :basemodulepath => '/base/modules', :modulepath => nil }) end context "Puppet::OLDEST_RECOMMENDED_RUBY_VERSION" do it "should have an oldest recommended ruby version constant" do expect(Puppet::OLDEST_RECOMMENDED_RUBY_VERSION).not_to be_nil end it "should be a string" do expect(Puppet::OLDEST_RECOMMENDED_RUBY_VERSION).to be_a_kind_of(String) end it "should match a semver version" do expect(SemanticPuppet::Version).to be_valid(Puppet::OLDEST_RECOMMENDED_RUBY_VERSION) end end context "newtype" do it "should issue a deprecation warning" do subject.expects(:deprecation_warning).with("Creating sometype via Puppet.newtype is deprecated and will be removed in a future release. Use Puppet::Type.newtype instead.") subject.newtype("sometype") end end end puppet-5.5.10/spec/unit/resource_spec.rb0000644005276200011600000011767713417161722020135 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet/resource' describe Puppet::Resource do include PuppetSpec::Files let(:basepath) { make_absolute("/somepath") } let(:environment) { Puppet::Node::Environment.create(:testing, []) } def expect_lookup(key) Puppet::Pops::Lookup::GlobalDataProvider.any_instance.expects(:unchecked_key_lookup).with(Puppet::Pops::Lookup::LookupKey.new(key), any_parameters) end [:catalog, :file, :line].each do |attr| it "should have an #{attr} attribute" do resource = Puppet::Resource.new("file", "/my/file") expect(resource).to respond_to(attr) expect(resource).to respond_to(attr.to_s + "=") end end it "should have a :title attribute" do expect(Puppet::Resource.new(:user, "foo").title).to eq("foo") end it "should require the type and title" do expect { Puppet::Resource.new }.to raise_error(ArgumentError) end it "should canonize types to capitalized strings" do expect(Puppet::Resource.new(:user, "foo").type).to eq("User") end it "should canonize qualified types so all strings are capitalized" do expect(Puppet::Resource.new("foo::bar", "foo").type).to eq("Foo::Bar") end it "should tag itself with its type" do expect(Puppet::Resource.new("file", "/f")).to be_tagged("file") end it "should tag itself with its title if the title is a valid tag" do expect(Puppet::Resource.new("user", "bar")).to be_tagged("bar") end it "should not tag itself with its title if the title is a not valid tag" do expect(Puppet::Resource.new("file", "/bar")).not_to be_tagged("/bar") end it "should allow setting of attributes" do expect(Puppet::Resource.new("file", "/bar", :file => "/foo").file).to eq("/foo") expect(Puppet::Resource.new("file", "/bar", :exported => true)).to be_exported end it "should set its type to 'Class' and its title to the passed title if the passed type is :component and the title has no square brackets in it" do ref = Puppet::Resource.new(:component, "foo") expect(ref.type).to eq("Class") expect(ref.title).to eq("Foo") end it "should interpret the title as a reference and assign appropriately if the type is :component and the title contains square brackets" do ref = Puppet::Resource.new(:component, "foo::bar[yay]") expect(ref.type).to eq("Foo::Bar") expect(ref.title).to eq("yay") end it "should set the type to 'Class' if it is nil and the title contains no square brackets" do ref = Puppet::Resource.new(nil, "yay") expect(ref.type).to eq("Class") expect(ref.title).to eq("Yay") end it "should interpret the title as a reference and assign appropriately if the type is nil and the title contains square brackets" do ref = Puppet::Resource.new(nil, "foo::bar[yay]") expect(ref.type).to eq("Foo::Bar") expect(ref.title).to eq("yay") end it "should interpret the title as a reference and assign appropriately if the type is nil and the title contains nested square brackets" do ref = Puppet::Resource.new(nil, "foo::bar[baz[yay]]") expect(ref.type).to eq("Foo::Bar") expect(ref.title).to eq("baz[yay]") end it "should interpret the type as a reference and assign appropriately if the title is nil and the type contains square brackets" do ref = Puppet::Resource.new("foo::bar[baz]") expect(ref.type).to eq("Foo::Bar") expect(ref.title).to eq("baz") end it "should not interpret the title as a reference if the type is a non component or whit reference" do ref = Puppet::Resource.new("Notify", "foo::bar[baz]") expect(ref.type).to eq("Notify") expect(ref.title).to eq("foo::bar[baz]") end it "should be able to extract its information from a Puppet::Type instance" do ral = Puppet::Type.type(:file).new :path => basepath+"/foo" ref = Puppet::Resource.new(ral) expect(ref.type).to eq("File") expect(ref.title).to eq(basepath+"/foo") end it "should fail if the title is nil and the type is not a valid resource reference string" do expect { Puppet::Resource.new("resource-spec-foo") }.to raise_error(ArgumentError) end it 'should fail if strict is set and type does not exist' do expect { Puppet::Resource.new('resource-spec-foo', 'title', {:strict=>true}) }.to raise_error(ArgumentError, 'Invalid resource type resource-spec-foo') end it 'should fail if strict is set and class does not exist' do expect { Puppet::Resource.new('Class', 'resource-spec-foo', {:strict=>true}) }.to raise_error(ArgumentError, 'Could not find declared class resource-spec-foo') end it "should fail if the title is a hash and the type is not a valid resource reference string" do expect { Puppet::Resource.new({:type => "resource-spec-foo", :title => "bar"}) }. to raise_error ArgumentError, /Puppet::Resource.new does not take a hash/ end it "should be taggable" do expect(Puppet::Resource.ancestors).to be_include(Puppet::Util::Tagging) end it "should have an 'exported' attribute" do resource = Puppet::Resource.new("file", "/f") resource.exported = true expect(resource.exported).to eq(true) expect(resource).to be_exported end describe "and munging its type and title" do describe "when modeling a builtin resource" do it "should be able to find the resource type" do expect(Puppet::Resource.new("file", "/my/file").resource_type).to equal(Puppet::Type.type(:file)) end it "should set its type to the capitalized type name" do expect(Puppet::Resource.new("file", "/my/file").type).to eq("File") end end describe "when modeling a defined resource" do describe "that exists" do before do @type = Puppet::Resource::Type.new(:definition, "foo::bar") environment.known_resource_types.add @type end it "should set its type to the capitalized type name" do expect(Puppet::Resource.new("foo::bar", "/my/file", :environment => environment).type).to eq("Foo::Bar") end it "should be able to find the resource type" do expect(Puppet::Resource.new("foo::bar", "/my/file", :environment => environment).resource_type).to equal(@type) end it "should set its title to the provided title" do expect(Puppet::Resource.new("foo::bar", "/my/file", :environment => environment).title).to eq("/my/file") end end describe "that does not exist" do it "should set its resource type to the capitalized resource type name" do expect(Puppet::Resource.new("foo::bar", "/my/file").type).to eq("Foo::Bar") end end end describe "when modeling a node" do # Life's easier with nodes, because they can't be qualified. it "should set its type to 'Node' and its title to the provided title" do node = Puppet::Resource.new("node", "foo") expect(node.type).to eq("Node") expect(node.title).to eq("foo") end end describe "when modeling a class" do it "should set its type to 'Class'" do expect(Puppet::Resource.new("class", "foo").type).to eq("Class") end describe "that exists" do before do @type = Puppet::Resource::Type.new(:hostclass, "foo::bar") environment.known_resource_types.add @type end it "should set its title to the capitalized, fully qualified resource type" do expect(Puppet::Resource.new("class", "foo::bar", :environment => environment).title).to eq("Foo::Bar") end it "should be able to find the resource type" do expect(Puppet::Resource.new("class", "foo::bar", :environment => environment).resource_type).to equal(@type) end end describe "that does not exist" do it "should set its type to 'Class' and its title to the capitalized provided name" do klass = Puppet::Resource.new("class", "foo::bar") expect(klass.type).to eq("Class") expect(klass.title).to eq("Foo::Bar") end end describe "and its name is set to the empty string" do it "should set its title to :main" do expect(Puppet::Resource.new("class", "").title).to eq(:main) end describe "and a class exists whose name is the empty string" do # this was a bit tough to track down it "should set its title to :main" do @type = Puppet::Resource::Type.new(:hostclass, "") environment.known_resource_types.add @type expect(Puppet::Resource.new("class", "", :environment => environment).title).to eq(:main) end end end describe "and its name is set to :main" do it "should set its title to :main" do expect(Puppet::Resource.new("class", :main).title).to eq(:main) end describe "and a class exists whose name is the empty string" do # this was a bit tough to track down it "should set its title to :main" do @type = Puppet::Resource::Type.new(:hostclass, "") environment.known_resource_types.add @type expect(Puppet::Resource.new("class", :main, :environment => environment).title).to eq(:main) end end end end end it "should return nil when looking up resource types that don't exist" do expect(Puppet::Resource.new("foobar", "bar").resource_type).to be_nil end it "should not fail when an invalid parameter is used and strict mode is disabled" do type = Puppet::Resource::Type.new(:definition, "foobar") environment.known_resource_types.add type resource = Puppet::Resource.new("foobar", "/my/file", :environment => environment) resource[:yay] = true end it "should be considered equivalent to another resource if their type and title match and no parameters are set" do expect(Puppet::Resource.new("file", "/f")).to eq(Puppet::Resource.new("file", "/f")) end it "should be considered equivalent to another resource if their type, title, and parameters are equal" do expect(Puppet::Resource.new("file", "/f", :parameters => {:foo => "bar"})).to eq(Puppet::Resource.new("file", "/f", :parameters => {:foo => "bar"})) end it "should not be considered equivalent to another resource if their type and title match but parameters are different" do expect(Puppet::Resource.new("file", "/f", :parameters => {:fee => "baz"})).not_to eq(Puppet::Resource.new("file", "/f", :parameters => {:foo => "bar"})) end it "should not be considered equivalent to a non-resource" do expect(Puppet::Resource.new("file", "/f")).not_to eq("foo") end it "should not be considered equivalent to another resource if their types do not match" do expect(Puppet::Resource.new("file", "/f")).not_to eq(Puppet::Resource.new("exec", "/f")) end it "should not be considered equivalent to another resource if their titles do not match" do expect(Puppet::Resource.new("file", "/foo")).not_to eq(Puppet::Resource.new("file", "/f")) end describe "when setting default parameters" do let(:foo_node) { Puppet::Node.new('foo', :environment => environment) } let(:compiler) { Puppet::Parser::Compiler.new(foo_node) } let(:scope) { Puppet::Parser::Scope.new(compiler) } def ast_leaf(value) Puppet::Parser::AST::Leaf.new({:value => value}) end it "should fail when asked to set default values and it is not a parser resource" do environment.known_resource_types.add( Puppet::Resource::Type.new(:definition, "default_param", :arguments => {"a" => ast_leaf("default")}) ) resource = Puppet::Resource.new("default_param", "name", :environment => environment) expect { resource.set_default_parameters(scope) }.to raise_error(Puppet::DevError) end it "should evaluate and set any default values when no value is provided" do environment.known_resource_types.add( Puppet::Resource::Type.new(:definition, "default_param", :arguments => {"a" => ast_leaf("a_default_value")}) ) resource = Puppet::Parser::Resource.new("default_param", "name", :scope => scope) resource.set_default_parameters(scope) expect(resource["a"]).to eq("a_default_value") end it "should skip attributes with no default value" do environment.known_resource_types.add( Puppet::Resource::Type.new(:definition, "no_default_param", :arguments => {"a" => ast_leaf("a_default_value")}) ) resource = Puppet::Parser::Resource.new("no_default_param", "name", :scope => scope) expect { resource.set_default_parameters(scope) }.not_to raise_error end it "should return the list of default parameters set" do environment.known_resource_types.add( Puppet::Resource::Type.new(:definition, "default_param", :arguments => {"a" => ast_leaf("a_default_value")}) ) resource = Puppet::Parser::Resource.new("default_param", "name", :scope => scope) expect(resource.set_default_parameters(scope)).to eq(["a"]) end describe "when the resource type is :hostclass" do let(:environment_name) { "testing env" } let(:fact_values) { { :a => 1 } } let(:port) { Puppet::Parser::AST::Leaf.new(:value => '80') } def inject_and_set_defaults(resource, scope) resource.resource_type.set_resource_parameters(resource, scope) end before do environment.known_resource_types.add(apache) scope.stubs(:host).returns('host') scope.stubs(:environment).returns(environment) scope.stubs(:facts).returns(Puppet::Node::Facts.new("facts", fact_values)) end context 'with a default value expression' do let(:apache) { Puppet::Resource::Type.new(:hostclass, 'apache', :arguments => { 'port' => port }) } context "when no value is provided" do let(:resource) do Puppet::Parser::Resource.new("class", "apache", :scope => scope) end it "should query the data_binding terminus using a namespaced key" do expect_lookup('lookup_options').throws(:no_such_key) expect_lookup('apache::port') inject_and_set_defaults(resource, scope) end it "should use the value from the data_binding terminus" do expect_lookup('lookup_options').throws(:no_such_key) expect_lookup('apache::port').returns('443') inject_and_set_defaults(resource, scope) expect(resource[:port]).to eq('443') end it 'should use the default value if no value is found using the data_binding terminus' do expect_lookup('lookup_options').throws(:no_such_key) expect_lookup('apache::port').throws(:no_such_key) inject_and_set_defaults(resource, scope) expect(resource[:port]).to eq('80') end it 'should use the default value if an undef value is found using the data_binding terminus' do expect_lookup('lookup_options').throws(:no_such_key) expect_lookup('apache::port').returns(nil) inject_and_set_defaults(resource, scope) expect(resource[:port]).to eq('80') end it "should fail with error message about data binding on a hiera failure" do expect_lookup('lookup_options').throws(:no_such_key) expect_lookup('apache::port').raises(Puppet::DataBinding::LookupError, 'Forgettabotit') expect { inject_and_set_defaults(resource, scope) }.to raise_error(Puppet::Error, /Lookup of key 'apache::port' failed: Forgettabotit/) end end context "when a value is provided" do let(:port_parameter) do Puppet::Parser::Resource::Param.new( { :name => 'port', :value => '8080' } ) end let(:resource) do Puppet::Parser::Resource.new("class", "apache", :scope => scope, :parameters => [port_parameter]) end it "should not query the data_binding terminus" do Puppet::DataBinding.indirection.expects(:find).never inject_and_set_defaults(resource, scope) end it "should use the value provided" do Puppet::DataBinding.indirection.expects(:find).never expect(resource.set_default_parameters(scope)).to eq([]) expect(resource[:port]).to eq('8080') end it "should use the value from the data_binding terminus when provided value is undef" do expect_lookup('lookup_options').throws(:no_such_key) expect_lookup('apache::port').returns('443') rs = Puppet::Parser::Resource.new("class", "apache", :scope => scope, :parameters => [Puppet::Parser::Resource::Param.new({ :name => 'port', :value => nil })]) rs.resource_type.set_resource_parameters(rs, scope) expect(rs[:port]).to eq('443') end end end context 'without a default value expression' do let(:apache) { Puppet::Resource::Type.new(:hostclass, 'apache', :arguments => { 'port' => nil }) } let(:resource) { Puppet::Parser::Resource.new("class", "apache", :scope => scope) } it "should use the value from the data_binding terminus" do expect_lookup('lookup_options').throws(:no_such_key) expect_lookup('apache::port').returns('443') inject_and_set_defaults(resource, scope) expect(resource[:port]).to eq('443') end it "should use an undef value from the data_binding terminus" do expect_lookup('lookup_options').throws(:no_such_key) expect_lookup('apache::port').returns(nil) inject_and_set_defaults(resource, scope) expect(resource[:port]).to be_nil end end end end describe "when validating all required parameters are present" do it "should be able to validate that all required parameters are present" do environment.known_resource_types.add( Puppet::Resource::Type.new(:definition, "required_param", :arguments => {"a" => nil}) ) expect { Puppet::Resource.new("required_param", "name", :environment => environment).validate_complete }.to raise_error(Puppet::ParseError) end it "should not fail when all required parameters are present" do environment.known_resource_types.add( Puppet::Resource::Type.new(:definition, "no_required_param") ) resource = Puppet::Resource.new("no_required_param", "name", :environment => environment) resource["a"] = "meh" expect { resource.validate_complete }.not_to raise_error end it "should not validate against builtin types" do expect { Puppet::Resource.new("file", "/bar").validate_complete }.not_to raise_error end end describe "when referring to a resource with name canonicalization" do it "should canonicalize its own name" do res = Puppet::Resource.new("file", "/path/") expect(res.uniqueness_key).to eq(["/path"]) expect(res.ref).to eq("File[/path/]") end end describe "when running in strict mode" do it "should be strict" do expect(Puppet::Resource.new("file", "/path", :strict => true)).to be_strict end it "should fail if invalid parameters are used" do expect { Puppet::Resource.new("file", "/path", :strict => true, :parameters => {:nosuchparam => "bar"}) }.to raise_error(Puppet::Error, /no parameter named 'nosuchparam'/) end it "should fail if the resource type cannot be resolved" do expect { Puppet::Resource.new("nosuchtype", "/path", :strict => true) }.to raise_error(ArgumentError, /Invalid resource type/) end end describe "when managing parameters" do before do @resource = Puppet::Resource.new("file", "/my/file") end it "should correctly detect when provided parameters are not valid for builtin types" do expect(Puppet::Resource.new("file", "/my/file")).not_to be_valid_parameter("foobar") end it "should correctly detect when provided parameters are valid for builtin types" do expect(Puppet::Resource.new("file", "/my/file")).to be_valid_parameter("mode") end it "should correctly detect when provided parameters are not valid for defined resource types" do type = Puppet::Resource::Type.new(:definition, "foobar") environment.known_resource_types.add type expect(Puppet::Resource.new("foobar", "/my/file", :environment => environment)).not_to be_valid_parameter("myparam") end it "should correctly detect when provided parameters are valid for defined resource types" do type = Puppet::Resource::Type.new(:definition, "foobar", :arguments => {"myparam" => nil}) environment.known_resource_types.add type expect(Puppet::Resource.new("foobar", "/my/file", :environment => environment)).to be_valid_parameter("myparam") end it "should allow setting and retrieving of parameters" do @resource[:foo] = "bar" expect(@resource[:foo]).to eq("bar") end it "should allow setting of parameters at initialization" do expect(Puppet::Resource.new("file", "/my/file", :parameters => {:foo => "bar"})[:foo]).to eq("bar") end it "should canonicalize retrieved parameter names to treat symbols and strings equivalently" do @resource[:foo] = "bar" expect(@resource["foo"]).to eq("bar") end it "should canonicalize set parameter names to treat symbols and strings equivalently" do @resource["foo"] = "bar" expect(@resource[:foo]).to eq("bar") end it "should set the namevar when asked to set the name" do resource = Puppet::Resource.new("user", "bob") Puppet::Type.type(:user).stubs(:key_attributes).returns [:myvar] resource[:name] = "bob" expect(resource[:myvar]).to eq("bob") end it "should return the namevar when asked to return the name" do resource = Puppet::Resource.new("user", "bob") Puppet::Type.type(:user).stubs(:key_attributes).returns [:myvar] resource[:myvar] = "test" expect(resource[:name]).to eq("test") end it "should be able to set the name for non-builtin types" do resource = Puppet::Resource.new(:foo, "bar") resource[:name] = "eh" expect { resource[:name] = "eh" }.to_not raise_error end it "should be able to return the name for non-builtin types" do resource = Puppet::Resource.new(:foo, "bar") resource[:name] = "eh" expect(resource[:name]).to eq("eh") end it "should be able to iterate over parameters" do @resource[:foo] = "bar" @resource[:fee] = "bare" params = {} @resource.each do |key, value| params[key] = value end expect(params).to eq({:foo => "bar", :fee => "bare"}) end it "should include Enumerable" do expect(@resource.class.ancestors).to be_include(Enumerable) end it "should have a method for testing whether a parameter is included" do @resource[:foo] = "bar" expect(@resource).to be_has_key(:foo) expect(@resource).not_to be_has_key(:eh) end it "should have a method for providing the list of parameters" do @resource[:foo] = "bar" @resource[:bar] = "foo" keys = @resource.keys expect(keys).to be_include(:foo) expect(keys).to be_include(:bar) end it "should have a method for providing the number of parameters" do @resource[:foo] = "bar" expect(@resource.length).to eq(1) end it "should have a method for deleting parameters" do @resource[:foo] = "bar" @resource.delete(:foo) expect(@resource[:foo]).to be_nil end it "should have a method for testing whether the parameter list is empty" do expect(@resource).to be_empty @resource[:foo] = "bar" expect(@resource).not_to be_empty end it "should be able to produce a hash of all existing parameters" do @resource[:foo] = "bar" @resource[:fee] = "yay" hash = @resource.to_hash expect(hash[:foo]).to eq("bar") expect(hash[:fee]).to eq("yay") end it "should not provide direct access to the internal parameters hash when producing a hash" do hash = @resource.to_hash hash[:foo] = "bar" expect(@resource[:foo]).to be_nil end it "should use the title as the namevar to the hash if no namevar is present" do resource = Puppet::Resource.new("user", "bob") Puppet::Type.type(:user).stubs(:key_attributes).returns [:myvar] expect(resource.to_hash[:myvar]).to eq("bob") end it "should set :name to the title if :name is not present for non-builtin types" do krt = Puppet::Resource::TypeCollection.new("myenv") krt.add Puppet::Resource::Type.new(:definition, :foo) resource = Puppet::Resource.new :foo, "bar" resource.stubs(:known_resource_types).returns krt expect(resource.to_hash[:name]).to eq("bar") end end describe "when serializing a native type" do before do @resource = Puppet::Resource.new("file", "/my/file") @resource["one"] = "test" @resource["two"] = "other" end # PUP-3272, needs to work becuse serialization is not only to network # it "should produce an equivalent yaml object" do text = @resource.render('yaml') newresource = Puppet::Resource.convert_from('yaml', text) expect(newresource).to equal_resource_attributes_of(@resource) end # PUP-3272, since serialization to network is done in json, not yaml it "should produce an equivalent json object" do text = @resource.render('json') newresource = Puppet::Resource.convert_from('json', text) expect(newresource).to equal_resource_attributes_of(@resource) end end describe "when serializing a defined type" do before do type = Puppet::Resource::Type.new(:definition, "foo::bar") environment.known_resource_types.add type @resource = Puppet::Resource.new('foo::bar', 'xyzzy', :environment => environment) @resource['one'] = 'test' @resource['two'] = 'other' @resource.resource_type end it "doesn't include transient instance variables (#4506)" do expect(@resource.to_data_hash.keys).to_not include('rstype') end it "produces an equivalent json object" do text = @resource.render('json') newresource = Puppet::Resource.convert_from('json', text) expect(newresource).to equal_resource_attributes_of(@resource) end it 'to_data_hash returns value that is instance of to Data' do Puppet::Pops::Types::TypeAsserter.assert_instance_of('', Puppet::Pops::Types::TypeFactory.data, @resource.to_data_hash) expect(Puppet::Pops::Types::TypeFactory.data.instance?(@resource.to_data_hash)).to be_truthy end end describe "when converting to a RAL resource" do it "should use the resource type's :new method to create the resource if the resource is of a builtin type" do resource = Puppet::Resource.new("file", basepath+"/my/file") result = resource.to_ral expect(result).to be_instance_of(Puppet::Type.type(:file)) expect(result[:path]).to eq(basepath+"/my/file") end it "should convert to a component instance if the resource type is not of a builtin type" do resource = Puppet::Resource.new("foobar", "somename") result = resource.to_ral expect(result).to be_instance_of(Puppet::Type.type(:component)) expect(result.title).to eq("Foobar[somename]") end end describe "when converting to puppet code" do before do @resource = Puppet::Resource.new("one::two", "/my/file", :parameters => { :noop => true, :foo => %w{one two}, :ensure => 'present', } ) end it "should escape internal single quotes in a title" do singlequote_resource = Puppet::Resource.new("one::two", "/my/file'b'a'r", :parameters => { :ensure => 'present', } ) expect(singlequote_resource.to_manifest).to eq <<-HEREDOC.gsub(/^\s{8}/, '').gsub(/\n$/, '') one::two { '/my/file\\'b\\'a\\'r': ensure => 'present', } HEREDOC end it "should align, sort and add trailing commas to attributes with ensure first" do expect(@resource.to_manifest).to eq <<-HEREDOC.gsub(/^\s{8}/, '').gsub(/\n$/, '') one::two { '/my/file': ensure => 'present', foo => ['one', 'two'], noop => true, } HEREDOC end end describe "when converting to Yaml for Hiera" do before do @resource = Puppet::Resource.new("one::two", "/my/file", :parameters => { :noop => true, :foo => %w{one two}, :ensure => 'present', } ) end it "should align and sort to attributes with ensure first" do expect(@resource.to_hierayaml).to eq <<-HEREDOC.gsub(/^\s{8}/, '') /my/file: ensure: 'present' foo : ['one', 'two'] noop : true HEREDOC end end describe "when converting to json" do # LAK:NOTE For all of these tests, we convert back to the resource so we can # trap the actual data structure then. it "should set its type to the provided type" do expect(Puppet::Resource.from_data_hash(JSON.parse(Puppet::Resource.new("File", "/foo").to_json)).type).to eq("File") end it "should set its title to the provided title" do expect(Puppet::Resource.from_data_hash(JSON.parse(Puppet::Resource.new("File", "/foo").to_json)).title).to eq("/foo") end it "should include all tags from the resource" do resource = Puppet::Resource.new("File", "/foo") resource.tag("yay") expect(Puppet::Resource.from_data_hash(JSON.parse(resource.to_json)).tags).to eq(resource.tags) end it "should include the file if one is set" do resource = Puppet::Resource.new("File", "/foo") resource.file = "/my/file" expect(Puppet::Resource.from_data_hash(JSON.parse(resource.to_json)).file).to eq("/my/file") end it "should include the line if one is set" do resource = Puppet::Resource.new("File", "/foo") resource.line = 50 expect(Puppet::Resource.from_data_hash(JSON.parse(resource.to_json)).line).to eq(50) end it "should include the 'exported' value if one is set" do resource = Puppet::Resource.new("File", "/foo") resource.exported = true expect(Puppet::Resource.from_data_hash(JSON.parse(resource.to_json)).exported?).to be_truthy end it "should set 'exported' to false if no value is set" do resource = Puppet::Resource.new("File", "/foo") expect(Puppet::Resource.from_data_hash(JSON.parse(resource.to_json)).exported?).to be_falsey end it "should set all of its parameters as the 'parameters' entry" do resource = Puppet::Resource.new("File", "/foo") resource[:foo] = %w{bar eh} resource[:fee] = %w{baz} result = Puppet::Resource.from_data_hash(JSON.parse(resource.to_json)) expect(result["foo"]).to eq(%w{bar eh}) expect(result["fee"]).to eq(%w{baz}) end it "should set sensitive parameters as an array of strings" do resource = Puppet::Resource.new("File", "/foo", :sensitive_parameters => [:foo, :fee]) result = JSON.parse(resource.to_json) expect(result["sensitive_parameters"]).to eq ["foo", "fee"] end it "should serialize relationships as reference strings" do resource = Puppet::Resource.new("File", "/foo") resource[:requires] = Puppet::Resource.new("File", "/bar") result = Puppet::Resource.from_data_hash(JSON.parse(resource.to_json)) expect(result[:requires]).to eq("File[/bar]") end it "should serialize multiple relationships as arrays of reference strings" do resource = Puppet::Resource.new("File", "/foo") resource[:requires] = [Puppet::Resource.new("File", "/bar"), Puppet::Resource.new("File", "/baz")] result = Puppet::Resource.from_data_hash(JSON.parse(resource.to_json)) expect(result[:requires]).to eq([ "File[/bar]", "File[/baz]" ]) end end describe "when converting from json" do before do @data = { 'type' => "file", 'title' => basepath+"/yay", } end it "should set its type to the provided type" do expect(Puppet::Resource.from_data_hash(@data).type).to eq("File") end it "should set its title to the provided title" do expect(Puppet::Resource.from_data_hash(@data).title).to eq(basepath+"/yay") end it "should tag the resource with any provided tags" do @data['tags'] = %w{foo bar} resource = Puppet::Resource.from_data_hash(@data) expect(resource.tags).to be_include("foo") expect(resource.tags).to be_include("bar") end it "should set its file to the provided file" do @data['file'] = "/foo/bar" expect(Puppet::Resource.from_data_hash(@data).file).to eq("/foo/bar") end it "should set its line to the provided line" do @data['line'] = 50 expect(Puppet::Resource.from_data_hash(@data).line).to eq(50) end it "should 'exported' to true if set in the json data" do @data['exported'] = true expect(Puppet::Resource.from_data_hash(@data).exported).to be_truthy end it "should 'exported' to false if not set in the json data" do expect(Puppet::Resource.from_data_hash(@data).exported).to be_falsey end it "should fail if no title is provided" do @data.delete('title') expect { Puppet::Resource.from_data_hash(@data) }.to raise_error(ArgumentError) end it "should fail if no type is provided" do @data.delete('type') expect { Puppet::Resource.from_data_hash(@data) }.to raise_error(ArgumentError) end it "should set each of the provided parameters" do @data['parameters'] = {'foo' => %w{one two}, 'fee' => %w{three four}} resource = Puppet::Resource.from_data_hash(@data) expect(resource['foo']).to eq(%w{one two}) expect(resource['fee']).to eq(%w{three four}) end it "should convert single-value array parameters to normal values" do @data['parameters'] = {'foo' => %w{one}} resource = Puppet::Resource.from_data_hash(@data) expect(resource['foo']).to eq(%w{one}) end it "converts deserialized sensitive parameters as symbols" do @data['sensitive_parameters'] = ['content', 'mode'] expect(Puppet::Resource.from_data_hash(@data).sensitive_parameters).to eq [:content, :mode] end end it "implements copy_as_resource" do resource = Puppet::Resource.new("file", "/my/file") expect(resource.copy_as_resource).to eq(resource) end describe "when copying resources" do it "deep copies over 'sensitive' values" do rhs = Puppet::Resource.new("file", "/my/file", {:parameters => {:content => "foo"}, :sensitive_parameters => [:content]}) lhs = Puppet::Resource.new(rhs) expect(lhs.sensitive_parameters).to eq [:content] end end describe "because it is an indirector model" do it "should include Puppet::Indirector" do expect(Puppet::Resource).to be_is_a(Puppet::Indirector) end it "should have a default terminus" do expect(Puppet::Resource.indirection.terminus_class).to be end it "should have a name" do expect(Puppet::Resource.new("file", "/my/file").name).to eq("File//my/file") end end describe "when resolving resources with a catalog" do it "should resolve all resources using the catalog" do catalog = mock 'catalog' resource = Puppet::Resource.new("foo::bar", "yay") resource.catalog = catalog catalog.expects(:resource).with("Foo::Bar[yay]").returns(:myresource) expect(resource.resolve).to eq(:myresource) end end describe "when generating the uniqueness key" do it "should include all of the key_attributes in alphabetical order by attribute name" do Puppet::Type.type(:file).stubs(:key_attributes).returns [:myvar, :owner, :path] Puppet::Type.type(:file).stubs(:title_patterns).returns( [ [ /(.*)/, [ [:path, lambda{|x| x} ] ] ] ] ) res = Puppet::Resource.new("file", "/my/file", :parameters => {:owner => 'root', :content => 'hello'}) expect(res.uniqueness_key).to eq([ nil, 'root', '/my/file']) end end describe '#parse_title' do describe 'with a composite namevar' do before do Puppet::Type.newtype(:composite) do newparam(:name) newparam(:value) # Configure two title patterns to match a title that is either # separated with a colon or exclamation point. The first capture # will be used for the :name param, and the second capture will be # used for the :value param. def self.title_patterns identity = lambda {|x| x } reverse = lambda {|x| x.reverse } [ [ /^(.*?):(.*?)$/, [ [:name, identity], [:value, identity], ] ], [ /^(.*?)!(.*?)$/, [ [:name, reverse], [:value, reverse], ] ], ] end end end describe "with no matching title patterns" do subject { Puppet::Resource.new(:composite, 'unmatching title')} it "should raise an exception if no title patterns match" do expect do subject.to_hash end.to raise_error(Puppet::Error, /No set of title patterns matched/) end end describe "with a matching title pattern" do subject { Puppet::Resource.new(:composite, 'matching:title') } it "should not raise an exception if there was a match" do expect do subject.to_hash end.to_not raise_error end it "should set the resource parameters from the parsed title values" do h = subject.to_hash expect(h[:name]).to eq('matching') expect(h[:value]).to eq('title') end end describe "and multiple title patterns" do subject { Puppet::Resource.new(:composite, 'matching!title') } it "should use the first title pattern that matches" do h = subject.to_hash expect(h[:name]).to eq('gnihctam') expect(h[:value]).to eq('eltit') end end end end describe "#prune_parameters" do before do Puppet::Type.newtype('blond') do newproperty(:ensure) newproperty(:height) newproperty(:weight) newproperty(:sign) newproperty(:friends) newparam(:admits_to_dying_hair) newparam(:admits_to_age) newparam(:name) end end it "should strip all parameters and strip properties that are nil, empty or absent except for ensure" do resource = Puppet::Resource.new("blond", "Bambi", :parameters => { :ensure => 'absent', :height => '', :weight => 'absent', :friends => [], :admits_to_age => true, :admits_to_dying_hair => false }) pruned_resource = resource.prune_parameters expect(pruned_resource).to eq(Puppet::Resource.new("blond", "Bambi", :parameters => {:ensure => 'absent'})) end it "should leave parameters alone if in parameters_to_include" do resource = Puppet::Resource.new("blond", "Bambi", :parameters => { :admits_to_age => true, :admits_to_dying_hair => false }) pruned_resource = resource.prune_parameters(:parameters_to_include => [:admits_to_dying_hair]) expect(pruned_resource).to eq(Puppet::Resource.new("blond", "Bambi", :parameters => {:admits_to_dying_hair => false})) end it "should leave properties if not nil, absent or empty" do resource = Puppet::Resource.new("blond", "Bambi", :parameters => { :ensure => 'silly', :height => '7 ft 5 in', :friends => ['Oprah'], }) pruned_resource = resource.prune_parameters expect(pruned_resource).to eq( resource = Puppet::Resource.new("blond", "Bambi", :parameters => { :ensure => 'silly', :height => '7 ft 5 in', :friends => ['Oprah'], }) ) end end end puppet-5.5.10/spec/unit/settings_spec.rb0000644005276200011600000022556513417161722020142 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'ostruct' require 'puppet/settings/errors' require 'puppet_spec/files' require 'matchers/resource' describe Puppet::Settings do include PuppetSpec::Files include Matchers::Resource let(:main_config_file_default_location) do File.join(Puppet::Util::RunMode[:master].conf_dir, "puppet.conf") end let(:user_config_file_default_location) do File.join(Puppet::Util::RunMode[:user].conf_dir, "puppet.conf") end # Return a given object's file metadata. def metadata(setting) if setting.is_a?(Puppet::Settings::FileSetting) { :owner => setting.owner, :group => setting.group, :mode => setting.mode }.delete_if { |key, value| value.nil? } else nil end end describe "when specifying defaults" do before do @settings = Puppet::Settings.new end it "should start with no defined sections or parameters" do # Note this relies on undocumented side effect that eachsection returns the Settings internal # configuration on which keys returns all parameters. expect(@settings.eachsection.keys.length).to eq(0) end it "should not allow specification of default values associated with a section as an array" do expect { @settings.define_settings(:section, :myvalue => ["defaultval", "my description"]) }.to raise_error(ArgumentError, /setting definition for 'myvalue' is not a hash!/) end it "should not allow duplicate parameter specifications" do @settings.define_settings(:section, :myvalue => { :default => "a", :desc => "b" }) expect { @settings.define_settings(:section, :myvalue => { :default => "c", :desc => "d" }) }.to raise_error(ArgumentError) end it "should allow specification of default values associated with a section as a hash" do @settings.define_settings(:section, :myvalue => {:default => "defaultval", :desc => "my description"}) end it "should consider defined parameters to be valid" do @settings.define_settings(:section, :myvalue => { :default => "defaultval", :desc => "my description" }) expect(@settings.valid?(:myvalue)).to be_truthy end it "should require a description when defaults are specified with a hash" do expect { @settings.define_settings(:section, :myvalue => {:default => "a value"}) }.to raise_error(ArgumentError) end it "should support specifying owner, group, and mode when specifying files" do @settings.define_settings(:section, :myvalue => {:type => :file, :default => "/some/file", :owner => "service", :mode => "boo", :group => "service", :desc => "whatever"}) end it "should support specifying a short name" do @settings.define_settings(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) end it "should support specifying the setting type" do @settings.define_settings(:section, :myvalue => {:default => "/w", :desc => "b", :type => :string}) expect(@settings.setting(:myvalue)).to be_instance_of(Puppet::Settings::StringSetting) end it "should fail if an invalid setting type is specified" do expect { @settings.define_settings(:section, :myvalue => {:default => "w", :desc => "b", :type => :foo}) }.to raise_error(ArgumentError) end it "should fail when short names conflict" do @settings.define_settings(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) expect { @settings.define_settings(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) }.to raise_error(ArgumentError) end end describe "when initializing application defaults do" do let(:default_values) do values = {} PuppetSpec::Settings::TEST_APP_DEFAULT_DEFINITIONS.keys.each do |key| values[key] = 'default value' end values end before do @settings = Puppet::Settings.new @settings.define_settings(:main, PuppetSpec::Settings::TEST_APP_DEFAULT_DEFINITIONS) end it "should fail if the app defaults hash is missing any required values" do expect { @settings.initialize_app_defaults(default_values.reject { |key, _| key == :confdir }) }.to raise_error(Puppet::Settings::SettingsError) end # ultimately I'd like to stop treating "run_mode" as a normal setting, because it has so many special # case behaviors / uses. However, until that time... we need to make sure that our private run_mode= # setter method gets properly called during app initialization. it "sets the preferred run mode when initializing the app defaults" do @settings.initialize_app_defaults(default_values.merge(:run_mode => :master)) expect(@settings.preferred_run_mode).to eq(:master) end it "creates ancestor directories for all required app settings" do # initialize_app_defaults is called in spec_helper, before we even # get here, but call it here to make it explicit what we're trying # to do. @settings.initialize_app_defaults(default_values) Puppet::Settings::REQUIRED_APP_SETTINGS.each do |key| expect(File).to exist(File.dirname(Puppet[key])) end end end describe "#call_hooks_deferred_to_application_initialization" do let(:good_default) { "yay" } let(:bad_default) { "$doesntexist" } before(:each) do @settings = Puppet::Settings.new end describe "when ignoring dependency interpolation errors" do let(:options) { {:ignore_interpolation_dependency_errors => true} } describe "if interpolation error" do it "should not raise an error" do hook_values = [] @settings.define_settings(:section, :badhook => {:default => bad_default, :desc => "boo", :call_hook => :on_initialize_and_write, :hook => lambda { |v| hook_values << v }}) expect do @settings.send(:call_hooks_deferred_to_application_initialization, options) end.to_not raise_error end end describe "if no interpolation error" do it "should not raise an error" do hook_values = [] @settings.define_settings(:section, :goodhook => {:default => good_default, :desc => "boo", :call_hook => :on_initialize_and_write, :hook => lambda { |v| hook_values << v }}) expect do @settings.send(:call_hooks_deferred_to_application_initialization, options) end.to_not raise_error end end end describe "when not ignoring dependency interpolation errors" do [ {}, {:ignore_interpolation_dependency_errors => false}].each do |options| describe "if interpolation error" do it "should raise an error" do hook_values = [] @settings.define_settings( :section, :badhook => { :default => bad_default, :desc => "boo", :call_hook => :on_initialize_and_write, :hook => lambda { |v| hook_values << v } } ) expect do @settings.send(:call_hooks_deferred_to_application_initialization, options) end.to raise_error(Puppet::Settings::InterpolationError) end it "should contain the setting name in error message" do hook_values = [] @settings.define_settings( :section, :badhook => { :default => bad_default, :desc => "boo", :call_hook => :on_initialize_and_write, :hook => lambda { |v| hook_values << v } } ) expect do @settings.send(:call_hooks_deferred_to_application_initialization, options) end.to raise_error(Puppet::Settings::InterpolationError, /badhook/) end end describe "if no interpolation error" do it "should not raise an error" do hook_values = [] @settings.define_settings( :section, :goodhook => { :default => good_default, :desc => "boo", :call_hook => :on_initialize_and_write, :hook => lambda { |v| hook_values << v } } ) expect do @settings.send(:call_hooks_deferred_to_application_initialization, options) end.to_not raise_error end end end end end describe "when setting values" do before do @settings = Puppet::Settings.new @settings.define_settings :main, :myval => { :default => "val", :desc => "desc" } @settings.define_settings :main, :bool => { :type => :boolean, :default => true, :desc => "desc" } end it "should provide a method for setting values from other objects" do @settings[:myval] = "something else" expect(@settings[:myval]).to eq("something else") end it "should support a getopt-specific mechanism for setting values" do @settings.handlearg("--myval", "newval") expect(@settings[:myval]).to eq("newval") end it "should support a getopt-specific mechanism for turning booleans off" do @settings.override_default(:bool, true) @settings.handlearg("--no-bool", "") expect(@settings[:bool]).to eq(false) end it "should support a getopt-specific mechanism for turning booleans on" do # Turn it off first @settings.override_default(:bool, false) @settings.handlearg("--bool", "") expect(@settings[:bool]).to eq(true) end it "should consider a cli setting with no argument to be a boolean" do # Turn it off first @settings.override_default(:bool, false) @settings.handlearg("--bool") expect(@settings[:bool]).to eq(true) end it "should consider a cli setting with an empty string as an argument to be an empty argument, if the setting itself is not a boolean" do @settings.override_default(:myval, "bob") @settings.handlearg("--myval", "") expect(@settings[:myval]).to eq("") end it "should consider a cli setting with a boolean as an argument to be a boolean" do # Turn it off first @settings.override_default(:bool, false) @settings.handlearg("--bool", "true") expect(@settings[:bool]).to eq(true) end it "should not consider a cli setting of a non boolean with a boolean as an argument to be a boolean" do @settings.override_default(:myval, "bob") @settings.handlearg("--no-myval", "") expect(@settings[:myval]).to eq("") end it "should flag string settings from the CLI" do @settings.handlearg("--myval", "12") expect(@settings.set_by_cli?(:myval)).to be_truthy end it "should flag bool settings from the CLI" do @settings.handlearg("--bool") expect(@settings.set_by_cli?(:bool)).to be_truthy end it "should not flag settings memory as from CLI" do @settings[:myval] = "12" expect(@settings.set_by_cli?(:myval)).to be_falsey end it "should find no configured settings by default" do expect(@settings.set_by_config?(:myval)).to be_falsey end it "should identify configured settings in memory" do @settings.instance_variable_get(:@value_sets)[:memory].expects(:lookup).with(:myval).returns('foo') expect(@settings.set_by_config?(:myval)).to be_truthy end it "should identify configured settings from CLI" do @settings.instance_variable_get(:@value_sets)[:cli].expects(:lookup).with(:myval).returns('foo') expect(@settings.set_by_config?(:myval)).to be_truthy end it "should not identify configured settings from environment by default" do Puppet.lookup(:environments).expects(:get_conf).with(Puppet[:environment].to_sym).never expect(@settings.set_by_config?(:manifest)).to be_falsey end it "should identify configured settings from environment by when an environment is specified" do foo = mock('environment', :manifest => 'foo') Puppet.lookup(:environments).expects(:get_conf).with(Puppet[:environment].to_sym).returns(foo) expect(@settings.set_by_config?(:manifest, Puppet[:environment])).to be_truthy end it "should identify configured settings from the preferred run mode" do user_config_text = "[#{@settings.preferred_run_mode}]\nmyval = foo" seq = sequence "config_file_sequence" Puppet.features.stubs(:root?).returns(false) Puppet::FileSystem.expects(:exist?). with(user_config_file_default_location). returns(true).in_sequence(seq) @settings.expects(:read_file). with(user_config_file_default_location). returns(user_config_text).in_sequence(seq) @settings.send(:parse_config_files) expect(@settings.set_by_config?(:myval)).to be_truthy end it "should identify configured settings from the specified run mode" do user_config_text = "[master]\nmyval = foo" seq = sequence "config_file_sequence" Puppet.features.stubs(:root?).returns(false) Puppet::FileSystem.expects(:exist?). with(user_config_file_default_location). returns(true).in_sequence(seq) @settings.expects(:read_file). with(user_config_file_default_location). returns(user_config_text).in_sequence(seq) @settings.send(:parse_config_files) expect(@settings.set_by_config?(:myval, nil, :master)).to be_truthy end it "should not identify configured settings from an unspecified run mode" do user_config_text = "[zaz]\nmyval = foo" seq = sequence "config_file_sequence" Puppet.features.stubs(:root?).returns(false) Puppet::FileSystem.expects(:exist?). with(user_config_file_default_location). returns(true).in_sequence(seq) @settings.expects(:read_file). with(user_config_file_default_location). returns(user_config_text).in_sequence(seq) @settings.send(:parse_config_files) expect(@settings.set_by_config?(:myval)).to be_falsey end it "should identify configured settings from the main section" do user_config_text = "[main]\nmyval = foo" seq = sequence "config_file_sequence" Puppet.features.stubs(:root?).returns(false) Puppet::FileSystem.expects(:exist?). with(user_config_file_default_location). returns(true).in_sequence(seq) @settings.expects(:read_file). with(user_config_file_default_location). returns(user_config_text).in_sequence(seq) @settings.send(:parse_config_files) expect(@settings.set_by_config?(:myval)).to be_truthy end it "should clear the cache when setting getopt-specific values" do @settings.define_settings :mysection, :one => { :default => "whah", :desc => "yay" }, :two => { :default => "$one yay", :desc => "bah" } @settings.expects(:unsafe_flush_cache) expect(@settings[:two]).to eq("whah yay") @settings.handlearg("--one", "else") expect(@settings[:two]).to eq("else yay") end it "should clear the cache when the preferred_run_mode is changed" do @settings.expects(:flush_cache) @settings.preferred_run_mode = :master end it "should not clear other values when setting getopt-specific values" do @settings[:myval] = "yay" @settings.handlearg("--no-bool", "") expect(@settings[:myval]).to eq("yay") end it "should clear the list of used sections" do @settings.expects(:clearused) @settings[:myval] = "yay" end describe "call_hook" do Puppet::Settings::StringSetting.available_call_hook_values.each do |val| describe "when :#{val}" do describe "and definition invalid" do it "should raise error if no hook defined" do expect do @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => val}) end.to raise_error(ArgumentError, /no :hook/) end it "should include the setting name in the error message" do expect do @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => val}) end.to raise_error(ArgumentError, /for :hooker/) end end describe "and definition valid" do before(:each) do hook_values = [] @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => val, :hook => lambda { |v| hook_values << v }}) end it "should call the hook when value written" do @settings.setting(:hooker).expects(:handle).with("something").once @settings[:hooker] = "something" end end end end it "should have a default value of :on_write_only" do @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :hook => lambda { |v| hook_values << v }}) expect(@settings.setting(:hooker).call_hook).to eq(:on_write_only) end describe "when nil" do it "should generate a warning" do Puppet.expects(:warning) @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => nil, :hook => lambda { |v| hook_values << v }}) end it "should use default" do @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => nil, :hook => lambda { |v| hook_values << v }}) expect(@settings.setting(:hooker).call_hook).to eq(:on_write_only) end end describe "when invalid" do it "should raise an error" do expect do @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => :foo, :hook => lambda { |v| hook_values << v }}) end.to raise_error(ArgumentError, /invalid.*call_hook/i) end end describe "when :on_define_and_write" do it "should call the hook at definition" do hook_values = [] @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => :on_define_and_write, :hook => lambda { |v| hook_values << v }}) expect(@settings.setting(:hooker).call_hook).to eq(:on_define_and_write) expect(hook_values).to eq(%w{yay}) end end describe "when :on_initialize_and_write" do before(:each) do @hook_values = [] @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => :on_initialize_and_write, :hook => lambda { |v| @hook_values << v }}) end it "should not call the hook at definition" do expect(@hook_values).to eq([]) expect(@hook_values).not_to eq(%w{yay}) end it "should call the hook at initialization" do app_defaults = {} Puppet::Settings::REQUIRED_APP_SETTINGS.each do |key| app_defaults[key] = "foo" end app_defaults[:run_mode] = :user @settings.define_settings(:main, PuppetSpec::Settings::TEST_APP_DEFAULT_DEFINITIONS) @settings.setting(:hooker).expects(:handle).with("yay").once @settings.initialize_app_defaults app_defaults end end end it "should call passed blocks when values are set" do values = [] @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :hook => lambda { |v| values << v }}) expect(values).to eq([]) @settings[:hooker] = "something" expect(values).to eq(%w{something}) end it "should call passed blocks when values are set via the command line" do values = [] @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :hook => lambda { |v| values << v }}) expect(values).to eq([]) @settings.handlearg("--hooker", "yay") expect(values).to eq(%w{yay}) end it "should provide an option to call passed blocks during definition" do values = [] @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => :on_define_and_write, :hook => lambda { |v| values << v }}) expect(values).to eq(%w{yay}) end it "should pass the fully interpolated value to the hook when called on definition" do values = [] @settings.define_settings(:section, :one => { :default => "test", :desc => "a" }) @settings.define_settings(:section, :hooker => {:default => "$one/yay", :desc => "boo", :call_hook => :on_define_and_write, :hook => lambda { |v| values << v }}) expect(values).to eq(%w{test/yay}) end it "should munge values using the setting-specific methods" do @settings[:bool] = "false" expect(@settings[:bool]).to eq(false) end it "should prefer values set in ruby to values set on the cli" do @settings[:myval] = "memarg" @settings.handlearg("--myval", "cliarg") expect(@settings[:myval]).to eq("memarg") end it "should raise an error if we try to set a setting that hasn't been defined'" do expect{ @settings[:why_so_serious] = "foo" }.to raise_error(ArgumentError, /unknown setting/) end it "allows overriding cli args based on the cli-set value" do @settings.handlearg("--myval", "cliarg") @settings.patch_value(:myval, "modified #{@settings[:myval]}", :cli) expect(@settings[:myval]).to eq("modified cliarg") end end describe "when returning values" do before do @settings = Puppet::Settings.new @settings.define_settings :section, :config => { :type => :file, :default => "/my/file", :desc => "eh" }, :one => { :default => "ONE", :desc => "a" }, :two => { :default => "$one TWO", :desc => "b"}, :three => { :default => "$one $two THREE", :desc => "c"}, :four => { :default => "$two $three FOUR", :desc => "d"}, :five => { :default => nil, :desc => "e" }, :code => { :default => "", :desc => "my code"} Puppet::FileSystem.stubs(:exist?).returns true end it "should provide a mechanism for returning set values" do @settings[:one] = "other" expect(@settings[:one]).to eq("other") end it "setting a value to nil causes it to return to its default" do default_values = { :one => "skipped value" } [:logdir, :confdir, :codedir, :vardir].each do |key| default_values[key] = 'default value' end @settings.define_settings :main, PuppetSpec::Settings::TEST_APP_DEFAULT_DEFINITIONS @settings.initialize_app_defaults(default_values) @settings[:one] = "value will disappear" @settings[:one] = nil expect(@settings[:one]).to eq("ONE") end it "should interpolate default values for other parameters into returned parameter values" do expect(@settings[:one]).to eq("ONE") expect(@settings[:two]).to eq("ONE TWO") expect(@settings[:three]).to eq("ONE ONE TWO THREE") end it "should interpolate default values that themselves need to be interpolated" do expect(@settings[:four]).to eq("ONE TWO ONE ONE TWO THREE FOUR") end it "should provide a method for returning uninterpolated values" do @settings[:two] = "$one tw0" expect(@settings.value(:two, nil, true)).to eq("$one tw0") expect(@settings.value(:four, nil, true)).to eq("$two $three FOUR") end it "should interpolate set values for other parameters into returned parameter values" do @settings[:one] = "on3" @settings[:two] = "$one tw0" @settings[:three] = "$one $two thr33" @settings[:four] = "$one $two $three f0ur" expect(@settings[:one]).to eq("on3") expect(@settings[:two]).to eq("on3 tw0") expect(@settings[:three]).to eq("on3 on3 tw0 thr33") expect(@settings[:four]).to eq("on3 on3 tw0 on3 on3 tw0 thr33 f0ur") end it "should not cache interpolated values such that stale information is returned" do expect(@settings[:two]).to eq("ONE TWO") @settings[:one] = "one" expect(@settings[:two]).to eq("one TWO") end it "should not interpolate the value of the :code setting" do @code = @settings.setting(:code) @code.expects(:munge).never expect(@settings[:code]).to eq("") end it "should have a run_mode that defaults to user" do expect(@settings.preferred_run_mode).to eq(:user) end it "interpolates a boolean false without raising an error" do @settings.define_settings(:section, :trip_wire => { :type => :boolean, :default => false, :desc => "a trip wire" }, :tripping => { :default => '$trip_wire', :desc => "once tripped if interpolated was false" }) expect(@settings[:tripping]).to eq("false") end end describe "when choosing which value to return" do before do @settings = Puppet::Settings.new @settings.define_settings :section, :config => { :type => :file, :default => "/my/file", :desc => "a" }, :one => { :default => "ONE", :desc => "a" }, :two => { :default => "TWO", :desc => "b" } Puppet::FileSystem.stubs(:exist?).returns true @settings.preferred_run_mode = :agent end it "should return default values if no values have been set" do expect(@settings[:one]).to eq("ONE") end it "should return values set on the cli before values set in the configuration file" do text = "[main]\none = fileval\n" @settings.stubs(:read_file).returns(text) @settings.handlearg("--one", "clival") @settings.send(:parse_config_files) expect(@settings[:one]).to eq("clival") end it "should return values set in the mode-specific section before values set in the main section" do text = "[main]\none = mainval\n[agent]\none = modeval\n" @settings.stubs(:read_file).returns(text) @settings.send(:parse_config_files) expect(@settings[:one]).to eq("modeval") end it "should not return values outside of its search path" do text = "[other]\none = oval\n" @settings.stubs(:read_file).returns(text) @settings.send(:parse_config_files) expect(@settings[:one]).to eq("ONE") end it 'should use the current environment for $environment' do @settings.define_settings :main, :config_version => { :default => "$environment/foo", :desc => "mydocs" } expect(@settings.value(:config_version, "myenv")).to eq("myenv/foo") end end describe "when locating config files" do before do @settings = Puppet::Settings.new end describe "when root" do it "should look for the main config file default location config settings haven't been overridden'" do Puppet.features.stubs(:root?).returns(true) Puppet::FileSystem.expects(:exist?).with(main_config_file_default_location).returns(false) Puppet::FileSystem.expects(:exist?).with(user_config_file_default_location).never @settings.send(:parse_config_files) end end describe "when not root" do it "should look for user config file default location if config settings haven't been overridden'" do Puppet.features.stubs(:root?).returns(false) seq = sequence "load config files" Puppet::FileSystem.expects(:exist?).with(user_config_file_default_location).returns(false).in_sequence(seq) @settings.send(:parse_config_files) end end end describe "when parsing its configuration" do before do @settings = Puppet::Settings.new @settings.stubs(:service_user_available?).returns true @settings.stubs(:service_group_available?).returns true @file = make_absolute("/some/file") @userconfig = make_absolute("/test/userconfigfile") @settings.define_settings :section, :user => { :default => "suser", :desc => "doc" }, :group => { :default => "sgroup", :desc => "doc" } @settings.define_settings :section, :config => { :type => :file, :default => @file, :desc => "eh" }, :one => { :default => "ONE", :desc => "a" }, :two => { :default => "$one TWO", :desc => "b" }, :three => { :default => "$one $two THREE", :desc => "c" } @settings.stubs(:user_config_file).returns(@userconfig) Puppet::FileSystem.stubs(:exist?).with(@file).returns true Puppet::FileSystem.stubs(:exist?).with(@userconfig).returns false end it "should not ignore the report setting" do @settings.define_settings :section, :report => { :default => "false", :desc => "a" } # This is needed in order to make sure we pass on windows myfile = File.expand_path(@file) @settings[:config] = myfile text = <<-CONF [puppetd] report=true CONF Puppet::FileSystem.expects(:exist?).with(myfile).returns(true) @settings.expects(:read_file).returns(text) @settings.send(:parse_config_files) expect(@settings[:report]).to be_truthy end it "should use its current ':config' value for the file to parse" do myfile = make_absolute("/my/file") # do not stub expand_path here, as this leads to a stack overflow, when mocha tries to use it @settings[:config] = myfile Puppet::FileSystem.expects(:exist?).with(myfile).returns(true) Puppet::FileSystem.expects(:read).with(myfile, :encoding => 'utf-8').returns "[main]" @settings.send(:parse_config_files) end it "should not try to parse non-existent files" do Puppet::FileSystem.expects(:exist?).with(@file).returns false File.expects(:read).with(@file).never @settings.send(:parse_config_files) end it "should return values set in the configuration file" do text = "[main] one = fileval " @settings.expects(:read_file).returns(text) @settings.send(:parse_config_files) expect(@settings[:one]).to eq("fileval") end #484 - this should probably be in the regression area it "should not throw an exception on unknown parameters" do text = "[main]\nnosuchparam = mval\n" @settings.expects(:read_file).returns(text) expect { @settings.send(:parse_config_files) }.not_to raise_error end it "should convert booleans in the configuration file into Ruby booleans" do text = "[main] one = true two = false " @settings.expects(:read_file).returns(text) @settings.send(:parse_config_files) expect(@settings[:one]).to eq(true) expect(@settings[:two]).to eq(false) end it "should convert integers in the configuration file into Ruby Integers" do text = "[main] one = 65 " @settings.expects(:read_file).returns(text) @settings.send(:parse_config_files) expect(@settings[:one]).to eq(65) end it "should support specifying all metadata (owner, group, mode) in the configuration file" do @settings.define_settings :section, :myfile => { :type => :file, :default => make_absolute("/myfile"), :desc => "a" } otherfile = make_absolute("/other/file") @settings.parse_config(<<-CONF) [main] myfile = #{otherfile} {owner = service, group = service, mode = 644} CONF expect(@settings[:myfile]).to eq(otherfile) expect(metadata(@settings.setting(:myfile))).to eq({:owner => "suser", :group => "sgroup", :mode => "644"}) end it "should support specifying a single piece of metadata (owner, group, or mode) in the configuration file" do @settings.define_settings :section, :myfile => { :type => :file, :default => make_absolute("/myfile"), :desc => "a" } otherfile = make_absolute("/other/file") @settings.parse_config(<<-CONF) [main] myfile = #{otherfile} {owner = service} CONF expect(@settings[:myfile]).to eq(otherfile) expect(metadata(@settings.setting(:myfile))).to eq({:owner => "suser"}) end it "should support loading metadata (owner, group, or mode) from a run_mode section in the configuration file" do default_values = {} PuppetSpec::Settings::TEST_APP_DEFAULT_DEFINITIONS.keys.each do |key| default_values[key] = 'default value' end @settings.define_settings :main, PuppetSpec::Settings::TEST_APP_DEFAULT_DEFINITIONS @settings.define_settings :master, :myfile => { :type => :file, :default => make_absolute("/myfile"), :desc => "a" } otherfile = make_absolute("/other/file") text = "[master] myfile = #{otherfile} {mode = 664} " @settings.expects(:read_file).returns(text) # will start initialization as user expect(@settings.preferred_run_mode).to eq(:user) @settings.send(:parse_config_files) # change app run_mode to master @settings.initialize_app_defaults(default_values.merge(:run_mode => :master)) expect(@settings.preferred_run_mode).to eq(:master) # initializing the app should have reloaded the metadata based on run_mode expect(@settings[:myfile]).to eq(otherfile) expect(metadata(@settings.setting(:myfile))).to eq({:mode => "664"}) end it "does not use the metadata from the same setting in a different section" do default_values = {} PuppetSpec::Settings::TEST_APP_DEFAULT_DEFINITIONS.keys.each do |key| default_values[key] = 'default value' end file = make_absolute("/file") default_mode = "0600" @settings.define_settings :main, PuppetSpec::Settings::TEST_APP_DEFAULT_DEFINITIONS @settings.define_settings :master, :myfile => { :type => :file, :default => file, :desc => "a", :mode => default_mode } text = "[master] myfile = #{file}/foo [agent] myfile = #{file} {mode = 664} " @settings.expects(:read_file).returns(text) # will start initialization as user expect(@settings.preferred_run_mode).to eq(:user) @settings.send(:parse_config_files) # change app run_mode to master @settings.initialize_app_defaults(default_values.merge(:run_mode => :master)) expect(@settings.preferred_run_mode).to eq(:master) # initializing the app should have reloaded the metadata based on run_mode expect(@settings[:myfile]).to eq("#{file}/foo") expect(metadata(@settings.setting(:myfile))).to eq({ :mode => default_mode }) end it "should call hooks associated with values set in the configuration file" do values = [] @settings.define_settings :section, :mysetting => {:default => "defval", :desc => "a", :hook => proc { |v| values << v }} text = "[main] mysetting = setval " @settings.expects(:read_file).returns(text) @settings.send(:parse_config_files) expect(values).to eq(["setval"]) end it "should not call the same hook for values set multiple times in the configuration file" do values = [] @settings.define_settings :section, :mysetting => {:default => "defval", :desc => "a", :hook => proc { |v| values << v }} text = "[user] mysetting = setval [main] mysetting = other " @settings.expects(:read_file).returns(text) @settings.send(:parse_config_files) expect(values).to eq(["setval"]) end it "should pass the interpolated value to the hook when one is available" do values = [] @settings.define_settings :section, :base => {:default => "yay", :desc => "a", :hook => proc { |v| values << v }} @settings.define_settings :section, :mysetting => {:default => "defval", :desc => "a", :hook => proc { |v| values << v }} text = "[main] mysetting = $base/setval " @settings.expects(:read_file).returns(text) @settings.send(:parse_config_files) expect(values).to eq(["yay/setval"]) end it "should allow hooks invoked at parse time to be deferred" do hook_invoked = false @settings.define_settings :section, :deferred => {:desc => '', :hook => proc { |v| hook_invoked = true }, :call_hook => :on_initialize_and_write, } @settings.define_settings(:main, :logdir => { :type => :directory, :default => nil, :desc => "logdir" }, :confdir => { :type => :directory, :default => nil, :desc => "confdir" }, :codedir => { :type => :directory, :default => nil, :desc => "codedir" }, :vardir => { :type => :directory, :default => nil, :desc => "vardir" }) text = <<-EOD [main] deferred=$confdir/goose EOD @settings.stubs(:read_file).returns(text) @settings.initialize_global_settings expect(hook_invoked).to be_falsey @settings.initialize_app_defaults(:logdir => '/path/to/logdir', :confdir => '/path/to/confdir', :vardir => '/path/to/vardir', :codedir => '/path/to/codedir') expect(hook_invoked).to be_truthy expect(@settings[:deferred]).to eq(File.expand_path('/path/to/confdir/goose')) end it "does not require the value for a setting without a hook to resolve during global setup" do @settings.define_settings :section, :can_cause_problems => {:desc => '' } @settings.define_settings(:main, :logdir => { :type => :directory, :default => nil, :desc => "logdir" }, :confdir => { :type => :directory, :default => nil, :desc => "confdir" }, :codedir => { :type => :directory, :default => nil, :desc => "codedir" }, :vardir => { :type => :directory, :default => nil, :desc => "vardir" }) text = <<-EOD [main] can_cause_problems=$confdir/goose EOD @settings.stubs(:read_file).returns(text) @settings.initialize_global_settings @settings.initialize_app_defaults(:logdir => '/path/to/logdir', :confdir => '/path/to/confdir', :vardir => '/path/to/vardir', :codedir => '/path/to/codedir') expect(@settings[:can_cause_problems]).to eq(File.expand_path('/path/to/confdir/goose')) end it "should allow empty values" do @settings.define_settings :section, :myarg => { :default => "myfile", :desc => "a" } text = "[main] myarg = " @settings.stubs(:read_file).returns(text) @settings.send(:parse_config_files) expect(@settings[:myarg]).to eq("") end describe "deprecations" do let(:settings) { Puppet::Settings.new } let(:app_defaults) { { :logdir => "/dev/null", :confdir => "/dev/null", :codedir => "/dev/null", :vardir => "/dev/null", } } def assert_accessing_setting_is_deprecated(settings, setting) Puppet.expects(:deprecation_warning).with("Accessing '#{setting}' as a setting is deprecated.") Puppet.expects(:deprecation_warning).with("Modifying '#{setting}' as a setting is deprecated.") settings[setting.intern] = apath = File.expand_path('foo') expect(settings[setting.intern]).to eq(apath) end before(:each) do settings.define_settings(:main, { :logdir => { :default => 'a', :desc => 'a' }, :confdir => { :default => 'b', :desc => 'b' }, :vardir => { :default => 'c', :desc => 'c' }, :codedir => { :default => 'd', :desc => 'd' }, }) end context "complete" do let(:completely_deprecated_settings) do settings.define_settings(:main, { :completely_deprecated_setting => { :default => 'foo', :desc => 'a deprecated setting', :deprecated => :completely, } }) settings end it "warns when set in puppet.conf" do Puppet.expects(:deprecation_warning).with(regexp_matches(/completely_deprecated_setting is deprecated\./), 'setting-completely_deprecated_setting') completely_deprecated_settings.parse_config(<<-CONF) completely_deprecated_setting='should warn' CONF completely_deprecated_settings.initialize_app_defaults(app_defaults) end it "warns when set on the commandline" do Puppet.expects(:deprecation_warning).with(regexp_matches(/completely_deprecated_setting is deprecated\./), 'setting-completely_deprecated_setting') args = ["--completely_deprecated_setting", "/some/value"] completely_deprecated_settings.send(:parse_global_options, args) completely_deprecated_settings.initialize_app_defaults(app_defaults) end it "warns when set in code" do assert_accessing_setting_is_deprecated(completely_deprecated_settings, 'completely_deprecated_setting') end end context "partial" do let(:partially_deprecated_settings) do settings.define_settings(:main, { :partially_deprecated_setting => { :default => 'foo', :desc => 'a partially deprecated setting', :deprecated => :allowed_on_commandline, } }) settings end it "warns for a deprecated setting allowed on the command line set in puppet.conf" do Puppet.expects(:deprecation_warning).with(regexp_matches(/partially_deprecated_setting is deprecated in puppet\.conf/), 'puppet-conf-setting-partially_deprecated_setting') partially_deprecated_settings.parse_config(<<-CONF) partially_deprecated_setting='should warn' CONF partially_deprecated_settings.initialize_app_defaults(app_defaults) end it "does not warn when manifest is set on command line" do Puppet.expects(:deprecation_warning).never args = ["--partially_deprecated_setting", "/some/value"] partially_deprecated_settings.send(:parse_global_options, args) partially_deprecated_settings.initialize_app_defaults(app_defaults) end it "warns when set in code" do assert_accessing_setting_is_deprecated(partially_deprecated_settings, 'partially_deprecated_setting') end end end end describe "when there are multiple config files" do let(:main_config_text) { "[main]\none = main\ntwo = main2" } let(:user_config_text) { "[main]\none = user\n" } let(:seq) { sequence "config_file_sequence" } before :each do @settings = Puppet::Settings.new @settings.define_settings(:section, { :confdir => { :default => nil, :desc => "Conf dir" }, :config => { :default => "$confdir/puppet.conf", :desc => "Config" }, :one => { :default => "ONE", :desc => "a" }, :two => { :default => "TWO", :desc => "b" }, }) end context "running non-root without explicit config file" do before :each do Puppet.features.stubs(:root?).returns(false) Puppet::FileSystem.expects(:exist?). with(user_config_file_default_location). returns(true).in_sequence(seq) @settings.expects(:read_file). with(user_config_file_default_location). returns(user_config_text).in_sequence(seq) end it "should return values from the user config file" do @settings.send(:parse_config_files) expect(@settings[:one]).to eq("user") end it "should not return values from the main config file" do @settings.send(:parse_config_files) expect(@settings[:two]).to eq("TWO") end end context "running as root without explicit config file" do before :each do Puppet.features.stubs(:root?).returns(true) Puppet::FileSystem.expects(:exist?). with(main_config_file_default_location). returns(true).in_sequence(seq) @settings.expects(:read_file). with(main_config_file_default_location). returns(main_config_text).in_sequence(seq) end it "should return values from the main config file" do @settings.send(:parse_config_files) expect(@settings[:one]).to eq("main") end it "should not return values from the user config file" do @settings.send(:parse_config_files) expect(@settings[:two]).to eq("main2") end end context "running with an explicit config file as a user (e.g. Apache + Passenger)" do before :each do Puppet.features.stubs(:root?).returns(false) @settings[:confdir] = File.dirname(main_config_file_default_location) Puppet::FileSystem.expects(:exist?). with(main_config_file_default_location). returns(true).in_sequence(seq) @settings.expects(:read_file). with(main_config_file_default_location). returns(main_config_text).in_sequence(seq) end it "should return values from the main config file" do @settings.send(:parse_config_files) expect(@settings[:one]).to eq("main") end it "should not return values from the user config file" do @settings.send(:parse_config_files) expect(@settings[:two]).to eq("main2") end end end describe "when reparsing its configuration" do before do @file = make_absolute("/test/file") @userconfig = make_absolute("/test/userconfigfile") @settings = Puppet::Settings.new @settings.define_settings :section, :config => { :type => :file, :default => @file, :desc => "a" }, :one => { :default => "ONE", :desc => "a" }, :two => { :default => "$one TWO", :desc => "b" }, :three => { :default => "$one $two THREE", :desc => "c" } Puppet::FileSystem.stubs(:exist?).with(@file).returns true Puppet::FileSystem.stubs(:exist?).with(@userconfig).returns false @settings.stubs(:user_config_file).returns(@userconfig) end it "does not create the WatchedFile instance and should not parse if the file does not exist" do Puppet::FileSystem.expects(:exist?).with(@file).returns false Puppet::Util::WatchedFile.expects(:new).never @settings.expects(:parse_config_files).never @settings.reparse_config_files end context "and watched file exists" do before do @watched_file = Puppet::Util::WatchedFile.new(@file) Puppet::Util::WatchedFile.expects(:new).with(@file).returns @watched_file end it "uses a WatchedFile instance to determine if the file has changed" do @watched_file.expects(:changed?) @settings.reparse_config_files end it "does not reparse if the file has not changed" do @watched_file.expects(:changed?).returns false @settings.expects(:parse_config_files).never @settings.reparse_config_files end it "reparses if the file has changed" do @watched_file.expects(:changed?).returns true @settings.expects(:parse_config_files) @settings.reparse_config_files end it "replaces in-memory values with on-file values" do @watched_file.stubs(:changed?).returns(true) @settings[:one] = "init" # Now replace the value text = "[main]\none = disk-replace\n" @settings.stubs(:read_file).returns(text) @settings.reparse_config_files expect(@settings[:one]).to eq("disk-replace") end end it "should retain parameters set by cli when configuration files are reparsed" do @settings.handlearg("--one", "clival") text = "[main]\none = on-disk\n" @settings.stubs(:read_file).returns(text) @settings.send(:parse_config_files) expect(@settings[:one]).to eq("clival") end it "should remove in-memory values that are no longer set in the file" do # Init the value text = "[main]\none = disk-init\n" @settings.expects(:read_file).returns(text) @settings.send(:parse_config_files) expect(@settings[:one]).to eq("disk-init") # Now replace the value text = "[main]\ntwo = disk-replace\n" @settings.expects(:read_file).returns(text) @settings.send(:parse_config_files) # The originally-overridden value should be replaced with the default expect(@settings[:one]).to eq("ONE") # and we should now have the new value in memory expect(@settings[:two]).to eq("disk-replace") end it "should retain in-memory values if the file has a syntax error" do # Init the value text = "[main]\none = initial-value\n" @settings.expects(:read_file).with(@file).returns(text) @settings.send(:parse_config_files) expect(@settings[:one]).to eq("initial-value") # Now replace the value with something bogus text = "[main]\nkenny = killed-by-what-follows\n1 is 2, blah blah florp\n" @settings.expects(:read_file).with(@file).returns(text) @settings.send(:parse_config_files) # The originally-overridden value should not be replaced with the default expect(@settings[:one]).to eq("initial-value") # and we should not have the new value in memory expect(@settings[:kenny]).to be_nil end end it "should provide a method for creating a catalog of resources from its configuration" do expect(Puppet::Settings.new).to respond_to(:to_catalog) end describe "when creating a catalog" do before do @settings = Puppet::Settings.new @settings.stubs(:service_user_available?).returns true @prefix = Puppet.features.posix? ? "" : "C:" end it "should add all file resources to the catalog if no sections have been specified" do @settings.define_settings :main, :maindir => { :type => :directory, :default => @prefix+"/maindir", :desc => "a"}, :seconddir => { :type => :directory, :default => @prefix+"/seconddir", :desc => "a"} @settings.define_settings :other, :otherdir => { :type => :directory, :default => @prefix+"/otherdir", :desc => "a" } catalog = @settings.to_catalog [@prefix+"/maindir", @prefix+"/seconddir", @prefix+"/otherdir"].each do |path| expect(catalog.resource(:file, path)).to be_instance_of(Puppet::Resource) end end it "should add only files in the specified sections if section names are provided" do @settings.define_settings :main, :maindir => { :type => :directory, :default => @prefix+"/maindir", :desc => "a" } @settings.define_settings :other, :otherdir => { :type => :directory, :default => @prefix+"/otherdir", :desc => "a" } catalog = @settings.to_catalog(:main) expect(catalog.resource(:file, @prefix+"/otherdir")).to be_nil expect(catalog.resource(:file, @prefix+"/maindir")).to be_instance_of(Puppet::Resource) end it "should not try to add the same file twice" do @settings.define_settings :main, :maindir => { :type => :directory, :default => @prefix+"/maindir", :desc => "a" } @settings.define_settings :other, :otherdir => { :type => :directory, :default => @prefix+"/maindir", :desc => "a" } expect { @settings.to_catalog }.not_to raise_error end it "should ignore files whose :to_resource method returns nil" do @settings.define_settings :main, :maindir => { :type => :directory, :default => @prefix+"/maindir", :desc => "a" } @settings.setting(:maindir).expects(:to_resource).returns nil Puppet::Resource::Catalog.any_instance.expects(:add_resource).never @settings.to_catalog end describe "on Microsoft Windows", :if => Puppet.features.microsoft_windows? do before :each do Puppet.features.stubs(:root?).returns true @settings.define_settings :foo, :mkusers => { :type => :boolean, :default => true, :desc => "e" }, :user => { :default => "suser", :desc => "doc" }, :group => { :default => "sgroup", :desc => "doc" } @settings.define_settings :other, :otherdir => { :type => :directory, :default => "/otherdir", :desc => "a", :owner => "service", :group => "service"} @catalog = @settings.to_catalog end it "it should not add users and groups to the catalog" do expect(@catalog.resource(:user, "suser")).to be_nil expect(@catalog.resource(:group, "sgroup")).to be_nil end end describe "adding default directory environment to the catalog" do let(:tmpenv) { tmpdir("envs") } let(:default_path) { "#{tmpenv}/environments" } before(:each) do @settings.define_settings :main, :environment => { :default => "production", :desc => "env"}, :environmentpath => { :type => :path, :default => default_path, :desc => "envpath"} end it "adds if environmentpath exists" do envpath = "#{tmpenv}/custom_envpath" @settings[:environmentpath] = envpath Dir.mkdir(envpath) catalog = @settings.to_catalog expect(catalog.resource_keys).to include(["File", "#{envpath}/production"]) end it "adds the first directory of environmentpath" do envdir = "#{tmpenv}/custom_envpath" envpath = "#{envdir}#{File::PATH_SEPARATOR}/some/other/envdir" @settings[:environmentpath] = envpath Dir.mkdir(envdir) catalog = @settings.to_catalog expect(catalog.resource_keys).to include(["File", "#{envdir}/production"]) end it 'adds the creation of the production directory when not run as root' do envdir = "#{tmpenv}/custom_envpath" envpath = "#{envdir}#{File::PATH_SEPARATOR}/some/other/envdir" @settings[:environmentpath] = envpath Dir.mkdir(envdir) Puppet.features.stubs(:root?).returns false catalog = @settings.to_catalog resource = catalog.resource('File', File.join(envdir, 'production')) expect(resource[:mode]).to eq('0750') expect(resource[:owner]).to be_nil expect(resource[:group]).to be_nil end it 'adds the creation of the production directory with service owner and group information when available' do envdir = "#{tmpenv}/custom_envpath" envpath = "#{envdir}#{File::PATH_SEPARATOR}/some/other/envdir" @settings[:environmentpath] = envpath Dir.mkdir(envdir) Puppet.features.stubs(:root?).returns true @settings.stubs(:service_user_available?).returns true @settings.stubs(:service_group_available?).returns true catalog = @settings.to_catalog resource = catalog.resource('File', File.join(envdir, 'production')) expect(resource[:mode]).to eq('0750') expect(resource[:owner]).to eq('puppet') expect(resource[:group]).to eq('puppet') end it 'adds the creation of the production directory without service owner and group when not available' do envdir = "#{tmpenv}/custom_envpath" envpath = "#{envdir}#{File::PATH_SEPARATOR}/some/other/envdir" @settings[:environmentpath] = envpath Dir.mkdir(envdir) Puppet.features.stubs(:root?).returns true @settings.stubs(:service_user_available?).returns false @settings.stubs(:service_group_available?).returns false catalog = @settings.to_catalog resource = catalog.resource('File', File.join(envdir, 'production')) expect(resource[:mode]).to eq('0750') expect(resource[:owner]).to be_nil expect(resource[:group]).to be_nil end it "handles a non-existent environmentpath" do catalog = @settings.to_catalog expect(catalog.resource_keys).to be_empty end it "handles a default environmentpath" do Dir.mkdir(default_path) catalog = @settings.to_catalog expect(catalog.resource_keys).to include(["File", "#{default_path}/production"]) end it "does not add if the path to the default directory environment exists as a symlink", :if => Puppet.features.manages_symlinks? do Dir.mkdir(default_path) Puppet::FileSystem.symlink("#{tmpenv}/nowhere", File.join(default_path, 'production')) catalog = @settings.to_catalog expect(catalog.resource_keys).to_not include(["File", "#{default_path}/production"]) end end describe "when adding users and groups to the catalog" do before :all do # when this spec is run in isolation to build a settings catalog # it will not be able to autorequire and load types for the first time # on Windows with microsoft_windows? stubbed to false, because # Puppet::Util.path_to_uri is called to generate a URI to load code # and it manipulates the path based on OS # so instead we forcefully "prime" the cached types Puppet::Type.type(:user).new(:name => 'foo') Puppet::Type.type(:group).new(:name => 'bar') Puppet::Type.type(:file).new(:name => Dir.pwd) # appropriate for OS end before do Puppet.features.stubs(:root?).returns true # stubbed to false, as Windows catalogs don't add users / groups Puppet.features.stubs(:microsoft_windows?).returns false @settings.define_settings :foo, :mkusers => { :type => :boolean, :default => true, :desc => "e" }, :user => { :default => "suser", :desc => "doc" }, :group => { :default => "sgroup", :desc => "doc" } @settings.define_settings :other, :otherdir => {:type => :directory, :default => "/otherdir", :desc => "a", :owner => "service", :group => "service"} @catalog = @settings.to_catalog end it "should add each specified user and group to the catalog if :mkusers is a valid setting, is enabled, and we're running as root" do expect(@catalog.resource(:user, "suser")).to be_instance_of(Puppet::Resource) expect(@catalog.resource(:group, "sgroup")).to be_instance_of(Puppet::Resource) end it "should only add users and groups to the catalog from specified sections" do @settings.define_settings :yay, :yaydir => { :type => :directory, :default => "/yaydir", :desc => "a", :owner => "service", :group => "service"} catalog = @settings.to_catalog(:other) expect(catalog.resource(:user, "jane")).to be_nil expect(catalog.resource(:group, "billy")).to be_nil end it "should not add users or groups to the catalog if :mkusers not running as root" do Puppet.features.stubs(:root?).returns false catalog = @settings.to_catalog expect(catalog.resource(:user, "suser")).to be_nil expect(catalog.resource(:group, "sgroup")).to be_nil end it "should not add users or groups to the catalog if :mkusers is not a valid setting" do Puppet.features.stubs(:root?).returns true settings = Puppet::Settings.new settings.define_settings :other, :otherdir => {:type => :directory, :default => "/otherdir", :desc => "a", :owner => "service", :group => "service"} catalog = settings.to_catalog expect(catalog.resource(:user, "suser")).to be_nil expect(catalog.resource(:group, "sgroup")).to be_nil end it "should not add users or groups to the catalog if :mkusers is a valid setting but is disabled" do @settings[:mkusers] = false catalog = @settings.to_catalog expect(catalog.resource(:user, "suser")).to be_nil expect(catalog.resource(:group, "sgroup")).to be_nil end it "should not try to add users or groups to the catalog twice" do @settings.define_settings :yay, :yaydir => {:type => :directory, :default => "/yaydir", :desc => "a", :owner => "service", :group => "service"} # This would fail if users/groups were added twice expect { @settings.to_catalog }.not_to raise_error end it "should set :ensure to :present on each created user and group" do expect(@catalog.resource(:user, "suser")[:ensure]).to eq(:present) expect(@catalog.resource(:group, "sgroup")[:ensure]).to eq(:present) end it "should set each created user's :gid to the service group" do expect(@settings.to_catalog.resource(:user, "suser")[:gid]).to eq("sgroup") end it "should not attempt to manage the root user" do Puppet.features.stubs(:root?).returns true @settings.define_settings :foo, :foodir => {:type => :directory, :default => "/foodir", :desc => "a", :owner => "root", :group => "service"} expect(@settings.to_catalog.resource(:user, "root")).to be_nil end end end it "should be able to be converted to a manifest" do expect(Puppet::Settings.new).to respond_to(:to_manifest) end describe "when being converted to a manifest" do it "should produce a string with the code for each resource joined by two carriage returns" do @settings = Puppet::Settings.new @settings.define_settings :main, :maindir => { :type => :directory, :default => "/maindir", :desc => "a"}, :seconddir => { :type => :directory, :default => "/seconddir", :desc => "a"} main = stub 'main_resource', :ref => "File[/maindir]" main.expects(:to_manifest).returns "maindir" main.expects(:'[]').with(:alias).returns nil second = stub 'second_resource', :ref => "File[/seconddir]" second.expects(:to_manifest).returns "seconddir" second.expects(:'[]').with(:alias).returns nil @settings.setting(:maindir).expects(:to_resource).returns main @settings.setting(:seconddir).expects(:to_resource).returns second expect(@settings.to_manifest.split("\n\n").sort).to eq(%w{maindir seconddir}) end end describe "when using sections of the configuration to manage the local host" do before do @settings = Puppet::Settings.new @settings.stubs(:service_user_available?).returns true @settings.stubs(:service_group_available?).returns true @settings.define_settings :main, :noop => { :default => false, :desc => "", :type => :boolean } @settings.define_settings :main, :maindir => { :type => :directory, :default => make_absolute("/maindir"), :desc => "a" }, :seconddir => { :type => :directory, :default => make_absolute("/seconddir"), :desc => "a"} @settings.define_settings :main, :user => { :default => "suser", :desc => "doc" }, :group => { :default => "sgroup", :desc => "doc" } @settings.define_settings :other, :otherdir => {:type => :directory, :default => make_absolute("/otherdir"), :desc => "a", :owner => "service", :group => "service", :mode => '0755'} @settings.define_settings :third, :thirddir => { :type => :directory, :default => make_absolute("/thirddir"), :desc => "b"} @settings.define_settings :files, :myfile => {:type => :file, :default => make_absolute("/myfile"), :desc => "a", :mode => '0755'} end it "should create a catalog with the specified sections" do @settings.expects(:to_catalog).with(:main, :other).returns Puppet::Resource::Catalog.new("foo") @settings.use(:main, :other) end it "should canonicalize the sections" do @settings.expects(:to_catalog).with(:main, :other).returns Puppet::Resource::Catalog.new("foo") @settings.use("main", "other") end it "should ignore sections that have already been used" do @settings.expects(:to_catalog).with(:main).returns Puppet::Resource::Catalog.new("foo") @settings.use(:main) @settings.expects(:to_catalog).with(:other).returns Puppet::Resource::Catalog.new("foo") @settings.use(:main, :other) end it "should convert the created catalog to a RAL catalog" do @catalog = Puppet::Resource::Catalog.new("foo") @settings.expects(:to_catalog).with(:main).returns @catalog @catalog.expects(:to_ral).returns @catalog @settings.use(:main) end it "should specify that it is not managing a host catalog" do catalog = Puppet::Resource::Catalog.new("foo") catalog.expects(:apply) @settings.expects(:to_catalog).returns catalog catalog.stubs(:to_ral).returns catalog catalog.expects(:host_config=).with false @settings.use(:main) end it "should support a method for re-using all currently used sections" do @settings.expects(:to_catalog).with(:main, :third).times(2).returns Puppet::Resource::Catalog.new("foo") @settings.use(:main, :third) @settings.reuse end it "should fail with an appropriate message if any resources fail" do @catalog = Puppet::Resource::Catalog.new("foo") @catalog.stubs(:to_ral).returns @catalog @settings.expects(:to_catalog).returns @catalog @trans = mock("transaction") @catalog.expects(:apply).yields(@trans) @trans.expects(:any_failed?).returns(true) resource = Puppet::Type.type(:notify).new(:title => 'failed') status = Puppet::Resource::Status.new(resource) event = Puppet::Transaction::Event.new( :name => 'failure', :status => 'failure', :message => 'My failure') status.add_event(event) report = Puppet::Transaction::Report.new('apply') report.add_resource_status(status) @trans.expects(:report).returns report @settings.expects(:raise).with(includes("My failure")) @settings.use(:whatever) end end describe "when dealing with printing configs" do before do @settings = Puppet::Settings.new #these are the magic default values @settings.stubs(:value).with(:configprint).returns("") @settings.stubs(:value).with(:genconfig).returns(false) @settings.stubs(:value).with(:genmanifest).returns(false) @settings.stubs(:value).with(:environment).returns(nil) end describe "when checking print_config?" do it "should return false when the :configprint, :genconfig and :genmanifest are not set" do expect(@settings.print_configs?).to be_falsey end it "should return true when :configprint has a value" do @settings.stubs(:value).with(:configprint).returns("something") expect(@settings.print_configs?).to be_truthy end it "should return true when :genconfig has a value" do @settings.stubs(:value).with(:genconfig).returns(true) expect(@settings.print_configs?).to be_truthy end it "should return true when :genmanifest has a value" do @settings.stubs(:value).with(:genmanifest).returns(true) expect(@settings.print_configs?).to be_truthy end end describe "when printing configs" do describe "when :configprint has a value" do it "should call print_config_options" do @settings.stubs(:value).with(:configprint).returns("something") @settings.expects(:print_config_options) @settings.print_configs end it "should get the value of the option using the environment" do @settings.stubs(:value).with(:configprint).returns("something") @settings.stubs(:include?).with("something").returns(true) @settings.expects(:value).with(:environment).returns("env") @settings.expects(:value).with("something", "env").returns("foo") @settings.stubs(:puts).with("foo") @settings.print_configs end it "should print the value of the option" do @settings.stubs(:value).with(:configprint).returns("something") @settings.stubs(:include?).with("something").returns(true) @settings.stubs(:value).with("something", nil).returns("foo") @settings.expects(:puts).with("foo") @settings.print_configs end it "should print the value pairs if there are multiple options" do @settings.stubs(:value).with(:configprint).returns("bar,baz") @settings.stubs(:include?).with("bar").returns(true) @settings.stubs(:include?).with("baz").returns(true) @settings.stubs(:value).with("bar", nil).returns("foo") @settings.stubs(:value).with("baz", nil).returns("fud") @settings.expects(:puts).with("bar = foo") @settings.expects(:puts).with("baz = fud") @settings.print_configs end it "should return true after printing" do @settings.stubs(:value).with(:configprint).returns("something") @settings.stubs(:include?).with("something").returns(true) @settings.stubs(:value).with("something", nil).returns("foo") @settings.stubs(:puts).with("foo") expect(@settings.print_configs).to be_truthy end it "should return false if a config param is not found" do @settings.stubs :puts @settings.stubs(:value).with(:configprint).returns("something") @settings.stubs(:include?).with("something").returns(false) expect(@settings.print_configs).to be_falsey end end describe "when genconfig is true" do before do @settings.stubs :puts end it "should call to_config" do @settings.stubs(:value).with(:genconfig).returns(true) @settings.expects(:to_config) @settings.print_configs end it "should return true from print_configs" do @settings.stubs(:value).with(:genconfig).returns(true) @settings.stubs(:to_config) expect(@settings.print_configs).to be_truthy end end describe "when genmanifest is true" do before do @settings.stubs :puts end it "should call to_config" do @settings.stubs(:value).with(:genmanifest).returns(true) @settings.expects(:to_manifest) @settings.print_configs end it "should return true from print_configs" do @settings.stubs(:value).with(:genmanifest).returns(true) @settings.stubs(:to_manifest) expect(@settings.print_configs).to be_truthy end end end end describe "when determining if the service user is available" do let(:settings) do settings = Puppet::Settings.new settings.define_settings :main, :user => { :default => nil, :desc => "doc" } settings end def a_user_type_for(username) user = mock 'user' Puppet::Type.type(:user).expects(:new).with { |args| args[:name] == username }.returns user user end it "should return false if there is no user setting" do expect(settings).not_to be_service_user_available end it "should return false if the user provider says the user is missing" do settings[:user] = "foo" a_user_type_for("foo").expects(:exists?).returns false expect(settings).not_to be_service_user_available end it "should return true if the user provider says the user is present" do settings[:user] = "foo" a_user_type_for("foo").expects(:exists?).returns true expect(settings).to be_service_user_available end it "caches the result of determining if the user is present" do settings[:user] = "foo" a_user_type_for("foo").expects(:exists?).returns true expect(settings).to be_service_user_available expect(settings).to be_service_user_available end end describe "when determining if the service group is available" do let(:settings) do settings = Puppet::Settings.new settings.define_settings :main, :group => { :default => nil, :desc => "doc" } settings end def a_group_type_for(groupname) group = mock 'group' Puppet::Type.type(:group).expects(:new).with { |args| args[:name] == groupname }.returns group group end it "should return false if there is no group setting" do expect(settings).not_to be_service_group_available end it "should return false if the group provider says the group is missing" do settings[:group] = "foo" a_group_type_for("foo").expects(:exists?).returns false expect(settings).not_to be_service_group_available end it "should return true if the group provider says the group is present" do settings[:group] = "foo" a_group_type_for("foo").expects(:exists?).returns true expect(settings).to be_service_group_available end it "caches the result of determining if the group is present" do settings[:group] = "foo" a_group_type_for("foo").expects(:exists?).returns true expect(settings).to be_service_group_available expect(settings).to be_service_group_available end end describe "when dealing with command-line options" do let(:settings) { Puppet::Settings.new } it "should get options from Puppet.settings.optparse_addargs" do settings.expects(:optparse_addargs).returns([]) settings.send(:parse_global_options, []) end it "should add options to OptionParser" do settings.stubs(:optparse_addargs).returns( [["--option","-o", "Funny Option", :NONE]]) settings.expects(:handlearg).with("--option", true) settings.send(:parse_global_options, ["--option"]) end it "should not die if it sees an unrecognized option, because the app/face may handle it later" do expect { settings.send(:parse_global_options, ["--topuppet", "value"]) } .to_not raise_error end it "should not pass an unrecognized option to handleargs" do settings.expects(:handlearg).with("--topuppet", "value").never expect { settings.send(:parse_global_options, ["--topuppet", "value"]) } .to_not raise_error end it "should pass valid puppet settings options to handlearg even if they appear after an unrecognized option" do settings.stubs(:optparse_addargs).returns( [["--option","-o", "Funny Option", :NONE]]) settings.expects(:handlearg).with("--option", true) settings.send(:parse_global_options, ["--invalidoption", "--option"]) end it "should transform boolean option to normal form" do expect(Puppet::Settings.clean_opt("--[no-]option", true)).to eq(["--option", true]) end it "should transform boolean option to no- form" do expect(Puppet::Settings.clean_opt("--[no-]option", false)).to eq(["--no-option", false]) end it "should set preferred run mode from --run_mode string without error" do args = ["--run_mode", "master"] settings.expects(:handlearg).with("--run_mode", "master").never expect { settings.send(:parse_global_options, args) } .to_not raise_error expect(Puppet.settings.preferred_run_mode).to eq(:master) expect(args.empty?).to eq(true) end it "should set preferred run mode from --run_mode= string without error" do args = ["--run_mode=master"] settings.expects(:handlearg).with("--run_mode", "master").never expect { settings.send(:parse_global_options, args) } .to_not raise_error expect(Puppet.settings.preferred_run_mode).to eq(:master) expect(args.empty?).to eq(true) end end describe "default_certname" do describe "using hostname and domain" do before :each do Puppet::Settings.stubs(:hostname_fact).returns("testhostname") Puppet::Settings.stubs(:domain_fact).returns("domain.test.") end it "should use both to generate fqdn" do expect(Puppet::Settings.default_certname).to match(/testhostname\.domain\.test/) end it "should remove trailing dots from fqdn" do expect(Puppet::Settings.default_certname).to eq('testhostname.domain.test') end end describe "using just hostname" do before :each do Puppet::Settings.stubs(:hostname_fact).returns("testhostname") Puppet::Settings.stubs(:domain_fact).returns("") end it "should use only hostname to generate fqdn" do expect(Puppet::Settings.default_certname).to eq("testhostname") end it "should removing trailing dots from fqdn" do expect(Puppet::Settings.default_certname).to eq("testhostname") end end end end puppet-5.5.10/spec/unit/task_spec.rb0000644005276200011600000001133513417161722017230 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/files' require 'puppet_spec/modules' require 'puppet/module/task' describe Puppet::Module::Task do include PuppetSpec::Files let(:modpath) { tmpdir('task_modpath') } let(:mymodpath) { File.join(modpath, 'mymod') } let(:mymod) { Puppet::Module.new('mymod', mymodpath, nil) } let(:tasks_path) { File.join(mymodpath, 'tasks') } let(:tasks_glob) { File.join(mymodpath, 'tasks', '*') } it "cannot construct tasks with illegal names" do expect { Puppet::Module::Task.new(mymod, "iLegal", []) } .to raise_error(Puppet::Module::Task::InvalidName, "Task names must start with a lowercase letter and be composed of only lowercase letters, numbers, and underscores") end it "cannot construct tasks whose files are outside of their module's tasks directory" do outside_file = "/var/root/secret_tasks/classified" expect { Puppet::Module::Task.new(mymod, "fail", [outside_file]) } .to raise_error(Puppet::Module::Task::InvalidFile, "The file '#{outside_file}' is not located in the mymod module's tasks directory") end it "constructs tasks as expected when every task has a metadata file with the same name (besides extension)" do Dir.expects(:glob).with(tasks_glob).returns(%w{task1.json task1 task2.json task2.exe task3.json task3.sh}) tasks = Puppet::Module::Task.tasks_in_module(mymod) expect(tasks.count).to eq(3) expect(tasks.map{|t| t.name}).to eq(%w{mymod::task1 mymod::task2 mymod::task3}) expect(tasks.map{|t| t.metadata_file}).to eq(["#{tasks_path}/task1.json", "#{tasks_path}/task2.json", "#{tasks_path}/task3.json"]) expect(tasks.map{|t| t.files}).to eq([["#{tasks_path}/task1"], ["#{tasks_path}/task2.exe"], ["#{tasks_path}/task3.sh"]]) tasks.map{|t| t.metadata_file}.each do |metadata_file| expect(metadata_file).to eq(File.absolute_path(metadata_file)) end tasks.map{|t| t.files}.each do |task_files| task_file = task_files[0] expect(task_file).to eq(File.absolute_path(task_file)) end end it "constructs tasks as expected when some tasks don't have a metadata file" do Dir.expects(:glob).with(tasks_glob).returns(%w{task1 task2.exe task3.json task3.sh}) tasks = Puppet::Module::Task.tasks_in_module(mymod) expect(tasks.count).to eq(3) expect(tasks.map{|t| t.name}).to eq(%w{mymod::task1 mymod::task2 mymod::task3}) expect(tasks.map{|t| t.metadata_file}).to eq([nil, nil, "#{tasks_path}/task3.json"]) expect(tasks.map{|t| t.files}).to eq([["#{tasks_path}/task1"], ["#{tasks_path}/task2.exe"], ["#{tasks_path}/task3.sh"]]) end it "constructs tasks as expected when a task has multiple executable files" do Dir.expects(:glob).with(tasks_glob).returns(%w{task1.elf task1.exe task1.json task2.ps1 task2.sh}) tasks = Puppet::Module::Task.tasks_in_module(mymod) expect(tasks.count).to eq(2) expect(tasks.map{|t| t.name}).to eq(%w{mymod::task1 mymod::task2}) expect(tasks.map{|t| t.metadata_file}).to eq(["#{tasks_path}/task1.json", nil]) expect(tasks.map{|t| t.files}).to eq([["#{tasks_path}/task1.elf", "#{tasks_path}/task1.exe"], ["#{tasks_path}/task2.ps1", "#{tasks_path}/task2.sh"]]) end it "finds files whose names (besides extensions) are valid task names" do Dir.expects(:glob).with(tasks_glob).returns(%w{task task_1 xx_t_a_s_k_2_xx}) tasks = Puppet::Module::Task.tasks_in_module(mymod) expect(tasks.count).to eq(3) expect(tasks.map{|t| t.name}).to eq(%w{mymod::task mymod::task_1 mymod::xx_t_a_s_k_2_xx}) end it "ignores files that have names (besides extensions) that are not valid task names" do Dir.expects(:glob).with(tasks_glob).returns(%w{.nottask.exe .wat !runme _task 2task2furious def_a_task_PSYCH Fake_task not-a-task realtask}) tasks = Puppet::Module::Task.tasks_in_module(mymod) expect(tasks.count).to eq(1) expect(tasks.map{|t| t.name}).to eq(%w{mymod::realtask}) end it "ignores files that have names ending in .conf and .md" do Dir.expects(:glob).with(tasks_glob).returns(%w{ginuwine_task task.conf readme.md other_task.md}) tasks = Puppet::Module::Task.tasks_in_module(mymod) expect(tasks.count).to eq(1) expect(tasks.map{|t| t.name}).to eq(%w{mymod::ginuwine_task}) end it "gives the 'init' task a name that is just the module's name" do expect(Puppet::Module::Task.new(mymod, 'init', ["#{tasks_path}/init.sh"]).name).to eq('mymod') end end puppet-5.5.10/spec/unit/transaction_spec.rb0000644005276200011600000010275713417161722020624 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'matchers/include_in_order' require 'puppet_spec/compiler' require 'puppet/transaction' require 'puppet/type/notify' require 'fileutils' describe Puppet::Transaction do include PuppetSpec::Files include PuppetSpec::Compiler def catalog_with_resource(resource) catalog = Puppet::Resource::Catalog.new catalog.add_resource(resource) catalog end def transaction_with_resource(resource) transaction = Puppet::Transaction.new(catalog_with_resource(resource), nil, Puppet::Graph::RandomPrioritizer.new) transaction end before do @basepath = make_absolute("/what/ever") @transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new, nil, Puppet::Graph::RandomPrioritizer.new) end it "should be able to look resource status up by resource reference" do resource = Puppet::Type.type(:notify).new :title => "foobar" transaction = transaction_with_resource(resource) transaction.evaluate expect(transaction.resource_status(resource.to_s)).to be_changed end # This will basically only ever be used during testing. it "should automatically create resource statuses if asked for a non-existent status" do resource = Puppet::Type.type(:notify).new :title => "foobar" transaction = transaction_with_resource(resource) expect(transaction.resource_status(resource)).to be_instance_of(Puppet::Resource::Status) end it "should add provided resource statuses to its report" do resource = Puppet::Type.type(:notify).new :title => "foobar" transaction = transaction_with_resource(resource) transaction.evaluate status = transaction.resource_status(resource) expect(transaction.report.resource_statuses[resource.to_s]).to equal(status) end it "should not consider there to be failed or failed_to_restart resources if no statuses are marked failed" do resource = Puppet::Type.type(:notify).new :title => "foobar" transaction = transaction_with_resource(resource) transaction.evaluate expect(transaction).not_to be_any_failed end it "should use the provided report object" do report = Puppet::Transaction::Report.new transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new, report, nil) expect(transaction.report).to eq(report) end it "should create a report if none is provided" do transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new, nil, nil) expect(transaction.report).to be_kind_of Puppet::Transaction::Report end describe "when initializing" do it "should create an event manager" do transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new, nil, nil) expect(transaction.event_manager).to be_instance_of(Puppet::Transaction::EventManager) expect(transaction.event_manager.transaction).to equal(transaction) end it "should create a resource harness" do transaction = Puppet::Transaction.new(Puppet::Resource::Catalog.new, nil, nil) expect(transaction.resource_harness).to be_instance_of(Puppet::Transaction::ResourceHarness) expect(transaction.resource_harness.transaction).to equal(transaction) end it "should set retrieval time on the report" do catalog = Puppet::Resource::Catalog.new report = Puppet::Transaction::Report.new catalog.retrieval_duration = 5 report.expects(:add_times).with(:config_retrieval, 5) Puppet::Transaction.new(catalog, report, nil) end end describe "when evaluating a resource" do let(:resource) { Puppet::Type.type(:file).new :path => @basepath } it "should process events" do transaction = transaction_with_resource(resource) transaction.expects(:skip?).with(resource).returns false transaction.event_manager.expects(:process_events).with(resource) transaction.evaluate end describe "and the resource should be skipped" do it "should mark the resource's status as skipped" do transaction = transaction_with_resource(resource) transaction.expects(:skip?).with(resource).returns true transaction.evaluate expect(transaction.resource_status(resource)).to be_skipped end it "does not process any scheduled events" do transaction = transaction_with_resource(resource) transaction.expects(:skip?).with(resource).returns true transaction.event_manager.expects(:process_events).with(resource).never transaction.evaluate end it "dequeues all events scheduled on that resource" do transaction = transaction_with_resource(resource) transaction.expects(:skip?).with(resource).returns true transaction.event_manager.expects(:dequeue_all_events_for_resource).with(resource) transaction.evaluate end end end describe "when evaluating a skipped resource for corrective change it" do before :each do # Enable persistence during tests Puppet::Transaction::Persistence.any_instance.stubs(:enabled?).returns(true) end it "should persist in the transactionstore" do Puppet[:transactionstorefile] = tmpfile('persistence_test') resource = Puppet::Type.type(:notify).new :title => "foobar" transaction = transaction_with_resource(resource) transaction.evaluate expect(transaction.resource_status(resource)).to be_changed transaction = transaction_with_resource(resource) transaction.expects(:skip?).with(resource).returns true transaction.event_manager.expects(:process_events).with(resource).never transaction.evaluate expect(transaction.resource_status(resource)).to be_skipped persistence = Puppet::Transaction::Persistence.new persistence.load expect(persistence.get_system_value(resource.ref, "message")).to eq(["foobar"]) end end describe "when applying a resource" do before do @catalog = Puppet::Resource::Catalog.new @resource = Puppet::Type.type(:file).new :path => @basepath @catalog.add_resource(@resource) @status = Puppet::Resource::Status.new(@resource) @transaction = Puppet::Transaction.new(@catalog, nil, Puppet::Graph::RandomPrioritizer.new) @transaction.event_manager.stubs(:queue_events) end it "should use its resource harness to apply the resource" do @transaction.resource_harness.expects(:evaluate).with(@resource) @transaction.evaluate end it "should add the resulting resource status to its status list" do @transaction.resource_harness.stubs(:evaluate).returns(@status) @transaction.evaluate expect(@transaction.resource_status(@resource)).to be_instance_of(Puppet::Resource::Status) end it "should queue any events added to the resource status" do @transaction.resource_harness.stubs(:evaluate).returns(@status) @status.expects(:events).returns %w{a b} @transaction.event_manager.expects(:queue_events).with(@resource, ["a", "b"]) @transaction.evaluate end it "should log and skip any resources that cannot be applied" do @resource.expects(:properties).raises ArgumentError @transaction.evaluate expect(@transaction.report.resource_statuses[@resource.to_s]).to be_failed end it "should report any_failed if any resources failed" do @resource.expects(:properties).raises ArgumentError @transaction.evaluate expect(@transaction).to be_any_failed end it "should report any_failed if any resources failed to restart" do @transaction.evaluate @transaction.report.resource_statuses[@resource.to_s].failed_to_restart = true expect(@transaction).to be_any_failed end end describe "#unblock" do let(:graph) { @transaction.relationship_graph } let(:resource) { Puppet::Type.type(:notify).new(:name => 'foo') } it "should calculate the number of blockers if it's not known" do graph.add_vertex(resource) 3.times do |i| other = Puppet::Type.type(:notify).new(:name => i.to_s) graph.add_vertex(other) graph.add_edge(other, resource) end graph.unblock(resource) expect(graph.blockers[resource]).to eq(2) end it "should decrement the number of blockers if there are any" do graph.blockers[resource] = 40 graph.unblock(resource) expect(graph.blockers[resource]).to eq(39) end it "should warn if there are no blockers" do vertex = stub('vertex') vertex.expects(:warning).with "appears to have a negative number of dependencies" graph.blockers[vertex] = 0 graph.unblock(vertex) end it "should return true if the resource is now unblocked" do graph.blockers[resource] = 1 expect(graph.unblock(resource)).to eq(true) end it "should return false if the resource is still blocked" do graph.blockers[resource] = 2 expect(graph.unblock(resource)).to eq(false) end end describe "when traversing" do let(:path) { tmpdir('eval_generate') } let(:resource) { Puppet::Type.type(:file).new(:path => path, :recurse => true) } before :each do @transaction.catalog.add_resource(resource) end it "should yield the resource even if eval_generate is called" do Puppet::Transaction::AdditionalResourceGenerator.any_instance.expects(:eval_generate).with(resource).returns true yielded = false @transaction.evaluate do |res| yielded = true if res == resource end expect(yielded).to eq(true) end it "should prefetch the provider if necessary" do @transaction.expects(:prefetch_if_necessary).with(resource) @transaction.evaluate {} end it "traverses independent resources before dependent resources" do dependent = Puppet::Type.type(:notify).new(:name => "hello", :require => resource) @transaction.catalog.add_resource(dependent) seen = [] @transaction.evaluate do |res| seen << res end expect(seen).to include_in_order(resource, dependent) end it "traverses completely independent resources in the order they appear in the catalog" do independent = Puppet::Type.type(:notify).new(:name => "hello", :require => resource) @transaction.catalog.add_resource(independent) seen = [] @transaction.evaluate do |res| seen << res end expect(seen).to include_in_order(resource, independent) end it "should fail unsuitable resources and go on if it gets blocked" do dependent = Puppet::Type.type(:notify).new(:name => "hello", :require => resource) @transaction.catalog.add_resource(dependent) resource.stubs(:suitable?).returns false evaluated = [] @transaction.evaluate do |res| evaluated << res end # We should have gone on to evaluate the children expect(evaluated).to eq([dependent]) expect(@transaction.resource_status(resource)).to be_failed end end describe "when generating resources before traversal" do let(:catalog) { Puppet::Resource::Catalog.new } let(:transaction) { Puppet::Transaction.new(catalog, nil, Puppet::Graph::RandomPrioritizer.new) } let(:generator) { Puppet::Type.type(:notify).new :title => "generator" } let(:generated) do %w[a b c].map { |name| Puppet::Type.type(:notify).new(:name => name) } end before :each do catalog.add_resource generator generator.stubs(:generate).returns generated # avoid crude failures because of nil resources that result # from implicit containment and lacking containers catalog.stubs(:container_of).returns generator end it "should call 'generate' on all created resources" do generated.each { |res| res.expects(:generate) } transaction.evaluate end it "should finish all resources" do generated.each { |res| res.expects(:finish) } transaction.evaluate end it "should copy all tags to the newly generated resources" do generator.tag('one', 'two') transaction.evaluate generated.each do |res| expect(res).to be_tagged(*generator.tags) end end end describe "after resource traversal" do let(:catalog) { Puppet::Resource::Catalog.new } let(:prioritizer) { Puppet::Graph::RandomPrioritizer.new } let(:report) { Puppet::Transaction::Report.new } let(:transaction) { Puppet::Transaction.new(catalog, report, prioritizer) } let(:generator) { Puppet::Transaction::AdditionalResourceGenerator.new(catalog, nil, prioritizer) } before :each do generator = Puppet::Transaction::AdditionalResourceGenerator.new(catalog, nil, prioritizer) Puppet::Transaction::AdditionalResourceGenerator.stubs(:new).returns(generator) end it "should should query the generator for whether resources failed to generate" do relationship_graph = Puppet::Graph::RelationshipGraph.new(prioritizer) catalog.stubs(:relationship_graph).returns(relationship_graph) sequence = sequence(:traverse_first) relationship_graph.expects(:traverse).in_sequence(sequence) generator.expects(:resources_failed_to_generate).in_sequence(sequence) transaction.evaluate end it "should report that resources failed to generate" do generator.expects(:resources_failed_to_generate).returns(true) report.expects(:resources_failed_to_generate=).with(true) transaction.evaluate end it "should not report that resources failed to generate if none did" do generator.expects(:resources_failed_to_generate).returns(false) report.expects(:resources_failed_to_generate=).never transaction.evaluate end end describe "when performing pre-run checks" do let(:resource) { Puppet::Type.type(:notify).new(:title => "spec") } let(:transaction) { transaction_with_resource(resource) } let(:spec_exception) { 'spec-exception' } it "should invoke each resource's hook and apply the catalog after no failures" do resource.expects(:pre_run_check) transaction.evaluate end it "should abort the transaction on failure" do resource.expects(:pre_run_check).raises(Puppet::Error, spec_exception) expect { transaction.evaluate }.to raise_error(Puppet::Error, /Some pre-run checks failed/) end it "should log the resource-specific exception" do resource.expects(:pre_run_check).raises(Puppet::Error, spec_exception) resource.expects(:log_exception).with(responds_with(:message, spec_exception)) expect { transaction.evaluate }.to raise_error(Puppet::Error) end end describe "when skipping a resource" do before :each do @resource = Puppet::Type.type(:notify).new :name => "foo" @catalog = Puppet::Resource::Catalog.new @resource.catalog = @catalog @transaction = Puppet::Transaction.new(@catalog, nil, nil) end it "should skip resource with missing tags" do @transaction.stubs(:missing_tags?).returns(true) expect(@transaction).to be_skip(@resource) end it "should skip resources tagged with the skip tags" do @transaction.stubs(:skip_tags?).returns(true) expect(@transaction).to be_skip(@resource) end it "should skip unscheduled resources" do @transaction.stubs(:scheduled?).returns(false) expect(@transaction).to be_skip(@resource) end it "should skip resources with failed dependencies" do @transaction.stubs(:failed_dependencies?).returns(true) expect(@transaction).to be_skip(@resource) end it "should skip virtual resource" do @resource.stubs(:virtual?).returns true expect(@transaction).to be_skip(@resource) end it "should skip device only resouce on normal host" do @resource.stubs(:appliable_to_host?).returns false @resource.stubs(:appliable_to_device?).returns true @transaction.for_network_device = false expect(@transaction).to be_skip(@resource) end it "should not skip device only resouce on remote device" do @resource.stubs(:appliable_to_host?).returns false @resource.stubs(:appliable_to_device?).returns true @transaction.for_network_device = true expect(@transaction).not_to be_skip(@resource) end it "should skip host resouce on device" do @resource.stubs(:appliable_to_host?).returns true @resource.stubs(:appliable_to_device?).returns false @transaction.for_network_device = true expect(@transaction).to be_skip(@resource) end it "should not skip resouce available on both device and host when on device" do @resource.stubs(:appliable_to_host?).returns true @resource.stubs(:appliable_to_device?).returns true @transaction.for_network_device = true expect(@transaction).not_to be_skip(@resource) end it "should not skip resouce available on both device and host when on host" do @resource.stubs(:appliable_to_host?).returns true @resource.stubs(:appliable_to_device?).returns true @transaction.for_network_device = false expect(@transaction).not_to be_skip(@resource) end end describe "when determining if tags are missing" do before :each do @resource = Puppet::Type.type(:notify).new :name => "foo" @catalog = Puppet::Resource::Catalog.new @resource.catalog = @catalog @transaction = Puppet::Transaction.new(@catalog, nil, nil) @transaction.stubs(:ignore_tags?).returns false end it "should not be missing tags if tags are being ignored" do @transaction.expects(:ignore_tags?).returns true @resource.expects(:tagged?).never expect(@transaction).not_to be_missing_tags(@resource) end it "should not be missing tags if the transaction tags are empty" do @transaction.tags = [] @resource.expects(:tagged?).never expect(@transaction).not_to be_missing_tags(@resource) end it "should otherwise let the resource determine if it is missing tags" do tags = ['one', 'two'] @transaction.tags = tags expect(@transaction).to be_missing_tags(@resource) end end describe "when determining if a resource should be scheduled" do before :each do @resource = Puppet::Type.type(:notify).new :name => "foo" @catalog = Puppet::Resource::Catalog.new @catalog.add_resource(@resource) @transaction = Puppet::Transaction.new(@catalog, nil, Puppet::Graph::RandomPrioritizer.new) end it "should always schedule resources if 'ignoreschedules' is set" do @transaction.ignoreschedules = true @transaction.resource_harness.expects(:scheduled?).never @transaction.evaluate expect(@transaction.resource_status(@resource)).to be_changed end it "should let the resource harness determine whether the resource should be scheduled" do @transaction.resource_harness.expects(:scheduled?).with(@resource).returns "feh" @transaction.evaluate end end describe "when prefetching" do let(:catalog) { Puppet::Resource::Catalog.new } let(:transaction) { Puppet::Transaction.new(catalog, nil, nil) } let(:resource) { Puppet::Type.type(:sshkey).new :title => "foo", :name => "bar", :type => :dsa, :key => "eh", :provider => :parsed } let(:resource2) { Puppet::Type.type(:package).new :title => "blah", :provider => "apt" } before :each do catalog.add_resource resource catalog.add_resource resource2 end it "should match resources by name, not title" do resource.provider.class.expects(:prefetch).with("bar" => resource) transaction.prefetch_if_necessary(resource) end it "should not prefetch a provider which has already been prefetched" do transaction.prefetched_providers[:sshkey][:parsed] = true resource.provider.class.expects(:prefetch).never transaction.prefetch_if_necessary(resource) end it "should mark the provider prefetched" do resource.provider.class.stubs(:prefetch) transaction.prefetch_if_necessary(resource) expect(transaction.prefetched_providers[:sshkey][:parsed]).to be_truthy end it "should prefetch resources without a provider if prefetching the default provider" do other = Puppet::Type.type(:sshkey).new :name => "other" other.instance_variable_set(:@provider, nil) catalog.add_resource other resource.provider.class.expects(:prefetch).with('bar' => resource, 'other' => other) transaction.prefetch_if_necessary(resource) end it "should not prefetch a provider which has failed" do transaction.prefetch_failed_providers[:sshkey][:parsed] = true resource.provider.class.expects(:prefetch).never transaction.prefetch_if_necessary(resource) end describe "and prefetching fails" do before :each do resource.provider.class.expects(:prefetch).raises(Puppet::Error, "message") end context "without future_features flag" do before :each do Puppet.settings[:future_features] = false end it "should not rescue prefetch executions" do expect { transaction.prefetch_if_necessary(resource) }.to raise_error(Puppet::Error) end end context "with future_features flag" do before :each do Puppet.settings[:future_features] = true end it "should rescue prefetch executions" do transaction.prefetch_if_necessary(resource) expect(transaction.prefetched_providers[:sshkey][:parsed]).to be_truthy end it "should mark resources as failed" do transaction.evaluate expect(transaction.resource_status(resource).failed?).to be_truthy end it "should mark a provider that has failed prefetch" do transaction.prefetch_if_necessary(resource) expect(transaction.prefetch_failed_providers[:sshkey][:parsed]).to be_truthy end describe "and new resources are generated" do let(:generator) { Puppet::Type.type(:notify).new :title => "generator" } let(:generated) do %w[a b c].map { |name| Puppet::Type.type(:sshkey).new :title => "foo", :name => name, :type => :dsa, :key => "eh", :provider => :parsed } end before :each do catalog.add_resource generator generator.stubs(:generate).returns generated catalog.stubs(:container_of).returns generator end it "should not evaluate resources with a failed provider, even if the prefetch is rescued" do #Only the generator resource should be applied, all the other resources are failed, and skipped. catalog.remove_resource resource2 transaction.expects(:apply).once transaction.evaluate end it "should not fail other resources added after the failing resource" do new_resource = Puppet::Type.type(:notify).new :name => "baz" catalog.add_resource(new_resource) transaction.evaluate expect(transaction.resource_status(new_resource).failed?).to be_falsey end it "should fail other resources that require the failing resource" do new_resource = Puppet::Type.type(:notify).new(:name => "baz", :require => resource) catalog.add_resource(new_resource) catalog.remove_resource resource2 transaction.expects(:apply).once transaction.evaluate expect(transaction.resource_status(resource).failed?).to be_truthy expect(transaction.resource_status(new_resource).dependency_failed?).to be_truthy expect(transaction.skip?(new_resource)).to be_truthy end end end end end describe "during teardown" do let(:catalog) { Puppet::Resource::Catalog.new } let(:transaction) do Puppet::Transaction.new(catalog, nil, Puppet::Graph::RandomPrioritizer.new) end let(:teardown_type) do Puppet::Type.newtype(:teardown_test) do newparam(:name) {} end end before :each do teardown_type.provide(:teardown_provider) do class << self attr_reader :result def post_resource_eval @result = 'passed' end end end end it "should call ::post_resource_eval on provider classes that support it" do resource = teardown_type.new(:title => "foo", :provider => :teardown_provider) transaction = transaction_with_resource(resource) transaction.evaluate expect(resource.provider.class.result).to eq('passed') end it "should call ::post_resource_eval even if other providers' ::post_resource_eval fails" do teardown_type.provide(:always_fails) do class << self attr_reader :result def post_resource_eval @result = 'failed' raise Puppet::Error, "This provider always fails" end end end good_resource = teardown_type.new(:title => "bloo", :provider => :teardown_provider) bad_resource = teardown_type.new(:title => "blob", :provider => :always_fails) catalog.add_resource(bad_resource) catalog.add_resource(good_resource) transaction.evaluate expect(good_resource.provider.class.result).to eq('passed') expect(bad_resource.provider.class.result).to eq('failed') end it "should call ::post_resource_eval even if one of the resources fails" do resource = teardown_type.new(:title => "foo", :provider => :teardown_provider) resource.stubs(:retrieve_resource).raises catalog.add_resource resource resource.provider.class.expects(:post_resource_eval) transaction.evaluate end end describe 'when checking application run state' do before do @catalog = Puppet::Resource::Catalog.new @transaction = Puppet::Transaction.new(@catalog, nil, Puppet::Graph::RandomPrioritizer.new) end context "when stop is requested" do before :each do Puppet::Application.stubs(:stop_requested?).returns(true) end it 'should return true for :stop_processing?' do expect(@transaction).to be_stop_processing end it 'always evaluates non-host_config catalogs' do @catalog.host_config = false expect(@transaction).not_to be_stop_processing end end it 'should return false for :stop_processing? if Puppet::Application.stop_requested? is false' do Puppet::Application.stubs(:stop_requested?).returns(false) expect(@transaction.stop_processing?).to be_falsey end describe 'within an evaluate call' do before do @resource = Puppet::Type.type(:notify).new :title => "foobar" @catalog.add_resource @resource @transaction.stubs(:add_dynamically_generated_resources) end it 'should stop processing if :stop_processing? is true' do @transaction.stubs(:stop_processing?).returns(true) @transaction.expects(:eval_resource).never @transaction.evaluate end it 'should continue processing if :stop_processing? is false' do @transaction.stubs(:stop_processing?).returns(false) @transaction.expects(:eval_resource).returns(nil) @transaction.evaluate end end end it "errors with a dependency cycle for a resource that requires itself" do Puppet.expects(:err).with(regexp_matches(/Found 1 dependency cycle:.*\(Notify\[cycle\] => Notify\[cycle\]\)/m)) expect do apply_compiled_manifest(<<-MANIFEST) notify { cycle: require => Notify[cycle] } MANIFEST end.to raise_error(Puppet::Error, 'One or more resource dependency cycles detected in graph') end it "errors with a dependency cycle for a self-requiring resource also required by another resource" do Puppet.expects(:err).with(regexp_matches(/Found 1 dependency cycle:.*\(Notify\[cycle\] => Notify\[cycle\]\)/m)) expect do apply_compiled_manifest(<<-MANIFEST) notify { cycle: require => Notify[cycle] } notify { other: require => Notify[cycle] } MANIFEST end.to raise_error(Puppet::Error, 'One or more resource dependency cycles detected in graph') end it "errors with a dependency cycle for a resource that requires itself and another resource" do Puppet.expects(:err).with(regexp_matches(/Found 1 dependency cycle:.*\(Notify\[cycle\] => Notify\[cycle\]\)/m)) expect do apply_compiled_manifest(<<-MANIFEST) notify { cycle: require => [Notify[other], Notify[cycle]] } notify { other: } MANIFEST end.to raise_error(Puppet::Error, 'One or more resource dependency cycles detected in graph') end it "errors with a dependency cycle for a resource that is later modified to require itself" do Puppet.expects(:err).with(regexp_matches(/Found 1 dependency cycle:.*\(Notify\[cycle\] => Notify\[cycle\]\)/m)) expect do apply_compiled_manifest(<<-MANIFEST) notify { cycle: } Notify <| title == 'cycle' |> { require => Notify[cycle] } MANIFEST end.to raise_error(Puppet::Error, 'One or more resource dependency cycles detected in graph') end context "when generating a report for a transaction with a dependency cycle" do let(:catalog) do compile_to_ral(<<-MANIFEST) notify { foo: require => Notify[bar] } notify { bar: require => Notify[foo] } MANIFEST end let(:prioritizer) { Puppet::Graph::SequentialPrioritizer.new } let(:transaction) { Puppet::Transaction.new(catalog, Puppet::Transaction::Report.new("apply"), prioritizer) } before(:each) do expect { transaction.evaluate }.to raise_error(Puppet::Error) transaction.report.finalize_report end it "should report resources involved in a dependency cycle as failed" do expect(transaction.report.resource_statuses['Notify[foo]']).to be_failed expect(transaction.report.resource_statuses['Notify[bar]']).to be_failed end it "should generate a failure event for a resource in a dependency cycle" do status = transaction.report.resource_statuses['Notify[foo]'] expect(status.events.first.status).to eq('failure') expect(status.events.first.message).to eq('resource is part of a dependency cycle') end it "should report that the transaction is failed" do expect(transaction.report.status).to eq('failed') end end it "reports a changed resource with a successful run" do transaction = apply_compiled_manifest("notify { one: }") expect(transaction.report.status).to eq('changed') expect(transaction.report.resource_statuses['Notify[one]']).to be_changed end describe "when interrupted" do it "marks unprocessed resources as skipped" do Puppet::Application.stop! transaction = apply_compiled_manifest(<<-MANIFEST) notify { a: } -> notify { b: } MANIFEST expect(transaction.report.resource_statuses['Notify[a]']).to be_skipped expect(transaction.report.resource_statuses['Notify[b]']).to be_skipped end end describe "failed dependency is depended on multiple times" do it "notifies the failed dependency once" do command_string = File.expand_path('/my/command') Puppet::Util::Execution.stubs(:execute).with([command_string]).raises(Puppet::ExecutionFailure, "Failed") Puppet::Type::Notify.any_instance.expects(:send_log).with(:notice, "Dependency Exec[exec1] has failures: true") Puppet::Type::Notify.any_instance.expects(:send_log).with(:notice, "Dependency Exec[exec2] has failures: true") Puppet::Type::Notify.any_instance.expects(:send_log).with(:notice, "Dependency Exec[exec3] has failures: true") Puppet::Type::Notify.any_instance.expects(:send_log).with(:notice, "Dependency Exec[exec4] has failures: true") Puppet::Type::Notify.any_instance.expects(:send_log).with(:notice, "Dependency Exec[exec5] has failures: true") Puppet::Type::Notify.any_instance.expects(:send_log).with(:warning, "Skipping because of failed dependencies").times(3) apply_compiled_manifest(<<-MANIFEST) exec { ['exec1', 'exec2', 'exec3', 'exec4', 'exec5']: command => '#{command_string}' } -> notify { ['notify1', 'notify2', 'notify3']: } MANIFEST end end end describe Puppet::Transaction, " when determining tags" do before do @config = Puppet::Resource::Catalog.new @transaction = Puppet::Transaction.new(@config, nil, nil) end it "should default to the tags specified in the :tags setting" do Puppet[:tags] = "one" expect(@transaction).to be_tagged("one") end it "should split tags based on ','" do Puppet[:tags] = "one,two" expect(@transaction).to be_tagged("one") expect(@transaction).to be_tagged("two") end it "should use any tags set after creation" do Puppet[:tags] = "" @transaction.tags = %w{one two} expect(@transaction).to be_tagged("one") expect(@transaction).to be_tagged("two") end it "should always convert assigned tags to an array" do @transaction.tags = "one::two" expect(@transaction).to be_tagged("one::two") end it "should tag one::two only as 'one::two' and not 'one', 'two', and 'one::two'" do @transaction.tags = "one::two" expect(@transaction).to be_tagged("one::two") expect(@transaction).to_not be_tagged("one") expect(@transaction).to_not be_tagged("two") end it "should accept a comma-delimited string" do @transaction.tags = "one, two" expect(@transaction).to be_tagged("one") expect(@transaction).to be_tagged("two") end it "should accept an empty string" do @transaction.tags = "one, two" expect(@transaction).to be_tagged("one") @transaction.tags = "" expect(@transaction).not_to be_tagged("one") end end puppet-5.5.10/spec/unit/type_spec.rb0000644005276200011600000013576213417161722017262 0ustar jenkinsjenkins#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/compiler' describe Puppet::Type, :unless => Puppet.features.microsoft_windows? do include PuppetSpec::Files include PuppetSpec::Compiler it "should be Comparable" do a = Puppet::Type.type(:notify).new(:name => "a") b = Puppet::Type.type(:notify).new(:name => "b") c = Puppet::Type.type(:notify).new(:name => "c") [[a, b, c], [a, c, b], [b, a, c], [b, c, a], [c, a, b], [c, b, a]].each do |this| expect(this.sort).to eq([a, b, c]) end expect(a).to be < b expect(a).to be < c expect(b).to be > a expect(b).to be < c expect(c).to be > a expect(c).to be > b [a, b, c].each {|x| expect(a).to be <= x } [a, b, c].each {|x| expect(c).to be >= x } expect(b).to be_between(a, c) end it "should consider a parameter to be valid if it is a valid parameter" do expect(Puppet::Type.type(:mount)).to be_valid_parameter(:name) end it "should consider a parameter to be valid if it is a valid property" do expect(Puppet::Type.type(:mount)).to be_valid_parameter(:fstype) end it "should consider a parameter to be valid if it is a valid metaparam" do expect(Puppet::Type.type(:mount)).to be_valid_parameter(:noop) end it "should be able to retrieve a property by name" do resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present) expect(resource.property(:fstype)).to be_instance_of(Puppet::Type.type(:mount).attrclass(:fstype)) end it "should be able to retrieve a parameter by name" do resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present) expect(resource.parameter(:name)).to be_instance_of(Puppet::Type.type(:mount).attrclass(:name)) end it "should be able to retrieve a property by name using the :parameter method" do resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present) expect(resource.parameter(:fstype)).to be_instance_of(Puppet::Type.type(:mount).attrclass(:fstype)) end it "should be able to retrieve all set properties" do resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present) props = resource.properties expect(props).not_to be_include(nil) [:fstype, :ensure, :pass].each do |name| expect(props).to be_include(resource.parameter(name)) end end it "can retrieve all set parameters" do resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present, :tag => 'foo') params = resource.parameters_with_value [:name, :provider, :ensure, :fstype, :pass, :dump, :target, :loglevel, :tag].each do |name| expect(params).to be_include(resource.parameter(name)) end end it "can not return any `nil` values when retrieving all set parameters" do resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present, :tag => 'foo') params = resource.parameters_with_value expect(params).not_to be_include(nil) end it "can return an iterator for all set parameters" do resource = Puppet::Type.type(:notify).new(:name=>'foo',:message=>'bar',:tag=>'baz',:require=> "File['foo']") params = [:name, :message, :withpath, :loglevel, :tag, :require] resource.eachparameter { |param| expect(params).to be_include(param.to_s.to_sym) } end it "should have a method for setting default values for resources" do expect(Puppet::Type.type(:mount).new(:name => "foo")).to respond_to(:set_default) end it "should do nothing for attributes that have no defaults and no specified value" do expect(Puppet::Type.type(:mount).new(:name => "foo").parameter(:noop)).to be_nil end it "should have a method for adding tags" do expect(Puppet::Type.type(:mount).new(:name => "foo")).to respond_to(:tags) end it "should use the tagging module" do expect(Puppet::Type.type(:mount).ancestors).to be_include(Puppet::Util::Tagging) end it "should delegate to the tagging module when tags are added" do resource = Puppet::Type.type(:mount).new(:name => "foo") resource.stubs(:tag).with(:mount) resource.expects(:tag).with(:tag1, :tag2) resource.tags = [:tag1,:tag2] end it "should add the current type as tag" do resource = Puppet::Type.type(:mount).new(:name => "foo") resource.stubs(:tag) resource.expects(:tag).with(:mount) resource.tags = [:tag1,:tag2] end it "should have a method to know if the resource is exported" do expect(Puppet::Type.type(:mount).new(:name => "foo")).to respond_to(:exported?) end it "should have a method to know if the resource is virtual" do expect(Puppet::Type.type(:mount).new(:name => "foo")).to respond_to(:virtual?) end it "should consider its version to be zero if it has no catalog" do expect(Puppet::Type.type(:mount).new(:name => "foo").version).to eq(0) end it "reports the correct path even after path is used during setup of the type" do Puppet::Type.newtype(:testing) do newparam(:name) do isnamevar validate do |value| path # forces the computation of the path end end end ral = compile_to_ral(<<-MANIFEST) class something { testing { something: } } include something MANIFEST expect(ral.resource("Testing[something]").path).to eq("/Stage[main]/Something/Testing[something]") end context "alias metaparam" do it "creates a new name that can be used for resource references" do ral = compile_to_ral(<<-MANIFEST) notify { a: alias => c } MANIFEST expect(ral.resource("Notify[a]")).to eq(ral.resource("Notify[c]")) end end context "resource attributes" do let(:resource) { resource = Puppet::Type.type(:mount).new(:name => "foo") catalog = Puppet::Resource::Catalog.new catalog.version = 50 catalog.add_resource resource resource } it "should consider its version to be its catalog version" do expect(resource.version).to eq(50) end it "should have tags" do expect(resource).to be_tagged("mount") expect(resource).to be_tagged("foo") end it "should have a path" do expect(resource.path).to eq("/Mount[foo]") end end it "should consider its type to be the name of its class" do expect(Puppet::Type.type(:mount).new(:name => "foo").type).to eq(:mount) end it "should use any provided noop value" do expect(Puppet::Type.type(:mount).new(:name => "foo", :noop => true)).to be_noop end it "should use the global noop value if none is provided" do Puppet[:noop] = true expect(Puppet::Type.type(:mount).new(:name => "foo")).to be_noop end it "should not be noop if in a non-host_config catalog" do resource = Puppet::Type.type(:mount).new(:name => "foo") catalog = Puppet::Resource::Catalog.new catalog.add_resource resource expect(resource).not_to be_noop end describe "when creating an event" do before do @resource = Puppet::Type.type(:mount).new :name => "foo" end it "should have the resource's reference as the resource" do expect(@resource.event.resource).to eq("Mount[foo]") end it "should have the resource's log level as the default log level" do @resource[:loglevel] = :warning expect(@resource.event.default_log_level).to eq(:warning) end {:file => "/my/file", :line => 50}.each do |attr, value| it "should set the #{attr}" do @resource.stubs(attr).returns value expect(@resource.event.send(attr)).to eq(value) end end it "should set the tags" do @resource.tag("abc", "def") expect(@resource.event).to be_tagged("abc") expect(@resource.event).to be_tagged("def") end it "should allow specification of event attributes" do expect(@resource.event(:status => "noop").status).to eq("noop") end end describe "when creating a provider" do before :each do @type = Puppet::Type.newtype(:provider_test_type) do newparam(:name) { isnamevar } newparam(:foo) newproperty(:bar) end end after :each do @type.provider_hash.clear end describe "when determining if instances of the type are managed" do it "should not consider audit only resources to be managed" do expect(@type.new(:name => "foo", :audit => 'all').managed?).to be_falsey end it "should not consider resources with only parameters to be managed" do expect(@type.new(:name => "foo", :foo => 'did someone say food?').managed?).to be_falsey end it "should consider resources with any properties set to be managed" do expect(@type.new(:name => "foo", :bar => 'Let us all go there').managed?).to be_truthy end end it "should have documentation for the 'provider' parameter if there are providers" do @type.provide(:test_provider) expect(@type.paramdoc(:provider)).to match(/`provider_test_type`[\s]+resource/) end it "should not have documentation for the 'provider' parameter if there are no providers" do expect { @type.paramdoc(:provider) }.to raise_error(NoMethodError) end it "should create a subclass of Puppet::Provider for the provider" do provider = @type.provide(:test_provider) expect(provider.ancestors).to include(Puppet::Provider) end it "should use a parent class if specified" do parent_provider = @type.provide(:parent_provider) child_provider = @type.provide(:child_provider, :parent => parent_provider) expect(child_provider.ancestors).to include(parent_provider) end it "should use a parent class if specified by name" do parent_provider = @type.provide(:parent_provider) child_provider = @type.provide(:child_provider, :parent => :parent_provider) expect(child_provider.ancestors).to include(parent_provider) end it "should raise an error when the parent class can't be found" do expect { @type.provide(:child_provider, :parent => :parent_provider) }.to raise_error(Puppet::DevError, /Could not find parent provider.+parent_provider/) end it "should ensure its type has a 'provider' parameter" do @type.provide(:test_provider) expect(@type.parameters).to include(:provider) end it "should remove a previously registered provider with the same name" do old_provider = @type.provide(:test_provider) new_provider = @type.provide(:test_provider) expect(old_provider).not_to equal(new_provider) end it "should register itself as a provider for the type" do provider = @type.provide(:test_provider) expect(provider).to eq(@type.provider(:test_provider)) end it "should create a provider when a provider with the same name previously failed" do @type.provide(:test_provider) do raise "failed to create this provider" end rescue nil provider = @type.provide(:test_provider) expect(provider.ancestors).to include(Puppet::Provider) expect(provider).to eq(@type.provider(:test_provider)) end describe "with a parent class from another type" do before :each do @parent_type = Puppet::Type.newtype(:provider_parent_type) do newparam(:name) { isnamevar } end @parent_provider = @parent_type.provide(:parent_provider) end it "should be created successfully" do child_provider = @type.provide(:child_provider, :parent => @parent_provider) expect(child_provider.ancestors).to include(@parent_provider) end it "should be registered as a provider of the child type" do @type.provide(:child_provider, :parent => @parent_provider) expect(@type.providers).to include(:child_provider) expect(@parent_type.providers).not_to include(:child_provider) end end end describe "when choosing a default provider" do it "should choose the provider with the highest specificity" do # Make a fake type type = Puppet::Type.newtype(:defaultprovidertest) do newparam(:name) do end end basic = type.provide(:basic) {} greater = type.provide(:greater) {} basic.stubs(:specificity).returns 1 greater.stubs(:specificity).returns 2 expect(type.defaultprovider).to equal(greater) end end context "autorelations" do before :each do Puppet::Type.newtype(:autorelation_one) do newparam(:name) { isnamevar } end end describe "when building autorelations" do it "should be able to autorequire resources" do Puppet::Type.newtype(:autorelation_two) do newparam(:name) { isnamevar } autorequire(:autorelation_one) { ['foo'] } end relationship_graph = compile_to_relationship_graph(<<-MANIFEST) autorelation_one { 'foo': } autorelation_two { 'bar': } MANIFEST src = relationship_graph.vertices.select{ |x| x.ref.to_s == 'Autorelation_one[foo]' }.first dst = relationship_graph.vertices.select{ |x| x.ref.to_s == 'Autorelation_two[bar]' }.first expect(relationship_graph.edge?(src,dst)).to be_truthy expect(relationship_graph.edges_between(src,dst).first.event).to eq(:NONE) end it 'should not fail autorequire contains undef entries' do Puppet::Type.newtype(:autorelation_two) do newparam(:name) { isnamevar } autorequire(:autorelation_one) { [nil, 'foo'] } end relationship_graph = compile_to_relationship_graph(<<-MANIFEST) autorelation_one { 'foo': } autorelation_two { 'bar': } MANIFEST src = relationship_graph.vertices.select{ |x| x.ref.to_s == 'Autorelation_one[foo]' }.first dst = relationship_graph.vertices.select{ |x| x.ref.to_s == 'Autorelation_two[bar]' }.first expect(relationship_graph.edge?(src,dst)).to be_truthy expect(relationship_graph.edges_between(src,dst).first.event).to eq(:NONE) end it "should be able to autosubscribe resources" do Puppet::Type.newtype(:autorelation_two) do newparam(:name) { isnamevar } autosubscribe(:autorelation_one) { ['foo'] } end relationship_graph = compile_to_relationship_graph(<<-MANIFEST) autorelation_one { 'foo': } autorelation_two { 'bar': } MANIFEST src = relationship_graph.vertices.select{ |x| x.ref.to_s == 'Autorelation_one[foo]' }.first dst = relationship_graph.vertices.select{ |x| x.ref.to_s == 'Autorelation_two[bar]' }.first expect(relationship_graph.edge?(src,dst)).to be_truthy expect(relationship_graph.edges_between(src,dst).first.event).to eq(:ALL_EVENTS) end it 'should not fail if autosubscribe contains undef entries' do Puppet::Type.newtype(:autorelation_two) do newparam(:name) { isnamevar } autosubscribe(:autorelation_one) { [nil, 'foo'] } end relationship_graph = compile_to_relationship_graph(<<-MANIFEST) autorelation_one { 'foo': } autorelation_two { 'bar': } MANIFEST src = relationship_graph.vertices.select{ |x| x.ref.to_s == 'Autorelation_one[foo]' }.first dst = relationship_graph.vertices.select{ |x| x.ref.to_s == 'Autorelation_two[bar]' }.first expect(relationship_graph.edge?(src,dst)).to be_truthy expect(relationship_graph.edges_between(src,dst).first.event).to eq(:ALL_EVENTS) end it "should be able to autobefore resources" do Puppet::Type.newtype(:autorelation_two) do newparam(:name) { isnamevar } autobefore(:autorelation_one) { ['foo'] } end relationship_graph = compile_to_relationship_graph(<<-MANIFEST) autorelation_one { 'foo': } autorelation_two { 'bar': } MANIFEST src = relationship_graph.vertices.select{ |x| x.ref.to_s == 'Autorelation_two[bar]' }.first dst = relationship_graph.vertices.select{ |x| x.ref.to_s == 'Autorelation_one[foo]' }.first expect(relationship_graph.edge?(src,dst)).to be_truthy expect(relationship_graph.edges_between(src,dst).first.event).to eq(:NONE) end it "should not fail when autobefore contains undef entries" do Puppet::Type.newtype(:autorelation_two) do newparam(:name) { isnamevar } autobefore(:autorelation_one) { [nil, 'foo'] } end relationship_graph = compile_to_relationship_graph(<<-MANIFEST) autorelation_one { 'foo': } autorelation_two { 'bar': } MANIFEST src = relationship_graph.vertices.select{ |x| x.ref.to_s == 'Autorelation_two[bar]' }.first dst = relationship_graph.vertices.select{ |x| x.ref.to_s == 'Autorelation_one[foo]' }.first expect(relationship_graph.edge?(src,dst)).to be_truthy expect(relationship_graph.edges_between(src,dst).first.event).to eq(:NONE) end it "should be able to autonotify resources" do Puppet::Type.newtype(:autorelation_two) do newparam(:name) { isnamevar } autonotify(:autorelation_one) { ['foo'] } end relationship_graph = compile_to_relationship_graph(<<-MANIFEST) autorelation_one { 'foo': } autorelation_two { 'bar': } MANIFEST src = relationship_graph.vertices.select{ |x| x.ref.to_s == 'Autorelation_two[bar]' }.first dst = relationship_graph.vertices.select{ |x| x.ref.to_s == 'Autorelation_one[foo]' }.first expect(relationship_graph.edge?(src,dst)).to be_truthy expect(relationship_graph.edges_between(src,dst).first.event).to eq(:ALL_EVENTS) end it 'should not fail if autonotify contains undef entries' do Puppet::Type.newtype(:autorelation_two) do newparam(:name) { isnamevar } autonotify(:autorelation_one) { [nil, 'foo'] } end relationship_graph = compile_to_relationship_graph(<<-MANIFEST) autorelation_one { 'foo': } autorelation_two { 'bar': } MANIFEST src = relationship_graph.vertices.select{ |x| x.ref.to_s == 'Autorelation_two[bar]' }.first dst = relationship_graph.vertices.select{ |x| x.ref.to_s == 'Autorelation_one[foo]' }.first expect(relationship_graph.edge?(src,dst)).to be_truthy expect(relationship_graph.edges_between(src,dst).first.event).to eq(:ALL_EVENTS) end end end describe "when initializing" do describe "and passed a Puppet::Resource instance" do it "should set its title to the title of the resource if the resource type is equal to the current type" do resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:name => "/other"}) expect(Puppet::Type.type(:mount).new(resource).title).to eq("/foo") end it "should set its title to the resource reference if the resource type is not equal to the current type" do resource = Puppet::Resource.new(:user, "foo") expect(Puppet::Type.type(:mount).new(resource).title).to eq("User[foo]") end [:line, :file, :catalog, :exported, :virtual].each do |param| it "should copy '#{param}' from the resource if present" do resource = Puppet::Resource.new(:mount, "/foo") resource.send(param.to_s + "=", "foo") resource.send(param.to_s + "=", "foo") expect(Puppet::Type.type(:mount).new(resource).send(param)).to eq("foo") end end it "should copy any tags from the resource" do resource = Puppet::Resource.new(:mount, "/foo") resource.tag "one", "two" tags = Puppet::Type.type(:mount).new(resource).tags expect(tags).to be_include("one") expect(tags).to be_include("two") end it "should copy the resource's parameters as its own" do resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:atboot => :yes, :fstype => "boo"}) params = Puppet::Type.type(:mount).new(resource).to_hash expect(params[:fstype]).to eq("boo") expect(params[:atboot]).to eq(:yes) end it "copies sensitive parameters to the appropriate properties" do resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:atboot => :yes, :fstype => "boo"}, :sensitive_parameters => [:fstype]) type = Puppet::Type.type(:mount).new(resource) expect(type.property(:fstype).sensitive).to eq true end it "logs a warning when a parameter is marked as sensitive" do resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:atboot => :yes, :fstype => "boo", :remounts => true}, :sensitive_parameters => [:remounts]) Puppet::Type.type(:mount).any_instance.expects(:warning).with(regexp_matches(/Unable to mark 'remounts' as sensitive: remounts is a parameter and not a property/)) Puppet::Type.type(:mount).new(resource) end it "logs a warning when a property is not set but is marked as sensitive" do resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:atboot => :yes, :fstype => "boo"}, :sensitive_parameters => [:device]) Puppet::Type.type(:mount).any_instance.expects(:warning).with("Unable to mark 'device' as sensitive: the property itself was not assigned a value.") Puppet::Type.type(:mount).new(resource) end it "logs an error when a property is not defined on the type but is marked as sensitive" do resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:atboot => :yes, :fstype => "boo"}, :sensitive_parameters => [:content]) Puppet::Type.type(:mount).any_instance.expects(:err).with("Unable to mark 'content' as sensitive: the property itself is not defined on mount.") Puppet::Type.type(:mount).new(resource) end end describe "and passed a Hash" do it "should extract the title from the hash" do expect(Puppet::Type.type(:mount).new(:title => "/yay").title).to eq("/yay") end it "should work when hash keys are provided as strings" do expect(Puppet::Type.type(:mount).new("title" => "/yay").title).to eq("/yay") end it "should work when hash keys are provided as symbols" do expect(Puppet::Type.type(:mount).new(:title => "/yay").title).to eq("/yay") end it "should use the name from the hash as the title if no explicit title is provided" do expect(Puppet::Type.type(:mount).new(:name => "/yay").title).to eq("/yay") end it "should use the Resource Type's namevar to determine how to find the name in the hash" do yay = make_absolute('/yay') expect(Puppet::Type.type(:file).new(:path => yay).title).to eq(yay) end [:catalog].each do |param| it "should extract '#{param}' from the hash if present" do expect(Puppet::Type.type(:mount).new(:name => "/yay", param => "foo").send(param)).to eq("foo") end end it "should use any remaining hash keys as its parameters" do resource = Puppet::Type.type(:mount).new(:title => "/foo", :catalog => "foo", :atboot => :yes, :fstype => "boo") expect(resource[:fstype]).to eq("boo") expect(resource[:atboot]).to eq(:yes) end end it "should fail if any invalid attributes have been provided" do expect { Puppet::Type.type(:mount).new(:title => "/foo", :nosuchattr => "whatever") }.to raise_error(Puppet::Error, /no parameter named 'nosuchattr'/) end context "when an attribute fails validation" do it "should fail with Puppet::ResourceError when PuppetError raised" do expect { Puppet::Type.type(:file).new(:title => "/foo", :source => "unknown:///") }.to raise_error(Puppet::ResourceError, /Parameter source failed on File\[.*foo\]/) end it "should fail with Puppet::ResourceError when ArgumentError raised" do expect { Puppet::Type.type(:file).new(:title => "/foo", :mode => "abcdef") }.to raise_error(Puppet::ResourceError, /Parameter mode failed on File\[.*foo\]/) end it "should include the file/line in the error" do Puppet::Type.type(:file).any_instance.stubs(:file).returns("example.pp") Puppet::Type.type(:file).any_instance.stubs(:line).returns(42) expect { Puppet::Type.type(:file).new(:title => "/foo", :source => "unknown:///") }.to raise_error(Puppet::ResourceError, /\(file: example\.pp, line: 42\)/) end end it "should set its name to the resource's title if the resource does not have a :name or namevar parameter set" do resource = Puppet::Resource.new(:mount, "/foo") expect(Puppet::Type.type(:mount).new(resource).name).to eq("/foo") end it "should fail if no title, name, or namevar are provided" do expect { Puppet::Type.type(:mount).new(:atboot => :yes) }.to raise_error(Puppet::Error) end it "should set the attributes in the order returned by the class's :allattrs method" do Puppet::Type.type(:mount).stubs(:allattrs).returns([:name, :atboot, :noop]) resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:name => "myname", :atboot => :yes, :noop => "whatever"}) set = [] Puppet::Type.type(:mount).any_instance.stubs(:newattr).with do |param, hash| set << param true end.returns(stub_everything("a property")) Puppet::Type.type(:mount).new(resource) expect(set[-1]).to eq(:noop) expect(set[-2]).to eq(:atboot) end it "should always set the name and then default provider before anything else" do Puppet::Type.type(:mount).stubs(:allattrs).returns([:provider, :name, :atboot]) resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:name => "myname", :atboot => :yes}) set = [] Puppet::Type.type(:mount).any_instance.stubs(:newattr).with do |param, hash| set << param true end.returns(stub_everything("a property")) Puppet::Type.type(:mount).new(resource) expect(set[0]).to eq(:name) expect(set[1]).to eq(:provider) end # This one is really hard to test :/ it "should set each default immediately if no value is provided" do defaults = [] Puppet::Type.type(:service).any_instance.stubs(:set_default).with { |value| defaults << value; true } Puppet::Type.type(:service).new :name => "whatever" expect(defaults[0]).to eq(:provider) end it "should retain a copy of the originally provided parameters" do expect(Puppet::Type.type(:mount).new(:name => "foo", :atboot => :yes, :noop => false).original_parameters).to eq({:atboot => :yes, :noop => false}) end it "should delete the name via the namevar from the originally provided parameters" do expect(Puppet::Type.type(:file).new(:name => make_absolute('/foo')).original_parameters[:path]).to be_nil end context "when validating the resource" do it "should call the type's validate method if present" do Puppet::Type.type(:file).any_instance.expects(:validate) Puppet::Type.type(:file).new(:name => make_absolute('/foo')) end it "should raise Puppet::ResourceError with resource name when Puppet::Error raised" do expect do Puppet::Type.type(:file).new( :name => make_absolute('/foo'), :source => "puppet:///", :content => "foo" ) end.to raise_error(Puppet::ResourceError, /Validation of File\[.*foo.*\]/) end it "should raise Puppet::ResourceError with manifest file and line on failure" do Puppet::Type.type(:file).any_instance.stubs(:file).returns("example.pp") Puppet::Type.type(:file).any_instance.stubs(:line).returns(42) expect do Puppet::Type.type(:file).new( :name => make_absolute('/foo'), :source => "puppet:///", :content => "foo" ) end.to raise_error(Puppet::ResourceError, /Validation.*\(file: example\.pp, line: 42\)/) end end end describe "when #finish is called on a type" do let(:post_hook_type) do Puppet::Type.newtype(:finish_test) do newparam(:name) { isnamevar } newparam(:post) do def post_compile raise "post_compile hook ran" end end end end let(:post_hook_resource) do post_hook_type.new(:name => 'foo',:post => 'fake_value') end it "should call #post_compile on parameters that implement it" do expect { post_hook_resource.finish }.to raise_error(RuntimeError, "post_compile hook ran") end end it "should have a class method for converting a hash into a Puppet::Resource instance" do expect(Puppet::Type.type(:mount)).to respond_to(:hash2resource) end describe "when converting a hash to a Puppet::Resource instance" do before do @type = Puppet::Type.type(:mount) end it "should treat a :title key as the title of the resource" do expect(@type.hash2resource(:name => "/foo", :title => "foo").title).to eq("foo") end it "should use the name from the hash as the title if no explicit title is provided" do expect(@type.hash2resource(:name => "foo").title).to eq("foo") end it "should use the Resource Type's namevar to determine how to find the name in the hash" do @type.stubs(:key_attributes).returns([ :myname ]) expect(@type.hash2resource(:myname => "foo").title).to eq("foo") end [:catalog].each do |attr| it "should use any provided #{attr}" do expect(@type.hash2resource(:name => "foo", attr => "eh").send(attr)).to eq("eh") end end it "should set all provided parameters on the resource" do expect(@type.hash2resource(:name => "foo", :fstype => "boo", :boot => "fee").to_hash).to eq({:name => "foo", :fstype => "boo", :boot => "fee"}) end it "should not set the title as a parameter on the resource" do expect(@type.hash2resource(:name => "foo", :title => "eh")[:title]).to be_nil end it "should not set the catalog as a parameter on the resource" do expect(@type.hash2resource(:name => "foo", :catalog => "eh")[:catalog]).to be_nil end it "should treat hash keys equivalently whether provided as strings or symbols" do resource = @type.hash2resource("name" => "foo", "title" => "eh", "fstype" => "boo") expect(resource.title).to eq("eh") expect(resource[:name]).to eq("foo") expect(resource[:fstype]).to eq("boo") end end describe "when retrieving current property values" do before do @resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present) @resource.property(:ensure).stubs(:retrieve).returns :absent end it "should always retrieve the ensure value by default" do @ensurable_resource = Puppet::Type.type(:file).new(:name => "/not/existent", :mode => "0644") Puppet::Type::File::Ensure.stubs(:ensure).returns :absent Puppet::Type::File::Ensure.any_instance.expects(:retrieve).once @ensurable_resource.retrieve_resource end it "should not retrieve the ensure value if specified" do @ensurable_resource = Puppet::Type.type(:service).new(:name => "DummyService", :enable => true) @ensurable_resource.properties.each { |prop| prop.stubs(:retrieve) } Puppet::Type::Service::Ensure.any_instance.expects(:retrieve).never @ensurable_resource.retrieve_resource end it "should fail if its provider is unsuitable" do @resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present) @resource.provider.class.expects(:suitable?).returns false expect { @resource.retrieve_resource }.to raise_error(Puppet::Error) end it "should return a Puppet::Resource instance with its type and title set appropriately" do result = @resource.retrieve_resource expect(result).to be_instance_of(Puppet::Resource) expect(result.type).to eq("Mount") expect(result.title).to eq("foo") end it "should set the name of the returned resource if its own name and title differ" do @resource[:name] = "myname" @resource.title = "other name" expect(@resource.retrieve_resource[:name]).to eq("myname") end it "should provide a value for all set properties" do values = @resource.retrieve_resource [:ensure, :fstype, :pass].each { |property| expect(values[property]).not_to be_nil } end it "should provide a value for 'ensure' even if no desired value is provided" do @resource = Puppet::Type.type(:file).new(:path => make_absolute("/my/file/that/can't/exist")) end it "should not call retrieve on non-ensure properties if the resource is absent and should consider the property absent" do @resource.property(:ensure).expects(:retrieve).returns :absent @resource.property(:fstype).expects(:retrieve).never expect(@resource.retrieve_resource[:fstype]).to eq(:absent) end it "should include the result of retrieving each property's current value if the resource is present" do @resource.property(:ensure).expects(:retrieve).returns :present @resource.property(:fstype).expects(:retrieve).returns 15 @resource.retrieve_resource[:fstype] == 15 end end describe "#to_resource" do it "should return a Puppet::Resource that includes properties, parameters and tags" do type_resource = Puppet::Type.type(:mount).new( :ensure => :present, :name => "foo", :fstype => "bar", :remounts => true ) type_resource.tags = %w{bar baz} # If it's not a property it's a parameter expect(type_resource.parameters[:remounts]).not_to be_a(Puppet::Property) expect(type_resource.parameters[:fstype].is_a?(Puppet::Property)).to be_truthy type_resource.property(:ensure).expects(:retrieve).returns :present type_resource.property(:fstype).expects(:retrieve).returns 15 resource = type_resource.to_resource expect(resource).to be_a Puppet::Resource expect(resource[:fstype]).to eq(15) expect(resource[:remounts]).to eq(:true) expect(resource.tags).to eq(Puppet::Util::TagSet.new(%w{foo bar baz mount})) end end describe ".title_patterns" do describe "when there's one namevar" do before do @type_class = Puppet::Type.type(:notify) @type_class.stubs(:key_attributes).returns([:one]) end it "should have a default pattern for when there's one namevar" do patterns = @type_class.title_patterns expect(patterns.length).to eq(1) expect(patterns[0].length).to eq(2) end it "should have a regexp that captures the entire string" do patterns = @type_class.title_patterns string = "abc\n\tdef" patterns[0][0] =~ string expect($1).to eq("abc\n\tdef") end end end describe "when in a catalog" do before do @catalog = Puppet::Resource::Catalog.new @container = Puppet::Type.type(:component).new(:name => "container") @one = Puppet::Type.type(:file).new(:path => make_absolute("/file/one")) @two = Puppet::Type.type(:file).new(:path => make_absolute("/file/two")) @catalog.add_resource @container @catalog.add_resource @one @catalog.add_resource @two @catalog.add_edge @container, @one @catalog.add_edge @container, @two end it "should have no parent if there is no in edge" do expect(@container.parent).to be_nil end it "should set its parent to its in edge" do expect(@one.parent.ref).to eq(@container.ref) end after do @catalog.clear(true) end end it "should have a 'stage' metaparam" do expect(Puppet::Type.metaparamclass(:stage)).to be_instance_of(Class) end describe "#suitable?" do let(:type) { Puppet::Type.type(:file) } let(:resource) { type.new :path => tmpfile('suitable') } let(:provider) { resource.provider } it "should be suitable if its type doesn't use providers" do type.stubs(:paramclass).with(:provider).returns nil expect(resource).to be_suitable end it "should be suitable if it has a provider which is suitable" do expect(resource).to be_suitable end it "should not be suitable if it has a provider which is not suitable" do provider.class.stubs(:suitable?).returns false expect(resource).not_to be_suitable end it "should be suitable if it does not have a provider and there is a default provider" do resource.stubs(:provider).returns nil expect(resource).to be_suitable end it "should not be suitable if it doesn't have a provider and there is not default provider" do resource.stubs(:provider).returns nil type.stubs(:defaultprovider).returns nil expect(resource).not_to be_suitable end end describe "::instances" do after :each do Puppet::Type.rmtype(:type_spec_fake_type) end let :type do Puppet::Type.newtype(:type_spec_fake_type) do newparam(:name) do isnamevar end newproperty(:prop1) {} end Puppet::Type.type(:type_spec_fake_type) end it "should not fail if no suitable providers are found" do type.provide(:fake1) do confine :exists => '/no/such/file' mk_resource_methods end expect { expect(type.instances).to eq([]) }.to_not raise_error end context "with a default provider" do before :each do type.provide(:default) do defaultfor :operatingsystem => Facter.value(:operatingsystem) mk_resource_methods class << self attr_accessor :names end def self.instance(name) new(:name => name, :ensure => :present) end def self.instances @instances ||= names.collect { |name| instance(name.to_s) } end @names = [:one, :two] end end it "should return only instances of the type" do expect(type.instances).to be_all {|x| x.is_a? type } end it "should return instances from the default provider" do expect(type.instances.map(&:name)).to eq(["one", "two"]) end it "should return instances from all providers" do type.provide(:fake1, :parent => :default) { @names = [:three, :four] } expect(type.instances.map(&:name)).to eq(["one", "two", "three", "four"]) end it "should not return instances from unsuitable providers" do type.provide(:fake1, :parent => :default) do @names = [:three, :four] confine :exists => "/no/such/file" end expect(type.instances.map(&:name)).to eq(["one", "two"]) end end end describe "::ensurable?" do before :each do class TestEnsurableType < Puppet::Type def exists?; end def create; end def destroy; end end end it "is true if the class has exists?, create, and destroy methods defined" do expect(TestEnsurableType).to be_ensurable end it "is false if exists? is not defined" do TestEnsurableType.class_eval { remove_method(:exists?) } expect(TestEnsurableType).not_to be_ensurable end it "is false if create is not defined" do TestEnsurableType.class_eval { remove_method(:create) } expect(TestEnsurableType).not_to be_ensurable end it "is false if destroy is not defined" do TestEnsurableType.class_eval { remove_method(:destroy) } expect(TestEnsurableType).not_to be_ensurable end end end describe Puppet::Type::RelationshipMetaparam do include PuppetSpec::Files it "should be a subclass of Puppet::Parameter" do expect(Puppet::Type::RelationshipMetaparam.superclass).to equal(Puppet::Parameter) end it "should be able to produce a list of subclasses" do expect(Puppet::Type::RelationshipMetaparam).to respond_to(:subclasses) end describe "when munging relationships" do before do @path = File.expand_path('/foo') @resource = Puppet::Type.type(:file).new :name => @path @metaparam = Puppet::Type.metaparamclass(:require).new :resource => @resource end it "should accept Puppet::Resource instances" do ref = Puppet::Resource.new(:file, @path) expect(@metaparam.munge(ref)[0]).to equal(ref) end it "should turn any string into a Puppet::Resource" do expect(@metaparam.munge("File[/ref]")[0]).to be_instance_of(Puppet::Resource) end end it "should be able to validate relationships" do expect(Puppet::Type.metaparamclass(:require).new(:resource => mock("resource"))).to respond_to(:validate_relationship) end describe 'if any specified resource is not in the catalog' do let(:catalog) { mock 'catalog' } let(:resource) do stub 'resource', :catalog => catalog, :ref => 'resource', :line= => nil, :line => nil, :file= => nil, :file => nil end let(:param) { Puppet::Type.metaparamclass(:require).new(:resource => resource, :value => %w{Foo[bar] Class[test]}) } before do catalog.expects(:resource).with("Foo[bar]").returns "something" catalog.expects(:resource).with("Class[Test]").returns nil end describe "and the resource doesn't have a file or line number" do it "raises an error" do expect { param.validate_relationship }.to raise_error do |error| expect(error).to be_a Puppet::ResourceError expect(error.message).to match %r[Class\[Test\]] end end end describe "and the resource has a file or line number" do before do resource.stubs(:line).returns '42' resource.stubs(:file).returns '/hitchhikers/guide/to/the/galaxy' end it "raises an error with context" do expect { param.validate_relationship }.to raise_error do |error| expect(error).to be_a Puppet::ResourceError expect(error.message).to match %r[Class\[Test\]] expect(error.message).to match %r[\(file: /hitchhikers/guide/to/the/galaxy, line: 42\)] end end end end end describe Puppet::Type.metaparamclass(:audit) do include PuppetSpec::Files before do @resource = Puppet::Type.type(:file).new :path => make_absolute('/foo') end it "should default to being nil" do expect(@resource[:audit]).to be_nil end it "should specify all possible properties when asked to audit all properties" do @resource[:audit] = :all list = @resource.class.properties.collect { |p| p.name } expect(@resource[:audit]).to eq(list) end it "should accept the string 'all' to specify auditing all possible properties" do @resource[:audit] = 'all' list = @resource.class.properties.collect { |p| p.name } expect(@resource[:audit]).to eq(list) end it "should fail if asked to audit an invalid property" do expect { @resource[:audit] = :foobar }.to raise_error(Puppet::Error) end it "should create an attribute instance for each auditable property" do @resource[:audit] = :mode expect(@resource.parameter(:mode)).not_to be_nil end it "should accept properties specified as a string" do @resource[:audit] = "mode" expect(@resource.parameter(:mode)).not_to be_nil end it "should not create attribute instances for parameters, only properties" do @resource[:audit] = :noop expect(@resource.parameter(:noop)).to be_nil end describe "when generating the uniqueness key" do it "should include all of the key_attributes in alphabetical order by attribute name" do Puppet::Type.type(:file).stubs(:key_attributes).returns [:path, :mode, :owner] Puppet::Type.type(:file).stubs(:title_patterns).returns( [ [ /(.*)/, [ [:path, lambda{|x| x} ] ] ] ] ) myfile = make_absolute('/my/file') res = Puppet::Type.type(:file).new( :title => myfile, :path => myfile, :owner => 'root', :content => 'hello' ) expect(res.uniqueness_key).to eq([ nil, 'root', myfile]) end end context "type attribute bracket methods" do after :each do Puppet::Type.rmtype(:attributes) end let :type do Puppet::Type.newtype(:attributes) do newparam(:name) {} end end it "should work with parameters" do type.newparam(:param) {} instance = type.new(:name => 'test') expect { instance[:param] = true }.to_not raise_error expect { instance["param"] = true }.to_not raise_error expect(instance[:param]).to eq(true) expect(instance["param"]).to eq(true) end it "should work with meta-parameters" do instance = type.new(:name => 'test') expect { instance[:noop] = true }.to_not raise_error expect { instance["noop"] = true }.to_not raise_error expect(instance[:noop]).to eq(true) expect(instance["noop"]).to eq(true) end it "should work with properties" do type.newproperty(:property) {} instance = type.new(:name => 'test') expect { instance[:property] = true }.to_not raise_error expect { instance["property"] = true }.to_not raise_error expect(instance.property(:property)).to be expect(instance.should(:property)).to be_truthy end it "should handle proprieties correctly" do # Order of assignment is significant in this test. [:one, :two, :three].each {|prop| type.newproperty(prop) {} } instance = type.new(:name => "test") instance[:one] = "boo" one = instance.property(:one) expect(instance.properties).to eq [one] instance[:three] = "rah" three = instance.property(:three) expect(instance.properties).to eq [one, three] instance[:two] = "whee" two = instance.property(:two) expect(instance.properties).to eq [one, two, three] end it "newattr should handle required features correctly" do Puppet::Util::Log.level = :debug type.feature :feature1, "one" type.feature :feature2, "two" type.newproperty(:none) {} type.newproperty(:one, :required_features => :feature1) {} type.newproperty(:two, :required_features => [:feature1, :feature2]) {} nope = type.provide(:nope) {} maybe = type.provide(:maybe) { has_features :feature1 } yep = type.provide(:yep) { has_features :feature1, :feature2 } [nope, maybe, yep].each_with_index do |provider, i| rsrc = type.new(:provider => provider.name, :name => "test#{i}", :none => "a", :one => "b", :two => "c") expect(rsrc.should(:none)).to be if provider.declared_feature? :feature1 expect(rsrc.should(:one)).to be else expect(rsrc.should(:one)).to_not be expect(@logs.find {|l| l.message =~ /not managing attribute one/ }).to be end if provider.declared_feature? :feature2 expect(rsrc.should(:two)).to be else expect(rsrc.should(:two)).to_not be expect(@logs.find {|l| l.message =~ /not managing attribute two/ }).to be end end end end end puppet-5.5.10/spec/unit/util_spec.rb0000644005276200011600000011155513417161722017250 0ustar jenkinsjenkins#!/usr/bin/env ruby require 'spec_helper' describe Puppet::Util do include PuppetSpec::Files # Discriminator for tests that attempts to unset HOME since that, for reasons currently unknown, # doesn't work in Ruby >= 2.4.0 def self.gte_ruby_2_4 @gte_ruby_2_4 ||= SemanticPuppet::Version.parse(RUBY_VERSION) >= SemanticPuppet::Version.parse('2.4.0') end if Puppet.features.microsoft_windows? def set_mode(mode, file) Puppet::Util::Windows::Security.set_mode(mode, file) end def get_mode(file) Puppet::Util::Windows::Security.get_mode(file) & 07777 end else def set_mode(mode, file) File.chmod(mode, file) end def get_mode(file) Puppet::FileSystem.lstat(file).mode & 07777 end end describe "#withenv" do let(:mode) { Puppet.features.microsoft_windows? ? :windows : :posix } before :each do @original_path = ENV["PATH"] @new_env = {:PATH => "/some/bogus/path"} end it "should change environment variables within the block then reset environment variables to their original values" do Puppet::Util.withenv @new_env, mode do expect(ENV["PATH"]).to eq("/some/bogus/path") end expect(ENV["PATH"]).to eq(@original_path) end it "should reset environment variables to their original values even if the block fails" do begin Puppet::Util.withenv @new_env, mode do expect(ENV["PATH"]).to eq("/some/bogus/path") raise "This is a failure" end rescue end expect(ENV["PATH"]).to eq(@original_path) end it "should reset environment variables even when they are set twice" do # Setting Path & Environment parameters in Exec type can cause weirdness @new_env["PATH"] = "/someother/bogus/path" Puppet::Util.withenv @new_env, mode do # When assigning duplicate keys, can't guarantee order of evaluation expect(ENV["PATH"]).to match(/\/some.*\/bogus\/path/) end expect(ENV["PATH"]).to eq(@original_path) end it "should remove any new environment variables after the block ends" do @new_env[:FOO] = "bar" ENV["FOO"] = nil Puppet::Util.withenv @new_env, mode do expect(ENV["FOO"]).to eq("bar") end expect(ENV["FOO"]).to eq(nil) end end describe "#withenv on POSIX", :unless => Puppet.features.microsoft_windows? do it "should preserve case" do # start with lower case key, env_key = SecureRandom.uuid.downcase begin original_value = 'hello' ENV[env_key] = original_value new_value = 'goodbye' Puppet::Util.withenv({env_key.upcase => new_value}, :posix) do expect(ENV[env_key]).to eq(original_value) expect(ENV[env_key.upcase]).to eq(new_value) end expect(ENV[env_key]).to eq(original_value) expect(ENV[env_key.upcase]).to be_nil ensure ENV.delete(env_key) end end end describe "#withenv on Windows", :if => Puppet.features.microsoft_windows? do let(:process) { Puppet::Util::Windows::Process } it "should ignore case" do # start with lower case key, ensuring string is not entirely numeric env_key = SecureRandom.uuid.downcase + 'a' begin original_value = 'hello' ENV[env_key] = original_value new_value = 'goodbye' Puppet::Util.withenv({env_key.upcase => new_value}, :windows) do expect(ENV[env_key]).to eq(new_value) expect(ENV[env_key.upcase]).to eq(new_value) end expect(ENV[env_key]).to eq(original_value) expect(ENV[env_key.upcase]).to eq(original_value) ensure ENV.delete(env_key) end end def withenv_utf8(&block) env_var_name = SecureRandom.uuid utf_8_bytes = [225, 154, 160] # rune áš  utf_8_key = env_var_name + utf_8_bytes.pack('c*').force_encoding(Encoding::UTF_8) utf_8_value = utf_8_key + 'value' codepage_key = utf_8_key.dup.force_encoding(Encoding.default_external) Puppet::Util.withenv({utf_8_key => utf_8_value}, :windows) do # the true Windows environment APIs see the variables correctly expect(process.get_environment_strings[utf_8_key]).to eq(utf_8_value) # the string contain the same bytes, but have different Ruby metadata expect(utf_8_key.bytes.to_a).to eq(codepage_key.bytes.to_a) yield utf_8_key, utf_8_value, codepage_key end # real environment shouldn't have env var anymore expect(process.get_environment_strings[utf_8_key]).to eq(nil) end # document buggy Ruby behavior here for https://bugs.ruby-lang.org/issues/8822 # Ruby retrieves / stores ENV names in the current codepage # when these tests no longer pass, Ruby has fixed its bugs and workarounds can be removed # interestingly we would expect some of these tests to fail when codepage is 65001 # but instead the env values are in Encoding::ASCII_8BIT! it "works around Ruby bug 8822 (which fails to preserve UTF-8 properly when accessing ENV) (Ruby <= 2.1) ", :if => ((RUBY_VERSION =~ /^(1\.|2\.0\.|2\.1\.)/) && Puppet.features.microsoft_windows?) do withenv_utf8 do |utf_8_key, utf_8_value, codepage_key| # both a string in UTF-8 and current codepage are deemed valid keys to the hash # which is because Ruby compares the BINARY versions of the string, but ignores encoding expect(ENV.key?(codepage_key)).to eq(true) expect(ENV.key?(utf_8_key)).to eq(true) # Ruby's ENV.keys has slightly different behavior than ENV.key?(key) # the keys collection in 2.1 has a string with the correct bytes # (codepage_key / utf_8_key have same bytes for the sake of searching) found = ENV.keys.find { |k| k.bytes == codepage_key.bytes } # but the string is actually a binary string expect(found.encoding).to eq(Encoding::BINARY) # meaning we can't use include? to find it in either UTF-8 or codepage encoding expect(ENV.keys.include?(codepage_key)).to eq(false) expect(ENV.keys.include?(utf_8_key)).to eq(false) # and can only search with a BINARY encoded string expect(ENV.keys.include?(utf_8_key.dup.force_encoding(Encoding::BINARY))).to eq(true) # similarly the value stored at the real key is in current codepage # but won't match real UTF-8 value env_value = ENV[utf_8_key] expect(env_value).to_not eq(utf_8_value) expect(env_value.encoding).to_not eq(Encoding::UTF_8) # but it can be forced back to UTF-8 to make it match.. ugh converted_value = ENV[utf_8_key].dup.force_encoding(Encoding::UTF_8) expect(converted_value).to eq(utf_8_value) end end # but in 2.3, the behavior is mostly correct when external codepage is 65001 / UTF-8 it "works around Ruby bug 8822 (which fails to preserve UTF-8 properly when accessing ENV) (Ruby >= 2.3.x) ", :if => ((match = RUBY_VERSION.match(/^2\.(\d+)\./)) && match.captures[0].to_i >= 3 && Puppet.features.microsoft_windows?) do raise 'This test requires a non-UTF8 codepage' if Encoding.default_external == Encoding::UTF_8 withenv_utf8 do |utf_8_key, utf_8_value, codepage_key| # Ruby 2.3 fixes access by the original UTF-8 key, and behaves differently than 2.1 # keying by local codepage will work only when the UTF-8 can be converted to local codepage # the key selected for this test contains characters unavailable to a local codepage, hence doesn't work # On Japanese Windows (Code Page 932) this test resolves as true. # otherwise the key selected for this test contains characters # unavailable to a local codepage, hence doesn't work # HACK: tech debt to replace once PUP-7019 is understood should_be_found = (Encoding.default_external == Encoding::CP932) expect(ENV.key?(codepage_key)).to eq(should_be_found) expect(ENV.key?(utf_8_key)).to eq(true) # Ruby's ENV.keys has slightly different behavior than ENV.key?(key), and 2.3 differs from 2.1 # (codepage_key / utf_8_key have same bytes for the sake of searching) found = ENV.keys.find { |k| k.bytes == codepage_key.bytes } # the keys collection in 2.3 does not have a string with the correct bytes! # a corrupt version of the key exists with the bytes [225, 154, 160] replaced with [63]! expect(found).to be_nil # given the key is corrupted, include? cannot be used to find it in either UTF-8 or codepage encoding expect(ENV.keys.include?(codepage_key)).to eq(false) expect(ENV.keys.include?(utf_8_key)).to eq(false) # The value stored at the UTF-8 key is a corrupted current codepage string and won't match UTF-8 value # again the bytes [225, 154, 160] have irreversibly been changed to [63]! env_value = ENV[utf_8_key] expect(env_value).to_not eq(utf_8_value) expect(env_value.encoding).to_not eq(Encoding::UTF_8) # the ENV value returned will be in the local codepage which may or may not be able to be # encoded to UTF8. Our test UTF8 data is not convertible to non-Unicode codepages converted_value = ENV[utf_8_key].dup.force_encoding(Encoding::UTF_8) expect(converted_value).to_not eq(utf_8_value) end end it "should preseve existing environment and should not corrupt UTF-8 environment variables" do env_var_name = SecureRandom.uuid utf_8_bytes = [225, 154, 160] # rune áš  utf_8_str = env_var_name + utf_8_bytes.pack('c*').force_encoding(Encoding::UTF_8) env_var_name_utf_8 = utf_8_str begin # UTF-8 name and value process.set_environment_variable(env_var_name_utf_8, utf_8_str) # ASCII name / UTF-8 value process.set_environment_variable(env_var_name, utf_8_str) original_keys = process.get_environment_strings.keys.to_a Puppet::Util.withenv({}, :windows) { } env = process.get_environment_strings expect(env[env_var_name]).to eq(utf_8_str) expect(env[env_var_name_utf_8]).to eq(utf_8_str) expect(env.keys.to_a).to eq(original_keys) ensure process.set_environment_variable(env_var_name_utf_8, nil) process.set_environment_variable(env_var_name, nil) end end end describe "#absolute_path?" do describe "on posix systems", :if => Puppet.features.posix? do it "should default to the platform of the local system" do expect(Puppet::Util).to be_absolute_path('/foo') expect(Puppet::Util).not_to be_absolute_path('C:/foo') end end describe "on windows", :if => Puppet.features.microsoft_windows? do it "should default to the platform of the local system" do expect(Puppet::Util).to be_absolute_path('C:/foo') expect(Puppet::Util).not_to be_absolute_path('/foo') end end describe "when using platform :posix" do %w[/ /foo /foo/../bar //foo //Server/Foo/Bar //?/C:/foo/bar /\Server/Foo /foo//bar/baz].each do |path| it "should return true for #{path}" do expect(Puppet::Util).to be_absolute_path(path, :posix) end end %w[. ./foo \foo C:/foo \\Server\Foo\Bar \\?\C:\foo\bar \/?/foo\bar \/Server/foo foo//bar/baz].each do |path| it "should return false for #{path}" do expect(Puppet::Util).not_to be_absolute_path(path, :posix) end end end describe "when using platform :windows" do %w[C:/foo C:\foo \\\\Server\Foo\Bar \\\\?\C:\foo\bar //Server/Foo/Bar //?/C:/foo/bar /\?\C:/foo\bar \/Server\Foo/Bar c:/foo//bar//baz].each do |path| it "should return true for #{path}" do expect(Puppet::Util).to be_absolute_path(path, :windows) end end %w[/ . ./foo \foo /foo /foo/../bar //foo C:foo/bar foo//bar/baz].each do |path| it "should return false for #{path}" do expect(Puppet::Util).not_to be_absolute_path(path, :windows) end end end end describe "#path_to_uri" do # different UTF-8 widths # 1-byte A # 2-byte Űż - http://www.fileformat.info/info/unicode/char/06ff/index.htm - 0xDB 0xBF / 219 191 # 3-byte áš  - http://www.fileformat.info/info/unicode/char/16A0/index.htm - 0xE1 0x9A 0xA0 / 225 154 160 # 4-byte ÜŽ - http://www.fileformat.info/info/unicode/char/2070E/index.htm - 0xF0 0xA0 0x9C 0x8E / 240 160 156 142 let (:mixed_utf8) { "A\u06FF\u16A0\u{2070E}" } # AŰżáš ÜŽ let (:mixed_utf8_urlencoded) { "A%DB%BF%E1%9A%A0%F0%A0%9C%8E" } %w[. .. foo foo/bar foo/../bar].each do |path| it "should reject relative path: #{path}" do expect { Puppet::Util.path_to_uri(path) }.to raise_error(Puppet::Error) end end it "should perform URI escaping" do expect(Puppet::Util.path_to_uri("/foo bar").path).to eq("/foo%20bar") end it "should properly URI encode + and space in path" do expect(Puppet::Util.path_to_uri("/foo+foo bar").path).to eq("/foo+foo%20bar") end # reserved characters are different for each part # https://web.archive.org/web/20151229061347/http://blog.lunatech.com/2009/02/03/what-every-web-developer-must-know-about-url-encoding#Thereservedcharactersaredifferentforeachpart # "?" is allowed unescaped anywhere within a query part, # "/" is allowed unescaped anywhere within a query part, # "=" is allowed unescaped anywhere within a path parameter or query parameter value, and within a path segment, # ":@-._~!$&'()*+,;=" are allowed unescaped anywhere within a path segment part, # "/?:@-._~!$&'()*+,;=" are allowed unescaped anywhere within a fragment part. it "should properly URI encode + and space in path and query" do path = "/foo+foo bar?foo+foo bar" uri = Puppet::Util.path_to_uri(path) # Ruby 1.9.3 URI#to_s has a bug that returns ASCII always # despite parts being UTF-8 strings expected_encoding = RUBY_VERSION == '1.9.3' ? Encoding::ASCII : Encoding::UTF_8 expect(uri.to_s.encoding).to eq(expected_encoding) expect(uri.path).to eq("/foo+foo%20bar") # either + or %20 is correct for an encoded space in query # + is usually used for backward compatibility, but %20 is preferred for compat with Uri.unescape expect(uri.query).to eq("foo%2Bfoo%20bar") # complete roundtrip expect(URI.unescape(uri.to_s)).to eq("file:#{path}") expect(URI.unescape(uri.to_s).encoding).to eq(expected_encoding) end it "should perform UTF-8 URI escaping" do uri = Puppet::Util.path_to_uri("/#{mixed_utf8}") expect(uri.path.encoding).to eq(Encoding::UTF_8) expect(uri.path).to eq("/#{mixed_utf8_urlencoded}") end describe "when using platform :posix" do before :each do Puppet.features.stubs(:posix).returns true Puppet.features.stubs(:microsoft_windows?).returns false end %w[/ /foo /foo/../bar].each do |path| it "should convert #{path} to URI" do expect(Puppet::Util.path_to_uri(path).path).to eq(path) end end end describe "when using platform :windows" do before :each do Puppet.features.stubs(:posix).returns false Puppet.features.stubs(:microsoft_windows?).returns true end it "should normalize backslashes" do expect(Puppet::Util.path_to_uri('c:\\foo\\bar\\baz').path).to eq('/' + 'c:/foo/bar/baz') end %w[C:/ C:/foo/bar].each do |path| it "should convert #{path} to absolute URI" do expect(Puppet::Util.path_to_uri(path).path).to eq('/' + path) end end %w[share C$].each do |path| it "should convert UNC #{path} to absolute URI" do uri = Puppet::Util.path_to_uri("\\\\server\\#{path}") expect(uri.host).to eq('server') expect(uri.path).to eq('/' + Puppet::Util.uri_encode(path)) end end end end describe "#uri_query_encode" do # different UTF-8 widths # 1-byte A # 2-byte Űż - http://www.fileformat.info/info/unicode/char/06ff/index.htm - 0xDB 0xBF / 219 191 # 3-byte áš  - http://www.fileformat.info/info/unicode/char/16A0/index.htm - 0xE1 0x9A 0xA0 / 225 154 160 # 4-byte 𠜎 - http://www.fileformat.info/info/unicode/char/2070E/index.htm - 0xF0 0xA0 0x9C 0x8E / 240 160 156 142 let (:mixed_utf8) { "A\u06FF\u16A0\u{2070E}" } # Aۿᚠ𠜎 let (:mixed_utf8_urlencoded) { "A%DB%BF%E1%9A%A0%F0%A0%9C%8E" } it "should perform basic URI escaping that includes space and +" do expect(Puppet::Util.uri_query_encode("foo bar+foo")).to eq("foo%20bar%2Bfoo") end it "should URI encode any special characters: = + & * and #" do expect(Puppet::Util.uri_query_encode("foo=bar+foo baz&bar=baz qux&special= *&qux=not fragment#")).to eq("foo%3Dbar%2Bfoo%20baz%26bar%3Dbaz%20qux%26special%3D%20%2A%26qux%3Dnot%20fragment%23") end [ "A\u06FF\u16A0\u{2070E}", "A\u06FF\u16A0\u{2070E}".force_encoding(Encoding::BINARY) ].each do |uri_string| it "should perform UTF-8 URI escaping, even when input strings are not UTF-8" do uri = Puppet::Util.uri_query_encode(mixed_utf8) expect(uri.encoding).to eq(Encoding::UTF_8) expect(uri).to eq(mixed_utf8_urlencoded) end end it "should be usable by URI::parse" do uri = URI::parse("puppet://server/path?" + Puppet::Util.uri_query_encode(mixed_utf8)) expect(uri.scheme).to eq('puppet') expect(uri.host).to eq('server') expect(uri.path).to eq('/path') expect(uri.query).to eq(mixed_utf8_urlencoded) end it "should be usable by URI::Generic.build" do params = { :scheme => 'file', :host => 'foobar', :path => '/path/to', :query => Puppet::Util.uri_query_encode(mixed_utf8) } uri = URI::Generic.build(params) expect(uri.scheme).to eq('file') expect(uri.host).to eq('foobar') expect(uri.path).to eq("/path/to") expect(uri.query).to eq(mixed_utf8_urlencoded) end end describe "#uri_encode" do # different UTF-8 widths # 1-byte A # 2-byte Űż - http://www.fileformat.info/info/unicode/char/06ff/index.htm - 0xDB 0xBF / 219 191 # 3-byte áš  - http://www.fileformat.info/info/unicode/char/16A0/index.htm - 0xE1 0x9A 0xA0 / 225 154 160 # 4-byte ÜŽ - http://www.fileformat.info/info/unicode/char/2070E/index.htm - 0xF0 0xA0 0x9C 0x8E / 240 160 156 142 let (:mixed_utf8) { "A\u06FF\u16A0\u{2070E}" } # AŰżáš ÜŽ let (:mixed_utf8_urlencoded) { "A%DB%BF%E1%9A%A0%F0%A0%9C%8E" } it "should perform URI escaping" do expect(Puppet::Util.uri_encode("/foo bar")).to eq("/foo%20bar") end [ "A\u06FF\u16A0\u{2070E}", "A\u06FF\u16A0\u{2070E}".force_encoding(Encoding::BINARY) ].each do |uri_string| it "should perform UTF-8 URI escaping, even when input strings are not UTF-8" do uri = Puppet::Util.uri_encode(mixed_utf8) expect(uri.encoding).to eq(Encoding::UTF_8) expect(uri).to eq(mixed_utf8_urlencoded) end end it "should treat & and = as delimiters in a query string, but URI encode other special characters: + * and #" do input = "http://foo.bar.com/path?foo=bar+foo baz&bar=baz qux&special= *&qux=not fragment#" expected_output = "http://foo.bar.com/path?foo=bar%2Bfoo%20baz&bar=baz%20qux&special=%20%2A&qux=not%20fragment%23" expect(Puppet::Util.uri_encode(input)).to eq(expected_output) end it "should be usable by URI::parse" do uri = URI::parse(Puppet::Util.uri_encode("puppet://server/path/to/#{mixed_utf8}")) expect(uri.scheme).to eq('puppet') expect(uri.host).to eq('server') expect(uri.path).to eq("/path/to/#{mixed_utf8_urlencoded}") end it "should be usable by URI::Generic.build" do params = { :scheme => 'file', :host => 'foobar', :path => Puppet::Util.uri_encode("/path/to/#{mixed_utf8}") } uri = URI::Generic.build(params) expect(uri.scheme).to eq('file') expect(uri.host).to eq('foobar') expect(uri.path).to eq("/path/to/#{mixed_utf8_urlencoded}") end describe "when using platform :posix" do before :each do Puppet.features.stubs(:posix).returns true Puppet.features.stubs(:microsoft_windows?).returns false end %w[/ /foo /foo/../bar].each do |path| it "should not replace / in #{path} with %2F" do expect(Puppet::Util.uri_encode(path)).to eq(path) end end end describe "with fragment support" do context "disabled by default" do it "should encode # as %23 in path" do encoded = Puppet::Util.uri_encode("/foo bar#fragment") expect(encoded).to eq("/foo%20bar%23fragment") end it "should encode # as %23 in query" do encoded = Puppet::Util.uri_encode("/foo bar?baz+qux#fragment") expect(encoded).to eq("/foo%20bar?baz%2Bqux%23fragment") end end context "optionally enabled" do it "should leave fragment delimiter # after encoded paths" do encoded = Puppet::Util.uri_encode("/foo bar#fragment", { :allow_fragment => true }) expect(encoded).to eq("/foo%20bar#fragment") end it "should leave fragment delimiter # after encoded query" do encoded = Puppet::Util.uri_encode("/foo bar?baz+qux#fragment", { :allow_fragment => true }) expect(encoded).to eq("/foo%20bar?baz%2Bqux#fragment") end end end describe "when using platform :windows" do before :each do Puppet.features.stubs(:posix).returns false Puppet.features.stubs(:microsoft_windows?).returns true end it "should url encode \\ as %5C, but not replace : as %3F" do expect(Puppet::Util.uri_encode('c:\\foo\\bar\\baz')).to eq('c:%5Cfoo%5Cbar%5Cbaz') end %w[C:/ C:/foo/bar].each do |path| it "should not replace / in #{path} with %2F" do expect(Puppet::Util.uri_encode(path)).to eq(path) end end end end describe ".uri_to_path" do require 'uri' # different UTF-8 widths # 1-byte A # 2-byte Űż - http://www.fileformat.info/info/unicode/char/06ff/index.htm - 0xDB 0xBF / 219 191 # 3-byte áš  - http://www.fileformat.info/info/unicode/char/16A0/index.htm - 0xE1 0x9A 0xA0 / 225 154 160 # 4-byte 𠜎 - http://www.fileformat.info/info/unicode/char/2070E/index.htm - 0xF0 0xA0 0x9C 0x8E / 240 160 156 142 let (:mixed_utf8) { "A\u06FF\u16A0\u{2070E}" } # Aۿᚠ𠜎 it "should strip host component" do expect(Puppet::Util.uri_to_path(URI.parse('http://foo/bar'))).to eq('/bar') end it "should accept puppet URLs" do expect(Puppet::Util.uri_to_path(URI.parse('puppet:///modules/foo'))).to eq('/modules/foo') end it "should return unencoded path" do expect(Puppet::Util.uri_to_path(URI.parse('http://foo/bar%20baz'))).to eq('/bar baz') end [ "http://foo/A%DB%BF%E1%9A%A0%F0%A0%9C%8E", "http://foo/A%DB%BF%E1%9A%A0%F0%A0%9C%8E".force_encoding(Encoding::ASCII) ].each do |uri_string| it "should return paths as UTF-8" do path = Puppet::Util.uri_to_path(URI.parse(uri_string)) expect(path).to eq("/#{mixed_utf8}") expect(path.encoding).to eq(Encoding::UTF_8) end end it "should be nil-safe" do expect(Puppet::Util.uri_to_path(nil)).to be_nil end describe "when using platform :posix",:if => Puppet.features.posix? do it "should accept root" do expect(Puppet::Util.uri_to_path(URI.parse('file:/'))).to eq('/') end it "should accept single slash" do expect(Puppet::Util.uri_to_path(URI.parse('file:/foo/bar'))).to eq('/foo/bar') end it "should accept triple slashes" do expect(Puppet::Util.uri_to_path(URI.parse('file:///foo/bar'))).to eq('/foo/bar') end end describe "when using platform :windows", :if => Puppet.features.microsoft_windows? do it "should accept root" do expect(Puppet::Util.uri_to_path(URI.parse('file:/C:/'))).to eq('C:/') end it "should accept single slash" do expect(Puppet::Util.uri_to_path(URI.parse('file:/C:/foo/bar'))).to eq('C:/foo/bar') end it "should accept triple slashes" do expect(Puppet::Util.uri_to_path(URI.parse('file:///C:/foo/bar'))).to eq('C:/foo/bar') end it "should accept file scheme with double slashes as a UNC path" do expect(Puppet::Util.uri_to_path(URI.parse('file://host/share/file'))).to eq('//host/share/file') end end end describe "safe_posix_fork" do let(:pid) { 5501 } before :each do # Most of the things this method does are bad to do during specs. :/ Kernel.stubs(:fork).returns(pid).yields $stdin.stubs(:reopen) $stdout.stubs(:reopen) $stderr.stubs(:reopen) # ensure that we don't really close anything! (0..256).each {|n| IO.stubs(:new) } end it "should close all open file descriptors except stdin/stdout/stderr when /proc/self/fd exists" do # This is ugly, but I can't really think of a better way to do it without # letting it actually close fds, which seems risky fds = [".", "..","0","1","2","3","5","100","1000"] fds.each do |fd| if fd == '.' || fd == '..' next elsif ['0', '1', '2'].include? fd IO.expects(:new).with(fd.to_i).never else IO.expects(:new).with(fd.to_i).returns mock('io', :close) end end Dir.stubs(:foreach).with('/proc/self/fd').multiple_yields(*fds) Puppet::Util.safe_posix_fork end it "should close all open file descriptors except stdin/stdout/stderr when /proc/self/fd doesn't exists" do # This is ugly, but I can't really think of a better way to do it without # letting it actually close fds, which seems risky (0..2).each {|n| IO.expects(:new).with(n).never} (3..256).each { |n| IO.expects(:new).with(n).returns mock('io', :close) } Dir.stubs(:foreach).with('/proc/self/fd') { raise Errno::ENOENT } Puppet::Util.safe_posix_fork end it "should fork a child process to execute the block" do Kernel.expects(:fork).returns(pid).yields Puppet::Util.safe_posix_fork do "Fork this!" end end it "should return the pid of the child process" do expect(Puppet::Util.safe_posix_fork).to eq(pid) end end describe "#which" do let(:base) { File.expand_path('/bin') } let(:path) { File.join(base, 'foo') } before :each do FileTest.stubs(:file?).returns false FileTest.stubs(:file?).with(path).returns true FileTest.stubs(:executable?).returns false FileTest.stubs(:executable?).with(path).returns true end it "should accept absolute paths" do expect(Puppet::Util.which(path)).to eq(path) end it "should return nil if no executable found" do expect(Puppet::Util.which('doesnotexist')).to be_nil end it "should warn if the user's HOME is not set but their PATH contains a ~", :unless => gte_ruby_2_4 do env_path = %w[~/bin /usr/bin /bin].join(File::PATH_SEPARATOR) env = {:HOME => nil, :PATH => env_path} env.merge!({:HOMEDRIVE => nil, :USERPROFILE => nil}) if Puppet.features.microsoft_windows? Puppet::Util.withenv(env) do Puppet::Util::Warnings.expects(:warnonce).once Puppet::Util.which('foo') end end it "should reject directories" do expect(Puppet::Util.which(base)).to be_nil end it "should ignore ~user directories if the user doesn't exist" do # Windows treats *any* user as a "user that doesn't exist", which means # that this will work correctly across all our platforms, and should # behave consistently. If they ever implement it correctly (eg: to do # the lookup for real) it should just work transparently. baduser = 'if_this_user_exists_I_will_eat_my_hat' Puppet::Util.withenv("PATH" => "~#{baduser}#{File::PATH_SEPARATOR}#{base}") do expect(Puppet::Util.which('foo')).to eq(path) end end describe "on POSIX systems" do before :each do Puppet.features.stubs(:posix?).returns true Puppet.features.stubs(:microsoft_windows?).returns false end it "should walk the search PATH returning the first executable" do Puppet::Util.stubs(:get_env).with('PATH').returns(File.expand_path('/bin')) Puppet::Util.stubs(:get_env).with('PATHEXT').returns(nil) expect(Puppet::Util.which('foo')).to eq(path) end end describe "on Windows systems" do let(:path) { File.expand_path(File.join(base, 'foo.CMD')) } before :each do Puppet.features.stubs(:posix?).returns false Puppet.features.stubs(:microsoft_windows?).returns true end describe "when a file extension is specified" do it "should walk each directory in PATH ignoring PATHEXT" do Puppet::Util.stubs(:get_env).with('PATH').returns(%w[/bar /bin].map{|dir| File.expand_path(dir)}.join(File::PATH_SEPARATOR)) Puppet::Util.stubs(:get_env).with('PATHEXT').returns('.FOOBAR') FileTest.expects(:file?).with(File.join(File.expand_path('/bar'), 'foo.CMD')).returns false expect(Puppet::Util.which('foo.CMD')).to eq(path) end end describe "when a file extension is not specified" do it "should walk each extension in PATHEXT until an executable is found" do bar = File.expand_path('/bar') Puppet::Util.stubs(:get_env).with('PATH').returns("#{bar}#{File::PATH_SEPARATOR}#{base}") Puppet::Util.stubs(:get_env).with('PATHEXT').returns(".EXE#{File::PATH_SEPARATOR}.CMD") exts = sequence('extensions') FileTest.expects(:file?).in_sequence(exts).with(File.join(bar, 'foo.EXE')).returns false FileTest.expects(:file?).in_sequence(exts).with(File.join(bar, 'foo.CMD')).returns false FileTest.expects(:file?).in_sequence(exts).with(File.join(base, 'foo.EXE')).returns false FileTest.expects(:file?).in_sequence(exts).with(path).returns true expect(Puppet::Util.which('foo')).to eq(path) end it "should walk the default extension path if the environment variable is not defined" do Puppet::Util.stubs(:get_env).with('PATH').returns(base) Puppet::Util.stubs(:get_env).with('PATHEXT').returns(nil) exts = sequence('extensions') %w[.COM .EXE .BAT].each do |ext| FileTest.expects(:file?).in_sequence(exts).with(File.join(base, "foo#{ext}")).returns false end FileTest.expects(:file?).in_sequence(exts).with(path).returns true expect(Puppet::Util.which('foo')).to eq(path) end it "should fall back if no extension matches" do Puppet::Util.stubs(:get_env).with('PATH').returns(base) Puppet::Util.stubs(:get_env).with('PATHEXT').returns(".EXE") FileTest.stubs(:file?).with(File.join(base, 'foo.EXE')).returns false FileTest.stubs(:file?).with(File.join(base, 'foo')).returns true FileTest.stubs(:executable?).with(File.join(base, 'foo')).returns true expect(Puppet::Util.which('foo')).to eq(File.join(base, 'foo')) end end end end describe "hash symbolizing functions" do let (:myhash) { { "foo" => "bar", :baz => "bam" } } let (:resulthash) { { :foo => "bar", :baz => "bam" } } describe "#symbolizehash" do it "should return a symbolized hash" do newhash = Puppet::Util.symbolizehash(myhash) expect(newhash).to eq(resulthash) end end end context "#replace_file" do subject { Puppet::Util } it { is_expected.to respond_to :replace_file } let :target do target = Tempfile.new("puppet-util-replace-file") target.puts("hello, world") target.flush # make sure content is on disk. target.fsync rescue nil target.close target end it "should fail if no block is given" do expect { subject.replace_file(target.path, 0600) }.to raise_error(/block/) end it "should replace a file when invoked" do # Check that our file has the expected content. expect(File.read(target.path)).to eq("hello, world\n") # Replace the file. subject.replace_file(target.path, 0600) do |fh| fh.puts "I am the passenger..." end # ...and check the replacement was complete. expect(File.read(target.path)).to eq("I am the passenger...\n") end # When running with the same user and group sid, which is the default, # Windows collapses the owner and group modes into a single ACE, resulting # in set(0600) => get(0660) and so forth. --daniel 2012-03-30 modes = [0555, 0660, 0770] modes += [0600, 0700] unless Puppet.features.microsoft_windows? modes.each do |mode| it "should copy 0#{mode.to_s(8)} permissions from the target file by default" do set_mode(mode, target.path) expect(get_mode(target.path)).to eq(mode) subject.replace_file(target.path, 0000) {|fh| fh.puts "bazam" } expect(get_mode(target.path)).to eq(mode) expect(File.read(target.path)).to eq("bazam\n") end end it "should copy the permissions of the source file after yielding on Unix", :if => !Puppet.features.microsoft_windows? do set_mode(0555, target.path) inode = Puppet::FileSystem.stat(target.path).ino yielded = false subject.replace_file(target.path, 0660) do |fh| expect(get_mode(fh.path)).to eq(0600) yielded = true end expect(yielded).to be_truthy expect(Puppet::FileSystem.stat(target.path).ino).not_to eq(inode) expect(get_mode(target.path)).to eq(0555) end it "should be able to create a new file with read-only permissions when it doesn't already exist" do temp_file = Tempfile.new('puppet-util-replace-file') temp_path = temp_file.path temp_file.close temp_file.unlink subject.replace_file(temp_path, 0440) do |fh| fh.puts('some text in there') end expect(File.read(temp_path)).to eq("some text in there\n") expect(get_mode(temp_path)).to eq(0440) end it "should use the default permissions if the source file doesn't exist" do new_target = target.path + '.foo' expect(Puppet::FileSystem.exist?(new_target)).to be_falsey begin subject.replace_file(new_target, 0555) {|fh| fh.puts "foo" } expect(get_mode(new_target)).to eq(0555) ensure Puppet::FileSystem.unlink(new_target) if Puppet::FileSystem.exist?(new_target) end end it "should not replace the file if an exception is thrown in the block" do yielded = false threw = false begin subject.replace_file(target.path, 0600) do |fh| yielded = true fh.puts "different content written, then..." raise "...throw some random failure" end rescue Exception => e if e.to_s =~ /some random failure/ threw = true else raise end end expect(yielded).to be_truthy expect(threw).to be_truthy # ...and check the replacement was complete. expect(File.read(target.path)).to eq("hello, world\n") end {:string => '664', :number => 0664, :symbolic => "ug=rw-,o=r--" }.each do |label,mode| it "should support #{label} format permissions" do new_target = target.path + "#{mode}.foo" expect(Puppet::FileSystem.exist?(new_target)).to be_falsey begin subject.replace_file(new_target, mode) {|fh| fh.puts "this is an interesting content" } expect(get_mode(new_target)).to eq(0664) ensure Puppet::FileSystem.unlink(new_target) if Puppet::FileSystem.exist?(new_target) end end end end describe "#pretty_backtrace" do it "should include lines that don't match the standard backtrace pattern" do line = "non-standard line\n" trace = caller[0..2] + [line] + caller[3..-1] expect(Puppet::Util.pretty_backtrace(trace)).to match(/#{line}/) end it "should include function names" do expect(Puppet::Util.pretty_backtrace).to match(/:in `\w+'/) end it "should work with Windows paths" do expect(Puppet::Util.pretty_backtrace(["C:/work/puppet/c.rb:12:in `foo'\n"])). to eq("C:/work/puppet/c.rb:12:in `foo'") end end describe "#deterministic_rand" do it "should not fiddle with future rand calls" do Puppet::Util.deterministic_rand(123,20) rand_one = rand() Puppet::Util.deterministic_rand(123,20) expect(rand()).not_to eql(rand_one) end if defined?(Random) == 'constant' && Random.class == Class it "should not fiddle with the global seed" do srand(1234) Puppet::Util.deterministic_rand(123,20) expect(srand()).to eql(1234) end # ruby below 1.9.2 variant else it "should set a new global seed" do srand(1234) Puppet::Util.deterministic_rand(123,20) expect(srand()).not_to eql(1234) end end end end puppet-5.5.10/spec/spec_helper.rb0000644005276200011600000001567313417161722016577 0ustar jenkinsjenkins# NOTE: a lot of the stuff in this file is duplicated in the "puppet_spec_helper" in the project # puppetlabs_spec_helper. We should probably eat our own dog food and get rid of most of this from here, # and have the puppet core itself use puppetlabs_spec_helper dir = File.expand_path(File.dirname(__FILE__)) $LOAD_PATH.unshift File.join(dir, 'lib') # Don't want puppet getting the command line arguments for rake or autotest ARGV.clear begin require 'rubygems' rescue LoadError end require 'puppet' # Stub out gettext's `_` and `n_()` methods, which attempt to load translations. # Several of our mocks (mostly around file system interaction) are broken by # FastGettext's implementation of these methods. require 'puppet/gettext/stubs' gem 'rspec', '>=3.1.0' require 'rspec/expectations' require 'rspec/its' require 'rspec/collection_matchers' # So everyone else doesn't have to include this base constant. module PuppetSpec FIXTURE_DIR = File.join(File.expand_path(File.dirname(__FILE__)), "fixtures") unless defined?(FIXTURE_DIR) end require 'pathname' require 'tmpdir' require 'fileutils' require 'puppet_spec/verbose' require 'puppet_spec/files' require 'puppet_spec/settings' require 'puppet_spec/fixtures' require 'puppet_spec/matchers' require 'puppet_spec/unindent' require 'puppet/test/test_helper' Pathname.glob("#{dir}/shared_contexts/*.rb") do |file| require file.relative_path_from(Pathname.new(dir)) end Pathname.glob("#{dir}/shared_behaviours/**/*.rb") do |behaviour| require behaviour.relative_path_from(Pathname.new(dir)) end Pathname.glob("#{dir}/shared_examples/**/*.rb") do |behaviour| require behaviour.relative_path_from(Pathname.new(dir)) end require 'vcr' VCR.configure do |vcr| vcr.cassette_library_dir = File.expand_path('vcr/cassettes', PuppetSpec::FIXTURE_DIR) vcr.hook_into :webmock vcr.configure_rspec_metadata! end RSpec.configure do |config| include PuppetSpec::Fixtures # Examples or groups can selectively tag themselves as broken. # For example; # # rbv = "#{RUBY_VERSION}-p#{RbConfig::CONFIG['PATCHLEVEL']}" # describe "mostly working", :broken => false unless rbv == "1.9.3-p327" do # it "parses a valid IP" do # IPAddr.new("::2:3:4:5:6:7:8") # end # end exclude_filters = {:broken => true} exclude_filters[:benchmark] = true unless ENV['BENCHMARK'] config.filter_run_excluding exclude_filters config.mock_with :mocha tmpdir = Puppet::FileSystem.expand_path(Dir.mktmpdir("rspecrun")) oldtmpdir = Puppet::FileSystem.expand_path(Dir.tmpdir()) ENV['TMPDIR'] = tmpdir Puppet::Test::TestHelper.initialize config.before :all do Puppet::Test::TestHelper.before_all_tests() if ENV['PROFILE'] == 'all' require 'ruby-prof' RubyProf.start end end config.after :all do if ENV['PROFILE'] == 'all' require 'ruby-prof' result = RubyProf.stop printer = RubyProf::CallTreePrinter.new(result) open(File.join(ENV['PROFILEOUT'],"callgrind.all.#{Time.now.to_i}.trace"), "w") do |f| printer.print(f) end end Puppet::Test::TestHelper.after_all_tests() end config.before :each do |test| # Disabling garbage collection inside each test, and only running it at # the end of each block, gives us an ~ 15 percent speedup, and more on # some platforms *cough* windows *cough* that are a little slower. GC.disable # REVISIT: I think this conceals other bad tests, but I don't have time to # fully diagnose those right now. When you read this, please come tell me # I suck for letting this float. --daniel 2011-04-21 Signal.stubs(:trap) # TODO: in a more sane world, we'd move this logging redirection into our TestHelper class. # Without doing so, external projects will all have to roll their own solution for # redirecting logging, and for validating expected log messages. However, because the # current implementation of this involves creating an instance variable "@logs" on # EVERY SINGLE TEST CLASS, and because there are over 1300 tests that are written to expect # this instance variable to be available--we can't easily solve this problem right now. # # redirecting logging away from console, because otherwise the test output will be # obscured by all of the log output @logs = [] if ENV["PUPPET_TEST_LOG_LEVEL"] Puppet::Util::Log.level = ENV["PUPPET_TEST_LOG_LEVEL"].intern end if ENV["PUPPET_TEST_LOG"] Puppet::Util::Log.newdestination(ENV["PUPPET_TEST_LOG"]) m = test.metadata Puppet.notice("*** BEGIN TEST #{m[:file_path]}:#{m[:line_number]}") end Puppet::Util::Log.newdestination(Puppet::Test::LogCollector.new(@logs)) @log_level = Puppet::Util::Log.level base = PuppetSpec::Files.tmpdir('tmp_settings') Puppet[:vardir] = File.join(base, 'var') Puppet[:confdir] = File.join(base, 'etc') Puppet[:codedir] = File.join(base, 'code') Puppet[:logdir] = "$vardir/log" Puppet[:rundir] = "$vardir/run" Puppet[:hiera_config] = File.join(base, 'hiera') FileUtils.mkdir_p Puppet[:statedir] Puppet::Test::TestHelper.before_each_test() end config.after :each do Puppet::Test::TestHelper.after_each_test() # TODO: would like to move this into puppetlabs_spec_helper, but there are namespace issues at the moment. PuppetSpec::Files.cleanup # TODO: this should be abstracted in the future--see comments above the '@logs' block in the # "before" code above. # # clean up after the logging changes that we made before each test. @logs.clear Puppet::Util::Log.close_all Puppet::Util::Log.level = @log_level # This will perform a GC between tests, but only if actually required. We # experimented with forcing a GC run, and that was less efficient than # just letting it run all the time. GC.enable end config.after :suite do # Log the spec order to a file, but only if the LOG_SPEC_ORDER environment variable is # set. This should be enabled on Jenkins runs, as it can be used with Nick L.'s bisect # script to help identify and debug order-dependent spec failures. if ENV['LOG_SPEC_ORDER'] File.open("./spec_order.txt", "w") do |logfile| config.instance_variable_get(:@files_to_run).each { |f| logfile.puts f } end end # return to original tmpdir ENV['TMPDIR'] = oldtmpdir FileUtils.rm_rf(tmpdir) end if ENV['PROFILE'] require 'ruby-prof' def profile result = RubyProf.profile { yield } name = RSpec.current_example.metadata[:full_description].downcase.gsub(/[^a-z0-9_-]/, "-").gsub(/-+/, "-") printer = RubyProf::CallTreePrinter.new(result) open(File.join(ENV['PROFILEOUT'],"callgrind.#{name}.#{Time.now.to_i}.trace"), "w") do |f| printer.print(f) end end config.around(:each) do |example| if ENV['PROFILE'] == 'each' or (example.metadata[:profile] and ENV['PROFILE']) profile { example.run } else example.run end end end end puppet-5.5.10/spec/watchr.rb0000644005276200011600000000533513417161722015570 0ustar jenkinsjenkinsENV["WATCHR"] = "1" ENV['AUTOTEST'] = 'true' def run_comp(cmd) puts cmd results = [] old_sync = $stdout.sync $stdout.sync = true line = [] begin open("| #{cmd}", "r") do |f| until f.eof? do c = f.getc putc c line << c if c == ?\n results << line.join line.clear end end end ensure $stdout.sync = old_sync end results.join end def clear #system("clear") end def growl(message, status) # Strip the color codes message.gsub!(/\[\d+m/, '') growlnotify = `which growlnotify`.chomp return if growlnotify.empty? title = "Watchr Test Results" image = status == :pass ? "autotest/images/pass.png" : "autotest/images/fail.png" options = "-w -n Watchr --image '#{File.expand_path(image)}' -m '#{message}' '#{title}'" system %(#{growlnotify} #{options} &) end def file2specs(file) %w{spec/unit spec/integration}.collect { |d| file.sub('lib/puppet', d).sub(".rb", "_spec.rb") }.find_all { |f| File.exist?(f) } end def file2test(file) result = file.sub('lib/puppet', 'test') return nil unless File.exist?(result) result end def run_spec(command) clear result = run_comp(command).split("\n").last status = result.include?('0 failures') ? :pass : :fail growl result, status end def run_test(command) clear result = run_comp(command).split("\n").last growl result.split("\n").last rescue nil end def run_test_file(file) run_test(%Q(#{file})) end def run_spec_files(files) files = Array(files) return if files.empty? begin # End users can put additional options into ~/.rspec run_spec("rspec --tty #{files.join(' ')}") rescue => detail puts "Failed to load #{files}: #{detail}" end end def run_all_tests run_test("rake unit") end def run_all_specs run_spec_files "spec" end def run_suite run_all_specs run_all_tests end watch('spec/spec_helper.rb') { run_all_specs } watch(%r{^spec/(unit|integration)/.*\.rb$}) { |md| run_spec_files(md[0]) } watch(%r{^lib/puppet/(.*)\.rb$}) { |md| run_spec_files(file2specs(md[0])) if t = file2test(md[0]) run_test_file(t) end } watch(%r{^spec/lib/spec.*}) { |md| run_all_specs } watch(%r{^spec/lib/monkey_patches/.*}) { |md| run_all_specs } watch(%r{test/.+\.rb}) { |md| if md[0] =~ /\/lib\// run_all_tests else run_test_file(md[0]) end } # Ctrl-\ Signal.trap 'QUIT' do puts " --- Running all tests ---\n\n" run_suite end @interrupted = false # Ctrl-C Signal.trap 'INT' do if @interrupted @wants_to_quit = true abort("\n") else puts "Interrupt a second time to quit; wait for rerun of tests" @interrupted = true Kernel.sleep 1.5 # raise Interrupt, nil # let the run loop catch it run_suite end end puppet-5.5.10/locales/0000755005276200011600000000000013417162177014442 5ustar jenkinsjenkinspuppet-5.5.10/locales/config.yaml0000644005276200011600000000235013417161721016565 0ustar jenkinsjenkins--- # This is the project-specific configuration file for setting up # fast_gettext for your project. gettext: # This is used for the name of the .pot and .po files; they will be # called .pot project_name: 'puppet' # This is used in comments in the .pot and .po files to indicate what # project the files belong to and should be a little more descriptive than # package_name: Puppet automation framework # The locale that the default messages in the .pot file are in default_locale: en # The address for sending bug reports. bugs_address: https://tickets.puppetlabs.com # The holder of the copyright. copyright_holder: Puppet, Inc. # Patterns for +Dir.glob+ used to find all files that might contain # translatable content, relative to the project root directory source_files: - 'lib/**/*.rb' # Patterns for +Dir.glob+ used to find all files contained in # `source_files` that should be ignored when searching for translatable # content, relative to the project root directory exclude_files: - 'lib/puppet/pops/types/type_formatter.rb' # The semantic_puppet gem is temporarily vendored (PUP-7114), and it # handles its own translation. - 'lib/puppet/vendor/**/*.rb' puppet-5.5.10/locales/en/0000755005276200011600000000000013417162177015044 5ustar jenkinsjenkinspuppet-5.5.10/locales/en/puppet.po0000644005276200011600000000127613417161721016721 0ustar jenkinsjenkins# English translations for Puppet automation framework package. # Copyright (C) 2017 Puppet, Inc. # This file is distributed under the same license as the Puppet automation framework package. # Automatically generated, 2017. # msgid "" msgstr "" "Project-Id-Version: Puppet automation framework 5.3.3-51-gc11ddbe\n" "\n" "Report-Msgid-Bugs-To: https://tickets.puppetlabs.com\n" "POT-Creation-Date: 2017-11-17 12:09+0000\n" "PO-Revision-Date: 2017-11-17 12:09+0000\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: en\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" puppet-5.5.10/locales/ja/0000755005276200011600000000000013417162177015034 5ustar jenkinsjenkinspuppet-5.5.10/locales/ja/puppet.po0000644005276200011600000205133513417161722016715 0ustar jenkinsjenkins# SOME DESCRIPTIVE TITLE. # Copyright (C) 2018 Puppet, Inc. # This file is distributed under the same license as the Puppet automation framework package. # FIRST AUTHOR , 2018. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: Puppet automation framework 5.5.6-182-g088460f\n" "\n" "Report-Msgid-Bugs-To: https://tickets.puppetlabs.com\n" "POT-Creation-Date: 2018-10-10 17:59+0000\n" "PO-Revision-Date: 2018-10-10 17:59+0000\n" "Last-Translator: Eriko Kashiwagi , 2018\n" "Language-Team: Japanese (Japan) (https://www.transifex.com/puppet/teams/29089/ja_JP/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ja_JP\n" "Plural-Forms: nplurals=1; plural=0;\n" #. TRANSLATORS 'lookup' is a puppet function and should not be translated #: ../lib/hiera/puppet_function.rb:64 msgid "The function '%{class_name}' is deprecated in favor of using 'lookup'." msgstr "関数'%{class_name}'ăŻĺ»ć­˘äşĺ®šă§ă™ă€‚'lookup'ăŚä»Łă‚Źă‚Šă«ä˝żç”¨ă•れăľă™ă€‚" #: ../lib/hiera/puppet_function.rb:65 msgid "" "See https://puppet.com/docs/puppet/%{minor_version}/deprecated_language.html" msgstr "" "詳細ă«ă¤ă„ă¦ăŻă€https://puppet.com/docs/puppet/ " "{minor_version}/deprecated_language.htmlを参照ă—ă¦ăŹă ă•ă„。" #: ../lib/hiera/scope.rb:43 ../lib/puppet/parser/scope.rb:537 msgid "Variable: %{name}" msgstr "変数: %{name}" #: ../lib/hiera/scope.rb:44 ../lib/hiera/scope.rb:46 #: ../lib/puppet/parser/scope.rb:538 ../lib/puppet/parser/scope.rb:540 msgid "Undefined variable '%{name}'; %{reason}" msgstr "定義ă•れă¦ă„ăŞă„変数'%{name}'; %{reason}" #: ../lib/hiera_puppet.rb:14 msgid "" "Could not find data item %{key} in any Hiera data file and no default " "supplied" msgstr "Hieraă‡ăĽă‚żă•ァイă«ă«ă‡ăĽă‚żé …ç›®%{key}ăŚč¦‹ă¤ă‹ă‚Šăľă›ă‚“ă§ă—ăźă€‚ăľăźă€ă‡ă•ă‚©ă«ă値も指定ă•れă¦ă„ăľă›ă‚“。" #: ../lib/hiera_puppet.rb:41 msgid "Please supply a parameter to perform a Hiera lookup" msgstr "Hieraă«ăクアăă—を実行ă™ă‚‹ă«ăŻă€ă‘ă©ăˇăĽă‚żă‚’指定ă—ă¦ăŹă ă•ă„。" #: ../lib/hiera_puppet.rb:74 ../lib/puppet/indirector/hiera.rb:79 msgid "Config file %{hiera_config} not found, using Hiera defaults" msgstr "Configă•ァイă«%{hiera_config}ăŚč¦‹ă¤ă‹ă‚‰ăšă€Hieraă‡ă•ă‚©ă«ăを使用ă—ă¦ă„ăľă™ă€‚" #: ../lib/puppet.rb:4 msgid "Puppet %{version} requires ruby 1.9.3 or greater." msgstr "Puppet %{version}ă§ăŻruby 1.9.3以上ăŚĺż…č¦ă§ă™ă€‚" #: ../lib/puppet.rb:130 msgid "" "Support for ruby version %{version} is deprecated and will be removed in a " "future release. See " "https://puppet.com/docs/puppet/latest/system_requirements.html for a list of" " supported ruby versions." msgstr "" "rubyăăĽă‚¸ă§ăł%{version}ă®ă‚µăťăĽăăŻĺ»ć­˘äşĺ®šă§ă‚りă€ä»ŠĺľŚă®ăŞăŞăĽă‚ąă§ĺ»ć­˘ă•れăľă™ă€‚サăťăĽăă•れă¦ă„ă‚‹rubyă®ăăĽă‚¸ă§ăłă«ă¤ă„ă¦ăŻă€https://puppet.com/docs/puppet/latest/system_requirements.htmlを参照ă—ă¦ăŹă ă•ă„。" #: ../lib/puppet.rb:177 msgid "" "Creating %{name} via Puppet.newtype is deprecated and will be removed in a " "future release. Use Puppet::Type.newtype instead." msgstr "" "Puppet.newtypeă«ă‚ă‚‹%{name}ă®ä˝śćăŻĺ»ć­˘äşĺ®šă§ă‚りă€ä»ŠĺľŚă®ăŞăŞăĽă‚ąă§ĺ»ć­˘ă•れăľă™ă€‚代わりă«Puppet::Type.newtypeを使用ă—ă¦ăŹă ă•ă„。" #: ../lib/puppet.rb:196 msgid "The environmentpath setting cannot be empty or nil." msgstr "environmentpath設定ăŻç©şă‚„ゼă­ă«ăŻă§ăŤăľă›ă‚“。" #: ../lib/puppet/agent.rb:41 msgid "" "Skipping run of %{client_class}; administratively disabled (Reason: '%{disable_message}');\n" "Use 'puppet agent --enable' to re-enable." msgstr "" "%{client_class}ă®ĺ®źčˇŚă‚’スキăă—ă—ă¦ă„ăľă™; 管ç†č€…ă«ă‚る無効化(ç†ç”±: '%{disable_message}'); 'puppet " "agent --enable'を用ă„ă¦ĺ†Ťĺş¦ćś‰ĺŠąĺŚ–ă—ă¦ăŹă ă•ă„。" #: ../lib/puppet/agent.rb:63 msgid "" "Run of %{client_class} already in progress; skipping (%{lockfile_path} " "exists)" msgstr "%{client_class}ă®ĺ®źčˇŚăŻă™ă§ă«é€˛čˇŚä¸­ă§ă™; スキăă—(%{lockfile_path}ăŚĺ­ĺś¨)" #: ../lib/puppet/agent.rb:66 msgid "" "Execution of %{client_class} did not complete within %{runtimeout} seconds " "and was terminated." msgstr "%{client_class}ăŻ%{runtimeout}秒以内ă«ĺ®Śäş†ă—ăŞă‹ăŁăźăźă‚ĺść­˘ă—ăľă—ăźă€‚" #: ../lib/puppet/agent.rb:71 msgid "Could not run %{client_class}: %{detail}" msgstr "%{client_class}を実行ă§ăŤăľă›ă‚“: %{detail}" #: ../lib/puppet/agent.rb:78 msgid "Shutdown/restart in progress (%{status}); skipping run" msgstr "ă‚·ăŁăăă€ă‚¦ăł/再起動ăŚé€˛čˇŚä¸­(%{status}); 実行をスキăă—" #: ../lib/puppet/agent.rb:96 msgid "puppet agent: applying configuration" msgstr "puppet agent: 設定をé©ç”¨" #: ../lib/puppet/agent.rb:127 msgid "Could not create instance of %{client_class}: %{detail}" msgstr "%{client_class}ă®ă‚¤ăłă‚ąă‚żăłă‚ąă‚’作ćă§ăŤăľă›ă‚“: %{detail}" #: ../lib/puppet/agent/disabler.rb:19 msgid "Enabling Puppet." msgstr "Puppetを有効化ă—ă¦ă„ăľă™ă€‚" #: ../lib/puppet/agent/disabler.rb:26 msgid "Disabling Puppet." msgstr "Puppetを無効化ă—ă¦ă„ăľă™ă€‚" #: ../lib/puppet/agent/locker.rb:26 msgid "Failed to acquire lock" msgstr "ă­ăクă®ĺŹ–ĺľ—ă«ĺ¤±ć•—ă—ăľă—ăźă€‚" #. TRANSLATORS 'Puppet::Agent::Locker.running?' is a method name and should #. not be translated #: ../lib/puppet/agent/locker.rb:33 msgid "" "Puppet::Agent::Locker.running? is deprecated as it is inherently unsafe." msgstr "Puppet::Agent::Locker.running?ăŻćś¬čłŞçš„ă«ĺ®‰ĺ…¨ă§ăŻăŞă„ăźă‚ĺ»ć­˘äşĺ®šă§ă™ă€‚" #. TRANSLATORS 'LockError' should not be translated #: ../lib/puppet/agent/locker.rb:35 msgid "" "The only safe way to know if the lock is locked is to try lock and perform " "some action and then handle the LockError that may result." msgstr "" "ă­ăクă•れă¦ă„ă‚‹ă‹ă©ă†ă‹ă‚’確認ă™ă‚‹ĺ”Żä¸€ă®ĺ®‰ĺ…¨ăŞć–ąćł•ăŻă€ă­ăクを試ăżă¦ä˝•らă‹ă®ă‚˘ă‚Żă‚·ă§ăłă‚’実行ă—ă€ăťă®çµćžśç”źăă‚‹LockErroră«ĺŻľĺ‡¦ă™ă‚‹ă“ă¨ă§ă™ă€‚" #: ../lib/puppet/application.rb:231 msgid "Unable to find application '%{application_name}'. %{error}" msgstr "アă—ăŞă‚±ăĽă‚·ă§ăł'%{application_name}'ăŚč¦‹ă¤ă‹ă‚Šăľă›ă‚“。%{error}" #: ../lib/puppet/application.rb:252 msgid "" "Unable to load application class '%{class_name}' from file " "'puppet/application/%{application_name}.rb'" msgstr "" "'puppet/application/%{application_name}.rb'ă‹ă‚‰ă‚˘ă—ăŞă‚±ăĽă‚·ă§ăłă‚Żă©ă‚ą'%{class_name}'ă‚’ă­ăĽă‰ă§ăŤăľă›ă‚“。" #: ../lib/puppet/application.rb:291 msgid "Invalid environment mode '%{mode_name}'" msgstr "無効ăŞenvironmentă˘ăĽă‰'%{mode_name}'" #: ../lib/puppet/application.rb:359 msgid "Could not get application-specific default settings" msgstr "アă—ăŞă‚±ăĽă‚·ă§ăłĺ›şćś‰ă®ă‡ă•ă‚©ă«ă設定を取得ă§ăŤăľă›ă‚“ă§ă—ăźă€‚" #: ../lib/puppet/application.rb:365 msgid "Could not initialize" msgstr "ĺťćśźĺŚ–ă§ăŤăľă›ă‚“ă§ă—ăźă€‚" #: ../lib/puppet/application.rb:366 msgid "Could not parse application options" msgstr "アă—ăŞă‚±ăĽă‚·ă§ăłă‚Şă—ă‚·ă§ăłă‚’č§Łćžă§ăŤăľă›ă‚“ă§ă—ăźă€‚" #: ../lib/puppet/application.rb:367 msgid "Could not prepare for execution" msgstr "実行を準備ă§ăŤăľă›ă‚“ă§ă—ăźă€‚" #: ../lib/puppet/application.rb:370 msgid "" "`puppet %{name}` is deprecated and will be removed in a future release." msgstr "`puppet %{name}`ăŻĺ»ć­˘äşĺ®šă§ă‚りă€ä»ŠĺľŚă®ăŞăŞăĽă‚ąă§ĺ»ć­˘ă•れăľă™ă€‚" #: ../lib/puppet/application.rb:373 msgid "Could not configure routes from %{route_file}" msgstr "%{route_file}ă‹ă‚‰ă®ă«ăĽăを設定ă§ăŤăľă›ă‚“ă§ă—ăźă€‚" #: ../lib/puppet/application.rb:374 msgid "Could not log runtime debug info" msgstr "ă©ăłă‚żă‚¤ă ă‡ăăクć…報をč¨éڞă§ăŤăľă›ă‚“ă§ă—ăźă€‚" #: ../lib/puppet/application.rb:375 msgid "Could not run" msgstr "実行ă§ăŤăľă›ă‚“ă§ă—ăźă€‚" #: ../lib/puppet/application.rb:379 msgid "No valid command or main" msgstr "有効ăŞă‚łăžăłă‰ăľăźăŻmainăŚă‚りăľă›ă‚“。" #: ../lib/puppet/application.rb:492 msgid "No help available for puppet %{app_name}" msgstr "puppet %{app_name}ă§ĺ©ç”¨ă§ăŤă‚‹ăă«ă—ăŚă‚りăľă›ă‚“。" #: ../lib/puppet/application/agent.rb:24 #: ../lib/puppet/application/device.rb:22 msgid "Cancelling startup" msgstr "čµ·ĺ‹•ă‚’ă‚­ăŁăłă‚»ă«ă—ă¦ă„ăľă™ă€‚" #: ../lib/puppet/application/agent.rb:82 msgid "The puppet agent daemon" msgstr "Puppet agentă‡ăĽă˘ăł" #: ../lib/puppet/application/agent.rb:360 msgid "" "Fingerprint asked but no certificate nor certificate request have yet been " "issued" msgstr "ă•ィăłă‚¬ăĽă—ăŞăłăăŚč¦ć±‚ă•れăľă—ăźăŚă€č¨ĽćŽć›¸ă‚„証ćŽć›¸ăŞă‚Żă‚¨ă‚ąăăŚăľă ç™şčˇŚă•れă¦ă„ăľă›ă‚“。" #: ../lib/puppet/application/agent.rb:365 msgid "Could not get fingerprint for digest '%{digest}'" msgstr "ă€ă‚¤ă‚¸ă‚§ă‚ąă'%{digest}'ă®ă•ィăłă‚¬ăĽă—ăŞăłăを取得ă§ăŤăľă›ă‚“ă§ă—ăźă€‚" #: ../lib/puppet/application/agent.rb:389 msgid "Starting Puppet client version %{version}" msgstr "Puppetクă©ă‚¤ă‚˘ăłăăăĽă‚¸ă§ăł%{version}ă‚’čµ·ĺ‹•ă—ă¦ă„ăľă™ă€‚" #: ../lib/puppet/application/agent.rb:405 msgid "The puppet agent command does not take parameters" msgstr "puppet agentコăžăłă‰ăŚă‘ă©ăˇăĽă‚żă‚’取ăŁă¦ă„ăľă›ă‚“。" #: ../lib/puppet/application/apply.rb:35 msgid "Apply Puppet manifests locally" msgstr "Puppetăžă‹ă•ェスăă‚’ă­ăĽă‚«ă«ă«é©ç”¨" #: ../lib/puppet/application/apply.rb:197 #: ../lib/puppet/application/script.rb:134 msgid "Could not find file %{manifest}" msgstr "ă•ァイă«%{manifest}ăŚč¦‹ă¤ă‹ă‚Šăľă›ă‚“ă§ă—ăźă€‚" #: ../lib/puppet/application/apply.rb:198 msgid "Only one file can be applied per run. Skipping %{files}" msgstr "1回ă®ĺ®źčˇŚă§é©ç”¨ă§ăŤă‚‹ă®ăŻ1ă•ァイă«ă®ăżă§ă™ă€‚%{files}をスキăă—ă—ă¦ă„ăľă™ă€‚" #: ../lib/puppet/application/apply.rb:207 #: ../lib/puppet/application/script.rb:141 msgid "Could not find facts for %{node}" msgstr "%{node}ă®factsăŚč¦‹ă¤ă‹ă‚Šăľă›ă‚“ă§ă—ăźă€‚" #: ../lib/puppet/application/apply.rb:216 #: ../lib/puppet/application/script.rb:150 msgid "Could not find node %{node}" msgstr "ăŽăĽă‰%{node}ăŚč¦‹ă¤ă‹ă‚Šăľă›ă‚“ă§ă—ăźă€‚" #. TRANSLATORS "puppet apply" is a program command and should not be #. translated #: ../lib/puppet/application/apply.rb:233 msgid "For puppet apply" msgstr "puppet applyを実行ă™ă‚‹ć™‚" #: ../lib/puppet/application/apply.rb:245 msgid "%{file} is not readable" msgstr "%{file}を読ăżčľĽă‚ăľă›ă‚“。" #: ../lib/puppet/application/apply.rb:318 #: ../lib/puppet/application/script.rb:238 msgid "Exiting" msgstr "終了ă—ă¦ă„ăľă™ă€‚" #: ../lib/puppet/application/apply.rb:346 msgid "Could not deserialize catalog from %{format}: %{detail}" msgstr "%{format}ă‹ă‚‰ă‚«ă‚żă­ă‚°ă‚’ă‡ă‚·ăŞă‚˘ă©ă‚¤ă‚şă§ăŤăľă›ă‚“ă§ă—ăź: %{detail}" #: ../lib/puppet/application/cert.rb:103 msgid "Manage certificates and requests (Deprecated)" msgstr "証ćŽć›¸ă¨ăŞă‚Żă‚¨ă‚ąăă®ç®ˇç†(ĺ»ć­˘äşĺ®š)" #: ../lib/puppet/application/cert.rb:272 msgid "" "Refusing to destroy all certs, provide an explicit list of certs to destroy" msgstr "ă™ăąă¦ă®č¨ĽćŽć›¸ă®ç ´ćŁ„ă‚’ć‹’ĺ¦ă—ă¦ă„ăľă™ă€‚破棄ă«ĺŻľă™ă‚‹č¨ĽćŽć›¸ă®ćŽç¤şçš„ăŞă‚ąăă‚’ćŹç¤şă—ă¦ăŹă ă•ă„。" #: ../lib/puppet/application/cert.rb:336 msgid "" "You must specify the hosts to apply to; valid values are an array or the " "symbol :all" msgstr "é©ç”¨ă™ă‚‹ă›ă‚ąăを指定ă™ă‚‹ĺż…č¦ăŚă‚りăľă™; 有効ăŞĺ€¤ăŻă€é…Ťĺ—ăľăźăŻč¨ĺŹ·:allă§ă™ă€‚" #: ../lib/puppet/application/describe.rb:181 msgid "Display help about resource types" msgstr "ăŞă‚˝ăĽă‚ąĺž‹ă«é–˘ă™ă‚‹ăă«ă—を表示" #: ../lib/puppet/application/device.rb:77 msgid "Manage remote network devices" msgstr "ăŞă˘ăĽăăŤăăăŻăĽă‚Żă‡ăイスを管ç†" #: ../lib/puppet/application/device.rb:216 msgid "resource command requires target" msgstr "ăŞă‚˝ăĽă‚ąă‚łăžăłă‰ă«ăŻă‚żăĽă‚˛ăăăŚĺż…č¦ă§ă™ă€‚" #: ../lib/puppet/application/device.rb:219 msgid "facts command requires target" msgstr "factsコăžăłă‰ă«ăŻă‚żăĽă‚˛ăăăŚĺż…č¦ă§ă™ă€‚" #: ../lib/puppet/application/device.rb:222 msgid "missing argument: --target is required when using --apply" msgstr "引数ăŚă‚りăľă›ă‚“: --applyă®ä˝żç”¨ć™‚ă«ăŻ--targetăŚĺż…č¦ă§ă™" #: ../lib/puppet/application/device.rb:223 msgid "%{file} does not exist, cannot apply" msgstr "%{file}ăŚĺ­ĺś¨ă›ăšă€é©ç”¨ă§ăŤăľă›ă‚“" #: ../lib/puppet/application/device.rb:239 msgid "Target device / certificate '%{target}' not found in %{config}" msgstr "タăĽă‚˛ăăă®ă‡ăイス/証ćŽć›¸'%{target}'ăŚ%{config}ă§č¦‹ă¤ă‹ă‚Šăľă›ă‚“。" #: ../lib/puppet/application/device.rb:241 msgid "No device found in %{config}" msgstr "ă‡ăイスăŚ%{config}ă§č¦‹ă¤ă‹ă‚Šăľă›ă‚“。" #: ../lib/puppet/application/device.rb:263 msgid "" "retrieving resource: %{resource} from %{target} at " "%{scheme}%{url_host}%{port}%{url_path}" msgstr "取得ăŞă‚˝ăĽă‚ą: %{scheme}%{url_host}%{port}%{url_path}ă§%{target}ă‹ă‚‰%{resource}" #: ../lib/puppet/application/device.rb:278 msgid "" "retrieving facts from %{target} at %{scheme}%{url_host}%{port}%{url_path}" msgstr "%{scheme}%{url_host}%{port}%{url_path}ă§%{target}ă‹ă‚‰factを取得ă—ă¦ă„ăľă™ă€‚ " #: ../lib/puppet/application/device.rb:303 msgid "" "starting applying configuration to %{target} at " "%{scheme}%{url_host}%{port}%{url_path}" msgstr "%{scheme}%{url_host}%{port}%{url_path}ă§%{target}ă¸ă®č¨­ĺ®šă®é©ç”¨ă‚’é–‹ĺ§‹ă—ă¦ă„ăľă™ă€‚" #: ../lib/puppet/application/device.rb:340 #: ../lib/puppet/application/resource.rb:196 msgid "You must specify the type to display" msgstr "表示ă™ă‚‹ă‚żă‚¤ă—を指定ă™ă‚‹ĺż…č¦ăŚă‚りăľă™ă€‚" #: ../lib/puppet/application/device.rb:341 #: ../lib/puppet/application/resource.rb:197 msgid "Could not find type %{type}" msgstr "ă•ァイă«ă‚żă‚¤ă—%{type}ăŚč¦‹ă¤ă‹ă‚Šăľă›ă‚“ă§ă—ăźă€‚" #: ../lib/puppet/application/doc.rb:28 msgid "Invalid output format %{arg}" msgstr "無効ăŞĺ‡şĺŠ›ă•ă‚©ăĽăžăă%{arg}" #: ../lib/puppet/application/doc.rb:37 msgid "Invalid output mode %{arg}" msgstr "無効ăŞĺ‡şĺŠ›ă˘ăĽă‰%{arg}" #: ../lib/puppet/application/doc.rb:52 msgid "Generate Puppet references" msgstr "PuppetăŞă•ァă¬ăłă‚ąă‚’生ć" #: ../lib/puppet/application/doc.rb:134 msgid "scanning: %{files}" msgstr "スキăŁăłă—ă¦ă„ăľă™: %{files}" #: ../lib/puppet/application/doc.rb:146 msgid "Could not generate documentation: %{detail}" msgstr "ă‰ă‚­ăĄăˇăłăを生ćă§ăŤăľă›ă‚“ă§ă—ăź: %{detail}" #: ../lib/puppet/application/doc.rb:158 msgid "Could not find reference %{name}" msgstr "ăŞă•ァă¬ăłă‚ą%{name}ăŚč¦‹ă¤ă‹ă‚Šăľă›ă‚“ă§ă—ăźă€‚" #: ../lib/puppet/application/doc.rb:164 msgid "Could not generate reference %{name}: %{detail}" msgstr "ăŞă•ァă¬ăłă‚ą%{name}を生ćă§ăŤăľă›ă‚“ă§ă—ăź: %{detail}" #: ../lib/puppet/application/face_base.rb:34 msgid "I don't know how to render '%{format}'" msgstr "'%{format}'ă®ă¬ăłă€ăŞăłă‚°ć–ąćł•ăŚä¸ŤćŽă§ă™ă€‚" #: ../lib/puppet/application/face_base.rb:56 msgid "Cancelling Face" msgstr "ă•ェイスをキăŁăłă‚»ă«ă—ă¦ă„ăľă™ă€‚" #: ../lib/puppet/application/face_base.rb:128 msgid "'%{face}' has no %{action} action. See `puppet help %{face}`." msgstr "'%{face}'ă«ăŻ%{action}アクシă§ăłăŻă‚りăľă›ă‚“。`puppet help %{face}`を参照ă—ă¦ăŹă ă•ă„。" #: ../lib/puppet/application/face_base.rb:206 msgid "%{face} does not respond to action %{arg}" msgstr "%{face}ăŚă‚˘ă‚Żă‚·ă§ăł%{arg}ă«ĺżśç­”ă—ăľă›ă‚“。" #: ../lib/puppet/application/face_base.rb:239 msgid "" "puppet %{face} %{action} takes %{arg_count} argument, but you gave " "%{given_count}" msgid_plural "" "puppet %{face} %{action} takes %{arg_count} arguments, but you gave " "%{given_count}" msgstr[0] "" "puppet %{face} %{action}ăŚĺŹ–ă‚‹ă®ăŻ%{arg_count}引数ă§ă™ăŚă€%{given_count}ăŚćŚ‡ĺ®šă•れă¦ă„ăľă™ă€‚" #: ../lib/puppet/application/face_base.rb:244 msgid "'puppet %{face}' is deprecated and will be removed in a future release" msgstr "'puppet %{face}'ăŻĺ»ć­˘äşĺ®šă§ă‚りă€ä»ŠĺľŚă®ăŞăŞăĽă‚ąă§ĺ»ć­˘ă•れăľă™ă€‚" #: ../lib/puppet/application/face_base.rb:263 msgid "Try 'puppet help %{face} %{action}' for usage" msgstr "'puppet help %{face} %{action}'を使用ă—ă¦ăżă¦ăŹă ă•ă„。" #: ../lib/puppet/application/filebucket.rb:16 msgid "Store and retrieve files in a filebucket" msgstr "ă•ァイă«ă‚’filebucketă«äżťĺ­ă—ă¦ĺŹ–ĺľ—" #: ../lib/puppet/application/filebucket.rb:203 msgid "You must specify a file to back up" msgstr "ăăクアăă—ă™ă‚‹ă•ァイă«ă‚’指定ă™ă‚‹ĺż…č¦ăŚă‚りăľă™ă€‚" #: ../lib/puppet/application/filebucket.rb:207 msgid "%{file}: no such file" msgstr "%{file}: ă“ă®ă•ァイă«ăŚă‚りăľă›ă‚“。" #: ../lib/puppet/application/filebucket.rb:211 msgid "%{file}: cannot read file" msgstr "%{file}: ă•ァイă«ă‚’読ăżčľĽă‚ăľă›ă‚“。" #: ../lib/puppet/application/filebucket.rb:233 #: ../lib/puppet/application/filebucket.rb:256 msgid "Need exactly two arguments: filebucket diff " msgstr "引数ăŻ2ă¤ĺż…č¦ă§ă™: filebucket diff " #: ../lib/puppet/application/filebucket.rb:253 msgid "Comparing %{checksum_a} %{checksum_b} %{file_a} %{file_b}" msgstr "%{checksum_a} %{checksum_b} %{file_a} %{file_b}を比čĽă—ă¦ă„ăľă™ă€‚" #: ../lib/puppet/application/filebucket.rb:267 msgid "Cancelling" msgstr "ă‚­ăŁăłă‚»ă«ă—ă¦ă„ăľă™ă€‚" #: ../lib/puppet/application/lookup.rb:8 msgid "Run 'puppet lookup --help' for more details" msgstr "詳細ă«ă¤ă„ă¦ăŻă€'puppet lookup --help'を実行ă—ă¦ăŹă ă•ă„。" #: ../lib/puppet/application/lookup.rb:60 msgid "" "The --fact file only accepts yaml and json files.\n" "%{run_help}" msgstr "" "--factă•ァイă«ăŻă€yamlăŠă‚ăłjsonă•ァイă«ă®ăżĺŹ—ă‘入れăľă™ă€‚\n" "%{run_help}" #: ../lib/puppet/application/lookup.rb:99 msgid "Interactive Hiera lookup" msgstr "イăłă‚żă©ă‚Żă†ă‚Łă–ăŞHiera lookup" #: ../lib/puppet/application/lookup.rb:266 msgid "" "The options %{deep_merge_opts} are only available with '--merge deep'\n" "%{run_help}" msgstr "" "オă—ă‚·ă§ăł%{deep_merge_opts}ăŻă€'--merge deep'ă§ă®ăżĺ©ç”¨ă§ăŤăľă™ă€‚\n" "%{run_help}" #: ../lib/puppet/application/lookup.rb:277 msgid "" "The --merge option only accepts %{strategies}, or %{last_strategy}\n" "%{run_help}" msgstr "" "--mergeオă—ă‚·ă§ăłăŻă€%{strategies}ăľăźăŻ%{last_strategy}ă®ăżĺŹ—ă‘入れăľă™ă€‚\n" "%{run_help}" #: ../lib/puppet/application/lookup.rb:302 msgid "No keys were given to lookup." msgstr "参照ă™ă‚‹ă‚­ăĽăŚćŚ‡ĺ®šă•れă¦ă„ăľă›ă‚“。" #: ../lib/puppet/application/lookup.rb:310 msgid "Unknown rendering format '%{format}'" msgstr "未知ă®ă¬ăłă€ăŞăłă‚°ă•ă‚©ăĽăžăă'%{format}'" #: ../lib/puppet/application/lookup.rb:360 ../lib/puppet/face/epp.rb:518 msgid "Incorrect formatted data in %{fact_file} given via the --facts flag" msgstr "--factsă•ă©ă‚°ă«ă‚り%{fact_file}ă§ä¸Ťé©ĺ‡ăŞă•ă‚©ăĽăžăăă®ă‡ăĽă‚żăŚćŚ‡ĺ®šă•れă¦ă„ăľă™ă€‚" #: ../lib/puppet/application/master.rb:29 msgid "The puppet master daemon" msgstr "Puppet masteră‡ăĽă˘ăł" #: ../lib/puppet/application/master.rb:150 msgid "Canceling startup" msgstr "čµ·ĺ‹•ă‚’ă‚­ăŁăłă‚»ă«ă—ă¦ă„ăľă™ă€‚" #: ../lib/puppet/application/master.rb:169 msgid "Could not compile catalog for %{node}" msgstr "%{node}ă®ă‚«ă‚żă­ă‚°ă‚’コăłă‘イă«ă§ăŤăľă›ă‚“ă§ă—ăźă€‚" #: ../lib/puppet/application/master.rb:174 msgid "Failed to compile catalog for node %{node}: %{detail}" msgstr "ăŽăĽă‰%{node}ă®ă‚«ă‚żă­ă‚°ă‚łăłă‘イă«ă«ĺ¤±ć•—ă—ăľă—ăź: %{detail}" #: ../lib/puppet/application/master.rb:194 msgid "Could not change user to %{user}: %{detail}" msgstr "%{user}ă¸ă®ă¦ăĽă‚¶ĺ¤‰ć›´ăŚă§ăŤăľă›ă‚“ă§ă—ăź: %{detail}" #: ../lib/puppet/application/master.rb:198 msgid "" "Could not change user to %{user}. User does not exist and is required to " "continue." msgstr "ă¦ăĽă‚¶ă‚’%{user}ă«ĺ¤‰ć›´ă§ăŤăľă›ă‚“ă§ă—ăźă€‚ă¦ăĽă‚¶ăŻĺ­ĺś¨ă—ăľă›ă‚“ăŚă“ă®ăľăľç¶šă‘ă‚‹ĺż…č¦ăŚă‚りăľă™ă€‚" #: ../lib/puppet/application/master.rb:204 msgid "" "The Rack Puppet master server is deprecated and will be removed in a future " "release. Please use Puppet Server instead. See http://links.puppet.com" "/deprecate-rack-webrick-servers for more information." msgstr "" "Rack Puppet master serverăŻĺ»ć­˘äşĺ®šă§ă‚りă€ä»ŠĺľŚă®ăŞăŞăĽă‚ąă§ĺ»ć­˘ă•れăľă™ă€‚代わりă«Puppet " "Serverを使用ă—ă¦ăŹă ă•ă„。詳細ă«ă¤ă„ă¦ăŻă€http://links.puppet.com/deprecate-rack-webrick-" "serversを参照ă—ă¦ăŹă ă•ă„。" #: ../lib/puppet/application/master.rb:207 msgid "" "The WEBrick Puppet master server is deprecated and will be removed in a " "future release. Please use Puppet Server instead. See " "http://links.puppet.com/deprecate-rack-webrick-servers for more information." msgstr "" "WEBrick Puppet master serverăŻĺ»ć­˘äşĺ®šă§ă‚りă€ä»ŠĺľŚă®ăŞăŞăĽă‚ąă§ĺ»ć­˘ă•れăľă™ă€‚代わりă«Puppet " "Serverを使用ă—ă¦ăŹă ă•ă„。詳細ă«ă¤ă„ă¦ăŻă€http://links.puppet.com/deprecate-rack-webrick-" "serversを参照ă—ă¦ăŹă ă•ă„。" #: ../lib/puppet/application/master.rb:266 msgid "Puppet master is not supported on Microsoft Windows" msgstr "Puppet masterăŻMicrosoft Windowsă§ăŻă‚µăťăĽăă•れă¦ă„ăľă›ă‚“。" #: ../lib/puppet/application/master.rb:315 msgid "Starting Puppet master version %{version}" msgstr "Puppet masterăăĽă‚¸ă§ăł%{version}ă‚’čµ·ĺ‹•ă—ă¦ă„ăľă™ă€‚" #: ../lib/puppet/application/resource.rb:32 msgid "The resource abstraction layer shell" msgstr "resource abstraction layeră‚·ă‚§ă«" #: ../lib/puppet/application/resource.rb:140 msgid "Editing with Yaml output is not supported" msgstr "Yamlアウăă—ăăă«ă‚る編集ăŻă‚µăťăĽăă•れă¦ă„ăľă›ă‚“。" #: ../lib/puppet/application/resource.rb:204 msgid "Invalid parameter setting %{setting}" msgstr "無効ăŞă‘ă©ăˇăĽă‚żč¨­ĺ®š%{setting}" #: ../lib/puppet/application/resource.rb:226 msgid "" "Listing all file instances is not supported. Please specify a file or " "directory, e.g. puppet resource file /etc" msgstr "" "ă™ăąă¦ă®ă•ァイă«ă‚¤ăłă‚ąă‚żăłă‚ąă®ăŞă‚ąă化ăŻă‚µăťăĽăă•れă¦ă„ăľă›ă‚“。ă•ァイă«ăľăźăŻă‡ă‚Łă¬ă‚ŻăăŞă‚’指定ă—ă¦ăŹă ă•ă„。例:puppet resource " "file /etc" #: ../lib/puppet/application/script.rb:20 msgid "Run a puppet manifests as a script without compiling a catalog" msgstr "カタă­ă‚°ă‚’コăłă‘イă«ă›ăšă«ă‚ąă‚ŻăŞă—ăă¨ă—ă¦Puppetăžă‹ă•ェスăを実行ă—ă¦ăŹă ă•ă„。" #: ../lib/puppet/application/script.rb:121 msgid "Bolt must be installed to use the script application" msgstr "スクăŞă—ăアă—ăŞă‚±ăĽă‚·ă§ăłă‚’使用ă™ă‚‹ă«ăŻBoltă®ă‚¤ăłă‚ąăăĽă«ăŚĺż…č¦ă§ă™ă€‚" #: ../lib/puppet/application/script.rb:135 msgid "Only one file can be used per run. Skipping %{files}" msgstr "1回ă®ĺ®źčˇŚă«ä˝żç”¨ă§ăŤă‚‹ă®ăŻ1ă•ァイă«ă®ăżă§ă™ă€‚%{files}をスキăă—ă—ă¦ă„ăľă™ă€‚" #: ../lib/puppet/configurer.rb:21 msgid "Puppet configuration client" msgstr "Puppet設定クă©ă‚¤ă‚˘ăłă" #: ../lib/puppet/configurer.rb:49 msgid "Removing corrupt state file %{file}: %{detail}" msgstr "ç ´ćŤă—ăźă•ァイă«%{file}を削除ă—ă¦ă„ăľă™: %{detail}" #: ../lib/puppet/configurer.rb:54 msgid "Cannot remove %{file}: %{detail}" msgstr "%{file}を削除ă§ăŤăľă›ă‚“: %{detail}" #: ../lib/puppet/configurer.rb:76 msgid "Using cached catalog from environment '%{environment}'" msgstr "ç’°ĺ˘'%{environment}'ă‹ă‚‰ă‚­ăŁăă‚·ăĄă—ăźă‚«ă‚żă­ă‚°ă‚’使用ă—ă¦ă„ăľă™ă€‚" #: ../lib/puppet/configurer.rb:82 msgid "Not using cache on failed catalog" msgstr "失敗ă—ăźă‚«ă‚żă­ă‚°ă§ăŻă‚­ăŁăă‚·ăĄă‚’使用ă§ăŤăľă›ă‚“。" #: ../lib/puppet/configurer.rb:91 msgid "" "Not using cached catalog because its environment '%{catalog_env}' does not " "match '%{local_env}'" msgstr "ç’°ĺ˘'%{catalog_env}'ăŚ'%{local_env}'ă¨ä¸€č‡´ă—ăŞă„ăźă‚ă€ă‚­ăŁăă‚·ăĄă—ăźă‚«ă‚żă­ă‚°ă‚’使用ă§ăŤăľă›ă‚“。" #: ../lib/puppet/configurer.rb:96 ../lib/puppet/configurer.rb:173 msgid "Using cached catalog from environment '%{catalog_env}'" msgstr "ç’°ĺ˘'%{catalog_env}'ă‹ă‚‰ă‚­ăŁăă‚·ăĄă—ăźă‚«ă‚żă­ă‚°ă‚’使用ă—ă¦ă„ăľă™ă€‚" #: ../lib/puppet/configurer.rb:166 msgid "Could not retrieve catalog; skipping run" msgstr "カタă­ă‚°ă‚’取得ă§ăŤăľă›ă‚“ă§ă—ăź; 実行をスキăă—ă—ă¦ă„ăľă™ă€‚" #: ../lib/puppet/configurer.rb:184 msgid "Applied catalog in %{seconds} seconds" msgstr "%{seconds}ç§’ă§ă‚«ă‚żă­ă‚°ă‚’é©ç”¨ă—ăľă—ăźă€‚" #: ../lib/puppet/configurer.rb:225 msgid "Could not select a functional puppet master" msgstr "機č˝ă™ă‚‹puppet masteră‚’é¸ćŠžă§ăŤăľă›ă‚“ă§ă—ăźă€‚" #: ../lib/puppet/configurer.rb:262 msgid "" "Local environment: '%{local_env}' doesn't match the environment of the " "cached catalog '%{catalog_env}', switching agent to '%{catalog_env}'." msgstr "" "ă­ăĽă‚«ă«ç’°ĺ˘: " "'%{local_env}'ăŚă‚­ăŁăă‚·ăĄă—ăźă‚«ă‚żă­ă‚°'%{catalog_env}'ă®ç’°ĺ˘ă¨ä¸€č‡´ă›ăšă€agentă‚’'%{catalog_env}'ă«ĺ‡ă‚Šć›żăă¦ă„ăľă™ă€‚" #: ../lib/puppet/configurer.rb:307 msgid "" "Local environment: '%{local_env}' doesn't match server specified node " "environment '%{node_env}', switching agent to '%{node_env}'." msgstr "" "ă­ăĽă‚«ă«ç’°ĺ˘: " "'%{local_env}'ăŚserver指定ă®ăŽăĽă‰ç’°ĺ˘'%{node_env}'ă¨ä¸€č‡´ă›ăšă€agentă‚’'%{node_env}'ă«ĺ‡ă‚Šć›żăă¦ă„ăľă™ă€‚" #: ../lib/puppet/configurer.rb:312 msgid "Using configured environment '%{env}'" msgstr "設定ă•れăźç’°ĺ˘'%{env}'を使用ă—ă¦ă„ăľă™ă€‚" #: ../lib/puppet/configurer.rb:316 msgid "Unable to fetch my node definition, but the agent run will continue:" msgstr "ăŽăĽă‰ĺ®šçľ©ă‚’取得ă§ăŤăľă›ă‚“ăŚă€agent実行を続行ă—ăľă™:" #: ../lib/puppet/configurer.rb:340 msgid "" "Not using catalog because its environment '%{catalog_env}' does not match " "agent specified environment '%{local_env}' and strict_environment_mode is " "set" msgstr "" "ç’°ĺ˘'%{catalog_env}'ăŚagent指定ă®ç’°ĺ˘'%{local_env}'ă¨ä¸€č‡´ă—ăŞă„ăźă‚ă€ă‚«ă‚żă­ă‚°ă‚’使用ă›ăšă«strict_environment_modeを設定ă—ă¦ă„ăľă™ă€‚" #: ../lib/puppet/configurer.rb:351 msgid "" "Catalog environment didn't stabilize after %{tries} fetches, aborting run" msgstr "%{tries}取得後ă«ă‚«ă‚żă­ă‚°ç’°ĺ˘ăŚĺ®‰ĺ®šă—ăŞă‹ăŁăźăźă‚ă€ĺ®źčˇŚă‚’中止ă—ă¦ă„ăľă™ă€‚" #: ../lib/puppet/configurer.rb:353 msgid "" "Local environment: '%{local_env}' doesn't match server specified environment" " '%{catalog_env}', restarting agent run with environment '%{catalog_env}'" msgstr "" "ă­ăĽă‚«ă«ç’°ĺ˘: " "'%{local_env}'ăŚserver指定ă®ç’°ĺ˘'%{catalog_env}'ă¨ä¸€č‡´ă—ăŞă„ăźă‚ă€ç’°ĺ˘'%{catalog_env}'ă§agent実行を再開ă—ă¦ă„ăľă™ă€‚" #: ../lib/puppet/configurer.rb:372 msgid "Failed to apply catalog: %{detail}" msgstr "カタă­ă‚°é©ç”¨ă«ĺ¤±ć•—ă—ăľă—ăź: %{detail}" #: ../lib/puppet/configurer.rb:421 ../lib/puppet/face/report.rb:47 msgid "Could not send report: %{detail}" msgstr "ă¬ăťăĽăă‚’é€äżˇă§ăŤăľă›ă‚“ă§ă—ăź: %{detail}" #: ../lib/puppet/configurer.rb:430 msgid "Could not save last run local report: %{detail}" msgstr "最後ă®ĺ®źčˇŚă®ă­ăĽă‚«ă«ă¬ăťăĽăă‚’äżťĺ­ă§ăŤăľă›ă‚“ă§ă—ăź: %{detail}" #: ../lib/puppet/configurer.rb:442 msgid "Could not run command from %{setting}: %{detail}" msgstr "%{setting}ă‹ă‚‰ă‚łăžăłă‰ă‚’実行ă§ăŤăľă›ă‚“ă§ă—ăź: %{detail}" #: ../lib/puppet/configurer.rb:460 msgid "Could not retrieve catalog from cache: %{detail}" msgstr "ă‚­ăŁăă‚·ăĄă‹ă‚‰ă‚«ă‚żă­ă‚°ă‚’取得ă§ăŤăľă›ă‚“ă§ă—ăź: %{detail}" #: ../lib/puppet/configurer.rb:480 msgid "Could not retrieve catalog from remote server: %{detail}" msgstr "ăŞă˘ăĽăserveră‹ă‚‰ă‚«ă‚żă­ă‚°ă‚’取得ă§ăŤăľă›ă‚“ă§ă—ăź: %{detail}" #: ../lib/puppet/configurer/downloader.rb:9 msgid "Retrieving %{name}" msgstr "%{name}を取得ă—ă¦ă„ăľă™ă€‚" #: ../lib/puppet/configurer/downloader.rb:20 msgid "Could not retrieve %{name}: %{detail}" msgstr "%{name}を取得ă§ăŤăľă›ă‚“ă§ă—ăź: %{detail}" #: ../lib/puppet/configurer/fact_handler.rb:24 msgid "Could not retrieve local facts: %{detail}" msgstr "ă­ăĽă‚«ă«factsを取得ă§ăŤăľă›ă‚“ă§ă—ăź: %{detail}" #: ../lib/puppet/context.rb:41 msgid "Attempted to pop, but already at root of the context stack." msgstr "popを試行ă—ă¦ă„ăľă™ăŚă€ă™ă§ă«ă‚łăłă†ă‚­ă‚ąăスタăクă®ă«ăĽăă§ă™ă€‚" #: ../lib/puppet/context.rb:55 msgid "Unable to lookup '%{name}'" msgstr "'%{name}'を検索ă§ăŤăľă›ă‚“" #: ../lib/puppet/context.rb:80 msgid "no '%{name}' in ignores %{ignores} at top of %{stack}" msgstr "%{stack}ă®ćś€ä¸Šä˝Ťă®ă‚¤ă‚°ăŽă‚˘%{ignores}ă«'%{name}'ăŚă‚りăľă›ă‚“。" #: ../lib/puppet/context.rb:93 msgid "Mark for '%{name}' already exists" msgstr "'%{name}'ă®ăžăĽă‚ŻăŚă™ă§ă«ĺ­ĺś¨ă—ăľă™ă€‚" #: ../lib/puppet/context.rb:107 msgid "Unknown mark '%{name}'" msgstr "不ćŽăŞăžăĽă‚Ż'%{name}'" #: ../lib/puppet/context/trusted_information.rb:48 msgid "TrustedInformation expected a certificate, but none was given." msgstr "TrustedInformationăŻč¨ĽćŽć›¸ă‚’求ă‚ă¦ă„ăľă™ăŚă€č¨ĽćŽć›¸ăŚćŚ‡ĺ®šă•れă¦ă„ăľă›ă‚“。" #: ../lib/puppet/daemon.rb:82 msgid "Cannot reexec unless ARGV arguments are set" msgstr "ARGV引数ăŚč¨­ĺ®šă•れă¦ă„ăŞă„é™ă‚Šă€ĺ†Ťĺ®źčˇŚă§ăŤăľă›ă‚“。" #: ../lib/puppet/daemon.rb:143 msgid "Daemons must have an agent, server, or both" msgstr "ă‡ăĽă˘ăłă«ăŻă€ĺĽ•ć•°ă€serveră€ăľăźăŻăťă®ä¸ˇć–ąăŚĺż…č¦ă§ă™ă€‚" #: ../lib/puppet/datatypes.rb:133 msgid "Data Type Load Error for type '%{type_name}': %{message}" msgstr "タイă—'%{type_name}'ă®ă‡ăĽă‚żă‚żă‚¤ă—ă­ăĽă‰ă‚¨ă©ăĽ: %{message}" #: ../lib/puppet/datatypes.rb:156 msgid "a data type must have an interface" msgstr "ă‡ăĽă‚żă‚żă‚¤ă—ă«ăŻă‚¤ăłă‚żăĽă•ă‚§ăĽă‚ąăŚĺż…č¦ă§ă™ă€‚" #: ../lib/puppet/datatypes.rb:195 msgid "a data type can only have one interface" msgstr "ă‡ăĽă‚żă‚żă‚¤ă—ă«ăŻ1ă¤ă®ă‚¤ăłă‚żăĽă•ă‚§ăĽă‚ąă ă‘ă—ă‹č¨­ĺ®šă§ăŤăľă›ă‚“。" #: ../lib/puppet/datatypes.rb:200 ../lib/puppet/datatypes.rb:205 msgid "a data type can only have one implementation" msgstr "ă‡ăĽă‚żă‚żă‚¤ă—ă«ăŻ1ă¤ă®ă‚¤ăłă—ăŞăˇăłă†ăĽă‚·ă§ăłă ă‘ă—ă‹č¨­ĺ®šă§ăŤăľă›ă‚“。" #: ../lib/puppet/defaults.rb:126 msgid "Cannot disable unrecognized warning types %{invalid}." msgstr "認č­ă•れă¦ă„ăŞă„警告タイă—%{invalid}を無効ă«ă§ăŤăľă›ă‚“。" #: ../lib/puppet/defaults.rb:127 msgid "Valid values are %{values}." msgstr "有効ăŞĺ€¤ăŻ%{values}ă§ă™ă€‚" #. TRANSLATORS 'data_binding_terminus' is a setting and should not be #. translated #: ../lib/puppet/defaults.rb:473 msgid "Setting 'data_binding_terminus' is deprecated." msgstr "'data_binding_terminus'ă®č¨­ĺ®šăŻĺ»ć­˘äşĺ®šă§ă™ă€‚" #. TRANSLATORS 'hiera' should not be translated #: ../lib/puppet/defaults.rb:475 msgid "Convert custom terminus to hiera 5 API." msgstr "カスタă ă‚żăĽăźăŠă‚ąă‚’Hiera 5 APIă«ĺ¤‰ćŹ›ă—ă¦ăŹă ă•ă„。" #. TRANSLATORS 'environment_data_provider' is a setting and should not be #. translated #: ../lib/puppet/defaults.rb:629 msgid "Setting 'environment_data_provider' is deprecated." msgstr "'environment_data_provider'ă®č¨­ĺ®šăŻĺ»ć­˘äşĺ®šă§ă™ă€‚" #: ../lib/puppet/defaults.rb:731 msgid "Certificate names must be lower case" msgstr "証ćŽć›¸ĺŤăŻĺ°Źć–‡ĺ­—ă§ăŞă‘れă°ăŞă‚Šăľă›ă‚“。" #: ../lib/puppet/defaults.rb:972 ../lib/puppet/settings/enum_setting.rb:13 #: ../lib/puppet/settings/symbolic_enum_setting.rb:14 msgid "" "Invalid value '%{value}' for parameter %{name}. Allowed values are " "'%{allowed_values}'" msgstr "ă‘ă©ăˇăĽă‚ż%{name}ă«é–˘ă—ă¦ç„ˇĺŠąăŞĺ€¤'%{value}'。許可ă•れă¦ă„る値ăŻ'%{allowed_values}'ă§ă™ă€‚" #: ../lib/puppet/defaults.rb:1034 ../lib/puppet/defaults.rb:1061 msgid "" "The 'caprivatedir' setting is deprecated and will be removed in Puppet 6." msgstr "'caprivatedir'ă®č¨­ĺ®šăŻĺ»ć­˘äşĺ®šă§ă‚りă€Puppet 6ă§ĺ»ć­˘ă•れăľă™ă€‚" #: ../lib/puppet/defaults.rb:1531 ../lib/puppet/defaults.rb:1546 msgid "Attempted to set both server and server_list." msgstr "serveră¨server_listă®ä¸ˇć–ąă‚’設定ă—ă‚ă†ă¨ă—ăľă—ăźă€‚" #: ../lib/puppet/defaults.rb:1532 ../lib/puppet/defaults.rb:1547 msgid "Server setting will not be used." msgstr "serveră®č¨­ĺ®šăŻä˝żç”¨ă•れăľă›ă‚“。" #: ../lib/puppet/defaults.rb:1817 ../lib/puppet/settings.rb:1167 msgid "Setting %{name} is deprecated." msgstr "%{name}ă®č¨­ĺ®šăŻĺ»ć­˘äşĺ®šă§ă™ă€‚" #. TRANSLATORS 'pluginsync' is a setting and should not be translated #: ../lib/puppet/defaults.rb:1868 msgid "Setting 'pluginsync' is deprecated." msgstr "'pluginsync'ă®č¨­ĺ®šăŻĺ»ć­˘äşĺ®šă§ă™ă€‚" #: ../lib/puppet/error.rb:69 msgid "Could not parse for environment %{environment}: %{message}" msgstr "environment%{environment}ă‚’č§Łćžă§ăŤăľă›ă‚“ă§ă—ăź: %{message}" #: ../lib/puppet/error.rb:70 ../lib/puppet/parser/compiler.rb:41 msgid "%{message} on node %{node}" msgstr "ăŽăĽă‰%{node}ă§ă®%{message}" #: ../lib/puppet/external/pson/common.rb:46 msgid "can't find const for unregistered document type %{path}" msgstr "登録ă•れă¦ă„ăŞă„ă‰ă‚­ăĄăˇăłăタイă—%{path}ă®constăŚč¦‹ă¤ă‹ă‚Šăľă›ă‚“。" #: ../lib/puppet/external/pson/common.rb:311 msgid "exceed depth limit" msgstr "ć·±ă•ă®ä¸Šé™ă‚’č¶…ăă¦ă„ăľă™ă€‚" #: ../lib/puppet/face/ca.rb:5 ../lib/puppet/face/certificate.rb:6 #: ../lib/puppet/face/config.rb:7 ../lib/puppet/face/epp.rb:8 #: ../lib/puppet/face/facts.rb:6 ../lib/puppet/face/generate.rb:7 #: ../lib/puppet/face/help.rb:9 ../lib/puppet/face/key.rb:5 #: ../lib/puppet/face/man.rb:8 ../lib/puppet/face/module.rb:9 #: ../lib/puppet/face/node.rb:4 ../lib/puppet/face/parser.rb:6 #: ../lib/puppet/face/plugin.rb:6 ../lib/puppet/face/report.rb:5 #: ../lib/puppet/face/resource.rb:5 ../lib/puppet/face/status.rb:5 msgid "Apache 2 license; see COPYING" msgstr "Apache 2ă©ă‚¤ă‚»ăłă‚ą; COPYINGを参照ă—ă¦ăŹă ă•ă„。" #: ../lib/puppet/face/ca.rb:7 msgid "Local Puppet Certificate Authority management." msgstr "ă­ăĽă‚«ă«PuppetčŞŤč¨Ľĺ±€ç®ˇç† " #: ../lib/puppet/face/ca.rb:17 msgid "List certificates and/or certificate requests." msgstr "証ćŽć›¸ăŠă‚ăłč¨ĽćŽć›¸ăŞă‚Żă‚¨ă‚ąăă‚’ăŞă‚ąăă«ă™ă‚‹ă€‚" #: ../lib/puppet/face/ca.rb:26 msgid "Include all certificates and requests." msgstr "ă™ăąă¦ă®č¨ĽćŽć›¸ăŠă‚ăłăŞă‚Żă‚¨ă‚ąăă‚’ĺ«ă‚る。" #: ../lib/puppet/face/ca.rb:30 msgid "Include pending certificate signing requests." msgstr "保留中ă®č¨ĽćŽć›¸ç˝˛ĺŤăŞă‚Żă‚¨ă‚ąăă‚’ĺ«ă‚る。" #: ../lib/puppet/face/ca.rb:34 msgid "Include signed certificates." msgstr "署ĺŤć¸ăżă®č¨ĽćŽć›¸ă‚’ĺ«ă‚る。" #: ../lib/puppet/face/ca.rb:37 ../lib/puppet/face/ca.rb:213 msgid "ALGORITHM" msgstr "アă«ă‚´ăŞă‚şă " #: ../lib/puppet/face/ca.rb:38 ../lib/puppet/face/ca.rb:214 msgid "The hash algorithm to use when displaying the fingerprint" msgstr "ă•ィăłă‚¬ăĽă—ăŞăłăを表示ă™ă‚‹éš›ă«ä˝żç”¨ă™ă‚‹ăŹăă‚·ăĄă‚˘ă«ă‚´ăŞă‚şă " #: ../lib/puppet/face/ca.rb:41 msgid "PATTERN" msgstr "ă‘タăĽăł" #: ../lib/puppet/face/ca.rb:42 msgid "Only list if the subject matches PATTERN." msgstr "対象ăŚă‘タăĽăłă«ä¸€č‡´ă™ă‚‹ĺ ´ĺă®ăżăŞă‚ąăă«ă™ă‚‹ă€‚" #. TRANSLATORS "CA" stands for "certificate authority" #. TRANSLATORS "CA" stands for "certificate authority" #. TRANSLATORS "CA" stands for "certificate authority" #: ../lib/puppet/face/ca.rb:54 ../lib/puppet/face/ca.rb:106 #: ../lib/puppet/face/ca.rb:119 ../lib/puppet/face/ca.rb:145 #: ../lib/puppet/face/ca.rb:176 ../lib/puppet/face/ca.rb:200 #: ../lib/puppet/face/ca.rb:219 ../lib/puppet/face/ca.rb:238 msgid "Not a CA" msgstr "CAă§ăŻă‚りăľă›ă‚“。" #. TRANSLATORS "CA" stands for "certificate authority" #. TRANSLATORS "CA" stands for "certificate authority" #. TRANSLATORS "CA" stands for "certificate authority" #: ../lib/puppet/face/ca.rb:57 ../lib/puppet/face/ca.rb:82 #: ../lib/puppet/face/ca.rb:108 ../lib/puppet/face/ca.rb:121 #: ../lib/puppet/face/ca.rb:147 ../lib/puppet/face/ca.rb:178 #: ../lib/puppet/face/ca.rb:202 ../lib/puppet/face/ca.rb:222 #: ../lib/puppet/face/ca.rb:241 msgid "Unable to fetch the CA" msgstr "CAを取得ă§ăŤăľă›ă‚“。" #: ../lib/puppet/face/ca.rb:104 msgid "Destroy named certificate or pending certificate request." msgstr "ĺŤĺ‰Ťă®ä»ă„ăźč¨ĽćŽć›¸ăľăźăŻćŹĺ‡şă•れă¦ă„る証ćŽć›¸ăŞă‚Żă‚¨ă‚ąăを破棄。" #: ../lib/puppet/face/ca.rb:117 msgid "Add certificate to certificate revocation list." msgstr "証ćŽć›¸ă‚’証ćŽć›¸ć’¤ĺ›žăŞă‚ąăă«čż˝ĺŠ ă€‚" #: ../lib/puppet/face/ca.rb:132 msgid "Nothing was revoked" msgstr "無効化ă•れăźă‚‚ă®ăŻă‚りăľă›ă‚“。" #: ../lib/puppet/face/ca.rb:138 msgid "Generate a certificate for a named client." msgstr "ĺŤĺ‰Ťă®ä»ă„ăźă‚Żă©ă‚¤ă‚˘ăłăă®č¨ĽćŽć›¸ă‚’作ć。" #: ../lib/puppet/face/ca.rb:139 ../lib/puppet/face/certificate.rb:56 msgid "NAMES" msgstr "ĺŤĺ‰Ť" #: ../lib/puppet/face/ca.rb:140 ../lib/puppet/face/certificate.rb:57 msgid "Additional DNS names to add to the certificate request" msgstr "証ćŽć›¸ăŞă‚Żă‚¨ă‚ąăă«čż˝ĺŠ ă™ă‚‹ĺĄă®DNSĺŤ" #: ../lib/puppet/face/ca.rb:155 msgid "%{host} already has a certificate request; use sign instead" msgstr "%{host}ă«ăŻă™ă§ă«č¨ĽćŽć›¸ăŞă‚Żă‚¨ă‚ąăăŚă‚りăľă™; 代わりă«ç˝˛ĺŤă‚’使用ă—ă¦ăŹă ă•ă„。" #: ../lib/puppet/face/ca.rb:161 msgid "%{host} already has a certificate" msgstr "%{host}ă«ăŻă™ă§ă«č¨ĽćŽć›¸ăŚă‚りăľă™ă€‚" #: ../lib/puppet/face/ca.rb:170 msgid "Sign an outstanding certificate request." msgstr "未処ç†ă®č¨ĽćŽć›¸ăŞă‚Żă‚¨ă‚ąăă«ç˝˛ĺŤă—ă¦ăŹă ă•ă„。" #: ../lib/puppet/face/ca.rb:172 ../lib/puppet/face/certificate.rb:117 msgid "Whether or not to accept DNS alt names in the certificate request" msgstr "証ćŽć›¸ăŞă‚Żă‚¨ă‚ąăă§DNSă®ĺĄĺŤă‚’受ç†ă™ă‚‹ă‹ă©ă†ă‹ă‚’指定。" #: ../lib/puppet/face/ca.rb:198 msgid "Print the full-text version of a host's certificate." msgstr "ă›ă‚ąăă®č¨ĽćŽć›¸ă®ă†ă‚­ă‚ąă全文を印ĺ·ă€‚" #. TRANSLATORS "DIGEST" refers to a hash algorithm #: ../lib/puppet/face/ca.rb:212 msgid "" "Print the DIGEST (defaults to the signing algorithm) fingerprint of a host's" " certificate." msgstr "ă›ă‚ąăă®č¨ĽćŽć›¸ă®ă€ă‚¤ă‚¸ă‚§ă‚ąă(アă«ă‚´ăŞă‚şă ç˝˛ĺŤă®ă‡ă•ă‚©ă«ă)を印ĺ·ă€‚" #: ../lib/puppet/face/ca.rb:260 msgid "Could not verify %{host}: %{error}" msgstr "%{host}を確認ă§ăŤăľă›ă‚“ă§ă—ăź: %{error}" #: ../lib/puppet/face/catalog.rb:7 msgid "Compile, save, view, and convert catalogs." msgstr "カタă­ă‚°ă‚’コăłă‘イă«ă€äżťĺ­ă€čˇ¨ç¤şă€ĺ¤‰ćŹ›" #: ../lib/puppet/face/catalog/select.rb:4 msgid "Retrieve a catalog and filter it for resources of a given type." msgstr "カタă­ă‚°ă‚’取得ă—ă€ćŚ‡ĺ®šă•れăźă‚żă‚¤ă—ă®ăŞă‚˝ăĽă‚ąă‚’ă•ィă«ă‚żăŞăłă‚°ă€‚" #: ../lib/puppet/face/catalog/select.rb:5 msgid " " msgstr " " #: ../lib/puppet/face/catalog/select.rb:6 msgid "" " A list of resource references (\"Type[title]\"). When used from the API,\n" " returns an array of Puppet::Resource objects excised from a catalog.\n" msgstr "" " ăŞă‚˝ăĽă‚ąăŞă•ァă¬ăłă‚ąă®ăŞă‚ąă(\"Type[title]\")。APIă‹ă‚‰ä˝żç”¨ă™ă‚‹ĺ ´ĺăŻă€\n" " カタă­ă‚°ă‹ă‚‰ć‘出ă•れăźPuppet::Resourceオă–ジェクăă®é…Ťĺ—ă‚’čż”ă—ăľă™ă€‚\n" #: ../lib/puppet/face/catalog/select.rb:43 msgid "no matching resources found" msgstr "一致ă™ă‚‹ăŞă‚˝ăĽă‚ąăŚč¦‹ă¤ă‹ă‚Šăľă›ă‚“ă§ă—ăźă€‚" #: ../lib/puppet/face/certificate.rb:8 msgid "Provide access to the CA for certificate management." msgstr "CAă«č¨ĽćŽć›¸ç®ˇç†ă®ă‚˘ă‚Żă‚»ă‚ąć¨©ă‚’ćŹäľ› 。" #: ../lib/puppet/face/certificate.rb:17 msgid "LOCATION" msgstr "ă­ă‚±ăĽă‚·ă§ăł" #: ../lib/puppet/face/certificate.rb:19 msgid "Which certificate authority to use (local or remote)." msgstr "使用ă™ă‚‹čŞŤč¨Ľĺ±€(ă­ăĽă‚«ă«ăľăźăŻăŞă˘ăĽă)" #: ../lib/puppet/face/certificate.rb:29 msgid "Valid values for ca-location are 'remote', 'local', 'only'." msgstr "CAă­ă‚±ăĽă‚·ă§ăłă®ćś‰ĺŠąăŞĺ€¤ăŻă€'remote'ă€'local'ă€'only'ă§ă™ă€‚" #: ../lib/puppet/face/certificate.rb:36 msgid "Generate a new certificate signing request." msgstr "新規証ćŽć›¸ç˝˛ĺŤăŞă‚Żă‚¨ă‚ąăを作ć。" #: ../lib/puppet/face/certificate.rb:37 ../lib/puppet/face/certificate.rb:106 #: ../lib/puppet/face/certificate.rb:146 ../lib/puppet/face/certificate.rb:157 #: ../lib/puppet/face/node.rb:19 msgid "" msgstr "" #: ../lib/puppet/face/certificate.rb:73 msgid "Can't specify both --dns_alt_names and --dns-alt-names" msgstr "--dns_alt_namesă¨--dns-alt-namesă®ă©ăˇă‚‰ă‚‚指定ă§ăŤăľă›ă‚“。" #: ../lib/puppet/face/certificate.rb:89 msgid "List all certificate signing requests." msgstr "ă™ăąă¦ă®č¨ĽćŽć›¸ç˝˛ĺŤăŞă‚Żă‚¨ă‚ąăă‚’ăŞă‚ąăă«ă™ă‚‹ă€‚" #: ../lib/puppet/face/certificate.rb:105 msgid "Sign a certificate signing request for HOST." msgstr "ă›ă‚ąăă®č¨ĽćŽć›¸ç˝˛ĺŤăŞă‚Żă‚¨ă‚ąăă«ç˝˛ĺŤă€‚" #: ../lib/puppet/face/certificate.rb:124 msgid "--allow-dns-alt-names may not be specified with a remote CA" msgstr "--allow-dns-alt-namesăŻăŞă˘ăĽăCAă§ăŻćŚ‡ĺ®šă•れăŞă„可č˝ć€§ăŚă‚りăľă™ă€‚" #: ../lib/puppet/face/certificate.rb:133 #: ../lib/puppet/indirector/certificate_status/file.rb:15 msgid "This process is not configured as a certificate authority" msgstr "ă“ă®ă—ă­ă‚»ă‚ąăŻčŞŤč¨Ľĺ±€ă¨ă—ă¦č¨­ĺ®šă•れă¦ă„ăľă›ă‚“。" #: ../lib/puppet/face/certificate.rb:145 msgid "Retrieve a certificate." msgstr "証ćŽć›¸ă‚’取得。" #: ../lib/puppet/face/certificate.rb:156 msgid "Delete a certificate." msgstr "証ćŽć›¸ă‚’削除。" #: ../lib/puppet/face/certificate_request.rb:7 msgid "Manage certificate requests." msgstr "証ćŽć›¸ăŞă‚Żă‚¨ă‚ąăを管ç†" #: ../lib/puppet/face/certificate_revocation_list.rb:7 msgid "Manage the list of revoked certificates." msgstr "無効化ă•れăźč¨ĽćŽć›¸ă®ăŞă‚ąăを管ç†" #: ../lib/puppet/face/config.rb:9 msgid "Interact with Puppet's settings." msgstr "Puppetă®č¨­ĺ®šă¨ć…報交換。" #: ../lib/puppet/face/config.rb:17 msgid "SECTION_NAME" msgstr "SECTION_NAME(セクシă§ăłĺŤ)" #: ../lib/puppet/face/config.rb:19 msgid "The section of the configuration file to interact with." msgstr "ć…報をやりă¨ă‚Šă™ă‚‹ć§‹ćă•ァイă«ă®ă‚»ă‚Żă‚·ă§ăł" #: ../lib/puppet/face/config.rb:37 msgid "Examine Puppet's current settings." msgstr "Puppetă®çŹľĺś¨ă®č¨­ĺ®šă‚’検証。" #: ../lib/puppet/face/config.rb:38 msgid "(all | [ ...]" msgstr "(all | [ ...]" #: ../lib/puppet/face/config.rb:83 msgid "New environment loaders generated from the requested section." msgstr "ăŞă‚Żă‚¨ă‚ąăă•れăźă‚»ă‚Żă‚·ă§ăłă‹ă‚‰ç”źćă•れăźć–°č¦Źç’°ĺ˘ă­ăĽă€" #: ../lib/puppet/face/config.rb:137 msgid "No section specified; defaulting to '%{section_name}'." msgstr "セクシă§ăłăŚćŚ‡ĺ®šă•れă¦ă„ăľă›ă‚“。ă‡ă•ă‚©ă«ă値ăŻ'%{section_name}'。" #. TRANSLATORS '--section' is a command line option and should not be #. translated #: ../lib/puppet/face/config.rb:140 msgid "Set the config section by using the `--section` flag." msgstr "`--section`ă•ă©ă‚°ă‚’使用ă—ă€configセクシă§ăłă‚’設定ă—ăľă™ă€‚" #. TRANSLATORS `puppet config --section user print foo` is a command line #. example and should not be translated #: ../lib/puppet/face/config.rb:142 msgid "For example, `puppet config --section user print foo`." msgstr "äľ‹ăă°ă€`puppet config --section user print foo`ăŞă©ă§ă™ă€‚" #: ../lib/puppet/face/config.rb:143 msgid "" "For more information, see " "https://puppet.com/docs/puppet/latest/configuration.html" msgstr "" "詳細ă«ă¤ă„ă¦ăŻă€https://puppet.com/docs/puppet/latest/configuration.htmlを参照ă—ă¦ăŹă ă•ă„。" #: ../lib/puppet/face/config.rb:150 msgid "" "Resolving settings from section '%{section_name}' in environment " "'%{environment_name}'" msgstr "ç’°ĺ˘'%{environment_name}'ă§ă‚»ă‚Żă‚·ă§ăł'%{section_name}'ă‹ă‚‰č¨­ĺ®šă‚’解決ă—ă¦ă„ăľă™" #: ../lib/puppet/face/config.rb:155 msgid "Set Puppet's settings." msgstr "Puppetを設定。" #: ../lib/puppet/face/config.rb:156 msgid "[setting_name] [setting_value]" msgstr "[setting_name] [setting_value]" #: ../lib/puppet/face/config.rb:184 msgid "" "The environment should be set in either the `[user]`, `[agent]`, or `[master]`\n" "section. Variables set in the `[agent]` section are used when running\n" "`puppet agent`. Variables set in the `[user]` section are used when running\n" "various other puppet subcommands, like `puppet apply` and `puppet module`; these\n" "require the defined environment directory to exist locally. Set the config\n" "section by using the `--section` flag. For example,\n" "`puppet config --section user set environment foo`. For more information, see\n" "https://puppet.com/docs/puppet/latest/configuration.html#environment\n" msgstr "" "environmentăŻă€`[user]`ă€`[agent]`ă€ăľăźăŻ`[master]`セクシă§ăłă§č¨­ĺ®šă™ă‚‹ĺż…č¦ăŚă‚りăľă™ă€‚`[agent]`セクシă§ăłă§č¨­ĺ®šă•れăźĺ¤‰ć•°ăŻă€`puppet" " agent`ă®ĺ®źčˇŚć™‚ă«ä˝żç”¨ă•れăľă™ă€‚`[user]`セクシă§ăłă§č¨­ĺ®šă•れăźĺ¤‰ć•°ăŻă€ăťă®ä»–ă®ĺ„種Puppetサă–コăžăłă‰(`puppet " "apply`ă‚„`puppet " "module`ăŞă©)ă®ĺ®źčˇŚć™‚ă«ä˝żç”¨ă•れăľă™ă€‚ă“れらăŚă­ăĽă‚«ă«ă§ĺ­ĺś¨ă™ă‚‹ă«ăŻă€ĺ®šçľ©ć¸ăżă®environmentă‡ă‚Łă¬ă‚ŻăăŞăŚĺż…č¦ă§ă™ă€‚`--section`ă•ă©ă‚°ă‚’使用ă—ă€configセクシă§ăłă‚’設定ă—ăľă™ă€‚äľ‹ăă°ă€`puppet" " config --section user set environment " "foo`ăŞă©ă§ă™ă€‚詳細ă«ă¤ă„ă¦ăŻă€https://puppet.com/docs/puppet/latest/configuration.html#environmentを参照ă—ă¦ăŹă ă•ă„。\n" #: ../lib/puppet/face/config.rb:212 msgid "Delete a Puppet setting." msgstr "Puppet設定を削除。" #: ../lib/puppet/face/config.rb:213 msgid "(" msgstr "(" #. TRANSLATORS 'main' is a specific section name and should not be translated #: ../lib/puppet/face/config.rb:250 msgid "Deleted setting from '%{section_name}': '%{setting_string}'" msgstr "'%{section_name}'ă‹ă‚‰č¨­ĺ®šăŚĺ‰Šé™¤ă•れăľă—ăź: '%{setting_string}'" #: ../lib/puppet/face/config.rb:253 msgid "" "No setting found in configuration file for section '%{section_name}' setting" " name '%{name}'" msgstr "セクシă§ăł'%{section_name}'設定ĺŤ'%{name}'ă®č¨­ĺ®šă•ァイă«ă«č¨­ĺ®šăŚč¦‹ă¤ă‹ă‚Šăľă›ă‚“ă§ă—ăźă€‚" #. TRANSLATORS the 'puppet.conf' is a specific file and should not be #. translated #: ../lib/puppet/face/config.rb:260 msgid "The puppet.conf file does not exist %{puppet_conf}" msgstr "puppet.confă•ァイă«ăŚĺ­ĺś¨ă—ăľă›ă‚“%{puppet_conf}" #: ../lib/puppet/face/epp.rb:10 msgid "Interact directly with the EPP template parser/renderer." msgstr "EPPă†ăłă—ă¬ăĽăă‘ăĽă‚µ/ă¬ăłă€ă©ăĽă¨ç›´ćŽĄć…報をやりă¨ă‚Šă™ă‚‹ă€‚" #: ../lib/puppet/face/epp.rb:13 msgid "Validate the syntax of one or more EPP templates." msgstr "1ă¤ăľăźăŻč¤‡ć•°ă®EPPă†ăłă—ă¬ăĽăă®ć§‹ć–‡ă‚’検証。" #: ../lib/puppet/face/epp.rb:14 msgid "[